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  }