mirror of
https://github.com/slackhq/nebula.git
synced 2026-08-15 21:46:59 +02:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f8b09a295d | |||
| f4907b6634 | |||
| f34e8fe0e6 | |||
| 0f27b81f19 | |||
| dc581359dd | |||
| 60e556866a | |||
| 1fd24a19c7 | |||
| f60cbfdc71 | |||
| 1dc30df88b | |||
| a13afb2cf8 | |||
| 4a2134775d | |||
| bd0a63a545 | |||
| f8f63c470a | |||
| c05fa793a6 | |||
| 5241bf6d16 | |||
| 9d59cba7e1 | |||
| ba8da0e86c | |||
| 6b2e6d9f55 | |||
| 183c1e3cfd | |||
| 4a91f0b8d5 | |||
| e448eb1a8c | |||
| 9dfa2a484c | |||
| 1cb5f9a00d | |||
| 2a0fd0be1d | |||
| b644131fd7 | |||
| 9ac45a06cf |
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+77
-186
@@ -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,122 +115,103 @@ 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 {
|
|
||||||
bytesRead := int(n)
|
var err error
|
||||||
if bytesRead < 4 {
|
if errno != 0 {
|
||||||
return 0, nil
|
err = syscall.Errno(errno)
|
||||||
}
|
} else {
|
||||||
return bytesRead - 4, nil
|
err = nil
|
||||||
}
|
}
|
||||||
switch errno {
|
// fix bytes read number to exclude header
|
||||||
case unix.EAGAIN:
|
bytesRead := int(n)
|
||||||
if err := t.blockOnRead(); err != nil {
|
if bytesRead < 0 {
|
||||||
return 0, err
|
return bytesRead, err
|
||||||
}
|
} else if bytesRead < 4 {
|
||||||
case unix.EINTR:
|
return 0, err
|
||||||
// retry
|
} else {
|
||||||
case unix.EBADF:
|
return bytesRead - 4, err
|
||||||
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 {
|
c := make(chan struct{})
|
||||||
_ = unix.Close(t.shutdownR)
|
go func() {
|
||||||
t.shutdownR = -1
|
// destroying the interface can block if a read() is still pending. Do this asynchronously.
|
||||||
}
|
defer close(c)
|
||||||
|
s, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_IP)
|
||||||
|
if err == nil {
|
||||||
|
defer syscall.Close(s)
|
||||||
|
ifreq := ifreqDestroy{Name: t.deviceBytes()}
|
||||||
|
err = ioctl(uintptr(s), syscall.SIOCIFDESTROY, uintptr(unsafe.Pointer(&ifreq)))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.l.WithError(err).Error("Error destroying tunnel")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
c := make(chan struct{})
|
// wait up to 1 second so we start blocking at the ioctl
|
||||||
go func() {
|
select {
|
||||||
// destroying the interface can block if a read() is still pending. Do this asynchronously.
|
case <-c:
|
||||||
defer close(c)
|
case <-time.After(1 * time.Second):
|
||||||
s, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_IP)
|
|
||||||
if err == nil {
|
|
||||||
defer syscall.Close(s)
|
|
||||||
ifreq := ifreqDestroy{Name: t.deviceBytes()}
|
|
||||||
err = ioctl(uintptr(s), syscall.SIOCIFDESTROY, uintptr(unsafe.Pointer(&ifreq)))
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
t.l.WithError(err).Error("Error destroying tunnel")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// wait up to 1 second so we start blocking at the ioctl
|
|
||||||
select {
|
|
||||||
case <-c:
|
|
||||||
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)
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-13
@@ -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,21 +94,11 @@ 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])
|
||||||
|
|||||||
Reference in New Issue
Block a user