diff --git a/docs/APP-CONNECTIVITY.md b/docs/APP-CONNECTIVITY.md index c5a908b737..c064a4d6a7 100644 --- a/docs/APP-CONNECTIVITY.md +++ b/docs/APP-CONNECTIVITY.md @@ -207,6 +207,25 @@ to retrieve cloud-init configuration, obtain information from EVE (e.g. device U hostname, uplink IP address) or to download [patch envelopes](PATCH-ENVELOPES.md). More information about metadata server can be found in [ECO-METADATA.md](ECO-METADATA.md). +#### IPAM + +Every Local Network Instance must be configured with an IPv4 network subnet and an IP +range within this subnet for automatic IP allocations. Host IP addresses from this subnet +that do not fall within the IP range are available for manual assignment. + +Whether an IP address is selected manually or dynamically assigned by EVE from the configured +IP range, an internal DHCP server is used to distribute these IP addresses to applications. +Container applications are deployed inside a "shim VM", which EVE prepares, ensuring that +a DHCP client is running for every virtual interface connected to a network instance +This guarantees that the IP address is received and applied before the application starts. +In contrast, VM applications are responsible for starting their own DHCP client and applying +the received IP addresses. + +Regardless of the application type, EVE does not automatically assume that the allocated +IP address is actually in use. Instead, it monitors the set of IP leases granted by the internal +DHCP server and updates the set of application IP addresses in the published info messages +accordingly. + ### Switch Network Instance Switch Network Instance is a simple L2-only bridge between connected applications and @@ -232,6 +251,52 @@ inbound ACL rules are that much more important. Metadata HTTP server is run for switch network instance only if it has uplink port with an IP address. +#### IP address detection + +Unlike a Local Network Instance, a switch network instance is configured without any IP +configuration, and EVE does not run an internal DHCP server. Instead, if IP connectivity +is required, IP addresses must be assigned statically within the connected applications +or provided by an external DHCP server or another application offering DHCP services. + +Since EVE is not in control of IP address allocations and leases, it must monitor application +traffic to learn which IP addresses are being used and report this information to the controller. + +In the case of an external DHCP server (IPv4), EVE captures the DHCPACK packet from the server, +which confirms the leased IP address. Because EVE manages MAC address allocations, it knows +the MAC address of every application's virtual interface (VIF). It can then map the CHADDR +(Client Hardware Address) attribute to the corresponding application VIF and learn the assigned +IP address from the YIADDR (your, i.e. client, IP Address) attribute. Additionally, EVE reads +the DHCP option 51 (Lease Time), if available, to determine how long the leased IP address +is valid. If EVE does not observe an IP renewal within this period, it assumes that the IP address +is no longer in use and reports this change to the controller. + +For statically assigned IPv4 addresses, EVE captures both ARP reply and request packets to learn +the application VIF IP assignment from either Sender IP + MAC or Target IP + MAC attribute +pairs. Since ARP cache entries have a limited lifetime — typically around 2 minutes — EVE expects +to see at least one ARP packet for every assigned IP within a 10-minute window (this is not +configurable). If no ARP packet is observed within this period for a previously detected IP +assignment, EVE assumes that the IP address has been removed and reports this change to +the controller. EVE also captures ARP packets for IP addresses configured via DHCP, but these +are ignored as the information from the previously captured DHCPACK takes precedence. +Note that ARP-based IP detection is enabled by default but can be disabled by setting +the configuration item `network.switch.enable.arpsnoop` to `false`. Change in this config +options will apply to already deployed switch network instances. + +For an external DHCPv6 server, EVE captures DHCPv6 REPLY messages. It learns the target MAC +address from the DUID option (Client Identifier, option code 1), while the IPv6 address +and its valid lifetime are provided by the IA Address (option code 5). + +To learn IPv6 addresses assigned using SLAAC (Stateless Address Auto Configuration), +EVE captures unicast ICMPv6 Neighbor Solicitation messages. These are sent from the interface +with the assigned IPv6 address to check if the address is free or already in use by another +host — a process known as Duplicate Address Detection (DAD). The ICMPv6 packet sent to detect +IP duplicates for a particular VIF IP will have the VIF MAC address as the source address +in the Ethernet header. EVE uses this, along with the "Target Address" field from the ICMPv6 +header, to identify the assigned IPv6 address. + +EVE is capable of detecting multiple IPs assigned to the same VIF MAC address. This is commonly +seen when applications use VLAN sub-interfaces, which share the parent interface's MAC address. + ### Uplink Port Network instances can be configured with an "uplink" network adapter, which will be used to provide diff --git a/docs/CONFIG-PROPERTIES.md b/docs/CONFIG-PROPERTIES.md index cb452c9a2e..64b4648763 100644 --- a/docs/CONFIG-PROPERTIES.md +++ b/docs/CONFIG-PROPERTIES.md @@ -53,7 +53,7 @@ | netdump.topic.maxcount | integer | 10 | maximum number of netdumps that can be published for each topic. The oldest netdump is unpublished should a new netdump exceed the limit. | netdump.downloader.with.pcap | boolean | false | include packet captures inside netdumps for download requests. However, even if enabled, TCP segments carrying non-empty payload (i.e. content which is being downloaded) are excluded and the overall PCAP size is limited to 64MB. | | netdump.downloader.http.with.fieldvalue | boolean | false | include HTTP header field values in captured network traces for download requests (beware: may contain secrets, such as datastore credentials). | -| network.switch.enable.arpsnoop | boolean | true | enable ARP Snooping on switch Network Instance, may need a device reboot to take effect | +| network.switch.enable.arpsnoop | boolean | true | enable ARP Snooping on switch Network Instances | | wwan.query.visible.providers | bool | false | enable to periodically (once per hour) query the set of visible cellular service providers and publish them under WirelessStatus (for every modem) | | network.local.legacy.mac.address | bool | false | enables legacy MAC address generation for local network instances for those EVE nodes where changing MAC addresses in applications will lead to incorrect network configuration | diff --git a/pkg/pillar/cmd/zedagent/handleconfig.go b/pkg/pillar/cmd/zedagent/handleconfig.go index 29f85724b4..26766c8510 100644 --- a/pkg/pillar/cmd/zedagent/handleconfig.go +++ b/pkg/pillar/cmd/zedagent/handleconfig.go @@ -1200,8 +1200,22 @@ func updateLocalServerMap(getconfigCtx *getconfigContext, localServerURL string) continue } if localServerIP != nil { - // check if the defined IP of localServer equals the allocated IP of the app - if adapterStatus.AllocatedIPv4Addr.Equal(localServerIP) { + // Check if the defined IP of localServer equals one of the IPs + // allocated to the app. + var matchesApp bool + for _, ip := range adapterStatus.AssignedAddresses.IPv4Addrs { + if ip.Address.Equal(localServerIP) { + matchesApp = true + break + } + } + for _, ip := range adapterStatus.AssignedAddresses.IPv6Addrs { + if ip.Address.Equal(localServerIP) { + matchesApp = true + break + } + } + if matchesApp { srvAddr := localServerAddr{ localServerAddr: localServerURL, bridgeIP: adapterStatus.BridgeIPAddr, diff --git a/pkg/pillar/cmd/zedagent/handlemetrics.go b/pkg/pillar/cmd/zedagent/handlemetrics.go index 43c601f806..50effcf8c0 100644 --- a/pkg/pillar/cmd/zedagent/handlemetrics.go +++ b/pkg/pillar/cmd/zedagent/handlemetrics.go @@ -1108,16 +1108,18 @@ func PublishAppInfoToZedCloud(ctx *zedagentContext, uuid string, for _, ifname := range ifNames { networkInfo := new(info.ZInfoNetwork) networkInfo.LocalName = *proto.String(ifname) - ipv4Addr, ipv6Addrs, allocated, macAddr, ipAddrMismatch := - getAppIP(ctx, aiStatus, ifname) - if ipv4Addr != nil { - networkInfo.IPAddrs = append(networkInfo.IPAddrs, ipv4Addr.String()) + addrs, hasIPv4Addr, macAddr, ipAddrMismatch := + getAppIPs(ctx, aiStatus, ifname) + for _, ipv4Addr := range addrs.IPv4Addrs { + networkInfo.IPAddrs = append(networkInfo.IPAddrs, + ipv4Addr.Address.String()) } - for _, ipv6Addr := range ipv6Addrs { - networkInfo.IPAddrs = append(networkInfo.IPAddrs, ipv6Addr.String()) + for _, ipv6Addr := range addrs.IPv6Addrs { + networkInfo.IPAddrs = append(networkInfo.IPAddrs, + ipv6Addr.Address.String()) } networkInfo.MacAddr = *proto.String(macAddr.String()) - networkInfo.Ipv4Up = allocated + networkInfo.Ipv4Up = hasIPv4Addr networkInfo.IpAddrMisMatch = ipAddrMismatch name := appIfnameToName(aiStatus, ifname) log.Tracef("app %s/%s localName %s devName %s", @@ -1563,21 +1565,22 @@ func sendMetricsProtobuf(ctx *getconfigContext, // Use the ifname/vifname to find the AppNetAdapter status // and from there the (ip, allocated, mac) addresses for the app -func getAppIP(ctx *zedagentContext, aiStatus *types.AppInstanceStatus, - vifname string) (net.IP, []net.IP, bool, net.HardwareAddr, bool) { +func getAppIPs(ctx *zedagentContext, aiStatus *types.AppInstanceStatus, + vifname string) (types.AssignedAddrs, bool, net.HardwareAddr, bool) { log.Tracef("getAppIP(%s, %s)", aiStatus.Key(), vifname) for _, adapterStatus := range aiStatus.AppNetAdapters { if adapterStatus.VifUsed != vifname { continue } - log.Tracef("getAppIP(%s, %s) found AppIP v4: %s, v6: %s, ipv4 assigned %v mac %s", - aiStatus.Key(), vifname, adapterStatus.AllocatedIPv4Addr, - adapterStatus.AllocatedIPv6List, adapterStatus.IPv4Assigned, adapterStatus.Mac) - return adapterStatus.AllocatedIPv4Addr, adapterStatus.AllocatedIPv6List, adapterStatus.IPv4Assigned, + log.Tracef("getAppIP(%s, %s) found AppIPs v4: %v, v6: %v, ipv4 assigned %v mac %s", + aiStatus.Key(), vifname, adapterStatus.AssignedAddresses.IPv4Addrs, + adapterStatus.AssignedAddresses.IPv6Addrs, adapterStatus.IPv4Assigned, + adapterStatus.Mac) + return adapterStatus.AssignedAddresses, adapterStatus.IPv4Assigned, adapterStatus.Mac, adapterStatus.IPAddrMisMatch } - return nil, nil, false, nil, false + return types.AssignedAddrs{}, false, nil, false } func createVolumeInstanceMetrics(ctx *zedagentContext, reportMetrics *metrics.ZMetricMsg) { diff --git a/pkg/pillar/cmd/zedagent/handlenetworkinstance.go b/pkg/pillar/cmd/zedagent/handlenetworkinstance.go index 5c4e952ec1..945d889623 100644 --- a/pkg/pillar/cmd/zedagent/handlenetworkinstance.go +++ b/pkg/pillar/cmd/zedagent/handlenetworkinstance.go @@ -8,7 +8,6 @@ package zedagent import ( "bytes" "fmt" - "net" "time" "github.com/golang/protobuf/ptypes/timestamp" @@ -109,11 +108,13 @@ func prepareAndPublishNetworkInstanceInfoMsg(ctx *zedagentContext, for mac, addrs := range status.IPAssignments { assignment := new(zinfo.ZmetIPAssignmentEntry) assignment.MacAddress = mac - if !addrs.IPv4Addr.Equal(net.IP{}) { - assignment.IpAddress = append(assignment.IpAddress, addrs.IPv4Addr.String()) + for _, assignedIP := range addrs.IPv4Addrs { + assignment.IpAddress = append(assignment.IpAddress, + assignedIP.Address.String()) } - for _, ip := range addrs.IPv6Addrs { - assignment.IpAddress = append(assignment.IpAddress, ip.String()) + for _, assignedIP := range addrs.IPv6Addrs { + assignment.IpAddress = append(assignment.IpAddress, + assignedIP.Address.String()) } info.IpAssignments = append(info.IpAssignments, assignment) } diff --git a/pkg/pillar/cmd/zedrouter/appnetwork.go b/pkg/pillar/cmd/zedrouter/appnetwork.go index 353d11370b..40a0609933 100644 --- a/pkg/pillar/cmd/zedrouter/appnetwork.go +++ b/pkg/pillar/cmd/zedrouter/appnetwork.go @@ -62,7 +62,8 @@ func (z *zedrouter) updateVIFsForStateCollecting( } _, vifs, err := z.getArgsForNIStateCollecting(network) if err == nil { - err = z.niStateCollector.UpdateCollectingForNI(*netConfig, vifs) + err = z.niStateCollector.UpdateCollectingForNI(*netConfig, vifs, + z.enableArpSnooping) } if err != nil { z.log.Error(err) @@ -168,10 +169,7 @@ func (z *zedrouter) updateNIStatusAfterAppNetworkActivate(status *types.AppNetwo netInstStatus.AddVif(z.log, adapterStatus.Vif, adapterStatus.Mac, status.UUIDandVersion.UUID) netInstStatus.IPAssignments[adapterStatus.Mac.String()] = - types.AssignedAddrs{ - IPv4Addr: adapterStatus.AllocatedIPv4Addr, - IPv6Addrs: adapterStatus.AllocatedIPv6List, - } + adapterStatus.AssignedAddresses z.publishNetworkInstanceStatus(netInstStatus) } } diff --git a/pkg/pillar/cmd/zedrouter/ipam.go b/pkg/pillar/cmd/zedrouter/ipam.go index b1afc6087f..31aa4fff2b 100644 --- a/pkg/pillar/cmd/zedrouter/ipam.go +++ b/pkg/pillar/cmd/zedrouter/ipam.go @@ -120,13 +120,15 @@ func (z *zedrouter) lookupOrAllocateIPv4ForVIF(niStatus *types.NetworkInstanceSt // Lookup to see if it is already allocated. if ipAddr == nil { addrs := niStatus.IPAssignments[adapterStatus.Mac.String()] - if !isEmptyIP(addrs.IPv4Addr) { - z.log.Functionf("lookupOrAllocateIPv4(NI:%v, app:%v): found IP %v for MAC %v", - networkID, appID, addrs.IPv4Addr, adapterStatus.Mac) - ipAddr = addrs.IPv4Addr + ipAddr = addrs.GetInternallyLeasedIPv4Addr() + if ipAddr != nil { + z.log.Functionf("lookupOrAllocateIPv4(NI:%v, app:%v): "+ + "found EVE-allocated IP %v for MAC %v", networkID, appID, ipAddr, + adapterStatus.Mac) } } + var newlyAllocated bool if ipAddr == nil { // Allocate IP address dynamically. // Get the app number for the AppNetAdapter entry. @@ -140,6 +142,7 @@ func (z *zedrouter) lookupOrAllocateIPv4ForVIF(niStatus *types.NetworkInstanceSt } // Pick an IP address from the subnet. ipAddr = netutils.AddToIP(niStatus.DhcpRange.Start, appNum) + newlyAllocated = true // 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)", @@ -162,12 +165,19 @@ func (z *zedrouter) lookupOrAllocateIPv4ForVIF(niStatus *types.NetworkInstanceSt } } // Later will be overwritten with addresses received from nistate.Collector, - // which snoops DHCP traffic and watches DNS server leases to learn the *actual* + // which snoops DHCP traffic and watches DHCP server leases to learn the *actual* // IP address assignments. - addrs := niStatus.IPAssignments[adapterStatus.Mac.String()] // preserve IPv6 addresses - addrs.IPv4Addr = ipAddr - niStatus.IPAssignments[adapterStatus.Mac.String()] = addrs - z.publishNetworkInstanceStatus(niStatus) + if newlyAllocated { + // Preserve other IPv4 and IPv6 addresses. + addrs := niStatus.IPAssignments[adapterStatus.Mac.String()] + addrs.IPv4Addrs = append(addrs.IPv4Addrs, + types.AssignedAddr{ + Address: ipAddr, + AssignedBy: types.AddressSourceInternalDHCP, + }) + niStatus.IPAssignments[adapterStatus.Mac.String()] = addrs + z.publishNetworkInstanceStatus(niStatus) + } z.log.Functionf("lookupOrAllocateIPv4(NI:%v, app:%v): allocated IP %v for MAC %v", networkID, appID, ipAddr, adapterStatus.Mac) return ipAddr, nil @@ -188,16 +198,16 @@ func (z *zedrouter) recordAssignedIPsToAdapterStatus(adapter *types.AppNetAdapte z.removeAssignedIPsFromAdapterStatus(adapter) return } - adapter.AllocatedIPv4Addr = vifAddrs.IPv4Addr - if !isEmptyIP(adapter.AppIPAddr) && - !adapter.AppIPAddr.Equal(adapter.AllocatedIPv4Addr) { - // Config and status do not match. - adapter.IPAddrMisMatch = true - } else { - adapter.IPAddrMisMatch = false + adapter.AssignedAddresses = vifAddrs.AssignedAddrs + adapter.IPAddrMisMatch = false + if !isEmptyIP(adapter.AppIPAddr) { + leasedIP := adapter.AssignedAddresses.GetInternallyLeasedIPv4Addr() + if !adapter.AppIPAddr.Equal(leasedIP) { + // Config and status do not match. + adapter.IPAddrMisMatch = true + } } - adapter.AllocatedIPv6List = vifAddrs.IPv6Addrs - adapter.IPv4Assigned = !isEmptyIP(vifAddrs.IPv4Addr) + adapter.IPv4Assigned = len(vifAddrs.IPv4Addrs) > 0 } func (z *zedrouter) removeAssignedIPsFromAppNetStatus(status *types.AppNetworkStatus) { @@ -208,8 +218,8 @@ func (z *zedrouter) removeAssignedIPsFromAppNetStatus(status *types.AppNetworkSt } func (z *zedrouter) removeAssignedIPsFromAdapterStatus(adapterStatus *types.AppNetAdapterStatus) { - adapterStatus.AllocatedIPv6List = nil - adapterStatus.AllocatedIPv4Addr = nil + adapterStatus.AssignedAddresses.IPv4Addrs = nil + adapterStatus.AssignedAddresses.IPv6Addrs = nil adapterStatus.IPAddrMisMatch = false adapterStatus.IPv4Assigned = false } diff --git a/pkg/pillar/cmd/zedrouter/networkinstance.go b/pkg/pillar/cmd/zedrouter/networkinstance.go index eb47848fa3..3926ebb038 100644 --- a/pkg/pillar/cmd/zedrouter/networkinstance.go +++ b/pkg/pillar/cmd/zedrouter/networkinstance.go @@ -230,7 +230,7 @@ func (z *zedrouter) doUpdateActivatedNetworkInstance(config types.NetworkInstanc z.processNIReconcileStatus(niRecStatus, status) _, vifs, err := z.getArgsForNIStateCollecting(config.UUID) if err == nil { - err = z.niStateCollector.UpdateCollectingForNI(config, vifs) + err = z.niStateCollector.UpdateCollectingForNI(config, vifs, z.enableArpSnooping) } if err != nil { z.log.Error(err) diff --git a/pkg/pillar/cmd/zedrouter/pubsubhandlers.go b/pkg/pillar/cmd/zedrouter/pubsubhandlers.go index e0e25ac2a7..4305111c59 100644 --- a/pkg/pillar/cmd/zedrouter/pubsubhandlers.go +++ b/pkg/pillar/cmd/zedrouter/pubsubhandlers.go @@ -51,7 +51,33 @@ func (z *zedrouter) handleGlobalConfigImpl(ctxArg interface{}, key string, } z.metricInterval = metricInterval } - z.enableArpSnooping = gcp.GlobalValueBool(types.EnableARPSnoop) + enableArpSnooping := gcp.GlobalValueBool(types.EnableARPSnoop) + if z.enableArpSnooping != enableArpSnooping { + z.enableArpSnooping = enableArpSnooping + // Start/Stop ARP snooping in every activated Switch NI. + for _, item := range z.pubNetworkInstanceStatus.GetAll() { + niStatus := item.(types.NetworkInstanceStatus) + if !niStatus.Activated { + continue + } + if niStatus.Type != types.NetworkInstanceTypeSwitch { + // ARP snooping is only used in Switch NIs. + continue + } + niConfig := z.lookupNetworkInstanceConfig(niStatus.Key()) + if niConfig == nil { + continue + } + _, vifs, err := z.getArgsForNIStateCollecting(niConfig.UUID) + if err == nil { + err = z.niStateCollector.UpdateCollectingForNI( + *niConfig, vifs, z.enableArpSnooping) + } + if err != nil { + z.log.Error(err) + } + } + } z.localLegacyMACAddr = gcp.GlobalValueBool(types.NetworkLocalLegacyMACAddress) z.niReconciler.ApplyUpdatedGCP(z.runCtx, *gcp) } @@ -223,7 +249,13 @@ func (z *zedrouter) handleNetworkInstanceCreate(ctxArg interface{}, key string, // Set bridge IP address. if status.Gateway != nil { - addrs := types.AssignedAddrs{IPv4Addr: status.Gateway} + addrs := types.AssignedAddrs{ + IPv4Addrs: []types.AssignedAddr{ + { + Address: status.Gateway, + AssignedBy: types.AddressSourceEVEInternal, + }}, + } status.IPAssignments[status.BridgeMac.String()] = addrs status.BridgeIPAddr = status.Gateway } @@ -359,8 +391,14 @@ func (z *zedrouter) handleNetworkInstanceModify(ctxArg interface{}, key string, if status.BridgeMac != nil { delete(status.IPAssignments, status.BridgeMac.String()) } - if status.Gateway != nil { - addrs := types.AssignedAddrs{IPv4Addr: status.Gateway} + if status.Gateway != nil && status.BridgeMac != nil { + addrs := types.AssignedAddrs{ + IPv4Addrs: []types.AssignedAddr{ + { + Address: status.Gateway, + AssignedBy: types.AddressSourceEVEInternal, + }}, + } status.IPAssignments[status.BridgeMac.String()] = addrs status.BridgeIPAddr = status.Gateway } diff --git a/pkg/pillar/cmd/zedrouter/zedrouter.go b/pkg/pillar/cmd/zedrouter/zedrouter.go index f2a65249b7..91481ffbae 100644 --- a/pkg/pillar/cmd/zedrouter/zedrouter.go +++ b/pkg/pillar/cmd/zedrouter/zedrouter.go @@ -520,10 +520,7 @@ func (z *zedrouter) run(ctx context.Context) (err error) { niKey, vif.NetAdapterName) continue } - netStatus.IPAssignments[mac] = types.AssignedAddrs{ - IPv4Addr: newAddrs.IPv4Addr, - IPv6Addrs: newAddrs.IPv6Addrs, - } + netStatus.IPAssignments[mac] = newAddrs.AssignedAddrs z.publishNetworkInstanceStatus(netStatus) appKey := vif.App.String() appStatus := z.lookupAppNetworkStatus(appKey) @@ -1099,11 +1096,13 @@ func (z *zedrouter) lookupNetworkInstanceStatusByAppIP( for _, st := range items { status := st.(types.NetworkInstanceStatus) for _, addrs := range status.IPAssignments { - if ip.Equal(addrs.IPv4Addr) { - return &status + for _, assignedIP := range addrs.IPv4Addrs { + if ip.Equal(assignedIP.Address) { + return &status + } } - for _, nip := range addrs.IPv6Addrs { - if ip.Equal(nip) { + for _, assignedIP := range addrs.IPv6Addrs { + if ip.Equal(assignedIP.Address) { return &status } } @@ -1286,8 +1285,15 @@ func (z *zedrouter) lookupAppNetworkStatusByAppIP(ip net.IP) *types.AppNetworkSt for _, st := range items { status := st.(types.AppNetworkStatus) for _, adapterStatus := range status.AppNetAdapterList { - if adapterStatus.AllocatedIPv4Addr.Equal(ip) { - return &status + for _, adapterIP := range adapterStatus.AssignedAddresses.IPv4Addrs { + if adapterIP.Address.Equal(ip) { + return &status + } + } + for _, adapterIP := range adapterStatus.AssignedAddresses.IPv6Addrs { + if adapterIP.Address.Equal(ip) { + return &status + } } } } diff --git a/pkg/pillar/nistate/linux.go b/pkg/pillar/nistate/linux.go index fdcea9a655..ef17a9f539 100644 --- a/pkg/pillar/nistate/linux.go +++ b/pkg/pillar/nistate/linux.go @@ -4,7 +4,9 @@ package nistate import ( + "bytes" "fmt" + "net" "path/filepath" "strings" "sync" @@ -40,13 +42,169 @@ type LinuxCollector struct { } type niInfo struct { - config types.NetworkInstanceConfig - bridge NIBridge - vifs VIFAddrsList - ipLeases dnsmasqIPLeases - cancelPCAP context.CancelFunc - ipv4DNSReqs []dnsReq - ipv6DNSReqs []dnsReq + config types.NetworkInstanceConfig + bridge NIBridge + vifs []*vifInfo + ipLeases dnsmasqIPLeases + cancelPCAP context.CancelFunc + pcapWG sync.WaitGroup + ipv4DNSReqs []dnsReq + ipv6DNSReqs []dnsReq + arpSnoopEnabled bool +} + +// lookupVIFByGuestMAC : Lookup VIF by the MAC address of the guest interface. +func (ni *niInfo) lookupVIFByGuestMAC(mac net.HardwareAddr) *vifInfo { + for _, vif := range ni.vifs { + if bytes.Equal(vif.GuestIfMAC, mac) { + return vif + } + } + return nil +} + +type detectedAddr struct { + types.AssignedAddr + validUntil time.Time // zero timestamp if validity is not known/limited +} + +func (addr detectedAddr) hasExpired() bool { + return !addr.validUntil.IsZero() && time.Now().After(addr.validUntil) +} + +func (addr detectedAddr) fromDHCP() bool { + return addr.AssignedBy == types.AddressSourceInternalDHCP || + addr.AssignedBy == types.AddressSourceExternalDHCP +} + +type vifInfo struct { + AppVIF + ipv4Addrs []detectedAddr + ipv6Addrs []detectedAddr +} + +func (vif *vifInfo) exportVIFAddrs() VIFAddrs { + var assignedAddrs types.AssignedAddrs + for _, addr := range vif.ipv4Addrs { + assignedAddrs.IPv4Addrs = append(assignedAddrs.IPv4Addrs, + types.AssignedAddr{ + Address: addr.Address, + AssignedBy: addr.AssignedBy, + }) + } + for _, addr := range vif.ipv6Addrs { + assignedAddrs.IPv6Addrs = append(assignedAddrs.IPv6Addrs, + types.AssignedAddr{ + Address: addr.Address, + AssignedBy: addr.AssignedBy, + }) + } + return VIFAddrs{ + AssignedAddrs: assignedAddrs, + VIF: vif.AppVIF, + } +} + +func (vif *vifInfo) hasIP(ip net.IP) bool { + for _, ipv4Addr := range vif.ipv4Addrs { + if ip.Equal(ipv4Addr.Address) { + return true + } + } + for _, ipv6Addr := range vif.ipv6Addrs { + if ip.Equal(ipv6Addr.Address) { + return true + } + } + return false +} + +// addIP adds or updates detected/leased IP address into the list of assigned addresses. +func (vif *vifInfo) addIP(ip net.IP, source types.AddressSource, + validUntil time.Time) (update *VIFAddrsUpdate) { + ipList := vif.ipv4Addrs + if ip.To4() == nil { + ipList = vif.ipv6Addrs + } + var alreadyExists, changed bool + prevAddrs := vif.exportVIFAddrs() + for i := range ipList { + if ipList[i].Address.Equal(ip) { + // IP address is already known for this VIF. + // Just update the source and the expiration time. + alreadyExists = true + if source == types.AddressSourceStatic && ipList[i].fromDHCP() { + // Prefer info from DHCP snooping over ARP snooping. + // Ignore this update. + return nil + } + ipList[i].validUntil = validUntil + if ipList[i].AssignedBy != source { + // LinuxCollector will publish VIFAddrsUpdate only to update + // the IP address source. + changed = true + ipList[i].AssignedBy = source + } + break + } + } + if !alreadyExists { + // Newly detected IP address. + changed = true + ipList = append(ipList, detectedAddr{ + AssignedAddr: types.AssignedAddr{ + Address: ip, + AssignedBy: source, + }, + validUntil: validUntil, + }) + } + if ip.To4() == nil { + vif.ipv6Addrs = ipList + } else { + vif.ipv4Addrs = ipList + } + if !changed { + return nil + } + newAddrs := vif.exportVIFAddrs() + return &VIFAddrsUpdate{ + Prev: prevAddrs, + New: newAddrs, + } +} + +// delIPs removes all or only some IPs based on the source and the expiration. +func (vif *vifInfo) delIPs(sourceMask int, onlyExpired bool) *VIFAddrsUpdate { + var changed bool + var filteredV4Addrs, filteredV6Addrs []detectedAddr + for _, addr := range vif.ipv4Addrs { + if (sourceMask&int(addr.AssignedBy) > 0) && + (!onlyExpired || addr.hasExpired()) { + changed = true + continue + } + filteredV4Addrs = append(filteredV4Addrs, addr) + } + for _, addr := range vif.ipv6Addrs { + if (sourceMask&int(addr.AssignedBy) > 0) && + (!onlyExpired || addr.hasExpired()) { + changed = true + continue + } + filteredV6Addrs = append(filteredV6Addrs, addr) + } + if !changed { + return nil + } + prevAddrs := vif.exportVIFAddrs() + vif.ipv4Addrs = filteredV4Addrs + vif.ipv6Addrs = filteredV6Addrs + newAddrs := vif.exportVIFAddrs() + return &VIFAddrsUpdate{ + Prev: prevAddrs, + New: newAddrs, + } } // NewLinuxCollector is a constructor for LinuxCollector. @@ -83,15 +241,17 @@ func (lc *LinuxCollector) StartCollectingForNI( } pcapCtx, cancelPCAP := context.WithCancel(context.Background()) ni := &niInfo{ - config: niConfig, - bridge: br, - cancelPCAP: cancelPCAP, + config: niConfig, + bridge: br, + cancelPCAP: cancelPCAP, + arpSnoopEnabled: enableARPSnoop, } for _, vif := range vifs { - ni.vifs = append(ni.vifs, VIFAddrs{VIF: vif}) + ni.vifs = append(ni.vifs, &vifInfo{AppVIF: vif}) } lc.nis[niConfig.UUID] = ni - go lc.sniffDNSandDHCP(pcapCtx, br, niConfig.Type, enableARPSnoop) + ni.pcapWG.Add(1) + go lc.sniffDNSandDHCP(pcapCtx, &ni.pcapWG, br, niConfig.Type, enableARPSnoop) lc.log.Noticef("%s: Started collecting state data for NI %v "+ "(br: %+v, vifs: %+v)", LogAndErrPrefix, niConfig.UUID, br, vifs) return nil @@ -104,7 +264,7 @@ func (lc *LinuxCollector) StartCollectingForNI( // Note that not every change in network instance config is supported. For example, // network instance type (switch / local) cannot change. func (lc *LinuxCollector) UpdateCollectingForNI( - niConfig types.NetworkInstanceConfig, vifs []AppVIF) error { + niConfig types.NetworkInstanceConfig, vifs []AppVIF, enableARPSnoop bool) error { lc.mu.Lock() defer lc.mu.Unlock() if _, exists := lc.nis[niConfig.UUID]; !exists { @@ -112,17 +272,27 @@ func (lc *LinuxCollector) UpdateCollectingForNI( } ni := lc.nis[niConfig.UUID] ni.config = niConfig - // Preserve already known IP assignments. - prevVIFs := ni.vifs - ni.vifs = nil + var newVifs []*vifInfo for _, vif := range vifs { - vifWithAddrs := VIFAddrs{VIF: vif} - prevVIF := prevVIFs.LookupByGuestMAC(vif.GuestIfMAC) - if prevVIF != nil && prevVIF.VIF.App == vif.App && prevVIF.VIF.NI == vif.NI { - vifWithAddrs.IPv4Addr = prevVIF.IPv4Addr - vifWithAddrs.IPv6Addrs = prevVIF.IPv6Addrs + newVif := &vifInfo{AppVIF: vif} + // Preserve already known IP assignments. + prevVIF := ni.lookupVIFByGuestMAC(vif.GuestIfMAC) + if prevVIF != nil && prevVIF.App == vif.App && prevVIF.NI == vif.NI { + newVif.ipv4Addrs = prevVIF.ipv4Addrs + newVif.ipv6Addrs = prevVIF.ipv6Addrs } - ni.vifs = append(ni.vifs, vifWithAddrs) + newVifs = append(newVifs, newVif) + } + ni.vifs = newVifs + if ni.arpSnoopEnabled != enableARPSnoop { + // Restart packet capture with changed BPF filter. + ni.cancelPCAP() + ni.pcapWG.Wait() + pcapCtx, cancelPCAP := context.WithCancel(context.Background()) + ni.cancelPCAP = cancelPCAP + ni.pcapWG.Add(1) + go lc.sniffDNSandDHCP(pcapCtx, &ni.pcapWG, ni.bridge, niConfig.Type, enableARPSnoop) + ni.arpSnoopEnabled = enableARPSnoop } lc.log.Noticef("%s: Updated state collecting for NI %v "+ "(br: %+v, vifs: %+v)", LogAndErrPrefix, niConfig.UUID, ni.bridge, ni.vifs) @@ -137,7 +307,9 @@ func (lc *LinuxCollector) StopCollectingForNI(niID uuid.UUID) error { if _, exists := lc.nis[niID]; !exists { return ErrUnknownNI{NI: niID} } - lc.nis[niID].cancelPCAP() + ni := lc.nis[niID] + ni.cancelPCAP() + ni.pcapWG.Wait() delete(lc.nis, niID) lc.log.Noticef("%s: Stopped collecting state data for NI %v", LogAndErrPrefix, niID) return nil @@ -152,7 +324,11 @@ func (lc *LinuxCollector) GetIPAssignments(niID uuid.UUID) (VIFAddrsList, error) if !exists { return nil, ErrUnknownNI{NI: niID} } - return niInfo.vifs, nil + var addrList VIFAddrsList + for _, vif := range niInfo.vifs { + addrList = append(addrList, vif.exportVIFAddrs()) + } + return addrList, nil } // WatchIPAssignments : watch for changes in IP assignments to VIFs across @@ -263,7 +439,7 @@ func (lc *LinuxCollector) WatchFlows() <-chan types.IPFlow { // Run periodic and on-change state data collecting for network instances // from a separate Go routine. func (lc *LinuxCollector) runStateCollecting() { - gcIPLeases := time.NewTicker(time.Minute) + gcIPAssignments := time.NewTicker(time.Minute) fmax := float64(flowCollectInterval) fmin := fmax * 0.9 flowCollectTimer := flextimer.NewRangeTicker(time.Duration(fmin), time.Duration(fmax)) @@ -297,19 +473,31 @@ func (lc *LinuxCollector) runStateCollecting() { } } } - case <-gcIPLeases.C: + case <-gcIPAssignments.C: lc.mu.Lock() var addrChanges []VIFAddrsUpdate for _, ni := range lc.nis { + // First remove IP leases which are no longer reported by the internal + // DHCP server. removedAny := lc.gcIPLeases(ni) if removedAny { addrChanges = append(addrChanges, lc.processIPLeases(ni)...) } + // Next remove expired IP leases granted from external DHCP servers + // or statically configured IPs with ARP not seen for more than 10 minutes. + for _, vif := range ni.vifs { + sourceMask := int(types.AddressSourceExternalDHCP) | + int(types.AddressSourceStatic) + update := vif.delIPs(sourceMask, true) + if update != nil { + addrChanges = append(addrChanges, *update) + } + } } watchers := lc.ipAssignWatchers lc.mu.Unlock() if len(addrChanges) != 0 { - lc.logAddrChanges("IP Lease GC event", addrChanges) + lc.logAddrChanges("IP Assignment GC event", addrChanges) for _, watcherCh := range watchers { watcherCh <- addrChanges } @@ -358,18 +546,18 @@ func (lc *LinuxCollector) logAddrChanges(event string, changes []VIFAddrsUpdate) func (lc *LinuxCollector) getVIFByIfName(ifName string) (vif AppVIF, found bool) { for _, niState := range lc.nis { for _, niVIF := range niState.vifs { - if niVIF.VIF.HostIfName == ifName { - return niVIF.VIF, true + if niVIF.HostIfName == ifName { + return niVIF.AppVIF, true } } } return vif, false } -func (lc *LinuxCollector) getVIFsByAppNum(appNum int) (vifs VIFAddrsList) { +func (lc *LinuxCollector) getVIFsByAppNum(appNum int) (vifs []*vifInfo) { for _, niState := range lc.nis { for _, niVIF := range niState.vifs { - if niVIF.VIF.AppNum != appNum { + if niVIF.AppNum != appNum { continue } vifs = append(vifs, niVIF) diff --git a/pkg/pillar/nistate/linux_dnsmasq.go b/pkg/pillar/nistate/linux_dnsmasq.go index 5cc7b84c37..d8d95080bf 100644 --- a/pkg/pillar/nistate/linux_dnsmasq.go +++ b/pkg/pillar/nistate/linux_dnsmasq.go @@ -123,26 +123,21 @@ func (lc *LinuxCollector) processIPLeases(niInfo *niInfo) ( LogAndErrPrefix, niInfo) return nil } - for i := range niInfo.vifs { - vifAddrs := &niInfo.vifs[i] - vif := vifAddrs.VIF + for _, vif := range niInfo.vifs { ipLease := niInfo.ipLeases.findLease(vif.App.String(), vif.GuestIfMAC, true) - if ipLease == nil && vifAddrs.IPv4Addr != nil { - prevAddrs := *vifAddrs - vifAddrs.IPv4Addr = nil - newAddrs := *vifAddrs - addrUpdates = append(addrUpdates, VIFAddrsUpdate{ - Prev: prevAddrs, - New: newAddrs, - }) - } else if ipLease != nil && !ipLease.ipAddr.Equal(vifAddrs.IPv4Addr) { - prevAddrs := *vifAddrs - vifAddrs.IPv4Addr = ipLease.ipAddr - newAddrs := *vifAddrs - addrUpdates = append(addrUpdates, VIFAddrsUpdate{ - Prev: prevAddrs, - New: newAddrs, - }) + if ipLease == nil { + // Remove all recorded internally-granted IP leases for this VIF. + sourceMask := int(types.AddressSourceInternalDHCP) + update := vif.delIPs(sourceMask, false) + if update != nil { + addrUpdates = append(addrUpdates, *update) + } + } else if ipLease != nil { + update := vif.addIP(ipLease.ipAddr, types.AddressSourceInternalDHCP, + ipLease.leaseTime) + if update != nil { + addrUpdates = append(addrUpdates, *update) + } } } return addrUpdates diff --git a/pkg/pillar/nistate/linux_flow.go b/pkg/pillar/nistate/linux_flow.go index 8cb2c49535..81f354fbc7 100644 --- a/pkg/pillar/nistate/linux_flow.go +++ b/pkg/pillar/nistate/linux_flow.go @@ -8,8 +8,10 @@ package nistate import ( "bytes" "context" + "encoding/binary" "net" "strconv" + "sync" "syscall" "time" @@ -41,6 +43,10 @@ const ( // flowLogPrefix allows to filter logs specific to flow collecting // and packet sniffing. flowLogPrefix = LogAndErrPrefix + " (FlowStats)" + + // Statically configured IP address, detected using ARP snooping, is considered + // valid until we do not see any more ARPs for this IP for more than 10 minutes. + staticIPValidDuration = 10 * time.Minute ) type capturedPacket struct { @@ -98,8 +104,8 @@ func (lc *LinuxCollector) collectFlows() (flows []types.IPFlow) { var sequence int ipFlow := types.IPFlow{ Scope: types.FlowScope{ - AppUUID: vif.VIF.App, - NetAdapterName: vif.VIF.NetAdapterName, + AppUUID: vif.App, + NetAdapterName: vif.NetAdapterName, BrIfName: niInfo.bridge.BrIfName, NetUUID: niInfo.config.UUID, }, @@ -117,8 +123,8 @@ func (lc *LinuxCollector) collectFlows() (flows []types.IPFlow) { sequence++ } for _, flowrec := range timeoutedFlows { - if flowrec.vif.App != vif.VIF.App || - flowrec.vif.NetAdapterName != vif.VIF.NetAdapterName { + if flowrec.vif.App != vif.App || + flowrec.vif.NetAdapterName != vif.NetAdapterName { continue } ipFlow.Flows = append(ipFlow.Flows, flowrec.FlowRec) @@ -129,7 +135,7 @@ func (lc *LinuxCollector) collectFlows() (flows []types.IPFlow) { // Append DNS flows corresponding to this app. for _, dnsReq := range dnsReqs { - if !vif.HasIP(dnsReq.appIP) { + if !vif.hasIP(dnsReq.appIP) { continue } ipFlow.DNSReqs = append(ipFlow.DNSReqs, dnsReq.DNSReq) @@ -190,16 +196,16 @@ func (lc *LinuxCollector) convertConntrackToFlow( // refers to a remote endpoint, even for outside initiated flows, meaning // that "src" and "dst" is indeed a very confusing naming (already used // in EVE API therefore not easy to change). - vif := vifs.LookupByIP(entry.Forward.SrcIP) + vif := lookupVIFByIP(entry.Forward.SrcIP, vifs) forwSrcApp = vif != nil if !forwSrcApp { - vif = vifs.LookupByIP(entry.Forward.DstIP) + vif = lookupVIFByIP(entry.Forward.DstIP, vifs) forwDstApp = vif != nil if !forwDstApp { - vif = vifs.LookupByIP(entry.Reverse.SrcIP) + vif = lookupVIFByIP(entry.Reverse.SrcIP, vifs) backSrcApp = vif != nil if !backSrcApp { - vif = vifs.LookupByIP(entry.Reverse.DstIP) + vif = lookupVIFByIP(entry.Reverse.DstIP, vifs) backDstApp = vif != nil } } @@ -222,7 +228,7 @@ func (lc *LinuxCollector) convertConntrackToFlow( // similar to RFC5130 to merge two bidirectional flow using the method of "Perimeter", // here we define the flow src is always the local App endpoint, the flow dst will // be the opposite endpoint. - ipFlow.vif = vif.VIF + ipFlow.vif = vif.AppVIF ipFlow.Flow.Proto = int32(entry.Forward.Protocol) if forwSrcApp { // Src initiated flow, forward-src is the src, reverse-src is the flow dst @@ -277,8 +283,9 @@ func (lc *LinuxCollector) convertConntrackToFlow( // This function is merely capturing packets and then sending them to runStateCollecting, // so that all state collecting and processing happens from the main event loop // (to simplify and avoid race conditions...). -func (lc *LinuxCollector) sniffDNSandDHCP(ctx context.Context, +func (lc *LinuxCollector) sniffDNSandDHCP(ctx context.Context, wg *sync.WaitGroup, br NIBridge, niType types.NetworkInstanceType, enableArpSnoop bool) { + defer wg.Done() var ( err error snapshotLen int32 = 1280 // draft-madi-dnsop-udp4dns-00 @@ -502,13 +509,13 @@ func (lc *LinuxCollector) processARPPacket( return nil } - var vif *VIFAddrs + var vif *vifInfo var weAreSource bool var gotAddress []byte if arp.Operation == layers.ARPReply || arp.Operation == layers.ARPRequest { - vif = niInfo.vifs.LookupByGuestMAC(arp.DstHwAddress) + vif = niInfo.lookupVIFByGuestMAC(arp.DstHwAddress) if vif == nil { - vif = niInfo.vifs.LookupByGuestMAC(arp.SourceHwAddress) + vif = niInfo.lookupVIFByGuestMAC(arp.SourceHwAddress) if vif != nil { weAreSource = true } @@ -520,21 +527,16 @@ func (lc *LinuxCollector) processARPPacket( return nil } - prevAddrs := *vif if weAreSource { gotAddress = arp.SourceProtAddress } else { gotAddress = arp.DstProtAddress } - if vif.IPv4Addr.Equal(gotAddress) { - return nil + validUntil := time.Now().Add(staticIPValidDuration) + update := vif.addIP(gotAddress, types.AddressSourceStatic, validUntil) + if update != nil { + addrUpdates = append(addrUpdates, *update) } - vif.IPv4Addr = gotAddress - newAddrs := *vif - addrUpdates = append(addrUpdates, VIFAddrsUpdate{ - Prev: prevAddrs, - New: newAddrs, - }) return addrUpdates } @@ -559,7 +561,7 @@ func (lc *LinuxCollector) processDHCPPacket( // need to check those in payload to see if it's for an app. isBroadcast = true } else { - foundDstMac = niInfo.vifs.LookupByGuestMAC(etherPkt.DstMAC) != nil + foundDstMac = niInfo.lookupVIFByGuestMAC(etherPkt.DstMAC) != nil } } if !foundDstMac && !isBroadcast { @@ -579,16 +581,22 @@ func (lc *LinuxCollector) processDHCPPacket( } dhcpv4 := dhcpLayer.(*layers.DHCPv4) var isReplyAck bool + var validUntil time.Time if dhcpv4.Operation == layers.DHCPOpReply { opts := dhcpv4.Options for _, opt := range opts { - if opt.Type == layers.DHCPOptMessageType && - int(opt.Data[0]) == int(layers.DHCPMsgTypeAck) { - isReplyAck = true - break + switch opt.Type { + case layers.DHCPOptMessageType: + if int(opt.Data[0]) == int(layers.DHCPMsgTypeAck) { + isReplyAck = true + } + case layers.DHCPOptLeaseTime: + leaseTimeSecs := binary.BigEndian.Uint32(opt.Data) + validUntil = time.Now().Add(time.Duration(leaseTimeSecs) * time.Second) } } } + if !isReplyAck { // This is indeed a DHCP packet but not the DHCP Reply type. return nil, true @@ -607,20 +615,15 @@ func (lc *LinuxCollector) processDHCPPacket( return nil, true } - vif := niInfo.vifs.LookupByGuestMAC(dhcpv4.ClientHWAddr) + vif := niInfo.lookupVIFByGuestMAC(dhcpv4.ClientHWAddr) if vif == nil { return nil, true } - if vif.IPv4Addr.Equal(dhcpv4.YourClientIP) { - return nil, true + update := vif.addIP(dhcpv4.YourClientIP, types.AddressSourceExternalDHCP, + validUntil) + if update != nil { + addrUpdates = append(addrUpdates, *update) } - prevAddrs := *vif - vif.IPv4Addr = dhcpv4.YourClientIP - newAddrs := *vif - addrUpdates = append(addrUpdates, VIFAddrsUpdate{ - Prev: prevAddrs, - New: newAddrs, - }) return addrUpdates, true } @@ -640,29 +643,32 @@ func (lc *LinuxCollector) processDHCPPacket( // This is indeed a DHCP packet but not the DHCP Reply type. return nil, true } + var vif *vifInfo + var validUntil time.Time for _, opt := range dhcpv6.Options { - if opt.Code != layers.DHCPv6OptClientID { - continue - } - clientOption := &layers.DHCPv6DUID{} - clientOption.DecodeFromBytes(opt.Data) - vif := niInfo.vifs.LookupByGuestMAC(clientOption.LinkLayerAddress) - if vif == nil { - return nil, true - } - if isAddrPresent(vif.IPv6Addrs, dhcpv6.LinkAddr) { - return nil, true + switch opt.Code { + case layers.DHCPv6OptClientID: + clientOption := &layers.DHCPv6DUID{} + clientOption.DecodeFromBytes(opt.Data) + vif = niInfo.lookupVIFByGuestMAC(clientOption.LinkLayerAddress) + case layers.DHCPv6OptIAAddr: + // Parse IA Address option to get valid-lifetime. + if len(opt.Data) >= 24 { + // Valid-lifetime is at offset 20-23 (4 bytes). + validLifetimeSecs := binary.BigEndian.Uint32(opt.Data[20:24]) + validUntil = time.Now().Add(time.Duration(validLifetimeSecs) * time.Second) + } } - prevAddrs := *vif - vif.IPv6Addrs = append(vif.IPv6Addrs, dhcpv6.LinkAddr) - newAddrs := *vif - addrUpdates = append(addrUpdates, VIFAddrsUpdate{ - Prev: prevAddrs, - New: newAddrs, - }) - return addrUpdates, true } - return nil, true + if vif == nil { + return nil, true + } + update := vif.addIP(dhcpv6.LinkAddr, types.AddressSourceExternalDHCP, + validUntil) + if update != nil { + addrUpdates = append(addrUpdates, *update) + } + return addrUpdates, true } // Process captured ICMPv6 NS packet for a switched network instance to learn @@ -671,10 +677,10 @@ func (lc *LinuxCollector) processDHCPPacket( // by processing this packet. func (lc *LinuxCollector) processDADProbe( niInfo *niInfo, packet gopacket.Packet) (addrUpdates []VIFAddrsUpdate) { - var vif *VIFAddrs + var vif *vifInfo if etherLayer := packet.Layer(layers.LayerTypeEthernet); etherLayer != nil { etherPkt := etherLayer.(*layers.Ethernet) - vif = niInfo.vifs.LookupByGuestMAC(etherPkt.SrcMAC) + vif = niInfo.lookupVIFByGuestMAC(etherPkt.SrcMAC) } if vif == nil { return @@ -695,16 +701,15 @@ func (lc *LinuxCollector) processDADProbe( return } icmp6 := icmp6Layer.(*layers.ICMPv6NeighborSolicitation) - if isAddrPresent(vif.IPv6Addrs, icmp6.TargetAddress) { - return nil + // DAD is not performed periodically, therefore we should not remove the IP address + // from the list after some time duration just because we have not seen another + // ICMPv6 NS packet. + undefinedValidity := time.Time{} + update := vif.addIP(icmp6.TargetAddress, types.AddressSourceSLAAC, + undefinedValidity) + if update != nil { + addrUpdates = append(addrUpdates, *update) } - prevAddrs := *vif - vif.IPv6Addrs = append(vif.IPv6Addrs, icmp6.TargetAddress) - newAddrs := *vif - addrUpdates = append(addrUpdates, VIFAddrsUpdate{ - Prev: prevAddrs, - New: newAddrs, - }) return addrUpdates } @@ -765,11 +770,11 @@ func (lc *LinuxCollector) processDNSPacketInfo( } } -func isAddrPresent(list []net.IP, addr net.IP) bool { - for i := 0; i < len(list); i++ { - if addr.Equal(list[i]) { - return true +func lookupVIFByIP(ip net.IP, vifs []*vifInfo) *vifInfo { + for _, vif := range vifs { + if vif.hasIP(ip) { + return vif } } - return false + return nil } diff --git a/pkg/pillar/nistate/linux_iptables.go b/pkg/pillar/nistate/linux_iptables.go index 632e02698c..868e782c7f 100644 --- a/pkg/pillar/nistate/linux_iptables.go +++ b/pkg/pillar/nistate/linux_iptables.go @@ -56,7 +56,7 @@ func (lc *LinuxCollector) fetchIptablesCounters() []aclCounters { for _, niState := range lc.nis { for _, niVif := range niState.vifs { vifs = append(vifs, vif{ - ifName: niVif.VIF.HostIfName, + ifName: niVif.HostIfName, bridge: niState.bridge.BrIfName, }) } diff --git a/pkg/pillar/nistate/statecollector.go b/pkg/pillar/nistate/statecollector.go index 3a11066fce..2c521a074d 100644 --- a/pkg/pillar/nistate/statecollector.go +++ b/pkg/pillar/nistate/statecollector.go @@ -10,7 +10,6 @@ package nistate import ( - "bytes" "fmt" "net" @@ -40,7 +39,7 @@ type Collector interface { // Note that not every change in network instance config is supported. For example, // network instance type (switch / local) cannot change. UpdateCollectingForNI( - niConfig types.NetworkInstanceConfig, vifs []AppVIF) error + niConfig types.NetworkInstanceConfig, vifs []AppVIF, enableArpSnoop bool) error // StopCollectingForNI : stop collecting state data for network instance. // It is called by zedrouter whenever a network instance is about to be deleted. @@ -135,27 +134,6 @@ func (vifs VIFAddrsList) LookupByAdapterName( return nil } -// LookupByGuestMAC : Lookup VIF by the MAC address of the guest interface. -func (vifs VIFAddrsList) LookupByGuestMAC(mac net.HardwareAddr) *VIFAddrs { - for i := range vifs { - if bytes.Equal(vifs[i].VIF.GuestIfMAC, mac) { - return &vifs[i] - } - } - return nil -} - -// LookupByIP : Lookup VIF by the IP address assigned to the guest interface. -// Returns first match. -func (vifs VIFAddrsList) LookupByIP(ip net.IP) *VIFAddrs { - for i := range vifs { - if vifs[i].HasIP(ip) { - return &vifs[i] - } - } - return nil -} - // VIFAddrs lists IP addresses assigned to a VIF on the guest side // (inside the app). This is provided to zedrouter by Collector. type VIFAddrs struct { @@ -163,19 +141,6 @@ type VIFAddrs struct { VIF AppVIF } -// HasIP returns true if the given IP address is assigned to this VIF. -func (vif VIFAddrs) HasIP(ip net.IP) bool { - if ip.Equal(vif.IPv4Addr) { - return true - } - for _, ipv6Addr := range vif.IPv6Addrs { - if ip.Equal(ipv6Addr) { - return true - } - } - return false -} - // VIFAddrsUpdate describes a change in the address assignment for a single VIF. // Prev.VIF and New.VIF are always the same. // This is provided to zedrouter by Collector. diff --git a/pkg/pillar/types/zedroutertypes.go b/pkg/pillar/types/zedroutertypes.go index 87c322e356..66b60b1c81 100644 --- a/pkg/pillar/types/zedroutertypes.go +++ b/pkg/pillar/types/zedroutertypes.go @@ -349,10 +349,9 @@ type AppNetAdapterStatus struct { AppNetAdapterConfig VifInfo BridgeMac net.HardwareAddr - BridgeIPAddr net.IP // The address for DNS/DHCP service in zedrouter - AllocatedIPv4Addr net.IP // Assigned to domU - AllocatedIPv6List []net.IP // IPv6 addresses assigned to domU - IPv4Assigned bool // Set to true once DHCP has assigned it to domU + BridgeIPAddr net.IP // The address for DNS/DHCP service in zedrouter + AssignedAddresses AssignedAddrs // IPv4 and IPv6 addresses assigned to domU + IPv4Assigned bool // Set to true once DHCP has assigned it to domU IPAddrMisMatch bool HostName string } @@ -380,8 +379,48 @@ type NetworkInstanceInfo struct { // AssignedAddrs : IP addresses assigned to application network adapter. type AssignedAddrs struct { - IPv4Addr net.IP - IPv6Addrs []net.IP + IPv4Addrs []AssignedAddr + IPv6Addrs []AssignedAddr +} + +// GetInternallyLeasedIPv4Addr returns IPv4 address leased by EVE using +// an internally run DHCP server. +func (aa AssignedAddrs) GetInternallyLeasedIPv4Addr() net.IP { + for _, addr := range aa.IPv4Addrs { + if addr.AssignedBy == AddressSourceInternalDHCP { + return addr.Address + } + } + return nil +} + +// AddressSource determines the source of an IP address assigned to an app VIF. +// Values are power of two and therefore can be used with a bit mask. +type AddressSource uint8 + +const ( + // AddressSourceUndefined : IP address source is not defined + AddressSourceUndefined AddressSource = 0 + // AddressSourceEVEInternal : IP address is used only internally by EVE + // (i.e. inside dom0). + AddressSourceEVEInternal AddressSource = 1 << iota + // AddressSourceInternalDHCP : IP address is leased to an app by an internal DHCP server + // run by EVE. + AddressSourceInternalDHCP + // AddressSourceExternalDHCP : IP address is leased to an app by an external DHCP server. + AddressSourceExternalDHCP + // AddressSourceSLAAC : Stateless Address Autoconfiguration (SLAAC) was used by the client + // to generate a unique IPv6 address. + AddressSourceSLAAC + // AddressSourceStatic : IP address is assigned to an app statically + // (using e.g. cloud-init). + AddressSourceStatic +) + +// AssignedAddr : IP address assigned to an application interface (on the guest side). +type AssignedAddr struct { + Address net.IP + AssignedBy AddressSource } // VifNameMac : name and MAC address assigned to app VIF. @@ -874,11 +913,13 @@ func (status NetworkInstanceStatus) LogKey() string { // IsIpAssigned returns true if the given IP address is assigned to any app VIF. func (status *NetworkInstanceStatus) IsIpAssigned(ip net.IP) bool { for _, assignments := range status.IPAssignments { - if ip.Equal(assignments.IPv4Addr) { - return true + for _, assignedIP := range assignments.IPv4Addrs { + if ip.Equal(assignedIP.Address) { + return true + } } - for _, nip := range assignments.IPv6Addrs { - if ip.Equal(nip) { + for _, assignedIP := range assignments.IPv6Addrs { + if ip.Equal(assignedIP.Address) { return true } }