blob: 36c0ac99af653e41917a921e637fd2a09aa82566 [file] [log] [blame]
// Copyright 2018 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package sniffer provides the implementation of data-link layer endpoints that
// wrap another endpoint and logs inbound and outbound packets.
//
// Sniffer endpoints can be used in the networking stack by calling New(eID) to
// create a new endpoint, where eID is the ID of the endpoint being wrapped,
// and then passing it as an argument to Stack.CreateNIC().
package sniffer
import (
"encoding/binary"
"fmt"
"io"
"time"
"gvisor.dev/gvisor/pkg/atomicbitops"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/header/parse"
"gvisor.dev/gvisor/pkg/tcpip/link/nested"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
// LogPackets is a flag used to enable or disable packet logging via the log
// package. Valid values are 0 or 1.
var LogPackets atomicbitops.Uint32 = atomicbitops.FromUint32(1)
// LogPacketsToPCAP is a flag used to enable or disable logging packets to a
// pcap writer. Valid values are 0 or 1. A writer must have been specified when the
// sniffer was created for this flag to have effect.
var LogPacketsToPCAP atomicbitops.Uint32 = atomicbitops.FromUint32(1)
type endpoint struct {
nested.Endpoint
writer io.Writer
maxPCAPLen uint32
logPrefix string
}
var _ stack.GSOEndpoint = (*endpoint)(nil)
var _ stack.LinkEndpoint = (*endpoint)(nil)
var _ stack.NetworkDispatcher = (*endpoint)(nil)
type direction int
const (
directionSend = iota
directionRecv
)
// New creates a new sniffer link-layer endpoint. It wraps around another
// endpoint and logs packets and they traverse the endpoint.
func New(lower stack.LinkEndpoint) stack.LinkEndpoint {
return NewWithPrefix(lower, "")
}
// NewWithPrefix creates a new sniffer link-layer endpoint. It wraps around
// another endpoint and logs packets prefixed with logPrefix as they traverse
// the endpoint.
//
// logPrefix is prepended to the log line without any separators.
// E.g. logPrefix = "NIC:en0/" will produce log lines like
// "NIC:en0/send udp [...]".
func NewWithPrefix(lower stack.LinkEndpoint, logPrefix string) stack.LinkEndpoint {
sniffer := &endpoint{logPrefix: logPrefix}
sniffer.Endpoint.Init(lower, sniffer)
return sniffer
}
func zoneOffset() (int32, error) {
date := time.Date(0, 0, 0, 0, 0, 0, 0, time.Local)
_, offset := date.Zone()
return int32(offset), nil
}
func writePCAPHeader(w io.Writer, maxLen uint32) error {
offset, err := zoneOffset()
if err != nil {
return err
}
return binary.Write(w, binary.BigEndian, pcapHeader{
// From https://wiki.wireshark.org/Development/LibpcapFileFormat
MagicNumber: 0xa1b2c3d4,
VersionMajor: 2,
VersionMinor: 4,
Thiszone: offset,
Sigfigs: 0,
Snaplen: maxLen,
Network: 101, // LINKTYPE_RAW
})
}
// NewWithWriter creates a new sniffer link-layer endpoint. It wraps around
// another endpoint and logs packets as they traverse the endpoint.
//
// Each packet is written to writer in the pcap format in a single Write call
// without synchronization. A sniffer created with this function will not emit
// packets using the standard log package.
//
// snapLen is the maximum amount of a packet to be saved. Packets with a length
// less than or equal to snapLen will be saved in their entirety. Longer
// packets will be truncated to snapLen.
func NewWithWriter(lower stack.LinkEndpoint, writer io.Writer, snapLen uint32) (stack.LinkEndpoint, error) {
if err := writePCAPHeader(writer, snapLen); err != nil {
return nil, err
}
sniffer := &endpoint{
writer: writer,
maxPCAPLen: snapLen,
}
sniffer.Endpoint.Init(lower, sniffer)
return sniffer, nil
}
// DeliverNetworkPacket implements the stack.NetworkDispatcher interface. It is
// called by the link-layer endpoint being wrapped when a packet arrives, and
// logs the packet before forwarding to the actual dispatcher.
func (e *endpoint) DeliverNetworkPacket(protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) {
e.dumpPacket(directionRecv, protocol, pkt)
e.Endpoint.DeliverNetworkPacket(protocol, pkt)
}
func (e *endpoint) dumpPacket(dir direction, protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) {
writer := e.writer
if writer == nil && LogPackets.Load() == 1 {
logPacket(e.logPrefix, dir, protocol, pkt)
}
if writer != nil && LogPacketsToPCAP.Load() == 1 {
packet := pcapPacket{
timestamp: time.Now(),
packet: pkt,
maxCaptureLen: int(e.maxPCAPLen),
}
b, err := packet.MarshalBinary()
if err != nil {
panic(err)
}
if _, err := writer.Write(b); err != nil {
panic(err)
}
}
}
// WritePackets implements the stack.LinkEndpoint interface. It is called by
// higher-level protocols to write packets; it just logs the packet and
// forwards the request to the lower endpoint.
func (e *endpoint) WritePackets(pkts stack.PacketBufferList) (int, tcpip.Error) {
for pkt := pkts.Front(); pkt != nil; pkt = pkt.Next() {
e.dumpPacket(directionSend, pkt.NetworkProtocolNumber, pkt)
}
return e.Endpoint.WritePackets(pkts)
}
func logPacket(prefix string, dir direction, protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) {
// Figure out the network layer info.
var transProto uint8
src := tcpip.Address("unknown")
dst := tcpip.Address("unknown")
var size uint16
var id uint32
var fragmentOffset uint16
var moreFragments bool
var directionPrefix string
switch dir {
case directionSend:
directionPrefix = "send"
case directionRecv:
directionPrefix = "recv"
default:
panic(fmt.Sprintf("unrecognized direction: %d", dir))
}
// Clone the packet buffer to not modify the original.
//
// We don't clone the original packet buffer so that the new packet buffer
// does not have any of its headers set.
//
// We trim the link headers from the cloned buffer as the sniffer doesn't
// handle link headers.
vv := buffer.NewVectorisedView(pkt.Size(), pkt.Views())
vv.TrimFront(len(pkt.VirtioNetHeader().View()))
vv.TrimFront(len(pkt.LinkHeader().View()))
pkt = stack.NewPacketBuffer(stack.PacketBufferOptions{Data: vv})
defer pkt.DecRef()
switch protocol {
case header.IPv4ProtocolNumber:
if ok := parse.IPv4(pkt); !ok {
return
}
ipv4 := header.IPv4(pkt.NetworkHeader().View())
fragmentOffset = ipv4.FragmentOffset()
moreFragments = ipv4.Flags()&header.IPv4FlagMoreFragments == header.IPv4FlagMoreFragments
src = ipv4.SourceAddress()
dst = ipv4.DestinationAddress()
transProto = ipv4.Protocol()
size = ipv4.TotalLength() - uint16(ipv4.HeaderLength())
id = uint32(ipv4.ID())
case header.IPv6ProtocolNumber:
proto, fragID, fragOffset, fragMore, ok := parse.IPv6(pkt)
if !ok {
return
}
ipv6 := header.IPv6(pkt.NetworkHeader().View())
src = ipv6.SourceAddress()
dst = ipv6.DestinationAddress()
transProto = uint8(proto)
size = ipv6.PayloadLength()
id = fragID
moreFragments = fragMore
fragmentOffset = fragOffset
case header.ARPProtocolNumber:
if !parse.ARP(pkt) {
return
}
arp := header.ARP(pkt.NetworkHeader().View())
log.Infof(
"%s%s arp %s (%s) -> %s (%s) valid:%t",
prefix,
directionPrefix,
tcpip.Address(arp.ProtocolAddressSender()), tcpip.LinkAddress(arp.HardwareAddressSender()),
tcpip.Address(arp.ProtocolAddressTarget()), tcpip.LinkAddress(arp.HardwareAddressTarget()),
arp.IsValid(),
)
return
default:
log.Infof("%s%s unknown network protocol", prefix, directionPrefix)
return
}
// Figure out the transport layer info.
transName := "unknown"
srcPort := uint16(0)
dstPort := uint16(0)
details := ""
switch tcpip.TransportProtocolNumber(transProto) {
case header.ICMPv4ProtocolNumber:
transName = "icmp"
hdr, ok := pkt.Data().PullUp(header.ICMPv4MinimumSize)
if !ok {
break
}
icmp := header.ICMPv4(hdr)
icmpType := "unknown"
if fragmentOffset == 0 {
switch icmp.Type() {
case header.ICMPv4EchoReply:
icmpType = "echo reply"
case header.ICMPv4DstUnreachable:
icmpType = "destination unreachable"
case header.ICMPv4SrcQuench:
icmpType = "source quench"
case header.ICMPv4Redirect:
icmpType = "redirect"
case header.ICMPv4Echo:
icmpType = "echo"
case header.ICMPv4TimeExceeded:
icmpType = "time exceeded"
case header.ICMPv4ParamProblem:
icmpType = "param problem"
case header.ICMPv4Timestamp:
icmpType = "timestamp"
case header.ICMPv4TimestampReply:
icmpType = "timestamp reply"
case header.ICMPv4InfoRequest:
icmpType = "info request"
case header.ICMPv4InfoReply:
icmpType = "info reply"
}
}
log.Infof("%s%s %s %s -> %s %s len:%d id:%04x code:%d", prefix, directionPrefix, transName, src, dst, icmpType, size, id, icmp.Code())
return
case header.ICMPv6ProtocolNumber:
transName = "icmp"
hdr, ok := pkt.Data().PullUp(header.ICMPv6MinimumSize)
if !ok {
break
}
icmp := header.ICMPv6(hdr)
icmpType := "unknown"
switch icmp.Type() {
case header.ICMPv6DstUnreachable:
icmpType = "destination unreachable"
case header.ICMPv6PacketTooBig:
icmpType = "packet too big"
case header.ICMPv6TimeExceeded:
icmpType = "time exceeded"
case header.ICMPv6ParamProblem:
icmpType = "param problem"
case header.ICMPv6EchoRequest:
icmpType = "echo request"
case header.ICMPv6EchoReply:
icmpType = "echo reply"
case header.ICMPv6RouterSolicit:
icmpType = "router solicit"
case header.ICMPv6RouterAdvert:
icmpType = "router advert"
case header.ICMPv6NeighborSolicit:
icmpType = "neighbor solicit"
case header.ICMPv6NeighborAdvert:
icmpType = "neighbor advert"
case header.ICMPv6RedirectMsg:
icmpType = "redirect message"
}
log.Infof("%s%s %s %s -> %s %s len:%d id:%04x code:%d", prefix, directionPrefix, transName, src, dst, icmpType, size, id, icmp.Code())
return
case header.UDPProtocolNumber:
transName = "udp"
if ok := parse.UDP(pkt); !ok {
break
}
udp := header.UDP(pkt.TransportHeader().View())
if fragmentOffset == 0 {
srcPort = udp.SourcePort()
dstPort = udp.DestinationPort()
details = fmt.Sprintf("xsum: 0x%x", udp.Checksum())
size -= header.UDPMinimumSize
}
case header.TCPProtocolNumber:
transName = "tcp"
if ok := parse.TCP(pkt); !ok {
break
}
tcp := header.TCP(pkt.TransportHeader().View())
if fragmentOffset == 0 {
offset := int(tcp.DataOffset())
if offset < header.TCPMinimumSize {
details += fmt.Sprintf("invalid packet: tcp data offset too small %d", offset)
break
}
if size := pkt.Data().Size() + len(tcp); offset > size && !moreFragments {
details += fmt.Sprintf("invalid packet: tcp data offset %d larger than tcp packet length %d", offset, size)
break
}
srcPort = tcp.SourcePort()
dstPort = tcp.DestinationPort()
size -= uint16(offset)
// Initialize the TCP flags.
flags := tcp.Flags()
details = fmt.Sprintf("flags: %s seqnum: %d ack: %d win: %d xsum:0x%x", flags, tcp.SequenceNumber(), tcp.AckNumber(), tcp.WindowSize(), tcp.Checksum())
if flags&header.TCPFlagSyn != 0 {
details += fmt.Sprintf(" options: %+v", header.ParseSynOptions(tcp.Options(), flags&header.TCPFlagAck != 0))
} else {
details += fmt.Sprintf(" options: %+v", tcp.ParsedOptions())
}
}
default:
log.Infof("%s%s %s -> %s unknown transport protocol: %d", prefix, directionPrefix, src, dst, transProto)
return
}
if pkt.GSOOptions.Type != stack.GSONone {
details += fmt.Sprintf(" gso: %#v", pkt.GSOOptions)
}
log.Infof("%s%s %s %s:%d -> %s:%d len:%d id:%04x %s", prefix, directionPrefix, transName, src, srcPort, dst, dstPort, size, id, details)
}