From 712ec5dccb881d4351c901e2643fa852e11f1235 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 support for device models where cellular modems are referenced by USB or PCI address, not by interface name. Multiple modems can be initialized in different order on each boot, meaning that interface naming is not stable and therefore not suitable for modem identification. * 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) * moved wwan types from zedroutertypes.go to a separate file wwan.go (these types are not used by zedrouter) Signed-off-by: Milan Lenco --- pkg/pillar/cmd/zedagent/handlemetrics.go | 69 +-- pkg/pillar/cmd/zedagent/parseconfig.go | 171 ++++-- pkg/pillar/cmd/zedagent/radiosilence.go | 2 +- pkg/pillar/cmd/zedagent/reportinfo.go | 278 +++++++-- 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 | 57 +- 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/genericitems/wwan.go | 18 +- pkg/pillar/dpcreconciler/linux.go | 120 +++- 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 | 4 +- .../nireconciler/genericitems/uplink.go | 5 +- pkg/pillar/nireconciler/linux_acl.go | 6 +- pkg/pillar/nireconciler/linux_config.go | 9 +- 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/wwan.go | 561 ++++++++++++++++++ pkg/pillar/types/zedroutertypes.go | 524 ++-------------- pkg/pillar/utils/{ => generics}/generics.go | 2 +- pkg/pillar/utils/ip.go | 36 -- pkg/pillar/utils/netutils/ip.go | 81 +++ pkg/pillar/zedcloud/send.go | 46 +- pkg/wwan/usr/bin/wwan-init.sh | 16 +- 39 files changed, 1435 insertions(+), 864 deletions(-) create mode 100644 pkg/pillar/types/wwan.go rename pkg/pillar/utils/{ => generics}/generics.go (99%) 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..c4a3165e388 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.SelectedUplinkIntfName != "" { 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 696f31e0170..aa9bb2105c5 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" ) @@ -798,7 +800,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) @@ -927,18 +929,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 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 { @@ -1118,7 +1149,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 { @@ -1129,7 +1160,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) } @@ -1153,7 +1184,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) } @@ -1164,14 +1195,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) } @@ -1183,7 +1214,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) } @@ -1850,37 +1881,97 @@ 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, + } + 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) @@ -1994,7 +2085,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) @@ -2002,7 +2093,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()) } @@ -2020,39 +2111,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 @@ -2068,7 +2159,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..d0e3e0dbd20 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 = 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 { - ports := deviceNetworkStatus.GetPortsByLogicallabel(p.Logicallabel) - if len(ports) == 0 { + if i == dpcl.CurrentIndex { + // For the currently used DPC we publish the status (DeviceNetworkStatus). + portStatus := deviceNetworkStatus.GetPortsByLogicallabel(p.Logicallabel) + if len(portStatus) == 1 { + dps.Ports[j] = encodeNetworkPortStatus(ctx, portStatus[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,158 @@ func encodeSystemAdapterInfo(ctx *zedagentContext) *info.SystemAdapterInfo { return sainfo } +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 to DevicePort? + // 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 fe2e562f15c..6d1289fb5b3 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 @@ -734,6 +735,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) @@ -994,6 +998,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) @@ -1675,6 +1682,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..0cbd0fc3743 100644 --- a/pkg/pillar/dpcmanager/dns.go +++ b/pkg/pillar/dpcmanager/dns.go @@ -66,33 +66,57 @@ 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. + // New errors are recorded from this function only when there is none yet + // (HasError() == false). 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("interface %s is missing", port.IfName) 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 +124,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 +165,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 +197,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 984e54f6966..dba8a1adff0 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/genericitems/wwan.go b/pkg/pillar/dpcreconciler/genericitems/wwan.go index 57887ea1aef..a81dd6824fe 100644 --- a/pkg/pillar/dpcreconciler/genericitems/wwan.go +++ b/pkg/pillar/dpcreconciler/genericitems/wwan.go @@ -54,22 +54,10 @@ func (w Wwan) String() string { return fmt.Sprintf("WWAN configuration: %+v", w.Config) } -// Dependencies lists every adapter referenced from the wwan config -// as a dependency. +// Dependencies return empty list - wwan config file can be created even before +// the referenced wwanX interface(s) are ready (the wwan microservice can deal with it). func (w Wwan) Dependencies() (deps []depgraph.Dependency) { - for _, network := range w.Config.Networks { - if network.PhysAddrs.Interface == "" { - continue - } - deps = append(deps, depgraph.Dependency{ - RequiredItem: depgraph.ItemRef{ - ItemType: AdapterTypename, - ItemName: network.PhysAddrs.Interface, - }, - Description: "The referenced (LTE) adapter must exist", - }) - } - return deps + return nil } // WwanConfigurator implements Configurator interface (libs/reconciler) for WWAN config. diff --git a/pkg/pillar/dpcreconciler/linux.go b/pkg/pillar/dpcreconciler/linux.go index 5de1345f11f..8cc3728a716 100644 --- a/pkg/pillar/dpcreconciler/linux.go +++ b/pkg/pillar/dpcreconciler/linux.go @@ -646,7 +646,7 @@ 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) @@ -690,7 +690,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 +740,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 +833,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 +864,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 +884,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 +971,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 +1019,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 +1078,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 +1148,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 +1235,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 +1334,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 + } + } + var accessPoint *types.CellularAccessPoint + for _, ap := range port.WirelessCfg.Cellular.AccessPoints { + if ap.Activated { + accessPoint = &ap + break + } } - if ioBundle.IsPCIBack { - r.Log.Warnf("wwan adapter with the logical label '%s' is assigned to pciback, skipping", - port.Logicallabel) + 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 +1706,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..32fc5d3bf53 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. 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 c8b4a63bce4..c97c7811d53 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" ) @@ -807,7 +808,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, @@ -990,7 +991,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", @@ -1063,7 +1064,7 @@ func gwViaLinkRoute(route netmonitor.Route, routingTable []netmonitor.Route) boo for _, route2 := range routingTable { netlinkRoute2 := route2.Data.(netlink.Route) if netlinkRoute2.Scope == netlink.SCOPE_LINK && - utils.EqualIPNets(netlinkRoute2.Dst, gwHostSubnet) { + netutils.EqualIPNets(netlinkRoute2.Dst, gwHostSubnet) { return true } } 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/wwan.go b/pkg/pillar/types/wwan.go new file mode 100644 index 00000000000..51332090c24 --- /dev/null +++ b/pkg/pillar/types/wwan.go @@ -0,0 +1,561 @@ +// Copyright (c) 2023 Zededa, Inc. +// SPDX-License-Identifier: Apache-2.0 + +// Types defined for interaction between pillar and the wwan microservice. + +//nolint:tagliatelle +package types + +import ( + "fmt" + + "github.com/google/go-cmp/cmp" + "github.com/lf-edge/eve/pkg/pillar/base" + "github.com/lf-edge/eve/pkg/pillar/utils/generics" +) + +// WwanConfig is published by nim and consumed by the wwan service. +type WwanConfig struct { + 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 || + wc.Verbose != wc2.Verbose { + return false + } + 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"` + // 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. + Probe WwanProbe `json:"probe"` + // Some LTE modems have GNSS receiver integrated and can be used + // for device location tracking. + // Enable this option to have location info periodically obtained + // from this modem and published into /run/wwan/location.json by the wwan + // microservice. This is further distributed to the controller and + // to applications by zedagent. + 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"` + // IP/FQDN address to periodically probe to determine connection status. + Address string `json:"address"` +} + +// Equal compares two instances of WwanNetworkConfig for equality. +func (wnc WwanNetworkConfig) Equal(wnc2 WwanNetworkConfig) bool { + if wnc.LogicalLabel != wnc2.LogicalLabel || + wnc.PhysAddrs != wnc2.PhysAddrs { + return false + } + if wnc.SIMSlot != wnc2.SIMSlot || + wnc.APN != wnc2.APN { + return false + } + if !generics.EqualLists(wnc.PreferredPLMNs, wnc2.PreferredPLMNs) || + !generics.EqualLists(wnc.PreferredRATs, wnc2.PreferredRATs) || + wnc.ForbidRoaming != wnc2.ForbidRoaming { + return false + } + if !generics.EqualLists(wnc.Proxies, wnc2.Proxies) { + return false + } + if wnc.Probe != wnc2.Probe { + return false + } + if wnc.LocationTracking != wnc2.LocationTracking { + return false + } + return true +} + +// WwanPhysAddrs is a physical address of a cellular modem. +// Not all fields have to be defined. Empty WwanPhysAddrs will match the first modem found in sysfs. +// With multiple LTE modems the USB address is the most unambiguous and reliable. +type WwanPhysAddrs struct { + // Interface name. + // For example: wwan0 + Interface string `json:"interface"` + // USB address in the format ":[]", with nested ports separated by dots. + // For example: 1:2.3 + USB string `json:"usb"` + // 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). + // This address is only published as part of the wwan status + // and can't be configured from the controller. + Dev string `json:"dev,omitempty"` +} + +// WwanStatus is published by the wwan service and consumed by nim. +type WwanStatus struct { + Networks []WwanNetworkStatus `json:"networks"` + // 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 { + if logicalLabel == status.LogicalLabel { + return status, true + } + } + return WwanNetworkStatus{}, false +} + +// DoSanitize fills in logical names for cellular modules and SIM cards. +func (ws WwanStatus) DoSanitize() { + uniqueModel := func(model string) bool { + var counter int + for i := range ws.Networks { + if ws.Networks[i].Module.Model == model { + counter++ + } + } + return counter == 1 + } + for i := range ws.Networks { + network := &ws.Networks[i] + if network.Module.Name == "" { + if network.Module.IMEI != "" { + network.Module.Name = network.Module.IMEI + } else if uniqueModel(network.Module.Model) { + network.Module.Name = network.Module.Model + } else { + network.Module.Name = network.PhysAddrs.USB + } + } + for j := range network.SimCards { + simCard := &network.SimCards[j] + if simCard.Name == "" { + if simCard.ICCID != "" { + simCard.Name = simCard.ICCID + } else { + simCard.Name = fmt.Sprintf("%s-SIM%d", + network.Module.Name, simCard.SlotNumber) + } + } + } + } +} + +// 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 + // (and hence logical label does not exist). + LogicalLabel string `json:"logical-label"` + PhysAddrs WwanPhysAddrs `json:"physical-addrs"` + Module WwanCellModule `json:"cellular-module"` + // 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 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 describes either empty SIM slot or a slot with a SIM card inserted. +type WwanSimCard struct { + // 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 { + // 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 roaming is ON. + Roaming bool `json:"roaming"` + // True if this provider is forbidden by SIM card config. + Forbidden bool `json:"forbidden"` +} + +// WwanOpMode : wwan operating mode +type WwanOpMode string + +const ( + // WwanOpModeUnspecified : operating mode is not specified + WwanOpModeUnspecified WwanOpMode = "" + // WwanOpModeOnline : modem is online but not connected + WwanOpModeOnline WwanOpMode = "online" + // WwanOpModeConnected : modem is online and connected + WwanOpModeConnected WwanOpMode = "online-and-connected" + // WwanOpModeRadioOff : modem has disabled radio transmission + WwanOpModeRadioOff WwanOpMode = "radio-off" + // WwanOpModeOffline : modem is offline + WwanOpModeOffline WwanOpMode = "offline" + // WwanOpModeUnrecognized : unrecongized operating mode + WwanOpModeUnrecognized WwanOpMode = "unrecognized" +) + +// WwanCtrlProt : wwan control protocol +type WwanCtrlProt string + +const ( + // WwanCtrlProtUnspecified : control protocol is not specified + WwanCtrlProtUnspecified WwanCtrlProt = "" + // WwanCtrlProtQMI : modem is controlled using the QMI protocol + WwanCtrlProtQMI WwanCtrlProt = "qmi" + // WwanCtrlProtMBIM : modem is controlled using the MBIM protocol + 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 { + if logicalLabel == metrics.LogicalLabel { + return metrics, true + } + } + return WwanNetworkMetrics{}, false +} + +// Key is used for pubsub +func (wm WwanMetrics) Key() string { + return "global" +} + +// LogCreate : +func (wm WwanMetrics) LogCreate(logBase *base.LogObject) { + logObject := base.NewLogObject(logBase, base.WwanMetricsLogType, "", + nilUUID, wm.LogKey()) + if logObject == nil { + return + } + logObject.Metricf("Wwan metrics create") +} + +// LogModify : +func (wm WwanMetrics) LogModify(logBase *base.LogObject, old interface{}) { + logObject := base.EnsureLogObject(logBase, base.WwanMetricsLogType, "", + nilUUID, wm.LogKey()) + + oldWm, ok := old.(WwanMetrics) + if !ok { + logObject.Clone().Fatalf("LogModify: Old object passed is not of WwanMetrics type") + } + // XXX remove? + logObject.CloneAndAddField("diff", cmp.Diff(oldWm, wm)). + Metricf("Wwan metrics modify") +} + +// LogDelete : +func (wm WwanMetrics) LogDelete(logBase *base.LogObject) { + logObject := base.EnsureLogObject(logBase, base.WwanMetricsLogType, "", + nilUUID, wm.LogKey()) + logObject.Metricf("Wwan metrics delete") + + base.DeleteLogObject(logBase, wm.LogKey()) +} + +// LogKey : +func (wm WwanMetrics) LogKey() string { + return string(base.WwanMetricsLogType) + "-" + wm.Key() +} + +// WwanNetworkMetrics contains metrics for a single cellular network. +type WwanNetworkMetrics struct { + // Logical label of the cellular modem in PhysicalIO. + // Can be empty if this device is not configured by the controller + // (and hence logical label does not exist). + LogicalLabel string `json:"logical-label"` + PhysAddrs WwanPhysAddrs `json:"physical-addrs"` + PacketStats WwanPacketStats `json:"packet-stats"` + SignalInfo WwanSignalInfo `json:"signal-info"` +} + +// WwanPacketStats contains packet statistics recorded by a cellular modem. +type WwanPacketStats struct { + RxBytes uint64 `json:"rx-bytes"` + RxPackets uint64 `json:"rx-packets"` + RxDrops uint64 `json:"rx-drops"` + TxBytes uint64 `json:"tx-bytes"` + TxPackets uint64 `json:"tx-packets"` + TxDrops uint64 `json:"tx-drops"` +} + +// WwanSignalInfo contains cellular signal strength information. +// The maximum value of int32 (0x7FFFFFFF) represents unspecified/unavailable metric. +type WwanSignalInfo struct { + // Received signal strength indicator (RSSI) measured in dBm (decibel-milliwatts). + RSSI int32 `json:"rssi"` + // Reference Signal Received Quality (RSRQ) measured in dB (decibels). + RSRQ int32 `json:"rsrq"` + // Reference Signal Receive Power (RSRP) measured in dBm (decibel-milliwatts). + RSRP int32 `json:"rsrp"` + // Signal-to-Noise Ratio (SNR) measured in dB (decibels). + SNR int32 `json:"snr"` +} + +// WwanLocationInfo contains device location information obtained from a GNSS +// receiver integrated into an LTE modem. +type WwanLocationInfo struct { + // Logical label of the device used to obtain this location information. + LogicalLabel string `json:"logical-label"` + // Latitude in the Decimal degrees (DD) notation. + // Valid values are in the range <-90, 90>. Anything outside of this range + // should be treated as an unavailable value. + // Note that wwan microservice uses -32768 specifically when latitude is not known. + Latitude float64 `json:"latitude"` + // Longitude in the Decimal degrees (DD) notation. + // Valid values are in the range <-180, 180>. Anything outside of this range + // should be treated as an unavailable value. + // Note that wwan microservice uses -32768 specifically when longitude is not known. + Longitude float64 `json:"longitude"` + // Altitude w.r.t. mean sea level in meters. + // Negative value of -32768 is returned when altitude is not known. + Altitude float64 `json:"altitude"` + // Circular horizontal position uncertainty in meters. + // Negative values are not valid and represent unavailable uncertainty. + // Note that wwan microservice uses -32768 specifically when horizontal + // uncertainty is not known. + HorizontalUncertainty float32 `json:"horizontal-uncertainty"` + // Reliability of the provided information for latitude and longitude. + HorizontalReliability LocReliability `json:"horizontal-reliability"` + // Vertical position uncertainty in meters. + // Negative values are not valid and represent unavailable uncertainty. + // Note that wwan microservice uses -32768 specifically when vertical + // uncertainty is not known. + VerticalUncertainty float32 `json:"vertical-uncertainty"` + // Reliability of the provided information for altitude. + VerticalReliability LocReliability `json:"vertical-reliability"` + // Unix timestamp in milliseconds. + // Zero value represents unavailable UTC timestamp. + UTCTimestamp uint64 `json:"utc-timestamp"` +} + +// Key is used for pubsub +func (wli WwanLocationInfo) Key() string { + return "global" +} + +// LogCreate : +func (wli WwanLocationInfo) LogCreate(logBase *base.LogObject) { + logObject := base.NewLogObject(logBase, base.WwanLocationInfoLogType, "", + nilUUID, wli.LogKey()) + if logObject == nil { + return + } + logObject.Metricf("Wwan location info create") +} + +// LogModify : +func (wli WwanLocationInfo) LogModify(logBase *base.LogObject, old interface{}) { + logObject := base.EnsureLogObject(logBase, base.WwanLocationInfoLogType, "", + nilUUID, wli.LogKey()) + + oldWli, ok := old.(WwanLocationInfo) + if !ok { + logObject.Clone().Fatalf("LogModify: Old object passed is not of WwanLocationInfo type") + } + // XXX remove? + logObject.CloneAndAddField("diff", cmp.Diff(oldWli, wli)). + Metricf("Wwan location info modify") +} + +// LogDelete : +func (wli WwanLocationInfo) LogDelete(logBase *base.LogObject) { + logObject := base.EnsureLogObject(logBase, base.WwanLocationInfoLogType, "", + nilUUID, wli.LogKey()) + logObject.Metricf("Wwan location info delete") + base.DeleteLogObject(logBase, wli.LogKey()) +} + +// LogKey : +func (wli WwanLocationInfo) LogKey() string { + return string(base.WwanLocationInfoLogType) + "-" + wli.Key() +} + +// LocReliability : reliability of location information. +type LocReliability string + +const ( + // LocReliabilityUnspecified : reliability is not specified + LocReliabilityUnspecified LocReliability = "not-set" + // LocReliabilityVeryLow : very low reliability + LocReliabilityVeryLow LocReliability = "very-low" + // LocReliabilityLow : low reliability + LocReliabilityLow LocReliability = "low" + // LocReliabilityMedium : medium reliability + LocReliabilityMedium LocReliability = "medium" + // LocReliabilityHigh : high reliability + LocReliabilityHigh LocReliability = "high" +) diff --git a/pkg/pillar/types/zedroutertypes.go b/pkg/pillar/types/zedroutertypes.go index 8fb40a8f3d5..e9c6d8492d6 100644 --- a/pkg/pillar/types/zedroutertypes.go +++ b/pkg/pillar/types/zedroutertypes.go @@ -849,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 || @@ -1003,20 +1005,49 @@ 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 +} + // 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 @@ -1137,6 +1168,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 @@ -1170,8 +1203,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 @@ -1370,7 +1404,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) { @@ -2983,53 +3017,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 ( @@ -3037,427 +3024,6 @@ const ( LargeSubnetSize = 16 // for determining default Dhcp Range ) -// WwanConfig is published by nim and consumed by the wwan service. -type WwanConfig struct { - RadioSilence bool `json:"radio-silence"` - 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) { - 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 -} - -// WwanNetworkConfig contains configuration for a single cellular network. -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"` - // Proxies configured for the cellular network. - Proxies []ProxyEntry `json:"proxies"` - // Probe used to detect broken connection. - Probe WwanProbe `json:"probe"` - // Some LTE modems have GNSS receiver integrated and can be used - // for device location tracking. - // Enable this option to have location info periodically obtained - // from this modem and published into /run/wwan/location.json by the wwan - // microservice. This is further distributed to the controller and - // to applications by zedagent. - LocationTracking bool `json:"location-tracking"` -} - -// WwanProbe : cellular connectivity verification probe. -type WwanProbe struct { - Disable bool `json:"disable"` - // IP/FQDN address to periodically probe to determine connection status. - Address string `json:"address"` -} - -// 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 { - return false - } - if wnc.Probe.Address != wnc2.Probe.Address || - wnc.Probe.Disable != wnc2.Probe.Disable { - return false - } - if len(wnc.Proxies) != len(wnc2.Proxies) { - return false - } - for i := range wnc.Proxies { - if wnc.Proxies[i] != wnc2.Proxies[i] { - return false - } - } - if wnc.LocationTracking != wnc2.LocationTracking { - return false - } - if len(wnc.Apns) != len(wnc2.Apns) { - 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 - } - } - return true -} - -// WwanPhysAddrs is a physical address of a cellular modem. -// Not all fields have to be defined. Empty WwanPhysAddrs will match the first modem found in sysfs. -// With multiple LTE modems the USB address is the most unambiguous and reliable. -type WwanPhysAddrs struct { - // Interface name. - // For example: wwan0 - Interface string `json:"interface"` - // USB address in the format ":[]", with nested ports separated by dots. - // For example: 1:2.3 - USB string `json:"usb"` - // PCI address in the long format. - // For example: 0000:00:15.0 - PCI string `json:"pci"` -} - -// 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). - ConfigChecksum string `json:"config-checksum,omitempty"` -} - -// LookupNetworkStatus returns status corresponding to the given cellular network. -func (ws WwanStatus) LookupNetworkStatus(logicalLabel string) (WwanNetworkStatus, bool) { - for _, status := range ws.Networks { - if logicalLabel == status.LogicalLabel { - return status, true - } - } - return WwanNetworkStatus{}, false -} - -// DoSanitize fills in logical names for cellular modules and SIM cards. -func (ws WwanStatus) DoSanitize() { - uniqueModel := func(model string) bool { - var counter int - for i := range ws.Networks { - if ws.Networks[i].Module.Model == model { - counter++ - } - } - return counter == 1 - } - for i := range ws.Networks { - network := &ws.Networks[i] - if network.Module.Name == "" { - if network.Module.IMEI != "" { - network.Module.Name = network.Module.IMEI - } else if uniqueModel(network.Module.Model) { - network.Module.Name = network.Module.Model - } else { - network.Module.Name = network.PhysAddrs.USB - } - } - for j := range network.SimCards { - simCard := &network.SimCards[j] - if simCard.Name == "" { - if simCard.ICCID != "" { - simCard.Name = simCard.ICCID - } else { - simCard.Name = fmt.Sprintf("%s - SIM%d", network.Module.Name, j) - } - } - } - } -} - -// WwanNetworkStatus contains status information for a single cellular network. -type WwanNetworkStatus struct { - // Logical label of the cellular modem in PhysicalIO. - // Can be empty if this device is not configured by the controller - // (and hence logical label does not exist). - 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"` -} - -// 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"` - ControlProtocol WwanCtrlProt `json:"control-protocol"` - OpMode WwanOpMode `json:"operating-mode"` -} - -// WwanSimCard contains SIM card information. -type WwanSimCard struct { - Name string `json:"name,omitempty"` - ICCID string `json:"iccid"` - IMSI string `json:"imsi"` - Status string `json:"status"` -} - -// 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"` -} - -// WwanOpMode : wwan operating mode -type WwanOpMode string - -const ( - // WwanOpModeUnspecified : operating mode is not specified - WwanOpModeUnspecified WwanOpMode = "" - // WwanOpModeOnline : modem is online but not connected - WwanOpModeOnline WwanOpMode = "online" - // WwanOpModeConnected : modem is online and connected - WwanOpModeConnected WwanOpMode = "online-and-connected" - // WwanOpModeRadioOff : modem has disabled radio transmission - WwanOpModeRadioOff WwanOpMode = "radio-off" - // WwanOpModeOffline : modem is offline - WwanOpModeOffline WwanOpMode = "offline" - // WwanOpModeUnrecognized : unrecongized operating mode - WwanOpModeUnrecognized WwanOpMode = "unrecognized" -) - -// WwanCtrlProt : wwan control protocol -type WwanCtrlProt string - -const ( - // WwanCtrlProtUnspecified : control protocol is not specified - WwanCtrlProtUnspecified WwanCtrlProt = "" - // WwanCtrlProtQMI : modem is controlled using the QMI protocol - WwanCtrlProtQMI WwanCtrlProt = "qmi" - // WwanCtrlProtMBIM : modem is controlled using the MBIM protocol - WwanCtrlProtMBIM WwanCtrlProt = "mbim" -) - -// WwanMetrics is published by the wwan service. -type WwanMetrics struct { - Networks []WwanNetworkMetrics `json:"networks"` -} - -// LookupNetworkMetrics returns metrics corresponding to the given cellular network. -func (wm WwanMetrics) LookupNetworkMetrics(logicalLabel string) (WwanNetworkMetrics, bool) { - for _, metrics := range wm.Networks { - if logicalLabel == metrics.LogicalLabel { - return metrics, true - } - } - return WwanNetworkMetrics{}, false -} - -// Key is used for pubsub -func (wm WwanMetrics) Key() string { - return "global" -} - -// LogCreate : -func (wm WwanMetrics) LogCreate(logBase *base.LogObject) { - logObject := base.NewLogObject(logBase, base.WwanMetricsLogType, "", - nilUUID, wm.LogKey()) - if logObject == nil { - return - } - logObject.Metricf("Wwan metrics create") -} - -// LogModify : -func (wm WwanMetrics) LogModify(logBase *base.LogObject, old interface{}) { - logObject := base.EnsureLogObject(logBase, base.WwanMetricsLogType, "", - nilUUID, wm.LogKey()) - - oldWm, ok := old.(WwanMetrics) - if !ok { - logObject.Clone().Fatalf("LogModify: Old object passed is not of WwanMetrics type") - } - // XXX remove? - logObject.CloneAndAddField("diff", cmp.Diff(oldWm, wm)). - Metricf("Wwan metrics modify") -} - -// LogDelete : -func (wm WwanMetrics) LogDelete(logBase *base.LogObject) { - logObject := base.EnsureLogObject(logBase, base.WwanMetricsLogType, "", - nilUUID, wm.LogKey()) - logObject.Metricf("Wwan metrics delete") - - base.DeleteLogObject(logBase, wm.LogKey()) -} - -// LogKey : -func (wm WwanMetrics) LogKey() string { - return string(base.WwanMetricsLogType) + "-" + wm.Key() -} - -// WwanNetworkMetrics contains metrics for a single cellular network. -type WwanNetworkMetrics struct { - // Logical label of the cellular modem in PhysicalIO. - // Can be empty if this device is not configured by the controller - // (and hence logical label does not exist). - LogicalLabel string `json:"logical-label"` - PhysAddrs WwanPhysAddrs `json:"physical-addrs"` - PacketStats WwanPacketStats `json:"packet-stats"` - SignalInfo WwanSignalInfo `json:"signal-info"` -} - -// WwanPacketStats contains packet statistics recorded by a cellular modem. -type WwanPacketStats struct { - RxBytes uint64 `json:"rx-bytes"` - RxPackets uint64 `json:"rx-packets"` - RxDrops uint64 `json:"rx-drops"` - TxBytes uint64 `json:"tx-bytes"` - TxPackets uint64 `json:"tx-packets"` - TxDrops uint64 `json:"tx-drops"` -} - -// WwanSignalInfo contains cellular signal strength information. -// The maximum value of int32 (0x7FFFFFFF) represents unspecified/unavailable metric. -type WwanSignalInfo struct { - // Received signal strength indicator (RSSI) measured in dBm (decibel-milliwatts). - RSSI int32 `json:"rssi"` - // Reference Signal Received Quality (RSRQ) measured in dB (decibels). - RSRQ int32 `json:"rsrq"` - // Reference Signal Receive Power (RSRP) measured in dBm (decibel-milliwatts). - RSRP int32 `json:"rsrp"` - // Signal-to-Noise Ratio (SNR) measured in dB (decibels). - SNR int32 `json:"snr"` -} - -// WwanLocationInfo contains device location information obtained from a GNSS -// receiver integrated into an LTE modem. -type WwanLocationInfo struct { - // Logical label of the device used to obtain this location information. - LogicalLabel string `json:"logical-label"` - // Latitude in the Decimal degrees (DD) notation. - // Valid values are in the range <-90, 90>. Anything outside of this range - // should be treated as an unavailable value. - // Note that wwan microservice uses -32768 specifically when latitude is not known. - Latitude float64 `json:"latitude"` - // Longitude in the Decimal degrees (DD) notation. - // Valid values are in the range <-180, 180>. Anything outside of this range - // should be treated as an unavailable value. - // Note that wwan microservice uses -32768 specifically when longitude is not known. - Longitude float64 `json:"longitude"` - // Altitude w.r.t. mean sea level in meters. - // Negative value of -32768 is returned when altitude is not known. - Altitude float64 `json:"altitude"` - // Circular horizontal position uncertainty in meters. - // Negative values are not valid and represent unavailable uncertainty. - // Note that wwan microservice uses -32768 specifically when horizontal - // uncertainty is not known. - HorizontalUncertainty float32 `json:"horizontal-uncertainty"` - // Reliability of the provided information for latitude and longitude. - HorizontalReliability LocReliability `json:"horizontal-reliability"` - // Vertical position uncertainty in meters. - // Negative values are not valid and represent unavailable uncertainty. - // Note that wwan microservice uses -32768 specifically when vertical - // uncertainty is not known. - VerticalUncertainty float32 `json:"vertical-uncertainty"` - // Reliability of the provided information for altitude. - VerticalReliability LocReliability `json:"vertical-reliability"` - // Unix timestamp in milliseconds. - // Zero value represents unavailable UTC timestamp. - UTCTimestamp uint64 `json:"utc-timestamp"` -} - -// Key is used for pubsub -func (wli WwanLocationInfo) Key() string { - return "global" -} - -// LogCreate : -func (wli WwanLocationInfo) LogCreate(logBase *base.LogObject) { - logObject := base.NewLogObject(logBase, base.WwanLocationInfoLogType, "", - nilUUID, wli.LogKey()) - if logObject == nil { - return - } - logObject.Metricf("Wwan location info create") -} - -// LogModify : -func (wli WwanLocationInfo) LogModify(logBase *base.LogObject, old interface{}) { - logObject := base.EnsureLogObject(logBase, base.WwanLocationInfoLogType, "", - nilUUID, wli.LogKey()) - - oldWli, ok := old.(WwanLocationInfo) - if !ok { - logObject.Clone().Fatalf("LogModify: Old object passed is not of WwanLocationInfo type") - } - // XXX remove? - logObject.CloneAndAddField("diff", cmp.Diff(oldWli, wli)). - Metricf("Wwan location info modify") -} - -// LogDelete : -func (wli WwanLocationInfo) LogDelete(logBase *base.LogObject) { - logObject := base.EnsureLogObject(logBase, base.WwanLocationInfoLogType, "", - nilUUID, wli.LogKey()) - logObject.Metricf("Wwan location info delete") - base.DeleteLogObject(logBase, wli.LogKey()) -} - -// LogKey : -func (wli WwanLocationInfo) LogKey() string { - return string(base.WwanLocationInfoLogType) + "-" + wli.Key() -} - -// LocReliability : reliability of location information. -type LocReliability string - -const ( - // LocReliabilityUnspecified : reliability is not specified - LocReliabilityUnspecified LocReliability = "not-set" - // LocReliabilityVeryLow : very low reliability - LocReliabilityVeryLow LocReliability = "very-low" - // LocReliabilityLow : low reliability - LocReliabilityLow LocReliability = "low" - // LocReliabilityMedium : medium reliability - LocReliabilityMedium LocReliability = "medium" - // LocReliabilityHigh : high reliability - LocReliabilityHigh LocReliability = "high" -) - // AppBlobsAvailable provides a list of AppCustom blobs which has been provided // from the cloud type AppBlobsAvailable struct { diff --git a/pkg/pillar/utils/generics.go b/pkg/pillar/utils/generics/generics.go similarity index 99% rename from pkg/pillar/utils/generics.go rename to pkg/pillar/utils/generics/generics.go index 45db569dfd8..6dafb734b76 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. diff --git a/pkg/pillar/utils/ip.go b/pkg/pillar/utils/ip.go deleted file mode 100644 index 1e93d738944..00000000000 --- a/pkg/pillar/utils/ip.go +++ /dev/null @@ -1,36 +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) -} - -// SameIPVersions returns true if both IP addresses are of the same version -func SameIPVersions(ip1, ip2 net.IP) bool { - firstIsV4 := ip1.To4() != nil - secondIsV4 := ip2.To4() != nil - return firstIsV4 == secondIsV4 -} diff --git a/pkg/pillar/utils/netutils/ip.go b/pkg/pillar/utils/netutils/ip.go new file mode 100644 index 00000000000..1bdbb46ff5e --- /dev/null +++ b/pkg/pillar/utils/netutils/ip.go @@ -0,0 +1,81 @@ +// 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) +} + +// SameIPVersions returns true if both IP addresses are of the same version +func SameIPVersions(ip1, ip2 net.IP) bool { + firstIsV4 := ip1.To4() != nil + secondIsV4 := ip2.To4() != nil + return firstIsV4 == secondIsV4 +} + +// 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/pillar/zedcloud/send.go b/pkg/pillar/zedcloud/send.go index 9c4ade7b824..f3cd733a4b8 100644 --- a/pkg/pillar/zedcloud/send.go +++ b/pkg/pillar/zedcloud/send.go @@ -30,6 +30,7 @@ import ( "github.com/lf-edge/eve/pkg/pillar/types" "github.com/lf-edge/eve/pkg/pillar/utils" logutils "github.com/lf-edge/eve/pkg/pillar/utils/logging" + "github.com/lf-edge/eve/pkg/pillar/utils/netutils" uuid "github.com/satori/go.uuid" "github.com/vishvananda/netlink" "google.golang.org/protobuf/proto" @@ -569,7 +570,7 @@ func (d *dialerWithResolverCache) DialContext( continue } if d.localIP != nil && - !utils.SameIPVersions(cachedEntry.IPAddress, d.localIP) { + !netutils.SameIPVersions(cachedEntry.IPAddress, d.localIP) { continue } var addrWithIP string @@ -649,36 +650,43 @@ func SendOnIntf(workContext context.Context, ctx *ZedCloudContext, destURL strin } addrCount, err := types.CountLocalAddrAnyNoLinkLocalIf(*ctx.DeviceNetworkStatus, intf) - if err != nil { - return rv, err - } - log.Tracef("Connecting to %s using intf %s #sources %d reqlen %d\n", - reqURL, intf, addrCount, reqlen) - - if addrCount == 0 { + if err != nil || addrCount == 0 { if ctx.FailureFunc != nil && !dryRun { ctx.FailureFunc(log, intf, reqURL, 0, 0, false) } // Determine a specific failure for intf - link, err := netlink.LinkByName(intf) - if err != nil { - errStr := fmt.Sprintf("Link not found to connect to %s using intf %s: %s", - reqURL, intf, err) - log.Traceln(errStr) - return rv, errors.New(errStr) + if intf == "" { + err = fmt.Errorf("missing interface name") + log.Tracef("unable to connect to %s: %v", reqURL, err) + return rv, err + } + link, err2 := netlink.LinkByName(intf) + if err2 != nil { + err = fmt.Errorf("link not found for interface %s", intf) + log.Tracef("unable to connect to %s: %v", reqURL, err) + return rv, err } attrs := link.Attrs() if attrs.OperState != netlink.OperUp { - errStr := fmt.Sprintf("Link not up to connect to %s using intf %s: %s", - reqURL, intf, attrs.OperState.String()) - log.Traceln(errStr) - return rv, errors.New(errStr) + err = fmt.Errorf("link not up for interface %s (%s)", + intf, attrs.OperState.String()) + log.Tracef("unable to connect to %s: %v", reqURL, err) + return rv, err + } + // A different issue caused CountLocalAddrAnyNoLinkLocalIf to fail. + if err != nil { + log.Tracef("unable to connect to %s: %v", reqURL, err) + return rv, err } + // err is nil but addrCount is zero err = &types.IPAddrNotAvail{IfName: intf} - log.Trace(err) + log.Tracef("unable to connect to %s: %v", reqURL, err) return rv, err } + log.Tracef("Connecting to %s using intf %s #sources %d reqlen %d\n", + reqURL, intf, addrCount, reqlen) + // Prepare config for the HTTP client. clientConfig := nettrace.HTTPClientCfg{ // Since we recreate the transport on each call there is no benefit 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\"")"