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