├── dropspy.png ├── cmd └── dropspy │ ├── dropspy │ └── main.go ├── ifaces.go ├── go.mod ├── LICENSE ├── README.md ├── netlink_test.go ├── go.sum ├── protocol.go └── drop_mon.go /dropspy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smallnest/dropspy/HEAD/dropspy.png -------------------------------------------------------------------------------- /cmd/dropspy/dropspy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smallnest/dropspy/HEAD/cmd/dropspy/dropspy -------------------------------------------------------------------------------- /ifaces.go: -------------------------------------------------------------------------------- 1 | package dropspy 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | // LinkList returns a map from interface index to interface name. 8 | func LinkList() (map[uint32]string, error) { 9 | ret := map[uint32]string{} 10 | 11 | ifaces, err := net.Interfaces() 12 | if err != nil { 13 | return nil, err 14 | } 15 | 16 | for _, iface := range ifaces { 17 | ret[uint32(iface.Index)] = iface.Name 18 | } 19 | 20 | return ret, nil 21 | } 22 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/smallnest/dropspy 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | github.com/google/gopacket v1.1.19 7 | github.com/mdlayher/genetlink v1.3.2 8 | github.com/mdlayher/netlink v1.7.2 9 | github.com/spf13/pflag v1.0.5 10 | ) 11 | 12 | require ( 13 | github.com/google/go-cmp v0.6.0 // indirect 14 | github.com/josharian/native v1.1.0 // indirect 15 | github.com/mdlayher/socket v0.5.1 // indirect 16 | golang.org/x/net v0.32.0 // indirect 17 | golang.org/x/sync v0.10.0 // indirect 18 | golang.org/x/sys v0.28.0 // indirect 19 | ) 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Thomas H. Ptacek 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dropspy 2 | dropspy is reworking of the C-language [dropwatch](https://github.com/nhorman/dropwatch) tool in Go, with some extra features. 3 | 4 | The original version `dropspy` is at [superfly/dropspy](https://github.com/superfly/dropspy), 5 | 6 | 7 | ## Installation 8 | 9 | ```bash 10 | go install github.com/smallnest/dropspy/cmd/dropspy@latest 11 | ``` 12 | 13 | ## Usage 14 | 15 | ![](./dropspy.png) 16 | 17 | ```bash 18 | ./dropspy: Report packet drops from Linux kernel DM_MON. 19 | ./dropspy [flags] [pcap filter] 20 | ie: ./dropspy --hex -I eth0 udp port 53 21 | -c, --count uint maximum drops to record 22 | --hex print hex dumps of matching packets 23 | --hw record hardware drops (default true) 24 | -I, --iface stringArray show only drops on this interface (may be repeated) 25 | --isym stringArray include drops from syms matching regexp (may be repeated) 26 | --maxlen uint maximum packet length for drops 27 | --minlen uint minimum packet length for drops 28 | --summary print summary of drops 29 | --sw record software drops (default true) 30 | -w, --timeout string duration to capture for (300ms, 2h15m, &c) 31 | --xsym stringArray exclude drops from syms matching regexp (may be repeated) 32 | ``` 33 | 34 | You can only print the summary of drops per second: 35 | 36 | ```bash 37 | ./dropspy --summary 38 | ``` 39 | 40 | ## License 41 | [MIT](https://choosealicense.com/licenses/mit/) 42 | -------------------------------------------------------------------------------- /netlink_test.go: -------------------------------------------------------------------------------- 1 | package dropspy 2 | 3 | import ( 4 | "encoding/hex" 5 | "testing" 6 | "time" 7 | 8 | "github.com/mdlayher/genetlink" 9 | ) 10 | 11 | func TestHello(t *testing.T) { 12 | conn, err := genetlink.Dial(nil) 13 | if err != nil { 14 | t.Fatalf("dial: %s", err) 15 | } 16 | 17 | fams, err := conn.ListFamilies() 18 | if err != nil { 19 | t.Fatalf("list: %s", err) 20 | } 21 | 22 | for _, fam := range fams { 23 | t.Logf("%+v", fam) 24 | } 25 | } 26 | 27 | func TestSession(t *testing.T) { 28 | s, err := NewSession(nil) 29 | if err != nil { 30 | t.Fatalf("init: %s", err) 31 | } 32 | 33 | conf, err := s.Config() 34 | if err != nil { 35 | t.Fatalf("config: %s", err) 36 | } 37 | 38 | t.Logf("%+v", conf) 39 | } 40 | 41 | func TestWatch(t *testing.T) { 42 | s, err := NewSession(nil) 43 | if err != nil { 44 | t.Fatalf("init: %s", err) 45 | } 46 | 47 | s.Stop(true, true) 48 | 49 | err = s.Start(true, false) 50 | if err != nil { 51 | t.Fatalf("start: %s", err) 52 | } 53 | 54 | defer func() { 55 | s.Stop(true, true) 56 | }() 57 | 58 | deadline := time.Now().Add(15 * time.Second) 59 | 60 | err = s.ReadUntil(deadline, func(pa PacketAlert) bool { 61 | t.Logf("drop at %s:%016x\n%s", pa.Symbol(), pa.PC(), hex.Dump(pa.L3Packet())) 62 | 63 | t.Logf("%+v", pa.attrs[ATTR_IN_PORT]) 64 | 65 | return true 66 | }) 67 | if err != nil { 68 | t.Fatalf("readuntil: %s", err) 69 | } 70 | } 71 | 72 | func TestLinks(t *testing.T) { 73 | links, err := LinkList() 74 | if err != nil { 75 | t.Fatalf("links: %s", err) 76 | } 77 | 78 | t.Logf("%+v", links) 79 | } 80 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 2 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 3 | github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= 4 | github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= 5 | github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= 6 | github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= 7 | github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= 8 | github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o= 9 | github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= 10 | github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= 11 | github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= 12 | github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= 13 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 14 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 15 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 16 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 17 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 18 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 19 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 20 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 21 | golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= 22 | golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= 23 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 24 | golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= 25 | golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 26 | golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= 27 | golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 28 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 29 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 30 | golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= 31 | golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 32 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 33 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 34 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 35 | -------------------------------------------------------------------------------- /protocol.go: -------------------------------------------------------------------------------- 1 | package dropspy 2 | 3 | // Third layer protocol 4 | const ( 5 | ETH_P_LOOP = 0x0060 // Ethernet Loopback packet 6 | ETH_P_PUP = 0x0200 // Xerox PUP packet 7 | ETH_P_PUPAT = 0x0201 // Xerox PUP Addr Trans packet 8 | ETH_P_TSN = 0x22F0 // TSN (IEEE 1722) packet 9 | ETH_P_ERSPAN2 = 0x22EB // ERSPAN version 2 (type III) 10 | ETH_P_IP = 0x0800 // Internet Protocol packet 11 | ETH_P_X25 = 0x0805 // CCITT X.25 12 | ETH_P_ARP = 0x0806 // Address Resolution packet 13 | ETH_P_BPQ = 0x08FF // G8BPQ AX.25 Ethernet Packet [ NOT AN OFFICIALLY REGISTERED ID ] 14 | ETH_P_IEEEPUP = 0x0a00 // Xerox IEEE802.3 PUP packet 15 | ETH_P_IEEEPUPAT = 0x0a01 // Xerox IEEE802.3 PUP Addr Trans packet 16 | ETH_P_BATMAN = 0x4305 // B.A.T.M.A.N.-Advanced packet [ NOT AN OFFICIALLY REGISTERED ID ] 17 | ETH_P_DEC = 0x6000 // DEC Assigned proto 18 | ETH_P_DNA_DL = 0x6001 // DEC DNA Dump/Load 19 | ETH_P_DNA_RC = 0x6002 // DEC DNA Remote Console 20 | ETH_P_DNA_RT = 0x6003 // DEC DNA Routing 21 | ETH_P_LAT = 0x6004 // DEC LAT 22 | ETH_P_DIAG = 0x6005 // DEC Diagnostics 23 | ETH_P_CUST = 0x6006 // DEC Customer use 24 | ETH_P_SCA = 0x6007 // DEC Systems Comms Arch 25 | ETH_P_TEB = 0x6558 // Trans Ether Bridging 26 | ETH_P_RARP = 0x8035 // Reverse Addr Res packet 27 | ETH_P_ATALK = 0x809B // Appletalk DDP 28 | ETH_P_AARP = 0x80F3 // Appletalk AARP 29 | ETH_P_8021Q = 0x8100 // 802.1Q VLAN Extended Header 30 | ETH_P_ERSPAN = 0x88BE // ERSPAN type II 31 | ETH_P_IPX = 0x8137 // IPX over DIX 32 | ETH_P_IPV6 = 0x86DD // IPv6 over bluebook 33 | ETH_P_PAUSE = 0x8808 // IEEE Pause frames. See 802.3 31B 34 | ETH_P_SLOW = 0x8809 // Slow Protocol. See 802.3ad 43B 35 | ETH_P_WCCP = 0x883E // Web-cache coordination protocol defined in draft-wilson-wrec-wccp-v2-00.txt 36 | ETH_P_MPLS_UC = 0x8847 // MPLS Unicast traffic 37 | ETH_P_MPLS_MC = 0x8848 // MPLS Multicast traffic 38 | ETH_P_ATMMPOA = 0x884c // MultiProtocol Over ATM 39 | ETH_P_PPP_DISC = 0x8863 // PPPoE discovery messages 40 | ETH_P_PPP_SES = 0x8864 // PPPoE session messages 41 | ETH_P_LINK_CTL = 0x886c // HPNA, wlan link local tunnel 42 | ETH_P_ATMFATE = 0x8884 // Frame-based ATM Transport over Ethernet 43 | ETH_P_PAE = 0x888E // Port Access Entity (IEEE 802.1X) 44 | ETH_P_PROFINET = 0x8892 // PROFINET 45 | ETH_P_REALTEK = 0x8899 // Multiple proprietary protocols 46 | ETH_P_AOE = 0x88A2 // ATA over Ethernet 47 | ETH_P_ETHERCAT = 0x88A4 // EtherCAT 48 | ETH_P_8021AD = 0x88A8 // 802.1ad Service VLAN 49 | ETH_P_802_EX1 = 0x88B5 // 802.1 Local Experimental 1. 50 | ETH_P_PREAUTH = 0x88C7 // 802.11 Preauthentication 51 | ETH_P_TIPC = 0x88CA // TIPC 52 | ETH_P_LLDP = 0x88CC // Link Layer Discovery Protocol 53 | ETH_P_MRP = 0x88E3 // Media Redundancy Protocol 54 | ETH_P_MACSEC = 0x88E5 // 802.1ae MACsec 55 | ETH_P_8021AH = 0x88E7 // 802.1ah Backbone Service Tag 56 | ETH_P_MVRP = 0x88F5 // 802.1Q MVRP 57 | ETH_P_1588 = 0x88F7 // IEEE 1588 Timesync 58 | ETH_P_NCSI = 0x88F8 // NCSI protocol 59 | ETH_P_PRP = 0x88FB // IEC 62439-3 PRP/HSRv0 60 | ETH_P_CFM = 0x8902 // Connectivity Fault Management 61 | ETH_P_FCOE = 0x8906 // Fibre Channel over Ethernet 62 | ETH_P_IBOE = 0x8915 // Infiniband over Ethernet 63 | ETH_P_TDLS = 0x890D // TDLS 64 | ETH_P_FIP = 0x8914 // FCoE Initialization Protocol 65 | ETH_P_80221 = 0x8917 // IEEE 802.21 Media Independent Handover Protocol 66 | ETH_P_HSR = 0x892F // IEC 62439-3 HSRv1 67 | ETH_P_NSH = 0x894F // Network Service Header 68 | ETH_P_LOOPBACK = 0x9000 // Ethernet loopback packet, per IEEE 802.3 69 | ETH_P_QINQ1 = 0x9100 // deprecated QinQ VLAN [ NOT AN OFFICIALLY REGISTERED ID ] 70 | ETH_P_QINQ2 = 0x9200 // deprecated QinQ VLAN [ NOT AN OFFICIALLY REGISTERED ID ] 71 | ETH_P_QINQ3 = 0x9300 // deprecated QinQ VLAN [ NOT AN OFFICIALLY REGISTERED ID ] 72 | ETH_P_EDSA = 0xDADA // Ethertype DSA [ NOT AN OFFICIALLY REGISTERED ID ] 73 | ETH_P_DSA_8021Q = 0xDADB // Fake VLAN Header for DSA [ NOT AN OFFICIALLY REGISTERED ID ] 74 | ETH_P_DSA_A5PSW = 0xE001 // A5PSW Tag Value [ NOT AN OFFICIALLY REGISTERED ID ] 75 | ETH_P_IFE = 0xED3E // ForCES inter-FE LFB type 76 | ETH_P_AF_IUCV = 0xFBFB // IBM af_iucv [ NOT AN OFFICIALLY REGISTERED ID ] 77 | ETH_P_802_3_MIN = 0x0600 // If the value in the ethernet type is more than this value then the frame is Ethernet II. Else it is 802.3 78 | 79 | // Non DIX types. Won't clash for 1500 types. 80 | ETH_P_802_3 = 0x0001 // Dummy type for 802.3 frames 81 | ETH_P_AX25 = 0x0002 // Dummy protocol id for AX.25 82 | ETH_P_ALL = 0x0003 // Every packet (be careful!!!) 83 | ETH_P_802_2 = 0x0004 // 802.2 frames 84 | ETH_P_SNAP = 0x0005 // Internal only 85 | ETH_P_DDCMP = 0x0006 // DEC DDCMP: Internal only 86 | ETH_P_WAN_PPP = 0x0007 // Dummy type for WAN PPP frames 87 | ETH_P_PPP_MP = 0x0008 // Dummy type for PPP MP frames 88 | ETH_P_LOCALTALK = 0x0009 // Localtalk pseudo type 89 | ETH_P_CAN = 0x000C // CAN: Controller Area Network 90 | ETH_P_CANFD = 0x000D // CANFD: CAN flexible data rate 91 | ETH_P_CANXL = 0x000E // CANXL: eXtended frame Length 92 | ETH_P_PPPTALK = 0x0010 // Dummy type for Atalk over PPP 93 | ETH_P_TR_802_2 = 0x0011 // 802.2 frames 94 | ETH_P_MOBITEX = 0x0015 // Mobitex (kaz@cafe.net) 95 | ETH_P_CONTROL = 0x0016 // Card specific control frames 96 | ETH_P_IRDA = 0x0017 // Linux-IrDA 97 | ETH_P_ECONET = 0x0018 // Acorn Econet 98 | ETH_P_HDLC = 0x0019 // HDLC frames 99 | ETH_P_ARCNET = 0x001A // 1A for ArcNet :-) 100 | ETH_P_DSA = 0x001B // Distributed Switch Arch. 101 | ETH_P_TRAILER = 0x001C // Trailer switch tagging 102 | ETH_P_PHONET = 0x00F5 // Nokia Phonet frames 103 | ETH_P_IEEE802154 = 0x00F6 // IEEE802.15.4 frame 104 | ETH_P_CAIF = 0x00F7 // ST-Ericsson CAIF protocol 105 | ETH_P_XDSA = 0x00F8 // Multiplexed DSA protocol 106 | ETH_P_MAP = 0x00F9 // Qualcomm multiplexing and aggregation protocol 107 | ETH_P_MCTP = 0x00FA // Management component transport protocol packets 108 | ) 109 | 110 | var ethTypeToString = map[uint16]string{ 111 | 0x0060: "ETH_P_LOOP", 112 | 0x0200: "ETH_P_PUP", 113 | 0x0201: "ETH_P_PUPAT", 114 | 0x22F0: "ETH_P_TSN", 115 | 0x22EB: "ETH_P_ERSPAN2", 116 | 0x0800: "ETH_P_IP", 117 | 0x0805: "ETH_P_X25", 118 | 0x0806: "ETH_P_ARP", 119 | 0x08FF: "ETH_P_BPQ", 120 | 0x0a00: "ETH_P_IEEEPUP", 121 | 0x0a01: "ETH_P_IEEEPUPAT", 122 | 0x4305: "ETH_P_BATMAN", 123 | 0x6000: "ETH_P_DEC", 124 | 0x6001: "ETH_P_DNA_DL", 125 | 0x6002: "ETH_P_DNA_RC", 126 | 0x6003: "ETH_P_DNA_RT", 127 | 0x6004: "ETH_P_LAT", 128 | 0x6005: "ETH_P_DIAG", 129 | 0x6006: "ETH_P_CUST", 130 | 0x6007: "ETH_P_SCA", 131 | 0x6558: "ETH_P_TEB", 132 | 0x8035: "ETH_P_RARP", 133 | 0x809B: "ETH_P_ATALK", 134 | 0x80F3: "ETH_P_AARP", 135 | 0x8100: "ETH_P_8021Q", 136 | 0x88BE: "ETH_P_ERSPAN", 137 | 0x8137: "ETH_P_IPX", 138 | 0x86DD: "ETH_P_IPV6", 139 | 0x8808: "ETH_P_PAUSE", 140 | 0x8809: "ETH_P_SLOW", 141 | 0x883E: "ETH_P_WCCP", 142 | 0x8847: "ETH_P_MPLS_UC", 143 | 0x8848: "ETH_P_MPLS_MC", 144 | 0x884c: "ETH_P_ATMMPOA", 145 | 0x8863: "ETH_P_PPP_DISC", 146 | 0x8864: "ETH_P_PPP_SES", 147 | 0x886c: "ETH_P_LINK_CTL", 148 | 0x8884: "ETH_P_ATMFATE", 149 | 0x888E: "ETH_P_PAE", 150 | 0x8892: "ETH_P_PROFINET", 151 | 0x8899: "ETH_P_REALTEK", 152 | 0x88A2: "ETH_P_AOE", 153 | 0x88A4: "ETH_P_ETHERCAT", 154 | 0x88A8: "ETH_P_8021AD", 155 | 0x88B5: "ETH_P_802_EX1", 156 | 0x88C7: "ETH_P_PREAUTH", 157 | 0x88CA: "ETH_P_TIPC", 158 | 0x88CC: "ETH_P_LLDP", 159 | 0x88E3: "ETH_P_MRP", 160 | 0x88E5: "ETH_P_MACSEC", 161 | 0x88E7: "ETH_P_8021AH", 162 | 0x88F5: "ETH_P_MVRP", 163 | 0x88F7: "ETH_P_1588", 164 | 0x88F8: "ETH_P_NCSI", 165 | 0x88FB: "ETH_P_PRP", 166 | 0x8902: "ETH_P_CFM", 167 | 0x8906: "ETH_P_FCOE", 168 | 0x8915: "ETH_P_IBOE", 169 | 0x890D: "ETH_P_TDLS", 170 | 0x8914: "ETH_P_FIP", 171 | 0x8917: "ETH_P_80221", 172 | 0x892F: "ETH_P_HSR", 173 | 0x894F: "ETH_P_NSH", 174 | 0x9000: "ETH_P_LOOPBACK", 175 | 0x9100: "ETH_P_QINQ1", 176 | 0x9200: "ETH_P_QINQ2", 177 | 0x9300: "ETH_P_QINQ3", 178 | 0xDADA: "ETH_P_EDSA", 179 | 0xDADB: "ETH_P_DSA_8021Q", 180 | 0xE001: "ETH_P_DSA_A5PSW", 181 | 0xED3E: "ETH_P_IFE", 182 | 0xFBFB: "ETH_P_AF_IUCV", 183 | 0x0600: "ETH_P_802_3_MIN", 184 | 0x0001: "ETH_P_802_3", 185 | 0x0002: "ETH_P_AX25", 186 | 0x0003: "ETH_P_ALL", 187 | 0x0004: "ETH_P_802_2", 188 | 0x0005: "ETH_P_SNAP", 189 | 0x0006: "ETH_P_DDCMP", 190 | 0x0007: "ETH_P_WAN_PPP", 191 | 0x0008: "ETH_P_PPP_MP", 192 | 0x0009: "ETH_P_LOCALTALK", 193 | 0x000C: "ETH_P_CAN", 194 | 0x000D: "ETH_P_CANFD", 195 | 0x000E: "ETH_P_CANXL", 196 | 0x0010: "ETH_P_PPPTALK", 197 | 0x0011: "ETH_P_TR_802_2", 198 | 0x0015: "ETH_P_MOBITEX", 199 | 0x0016: "ETH_P_CONTROL", 200 | 0x0017: "ETH_P_IRDA", 201 | 0x0018: "ETH_P_ECONET", 202 | 0x0019: "ETH_P_HDLC", 203 | 0x001A: "ETH_P_ARCNET", 204 | 0x001B: "ETH_P_DSA", 205 | 0x001C: "ETH_P_TRAILER", 206 | 0x00F5: "ETH_P_PHONET", 207 | 0x00F6: "ETH_P_IEEE802154", 208 | 0x00F7: "ETH_P_CAIF", 209 | 0x00F8: "ETH_P_XDSA", 210 | 0x00F9: "ETH_P_MAP", 211 | 0x00FA: "ETH_P_MCTP", 212 | } 213 | 214 | func EthTypeToString(ethType uint16) string { 215 | if name, ok := ethTypeToString[ethType]; ok { 216 | return name 217 | } 218 | return "UNKNOWN" 219 | } 220 | -------------------------------------------------------------------------------- /cmd/dropspy/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | "log" 7 | "os" 8 | "os/signal" 9 | "regexp" 10 | "strings" 11 | "syscall" 12 | "time" 13 | 14 | "github.com/google/gopacket" 15 | "github.com/google/gopacket/layers" 16 | "github.com/google/gopacket/pcap" 17 | "github.com/smallnest/dropspy" 18 | "github.com/spf13/pflag" 19 | ) 20 | 21 | // filter struct is used to store filter conditions 22 | type filter struct { 23 | ifaces map[uint32]bool // Mapping of interface indices 24 | min, max uint // Minimum and maximum packet length 25 | xSym, iSym []*regexp.Regexp // Exclude and include symbol regex 26 | bpf *pcap.BPF // BPF filter 27 | } 28 | 29 | // Match method is used to check if a packet matches the filter conditions 30 | func (f *filter) Match(pa *dropspy.PacketAlert) bool { 31 | if len(f.ifaces) > 0 { 32 | if !f.ifaces[pa.Link()] { // Check if the packet is from the specified interface 33 | return false 34 | } 35 | } 36 | 37 | plen := uint(pa.Length()) // Get packet length 38 | 39 | if f.min != 0 && plen < f.min { // Check minimum length 40 | return false 41 | } 42 | 43 | if f.max != 0 && plen > f.max { // Check maximum length 44 | return false 45 | } 46 | 47 | sym := pa.Symbol() // Get packet symbol 48 | 49 | if len(f.xSym) != 0 { 50 | for _, rx := range f.xSym { 51 | if rx.MatchString(sym) { // Check if it matches the exclude symbol 52 | return false 53 | } 54 | } 55 | } 56 | 57 | if len(f.iSym) != 0 { 58 | for _, rx := range f.iSym { 59 | if !rx.MatchString(sym) { // Check if it matches the include symbol 60 | return false 61 | } 62 | } 63 | } 64 | 65 | if f.bpf != nil { 66 | packet := pa.Packet() // Get raw packet 67 | 68 | ci := gopacket.CaptureInfo{ 69 | CaptureLength: len(packet), // Capture length 70 | Length: int(plen), // Packet length 71 | } 72 | 73 | if !f.bpf.Matches(ci, packet) { // Check BPF filter 74 | return false 75 | } 76 | } 77 | 78 | return true // Return true if all conditions match 79 | } 80 | 81 | var ( 82 | packetModeTruncation int = 100 // Packet mode truncation length 83 | ) 84 | 85 | func main() { 86 | var ( 87 | printHex bool // Whether to print hex data 88 | ifaces []string // Interface parameters 89 | xsyms []string // Exclude symbol parameters 90 | isyms []string // Include symbol parameters 91 | maxDrops uint64 // Maximum drop count 92 | timeout string // Capture timeout 93 | hw, sw bool // Hardware and software drop flags 94 | 95 | filter filter // Filter instance 96 | 97 | summary bool // Summary flag 98 | 99 | err error // Error variable 100 | ) 101 | 102 | // Define command line flags 103 | pflag.StringArrayVarP(&ifaces, "iface", "I", nil, "show only drops on this interface (may be repeated)") // Show only drops on specified interface 104 | pflag.StringArrayVar(&xsyms, "xsym", nil, "exclude drops from syms matching regexp (may be repeated)") // Exclude symbols matching regex 105 | pflag.StringArrayVar(&isyms, "isym", nil, "include drops from syms matching regexp (may be repeated)") // Include symbols matching regex 106 | pflag.UintVar(&filter.min, "minlen", 0, "minimum packet length for drops") // Set minimum packet length 107 | pflag.UintVar(&filter.max, "maxlen", 0, "maximum packet length for drops") // Set maximum packet length 108 | pflag.Uint64VarP(&maxDrops, "count", "c", 0, "maximum drops to record") // Set maximum drop count 109 | pflag.StringVarP(&timeout, "timeout", "w", "", "duration to capture for (300ms, 2h15m, &c)") // Set capture timeout 110 | pflag.BoolVar(&hw, "hw", true, "record hardware drops") // Record hardware drops 111 | pflag.BoolVar(&sw, "sw", true, "record software drops") // Record software drops 112 | pflag.BoolVar(&printHex, "hex", false, "print hex dumps of matching packets") // Print hex dumps of matching packets 113 | pflag.BoolVar(&summary, "summary", false, "print summary of drops") // Print summary of drops 114 | // pflag.BoolP("help", "h", false, "") 115 | 116 | // Set usage instructions 117 | pflag.Usage = func() { 118 | fmt.Fprintf(os.Stderr, "%s: Report packet drops from Linux kernel DM_MON.\n", os.Args[0]) 119 | fmt.Fprintf(os.Stderr, "%s [flags] [pcap filter]\n", os.Args[0]) 120 | fmt.Fprintf(os.Stderr, "ie: %s --hex -I eth0 udp port 53\n", os.Args[0]) 121 | pflag.PrintDefaults() // Print default values of all flags 122 | } 123 | 124 | pflag.ErrHelp = fmt.Errorf("") 125 | pflag.Parse() // Parse command line flags 126 | 127 | pcapExpr := strings.Join(pflag.Args(), " ") // Get pcap filter expression 128 | if pcapExpr != "" { 129 | filter.bpf, err = pcap.NewBPF(layers.LinkTypeEthernet, packetModeTruncation, pcapExpr) // Create BPF filter 130 | if err != nil { 131 | fmt.Fprintf(os.Stderr, "pcap expression: %s\n", err) // Print error message 132 | os.Exit(1) // Exit program 133 | } 134 | } 135 | 136 | if len([]string(xsyms)) > 0 && len([]string(isyms)) > 0 { 137 | fmt.Fprintf(os.Stderr, "-xsym and -isym are mutually exclusive\n") // Exclude and include symbols cannot be used together 138 | os.Exit(1) // Exit program 139 | } 140 | 141 | // Compile exclude symbol regex 142 | for _, symexpr := range []string(xsyms) { 143 | rx, err := regexp.Compile(symexpr) // Compile regex 144 | if err != nil { 145 | fmt.Fprintf(os.Stderr, "regexp compile %s: %s\n", symexpr, err) // Print error message 146 | os.Exit(1) // Exit program 147 | } 148 | 149 | filter.xSym = append(filter.xSym, rx) // Add to filter 150 | } 151 | 152 | // Compile include symbol regex 153 | for _, symexpr := range []string(isyms) { 154 | rx, err := regexp.Compile(symexpr) // Compile regex 155 | if err != nil { 156 | fmt.Fprintf(os.Stderr, "regexp compile %s: %s\n", symexpr, err) // Print error message 157 | os.Exit(1) // Exit program 158 | } 159 | 160 | filter.iSym = append(filter.iSym, rx) // Add to filter 161 | } 162 | 163 | links, err := dropspy.LinkList() // Get network interface list 164 | if err != nil { 165 | fmt.Fprintf(os.Stderr, "retrieve links: %s\n", err) // Print error message 166 | os.Exit(1) // Exit program 167 | } 168 | 169 | filter.ifaces = map[uint32]bool{} // Initialize interface mapping 170 | 171 | // Handle specified interfaces 172 | for _, iface := range []string(ifaces) { 173 | var rx *regexp.Regexp 174 | 175 | if strings.HasPrefix(iface, "/") && strings.HasSuffix(iface, "/") { 176 | rx, err = regexp.Compile(iface[1 : len(iface)-2]) // Compile regex 177 | if err != nil { 178 | fmt.Fprintf(os.Stderr, "compile interface regexp for %s: %s\n", iface[1:len(iface)-2], err) // Print error message 179 | os.Exit(1) // Exit program 180 | } 181 | } else { 182 | rx, err = regexp.Compile("^" + iface + "$") // Compile exact match regex 183 | if err != nil { 184 | fmt.Fprintf(os.Stderr, "compile interface regexp for %s: %s\n", iface, err) // Print error message 185 | os.Exit(1) // Exit program 186 | } 187 | } 188 | 189 | found := false 190 | for k, v := range links { 191 | if v == iface { 192 | if rx.MatchString(v) { // Check if it matches 193 | filter.ifaces[k] = true // Add interface to filter 194 | found = true 195 | break 196 | } 197 | } 198 | } 199 | 200 | if !found { 201 | fmt.Fprintf(os.Stderr, "no such interface '%s'\n", iface) // Print error message 202 | os.Exit(1) // Exit program 203 | } 204 | } 205 | 206 | session, err := dropspy.NewSession(links) // Create new dropspy session 207 | if err != nil { 208 | fmt.Fprintf(os.Stderr, "connect to drop_mon: %s\n", err) // Print error message 209 | os.Exit(1) // Exit program 210 | } 211 | 212 | sigCh := make(chan os.Signal, 1) // Create signal channel 213 | signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT) // Listen for interrupt signal 214 | go func() { 215 | _ = <-sigCh // Wait for signal 216 | fmt.Fprintf(os.Stderr, "got C-c: cleaning up and exiting\n") // Print cleanup message 217 | session.Stop(true, true) // Stop session 218 | os.Exit(1) // Exit program 219 | }() 220 | 221 | defer func() { 222 | session.Stop(true, true) // Ensure session stops on exit 223 | }() 224 | 225 | err = session.Start(sw, hw) // Start session 226 | if err != nil { 227 | fmt.Fprintf(os.Stderr, "enable drop_mon alerts: %s\n", err) // Print error message 228 | os.Exit(1) // Exit program 229 | } 230 | 231 | var deadline time.Time // Define deadline 232 | 233 | if timeout != "" { 234 | dur, err := time.ParseDuration(timeout) // Parse timeout 235 | if err != nil { 236 | fmt.Fprintf(os.Stderr, "can't parse timeout: %s\n", err) // Print error message 237 | os.Exit(1) // Exit program 238 | } 239 | 240 | deadline = time.Now().Add(dur) // Set deadline 241 | } 242 | 243 | dropCount := uint64(0) // Initialize drop count 244 | 245 | var summaries = make(map[string]int) 246 | 247 | defer func() { 248 | if summary { 249 | for k, v := range summaries { 250 | fmt.Printf("%s: %d\n", k, v) 251 | } 252 | } 253 | }() 254 | 255 | start := time.Now() 256 | for { 257 | err = session.ReadUntil(deadline, func(pa dropspy.PacketAlert) bool { 258 | if filter.Match(&pa) { // Check if packet matches filter conditions 259 | dropCount += 1 // Increment drop count 260 | 261 | if summary { 262 | key := fmt.Sprintf("%s (%016x) [%s] [%s]", pa.Symbol(), pa.PC(), dropspy.GetOrigin(&pa), dropspy.GetDropReason(&pa)) 263 | summaries[key] += 1 264 | 265 | now := time.Now() 266 | if now.Sub(start) > time.Second { 267 | start = now 268 | for k, v := range summaries { 269 | if strings.HasSuffix(k, " []") { 270 | k = k[:len(k)-3] 271 | } 272 | fmt.Printf("%d drops at %s\n", v, k) 273 | } 274 | summaries = make(map[string]int) 275 | } 276 | } else { 277 | // Record drop information 278 | pa.Output(links) 279 | if printHex { 280 | fmt.Println(hex.Dump(pa.L3Packet())) // Print hex data 281 | } 282 | log.Println("----------------") // Separator 283 | } 284 | 285 | if maxDrops != 0 && dropCount == maxDrops { // Check if maximum drop count is reached 286 | fmt.Fprintf(os.Stderr, "maximum drops reached, exiting\n") // Print message 287 | return false // Stop reading 288 | } 289 | } 290 | 291 | return true // Continue reading 292 | }) 293 | if err != nil { 294 | fmt.Fprintf(os.Stderr, "read: %s\n", err) // Print error message 295 | time.Sleep(250 * time.Millisecond) // Pause for a while 296 | } else { 297 | return // Exit loop 298 | } 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /drop_mon.go: -------------------------------------------------------------------------------- 1 | package dropspy 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | "time" 8 | 9 | "github.com/mdlayher/genetlink" 10 | "github.com/mdlayher/netlink" 11 | ) 12 | 13 | // These constants are extracted from the 5.6 mainline 14 | // include/uapi/linux/net_dropmon.h 15 | 16 | const ( 17 | CMD_UNSPEC = iota 18 | CMD_ALERT // 1 19 | CMD_CONFIG 20 | CMD_START 21 | CMD_STOP 22 | CMD_PACKET_ALERT // 5 23 | CMD_CONFIG_GET 24 | CMD_CONFIG_NEW 25 | CMD_STATS_GET 26 | CMD_STATS_NEW 27 | ) 28 | 29 | const ( 30 | ATTR_UNSPEC = iota 31 | ATTR_ALERT_MODE /* u8 */ // 1 32 | ATTR_PC /* u64 */ 33 | ATTR_SYMBOL /* string */ 34 | ATTR_IN_PORT /* nested */ 35 | ATTR_TIMESTAMP /* u64 */ // 5 36 | ATTR_PROTO /* u16 */ 37 | ATTR_PAYLOAD /* binary */ 38 | ATTR_PAD 39 | ATTR_TRUNC_LEN /* u32 */ 40 | ATTR_ORIG_LEN /* u32 */ // 10 41 | ATTR_QUEUE_LEN /* u32 */ 42 | ATTR_STATS /* nested */ 43 | ATTR_HW_STATS /* nested */ 44 | ATTR_ORIGIN /* u16 */ 45 | ATTR_HW_TRAP_GROUP_NAME /* string */ // 15 46 | ATTR_HW_TRAP_NAME /* string */ 47 | ATTR_HW_ENTRIES /* nested */ 48 | ATTR_HW_ENTRY /* nested */ 49 | ATTR_HW_TRAP_COUNT /* u32 */ 50 | ATTR_SW_DROPS /* flag */ // 20 51 | ATTR_HW_DROPS /* flag */ 52 | ATTR_FLOW_ACTION_COOKIE /* binary */ 53 | ATTR_DROP_REASON /* string */ // New: Drop reason 54 | ) 55 | 56 | const ( 57 | GRP_ALERT = 1 58 | 59 | // I don't know how to parse SUMMARY mode, so we always 60 | // use PACKET, which gives us payloads (but requires 61 | // privileges) 62 | ALERT_MODE_SUMMARY = 0 63 | ALERT_MODE_PACKET = 1 64 | 65 | NATTR_PORT_NETDEV_IFINDEX = 0 /* u32 */ 66 | NATTR_PORT_NETDEV_NAME = 1 /* string */ 67 | 68 | NATTR_STATS_DROPPED = 0 69 | 70 | ORIGIN_SW = 0 71 | ORIGIN_HW = 1 72 | 73 | CFG_ALERT_COUNT = 1 74 | CFG_ALERT_DELAY = 2 75 | ) 76 | 77 | // Session wraps a genetlink.Conn and looks up the DM_NET family 78 | // from the generic netlink registry 79 | type Session struct { 80 | conn *genetlink.Conn 81 | fam uint16 82 | group uint32 83 | links map[uint32]string 84 | } 85 | 86 | // NewSession connects to generic netlink and looks up the DM_NET 87 | // family so we can issue requests 88 | func NewSession(links map[uint32]string) (*Session, error) { 89 | conn, err := genetlink.Dial(nil) 90 | if err != nil { 91 | return nil, fmt.Errorf("session: %w", err) 92 | } 93 | 94 | s := &Session{ 95 | conn: conn, 96 | links: links, 97 | } 98 | 99 | f, g, err := s.dropMonitorLookup() 100 | if err != nil { 101 | return nil, fmt.Errorf("session: %w", err) 102 | } 103 | 104 | s.fam = f 105 | s.group = g 106 | 107 | return s, nil 108 | } 109 | 110 | // dropMonitorLookup looks up the DM_NET family and group 111 | func (s *Session) dropMonitorLookup() (famid uint16, group uint32, err error) { 112 | fam, err := s.conn.GetFamily("NET_DM") 113 | if err != nil { 114 | return 0, 0, fmt.Errorf("lookup: %w", err) 115 | } 116 | 117 | if len(fam.Groups) != 1 { 118 | return 0, 0, fmt.Errorf("lookup: martian NET_DM family (%d groups)", len(fam.Groups)) 119 | } 120 | 121 | return fam.ID, fam.Groups[0].ID, nil 122 | } 123 | 124 | // decodeConfig decodes the configuration 125 | func decodeConfig(raw []byte) (map[int]interface{}, error) { 126 | dec, err := netlink.NewAttributeDecoder(raw) 127 | if err != nil { 128 | return nil, fmt.Errorf("decode: %w", err) 129 | } 130 | 131 | ret := map[int]interface{}{} 132 | 133 | for dec.Next() { 134 | switch dec.Type() { 135 | case ATTR_ALERT_MODE: 136 | ret[ATTR_ALERT_MODE] = dec.Uint8() 137 | case ATTR_TRUNC_LEN: 138 | ret[ATTR_TRUNC_LEN] = dec.Uint32() 139 | case ATTR_QUEUE_LEN: 140 | ret[ATTR_QUEUE_LEN] = dec.Uint32() 141 | } 142 | } 143 | 144 | if err := dec.Err(); err != nil { 145 | return nil, err 146 | } 147 | 148 | return ret, nil 149 | } 150 | 151 | // Config returns the raw attribute bundle of the current DM_NET configuration (see ATTR_ constants) 152 | // Only includes alert mode, packet snapshot length, and queue length 153 | func (s *Session) Config() (map[int]interface{}, error) { 154 | err := s.req(CMD_CONFIG_GET, nil, false) 155 | if err != nil { 156 | return nil, fmt.Errorf("config: %w", err) 157 | } 158 | 159 | ms, _, err := s.conn.Receive() 160 | if err != nil { 161 | return nil, fmt.Errorf("config: %w", err) 162 | } 163 | 164 | conf, err := decodeConfig(ms[0].Data) 165 | if err != nil { 166 | return nil, fmt.Errorf("config: %w", err) 167 | } 168 | 169 | return conf, nil 170 | } 171 | 172 | // req sends a request to netlink 173 | func (s *Session) req(cmd uint8, data []byte, ack bool) error { 174 | flags := netlink.Request 175 | if ack { 176 | flags |= netlink.Acknowledge 177 | } 178 | 179 | _, err := s.conn.Send(genetlink.Message{ 180 | Header: genetlink.Header{ 181 | Command: cmd, 182 | }, 183 | Data: data, 184 | }, s.fam, flags) 185 | return err 186 | } 187 | 188 | // Start puts DM_NET in packet alert mode (so we get alerts for each packet, 189 | // including the raw contents of the dropped packet), issues an acknowledged CMD_START 190 | // to start monitoring, and then joins the GRP_ALERT netlink multicast group to read alerts. 191 | // DM_NET alerts need to be stopped to work. 192 | func (s *Session) Start(sw, hw bool) error { 193 | enc := netlink.NewAttributeEncoder() 194 | enc.Flag(ATTR_SW_DROPS, sw) // Set software drop flag 195 | enc.Flag(ATTR_HW_DROPS, hw) // Set hardware drop flag 196 | raw, err := enc.Encode() 197 | if err != nil { 198 | return fmt.Errorf("encode: %w", err) 199 | } 200 | 201 | err = s.setPacketMode() // Set packet mode 202 | if err != nil { 203 | return fmt.Errorf("packet mode: %w", err) 204 | } 205 | 206 | err = s.req(CMD_START, raw, true) // Send start monitoring request 207 | if err != nil { 208 | return fmt.Errorf("req: %w", err) 209 | } 210 | 211 | // Stop alerts if this fails 212 | _, _, err = s.conn.Receive() 213 | if err != nil { 214 | s.Stop(sw, hw) 215 | return fmt.Errorf("req ack: %w", err) 216 | } 217 | 218 | err = s.conn.JoinGroup(GRP_ALERT) // Join alert group 219 | if err != nil { 220 | s.Stop(sw, hw) 221 | return fmt.Errorf("join: %w", err) 222 | } 223 | 224 | return nil 225 | } 226 | 227 | // Stop sends an acknowledged CMD_STOP to turn off DM_NET alerts 228 | // (sw is true to disable software drops, hw is true to disable hardware drops), 229 | // and also leaves the GRP_ALERT multicast group. 230 | func (s *Session) Stop(sw, hw bool) error { 231 | _ = s.conn.LeaveGroup(GRP_ALERT) // Leave alert group 232 | 233 | // BUG(tqbf): Log this, but if we are asking this code to stop, I want it to try to stop. 234 | // In most cases, we leave the multicast group simply by closing the connection. 235 | 236 | enc := netlink.NewAttributeEncoder() 237 | enc.Flag(ATTR_SW_DROPS, sw) // Set software drop flag 238 | enc.Flag(ATTR_HW_DROPS, hw) // Set hardware drop flag 239 | raw, err := enc.Encode() 240 | if err != nil { 241 | return fmt.Errorf("encode: %w", err) 242 | } 243 | 244 | err = s.req(CMD_STOP, raw, false) // Send stop monitoring request 245 | if err != nil { 246 | return fmt.Errorf("req: %w", err) 247 | } 248 | 249 | return nil 250 | } 251 | 252 | // decodeAlert decodes the alert 253 | func decodeAlert(raw []byte) (map[int]interface{}, error) { 254 | dec, err := netlink.NewAttributeDecoder(raw) 255 | if err != nil { 256 | return nil, fmt.Errorf("decode: %w", err) 257 | } 258 | 259 | ret := map[int]interface{}{} 260 | 261 | for dec.Next() { 262 | switch dec.Type() { 263 | case ATTR_PC: 264 | ret[ATTR_PC] = dec.Uint64() 265 | case ATTR_SYMBOL: 266 | ret[ATTR_SYMBOL] = dec.String() 267 | case ATTR_IN_PORT: 268 | a := map[int]interface{}{} 269 | dec.Nested(func(d *netlink.AttributeDecoder) error { 270 | for d.Next() { 271 | switch d.Type() { 272 | case NATTR_PORT_NETDEV_IFINDEX: 273 | a[NATTR_PORT_NETDEV_IFINDEX] = d.Uint32() 274 | case NATTR_PORT_NETDEV_NAME: 275 | a[NATTR_PORT_NETDEV_NAME] = d.String() 276 | } 277 | } 278 | 279 | return nil 280 | }) 281 | ret[ATTR_IN_PORT] = a 282 | case ATTR_TIMESTAMP: 283 | ret[ATTR_TIMESTAMP] = dec.Uint64() 284 | case ATTR_PROTO: 285 | ret[ATTR_PROTO] = dec.Uint16() 286 | case ATTR_PAYLOAD: 287 | ret[ATTR_PAYLOAD] = dec.Bytes() 288 | case ATTR_ORIG_LEN: 289 | ret[ATTR_ORIG_LEN] = dec.Uint32() 290 | case ATTR_ORIGIN: 291 | ret[ATTR_ORIGIN] = dec.Uint16() 292 | case ATTR_HW_TRAP_GROUP_NAME: 293 | case ATTR_HW_TRAP_NAME: 294 | case ATTR_HW_ENTRIES: 295 | case ATTR_HW_ENTRY: 296 | case ATTR_HW_TRAP_COUNT: 297 | case ATTR_FLOW_ACTION_COOKIE: 298 | case ATTR_DROP_REASON: // New: Handle drop reason 299 | ret[ATTR_DROP_REASON] = dec.String() // Assuming ATTR_DROP_REASON is defined 300 | } 301 | } 302 | 303 | if err := dec.Err(); err != nil { 304 | return nil, err 305 | } 306 | 307 | return ret, nil 308 | } 309 | 310 | // setPacketMode sets the packet mode 311 | func (s *Session) setPacketMode() error { 312 | enc := netlink.NewAttributeEncoder() 313 | enc.Uint8(ATTR_ALERT_MODE, ALERT_MODE_PACKET) // Set alert mode to packet 314 | enc.Uint32(ATTR_TRUNC_LEN, 100) // Set truncation length 315 | enc.Uint32(ATTR_QUEUE_LEN, 4096) // Set queue length 316 | 317 | raw, err := enc.Encode() 318 | if err != nil { 319 | return fmt.Errorf("encode: %w", err) 320 | } 321 | 322 | err = s.req(CMD_CONFIG, raw, true) // Send configuration request 323 | if err != nil { 324 | return fmt.Errorf("req: %w", err) 325 | } 326 | 327 | _, _, err = s.conn.Receive() // Wait for acknowledgment 328 | if err != nil { 329 | return fmt.Errorf("req ack: %w", err) 330 | } 331 | 332 | return nil 333 | } 334 | 335 | // PacketAlertFunc returns false if we should stop reading drops 336 | type PacketAlertFunc func(PacketAlert) bool 337 | 338 | // ReadUntil reads packet alerts until the deadline is reached, calling 339 | // `f` on each alert; if the deadline is zero, reads indefinitely. 340 | func (s *Session) ReadUntil(deadline time.Time, f PacketAlertFunc) error { 341 | // BUG(tqbf): voodoo; I don't know if this is important 342 | s.conn.SetReadBuffer(4096) // Set read buffer size 343 | 344 | for { 345 | if !deadline.IsZero() { 346 | s.conn.SetReadDeadline(deadline) // Set read deadline 347 | } 348 | ms, _, err := s.conn.Receive() // Receive messages 349 | if err != nil { 350 | if nerr, ok := err.(net.Error); ok && nerr.Timeout() { 351 | // We are done reading 352 | return nil 353 | } 354 | 355 | return fmt.Errorf("recv: %w", err) 356 | } 357 | 358 | for _, m := range ms { 359 | if m.Header.Command != CMD_PACKET_ALERT { 360 | continue // Only process packet alerts 361 | } 362 | 363 | pa, err := PacketAlertFromRaw(m.Data) // Create PacketAlert from raw data 364 | if err != nil { 365 | return fmt.Errorf("parse alert packet: %w", err) 366 | } 367 | 368 | if !f(pa) { 369 | return nil // Stop reading if f returns false 370 | } 371 | } 372 | } 373 | } 374 | 375 | // GetOrigin is a helper function to determine the origin of the drop 376 | func GetOrigin(pa *PacketAlert) string { 377 | origin, ok := pa.attrs[ATTR_ORIGIN] 378 | if !ok { 379 | return "unknown" // Return unknown if not found 380 | } 381 | if origin.(uint16) == ORIGIN_SW { 382 | return "software" // Return software if software drop 383 | } 384 | return "hardware" // Otherwise return hardware 385 | } 386 | 387 | // GetDropReason is a helper function to determine the drop reason 388 | func GetDropReason(pa *PacketAlert) string { 389 | // Check if drop reason attribute is available 390 | reason, ok := pa.attrs[ATTR_DROP_REASON] // Assuming ATTR_DROP_REASON is defined 391 | if ok { 392 | return reason.(string) // Return specific drop reason 393 | } 394 | 395 | // Return static reason if not available 396 | return "UNSUPPORTED_FEATURE" // Return unsupported feature 397 | } 398 | 399 | // PacketAlert wraps the Netlink attributes parsed from a CMD_ALERT message 400 | type PacketAlert struct { 401 | attrs map[int]interface{} 402 | } 403 | 404 | // PacketAlertFromRaw creates a PacketAlert from the raw bytes of a CMD_ALERT message. 405 | func PacketAlertFromRaw(raw []byte) (PacketAlert, error) { 406 | attrs, err := decodeAlert(raw) // Decode alert 407 | if err != nil { 408 | return PacketAlert{}, fmt.Errorf("decode: %w", err) 409 | } 410 | 411 | return PacketAlert{ 412 | attrs: attrs, 413 | }, nil 414 | } 415 | 416 | // Packet returns the (truncated) raw bytes of the dropped packet, starting from the link layer header 417 | // (which might be an Ethernet header?). 418 | func (pa *PacketAlert) Packet() []byte { 419 | payload, ok := pa.attrs[ATTR_PAYLOAD] 420 | if !ok { 421 | return nil 422 | } 423 | 424 | return payload.([]byte) // Return payload 425 | } 426 | 427 | // L3Packet returns the (truncated) raw bytes of the dropped packet, skipping the link layer header 428 | // (i.e., starting from the IP packet's IP header) 429 | func (pa *PacketAlert) L3Packet() []byte { 430 | packet := pa.Packet() 431 | if len(packet) <= 14 { 432 | return nil // Return nil if packet length is less than or equal to 14 433 | } 434 | 435 | return packet[14:] // Return packet skipping link layer header 436 | } 437 | 438 | // Symbol returns the kernel function where the drop occurred, when available. 439 | func (pa *PacketAlert) Symbol() string { 440 | sym, ok := pa.attrs[ATTR_SYMBOL] 441 | if !ok { 442 | return "" // Return empty string if not found 443 | } 444 | 445 | return sym.(string) // Return symbol 446 | } 447 | 448 | // PC returns the $RIP of the CPU when the drop occurred, for later resolution to a symbol. 449 | func (pa *PacketAlert) PC() uint64 { 450 | pc, ok := pa.attrs[ATTR_PC] 451 | if !ok { 452 | return 0 // Return 0 if not found 453 | } 454 | 455 | return pc.(uint64) // Return program counter 456 | } 457 | 458 | // Proto returns the layer 3 protocol of the dropped packet. 459 | func (pa *PacketAlert) Proto() uint16 { 460 | proto, ok := pa.attrs[ATTR_PROTO] 461 | if !ok { 462 | return 0 // Return 0 if not found 463 | } 464 | 465 | return proto.(uint16) // Return protocol 466 | } 467 | 468 | // Is4 is true if the dropped packet is an IPv4 packet. 469 | func (pa *PacketAlert) Is4() bool { 470 | return pa.Proto() == 0x0800 // Check if protocol is IPv4 471 | } 472 | 473 | // Is16 is true if the dropped packet is an IPv6 packet. 474 | func (pa *PacketAlert) Is16() bool { 475 | return pa.Proto() == 0x86DD // Check if protocol is IPv6 476 | } 477 | 478 | // Length returns the original non-truncated length of the dropped packet. 479 | func (pa *PacketAlert) Length() uint32 { 480 | l, ok := pa.attrs[ATTR_ORIG_LEN] 481 | if !ok { 482 | return 0 483 | } 484 | 485 | return l.(uint32) 486 | } 487 | 488 | // Link returns the interface index of the dropped packet 489 | func (pa *PacketAlert) Link() uint32 { 490 | l, ok := pa.attrs[ATTR_IN_PORT] 491 | if !ok { 492 | return 0 493 | } 494 | 495 | a := l.(map[int]interface{}) 496 | lidx, ok := a[NATTR_PORT_NETDEV_IFINDEX] 497 | if !ok { 498 | return 0 499 | } 500 | 501 | return lidx.(uint32) // Return interface index 502 | } 503 | 504 | func (pa *PacketAlert) Output(links map[uint32]string) { 505 | // Log drop information 506 | iface := fmt.Sprintf("%d", pa.Link()) 507 | if links != nil { 508 | iface = links[pa.Link()] 509 | } 510 | log.Printf("drop at: %s:%016x", pa.Symbol(), pa.PC()) 511 | log.Printf("iface: %s", iface) 512 | log.Printf("timestamp: %s", 513 | time.Unix(0, int64(pa.attrs[ATTR_TIMESTAMP].(uint64))).Format("2006-01-02 15:04:05.000000")) 514 | log.Printf("protocol: %s(0x%x)", EthTypeToString(pa.Proto()), pa.Proto()) 515 | log.Printf("drop reason: %s", GetDropReason(pa)) 516 | log.Printf("origin: %s", GetOrigin(pa)) 517 | log.Printf("length: %d", pa.Length()) 518 | } 519 | --------------------------------------------------------------------------------