Compare commits

..

26 Commits

Author SHA1 Message Date
JackDoan f8b09a295d ReadBatch is named Read now 2026-04-20 12:50:43 -05:00
JackDoan f4907b6634 stuff 2026-04-20 11:57:48 -05:00
JackDoan f34e8fe0e6 potential for bug 2026-04-20 11:09:29 -05:00
JackDoan 0f27b81f19 robot say this faster 2026-04-17 16:12:33 -05:00
JackDoan dc581359dd stupid checksum 2026-04-17 15:51:37 -05:00
JackDoan 60e556866a holy crap 2x 2026-04-17 15:33:46 -05:00
JackDoan 1fd24a19c7 holy crap 2x 2026-04-17 14:56:18 -05:00
JackDoan f60cbfdc71 pretty spicy 2026-04-17 14:00:18 -05:00
JackDoan 1dc30df88b silly 2026-04-17 13:35:00 -05:00
JackDoan a13afb2cf8 drain reads before batching 2026-04-17 12:56:20 -05:00
JackDoan 4a2134775d checkpt 2026-04-17 12:27:50 -05:00
JackDoan bd0a63a545 checkpt 2026-04-17 11:39:51 -05:00
JackDoan f8f63c470a checkpt 2026-04-17 11:39:46 -05:00
JackDoan c05fa793a6 ReadBatch 2026-04-17 11:05:34 -05:00
JackDoan 5241bf6d16 no allocs 2026-04-17 10:29:46 -05:00
JackDoan 9d59cba7e1 first try 2026-04-17 10:25:05 -05:00
JackDoan ba8da0e86c fancy blocking writes 2026-04-16 13:43:13 -05:00
JackDoan 6b2e6d9f55 wait for goroutines to finish and for tun to actually be closed 2026-04-16 13:19:25 -05:00
JackDoan 183c1e3cfd remove yellow squiggles 2026-04-16 12:28:49 -05:00
JackDoan 4a91f0b8d5 nbio for tun 2026-04-16 12:28:48 -05:00
JackDoan e448eb1a8c bugz 2026-04-16 12:28:30 -05:00
JackDoan 9dfa2a484c bugz 2026-04-16 12:28:30 -05:00
Nate Brown 1cb5f9a00d Remove more os.Exit calls and give a more reliable wait for stop function 2026-04-16 12:28:28 -05:00
JackDoan 2a0fd0be1d checkpt 2026-04-16 12:26:35 -05:00
JackDoan b644131fd7 remove yellow squiggles 2026-04-15 17:54:21 -05:00
JackDoan 9ac45a06cf tun_linux.go: stdlib too slow, but can't use blocking IO and clean shutdown 2026-04-15 17:45:50 -05:00
18 changed files with 149 additions and 544 deletions
+8 -21
View File
@@ -18,8 +18,6 @@ jobs:
if: github.ref == 'refs/heads/master' || contains(github.event.pull_request.labels.*.name, 'smoke-test-extra') if: github.ref == 'refs/heads/master' || contains(github.event.pull_request.labels.*.name, 'smoke-test-extra')
name: Run extra smoke tests name: Run extra smoke tests
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
VAGRANT_DEFAULT_PROVIDER: libvirt
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
@@ -32,13 +30,11 @@ 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: install vagrant and libvirt - name: workaround AMD-V issue # https://github.com/cri-o/packaging/pull/306
run: | run: sudo rmmod kvm_amd
sudo apt-get update && sudo apt-get install -y vagrant libvirt-daemon-system libvirt-dev
sudo chmod 666 /dev/kvm - name: install vagrant
sudo usermod -aG libvirt $(whoami) run: sudo apt-get update && sudo apt-get install -y vagrant virtualbox
sudo chmod 666 /var/run/libvirt/libvirt-sock
vagrant plugin install vagrant-libvirt
- name: freebsd-amd64 - name: freebsd-amd64
run: make smoke-vagrant/freebsd-amd64 run: make smoke-vagrant/freebsd-amd64
@@ -49,19 +45,10 @@ jobs:
- name: netbsd-amd64 - name: netbsd-amd64
run: make smoke-vagrant/netbsd-amd64 run: make smoke-vagrant/netbsd-amd64
- name: linux-386
run: make smoke-vagrant/linux-386
- name: linux-amd64-ipv6disable - name: linux-amd64-ipv6disable
run: make smoke-vagrant/linux-amd64-ipv6disable run: make smoke-vagrant/linux-amd64-ipv6disable
# linux-386 runs last because it requires disabling KVM to use VirtualBox,
# which prevents libvirt (used by the other tests) from working after this point.
- name: install virtualbox for i386 test
run: |
sudo apt-get install -y virtualbox
sudo rmmod kvm_amd kvm_intel kvm 2>/dev/null || true
- name: linux-386
env:
VAGRANT_DEFAULT_PROVIDER: virtualbox
run: make smoke-vagrant/linux-386
timeout-minutes: 30 timeout-minutes: 30
+3 -5
View File
@@ -16,10 +16,8 @@ relay:
am_relay: true am_relay: true
EOF EOF
# TEST-NET-3 placeholder IPs; smoke-relay.sh seds them to real container IPs. export LIGHTHOUSES="192.168.100.1 172.17.0.2:4242"
# Mapping: .2 lighthouse1, .3 host2, .4 host3, .5 host4. export REMOTE_ALLOW_LIST='{"172.17.0.4/32": false, "172.17.0.5/32": false}'
export LIGHTHOUSES="192.168.100.1 203.0.113.2:4242"
export REMOTE_ALLOW_LIST='{"203.0.113.4/32": false, "203.0.113.5/32": false}'
HOST="host2" ../genconfig.sh >host2.yml <<EOF HOST="host2" ../genconfig.sh >host2.yml <<EOF
relay: relay:
@@ -27,7 +25,7 @@ relay:
- 192.168.100.1 - 192.168.100.1
EOF EOF
export REMOTE_ALLOW_LIST='{"203.0.113.3/32": false}' export REMOTE_ALLOW_LIST='{"172.17.0.3/32": false}'
HOST="host3" ../genconfig.sh >host3.yml HOST="host3" ../genconfig.sh >host3.yml
+6 -12
View File
@@ -5,15 +5,9 @@ set -e -x
rm -rf ./build rm -rf ./build
mkdir ./build mkdir ./build
# Smoke containers run on a dedicated docker network whose subnet is allocated # TODO: Assumes your docker bridge network is a /24, and the first container that launches will be .1
# at smoke time, not known at build time. Configs are written with TEST-NET-3 # - We could make this better by launching the lighthouse first and then fetching what IP it is.
# placeholder IPs (RFC 5737) and smoke.sh / smoke-vagrant.sh / smoke-relay.sh NET="$(docker network inspect bridge -f '{{ range .IPAM.Config }}{{ .Subnet }}{{ end }}' | cut -d. -f1-3)"
# sed the real container IPs in before starting nebula.
#
# Placeholder mapping (last octet == fixed container slot):
# 203.0.113.2 -> lighthouse1, 203.0.113.3 -> host2,
# 203.0.113.4 -> host3, 203.0.113.5 -> host4.
LIGHTHOUSE_IP="203.0.113.2"
( (
cd build cd build
@@ -31,16 +25,16 @@ LIGHTHOUSE_IP="203.0.113.2"
../genconfig.sh >lighthouse1.yml ../genconfig.sh >lighthouse1.yml
HOST="host2" \ HOST="host2" \
LIGHTHOUSES="192.168.100.1 $LIGHTHOUSE_IP:4242" \ LIGHTHOUSES="192.168.100.1 $NET.2:4242" \
../genconfig.sh >host2.yml ../genconfig.sh >host2.yml
HOST="host3" \ HOST="host3" \
LIGHTHOUSES="192.168.100.1 $LIGHTHOUSE_IP:4242" \ LIGHTHOUSES="192.168.100.1 $NET.2:4242" \
INBOUND='[{"port": "any", "proto": "icmp", "group": "lighthouse"}]' \ INBOUND='[{"port": "any", "proto": "icmp", "group": "lighthouse"}]' \
../genconfig.sh >host3.yml ../genconfig.sh >host3.yml
HOST="host4" \ HOST="host4" \
LIGHTHOUSES="192.168.100.1 $LIGHTHOUSE_IP:4242" \ LIGHTHOUSES="192.168.100.1 $NET.2:4242" \
OUTBOUND='[{"port": "any", "proto": "icmp", "group": "lighthouse"}]' \ OUTBOUND='[{"port": "any", "proto": "icmp", "group": "lighthouse"}]' \
../genconfig.sh >host4.yml ../genconfig.sh >host4.yml
+8 -47
View File
@@ -6,8 +6,6 @@ set -o pipefail
mkdir -p logs mkdir -p logs
NETWORK="nebula-smoke-relay"
cleanup() { cleanup() {
echo echo
echo " *** cleanup" echo " *** cleanup"
@@ -18,53 +16,22 @@ cleanup() {
then then
docker kill lighthouse1 host2 host3 host4 docker kill lighthouse1 host2 host3 host4
fi fi
docker network rm "$NETWORK" >/dev/null 2>&1
} }
trap cleanup EXIT trap cleanup EXIT
# Create a dedicated smoke network with an explicit subnet (required for --ip
# below). Probe a short list of candidates so a locally-used range doesn't
# fail the whole test — we only need one to be free.
docker network rm "$NETWORK" >/dev/null 2>&1 || true
for candidate in 172.30.0.0/24 172.31.0.0/24 10.98.0.0/24 10.99.0.0/24 192.168.230.0/24; do
if docker network create --subnet "$candidate" "$NETWORK" >/dev/null 2>&1; then
break
fi
done
if ! docker network inspect "$NETWORK" >/dev/null 2>&1; then
echo "failed to create $NETWORK: every candidate subnet is in use" >&2
exit 1
fi
# Derive container IPs from the network's assigned subnet. Slots: .2 lighthouse1,
# .3 host2, .4 host3, .5 host4 — matches the placeholders in build-relay.sh.
SUBNET="$(docker network inspect -f '{{(index .IPAM.Config 0).Subnet}}' "$NETWORK")"
PREFIX="${SUBNET%/*}"
PREFIX="${PREFIX%.*}"
LIGHTHOUSE_IP="$PREFIX.2"
HOST2_IP="$PREFIX.3"
HOST3_IP="$PREFIX.4"
HOST4_IP="$PREFIX.5"
# Sed the placeholder TEST-NET-3 IPs in the host configs to the real ones.
for f in build/host2.yml build/host3.yml build/host4.yml; do
sed "s|203\.0\.113\.|$PREFIX.|g" "$f" >"$f.tmp"
mv "$f.tmp" "$f"
done
docker run --name lighthouse1 --rm nebula:smoke-relay -config lighthouse1.yml -test docker run --name lighthouse1 --rm nebula:smoke-relay -config lighthouse1.yml -test
docker run --name host2 --rm -v "$PWD/build/host2.yml:/nebula/host2.yml:ro" nebula:smoke-relay -config host2.yml -test docker run --name host2 --rm nebula:smoke-relay -config host2.yml -test
docker run --name host3 --rm -v "$PWD/build/host3.yml:/nebula/host3.yml:ro" nebula:smoke-relay -config host3.yml -test docker run --name host3 --rm nebula:smoke-relay -config host3.yml -test
docker run --name host4 --rm -v "$PWD/build/host4.yml:/nebula/host4.yml:ro" nebula:smoke-relay -config host4.yml -test docker run --name host4 --rm nebula:smoke-relay -config host4.yml -test
docker run --name lighthouse1 --network "$NETWORK" --ip "$LIGHTHOUSE_IP" --device /dev/net/tun:/dev/net/tun --cap-add NET_ADMIN --rm nebula:smoke-relay -config lighthouse1.yml 2>&1 | tee logs/lighthouse1 | sed -u 's/^/ [lighthouse1] /' & docker run --name lighthouse1 --device /dev/net/tun:/dev/net/tun --cap-add NET_ADMIN --rm nebula:smoke-relay -config lighthouse1.yml 2>&1 | tee logs/lighthouse1 | sed -u 's/^/ [lighthouse1] /' &
sleep 1 sleep 1
docker run --name host2 --network "$NETWORK" --ip "$HOST2_IP" -v "$PWD/build/host2.yml:/nebula/host2.yml:ro" --device /dev/net/tun:/dev/net/tun --cap-add NET_ADMIN --rm nebula:smoke-relay -config host2.yml 2>&1 | tee logs/host2 | sed -u 's/^/ [host2] /' & docker run --name host2 --device /dev/net/tun:/dev/net/tun --cap-add NET_ADMIN --rm nebula:smoke-relay -config host2.yml 2>&1 | tee logs/host2 | sed -u 's/^/ [host2] /' &
sleep 1 sleep 1
docker run --name host3 --network "$NETWORK" --ip "$HOST3_IP" -v "$PWD/build/host3.yml:/nebula/host3.yml:ro" --device /dev/net/tun:/dev/net/tun --cap-add NET_ADMIN --rm nebula:smoke-relay -config host3.yml 2>&1 | tee logs/host3 | sed -u 's/^/ [host3] /' & docker run --name host3 --device /dev/net/tun:/dev/net/tun --cap-add NET_ADMIN --rm nebula:smoke-relay -config host3.yml 2>&1 | tee logs/host3 | sed -u 's/^/ [host3] /' &
sleep 1 sleep 1
docker run --name host4 --network "$NETWORK" --ip "$HOST4_IP" -v "$PWD/build/host4.yml:/nebula/host4.yml:ro" --device /dev/net/tun:/dev/net/tun --cap-add NET_ADMIN --rm nebula:smoke-relay -config host4.yml 2>&1 | tee logs/host4 | sed -u 's/^/ [host4] /' & docker run --name host4 --device /dev/net/tun:/dev/net/tun --cap-add NET_ADMIN --rm nebula:smoke-relay -config host4.yml 2>&1 | tee logs/host4 | sed -u 's/^/ [host4] /' &
sleep 1 sleep 1
set +x set +x
@@ -109,13 +76,7 @@ docker exec host4 sh -c 'kill 1'
docker exec host3 sh -c 'kill 1' docker exec host3 sh -c 'kill 1'
docker exec host2 sh -c 'kill 1' docker exec host2 sh -c 'kill 1'
docker exec lighthouse1 sh -c 'kill 1' docker exec lighthouse1 sh -c 'kill 1'
sleep 5
# Wait up to 30s for all backgrounded jobs to exit rather than relying on a
# fixed sleep.
for _ in $(seq 1 30); do
[ -z "$(jobs -r)" ] && break
sleep 1
done
if [ "$(jobs -r)" ] if [ "$(jobs -r)" ]
then then
+15 -43
View File
@@ -8,8 +8,6 @@ export VAGRANT_CWD="$PWD/vagrant-$1"
mkdir -p logs mkdir -p logs
NETWORK="nebula-smoke"
cleanup() { cleanup() {
echo echo
echo " *** cleanup" echo " *** cleanup"
@@ -21,51 +19,32 @@ cleanup() {
docker kill lighthouse1 host2 docker kill lighthouse1 host2
fi fi
vagrant destroy -f vagrant destroy -f
docker network rm "$NETWORK" >/dev/null 2>&1
} }
trap cleanup EXIT trap cleanup EXIT
# Create a dedicated smoke network with an explicit subnet (required for --ip
# below). Probe a short list of candidates so a locally-used range doesn't
# fail the whole test — we only need one to be free.
docker network rm "$NETWORK" >/dev/null 2>&1 || true
for candidate in 172.30.0.0/24 172.31.0.0/24 10.98.0.0/24 10.99.0.0/24 192.168.230.0/24; do
if docker network create --subnet "$candidate" "$NETWORK" >/dev/null 2>&1; then
break
fi
done
if ! docker network inspect "$NETWORK" >/dev/null 2>&1; then
echo "failed to create $NETWORK: every candidate subnet is in use" >&2
exit 1
fi
# Derive container IPs from the network's assigned subnet. Slots: .2 lighthouse1,
# .3 host2 — matches the placeholders in build.sh.
SUBNET="$(docker network inspect -f '{{(index .IPAM.Config 0).Subnet}}' "$NETWORK")"
PREFIX="${SUBNET%/*}"
PREFIX="${PREFIX%.*}"
LIGHTHOUSE_IP="$PREFIX.2"
HOST2_IP="$PREFIX.3"
# Sed the placeholder TEST-NET-3 IPs in the host configs to the real ones.
# This must happen before `vagrant up` rsyncs build/ into the VM for host3.
for f in build/host2.yml build/host3.yml; do
sed "s|203\.0\.113\.|$PREFIX.|g" "$f" >"$f.tmp"
mv "$f.tmp" "$f"
done
CONTAINER="nebula:${NAME:-smoke}" CONTAINER="nebula:${NAME:-smoke}"
docker run --name lighthouse1 --rm "$CONTAINER" -config lighthouse1.yml -test docker run --name lighthouse1 --rm "$CONTAINER" -config lighthouse1.yml -test
docker run --name host2 --rm -v "$PWD/build/host2.yml:/nebula/host2.yml:ro" "$CONTAINER" -config host2.yml -test docker run --name host2 --rm "$CONTAINER" -config host2.yml -test
vagrant up vagrant up
# OpenBSD: synced folders are disabled because Vagrant's rsync installer
# uses ftp.openbsd.org which no longer hosts packages for older releases.
# Copy build artifacts in via scp instead.
case "$1" in
openbsd-*)
vagrant ssh -c "sudo mkdir -p /nebula" -- -T
tar -cf - -C build . | vagrant ssh -c "sudo tar -xf - -C /nebula && sudo chmod -R a+r /nebula" -- -T
;;
esac
vagrant ssh -c "cd /nebula && /nebula/$1-nebula -config host3.yml -test" -- -T vagrant ssh -c "cd /nebula && /nebula/$1-nebula -config host3.yml -test" -- -T
docker run --name lighthouse1 --network "$NETWORK" --ip "$LIGHTHOUSE_IP" --device /dev/net/tun:/dev/net/tun --cap-add NET_ADMIN --rm "$CONTAINER" -config lighthouse1.yml 2>&1 | tee logs/lighthouse1 | sed -u 's/^/ [lighthouse1] /' & docker run --name lighthouse1 --device /dev/net/tun:/dev/net/tun --cap-add NET_ADMIN --rm "$CONTAINER" -config lighthouse1.yml 2>&1 | tee logs/lighthouse1 | sed -u 's/^/ [lighthouse1] /' &
sleep 1 sleep 1
docker run --name host2 --network "$NETWORK" --ip "$HOST2_IP" -v "$PWD/build/host2.yml:/nebula/host2.yml:ro" --device /dev/net/tun:/dev/net/tun --cap-add NET_ADMIN --rm "$CONTAINER" -config host2.yml 2>&1 | tee logs/host2 | sed -u 's/^/ [host2] /' & docker run --name host2 --device /dev/net/tun:/dev/net/tun --cap-add NET_ADMIN --rm "$CONTAINER" -config host2.yml 2>&1 | tee logs/host2 | sed -u 's/^/ [host2] /' &
sleep 1 sleep 1
vagrant ssh -c "cd /nebula && sudo sh -c 'echo \$\$ >/nebula/pid && exec /nebula/$1-nebula -config host3.yml'" 2>&1 -- -T | tee logs/host3 | sed -u 's/^/ [host3] /' & vagrant ssh -c "cd /nebula && sudo sh -c 'echo \$\$ >/nebula/pid && exec /nebula/$1-nebula -config host3.yml'" 2>&1 -- -T | tee logs/host3 | sed -u 's/^/ [host3] /' &
sleep 15 sleep 15
@@ -128,14 +107,7 @@ vagrant ssh -c "ping -c1 192.168.100.2" -- -T
vagrant ssh -c "sudo xargs kill </nebula/pid" -- -T vagrant ssh -c "sudo xargs kill </nebula/pid" -- -T
docker exec host2 sh -c 'kill 1' docker exec host2 sh -c 'kill 1'
docker exec lighthouse1 sh -c 'kill 1' docker exec lighthouse1 sh -c 'kill 1'
sleep 1
# Wait up to 30s for all backgrounded jobs to exit. vagrant ssh in particular
# takes a beat to tear down after nebula exits on the VM, so a fixed sleep is
# racy.
for _ in $(seq 1 30); do
[ -z "$(jobs -r)" ] && break
sleep 1
done
if [ "$(jobs -r)" ] if [ "$(jobs -r)" ]
then then
+8 -48
View File
@@ -6,8 +6,6 @@ set -o pipefail
mkdir -p logs mkdir -p logs
NETWORK="nebula-smoke"
cleanup() { cleanup() {
echo echo
echo " *** cleanup" echo " *** cleanup"
@@ -18,56 +16,24 @@ cleanup() {
then then
docker kill lighthouse1 host2 host3 host4 docker kill lighthouse1 host2 host3 host4
fi fi
docker network rm "$NETWORK" >/dev/null 2>&1
} }
trap cleanup EXIT trap cleanup EXIT
# Create a dedicated smoke network with an explicit subnet (required for --ip
# below). Probe a short list of candidates so a locally-used range doesn't
# fail the whole test — we only need one to be free.
docker network rm "$NETWORK" >/dev/null 2>&1 || true
for candidate in 172.30.0.0/24 172.31.0.0/24 10.98.0.0/24 10.99.0.0/24 192.168.230.0/24; do
if docker network create --subnet "$candidate" "$NETWORK" >/dev/null 2>&1; then
break
fi
done
if ! docker network inspect "$NETWORK" >/dev/null 2>&1; then
echo "failed to create $NETWORK: every candidate subnet is in use" >&2
exit 1
fi
# Derive container IPs from the network's assigned subnet. Slots: .2 lighthouse1,
# .3 host2, .4 host3, .5 host4 — matches the placeholders in build.sh.
SUBNET="$(docker network inspect -f '{{(index .IPAM.Config 0).Subnet}}' "$NETWORK")"
PREFIX="${SUBNET%/*}"
PREFIX="${PREFIX%.*}"
LIGHTHOUSE_IP="$PREFIX.2"
HOST2_IP="$PREFIX.3"
HOST3_IP="$PREFIX.4"
HOST4_IP="$PREFIX.5"
# Sed the placeholder TEST-NET-3 IPs in the host configs to the real ones.
# build/lighthouse1.yml has no IPs to rewrite so it's skipped.
for f in build/host2.yml build/host3.yml build/host4.yml; do
sed "s|203\.0\.113\.|$PREFIX.|g" "$f" >"$f.tmp"
mv "$f.tmp" "$f"
done
CONTAINER="nebula:${NAME:-smoke}" CONTAINER="nebula:${NAME:-smoke}"
docker run --name lighthouse1 --rm "$CONTAINER" -config lighthouse1.yml -test docker run --name lighthouse1 --rm "$CONTAINER" -config lighthouse1.yml -test
docker run --name host2 --rm -v "$PWD/build/host2.yml:/nebula/host2.yml:ro" "$CONTAINER" -config host2.yml -test docker run --name host2 --rm "$CONTAINER" -config host2.yml -test
docker run --name host3 --rm -v "$PWD/build/host3.yml:/nebula/host3.yml:ro" "$CONTAINER" -config host3.yml -test docker run --name host3 --rm "$CONTAINER" -config host3.yml -test
docker run --name host4 --rm -v "$PWD/build/host4.yml:/nebula/host4.yml:ro" "$CONTAINER" -config host4.yml -test docker run --name host4 --rm "$CONTAINER" -config host4.yml -test
docker run --name lighthouse1 --network "$NETWORK" --ip "$LIGHTHOUSE_IP" --device /dev/net/tun:/dev/net/tun --cap-add NET_ADMIN --rm "$CONTAINER" -config lighthouse1.yml 2>&1 | tee logs/lighthouse1 | sed -u 's/^/ [lighthouse1] /' & docker run --name lighthouse1 --device /dev/net/tun:/dev/net/tun --cap-add NET_ADMIN --rm "$CONTAINER" -config lighthouse1.yml 2>&1 | tee logs/lighthouse1 | sed -u 's/^/ [lighthouse1] /' &
sleep 1 sleep 1
docker run --name host2 --network "$NETWORK" --ip "$HOST2_IP" -v "$PWD/build/host2.yml:/nebula/host2.yml:ro" --device /dev/net/tun:/dev/net/tun --cap-add NET_ADMIN --rm "$CONTAINER" -config host2.yml 2>&1 | tee logs/host2 | sed -u 's/^/ [host2] /' & docker run --name host2 --device /dev/net/tun:/dev/net/tun --cap-add NET_ADMIN --rm "$CONTAINER" -config host2.yml 2>&1 | tee logs/host2 | sed -u 's/^/ [host2] /' &
sleep 1 sleep 1
docker run --name host3 --network "$NETWORK" --ip "$HOST3_IP" -v "$PWD/build/host3.yml:/nebula/host3.yml:ro" --device /dev/net/tun:/dev/net/tun --cap-add NET_ADMIN --rm "$CONTAINER" -config host3.yml 2>&1 | tee logs/host3 | sed -u 's/^/ [host3] /' & docker run --name host3 --device /dev/net/tun:/dev/net/tun --cap-add NET_ADMIN --rm "$CONTAINER" -config host3.yml 2>&1 | tee logs/host3 | sed -u 's/^/ [host3] /' &
sleep 1 sleep 1
docker run --name host4 --network "$NETWORK" --ip "$HOST4_IP" -v "$PWD/build/host4.yml:/nebula/host4.yml:ro" --device /dev/net/tun:/dev/net/tun --cap-add NET_ADMIN --rm "$CONTAINER" -config host4.yml 2>&1 | tee logs/host4 | sed -u 's/^/ [host4] /' & docker run --name host4 --device /dev/net/tun:/dev/net/tun --cap-add NET_ADMIN --rm "$CONTAINER" -config host4.yml 2>&1 | tee logs/host4 | sed -u 's/^/ [host4] /' &
sleep 1 sleep 1
# grab tcpdump pcaps for debugging # grab tcpdump pcaps for debugging
@@ -165,13 +131,7 @@ docker exec host4 sh -c 'kill 1'
docker exec host3 sh -c 'kill 1' docker exec host3 sh -c 'kill 1'
docker exec host2 sh -c 'kill 1' docker exec host2 sh -c 'kill 1'
docker exec lighthouse1 sh -c 'kill 1' docker exec lighthouse1 sh -c 'kill 1'
sleep 5
# Wait up to 30s for all backgrounded jobs to exit rather than relying on a
# fixed sleep.
for _ in $(seq 1 30); do
[ -z "$(jobs -r)" ] && break
sleep 1
done
if [ "$(jobs -r)" ] if [ "$(jobs -r)" ]
then then
@@ -1,7 +1,7 @@
# -*- mode: ruby -*- # -*- mode: ruby -*-
# vi: set ft=ruby : # vi: set ft=ruby :
Vagrant.configure("2") do |config| Vagrant.configure("2") do |config|
config.vm.box = "bento/ubuntu-24.04" config.vm.box = "ubuntu/jammy64"
config.vm.synced_folder "../build", "/nebula" config.vm.synced_folder "../build", "/nebula"
@@ -1,7 +1,7 @@
# -*- mode: ruby -*- # -*- mode: ruby -*-
# vi: set ft=ruby : # vi: set ft=ruby :
Vagrant.configure("2") do |config| Vagrant.configure("2") do |config|
config.vm.box = "DefinedNet/openbsd78" config.vm.box = "generic/openbsd7"
config.vm.synced_folder "../build", "/nebula", type: "rsync" config.vm.synced_folder ".", "/vagrant", disabled: true
end end
+1
View File
@@ -88,6 +88,7 @@ func main() {
if err := wait(); err != nil { if err := wait(); err != nil {
l.WithError(err).Error("Nebula stopped due to fatal error") l.WithError(err).Error("Nebula stopped due to fatal error")
l.Info("Goodbye")
os.Exit(2) os.Exit(2)
} }
+1
View File
@@ -83,6 +83,7 @@ func main() {
if err := wait(); err != nil { if err := wait(); err != nil {
l.WithError(err).Error("Nebula stopped due to fatal error") l.WithError(err).Error("Nebula stopped due to fatal error")
l.Info("Goodbye")
os.Exit(2) os.Exit(2)
} }
+12 -27
View File
@@ -18,16 +18,12 @@ import (
type RunState int type RunState int
const ( const (
StateUnknown RunState = iota Stopped RunState = 0 // The control has yet to be started
StateReady Started RunState = 1 // The control has been started
StateStarted Stopping RunState = 2 // The control is stopping
StateStopping
StateStopped
) )
var ErrAlreadyStarted = errors.New("nebula is already started") var ErrAlreadyStarted = errors.New("nebula is already started")
var ErrAlreadyStopped = errors.New("nebula cannot be restarted")
var ErrUnknownState = errors.New("nebula state is invalid")
// Every interaction here needs to take extra care to copy memory and not return or use arguments "as is" when touching // Every interaction here needs to take extra care to copy memory and not return or use arguments "as is" when touching
// core. This means copying IP objects, slices, de-referencing pointers and taking the actual value, etc // core. This means copying IP objects, slices, de-referencing pointers and taking the actual value, etc
@@ -75,22 +71,15 @@ type ControlHostInfo struct {
// triggered the shutdown. // triggered the shutdown.
func (c *Control) Start() (func() error, error) { func (c *Control) Start() (func() error, error) {
c.stateLock.Lock() c.stateLock.Lock()
defer c.stateLock.Unlock() if c.state != Stopped {
switch c.state { c.stateLock.Unlock()
case StateReady:
//yay!
case StateStopped, StateStopping:
return nil, ErrAlreadyStopped
case StateStarted:
return nil, ErrAlreadyStarted return nil, ErrAlreadyStarted
default:
return nil, ErrUnknownState
} }
// Activate the interface // Activate the interface
err := c.f.activate() err := c.f.activate()
if err != nil { if err != nil {
c.state = StateStopped c.stateLock.Unlock()
return nil, err return nil, err
} }
@@ -114,13 +103,9 @@ func (c *Control) Start() (func() error, error) {
c.f.triggerShutdown = c.Stop c.f.triggerShutdown = c.Stop
// Start reading packets. // Start reading packets.
out, err := c.f.run() c.state = Started
if err != nil { c.stateLock.Unlock()
c.state = StateStopped return c.f.run()
return nil, err
}
c.state = StateStarted
return out, nil
} }
func (c *Control) State() RunState { func (c *Control) State() RunState {
@@ -136,13 +121,13 @@ func (c *Control) Context() context.Context {
// Stop is a non-blocking call that signals nebula to close all tunnels and shut down // Stop is a non-blocking call that signals nebula to close all tunnels and shut down
func (c *Control) Stop() { func (c *Control) Stop() {
c.stateLock.Lock() c.stateLock.Lock()
if c.state != StateStarted { if c.state != Started {
c.stateLock.Unlock() c.stateLock.Unlock()
// We are stopping or stopped already // We are stopping or stopped already
return return
} }
c.state = StateStopping c.state = Stopping
c.stateLock.Unlock() c.stateLock.Unlock()
// Stop the handshakeManager (and other services), to prevent new tunnels from // Stop the handshakeManager (and other services), to prevent new tunnels from
@@ -154,7 +139,7 @@ func (c *Control) Stop() {
c.l.WithError(err).Error("Close interface failed") c.l.WithError(err).Error("Close interface failed")
} }
c.stateLock.Lock() c.stateLock.Lock()
c.state = StateStopped c.state = Stopped
c.stateLock.Unlock() c.stateLock.Unlock()
} }
-1
View File
@@ -79,7 +79,6 @@ func TestControl_GetHostInfoByVpnIp(t *testing.T) {
}, &Interface{}) }, &Interface{})
c := Control{ c := Control{
state: StateReady,
f: &Interface{ f: &Interface{
hostMap: hm, hostMap: hm,
}, },
+6 -11
View File
@@ -257,7 +257,6 @@ func (f *Interface) activate() error {
f.wg.Add(1) // for us to wait on Close() to return f.wg.Add(1) // for us to wait on Close() to return
if err = f.inside.Activate(); err != nil { if err = f.inside.Activate(); err != nil {
f.wg.Done()
f.inside.Close() f.inside.Close()
return err return err
} }
@@ -339,7 +338,7 @@ func (f *Interface) listenOut(i int) {
f.onFatal(err) f.onFatal(err)
} }
f.l.Debugf("underlay reader %v is done", i) f.l.Infof("underlay reader %v is done", i)
} }
func (f *Interface) listenIn(reader overlay.Queue, i int) { func (f *Interface) listenIn(reader overlay.Queue, i int) {
@@ -373,7 +372,7 @@ func (f *Interface) listenIn(reader overlay.Queue, i int) {
} }
} }
f.l.Debugf("overlay reader %v is done", i) f.l.Infof("overlay reader %v is done", i)
} }
func (f *Interface) flushBatch(batch *sendBatch, q int) { func (f *Interface) flushBatch(batch *sendBatch, q int) {
@@ -597,23 +596,19 @@ func (f *Interface) GetCertState() *CertState {
} }
func (f *Interface) Close() error { func (f *Interface) Close() error {
var errs []error var err error
f.closed.Store(true) f.closed.Store(true)
// Release the udp readers // Release the udp readers
for i, u := range f.writers { for i, u := range f.writers {
err := u.Close() err = u.Close()
if err != nil { if err != nil {
f.l.WithError(err).WithField("writer", i).Error("Error while closing udp socket") f.l.WithError(err).WithField("writer", i).Error("Error while closing udp socket")
errs = append(errs, err)
} }
} }
// Release the tun device (closing the tun also closes all readers) // Release the tun device (closing the tun also closes all readers)
closeErr := f.inside.Close() err = f.inside.Close()
if closeErr != nil {
errs = append(errs, closeErr)
}
f.wg.Done() f.wg.Done()
return errors.Join(errs...) return err
} }
-1
View File
@@ -288,7 +288,6 @@ func Main(c *config.C, configTest bool, buildVersion string, logger *logrus.Logg
} }
return &Control{ return &Control{
state: StateReady,
f: ifce, f: ifce,
l: l, l: l,
ctx: ctx, ctx: ctx,
-120
View File
@@ -1,120 +0,0 @@
//go:build linux && !android && !e2e_testing
// +build linux,!android,!e2e_testing
package overlay
import (
"errors"
"os"
"sync"
"testing"
"time"
"golang.org/x/sys/unix"
)
// newReadPipe returns a read fd. The matching write fd is registered for cleanup.
// The caller takes ownership of the read fd (pass it to newTunFd / newFriend).
func newReadPipe(t *testing.T) int {
t.Helper()
var fds [2]int
if err := unix.Pipe2(fds[:], unix.O_CLOEXEC); err != nil {
t.Fatalf("pipe2: %v", err)
}
t.Cleanup(func() { _ = unix.Close(fds[1]) })
return fds[0]
}
func TestTunFile_WakeForShutdown_UnblocksRead(t *testing.T) {
tf, err := newTunFd(newReadPipe(t))
if err != nil {
t.Fatalf("newTunFd: %v", err)
}
t.Cleanup(func() { _ = tf.Close() })
done := make(chan error, 1)
go func() {
_, err := tf.Read(make([]byte, 64))
done <- err
}()
// Verify Read is actually blocked in poll.
select {
case err := <-done:
t.Fatalf("Read returned before shutdown signal: %v", err)
case <-time.After(50 * time.Millisecond):
}
if err := tf.wakeForShutdown(); err != nil {
t.Fatalf("wakeForShutdown: %v", err)
}
select {
case err := <-done:
if !errors.Is(err, os.ErrClosed) {
t.Fatalf("expected os.ErrClosed, got %v", err)
}
case <-time.After(2 * time.Second):
t.Fatal("Read did not wake on shutdown")
}
}
func TestTunFile_WakeForShutdown_WakesFriends(t *testing.T) {
parent, err := newTunFd(newReadPipe(t))
if err != nil {
t.Fatalf("newTunFd: %v", err)
}
friend, err := parent.newFriend(newReadPipe(t))
if err != nil {
_ = parent.Close()
t.Fatalf("newFriend: %v", err)
}
t.Cleanup(func() {
_ = friend.Close()
_ = parent.Close()
})
readers := []*tunFile{parent, friend}
errs := make([]error, len(readers))
var wg sync.WaitGroup
for i, r := range readers {
wg.Add(1)
go func(i int, r *tunFile) {
defer wg.Done()
_, errs[i] = r.Read(make([]byte, 64))
}(i, r)
}
time.Sleep(50 * time.Millisecond)
if err := parent.wakeForShutdown(); err != nil {
t.Fatalf("wakeForShutdown: %v", err)
}
done := make(chan struct{})
go func() { wg.Wait(); close(done) }()
select {
case <-done:
case <-time.After(2 * time.Second):
t.Fatal("readers did not wake")
}
for i, err := range errs {
if !errors.Is(err, os.ErrClosed) {
t.Errorf("reader %d: expected os.ErrClosed, got %v", i, err)
}
}
}
func TestTunFile_Close_Idempotent(t *testing.T) {
tf, err := newTunFd(newReadPipe(t))
if err != nil {
t.Fatalf("newTunFd: %v", err)
}
if err := tf.Close(); err != nil {
t.Fatalf("first Close: %v", err)
}
if err := tf.Close(); err != nil {
t.Fatalf("second Close should be a no-op, got %v", err)
}
}
+58 -167
View File
@@ -9,7 +9,6 @@ import (
"fmt" "fmt"
"io/fs" "io/fs"
"net/netip" "net/netip"
"os"
"sync/atomic" "sync/atomic"
"syscall" "syscall"
"time" "time"
@@ -93,70 +92,12 @@ type tun struct {
routeTree atomic.Pointer[bart.Table[routing.Gateways]] routeTree atomic.Pointer[bart.Table[routing.Gateways]]
linkAddr *netroute.LinkAddr linkAddr *netroute.LinkAddr
l *logrus.Logger l *logrus.Logger
devFd int
fd int
shutdownR int // read end of the shutdown pipe; closing the write end wakes blocked polls
shutdownW int // write end of the shutdown pipe; closing this signals shutdown to any blocked reader/writer
readPoll [2]unix.PollFd
writePoll [2]unix.PollFd
closed atomic.Bool
readBuf []byte readBuf []byte
batchRet [1][]byte batchRet [1][]byte
} }
// blockOnRead waits until the tun fd is readable or shutdown has been signaled.
// Returns os.ErrClosed if Close was called.
func (t *tun) blockOnRead() error {
const problemFlags = unix.POLLHUP | unix.POLLNVAL | unix.POLLERR
var err error
for {
_, err = unix.Poll(t.readPoll[:], -1)
if err != unix.EINTR {
break
}
}
tunEvents := t.readPoll[0].Revents
shutdownEvents := t.readPoll[1].Revents
t.readPoll[0].Revents = 0
t.readPoll[1].Revents = 0
if err != nil {
return err
}
if shutdownEvents&(unix.POLLIN|problemFlags) != 0 {
return os.ErrClosed
}
if tunEvents&problemFlags != 0 {
return os.ErrClosed
}
return nil
}
func (t *tun) blockOnWrite() error {
const problemFlags = unix.POLLHUP | unix.POLLNVAL | unix.POLLERR
var err error
for {
_, err = unix.Poll(t.writePoll[:], -1)
if err != unix.EINTR {
break
}
}
tunEvents := t.writePoll[0].Revents
shutdownEvents := t.writePoll[1].Revents
t.writePoll[0].Revents = 0
t.writePoll[1].Revents = 0
if err != nil {
return err
}
if shutdownEvents&(unix.POLLIN|problemFlags) != 0 {
return os.ErrClosed
}
if tunEvents&problemFlags != 0 {
return os.ErrClosed
}
return nil
}
func (t *tun) Read() ([][]byte, error) { func (t *tun) Read() ([][]byte, error) {
if t.readBuf == nil { if t.readBuf == nil {
t.readBuf = make([]byte, defaultBatchBufSize) t.readBuf = make([]byte, defaultBatchBufSize)
@@ -174,102 +115,82 @@ func (t *tun) WriteReject(p []byte) (int, error) {
} }
func (t *tun) readOne(to []byte) (int, error) { func (t *tun) readOne(to []byte) (int, error) {
// use readv() to read from the tunnel device, to eliminate the need for copying the buffer
if t.devFd < 0 {
return -1, syscall.EINVAL
}
// first 4 bytes is protocol family, in network byte order // first 4 bytes is protocol family, in network byte order
var head [4]byte head := make([]byte, 4)
iovecs := [2]syscall.Iovec{
iovecs := []syscall.Iovec{
{&head[0], 4}, {&head[0], 4},
{&to[0], uint64(len(to))}, {&to[0], uint64(len(to))},
} }
for {
n, _, errno := syscall.Syscall(syscall.SYS_READV, uintptr(t.fd), uintptr(unsafe.Pointer(&iovecs[0])), 2) n, _, errno := syscall.Syscall(syscall.SYS_READV, uintptr(t.devFd), uintptr(unsafe.Pointer(&iovecs[0])), uintptr(2))
if errno == 0 {
var err error
if errno != 0 {
err = syscall.Errno(errno)
} else {
err = nil
}
// fix bytes read number to exclude header
bytesRead := int(n) bytesRead := int(n)
if bytesRead < 4 { if bytesRead < 0 {
return 0, nil return bytesRead, err
} } else if bytesRead < 4 {
return bytesRead - 4, nil
}
switch errno {
case unix.EAGAIN:
if err := t.blockOnRead(); err != nil {
return 0, err return 0, err
} } else {
case unix.EINTR: return bytesRead - 4, err
// retry
case unix.EBADF:
return 0, os.ErrClosed
default:
return 0, errno
}
} }
} }
// Write is only valid for single threaded use // Write is only valid for single threaded use
func (t *tun) Write(from []byte) (int, error) { func (t *tun) Write(from []byte) (int, error) {
// use writev() to write to the tunnel device, to eliminate the need for copying the buffer
if t.devFd < 0 {
return -1, syscall.EINVAL
}
if len(from) <= 1 { if len(from) <= 1 {
return 0, syscall.EIO return 0, syscall.EIO
} }
ipVer := from[0] >> 4 ipVer := from[0] >> 4
var head [4]byte var head []byte
// first 4 bytes is protocol family, in network byte order // first 4 bytes is protocol family, in network byte order
switch ipVer { if ipVer == 4 {
case 4: head = []byte{0, 0, 0, syscall.AF_INET}
head[3] = syscall.AF_INET } else if ipVer == 6 {
case 6: head = []byte{0, 0, 0, syscall.AF_INET6}
head[3] = syscall.AF_INET6 } else {
default:
return 0, fmt.Errorf("unable to determine IP version from packet") return 0, fmt.Errorf("unable to determine IP version from packet")
} }
iovecs := []syscall.Iovec{
iovecs := [2]syscall.Iovec{
{&head[0], 4}, {&head[0], 4},
{&from[0], uint64(len(from))}, {&from[0], uint64(len(from))},
} }
for {
n, _, errno := syscall.Syscall(syscall.SYS_WRITEV, uintptr(t.fd), uintptr(unsafe.Pointer(&iovecs[0])), 2) n, _, errno := syscall.Syscall(syscall.SYS_WRITEV, uintptr(t.devFd), uintptr(unsafe.Pointer(&iovecs[0])), uintptr(2))
if errno == 0 {
return int(n) - 4, nil var err error
} if errno != 0 {
switch errno { err = syscall.Errno(errno)
case unix.EAGAIN: } else {
if err := t.blockOnWrite(); err != nil { err = nil
return 0, err
}
case unix.EINTR:
// retry
case unix.EBADF:
return 0, os.ErrClosed
default:
return 0, errno
}
} }
return int(n) - 4, err
} }
func (t *tun) Close() error { func (t *tun) Close() error {
if t.closed.Swap(true) { if t.devFd >= 0 {
return nil err := syscall.Close(t.devFd)
} if err != nil {
// Closing the write end of the shutdown pipe causes any blocked Poll to
// return with POLLHUP on the shutdown fd, so readers/writers wake up and
// exit with os.ErrClosed.
if t.shutdownW >= 0 {
_ = unix.Close(t.shutdownW)
t.shutdownW = -1
}
if t.fd >= 0 {
if err := unix.Close(t.fd); err != nil {
t.l.WithError(err).Error("Error closing device") t.l.WithError(err).Error("Error closing device")
} }
t.fd = -1 t.devFd = -1
}
if t.shutdownR >= 0 {
_ = unix.Close(t.shutdownR)
t.shutdownR = -1
}
c := make(chan struct{}) c := make(chan struct{})
go func() { go func() {
@@ -291,6 +212,7 @@ func (t *tun) Close() error {
case <-c: case <-c:
case <-time.After(1 * time.Second): case <-time.After(1 * time.Second):
} }
}
return nil return nil
} }
@@ -305,38 +227,16 @@ func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (
var err error var err error
deviceName := c.GetString("tun.dev", "") deviceName := c.GetString("tun.dev", "")
if deviceName != "" { if deviceName != "" {
fd, err = unix.Open("/dev/"+deviceName, os.O_RDWR, 0) fd, err = syscall.Open("/dev/"+deviceName, syscall.O_RDWR, 0)
} }
if errors.Is(err, fs.ErrNotExist) || deviceName == "" { if errors.Is(err, fs.ErrNotExist) || deviceName == "" {
// If the device doesn't already exist, request a new one and rename it // If the device doesn't already exist, request a new one and rename it
fd, err = unix.Open("/dev/tun", os.O_RDWR, 0) fd, err = syscall.Open("/dev/tun", syscall.O_RDWR, 0)
} }
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err = unix.SetNonblock(fd, true); err != nil {
_ = unix.Close(fd)
return nil, fmt.Errorf("failed to set tun device as nonblocking: %w", err)
}
// Shutdown pipe lets Close wake any reader/writer blocked in Poll.
var pipeFds [2]int
if err = unix.Pipe2(pipeFds[:], unix.O_CLOEXEC|unix.O_NONBLOCK); err != nil {
_ = unix.Close(fd)
return nil, fmt.Errorf("failed to create shutdown pipe: %w", err)
}
shutdownR, shutdownW := pipeFds[0], pipeFds[1]
closeOnErr := true
defer func() {
if closeOnErr {
_ = unix.Close(fd)
_ = unix.Close(shutdownR)
_ = unix.Close(shutdownW)
}
}()
// Read the name of the interface // Read the name of the interface
var name [16]byte var name [16]byte
arg := fiodgnameArg{length: 16, buf: unsafe.Pointer(&name)} arg := fiodgnameArg{length: 16, buf: unsafe.Pointer(&name)}
@@ -355,7 +255,7 @@ func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (
} }
if ctrlErr != nil { if ctrlErr != nil {
return nil, ctrlErr return nil, err
} }
ifName := string(bytes.TrimRight(name[:], "\x00")) ifName := string(bytes.TrimRight(name[:], "\x00"))
@@ -371,6 +271,8 @@ func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (
} }
defer syscall.Close(s) defer syscall.Close(s)
fd := uintptr(s)
var fromName [16]byte var fromName [16]byte
var toName [16]byte var toName [16]byte
copy(fromName[:], ifName) copy(fromName[:], ifName)
@@ -382,7 +284,7 @@ func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (
} }
// Set the device name // Set the device name
_ = ioctl(uintptr(s), syscall.SIOCSIFNAME, uintptr(unsafe.Pointer(&ifrr))) _ = ioctl(fd, syscall.SIOCSIFNAME, uintptr(unsafe.Pointer(&ifrr)))
} }
t := &tun{ t := &tun{
@@ -390,24 +292,13 @@ func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (
vpnNetworks: vpnNetworks, vpnNetworks: vpnNetworks,
MTU: c.GetInt("tun.mtu", DefaultMTU), MTU: c.GetInt("tun.mtu", DefaultMTU),
l: l, l: l,
fd: fd, devFd: fd,
shutdownR: shutdownR,
shutdownW: shutdownW,
readPoll: [2]unix.PollFd{
{Fd: int32(fd), Events: unix.POLLIN},
{Fd: int32(shutdownR), Events: unix.POLLIN},
},
writePoll: [2]unix.PollFd{
{Fd: int32(fd), Events: unix.POLLOUT},
{Fd: int32(shutdownR), Events: unix.POLLIN},
},
} }
err = t.reload(c, true) err = t.reload(c, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
closeOnErr = false
c.RegisterReloadCallback(func(c *config.C) { c.RegisterReloadCallback(func(c *config.C) {
err := t.reload(c, false) err := t.reload(c, false)
-6
View File
@@ -210,10 +210,6 @@ func (r *tunFile) readRaw(buf []byte) (int, error) {
return 0, err return 0, err
} }
continue continue
} else if err == unix.EINTR {
continue
} else if err == unix.EBADF {
return 0, os.ErrClosed
} else { } else {
return 0, err return 0, err
} }
@@ -318,8 +314,6 @@ func (r *tunFile) writeWithScratch(buf []byte, iovs *[2]unix.Iovec) (int, error)
continue continue
} else if err == unix.EINTR { } else if err == unix.EINTR {
continue continue
} else if err == unix.EBADF {
return 0, os.ErrClosed
} else { } else {
return 0, err return 0, err
} }
-12
View File
@@ -10,11 +10,9 @@ package udp
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"net" "net"
"net/netip" "net/netip"
"time"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/slackhq/nebula/config" "github.com/slackhq/nebula/config"
@@ -96,22 +94,12 @@ type rawMessage struct {
func (u *GenericConn) ListenOut(r EncReader, flush func()) error { func (u *GenericConn) ListenOut(r EncReader, flush func()) error {
buffer := make([]byte, MTU) buffer := make([]byte, MTU)
var lastRecvErr time.Time
for { for {
// Just read one packet at a time // Just read one packet at a time
n, rua, err := u.ReadFromUDPAddrPort(buffer) n, rua, err := u.ReadFromUDPAddrPort(buffer)
if err != nil { if err != nil {
if errors.Is(err, net.ErrClosed) {
return err return err
} }
// Dampen unexpected message warns to once per minute
if lastRecvErr.IsZero() || time.Since(lastRecvErr) > time.Minute {
lastRecvErr = time.Now()
u.l.WithError(err).Warn("unexpected udp socket receive error")
}
continue
}
r(netip.AddrPortFrom(rua.Addr().Unmap(), rua.Port()), buffer[:n]) r(netip.AddrPortFrom(rua.Addr().Unmap(), rua.Port()), buffer[:n])
flush() flush()