Jail VNET Networking Issue: Pings Not Reaching vlan1 Interface

Hello FreeBSD community,

I’m encountering a persistent issue with VNET jails on my FreeBSD 14.2 system acting as a network router. The setup involves a VLAN interface (vlan1) on ix1 with the IP 192.168.98.5, serving the 192.168.98.0/24 subnet. I have a jail (local_services) configured with VNET, using epair0b with IP 192.168.98.7, and the host can ping the jail successfully. However, pings from the jail to the host (192.168.98.5) fail with “Network is unreachable,” and I don’t see the ICMP traffic on tcpdump -i vlan1 -n.

System Details:
  • OS: FreeBSD 14.2
  • Hardware: Multi-NIC system with ix0 (WAN) and ix1 (parent of vlan1).
  • Network: vlan1 is tagged with VLAN ID 1, matching the switch configuration (ports 1-24, 27, 28 untagged, 25 tagged for VLAN 1). The Asus router (192.168.98.1) and *few* others (insignificant) are on the switch, with untagged ports 25 and 26.
  • Jail Config: VNET with epair0b, default route 192.168.98.1, and a direct route to 192.168.98.5 via epair0b.
  • pf: Enabled with rules to allow traffic, including pass on vlan1 all, but disabling pf didn’t resolve the issue.
Symptoms:
  • Host-to-jail ping (192.168.98.5 to 192.168.98.7) works, with 192.168.98.7 in arp -a.
  • Jail-to-host ping fails, with no ICMP or ARP requests visible on tcpdump -i vlan1 -n.
  • tcpdump -i vlan1 -n shows other subnet traffic (e.g., 10.177.9.0/24 from iLo), suggesting untagged traffic might be interfering.
Attempts:
  • Added ifconfig epair0a vlan 1 vlandev vlan1 to ensure tagged traffic.
  • Set net.inet.ip.forwarding=1 on host.
  • Added a direct route in the jail to 192.168.98.5 via epair0b.
  • Disabled pf temporarily, but no change (except loss of NAT, expected).
  • Captured traffic on epair0a, but pings still don’t reach vlan1.

local_services.conf
Code:
local_services {
  exec.start = "/bin/sh /etc/rc";
  exec.stop = "/bin/sh /etc/rc.shutdown";
  exec.consolelog = "/var/log/jail_console_${name}.log";
  allow.raw_sockets;
  sysvshm = "new";
  exec.clean;
  mount.devfs;
  devfs_ruleset = "4";
  exec.jail_user = "root";
  host.hostname = "${name}";
  path = "/storage/jails/containers/${name}";
  vnet;
  vnet.interface = "epair0b";
  exec.prestart = "ifconfig epair0 create || exit 1";
  exec.prestart += "ifconfig epair0a up || exit 1";
  exec.prestart += "ifconfig epair0a vlan 1 vlandev vlan1 || exit 1";
  exec.prestart += "ifconfig epair0a inet 192.168.98.7/24 alias || exit 1";
  exec.prestart += "sysctl net.inet.ip.forwarding=1";
  exec.poststart = "jexec -U root ${name} sysctl net.inet.ip.forwarding=1 2>/dev/null || true";
  exec.poststart += "jexec -U root ${name} route add -host 192.168.98.5 -interface epair0b 2>/dev/null || true";
  exec.poststop = "sysctl net.inet.ip.forwarding=0 2>/dev/null || true";
  exec.poststop += "jexec -U root ${name} sysctl net.inet.ip.forwarding=0 2>/dev/null || true";
  exec.poststop += "jexec -U root ${name} route delete -host 192.168.98.5 2>/dev/null || true";
  exec.poststop += "ifconfig epair0a -alias 192.168.98.7 2>/dev/null || true";
  exec.poststop += "ifconfig epair0 destroy 2>/dev/null || true";
}

pf.conf
Code:
# Interface definitions
ext_if="ix0"
int_if="ix1"
lan_if="vlan1"
wan_net="192.168.2.0/24"
lan_net="192.168.98.0/24"
ring_net="192.168.99.0/24"

