Automatically Disconnect Wifi When Wired Interface Is Detected.

EDIT: So it looks like some people are concerned with the sudo requirement. I need AirDrop and other macOS services that rely on WiFi to continue working so I cannot afford to disable the wireless interface. As far as I know there is no way to just disassociate from the network without sudo or turning off the interface. Since I have https://digitaino.com/use-touchid-to-authenticate-sudo-on-macos/ also enabled, it just brings up a Touch ID prompt on my screen whenever it needs to run the sudo command.

After using a Sonnet Solo10G SFP+ network adapter with my 14″ MacBook Pro for a few months it was great but something felt off. I was looking for a way to have wifi automatically disconnect (not turn off) when the SFP adapter established a connection and then reconnect once the SFP adapter was removed.

The issue is that since each network interface gets its own IP from the router’s DHCP, it fails to register the DNS record for the new interface since one already exists with the same name. This causes macOS to throw an error that the hostname is already in use and then appends a number to the hostname as a workaround to resolve the conflict. After a month your mac’s hostname would look something like ”macbook pro-1-5-9” or something weird like that. Not ideal.

Error message from an older version of macOS.

So I started looking for ways to interface with airport from the terminal.

Connect to a network:

networksetup -setairportnetwork en0 "$WIFI_SSID"

Get current network SSID:

networksetup -getairportnetwork en0 

Disconnect from current network:

sudo /System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport "en0" -z

Then, a shell script based on the commands above:

#!/bin/sh

WIFI_INTERFACE=en0
WIRED_INTERFACE=en4
DEFAULT_SSID="Your SSID"
TEMP_LOCATION="/Users/YOURUSER/.wifi_ssid"


WIFI_STATUS=$(ifconfig $WIFI_INTERFACE | grep status | awk -F' ' '{ print$2 }')

if [[ ("$(ifconfig $WIRED_INTERFACE | grep status | awk -F' ' '{ print$2 }')" = "active" ) ]]
then
	WIRED_STATUS="active"
else
	WIRED_STATUS="disconnected"
fi

echo "------------Network Check RUN------------"
echo $(date)
echo "WIFI_STATUS: $WIFI_STATUS"
echo "WIRED_STATUS: $WIRED_STATUS"


if [[ ("$WIRED_STATUS" = "active" ) && ("$WIFI_STATUS" = "active" ) ]]
then
	networksetup -getairportnetwork $WIFI_INTERFACE | awk -F':' '{ print$2 }' | cut -c 2- > $TEMP_LOCATION
	read WIFI_SSID < $TEMP_LOCATION
	sudo /System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport "$WIFI_INTERFACE" -z
	echo "Action: WiFi disconnecting from $WIFI_SSID"
elif [[ ("$WIRED_STATUS" != "active" ) && ("$WIFI_STATUS" != "active" ) ]]; then
	read WIFI_SSID < $TEMP_LOCATION
	if [[ -z "$WIFI_SSID"  ]]; then
		WIFI_SSID=$DEFAULT_SSID
		echo "No wifi_ssid found in $TEMP_LOCATION. Using default_ssid $DEFAULT_SSID"
	fi
	networksetup -setairportnetwork $WIFI_INTERFACE "$WIFI_SSID"
	echo "Action: WiFi connecting to $WIFI_SSID"
else
	echo "Action: NO CHANGE"
fi

Remember to update the WIFI_INTERFACE=en0, WIRED_INTERFACE=en4, DEFAULT_SSID="Your SSID" and TEMP_LOCATION="/Users/YOURUSER/.wifi_ssid" variables for your system.

Saving this to ~/network_check and then running:

sudo chmod u+x ~/network_check

Now we need a way to run this script each time there is a change in the network configuration. Based on some google research it seems like a good file to watch for changes is:

/private/var/run/resolv.conf

