nftables icon indicating copy to clipboard operation
nftables copied to clipboard

feature: add support for comments

Open profawk opened this issue 3 years ago • 8 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 :smile:

profawk avatar Apr 23 '21 17:04 profawk

The UserData field was last touched in commit 0b3d8b56f6af673267457b98226f6659773ae6f7 by @sbezverk — any comments on this?

stapelberg avatar Apr 24 '21 08:04 stapelberg

@profawk Here is an example how UserData is used: https://github.com/sbezverk/nftableslib/blob/master/nfrules.go#L244, please let me know if you have further questions.

sbezverk avatar Apr 24 '21 13:04 sbezverk

This features doesn't seem to work for me. I can add a comment to a rule (with nft cmdline) and I do see it in the UserData field when printing out the rule content. So the "Read" direction works.

But when I try to add UserData to a rule, while it does seem to be added (I see the bytes in the rule dump), I don't see the comment when listing the ruleset with the nft cmdline tool.

Example: This was added with the cmdline:

meta l4proto . ip6 saddr . ip6 daddr . th dport @ip6_rule_collection_2_drop jump drop_actions comment "test comment"

And this is the dump:

&nftables.Rule{..., UserData:[]uint8{0x0, 0xd, 0x74, 0x65, 0x73, 0x74, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x0}}

So far so good.

And this is the other way around:

	rule := &nftables.Rule{...}
	rule.UserData = []byte("another test comment")
	c.AddRule(rule)
        c.Flush()

And this is the dump I get (also good):

&nftables.Rule{...UserData:[]uint8{0x61, 0x6e, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x2, 0x2, 0x0, 0x0}}

Again, so far so good.

But when I list the ruleset with nft cmdline, I don't get a comment. I see the rule, but no comment at the end.

Also, unrelated but could be useful to also have UserData support for sets and set elements. Netlink obviously supports it, and implementation should be pretty similar to rule comments (or so I gather from the code, but I didn't deep dive), just use NFTA_SET_USERDATA/NFTA_SET_ELEM_USERDATA.

msdean avatar May 02 '22 11:05 msdean

@msdean I seem to have the same issue as you.

stv0g avatar Oct 18 '22 11:10 stv0g

Okay, I solved it:

libnftl uses TVLs to encode different types of fields in the UserData attribute. This has been added via this patch: https://git.netfilter.org/libnftnl/commit/?id=5c3bc232dc9d1dd01d589fab096f67d944621fc2

Here is some quick example code to encode comments in the UserData:

package proxy

import "encoding/binary"

type NftablesUserDataType byte

const (
	NftablesUserDataTypeComment NftablesUserDataType = iota
	NftablesUserDataTypeRuleID  NftablesUserDataType = 100 // custom extension
)

func NftablesUserDataPut(udata []byte, typ NftablesUserDataType, data []byte) []byte {
	udata = append(udata, byte(typ), byte(len(data)))
	udata = append(udata, data...)

	return udata
}

func NftablesUserDataGet(udata []byte, styp NftablesUserDataType) []byte {
	for {
		if len(udata) < 2 {
			break
		}

		typ := NftablesUserDataType(udata[0])
		length := int(udata[1])
		data := udata[2 : 2+length]

		if styp == typ {
			return data
		}

		if len(udata) < 2+length {
			break
		} else {
			udata = udata[2+length:]
		}
	}

	return nil
}

func NftablesUserDataPutInt(udata []byte, typ NftablesUserDataType, num uint32) []byte {
	data := make([]byte, 4)
	binary.LittleEndian.PutUint32(data, num)

	return NftablesUserDataPut(udata, typ, data)
}

func NftablesUserDataGetInt(udata []byte, typ NftablesUserDataType) (uint32, bool) {
	data := NftablesUserDataGet(udata, typ)
	if data == nil {
		return 0, false
	}

	return binary.LittleEndian.Uint32(data), true
}

func NftablesUserDataPutString(udata []byte, typ NftablesUserDataType, str string) []byte {
	data := append([]byte(str), 0)
	return NftablesUserDataPut(udata, typ, data)
}

func NftablesUserDataGetString(udata []byte, typ NftablesUserDataType) (string, bool) {
	data := NftablesUserDataGet(udata, typ)
	if data == nil {
		return "", false
	}

	return string(data), true
}


func AddRule(r *nftables.Rule) {
	[...]
	r.UserData = NftablesUdataPutString(r.UserData, NftablesUserDataTypeComment, "this is my comment")
	r.UserData = NftablesUdataPutInt(r.UserData, NftablesUserDataTypeRuleId, uint32(1234))
	[...]
	ruleID, ok := NftablesUdataGetInt(r.UserData, NftablesUserDataTypeRuleId)
	comment, ok := NftablesUdataGetString(r.UserData, NftablesUserDataTypeComment)
}

stv0g avatar Oct 18 '22 11:10 stv0g

@stv0g Very nice code. I strongly recommend making a PR to add these methods to nftables.Rule struct.

fungaren avatar Apr 03 '23 12:04 fungaren

I submitted a PR #221

stv0g avatar Apr 15 '23 09:04 stv0g

Also related to #123?

stv0g avatar Apr 15 '23 09:04 stv0g