| // 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 netstack |
| |
| import ( |
| "context" |
| "crypto/rand" |
| "encoding/base64" |
| "flag" |
| "fmt" |
| "log" |
| "os" |
| "reflect" |
| "runtime" |
| "strconv" |
| "sync" |
| "sync/atomic" |
| "syscall/zx" |
| "syscall/zx/fidl" |
| "time" |
| |
| appcontext "app/context" |
| "syslog" |
| |
| "netstack/connectivity" |
| "netstack/dns" |
| "netstack/filter" |
| "netstack/link/eth" |
| networking_metrics "networking_metrics_golib" |
| |
| "fidl/fuchsia/cobalt" |
| "fidl/fuchsia/device" |
| inspect "fidl/fuchsia/inspect/deprecated" |
| "fidl/fuchsia/net" |
| "fidl/fuchsia/net/stack" |
| "fidl/fuchsia/netstack" |
| "fidl/fuchsia/posix/socket" |
| "fidl/fuchsia/stash" |
| |
| "gvisor.dev/gvisor/pkg/tcpip" |
| "gvisor.dev/gvisor/pkg/tcpip/header" |
| "gvisor.dev/gvisor/pkg/tcpip/link/sniffer" |
| "gvisor.dev/gvisor/pkg/tcpip/network/arp" |
| "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" |
| "gvisor.dev/gvisor/pkg/tcpip/network/ipv6" |
| tcpipstack "gvisor.dev/gvisor/pkg/tcpip/stack" |
| "gvisor.dev/gvisor/pkg/tcpip/transport/icmp" |
| "gvisor.dev/gvisor/pkg/tcpip/transport/tcp" |
| "gvisor.dev/gvisor/pkg/tcpip/transport/udp" |
| ) |
| |
| const ( |
| // stashStoreIdentificationName is the name used to identify this (netstack) |
| // component to the fuchsia.stash.SecureStore service. |
| stashStoreIdentificationName = "netstack-stash" |
| |
| // opaqueIIDSecretKeyName is the name of the key used to access the secret key |
| // for opaque IIDs from the secure stash store. |
| opaqueIIDSecretKeyName = "opaque-iid-secret-key" |
| |
| // dadTransmits is the number of consecutive NDP Neighbor Solicitation |
| // messages sent while performing Duplicate Address Detection on a IPv6 |
| // tentative address. |
| // |
| // As per RFC 4862 section 5.1, 1 is the default number of messages to send. |
| dadTransmits = 1 |
| |
| // dadRetransmitTimer is the time between retransmissions of NDP Neighbor |
| // Solicitation messages to a neighbor. |
| // |
| // As per RFC 4861 section 10, 1s is the default time between retransmissions. |
| dadRetransmitTimer = time.Second |
| |
| // maxRtrSolicitations is the maximum number of Router Solicitation messages |
| // to send when a NIC becomes enabled. |
| // |
| // As per RFC 4861 section 10, 3 is the default number of messages. |
| maxRtrSolicitations = 3 |
| |
| // rtrSolicitationInterval is the amount of time between sending Router |
| // Solicitation messages. |
| // |
| // As per RFC 4861 section 10, 4s is the default time between transmissions. |
| rtrSolicitationInterval = 4 * time.Second |
| |
| // maxRtrSolicitationDelay is the maximum amount of time to wait before |
| // sending the first Router Solicitation message. |
| // |
| // As per RFC 4861 section 10, 1s is the default maximum time to wait. |
| maxRtrSolicitationDelay = time.Second |
| ) |
| |
| type bindingSetCounterStat struct { |
| bindingSets []*fidl.BindingSet |
| } |
| |
| func (s *bindingSetCounterStat) Value() uint64 { |
| var sum int |
| for _, s := range s.bindingSets { |
| sum += s.Size() |
| } |
| return uint64(sum) |
| } |
| |
| type atomicBool uint32 |
| |
| // IsBoolFlag implements flag.boolFlag.IsBoolFlag. |
| // |
| // See the flag.Value documentation for more information. |
| func (*atomicBool) IsBoolFlag() bool { |
| return true |
| } |
| |
| // Set implements flag.Value.Set. |
| func (a *atomicBool) Set(s string) error { |
| v, err := strconv.ParseBool(s) |
| if err != nil { |
| return err |
| } |
| var val uint32 |
| if v { |
| val = 1 |
| } |
| atomic.StoreUint32((*uint32)(a), val) |
| return nil |
| } |
| |
| // String implements flag.Value.String. |
| func (a *atomicBool) String() string { |
| return strconv.FormatBool(atomic.LoadUint32((*uint32)(a)) != 0) |
| } |
| |
| func init() { |
| // As of this writing the default is 1. |
| atomic.StoreUint32(&sniffer.LogPackets, 0) |
| } |
| |
| func Main() { |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
| |
| logLevel := syslog.InfoLevel |
| |
| flags := flag.NewFlagSet(os.Args[0], flag.ContinueOnError) |
| flags.Var((*atomicBool)(&sniffer.LogPackets), "log-packets", "Enable packet logging") |
| flags.Var(&logLevel, "verbosity", "Set the logging verbosity") |
| if err := flags.Parse(os.Args[1:]); err != nil { |
| panic(err) |
| } |
| |
| appCtx := appcontext.CreateFromStartupInfo() |
| |
| l, err := syslog.NewLogger(syslog.LogInitOptions{ |
| LogLevel: logLevel, |
| MinSeverityForFileAndLineInfo: syslog.InfoLevel, |
| Tags: []string{"netstack"}, |
| }) |
| if err != nil { |
| panic(err) |
| } |
| syslog.SetDefaultLogger(l) |
| log.SetOutput(&syslog.Writer{Logger: l}) |
| log.SetFlags(log.Lshortfile) |
| |
| secretKeyForOpaqueIID, err := getSecretKeyForOpaqueIID(appCtx) |
| if err != nil { |
| panic(fmt.Sprintf("failed to get secret key for opaque IIDs: %s", err)) |
| } |
| |
| stk := tcpipstack.New(tcpipstack.Options{ |
| NetworkProtocols: []tcpipstack.NetworkProtocol{ |
| arp.NewProtocol(), |
| ipv4.NewProtocol(), |
| ipv6.NewProtocol(), |
| }, |
| TransportProtocols: []tcpipstack.TransportProtocol{ |
| icmp.NewProtocol4(), |
| icmp.NewProtocol6(), |
| tcp.NewProtocol(), |
| udp.NewProtocol(), |
| }, |
| HandleLocal: true, |
| |
| // TODO(b/150455090): Re-enable the below configuations. |
| //NDPConfigs: tcpipstack.NDPConfigurations{ |
| // DupAddrDetectTransmits: dadTransmits, |
| // RetransmitTimer: dadRetransmitTimer, |
| // MaxRtrSolicitations: maxRtrSolicitations, |
| // RtrSolicitationInterval: rtrSolicitationInterval, |
| // MaxRtrSolicitationDelay: maxRtrSolicitationDelay, |
| // HandleRAs: true, |
| // DiscoverDefaultRouters: true, |
| // DiscoverOnLinkPrefixes: true, |
| // AutoGenGlobalAddresses: true, |
| //}, |
| //NDPDisp: ndpDisp, |
| //AutoGenIPv6LinkLocal: true, |
| |
| // Raw sockets are typically used for implementing custom protocols. We intend |
| // to support custom protocols through structured FIDL APIs in the future, so |
| // disable raw sockets to prevent them from accidentally becoming load-bearing. |
| RawFactory: nil, |
| OpaqueIIDOpts: tcpipstack.OpaqueInterfaceIdentifierOptions{ |
| NICNameFromID: func(nicID tcpip.NICID, nicName string) string { |
| // As of writing, Netstack creates NICs with names so we return the name |
| // the NIC was created with. Just in case, we have a default NIC name |
| // format for NICs that were not created with a name. |
| if nicName != "" { |
| return nicName |
| } |
| return fmt.Sprintf("opaqueIIDNIC%d", nicID) |
| }, |
| SecretKey: secretKeyForOpaqueIID, |
| }, |
| }) |
| if err := stk.SetTransportProtocolOption(tcp.ProtocolNumber, tcp.SACKEnabled(true)); err != nil { |
| syslog.Fatalf("method SetTransportProtocolOption(%v, tcp.SACKEnabled(true)) failed: %v", tcp.ProtocolNumber, err) |
| } |
| if err := stk.SetTransportProtocolOption(tcp.ProtocolNumber, tcp.DelayEnabled(true)); err != nil { |
| syslog.Fatalf("method SetTransportProtocolOption(%v, tcp.DelayEnabled(true)) failed: %v", tcp.ProtocolNumber, err) |
| } |
| |
| arena, err := eth.NewArena() |
| if err != nil { |
| syslog.Fatalf("ethernet: %s", err) |
| } |
| |
| req, np, err := device.NewNameProviderInterfaceRequest() |
| if err != nil { |
| syslog.Fatalf("could not connect to device name provider service: %s", err) |
| } |
| appCtx.ConnectToEnvService(req) |
| |
| ns := &Netstack{ |
| arena: arena, |
| dnsClient: dns.NewClient(stk), |
| nameProvider: np, |
| stack: stk, |
| } |
| |
| if err := ns.addLoopback(); err != nil { |
| syslog.Fatalf("loopback: %s", err) |
| } |
| |
| var posixSocketProviderService socket.ProviderService |
| |
| socketProviderImpl := providerImpl{ns: ns} |
| ns.stats = stats{ |
| Stats: stk.Stats(), |
| SocketCount: bindingSetCounterStat{bindingSets: []*fidl.BindingSet{ |
| &socketProviderImpl.datagramSocketService.BindingSet, |
| &socketProviderImpl.streamSocketService.BindingSet, |
| }}, |
| } |
| |
| var inspectService inspect.InspectService |
| appCtx.OutgoingService.AddDiagnostics("counters", &appcontext.DirectoryWrapper{ |
| Directory: &inspectDirectory{ |
| asService: (&inspectImpl{ |
| inner: &statCounterInspectImpl{ |
| name: "Networking Stat Counters", |
| value: reflect.ValueOf(ns.stats), |
| }, |
| service: &inspectService, |
| }).asService, |
| }, |
| }) |
| appCtx.OutgoingService.AddDiagnostics("interfaces", &appcontext.DirectoryWrapper{ |
| Directory: &inspectDirectory{ |
| // asService is late-bound so that each call retrieves fresh NIC info. |
| asService: func() *appcontext.Service { |
| return (&inspectImpl{ |
| inner: &nicInfoMapInspectImpl{value: ns.getIfStateInfo(stk.NICInfo())}, |
| service: &inspectService, |
| }).asService() |
| }, |
| }, |
| }) |
| |
| appCtx.OutgoingService.AddDiagnostics("sockets", &appcontext.DirectoryWrapper{ |
| Directory: &inspectDirectory{ |
| asService: (&inspectImpl{ |
| inner: &socketInfoMapInspectImpl{ |
| value: &ns.endpoints, |
| }, |
| service: &inspectService, |
| }).asService, |
| }, |
| }) |
| |
| appCtx.OutgoingService.AddService( |
| netstack.NetstackName, |
| &netstack.NetstackStub{Impl: &netstackImpl{ |
| ns: ns, |
| }}, |
| func(s fidl.Stub, c zx.Channel) error { |
| k, err := ns.netstackService.BindingSet.Add(s, c, nil) |
| if err != nil { |
| syslog.Fatalf("%v", err) |
| } |
| // Send a synthetic InterfacesChanged event to each client when they join |
| // Prevents clients from having to race GetInterfaces / InterfacesChanged. |
| if p, ok := ns.netstackService.EventProxyFor(k); ok { |
| interfaces2 := ns.getNetInterfaces2() |
| interfaces := interfaces2ListToInterfacesList(interfaces2) |
| |
| if err := p.OnInterfacesChanged(interfaces); err != nil { |
| syslog.Warnf("OnInterfacesChanged failed: %v", err) |
| } |
| } |
| return nil |
| }, |
| ) |
| |
| var dnsService netstack.ResolverAdminService |
| appCtx.OutgoingService.AddService( |
| netstack.ResolverAdminName, |
| &netstack.ResolverAdminStub{Impl: &dnsImpl{ns: ns}}, |
| func(s fidl.Stub, c zx.Channel) error { |
| _, err := dnsService.BindingSet.Add(s, c, nil) |
| return err |
| }, |
| ) |
| |
| var stackService stack.StackService |
| appCtx.OutgoingService.AddService( |
| stack.StackName, |
| &stack.StackStub{Impl: &stackImpl{ |
| ns: ns, |
| }}, |
| func(s fidl.Stub, c zx.Channel) error { |
| _, err := stackService.BindingSet.Add(s, c, nil) |
| return err |
| }, |
| ) |
| |
| var logService stack.LogService |
| appCtx.OutgoingService.AddService( |
| stack.LogName, |
| &stack.LogStub{Impl: &logImpl{logger: l}}, |
| func(s fidl.Stub, c zx.Channel) error { |
| _, err := logService.BindingSet.Add(s, c, nil) |
| return err |
| }) |
| |
| var nameLookupService net.NameLookupService |
| appCtx.OutgoingService.AddService( |
| net.NameLookupName, |
| &net.NameLookupStub{Impl: &nameLookupImpl{dnsClient: ns.dnsClient}}, |
| func(s fidl.Stub, c zx.Channel) error { |
| _, err := nameLookupService.BindingSet.Add(s, c, nil) |
| return err |
| }, |
| ) |
| |
| appCtx.OutgoingService.AddService( |
| socket.ProviderName, |
| &socket.ProviderStub{Impl: &socketProviderImpl}, |
| func(s fidl.Stub, c zx.Channel) error { |
| _, err := posixSocketProviderService.BindingSet.Add(s, c, nil) |
| return err |
| }, |
| ) |
| |
| if cobaltLogger, err := connectCobaltLogger(appCtx); err != nil { |
| syslog.Warnf("could not initialize cobalt client: %s", err) |
| } else { |
| go func() { |
| if err := runCobaltClient(ctx, cobaltLogger, &ns.stats, ns.stack); err != nil { |
| syslog.Errorf("cobalt client exited unexpectedly: %s", err) |
| } |
| }() |
| } |
| |
| if err := connectivity.AddOutgoingService(appCtx); err != nil { |
| syslog.Fatalf("%v", err) |
| } |
| |
| f := filter.New(stk.PortManager) |
| if err := filter.AddOutgoingService(appCtx, f); err != nil { |
| syslog.Fatalf("%v", err) |
| } |
| ns.filter = f |
| |
| go pprofListen() |
| |
| var wg sync.WaitGroup |
| for i := 0; i < runtime.NumCPU(); i++ { |
| wg.Add(1) |
| go func() { |
| fidl.Serve() |
| wg.Done() |
| }() |
| } |
| wg.Wait() |
| } |
| |
| // newSecretKeyForOpaqueIID returns a new secret key for opaque IID generation. |
| func newSecretKeyForOpaqueIID() ([]byte, error) { |
| var secretKeyBuf [header.OpaqueIIDSecretKeyMinBytes]byte |
| secretKey := secretKeyBuf[:] |
| n, err := rand.Read(secretKey) |
| if err != nil { |
| return nil, fmt.Errorf("failed to populate a new secret key for opaque IIDs: %s", err) |
| } |
| if n != header.OpaqueIIDSecretKeyMinBytes { |
| return nil, fmt.Errorf("failed to populate a new secret key for opaque IIDs: got rand.Read(_) = %d, want = %d", n, header.OpaqueIIDSecretKeyMinBytes) |
| } |
| return secretKey, nil |
| } |
| |
| // getSecretKeyForOpaqueIID gets a secret key for opaque IID generation from the |
| // secure stash store service, or attempts to create one. If the stash service |
| // is unavailable, a temporary secret key will be returned. |
| func getSecretKeyForOpaqueIID(appCtx *appcontext.Context) ([]byte, error) { |
| syslog.VLogf(syslog.DebugVerbosity, "getting or creating secret key for opaque IID from secure stash store") |
| |
| // Connect to the secure stash store service. |
| storeReq, store, err := stash.NewSecureStoreInterfaceRequest() |
| if err != nil { |
| syslog.Errorf("could not create the request to connect to the %s service: %s", stash.SecureStoreName, err) |
| return newSecretKeyForOpaqueIID() |
| } |
| defer store.Close() |
| appCtx.ConnectToEnvService(storeReq) |
| |
| // Use our secure stash. |
| if err := store.Identify(stashStoreIdentificationName); err != nil { |
| syslog.Errorf("failed to identify as %s to the secure stash store: %s", stashStoreIdentificationName, err) |
| return newSecretKeyForOpaqueIID() |
| } |
| storeAccessorReq, storeAccessor, err := stash.NewStoreAccessorInterfaceRequest() |
| if err != nil { |
| syslog.Errorf("could not create the secure stash store accessor request: %s", err) |
| return newSecretKeyForOpaqueIID() |
| } |
| defer storeAccessor.Close() |
| if err := store.CreateAccessor(false /* readOnly */, storeAccessorReq); err != nil { |
| syslog.Errorf("failed to create accessor to the secure stash store: %s", err) |
| return newSecretKeyForOpaqueIID() |
| } |
| |
| // Attempt to get the existing secret key. |
| opaqueIIDSecretKeyValue, err := storeAccessor.GetValue(opaqueIIDSecretKeyName) |
| if err != nil { |
| syslog.Errorf("failed to get opaque IID secret key from secure stash store: %s", err) |
| return newSecretKeyForOpaqueIID() |
| } |
| |
| // If a key exists, make sure it is valid before returning it. |
| // |
| // The value should be stored as a base64 encoded string. |
| // |
| // We use a string because stash.Value.Bytesval uses a fuchsia.mem.Buffer |
| // which uses a VMO (which uses memory in page size increments). This is |
| // wasteful as the key only uses 16 bytes. We base64 encode the string |
| // because stash.Value.Stringval expects an ascii string; the store returns an |
| // error when flushing the store with a stash.Value.Stringval of raw bytes. |
| if opaqueIIDSecretKeyValue != nil && opaqueIIDSecretKeyValue.Which() == stash.ValueStringval { |
| syslog.VLogf(syslog.DebugVerbosity, "found a secret key for opaque IIDs in the secure stash store") |
| |
| if secretKey, err := base64.StdEncoding.DecodeString(opaqueIIDSecretKeyValue.Stringval); err != nil { |
| syslog.Errorf("failed to decode the secret key string: %s", err) |
| } else if l := len(secretKey); l != header.OpaqueIIDSecretKeyMinBytes { |
| syslog.Errorf("invalid secret key for opaque IIDs; got length = %d, want = %d", l, header.OpaqueIIDSecretKeyMinBytes) |
| } else { |
| syslog.VLogf(syslog.DebugVerbosity, "using existing secret key for opaque IIDs") |
| return secretKey, nil |
| } |
| } |
| |
| // Generate a new secret key as we either do not have one or the one we have |
| // is invalid. |
| syslog.VLogf(syslog.DebugVerbosity, "generating a new secret key for opaque IIDs") |
| secretKey, err := newSecretKeyForOpaqueIID() |
| if err != nil { |
| return nil, err |
| } |
| |
| // Store the newly generated key to the secure stash store as a base64 |
| // encoded string. |
| if err := storeAccessor.SetValue(opaqueIIDSecretKeyName, stash.ValueWithStringval(base64.StdEncoding.EncodeToString(secretKey))); err != nil { |
| syslog.Errorf("failed to set newly created secret key for opaque IID to secure stash store: %s", err) |
| return secretKey, nil |
| } |
| flushResp, err := storeAccessor.Flush() |
| if err != nil { |
| syslog.Errorf("failed to flush secure stash store with updated secret key for opaque IID: %s", err) |
| return secretKey, nil |
| } |
| switch w := flushResp.Which(); w { |
| case stash.StoreAccessorFlushResultErr: |
| syslog.Errorf("got error response when flushing secure stash store with updated secret key for opaque IID: %s", flushResp.Err) |
| return secretKey, nil |
| |
| case stash.StoreAccessorFlushResultResponse: |
| syslog.VLogf(syslog.DebugVerbosity, "saved newly generated secret key for opaque IIDs to secure stash store") |
| return secretKey, nil |
| |
| default: |
| panic(fmt.Sprintf("unexpected store accessor flush result type: %d", w)) |
| } |
| } |
| |
| func connectCobaltLogger(ctx *appcontext.Context) (*cobalt.LoggerInterface, error) { |
| freq, cobaltLoggerFactory, err := cobalt.NewLoggerFactoryInterfaceRequest() |
| if err != nil { |
| return nil, fmt.Errorf("could not connect to cobalt logger factory service: %s", err) |
| } |
| ctx.ConnectToEnvService(freq) |
| lreq, cobaltLogger, err := cobalt.NewLoggerInterfaceRequest() |
| if err != nil { |
| return nil, fmt.Errorf("could not connect to cobalt logger service: %s", err) |
| } |
| result, err := cobaltLoggerFactory.CreateLoggerFromProjectId(networking_metrics.ProjectId, lreq) |
| if err != nil { |
| return nil, fmt.Errorf("CreateLoggerFromProjectId(%d, ...) = _, %s", networking_metrics.ProjectId, err) |
| } |
| if result != cobalt.StatusOk { |
| return nil, fmt.Errorf("could not create logger for project %s: result: %s", networking_metrics.ProjectName, result) |
| } |
| |
| return cobaltLogger, nil |
| } |