New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
util/linuxfw: add nftable support for linux router #8555
util/linuxfw: add nftable support for linux router #8555
Conversation
661c6cb
to
2a47cc8
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A first pass, will need other eyes on too!
nftables package should work in most cases, the only case wouldn't work is that system has security policy (say using SElinux) that prevents user space program to work with netlink api but still supports system utilities like nft to function. Since nftables rely on golang.org/x/sys/unix, it should compile even when netlink header is missing. The solution I can think of are :
I'm not sure it if worth talking about this case, since it's kind of like an edge case that usually wouldn't happen? Tho I do recall that @raggi mentioned to me there are distros only giveing us access to nft but not netlink api. I didn't solve this problem when I implemented this solution because we thought the nftables package would solve this case, but I did some further investigation today and it doesn't seem like the case. We might need @raggi to provide further information |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Luv it
[xxx@tslptp:~]$ sudo nft list ruleset
table ip filter {
chain INPUT {
type filter hook input priority filter; policy accept;
counter packets 4578 bytes 9213931 jump ts-input
}
chain FORWARD {
type filter hook forward priority filter; policy accept;
counter packets 0 bytes 0 jump ts-forward
}
chain ts-input {
iifname "lo" ip saddr 100.82.9.85 counter packets 0 bytes 0 accept
iifname != "tailscale0" ip saddr 100.115.92.0/23 counter packets 0 bytes 0 return
iifname != "tailscale0" ip saddr 100.64.0.0/10 counter packets 0 bytes 0 drop
}
chain ts-forward {
iifname "tailscale0" counter packets 0 bytes 0 meta mark set mark and 0xff00ffff xor 0x40000
meta mark & 0x00ff0000 == 0x00040000 counter packets 0 bytes 0 accept
oifname "tailscale0" ip saddr 100.64.0.0/10 counter packets 0 bytes 0 drop
oifname "tailscale0" counter packets 0 bytes 0 accept
}
}
table ip6 filter {
chain INPUT {
type filter hook input priority filter; policy accept;
counter packets 1 bytes 88 jump ts-input
}
chain FORWARD {
type filter hook forward priority filter; policy accept;
counter packets 0 bytes 0 jump ts-forward
}
chain ts-input {
iifname "lo" ip6 saddr fd7a:115c:a1e0:ab12:4843:cd96:6252:955 counter packets 0 bytes 0 accept
}
chain ts-forward {
iifname "tailscale0" counter packets 0 bytes 0 meta mark set mark and 0xff00ffff xor 0x40000
meta mark & 0x00ff0000 == 0x00040000 counter packets 0 bytes 0 accept
oifname "tailscale0" counter packets 0 bytes 0 accept
}
}
table ip nat {
chain POSTROUTING {
type nat hook postrouting priority srcnat; policy accept;
counter packets 423 bytes 90219 jump ts-postrouting
}
chain ts-postrouting {
meta mark & 0x00ff0000 == 0x00040000 counter packets 0 bytes 0 masquerade
}
}
table ip6 nat {
chain POSTROUTING {
type nat hook postrouting priority srcnat; policy accept;
counter packets 0 bytes 0 jump ts-postrouting
}
chain ts-postrouting {
meta mark & 0x00ff0000 == 0x00040000 counter packets 0 bytes 0 masquerade
}
}
@danderson fyi - is this something you want to take a pass at?
@twitchyliquid64 I don't need to take a pass at this, if it implements the same behavior as the iptables implementation then it's good to go. I also frankly don't know nftables well enough to find subtle issues. Do you want additional scrutiny? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, what platforms has this been tested on?
Gotcha - I'm happy, and ive done some local testing too. I'm feeling good about this going in after 1.46 is cut (for 1.48). We should also try and get it on some subnet routers / exit nodes. |
b748be3
to
f348f17
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking really good Kevin! I've not had a chance to test this out locally yet, but I just did a pass through, some comments included. The key thing I would like to understand is how evolution will work as we change the rules
|
||
// If TestDial is set, we are running in test mode and we should not | ||
// find rule because header will mismatch. | ||
if conn.TestDial == nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Related to the comment above on findRule around the longer term lifetime of matching and upgrading these rules, related considerations show up here.
Perhaps what we want to find is a rule that matches a pattern, rather than exact expression list. We might for example look for a rule that matches the core spirit of the operation, rather than an exact expression list. If we do that it may also avoid the need to relax conditions or skip over implementation in the tests. Did you try something like this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Relatedly, is there an expression we can include that has no runtime effect, but acts as an explicit marker that demonstrates that it is a rule we inserted?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this topic Adrian brought up was the answer for the marker. We can do this by adding comment to rule but don't have a simple way to do this.
|
||
// nfdump returns a hexdump of 4 bytes per line (like nft --debug=all), allowing | ||
// users to make sense of large byte literals more easily. | ||
func nfdump(b []byte) string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You might like https://pkg.go.dev/encoding/hex#Dump which does most of what I think you want here, it takes a []byte and gives you a string containing a hexdump of the format:
00000000 47 6f 20 69 73 20 61 6e 20 6f 70 65 6e 20 73 6f |Go is an open so|
00000010 75 72 63 65 20 70 72 6f 67 72 61 6d 6d 69 6e 67 |urce programming|
00000020 20 6c 61 6e 67 75 61 67 65 2e | language.|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tried Hex.Dump, the fact that I can't change number of bytes in single like makes me don't like it. If only one bit goes wrong it takes much more effort to find the mismatch (at least for me)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like there are still some details regarding rule life cycle that we might need to work out further but I also feel like it's probably fine to give this a try for now (especially since it requires an env var to opt in) and we can make incremental improvements later. I suspect it's already more robust than the iptables path.
util/linuxfw/nftables_runner_test.go
Outdated
} | ||
|
||
conn, ns := newSysConn(t) | ||
defer cleanupSysConn(t, ns) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consider having newSysConn
call t.Cleanup(cleanupSysConn)
(which will require reworking how t
and ns
are stored a little, maybe the cleanup would need to be a closure) to make it a bit easier for test writers
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, and now had a chance to test on my router and it's working there too!
Please squash when you submit! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great job Kevin!
This commit adds nftable rule injection for tailscaled. If tailscaled is started with envknob TS_DEBUG_USE_NETLINK_NFTABLES = true, the router will use nftables to manage firewall rules. Updates: #391 Signed-off-by: KevinLiang10 <kevinliang@tailscale.com>
f5ef839
to
bbb87f7
Compare
…le TS_DEBUG_USE_NETLINK_NFTABLES in tailscaled that was introduced in tailscale#8555 Fixes tailscale#8111, tailscale#8733
…le TS_DEBUG_USE_NETLINK_NFTABLES in tailscaled that was introduced in tailscale#8555 Fixes tailscale#8111, tailscale#8733
…le TS_DEBUG_USE_NETLINK_NFTABLES in tailscaled that was introduced in tailscale#8555 Fixes tailscale#8111, tailscale#8733
Add flag to k8s-operator to enable TS_DEBUG_USE_NETLINK_NFTABLES in tailscaled that was introduced in tailscale#8555 Fixes tailscale#8111, tailscale#8733
Add flag to k8s-operator to enable TS_DEBUG_USE_NETLINK_NFTABLES in tailscaled that was introduced in tailscale#8555 Fixes tailscale#8111, tailscale#8733 Signed-off-by: James Clarke <james@clarkezone.net>
We were previously using the netlink API to see if there are chains/rules that already exist. This works fine in environments where there is either full nftable support or no support at all. However, we have identified certain environments which have partial nftable support and the only feasible way of detecting such an environment is to try to create some of the chains that we need. This adds a check to create a dummy postrouting chain which is immediately deleted. The goal of the check is to ensure we are able to use nftables and that it won't error out later. This check is only done in the path where we detected that the system has no nftable rules. Updates #5621 Updates #8555 Updates #8762 Signed-off-by: Maisem Ali <maisem@tailscale.com>
We were previously using the netlink API to see if there are chains/rules that already exist. This works fine in environments where there is either full nftable support or no support at all. However, we have identified certain environments which have partial nftable support and the only feasible way of detecting such an environment is to try to create some of the chains that we need. This adds a check to create a dummy postrouting chain which is immediately deleted. The goal of the check is to ensure we are able to use nftables and that it won't error out later. This check is only done in the path where we detected that the system has no nftable rules. Updates #5621 Updates #8555 Updates #8762 Signed-off-by: Maisem Ali <maisem@tailscale.com>
We were previously using the netlink API to see if there are chains/rules that already exist. This works fine in environments where there is either full nftable support or no support at all. However, we have identified certain environments which have partial nftable support and the only feasible way of detecting such an environment is to try to create some of the chains that we need. This adds a check to create a dummy postrouting chain which is immediately deleted. The goal of the check is to ensure we are able to use nftables and that it won't error out later. This check is only done in the path where we detected that the system has no nftable rules. Updates #5621 Updates #8555 Updates #8762 Signed-off-by: Maisem Ali <maisem@tailscale.com>
We were previously using the netlink API to see if there are chains/rules that already exist. This works fine in environments where there is either full nftable support or no support at all. However, we have identified certain environments which have partial nftable support and the only feasible way of detecting such an environment is to try to create some of the chains that we need. This adds a check to create a dummy postrouting chain which is immediately deleted. The goal of the check is to ensure we are able to use nftables and that it won't error out later. This check is only done in the path where we detected that the system has no preexisting nftable rules. Updates #5621 Updates #8555 Updates #8762
We were previously using the netlink API to see if there are chains/rules that already exist. This works fine in environments where there is either full nftable support or no support at all. However, we have identified certain environments which have partial nftable support and the only feasible way of detecting such an environment is to try to create some of the chains that we need. This adds a check to create a dummy postrouting chain which is immediately deleted. The goal of the check is to ensure we are able to use nftables and that it won't error out later. This check is only done in the path where we detected that the system has no preexisting nftable rules. Updates #5621 Updates #8555 Updates #8762 Signed-off-by: Maisem Ali <maisem@tailscale.com>
We were previously using the netlink API to see if there are chains/rules that already exist. This works fine in environments where there is either full nftable support or no support at all. However, we have identified certain environments which have partial nftable support and the only feasible way of detecting such an environment is to try to create some of the chains that we need. This adds a check to create a dummy postrouting chain which is immediately deleted. The goal of the check is to ensure we are able to use nftables and that it won't error out later. This check is only done in the path where we detected that the system has no preexisting nftable rules. Updates #5621 Updates #8555 Updates #8762 Signed-off-by: Maisem Ali <maisem@tailscale.com>
We were previously using the netlink API to see if there are chains/rules that already exist. This works fine in environments where there is either full nftable support or no support at all. However, we have identified certain environments which have partial nftable support and the only feasible way of detecting such an environment is to try to create some of the chains that we need. This adds a check to create a dummy postrouting chain which is immediately deleted. The goal of the check is to ensure we are able to use nftables and that it won't error out later. This check is only done in the path where we detected that the system has no preexisting nftable rules. Updates #5621 Updates #8555 Updates #8762 Signed-off-by: Maisem Ali <maisem@tailscale.com>
We were previously using the netlink API to see if there are chains/rules that already exist. This works fine in environments where there is either full nftable support or no support at all. However, we have identified certain environments which have partial nftable support and the only feasible way of detecting such an environment is to try to create some of the chains that we need. This adds a check to create a dummy postrouting chain which is immediately deleted. The goal of the check is to ensure we are able to use nftables and that it won't error out later. This check is only done in the path where we detected that the system has no preexisting nftable rules. Updates #5621 Updates #8555 Updates #8762 Signed-off-by: Maisem Ali <maisem@tailscale.com>
We were previously using the netlink API to see if there are chains/rules that already exist. This works fine in environments where there is either full nftable support or no support at all. However, we have identified certain environments which have partial nftable support and the only feasible way of detecting such an environment is to try to create some of the chains that we need. This adds a check to create a dummy postrouting chain which is immediately deleted. The goal of the check is to ensure we are able to use nftables and that it won't error out later. This check is only done in the path where we detected that the system has no preexisting nftable rules. Updates #5621 Updates #8555 Updates #8762 Signed-off-by: Maisem Ali <maisem@tailscale.com>
We were previously using the netlink API to see if there are chains/rules that already exist. This works fine in environments where there is either full nftable support or no support at all. However, we have identified certain environments which have partial nftable support and the only feasible way of detecting such an environment is to try to create some of the chains that we need. This adds a check to create a dummy postrouting chain which is immediately deleted. The goal of the check is to ensure we are able to use nftables and that it won't error out later. This check is only done in the path where we detected that the system has no preexisting nftable rules. Updates #5621 Updates #8555 Updates #8762 Signed-off-by: Maisem Ali <maisem@tailscale.com>
These tests were broken at HEAD. CI currently does not run these as root, will figure out how to do that in a followup. Updates tailscale#5621 Updates tailscale#8555 Updates tailscale#8762 Signed-off-by: Maisem Ali <maisem@tailscale.com> Signed-off-by: Alex Paguis <alex@windscribe.com>
We were previously using the netlink API to see if there are chains/rules that already exist. This works fine in environments where there is either full nftable support or no support at all. However, we have identified certain environments which have partial nftable support and the only feasible way of detecting such an environment is to try to create some of the chains that we need. This adds a check to create a dummy postrouting chain which is immediately deleted. The goal of the check is to ensure we are able to use nftables and that it won't error out later. This check is only done in the path where we detected that the system has no preexisting nftable rules. Updates tailscale#5621 Updates tailscale#8555 Updates tailscale#8762 Signed-off-by: Maisem Ali <maisem@tailscale.com> Signed-off-by: Alex Paguis <alex@windscribe.com>
Summary:
This change implements
netfilterRunner
interface for nftables manipulation as a low level nftables runner. This change allows manipulation to netfilter rules via nftables.Issue related
Updates: #391
Changes made
nftableRunner
that implementsnetfilterRunner
andnftable
as a container of thenftables.Table
abstraction.TS_DEBUG_USE_NETLINK_NFTABLES
Which controls using nftables or iptables.Thought Process
nftablesRunner
contains a single connection for all nftables operation. This design was for testing with customized connections.counter
expression for basic debug log purposes provided to user. If we want we can also addlog
expressions for logging in systemExpected Result
tailscaled should be manipulating rules using nftables when it's running with envknob
TS_DEBUG_USE_NETLINK_NFTABLES=true
set. We expect that the app works the exact same way as we use iptables, and also expect these rules to be present when we runsudo nft list ruleset
. Notice that the rule matchingiifname "lo*"
has local machine address as saddr, and the rules have the bytes displayed in little endian in the file paste file)As an example for IPv4 rules.
How to test
On linux environment do the following:
sudo TS_DEBUG_USE_NETLINK_NFTABLES=true ./tool/go run ./cmd/tailscaled
sudo nft list ruleset
to view the rules added.sudo ./tool/go run ./cmd/tailscale up --advertise-exit-node
, the way of allowing forward and exit node can be found here.Note
If we want to build then run, the env var should come with run, not build. So basically something like