This problem is pretty straight forward, i want to have different network configurations depending on the Wi-Fi i am using. It was pretty straight forward on my Iphone, because they keep the network configuration linked to the network you are using. That’s not the case for macOS.

When you change your network configuration, you change it for all of your networks. To have different configurations you need to use Location.

But you need to manually change the location when using a different network.

I wanted my computer to automatically choose the location depending on the Wi-Fi network.

Step 1: Monitoring Network Changes

To achieve that, i chose to use NWPathMonitor, it is an API to react to network changes. You give it a callback function, and the OS will call this callback everytime the network changes. It gives back a NWPath, with the status of the connection, what interfaces are available and their information. But the issue is that it doesn’t hold the SSID, or any way of identifying the connection.

import Network

let monitor = NWPathMonitor()
monitor.pathUpdateHandler = { path in
  dump(path)
}
monitor.start(queue: DispatchQueue(label: "net-monitor"))
dispatchMain()

Put this into dump_nw_path.swift, compile with swiftc dump_nw_path.swift and run with ./dump_nw_path and you should see something like that :

▿ satisfied (Path is satisfied), interface: en0[802.11], ipv4, dns, uses Wi-Fi
  - status: Network.NWPath.Status.satisfied
  ▿ availableInterfaces: 1 element
    ▿ en0
      ▿ _nw: Optional(Network.UncheckedSendable<__C.OS_nw_interface>(rawValue: en0))
        ▿ some: Network.UncheckedSendable<__C.OS_nw_interface>
          - rawValue: en0 #0
            - super: NSObject
  - isExpensive: false
  - supportsIPv4: true
  - supportsIPv6: false
  - supportsDNS: true
  ▿ internalGateways: 1 element
    ▿ 192.168.0.1:0
      ▿ hostPort: (2 elements)
        ▿ host: 192.168.0.1
          ▿ ipv4: 192.168.0.1
            ▿ address: __C.in_addr
              - s_addr: 16820416
            - interface: nil
        ▿ port: 0
          - port: 0
  - localEndpoint: nil
  - remoteEndpoint: nil
  ▿ _nw: Optional(Network.UncheckedSendable<__C.OS_nw_path>(rawValue: satisfied (Path is satisfied), interface: en0[802.11], ipv4, dns, uses Wi-Fi))
    ▿ some: Network.UncheckedSendable<__C.OS_nw_path>
      - rawValue: satisfied (Path is satisfied), interface: en0[802.11], ipv4, dns, uses Wi-Fi #1
        - super: NSObject

You can try and connect and disconnect your Wi-Fi and you’ll see the updates on your command line.

Now that we have a callback when the network changes, we only need to update the location depending on the Wi-Fi name.

Step 2: Getting the Wi-Fi SSID

While searching for a way to programatically change the network location, i stumbled onto the networksetup CLI. It is used to configure network settings that would be configured in the System Preferences application. Two specific endpoints are usefull for us, first networksetup -getairportnetwork interface returns the current Wi-Fi Network, and networksetup -switchtolocation Home sets the current location.

the output of both being :

% networksetup -getairportnetwork en0
Current Wi-Fi Network: HomeWifi
% networksetup -switchtolocation Home
found it!

Step 3: Switching Locations Automatically

The clean way of doing this would be to use the APIs provided by Apple in Swift, but for that you would need to create a real app, with an info.plist and all the full package. I wanted a quick and dirty fix so i passed on that.

We can call the networksetup cli from the swift, and retreive the SSID when the Wi-Fi updates.

import Foundation
import Network
let networkInterface = "en0"

func executeNetworkSetup(arguments: [String]) -> String? {
  let task = Process()
  task.executableURL = URL(fileURLWithPath: "/usr/sbin/networksetup")
  task.arguments = arguments

  let pipe = Pipe()
  task.standardOutput = pipe

  do {
    try task.run()
    task.waitUntilExit()
  } catch {
    return nil
  }

  let data = pipe.fileHandleForReading.readDataToEndOfFile()
  guard let output = String(data: data, encoding: .utf8) else { return nil }
  return output
}

func getSSID() -> String? {
  let args = ["-getairportnetwork", networkInterface]
  let output = executeNetworkSetup(arguments: args)

  if let outputString = output {
    if let ssid = outputString.split(separator: ":").last {
      return ssid.trimmingCharacters(in: .whitespacesAndNewlines)
    }
  }
  return nil
}

func changeLocation(locationName: String) -> Bool {
  let args = ["-switchtolocation", locationName]
  let output = executeNetworkSetup(arguments: args)

  if let outputString = output {
    let trimmedOutput = outputString.trimmingCharacters(in: .whitespacesAndNewlines)
    if trimmedOutput == "found it!" {
      return true
    } else {
      print(outputString)
    }
  }
  return false
}

let monitor = NWPathMonitor()
monitor.pathUpdateHandler = { path in
  let ssid = getSSID()
  if let name = ssid {
    print(name)
    var location_ = "default"
    if name == "HomeWifi" {
      location_ = "HomeLocation"
    } else if name == "WorkWifi" {
      location_ = "WorkLocation"
    }
    if !changeLocation(locationName:location_){
      print("Bad location name")
    }
  }
}

monitor.start(queue: DispatchQueue(label: "net-monitor"))
dispatchMain()

Step 4: Adding Configuration

This is not the final code, because now i wanted to be able to have a configuration file, where i could just put my SSID -> Location mapping, and provide a default Location.

You just need to compile it with swiftc, and put a config file in your ~/.location-settings.json, with an example below :

Then you can run the program, and it will work till you stop it.

Setting Up Auto-Start with Cron

To have it execute it all the time automatically, you can use crontab to have it run at the launch of your computer. You can edit it with crontab (if you want to use vi, replace nano by vi).

EDITOR=nano crontab -e

and add the following line :

@reboot path-to-your-executable/your-program

Check if it worked with

crontab -l

You can reboot and voila! Your network locations are managed automatically depending on the Wi-Fi network you are using !