2

When connecting my M1 Mac (macOS 14/Sonoma) to my iPhone's Personal Hotspot for the first time after a reboot, I see the 'interlocked chain links' iOS hotspot icon icon in the toolbar, and my Mac can successfully use the internet. However, if I disconnect from Wi-Fi and reconnect, I see the 'signal bar with exclamation mark' iOS hotspot icon icon instead, which Apple's support website describes as:

No internet. This icon is displayed when a router and IP address can't be assigned to the Wi-Fi interface.

Is there any way to fix this without rebooting my computer? Basic solutions that I've tried before that didn't work:

  • Using USB tethering instead: it works initially but suffers from the exact same problem if I disconnect and then reconnect the cable
  • Turning Wi-Fi on and off
  • Clicking 'Renew DHCP lease'
  • Turning Personal Hotspot on/off on the iPhone or turning on Compatibility Mode
  • Enabling or disabling Bluetooth on the Mac/iPhone
  • Clicking 'Forget Network' on the Mac and reconnecting from scratch
  • Putting the phone closer to the laptop
5
  • Try this instead of restarting - switch off wifi and switch it on again. Click the wifi icon on the menu bar. In the drop down menu select Turn Wifi off. Wait for 10-20 seconds. Then again click the wifi icon in the menu and from the drop down menu select Turn Wifi on. Wait for sometime for the Mac to scan for wifi network. Click the Wifi icon in the menubar and it will show you a list of all the wifi network it has detected. The iPhone hotspot will also appear here and you can click to connect to it. Before connecting your Mac to the hotspot always make sure that hotspot is ON in iPhone
    – sfxedit
    Commented Nov 21 at 13:25
  • The above technique works like 75% of the time for me. Let me know if it works for you and I'll convert the above into an answer.
    – sfxedit
    Commented Nov 21 at 13:27
  • @sfxedit thanks. Yes, I did try that, also tried putting phone in Airplane mode and back, enabling compatibility mode for Personal Hotspot, clicking renew DHCP, and everything else I could find online. In the end nothing worked except the scipt below. Commented Nov 21 at 13:31
  • Ok. One other thing I have noticed is that the phone needs to be near the Mac too so that it can connect / detect it via Bluetooth (which should be ON on both devices), for better success. (Yeah, hotspot connection is a pain with iPhone - I now prefer to use hotspot with USB tethering instead, which is more reliable).
    – sfxedit
    Commented Nov 21 at 13:50
  • 1
    @sfxedit updated the question, thanks. USB tethering has the exact same issue on my phone, I believe it could be related to T-Mobile's weird tethering configuration profile for the iPhone. Commented Nov 21 at 21:19

1 Answer 1

2

Option 1: Use Mac's built-in Wireless Diagnostics tool

Apple provides a built-in tool called Wireless Diagnostics which automatically solves many networking issues. You can run it by searching for the tool name in Spotlight or going to /System/Library/CoreServices/Applications/ where the app is located. Alternatively, you can start it from the terminal, where it will also print a lot of useful debugging information, even if it fails to fix the issue:

sudo wdutil diagnose -q # -q flag is used to skip the initial Yes/No prompt

Option 2: Use a Python script to save and later restore working network settings

If the Wireless Diagnostics / wdutil solution fails to work, use these two Python scripts that I wrote. This may seem like a bit of overkill but based on my extensive testing, nothing else (short of a reboot) can fix tethering. Unfortunately, ~5% of the time even the script solution fails and I'm forced to reboot OSX to fix connectivity, but it does save time the other 95% of the time.

Script 1: Export working network settings while tethering works

Reboot the machine, connect to Personal Hotspot, and then persist the valid network configuration using the following Python script. The script will try to save the configuration for both Wi-Fi and USB tethering by default. It will generate a .json file containing the interface name and the network name, so you could potentially use this to store the network information for non-hotspot networks as well.

AFAIK all modern iPhones use 192.0.0.1 and 172.0.0.1 as their router address, however each phone uses a different IPv6 router address, so running this script is unavoidable.

import subprocess
import re
import json

DEVICE_NAME_WIFI = "Wi-Fi"  # Tethering via Wi-Fi
DEVICE_NAME_USB = "iPhone USB"  # Tethering via USB cable


def remove_non_alphanumeric(string):
    if string is None:
        return ""
    return "".join(re.findall("[A-Za-z0-9]", string))


def get_config_filename(device_name, interface):
    filename = f"network_settings_{remove_non_alphanumeric(device_name)}"
    if device_name == DEVICE_NAME_WIFI:
        try:
            network_info = subprocess.check_output(
                ["networksetup", "-getairportnetwork", interface], text=True
            )
            network_name = remove_non_alphanumeric(network_info.split(": ")[1].strip())
            network_name = network_name[:15]  # Avoid saving overly long network names
            suffix = f"_{network_name}"
        except Exception as e:
            suffix = "_unknown"
    return f"{filename}{suffix}.json"


