Page MenuHomeFreeBSD

New netgraph nodetype: ng_antispoof
AbandonedPublic

Authored by markus_stoffdv.at on Sep 13 2020, 9:38 AM.
Referenced Files
Unknown Object (File)
Sep 26 2024, 2:29 AM
Unknown Object (File)
Sep 13 2024, 9:41 PM
Unknown Object (File)
Sep 3 2024, 10:23 PM
Unknown Object (File)
Aug 18 2024, 8:49 PM
Unknown Object (File)
Aug 14 2024, 5:46 PM
Unknown Object (File)
Aug 13 2024, 11:02 AM
Unknown Object (File)
Aug 13 2024, 11:02 AM
Unknown Object (File)
Aug 10 2024, 2:35 AM

Details

Reviewers
donner
Group Reviewers
network
Summary

This introduces a new netgraph node type that prevents the upstream network from spoofing ethernet and IP addresses. It is called 'ng_antispoof' (name is open for debate, of course).

What it does:

It validates the upstream address of each packet against a set of rules. If at least one rule matches, the packet is passed through, otherwise it is blocked.

Each rule consists of an ethernet address and an IP or IPv6 address (in a simplified point of view).

How it works:

Each node provides three hooks:

  • filter: Upstream node to be protected (e.g. a jail, a VM, ...).
  • downstream: Downstream node (e.g. a bridge device, the internet, ...).
  • nomatch: Useful for debugging with tcpdump(1). If connected, blocked traffic is forwarded on this hook instead of being discarded. This is output only, traffic arriving on this hook is immediately discarded.
      ___               +----------------+
 __.(     ).__          |                |
( downstream  )<------->|                |         
 .._ ( ) _ _..          |                |         I==========I
                        |  ng_antispoof  |<------->I  filter  I
                        |                |         I==========I
  nomatch <-------------|                |         
                        |                |
                        +----------------+

Use Case:

Prevent VNET jails from spoofing IP/MAC addresses while using pf(4) as the firewall on the host system.

Example:

Given a virtual network interface host_if on the host system and jail_if in a VNET enabled jail, restrict the jail to the IP 192.168.1.42 with the MAC 1a:00:de:ad:be:ef:

# Create ng_antispoof node
ngctl mkpeer host_if: antispoof ether downstream
ngctl name host_if:ether as

# Add filter rule
ngctl msg as: addinet '{ ether=1a:00:de:ad:be:ef ip_addr=192.168.1.42 }'

# Plug the jail
ngctl connect as: jail_if: filter ether

More Details:

Currently filter rules for IP and IPv6 address types can be created. The maximum number of rules is 65535 (UINT16_MAX).

# For IPv4, subnets can be provided in CIDR or netmask notation
ngctl msg as: addinet '{ ether=0a:00:de:ad:be:ef ip_addr=192.168.1.42 }'
ngctl msg as: addinet '{ ether=0a:00:de:ad:be:ef ip_addr=192.168.1.0/24 }'
ngctl msg as: addinet '{ ether=0a:00:de:ad:be:ef ip_addr=192.168.1.0 ip_mask=255.255.255.0 }'

# For IPv6, subnets can be provided in CIDR notation
ngctl msg as: addinet6 '{ ether=0a:00:de:ad:be:ef ip6_addr=::1 }'
ngctl msg as: addinet6 '{ ether=0a:00:de:ad:be:ef ip6_addr=1:2::/32 }'

Open Questions:

  • Is it okay to add the IPv6 parsing type to ng_parse so it can be reused by others?
  • Should the IPv4 and IPv6 prefix types for CIDR notation also go into ng_parse, or is this too custom already?
  • What is the idiomatic way to provide tests for kernel modules?
  • Should VLAN tags be filtered as well (currently all VLAN tags are ignored)?
    • Filtering only the outermost tag would probably be the sensible approach here.
    • Should a single rule filter a single VID, a range of VIDs or a list (of ranges) of VIDs?
      • Filtering a single VID would be possible without any additional overhead (just 4 more bytes on the same cache line).
      • Filtering a range would probably impose very little overhead (6 more bytes on the same cache line and an additional evaluation).
      • Filtering a list would introduce additional complexity and probably fetch data (the list of allowed VIDs for the current rule) from main memory.
Test Plan

Sample run of my current test suite:

Running allowsSingleMacAndIp ...                           SUCCESS   (  2 seconds)
Running allowsSingleMacAndIpRange ...                      SUCCESS   (  2 seconds)
Running preventsMacSpoofing ...                            SUCCESS   (  2 seconds)
Running blocksAccessToSpoofedMac ...                       SUCCESS   (  3 seconds)
Running preventsIpSpoofing ...                             SUCCESS   (  2 seconds)
Running blocksAccessToSpoofedIp ...                        SUCCESS   (  3 seconds)
Running preventsIpSpoofingViaArp ...                       SUCCESS   (  2 seconds)
Running blocksAccessToSpoofedIpViaArp ...                  SUCCESS   (  3 seconds)
Running supportsVlanTagging ...                            SUCCESS   (  2 seconds)
Running supportsQinQTagging ...                            SUCCESS   (  2 seconds)
Running createsAndGetsRules:
    Running getsEmptyRules ...                                 SUCCESS   (  0 seconds)
    Running doesNotDisplayDefaultValues ...                    SUCCESS   (  0 seconds)
    Running returnsRulesInCanonicalForm ...                    SUCCESS   (  0 seconds)
    Running resetsRules ...                                    SUCCESS   (  0 seconds)
    Running addsInetAddress ...                                SUCCESS   (  0 seconds)
    Running addsInetPrefixedSubnet ...                         SUCCESS   (  0 seconds)
    Running addsInetMaskedSubnet ...                           SUCCESS   (  0 seconds)
    Running addsInet6Address ...                               SUCCESS   (  0 seconds)
    Running addsInet6PrefixedSubnet ...                        SUCCESS   (  0 seconds)
    Running addsMultipleRules ...                              SUCCESS   (  0 seconds)
    SUCCESS (  0 seconds)

