{"id":5219,"date":"2020-11-20T19:55:38","date_gmt":"2020-11-20T18:55:38","guid":{"rendered":"https:\/\/rakhesh.com\/?p=5219"},"modified":"2020-11-20T19:55:38","modified_gmt":"2020-11-20T18:55:38","slug":"mullvad-and-tailscale-coexisting-or-hello-nftables","status":"publish","type":"post","link":"https:\/\/rakhesh.com\/linux-bsd\/mullvad-and-tailscale-coexisting-or-hello-nftables\/","title":{"rendered":"Mullvad and TailScale coexisting (or “Hello Nftables!”)"},"content":{"rendered":"

A while ago I had written a post on TailScale and WireGuard (and policy based routing)<\/a>. A reader (Doug Miller) wrote to say that he was using WireGuard via the Mullvad VPN app and whenever he started VPN TailScale would stop working. He was curious to know if I had encountered this.<\/p>\n

(Mullvad is the VPN provider I too use btw. I don’t use their app though, I use the WireGuard app with the config pointing to Mullvad).<\/em><\/p>\n

I was curious and so I installed Ubuntu 20.04 (which is what he was using) in a VM and took a look at things. Ubuntu use Nftables rather than IPtables and that was new to me. Everything else was similar to what I had blogged about earlier – the IProute2 stuff about policy based routing – and the issue here didn’t seem to be policy based routing related, rather it was to do with the firewall. When I’d delete the firewall rules added by the Mullvad client TailScale would work fine. (Moreover, when pinging the message was ping: sendmsg: operation not permitted<\/code> again indicating a firewall).<\/p>\n

The fix was simple eventually – add two rules to the rules created by Mullvad, allowing access to & from the tailscale interface. However, since I took a look at Nftables, and I am sure I’ll forget it in a few days, I wanted to jot down the commands here for future reference.<\/p>\n

What is Nftables?<\/h3>\n

For a quick intro on what Nftables is and how it compares to IPtables check out this article<\/a> from ungleich.ch. Also great references are the Arch Wiki <\/a>, Gentoo Wiki<\/a>, and to a lesser extent the Debian Wiki<\/a>. Also, nft(8)<\/code> manpage<\/a>.<\/p>\n

You may already be using Nftables<\/h3>\n

I hadn’t realized, but since I had some VMs running Debian Buster I was already using Nftables. But Buster only uses Nftables in the backend and users deal with it via an IPtables layer (thus you use the IPtables syntax) and that’s why I hadn’t noticed it. I remember reading about Nftables in the Debian IPtables Wiki page earlier but hadn’t paid much notice.<\/p>\n

The Syntax<\/h3>\n

For some reason the syntax of Nftables rules reminds me of OpenBSD’s pf. There’s a certain similarity, but it’s not the same. It’s very different from IPtables for sure and feels more like pf.<\/p>\n

For example here’s a pf rule to allow incoming SSH (port 22):<\/p>\n

pass in quick on egress proto tcp from any to egress port 22 <\/pre>\n

The equivalent in Nftables is:<\/p>\n

nft add rule ip filter input protocol tcp dport 22 accept<\/pre>\n

The slight difference is Nftables is adding this rule to a table called filter<\/code> (I can have other tables too, the name doesn’t matter) to its input<\/code> chain, while pf doesn’t have that concept. And what I typed above is the command to add a rule on the fly, while with pf the rule was in a file.<\/p>\n

Which brings us to another commonality. With pf I can save rules to \/etc\/pf.conf<\/code>; similarly in Nftables I can save rules to \/etc\/nftables.conf<\/code>. IPtables has no notion of saving and restoring rules (but you could do it in other ways using iptables-save<\/code> and iptables-restore<\/code>).<\/p>\n

View all rules<\/h3>\n

To see all your Nftables rules do:<\/p>\n

nft list ruleset<\/pre>\n

To save them do to a file do (the -s<\/code> removes any stateful info):<\/p>\n

nft -s list ruleset > ~\/somefile<\/pre>\n

Tables<\/h3>\n

Creating a table:<\/p>\n

# nft add table family_type table_name\r\nnft add table inet my_table<\/pre>\n

The family_type<\/code> can be ip<\/code>, ip6<\/code>, inet<\/code> (for both ip<\/code> & ip6<\/code>), arp<\/code>, or bridge<\/code>.<\/p>\n

Listing tables:<\/p>\n

nft list tables<\/pre>\n

Listing chains and rules in a table:<\/p>\n

# nft list table family_type table_name\r\nnft list table inet my_table<\/pre>\n

Delete table:<\/p>\n

# nft delete table family_type table_name\r\nnft delete table inet my_table<\/pre>\n

You can see the pattern… It extends to other things too.<\/p>\n

Chains<\/h3>\n

I am going to be lazy here.<\/p>\n

# Creating a chain\r\nnft create chain family_type table_name chain_name\r\n\r\n# Deleting a chain\r\nnft delete chain family_type table_name chain_name<\/pre>\n

Chains have a concept of “base” chains. This seems to be when you “hook” a chain to the networking stack. Then you have to specify a priority too, with lower number having higher priority. I think this is similar to how in IPtables you have the default chains (INPUT, OUTPUT, etc.) and you could have other chains you jump to. In Nftables there’s no default chains so you create a chain (of whatever name), define it as a default by hooking it up the networking stack, and that’s it. You can have multiple chains hooked to the networking stack and that’s where priority comes in.<\/p>\n

Here’s how you create a new chain and hook it etc.<\/p>\n

nft add chain family_type table_name chain_name '{ type chain_type hook hook_type priority priority_value ; }'<\/pre>\n

The chain_type<\/code> can be filter<\/code>, route<\/code>, or nat<\/code>. Of these route<\/code> can only be used in the output<\/code> hook (it’s used to do a route lookup in case a packet has changed after processing by this chain); and nat<\/code> can be in prerouting<\/code>, input<\/code>, output<\/code>, or postrouting<\/code>.<\/p>\n

The hook_type<\/code> can be prerouting<\/code>, input<\/code>, forward<\/code>, output<\/code>, or postrouting<\/code>.<\/p>\n

It’s all quite similar to IPtables.<\/p>\n

The default priority value for a filter<\/code> chain is 0. You can do negative numbers too to override something with priority 0. In the example below the chain of table2 has higher priority over table1:<\/p>\n

# create two tables\r\nnft add table inet table1\r\nnft add table inet table2\r\n\r\n# add chains\r\nnft add chain inet table1 filter '{type filter hook input priority 0; }'\r\nnft add chain inet table2 filter '{type filter hook input priority -1; }'<\/pre>\n

Negative priorities have some quirks I believe (see this PDF<\/a>). In the Mullvad case I actually tried using a negative priority chain to override the one created by Mullvad, and that didn’t seem to take effect (or I could have created the chain wrongly)… so I am unsure about this.<\/em><\/p>\n

You can edit chains too.<\/p>\n

Rules<\/h3>\n

First command adds a rule at the end of the existing list (or after handle_value<\/code> if specified). Second commands adds at the top of the list (or before handle_value<\/code>).<\/p>\n

nft add rule family_type table_name chain_name [handle handle_value] statement\r\n\r\nnft insert rule family_type table_name chain_name [handle handle_value] statement<\/pre>\n

The key thing here is statement<\/code>. These are things like:<\/p>\n

# accept from source IP address 127.0.0.1\r\nip saddr 127.0.0.1 accept \r\n\r\n# accept from incoming interface lan0\r\niifname \"lan0\" accept\r\n\r\n# accept from any incoming interface matching this wildcard\r\niifname \"tailscale*\" accept\r\n\r\n# SNAT\/ Masquerade on outgoing interface\r\noifname \"wan0\" masquerade\r\n\r\n# self explanatory due to the comments\r\niif != lo ip daddr 127.0.0.1\/8 counter drop comment \"drop connections to loopback not coming from loopback\"\r\nip protocol icmp counter accept comment \"accept all ICMP types\"\r\ntcp dport 22 counter accept comment \"accept SSH\"<\/pre>\n

Nice!<\/p>\n

Back to the Mullvad\/ TailScale issue<\/h3>\n

In the case of Mullvad it looks like the latest release of their agent (2020.7; previous one being 2020.6) has started adding tables to block traffic. (Doug, who reported this discovered it to be the case). Of these the table called mullvad<\/code> is the one that seemed to be blocking outgoing traffic. I could see in its rules that it was only allowing outgoing via its tunnel interface and so the fix was to just add allow rules for the TailScale interfaces. I did this to both the incoming and outgoing chains and thus all I did was the following:<\/p>\n

nft insert rule inet mullvad output oifname \"tailscale*\" accept\r\nnft insert rule inet mullvad input iifname \"tailscale*\" accept<\/pre>\n

I used oifname<\/code> and iifname<\/code> instead of oif<\/code> and iif<\/code> so I can do a wildcard on any tailscale<\/code> interfaces.<\/p>\n

I suppose there’s some way to run this once the Mullvad agent launches but I didn’t bother much as I don’t use the agent (or Ubuntu) so it wasn’t much relevant to me. Am glad I came across this though as I got to discover Nftables. Good to know that one.<\/p>\n

 <\/p>\n","protected":false},"excerpt":{"rendered":"

A while ago I had written a post on TailScale and WireGuard (and policy based routing). A reader (Doug Miller) wrote to say that he was using WireGuard via the Mullvad VPN app and whenever he started VPN TailScale would stop working. He was curious to know if I had encountered this. (Mullvad is the … Continue reading Mullvad and TailScale coexisting (or “Hello Nftables!”)<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","enabled":false}}},"categories":[76],"tags":[59,556,962,692,929,99,918],"jetpack_publicize_connections":[],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/rakhesh.com\/wp-json\/wp\/v2\/posts\/5219"}],"collection":[{"href":"https:\/\/rakhesh.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rakhesh.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/rakhesh.com\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/rakhesh.com\/wp-json\/wp\/v2\/comments?post=5219"}],"version-history":[{"count":0,"href":"https:\/\/rakhesh.com\/wp-json\/wp\/v2\/posts\/5219\/revisions"}],"wp:attachment":[{"href":"https:\/\/rakhesh.com\/wp-json\/wp\/v2\/media?parent=5219"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rakhesh.com\/wp-json\/wp\/v2\/categories?post=5219"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rakhesh.com\/wp-json\/wp\/v2\/tags?post=5219"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}