def get_network_info(device_name):
    print(f"Saving {device_name} settings...")
    try:
        hardware_ports = subprocess.check_output(
            ["networksetup", "-listallhardwareports"], text=True
        )

        device_match = re.search(
            f"Hardware Port: {device_name}\nDevice: (\\w+)", hardware_ports
        )
        if not device_match:
            print(f"Failed: No {device_name} interface found")
            if device_name == DEVICE_NAME_USB:
                print(
                    "Ensure your iPhone is plugged in and Personal Hotspot is enabled to save USB tethering settings."
                )
            return
        interface = device_match.group(1)

        ifconfig_output = (
            subprocess.check_output(f"ifconfig {interface}", shell=True)
            .decode()
            .splitlines()
        )

        ipv4_address = None
        ipv6_addresses = []
        for line in ifconfig_output:
            if "inet " in line:
                ipv4_address = line.split()[1]
            elif "inet6 " in line:
                ipv6_addresses.append(line.split()[1].split("%")[0])

        netstat_output = (
            subprocess.check_output(
                f"netstat -nr | grep default | grep {interface}", shell=True
            )
            .decode()
            .splitlines()
        )

        ipv4_router = ipv6_router = None
        for line in netstat_output:
            parts = line.split()
            if len(parts) > 1:
                if ":" not in parts[1]:
                    ipv4_router = parts[1]
                else:
                    ipv6_router = parts[1].split("%")[0]

        config = {
            "network_service_name": device_name,
            "network_interface_id": interface,
            "ipv4_address": ipv4_address or "Unknown",
            "ipv4_subnet_mask": "255.255.255.0",
            "ipv4_router": ipv4_router or "Unknown",
            "ipv6_primary_address": ipv6_addresses[0] if ipv6_addresses else "Unknown",
            "ipv6_temporary_address": (
                ipv6_addresses[1] if ipv6_addresses[1:] else "Unknown"
            ),
            "ipv6_clat46_address": (
                ipv6_addresses[2] if ipv6_addresses[2:] else "Unknown"
            ),
            "ipv6_prefix_length": "64",
            "ipv6_router": ipv6_router or "Unknown",
        }

        config_filename = get_config_filename(device_name, interface)

        with open(config_filename, "w") as f:
            json.dump(config, f, indent=2)

        print(
            f"Success: Saved config for {device_name} ({interface}) to {config_filename}"
        )

    except Exception as e:
        print(f"Failed: Error saving config for {device_name}: {str(e)}")


if __name__ == "__main__":
    get_network_info(DEVICE_NAME_WIFI)
    print("\n")
    get_network_info(DEVICE_NAME_USB)

The Python script will generate a JSON file with working IPv4 and IPv6 settings within the same folder.

Script 2: Enable manual TCP/IP configuration → restore settings from Script 1 → re-enable DHCP again

Create another Python script. Note that running it requires sudo access, because changing most network settings on Mac cannot be done without sudo privileges.

import subprocess
import json
import time
import sys

def run_cmd(cmd, ignore_errors=False):
    try:
        subprocess.run(cmd, check=True)
        print(f"Success: {' '.join(cmd)}")
        return True
    except subprocess.CalledProcessError as e:
        if not ignore_errors:
            print(f"Failed: {' '.join(cmd)}: {e}")
        return False
    
def enable_network_autoconfiguration(config):
    print("Resetting to automatic configuration...")
    
    # Remove all configured IP addresses
    output = subprocess.check_output(
        f"ifconfig {config['network_interface_id']}", 
        shell=True
    ).decode().splitlines()

    for line in output:
        if "inet " in line:
            run_cmd([
                "sudo", "ifconfig", config["network_interface_id"],
                "inet", line.split()[1], "remove"
            ])
        elif "inet6 " in line:
            run_cmd([
                "sudo", "ifconfig", config["network_interface_id"],
                "inet6", "del", line.split()[1]
            ])

    # Reset to automatic configuration
    run_cmd(["sudo", "networksetup", "-setdhcp", config["network_service_name"]])
    run_cmd(["sudo", "networksetup", "-setv6automatic", config["network_service_name"]])

def load_network_settings(config):
    print("\nSetting manual IP configuration...")
    
    # Configure IPv4 settings
    run_cmd([
        "sudo", "networksetup", "-setmanual",
        config["network_service_name"],
        config["ipv4_address"],
        config["ipv4_subnet_mask"],
        config["ipv4_router"]
    ])

    # Update IPv4 routing
    run_cmd(["sudo", "route", "delete", "default"])
    run_cmd(["sudo", "route", "add", "default", config["ipv4_router"]])

    # Configure IPv6 settings
    run_cmd([
        "sudo", "networksetup", "-setv6manual",
        config["network_service_name"],
        config["ipv6_primary_address"],
        config["ipv6_prefix_length"],
        config["ipv6_router"]
    ])
    
    # Add additional IPv6 addresses
    run_cmd([
        "sudo", "ifconfig", config["network_interface_id"],
        "inet6", "add", config["ipv6_temporary_address"],
        "prefixlen", config["ipv6_prefix_length"]
    ])
    run_cmd([
        "sudo", "ifconfig", config["network_interface_id"],
        "inet6", "add", config["ipv6_clat46_address"],
        "prefixlen", config["ipv6_prefix_length"]
    ])

    # Update IPv6 routing
    run_cmd(["sudo", "route", "delete", "-inet6", "default"], ignore_errors=True)
    run_cmd([
        "sudo", "route", "add", "-inet6", "default",
        f"{config['ipv6_router']}%{config['network_interface_id']}"
    ])

if __name__ == "__main__":
    with open(sys.argv[1]) as f:
       config = json.load(f)
    load_network_settings(config)
    print("\nWaiting for 1 second before resetting back to auto/DHCP network configuration...\n")
    time.sleep(1)
    enable_network_autoconfiguration(config)

When tethering stops working, execute the script, passing the generated config file name into it:

python3 load_working_network_settings.py network_settings_en0.json

Voila! Your internet access should (hopefully) be restored now.

Option 3: Reboot your Mac

If the first two options fail, rebooting the Mac is the only option. If that doesn't work, try running the Wireless Diagnostics tool again after the reboot.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .