This is a guide on how to set up a wireless hotspot / software access point with local networking only on devices running the Linuxoperating system.
Find a file
2025-08-19 02:41:19 +02:00
.gitignore add gitignore 2025-08-19 02:21:15 +02:00
LICENCE add licence 2025-08-19 02:37:50 +02:00
README.md Update README.md 2025-08-19 02:41:19 +02:00

ohneLAN v1.0

This is a guide on how to set up a wireless hotspot / software access point with local networking only on devices running the Linux operating system.

The documentation of theohneLAN specifically described here is an instance of said hotspot set up on a Raspberry Pi and will be referenced as an example setup in this guide.

Materials

Instructions

Setting up the operating system

First, the SD-Card is flashed with the Raspberry Pi OS using the rpi-imager. The imager allows to configure the Pi in advance, especially the SSH-Server option will be of use.

Afterwards, boot up the system and connect to it either via direct peripheral connection or SSH. Ensure that the Pi has internet connection.

Log in with a predefined user (needs sudo privileges, see here), and type in following commands to upgrade the system:

sudo apt update --allow-releaseinfo-change && sudo apt upgrade

Installing the tools

For the hotspot to be configured, we need following tools:

  1. hostapd (v2.10)
  2. dnsmasq (v2.85-1)

These two tools represent the two main steps for setting up an access point:

  1. Setting up the Wi-Fi link layer so wireless clients can associate to the software access point and exchange IP packets with it.
  2. Setting up the network configuration like DNS / DHCP.

Simply install them using:

sudo apt install -y hostapd dnsmasq

Configuring the wireless adapter interface

Check your wireless device for nl80211 compatibility (which supports the AP wireless mode). This can be verified using iw list while your interface is plugged in. Under the Supported interface modes block there should be AP lsited:

Wiphy phy1
...
	Supported interface modes:
		 * IBSS
		 * managed
		 * AP <-- this needs to be displayed
		 * AP/VLAN
		 * WDS
		 * monitor
		 * mesh point
...

To make sure when plugging in, the wireless adapter will always present the same interface name, a udev rule needs to be applied.

"udev (userspace /dev) is a device manager for the Linux kernel. As the successor of devfsd and hotplug, udev primarily manages device nodes in the /dev directory. At the same time, udev also handles all user space events raised when hardware devices are added into the system or removed from it, including firmware loading as required by certain devices. " -- Wikipedia

Create the file /etc/udev/rules.d/10-ohnelan.rules and apply these options:

SUBSYSTEM=="net", ACTION=="add", ATTR{address}=="<interface_mac>", NAME="<preferred_interface_name>"

SUBSYSTEM=="net": Rule applies to devices like network interfaces ACTION=="add": The rule is triggered when a new network device is added to the system (on boot) ATTR{address}=="<interface_mac>": Match it with the MAC address of the network device NAME="<preferred_interface_name>": Assigns the preferred name to the matching network device

After reboot the new rule is applied.

hostapd

"Hostapd is an IEEE 802.11 access point and IEEE 802.1X/WPA/WPA2/EAP/RADIUS authenticator [...] used to communicate with the kernel drivers (eg. cfg80211) via the nl80211 interface." "nl80211 made it possible to handle wireless infrastructures using a single interface only (standard traffic and authentication)"-- About hostapd

Here is an example configuration of /etc/hostapd/hostapd.conf:

## General information
ssid=<network_name> # network name
interface=<interface_name> # interface name of the wireless adapter
driver=nl80211 # preferred driver
country_code=DE
ieee80211d=1 # advertise metadata to clients
ap_max_inactivity=600 # clear station table with timeouting inactive clients

## IEEE 802.11u options (enable interworking with external networks)
interworking=1
access_network_type=3 # "Free Public Network"
internet=0 # network connection unspecified

