nftables icon indicating copy to clipboard operation
nftables copied to clipboard

Set with key type nftables.TypeIFName not working

Open rdmcguire opened this issue 3 years ago • 2 comments

Creating a set with Type: nftables.TypeIFName seems to work, but the set acts strangely.

Sample code for set:

        conn.AddSet(&nftables.Set{
                Table:   table,
                Name:    "test_set",
                KeyType: nftables.TypeIFName,
        }, nil)

I also created a set called "manual_set" manually, and they both list:

nftables pkg set and nft cli set

% nft 'add set test_table manual_set { type ifname; }'
% nft list table test_table
table ip test_table {
        set test_set {
                type ifname
        }

        set manual_set {
                type ifname
        }
}

I then added an element into each set using nft cli and they both seem to succeed, however the set created by this package shows empty elements.

Added Elements

% nft 'add element test_table test_set { "wg0" }'
% nft 'add element test_table manual_set { "wg0" }'
% nft list table test_table                                                                      ☸ kubernetes-admin@50w_k8s:argocd 
table ip test_table {
        set test_set {
                type ifname
                elements = { "" }
        }

        set manual_set {
                type ifname
                elements = { "wg0" }
        }
}

Additionally, trying to add an element through this package fails for either table, though I do wonder if I've missed something in my code:

Attempt to add set element to both tables

        // github.com/google/nftables set and manual set
        testSet, _ := c.GetSetByName(table, "test_set")
        manualSet, _ := c.GetSetByName(table, "manual_set")

        // Second Element
        elements := []nftables.SetElement{{Key: []byte("wg1")}}

        c.SetAddElements(manualSet, elements)
        c.SetAddElements(testSet, elements)

        if err := c.Flush(); err != nil {
                log.Panicf("Error: %s", err)
        }

Running returns error: "conn.Receive: netlink receive: invalid argument"

I've tried a few different key types and have only had success with TypeFamilyIPv4.

rdmcguire avatar Aug 28 '22 01:08 rdmcguire

Hi @rdmcguire,

I have checked your issue and it seems that there is a bug in the set marshaling logic in the nftables go lib. If we observe the messages sent by the nft cmdline tool:

[
  // NFTA_SET_TABLE, "filter\x00"
  [{nla_len=11, nla_type=0x1}, "\x66\x69\x6c\x74\x65\x72\x00"], 
  // NFTA_SET_NAME, "test_set\x00"
  [{nla_len=13, nla_type=0x2}, "\x74\x65\x73\x74\x5f\x73\x65\x74\x00"], 
  // NFTA_SET_FLAGS, 0
  [{nla_len=8, nla_type=0x3}, "\x00\x00\x00\x00"], 
  // NFTA_SET_KEY_TYPE, IFName (0x29)
  [{nla_len=8, nla_type=0x4}, "\x00\x00\x00\x29"], 
  // NFTA_SET_KEY_LEN, 16
  [{nla_len=8, nla_type=0x5}, "\x00\x00\x00\x10"], 
  // NFTA_SET_ID, 1
  [{nla_len=8, nla_type=0xa}, "\x00\x00\x00\x01"], 
  // NFTA_SET_USERDATA, "\x00\x04\x01\x00\x00\x00"
  [{nla_len=10, nla_type=0xd}, "\x00\x04\x01\x00\x00\x00"]
]

and compare it with the conn.AddSet call that you've written (the data part):

[
  {Header:{Length:0 Type:unknown(2569) Flags:request|acknowledge|0x400 Sequence:0 PID:0} 
  Data:[
    2 0 0 0 
    // NFTA_SET_TABLE, "filter\x00"
    11 0 1 0 102 105 108 116 101 114 0 0 
    // NFTA_SET_NAME, "test_set\x00"
    13 0 2 0 116 101 115 116 95 115 101 116 0 0 0 0 
    // NFTA_SET_FLAGS, 0
    8 0 3 0 0 0 0 0 
    // NFTA_SET_KEY_TYPE, IFName (0x29 = 41)
    8 0 4 0 0 0 0 41 
    // NFTA_SET_KEY_LEN, 16
    8 0 5 0 0 0 0 16 
    // NFTA_SET_ID, 1
    8 0 10 0 0 0 0 1
    // NFTA_SET_USERDATA is not here
  ]}
...

You immediately see that the NFTA_SET_USER_DATA is missing from the message list.

Digging into nftables source code shows that user data is always loaded with at least one TLV structure: https://git.netfilter.org/nftables/tree/src/mnl.c?id=187c6d01d35722618c2711bbc49262c286472c8f#n1165. The one that is loaded by default contains NFTNL_UDATA_SET_KEYBYTEORDER (definition is at https://git.netfilter.org/libnftnl/tree/include/libnftnl/udata.h?id=212479ad2c9200fa858a37de14a2e5e996f10105#n40) and the structure is described here: https://git.netfilter.org/libnftnl/tree/include/udata.h?id=212479ad2c9200fa858a37de14a2e5e996f10105

The description matches the udata structure we see in the message sent by the nft cmdline tool:

// NFTA_SET_USERDATA, {type: NFTNL_UDATA_SET_KEYBYTEORDER (0x00), len: 4, value: 1}
[{nla_len=10, nla_type=0xd}, "\x00\x04\x01\x00\x00\x00"]

Currently, in nftables go lib, setting set user data is done only in certain cases: https://github.com/google/nftables/blob/ec1e802faf9426e1f27cd8bfa8434e110bae5146/set.go#L542

I managed to work around your first issue by adding an else to the existing if that loads user data (#180):

else {
	tableInfo = append(tableInfo,
		netlink.Attribute{Type: unix.NFTA_SET_USERDATA, Data: []byte("\x00\x04\x01\x00\x00\x00")})
}

This fixes the first part of the problem. The second part of your issue started working for me when I have aligned the "wg1" string in the code to a 16 byte value:

elements := []nftables.SetElement{{Key: []byte("wg1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")}}

My guess is that this is because the IFName type has the key length type set to 16 when marshaling data to nftables. Remember this one from above?

// NFTA_SET_KEY_LEN, 16
8 0 5 0 0 0 0 16

turekt avatar Aug 30 '22 18:08 turekt