inet.af/netstack@v0.0.0-20220214151720-7585b01ddccf/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  	"encoding/binary"
    25  	"fmt"
    26  	"io"
    27  	"sync/atomic"
    28  	"time"
    29  
    30  	"inet.af/netstack/log"
    31  	"inet.af/netstack/tcpip"
    32  	"inet.af/netstack/tcpip/buffer"
    33  	"inet.af/netstack/tcpip/header"
    34  	"inet.af/netstack/tcpip/header/parse"
    35  	"inet.af/netstack/tcpip/link/nested"
    36  	"inet.af/netstack/tcpip/stack"
    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  // LogPacketsToPCAP is a flag used to enable or disable logging packets to a
    46  // pcap writer. Valid values are 0 or 1. A writer must have been specified when the
    47  // sniffer was created for this flag to have effect.
    48  //
    49  // LogPacketsToPCAP must be accessed atomically.
    50  var LogPacketsToPCAP uint32 = 1
    51  
    52  type endpoint struct {
    53  	nested.Endpoint
    54  	writer     io.Writer
    55  	maxPCAPLen uint32
    56  	logPrefix  string
    57  }
    58  
    59  var _ stack.GSOEndpoint = (*endpoint)(nil)
    60  var _ stack.LinkEndpoint = (*endpoint)(nil)
    61  var _ stack.NetworkDispatcher = (*endpoint)(nil)
    62  
    63  type direction int
    64  
    65  const (
    66  	directionSend = iota
    67  	directionRecv
    68  )
    69  
    70  // New creates a new sniffer link-layer endpoint. It wraps around another
    71  // endpoint and logs packets and they traverse the endpoint.
    72  func New(lower stack.LinkEndpoint) stack.LinkEndpoint {
    73  	return NewWithPrefix(lower, "")
    74  }
    75  
    76  // NewWithPrefix creates a new sniffer link-layer endpoint. It wraps around
    77  // another endpoint and logs packets prefixed with logPrefix as they traverse
    78  // the endpoint.
    79  //
    80  // logPrefix is prepended to the log line without any separators.
    81  // E.g. logPrefix = "NIC:en0/" will produce log lines like
    82  // "NIC:en0/send udp [...]".
    83  func NewWithPrefix(lower stack.LinkEndpoint, logPrefix string) stack.LinkEndpoint {
    84  	sniffer := &endpoint{logPrefix: logPrefix}
    85  	sniffer.Endpoint.Init(lower, sniffer)
    86  	return sniffer
    87  }
    88  
    89  func zoneOffset() (int32, error) {
    90  	date := time.Date(0, 0, 0, 0, 0, 0, 0, time.Local)
    91  	_, offset := date.Zone()
    92  	return int32(offset), nil
    93  }
    94  
    95  func writePCAPHeader(w io.Writer, maxLen uint32) error {
    96  	offset, err := zoneOffset()
    97  	if err != nil {
    98  		return err
    99  	}
   100  	return binary.Write(w, binary.BigEndian, pcapHeader{
   101  		// From https://wiki.wireshark.org/Development/LibpcapFileFormat
   102  		MagicNumber: 0xa1b2c3d4,
   103  
   104  		VersionMajor: 2,
   105  		VersionMinor: 4,
   106  		Thiszone:     offset,
   107  		Sigfigs:      0,
   108  		Snaplen:      maxLen,
   109  		Network:      101, // LINKTYPE_RAW
   110  	})
   111  }
   112  
   113  // NewWithWriter creates a new sniffer link-layer endpoint. It wraps around
   114  // another endpoint and logs packets as they traverse the endpoint.
   115  //
   116  // Each packet is written to writer in the pcap format in a single Write call
   117  // without synchronization. A sniffer created with this function will not emit
   118  // packets using the standard log package.
   119  //
   120  // snapLen is the maximum amount of a packet to be saved. Packets with a length
   121  // less than or equal to snapLen will be saved in their entirety. Longer
   122  // packets will be truncated to snapLen.
   123  func NewWithWriter(lower stack.LinkEndpoint, writer io.Writer, snapLen uint32) (stack.LinkEndpoint, error) {
   124  	if err := writePCAPHeader(writer, snapLen); err != nil {
   125  		return nil, err
   126  	}
   127  	sniffer := &endpoint{
   128  		writer:     writer,
   129  		maxPCAPLen: snapLen,
   130  	}
   131  	sniffer.Endpoint.Init(lower, sniffer)
   132  	return sniffer, nil
   133  }
   134  
   135  // DeliverNetworkPacket implements the stack.NetworkDispatcher interface. It is
   136  // called by the link-layer endpoint being wrapped when a packet arrives, and
   137  // logs the packet before forwarding to the actual dispatcher.
   138  func (e *endpoint) DeliverNetworkPacket(remote, local tcpip.LinkAddress, protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) {
   139  	e.dumpPacket(directionRecv, protocol, pkt)
   140  	e.Endpoint.DeliverNetworkPacket(remote, local, protocol, pkt)
   141  }
   142  
   143  func (e *endpoint) dumpPacket(dir direction, protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) {
   144  	writer := e.writer
   145  	if writer == nil && atomic.LoadUint32(&LogPackets) == 1 {
   146  		logPacket(e.logPrefix, dir, protocol, pkt)
   147  	}
   148  	if writer != nil && atomic.LoadUint32(&LogPacketsToPCAP) == 1 {
   149  		packet := pcapPacket{
   150  			timestamp:     time.Now(),
   151  			packet:        pkt,
   152  			maxCaptureLen: int(e.maxPCAPLen),
   153  		}
   154  		b, err := packet.MarshalBinary()
   155  		if err != nil {
   156  			panic(err)
   157  		}
   158  		if _, err := writer.Write(b); err != nil {
   159  			panic(err)
   160  		}
   161  	}
   162  }
   163  
   164  // WritePacket implements the stack.LinkEndpoint interface. It is called by
   165  // higher-level protocols to write packets; it just logs the packet and
   166  // forwards the request to the lower endpoint.
   167  func (e *endpoint) WritePacket(r stack.RouteInfo, protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) tcpip.Error {
   168  	e.dumpPacket(directionSend, protocol, pkt)
   169  	return e.Endpoint.WritePacket(r, protocol, pkt)
   170  }
   171  
   172  // WritePackets implements the stack.LinkEndpoint interface. It is called by
   173  // higher-level protocols to write packets; it just logs the packet and
   174  // forwards the request to the lower endpoint.
   175  func (e *endpoint) WritePackets(r stack.RouteInfo, pkts stack.PacketBufferList, protocol tcpip.NetworkProtocolNumber) (int, tcpip.Error) {
   176  	for pkt := pkts.Front(); pkt != nil; pkt = pkt.Next() {
   177  		e.dumpPacket(directionSend, protocol, pkt)
   178  	}
   179  	return e.Endpoint.WritePackets(r, pkts, protocol)
   180  }
   181  
   182  func logPacket(prefix string, dir direction, protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) {
   183  	// Figure out the network layer info.
   184  	var transProto uint8
   185  	src := tcpip.Address("unknown")
   186  	dst := tcpip.Address("unknown")
   187  	var size uint16
   188  	var id uint32
   189  	var fragmentOffset uint16
   190  	var moreFragments bool
   191  
   192  	var directionPrefix string
   193  	switch dir {
   194  	case directionSend:
   195  		directionPrefix = "send"
   196  	case directionRecv:
   197  		directionPrefix = "recv"
   198  	default:
   199  		panic(fmt.Sprintf("unrecognized direction: %d", dir))
   200  	}
   201  
   202  	// Clone the packet buffer to not modify the original.
   203  	//
   204  	// We don't clone the original packet buffer so that the new packet buffer
   205  	// does not have any of its headers set.
   206  	//
   207  	// We trim the link headers from the cloned buffer as the sniffer doesn't
   208  	// handle link headers.
   209  	vv := buffer.NewVectorisedView(pkt.Size(), pkt.Views())
   210  	vv.TrimFront(len(pkt.LinkHeader().View()))
   211  	pkt = stack.NewPacketBuffer(stack.PacketBufferOptions{Data: vv})
   212  	defer pkt.DecRef()
   213  	switch protocol {
   214  	case header.IPv4ProtocolNumber:
   215  		if ok := parse.IPv4(pkt); !ok {
   216  			return
   217  		}
   218  
   219  		ipv4 := header.IPv4(pkt.NetworkHeader().View())
   220  		fragmentOffset = ipv4.FragmentOffset()
   221  		moreFragments = ipv4.Flags()&header.IPv4FlagMoreFragments == header.IPv4FlagMoreFragments
   222  		src = ipv4.SourceAddress()
   223  		dst = ipv4.DestinationAddress()
   224  		transProto = ipv4.Protocol()
   225  		size = ipv4.TotalLength() - uint16(ipv4.HeaderLength())
   226  		id = uint32(ipv4.ID())
   227  
   228  	case header.IPv6ProtocolNumber:
   229  		proto, fragID, fragOffset, fragMore, ok := parse.IPv6(pkt)
   230  		if !ok {
   231  			return
   232  		}
   233  
   234  		ipv6 := header.IPv6(pkt.NetworkHeader().View())
   235  		src = ipv6.SourceAddress()
   236  		dst = ipv6.DestinationAddress()
   237  		transProto = uint8(proto)
   238  		size = ipv6.PayloadLength()
   239  		id = fragID
   240  		moreFragments = fragMore
   241  		fragmentOffset = fragOffset
   242  
   243  	case header.ARPProtocolNumber:
   244  		if !parse.ARP(pkt) {
   245  			return
   246  		}
   247  
   248  		arp := header.ARP(pkt.NetworkHeader().View())
   249  		log.Infof(
   250  			"%s%s arp %s (%s) -> %s (%s) valid:%t",
   251  			prefix,
   252  			directionPrefix,
   253  			tcpip.Address(arp.ProtocolAddressSender()), tcpip.LinkAddress(arp.HardwareAddressSender()),
   254  			tcpip.Address(arp.ProtocolAddressTarget()), tcpip.LinkAddress(arp.HardwareAddressTarget()),
   255  			arp.IsValid(),
   256  		)
   257  		return
   258  	default:
   259  		log.Infof("%s%s unknown network protocol", prefix, directionPrefix)
   260  		return
   261  	}
   262  
   263  	// Figure out the transport layer info.
   264  	transName := "unknown"
   265  	srcPort := uint16(0)
   266  	dstPort := uint16(0)
   267  	details := ""
   268  	switch tcpip.TransportProtocolNumber(transProto) {
   269  	case header.ICMPv4ProtocolNumber:
   270  		transName = "icmp"
   271  		hdr, ok := pkt.Data().PullUp(header.ICMPv4MinimumSize)
   272  		if !ok {
   273  			break
   274  		}
   275  		icmp := header.ICMPv4(hdr)
   276  		icmpType := "unknown"
   277  		if fragmentOffset == 0 {
   278  			switch icmp.Type() {
   279  			case header.ICMPv4EchoReply:
   280  				icmpType = "echo reply"
   281  			case header.ICMPv4DstUnreachable:
   282  				icmpType = "destination unreachable"
   283  			case header.ICMPv4SrcQuench:
   284  				icmpType = "source quench"
   285  			case header.ICMPv4Redirect:
   286  				icmpType = "redirect"
   287  			case header.ICMPv4Echo:
   288  				icmpType = "echo"
   289  			case header.ICMPv4TimeExceeded:
   290  				icmpType = "time exceeded"
   291  			case header.ICMPv4ParamProblem:
   292  				icmpType = "param problem"
   293  			case header.ICMPv4Timestamp:
   294  				icmpType = "timestamp"
   295  			case header.ICMPv4TimestampReply:
   296  				icmpType = "timestamp reply"
   297  			case header.ICMPv4InfoRequest:
   298  				icmpType = "info request"
   299  			case header.ICMPv4InfoReply:
   300  				icmpType = "info reply"
   301  			}
   302  		}
   303  		log.Infof("%s%s %s %s -> %s %s len:%d id:%04x code:%d", prefix, directionPrefix, transName, src, dst, icmpType, size, id, icmp.Code())
   304  		return
   305  
   306  	case header.ICMPv6ProtocolNumber:
   307  		transName = "icmp"
   308  		hdr, ok := pkt.Data().PullUp(header.ICMPv6MinimumSize)
   309  		if !ok {
   310  			break
   311  		}
   312  		icmp := header.ICMPv6(hdr)
   313  		icmpType := "unknown"
   314  		switch icmp.Type() {
   315  		case header.ICMPv6DstUnreachable:
   316  			icmpType = "destination unreachable"
   317  		case header.ICMPv6PacketTooBig:
   318  			icmpType = "packet too big"
   319  		case header.ICMPv6TimeExceeded:
   320  			icmpType = "time exceeded"
   321  		case header.ICMPv6ParamProblem:
   322  			icmpType = "param problem"
   323  		case header.ICMPv6EchoRequest:
   324  			icmpType = "echo request"
   325  		case header.ICMPv6EchoReply:
   326  			icmpType = "echo reply"
   327  		case header.ICMPv6RouterSolicit:
   328  			icmpType = "router solicit"
   329  		case header.ICMPv6RouterAdvert:
   330  			icmpType = "router advert"
   331  		case header.ICMPv6NeighborSolicit:
   332  			icmpType = "neighbor solicit"
   333  		case header.ICMPv6NeighborAdvert:
   334  			icmpType = "neighbor advert"
   335  		case header.ICMPv6RedirectMsg:
   336  			icmpType = "redirect message"
   337  		}
   338  		log.Infof("%s%s %s %s -> %s %s len:%d id:%04x code:%d", prefix, directionPrefix, transName, src, dst, icmpType, size, id, icmp.Code())
   339  		return
   340  
   341  	case header.UDPProtocolNumber:
   342  		transName = "udp"
   343  		if ok := parse.UDP(pkt); !ok {
   344  			break
   345  		}
   346  
   347  		udp := header.UDP(pkt.TransportHeader().View())
   348  		if fragmentOffset == 0 {
   349  			srcPort = udp.SourcePort()
   350  			dstPort = udp.DestinationPort()
   351  			details = fmt.Sprintf("xsum: 0x%x", udp.Checksum())
   352  			size -= header.UDPMinimumSize
   353  		}
   354  
   355  	case header.TCPProtocolNumber:
   356  		transName = "tcp"
   357  		if ok := parse.TCP(pkt); !ok {
   358  			break
   359  		}
   360  
   361  		tcp := header.TCP(pkt.TransportHeader().View())
   362  		if fragmentOffset == 0 {
   363  			offset := int(tcp.DataOffset())
   364  			if offset < header.TCPMinimumSize {
   365  				details += fmt.Sprintf("invalid packet: tcp data offset too small %d", offset)
   366  				break
   367  			}
   368  			if size := pkt.Data().Size() + len(tcp); offset > size && !moreFragments {
   369  				details += fmt.Sprintf("invalid packet: tcp data offset %d larger than tcp packet length %d", offset, size)
   370  				break
   371  			}
   372  
   373  			srcPort = tcp.SourcePort()
   374  			dstPort = tcp.DestinationPort()
   375  			size -= uint16(offset)
   376  
   377  			// Initialize the TCP flags.
   378  			flags := tcp.Flags()
   379  			details = fmt.Sprintf("flags: %s seqnum: %d ack: %d win: %d xsum:0x%x", flags, tcp.SequenceNumber(), tcp.AckNumber(), tcp.WindowSize(), tcp.Checksum())
   380  			if flags&header.TCPFlagSyn != 0 {
   381  				details += fmt.Sprintf(" options: %+v", header.ParseSynOptions(tcp.Options(), flags&header.TCPFlagAck != 0))
   382  			} else {
   383  				details += fmt.Sprintf(" options: %+v", tcp.ParsedOptions())
   384  			}
   385  		}
   386  
   387  	default:
   388  		log.Infof("%s%s %s -> %s unknown transport protocol: %d", prefix, directionPrefix, src, dst, transProto)
   389  		return
   390  	}
   391  
   392  	if pkt.GSOOptions.Type != stack.GSONone {
   393  		details += fmt.Sprintf(" gso: %#v", pkt.GSOOptions)
   394  	}
   395  
   396  	log.Infof("%s%s %s %s:%d -> %s:%d len:%d id:%04x %s", prefix, directionPrefix, transName, src, srcPort, dst, dstPort, size, id, details)
   397  }