github.com/google/netstack@v0.0.0-20191123085552-55fcc16cd0eb/tcpip/link/sniffer/sniffer.go (about)

     1  // Copyright 2018 The gVisor Authors.
     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 sniffer provides the implementation of data-link layer endpoints that
    16  // wrap another endpoint and logs inbound and outbound packets.
    17  //
    18  // Sniffer endpoints can be used in the networking stack by calling New(eID) to
    19  // create a new endpoint, where eID is the ID of the endpoint being wrapped,
    20  // and then passing it as an argument to Stack.CreateNIC().
    21  package sniffer
    22  
    23  import (
    24  	"bytes"
    25  	"encoding/binary"
    26  	"fmt"
    27  	"io"
    28  	"os"
    29  	"sync/atomic"
    30  	"time"
    31  
    32  	"github.com/google/netstack/tcpip"
    33  	"github.com/google/netstack/tcpip/buffer"
    34  	"github.com/google/netstack/tcpip/header"
    35  	"github.com/google/netstack/tcpip/stack"
    36  	"log"
    37  )
    38  
    39  // LogPackets is a flag used to enable or disable packet logging via the log
    40  // package. Valid values are 0 or 1.
    41  //
    42  // LogPackets must be accessed atomically.
    43  var LogPackets uint32 = 1
    44  
    45  // LogPacketsToFile is a flag used to enable or disable logging packets to a
    46  // pcap file. Valid values are 0 or 1. A file must have been specified when the
    47  // sniffer was created for this flag to have effect.
    48  //
    49  // LogPacketsToFile must be accessed atomically.
    50  var LogPacketsToFile uint32 = 1
    51  
    52  type endpoint struct {
    53  	dispatcher stack.NetworkDispatcher
    54  	lower      stack.LinkEndpoint
    55  	file       *os.File
    56  	maxPCAPLen uint32
    57  }
    58  
    59  // New creates a new sniffer link-layer endpoint. It wraps around another
    60  // endpoint and logs packets and they traverse the endpoint.
    61  func New(lower stack.LinkEndpoint) stack.LinkEndpoint {
    62  	return &endpoint{
    63  		lower: lower,
    64  	}
    65  }
    66  
    67  func zoneOffset() (int32, error) {
    68  	loc, err := time.LoadLocation("Local")
    69  	if err != nil {
    70  		return 0, err
    71  	}
    72  	date := time.Date(0, 0, 0, 0, 0, 0, 0, loc)
    73  	_, offset := date.Zone()
    74  	return int32(offset), nil
    75  }
    76  
    77  func writePCAPHeader(w io.Writer, maxLen uint32) error {
    78  	offset, err := zoneOffset()
    79  	if err != nil {
    80  		return err
    81  	}
    82  	return binary.Write(w, binary.BigEndian, pcapHeader{
    83  		// From https://wiki.wireshark.org/Development/LibpcapFileFormat
    84  		MagicNumber: 0xa1b2c3d4,
    85  
    86  		VersionMajor: 2,
    87  		VersionMinor: 4,
    88  		Thiszone:     offset,
    89  		Sigfigs:      0,
    90  		Snaplen:      maxLen,
    91  		Network:      101, // LINKTYPE_RAW
    92  	})
    93  }
    94  
    95  // NewWithFile creates a new sniffer link-layer endpoint. It wraps around
    96  // another endpoint and logs packets and they traverse the endpoint.
    97  //
    98  // Packets can be logged to file in the pcap format. A sniffer created
    99  // with this function will not emit packets using the standard log
   100  // package.
   101  //
   102  // snapLen is the maximum amount of a packet to be saved. Packets with a length
   103  // less than or equal too snapLen will be saved in their entirety. Longer
   104  // packets will be truncated to snapLen.
   105  func NewWithFile(lower stack.LinkEndpoint, file *os.File, snapLen uint32) (stack.LinkEndpoint, error) {
   106  	if err := writePCAPHeader(file, snapLen); err != nil {
   107  		return nil, err
   108  	}
   109  	return &endpoint{
   110  		lower:      lower,
   111  		file:       file,
   112  		maxPCAPLen: snapLen,
   113  	}, nil
   114  }
   115  
   116  // DeliverNetworkPacket implements the stack.NetworkDispatcher interface. It is
   117  // called by the link-layer endpoint being wrapped when a packet arrives, and
   118  // logs the packet before forwarding to the actual dispatcher.
   119  func (e *endpoint) DeliverNetworkPacket(linkEP stack.LinkEndpoint, remote, local tcpip.LinkAddress, protocol tcpip.NetworkProtocolNumber, pkt tcpip.PacketBuffer) {
   120  	if atomic.LoadUint32(&LogPackets) == 1 && e.file == nil {
   121  		logPacket("recv", protocol, pkt.Data.First(), nil)
   122  	}
   123  	if e.file != nil && atomic.LoadUint32(&LogPacketsToFile) == 1 {
   124  		vs := pkt.Data.Views()
   125  		length := pkt.Data.Size()
   126  		if length > int(e.maxPCAPLen) {
   127  			length = int(e.maxPCAPLen)
   128  		}
   129  
   130  		buf := bytes.NewBuffer(make([]byte, 0, pcapPacketHeaderLen+length))
   131  		if err := binary.Write(buf, binary.BigEndian, newPCAPPacketHeader(uint32(length), uint32(pkt.Data.Size()))); err != nil {
   132  			panic(err)
   133  		}
   134  		for _, v := range vs {
   135  			if length == 0 {
   136  				break
   137  			}
   138  			if len(v) > length {
   139  				v = v[:length]
   140  			}
   141  			if _, err := buf.Write([]byte(v)); err != nil {
   142  				panic(err)
   143  			}
   144  			length -= len(v)
   145  		}
   146  		if _, err := e.file.Write(buf.Bytes()); err != nil {
   147  			panic(err)
   148  		}
   149  	}
   150  	e.dispatcher.DeliverNetworkPacket(e, remote, local, protocol, pkt)
   151  }
   152  
   153  // Attach implements the stack.LinkEndpoint interface. It saves the dispatcher
   154  // and registers with the lower endpoint as its dispatcher so that "e" is called
   155  // for inbound packets.
   156  func (e *endpoint) Attach(dispatcher stack.NetworkDispatcher) {
   157  	e.dispatcher = dispatcher
   158  	e.lower.Attach(e)
   159  }
   160  
   161  // IsAttached implements stack.LinkEndpoint.IsAttached.
   162  func (e *endpoint) IsAttached() bool {
   163  	return e.dispatcher != nil
   164  }
   165  
   166  // MTU implements stack.LinkEndpoint.MTU. It just forwards the request to the
   167  // lower endpoint.
   168  func (e *endpoint) MTU() uint32 {
   169  	return e.lower.MTU()
   170  }
   171  
   172  // Capabilities implements stack.LinkEndpoint.Capabilities. It just forwards the
   173  // request to the lower endpoint.
   174  func (e *endpoint) Capabilities() stack.LinkEndpointCapabilities {
   175  	return e.lower.Capabilities()
   176  }
   177  
   178  // MaxHeaderLength implements the stack.LinkEndpoint interface. It just forwards
   179  // the request to the lower endpoint.
   180  func (e *endpoint) MaxHeaderLength() uint16 {
   181  	return e.lower.MaxHeaderLength()
   182  }
   183  
   184  func (e *endpoint) LinkAddress() tcpip.LinkAddress {
   185  	return e.lower.LinkAddress()
   186  }
   187  
   188  // GSOMaxSize returns the maximum GSO packet size.
   189  func (e *endpoint) GSOMaxSize() uint32 {
   190  	if gso, ok := e.lower.(stack.GSOEndpoint); ok {
   191  		return gso.GSOMaxSize()
   192  	}
   193  	return 0
   194  }
   195  
   196  func (e *endpoint) dumpPacket(gso *stack.GSO, protocol tcpip.NetworkProtocolNumber, pkt tcpip.PacketBuffer) {
   197  	if atomic.LoadUint32(&LogPackets) == 1 && e.file == nil {
   198  		logPacket("send", protocol, pkt.Header.View(), gso)
   199  	}
   200  	if e.file != nil && atomic.LoadUint32(&LogPacketsToFile) == 1 {
   201  		hdrBuf := pkt.Header.View()
   202  		length := len(hdrBuf) + pkt.Data.Size()
   203  		if length > int(e.maxPCAPLen) {
   204  			length = int(e.maxPCAPLen)
   205  		}
   206  
   207  		buf := bytes.NewBuffer(make([]byte, 0, pcapPacketHeaderLen+length))
   208  		if err := binary.Write(buf, binary.BigEndian, newPCAPPacketHeader(uint32(length), uint32(len(hdrBuf)+pkt.Data.Size()))); err != nil {
   209  			panic(err)
   210  		}
   211  		if len(hdrBuf) > length {
   212  			hdrBuf = hdrBuf[:length]
   213  		}
   214  		if _, err := buf.Write(hdrBuf); err != nil {
   215  			panic(err)
   216  		}
   217  		length -= len(hdrBuf)
   218  		logVectorisedView(pkt.Data, length, buf)
   219  		if _, err := e.file.Write(buf.Bytes()); err != nil {
   220  			panic(err)
   221  		}
   222  	}
   223  }
   224  
   225  // WritePacket implements the stack.LinkEndpoint interface. It is called by
   226  // higher-level protocols to write packets; it just logs the packet and
   227  // forwards the request to the lower endpoint.
   228  func (e *endpoint) WritePacket(r *stack.Route, gso *stack.GSO, protocol tcpip.NetworkProtocolNumber, pkt tcpip.PacketBuffer) *tcpip.Error {
   229  	e.dumpPacket(gso, protocol, pkt)
   230  	return e.lower.WritePacket(r, gso, protocol, pkt)
   231  }
   232  
   233  // WritePackets implements the stack.LinkEndpoint interface. It is called by
   234  // higher-level protocols to write packets; it just logs the packet and
   235  // forwards the request to the lower endpoint.
   236  func (e *endpoint) WritePackets(r *stack.Route, gso *stack.GSO, hdrs []stack.PacketDescriptor, payload buffer.VectorisedView, protocol tcpip.NetworkProtocolNumber) (int, *tcpip.Error) {
   237  	view := payload.ToView()
   238  	for _, d := range hdrs {
   239  		e.dumpPacket(gso, protocol, tcpip.PacketBuffer{
   240  			Header: d.Hdr,
   241  			Data:   view[d.Off:][:d.Size].ToVectorisedView(),
   242  		})
   243  	}
   244  	return e.lower.WritePackets(r, gso, hdrs, payload, protocol)
   245  }
   246  
   247  // WriteRawPacket implements stack.LinkEndpoint.WriteRawPacket.
   248  func (e *endpoint) WriteRawPacket(vv buffer.VectorisedView) *tcpip.Error {
   249  	if atomic.LoadUint32(&LogPackets) == 1 && e.file == nil {
   250  		logPacket("send", 0, buffer.View("[raw packet, no header available]"), nil /* gso */)
   251  	}
   252  	if e.file != nil && atomic.LoadUint32(&LogPacketsToFile) == 1 {
   253  		length := vv.Size()
   254  		if length > int(e.maxPCAPLen) {
   255  			length = int(e.maxPCAPLen)
   256  		}
   257  
   258  		buf := bytes.NewBuffer(make([]byte, 0, pcapPacketHeaderLen+length))
   259  		if err := binary.Write(buf, binary.BigEndian, newPCAPPacketHeader(uint32(length), uint32(vv.Size()))); err != nil {
   260  			panic(err)
   261  		}
   262  		logVectorisedView(vv, length, buf)
   263  		if _, err := e.file.Write(buf.Bytes()); err != nil {
   264  			panic(err)
   265  		}
   266  	}
   267  	return e.lower.WriteRawPacket(vv)
   268  }
   269  
   270  func logVectorisedView(vv buffer.VectorisedView, length int, buf *bytes.Buffer) {
   271  	if length <= 0 {
   272  		return
   273  	}
   274  	for _, v := range vv.Views() {
   275  		if len(v) > length {
   276  			v = v[:length]
   277  		}
   278  		n, err := buf.Write(v)
   279  		if err != nil {
   280  			panic(err)
   281  		}
   282  		length -= n
   283  		if length == 0 {
   284  			return
   285  		}
   286  	}
   287  }
   288  
   289  // Wait implements stack.LinkEndpoint.Wait.
   290  func (*endpoint) Wait() {}
   291  
   292  func logPacket(prefix string, protocol tcpip.NetworkProtocolNumber, b buffer.View, gso *stack.GSO) {
   293  	// Figure out the network layer info.
   294  	var transProto uint8
   295  	src := tcpip.Address("unknown")
   296  	dst := tcpip.Address("unknown")
   297  	id := 0
   298  	size := uint16(0)
   299  	var fragmentOffset uint16
   300  	var moreFragments bool
   301  	switch protocol {
   302  	case header.IPv4ProtocolNumber:
   303  		ipv4 := header.IPv4(b)
   304  		fragmentOffset = ipv4.FragmentOffset()
   305  		moreFragments = ipv4.Flags()&header.IPv4FlagMoreFragments == header.IPv4FlagMoreFragments
   306  		src = ipv4.SourceAddress()
   307  		dst = ipv4.DestinationAddress()
   308  		transProto = ipv4.Protocol()
   309  		size = ipv4.TotalLength() - uint16(ipv4.HeaderLength())
   310  		b = b[ipv4.HeaderLength():]
   311  		id = int(ipv4.ID())
   312  
   313  	case header.IPv6ProtocolNumber:
   314  		ipv6 := header.IPv6(b)
   315  		src = ipv6.SourceAddress()
   316  		dst = ipv6.DestinationAddress()
   317  		transProto = ipv6.NextHeader()
   318  		size = ipv6.PayloadLength()
   319  		b = b[header.IPv6MinimumSize:]
   320  
   321  	case header.ARPProtocolNumber:
   322  		arp := header.ARP(b)
   323  		log.Printf(
   324  			"%s arp %v (%v) -> %v (%v) valid:%v",
   325  			prefix,
   326  			tcpip.Address(arp.ProtocolAddressSender()), tcpip.LinkAddress(arp.HardwareAddressSender()),
   327  			tcpip.Address(arp.ProtocolAddressTarget()), tcpip.LinkAddress(arp.HardwareAddressTarget()),
   328  			arp.IsValid(),
   329  		)
   330  		return
   331  	default:
   332  		log.Printf("%s unknown network protocol", prefix)
   333  		return
   334  	}
   335  
   336  	// Figure out the transport layer info.
   337  	transName := "unknown"
   338  	srcPort := uint16(0)
   339  	dstPort := uint16(0)
   340  	details := ""
   341  	switch tcpip.TransportProtocolNumber(transProto) {
   342  	case header.ICMPv4ProtocolNumber:
   343  		transName = "icmp"
   344  		icmp := header.ICMPv4(b)
   345  		icmpType := "unknown"
   346  		if fragmentOffset == 0 {
   347  			switch icmp.Type() {
   348  			case header.ICMPv4EchoReply:
   349  				icmpType = "echo reply"
   350  			case header.ICMPv4DstUnreachable:
   351  				icmpType = "destination unreachable"
   352  			case header.ICMPv4SrcQuench:
   353  				icmpType = "source quench"
   354  			case header.ICMPv4Redirect:
   355  				icmpType = "redirect"
   356  			case header.ICMPv4Echo:
   357  				icmpType = "echo"
   358  			case header.ICMPv4TimeExceeded:
   359  				icmpType = "time exceeded"
   360  			case header.ICMPv4ParamProblem:
   361  				icmpType = "param problem"
   362  			case header.ICMPv4Timestamp:
   363  				icmpType = "timestamp"
   364  			case header.ICMPv4TimestampReply:
   365  				icmpType = "timestamp reply"
   366  			case header.ICMPv4InfoRequest:
   367  				icmpType = "info request"
   368  			case header.ICMPv4InfoReply:
   369  				icmpType = "info reply"
   370  			}
   371  		}
   372  		log.Printf("%s %s %v -> %v %s len:%d id:%04x code:%d", prefix, transName, src, dst, icmpType, size, id, icmp.Code())
   373  		return
   374  
   375  	case header.ICMPv6ProtocolNumber:
   376  		transName = "icmp"
   377  		icmp := header.ICMPv6(b)
   378  		icmpType := "unknown"
   379  		switch icmp.Type() {
   380  		case header.ICMPv6DstUnreachable:
   381  			icmpType = "destination unreachable"
   382  		case header.ICMPv6PacketTooBig:
   383  			icmpType = "packet too big"
   384  		case header.ICMPv6TimeExceeded:
   385  			icmpType = "time exceeded"
   386  		case header.ICMPv6ParamProblem:
   387  			icmpType = "param problem"
   388  		case header.ICMPv6EchoRequest:
   389  			icmpType = "echo request"
   390  		case header.ICMPv6EchoReply:
   391  			icmpType = "echo reply"
   392  		case header.ICMPv6RouterSolicit:
   393  			icmpType = "router solicit"
   394  		case header.ICMPv6RouterAdvert:
   395  			icmpType = "router advert"
   396  		case header.ICMPv6NeighborSolicit:
   397  			icmpType = "neighbor solicit"
   398  		case header.ICMPv6NeighborAdvert:
   399  			icmpType = "neighbor advert"
   400  		case header.ICMPv6RedirectMsg:
   401  			icmpType = "redirect message"
   402  		}
   403  		log.Printf("%s %s %v -> %v %s len:%d id:%04x code:%d", prefix, transName, src, dst, icmpType, size, id, icmp.Code())
   404  		return
   405  
   406  	case header.UDPProtocolNumber:
   407  		transName = "udp"
   408  		udp := header.UDP(b)
   409  		if fragmentOffset == 0 && len(udp) >= header.UDPMinimumSize {
   410  			srcPort = udp.SourcePort()
   411  			dstPort = udp.DestinationPort()
   412  			details = fmt.Sprintf("xsum: 0x%x", udp.Checksum())
   413  			size -= header.UDPMinimumSize
   414  		}
   415  
   416  	case header.TCPProtocolNumber:
   417  		transName = "tcp"
   418  		tcp := header.TCP(b)
   419  		if fragmentOffset == 0 && len(tcp) >= header.TCPMinimumSize {
   420  			offset := int(tcp.DataOffset())
   421  			if offset < header.TCPMinimumSize {
   422  				details += fmt.Sprintf("invalid packet: tcp data offset too small %d", offset)
   423  				break
   424  			}
   425  			if offset > len(tcp) && !moreFragments {
   426  				details += fmt.Sprintf("invalid packet: tcp data offset %d larger than packet buffer length %d", offset, len(tcp))
   427  				break
   428  			}
   429  
   430  			srcPort = tcp.SourcePort()
   431  			dstPort = tcp.DestinationPort()
   432  			size -= uint16(offset)
   433  
   434  			// Initialize the TCP flags.
   435  			flags := tcp.Flags()
   436  			flagsStr := []byte("FSRPAU")
   437  			for i := range flagsStr {
   438  				if flags&(1<<uint(i)) == 0 {
   439  					flagsStr[i] = ' '
   440  				}
   441  			}
   442  			details = fmt.Sprintf("flags:0x%02x (%v) seqnum: %v ack: %v win: %v xsum:0x%x", flags, string(flagsStr), tcp.SequenceNumber(), tcp.AckNumber(), tcp.WindowSize(), tcp.Checksum())
   443  			if flags&header.TCPFlagSyn != 0 {
   444  				details += fmt.Sprintf(" options: %+v", header.ParseSynOptions(tcp.Options(), flags&header.TCPFlagAck != 0))
   445  			} else {
   446  				details += fmt.Sprintf(" options: %+v", tcp.ParsedOptions())
   447  			}
   448  		}
   449  
   450  	default:
   451  		log.Printf("%s %v -> %v unknown transport protocol: %d", prefix, src, dst, transProto)
   452  		return
   453  	}
   454  
   455  	if gso != nil {
   456  		details += fmt.Sprintf(" gso: %+v", gso)
   457  	}
   458  
   459  	log.Printf("%s %s %v:%v -> %v:%v len:%d id:%04x %s", prefix, transName, src, srcPort, dst, dstPort, size, id, details)
   460  }