From f22211b11ee8c381c79206bf555380f3c6db4533 Mon Sep 17 00:00:00 2001 From: Patrick Balsiger Date: Fri, 2 May 2025 13:49:24 +0200 Subject: [PATCH] initial commit --- .gitignore | 1 + Makefile | 5 ++ README.md | 33 ++++++++ go.mod | 5 ++ go.sum | 2 + main.go | 242 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 288 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4b4a28f --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +pifi \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..edb1b77 --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +build: + env GOOS=linux GOARCH=arm64 go build -o pifi main.go + +upload: + scp pifi pi@rpi-40ac:/home/pi/pifi diff --git a/README.md b/README.md new file mode 100644 index 0000000..613333b --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# PiFi + +This is a simple tool to create and manage a hotspot on a Raspberry Pi (ARM64). +The code is based on https://github.com/NetworkManager/NetworkManager/blob/main/examples/python/dbus/wifi-hotspot.py +and adds following features: + +- Environment variables or intput parameters for SSID and password +- Option to remove the connection + +## Build + +```bash +make build +``` + +## Usage + +```bash +sudo ./pifi wlan0 up "My Hotspot" "my password" +sudo ./pifi wlan0 down +sudo ./pifi wlan0 remove +``` + +## Environment variables + +The hotspot will use the environment variables `WIFI_SSID` and `WIFI_PASSWORD` if they are set. + +```bash +export WIFI_SSID="My Hotspot" +export WIFI_PASSWORD="my password" + +sudo -E ./pifi wlan0 up +``` diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..97359c0 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module dcentral/pifi + +go 1.23.4 + +require github.com/godbus/dbus/v5 v5.1.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..024b269 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= diff --git a/main.go b/main.go new file mode 100644 index 0000000..16f4f48 --- /dev/null +++ b/main.go @@ -0,0 +1,242 @@ +// Usage: go run hotspot.go [up|down|remove] [ssid] [password] + +package main + +import ( + "fmt" + "log" + "os" + "time" + + "github.com/godbus/dbus/v5" +) + +const ( + ourUUID = "7d706027-727c-4d4c-a816-f0e1b99db8ab" +) + +var ( + defaultSSID = "PIAP" + defaultPassword = "raspberry" +) + +func usage() { + fmt.Printf("Usage: %s [up|down|remove] [ssid] [password]\n", os.Args[0]) + os.Exit(0) +} + +func startAccessPoint(conn *dbus.Conn, connPath, devPath dbus.ObjectPath) error { + nmObj := conn.Object( + "org.freedesktop.NetworkManager", + "/org/freedesktop/NetworkManager", + ) + + // Activate the connection + var activePath dbus.ObjectPath + err := nmObj. + Call("org.freedesktop.NetworkManager.ActivateConnection", 0, + connPath, devPath, dbus.ObjectPath("/")). + Store(&activePath) + if err != nil { + return fmt.Errorf("ActivateConnection failed: %v", err) + } + + // Wait until the connection is activated + props := conn.Object( + "org.freedesktop.NetworkManager", + activePath, + ) + + start := time.Now() + for time.Since(start) < 10*time.Second { + var stateVar dbus.Variant + err = props. + Call("org.freedesktop.DBus.Properties.Get", 0, + "org.freedesktop.NetworkManager.Connection.Active", + "State"). + Store(&stateVar) + if err != nil { + return fmt.Errorf("Properties.Get(State) failed: %v", err) + } + if state, ok := stateVar.Value().(uint32); ok && state == 2 { + fmt.Println("Access point started") + return nil + } + time.Sleep(1 * time.Second) + } + return fmt.Errorf("failed to start access point") +} + +func stopAccessPoint(conn *dbus.Conn, devPath dbus.ObjectPath) error { + devObj := conn.Object( + "org.freedesktop.NetworkManager", + devPath, + ) + err := devObj. + Call("org.freedesktop.NetworkManager.Device.Disconnect", 0). + Err + if err != nil { + return fmt.Errorf("Device.Disconnect failed: %v", err) + } + fmt.Println("Access point stopped") + return nil +} + +func removeConnection(conn *dbus.Conn, connPath dbus.ObjectPath) error { + connObj := conn.Object( + "org.freedesktop.NetworkManager", + connPath, + ) + err := connObj. + Call("org.freedesktop.NetworkManager.Settings.Connection.Delete", 0). + Err + if err != nil { + return fmt.Errorf("Connection.Delete failed: %v", err) + } + fmt.Println("Connection removed") + return nil +} + +func main() { + if len(os.Args) < 3 { + usage() + } + iface := os.Args[1] + op := os.Args[2] + + // Get SSID and password from args, env vars or use defaults + ssid := defaultSSID + password := defaultPassword + + // Check environment variables first + if v, ok := os.LookupEnv("WIFI_SSID"); ok { + ssid = v + } + if v, ok := os.LookupEnv("WIFI_PASSWORD"); ok { + password = v + } + + // Command line args override environment variables + if op == "up" && len(os.Args) >= 5 { + ssid = os.Args[3] + password = os.Args[4] + } + + // Connect to the system bus + conn, err := dbus.SystemBus() + if err != nil { + log.Fatalf("Failed to connect to system bus: %v", err) + } + + // Get the Settings interface + settingsObj := conn.Object( + "org.freedesktop.NetworkManager", + "/org/freedesktop/NetworkManager/Settings", + ) + + // List existing connections + var paths []dbus.ObjectPath + err = settingsObj. + Call("org.freedesktop.NetworkManager.Settings.ListConnections", 0). + Store(&paths) + if err != nil { + log.Fatalf("ListConnections failed: %v", err) + } + + // Look up our connection by UUID + var connPath dbus.ObjectPath + for _, p := range paths { + obj := conn.Object( + "org.freedesktop.NetworkManager", + p, + ) + var cfg map[string]map[string]dbus.Variant + err = obj. + Call("org.freedesktop.NetworkManager.Settings.Connection.GetSettings", 0). + Store(&cfg) + if err != nil { + continue + } + if v, ok := cfg["connection"]["uuid"].Value().(string); ok && v == ourUUID { + connPath = p + break + } + } + + // If not found, add it + if connPath == "" { + settingsMap := map[string]map[string]dbus.Variant{ + "connection": { + "type": dbus.MakeVariant("802-11-wireless"), + "uuid": dbus.MakeVariant(ourUUID), + "id": dbus.MakeVariant(ssid), + "autoconnect": dbus.MakeVariant(false), + }, + "802-11-wireless": { + "ssid": dbus.MakeVariant([]byte(ssid)), + "mode": dbus.MakeVariant("ap"), + "band": dbus.MakeVariant("bg"), + "channel": dbus.MakeVariant(uint32(1)), + }, + "802-11-wireless-security": { + "key-mgmt": dbus.MakeVariant("wpa-psk"), + "psk": dbus.MakeVariant(password), + }, + "ipv4": { + "method": dbus.MakeVariant("shared"), + }, + "ipv6": { + "method": dbus.MakeVariant("ignore"), + }, + } + + err = settingsObj. + Call("org.freedesktop.NetworkManager.Settings.AddConnection", 0, settingsMap). + Store(&connPath) + if err != nil { + log.Fatalf("AddConnection failed: %v", err) + } + } + + // Get the NetworkManager interface + nmObj := conn.Object( + "org.freedesktop.NetworkManager", + "/org/freedesktop/NetworkManager", + ) + + // Find the device by interface name + var devPath dbus.ObjectPath + err = nmObj. + Call("org.freedesktop.NetworkManager.GetDeviceByIpIface", 0, iface). + Store(&devPath) + if err != nil { + log.Fatalf("GetDeviceByIpIface(%s) failed: %v", iface, err) + } + + switch op { + case "up": + if err := startAccessPoint(conn, connPath, devPath); err != nil { + log.Fatal(err) + } + os.Exit(0) + + case "down": + if err := stopAccessPoint(conn, devPath); err != nil { + log.Fatal(err) + } + os.Exit(0) + + case "remove": + if connPath == "" { + fmt.Println("No connection found to remove") + os.Exit(0) + } + if err := removeConnection(conn, connPath); err != nil { + log.Fatal(err) + } + os.Exit(0) + + default: + usage() + } +}