github.com/cilium/cilium@v1.16.2/pkg/monitor/dissect.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package monitor 5 6 import ( 7 "encoding/hex" 8 "fmt" 9 "net" 10 "strconv" 11 12 "github.com/google/gopacket" 13 "github.com/google/gopacket/layers" 14 15 "github.com/cilium/cilium/pkg/lock" 16 "github.com/cilium/cilium/pkg/logging" 17 "github.com/cilium/cilium/pkg/logging/logfields" 18 ) 19 20 type DisplayFormat bool 21 22 const ( 23 DisplayLabel DisplayFormat = false 24 DisplayNumeric DisplayFormat = true 25 ) 26 27 type parserCache struct { 28 eth layers.Ethernet 29 ip4 layers.IPv4 30 ip6 layers.IPv6 31 icmp4 layers.ICMPv4 32 icmp6 layers.ICMPv6 33 tcp layers.TCP 34 udp layers.UDP 35 sctp layers.SCTP 36 decoded []gopacket.LayerType 37 } 38 39 var ( 40 cache *parserCache 41 dissectLock lock.Mutex 42 parser *gopacket.DecodingLayerParser 43 44 log = logging.DefaultLogger.WithField(logfields.LogSubsys, "monitor") 45 ) 46 47 // getParser must be called with dissectLock held 48 func initParser() { 49 if cache == nil { 50 log.Info("Initializing dissection cache...") 51 52 cache = &parserCache{ 53 decoded: []gopacket.LayerType{}, 54 } 55 56 parser = gopacket.NewDecodingLayerParser( 57 layers.LayerTypeEthernet, 58 &cache.eth, &cache.ip4, &cache.ip6, 59 &cache.icmp4, &cache.icmp6, &cache.tcp, &cache.udp, 60 &cache.sctp) 61 } 62 } 63 64 func getTCPInfo() string { 65 info := "" 66 addTCPFlag := func(flag, new string) string { 67 if flag == "" { 68 return new 69 } 70 return flag + ", " + new 71 } 72 73 if cache.tcp.SYN { 74 info = addTCPFlag(info, "SYN") 75 } 76 77 if cache.tcp.ACK { 78 info = addTCPFlag(info, "ACK") 79 } 80 81 if cache.tcp.RST { 82 info = addTCPFlag(info, "RST") 83 } 84 85 if cache.tcp.FIN { 86 info = addTCPFlag(info, "FIN") 87 } 88 89 return info 90 } 91 92 // ConnectionInfo contains tuple information and icmp code for a connection 93 type ConnectionInfo struct { 94 SrcIP net.IP 95 DstIP net.IP 96 SrcPort uint16 97 DstPort uint16 98 Proto string 99 IcmpCode string 100 } 101 102 // getConnectionInfoFromCache assume dissectLock is obtained at the caller and data is already 103 // parsed to cache.decoded 104 func getConnectionInfoFromCache() (c *ConnectionInfo, hasIP, hasEth bool) { 105 c = &ConnectionInfo{} 106 for _, typ := range cache.decoded { 107 switch typ { 108 case layers.LayerTypeEthernet: 109 hasEth = true 110 case layers.LayerTypeIPv4: 111 hasIP = true 112 c.SrcIP, c.DstIP = cache.ip4.SrcIP, cache.ip4.DstIP 113 case layers.LayerTypeIPv6: 114 hasIP = true 115 c.SrcIP, c.DstIP = cache.ip6.SrcIP, cache.ip6.DstIP 116 case layers.LayerTypeTCP: 117 c.Proto = "tcp" 118 c.SrcPort, c.DstPort = uint16(cache.tcp.SrcPort), uint16(cache.tcp.DstPort) 119 case layers.LayerTypeUDP: 120 c.Proto = "udp" 121 c.SrcPort, c.DstPort = uint16(cache.udp.SrcPort), uint16(cache.udp.DstPort) 122 case layers.LayerTypeSCTP: 123 c.Proto = "sctp" 124 c.SrcPort, c.DstPort = uint16(cache.sctp.SrcPort), uint16(cache.sctp.DstPort) 125 case layers.LayerTypeIPSecAH: 126 c.Proto = "IPsecAH" 127 case layers.LayerTypeIPSecESP: 128 c.Proto = "IPsecESP" 129 case layers.LayerTypeICMPv4: 130 c.Proto = "icmp" 131 c.IcmpCode = cache.icmp4.TypeCode.String() 132 case layers.LayerTypeICMPv6: 133 c.Proto = "icmp" 134 c.IcmpCode = cache.icmp6.TypeCode.String() 135 } 136 } 137 return c, hasIP, hasEth 138 } 139 140 // GetConnectionInfo returns the ConnectionInfo structure from data 141 func GetConnectionInfo(data []byte) *ConnectionInfo { 142 dissectLock.Lock() 143 defer dissectLock.Unlock() 144 145 initParser() 146 parser.DecodeLayers(data, &cache.decoded) 147 148 c, _, _ := getConnectionInfoFromCache() 149 return c 150 } 151 152 // GetConnectionSummary decodes the data into layers and returns a connection 153 // summary in the format: 154 // 155 // - sIP:sPort -> dIP:dPort, e.g. 1.1.1.1:2000 -> 2.2.2.2:80 156 // - sIP -> dIP icmpCode, 1.1.1.1 -> 2.2.2.2 echo-request 157 func GetConnectionSummary(data []byte) string { 158 dissectLock.Lock() 159 defer dissectLock.Unlock() 160 161 initParser() 162 parser.DecodeLayers(data, &cache.decoded) 163 164 c, hasIP, hasEth := getConnectionInfoFromCache() 165 srcIP, dstIP := c.SrcIP, c.DstIP 166 srcPort, dstPort := strconv.Itoa(int(c.SrcPort)), strconv.Itoa(int(c.DstPort)) 167 icmpCode, proto := c.IcmpCode, c.Proto 168 169 switch { 170 case icmpCode != "": 171 return fmt.Sprintf("%s -> %s %s", srcIP, dstIP, icmpCode) 172 case proto != "": 173 var s string 174 175 if proto == "esp" { 176 s = proto 177 } else { 178 s = fmt.Sprintf("%s -> %s %s", 179 net.JoinHostPort(srcIP.String(), srcPort), 180 net.JoinHostPort(dstIP.String(), dstPort), 181 proto) 182 } 183 if proto == "tcp" { 184 s += " " + getTCPInfo() 185 } 186 return s 187 case hasIP: 188 return fmt.Sprintf("%s -> %s", srcIP, dstIP) 189 case hasEth: 190 return fmt.Sprintf("%s -> %s %s", cache.eth.SrcMAC, cache.eth.DstMAC, cache.eth.EthernetType.String()) 191 } 192 193 return "[unknown]" 194 } 195 196 // Dissect parses and prints the provided data if dissect is set to true, 197 // otherwise the data is printed as HEX output 198 func Dissect(dissect bool, data []byte) { 199 if dissect { 200 dissectLock.Lock() 201 defer dissectLock.Unlock() 202 203 initParser() 204 err := parser.DecodeLayers(data, &cache.decoded) 205 206 for _, typ := range cache.decoded { 207 switch typ { 208 case layers.LayerTypeEthernet: 209 fmt.Println(gopacket.LayerString(&cache.eth)) 210 case layers.LayerTypeIPv4: 211 fmt.Println(gopacket.LayerString(&cache.ip4)) 212 case layers.LayerTypeIPv6: 213 fmt.Println(gopacket.LayerString(&cache.ip6)) 214 case layers.LayerTypeTCP: 215 fmt.Println(gopacket.LayerString(&cache.tcp)) 216 case layers.LayerTypeUDP: 217 fmt.Println(gopacket.LayerString(&cache.udp)) 218 case layers.LayerTypeSCTP: 219 fmt.Println(gopacket.LayerString(&cache.sctp)) 220 case layers.LayerTypeICMPv4: 221 fmt.Println(gopacket.LayerString(&cache.icmp4)) 222 case layers.LayerTypeICMPv6: 223 fmt.Println(gopacket.LayerString(&cache.icmp6)) 224 default: 225 fmt.Println("Unknown layer") 226 } 227 } 228 if parser.Truncated { 229 fmt.Println(" Packet has been truncated") 230 } 231 if err != nil { 232 fmt.Println(" Failed to decode layer:", err) 233 } 234 235 } else { 236 fmt.Print(hex.Dump(data)) 237 } 238 } 239 240 // Flow contains source and destination 241 type Flow struct { 242 Src string `json:"src"` 243 Dst string `json:"dst"` 244 } 245 246 // DissectSummary bundles decoded layers into json-marshallable message 247 type DissectSummary struct { 248 Ethernet string `json:"ethernet,omitempty"` 249 IPv4 string `json:"ipv4,omitempty"` 250 IPv6 string `json:"ipv6,omitempty"` 251 TCP string `json:"tcp,omitempty"` 252 UDP string `json:"udp,omitempty"` 253 SCTP string `json:"sctp,omitempty"` 254 ICMPv4 string `json:"icmpv4,omitempty"` 255 ICMPv6 string `json:"icmpv6,omitempty"` 256 L2 *Flow `json:"l2,omitempty"` 257 L3 *Flow `json:"l3,omitempty"` 258 L4 *Flow `json:"l4,omitempty"` 259 } 260 261 // GetDissectSummary returns DissectSummary created from data 262 func GetDissectSummary(data []byte) *DissectSummary { 263 dissectLock.Lock() 264 defer dissectLock.Unlock() 265 266 initParser() 267 parser.DecodeLayers(data, &cache.decoded) 268 269 ret := &DissectSummary{} 270 271 for _, typ := range cache.decoded { 272 switch typ { 273 case layers.LayerTypeEthernet: 274 ret.Ethernet = gopacket.LayerString(&cache.eth) 275 src, dst := cache.eth.LinkFlow().Endpoints() 276 ret.L2 = &Flow{Src: src.String(), Dst: dst.String()} 277 case layers.LayerTypeIPv4: 278 ret.IPv4 = gopacket.LayerString(&cache.ip4) 279 src, dst := cache.ip4.NetworkFlow().Endpoints() 280 ret.L3 = &Flow{Src: src.String(), Dst: dst.String()} 281 case layers.LayerTypeIPv6: 282 ret.IPv6 = gopacket.LayerString(&cache.ip6) 283 src, dst := cache.ip6.NetworkFlow().Endpoints() 284 ret.L3 = &Flow{Src: src.String(), Dst: dst.String()} 285 case layers.LayerTypeTCP: 286 ret.TCP = gopacket.LayerString(&cache.tcp) 287 src, dst := cache.tcp.TransportFlow().Endpoints() 288 ret.L4 = &Flow{Src: src.String(), Dst: dst.String()} 289 case layers.LayerTypeUDP: 290 ret.UDP = gopacket.LayerString(&cache.udp) 291 src, dst := cache.udp.TransportFlow().Endpoints() 292 ret.L4 = &Flow{Src: src.String(), Dst: dst.String()} 293 case layers.LayerTypeSCTP: 294 ret.SCTP = gopacket.LayerString(&cache.sctp) 295 src, dst := cache.sctp.TransportFlow().Endpoints() 296 ret.L4 = &Flow{Src: src.String(), Dst: dst.String()} 297 case layers.LayerTypeICMPv4: 298 ret.ICMPv4 = gopacket.LayerString(&cache.icmp4) 299 case layers.LayerTypeICMPv6: 300 ret.ICMPv6 = gopacket.LayerString(&cache.icmp6) 301 } 302 } 303 return ret 304 }