github.com/vishvananda/netlink@v1.1.0/conntrack_linux.go (about)

     1  package netlink
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"errors"
     7  	"fmt"
     8  	"net"
     9  
    10  	"github.com/vishvananda/netlink/nl"
    11  	"golang.org/x/sys/unix"
    12  )
    13  
    14  // ConntrackTableType Conntrack table for the netlink operation
    15  type ConntrackTableType uint8
    16  
    17  const (
    18  	// ConntrackTable Conntrack table
    19  	// https://github.com/torvalds/linux/blob/master/include/uapi/linux/netfilter/nfnetlink.h -> #define NFNL_SUBSYS_CTNETLINK		 1
    20  	ConntrackTable = 1
    21  	// ConntrackExpectTable Conntrack expect table
    22  	// https://github.com/torvalds/linux/blob/master/include/uapi/linux/netfilter/nfnetlink.h -> #define NFNL_SUBSYS_CTNETLINK_EXP 2
    23  	ConntrackExpectTable = 2
    24  )
    25  
    26  const (
    27  	// backward compatibility with golang 1.6 which does not have io.SeekCurrent
    28  	seekCurrent = 1
    29  )
    30  
    31  // InetFamily Family type
    32  type InetFamily uint8
    33  
    34  //  -L [table] [options]          List conntrack or expectation table
    35  //  -G [table] parameters         Get conntrack or expectation
    36  
    37  //  -I [table] parameters         Create a conntrack or expectation
    38  //  -U [table] parameters         Update a conntrack
    39  //  -E [table] [options]          Show events
    40  
    41  //  -C [table]                    Show counter
    42  //  -S                            Show statistics
    43  
    44  // ConntrackTableList returns the flow list of a table of a specific family
    45  // conntrack -L [table] [options]          List conntrack or expectation table
    46  func ConntrackTableList(table ConntrackTableType, family InetFamily) ([]*ConntrackFlow, error) {
    47  	return pkgHandle.ConntrackTableList(table, family)
    48  }
    49  
    50  // ConntrackTableFlush flushes all the flows of a specified table
    51  // conntrack -F [table]            Flush table
    52  // The flush operation applies to all the family types
    53  func ConntrackTableFlush(table ConntrackTableType) error {
    54  	return pkgHandle.ConntrackTableFlush(table)
    55  }
    56  
    57  // ConntrackDeleteFilter deletes entries on the specified table on the base of the filter
    58  // conntrack -D [table] parameters         Delete conntrack or expectation
    59  func ConntrackDeleteFilter(table ConntrackTableType, family InetFamily, filter CustomConntrackFilter) (uint, error) {
    60  	return pkgHandle.ConntrackDeleteFilter(table, family, filter)
    61  }
    62  
    63  // ConntrackTableList returns the flow list of a table of a specific family using the netlink handle passed
    64  // conntrack -L [table] [options]          List conntrack or expectation table
    65  func (h *Handle) ConntrackTableList(table ConntrackTableType, family InetFamily) ([]*ConntrackFlow, error) {
    66  	res, err := h.dumpConntrackTable(table, family)
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  
    71  	// Deserialize all the flows
    72  	var result []*ConntrackFlow
    73  	for _, dataRaw := range res {
    74  		result = append(result, parseRawData(dataRaw))
    75  	}
    76  
    77  	return result, nil
    78  }
    79  
    80  // ConntrackTableFlush flushes all the flows of a specified table using the netlink handle passed
    81  // conntrack -F [table]            Flush table
    82  // The flush operation applies to all the family types
    83  func (h *Handle) ConntrackTableFlush(table ConntrackTableType) error {
    84  	req := h.newConntrackRequest(table, unix.AF_INET, nl.IPCTNL_MSG_CT_DELETE, unix.NLM_F_ACK)
    85  	_, err := req.Execute(unix.NETLINK_NETFILTER, 0)
    86  	return err
    87  }
    88  
    89  // ConntrackDeleteFilter deletes entries on the specified table on the base of the filter using the netlink handle passed
    90  // conntrack -D [table] parameters         Delete conntrack or expectation
    91  func (h *Handle) ConntrackDeleteFilter(table ConntrackTableType, family InetFamily, filter CustomConntrackFilter) (uint, error) {
    92  	res, err := h.dumpConntrackTable(table, family)
    93  	if err != nil {
    94  		return 0, err
    95  	}
    96  
    97  	var matched uint
    98  	for _, dataRaw := range res {
    99  		flow := parseRawData(dataRaw)
   100  		if match := filter.MatchConntrackFlow(flow); match {
   101  			req2 := h.newConntrackRequest(table, family, nl.IPCTNL_MSG_CT_DELETE, unix.NLM_F_ACK)
   102  			// skip the first 4 byte that are the netfilter header, the newConntrackRequest is adding it already
   103  			req2.AddRawData(dataRaw[4:])
   104  			req2.Execute(unix.NETLINK_NETFILTER, 0)
   105  			matched++
   106  		}
   107  	}
   108  
   109  	return matched, nil
   110  }
   111  
   112  func (h *Handle) newConntrackRequest(table ConntrackTableType, family InetFamily, operation, flags int) *nl.NetlinkRequest {
   113  	// Create the Netlink request object
   114  	req := h.newNetlinkRequest((int(table)<<8)|operation, flags)
   115  	// Add the netfilter header
   116  	msg := &nl.Nfgenmsg{
   117  		NfgenFamily: uint8(family),
   118  		Version:     nl.NFNETLINK_V0,
   119  		ResId:       0,
   120  	}
   121  	req.AddData(msg)
   122  	return req
   123  }
   124  
   125  func (h *Handle) dumpConntrackTable(table ConntrackTableType, family InetFamily) ([][]byte, error) {
   126  	req := h.newConntrackRequest(table, family, nl.IPCTNL_MSG_CT_GET, unix.NLM_F_DUMP)
   127  	return req.Execute(unix.NETLINK_NETFILTER, 0)
   128  }
   129  
   130  // The full conntrack flow structure is very complicated and can be found in the file:
   131  // http://git.netfilter.org/libnetfilter_conntrack/tree/include/internal/object.h
   132  // For the time being, the structure below allows to parse and extract the base information of a flow
   133  type ipTuple struct {
   134  	Bytes    uint64
   135  	DstIP    net.IP
   136  	DstPort  uint16
   137  	Packets  uint64
   138  	Protocol uint8
   139  	SrcIP    net.IP
   140  	SrcPort  uint16
   141  }
   142  
   143  type ConntrackFlow struct {
   144  	FamilyType uint8
   145  	Forward    ipTuple
   146  	Reverse    ipTuple
   147  	Mark       uint32
   148  }
   149  
   150  func (s *ConntrackFlow) String() string {
   151  	// conntrack cmd output:
   152  	// udp      17 src=127.0.0.1 dst=127.0.0.1 sport=4001 dport=1234 packets=5 bytes=532 [UNREPLIED] src=127.0.0.1 dst=127.0.0.1 sport=1234 dport=4001 packets=10 bytes=1078 mark=0
   153  	return fmt.Sprintf("%s\t%d src=%s dst=%s sport=%d dport=%d packets=%d bytes=%d\tsrc=%s dst=%s sport=%d dport=%d packets=%d bytes=%d mark=%d",
   154  		nl.L4ProtoMap[s.Forward.Protocol], s.Forward.Protocol,
   155  		s.Forward.SrcIP.String(), s.Forward.DstIP.String(), s.Forward.SrcPort, s.Forward.DstPort, s.Forward.Packets, s.Forward.Bytes,
   156  		s.Reverse.SrcIP.String(), s.Reverse.DstIP.String(), s.Reverse.SrcPort, s.Reverse.DstPort, s.Reverse.Packets, s.Reverse.Bytes,
   157  		s.Mark)
   158  }
   159  
   160  // This method parse the ip tuple structure
   161  // The message structure is the following:
   162  // <len, [CTA_IP_V4_SRC|CTA_IP_V6_SRC], 16 bytes for the IP>
   163  // <len, [CTA_IP_V4_DST|CTA_IP_V6_DST], 16 bytes for the IP>
   164  // <len, NLA_F_NESTED|nl.CTA_TUPLE_PROTO, 1 byte for the protocol, 3 bytes of padding>
   165  // <len, CTA_PROTO_SRC_PORT, 2 bytes for the source port, 2 bytes of padding>
   166  // <len, CTA_PROTO_DST_PORT, 2 bytes for the source port, 2 bytes of padding>
   167  func parseIpTuple(reader *bytes.Reader, tpl *ipTuple) uint8 {
   168  	for i := 0; i < 2; i++ {
   169  		_, t, _, v := parseNfAttrTLV(reader)
   170  		switch t {
   171  		case nl.CTA_IP_V4_SRC, nl.CTA_IP_V6_SRC:
   172  			tpl.SrcIP = v
   173  		case nl.CTA_IP_V4_DST, nl.CTA_IP_V6_DST:
   174  			tpl.DstIP = v
   175  		}
   176  	}
   177  	// Skip the next 4 bytes  nl.NLA_F_NESTED|nl.CTA_TUPLE_PROTO
   178  	reader.Seek(4, seekCurrent)
   179  	_, t, _, v := parseNfAttrTLV(reader)
   180  	if t == nl.CTA_PROTO_NUM {
   181  		tpl.Protocol = uint8(v[0])
   182  	}
   183  	// Skip some padding 3 bytes
   184  	reader.Seek(3, seekCurrent)
   185  	for i := 0; i < 2; i++ {
   186  		_, t, _ := parseNfAttrTL(reader)
   187  		switch t {
   188  		case nl.CTA_PROTO_SRC_PORT:
   189  			parseBERaw16(reader, &tpl.SrcPort)
   190  		case nl.CTA_PROTO_DST_PORT:
   191  			parseBERaw16(reader, &tpl.DstPort)
   192  		}
   193  		// Skip some padding 2 byte
   194  		reader.Seek(2, seekCurrent)
   195  	}
   196  	return tpl.Protocol
   197  }
   198  
   199  func parseNfAttrTLV(r *bytes.Reader) (isNested bool, attrType, len uint16, value []byte) {
   200  	isNested, attrType, len = parseNfAttrTL(r)
   201  
   202  	value = make([]byte, len)
   203  	binary.Read(r, binary.BigEndian, &value)
   204  	return isNested, attrType, len, value
   205  }
   206  
   207  func parseNfAttrTL(r *bytes.Reader) (isNested bool, attrType, len uint16) {
   208  	binary.Read(r, nl.NativeEndian(), &len)
   209  	len -= nl.SizeofNfattr
   210  
   211  	binary.Read(r, nl.NativeEndian(), &attrType)
   212  	isNested = (attrType & nl.NLA_F_NESTED) == nl.NLA_F_NESTED
   213  	attrType = attrType & (nl.NLA_F_NESTED - 1)
   214  
   215  	return isNested, attrType, len
   216  }
   217  
   218  func parseBERaw16(r *bytes.Reader, v *uint16) {
   219  	binary.Read(r, binary.BigEndian, v)
   220  }
   221  
   222  func parseBERaw32(r *bytes.Reader, v *uint32) {
   223  	binary.Read(r, binary.BigEndian, v)
   224  }
   225  
   226  func parseBERaw64(r *bytes.Reader, v *uint64) {
   227  	binary.Read(r, binary.BigEndian, v)
   228  }
   229  
   230  func parseByteAndPacketCounters(r *bytes.Reader) (bytes, packets uint64) {
   231  	for i := 0; i < 2; i++ {
   232  		switch _, t, _ := parseNfAttrTL(r); t {
   233  		case nl.CTA_COUNTERS_BYTES:
   234  			parseBERaw64(r, &bytes)
   235  		case nl.CTA_COUNTERS_PACKETS:
   236  			parseBERaw64(r, &packets)
   237  		default:
   238  			return
   239  		}
   240  	}
   241  	return
   242  }
   243  
   244  func parseConnectionMark(r *bytes.Reader) (mark uint32) {
   245  	parseBERaw32(r, &mark)
   246  	return
   247  }
   248  
   249  func parseRawData(data []byte) *ConntrackFlow {
   250  	s := &ConntrackFlow{}
   251  	// First there is the Nfgenmsg header
   252  	// consume only the family field
   253  	reader := bytes.NewReader(data)
   254  	binary.Read(reader, nl.NativeEndian(), &s.FamilyType)
   255  
   256  	// skip rest of the Netfilter header
   257  	reader.Seek(3, seekCurrent)
   258  	// The message structure is the following:
   259  	// <len, NLA_F_NESTED|CTA_TUPLE_ORIG> 4 bytes
   260  	// <len, NLA_F_NESTED|CTA_TUPLE_IP> 4 bytes
   261  	// flow information of the forward flow
   262  	// <len, NLA_F_NESTED|CTA_TUPLE_REPLY> 4 bytes
   263  	// <len, NLA_F_NESTED|CTA_TUPLE_IP> 4 bytes
   264  	// flow information of the reverse flow
   265  	for reader.Len() > 0 {
   266  		if nested, t, l := parseNfAttrTL(reader); nested {
   267  			switch t {
   268  			case nl.CTA_TUPLE_ORIG:
   269  				if nested, t, _ = parseNfAttrTL(reader); nested && t == nl.CTA_TUPLE_IP {
   270  					parseIpTuple(reader, &s.Forward)
   271  				}
   272  			case nl.CTA_TUPLE_REPLY:
   273  				if nested, t, _ = parseNfAttrTL(reader); nested && t == nl.CTA_TUPLE_IP {
   274  					parseIpTuple(reader, &s.Reverse)
   275  				} else {
   276  					// Header not recognized skip it
   277  					reader.Seek(int64(l), seekCurrent)
   278  				}
   279  			case nl.CTA_COUNTERS_ORIG:
   280  				s.Forward.Bytes, s.Forward.Packets = parseByteAndPacketCounters(reader)
   281  			case nl.CTA_COUNTERS_REPLY:
   282  				s.Reverse.Bytes, s.Reverse.Packets = parseByteAndPacketCounters(reader)
   283  			}
   284  		} else {
   285  			switch t {
   286  			case nl.CTA_MARK:
   287  				s.Mark = parseConnectionMark(reader)
   288  			}
   289  		}
   290  	}
   291  	return s
   292  }
   293  
   294  // Conntrack parameters and options:
   295  //   -n, --src-nat ip                      source NAT ip
   296  //   -g, --dst-nat ip                      destination NAT ip
   297  //   -j, --any-nat ip                      source or destination NAT ip
   298  //   -m, --mark mark                       Set mark
   299  //   -c, --secmark secmark                 Set selinux secmark
   300  //   -e, --event-mask eventmask            Event mask, eg. NEW,DESTROY
   301  //   -z, --zero                            Zero counters while listing
   302  //   -o, --output type[,...]               Output format, eg. xml
   303  //   -l, --label label[,...]               conntrack labels
   304  
   305  // Common parameters and options:
   306  //   -s, --src, --orig-src ip              Source address from original direction
   307  //   -d, --dst, --orig-dst ip              Destination address from original direction
   308  //   -r, --reply-src ip            Source address from reply direction
   309  //   -q, --reply-dst ip            Destination address from reply direction
   310  //   -p, --protonum proto          Layer 4 Protocol, eg. 'tcp'
   311  //   -f, --family proto            Layer 3 Protocol, eg. 'ipv6'
   312  //   -t, --timeout timeout         Set timeout
   313  //   -u, --status status           Set status, eg. ASSURED
   314  //   -w, --zone value              Set conntrack zone
   315  //   --orig-zone value             Set zone for original direction
   316  //   --reply-zone value            Set zone for reply direction
   317  //   -b, --buffer-size             Netlink socket buffer size
   318  //   --mask-src ip                 Source mask address
   319  //   --mask-dst ip                 Destination mask address
   320  
   321  // Filter types
   322  type ConntrackFilterType uint8
   323  
   324  const (
   325  	ConntrackOrigSrcIP  = iota                // -orig-src ip    Source address from original direction
   326  	ConntrackOrigDstIP                        // -orig-dst ip    Destination address from original direction
   327  	ConntrackReplySrcIP                       // --reply-src ip  Reply Source IP
   328  	ConntrackReplyDstIP                       // --reply-dst ip  Reply Destination IP
   329  	ConntrackReplyAnyIP                       // Match source or destination reply IP
   330  	ConntrackNatSrcIP   = ConntrackReplySrcIP // deprecated use instead ConntrackReplySrcIP
   331  	ConntrackNatDstIP   = ConntrackReplyDstIP // deprecated use instead ConntrackReplyDstIP
   332  	ConntrackNatAnyIP   = ConntrackReplyAnyIP // deprecated use instaed ConntrackReplyAnyIP
   333  )
   334  
   335  type CustomConntrackFilter interface {
   336  	// MatchConntrackFlow applies the filter to the flow and returns true if the flow matches
   337  	// the filter or false otherwise
   338  	MatchConntrackFlow(flow *ConntrackFlow) bool
   339  }
   340  
   341  type ConntrackFilter struct {
   342  	ipFilter map[ConntrackFilterType]net.IP
   343  }
   344  
   345  // AddIP adds an IP to the conntrack filter
   346  func (f *ConntrackFilter) AddIP(tp ConntrackFilterType, ip net.IP) error {
   347  	if f.ipFilter == nil {
   348  		f.ipFilter = make(map[ConntrackFilterType]net.IP)
   349  	}
   350  	if _, ok := f.ipFilter[tp]; ok {
   351  		return errors.New("Filter attribute already present")
   352  	}
   353  	f.ipFilter[tp] = ip
   354  	return nil
   355  }
   356  
   357  // MatchConntrackFlow applies the filter to the flow and returns true if the flow matches the filter
   358  // false otherwise
   359  func (f *ConntrackFilter) MatchConntrackFlow(flow *ConntrackFlow) bool {
   360  	if len(f.ipFilter) == 0 {
   361  		// empty filter always not match
   362  		return false
   363  	}
   364  
   365  	match := true
   366  	// -orig-src ip   Source address from original direction
   367  	if elem, found := f.ipFilter[ConntrackOrigSrcIP]; found {
   368  		match = match && elem.Equal(flow.Forward.SrcIP)
   369  	}
   370  
   371  	// -orig-dst ip   Destination address from original direction
   372  	if elem, found := f.ipFilter[ConntrackOrigDstIP]; match && found {
   373  		match = match && elem.Equal(flow.Forward.DstIP)
   374  	}
   375  
   376  	// -src-nat ip    Source NAT ip
   377  	if elem, found := f.ipFilter[ConntrackReplySrcIP]; match && found {
   378  		match = match && elem.Equal(flow.Reverse.SrcIP)
   379  	}
   380  
   381  	// -dst-nat ip    Destination NAT ip
   382  	if elem, found := f.ipFilter[ConntrackReplyDstIP]; match && found {
   383  		match = match && elem.Equal(flow.Reverse.DstIP)
   384  	}
   385  
   386  	// Match source or destination reply IP
   387  	if elem, found := f.ipFilter[ConntrackReplyAnyIP]; match && found {
   388  		match = match && (elem.Equal(flow.Reverse.SrcIP) || elem.Equal(flow.Reverse.DstIP))
   389  	}
   390  
   391  	return match
   392  }
   393  
   394  var _ CustomConntrackFilter = (*ConntrackFilter)(nil)