gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/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 "gvisor.dev/gvisor/pkg/atomicbitops" 30 "gvisor.dev/gvisor/pkg/log" 31 "gvisor.dev/gvisor/pkg/tcpip" 32 "gvisor.dev/gvisor/pkg/tcpip/header" 33 "gvisor.dev/gvisor/pkg/tcpip/header/parse" 34 "gvisor.dev/gvisor/pkg/tcpip/link/nested" 35 "gvisor.dev/gvisor/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.PacketBuffer) { 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.PacketBuffer) { 142 writer := e.writer 143 if 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.PacketBuffer) { 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 clone := trimmedClone(pkt) 194 defer clone.DecRef() 195 switch protocol { 196 case header.IPv4ProtocolNumber: 197 if ok := parse.IPv4(clone); !ok { 198 return 199 } 200 201 ipv4 := header.IPv4(clone.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(clone) 212 if !ok { 213 return 214 } 215 216 ipv6 := header.IPv6(clone.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(clone) { 227 return 228 } 229 230 arp := header.ARP(clone.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 := clone.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 := clone.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(clone); !ok { 326 break 327 } 328 329 udp := header.UDP(clone.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(clone); !ok { 340 break 341 } 342 343 tcp := header.TCP(clone.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 := clone.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.PacketBuffer) *stack.PacketBuffer { 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 }