Skip to content

Instantly share code, notes, and snippets.

@MohamedElashri
Last active October 6, 2025 00:42
Show Gist options
  • Select an option

  • Save MohamedElashri/b051792140e4f3ad66f20e25df6e9119 to your computer and use it in GitHub Desktop.

Select an option

Save MohamedElashri/b051792140e4f3ad66f20e25df6e9119 to your computer and use it in GitHub Desktop.
Change DNS servers on Mac based on SSID
<?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>com.melashri.ssid-dns</string>
<key>ProgramArguments</key>
<array>
<string>/Users/melashri/.local/bin/ssid-dns</string>
</array>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key>
<dict>
<key>NetworkState</key><true/>
</dict>
<key>StartInterval</key><integer>60</integer>
<key>StandardOutPath</key><string>/tmp/ssid-dns.out</string>
<key>StandardErrorPath</key><string>/tmp/ssid-dns.err</string>
</dict>
</plist>
#!/usr/bin/env bash
set -euo pipefail
# --- Policy ---
HOME_SSIDS=("HomeNetworkI" "HomeNetworkII") # home Wi-Fi names
CERN_SSID_REGEX='^(CERN|eduroam|CERN-Visitor)$'
CERN_DNS="137.138.16.5 137.138.17.5"
FALLBACK_DNS="9.9.9.9"
# CERN network ranges (add as needed)
CERN_NETS=( "137.138.0.0/16" "128.142.0.0/16" "188.184.0.0/15" "194.12.128.0/17" )
# --- Helpers ---
get_wifi_device() {
/usr/sbin/networksetup -listallhardwareports | awk '
/^Hardware Port: (Wi-Fi|AirPort)$/ {f=1; next}
f && /^Device:/ {print $2; exit}
'
}
get_service_for_device() {
local dev="$1"
/usr/sbin/networksetup -listnetworkserviceorder | awk -v d="$dev" '
/^\([0-9]+\)/ {svc=$0; sub(/^\([0-9]+\) /,"",svc); gsub(/[[:space:]]+$/,"",svc)}
/Device: / && $0 ~ "Device: " d {print svc; exit}
'
}
get_default_if() {
/sbin/route -n get default 2>/dev/null | awk '/interface:/{print $2; exit}'
}
get_gateway_ip() {
/sbin/route -n get default 2>/dev/null | awk '/gateway:/{print $2; exit}'
}
cidr_match() { # cidr_match <ip> <cidr>
python3 - "$1" "$2" <<'PY'
import ipaddress,sys
try:
print(ipaddress.ip_address(sys.argv[1]) in ipaddress.ip_network(sys.argv[2], strict=False))
except Exception:
print(False)
PY
}
get_search_domains() {
/usr/sbin/scutil --dns | awk -F': ' '
/^resolver #[0-9]+$/ {blk=1; next}
blk && NF==0 {blk=0}
blk && /^[[:space:]]*search domain/ {print $2}
blk && /^[[:space:]]*domain/ {print $2}
' | sed 's/[[:space:]]*$//'
}
get_ssid() {
local ssid dev
dev="$(get_wifi_device || true)"
# 0) networksetup with device (handles most cases)
if [[ -n "${dev:-}" ]]; then
if out=$(/usr/sbin/networksetup -getairportnetwork "$dev" 2>/dev/null); then
# "Current Wi-Fi Network: <SSID>" or "You are not associated ..."
if [[ "$out" =~ ^Current[[:space:]]Wi-?Fi[[:space:]]Network:\ (.*)$ ]]; then
printf '%s\n' "${BASH_REMATCH[1]}"; return 0
fi
fi
fi
# 1) system_profiler
ssid="$(/usr/sbin/system_profiler SPAirPortDataType 2>/dev/null | awk '
/Current Network Information:/ {f=1; next}
f && /^[[:space:]]*SSID:/ {sub(/^[[:space:]]*SSID:[[:space:]]*/,""); print; exit}
f && NF==0 {f=0}
')"
[[ -n "${ssid:-}" ]] && { printf '%s\n' "$ssid"; return 0; }
# 2) ioreg
ssid="$(/usr/sbin/ioreg -l | awk -F\" '/SSID_STR/ {print $4; exit}')"
[[ -n "${ssid:-}" ]] && { printf '%s\n' "$ssid"; return 0; }
# 3) scutil --nwi
ssid="$(/usr/sbin/scutil --nwi 2>/dev/null | awk -F': ' '/SSID:/ {print $2; exit}')"
[[ -n "${ssid:-}" ]] && { printf '%s\n' "$ssid"; return 0; }
# 4) airport fallbacks
for p in \
/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport \
/System/Library/PrivateFrameworks/Apple80211.framework/Versions/A/Resources/airport \
/System/Library/PrivateFrameworks/AppleWiFi.framework/Versions/Current/Resources/airport
do
[[ -x "$p" ]] || continue
ssid="$("$p" -I 2>/dev/null | awk -F': ' '/ SSID/ {print $2; exit}')"
[[ -n "${ssid:-}" ]] && { printf '%s\n' "$ssid"; return 0; }
done
return 1
}
get_current_dns() {
/usr/sbin/networksetup -getdnsservers "$1" 2>/dev/null | awk '
/^There (are|aren.t) any DNS Servers/ {print "Empty"; exit}
{print}
' | paste -sd' ' -
}
set_dns_if_needed() {
local service="$1" target="$2" current
current="$(get_current_dns "$service")"
if [[ "$target" == "$current" ]]; then
echo "[dns] No change ($current) on $service"
return
fi
if [[ "$target" == "Empty" ]]; then
/usr/sbin/networksetup -setdnsservers "$service" Empty
else
# shellcheck disable=SC2086
/usr/sbin/networksetup -setdnsservers "$service" $target
fi
echo "[dns] Applied: $target on $service"
}
# --- Detect environment ---
wifi_dev="$(get_wifi_device || true)"
def_if="$(get_default_if || true)"
prim_svc="$(get_service_for_device "${def_if:-}" || true)"
ssid="$(get_ssid || true)"
gw="$(get_gateway_ip || true)"
domains="$(get_search_domains || true)"
# CERN heuristics (any true -> use CERN DNS)
cern_ssid=false
cern_gw=false
cern_domain=false
[[ -n "${ssid:-}" && "$ssid" =~ $CERN_SSID_REGEX ]] && cern_ssid=true
if [[ -n "${gw:-}" ]]; then
for net in "${CERN_NETS[@]}"; do
if [[ "$(cidr_match "$gw" "$net")" == "True" ]]; then cern_gw=true; break; fi
done
fi
if printf '%s\n' "$domains" | grep -qiE '(^|[[:space:]])([^.]*\.)?cern\.ch$'; then cern_domain=true; fi
echo "[ssid-dns] def_if=${def_if:-?} prim_svc=${prim_svc:-?} ssid=${ssid:-<none>} gw=${gw:-<none>}"
echo "[ssid-dns] domains: $(printf '%s' "$domains" | tr '\n' ' | ')"
echo "[ssid-dns] flags: CERN_SSID=$cern_ssid CERN_GW=$cern_gw CERN_DOMAIN=$cern_domain"
# --- Decide target DNS ---
target="$FALLBACK_DNS" # default
if [[ -n "${ssid:-}" ]] && printf '%s\n' "${HOME_SSIDS[@]}" | grep -qx -- "$ssid"; then
target="Empty" # home Wi-Fi -> router DNS
elif $cern_ssid || $cern_gw || $cern_domain; then
target="$CERN_DNS" # CERN -> CERN DNS
fi
# --- Apply ---
if [[ -n "${prim_svc:-}" ]]; then
set_dns_if_needed "$prim_svc" "$target"
else
echo "[dns] Could not resolve primary service; skipping change."
fi
# --- Show effective default resolver (resolver #1) ---
/usr/sbin/scutil --dns | awk '
$0=="resolver #1" {f=1; next}
f && NF==0 {f=0}
f && /nameserver\[/ {print}
'

Comments are disabled for this gist.