123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372 |
- package netlink
- import (
- "bytes"
- "encoding/binary"
- "errors"
- "fmt"
- "net"
- "syscall"
- "github.com/vishvananda/netlink/nl"
- )
- // ConntrackTableType Conntrack table for the netlink operation
- type ConntrackTableType uint8
- const (
- // ConntrackTable Conntrack table
- // https://github.com/torvalds/linux/blob/master/include/uapi/linux/netfilter/nfnetlink.h -> #define NFNL_SUBSYS_CTNETLINK 1
- ConntrackTable = 1
- // ConntrackExpectTable Conntrack expect table
- // https://github.com/torvalds/linux/blob/master/include/uapi/linux/netfilter/nfnetlink.h -> #define NFNL_SUBSYS_CTNETLINK_EXP 2
- ConntrackExpectTable = 2
- )
- const (
- // For Parsing Mark
- TCP_PROTO = 6
- UDP_PROTO = 17
- )
- const (
- // backward compatibility with golang 1.6 which does not have io.SeekCurrent
- seekCurrent = 1
- )
- // InetFamily Family type
- type InetFamily uint8
- // -L [table] [options] List conntrack or expectation table
- // -G [table] parameters Get conntrack or expectation
- // -I [table] parameters Create a conntrack or expectation
- // -U [table] parameters Update a conntrack
- // -E [table] [options] Show events
- // -C [table] Show counter
- // -S Show statistics
- // ConntrackTableList returns the flow list of a table of a specific family
- // conntrack -L [table] [options] List conntrack or expectation table
- func ConntrackTableList(table ConntrackTableType, family InetFamily) ([]*ConntrackFlow, error) {
- return pkgHandle.ConntrackTableList(table, family)
- }
- // ConntrackTableFlush flushes all the flows of a specified table
- // conntrack -F [table] Flush table
- // The flush operation applies to all the family types
- func ConntrackTableFlush(table ConntrackTableType) error {
- return pkgHandle.ConntrackTableFlush(table)
- }
- // ConntrackDeleteFilter deletes entries on the specified table on the base of the filter
- // conntrack -D [table] parameters Delete conntrack or expectation
- func ConntrackDeleteFilter(table ConntrackTableType, family InetFamily, filter CustomConntrackFilter) (uint, error) {
- return pkgHandle.ConntrackDeleteFilter(table, family, filter)
- }
- // ConntrackTableList returns the flow list of a table of a specific family using the netlink handle passed
- // conntrack -L [table] [options] List conntrack or expectation table
- func (h *Handle) ConntrackTableList(table ConntrackTableType, family InetFamily) ([]*ConntrackFlow, error) {
- res, err := h.dumpConntrackTable(table, family)
- if err != nil {
- return nil, err
- }
- // Deserialize all the flows
- var result []*ConntrackFlow
- for _, dataRaw := range res {
- result = append(result, parseRawData(dataRaw))
- }
- return result, nil
- }
- // ConntrackTableFlush flushes all the flows of a specified table using the netlink handle passed
- // conntrack -F [table] Flush table
- // The flush operation applies to all the family types
- func (h *Handle) ConntrackTableFlush(table ConntrackTableType) error {
- req := h.newConntrackRequest(table, syscall.AF_INET, nl.IPCTNL_MSG_CT_DELETE, syscall.NLM_F_ACK)
- _, err := req.Execute(syscall.NETLINK_NETFILTER, 0)
- return err
- }
- // ConntrackDeleteFilter deletes entries on the specified table on the base of the filter using the netlink handle passed
- // conntrack -D [table] parameters Delete conntrack or expectation
- func (h *Handle) ConntrackDeleteFilter(table ConntrackTableType, family InetFamily, filter CustomConntrackFilter) (uint, error) {
- res, err := h.dumpConntrackTable(table, family)
- if err != nil {
- return 0, err
- }
- var matched uint
- for _, dataRaw := range res {
- flow := parseRawData(dataRaw)
- if match := filter.MatchConntrackFlow(flow); match {
- req2 := h.newConntrackRequest(table, family, nl.IPCTNL_MSG_CT_DELETE, syscall.NLM_F_ACK)
- // skip the first 4 byte that are the netfilter header, the newConntrackRequest is adding it already
- req2.AddRawData(dataRaw[4:])
- req2.Execute(syscall.NETLINK_NETFILTER, 0)
- matched++
- }
- }
- return matched, nil
- }
- func (h *Handle) newConntrackRequest(table ConntrackTableType, family InetFamily, operation, flags int) *nl.NetlinkRequest {
- // Create the Netlink request object
- req := h.newNetlinkRequest((int(table)<<8)|operation, flags)
- // Add the netfilter header
- msg := &nl.Nfgenmsg{
- NfgenFamily: uint8(family),
- Version: nl.NFNETLINK_V0,
- ResId: 0,
- }
- req.AddData(msg)
- return req
- }
- func (h *Handle) dumpConntrackTable(table ConntrackTableType, family InetFamily) ([][]byte, error) {
- req := h.newConntrackRequest(table, family, nl.IPCTNL_MSG_CT_GET, syscall.NLM_F_DUMP)
- return req.Execute(syscall.NETLINK_NETFILTER, 0)
- }
- // The full conntrack flow structure is very complicated and can be found in the file:
- // http://git.netfilter.org/libnetfilter_conntrack/tree/include/internal/object.h
- // For the time being, the structure below allows to parse and extract the base information of a flow
- type ipTuple struct {
- SrcIP net.IP
- DstIP net.IP
- Protocol uint8
- SrcPort uint16
- DstPort uint16
- }
- type ConntrackFlow struct {
- FamilyType uint8
- Forward ipTuple
- Reverse ipTuple
- Mark uint32
- }
- func (s *ConntrackFlow) String() string {
- // conntrack cmd output:
- // udp 17 src=127.0.0.1 dst=127.0.0.1 sport=4001 dport=1234 [UNREPLIED] src=127.0.0.1 dst=127.0.0.1 sport=1234 dport=4001 mark=0
- return fmt.Sprintf("%s\t%d src=%s dst=%s sport=%d dport=%d\tsrc=%s dst=%s sport=%d dport=%d mark=%d",
- nl.L4ProtoMap[s.Forward.Protocol], s.Forward.Protocol,
- s.Forward.SrcIP.String(), s.Forward.DstIP.String(), s.Forward.SrcPort, s.Forward.DstPort,
- s.Reverse.SrcIP.String(), s.Reverse.DstIP.String(), s.Reverse.SrcPort, s.Reverse.DstPort, s.Mark)
- }
- // This method parse the ip tuple structure
- // The message structure is the following:
- // <len, [CTA_IP_V4_SRC|CTA_IP_V6_SRC], 16 bytes for the IP>
- // <len, [CTA_IP_V4_DST|CTA_IP_V6_DST], 16 bytes for the IP>
- // <len, NLA_F_NESTED|nl.CTA_TUPLE_PROTO, 1 byte for the protocol, 3 bytes of padding>
- // <len, CTA_PROTO_SRC_PORT, 2 bytes for the source port, 2 bytes of padding>
- // <len, CTA_PROTO_DST_PORT, 2 bytes for the source port, 2 bytes of padding>
- func parseIpTuple(reader *bytes.Reader, tpl *ipTuple) uint8 {
- for i := 0; i < 2; i++ {
- _, t, _, v := parseNfAttrTLV(reader)
- switch t {
- case nl.CTA_IP_V4_SRC, nl.CTA_IP_V6_SRC:
- tpl.SrcIP = v
- case nl.CTA_IP_V4_DST, nl.CTA_IP_V6_DST:
- tpl.DstIP = v
- }
- }
- // Skip the next 4 bytes nl.NLA_F_NESTED|nl.CTA_TUPLE_PROTO
- reader.Seek(4, seekCurrent)
- _, t, _, v := parseNfAttrTLV(reader)
- if t == nl.CTA_PROTO_NUM {
- tpl.Protocol = uint8(v[0])
- }
- // Skip some padding 3 bytes
- reader.Seek(3, seekCurrent)
- for i := 0; i < 2; i++ {
- _, t, _ := parseNfAttrTL(reader)
- switch t {
- case nl.CTA_PROTO_SRC_PORT:
- parseBERaw16(reader, &tpl.SrcPort)
- case nl.CTA_PROTO_DST_PORT:
- parseBERaw16(reader, &tpl.DstPort)
- }
- // Skip some padding 2 byte
- reader.Seek(2, seekCurrent)
- }
- return tpl.Protocol
- }
- func parseNfAttrTLV(r *bytes.Reader) (isNested bool, attrType, len uint16, value []byte) {
- isNested, attrType, len = parseNfAttrTL(r)
- value = make([]byte, len)
- binary.Read(r, binary.BigEndian, &value)
- return isNested, attrType, len, value
- }
- func parseNfAttrTL(r *bytes.Reader) (isNested bool, attrType, len uint16) {
- binary.Read(r, nl.NativeEndian(), &len)
- len -= nl.SizeofNfattr
- binary.Read(r, nl.NativeEndian(), &attrType)
- isNested = (attrType & nl.NLA_F_NESTED) == nl.NLA_F_NESTED
- attrType = attrType & (nl.NLA_F_NESTED - 1)
- return isNested, attrType, len
- }
- func parseBERaw16(r *bytes.Reader, v *uint16) {
- binary.Read(r, binary.BigEndian, v)
- }
- func parseRawData(data []byte) *ConntrackFlow {
- s := &ConntrackFlow{}
- var proto uint8
- // First there is the Nfgenmsg header
- // consume only the family field
- reader := bytes.NewReader(data)
- binary.Read(reader, nl.NativeEndian(), &s.FamilyType)
- // skip rest of the Netfilter header
- reader.Seek(3, seekCurrent)
- // The message structure is the following:
- // <len, NLA_F_NESTED|CTA_TUPLE_ORIG> 4 bytes
- // <len, NLA_F_NESTED|CTA_TUPLE_IP> 4 bytes
- // flow information of the forward flow
- // <len, NLA_F_NESTED|CTA_TUPLE_REPLY> 4 bytes
- // <len, NLA_F_NESTED|CTA_TUPLE_IP> 4 bytes
- // flow information of the reverse flow
- for reader.Len() > 0 {
- nested, t, l := parseNfAttrTL(reader)
- if nested && t == nl.CTA_TUPLE_ORIG {
- if nested, t, _ = parseNfAttrTL(reader); nested && t == nl.CTA_TUPLE_IP {
- proto = parseIpTuple(reader, &s.Forward)
- }
- } else if nested && t == nl.CTA_TUPLE_REPLY {
- if nested, t, _ = parseNfAttrTL(reader); nested && t == nl.CTA_TUPLE_IP {
- parseIpTuple(reader, &s.Reverse)
- // Got all the useful information stop parsing
- break
- } else {
- // Header not recognized skip it
- reader.Seek(int64(l), seekCurrent)
- }
- }
- }
- if proto == TCP_PROTO {
- reader.Seek(64, seekCurrent)
- _, t, _, v := parseNfAttrTLV(reader)
- if t == nl.CTA_MARK {
- s.Mark = uint32(v[3])
- }
- } else if proto == UDP_PROTO {
- reader.Seek(16, seekCurrent)
- _, t, _, v := parseNfAttrTLV(reader)
- if t == nl.CTA_MARK {
- s.Mark = uint32(v[3])
- }
- }
- return s
- }
- // Conntrack parameters and options:
- // -n, --src-nat ip source NAT ip
- // -g, --dst-nat ip destination NAT ip
- // -j, --any-nat ip source or destination NAT ip
- // -m, --mark mark Set mark
- // -c, --secmark secmark Set selinux secmark
- // -e, --event-mask eventmask Event mask, eg. NEW,DESTROY
- // -z, --zero Zero counters while listing
- // -o, --output type[,...] Output format, eg. xml
- // -l, --label label[,...] conntrack labels
- // Common parameters and options:
- // -s, --src, --orig-src ip Source address from original direction
- // -d, --dst, --orig-dst ip Destination address from original direction
- // -r, --reply-src ip Source addres from reply direction
- // -q, --reply-dst ip Destination address from reply direction
- // -p, --protonum proto Layer 4 Protocol, eg. 'tcp'
- // -f, --family proto Layer 3 Protocol, eg. 'ipv6'
- // -t, --timeout timeout Set timeout
- // -u, --status status Set status, eg. ASSURED
- // -w, --zone value Set conntrack zone
- // --orig-zone value Set zone for original direction
- // --reply-zone value Set zone for reply direction
- // -b, --buffer-size Netlink socket buffer size
- // --mask-src ip Source mask address
- // --mask-dst ip Destination mask address
- // Filter types
- type ConntrackFilterType uint8
- const (
- ConntrackOrigSrcIP = iota // -orig-src ip Source address from original direction
- ConntrackOrigDstIP // -orig-dst ip Destination address from original direction
- ConntrackNatSrcIP // -src-nat ip Source NAT ip
- ConntrackNatDstIP // -dst-nat ip Destination NAT ip
- ConntrackNatAnyIP // -any-nat ip Source or destination NAT ip
- )
- type CustomConntrackFilter interface {
- // MatchConntrackFlow applies the filter to the flow and returns true if the flow matches
- // the filter or false otherwise
- MatchConntrackFlow(flow *ConntrackFlow) bool
- }
- type ConntrackFilter struct {
- ipFilter map[ConntrackFilterType]net.IP
- }
- // AddIP adds an IP to the conntrack filter
- func (f *ConntrackFilter) AddIP(tp ConntrackFilterType, ip net.IP) error {
- if f.ipFilter == nil {
- f.ipFilter = make(map[ConntrackFilterType]net.IP)
- }
- if _, ok := f.ipFilter[tp]; ok {
- return errors.New("Filter attribute already present")
- }
- f.ipFilter[tp] = ip
- return nil
- }
- // MatchConntrackFlow applies the filter to the flow and returns true if the flow matches the filter
- // false otherwise
- func (f *ConntrackFilter) MatchConntrackFlow(flow *ConntrackFlow) bool {
- if len(f.ipFilter) == 0 {
- // empty filter always not match
- return false
- }
- match := true
- // -orig-src ip Source address from original direction
- if elem, found := f.ipFilter[ConntrackOrigSrcIP]; found {
- match = match && elem.Equal(flow.Forward.SrcIP)
- }
- // -orig-dst ip Destination address from original direction
- if elem, found := f.ipFilter[ConntrackOrigDstIP]; match && found {
- match = match && elem.Equal(flow.Forward.DstIP)
- }
- // -src-nat ip Source NAT ip
- if elem, found := f.ipFilter[ConntrackNatSrcIP]; match && found {
- match = match && elem.Equal(flow.Reverse.SrcIP)
- }
- // -dst-nat ip Destination NAT ip
- if elem, found := f.ipFilter[ConntrackNatDstIP]; match && found {
- match = match && elem.Equal(flow.Reverse.DstIP)
- }
- // -any-nat ip Source or destination NAT ip
- if elem, found := f.ipFilter[ConntrackNatAnyIP]; match && found {
- match = match && (elem.Equal(flow.Reverse.SrcIP) || elem.Equal(flow.Reverse.DstIP))
- }
- return match
- }
- var _ CustomConntrackFilter = (*ConntrackFilter)(nil)
|