github.com/lightlus/netstack@v1.2.0/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/lightlus/netstack/tcpip" 35 "github.com/lightlus/netstack/tcpip/buffer" 36 "github.com/lightlus/netstack/tcpip/header" 37 "github.com/lightlus/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 }