Closed laduke closed 8 months ago
Trying write a simpler the explanation of the impact of this:
If your rule uses a not
modifier, and one of the affected matches (list above), then the rule evaluator will match on non-IP ethertypes.
The list of ethertypes (that we handle) is: ipv4, arp, ipv6, wol (wake on LAN), rarp, atalk, aarp, ipx_a, ipx_b complete list Most of these are rare, and the default ruleset blocks most ethertypes:
drop
not ethertype ipv4
and not ethertype arp
and not ethertype ipv6
;
Some rules may be blocking or allowing ARP inadvertently. Blocking ARP will make IPv4 traffic very unreliable. Accidentally accepting all ARP is probably fine.
The ipsrc/dest rules could be problematic on dual stack networks. But if you have a dual stack network, why would use ipsrc/dest? You'd have to handle every node twice or more.
Summary of each rule:
accept/drop no ipsrc/ipdest <ipv4 address>
Packets with ethertypes other than IPv4 will match.
This includes IPv6 traffic and the ethertypes listed above.
accept/drop not ipsrc/ipdest <ipv6 address>
Similarly to above, will match IPv4 packets, and non-IP packets.
accept/drop not dport/sport <port number>
It matches on non-ip ethertypes.
accept/drop not ipprotocol <proto>
Also will match on non-IP ethertypes
accept/drop not icmp <type> <code>
Is a little more complicated. It'll match on non-IP ethertypes like the rest, but it's also looking into the frame data in a way I don't know off the top of my head.
It might end up matching on IP packets that aren't ICMP packets.
accept/drop not iptos
Is the same as the others. Matches on non-ipv4/v6 packets. I doubt anyone is using an iptos
rule.
No one on my.zerotier.com is.
Hello
This is one issue in two parts. It all started with another attempt at figuring out why tag-based rulesets sorta mostly work but then don't in confusing ways. When we finally figured that out, we noticed a more of the same issue with some non-tag rules/matches.
Related: #1495 There are many other's in the discussion board and internal ticket tracker, etc...
Old context
There have been tickets/complaints/confusions over the years with tag rules. We had been suggesting putting
accept ethertype arp
at the top of the rule set to work around it. We weren't 100% sure why this helped.We think we finally understand.
accept ethertype arp
lets arp packets flow before checking tags, and so tag values are exchanged between peers during the arp request and repsonse. Then, both peers have knowledge of each other's tag values and can evaluate all other traffic without any special casing.Consider this tag based ruleset
tand is bitwise AND
If both members have the "foo" bit set, they can talk to each other. If either or both don't have the bit, they can't talk.
Now consider this code in the rule evaluator
Open up the code to get a little more context. We're in the tag processing section of the rule evaluator.
Forcing
thisRuleMatches = 1
causes the rule evalutor to match and stop ataccept tand role 1
, even though we don't know the recipient's tags yet.Makes sense.
Otherwise
accept tand role 1
wouldn't match, and the evaluator would go to the next rules. The only rule left isdrop
. With out thethisRuleMatches = 1
special case, we wouldn't be able to talk.Now consider this slightly different ruleset
It doesn't the same thing, but with inverted logic.
What happens when we set
thisRuleMatches = 1
here?drop tand role 0;
matches, and we never get toaccept
.We can never send a packet! So we can never send our tags to each other. We can never talk.
Not too hard to fix
If we can keep a little state we can skip "drops" caused by missing remote tags.
We think this is OK security-wise and matches the original intention. The remote node will still do the right thing, drop the packet if needed.
If the rules were something like this:
drop tand role 0;
will get skipped by the sender. If it's a sctp packet, it'll get stopped at the sender. Otherwise it'll get sent, then the recipient will evaluate the rules again -with all the needed tag information.This is what we tried so far anyways. There may be other solutions.
But wait there's more.
Rules can be inverted with the
not
keyword.When we get to the end of the evaluation of a rule, we check if the
not
bit is set, and invert the match.This is XOR of thisRuleMatches and the NOT bit of the rule.
So
accept not tand role 0;
doesn't match, and we fall through todrop
.We can't talk.
So we think we need to use something like:
There many cases, not just in the tags processing, where we hard code thisRuleMatches to either true or false:
Which we think if we're in a rule with a
not
modifier, it will be wrong.We implemented both of the mentioned changes and tag based rulesets work better.
Test cases
Here are some of the rulesets we've tested:
Bugs in other types of matches
This leads us to bugs in other matches
ipsrc <ipv4 address>
gets evaluated inZT_NETWORK_RULE_MATCH_IPV4_DEST
All IPv6 traffic should work with this rule, we think. It's not IPv4 so it's intended to be skipped by the
thisRuleMatches = 0
. But because it's anot
rule, the 0 gets inverted.accept ethertype arp;
is needed for the desired IPv4 traffic to work as well.Another example:
Should this accept non-ipv4 traffic? It does... ping6 and arp (conveniently) work. We think the intention is that
not ipsrc
only match on ipv4 traffic.One more example
sport/dport consider only ipv4 or 6 ethertypes. So this rule (maybe conveniently) accepts ARP. Pinging works on this network:
But ping does not work here:
Others
There are a few other hard coded
thisRuleMatches = 0;
branches in the evaluater that might cause trouble, we just haven't thought through the rest of them.List of possibly affected matches
Possibly this only applies when used with a
not
modifier.In rules language the affected matches are:
In c++ they are:
A branch with a potential fix
Using this issue for discussion for now, but will make a PR to discuss code eventually
https://github.com/zerotier/ZeroTierOne/tree/tl-tags-3
Breaking changes
Fixing this is a "breaking" change. Though for probably a small amount of users. They might have to adjust rule sets on their networks if any clients get updated to a fixed version. We can dig through the Central database for rulesets that use the above rules to get a better idea.
It might not be breaking to fix only the Tags portion of this. It would just make an inconsistent behavior more consistent. Nothing that is consistently blocked would become allowed or vice versa.