# Tables for tracking malicious IPs
table <port_scanners> persist
table <bruteforce> persist

# NAT rules
nat on $ext_if inet proto udp from any to ($ext_if) port 51820 -> 192.168.2.2 port 51820
nat on $ext_if from 192.168.2.2 to any -> ($ext_if)
nat on $ext_if from $wan_net to any -> ($ext_if)
nat on $ext_if from $lan_net to any -> ($ext_if)
nat on $ext_if from $ring_net to any -> ($ext_if)

# Block known malicious IPs
block in quick on $ext_if from <port_scanners> to any
block in quick on $ext_if from <bruteforce> to any

# Default deny policy
block in log on $ext_if

# Allow Ring UDP traffic (video streaming)
pass in on $ext_if proto udp from 3.138.237.19 port 30595 to $ring_net keep state
pass out on $ext_if proto udp from $ring_net to 3.138.237.19 port 30595 keep state

# Allow Ring TCP traffic (HTTPS to AWS servers)
pass in on $ext_if proto tcp from {15.197.190.1, 3.129.199.65, 3.131.145.165, 3.231.47.171, 52.0.171.160, 18.233.69.115, 52.3.90.173} port 443 to $ring_net flags S/SA keep state
pass out on $ext_if proto tcp from $ring_net to {15.197.190.1, 3.129.199.65, 3.131.145.165, 3.231.47.171, 52.0.171.160, 18.233.69.115, 52.3.90.173} port 443 flags S/SA keep state

# Allow gaming traffic
pass in on $ext_if proto udp from any to $lan_net port {3074, 27015:27030} keep state
pass out on $ext_if proto udp from $lan_net to any port {3074, 27015:27030} keep state

# Allow camera traffic
pass in on $ext_if proto {tcp, udp} from any to $ring_net port {80, 554, 443} keep state
pass out on $ext_if proto {tcp, udp} from $ring_net to any port {80, 554, 443} keep state

# Allow inbound TCP (port scans to be handled manually)
pass in log on $ext_if proto tcp from any to ($ext_if) port 1:65535 flags S/SA keep state

# Allow inbound TCP/UDP
pass in log on $ext_if proto { tcp, udp } from any to ($ext_if) keep state

# Allow WireGuard traffic to the router
pass in log on $ext_if proto udp from any to 192.168.2.2 port 51820 keep state
pass out log on $int_if proto udp from any to 192.168.2.2 port 51820 keep state

# Allow outbound traffic from internal networks
pass out on $ext_if from ($ext_if) to any keep state

# Allow inbound replies to established connections
pass in on $ext_if from any to ($ext_if) keep state

# Allow all traffic on the internal interface
pass on $int_if keep state

# Allow all traffic on the LAN interface (jails) with explicit protocols
pass on $lan_if proto icmp all keep state
pass on $lan_if proto { tcp, udp } all keep state

rc.conf (snipped)
Code:
# Network configuration
ifconfig_ix0="DHCP"
ifconfig_ix1="up"
ifconfig_vlan1="inet 192.168.98.5 netmask 255.255.255.0 vlan 1 vlandev ix1"
gateway_enable="YES"
pf_enable="YES"
pflog_enable="YES"
pf_rules="/etc/pf.conf"

Code:
root@vector_edge:/etc # ifconfig vlan1
vlan1: flags=1008943<UP,BROADCAST,RUNNING,PROMISC,SIMPLEX,MULTICAST,LOWER_UP> metric 0 mtu 1500
options=4600703<RXCSUM,TXCSUM,TSO4,TSO6,LRO,RXCSUM_IPV6,TXCSUM_IPV6,MEXTPG>
ether 38:ea:a7:32:1a:8d
inet 192.168.98.5 netmask 0xffffff00 broadcast 192.168.98.255
groups: vlan
vlan: 1 vlanproto: 802.1q vlanpcp: 0 parent interface: ix1
media: Ethernet autoselect (10Gbase-T <full-duplex,rxpause,txpause>)
status: active
nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>


