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