github.com/cilium/cilium@v1.16.2/pkg/maps/ctmap/types.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package ctmap
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  	"unsafe"
    10  
    11  	"github.com/cilium/cilium/pkg/bpf"
    12  	"github.com/cilium/cilium/pkg/byteorder"
    13  	"github.com/cilium/cilium/pkg/option"
    14  	"github.com/cilium/cilium/pkg/tuple"
    15  )
    16  
    17  // mapType is a type of connection tracking map.
    18  type mapType int
    19  
    20  const (
    21  	// mapTypeIPv4TCPLocal and friends are map types which correspond to a
    22  	// combination of the following attributes:
    23  	// * IPv4 or IPv6;
    24  	// * TCP or non-TCP (shortened to Any)
    25  	// * Local (endpoint-specific) or global (endpoint-oblivious).
    26  	mapTypeIPv4TCPLocal mapType = iota
    27  	mapTypeIPv6TCPLocal
    28  	mapTypeIPv4TCPGlobal
    29  	mapTypeIPv6TCPGlobal
    30  	mapTypeIPv4AnyLocal
    31  	mapTypeIPv6AnyLocal
    32  	mapTypeIPv4AnyGlobal
    33  	mapTypeIPv6AnyGlobal
    34  	mapTypeMax
    35  )
    36  
    37  // String renders the map type into a user-readable string.
    38  func (m mapType) String() string {
    39  	switch m {
    40  	case mapTypeIPv4TCPLocal:
    41  		return "Local IPv4 TCP CT map"
    42  	case mapTypeIPv6TCPLocal:
    43  		return "Local IPv6 TCP CT map"
    44  	case mapTypeIPv4TCPGlobal:
    45  		return "Global IPv4 TCP CT map"
    46  	case mapTypeIPv6TCPGlobal:
    47  		return "Global IPv6 TCP CT map"
    48  	case mapTypeIPv4AnyLocal:
    49  		return "Local IPv4 non-TCP CT map"
    50  	case mapTypeIPv6AnyLocal:
    51  		return "Local IPv6 non-TCP CT map"
    52  	case mapTypeIPv4AnyGlobal:
    53  		return "Global IPv4 non-TCP CT map"
    54  	case mapTypeIPv6AnyGlobal:
    55  		return "Global IPv6 non-TCP CT map"
    56  	}
    57  	return fmt.Sprintf("Unknown (%d)", int(m))
    58  }
    59  
    60  func (m mapType) name() string {
    61  	switch m {
    62  	case mapTypeIPv4TCPLocal, mapTypeIPv4TCPGlobal:
    63  		return "tcp4"
    64  	case mapTypeIPv6TCPLocal, mapTypeIPv6TCPGlobal:
    65  		return "tcp6"
    66  	case mapTypeIPv4AnyLocal, mapTypeIPv4AnyGlobal:
    67  		return "any4"
    68  	case mapTypeIPv6AnyLocal, mapTypeIPv6AnyGlobal:
    69  		return "any6"
    70  	default:
    71  		panic("Unexpected map type " + m.String())
    72  	}
    73  }
    74  
    75  func (m mapType) isIPv4() bool {
    76  	switch m {
    77  	case mapTypeIPv4TCPLocal, mapTypeIPv4TCPGlobal, mapTypeIPv4AnyLocal, mapTypeIPv4AnyGlobal:
    78  		return true
    79  	}
    80  	return false
    81  }
    82  
    83  func (m mapType) isIPv6() bool {
    84  	switch m {
    85  	case mapTypeIPv6TCPLocal, mapTypeIPv6TCPGlobal, mapTypeIPv6AnyLocal, mapTypeIPv6AnyGlobal:
    86  		return true
    87  	}
    88  	return false
    89  }
    90  
    91  func (m mapType) isLocal() bool {
    92  	switch m {
    93  	case mapTypeIPv4TCPLocal, mapTypeIPv6TCPLocal, mapTypeIPv4AnyLocal, mapTypeIPv6AnyLocal:
    94  		return true
    95  	}
    96  	return false
    97  }
    98  
    99  func (m mapType) isGlobal() bool {
   100  	switch m {
   101  	case mapTypeIPv4TCPGlobal, mapTypeIPv6TCPGlobal, mapTypeIPv4AnyGlobal, mapTypeIPv6AnyGlobal:
   102  		return true
   103  	}
   104  	return false
   105  }
   106  
   107  func (m mapType) isTCP() bool {
   108  	switch m {
   109  	case mapTypeIPv4TCPLocal, mapTypeIPv6TCPLocal, mapTypeIPv4TCPGlobal, mapTypeIPv6TCPGlobal:
   110  		return true
   111  	}
   112  	return false
   113  }
   114  
   115  func (m mapType) key() bpf.MapKey {
   116  	switch m {
   117  	case mapTypeIPv4TCPLocal, mapTypeIPv4AnyLocal:
   118  		return &CtKey4{}
   119  	case mapTypeIPv6TCPLocal, mapTypeIPv6AnyLocal:
   120  		return &CtKey6{}
   121  	case mapTypeIPv4TCPGlobal, mapTypeIPv4AnyGlobal:
   122  		return &CtKey4Global{}
   123  	case mapTypeIPv6TCPGlobal, mapTypeIPv6AnyGlobal:
   124  		return &CtKey6Global{}
   125  	default:
   126  		panic("Unexpected map type " + m.String())
   127  	}
   128  }
   129  
   130  func (m mapType) value() bpf.MapValue {
   131  	return &CtEntry{}
   132  }
   133  
   134  func (m mapType) maxEntries() int {
   135  	switch m {
   136  	case mapTypeIPv4TCPGlobal, mapTypeIPv6TCPGlobal:
   137  		if option.Config.CTMapEntriesGlobalTCP != 0 {
   138  			return option.Config.CTMapEntriesGlobalTCP
   139  		}
   140  		return option.CTMapEntriesGlobalTCPDefault
   141  
   142  	case mapTypeIPv4AnyGlobal, mapTypeIPv6AnyGlobal:
   143  		if option.Config.CTMapEntriesGlobalAny != 0 {
   144  			return option.Config.CTMapEntriesGlobalAny
   145  		}
   146  		return option.CTMapEntriesGlobalAnyDefault
   147  
   148  	case mapTypeIPv4TCPLocal, mapTypeIPv6TCPLocal, mapTypeIPv4AnyLocal, mapTypeIPv6AnyLocal:
   149  		return mapNumEntriesLocal
   150  
   151  	default:
   152  		panic("Unexpected map type " + m.String())
   153  	}
   154  }
   155  
   156  func (m mapType) bpfDefine() string {
   157  	switch m {
   158  	case mapTypeIPv4TCPLocal, mapTypeIPv4TCPGlobal:
   159  		return "CT_MAP_TCP4"
   160  	case mapTypeIPv6TCPLocal, mapTypeIPv6TCPGlobal:
   161  		return "CT_MAP_TCP6"
   162  	case mapTypeIPv4AnyLocal, mapTypeIPv4AnyGlobal:
   163  		return "CT_MAP_ANY4"
   164  	case mapTypeIPv6AnyLocal, mapTypeIPv6AnyGlobal:
   165  		return "CT_MAP_ANY6"
   166  	default:
   167  		panic("Unexpected map type " + m.String())
   168  	}
   169  }
   170  
   171  type CTMapIPVersion int
   172  
   173  const (
   174  	CTMapIPv4 CTMapIPVersion = iota
   175  	CTMapIPv6
   176  )
   177  
   178  // FilterMapsByProto filters the given CT maps by the given IP version, and
   179  // returns two maps - one for TCP and one for any protocol.
   180  func FilterMapsByProto(maps []*Map, ipVsn CTMapIPVersion) (ctMapTCP *Map, ctMapAny *Map) {
   181  	for _, m := range maps {
   182  		switch ipVsn {
   183  		case CTMapIPv4:
   184  			switch m.mapType {
   185  			case mapTypeIPv4TCPLocal, mapTypeIPv4TCPGlobal:
   186  				ctMapTCP = m
   187  			case mapTypeIPv4AnyLocal, mapTypeIPv4AnyGlobal:
   188  				ctMapAny = m
   189  			}
   190  		case CTMapIPv6:
   191  			switch m.mapType {
   192  			case mapTypeIPv6TCPLocal, mapTypeIPv6TCPGlobal:
   193  				ctMapTCP = m
   194  			case mapTypeIPv6AnyLocal, mapTypeIPv6AnyGlobal:
   195  				ctMapAny = m
   196  			}
   197  		}
   198  	}
   199  	return
   200  }
   201  
   202  type CtKey interface {
   203  	bpf.MapKey
   204  
   205  	// ToNetwork converts fields to network byte order.
   206  	ToNetwork() CtKey
   207  
   208  	// ToHost converts fields to host byte order.
   209  	ToHost() CtKey
   210  
   211  	// Dump contents of key to sb. Returns true if successful.
   212  	Dump(sb *strings.Builder, reverse bool) bool
   213  
   214  	// GetFlags flags containing the direction of the CtKey.
   215  	GetFlags() uint8
   216  
   217  	GetTupleKey() tuple.TupleKey
   218  }
   219  
   220  type CtKey4 struct {
   221  	tuple.TupleKey4
   222  }
   223  
   224  // ToNetwork converts CtKey4 ports to network byte order.
   225  func (k *CtKey4) ToNetwork() CtKey {
   226  	n := *k
   227  	n.SourcePort = byteorder.HostToNetwork16(n.SourcePort)
   228  	n.DestPort = byteorder.HostToNetwork16(n.DestPort)
   229  	return &n
   230  }
   231  
   232  // ToHost converts CtKey ports to host byte order.
   233  func (k *CtKey4) ToHost() CtKey {
   234  	n := *k
   235  	n.SourcePort = byteorder.NetworkToHost16(n.SourcePort)
   236  	n.DestPort = byteorder.NetworkToHost16(n.DestPort)
   237  	return &n
   238  }
   239  
   240  // GetFlags returns the tuple's flags.
   241  func (k *CtKey4) GetFlags() uint8 {
   242  	return k.Flags
   243  }
   244  
   245  func (k *CtKey4) String() string {
   246  	return fmt.Sprintf("%s:%d, %d, %d, %d", k.DestAddr, k.SourcePort, k.DestPort, k.NextHeader, k.Flags)
   247  }
   248  
   249  func (k *CtKey4) New() bpf.MapKey { return &CtKey4{} }
   250  
   251  // Dump writes the contents of key to sb and returns true if the value for next
   252  // header in the key is nonzero.
   253  func (k *CtKey4) Dump(sb *strings.Builder, reverse bool) bool {
   254  	var addrDest string
   255  
   256  	if k.NextHeader == 0 {
   257  		return false
   258  	}
   259  
   260  	// Addresses swapped, see issue #5848
   261  	if reverse {
   262  		addrDest = k.SourceAddr.String()
   263  	} else {
   264  		addrDest = k.DestAddr.String()
   265  	}
   266  
   267  	if k.Flags&TUPLE_F_SERVICE != 0 {
   268  		sb.WriteString(fmt.Sprintf("%s SVC %s %d:%d ",
   269  			k.NextHeader.String(), k.DestAddr.String(), k.DestPort,
   270  			k.SourcePort),
   271  		)
   272  	} else if k.Flags&TUPLE_F_IN != 0 {
   273  		sb.WriteString(fmt.Sprintf("%s IN %s %d:%d ",
   274  			k.NextHeader.String(), addrDest, k.SourcePort,
   275  			k.DestPort),
   276  		)
   277  	} else {
   278  		sb.WriteString(fmt.Sprintf("%s OUT %s %d:%d ",
   279  			k.NextHeader.String(), addrDest, k.DestPort,
   280  			k.SourcePort),
   281  		)
   282  	}
   283  
   284  	if k.Flags&TUPLE_F_RELATED != 0 {
   285  		sb.WriteString("related ")
   286  	}
   287  
   288  	return true
   289  }
   290  
   291  func (k *CtKey4) GetTupleKey() tuple.TupleKey {
   292  	return &k.TupleKey4
   293  }
   294  
   295  type CtKey4Global struct {
   296  	tuple.TupleKey4Global
   297  }
   298  
   299  // ToNetwork converts ports to network byte order.
   300  //
   301  // This is necessary to prevent callers from implicitly converting
   302  // the CtKey4Global type here into a local key type in the nested
   303  // TupleKey4Global field.
   304  func (k *CtKey4Global) ToNetwork() CtKey {
   305  	return &CtKey4Global{
   306  		TupleKey4Global: *k.TupleKey4Global.ToNetwork().(*tuple.TupleKey4Global),
   307  	}
   308  }
   309  
   310  // ToHost converts ports to host byte order.
   311  //
   312  // This is necessary to prevent callers from implicitly converting
   313  // the CtKey4Global type here into a local key type in the nested
   314  // TupleKey4Global field.
   315  func (k *CtKey4Global) ToHost() CtKey {
   316  	return &CtKey4Global{
   317  		TupleKey4Global: *k.TupleKey4Global.ToHost().(*tuple.TupleKey4Global),
   318  	}
   319  }
   320  
   321  // GetFlags returns the tuple's flags.
   322  func (k *CtKey4Global) GetFlags() uint8 {
   323  	return k.Flags
   324  }
   325  
   326  func (k *CtKey4Global) String() string {
   327  	return fmt.Sprintf("%s:%d --> %s:%d, %d, %d", k.SourceAddr, k.SourcePort, k.DestAddr, k.DestPort, k.NextHeader, k.Flags)
   328  }
   329  
   330  func (k *CtKey4Global) New() bpf.MapKey { return &CtKey4Global{} }
   331  
   332  // Dump writes the contents of key to sb and returns true if the value for next
   333  // header in the key is nonzero.
   334  func (k *CtKey4Global) Dump(sb *strings.Builder, reverse bool) bool {
   335  	var addrSource, addrDest string
   336  
   337  	if k.NextHeader == 0 {
   338  		return false
   339  	}
   340  
   341  	// Addresses swapped, see issue #5848
   342  	if reverse {
   343  		addrSource = k.DestAddr.String()
   344  		addrDest = k.SourceAddr.String()
   345  	} else {
   346  		addrSource = k.SourceAddr.String()
   347  		addrDest = k.DestAddr.String()
   348  	}
   349  
   350  	if k.Flags&TUPLE_F_SERVICE != 0 {
   351  		sb.WriteString(fmt.Sprintf("%s SVC %s:%d -> %s:%d ",
   352  			k.NextHeader.String(), k.SourceAddr.String(), k.DestPort,
   353  			k.DestAddr.String(), k.SourcePort),
   354  		)
   355  	} else if k.Flags&TUPLE_F_IN != 0 {
   356  		sb.WriteString(fmt.Sprintf("%s IN %s:%d -> %s:%d ",
   357  			k.NextHeader.String(), addrSource, k.SourcePort,
   358  			addrDest, k.DestPort),
   359  		)
   360  	} else {
   361  		sb.WriteString(fmt.Sprintf("%s OUT %s:%d -> %s:%d ",
   362  			k.NextHeader.String(), addrSource, k.SourcePort,
   363  			addrDest, k.DestPort),
   364  		)
   365  	}
   366  
   367  	if k.Flags&TUPLE_F_RELATED != 0 {
   368  		sb.WriteString("related ")
   369  	}
   370  
   371  	return true
   372  }
   373  
   374  func (k *CtKey4Global) GetTupleKey() tuple.TupleKey {
   375  	return &k.TupleKey4Global
   376  }
   377  
   378  // CtKey6 is needed to provide CtEntry type to Lookup values
   379  type CtKey6 struct {
   380  	tuple.TupleKey6
   381  }
   382  
   383  // ToNetwork converts CtKey6 ports to network byte order.
   384  func (k *CtKey6) ToNetwork() CtKey {
   385  	return &CtKey6{
   386  		TupleKey6: *k.TupleKey6.ToNetwork().(*tuple.TupleKey6),
   387  	}
   388  }
   389  
   390  // ToHost converts CtKey ports to host byte order.
   391  func (k *CtKey6) ToHost() CtKey {
   392  	return &CtKey6{
   393  		TupleKey6: *k.TupleKey6.ToHost().(*tuple.TupleKey6),
   394  	}
   395  }
   396  
   397  // GetFlags returns the tuple's flags.
   398  func (k *CtKey6) GetFlags() uint8 {
   399  	return k.Flags
   400  }
   401  
   402  func (k *CtKey6) String() string {
   403  	return fmt.Sprintf("[%s]:%d, %d, %d, %d", k.DestAddr, k.SourcePort, k.DestPort, k.NextHeader, k.Flags)
   404  }
   405  
   406  func (k *CtKey6) New() bpf.MapKey { return &CtKey6{} }
   407  
   408  // Dump writes the contents of key to sb and returns true if the value for next
   409  // header in the key is nonzero.
   410  func (k *CtKey6) Dump(sb *strings.Builder, reverse bool) bool {
   411  	var addrDest string
   412  
   413  	if k.NextHeader == 0 {
   414  		return false
   415  	}
   416  
   417  	// Addresses swapped, see issue #5848
   418  	if reverse {
   419  		addrDest = k.SourceAddr.String()
   420  	} else {
   421  		addrDest = k.DestAddr.String()
   422  	}
   423  
   424  	if k.Flags&TUPLE_F_SERVICE != 0 {
   425  		sb.WriteString(fmt.Sprintf("%s SVC %s %d:%d ",
   426  			k.NextHeader.String(), k.DestAddr.String(), k.DestPort,
   427  			k.SourcePort),
   428  		)
   429  	} else if k.Flags&TUPLE_F_IN != 0 {
   430  		sb.WriteString(fmt.Sprintf("%s IN %s %d:%d ",
   431  			k.NextHeader.String(), addrDest, k.SourcePort,
   432  			k.DestPort),
   433  		)
   434  	} else {
   435  		sb.WriteString(fmt.Sprintf("%s OUT %s %d:%d ",
   436  			k.NextHeader.String(), addrDest, k.DestPort,
   437  			k.SourcePort),
   438  		)
   439  	}
   440  
   441  	if k.Flags&TUPLE_F_RELATED != 0 {
   442  		sb.WriteString("related ")
   443  	}
   444  
   445  	return true
   446  }
   447  
   448  func (k *CtKey6) GetTupleKey() tuple.TupleKey {
   449  	return &k.TupleKey6
   450  }
   451  
   452  // CtKey6Global is needed to provide CtEntry type to Lookup values
   453  type CtKey6Global struct {
   454  	tuple.TupleKey6Global
   455  }
   456  
   457  const SizeofCtKey6Global = int(unsafe.Sizeof(CtKey6Global{}))
   458  
   459  // ToNetwork converts ports to network byte order.
   460  //
   461  // This is necessary to prevent callers from implicitly converting
   462  // the CtKey6Global type here into a local key type in the nested
   463  // TupleKey6Global field.
   464  func (k *CtKey6Global) ToNetwork() CtKey {
   465  	return &CtKey6Global{
   466  		TupleKey6Global: *k.TupleKey6Global.ToNetwork().(*tuple.TupleKey6Global),
   467  	}
   468  }
   469  
   470  // ToHost converts ports to host byte order.
   471  //
   472  // This is necessary to prevent callers from implicitly converting
   473  // the CtKey6Global type here into a local key type in the nested
   474  // TupleKey6Global field.
   475  func (k *CtKey6Global) ToHost() CtKey {
   476  	return &CtKey6Global{
   477  		TupleKey6Global: *k.TupleKey6Global.ToHost().(*tuple.TupleKey6Global),
   478  	}
   479  }
   480  
   481  // GetFlags returns the tuple's flags.
   482  func (k *CtKey6Global) GetFlags() uint8 {
   483  	return k.Flags
   484  }
   485  
   486  func (k *CtKey6Global) String() string {
   487  	return fmt.Sprintf("[%s]:%d --> [%s]:%d, %d, %d", k.SourceAddr, k.SourcePort, k.DestAddr, k.DestPort, k.NextHeader, k.Flags)
   488  }
   489  
   490  func (k *CtKey6Global) New() bpf.MapKey { return &CtKey6Global{} }
   491  
   492  // Dump writes the contents of key to sb and returns true if the value for next
   493  // header in the key is nonzero.
   494  func (k *CtKey6Global) Dump(sb *strings.Builder, reverse bool) bool {
   495  	var addrSource, addrDest string
   496  
   497  	if k.NextHeader == 0 {
   498  		return false
   499  	}
   500  
   501  	// Addresses swapped, see issue #5848
   502  	if reverse {
   503  		addrSource = k.DestAddr.String()
   504  		addrDest = k.SourceAddr.String()
   505  	} else {
   506  		addrSource = k.SourceAddr.String()
   507  		addrDest = k.DestAddr.String()
   508  	}
   509  
   510  	if k.Flags&TUPLE_F_SERVICE != 0 {
   511  		sb.WriteString(fmt.Sprintf("%s SVC %s:%d -> %s:%d ",
   512  			k.NextHeader.String(), k.SourceAddr.String(), k.DestPort,
   513  			k.DestAddr.String(), k.SourcePort),
   514  		)
   515  	} else if k.Flags&TUPLE_F_IN != 0 {
   516  		sb.WriteString(fmt.Sprintf("%s IN %s:%d -> %s:%d ",
   517  			k.NextHeader.String(), addrSource, k.SourcePort,
   518  			addrDest, k.DestPort),
   519  		)
   520  	} else {
   521  		sb.WriteString(fmt.Sprintf("%s OUT %s:%d -> %s:%d ",
   522  			k.NextHeader.String(), addrSource, k.SourcePort,
   523  			addrDest, k.DestPort),
   524  		)
   525  	}
   526  
   527  	if k.Flags&TUPLE_F_RELATED != 0 {
   528  		sb.WriteString("related ")
   529  	}
   530  
   531  	return true
   532  }
   533  
   534  func (k *CtKey6Global) GetTupleKey() tuple.TupleKey {
   535  	return &k.TupleKey6Global
   536  }
   537  
   538  // CtEntry represents an entry in the connection tracking table.
   539  type CtEntry struct {
   540  	Reserved0 uint64 `align:"reserved0"`
   541  	BackendID uint64 `align:"backend_id"`
   542  	Packets   uint64 `align:"packets"`
   543  	Bytes     uint64 `align:"bytes"`
   544  	Lifetime  uint32 `align:"lifetime"`
   545  	Flags     uint16 `align:"rx_closing"`
   546  	// RevNAT is in network byte order
   547  	RevNAT           uint16 `align:"rev_nat_index"`
   548  	IfIndex          uint16 `align:"ifindex"`
   549  	TxFlagsSeen      uint8  `align:"tx_flags_seen"`
   550  	RxFlagsSeen      uint8  `align:"rx_flags_seen"`
   551  	SourceSecurityID uint32 `align:"src_sec_id"`
   552  	LastTxReport     uint32 `align:"last_tx_report"`
   553  	LastRxReport     uint32 `align:"last_rx_report"`
   554  }
   555  
   556  const SizeofCtEntry = int(unsafe.Sizeof(CtEntry{}))
   557  
   558  const (
   559  	RxClosing = 1 << iota
   560  	TxClosing
   561  	Nat64
   562  	LBLoopback
   563  	SeenNonSyn
   564  	NodePort
   565  	ProxyRedirect
   566  	DSRInternal
   567  	FromL7LB
   568  	Reserved1
   569  	FromTunnel
   570  	MaxFlags
   571  )
   572  
   573  func (c *CtEntry) isDsrInternalEntry() bool {
   574  	return c.Flags&DSRInternal != 0
   575  }
   576  
   577  func (c *CtEntry) flagsString() string {
   578  	var sb strings.Builder
   579  
   580  	sb.WriteString(fmt.Sprintf("Flags=%#04x [ ", c.Flags))
   581  	if (c.Flags & RxClosing) != 0 {
   582  		sb.WriteString("RxClosing ")
   583  	}
   584  	if (c.Flags & TxClosing) != 0 {
   585  		sb.WriteString("TxClosing ")
   586  	}
   587  	if (c.Flags & Nat64) != 0 {
   588  		sb.WriteString("Nat64 ")
   589  	}
   590  	if (c.Flags & LBLoopback) != 0 {
   591  		sb.WriteString("LBLoopback ")
   592  	}
   593  	if (c.Flags & SeenNonSyn) != 0 {
   594  		sb.WriteString("SeenNonSyn ")
   595  	}
   596  	if (c.Flags & NodePort) != 0 {
   597  		sb.WriteString("NodePort ")
   598  	}
   599  	if (c.Flags & ProxyRedirect) != 0 {
   600  		sb.WriteString("ProxyRedirect ")
   601  	}
   602  	if (c.Flags & DSRInternal) != 0 {
   603  		sb.WriteString("DSRInternal ")
   604  	}
   605  	if (c.Flags & FromL7LB) != 0 {
   606  		sb.WriteString("FromL7LB ")
   607  	}
   608  	if (c.Flags & FromTunnel) != 0 {
   609  		sb.WriteString("FromTunnel ")
   610  	}
   611  
   612  	unknownFlags := c.Flags
   613  	unknownFlags &^= MaxFlags - 1
   614  	if unknownFlags != 0 {
   615  		sb.WriteString(fmt.Sprintf("Unknown=%#04x ", unknownFlags))
   616  	}
   617  	sb.WriteString("]")
   618  	return sb.String()
   619  }
   620  
   621  func (c *CtEntry) StringWithTimeDiff(toRemSecs func(uint32) string) string {
   622  
   623  	var timeDiff string
   624  	if toRemSecs != nil {
   625  		timeDiff = fmt.Sprintf(" (%s)", toRemSecs(c.Lifetime))
   626  	} else {
   627  		timeDiff = ""
   628  	}
   629  
   630  	return fmt.Sprintf("expires=%d%s Packets=%d Bytes=%d RxFlagsSeen=%#02x LastRxReport=%d TxFlagsSeen=%#02x LastTxReport=%d %s RevNAT=%d SourceSecurityID=%d IfIndex=%d BackendID=%d \n",
   631  		c.Lifetime,
   632  		timeDiff,
   633  		c.Packets,
   634  		c.Bytes,
   635  		c.RxFlagsSeen,
   636  		c.LastRxReport,
   637  		c.TxFlagsSeen,
   638  		c.LastTxReport,
   639  		c.flagsString(),
   640  		byteorder.NetworkToHost16(c.RevNAT),
   641  		c.SourceSecurityID,
   642  		c.IfIndex,
   643  		c.BackendID)
   644  }
   645  
   646  // String returns the readable format
   647  func (c *CtEntry) String() string {
   648  	return c.StringWithTimeDiff(nil)
   649  }
   650  
   651  func (c *CtEntry) New() bpf.MapValue { return &CtEntry{} }