Git Product home page Git Product logo

nftables's Introduction

Build Status GoDoc

This is not the correct repository for issues with the Linux nftables project! This repository contains a third-party Go package to programmatically interact with nftables. Find the official nftables website at https://wiki.nftables.org/

This package manipulates Linux nftables (the iptables successor). It is implemented in pure Go, i.e. does not wrap libnftnl.

This is not an official Google product.

Breaking changes

This package is in very early stages, and only contains enough data types and functions to install very basic nftables rules. It is likely that mistakes with the data types/API will be identified as more functionality is added.

Contributions

Contributions are very welcome!

nftables's People

Contributors

004helix avatar alexispires avatar black-desk avatar capnspacehook avatar cheina97 avatar dependabot[bot] avatar greenpau avatar gregdel avatar iwind avatar jniewt avatar jronak avatar konradh avatar logicaloverflow avatar lyonsdpy avatar mdlayher avatar methril avatar minaru avatar rwhelan avatar sbezverk avatar singchia avatar stapelberg avatar stv0g avatar thediveo avatar ti-mo avatar tommie avatar turekt avatar twitchy-jsonp avatar vsandonis avatar zacatac avatar zhaofeng0019 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

nftables's Issues

Impossible to create a chain with a DROP default policy

Hello,

As I've started to try around some basic nftables manipulation through this package, I encountered a behavior which doesn't seem wanted.

As we can see here, Chain is a struct defined with a Policy field, typed as uint32. It takes some research (netlink.h) to know what value to use to get the result we want (as in, DROP or ACCEPT).

From the previous link, the dev now knows that they should use 0 to make their default policy DROP, and use 1 to make their default policy ACCEPT. Default policies can't be STOLEN, QUEUE, REPEAT or STOP, and as such, the netlink lib returns you an error if you try to do so.

The issue lies in the fact that if you set the Policy to DROP (0), the resulting chain has an ACCEPT default policy.

After some research within the code, I've encountered that particular function which seems to be the faulty part.

In that function, we can find this snippet of code:

if c.Policy > 0 {
		data = append(data, cc.marshalAttr([]netlink.Attribute{
			{Type: unix.NFTA_CHAIN_POLICY, Data: binaryutil.BigEndian.PutUint32(uint32(c.Policy))},
		})...)
	}

Basically, the function AddChain ignores the policy if it's equal to 0, which is the value for the default policy to be set to DROP.