## Enable IEEE 802.11n support
hw_mode=g # Operation Mode [a,b,g,ad,n]
channel=acs_survey # auto channel selection
chanlist=1,6,11 # list to scan for auto selection
ieee80211n=1 # enable IEEE 802.11n to specify the band
wmm_enabled=1 # enable prioritization of traffic for different types of data
ht_capab=[HT40+][SHORT-GI-40][TX-STBC][RX-STBC1] # specify supported 802.11n features of the AP

## WPA options
auth_algs=0 # choose Open System Auth and/or Shared Key Auth
wpa=2 # Enable WPA and/or WPA2
wpa_pairwise=CCMP # set accepted cipher suites
wpa_key_mgmt=WPA-PSK # set accepted key management alogrithms
wpa_psk=<wpa_psk> # Pre Shared Key for authentication

## Event logging configuration
logger_syslog=-1 # write to syslog
logger_syslog_level=1 # level debug

All possible configuration options can be looked up here


Notes for auto channel selection:

  1. Ensure that you are using hostapd program that is patched with Infineon provided hostapd patches. This will ensure that the CONFIG_ACS build option is enabled.
  2. Ensure that your device firmware supports the ACS function. If your device does not support ACS, you will notice the error below:
ACS: Possibly channel configuration is invalid, please report this along with your config file.
ACS: Failed to start
wlan0: AP-DISABLED

Notes for ht_capab:

The capabilities are dependent on the wireless adapter. A way to list these is to use the iw list command. Look for blocks that look like this:

HT Capabilities:
Capabilities: 0x17e
RX STBC
TX STBC
short GI (20 MHz)
max A-MSDU length: 3839 bytes
supported MCS:
0-15
32

or this:

VHT Capabilities:
Capabilities: 0x03
VHT80
VHT160
VHT MCS 0-9

Note for wpa_psk:

To avoid hard-coding the plain-text passphrase of the network into the configuration file, a WPA Pre-Shared-Key can be generated from the SSID and the passphrase like so:

wpa_passphrase "<ssid>" "<passphrase>"

After saving the configuration file, the service needs to be started:

sudo systemctl enable --now hostapd

Common errors include:

  1. hostapd.service is masked: Unmask it with:
sudo systemctl unmask hostapd
  1. WiFi is soft-blocked: The WLAN country needs to be defined (on the Raspberry Pi) with:
sudo raspi-config

dnsmask

"Dnsmasq is a lightweight, easy to configure, DNS forwarder and DHCP server. It is designed to provide DNS and optionally, DHCP, to a small network." -- wiki.debian

Create a file /etc/dnsmasq.d/ohnelan.conf.

Following is an example configuration of the file:

interface=<interface_name> # interface to listen for DHCP/DNS requests
dhcp-range=192.168.178.2,192.168.178.254,24h # create a range of valid host-IPs, lease time
dhcp-option=3,192.168.178.1 # define the Gateway-IP
dhcp-option=6,192.168.178.1 # define the DNS-Server-IP
no-dhcp-interface=eth0 # no DHCP service on specified interface
server=192.168.178.1 # add a specific nameserver
no-resolv # don't read /etc/resolv.conf (or other)
no-poll # don't poll /etc/resolv.conf (or other)
address=/.local/192.168.178.1 # force `.local` domains to `192.168.178.1`
address=/#/192.168.178.1 # force `#` -> all domains to `192.168.178.1`

All possible configuration options can be looked up here

After saving the configuration file, the service needs to be started:

sudo systemctl enable --now dnsmasq

Setting up a static IP

To make sure the private IP address of the interface will stay static, systemd-networkd can be configured by creating a configuration file /etc/systemd/network/10-ohnelan.network, that can be configured like so:

[Match]
Name=ol1 # specify the interface to match noww

[Network]
Address=192.168.178.1/24 # specify private IP with auto `Address Conflict Detection`
ConfigureWithoutCarrier=yes # configure without the presence of a carrier
IPv6AcceptRA=no # disable IPv6 `Router Advertisements`
LinkLocalAddressing=no # disable link-local IPv6 address auto assignment

