Page MenuHomeFreeBSD

Simplify simloop check in ether_output() / clarify ARP/ND behavior for duplicate lladdr
Needs ReviewPublic

Authored by melifaro on Nov 1 2015, 3:39 PM.

Details

Reviewers
None
Group Reviewers
network
Summary

Right now we have big "IFF_SIMPLEX" condition inside ether_output() which is responsible for
a) looping back copies for broadcast frames and
b) re-injecting back traffic to "our" mac address.

The goal of this review is to eliminate (b) to simplify code.

Given that the condition requires loop_copy to be non-zero (b) case is limited to AF_INET[6] families with unicast destinations (because source ether_shost would always be address of local interface).
(There are some nasty examples of lle w/ multicast macs (like Microsoft NLB), but this change does not seem to touch these).
Note that all lles for local IPv4/IPv6 addresses are marked with LLE_IFADDR flag on creation, so traffic to all these addresses are already handled by LLE_IFADDR check.
The only remaining case for (b) is existing lle with dst address of our system BUT without LLE_IFADDR flag (e.g. originated by other network node).

Such binding requests are discarded by ARP stack (see if_ether.c "match" label and "enaddr" check) because they were treated as locally-originated since the beginning. I was not able to find to find any guidelines on handling that in RFC 826 or RFC 5227.
ND's RFC 4861 also does not seem to restrict processing such bindings. Our stack accepts them as valid and installs appropriate records (see Test plan section). However, traffic to these entries flows back to input path via simloop() (which then turns in TTL-length cycle for all given packets (if forwarding is no) that can be considered as and attack).
My position is that we definitely don't want to cycle this traffic inside kernel so we either (1) put this to the wire (as done in the patch) or (2) add tunable like allow_local_mac ( as done for multicast: net.link.ether.inet.allow_multicast) in ARP/ND code.

The former is already implemented for BPF writes, and from code point of view it is "do not do anything special to handle this situation".

So, as the result we optimize TX part (no pf* call, no memcmp checks) and simplify future pseudo_AF_HDRCMPLT changes (no need to fake ethernet_header anymore).

Test Plan

Test scenario (HEAD, local addr IPv4/IPv6 case):

Steps:

  • add printf() to LLE_IFADDR check
  • add printf() to IFF_SIMPLEX unicast case
  • try to ping IPv4/IPv6 LL/global addresses on ethernet interface

Results:
IPv4: no match for both printfs (loopback route shortcuts the way)
IPv6: match for first check for both LL/global addresses (Hello, D3868)

Test scenario: HEAD, insert IPv6 GU lle with our local mac:

Host1 runs the script, host2 runs the ping.

#!/usr/local/bin/python2.7

from scapy.all import *

#interface we're using
SRC_IF="vtnet0"
# real local MAC
SRC_MAC="52:54:00:f1:46:81"
# SRC address we're answering
SRC_OUT6="2a02::5054:ff:1111:2222"
# lladdr of victim machine
DST_MAC="52:54:00:04:17:89"
# Victim machine addr (for unicast reply)
DST_OUT6="2a02::5054:ff:fe04:1789"

ip=IPv6(src=SRC_OUT6, dst=DST_OUT6)/ICMPv6ND_NA(tgt=SRC_OUT6, S=1)/ICMPv6NDOptDstLLAddr(lladdr=DST_MAC)
eth=Ether(src=SRC_MAC, dst=DST_MAC)/ip
sendp(eth, iface=SRC_IF)

host2: ping6 SRC_OUT6 (issues NS)
host1: python script.py (issues NA)
host2: sees NA, installs new ND record with lladdr==local addr
host2: sends ICMP6 which gets to if_simploop(), ip6_input() -> check forwarding -> drop
host2: sysctl net.inet6.ip6.forwarding=1
host2: sends ICMP6 which gets to if_simploop(), ip6_input() -> ip6_forward() -> ether_output() -> if_simloop() ... (till TTL expires)

Results:
packets towards external network node with our mac are either dropped (no forwarding) or cycled in the kernel until TTL expires

Test scenario (HEAD+patch, insert IPv6 GU lle with our local mac)

host2: ping6 SRC_OUT6 (issues NS)
host1: python script.py (issues NA)
host2: sees NA, installs new ND record with lladdr==local addr
host2: sends ICMP6 which gets to if_simploop(), ip6_input() -> push to the wire
host2: sysctl net.inet6.ip6.forwarding=1
host2: sends ICMP6 which gets to if_simploop(), ip6_input() -> push to the wire

Results:
packets towards external network node with our mac are pushed to the wire

Diff Detail

Repository
rS FreeBSD src repository - subversion
Lint
Lint OK
Unit
No Unit Test Coverage
Build Status
Buildable 992
Build 992: arc lint + arc unit

Event Timeline

melifaro retitled this revision from to Simplify simloop check in ether_output() / clarify ARP/ND behavior for mac conflict..
melifaro updated this object.
melifaro edited the test plan for this revision. (Show Details)
melifaro edited the test plan for this revision. (Show Details)
melifaro retitled this revision from Simplify simloop check in ether_output() / clarify ARP/ND behavior for mac conflict. to Simplify simloop check in ether_output() / clarify ARP/ND behavior for duplicate lladdr.Nov 1 2015, 4:24 PM
melifaro updated this object.
melifaro edited the test plan for this revision. (Show Details)
melifaro edited the test plan for this revision. (Show Details)
melifaro edited the test plan for this revision. (Show Details)
melifaro edited the test plan for this revision. (Show Details)
melifaro updated this object.