From 4c0ea8f440fbd96799f935d6a3aa1a68ac6b02cf Mon Sep 17 00:00:00 2001 From: Milan Lenco Date: Thu, 20 Jul 2023 10:00:13 +0200 Subject: [PATCH] Support parsing and status publishing for the new cellular APIs This is a first part in a series of commits implementing the new cellular APIs in EVE. All parts together will provide many new features for cellular connectivity, such as support for user credentials, multiple modems, multiple SIM slots, switching between 5G/4G/3G, and much more. Part I changelog: * added support for parsing and status publishing for the new cellular APIs into zedagent * extended JSON structs used to move config and status between NIM and wwan microservice to accommodate newly introduced attributes * NIM and wwan microservice for now work only in the backward-compatible mode. Support for the new APIs will be added into these microservices in the next parts of this commit/PR series. * added publishing of port MTU. This is for every port, not just wwan. However, especially for wwan interfaces this is useful because MTU config comes from the networt and it can vary quite a bit. * in DeviceNetworkStatus (DNS), errors collect from verification take precedence over whatever failures are detected when DNS is composed. For cellular connectivity, this ensures that we will not overwrite errors reported by the wwan microservice, which are much more useful than whatever NIM would report from its viewpoint. * moved ip utils and generics to sub-packages of pillar/utils so that they can be used from pillar/types without circular dependencies (used them for methods of wwan structures) Signed-off-by: Milan Lenco --- pkg/pillar/cmd/zedagent/handlemetrics.go | 69 +--- pkg/pillar/cmd/zedagent/parseconfig.go | 196 ++++++++-- pkg/pillar/cmd/zedagent/radiosilence.go | 2 +- pkg/pillar/cmd/zedagent/reportinfo.go | 277 +++++++++++--- pkg/pillar/cmd/zedagent/zedagent.go | 23 ++ pkg/pillar/cmd/zedrouter/appnetwork.go | 5 +- pkg/pillar/cmd/zedrouter/ipam.go | 3 +- pkg/pillar/conntester/mock.go | 11 +- pkg/pillar/dpcmanager/dns.go | 54 ++- pkg/pillar/dpcmanager/dpc.go | 2 + pkg/pillar/dpcmanager/dpcmanager.go | 2 +- pkg/pillar/dpcmanager/dpcmanager_test.go | 56 ++- pkg/pillar/dpcmanager/verify.go | 3 + pkg/pillar/dpcmanager/wwan.go | 73 +++- pkg/pillar/dpcreconciler/linux.go | 125 ++++-- pkg/pillar/dpcreconciler/linux_test.go | 30 +- pkg/pillar/iptables/configitem.go | 8 +- pkg/pillar/netmonitor/linux.go | 1 + pkg/pillar/netmonitor/netmonitor.go | 2 + .../nireconciler/genericitems/dnsmasq.go | 37 +- .../nireconciler/genericitems/httpsrv.go | 4 +- .../nireconciler/genericitems/ipreserve.go | 6 +- .../nireconciler/genericitems/uplink.go | 5 +- pkg/pillar/nireconciler/linux_acl.go | 6 +- pkg/pillar/nireconciler/linux_config.go | 7 +- pkg/pillar/nireconciler/linux_reconciler.go | 6 +- pkg/pillar/nireconciler/linuxitems/bridge.go | 5 +- pkg/pillar/nireconciler/linuxitems/iprule.go | 6 +- pkg/pillar/nireconciler/linuxitems/ipset.go | 4 +- .../nireconciler/linuxitems/vlanport.go | 4 +- pkg/pillar/nireconciler/nireconciler.go | 9 +- pkg/pillar/types/cipherinfotypes.go | 2 + pkg/pillar/types/zedroutertypes.go | 361 ++++++++++++------ pkg/pillar/utils/{ => generics}/generics.go | 11 +- pkg/pillar/utils/ip.go | 29 -- pkg/pillar/utils/netutils/ip.go | 74 ++++ pkg/wwan/usr/bin/wwan-init.sh | 16 +- 37 files changed, 1069 insertions(+), 465 deletions(-) rename pkg/pillar/utils/{ => generics}/generics.go (94%) delete mode 100644 pkg/pillar/utils/ip.go create mode 100644 pkg/pillar/utils/netutils/ip.go diff --git a/pkg/pillar/cmd/zedagent/handlemetrics.go b/pkg/pillar/cmd/zedagent/handlemetrics.go index c1e5729c833..db796f75245 100644 --- a/pkg/pillar/cmd/zedagent/handlemetrics.go +++ b/pkg/pillar/cmd/zedagent/handlemetrics.go @@ -27,7 +27,6 @@ import ( "github.com/lf-edge/eve/pkg/pillar/utils" "github.com/lf-edge/eve/pkg/pillar/vault" "github.com/lf-edge/eve/pkg/pillar/zedcloud" - uuid "github.com/satori/go.uuid" "github.com/shirou/gopsutil/host" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" @@ -343,18 +342,16 @@ func publishMetrics(ctx *zedagentContext, iteration int) { // Use the network metrics from zedrouter subscription // Only report stats for the ports in DeviceNetworkStatus - labelList := types.ReportLogicallabels(*deviceNetworkStatus) - for _, label := range labelList { + for _, p := range deviceNetworkStatus.Ports { var metric *types.NetworkMetric - ports := deviceNetworkStatus.GetPortsByLogicallabel(label) - if len(ports) == 0 { - continue - } - p := ports[0] if !p.IsL3Port { // metrics for ports from lower layers are not reported continue } + if p.IfName == "" { + // Cannot associate metrics with the port until interface name is known. + continue + } for _, m := range networkMetrics.MetricList { if p.IfName == m.IfName { metric = &m @@ -366,7 +363,7 @@ func publishMetrics(ctx *zedagentContext, iteration int) { } networkDetails := new(metrics.NetworkMetric) networkDetails.LocalName = metric.IfName - networkDetails.IName = label + networkDetails.IName = p.Logicallabel networkDetails.Alias = p.Alias networkDetails.TxPkts = metric.TxPkts networkDetails.RxPkts = metric.RxPkts @@ -1033,55 +1030,6 @@ func encodeProxyStatus(proxyConfig *types.ProxyConfig) *info.ProxyStatus { return status } -func encodeNetworkPortConfig(ctx *zedagentContext, - npc *types.NetworkPortConfig) *info.DevicePort { - aa := ctx.assignableAdapters - - dp := new(info.DevicePort) - dp.Ifname = npc.IfName - // XXX rename the protobuf field Name to Logicallabel and add Phylabel? - dp.Name = npc.Logicallabel - // XXX Add Alias in proto file? - // dp.Alias = npc.Alias - - ibPtr := aa.LookupIoBundlePhylabel(npc.Phylabel) - if ibPtr != nil { - dp.Usage = evecommon.PhyIoMemberUsage(ibPtr.Usage) - } - - dp.IsMgmt = npc.IsMgmt - dp.Cost = uint32(npc.Cost) - dp.Free = npc.Cost == 0 // To be deprecated - // DhcpConfig - dp.DhcpType = uint32(npc.Dhcp) - dp.Subnet = npc.AddrSubnet - - dp.DefaultRouters = make([]string, 0) - dp.DefaultRouters = append(dp.DefaultRouters, npc.Gateway.String()) - - dp.NtpServer = npc.NtpServer.String() - - dp.Dns = new(info.ZInfoDNS) - dp.Dns.DNSdomain = npc.DomainName - dp.Dns.DNSservers = make([]string, 0) - for _, d := range npc.DnsServers { - dp.Dns.DNSservers = append(dp.Dns.DNSservers, d.String()) - } - // XXX Not in definition. Remove? - // XXX string dhcpRangeLow = 17; - // XXX string dhcpRangeHigh = 18; - - dp.Proxy = encodeProxyStatus(&npc.ProxyConfig) - - dp.Err = encodeTestResults(npc.TestResults) - - var nilUUID uuid.UUID - if npc.NetworkUUID != nilUUID { - dp.NetworkUUID = npc.NetworkUUID.String() - } - return dp -} - // This function is called per change, hence needs to try over all management ports // When aiStatus is nil it means a delete and we send a message // containing only the UUID to inform zedcloud about the delete. @@ -1175,8 +1123,9 @@ func PublishAppInfoToZedCloud(ctx *zedagentContext, uuid string, if niStatus != nil { networkInfo.NtpServers = []string{} if niStatus.NtpServer != nil { - networkInfo.NtpServers = append(networkInfo.NtpServers, niStatus.NtpServer.String()) - } else { + networkInfo.NtpServers = append(networkInfo.NtpServers, + niStatus.NtpServer.String()) + } else if niStatus.SelectedUplinkLogicalLabel != "" { ntpServers := types.GetNTPServers(*deviceNetworkStatus, niStatus.SelectedUplinkIntfName) for _, server := range ntpServers { diff --git a/pkg/pillar/cmd/zedagent/parseconfig.go b/pkg/pillar/cmd/zedagent/parseconfig.go index ebe544d9295..0ed8471bda7 100644 --- a/pkg/pillar/cmd/zedagent/parseconfig.go +++ b/pkg/pillar/cmd/zedagent/parseconfig.go @@ -19,10 +19,12 @@ import ( "github.com/google/go-cmp/cmp" zconfig "github.com/lf-edge/eve/api/go/config" + zevecommon "github.com/lf-edge/eve/api/go/evecommon" "github.com/lf-edge/eve/pkg/pillar/objtonum" "github.com/lf-edge/eve/pkg/pillar/sriov" "github.com/lf-edge/eve/pkg/pillar/types" fileutils "github.com/lf-edge/eve/pkg/pillar/utils/file" + "github.com/lf-edge/eve/pkg/pillar/utils/netutils" uuid "github.com/satori/go.uuid" ) @@ -761,7 +763,7 @@ func validateAndAssignNetPorts(dpc *types.DevicePortConfig, newPorts []*types.Ne port2.RecordFailure(errStr) break } - if port.IfName == port2.IfName { + if port.IfName != "" && port.IfName == port2.IfName { errStr := fmt.Sprintf( "Port collides with another port with the same interface name (%s)", port.IfName) @@ -890,18 +892,47 @@ func propagateError(higherLayerPort, lowerLayerPort *types.NetworkPortConfig) { func propagatePhyioAttrsToPort(port *types.NetworkPortConfig, phyio *types.PhysicalIOAdapter) { port.Phylabel = phyio.Phylabel port.IfName = phyio.Phyaddr.Ifname + port.USBAddr = phyio.Phyaddr.UsbAddr + port.PCIAddr = phyio.Phyaddr.PciLong if port.IfName == "" { - // Might not be set for all models - log.Warnf("Physical IO %s (Phylabel %s) has no ifname", - phyio.Logicallabel, phyio.Phylabel) - if phyio.Logicallabel != "" { - port.IfName = phyio.Logicallabel - } else { - port.IfName = phyio.Phylabel + // Inside the device model, network adapter may be referenced by PCI or USB address + // instead of the interface name. In fact, with multiple network ports, interface naming + // is not necessary deterministic and may depend on the order of network adapter + // initialization and discovery by the kernel. + // Moreover, once EVE supports userspace vswitch, interface names of ports will differ + // depending on if they are assigned to the kernel or vswitch. + // For the reasons above, it is preferred to reference network adapters by PCI/USB + // addresses going forward. + // For now, we will allow network port configs without interface name at least for + // cellular modems. + // TODO: Allow any type of network port to be defined in PhysicalIOAdapter without + // interface name. + switch types.IoType(phyio.Ptype) { + case types.IoNetWWAN: + if port.USBAddr == "" && port.PCIAddr == "" { + log.Warnf("Physical IO %s (Phylabel %s) has no physical address", + phyio.Logicallabel, phyio.Phylabel) + handleMissingIfname(port, phyio) + } + default: + log.Warnf("Physical IO %s (Phylabel %s) has no ifname", + phyio.Logicallabel, phyio.Phylabel) + handleMissingIfname(port, phyio) } } } +func handleMissingIfname(port *types.NetworkPortConfig, phyio *types.PhysicalIOAdapter) { + // Try to use logical or physical label as interface name. + // If such interface name is not valid, NIM will report error in DeviceNetworkStatus + // under the port's TestResults. + if phyio.Logicallabel != "" { + port.IfName = phyio.Logicallabel + } else { + port.IfName = phyio.Phylabel + } +} + // Make NetworkPortConfig entry for PhysicalIO which is below an L2 port. // The port configuration will contain only labels and the interface name. func makeL2PhyioPort(phyio *types.PhysicalIOAdapter) *types.NetworkPortConfig { @@ -1081,7 +1112,7 @@ func parseOneSystemAdapterConfig(getconfigCtx *getconfigContext, errStr := fmt.Sprintf("Device Config Error. Port %s configured with "+ "UNKNOWN Network UUID (%s). Err: %s. Please fix the "+ "device configuration.", - port.IfName, sysAdapter.NetworkUUID, err) + port.Logicallabel, sysAdapter.NetworkUUID, err) log.Errorf("parseSystemAdapterConfig: %s", errStr) port.RecordFailure(errStr) } else { @@ -1092,7 +1123,7 @@ func parseOneSystemAdapterConfig(getconfigCtx *getconfigContext, if network.HasError() { errStr := fmt.Sprintf("Port %s configured with a network "+ "(UUID: %s) which has an error (%s).", - port.IfName, port.NetworkUUID, network.Error) + port.Logicallabel, port.NetworkUUID, network.Error) log.Errorf("parseSystemAdapterConfig: %s", errStr) port.RecordFailure(errStr) } @@ -1116,7 +1147,7 @@ func parseOneSystemAdapterConfig(getconfigCtx *getconfigContext, if port.AddrSubnet == "" { errStr := fmt.Sprintf("Port %s Configured as DT_STATIC but "+ "missing subnet address. SysAdapter - Name: %s, Addr:%s", - port.IfName, sysAdapter.Name, sysAdapter.Addr) + port.Logicallabel, sysAdapter.Name, sysAdapter.Addr) log.Errorf("parseSystemAdapterConfig: %s", errStr) port.RecordFailure(errStr) } @@ -1127,14 +1158,14 @@ func parseOneSystemAdapterConfig(getconfigCtx *getconfigContext, errStr := fmt.Sprintf("Port %s configured as Management port "+ "with an unsupported DHCP type %d. Client and static are "+ "the only allowed DHCP modes for management ports.", - port.IfName, types.DT_NONE) + port.Logicallabel, types.DT_NONE) log.Errorf("parseSystemAdapterConfig: %s", errStr) port.RecordFailure(errStr) } default: errStr := fmt.Sprintf("Port %s configured with unknown DHCP type %v", - port.IfName, network.Dhcp) + port.Logicallabel, network.Dhcp) log.Errorf("parseSystemAdapterConfig: %s", errStr) port.RecordFailure(errStr) } @@ -1146,7 +1177,7 @@ func parseOneSystemAdapterConfig(getconfigCtx *getconfigContext, } else if isMgmt { errStr := fmt.Sprintf("Port %s Configured as Management port without "+ "configuring a Network. Network is required for Management ports", - port.IfName) + port.Logicallabel) log.Errorf("parseSystemAdapterConfig: %s", errStr) port.RecordFailure(errStr) } @@ -1813,37 +1844,122 @@ func parseNetworkWirelessConfig(ctx *getconfigContext, key string, netEnt *zconf wType := netWireless.GetType() switch wType { case zconfig.WirelessType_Cellular: - // wconfig.WType = types.WirelessTypeCellular - cellulars := netWireless.GetCellularCfg() - for _, cellular := range cellulars { - var wcell types.CellConfig - wcell.APN = cellular.GetAPN() - wcell.ProbeAddr = cellular.GetProbe().GetProbeAddress() - wcell.DisableProbe = cellular.GetProbe().GetDisable() - wcell.LocationTracking = cellular.GetLocationTracking() - wconfig.Cellular = append(wconfig.Cellular, wcell) + cellNetConfigs := netWireless.GetCellularCfg() + if len(cellNetConfigs) == 0 { + log.Errorf("parseNetworkWirelessConfig: missing cellular config in: %v", + netWireless) + return wconfig + } + // CellularCfg should really have been defined in the EVE API as a single entry + // rather than as a list (for multiple SIM cards and APNs there is AccessPoints list + // underneath). However, marking this field as deprecated and creating a new non-list + // field seems unnecessary - let's instead expect single entry. + if len(cellNetConfigs) > 1 { + log.Errorf( + "parseNetworkWirelessConfig: unexpected multiple cellular configs in: %v", + netWireless) + return wconfig + } + cellNetConfig := cellNetConfigs[0] + for _, accessPoint := range cellNetConfig.AccessPoints { + var ap types.CellularAccessPoint + ap.APN = accessPoint.Apn + ap.SIMSlot = uint8(accessPoint.SimSlot) + // By default (ActivatedSimSlot is not defined), any configured Access Point + // should be activated. + ap.Activated = cellNetConfig.ActivatedSimSlot == 0 || + cellNetConfig.ActivatedSimSlot == accessPoint.SimSlot + switch accessPoint.AuthProtocol { + case zconfig.CellularAuthProtocol_CELLULAR_AUTH_PROTOCOL_PAP: + ap.AuthProtocol = types.WwanAuthProtocolPAP + case zconfig.CellularAuthProtocol_CELLULAR_AUTH_PROTOCOL_CHAP: + ap.AuthProtocol = types.WwanAuthProtocolCHAP + case zconfig.CellularAuthProtocol_CELLULAR_AUTH_PROTOCOL_PAP_AND_CHAP: + ap.AuthProtocol = types.WwanAuthProtocolPAPAndCHAP + default: + log.Errorf("parseNetworkWirelessConfig: unrecognized AuthProtocol: %+v", + accessPoint) + } + ap.CipherBlockStatus = parseCipherBlock(ctx, key, accessPoint.GetCipherData()) + for _, plmn := range accessPoint.PreferredPlmns { + ap.PreferredPLMNs = append(ap.PreferredPLMNs, plmn) + } + for _, rat := range accessPoint.PreferredRats { + switch rat { + case zevecommon.RadioAccessTechnology_RADIO_ACCESS_TECHNOLOGY_GSM: + ap.PreferredRATs = append(ap.PreferredRATs, types.WwanRATGSM) + case zevecommon.RadioAccessTechnology_RADIO_ACCESS_TECHNOLOGY_UMTS: + ap.PreferredRATs = append(ap.PreferredRATs, types.WwanRATUMTS) + case zevecommon.RadioAccessTechnology_RADIO_ACCESS_TECHNOLOGY_LTE: + ap.PreferredRATs = append(ap.PreferredRATs, types.WwanRATLTE) + case zevecommon.RadioAccessTechnology_RADIO_ACCESS_TECHNOLOGY_5GNR: + ap.PreferredRATs = append(ap.PreferredRATs, types.WwanRAT5GNR) + default: + log.Errorf("parseNetworkWirelessConfig: unrecognized RAT: %+v", + accessPoint) + } + } + ap.ForbidRoaming = accessPoint.ForbidRoaming + wconfig.Cellular.AccessPoints = append(wconfig.Cellular.AccessPoints, ap) + } + // For backward compatibility. + if len(cellNetConfig.AccessPoints) == 0 && cellNetConfig.APN != "" { + ap := types.CellularAccessPoint{ + Activated: true, + APN: cellNetConfig.APN, + } + + // Just for demo. + if strings.Contains(cellNetConfig.APN, "://") { + // Format: ://:@apn + splitProto := strings.Split(cellNetConfig.APN, "://") + if len(splitProto) == 2 { + proto := strings.ToLower(splitProto[0]) + switch proto { + case "pap": + ap.AuthProtocol = types.WwanAuthProtocolPAP + case "chap": + ap.AuthProtocol = types.WwanAuthProtocolCHAP + } + splitUsername := strings.Split(splitProto[1], ":") + if len(splitUsername) == 2 { + ap.CleartextUsername = splitUsername[0] + splitPassword := strings.Split(splitUsername[1], "@") + if len(splitPassword) == 2 { + ap.CleartextPassword = splitPassword[0] + ap.APN = splitPassword[1] + } + } + } + } + + wconfig.Cellular.AccessPoints = append(wconfig.Cellular.AccessPoints, ap) } + wconfig.Cellular.Probe.Disable = cellNetConfig.Probe.GetDisable() + wconfig.Cellular.Probe.Address = cellNetConfig.Probe.GetProbeAddress() + wconfig.Cellular.LocationTracking = cellNetConfig.GetLocationTracking() log.Functionf("parseNetworkWirelessConfig: Wireless of network Cellular, %v", wconfig.Cellular) case zconfig.WirelessType_WiFi: - // wconfig.WType = types.WirelessTypeWifi wificfgs := netWireless.GetWifiCfg() - for _, wificfg := range wificfgs { var wifi types.WifiConfig wifi.SSID = wificfg.GetWifiSSID() - if wificfg.GetKeyScheme() == zconfig.WiFiKeyScheme_WPAPSK { + switch wificfg.GetKeyScheme() { + case zconfig.WiFiKeyScheme_WPAPSK: wifi.KeyScheme = types.KeySchemeWpaPsk - } else if wificfg.GetKeyScheme() == zconfig.WiFiKeyScheme_WPAEAP { + case zconfig.WiFiKeyScheme_WPAEAP: wifi.KeyScheme = types.KeySchemeWpaEap + default: + log.Errorf("parseNetworkWirelessConfig: unrecognized WiFi Key scheme: %+v", + wificfg) } wifi.Identity = wificfg.GetIdentity() wifi.Password = wificfg.GetPassword() wifi.Priority = wificfg.GetPriority() - key = fmt.Sprintf("%s-%s", key, wifi.SSID) - wifi.CipherBlockStatus = parseCipherBlock(ctx, key, wificfg.GetCipherData()) - + wifiKey := fmt.Sprintf("%s-%s", key, wifi.SSID) + wifi.CipherBlockStatus = parseCipherBlock(ctx, wifiKey, wificfg.GetCipherData()) wconfig.Wifi = append(wconfig.Wifi, wifi) } log.Functionf("parseNetworkWirelessConfig: Wireless of network Wifi, %v", wconfig.Wifi) @@ -1957,7 +2073,7 @@ func parseIpspec(ipspec *zconfig.Ipspec, config.DhcpRange.End = end } - addrCount := types.GetIPAddrCountOnSubnet(config.Subnet) + addrCount := netutils.GetIPAddrCountOnSubnet(config.Subnet) if addrCount < types.MinSubnetSize { return fmt.Errorf("network(%s), Subnet too small(%d)", config.Key(), addrCount) @@ -1965,7 +2081,7 @@ func parseIpspec(ipspec *zconfig.Ipspec, // if not set, take some default if config.Gateway == nil { - config.Gateway = types.AddToIP(config.Subnet.IP, 1) + config.Gateway = netutils.AddToIP(config.Subnet.IP, 1) log.Warnf("network(%s), No Gateway, setting default(%s)", config.Key(), config.Gateway.String()) } @@ -1983,39 +2099,39 @@ func parseIpspec(ipspec *zconfig.Ipspec, // if not set, take some default if config.DhcpRange.Start == nil { - config.DhcpRange.Start = types.AddToIP(config.Subnet.IP, + config.DhcpRange.Start = netutils.AddToIP(config.Subnet.IP, dhcpRangeStart) log.Warnf("network(%s), No Dhcp Start, setting default(%s)", config.Key(), config.DhcpRange.Start.String()) } if config.DhcpRange.End == nil { - config.DhcpRange.End = types.AddToIP(config.Subnet.IP, + config.DhcpRange.End = netutils.AddToIP(config.Subnet.IP, dhcpRangeEnd) log.Warnf("network(%s), No Dhcp End, setting default(%s)", config.Key(), config.DhcpRange.End.String()) } // check whether the dhcp range(start, end) // equal (network, gateway, broadcast) addresses - if network := types.GetIPNetwork(config.Subnet); network != nil { + if network := netutils.GetIPNetwork(config.Subnet); network != nil { if network.Equal(config.DhcpRange.Start) { log.Warnf("network(%s), Dhcp Start is Network(%s), correcting", config.Key(), config.Subnet.IP.String()) config.DhcpRange.Start = - types.AddToIP(config.DhcpRange.Start, 1) + netutils.AddToIP(config.DhcpRange.Start, 1) } if config.Gateway.Equal(config.DhcpRange.Start) { log.Warnf("network(%s), Dhcp Start is Gateway(%s), correcting", config.Key(), config.Gateway.String()) config.DhcpRange.Start = - types.AddToIP(config.Gateway, 1) + netutils.AddToIP(config.Gateway, 1) } } - if bcast := types.GetIPBroadcast(config.Subnet); bcast != nil { + if bcast := netutils.GetIPBroadcast(config.Subnet); bcast != nil { if bcast.Equal(config.DhcpRange.End) { log.Warnf("network(%s), Dhcp End is Broadcast(%s), correcting", config.Key(), bcast.String()) config.DhcpRange.End = - types.AddToIP(config.DhcpRange.End, -1) + netutils.AddToIP(config.DhcpRange.End, -1) } } // Gateway should not be inside the DhcpRange @@ -2031,7 +2147,7 @@ func parseIpspec(ipspec *zconfig.Ipspec, // what ByteAllocator can provide) and use it for network instances // that require bigger subnets. if addressesInRange > objtonum.ByteAllocatorMaxNum { - config.DhcpRange.End = types.AddToIP(config.DhcpRange.Start, objtonum.ByteAllocatorMaxNum) + config.DhcpRange.End = netutils.AddToIP(config.DhcpRange.Start, objtonum.ByteAllocatorMaxNum) } return nil } diff --git a/pkg/pillar/cmd/zedagent/radiosilence.go b/pkg/pillar/cmd/zedagent/radiosilence.go index fa6d366db4b..0072d19fc04 100644 --- a/pkg/pillar/cmd/zedagent/radiosilence.go +++ b/pkg/pillar/cmd/zedagent/radiosilence.go @@ -144,7 +144,7 @@ func getRadioStatus(ctx *getconfigContext) *profile.RadioStatus { Logicallabel: port.Logicallabel, Module: encodeCellModuleInfo(wwanStatus.Module), SimCards: encodeSimCards(wwanStatus.Module.Name, wwanStatus.SimCards), - Providers: encodeCellProviders(wwanStatus.Providers), + Providers: encodeCellProviders(wwanStatus), ConfigError: wwanStatus.ConfigError, ProbeError: wwanStatus.ProbeError, }) diff --git a/pkg/pillar/cmd/zedagent/reportinfo.go b/pkg/pillar/cmd/zedagent/reportinfo.go index 11b17173921..b324ebac906 100644 --- a/pkg/pillar/cmd/zedagent/reportinfo.go +++ b/pkg/pillar/cmd/zedagent/reportinfo.go @@ -26,9 +26,11 @@ import ( "github.com/lf-edge/eve/pkg/pillar/utils" fileutils "github.com/lf-edge/eve/pkg/pillar/utils/file" "github.com/lf-edge/eve/pkg/pillar/vault" + uuid "github.com/satori/go.uuid" "github.com/shirou/gopsutil/host" "golang.org/x/sys/unix" "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" ) const ( @@ -440,28 +442,25 @@ func PublishDeviceInfoToZedCloud(ctx *zedagentContext, dest destinationBitset) { } // We report all the ports in DeviceNetworkStatus - // TODO: report also modems not used by any port. - labelList := types.ReportLogicallabels(*deviceNetworkStatus) - for _, label := range labelList { - ports := deviceNetworkStatus.GetPortsByLogicallabel(label) - if len(ports) == 0 { - continue - } - p := ports[0] - ReportDeviceNetworkInfo := encodeNetInfo(*p) + // Note that this is deprecated in favour of SystemAdapterInfo. + for _, p := range deviceNetworkStatus.Ports { + ReportDeviceNetworkInfo := encodeNetInfo(p) // XXX rename DevName to Logicallabel in proto file - ReportDeviceNetworkInfo.DevName = *proto.String(label) + ReportDeviceNetworkInfo.DevName = *proto.String(p.Logicallabel) ReportDeviceInfo.Network = append(ReportDeviceInfo.Network, ReportDeviceNetworkInfo) - // Report all SIM cards and cellular modules - if p.WirelessStatus.WType == types.WirelessTypeCellular { - wwanStatus := p.WirelessStatus.Cellular - ReportDeviceInfo.CellRadios = append( - ReportDeviceInfo.CellRadios, - encodeCellModuleInfo(wwanStatus.Module)) - ReportDeviceInfo.Sims = append( - ReportDeviceInfo.Sims, - encodeSimCards(wwanStatus.Module.Name, wwanStatus.SimCards)...) + } + // Report all SIM cards and cellular modules, including those not in use. + if statusObj, _ := ctx.subWwanStatus.Get("global"); statusObj != nil { + if status, ok := statusObj.(types.WwanStatus); ok { + for _, cellNet := range status.Networks { + ReportDeviceInfo.CellRadios = append( + ReportDeviceInfo.CellRadios, + encodeCellModuleInfo(cellNet.Module)) + ReportDeviceInfo.Sims = append( + ReportDeviceInfo.Sims, + encodeSimCards(cellNet.Module.Name, cellNet.SimCards)...) + } } } // Fill in global ZInfoDNS dns from /etc/resolv.conf @@ -803,18 +802,16 @@ func addUserSwInfo(ctx *zedagentContext, swInfo *info.ZInfoDevSW, tooEarly bool) func encodeNetInfo(port types.NetworkPortStatus) *info.ZInfoNetwork { networkInfo := new(info.ZInfoNetwork) - networkInfo.LocalName = *proto.String(port.IfName) + networkInfo.LocalName = port.IfName networkInfo.IPAddrs = make([]string, len(port.AddrInfoList)) for index, ai := range port.AddrInfoList { - networkInfo.IPAddrs[index] = *proto.String(ai.Addr.String()) + networkInfo.IPAddrs[index] = ai.Addr.String() } networkInfo.Ipv4Up = port.Up - networkInfo.MacAddr = *proto.String(port.MacAddr) + networkInfo.MacAddr = port.MacAddr.String() + networkInfo.DevName = port.Logicallabel - // In case caller doesn't override - networkInfo.DevName = *proto.String(port.IfName) - - networkInfo.Alias = *proto.String(port.Alias) + networkInfo.Alias = port.Alias // Default routers from kernel whether or not we are using DHCP networkInfo.DefaultRouters = make([]string, len(port.DefaultRouters)) for index, dr := range port.DefaultRouters { @@ -839,13 +836,13 @@ func encodeNetInfo(port types.NetworkPortStatus) *info.ZInfoNetwork { continue } geo := new(info.GeoLoc) - geo.UnderlayIP = *proto.String(ai.Geo.IP) - geo.Hostname = *proto.String(ai.Geo.Hostname) - geo.City = *proto.String(ai.Geo.City) - geo.Country = *proto.String(ai.Geo.Country) - geo.Loc = *proto.String(ai.Geo.Loc) - geo.Org = *proto.String(ai.Geo.Org) - geo.Postal = *proto.String(ai.Geo.Postal) + geo.UnderlayIP = ai.Geo.IP + geo.Hostname = ai.Geo.Hostname + geo.City = ai.Geo.City + geo.Country = ai.Geo.Country + geo.Loc = ai.Geo.Loc + geo.Org = ai.Geo.Org + geo.Postal = ai.Geo.Postal networkInfo.Location = geo break } @@ -895,6 +892,7 @@ func encodeCellModuleInfo(wwanModule types.WwanCellModule) *info.ZCellularModule Imei: wwanModule.IMEI, FirmwareVersion: wwanModule.Revision, Model: wwanModule.Model, + Manufacturer: wwanModule.Manufacturer, OperatingState: opState, ControlProtocol: ctrlProto, } @@ -907,20 +905,34 @@ func encodeSimCards(cellModule string, wwanSimCards []types.WwanSimCard) (simCar CellModuleName: cellModule, Imsi: simCard.IMSI, Iccid: simCard.ICCID, - State: simCard.Status, + State: simCard.State, + SlotNumber: uint32(simCard.SlotNumber), + SlotActivated: simCard.SlotActivated, }) } return simCards } -func encodeCellProviders(wwanProviders []types.WwanProvider) (providers []*info.ZCellularProvider) { - for _, provider := range wwanProviders { - providers = append(providers, &info.ZCellularProvider{ - Plmn: provider.PLMN, - Description: provider.Description, - CurrentServing: provider.CurrentServing, - Roaming: provider.Roaming, - }) +func encodeCellProvider(wwanProvider types.WwanProvider) (provider *info.ZCellularProvider) { + return &info.ZCellularProvider{ + Plmn: wwanProvider.PLMN, + Description: wwanProvider.Description, + CurrentServing: wwanProvider.CurrentServing, + Roaming: wwanProvider.Roaming, + Forbidden: wwanProvider.Forbidden, + } +} + +func encodeCellProviders(wwanStatus types.WwanNetworkStatus) (providers []*info.ZCellularProvider) { + var includedCurrentProvider bool + for _, provider := range wwanStatus.VisibleProviders { + if provider == wwanStatus.CurrentProvider { + includedCurrentProvider = true + } + providers = append(providers, encodeCellProvider(provider)) + } + if !includedCurrentProvider && wwanStatus.CurrentProvider.PLMN != "" { + providers = append(providers, encodeCellProvider(wwanStatus.CurrentProvider)) } return providers } @@ -952,31 +964,17 @@ func encodeSystemAdapterInfo(ctx *zedagentContext) *info.SystemAdapterInfo { // info for ports from lower layers is not published continue } - // FIXME: publish status here, not config! - // TODO: include DNS servers, MTU, etc. - dps.Ports[j] = encodeNetworkPortConfig(ctx, &p) - if i == dpcl.CurrentIndex && p.WirelessCfg.WType == types.WirelessTypeCellular { + if i == dpcl.CurrentIndex { + // For the currently used DPC we publish the status (DeviceNetworkStatus). ports := deviceNetworkStatus.GetPortsByLogicallabel(p.Logicallabel) - if len(ports) == 0 { + if len(ports) == 1 { + dps.Ports[j] = encodeNetworkPortStatus(ctx, ports[0], p.NetworkUUID) continue } - portStatus := ports[0] - wwanStatus := portStatus.WirelessStatus.Cellular - var simCards []string - for _, simCard := range wwanStatus.SimCards { - simCards = append(simCards, simCard.Name) - } - dps.Ports[j].WirelessStatus = &info.WirelessStatus{ - Type: info.WirelessType_WIRELESS_TYPE_CELLULAR, - Cellular: &info.ZCellularStatus{ - CellularModule: wwanStatus.Module.Name, - SimCards: simCards, - Providers: encodeCellProviders(wwanStatus.Providers), - ConfigError: wwanStatus.ConfigError, - ProbeError: wwanStatus.ProbeError, - }, - } } + // For inactive DPCs (or if DNS is not available) we publish the config + // (DevicePortConfig). + dps.Ports[j] = encodeNetworkPortConfig(ctx, &p) } sainfo.Status[i] = dps } @@ -984,6 +982,159 @@ func encodeSystemAdapterInfo(ctx *zedagentContext) *info.SystemAdapterInfo { return sainfo } +// encodeNetInfo encodes info from the port +func encodeNetworkPortStatus(ctx *zedagentContext, + port *types.NetworkPortStatus, network uuid.UUID) *info.DevicePort { + aa := ctx.assignableAdapters + ioBundle := aa.LookupIoBundleLogicallabel(port.Logicallabel) + + devicePort := new(info.DevicePort) + devicePort.Ifname = port.IfName + devicePort.Name = port.Logicallabel + devicePort.Err = encodeTestResults(port.TestResults) + if ioBundle != nil { + devicePort.Usage = ioBundle.Usage + } + devicePort.Cost = uint32(port.Cost) + devicePort.IsMgmt = port.IsMgmt + devicePort.Free = port.Cost == 0 // To be deprecated + devicePort.NetworkUUID = network.String() + devicePort.DhcpType = uint32(port.Dhcp) + devicePort.Subnet = port.Subnet.String() + devicePort.Up = port.Up + devicePort.Mtu = uint32(port.MTU) + devicePort.Domainname = port.DomainName + // TODO: modify EVE APIs and allow to publish full list of NTP servers + if port.NtpServer != nil { + devicePort.NtpServer = port.NtpServer.String() + } else if len(port.NtpServers) > 0 { + devicePort.NtpServer = port.NtpServers[0].String() + } + devicePort.Proxy = encodeProxyStatus(&port.ProxyConfig) + devicePort.MacAddr = port.MacAddr.String() + for _, ipAddr := range port.AddrInfoList { + devicePort.IPAddrs = append(devicePort.IPAddrs, ipAddr.Addr.String()) + } + // devicePort.Gateway is deprecated - replaced by DefaultRouters + for _, router := range port.DefaultRouters { + devicePort.DefaultRouters = append(devicePort.DefaultRouters, router.String()) + } + // devicePort.DnsServers is deprecated - replaced by Dns + devicePort.Dns = new(info.ZInfoDNS) + devicePort.Dns.DNSdomain = port.DomainName + for _, dnsServer := range port.DNSServers { + devicePort.Dns.DNSservers = append(devicePort.Dns.DNSservers, dnsServer.String()) + } + // TODO We may have geoloc information for each IP address. + // For now fill in using the first IP address which has location info available. + for _, ai := range port.AddrInfoList { + if ai.Geo == nilIPInfo { + continue + } + geo := new(info.GeoLoc) + geo.UnderlayIP = ai.Geo.IP + geo.Hostname = ai.Geo.Hostname + geo.City = ai.Geo.City + geo.Country = ai.Geo.Country + geo.Loc = ai.Geo.Loc + geo.Org = ai.Geo.Org + geo.Postal = ai.Geo.Postal + devicePort.Location = geo + break + } + switch port.WirelessStatus.WType { + case types.WirelessTypeCellular: + wwanStatus := port.WirelessStatus.Cellular + var simCards []string + for _, simCard := range wwanStatus.SimCards { + simCards = append(simCards, simCard.Name) + } + var rats []evecommon.RadioAccessTechnology + for _, rat := range wwanStatus.CurrentRATs { + switch rat { + case types.WwanRATGSM: + rats = append(rats, evecommon.RadioAccessTechnology_RADIO_ACCESS_TECHNOLOGY_GSM) + case types.WwanRATUMTS: + rats = append(rats, evecommon.RadioAccessTechnology_RADIO_ACCESS_TECHNOLOGY_UMTS) + case types.WwanRATLTE: + rats = append(rats, evecommon.RadioAccessTechnology_RADIO_ACCESS_TECHNOLOGY_LTE) + case types.WwanRAT5GNR: + rats = append(rats, evecommon.RadioAccessTechnology_RADIO_ACCESS_TECHNOLOGY_5GNR) + } + } + var connectedAt *timestamppb.Timestamp + if wwanStatus.ConnectedAt != 0 { + connectedAt = timestamppb.New(time.Unix(int64(wwanStatus.ConnectedAt), 0)) + } + devicePort.WirelessStatus = &info.WirelessStatus{ + Type: info.WirelessType_WIRELESS_TYPE_CELLULAR, + Cellular: &info.ZCellularStatus{ + CellularModule: wwanStatus.Module.Name, + SimCards: simCards, + Providers: encodeCellProviders(wwanStatus), + CurrentRats: rats, + ConnectedAt: connectedAt, + ConfigError: wwanStatus.ConfigError, + ProbeError: wwanStatus.ProbeError, + }, + } + case types.WirelessTypeWifi: + devicePort.WirelessStatus = &info.WirelessStatus{ + Type: info.WirelessType_WIRELESS_TYPE_WIFI, + } + } + return devicePort +} + +func encodeNetworkPortConfig(ctx *zedagentContext, + npc *types.NetworkPortConfig) *info.DevicePort { + aa := ctx.assignableAdapters + + dp := new(info.DevicePort) + dp.Ifname = npc.IfName + // XXX rename the protobuf field Name to Logicallabel and add Phylabel? + dp.Name = npc.Logicallabel + // XXX Add Alias in proto file? + // dp.Alias = npc.Alias + + ibPtr := aa.LookupIoBundleLogicallabel(npc.Logicallabel) + if ibPtr != nil { + dp.Usage = ibPtr.Usage + } + + dp.IsMgmt = npc.IsMgmt + dp.Cost = uint32(npc.Cost) + dp.Free = npc.Cost == 0 // To be deprecated + // DhcpConfig + dp.DhcpType = uint32(npc.Dhcp) + dp.Subnet = npc.AddrSubnet + + dp.DefaultRouters = make([]string, 0) + dp.DefaultRouters = append(dp.DefaultRouters, npc.Gateway.String()) + + dp.NtpServer = npc.NtpServer.String() + + dp.Dns = new(info.ZInfoDNS) + dp.Dns.DNSdomain = npc.DomainName + dp.Dns.DNSservers = make([]string, 0) + for _, d := range npc.DnsServers { + dp.Dns.DNSservers = append(dp.Dns.DNSservers, d.String()) + } + // XXX Not in definition. Remove? + // XXX string dhcpRangeLow = 17; + // XXX string dhcpRangeHigh = 18; + + dp.Proxy = encodeProxyStatus(&npc.ProxyConfig) + + dp.Err = encodeTestResults(npc.TestResults) + + var nilUUID uuid.UUID + if npc.NetworkUUID != nilUUID { + dp.NetworkUUID = npc.NetworkUUID.String() + } + return dp +} + // getDataSecAtRestInfo prepares status related to Data security at Rest func getDataSecAtRestInfo(ctx *zedagentContext) *info.DataSecAtRest { subVaultStatus := ctx.subVaultStatus diff --git a/pkg/pillar/cmd/zedagent/zedagent.go b/pkg/pillar/cmd/zedagent/zedagent.go index 6b6f5053d87..4206d10dc2c 100644 --- a/pkg/pillar/cmd/zedagent/zedagent.go +++ b/pkg/pillar/cmd/zedagent/zedagent.go @@ -137,6 +137,7 @@ type zedagentContext struct { subCapabilities pubsub.Subscription subAppInstMetaData pubsub.Subscription subWwanMetrics pubsub.Subscription + subWwanStatus pubsub.Subscription subLocationInfo pubsub.Subscription subZFSPoolStatus pubsub.Subscription subZFSPoolMetrics pubsub.Subscription @@ -713,6 +714,9 @@ func waitUntilDNSReady(zedagentCtx *zedagentContext, stillRunning *time.Ticker) getconfigCtx.localServerMap.upToDate = false getconfigCtx.subAppNetworkStatus.ProcessChange(change) + case change := <-zedagentCtx.subWwanStatus.MsgChan(): + zedagentCtx.subWwanStatus.ProcessChange(change) + case change := <-zedagentCtx.subWwanMetrics.MsgChan(): zedagentCtx.subWwanMetrics.ProcessChange(change) @@ -973,6 +977,9 @@ func mainEventLoop(zedagentCtx *zedagentContext, stillRunning *time.Ticker) { case change := <-zedagentCtx.subAppInstMetaData.MsgChan(): zedagentCtx.subAppInstMetaData.ProcessChange(change) + case change := <-zedagentCtx.subWwanStatus.MsgChan(): + zedagentCtx.subWwanStatus.ProcessChange(change) + case change := <-zedagentCtx.subWwanMetrics.MsgChan(): zedagentCtx.subWwanMetrics.ProcessChange(change) @@ -1651,6 +1658,22 @@ func initPostOnboardSubs(zedagentCtx *zedagentContext) { } zedagentCtx.subAppInstMetaData.Activate() + // Used to publish info about unused cellular modems. + // Cellular modems used by configured network ports have status published + // as part of DeviceNetworkStatus. + zedagentCtx.subWwanStatus, err = ps.NewSubscription(pubsub.SubscriptionOptions{ + AgentName: "nim", + MyAgentName: agentName, + TopicImpl: types.WwanStatus{}, + Activate: true, + Ctx: zedagentCtx, + WarningTime: warningTime, + ErrorTime: errorTime, + }) + if err != nil { + log.Fatal(err) + } + zedagentCtx.subWwanMetrics, err = ps.NewSubscription(pubsub.SubscriptionOptions{ AgentName: "nim", MyAgentName: agentName, diff --git a/pkg/pillar/cmd/zedrouter/appnetwork.go b/pkg/pillar/cmd/zedrouter/appnetwork.go index 2d7ac107620..ec02a0eddc0 100644 --- a/pkg/pillar/cmd/zedrouter/appnetwork.go +++ b/pkg/pillar/cmd/zedrouter/appnetwork.go @@ -5,9 +5,10 @@ package zedrouter import ( "fmt" + "github.com/lf-edge/eve/pkg/pillar/nireconciler" "github.com/lf-edge/eve/pkg/pillar/types" - "github.com/lf-edge/eve/pkg/pillar/utils" + "github.com/lf-edge/eve/pkg/pillar/utils/generics" uuid "github.com/satori/go.uuid" ) @@ -50,7 +51,7 @@ func (z *zedrouter) updateVIFsForStateCollecting( networks = append(networks, ul.Network) } } - networks = utils.FilterDuplicates(networks) + networks = generics.FilterDuplicates(networks) // Update state collecting for NIs that the app is or was connected to. for _, network := range networks { netConfig := z.lookupNetworkInstanceConfig(network.String()) diff --git a/pkg/pillar/cmd/zedrouter/ipam.go b/pkg/pillar/cmd/zedrouter/ipam.go index 7d96a460a6e..7c4592ba066 100644 --- a/pkg/pillar/cmd/zedrouter/ipam.go +++ b/pkg/pillar/cmd/zedrouter/ipam.go @@ -10,6 +10,7 @@ import ( "github.com/lf-edge/eve/pkg/pillar/nistate" "github.com/lf-edge/eve/pkg/pillar/types" + "github.com/lf-edge/eve/pkg/pillar/utils/netutils" uuid "github.com/satori/go.uuid" ) @@ -127,7 +128,7 @@ func (z *zedrouter) lookupOrAllocateIPv4ForVIF(niStatus *types.NetworkInstanceSt return nil, err } // Pick an IP address from the subnet. - ipAddr = types.AddToIP(niStatus.DhcpRange.Start, appNum) + ipAddr = netutils.AddToIP(niStatus.DhcpRange.Start, appNum) // Check if the address falls into the Dhcp Range. if !niStatus.DhcpRange.Contains(ipAddr) { err := fmt.Errorf("no free IP addresses in DHCP range(%v, %v)", diff --git a/pkg/pillar/conntester/mock.go b/pkg/pillar/conntester/mock.go index 3209933d771..a8beb84304e 100644 --- a/pkg/pillar/conntester/mock.go +++ b/pkg/pillar/conntester/mock.go @@ -10,13 +10,15 @@ import ( "time" "github.com/lf-edge/eve/pkg/pillar/netdump" + "github.com/lf-edge/eve/pkg/pillar/netmonitor" "github.com/lf-edge/eve/pkg/pillar/types" ) // MockConnectivityTester is used for unit testing. type MockConnectivityTester struct { sync.Mutex - TestDuration time.Duration // inject + TestDuration time.Duration // inject + NetworkMonitor netmonitor.NetworkMonitor // inject iteration int connErrors map[ifRef]error @@ -71,14 +73,13 @@ func (t *MockConnectivityTester) TestConnectivity(dns types.DeviceNetworkStatus, // We have enough uplinks with cloud connectivity working. break } - port := dns.GetPortByIfName(ifName) - missingErr := fmt.Sprintf("port %s does not exist - ignored", ifName) - if port == nil || port.LastError == missingErr { - err := fmt.Errorf("interface %s is missing", ifName) + if _, exists, _ := t.NetworkMonitor.GetInterfaceIndex(ifName); !exists { + err = fmt.Errorf("interface %s is missing", ifName) errorList = append(errorList, err) intfStatusMap.RecordFailure(ifName, err.Error()) continue } + port := dns.GetPortByIfName(ifName) if !port.IsMgmt { continue } diff --git a/pkg/pillar/dpcmanager/dns.go b/pkg/pillar/dpcmanager/dns.go index 961440335b6..bd3f40dffec 100644 --- a/pkg/pillar/dpcmanager/dns.go +++ b/pkg/pillar/dpcmanager/dns.go @@ -66,33 +66,58 @@ func (m *DpcManager) updateDNS() { m.deviceNetStatus.Ports[ix].DomainName = port.DomainName m.deviceNetStatus.Ports[ix].DNSServers = port.DnsServers m.deviceNetStatus.Ports[ix].NtpServer = port.NtpServer + // Prefer errors recorded by DPC verification. + // Errors are recorded from this function only when there is none yet. m.deviceNetStatus.Ports[ix].TestResults = port.TestResults + m.deviceNetStatus.Ports[ix].WirelessStatus.WType = port.WirelessCfg.WType + // If this is a cellular network connectivity, add status information + // obtained from the wwan service. + if port.WirelessCfg.WType == types.WirelessTypeCellular { + wwanNetStatus, found := m.wwanStatus.LookupNetworkStatus(port.Logicallabel) + if found { + m.deviceNetStatus.Ports[ix].WirelessStatus.Cellular = wwanNetStatus + } + } // Do not try to get state data for interface which is in PCIback. - ioBundle := m.adapters.LookupIoBundleIfName(port.IfName) + ioBundle := m.adapters.LookupIoBundleLogicallabel(port.Logicallabel) if ioBundle != nil && ioBundle.IsPCIBack { - err := fmt.Errorf("port %s is in PCIBack - ignored", port.IfName) + err := fmt.Errorf("port %s is in PCIBack", port.Logicallabel) m.Log.Warnf("updateDNS: %v", err) m.deviceNetStatus.Ports[ix].RecordFailure(err.Error()) + if !m.deviceNetStatus.Ports[ix].HasError() { + m.deviceNetStatus.Ports[ix].RecordFailure(err.Error()) + } + continue + } + if port.IfName == "" { + err := fmt.Errorf("port %s is missing interface name", port.Logicallabel) + if !m.deviceNetStatus.Ports[ix].HasError() { + m.deviceNetStatus.Ports[ix].RecordFailure(err.Error()) + } + m.Log.Warnf("updateDNS: interface name of port %s is not yet known, "+ + "will not retrieve some attributes", port.Logicallabel) continue } // Get interface state data from the network stack. ifindex, exists, err := m.NetworkMonitor.GetInterfaceIndex(port.IfName) if !exists || err != nil { - err = fmt.Errorf("port %s does not exist - ignored", port.IfName) + err = fmt.Errorf("port %s is missing", port.Logicallabel) m.Log.Warnf("updateDNS: %v", err) m.deviceNetStatus.Ports[ix].RecordFailure(err.Error()) + if !m.deviceNetStatus.Ports[ix].HasError() { + m.deviceNetStatus.Ports[ix].RecordFailure(err.Error()) + } continue } - var isUp bool ifAttrs, err := m.NetworkMonitor.GetInterfaceAttrs(ifindex) if err != nil { m.Log.Warnf( "updateDNS: failed to get attrs for interface %s with index %d: %v", port.IfName, ifindex, err) } else { - isUp = ifAttrs.AdminUp + m.deviceNetStatus.Ports[ix].Up = ifAttrs.AdminUp + m.deviceNetStatus.Ports[ix].MTU = ifAttrs.MTU } - m.deviceNetStatus.Ports[ix].Up = isUp ipAddrs, macAddr, err := m.NetworkMonitor.GetInterfaceAddrs(ifindex) if err != nil { m.Log.Warnf( @@ -100,7 +125,7 @@ func (m *DpcManager) updateDNS() { port.IfName, ifindex, err) ipAddrs = nil } - m.deviceNetStatus.Ports[ix].MacAddr = macAddr.String() + m.deviceNetStatus.Ports[ix].MacAddr = macAddr m.deviceNetStatus.Ports[ix].AddrInfoList = make([]types.AddrInfo, len(ipAddrs)) if len(ipAddrs) == 0 { m.Log.Functionf("updateDNS: interface %s has NO IP addresses", port.IfName) @@ -141,18 +166,6 @@ func (m *DpcManager) updateDNS() { // Already have TestResults set from above m.Log.Error(err) } - - // If this is a cellular network connectivity, add status information - // obtained from the wwan service. - if port.WirelessCfg.WType == types.WirelessTypeCellular { - wwanNetStatus, found := m.wwanStatus.LookupNetworkStatus(port.Logicallabel) - if found { - m.deviceNetStatus.Ports[ix].WirelessStatus = types.WirelessStatus{ - WType: types.WirelessTypeCellular, - Cellular: wwanNetStatus, - } - } - } } // Preserve geo info for existing interface and IP address @@ -185,6 +198,9 @@ func (m *DpcManager) updateGeo() { if m.deviceNetStatus.Version >= types.DPCIsMgmt && !port.IsMgmt { continue } + if port.IfName == "" { + continue + } for i := range port.AddrInfoList { // Need pointer since we are going to modify. ai := &port.AddrInfoList[i] diff --git a/pkg/pillar/dpcmanager/dpc.go b/pkg/pillar/dpcmanager/dpc.go index ae7b2b9b7e2..062f5c7693a 100644 --- a/pkg/pillar/dpcmanager/dpc.go +++ b/pkg/pillar/dpcmanager/dpc.go @@ -22,6 +22,7 @@ func (m *DpcManager) currentDPC() *types.DevicePortConfig { } func (m *DpcManager) doAddDPC(ctx context.Context, dpc types.DevicePortConfig) { + m.setDiscoveredWwanIfNames(&dpc) mgmtCount := dpc.CountMgmtPorts() if mgmtCount == 0 { // This DPC will be ignored when we check IsDPCUsable which @@ -55,6 +56,7 @@ func (m *DpcManager) doAddDPC(ctx context.Context, dpc types.DevicePortConfig) { } func (m *DpcManager) doDelDPC(ctx context.Context, dpc types.DevicePortConfig) { + m.setDiscoveredWwanIfNames(&dpc) configChanged := m.updateDPCListAndPublish(dpc, true) if !configChanged { m.Log.Functionf("doDelDPC: System current. No change detected.\n") diff --git a/pkg/pillar/dpcmanager/dpcmanager.go b/pkg/pillar/dpcmanager/dpcmanager.go index 7adf3df0883..9dfd0cafcbd 100644 --- a/pkg/pillar/dpcmanager/dpcmanager.go +++ b/pkg/pillar/dpcmanager/dpcmanager.go @@ -406,7 +406,7 @@ func (m *DpcManager) run(ctx context.Context) { case WwanEventUndefined: m.Log.Warnf("Undefined event received from WwanWatcher") case WwanEventNewStatus: - m.reloadWwanStatus() + m.reloadWwanStatus(ctx) case WwanEventNewMetrics: m.reloadWwanMetrics() case WwanEventNewLocationInfo: diff --git a/pkg/pillar/dpcmanager/dpcmanager_test.go b/pkg/pillar/dpcmanager/dpcmanager_test.go index be444224bef..295237f3f89 100644 --- a/pkg/pillar/dpcmanager/dpcmanager_test.go +++ b/pkg/pillar/dpcmanager/dpcmanager_test.go @@ -122,7 +122,8 @@ func initTest(test *testing.T) *GomegaWithT { wwanWatcher = &MockWwanWatcher{} geoService = &MockGeoService{} connTester = &conntester.MockConnectivityTester{ - TestDuration: 2 * time.Second, + TestDuration: 2 * time.Second, + NetworkMonitor: networkMonitor, } dpcManager = &dpcmngr.DpcManager{ Log: logObj, @@ -488,12 +489,22 @@ func mockWwan0Status() types.WwanStatus { IMSI: "310180933695713", }, }, - Providers: []types.WwanProvider{ + CurrentProvider: types.WwanProvider{ + PLMN: "310-410", + Description: "AT&T", + CurrentServing: true, + }, + VisibleProviders: []types.WwanProvider{ { PLMN: "310-410", Description: "AT&T", CurrentServing: true, }, + { + PLMN: "231-02", + Description: "Telekom", + CurrentServing: false, + }, }, }, }, @@ -616,11 +627,14 @@ func makeDPC(key string, timePrio time.Time, intfs selectedIntfs) types.DevicePo }, WirelessCfg: types.WirelessConfig{ WType: types.WirelessTypeCellular, - Cellular: []types.CellConfig{ - { - APN: "apn", - LocationTracking: true, + Cellular: types.CellNetPortConfig{ + AccessPoints: []types.CellularAccessPoint{ + { + APN: "apn", + Activated: true, + }, }, + LocationTracking: true, }, }, }) @@ -1043,7 +1057,7 @@ func TestDNS(test *testing.T) { t.Expect(eth0State.NtpServers).To(HaveLen(1)) t.Expect(eth0State.NtpServers[0].String()).To(Equal("132.163.96.5")) t.Expect(eth0State.Subnet.String()).To(Equal("192.168.10.0/24")) - t.Expect(eth0State.MacAddr).To(Equal("02:00:00:00:00:01")) + t.Expect(eth0State.MacAddr.String()).To(Equal("02:00:00:00:00:01")) t.Expect(eth0State.Up).To(BeTrue()) t.Expect(eth0State.Type).To(BeEquivalentTo(types.NT_IPV4)) t.Expect(eth0State.Dhcp).To(BeEquivalentTo(types.DT_CLIENT)) @@ -1066,7 +1080,7 @@ func TestDNS(test *testing.T) { t.Expect(eth1State.NtpServers).To(HaveLen(1)) t.Expect(eth1State.NtpServers[0].String()).To(Equal("132.163.96.6")) t.Expect(eth1State.Subnet.String()).To(Equal("172.20.1.0/24")) - t.Expect(eth1State.MacAddr).To(Equal("02:00:00:00:00:02")) + t.Expect(eth1State.MacAddr.String()).To(Equal("02:00:00:00:00:02")) t.Expect(eth1State.Up).To(BeTrue()) t.Expect(eth1State.Type).To(BeEquivalentTo(types.NT_IPV4)) t.Expect(eth1State.Dhcp).To(BeEquivalentTo(types.DT_CLIENT)) @@ -1128,7 +1142,7 @@ func TestWireless(test *testing.T) { PhysAddrs: types.WwanPhysAddrs{ Interface: "wwan0", }, - Apns: []string{"apn"}, + APN: "apn", LocationTracking: true, }, }, @@ -1152,10 +1166,16 @@ func TestWireless(test *testing.T) { t.Expect(wwanDNS.Cellular.Module.Revision).To(Equal("SWI9X50C_01.08.04.00")) t.Expect(wwanDNS.Cellular.ConfigError).To(BeEmpty()) t.Expect(wwanDNS.Cellular.ProbeError).To(BeEmpty()) - t.Expect(wwanDNS.Cellular.Providers).To(HaveLen(1)) - t.Expect(wwanDNS.Cellular.Providers[0].Description).To(Equal("AT&T")) - t.Expect(wwanDNS.Cellular.Providers[0].CurrentServing).To(BeTrue()) - t.Expect(wwanDNS.Cellular.Providers[0].PLMN).To(Equal("310-410")) + t.Expect(wwanDNS.Cellular.CurrentProvider.Description).To(Equal("AT&T")) + t.Expect(wwanDNS.Cellular.CurrentProvider.CurrentServing).To(BeTrue()) + t.Expect(wwanDNS.Cellular.CurrentProvider.PLMN).To(Equal("310-410")) + t.Expect(wwanDNS.Cellular.VisibleProviders).To(HaveLen(2)) + t.Expect(wwanDNS.Cellular.VisibleProviders[0].Description).To(Equal("AT&T")) + t.Expect(wwanDNS.Cellular.VisibleProviders[0].CurrentServing).To(BeTrue()) + t.Expect(wwanDNS.Cellular.VisibleProviders[0].PLMN).To(Equal("310-410")) + t.Expect(wwanDNS.Cellular.VisibleProviders[1].Description).To(Equal("Telekom")) + t.Expect(wwanDNS.Cellular.VisibleProviders[1].CurrentServing).To(BeFalse()) + t.Expect(wwanDNS.Cellular.VisibleProviders[1].PLMN).To(Equal("231-02")) t.Expect(wwanDNS.Cellular.SimCards).To(HaveLen(1)) t.Expect(wwanDNS.Cellular.SimCards[0].Name).To(Equal("89012703578345957137")) // ICCID put by DoSanitize() t.Expect(wwanDNS.Cellular.SimCards[0].ICCID).To(Equal("89012703578345957137")) @@ -1718,7 +1738,7 @@ func TestVlansAndBonds(test *testing.T) { t.Expect(eth0State.DNSServers).To(BeEmpty()) t.Expect(eth0State.NtpServers).To(BeEmpty()) t.Expect(eth0State.Subnet.IP).To(BeNil()) - t.Expect(eth0State.MacAddr).To(Equal("02:00:00:00:00:01")) + t.Expect(eth0State.MacAddr.String()).To(Equal("02:00:00:00:00:01")) t.Expect(eth0State.Up).To(BeTrue()) t.Expect(eth0State.Type).To(BeEquivalentTo(types.NT_NOOP)) t.Expect(eth0State.Dhcp).To(BeEquivalentTo(types.DT_NOOP)) @@ -1735,7 +1755,7 @@ func TestVlansAndBonds(test *testing.T) { t.Expect(eth1State.DNSServers).To(BeEmpty()) t.Expect(eth1State.NtpServers).To(BeEmpty()) t.Expect(eth1State.Subnet.IP).To(BeNil()) - t.Expect(eth1State.MacAddr).To(Equal("02:00:00:00:00:02")) + t.Expect(eth1State.MacAddr.String()).To(Equal("02:00:00:00:00:02")) t.Expect(eth1State.Up).To(BeTrue()) t.Expect(eth1State.Type).To(BeEquivalentTo(types.NT_NOOP)) t.Expect(eth1State.Dhcp).To(BeEquivalentTo(types.DT_NOOP)) @@ -1751,7 +1771,7 @@ func TestVlansAndBonds(test *testing.T) { t.Expect(bond0State.DNSServers).To(BeEmpty()) t.Expect(bond0State.NtpServers).To(BeEmpty()) t.Expect(bond0State.Subnet.IP).To(BeNil()) - t.Expect(bond0State.MacAddr).To(Equal("02:00:00:00:00:03")) + t.Expect(bond0State.MacAddr.String()).To(Equal("02:00:00:00:00:03")) t.Expect(bond0State.Up).To(BeTrue()) t.Expect(bond0State.Type).To(BeEquivalentTo(types.NT_NOOP)) t.Expect(bond0State.Dhcp).To(BeEquivalentTo(types.DT_NOOP)) @@ -1770,7 +1790,7 @@ func TestVlansAndBonds(test *testing.T) { t.Expect(vlan100State.NtpServers).To(HaveLen(1)) t.Expect(vlan100State.NtpServers[0].String()).To(Equal("132.163.96.5")) t.Expect(vlan100State.Subnet.String()).To(Equal("192.168.10.0/24")) - t.Expect(vlan100State.MacAddr).To(Equal("02:00:00:00:00:04")) + t.Expect(vlan100State.MacAddr.String()).To(Equal("02:00:00:00:00:04")) t.Expect(vlan100State.Up).To(BeTrue()) t.Expect(vlan100State.Type).To(BeEquivalentTo(types.NT_IPV4)) t.Expect(vlan100State.Dhcp).To(BeEquivalentTo(types.DT_CLIENT)) @@ -1790,7 +1810,7 @@ func TestVlansAndBonds(test *testing.T) { t.Expect(vlan200State.NtpServers).To(HaveLen(1)) t.Expect(vlan200State.NtpServers[0].String()).To(Equal("132.163.96.6")) t.Expect(vlan200State.Subnet.String()).To(Equal("172.20.1.0/24")) - t.Expect(vlan200State.MacAddr).To(Equal("02:00:00:00:00:05")) + t.Expect(vlan200State.MacAddr.String()).To(Equal("02:00:00:00:00:05")) t.Expect(vlan200State.Up).To(BeTrue()) t.Expect(vlan200State.Type).To(BeEquivalentTo(types.NT_IPV4)) t.Expect(vlan200State.Dhcp).To(BeEquivalentTo(types.DT_CLIENT)) diff --git a/pkg/pillar/dpcmanager/verify.go b/pkg/pillar/dpcmanager/verify.go index c81b758d3b3..aebcf7fd46b 100644 --- a/pkg/pillar/dpcmanager/verify.go +++ b/pkg/pillar/dpcmanager/verify.go @@ -63,6 +63,9 @@ func (m *DpcManager) setupVerify(index int, reason string) { m.dpcList.CurrentIndex = index m.dpcVerify.inProgress = true m.dpcVerify.startedAt = time.Now() + if dpc := m.currentDPC(); dpc != nil { + m.setDiscoveredWwanIfNames(dpc) + } m.Log.Functionf("DPC verify: Started testing DPC (index %d): %v", m.dpcList.CurrentIndex, m.dpcList.PortConfigList[m.dpcList.CurrentIndex]) } diff --git a/pkg/pillar/dpcmanager/wwan.go b/pkg/pillar/dpcmanager/wwan.go index cc54b41e7c0..6ba850f7476 100644 --- a/pkg/pillar/dpcmanager/wwan.go +++ b/pkg/pillar/dpcmanager/wwan.go @@ -9,7 +9,6 @@ import ( "fmt" "io" "os" - "reflect" "strings" "time" @@ -142,7 +141,7 @@ func (w *wwanWatcher) createWwanDir() error { } // reloadWwanStatus loads the latest state data published by the wwan service. -func (m *DpcManager) reloadWwanStatus() { +func (m *DpcManager) reloadWwanStatus(ctx context.Context) { status, err := m.WwanWatcher.LoadStatus() if err != nil { // Already logged. @@ -163,7 +162,7 @@ func (m *DpcManager) reloadWwanStatus() { } status.DoSanitize() - changed := !reflect.DeepEqual(m.wwanStatus, status) + changed := !m.wwanStatus.Equal(status) if changed { m.Log.Functionf("Have new wwan status: %v", m.wwanStatus) } @@ -203,6 +202,13 @@ func (m *DpcManager) reloadWwanStatus() { } if changed || wasInProgress { + if m.currentDPC() != nil { + changedDPC := m.setDiscoveredWwanIfNames(m.currentDPC()) + if changedDPC { + m.publishDPCL() + } + } + m.restartVerify(ctx, "wwan status changed") m.updateDNS() } if changed && m.PubWwanStatus != nil { @@ -222,7 +228,7 @@ func (m *DpcManager) reloadWwanMetrics() { // Already logged. return } - if reflect.DeepEqual(metrics, m.wwanMetrics) { + if m.wwanMetrics.Equal(metrics) { // nothing really changed return } @@ -342,3 +348,62 @@ func (m *DpcManager) doUpdateRadioSilence(ctx context.Context, newRS types.Radio m.rsStatus.ConfigError = strings.Join(errMsgs, "\n") m.updateDNS() } + +// Handle cellular modems referenced in the device model by USB or PCI addresses +// but without interface name included. +// Use status published by the wwan microservice to learn the name of the interface +// created by the kernel for the modem data-path. +func (m *DpcManager) setDiscoveredWwanIfNames(dpc *types.DevicePortConfig) bool { + var changed bool + ifNames := make(map[string]string) // interface name -> logical label + currentDPC := m.currentDPC() + for i := range dpc.Ports { + port := &dpc.Ports[i] + if port.WirelessCfg.WType != types.WirelessTypeCellular { + continue + } + wwanNetStatus, found := m.wwanStatus.LookupNetworkStatus(port.Logicallabel) + if found && wwanNetStatus.PhysAddrs.Interface != "" { + ifNames[wwanNetStatus.PhysAddrs.Interface] = port.Logicallabel + if port.IfName != wwanNetStatus.PhysAddrs.Interface { + changed = true + } + } else if port.IfName == "" && currentDPC != nil && currentDPC != dpc { + // Maybe we received new DPC while modem status is not yet available. + // See if we can get interface name from the current DPC. + currentPortConfig := currentDPC.LookupPortByLogicallabel(port.Logicallabel) + if currentPortConfig != nil && currentPortConfig.IfName != "" && + currentPortConfig.USBAddr == port.USBAddr && + currentPortConfig.PCIAddr == port.PCIAddr { + if _, used := ifNames[currentPortConfig.IfName]; !used { + ifNames[currentPortConfig.IfName] = port.Logicallabel + changed = true + } + } + } + } + if !changed { + return false + } + updatedPorts := make([]types.NetworkPortConfig, len(dpc.Ports)) + // First see if any wwan modem has changed interface name. + for i := range dpc.Ports { + port := &dpc.Ports[i] + updatedPorts[i] = *port // copy + if port.IfName != "" { + if port2 := ifNames[port.IfName]; port2 != "" && port2 != port.Logicallabel { + // This interface name was taken by port2. + updatedPorts[i].IfName = "" + m.Log.Noticef("Interface name %s was taken from port %s by port %s", + port.IfName, port.Logicallabel, port2) + } + } + for ifName, port2 := range ifNames { + if port.Logicallabel == port2 { + updatedPorts[i].IfName = ifName + } + } + } + dpc.Ports = updatedPorts + return true +} diff --git a/pkg/pillar/dpcreconciler/linux.go b/pkg/pillar/dpcreconciler/linux.go index 5de1345f11f..4da37a94fae 100644 --- a/pkg/pillar/dpcreconciler/linux.go +++ b/pkg/pillar/dpcreconciler/linux.go @@ -646,10 +646,10 @@ func (r *LinuxDpcReconciler) updateCurrentPhysicalIO( dpc types.DevicePortConfig, aa types.AssignableAdapters) (changed bool) { currentIO := dg.New(dg.InitArgs{Name: PhysicalIoSG}) for _, port := range dpc.Ports { - if port.L2Type != types.L2LinkTypeNone { + if port.L2Type != types.L2LinkTypeNone || port.IfName == "" { continue } - ioBundle := aa.LookupIoBundleIfName(port.IfName) + ioBundle := aa.LookupIoBundleLogicallabel(port.Logicallabel) if ioBundle != nil && ioBundle.IsPCIBack { // Until confirmed by domainmgr that the interface is out of PCIBack // and ready, pretend that it doesn't exist. This is because domainmgr @@ -660,6 +660,9 @@ func (r *LinuxDpcReconciler) updateCurrentPhysicalIO( // entries in AssignableAdapters should not be ignored. continue } + if port.IfName == "" { + continue + } _, found, err := r.NetworkMonitor.GetInterfaceIndex(port.IfName) if err != nil { r.Log.Errorf("updateCurrentPhysicalIO: failed to get ifIndex for %s: %v", @@ -690,7 +693,7 @@ func (r *LinuxDpcReconciler) updateCurrentAdapterAddrs( sgPath := dg.NewSubGraphPath(L3SG, AdaptersSG, AdapterAddrsSG) currentAddrs := dg.New(dg.InitArgs{Name: AdapterAddrsSG}) for _, port := range dpc.Ports { - if !port.IsL3Port { + if !port.IsL3Port || port.IfName == "" { continue } ifIndex, found, err := r.NetworkMonitor.GetInterfaceIndex(port.IfName) @@ -740,6 +743,9 @@ func (r *LinuxDpcReconciler) updateCurrentRoutes(dpc types.DevicePortConfig) (ch } } for _, port := range dpc.Ports { + if port.IfName == "" { + continue + } ifIndex, found, err := r.NetworkMonitor.GetInterfaceIndex(port.IfName) if err != nil { r.Log.Errorf("updateCurrentRoutes: failed to get ifIndex for %s: %v", @@ -830,7 +836,7 @@ func (r *LinuxDpcReconciler) getIntendedGlobalCfg(dpc types.DevicePortConfig) dg // Intended content of /etc/resolv.conf dnsServers := make(map[string][]net.IP) for _, port := range dpc.Ports { - if !port.IsMgmt { + if !port.IsMgmt || port.IfName == "" { continue } ifIndex, found, err := r.NetworkMonitor.GetInterfaceIndex(port.IfName) @@ -861,6 +867,9 @@ func (r *LinuxDpcReconciler) getIntendedPhysicalIO(dpc types.DevicePortConfig) d } intendedIO := dg.New(graphArgs) for _, port := range dpc.Ports { + if port.IfName == "" { + continue + } if port.L2Type == types.L2LinkTypeNone { intendedIO.PutItem(generic.PhysIf{ LogicalLabel: port.Logicallabel, @@ -878,6 +887,9 @@ func (r *LinuxDpcReconciler) getIntendedLogicalIO(dpc types.DevicePortConfig) dg } intendedIO := dg.New(graphArgs) for _, port := range dpc.Ports { + if port.IfName == "" { + continue + } switch port.L2Type { case types.L2LinkTypeVLAN: parent := dpc.LookupPortByLogicallabel(port.VLAN.ParentPort) @@ -962,7 +974,7 @@ func (r *LinuxDpcReconciler) getIntendedAdapters(dpc types.DevicePortConfig) dg. } intendedAdapters := dg.New(graphArgs) for _, port := range dpc.Ports { - if !port.IsL3Port { + if !port.IsL3Port || port.IfName == "" { continue } adapter := linux.Adapter{ @@ -1010,6 +1022,9 @@ func (r *LinuxDpcReconciler) getIntendedSrcIPRules(dpc types.DevicePortConfig) d } intendedRules := dg.New(graphArgs) for _, port := range dpc.Ports { + if port.IfName == "" { + continue + } ifIndex, found, err := r.NetworkMonitor.GetInterfaceIndex(port.IfName) if err != nil { r.Log.Errorf("getIntendedSrcIPRules: failed to get ifIndex for %s: %v", @@ -1066,6 +1081,9 @@ func (r *LinuxDpcReconciler) getIntendedRoutes(dpc types.DevicePortConfig) dg.Gr } } for _, port := range dpc.Ports { + if port.IfName == "" { + continue + } ifIndex, found, err := r.NetworkMonitor.GetInterfaceIndex(port.IfName) if err != nil { r.Log.Errorf("getIntendedRoutes: failed to get ifIndex for %s: %v", @@ -1133,6 +1151,9 @@ type portAddr struct { func (r *LinuxDpcReconciler) groupPortAddrs(dpc types.DevicePortConfig) map[string][]portAddr { arpGroups := map[string][]portAddr{} for _, port := range dpc.Ports { + if port.IfName == "" { + continue + } ifIndex, found, err := r.NetworkMonitor.GetInterfaceIndex(port.IfName) if err != nil { r.Log.Errorf("groupPortAddrs: failed to get ifIndex for %s: %v", @@ -1217,8 +1238,17 @@ func (r *LinuxDpcReconciler) getIntendedWirelessCfg(dpc types.DevicePortConfig, rsImposed := radioSilence.Imposed intendedWirelessCfg.PutItem( r.getIntendedWlanConfig(dpc, rsImposed), nil) - intendedWirelessCfg.PutItem( - r.getIntendedWwanConfig(dpc, aa, rsImposed), nil) + if dpc.Key != "" { + // Do not send config to wwan microservice until we receive DPC. + // The default behaviour of wwan microservice (i.e. without config) is to disable + // radios of all cellular modems, which is the same effect we would get with empty + // config anyway. However, without config the wwan microservice does just that + // and does not waste time collecting e.g. modem state data. This is important + // because when the DPC arrives, wwan microservice won't be blocked on retrieving + // some state data but will be ready to apply the config immediately. + intendedWirelessCfg.PutItem( + r.getIntendedWwanConfig(dpc, aa, rsImposed), nil) + } return intendedWirelessCfg } @@ -1307,38 +1337,66 @@ func (r *LinuxDpcReconciler) getWifiCredentials(wifi types.WifiConfig) (types.En func (r *LinuxDpcReconciler) getIntendedWwanConfig(dpc types.DevicePortConfig, aa types.AssignableAdapters, radioSilence bool) dg.Item { - config := types.WwanConfig{RadioSilence: radioSilence, Networks: []types.WwanNetworkConfig{}} - + config := types.WwanConfig{ + RadioSilence: radioSilence, + Networks: []types.WwanNetworkConfig{}, + } for _, port := range dpc.Ports { - if port.WirelessCfg.WType != types.WirelessTypeCellular || len(port.WirelessCfg.Cellular) == 0 { + if port.WirelessCfg.WType != types.WirelessTypeCellular { continue } - ioBundle := aa.LookupIoBundleLogicallabel(port.Logicallabel) - if ioBundle == nil { - r.Log.Warnf("Failed to find adapter with logical label '%s'", port.Logicallabel) - continue + if !aa.Initialized { + r.Log.Warnf("getIntendedWwanConfig: AA is not yet initialized, "+ + "skipping IsPCIBack check for port %s", port.Logicallabel) + } else { + ioBundle := aa.LookupIoBundleLogicallabel(port.Logicallabel) + if ioBundle == nil { + r.Log.Warnf("Failed to find adapter with logical label '%s'", + port.Logicallabel) + continue + } + if ioBundle.IsPCIBack { + r.Log.Warnf("getIntendedWwanConfig: wwan adapter with the logical label "+ + "'%s' is assigned to pciback, skipping", port.Logicallabel) + continue + } } - if ioBundle.IsPCIBack { - r.Log.Warnf("wwan adapter with the logical label '%s' is assigned to pciback, skipping", - port.Logicallabel) + var accessPoint *types.CellularAccessPoint + for _, ap := range port.WirelessCfg.Cellular.AccessPoints { + if ap.Activated { + accessPoint = &ap + break + } + } + if accessPoint == nil { + r.Log.Warnf("getIntendedWwanConfig: no activated access point for port %s, "+ + "skipping", port.Logicallabel) continue } - // XXX Limited to a single APN for now - cellCfg := port.WirelessCfg.Cellular[0] + // Prefer USB and PCI addresses over interface name. + // Plus we want to avoid changing /run/wwan/config.json just to put there + // discovered interface name (it is the wwan microservice that discovered it anyway + // and changing config.json would just trigger many redundant operations inside + // of that microservice) + var physAddress types.WwanPhysAddrs + if port.USBAddr != "" || port.PCIAddr != "" { + physAddress.USB = port.USBAddr + physAddress.PCI = port.PCIAddr + } else { + physAddress.Interface = port.IfName + } network := types.WwanNetworkConfig{ - LogicalLabel: port.Logicallabel, - PhysAddrs: types.WwanPhysAddrs{ - Interface: ioBundle.Ifname, - USB: ioBundle.UsbAddr, - PCI: ioBundle.PciLong, - }, - Apns: []string{cellCfg.APN}, - Proxies: port.Proxies, - Probe: types.WwanProbe{ - Disable: cellCfg.DisableProbe, - Address: cellCfg.ProbeAddr, - }, - LocationTracking: cellCfg.LocationTracking, + // TODO: Username + Password (will be done in the next PR) + LogicalLabel: port.Logicallabel, + PhysAddrs: physAddress, + SIMSlot: accessPoint.SIMSlot, + APN: accessPoint.APN, + PreferredPLMNs: accessPoint.PreferredPLMNs, + PreferredRATs: accessPoint.PreferredRATs, + ForbidRoaming: accessPoint.ForbidRoaming, + Proxies: port.Proxies, + Probe: port.WirelessCfg.Cellular.Probe, + LocationTracking: port.WirelessCfg.Cellular.LocationTracking, } config.Networks = append(config.Networks, network) } @@ -1651,6 +1709,9 @@ func (r *LinuxDpcReconciler) getIntendedACLs( // i.e. these rules are below protoMarkV4Rules/protoMarkV6Rules var dropMarkRules []iptables.Rule for _, port := range dpc.Ports { + if port.IfName == "" { + continue + } dropIngressRule := iptables.Rule{ RuleLabel: fmt.Sprintf("Ingress from %s", port.IfName), MatchOpts: []string{"-i", port.IfName}, diff --git a/pkg/pillar/dpcreconciler/linux_test.go b/pkg/pillar/dpcreconciler/linux_test.go index dde66f3ae70..1ed99dff14a 100644 --- a/pkg/pillar/dpcreconciler/linux_test.go +++ b/pkg/pillar/dpcreconciler/linux_test.go @@ -579,7 +579,7 @@ func TestWireless(test *testing.T) { }, }, { - IfName: "wwan0", + USBAddr: "3:7.4", Phylabel: "wwan0", Logicallabel: "mock-wwan0", IsMgmt: true, @@ -590,9 +590,12 @@ func TestWireless(test *testing.T) { }, WirelessCfg: types.WirelessConfig{ WType: types.WirelessTypeCellular, - Cellular: []types.CellConfig{ - { - APN: "my-apn", + Cellular: types.CellNetPortConfig{ + AccessPoints: []types.CellularAccessPoint{ + { + APN: "my-apn", + Activated: true, + }, }, }, }, @@ -619,7 +622,7 @@ func TestWireless(test *testing.T) { Logicallabel: "mock-wwan0", Usage: evecommon.PhyIoMemberUsage_PhyIoUsageMgmtAndApps, Cost: 0, - Ifname: "wwan0", + UsbAddr: "3:7.4", MacAddr: wwan0Mac, IsPCIBack: false, IsPort: true, @@ -641,10 +644,23 @@ func TestWireless(test *testing.T) { t.Expect(itemDescription(wlan)).ToNot(ContainSubstring("my-password")) t.Expect(itemDescription(wlan)).To(ContainSubstring("enable RF: true")) wwan := dg.Reference(generic.Wwan{}) - t.Expect(itemDescription(wwan)).To(ContainSubstring("Apns:[my-apn]")) - t.Expect(itemDescription(wwan)).To(ContainSubstring("Interface:wwan0")) + t.Expect(itemDescription(wwan)).To(ContainSubstring("SIMSlot:0 APN:my-apn")) + t.Expect(itemDescription(wwan)).To(ContainSubstring("PhysAddrs:{Interface: USB:3:7.4 PCI: Dev:}")) t.Expect(itemDescription(wwan)).To(ContainSubstring("LogicalLabel:mock-wwan0")) t.Expect(itemDescription(wwan)).To(ContainSubstring("RadioSilence:false")) + t.Expect(itemCountWithType(generic.IOHandleTypename)).To(Equal(1)) + t.Expect(itemCountWithType(generic.AdapterTypename)).To(Equal(1)) + t.Expect(itemCountWithType(generic.AdapterAddrsTypename)).To(Equal(1)) + t.Expect(itemCountWithType(generic.DhcpcdTypename)).To(Equal(1)) + t.Expect(itemCountWithType(generic.IPv4RouteTypename)).To(Equal(0)) + t.Expect(itemCountWithType(generic.ArpTypename)).To(Equal(0)) + + // Simulate that DPC Manager learnt the wwan interface name. + dpc.Ports = []types.NetworkPortConfig{dpc.Ports[0], dpc.Ports[1]} // copy + dpc.Ports[1].IfName = "wwan0" + ctx = reconciler.MockRun(context.Background()) + status = dpcReconciler.Reconcile(ctx, dpcrec.Args{GCP: *gcp, DPC: dpc, AA: aa}) + t.Expect(status.Error).To(BeNil()) t.Expect(itemCountWithType(generic.IOHandleTypename)).To(Equal(2)) t.Expect(itemCountWithType(generic.AdapterTypename)).To(Equal(2)) t.Expect(itemCountWithType(generic.AdapterAddrsTypename)).To(Equal(2)) diff --git a/pkg/pillar/iptables/configitem.go b/pkg/pillar/iptables/configitem.go index 1dc6ca4e530..72b5c3c2ee5 100644 --- a/pkg/pillar/iptables/configitem.go +++ b/pkg/pillar/iptables/configitem.go @@ -16,7 +16,7 @@ import ( "github.com/lf-edge/eve/libs/reconciler" "github.com/lf-edge/eve/pkg/pillar/base" "github.com/lf-edge/eve/pkg/pillar/nireconciler/genericitems" - "github.com/lf-edge/eve/pkg/pillar/utils" + "github.com/lf-edge/eve/pkg/pillar/utils/generics" ) const ( @@ -192,9 +192,9 @@ func (r Rule) Equal(other depgraph.Item) bool { r.Table == r2.Table && r.ChainName == r2.ChainName && r.ForIPv6 == r2.ForIPv6 && - utils.EqualSets(r.AppliedBefore, r2.AppliedBefore) && - utils.EqualLists(r.MatchOpts, r2.MatchOpts) && - utils.EqualLists(r.TargetOpts, r2.TargetOpts) && + generics.EqualSets(r.AppliedBefore, r2.AppliedBefore) && + generics.EqualLists(r.MatchOpts, r2.MatchOpts) && + generics.EqualLists(r.TargetOpts, r2.TargetOpts) && r.Target == r2.Target && r.Description == r2.Description } diff --git a/pkg/pillar/netmonitor/linux.go b/pkg/pillar/netmonitor/linux.go index 31eff967a42..43d1d979d80 100644 --- a/pkg/pillar/netmonitor/linux.go +++ b/pkg/pillar/netmonitor/linux.go @@ -154,6 +154,7 @@ func (m *LinuxNetworkMonitor) ifAttrsFromLink(link netlink.Link) IfAttrs { LowerUp: link.Attrs().OperState == netlink.OperUp, Enslaved: link.Attrs().MasterIndex != 0, MasterIfIndex: link.Attrs().MasterIndex, + MTU: uint16(link.Attrs().MTU), } } diff --git a/pkg/pillar/netmonitor/netmonitor.go b/pkg/pillar/netmonitor/netmonitor.go index bdcc129b8ed..11e4fa53ba6 100644 --- a/pkg/pillar/netmonitor/netmonitor.go +++ b/pkg/pillar/netmonitor/netmonitor.go @@ -146,6 +146,8 @@ type IfAttrs struct { Enslaved bool // If interface is enslaved, this should contain index of the master interface. MasterIfIndex int + // Maximum Transmission Unit configured on the interface. + MTU uint16 } // Equal allows to compare two sets of interface attributes for equality. diff --git a/pkg/pillar/nireconciler/genericitems/dnsmasq.go b/pkg/pillar/nireconciler/genericitems/dnsmasq.go index 8d9823fe33f..b1747f7fcf5 100644 --- a/pkg/pillar/nireconciler/genericitems/dnsmasq.go +++ b/pkg/pillar/nireconciler/genericitems/dnsmasq.go @@ -19,7 +19,8 @@ import ( "github.com/lf-edge/eve/libs/reconciler" "github.com/lf-edge/eve/pkg/pillar/base" "github.com/lf-edge/eve/pkg/pillar/devicenetwork" - "github.com/lf-edge/eve/pkg/pillar/utils" + "github.com/lf-edge/eve/pkg/pillar/utils/generics" + "github.com/lf-edge/eve/pkg/pillar/utils/netutils" uuid "github.com/satori/go.uuid" "github.com/sirupsen/logrus" ) @@ -82,16 +83,16 @@ func (d DHCPServer) String() string { // Equal compares two DHCPServer instances func (d DHCPServer) Equal(d2 DHCPServer, withStaticEntries bool) bool { - return utils.EqualIPNets(d.Subnet, d2.Subnet) && + return netutils.EqualIPNets(d.Subnet, d2.Subnet) && d.AllOnesNetmask == d2.AllOnesNetmask && - utils.EqualIPs(d.IPRange.FromIP, d2.IPRange.FromIP) && - utils.EqualIPs(d.IPRange.ToIP, d2.IPRange.ToIP) && - utils.EqualIPs(d.GatewayIP, d2.GatewayIP) && + netutils.EqualIPs(d.IPRange.FromIP, d2.IPRange.FromIP) && + netutils.EqualIPs(d.IPRange.ToIP, d2.IPRange.ToIP) && + netutils.EqualIPs(d.GatewayIP, d2.GatewayIP) && d.DomainName == d2.DomainName && - utils.EqualSetsFn(d.DNSServers, d2.DNSServers, utils.EqualIPs) && - utils.EqualSetsFn(d.NTPServers, d2.NTPServers, utils.EqualIPs) && + generics.EqualSetsFn(d.DNSServers, d2.DNSServers, netutils.EqualIPs) && + generics.EqualSetsFn(d.NTPServers, d2.NTPServers, netutils.EqualIPs) && (!withStaticEntries || - utils.EqualSetsFn(d.StaticEntries, d2.StaticEntries, equalMACToIP)) + generics.EqualSetsFn(d.StaticEntries, d2.StaticEntries, equalMACToIP)) } // DNSServer : part of the dnsmasq config specific to DNS server. @@ -125,12 +126,12 @@ func (d DNSServer) String() string { // Equal compares two DNSServer instances func (d DNSServer) Equal(d2 DNSServer, withStaticEntries bool) bool { - return utils.EqualIPs(d.ListenIP, d2.ListenIP) && + return netutils.EqualIPs(d.ListenIP, d2.ListenIP) && d.UplinkIf == d2.UplinkIf && - utils.EqualSetsFn(d.UpstreamServers, d2.UpstreamServers, utils.EqualIPs) && - utils.EqualSetsFn(d.LinuxIPSets, d2.LinuxIPSets, equalLinuxIPSet) && + generics.EqualSetsFn(d.UpstreamServers, d2.UpstreamServers, netutils.EqualIPs) && + generics.EqualSetsFn(d.LinuxIPSets, d2.LinuxIPSets, equalLinuxIPSet) && (!withStaticEntries || - utils.EqualSetsFn(d.StaticEntries, d2.StaticEntries, equalHostnameToIP)) + generics.EqualSetsFn(d.StaticEntries, d2.StaticEntries, equalHostnameToIP)) } // IPRange : a range of IP addresses. @@ -150,7 +151,7 @@ type MACToIP struct { func equalMACToIP(a, b MACToIP) bool { return bytes.Equal(a.MAC, b.MAC) && - utils.EqualIPs(a.IP, b.IP) && + netutils.EqualIPs(a.IP, b.IP) && a.Hostname == b.Hostname } @@ -162,7 +163,7 @@ type HostnameToIP struct { func equalHostnameToIP(a, b HostnameToIP) bool { return a.Hostname == b.Hostname && - utils.EqualIPs(a.IP, b.IP) + netutils.EqualIPs(a.IP, b.IP) } // LinuxIPSet : see https://www.netfilter.org/projects/ipset/index.html @@ -174,8 +175,8 @@ type LinuxIPSet struct { } func equalLinuxIPSet(a, b LinuxIPSet) bool { - return utils.EqualSets(a.Domains, b.Domains) && - utils.EqualSets(a.Sets, b.Sets) + return generics.EqualSets(a.Domains, b.Domains) && + generics.EqualSets(a.Sets, b.Sets) } // NetworkIf : network interface used by dnsmasq. @@ -351,7 +352,7 @@ func (c *DnsmasqConfigurator) Modify(ctx context.Context, oldItem, newItem dg.It if !isDnsmasq { return fmt.Errorf("invalid item type %T, expected Dnsmasq", newItem) } - obsoleteDHCPHosts, newDHCPHosts := utils.DiffSetsFn( + obsoleteDHCPHosts, newDHCPHosts := generics.DiffSetsFn( oldDnsmasq.DHCPServer.StaticEntries, newDnsmasq.DHCPServer.StaticEntries, equalMACToIP) for _, host := range obsoleteDHCPHosts { @@ -364,7 +365,7 @@ func (c *DnsmasqConfigurator) Modify(ctx context.Context, oldItem, newItem dg.It return err } } - obsoleteDNSHosts, newDNSHosts := utils.DiffSetsFn( + obsoleteDNSHosts, newDNSHosts := generics.DiffSetsFn( oldDnsmasq.DNSServer.StaticEntries, newDnsmasq.DNSServer.StaticEntries, equalHostnameToIP) for _, host := range obsoleteDNSHosts { diff --git a/pkg/pillar/nireconciler/genericitems/httpsrv.go b/pkg/pillar/nireconciler/genericitems/httpsrv.go index 4bcdeac8777..a02a42838d1 100644 --- a/pkg/pillar/nireconciler/genericitems/httpsrv.go +++ b/pkg/pillar/nireconciler/genericitems/httpsrv.go @@ -19,7 +19,7 @@ import ( "github.com/lf-edge/eve/libs/reconciler" "github.com/lf-edge/eve/pkg/pillar/agentlog" "github.com/lf-edge/eve/pkg/pillar/base" - "github.com/lf-edge/eve/pkg/pillar/utils" + "github.com/lf-edge/eve/pkg/pillar/utils/netutils" uuid "github.com/satori/go.uuid" "github.com/sirupsen/logrus" ) @@ -68,7 +68,7 @@ func (s HTTPServer) Type() string { func (s HTTPServer) Equal(other dg.Item) bool { s2 := other.(HTTPServer) return s.ForNI == s2.ForNI && - utils.EqualIPs(s.ListenIP, s2.ListenIP) && + netutils.EqualIPs(s.ListenIP, s2.ListenIP) && s.ListenIf == s2.ListenIf && s.Port == s2.Port } diff --git a/pkg/pillar/nireconciler/genericitems/ipreserve.go b/pkg/pillar/nireconciler/genericitems/ipreserve.go index a86c7d2f320..2dac6c70038 100644 --- a/pkg/pillar/nireconciler/genericitems/ipreserve.go +++ b/pkg/pillar/nireconciler/genericitems/ipreserve.go @@ -11,7 +11,7 @@ import ( dg "github.com/lf-edge/eve/libs/depgraph" "github.com/lf-edge/eve/pkg/pillar/base" - "github.com/lf-edge/eve/pkg/pillar/utils" + "github.com/lf-edge/eve/pkg/pillar/utils/netutils" ) // IPReserve : an item representing allocation and use of an IP address (for bridge). @@ -50,7 +50,7 @@ func (ip IPReserve) Equal(other dg.Item) bool { return false } return ip.NetIf == ip2.NetIf && - utils.EqualIPNets(ip.AddrWithMask, ip2.AddrWithMask) + netutils.EqualIPNets(ip.AddrWithMask, ip2.AddrWithMask) } // External returns false - not used for IPs assigned by NIM. @@ -92,7 +92,7 @@ func (c *IPReserveConfigurator) Delete(ctx context.Context, item dg.Item) error } // NeedsRecreate returns true - change in IPReserve.NetIf usage intentionally triggers -// recreate which cascades to the bridge and other dependent higher-layer items. +// recreate which cascades to the bridge and other dependant higher-layer items. func (c *IPReserveConfigurator) NeedsRecreate(oldItem, newItem dg.Item) (recreate bool) { return true } diff --git a/pkg/pillar/nireconciler/genericitems/uplink.go b/pkg/pillar/nireconciler/genericitems/uplink.go index a1f1870b9e2..a705f2cf5aa 100644 --- a/pkg/pillar/nireconciler/genericitems/uplink.go +++ b/pkg/pillar/nireconciler/genericitems/uplink.go @@ -8,7 +8,8 @@ import ( "net" dg "github.com/lf-edge/eve/libs/depgraph" - "github.com/lf-edge/eve/pkg/pillar/utils" + "github.com/lf-edge/eve/pkg/pillar/utils/generics" + "github.com/lf-edge/eve/pkg/pillar/utils/netutils" ) // Uplink : uplink interface used by network instance for connectivity to outside networks. @@ -51,7 +52,7 @@ func (u Uplink) Equal(other dg.Item) bool { u.LogicalLabel == u2.LogicalLabel && u.MasterIfName == u2.MasterIfName && u.AdminUp == u2.AdminUp && - utils.EqualSetsFn(u.IPAddresses, u2.IPAddresses, utils.EqualIPNets) + generics.EqualSetsFn(u.IPAddresses, u2.IPAddresses, netutils.EqualIPNets) } // External returns true - uplinks are physical interfaces, i.e. not created by zedrouter. diff --git a/pkg/pillar/nireconciler/linux_acl.go b/pkg/pillar/nireconciler/linux_acl.go index d1c64e105c9..71bbc423c4b 100644 --- a/pkg/pillar/nireconciler/linux_acl.go +++ b/pkg/pillar/nireconciler/linux_acl.go @@ -13,7 +13,7 @@ import ( "github.com/lf-edge/eve/pkg/pillar/devicenetwork" "github.com/lf-edge/eve/pkg/pillar/iptables" "github.com/lf-edge/eve/pkg/pillar/types" - "github.com/lf-edge/eve/pkg/pillar/utils" + "github.com/lf-edge/eve/pkg/pillar/utils/generics" uuid "github.com/satori/go.uuid" ) @@ -497,7 +497,7 @@ func (r *LinuxNIReconciler) getIntendedAppConnACLs(niID uuid.UUID, "%s: getIntendedAppConnACLs: failed to get uplink %s addresses: %v", LogAndErrPrefix, uplink, err) } - uplinkIPs = utils.FilterList(uplinkIPs, func(ipNet *net.IPNet) bool { + uplinkIPs = generics.FilterList(uplinkIPs, func(ipNet *net.IPNet) bool { return ipNet.IP.IsGlobalUnicast() }) } @@ -509,7 +509,7 @@ func (r *LinuxNIReconciler) getIntendedAppConnACLs(niID uuid.UUID, continue } } - uplinkIPvXs := utils.FilterList(uplinkIPs, func(ipNet *net.IPNet) bool { + uplinkIPvXs := generics.FilterList(uplinkIPs, func(ipNet *net.IPNet) bool { return (ipNet.IP.To4() == nil) == ipv6 }) for _, item := range r.getIntendedAppConnRawIptables(vif, ul, ipv6) { diff --git a/pkg/pillar/nireconciler/linux_config.go b/pkg/pillar/nireconciler/linux_config.go index c94a8c0be3c..7cc683db71e 100644 --- a/pkg/pillar/nireconciler/linux_config.go +++ b/pkg/pillar/nireconciler/linux_config.go @@ -19,7 +19,8 @@ import ( generic "github.com/lf-edge/eve/pkg/pillar/nireconciler/genericitems" linux "github.com/lf-edge/eve/pkg/pillar/nireconciler/linuxitems" "github.com/lf-edge/eve/pkg/pillar/types" - "github.com/lf-edge/eve/pkg/pillar/utils" + "github.com/lf-edge/eve/pkg/pillar/utils/generics" + "github.com/lf-edge/eve/pkg/pillar/utils/netutils" uuid "github.com/satori/go.uuid" "github.com/vishvananda/netlink" ) @@ -805,7 +806,7 @@ func (r *LinuxNIReconciler) getIntendedDnsmasqCfg(niID uuid.UUID) (items []dg.It if ni.config.NtpServer != nil { ntpServers = append(ntpServers, ni.config.NtpServer) } - ntpServers = utils.FilterDuplicatesFn(ntpServers, utils.EqualIPs) + ntpServers = generics.FilterDuplicatesFn(ntpServers, netutils.EqualIPs) dhcpCfg := generic.DHCPServer{ Subnet: r.getNISubnet(ni), AllOnesNetmask: !r.disableAllOnesNetmask, @@ -988,7 +989,7 @@ func (r *LinuxNIReconciler) getIntendedAppConnCfg(niID uuid.UUID, if vif.GuestIP != nil { ips = append(ips, vif.GuestIP) } - ips = utils.FilterDuplicatesFn(ips, utils.EqualIPs) + ips = generics.FilterDuplicatesFn(ips, netutils.EqualIPs) ipv4Eids := linux.IPSet{ SetName: eidsIpsetName(vif, false), TypeName: "hash:ip", diff --git a/pkg/pillar/nireconciler/linux_reconciler.go b/pkg/pillar/nireconciler/linux_reconciler.go index e13f86bdec6..4f69ec3ae2a 100644 --- a/pkg/pillar/nireconciler/linux_reconciler.go +++ b/pkg/pillar/nireconciler/linux_reconciler.go @@ -21,8 +21,8 @@ import ( generic "github.com/lf-edge/eve/pkg/pillar/nireconciler/genericitems" linux "github.com/lf-edge/eve/pkg/pillar/nireconciler/linuxitems" "github.com/lf-edge/eve/pkg/pillar/types" - "github.com/lf-edge/eve/pkg/pillar/utils" fileutils "github.com/lf-edge/eve/pkg/pillar/utils/file" + "github.com/lf-edge/eve/pkg/pillar/utils/generics" uuid "github.com/satori/go.uuid" "github.com/sirupsen/logrus" "github.com/vishvananda/netlink" @@ -718,14 +718,14 @@ func (r *LinuxNIReconciler) scheduleGlobalCfgRebuild(reason string) { r.pendingReconcile.rebuildGlobalCfg = &pendingCfgRebuild{} } rebuild := r.pendingReconcile.rebuildGlobalCfg - if !utils.ContainsItem(rebuild.reasons, reason) { + if !generics.ContainsItem(rebuild.reasons, reason) { rebuild.reasons = append(rebuild.reasons, reason) } } func (r *LinuxNIReconciler) scheduleNICfgRebuild(niID uuid.UUID, reason string) { rebuild := r.pendingReconcile.rebuildNICfg[niID] - if !utils.ContainsItem(rebuild.reasons, reason) { + if !generics.ContainsItem(rebuild.reasons, reason) { rebuild.reasons = append(rebuild.reasons, reason) } r.pendingReconcile.rebuildNICfg[niID] = rebuild diff --git a/pkg/pillar/nireconciler/linuxitems/bridge.go b/pkg/pillar/nireconciler/linuxitems/bridge.go index e802204146d..368d60434ba 100644 --- a/pkg/pillar/nireconciler/linuxitems/bridge.go +++ b/pkg/pillar/nireconciler/linuxitems/bridge.go @@ -13,7 +13,8 @@ import ( dg "github.com/lf-edge/eve/libs/depgraph" "github.com/lf-edge/eve/pkg/pillar/base" "github.com/lf-edge/eve/pkg/pillar/nireconciler/genericitems" - "github.com/lf-edge/eve/pkg/pillar/utils" + "github.com/lf-edge/eve/pkg/pillar/utils/generics" + "github.com/lf-edge/eve/pkg/pillar/utils/netutils" "github.com/vishvananda/netlink" ) @@ -55,7 +56,7 @@ func (b Bridge) Equal(other dg.Item) bool { return b.IfName == b2.IfName && b.CreatedByNIM == b2.CreatedByNIM && bytes.Equal(b.MACAddress, b2.MACAddress) && - utils.EqualSetsFn(b.IPAddresses, b2.IPAddresses, utils.EqualIPNets) + generics.EqualSetsFn(b.IPAddresses, b2.IPAddresses, netutils.EqualIPNets) } // External returns true if it was created by NIM and not be zedrouter. diff --git a/pkg/pillar/nireconciler/linuxitems/iprule.go b/pkg/pillar/nireconciler/linuxitems/iprule.go index d62d440304c..09312867e2d 100644 --- a/pkg/pillar/nireconciler/linuxitems/iprule.go +++ b/pkg/pillar/nireconciler/linuxitems/iprule.go @@ -11,7 +11,7 @@ import ( dg "github.com/lf-edge/eve/libs/depgraph" "github.com/lf-edge/eve/pkg/pillar/base" - "github.com/lf-edge/eve/pkg/pillar/utils" + "github.com/lf-edge/eve/pkg/pillar/utils/netutils" "github.com/vishvananda/netlink" ) @@ -63,8 +63,8 @@ func (r IPRule) Equal(other dg.Item) bool { r.Table == r2.Table && r.Mark == r2.Mark && r.Mask == r2.Mask && - utils.EqualIPNets(r.Src, r2.Src) && - utils.EqualIPNets(r.Dst, r2.Dst) + netutils.EqualIPNets(r.Src, r2.Src) && + netutils.EqualIPNets(r.Dst, r2.Dst) } // External returns false. diff --git a/pkg/pillar/nireconciler/linuxitems/ipset.go b/pkg/pillar/nireconciler/linuxitems/ipset.go index 71d905754f4..da9e7c26796 100644 --- a/pkg/pillar/nireconciler/linuxitems/ipset.go +++ b/pkg/pillar/nireconciler/linuxitems/ipset.go @@ -12,7 +12,7 @@ import ( dg "github.com/lf-edge/eve/libs/depgraph" "github.com/lf-edge/eve/pkg/pillar/base" generic "github.com/lf-edge/eve/pkg/pillar/nireconciler/genericitems" - "github.com/lf-edge/eve/pkg/pillar/utils" + "github.com/lf-edge/eve/pkg/pillar/utils/generics" "github.com/vishvananda/netlink" ) @@ -56,7 +56,7 @@ func (s IPSet) Equal(other dg.Item) bool { } return s.SetName == s2.SetName && s.TypeName == s2.TypeName && - utils.EqualSets(s.Entries, s2.Entries) + generics.EqualSets(s.Entries, s2.Entries) } // External returns false. diff --git a/pkg/pillar/nireconciler/linuxitems/vlanport.go b/pkg/pillar/nireconciler/linuxitems/vlanport.go index c94aaa2d344..08ce62da912 100644 --- a/pkg/pillar/nireconciler/linuxitems/vlanport.go +++ b/pkg/pillar/nireconciler/linuxitems/vlanport.go @@ -12,7 +12,7 @@ import ( "github.com/lf-edge/eve/pkg/pillar/base" "github.com/lf-edge/eve/pkg/pillar/netmonitor" generic "github.com/lf-edge/eve/pkg/pillar/nireconciler/genericitems" - "github.com/lf-edge/eve/pkg/pillar/utils" + "github.com/lf-edge/eve/pkg/pillar/utils/generics" "github.com/vishvananda/netlink" ) @@ -87,7 +87,7 @@ func (v VLANPort) Equal(other dg.Item) bool { } if isTrunk1 { if v.VLANConfig.TrunkPort.AllVIDs != v2.VLANConfig.TrunkPort.AllVIDs || - !utils.EqualSets(v.VLANConfig.TrunkPort.VIDs, v2.VLANConfig.TrunkPort.VIDs) { + !generics.EqualSets(v.VLANConfig.TrunkPort.VIDs, v2.VLANConfig.TrunkPort.VIDs) { return false } } else { diff --git a/pkg/pillar/nireconciler/nireconciler.go b/pkg/pillar/nireconciler/nireconciler.go index 2e2f780b92e..4566c4b1b2f 100644 --- a/pkg/pillar/nireconciler/nireconciler.go +++ b/pkg/pillar/nireconciler/nireconciler.go @@ -17,7 +17,8 @@ import ( dg "github.com/lf-edge/eve/libs/depgraph" "github.com/lf-edge/eve/pkg/pillar/types" - "github.com/lf-edge/eve/pkg/pillar/utils" + "github.com/lf-edge/eve/pkg/pillar/utils/generics" + "github.com/lf-edge/eve/pkg/pillar/utils/netutils" uuid "github.com/satori/go.uuid" ) @@ -111,8 +112,8 @@ type Uplink struct { func (u Uplink) Equal(u2 Uplink) bool { return u.LogicalLabel == u2.LogicalLabel && u.IfName == u2.IfName && - utils.EqualSetsFn(u.DNSServers, u2.DNSServers, utils.EqualIPs) && - utils.EqualSetsFn(u.NTPServers, u2.NTPServers, utils.EqualIPs) + generics.EqualSetsFn(u.DNSServers, u2.DNSServers, netutils.EqualIPs) && + generics.EqualSetsFn(u.NTPServers, u2.NTPServers, netutils.EqualIPs) } // AppVIF : describes interface created to connect application with network instance. @@ -222,7 +223,7 @@ type AppConnReconcileStatus struct { // Equal compares two instances of AppConnReconcileStatus. func (s AppConnReconcileStatus) Equal(s2 AppConnReconcileStatus) bool { return s.App == s2.App && s.Deleted == s2.Deleted && - utils.EqualSetsFn(s.VIFs, s2.VIFs, + generics.EqualSetsFn(s.VIFs, s2.VIFs, func(v1, v2 AppVIFReconcileStatus) bool { return v1.Equal(v2) }) diff --git a/pkg/pillar/types/cipherinfotypes.go b/pkg/pillar/types/cipherinfotypes.go index 8ff10dd1dda..9ca26017da6 100644 --- a/pkg/pillar/types/cipherinfotypes.go +++ b/pkg/pillar/types/cipherinfotypes.go @@ -143,5 +143,7 @@ type EncryptionBlock struct { DsPassword string WifiUserName string // If the authentication type is EAP WifiPassword string + CellNetUsername string + CellNetPassword string ProtectedUserData string } diff --git a/pkg/pillar/types/zedroutertypes.go b/pkg/pillar/types/zedroutertypes.go index 1e5deb8e791..aea253b2dd7 100644 --- a/pkg/pillar/types/zedroutertypes.go +++ b/pkg/pillar/types/zedroutertypes.go @@ -16,6 +16,7 @@ import ( "github.com/eriknordmark/ipinfo" "github.com/google/go-cmp/cmp" "github.com/lf-edge/eve/pkg/pillar/base" + "github.com/lf-edge/eve/pkg/pillar/utils/generics" uuid "github.com/satori/go.uuid" "github.com/sirupsen/logrus" "github.com/vishvananda/netlink" @@ -848,6 +849,8 @@ func (config *DevicePortConfig) MostlyEqual(config2 *DevicePortConfig) bool { for i, p1 := range config.Ports { p2 := config2.Ports[i] if p1.IfName != p2.IfName || + p1.PCIAddr != p2.PCIAddr || + p1.USBAddr != p2.USBAddr || p1.Phylabel != p2.Phylabel || p1.Logicallabel != p2.Logicallabel || p1.Alias != p2.Alias || @@ -1002,20 +1005,53 @@ type WifiConfig struct { CipherBlockStatus } -// CellConfig - Cellular part of the configure -type CellConfig struct { - APN string // LTE APN - ProbeAddr string - DisableProbe bool - // Enable to get location info from the GNSS receiver of the LTE modem. +// CellNetPortConfig - configuration for cellular network port (part of DPC). +type CellNetPortConfig struct { + // Parameters to apply for connecting to cellular networks. + // Configured separately for every SIM card inserted into the modem. + AccessPoints []CellularAccessPoint + // Probe used to detect broken connection. + Probe WwanProbe + // Enable to get location info from the GNSS receiver of the cellular modem. LocationTracking bool } +// CellularAccessPoint contains config parameters for connecting to a cellular network. +type CellularAccessPoint struct { + // SIM card slot to which this configuration applies. + // 0 - unspecified (apply to currently activated or the only available) + // 1 - config for SIM card in the first slot + // 2 - config for SIM card in the second slot + // etc. + SIMSlot uint8 + // If true, then this configuration is currently activated. + Activated bool + // Access Point Network + APN string + // Authentication protocol used by the network. + AuthProtocol WwanAuthProtocol + // CipherBlockStatus with encrypted credentials. + CipherBlockStatus + // The set of cellular network operators that modem should preferably try to register + // and connect into. + // Network operator should be referenced by PLMN (Public Land Mobile Network) code. + PreferredPLMNs []string + // The list of preferred Radio Access Technologies (RATs) to use for connecting + // to the network. + PreferredRATs []WwanRAT + // If true, then modem will avoid connecting to networks with roaming. + ForbidRoaming bool + + // Just for demo. + CleartextUsername string + CleartextPassword string +} + // WirelessConfig - wireless structure type WirelessConfig struct { - WType WirelessType // Wireless Type - Cellular []CellConfig // LTE APN - Wifi []WifiConfig // Wifi Config params + WType WirelessType // Wireless Type + Cellular CellNetPortConfig // Cellular connectivity config params + Wifi []WifiConfig // Wifi Config params } // WirelessStatus : state information for a single wireless device @@ -1136,6 +1172,8 @@ type BondArpMonitor struct { // Note that if fields are added the MostlyEqual function needs to be updated. type NetworkPortConfig struct { IfName string + USBAddr string + PCIAddr string Phylabel string // Physical name set by controller/model Logicallabel string // SystemAdapter's name which is logical label in phyio Alias string // From SystemAdapter's alias @@ -1169,8 +1207,9 @@ type NetworkPortStatus struct { NtpServers []net.IP // This comes from DHCP done on uplink port AddrInfoList []AddrInfo Up bool - MacAddr string + MacAddr net.HardwareAddr DefaultRouters []net.IP + MTU uint16 WirelessCfg WirelessConfig WirelessStatus WirelessStatus ProxyConfig @@ -1369,7 +1408,7 @@ func (status DeviceNetworkStatus) MostlyEqual(status2 DeviceNetworkStatus) bool } } if p1.Up != p2.Up || - p1.MacAddr != p2.MacAddr { + !bytes.Equal(p1.MacAddr, p2.MacAddr) { return false } if len(p1.DefaultRouters) != len(p2.DefaultRouters) { @@ -2982,53 +3021,6 @@ func (data AppInstMetaData) Key() string { return data.AppInstUUID.String() + "-" + string(data.Type) } -// AddToIP : -func AddToIP(ip net.IP, addition int) net.IP { - if addr := ip.To4(); addr != nil { - val := uint32(addr[0])<<24 + uint32(addr[1])<<16 + - uint32(addr[2])<<8 + uint32(addr[3]) - val += uint32(addition) - byte0 := byte((val >> 24) & 0xFF) - byte1 := byte((val >> 16) & 0xFF) - byte2 := byte((val >> 8) & 0xFF) - byte3 := byte(val & 0xFF) - return net.IPv4(byte0, byte1, byte2, byte3) - } - //TBD:XXX, IPv6 handling - return net.IP{} -} - -// GetIPAddrCountOnSubnet IP address count on subnet -func GetIPAddrCountOnSubnet(subnet net.IPNet) int { - prefixLen, _ := subnet.Mask.Size() - if prefixLen != 0 { - if subnet.IP.To4() != nil { - return 0x01 << (32 - prefixLen) - } - if subnet.IP.To16() != nil { - return 0x01 << (128 - prefixLen) - } - } - return 0 -} - -// GetIPNetwork : -// returns the first IP Address of the subnet(Network Address) -func GetIPNetwork(subnet net.IPNet) net.IP { - return subnet.IP.Mask(subnet.Mask) -} - -// GetIPBroadcast : -// returns the last IP Address of the subnet(Broadcast Address) -func GetIPBroadcast(subnet net.IPNet) net.IP { - if network := GetIPNetwork(subnet); network != nil { - if addrCount := GetIPAddrCountOnSubnet(subnet); addrCount != 0 { - return AddToIP(network, addrCount-1) - } - } - return net.IP{} -} - // At the MinSubnetSize there is room for one app instance (.0 being reserved, // .3 broadcast, .1 is the bridgeIPAddr, and .2 is usable). const ( @@ -3038,40 +3030,57 @@ const ( // WwanConfig is published by nim and consumed by the wwan service. type WwanConfig struct { - RadioSilence bool `json:"radio-silence"` - Networks []WwanNetworkConfig `json:"networks"` + RadioSilence bool `json:"radio-silence"` + // Enable verbose logging in the wwan microservice. + Verbose bool `json:"verbose"` + Networks []WwanNetworkConfig `json:"networks"` } // Equal compares two instances of WwanConfig for equality. func (wc WwanConfig) Equal(wc2 WwanConfig) bool { - if wc.RadioSilence != wc2.RadioSilence { - return false - } - if len(wc.Networks) != len(wc2.Networks) { + if wc.RadioSilence != wc2.RadioSilence || + wc.Verbose != wc2.Verbose { return false } - for _, m1 := range wc.Networks { - var found bool - for _, m2 := range wc2.Networks { - if m1.Equal(m2) { - found = true - break - } - } - if !found { - return false - } - } - return true + return generics.EqualSetsFn(wc.Networks, wc2.Networks, + func(wnc1, wnc2 WwanNetworkConfig) bool { + return wnc1.Equal(wnc2) + }) } // WwanNetworkConfig contains configuration for a single cellular network. +// In case there are multiple SIM cards/slots in the modem, WwanNetworkConfig +// contains config only for the activated one. +// TODO: Add username + password (will be done in the next PR) type WwanNetworkConfig struct { // Logical label in PhysicalIO. LogicalLabel string `json:"logical-label"` PhysAddrs WwanPhysAddrs `json:"physical-addrs"` - // XXX Multiple APNs are not yet supported. - Apns []string `json:"apns"` + // Index of the SIM slot to activate and use for the connection. + // Note that slots are indexed incrementally, starting with 1. + // Zero value means that the slot is undefined and EVE will not change + // SIM slot activation settings, meaning that the currently activated + // slot will remain being used. + SIMSlot uint8 `json:"sim-slot"` + // Access Point Network to connect into. + // By default, it is "internet". + APN string `json:"apn"` + // The set of cellular network operators that modem should preferably try to register + // and connect into. + // Network operator should be referenced by PLMN (Public Land Mobile Network) code, + // consisting of 3-digits MCC (Mobile Country Code) and 2 or 3-digits MNC + // (Mobile Network Code), separated by a dash, e.g. "310-260". + // If empty, then modem will select the network automatically based on the SIM + // card config. + PreferredPLMNs []string `json:"preferred-plmns,omitempty"` + // The list of preferred Radio Access Technologies (RATs) to use for connecting + // to the network. + // Order matters, first is the most preferred, second is tried next, etc. + // Not listed technologies will not be tried. + // If empty, then modem will select RAT automatically. + PreferredRATs []WwanRAT `json:"preferred-rats,omitempty"` + // Enable or disable data roaming. + ForbidRoaming bool `json:"forbid-roaming"` // Proxies configured for the cellular network. Proxies []ProxyEntry `json:"proxies"` // Probe used to detect broken connection. @@ -3085,6 +3094,36 @@ type WwanNetworkConfig struct { LocationTracking bool `json:"location-tracking"` } +// WwanAuthProtocol : authentication protocol used by cellular network. +type WwanAuthProtocol string + +const ( + // WwanAuthProtocolNone : no authentication. + WwanAuthProtocolNone WwanAuthProtocol = "" + // WwanAuthProtocolPAP : Password Authentication Protocol. + WwanAuthProtocolPAP WwanAuthProtocol = "pap" + // WwanAuthProtocolCHAP : Challenge-Handshake Authentication Protocol. + WwanAuthProtocolCHAP WwanAuthProtocol = "chap" + // WwanAuthProtocolPAPAndCHAP : Both PAP and CHAP. + WwanAuthProtocolPAPAndCHAP WwanAuthProtocol = "pap-and-chap" +) + +// WwanRAT : Radio Access Technology. +type WwanRAT string + +const ( + // WwanRATUnspecified : select RAT automatically. + WwanRATUnspecified WwanRAT = "" + // WwanRATGSM : Global System for Mobile Communications (2G). + WwanRATGSM WwanRAT = "gsm" + // WwanRATUMTS : Universal Mobile Telecommunications System (3G). + WwanRATUMTS WwanRAT = "umts" + // WwanRATLTE : Long-Term Evolution (4G). + WwanRATLTE WwanRAT = "lte" + // WwanRAT5GNR : 5th Generation New Radio (5G). + WwanRAT5GNR WwanRAT = "5gnr" +) + // WwanProbe : cellular connectivity verification probe. type WwanProbe struct { Disable bool `json:"disable"` @@ -3095,40 +3134,26 @@ type WwanProbe struct { // Equal compares two instances of WwanNetworkConfig for equality. func (wnc WwanNetworkConfig) Equal(wnc2 WwanNetworkConfig) bool { if wnc.LogicalLabel != wnc2.LogicalLabel || - wnc.PhysAddrs.PCI != wnc2.PhysAddrs.PCI || - wnc.PhysAddrs.USB != wnc2.PhysAddrs.USB || - wnc.PhysAddrs.Interface != wnc2.PhysAddrs.Interface { + wnc.PhysAddrs != wnc2.PhysAddrs { return false } - if wnc.Probe.Address != wnc2.Probe.Address || - wnc.Probe.Disable != wnc2.Probe.Disable { + if wnc.SIMSlot != wnc2.SIMSlot || + wnc.APN != wnc2.APN { return false } - if len(wnc.Proxies) != len(wnc2.Proxies) { + if !generics.EqualLists(wnc.PreferredPLMNs, wnc2.PreferredPLMNs) || + !generics.EqualLists(wnc.PreferredRATs, wnc2.PreferredRATs) || + wnc.ForbidRoaming != wnc2.ForbidRoaming { return false } - for i := range wnc.Proxies { - if wnc.Proxies[i] != wnc2.Proxies[i] { - return false - } - } - if wnc.LocationTracking != wnc2.LocationTracking { + if !generics.EqualLists(wnc.Proxies, wnc2.Proxies) { return false } - if len(wnc.Apns) != len(wnc2.Apns) { + if wnc.Probe != wnc2.Probe { return false } - for _, apn1 := range wnc.Apns { - var found bool - for _, apn2 := range wnc2.Apns { - if apn1 == apn2 { - found = true - break - } - } - if !found { - return false - } + if wnc.LocationTracking != wnc2.LocationTracking { + return false } return true } @@ -3146,15 +3171,28 @@ type WwanPhysAddrs struct { // PCI address in the long format. // For example: 0000:00:15.0 PCI string `json:"pci"` + // Dev : device file representing the modem (e.g. /dev/cdc-wdm0). + Dev string `json:"dev"` } // WwanStatus is published by the wwan service and consumed by nim. type WwanStatus struct { Networks []WwanNetworkStatus `json:"networks"` - // MD5 checksum of the corresponding WwanConfig (as config.json). + // SHA256 hash of the corresponding WwanConfig (as config.json). ConfigChecksum string `json:"config-checksum,omitempty"` } +// Equal compares two instances of WwanStatus for equality. +func (ws WwanStatus) Equal(ws2 WwanStatus) bool { + if ws.ConfigChecksum != ws2.ConfigChecksum { + return false + } + return generics.EqualSetsFn(ws.Networks, ws2.Networks, + func(wns1, wns2 WwanNetworkStatus) bool { + return wns1.Equal(wns2) + }) +} + // LookupNetworkStatus returns status corresponding to the given cellular network. func (ws WwanStatus) LookupNetworkStatus(logicalLabel string) (WwanNetworkStatus, bool) { for _, status := range ws.Networks { @@ -3193,14 +3231,16 @@ func (ws WwanStatus) DoSanitize() { if simCard.ICCID != "" { simCard.Name = simCard.ICCID } else { - simCard.Name = fmt.Sprintf("%s - SIM%d", network.Module.Name, j) + simCard.Name = fmt.Sprintf("%s-SIM%d", + network.Module.Name, simCard.SlotNumber) } } } } } -// WwanNetworkStatus contains status information for a single cellular network. +// WwanNetworkStatus contains status information for a single cellular network +// (i.e. one modem but possibly multiple SIM slots/cards). type WwanNetworkStatus struct { // Logical label of the cellular modem in PhysicalIO. // Can be empty if this device is not configured by the controller @@ -3208,36 +3248,77 @@ type WwanNetworkStatus struct { LogicalLabel string `json:"logical-label"` PhysAddrs WwanPhysAddrs `json:"physical-addrs"` Module WwanCellModule `json:"cellular-module"` - SimCards []WwanSimCard `json:"sim-cards"` - ConfigError string `json:"config-error"` - ProbeError string `json:"probe-error"` - Providers []WwanProvider `json:"providers"` + // One entry for every SIM slot (incl. those without SIM card). + SimCards []WwanSimCard `json:"sim-cards"` + // Non-empty if the wwan microservice failed to apply config submitted by NIM. + ConfigError string `json:"config-error"` + // Error message from the last connectivity probing. + ProbeError string `json:"probe-error"` + // Network where the modem is currently registered. + CurrentProvider WwanProvider `json:"current-provider"` + // All networks that the modem is able to detect. + // This will include the currently used provider as well as other visible networks. + VisibleProviders []WwanProvider `json:"visible-providers"` + // The list of Radio Access Technologies (RATs) currently used for registering/connecting + // to the network (typically just one). + CurrentRATs []WwanRAT `json:"current-rats"` + // Unix timestamp in seconds made when the current connection was established. + // Zero value if the modem is not connected. + ConnectedAt uint64 `json:"connected-at"` } // WwanCellModule contains cellular module specs. type WwanCellModule struct { - Name string `json:"name,omitempty"` - IMEI string `json:"imei"` - Model string `json:"model"` - Revision string `json:"revision"` + // Name is a module identifier. For example IMEI if available. + // Guaranteed to be unique among all modems attached to the edge node. + Name string `json:"name,omitempty"` + // International Mobile Equipment Identity. + IMEI string `json:"imei"` + Model string `json:"model"` + Manufacturer string `json:"manufacturer"` + // Firmware version identifier. + Revision string `json:"revision"` + // QMI or MBIM. ControlProtocol WwanCtrlProt `json:"control-protocol"` OpMode WwanOpMode `json:"operating-mode"` } -// WwanSimCard contains SIM card information. +// WwanSimCard describes either empty SIM slot or a slot with a SIM card inserted. type WwanSimCard struct { - Name string `json:"name,omitempty"` - ICCID string `json:"iccid"` - IMSI string `json:"imsi"` - Status string `json:"status"` + // Name is a SIM card/slot identifier. + // Guaranteed to be unique across all modems and their SIM slots attached + // to the edge node. + Name string `json:"name,omitempty"` + // SIM slot number which this WwanSimCard instance describes. + SlotNumber uint8 `json:"slot-number"` + // True if this SIM slot is activated, i.e. the inserted SIM card (if any) can be used + // to connect to a cellular network. + SlotActivated bool `json:"slot-activated"` + // Integrated Circuit Card Identifier. + // Empty if no SIM card is inserted into the slot or if the SIM card is not recognized. + ICCID string `json:"iccid,omitempty"` + // International Mobile Subscriber Identity. + // Empty if no SIM card is inserted into the slot or if the SIM card is not recognized. + IMSI string `json:"imsi,omitempty"` + // The current state of the SIM card (absent, initialized, not recognized, etc.). + // This state is not modeled using enum because the set of possible values differs + // between QMI and MBIM protocols (used to control cellular modules) and there is + // no 1:1 mapping between them. + State string `json:"state"` } // WwanProvider contains information about a cellular connectivity provider. type WwanProvider struct { - PLMN string `json:"plmn"` - Description string `json:"description"` - CurrentServing bool `json:"current-serving"` - Roaming bool `json:"roaming"` + // Public Land Mobile Network identifier. + PLMN string `json:"plmn"` + // Human-readable label identifying the provider. + Description string `json:"description"` + // True if this is the provider currently being used. + CurrentServing bool `json:"current-serving"` + // True if data romaing is ON. + Roaming bool `json:"roaming"` + // True if this provider is forbidden by SIM card config. + Forbidden bool `json:"forbidden"` } // WwanOpMode : wwan operating mode @@ -3270,11 +3351,45 @@ const ( WwanCtrlProtMBIM WwanCtrlProt = "mbim" ) +// Equal compares two instances of WwanNetworkStatus for equality. +func (wns WwanNetworkStatus) Equal(wns2 WwanNetworkStatus) bool { + if wns.LogicalLabel != wns2.LogicalLabel || + wns.PhysAddrs != wns2.PhysAddrs { + return false + } + if wns.Module != wns2.Module { + return false + } + if !generics.EqualSets(wns.SimCards, wns2.SimCards) { + return false + } + if wns.ConfigError != wns2.ConfigError || + wns.ProbeError != wns2.ProbeError { + return false + } + if wns.CurrentProvider != wns2.CurrentProvider || + !generics.EqualSets(wns.VisibleProviders, wns2.VisibleProviders) { + return false + } + if !generics.EqualSets(wns.CurrentRATs, wns2.CurrentRATs) { + return false + } + if wns.ConnectedAt != wns2.ConnectedAt { + return false + } + return true +} + // WwanMetrics is published by the wwan service. type WwanMetrics struct { Networks []WwanNetworkMetrics `json:"networks"` } +// Equal compares two instances of WwanMetrics for equality. +func (wm WwanMetrics) Equal(wm2 WwanMetrics) bool { + return generics.EqualSets(wm.Networks, wm2.Networks) +} + // LookupNetworkMetrics returns metrics corresponding to the given cellular network. func (wm WwanMetrics) LookupNetworkMetrics(logicalLabel string) (WwanNetworkMetrics, bool) { for _, metrics := range wm.Networks { diff --git a/pkg/pillar/utils/generics.go b/pkg/pillar/utils/generics/generics.go similarity index 94% rename from pkg/pillar/utils/generics.go rename to pkg/pillar/utils/generics/generics.go index 45db569dfd8..b9e0cf65b38 100644 --- a/pkg/pillar/utils/generics.go +++ b/pkg/pillar/utils/generics/generics.go @@ -1,7 +1,7 @@ // Copyright (c) 2023 Zededa, Inc. // SPDX-License-Identifier: Apache-2.0 -package utils +package generics // EqualLists returns true if the two slices representing lists (i.e. order dependent) // are equal in size and items they contain. @@ -161,3 +161,12 @@ func ContainsItemFn[Type any](list []Type, item Type, equal func(a, b Type) bool } return false } + +// RotateList rotates the list items by the given amount. +func RotateList[Type any](arr []Type, amount int) []Type { + if len(arr) == 0 { + return []Type{} + } + amount = amount % len(arr) + return append(append([]Type{}, arr[amount:]...), arr[:amount]...) +} diff --git a/pkg/pillar/utils/ip.go b/pkg/pillar/utils/ip.go deleted file mode 100644 index 43c735afec4..00000000000 --- a/pkg/pillar/utils/ip.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2023 Zededa, Inc. -// SPDX-License-Identifier: Apache-2.0 - -package utils - -import ( - "bytes" - "net" -) - -// EqualIPs compares two IP addresses. -func EqualIPs(ip1 net.IP, ip2 net.IP) bool { - if ip1 == nil { - return ip2 == nil - } - if ip2 == nil { - return ip1 == nil - } - return ip1.Equal(ip2) -} - -// EqualIPNets compares two IP addresses with masks. -func EqualIPNets(ipNet1, ipNet2 *net.IPNet) bool { - if ipNet1 == nil || ipNet2 == nil { - return ipNet1 == ipNet2 - } - return ipNet1.IP.Equal(ipNet2.IP) && - bytes.Equal(ipNet1.Mask, ipNet2.Mask) -} diff --git a/pkg/pillar/utils/netutils/ip.go b/pkg/pillar/utils/netutils/ip.go new file mode 100644 index 00000000000..06ca0115ee3 --- /dev/null +++ b/pkg/pillar/utils/netutils/ip.go @@ -0,0 +1,74 @@ +// Copyright (c) 2023 Zededa, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package netutils + +import ( + "bytes" + "net" +) + +// EqualIPs compares two IP addresses. +func EqualIPs(ip1 net.IP, ip2 net.IP) bool { + if ip1 == nil { + return ip2 == nil + } + if ip2 == nil { + return ip1 == nil + } + return ip1.Equal(ip2) +} + +// EqualIPNets compares two IP addresses with masks. +func EqualIPNets(ipNet1, ipNet2 *net.IPNet) bool { + if ipNet1 == nil || ipNet2 == nil { + return ipNet1 == ipNet2 + } + return ipNet1.IP.Equal(ipNet2.IP) && + bytes.Equal(ipNet1.Mask, ipNet2.Mask) +} + +// AddToIP increments IP address by the given integer number. +func AddToIP(ip net.IP, addition int) net.IP { + if addr := ip.To4(); addr != nil { + val := uint32(addr[0])<<24 + uint32(addr[1])<<16 + + uint32(addr[2])<<8 + uint32(addr[3]) + val += uint32(addition) + byte0 := byte((val >> 24) & 0xFF) + byte1 := byte((val >> 16) & 0xFF) + byte2 := byte((val >> 8) & 0xFF) + byte3 := byte(val & 0xFF) + return net.IPv4(byte0, byte1, byte2, byte3) + } + //TBD:XXX, IPv6 handling + return net.IP{} +} + +// GetIPAddrCountOnSubnet return the number or available IP addresses inside a subnet. +func GetIPAddrCountOnSubnet(subnet net.IPNet) int { + prefixLen, _ := subnet.Mask.Size() + if prefixLen != 0 { + if subnet.IP.To4() != nil { + return 0x01 << (32 - prefixLen) + } + if subnet.IP.To16() != nil { + return 0x01 << (128 - prefixLen) + } + } + return 0 +} + +// GetIPNetwork returns the first IP Address of the subnet(Network Address) +func GetIPNetwork(subnet net.IPNet) net.IP { + return subnet.IP.Mask(subnet.Mask) +} + +// GetIPBroadcast returns the last IP Address of the subnet(Broadcast Address) +func GetIPBroadcast(subnet net.IPNet) net.IP { + if network := GetIPNetwork(subnet); network != nil { + if addrCount := GetIPAddrCountOnSubnet(subnet); addrCount != 0 { + return AddToIP(network, addrCount-1) + } + } + return net.IP{} +} diff --git a/pkg/wwan/usr/bin/wwan-init.sh b/pkg/wwan/usr/bin/wwan-init.sh index 45372f29316..47d062f966e 100755 --- a/pkg/wwan/usr/bin/wwan-init.sh +++ b/pkg/wwan/usr/bin/wwan-init.sh @@ -356,13 +356,13 @@ collect_network_status() { "$(json_str_attr control-protocol "$PROTOCOL")" \ "$(json_str_attr operating-mode "$("${PROTOCOL}_get_op_mode")")")" local NETWORK_STATUS="$(json_struct \ - "$(json_str_attr logical-label "$LOGICAL_LABEL")" \ - "$(json_attr physical-addrs "$ADDRS")" \ - "$(json_attr cellular-module "$MODULE")" \ - "$(json_attr sim-cards "$("${PROTOCOL}_get_sim_cards")")" \ - "$(json_str_attr config-error "$CONFIG_ERROR")" \ - "$(json_str_attr probe-error "$PROBE_ERROR")" \ - "$(json_attr providers "$PROVIDERS")")" + "$(json_str_attr logical-label "$LOGICAL_LABEL")" \ + "$(json_attr physical-addrs "$ADDRS")" \ + "$(json_attr cellular-module "$MODULE")" \ + "$(json_attr sim-cards "$("${PROTOCOL}_get_sim_cards")")" \ + "$(json_str_attr config-error "$CONFIG_ERROR")" \ + "$(json_str_attr probe-error "$PROBE_ERROR")" \ + "$(json_attr visible-providers "$PROVIDERS")")" STATUS="${STATUS}${NETWORK_STATUS}\n" } @@ -480,7 +480,7 @@ event_stream | while read -r EVENT; do PROBE_DISABLED="$(parse_json_attr "$PROBE" "disable")" PROBE_ADDR="$(parse_json_attr "$PROBE" "address")" PROXIES="$(parse_json_attr "$NETWORK" "proxies")" - APN="$(parse_json_attr "$NETWORK" "apns[0]")" # FIXME XXX limited to a single APN for now + APN="$(parse_json_attr "$NETWORK" "apn")" APN="${APN:-$DEFAULT_APN}" LOC_TRACKING="$(parse_json_attr "$NETWORK" "\"location-tracking\"")"