invalid argument for match
joewilliams opened this issue ยท 10 comments
I am attempting to use a match expression and running into an invalid argument
error I do not understand:
matchTable := c.AddTable(&nftables.Table{
Family: nftables.TableFamilyIPv4,
Name: "match",
})
matchChain := c.AddChain(&nftables.Chain{
Name: "match-filter",
Table: matchTable,
Type: nftables.ChainTypeFilter,
})
c.AddRule(&nftables.Rule{
Table: matchTable,
Chain: matchChain,
Exprs: []expr.Any{
&expr.Match{
Name: "addrtype",
Rev: 1,
Info: &xt.AddrType{
Source: unix.RTN_LOCAL,
},
},
},
})
if err := c.Flush(); err != nil {
log.Fatalf("nftables.Flush() failed: %v", err)
}
The above code results in
2022/09/23 02:21:07 nftables.Flush() failed: conn.Receive: netlink receive: invalid argument
I am pretty sure this is equivalent to something like iptables -A match-filter -m addrtype --src-type LOCAL
which does work.
cc @thediveo since I think they worked on this most recently.
Thanks!
I think I may have found the problem with this. Attempting to do the equivalent with raw netlink messages I was able to reproduce the same invalid argument error. It seems to stem from the attributes when creating rules.
{Type: unix.NLA_F_NESTED | unix.NFTA_RULE_EXPRESSIONS, Data: cc.marshalAttr(exprAttrs)},
https://github.com/google/nftables/blob/main/rule.go#L133
In the case of most rules the above works fine but in the case of a match
I believe unix.NLA_F_NESTED
needs to be unix.NFTA_MATCH_NAME
. In my test code switching to unix.NFTA_MATCH_NAME
the invalid argument error goes away.
I would generally recommend using nft
when exploring nftables, instead of using this package. That should give you better error messages and input validation, and more recipes to look at online. Afterwards, you can translate what nft
does into Go.
Iโll close this issue since it sounds like you figured out the issue.
@stapelberg unless I am mistaken adding a match expression rule is broken due to the above NLA_F_NESTED
vs NFTA_MATCH_NAME
problem in this library itself.
Hi @joewilliams,
I am not that familiar with match rules so I might be wrong, but I would suggest trying another approach before making any changes to the current match implementation.
I have successfully replicated and resolved your issue with the following code:
# nft flush ruleset
# cat main.go
package main
import (
"fmt"
"github.com/google/nftables/binaryutil"
"github.com/google/nftables/expr"
"github.com/google/nftables"
"golang.org/x/sys/unix"
)
func main() {
c, _ := nftables.New()
matchTable := c.AddTable(&nftables.Table{
Family: nftables.TableFamilyIPv4,
Name: "filter",
})
matchChain := c.AddChain(&nftables.Chain{
Name: "forward",
Table: matchTable,
Type: nftables.ChainTypeFilter,
})
c.AddRule(&nftables.Rule{
Table: matchTable,
Chain: matchChain,
Exprs: []expr.Any{
&expr.Fib{
Register: 1,
ResultADDRTYPE: true,
FlagSADDR: true,
},
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: binaryutil.NativeEndian.PutUint32(unix.RTN_LOCAL),
},
},
})
if err := c.Flush(); err != nil {
fmt.Printf("nftables.Flush() failed: %v", err)
}
}
# go run main.go
# nft list ruleset
table ip filter {
chain forward {
fib saddr type local
}
}
Here are some translation examples that I have found which might help you in constructing other nftables rule expressions: https://git.netfilter.org/iptables/tree/extensions/libxt_addrtype.txlate?id=d1aa01483b5cac8c70c9385033e60efd7a744e1f.
I hope this resolves your issue.
Thanks @turekt, it's very nice of you to work up an example! Unfortunately I need to specifically use match rules for my use case.
I have also tried a UDP based match and get the same invalid argument
error.
&expr.Match{
Name: "udp",
Rev: 0,
Info: &xt.Udp{
DstPorts: [2]uint16{uint16(50), uint16(60)},
},
},
&expr.Verdict{
Kind: expr.VerdictDrop,
},
2022/09/30 16:11:49 nftables.Flush() failed: conn.Receive: netlink receive: invalid argument
This cooresponds with:
Sep 30 16:11:49 dev kernel: [552795.419956] x_tables: ip_tables: udp match: only valid for protocol 17
Using my own fork changing NLA_F_NESTED to NFTA_MATCH_NAME on https://github.com/google/nftables/blob/main/rule.go#L133 fixes the error but unfortunately the rule seems "blank". Maybe because nft
doesn't know how to print it?
The kernel log error message goes away as well with the above change.
Even though the error goes away the traffic is not blocked as a result of the above rule. I would expect outgoing DNS traffic to stop.
What GetRules
gets back:
rule 0: &nftables.Rule{Table:(*nftables.Table)(0x40000b2000), Chain:(*nftables.Chain)(0x400009a140), Position:0x0, Handle:0x2, Flags:0x0, Exprs:[]expr.Any{}, UserData:[]uint8(nil)}
$ sudo nft --debug all list ruleset
Entering state 0
Reducing stack by rule 1 (line 808):
-> $$ = nterm input (: )
Stack now 0
Entering state 1
Reading a token: --accepting rule at line 292 ("list")
Next token is token "list" (: )
Shifting token "list" (: )
Entering state 26
Reading a token: --accepting rule at line 653 (" ")
--accepting rule at line 266 ("ruleset")
Next token is token "ruleset" (: )
Shifting token "ruleset" (: )
Entering state 114
Reading a token: --accepting rule at line 647 ("
")
Next token is token "newline" (: )
Reducing stack by rule 331 (line 2350):
-> $$ = nterm ruleset_spec (: )
Stack now 0 1 26 114
Entering state 444
Reducing stack by rule 114 (line 1300):
$1 = token "ruleset" (: )
$2 = nterm ruleset_spec (: )
-> $$ = nterm list_cmd (: )
Stack now 0 1 26
Entering state 129
Reducing stack by rule 23 (line 904):
$1 = token "list" (: )
$2 = nterm list_cmd (: )
-> $$ = nterm base_cmd (: )
Stack now 0 1
Entering state 46
Next token is token "newline" (: )
Shifting token "newline" (: )
Entering state 4
Reducing stack by rule 3 (line 818):
$1 = token "newline" (: )
-> $$ = nterm stmt_separator (: )
Stack now 0 1 46
Entering state 275
Reducing stack by rule 14 (line 876):
$1 = nterm base_cmd (: )
$2 = nterm stmt_separator (: )
-> $$ = nterm line (: )
Stack now 0 1
Entering state 45
Reducing stack by rule 2 (line 809):
$1 = nterm input (: )
$2 = nterm line (: )
-> $$ = nterm input (: )
Stack now 0
Entering state 1
Reading a token: --(end of buffer or a NUL)
--EOF (start condition 0)
Now at end of input.
Shifting token "end of file" (: )
Entering state 2
Stack now 0 1 2
Cleanup: popping token "end of file" (: )
Cleanup: popping nterm input (: )
---------------- ------------------
| 0000000020 | | message length |
| 02576 | R--- | | type | flags |
| 0000000000 | | sequence number|
| 0000000000 | | port ID |
---------------- ------------------
| 00 00 00 00 | | extra header |
---------------- ------------------
---------------- ------------------
| 0000000020 | | message length |
| 02561 | R--- | | type | flags |
| 0000000000 | | sequence number|
| 0000000000 | | port ID |
---------------- ------------------
| 00 00 00 00 | | extra header |
---------------- ------------------
---------------- ------------------
| 0000000036 | | message length |
| 02570 | R--- | | type | flags |
| 0000000000 | | sequence number|
| 0000000000 | | port ID |
---------------- ------------------
| 02 00 00 00 | | extra header |
|00015|--|00001| |len |flags| type|
| 6d 61 74 63 | | data | m a t c
| 68 74 61 62 | | data | h t a b
| 6c 65 00 00 | | data | l e
---------------- ------------------
---------------- ------------------
| 0000000020 | | message length |
| 02564 | R--- | | type | flags |
| 0000000000 | | sequence number|
| 0000000000 | | port ID |
---------------- ------------------
| 02 00 00 00 | | extra header |
---------------- ------------------
---------------- ------------------
| 0000000036 | | message length |
| 02583 | R--- | | type | flags |
| 0000000000 | | sequence number|
| 0000000000 | | port ID |
---------------- ------------------
| 02 00 00 00 | | extra header |
|00015|--|00001| |len |flags| type|
| 6d 61 74 63 | | data | m a t c
| 68 74 61 62 | | data | h t a b
| 6c 65 00 e0 | | data | l e
---------------- ------------------
---------------- ------------------
| 0000000036 | | message length |
| 02579 | R--- | | type | flags |
| 0000000000 | | sequence number|
| 0000000000 | | port ID |
---------------- ------------------
| 02 00 00 00 | | extra header |
|00015|--|00001| |len |flags| type|
| 6d 61 74 63 | | data | m a t c
| 68 74 61 62 | | data | h t a b
| 6c 65 00 00 | | data | l e
---------------- ------------------
---------------- ------------------
| 0000000020 | | message length |
| 02567 | R--- | | type | flags |
| 0000000000 | | sequence number|
| 0000000000 | | port ID |
---------------- ------------------
| 02 00 00 00 | | extra header |
---------------- ------------------
ip matchtable output 2
update network layer protocol context:
link layer : none
network layer : ip <-
transport layer : none
---------------- ------------------
| 0000000020 | | message length |
| 02576 | R--- | | type | flags |
| 0000000000 | | sequence number|
| 0000000000 | | port ID |
---------------- ------------------
| 00 00 00 00 | | extra header |
---------------- ------------------
Evaluate list
list ruleset
^^^^^^^^^^^^^
table ip matchtable {
chain output {
type filter hook output priority -2147483648; policy accept;
}
}
Trying this from a different angle, I used iptables-nft
to create a conntrack match which is applied as a match
in nftables.
sudo iptables-nft -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
This seems to come back correctly:
rule 0: &nftables.Rule{Table:(*nftables.Table)(0x40000b4000), Chain:(*nftables.Chain)(0x400009a100), Position:0x0, Handle:0x4, Flags:0x0, Exprs:[]expr.Any{(*expr.Match)(0x4000086930), (*expr.Counter)(0x40000a0420), (*expr.Verdict)(0x400008e168)}, UserData:[]uint8(nil)}
--- expr: 0: &expr.Match{Name:"conntrack", Rev:0x3, Info:(*xt.ConntrackMtinfo3)(0x40000921e0)}
--- expr: 1: &expr.Counter{Bytes:0x0, Packets:0x0}
--- expr: 2: &expr.Verdict{Kind:1, Chain:""}
So my guess is that some match types are encoded/decoded correctly but others are not.
I would expect the UDP match above to come back from GetRules
the same as this conntrack one.
I am not sure what's up with the UDP match support but it seems like my original problem was using Rev:1
and xt.AddrType
rather than xt.AddrType1
.
The xt
parts are extremely nft version dependent. The nftables project only regards the nft
tool as the official tool, but not the netlink messages. Personally, I find this a bad decision, as it forces devs to parse their special format that doesn't seem to be properly documented. Alas, unfortunately the xt
territory is something where nftables do as they like and that's probably the reason for several different variants of basically the same data structures.
@thediveo thanks for the info!