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