mirror of
https://github.com/slackhq/nebula.git
synced 2026-08-15 20:07:00 +02:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| be58a866d9 | |||
| ed960c7fb8 |
@@ -30,9 +30,6 @@ jobs:
|
|||||||
- name: add hashicorp source
|
- name: add hashicorp source
|
||||||
run: wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg && echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
|
run: wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg && echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
|
||||||
|
|
||||||
- name: workaround AMD-V issue # https://github.com/cri-o/packaging/pull/306
|
|
||||||
run: sudo rmmod kvm_amd
|
|
||||||
|
|
||||||
- name: install vagrant
|
- name: install vagrant
|
||||||
run: sudo apt-get update && sudo apt-get install -y vagrant virtualbox
|
run: sudo apt-get update && sudo apt-get install -y vagrant virtualbox
|
||||||
|
|
||||||
|
|||||||
@@ -37,18 +37,17 @@ docker run --name host4 --device /dev/net/tun:/dev/net/tun --cap-add NET_ADMIN -
|
|||||||
sleep 1
|
sleep 1
|
||||||
|
|
||||||
# grab tcpdump pcaps for debugging
|
# grab tcpdump pcaps for debugging
|
||||||
docker exec lighthouse1 tcpdump -i tun0 -q -w - -U 2>logs/lighthouse1.inside.log >logs/lighthouse1.inside.pcap &
|
docker exec lighthouse1 tcpdump -i nebula1 -q -w - -U 2>logs/lighthouse1.inside.log >logs/lighthouse1.inside.pcap &
|
||||||
docker exec lighthouse1 tcpdump -i eth0 -q -w - -U 2>logs/lighthouse1.outside.log >logs/lighthouse1.outside.pcap &
|
docker exec lighthouse1 tcpdump -i eth0 -q -w - -U 2>logs/lighthouse1.outside.log >logs/lighthouse1.outside.pcap &
|
||||||
docker exec host2 tcpdump -i tun0 -q -w - -U 2>logs/host2.inside.log >logs/host2.inside.pcap &
|
docker exec host2 tcpdump -i nebula1 -q -w - -U 2>logs/host2.inside.log >logs/host2.inside.pcap &
|
||||||
docker exec host2 tcpdump -i eth0 -q -w - -U 2>logs/host2.outside.log >logs/host2.outside.pcap &
|
docker exec host2 tcpdump -i eth0 -q -w - -U 2>logs/host2.outside.log >logs/host2.outside.pcap &
|
||||||
docker exec host3 tcpdump -i tun0 -q -w - -U 2>logs/host3.inside.log >logs/host3.inside.pcap &
|
docker exec host3 tcpdump -i nebula1 -q -w - -U 2>logs/host3.inside.log >logs/host3.inside.pcap &
|
||||||
docker exec host3 tcpdump -i eth0 -q -w - -U 2>logs/host3.outside.log >logs/host3.outside.pcap &
|
docker exec host3 tcpdump -i eth0 -q -w - -U 2>logs/host3.outside.log >logs/host3.outside.pcap &
|
||||||
docker exec host4 tcpdump -i tun0 -q -w - -U 2>logs/host4.inside.log >logs/host4.inside.pcap &
|
docker exec host4 tcpdump -i nebula1 -q -w - -U 2>logs/host4.inside.log >logs/host4.inside.pcap &
|
||||||
docker exec host4 tcpdump -i eth0 -q -w - -U 2>logs/host4.outside.log >logs/host4.outside.pcap &
|
docker exec host4 tcpdump -i eth0 -q -w - -U 2>logs/host4.outside.log >logs/host4.outside.pcap &
|
||||||
|
|
||||||
docker exec host2 ncat -nklv 0.0.0.0 2000 &
|
docker exec host2 ncat -nklv 0.0.0.0 2000 &
|
||||||
docker exec host3 ncat -nklv 0.0.0.0 2000 &
|
docker exec host3 ncat -nklv 0.0.0.0 2000 &
|
||||||
docker exec host4 ncat -nkluv 0.0.0.0 4000 &
|
|
||||||
docker exec host2 ncat -e '/usr/bin/echo host2' -nkluv 0.0.0.0 3000 &
|
docker exec host2 ncat -e '/usr/bin/echo host2' -nkluv 0.0.0.0 3000 &
|
||||||
docker exec host3 ncat -e '/usr/bin/echo host3' -nkluv 0.0.0.0 3000 &
|
docker exec host3 ncat -e '/usr/bin/echo host3' -nkluv 0.0.0.0 3000 &
|
||||||
|
|
||||||
@@ -120,11 +119,11 @@ echo
|
|||||||
echo " *** Testing conntrack"
|
echo " *** Testing conntrack"
|
||||||
echo
|
echo
|
||||||
set -x
|
set -x
|
||||||
|
# host2 can ping host3 now that host3 pinged it first
|
||||||
# host2 speaking to host4 on UDP 4000 should allow it to reply, when firewall rules would normally not permit this
|
docker exec host2 ping -c1 192.168.100.3
|
||||||
docker exec host2 sh -c "/usr/bin/echo host2 | ncat -nuv 192.168.100.4 4000"
|
# host4 can ping host2 once conntrack established
|
||||||
docker exec host2 ncat -e '/usr/bin/echo helloagainfromhost2' -nkluv 0.0.0.0 4000 &
|
docker exec host2 ping -c1 192.168.100.4
|
||||||
docker exec host4 sh -c "/usr/bin/echo host4 | ncat -nuv 192.168.100.2 4000"
|
docker exec host4 ping -c1 192.168.100.2
|
||||||
|
|
||||||
docker exec host4 sh -c 'kill 1'
|
docker exec host4 sh -c 'kill 1'
|
||||||
docker exec host3 sh -c 'kill 1'
|
docker exec host3 sh -c 'kill 1'
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build boringcrypto
|
//go:build boringcrypto
|
||||||
|
// +build boringcrypto
|
||||||
|
|
||||||
package nebula
|
package nebula
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build e2e_testing
|
//go:build e2e_testing
|
||||||
|
// +build e2e_testing
|
||||||
|
|
||||||
package nebula
|
package nebula
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -382,8 +382,8 @@ firewall:
|
|||||||
# Rules are comprised of a protocol, port, and one or more of host, group, or CIDR
|
# Rules are comprised of a protocol, port, and one or more of host, group, or CIDR
|
||||||
# Logical evaluation is roughly: port AND proto AND (ca_sha OR ca_name) AND (host OR group OR groups OR cidr) AND (local cidr)
|
# Logical evaluation is roughly: port AND proto AND (ca_sha OR ca_name) AND (host OR group OR groups OR cidr) AND (local cidr)
|
||||||
# - port: Takes `0` or `any` as any, a single number `80`, a range `200-901`, or `fragment` to match second and further fragments of fragmented packets (since there is no port available).
|
# - port: Takes `0` or `any` as any, a single number `80`, a range `200-901`, or `fragment` to match second and further fragments of fragmented packets (since there is no port available).
|
||||||
|
# code: same as port but makes more sense when talking about ICMP, TODO: this is not currently implemented in a way that works, use `any`
|
||||||
# proto: `any`, `tcp`, `udp`, or `icmp`
|
# proto: `any`, `tcp`, `udp`, or `icmp`
|
||||||
# a port specification is ignored if proto is `icmp`
|
|
||||||
# host: `any` or a literal hostname, ie `test-host`
|
# host: `any` or a literal hostname, ie `test-host`
|
||||||
# group: `any` or a literal group name, ie `default-group`
|
# group: `any` or a literal group name, ie `default-group`
|
||||||
# groups: Same as group but accepts a list of values. Multiple values are AND'd together and a certificate would have to contain all groups to pass
|
# groups: Same as group but accepts a list of values. Multiple values are AND'd together and a certificate would have to contain all groups to pass
|
||||||
|
|||||||
+63
-83
@@ -249,6 +249,20 @@ func NewFirewallFromConfig(l *logrus.Logger, cs *CertState, c *config.C) (*Firew
|
|||||||
|
|
||||||
// AddRule properly creates the in memory rule structure for a firewall table.
|
// AddRule properly creates the in memory rule structure for a firewall table.
|
||||||
func (f *Firewall) AddRule(incoming bool, proto uint8, startPort int32, endPort int32, groups []string, host string, cidr, localCidr, caName string, caSha string) error {
|
func (f *Firewall) AddRule(incoming bool, proto uint8, startPort int32, endPort int32, groups []string, host string, cidr, localCidr, caName string, caSha string) error {
|
||||||
|
// We need this rule string because we generate a hash. Removing this will break firewall reload.
|
||||||
|
ruleString := fmt.Sprintf(
|
||||||
|
"incoming: %v, proto: %v, startPort: %v, endPort: %v, groups: %v, host: %v, ip: %v, localIp: %v, caName: %v, caSha: %s",
|
||||||
|
incoming, proto, startPort, endPort, groups, host, cidr, localCidr, caName, caSha,
|
||||||
|
)
|
||||||
|
f.rules += ruleString + "\n"
|
||||||
|
|
||||||
|
direction := "incoming"
|
||||||
|
if !incoming {
|
||||||
|
direction = "outgoing"
|
||||||
|
}
|
||||||
|
f.l.WithField("firewallRule", m{"direction": direction, "proto": proto, "startPort": startPort, "endPort": endPort, "groups": groups, "host": host, "cidr": cidr, "localCidr": localCidr, "caName": caName, "caSha": caSha}).
|
||||||
|
Info("Firewall rule added")
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ft *FirewallTable
|
ft *FirewallTable
|
||||||
fp firewallPort
|
fp firewallPort
|
||||||
@@ -266,12 +280,6 @@ func (f *Firewall) AddRule(incoming bool, proto uint8, startPort int32, endPort
|
|||||||
case firewall.ProtoUDP:
|
case firewall.ProtoUDP:
|
||||||
fp = ft.UDP
|
fp = ft.UDP
|
||||||
case firewall.ProtoICMP, firewall.ProtoICMPv6:
|
case firewall.ProtoICMP, firewall.ProtoICMPv6:
|
||||||
//ICMP traffic doesn't have ports, so we always coerce to "any", even if a value is provided
|
|
||||||
if startPort != firewall.PortAny {
|
|
||||||
f.l.WithField("startPort", startPort).Warn("ignoring port specification for ICMP firewall rule")
|
|
||||||
}
|
|
||||||
startPort = firewall.PortAny
|
|
||||||
endPort = firewall.PortAny
|
|
||||||
fp = ft.ICMP
|
fp = ft.ICMP
|
||||||
case firewall.ProtoAny:
|
case firewall.ProtoAny:
|
||||||
fp = ft.AnyProto
|
fp = ft.AnyProto
|
||||||
@@ -279,20 +287,6 @@ func (f *Firewall) AddRule(incoming bool, proto uint8, startPort int32, endPort
|
|||||||
return fmt.Errorf("unknown protocol %v", proto)
|
return fmt.Errorf("unknown protocol %v", proto)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need this rule string because we generate a hash. Removing this will break firewall reload.
|
|
||||||
ruleString := fmt.Sprintf(
|
|
||||||
"incoming: %v, proto: %v, startPort: %v, endPort: %v, groups: %v, host: %v, ip: %v, localIp: %v, caName: %v, caSha: %s",
|
|
||||||
incoming, proto, startPort, endPort, groups, host, cidr, localCidr, caName, caSha,
|
|
||||||
)
|
|
||||||
f.rules += ruleString + "\n"
|
|
||||||
|
|
||||||
direction := "incoming"
|
|
||||||
if !incoming {
|
|
||||||
direction = "outgoing"
|
|
||||||
}
|
|
||||||
f.l.WithField("firewallRule", m{"direction": direction, "proto": proto, "startPort": startPort, "endPort": endPort, "groups": groups, "host": host, "cidr": cidr, "localCidr": localCidr, "caName": caName, "caSha": caSha}).
|
|
||||||
Info("Firewall rule added")
|
|
||||||
|
|
||||||
return fp.addRule(f, startPort, endPort, groups, host, cidr, localCidr, caName, caSha)
|
return fp.addRule(f, startPort, endPort, groups, host, cidr, localCidr, caName, caSha)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,31 +349,24 @@ func AddFirewallRulesFromConfig(l *logrus.Logger, inbound bool, c *config.C, fw
|
|||||||
sPort = r.Port
|
sPort = r.Port
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startPort, endPort, err := parsePort(sPort)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s rule #%v; %s %s", table, i, errPort, err)
|
||||||
|
}
|
||||||
|
|
||||||
var proto uint8
|
var proto uint8
|
||||||
var startPort, endPort int32
|
|
||||||
switch r.Proto {
|
switch r.Proto {
|
||||||
case "any":
|
case "any":
|
||||||
proto = firewall.ProtoAny
|
proto = firewall.ProtoAny
|
||||||
startPort, endPort, err = parsePort(sPort)
|
|
||||||
case "tcp":
|
case "tcp":
|
||||||
proto = firewall.ProtoTCP
|
proto = firewall.ProtoTCP
|
||||||
startPort, endPort, err = parsePort(sPort)
|
|
||||||
case "udp":
|
case "udp":
|
||||||
proto = firewall.ProtoUDP
|
proto = firewall.ProtoUDP
|
||||||
startPort, endPort, err = parsePort(sPort)
|
|
||||||
case "icmp":
|
case "icmp":
|
||||||
proto = firewall.ProtoICMP
|
proto = firewall.ProtoICMP
|
||||||
startPort = firewall.PortAny
|
|
||||||
endPort = firewall.PortAny
|
|
||||||
if sPort != "" {
|
|
||||||
l.WithField("port", sPort).Warn("ignoring port specification for ICMP firewall rule")
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("%s rule #%v; proto was not understood; `%s`", table, i, r.Proto)
|
return fmt.Errorf("%s rule #%v; proto was not understood; `%s`", table, i, r.Proto)
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("%s rule #%v; %s %s", table, i, errPort, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Cidr != "" && r.Cidr != "any" {
|
if r.Cidr != "" && r.Cidr != "any" {
|
||||||
_, err = netip.ParsePrefix(r.Cidr)
|
_, err = netip.ParsePrefix(r.Cidr)
|
||||||
@@ -480,7 +467,7 @@ func (f *Firewall) metrics(incoming bool) firewallMetrics {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destroy cleans up any known cyclical references so the object can be freed by GC. This should be called if a new
|
// Destroy cleans up any known cyclical references so the object can be free'd my GC. This should be called if a new
|
||||||
// firewall object is created
|
// firewall object is created
|
||||||
func (f *Firewall) Destroy() {
|
func (f *Firewall) Destroy() {
|
||||||
//TODO: clean references if/when needed
|
//TODO: clean references if/when needed
|
||||||
@@ -673,13 +660,6 @@ func (fp firewallPort) match(p firewall.Packet, incoming bool, c *cert.CachedCer
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// this branch is here to catch traffic from FirewallTable.Any.match and FirewallTable.ICMP.match
|
|
||||||
if p.Protocol == firewall.ProtoICMP || p.Protocol == firewall.ProtoICMPv6 {
|
|
||||||
// port numbers are re-used for connection tracking of ICMP,
|
|
||||||
// but we don't want to actually filter on them.
|
|
||||||
return fp[firewall.PortAny].match(p, c, caPool)
|
|
||||||
}
|
|
||||||
|
|
||||||
var port int32
|
var port int32
|
||||||
|
|
||||||
if p.Fragment {
|
if p.Fragment {
|
||||||
@@ -824,8 +804,10 @@ func (fr *FirewallRule) isAny(groups []string, host string, cidr string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if slices.Contains(groups, "any") {
|
for _, group := range groups {
|
||||||
return true
|
if group == "any" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if host == "any" {
|
if host == "any" {
|
||||||
@@ -1036,56 +1018,54 @@ func (r *rule) sanity() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Code != "" {
|
|
||||||
return fmt.Errorf("code specified as [%s]. Support for 'code' will be dropped in a future release, as it has never been functional", r.Code)
|
|
||||||
}
|
|
||||||
|
|
||||||
//todo alert on cidr-any
|
//todo alert on cidr-any
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parsePort(s string) (int32, int32, error) {
|
func parsePort(s string) (startPort, endPort int32, err error) {
|
||||||
var err error
|
|
||||||
const notAPort int32 = -2
|
|
||||||
if s == "any" {
|
if s == "any" {
|
||||||
return firewall.PortAny, firewall.PortAny, nil
|
startPort = firewall.PortAny
|
||||||
}
|
endPort = firewall.PortAny
|
||||||
if s == "fragment" {
|
|
||||||
return firewall.PortFragment, firewall.PortFragment, nil
|
} else if s == "fragment" {
|
||||||
}
|
startPort = firewall.PortFragment
|
||||||
if !strings.Contains(s, `-`) {
|
endPort = firewall.PortFragment
|
||||||
|
|
||||||
|
} else if strings.Contains(s, `-`) {
|
||||||
|
sPorts := strings.SplitN(s, `-`, 2)
|
||||||
|
sPorts[0] = strings.Trim(sPorts[0], " ")
|
||||||
|
sPorts[1] = strings.Trim(sPorts[1], " ")
|
||||||
|
|
||||||
|
if len(sPorts) != 2 || sPorts[0] == "" || sPorts[1] == "" {
|
||||||
|
return 0, 0, fmt.Errorf("appears to be a range but could not be parsed; `%s`", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
rStartPort, err := strconv.Atoi(sPorts[0])
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, fmt.Errorf("beginning range was not a number; `%s`", sPorts[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
rEndPort, err := strconv.Atoi(sPorts[1])
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, fmt.Errorf("ending range was not a number; `%s`", sPorts[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
startPort = int32(rStartPort)
|
||||||
|
endPort = int32(rEndPort)
|
||||||
|
|
||||||
|
if startPort == firewall.PortAny {
|
||||||
|
endPort = firewall.PortAny
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
rPort, err := strconv.Atoi(s)
|
rPort, err := strconv.Atoi(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return notAPort, notAPort, fmt.Errorf("was not a number; `%s`", s)
|
return 0, 0, fmt.Errorf("was not a number; `%s`", s)
|
||||||
}
|
}
|
||||||
return int32(rPort), int32(rPort), nil
|
startPort = int32(rPort)
|
||||||
|
endPort = startPort
|
||||||
}
|
}
|
||||||
|
|
||||||
sPorts := strings.SplitN(s, `-`, 2)
|
return
|
||||||
for i := range sPorts {
|
|
||||||
sPorts[i] = strings.Trim(sPorts[i], " ")
|
|
||||||
}
|
|
||||||
if len(sPorts) != 2 || sPorts[0] == "" || sPorts[1] == "" {
|
|
||||||
return notAPort, notAPort, fmt.Errorf("appears to be a range but could not be parsed; `%s`", s)
|
|
||||||
}
|
|
||||||
|
|
||||||
rStartPort, err := strconv.Atoi(sPorts[0])
|
|
||||||
if err != nil {
|
|
||||||
return notAPort, notAPort, fmt.Errorf("beginning range was not a number; `%s`", sPorts[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
rEndPort, err := strconv.Atoi(sPorts[1])
|
|
||||||
if err != nil {
|
|
||||||
return notAPort, notAPort, fmt.Errorf("ending range was not a number; `%s`", sPorts[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
startPort := int32(rStartPort)
|
|
||||||
endPort := int32(rEndPort)
|
|
||||||
|
|
||||||
if startPort == firewall.PortAny {
|
|
||||||
endPort = firewall.PortAny
|
|
||||||
}
|
|
||||||
|
|
||||||
return startPort, endPort, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
+187
-6
@@ -1,9 +1,15 @@
|
|||||||
package firewall
|
package firewall
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/google/gopacket/layers"
|
||||||
|
"golang.org/x/net/ipv4"
|
||||||
|
"golang.org/x/net/ipv6"
|
||||||
)
|
)
|
||||||
|
|
||||||
type m = map[string]any
|
type m = map[string]any
|
||||||
@@ -17,15 +23,23 @@ const (
|
|||||||
|
|
||||||
PortAny = 0 // Special value for matching `port: any`
|
PortAny = 0 // Special value for matching `port: any`
|
||||||
PortFragment = -1 // Special value for matching `port: fragment`
|
PortFragment = -1 // Special value for matching `port: fragment`
|
||||||
|
|
||||||
|
minFwPacketLen = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrPacketTooShort = errors.New("packet is too short")
|
||||||
|
ErrUnknownIPVersion = errors.New("packet is an unknown ip version")
|
||||||
|
ErrIPv4InvalidHeaderLength = errors.New("invalid ipv4 header length")
|
||||||
|
ErrIPv4PacketTooShort = errors.New("ipv4 packet is too short")
|
||||||
|
ErrIPv6PacketTooShort = errors.New("ipv6 packet is too short")
|
||||||
|
ErrIPv6CouldNotFindPayload = errors.New("could not find payload in ipv6 packet")
|
||||||
)
|
)
|
||||||
|
|
||||||
type Packet struct {
|
type Packet struct {
|
||||||
LocalAddr netip.Addr
|
LocalAddr netip.Addr
|
||||||
RemoteAddr netip.Addr
|
RemoteAddr netip.Addr
|
||||||
// LocalPort is the destination port for incoming traffic, or the source port for outgoing. Zero for ICMP.
|
LocalPort uint16
|
||||||
LocalPort uint16
|
|
||||||
// RemotePort is the source port for incoming traffic, or the destination port for outgoing.
|
|
||||||
// For ICMP, it's the "identifier". This is only used for connection tracking, actual firewall rules will not filter on ICMP identifier
|
|
||||||
RemotePort uint16
|
RemotePort uint16
|
||||||
Protocol uint8
|
Protocol uint8
|
||||||
Fragment bool
|
Fragment bool
|
||||||
@@ -49,8 +63,6 @@ func (fp Packet) MarshalJSON() ([]byte, error) {
|
|||||||
proto = "tcp"
|
proto = "tcp"
|
||||||
case ProtoICMP:
|
case ProtoICMP:
|
||||||
proto = "icmp"
|
proto = "icmp"
|
||||||
case ProtoICMPv6:
|
|
||||||
proto = "icmpv6"
|
|
||||||
case ProtoUDP:
|
case ProtoUDP:
|
||||||
proto = "udp"
|
proto = "udp"
|
||||||
default:
|
default:
|
||||||
@@ -65,3 +77,172 @@ func (fp Packet) MarshalJSON() ([]byte, error) {
|
|||||||
"Fragment": fp.Fragment,
|
"Fragment": fp.Fragment,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseV6(data []byte, incoming bool, fp *Packet) error {
|
||||||
|
dataLen := len(data)
|
||||||
|
if dataLen < ipv6.HeaderLen {
|
||||||
|
return ErrIPv6PacketTooShort
|
||||||
|
}
|
||||||
|
|
||||||
|
if incoming {
|
||||||
|
fp.RemoteAddr, _ = netip.AddrFromSlice(data[8:24])
|
||||||
|
fp.LocalAddr, _ = netip.AddrFromSlice(data[24:40])
|
||||||
|
} else {
|
||||||
|
fp.LocalAddr, _ = netip.AddrFromSlice(data[8:24])
|
||||||
|
fp.RemoteAddr, _ = netip.AddrFromSlice(data[24:40])
|
||||||
|
}
|
||||||
|
|
||||||
|
protoAt := 6 // NextHeader is at 6 bytes into the ipv6 header
|
||||||
|
offset := ipv6.HeaderLen // Start at the end of the ipv6 header
|
||||||
|
next := 0
|
||||||
|
for {
|
||||||
|
if protoAt >= dataLen {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
proto := layers.IPProtocol(data[protoAt])
|
||||||
|
|
||||||
|
switch proto {
|
||||||
|
case layers.IPProtocolICMPv6, layers.IPProtocolESP, layers.IPProtocolNoNextHeader:
|
||||||
|
fp.Protocol = uint8(proto)
|
||||||
|
fp.RemotePort = 0
|
||||||
|
fp.LocalPort = 0
|
||||||
|
fp.Fragment = false
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case layers.IPProtocolTCP, layers.IPProtocolUDP:
|
||||||
|
if dataLen < offset+4 {
|
||||||
|
return ErrIPv6PacketTooShort
|
||||||
|
}
|
||||||
|
|
||||||
|
fp.Protocol = uint8(proto)
|
||||||
|
if incoming {
|
||||||
|
fp.RemotePort = binary.BigEndian.Uint16(data[offset : offset+2])
|
||||||
|
fp.LocalPort = binary.BigEndian.Uint16(data[offset+2 : offset+4])
|
||||||
|
} else {
|
||||||
|
fp.LocalPort = binary.BigEndian.Uint16(data[offset : offset+2])
|
||||||
|
fp.RemotePort = binary.BigEndian.Uint16(data[offset+2 : offset+4])
|
||||||
|
}
|
||||||
|
|
||||||
|
fp.Fragment = false
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case layers.IPProtocolIPv6Fragment:
|
||||||
|
// Fragment header is 8 bytes, need at least offset+4 to read the offset field
|
||||||
|
if dataLen < offset+8 {
|
||||||
|
return ErrIPv6PacketTooShort
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this is the first fragment
|
||||||
|
fragmentOffset := binary.BigEndian.Uint16(data[offset+2:offset+4]) &^ uint16(0x7) // Remove the reserved and M flag bits
|
||||||
|
if fragmentOffset != 0 {
|
||||||
|
// Non-first fragment, use what we have now and stop processing
|
||||||
|
fp.Protocol = data[offset]
|
||||||
|
fp.Fragment = true
|
||||||
|
fp.RemotePort = 0
|
||||||
|
fp.LocalPort = 0
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The next loop should be the transport layer since we are the first fragment
|
||||||
|
next = 8 // Fragment headers are always 8 bytes
|
||||||
|
|
||||||
|
case layers.IPProtocolAH:
|
||||||
|
// Auth headers, used by IPSec, have a different meaning for header length
|
||||||
|
if dataLen <= offset+1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
next = int(data[offset+1]+2) << 2
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Normal ipv6 header length processing
|
||||||
|
if dataLen <= offset+1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
next = int(data[offset+1]+1) << 3
|
||||||
|
}
|
||||||
|
|
||||||
|
if next <= 0 {
|
||||||
|
// Safety check, each ipv6 header has to be at least 8 bytes
|
||||||
|
next = 8
|
||||||
|
}
|
||||||
|
|
||||||
|
protoAt = offset
|
||||||
|
offset = offset + next
|
||||||
|
}
|
||||||
|
|
||||||
|
return ErrIPv6CouldNotFindPayload
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseV4(data []byte, incoming bool, fp *Packet) error {
|
||||||
|
// Do we at least have an ipv4 header worth of data?
|
||||||
|
if len(data) < ipv4.HeaderLen {
|
||||||
|
return ErrIPv4PacketTooShort
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust our start position based on the advertised ip header length
|
||||||
|
ihl := int(data[0]&0x0f) << 2
|
||||||
|
|
||||||
|
// Well-formed ip header length?
|
||||||
|
if ihl < ipv4.HeaderLen {
|
||||||
|
return ErrIPv4InvalidHeaderLength
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this is the second or further fragment of a fragmented packet.
|
||||||
|
flagsfrags := binary.BigEndian.Uint16(data[6:8])
|
||||||
|
fp.Fragment = (flagsfrags & 0x1FFF) != 0
|
||||||
|
|
||||||
|
// Firewall handles protocol checks
|
||||||
|
fp.Protocol = data[9]
|
||||||
|
|
||||||
|
// Accounting for a variable header length, do we have enough data for our src/dst tuples?
|
||||||
|
minLen := ihl
|
||||||
|
if !fp.Fragment && fp.Protocol != ProtoICMP {
|
||||||
|
minLen += minFwPacketLen
|
||||||
|
}
|
||||||
|
if len(data) < minLen {
|
||||||
|
return ErrIPv4InvalidHeaderLength
|
||||||
|
}
|
||||||
|
|
||||||
|
// Firewall packets are locally oriented
|
||||||
|
if incoming {
|
||||||
|
fp.RemoteAddr, _ = netip.AddrFromSlice(data[12:16])
|
||||||
|
fp.LocalAddr, _ = netip.AddrFromSlice(data[16:20])
|
||||||
|
if fp.Fragment || fp.Protocol == ProtoICMP {
|
||||||
|
fp.RemotePort = 0
|
||||||
|
fp.LocalPort = 0
|
||||||
|
} else {
|
||||||
|
fp.RemotePort = binary.BigEndian.Uint16(data[ihl : ihl+2])
|
||||||
|
fp.LocalPort = binary.BigEndian.Uint16(data[ihl+2 : ihl+4])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fp.LocalAddr, _ = netip.AddrFromSlice(data[12:16])
|
||||||
|
fp.RemoteAddr, _ = netip.AddrFromSlice(data[16:20])
|
||||||
|
if fp.Fragment || fp.Protocol == ProtoICMP {
|
||||||
|
fp.RemotePort = 0
|
||||||
|
fp.LocalPort = 0
|
||||||
|
} else {
|
||||||
|
fp.LocalPort = binary.BigEndian.Uint16(data[ihl : ihl+2])
|
||||||
|
fp.RemotePort = binary.BigEndian.Uint16(data[ihl+2 : ihl+4])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPacket validates and parses the interesting bits for the firewall out of the ip and sub protocol headers
|
||||||
|
func NewPacket(data []byte, incoming bool, fp *Packet) error {
|
||||||
|
if len(data) < 1 {
|
||||||
|
return ErrPacketTooShort
|
||||||
|
}
|
||||||
|
|
||||||
|
version := int((data[0] >> 4) & 0x0f)
|
||||||
|
switch version {
|
||||||
|
case ipv4.Version:
|
||||||
|
return parseV4(data, incoming, fp)
|
||||||
|
case ipv6.Version:
|
||||||
|
return parseV6(data, incoming, fp)
|
||||||
|
}
|
||||||
|
return ErrUnknownIPVersion
|
||||||
|
}
|
||||||
|
|||||||
+6
-158
@@ -87,10 +87,9 @@ func TestFirewall_AddRule(t *testing.T) {
|
|||||||
|
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
||||||
require.NoError(t, fw.AddRule(true, firewall.ProtoICMP, 1, 1, []string{}, "h1", "", "", "", ""))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoICMP, 1, 1, []string{}, "h1", "", "", "", ""))
|
||||||
//no matter what port is given for icmp, it should end up as "any"
|
assert.Nil(t, fw.InRules.ICMP[1].Any.Any)
|
||||||
assert.Nil(t, fw.InRules.ICMP[firewall.PortAny].Any.Any)
|
assert.Empty(t, fw.InRules.ICMP[1].Any.Groups)
|
||||||
assert.Empty(t, fw.InRules.ICMP[firewall.PortAny].Any.Groups)
|
assert.Contains(t, fw.InRules.ICMP[1].Any.Hosts, "h1")
|
||||||
assert.Contains(t, fw.InRules.ICMP[firewall.PortAny].Any.Hosts, "h1")
|
|
||||||
|
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
||||||
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 1, 1, []string{}, "", ti.String(), "", "", ""))
|
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 1, 1, []string{}, "", ti.String(), "", "", ""))
|
||||||
@@ -735,150 +734,6 @@ func TestFirewall_DropConntrackReload(t *testing.T) {
|
|||||||
assert.Equal(t, fw.Drop(p, false, &h, cp, nil), ErrNoMatchingRule)
|
assert.Equal(t, fw.Drop(p, false, &h, cp, nil), ErrNoMatchingRule)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFirewall_ICMPPortBehavior(t *testing.T) {
|
|
||||||
l := test.NewLogger()
|
|
||||||
ob := &bytes.Buffer{}
|
|
||||||
l.SetOutput(ob)
|
|
||||||
myVpnNetworksTable := new(bart.Lite)
|
|
||||||
myVpnNetworksTable.Insert(netip.MustParsePrefix("1.1.1.1/8"))
|
|
||||||
|
|
||||||
network := netip.MustParsePrefix("1.2.3.4/24")
|
|
||||||
|
|
||||||
c := cert.CachedCertificate{
|
|
||||||
Certificate: &dummyCert{
|
|
||||||
name: "host1",
|
|
||||||
networks: []netip.Prefix{network},
|
|
||||||
groups: []string{"default-group"},
|
|
||||||
issuer: "signer-shasum",
|
|
||||||
},
|
|
||||||
InvertedGroups: map[string]struct{}{"default-group": {}},
|
|
||||||
}
|
|
||||||
h := HostInfo{
|
|
||||||
ConnectionState: &ConnectionState{
|
|
||||||
peerCert: &c,
|
|
||||||
},
|
|
||||||
vpnAddrs: []netip.Addr{network.Addr()},
|
|
||||||
}
|
|
||||||
h.buildNetworks(myVpnNetworksTable, c.Certificate)
|
|
||||||
|
|
||||||
cp := cert.NewCAPool()
|
|
||||||
|
|
||||||
templ := firewall.Packet{
|
|
||||||
LocalAddr: netip.MustParseAddr("1.2.3.4"),
|
|
||||||
RemoteAddr: netip.MustParseAddr("1.2.3.4"),
|
|
||||||
Protocol: firewall.ProtoICMP,
|
|
||||||
Fragment: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("ICMP allowed", func(t *testing.T) {
|
|
||||||
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
|
||||||
require.NoError(t, fw.AddRule(true, firewall.ProtoICMP, 0, 0, []string{"any"}, "", "", "", "", ""))
|
|
||||||
t.Run("zero ports", func(t *testing.T) {
|
|
||||||
p := templ.Copy()
|
|
||||||
p.LocalPort = 0
|
|
||||||
p.RemotePort = 0
|
|
||||||
// Drop outbound
|
|
||||||
assert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)
|
|
||||||
// Allow inbound
|
|
||||||
resetConntrack(fw)
|
|
||||||
require.NoError(t, fw.Drop(*p, true, &h, cp, nil))
|
|
||||||
//now also allow outbound
|
|
||||||
require.NoError(t, fw.Drop(*p, false, &h, cp, nil))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("nonzero ports", func(t *testing.T) {
|
|
||||||
p := templ.Copy()
|
|
||||||
p.LocalPort = 0xabcd
|
|
||||||
p.RemotePort = 0x1234
|
|
||||||
// Drop outbound
|
|
||||||
assert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)
|
|
||||||
// Allow inbound
|
|
||||||
resetConntrack(fw)
|
|
||||||
require.NoError(t, fw.Drop(*p, true, &h, cp, nil))
|
|
||||||
//now also allow outbound
|
|
||||||
require.NoError(t, fw.Drop(*p, false, &h, cp, nil))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Any proto, some ports allowed", func(t *testing.T) {
|
|
||||||
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
|
||||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 80, 444, []string{"any"}, "", "", "", "", ""))
|
|
||||||
t.Run("zero ports, still blocked", func(t *testing.T) {
|
|
||||||
p := templ.Copy()
|
|
||||||
p.LocalPort = 0
|
|
||||||
p.RemotePort = 0
|
|
||||||
// Drop outbound
|
|
||||||
assert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)
|
|
||||||
// Allow inbound
|
|
||||||
resetConntrack(fw)
|
|
||||||
assert.Equal(t, fw.Drop(*p, true, &h, cp, nil), ErrNoMatchingRule)
|
|
||||||
//now also allow outbound
|
|
||||||
assert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("nonzero ports, still blocked", func(t *testing.T) {
|
|
||||||
p := templ.Copy()
|
|
||||||
p.LocalPort = 0xabcd
|
|
||||||
p.RemotePort = 0x1234
|
|
||||||
// Drop outbound
|
|
||||||
assert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)
|
|
||||||
// Allow inbound
|
|
||||||
resetConntrack(fw)
|
|
||||||
assert.Equal(t, fw.Drop(*p, true, &h, cp, nil), ErrNoMatchingRule)
|
|
||||||
//now also allow outbound
|
|
||||||
assert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("nonzero, matching ports, still blocked", func(t *testing.T) {
|
|
||||||
p := templ.Copy()
|
|
||||||
p.LocalPort = 80
|
|
||||||
p.RemotePort = 80
|
|
||||||
// Drop outbound
|
|
||||||
assert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)
|
|
||||||
// Allow inbound
|
|
||||||
resetConntrack(fw)
|
|
||||||
assert.Equal(t, fw.Drop(*p, true, &h, cp, nil), ErrNoMatchingRule)
|
|
||||||
//now also allow outbound
|
|
||||||
assert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
t.Run("Any proto, any port", func(t *testing.T) {
|
|
||||||
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
|
||||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"any"}, "", "", "", "", ""))
|
|
||||||
t.Run("zero ports, allowed", func(t *testing.T) {
|
|
||||||
resetConntrack(fw)
|
|
||||||
p := templ.Copy()
|
|
||||||
p.LocalPort = 0
|
|
||||||
p.RemotePort = 0
|
|
||||||
// Drop outbound
|
|
||||||
assert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)
|
|
||||||
// Allow inbound
|
|
||||||
resetConntrack(fw)
|
|
||||||
require.NoError(t, fw.Drop(*p, true, &h, cp, nil))
|
|
||||||
//now also allow outbound
|
|
||||||
require.NoError(t, fw.Drop(*p, false, &h, cp, nil))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("nonzero ports, allowed", func(t *testing.T) {
|
|
||||||
resetConntrack(fw)
|
|
||||||
p := templ.Copy()
|
|
||||||
p.LocalPort = 0xabcd
|
|
||||||
p.RemotePort = 0x1234
|
|
||||||
// Drop outbound
|
|
||||||
assert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)
|
|
||||||
// Allow inbound
|
|
||||||
resetConntrack(fw)
|
|
||||||
require.NoError(t, fw.Drop(*p, true, &h, cp, nil))
|
|
||||||
//now also allow outbound
|
|
||||||
require.NoError(t, fw.Drop(*p, false, &h, cp, nil))
|
|
||||||
//different ID is blocked
|
|
||||||
p.RemotePort++
|
|
||||||
require.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFirewall_DropIPSpoofing(t *testing.T) {
|
func TestFirewall_DropIPSpoofing(t *testing.T) {
|
||||||
l := test.NewLogger()
|
l := test.NewLogger()
|
||||||
ob := &bytes.Buffer{}
|
ob := &bytes.Buffer{}
|
||||||
@@ -1064,11 +919,11 @@ func TestNewFirewallFromConfig(t *testing.T) {
|
|||||||
|
|
||||||
// Test code/port error
|
// Test code/port error
|
||||||
conf = config.NewC(l)
|
conf = config.NewC(l)
|
||||||
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"code": "a", "host": "testh", "proto": "any"}}}
|
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"code": "a", "host": "testh"}}}
|
||||||
_, err = NewFirewallFromConfig(l, cs, conf)
|
_, err = NewFirewallFromConfig(l, cs, conf)
|
||||||
require.EqualError(t, err, "firewall.outbound rule #0; code was not a number; `a`")
|
require.EqualError(t, err, "firewall.outbound rule #0; code was not a number; `a`")
|
||||||
|
|
||||||
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "a", "host": "testh", "proto": "any"}}}
|
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "a", "host": "testh"}}}
|
||||||
_, err = NewFirewallFromConfig(l, cs, conf)
|
_, err = NewFirewallFromConfig(l, cs, conf)
|
||||||
require.EqualError(t, err, "firewall.outbound rule #0; port was not a number; `a`")
|
require.EqualError(t, err, "firewall.outbound rule #0; port was not a number; `a`")
|
||||||
|
|
||||||
@@ -1118,14 +973,7 @@ func TestAddFirewallRulesFromConfig(t *testing.T) {
|
|||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "proto": "icmp", "host": "a"}}}
|
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "proto": "icmp", "host": "a"}}}
|
||||||
require.NoError(t, AddFirewallRulesFromConfig(l, false, conf, mf))
|
require.NoError(t, AddFirewallRulesFromConfig(l, false, conf, mf))
|
||||||
assert.Equal(t, addRuleCall{incoming: false, proto: firewall.ProtoICMP, startPort: firewall.PortAny, endPort: firewall.PortAny, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
|
assert.Equal(t, addRuleCall{incoming: false, proto: firewall.ProtoICMP, startPort: 1, endPort: 1, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
|
||||||
|
|
||||||
// Test adding icmp rule no port
|
|
||||||
conf = config.NewC(l)
|
|
||||||
mf = &mockFirewall{}
|
|
||||||
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"proto": "icmp", "host": "a"}}}
|
|
||||||
require.NoError(t, AddFirewallRulesFromConfig(l, false, conf, mf))
|
|
||||||
assert.Equal(t, addRuleCall{incoming: false, proto: firewall.ProtoICMP, startPort: firewall.PortAny, endPort: firewall.PortAny, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
|
|
||||||
|
|
||||||
// Test adding any rule
|
// Test adding any rule
|
||||||
conf = config.NewC(l)
|
conf = config.NewC(l)
|
||||||
|
|||||||
@@ -590,7 +590,7 @@ func (hm *HandshakeManager) allocateIndex(hh *HandshakeHostInfo) error {
|
|||||||
hm.Lock()
|
hm.Lock()
|
||||||
defer hm.Unlock()
|
defer hm.Unlock()
|
||||||
|
|
||||||
for range 32 {
|
for i := 0; i < 32; i++ {
|
||||||
index, err := generateIndex(hm.l)
|
index, err := generateIndex(hm.l)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build e2e_testing
|
//go:build e2e_testing
|
||||||
|
// +build e2e_testing
|
||||||
|
|
||||||
package nebula
|
package nebula
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (f *Interface) consumeInsidePacket(packet []byte, fwPacket *firewall.Packet, nb, out []byte, q int, localCache firewall.ConntrackCache) {
|
func (f *Interface) consumeInsidePacket(packet []byte, fwPacket *firewall.Packet, nb, out []byte, q int, localCache firewall.ConntrackCache) {
|
||||||
err := newPacket(packet, false, fwPacket)
|
err := firewall.NewPacket(packet, false, fwPacket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if f.l.Level >= logrus.DebugLevel {
|
if f.l.Level >= logrus.DebugLevel {
|
||||||
f.l.WithField("packet", packet).Debugf("Error while validating outbound packet: %s", err)
|
f.l.WithField("packet", packet).Debugf("Error while validating outbound packet: %s", err)
|
||||||
@@ -211,7 +211,7 @@ func (f *Interface) getOrHandshakeConsiderRouting(fwPacket *firewall.Packet, cac
|
|||||||
|
|
||||||
func (f *Interface) sendMessageNow(t header.MessageType, st header.MessageSubType, hostinfo *HostInfo, p, nb, out []byte) {
|
func (f *Interface) sendMessageNow(t header.MessageType, st header.MessageSubType, hostinfo *HostInfo, p, nb, out []byte) {
|
||||||
fp := &firewall.Packet{}
|
fp := &firewall.Packet{}
|
||||||
err := newPacket(p, false, fp)
|
err := firewall.NewPacket(p, false, fp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.l.Warnf("error while parsing outgoing packet for firewall check; %v", err)
|
f.l.Warnf("error while parsing outgoing packet for firewall check; %v", err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build darwin || dragonfly || freebsd || netbsd || openbsd
|
//go:build darwin || dragonfly || freebsd || netbsd || openbsd
|
||||||
|
// +build darwin dragonfly freebsd netbsd openbsd
|
||||||
|
|
||||||
package nebula
|
package nebula
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build !darwin && !dragonfly && !freebsd && !netbsd && !openbsd
|
//go:build !darwin && !dragonfly && !freebsd && !netbsd && !openbsd
|
||||||
|
// +build !darwin,!dragonfly,!freebsd,!netbsd,!openbsd
|
||||||
|
|
||||||
package nebula
|
package nebula
|
||||||
|
|
||||||
|
|||||||
@@ -263,6 +263,8 @@ func (f *Interface) run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *Interface) listenOut(i int) {
|
func (f *Interface) listenOut(i int) {
|
||||||
|
runtime.LockOSThread()
|
||||||
|
|
||||||
var li udp.Conn
|
var li udp.Conn
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
li = f.writers[i]
|
li = f.writers[i]
|
||||||
|
|||||||
+10
-3
@@ -713,14 +713,21 @@ func (lh *LightHouse) unlockedShouldAddV6(vpnAddr netip.Addr, to *V6AddrPort) bo
|
|||||||
|
|
||||||
func (lh *LightHouse) IsLighthouseAddr(vpnAddr netip.Addr) bool {
|
func (lh *LightHouse) IsLighthouseAddr(vpnAddr netip.Addr) bool {
|
||||||
l := lh.GetLighthouses()
|
l := lh.GetLighthouses()
|
||||||
return slices.Contains(l, vpnAddr)
|
for i := range l {
|
||||||
|
if l[i] == vpnAddr {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lh *LightHouse) IsAnyLighthouseAddr(vpnAddrs []netip.Addr) bool {
|
func (lh *LightHouse) IsAnyLighthouseAddr(vpnAddrs []netip.Addr) bool {
|
||||||
l := lh.GetLighthouses()
|
l := lh.GetLighthouses()
|
||||||
for i := range vpnAddrs {
|
for i := range vpnAddrs {
|
||||||
if slices.Contains(l, vpnAddrs[i]) {
|
for j := range l {
|
||||||
return true
|
if l[j] == vpnAddrs[i] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -105,7 +105,11 @@ func Main(c *config.C, configTest bool, buildVersion string, logger *logrus.Logg
|
|||||||
// deprecated and undocumented
|
// deprecated and undocumented
|
||||||
tunQueues := c.GetInt("tun.routines", 1)
|
tunQueues := c.GetInt("tun.routines", 1)
|
||||||
udpQueues := c.GetInt("listen.routines", 1)
|
udpQueues := c.GetInt("listen.routines", 1)
|
||||||
routines = max(tunQueues, udpQueues)
|
if tunQueues > udpQueues {
|
||||||
|
routines = tunQueues
|
||||||
|
} else {
|
||||||
|
routines = udpQueues
|
||||||
|
}
|
||||||
if routines != 1 {
|
if routines != 1 {
|
||||||
l.WithField("routines", routines).Warn("Setting tun.routines and listen.routines is deprecated. Use `routines` instead")
|
l.WithField("routines", routines).Warn("Setting tun.routines and listen.routines is deprecated. Use `routines` instead")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build !boringcrypto
|
//go:build !boringcrypto
|
||||||
|
// +build !boringcrypto
|
||||||
|
|
||||||
package nebula
|
package nebula
|
||||||
|
|
||||||
|
|||||||
+1
-208
@@ -1,22 +1,13 @@
|
|||||||
package nebula
|
package nebula
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
"errors"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/gopacket/layers"
|
|
||||||
"golang.org/x/net/ipv6"
|
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/firewall"
|
"github.com/slackhq/nebula/firewall"
|
||||||
"github.com/slackhq/nebula/header"
|
"github.com/slackhq/nebula/header"
|
||||||
"golang.org/x/net/ipv4"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
minFwPacketLen = 4
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (f *Interface) readOutsidePackets(via ViaSender, out []byte, packet []byte, h *header.H, fwPacket *firewall.Packet, lhf *LightHouseHandler, nb []byte, q int, localCache firewall.ConntrackCache) {
|
func (f *Interface) readOutsidePackets(via ViaSender, out []byte, packet []byte, h *header.H, fwPacket *firewall.Packet, lhf *LightHouseHandler, nb []byte, q int, localCache firewall.ConntrackCache) {
|
||||||
@@ -278,204 +269,6 @@ func (f *Interface) handleEncrypted(ci *ConnectionState, via ViaSender, h *heade
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
ErrPacketTooShort = errors.New("packet is too short")
|
|
||||||
ErrUnknownIPVersion = errors.New("packet is an unknown ip version")
|
|
||||||
ErrIPv4InvalidHeaderLength = errors.New("invalid ipv4 header length")
|
|
||||||
ErrIPv4PacketTooShort = errors.New("ipv4 packet is too short")
|
|
||||||
ErrIPv6PacketTooShort = errors.New("ipv6 packet is too short")
|
|
||||||
ErrIPv6CouldNotFindPayload = errors.New("could not find payload in ipv6 packet")
|
|
||||||
)
|
|
||||||
|
|
||||||
// newPacket validates and parses the interesting bits for the firewall out of the ip and sub protocol headers
|
|
||||||
func newPacket(data []byte, incoming bool, fp *firewall.Packet) error {
|
|
||||||
if len(data) < 1 {
|
|
||||||
return ErrPacketTooShort
|
|
||||||
}
|
|
||||||
|
|
||||||
version := int((data[0] >> 4) & 0x0f)
|
|
||||||
switch version {
|
|
||||||
case ipv4.Version:
|
|
||||||
return parseV4(data, incoming, fp)
|
|
||||||
case ipv6.Version:
|
|
||||||
return parseV6(data, incoming, fp)
|
|
||||||
}
|
|
||||||
return ErrUnknownIPVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseV6(data []byte, incoming bool, fp *firewall.Packet) error {
|
|
||||||
dataLen := len(data)
|
|
||||||
if dataLen < ipv6.HeaderLen {
|
|
||||||
return ErrIPv6PacketTooShort
|
|
||||||
}
|
|
||||||
|
|
||||||
if incoming {
|
|
||||||
fp.RemoteAddr, _ = netip.AddrFromSlice(data[8:24])
|
|
||||||
fp.LocalAddr, _ = netip.AddrFromSlice(data[24:40])
|
|
||||||
} else {
|
|
||||||
fp.LocalAddr, _ = netip.AddrFromSlice(data[8:24])
|
|
||||||
fp.RemoteAddr, _ = netip.AddrFromSlice(data[24:40])
|
|
||||||
}
|
|
||||||
|
|
||||||
protoAt := 6 // NextHeader is at 6 bytes into the ipv6 header
|
|
||||||
offset := ipv6.HeaderLen // Start at the end of the ipv6 header
|
|
||||||
next := 0
|
|
||||||
for {
|
|
||||||
if protoAt >= dataLen {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
proto := layers.IPProtocol(data[protoAt])
|
|
||||||
|
|
||||||
switch proto {
|
|
||||||
case layers.IPProtocolESP, layers.IPProtocolNoNextHeader:
|
|
||||||
fp.Protocol = uint8(proto)
|
|
||||||
fp.RemotePort = 0
|
|
||||||
fp.LocalPort = 0
|
|
||||||
fp.Fragment = false
|
|
||||||
return nil
|
|
||||||
|
|
||||||
case layers.IPProtocolICMPv6:
|
|
||||||
if dataLen < offset+6 {
|
|
||||||
return ErrIPv6PacketTooShort
|
|
||||||
}
|
|
||||||
fp.Protocol = uint8(proto)
|
|
||||||
fp.LocalPort = 0 //incoming vs outgoing doesn't matter for icmpv6
|
|
||||||
icmptype := data[offset+1]
|
|
||||||
switch icmptype {
|
|
||||||
case layers.ICMPv6TypeEchoRequest, layers.ICMPv6TypeEchoReply:
|
|
||||||
fp.RemotePort = binary.BigEndian.Uint16(data[offset+4 : offset+6]) //identifier
|
|
||||||
default:
|
|
||||||
fp.RemotePort = 0
|
|
||||||
}
|
|
||||||
fp.Fragment = false
|
|
||||||
return nil
|
|
||||||
|
|
||||||
case layers.IPProtocolTCP, layers.IPProtocolUDP:
|
|
||||||
if dataLen < offset+4 {
|
|
||||||
return ErrIPv6PacketTooShort
|
|
||||||
}
|
|
||||||
|
|
||||||
fp.Protocol = uint8(proto)
|
|
||||||
if incoming {
|
|
||||||
fp.RemotePort = binary.BigEndian.Uint16(data[offset : offset+2])
|
|
||||||
fp.LocalPort = binary.BigEndian.Uint16(data[offset+2 : offset+4])
|
|
||||||
} else {
|
|
||||||
fp.LocalPort = binary.BigEndian.Uint16(data[offset : offset+2])
|
|
||||||
fp.RemotePort = binary.BigEndian.Uint16(data[offset+2 : offset+4])
|
|
||||||
}
|
|
||||||
|
|
||||||
fp.Fragment = false
|
|
||||||
return nil
|
|
||||||
|
|
||||||
case layers.IPProtocolIPv6Fragment:
|
|
||||||
// Fragment header is 8 bytes, need at least offset+4 to read the offset field
|
|
||||||
if dataLen < offset+8 {
|
|
||||||
return ErrIPv6PacketTooShort
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if this is the first fragment
|
|
||||||
fragmentOffset := binary.BigEndian.Uint16(data[offset+2:offset+4]) &^ uint16(0x7) // Remove the reserved and M flag bits
|
|
||||||
if fragmentOffset != 0 {
|
|
||||||
// Non-first fragment, use what we have now and stop processing
|
|
||||||
fp.Protocol = data[offset]
|
|
||||||
fp.Fragment = true
|
|
||||||
fp.RemotePort = 0
|
|
||||||
fp.LocalPort = 0
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// The next loop should be the transport layer since we are the first fragment
|
|
||||||
next = 8 // Fragment headers are always 8 bytes
|
|
||||||
|
|
||||||
case layers.IPProtocolAH:
|
|
||||||
// Auth headers, used by IPSec, have a different meaning for header length
|
|
||||||
if dataLen <= offset+1 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
next = int(data[offset+1]+2) << 2
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Normal ipv6 header length processing
|
|
||||||
if dataLen <= offset+1 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
next = int(data[offset+1]+1) << 3
|
|
||||||
}
|
|
||||||
|
|
||||||
if next <= 0 {
|
|
||||||
// Safety check, each ipv6 header has to be at least 8 bytes
|
|
||||||
next = 8
|
|
||||||
}
|
|
||||||
|
|
||||||
protoAt = offset
|
|
||||||
offset = offset + next
|
|
||||||
}
|
|
||||||
|
|
||||||
return ErrIPv6CouldNotFindPayload
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseV4(data []byte, incoming bool, fp *firewall.Packet) error {
|
|
||||||
// Do we at least have an ipv4 header worth of data?
|
|
||||||
if len(data) < ipv4.HeaderLen {
|
|
||||||
return ErrIPv4PacketTooShort
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adjust our start position based on the advertised ip header length
|
|
||||||
ihl := int(data[0]&0x0f) << 2
|
|
||||||
|
|
||||||
// Well-formed ip header length?
|
|
||||||
if ihl < ipv4.HeaderLen {
|
|
||||||
return ErrIPv4InvalidHeaderLength
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if this is the second or further fragment of a fragmented packet.
|
|
||||||
flagsfrags := binary.BigEndian.Uint16(data[6:8])
|
|
||||||
fp.Fragment = (flagsfrags & 0x1FFF) != 0
|
|
||||||
|
|
||||||
// Firewall handles protocol checks
|
|
||||||
fp.Protocol = data[9]
|
|
||||||
|
|
||||||
// Accounting for a variable header length, do we have enough data for our src/dst tuples?
|
|
||||||
minLen := ihl
|
|
||||||
if !fp.Fragment {
|
|
||||||
if fp.Protocol == firewall.ProtoICMP {
|
|
||||||
minLen += minFwPacketLen + 2
|
|
||||||
} else {
|
|
||||||
minLen += minFwPacketLen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(data) < minLen {
|
|
||||||
return ErrIPv4InvalidHeaderLength
|
|
||||||
}
|
|
||||||
|
|
||||||
if incoming { // Firewall packets are locally oriented
|
|
||||||
fp.RemoteAddr, _ = netip.AddrFromSlice(data[12:16])
|
|
||||||
fp.LocalAddr, _ = netip.AddrFromSlice(data[16:20])
|
|
||||||
} else {
|
|
||||||
fp.LocalAddr, _ = netip.AddrFromSlice(data[12:16])
|
|
||||||
fp.RemoteAddr, _ = netip.AddrFromSlice(data[16:20])
|
|
||||||
}
|
|
||||||
|
|
||||||
if fp.Fragment {
|
|
||||||
fp.RemotePort = 0
|
|
||||||
fp.LocalPort = 0
|
|
||||||
} else if fp.Protocol == firewall.ProtoICMP { //note that orientation doesn't matter on ICMP
|
|
||||||
fp.RemotePort = binary.BigEndian.Uint16(data[ihl+4 : ihl+6]) //identifier
|
|
||||||
fp.LocalPort = 0 //code would be uint16(data[ihl+1])
|
|
||||||
} else if incoming {
|
|
||||||
fp.RemotePort = binary.BigEndian.Uint16(data[ihl : ihl+2]) //src port
|
|
||||||
fp.LocalPort = binary.BigEndian.Uint16(data[ihl+2 : ihl+4]) //dst port
|
|
||||||
} else {
|
|
||||||
fp.LocalPort = binary.BigEndian.Uint16(data[ihl : ihl+2]) //src port
|
|
||||||
fp.RemotePort = binary.BigEndian.Uint16(data[ihl+2 : ihl+4]) //dst port
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Interface) decrypt(hostinfo *HostInfo, mc uint64, out []byte, packet []byte, h *header.H, nb []byte) ([]byte, error) {
|
func (f *Interface) decrypt(hostinfo *HostInfo, mc uint64, out []byte, packet []byte, h *header.H, nb []byte) ([]byte, error) {
|
||||||
var err error
|
var err error
|
||||||
out, err = hostinfo.ConnectionState.dKey.DecryptDanger(out, packet[:header.Len], packet[header.Len:], mc, nb)
|
out, err = hostinfo.ConnectionState.dKey.DecryptDanger(out, packet[:header.Len], packet[header.Len:], mc, nb)
|
||||||
@@ -501,7 +294,7 @@ func (f *Interface) decryptToTun(hostinfo *HostInfo, messageCounter uint64, out
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
err = newPacket(out, true, fwPacket)
|
err = firewall.NewPacket(out, true, fwPacket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hostinfo.logger(f.l).WithError(err).WithField("packet", out).
|
hostinfo.logger(f.l).WithError(err).WithField("packet", out).
|
||||||
Warnf("Error while validating inbound packet")
|
Warnf("Error while validating inbound packet")
|
||||||
|
|||||||
+37
-44
@@ -20,13 +20,13 @@ func Test_newPacket(t *testing.T) {
|
|||||||
p := &firewall.Packet{}
|
p := &firewall.Packet{}
|
||||||
|
|
||||||
// length fails
|
// length fails
|
||||||
err := newPacket([]byte{}, true, p)
|
err := firewall.NewPacket([]byte{}, true, p)
|
||||||
require.ErrorIs(t, err, ErrPacketTooShort)
|
require.ErrorIs(t, err, ErrPacketTooShort)
|
||||||
|
|
||||||
err = newPacket([]byte{0x40}, true, p)
|
err = firewall.NewPacket([]byte{0x40}, true, p)
|
||||||
require.ErrorIs(t, err, ErrIPv4PacketTooShort)
|
require.ErrorIs(t, err, ErrIPv4PacketTooShort)
|
||||||
|
|
||||||
err = newPacket([]byte{0x60}, true, p)
|
err = firewall.NewPacket([]byte{0x60}, true, p)
|
||||||
require.ErrorIs(t, err, ErrIPv6PacketTooShort)
|
require.ErrorIs(t, err, ErrIPv6PacketTooShort)
|
||||||
|
|
||||||
// length fail with ip options
|
// length fail with ip options
|
||||||
@@ -39,15 +39,15 @@ func Test_newPacket(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
b, _ := h.Marshal()
|
b, _ := h.Marshal()
|
||||||
err = newPacket(b, true, p)
|
err = firewall.NewPacket(b, true, p)
|
||||||
require.ErrorIs(t, err, ErrIPv4InvalidHeaderLength)
|
require.ErrorIs(t, err, ErrIPv4InvalidHeaderLength)
|
||||||
|
|
||||||
// not an ipv4 packet
|
// not an ipv4 packet
|
||||||
err = newPacket([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, true, p)
|
err = firewall.NewPacket([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, true, p)
|
||||||
require.ErrorIs(t, err, ErrUnknownIPVersion)
|
require.ErrorIs(t, err, ErrUnknownIPVersion)
|
||||||
|
|
||||||
// invalid ihl
|
// invalid ihl
|
||||||
err = newPacket([]byte{4<<4 | (8 >> 2 & 0x0f), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, true, p)
|
err = firewall.NewPacket([]byte{4<<4 | (8 >> 2 & 0x0f), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, true, p)
|
||||||
require.ErrorIs(t, err, ErrIPv4InvalidHeaderLength)
|
require.ErrorIs(t, err, ErrIPv4InvalidHeaderLength)
|
||||||
|
|
||||||
// account for variable ip header length - incoming
|
// account for variable ip header length - incoming
|
||||||
@@ -62,7 +62,7 @@ func Test_newPacket(t *testing.T) {
|
|||||||
|
|
||||||
b, _ = h.Marshal()
|
b, _ = h.Marshal()
|
||||||
b = append(b, []byte{0, 3, 0, 4}...)
|
b = append(b, []byte{0, 3, 0, 4}...)
|
||||||
err = newPacket(b, true, p)
|
err = firewall.NewPacket(b, true, p)
|
||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, uint8(firewall.ProtoTCP), p.Protocol)
|
assert.Equal(t, uint8(firewall.ProtoTCP), p.Protocol)
|
||||||
@@ -84,7 +84,7 @@ func Test_newPacket(t *testing.T) {
|
|||||||
|
|
||||||
b, _ = h.Marshal()
|
b, _ = h.Marshal()
|
||||||
b = append(b, []byte{0, 5, 0, 6}...)
|
b = append(b, []byte{0, 5, 0, 6}...)
|
||||||
err = newPacket(b, false, p)
|
err = firewall.NewPacket(b, false, p)
|
||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, uint8(2), p.Protocol)
|
assert.Equal(t, uint8(2), p.Protocol)
|
||||||
@@ -114,7 +114,7 @@ func Test_newPacket_v6(t *testing.T) {
|
|||||||
err := gopacket.SerializeLayers(buffer, opt, &ip)
|
err := gopacket.SerializeLayers(buffer, opt, &ip)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = newPacket(buffer.Bytes(), true, p)
|
err = firewall.NewPacket(buffer.Bytes(), true, p)
|
||||||
require.ErrorIs(t, err, ErrIPv6CouldNotFindPayload)
|
require.ErrorIs(t, err, ErrIPv6CouldNotFindPayload)
|
||||||
|
|
||||||
// A v6 packet with a hop-by-hop extension
|
// A v6 packet with a hop-by-hop extension
|
||||||
@@ -148,14 +148,13 @@ func Test_newPacket_v6(t *testing.T) {
|
|||||||
|
|
||||||
// A full IPv6 header and 1 byte in the first extension, but missing
|
// A full IPv6 header and 1 byte in the first extension, but missing
|
||||||
// the length byte.
|
// the length byte.
|
||||||
err = newPacket(buffer.Bytes()[:41], true, p)
|
err = firewall.NewPacket(buffer.Bytes()[:41], true, p)
|
||||||
require.ErrorIs(t, err, ErrIPv6CouldNotFindPayload)
|
require.ErrorIs(t, err, ErrIPv6CouldNotFindPayload)
|
||||||
|
|
||||||
// A full IPv6 header plus 1 full extension, but only 1 byte of the
|
// A full IPv6 header plus 1 full extension, but only 1 byte of the
|
||||||
// next layer, missing length byte
|
// next layer, missing length byte
|
||||||
err = newPacket(buffer.Bytes()[:49], true, p)
|
err = firewall.NewPacket(buffer.Bytes()[:49], true, p)
|
||||||
require.ErrorIs(t, err, ErrIPv6CouldNotFindPayload)
|
require.ErrorIs(t, err, ErrIPv6CouldNotFindPayload)
|
||||||
err = nil
|
|
||||||
|
|
||||||
// A good ICMP packet
|
// A good ICMP packet
|
||||||
ip = layers.IPv6{
|
ip = layers.IPv6{
|
||||||
@@ -166,33 +165,27 @@ func Test_newPacket_v6(t *testing.T) {
|
|||||||
DstIP: net.IPv6linklocalallnodes,
|
DstIP: net.IPv6linklocalallnodes,
|
||||||
}
|
}
|
||||||
|
|
||||||
icmp := layers.ICMPv6{
|
icmp := layers.ICMPv6{}
|
||||||
TypeCode: layers.ICMPv6TypeEchoRequest,
|
|
||||||
Checksum: 0x1234,
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.Clear()
|
buffer.Clear()
|
||||||
require.NoError(t, gopacket.SerializeLayers(buffer, opt, &ip, &icmp))
|
err = gopacket.SerializeLayers(buffer, opt, &ip, &icmp)
|
||||||
require.Error(t, newPacket(buffer.Bytes(), true, p))
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
buffer.Clear()
|
|
||||||
echo := layers.ICMPv6Echo{
|
|
||||||
Identifier: 0xabcd,
|
|
||||||
SeqNumber: 1234,
|
|
||||||
}
|
}
|
||||||
require.NoError(t, gopacket.SerializeLayers(buffer, opt, &ip, &icmp, &echo))
|
|
||||||
require.NoError(t, newPacket(buffer.Bytes(), true, p))
|
err = firewall.NewPacket(buffer.Bytes(), true, p)
|
||||||
|
require.NoError(t, err)
|
||||||
assert.Equal(t, uint8(layers.IPProtocolICMPv6), p.Protocol)
|
assert.Equal(t, uint8(layers.IPProtocolICMPv6), p.Protocol)
|
||||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
|
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
|
||||||
assert.Equal(t, netip.MustParseAddr("ff02::1"), p.LocalAddr)
|
assert.Equal(t, netip.MustParseAddr("ff02::1"), p.LocalAddr)
|
||||||
assert.Equal(t, uint16(0xabcd), p.RemotePort)
|
assert.Equal(t, uint16(0), p.RemotePort)
|
||||||
assert.Equal(t, uint16(0), p.LocalPort)
|
assert.Equal(t, uint16(0), p.LocalPort)
|
||||||
assert.False(t, p.Fragment)
|
assert.False(t, p.Fragment)
|
||||||
|
|
||||||
// A good ESP packet
|
// A good ESP packet
|
||||||
b := buffer.Bytes()
|
b := buffer.Bytes()
|
||||||
b[6] = byte(layers.IPProtocolESP)
|
b[6] = byte(layers.IPProtocolESP)
|
||||||
err = newPacket(b, true, p)
|
err = firewall.NewPacket(b, true, p)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, uint8(layers.IPProtocolESP), p.Protocol)
|
assert.Equal(t, uint8(layers.IPProtocolESP), p.Protocol)
|
||||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
|
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
|
||||||
@@ -204,7 +197,7 @@ func Test_newPacket_v6(t *testing.T) {
|
|||||||
// A good None packet
|
// A good None packet
|
||||||
b = buffer.Bytes()
|
b = buffer.Bytes()
|
||||||
b[6] = byte(layers.IPProtocolNoNextHeader)
|
b[6] = byte(layers.IPProtocolNoNextHeader)
|
||||||
err = newPacket(b, true, p)
|
err = firewall.NewPacket(b, true, p)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, uint8(layers.IPProtocolNoNextHeader), p.Protocol)
|
assert.Equal(t, uint8(layers.IPProtocolNoNextHeader), p.Protocol)
|
||||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
|
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
|
||||||
@@ -216,7 +209,7 @@ func Test_newPacket_v6(t *testing.T) {
|
|||||||
// An unknown protocol packet
|
// An unknown protocol packet
|
||||||
b = buffer.Bytes()
|
b = buffer.Bytes()
|
||||||
b[6] = 255 // 255 is a reserved protocol number
|
b[6] = 255 // 255 is a reserved protocol number
|
||||||
err = newPacket(b, true, p)
|
err = firewall.NewPacket(b, true, p)
|
||||||
require.ErrorIs(t, err, ErrIPv6CouldNotFindPayload)
|
require.ErrorIs(t, err, ErrIPv6CouldNotFindPayload)
|
||||||
|
|
||||||
// A good UDP packet
|
// A good UDP packet
|
||||||
@@ -243,7 +236,7 @@ func Test_newPacket_v6(t *testing.T) {
|
|||||||
b = buffer.Bytes()
|
b = buffer.Bytes()
|
||||||
|
|
||||||
// incoming
|
// incoming
|
||||||
err = newPacket(b, true, p)
|
err = firewall.NewPacket(b, true, p)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, uint8(firewall.ProtoUDP), p.Protocol)
|
assert.Equal(t, uint8(firewall.ProtoUDP), p.Protocol)
|
||||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
|
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
|
||||||
@@ -253,7 +246,7 @@ func Test_newPacket_v6(t *testing.T) {
|
|||||||
assert.False(t, p.Fragment)
|
assert.False(t, p.Fragment)
|
||||||
|
|
||||||
// outgoing
|
// outgoing
|
||||||
err = newPacket(b, false, p)
|
err = firewall.NewPacket(b, false, p)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, uint8(firewall.ProtoUDP), p.Protocol)
|
assert.Equal(t, uint8(firewall.ProtoUDP), p.Protocol)
|
||||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.LocalAddr)
|
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.LocalAddr)
|
||||||
@@ -263,14 +256,14 @@ func Test_newPacket_v6(t *testing.T) {
|
|||||||
assert.False(t, p.Fragment)
|
assert.False(t, p.Fragment)
|
||||||
|
|
||||||
// Too short UDP packet
|
// Too short UDP packet
|
||||||
err = newPacket(b[:len(b)-10], false, p) // pull off the last 10 bytes
|
err = firewall.NewPacket(b[:len(b)-10], false, p) // pull off the last 10 bytes
|
||||||
require.ErrorIs(t, err, ErrIPv6PacketTooShort)
|
require.ErrorIs(t, err, ErrIPv6PacketTooShort)
|
||||||
|
|
||||||
// A good TCP packet
|
// A good TCP packet
|
||||||
b[6] = byte(layers.IPProtocolTCP)
|
b[6] = byte(layers.IPProtocolTCP)
|
||||||
|
|
||||||
// incoming
|
// incoming
|
||||||
err = newPacket(b, true, p)
|
err = firewall.NewPacket(b, true, p)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, uint8(firewall.ProtoTCP), p.Protocol)
|
assert.Equal(t, uint8(firewall.ProtoTCP), p.Protocol)
|
||||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
|
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
|
||||||
@@ -280,7 +273,7 @@ func Test_newPacket_v6(t *testing.T) {
|
|||||||
assert.False(t, p.Fragment)
|
assert.False(t, p.Fragment)
|
||||||
|
|
||||||
// outgoing
|
// outgoing
|
||||||
err = newPacket(b, false, p)
|
err = firewall.NewPacket(b, false, p)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, uint8(firewall.ProtoTCP), p.Protocol)
|
assert.Equal(t, uint8(firewall.ProtoTCP), p.Protocol)
|
||||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.LocalAddr)
|
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.LocalAddr)
|
||||||
@@ -290,7 +283,7 @@ func Test_newPacket_v6(t *testing.T) {
|
|||||||
assert.False(t, p.Fragment)
|
assert.False(t, p.Fragment)
|
||||||
|
|
||||||
// Too short TCP packet
|
// Too short TCP packet
|
||||||
err = newPacket(b[:len(b)-10], false, p) // pull off the last 10 bytes
|
err = firewall.NewPacket(b[:len(b)-10], false, p) // pull off the last 10 bytes
|
||||||
require.ErrorIs(t, err, ErrIPv6PacketTooShort)
|
require.ErrorIs(t, err, ErrIPv6PacketTooShort)
|
||||||
|
|
||||||
// A good UDP packet with an AH header
|
// A good UDP packet with an AH header
|
||||||
@@ -325,7 +318,7 @@ func Test_newPacket_v6(t *testing.T) {
|
|||||||
b = append(b, ahb...)
|
b = append(b, ahb...)
|
||||||
b = append(b, udpHeader...)
|
b = append(b, udpHeader...)
|
||||||
|
|
||||||
err = newPacket(b, true, p)
|
err = firewall.NewPacket(b, true, p)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, uint8(firewall.ProtoUDP), p.Protocol)
|
assert.Equal(t, uint8(firewall.ProtoUDP), p.Protocol)
|
||||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
|
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
|
||||||
@@ -335,12 +328,12 @@ func Test_newPacket_v6(t *testing.T) {
|
|||||||
assert.False(t, p.Fragment)
|
assert.False(t, p.Fragment)
|
||||||
|
|
||||||
// Ensure buffer bounds checking during processing
|
// Ensure buffer bounds checking during processing
|
||||||
err = newPacket(b[:41], true, p)
|
err = firewall.NewPacket(b[:41], true, p)
|
||||||
require.ErrorIs(t, err, ErrIPv6PacketTooShort)
|
require.ErrorIs(t, err, ErrIPv6PacketTooShort)
|
||||||
|
|
||||||
// Invalid AH header
|
// Invalid AH header
|
||||||
b = buffer.Bytes()
|
b = buffer.Bytes()
|
||||||
err = newPacket(b, true, p)
|
err = firewall.NewPacket(b, true, p)
|
||||||
require.ErrorIs(t, err, ErrIPv6CouldNotFindPayload)
|
require.ErrorIs(t, err, ErrIPv6CouldNotFindPayload)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,7 +381,7 @@ func Test_newPacket_ipv6Fragment(t *testing.T) {
|
|||||||
firstFrag = append(firstFrag, []byte{0xde, 0xad, 0xbe, 0xef}...)
|
firstFrag = append(firstFrag, []byte{0xde, 0xad, 0xbe, 0xef}...)
|
||||||
|
|
||||||
// Test first fragment incoming
|
// Test first fragment incoming
|
||||||
err = newPacket(firstFrag, true, p)
|
err = firewall.NewPacket(firstFrag, true, p)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
|
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
|
||||||
assert.Equal(t, netip.MustParseAddr("ff02::1"), p.LocalAddr)
|
assert.Equal(t, netip.MustParseAddr("ff02::1"), p.LocalAddr)
|
||||||
@@ -398,7 +391,7 @@ func Test_newPacket_ipv6Fragment(t *testing.T) {
|
|||||||
assert.False(t, p.Fragment)
|
assert.False(t, p.Fragment)
|
||||||
|
|
||||||
// Test first fragment outgoing
|
// Test first fragment outgoing
|
||||||
err = newPacket(firstFrag, false, p)
|
err = firewall.NewPacket(firstFrag, false, p)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.LocalAddr)
|
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.LocalAddr)
|
||||||
assert.Equal(t, netip.MustParseAddr("ff02::1"), p.RemoteAddr)
|
assert.Equal(t, netip.MustParseAddr("ff02::1"), p.RemoteAddr)
|
||||||
@@ -427,7 +420,7 @@ func Test_newPacket_ipv6Fragment(t *testing.T) {
|
|||||||
secondFrag = append(secondFrag, []byte{0xde, 0xad, 0xbe, 0xef}...)
|
secondFrag = append(secondFrag, []byte{0xde, 0xad, 0xbe, 0xef}...)
|
||||||
|
|
||||||
// Test second fragment incoming
|
// Test second fragment incoming
|
||||||
err = newPacket(secondFrag, true, p)
|
err = firewall.NewPacket(secondFrag, true, p)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
|
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
|
||||||
assert.Equal(t, netip.MustParseAddr("ff02::1"), p.LocalAddr)
|
assert.Equal(t, netip.MustParseAddr("ff02::1"), p.LocalAddr)
|
||||||
@@ -437,7 +430,7 @@ func Test_newPacket_ipv6Fragment(t *testing.T) {
|
|||||||
assert.True(t, p.Fragment)
|
assert.True(t, p.Fragment)
|
||||||
|
|
||||||
// Test second fragment outgoing
|
// Test second fragment outgoing
|
||||||
err = newPacket(secondFrag, false, p)
|
err = firewall.NewPacket(secondFrag, false, p)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.LocalAddr)
|
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.LocalAddr)
|
||||||
assert.Equal(t, netip.MustParseAddr("ff02::1"), p.RemoteAddr)
|
assert.Equal(t, netip.MustParseAddr("ff02::1"), p.RemoteAddr)
|
||||||
@@ -447,7 +440,7 @@ func Test_newPacket_ipv6Fragment(t *testing.T) {
|
|||||||
assert.True(t, p.Fragment)
|
assert.True(t, p.Fragment)
|
||||||
|
|
||||||
// Too short of a fragment packet
|
// Too short of a fragment packet
|
||||||
err = newPacket(secondFrag[:len(secondFrag)-10], false, p)
|
err = firewall.NewPacket(secondFrag[:len(secondFrag)-10], false, p)
|
||||||
require.ErrorIs(t, err, ErrIPv6PacketTooShort)
|
require.ErrorIs(t, err, ErrIPv6PacketTooShort)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -581,7 +574,7 @@ func BenchmarkParseV6(b *testing.B) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
evilBytes := buffer.Bytes()
|
evilBytes := buffer.Bytes()
|
||||||
for range 200 {
|
for i := 0; i < 200; i++ {
|
||||||
evilBytes = append(evilBytes, hopHeader...)
|
evilBytes = append(evilBytes, hopHeader...)
|
||||||
}
|
}
|
||||||
evilBytes = append(evilBytes, lastHopHeader...)
|
evilBytes = append(evilBytes, lastHopHeader...)
|
||||||
|
|||||||
+30
-5
@@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/rcrowley/go-metrics"
|
"github.com/rcrowley/go-metrics"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/slackhq/nebula/firewall"
|
||||||
"github.com/slackhq/nebula/iputil"
|
"github.com/slackhq/nebula/iputil"
|
||||||
"github.com/slackhq/nebula/routing"
|
"github.com/slackhq/nebula/routing"
|
||||||
)
|
)
|
||||||
@@ -87,21 +88,45 @@ func (t *disabledTun) handleICMPEchoRequest(b []byte) bool {
|
|||||||
default:
|
default:
|
||||||
t.l.Debugf("tun_disabled: dropped ICMP Echo Reply response")
|
t.l.Debugf("tun_disabled: dropped ICMP Echo Reply response")
|
||||||
}
|
}
|
||||||
|
if t.l.Level >= logrus.DebugLevel {
|
||||||
|
t.l.WithField("raw", prettyPacket(b)).Debugf("Disabled tun responded to ICMP Echo Request")
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *disabledTun) handleOtherPackets(b []byte) error {
|
||||||
|
fp := &firewall.Packet{}
|
||||||
|
err := firewall.NewPacket(b, true, fp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
out := make([]byte, len(b)) //todo do something!
|
||||||
|
|
||||||
|
// attempt to write it, but don't block
|
||||||
|
select {
|
||||||
|
case t.read <- out:
|
||||||
|
default:
|
||||||
|
t.l.Debugf("tun_disabled: dropped reply")
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (t *disabledTun) Write(b []byte) (int, error) {
|
func (t *disabledTun) Write(b []byte) (int, error) {
|
||||||
t.rx.Inc(1)
|
t.rx.Inc(1)
|
||||||
|
|
||||||
// Check for ICMP Echo Request before spending time doing the full parsing
|
|
||||||
if t.handleICMPEchoRequest(b) {
|
if t.handleICMPEchoRequest(b) {
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
if err := t.handleOtherPackets(b); err != nil {
|
||||||
if t.l.Level >= logrus.DebugLevel {
|
if t.l.Level >= logrus.DebugLevel {
|
||||||
t.l.WithField("raw", prettyPacket(b)).Debugf("Disabled tun responded to ICMP Echo Request")
|
t.l.WithField("raw", prettyPacket(b)).WithError(err).Debugf("Disabled tun failed to respond")
|
||||||
}
|
}
|
||||||
} else if t.l.Level >= logrus.DebugLevel {
|
return len(b), nil
|
||||||
|
}
|
||||||
|
if t.l.Level >= logrus.DebugLevel {
|
||||||
t.l.WithField("raw", prettyPacket(b)).Debugf("Disabled tun received unexpected payload")
|
t.l.WithField("raw", prettyPacket(b)).Debugf("Disabled tun received unexpected payload")
|
||||||
}
|
}
|
||||||
|
|
||||||
return len(b), nil
|
return len(b), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -55,7 +55,7 @@ func (rm *relayManager) setAmRelay(v bool) {
|
|||||||
func AddRelay(l *logrus.Logger, relayHostInfo *HostInfo, hm *HostMap, vpnIp netip.Addr, remoteIdx *uint32, relayType int, state int) (uint32, error) {
|
func AddRelay(l *logrus.Logger, relayHostInfo *HostInfo, hm *HostMap, vpnIp netip.Addr, remoteIdx *uint32, relayType int, state int) (uint32, error) {
|
||||||
hm.Lock()
|
hm.Lock()
|
||||||
defer hm.Unlock()
|
defer hm.Unlock()
|
||||||
for range 32 {
|
for i := 0; i < 32; i++ {
|
||||||
index, err := generateIndex(l)
|
index, err := generateIndex(l)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
|||||||
+6
-1
@@ -404,7 +404,12 @@ func (r *RemoteList) Rebuild(preferredRanges []netip.Prefix) {
|
|||||||
|
|
||||||
// unlockedIsBad assumes you have the write lock and checks if the remote matches any entry in the blocked address list
|
// unlockedIsBad assumes you have the write lock and checks if the remote matches any entry in the blocked address list
|
||||||
func (r *RemoteList) unlockedIsBad(remote netip.AddrPort) bool {
|
func (r *RemoteList) unlockedIsBad(remote netip.AddrPort) bool {
|
||||||
return slices.Contains(r.badRemotes, remote)
|
for _, v := range r.badRemotes {
|
||||||
|
if v == remote {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// unlockedSetLearnedV4 assumes you have the write lock and sets the current learned address for this owner and marks the
|
// unlockedSetLearnedV4 assumes you have the write lock and sets the current learned address for this owner and marks the
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"maps"
|
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
@@ -832,7 +831,9 @@ func sshPrintRelays(ifce *Interface, fs any, a []string, w sshd.StringWriter) er
|
|||||||
|
|
||||||
relays := map[uint32]*HostInfo{}
|
relays := map[uint32]*HostInfo{}
|
||||||
ifce.hostMap.Lock()
|
ifce.hostMap.Lock()
|
||||||
maps.Copy(relays, ifce.hostMap.Relays)
|
for k, v := range ifce.hostMap.Relays {
|
||||||
|
relays[k] = v
|
||||||
|
}
|
||||||
ifce.hostMap.Unlock()
|
ifce.hostMap.Unlock()
|
||||||
|
|
||||||
type RelayFor struct {
|
type RelayFor struct {
|
||||||
|
|||||||
+2
-2
@@ -134,7 +134,7 @@ func TestTimerWheel_Purge(t *testing.T) {
|
|||||||
assert.True(t, tw.lastTick.After(lastTick))
|
assert.True(t, tw.lastTick.After(lastTick))
|
||||||
|
|
||||||
// Make sure we get all 4 packets back
|
// Make sure we get all 4 packets back
|
||||||
for i := range 4 {
|
for i := 0; i < 4; i++ {
|
||||||
p, has := tw.Purge()
|
p, has := tw.Purge()
|
||||||
assert.True(t, has)
|
assert.True(t, has)
|
||||||
assert.Equal(t, fps[i], p)
|
assert.Equal(t, fps[i], p)
|
||||||
@@ -149,7 +149,7 @@ func TestTimerWheel_Purge(t *testing.T) {
|
|||||||
// Make sure we cached the free'd items
|
// Make sure we cached the free'd items
|
||||||
assert.Equal(t, 4, tw.itemsCached)
|
assert.Equal(t, 4, tw.itemsCached)
|
||||||
ci := tw.itemCache
|
ci := tw.itemCache
|
||||||
for range 4 {
|
for i := 0; i < 4; i++ {
|
||||||
assert.NotNil(t, ci)
|
assert.NotNil(t, ci)
|
||||||
ci = ci.Next
|
ci = ci.Next
|
||||||
}
|
}
|
||||||
|
|||||||
+103
-158
@@ -4,7 +4,6 @@
|
|||||||
package udp
|
package udp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
@@ -19,66 +18,58 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type StdConn struct {
|
type StdConn struct {
|
||||||
udpConn *net.UDPConn
|
sysFd int
|
||||||
rawConn syscall.RawConn
|
isV4 bool
|
||||||
sockFd int
|
l *logrus.Logger
|
||||||
isV4 bool
|
batch int
|
||||||
l *logrus.Logger
|
|
||||||
batch int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func setReusePort(network, address string, c syscall.RawConn) error {
|
func maybeIPV4(ip net.IP) (net.IP, bool) {
|
||||||
var opErr error
|
ip4 := ip.To4()
|
||||||
err := c.Control(func(fd uintptr) {
|
if ip4 != nil {
|
||||||
opErr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
|
return ip4, true
|
||||||
//CloseOnExec already set by the runtime
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
return opErr
|
return ip, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewListener(l *logrus.Logger, ip netip.Addr, port int, multi bool, batch int) (Conn, error) {
|
func NewListener(l *logrus.Logger, ip netip.Addr, port int, multi bool, batch int) (Conn, error) {
|
||||||
listen := netip.AddrPortFrom(ip, uint16(port))
|
af := unix.AF_INET6
|
||||||
lc := net.ListenConfig{}
|
if ip.Is4() {
|
||||||
if multi {
|
af = unix.AF_INET
|
||||||
lc.Control = setReusePort
|
|
||||||
}
|
}
|
||||||
network := "udp"
|
syscall.ForkLock.RLock()
|
||||||
if ip.Is4() { //todo I am not a fan of this, would prefer a default where we always bind dual-stack unless given a non-wildcard v4 addr
|
fd, err := unix.Socket(af, unix.SOCK_DGRAM, unix.IPPROTO_UDP)
|
||||||
network = "udp4"
|
if err == nil {
|
||||||
|
unix.CloseOnExec(fd)
|
||||||
}
|
}
|
||||||
//this context is only used during the bind operation, you can't cancel it to kill the socket
|
syscall.ForkLock.RUnlock()
|
||||||
pc, err := lc.ListenPacket(context.Background(), network, listen.String())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
unix.Close(fd)
|
||||||
return nil, fmt.Errorf("unable to open socket: %s", err)
|
return nil, fmt.Errorf("unable to open socket: %s", err)
|
||||||
}
|
}
|
||||||
udpConn := pc.(*net.UDPConn)
|
|
||||||
rawConn, err := udpConn.SyscallConn()
|
if multi {
|
||||||
if err != nil {
|
if err = unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_REUSEPORT, 1); err != nil {
|
||||||
_ = udpConn.Close()
|
return nil, fmt.Errorf("unable to set SO_REUSEPORT: %s", err)
|
||||||
return nil, err
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//steal the socket's fd for sending
|
var sa unix.Sockaddr
|
||||||
var sockFd uintptr
|
if ip.Is4() {
|
||||||
err = rawConn.Control(func(fd uintptr) {
|
sa4 := &unix.SockaddrInet4{Port: port}
|
||||||
sockFd = fd
|
sa4.Addr = ip.As4()
|
||||||
})
|
sa = sa4
|
||||||
if err != nil {
|
} else {
|
||||||
_ = udpConn.Close()
|
sa6 := &unix.SockaddrInet6{Port: port}
|
||||||
return nil, err
|
sa6.Addr = ip.As16()
|
||||||
|
sa = sa6
|
||||||
|
}
|
||||||
|
if err = unix.Bind(fd, sa); err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to bind to socket: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &StdConn{
|
return &StdConn{sysFd: fd, isV4: ip.Is4(), l: l, batch: batch}, err
|
||||||
udpConn: udpConn,
|
|
||||||
rawConn: rawConn,
|
|
||||||
sockFd: int(sockFd),
|
|
||||||
isV4: ip.Is4(),
|
|
||||||
l: l,
|
|
||||||
batch: batch,
|
|
||||||
}, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *StdConn) SupportsMultipleReaders() bool {
|
func (u *StdConn) SupportsMultipleReaders() bool {
|
||||||
@@ -89,126 +80,63 @@ func (u *StdConn) Rebind() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *StdConn) getSockOptInt(opt int) (int, error) {
|
|
||||||
if u.rawConn == nil {
|
|
||||||
return 0, fmt.Errorf("no UDP connection")
|
|
||||||
}
|
|
||||||
var out int
|
|
||||||
var opErr error
|
|
||||||
err := u.rawConn.Control(func(fd uintptr) {
|
|
||||||
out, opErr = unix.GetsockoptInt(int(fd), unix.SOL_SOCKET, opt)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return out, opErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *StdConn) setSockOptInt(opt int, n int) error {
|
|
||||||
if u.rawConn == nil {
|
|
||||||
return fmt.Errorf("no UDP connection")
|
|
||||||
}
|
|
||||||
var opErr error
|
|
||||||
err := u.rawConn.Control(func(fd uintptr) {
|
|
||||||
opErr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, opt, n)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return opErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *StdConn) SetRecvBuffer(n int) error {
|
func (u *StdConn) SetRecvBuffer(n int) error {
|
||||||
return u.setSockOptInt(unix.SO_RCVBUFFORCE, n)
|
return unix.SetsockoptInt(u.sysFd, unix.SOL_SOCKET, unix.SO_RCVBUFFORCE, n)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *StdConn) SetSendBuffer(n int) error {
|
func (u *StdConn) SetSendBuffer(n int) error {
|
||||||
return u.setSockOptInt(unix.SO_SNDBUFFORCE, n)
|
return unix.SetsockoptInt(u.sysFd, unix.SOL_SOCKET, unix.SO_SNDBUFFORCE, n)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *StdConn) SetSoMark(mark int) error {
|
func (u *StdConn) SetSoMark(mark int) error {
|
||||||
return u.setSockOptInt(unix.SO_MARK, mark)
|
return unix.SetsockoptInt(u.sysFd, unix.SOL_SOCKET, unix.SO_MARK, mark)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *StdConn) GetRecvBuffer() (int, error) {
|
func (u *StdConn) GetRecvBuffer() (int, error) {
|
||||||
return u.getSockOptInt(unix.SO_RCVBUF)
|
return unix.GetsockoptInt(int(u.sysFd), unix.SOL_SOCKET, unix.SO_RCVBUF)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *StdConn) GetSendBuffer() (int, error) {
|
func (u *StdConn) GetSendBuffer() (int, error) {
|
||||||
return u.getSockOptInt(unix.SO_SNDBUF)
|
return unix.GetsockoptInt(int(u.sysFd), unix.SOL_SOCKET, unix.SO_SNDBUF)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *StdConn) GetSoMark() (int, error) {
|
func (u *StdConn) GetSoMark() (int, error) {
|
||||||
return u.getSockOptInt(unix.SO_MARK)
|
return unix.GetsockoptInt(int(u.sysFd), unix.SOL_SOCKET, unix.SO_MARK)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *StdConn) LocalAddr() (netip.AddrPort, error) {
|
func (u *StdConn) LocalAddr() (netip.AddrPort, error) {
|
||||||
addr := u.udpConn.LocalAddr()
|
sa, err := unix.Getsockname(u.sysFd)
|
||||||
return netip.ParseAddrPort(addr.String())
|
if err != nil {
|
||||||
}
|
return netip.AddrPort{}, err
|
||||||
|
|
||||||
func recvmmsg(fd uintptr, msgs []rawMessage) (int, bool, error) {
|
|
||||||
var errno syscall.Errno
|
|
||||||
n, _, errno := unix.Syscall6(
|
|
||||||
unix.SYS_RECVMMSG,
|
|
||||||
fd,
|
|
||||||
uintptr(unsafe.Pointer(&msgs[0])),
|
|
||||||
uintptr(len(msgs)),
|
|
||||||
unix.MSG_WAITFORONE,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
if errno == syscall.EAGAIN || errno == syscall.EWOULDBLOCK {
|
|
||||||
// No data available, block for I/O and try again.
|
|
||||||
return int(n), false, nil
|
|
||||||
}
|
}
|
||||||
if errno != 0 {
|
|
||||||
return int(n), true, &net.OpError{Op: "recvmmsg", Err: errno}
|
|
||||||
}
|
|
||||||
return int(n), true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *StdConn) listenOutSingle(r EncReader) {
|
switch sa := sa.(type) {
|
||||||
var err error
|
case *unix.SockaddrInet4:
|
||||||
var n int
|
return netip.AddrPortFrom(netip.AddrFrom4(sa.Addr), uint16(sa.Port)), nil
|
||||||
var from netip.AddrPort
|
|
||||||
buffer := make([]byte, MTU)
|
|
||||||
|
|
||||||
for {
|
case *unix.SockaddrInet6:
|
||||||
n, from, err = u.udpConn.ReadFromUDPAddrPort(buffer)
|
return netip.AddrPortFrom(netip.AddrFrom16(sa.Addr), uint16(sa.Port)), nil
|
||||||
if err != nil {
|
|
||||||
u.l.WithError(err).Debug("udp socket is closed, exiting read loop")
|
default:
|
||||||
return
|
return netip.AddrPort{}, fmt.Errorf("unsupported sock type: %T", sa)
|
||||||
}
|
|
||||||
from = netip.AddrPortFrom(from.Addr().Unmap(), from.Port())
|
|
||||||
r(from, buffer[:n])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *StdConn) listenOutBatch(r EncReader) {
|
func (u *StdConn) ListenOut(r EncReader) {
|
||||||
var ip netip.Addr
|
var ip netip.Addr
|
||||||
var n int
|
|
||||||
var operr error
|
|
||||||
|
|
||||||
msgs, buffers, names := u.PrepareRawMessages(u.batch)
|
msgs, buffers, names := u.PrepareRawMessages(u.batch)
|
||||||
|
read := u.ReadMulti
|
||||||
//reader needs to capture variables from this function, since it's used as a lambda with rawConn.Read
|
if u.batch == 1 {
|
||||||
//defining it outside the loop so it gets re-used
|
read = u.ReadSingle
|
||||||
reader := func(fd uintptr) (done bool) {
|
|
||||||
n, done, operr = recvmmsg(fd, msgs)
|
|
||||||
return done
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
err := u.rawConn.Read(reader)
|
n, err := read(msgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
u.l.WithError(err).Debug("udp socket is closed, exiting read loop")
|
u.l.WithError(err).Debug("udp socket is closed, exiting read loop")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if operr != nil {
|
|
||||||
u.l.WithError(operr).Debug("operr: udp socket is closed, exiting read loop")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
// Its ok to skip the ok check here, the slicing is the only error that can occur and it will panic
|
// Its ok to skip the ok check here, the slicing is the only error that can occur and it will panic
|
||||||
@@ -222,14 +150,44 @@ func (u *StdConn) listenOutBatch(r EncReader) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *StdConn) ListenOut(r EncReader) {
|
func (u *StdConn) ReadSingle(msgs []rawMessage) (int, error) {
|
||||||
if u.batch == 1 {
|
for {
|
||||||
//save some ram by not calling PrepareRawMessages for fields we won't use
|
n, _, err := unix.Syscall6(
|
||||||
//we could also make this path more common by calling recvmmsg with msgs[:1],
|
unix.SYS_RECVMSG,
|
||||||
//but that's still the recvmmsg syscall, which would be a change
|
uintptr(u.sysFd),
|
||||||
u.listenOutSingle(r)
|
uintptr(unsafe.Pointer(&(msgs[0].Hdr))),
|
||||||
} else {
|
0,
|
||||||
u.listenOutBatch(r)
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != 0 {
|
||||||
|
return 0, &net.OpError{Op: "recvmsg", Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
msgs[0].Len = uint32(n)
|
||||||
|
return 1, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *StdConn) ReadMulti(msgs []rawMessage) (int, error) {
|
||||||
|
for {
|
||||||
|
n, _, err := unix.Syscall6(
|
||||||
|
unix.SYS_RECVMMSG,
|
||||||
|
uintptr(u.sysFd),
|
||||||
|
uintptr(unsafe.Pointer(&msgs[0])),
|
||||||
|
uintptr(len(msgs)),
|
||||||
|
unix.MSG_WAITFORONE,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != 0 {
|
||||||
|
return 0, &net.OpError{Op: "recvmmsg", Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(n), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,7 +207,7 @@ func (u *StdConn) writeTo6(b []byte, ip netip.AddrPort) error {
|
|||||||
for {
|
for {
|
||||||
_, _, err := unix.Syscall6(
|
_, _, err := unix.Syscall6(
|
||||||
unix.SYS_SENDTO,
|
unix.SYS_SENDTO,
|
||||||
uintptr(u.sockFd),
|
uintptr(u.sysFd),
|
||||||
uintptr(unsafe.Pointer(&b[0])),
|
uintptr(unsafe.Pointer(&b[0])),
|
||||||
uintptr(len(b)),
|
uintptr(len(b)),
|
||||||
uintptr(0),
|
uintptr(0),
|
||||||
@@ -278,7 +236,7 @@ func (u *StdConn) writeTo4(b []byte, ip netip.AddrPort) error {
|
|||||||
for {
|
for {
|
||||||
_, _, err := unix.Syscall6(
|
_, _, err := unix.Syscall6(
|
||||||
unix.SYS_SENDTO,
|
unix.SYS_SENDTO,
|
||||||
uintptr(u.sockFd),
|
uintptr(u.sysFd),
|
||||||
uintptr(unsafe.Pointer(&b[0])),
|
uintptr(unsafe.Pointer(&b[0])),
|
||||||
uintptr(len(b)),
|
uintptr(len(b)),
|
||||||
uintptr(0),
|
uintptr(0),
|
||||||
@@ -344,28 +302,15 @@ func (u *StdConn) ReloadConfig(c *config.C) {
|
|||||||
|
|
||||||
func (u *StdConn) getMemInfo(meminfo *[unix.SK_MEMINFO_VARS]uint32) error {
|
func (u *StdConn) getMemInfo(meminfo *[unix.SK_MEMINFO_VARS]uint32) error {
|
||||||
var vallen uint32 = 4 * unix.SK_MEMINFO_VARS
|
var vallen uint32 = 4 * unix.SK_MEMINFO_VARS
|
||||||
|
_, _, err := unix.Syscall6(unix.SYS_GETSOCKOPT, uintptr(u.sysFd), uintptr(unix.SOL_SOCKET), uintptr(unix.SO_MEMINFO), uintptr(unsafe.Pointer(meminfo)), uintptr(unsafe.Pointer(&vallen)), 0)
|
||||||
if u.rawConn == nil {
|
if err != 0 {
|
||||||
return fmt.Errorf("no UDP connection")
|
|
||||||
}
|
|
||||||
var opErr error
|
|
||||||
err := u.rawConn.Control(func(fd uintptr) {
|
|
||||||
_, _, syserr := unix.Syscall6(unix.SYS_GETSOCKOPT, fd, uintptr(unix.SOL_SOCKET), uintptr(unix.SO_MEMINFO), uintptr(unsafe.Pointer(meminfo)), uintptr(unsafe.Pointer(&vallen)), 0)
|
|
||||||
if syserr != 0 {
|
|
||||||
opErr = syserr
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return opErr
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *StdConn) Close() error {
|
func (u *StdConn) Close() error {
|
||||||
if u.udpConn != nil {
|
return syscall.Close(u.sysFd)
|
||||||
return u.udpConn.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUDPStatsEmitter(udpConns []Conn) func() {
|
func NewUDPStatsEmitter(udpConns []Conn) func() {
|
||||||
|
|||||||
Reference in New Issue
Block a user