Total Time:  23 seconds
SUCCESS

Diff Detail

Repository
rS FreeBSD src repository - subversion
Lint
Lint Skipped
Unit
Tests Skipped

Event Timeline

donner requested changes to this revision.Sep 13 2020, 1:31 PM

Can you please explain, what your node has in favor of ng_bpf (which is scripted with an arbitrary tcpdump expression)?
Currently I see a lot of parsing complexity moving into the kernel, which lacks a lot of expressiveness.

This revision now requires changes to proceed.Sep 13 2020, 1:31 PM

To be honest, I did not recognize ng_bpf would do that. I will reevaluate my requirements based on that.

I had another look into ng_bpf and I still do not understand how to express the required directionality in a tcpdump filter.

For example:

# MAC and IP not necessarily on the same side
tcpdump -p ether host 1a:00:de:ad:be:ef and host 192.168.1.42

# Make sure MAC and IP match on the same side
# But still no control over *which* side this is
tcpdump -p (ether src 1a:00:de:ad:be:ef and src 192.168.1.42) or (ether dst 1a:00:de:ad:be:ef and dst 192.168.1.42)

So all I manage to do is to say: either you are XYZ or you are talking to XYZ, while what I really want to say is: you have to be XYZ.

However, maybe there is a better way to achieve this than with what I've offered so far. If so, please let me know. I've no problem with throwing everything away and starting all over again.

I had another look into ng_bpf and I still do not understand how to express the required directionality in a tcpdump filter.

Any input to the ng_bpf node is evaluated by a hook specific bpf program. If the program returns 0, the paket is forwarded to the hook defined by "ifNotMatched". Otherwise the packet is truncated to the return value of the bpf program and forwarded to the hook defined by "ifMatch".

  1. Make sure MAC and IP match on the same side
  2. But still no control over *which* side this is

tcpdump -p (ether src 1a:00:de:ad:be:ef and src 192.168.1.42) or (ether dst 1a:00:de:ad:be:ef and dst 192.168.1.42)

Assume the following setup to filter the traffic from and to the physical interface:

ng_ether -lower---down- ng_bpf -up------link0- ng_bridge
                              \-debug---ether- ng_eiface

Then you may want to apply "ether src 1a:00:de:ad:be:ef and src 192.168.1.42" incoming from the physical interface to the bridge, and "ether dst 1a:00:de:ad:be:ef and dst 192.168.1.42" from the bridge to the physical interface.

So you feed the example script of https://www.freebsd.org/cgi/man.cgi?query=ng_bpf&apropos=0&sektion=4&manpath=FreeBSD+12.1-RELEASE&arch=default&format=html with the following parameters:

PATTERN="ether src 1a:00:de:ad:be:ef and src 192.168.1.42"
NODEPATH="ng_bpf:"
INHOOK="down"
MATCHHOOK="up"
NOTMATCHHOOK="debug"

PATTERN="ether dst 1a:00:de:ad:be:ef and dst 192.168.1.4280"
NODEPATH="ng_bpf:"
INHOOK="up"
MATCHHOOK="down"
NOTMATCHHOOK="debug"

Of course, you may choose different debug hooks, if you like to retain the direction.

So all I manage to do is to say: either you are XYZ or you are talking to XYZ, while what I really want to say is: you have to be XYZ.

Does this match your requirements?

To be honest, I did not recognize ng_bpf would do that. I will reevaluate my requirements based on that.

I appreciate you efforts, solving your problem with a good understanding of netgraph.

Netgraph is a framework with a couple of nodes, which seemed necessary for somebody sometimes. Most nodes are never finished, work only in a specific environment, and miss a general roadmap.

So you might stumble over the "ipfw" parameters of ng_bridge and wonder why they are never implemented and it's gone now. It's a common approach to define your own netgraph node to solve (only) your current problem.

So you are on a good way, thank you.

In D26420#587703, @lutz_donnerhacke.de wrote:

Does this match your requirements?

Yes, it absolutely does. Thank you very much!

I will just submit a patch for ng_parse.c (the one at line 963), which fixes a parse error (eats one character to much).

Do you think it would be useful to add an example for this use case to the ng_bpf(4) man page?

In D26420#587703, @lutz_donnerhacke.de wrote:

Does this match your requirements?

Yes, it absolutely does. Thank you very much!

Fine. Than please abandon this review.

I will just submit a patch for ng_parse.c (the one at line 963), which fixes a parse error (eats one character to much).

Any fix is welcomed. Please open a new review for this, so that the review process is simplified.

Do you think it would be useful to add an example for this use case to the ng_bpf(4) man page?

Yes, please do. Anything which is an improvement may help.