Switching Network Interfaces On FreeBSD

Jan 31, 2026 · 1047 words · 5 minute read

A quick note for me on switching between wireless and wired networks on my FreeBSD laptops.

For a long time I've been confused at how to switch from the wireless (iwn0) connection to a wired one (em0). I'm mostly using a ThinkPad X220 (currently on 14.3-RELEASE), which has both a wireless card and (like all good machines should) a network jack. The network is setup as following /etc/rc.conf:

wlans_iwn0="wlan0"
ifconfig_wlan0="WPA DHCP"  # remove WPA when using an open network

with the relevant details for joining the network in /etc/wpa_supplicant.conf, all as described in the FreeBSD Handbook.

I did, for a while, have a LAGG setup which would switch between the two (as per example 3), but that ended up causing confusion when I wanted to try more complicated networking with jails, and 99% of the time I use the wireless, so there wasn't a big advantage.

But when I wanted to switch, I thought all I had to do was (running as root):

# ifconfig wlan0 down
# dhclient em0  # implicitly 'up's the interface

Which should turn the wireless off, and get a new IP address and DNS information from the network. Except it didn't work. I could connect to IP in the browser, ping local machines and drill(1) would give me correct DNS resolution - but, I couldn't connect to any websites in the browser. Typing henryleach.com into would result in browsers claiming there was no network connection.

Turns out the problem is that down isn't sufficient to completely kill off the connection somehow. I've not understood the details, but I suspect this is what this line in the ifconfig(8) manual page "..This action does not automatically disable routes using the interface." is alluding to. More digging didn't reveal to me what the actual difference is (if you have a good explanation, please let me know).

To properly kill the connection it's necessary to also 'destroy' the cloned wlan0 interface, so the steps should be:

# ifconfig wlan0 down
# ifconfig wlan0 destroy
# dhclient em0

With wlan0 completely gone, I didn't have this strange half state that confused the browsers. Or it's possible just to do:

# service netif stop

That will down and destroy the cloned interface for you. Once finished with the wired connection, you can ifconfig em0 down and start netif again with service netif start.

What's Happening?

After asking on Mastodon I got some hints to what might be happening, and certainly it's possible to see that the default route doesn't seem to update when not using the 'destroy' option, but what is triggering this still isn't clear to me.

When starting the network with wlan0 as per the rc.conf file above, then using netstat(1) we can see the following:

# netstat -4 -r -n | grep -v lo0
Routing tables
Internet:
Destination        Gateway            Flags         Netif Expire
default            10.0.0.1           UGS           wlan0
10.0.0.0/16        link#3             U             wlan0

Here 10.0.0.1 is the router/firewall of my home network. Using route(8) on the 'default' route shows:

# route -4 -d get default
   route to: default
destination: default
       mask: default
    gateway: <10.0.0.1 full domain name>
        fib: 0
  interface: wlan0
      flags: <UP,GATEWAY,DONE,STATIC>
 recvpipe  sendpipe  ssthresh  rtt,msec    mtu        weight    expire
       0         0         0         0      1500         1         0

The running the commands that end up leaving the machine in a confused state:

# ifconfig wlan0 down
# dhclient em0

Results in netstat still showing wlan0 for the Net(work)i(nter)f(ace) on the default route:

# netstat -4 -r -n | grep -v lo0
Routing tables
Internet:
Destination        Gateway            Flags         Netif Expire
default            10.0.0.1           UGS           wlan0
10.0.0.0/16        link#1             U               em0

and route shows:

# route -4 -d get default
   route to: default
destination: default
       mask: default
    gateway: <10.0.0.1 full domain name>
        fib: 0
  interface: wlan0
      flags: <UP,GATEWAY,DONE,STATIC>
 recvpipe  sendpipe  ssthresh  rtt,msec    mtu        weight    expire
       0         0         0         0      1500         1         0

Which seems to prove that the route isn't updated with just the 'down' option. Doing things cleanly with:

# service netif stop
# dhclient em0

We can see em0 listed in netstat:

# netstat -4 -r -n | grep -v lo0
Routing tables
Internet:
Destination        Gateway            Flags         Netif Expire
default            10.0.0.1           UGS             em0
10.0.0.0/16        link#1             U               em0

and route:

# route -4 -d get default
   route to: default
destination: default
       mask: default
    gateway: <10.0.0.1 (gateway) full domain name>
        fib: 0
  interface: em0
      flags: <UP,GATEWAY,DONE,STATIC>
 recvpipe  sendpipe  ssthresh  rtt,msec    mtu        weight    expire
       0         0         0         0      1500         1         0

I also did some searching through /etc/rc.d/netif, but wasn't able to see any reference to the difference, but that's probably just my limited ability to understand longer shell scripts (a 'language' I have to admit I find hard to work with for anything but the most basic scripts).

The other thing that currently doesn't work for me, even doing service netif stop and then dhclient em0, is that the route for the loopback/localhost, lo0, interface, which I notice when testing this post using Hugo's built in server. That normally serves a page at http://localhost:1313, but with the above this doesn't work anymore.

After a normal 'netif start', including the local interface I see:

# netstat -4 -r -n
Routing tables
Internet:
Destination        Gateway            Flags         Netif Expire
default            10.0.0.1           UGS           wlan0
10.0.0.0/16        link#3             U             wlan0
<my IP>            link#2             UHS             lo0
127.0.0.1          link#2             UH              lo0

and route shows:

# route -d get 127.0.0.1
   route to: localhost
destination: localhost
        fib: 0
  interface: lo0
      flags: <UP,HOST,DONE,PINNED>
 recvpipe  sendpipe  ssthresh  rtt,msec    mtu        weight    expire
       0         0         0         0     16384         1         0

Then after that netstat shows zero routes, and running dhclient em0, doesn't restore this route:

# netstat -4 -r -n
Routing tables
Internet:
Destination        Gateway            Flags         Netif Expire
default            10.0.0.1           UGS             em0
10.0.0.0/16        link#1             U               em0
<my IP>            link#2             UHS             lo0

Which then implies if you want to make sure all the routes you're normally expecting work, it's best to change the configuration in /etc/rc.conf and then stop and start netif, as it's clearly doing some additional things that right now I can't figure out.

The only other point of interest I could see is that on OpenBSD's ifconfig(8) man page it says next to the 'down' option: "This action automatically disables routes using the interface.". Then I'm assuming that there (I don't currently have an OpenBSD installation up and running) just the 'down' option would work?