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