github.com/transparency-dev/armored-witness-applet@v0.1.1/trusted_applet/net.go (about)

     1  // Copyright 2022 The Armored Witness Applet authors. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package main
    16  
    17  import (
    18  	"context"
    19  	"crypto/sha256"
    20  	"encoding/binary"
    21  	"errors"
    22  	"fmt"
    23  	"net"
    24  	"net/http"
    25  	"time"
    26  
    27  	"gvisor.dev/gvisor/pkg/buffer"
    28  	"gvisor.dev/gvisor/pkg/tcpip"
    29  	"gvisor.dev/gvisor/pkg/tcpip/header"
    30  	"gvisor.dev/gvisor/pkg/tcpip/link/channel"
    31  	"gvisor.dev/gvisor/pkg/tcpip/network/arp"
    32  	"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
    33  	"gvisor.dev/gvisor/pkg/tcpip/stack"
    34  	"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
    35  	"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
    36  	"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
    37  	"k8s.io/klog/v2"
    38  
    39  	"github.com/beevik/ntp"
    40  	"github.com/transparency-dev/armored-witness-applet/third_party/dhcp"
    41  	"github.com/transparency-dev/armored-witness-os/api"
    42  	"go.mercari.io/go-dnscache"
    43  
    44  	"github.com/usbarmory/GoTEE/applet"
    45  	"github.com/usbarmory/GoTEE/syscall"
    46  	enet "github.com/usbarmory/imx-enet"
    47  )
    48  
    49  // default Trusted Applet network settings
    50  const (
    51  	DHCP            = true
    52  	IP              = "10.0.0.1"
    53  	Netmask         = "255.255.255.0"
    54  	Gateway         = "10.0.0.2"
    55  	DefaultResolver = "8.8.8.8:53"
    56  	DefaultNTP      = "time.google.com"
    57  
    58  	nicID = tcpip.NICID(1)
    59  
    60  	// Timeout for any http requests.
    61  	httpTimeout = 30 * time.Second
    62  
    63  	// DNS cache settings.
    64  	dnsUpdateFreq    = 1 * time.Minute
    65  	dnsUpdateTimeout = 5 * time.Second
    66  )
    67  
    68  // Trusted OS syscalls
    69  const (
    70  	RX   = 0x10000000
    71  	TX   = 0x10000001
    72  	FIQ  = 0x10000002
    73  	FREQ = 0x10000003
    74  )
    75  
    76  var (
    77  	iface *enet.Interface
    78  )
    79  
    80  func init() {
    81  	net.DefaultNS = []string{DefaultResolver}
    82  }
    83  
    84  // runDHCP starts the dhcp client.
    85  //
    86  // When an IP is successfully leased and configured on the interface, f is called with a context
    87  // which will become Done when the leased address expires. Callers can use this as a mechanism to
    88  // ensure that networking clients/services are only run while a leased IP is held.
    89  //
    90  // This function blocks until the passed-in ctx is Done.
    91  func runDHCP(ctx context.Context, nicID tcpip.NICID, clientID string, hostname string, f func(context.Context) error) {
    92  	// This context tracks the lifetime of the IP lease we get (if any) from the DHCP server.
    93  	// We'll only know what that lease is once we acquire the new IP, which happens inside
    94  	// the aquired func below.
    95  	var (
    96  		childCtx    context.Context
    97  		cancelChild context.CancelFunc
    98  	)
    99  	// fDone is used to ensure that we wait for the passed-in func f to complete before
   100  	// make changes to the network stack or attempt to rerun f when we've acquired a new lease.
   101  	fDone := make(chan bool, 1)
   102  	defer close(fDone)
   103  
   104  	// acquired handles our dhcp.Client events - acquiring, releasing, renewing DHCP leases.
   105  	acquired := func(oldAddr, newAddr tcpip.AddressWithPrefix, cfg dhcp.Config) {
   106  		klog.Infof("DHCPC: lease update - old: %v, new: %v", oldAddr.String(), newAddr.String())
   107  		// Handled renewals first, old and new addresses will be equivalent in this case.
   108  		// We may still have to reconfigure the networking stack, even though our assigned IP
   109  		// isn't changing, the DHCP server could have changed routing or DNS info.
   110  		if oldAddr.Address == newAddr.Address && oldAddr.PrefixLen == newAddr.PrefixLen {
   111  			klog.Infof("DHCPC: existing lease on %v renewed", newAddr.String())
   112  			// reconfigure network stuff in-case DNS or gateway routes have changed.
   113  			configureNetFromDHCP(newAddr, cfg)
   114  			// f should already be running, no need to interfere with it.
   115  			return
   116  		}
   117  
   118  		// If oldAddr is specified, then our lease on that address has experied - remove it
   119  		// from our stack.
   120  		if !oldAddr.Address.Unspecified() {
   121  			// Since we're changing our primary IP address we must tell f to exit,
   122  			// and wait for it to do so
   123  			cancelChild()
   124  			klog.Info("Waiting for child to complete...")
   125  			<-fDone
   126  
   127  			klog.Infof("DHCPC: Releasing %v", oldAddr.String())
   128  			if err := iface.Stack.RemoveAddress(nicID, oldAddr.Address); err != nil {
   129  				klog.Errorf("Failed to remove expired address from stack: %v", err)
   130  			}
   131  		}
   132  
   133  		// If newAddr is specified, then we've been granted a lease on a new IP address, so
   134  		// we'll configure our stack to use it, along with whatever routes and DNS info
   135  		// we've been sent.
   136  		if !newAddr.Address.Unspecified() {
   137  			klog.Infof("DHCPC: Acquired %v", newAddr.String())
   138  
   139  			newProtoAddr := tcpip.ProtocolAddress{
   140  				Protocol:          ipv4.ProtocolNumber,
   141  				AddressWithPrefix: newAddr,
   142  			}
   143  			if err := iface.Stack.AddProtocolAddress(nicID, newProtoAddr, stack.AddressProperties{PEB: stack.FirstPrimaryEndpoint}); err != nil {
   144  				klog.Errorf("Failed to add newly acquired address to stack: %v", err)
   145  			} else {
   146  				configureNetFromDHCP(newAddr, cfg)
   147  
   148  				// Set up a context we'll use to control f's execution lifetime.
   149  				// This will get canceled above if/when our IP lease expires.
   150  				childCtx, cancelChild = context.WithCancel(ctx)
   151  
   152  				// And execute f in its own goroutine so we don't block the dhcp.Client.
   153  				go func(childCtx context.Context) {
   154  					// Signal when we exit:
   155  					defer func() { fDone <- true }()
   156  
   157  					klog.Info("DHCP: running f")
   158  					for {
   159  						if err := f(childCtx); err != nil {
   160  							klog.Errorf("runDHCP f: %v", err)
   161  							if errors.Is(err, context.Canceled) {
   162  								break
   163  							}
   164  						}
   165  					}
   166  				}(childCtx)
   167  			}
   168  		} else {
   169  			klog.Infof("DHCPC: no address acquired")
   170  		}
   171  	}
   172  
   173  	// Start the DHCP client.
   174  	c := dhcp.NewClient(iface.Stack, nicID, iface.Link.LinkAddress(), clientID, hostname, 30*time.Second, time.Second, time.Second, acquired)
   175  	klog.Info("Starting DHCPClient...")
   176  	c.Run(ctx)
   177  }
   178  
   179  // configureNetFromDHCP sets up network related configuration, e.g. DNS servers,
   180  // gateway routes, etc. based on config received from the DHCP server.
   181  // Note that this function does not update the network stack's assigned IP address.
   182  func configureNetFromDHCP(newAddr tcpip.AddressWithPrefix, cfg dhcp.Config) {
   183  	if len(cfg.DNS) > 0 {
   184  		resolvers := []string{}
   185  		for _, r := range cfg.DNS {
   186  			resolver := fmt.Sprintf("%s:53", r.String())
   187  			resolvers = append(resolvers, resolver)
   188  		}
   189  		klog.Infof("DHCPC: Using DNS server(s) %v", resolvers)
   190  		net.DefaultNS = resolvers
   191  	}
   192  	// Set up routing for new address
   193  	// Start with the implicit route to local segment
   194  	table := []tcpip.Route{
   195  		{Destination: newAddr.Subnet(), NIC: nicID},
   196  	}
   197  	// add any additional routes from the DHCP server
   198  	if len(cfg.Router) > 0 {
   199  		for _, gw := range cfg.Router {
   200  			table = append(table, tcpip.Route{Destination: header.IPv4EmptySubnet, Gateway: gw, NIC: nicID})
   201  			klog.Infof("DHCPC: Using Gateway %v", gw)
   202  		}
   203  	}
   204  	iface.Stack.SetRouteTable(table)
   205  }
   206  
   207  // runNTP starts periodically attempting to sync the system time with NTP.
   208  // Returns a channel which become closed once we have obtained an initial time.
   209  func runNTP(ctx context.Context) chan bool {
   210  	if cfg.NTPServer == "" {
   211  		klog.Info("NTP disabled.")
   212  		return nil
   213  	}
   214  
   215  	r := make(chan bool)
   216  
   217  	go func(ctx context.Context) {
   218  		// i specifies the interval between checking in with the NTP server.
   219  		// Initially we'll check in more frequently until we have set a time.
   220  		i := time.Second * 10
   221  		for {
   222  			select {
   223  			case <-ctx.Done():
   224  				return
   225  			case <-time.After(i):
   226  			}
   227  
   228  			ip, err := net.DefaultResolver.LookupIP(ctx, "ip4", cfg.NTPServer)
   229  			if err != nil {
   230  				klog.Errorf("Failed to resolve NTP server %q: %v", DefaultNTP, err)
   231  				continue
   232  			}
   233  			ntpR, err := ntp.QueryWithOptions(
   234  				ip[0].String(),
   235  				ntp.QueryOptions{},
   236  			)
   237  			if err != nil {
   238  				klog.Errorf("Failed to get NTP time: %v", err)
   239  				continue
   240  			}
   241  			if err := ntpR.Validate(); err != nil {
   242  				klog.Errorf("got invalid time from NTP server: %v", err)
   243  				continue
   244  			}
   245  			applet.ARM.SetTimer(ntpR.Time.UnixNano())
   246  
   247  			// We've got some sort of sensible time set now, so check in with NTP
   248  			// much less frequently.
   249  			i = time.Hour
   250  			if r != nil {
   251  				// Signal that we've got an initial time.
   252  				close(r)
   253  				r = nil
   254  			}
   255  		}
   256  	}(ctx)
   257  
   258  	return r
   259  }
   260  
   261  func rxFromEth(buf []byte) int {
   262  	n := syscall.Read(RX, buf, uint(len(buf)))
   263  
   264  	if n == 0 || n > int(enet.MTU) {
   265  		return 0
   266  	}
   267  
   268  	return n
   269  }
   270  
   271  func rx(buf []byte) {
   272  	if len(buf) < 14 {
   273  		return
   274  	}
   275  
   276  	hdr := buf[0:14]
   277  	proto := tcpip.NetworkProtocolNumber(binary.BigEndian.Uint16(buf[12:14]))
   278  	payload := buf[14:]
   279  
   280  	pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
   281  		ReserveHeaderBytes: len(hdr),
   282  		Payload:            buffer.MakeWithData(payload),
   283  	})
   284  
   285  	copy(pkt.LinkHeader().Push(len(hdr)), hdr)
   286  
   287  	iface.Link.InjectInbound(proto, pkt)
   288  }
   289  
   290  func tx() (buf []byte) {
   291  	var pkt *stack.PacketBuffer
   292  
   293  	if pkt = iface.NIC.Link.Read(); pkt.IsNil() {
   294  		return
   295  	}
   296  
   297  	proto := make([]byte, 2)
   298  	binary.BigEndian.PutUint16(proto, uint16(pkt.NetworkProtocolNumber))
   299  
   300  	// Ethernet frame header
   301  	buf = append(buf, pkt.EgressRoute.RemoteLinkAddress...)
   302  	buf = append(buf, iface.NIC.MAC...)
   303  	buf = append(buf, proto...)
   304  
   305  	for _, v := range pkt.AsSlices() {
   306  		buf = append(buf, v...)
   307  	}
   308  
   309  	return
   310  }
   311  
   312  type txNotification struct{}
   313  
   314  func (n *txNotification) WriteNotify() {
   315  	buf := tx()
   316  	syscall.Write(TX, buf, uint(len(buf)))
   317  }
   318  
   319  // mac creates a stable "local administered" MAC address for the network based on the
   320  // provided unit serial number.
   321  func mac(serial string) string {
   322  	m := sha256.Sum256([]byte(fmt.Sprintf("MAC:%s", serial)))
   323  	// The first byte of the MAC address has a couple of flags which must be set correctly:
   324  	// - Unicast(0)/multicast(1) in the least significant bit of the byte.
   325  	//   This must be set to unicast.
   326  	// - Universally unique(0)/Local administered(1) in the second least significant bit.
   327  	//   Since we're not using an organisationally unique prefix triplet, this must be set to
   328  	//   Locally administered
   329  	m[0] &= 0xfe
   330  	m[0] |= 0x02
   331  	return fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x", m[0], m[1], m[2], m[3], m[4], m[5])
   332  }
   333  
   334  func startNetworking() (err error) {
   335  	// Set the default resolver from the config, if we're using DHCP this may be updated.
   336  	net.DefaultNS = []string{cfg.Resolver}
   337  
   338  	var status api.Status
   339  	if err := syscall.Call("RPC.Status", nil, &status); err != nil {
   340  		return fmt.Errorf("failed to fetch Status: %v", err)
   341  	}
   342  
   343  	iface = &enet.Interface{
   344  		Stack: stack.New(stack.Options{
   345  			NetworkProtocols: []stack.NetworkProtocolFactory{
   346  				ipv4.NewProtocol,
   347  				arp.NewProtocol,
   348  			},
   349  			TransportProtocols: []stack.TransportProtocolFactory{
   350  				tcp.NewProtocol,
   351  				icmp.NewProtocol4,
   352  				udp.NewProtocol,
   353  			},
   354  		}),
   355  	}
   356  
   357  	if cfg.DHCP {
   358  		// This is essentially the contents of enet.Init (plus enet.configure)
   359  		// with anything to do with setting up static IP addresses/routes
   360  		// stripped out.
   361  		//
   362  		// TODO(al): Refactor imx-enet to make this cleaner
   363  		macAddress := mac(status.Serial)
   364  		linkAddress, err := net.ParseMAC(macAddress)
   365  		if err != nil {
   366  			return fmt.Errorf("invalid MAC: %v", err)
   367  		}
   368  
   369  		if iface.NICID == 0 {
   370  			iface.NICID = enet.NICID
   371  		}
   372  
   373  		gvHWAddress, err := tcpip.ParseMACAddress(macAddress)
   374  		if err != nil {
   375  			return fmt.Errorf("invalid MAC: %v", err)
   376  		}
   377  		iface.Link = channel.New(256, enet.MTU, gvHWAddress)
   378  		iface.Link.LinkEPCapabilities |= stack.CapabilityResolutionRequired
   379  
   380  		linkEP := stack.LinkEndpoint(iface.Link)
   381  
   382  		if err := iface.Stack.CreateNIC(iface.NICID, linkEP); err != nil {
   383  			return fmt.Errorf("%v", err)
   384  		}
   385  
   386  		iface.NIC = &enet.NIC{
   387  			MAC:    linkAddress,
   388  			Link:   iface.Link,
   389  			Device: nil,
   390  		}
   391  		err = iface.NIC.Init()
   392  
   393  	} else {
   394  		if err = iface.Init(nil, cfg.IP, cfg.Netmask, mac(status.Serial), cfg.Gateway); err != nil {
   395  			return
   396  		}
   397  	}
   398  
   399  	iface.EnableICMP()
   400  	iface.Link.AddNotify(&txNotification{})
   401  
   402  	resolver, err := dnscache.New(dnsUpdateFreq, dnsUpdateTimeout)
   403  	if err != nil {
   404  		return fmt.Errorf("failed to create DNS cache: %v", err)
   405  	}
   406  	// hook interface into Go runtime
   407  	net.SocketFunc = iface.Socket
   408  	http.DefaultClient = &http.Client{
   409  		Timeout: httpTimeout,
   410  		Transport: &http.Transport{
   411  			DialContext: dnscache.DialFunc(resolver, (&net.Dialer{
   412  				Timeout:   30 * time.Second,
   413  				KeepAlive: 30 * time.Second,
   414  			}).DialContext),
   415  			DisableKeepAlives:     true,
   416  			ForceAttemptHTTP2:     false,
   417  			MaxIdleConns:          100,
   418  			IdleConnTimeout:       90 * time.Second,
   419  			ResponseHeaderTimeout: 10 * time.Second,
   420  			TLSHandshakeTimeout:   10 * time.Second,
   421  			ExpectContinueTimeout: 1 * time.Second,
   422  		},
   423  	}
   424  
   425  	return
   426  }