github.com/simpleiot/simpleiot@v0.18.3/client/network-manager-conn.go (about) 1 //go:build linux 2 // +build linux 3 4 package client 5 6 import ( 7 "encoding/binary" 8 "math/bits" 9 "net" 10 "reflect" 11 "strings" 12 13 nm "github.com/Wifx/gonetworkmanager/v2" 14 ) 15 16 // NetworkManagerConn defines a NetworkManager connection 17 type NetworkManagerConn struct { 18 ID string `node:"id"` // matches UUID in NetworkManager 19 Parent string `node:"parent"` 20 Description string `point:"description"` // matches ID in NetworkManager 21 // Type is one of the NetworkManager connection types (i.e. 802-3-ethernet) 22 // See https://developer-old.gnome.org/NetworkManager/stable/ 23 Type string `point:"type"` 24 // Managed flag indicates that SimpleIoT is managing this connection. 25 // All connections in NetworkManager are added to the SIOT tree, but if a 26 // connection is flagged "managed", the SIOT tree is used as the source of 27 // truth, and settings are synchronized one-way from SIOT to NetworkManager. 28 Managed bool `point:"managed"` 29 AutoConnect bool `point:"autoConnect"` 30 AutoConnectPriority int32 `point:"autoConnectPriority"` 31 InterfaceName string `point:"interfaceName"` 32 // LastActivated is the timestamp the connection was last activated in 33 // seconds since the UNIX epoch. Called "timestamp" in NetworkManager. 34 LastActivated uint64 `point:"lastActivated"` 35 IPv4Config IPv4Config `point:"ipv4Config"` 36 IPv6Config IPv6Config `point:"ipv6Config"` 37 WiFiConfig WiFiConfig `point:"wiFiConfig"` 38 // Error contains an error message from the last NetworkManager sync or an 39 // empty string if sync was successful 40 Error string `point:"error"` 41 } 42 43 // WiFiConfig defines 802.11 wireless configuration 44 type WiFiConfig struct { 45 SSID string `point:"ssid"` 46 // From NetworkManager: Key management used for the connection. One of 47 // "none" (WEP), "ieee8021x" (Dynamic WEP), "wpa-none" (Ad-Hoc WPA-PSK), 48 // "wpa-psk" (infrastructure WPA-PSK), "sae" (SAE) or "wpa-eap" 49 // (WPA-Enterprise). This property must be set for any Wi-Fi connection that 50 // uses security. 51 KeyManagement string `point:"keyManagement"` 52 PSK string `point:"psk"` 53 } 54 55 // ResolveNetworkManagerConn returns a NetworkManagerConn from D-Bus settings 56 // Note: Secrets must be added to the connection manually 57 func ResolveNetworkManagerConn(settings nm.ConnectionSettings) NetworkManagerConn { 58 sc := settings["connection"] 59 conn := NetworkManagerConn{ 60 ID: sc["uuid"].(string), 61 Description: sc["id"].(string), 62 Type: sc["type"].(string), 63 AutoConnect: true, 64 } 65 if val, ok := sc["autoconnect"].(bool); ok { 66 conn.AutoConnect = val 67 } 68 if val, ok := sc["autoconnect-priority"].(int32); ok { 69 conn.AutoConnectPriority = val 70 } 71 if val, ok := sc["interface-name"].(string); ok { 72 conn.InterfaceName = val 73 } 74 if val, ok := sc["timestamp"].(uint64); ok { 75 conn.LastActivated = val 76 } 77 78 // Parse IPv4 / IPv6 settings 79 if val, ok := settings["ipv4"]; ok { 80 conn.IPv4Config = ResolveIPv4Config(val) 81 } 82 if val, ok := settings["ipv6"]; ok { 83 conn.IPv6Config = ResolveIPv6Config(val) 84 } 85 86 // Parse WiFiConfig 87 if conn.Type == "802-11-wireless" { 88 sWiFi := settings["802-11-wireless"] 89 if val, ok := sWiFi["ssid"].([]byte); ok { 90 conn.WiFiConfig.SSID = string(val) 91 } 92 sWiFiSecurity := settings["802-11-wireless-security"] 93 if val, ok := sWiFiSecurity["key-mgmt"].(string); ok { 94 conn.WiFiConfig.KeyManagement = val 95 } 96 } 97 98 return conn 99 } 100 101 // DBus returns an object that can be passed over D-Bus 102 // Returns nil if the connection ID does not include the prefix `SimpleIoT:` 103 // See https://developer-old.gnome.org/NetworkManager/stable/ch01.html 104 func (c NetworkManagerConn) DBus() nm.ConnectionSettings { 105 sc := map[string]any{ 106 "uuid": c.ID, 107 "id": c.Description, 108 "type": c.Type, 109 "autoconnect": c.AutoConnect, 110 "autoconnect-priority": c.AutoConnectPriority, 111 } 112 if c.InterfaceName != "" { 113 sc["interface-name"] = c.InterfaceName 114 } 115 settings := nm.ConnectionSettings{ 116 "connection": sc, 117 "ipv4": c.IPv4Config.DBus(), 118 "ipv6": c.IPv6Config.DBus(), 119 } 120 if c.Type == "802-11-wireless" { 121 settings["802-11-wireless"] = map[string]any{ 122 "ssid": []byte(c.WiFiConfig.SSID), 123 } 124 if c.WiFiConfig.KeyManagement != "" { 125 wiFiSecurity := map[string]any{ 126 "key-mgmt": c.WiFiConfig.KeyManagement, 127 } 128 settings["802-11-wireless-security"] = wiFiSecurity 129 // Only add PSK for Managed connections 130 if c.Managed { 131 wiFiSecurity["psk"] = c.WiFiConfig.PSK 132 } 133 } 134 } 135 return settings 136 } 137 138 // Equal returns true if and only if the two connections will produce the same 139 // DBus settings 140 func (c NetworkManagerConn) Equal(c2 NetworkManagerConn) bool { 141 v := reflect.ValueOf(c) 142 v2 := reflect.ValueOf(c2) 143 numFields := v.NumField() 144 t := v.Type() 145 for i := 0; i < numFields; i++ { 146 sf := t.Field(i) 147 // Skip certain fields 148 switch sf.Name { 149 case "Parent": 150 continue 151 case "Managed": 152 continue 153 case "LastActivated": 154 continue 155 case "IPv4Config": 156 continue 157 case "IPv6Config": 158 continue 159 case "Error": 160 continue 161 } 162 if !v.Field(i).Equal(v2.Field(i)) { 163 return false 164 } 165 } 166 return c.IPv4Config.Equal(c2.IPv4Config) && 167 c.IPv6Config.Equal(c2.IPv6Config) 168 } 169 170 // IPv4Address is a string representation of an IPv4 address 171 type IPv4Address string 172 173 // IPv4AddressUint32 converts an IPv4 address in uint32 format to a string 174 func IPv4AddressUint32(n uint32, order binary.ByteOrder) IPv4Address { 175 buf := []byte{0, 0, 0, 0} 176 order.PutUint32(buf, n) 177 return IPv4Address(net.IP(buf).String()) 178 } 179 180 // Uint32 convert an IPv4 address in string format to a uint32 181 func (addr IPv4Address) Uint32(order binary.ByteOrder) uint32 { 182 ip := net.ParseIP(addr.String()).To4() 183 if len(ip) != 4 { 184 return 0 185 } 186 return order.Uint32(ip) 187 } 188 189 // Valid returns true if string is valid IPv4 address 190 func (addr IPv4Address) Valid() bool { 191 str := addr.String() 192 return strings.Contains(str, ".") && net.ParseIP(str).To4() != nil 193 } 194 195 // String returns the underlying string 196 func (addr IPv4Address) String() string { 197 return string(addr) 198 } 199 200 // IPv4Netmask is a string representation of an IPv4 netmask 201 type IPv4Netmask IPv4Address 202 203 // IPv4NetmaskPrefix converts an integer IPv4 prefix to netmask string 204 func IPv4NetmaskPrefix(prefix uint8) IPv4Netmask { 205 var mask uint32 = 0xFFFFFFFF << (32 - prefix) 206 return IPv4Netmask(IPv4AddressUint32(mask, binary.BigEndian)) 207 } 208 209 // Prefix converts a subnet mask string to IPv4 prefix 210 func (str IPv4Netmask) Prefix() uint32 { 211 return uint32(bits.OnesCount32(IPv4Address(str).Uint32(binary.BigEndian))) 212 } 213 214 // Valid returns true if subnet mask in dot notation is valid 215 func (str IPv4Netmask) Valid() bool { 216 if !IPv4Address(str).Valid() { 217 return false 218 } 219 220 mask := IPv4Address(str).Uint32(binary.BigEndian) 221 return (mask & (^mask >> 1)) <= 0 222 } 223 224 // IPv4Config defines data for IPv4 config 225 type IPv4Config struct { 226 StaticIP bool `json:"staticIP" point:"staticIP"` 227 Address IPv4Address `json:"address" point:"address"` 228 Netmask IPv4Netmask `json:"netmask" point:"netmask"` 229 Gateway IPv4Address `json:"gateway" point:"gateway"` 230 DNSServer1 IPv4Address `json:"dnsServer1" point:"dnsServer1"` 231 DNSServer2 IPv4Address `json:"dnsServer2" point:"dnsServer2"` 232 } 233 234 // ResolveIPv4Config creates a new IPv4Config from a map of D-Bus settings 235 func ResolveIPv4Config(settings map[string]any) IPv4Config { 236 c := IPv4Config{ 237 // 'method' setting can be 'auto', 'manual', or 'link-local' 238 // Go is so cool; you can compare interface{} with a string no problem 239 StaticIP: settings["method"] == "manual", 240 } 241 242 // Note: 'address-data' is []any where elements are a map[string]any and 243 // where each map has "address" (string) and "prefix" (uint32) keys 244 addressData, _ := settings["address-data"].([]any) 245 if len(addressData) > 0 { 246 addr1, _ := addressData[0].(map[string]any) 247 str, _ := addr1["address"].(string) 248 c.Address = IPv4Address(str) 249 // Convert integer prefix to string subnet mask format 250 if prefix, ok := addr1["prefix"].(uint32); ok && prefix <= 32 { 251 c.Netmask = IPv4NetmaskPrefix(uint8(prefix)) 252 } 253 } 254 255 str, _ := settings["gateway"].(string) 256 c.Gateway = IPv4Address(str) 257 258 // 'dns' setting is slice of IP addresses in uint32 format 259 dns, _ := settings["dns"].([]uint32) 260 if len(dns) > 0 { 261 c.DNSServer1 = IPv4AddressUint32(dns[0], binary.LittleEndian) 262 } 263 if len(dns) > 1 { 264 c.DNSServer2 = IPv4AddressUint32(dns[1], binary.LittleEndian) 265 } 266 267 return c 268 } 269 270 // Equal returns true if and only if the two IPv4Config structs are equivalent 271 func (c IPv4Config) Equal(c2 IPv4Config) bool { 272 if c.Method() == "auto" && c2.Method() == "auto" { 273 // Ignore other fields; these two IP Configs are automatic / DHCP 274 return true 275 } 276 return reflect.DeepEqual(c, c2) 277 } 278 279 // Method returns the IP configuration method (i.e. "auto" or "manual") 280 func (c IPv4Config) Method() string { 281 if c.StaticIP && 282 c.Address.Valid() && 283 c.Netmask.Valid() && 284 c.Gateway.Valid() { 285 // Manual (Static IP) 286 return "manual" 287 } 288 // Automatic (DHCP) 289 return "auto" 290 } 291 292 // DBus returns the IPv4 settings in a generic map to be sent over D-Bus 293 // See https://developer-old.gnome.org/NetworkManager/stable/settings-ipv4.html 294 func (c IPv4Config) DBus() map[string]any { 295 settings := map[string]any{ 296 "method": c.Method(), 297 } 298 if settings["method"] == "auto" { 299 return settings 300 } 301 302 // Manual (Static IP) 303 settings["address-data"] = []map[string]any{{ 304 "address": c.Address.String(), 305 "prefix": c.Netmask.Prefix(), 306 }} 307 settings["gateway"] = c.Gateway.String() 308 309 dns := make([]uint32, 0, 2) 310 if c.DNSServer1.Valid() { 311 dns = append(dns, c.DNSServer1.Uint32(binary.LittleEndian)) 312 } 313 if c.DNSServer2.Valid() { 314 dns = append(dns, c.DNSServer2.Uint32(binary.LittleEndian)) 315 } 316 settings["dns"] = dns 317 318 return settings 319 } 320 321 // IPv6Address is a string representation of an IPv4 address 322 type IPv6Address string 323 324 // NewIPv6Address converts an IPv6 address in []byte format to a string 325 func NewIPv6Address(bs []byte) IPv6Address { 326 return IPv6Address(net.IP(bs).To16().String()) 327 } 328 329 // Valid return true if string is valid IPv6 address 330 func (addr IPv6Address) Valid() bool { 331 str := addr.String() 332 return strings.Contains(str, ":") && net.ParseIP(str).To16() != nil 333 } 334 335 // Bytes convert an IPv6 address in string format to []byte 336 func (addr IPv6Address) Bytes() []byte { 337 return []byte(net.ParseIP(addr.String()).To16()) 338 } 339 340 // String returns the underlying string 341 func (addr IPv6Address) String() string { 342 return string(addr) 343 } 344 345 // IPv6Config defines data for IPv6 configs 346 type IPv6Config struct { 347 StaticIP bool `json:"staticIP"` 348 Address IPv6Address `json:"address"` 349 Prefix uint8 `json:"prefix"` 350 Gateway IPv6Address `json:"gateway"` 351 DNSServer1 IPv6Address `json:"dnsServer1"` 352 DNSServer2 IPv6Address `json:"dnsServer2"` 353 } 354 355 // ResolveIPv6Config creates a new IPv6Config from a map of D-Bus settings 356 func ResolveIPv6Config(settings map[string]any) IPv6Config { 357 c := IPv6Config{ 358 StaticIP: settings["method"] == "manual", 359 } 360 361 addressData, _ := settings["address-data"].([]any) 362 if len(addressData) > 0 { 363 addr1, _ := addressData[0].(map[string]any) 364 str, _ := addr1["address"].(string) 365 c.Address = IPv6Address(str) 366 if prefix, ok := addr1["prefix"].(uint32); ok && prefix <= 128 { 367 c.Prefix = uint8(prefix) 368 } 369 } 370 371 str, _ := settings["gateway"].(string) 372 c.Gateway = IPv6Address(str) 373 374 // 'dns' setting is slice of IP addresses as 16-byte slices 375 dns, _ := settings["dns"].([][]byte) 376 if len(dns) > 0 { 377 c.DNSServer1 = NewIPv6Address(dns[0]) 378 } 379 if len(dns) > 1 { 380 c.DNSServer2 = NewIPv6Address(dns[1]) 381 } 382 383 return c 384 } 385 386 // Equal returns true if and only if the two IPv6Config structs are equivalent 387 func (c IPv6Config) Equal(c2 IPv6Config) bool { 388 if c.Method() == "auto" && c2.Method() == "auto" { 389 // Ignore other fields; these two IP Configs are automatic / DHCP 390 return true 391 } 392 return reflect.DeepEqual(c, c2) 393 } 394 395 // Method returns the IP configuration method (i.e. "auto" or "manual") 396 func (c IPv6Config) Method() string { 397 if c.StaticIP && 398 c.Address.Valid() && 399 c.Gateway.Valid() { 400 // Manual (Static IP) 401 return "manual" 402 } 403 // Automatic (DHCP) 404 return "auto" 405 } 406 407 // DBus returns the IPv6 settings in a generic map to be sent over D-Bus 408 // See https://developer-old.gnome.org/NetworkManager/stable/settings-ipv6.html 409 func (c IPv6Config) DBus() map[string]any { 410 settings := map[string]any{ 411 "method": c.Method(), 412 } 413 if settings["method"] == "auto" { 414 return settings 415 } 416 417 // Manual (Static IP) 418 settings["address-data"] = []map[string]any{{ 419 "address": c.Address.String(), 420 "prefix": c.Prefix, 421 }} 422 settings["gateway"] = c.Gateway.String() 423 424 dns := make([][]byte, 0, 2) 425 if c.DNSServer1.Valid() { 426 dns = append(dns, c.DNSServer1.Bytes()) 427 } 428 if c.DNSServer2 != "" { 429 dns = append(dns, c.DNSServer2.Bytes()) 430 } 431 settings["dns"] = dns 432 433 return settings 434 }