| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package main |
| |
| import ( |
| "context" |
| "errors" |
| "fmt" |
| "log" |
| "sort" |
| "strings" |
| "sync" |
| |
| "fidl/fuchsia/devicesettings" |
| nsfidl "fidl/fuchsia/netstack" |
| |
| "netstack/dns" |
| "netstack/filter" |
| "netstack/link/eth" |
| "netstack/link/stats" |
| "netstack/netiface" |
| |
| "github.com/google/netstack/dhcp" |
| "github.com/google/netstack/tcpip" |
| "github.com/google/netstack/tcpip/header" |
| "github.com/google/netstack/tcpip/link/loopback" |
| "github.com/google/netstack/tcpip/link/sniffer" |
| "github.com/google/netstack/tcpip/network/arp" |
| "github.com/google/netstack/tcpip/network/ipv4" |
| "github.com/google/netstack/tcpip/network/ipv6" |
| "github.com/google/netstack/tcpip/stack" |
| ) |
| |
| const ( |
| deviceSettingsManagerNodenameKey = "DeviceName" |
| defaultNodename = "fuchsia-unset-device-name" |
| ) |
| |
| // A netstack tracks all of the running state of the network stack. |
| type netstack struct { |
| arena *eth.Arena |
| stack *stack.Stack |
| socketServer *socketServer |
| |
| deviceSettings *devicesettings.DeviceSettingsManagerInterface |
| dnsClient *dns.Client |
| |
| mu sync.Mutex |
| nodename string |
| ifStates map[tcpip.NICID]*ifState |
| |
| countNIC tcpip.NICID |
| |
| filter *filter.Filter |
| } |
| |
| type dhcpState struct { |
| client *dhcp.Client |
| ctx context.Context |
| cancel context.CancelFunc |
| enabled bool |
| } |
| |
| // Each ifState tracks the state of a network interface. |
| type ifState struct { |
| ns *netstack |
| ctx context.Context |
| cancel context.CancelFunc |
| eth *eth.Client |
| state eth.State |
| dhcpState |
| |
| // guarded by ns.mu |
| // NIC is defined in //garnet/go/src/netstack/netiface/netiface.go |
| // TODO(porce): Consider replacement with //third_party/netstack/tcpip/stack/stack.go |
| nic *netiface.NIC |
| |
| // LinkEndpoint responsible to track traffic statistics |
| statsEP stats.StatsEndpoint |
| } |
| |
| func defaultRouteTable(nicid tcpip.NICID, gateway tcpip.Address) []tcpip.Route { |
| return []tcpip.Route{ |
| { |
| Destination: tcpip.Address(strings.Repeat("\x00", 4)), |
| Mask: tcpip.Address(strings.Repeat("\x00", 4)), |
| Gateway: gateway, |
| NIC: nicid, |
| }, |
| { |
| Destination: tcpip.Address(strings.Repeat("\x00", 16)), |
| Mask: tcpip.Address(strings.Repeat("\x00", 16)), |
| NIC: nicid, |
| }, |
| } |
| } |
| |
| func subnetRoute(addr tcpip.Address, mask tcpip.AddressMask, nicid tcpip.NICID) tcpip.Route { |
| return tcpip.Route{ |
| Destination: applyMask(addr, mask), |
| Mask: tcpip.Address(mask), |
| Gateway: tcpip.Address(""), |
| NIC: nicid, |
| } |
| } |
| |
| func applyMask(addr tcpip.Address, mask tcpip.AddressMask) tcpip.Address { |
| if len(addr) != len(mask) { |
| return "" |
| } |
| subnet := []byte(addr) |
| for i := 0; i < len(subnet); i++ { |
| subnet[i] &= mask[i] |
| } |
| return tcpip.Address(subnet) |
| } |
| |
| func (ns *netstack) removeInterfaceAddress(nic tcpip.NICID, protocol tcpip.NetworkProtocolNumber, addr tcpip.Address, prefixLen uint8) error { |
| sn, err := toSubnet(addr, prefixLen) |
| |
| if err != nil { |
| return fmt.Errorf("Error parsing subnet format for NIC ID %d, error: %s", nic, err) |
| } |
| |
| hasSubnet, tcpipErr := ns.stack.ContainsSubnet(nic, sn) |
| if tcpipErr != nil { |
| return errors.New(fmt.Sprintf("Error finding subnet %s for NIC ID %d, error: %s", sn, nic, tcpipErr)) |
| } |
| |
| if hasSubnet { |
| tcpipErr = ns.stack.RemoveSubnet(nic, sn) |
| if tcpipErr != nil { |
| return errors.New(fmt.Sprintf("Error removing subnet %s from NIC ID %d, error: %s", sn, nic, tcpipErr)) |
| } |
| } else { |
| return errors.New(fmt.Sprintf("No such subnet %s for NIC ID %d", sn, nic)) |
| } |
| |
| tcpipErr = ns.stack.RemoveAddress(nic, addr) |
| if tcpipErr != nil { |
| return fmt.Errorf("Error removing address %s from NIC ID %d, error: %s", addr, nic, tcpipErr) |
| } |
| |
| ifs, ok := ns.ifStates[nic] |
| if !ok { |
| panic(fmt.Sprintf("Interface state table out of sync: NIC [%d] known to third_party/netstack not found in garnet/netstack", nic)) |
| } |
| |
| newAddr, newSubnet, err1 := ns.stack.GetMainNICAddress(nic, protocol) |
| netmask := newSubnet.Mask() |
| if netmask == "" { |
| addressSize := int(len(newAddr) * 8) |
| netmask = tcpip.CIDRMask(addressSize, addressSize) |
| } |
| ifs.staticAddressChanged(newAddr, netmask) |
| if err1 != nil { |
| return fmt.Errorf("Error querying NIC ID %d, error: %s", nic, err) |
| } |
| return nil |
| } |
| |
| func toSubnet(address tcpip.Address, prefixLen uint8) (tcpip.Subnet, error) { |
| m := tcpip.CIDRMask(int(prefixLen), int(len(address)*8)) |
| return tcpip.NewSubnet(address.Mask(m), m) |
| } |
| |
| func (ns *netstack) setInterfaceAddress(nic tcpip.NICID, protocol tcpip.NetworkProtocolNumber, addr tcpip.Address, prefixLen uint8) error { |
| |
| sn, err := toSubnet(addr, prefixLen) |
| if err != nil { |
| return fmt.Errorf("Error parsing subnet format for NIC ID %d, error: %s", nic, err) |
| } |
| tcpipErr := ns.stack.AddAddress(nic, protocol, addr) |
| if tcpipErr != nil { |
| return fmt.Errorf("Error adding address %s to NIC ID %d, error: %s", addr, nic, tcpipErr) |
| } |
| |
| tcpipErr = ns.stack.AddSubnet(nic, protocol, sn) |
| if tcpipErr != nil { |
| return errors.New(fmt.Sprintf("Error adding subnet %s to NIC ID %d, error: %s", sn, nic, tcpipErr)) |
| } |
| |
| ifs, ok := ns.ifStates[nic] |
| if !ok { |
| panic(fmt.Sprintf("Interface state table out of sync: NIC [%d] known to third_party/netstack not found in garnet/netstack", nic)) |
| } |
| |
| ifs.staticAddressChanged(addr, sn.Mask()) |
| return nil |
| } |
| |
| func (ifs *ifState) staticAddressChanged(newAddr tcpip.Address, netmask tcpip.AddressMask) { |
| ifs.ns.mu.Lock() |
| ifs.nic.Addr = newAddr |
| ifs.nic.Netmask = netmask |
| ifs.ns.mu.Unlock() |
| |
| OnInterfacesChanged() |
| } |
| |
| func (ifs *ifState) dhcpAcquired(oldAddr, newAddr tcpip.Address, config dhcp.Config) { |
| if oldAddr != "" && oldAddr != newAddr { |
| log.Printf("NIC %s: DHCP IP %s expired", ifs.nic.Name, oldAddr) |
| } |
| if config.Error != nil { |
| log.Printf("%v", config.Error) |
| return |
| } |
| if newAddr == "" { |
| log.Printf("NIC %s: DHCP could not acquire address", ifs.nic.Name) |
| return |
| } |
| log.Printf("NIC %s: DHCP acquired IP %s for %s", ifs.nic.Name, newAddr, config.LeaseLength) |
| log.Printf("NIC %s: Adding DNS servers: %v", ifs.nic.Name, config.DNS) |
| |
| // Update default route with new gateway. |
| ifs.ns.mu.Lock() |
| ifs.nic.Routes = defaultRouteTable(ifs.nic.ID, config.Gateway) |
| ifs.nic.Routes = append(ifs.nic.Routes, subnetRoute(newAddr, config.SubnetMask, ifs.nic.ID)) |
| ifs.nic.Netmask = config.SubnetMask |
| ifs.nic.Addr = newAddr |
| ifs.nic.DNSServers = config.DNS |
| ifs.ns.mu.Unlock() |
| |
| ifs.ns.stack.SetRouteTable(ifs.ns.flattenRouteTables()) |
| ifs.ns.dnsClient.SetRuntimeServers(ifs.ns.getRuntimeDNSServerRefs()) |
| |
| OnInterfacesChanged() |
| } |
| |
| func (ifs *ifState) setDHCPStatus(enabled bool) { |
| ifs.ns.mu.Lock() |
| defer ifs.ns.mu.Unlock() |
| d := &ifs.dhcpState |
| if enabled == d.enabled { |
| return |
| } |
| if enabled { |
| d.ctx, d.cancel = context.WithCancel(ifs.ctx) |
| d.client.Run(d.ctx) |
| } else if d.cancel != nil { |
| d.cancel() |
| } |
| d.enabled = enabled |
| } |
| |
| func (ifs *ifState) stateChange(s eth.State) { |
| switch s { |
| case eth.StateDown: |
| ifs.onEthStop() |
| case eth.StateClosed: |
| ifs.onEthStop() |
| ifs.ns.mu.Lock() |
| delete(ifs.ns.ifStates, ifs.nic.ID) |
| ifs.ns.mu.Unlock() |
| case eth.StateStarted: |
| // Only call `restarted` if we are not in the initial state (which means we're still starting). |
| if ifs.state != eth.StateUnknown { |
| ifs.onEthRestart() |
| } |
| } |
| ifs.state = s |
| // Note: This will fire again once DHCP succeeds. |
| OnInterfacesChanged() |
| } |
| |
| func (ifs *ifState) onEthRestart() { |
| log.Printf("NIC %s: restarting", ifs.nic.Name) |
| ifs.ns.mu.Lock() |
| ifs.ctx, ifs.cancel = context.WithCancel(context.Background()) |
| ifs.nic.Routes = defaultRouteTable(ifs.nic.ID, "") |
| ifs.ns.mu.Unlock() |
| |
| ifs.ns.stack.SetRouteTable(ifs.ns.flattenRouteTables()) |
| // TODO(NET-298): remove special case for WLAN after fix for multiple DHCP clients |
| // is enabled |
| ifs.setDHCPStatus(ifs.dhcpState.enabled || ifs.eth.Features&nsfidl.InterfaceFeatureWlan != 0) |
| } |
| |
| func (ifs *ifState) onEthStop() { |
| log.Printf("NIC %s: stopped", ifs.nic.Name) |
| if ifs.cancel != nil { |
| ifs.cancel() |
| } |
| if ifs.dhcpState.cancel != nil { |
| // TODO: consider remembering DHCP status |
| ifs.setDHCPStatus(false) |
| } |
| |
| // TODO(crawshaw): more cleanup to be done here: |
| // - remove link endpoint |
| // - reclaim NICID? |
| |
| ifs.ns.mu.Lock() |
| ifs.nic.Routes = nil |
| ifs.nic.Netmask = "" |
| ifs.nic.Addr = "" |
| ifs.nic.DNSServers = nil |
| ifs.ns.mu.Unlock() |
| |
| ifs.ns.stack.SetRouteTable(ifs.ns.flattenRouteTables()) |
| ifs.ns.dnsClient.SetRuntimeServers(ifs.ns.getRuntimeDNSServerRefs()) |
| } |
| |
| func (ns *netstack) flattenRouteTables() []tcpip.Route { |
| ns.mu.Lock() |
| defer ns.mu.Unlock() |
| |
| routes := make([]tcpip.Route, 0) |
| nics := make(map[tcpip.NICID]*netiface.NIC) |
| for _, ifs := range ns.ifStates { |
| routes = append(routes, ifs.nic.Routes...) |
| nics[ifs.nic.ID] = ifs.nic |
| } |
| sort.Slice(routes, func(i, j int) bool { |
| return netiface.Less(&routes[i], &routes[j], nics) |
| }) |
| if debug2 { |
| for i, ifs := range ns.ifStates { |
| log.Printf("[%v] nicid: %v, addr: %v, routes: %v", |
| i, ifs.nic.ID, ifs.nic.Addr, ifs.nic.Routes) |
| } |
| } |
| |
| return routes |
| } |
| |
| // Return a slice of references to each NIC's DNS servers. |
| // The caller takes ownership of the returned slice. |
| func (ns *netstack) getRuntimeDNSServerRefs() []*[]tcpip.Address { |
| ns.mu.Lock() |
| defer ns.mu.Unlock() |
| |
| refs := make([]*[]tcpip.Address, 0, len(ns.ifStates)) |
| for _, ifs := range ns.ifStates { |
| refs = append(refs, &ifs.nic.DNSServers) |
| } |
| return refs |
| } |
| |
| func (ns *netstack) getDNSServers() []tcpip.Address { |
| ns.mu.Lock() |
| defer ns.mu.Unlock() |
| |
| defaultServers := ns.dnsClient.GetDefaultServers() |
| uniqServers := make(map[tcpip.Address]struct{}) |
| for _, ifs := range ns.ifStates { |
| for _, server := range ifs.nic.DNSServers { |
| uniqServers[server] = struct{}{} |
| } |
| } |
| |
| out := make([]tcpip.Address, 0, len(defaultServers)+len(uniqServers)) |
| out = append(out, defaultServers...) |
| for server := range uniqServers { |
| out = append(out, server) |
| } |
| return out |
| } |
| |
| func (ns *netstack) addLoopback() error { |
| const nicid = 1 |
| ctx, cancel := context.WithCancel(context.Background()) |
| nic := &netiface.NIC{ |
| ID: nicid, |
| Addr: header.IPv4Loopback, |
| Netmask: tcpip.AddressMask(strings.Repeat("\xff", 4)), |
| Features: nsfidl.InterfaceFeatureLoopback, |
| Routes: []tcpip.Route{ |
| { |
| Destination: header.IPv4Loopback, |
| Mask: tcpip.Address(strings.Repeat("\xff", 4)), |
| NIC: nicid, |
| }, |
| { |
| Destination: header.IPv6Loopback, |
| Mask: tcpip.Address(strings.Repeat("\xff", 16)), |
| NIC: nicid, |
| }, |
| }, |
| } |
| |
| setNICName(nic) |
| |
| ifs := &ifState{ |
| ns: ns, |
| ctx: ctx, |
| cancel: cancel, |
| nic: nic, |
| state: eth.StateStarted, |
| } |
| ifs.statsEP.Nic = ifs.nic |
| |
| ns.mu.Lock() |
| if len(ns.ifStates) > 0 { |
| ns.mu.Unlock() |
| return fmt.Errorf("loopback: other interfaces already registered") |
| } |
| ns.ifStates[nicid] = ifs |
| ns.countNIC++ |
| ns.mu.Unlock() |
| |
| linkID := loopback.New() |
| if debug2 { |
| linkID = sniffer.New(linkID) |
| } |
| linkID = ifs.statsEP.Wrap(linkID) |
| |
| if err := ns.stack.CreateNIC(nicid, linkID); err != nil { |
| return fmt.Errorf("loopback: could not create interface: %v", err) |
| } |
| if err := ns.stack.AddAddress(nicid, ipv4.ProtocolNumber, header.IPv4Loopback); err != nil { |
| return fmt.Errorf("loopback: adding ipv4 address failed: %v", err) |
| } |
| if err := ns.stack.AddAddress(nicid, ipv6.ProtocolNumber, header.IPv6Loopback); err != nil { |
| return fmt.Errorf("loopback: adding ipv6 address failed: %v", err) |
| } |
| |
| ns.stack.SetRouteTable(ns.flattenRouteTables()) |
| |
| return nil |
| } |
| |
| func (ns *netstack) Bridge(nics []tcpip.NICID) error { |
| // TODO(stijlist): save bridge in netstack state as NetInterface |
| // TODO(stijlist): initialize bridge context.Context & cancelFunc |
| b, err := ns.stack.Bridge(nics) |
| if err != nil { |
| return fmt.Errorf("%s", err) |
| } |
| |
| for _, nicid := range nics { |
| nic, ok := ns.ifStates[nicid] |
| if !ok { |
| panic("NIC known by netstack not in interface table") |
| } |
| nic.eth.SetPromiscuousMode(true) |
| } |
| |
| b.Enable() |
| return nil |
| } |
| |
| func (ns *netstack) addEth(path string) error { |
| ctx, cancel := context.WithCancel(context.Background()) |
| |
| ifs := &ifState{ |
| ns: ns, |
| ctx: ctx, |
| cancel: cancel, |
| nic: &netiface.NIC{}, |
| state: eth.StateUnknown, |
| } |
| ifs.statsEP.Nic = ifs.nic |
| |
| client, err := eth.NewClient("netstack", path, ns.arena, ifs.stateChange) |
| if err != nil { |
| return err |
| } |
| ifs.eth = client |
| ep := eth.NewLinkEndpoint(client) |
| linkID := stack.RegisterLinkEndpoint(ep) |
| linkAddr := ep.LinkAddress() |
| lladdr := ipv6.LinkLocalAddr(linkAddr) |
| |
| // LinkEndpoint chains: |
| // Put sniffer as close as the NIC. |
| if debug2 { |
| // A wrapper LinkEndpoint should encapsulate the underlying one, |
| // and manifest itself to 3rd party netstack. |
| linkID = sniffer.New(linkID) |
| } |
| |
| f := filter.New(ns.stack.PortManager) |
| linkID = filter.NewEndpoint(f, linkID) |
| ns.filter = f |
| |
| linkID = ifs.statsEP.Wrap(linkID) |
| |
| ns.mu.Lock() |
| ifs.nic.Ipv6addrs = []tcpip.Address{lladdr} |
| |
| nicid := ns.countNIC + 1 |
| firstNIC := nicid == 2 |
| ifs.nic.ID = nicid |
| ifs.nic.Features = client.Features |
| setNICName(ifs.nic) |
| |
| ifs.nic.Routes = defaultRouteTable(nicid, "") |
| ns.ifStates[nicid] = ifs |
| ns.countNIC++ |
| ns.mu.Unlock() |
| |
| log.Printf("NIC %s added using ethernet device %q", ifs.nic.Name, path) |
| |
| if err := ns.stack.CreateNIC(nicid, linkID); err != nil { |
| return fmt.Errorf("NIC %s: could not create NIC for %q: %v", ifs.nic.Name, path, err) |
| } |
| if err := ns.stack.AddAddress(nicid, arp.ProtocolNumber, arp.ProtocolAddress); err != nil { |
| return fmt.Errorf("NIC %s: adding arp address failed: %v", ifs.nic.Name, err) |
| } |
| if err := ns.stack.AddAddress(nicid, ipv6.ProtocolNumber, lladdr); err != nil { |
| return fmt.Errorf("NIC %s: adding link-local IPv6 %v failed: %v", ifs.nic.Name, lladdr, err) |
| } |
| snaddr := ipv6.SolicitedNodeAddr(lladdr) |
| if err := ns.stack.AddAddress(nicid, ipv6.ProtocolNumber, snaddr); err != nil { |
| return fmt.Errorf("NIC %s: adding solicited-node IPv6 %v (link-local IPv6 %v) failed: %v", ifs.nic.Name, snaddr, lladdr, err) |
| } |
| log.Printf("NIC %s: link-local IPv6: %v", ifs.nic.Name, lladdr) |
| |
| ifs.dhcpState.client = dhcp.NewClient(ns.stack, nicid, linkAddr, ifs.dhcpAcquired) |
| |
| // Add default route. This will get clobbered later when we get a DHCP response. |
| ns.stack.SetRouteTable(ns.flattenRouteTables()) |
| |
| // TODO(NET-298): Delete this condition after enabling multiple concurrent DHCP clients |
| // in third_party/netstack. |
| if client.Features&nsfidl.InterfaceFeatureWlan != 0 { |
| // WLAN: Upon 802.1X port open, the state change will ensue, which |
| // will invoke the DHCP Client. |
| return nil |
| } |
| |
| // TODO(stijlist): remove default DHCP policy for first NIC once policy manager |
| // sets DHCP status. |
| if firstNIC { |
| ifs.setDHCPStatus(true) |
| } |
| return nil |
| } |
| |
| func setNICName(nic *netiface.NIC) { |
| if nic.Features&nsfidl.InterfaceFeatureLoopback != 0 { |
| nic.Name = "lo" |
| } else if nic.Features&nsfidl.InterfaceFeatureWlan != 0 { |
| nic.Name = fmt.Sprintf("wlan%d", nic.ID) |
| } else { |
| nic.Name = fmt.Sprintf("en%d", nic.ID) |
| } |
| } |