There's several ways to fix this, which is why I'm writing this issue toward the goal of getting feedback on how to solve it.

  • We could just fix the > operator issue, which would simply result into using a >= instead.
  • We could create a new type in chain.go, called PolicyVerdict, that would be a uint32. We also make two const, PolicyDrop and PolicyAccept, which would be defined as equal to iota (so 0 and 1, exactly the netfilter numbers for DROP and ACCEPT. This solution makes things easily understandable without any extra import, allow additional documentation about the Policy field (currently there isn't any doc about that field beside its definition) but is kind of redundant with the VerdictKind type already existing in expr submodule.
  • We use VerdictKind type as existing in expr submodule, which means we pass an int64 as an uint32 through the type. In that solution, I'm a bit scared that the endianness might create issues at some point/on some systems, due to the different size of int64/uint32 that will lead in some case to values not reflecting the initial user entry.
  • We use VerdictKind type, but this time we modify VerdictKind type to be an uint32; As defined in netfilter.h, verdict values are included between 0 and 5, so it shouldn't create any issue to functions already expecting an int64 under the VerdictKind type. It's, in my opinion, the best solution, beside the fact that an extra export will be needed to use the policy field (both nftables and nftables/expr)

I'm up to make the PR as soon as something is decided

Awaiting for your input,
Regards

DelRule fails

It appears that deleting a rule previosly fetched via GetRule() fails.

            forwardChainRules, err := nb.conn.GetRule(addr.table, forwardChain)

...
            for _, r := range forwardChainRules {
                nb.conn.DelRule(r)
...

The error is netlink receive: no such file or directory not to have occurred.

At the same time, the deleting by constructing a rule from scractch works:

                nb.conn.DelRule(&nftables.Rule{
                    Table:  &nftables.Table{Name: addr.table.Name, Family: addr.table.Family},
                    Chain:  &nftables.Chain{Name: forwardChain.Name, Type: forwardChain.Type},
                    Handle: forwardChainJumpRule.Handle,
                })

                if err := nb.conn.Flush(); err != nil {
                    return fmt.Errorf(
                        "error deleting jump rule to %s chain found in chain %s in %s table: %s",
                        chainName, forwardChain.Name, addr.table.Name, err,
                    )
                }

Question: Check if nf_tables is initialized

What would you suggest to use to check if nf_tables module is loaded from the go code. I mean other that executing external lsmod and grep for nf_tables?
Appreciate your thought about it.

Problem with creating a Set

Hi.

I took the example about a blacklisting Set from here

When i run this code, nothing shows up with "nft list ruleset".
If I remove the Set related code and the Rule, at least Table and Chain show up in the current ruleset.

Without understanding all the wire format stuff it looks like the Set related things are breaking the batch issued with Flush().

Any hint?

Add ct_stat constants to add rule with ct_state easier

Normally, I add a rule with ct_state like this , the mask field indicate the specific state like(established,related,new...). but as a user I probably dont know how to set the mask value . (I find answer on nftables.org)

func TestRule(t *testing.T) {
	conn, err := getConn()
	if err != nil {
		t.Fatal(err)
	}
	defer closeNs(netns.NsHandle(conn.NetNS))

	rule := &Rule{
		Table: mytable,
		Chain: cout,
		Exprs: []expr.Any{
			&expr.Ct{Register: 1, SourceRegister: false, Key: expr.CtKeyPROTOCOL},
			&expr.Cmp{Op: expr.CmpOpEq, Data: []byte{unix.IPPROTO_TCP}, Register: 1},
			&expr.Ct{Register: 1, SourceRegister: false, Key: expr.CtKeySTATE},
			&expr.Bitwise{
				SourceRegister: 1,
				DestRegister:   1,
				Len:            4,
				Mask:          []byte{6, 0, 0, 0},
				Xor:            []byte{0, 0, 0, 0},
			},
			&expr.Cmp{Op: expr.CmpOpNeq, Register: 1, Data: []byte{0, 0, 0, 0}},
			&expr.Verdict{Kind: expr.VerdictAccept},
		},
	}

	conn.AddRule(rule)

	err = conn.Flush()
	if err != nil {
		t.Fatal(err)
	}
}

So I think it's good to add some constants in expr/ct.go like

const (
        CtStatINVALID     uint32 = 1
	CtStatESTABLISHED uint32 = 2
	CtStatRELATED     uint32 = 4
	CtStatNEW         uint32 = 8
	CtStatUNTRACKED   uint32 = 64
)

And use them

func TestRule(t *testing.T) {
	conn, err := getConn()
	if err != nil {
		t.Fatal(err)
	}
	defer closeNs(netns.NsHandle(conn.NetNS))

	rule := &Rule{
		Table: mytable,
		Chain: cout,
		Exprs: []expr.Any{
			&expr.Ct{Register: 1, SourceRegister: false, Key: expr.CtKeyPROTOCOL},
			&expr.Cmp{Op: expr.CmpOpEq, Data: []byte{unix.IPPROTO_TCP}, Register: 1},
			&expr.Ct{Register: 1, SourceRegister: false, Key: expr.CtKeySTATE},
			&expr.Bitwise{
				SourceRegister: 1,
				DestRegister:   1,
				Len:            4,
				Mask:  binaryutil.NativeEndian.PutUint32(expr.CtStatESTABLISHED | expr.CtStatRELATED),
				Xor:   binaryutil.NativeEndian.PutUint32(0),
			},
			&expr.Cmp{Op: expr.CmpOpNeq, Register: 1, Data: []byte{0, 0, 0, 0}},
			&expr.Verdict{Kind: expr.VerdictAccept},
		},
	}

	conn.AddRule(rule)

	err = conn.Flush()
	if err != nil {
		t.Fatal(err)
	}
}

bug: GetRule and log prefix rule is not being parsed

It appears that log prefix rules are not bring parsed by GetRule()

table ip filter { # handle 443
        chain forward { # handle 1
                type filter hook forward priority filter; policy drop;
                oifname "cni-podman0" ip daddr 10.88.0.7 tcp dport 80 counter packets 0 bytes 0 accept # handle 4
                log prefix "ip4 forward drop: " # handle 2
                counter packets 0 bytes 0 drop # handle 3
        }
}

The following output represents the dump of the above rules.

Issue: Notice the Exprs: ([]expr.Any) <nil> as it relates to log prefix rule.

                oifname "cni-podman0" ip daddr 10.88.0.7 tcp dport 80 counter packets 0 bytes 0 accept # handle 4

is:

(*nftables.Rule)(0xc000267900)({
 Table: (*nftables.Table)(0xc00034a2e0)({
  Name: (string) (len=6) "filter",
  Use: (uint32) 0,
  Flags: (uint32) 0,
  Family: (nftables.TableFamily) 0
 }),
 Chain: (*nftables.Chain)(0xc00032bf80)({
  Name: (string) (len=7) "forward",
  Table: (*nftables.Table)(<nil>),
  Hooknum: (nftables.ChainHook) 0,
  Priority: (nftables.ChainPriority) 0,
  Type: (nftables.ChainType) "",
  Policy: (*nftables.ChainPolicy)(<nil>)
 }),
 Position: (uint64) 0,
 Handle: (uint64) 4,
 Exprs: ([]expr.Any) (len=10 cap=16) {
  (*expr.Meta)(0xc000307b90)({
   Key: (expr.MetaKey) 7,
   SourceRegister: (bool) false,
   Register: (uint32) 1
  }),
  (*expr.Cmp)(0xc00034a3e0)({
   Op: (expr.CmpOp) 0,
   Register: (uint32) 1,
   Data: ([]uint8) (len=16 cap=16) {
    00000000  63 6e 69 2d 70 6f 64 6d  61 6e 30 00 00 00 00 00  |cni-podman0.....|
   }
  }),
  (*expr.Payload)(0xc00034e060)({
   OperationType: (expr.PayloadOperationType) 0,
   DestRegister: (uint32) 1,
   SourceRegister: (uint32) 0,
   Base: (expr.PayloadBase) 1,
   Offset: (uint32) 16,
   Len: (uint32) 4,
   CsumType: (expr.PayloadCsumType) 0,
   CsumOffset: (uint32) 0,
   CsumFlags: (uint32) 0
  }),
  (*expr.Cmp)(0xc00034a500)({
   Op: (expr.CmpOp) 0,
   Register: (uint32) 1,
   Data: ([]uint8) (len=4 cap=4) {
    00000000  0a 58 00 07                                       |.X..|
   }
  }),
  (*expr.Meta)(0xc000307c20)({
   Key: (expr.MetaKey) 16,
   SourceRegister: (bool) false,
   Register: (uint32) 1
  }),
  (*expr.Cmp)(0xc00034a600)({
   Op: (expr.CmpOp) 0,
   Register: (uint32) 1,
   Data: ([]uint8) (len=1 cap=1) {
    00000000  06                                                |.|
   }
  }),
  (*expr.Payload)(0xc00034e090)({
   OperationType: (expr.PayloadOperationType) 0,
   DestRegister: (uint32) 1,
   SourceRegister: (uint32) 0,
   Base: (expr.PayloadBase) 2,
   Offset: (uint32) 2,
   Len: (uint32) 2,
   CsumType: (expr.PayloadCsumType) 0,
   CsumOffset: (uint32) 0,
   CsumFlags: (uint32) 0
  }),
  (*expr.Cmp)(0xc00034a700)({
   Op: (expr.CmpOp) 0,
   Register: (uint32) 1,
   Data: ([]uint8) (len=2 cap=2) {
    00000000  00 50                                             |.P|
   }
  }),
  (*expr.Counter)(0xc000307cb0)({
   Bytes: (uint64) 0,
   Packets: (uint64) 0
  }),
  (*expr.Verdict)(0xc00034a860)({
   Kind: (expr.VerdictKind) 1,
   Chain: (string) ""
  })
 },
 UserData: ([]uint8) <nil>
}),

Next:

                log prefix "ip4 forward drop: " # handle 2

is:

(*nftables.Rule)(0xc000267950)({
 Table: (*nftables.Table)(0xc00034a900)({
  Name: (string) (len=6) "filter",
  Use: (uint32) 0,
  Flags: (uint32) 0,
  Family: (nftables.TableFamily) 0
 }),
 Chain: (*nftables.Chain)(0xc000350d00)({
  Name: (string) (len=7) "forward",
  Table: (*nftables.Table)(<nil>),
  Hooknum: (nftables.ChainHook) 0,
  Priority: (nftables.ChainPriority) 0,
  Type: (nftables.ChainType) "",
  Policy: (*nftables.ChainPolicy)(<nil>)
 }),
 Position: (uint64) 4,
 Handle: (uint64) 2,
 Exprs: ([]expr.Any) <nil>,
 UserData: ([]uint8) <nil>
}),

Next:

                log prefix "ip4 forward drop: " # handle 2

is:

(*nftables.Rule)(0xc0002679a0)({
 Table: (*nftables.Table)(0xc00034a9e0)({
  Name: (string) (len=6) "filter",
  Use: (uint32) 0,
  Flags: (uint32) 0,
  Family: (nftables.TableFamily) 0
 }),
 Chain: (*nftables.Chain)(0xc000350e80)({
  Name: (string) (len=7) "forward",
  Table: (*nftables.Table)(<nil>),
  Hooknum: (nftables.ChainHook) 0,
  Priority: (nftables.ChainPriority) 0,
  Type: (nftables.ChainType) "",
  Policy: (*nftables.ChainPolicy)(<nil>)
 }),
 Position: (uint64) 2,
 Handle: (uint64) 3,
 Exprs: ([]expr.Any) (len=2 cap=2) {
  (*expr.Counter)(0xc000307dd0)({
   Bytes: (uint64) 0,
   Packets: (uint64) 0
  }),
  (*expr.Verdict)(0xc00034ab40)({
   Kind: (expr.VerdictKind) 0,
   Chain: (string) ""
  })
 },
 UserData: ([]uint8) <nil>
})
}

When adding chain, how to specify address family?

According to documentation when adding a chain, there is an optional parameter to specify address family:

nft add chain  >>> ip <<< foo output { type filter hook output priority 0 \; policy accept\; }

Chain has a link to table, but I do not see table's address family used when a chain's attributes are getting prepared for passing down to the net link library.

SetID is missing in both GetSets and GetRules

It looks like even though netlink message sent to kernel carries SetID when Set is created or a rule with Lookup expression is created. But when GetRules or GetSets returns information, SetID is always 0. It looks like the return message is missing unix.NFTA_SET_ID attribute. I have done all debugging I could in user land, now need some help from kernel land.
It is a big problem if anonymous sets are used in the same table.

bug: GetRule returns entries for all chains, not the one provided

I make GetRule() call in the following way.

	tb := &nftables.Table{
		Name: tableName,
	}
	if v == "4" {
		tb.Family = nftables.TableFamilyIPv4
	} else {
		tb.Family = nftables.TableFamilyIPv6
	}

	ch := &nftables.Chain{
		Name:  chainName,
		Table: tb,
	}

	rules, err := conn.GetRule(tb, ch)
	if err != nil {
		return nil, err
	}

The issue is I am getting back all chains, not the table and change I passed as arguments.
Here is a dump of such response, Please note the Name: (string) (len=10) "prerouting" and Name: (string) (len=11) "postrouting",.

(*utils.chainInfo)(0xc00005d6d0)({
         RuleCount: (int) 9,
         Positions: ([]uint64) (len=9 cap=16) {
          (uint64) 0,
          (uint64) 0,
          (uint64) 0,
          (uint64) 7,
          (uint64) 8,
          (uint64) 0,
          (uint64) 0,
          (uint64) 0,
          (uint64) 2
         },
         Handles: ([]uint64) (len=9 cap=16) {
          (uint64) 6,
          (uint64) 11,
          (uint64) 7,
          (uint64) 8,
          (uint64) 9,
          (uint64) 12,
          (uint64) 2,
          (uint64) 2,
          (uint64) 3
         },
         Rules: ([]*nftables.Rule) (len=9 cap=16) {
          (*nftables.Rule)(0xc00005d770)({
           Table: (*nftables.Table)(0xc0001dc8e0)({
            Name: (string) (len=3) "nat",
            Use: (uint32) 0,
            Flags: (uint32) 0,
            Family: (nftables.TableFamily) 0
           }),
           Chain: (*nftables.Chain)(0xc0001cad80)({
            Name: (string) (len=11) "postrouting",
            Table: (*nftables.Table)(<nil>),
            Hooknum: (nftables.ChainHook) 0,
            Priority: (nftables.ChainPriority) 0,
            Type: (nftables.ChainType) "",
            Policy: (*nftables.ChainPolicy)(<nil>)
           }),
           Position: (uint64) 0,
           Handle: (uint64) 6,
           Exprs: ([]expr.Any) (len=1 cap=1) {
            (*expr.Verdict)(0xc0001dc9e0)({
             Kind: (expr.VerdictKind) -3,
             Chain: (string) (len=31) "cni-npo-163852eb9be80b2d5547968"
            })
           },
           UserData: ([]uint8) <nil>
          }),
          (*nftables.Rule)(0xc00005d7c0)({
           Table: (*nftables.Table)(0xc0001dcaa0)({
            Name: (string) (len=3) "nat",
            Use: (uint32) 0,
            Flags: (uint32) 0,
            Family: (nftables.TableFamily) 0
           }),
           Chain: (*nftables.Chain)(0xc0001cb080)({
            Name: (string) (len=10) "prerouting",
            Table: (*nftables.Table)(<nil>),
            Hooknum: (nftables.ChainHook) 0,
            Priority: (nftables.ChainPriority) 0,
            Type: (nftables.ChainType) "",
            Policy: (*nftables.ChainPolicy)(<nil>)
           }),
           Position: (uint64) 0,
           Handle: (uint64) 11,
           Exprs: ([]expr.Any) (len=1 cap=1) {
            (*expr.Verdict)(0xc0001dcba0)({
             Kind: (expr.VerdictKind) -3,
             Chain: (string) (len=31) "cni-npr-163852eb9be80b2d5547968"
            })
           },
           UserData: ([]uint8) <nil>
          }),
          (*nftables.Rule)(0xc00005d810)({
           Table: (*nftables.Table)(0xc0001dcc60)({
            Name: (string) (len=3) "nat",
            Use: (uint32) 0,
            Flags: (uint32) 0,
            Family: (nftables.TableFamily) 0
           }),
           Chain: (*nftables.Chain)(0xc0001cb380)({
            Name: (string) (len=31) "cni-npo-163852eb9be80b2d5547968",
            Table: (*nftables.Table)(<nil>),
            Hooknum: (nftables.ChainHook) 0,
            Priority: (nftables.ChainPriority) 0,
            Type: (nftables.ChainType) "",
            Policy: (*nftables.ChainPolicy)(<nil>)
           }),
           Position: (uint64) 0,
           Handle: (uint64) 7,
           Exprs: ([]expr.Any) (len=7 cap=8) {
            (*expr.Payload)(0xc00001fda0)({
             OperationType: (expr.PayloadOperationType) 0,
             DestRegister: (uint32) 1,
             SourceRegister: (uint32) 0,
             Base: (expr.PayloadBase) 1,
             Offset: (uint32) 12,
             Len: (uint32) 4,
             CsumType: (expr.PayloadCsumType) 0,
             CsumOffset: (uint32) 0,
             CsumFlags: (uint32) 0
            }),
            (*expr.Cmp)(0xc0001dcd60)({
             Op: (expr.CmpOp) 0,
             Register: (uint32) 1,
             Data: ([]uint8) (len=4 cap=4) {
              00000000  0a 58 00 07                                       |.X..|
             }
            }),
            (*expr.Payload)(0xc00001fdd0)({
             OperationType: (expr.PayloadOperationType) 0,
             DestRegister: (uint32) 1,
             SourceRegister: (uint32) 0,
             Base: (expr.PayloadBase) 1,
             Offset: (uint32) 16,
             Len: (uint32) 4,
             CsumType: (expr.PayloadCsumType) 0,
             CsumOffset: (uint32) 0,
             CsumFlags: (uint32) 0
            }),
            (*expr.Bitwise)(0xc0001cb840)({
             SourceRegister: (uint32) 1,
             DestRegister: (uint32) 1,
             Len: (uint32) 4,
             Mask: ([]uint8) (len=4 cap=4) {
              00000000  ff ff ff 00                                       |....|
             },
             Xor: ([]uint8) (len=4 cap=4) {
              00000000  00 00 00 00                                       |....|
             }
            }),
            (*expr.Cmp)(0xc0001dcf20)({
             Op: (expr.CmpOp) 0,
             Register: (uint32) 1,
             Data: ([]uint8) (len=4 cap=4) {
              00000000  e0 00 00 00                                       |....|
             }
            }),
            (*expr.Counter)(0xc0001c30b0)({
             Bytes: (uint64) 0,
             Packets: (uint64) 0
            }),
            (*expr.Verdict)(0xc0001dd080)({
             Kind: (expr.VerdictKind) -5,
             Chain: (string) ""
            })
           },
           UserData: ([]uint8) <nil>
          }),
          (*nftables.Rule)(0xc00005d860)({
           Table: (*nftables.Table)(0xc0001dd140)({
            Name: (string) (len=3) "nat",
            Use: (uint32) 0,
            Flags: (uint32) 0,
            Family: (nftables.TableFamily) 0
           }),
           Chain: (*nftables.Chain)(0xc0001cbe40)({
            Name: (string) (len=31) "cni-npo-163852eb9be80b2d5547968",
            Table: (*nftables.Table)(<nil>),
            Hooknum: (nftables.ChainHook) 0,
            Priority: (nftables.ChainPriority) 0,
            Type: (nftables.ChainType) "",
            Policy: (*nftables.ChainPolicy)(<nil>)
           }),
           Position: (uint64) 7,
           Handle: (uint64) 8,
           Exprs: ([]expr.Any) (len=6 cap=8) {
            (*expr.Payload)(0xc00001ff50)({
             OperationType: (expr.PayloadOperationType) 0,
             DestRegister: (uint32) 1,
             SourceRegister: (uint32) 0,
             Base: (expr.PayloadBase) 1,
             Offset: (uint32) 12,
             Len: (uint32) 4,
             CsumType: (expr.PayloadCsumType) 0,
             CsumOffset: (uint32) 0,
             CsumFlags: (uint32) 0
            }),
            (*expr.Cmp)(0xc0001dd240)({
             Op: (expr.CmpOp) 0,
             Register: (uint32) 1,
             Data: ([]uint8) (len=4 cap=4) {
              00000000  0a 58 00 07                                       |.X..|
             }
            }),
            (*expr.Payload)(0xc00001ff80)({
             OperationType: (expr.PayloadOperationType) 0,
             DestRegister: (uint32) 1,
             SourceRegister: (uint32) 0,
             Base: (expr.PayloadBase) 1,
             Offset: (uint32) 16,
             Len: (uint32) 4,
             CsumType: (expr.PayloadCsumType) 0,
             CsumOffset: (uint32) 0,
             CsumFlags: (uint32) 0
            }),
            (*expr.Cmp)(0xc0001dd360)({
             Op: (expr.CmpOp) 0,
             Register: (uint32) 1,
             Data: ([]uint8) (len=4 cap=4) {
              00000000  ff ff ff ff                                       |....|
             }
            }),
            (*expr.Counter)(0xc0001c3210)({
             Bytes: (uint64) 0,
             Packets: (uint64) 0
            }),
            (*expr.Verdict)(0xc0001dd4c0)({
             Kind: (expr.VerdictKind) -5,
             Chain: (string) ""
            })
           },
           UserData: ([]uint8) <nil>
          }),
          (*nftables.Rule)(0xc00005d8b0)({
           Table: (*nftables.Table)(0xc0001dd560)({
            Name: (string) (len=3) "nat",
            Use: (uint32) 0,
            Flags: (uint32) 0,
            Family: (nftables.TableFamily) 0
           }),
           Chain: (*nftables.Chain)(0xc0001e4740)({
            Name: (string) (len=31) "cni-npo-163852eb9be80b2d5547968",
            Table: (*nftables.Table)(<nil>),
            Hooknum: (nftables.ChainHook) 0,
            Priority: (nftables.ChainPriority) 0,
            Type: (nftables.ChainType) "",
            Policy: (*nftables.ChainPolicy)(<nil>)
           }),
           Position: (uint64) 8,
           Handle: (uint64) 9,
           Exprs: ([]expr.Any) (len=5 cap=8) {
            (*expr.Payload)(0xc0001e6060)({
             OperationType: (expr.PayloadOperationType) 0,
             DestRegister: (uint32) 1,
             SourceRegister: (uint32) 0,
             Base: (expr.PayloadBase) 1,
             Offset: (uint32) 12,
             Len: (uint32) 4,
             CsumType: (expr.PayloadCsumType) 0,
             CsumOffset: (uint32) 0,
             CsumFlags: (uint32) 0
            }),
            (*expr.Cmp)(0xc0001dd660)({
             Op: (expr.CmpOp) 0,
             Register: (uint32) 1,
             Data: ([]uint8) (len=4 cap=4) {
              00000000  0a 58 00 07                                       |.X..|
             }
            }),
            (*expr.Meta)(0xc0001c3340)({
             Key: (expr.MetaKey) 7,
             SourceRegister: (bool) false,
             Register: (uint32) 1
            }),
            (*expr.Cmp)(0xc0001dd780)({
             Op: (expr.CmpOp) 0,
             Register: (uint32) 1,
             Data: ([]uint8) (len=16 cap=16) {
              00000000  63 6e 69 2d 70 6f 64 6d  61 6e 30 00 00 00 00 00  |cni-podman0.....|
             }
            }),
            (*expr.Counter)(0xc0001c3390)({
             Bytes: (uint64) 0,
             Packets: (uint64) 0
            })
           },
           UserData: ([]uint8) <nil>
          }),
          (*nftables.Rule)(0xc00005d900)({
           Table: (*nftables.Table)(0xc0001dd8c0)({
            Name: (string) (len=3) "nat",
            Use: (uint32) 0,
            Flags: (uint32) 0,
            Family: (nftables.TableFamily) 0
           }),
           Chain: (*nftables.Chain)(0xc0001e4f00)({
            Name: (string) (len=31) "cni-npr-163852eb9be80b2d5547968",
            Table: (*nftables.Table)(<nil>),
            Hooknum: (nftables.ChainHook) 0,
            Priority: (nftables.ChainPriority) 0,
            Type: (nftables.ChainType) "",
            Policy: (*nftables.ChainPolicy)(<nil>)
           }),
           Position: (uint64) 0,
           Handle: (uint64) 12,
           Exprs: ([]expr.Any) (len=7 cap=8) {
            (*expr.Meta)(0xc0001c33e0)({
             Key: (expr.MetaKey) 16,
             SourceRegister: (bool) false,
             Register: (uint32) 1
            }),
            (*expr.Cmp)(0xc0001dd9c0)({
             Op: (expr.CmpOp) 0,
             Register: (uint32) 1,
             Data: ([]uint8) (len=1 cap=1) {
              00000000  06                                                |.|
             }
            }),
            (*expr.Payload)(0xc0001e61b0)({
             OperationType: (expr.PayloadOperationType) 0,
             DestRegister: (uint32) 1,
             SourceRegister: (uint32) 0,
             Base: (expr.PayloadBase) 2,
             Offset: (uint32) 2,
             Len: (uint32) 2,
             CsumType: (expr.PayloadCsumType) 0,
             CsumOffset: (uint32) 0,
             CsumFlags: (uint32) 0
            }),
            (*expr.Cmp)(0xc0001ddae0)({
             Op: (expr.CmpOp) 0,
             Register: (uint32) 1,
             Data: ([]uint8) (len=2 cap=2) {
              00000000  b3 ef                                             |..|
             }
            }),
            (*expr.Immediate)(0xc0001ddb80)({
             Register: (uint32) 1,
             Data: ([]uint8) (len=4 cap=4) {
              00000000  0a 58 00 07                                       |.X..|
             }
            }),
            (*expr.Immediate)(0xc0001ddc20)({
             Register: (uint32) 2,
             Data: ([]uint8) (len=2 cap=2) {
              00000000  00 50                                             |.P|
             }
            }),
            (*expr.NAT)(0xc0001de520)({
             Type: (expr.NATType) 1,
             Family: (uint32) 2,
             RegAddrMin: (uint32) 1,
             RegAddrMax: (uint32) 1,
             RegProtoMin: (uint32) 2,
             RegProtoMax: (uint32) 2,
             Random: (bool) false,
             FullyRandom: (bool) false,
             Persistent: (bool) false
            })
           },
           UserData: ([]uint8) <nil>
          }),
          (*nftables.Rule)(0xc00005d950)({
           Table: (*nftables.Table)(0xc0001ddd20)({
            Name: (string) (len=3) "raw",
            Use: (uint32) 0,
            Flags: (uint32) 0,
            Family: (nftables.TableFamily) 0
           }),
           Chain: (*nftables.Chain)(0xc0001e5880)({
            Name: (string) (len=10) "prerouting",
            Table: (*nftables.Table)(<nil>),
            Hooknum: (nftables.ChainHook) 0,
            Priority: (nftables.ChainPriority) 0,
            Type: (nftables.ChainType) "",
            Policy: (*nftables.ChainPolicy)(<nil>)
           }),
           Position: (uint64) 0,
           Handle: (uint64) 2,
           Exprs: ([]expr.Any) (len=12 cap=16) {
            (*expr.Counter)(0xc0001c3520)({
             Bytes: (uint64) 0,
             Packets: (uint64) 0
            }),
            (*expr.Meta)(0xc0001c3560)({
             Key: (expr.MetaKey) 16,
             SourceRegister: (bool) false,
             Register: (uint32) 1
            }),
            (*expr.Cmp)(0xc0001ddea0)({
             Op: (expr.CmpOp) 0,
             Register: (uint32) 1,
             Data: ([]uint8) (len=1 cap=1) {
              00000000  06                                                |.|
             }
            }),
            (*expr.Payload)(0xc0001e63c0)({
             OperationType: (expr.PayloadOperationType) 0,
             DestRegister: (uint32) 1,
             SourceRegister: (uint32) 0,
             Base: (expr.PayloadBase) 2,
             Offset: (uint32) 2,
             Len: (uint32) 2,
             CsumType: (expr.PayloadCsumType) 0,
             CsumOffset: (uint32) 0,
             CsumFlags: (uint32) 0
            }),
            (*expr.Cmp)(0xc0001ddfa0)({
             Op: (expr.CmpOp) 0,
             Register: (uint32) 1,
             Data: ([]uint8) (len=2 cap=2) {
              00000000  b3 ef                                             |..|
             }
            }),
            (*expr.Immediate)(0xc0001ec040)({
             Register: (uint32) 1,
             Data: ([]uint8) (len=4 cap=4) {
              00000000  0a 58 00 07                                       |.X..|
             }
            }),
            (*expr.Immediate)(0xc0001ec0e0)({
             Register: (uint32) 2,
             Data: ([]uint8) (len=2 cap=2) {
              00000000  00 50                                             |.P|
             }
            }),
            (*expr.Immediate)(0xc0001ec180)({
             Register: (uint32) 1,
             Data: ([]uint8) (len=4 cap=4) {
              00000000  0a 58 00 07                                       |.X..|
             }
            }),
            (*expr.Payload)(0xc0001e6420)({
             OperationType: (expr.PayloadOperationType) 0,
             DestRegister: (uint32) 0,
             SourceRegister: (uint32) 1,
             Base: (expr.PayloadBase) 1,
             Offset: (uint32) 16,
             Len: (uint32) 4,
             CsumType: (expr.PayloadCsumType) 1,
             CsumOffset: (uint32) 10,
             CsumFlags: (uint32) 0
            }),
            (*expr.Immediate)(0xc0001ec280)({
             Register: (uint32) 1,
             Data: ([]uint8) (len=2 cap=2) {
              00000000  00 50                                             |.P|
             }
            }),
            (*expr.Payload)(0xc0001e6480)({
             OperationType: (expr.PayloadOperationType) 0,
             DestRegister: (uint32) 0,
             SourceRegister: (uint32) 1,
             Base: (expr.PayloadBase) 2,
             Offset: (uint32) 2,
             Len: (uint32) 2,
             CsumType: (expr.PayloadCsumType) 1,
             CsumOffset: (uint32) 16,
             CsumFlags: (uint32) 0
            }),
            (*expr.Verdict)(0xc0001ec3e0)({
             Kind: (expr.VerdictKind) -5,
             Chain: (string) ""
            })
           },
           UserData: ([]uint8) <nil>
          }),
          (*nftables.Rule)(0xc00005d9a0)({
           Table: (*nftables.Table)(0xc0001ec480)({
            Name: (string) (len=6) "filter",
            Use: (uint32) 0,
            Flags: (uint32) 0,
            Family: (nftables.TableFamily) 0
           }),
           Chain: (*nftables.Chain)(0xc0001ee880)({
            Name: (string) (len=7) "forward",
            Table: (*nftables.Table)(<nil>),
            Hooknum: (nftables.ChainHook) 0,
            Priority: (nftables.ChainPriority) 0,
            Type: (nftables.ChainType) "",
            Policy: (*nftables.ChainPolicy)(<nil>)
           }),
           Position: (uint64) 0,
           Handle: (uint64) 2,
           Exprs: ([]expr.Any) <nil>,
           UserData: ([]uint8) <nil>
          }),
          (*nftables.Rule)(0xc00005d9f0)({
           Table: (*nftables.Table)(0xc0001ec560)({
            Name: (string) (len=6) "filter",
            Use: (uint32) 0,
            Flags: (uint32) 0,
            Family: (nftables.TableFamily) 0
           }),
           Chain: (*nftables.Chain)(0xc0001eea00)({
            Name: (string) (len=7) "forward",
            Table: (*nftables.Table)(<nil>),
            Hooknum: (nftables.ChainHook) 0,
            Priority: (nftables.ChainPriority) 0,
            Type: (nftables.ChainType) "",
            Policy: (*nftables.ChainPolicy)(<nil>)
           }),
           Position: (uint64) 2,
           Handle: (uint64) 3,
           Exprs: ([]expr.Any) (len=2 cap=2) {
            (*expr.Counter)(0xc0001c3800)({
             Bytes: (uint64) 0,
             Packets: (uint64) 0
            }),
            (*expr.Verdict)(0xc0001ec6c0)({
             Kind: (expr.VerdictKind) 0,
             Chain: (string) ""
            })
           },
           UserData: ([]uint8) <nil>
          })
         }
        })

feature: add support for comments

When adding rules there could be an option to set the UserData field to a comment with a helper function maybe
Or maybe add a field, this however is not as good of an option IMO because when flushing the rule the library will need to override the UserData field.
I do not know if it is used for anything else currently, i have only looked it the nft and kernel impl of comments to see if I could do it alone 😄

Implement value setting for ct

Currently we are only able to read information from the conntrack module of nftables through this package.

The goal would be to be able to write into it, for example to set the conntrack mark or other keys.

The base code is already working in the Meta module, it just has to be taken and adapted for ct with the proper DREG/SREG related to NFTA_CT.

I'll make a PR as soon as the code is ready, it shouldn't take too long as it's only about adding a field in the struct/modifying (un)marshal functions.

`NFTA_RULE_HANDLE` vs. `NFTA_RULE_POSITION` attribute type for "rule add" and "rule insert" operations

this issue involves some reverse engineering done on my system:

cco@firiel:~$ uname -a
Linux firiel 5.8.0-63-generic #71-Ubuntu SMP Tue Jul 13 15:59:12 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
cco@firiel:~$ lsb_release -a
LSB Version:	core-11.1.0ubuntu2-noarch:printing-11.1.0ubuntu2-noarch:security-11.1.0ubuntu2-noarch
Distributor ID:	Ubuntu
Description:	Ubuntu 20.10
Release:	20.10
Codename:	groovy

I ran into some problems when trying use the nftables.Conn.AddRule() and nftables.Conn.InsertRule() for adding or inserting new rules in a specified position.

I have checked with strace which are the systemcalls to which the nft tool translates "rule add/insert with handle" operations; like this for example:

strace -o /tmp/nft_dump nft insert rule ip filter MONITORING handle 382 ip saddr @whitelist counter

I have seen in the output that only the attribute NFTA_RULE_POSITION (0x6) is used in netlink messages for these operations; it should be filled in with the actual handle where the rule should be added or inserted. here is the relevant sendmsg() sycall from the strace output:

sendmsg(3, {msg_name={sa_family=AF_NETLINK, nl_pid=0, nl_groups=00000000}, msg_namelen=12, 
    msg_iov=[
        {iov_base=
            [
            {
                {len=20, type=NFNL_MSG_BATCH_BEGIN, flags=NLM_F_REQUEST, seq=0, pid=0}, 
                {nfgen_family=AF_UNSPEC, version=NFNETLINK_V0, res_id=htons(2560)}, 
                {
                    {len=188, type=NFNL_SUBSYS_NFTABLES<<8|NFT_MSG_NEWRULE, flags=NLM_F_REQUEST|NLM_F_CREATE, seq=1, pid=0}, 
                    {
                        nfgen_family=AF_INET, version=NFNETLINK_V0, res_id=htons(0), 
                        [
                            {{nla_len=11, nla_type=NFNETLINK_V1}, "\x66\x69\x6c\x74\x65\x72\x00"}, 
                            {{nla_len=15, nla_type=0x2}, "\x4d\x4f\x4e\x49\x54\x4f\x52\x49\x4e\x47\x00"}, 
                            {{nla_len=12, nla_type=0x6}, "\x00\x00\x00\x00\x00\x00\x01\x84"},
                            {{nla_len=128, nla_type=NLA_F_NESTED|0x4}, "\x34\x00\x01\x80\x0c\x00\x01\x00\x70\x61\x79\x6c\x6f\x61\x64\x00\x24\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00"...}
                        ]
                    }, 
                    {
                        {len=20, type=NFNL_MSG_BATCH_END, flags=NLM_F_REQUEST, seq=2, pid=0}, 
                        {nfgen_family=AF_UNSPEC, version=NFNETLINK_V0, res_id=htons(2560)}
                    }
            ], iov_len=228}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, 0) = 228

as far as I can tell from my reverse engineering sessions, NFTA_RULE_HANDLE is NOT used when adding or inserting rules with specified handle through the nft command line.

now, one can still use the APIs nftables.Conn.AddRule, nftables.Conn.InsertRule by just setting the Rule.Handle to 0 and the actual handle in Rule.Position

maybe:

  1. this behaviour should be documented somehow in nftables;
  2. encoding of Rule.Handle should be removed from the insert/add APIs

error: e.Timeout.Milliseconds undefined

When linking this library in github.com/containernetworking/plugin via vendors, I get the following error:

Building plugins 
  bandwidth
  firewall
# github.com/google/nftables/expr
vendor/github.com/google/nftables/expr/dynset.go:46:132: e.Timeout.Milliseconds undefined (type time.Duration has no field or method Milliseconds)

See also: containernetworking/plugins#518 (comment)

What is the best way of resolving this error?

L4 ipv6 rule fails when the same ipv4 rule works

Here is the code almost identical, it creates L4 rule, the only difference is Table's Family in working case it is IPv4 and not working case it is IPv6.

Working:

package main

import (
	"fmt"
	"math/rand"
	"os"

	"golang.org/x/sys/unix"

	"github.com/google/nftables"
	"github.com/google/nftables/binaryutil"
	"github.com/google/nftables/expr"
)

func main() {
	fmt.Printf("Testing raw rule for port set\n")
	c := nftables.Conn{}

	t := &nftables.Table{
		Name:   "ipv6table",
		Family: nftables.TableFamilyIPv4,
	}
	ch := &nftables.Chain{
		Name:     "ipv6chain-2",
		Table:    t,
		Type:     nftables.ChainTypeNAT,
		Priority: nftables.ChainPriorityNATDest,
		Hooknum:  nftables.ChainHookPrerouting,
	}
	set := nftables.Set{
		Anonymous: false,
		Constant:  true,
		Name:      "test-set",
		ID:        uint32(rand.Intn(0xffff)),
		Table:     t,
		KeyType:   nftables.TypeInetService,
	}
	c.AddTable(t)
	c.AddChain(ch)
	if err := c.Flush(); err != nil {
		fmt.Printf("failed to program with error: %+v\n", err)
		os.Exit(1)
	}

	re := []expr.Any{}
	re = append(re, &expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1})
	re = append(re, &expr.Cmp{
		Op:       expr.CmpOpEq,
		Register: 1,
		Data:     []byte{unix.IPPROTO_TCP},
	})
	re = append(re, &expr.Payload{
		DestRegister: 1,
		Base:         expr.PayloadBaseTransportHeader,
		Offset:       2, // Offset for a transport protocol header
		Len:          2, // 2 bytes for port
	})
	re = append(re, &expr.Lookup{
		SourceRegister: 1,
		Invert:         false,
		SetID:          set.ID,
		SetName:        set.Name,
	})

	ports := []uint16{22}
	setElements := make([]nftables.SetElement, len(ports))
	for i := 0; i < len(ports); i++ {
		setElements[i].Key = binaryutil.BigEndian.PutUint16(ports[i])
	}

	if err := c.AddSet(&set, setElements); err != nil {
		fmt.Printf("failed to add set  with error: %+v\n", err)
		os.Exit(1)
	}

	c.AddRule(&nftables.Rule{
		Table: t,
		Chain: ch,
		Exprs: re,
	})

	if err := c.Flush(); err != nil {
		fmt.Printf("failed to program with error: %+v\n", err)
		os.Exit(1)
	}
}

Not working case, please note the only change is in Table's Family

package main

import (
	"fmt"
	"math/rand"
	"os"

	"golang.org/x/sys/unix"

	"github.com/google/nftables"
	"github.com/google/nftables/binaryutil"
	"github.com/google/nftables/expr"
)

func main() {
	fmt.Printf("Testing raw rule for port set\n")
	c := nftables.Conn{}

	t := &nftables.Table{
		Name:   "ipv6table",
		Family: nftables.TableFamilyIPv6,
	}
	ch := &nftables.Chain{
		Name:     "ipv6chain-2",
		Table:    t,
		Type:     nftables.ChainTypeNAT,
		Priority: nftables.ChainPriorityNATDest,
		Hooknum:  nftables.ChainHookPrerouting,
	}
	set := nftables.Set{
		Anonymous: false,
		Constant:  true,
		Name:      "test-set",
		ID:        uint32(rand.Intn(0xffff)),
		Table:     t,
		KeyType:   nftables.TypeInetService,
	}
	c.AddTable(t)
	c.AddChain(ch)
	if err := c.Flush(); err != nil {
		fmt.Printf("failed to program with error: %+v\n", err)
		os.Exit(1)
	}

	re := []expr.Any{}
	re = append(re, &expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1})
	re = append(re, &expr.Cmp{
		Op:       expr.CmpOpEq,
		Register: 1,
		Data:     []byte{unix.IPPROTO_TCP},
	})
	re = append(re, &expr.Payload{
		DestRegister: 1,
		Base:         expr.PayloadBaseTransportHeader,
		Offset:       2, // Offset for a transport protocol header
		Len:          2, // 2 bytes for port
	})
	re = append(re, &expr.Lookup{
		SourceRegister: 1,
		Invert:         false,
		SetID:          set.ID,
		SetName:        set.Name,
	})

	ports := []uint16{22}
	setElements := make([]nftables.SetElement, len(ports))
	for i := 0; i < len(ports); i++ {
		setElements[i].Key = binaryutil.BigEndian.PutUint16(ports[i])
	}

	if err := c.AddSet(&set, setElements); err != nil {
		fmt.Printf("failed to add set  with error: %+v\n", err)
		os.Exit(1)
	}

	c.AddRule(&nftables.Rule{
		Table: t,
		Chain: ch,
		Exprs: re,
	})

	if err := c.Flush(); err != nil {
		fmt.Printf("failed to program with error: %+v\n", err)
		os.Exit(1)
	}
}

Working case produces expected table:

table ip ipv6table {
	set test-set {
		type inet_service
		flags constant
		elements = { 22 }
	}

	chain ipv6chain-2 {
		type nat hook prerouting priority dstnat; policy accept;
		tcp dport @test-set
	}
}

Not working case:

failed to program with error: Receive: netlink receive: no such file or directory

I suspect that either chain or rule or set does not inherit from table its Family type.

Userdata in sets always set key to big endian

There is static userdata inside the set marshal:

	if s.Anonymous || s.Constant || s.Interval {
		tableInfo = append(tableInfo,
			// Semantically useless - kept for binary compatability with nft
			netlink.Attribute{Type: unix.NFTA_SET_USERDATA, Data: []byte("\x00\x04\x02\x00\x00\x00")})
	}

https://github.com/google/nftables/blob/master/set.go#L386

According to nftables code:

static int set_parse_udata_cb(const struct nftnl_udata *attr, void *data)
{
	unsigned char *value = nftnl_udata_get(attr);
	const struct nftnl_udata **tb = data;
	uint8_t type = nftnl_udata_type(attr);
	uint8_t len = nftnl_udata_len(attr);

	switch (type) {
	case NFTNL_UDATA_SET_KEYBYTEORDER:
	case NFTNL_UDATA_SET_DATABYTEORDER:
	case NFTNL_UDATA_SET_MERGE_ELEMENTS:
	case NFTNL_UDATA_SET_DATA_INTERVAL:
		if (len != sizeof(uint32_t))
			return -1;
		break;
	case NFTNL_UDATA_SET_KEY_TYPEOF:
	case NFTNL_UDATA_SET_DATA_TYPEOF:
		if (len < 3)
			return -1;
		break;
	case NFTNL_UDATA_SET_COMMENT:
		if (value[len - 1] != '\0')
			return -1;
		break;
	default:
		return 0;
	}
	tb[type] = attr;
	return 0;
}

https://git.netfilter.org/nftables/tree/src/netlink.c#n715

When i look up the enum in the nft lib i found that keybyteorder and databyteorder are values 0 and 1:

enum nftnl_udata_set_types {
	NFTNL_UDATA_SET_KEYBYTEORDER,
	NFTNL_UDATA_SET_DATABYTEORDER,
	NFTNL_UDATA_SET_MERGE_ELEMENTS,
	NFTNL_UDATA_SET_KEY_TYPEOF,
	NFTNL_UDATA_SET_DATA_TYPEOF,
	NFTNL_UDATA_SET_EXPR,
	NFTNL_UDATA_SET_DATA_INTERVAL,
	NFTNL_UDATA_SET_COMMENT,
	__NFTNL_UDATA_SET_MAX
};

https://git.netfilter.org/libnftnl/tree/include/libnftnl/udata.h#n40

I also found this byteoder enum: http://charette.no-ip.com:81/programming/doxygen/netfilter/datatype_8h.html#aec0612005abb2f2a88c2de638a442cf2

This would set the key data type to be big endian encoded. I can also observe that when i use this example code:

	workerSetElements := make([]nftables.SetElement, len(setElements))
	for i, v := range setElements {
		bs := make([]byte, 4)
		binary.LittleEndian.PutUint32(bs, uint32(i))
		workerSetElements[i] = nftables.SetElement{
			Key:         bs,
			Val:         ip2array(v),
			IntervalEnd: false,
		}
	}

This generates a netlink message like this:

May 17 06:44:01 ip-10-0-1-19 nftables-controller[31154]: nl: send msgs: {Header:{Length:116 Type:unknown(2572) Flags:request|acknowledge|0x400 Sequence:3804372827 PID:31154} Data:[2 0 0 0 12 0 2 0 95 95 109 97 112 37 100 0 8 0 4 0 0 0 0 1 14 0 1 0 115 116 97 116 101 108 101 115 115 0 0 0 60 0 3 128 28 0 1 128 12 0 1 128 8 0 1 0 0 0 0 0 12 0 2 128 8 0 1 0 10 0 1 225 28 0 2 128 12 0 1 128 8 0 1 0 1 0 0 0 12 0 2 128 8 0 1 0 10 0 1 82]}

When i look into how the second key is defined we can see that its correctly little endian: 8 0 1 0 1 0 0 0

Here is the userdata from nftables when i create the same set with it:

|00016|--|00013|        |len |flags| type|
| 00 04 01 00  |        |      data      |
| 00 00 01 04  |        |      data      |
| 02 00 00 00  |        |      data      |

I can observe that it sets key 0 (endianness for key) to 1 (LE) and for key 1 (data) to 2 (BE).

It would be awesome to control userdata in that case so that nft does show the correct output and not display LE uint32 as BE which confuses operations :D

Thanks

Support for tproxy

With new 0.9.1 version of nftables, support for tproxy is fixed.

I will look into adding this support to nftables.

  [ meta load l4proto => reg 1 ]
  [ cmp eq reg 1 0x00000006 ]
  [ payload load 2b @ transport header + 2 => reg 1 ]
  [ cmp eq reg 1 0x00005000 ]
  [ immediate reg 1 0x0000a0c3 ]
  [ tproxy ip port reg 1 ]

No support for regular chain

There are two types of chains in nft base, the one that have hooks and regular chain which do not have any of base chain attributes. Regular chains are used as a targets for jump. Currently nftables seems to support only base chains.
I will work on PR to add regular chains support.

Rule returned from AddRule does not contain Handle, therefore impossible to delete

I hope the subject line is already sufficiently descriptive. Achieving this would probably require to flush the connection's messages and parse the appropriate reply on every call to AddRule, so this may not be easy to integrate with the current design. I have little experience with Netlink, so apologies if I'm on the wrong track.

A workaround is of course to get all rules, somehow find the one you just added and delete it, since it should contain the Handle.

Not building on FreeBSD 12.2

I'm trying to build a project that depends on this nftables library and the build is failing, though I don't think it's really a problem with this library I just don't know where to take it :(

[root@wireguard ~/src/nftables]# go build
# github.com/google/nftables/expr
expr/byteorder.go:28:30: undefined: unix.NFT_BYTEORDER_NTOH
expr/byteorder.go:29:30: undefined: unix.NFT_BYTEORDER_HTON
expr/ct.go:31:26: undefined: unix.NFT_CT_STATE
expr/ct.go:32:26: undefined: unix.NFT_CT_DIRECTION
expr/ct.go:33:26: undefined: unix.NFT_CT_STATUS
expr/ct.go:34:26: undefined: unix.NFT_CT_MARK
expr/ct.go:35:26: undefined: unix.NFT_CT_SECMARK
expr/ct.go:36:26: undefined: unix.NFT_CT_EXPIRATION
expr/ct.go:37:26: undefined: unix.NFT_CT_HELPER
expr/ct.go:38:26: undefined: unix.NFT_CT_L3PROTOCOL
expr/ct.go:38:26: too many errors

Details:

# freebsd-version
12.2-RELEASE-p2

# freebsd-version
12.2-RELEASE-p2

jump to a chain

nft rule to jump to a different chain requires immediate expression to provide two parameters, 1 is jump command and 2 is the name of chain. Currently it does not seem to be supported.

sbezverk@ubuntu:~$ sudo nft --debug all add rule istio istio_outbound tcp dport 1-65535 jump istio_redirect

Log expression Flags key not implented

Trying to add the following rule:

log prefix "ipv6 input drop: " flags all

The nft debug says:

  ip filter forward 
    [ log tcpseq tcpopt ipopt uid macdecode ]

The following snippet adds log prefix "ipv6 input drop: "

    r := &nftables.Rule{
        Table: tb,
        Chain: ch,
        Exprs: []expr.Any{},
    }

    r.Exprs = append(r.Exprs, &expr.Log{
        Key:  unix.NFTA_LOG_PREFIX,
        Data: []byte(prefix),
    })

The various keys are here:

nftables/expr/log.go

Lines 30 to 65 in c25e4f6

func (e *Log) marshal() ([]byte, error) {
var data []byte
var err error
switch e.Key {
case unix.NFTA_LOG_GROUP:
data, err = netlink.MarshalAttributes([]netlink.Attribute{
{Type: unix.NFTA_LOG_GROUP, Data: e.Data},
})
case unix.NFTA_LOG_PREFIX:
prefix := append(e.Data, '\x00')
data, err = netlink.MarshalAttributes([]netlink.Attribute{
{Type: unix.NFTA_LOG_PREFIX, Data: prefix},
})
case unix.NFTA_LOG_SNAPLEN:
data, err = netlink.MarshalAttributes([]netlink.Attribute{
{Type: unix.NFTA_LOG_SNAPLEN, Data: e.Data},
})
case unix.NFTA_LOG_QTHRESHOLD:
data, err = netlink.MarshalAttributes([]netlink.Attribute{
{Type: unix.NFTA_LOG_QTHRESHOLD, Data: e.Data},
})
case unix.NFTA_LOG_LEVEL:
level := append(e.Data, '\x00')
data, err = netlink.MarshalAttributes([]netlink.Attribute{
{Type: unix.NFTA_LOG_LEVEL, Data: level},
})
}
if err != nil {
return nil, err
}
return netlink.MarshalAttributes([]netlink.Attribute{
{Type: unix.NFTA_EXPR_NAME, Data: []byte("log\x00")},
{Type: unix.NLA_F_NESTED | unix.NFTA_EXPR_DATA, Data: data},
})
}

However, it is unclear how to use this library to get flags all.

Reading through the various key, the tcpseq tcpopt ipopt uid macdecode do not popup. My guess is this is something that needs to be added to this library.

The nftables include/linux/netfilter/nf_tables.h contains the following snippet. It does refer to NFTA_LOG_FLAGS.

enum nft_log_attributes {
    NFTA_LOG_UNSPEC,
    NFTA_LOG_GROUP,
    NFTA_LOG_PREFIX,
    NFTA_LOG_SNAPLEN,
    NFTA_LOG_QTHRESHOLD,
    NFTA_LOG_LEVEL,
    NFTA_LOG_FLAGS,
    __NFTA_LOG_MAX
};

Perhaps there is a need to pass FF with NFTA_LOG_FLAGS? .. 🤔 ...

 * @NFTA_LOG_LEVEL: log level (NLA_U32)
 * @NFTA_LOG_FLAGS: logging flags (NLA_U32)

range support

Appreciate if you could add support for range
add rule ip filter-v4 chain-1-v4 tcp sport != 2024-2030 return

[ meta load l4proto => reg 1 ]
[ cmp eq reg 1 0x00000006 ]
[ payload load 2b @ transport header + 0 => reg 1 ]
[ range neq reg 1 0x0000e807 0x0000ee07 ]
[ immediate reg 0 return ]

How to create SNPT

I used nftables to create a rule based on nat66:

table ip6 nat {
        chain PREROUTING {
                type nat hook prerouting priority dstnat; policy accept;
        }

        chain INPUT {
                type nat hook input priority 100; policy accept;
        }

        chain POSTROUTING {
                type nat hook postrouting priority srcnat; policy accept;
                counter packets 0 bytes 0 ip6 saddr fc00:2::/64 snat to 2001:5::/64 comment "NPT-NAT-1"
        }

        chain OUTPUT {
                type nat hook output priority -100; policy accept;
        }

        chain VYOS_DNPT_HOOK {
        }

        chain VYOS_SNPT_HOOK {
        }
}

The NAT66 rule seems to work normally, but it will change the interface identifier of the translated prefix, what should I do so that it does not change the interface identifier

Adding a rule on position (index) 0

I have not checked yet the netlink/netfilter code in the linux kernel for adding a new nf rule.
however, based on the nft(8), "RULES" section, a 0 value for index is valid:

The add and insert commands support an optional location specifier, 
which is either a handle or the index (starting at zero) of an existing rule.

as far as I understand from the code, Rule.Position is basically the index documented in the nft(8) man page.
currently, the code in rule.go treats the 0 Rule.Position as "don't care" index:

    if r.Position != 0 {
        msgData = append(msgData, cc.marshalAttr([]netlink.Attribute{
            {Type: unix.NFTA_RULE_POSITION, Data: binaryutil.BigEndian.PutUint64(r.Position)},
        })...)
    }

is there any reason for this?
imo, if 0 is indeed a valid index, there should be a flag added to the Rule data structures which signals whether Rule.Position should be used or not.

Receive: netlink receive: recvmsg: no buffer space available

Hello, I've been using this library to repeatedly 'replace' a table with ~50 rules, and one thing I've noticed is that certain patterns seem to trigger this cryptic message:

Receive: netlink receive: recvmsg: no buffer space available

The cryptic part is that it doesn't seem related to the overall size of the table, but rather the presence of certain rules (jumps, etc).

Even so, the flush seems to take effect anyway; the changes are reflected in the kernel after receiving the message.

I found some info on ENOBUFS here: https://netfilter.vger.kernel.narkive.com/cwzlgk8d/why-no-buffer-space-available and noticed that the golang netlink library has an option exposed to help the error: https://github.com/mdlayher/netlink/blob/c5f8ab79aa345dcfcf7f14d746659ca1b80a0ecc/conn.go#L437

I patched my local checkout of google/nftables/conn.go to include this line:

        conn.SetOption(netlink.NoENOBUFS, true)

after:

        defer conn.Close()

and it seems to fix the error.. I don't know enough at this point to understand the implications of using that option, or why it might not be desired, so I decided to start with a ticket instead of a PR.

Has anyone else seen this message?

List chains?

Is there a way to list all existing chains for a table with this library?

Support redirect

Hello,

I was trying to find if
nft add rule nat prerouting tcp dport 22 redirect to 2222

can be implemented, but I could not find expr for it. Could you please confirm whether it is implemented or not.

If it is not implemented, I do have some cycles to work on it, some guidance would be extremely useful.

Network byte order policy returned by `Conn.ListChains` is not converted to host byte order

chainFromMsg() decodes the netlink message like this:

...
case unix.NFTA_CHAIN_POLICY:
  policy := ChainPolicy(ad.Uint32())
  c.Policy = &policy
...  

for example, for a chain configured on the system like this:

chain INPUT { # handle 2
   type nat hook input priority 100; policy accept;
}

Conn.ListChains() reports the policy value as 16777216, or in hexadecimal 1000000, whereas it should be 1:

// Possible ChainPolicy values.
const (
	ChainPolicyDrop ChainPolicy = iota
	ChainPolicyAccept
)

Support for nftables maps

I can see that the golang nftables has support for sets, are their any plans to have support for maps in the future?

 nft --debug=netlink add rule nat prerouting ip daddr 192.168.1.5 tcp dport 6443 dnat to numgen inc mod 2 map { 0 : 192.168.1.110, 1 : 192.168.1.101 }
__map%d nat b size 2
__map%d nat 0
	element 00000000  : 6e01a8c0 0 [end]	element 00000001  : 6501a8c0 0 [end]
ip nat prerouting 
  [ payload load 4b @ network header + 16 => reg 1 ]
  [ cmp eq reg 1 0x0501a8c0 ]
  [ meta load l4proto => reg 1 ]
  [ cmp eq reg 1 0x00000006 ]
  [ payload load 2b @ transport header + 2 => reg 1 ]
  [ cmp eq reg 1 0x00002b19 ]
  [ numgen reg 1 = inc mod 2 ]
  [ lookup reg 1 set __map%d dreg 1 ]
  [ nat dnat ip addr_min reg 1 addr_max reg 0 ]

(Thanks for the great work :) )

bug: GetRule missing parse expr.Ct and expr.Range in rule

1- create a new rule match ct stat and ip range

func TestRule(t *testing.T) {
	conn, err := getConn()
	if err != nil {
		t.Fatal(err)
	}
	defer closeNs(netns.NsHandle(conn.NetNS))

	ruleIn := &Rule{
		Table: mytable,
		Chain: cin,
		Exprs: []expr.Any{
			&expr.Ct{Register: 1, SourceRegister: false, Key: expr.CtKeySTATE},
			&expr.Bitwise{
				SourceRegister: 1,
				DestRegister:   1,
				Len:            4,
				Mask:           []byte{6, 0, 0, 0},
				Xor:            []byte{0, 0, 0, 0},
			},
			&expr.Cmp{Op: expr.CmpOpNeq, Register: 1, Data: []byte{0, 0, 0, 0}},
			&expr.Payload{
				Base:         expr.PayloadBaseNetworkHeader,
				DestRegister: 1,
				Offset:       16,
				Len:          4,
			},
			&expr.Range{
				Op:       expr.CmpOpNeq,
				Register: 1,
				FromData: []byte{192, 168, 1, 1},
				ToData:   []byte{192, 168, 1, 100},
			},
			&expr.Verdict{Kind: expr.VerdictAccept},
		},
	}

	conn.AddRule(ruleIn)

	err = conn.Flush()
	if err != nil {
		t.Fatal(err)
	}
}

2- use nft tool check the rule

$ nft list ruleset
table ip mytable {
        chain c_in {
                type filter hook input priority filter; policy accept;
                ct state established,related ip daddr != 192.168.1.1-192.168.1.100 accept
        }

        chain c_out {
                type filter hook output priority filter; policy accept;
        }
}

3- use GetRule and print rule.Expr type and content

func TestListRuleSet(t *testing.T) {
	conn, err := getConn()
	if err != nil {
		t.Fatal(err)
	}
	defer closeNs(netns.NsHandle(conn.NetNS))

	rules, err := conn.GetRule(mytable, cin)
	if err != nil {
		t.Fatal(err)
	}

	for _, rule := range rules {
		for _, exp := range rule.Exprs {
			t.Log("\n", reflect.TypeOf(exp))
			t.Logf("%+v", exp)
		}
	}

	rules, err = conn.GetRule(mytable, cout)
	if err != nil {
		t.Fatal(err)
	}

	for _, rule := range rules {
		for _, exp := range rule.Exprs {
			t.Log("\n", reflect.TypeOf(exp))
			t.Logf("%+v", exp)
		}
	}
}
/*output
*expr.Bitwise
&{SourceRegister:1 DestRegister:1 Len:4 Mask:[6 0 0 0] Xor:[0 0 0 0]}

*expr.Cmp
&{Op:1 Register:1 Data:[0 0 0 0]}

*expr.Payload
&{OperationType:0 DestRegister:1 SourceRegister:0 Base:1 Offset:16 Len:4 CsumType:0  CsumFlags:0}

*expr.Verdict
&{Kind:1 Chain:}
*/

notice that no *expr.Ct and *expr.Range appear

So I tried to add related type select below case unix.NFTA_EXPR_DATA in fucntion exprsFromMsg

case "ct":
    e = &expr.Ct{}
case "range":
    e = &expr.Range{}

then test work fine

/*output
*expr.Ct
&{Register:1 SourceRegister:false Key:0}

*expr.Bitwise
&{SourceRegister:1 DestRegister:1 Len:4 Mask:[6 0 0 0] Xor:[0 0 0 0]}

*expr.Cmp
&{Op:1 Register:1 Data:[0 0 0 0]}

*expr.Payload
&{OperationType:0 DestRegister:1 SourceRegister:0 Base:1 Offset:16 Len:4 CsumType:0  CsumFlags:0}

*expr.Range
&{Op:1 Register:1 FromData:[8 0 1 0 192 168 1 1] ToData:[8 0 1 0 192 168 1 100]}

*expr.Verdict
&{Kind:1 Chain:}
*/

question: Rule to string representation an reverse

All, and @rwhelan , @sbezverk,

In a nutshell, I want to validate the existence of the following nftables rule:

oifname "cni-podman0" ip daddr 192.168.124.0/24 ct state established,related counter packets 108 bytes 125682 accept # handle 71

The Rule object looks like this:

(*nftables.Rule)(0xc000140780)({
    Table: (*nftables.Table)(0xc0001cd660)({
        Name: (string) (len=6) "filter",
        Use: (uint32) 0,
        Flags: (uint32) 0,
        Family: (nftables.TableFamily) 0
    }),
    Chain: (*nftables.Chain)(0xc0001d0580)({
        Name: (string) (len=7) "FORWARD",
        Table: (*nftables.Table)(<nil>),
        Hooknum: (nftables.ChainHook) 0,
        Priority: (nftables.ChainPriority) 0,
        Type: (nftables.ChainType) "",
        Policy: (*nftables.ChainPolicy)(<nil>)
    }),
    Position: (uint64) 0,
    Handle: (uint64) 71,
    Exprs: ([]expr.Any) (len=9 cap=16) {
        (*expr.Meta)(0xc0001c4ad0)({
            Key: (expr.MetaKey) 7,
            SourceRegister: (bool) false,
            Register: (uint32) 1
        }),
        (*expr.Cmp)(0xc0001cd760)({
            Op: (expr.CmpOp) 0,
            Register: (uint32) 1,
            Data: ([]uint8) (len=16 cap=16) {
                0000000063 6e 69 2d 70 6f 64 6d61 6e 30 00 00 00 00 00|cni-podman0.....|
            }
        }),
        (*expr.Payload)(0xc00012c270)({
            OperationType: (expr.PayloadOperationType) 0,
            DestRegister: (uint32) 1,
            SourceRegister: (uint32) 0,
            Base: (expr.PayloadBase) 1,
            Offset: (uint32) 16,
            Len: (uint32) 4,
            CsumType: (expr.PayloadCsumType) 0,
            CsumOffset: (uint32) 0,
            CsumFlags: (uint32) 0
        }),
        (*expr.Bitwise)(0xc0001d0a40)({
            SourceRegister: (uint32) 1,
            DestRegister: (uint32) 1,
            Len: (uint32) 4,
            Mask: ([]uint8) (len=4 cap=4) {
                00000000ff ff ff 00 |....|
            },
            Xor: ([]uint8) (len=4 cap=4) {
                0000000000 00 00 00 |....|
            }
        }),
        (*expr.Cmp)(0xc0001cd920)({
            Op: (expr.CmpOp) 0,
            Register: (uint32) 1,
            Data: ([]uint8) (len=4 cap=4) {
                00000000c0 a8 7c 00 |..|.|
            }
        }),
        (*expr.Bitwise)(0xc0001d0dc0)({
            SourceRegister: (uint32) 1,
            DestRegister: (uint32) 1,
            Len: (uint32) 4,
            Mask: ([]uint8) (len=4 cap=4) {
                0000000006 00 00 00 |....|
            },
            Xor: ([]uint8) (len=4 cap=4) {
                0000000000 00 00 00 |....|
            }
        }),
        (*expr.Cmp)(0xc0001cdaa0)({
            Op: (expr.CmpOp) 1,
            Register: (uint32) 1,
            Data: ([]uint8) (len=4 cap=4) {
                0000000000 00 00 00 |....|
            }
        }),
        (*expr.Counter)(0xc0001c4c10)({
            Bytes: (uint64) 125682,
            Packets: (uint64) 108
        }),
        (*expr.Verdict)(0xc0001cdc00)({
            Kind: (expr.VerdictKind) 1,
            Chain: (string) ""
        })
    },
    UserData: ([]uint8) <nil>
})

Is there a way to convert Rule struct to text representation (i.e. the output of nft list chain ip filter FORWARD -a? I did not find an existing method to do so for the Rule.

support for list

add rule ip filter-v4 chain-1-v4 tcp sport { 1024, 1025, 1030 } return

__set%d filter-v4 3 size 3
__set%d filter-v4 0
element 00000004 : 0 [end] element 00000104 : 0 [end] element 00000604 : 0 [end]

[ meta load l4proto => reg 1 ]
[ cmp eq reg 1 0x00000006 ]
[ payload load 2b @ transport header + 0 => reg 1 ]
[ lookup reg 1 set __set%d ]
[ immediate reg 0 return ]

nftables failed to cooperate with fail2ban...

jail.conf

[sshd]
enabled = true
port = 22,9022
bantime = 1h
maxretry = 5
#ignoreip = 19.19.20.43
banaction = nftables-multiport
banaction_allports = nftables-allports

[ASTERISK]
enabled = true
bantime = 1h
maxretry = 5
banaction = nftables-multiport
banaction_allports = nftables-allports


nft list table fail2ban

(fail2ban table name)

table ip fail2ban { # handle 229
set f2b-ASTERISK { # handle 4
type ipv4_addr
}

set f2b-sshd { # handle 5
	type ipv4_addr
}

chain input { # handle 1
	type filter hook input priority 0; policy accept;
	counter packets 25070 bytes 4424719 jump f2b-sshd # handle 8
	udp dport { sip, 10000-20000 } ip saddr @f2b-ASTERISK drop # handle 9
	counter packets 5665 bytes 944892 jump f2b-sshd # handle 10
	tcp dport { ssh, 9022 } ip saddr @f2b-sshd drop # handle 11
}

chain f2b-sshd { # handle 2
	counter packets 30904 bytes 5383849 return # handle 12
}

chain f2b-ASTERISK { # handle 3
	counter packets 0 bytes 0 return # handle 13
}

}


fail2ban.log

2019-11-12 11:47:34,537 fail2ban.server [15189]: INFO --------------------------------------------------
2019-11-12 11:47:34,537 fail2ban.server [15189]: INFO Starting Fail2ban v0.10.4
2019-11-12 11:47:34,543 fail2ban.database [15189]: INFO Connected to fail2ban persistent database '/var/lib/fail2ban/fail2ban.sqlite3'
2019-11-12 11:47:34,544 fail2ban.jail [15189]: INFO Creating new jail 'sshd'
2019-11-12 11:47:34,557 fail2ban.jail [15189]: INFO Jail 'sshd' uses systemd {}
2019-11-12 11:47:34,558 fail2ban.jail [15189]: INFO Initiated 'systemd' backend
2019-11-12 11:47:34,559 fail2ban.filter [15189]: INFO maxLines: 1
2019-11-12 11:47:34,592 fail2ban.filtersystemd [15189]: INFO [sshd] Added journal match for: '_SYSTEMD_UNIT=sshd.service + _COMM=sshd'
2019-11-12 11:47:34,592 fail2ban.filter [15189]: INFO maxRetry: 5
2019-11-12 11:47:34,593 fail2ban.filter [15189]: INFO encoding: UTF-8
2019-11-12 11:47:34,594 fail2ban.jail [15189]: INFO Creating new jail 'ASTERISK'
2019-11-12 11:47:34,602 fail2ban.jail [15189]: INFO Jail 'ASTERISK' uses pyinotify {}
2019-11-12 11:47:34,605 fail2ban.jail [15189]: INFO Initiated 'pyinotify' backend
2019-11-12 11:47:34,621 fail2ban.filter [15189]: INFO maxRetry: 5
2019-11-12 13:50:21,080 fail2ban.actions [16260]: INFO banTime: 3600
2019-11-12 13:50:21,081 fail2ban.filter [16260]: INFO encoding: UTF-8
2019-11-12 13:50:21,081 fail2ban.jail [16260]: INFO Creating new jail 'ASTERISK'
2019-11-12 13:50:21,089 fail2ban.jail [16260]: INFO Jail 'ASTERISK' uses pyinotify {}
2019-11-12 13:50:21,093 fail2ban.jail [16260]: INFO Initiated 'pyinotify' backend
2019-11-12 13:50:21,108 fail2ban.filter [16260]: INFO maxRetry: 5
2019-11-12 13:50:21,109 fail2ban.filter [16260]: INFO findtime: 600
2019-11-12 13:50:21,109 fail2ban.actions [16260]: INFO banTime: 3600
2019-11-12 13:50:21,109 fail2ban.filter [16260]: INFO encoding: UTF-8
2019-11-12 13:50:21,110 fail2ban.filter [16260]: INFO Added logfile: '/var/log/asterisk/security' (pos = 496567, hash = 1604fd21935e1203261c5d823e388d804d316c46)
2019-11-12 13:50:21,112 fail2ban.jail [16260]: INFO Creating new jail 'recidive'
2019-11-12 13:50:21,112 fail2ban.jail [16260]: INFO Jail 'recidive' uses pyinotify {}
2019-11-12 13:50:21,115 fail2ban.jail [16260]: INFO Initiated 'pyinotify' backend
2019-11-12 13:50:21,122 fail2ban.server [16260]: INFO Jail recidive is not a JournalFilter instance
2019-11-12 13:50:21,122 fail2ban.filter [16260]: INFO maxRetry: 3
2019-11-12 13:50:21,122 fail2ban.filter [16260]: INFO findtime: 86400
2019-11-12 13:50:21,123 fail2ban.actions [16260]: INFO banTime: 86400
2019-11-12 13:50:21,123 fail2ban.filter [16260]: INFO encoding: UTF-8
2019-11-12 13:50:21,124 fail2ban.filter [16260]: INFO Added logfile: '/var/log/fail2ban.log' (pos = 239881, hash = 3f7c7b000305d0dcea7817f9c73ce9bb57d5d6aa)
2019-11-12 13:50:21,128 fail2ban.jail [16260]: INFO Jail 'sshd' started
2019-11-12 13:50:21,134 fail2ban.jail [16260]: INFO Jail 'ASTERISK' started
2019-11-12 13:50:21,139 fail2ban.jail [16260]: INFO Jail 'recidive' started
2019-11-12 13:50:21,185 fail2ban.actions [16260]: NOTICE [ASTERISK] Restore Ban 19.19.20.43
2019-11-12 13:50:21,195 fail2ban.utils [16260]: #39-Lev. 7ffb06c90570 -- exec: nft add set ip fail2ban f2b-ASTERISK { type ipv4_addr; }
nft insert rule ip fail2ban input meta l4proto all ip saddr @f2b-ASTERISK drop
2019-11-12 13:50:21,195 fail2ban.utils [16260]: ERROR 7ffb06c90570 -- stderr: 'Error: syntax error, unexpected all'
2019-11-12 13:50:21,196 fail2ban.utils [16260]: ERROR 7ffb06c90570 -- stderr: 'insert rule ip fail2ban input meta l4proto all ip saddr @f2b-ASTERISK drop'
2019-11-12 13:50:21,201 fail2ban.utils [16260]: ERROR 7ffb06c90570 -- stderr: ' ^^^'
2019-11-12 13:50:21,211 fail2ban.utils [16260]: ERROR 7ffb06c90570 -- returned 1
2019-11-12 13:50:21,214 fail2ban.actions [16260]: ERROR Failed to execute ban jail 'ASTERISK' action 'nftables-allports' info 'ActionInfo({'ip': '19.19.20.43', 'family': 'inet4', 'fid': <function Actions.ActionInfo. at 0x7ffb076b3840>, 'raw-ticket': <function Actions.ActionInfo. at 0x7ffb076b3d90>})': Error starting action Jail('ASTERISK')/nftables-allports

2019-11-12 13:50:46,256 fail2ban.filter [16260]: INFO [ASTERISK] Found 19.19.20.43 - 2019-11-12 13:50:46
2019-11-12 13:50:49,201 fail2ban.filter [16260]: INFO [ASTERISK] Found 19.19.20.43 - 2019-11-12 13:50:49
2019-11-12 13:50:49,201 fail2ban.filter [16260]: INFO [ASTERISK] Found 19.19.20.43 - 2019-11-12 13:50:49
2019-11-12 13:50:49,228 fail2ban.filter [16260]: INFO [ASTERISK] Found 19.19.20.43 - 2019-11-12 13:50:49
2019-11-12 13:50:52,240 fail2ban.filter [16260]: INFO [ASTERISK] Found 19.19.20.43 - 2019-11-12 13:50:52
2019-11-12 13:50:52,240 fail2ban.filter [16260]: INFO [ASTERISK] Found 19.19.20.43 - 2019-11-12 13:50:52
2019-11-12 13:50:52,251 fail2ban.filter [16260]: INFO [ASTERISK] Found 19.19.20.43 - 2019-11-12 13:50:52
2019-11-12 13:50:52,445 fail2ban.actions [16260]: NOTICE [ASTERISK] 19.19.20.43 already banned
2019-11-12 13:50:55,279 fail2ban.filter [16260]: INFO [ASTERISK] Found 19.19.20.43 - 2019-11-12 13:50:55
2019-11-12 13:50:55,280 fail2ban.filter [16260]: INFO [ASTERISK] Found 19.19.20.43 - 2019-11-12 13:50:55

I'm wondering why firewall failures and error logs are occurring.

Missing expressions decoders in exprsFromMsg

The following expressions' decoders are missing in exprsFromMsg call.

Unknown expression name: bitwise
Unknown expression name: redir
Unknown expression name: nat

As a result some expressions information gets lost while reading existing nftables rules from the netlink.
I will add these expressions in the following PRs.

AddSet silently fails (!) when adding IP addresses with too many bytes (without To4() call)

Hi am facing trouble when using this package i can't load my rule this is my code :

package main

import (
	"fmt"
	"log"
	"net"

	"github.com/google/nftables"
	"github.com/google/nftables/expr"
)

func main() {
	c := &nftables.Conn{}

	// Basic boilerplate; create a table & chain.
	table := &nftables.Table{
		Family: nftables.TableFamilyIPv4,
		Name:   "ip_filter",
	}
	table = c.AddTable(table)

	myChain := c.AddChain(&nftables.Chain{
		Name:     "filter_chain",
		Table:    table,
		Type:     nftables.ChainTypeFilter,
		Hooknum:  nftables.ChainHookInput,
		Priority: nftables.ChainPriorityFilter,
	})

	set := &nftables.Set{
		Name:    "whitelist",
		Table:   table,
		KeyType: nftables.TypeIPAddr, // our keys are IPv4 addresses
	}

	// Create the set with a bunch of initial values.
	if err := c.AddSet(set, []nftables.SetElement{
		{Key: net.ParseIP("8.8.8.8")},
	}); err != nil {
		log.Fatal(err.Error())
	}

	c.AddRule(&nftables.Rule{
		Table: table,
		Chain: myChain,
		Exprs: []expr.Any{
			// [ payload load 4b @ network header + 16 => reg 1 ]
			&expr.Payload{
				DestRegister: 1,
				Base:         expr.PayloadBaseNetworkHeader,
				Offset:       16,
				Len:          4,
			},
			// [ lookup reg 1 set whitelist ]
			&expr.Lookup{
				SourceRegister: 1,
				SetName:        set.Name,
				SetID:          set.ID,
			},
			//[ immediate reg 0 drop ]
			&expr.Verdict{
				Kind: expr.VerdictDrop,
			},
		},
	})
	if err := c.Flush(); err != nil {
		log.Fatal(err.Error())
	}
	rules, _ := c.GetRule(table, myChain)
	fmt.Println(rules)
}

after executing my code and run the following command : sudo nft list ruleset i expect to found my configured config but i found nothing. is this a compatibility problem ?

Implement hash module

The goal would be to be able to use the internal hashing capabilities of nftables through a new kind of expr (Hashing section).

I already found all the hashing related unix constants, need to figure out what each of those keys expect as attributes before implementing that in the golang nftables module.

NFT_HASH_JENKINS                  = 0x0
NFT_HASH_SYM                      = 0x1
NFTA_HASH_UNSPEC                  = 0x0
NFTA_HASH_SREG                    = 0x1
NFTA_HASH_DREG                    = 0x2
NFTA_HASH_LEN                     = 0x3
NFTA_HASH_MODULUS                 = 0x4
NFTA_HASH_SEED                    = 0x5
NFTA_HASH_OFFSET                  = 0x6
NFTA_HASH_TYPE                    = 0x7

I can take care of that PR as well, but I'll probably need some guidance/feedback/correction on the code I'll produce as it'll be my first time contributing on a big part of the project.

Consider using netlink.AttributeDecoder

Hey hey. Not really an issue, per say, but I recently decided to rethink how I was decoding netlink attributes and ended up coming up with:

https://godoc.org/github.com/mdlayher/netlink#AttributeDecoder

See the example there for some usage too. It's a bit safer than the typical memory casts I'd been doing before with nlenc directly. Perhaps it'll be useful here too.

I don't have a better encoding solution yet but perhaps that will come as well as I continue working on wireguardctrl!

Question: adding Goto or Jump rule to base chain

It is not related to google nftables implementation, but I came across and issue when I get operation not supported when I try to add GoTo or Jump to non-base chain. According to the documentation here it should be possible.
https://wiki.nftables.org/wiki-nftables/index.php/Jumping_to_chain

Appreciate any thoughts/suggestions or workarounds :)

sudo  nft add rule ip istio_ipv4 istio_output_ipv4 jump istio_outbound_ipv4
Error: Could not process rule: Operation not supported
add rule ip istio_ipv4 istio_output_ipv4 jump istio_outbound_ipv4
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

I get the same error where I try to add the same rule from the go code, so it is like the error comes from the kernel.

The complete table built by go program.

able ip istio_ipv4 {
	set istio_ssh_excl_ipv4 {
		type inet_service
		flags constant
		elements = { 22 }
	}

	set 10a90ef5-3e62-4de1-ad23-97ae01bb6964 {
		type inet_service
		flags constant
		elements = { 11111, 22222 }
	}

	set ec11fb42-886d-4c03-bb5e-1b9fda833122 {
		type inet_service
		flags constant
		elements = { 44444, 55555 }
	}

	chain istio_prerouting_ipv4 {
		type filter hook prerouting priority filter; policy accept;
	}

	chain istio_output_ipv4 {
		type filter hook output priority filter; policy accept;
	}

	chain istio_inbound_ipv4 {
		tcp dport @istio_ssh_excl_ipv4 return comment "�U"
		tcp dport @ec11fb42-886d-4c03-bb5e-1b9fda833122 return comment ""
		goto istio_redirect_ipv4
	}

	chain istio_outbound_ipv4 {
		ip daddr 127.0.0.1 return
		meta skuid 1339 return comment "�"
		meta skgid 1339 return
		tcp dport @10a90ef5-3e62-4de1-ad23-97ae01bb6964 return
		ip daddr 1.1.1.0/24 return comment ""
		ip daddr 2.2.2.0/30 return comment ""
		goto istio_redirect_ipv4 comment ""
	}

	chain istio_redirect_ipv4 {
		ip protocol tcp redirect to :15001 comment ""
	}
}

Implement expression for matching on source IP?

Heya,

TL;DR: I'm trying to implement the equivalent of running:

sudo strace nft add rule nat postrouting ip saddr 192.168.69.2 masquerade

It doesnt look like this library supports source address rules out of the box. I found this which doesnt seem to be terribly different, but I also dont understand (we are throwing bytecode down the netlink socket?)

I tried stracing nft, I tried poking around the nfnl source ... everything seems really hard to follow. Could you give me some direction on how to implement this? happy to write the code, I'm just very lost rn.

Tail end of the strace:


socket(AF_NETLINK, SOCK_RAW, NETLINK_NETFILTER) = 3
fcntl(3, F_SETFL, O_RDONLY|O_NONBLOCK)  = 0
mmap(NULL, 204800, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7feba6a75000
setsockopt(3, SOL_SOCKET, SO_SNDBUFFORCE, [131072], 4) = 0
sendmsg(3, {msg_name={sa_family=AF_NETLINK, nl_pid=0, nl_groups=00000000}, msg_namelen=12, msg_iov=[{iov_base=[{{len=20, type=0x10 /* NLMSG_??? */, flags=NLM_F_REQUEST, seq=0, pid=0}, "\x00\x00\x0a\x00"}, {{len=184, type=0xa06 /* NLMSG_??? */, flags=NLM_F_REQUEST|0xe00, seq=1, pid=0}, "\x02\x00\x00\x00\x08\x00\x01\x00\x6e\x61\x74\x00\x10\x00\x02\x00\x70\x6f\x73\x74\x72\x6f\x75\x74\x69\x6e\x67\x00\x8c\x00\x04\x80"...}, {{len=20, type=0x11 /* NLMSG_??? */, flags=NLM_F_REQUEST, seq=2, pid=0}, "\x00\x00\x0a\x00"}], iov_len=224}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, 0) = 224
select(4, [3], NULL, NULL, {tv_sec=0, tv_usec=0}) = 0 (Timeout)
munmap(0x7feba6a75000, 204800)          = 0
close(3)                                = 0
exit_group(0)                           = ?

Weird behaviors of Obj API

Hello,

I noticed weird behaviors of Obj API with counters (perhaps with other objects too):

  • GetObj(Obj) : return all counters, not only the given one
  • GetObjReset(Obj) : reset all counters, not only the given one

Tests are not robust and can't tell me if it's the wanted behaviors.

The consequence of that:

  • No clean API to retrieve all counters
  • No API to retrieve values of a given counter
  • No API to reset a given counter

@stapelberg @sbezverk

Request for comment: Introduce UserData struct in a rule

Currently if the process which created a specific rule in nftables restart there is no easy way to associate it with this rule consumer.
Example when kube-proxy creates iptables rule for a service or an endpoint, even if kube-proxy restarts it can compute this association from a rule.
To achieve something similar with nftables I propose to add UserData struct as a part of Rule struct. Rule's UserData is preserved in the kernel so it can reliably retrieved even if the process which created a specific rule was restarted.
I propose:

type Rule struct {
	Table    *Table
	Chain    *Chain
	RuleID   uint32    < ----- Remove
	Position uint64
	Handle   uint64
	Exprs    []expr.Any
        UserData *UserData  < ----- Add this struct
}

type UserData struct {
        RuleID   uint32   < -------- Move here to preserve current functionality
        Data .   []byte
}

RuleID must be moved inside of a UserData struct as currently RuleID already uses UserData but just for itself. With this approach UserData will carry RuleID and byte slice which can be use to store any arbitrary data, example json with details on a service or endpoint which is using this rule.

I will do the implementation, but I would your opinion about it.

@stapelberg WDYT??

bug: chain name is empty when unpacking expr.Verdict

The chain FORWARD looks as follows:

table ip6 filter {
        chain FORWARD {
                type filter hook forward priority filter; policy drop;
                jump cnins-3-4026600826-dummy0
        }

        chain cnins-3-4026600826-dummy0 {
                oifname "dummy0" ip6 daddr 2001:db8:100:100::1 ct state established,related counter packets 0 bytes 0 accept
                iifname "dummy0" ip6 saddr 2001:db8:100:100::1 counter packets 0 bytes 0 accept
                iifname "dummy0" oifname "dummy0" counter packets 0 bytes 0 accept
        }
}

The jump rules was created with:

            // Finally, add the jump to the above chain in FORWARD chain.
            jumpRule := &nftables.Rule{
                Table: addr.table,
                Chain: addr.chain,
                Exprs: []expr.Any{},
            }
            jumpRule.Exprs = append(jumpRule.Exprs, &expr.Verdict{
                Kind:  expr.VerdictJump,
                Chain: chainName,
            })

            nb.conn.AddRule(jumpRule)
            if err := nb.conn.Flush(); err != nil {
                return fmt.Errorf(
                    "failed adding jump rule in table %s chain %s for address %v of interface %s",
                    addr.table.Name, addr.chain.Name, addr.conf, intfName,
                )
            }

Subsequently, when retriving the rule, it does not resolve to expr.VerdictJump and Chain name is empty:

([]*nftables.Rule) (len=1 cap=1) {
 (*nftables.Rule)(0xc00033ae60)({
  Table: (*nftables.Table)(0xc00000f400)({
   Name: (string) (len=6) "filter",
   Use: (uint32) 0,
   Flags: (uint32) 0,
   Family: (nftables.TableFamily) 0
  }),
  Chain: (*nftables.Chain)(0xc00045e780)({
   Name: (string) (len=7) "FORWARD",
   Table: (*nftables.Table)(<nil>),
   Hooknum: (nftables.ChainHook) 0,
   Priority: (nftables.ChainPriority) 0,
   Type: (nftables.ChainType) "",
   Policy: (*nftables.ChainPolicy)(<nil>)
  }),
  Position: (uint64) 0,
  Handle: (uint64) 6,
  Exprs: ([]expr.Any) (len=1 cap=1) {
   (*expr.Verdict)(0xc00000f580)({
    Kind: (expr.VerdictKind) 4294967293,
    Chain: (string) ""
   })
  },
  UserData: ([]uint8) <nil>
 })
}

Please help!

Conn.GetRule returns no rules

My test code:

package main

import (
	"fmt"
	"github.com/google/nftables"
)

func main() {
	c := &nftables.Conn{}
	c.FlushRuleset()
	rules, err := c.GetRule(
		&nftables.Table{
			Family: nftables.TableFamilyIPv4,
			Name:   "filter"},
		&nftables.Chain{
			Name: "INPUT"})

	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Printf("Retrieved %v rules\n", len(rules))
	for _, rule := range rules {
		fmt.Println(rule)
	}
}

Output:

$ go build nftest
$ sudo ./nftest#
Retrieved 0 rules

Yet:

$ sudo iptables -t filter -L INPUT
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
DROP       tcp  --  anywhere             dns.quad9.net        tcp dpt:5000 /* A test rule */

Set for 2 elements (uint16) ports works, but not for 3 or more

I came across an issue when the same code works as expected when set has 1 or 2 elements which are uint16 ports, but does not work for more than 2. Interestingly, the same code works for 3 or more when elements are IPv4 addresses.

[pid 87287] <... recvmsg resumed> {msg_name={sa_family=AF_NETLINK, nl_pid=0, nl_groups=00000000}, msg_namelen=112->12, msg_iov=[{iov_base={{len=36, type=NLMSG_ERROR, flags=NLM_F_CAPPED, seq=3291302533, pid=87287}, {error=0, msg={len=108, type=NFNL_SUBSYS_NFTABLES<<8|NFT_MSG_NEWSET, flags=NLM_F_REQUEST|NLM_F_ACK|NLM_F_CREATE, seq=3291302533, pid=87287}}}, iov_len=4096}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, 0) = 36

This error I see in strace when a set with 3 elements is created. Appreciate some ideas where to go next.

feature: Rule methods

@stapelberg, @sbezverk and other maintainers, currently many module's structs have no methods of their own. For example, it would have been nice to have String() method for Rule.

Is it OK to submit a PR for this?

Receive: netlink receive: numerical result out of range

Getting the following error:

Receive: netlink receive: numerical result out of range

It is somehow kernel related. It appeared when I was testing on CentoOS 7 with Linux test1.local 3.10.0-1127.18.2.el7.x86_64 #1 SMP Sun Jul 26 15:27:06 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux. It was not happening with CentOS 8.

The code triggering the issue:

            // Add a new chain
            // defaultDropPolicy := nftables.ChainPolicyDrop
            chain := p.conn.AddChain(&nftables.Chain{
                Name: chainName,
                //Table:    addr.table,
                Table: &nftables.Table{Name: addr.table.Name, Family: addr.table.Family},
                //Type:     nftables.ChainTypeFilter,
                //Hooknum:  nftables.ChainHookForward,
                //Priority: nftables.ChainPriorityFilter,
                //Policy:   &defaultDropPolicy,
            })
            if err := p.conn.Flush(); err != nil {
                return fmt.Errorf(
                    "failed adding chain %s for address %v of interface %s: %s",
                    chainName, addr.conf, intfName, err,
                )
            }

It looks related to: #62

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.