github.com/flowerwrong/netstack@v0.0.0-20191009141956-e5848263af28/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/FlowerWrong/netstack/tcpip"
    33  	"github.com/FlowerWrong/netstack/tcpip/buffer"
    34  	"github.com/FlowerWrong/netstack/tcpip/header"
    35  	"github.com/FlowerWrong/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, vv buffer.VectorisedView) {
   120  	if atomic.LoadUint32(&LogPackets) == 1 && e.file == nil {
   121  		logPacket("recv", protocol, vv.First(), nil)
   122  	}
   123  	if e.file != nil && atomic.LoadUint32(&LogPacketsToFile) == 1 {
   124  		vs := vv.Views()
   125  		length := vv.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(vv.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, vv)
   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  // WritePacket implements the stack.LinkEndpoint interface. It is called by
   197  // higher-level protocols to write packets; it just logs the packet and forwards
   198  // the request to the lower endpoint.
   199  func (e *endpoint) WritePacket(r *stack.Route, gso *stack.GSO, hdr buffer.Prependable, payload buffer.VectorisedView, protocol tcpip.NetworkProtocolNumber) *tcpip.Error {
   200  	if atomic.LoadUint32(&LogPackets) == 1 && e.file == nil {
   201  		logPacket("send", protocol, hdr.View(), gso)
   202  	}
   203  	if e.file != nil && atomic.LoadUint32(&LogPacketsToFile) == 1 {
   204  		hdrBuf := hdr.View()
   205  		length := len(hdrBuf) + payload.Size()
   206  		if length > int(e.maxPCAPLen) {
   207  			length = int(e.maxPCAPLen)
   208  		}
   209  
   210  		buf := bytes.NewBuffer(make([]byte, 0, pcapPacketHeaderLen+length))
   211  		if err := binary.Write(buf, binary.BigEndian, newPCAPPacketHeader(uint32(length), uint32(len(hdrBuf)+payload.Size()))); err != nil {
   212  			panic(err)
   213  		}
   214  		if len(hdrBuf) > length {
   215  			hdrBuf = hdrBuf[:length]
   216  		}
   217  		if _, err := buf.Write(hdrBuf); err != nil {
   218  			panic(err)
   219  		}
   220  		length -= len(hdrBuf)
   221  		if length > 0 {
   222  			for _, v := range payload.Views() {
   223  				if len(v) > length {
   224  					v = v[:length]
   225  				}
   226  				n, err := buf.Write(v)
   227  				if err != nil {
   228  					panic(err)
   229  				}
   230  				length -= n
   231  				if length == 0 {
   232  					break
   233  				}
   234  			}
   235  		}
   236  		if _, err := e.file.Write(buf.Bytes()); err != nil {
   237  			panic(err)
   238  		}
   239  	}
   240  	return e.lower.WritePacket(r, gso, hdr, payload, protocol)
   241  }
   242  
   243  // Wait implements stack.LinkEndpoint.Wait.
   244  func (*endpoint) Wait() {}
   245  
   246  func logPacket(prefix string, protocol tcpip.NetworkProtocolNumber, b buffer.View, gso *stack.GSO) {
   247  	// Figure out the network layer info.
   248  	var transProto uint8
   249  	src := tcpip.Address("unknown")
   250  	dst := tcpip.Address("unknown")
   251  	id := 0
   252  	size := uint16(0)
   253  	var fragmentOffset uint16
   254  	var moreFragments bool
   255  	switch protocol {
   256  	case header.IPv4ProtocolNumber:
   257  		ipv4 := header.IPv4(b)
   258  		fragmentOffset = ipv4.FragmentOffset()
   259  		moreFragments = ipv4.Flags()&header.IPv4FlagMoreFragments == header.IPv4FlagMoreFragments
   260  		src = ipv4.SourceAddress()
   261  		dst = ipv4.DestinationAddress()
   262  		transProto = ipv4.Protocol()
   263  		size = ipv4.TotalLength() - uint16(ipv4.HeaderLength())
   264  		b = b[ipv4.HeaderLength():]
   265  		id = int(ipv4.ID())
   266  
   267  	case header.IPv6ProtocolNumber:
   268  		ipv6 := header.IPv6(b)
   269  		src = ipv6.SourceAddress()
   270  		dst = ipv6.DestinationAddress()
   271  		transProto = ipv6.NextHeader()
   272  		size = ipv6.PayloadLength()
   273  		b = b[header.IPv6MinimumSize:]
   274  
   275  	case header.ARPProtocolNumber:
   276  		arp := header.ARP(b)
   277  		log.Printf(
   278  			"%s arp %v (%v) -> %v (%v) valid:%v",
   279  			prefix,
   280  			tcpip.Address(arp.ProtocolAddressSender()), tcpip.LinkAddress(arp.HardwareAddressSender()),
   281  			tcpip.Address(arp.ProtocolAddressTarget()), tcpip.LinkAddress(arp.HardwareAddressTarget()),
   282  			arp.IsValid(),
   283  		)
   284  		return
   285  	default:
   286  		log.Printf("%s unknown network protocol", prefix)
   287  		return
   288  	}
   289  
   290  	// Figure out the transport layer info.
   291  	transName := "unknown"
   292  	srcPort := uint16(0)
   293  	dstPort := uint16(0)
   294  	details := ""
   295  	switch tcpip.TransportProtocolNumber(transProto) {
   296  	case header.ICMPv4ProtocolNumber:
   297  		transName = "icmp"
   298  		icmp := header.ICMPv4(b)
   299  		icmpType := "unknown"
   300  		if fragmentOffset == 0 {
   301  			switch icmp.Type() {
   302  			case header.ICMPv4EchoReply:
   303  				icmpType = "echo reply"
   304  			case header.ICMPv4DstUnreachable:
   305  				icmpType = "destination unreachable"
   306  			case header.ICMPv4SrcQuench:
   307  				icmpType = "source quench"
   308  			case header.ICMPv4Redirect:
   309  				icmpType = "redirect"
   310  			case header.ICMPv4Echo:
   311  				icmpType = "echo"
   312  			case header.ICMPv4TimeExceeded:
   313  				icmpType = "time exceeded"
   314  			case header.ICMPv4ParamProblem:
   315  				icmpType = "param problem"
   316  			case header.ICMPv4Timestamp:
   317  				icmpType = "timestamp"
   318  			case header.ICMPv4TimestampReply:
   319  				icmpType = "timestamp reply"
   320  			case header.ICMPv4InfoRequest:
   321  				icmpType = "info request"
   322  			case header.ICMPv4InfoReply:
   323  				icmpType = "info reply"
   324  			}
   325  		}
   326  		log.Printf("%s %s %v -> %v %s len:%d id:%04x code:%d", prefix, transName, src, dst, icmpType, size, id, icmp.Code())
   327  		return
   328  
   329  	case header.ICMPv6ProtocolNumber:
   330  		transName = "icmp"
   331  		icmp := header.ICMPv6(b)
   332  		icmpType := "unknown"
   333  		switch icmp.Type() {
   334  		case header.ICMPv6DstUnreachable:
   335  			icmpType = "destination unreachable"
   336  		case header.ICMPv6PacketTooBig:
   337  			icmpType = "packet too big"
   338  		case header.ICMPv6TimeExceeded:
   339  			icmpType = "time exceeded"
   340  		case header.ICMPv6ParamProblem:
   341  			icmpType = "param problem"
   342  		case header.ICMPv6EchoRequest:
   343  			icmpType = "echo request"
   344  		case header.ICMPv6EchoReply:
   345  			icmpType = "echo reply"
   346  		case header.ICMPv6RouterSolicit:
   347  			icmpType = "router solicit"
   348  		case header.ICMPv6RouterAdvert:
   349  			icmpType = "router advert"
   350  		case header.ICMPv6NeighborSolicit:
   351  			icmpType = "neighbor solicit"
   352  		case header.ICMPv6NeighborAdvert:
   353  			icmpType = "neighbor advert"
   354  		case header.ICMPv6RedirectMsg:
   355  			icmpType = "redirect message"
   356  		}
   357  		log.Printf("%s %s %v -> %v %s len:%d id:%04x code:%d", prefix, transName, src, dst, icmpType, size, id, icmp.Code())
   358  		return
   359  
   360  	case header.UDPProtocolNumber:
   361  		transName = "udp"
   362  		udp := header.UDP(b)
   363  		if fragmentOffset == 0 && len(udp) >= header.UDPMinimumSize {
   364  			srcPort = udp.SourcePort()
   365  			dstPort = udp.DestinationPort()
   366  			details = fmt.Sprintf("xsum: 0x%x", udp.Checksum())
   367  			size -= header.UDPMinimumSize
   368  		}
   369  
   370  	case header.TCPProtocolNumber:
   371  		transName = "tcp"
   372  		tcp := header.TCP(b)
   373  		if fragmentOffset == 0 && len(tcp) >= header.TCPMinimumSize {
   374  			offset := int(tcp.DataOffset())
   375  			if offset < header.TCPMinimumSize {
   376  				details += fmt.Sprintf("invalid packet: tcp data offset too small %d", offset)
   377  				break
   378  			}
   379  			if offset > len(tcp) && !moreFragments {
   380  				details += fmt.Sprintf("invalid packet: tcp data offset %d larger than packet buffer length %d", offset, len(tcp))
   381  				break
   382  			}
   383  
   384  			srcPort = tcp.SourcePort()
   385  			dstPort = tcp.DestinationPort()
   386  			size -= uint16(offset)
   387  
   388  			// Initialize the TCP flags.
   389  			flags := tcp.Flags()
   390  			flagsStr := []byte("FSRPAU")
   391  			for i := range flagsStr {
   392  				if flags&(1<<uint(i)) == 0 {
   393  					flagsStr[i] = ' '
   394  				}
   395  			}
   396  			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())
   397  			if flags&header.TCPFlagSyn != 0 {
   398  				details += fmt.Sprintf(" options: %+v", header.ParseSynOptions(tcp.Options(), flags&header.TCPFlagAck != 0))
   399  			} else {
   400  				details += fmt.Sprintf(" options: %+v", tcp.ParsedOptions())
   401  			}
   402  		}
   403  
   404  	default:
   405  		log.Printf("%s %v -> %v unknown transport protocol: %d", prefix, src, dst, transProto)
   406  		return
   407  	}
   408  
   409  	if gso != nil {
   410  		details += fmt.Sprintf(" gso: %+v", gso)
   411  	}
   412  
   413  	log.Printf("%s %s %v:%v -> %v:%v len:%d id:%04x %s", prefix, transName, src, srcPort, dst, dstPort, size, id, details)
   414  }