root@vector_edge:/etc # jexec 1 netstat -rn
Routing tables

Internet:
Destination        Gateway            Flags         Netif Expire
default            192.168.98.1       UGS         epair0b
127.0.0.1          link#8             UH              lo0
192.168.98.0/24    link#7             U           epair0b
192.168.98.7       link#8             UHS             lo0

Internet6:
Destination                       Gateway                       Flags         Netif Expire
::/96                             link#8                        URS             lo0
::1                               link#8                        UHS             lo0
::ffff:0.0.0.0/96                 link#8                        URS             lo0
fe80::%lo0/10                     link#8                        URS             lo0
fe80::%lo0/64                     link#8                        U               lo0
fe80::1%lo0                       link#8                        UHS             lo0
ff02::/16                         link#8                        URS             lo0
root@vector_edge:/etc #
Is this a VNET configuration issue, a VLAN tagging mismatch, or a routing problem? Why aren’t the jail’s pings reaching vlan1? Any suggestions for debugging or config adjustments would be greatly appreciated!
 
Shot in the dark here, but can you try adding your subnet to the jail IP in the form of /24?

I’ve had an issue in the past where a jail on a separate VLAN could not ping even the host of the subnet was not added.
 
Shot in the dark here, but can you try adding your subnet to the jail IP in the form of /24?

I’ve had an issue in the past where a jail on a separate VLAN could not ping even the host of the subnet was not added.
Thanks for the suggestion.

The jail .conf file already uses CIDR notation, so I assumed you were referring to the route. I added
exec.poststart += "jexec -U root ${name} route add -net 192.168.98.0/24 -interface epair0b 2>/dev/null || true"; to the .conf, but no change in behavior. Was this what you meant?
 
Thanks for the suggestion.

The jail .conf file already uses CIDR notation, so I assumed you were referring to the route. I added
exec.poststart += "jexec -U root ${name} route add -net 192.168.98.0/24 -interface epair0b 2>/dev/null || true"; to the .conf, but no change in behavior. Was this what you meant?
/etc/rc.conf
 
Your ASUS router subnet overlaps with the JAIL subnet. You need to use bridge if you want the jails to be in the same subnet as your internal LAN network.
 
I'm not sure if you are supposed to specify the vlan again on the epair interface if it is connected to the already existing vlan interface. There's a good chance you are double-tagging that VLAN. (also: *never* use VLAN 1 except in a flat network - this is used as a special 'default' vlan by switches and hence shouldn't be used at all for security reasons. Tagging the default VLAN may/will also lead to various problems...)

The usual setup uses a bridge to which the epair interfaces for jails and the outgoing host interface are connected. E.g.:
Code:
[...]
ifconfig_ix0="-tso -lro -txcsum -txcsum6 -rxcsum up"
vlans_ix0="3 4 5"
ifconfig_ix0_3="description vm-lan group vm-lan up"
ifconfig_ix0_4="descritpino vm-dmz group vm-dmz up"
ifconfig_ix0_5="description vm-mgmt group vm-mgmt up"

cloned_interfaces="bridge3 bridge4 bridge5"
ifconfig_bridge3="addm ix0.3 name br-lan up"
ifconfig_bridge4="addm ix0.4 name br-dmz up"
ifconfig_bridge5="addm ix0.5 name br-mgmt up"

jails are then connected to those bridges depending on which VLAN(s) they need to connect to.

If you only need a single bridge, you could use the autobridge_interface and autobridge_<IFNAME> options to automagically connect all new 'epair*a' interfaces to that bridge.
 
Thanks for the feedback everyone.

I tried vlan2 on bridge2, but “BRDGADD bridge2: Invalid argument” persists on epair0a. I had tried the bridge before and got the same results which is why I utilized vlan tagging to differentiate between the 2 networks.

I just don't have the desire to troubleshoot this any further (already spent about a month on it). I still thank you for the responses.

I'll probably end up either aliasing the jail nets with the vlan or abandon the vlan althogether and keep the jails out of the downstream network.
 
Back
Top