NixOS access point via hostapd

I would like to make available an access point from my NixOS laptop:

services.hostapd.enable = true;
services.hostapd.interface = “wlp4s0”;
services.hostapd.ssid = “myname”;
services.hostapd.wpaPassphrase = “mysecret”;

However, when another laptop tries to connect to it, it’s immediately disconnected and tries to connect again in an infinite loop.

I think we should have a module where you only need to put in the password and an ssid name and then it should work. hostapd should only be an implementation detail.

An interface like services.wifi_hotspot = {wpaPassPhrase = “mysecret”; ssid = “myname”};

Any other configuration options are just line noise as far as I am concerned.

1 Like

This doesn’t solve your problem declaratively, but I think the easiest option currently available is creating a “shared connection” using network-manager.

Since networking has an overhaul on its way with systemd-networkd anyway, I imagine this might also bring in an easier way to set up a wireless network declaratively.

That aside, I agree it would be nice to have this work simply.

1 Like

@lheckemann do you know if the overhaul will touch how the firewall works? I have a few grievances how forwarding (isn’t) handled and might put a bit of time in improving it otherwise. is there a discussion somewhere what will be done?

I am using the code below
It allows to configure “shared connection” declaratively, and disallow network-manager to use the virtual interface used by hostapd

# "wlp3s0" is the hardware device, "wlan-station0" is for wifi-client managed by network manager, "wlan-ap0" is for hostap
networking.wlanInterfaces = {
  "wlan-station0" = { device = "wlp3s0";                            };
  "wlan-ap0"      = { device = "wlp3s0"; mac = "08:11:96:0e:08:0a"; };
};

networking.networkmanager.unmanaged = [ "interface-name:wlp*" ]
    ++ lib.optional config.services.hostapd.enable "interface-name:${config.services.hostapd.interface}";

services.hostapd = {
  enable        = true;
  interface     = "wlan-ap0";
  hwMode        = "g";
  ssid          = "nix";
  wpaPassphrase = "mysekret";
};

networking.interfaces."wlan-ap0".ipv4.addresses =
  lib.optionals config.services.hostapd.enable [{ address = "192.168.12.1"; prefixLength = 24; }];

services.dnsmasq = lib.optionalAttrs config.services.hostapd.enable {
  enable = true;
  extraConfig = ''
    interface=wlan-ap0
    bind-interfaces
    dhcp-range=192.168.12.10,192.168.12.254,24h
  '';
};
networking.firewall.allowedUDPPorts = lib.optionals config.services.hostapd.enable [53 67]; # DNS & DHCP
services.haveged.enable = config.services.hostapd.enable;

And there is also NAT settings

1 Like

This is my version of doing so. With all iptables rules set up, it is fully declaritive

{ pkgs, config, lib, ... }:

with lib;

let
  cfg = config.icebox.static.system.wifi-relay;
  inherit (config.icebox.static.lib.configs) devices;
in {
  options.icebox.static.system.wifi-relay = {
    enable = mkOption {
      type = types.bool;
      default = false;
      description = "Enable Wi-Fi relay on the same card.";
    };

    network-interface = mkOption {
      type = types.enum devices.network-interface;
      description = "Wi-Fi network interface to use";
    };

    dns = mkOption {
      default = "192.168.12.1";
      type = types.str;
      description = "DNS address to advertise through DHCP";
      example = "192.168.12.1, 8.8.8.8, 1.1.1.1";
    };
  };

  config = mkIf (cfg.enable) {
    # "wlan-station0" is for wifi-client managed by network manager, "wlan-ap0" is for hostap
    networking.wlanInterfaces = {
      "wlan-station0" = { device = cfg.network-interface; };
      "wlan-ap0" = {
        device = cfg.network-interface;
        mac = "08:11:96:0e:08:0a";
      };
    };

    networking.networkmanager.unmanaged =
      [ "interface-name:${cfg.network-interface}" "interface-name:wlan-ap0" ];

    services.hostapd = {
      enable = true;
      interface = "wlan-ap0";
      hwMode = "g";
      ssid = "AP-Freedom";
      wpaPassphrase = "88888888";
    };

    networking.interfaces."wlan-ap0".ipv4.addresses = [{
      address = "192.168.12.1";
      prefixLength = 24;
    }];

    services.dhcpd4 = {
      enable = true;
      interfaces = [ "wlan-ap0" ];
      extraConfig = ''
        option subnet-mask 255.255.255.0;
        option broadcast-address 192.168.12.255;
        option routers 192.168.12.1;
        option domain-name-servers ${cfg.dns};
        subnet 192.168.12.0 netmask 255.255.255.0 {
          range 192.168.12.100 192.168.12.200;
        }
      '';
    };
    networking.firewall.allowedUDPPorts = [ 53 67 ]; # DNS & DHCP
    services.haveged.enable =
      config.services.hostapd.enable; # Sometimes slow connection speeds are attributed to absence of haveged.

    boot.kernel.sysctl."net.ipv4.ip_forward" = 1; # Enable package forwarding.

    systemd.services.wifi-relay = let inherit (pkgs) iptables gnugrep;
    in {
      description = "iptables rules for wifi-relay";
      after = [ "dhcpd4.service" ];
      wantedBy = [ "multi-user.target" ];
      script = ''
        ${iptables}/bin/iptables -w -t nat -I POSTROUTING -s 192.168.12.0/24 ! -o wlan-ap0 -j MASQUERADE
        ${iptables}/bin/iptables -w -I FORWARD -i wlan-ap0 -s 192.168.12.0/24 -j ACCEPT
        ${iptables}/bin/iptables -w -I FORWARD -i wlan-station0 -d 192.168.12.0/24 -j ACCEPT
      '';
    };
  };
}
1 Like

I tried your config. I just replaced both occurrences of wpl3s0 by wlan0 and the mac address by the wlan0 mac address. When I build, dnsmasq.service fails to start and this can be found in the journal:

Mar 07 22:22:28 nixos dnsmasq-pre-start[104340]: dnsmasq: syntax check OK.
Mar 07 22:22:28 nixos dnsmasq[104341]: dnsmasq: unknown interface wlan-ap0
Mar 07 22:22:28 nixos dnsmasq[104341]: unknown interface wlan-ap0
Mar 07 22:22:28 nixos dnsmasq[104341]: FAILED to start up
Mar 07 22:22:28 nixos systemd[1]: dnsmasq.service: Main process exited, code=exited, status=2/INVALIDARGUMENT

Do you have any idea, why this might be?

I somehow got to a point, where I don’t get an error while building. I can connect to the hotspot and use it to ssh into the server, but I can not access the internet with it.

Is there no need to bridge interfaces?

Your config compiles and switch happens with no problem. But the AP is not on. Are there any perquisites? Am I supposed to actually change something, like the mac address?