[DHCP]
UseDNS=no # don't receive any DNS server (from `/etc/resolv.conf`)

_All possible configuration options can be looked up in the man page like so man systemd.network

After saving the configuration file, the service needs to be started:

sudo systemctl enable --now systemd-networkd

Make sure no other service is binding to port 53.

Boot persistence

After reboot, the services may not start in the order for it to work fully automatically. To resolve this, first append DAEMON_OPTS="-dd -t -f /<log_folder>/hostapd.log" to /etc/default/hostapd like so:

sudo bash -c "echo 'DAEMON_OPTS=\"-dd -t -f /<log_folder>/hostapd.log\"' >> /etc/default/hostapd"

and create the unit file /etc/systemd/system/multi-user.target.wants/hostapd.service:

[Unit]
Description=Access point and authentication server for Wi-Fi and Ethernet
Documentation=man:hostapd(8)
After=network.target

[Service]
Type=forking # considered started after binary executes
PIDFile=/run/hostapd.pid # location of the binary `PID` to determine main process
Restart=on-failure
RestartSec=2
Environment=DAEMON_CONF=/etc/hostapd/hostapd.conf
EnvironmentFile=-/etc/default/hostapd # don't fail if the file doesn't exist
ExecStartPre=/bin/sleep 5 # wait before running hostapd
ExecStart=/usr/sbin/hostapd -B -P /run/hostapd.pid $DAEMON_OPTS ${DAEMON_CONF}

[Install]
WantedBy=multi-user.target # start, when reached `runlevel 3`

_All possible configuration options can be looked up in the man pages like so man systemd.service and man systemd.unit

To finalize it, run sudo systemctl daemon-reload, check for errors, and reboot the system sudo reboot.

Optional

At this state of configuration everything should work. Meaning:

  • the system starts
  • the interface gets configured (static IP, static name)
  • DNS / DHCP server is running
  • the network gets advertised
  • clients can connect to it

Great, but these is one issue that should be resolved.

Modern devices, especially Smartphones, can detect the absence of outbound networking. This usually won't hinder the use of the network, but often times throws a waring, that needs confirmation to proceed with the connection. This process is often times referred to as Captive Portal Checking.

The way to resolv this seems impossible (at least for Goole Devices), because in addition to them requesting a 204 HTTP Status Code from a specific website, connection to the Google service is also tested.

Faking it might seem possible because the dnsmasq configuration already redirects all outbound requests to the local router. We only need to set up a local web server that responds with 204 No Content for each of these requests.

Example Python Server configuration:

from http.server import HTTPServer, BaseHTTPRequestHandler
import threading

class NoContentHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(204)  # 204 No Content
        self.end_headers()

    def do_POST(self):
        self.send_response(204)
        self.end_headers()

    def do_PUT(self):
        self.send_response(204)
        self.end_headers()

    def do_DELETE(self):
        self.send_response(204)
        self.end_headers()

    def do_HEAD(self):
        self.send_response(204)
        self.end_headers()

    def log_message(self, format, *args):
        return  # Suppress console logging

def run_http_server():
    httpd = HTTPServer(('0.0.0.0', 80), NoContentHandler)
    print("Captive-Captive-Portal server running on port 80...")
    httpd.serve_forever()

def run_https_server():
    httpd = HTTPServer(('0.0.0.0', 443), NoContentHandler)
    print("Captive-Captive-Portal server running on port 443...")
    httpd.serve_forever()

if __name__ == '__main__':
    # Run both servers on separate threads
    threading.Thread(target=run_http_server).start()
    threading.Thread(target=run_https_server).start()

This makes things a bit better. Instead of the no internet connection warning on my phone, I now get restricted access.

Unfortunately, faking a valid connection to google.com is almost impossible, which is why I won't bother doing it.

There is an option to disable captive portal checking on individual devices. If you're interested in learning more about this, have a look at this thread or this blog.

Licence

CC0