Set up a user agent to watch for this file and then run the script.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>Label</key>
	<string>local.network_check</string>
	<key>Program</key>
	<string>/Users/YOURUSER/network_check</string>
	<key>RunAtLoad</key>
	<false/>
	<key>StandardErrorPath</key>
	<string>/Library/Logs/local.network_check.error.log</string>
	<key>StandardOutPath</key>
	<string>/Library/Logs/local.network_check.log</string>
	<key>WatchPaths</key>
	<array>
		<string>/private/var/run/resolv.conf</string>
	</array>
</dict>
</plist>

Remember to update the Program string with your script path.

Save this plist to:

~/Library/LaunchAgents/local.network_check.plist

Then run the following to load the agent:

launchctl load ~/Library/LaunchAgents/local.network_check.plist

For more details on how macOS daemons and agents work check out this post https://medium.com/swlh/how-to-use-launchd-to-run-services-in-macos-b972ed1e352 .

Now whenever the network adapter is connected or disconnected we see the desired behavior:

------------Network Check RUN------------
Fri Jul 1 12:09:00 CDT 2022
WIFI_STATUS: active
WIRED_STATUS: disconnected
Action: NO CHANGE
------------Network Check RUN------------
Fri Jul 1 12:09:35 CDT 2022
WIFI_STATUS: active
WIRED_STATUS: active
Action: WiFi disconnecting from MYSSID
------------Network Check RUN------------
Fri Jul 1 12:09:46 CDT 2022
WIFI_STATUS: inactive
WIRED_STATUS: active
Action: NO CHANGE
------------Network Check RUN------------
Fri Jul 1 12:19:54 CDT 2022
WIFI_STATUS: inactive
WIRED_STATUS: disconnected
Action: WiFi connecting to MYSSID

Here is a link to my GitHub page for the script.

10 thoughts on “Automatically Disconnect Wifi When Wired Interface Is Detected.

  1. Thx, it works mostly awesome with two exceptions:

    1. sudo: a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper
    sudo: a password is required

    “sudo” can be avoided with commands below and there will also be no need to manage wifi name.

    networksetup -setairportpower Wi-Fi off
    networksetup -setairportpower Wi-Fi on

    2. “/private/var/run/resolv.conf” does not work if a VPN is used (the file does not change in that case). Currently looking for a better way and will share if I find it.

      1. Fair enough, making this a Launch Daemon will remove the need for sudo at all (I removed it from the script and it now all works your way). VPN is still a pain, though.

        sudo launchctl load /Library/LaunchDaemons/local.network_check.plist

          1. Now it is all working for me with VPN. Two comments:

            1. (Only for a launch daemon running as root) If you change the watch path to “/private/var/db/dhcpclient” (directory), then the script will work for any VPN as well.
            2. “WIRED_INTERFACE” may disappear if a USB-C hub/adapter is disconnected and the script will complain with “ifconfig: interface en? does not exist”. There is no issue if a cable is unplugged.

            Thx a lot for the initial work and the write up!

  2. Your hostname shouldn’t be colliding and changing like that. I connect and disconnect from a wired network via the port in my USB docking station multiple times a day while remaining connected to the same subnet via WiFi. My computer name never changes. dns-sd -g v4 myhost.local resolves IP addresses for both interfaces.

    A variant of your tool could be useful for another scenario. The MacOS SMB client chooses the fastest available interface when connecting to an SMB server, and it transparently fails-over if the preferred interface disappears but there are others available (such as when disconnecting from a wired connection. Unfortunately, it doesn’t automatically upgrade the connection when a faster interface is connected. Cycling the WiFi connection automatically upon connecting to a wired network would cause the smb client to switch to using the wired interface while also retaining the WiFi interface as an option.

  3. I was finally able to get this working using G’s LaunchDaemon solution as well as his watch path.

    However, I had to make two more adjustments:

    1. I had to turn off auto-join on my network.
    2. I had to modify the line that reconnects to the Wifi network to include my password:
    networksetup -setairportnetwork $WIFI_INTERFACE “$WIFI_SSID”

    It’s finally working correctly.

Leave a Reply

Your email address will not be published. Required fields are marked *