diff --git a/Cargo.lock b/Cargo.lock index aecc5f616..162287f22 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -402,6 +402,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -433,6 +439,19 @@ dependencies = [ "typenum", ] +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + [[package]] name = "digest" version = "0.10.7" @@ -1066,6 +1085,7 @@ dependencies = [ "log", "mozim", "netlink-packet-core", + "netlink-packet-netfilter", "netlink-packet-route", "netlink-sys", "nftables", @@ -1095,6 +1115,17 @@ dependencies = [ "paste", ] +[[package]] +name = "netlink-packet-netfilter" +version = "0.2.0" +source = "git+https://github.com/shivkr6/netlink-packet-netfilter.git?branch=conntrack-new#41c8cb88fefb99981f80534b4ac353a383e367bc" +dependencies = [ + "bitflags", + "derive_more", + "libc", + "netlink-packet-core", +] + [[package]] name = "netlink-packet-route" version = "0.25.1" @@ -1526,6 +1557,15 @@ dependencies = [ "tokio", ] +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "1.1.2" @@ -1576,6 +1616,12 @@ dependencies = [ "syn", ] +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" version = "1.0.228" diff --git a/Cargo.toml b/Cargo.toml index 07a944263..651efa697 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,7 @@ nix = { version = "0.30.1", features = ["net", "sched", "signal", "socket", "use rand = "0.9.2" sha2 = "0.10.9" netlink-packet-route = "0.25.1" +netlink-packet-netfilter = { git = "https://github.com/shivkr6/netlink-packet-netfilter.git", branch = "conntrack-new" } netlink-packet-core = "0.8.1" netlink-sys = "0.8.7" nftables = "0.6.3" diff --git a/src/network/bridge.rs b/src/network/bridge.rs index 34990c0cc..a395f3649 100644 --- a/src/network/bridge.rs +++ b/src/network/bridge.rs @@ -5,7 +5,6 @@ use std::{ os::fd::BorrowedFd, }; -use crate::dns::aardvark::SafeString; use crate::network::core_utils::get_default_route_interface; use crate::network::dhcp::{dhcp_teardown, get_dhcp_lease}; use crate::network::netlink::Socket; @@ -22,6 +21,7 @@ use crate::{ }, network::{constants, sysctl::disable_ipv6_autoconf, types}, }; +use crate::{dns::aardvark::SafeString, network::netlink_netfilter::flush_udp_conntrack}; use ipnet::IpNet; use log::{debug, error}; use netlink_packet_route::address::{AddressAttribute, AddressScope}; @@ -542,7 +542,18 @@ impl<'a> Bridge<'a> { self.info.firewall.setup_network(sn, &system_dbus)?; + let port_mappings = spf.port_mappings; + let container_ip_v4 = spf.container_ip_v4; + let container_ip_v6 = spf.container_ip_v6; + self.info.firewall.setup_port_forward(spf, &system_dbus)?; + + if let Some(port_mappings) = port_mappings { + // Flush stale UDP conntrack entries to prevent dropped packets. + // See the function's doc comment for more details. + flush_udp_conntrack(port_mappings, container_ip_v4, container_ip_v6)?; + } + Ok(()) } @@ -639,7 +650,18 @@ impl<'a> Bridge<'a> { complete_teardown, }; + let port_mappings = tpf.config.port_mappings; + let container_ip_v4 = tpf.config.container_ip_v4; + let container_ip_v6 = tpf.config.container_ip_v6; + self.info.firewall.teardown_port_forward(tpf)?; + + if let Some(port_mappings) = port_mappings { + // Flush stale UDP conntrack entries to prevent dropped packets. + // See the function's doc comment for more details. + flush_udp_conntrack(port_mappings, container_ip_v4, container_ip_v6)?; + } + Ok(()) } } diff --git a/src/network/mod.rs b/src/network/mod.rs index ed79a9f8c..dd96f65fd 100644 --- a/src/network/mod.rs +++ b/src/network/mod.rs @@ -18,6 +18,7 @@ pub mod driver; pub mod internal_types; pub mod netlink; +pub mod netlink_netfilter; pub mod netlink_route; pub mod plugin; diff --git a/src/network/netlink_netfilter.rs b/src/network/netlink_netfilter.rs new file mode 100644 index 000000000..c3d8ad7b2 --- /dev/null +++ b/src/network/netlink_netfilter.rs @@ -0,0 +1,278 @@ +use netlink_packet_netfilter::{ + conntrack::{ConntrackAttribute, IPTuple, ProtoTuple, Protocol, Tuple}, + NetfilterMessageInner, +}; +use std::{collections::HashSet, net::IpAddr, num::NonZeroI32}; + +use crate::{ + error::{ErrorWrap, NetavarkError, NetavarkResult}, + network::{ + netlink::{expect_netlink_result, function, NetlinkFamily, Socket}, + types::PortMapping, + }, +}; +use netlink_packet_core::{NLM_F_ACK, NLM_F_DUMP}; +use netlink_packet_netfilter::{ + conntrack::ConntrackMessage, NetfilterHeader, NetfilterMessage, ProtoFamily, +}; +use netlink_sys::protocols::NETLINK_NETFILTER; + +pub struct NetlinkNetfilter; + +impl NetlinkFamily for NetlinkNetfilter { + const PROTOCOL: isize = NETLINK_NETFILTER; + type Message = NetfilterMessage; +} + +// Represents the 5-tuple for a single direction of a connection flow. +#[derive(Clone)] +pub struct FlowTuple { + pub src_ip: IpAddr, + pub dst_ip: IpAddr, + pub src_port: u16, + pub dst_port: u16, + pub protocol: Protocol, +} +impl FlowTuple { + pub fn is_ipv6(&self) -> bool { + self.src_ip.is_ipv6() && self.dst_ip.is_ipv6() + } +} + +// Represents a conntrack entry, detailing the original and reply flows. +#[derive(Clone)] +pub struct ConntrackFlow { + pub origin: Option, + pub reply: Option, +} + +// Converts a `ConntrackFlow` into a vector of `ConntrackAttribute` +// attributes suitable for a netlink message payload. +impl ConntrackFlow { + fn to_attributes(self: ConntrackFlow) -> Vec { + fn to_tuple_attributes(flow_tuple: &FlowTuple) -> Vec { + let ip_attributes = vec![ + IPTuple::SourceAddress(flow_tuple.src_ip), + IPTuple::DestinationAddress(flow_tuple.dst_ip), + ]; + + let proto_attributes = vec![ + ProtoTuple::Protocol(flow_tuple.protocol), + ProtoTuple::SourcePort(flow_tuple.src_port), + ProtoTuple::DestinationPort(flow_tuple.dst_port), + ]; + vec![Tuple::Ip(ip_attributes), Tuple::Proto(proto_attributes)] + } + + let mut attributes = Vec::new(); + + if let Some(ref origin_flow) = self.origin { + let origin_attributes = to_tuple_attributes(origin_flow); + if !origin_attributes.is_empty() { + attributes.push(ConntrackAttribute::CtaTupleOrig(origin_attributes)); + } + } + + if let Some(ref reply_flow) = self.reply { + let reply_attributes = to_tuple_attributes(reply_flow); + if !reply_attributes.is_empty() { + attributes.push(ConntrackAttribute::CtaTupleReply(reply_attributes)); + } + } + + attributes + } + fn is_ipv6(&self) -> NetavarkResult { + match (&self.origin, &self.reply) { + (Some(origin), Some(reply)) => { + let origin_is_v6 = origin.is_ipv6(); + + if origin_is_v6 != reply.is_ipv6() { + Err(NetavarkError::Message( + "Mismatched IP families in conntrack flow".to_string(), + )) + } else { + Ok(origin_is_v6) + } + } + + (Some(origin), None) => Ok(origin.is_ipv6()), + + (None, Some(reply)) => Ok(reply.is_ipv6()), + + (None, None) => Err(NetavarkError::Message( + "There needs to be atleast one FlowTuple in a conntrack flow".to_string(), + )), + } + } +} + +impl Socket { + pub fn dump_conntrack(&mut self) -> NetavarkResult> { + let msg: NetfilterMessage = NetfilterMessage::new( + NetfilterHeader::new(ProtoFamily::ProtoUnspec, 0, 0), + ConntrackMessage::Get(vec![]), + ); + + let result = self.make_netlink_request(msg, NLM_F_DUMP)?; + + Ok(result) + } + + pub fn del_conntrack(&mut self, flow: ConntrackFlow) -> NetavarkResult<()> { + let is_v6 = flow.is_ipv6()?; + let proto_family = if is_v6 { + ProtoFamily::ProtoIPv6 + } else { + ProtoFamily::ProtoIPv4 + }; + let msg: NetfilterMessage = NetfilterMessage::new( + NetfilterHeader::new(proto_family, 0, 0), + ConntrackMessage::Delete(flow.to_attributes()), + ); + + let result = self.make_netlink_request(msg, NLM_F_ACK)?; + expect_netlink_result!(result, 0); + + Ok(()) + } +} + +/// This function addresses an issue where UDP traffic is dropped due to stale conntrack entries. +/// +/// To solve this, we proactively flush any conntrack entries associated with the mapped UDP +/// host ports before the network is fully set up. This ensures the kernel creates a fresh, +/// correct entry for the new service instance. +/// +/// Fixes: https://github.com/containers/netavark/issues/1045 +pub fn flush_udp_conntrack( + port_mappings: &[PortMapping], + container_ipv4: Option, + container_ipv6: Option, +) -> NetavarkResult<()> { + let mut host_ports_to_flush = HashSet::::new(); + for pm in port_mappings.iter().filter(|pm| pm.protocol == "udp") { + for port in pm.host_port..(pm.host_port + pm.range) { + host_ports_to_flush.insert(port); + } + } + + if host_ports_to_flush.is_empty() && container_ipv4.is_none() && container_ipv6.is_none() { + return Ok(()); + } + + let mut ct_socket = Socket::::new().wrap("conntrack netlink socket")?; + let conntrack_dump = ct_socket.dump_conntrack()?; + + for msg in &conntrack_dump { + if let Some(flow) = parse_ct_new_msg(msg) { + if matches_flow(&flow, &host_ports_to_flush, container_ipv4, container_ipv6) { + match ct_socket.del_conntrack(flow) { + Ok(_) => {} + // We iterate over a dump of entries. Between the time we dump the table + // and the time we attempt the delete, the entry might have naturally + // expired. Treating ENOENT as success ensures we don't fail the setup + // just because the cleanup happened automatically. + Err(NetavarkError::Netlink(e)) if e.code == NonZeroI32::new(-libc::ENOENT) => { + log::debug!("Conntrack entry already deleted, skipping"); + } + Err(e) => return Err(e), + } + } + } + } + + Ok(()) +} + +fn matches_flow( + flow: &ConntrackFlow, + ports: &HashSet, + ipv4: Option, + ipv6: Option, +) -> bool { + if let Some(origin) = &flow.origin { + if origin.protocol == Protocol::Udp && ports.contains(&origin.dst_port) { + return true; + } + } + if let Some(reply) = &flow.reply { + let is_match = |ip| ipv4 == Some(ip) || ipv6 == Some(ip); + if is_match(reply.src_ip) || is_match(reply.dst_ip) { + return true; + } + } + false +} + +/// parses a conntrack new netfilter message into a ConntrackFlow struct +pub fn parse_ct_new_msg(msg: &NetfilterMessage) -> Option { + let attributes = match &msg.inner { + NetfilterMessageInner::Conntrack(ConntrackMessage::New(attr)) => attr, + _ => return None, + }; + + let mut origin_tuples = None; + let mut reply_tuples = None; + + for nla in attributes { + match nla { + ConntrackAttribute::CtaTupleOrig(tuples) => origin_tuples = Some(tuples.as_slice()), + ConntrackAttribute::CtaTupleReply(tuples) => reply_tuples = Some(tuples.as_slice()), + _ => {} + } + + if origin_tuples.is_some() && reply_tuples.is_some() { + break; + } + } + + let origin_flow = parse_tuples_to_flow(origin_tuples?)?; + let reply_flow = parse_tuples_to_flow(reply_tuples?)?; + + Some(ConntrackFlow { + origin: Some(origin_flow), + reply: Some(reply_flow), + }) +} + +fn parse_tuples_to_flow(tuples: &[Tuple]) -> Option { + let mut src_ip = None; + let mut dst_ip = None; + let mut src_port = None; + let mut dst_port = None; + let mut protocol_from_tuple = None; + + for tuple in tuples { + match tuple { + Tuple::Ip(ip_tuples) => { + for ip_tuple in ip_tuples { + match ip_tuple { + IPTuple::SourceAddress(ip) => src_ip = Some(*ip), + IPTuple::DestinationAddress(ip) => dst_ip = Some(*ip), + _ => (), + } + } + } + Tuple::Proto(proto_tuples) => { + for proto_tuple in proto_tuples { + match proto_tuple { + ProtoTuple::Protocol(p) => protocol_from_tuple = Some(*p), + ProtoTuple::SourcePort(p) => src_port = Some(*p), + ProtoTuple::DestinationPort(p) => dst_port = Some(*p), + _ => (), + } + } + } + _ => (), + } + } + + Some(FlowTuple { + src_ip: src_ip?, + dst_ip: dst_ip?, + src_port: src_port?, + dst_port: dst_port?, + protocol: protocol_from_tuple?, + }) +} diff --git a/src/test/netlink.rs b/src/test/netlink.rs index 85907c7dc..d1b66dabe 100644 --- a/src/test/netlink.rs +++ b/src/test/netlink.rs @@ -1,10 +1,14 @@ #[cfg(test)] mod tests { - use std::net::{IpAddr, Ipv4Addr}; - use netavark::network::netlink::Socket; + use netavark::network::netlink_netfilter::{ + parse_ct_new_msg, ConntrackFlow, FlowTuple, NetlinkNetfilter, + }; use netavark::network::netlink_route::{CreateLinkOptions, LinkID, NetlinkRoute, Route}; + use netlink_packet_netfilter::conntrack::Protocol; + use netlink_packet_route::{address, link::InfoKind}; + use std::net::{IpAddr, Ipv4Addr}; macro_rules! test_setup { () => { @@ -225,4 +229,167 @@ mod tests { } } } + + #[test] + fn test_dump_conntrack() { + test_setup!(); + let mut sock = Socket::::new().expect("Socket::new()"); + + let tcp_src_ip = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 100)); + let tcp_dst_ip = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)); + let tcp_src_port: u16 = 12345; + let tcp_dst_port: u16 = 80; + + let udp_src_ip = IpAddr::V4(Ipv4Addr::new(172, 16, 30, 5)); + let udp_dst_ip = IpAddr::V4(Ipv4Addr::new(8, 8, 4, 4)); + let udp_src_port: u16 = 49152; + let udp_dst_port: u16 = 53; + + let out = run_command!( + "conntrack", + "-I", + "-p", + "tcp", + "--src", + &tcp_src_ip.to_string(), + "--dst", + &tcp_dst_ip.to_string(), + "--sport", + &tcp_src_port.to_string(), + "--dport", + &tcp_dst_port.to_string(), + "--state", + "SYN_SENT", + "--timeout", + "60" + ); + assert!(out.status.success(), "failed to add TCP conntrack entry"); + + let out = run_command!( + "conntrack", + "-I", + "-p", + "udp", + "--src", + &udp_src_ip.to_string(), + "--dst", + &udp_dst_ip.to_string(), + "--sport", + &udp_src_port.to_string(), + "--dport", + &udp_dst_port.to_string(), + "--timeout", + "30" + ); + assert!(out.status.success(), "failed to add UDP conntrack entry"); + + let msgs = sock.dump_conntrack().expect("dump_conntrack failed"); + assert!(msgs.len() == 2, "Expected two conntrack entries"); + + let mut found_tcp_flow = false; + let mut found_udp_flow = false; + + for msg in &msgs { + if let Some(flow) = parse_ct_new_msg(msg) { + let origin = flow.origin.as_ref().unwrap(); + + match origin.protocol { + Protocol::Tcp if origin.dst_port == tcp_dst_port => { + assert_eq!(origin.src_ip, tcp_src_ip, "TCP origin source IP mismatch"); + assert_eq!( + origin.src_port, tcp_src_port, + "TCP origin source port mismatch" + ); + found_tcp_flow = true; + } + Protocol::Udp if origin.dst_port == udp_dst_port => { + assert_eq!(origin.src_ip, udp_src_ip, "UDP origin source IP mismatch"); + assert_eq!( + origin.src_port, udp_src_port, + "UDP origin source port mismatch" + ); + found_udp_flow = true; + } + _ => (), + } + } + } + + assert!( + found_tcp_flow, + "Did not find the expected TCP conntrack flow in the dump" + ); + assert!( + found_udp_flow, + "Did not find the expected UDP conntrack flow in the dump" + ); + } + + #[test] + fn test_del_conntrack() { + test_setup!(); + let mut sock = Socket::::new().expect("Socket::new()"); + + let src_ip = "192.168.1.100"; + let src_port = "12345"; + let dst_ip = "10.0.0.1"; + let dst_port = "80"; + + let out = run_command!( + "conntrack", + "-I", + "-p", + "tcp", + "--src", + src_ip, + "--dst", + dst_ip, + "--sport", + src_port, + "--dport", + dst_port, + "--state", + "SYN_SENT", + "--timeout", + "60" + ); + eprintln!("{}", String::from_utf8_lossy(&out.stderr)); + assert!( + out.status.success(), + "failed to add conntrack entry via conntrack-tools" + ); + + let conntrack_flow = ConntrackFlow { + origin: Some(FlowTuple { + src_ip: IpAddr::V4(src_ip.parse().unwrap()), + dst_ip: IpAddr::V4(dst_ip.parse().unwrap()), + src_port: src_port.parse().unwrap(), + dst_port: dst_port.parse().unwrap(), + protocol: Protocol::Tcp, + }), + reply: None, + }; + + sock.del_conntrack(conntrack_flow) + .expect("del_conntrack failed"); + + let out = run_command!( + "conntrack", + "-G", + "-p", + "tcp", + "--src", + src_ip, + "--dst", + dst_ip, + "--sport", + src_port, + "--dport", + dst_port + ); + assert!( + !out.status.success(), + "got deleted conntrack entry via conntrack-tools, i.e., deleting unsuccessful" + ); + } } diff --git a/test/700-udp-traffic-flush.bats b/test/700-udp-traffic-flush.bats new file mode 100644 index 000000000..f94a989a4 --- /dev/null +++ b/test/700-udp-traffic-flush.bats @@ -0,0 +1,107 @@ +#!/usr/bin/env bats -*- bats -*- +# +# Regression test for flushing stale conntrack udp entries https://github.com/containers/netavark/issues/1045 +# + +load helpers + +function start_sticky_udp_spammer() { + local target_ip=$1 + local target_port=$2 + local message=$3 + + cat << EOF > "$NETAVARK_TMPDIR/spammer.sh" +#!/bin/bash +# Open UDP connection to target on FD 3. +# Keeps Source Port constant. +exec 3<>/dev/udp/$target_ip/$target_port +while true; do + echo "$message" >&3 + sleep 0.1 +done +EOF + chmod +x "$NETAVARK_TMPDIR/spammer.sh" + run_in_host_netns "$NETAVARK_TMPDIR/spammer.sh" & + SPAMMER_PID=$! +} + +function run_ct_udp_flush_test() { + local config_file=$1 + local target_ip=$2 + local target_port=$3 + local container_port=$4 + + local msg="payload_$$" + + start_sticky_udp_spammer "$target_ip" "$target_port" "$msg" + + # avoid race condition and allow conntrack entry creation + sleep 1 + + run_in_host_netns conntrack -L + assert "$output" =~ "dst=${target_ip}.*dport=${target_port}" "Conntrack flow matches target IP and Port" + + run_netavark --file "$config_file" setup $(get_container_netns_path) + + run_in_host_netns conntrack -L + assert "$output" !~ "dst=${target_ip}.*dport=${target_port}" "Conntrack entry should NOT exist" + + run_in_container_netns timeout 1 sh -c "socat -u UDP4-LISTEN:$container_port STDOUT | head -n 1" + + local container_output="$output" + + kill $SPAMMER_PID + wait $SPAMMER_PID || true + run_netavark --file "$config_file" teardown $(get_container_netns_path) + + assert "$container_output" =~ "$msg" "received proper payload" +} + +@test "nftables: receive udp traffic with pre-existing stale conntrack entry" { + export NETAVARK_FW="nftables" + # Explicitly add a rule to trigger connection tracking. + # This ensures the traffic generated by the spammer creates a conntrack entry that + # Netavark must flush. Without this, the kernel might not track the flow, preventing + # the reproduction of the stale entry issue (causing a false positive test pass). + run_in_host_netns nft -f - << EOF +add table inet bats { + chain output { + type filter hook output priority 0; policy accept; + udp dport 10080 ct state new + } +} +EOF + run_ct_udp_flush_test "${TESTSDIR}/testfiles/bridge-udp-stale-conntrack.json" "127.0.0.1" "10080" "8080" +} + +@test "nftables: receive udp traffic with pre-existing stale conntrack entry (range)" { + export NETAVARK_FW="nftables" + # Explicitly add a rule to trigger connection tracking for the range 10080-10081 + run_in_host_netns nft -f - << EOF +add table inet bats { + chain output { + type filter hook output priority 0; policy accept; + udp dport 10080-10081 ct state new + } +} +EOF + run_ct_udp_flush_test "${TESTSDIR}/testfiles/bridge-udp-stale-conntrack-range.json" "127.0.0.1" "10081" "8081" +} + +@test "firewalld: receive udp traffic with pre-existing stale conntrack entry" { + setup_firewalld + export NETAVARK_FW="firewalld" + # Explicitly add a rule to trigger connection tracking. + run_in_host_netns firewall-cmd --direct --add-rule ipv4 filter OUTPUT 0 -p udp -m state --state NEW -j ACCEPT + run_ct_udp_flush_test "${TESTSDIR}/testfiles/bridge-udp-stale-conntrack.json" "127.0.0.1" "10080" "8080" +} + +@test "firewalld: receive udp traffic with pre-existing stale conntrack entry (range)" { + skip "port forwarding range seems to be broken with firewalld. All the traffic gets redirected to the first container port no matter which host range port you pick" + + setup_firewalld + export NETAVARK_FW="firewalld" + # Explicitly add a rule to trigger connection tracking. + run_in_host_netns firewall-cmd --direct --add-rule ipv4 filter OUTPUT 0 -p udp -m state --state NEW -j ACCEPT + run_ct_udp_flush_test "${TESTSDIR}/testfiles/bridge-udp-stale-conntrack-range.json" "127.0.0.1" "10081" "8081" +} diff --git a/test/testfiles/bridge-udp-stale-conntrack-range.json b/test/testfiles/bridge-udp-stale-conntrack-range.json new file mode 100644 index 000000000..20b1908ca --- /dev/null +++ b/test/testfiles/bridge-udp-stale-conntrack-range.json @@ -0,0 +1,32 @@ +{ + "container_id": "udp_traffic_container", + "container_name": "udp_traffic_test", + "port_mappings": [ + { + "host_ip": "127.0.0.1", + "container_port": 8080, + "host_port": 10080, + "range": 2, + "protocol": "udp" + } + ], + "networks": { + "podman": { + "static_ips": [ "10.88.0.20" ], + "interface_name": "eth0" + } + }, + "network_info": { + "podman": { + "name": "podman", + "id": "udp_traffic_id", + "driver": "bridge", + "network_interface": "podman0", + "subnets": [ { "subnet": "10.88.0.0/16", "gateway": "10.88.0.1" } ], + "ipam_options": { "driver": "host-local" }, + "dns_enabled": false, + "ipv6_enabled": false, + "internal": false + } + } +} diff --git a/test/testfiles/bridge-udp-stale-conntrack.json b/test/testfiles/bridge-udp-stale-conntrack.json new file mode 100644 index 000000000..b3a39d026 --- /dev/null +++ b/test/testfiles/bridge-udp-stale-conntrack.json @@ -0,0 +1,32 @@ +{ + "container_id": "udp_traffic_container", + "container_name": "udp_traffic_test", + "port_mappings": [ + { + "host_ip": "127.0.0.1", + "container_port": 8080, + "host_port": 10080, + "range": 1, + "protocol": "udp" + } + ], + "networks": { + "podman": { + "static_ips": [ "10.88.0.20" ], + "interface_name": "eth0" + } + }, + "network_info": { + "podman": { + "name": "podman", + "id": "udp_traffic_id", + "driver": "bridge", + "network_interface": "podman0", + "subnets": [ { "subnet": "10.88.0.0/16", "gateway": "10.88.0.1" } ], + "ipam_options": { "driver": "host-local" }, + "dns_enabled": false, + "ipv6_enabled": false, + "internal": false + } + } +}