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