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