[
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.yml",
    "content": "name: \"\\U0001F41B Bug Report\"\ndescription: Report an issue or possible bug\ntitle: \"\\U0001F41B BUG:\"\nlabels: []\nassignees: []\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        ### Thank you for taking the time to file a bug report!\n\n        Please fill out this form as completely as possible.\n\n  - type: input\n    id: version\n    attributes:\n      label: What version of `nebula` are you using? (`nebula -version`)\n      placeholder: 0.0.0\n    validations:\n      required: true\n\n  - type: input\n    id: os\n    attributes:\n      label: What operating system are you using?\n      description: iOS and Android specific issues belong in the [mobile_nebula](https://github.com/DefinedNet/mobile_nebula) repo.\n      placeholder: Linux, Mac, Windows\n    validations:\n      required: true\n\n  - type: textarea\n    id: description\n    attributes:\n      label: Describe the Bug\n      description: A clear and concise description of what the bug is.\n    validations:\n      required: true\n\n  - type: textarea\n    id: logs\n    attributes:\n      label: Logs from affected hosts\n      description: |\n        Please provide logs from ALL affected hosts during the time of the issue. If you do not provide logs we will be unable to assist you!\n\n        [Learn how to find Nebula logs here.](https://nebula.defined.net/docs/guides/viewing-nebula-logs/)\n\n        Improve formatting by using <code>```</code> at the beginning and end of each log block.\n      value: |\n        ```\n\n        ```\n    validations:\n      required: true\n\n  - type: textarea\n    id: configs\n    attributes:\n      label: Config files from affected hosts\n      description: |\n        Provide config files for all affected hosts.\n\n        Improve formatting by using <code>```</code> at the beginning and end of each config file.\n      value: |\n        ```\n\n        ```\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: true\ncontact_links:\n  - name: 💨 Performance Issues\n    url: https://github.com/slackhq/nebula/discussions/new/choose\n    about: 'We ask that you create a discussion instead of an issue for performance-related questions. This allows us to have a more open conversation about the issue and helps us to better understand the problem.'\n\n  - name: 📄 Documentation Issues\n    url: https://github.com/definednet/nebula-docs\n    about: \"If you've found an issue with the website documentation, please file it in the nebula-docs repository.\"\n\n  - name: 📱 Mobile Nebula Issues\n    url: https://github.com/definednet/mobile_nebula\n    about: \"If you're using the mobile Nebula app and have found an issue, please file it in the mobile_nebula repository.\"\n\n  - name: 📘 Documentation\n    url: https://nebula.defined.net/docs/\n    about: 'The documentation is the best place to start if you are new to Nebula.'\n\n  - name: 💁 Support/Chat\n    url: https://join.slack.com/t/nebulaoss/shared_invite/zt-39pk4xopc-CUKlGcb5Z39dQ0cK1v7ehA\n    about: 'For faster support, join us on Slack for assistance!'\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n\n  - package-ecosystem: \"gomod\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n    groups:\n      golang-x-dependencies:\n        patterns:\n          - \"golang.org/x/*\"\n      zx2c4-dependencies:\n        patterns:\n          - \"golang.zx2c4.com/*\"\n      protobuf-dependencies:\n        patterns:\n          - \"github.com/golang/protobuf\"\n          - \"google.golang.org/protobuf\"\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "<!--\nThank you for taking the time to submit a pull request!\n\nPlease be sure to provide a clear description of what you're trying to achieve with the change.\n\n- If you're submitting a new feature, please explain how to use it and document any new config options in the example config.\n- If you're submitting a bugfix, please link the related issue or describe the circumstances surrounding the issue.\n- If you're changing a default, explain why you believe the new default is appropriate for most users.\n\nP.S. If you're only updating the README or other docs, please file a pull request here instead: https://github.com/DefinedNet/nebula-docs\n-->\n"
  },
  {
    "path": ".github/workflows/gofmt.yml",
    "content": "name: gofmt\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    paths:\n      - '.github/workflows/gofmt.yml'\n      - '**.go'\njobs:\n\n  gofmt:\n    name: Run gofmt\n    runs-on: ubuntu-latest\n    steps:\n\n    - uses: actions/checkout@v6\n\n    - uses: actions/setup-go@v6\n      with:\n        go-version: '1.25'\n        check-latest: true\n\n    - name: Install goimports\n      run: |\n        go install golang.org/x/tools/cmd/goimports@latest\n\n    - name: gofmt\n      run: |\n        if [ \"$(find . -iname '*.go' | grep -v '\\.pb\\.go$' | xargs goimports -l)\" ]\n        then\n          find . -iname '*.go' | grep -v '\\.pb\\.go$' | xargs goimports -d\n          exit 1\n        fi\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "on:\n  push:\n    tags:\n    - 'v[0-9]+.[0-9]+.[0-9]*'\n\nname: Create release and upload binaries\n\njobs:\n  build-linux:\n    name: Build Linux/BSD All\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n\n      - uses: actions/setup-go@v6\n        with:\n          go-version: '1.25'\n          check-latest: true\n\n      - name: Build\n        run: |\n          make BUILD_NUMBER=\"${GITHUB_REF#refs/tags/v}\" release-linux release-freebsd release-openbsd release-netbsd\n          mkdir release\n          mv build/*.tar.gz release\n\n      - name: Upload artifacts\n        uses: actions/upload-artifact@v6\n        with:\n          name: linux-latest\n          path: release\n\n  build-windows:\n    name: Build Windows\n    runs-on: windows-latest\n    steps:\n      - uses: actions/checkout@v6\n\n      - uses: actions/setup-go@v6\n        with:\n          go-version: '1.25'\n          check-latest: true\n\n      - name: Build\n        run: |\n          echo $Env:GITHUB_REF.Substring(11)\n          mkdir build\\windows-amd64\n          $Env:GOARCH = \"amd64\"\n          go build -trimpath -ldflags \"-X main.Build=$($Env:GITHUB_REF.Substring(11))\" -o build\\windows-amd64\\nebula.exe ./cmd/nebula-service\n          go build -trimpath -ldflags \"-X main.Build=$($Env:GITHUB_REF.Substring(11))\" -o build\\windows-amd64\\nebula-cert.exe ./cmd/nebula-cert\n          mkdir build\\windows-arm64\n          $Env:GOARCH = \"arm64\"\n          go build -trimpath -ldflags \"-X main.Build=$($Env:GITHUB_REF.Substring(11))\" -o build\\windows-arm64\\nebula.exe ./cmd/nebula-service\n          go build -trimpath -ldflags \"-X main.Build=$($Env:GITHUB_REF.Substring(11))\" -o build\\windows-arm64\\nebula-cert.exe ./cmd/nebula-cert\n          mkdir build\\dist\\windows\n          mv dist\\windows\\wintun build\\dist\\windows\\\n\n      - name: Upload artifacts\n        uses: actions/upload-artifact@v6\n        with:\n          name: windows-latest\n          path: build\n\n  build-darwin:\n    name: Build Universal Darwin\n    env:\n      HAS_SIGNING_CREDS: ${{ secrets.AC_USERNAME != '' }}\n    runs-on: macos-latest\n    steps:\n      - uses: actions/checkout@v6\n\n      - uses: actions/setup-go@v6\n        with:\n          go-version: '1.25'\n          check-latest: true\n\n      - name: Import certificates\n        if: env.HAS_SIGNING_CREDS == 'true'\n        uses: Apple-Actions/import-codesign-certs@v6\n        with:\n          p12-file-base64: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_P12_BASE64 }}\n          p12-password: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_PASSWORD }}\n\n      - name: Build, sign, and notarize\n        env:\n          AC_USERNAME: ${{ secrets.AC_USERNAME }}\n          AC_PASSWORD: ${{ secrets.AC_PASSWORD }}\n        run: |\n          rm -rf release\n          mkdir release\n          make BUILD_NUMBER=\"${GITHUB_REF#refs/tags/v}\" service build/darwin-amd64/nebula build/darwin-amd64/nebula-cert\n          make BUILD_NUMBER=\"${GITHUB_REF#refs/tags/v}\" service build/darwin-arm64/nebula build/darwin-arm64/nebula-cert\n          lipo -create -output ./release/nebula ./build/darwin-amd64/nebula ./build/darwin-arm64/nebula\n          lipo -create -output ./release/nebula-cert ./build/darwin-amd64/nebula-cert ./build/darwin-arm64/nebula-cert\n\n          if [ -n \"$AC_USERNAME\" ]; then\n            codesign -s \"10BC1FDDEB6CE753550156C0669109FAC49E4D1E\" -f -v --timestamp --options=runtime -i \"net.defined.nebula\" ./release/nebula\n            codesign -s \"10BC1FDDEB6CE753550156C0669109FAC49E4D1E\" -f -v --timestamp --options=runtime -i \"net.defined.nebula-cert\" ./release/nebula-cert\n          fi\n\n          zip -j release/nebula-darwin.zip release/nebula-cert release/nebula\n\n          if [ -n \"$AC_USERNAME\" ]; then\n            xcrun notarytool submit ./release/nebula-darwin.zip --team-id \"576H3XS7FP\" --apple-id \"$AC_USERNAME\" --password \"$AC_PASSWORD\" --wait\n          fi\n\n      - name: Upload artifacts\n        uses: actions/upload-artifact@v6\n        with:\n          name: darwin-latest\n          path: ./release/*\n\n  build-docker:\n    name: Create and Upload Docker Images\n    # Technically we only need build-linux to succeed, but if any platforms fail we'll\n    # want to investigate and restart the build\n    needs: [build-linux, build-darwin, build-windows]\n    runs-on: ubuntu-latest\n    env:\n      HAS_DOCKER_CREDS: ${{ vars.DOCKERHUB_USERNAME != '' && secrets.DOCKERHUB_TOKEN != '' }}\n    # XXX It's not possible to write a conditional here, so instead we do it on every step\n    #if: ${{ env.HAS_DOCKER_CREDS == 'true' }}\n    steps:\n      # Be sure to checkout the code before downloading artifacts, or they will\n      # be overwritten\n      - name: Checkout code\n        if: ${{ env.HAS_DOCKER_CREDS == 'true' }}\n        uses: actions/checkout@v6\n\n      - name: Download artifacts\n        if: ${{ env.HAS_DOCKER_CREDS == 'true' }}\n        uses: actions/download-artifact@v7\n        with:\n          name: linux-latest\n          path: artifacts\n\n      - name: Login to Docker Hub\n        if: ${{ env.HAS_DOCKER_CREDS == 'true' }}\n        uses: docker/login-action@v3\n        with:\n          username: ${{ vars.DOCKERHUB_USERNAME }}\n          password: ${{ secrets.DOCKERHUB_TOKEN }}\n\n      - name: Set up Docker Buildx\n        if: ${{ env.HAS_DOCKER_CREDS == 'true' }}\n        uses: docker/setup-buildx-action@v3\n\n      - name: Build and push images\n        if: ${{ env.HAS_DOCKER_CREDS == 'true' }}\n        env:\n          DOCKER_IMAGE_REPO: ${{ vars.DOCKER_IMAGE_REPO || 'nebulaoss/nebula' }}\n          DOCKER_IMAGE_TAG: ${{ vars.DOCKER_IMAGE_TAG || 'latest' }}\n        run: |\n          mkdir -p build/linux-{amd64,arm64}\n          tar -zxvf artifacts/nebula-linux-amd64.tar.gz -C build/linux-amd64/\n          tar -zxvf artifacts/nebula-linux-arm64.tar.gz -C build/linux-arm64/\n          docker buildx build . --push -f docker/Dockerfile --platform linux/amd64,linux/arm64 --tag \"${DOCKER_IMAGE_REPO}:${DOCKER_IMAGE_TAG}\" --tag \"${DOCKER_IMAGE_REPO}:${GITHUB_REF#refs/tags/v}\"\n\n  release:\n    name: Create and Upload Release\n    needs: [build-linux, build-darwin, build-windows]\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Download artifacts\n        uses: actions/download-artifact@v7\n        with:\n          path: artifacts\n\n      - name: Zip Windows\n        run: |\n          cd artifacts/windows-latest\n          cp windows-amd64/* .\n          zip -r nebula-windows-amd64.zip nebula.exe nebula-cert.exe dist\n          cp windows-arm64/* .\n          zip -r nebula-windows-arm64.zip nebula.exe nebula-cert.exe dist\n\n      - name: Create sha256sum\n        run: |\n          cd artifacts\n          for dir in linux-latest darwin-latest windows-latest\n          do\n            (\n              cd $dir\n              if [ \"$dir\" = windows-latest ]\n              then\n                sha256sum <windows-amd64/nebula.exe | sed 's=-$=nebula-windows-amd64.zip/nebula.exe='\n                sha256sum <windows-amd64/nebula-cert.exe | sed 's=-$=nebula-windows-amd64.zip/nebula-cert.exe='\n                sha256sum <windows-arm64/nebula.exe | sed 's=-$=nebula-windows-arm64.zip/nebula.exe='\n                sha256sum <windows-arm64/nebula-cert.exe | sed 's=-$=nebula-windows-arm64.zip/nebula-cert.exe='\n                sha256sum nebula-windows-amd64.zip\n                sha256sum nebula-windows-arm64.zip\n              elif [ \"$dir\" = darwin-latest ]\n              then\n                sha256sum <nebula-darwin.zip | sed 's=-$=nebula-darwin.zip='\n                sha256sum <nebula | sed 's=-$=nebula-darwin.zip/nebula='\n                sha256sum <nebula-cert | sed 's=-$=nebula-darwin.zip/nebula-cert='\n              else\n                for v in *.tar.gz\n                do\n                  sha256sum $v\n                  tar zxf $v --to-command='sh -c \"sha256sum | sed s=-$='$v'/$TAR_FILENAME=\"'\n                done\n              fi\n            )\n          done | sort -k 2 >SHASUM256.txt\n\n      - name: Create Release\n        id: create_release\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          cd artifacts\n          gh release create \\\n            --verify-tag \\\n            --title \"Release ${{ github.ref_name }}\" \\\n            \"${{ github.ref_name }}\" \\\n            SHASUM256.txt *-latest/*.zip *-latest/*.tar.gz\n"
  },
  {
    "path": ".github/workflows/smoke/.gitignore",
    "content": "/build\n"
  },
  {
    "path": ".github/workflows/smoke/Dockerfile",
    "content": "FROM ubuntu:jammy\n\nRUN apt-get update && apt-get install -y iputils-ping ncat tcpdump\n\nADD ./build /nebula\n\nWORKDIR /nebula\n\nENTRYPOINT [\"/nebula/nebula\"]\n"
  },
  {
    "path": ".github/workflows/smoke/build-relay.sh",
    "content": "#!/bin/sh\n\nset -e -x\n\nrm -rf ./build\nmkdir ./build\n\n(\n    cd build\n\n    cp ../../../../build/linux-amd64/nebula .\n    cp ../../../../build/linux-amd64/nebula-cert .\n\n    HOST=\"lighthouse1\" AM_LIGHTHOUSE=true ../genconfig.sh >lighthouse1.yml <<EOF\nrelay:\n  am_relay: true\nEOF\n\n    export LIGHTHOUSES=\"192.168.100.1 172.17.0.2:4242\"\n    export REMOTE_ALLOW_LIST='{\"172.17.0.4/32\": false, \"172.17.0.5/32\": false}'\n\n    HOST=\"host2\" ../genconfig.sh >host2.yml <<EOF\nrelay:\n  relays:\n    - 192.168.100.1\nEOF\n\n    export REMOTE_ALLOW_LIST='{\"172.17.0.3/32\": false}'\n\n    HOST=\"host3\" ../genconfig.sh >host3.yml\n\n    HOST=\"host4\" ../genconfig.sh >host4.yml <<EOF\nrelay:\n  use_relays: false\nEOF\n\n    ../../../../nebula-cert ca -name \"Smoke Test\"\n    ../../../../nebula-cert sign -name \"lighthouse1\" -groups \"lighthouse,lighthouse1\" -ip \"192.168.100.1/24\"\n    ../../../../nebula-cert sign -name \"host2\" -groups \"host,host2\" -ip \"192.168.100.2/24\"\n    ../../../../nebula-cert sign -name \"host3\" -groups \"host,host3\" -ip \"192.168.100.3/24\"\n    ../../../../nebula-cert sign -name \"host4\" -groups \"host,host4\" -ip \"192.168.100.4/24\"\n)\n\ndocker build -t nebula:smoke-relay .\n"
  },
  {
    "path": ".github/workflows/smoke/build.sh",
    "content": "#!/bin/sh\n\nset -e -x\n\nrm -rf ./build\nmkdir ./build\n\n# TODO: Assumes your docker bridge network is a /24, and the first container that launches will be .1\n# - We could make this better by launching the lighthouse first and then fetching what IP it is.\nNET=\"$(docker network inspect bridge -f '{{ range .IPAM.Config }}{{ .Subnet }}{{ end }}' | cut -d. -f1-3)\"\n\n(\n    cd build\n\n    cp ../../../../build/linux-amd64/nebula .\n    cp ../../../../build/linux-amd64/nebula-cert .\n\n    if [ \"$1\" ]\n    then\n        cp \"../../../../build/$1/nebula\" \"$1-nebula\"\n    fi\n\n    HOST=\"lighthouse1\" \\\n        AM_LIGHTHOUSE=true \\\n        ../genconfig.sh >lighthouse1.yml\n\n    HOST=\"host2\" \\\n        LIGHTHOUSES=\"192.168.100.1 $NET.2:4242\" \\\n        ../genconfig.sh >host2.yml\n\n    HOST=\"host3\" \\\n        LIGHTHOUSES=\"192.168.100.1 $NET.2:4242\" \\\n        INBOUND='[{\"port\": \"any\", \"proto\": \"icmp\", \"group\": \"lighthouse\"}]' \\\n        ../genconfig.sh >host3.yml\n\n    HOST=\"host4\" \\\n        LIGHTHOUSES=\"192.168.100.1 $NET.2:4242\" \\\n        OUTBOUND='[{\"port\": \"any\", \"proto\": \"icmp\", \"group\": \"lighthouse\"}]' \\\n        ../genconfig.sh >host4.yml\n\n    ../../../../nebula-cert ca -curve \"${CURVE:-25519}\" -name \"Smoke Test\"\n    ../../../../nebula-cert sign -name \"lighthouse1\" -groups \"lighthouse,lighthouse1\" -ip \"192.168.100.1/24\"\n    ../../../../nebula-cert sign -name \"host2\" -groups \"host,host2\" -ip \"192.168.100.2/24\"\n    ../../../../nebula-cert sign -name \"host3\" -groups \"host,host3\" -ip \"192.168.100.3/24\"\n    ../../../../nebula-cert sign -name \"host4\" -groups \"host,host4\" -ip \"192.168.100.4/24\"\n)\n\ndocker build -t \"nebula:${NAME:-smoke}\" .\n"
  },
  {
    "path": ".github/workflows/smoke/genconfig.sh",
    "content": "#!/bin/sh\n\nset -e\n\nFIREWALL_ALL='[{\"port\": \"any\", \"proto\": \"any\", \"host\": \"any\"}]'\n\nif [ \"$STATIC_HOSTS\" ] || [ \"$LIGHTHOUSES\" ]\nthen\n  echo \"static_host_map:\"\n  echo \"$STATIC_HOSTS\" | while read -r NEBULA_IP STATIC\n  do\n    [ -z \"$NEBULA_IP\" ] || echo \"  '$NEBULA_IP': ['$STATIC']\"\n  done\n  echo \"$LIGHTHOUSES\" | while read -r NEBULA_IP STATIC\n  do\n    [ -z \"$NEBULA_IP\" ] || echo \"  '$NEBULA_IP': ['$STATIC']\"\n  done\n  echo\nfi\n\nlighthouse_hosts() {\n  if [ \"$LIGHTHOUSES\" ]\n  then\n    echo\n    echo \"$LIGHTHOUSES\" | while read -r NEBULA_IP STATIC\n    do\n      echo \"    - '$NEBULA_IP'\"\n    done\n  else\n    echo \"[]\"\n  fi\n}\n\ncat <<EOF\npki:\n  ca: ca.crt\n  cert: ${HOST}.crt\n  key: ${HOST}.key\n\nlighthouse:\n  am_lighthouse: ${AM_LIGHTHOUSE:-false}\n  hosts: $(lighthouse_hosts)\n  remote_allow_list: ${REMOTE_ALLOW_LIST}\n\nlisten:\n  host: 0.0.0.0\n  port: ${LISTEN_PORT:-4242}\n\ntun:\n  dev: ${TUN_DEV:-tun0}\n\nfirewall:\n  inbound_action: reject\n  outbound_action: reject\n  outbound: ${OUTBOUND:-$FIREWALL_ALL}\n  inbound: ${INBOUND:-$FIREWALL_ALL}\n\n$(test -t 0 || cat)\nEOF\n"
  },
  {
    "path": ".github/workflows/smoke/smoke-relay.sh",
    "content": "#!/bin/bash\n\nset -e -x\n\nset -o pipefail\n\nmkdir -p logs\n\ncleanup() {\n    echo\n    echo \" *** cleanup\"\n    echo\n\n    set +e\n    if [ \"$(jobs -r)\" ]\n    then\n        docker kill lighthouse1 host2 host3 host4\n    fi\n}\n\ntrap cleanup EXIT\n\ndocker run --name lighthouse1 --rm nebula:smoke-relay -config lighthouse1.yml -test\ndocker run --name host2 --rm nebula:smoke-relay -config host2.yml -test\ndocker run --name host3 --rm nebula:smoke-relay -config host3.yml -test\ndocker run --name host4 --rm nebula:smoke-relay -config host4.yml -test\n\ndocker 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]  /' &\nsleep 1\ndocker 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]  /' &\nsleep 1\ndocker 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]  /' &\nsleep 1\ndocker 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]  /' &\nsleep 1\n\nset +x\necho\necho \" *** Testing ping from lighthouse1\"\necho\nset -x\ndocker exec lighthouse1 ping -c1 192.168.100.2\ndocker exec lighthouse1 ping -c1 192.168.100.3\ndocker exec lighthouse1 ping -c1 192.168.100.4\n\nset +x\necho\necho \" *** Testing ping from host2\"\necho\nset -x\ndocker exec host2 ping -c1 192.168.100.1\n# Should fail because no relay configured in this direction\n! docker exec host2 ping -c1 192.168.100.3 -w5 || exit 1\n! docker exec host2 ping -c1 192.168.100.4 -w5 || exit 1\n\nset +x\necho\necho \" *** Testing ping from host3\"\necho\nset -x\ndocker exec host3 ping -c1 192.168.100.1\ndocker exec host3 ping -c1 192.168.100.2\ndocker exec host3 ping -c1 192.168.100.4\n\nset +x\necho\necho \" *** Testing ping from host4\"\necho\nset -x\ndocker exec host4 ping -c1 192.168.100.1\n# Should fail because relays not allowed\n! docker exec host4 ping -c1 192.168.100.2 -w5 || exit 1\ndocker exec host4 ping -c1 192.168.100.3\n\ndocker exec host4 sh -c 'kill 1'\ndocker exec host3 sh -c 'kill 1'\ndocker exec host2 sh -c 'kill 1'\ndocker exec lighthouse1 sh -c 'kill 1'\nsleep 5\n\nif [ \"$(jobs -r)\" ]\nthen\n    echo \"nebula still running after SIGTERM sent\" >&2\n    exit 1\nfi\n"
  },
  {
    "path": ".github/workflows/smoke/smoke-vagrant.sh",
    "content": "#!/bin/bash\n\nset -e -x\n\nset -o pipefail\n\nexport VAGRANT_CWD=\"$PWD/vagrant-$1\"\n\nmkdir -p logs\n\ncleanup() {\n    echo\n    echo \" *** cleanup\"\n    echo\n\n    set +e\n    if [ \"$(jobs -r)\" ]\n    then\n        docker kill lighthouse1 host2\n    fi\n    vagrant destroy -f\n}\n\ntrap cleanup EXIT\n\nCONTAINER=\"nebula:${NAME:-smoke}\"\n\ndocker run --name lighthouse1 --rm \"$CONTAINER\" -config lighthouse1.yml -test\ndocker run --name host2 --rm \"$CONTAINER\" -config host2.yml -test\n\nvagrant up\nvagrant ssh -c \"cd /nebula && /nebula/$1-nebula -config host3.yml -test\" -- -T\n\ndocker 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]  /' &\nsleep 1\ndocker 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]  /' &\nsleep 1\nvagrant 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]  /' &\nsleep 15\n\n# grab tcpdump pcaps for debugging\ndocker exec lighthouse1 tcpdump -i nebula1 -q -w - -U 2>logs/lighthouse1.inside.log >logs/lighthouse1.inside.pcap &\ndocker exec lighthouse1 tcpdump -i eth0 -q -w - -U 2>logs/lighthouse1.outside.log >logs/lighthouse1.outside.pcap &\ndocker exec host2 tcpdump -i nebula1 -q -w - -U 2>logs/host2.inside.log >logs/host2.inside.pcap &\ndocker exec host2 tcpdump -i eth0 -q -w - -U 2>logs/host2.outside.log >logs/host2.outside.pcap &\n# vagrant ssh -c \"tcpdump -i nebula1 -q -w - -U\" 2>logs/host3.inside.log >logs/host3.inside.pcap &\n# vagrant ssh -c \"tcpdump -i eth0 -q -w - -U\" 2>logs/host3.outside.log >logs/host3.outside.pcap &\n\n#docker exec host2 ncat -nklv 0.0.0.0 2000 &\n#vagrant ssh -c \"ncat -nklv 0.0.0.0 2000\" &\n#docker exec host2 ncat -e '/usr/bin/echo host2' -nkluv 0.0.0.0 3000 &\n#vagrant ssh -c \"ncat -e '/usr/bin/echo host3' -nkluv 0.0.0.0 3000\" &\n\nset +x\necho\necho \" *** Testing ping from lighthouse1\"\necho\nset -x\ndocker exec lighthouse1 ping -c1 192.168.100.2\ndocker exec lighthouse1 ping -c1 192.168.100.3\n\nset +x\necho\necho \" *** Testing ping from host2\"\necho\nset -x\ndocker exec host2 ping -c1 192.168.100.1\n# Should fail because not allowed by host3 inbound firewall\n! docker exec host2 ping -c1 192.168.100.3 -w5 || exit 1\n\n#set +x\n#echo\n#echo \" *** Testing ncat from host2\"\n#echo\n#set -x\n# Should fail because not allowed by host3 inbound firewall\n#! docker exec host2 ncat -nzv -w5 192.168.100.3 2000 || exit 1\n#! docker exec host2 ncat -nzuv -w5 192.168.100.3 3000 | grep -q host3 || exit 1\n\nset +x\necho\necho \" *** Testing ping from host3\"\necho\nset -x\nvagrant ssh -c \"ping -c1 192.168.100.1\" -- -T\nvagrant ssh -c \"ping -c1 192.168.100.2\" -- -T\n\n#set +x\n#echo\n#echo \" *** Testing ncat from host3\"\n#echo\n#set -x\n#vagrant ssh -c \"ncat -nzv -w5 192.168.100.2 2000\"\n#vagrant ssh -c \"ncat -nzuv -w5 192.168.100.2 3000\" | grep -q host2\n\nvagrant ssh -c \"sudo xargs kill </nebula/pid\" -- -T\ndocker exec host2 sh -c 'kill 1'\ndocker exec lighthouse1 sh -c 'kill 1'\nsleep 1\n\nif [ \"$(jobs -r)\" ]\nthen\n    echo \"nebula still running after SIGTERM sent\" >&2\n    exit 1\nfi\n"
  },
  {
    "path": ".github/workflows/smoke/smoke.sh",
    "content": "#!/bin/bash\n\nset -e -x\n\nset -o pipefail\n\nmkdir -p logs\n\ncleanup() {\n    echo\n    echo \" *** cleanup\"\n    echo\n\n    set +e\n    if [ \"$(jobs -r)\" ]\n    then\n        docker kill lighthouse1 host2 host3 host4\n    fi\n}\n\ntrap cleanup EXIT\n\nCONTAINER=\"nebula:${NAME:-smoke}\"\n\ndocker run --name lighthouse1 --rm \"$CONTAINER\" -config lighthouse1.yml -test\ndocker run --name host2 --rm \"$CONTAINER\" -config host2.yml -test\ndocker run --name host3 --rm \"$CONTAINER\" -config host3.yml -test\ndocker run --name host4 --rm \"$CONTAINER\" -config host4.yml -test\n\ndocker 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]  /' &\nsleep 1\ndocker 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]  /' &\nsleep 1\ndocker 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]  /' &\nsleep 1\ndocker 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]  /' &\nsleep 1\n\n# grab tcpdump pcaps for debugging\ndocker exec lighthouse1 tcpdump -i tun0 -q -w - -U 2>logs/lighthouse1.inside.log >logs/lighthouse1.inside.pcap &\ndocker exec lighthouse1 tcpdump -i eth0 -q -w - -U 2>logs/lighthouse1.outside.log >logs/lighthouse1.outside.pcap &\ndocker exec host2 tcpdump -i tun0 -q -w - -U 2>logs/host2.inside.log >logs/host2.inside.pcap &\ndocker exec host2 tcpdump -i eth0 -q -w - -U 2>logs/host2.outside.log >logs/host2.outside.pcap &\ndocker exec host3 tcpdump -i tun0 -q -w - -U 2>logs/host3.inside.log >logs/host3.inside.pcap &\ndocker exec host3 tcpdump -i eth0 -q -w - -U 2>logs/host3.outside.log >logs/host3.outside.pcap &\ndocker exec host4 tcpdump -i tun0 -q -w - -U 2>logs/host4.inside.log >logs/host4.inside.pcap &\ndocker exec host4 tcpdump -i eth0 -q -w - -U 2>logs/host4.outside.log >logs/host4.outside.pcap &\n\ndocker exec host2 ncat -nklv 0.0.0.0 2000 &\ndocker exec host3 ncat -nklv 0.0.0.0 2000 &\ndocker exec host4 ncat -nkluv 0.0.0.0 4000 &\ndocker exec host2 ncat -e '/usr/bin/echo host2' -nkluv 0.0.0.0 3000 &\ndocker exec host3 ncat -e '/usr/bin/echo host3' -nkluv 0.0.0.0 3000 &\n\nset +x\necho\necho \" *** Testing ping from lighthouse1\"\necho\nset -x\ndocker exec lighthouse1 ping -c1 192.168.100.2\ndocker exec lighthouse1 ping -c1 192.168.100.3\n\nset +x\necho\necho \" *** Testing ping from host2\"\necho\nset -x\ndocker exec host2 ping -c1 192.168.100.1\n# Should fail because not allowed by host3 inbound firewall\n! docker exec host2 ping -c1 192.168.100.3 -w5 || exit 1\n\nset +x\necho\necho \" *** Testing ncat from host2\"\necho\nset -x\n# Should fail because not allowed by host3 inbound firewall\n! docker exec host2 ncat -nzv -w5 192.168.100.3 2000 || exit 1\n! docker exec host2 ncat -nzuv -w5 192.168.100.3 3000 | grep -q host3 || exit 1\n\nset +x\necho\necho \" *** Testing ping from host3\"\necho\nset -x\ndocker exec host3 ping -c1 192.168.100.1\ndocker exec host3 ping -c1 192.168.100.2\n\nset +x\necho\necho \" *** Testing ncat from host3\"\necho\nset -x\ndocker exec host3 ncat -nzv -w5 192.168.100.2 2000\ndocker exec host3 ncat -nzuv -w5 192.168.100.2 3000 | grep -q host2\n\nset +x\necho\necho \" *** Testing ping from host4\"\necho\nset -x\ndocker exec host4 ping -c1 192.168.100.1\n# Should fail because not allowed by host4 outbound firewall\n! docker exec host4 ping -c1 192.168.100.2 -w5 || exit 1\n! docker exec host4 ping -c1 192.168.100.3 -w5 || exit 1\n\nset +x\necho\necho \" *** Testing ncat from host4\"\necho\nset -x\n# Should fail because not allowed by host4 outbound firewall\n! docker exec host4 ncat -nzv -w5 192.168.100.2 2000 || exit 1\n! docker exec host4 ncat -nzv -w5 192.168.100.3 2000 || exit 1\n! docker exec host4 ncat -nzuv -w5 192.168.100.2 3000 | grep -q host2 || exit 1\n! docker exec host4 ncat -nzuv -w5 192.168.100.3 3000 | grep -q host3 || exit 1\n\nset +x\necho\necho \" *** Testing conntrack\"\necho\nset -x\n\n# host2 speaking to host4 on UDP 4000 should allow it to reply, when firewall rules would normally not permit this\ndocker exec host2 sh -c \"/usr/bin/echo host2 | ncat -nuv 192.168.100.4 4000\"\ndocker exec host2 ncat -e '/usr/bin/echo helloagainfromhost2' -nkluv 0.0.0.0 4000 &\ndocker exec host4 sh -c \"/usr/bin/echo host4 | ncat -nuv 192.168.100.2 4000\"\n\ndocker exec host4 sh -c 'kill 1'\ndocker exec host3 sh -c 'kill 1'\ndocker exec host2 sh -c 'kill 1'\ndocker exec lighthouse1 sh -c 'kill 1'\nsleep 5\n\nif [ \"$(jobs -r)\" ]\nthen\n    echo \"nebula still running after SIGTERM sent\" >&2\n    exit 1\nfi\n"
  },
  {
    "path": ".github/workflows/smoke/vagrant-freebsd-amd64/Vagrantfile",
    "content": "# -*- mode: ruby -*-\n# vi: set ft=ruby :\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"generic/freebsd14\"\n\n  config.vm.synced_folder \"../build\", \"/nebula\", type: \"rsync\"\nend\n"
  },
  {
    "path": ".github/workflows/smoke/vagrant-linux-386/Vagrantfile",
    "content": "# -*- mode: ruby -*-\n# vi: set ft=ruby :\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"ubuntu/xenial32\"\n\n  config.vm.synced_folder \"../build\", \"/nebula\"\nend\n"
  },
  {
    "path": ".github/workflows/smoke/vagrant-linux-amd64-ipv6disable/Vagrantfile",
    "content": "# -*- mode: ruby -*-\n# vi: set ft=ruby :\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"ubuntu/jammy64\"\n\n  config.vm.synced_folder \"../build\", \"/nebula\"\n\n  config.vm.provision :shell do |shell|\n    shell.inline = <<-EOF\n      sed -i 's/GRUB_CMDLINE_LINUX=\"\"/GRUB_CMDLINE_LINUX=\"ipv6.disable=1\"/' /etc/default/grub\n      update-grub\n    EOF\n    shell.privileged = true\n    shell.reboot = true\n  end\nend\n"
  },
  {
    "path": ".github/workflows/smoke/vagrant-netbsd-amd64/Vagrantfile",
    "content": "# -*- mode: ruby -*-\n# vi: set ft=ruby :\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"generic/netbsd9\"\n\n  config.vm.synced_folder \"../build\", \"/nebula\", type: \"rsync\"\nend\n"
  },
  {
    "path": ".github/workflows/smoke/vagrant-openbsd-amd64/Vagrantfile",
    "content": "# -*- mode: ruby -*-\n# vi: set ft=ruby :\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"generic/openbsd7\"\n\n  config.vm.synced_folder \"../build\", \"/nebula\", type: \"rsync\"\nend\n"
  },
  {
    "path": ".github/workflows/smoke-extra.yml",
    "content": "name: smoke-extra\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    types: [opened, synchronize, labeled, reopened]\n    paths:\n      - '.github/workflows/smoke**'\n      - '**Makefile'\n      - '**.go'\n      - '**.proto'\n      - 'go.mod'\n      - 'go.sum'\njobs:\n\n  smoke-extra:\n    if: github.ref == 'refs/heads/master' || contains(github.event.pull_request.labels.*.name, 'smoke-test-extra')\n    name: Run extra smoke tests\n    runs-on: ubuntu-latest\n    steps:\n\n    - uses: actions/checkout@v6\n\n    - uses: actions/setup-go@v6\n      with:\n        go-version: '1.25'\n        check-latest: true\n\n    - name: add hashicorp source\n      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\n\n    - name: workaround AMD-V issue  # https://github.com/cri-o/packaging/pull/306\n      run: sudo rmmod kvm_amd\n\n    - name: install vagrant\n      run: sudo apt-get update && sudo apt-get install -y vagrant virtualbox\n\n    - name: freebsd-amd64\n      run: make smoke-vagrant/freebsd-amd64\n\n    - name: openbsd-amd64\n      run: make smoke-vagrant/openbsd-amd64\n\n    - name: netbsd-amd64\n      run: make smoke-vagrant/netbsd-amd64\n\n    - name: linux-386\n      run: make smoke-vagrant/linux-386\n\n    - name: linux-amd64-ipv6disable\n      run: make smoke-vagrant/linux-amd64-ipv6disable\n\n    timeout-minutes: 30\n"
  },
  {
    "path": ".github/workflows/smoke.yml",
    "content": "name: smoke\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    paths:\n      - '.github/workflows/smoke**'\n      - '**Makefile'\n      - '**.go'\n      - '**.proto'\n      - 'go.mod'\n      - 'go.sum'\njobs:\n\n  smoke:\n    name: Run multi node smoke test\n    runs-on: ubuntu-latest\n    steps:\n\n    - uses: actions/checkout@v6\n\n    - uses: actions/setup-go@v6\n      with:\n        go-version: '1.25'\n        check-latest: true\n\n    - name: build\n      run: make bin-docker CGO_ENABLED=1 BUILD_ARGS=-race\n\n    - name: setup docker image\n      working-directory: ./.github/workflows/smoke\n      run: ./build.sh\n\n    - name: run smoke\n      working-directory: ./.github/workflows/smoke\n      run: ./smoke.sh\n\n    - name: setup relay docker image\n      working-directory: ./.github/workflows/smoke\n      run: ./build-relay.sh\n\n    - name: run smoke relay\n      working-directory: ./.github/workflows/smoke\n      run: ./smoke-relay.sh\n\n    - name: setup docker image for P256\n      working-directory: ./.github/workflows/smoke\n      run: NAME=\"smoke-p256\" CURVE=P256 ./build.sh\n\n    - name: run smoke-p256\n      working-directory: ./.github/workflows/smoke\n      run: NAME=\"smoke-p256\" ./smoke.sh\n\n    timeout-minutes: 10\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Build and test\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    paths:\n      - '.github/workflows/test.yml'\n      - '**Makefile'\n      - '**.go'\n      - '**.proto'\n      - 'go.mod'\n      - 'go.sum'\njobs:\n\n  test-linux:\n    name: Build all and test on ubuntu-linux\n    runs-on: ubuntu-latest\n    steps:\n\n    - uses: actions/checkout@v6\n\n    - uses: actions/setup-go@v6\n      with:\n        go-version: '1.25'\n        check-latest: true\n\n    - name: Build\n      run: make all\n\n    - name: Vet\n      run: make vet\n\n    - name: golangci-lint\n      uses: golangci/golangci-lint-action@v9\n      with:\n        version: v2.5\n\n    - name: Test\n      run: make test\n\n    - name: End 2 end\n      run: make e2evv\n\n    - name: Build test mobile\n      run: make build-test-mobile\n\n    - uses: actions/upload-artifact@v6\n      with:\n        name: e2e packet flow linux-latest\n        path: e2e/mermaid/linux-latest\n        if-no-files-found: warn\n\n  test-linux-boringcrypto:\n    name: Build and test on linux with boringcrypto\n    runs-on: ubuntu-latest\n    steps:\n\n    - uses: actions/checkout@v6\n\n    - uses: actions/setup-go@v6\n      with:\n        go-version: '1.25'\n        check-latest: true\n\n    - name: Build\n      run: make bin-boringcrypto\n\n    - name: Test\n      run: make test-boringcrypto\n\n    - name: End 2 end\n      run: make e2e GOEXPERIMENT=boringcrypto CGO_ENABLED=1 TEST_ENV=\"TEST_LOGS=1\" TEST_FLAGS=\"-v -ldflags -checklinkname=0\"\n\n  test-linux-pkcs11:\n    name: Build and test on linux with pkcs11\n    runs-on: ubuntu-latest\n    steps:\n\n    - uses: actions/checkout@v6\n\n    - uses: actions/setup-go@v6\n      with:\n        go-version: '1.25'\n        check-latest: true\n\n    - name: Build\n      run: make bin-pkcs11\n\n    - name: Test\n      run: make test-pkcs11\n\n  test:\n    name: Build and test on ${{ matrix.os }}\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        os: [windows-latest, macos-latest]\n    steps:\n\n    - uses: actions/checkout@v6\n\n    - uses: actions/setup-go@v6\n      with:\n        go-version: '1.25'\n        check-latest: true\n\n    - name: Build nebula\n      run: go build ./cmd/nebula\n\n    - name: Build nebula-cert\n      run: go build ./cmd/nebula-cert\n\n    - name: Vet\n      run: make vet\n\n    - name: golangci-lint\n      uses: golangci/golangci-lint-action@v9\n      with:\n        version: v2.5\n\n    - name: Test\n      run: make test\n\n    - name: End 2 end\n      run: make e2evv\n\n    - uses: actions/upload-artifact@v6\n      with:\n        name: e2e packet flow ${{ matrix.os }}\n        path: e2e/mermaid/${{ matrix.os }}\n        if-no-files-found: warn\n"
  },
  {
    "path": ".gitignore",
    "content": "/nebula\n/nebula-cert\n/nebula-arm\n/nebula-arm6\n/nebula-darwin\n/nebula.exe\n/nebula-cert.exe\n**/coverage.out\n**/cover.out\n/cpu.pprof\n/build\n/*.tar.gz\n/e2e/mermaid/\n**.crt\n**.key\n**.pem\n**.pub\n!/examples/quickstart-vagrant/ansible/roles/nebula/files/vagrant-test-ca.key\n!/examples/quickstart-vagrant/ansible/roles/nebula/files/vagrant-test-ca.crt\n"
  },
  {
    "path": ".golangci.yaml",
    "content": "version: \"2\"\nlinters:\n  default: none\n  enable:\n    - testifylint\n  exclusions:\n    generated: lax\n    presets:\n      - comments\n      - common-false-positives\n      - legacy\n      - std-error-handling\n    paths:\n      - third_party$\n      - builtin$\n      - examples$\nformatters:\n  exclusions:\n    generated: lax\n    paths:\n      - third_party$\n      - builtin$\n      - examples$\n"
  },
  {
    "path": "AUTHORS",
    "content": "# This is the official list of Nebula authors for copyright purposes.\n\n# Names should be added to this file as:\n# Name or Organization <email address>\n# The email address is not required for organizations.\n\nSlack Technologies, Inc.\nNate Brown <nbrown.us@gmail.com>\nRyan Huber <rhuber@gmail.com>\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [Unreleased]\n\n## [1.10.3] - 2026-02-06\n\n### Security\n\n- Fix an issue where blocklist bypass is possible when using curve P256 since the signature can have 2 valid representations.\n  Both fingerprint representations will be tested against the blocklist.\n  Any newly issued P256 based certificates will have their signature clamped to the low-s form.\n  Nebula will assert the low-s signature form when validating certificates in a future version. [GHSA-69x3-g4r3-p962](https://github.com/slackhq/nebula/security/advisories/GHSA-69x3-g4r3-p962)\n\n### Changed\n\n- Improve error reporting if nebula fails to start due to a tun device naming issue. (#1588)\n\n## [1.10.2] - 2026-01-21\n\n### Fixed\n\n- Fix panic when using `use_system_route_table` that was introduced in v1.10.1. (#1580)\n\n### Changed\n\n- Fix some typos in comments. (#1582)\n- Dependency updates. (#1581)\n\n## [1.10.1] - 2026-01-16\n\nSee the [v1.10.1](https://github.com/slackhq/nebula/milestone/26?closed=1) milestone for a complete list of changes.\n\n### Fixed\n\n- Fix a bug where an unsafe route derived from the system route table could be lost on a config reload. (#1573)\n- Fix the PEM banner for ECDSA P256 public keys. (#1552)\n- Fix a regression on Windows from 1.9.x where nebula could fall back to a less performant UDP listener if \n  non-critical ioctls failed. (#1568)\n- Fix a bug in handshake processing when a peer sends an unexpected public key. (#1566)\n\n### Added\n\n- Add a config option to control accepting `recv_error` packets which defaults to `always`. (#1569)\n\n### Changed\n\n- Various dependency updates. (#1541, #1549, #1550, #1557, #1558, #1560, #1561, #1570, #1571)\n\n## [1.10.0] - 2025-12-04\n\nSee the [v1.10.0](https://github.com/slackhq/nebula/milestone/16?closed=1) milestone for a complete list of changes.\n\n### Added\n\n- Support for ipv6 and multiple ipv4/6 addresses in the overlay.\n  A new v2 ASN.1 based certificate format.\n  Certificates now have a unified interface for external implementations.\n  (#1212, #1216, #1345, #1359, #1381, #1419, #1464, #1466, #1451, #1476, #1467, #1481, #1399, #1488, #1492, #1495, #1468, #1521, #1535, #1538)\n- Add the ability to mark packets on linux to better target nebula packets in iptables/nftables. (#1331)\n- Add ECMP support for `unsafe_routes`. (#1332)\n- PKCS11 support for P256 keys when built with `pkcs11` tag (#1153, #1482)\n\n### Changed\n\n- **NOTE**: `default_local_cidr_any` now defaults to false, meaning that any firewall rule\n  intended to target an `unsafe_routes` entry must explicitly declare it via the\n  `local_cidr` field. This is almost always the intended behavior. This flag is\n  deprecated and will be removed in a future release. (#1373)\n- Improve logging when a relay is in use on an inbound packet. (#1533)\n- Avoid fatal errors if `rountines` is > 1 on systems that don't support more than 1 routine. (#1531)\n- Log a warning if a firewall rule contains an `any` that negates a more restrictive filter. (#1513)\n- Accept encrypted CA passphrase from an environment variable. (#1421)\n- Allow handshaking with any trusted remote. (#1509)\n- Log only the count of blocklisted certificate fingerprints instead of the entire list. (#1525)\n- Don't fatal when the ssh server is unable to be configured successfully. (#1520)\n- Update to build against go v1.25. (#1483)\n- Allow projects using `nebula` as a library with userspace networking to configure the `logger` and build version. (#1239) \n- Upgrade to `yaml.v3`. (#1148, #1371, #1438, #1478)\n\n### Fixed\n\n- Fix a potential bug with udp ipv4 only on darwin. (#1532)\n- Improve lost packet statistics. (#1441, #1537)\n- Honor `remote_allow_list` in hole punch response. (#1186)\n- Fix a panic when `tun.use_system_route_table` is `true` and a route lacks a destination. (#1437) \n- Fix an issue when `tun.use_system_route_table: true` could result in heavy CPU utilization when many thousands of routes\n  are present. (#1326) \n- Fix tests for 32 bit machines. (#1394)\n- Fix a possible 32bit integer underflow in config handling. (#1353)\n- Fix moving a udp address from one vpn address to another in the `static_host_map`\n  which could cause rapid re-handshaking with an incorrect remote. (#1259)\n- Improve smoke tests in environments where the docker network is not the default. (#1347)\n\n## [1.9.7] - 2025-10-10\n\n### Security\n\n- Fix an issue where Nebula could incorrectly accept and process a packet from an erroneous source IP when the sender's\n  certificate is configured with unsafe_routes (cert v1/v2) or multiple IPs (cert v2). (#1494)\n\n### Changed\n\n- Disable sending `recv_error` messages when a packet is received outside the allowable counter window. (#1459)\n- Improve error messages and remove some unnecessary fatal conditions in the Windows and generic udp listener. (#1453)\n\n## [1.9.6] - 2025-7-15\n\n### Added\n\n- Support dropping inactive tunnels. This is disabled by default in this release but can be enabled with `tunnels.drop_inactive`. See example config for more details. (#1413)\n\n### Fixed\n\n- Fix Darwin freeze due to presence of some Network Extensions (#1426)\n- Ensure the same relay tunnel is always used when multiple relay tunnels are present (#1422)\n- Fix Windows freeze due to ICMP error handling (#1412)\n- Fix relay migration panic (#1403)\n\n## [1.9.5] - 2024-12-05\n\n### Added\n\n- Gracefully ignore v2 certificates. (#1282)\n\n### Fixed\n\n- Fix relays that refuse to re-establish after one of the remote tunnel pairs breaks. (#1277)\n\n## [1.9.4] - 2024-09-09\n\n### Added\n\n- Support UDP dialing with gVisor. (#1181)\n\n### Changed\n\n- Make some Nebula state programmatically available via control object. (#1188)\n- Switch internal representation of IPs to netip, to prepare for IPv6 support\n  in the overlay. (#1173)\n- Minor build and cleanup changes. (#1171, #1164, #1162)\n- Various dependency updates. (#1195, #1190, #1174, #1168, #1167, #1161, #1147, #1146)\n\n### Fixed\n\n- Fix a bug on big endian hosts, like mips. (#1194)\n- Fix a rare panic if a local index collision happens. (#1191)\n- Fix integer wraparound in the calculation of handshake timeouts on 32-bit targets. (#1185)\n\n## [1.9.3] - 2024-06-06\n\n### Fixed\n\n- Initialize messageCounter to 2 instead of verifying later. (#1156)\n\n## [1.9.2] - 2024-06-03\n\n### Fixed\n\n- Ensure messageCounter is set before handshake is complete. (#1154)\n\n## [1.9.1] - 2024-05-29\n\n### Fixed\n\n- Fixed a potential deadlock in GetOrHandshake. (#1151)\n\n## [1.9.0] - 2024-05-07\n\n### Deprecated\n\n- This release adds a new setting `default_local_cidr_any` that defaults to\n  true to match previous behavior, but will default to false in the next\n  release (1.10). When set to false, `local_cidr` is matched correctly for\n  firewall rules on hosts acting as unsafe routers, and should be set for any\n  firewall rules you want to allow unsafe route hosts to access. See the issue\n  and example config for more details. (#1071, #1099)\n\n### Added\n\n- Nebula now has an official Docker image `nebulaoss/nebula` that is\n  distroless and contains just the `nebula` and `nebula-cert` binaries. You\n  can find it here: https://hub.docker.com/r/nebulaoss/nebula (#1037)\n\n- Experimental binaries for `loong64` are now provided. (#1003)\n\n- Added example service script for OpenRC. (#711)\n\n- The SSH daemon now supports inlined host keys. (#1054)\n\n- The SSH daemon now supports certificates with `sshd.trusted_cas`. (#1098)\n\n### Changed\n\n- Config setting `tun.unsafe_routes` is now reloadable. (#1083)\n\n- Small documentation and internal improvements. (#1065, #1067, #1069, #1108,\n  #1109, #1111, #1135)\n\n- Various dependency updates. (#1139, #1138, #1134, #1133, #1126, #1123, #1110,\n  #1094, #1092, #1087, #1086, #1085, #1072, #1063, #1059, #1055, #1053, #1047,\n  #1046, #1034, #1022)\n\n### Removed\n\n- Support for the deprecated `local_range` option has been removed. Please\n  change to `preferred_ranges` (which is also now reloadable). (#1043)\n\n- We are now building with go1.22, which means that for Windows you need at\n  least Windows 10 or Windows Server 2016. This is because support for earlier\n  versions was removed in Go 1.21. See https://go.dev/doc/go1.21#windows (#981)\n\n- Removed vagrant example, as it was unmaintained. (#1129)\n\n- Removed Fedora and Arch nebula.service files, as they are maintained in the\n  upstream repos. (#1128, #1132)\n\n- Remove the TCP round trip tracking metrics, as they never had correct data\n  and were an experiment to begin with. (#1114)\n\n### Fixed\n\n- Fixed a potential deadlock introduced in 1.8.1. (#1112)\n\n- Fixed support for Linux when IPv6 has been disabled at the OS level. (#787)\n\n- DNS will return NXDOMAIN now when there are no results. (#845)\n\n- Allow `::` in `lighthouse.dns.host`. (#1115)\n\n- Capitalization of `NotAfter` fixed in DNS TXT response. (#1127)\n\n- Don't log invalid certificates. It is untrusted data and can cause a large\n  volume of logs. (#1116)\n\n## [1.8.2] - 2024-01-08\n\n### Fixed\n\n- Fix multiple routines when listen.port is zero. This was a regression\n  introduced in v1.6.0. (#1057)\n\n### Changed\n\n- Small dependency update for Noise. (#1038)\n\n## [1.8.1] - 2023-12-19\n\n### Security\n\n- Update `golang.org/x/crypto`, which includes a fix for CVE-2023-48795. (#1048)\n\n### Fixed\n\n- Fix a deadlock introduced in v1.8.0 that could occur during handshakes. (#1044)\n\n- Fix mobile builds. (#1035)\n\n## [1.8.0] - 2023-12-06\n\n### Deprecated\n\n- The next minor release of Nebula, 1.9.0, will require at least Windows 10 or\n  Windows Server 2016. This is because support for earlier versions was removed\n  in Go 1.21. See https://go.dev/doc/go1.21#windows\n\n### Added\n\n- Linux: Notify systemd of service readiness. This should resolve timing issues\n  with services that depend on Nebula being active. For an example of how to\n  enable this, see: `examples/service_scripts/nebula.service`. (#929)\n\n- Windows: Use Registered IO (RIO) when possible. Testing on a Windows 11\n  machine shows ~50x improvement in throughput. (#905)\n\n- NetBSD, OpenBSD: Added rudimentary support. (#916, #812)\n\n- FreeBSD: Add support for naming tun devices. (#903)\n\n### Changed\n\n- `pki.disconnect_invalid` will now default to true. This means that once a\n  certificate expires, the tunnel will be disconnected. If you use SIGHUP to\n  reload certificates without restarting Nebula, you should ensure all of your\n  clients are on 1.7.0 or newer before you enable this feature. (#859)\n\n- Limit how often a busy tunnel can requery the lighthouse. The new config\n  option `timers.requery_wait_duration` defaults to `60s`. (#940)\n\n- The internal structures for hostmaps were refactored to reduce memory usage\n  and the potential for subtle bugs. (#843, #938, #953, #954, #955)\n\n- Lots of dependency updates.\n\n### Fixed\n\n- Windows: Retry wintun device creation if it fails the first time. (#985)\n\n- Fix issues with firewall reject packets that could cause panics. (#957)\n\n- Fix relay migration during re-handshakes. (#964)\n\n- Various other refactors and fixes. (#935, #952, #972, #961, #996, #1002,\n  #987, #1004, #1030, #1032, ...)\n\n## [1.7.2] - 2023-06-01\n\n### Fixed\n\n- Fix a freeze during config reload if the `static_host_map` config was changed. (#886)\n\n## [1.7.1] - 2023-05-18\n\n### Fixed\n\n- Fix IPv4 addresses returned by `static_host_map` DNS lookup queries being\n  treated as IPv6 addresses. (#877)\n\n## [1.7.0] - 2023-05-17\n\n### Added\n\n- `nebula-cert ca` now supports encrypting the CA's private key with a\n  passphrase. Pass `-encrypt` in order to be prompted for a passphrase.\n  Encryption is performed using AES-256-GCM and Argon2id for KDF. KDF\n  parameters default to RFC recommendations, but can be overridden via CLI\n  flags `-argon-memory`, `-argon-parallelism`, and `-argon-iterations`. (#386)\n\n- Support for curve P256 and BoringCrypto has been added. See README section\n  \"Curve P256 and BoringCrypto\" for more details. (#865, #861, #769, #856, #803)\n\n- New firewall rule `local_cidr`. This could be used to filter destinations\n  when using `unsafe_routes`. (#507)\n\n- Add `unsafe_route` option `install`. This controls whether the route is\n  installed in the systems routing table. (#831)\n\n- Add `tun.use_system_route_table` option. Set to true to manage unsafe routes\n  directly on the system route table with gateway routes instead of in Nebula\n  configuration files. This is only supported on Linux. (#839)\n\n- The metric `certificate.ttl_seconds` is now exposed via stats. (#782)\n\n- Add `punchy.respond_delay` option. This allows you to change the delay\n  before attempting punchy.respond. Default is 5 seconds. (#721)\n\n- Added SSH commands to allow the capture of a mutex profile. (#737)\n\n- You can now set `lighthouse.calculated_remotes` to make it possible to do\n  handshakes without a lighthouse in certain configurations. (#759)\n\n- The firewall can be configured to send REJECT replies instead of the default\n  DROP behavior. (#738)\n\n- For macOS, an example launchd configuration file is now provided. (#762)\n\n### Changed\n\n- Lighthouses and other `static_host_map` entries that use DNS names will now\n  be automatically refreshed to detect when the IP address changes. (#796)\n\n- Lighthouses send ACK replies back to clients so that they do not fall into\n  connection testing as often by clients. (#851, #408)\n\n- Allow the `listen.host` option to contain a hostname. (#825)\n\n- When Nebula switches to a new certificate (such as via SIGHUP), we now\n  rehandshake with all existing tunnels. This allows firewall groups to be\n  updated and `pki.disconnect_invalid` to know about the new certificate\n  expiration time. (#838, #857, #842, #840, #835, #828, #820, #807)\n\n### Fixed\n\n- Always disconnect blocklisted hosts, even if `pki.disconnect_invalid` is\n  not set. (#858)\n\n- Dependencies updated and go1.20 required. (#780, #824, #855, #854)\n\n- Fix possible race condition with relays. (#827)\n\n- FreeBSD: Fix connection to the localhost's own Nebula IP. (#808)\n\n- Normalize and document some common log field values. (#837, #811)\n\n- Fix crash if you set unlucky values for the firewall timeout configuration\n  options. (#802)\n\n- Make DNS queries case insensitive. (#793)\n\n- Update example systemd configurations to want `nss-lookup`. (#791)\n\n- Errors with SSH commands now go to the SSH tunnel instead of stderr. (#757)\n\n- Fix a hang when shutting down Android. (#772)\n\n## [1.6.1] - 2022-09-26\n\n### Fixed\n\n- Refuse to process underlay packets received from overlay IPs. This prevents\n  confusion on hosts that have unsafe routes configured. (#741)\n\n- The ssh `reload` command did not work on Windows, since it relied on sending\n  a SIGHUP signal internally. This has been fixed. (#725)\n\n- A regression in v1.5.2 that broke unsafe routes on Mobile clients has been\n  fixed. (#729)\n\n## [1.6.0] - 2022-06-30\n\n### Added\n\n- Experimental: nebula clients can be configured to act as relays for other nebula clients.\n  Primarily useful when stubborn NATs make a direct tunnel impossible. (#678)\n\n- Configuration option to report manually specified `ip:port`s to lighthouses. (#650)\n\n- Windows arm64 build. (#638)\n\n- `punchy` and most `lighthouse` config options now support hot reloading. (#649)\n\n### Changed\n\n- Build against go 1.18. (#656)\n\n- Promoted `routines` config from experimental to supported feature. (#702)\n\n- Dependencies updated. (#664)\n\n### Fixed\n\n- Packets destined for the same host that sent it will be returned on MacOS.\n  This matches the default behavior of other operating systems. (#501)\n\n- `unsafe_route` configuration will no longer crash on Windows. (#648)\n\n- A few panics that were introduced in 1.5.x. (#657, #658, #675)\n\n### Security\n\n- You can set `listen.send_recv_error` to control the conditions in which\n  `recv_error` messages are sent. Sending these messages can expose the fact\n  that Nebula is running on a host, but it speeds up re-handshaking. (#670)\n\n### Removed\n\n- `x509` config stanza support has been removed. (#685)\n\n## [1.5.2] - 2021-12-14\n\n### Added\n\n- Warn when a non lighthouse node does not have lighthouse hosts configured. (#587)\n\n### Changed\n\n- No longer fatals if expired CA certificates are present in `pki.ca`, as long as 1 valid CA is present. (#599)\n\n- `nebula-cert` will now enforce ipv4 addresses. (#604)\n\n- Warn on macOS if an unsafe route cannot be created due to a collision with an\n  existing route. (#610)\n\n- Warn if you set a route MTU on platforms where we don't support it. (#611)\n\n### Fixed\n\n- Rare race condition when tearing down a tunnel due to `recv_error` and sending packets on another thread. (#590)\n\n- Bug in `routes` and `unsafe_routes` handling that was introduced in 1.5.0. (#595)\n\n- `-test` mode no longer results in a crash. (#602)\n\n### Removed\n\n- `x509.ca` config alias for `pki.ca`. (#604)\n\n### Security\n\n- Upgraded `golang.org/x/crypto` to address an issue which allowed unauthenticated clients to cause a panic in SSH\n  servers. (#603)\n\n## 1.5.1 - 2021-12-13\n\n(This release was skipped due to discovering #610 and #611 after the tag was\ncreated.)\n\n## [1.5.0] - 2021-11-11\n\n### Added\n\n- SSH `print-cert` has a new `-raw` flag to get the PEM representation of a certificate. (#483)\n\n- New build architecture: Linux `riscv64`. (#542)\n\n- New experimental config option `remote_allow_ranges`. (#540)\n\n- New config option `pki.disconnect_invalid` that will tear down tunnels when they become invalid (through expiry or\n  removal of root trust). Default is `false`. Note, this will not currently recognize if a remote has changed\n  certificates since the last handshake. (#370)\n\n- New config option `unsafe_routes.<route>.metric` will set a metric for a specific unsafe route. It's useful if you have\n  more than one identical route and want to prefer one against the other. (#353)\n\n### Changed\n\n- Build against go 1.17. (#553)\n\n- Build with `CGO_ENABLED=0` set, to create more portable binaries. This could\n  have an effect on DNS resolution if you rely on anything non-standard. (#421)\n\n- Windows now uses the [wintun](https://www.wintun.net/) driver which does not require installation. This driver\n  is a large improvement over the TAP driver that was used in previous versions. If you had a previous version\n  of `nebula` running, you will want to disable the tap driver in Control Panel, or uninstall the `tap0901` driver\n  before running this version. (#289)\n\n- Darwin binaries are now universal (works on both amd64 and arm64), signed, and shipped in a notarized zip file.\n  `nebula-darwin.zip` will be the only darwin release artifact. (#571)\n\n- Darwin uses syscalls and AF_ROUTE to configure the routing table, instead of\n  using `/sbin/route`. Setting `tun.dev` is now allowed on Darwin as well, it\n  must be in the format `utun[0-9]+` or it will be ignored. (#163)\n\n### Deprecated\n\n- The `preferred_ranges` option has been supported as a replacement for\n  `local_range` since v1.0.0. It has now been documented and `local_range`\n  has been officially deprecated. (#541)\n\n### Fixed\n\n- Valid recv_error packets were incorrectly marked as \"spoofing\" and ignored. (#482)\n\n- SSH server handles single `exec` requests correctly. (#483)\n\n- Signing a certificate with `nebula-cert sign` now verifies that the supplied\n  ca-key matches the ca-crt. (#503)\n\n- If `preferred_ranges` (or the deprecated `local_range`) is configured, we\n  will immediately switch to a preferred remote address after the reception of\n  a handshake packet (instead of waiting until 1,000 packets have been sent).\n  (#532)\n\n- A race condition when `punchy.respond` is enabled and ensures the correct\n  vpn ip is sent a punch back response in highly queried node. (#566)\n\n- Fix a rare crash during handshake due to a race condition. (#535)\n\n## [1.4.0] - 2021-05-11\n\n### Added\n\n- Ability to output qr code images in `print`, `ca`, and `sign` modes for `nebula-cert`.\n  This is useful when configuring mobile clients. (#297)\n\n- Experimental: Nebula can now do work on more than 2 cpu cores in send and receive paths via\n  the new `routines` config option. (#382, #391, #395)\n\n- ICMP ping requests can be responded to when the `tun.disabled` is `true`.\n  This is useful so that you can \"ping\" a lighthouse running in this mode. (#342)\n\n- Run smoke tests via `make smoke-docker`. (#287)\n\n- More reported stats, udp memory use on linux, build version (when using Prometheus), firewall,\n  handshake, and cached packet stats. (#390, #405, #450, #453)\n\n- IPv6 support for the underlay network. (#369)\n\n- End to end testing, run with `make e2e`. (#425, #427, #428)\n\n### Changed\n\n- Darwin will now log stdout/stderr to a file when using `-service` mode. (#303)\n\n- Example systemd unit file now better arranged startup order when using `sshd`\n  and other fixes. (#317, #412, #438)\n\n- Reduced memory utilization/garbage collection. (#320, #323, #340)\n\n- Reduced CPU utilization. (#329)\n\n- Build against go 1.16. (#381)\n\n- Refactored handshakes to improve performance and correctness. (#401, #402, #404, #416, #451)\n\n- Improved roaming support for mobile clients. (#394, #457)\n\n- Lighthouse performance and correctness improvements. (#406, #418, #429, #433, #437, #442, #449)\n\n- Better ordered startup to enable `sshd`, `stats`, and `dns` subsystems to listen on\n  the nebula interface. (#375)\n\n### Fixed\n\n- No longer report handshake packets as `lost` in stats. (#331)\n\n- Error handling in the `cert` package. (#339, #373)\n\n- Orphaned pending hostmap entries are cleaned up. (#344)\n\n- Most known data races are now resolved. (#396, #400, #424)\n\n- Refuse to run a lighthouse on an ephemeral port. (#399)\n\n- Removed the global references. (#423, #426, #446)\n\n- Reloading via ssh command avoids a panic. (#447)\n\n- Shutdown is now performed in a cleaner way. (#448)\n\n- Logs will now find their way to Windows event viewer when running under `-service` mode\n  in Windows. (#443)\n\n## [1.3.0] - 2020-09-22\n\n### Added\n\n- You can emit statistics about non-message packets by setting the option\n  `stats.message_metrics`. You can similarly emit detailed statistics about\n  lighthouse packets by setting the option `stats.lighthouse_metrics`. See\n  the example config for more details. (#230)\n\n- We now support freebsd/amd64. This is experimental, please give us feedback.\n  (#103)\n\n- We now release a binary for `linux/mips-softfloat` which has also been\n  stripped to reduce filesize and hopefully have a better chance on running on\n  small mips devices. (#231)\n\n- You can set `tun.disabled` to true to run a standalone lighthouse without a\n  tun device (and thus, without root). (#269)\n\n- You can set `logging.disable_timestamp` to remove timestamps from log lines,\n  which is useful when output is redirected to a logging system that already\n  adds timestamps. (#288)\n\n### Changed\n\n- Handshakes should now trigger faster, as we try to be proactive with sending\n  them instead of waiting for the next timer tick in most cases. (#246, #265)\n\n- Previously, we would drop the conntrack table whenever firewall rules were\n  changed during a SIGHUP. Now, we will maintain the table and just validate\n  that an entry still matches with the new rule set. (#233)\n\n- Debug logs for firewall drops now include the reason. (#220, #239)\n\n- Logs for handshakes now include the fingerprint of the remote host. (#262)\n\n- Config item `pki.blacklist` is now `pki.blocklist`. (#272)\n\n- Better support for older Linux kernels. We now only set `SO_REUSEPORT` if\n  `tun.routines` is greater than 1 (default is 1). We also only use the\n  `recvmmsg` syscall if `listen.batch` is greater than 1 (default is 64).\n  (#275)\n\n- It is possible to run Nebula as a library inside of another process now.\n  Note that this is still experimental and the internal APIs around this might\n  change in minor version releases. (#279)\n\n### Deprecated\n\n- `pki.blacklist` is deprecated in favor of `pki.blocklist` with the same\n   functionality. Existing configs will continue to load for this release to\n   allow for migrations. (#272)\n\n### Fixed\n\n- `advmss` is now set correctly for each route table entry when `tun.routes`\n  is configured to have some routes with higher MTU. (#245)\n\n- Packets that arrive on the tun device with an unroutable destination IP are\n  now dropped correctly, instead of wasting time making queries to the\n  lighthouses for IP `0.0.0.0` (#267)\n\n## [1.2.0] - 2020-04-08\n\n### Added\n\n- Add `logging.timestamp_format` config option. The primary purpose of this\n  change is to allow logging timestamps with millisecond precision. (#187)\n\n- Support `unsafe_routes` on Windows. (#184)\n\n- Add `lighthouse.remote_allow_list` to filter which subnets we will use to\n  handshake with other hosts. See the example config for more details. (#217)\n\n- Add `lighthouse.local_allow_list` to filter which local IP addresses and/or\n  interfaces we advertise to the lighthouses. See the example config for more\n  details. (#217)\n\n- Wireshark dissector plugin. Add this file in `dist/wireshark` to your\n  Wireshark plugins folder to see Nebula packet headers decoded. (#216)\n\n- systemd unit for Arch, so it can be built entirely from this repo. (#216)\n\n### Changed\n\n- Added a delay to punching via lighthouse signal to deal with race conditions\n  in some linux conntrack implementations. (#210)\n\n  See deprecated, this also adds a new `punchy.delay` option that defaults to `1s`.\n\n- Validate all `lighthouse.hosts` and `static_host_map` VPN IPs are in the\n  subnet defined in our cert. Exit with a fatal error if they are not in our\n  subnet, as this is an invalid configuration (we will not have the proper\n  routes set up to communicate with these hosts). (#170)\n\n- Use absolute paths to system binaries on macOS and Windows. (#191)\n\n- Add configuration options for `handshakes`. This includes options to tweak\n  `try_interval`, `retries` and `wait_rotation`. See example config for\n  descriptions. (#179)\n\n- Allow `-config` file to not end in `.yaml` or `yml`. Useful when using\n  `-test` and automated tools like Ansible that create temporary files without\n  suffixes. (#189)\n\n- The config test mode, `-test`, is now more thorough and catches more parsing\n  issues. (#177)\n\n- Various documentation and example fixes. (#196)\n\n- Improved log messages. (#181, #200)\n\n- Dependencies updated. (#188)\n\n### Deprecated\n\n- `punchy`, `punch_back` configuration options have been collapsed under the\n  now top level `punchy` config directive. (#210)\n\n  `punchy.punch` - This is the old `punchy` option. Should we perform NAT hole\n  punching (default false)?\n\n  `punchy.respond` - This is the old `punch_back` option. Should we respond to\n  hole punching by hole punching back (default false)?\n\n### Fixed\n\n- Reduce memory allocations when not using `unsafe_routes`. (#198)\n\n- Ignore packets from self to self. (#192)\n\n- MTU fixed for `unsafe_routes`. (#209)\n\n## [1.1.0] - 2020-01-17\n\n### Added\n\n- For macOS and Windows, build a special version of the binary that can install\n  and manage its own service configuration. You can use this with `nebula\n  -service`.  If you are building from source, use `make service` to build this feature.\n- Support for `mips`, `mips64`, `386` and `ppc64le` processors on Linux.\n- You can now configure the DNS listen host and port with `lighthouse.dns.host`\n  and `lighthouse.dns.port`.\n- Subnet and routing support. You can now add a `unsafe_routes` section to your\n  config to allow hosts to act as gateways to other subnets. Read the example\n  config for more details. This is supported on Linux and macOS.\n\n### Changed\n\n- Certificates now have more verifications performed, including making sure\n  the certificate lifespan does not exceed the lifespan of the root CA. This\n  could cause issues if you have signed certificates with expirations beyond\n  the expiration of your CA, and you will need to reissue your certificates.\n- If lighthouse interval is set to `0`, never update the lighthouse (mobile\n  optimization).\n- Various documentation and example fixes.\n- Improved error messages.\n- Dependencies updated.\n\n### Fixed\n\n- If you have a firewall rule with `group: [\"one-group\"]`, this will\n  now be accepted, with a warning to use `group: \"one-group\"` instead.\n- The `listen.host` configuration option was previously ignored (the bind host\n  was always 0.0.0.0). This option will now be honored.\n- The `ca_sha` and `ca_name` firewall rule options should now work correctly.\n\n## [1.0.0] - 2019-11-19\n\n### Added\n\n- Initial public release.\n\n[Unreleased]: https://github.com/slackhq/nebula/compare/v1.10.3...HEAD\n[1.10.3]: https://github.com/slackhq/nebula/releases/tag/v1.10.3\n[1.10.2]: https://github.com/slackhq/nebula/releases/tag/v1.10.2\n[1.10.1]: https://github.com/slackhq/nebula/releases/tag/v1.10.1\n[1.10.0]: https://github.com/slackhq/nebula/releases/tag/v1.10.0\n[1.9.7]: https://github.com/slackhq/nebula/releases/tag/v1.9.7\n[1.9.6]: https://github.com/slackhq/nebula/releases/tag/v1.9.6\n[1.9.5]: https://github.com/slackhq/nebula/releases/tag/v1.9.5\n[1.9.4]: https://github.com/slackhq/nebula/releases/tag/v1.9.4\n[1.9.3]: https://github.com/slackhq/nebula/releases/tag/v1.9.3\n[1.9.2]: https://github.com/slackhq/nebula/releases/tag/v1.9.2\n[1.9.1]: https://github.com/slackhq/nebula/releases/tag/v1.9.1\n[1.9.0]: https://github.com/slackhq/nebula/releases/tag/v1.9.0\n[1.8.2]: https://github.com/slackhq/nebula/releases/tag/v1.8.2\n[1.8.1]: https://github.com/slackhq/nebula/releases/tag/v1.8.1\n[1.8.0]: https://github.com/slackhq/nebula/releases/tag/v1.8.0\n[1.7.2]: https://github.com/slackhq/nebula/releases/tag/v1.7.2\n[1.7.1]: https://github.com/slackhq/nebula/releases/tag/v1.7.1\n[1.7.0]: https://github.com/slackhq/nebula/releases/tag/v1.7.0\n[1.6.1]: https://github.com/slackhq/nebula/releases/tag/v1.6.1\n[1.6.0]: https://github.com/slackhq/nebula/releases/tag/v1.6.0\n[1.5.2]: https://github.com/slackhq/nebula/releases/tag/v1.5.2\n[1.5.0]: https://github.com/slackhq/nebula/releases/tag/v1.5.0\n[1.4.0]: https://github.com/slackhq/nebula/releases/tag/v1.4.0\n[1.3.0]: https://github.com/slackhq/nebula/releases/tag/v1.3.0\n[1.2.0]: https://github.com/slackhq/nebula/releases/tag/v1.2.0\n[1.1.0]: https://github.com/slackhq/nebula/releases/tag/v1.1.0\n[1.0.0]: https://github.com/slackhq/nebula/releases/tag/v1.0.0\n"
  },
  {
    "path": "CODEOWNERS",
    "content": "#ECCN:Open Source\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018-2019 Slack Technologies, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n\n"
  },
  {
    "path": "LOGGING.md",
    "content": "### Logging conventions\n\nA log message (the string/format passed to `Info`, `Error`, `Debug` etc, as well as their `Sprintf` counterparts) should\nbe a descriptive message about the event and may contain specific identifying characteristics. Regardless of the\nlevel of detail in the message identifying characteristics should always be included via `WithField`, `WithFields` or\n`WithError`\n\nIf an error is being logged use `l.WithError(err)` so that there is better discoverability about the event as well\nas the specific error condition.\n\n#### Common fields\n\n- `cert` - a `cert.NebulaCertificate` object, do not `.String()` this manually, `logrus` will marshal objects properly\n  for the formatter it is using.\n- `fingerprint` - a single `NebeulaCertificate` hex encoded fingerprint\n- `fingerprints` - an array of `NebulaCertificate` hex encoded fingerprints\n- `fwPacket` - a FirewallPacket object\n- `handshake` - an object containing:\n    - `stage` - the current stage counter\n    - `style` - noise handshake style `ix_psk0`, `xx`, etc\n- `header` - a nebula header object\n- `udpAddr` - a `net.UDPAddr` object\n- `udpIp` - a udp ip address\n- `vpnIp` - vpn ip of the host (remote or local)\n- `relay` - the vpnIp of the relay host that is or should be handling the relay packet\n- `relayFrom` - The vpnIp of the initial sender of the relayed packet \n- `relayTo` - The vpnIp of the final destination of a relayed packet\n\n#### Example:\n\n```\nl.WithError(err).\n    WithField(\"vpnIp\", IntIp(hostinfo.hostId)).\n    WithField(\"udpAddr\", addr).\n    WithField(\"handshake\", m{\"stage\": 1, \"style\": \"ix\"}).\n    Info(\"Invalid certificate from host\")\n```"
  },
  {
    "path": "Makefile",
    "content": "NEBULA_CMD_PATH = \"./cmd/nebula\"\nCGO_ENABLED = 0\nexport CGO_ENABLED\n\n# Set up OS specific bits\nifeq ($(OS),Windows_NT)\n\tNEBULA_CMD_SUFFIX = .exe\n\tNULL_FILE = nul\n\t# RIO on windows does pointer stuff that makes go vet angry\n\tVET_FLAGS = -unsafeptr=false\nelse\n\tNEBULA_CMD_SUFFIX =\n\tNULL_FILE = /dev/null\nendif\n\n# Only defined the build number if we haven't already\nifndef BUILD_NUMBER\n\tifeq ($(shell git describe --exact-match 2>$(NULL_FILE)),)\n\t\tBUILD_NUMBER = $(shell git describe --abbrev=0 --match \"v*\" | cut -dv -f2)-$(shell git branch --show-current)-$(shell git describe --long --dirty | cut -d- -f2-)\n\telse\n\t\tBUILD_NUMBER = $(shell git describe --exact-match --dirty | cut -dv -f2)\n\tendif\nendif\n\nDOCKER_IMAGE_REPO ?= nebulaoss/nebula\nDOCKER_IMAGE_TAG ?= latest\n\nLDFLAGS = -X main.Build=$(BUILD_NUMBER)\n\nALL_LINUX = linux-amd64 \\\n\tlinux-386 \\\n\tlinux-ppc64le \\\n\tlinux-arm-5 \\\n\tlinux-arm-6 \\\n\tlinux-arm-7 \\\n\tlinux-arm64 \\\n\tlinux-mips \\\n\tlinux-mipsle \\\n\tlinux-mips64 \\\n\tlinux-mips64le \\\n\tlinux-mips-softfloat \\\n\tlinux-riscv64 \\\n\tlinux-loong64\n\nALL_FREEBSD = freebsd-amd64 \\\n\tfreebsd-arm64\n\nALL_OPENBSD = openbsd-amd64 \\\n\topenbsd-arm64\n\nALL_NETBSD = netbsd-amd64 \\\n \tnetbsd-arm64\n\nALL = $(ALL_LINUX) \\\n\t$(ALL_FREEBSD) \\\n\t$(ALL_OPENBSD) \\\n\t$(ALL_NETBSD) \\\n\tdarwin-amd64 \\\n\tdarwin-arm64 \\\n\twindows-amd64 \\\n\twindows-arm64\n\ne2e:\n\t$(TEST_ENV) go test -tags=e2e_testing -count=1 $(TEST_FLAGS) ./e2e\n\ne2ev: TEST_FLAGS += -v\ne2ev: e2e\n\ne2evv: TEST_ENV += TEST_LOGS=1\ne2evv: e2ev\n\ne2evvv: TEST_ENV += TEST_LOGS=2\ne2evvv: e2ev\n\ne2evvvv: TEST_ENV += TEST_LOGS=3\ne2evvvv: e2ev\n\ne2e-bench: TEST_FLAGS = -bench=. -benchmem -run=^$\ne2e-bench: e2e\n\nDOCKER_BIN = build/linux-amd64/nebula build/linux-amd64/nebula-cert\n\nall: $(ALL:%=build/%/nebula) $(ALL:%=build/%/nebula-cert)\n\ndocker: docker/linux-$(shell go env GOARCH)\n\nrelease: $(ALL:%=build/nebula-%.tar.gz)\n\nrelease-linux: $(ALL_LINUX:%=build/nebula-%.tar.gz)\n\nrelease-freebsd: $(ALL_FREEBSD:%=build/nebula-%.tar.gz)\n\nrelease-openbsd: $(ALL_OPENBSD:%=build/nebula-%.tar.gz)\n\nrelease-netbsd: $(ALL_NETBSD:%=build/nebula-%.tar.gz)\n\nrelease-boringcrypto: build/nebula-linux-$(shell go env GOARCH)-boringcrypto.tar.gz\n\nBUILD_ARGS += -trimpath\n\nbin-windows: build/windows-amd64/nebula.exe build/windows-amd64/nebula-cert.exe\n\tmv $? .\n\nbin-windows-arm64: build/windows-arm64/nebula.exe build/windows-arm64/nebula-cert.exe\n\tmv $? .\n\nbin-darwin: build/darwin-amd64/nebula build/darwin-amd64/nebula-cert\n\tmv $? .\n\nbin-freebsd: build/freebsd-amd64/nebula build/freebsd-amd64/nebula-cert\n\tmv $? .\n\nbin-freebsd-arm64: build/freebsd-arm64/nebula build/freebsd-arm64/nebula-cert\n\tmv $? .\n\nbin-boringcrypto: build/linux-$(shell go env GOARCH)-boringcrypto/nebula build/linux-$(shell go env GOARCH)-boringcrypto/nebula-cert\n\tmv $? .\n\nbin-pkcs11: BUILD_ARGS += -tags pkcs11\nbin-pkcs11: CGO_ENABLED = 1\nbin-pkcs11: bin\n\nbin:\n\tgo build $(BUILD_ARGS) -ldflags \"$(LDFLAGS)\" -o ./nebula${NEBULA_CMD_SUFFIX} ${NEBULA_CMD_PATH}\n\tgo build $(BUILD_ARGS) -ldflags \"$(LDFLAGS)\" -o ./nebula-cert${NEBULA_CMD_SUFFIX} ./cmd/nebula-cert\n\ninstall:\n\tgo install $(BUILD_ARGS) -ldflags \"$(LDFLAGS)\" ${NEBULA_CMD_PATH}\n\tgo install $(BUILD_ARGS) -ldflags \"$(LDFLAGS)\" ./cmd/nebula-cert\n\nbuild/linux-arm-%: GOENV += GOARM=$(word 3, $(subst -, ,$*))\nbuild/linux-mips-%: GOENV += GOMIPS=$(word 3, $(subst -, ,$*))\n\n# Build an extra small binary for mips-softfloat\nbuild/linux-mips-softfloat/%: LDFLAGS += -s -w\n\n# boringcrypto\nbuild/linux-amd64-boringcrypto/%: GOENV += GOEXPERIMENT=boringcrypto CGO_ENABLED=1\nbuild/linux-arm64-boringcrypto/%: GOENV += GOEXPERIMENT=boringcrypto CGO_ENABLED=1\nbuild/linux-amd64-boringcrypto/%: LDFLAGS += -checklinkname=0\nbuild/linux-arm64-boringcrypto/%: LDFLAGS += -checklinkname=0\n\nbuild/%/nebula: .FORCE\n\tGOOS=$(firstword $(subst -, , $*)) \\\n\t\tGOARCH=$(word 2, $(subst -, ,$*)) $(GOENV) \\\n\t\tgo build $(BUILD_ARGS) -o $@ -ldflags \"$(LDFLAGS)\" ${NEBULA_CMD_PATH}\n\nbuild/%/nebula-cert: .FORCE\n\tGOOS=$(firstword $(subst -, , $*)) \\\n\t\tGOARCH=$(word 2, $(subst -, ,$*)) $(GOENV) \\\n\t\tgo build $(BUILD_ARGS) -o $@ -ldflags \"$(LDFLAGS)\" ./cmd/nebula-cert\n\nbuild/%/nebula.exe: build/%/nebula\n\tmv $< $@\n\nbuild/%/nebula-cert.exe: build/%/nebula-cert\n\tmv $< $@\n\nbuild/nebula-%.tar.gz: build/%/nebula build/%/nebula-cert\n\ttar -zcv -C build/$* -f $@ nebula nebula-cert\n\nbuild/nebula-%.zip: build/%/nebula.exe build/%/nebula-cert.exe\n\tcd build/$* && zip ../nebula-$*.zip nebula.exe nebula-cert.exe\n\ndocker/%: build/%/nebula build/%/nebula-cert\n\tdocker build . $(DOCKER_BUILD_ARGS) -f docker/Dockerfile --platform \"$(subst -,/,$*)\" --tag \"${DOCKER_IMAGE_REPO}:${DOCKER_IMAGE_TAG}\" --tag \"${DOCKER_IMAGE_REPO}:$(BUILD_NUMBER)\"\n\nvet:\n\tgo vet $(VET_FLAGS) -v ./...\n\ntest:\n\tgo test -v ./...\n\ntest-boringcrypto:\n\tGOEXPERIMENT=boringcrypto CGO_ENABLED=1 go test -ldflags \"-checklinkname=0\" -v ./...\n\ntest-pkcs11:\n\tCGO_ENABLED=1 go test -v -tags pkcs11 ./...\n\ntest-cov-html:\n\tgo test -coverprofile=coverage.out\n\tgo tool cover -html=coverage.out\n\nbuild-test-mobile:\n\tGOARCH=amd64 GOOS=ios go build $(shell go list ./... | grep -v '/cmd/\\|/examples/')\n\tGOARCH=arm64 GOOS=ios go build $(shell go list ./... | grep -v '/cmd/\\|/examples/')\n\tGOARCH=amd64 GOOS=android go build $(shell go list ./... | grep -v '/cmd/\\|/examples/')\n\tGOARCH=arm64 GOOS=android go build $(shell go list ./... | grep -v '/cmd/\\|/examples/')\n\nbench:\n\tgo test -bench=.\n\nbench-cpu:\n\tgo test -bench=. -benchtime=5s -cpuprofile=cpu.pprof\n\tgo tool pprof go-audit.test cpu.pprof\n\nbench-cpu-long:\n\tgo test -bench=. -benchtime=60s -cpuprofile=cpu.pprof\n\tgo tool pprof go-audit.test cpu.pprof\n\nproto: nebula.pb.go cert/cert_v1.pb.go\n\nnebula.pb.go: nebula.proto .FORCE\n\tgo build github.com/gogo/protobuf/protoc-gen-gogofaster\n\tPATH=\"$(CURDIR):$(PATH)\" protoc --gogofaster_out=paths=source_relative:. $<\n\trm protoc-gen-gogofaster\n\ncert/cert.pb.go: cert/cert.proto .FORCE\n\t$(MAKE) -C cert cert.pb.go\n\nservice:\n\t@echo > $(NULL_FILE)\n\t$(eval NEBULA_CMD_PATH := \"./cmd/nebula-service\")\nifeq ($(words $(MAKECMDGOALS)),1)\n\t@$(MAKE) service ${.DEFAULT_GOAL} --no-print-directory\nendif\n\nbin-docker: bin build/linux-amd64/nebula build/linux-amd64/nebula-cert\n\nsmoke-docker: bin-docker\n\tcd .github/workflows/smoke/ && ./build.sh\n\tcd .github/workflows/smoke/ && ./smoke.sh\n\tcd .github/workflows/smoke/ && NAME=\"smoke-p256\" CURVE=\"P256\" ./build.sh\n\tcd .github/workflows/smoke/ && NAME=\"smoke-p256\" ./smoke.sh\n\nsmoke-relay-docker: bin-docker\n\tcd .github/workflows/smoke/ && ./build-relay.sh\n\tcd .github/workflows/smoke/ && ./smoke-relay.sh\n\nsmoke-docker-race: BUILD_ARGS = -race\nsmoke-docker-race: CGO_ENABLED = 1\nsmoke-docker-race: smoke-docker\n\nsmoke-vagrant/%: bin-docker build/%/nebula\n\tcd .github/workflows/smoke/ && ./build.sh $*\n\tcd .github/workflows/smoke/ && ./smoke-vagrant.sh $*\n\n.FORCE:\n.PHONY: bench bench-cpu bench-cpu-long bin build-test-mobile e2e e2ev e2evv e2evvv e2evvvv proto release service smoke-docker smoke-docker-race test test-cov-html smoke-vagrant/%\n.DEFAULT_GOAL := bin\n"
  },
  {
    "path": "README.md",
    "content": "## What is Nebula?\nNebula is a scalable overlay networking tool with a focus on performance, simplicity and security.\nIt lets you seamlessly connect computers anywhere in the world. Nebula is portable, and runs on Linux, OSX, Windows, iOS, and Android.\nIt can be used to connect a small number of computers, but is also able to connect tens of thousands of computers.\n\nNebula incorporates a number of existing concepts like encryption, security groups, certificates,\nand tunneling.\nWhat makes Nebula different to existing offerings is that it brings all of these ideas together,\nresulting in a sum that is greater than its individual parts.\n\nFurther documentation can be found [here](https://nebula.defined.net/docs/).\n\nYou can read more about Nebula [here](https://medium.com/p/884110a5579).\n\nYou can also join the NebulaOSS Slack group [here](https://join.slack.com/t/nebulaoss/shared_invite/zt-39pk4xopc-CUKlGcb5Z39dQ0cK1v7ehA).\n\n## Supported Platforms\n\n#### Desktop and Server\n\nCheck the [releases](https://github.com/slackhq/nebula/releases/latest) page for downloads or see the [Distribution Packages](https://github.com/slackhq/nebula#distribution-packages) section.\n\n- Linux - 64 and 32 bit, arm, and others\n- Windows\n- MacOS\n- Freebsd\n\n#### Distribution Packages\n\n- [Arch Linux](https://archlinux.org/packages/extra/x86_64/nebula/)\n    ```sh\n    sudo pacman -S nebula\n    ```\n\n- [Fedora Linux](https://src.fedoraproject.org/rpms/nebula)\n    ```sh\n    sudo dnf install nebula\n    ```\n\n- [Debian Linux](https://packages.debian.org/source/stable/nebula)\n    ```sh\n    sudo apt install nebula\n    ```\n\n- [Alpine Linux](https://pkgs.alpinelinux.org/packages?name=nebula)\n    ```sh\n    sudo apk add nebula\n    ```\n\n- [macOS Homebrew](https://github.com/Homebrew/homebrew-core/blob/HEAD/Formula/n/nebula.rb)\n    ```sh\n    brew install nebula\n    ```\n\n- [Docker](https://hub.docker.com/r/nebulaoss/nebula)\n    ```sh\n    docker pull nebulaoss/nebula\n    ```\n\n#### Mobile\n\n- [iOS](https://apps.apple.com/us/app/mobile-nebula/id1509587936?itsct=apps_box&amp;itscg=30200)\n- [Android](https://play.google.com/store/apps/details?id=net.defined.mobile_nebula&pcampaignid=pcampaignidMKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1)\n\n## Technical Overview\n\nNebula is a mutually authenticated peer-to-peer software-defined network based on the [Noise Protocol Framework](https://noiseprotocol.org/).\nNebula uses certificates to assert a node's IP address, name, and membership within user-defined groups.\nNebula's user-defined groups allow for provider agnostic traffic filtering between nodes.\nDiscovery nodes (aka lighthouses) allow individual peers to find each other and optionally use UDP hole punching to establish connections from behind most firewalls or NATs.\nUsers can move data between nodes in any number of cloud service providers, datacenters, and endpoints, without needing to maintain a particular addressing scheme.\n\nNebula uses Elliptic-curve Diffie-Hellman (`ECDH`) key exchange and `AES-256-GCM` in its default configuration.\n\nNebula was created to provide a mechanism for groups of hosts to communicate securely, even across the internet, while enabling expressive firewall definitions similar in style to cloud security groups.\n\n## Getting started (quickly)\n\nTo set up a Nebula network, you'll need:\n\n#### 1. The [Nebula binaries](https://github.com/slackhq/nebula/releases) or [Distribution Packages](https://github.com/slackhq/nebula#distribution-packages) for your specific platform. Specifically you'll need `nebula-cert` and the specific nebula binary for each platform you use.\n\n#### 2. (Optional, but you really should..) At least one discovery node with a routable IP address, which we call a lighthouse.\n\nNebula lighthouses allow nodes to find each other, anywhere in the world. A lighthouse is the only node in a Nebula network whose IP should not change. Running a lighthouse requires very few compute resources, and you can easily use the least expensive option from a cloud hosting provider. If you're not sure which provider to use, a number of us have used $6/mo [DigitalOcean](https://digitalocean.com) droplets as lighthouses.\n\nOnce you have launched an instance, ensure that Nebula udp traffic (default port udp/4242) can reach it over the internet.\n\n#### 3. A Nebula certificate authority, which will be the root of trust for a particular Nebula network.\n\n```sh\n./nebula-cert ca -name \"Myorganization, Inc\"\n```\n\nThis will create files named `ca.key` and `ca.cert` in the current directory. The `ca.key` file is the most sensitive file you'll create, because it is the key used to sign the certificates for individual nebula nodes/hosts. Please store this file somewhere safe, preferably with strong encryption.\n\n**Be aware!** By default, certificate authorities have a 1-year lifetime before expiration. See [this guide](https://nebula.defined.net/docs/guides/rotating-certificate-authority/) for details on rotating a CA.\n\n#### 4. Nebula host keys and certificates generated from that certificate authority\n\nThis assumes you have four nodes, named lighthouse1, laptop, server1, host3. You can name the nodes any way you'd like, including FQDN. You'll also need to choose IP addresses and the associated subnet. In this example, we are creating a nebula network that will use 192.168.100.x/24 as its network range. This example also demonstrates nebula groups, which can later be used to define traffic rules in a nebula network.\n```sh\n./nebula-cert sign -name \"lighthouse1\" -ip \"192.168.100.1/24\"\n./nebula-cert sign -name \"laptop\" -ip \"192.168.100.2/24\" -groups \"laptop,home,ssh\"\n./nebula-cert sign -name \"server1\" -ip \"192.168.100.9/24\" -groups \"servers\"\n./nebula-cert sign -name \"host3\" -ip \"192.168.100.10/24\"\n```\n\nBy default, host certificates will expire 1 second before the CA expires. Use the `-duration` flag to specify a shorter lifetime.\n\n#### 5. Configuration files for each host\n\nDownload a copy of the nebula [example configuration](https://github.com/slackhq/nebula/blob/master/examples/config.yml).\n\n* On the lighthouse node, you'll need to ensure `am_lighthouse: true` is set.\n\n* On the individual hosts, ensure the lighthouse is defined properly in the `static_host_map` section, and is added to the lighthouse `hosts` section.\n\n\n#### 6. Copy nebula credentials, configuration, and binaries to each host\n\nFor each host, copy the nebula binary to the host, along with `config.yml` from step 5, and the files `ca.crt`, `{host}.crt`, and `{host}.key` from step 4.\n\n**DO NOT COPY `ca.key` TO INDIVIDUAL NODES.**\n\n#### 7. Run nebula on each host\n\n```sh\n./nebula -config /path/to/config.yml\n```\n\nFor more detailed instructions, [find the full documentation here](https://nebula.defined.net/docs/).\n\n## Building Nebula from source\n\nMake sure you have [go](https://go.dev/doc/install) installed and clone this repo. Change to the nebula directory.\n\nTo build nebula for all platforms:\n`make all`\n\nTo build nebula for a specific platform (ex, Windows):\n`make bin-windows`\n\nSee the [Makefile](Makefile) for more details on build targets\n\n## Curve P256 and BoringCrypto\n\nThe default curve used for cryptographic handshakes and signatures is Curve25519. This is the recommended setting for most users. If your deployment has certain compliance requirements, you have the option of creating your CA using `nebula-cert ca -curve P256` to use NIST Curve P256. The CA will then sign certificates using ECDSA P256, and any hosts using these certificates will use P256 for ECDH handshakes.\n\nIn addition, Nebula can be built using the [BoringCrypto GOEXPERIMENT](https://github.com/golang/go/blob/go1.20/src/crypto/internal/boring/README.md) by running either of the following make targets:\n\n```sh\nmake bin-boringcrypto\nmake release-boringcrypto\n```\n\nThis is not the recommended default deployment, but may be useful based on your compliance requirements.\n\n## Credits\n\nNebula was created at Slack Technologies, Inc by Nate Brown and Ryan Huber, with contributions from Oliver Fross, Alan Lam, Wade Simmons, and Lining Wang.\n\n"
  },
  {
    "path": "SECURITY.md",
    "content": "Security Policy\n===============\n\nReporting a Vulnerability\n-------------------------\n\nIf you believe you have found a security vulnerability with Nebula, please let\nus know right away. We will investigate all reports and do our best to quickly\nfix valid issues.\n\nYou can submit your report on [HackerOne](https://hackerone.com/slack) and our\nsecurity team will respond as soon as possible.\n"
  },
  {
    "path": "allow_list.go",
    "content": "package nebula\n\nimport (\n\t\"fmt\"\n\t\"net/netip\"\n\t\"regexp\"\n\n\t\"github.com/gaissmai/bart\"\n\t\"github.com/slackhq/nebula/config\"\n)\n\ntype AllowList struct {\n\t// The values of this cidrTree are `bool`, signifying allow/deny\n\tcidrTree *bart.Table[bool]\n}\n\ntype RemoteAllowList struct {\n\tAllowList *AllowList\n\n\t// Inside Range Specific, keys of this tree are inside CIDRs and values\n\t// are *AllowList\n\tinsideAllowLists *bart.Table[*AllowList]\n}\n\ntype LocalAllowList struct {\n\tAllowList *AllowList\n\n\t// To avoid ambiguity, all rules must be true, or all rules must be false.\n\tnameRules []AllowListNameRule\n}\n\ntype AllowListNameRule struct {\n\tName  *regexp.Regexp\n\tAllow bool\n}\n\nfunc NewLocalAllowListFromConfig(c *config.C, k string) (*LocalAllowList, error) {\n\tvar nameRules []AllowListNameRule\n\thandleKey := func(key string, value any) (bool, error) {\n\t\tif key == \"interfaces\" {\n\t\t\tvar err error\n\t\t\tnameRules, err = getAllowListInterfaces(k, value)\n\t\t\tif err != nil {\n\t\t\t\treturn false, err\n\t\t\t}\n\n\t\t\treturn true, nil\n\t\t}\n\t\treturn false, nil\n\t}\n\n\tal, err := newAllowListFromConfig(c, k, handleKey)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &LocalAllowList{AllowList: al, nameRules: nameRules}, nil\n}\n\nfunc NewRemoteAllowListFromConfig(c *config.C, k, rangesKey string) (*RemoteAllowList, error) {\n\tal, err := newAllowListFromConfig(c, k, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tremoteAllowRanges, err := getRemoteAllowRanges(c, rangesKey)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &RemoteAllowList{AllowList: al, insideAllowLists: remoteAllowRanges}, nil\n}\n\n// If the handleKey func returns true, the rest of the parsing is skipped\n// for this key. This allows parsing of special values like `interfaces`.\nfunc newAllowListFromConfig(c *config.C, k string, handleKey func(key string, value any) (bool, error)) (*AllowList, error) {\n\tr := c.Get(k)\n\tif r == nil {\n\t\treturn nil, nil\n\t}\n\n\treturn newAllowList(k, r, handleKey)\n}\n\n// If the handleKey func returns true, the rest of the parsing is skipped\n// for this key. This allows parsing of special values like `interfaces`.\nfunc newAllowList(k string, raw any, handleKey func(key string, value any) (bool, error)) (*AllowList, error) {\n\trawMap, ok := raw.(map[string]any)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"config `%s` has invalid type: %T\", k, raw)\n\t}\n\n\ttree := new(bart.Table[bool])\n\n\t// Keep track of the rules we have added for both ipv4 and ipv6\n\ttype allowListRules struct {\n\t\tfirstValue     bool\n\t\tallValuesMatch bool\n\t\tdefaultSet     bool\n\t\tallValues      bool\n\t}\n\n\trules4 := allowListRules{firstValue: true, allValuesMatch: true, defaultSet: false}\n\trules6 := allowListRules{firstValue: true, allValuesMatch: true, defaultSet: false}\n\n\tfor rawCIDR, rawValue := range rawMap {\n\t\tif handleKey != nil {\n\t\t\thandled, err := handleKey(rawCIDR, rawValue)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif handled {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tvalue, ok := config.AsBool(rawValue)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"config `%s` has invalid value (type %T): %v\", k, rawValue, rawValue)\n\t\t}\n\n\t\tipNet, err := netip.ParsePrefix(rawCIDR)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"config `%s` has invalid CIDR: %s. %w\", k, rawCIDR, err)\n\t\t}\n\n\t\tipNet = netip.PrefixFrom(ipNet.Addr().Unmap(), ipNet.Bits())\n\n\t\ttree.Insert(ipNet, value)\n\n\t\tmaskBits := ipNet.Bits()\n\n\t\tvar rules *allowListRules\n\t\tif ipNet.Addr().Is4() {\n\t\t\trules = &rules4\n\t\t} else {\n\t\t\trules = &rules6\n\t\t}\n\n\t\tif rules.firstValue {\n\t\t\trules.allValues = value\n\t\t\trules.firstValue = false\n\t\t} else {\n\t\t\tif value != rules.allValues {\n\t\t\t\trules.allValuesMatch = false\n\t\t\t}\n\t\t}\n\n\t\t// Check if this is 0.0.0.0/0 or ::/0\n\t\tif maskBits == 0 {\n\t\t\trules.defaultSet = true\n\t\t}\n\t}\n\n\tif !rules4.defaultSet {\n\t\tif rules4.allValuesMatch {\n\t\t\ttree.Insert(netip.PrefixFrom(netip.IPv4Unspecified(), 0), !rules4.allValues)\n\t\t} else {\n\t\t\treturn nil, fmt.Errorf(\"config `%s` contains both true and false rules, but no default set for 0.0.0.0/0\", k)\n\t\t}\n\t}\n\n\tif !rules6.defaultSet {\n\t\tif rules6.allValuesMatch {\n\t\t\ttree.Insert(netip.PrefixFrom(netip.IPv6Unspecified(), 0), !rules6.allValues)\n\t\t} else {\n\t\t\treturn nil, fmt.Errorf(\"config `%s` contains both true and false rules, but no default set for ::/0\", k)\n\t\t}\n\t}\n\n\treturn &AllowList{cidrTree: tree}, nil\n}\n\nfunc getAllowListInterfaces(k string, v any) ([]AllowListNameRule, error) {\n\tvar nameRules []AllowListNameRule\n\n\trawRules, ok := v.(map[string]any)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"config `%s.interfaces` is invalid (type %T): %v\", k, v, v)\n\t}\n\n\tfirstEntry := true\n\tvar allValues bool\n\tfor name, rawAllow := range rawRules {\n\t\tallow, ok := config.AsBool(rawAllow)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"config `%s.interfaces` has invalid value (type %T): %v\", k, rawAllow, rawAllow)\n\t\t}\n\n\t\tnameRE, err := regexp.Compile(\"^\" + name + \"$\")\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"config `%s.interfaces` has invalid key: %s: %v\", k, name, err)\n\t\t}\n\n\t\tnameRules = append(nameRules, AllowListNameRule{\n\t\t\tName:  nameRE,\n\t\t\tAllow: allow,\n\t\t})\n\n\t\tif firstEntry {\n\t\t\tallValues = allow\n\t\t\tfirstEntry = false\n\t\t} else {\n\t\t\tif allow != allValues {\n\t\t\t\treturn nil, fmt.Errorf(\"config `%s.interfaces` values must all be the same true/false value\", k)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nameRules, nil\n}\n\nfunc getRemoteAllowRanges(c *config.C, k string) (*bart.Table[*AllowList], error) {\n\tvalue := c.Get(k)\n\tif value == nil {\n\t\treturn nil, nil\n\t}\n\n\tremoteAllowRanges := new(bart.Table[*AllowList])\n\n\trawMap, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"config `%s` has invalid type: %T\", k, value)\n\t}\n\tfor rawCIDR, rawValue := range rawMap {\n\t\tallowList, err := newAllowList(fmt.Sprintf(\"%s.%s\", k, rawCIDR), rawValue, nil)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tipNet, err := netip.ParsePrefix(rawCIDR)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"config `%s` has invalid CIDR: %s. %w\", k, rawCIDR, err)\n\t\t}\n\n\t\tremoteAllowRanges.Insert(netip.PrefixFrom(ipNet.Addr().Unmap(), ipNet.Bits()), allowList)\n\t}\n\n\treturn remoteAllowRanges, nil\n}\n\nfunc (al *AllowList) Allow(addr netip.Addr) bool {\n\tif al == nil {\n\t\treturn true\n\t}\n\n\tresult, _ := al.cidrTree.Lookup(addr)\n\treturn result\n}\n\nfunc (al *LocalAllowList) Allow(udpAddr netip.Addr) bool {\n\tif al == nil {\n\t\treturn true\n\t}\n\treturn al.AllowList.Allow(udpAddr)\n}\n\nfunc (al *LocalAllowList) AllowName(name string) bool {\n\tif al == nil || len(al.nameRules) == 0 {\n\t\treturn true\n\t}\n\n\tfor _, rule := range al.nameRules {\n\t\tif rule.Name.MatchString(name) {\n\t\t\treturn rule.Allow\n\t\t}\n\t}\n\n\t// If no rules match, return the default, which is the inverse of the rules\n\treturn !al.nameRules[0].Allow\n}\n\nfunc (al *RemoteAllowList) AllowUnknownVpnAddr(vpnAddr netip.Addr) bool {\n\tif al == nil {\n\t\treturn true\n\t}\n\treturn al.AllowList.Allow(vpnAddr)\n}\n\nfunc (al *RemoteAllowList) Allow(vpnAddr netip.Addr, udpAddr netip.Addr) bool {\n\tif !al.getInsideAllowList(vpnAddr).Allow(udpAddr) {\n\t\treturn false\n\t}\n\treturn al.AllowList.Allow(udpAddr)\n}\n\nfunc (al *RemoteAllowList) AllowAll(vpnAddrs []netip.Addr, udpAddr netip.Addr) bool {\n\tif !al.AllowList.Allow(udpAddr) {\n\t\treturn false\n\t}\n\n\tfor _, vpnAddr := range vpnAddrs {\n\t\tif !al.getInsideAllowList(vpnAddr).Allow(udpAddr) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc (al *RemoteAllowList) getInsideAllowList(vpnAddr netip.Addr) *AllowList {\n\tif al.insideAllowLists != nil {\n\t\tinside, ok := al.insideAllowLists.Lookup(vpnAddr)\n\t\tif ok {\n\t\t\treturn inside\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "allow_list_test.go",
    "content": "package nebula\n\nimport (\n\t\"net/netip\"\n\t\"regexp\"\n\t\"testing\"\n\n\t\"github.com/gaissmai/bart\"\n\t\"github.com/slackhq/nebula/config\"\n\t\"github.com/slackhq/nebula/test\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNewAllowListFromConfig(t *testing.T) {\n\tl := test.NewLogger()\n\tc := config.NewC(l)\n\tc.Settings[\"allowlist\"] = map[string]any{\n\t\t\"192.168.0.0\": true,\n\t}\n\tr, err := newAllowListFromConfig(c, \"allowlist\", nil)\n\trequire.EqualError(t, err, \"config `allowlist` has invalid CIDR: 192.168.0.0. netip.ParsePrefix(\\\"192.168.0.0\\\"): no '/'\")\n\tassert.Nil(t, r)\n\n\tc.Settings[\"allowlist\"] = map[string]any{\n\t\t\"192.168.0.0/16\": \"abc\",\n\t}\n\tr, err = newAllowListFromConfig(c, \"allowlist\", nil)\n\trequire.EqualError(t, err, \"config `allowlist` has invalid value (type string): abc\")\n\n\tc.Settings[\"allowlist\"] = map[string]any{\n\t\t\"192.168.0.0/16\": true,\n\t\t\"10.0.0.0/8\":     false,\n\t}\n\tr, err = newAllowListFromConfig(c, \"allowlist\", nil)\n\trequire.EqualError(t, err, \"config `allowlist` contains both true and false rules, but no default set for 0.0.0.0/0\")\n\n\tc.Settings[\"allowlist\"] = map[string]any{\n\t\t\"0.0.0.0/0\":      true,\n\t\t\"10.0.0.0/8\":     false,\n\t\t\"10.42.42.0/24\":  true,\n\t\t\"fd00::/8\":       true,\n\t\t\"fd00:fd00::/16\": false,\n\t}\n\tr, err = newAllowListFromConfig(c, \"allowlist\", nil)\n\trequire.EqualError(t, err, \"config `allowlist` contains both true and false rules, but no default set for ::/0\")\n\n\tc.Settings[\"allowlist\"] = map[string]any{\n\t\t\"0.0.0.0/0\":     true,\n\t\t\"10.0.0.0/8\":    false,\n\t\t\"10.42.42.0/24\": true,\n\t}\n\tr, err = newAllowListFromConfig(c, \"allowlist\", nil)\n\tif assert.NoError(t, err) {\n\t\tassert.NotNil(t, r)\n\t}\n\n\tc.Settings[\"allowlist\"] = map[string]any{\n\t\t\"0.0.0.0/0\":      true,\n\t\t\"10.0.0.0/8\":     false,\n\t\t\"10.42.42.0/24\":  true,\n\t\t\"::/0\":           false,\n\t\t\"fd00::/8\":       true,\n\t\t\"fd00:fd00::/16\": false,\n\t}\n\tr, err = newAllowListFromConfig(c, \"allowlist\", nil)\n\tif assert.NoError(t, err) {\n\t\tassert.NotNil(t, r)\n\t}\n\n\t// Test interface names\n\n\tc.Settings[\"allowlist\"] = map[string]any{\n\t\t\"interfaces\": map[string]any{\n\t\t\t`docker.*`: \"foo\",\n\t\t},\n\t}\n\tlr, err := NewLocalAllowListFromConfig(c, \"allowlist\")\n\trequire.EqualError(t, err, \"config `allowlist.interfaces` has invalid value (type string): foo\")\n\n\tc.Settings[\"allowlist\"] = map[string]any{\n\t\t\"interfaces\": map[string]any{\n\t\t\t`docker.*`: false,\n\t\t\t`eth.*`:    true,\n\t\t},\n\t}\n\tlr, err = NewLocalAllowListFromConfig(c, \"allowlist\")\n\trequire.EqualError(t, err, \"config `allowlist.interfaces` values must all be the same true/false value\")\n\n\tc.Settings[\"allowlist\"] = map[string]any{\n\t\t\"interfaces\": map[string]any{\n\t\t\t`docker.*`: false,\n\t\t},\n\t}\n\tlr, err = NewLocalAllowListFromConfig(c, \"allowlist\")\n\tif assert.NoError(t, err) {\n\t\tassert.NotNil(t, lr)\n\t}\n}\n\nfunc TestAllowList_Allow(t *testing.T) {\n\tassert.True(t, ((*AllowList)(nil)).Allow(netip.MustParseAddr(\"1.1.1.1\")))\n\n\ttree := new(bart.Table[bool])\n\ttree.Insert(netip.MustParsePrefix(\"0.0.0.0/0\"), true)\n\ttree.Insert(netip.MustParsePrefix(\"10.0.0.0/8\"), false)\n\ttree.Insert(netip.MustParsePrefix(\"10.42.42.42/32\"), true)\n\ttree.Insert(netip.MustParsePrefix(\"10.42.0.0/16\"), true)\n\ttree.Insert(netip.MustParsePrefix(\"10.42.42.0/24\"), true)\n\ttree.Insert(netip.MustParsePrefix(\"10.42.42.0/24\"), false)\n\ttree.Insert(netip.MustParsePrefix(\"::1/128\"), true)\n\ttree.Insert(netip.MustParsePrefix(\"::2/128\"), false)\n\tal := &AllowList{cidrTree: tree}\n\n\tassert.True(t, al.Allow(netip.MustParseAddr(\"1.1.1.1\")))\n\tassert.False(t, al.Allow(netip.MustParseAddr(\"10.0.0.4\")))\n\tassert.True(t, al.Allow(netip.MustParseAddr(\"10.42.42.42\")))\n\tassert.False(t, al.Allow(netip.MustParseAddr(\"10.42.42.41\")))\n\tassert.True(t, al.Allow(netip.MustParseAddr(\"10.42.0.1\")))\n\tassert.True(t, al.Allow(netip.MustParseAddr(\"::1\")))\n\tassert.False(t, al.Allow(netip.MustParseAddr(\"::2\")))\n}\n\nfunc TestLocalAllowList_AllowName(t *testing.T) {\n\tassert.True(t, ((*LocalAllowList)(nil)).AllowName(\"docker0\"))\n\n\trules := []AllowListNameRule{\n\t\t{Name: regexp.MustCompile(\"^docker.*$\"), Allow: false},\n\t\t{Name: regexp.MustCompile(\"^tun.*$\"), Allow: false},\n\t}\n\tal := &LocalAllowList{nameRules: rules}\n\n\tassert.False(t, al.AllowName(\"docker0\"))\n\tassert.False(t, al.AllowName(\"tun0\"))\n\tassert.True(t, al.AllowName(\"eth0\"))\n\n\trules = []AllowListNameRule{\n\t\t{Name: regexp.MustCompile(\"^eth.*$\"), Allow: true},\n\t\t{Name: regexp.MustCompile(\"^ens.*$\"), Allow: true},\n\t}\n\tal = &LocalAllowList{nameRules: rules}\n\n\tassert.False(t, al.AllowName(\"docker0\"))\n\tassert.True(t, al.AllowName(\"eth0\"))\n\tassert.True(t, al.AllowName(\"ens5\"))\n}\n"
  },
  {
    "path": "bits.go",
    "content": "package nebula\n\nimport (\n\t\"github.com/rcrowley/go-metrics\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype Bits struct {\n\tlength             uint64\n\tcurrent            uint64\n\tbits               []bool\n\tlostCounter        metrics.Counter\n\tdupeCounter        metrics.Counter\n\toutOfWindowCounter metrics.Counter\n}\n\nfunc NewBits(bits uint64) *Bits {\n\tb := &Bits{\n\t\tlength:             bits,\n\t\tbits:               make([]bool, bits, bits),\n\t\tcurrent:            0,\n\t\tlostCounter:        metrics.GetOrRegisterCounter(\"network.packets.lost\", nil),\n\t\tdupeCounter:        metrics.GetOrRegisterCounter(\"network.packets.duplicate\", nil),\n\t\toutOfWindowCounter: metrics.GetOrRegisterCounter(\"network.packets.out_of_window\", nil),\n\t}\n\n\t// There is no counter value 0, mark it to avoid counting a lost packet later.\n\tb.bits[0] = true\n\tb.current = 0\n\treturn b\n}\n\nfunc (b *Bits) Check(l *logrus.Logger, i uint64) bool {\n\t// If i is the next number, return true.\n\tif i > b.current {\n\t\treturn true\n\t}\n\n\t// If i is within the window, check if it's been set already.\n\tif i > b.current-b.length || i < b.length && b.current < b.length {\n\t\treturn !b.bits[i%b.length]\n\t}\n\n\t// Not within the window\n\tif l.Level >= logrus.DebugLevel {\n\t\tl.Debugf(\"rejected a packet (top) %d %d\\n\", b.current, i)\n\t}\n\treturn false\n}\n\nfunc (b *Bits) Update(l *logrus.Logger, i uint64) bool {\n\t// If i is the next number, return true and update current.\n\tif i == b.current+1 {\n\t\t// Check if the oldest bit was lost since we are shifting the window by 1 and occupying it with this counter\n\t\t// The very first window can only be tracked as lost once we are on the 2nd window or greater\n\t\tif b.bits[i%b.length] == false && i > b.length {\n\t\t\tb.lostCounter.Inc(1)\n\t\t}\n\t\tb.bits[i%b.length] = true\n\t\tb.current = i\n\t\treturn true\n\t}\n\n\t// If i is a jump, adjust the window, record lost, update current, and return true\n\tif i > b.current {\n\t\tlost := int64(0)\n\t\t// Zero out the bits between the current and the new counter value, limited by the window size,\n\t\t// since the window is shifting\n\t\tfor n := b.current + 1; n <= min(i, b.current+b.length); n++ {\n\t\t\tif b.bits[n%b.length] == false && n > b.length {\n\t\t\t\tlost++\n\t\t\t}\n\t\t\tb.bits[n%b.length] = false\n\t\t}\n\n\t\t// Only record any skipped packets as a result of the window moving further than the window length\n\t\t// Any loss within the new window will be accounted for in future calls\n\t\tlost += max(0, int64(i-b.current-b.length))\n\t\tb.lostCounter.Inc(lost)\n\n\t\tb.bits[i%b.length] = true\n\t\tb.current = i\n\t\treturn true\n\t}\n\n\t// If i is within the current window but below the current counter,\n\t// Check to see if it's a duplicate\n\tif i > b.current-b.length || i < b.length && b.current < b.length {\n\t\tif b.current == i || b.bits[i%b.length] == true {\n\t\t\tif l.Level >= logrus.DebugLevel {\n\t\t\t\tl.WithField(\"receiveWindow\", m{\"accepted\": false, \"currentCounter\": b.current, \"incomingCounter\": i, \"reason\": \"duplicate\"}).\n\t\t\t\t\tDebug(\"Receive window\")\n\t\t\t}\n\t\t\tb.dupeCounter.Inc(1)\n\t\t\treturn false\n\t\t}\n\n\t\tb.bits[i%b.length] = true\n\t\treturn true\n\t}\n\n\t// In all other cases, fail and don't change current.\n\tb.outOfWindowCounter.Inc(1)\n\tif l.Level >= logrus.DebugLevel {\n\t\tl.WithField(\"accepted\", false).\n\t\t\tWithField(\"currentCounter\", b.current).\n\t\t\tWithField(\"incomingCounter\", i).\n\t\t\tWithField(\"reason\", \"nonsense\").\n\t\t\tDebug(\"Receive window\")\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "bits_test.go",
    "content": "package nebula\n\nimport (\n\t\"testing\"\n\n\t\"github.com/slackhq/nebula/test\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestBits(t *testing.T) {\n\tl := test.NewLogger()\n\tb := NewBits(10)\n\n\t// make sure it is the right size\n\tassert.Len(t, b.bits, 10)\n\n\t// This is initialized to zero - receive one. This should work.\n\tassert.True(t, b.Check(l, 1))\n\tassert.True(t, b.Update(l, 1))\n\tassert.EqualValues(t, 1, b.current)\n\tg := []bool{true, true, false, false, false, false, false, false, false, false}\n\tassert.Equal(t, g, b.bits)\n\n\t// Receive two\n\tassert.True(t, b.Check(l, 2))\n\tassert.True(t, b.Update(l, 2))\n\tassert.EqualValues(t, 2, b.current)\n\tg = []bool{true, true, true, false, false, false, false, false, false, false}\n\tassert.Equal(t, g, b.bits)\n\n\t// Receive two again - it will fail\n\tassert.False(t, b.Check(l, 2))\n\tassert.False(t, b.Update(l, 2))\n\tassert.EqualValues(t, 2, b.current)\n\n\t// Jump ahead to 15, which should clear everything and set the 6th element\n\tassert.True(t, b.Check(l, 15))\n\tassert.True(t, b.Update(l, 15))\n\tassert.EqualValues(t, 15, b.current)\n\tg = []bool{false, false, false, false, false, true, false, false, false, false}\n\tassert.Equal(t, g, b.bits)\n\n\t// Mark 14, which is allowed because it is in the window\n\tassert.True(t, b.Check(l, 14))\n\tassert.True(t, b.Update(l, 14))\n\tassert.EqualValues(t, 15, b.current)\n\tg = []bool{false, false, false, false, true, true, false, false, false, false}\n\tassert.Equal(t, g, b.bits)\n\n\t// Mark 5, which is not allowed because it is not in the window\n\tassert.False(t, b.Check(l, 5))\n\tassert.False(t, b.Update(l, 5))\n\tassert.EqualValues(t, 15, b.current)\n\tg = []bool{false, false, false, false, true, true, false, false, false, false}\n\tassert.Equal(t, g, b.bits)\n\n\t// make sure we handle wrapping around once to the current position\n\tb = NewBits(10)\n\tassert.True(t, b.Update(l, 1))\n\tassert.True(t, b.Update(l, 11))\n\tassert.Equal(t, []bool{false, true, false, false, false, false, false, false, false, false}, b.bits)\n\n\t// Walk through a few windows in order\n\tb = NewBits(10)\n\tfor i := uint64(1); i <= 100; i++ {\n\t\tassert.True(t, b.Check(l, i), \"Error while checking %v\", i)\n\t\tassert.True(t, b.Update(l, i), \"Error while updating %v\", i)\n\t}\n\n\tassert.False(t, b.Check(l, 1), \"Out of window check\")\n}\n\nfunc TestBitsLargeJumps(t *testing.T) {\n\tl := test.NewLogger()\n\tb := NewBits(10)\n\tb.lostCounter.Clear()\n\n\tb = NewBits(10)\n\tb.lostCounter.Clear()\n\tassert.True(t, b.Update(l, 55)) // We saw packet 55 and can still track 45,46,47,48,49,50,51,52,53,54\n\tassert.Equal(t, int64(45), b.lostCounter.Count())\n\n\tassert.True(t, b.Update(l, 100)) // We saw packet 55 and 100 and can still track 90,91,92,93,94,95,96,97,98,99\n\tassert.Equal(t, int64(89), b.lostCounter.Count())\n\n\tassert.True(t, b.Update(l, 200)) // We saw packet 55, 100, and 200 and can still track 190,191,192,193,194,195,196,197,198,199\n\tassert.Equal(t, int64(188), b.lostCounter.Count())\n}\n\nfunc TestBitsDupeCounter(t *testing.T) {\n\tl := test.NewLogger()\n\tb := NewBits(10)\n\tb.lostCounter.Clear()\n\tb.dupeCounter.Clear()\n\tb.outOfWindowCounter.Clear()\n\n\tassert.True(t, b.Update(l, 1))\n\tassert.Equal(t, int64(0), b.dupeCounter.Count())\n\n\tassert.False(t, b.Update(l, 1))\n\tassert.Equal(t, int64(1), b.dupeCounter.Count())\n\n\tassert.True(t, b.Update(l, 2))\n\tassert.Equal(t, int64(1), b.dupeCounter.Count())\n\n\tassert.True(t, b.Update(l, 3))\n\tassert.Equal(t, int64(1), b.dupeCounter.Count())\n\n\tassert.False(t, b.Update(l, 1))\n\tassert.Equal(t, int64(0), b.lostCounter.Count())\n\tassert.Equal(t, int64(2), b.dupeCounter.Count())\n\tassert.Equal(t, int64(0), b.outOfWindowCounter.Count())\n}\n\nfunc TestBitsOutOfWindowCounter(t *testing.T) {\n\tl := test.NewLogger()\n\tb := NewBits(10)\n\tb.lostCounter.Clear()\n\tb.dupeCounter.Clear()\n\tb.outOfWindowCounter.Clear()\n\n\tassert.True(t, b.Update(l, 20))\n\tassert.Equal(t, int64(0), b.outOfWindowCounter.Count())\n\n\tassert.True(t, b.Update(l, 21))\n\tassert.True(t, b.Update(l, 22))\n\tassert.True(t, b.Update(l, 23))\n\tassert.True(t, b.Update(l, 24))\n\tassert.True(t, b.Update(l, 25))\n\tassert.True(t, b.Update(l, 26))\n\tassert.True(t, b.Update(l, 27))\n\tassert.True(t, b.Update(l, 28))\n\tassert.True(t, b.Update(l, 29))\n\tassert.Equal(t, int64(0), b.outOfWindowCounter.Count())\n\n\tassert.False(t, b.Update(l, 0))\n\tassert.Equal(t, int64(1), b.outOfWindowCounter.Count())\n\n\tassert.Equal(t, int64(19), b.lostCounter.Count()) // packet 0 wasn't lost\n\tassert.Equal(t, int64(0), b.dupeCounter.Count())\n\tassert.Equal(t, int64(1), b.outOfWindowCounter.Count())\n}\n\nfunc TestBitsLostCounter(t *testing.T) {\n\tl := test.NewLogger()\n\tb := NewBits(10)\n\tb.lostCounter.Clear()\n\tb.dupeCounter.Clear()\n\tb.outOfWindowCounter.Clear()\n\n\tassert.True(t, b.Update(l, 20))\n\tassert.True(t, b.Update(l, 21))\n\tassert.True(t, b.Update(l, 22))\n\tassert.True(t, b.Update(l, 23))\n\tassert.True(t, b.Update(l, 24))\n\tassert.True(t, b.Update(l, 25))\n\tassert.True(t, b.Update(l, 26))\n\tassert.True(t, b.Update(l, 27))\n\tassert.True(t, b.Update(l, 28))\n\tassert.True(t, b.Update(l, 29))\n\tassert.Equal(t, int64(19), b.lostCounter.Count()) // packet 0 wasn't lost\n\tassert.Equal(t, int64(0), b.dupeCounter.Count())\n\tassert.Equal(t, int64(0), b.outOfWindowCounter.Count())\n\n\tb = NewBits(10)\n\tb.lostCounter.Clear()\n\tb.dupeCounter.Clear()\n\tb.outOfWindowCounter.Clear()\n\n\tassert.True(t, b.Update(l, 9))\n\tassert.Equal(t, int64(0), b.lostCounter.Count())\n\t// 10 will set 0 index, 0 was already set, no lost packets\n\tassert.True(t, b.Update(l, 10))\n\tassert.Equal(t, int64(0), b.lostCounter.Count())\n\t// 11 will set 1 index, 1 was missed, we should see 1 packet lost\n\tassert.True(t, b.Update(l, 11))\n\tassert.Equal(t, int64(1), b.lostCounter.Count())\n\t// Now let's fill in the window, should end up with 8 lost packets\n\tassert.True(t, b.Update(l, 12))\n\tassert.True(t, b.Update(l, 13))\n\tassert.True(t, b.Update(l, 14))\n\tassert.True(t, b.Update(l, 15))\n\tassert.True(t, b.Update(l, 16))\n\tassert.True(t, b.Update(l, 17))\n\tassert.True(t, b.Update(l, 18))\n\tassert.True(t, b.Update(l, 19))\n\tassert.Equal(t, int64(8), b.lostCounter.Count())\n\n\t// Jump ahead by a window size\n\tassert.True(t, b.Update(l, 29))\n\tassert.Equal(t, int64(8), b.lostCounter.Count())\n\t// Now lets walk ahead normally through the window, the missed packets should fill in\n\tassert.True(t, b.Update(l, 30))\n\tassert.True(t, b.Update(l, 31))\n\tassert.True(t, b.Update(l, 32))\n\tassert.True(t, b.Update(l, 33))\n\tassert.True(t, b.Update(l, 34))\n\tassert.True(t, b.Update(l, 35))\n\tassert.True(t, b.Update(l, 36))\n\tassert.True(t, b.Update(l, 37))\n\tassert.True(t, b.Update(l, 38))\n\t// 39 packets tracked, 22 seen, 17 lost\n\tassert.Equal(t, int64(17), b.lostCounter.Count())\n\n\t// Jump ahead by 2 windows, should have recording 1 full window missing\n\tassert.True(t, b.Update(l, 58))\n\tassert.Equal(t, int64(27), b.lostCounter.Count())\n\t// Now lets walk ahead normally through the window, the missed packets should fill in from this window\n\tassert.True(t, b.Update(l, 59))\n\tassert.True(t, b.Update(l, 60))\n\tassert.True(t, b.Update(l, 61))\n\tassert.True(t, b.Update(l, 62))\n\tassert.True(t, b.Update(l, 63))\n\tassert.True(t, b.Update(l, 64))\n\tassert.True(t, b.Update(l, 65))\n\tassert.True(t, b.Update(l, 66))\n\tassert.True(t, b.Update(l, 67))\n\t// 68 packets tracked, 32 seen, 36 missed\n\tassert.Equal(t, int64(36), b.lostCounter.Count())\n\tassert.Equal(t, int64(0), b.dupeCounter.Count())\n\tassert.Equal(t, int64(0), b.outOfWindowCounter.Count())\n}\n\nfunc TestBitsLostCounterIssue1(t *testing.T) {\n\tl := test.NewLogger()\n\tb := NewBits(10)\n\tb.lostCounter.Clear()\n\tb.dupeCounter.Clear()\n\tb.outOfWindowCounter.Clear()\n\n\tassert.True(t, b.Update(l, 4))\n\tassert.Equal(t, int64(0), b.lostCounter.Count())\n\tassert.True(t, b.Update(l, 1))\n\tassert.Equal(t, int64(0), b.lostCounter.Count())\n\tassert.True(t, b.Update(l, 9))\n\tassert.Equal(t, int64(0), b.lostCounter.Count())\n\tassert.True(t, b.Update(l, 2))\n\tassert.Equal(t, int64(0), b.lostCounter.Count())\n\tassert.True(t, b.Update(l, 3))\n\tassert.Equal(t, int64(0), b.lostCounter.Count())\n\tassert.True(t, b.Update(l, 5))\n\tassert.Equal(t, int64(0), b.lostCounter.Count())\n\tassert.True(t, b.Update(l, 6))\n\tassert.Equal(t, int64(0), b.lostCounter.Count())\n\tassert.True(t, b.Update(l, 7))\n\tassert.Equal(t, int64(0), b.lostCounter.Count())\n\t// assert.True(t, b.Update(l, 8))\n\tassert.True(t, b.Update(l, 10))\n\tassert.Equal(t, int64(0), b.lostCounter.Count())\n\tassert.True(t, b.Update(l, 11))\n\tassert.Equal(t, int64(0), b.lostCounter.Count())\n\n\tassert.True(t, b.Update(l, 14))\n\tassert.Equal(t, int64(0), b.lostCounter.Count())\n\t// Issue seems to be here, we reset missing packet 8 to false here and don't increment the lost counter\n\tassert.True(t, b.Update(l, 19))\n\tassert.Equal(t, int64(1), b.lostCounter.Count())\n\tassert.True(t, b.Update(l, 12))\n\tassert.Equal(t, int64(1), b.lostCounter.Count())\n\tassert.True(t, b.Update(l, 13))\n\tassert.Equal(t, int64(1), b.lostCounter.Count())\n\tassert.True(t, b.Update(l, 15))\n\tassert.Equal(t, int64(1), b.lostCounter.Count())\n\tassert.True(t, b.Update(l, 16))\n\tassert.Equal(t, int64(1), b.lostCounter.Count())\n\tassert.True(t, b.Update(l, 17))\n\tassert.Equal(t, int64(1), b.lostCounter.Count())\n\tassert.True(t, b.Update(l, 18))\n\tassert.Equal(t, int64(1), b.lostCounter.Count())\n\tassert.True(t, b.Update(l, 20))\n\tassert.Equal(t, int64(1), b.lostCounter.Count())\n\tassert.True(t, b.Update(l, 21))\n\n\t// We missed packet 8 above\n\tassert.Equal(t, int64(1), b.lostCounter.Count())\n\tassert.Equal(t, int64(0), b.dupeCounter.Count())\n\tassert.Equal(t, int64(0), b.outOfWindowCounter.Count())\n}\n\nfunc BenchmarkBits(b *testing.B) {\n\tz := NewBits(10)\n\tfor n := 0; n < b.N; n++ {\n\t\tfor i := range z.bits {\n\t\t\tz.bits[i] = true\n\t\t}\n\t\tfor i := range z.bits {\n\t\t\tz.bits[i] = false\n\t\t}\n\n\t}\n}\n"
  },
  {
    "path": "boring.go",
    "content": "//go:build boringcrypto\n\npackage nebula\n\nimport \"crypto/boring\"\n\nvar boringEnabled = boring.Enabled\n"
  },
  {
    "path": "calculated_remote.go",
    "content": "package nebula\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"math\"\n\t\"net\"\n\t\"net/netip\"\n\t\"strconv\"\n\n\t\"github.com/gaissmai/bart\"\n\t\"github.com/slackhq/nebula/config\"\n)\n\n// This allows us to \"guess\" what the remote might be for a host while we wait\n// for the lighthouse response. See \"lighthouse.calculated_remotes\" in the\n// example config file.\ntype calculatedRemote struct {\n\tipNet netip.Prefix\n\tmask  netip.Prefix\n\tport  uint32\n}\n\nfunc newCalculatedRemote(cidr, maskCidr netip.Prefix, port int) (*calculatedRemote, error) {\n\tif maskCidr.Addr().BitLen() != cidr.Addr().BitLen() {\n\t\treturn nil, fmt.Errorf(\"invalid mask: %s for cidr: %s\", maskCidr, cidr)\n\t}\n\n\tmasked := maskCidr.Masked()\n\tif port < 0 || port > math.MaxUint16 {\n\t\treturn nil, fmt.Errorf(\"invalid port: %d\", port)\n\t}\n\n\treturn &calculatedRemote{\n\t\tipNet: maskCidr,\n\t\tmask:  masked,\n\t\tport:  uint32(port),\n\t}, nil\n}\n\nfunc (c *calculatedRemote) String() string {\n\treturn fmt.Sprintf(\"CalculatedRemote(mask=%v port=%d)\", c.ipNet, c.port)\n}\n\nfunc (c *calculatedRemote) ApplyV4(addr netip.Addr) *V4AddrPort {\n\t// Combine the masked bytes of the \"mask\" IP with the unmasked bytes of the overlay IP\n\tmaskb := net.CIDRMask(c.mask.Bits(), c.mask.Addr().BitLen())\n\tmask := binary.BigEndian.Uint32(maskb[:])\n\n\tb := c.mask.Addr().As4()\n\tmaskAddr := binary.BigEndian.Uint32(b[:])\n\n\tb = addr.As4()\n\tintAddr := binary.BigEndian.Uint32(b[:])\n\n\treturn &V4AddrPort{(maskAddr & mask) | (intAddr & ^mask), c.port}\n}\n\nfunc (c *calculatedRemote) ApplyV6(addr netip.Addr) *V6AddrPort {\n\tmask := net.CIDRMask(c.mask.Bits(), c.mask.Addr().BitLen())\n\tmaskAddr := c.mask.Addr().As16()\n\tcalcAddr := addr.As16()\n\n\tap := V6AddrPort{Port: c.port}\n\n\tmaskb := binary.BigEndian.Uint64(mask[:8])\n\tmaskAddrb := binary.BigEndian.Uint64(maskAddr[:8])\n\tcalcAddrb := binary.BigEndian.Uint64(calcAddr[:8])\n\tap.Hi = (maskAddrb & maskb) | (calcAddrb & ^maskb)\n\n\tmaskb = binary.BigEndian.Uint64(mask[8:])\n\tmaskAddrb = binary.BigEndian.Uint64(maskAddr[8:])\n\tcalcAddrb = binary.BigEndian.Uint64(calcAddr[8:])\n\tap.Lo = (maskAddrb & maskb) | (calcAddrb & ^maskb)\n\n\treturn &ap\n}\n\nfunc NewCalculatedRemotesFromConfig(c *config.C, k string) (*bart.Table[[]*calculatedRemote], error) {\n\tvalue := c.Get(k)\n\tif value == nil {\n\t\treturn nil, nil\n\t}\n\n\tcalculatedRemotes := new(bart.Table[[]*calculatedRemote])\n\n\trawMap, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"config `%s` has invalid type: %T\", k, value)\n\t}\n\tfor rawCIDR, rawValue := range rawMap {\n\t\tcidr, err := netip.ParsePrefix(rawCIDR)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"config `%s` has invalid CIDR: %s\", k, rawCIDR)\n\t\t}\n\n\t\tentry, err := newCalculatedRemotesListFromConfig(cidr, rawValue)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"config '%s.%s': %w\", k, rawCIDR, err)\n\t\t}\n\n\t\tcalculatedRemotes.Insert(cidr, entry)\n\t}\n\n\treturn calculatedRemotes, nil\n}\n\nfunc newCalculatedRemotesListFromConfig(cidr netip.Prefix, raw any) ([]*calculatedRemote, error) {\n\trawList, ok := raw.([]any)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"calculated_remotes entry has invalid type: %T\", raw)\n\t}\n\n\tvar l []*calculatedRemote\n\tfor _, e := range rawList {\n\t\tc, err := newCalculatedRemotesEntryFromConfig(cidr, e)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"calculated_remotes entry: %w\", err)\n\t\t}\n\t\tl = append(l, c)\n\t}\n\n\treturn l, nil\n}\n\nfunc newCalculatedRemotesEntryFromConfig(cidr netip.Prefix, raw any) (*calculatedRemote, error) {\n\trawMap, ok := raw.(map[string]any)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"invalid type: %T\", raw)\n\t}\n\n\trawValue := rawMap[\"mask\"]\n\tif rawValue == nil {\n\t\treturn nil, fmt.Errorf(\"missing mask: %v\", rawMap)\n\t}\n\trawMask, ok := rawValue.(string)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"invalid mask (type %T): %v\", rawValue, rawValue)\n\t}\n\tmaskCidr, err := netip.ParsePrefix(rawMask)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid mask: %s\", rawMask)\n\t}\n\n\tvar port int\n\trawValue = rawMap[\"port\"]\n\tif rawValue == nil {\n\t\treturn nil, fmt.Errorf(\"missing port: %v\", rawMap)\n\t}\n\tswitch v := rawValue.(type) {\n\tcase int:\n\t\tport = v\n\tcase string:\n\t\tport, err = strconv.Atoi(v)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid port: %s: %w\", v, err)\n\t\t}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"invalid port (type %T): %v\", rawValue, rawValue)\n\t}\n\n\treturn newCalculatedRemote(cidr, maskCidr, port)\n}\n"
  },
  {
    "path": "calculated_remote_test.go",
    "content": "package nebula\n\nimport (\n\t\"net/netip\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestCalculatedRemoteApply(t *testing.T) {\n\t// Test v4 addresses\n\tipNet := netip.MustParsePrefix(\"192.168.1.0/24\")\n\tc, err := newCalculatedRemote(ipNet, ipNet, 4242)\n\trequire.NoError(t, err)\n\n\tinput, err := netip.ParseAddr(\"10.0.10.182\")\n\trequire.NoError(t, err)\n\n\texpected, err := netip.ParseAddr(\"192.168.1.182\")\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, netAddrToProtoV4AddrPort(expected, 4242), c.ApplyV4(input))\n\n\t// Test v6 addresses\n\tipNet = netip.MustParsePrefix(\"ffff:ffff:ffff:ffff::0/64\")\n\tc, err = newCalculatedRemote(ipNet, ipNet, 4242)\n\trequire.NoError(t, err)\n\n\tinput, err = netip.ParseAddr(\"beef:beef:beef:beef:beef:beef:beef:beef\")\n\trequire.NoError(t, err)\n\n\texpected, err = netip.ParseAddr(\"ffff:ffff:ffff:ffff:beef:beef:beef:beef\")\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, netAddrToProtoV6AddrPort(expected, 4242), c.ApplyV6(input))\n\n\t// Test v6 addresses part 2\n\tipNet = netip.MustParsePrefix(\"ffff:ffff:ffff:ffff:ffff::0/80\")\n\tc, err = newCalculatedRemote(ipNet, ipNet, 4242)\n\trequire.NoError(t, err)\n\n\tinput, err = netip.ParseAddr(\"beef:beef:beef:beef:beef:beef:beef:beef\")\n\trequire.NoError(t, err)\n\n\texpected, err = netip.ParseAddr(\"ffff:ffff:ffff:ffff:ffff:beef:beef:beef\")\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, netAddrToProtoV6AddrPort(expected, 4242), c.ApplyV6(input))\n\n\t// Test v6 addresses part 2\n\tipNet = netip.MustParsePrefix(\"ffff:ffff:ffff::0/48\")\n\tc, err = newCalculatedRemote(ipNet, ipNet, 4242)\n\trequire.NoError(t, err)\n\n\tinput, err = netip.ParseAddr(\"beef:beef:beef:beef:beef:beef:beef:beef\")\n\trequire.NoError(t, err)\n\n\texpected, err = netip.ParseAddr(\"ffff:ffff:ffff:beef:beef:beef:beef:beef\")\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, netAddrToProtoV6AddrPort(expected, 4242), c.ApplyV6(input))\n}\n\nfunc Test_newCalculatedRemote(t *testing.T) {\n\tc, err := newCalculatedRemote(netip.MustParsePrefix(\"1::1/128\"), netip.MustParsePrefix(\"1.0.0.0/32\"), 4242)\n\trequire.EqualError(t, err, \"invalid mask: 1.0.0.0/32 for cidr: 1::1/128\")\n\trequire.Nil(t, c)\n\n\tc, err = newCalculatedRemote(netip.MustParsePrefix(\"1.0.0.0/32\"), netip.MustParsePrefix(\"1::1/128\"), 4242)\n\trequire.EqualError(t, err, \"invalid mask: 1::1/128 for cidr: 1.0.0.0/32\")\n\trequire.Nil(t, c)\n\n\tc, err = newCalculatedRemote(netip.MustParsePrefix(\"1.0.0.0/32\"), netip.MustParsePrefix(\"1.0.0.0/32\"), 4242)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, c)\n\n\tc, err = newCalculatedRemote(netip.MustParsePrefix(\"1::1/128\"), netip.MustParsePrefix(\"1::1/128\"), 4242)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, c)\n}\n"
  },
  {
    "path": "cert/Makefile",
    "content": "GO111MODULE = on\nexport GO111MODULE\n\ncert_v1.pb.go: cert_v1.proto .FORCE\n\tgo build google.golang.org/protobuf/cmd/protoc-gen-go\n\tPATH=\"$(CURDIR):$(PATH)\" protoc --go_out=. --go_opt=paths=source_relative $<\n\trm protoc-gen-go\n\n.FORCE:\n"
  },
  {
    "path": "cert/README.md",
    "content": "## `cert`\n\nThis is a library for interacting with `nebula` style certificates and authorities.\n\nThere are now 2 versions of `nebula` certificates:\n\n## v1\n\nThis version is deprecated.\n\nA `protobuf` definition of the certificate format is included at `cert_v1.proto`\n\nTo compile the definition you will need `protoc` installed.\n\nTo compile for `go` with the same version of protobuf specified in go.mod:\n\n```bash\nmake proto\n```\n\n## v2\n\nThis is the latest version which uses asn.1 DER encoding. It can support ipv4 and ipv6 and tolerate\nfuture certificate changes better than v1.\n\n`cert_v2.asn1` defines the wire format and can be used to compile marshalers."
  },
  {
    "path": "cert/asn1.go",
    "content": "package cert\n\nimport (\n\t\"golang.org/x/crypto/cryptobyte\"\n\t\"golang.org/x/crypto/cryptobyte/asn1\"\n)\n\n// readOptionalASN1Boolean reads an asn.1 boolean with a specific tag instead of a asn.1 tag wrapping a boolean with a value\n// https://github.com/golang/go/issues/64811#issuecomment-1944446920\nfunc readOptionalASN1Boolean(b *cryptobyte.String, out *bool, tag asn1.Tag, defaultValue bool) bool {\n\tvar present bool\n\tvar child cryptobyte.String\n\tif !b.ReadOptionalASN1(&child, &present, tag) {\n\t\treturn false\n\t}\n\n\tif !present {\n\t\t*out = defaultValue\n\t\treturn true\n\t}\n\n\t// Ensure we have 1 byte\n\tif len(child) == 1 {\n\t\t*out = child[0] > 0\n\t\treturn true\n\t}\n\n\treturn false\n}\n\n// readOptionalASN1Byte reads an asn.1 uint8 with a specific tag instead of a asn.1 tag wrapping a uint8 with a value\n// Similar issue as with readOptionalASN1Boolean\nfunc readOptionalASN1Byte(b *cryptobyte.String, out *byte, tag asn1.Tag, defaultValue byte) bool {\n\tvar present bool\n\tvar child cryptobyte.String\n\tif !b.ReadOptionalASN1(&child, &present, tag) {\n\t\treturn false\n\t}\n\n\tif !present {\n\t\t*out = defaultValue\n\t\treturn true\n\t}\n\n\t// Ensure we have 1 byte\n\tif len(child) == 1 {\n\t\t*out = child[0]\n\t\treturn true\n\t}\n\n\treturn false\n}\n"
  },
  {
    "path": "cert/ca_pool.go",
    "content": "package cert\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"slices\"\n\t\"strings\"\n\t\"time\"\n)\n\ntype CAPool struct {\n\tCAs           map[string]*CachedCertificate\n\tcertBlocklist map[string]struct{}\n}\n\n// NewCAPool creates an empty CAPool\nfunc NewCAPool() *CAPool {\n\tca := CAPool{\n\t\tCAs:           make(map[string]*CachedCertificate),\n\t\tcertBlocklist: make(map[string]struct{}),\n\t}\n\n\treturn &ca\n}\n\n// NewCAPoolFromPEM will create a new CA pool from the provided\n// input bytes, which must be a PEM-encoded set of nebula certificates.\n// If the pool contains any expired certificates, an ErrExpired will be\n// returned along with the pool. The caller must handle any such errors.\nfunc NewCAPoolFromPEM(caPEMs []byte) (*CAPool, error) {\n\tpool := NewCAPool()\n\tvar err error\n\tvar expired bool\n\tfor {\n\t\tcaPEMs, err = pool.AddCAFromPEM(caPEMs)\n\t\tif errors.Is(err, ErrExpired) {\n\t\t\texpired = true\n\t\t\terr = nil\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif len(caPEMs) == 0 || strings.TrimSpace(string(caPEMs)) == \"\" {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif expired {\n\t\treturn pool, ErrExpired\n\t}\n\n\treturn pool, nil\n}\n\n// AddCAFromPEM verifies a Nebula CA certificate and adds it to the pool.\n// Only the first pem encoded object will be consumed, any remaining bytes are returned.\n// Parsed certificates will be verified and must be a CA\nfunc (ncp *CAPool) AddCAFromPEM(pemBytes []byte) ([]byte, error) {\n\tc, pemBytes, err := UnmarshalCertificateFromPEM(pemBytes)\n\tif err != nil {\n\t\treturn pemBytes, err\n\t}\n\n\terr = ncp.AddCA(c)\n\tif err != nil {\n\t\treturn pemBytes, err\n\t}\n\n\treturn pemBytes, nil\n}\n\n// AddCA verifies a Nebula CA certificate and adds it to the pool.\nfunc (ncp *CAPool) AddCA(c Certificate) error {\n\tif !c.IsCA() {\n\t\treturn fmt.Errorf(\"%s: %w\", c.Name(), ErrNotCA)\n\t}\n\n\tif !c.CheckSignature(c.PublicKey()) {\n\t\treturn fmt.Errorf(\"%s: %w\", c.Name(), ErrNotSelfSigned)\n\t}\n\n\tsum, err := c.Fingerprint()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not calculate fingerprint for provided CA; error: %w; %s\", err, c.Name())\n\t}\n\n\tcc := &CachedCertificate{\n\t\tCertificate:    c,\n\t\tFingerprint:    sum,\n\t\tInvertedGroups: make(map[string]struct{}),\n\t}\n\n\tfor _, g := range c.Groups() {\n\t\tcc.InvertedGroups[g] = struct{}{}\n\t}\n\n\tncp.CAs[sum] = cc\n\n\tif c.Expired(time.Now()) {\n\t\treturn fmt.Errorf(\"%s: %w\", c.Name(), ErrExpired)\n\t}\n\n\treturn nil\n}\n\n// BlocklistFingerprint adds a cert fingerprint to the blocklist\nfunc (ncp *CAPool) BlocklistFingerprint(f string) {\n\tncp.certBlocklist[f] = struct{}{}\n}\n\n// ResetCertBlocklist removes all previously blocklisted cert fingerprints\nfunc (ncp *CAPool) ResetCertBlocklist() {\n\tncp.certBlocklist = make(map[string]struct{})\n}\n\n// IsBlocklisted tests the provided fingerprint against the pools blocklist.\n// Returns true if the fingerprint is blocked.\nfunc (ncp *CAPool) IsBlocklisted(fingerprint string) bool {\n\tif _, ok := ncp.certBlocklist[fingerprint]; ok {\n\t\treturn true\n\t}\n\n\treturn false\n}\n\n// VerifyCertificate verifies the certificate is valid and is signed by a trusted CA in the pool.\n// If the certificate is valid then the returned CachedCertificate can be used in subsequent verification attempts\n// to increase performance.\nfunc (ncp *CAPool) VerifyCertificate(now time.Time, c Certificate) (*CachedCertificate, error) {\n\tif c == nil {\n\t\treturn nil, fmt.Errorf(\"no certificate\")\n\t}\n\tfp, err := c.Fingerprint()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not calculate fingerprint to verify: %w\", err)\n\t}\n\n\tsigner, err := ncp.verify(c, now, fp, \"\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Pre nebula v1.10.3 could generate signatures in either high or low s form and validation\n\t// of signatures allowed for either. Nebula v1.10.3 and beyond clamps signature generation to low-s form\n\t// but validation still allows for either. Since a change in the signature bytes affects the fingerprint, we\n\t// need to test both forms until such a time comes that we enforce low-s form on signature validation.\n\tfp2, err := CalculateAlternateFingerprint(c)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not calculate alternate fingerprint to verify: %w\", err)\n\t}\n\tif fp2 != \"\" && ncp.IsBlocklisted(fp2) {\n\t\treturn nil, ErrBlockListed\n\t}\n\n\tcc := CachedCertificate{\n\t\tCertificate:       c,\n\t\tInvertedGroups:    make(map[string]struct{}),\n\t\tFingerprint:       fp,\n\t\tfingerprint2:      fp2,\n\t\tsignerFingerprint: signer.Fingerprint,\n\t}\n\n\tfor _, g := range c.Groups() {\n\t\tcc.InvertedGroups[g] = struct{}{}\n\t}\n\n\treturn &cc, nil\n}\n\n// VerifyCachedCertificate is the same as VerifyCertificate other than it operates on a pre-verified structure and\n// is a cheaper operation to perform as a result.\nfunc (ncp *CAPool) VerifyCachedCertificate(now time.Time, c *CachedCertificate) error {\n\t// Check any available alternate fingerprint forms for this certificate, re P256 high-s/low-s\n\tif c.fingerprint2 != \"\" && ncp.IsBlocklisted(c.fingerprint2) {\n\t\treturn ErrBlockListed\n\t}\n\n\t_, err := ncp.verify(c.Certificate, now, c.Fingerprint, c.signerFingerprint)\n\treturn err\n}\n\nfunc (ncp *CAPool) verify(c Certificate, now time.Time, certFp string, signerFp string) (*CachedCertificate, error) {\n\tif ncp.IsBlocklisted(certFp) {\n\t\treturn nil, ErrBlockListed\n\t}\n\n\tsigner, err := ncp.GetCAForCert(c)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif signer.Certificate.Expired(now) {\n\t\treturn nil, ErrRootExpired\n\t}\n\n\tif c.Expired(now) {\n\t\treturn nil, ErrExpired\n\t}\n\n\t// If we are checking a cached certificate then we can bail early here\n\t// Either the root is no longer trusted or everything is fine\n\tif len(signerFp) > 0 {\n\t\tif signerFp != signer.Fingerprint {\n\t\t\treturn nil, ErrFingerprintMismatch\n\t\t}\n\t\treturn signer, nil\n\t}\n\tif !c.CheckSignature(signer.Certificate.PublicKey()) {\n\t\treturn nil, ErrSignatureMismatch\n\t}\n\n\terr = CheckCAConstraints(signer.Certificate, c)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn signer, nil\n}\n\n// GetCAForCert attempts to return the signing certificate for the provided certificate.\n// No signature validation is performed\nfunc (ncp *CAPool) GetCAForCert(c Certificate) (*CachedCertificate, error) {\n\tissuer := c.Issuer()\n\tif issuer == \"\" {\n\t\treturn nil, fmt.Errorf(\"no issuer in certificate\")\n\t}\n\n\tsigner, ok := ncp.CAs[issuer]\n\tif ok {\n\t\treturn signer, nil\n\t}\n\n\treturn nil, ErrCaNotFound\n}\n\n// GetFingerprints returns an array of trusted CA fingerprints\nfunc (ncp *CAPool) GetFingerprints() []string {\n\tfp := make([]string, len(ncp.CAs))\n\n\ti := 0\n\tfor k := range ncp.CAs {\n\t\tfp[i] = k\n\t\ti++\n\t}\n\n\treturn fp\n}\n\n// CheckCAConstraints returns an error if the sub certificate violates constraints present in the signer certificate.\nfunc CheckCAConstraints(signer Certificate, sub Certificate) error {\n\treturn checkCAConstraints(signer, sub.NotBefore(), sub.NotAfter(), sub.Groups(), sub.Networks(), sub.UnsafeNetworks())\n}\n\n// checkCAConstraints is a very generic function allowing both Certificates and TBSCertificates to be tested.\nfunc checkCAConstraints(signer Certificate, notBefore, notAfter time.Time, groups []string, networks, unsafeNetworks []netip.Prefix) error {\n\t// Make sure this cert isn't valid after the root\n\tif notAfter.After(signer.NotAfter()) {\n\t\treturn fmt.Errorf(\"certificate expires after signing certificate\")\n\t}\n\n\t// Make sure this cert wasn't valid before the root\n\tif notBefore.Before(signer.NotBefore()) {\n\t\treturn fmt.Errorf(\"certificate is valid before the signing certificate\")\n\t}\n\n\t// If the signer has a limited set of groups make sure the cert only contains a subset\n\tsignerGroups := signer.Groups()\n\tif len(signerGroups) > 0 {\n\t\tfor _, g := range groups {\n\t\t\tif !slices.Contains(signerGroups, g) {\n\t\t\t\treturn fmt.Errorf(\"certificate contained a group not present on the signing ca: %s\", g)\n\t\t\t}\n\t\t}\n\t}\n\n\t// If the signer has a limited set of ip ranges to issue from make sure the cert only contains a subset\n\tsigningNetworks := signer.Networks()\n\tif len(signingNetworks) > 0 {\n\t\tfor _, certNetwork := range networks {\n\t\t\tfound := false\n\t\t\tfor _, signingNetwork := range signingNetworks {\n\t\t\t\tif signingNetwork.Contains(certNetwork.Addr()) && signingNetwork.Bits() <= certNetwork.Bits() {\n\t\t\t\t\tfound = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !found {\n\t\t\t\treturn fmt.Errorf(\"certificate contained a network assignment outside the limitations of the signing ca: %s\", certNetwork.String())\n\t\t\t}\n\t\t}\n\t}\n\n\t// If the signer has a limited set of subnet ranges to issue from make sure the cert only contains a subset\n\tsigningUnsafeNetworks := signer.UnsafeNetworks()\n\tif len(signingUnsafeNetworks) > 0 {\n\t\tfor _, certUnsafeNetwork := range unsafeNetworks {\n\t\t\tfound := false\n\t\t\tfor _, caNetwork := range signingUnsafeNetworks {\n\t\t\t\tif caNetwork.Contains(certUnsafeNetwork.Addr()) && caNetwork.Bits() <= certUnsafeNetwork.Bits() {\n\t\t\t\t\tfound = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !found {\n\t\t\t\treturn fmt.Errorf(\"certificate contained an unsafe network assignment outside the limitations of the signing ca: %s\", certUnsafeNetwork.String())\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "cert/ca_pool_test.go",
    "content": "package cert\n\nimport (\n\t\"net/netip\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/slackhq/nebula/cert/p256\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNewCAPoolFromBytes(t *testing.T) {\n\tnoNewLines := `\n# Current provisional, Remove once everything moves over to the real root.\n-----BEGIN NEBULA CERTIFICATE-----\nCj4KDm5lYnVsYSByb290IGNhKM0cMM24zPCvBzogV24YEw5YiqeI/oYo8XXFsoo+\nPBmiOafNJhLacf9rsspAARJAz9OAnh8TKAUKix1kKVMyQU4iM3LsFfZRf6ODWXIf\n2qWMpB6fpd3PSoVYziPoOt2bIHIFLlgRLPJz3I3xBEdBCQ==\n-----END NEBULA CERTIFICATE-----\n# root-ca01\n-----BEGIN NEBULA CERTIFICATE-----\nCkEKEW5lYnVsYSByb290IGNhIDAxKM0cMM24zPCvBzogPzbWTxt8ZgXPQEwup7Br\nBrtIt1O0q5AuTRT3+t2x1VJAARJAZ+2ib23qBXjdy49oU1YysrwuKkWWKrtJ7Jye\nrFBQpDXikOukhQD/mfkloFwJ+Yjsfru7IpTN4ZfjXL+kN/2sCA==\n-----END NEBULA CERTIFICATE-----\n`\n\n\twithNewLines := `\n# Current provisional, Remove once everything moves over to the real root.\n\n-----BEGIN NEBULA CERTIFICATE-----\nCj4KDm5lYnVsYSByb290IGNhKM0cMM24zPCvBzogV24YEw5YiqeI/oYo8XXFsoo+\nPBmiOafNJhLacf9rsspAARJAz9OAnh8TKAUKix1kKVMyQU4iM3LsFfZRf6ODWXIf\n2qWMpB6fpd3PSoVYziPoOt2bIHIFLlgRLPJz3I3xBEdBCQ==\n-----END NEBULA CERTIFICATE-----\n\n# root-ca01\n\n\n-----BEGIN NEBULA CERTIFICATE-----\nCkEKEW5lYnVsYSByb290IGNhIDAxKM0cMM24zPCvBzogPzbWTxt8ZgXPQEwup7Br\nBrtIt1O0q5AuTRT3+t2x1VJAARJAZ+2ib23qBXjdy49oU1YysrwuKkWWKrtJ7Jye\nrFBQpDXikOukhQD/mfkloFwJ+Yjsfru7IpTN4ZfjXL+kN/2sCA==\n-----END NEBULA CERTIFICATE-----\n\n`\n\n\texpired := `\n# expired certificate\n-----BEGIN NEBULA CERTIFICATE-----\nCjMKB2V4cGlyZWQozRwwzRw6ICJSG94CqX8wn5I65Pwn25V6HftVfWeIySVtp2DA\n7TY/QAESQMaAk5iJT5EnQwK524ZaaHGEJLUqqbh5yyOHhboIGiVTWkFeH3HccTW8\nTq5a8AyWDQdfXbtEZ1FwabeHfH5Asw0=\n-----END NEBULA CERTIFICATE-----\n`\n\n\tp256 := `\n# p256 certificate\n-----BEGIN NEBULA CERTIFICATE-----\nCmQKEG5lYnVsYSBQMjU2IHRlc3QozRwwzbjM8K8HOkEEdrmmg40zQp44AkMq6DZp\nk+coOv04r+zh33ISyhbsafnYduN17p2eD7CmHvHuerguXD9f32gcxo/KsFCKEjMe\n+0ABoAYBEkcwRQIgVoTg38L7uWku9xQgsr06kxZ/viQLOO/w1Qj1vFUEnhcCIQCq\n75SjTiV92kv/1GcbT3wWpAZQQDBiUHVMVmh1822szA==\n-----END NEBULA CERTIFICATE-----\n`\n\n\trootCA := certificateV1{\n\t\tdetails: detailsV1{\n\t\t\tname: \"nebula root ca\",\n\t\t},\n\t}\n\n\trootCA01 := certificateV1{\n\t\tdetails: detailsV1{\n\t\t\tname: \"nebula root ca 01\",\n\t\t},\n\t}\n\n\trootCAP256 := certificateV1{\n\t\tdetails: detailsV1{\n\t\t\tname: \"nebula P256 test\",\n\t\t},\n\t}\n\n\tp, err := NewCAPoolFromPEM([]byte(noNewLines))\n\trequire.NoError(t, err)\n\tassert.Equal(t, p.CAs[\"ce4e6c7a596996eb0d82a8875f0f0137a4b53ce22d2421c9fd7150e7a26f6300\"].Certificate.Name(), rootCA.details.name)\n\tassert.Equal(t, p.CAs[\"04c585fcd9a49b276df956a22b7ebea3bf23f1fca5a17c0b56ce2e626631969e\"].Certificate.Name(), rootCA01.details.name)\n\n\tpp, err := NewCAPoolFromPEM([]byte(withNewLines))\n\trequire.NoError(t, err)\n\tassert.Equal(t, pp.CAs[\"ce4e6c7a596996eb0d82a8875f0f0137a4b53ce22d2421c9fd7150e7a26f6300\"].Certificate.Name(), rootCA.details.name)\n\tassert.Equal(t, pp.CAs[\"04c585fcd9a49b276df956a22b7ebea3bf23f1fca5a17c0b56ce2e626631969e\"].Certificate.Name(), rootCA01.details.name)\n\n\t// expired cert, no valid certs\n\tppp, err := NewCAPoolFromPEM([]byte(expired))\n\tassert.Equal(t, ErrExpired, err)\n\tassert.Equal(t, \"expired\", ppp.CAs[\"c39b35a0e8f246203fe4f32b9aa8bfd155f1ae6a6be9d78370641e43397f48f5\"].Certificate.Name())\n\n\t// expired cert, with valid certs\n\tpppp, err := NewCAPoolFromPEM(append([]byte(expired), noNewLines...))\n\tassert.Equal(t, ErrExpired, err)\n\tassert.Equal(t, pppp.CAs[\"ce4e6c7a596996eb0d82a8875f0f0137a4b53ce22d2421c9fd7150e7a26f6300\"].Certificate.Name(), rootCA.details.name)\n\tassert.Equal(t, pppp.CAs[\"04c585fcd9a49b276df956a22b7ebea3bf23f1fca5a17c0b56ce2e626631969e\"].Certificate.Name(), rootCA01.details.name)\n\tassert.Equal(t, \"expired\", pppp.CAs[\"c39b35a0e8f246203fe4f32b9aa8bfd155f1ae6a6be9d78370641e43397f48f5\"].Certificate.Name())\n\tassert.Len(t, pppp.CAs, 3)\n\n\tppppp, err := NewCAPoolFromPEM([]byte(p256))\n\trequire.NoError(t, err)\n\tassert.Equal(t, ppppp.CAs[\"552bf7d99bec1fc775a0e4c324bf6d8f789b3078f1919c7960d2e5e0c351ee97\"].Certificate.Name(), rootCAP256.details.name)\n\tassert.Len(t, ppppp.CAs, 1)\n}\n\nfunc TestCertificateV1_Verify(t *testing.T) {\n\tca, _, caKey, _ := NewTestCaCert(Version1, Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, nil)\n\tc, _, _, _ := NewTestCert(Version1, Curve_CURVE25519, ca, caKey, \"test cert\", time.Now(), time.Now().Add(5*time.Minute), nil, nil, nil)\n\n\tcaPool := NewCAPool()\n\trequire.NoError(t, caPool.AddCA(ca))\n\n\tf, err := c.Fingerprint()\n\trequire.NoError(t, err)\n\tcaPool.BlocklistFingerprint(f)\n\n\t_, err = caPool.VerifyCertificate(time.Now(), c)\n\trequire.EqualError(t, err, \"certificate is in the block list\")\n\n\tcaPool.ResetCertBlocklist()\n\t_, err = caPool.VerifyCertificate(time.Now(), c)\n\trequire.NoError(t, err)\n\n\t_, err = caPool.VerifyCertificate(time.Now().Add(time.Hour*1000), c)\n\trequire.EqualError(t, err, \"root certificate is expired\")\n\n\tassert.PanicsWithError(t, \"certificate is valid before the signing certificate\", func() {\n\t\tNewTestCert(Version1, Curve_CURVE25519, ca, caKey, \"test cert2\", time.Time{}, time.Time{}, nil, nil, nil)\n\t})\n\n\t// Test group assertion\n\tca, _, caKey, _ = NewTestCaCert(Version1, Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{\"test1\", \"test2\"})\n\tcaPem, err := ca.MarshalPEM()\n\trequire.NoError(t, err)\n\n\tcaPool = NewCAPool()\n\tb, err := caPool.AddCAFromPEM(caPem)\n\trequire.NoError(t, err)\n\tassert.Empty(t, b)\n\n\tassert.PanicsWithError(t, \"certificate contained a group not present on the signing ca: bad\", func() {\n\t\tNewTestCert(Version1, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), nil, nil, []string{\"test1\", \"bad\"})\n\t})\n\n\tc, _, _, _ = NewTestCert(Version1, Curve_CURVE25519, ca, caKey, \"test2\", time.Now(), time.Now().Add(5*time.Minute), nil, nil, []string{\"test1\"})\n\trequire.NoError(t, err)\n\t_, err = caPool.VerifyCertificate(time.Now(), c)\n\trequire.NoError(t, err)\n}\n\nfunc TestCertificateV1_VerifyP256(t *testing.T) {\n\tca, _, caKey, _ := NewTestCaCert(Version1, Curve_P256, time.Now(), time.Now().Add(10*time.Minute), nil, nil, nil)\n\tc, _, _, _ := NewTestCert(Version1, Curve_P256, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), nil, nil, nil)\n\n\tcaPool := NewCAPool()\n\trequire.NoError(t, caPool.AddCA(ca))\n\n\tf, err := c.Fingerprint()\n\trequire.NoError(t, err)\n\tcaPool.BlocklistFingerprint(f)\n\n\t_, err = caPool.VerifyCertificate(time.Now(), c)\n\trequire.EqualError(t, err, \"certificate is in the block list\")\n\n\t// Create a copy of the cert and swap to the alternate form for the signature\n\tnc := c.Copy()\n\tb, err := p256.Swap(c.Signature())\n\trequire.NoError(t, err)\n\trequire.NoError(t, nc.(*certificateV1).setSignature(b))\n\n\t_, err = caPool.VerifyCertificate(time.Now(), nc)\n\trequire.EqualError(t, err, \"certificate is in the block list\")\n\n\tcaPool.ResetCertBlocklist()\n\t_, err = caPool.VerifyCertificate(time.Now(), c)\n\trequire.NoError(t, err)\n\n\t_, err = caPool.VerifyCertificate(time.Now().Add(time.Hour*1000), c)\n\trequire.EqualError(t, err, \"root certificate is expired\")\n\n\tassert.PanicsWithError(t, \"certificate is valid before the signing certificate\", func() {\n\t\tNewTestCert(Version1, Curve_P256, ca, caKey, \"test\", time.Time{}, time.Time{}, nil, nil, nil)\n\t})\n\n\t// Test group assertion\n\tca, _, caKey, _ = NewTestCaCert(Version1, Curve_P256, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{\"test1\", \"test2\"})\n\tcaPem, err := ca.MarshalPEM()\n\trequire.NoError(t, err)\n\n\tcaPool = NewCAPool()\n\tb, err = caPool.AddCAFromPEM(caPem)\n\trequire.NoError(t, err)\n\tassert.Empty(t, b)\n\n\tassert.PanicsWithError(t, \"certificate contained a group not present on the signing ca: bad\", func() {\n\t\tNewTestCert(Version1, Curve_P256, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), nil, nil, []string{\"test1\", \"bad\"})\n\t})\n\n\tc, _, _, _ = NewTestCert(Version1, Curve_P256, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), nil, nil, []string{\"test1\"})\n\tcc, err := caPool.VerifyCertificate(time.Now(), c)\n\trequire.NoError(t, err)\n\n\t// Reset the blocklist and block the alternate form fingerprint\n\tcaPool.ResetCertBlocklist()\n\tcaPool.BlocklistFingerprint(cc.fingerprint2)\n\terr = caPool.VerifyCachedCertificate(time.Now(), cc)\n\trequire.EqualError(t, err, \"certificate is in the block list\")\n\n\tcaPool.ResetCertBlocklist()\n\terr = caPool.VerifyCachedCertificate(time.Now(), cc)\n\trequire.NoError(t, err)\n}\n\nfunc TestCertificateV1_Verify_IPs(t *testing.T) {\n\tcaIp1 := mustParsePrefixUnmapped(\"10.0.0.0/16\")\n\tcaIp2 := mustParsePrefixUnmapped(\"192.168.0.0/24\")\n\tca, _, caKey, _ := NewTestCaCert(Version1, Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), []netip.Prefix{caIp1, caIp2}, nil, []string{\"test\"})\n\n\tcaPem, err := ca.MarshalPEM()\n\trequire.NoError(t, err)\n\n\tcaPool := NewCAPool()\n\tb, err := caPool.AddCAFromPEM(caPem)\n\trequire.NoError(t, err)\n\tassert.Empty(t, b)\n\n\t// ip is outside the network\n\tcIp1 := mustParsePrefixUnmapped(\"10.1.0.0/24\")\n\tcIp2 := mustParsePrefixUnmapped(\"192.168.0.1/16\")\n\tassert.PanicsWithError(t, \"certificate contained a network assignment outside the limitations of the signing ca: 10.1.0.0/24\", func() {\n\t\tNewTestCert(Version1, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{cIp1, cIp2}, nil, []string{\"test\"})\n\t})\n\n\t// ip is outside the network reversed order of above\n\tcIp1 = mustParsePrefixUnmapped(\"192.168.0.1/24\")\n\tcIp2 = mustParsePrefixUnmapped(\"10.1.0.0/24\")\n\tassert.PanicsWithError(t, \"certificate contained a network assignment outside the limitations of the signing ca: 10.1.0.0/24\", func() {\n\t\tNewTestCert(Version1, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{cIp1, cIp2}, nil, []string{\"test\"})\n\t})\n\n\t// ip is within the network but mask is outside\n\tcIp1 = mustParsePrefixUnmapped(\"10.0.1.0/15\")\n\tcIp2 = mustParsePrefixUnmapped(\"192.168.0.1/24\")\n\tassert.PanicsWithError(t, \"certificate contained a network assignment outside the limitations of the signing ca: 10.0.1.0/15\", func() {\n\t\tNewTestCert(Version1, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{cIp1, cIp2}, nil, []string{\"test\"})\n\t})\n\n\t// ip is within the network but mask is outside reversed order of above\n\tcIp1 = mustParsePrefixUnmapped(\"192.168.0.1/24\")\n\tcIp2 = mustParsePrefixUnmapped(\"10.0.1.0/15\")\n\tassert.PanicsWithError(t, \"certificate contained a network assignment outside the limitations of the signing ca: 10.0.1.0/15\", func() {\n\t\tNewTestCert(Version1, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{cIp1, cIp2}, nil, []string{\"test\"})\n\t})\n\n\t// ip and mask are within the network\n\tcIp1 = mustParsePrefixUnmapped(\"10.0.1.0/16\")\n\tcIp2 = mustParsePrefixUnmapped(\"192.168.0.1/25\")\n\tc, _, _, _ := NewTestCert(Version1, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{cIp1, cIp2}, nil, []string{\"test\"})\n\t_, err = caPool.VerifyCertificate(time.Now(), c)\n\trequire.NoError(t, err)\n\n\t// Exact matches\n\tc, _, _, _ = NewTestCert(Version1, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{caIp1, caIp2}, nil, []string{\"test\"})\n\trequire.NoError(t, err)\n\t_, err = caPool.VerifyCertificate(time.Now(), c)\n\trequire.NoError(t, err)\n\n\t// Exact matches reversed\n\tc, _, _, _ = NewTestCert(Version1, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{caIp2, caIp1}, nil, []string{\"test\"})\n\trequire.NoError(t, err)\n\t_, err = caPool.VerifyCertificate(time.Now(), c)\n\trequire.NoError(t, err)\n\n\t// Exact matches reversed with just 1\n\tc, _, _, _ = NewTestCert(Version1, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{caIp1}, nil, []string{\"test\"})\n\trequire.NoError(t, err)\n\t_, err = caPool.VerifyCertificate(time.Now(), c)\n\trequire.NoError(t, err)\n}\n\nfunc TestCertificateV1_Verify_Subnets(t *testing.T) {\n\tcaIp1 := mustParsePrefixUnmapped(\"10.0.0.0/16\")\n\tcaIp2 := mustParsePrefixUnmapped(\"192.168.0.0/24\")\n\tca, _, caKey, _ := NewTestCaCert(Version1, Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, []netip.Prefix{caIp1, caIp2}, []string{\"test\"})\n\n\tcaPem, err := ca.MarshalPEM()\n\trequire.NoError(t, err)\n\n\tcaPool := NewCAPool()\n\tb, err := caPool.AddCAFromPEM(caPem)\n\trequire.NoError(t, err)\n\tassert.Empty(t, b)\n\n\t// ip is outside the network\n\tcIp1 := mustParsePrefixUnmapped(\"10.1.0.0/24\")\n\tcIp2 := mustParsePrefixUnmapped(\"192.168.0.1/16\")\n\tassert.PanicsWithError(t, \"certificate contained an unsafe network assignment outside the limitations of the signing ca: 10.1.0.0/24\", func() {\n\t\tNewTestCert(Version1, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), nil, []netip.Prefix{cIp1, cIp2}, []string{\"test\"})\n\t})\n\n\t// ip is outside the network reversed order of above\n\tcIp1 = mustParsePrefixUnmapped(\"192.168.0.1/24\")\n\tcIp2 = mustParsePrefixUnmapped(\"10.1.0.0/24\")\n\tassert.PanicsWithError(t, \"certificate contained an unsafe network assignment outside the limitations of the signing ca: 10.1.0.0/24\", func() {\n\t\tNewTestCert(Version1, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), nil, []netip.Prefix{cIp1, cIp2}, []string{\"test\"})\n\t})\n\n\t// ip is within the network but mask is outside\n\tcIp1 = mustParsePrefixUnmapped(\"10.0.1.0/15\")\n\tcIp2 = mustParsePrefixUnmapped(\"192.168.0.1/24\")\n\tassert.PanicsWithError(t, \"certificate contained an unsafe network assignment outside the limitations of the signing ca: 10.0.1.0/15\", func() {\n\t\tNewTestCert(Version1, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), nil, []netip.Prefix{cIp1, cIp2}, []string{\"test\"})\n\t})\n\n\t// ip is within the network but mask is outside reversed order of above\n\tcIp1 = mustParsePrefixUnmapped(\"192.168.0.1/24\")\n\tcIp2 = mustParsePrefixUnmapped(\"10.0.1.0/15\")\n\tassert.PanicsWithError(t, \"certificate contained an unsafe network assignment outside the limitations of the signing ca: 10.0.1.0/15\", func() {\n\t\tNewTestCert(Version1, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), nil, []netip.Prefix{cIp1, cIp2}, []string{\"test\"})\n\t})\n\n\t// ip and mask are within the network\n\tcIp1 = mustParsePrefixUnmapped(\"10.0.1.0/16\")\n\tcIp2 = mustParsePrefixUnmapped(\"192.168.0.1/25\")\n\tc, _, _, _ := NewTestCert(Version1, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), nil, []netip.Prefix{cIp1, cIp2}, []string{\"test\"})\n\trequire.NoError(t, err)\n\t_, err = caPool.VerifyCertificate(time.Now(), c)\n\trequire.NoError(t, err)\n\n\t// Exact matches\n\tc, _, _, _ = NewTestCert(Version1, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), nil, []netip.Prefix{caIp1, caIp2}, []string{\"test\"})\n\trequire.NoError(t, err)\n\t_, err = caPool.VerifyCertificate(time.Now(), c)\n\trequire.NoError(t, err)\n\n\t// Exact matches reversed\n\tc, _, _, _ = NewTestCert(Version1, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), nil, []netip.Prefix{caIp2, caIp1}, []string{\"test\"})\n\trequire.NoError(t, err)\n\t_, err = caPool.VerifyCertificate(time.Now(), c)\n\trequire.NoError(t, err)\n\n\t// Exact matches reversed with just 1\n\tc, _, _, _ = NewTestCert(Version1, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), nil, []netip.Prefix{caIp1}, []string{\"test\"})\n\trequire.NoError(t, err)\n\t_, err = caPool.VerifyCertificate(time.Now(), c)\n\trequire.NoError(t, err)\n}\n\nfunc TestCertificateV2_Verify(t *testing.T) {\n\tca, _, caKey, _ := NewTestCaCert(Version2, Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, nil)\n\tc, _, _, _ := NewTestCert(Version2, Curve_CURVE25519, ca, caKey, \"test cert\", time.Now(), time.Now().Add(5*time.Minute), nil, nil, nil)\n\n\tcaPool := NewCAPool()\n\trequire.NoError(t, caPool.AddCA(ca))\n\n\tf, err := c.Fingerprint()\n\trequire.NoError(t, err)\n\tcaPool.BlocklistFingerprint(f)\n\n\t_, err = caPool.VerifyCertificate(time.Now(), c)\n\trequire.EqualError(t, err, \"certificate is in the block list\")\n\n\tcaPool.ResetCertBlocklist()\n\t_, err = caPool.VerifyCertificate(time.Now(), c)\n\trequire.NoError(t, err)\n\n\t_, err = caPool.VerifyCertificate(time.Now().Add(time.Hour*1000), c)\n\trequire.EqualError(t, err, \"root certificate is expired\")\n\n\tassert.PanicsWithError(t, \"certificate is valid before the signing certificate\", func() {\n\t\tNewTestCert(Version2, Curve_CURVE25519, ca, caKey, \"test cert2\", time.Time{}, time.Time{}, nil, nil, nil)\n\t})\n\n\t// Test group assertion\n\tca, _, caKey, _ = NewTestCaCert(Version2, Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{\"test1\", \"test2\"})\n\tcaPem, err := ca.MarshalPEM()\n\trequire.NoError(t, err)\n\n\tcaPool = NewCAPool()\n\tb, err := caPool.AddCAFromPEM(caPem)\n\trequire.NoError(t, err)\n\tassert.Empty(t, b)\n\n\tassert.PanicsWithError(t, \"certificate contained a group not present on the signing ca: bad\", func() {\n\t\tNewTestCert(Version2, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), nil, nil, []string{\"test1\", \"bad\"})\n\t})\n\n\tc, _, _, _ = NewTestCert(Version2, Curve_CURVE25519, ca, caKey, \"test2\", time.Now(), time.Now().Add(5*time.Minute), nil, nil, []string{\"test1\"})\n\trequire.NoError(t, err)\n\t_, err = caPool.VerifyCertificate(time.Now(), c)\n\trequire.NoError(t, err)\n}\n\nfunc TestCertificateV2_VerifyP256(t *testing.T) {\n\tca, _, caKey, _ := NewTestCaCert(Version2, Curve_P256, time.Now(), time.Now().Add(10*time.Minute), nil, nil, nil)\n\tc, _, _, _ := NewTestCert(Version2, Curve_P256, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), nil, nil, nil)\n\n\tcaPool := NewCAPool()\n\trequire.NoError(t, caPool.AddCA(ca))\n\n\tf, err := c.Fingerprint()\n\trequire.NoError(t, err)\n\tcaPool.BlocklistFingerprint(f)\n\n\t_, err = caPool.VerifyCertificate(time.Now(), c)\n\trequire.EqualError(t, err, \"certificate is in the block list\")\n\n\t// Create a copy of the cert and swap to the alternate form for the signature\n\tnc := c.Copy()\n\tb, err := p256.Swap(c.Signature())\n\trequire.NoError(t, err)\n\trequire.NoError(t, nc.(*certificateV2).setSignature(b))\n\n\t_, err = caPool.VerifyCertificate(time.Now(), nc)\n\trequire.EqualError(t, err, \"certificate is in the block list\")\n\n\tcaPool.ResetCertBlocklist()\n\t_, err = caPool.VerifyCertificate(time.Now(), c)\n\trequire.NoError(t, err)\n\n\t_, err = caPool.VerifyCertificate(time.Now().Add(time.Hour*1000), c)\n\trequire.EqualError(t, err, \"root certificate is expired\")\n\n\tassert.PanicsWithError(t, \"certificate is valid before the signing certificate\", func() {\n\t\tNewTestCert(Version2, Curve_P256, ca, caKey, \"test\", time.Time{}, time.Time{}, nil, nil, nil)\n\t})\n\n\t// Test group assertion\n\tca, _, caKey, _ = NewTestCaCert(Version2, Curve_P256, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{\"test1\", \"test2\"})\n\tcaPem, err := ca.MarshalPEM()\n\trequire.NoError(t, err)\n\n\tcaPool = NewCAPool()\n\tb, err = caPool.AddCAFromPEM(caPem)\n\trequire.NoError(t, err)\n\tassert.Empty(t, b)\n\n\tassert.PanicsWithError(t, \"certificate contained a group not present on the signing ca: bad\", func() {\n\t\tNewTestCert(Version2, Curve_P256, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), nil, nil, []string{\"test1\", \"bad\"})\n\t})\n\n\tc, _, _, _ = NewTestCert(Version2, Curve_P256, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), nil, nil, []string{\"test1\"})\n\tcc, err := caPool.VerifyCertificate(time.Now(), c)\n\trequire.NoError(t, err)\n\n\t// Reset the blocklist and block the alternate form fingerprint\n\tcaPool.ResetCertBlocklist()\n\tcaPool.BlocklistFingerprint(cc.fingerprint2)\n\terr = caPool.VerifyCachedCertificate(time.Now(), cc)\n\trequire.EqualError(t, err, \"certificate is in the block list\")\n\n\tcaPool.ResetCertBlocklist()\n\terr = caPool.VerifyCachedCertificate(time.Now(), cc)\n\trequire.NoError(t, err)\n}\n\nfunc TestCertificateV2_Verify_IPs(t *testing.T) {\n\tcaIp1 := mustParsePrefixUnmapped(\"10.0.0.0/16\")\n\tcaIp2 := mustParsePrefixUnmapped(\"192.168.0.0/24\")\n\tca, _, caKey, _ := NewTestCaCert(Version2, Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), []netip.Prefix{caIp1, caIp2}, nil, []string{\"test\"})\n\n\tcaPem, err := ca.MarshalPEM()\n\trequire.NoError(t, err)\n\n\tcaPool := NewCAPool()\n\tb, err := caPool.AddCAFromPEM(caPem)\n\trequire.NoError(t, err)\n\tassert.Empty(t, b)\n\n\t// ip is outside the network\n\tcIp1 := mustParsePrefixUnmapped(\"10.1.0.0/24\")\n\tcIp2 := mustParsePrefixUnmapped(\"192.168.0.1/16\")\n\tassert.PanicsWithError(t, \"certificate contained a network assignment outside the limitations of the signing ca: 10.1.0.0/24\", func() {\n\t\tNewTestCert(Version2, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{cIp1, cIp2}, nil, []string{\"test\"})\n\t})\n\n\t// ip is outside the network reversed order of above\n\tcIp1 = mustParsePrefixUnmapped(\"192.168.0.1/24\")\n\tcIp2 = mustParsePrefixUnmapped(\"10.1.0.0/24\")\n\tassert.PanicsWithError(t, \"certificate contained a network assignment outside the limitations of the signing ca: 10.1.0.0/24\", func() {\n\t\tNewTestCert(Version2, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{cIp1, cIp2}, nil, []string{\"test\"})\n\t})\n\n\t// ip is within the network but mask is outside\n\tcIp1 = mustParsePrefixUnmapped(\"10.0.1.0/15\")\n\tcIp2 = mustParsePrefixUnmapped(\"192.168.0.1/24\")\n\tassert.PanicsWithError(t, \"certificate contained a network assignment outside the limitations of the signing ca: 10.0.1.0/15\", func() {\n\t\tNewTestCert(Version2, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{cIp1, cIp2}, nil, []string{\"test\"})\n\t})\n\n\t// ip is within the network but mask is outside reversed order of above\n\tcIp1 = mustParsePrefixUnmapped(\"192.168.0.1/24\")\n\tcIp2 = mustParsePrefixUnmapped(\"10.0.1.0/15\")\n\tassert.PanicsWithError(t, \"certificate contained a network assignment outside the limitations of the signing ca: 10.0.1.0/15\", func() {\n\t\tNewTestCert(Version2, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{cIp1, cIp2}, nil, []string{\"test\"})\n\t})\n\n\t// ip and mask are within the network\n\tcIp1 = mustParsePrefixUnmapped(\"10.0.1.0/16\")\n\tcIp2 = mustParsePrefixUnmapped(\"192.168.0.1/25\")\n\tc, _, _, _ := NewTestCert(Version2, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{cIp1, cIp2}, nil, []string{\"test\"})\n\t_, err = caPool.VerifyCertificate(time.Now(), c)\n\trequire.NoError(t, err)\n\n\t// Exact matches\n\tc, _, _, _ = NewTestCert(Version2, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{caIp1, caIp2}, nil, []string{\"test\"})\n\trequire.NoError(t, err)\n\t_, err = caPool.VerifyCertificate(time.Now(), c)\n\trequire.NoError(t, err)\n\n\t// Exact matches reversed\n\tc, _, _, _ = NewTestCert(Version2, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{caIp2, caIp1}, nil, []string{\"test\"})\n\trequire.NoError(t, err)\n\t_, err = caPool.VerifyCertificate(time.Now(), c)\n\trequire.NoError(t, err)\n\n\t// Exact matches reversed with just 1\n\tc, _, _, _ = NewTestCert(Version2, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{caIp1}, nil, []string{\"test\"})\n\trequire.NoError(t, err)\n\t_, err = caPool.VerifyCertificate(time.Now(), c)\n\trequire.NoError(t, err)\n}\n\nfunc TestCertificateV2_Verify_Subnets(t *testing.T) {\n\tcaIp1 := mustParsePrefixUnmapped(\"10.0.0.0/16\")\n\tcaIp2 := mustParsePrefixUnmapped(\"192.168.0.0/24\")\n\tca, _, caKey, _ := NewTestCaCert(Version2, Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, []netip.Prefix{caIp1, caIp2}, []string{\"test\"})\n\n\tcaPem, err := ca.MarshalPEM()\n\trequire.NoError(t, err)\n\n\tcaPool := NewCAPool()\n\tb, err := caPool.AddCAFromPEM(caPem)\n\trequire.NoError(t, err)\n\tassert.Empty(t, b)\n\n\t// ip is outside the network\n\tcIp1 := mustParsePrefixUnmapped(\"10.1.0.0/24\")\n\tcIp2 := mustParsePrefixUnmapped(\"192.168.0.1/16\")\n\tassert.PanicsWithError(t, \"certificate contained an unsafe network assignment outside the limitations of the signing ca: 10.1.0.0/24\", func() {\n\t\tNewTestCert(Version2, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), nil, []netip.Prefix{cIp1, cIp2}, []string{\"test\"})\n\t})\n\n\t// ip is outside the network reversed order of above\n\tcIp1 = mustParsePrefixUnmapped(\"192.168.0.1/24\")\n\tcIp2 = mustParsePrefixUnmapped(\"10.1.0.0/24\")\n\tassert.PanicsWithError(t, \"certificate contained an unsafe network assignment outside the limitations of the signing ca: 10.1.0.0/24\", func() {\n\t\tNewTestCert(Version2, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), nil, []netip.Prefix{cIp1, cIp2}, []string{\"test\"})\n\t})\n\n\t// ip is within the network but mask is outside\n\tcIp1 = mustParsePrefixUnmapped(\"10.0.1.0/15\")\n\tcIp2 = mustParsePrefixUnmapped(\"192.168.0.1/24\")\n\tassert.PanicsWithError(t, \"certificate contained an unsafe network assignment outside the limitations of the signing ca: 10.0.1.0/15\", func() {\n\t\tNewTestCert(Version2, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), nil, []netip.Prefix{cIp1, cIp2}, []string{\"test\"})\n\t})\n\n\t// ip is within the network but mask is outside reversed order of above\n\tcIp1 = mustParsePrefixUnmapped(\"192.168.0.1/24\")\n\tcIp2 = mustParsePrefixUnmapped(\"10.0.1.0/15\")\n\tassert.PanicsWithError(t, \"certificate contained an unsafe network assignment outside the limitations of the signing ca: 10.0.1.0/15\", func() {\n\t\tNewTestCert(Version2, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), nil, []netip.Prefix{cIp1, cIp2}, []string{\"test\"})\n\t})\n\n\t// ip and mask are within the network\n\tcIp1 = mustParsePrefixUnmapped(\"10.0.1.0/16\")\n\tcIp2 = mustParsePrefixUnmapped(\"192.168.0.1/25\")\n\tc, _, _, _ := NewTestCert(Version2, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), nil, []netip.Prefix{cIp1, cIp2}, []string{\"test\"})\n\trequire.NoError(t, err)\n\t_, err = caPool.VerifyCertificate(time.Now(), c)\n\trequire.NoError(t, err)\n\n\t// Exact matches\n\tc, _, _, _ = NewTestCert(Version2, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), nil, []netip.Prefix{caIp1, caIp2}, []string{\"test\"})\n\trequire.NoError(t, err)\n\t_, err = caPool.VerifyCertificate(time.Now(), c)\n\trequire.NoError(t, err)\n\n\t// Exact matches reversed\n\tc, _, _, _ = NewTestCert(Version2, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), nil, []netip.Prefix{caIp2, caIp1}, []string{\"test\"})\n\trequire.NoError(t, err)\n\t_, err = caPool.VerifyCertificate(time.Now(), c)\n\trequire.NoError(t, err)\n\n\t// Exact matches reversed with just 1\n\tc, _, _, _ = NewTestCert(Version2, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), nil, []netip.Prefix{caIp1}, []string{\"test\"})\n\trequire.NoError(t, err)\n\t_, err = caPool.VerifyCertificate(time.Now(), c)\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "cert/cert.go",
    "content": "package cert\n\nimport (\n\t\"fmt\"\n\t\"net/netip\"\n\t\"time\"\n\n\t\"github.com/slackhq/nebula/cert/p256\"\n)\n\ntype Version uint8\n\nconst (\n\tVersionPre1 Version = 0\n\tVersion1    Version = 1\n\tVersion2    Version = 2\n)\n\ntype Certificate interface {\n\t// Version defines the underlying certificate structure and wire protocol version\n\t// Version1 certificates are ipv4 only and uses protobuf serialization\n\t// Version2 certificates are ipv4 or ipv6 and uses asn.1 serialization\n\tVersion() Version\n\n\t// Name is the human-readable name that identifies this certificate.\n\tName() string\n\n\t// Networks is a list of ip addresses and network sizes assigned to this certificate.\n\t// If IsCA is true then certificates signed by this CA can only have ip addresses and\n\t// networks that are contained by an entry in this list.\n\tNetworks() []netip.Prefix\n\n\t// UnsafeNetworks is a list of networks that this host can act as an unsafe router for.\n\t// If IsCA is true then certificates signed by this CA can only have networks that are\n\t// contained by an entry in this list.\n\tUnsafeNetworks() []netip.Prefix\n\n\t// Groups is a list of identities that can be used to write more general firewall rule\n\t// definitions.\n\t// If IsCA is true then certificates signed by this CA can only use groups that are\n\t// in this list.\n\tGroups() []string\n\n\t// IsCA signifies if this is a certificate authority (true) or a host certificate (false).\n\t// It is invalid to use a CA certificate as a host certificate.\n\tIsCA() bool\n\n\t// NotBefore is the time at which this certificate becomes valid.\n\t// If IsCA is true then certificate signed by this CA can not have a time before this.\n\tNotBefore() time.Time\n\n\t// NotAfter is the time at which this certificate becomes invalid.\n\t// If IsCA is true then certificate signed by this CA can not have a time after this.\n\tNotAfter() time.Time\n\n\t// Issuer is the fingerprint of the CA that signed this certificate.\n\t// If IsCA is true then this will be empty.\n\tIssuer() string\n\n\t// PublicKey is the raw bytes to be used in asymmetric cryptographic operations.\n\tPublicKey() []byte\n\n\t// MarshalPublicKeyPEM is the value of PublicKey marshalled to PEM\n\tMarshalPublicKeyPEM() []byte\n\n\t// Curve identifies which curve was used for the PublicKey and Signature.\n\tCurve() Curve\n\n\t// Signature is the cryptographic seal for all the details of this certificate.\n\t// CheckSignature can be used to verify that the details of this certificate are valid.\n\tSignature() []byte\n\n\t// CheckSignature will check that the certificate Signature() matches the\n\t// computed signature. A true result means this certificate has not been tampered with.\n\tCheckSignature(signingPublicKey []byte) bool\n\n\t// Fingerprint returns the hex encoded sha256 sum of the certificate.\n\t// This acts as a unique fingerprint and can be used to blocklist certificates.\n\tFingerprint() (string, error)\n\n\t// Expired tests if the certificate is valid for the provided time.\n\tExpired(t time.Time) bool\n\n\t// VerifyPrivateKey returns an error if the private key is not a pair with the certificates public key.\n\tVerifyPrivateKey(curve Curve, privateKey []byte) error\n\n\t// Marshal will return the byte representation of this certificate\n\t// This is primarily the format transmitted on the wire.\n\tMarshal() ([]byte, error)\n\n\t// MarshalForHandshakes prepares the bytes needed to use directly in a handshake\n\tMarshalForHandshakes() ([]byte, error)\n\n\t// MarshalPEM will return a PEM encoded representation of this certificate\n\t// This is primarily the format stored on disk\n\tMarshalPEM() ([]byte, error)\n\n\t// MarshalJSON will return the json representation of this certificate\n\tMarshalJSON() ([]byte, error)\n\n\t// String will return a human-readable representation of this certificate\n\tString() string\n\n\t// Copy creates a copy of the certificate\n\tCopy() Certificate\n}\n\n// CachedCertificate represents a verified certificate with some cached fields to improve\n// performance.\ntype CachedCertificate struct {\n\tCertificate       Certificate\n\tInvertedGroups    map[string]struct{}\n\tFingerprint       string\n\tsignerFingerprint string\n\n\t// A place to store a 2nd fingerprint if the certificate could have one, such as with P256\n\tfingerprint2 string\n}\n\nfunc (cc *CachedCertificate) String() string {\n\treturn cc.Certificate.String()\n}\n\n// Recombine will attempt to unmarshal a certificate received in a handshake.\n// Handshakes save space by placing the peers public key in a different part of the packet, we have to\n// reassemble the actual certificate structure with that in mind.\n// Implementations MUST assert the public key is not in the raw certificate bytes if the passed in public key is not empty.\nfunc Recombine(v Version, rawCertBytes, publicKey []byte, curve Curve) (Certificate, error) {\n\tif publicKey == nil {\n\t\treturn nil, ErrNoPeerStaticKey\n\t}\n\n\tif rawCertBytes == nil {\n\t\treturn nil, ErrNoPayload\n\t}\n\n\tvar c Certificate\n\tvar err error\n\n\tswitch v {\n\t// Implementations must ensure the result is a valid cert!\n\tcase VersionPre1, Version1:\n\t\tc, err = unmarshalCertificateV1(rawCertBytes, publicKey)\n\tcase Version2:\n\t\tc, err = unmarshalCertificateV2(rawCertBytes, publicKey, curve)\n\tdefault:\n\t\treturn nil, ErrUnknownVersion\n\t}\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif c.Curve() != curve {\n\t\treturn nil, fmt.Errorf(\"certificate curve %s does not match expected %s\", c.Curve().String(), curve.String())\n\t}\n\n\treturn c, nil\n}\n\n// CalculateAlternateFingerprint calculates a 2nd fingerprint representation for P256 certificates\n// CAPool blocklist testing through `VerifyCertificate` and `VerifyCachedCertificate` automatically performs this step.\nfunc CalculateAlternateFingerprint(c Certificate) (string, error) {\n\tif c.Curve() != Curve_P256 {\n\t\treturn \"\", nil\n\t}\n\n\tnc := c.Copy()\n\tb, err := p256.Swap(nc.Signature())\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tswitch v := nc.(type) {\n\tcase *certificateV1:\n\t\terr = v.setSignature(b)\n\tcase *certificateV2:\n\t\terr = v.setSignature(b)\n\tdefault:\n\t\treturn \"\", ErrUnknownVersion\n\t}\n\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn nc.Fingerprint()\n}\n"
  },
  {
    "path": "cert/cert_v1.go",
    "content": "package cert\n\nimport (\n\t\"bytes\"\n\t\"crypto/ecdh\"\n\t\"crypto/ecdsa\"\n\t\"crypto/ed25519\"\n\t\"crypto/elliptic\"\n\t\"crypto/sha256\"\n\t\"encoding/binary\"\n\t\"encoding/hex\"\n\t\"encoding/json\"\n\t\"encoding/pem\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"time\"\n\n\t\"golang.org/x/crypto/curve25519\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\nconst publicKeyLen = 32\n\ntype certificateV1 struct {\n\tdetails   detailsV1\n\tsignature []byte\n}\n\ntype detailsV1 struct {\n\tname           string\n\tnetworks       []netip.Prefix\n\tunsafeNetworks []netip.Prefix\n\tgroups         []string\n\tnotBefore      time.Time\n\tnotAfter       time.Time\n\tpublicKey      []byte\n\tisCA           bool\n\tissuer         string\n\n\tcurve Curve\n}\n\ntype m = map[string]any\n\nfunc (c *certificateV1) Version() Version {\n\treturn Version1\n}\n\nfunc (c *certificateV1) Curve() Curve {\n\treturn c.details.curve\n}\n\nfunc (c *certificateV1) Groups() []string {\n\treturn c.details.groups\n}\n\nfunc (c *certificateV1) IsCA() bool {\n\treturn c.details.isCA\n}\n\nfunc (c *certificateV1) Issuer() string {\n\treturn c.details.issuer\n}\n\nfunc (c *certificateV1) Name() string {\n\treturn c.details.name\n}\n\nfunc (c *certificateV1) Networks() []netip.Prefix {\n\treturn c.details.networks\n}\n\nfunc (c *certificateV1) NotAfter() time.Time {\n\treturn c.details.notAfter\n}\n\nfunc (c *certificateV1) NotBefore() time.Time {\n\treturn c.details.notBefore\n}\n\nfunc (c *certificateV1) PublicKey() []byte {\n\treturn c.details.publicKey\n}\n\nfunc (c *certificateV1) MarshalPublicKeyPEM() []byte {\n\treturn marshalCertPublicKeyToPEM(c)\n}\n\nfunc (c *certificateV1) Signature() []byte {\n\treturn c.signature\n}\n\nfunc (c *certificateV1) UnsafeNetworks() []netip.Prefix {\n\treturn c.details.unsafeNetworks\n}\n\nfunc (c *certificateV1) Fingerprint() (string, error) {\n\tb, err := c.Marshal()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tsum := sha256.Sum256(b)\n\treturn hex.EncodeToString(sum[:]), nil\n}\n\nfunc (c *certificateV1) CheckSignature(key []byte) bool {\n\tb, err := proto.Marshal(c.getRawDetails())\n\tif err != nil {\n\t\treturn false\n\t}\n\tswitch c.details.curve {\n\tcase Curve_CURVE25519:\n\t\treturn ed25519.Verify(key, b, c.signature)\n\tcase Curve_P256:\n\t\tpubKey, err := ecdsa.ParseUncompressedPublicKey(elliptic.P256(), key)\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\t\thashed := sha256.Sum256(b)\n\t\treturn ecdsa.VerifyASN1(pubKey, hashed[:], c.signature)\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc (c *certificateV1) Expired(t time.Time) bool {\n\treturn c.details.notBefore.After(t) || c.details.notAfter.Before(t)\n}\n\nfunc (c *certificateV1) VerifyPrivateKey(curve Curve, key []byte) error {\n\tif curve != c.details.curve {\n\t\treturn fmt.Errorf(\"curve in cert and private key supplied don't match\")\n\t}\n\tif c.details.isCA {\n\t\tswitch curve {\n\t\tcase Curve_CURVE25519:\n\t\t\t// the call to PublicKey below will panic slice bounds out of range otherwise\n\t\t\tif len(key) != ed25519.PrivateKeySize {\n\t\t\t\treturn fmt.Errorf(\"key was not 64 bytes, is invalid ed25519 private key\")\n\t\t\t}\n\n\t\t\tif !ed25519.PublicKey(c.details.publicKey).Equal(ed25519.PrivateKey(key).Public()) {\n\t\t\t\treturn fmt.Errorf(\"public key in cert and private key supplied don't match\")\n\t\t\t}\n\t\tcase Curve_P256:\n\t\t\tprivkey, err := ecdh.P256().NewPrivateKey(key)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"cannot parse private key as P256: %w\", err)\n\t\t\t}\n\t\t\tpub := privkey.PublicKey().Bytes()\n\t\t\tif !bytes.Equal(pub, c.details.publicKey) {\n\t\t\t\treturn fmt.Errorf(\"public key in cert and private key supplied don't match\")\n\t\t\t}\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"invalid curve: %s\", curve)\n\t\t}\n\t\treturn nil\n\t}\n\n\tvar pub []byte\n\tswitch curve {\n\tcase Curve_CURVE25519:\n\t\tvar err error\n\t\tpub, err = curve25519.X25519(key, curve25519.Basepoint)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\tcase Curve_P256:\n\t\tprivkey, err := ecdh.P256().NewPrivateKey(key)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tpub = privkey.PublicKey().Bytes()\n\tdefault:\n\t\treturn fmt.Errorf(\"invalid curve: %s\", curve)\n\t}\n\tif !bytes.Equal(pub, c.details.publicKey) {\n\t\treturn fmt.Errorf(\"public key in cert and private key supplied don't match\")\n\t}\n\n\treturn nil\n}\n\n// getRawDetails marshals the raw details into protobuf ready struct\nfunc (c *certificateV1) getRawDetails() *RawNebulaCertificateDetails {\n\trd := &RawNebulaCertificateDetails{\n\t\tName:      c.details.name,\n\t\tGroups:    c.details.groups,\n\t\tNotBefore: c.details.notBefore.Unix(),\n\t\tNotAfter:  c.details.notAfter.Unix(),\n\t\tPublicKey: make([]byte, len(c.details.publicKey)),\n\t\tIsCA:      c.details.isCA,\n\t\tCurve:     c.details.curve,\n\t}\n\n\tfor _, ipNet := range c.details.networks {\n\t\tmask := net.CIDRMask(ipNet.Bits(), ipNet.Addr().BitLen())\n\t\trd.Ips = append(rd.Ips, addr2int(ipNet.Addr()), ip2int(mask))\n\t}\n\n\tfor _, ipNet := range c.details.unsafeNetworks {\n\t\tmask := net.CIDRMask(ipNet.Bits(), ipNet.Addr().BitLen())\n\t\trd.Subnets = append(rd.Subnets, addr2int(ipNet.Addr()), ip2int(mask))\n\t}\n\n\tcopy(rd.PublicKey, c.details.publicKey[:])\n\n\t// I know, this is terrible\n\trd.Issuer, _ = hex.DecodeString(c.details.issuer)\n\n\treturn rd\n}\n\nfunc (c *certificateV1) String() string {\n\tb, err := json.MarshalIndent(c.marshalJSON(), \"\", \"\\t\")\n\tif err != nil {\n\t\treturn fmt.Sprintf(\"<error marshalling certificate: %v>\", err)\n\t}\n\treturn string(b)\n}\n\nfunc (c *certificateV1) MarshalForHandshakes() ([]byte, error) {\n\tpubKey := c.details.publicKey\n\tc.details.publicKey = nil\n\trawCertNoKey, err := c.Marshal()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tc.details.publicKey = pubKey\n\treturn rawCertNoKey, nil\n}\n\nfunc (c *certificateV1) Marshal() ([]byte, error) {\n\trc := RawNebulaCertificate{\n\t\tDetails:   c.getRawDetails(),\n\t\tSignature: c.signature,\n\t}\n\n\treturn proto.Marshal(&rc)\n}\n\nfunc (c *certificateV1) MarshalPEM() ([]byte, error) {\n\tb, err := c.Marshal()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn pem.EncodeToMemory(&pem.Block{Type: CertificateBanner, Bytes: b}), nil\n}\n\nfunc (c *certificateV1) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(c.marshalJSON())\n}\n\nfunc (c *certificateV1) marshalJSON() m {\n\tfp, _ := c.Fingerprint()\n\treturn m{\n\t\t\"version\": Version1,\n\t\t\"details\": m{\n\t\t\t\"name\":           c.details.name,\n\t\t\t\"networks\":       c.details.networks,\n\t\t\t\"unsafeNetworks\": c.details.unsafeNetworks,\n\t\t\t\"groups\":         c.details.groups,\n\t\t\t\"notBefore\":      c.details.notBefore,\n\t\t\t\"notAfter\":       c.details.notAfter,\n\t\t\t\"publicKey\":      fmt.Sprintf(\"%x\", c.details.publicKey),\n\t\t\t\"isCa\":           c.details.isCA,\n\t\t\t\"issuer\":         c.details.issuer,\n\t\t\t\"curve\":          c.details.curve.String(),\n\t\t},\n\t\t\"fingerprint\": fp,\n\t\t\"signature\":   fmt.Sprintf(\"%x\", c.Signature()),\n\t}\n}\n\nfunc (c *certificateV1) Copy() Certificate {\n\tnc := &certificateV1{\n\t\tdetails: detailsV1{\n\t\t\tname:      c.details.name,\n\t\t\tnotBefore: c.details.notBefore,\n\t\t\tnotAfter:  c.details.notAfter,\n\t\t\tpublicKey: make([]byte, len(c.details.publicKey)),\n\t\t\tisCA:      c.details.isCA,\n\t\t\tissuer:    c.details.issuer,\n\t\t\tcurve:     c.details.curve,\n\t\t},\n\t\tsignature: make([]byte, len(c.signature)),\n\t}\n\n\tif c.details.groups != nil {\n\t\tnc.details.groups = make([]string, len(c.details.groups))\n\t\tcopy(nc.details.groups, c.details.groups)\n\t}\n\n\tif c.details.networks != nil {\n\t\tnc.details.networks = make([]netip.Prefix, len(c.details.networks))\n\t\tcopy(nc.details.networks, c.details.networks)\n\t}\n\n\tif c.details.unsafeNetworks != nil {\n\t\tnc.details.unsafeNetworks = make([]netip.Prefix, len(c.details.unsafeNetworks))\n\t\tcopy(nc.details.unsafeNetworks, c.details.unsafeNetworks)\n\t}\n\n\tcopy(nc.signature, c.signature)\n\tcopy(nc.details.publicKey, c.details.publicKey)\n\n\treturn nc\n}\n\nfunc (c *certificateV1) fromTBSCertificate(t *TBSCertificate) error {\n\tc.details = detailsV1{\n\t\tname:           t.Name,\n\t\tnetworks:       t.Networks,\n\t\tunsafeNetworks: t.UnsafeNetworks,\n\t\tgroups:         t.Groups,\n\t\tnotBefore:      t.NotBefore,\n\t\tnotAfter:       t.NotAfter,\n\t\tpublicKey:      t.PublicKey,\n\t\tisCA:           t.IsCA,\n\t\tcurve:          t.Curve,\n\t\tissuer:         t.issuer,\n\t}\n\n\treturn c.validate()\n}\n\nfunc (c *certificateV1) validate() error {\n\t// Empty names are allowed\n\n\tif len(c.details.publicKey) == 0 {\n\t\treturn ErrInvalidPublicKey\n\t}\n\n\t// Original v1 rules allowed multiple networks to be present but ignored all but the first one.\n\t// Continue to allow this behavior\n\tif !c.details.isCA && len(c.details.networks) == 0 {\n\t\treturn NewErrInvalidCertificateProperties(\"non-CA certificates must contain exactly one network\")\n\t}\n\n\tfor _, network := range c.details.networks {\n\t\tif !network.IsValid() || !network.Addr().IsValid() {\n\t\t\treturn NewErrInvalidCertificateProperties(\"invalid network: %s\", network)\n\t\t}\n\n\t\tif network.Addr().Is6() {\n\t\t\treturn NewErrInvalidCertificateProperties(\"certificate may not contain IPv6 networks: %v\", network)\n\t\t}\n\n\t\tif network.Addr().IsUnspecified() {\n\t\t\treturn NewErrInvalidCertificateProperties(\"non-CA certificates must not use the zero address as a network: %s\", network)\n\t\t}\n\n\t\tif network.Addr().Zone() != \"\" {\n\t\t\treturn NewErrInvalidCertificateProperties(\"networks may not contain zones: %s\", network)\n\t\t}\n\t}\n\n\tfor _, network := range c.details.unsafeNetworks {\n\t\tif !network.IsValid() || !network.Addr().IsValid() {\n\t\t\treturn NewErrInvalidCertificateProperties(\"invalid unsafe network: %s\", network)\n\t\t}\n\n\t\tif network.Addr().Is6() {\n\t\t\treturn NewErrInvalidCertificateProperties(\"certificate may not contain IPv6 unsafe networks: %v\", network)\n\t\t}\n\n\t\tif network.Addr().Zone() != \"\" {\n\t\t\treturn NewErrInvalidCertificateProperties(\"unsafe networks may not contain zones: %s\", network)\n\t\t}\n\t}\n\n\t// v1 doesn't bother with sort order or uniqueness of networks or unsafe networks.\n\t// We can't modify the unmarshalled data because verification requires re-marshalling and a re-ordered\n\t// unsafe networks would result in a different signature.\n\n\treturn nil\n}\n\nfunc (c *certificateV1) marshalForSigning() ([]byte, error) {\n\tb, err := proto.Marshal(c.getRawDetails())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn b, nil\n}\n\nfunc (c *certificateV1) setSignature(b []byte) error {\n\tif len(b) == 0 {\n\t\treturn ErrEmptySignature\n\t}\n\tc.signature = b\n\treturn nil\n}\n\n// unmarshalCertificateV1 will unmarshal a protobuf byte representation of a nebula cert\n// if the publicKey is provided here then it is not required to be present in `b`\nfunc unmarshalCertificateV1(b []byte, publicKey []byte) (*certificateV1, error) {\n\tif len(b) == 0 {\n\t\treturn nil, fmt.Errorf(\"nil byte array\")\n\t}\n\tvar rc RawNebulaCertificate\n\terr := proto.Unmarshal(b, &rc)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif rc.Details == nil {\n\t\treturn nil, fmt.Errorf(\"encoded Details was nil\")\n\t}\n\n\tif len(rc.Details.Ips)%2 != 0 {\n\t\treturn nil, fmt.Errorf(\"encoded IPs should be in pairs, an odd number was found\")\n\t}\n\n\tif len(rc.Details.Subnets)%2 != 0 {\n\t\treturn nil, fmt.Errorf(\"encoded Subnets should be in pairs, an odd number was found\")\n\t}\n\n\tnc := certificateV1{\n\t\tdetails: detailsV1{\n\t\t\tname:           rc.Details.Name,\n\t\t\tgroups:         make([]string, len(rc.Details.Groups)),\n\t\t\tnetworks:       make([]netip.Prefix, len(rc.Details.Ips)/2),\n\t\t\tunsafeNetworks: make([]netip.Prefix, len(rc.Details.Subnets)/2),\n\t\t\tnotBefore:      time.Unix(rc.Details.NotBefore, 0),\n\t\t\tnotAfter:       time.Unix(rc.Details.NotAfter, 0),\n\t\t\tpublicKey:      nil,\n\t\t\tisCA:           rc.Details.IsCA,\n\t\t\tcurve:          rc.Details.Curve,\n\t\t},\n\t\tsignature: make([]byte, len(rc.Signature)),\n\t}\n\n\tcopy(nc.signature, rc.Signature)\n\tcopy(nc.details.groups, rc.Details.Groups)\n\tnc.details.issuer = hex.EncodeToString(rc.Details.Issuer)\n\n\t// If a public key is passed in as an argument, the certificate pubkey must be empty\n\t// and the passed-in pubkey copied into the cert.\n\tif len(publicKey) > 0 {\n\t\tif len(rc.Details.PublicKey) != 0 {\n\t\t\treturn nil, ErrCertPubkeyPresent\n\t\t}\n\t\tnc.details.publicKey = make([]byte, len(publicKey))\n\t\tcopy(nc.details.publicKey, publicKey)\n\t} else {\n\t\tnc.details.publicKey = make([]byte, len(rc.Details.PublicKey))\n\t\tcopy(nc.details.publicKey, rc.Details.PublicKey)\n\t}\n\n\tvar ip netip.Addr\n\tfor i, rawIp := range rc.Details.Ips {\n\t\tif i%2 == 0 {\n\t\t\tip = int2addr(rawIp)\n\t\t} else {\n\t\t\tones, _ := net.IPMask(int2ip(rawIp)).Size()\n\t\t\tnc.details.networks[i/2] = netip.PrefixFrom(ip, ones)\n\t\t}\n\t}\n\n\tfor i, rawIp := range rc.Details.Subnets {\n\t\tif i%2 == 0 {\n\t\t\tip = int2addr(rawIp)\n\t\t} else {\n\t\t\tones, _ := net.IPMask(int2ip(rawIp)).Size()\n\t\t\tnc.details.unsafeNetworks[i/2] = netip.PrefixFrom(ip, ones)\n\t\t}\n\t}\n\n\terr = nc.validate()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &nc, nil\n}\n\nfunc ip2int(ip []byte) uint32 {\n\tif len(ip) == 16 {\n\t\treturn binary.BigEndian.Uint32(ip[12:16])\n\t}\n\treturn binary.BigEndian.Uint32(ip)\n}\n\nfunc int2ip(nn uint32) net.IP {\n\tip := make(net.IP, net.IPv4len)\n\tbinary.BigEndian.PutUint32(ip, nn)\n\treturn ip\n}\n\nfunc addr2int(addr netip.Addr) uint32 {\n\tb := addr.Unmap().As4()\n\treturn binary.BigEndian.Uint32(b[:])\n}\n\nfunc int2addr(nn uint32) netip.Addr {\n\tip := [4]byte{}\n\tbinary.BigEndian.PutUint32(ip[:], nn)\n\treturn netip.AddrFrom4(ip).Unmap()\n}\n"
  },
  {
    "path": "cert/cert_v1.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.34.2\n// \tprotoc        v3.21.5\n// source: cert_v1.proto\n\npackage cert\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Curve int32\n\nconst (\n\tCurve_CURVE25519 Curve = 0\n\tCurve_P256       Curve = 1\n)\n\n// Enum value maps for Curve.\nvar (\n\tCurve_name = map[int32]string{\n\t\t0: \"CURVE25519\",\n\t\t1: \"P256\",\n\t}\n\tCurve_value = map[string]int32{\n\t\t\"CURVE25519\": 0,\n\t\t\"P256\":       1,\n\t}\n)\n\nfunc (x Curve) Enum() *Curve {\n\tp := new(Curve)\n\t*p = x\n\treturn p\n}\n\nfunc (x Curve) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (Curve) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_cert_v1_proto_enumTypes[0].Descriptor()\n}\n\nfunc (Curve) Type() protoreflect.EnumType {\n\treturn &file_cert_v1_proto_enumTypes[0]\n}\n\nfunc (x Curve) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use Curve.Descriptor instead.\nfunc (Curve) EnumDescriptor() ([]byte, []int) {\n\treturn file_cert_v1_proto_rawDescGZIP(), []int{0}\n}\n\ntype RawNebulaCertificate struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tDetails   *RawNebulaCertificateDetails `protobuf:\"bytes,1,opt,name=Details,proto3\" json:\"Details,omitempty\"`\n\tSignature []byte                       `protobuf:\"bytes,2,opt,name=Signature,proto3\" json:\"Signature,omitempty\"`\n}\n\nfunc (x *RawNebulaCertificate) Reset() {\n\t*x = RawNebulaCertificate{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_cert_v1_proto_msgTypes[0]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *RawNebulaCertificate) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RawNebulaCertificate) ProtoMessage() {}\n\nfunc (x *RawNebulaCertificate) ProtoReflect() protoreflect.Message {\n\tmi := &file_cert_v1_proto_msgTypes[0]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RawNebulaCertificate.ProtoReflect.Descriptor instead.\nfunc (*RawNebulaCertificate) Descriptor() ([]byte, []int) {\n\treturn file_cert_v1_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *RawNebulaCertificate) GetDetails() *RawNebulaCertificateDetails {\n\tif x != nil {\n\t\treturn x.Details\n\t}\n\treturn nil\n}\n\nfunc (x *RawNebulaCertificate) GetSignature() []byte {\n\tif x != nil {\n\t\treturn x.Signature\n\t}\n\treturn nil\n}\n\ntype RawNebulaCertificateDetails struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tName string `protobuf:\"bytes,1,opt,name=Name,proto3\" json:\"Name,omitempty\"`\n\t// Ips and Subnets are in big endian 32 bit pairs, 1st the ip, 2nd the mask\n\tIps       []uint32 `protobuf:\"varint,2,rep,packed,name=Ips,proto3\" json:\"Ips,omitempty\"`\n\tSubnets   []uint32 `protobuf:\"varint,3,rep,packed,name=Subnets,proto3\" json:\"Subnets,omitempty\"`\n\tGroups    []string `protobuf:\"bytes,4,rep,name=Groups,proto3\" json:\"Groups,omitempty\"`\n\tNotBefore int64    `protobuf:\"varint,5,opt,name=NotBefore,proto3\" json:\"NotBefore,omitempty\"`\n\tNotAfter  int64    `protobuf:\"varint,6,opt,name=NotAfter,proto3\" json:\"NotAfter,omitempty\"`\n\tPublicKey []byte   `protobuf:\"bytes,7,opt,name=PublicKey,proto3\" json:\"PublicKey,omitempty\"`\n\tIsCA      bool     `protobuf:\"varint,8,opt,name=IsCA,proto3\" json:\"IsCA,omitempty\"`\n\t// sha-256 of the issuer certificate, if this field is blank the cert is self-signed\n\tIssuer []byte `protobuf:\"bytes,9,opt,name=Issuer,proto3\" json:\"Issuer,omitempty\"`\n\tCurve  Curve  `protobuf:\"varint,100,opt,name=curve,proto3,enum=cert.Curve\" json:\"curve,omitempty\"`\n}\n\nfunc (x *RawNebulaCertificateDetails) Reset() {\n\t*x = RawNebulaCertificateDetails{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_cert_v1_proto_msgTypes[1]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *RawNebulaCertificateDetails) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RawNebulaCertificateDetails) ProtoMessage() {}\n\nfunc (x *RawNebulaCertificateDetails) ProtoReflect() protoreflect.Message {\n\tmi := &file_cert_v1_proto_msgTypes[1]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RawNebulaCertificateDetails.ProtoReflect.Descriptor instead.\nfunc (*RawNebulaCertificateDetails) Descriptor() ([]byte, []int) {\n\treturn file_cert_v1_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *RawNebulaCertificateDetails) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *RawNebulaCertificateDetails) GetIps() []uint32 {\n\tif x != nil {\n\t\treturn x.Ips\n\t}\n\treturn nil\n}\n\nfunc (x *RawNebulaCertificateDetails) GetSubnets() []uint32 {\n\tif x != nil {\n\t\treturn x.Subnets\n\t}\n\treturn nil\n}\n\nfunc (x *RawNebulaCertificateDetails) GetGroups() []string {\n\tif x != nil {\n\t\treturn x.Groups\n\t}\n\treturn nil\n}\n\nfunc (x *RawNebulaCertificateDetails) GetNotBefore() int64 {\n\tif x != nil {\n\t\treturn x.NotBefore\n\t}\n\treturn 0\n}\n\nfunc (x *RawNebulaCertificateDetails) GetNotAfter() int64 {\n\tif x != nil {\n\t\treturn x.NotAfter\n\t}\n\treturn 0\n}\n\nfunc (x *RawNebulaCertificateDetails) GetPublicKey() []byte {\n\tif x != nil {\n\t\treturn x.PublicKey\n\t}\n\treturn nil\n}\n\nfunc (x *RawNebulaCertificateDetails) GetIsCA() bool {\n\tif x != nil {\n\t\treturn x.IsCA\n\t}\n\treturn false\n}\n\nfunc (x *RawNebulaCertificateDetails) GetIssuer() []byte {\n\tif x != nil {\n\t\treturn x.Issuer\n\t}\n\treturn nil\n}\n\nfunc (x *RawNebulaCertificateDetails) GetCurve() Curve {\n\tif x != nil {\n\t\treturn x.Curve\n\t}\n\treturn Curve_CURVE25519\n}\n\ntype RawNebulaEncryptedData struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tEncryptionMetadata *RawNebulaEncryptionMetadata `protobuf:\"bytes,1,opt,name=EncryptionMetadata,proto3\" json:\"EncryptionMetadata,omitempty\"`\n\tCiphertext         []byte                       `protobuf:\"bytes,2,opt,name=Ciphertext,proto3\" json:\"Ciphertext,omitempty\"`\n}\n\nfunc (x *RawNebulaEncryptedData) Reset() {\n\t*x = RawNebulaEncryptedData{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_cert_v1_proto_msgTypes[2]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *RawNebulaEncryptedData) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RawNebulaEncryptedData) ProtoMessage() {}\n\nfunc (x *RawNebulaEncryptedData) ProtoReflect() protoreflect.Message {\n\tmi := &file_cert_v1_proto_msgTypes[2]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RawNebulaEncryptedData.ProtoReflect.Descriptor instead.\nfunc (*RawNebulaEncryptedData) Descriptor() ([]byte, []int) {\n\treturn file_cert_v1_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *RawNebulaEncryptedData) GetEncryptionMetadata() *RawNebulaEncryptionMetadata {\n\tif x != nil {\n\t\treturn x.EncryptionMetadata\n\t}\n\treturn nil\n}\n\nfunc (x *RawNebulaEncryptedData) GetCiphertext() []byte {\n\tif x != nil {\n\t\treturn x.Ciphertext\n\t}\n\treturn nil\n}\n\ntype RawNebulaEncryptionMetadata struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tEncryptionAlgorithm string                     `protobuf:\"bytes,1,opt,name=EncryptionAlgorithm,proto3\" json:\"EncryptionAlgorithm,omitempty\"`\n\tArgon2Parameters    *RawNebulaArgon2Parameters `protobuf:\"bytes,2,opt,name=Argon2Parameters,proto3\" json:\"Argon2Parameters,omitempty\"`\n}\n\nfunc (x *RawNebulaEncryptionMetadata) Reset() {\n\t*x = RawNebulaEncryptionMetadata{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_cert_v1_proto_msgTypes[3]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *RawNebulaEncryptionMetadata) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RawNebulaEncryptionMetadata) ProtoMessage() {}\n\nfunc (x *RawNebulaEncryptionMetadata) ProtoReflect() protoreflect.Message {\n\tmi := &file_cert_v1_proto_msgTypes[3]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RawNebulaEncryptionMetadata.ProtoReflect.Descriptor instead.\nfunc (*RawNebulaEncryptionMetadata) Descriptor() ([]byte, []int) {\n\treturn file_cert_v1_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *RawNebulaEncryptionMetadata) GetEncryptionAlgorithm() string {\n\tif x != nil {\n\t\treturn x.EncryptionAlgorithm\n\t}\n\treturn \"\"\n}\n\nfunc (x *RawNebulaEncryptionMetadata) GetArgon2Parameters() *RawNebulaArgon2Parameters {\n\tif x != nil {\n\t\treturn x.Argon2Parameters\n\t}\n\treturn nil\n}\n\ntype RawNebulaArgon2Parameters struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tVersion     int32  `protobuf:\"varint,1,opt,name=version,proto3\" json:\"version,omitempty\"` // rune in Go\n\tMemory      uint32 `protobuf:\"varint,2,opt,name=memory,proto3\" json:\"memory,omitempty\"`\n\tParallelism uint32 `protobuf:\"varint,4,opt,name=parallelism,proto3\" json:\"parallelism,omitempty\"` // uint8 in Go\n\tIterations  uint32 `protobuf:\"varint,3,opt,name=iterations,proto3\" json:\"iterations,omitempty\"`\n\tSalt        []byte `protobuf:\"bytes,5,opt,name=salt,proto3\" json:\"salt,omitempty\"`\n}\n\nfunc (x *RawNebulaArgon2Parameters) Reset() {\n\t*x = RawNebulaArgon2Parameters{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_cert_v1_proto_msgTypes[4]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *RawNebulaArgon2Parameters) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RawNebulaArgon2Parameters) ProtoMessage() {}\n\nfunc (x *RawNebulaArgon2Parameters) ProtoReflect() protoreflect.Message {\n\tmi := &file_cert_v1_proto_msgTypes[4]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RawNebulaArgon2Parameters.ProtoReflect.Descriptor instead.\nfunc (*RawNebulaArgon2Parameters) Descriptor() ([]byte, []int) {\n\treturn file_cert_v1_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *RawNebulaArgon2Parameters) GetVersion() int32 {\n\tif x != nil {\n\t\treturn x.Version\n\t}\n\treturn 0\n}\n\nfunc (x *RawNebulaArgon2Parameters) GetMemory() uint32 {\n\tif x != nil {\n\t\treturn x.Memory\n\t}\n\treturn 0\n}\n\nfunc (x *RawNebulaArgon2Parameters) GetParallelism() uint32 {\n\tif x != nil {\n\t\treturn x.Parallelism\n\t}\n\treturn 0\n}\n\nfunc (x *RawNebulaArgon2Parameters) GetIterations() uint32 {\n\tif x != nil {\n\t\treturn x.Iterations\n\t}\n\treturn 0\n}\n\nfunc (x *RawNebulaArgon2Parameters) GetSalt() []byte {\n\tif x != nil {\n\t\treturn x.Salt\n\t}\n\treturn nil\n}\n\nvar File_cert_v1_proto protoreflect.FileDescriptor\n\nvar file_cert_v1_proto_rawDesc = []byte{\n\t0x0a, 0x0d, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x76, 0x31, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,\n\t0x04, 0x63, 0x65, 0x72, 0x74, 0x22, 0x71, 0x0a, 0x14, 0x52, 0x61, 0x77, 0x4e, 0x65, 0x62, 0x75,\n\t0x6c, 0x61, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x3b, 0x0a,\n\t0x07, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21,\n\t0x2e, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x52, 0x61, 0x77, 0x4e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x43,\n\t0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c,\n\t0x73, 0x52, 0x07, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x53, 0x69,\n\t0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x53,\n\t0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x9c, 0x02, 0x0a, 0x1b, 0x52, 0x61, 0x77,\n\t0x4e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,\n\t0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03,\n\t0x49, 0x70, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x03, 0x49, 0x70, 0x73, 0x12, 0x18,\n\t0x0a, 0x07, 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0d, 0x52,\n\t0x07, 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x47, 0x72, 0x6f, 0x75,\n\t0x70, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73,\n\t0x12, 0x1c, 0x0a, 0x09, 0x4e, 0x6f, 0x74, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x18, 0x05, 0x20,\n\t0x01, 0x28, 0x03, 0x52, 0x09, 0x4e, 0x6f, 0x74, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x12, 0x1a,\n\t0x0a, 0x08, 0x4e, 0x6f, 0x74, 0x41, 0x66, 0x74, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03,\n\t0x52, 0x08, 0x4e, 0x6f, 0x74, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x75,\n\t0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x50,\n\t0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x49, 0x73, 0x43, 0x41,\n\t0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x49, 0x73, 0x43, 0x41, 0x12, 0x16, 0x0a, 0x06,\n\t0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x49, 0x73,\n\t0x73, 0x75, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x05, 0x63, 0x75, 0x72, 0x76, 0x65, 0x18, 0x64, 0x20,\n\t0x01, 0x28, 0x0e, 0x32, 0x0b, 0x2e, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x43, 0x75, 0x72, 0x76, 0x65,\n\t0x52, 0x05, 0x63, 0x75, 0x72, 0x76, 0x65, 0x22, 0x8b, 0x01, 0x0a, 0x16, 0x52, 0x61, 0x77, 0x4e,\n\t0x65, 0x62, 0x75, 0x6c, 0x61, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61,\n\t0x74, 0x61, 0x12, 0x51, 0x0a, 0x12, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e,\n\t0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21,\n\t0x2e, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x52, 0x61, 0x77, 0x4e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x45,\n\t0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,\n\t0x61, 0x52, 0x12, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74,\n\t0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1e, 0x0a, 0x0a, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x74,\n\t0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x43, 0x69, 0x70, 0x68, 0x65,\n\t0x72, 0x74, 0x65, 0x78, 0x74, 0x22, 0x9c, 0x01, 0x0a, 0x1b, 0x52, 0x61, 0x77, 0x4e, 0x65, 0x62,\n\t0x75, 0x6c, 0x61, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74,\n\t0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x30, 0x0a, 0x13, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74,\n\t0x69, 0x6f, 0x6e, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x18, 0x01, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x13, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x6c,\n\t0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x12, 0x4b, 0x0a, 0x10, 0x41, 0x72, 0x67, 0x6f, 0x6e,\n\t0x32, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28,\n\t0x0b, 0x32, 0x1f, 0x2e, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x52, 0x61, 0x77, 0x4e, 0x65, 0x62, 0x75,\n\t0x6c, 0x61, 0x41, 0x72, 0x67, 0x6f, 0x6e, 0x32, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65,\n\t0x72, 0x73, 0x52, 0x10, 0x41, 0x72, 0x67, 0x6f, 0x6e, 0x32, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65,\n\t0x74, 0x65, 0x72, 0x73, 0x22, 0xa3, 0x01, 0x0a, 0x19, 0x52, 0x61, 0x77, 0x4e, 0x65, 0x62, 0x75,\n\t0x6c, 0x61, 0x41, 0x72, 0x67, 0x6f, 0x6e, 0x32, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65,\n\t0x72, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20,\n\t0x01, 0x28, 0x05, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06,\n\t0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6d, 0x65,\n\t0x6d, 0x6f, 0x72, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c,\n\t0x69, 0x73, 0x6d, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x70, 0x61, 0x72, 0x61, 0x6c,\n\t0x6c, 0x65, 0x6c, 0x69, 0x73, 0x6d, 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74,\n\t0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x69, 0x74, 0x65, 0x72,\n\t0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x61, 0x6c, 0x74, 0x18, 0x05,\n\t0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x73, 0x61, 0x6c, 0x74, 0x2a, 0x21, 0x0a, 0x05, 0x43, 0x75,\n\t0x72, 0x76, 0x65, 0x12, 0x0e, 0x0a, 0x0a, 0x43, 0x55, 0x52, 0x56, 0x45, 0x32, 0x35, 0x35, 0x31,\n\t0x39, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x50, 0x32, 0x35, 0x36, 0x10, 0x01, 0x42, 0x20, 0x5a,\n\t0x1e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x6c, 0x61, 0x63,\n\t0x6b, 0x68, 0x71, 0x2f, 0x6e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x62,\n\t0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n}\n\nvar (\n\tfile_cert_v1_proto_rawDescOnce sync.Once\n\tfile_cert_v1_proto_rawDescData = file_cert_v1_proto_rawDesc\n)\n\nfunc file_cert_v1_proto_rawDescGZIP() []byte {\n\tfile_cert_v1_proto_rawDescOnce.Do(func() {\n\t\tfile_cert_v1_proto_rawDescData = protoimpl.X.CompressGZIP(file_cert_v1_proto_rawDescData)\n\t})\n\treturn file_cert_v1_proto_rawDescData\n}\n\nvar file_cert_v1_proto_enumTypes = make([]protoimpl.EnumInfo, 1)\nvar file_cert_v1_proto_msgTypes = make([]protoimpl.MessageInfo, 5)\nvar file_cert_v1_proto_goTypes = []any{\n\t(Curve)(0),                          // 0: cert.Curve\n\t(*RawNebulaCertificate)(nil),        // 1: cert.RawNebulaCertificate\n\t(*RawNebulaCertificateDetails)(nil), // 2: cert.RawNebulaCertificateDetails\n\t(*RawNebulaEncryptedData)(nil),      // 3: cert.RawNebulaEncryptedData\n\t(*RawNebulaEncryptionMetadata)(nil), // 4: cert.RawNebulaEncryptionMetadata\n\t(*RawNebulaArgon2Parameters)(nil),   // 5: cert.RawNebulaArgon2Parameters\n}\nvar file_cert_v1_proto_depIdxs = []int32{\n\t2, // 0: cert.RawNebulaCertificate.Details:type_name -> cert.RawNebulaCertificateDetails\n\t0, // 1: cert.RawNebulaCertificateDetails.curve:type_name -> cert.Curve\n\t4, // 2: cert.RawNebulaEncryptedData.EncryptionMetadata:type_name -> cert.RawNebulaEncryptionMetadata\n\t5, // 3: cert.RawNebulaEncryptionMetadata.Argon2Parameters:type_name -> cert.RawNebulaArgon2Parameters\n\t4, // [4:4] is the sub-list for method output_type\n\t4, // [4:4] is the sub-list for method input_type\n\t4, // [4:4] is the sub-list for extension type_name\n\t4, // [4:4] is the sub-list for extension extendee\n\t0, // [0:4] is the sub-list for field type_name\n}\n\nfunc init() { file_cert_v1_proto_init() }\nfunc file_cert_v1_proto_init() {\n\tif File_cert_v1_proto != nil {\n\t\treturn\n\t}\n\tif !protoimpl.UnsafeEnabled {\n\t\tfile_cert_v1_proto_msgTypes[0].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*RawNebulaCertificate); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_cert_v1_proto_msgTypes[1].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*RawNebulaCertificateDetails); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_cert_v1_proto_msgTypes[2].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*RawNebulaEncryptedData); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_cert_v1_proto_msgTypes[3].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*RawNebulaEncryptionMetadata); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_cert_v1_proto_msgTypes[4].Exporter = func(v any, i int) any {\n\t\t\tswitch v := v.(*RawNebulaArgon2Parameters); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: file_cert_v1_proto_rawDesc,\n\t\t\tNumEnums:      1,\n\t\t\tNumMessages:   5,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_cert_v1_proto_goTypes,\n\t\tDependencyIndexes: file_cert_v1_proto_depIdxs,\n\t\tEnumInfos:         file_cert_v1_proto_enumTypes,\n\t\tMessageInfos:      file_cert_v1_proto_msgTypes,\n\t}.Build()\n\tFile_cert_v1_proto = out.File\n\tfile_cert_v1_proto_rawDesc = nil\n\tfile_cert_v1_proto_goTypes = nil\n\tfile_cert_v1_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "cert/cert_v1.proto",
    "content": "syntax = \"proto3\";\npackage cert;\n\noption go_package = \"github.com/slackhq/nebula/cert\";\n\n//import \"google/protobuf/timestamp.proto\";\n\nenum Curve {\n  CURVE25519 = 0;\n  P256 = 1;\n}\n\nmessage RawNebulaCertificate {\n    RawNebulaCertificateDetails Details = 1;\n    bytes Signature = 2;\n}\n\nmessage RawNebulaCertificateDetails {\n    string Name = 1;\n\n    // Ips and Subnets are in big endian 32 bit pairs, 1st the ip, 2nd the mask\n    repeated uint32 Ips = 2;\n    repeated uint32 Subnets = 3;\n\n    repeated string Groups = 4;\n    int64 NotBefore = 5;\n    int64 NotAfter = 6;\n    bytes PublicKey = 7;\n\n    bool IsCA = 8;\n\n    // sha-256 of the issuer certificate, if this field is blank the cert is self-signed\n    bytes Issuer = 9;\n\n    Curve curve = 100;\n}\n\nmessage RawNebulaEncryptedData {\n\tRawNebulaEncryptionMetadata EncryptionMetadata = 1;\n\tbytes Ciphertext = 2;\n}\n\nmessage RawNebulaEncryptionMetadata {\n\tstring EncryptionAlgorithm = 1;\n\tRawNebulaArgon2Parameters Argon2Parameters = 2;\n}\n\nmessage RawNebulaArgon2Parameters {\n\tint32 version = 1; // rune in Go\n\tuint32 memory = 2;\n\tuint32 parallelism = 4; // uint8 in Go\n\tuint32 iterations = 3;\n\tbytes salt = 5;\n}\n"
  },
  {
    "path": "cert/cert_v1_test.go",
    "content": "package cert\n\nimport (\n\t\"crypto/ed25519\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/slackhq/nebula/test\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\nfunc TestCertificateV1_Marshal(t *testing.T) {\n\tt.Parallel()\n\tbefore := time.Now().Add(time.Second * -60).Round(time.Second)\n\tafter := time.Now().Add(time.Second * 60).Round(time.Second)\n\tpubKey := []byte(\"1234567890abcedfghij1234567890ab\")\n\n\tnc := certificateV1{\n\t\tdetails: detailsV1{\n\t\t\tname: \"testing\",\n\t\t\tnetworks: []netip.Prefix{\n\t\t\t\tmustParsePrefixUnmapped(\"10.1.1.1/24\"),\n\t\t\t\tmustParsePrefixUnmapped(\"10.1.1.2/16\"),\n\t\t\t},\n\t\t\tunsafeNetworks: []netip.Prefix{\n\t\t\t\tmustParsePrefixUnmapped(\"9.1.1.2/24\"),\n\t\t\t\tmustParsePrefixUnmapped(\"9.1.1.3/16\"),\n\t\t\t},\n\t\t\tgroups:    []string{\"test-group1\", \"test-group2\", \"test-group3\"},\n\t\t\tnotBefore: before,\n\t\t\tnotAfter:  after,\n\t\t\tpublicKey: pubKey,\n\t\t\tisCA:      false,\n\t\t\tissuer:    \"1234567890abcedfghij1234567890ab\",\n\t\t},\n\t\tsignature: []byte(\"1234567890abcedfghij1234567890ab\"),\n\t}\n\n\tb, err := nc.Marshal()\n\trequire.NoError(t, err)\n\t//t.Log(\"Cert size:\", len(b))\n\n\tnc2, err := unmarshalCertificateV1(b, nil)\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, Version1, nc.Version())\n\tassert.Equal(t, Curve_CURVE25519, nc.Curve())\n\tassert.Equal(t, nc.Signature(), nc2.Signature())\n\tassert.Equal(t, nc.Name(), nc2.Name())\n\tassert.Equal(t, nc.NotBefore(), nc2.NotBefore())\n\tassert.Equal(t, nc.NotAfter(), nc2.NotAfter())\n\tassert.Equal(t, nc.PublicKey(), nc2.PublicKey())\n\tassert.Equal(t, nc.IsCA(), nc2.IsCA())\n\n\tassert.Equal(t, nc.Networks(), nc2.Networks())\n\tassert.Equal(t, nc.UnsafeNetworks(), nc2.UnsafeNetworks())\n\n\tassert.Equal(t, nc.Groups(), nc2.Groups())\n}\n\nfunc TestCertificateV1_Unmarshal(t *testing.T) {\n\tt.Parallel()\n\tbefore := time.Now().Add(time.Second * -60).Round(time.Second)\n\tafter := time.Now().Add(time.Second * 60).Round(time.Second)\n\tpubKey := []byte(\"1234567890abcedfghij1234567890ab\")\n\tinvalidPubkey := []byte(\"00000000000000000000000000000000\")\n\n\tnc := certificateV1{\n\t\tdetails: detailsV1{\n\t\t\tname: \"testing\",\n\t\t\tnetworks: []netip.Prefix{\n\t\t\t\tmustParsePrefixUnmapped(\"10.1.1.1/24\"),\n\t\t\t\tmustParsePrefixUnmapped(\"10.1.1.2/16\"),\n\t\t\t},\n\t\t\tunsafeNetworks: []netip.Prefix{\n\t\t\t\tmustParsePrefixUnmapped(\"9.1.1.2/24\"),\n\t\t\t\tmustParsePrefixUnmapped(\"9.1.1.3/16\"),\n\t\t\t},\n\t\t\tgroups:    []string{\"test-group1\", \"test-group2\", \"test-group3\"},\n\t\t\tnotBefore: before,\n\t\t\tnotAfter:  after,\n\t\t\tpublicKey: pubKey,\n\t\t\tisCA:      false,\n\t\t\tissuer:    \"1234567890abcedfghij1234567890ab\",\n\t\t},\n\t\tsignature: []byte(\"1234567890abcedfghij1234567890ab\"),\n\t}\n\n\t// This certificate has a pubkey included\n\tcertWithPubkey, err := nc.Marshal()\n\trequire.NoError(t, err)\n\n\t// This certificate is missing the pubkey section\n\tcertWithoutPubkey, err := nc.MarshalForHandshakes()\n\trequire.NoError(t, err)\n\n\t// Cert has no pubkey and no pubkey passed in must fail to validate\n\tisNil, err := unmarshalCertificateV1(certWithoutPubkey, nil)\n\trequire.Error(t, err)\n\n\t// Cert has different pubkey than one passed in must fail\n\tisNil, err = unmarshalCertificateV1(certWithPubkey, invalidPubkey)\n\trequire.Nil(t, isNil)\n\trequire.Error(t, err)\n\n\t// Cert has pubkey and no pubkey argument works ok\n\t_, err = unmarshalCertificateV1(certWithPubkey, nil)\n\trequire.NoError(t, err)\n\n\t// Cert has no pubkey and valid, correctly signed pubkey passed in\n\tnc2, err := unmarshalCertificateV1(certWithoutPubkey, pubKey)\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, pubKey, nc2.PublicKey())\n}\n\nfunc TestCertificateV1_PublicKeyPem(t *testing.T) {\n\tt.Parallel()\n\tbefore := time.Now().Add(time.Second * -60).Round(time.Second)\n\tafter := time.Now().Add(time.Second * 60).Round(time.Second)\n\tpubKey := ed25519.PublicKey(\"1234567890abcedfghij1234567890ab\")\n\n\tnc := certificateV1{\n\t\tdetails: detailsV1{\n\t\t\tname:           \"testing\",\n\t\t\tnetworks:       []netip.Prefix{},\n\t\t\tunsafeNetworks: []netip.Prefix{},\n\t\t\tgroups:         []string{\"test-group1\", \"test-group2\", \"test-group3\"},\n\t\t\tnotBefore:      before,\n\t\t\tnotAfter:       after,\n\t\t\tpublicKey:      pubKey,\n\t\t\tisCA:           false,\n\t\t\tissuer:         \"1234567890abcedfghij1234567890ab\",\n\t\t},\n\t\tsignature: []byte(\"1234567890abcedfghij1234567890ab\"),\n\t}\n\n\tassert.Equal(t, Version1, nc.Version())\n\tassert.Equal(t, Curve_CURVE25519, nc.Curve())\n\tpubPem := \"-----BEGIN NEBULA X25519 PUBLIC KEY-----\\nMTIzNDU2Nzg5MGFiY2VkZmdoaWoxMjM0NTY3ODkwYWI=\\n-----END NEBULA X25519 PUBLIC KEY-----\\n\"\n\tassert.Equal(t, string(nc.MarshalPublicKeyPEM()), pubPem)\n\tassert.False(t, nc.IsCA())\n\n\tnc.details.isCA = true\n\tassert.Equal(t, Curve_CURVE25519, nc.Curve())\n\tpubPem = \"-----BEGIN NEBULA ED25519 PUBLIC KEY-----\\nMTIzNDU2Nzg5MGFiY2VkZmdoaWoxMjM0NTY3ODkwYWI=\\n-----END NEBULA ED25519 PUBLIC KEY-----\\n\"\n\tassert.Equal(t, string(nc.MarshalPublicKeyPEM()), pubPem)\n\tassert.True(t, nc.IsCA())\n\n\tpubP256KeyPem := []byte(`-----BEGIN NEBULA P256 PUBLIC KEY-----\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAA=\n-----END NEBULA P256 PUBLIC KEY-----\n`)\n\n\tpubP256KeyPemCA := []byte(`-----BEGIN NEBULA ECDSA P256 PUBLIC KEY-----\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAA=\n-----END NEBULA ECDSA P256 PUBLIC KEY-----\n`)\n\tpubP256Key, _, _, err := UnmarshalPublicKeyFromPEM(pubP256KeyPem)\n\trequire.NoError(t, err)\n\tnc.details.curve = Curve_P256\n\tnc.details.publicKey = pubP256Key\n\tassert.Equal(t, Curve_P256, nc.Curve())\n\tassert.Equal(t, string(nc.MarshalPublicKeyPEM()), string(pubP256KeyPemCA))\n\tassert.True(t, nc.IsCA())\n\n\tnc.details.isCA = false\n\tassert.Equal(t, Curve_P256, nc.Curve())\n\tassert.Equal(t, string(nc.MarshalPublicKeyPEM()), string(pubP256KeyPem))\n\tassert.False(t, nc.IsCA())\n}\n\nfunc TestCertificateV1_Expired(t *testing.T) {\n\tnc := certificateV1{\n\t\tdetails: detailsV1{\n\t\t\tnotBefore: time.Now().Add(time.Second * -60).Round(time.Second),\n\t\t\tnotAfter:  time.Now().Add(time.Second * 60).Round(time.Second),\n\t\t},\n\t}\n\n\tassert.True(t, nc.Expired(time.Now().Add(time.Hour)))\n\tassert.True(t, nc.Expired(time.Now().Add(-time.Hour)))\n\tassert.False(t, nc.Expired(time.Now()))\n}\n\nfunc TestCertificateV1_MarshalJSON(t *testing.T) {\n\ttime.Local = time.UTC\n\tpubKey := []byte(\"1234567890abcedfghij1234567890ab\")\n\n\tnc := certificateV1{\n\t\tdetails: detailsV1{\n\t\t\tname: \"testing\",\n\t\t\tnetworks: []netip.Prefix{\n\t\t\t\tmustParsePrefixUnmapped(\"10.1.1.1/24\"),\n\t\t\t\tmustParsePrefixUnmapped(\"10.1.1.2/16\"),\n\t\t\t},\n\t\t\tunsafeNetworks: []netip.Prefix{\n\t\t\t\tmustParsePrefixUnmapped(\"9.1.1.2/24\"),\n\t\t\t\tmustParsePrefixUnmapped(\"9.1.1.3/16\"),\n\t\t\t},\n\t\t\tgroups:    []string{\"test-group1\", \"test-group2\", \"test-group3\"},\n\t\t\tnotBefore: time.Date(1, 0, 0, 1, 0, 0, 0, time.UTC),\n\t\t\tnotAfter:  time.Date(1, 0, 0, 2, 0, 0, 0, time.UTC),\n\t\t\tpublicKey: pubKey,\n\t\t\tisCA:      false,\n\t\t\tissuer:    \"1234567890abcedfghij1234567890ab\",\n\t\t},\n\t\tsignature: []byte(\"1234567890abcedfghij1234567890ab\"),\n\t}\n\n\tb, err := nc.MarshalJSON()\n\trequire.NoError(t, err)\n\tassert.JSONEq(\n\t\tt,\n\t\t\"{\\\"details\\\":{\\\"curve\\\":\\\"CURVE25519\\\",\\\"groups\\\":[\\\"test-group1\\\",\\\"test-group2\\\",\\\"test-group3\\\"],\\\"isCa\\\":false,\\\"issuer\\\":\\\"1234567890abcedfghij1234567890ab\\\",\\\"name\\\":\\\"testing\\\",\\\"networks\\\":[\\\"10.1.1.1/24\\\",\\\"10.1.1.2/16\\\"],\\\"notAfter\\\":\\\"0000-11-30T02:00:00Z\\\",\\\"notBefore\\\":\\\"0000-11-30T01:00:00Z\\\",\\\"publicKey\\\":\\\"313233343536373839306162636564666768696a313233343536373839306162\\\",\\\"unsafeNetworks\\\":[\\\"9.1.1.2/24\\\",\\\"9.1.1.3/16\\\"]},\\\"fingerprint\\\":\\\"3944c53d4267a229295b56cb2d27d459164c010ac97d655063ba421e0670f4ba\\\",\\\"signature\\\":\\\"313233343536373839306162636564666768696a313233343536373839306162\\\",\\\"version\\\":1}\",\n\t\tstring(b),\n\t)\n}\n\nfunc TestCertificateV1_VerifyPrivateKey(t *testing.T) {\n\tca, _, caKey, _ := NewTestCaCert(Version1, Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil)\n\terr := ca.VerifyPrivateKey(Curve_CURVE25519, caKey)\n\trequire.NoError(t, err)\n\n\t_, _, caKey2, _ := NewTestCaCert(Version1, Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil)\n\trequire.NoError(t, err)\n\terr = ca.VerifyPrivateKey(Curve_CURVE25519, caKey2)\n\trequire.Error(t, err)\n\n\tc, _, priv, _ := NewTestCert(Version1, Curve_CURVE25519, ca, caKey, \"test\", time.Time{}, time.Time{}, nil, nil, nil)\n\trawPriv, b, curve, err := UnmarshalPrivateKeyFromPEM(priv)\n\trequire.NoError(t, err)\n\tassert.Empty(t, b)\n\tassert.Equal(t, Curve_CURVE25519, curve)\n\terr = c.VerifyPrivateKey(Curve_CURVE25519, rawPriv)\n\trequire.NoError(t, err)\n\n\t_, priv2 := X25519Keypair()\n\terr = c.VerifyPrivateKey(Curve_CURVE25519, priv2)\n\trequire.Error(t, err)\n}\n\nfunc TestCertificateV1_VerifyPrivateKeyP256(t *testing.T) {\n\tca, _, caKey, _ := NewTestCaCert(Version1, Curve_P256, time.Time{}, time.Time{}, nil, nil, nil)\n\terr := ca.VerifyPrivateKey(Curve_P256, caKey)\n\trequire.NoError(t, err)\n\n\t_, _, caKey2, _ := NewTestCaCert(Version1, Curve_P256, time.Time{}, time.Time{}, nil, nil, nil)\n\trequire.NoError(t, err)\n\terr = ca.VerifyPrivateKey(Curve_P256, caKey2)\n\trequire.Error(t, err)\n\n\tc, _, priv, _ := NewTestCert(Version1, Curve_P256, ca, caKey, \"test\", time.Time{}, time.Time{}, nil, nil, nil)\n\trawPriv, b, curve, err := UnmarshalPrivateKeyFromPEM(priv)\n\trequire.NoError(t, err)\n\tassert.Empty(t, b)\n\tassert.Equal(t, Curve_P256, curve)\n\terr = c.VerifyPrivateKey(Curve_P256, rawPriv)\n\trequire.NoError(t, err)\n\n\t_, priv2 := P256Keypair()\n\terr = c.VerifyPrivateKey(Curve_P256, priv2)\n\trequire.Error(t, err)\n}\n\n// Ensure that upgrading the protobuf library does not change how certificates\n// are marshalled, since this would break signature verification\nfunc TestMarshalingCertificateV1Consistency(t *testing.T) {\n\tbefore := time.Date(1970, time.January, 1, 1, 1, 1, 1, time.UTC)\n\tafter := time.Date(9999, time.January, 1, 1, 1, 1, 1, time.UTC)\n\tpubKey := []byte(\"1234567890abcedfghij1234567890ab\")\n\n\tnc := certificateV1{\n\t\tdetails: detailsV1{\n\t\t\tname: \"testing\",\n\t\t\tnetworks: []netip.Prefix{\n\t\t\t\tmustParsePrefixUnmapped(\"10.1.1.2/16\"),\n\t\t\t\tmustParsePrefixUnmapped(\"10.1.1.1/24\"),\n\t\t\t},\n\t\t\tunsafeNetworks: []netip.Prefix{\n\t\t\t\tmustParsePrefixUnmapped(\"9.1.1.3/16\"),\n\t\t\t\tmustParsePrefixUnmapped(\"9.1.1.2/24\"),\n\t\t\t},\n\t\t\tgroups:    []string{\"test-group1\", \"test-group2\", \"test-group3\"},\n\t\t\tnotBefore: before,\n\t\t\tnotAfter:  after,\n\t\t\tpublicKey: pubKey,\n\t\t\tisCA:      false,\n\t\t\tissuer:    \"1234567890abcedfghij1234567890ab\",\n\t\t},\n\t\tsignature: []byte(\"1234567890abcedfghij1234567890ab\"),\n\t}\n\n\tb, err := nc.Marshal()\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"0a8e010a0774657374696e671212828284508080fcff0f8182845080feffff0f1a12838284488080fcff0f8282844880feffff0f220b746573742d67726f757031220b746573742d67726f757032220b746573742d67726f75703328cd1c30cdb8ccf0af073a20313233343536373839306162636564666768696a3132333435363738393061624a081234567890abcedf1220313233343536373839306162636564666768696a313233343536373839306162\", fmt.Sprintf(\"%x\", b))\n\n\tb, err = proto.Marshal(nc.getRawDetails())\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"0a0774657374696e671212828284508080fcff0f8182845080feffff0f1a12838284488080fcff0f8282844880feffff0f220b746573742d67726f757031220b746573742d67726f757032220b746573742d67726f75703328cd1c30cdb8ccf0af073a20313233343536373839306162636564666768696a3132333435363738393061624a081234567890abcedf\", fmt.Sprintf(\"%x\", b))\n}\n\nfunc TestCertificateV1_Copy(t *testing.T) {\n\tca, _, caKey, _ := NewTestCaCert(Version1, Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, nil)\n\tc, _, _, _ := NewTestCert(Version1, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), nil, nil, nil)\n\tcc := c.Copy()\n\ttest.AssertDeepCopyEqual(t, c, cc)\n}\n\nfunc TestUnmarshalCertificateV1(t *testing.T) {\n\t// Test that we don't panic with an invalid certificate (#332)\n\tdata := []byte(\"\\x98\\x00\\x00\")\n\t_, err := unmarshalCertificateV1(data, nil)\n\trequire.EqualError(t, err, \"encoded Details was nil\")\n}\n\nfunc appendByteSlices(b ...[]byte) []byte {\n\tretSlice := []byte{}\n\tfor _, v := range b {\n\t\tretSlice = append(retSlice, v...)\n\t}\n\treturn retSlice\n}\n\nfunc mustParsePrefixUnmapped(s string) netip.Prefix {\n\tprefix := netip.MustParsePrefix(s)\n\treturn netip.PrefixFrom(prefix.Addr().Unmap(), prefix.Bits())\n}\n"
  },
  {
    "path": "cert/cert_v2.asn1",
    "content": "Nebula DEFINITIONS AUTOMATIC TAGS ::= BEGIN\n\nName ::= UTF8String (SIZE (1..253))\nTime ::= INTEGER (0..18446744073709551615) -- Seconds since unix epoch, uint64 maximum\nNetwork ::= OCTET STRING (SIZE (5,17)) -- IP addresses are 4 or 16 bytes + 1 byte for the prefix length\nCurve ::= ENUMERATED {\n    curve25519 (0),\n    p256 (1)\n}\n\n-- The maximum size of a certificate must not exceed 65536 bytes\nCertificate ::= SEQUENCE {\n    details OCTET STRING,\n    curve Curve DEFAULT curve25519,\n    publicKey OCTET STRING,\n    -- signature(details + curve + publicKey) using the appropriate method for curve\n    signature OCTET STRING\n}\n\nDetails ::= SEQUENCE {\n    name Name,\n\n    -- At least 1 ipv4 or ipv6 address must be present if isCA is false\n    networks SEQUENCE OF Network OPTIONAL,\n    unsafeNetworks SEQUENCE OF Network OPTIONAL,\n    groups SEQUENCE OF Name OPTIONAL,\n    isCA BOOLEAN DEFAULT false,\n    notBefore Time,\n    notAfter Time,\n\n    -- issuer is only required if isCA is false, if isCA is true then it must not be present\n    issuer OCTET STRING OPTIONAL,\n    ...\n    -- New fields can be added below here\n}\n\nEND"
  },
  {
    "path": "cert/cert_v2.go",
    "content": "package cert\n\nimport (\n\t\"bytes\"\n\t\"crypto/ecdh\"\n\t\"crypto/ecdsa\"\n\t\"crypto/ed25519\"\n\t\"crypto/elliptic\"\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"encoding/json\"\n\t\"encoding/pem\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"slices\"\n\t\"time\"\n\n\t\"golang.org/x/crypto/cryptobyte\"\n\t\"golang.org/x/crypto/cryptobyte/asn1\"\n\t\"golang.org/x/crypto/curve25519\"\n)\n\nconst (\n\tclassConstructed     = 0x20\n\tclassContextSpecific = 0x80\n\n\tTagCertDetails   = 0 | classConstructed | classContextSpecific\n\tTagCertCurve     = 1 | classContextSpecific\n\tTagCertPublicKey = 2 | classContextSpecific\n\tTagCertSignature = 3 | classContextSpecific\n\n\tTagDetailsName           = 0 | classContextSpecific\n\tTagDetailsNetworks       = 1 | classConstructed | classContextSpecific\n\tTagDetailsUnsafeNetworks = 2 | classConstructed | classContextSpecific\n\tTagDetailsGroups         = 3 | classConstructed | classContextSpecific\n\tTagDetailsIsCA           = 4 | classContextSpecific\n\tTagDetailsNotBefore      = 5 | classContextSpecific\n\tTagDetailsNotAfter       = 6 | classContextSpecific\n\tTagDetailsIssuer         = 7 | classContextSpecific\n)\n\nconst (\n\t// MaxCertificateSize is the maximum length a valid certificate can be\n\tMaxCertificateSize = 65536\n\n\t// MaxNameLength is limited to a maximum realistic DNS domain name to help facilitate DNS systems\n\tMaxNameLength = 253\n\n\t// MaxNetworkLength is the maximum length a network value can be.\n\t// 16 bytes for an ipv6 address + 1 byte for the prefix length\n\tMaxNetworkLength = 17\n)\n\ntype certificateV2 struct {\n\tdetails detailsV2\n\n\t// RawDetails contains the entire asn.1 DER encoded Details struct\n\t// This is to benefit forwards compatibility in signature checking.\n\t// signature(RawDetails + Curve + PublicKey) == Signature\n\trawDetails []byte\n\tcurve      Curve\n\tpublicKey  []byte\n\tsignature  []byte\n}\n\ntype detailsV2 struct {\n\tname           string\n\tnetworks       []netip.Prefix // MUST BE SORTED\n\tunsafeNetworks []netip.Prefix // MUST BE SORTED\n\tgroups         []string\n\tisCA           bool\n\tnotBefore      time.Time\n\tnotAfter       time.Time\n\tissuer         string\n}\n\nfunc (c *certificateV2) Version() Version {\n\treturn Version2\n}\n\nfunc (c *certificateV2) Curve() Curve {\n\treturn c.curve\n}\n\nfunc (c *certificateV2) Groups() []string {\n\treturn c.details.groups\n}\n\nfunc (c *certificateV2) IsCA() bool {\n\treturn c.details.isCA\n}\n\nfunc (c *certificateV2) Issuer() string {\n\treturn c.details.issuer\n}\n\nfunc (c *certificateV2) Name() string {\n\treturn c.details.name\n}\n\nfunc (c *certificateV2) Networks() []netip.Prefix {\n\treturn c.details.networks\n}\n\nfunc (c *certificateV2) NotAfter() time.Time {\n\treturn c.details.notAfter\n}\n\nfunc (c *certificateV2) NotBefore() time.Time {\n\treturn c.details.notBefore\n}\n\nfunc (c *certificateV2) PublicKey() []byte {\n\treturn c.publicKey\n}\n\nfunc (c *certificateV2) MarshalPublicKeyPEM() []byte {\n\treturn marshalCertPublicKeyToPEM(c)\n}\n\nfunc (c *certificateV2) Signature() []byte {\n\treturn c.signature\n}\n\nfunc (c *certificateV2) UnsafeNetworks() []netip.Prefix {\n\treturn c.details.unsafeNetworks\n}\n\nfunc (c *certificateV2) Fingerprint() (string, error) {\n\tif len(c.rawDetails) == 0 {\n\t\treturn \"\", ErrMissingDetails\n\t}\n\n\tb := make([]byte, len(c.rawDetails)+1+len(c.publicKey)+len(c.signature))\n\tcopy(b, c.rawDetails)\n\tb[len(c.rawDetails)] = byte(c.curve)\n\tcopy(b[len(c.rawDetails)+1:], c.publicKey)\n\tcopy(b[len(c.rawDetails)+1+len(c.publicKey):], c.signature)\n\tsum := sha256.Sum256(b)\n\treturn hex.EncodeToString(sum[:]), nil\n}\n\nfunc (c *certificateV2) CheckSignature(key []byte) bool {\n\tif len(c.rawDetails) == 0 {\n\t\treturn false\n\t}\n\tb := make([]byte, len(c.rawDetails)+1+len(c.publicKey))\n\tcopy(b, c.rawDetails)\n\tb[len(c.rawDetails)] = byte(c.curve)\n\tcopy(b[len(c.rawDetails)+1:], c.publicKey)\n\n\tswitch c.curve {\n\tcase Curve_CURVE25519:\n\t\treturn ed25519.Verify(key, b, c.signature)\n\tcase Curve_P256:\n\t\tpubKey, err := ecdsa.ParseUncompressedPublicKey(elliptic.P256(), key)\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\t\thashed := sha256.Sum256(b)\n\t\treturn ecdsa.VerifyASN1(pubKey, hashed[:], c.signature)\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc (c *certificateV2) Expired(t time.Time) bool {\n\treturn c.details.notBefore.After(t) || c.details.notAfter.Before(t)\n}\n\nfunc (c *certificateV2) VerifyPrivateKey(curve Curve, key []byte) error {\n\tif curve != c.curve {\n\t\treturn ErrPublicPrivateCurveMismatch\n\t}\n\tif c.details.isCA {\n\t\tswitch curve {\n\t\tcase Curve_CURVE25519:\n\t\t\t// the call to PublicKey below will panic slice bounds out of range otherwise\n\t\t\tif len(key) != ed25519.PrivateKeySize {\n\t\t\t\treturn ErrInvalidPrivateKey\n\t\t\t}\n\n\t\t\tif !ed25519.PublicKey(c.publicKey).Equal(ed25519.PrivateKey(key).Public()) {\n\t\t\t\treturn ErrPublicPrivateKeyMismatch\n\t\t\t}\n\t\tcase Curve_P256:\n\t\t\tprivkey, err := ecdh.P256().NewPrivateKey(key)\n\t\t\tif err != nil {\n\t\t\t\treturn ErrInvalidPrivateKey\n\t\t\t}\n\t\t\tpub := privkey.PublicKey().Bytes()\n\t\t\tif !bytes.Equal(pub, c.publicKey) {\n\t\t\t\treturn ErrPublicPrivateKeyMismatch\n\t\t\t}\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"invalid curve: %s\", curve)\n\t\t}\n\t\treturn nil\n\t}\n\n\tvar pub []byte\n\tswitch curve {\n\tcase Curve_CURVE25519:\n\t\tvar err error\n\t\tpub, err = curve25519.X25519(key, curve25519.Basepoint)\n\t\tif err != nil {\n\t\t\treturn ErrInvalidPrivateKey\n\t\t}\n\tcase Curve_P256:\n\t\tprivkey, err := ecdh.P256().NewPrivateKey(key)\n\t\tif err != nil {\n\t\t\treturn ErrInvalidPrivateKey\n\t\t}\n\t\tpub = privkey.PublicKey().Bytes()\n\tdefault:\n\t\treturn fmt.Errorf(\"invalid curve: %s\", curve)\n\t}\n\tif !bytes.Equal(pub, c.publicKey) {\n\t\treturn ErrPublicPrivateKeyMismatch\n\t}\n\n\treturn nil\n}\n\nfunc (c *certificateV2) String() string {\n\tmb, err := c.marshalJSON()\n\tif err != nil {\n\t\treturn fmt.Sprintf(\"<error marshalling certificate: %v>\", err)\n\t}\n\n\tb, err := json.MarshalIndent(mb, \"\", \"\\t\")\n\tif err != nil {\n\t\treturn fmt.Sprintf(\"<error marshalling certificate: %v>\", err)\n\t}\n\treturn string(b)\n}\n\nfunc (c *certificateV2) MarshalForHandshakes() ([]byte, error) {\n\tif c.rawDetails == nil {\n\t\treturn nil, ErrEmptyRawDetails\n\t}\n\tvar b cryptobyte.Builder\n\t// Outermost certificate\n\tb.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) {\n\n\t\t// Add the cert details which is already marshalled\n\t\tb.AddBytes(c.rawDetails)\n\n\t\t// Skipping the curve and public key since those come across in a different part of the handshake\n\n\t\t// Add the signature\n\t\tb.AddASN1(TagCertSignature, func(b *cryptobyte.Builder) {\n\t\t\tb.AddBytes(c.signature)\n\t\t})\n\t})\n\n\treturn b.Bytes()\n}\n\nfunc (c *certificateV2) Marshal() ([]byte, error) {\n\tif c.rawDetails == nil {\n\t\treturn nil, ErrEmptyRawDetails\n\t}\n\tvar b cryptobyte.Builder\n\t// Outermost certificate\n\tb.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) {\n\n\t\t// Add the cert details which is already marshalled\n\t\tb.AddBytes(c.rawDetails)\n\n\t\t// Add the curve only if its not the default value\n\t\tif c.curve != Curve_CURVE25519 {\n\t\t\tb.AddASN1(TagCertCurve, func(b *cryptobyte.Builder) {\n\t\t\t\tb.AddBytes([]byte{byte(c.curve)})\n\t\t\t})\n\t\t}\n\n\t\t// Add the public key if it is not empty\n\t\tif c.publicKey != nil {\n\t\t\tb.AddASN1(TagCertPublicKey, func(b *cryptobyte.Builder) {\n\t\t\t\tb.AddBytes(c.publicKey)\n\t\t\t})\n\t\t}\n\n\t\t// Add the signature\n\t\tb.AddASN1(TagCertSignature, func(b *cryptobyte.Builder) {\n\t\t\tb.AddBytes(c.signature)\n\t\t})\n\t})\n\n\treturn b.Bytes()\n}\n\nfunc (c *certificateV2) MarshalPEM() ([]byte, error) {\n\tb, err := c.Marshal()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn pem.EncodeToMemory(&pem.Block{Type: CertificateV2Banner, Bytes: b}), nil\n}\n\nfunc (c *certificateV2) MarshalJSON() ([]byte, error) {\n\tb, err := c.marshalJSON()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn json.Marshal(b)\n}\n\nfunc (c *certificateV2) marshalJSON() (m, error) {\n\tfp, err := c.Fingerprint()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn m{\n\t\t\"details\": m{\n\t\t\t\"name\":           c.details.name,\n\t\t\t\"networks\":       c.details.networks,\n\t\t\t\"unsafeNetworks\": c.details.unsafeNetworks,\n\t\t\t\"groups\":         c.details.groups,\n\t\t\t\"notBefore\":      c.details.notBefore,\n\t\t\t\"notAfter\":       c.details.notAfter,\n\t\t\t\"isCa\":           c.details.isCA,\n\t\t\t\"issuer\":         c.details.issuer,\n\t\t},\n\t\t\"version\":     Version2,\n\t\t\"publicKey\":   fmt.Sprintf(\"%x\", c.publicKey),\n\t\t\"curve\":       c.curve.String(),\n\t\t\"fingerprint\": fp,\n\t\t\"signature\":   fmt.Sprintf(\"%x\", c.Signature()),\n\t}, nil\n}\n\nfunc (c *certificateV2) Copy() Certificate {\n\tnc := &certificateV2{\n\t\tdetails: detailsV2{\n\t\t\tname:      c.details.name,\n\t\t\tnotBefore: c.details.notBefore,\n\t\t\tnotAfter:  c.details.notAfter,\n\t\t\tisCA:      c.details.isCA,\n\t\t\tissuer:    c.details.issuer,\n\t\t},\n\t\tcurve:      c.curve,\n\t\tpublicKey:  make([]byte, len(c.publicKey)),\n\t\tsignature:  make([]byte, len(c.signature)),\n\t\trawDetails: make([]byte, len(c.rawDetails)),\n\t}\n\n\tif c.details.groups != nil {\n\t\tnc.details.groups = make([]string, len(c.details.groups))\n\t\tcopy(nc.details.groups, c.details.groups)\n\t}\n\n\tif c.details.networks != nil {\n\t\tnc.details.networks = make([]netip.Prefix, len(c.details.networks))\n\t\tcopy(nc.details.networks, c.details.networks)\n\t}\n\n\tif c.details.unsafeNetworks != nil {\n\t\tnc.details.unsafeNetworks = make([]netip.Prefix, len(c.details.unsafeNetworks))\n\t\tcopy(nc.details.unsafeNetworks, c.details.unsafeNetworks)\n\t}\n\n\tcopy(nc.rawDetails, c.rawDetails)\n\tcopy(nc.signature, c.signature)\n\tcopy(nc.publicKey, c.publicKey)\n\n\treturn nc\n}\n\nfunc (c *certificateV2) fromTBSCertificate(t *TBSCertificate) error {\n\tc.details = detailsV2{\n\t\tname:           t.Name,\n\t\tnetworks:       t.Networks,\n\t\tunsafeNetworks: t.UnsafeNetworks,\n\t\tgroups:         t.Groups,\n\t\tisCA:           t.IsCA,\n\t\tnotBefore:      t.NotBefore,\n\t\tnotAfter:       t.NotAfter,\n\t\tissuer:         t.issuer,\n\t}\n\tc.curve = t.Curve\n\tc.publicKey = t.PublicKey\n\treturn c.validate()\n}\n\nfunc (c *certificateV2) validate() error {\n\t// Empty names are allowed\n\n\tif len(c.publicKey) == 0 {\n\t\treturn ErrInvalidPublicKey\n\t}\n\n\tif !c.details.isCA && len(c.details.networks) == 0 {\n\t\treturn NewErrInvalidCertificateProperties(\"non-CA certificate must contain at least 1 network\")\n\t}\n\n\thasV4Networks := false\n\thasV6Networks := false\n\tfor _, network := range c.details.networks {\n\t\tif !network.IsValid() || !network.Addr().IsValid() {\n\t\t\treturn NewErrInvalidCertificateProperties(\"invalid network: %s\", network)\n\t\t}\n\n\t\tif network.Addr().IsUnspecified() {\n\t\t\treturn NewErrInvalidCertificateProperties(\"non-CA certificates must not use the zero address as a network: %s\", network)\n\t\t}\n\n\t\tif network.Addr().Zone() != \"\" {\n\t\t\treturn NewErrInvalidCertificateProperties(\"networks may not contain zones: %s\", network)\n\t\t}\n\n\t\tif network.Addr().Is4In6() {\n\t\t\treturn NewErrInvalidCertificateProperties(\"4in6 networks are not allowed: %s\", network)\n\t\t}\n\n\t\thasV4Networks = hasV4Networks || network.Addr().Is4()\n\t\thasV6Networks = hasV6Networks || network.Addr().Is6()\n\t}\n\n\tslices.SortFunc(c.details.networks, comparePrefix)\n\terr := findDuplicatePrefix(c.details.networks)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, network := range c.details.unsafeNetworks {\n\t\tif !network.IsValid() || !network.Addr().IsValid() {\n\t\t\treturn NewErrInvalidCertificateProperties(\"invalid unsafe network: %s\", network)\n\t\t}\n\n\t\tif network.Addr().Zone() != \"\" {\n\t\t\treturn NewErrInvalidCertificateProperties(\"unsafe networks may not contain zones: %s\", network)\n\t\t}\n\n\t\tif !c.details.isCA {\n\t\t\tif network.Addr().Is6() {\n\t\t\t\tif !hasV6Networks {\n\t\t\t\t\treturn NewErrInvalidCertificateProperties(\"IPv6 unsafe networks require an IPv6 address assignment: %s\", network)\n\t\t\t\t}\n\t\t\t} else if network.Addr().Is4() {\n\t\t\t\tif !hasV4Networks {\n\t\t\t\t\treturn NewErrInvalidCertificateProperties(\"IPv4 unsafe networks require an IPv4 address assignment: %s\", network)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tslices.SortFunc(c.details.unsafeNetworks, comparePrefix)\n\terr = findDuplicatePrefix(c.details.unsafeNetworks)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (c *certificateV2) marshalForSigning() ([]byte, error) {\n\td, err := c.details.Marshal()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"marshalling certificate details failed: %w\", err)\n\t}\n\tc.rawDetails = d\n\n\tb := make([]byte, len(c.rawDetails)+1+len(c.publicKey))\n\tcopy(b, c.rawDetails)\n\tb[len(c.rawDetails)] = byte(c.curve)\n\tcopy(b[len(c.rawDetails)+1:], c.publicKey)\n\treturn b, nil\n}\n\nfunc (c *certificateV2) setSignature(b []byte) error {\n\tif len(b) == 0 {\n\t\treturn ErrEmptySignature\n\t}\n\tc.signature = b\n\treturn nil\n}\n\nfunc (d *detailsV2) Marshal() ([]byte, error) {\n\tvar b cryptobyte.Builder\n\tvar err error\n\n\t// Details are a structure\n\tb.AddASN1(TagCertDetails, func(b *cryptobyte.Builder) {\n\n\t\t// Add the name\n\t\tb.AddASN1(TagDetailsName, func(b *cryptobyte.Builder) {\n\t\t\tb.AddBytes([]byte(d.name))\n\t\t})\n\n\t\t// Add the networks if any exist\n\t\tif len(d.networks) > 0 {\n\t\t\tb.AddASN1(TagDetailsNetworks, func(b *cryptobyte.Builder) {\n\t\t\t\tfor _, n := range d.networks {\n\t\t\t\t\tsb, innerErr := n.MarshalBinary()\n\t\t\t\t\tif innerErr != nil {\n\t\t\t\t\t\t// MarshalBinary never returns an error\n\t\t\t\t\t\terr = fmt.Errorf(\"unable to marshal network: %w\", innerErr)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tb.AddASN1OctetString(sb)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\n\t\t// Add the unsafe networks if any exist\n\t\tif len(d.unsafeNetworks) > 0 {\n\t\t\tb.AddASN1(TagDetailsUnsafeNetworks, func(b *cryptobyte.Builder) {\n\t\t\t\tfor _, n := range d.unsafeNetworks {\n\t\t\t\t\tsb, innerErr := n.MarshalBinary()\n\t\t\t\t\tif innerErr != nil {\n\t\t\t\t\t\t// MarshalBinary never returns an error\n\t\t\t\t\t\terr = fmt.Errorf(\"unable to marshal unsafe network: %w\", innerErr)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tb.AddASN1OctetString(sb)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\n\t\t// Add groups if any exist\n\t\tif len(d.groups) > 0 {\n\t\t\tb.AddASN1(TagDetailsGroups, func(b *cryptobyte.Builder) {\n\t\t\t\tfor _, group := range d.groups {\n\t\t\t\t\tb.AddASN1(asn1.UTF8String, func(b *cryptobyte.Builder) {\n\t\t\t\t\t\tb.AddBytes([]byte(group))\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\n\t\t// Add IsCA only if true\n\t\tif d.isCA {\n\t\t\tb.AddASN1(TagDetailsIsCA, func(b *cryptobyte.Builder) {\n\t\t\t\tb.AddUint8(0xff)\n\t\t\t})\n\t\t}\n\n\t\t// Add not before\n\t\tb.AddASN1Int64WithTag(d.notBefore.Unix(), TagDetailsNotBefore)\n\n\t\t// Add not after\n\t\tb.AddASN1Int64WithTag(d.notAfter.Unix(), TagDetailsNotAfter)\n\n\t\t// Add the issuer if present\n\t\tif d.issuer != \"\" {\n\t\t\tissuerBytes, innerErr := hex.DecodeString(d.issuer)\n\t\t\tif innerErr != nil {\n\t\t\t\terr = fmt.Errorf(\"failed to decode issuer: %w\", innerErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tb.AddASN1(TagDetailsIssuer, func(b *cryptobyte.Builder) {\n\t\t\t\tb.AddBytes(issuerBytes)\n\t\t\t})\n\t\t}\n\t})\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn b.Bytes()\n}\n\nfunc unmarshalCertificateV2(b []byte, publicKey []byte, curve Curve) (*certificateV2, error) {\n\tl := len(b)\n\tif l == 0 || l > MaxCertificateSize {\n\t\treturn nil, ErrBadFormat\n\t}\n\n\tinput := cryptobyte.String(b)\n\t// Open the envelope\n\tif !input.ReadASN1(&input, asn1.SEQUENCE) || input.Empty() {\n\t\treturn nil, ErrBadFormat\n\t}\n\n\t// Grab the cert details, we need to preserve the tag and length\n\tvar rawDetails cryptobyte.String\n\tif !input.ReadASN1Element(&rawDetails, TagCertDetails) || rawDetails.Empty() {\n\t\treturn nil, ErrBadFormat\n\t}\n\n\t//Maybe grab the curve\n\tvar rawCurve byte\n\tif !readOptionalASN1Byte(&input, &rawCurve, TagCertCurve, byte(curve)) {\n\t\treturn nil, ErrBadFormat\n\t}\n\tcurve = Curve(rawCurve)\n\n\t// Maybe grab the public key\n\tvar rawPublicKey cryptobyte.String\n\tif len(publicKey) > 0 {\n\t\t// If a public key is passed in, then the handshake certificate must\n\t\t// not have a public key present\n\t\tif input.PeekASN1Tag(TagCertPublicKey) {\n\t\t\treturn nil, ErrCertPubkeyPresent\n\t\t}\n\t\trawPublicKey = make(cryptobyte.String, len(publicKey))\n\t\tcopy(rawPublicKey, publicKey)\n\t} else if !input.ReadOptionalASN1(&rawPublicKey, nil, TagCertPublicKey) {\n\t\treturn nil, ErrBadFormat\n\t}\n\n\tif len(rawPublicKey) == 0 {\n\t\treturn nil, ErrBadFormat\n\t}\n\n\t// Grab the signature\n\tvar rawSignature cryptobyte.String\n\tif !input.ReadASN1(&rawSignature, TagCertSignature) || rawSignature.Empty() {\n\t\treturn nil, ErrBadFormat\n\t}\n\n\t// Finally unmarshal the details\n\tdetails, err := unmarshalDetails(rawDetails)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tc := &certificateV2{\n\t\tdetails:    details,\n\t\trawDetails: rawDetails,\n\t\tcurve:      curve,\n\t\tpublicKey:  rawPublicKey,\n\t\tsignature:  rawSignature,\n\t}\n\n\terr = c.validate()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn c, nil\n}\n\nfunc unmarshalDetails(b cryptobyte.String) (detailsV2, error) {\n\t// Open the envelope\n\tif !b.ReadASN1(&b, TagCertDetails) || b.Empty() {\n\t\treturn detailsV2{}, ErrBadFormat\n\t}\n\n\t// Read the name\n\tvar name cryptobyte.String\n\tif !b.ReadASN1(&name, TagDetailsName) || name.Empty() || len(name) > MaxNameLength {\n\t\treturn detailsV2{}, ErrBadFormat\n\t}\n\n\t// Read the network addresses\n\tvar subString cryptobyte.String\n\tvar found bool\n\n\tif !b.ReadOptionalASN1(&subString, &found, TagDetailsNetworks) {\n\t\treturn detailsV2{}, ErrBadFormat\n\t}\n\n\tvar networks []netip.Prefix\n\tvar val cryptobyte.String\n\tif found {\n\t\tfor !subString.Empty() {\n\t\t\tif !subString.ReadASN1(&val, asn1.OCTET_STRING) || val.Empty() || len(val) > MaxNetworkLength {\n\t\t\t\treturn detailsV2{}, ErrBadFormat\n\t\t\t}\n\n\t\t\tvar n netip.Prefix\n\t\t\tif err := n.UnmarshalBinary(val); err != nil {\n\t\t\t\treturn detailsV2{}, ErrBadFormat\n\t\t\t}\n\t\t\tnetworks = append(networks, n)\n\t\t}\n\t}\n\n\t// Read out any unsafe networks\n\tif !b.ReadOptionalASN1(&subString, &found, TagDetailsUnsafeNetworks) {\n\t\treturn detailsV2{}, ErrBadFormat\n\t}\n\n\tvar unsafeNetworks []netip.Prefix\n\tif found {\n\t\tfor !subString.Empty() {\n\t\t\tif !subString.ReadASN1(&val, asn1.OCTET_STRING) || val.Empty() || len(val) > MaxNetworkLength {\n\t\t\t\treturn detailsV2{}, ErrBadFormat\n\t\t\t}\n\n\t\t\tvar n netip.Prefix\n\t\t\tif err := n.UnmarshalBinary(val); err != nil {\n\t\t\t\treturn detailsV2{}, ErrBadFormat\n\t\t\t}\n\t\t\tunsafeNetworks = append(unsafeNetworks, n)\n\t\t}\n\t}\n\n\t// Read out any groups\n\tif !b.ReadOptionalASN1(&subString, &found, TagDetailsGroups) {\n\t\treturn detailsV2{}, ErrBadFormat\n\t}\n\n\tvar groups []string\n\tif found {\n\t\tfor !subString.Empty() {\n\t\t\tif !subString.ReadASN1(&val, asn1.UTF8String) || val.Empty() {\n\t\t\t\treturn detailsV2{}, ErrBadFormat\n\t\t\t}\n\t\t\tgroups = append(groups, string(val))\n\t\t}\n\t}\n\n\t// Read out IsCA\n\tvar isCa bool\n\tif !readOptionalASN1Boolean(&b, &isCa, TagDetailsIsCA, false) {\n\t\treturn detailsV2{}, ErrBadFormat\n\t}\n\n\t// Read not before and not after\n\tvar notBefore int64\n\tif !b.ReadASN1Int64WithTag(&notBefore, TagDetailsNotBefore) {\n\t\treturn detailsV2{}, ErrBadFormat\n\t}\n\n\tvar notAfter int64\n\tif !b.ReadASN1Int64WithTag(&notAfter, TagDetailsNotAfter) {\n\t\treturn detailsV2{}, ErrBadFormat\n\t}\n\n\t// Read issuer\n\tvar issuer cryptobyte.String\n\tif !b.ReadOptionalASN1(&issuer, nil, TagDetailsIssuer) {\n\t\treturn detailsV2{}, ErrBadFormat\n\t}\n\n\treturn detailsV2{\n\t\tname:           string(name),\n\t\tnetworks:       networks,\n\t\tunsafeNetworks: unsafeNetworks,\n\t\tgroups:         groups,\n\t\tisCA:           isCa,\n\t\tnotBefore:      time.Unix(notBefore, 0),\n\t\tnotAfter:       time.Unix(notAfter, 0),\n\t\tissuer:         hex.EncodeToString(issuer),\n\t}, nil\n}\n"
  },
  {
    "path": "cert/cert_v2_test.go",
    "content": "package cert\n\nimport (\n\t\"crypto/ed25519\"\n\t\"crypto/rand\"\n\t\"encoding/hex\"\n\t\"net/netip\"\n\t\"slices\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/slackhq/nebula/test\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestCertificateV2_Marshal(t *testing.T) {\n\tt.Parallel()\n\tbefore := time.Now().Add(time.Second * -60).Round(time.Second)\n\tafter := time.Now().Add(time.Second * 60).Round(time.Second)\n\tpubKey := []byte(\"1234567890abcedfghij1234567890ab\")\n\n\tnc := certificateV2{\n\t\tdetails: detailsV2{\n\t\t\tname: \"testing\",\n\t\t\tnetworks: []netip.Prefix{\n\t\t\t\tmustParsePrefixUnmapped(\"10.1.1.2/16\"),\n\t\t\t\tmustParsePrefixUnmapped(\"10.1.1.1/24\"),\n\t\t\t},\n\t\t\tunsafeNetworks: []netip.Prefix{\n\t\t\t\tmustParsePrefixUnmapped(\"9.1.1.3/16\"),\n\t\t\t\tmustParsePrefixUnmapped(\"9.1.1.2/24\"),\n\t\t\t},\n\t\t\tgroups:    []string{\"test-group1\", \"test-group2\", \"test-group3\"},\n\t\t\tnotBefore: before,\n\t\t\tnotAfter:  after,\n\t\t\tisCA:      false,\n\t\t\tissuer:    \"1234567890abcdef1234567890abcdef\",\n\t\t},\n\t\tsignature: []byte(\"1234567890abcdef1234567890abcdef\"),\n\t\tpublicKey: pubKey,\n\t}\n\n\tdb, err := nc.details.Marshal()\n\trequire.NoError(t, err)\n\tnc.rawDetails = db\n\n\tb, err := nc.Marshal()\n\trequire.NoError(t, err)\n\t//t.Log(\"Cert size:\", len(b))\n\n\tnc2, err := unmarshalCertificateV2(b, nil, Curve_CURVE25519)\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, Version2, nc.Version())\n\tassert.Equal(t, Curve_CURVE25519, nc.Curve())\n\tassert.Equal(t, nc.Signature(), nc2.Signature())\n\tassert.Equal(t, nc.Name(), nc2.Name())\n\tassert.Equal(t, nc.NotBefore(), nc2.NotBefore())\n\tassert.Equal(t, nc.NotAfter(), nc2.NotAfter())\n\tassert.Equal(t, nc.PublicKey(), nc2.PublicKey())\n\tassert.Equal(t, nc.IsCA(), nc2.IsCA())\n\tassert.Equal(t, nc.Issuer(), nc2.Issuer())\n\n\t// unmarshalling will sort networks and unsafeNetworks, we need to do the same\n\t// but first make sure it fails\n\tassert.NotEqual(t, nc.Networks(), nc2.Networks())\n\tassert.NotEqual(t, nc.UnsafeNetworks(), nc2.UnsafeNetworks())\n\n\tslices.SortFunc(nc.details.networks, comparePrefix)\n\tslices.SortFunc(nc.details.unsafeNetworks, comparePrefix)\n\n\tassert.Equal(t, nc.Networks(), nc2.Networks())\n\tassert.Equal(t, nc.UnsafeNetworks(), nc2.UnsafeNetworks())\n\n\tassert.Equal(t, nc.Groups(), nc2.Groups())\n}\n\nfunc TestCertificateV2_Unmarshal(t *testing.T) {\n\tt.Parallel()\n\tbefore := time.Now().Add(time.Second * -60).Round(time.Second)\n\tafter := time.Now().Add(time.Second * 60).Round(time.Second)\n\tpubKey := []byte(\"1234567890abcedfghij1234567890ab\")\n\n\tnc := certificateV2{\n\t\tdetails: detailsV2{\n\t\t\tname: \"testing\",\n\t\t\tnetworks: []netip.Prefix{\n\t\t\t\tmustParsePrefixUnmapped(\"10.1.1.2/16\"),\n\t\t\t\tmustParsePrefixUnmapped(\"10.1.1.1/24\"),\n\t\t\t},\n\t\t\tunsafeNetworks: []netip.Prefix{\n\t\t\t\tmustParsePrefixUnmapped(\"9.1.1.3/16\"),\n\t\t\t\tmustParsePrefixUnmapped(\"9.1.1.2/24\"),\n\t\t\t},\n\t\t\tgroups:    []string{\"test-group1\", \"test-group2\", \"test-group3\"},\n\t\t\tnotBefore: before,\n\t\t\tnotAfter:  after,\n\t\t\tisCA:      false,\n\t\t\tissuer:    \"1234567890abcdef1234567890abcdef\",\n\t\t},\n\t\tsignature: []byte(\"1234567890abcdef1234567890abcdef\"),\n\t\tpublicKey: pubKey,\n\t}\n\n\tdb, err := nc.details.Marshal()\n\trequire.NoError(t, err)\n\tnc.rawDetails = db\n\n\tcertWithPubkey, err := nc.Marshal()\n\trequire.NoError(t, err)\n\t//t.Log(\"Cert size:\", len(b))\n\tcertWithoutPubkey, err := nc.MarshalForHandshakes()\n\trequire.NoError(t, err)\n\n\t// Cert must not have a pubkey if one is passed in as an argument\n\t_, err = unmarshalCertificateV2(certWithPubkey, pubKey, Curve_CURVE25519)\n\trequire.ErrorIs(t, err, ErrCertPubkeyPresent)\n\n\t// Certs must have pubkeys\n\t_, err = unmarshalCertificateV2(certWithoutPubkey, nil, Curve_CURVE25519)\n\trequire.ErrorIs(t, err, ErrBadFormat)\n\n\t// Ensure proper unmarshal if a pubkey is passed in\n\tnc2, err := unmarshalCertificateV2(certWithoutPubkey, pubKey, Curve_CURVE25519)\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, nc.PublicKey(), nc2.PublicKey())\n}\n\nfunc TestCertificateV2_PublicKeyPem(t *testing.T) {\n\tt.Parallel()\n\tbefore := time.Now().Add(time.Second * -60).Round(time.Second)\n\tafter := time.Now().Add(time.Second * 60).Round(time.Second)\n\tpubKey := ed25519.PublicKey(\"1234567890abcedfghij1234567890ab\")\n\n\tnc := certificateV2{\n\t\tdetails: detailsV2{\n\t\t\tname:           \"testing\",\n\t\t\tnetworks:       []netip.Prefix{},\n\t\t\tunsafeNetworks: []netip.Prefix{},\n\t\t\tgroups:         []string{\"test-group1\", \"test-group2\", \"test-group3\"},\n\t\t\tnotBefore:      before,\n\t\t\tnotAfter:       after,\n\t\t\tisCA:           false,\n\t\t\tissuer:         \"1234567890abcedfghij1234567890ab\",\n\t\t},\n\t\tpublicKey: pubKey,\n\t\tsignature: []byte(\"1234567890abcedfghij1234567890ab\"),\n\t}\n\n\tassert.Equal(t, Version2, nc.Version())\n\tassert.Equal(t, Curve_CURVE25519, nc.Curve())\n\tpubPem := \"-----BEGIN NEBULA X25519 PUBLIC KEY-----\\nMTIzNDU2Nzg5MGFiY2VkZmdoaWoxMjM0NTY3ODkwYWI=\\n-----END NEBULA X25519 PUBLIC KEY-----\\n\"\n\tassert.Equal(t, string(nc.MarshalPublicKeyPEM()), pubPem)\n\tassert.False(t, nc.IsCA())\n\n\tnc.details.isCA = true\n\tassert.Equal(t, Curve_CURVE25519, nc.Curve())\n\tpubPem = \"-----BEGIN NEBULA ED25519 PUBLIC KEY-----\\nMTIzNDU2Nzg5MGFiY2VkZmdoaWoxMjM0NTY3ODkwYWI=\\n-----END NEBULA ED25519 PUBLIC KEY-----\\n\"\n\tassert.Equal(t, string(nc.MarshalPublicKeyPEM()), pubPem)\n\tassert.True(t, nc.IsCA())\n\n\tpubP256KeyPem := []byte(`-----BEGIN NEBULA P256 PUBLIC KEY-----\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAA=\n-----END NEBULA P256 PUBLIC KEY-----\n`)\n\n\tpubP256KeyPemCA := []byte(`-----BEGIN NEBULA ECDSA P256 PUBLIC KEY-----\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAA=\n-----END NEBULA ECDSA P256 PUBLIC KEY-----\n`)\n\n\tpubP256Key, _, _, err := UnmarshalPublicKeyFromPEM(pubP256KeyPem)\n\trequire.NoError(t, err)\n\tnc.curve = Curve_P256\n\tnc.publicKey = pubP256Key\n\tassert.Equal(t, Curve_P256, nc.Curve())\n\tassert.Equal(t, string(nc.MarshalPublicKeyPEM()), string(pubP256KeyPemCA))\n\tassert.True(t, nc.IsCA())\n\n\tnc.details.isCA = false\n\tassert.Equal(t, Curve_P256, nc.Curve())\n\tassert.Equal(t, string(nc.MarshalPublicKeyPEM()), string(pubP256KeyPem))\n\tassert.False(t, nc.IsCA())\n}\n\nfunc TestCertificateV2_Expired(t *testing.T) {\n\tnc := certificateV2{\n\t\tdetails: detailsV2{\n\t\t\tnotBefore: time.Now().Add(time.Second * -60).Round(time.Second),\n\t\t\tnotAfter:  time.Now().Add(time.Second * 60).Round(time.Second),\n\t\t},\n\t}\n\n\tassert.True(t, nc.Expired(time.Now().Add(time.Hour)))\n\tassert.True(t, nc.Expired(time.Now().Add(-time.Hour)))\n\tassert.False(t, nc.Expired(time.Now()))\n}\n\nfunc TestCertificateV2_MarshalJSON(t *testing.T) {\n\ttime.Local = time.UTC\n\tpubKey := []byte(\"1234567890abcedf1234567890abcedf\")\n\n\tnc := certificateV2{\n\t\tdetails: detailsV2{\n\t\t\tname: \"testing\",\n\t\t\tnetworks: []netip.Prefix{\n\t\t\t\tmustParsePrefixUnmapped(\"10.1.1.1/24\"),\n\t\t\t\tmustParsePrefixUnmapped(\"10.1.1.2/16\"),\n\t\t\t},\n\t\t\tunsafeNetworks: []netip.Prefix{\n\t\t\t\tmustParsePrefixUnmapped(\"9.1.1.2/24\"),\n\t\t\t\tmustParsePrefixUnmapped(\"9.1.1.3/16\"),\n\t\t\t},\n\t\t\tgroups:    []string{\"test-group1\", \"test-group2\", \"test-group3\"},\n\t\t\tnotBefore: time.Date(1, 0, 0, 1, 0, 0, 0, time.UTC),\n\t\t\tnotAfter:  time.Date(1, 0, 0, 2, 0, 0, 0, time.UTC),\n\t\t\tisCA:      false,\n\t\t\tissuer:    \"1234567890abcedf1234567890abcedf\",\n\t\t},\n\t\tpublicKey: pubKey,\n\t\tsignature: []byte(\"1234567890abcedf1234567890abcedf1234567890abcedf1234567890abcedf\"),\n\t}\n\n\tb, err := nc.MarshalJSON()\n\trequire.ErrorIs(t, err, ErrMissingDetails)\n\n\trd, err := nc.details.Marshal()\n\trequire.NoError(t, err)\n\n\tnc.rawDetails = rd\n\tb, err = nc.MarshalJSON()\n\trequire.NoError(t, err)\n\tassert.JSONEq(\n\t\tt,\n\t\t\"{\\\"curve\\\":\\\"CURVE25519\\\",\\\"details\\\":{\\\"groups\\\":[\\\"test-group1\\\",\\\"test-group2\\\",\\\"test-group3\\\"],\\\"isCa\\\":false,\\\"issuer\\\":\\\"1234567890abcedf1234567890abcedf\\\",\\\"name\\\":\\\"testing\\\",\\\"networks\\\":[\\\"10.1.1.1/24\\\",\\\"10.1.1.2/16\\\"],\\\"notAfter\\\":\\\"0000-11-30T02:00:00Z\\\",\\\"notBefore\\\":\\\"0000-11-30T01:00:00Z\\\",\\\"unsafeNetworks\\\":[\\\"9.1.1.2/24\\\",\\\"9.1.1.3/16\\\"]},\\\"fingerprint\\\":\\\"152d9a7400c1e001cb76cffd035215ebb351f69eeb797f7f847dd086e15e56dd\\\",\\\"publicKey\\\":\\\"3132333435363738393061626365646631323334353637383930616263656466\\\",\\\"signature\\\":\\\"31323334353637383930616263656466313233343536373839306162636564663132333435363738393061626365646631323334353637383930616263656466\\\",\\\"version\\\":2}\",\n\t\tstring(b),\n\t)\n}\n\nfunc TestCertificateV2_VerifyPrivateKey(t *testing.T) {\n\tca, _, caKey, _ := NewTestCaCert(Version2, Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil)\n\terr := ca.VerifyPrivateKey(Curve_CURVE25519, caKey)\n\trequire.NoError(t, err)\n\n\terr = ca.VerifyPrivateKey(Curve_CURVE25519, caKey[:16])\n\trequire.ErrorIs(t, err, ErrInvalidPrivateKey)\n\n\t_, caKey2, err := ed25519.GenerateKey(rand.Reader)\n\trequire.NoError(t, err)\n\terr = ca.VerifyPrivateKey(Curve_CURVE25519, caKey2)\n\trequire.ErrorIs(t, err, ErrPublicPrivateKeyMismatch)\n\n\tc, _, priv, _ := NewTestCert(Version2, Curve_CURVE25519, ca, caKey, \"test\", time.Time{}, time.Time{}, nil, nil, nil)\n\trawPriv, b, curve, err := UnmarshalPrivateKeyFromPEM(priv)\n\trequire.NoError(t, err)\n\tassert.Empty(t, b)\n\tassert.Equal(t, Curve_CURVE25519, curve)\n\terr = c.VerifyPrivateKey(Curve_CURVE25519, rawPriv)\n\trequire.NoError(t, err)\n\n\t_, priv2 := X25519Keypair()\n\terr = c.VerifyPrivateKey(Curve_P256, priv2)\n\trequire.ErrorIs(t, err, ErrPublicPrivateCurveMismatch)\n\n\terr = c.VerifyPrivateKey(Curve_CURVE25519, priv2)\n\trequire.ErrorIs(t, err, ErrPublicPrivateKeyMismatch)\n\n\terr = c.VerifyPrivateKey(Curve_CURVE25519, priv2[:16])\n\trequire.ErrorIs(t, err, ErrInvalidPrivateKey)\n\n\tac, ok := c.(*certificateV2)\n\trequire.True(t, ok)\n\tac.curve = Curve(99)\n\terr = c.VerifyPrivateKey(Curve(99), priv2)\n\trequire.EqualError(t, err, \"invalid curve: 99\")\n\n\tca2, _, caKey2, _ := NewTestCaCert(Version2, Curve_P256, time.Time{}, time.Time{}, nil, nil, nil)\n\terr = ca.VerifyPrivateKey(Curve_CURVE25519, caKey)\n\trequire.NoError(t, err)\n\n\terr = ca2.VerifyPrivateKey(Curve_P256, caKey2[:16])\n\trequire.ErrorIs(t, err, ErrInvalidPrivateKey)\n\n\tc, _, priv, _ = NewTestCert(Version2, Curve_P256, ca2, caKey2, \"test\", time.Time{}, time.Time{}, nil, nil, nil)\n\trawPriv, b, curve, err = UnmarshalPrivateKeyFromPEM(priv)\n\n\terr = c.VerifyPrivateKey(Curve_P256, priv[:16])\n\trequire.ErrorIs(t, err, ErrInvalidPrivateKey)\n\n\terr = c.VerifyPrivateKey(Curve_P256, priv)\n\trequire.ErrorIs(t, err, ErrInvalidPrivateKey)\n\n\taCa, ok := ca2.(*certificateV2)\n\trequire.True(t, ok)\n\taCa.curve = Curve(99)\n\terr = aCa.VerifyPrivateKey(Curve(99), priv2)\n\trequire.EqualError(t, err, \"invalid curve: 99\")\n\n}\n\nfunc TestCertificateV2_VerifyPrivateKeyP256(t *testing.T) {\n\tca, _, caKey, _ := NewTestCaCert(Version2, Curve_P256, time.Time{}, time.Time{}, nil, nil, nil)\n\terr := ca.VerifyPrivateKey(Curve_P256, caKey)\n\trequire.NoError(t, err)\n\n\t_, _, caKey2, _ := NewTestCaCert(Version2, Curve_P256, time.Time{}, time.Time{}, nil, nil, nil)\n\trequire.NoError(t, err)\n\terr = ca.VerifyPrivateKey(Curve_P256, caKey2)\n\trequire.Error(t, err)\n\n\tc, _, priv, _ := NewTestCert(Version2, Curve_P256, ca, caKey, \"test\", time.Time{}, time.Time{}, nil, nil, nil)\n\trawPriv, b, curve, err := UnmarshalPrivateKeyFromPEM(priv)\n\trequire.NoError(t, err)\n\tassert.Empty(t, b)\n\tassert.Equal(t, Curve_P256, curve)\n\terr = c.VerifyPrivateKey(Curve_P256, rawPriv)\n\trequire.NoError(t, err)\n\n\t_, priv2 := P256Keypair()\n\terr = c.VerifyPrivateKey(Curve_P256, priv2)\n\trequire.Error(t, err)\n}\n\nfunc TestCertificateV2_Copy(t *testing.T) {\n\tca, _, caKey, _ := NewTestCaCert(Version2, Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, nil)\n\tc, _, _, _ := NewTestCert(Version2, Curve_CURVE25519, ca, caKey, \"test\", time.Now(), time.Now().Add(5*time.Minute), nil, nil, nil)\n\tcc := c.Copy()\n\ttest.AssertDeepCopyEqual(t, c, cc)\n}\n\nfunc TestUnmarshalCertificateV2(t *testing.T) {\n\tdata := []byte(\"\\x98\\x00\\x00\")\n\t_, err := unmarshalCertificateV2(data, nil, Curve_CURVE25519)\n\trequire.EqualError(t, err, \"bad wire format\")\n}\n\nfunc TestCertificateV2_marshalForSigningStability(t *testing.T) {\n\tbefore := time.Date(1996, time.May, 5, 0, 0, 0, 0, time.UTC)\n\tafter := before.Add(time.Second * 60).Round(time.Second)\n\tpubKey := []byte(\"1234567890abcedfghij1234567890ab\")\n\n\tnc := certificateV2{\n\t\tdetails: detailsV2{\n\t\t\tname: \"testing\",\n\t\t\tnetworks: []netip.Prefix{\n\t\t\t\tmustParsePrefixUnmapped(\"10.1.1.2/16\"),\n\t\t\t\tmustParsePrefixUnmapped(\"10.1.1.1/24\"),\n\t\t\t},\n\t\t\tunsafeNetworks: []netip.Prefix{\n\t\t\t\tmustParsePrefixUnmapped(\"9.1.1.3/16\"),\n\t\t\t\tmustParsePrefixUnmapped(\"9.1.1.2/24\"),\n\t\t\t},\n\t\t\tgroups:    []string{\"test-group1\", \"test-group2\", \"test-group3\"},\n\t\t\tnotBefore: before,\n\t\t\tnotAfter:  after,\n\t\t\tisCA:      false,\n\t\t\tissuer:    \"1234567890abcdef1234567890abcdef\",\n\t\t},\n\t\tsignature: []byte(\"1234567890abcdef1234567890abcdef\"),\n\t\tpublicKey: pubKey,\n\t}\n\n\tconst expectedRawDetailsStr = \"a070800774657374696e67a10e04050a0101021004050a01010118a20e0405090101031004050901010218a3270c0b746573742d67726f7570310c0b746573742d67726f7570320c0b746573742d67726f7570338504318bef808604318befbc87101234567890abcdef1234567890abcdef\"\n\texpectedRawDetails, err := hex.DecodeString(expectedRawDetailsStr)\n\trequire.NoError(t, err)\n\n\tdb, err := nc.details.Marshal()\n\trequire.NoError(t, err)\n\tassert.Equal(t, expectedRawDetails, db)\n\n\texpectedForSigning, err := hex.DecodeString(expectedRawDetailsStr + \"00313233343536373839306162636564666768696a313233343536373839306162\")\n\tb, err := nc.marshalForSigning()\n\trequire.NoError(t, err)\n\tassert.Equal(t, expectedForSigning, b)\n}\n"
  },
  {
    "path": "cert/crypto.go",
    "content": "package cert\n\nimport (\n\t\"crypto/aes\"\n\t\"crypto/cipher\"\n\t\"crypto/ed25519\"\n\t\"crypto/rand\"\n\t\"encoding/pem\"\n\t\"fmt\"\n\t\"io\"\n\t\"math\"\n\n\t\"golang.org/x/crypto/argon2\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\ntype NebulaEncryptedData struct {\n\tEncryptionMetadata NebulaEncryptionMetadata\n\tCiphertext         []byte\n}\n\ntype NebulaEncryptionMetadata struct {\n\tEncryptionAlgorithm string\n\tArgon2Parameters    Argon2Parameters\n}\n\n// Argon2Parameters KDF factors\ntype Argon2Parameters struct {\n\tversion     rune\n\tMemory      uint32 // KiB\n\tParallelism uint8\n\tIterations  uint32\n\tsalt        []byte\n}\n\n// NewArgon2Parameters Returns a new Argon2Parameters object with current version set\nfunc NewArgon2Parameters(memory uint32, parallelism uint8, iterations uint32) *Argon2Parameters {\n\treturn &Argon2Parameters{\n\t\tversion:     argon2.Version,\n\t\tMemory:      memory, // KiB\n\t\tParallelism: parallelism,\n\t\tIterations:  iterations,\n\t}\n}\n\n// Encrypts data using AES-256-GCM and the Argon2id key derivation function\nfunc aes256Encrypt(passphrase []byte, kdfParams *Argon2Parameters, data []byte) ([]byte, error) {\n\tkey, err := aes256DeriveKey(passphrase, kdfParams)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// this should never happen, but since this dictates how our calls into the\n\t// aes package behave and could be catastraphic, let's sanity check this\n\tif len(key) != 32 {\n\t\treturn nil, fmt.Errorf(\"invalid AES-256 key length (%d) - cowardly refusing to encrypt\", len(key))\n\t}\n\n\tblock, err := aes.NewCipher(key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tgcm, err := cipher.NewGCM(block)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tnonce := make([]byte, gcm.NonceSize())\n\tif _, err := io.ReadFull(rand.Reader, nonce); err != nil {\n\t\treturn nil, err\n\t}\n\n\tciphertext := gcm.Seal(nil, nonce, data, nil)\n\tblob := joinNonceCiphertext(nonce, ciphertext)\n\n\treturn blob, nil\n}\n\n// Decrypts data using AES-256-GCM and the Argon2id key derivation function\n// Expects the data to include an Argon2id parameter string before the encrypted data\nfunc aes256Decrypt(passphrase []byte, kdfParams *Argon2Parameters, data []byte) ([]byte, error) {\n\tkey, err := aes256DeriveKey(passphrase, kdfParams)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tblock, err := aes.NewCipher(key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tgcm, err := cipher.NewGCM(block)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tnonce, ciphertext, err := splitNonceCiphertext(data, gcm.NonceSize())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tplaintext, err := gcm.Open(nil, nonce, ciphertext, nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid passphrase or corrupt private key\")\n\t}\n\n\treturn plaintext, nil\n}\n\nfunc aes256DeriveKey(passphrase []byte, params *Argon2Parameters) ([]byte, error) {\n\tif params.salt == nil {\n\t\tparams.salt = make([]byte, 32)\n\t\tif _, err := rand.Read(params.salt); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\t// keySize of 32 bytes will result in AES-256 encryption\n\tkey, err := deriveKey(passphrase, 32, params)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn key, nil\n}\n\n// Derives a key from a passphrase using Argon2id\nfunc deriveKey(passphrase []byte, keySize uint32, params *Argon2Parameters) ([]byte, error) {\n\tif params.version != argon2.Version {\n\t\treturn nil, fmt.Errorf(\"incompatible Argon2 version: %d\", params.version)\n\t}\n\n\tif params.salt == nil {\n\t\treturn nil, fmt.Errorf(\"salt must be set in argon2Parameters\")\n\t} else if len(params.salt) < 16 {\n\t\treturn nil, fmt.Errorf(\"salt must be at least 128  bits\")\n\t}\n\n\tkey := argon2.IDKey(passphrase, params.salt, params.Iterations, params.Memory, params.Parallelism, keySize)\n\n\treturn key, nil\n}\n\n// Prepends nonce to ciphertext\nfunc joinNonceCiphertext(nonce []byte, ciphertext []byte) []byte {\n\treturn append(nonce, ciphertext...)\n}\n\n// Splits nonce from ciphertext\nfunc splitNonceCiphertext(blob []byte, nonceSize int) ([]byte, []byte, error) {\n\tif len(blob) <= nonceSize {\n\t\treturn nil, nil, fmt.Errorf(\"invalid ciphertext blob - blob shorter than nonce length\")\n\t}\n\n\treturn blob[:nonceSize], blob[nonceSize:], nil\n}\n\n// EncryptAndMarshalSigningPrivateKey is a simple helper to encrypt and PEM encode a private key\nfunc EncryptAndMarshalSigningPrivateKey(curve Curve, b []byte, passphrase []byte, kdfParams *Argon2Parameters) ([]byte, error) {\n\tciphertext, err := aes256Encrypt(passphrase, kdfParams, b)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tb, err = proto.Marshal(&RawNebulaEncryptedData{\n\t\tEncryptionMetadata: &RawNebulaEncryptionMetadata{\n\t\t\tEncryptionAlgorithm: \"AES-256-GCM\",\n\t\t\tArgon2Parameters: &RawNebulaArgon2Parameters{\n\t\t\t\tVersion:     kdfParams.version,\n\t\t\t\tMemory:      kdfParams.Memory,\n\t\t\t\tParallelism: uint32(kdfParams.Parallelism),\n\t\t\t\tIterations:  kdfParams.Iterations,\n\t\t\t\tSalt:        kdfParams.salt,\n\t\t\t},\n\t\t},\n\t\tCiphertext: ciphertext,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch curve {\n\tcase Curve_CURVE25519:\n\t\treturn pem.EncodeToMemory(&pem.Block{Type: EncryptedEd25519PrivateKeyBanner, Bytes: b}), nil\n\tcase Curve_P256:\n\t\treturn pem.EncodeToMemory(&pem.Block{Type: EncryptedECDSAP256PrivateKeyBanner, Bytes: b}), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"invalid curve: %v\", curve)\n\t}\n}\n\n// UnmarshalNebulaEncryptedData will unmarshal a protobuf byte representation of a nebula cert into its\n// protobuf-generated struct.\nfunc UnmarshalNebulaEncryptedData(b []byte) (*NebulaEncryptedData, error) {\n\tif len(b) == 0 {\n\t\treturn nil, fmt.Errorf(\"nil byte array\")\n\t}\n\tvar rned RawNebulaEncryptedData\n\terr := proto.Unmarshal(b, &rned)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif rned.EncryptionMetadata == nil {\n\t\treturn nil, fmt.Errorf(\"encoded EncryptionMetadata was nil\")\n\t}\n\n\tif rned.EncryptionMetadata.Argon2Parameters == nil {\n\t\treturn nil, fmt.Errorf(\"encoded Argon2Parameters was nil\")\n\t}\n\n\tparams, err := unmarshalArgon2Parameters(rned.EncryptionMetadata.Argon2Parameters)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tned := NebulaEncryptedData{\n\t\tEncryptionMetadata: NebulaEncryptionMetadata{\n\t\t\tEncryptionAlgorithm: rned.EncryptionMetadata.EncryptionAlgorithm,\n\t\t\tArgon2Parameters:    *params,\n\t\t},\n\t\tCiphertext: rned.Ciphertext,\n\t}\n\n\treturn &ned, nil\n}\n\nfunc unmarshalArgon2Parameters(params *RawNebulaArgon2Parameters) (*Argon2Parameters, error) {\n\tif params.Version < math.MinInt32 || params.Version > math.MaxInt32 {\n\t\treturn nil, fmt.Errorf(\"Argon2Parameters Version must be at least %d and no more than %d\", math.MinInt32, math.MaxInt32)\n\t}\n\tif params.Memory <= 0 || params.Memory > math.MaxUint32 {\n\t\treturn nil, fmt.Errorf(\"Argon2Parameters Memory must be be greater than 0 and no more than %d KiB\", uint32(math.MaxUint32))\n\t}\n\tif params.Parallelism <= 0 || params.Parallelism > math.MaxUint8 {\n\t\treturn nil, fmt.Errorf(\"Argon2Parameters Parallelism must be be greater than 0 and no more than %d\", math.MaxUint8)\n\t}\n\tif params.Iterations <= 0 || params.Iterations > math.MaxUint32 {\n\t\treturn nil, fmt.Errorf(\"-argon-iterations must be be greater than 0 and no more than %d\", uint32(math.MaxUint32))\n\t}\n\n\treturn &Argon2Parameters{\n\t\tversion:     params.Version,\n\t\tMemory:      params.Memory,\n\t\tParallelism: uint8(params.Parallelism),\n\t\tIterations:  params.Iterations,\n\t\tsalt:        params.Salt,\n\t}, nil\n\n}\n\n// DecryptAndUnmarshalSigningPrivateKey will try to pem decode and decrypt an Ed25519/ECDSA private key with\n// the given passphrase, returning any other bytes b or an error on failure\nfunc DecryptAndUnmarshalSigningPrivateKey(passphrase, b []byte) (Curve, []byte, []byte, error) {\n\tvar curve Curve\n\n\tk, r := pem.Decode(b)\n\tif k == nil {\n\t\treturn curve, nil, r, fmt.Errorf(\"input did not contain a valid PEM encoded block\")\n\t}\n\n\tswitch k.Type {\n\tcase EncryptedEd25519PrivateKeyBanner:\n\t\tcurve = Curve_CURVE25519\n\tcase EncryptedECDSAP256PrivateKeyBanner:\n\t\tcurve = Curve_P256\n\tdefault:\n\t\treturn curve, nil, r, fmt.Errorf(\"bytes did not contain a proper nebula encrypted Ed25519/ECDSA private key banner\")\n\t}\n\n\tned, err := UnmarshalNebulaEncryptedData(k.Bytes)\n\tif err != nil {\n\t\treturn curve, nil, r, err\n\t}\n\n\tvar bytes []byte\n\tswitch ned.EncryptionMetadata.EncryptionAlgorithm {\n\tcase \"AES-256-GCM\":\n\t\tbytes, err = aes256Decrypt(passphrase, &ned.EncryptionMetadata.Argon2Parameters, ned.Ciphertext)\n\t\tif err != nil {\n\t\t\treturn curve, nil, r, err\n\t\t}\n\tdefault:\n\t\treturn curve, nil, r, fmt.Errorf(\"unsupported encryption algorithm: %s\", ned.EncryptionMetadata.EncryptionAlgorithm)\n\t}\n\n\tswitch curve {\n\tcase Curve_CURVE25519:\n\t\tif len(bytes) != ed25519.PrivateKeySize {\n\t\t\treturn curve, nil, r, fmt.Errorf(\"key was not %d bytes, is invalid ed25519 private key\", ed25519.PrivateKeySize)\n\t\t}\n\tcase Curve_P256:\n\t\tif len(bytes) != 32 {\n\t\t\treturn curve, nil, r, fmt.Errorf(\"key was not 32 bytes, is invalid ECDSA P256 private key\")\n\t\t}\n\t}\n\n\treturn curve, bytes, r, nil\n}\n"
  },
  {
    "path": "cert/crypto_test.go",
    "content": "package cert\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"golang.org/x/crypto/argon2\"\n)\n\nfunc TestNewArgon2Parameters(t *testing.T) {\n\tp := NewArgon2Parameters(64*1024, 4, 3)\n\tassert.Equal(t, &Argon2Parameters{\n\t\tversion:     argon2.Version,\n\t\tMemory:      64 * 1024,\n\t\tParallelism: 4,\n\t\tIterations:  3,\n\t}, p)\n\tp = NewArgon2Parameters(2*1024*1024, 2, 1)\n\tassert.Equal(t, &Argon2Parameters{\n\t\tversion:     argon2.Version,\n\t\tMemory:      2 * 1024 * 1024,\n\t\tParallelism: 2,\n\t\tIterations:  1,\n\t}, p)\n}\n\nfunc TestDecryptAndUnmarshalSigningPrivateKey(t *testing.T) {\n\tpassphrase := []byte(\"DO NOT USE\")\n\tprivKey := []byte(`# A good key\n-----BEGIN NEBULA ED25519 ENCRYPTED PRIVATE KEY-----\nCjsKC0FFUy0yNTYtR0NNEiwIExCAgAQYAyAEKiCPoDfGQiosxNPTbPn5EsMlc2MI\nc0Bt4oz6gTrFQhX3aBJcimhHKeAuhyTGvllD0Z19fe+DFPcLH3h5VrdjVfIAajg0\nKrbV3n9UHif/Au5skWmquNJzoW1E4MTdRbvpti6o+WdQ49DxjBFhx0YH8LBqrbPU\n0BGkUHmIO7daP24=\n-----END NEBULA ED25519 ENCRYPTED PRIVATE KEY-----\n`)\n\tshortKey := []byte(`# A key which, once decrypted, is too short\n-----BEGIN NEBULA ED25519 ENCRYPTED PRIVATE KEY-----\nCjsKC0FFUy0yNTYtR0NNEiwIExCAgAQYAyAEKiAVJwdfl3r+eqi/vF6S7OMdpjfo\nhAzmTCRnr58Su4AqmBJbCv3zleYCEKYJP6UI3S8ekLMGISsgO4hm5leukCCyqT0Z\ncQ76yrberpzkJKoPLGisX8f+xdy4aXSZl7oEYWQte1+vqbtl/eY9PGZhxUQdcyq7\nhqzIyrRqfUgVuA==\n-----END NEBULA ED25519 ENCRYPTED PRIVATE KEY-----\n`)\n\tinvalidBanner := []byte(`# Invalid banner (not encrypted)\n-----BEGIN NEBULA ED25519 PRIVATE KEY-----\nbWRp2CTVFhW9HD/qCd28ltDgK3w8VXSeaEYczDWos8sMUBqDb9jP3+NYwcS4lURG\nXgLvodMXZJuaFPssp+WwtA==\n-----END NEBULA ED25519 PRIVATE KEY-----\n`)\n\tinvalidPem := []byte(`# Not a valid PEM format\n-BEGIN NEBULA ED25519 ENCRYPTED PRIVATE KEY-----\nCjwKC0FFUy0yNTYtR0NNEi0IExCAgIABGAEgBCognnjujd67Vsv99p22wfAjQaDT\noCMW1mdjkU3gACKNW4MSXOWR9Sts4C81yk1RUku2gvGKs3TB9LYoklLsIizSYOLl\n+Vs//O1T0I1Xbml2XBAROsb/VSoDln/6LMqR4B6fn6B3GOsLBBqRI8daDl9lRMPB\nqrlJ69wer3ZUHFXA\n-END NEBULA ED25519 ENCRYPTED PRIVATE KEY-----\n`)\n\n\tkeyBundle := appendByteSlices(privKey, shortKey, invalidBanner, invalidPem)\n\n\t// Success test case\n\tcurve, k, rest, err := DecryptAndUnmarshalSigningPrivateKey(passphrase, keyBundle)\n\trequire.NoError(t, err)\n\tassert.Equal(t, Curve_CURVE25519, curve)\n\tassert.Len(t, k, 64)\n\tassert.Equal(t, rest, appendByteSlices(shortKey, invalidBanner, invalidPem))\n\n\t// Fail due to short key\n\tcurve, k, rest, err = DecryptAndUnmarshalSigningPrivateKey(passphrase, rest)\n\trequire.EqualError(t, err, \"key was not 64 bytes, is invalid ed25519 private key\")\n\tassert.Nil(t, k)\n\tassert.Equal(t, rest, appendByteSlices(invalidBanner, invalidPem))\n\n\t// Fail due to invalid banner\n\tcurve, k, rest, err = DecryptAndUnmarshalSigningPrivateKey(passphrase, rest)\n\trequire.EqualError(t, err, \"bytes did not contain a proper nebula encrypted Ed25519/ECDSA private key banner\")\n\tassert.Nil(t, k)\n\tassert.Equal(t, rest, invalidPem)\n\n\t// Fail due to invalid PEM format, because\n\t// it's missing the requisite pre-encapsulation boundary.\n\tcurve, k, rest, err = DecryptAndUnmarshalSigningPrivateKey(passphrase, rest)\n\trequire.EqualError(t, err, \"input did not contain a valid PEM encoded block\")\n\tassert.Nil(t, k)\n\tassert.Equal(t, rest, invalidPem)\n\n\t// Fail due to invalid passphrase\n\tcurve, k, rest, err = DecryptAndUnmarshalSigningPrivateKey([]byte(\"invalid passphrase\"), privKey)\n\trequire.EqualError(t, err, \"invalid passphrase or corrupt private key\")\n\tassert.Nil(t, k)\n\tassert.Equal(t, []byte{}, rest)\n}\n\nfunc TestEncryptAndMarshalSigningPrivateKey(t *testing.T) {\n\t// Having proved that decryption works correctly above, we can test the\n\t// encryption function produces a value which can be decrypted\n\tpassphrase := []byte(\"passphrase\")\n\tbytes := []byte(\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\")\n\tkdfParams := NewArgon2Parameters(64*1024, 4, 3)\n\tkey, err := EncryptAndMarshalSigningPrivateKey(Curve_CURVE25519, bytes, passphrase, kdfParams)\n\trequire.NoError(t, err)\n\n\t// Verify the \"key\" can be decrypted successfully\n\tcurve, k, rest, err := DecryptAndUnmarshalSigningPrivateKey(passphrase, key)\n\tassert.Len(t, k, 64)\n\tassert.Equal(t, Curve_CURVE25519, curve)\n\tassert.Equal(t, []byte{}, rest)\n\trequire.NoError(t, err)\n\n\t// EncryptAndMarshalEd25519PrivateKey does not create any errors itself\n}\n"
  },
  {
    "path": "cert/errors.go",
    "content": "package cert\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n)\n\nvar (\n\tErrBadFormat                  = errors.New(\"bad wire format\")\n\tErrRootExpired                = errors.New(\"root certificate is expired\")\n\tErrExpired                    = errors.New(\"certificate is expired\")\n\tErrNotCA                      = errors.New(\"certificate is not a CA\")\n\tErrNotSelfSigned              = errors.New(\"certificate is not self-signed\")\n\tErrBlockListed                = errors.New(\"certificate is in the block list\")\n\tErrFingerprintMismatch        = errors.New(\"certificate fingerprint did not match\")\n\tErrSignatureMismatch          = errors.New(\"certificate signature did not match\")\n\tErrInvalidPublicKey           = errors.New(\"invalid public key\")\n\tErrInvalidPrivateKey          = errors.New(\"invalid private key\")\n\tErrPublicPrivateCurveMismatch = errors.New(\"public key does not match private key curve\")\n\tErrPublicPrivateKeyMismatch   = errors.New(\"public key and private key are not a pair\")\n\tErrPrivateKeyEncrypted        = errors.New(\"private key must be decrypted\")\n\tErrCaNotFound                 = errors.New(\"could not find ca for the certificate\")\n\tErrUnknownVersion             = errors.New(\"certificate version unrecognized\")\n\tErrCertPubkeyPresent          = errors.New(\"certificate has unexpected pubkey present\")\n\n\tErrInvalidPEMBlock                   = errors.New(\"input did not contain a valid PEM encoded block\")\n\tErrInvalidPEMCertificateBanner       = errors.New(\"bytes did not contain a proper certificate banner\")\n\tErrInvalidPEMX25519PublicKeyBanner   = errors.New(\"bytes did not contain a proper X25519 public key banner\")\n\tErrInvalidPEMX25519PrivateKeyBanner  = errors.New(\"bytes did not contain a proper X25519 private key banner\")\n\tErrInvalidPEMEd25519PublicKeyBanner  = errors.New(\"bytes did not contain a proper Ed25519 public key banner\")\n\tErrInvalidPEMEd25519PrivateKeyBanner = errors.New(\"bytes did not contain a proper Ed25519 private key banner\")\n\n\tErrNoPeerStaticKey = errors.New(\"no peer static key was present\")\n\tErrNoPayload       = errors.New(\"provided payload was empty\")\n\n\tErrMissingDetails  = errors.New(\"certificate did not contain details\")\n\tErrEmptySignature  = errors.New(\"empty signature\")\n\tErrEmptyRawDetails = errors.New(\"empty rawDetails not allowed\")\n)\n\ntype ErrInvalidCertificateProperties struct {\n\tstr string\n}\n\nfunc NewErrInvalidCertificateProperties(format string, a ...any) error {\n\treturn &ErrInvalidCertificateProperties{fmt.Sprintf(format, a...)}\n}\n\nfunc (e *ErrInvalidCertificateProperties) Error() string {\n\treturn e.str\n}\n"
  },
  {
    "path": "cert/helper_test.go",
    "content": "package cert\n\nimport (\n\t\"crypto/ecdh\"\n\t\"crypto/ecdsa\"\n\t\"crypto/elliptic\"\n\t\"crypto/rand\"\n\t\"io\"\n\t\"net/netip\"\n\t\"time\"\n\n\t\"golang.org/x/crypto/curve25519\"\n\t\"golang.org/x/crypto/ed25519\"\n)\n\n// NewTestCaCert will create a new ca certificate\nfunc NewTestCaCert(version Version, curve Curve, before, after time.Time, networks, unsafeNetworks []netip.Prefix, groups []string) (Certificate, []byte, []byte, []byte) {\n\tvar err error\n\tvar pub, priv []byte\n\n\tswitch curve {\n\tcase Curve_CURVE25519:\n\t\tpub, priv, err = ed25519.GenerateKey(rand.Reader)\n\tcase Curve_P256:\n\t\tprivk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tpub = elliptic.Marshal(elliptic.P256(), privk.PublicKey.X, privk.PublicKey.Y)\n\t\tpriv = privk.D.FillBytes(make([]byte, 32))\n\tdefault:\n\t\t// There is no default to allow the underlying lib to respond with an error\n\t}\n\n\tif before.IsZero() {\n\t\tbefore = time.Now().Add(time.Second * -60).Round(time.Second)\n\t}\n\tif after.IsZero() {\n\t\tafter = time.Now().Add(time.Second * 60).Round(time.Second)\n\t}\n\n\tt := &TBSCertificate{\n\t\tCurve:          curve,\n\t\tVersion:        version,\n\t\tName:           \"test ca\",\n\t\tNotBefore:      time.Unix(before.Unix(), 0),\n\t\tNotAfter:       time.Unix(after.Unix(), 0),\n\t\tPublicKey:      pub,\n\t\tNetworks:       networks,\n\t\tUnsafeNetworks: unsafeNetworks,\n\t\tGroups:         groups,\n\t\tIsCA:           true,\n\t}\n\n\tc, err := t.Sign(nil, curve, priv)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tpem, err := c.MarshalPEM()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn c, pub, priv, pem\n}\n\n// NewTestCert will generate a signed certificate with the provided details.\n// Expiry times are defaulted if you do not pass them in\nfunc NewTestCert(v Version, curve Curve, ca Certificate, key []byte, name string, before, after time.Time, networks, unsafeNetworks []netip.Prefix, groups []string) (Certificate, []byte, []byte, []byte) {\n\tif before.IsZero() {\n\t\tbefore = time.Now().Add(time.Second * -60).Round(time.Second)\n\t}\n\n\tif after.IsZero() {\n\t\tafter = time.Now().Add(time.Second * 60).Round(time.Second)\n\t}\n\n\tif len(networks) == 0 {\n\t\tnetworks = []netip.Prefix{netip.MustParsePrefix(\"10.0.0.123/8\")}\n\t}\n\n\tvar pub, priv []byte\n\tswitch curve {\n\tcase Curve_CURVE25519:\n\t\tpub, priv = X25519Keypair()\n\tcase Curve_P256:\n\t\tpub, priv = P256Keypair()\n\tdefault:\n\t\tpanic(\"unknown curve\")\n\t}\n\n\tnc := &TBSCertificate{\n\t\tVersion:        v,\n\t\tCurve:          curve,\n\t\tName:           name,\n\t\tNetworks:       networks,\n\t\tUnsafeNetworks: unsafeNetworks,\n\t\tGroups:         groups,\n\t\tNotBefore:      time.Unix(before.Unix(), 0),\n\t\tNotAfter:       time.Unix(after.Unix(), 0),\n\t\tPublicKey:      pub,\n\t\tIsCA:           false,\n\t}\n\n\tc, err := nc.Sign(ca, ca.Curve(), key)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tpem, err := c.MarshalPEM()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn c, pub, MarshalPrivateKeyToPEM(curve, priv), pem\n}\n\nfunc X25519Keypair() ([]byte, []byte) {\n\tprivkey := make([]byte, 32)\n\tif _, err := io.ReadFull(rand.Reader, privkey); err != nil {\n\t\tpanic(err)\n\t}\n\n\tpubkey, err := curve25519.X25519(privkey, curve25519.Basepoint)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn pubkey, privkey\n}\n\nfunc P256Keypair() ([]byte, []byte) {\n\tprivkey, err := ecdh.P256().GenerateKey(rand.Reader)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tpubkey := privkey.PublicKey()\n\treturn pubkey.Bytes(), privkey.Bytes()\n}\n"
  },
  {
    "path": "cert/p256/p256.go",
    "content": "package p256\n\nimport (\n\t\"crypto/elliptic\"\n\t\"errors\"\n\t\"math/big\"\n\n\t\"filippo.io/bigmod\"\n\n\t\"golang.org/x/crypto/cryptobyte\"\n\t\"golang.org/x/crypto/cryptobyte/asn1\"\n)\n\nvar halfN = new(big.Int).Rsh(elliptic.P256().Params().N, 1)\nvar nMod *bigmod.Modulus\n\nfunc init() {\n\tn, err := bigmod.NewModulus(elliptic.P256().Params().N.Bytes())\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tnMod = n\n}\n\nfunc IsNormalized(sig []byte) (bool, error) {\n\tr, s, err := parseSignature(sig)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn checkLowS(r, s), nil\n}\n\nfunc checkLowS(_, s []byte) bool {\n\tbigS := new(big.Int).SetBytes(s)\n\t// Check if S <= (N/2), because we want to include the midpoint in the set of low-s\n\treturn bigS.Cmp(halfN) <= 0\n}\n\nfunc swap(r, s []byte) ([]byte, []byte, error) {\n\tvar err error\n\tbigS, err := bigmod.NewNat().SetBytes(s, nMod)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tsNormalized := nMod.Nat().Sub(bigS, nMod)\n\n\treturn r, sNormalized.Bytes(nMod), nil\n}\n\nfunc Normalize(sig []byte) ([]byte, error) {\n\tr, s, err := parseSignature(sig)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif checkLowS(r, s) {\n\t\treturn sig, nil\n\t}\n\n\tnewR, newS, err := swap(r, s)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn encodeSignature(newR, newS)\n}\n\n// Swap will change sig between its current form to the opposite high or low form.\nfunc Swap(sig []byte) ([]byte, error) {\n\tr, s, err := parseSignature(sig)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tnewR, newS, err := swap(r, s)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn encodeSignature(newR, newS)\n}\n\n// parseSignature taken exactly from crypto/ecdsa/ecdsa.go\nfunc parseSignature(sig []byte) (r, s []byte, err error) {\n\tvar inner cryptobyte.String\n\tinput := cryptobyte.String(sig)\n\tif !input.ReadASN1(&inner, asn1.SEQUENCE) ||\n\t\t!input.Empty() ||\n\t\t!inner.ReadASN1Integer(&r) ||\n\t\t!inner.ReadASN1Integer(&s) ||\n\t\t!inner.Empty() {\n\t\treturn nil, nil, errors.New(\"invalid ASN.1\")\n\t}\n\treturn r, s, nil\n}\n\nfunc encodeSignature(r, s []byte) ([]byte, error) {\n\tvar b cryptobyte.Builder\n\tb.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) {\n\t\taddASN1IntBytes(b, r)\n\t\taddASN1IntBytes(b, s)\n\t})\n\treturn b.Bytes()\n}\n\n// addASN1IntBytes encodes in ASN.1 a positive integer represented as\n// a big-endian byte slice with zero or more leading zeroes.\nfunc addASN1IntBytes(b *cryptobyte.Builder, bytes []byte) {\n\tfor len(bytes) > 0 && bytes[0] == 0 {\n\t\tbytes = bytes[1:]\n\t}\n\tif len(bytes) == 0 {\n\t\tb.SetError(errors.New(\"invalid integer\"))\n\t\treturn\n\t}\n\tb.AddASN1(asn1.INTEGER, func(c *cryptobyte.Builder) {\n\t\tif bytes[0]&0x80 != 0 {\n\t\t\tc.AddUint8(0)\n\t\t}\n\t\tc.AddBytes(bytes)\n\t})\n}\n"
  },
  {
    "path": "cert/p256/p256_test.go",
    "content": "package p256\n\nimport (\n\t\"crypto/ecdsa\"\n\t\"crypto/elliptic\"\n\t\"crypto/rand\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestFlipping(t *testing.T) {\n\tpriv, err1 := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)\n\trequire.NoError(t, err1)\n\n\tout, err := ecdsa.SignASN1(rand.Reader, priv, []byte(\"big chungus\"))\n\trequire.NoError(t, err)\n\n\tr, s, err := parseSignature(out)\n\trequire.NoError(t, err)\n\n\tr, s1, err := swap(r, s)\n\trequire.NoError(t, err)\n\tr, s2, err := swap(r, s1)\n\trequire.NoError(t, err)\n\trequire.Equal(t, s, s2)\n\trequire.NotEqual(t, s, s1)\n}\n"
  },
  {
    "path": "cert/pem.go",
    "content": "package cert\n\nimport (\n\t\"encoding/pem\"\n\t\"fmt\"\n\n\t\"golang.org/x/crypto/ed25519\"\n)\n\nconst ( //cert banners\n\tCertificateBanner   = \"NEBULA CERTIFICATE\"\n\tCertificateV2Banner = \"NEBULA CERTIFICATE V2\"\n)\n\nconst ( //key-agreement-key banners\n\tX25519PrivateKeyBanner = \"NEBULA X25519 PRIVATE KEY\"\n\tX25519PublicKeyBanner  = \"NEBULA X25519 PUBLIC KEY\"\n\tP256PrivateKeyBanner   = \"NEBULA P256 PRIVATE KEY\"\n\tP256PublicKeyBanner    = \"NEBULA P256 PUBLIC KEY\"\n)\n\n/* including \"ECDSA\" in the P256 banners is a clue that these keys should be used only for signing */\nconst ( //signing key banners\n\tEncryptedECDSAP256PrivateKeyBanner = \"NEBULA ECDSA P256 ENCRYPTED PRIVATE KEY\"\n\tECDSAP256PrivateKeyBanner          = \"NEBULA ECDSA P256 PRIVATE KEY\"\n\tECDSAP256PublicKeyBanner           = \"NEBULA ECDSA P256 PUBLIC KEY\"\n\tEncryptedEd25519PrivateKeyBanner   = \"NEBULA ED25519 ENCRYPTED PRIVATE KEY\"\n\tEd25519PrivateKeyBanner            = \"NEBULA ED25519 PRIVATE KEY\"\n\tEd25519PublicKeyBanner             = \"NEBULA ED25519 PUBLIC KEY\"\n)\n\n// UnmarshalCertificateFromPEM will try to unmarshal the first pem block in a byte array, returning any non consumed\n// data or an error on failure\nfunc UnmarshalCertificateFromPEM(b []byte) (Certificate, []byte, error) {\n\tp, r := pem.Decode(b)\n\tif p == nil {\n\t\treturn nil, r, ErrInvalidPEMBlock\n\t}\n\n\tvar c Certificate\n\tvar err error\n\n\tswitch p.Type {\n\t// Implementations must validate the resulting certificate contains valid information\n\tcase CertificateBanner:\n\t\tc, err = unmarshalCertificateV1(p.Bytes, nil)\n\tcase CertificateV2Banner:\n\t\tc, err = unmarshalCertificateV2(p.Bytes, nil, Curve_CURVE25519)\n\tdefault:\n\t\treturn nil, r, ErrInvalidPEMCertificateBanner\n\t}\n\n\tif err != nil {\n\t\treturn nil, r, err\n\t}\n\n\treturn c, r, nil\n\n}\n\nfunc marshalCertPublicKeyToPEM(c Certificate) []byte {\n\tif c.IsCA() {\n\t\treturn MarshalSigningPublicKeyToPEM(c.Curve(), c.PublicKey())\n\t} else {\n\t\treturn MarshalPublicKeyToPEM(c.Curve(), c.PublicKey())\n\t}\n}\n\n// MarshalPublicKeyToPEM returns a PEM representation of a public key used for ECDH.\n// if your public key came from a certificate, prefer Certificate.PublicKeyPEM() if possible, to avoid mistakes!\nfunc MarshalPublicKeyToPEM(curve Curve, b []byte) []byte {\n\tswitch curve {\n\tcase Curve_CURVE25519:\n\t\treturn pem.EncodeToMemory(&pem.Block{Type: X25519PublicKeyBanner, Bytes: b})\n\tcase Curve_P256:\n\t\treturn pem.EncodeToMemory(&pem.Block{Type: P256PublicKeyBanner, Bytes: b})\n\tdefault:\n\t\treturn nil\n\t}\n}\n\n// MarshalSigningPublicKeyToPEM returns a PEM representation of a public key used for signing.\n// if your public key came from a certificate, prefer Certificate.PublicKeyPEM() if possible, to avoid mistakes!\nfunc MarshalSigningPublicKeyToPEM(curve Curve, b []byte) []byte {\n\tswitch curve {\n\tcase Curve_CURVE25519:\n\t\treturn pem.EncodeToMemory(&pem.Block{Type: Ed25519PublicKeyBanner, Bytes: b})\n\tcase Curve_P256:\n\t\treturn pem.EncodeToMemory(&pem.Block{Type: ECDSAP256PublicKeyBanner, Bytes: b})\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc UnmarshalPublicKeyFromPEM(b []byte) ([]byte, []byte, Curve, error) {\n\tk, r := pem.Decode(b)\n\tif k == nil {\n\t\treturn nil, r, 0, fmt.Errorf(\"input did not contain a valid PEM encoded block\")\n\t}\n\tvar expectedLen int\n\tvar curve Curve\n\tswitch k.Type {\n\tcase X25519PublicKeyBanner, Ed25519PublicKeyBanner:\n\t\texpectedLen = 32\n\t\tcurve = Curve_CURVE25519\n\tcase P256PublicKeyBanner, ECDSAP256PublicKeyBanner:\n\t\t// Uncompressed\n\t\texpectedLen = 65\n\t\tcurve = Curve_P256\n\tdefault:\n\t\treturn nil, r, 0, fmt.Errorf(\"bytes did not contain a proper public key banner\")\n\t}\n\tif len(k.Bytes) != expectedLen {\n\t\treturn nil, r, 0, fmt.Errorf(\"key was not %d bytes, is invalid %s public key\", expectedLen, curve)\n\t}\n\treturn k.Bytes, r, curve, nil\n}\n\nfunc MarshalPrivateKeyToPEM(curve Curve, b []byte) []byte {\n\tswitch curve {\n\tcase Curve_CURVE25519:\n\t\treturn pem.EncodeToMemory(&pem.Block{Type: X25519PrivateKeyBanner, Bytes: b})\n\tcase Curve_P256:\n\t\treturn pem.EncodeToMemory(&pem.Block{Type: P256PrivateKeyBanner, Bytes: b})\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc MarshalSigningPrivateKeyToPEM(curve Curve, b []byte) []byte {\n\tswitch curve {\n\tcase Curve_CURVE25519:\n\t\treturn pem.EncodeToMemory(&pem.Block{Type: Ed25519PrivateKeyBanner, Bytes: b})\n\tcase Curve_P256:\n\t\treturn pem.EncodeToMemory(&pem.Block{Type: ECDSAP256PrivateKeyBanner, Bytes: b})\n\tdefault:\n\t\treturn nil\n\t}\n}\n\n// UnmarshalPrivateKeyFromPEM will try to unmarshal the first pem block in a byte array, returning any non\n// consumed data or an error on failure\nfunc UnmarshalPrivateKeyFromPEM(b []byte) ([]byte, []byte, Curve, error) {\n\tk, r := pem.Decode(b)\n\tif k == nil {\n\t\treturn nil, r, 0, fmt.Errorf(\"input did not contain a valid PEM encoded block\")\n\t}\n\tvar expectedLen int\n\tvar curve Curve\n\tswitch k.Type {\n\tcase X25519PrivateKeyBanner:\n\t\texpectedLen = 32\n\t\tcurve = Curve_CURVE25519\n\tcase P256PrivateKeyBanner:\n\t\texpectedLen = 32\n\t\tcurve = Curve_P256\n\tdefault:\n\t\treturn nil, r, 0, fmt.Errorf(\"bytes did not contain a proper private key banner\")\n\t}\n\tif len(k.Bytes) != expectedLen {\n\t\treturn nil, r, 0, fmt.Errorf(\"key was not %d bytes, is invalid %s private key\", expectedLen, curve)\n\t}\n\treturn k.Bytes, r, curve, nil\n}\n\nfunc UnmarshalSigningPrivateKeyFromPEM(b []byte) ([]byte, []byte, Curve, error) {\n\tk, r := pem.Decode(b)\n\tif k == nil {\n\t\treturn nil, r, 0, fmt.Errorf(\"input did not contain a valid PEM encoded block\")\n\t}\n\tvar curve Curve\n\tswitch k.Type {\n\tcase EncryptedEd25519PrivateKeyBanner:\n\t\treturn nil, nil, Curve_CURVE25519, ErrPrivateKeyEncrypted\n\tcase EncryptedECDSAP256PrivateKeyBanner:\n\t\treturn nil, nil, Curve_P256, ErrPrivateKeyEncrypted\n\tcase Ed25519PrivateKeyBanner:\n\t\tcurve = Curve_CURVE25519\n\t\tif len(k.Bytes) != ed25519.PrivateKeySize {\n\t\t\treturn nil, r, 0, fmt.Errorf(\"key was not %d bytes, is invalid Ed25519 private key\", ed25519.PrivateKeySize)\n\t\t}\n\tcase ECDSAP256PrivateKeyBanner:\n\t\tcurve = Curve_P256\n\t\tif len(k.Bytes) != 32 {\n\t\t\treturn nil, r, 0, fmt.Errorf(\"key was not 32 bytes, is invalid ECDSA P256 private key\")\n\t\t}\n\tdefault:\n\t\treturn nil, r, 0, fmt.Errorf(\"bytes did not contain a proper Ed25519/ECDSA private key banner\")\n\t}\n\treturn k.Bytes, r, curve, nil\n}\n"
  },
  {
    "path": "cert/pem_test.go",
    "content": "package cert\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestUnmarshalCertificateFromPEM(t *testing.T) {\n\tgoodCert := []byte(`\n# A good cert\n-----BEGIN NEBULA CERTIFICATE-----\nCkAKDm5lYnVsYSByb290IGNhKJfap9AFMJfg1+YGOiCUQGByMuNRhIlQBOyzXWbL\nvcKBwDhov900phEfJ5DN3kABEkDCq5R8qBiu8sl54yVfgRcQXEDt3cHr8UTSLszv\nbzBEr00kERQxxTzTsH8cpYEgRoipvmExvg8WP8NdAJEYJosB\n-----END NEBULA CERTIFICATE-----\n`)\n\tbadBanner := []byte(`# A bad banner\n-----BEGIN NOT A NEBULA CERTIFICATE-----\nCkAKDm5lYnVsYSByb290IGNhKJfap9AFMJfg1+YGOiCUQGByMuNRhIlQBOyzXWbL\nvcKBwDhov900phEfJ5DN3kABEkDCq5R8qBiu8sl54yVfgRcQXEDt3cHr8UTSLszv\nbzBEr00kERQxxTzTsH8cpYEgRoipvmExvg8WP8NdAJEYJosB\n-----END NOT A NEBULA CERTIFICATE-----\n`)\n\tinvalidPem := []byte(`# Not a valid PEM format\n-BEGIN NEBULA CERTIFICATE-----\nCkAKDm5lYnVsYSByb290IGNhKJfap9AFMJfg1+YGOiCUQGByMuNRhIlQBOyzXWbL\nvcKBwDhov900phEfJ5DN3kABEkDCq5R8qBiu8sl54yVfgRcQXEDt3cHr8UTSLszv\nbzBEr00kERQxxTzTsH8cpYEgRoipvmExvg8WP8NdAJEYJosB\n-END NEBULA CERTIFICATE----`)\n\n\tcertBundle := appendByteSlices(goodCert, badBanner, invalidPem)\n\n\t// Success test case\n\tcert, rest, err := UnmarshalCertificateFromPEM(certBundle)\n\tassert.NotNil(t, cert)\n\tassert.Equal(t, rest, append(badBanner, invalidPem...))\n\trequire.NoError(t, err)\n\n\t// Fail due to invalid banner.\n\tcert, rest, err = UnmarshalCertificateFromPEM(rest)\n\tassert.Nil(t, cert)\n\tassert.Equal(t, rest, invalidPem)\n\trequire.EqualError(t, err, \"bytes did not contain a proper certificate banner\")\n\n\t// Fail due to invalid PEM format, because\n\t// it's missing the requisite pre-encapsulation boundary.\n\tcert, rest, err = UnmarshalCertificateFromPEM(rest)\n\tassert.Nil(t, cert)\n\tassert.Equal(t, rest, invalidPem)\n\trequire.EqualError(t, err, \"input did not contain a valid PEM encoded block\")\n}\n\nfunc TestUnmarshalSigningPrivateKeyFromPEM(t *testing.T) {\n\tprivKey := []byte(`# A good key\n-----BEGIN NEBULA ED25519 PRIVATE KEY-----\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\n-----END NEBULA ED25519 PRIVATE KEY-----\n`)\n\tprivP256Key := []byte(`# A good key\n-----BEGIN NEBULA ECDSA P256 PRIVATE KEY-----\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n-----END NEBULA ECDSA P256 PRIVATE KEY-----\n`)\n\tshortKey := []byte(`# A short key\n-----BEGIN NEBULA ED25519 PRIVATE KEY-----\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n-----END NEBULA ED25519 PRIVATE KEY-----\n`)\n\tinvalidBanner := []byte(`# Invalid banner\n-----BEGIN NOT A NEBULA PRIVATE KEY-----\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\n-----END NOT A NEBULA PRIVATE KEY-----\n`)\n\tinvalidPem := []byte(`# Not a valid PEM format\n-BEGIN NEBULA ED25519 PRIVATE KEY-----\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\n-END NEBULA ED25519 PRIVATE KEY-----`)\n\n\tkeyBundle := appendByteSlices(privKey, privP256Key, shortKey, invalidBanner, invalidPem)\n\n\t// Success test case\n\tk, rest, curve, err := UnmarshalSigningPrivateKeyFromPEM(keyBundle)\n\tassert.Len(t, k, 64)\n\tassert.Equal(t, rest, appendByteSlices(privP256Key, shortKey, invalidBanner, invalidPem))\n\tassert.Equal(t, Curve_CURVE25519, curve)\n\trequire.NoError(t, err)\n\n\t// Success test case\n\tk, rest, curve, err = UnmarshalSigningPrivateKeyFromPEM(rest)\n\tassert.Len(t, k, 32)\n\tassert.Equal(t, rest, appendByteSlices(shortKey, invalidBanner, invalidPem))\n\tassert.Equal(t, Curve_P256, curve)\n\trequire.NoError(t, err)\n\n\t// Fail due to short key\n\tk, rest, curve, err = UnmarshalSigningPrivateKeyFromPEM(rest)\n\tassert.Nil(t, k)\n\tassert.Equal(t, rest, appendByteSlices(invalidBanner, invalidPem))\n\trequire.EqualError(t, err, \"key was not 64 bytes, is invalid Ed25519 private key\")\n\n\t// Fail due to invalid banner\n\tk, rest, curve, err = UnmarshalSigningPrivateKeyFromPEM(rest)\n\tassert.Nil(t, k)\n\tassert.Equal(t, rest, invalidPem)\n\trequire.EqualError(t, err, \"bytes did not contain a proper Ed25519/ECDSA private key banner\")\n\n\t// Fail due to invalid PEM format, because\n\t// it's missing the requisite pre-encapsulation boundary.\n\tk, rest, curve, err = UnmarshalSigningPrivateKeyFromPEM(rest)\n\tassert.Nil(t, k)\n\tassert.Equal(t, rest, invalidPem)\n\trequire.EqualError(t, err, \"input did not contain a valid PEM encoded block\")\n}\n\nfunc TestUnmarshalPrivateKeyFromPEM(t *testing.T) {\n\tprivKey := []byte(`# A good key\n-----BEGIN NEBULA X25519 PRIVATE KEY-----\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n-----END NEBULA X25519 PRIVATE KEY-----\n`)\n\tprivP256Key := []byte(`# A good key\n-----BEGIN NEBULA P256 PRIVATE KEY-----\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n-----END NEBULA P256 PRIVATE KEY-----\n`)\n\tshortKey := []byte(`# A short key\n-----BEGIN NEBULA X25519 PRIVATE KEY-----\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\n-----END NEBULA X25519 PRIVATE KEY-----\n`)\n\tinvalidBanner := []byte(`# Invalid banner\n-----BEGIN NOT A NEBULA PRIVATE KEY-----\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n-----END NOT A NEBULA PRIVATE KEY-----\n`)\n\tinvalidPem := []byte(`# Not a valid PEM format\n-BEGIN NEBULA X25519 PRIVATE KEY-----\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n-END NEBULA X25519 PRIVATE KEY-----`)\n\n\tkeyBundle := appendByteSlices(privKey, privP256Key, shortKey, invalidBanner, invalidPem)\n\n\t// Success test case\n\tk, rest, curve, err := UnmarshalPrivateKeyFromPEM(keyBundle)\n\tassert.Len(t, k, 32)\n\tassert.Equal(t, rest, appendByteSlices(privP256Key, shortKey, invalidBanner, invalidPem))\n\tassert.Equal(t, Curve_CURVE25519, curve)\n\trequire.NoError(t, err)\n\n\t// Success test case\n\tk, rest, curve, err = UnmarshalPrivateKeyFromPEM(rest)\n\tassert.Len(t, k, 32)\n\tassert.Equal(t, rest, appendByteSlices(shortKey, invalidBanner, invalidPem))\n\tassert.Equal(t, Curve_P256, curve)\n\trequire.NoError(t, err)\n\n\t// Fail due to short key\n\tk, rest, curve, err = UnmarshalPrivateKeyFromPEM(rest)\n\tassert.Nil(t, k)\n\tassert.Equal(t, rest, appendByteSlices(invalidBanner, invalidPem))\n\trequire.EqualError(t, err, \"key was not 32 bytes, is invalid CURVE25519 private key\")\n\n\t// Fail due to invalid banner\n\tk, rest, curve, err = UnmarshalPrivateKeyFromPEM(rest)\n\tassert.Nil(t, k)\n\tassert.Equal(t, rest, invalidPem)\n\trequire.EqualError(t, err, \"bytes did not contain a proper private key banner\")\n\n\t// Fail due to invalid PEM format, because\n\t// it's missing the requisite pre-encapsulation boundary.\n\tk, rest, curve, err = UnmarshalPrivateKeyFromPEM(rest)\n\tassert.Nil(t, k)\n\tassert.Equal(t, rest, invalidPem)\n\trequire.EqualError(t, err, \"input did not contain a valid PEM encoded block\")\n}\n\nfunc TestUnmarshalPublicKeyFromPEM(t *testing.T) {\n\tt.Parallel()\n\tpubKey := []byte(`# A good key\n-----BEGIN NEBULA ED25519 PUBLIC KEY-----\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n-----END NEBULA ED25519 PUBLIC KEY-----\n`)\n\tshortKey := []byte(`# A short key\n-----BEGIN NEBULA ED25519 PUBLIC KEY-----\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\n-----END NEBULA ED25519 PUBLIC KEY-----\n`)\n\tinvalidBanner := []byte(`# Invalid banner\n-----BEGIN NOT A NEBULA PUBLIC KEY-----\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n-----END NOT A NEBULA PUBLIC KEY-----\n`)\n\tinvalidPem := []byte(`# Not a valid PEM format\n-BEGIN NEBULA ED25519 PUBLIC KEY-----\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n-END NEBULA ED25519 PUBLIC KEY-----`)\n\n\tkeyBundle := appendByteSlices(pubKey, shortKey, invalidBanner, invalidPem)\n\n\t// Success test case\n\tk, rest, curve, err := UnmarshalPublicKeyFromPEM(keyBundle)\n\tassert.Len(t, k, 32)\n\tassert.Equal(t, Curve_CURVE25519, curve)\n\trequire.NoError(t, err)\n\tassert.Equal(t, rest, appendByteSlices(shortKey, invalidBanner, invalidPem))\n\n\t// Fail due to short key\n\tk, rest, curve, err = UnmarshalPublicKeyFromPEM(rest)\n\tassert.Nil(t, k)\n\tassert.Equal(t, Curve_CURVE25519, curve)\n\tassert.Equal(t, rest, appendByteSlices(invalidBanner, invalidPem))\n\trequire.EqualError(t, err, \"key was not 32 bytes, is invalid CURVE25519 public key\")\n\n\t// Fail due to invalid banner\n\tk, rest, curve, err = UnmarshalPublicKeyFromPEM(rest)\n\tassert.Nil(t, k)\n\tassert.Equal(t, Curve_CURVE25519, curve)\n\trequire.EqualError(t, err, \"bytes did not contain a proper public key banner\")\n\tassert.Equal(t, rest, invalidPem)\n\n\t// Fail due to invalid PEM format, because\n\t// it's missing the requisite pre-encapsulation boundary.\n\tk, rest, curve, err = UnmarshalPublicKeyFromPEM(rest)\n\tassert.Nil(t, k)\n\tassert.Equal(t, Curve_CURVE25519, curve)\n\tassert.Equal(t, rest, invalidPem)\n\trequire.EqualError(t, err, \"input did not contain a valid PEM encoded block\")\n}\n\nfunc TestUnmarshalX25519PublicKey(t *testing.T) {\n\tt.Parallel()\n\tpubKey := []byte(`# A good key\n-----BEGIN NEBULA X25519 PUBLIC KEY-----\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n-----END NEBULA X25519 PUBLIC KEY-----\n`)\n\tpubP256Key := []byte(`# A good key\n-----BEGIN NEBULA P256 PUBLIC KEY-----\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAA=\n-----END NEBULA P256 PUBLIC KEY-----\n`)\n\toldPubP256Key := []byte(`# A good key\n-----BEGIN NEBULA ECDSA P256 PUBLIC KEY-----\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAA=\n-----END NEBULA ECDSA P256 PUBLIC KEY-----\n`)\n\tshortKey := []byte(`# A short key\n-----BEGIN NEBULA X25519 PUBLIC KEY-----\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\n-----END NEBULA X25519 PUBLIC KEY-----\n`)\n\tinvalidBanner := []byte(`# Invalid banner\n-----BEGIN NOT A NEBULA PUBLIC KEY-----\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n-----END NOT A NEBULA PUBLIC KEY-----\n`)\n\tinvalidPem := []byte(`# Not a valid PEM format\n-BEGIN NEBULA X25519 PUBLIC KEY-----\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n-END NEBULA X25519 PUBLIC KEY-----`)\n\n\tkeyBundle := appendByteSlices(pubKey, pubP256Key, oldPubP256Key, shortKey, invalidBanner, invalidPem)\n\n\t// Success test case\n\tk, rest, curve, err := UnmarshalPublicKeyFromPEM(keyBundle)\n\tassert.Len(t, k, 32)\n\trequire.NoError(t, err)\n\tassert.Equal(t, rest, appendByteSlices(pubP256Key, oldPubP256Key, shortKey, invalidBanner, invalidPem))\n\tassert.Equal(t, Curve_CURVE25519, curve)\n\n\t// Success test case\n\tk, rest, curve, err = UnmarshalPublicKeyFromPEM(rest)\n\tassert.Len(t, k, 65)\n\trequire.NoError(t, err)\n\tassert.Equal(t, rest, appendByteSlices(oldPubP256Key, shortKey, invalidBanner, invalidPem))\n\tassert.Equal(t, Curve_P256, curve)\n\n\t// Success test case\n\tk, rest, curve, err = UnmarshalPublicKeyFromPEM(rest)\n\tassert.Len(t, k, 65)\n\trequire.NoError(t, err)\n\tassert.Equal(t, rest, appendByteSlices(shortKey, invalidBanner, invalidPem))\n\tassert.Equal(t, Curve_P256, curve)\n\n\t// Fail due to short key\n\tk, rest, curve, err = UnmarshalPublicKeyFromPEM(rest)\n\tassert.Nil(t, k)\n\tassert.Equal(t, rest, appendByteSlices(invalidBanner, invalidPem))\n\trequire.EqualError(t, err, \"key was not 32 bytes, is invalid CURVE25519 public key\")\n\n\t// Fail due to invalid banner\n\tk, rest, curve, err = UnmarshalPublicKeyFromPEM(rest)\n\tassert.Nil(t, k)\n\trequire.EqualError(t, err, \"bytes did not contain a proper public key banner\")\n\tassert.Equal(t, rest, invalidPem)\n\n\t// Fail due to invalid PEM format, because\n\t// it's missing the requisite pre-encapsulation boundary.\n\tk, rest, curve, err = UnmarshalPublicKeyFromPEM(rest)\n\tassert.Nil(t, k)\n\tassert.Equal(t, rest, invalidPem)\n\trequire.EqualError(t, err, \"input did not contain a valid PEM encoded block\")\n}\n"
  },
  {
    "path": "cert/sign.go",
    "content": "package cert\n\nimport (\n\t\"crypto/ecdsa\"\n\t\"crypto/ed25519\"\n\t\"crypto/elliptic\"\n\t\"crypto/rand\"\n\t\"crypto/sha256\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"time\"\n\n\t\"github.com/slackhq/nebula/cert/p256\"\n)\n\n// TBSCertificate represents a certificate intended to be signed.\n// It is invalid to use this structure as a Certificate.\ntype TBSCertificate struct {\n\tVersion        Version\n\tName           string\n\tNetworks       []netip.Prefix\n\tUnsafeNetworks []netip.Prefix\n\tGroups         []string\n\tIsCA           bool\n\tNotBefore      time.Time\n\tNotAfter       time.Time\n\tPublicKey      []byte\n\tCurve          Curve\n\tissuer         string\n}\n\ntype beingSignedCertificate interface {\n\t// fromTBSCertificate copies the values from the TBSCertificate to this versions internal representation\n\t// Implementations must validate the resulting certificate contains valid information\n\tfromTBSCertificate(*TBSCertificate) error\n\n\t// marshalForSigning returns the bytes that should be signed\n\tmarshalForSigning() ([]byte, error)\n\n\t// setSignature sets the signature for the certificate that has just been signed. The signature must not be blank.\n\tsetSignature([]byte) error\n}\n\ntype SignerLambda func(certBytes []byte) ([]byte, error)\n\n// Sign will create a sealed certificate using details provided by the TBSCertificate as long as those\n// details do not violate constraints of the signing certificate.\n// If the TBSCertificate is a CA then signer must be nil.\nfunc (t *TBSCertificate) Sign(signer Certificate, curve Curve, key []byte) (Certificate, error) {\n\tswitch t.Curve {\n\tcase Curve_CURVE25519:\n\t\tpk := ed25519.PrivateKey(key)\n\t\tsp := func(certBytes []byte) ([]byte, error) {\n\t\t\tsig := ed25519.Sign(pk, certBytes)\n\t\t\treturn sig, nil\n\t\t}\n\t\treturn t.SignWith(signer, curve, sp)\n\tcase Curve_P256:\n\t\tpk, err := ecdsa.ParseRawPrivateKey(elliptic.P256(), key)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tsp := func(certBytes []byte) ([]byte, error) {\n\t\t\t// We need to hash first for ECDSA\n\t\t\t// - https://pkg.go.dev/crypto/ecdsa#SignASN1\n\t\t\thashed := sha256.Sum256(certBytes)\n\t\t\treturn ecdsa.SignASN1(rand.Reader, pk, hashed[:])\n\t\t}\n\t\treturn t.SignWith(signer, curve, sp)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"invalid curve: %s\", t.Curve)\n\t}\n}\n\n// SignWith does the same thing as sign, but uses the function in `sp` to calculate the signature.\n// You should only use SignWith if you do not have direct access to your private key.\nfunc (t *TBSCertificate) SignWith(signer Certificate, curve Curve, sp SignerLambda) (Certificate, error) {\n\tif curve != t.Curve {\n\t\treturn nil, fmt.Errorf(\"curve in cert and private key supplied don't match\")\n\t}\n\n\tif signer != nil {\n\t\tif t.IsCA {\n\t\t\treturn nil, fmt.Errorf(\"can not sign a CA certificate with another\")\n\t\t}\n\n\t\terr := checkCAConstraints(signer, t.NotBefore, t.NotAfter, t.Groups, t.Networks, t.UnsafeNetworks)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tissuer, err := signer.Fingerprint()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error computing issuer: %v\", err)\n\t\t}\n\t\tt.issuer = issuer\n\t} else {\n\t\tif !t.IsCA {\n\t\t\treturn nil, fmt.Errorf(\"self signed certificates must have IsCA set to true\")\n\t\t}\n\t}\n\n\tvar c beingSignedCertificate\n\tswitch t.Version {\n\tcase Version1:\n\t\tc = &certificateV1{}\n\t\terr := c.fromTBSCertificate(t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\tcase Version2:\n\t\tc = &certificateV2{}\n\t\terr := c.fromTBSCertificate(t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unknown cert version %d\", t.Version)\n\t}\n\n\tcertBytes, err := c.marshalForSigning()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsig, err := sp(certBytes)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif curve == Curve_P256 {\n\t\tsig, err = p256.Normalize(sig)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\terr = c.setSignature(sig)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsc, ok := c.(Certificate)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"invalid certificate\")\n\t}\n\n\treturn sc, nil\n}\n\nfunc comparePrefix(a, b netip.Prefix) int {\n\taddr := a.Addr().Compare(b.Addr())\n\tif addr == 0 {\n\t\treturn a.Bits() - b.Bits()\n\t}\n\treturn addr\n}\n\n// findDuplicatePrefix returns an error if there is a duplicate prefix in the pre-sorted input slice sortedPrefixes\nfunc findDuplicatePrefix(sortedPrefixes []netip.Prefix) error {\n\tif len(sortedPrefixes) < 2 {\n\t\treturn nil\n\t}\n\tfor i := 1; i < len(sortedPrefixes); i++ {\n\t\tif comparePrefix(sortedPrefixes[i], sortedPrefixes[i-1]) == 0 {\n\t\t\treturn NewErrInvalidCertificateProperties(\"duplicate network detected: %v\", sortedPrefixes[i])\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cert/sign_test.go",
    "content": "package cert\n\nimport (\n\t\"crypto/ecdsa\"\n\t\"crypto/ed25519\"\n\t\"crypto/elliptic\"\n\t\"crypto/rand\"\n\t\"net/netip\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/slackhq/nebula/cert/p256\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestCertificateV1_Sign(t *testing.T) {\n\tbefore := time.Now().Add(time.Second * -60).Round(time.Second)\n\tafter := time.Now().Add(time.Second * 60).Round(time.Second)\n\tpubKey := []byte(\"1234567890abcedfghij1234567890ab\")\n\n\ttbs := TBSCertificate{\n\t\tVersion: Version1,\n\t\tName:    \"testing\",\n\t\tNetworks: []netip.Prefix{\n\t\t\tmustParsePrefixUnmapped(\"10.1.1.1/24\"),\n\t\t\tmustParsePrefixUnmapped(\"10.1.1.2/16\"),\n\t\t},\n\t\tUnsafeNetworks: []netip.Prefix{\n\t\t\tmustParsePrefixUnmapped(\"9.1.1.2/24\"),\n\t\t\tmustParsePrefixUnmapped(\"9.1.1.3/24\"),\n\t\t},\n\t\tGroups:    []string{\"test-group1\", \"test-group2\", \"test-group3\"},\n\t\tNotBefore: before,\n\t\tNotAfter:  after,\n\t\tPublicKey: pubKey,\n\t\tIsCA:      false,\n\t}\n\n\tpub, priv, err := ed25519.GenerateKey(rand.Reader)\n\tc, err := tbs.Sign(&certificateV1{details: detailsV1{notBefore: before, notAfter: after}}, Curve_CURVE25519, priv)\n\trequire.NoError(t, err)\n\tassert.NotNil(t, c)\n\tassert.True(t, c.CheckSignature(pub))\n\n\tb, err := c.Marshal()\n\trequire.NoError(t, err)\n\tuc, err := unmarshalCertificateV1(b, nil)\n\trequire.NoError(t, err)\n\tassert.NotNil(t, uc)\n}\n\nfunc TestCertificateV1_SignP256(t *testing.T) {\n\tbefore := time.Now().Add(time.Second * -60).Round(time.Second)\n\tafter := time.Now().Add(time.Second * 60).Round(time.Second)\n\tpubKey := []byte(\"01234567890abcedfghij1234567890ab1234567890abcedfghij1234567890ab\")\n\n\ttbs := TBSCertificate{\n\t\tVersion: Version1,\n\t\tName:    \"testing\",\n\t\tNetworks: []netip.Prefix{\n\t\t\tmustParsePrefixUnmapped(\"10.1.1.1/24\"),\n\t\t\tmustParsePrefixUnmapped(\"10.1.1.2/16\"),\n\t\t},\n\t\tUnsafeNetworks: []netip.Prefix{\n\t\t\tmustParsePrefixUnmapped(\"9.1.1.2/24\"),\n\t\t\tmustParsePrefixUnmapped(\"9.1.1.3/16\"),\n\t\t},\n\t\tGroups:    []string{\"test-group1\", \"test-group2\", \"test-group3\"},\n\t\tNotBefore: before,\n\t\tNotAfter:  after,\n\t\tPublicKey: pubKey,\n\t\tIsCA:      false,\n\t\tCurve:     Curve_P256,\n\t}\n\n\tpriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)\n\trequire.NoError(t, err)\n\tpub := elliptic.Marshal(elliptic.P256(), priv.PublicKey.X, priv.PublicKey.Y)\n\trawPriv := priv.D.FillBytes(make([]byte, 32))\n\n\tc, err := tbs.Sign(&certificateV1{details: detailsV1{notBefore: before, notAfter: after}}, Curve_P256, rawPriv)\n\trequire.NoError(t, err)\n\tassert.NotNil(t, c)\n\tassert.True(t, c.CheckSignature(pub))\n\n\tb, err := c.Marshal()\n\trequire.NoError(t, err)\n\tuc, err := unmarshalCertificateV1(b, nil)\n\trequire.NoError(t, err)\n\tassert.NotNil(t, uc)\n}\n\nfunc TestCertificate_SignP256_AlwaysNormalized(t *testing.T) {\n\tbefore := time.Now().Add(time.Second * -60).Round(time.Second)\n\tafter := time.Now().Add(time.Second * 60).Round(time.Second)\n\tpubKey := []byte(\"01234567890abcedfghij1234567890ab1234567890abcedfghij1234567890ab\")\n\n\ttbs := TBSCertificate{\n\t\tVersion: Version1,\n\t\tName:    \"testing\",\n\t\tNetworks: []netip.Prefix{\n\t\t\tmustParsePrefixUnmapped(\"10.1.1.1/24\"),\n\t\t\tmustParsePrefixUnmapped(\"10.1.1.2/16\"),\n\t\t},\n\t\tUnsafeNetworks: []netip.Prefix{\n\t\t\tmustParsePrefixUnmapped(\"9.1.1.2/24\"),\n\t\t\tmustParsePrefixUnmapped(\"9.1.1.3/16\"),\n\t\t},\n\t\tGroups:    []string{\"test-group1\", \"test-group2\", \"test-group3\"},\n\t\tNotBefore: before,\n\t\tNotAfter:  after,\n\t\tPublicKey: pubKey,\n\t\tIsCA:      true,\n\t\tCurve:     Curve_P256,\n\t}\n\n\tpriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)\n\trequire.NoError(t, err)\n\tpub := elliptic.Marshal(elliptic.P256(), priv.PublicKey.X, priv.PublicKey.Y)\n\trawPriv := priv.D.FillBytes(make([]byte, 32))\n\n\tfor i := 0; i < 1000; i++ {\n\t\tif i&1 == 1 {\n\t\t\ttbs.Version = Version1\n\t\t} else {\n\t\t\ttbs.Version = Version2\n\t\t}\n\t\tc, err := tbs.Sign(nil, Curve_P256, rawPriv)\n\t\trequire.NoError(t, err)\n\t\tassert.NotNil(t, c)\n\t\tassert.True(t, c.CheckSignature(pub))\n\t\tnormie, err := p256.IsNormalized(c.Signature())\n\t\trequire.NoError(t, err)\n\t\tassert.True(t, normie)\n\t}\n}\n"
  },
  {
    "path": "cert_test/cert.go",
    "content": "package cert_test\n\nimport (\n\t\"crypto/ecdh\"\n\t\"crypto/ecdsa\"\n\t\"crypto/elliptic\"\n\t\"crypto/rand\"\n\t\"io\"\n\t\"net/netip\"\n\t\"time\"\n\n\t\"github.com/slackhq/nebula/cert\"\n\t\"golang.org/x/crypto/curve25519\"\n\t\"golang.org/x/crypto/ed25519\"\n)\n\n// NewTestCaCert will create a new ca certificate\nfunc NewTestCaCert(version cert.Version, curve cert.Curve, before, after time.Time, networks, unsafeNetworks []netip.Prefix, groups []string) (cert.Certificate, []byte, []byte, []byte) {\n\tvar err error\n\tvar pub, priv []byte\n\n\tswitch curve {\n\tcase cert.Curve_CURVE25519:\n\t\tpub, priv, err = ed25519.GenerateKey(rand.Reader)\n\tcase cert.Curve_P256:\n\t\tprivk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tpub = elliptic.Marshal(elliptic.P256(), privk.PublicKey.X, privk.PublicKey.Y)\n\t\tpriv = privk.D.FillBytes(make([]byte, 32))\n\tdefault:\n\t\t// There is no default to allow the underlying lib to respond with an error\n\t}\n\n\tif before.IsZero() {\n\t\tbefore = time.Now().Add(time.Second * -60).Round(time.Second)\n\t}\n\tif after.IsZero() {\n\t\tafter = time.Now().Add(time.Second * 60).Round(time.Second)\n\t}\n\n\tt := &cert.TBSCertificate{\n\t\tCurve:          curve,\n\t\tVersion:        version,\n\t\tName:           \"test ca\",\n\t\tNotBefore:      time.Unix(before.Unix(), 0),\n\t\tNotAfter:       time.Unix(after.Unix(), 0),\n\t\tPublicKey:      pub,\n\t\tNetworks:       networks,\n\t\tUnsafeNetworks: unsafeNetworks,\n\t\tGroups:         groups,\n\t\tIsCA:           true,\n\t}\n\n\tc, err := t.Sign(nil, curve, priv)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tpem, err := c.MarshalPEM()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn c, pub, priv, pem\n}\n\n// NewTestCert will generate a signed certificate with the provided details.\n// Expiry times are defaulted if you do not pass them in\nfunc NewTestCert(v cert.Version, curve cert.Curve, ca cert.Certificate, key []byte, name string, before, after time.Time, networks, unsafeNetworks []netip.Prefix, groups []string) (cert.Certificate, []byte, []byte, []byte) {\n\tif before.IsZero() {\n\t\tbefore = time.Now().Add(time.Second * -60).Round(time.Second)\n\t}\n\n\tif after.IsZero() {\n\t\tafter = time.Now().Add(time.Second * 60).Round(time.Second)\n\t}\n\n\tvar pub, priv []byte\n\tswitch curve {\n\tcase cert.Curve_CURVE25519:\n\t\tpub, priv = X25519Keypair()\n\tcase cert.Curve_P256:\n\t\tpub, priv = P256Keypair()\n\tdefault:\n\t\tpanic(\"unknown curve\")\n\t}\n\n\tnc := &cert.TBSCertificate{\n\t\tVersion:        v,\n\t\tCurve:          curve,\n\t\tName:           name,\n\t\tNetworks:       networks,\n\t\tUnsafeNetworks: unsafeNetworks,\n\t\tGroups:         groups,\n\t\tNotBefore:      time.Unix(before.Unix(), 0),\n\t\tNotAfter:       time.Unix(after.Unix(), 0),\n\t\tPublicKey:      pub,\n\t\tIsCA:           false,\n\t}\n\n\tc, err := nc.Sign(ca, ca.Curve(), key)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tpem, err := c.MarshalPEM()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn c, pub, cert.MarshalPrivateKeyToPEM(curve, priv), pem\n}\n\nfunc NewTestCertDifferentVersion(c cert.Certificate, v cert.Version, ca cert.Certificate, key []byte) (cert.Certificate, []byte) {\n\tnc := &cert.TBSCertificate{\n\t\tVersion:        v,\n\t\tCurve:          c.Curve(),\n\t\tName:           c.Name(),\n\t\tNetworks:       c.Networks(),\n\t\tUnsafeNetworks: c.UnsafeNetworks(),\n\t\tGroups:         c.Groups(),\n\t\tNotBefore:      time.Unix(c.NotBefore().Unix(), 0),\n\t\tNotAfter:       time.Unix(c.NotAfter().Unix(), 0),\n\t\tPublicKey:      c.PublicKey(),\n\t\tIsCA:           false,\n\t}\n\n\tc, err := nc.Sign(ca, ca.Curve(), key)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tpem, err := c.MarshalPEM()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn c, pem\n}\n\nfunc X25519Keypair() ([]byte, []byte) {\n\tprivkey := make([]byte, 32)\n\tif _, err := io.ReadFull(rand.Reader, privkey); err != nil {\n\t\tpanic(err)\n\t}\n\n\tpubkey, err := curve25519.X25519(privkey, curve25519.Basepoint)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn pubkey, privkey\n}\n\nfunc P256Keypair() ([]byte, []byte) {\n\tprivkey, err := ecdh.P256().GenerateKey(rand.Reader)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tpubkey := privkey.PublicKey()\n\treturn pubkey.Bytes(), privkey.Bytes()\n}\n"
  },
  {
    "path": "cmd/nebula/main.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\t\"runtime/debug\"\n\t\"strings\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula\"\n\t\"github.com/slackhq/nebula/config\"\n\t\"github.com/slackhq/nebula/util\"\n)\n\n// A version string that can be set with\n//\n//\t-ldflags \"-X main.Build=SOMEVERSION\"\n//\n// at compile-time.\nvar Build string\n\nfunc init() {\n\tif Build == \"\" {\n\t\tinfo, ok := debug.ReadBuildInfo()\n\t\tif !ok {\n\t\t\treturn\n\t\t}\n\n\t\tBuild = strings.TrimPrefix(info.Main.Version, \"v\")\n\t}\n}\n\nfunc main() {\n\tconfigPath := flag.String(\"config\", \"\", \"Path to either a file or directory to load configuration from\")\n\tconfigTest := flag.Bool(\"test\", false, \"Test the config and print the end result. Non zero exit indicates a faulty config\")\n\tprintVersion := flag.Bool(\"version\", false, \"Print version\")\n\tprintUsage := flag.Bool(\"help\", false, \"Print command line usage\")\n\n\tflag.Parse()\n\n\tif *printVersion {\n\t\tfmt.Printf(\"Version: %s\\n\", Build)\n\t\tos.Exit(0)\n\t}\n\n\tif *printUsage {\n\t\tflag.Usage()\n\t\tos.Exit(0)\n\t}\n\n\tif *configPath == \"\" {\n\t\tfmt.Println(\"-config flag must be set\")\n\t\tflag.Usage()\n\t\tos.Exit(1)\n\t}\n\n\tl := logrus.New()\n\tl.Out = os.Stdout\n\n\tc := config.NewC(l)\n\terr := c.Load(*configPath)\n\tif err != nil {\n\t\tfmt.Printf(\"failed to load config: %s\", err)\n\t\tos.Exit(1)\n\t}\n\n\tctrl, err := nebula.Main(c, *configTest, Build, l, nil)\n\tif err != nil {\n\t\tutil.LogWithContextIfNeeded(\"Failed to start\", err, l)\n\t\tos.Exit(1)\n\t}\n\n\tif !*configTest {\n\t\tctrl.Start()\n\t\tnotifyReady(l)\n\t\tctrl.ShutdownBlock()\n\t}\n\n\tos.Exit(0)\n}\n"
  },
  {
    "path": "cmd/nebula/notify_linux.go",
    "content": "package main\n\nimport (\n\t\"net\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/sirupsen/logrus\"\n)\n\n// SdNotifyReady tells systemd the service is ready and dependent services can now be started\n// https://www.freedesktop.org/software/systemd/man/sd_notify.html\n// https://www.freedesktop.org/software/systemd/man/systemd.service.html\nconst SdNotifyReady = \"READY=1\"\n\nfunc notifyReady(l *logrus.Logger) {\n\tsockName := os.Getenv(\"NOTIFY_SOCKET\")\n\tif sockName == \"\" {\n\t\tl.Debugln(\"NOTIFY_SOCKET systemd env var not set, not sending ready signal\")\n\t\treturn\n\t}\n\n\tconn, err := net.DialTimeout(\"unixgram\", sockName, time.Second)\n\tif err != nil {\n\t\tl.WithError(err).Error(\"failed to connect to systemd notification socket\")\n\t\treturn\n\t}\n\tdefer conn.Close()\n\n\terr = conn.SetWriteDeadline(time.Now().Add(time.Second))\n\tif err != nil {\n\t\tl.WithError(err).Error(\"failed to set the write deadline for the systemd notification socket\")\n\t\treturn\n\t}\n\n\tif _, err = conn.Write([]byte(SdNotifyReady)); err != nil {\n\t\tl.WithError(err).Error(\"failed to signal the systemd notification socket\")\n\t\treturn\n\t}\n\n\tl.Debugln(\"notified systemd the service is ready\")\n}\n"
  },
  {
    "path": "cmd/nebula/notify_notlinux.go",
    "content": "//go:build !linux\n// +build !linux\n\npackage main\n\nimport \"github.com/sirupsen/logrus\"\n\nfunc notifyReady(_ *logrus.Logger) {\n\t// No init service to notify\n}\n"
  },
  {
    "path": "cmd/nebula-cert/ca.go",
    "content": "package main\n\nimport (\n\t\"crypto/ecdsa\"\n\t\"crypto/elliptic\"\n\t\"crypto/rand\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"math\"\n\t\"net/netip\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/skip2/go-qrcode\"\n\t\"github.com/slackhq/nebula/cert\"\n\t\"github.com/slackhq/nebula/pkclient\"\n\t\"golang.org/x/crypto/ed25519\"\n)\n\ntype caFlags struct {\n\tset              *flag.FlagSet\n\tname             *string\n\tduration         *time.Duration\n\toutKeyPath       *string\n\toutCertPath      *string\n\toutQRPath        *string\n\tgroups           *string\n\tnetworks         *string\n\tunsafeNetworks   *string\n\targonMemory      *uint\n\targonIterations  *uint\n\targonParallelism *uint\n\tencryption       *bool\n\tversion          *uint\n\n\tcurve  *string\n\tp11url *string\n\n\t// Deprecated options\n\tips     *string\n\tsubnets *string\n}\n\nfunc newCaFlags() *caFlags {\n\tcf := caFlags{set: flag.NewFlagSet(\"ca\", flag.ContinueOnError)}\n\tcf.set.Usage = func() {}\n\tcf.name = cf.set.String(\"name\", \"\", \"Required: name of the certificate authority\")\n\tcf.version = cf.set.Uint(\"version\", uint(cert.Version2), \"Optional: version of the certificate format to use\")\n\tcf.duration = cf.set.Duration(\"duration\", time.Duration(time.Hour*8760), \"Optional: amount of time the certificate should be valid for. Valid time units are seconds: \\\"s\\\", minutes: \\\"m\\\", hours: \\\"h\\\"\")\n\tcf.outKeyPath = cf.set.String(\"out-key\", \"ca.key\", \"Optional: path to write the private key to\")\n\tcf.outCertPath = cf.set.String(\"out-crt\", \"ca.crt\", \"Optional: path to write the certificate to\")\n\tcf.outQRPath = cf.set.String(\"out-qr\", \"\", \"Optional: output a qr code image (png) of the certificate\")\n\tcf.groups = cf.set.String(\"groups\", \"\", \"Optional: comma separated list of groups. This will limit which groups subordinate certs can use\")\n\tcf.networks = cf.set.String(\"networks\", \"\", \"Optional: comma separated list of ip address and network in CIDR notation. This will limit which ip addresses and networks subordinate certs can use in networks\")\n\tcf.unsafeNetworks = cf.set.String(\"unsafe-networks\", \"\", \"Optional: comma separated list of ip address and network in CIDR notation. This will limit which ip addresses and networks subordinate certs can use in unsafe networks\")\n\tcf.argonMemory = cf.set.Uint(\"argon-memory\", 2*1024*1024, \"Optional: Argon2 memory parameter (in KiB) used for encrypted private key passphrase\")\n\tcf.argonParallelism = cf.set.Uint(\"argon-parallelism\", 4, \"Optional: Argon2 parallelism parameter used for encrypted private key passphrase\")\n\tcf.argonIterations = cf.set.Uint(\"argon-iterations\", 1, \"Optional: Argon2 iterations parameter used for encrypted private key passphrase\")\n\tcf.encryption = cf.set.Bool(\"encrypt\", false, \"Optional: prompt for passphrase and write out-key in an encrypted format\")\n\tcf.curve = cf.set.String(\"curve\", \"25519\", \"EdDSA/ECDSA Curve (25519, P256)\")\n\tcf.p11url = p11Flag(cf.set)\n\n\tcf.ips = cf.set.String(\"ips\", \"\", \"Deprecated, see -networks\")\n\tcf.subnets = cf.set.String(\"subnets\", \"\", \"Deprecated, see -unsafe-networks\")\n\treturn &cf\n}\n\nfunc parseArgonParameters(memory uint, parallelism uint, iterations uint) (*cert.Argon2Parameters, error) {\n\tif memory <= 0 || memory > math.MaxUint32 {\n\t\treturn nil, newHelpErrorf(\"-argon-memory must be be greater than 0 and no more than %d KiB\", uint32(math.MaxUint32))\n\t}\n\tif parallelism <= 0 || parallelism > math.MaxUint8 {\n\t\treturn nil, newHelpErrorf(\"-argon-parallelism must be be greater than 0 and no more than %d\", math.MaxUint8)\n\t}\n\tif iterations <= 0 || iterations > math.MaxUint32 {\n\t\treturn nil, newHelpErrorf(\"-argon-iterations must be be greater than 0 and no more than %d\", uint32(math.MaxUint32))\n\t}\n\n\treturn cert.NewArgon2Parameters(uint32(memory), uint8(parallelism), uint32(iterations)), nil\n}\n\nfunc ca(args []string, out io.Writer, errOut io.Writer, pr PasswordReader) error {\n\tcf := newCaFlags()\n\terr := cf.set.Parse(args)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tisP11 := len(*cf.p11url) > 0\n\n\tif err := mustFlagString(\"name\", cf.name); err != nil {\n\t\treturn err\n\t}\n\tif !isP11 {\n\t\tif err = mustFlagString(\"out-key\", cf.outKeyPath); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif err := mustFlagString(\"out-crt\", cf.outCertPath); err != nil {\n\t\treturn err\n\t}\n\tvar kdfParams *cert.Argon2Parameters\n\tif !isP11 && *cf.encryption {\n\t\tif kdfParams, err = parseArgonParameters(*cf.argonMemory, *cf.argonParallelism, *cf.argonIterations); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif *cf.duration <= 0 {\n\t\treturn &helpError{\"-duration must be greater than 0\"}\n\t}\n\n\tvar groups []string\n\tif *cf.groups != \"\" {\n\t\tfor _, rg := range strings.Split(*cf.groups, \",\") {\n\t\t\tg := strings.TrimSpace(rg)\n\t\t\tif g != \"\" {\n\t\t\t\tgroups = append(groups, g)\n\t\t\t}\n\t\t}\n\t}\n\n\tversion := cert.Version(*cf.version)\n\tif version != cert.Version1 && version != cert.Version2 {\n\t\treturn newHelpErrorf(\"-version must be either %v or %v\", cert.Version1, cert.Version2)\n\t}\n\n\tvar networks []netip.Prefix\n\tif *cf.networks == \"\" && *cf.ips != \"\" {\n\t\t// Pull up deprecated -ips flag if needed\n\t\t*cf.networks = *cf.ips\n\t}\n\n\tif *cf.networks != \"\" {\n\t\tfor _, rs := range strings.Split(*cf.networks, \",\") {\n\t\t\trs := strings.Trim(rs, \" \")\n\t\t\tif rs != \"\" {\n\t\t\t\tn, err := netip.ParsePrefix(rs)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn newHelpErrorf(\"invalid -networks definition: %s\", rs)\n\t\t\t\t}\n\t\t\t\tif version == cert.Version1 && !n.Addr().Is4() {\n\t\t\t\t\treturn newHelpErrorf(\"invalid -networks definition: v1 certificates can only be ipv4, have %s\", rs)\n\t\t\t\t}\n\t\t\t\tnetworks = append(networks, n)\n\t\t\t}\n\t\t}\n\t}\n\n\tvar unsafeNetworks []netip.Prefix\n\tif *cf.unsafeNetworks == \"\" && *cf.subnets != \"\" {\n\t\t// Pull up deprecated -subnets flag if needed\n\t\t*cf.unsafeNetworks = *cf.subnets\n\t}\n\n\tif *cf.unsafeNetworks != \"\" {\n\t\tfor _, rs := range strings.Split(*cf.unsafeNetworks, \",\") {\n\t\t\trs := strings.Trim(rs, \" \")\n\t\t\tif rs != \"\" {\n\t\t\t\tn, err := netip.ParsePrefix(rs)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn newHelpErrorf(\"invalid -unsafe-networks definition: %s\", rs)\n\t\t\t\t}\n\t\t\t\tif version == cert.Version1 && !n.Addr().Is4() {\n\t\t\t\t\treturn newHelpErrorf(\"invalid -unsafe-networks definition: v1 certificates can only be ipv4, have %s\", rs)\n\t\t\t\t}\n\t\t\t\tunsafeNetworks = append(unsafeNetworks, n)\n\t\t\t}\n\t\t}\n\t}\n\n\tvar passphrase []byte\n\tif !isP11 && *cf.encryption {\n\t\tpassphrase = []byte(os.Getenv(\"NEBULA_CA_PASSPHRASE\"))\n\t\tif len(passphrase) == 0 {\n\t\t\tfor i := 0; i < 5; i++ {\n\t\t\t\tout.Write([]byte(\"Enter passphrase: \"))\n\t\t\t\tpassphrase, err = pr.ReadPassword()\n\n\t\t\t\tif err == ErrNoTerminal {\n\t\t\t\t\treturn fmt.Errorf(\"out-key must be encrypted interactively\")\n\t\t\t\t} else if err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"error reading passphrase: %s\", err)\n\t\t\t\t}\n\n\t\t\t\tif len(passphrase) > 0 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif len(passphrase) == 0 {\n\t\t\t\treturn fmt.Errorf(\"no passphrase specified, remove -encrypt flag to write out-key in plaintext\")\n\t\t\t}\n\t\t}\n\t}\n\n\tvar curve cert.Curve\n\tvar pub, rawPriv []byte\n\tvar p11Client *pkclient.PKClient\n\n\tif isP11 {\n\t\tswitch *cf.curve {\n\t\tcase \"P256\":\n\t\t\tcurve = cert.Curve_P256\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"invalid curve for PKCS#11: %s\", *cf.curve)\n\t\t}\n\n\t\tp11Client, err = pkclient.FromUrl(*cf.p11url)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error while creating PKCS#11 client: %w\", err)\n\t\t}\n\t\tdefer func(client *pkclient.PKClient) {\n\t\t\t_ = client.Close()\n\t\t}(p11Client)\n\t\tpub, err = p11Client.GetPubKey()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error while getting public key with PKCS#11: %w\", err)\n\t\t}\n\t} else {\n\t\tswitch *cf.curve {\n\t\tcase \"25519\", \"X25519\", \"Curve25519\", \"CURVE25519\":\n\t\t\tcurve = cert.Curve_CURVE25519\n\t\t\tpub, rawPriv, err = ed25519.GenerateKey(rand.Reader)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error while generating ed25519 keys: %s\", err)\n\t\t\t}\n\t\tcase \"P256\":\n\t\t\tvar key *ecdsa.PrivateKey\n\t\t\tcurve = cert.Curve_P256\n\t\t\tkey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error while generating ecdsa keys: %s\", err)\n\t\t\t}\n\n\t\t\t// ecdh.PrivateKey lets us get at the encoded bytes, even though\n\t\t\t// we aren't using ECDH here.\n\t\t\teKey, err := key.ECDH()\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error while converting ecdsa key: %s\", err)\n\t\t\t}\n\t\t\trawPriv = eKey.Bytes()\n\t\t\tpub = eKey.PublicKey().Bytes()\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"invalid curve: %s\", *cf.curve)\n\t\t}\n\t}\n\n\tt := &cert.TBSCertificate{\n\t\tVersion:        version,\n\t\tName:           *cf.name,\n\t\tGroups:         groups,\n\t\tNetworks:       networks,\n\t\tUnsafeNetworks: unsafeNetworks,\n\t\tNotBefore:      time.Now(),\n\t\tNotAfter:       time.Now().Add(*cf.duration),\n\t\tPublicKey:      pub,\n\t\tIsCA:           true,\n\t\tCurve:          curve,\n\t}\n\n\tif !isP11 {\n\t\tif _, err := os.Stat(*cf.outKeyPath); err == nil {\n\t\t\treturn fmt.Errorf(\"refusing to overwrite existing CA key: %s\", *cf.outKeyPath)\n\t\t}\n\t}\n\n\tif _, err := os.Stat(*cf.outCertPath); err == nil {\n\t\treturn fmt.Errorf(\"refusing to overwrite existing CA cert: %s\", *cf.outCertPath)\n\t}\n\n\tvar c cert.Certificate\n\tvar b []byte\n\n\tif isP11 {\n\t\tc, err = t.SignWith(nil, curve, p11Client.SignASN1)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error while signing with PKCS#11: %w\", err)\n\t\t}\n\t} else {\n\t\tc, err = t.Sign(nil, curve, rawPriv)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error while signing: %s\", err)\n\t\t}\n\n\t\tif *cf.encryption {\n\t\t\tb, err = cert.EncryptAndMarshalSigningPrivateKey(curve, rawPriv, passphrase, kdfParams)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error while encrypting out-key: %s\", err)\n\t\t\t}\n\t\t} else {\n\t\t\tb = cert.MarshalSigningPrivateKeyToPEM(curve, rawPriv)\n\t\t}\n\n\t\terr = os.WriteFile(*cf.outKeyPath, b, 0600)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error while writing out-key: %s\", err)\n\t\t}\n\t}\n\n\tb, err = c.MarshalPEM()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error while marshalling certificate: %s\", err)\n\t}\n\n\terr = os.WriteFile(*cf.outCertPath, b, 0600)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error while writing out-crt: %s\", err)\n\t}\n\n\tif *cf.outQRPath != \"\" {\n\t\tb, err = qrcode.Encode(string(b), qrcode.Medium, -5)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error while generating qr code: %s\", err)\n\t\t}\n\n\t\terr = os.WriteFile(*cf.outQRPath, b, 0600)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error while writing out-qr: %s\", err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc caSummary() string {\n\treturn \"ca <flags>: create a self signed certificate authority\"\n}\n\nfunc caHelp(out io.Writer) {\n\tcf := newCaFlags()\n\tout.Write([]byte(\"Usage of \" + os.Args[0] + \" \" + caSummary() + \"\\n\"))\n\tcf.set.SetOutput(out)\n\tcf.set.PrintDefaults()\n}\n"
  },
  {
    "path": "cmd/nebula-cert/ca_test.go",
    "content": "//go:build !windows\n// +build !windows\n\npackage main\n\nimport (\n\t\"bytes\"\n\t\"encoding/pem\"\n\t\"errors\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/slackhq/nebula/cert\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_caSummary(t *testing.T) {\n\tassert.Equal(t, \"ca <flags>: create a self signed certificate authority\", caSummary())\n}\n\nfunc Test_caHelp(t *testing.T) {\n\tob := &bytes.Buffer{}\n\tcaHelp(ob)\n\tassert.Equal(\n\t\tt,\n\t\t\"Usage of \"+os.Args[0]+\" ca <flags>: create a self signed certificate authority\\n\"+\n\t\t\t\"  -argon-iterations uint\\n\"+\n\t\t\t\"    \\tOptional: Argon2 iterations parameter used for encrypted private key passphrase (default 1)\\n\"+\n\t\t\t\"  -argon-memory uint\\n\"+\n\t\t\t\"    \\tOptional: Argon2 memory parameter (in KiB) used for encrypted private key passphrase (default 2097152)\\n\"+\n\t\t\t\"  -argon-parallelism uint\\n\"+\n\t\t\t\"    \\tOptional: Argon2 parallelism parameter used for encrypted private key passphrase (default 4)\\n\"+\n\t\t\t\"  -curve string\\n\"+\n\t\t\t\"    \\tEdDSA/ECDSA Curve (25519, P256) (default \\\"25519\\\")\\n\"+\n\t\t\t\"  -duration duration\\n\"+\n\t\t\t\"    \\tOptional: amount of time the certificate should be valid for. Valid time units are seconds: \\\"s\\\", minutes: \\\"m\\\", hours: \\\"h\\\" (default 8760h0m0s)\\n\"+\n\t\t\t\"  -encrypt\\n\"+\n\t\t\t\"    \\tOptional: prompt for passphrase and write out-key in an encrypted format\\n\"+\n\t\t\t\"  -groups string\\n\"+\n\t\t\t\"    \\tOptional: comma separated list of groups. This will limit which groups subordinate certs can use\\n\"+\n\t\t\t\"  -ips string\\n\"+\n\t\t\t\"    \tDeprecated, see -networks\\n\"+\n\t\t\t\"  -name string\\n\"+\n\t\t\t\"    \\tRequired: name of the certificate authority\\n\"+\n\t\t\t\"  -networks string\\n\"+\n\t\t\t\"    \\tOptional: comma separated list of ip address and network in CIDR notation. This will limit which ip addresses and networks subordinate certs can use in networks\\n\"+\n\t\t\t\"  -out-crt string\\n\"+\n\t\t\t\"    \\tOptional: path to write the certificate to (default \\\"ca.crt\\\")\\n\"+\n\t\t\t\"  -out-key string\\n\"+\n\t\t\t\"    \\tOptional: path to write the private key to (default \\\"ca.key\\\")\\n\"+\n\t\t\t\"  -out-qr string\\n\"+\n\t\t\t\"    \\tOptional: output a qr code image (png) of the certificate\\n\"+\n\t\t\toptionalPkcs11String(\"  -pkcs11 string\\n    \\tOptional: PKCS#11 URI to an existing private key\\n\")+\n\t\t\t\"  -subnets string\\n\"+\n\t\t\t\"    \\tDeprecated, see -unsafe-networks\\n\"+\n\t\t\t\"  -unsafe-networks string\\n\"+\n\t\t\t\"    \\tOptional: comma separated list of ip address and network in CIDR notation. This will limit which ip addresses and networks subordinate certs can use in unsafe networks\\n\"+\n\t\t\t\"  -version uint\\n\"+\n\t\t\t\"    \\tOptional: version of the certificate format to use (default 2)\\n\",\n\t\tob.String(),\n\t)\n}\n\nfunc Test_ca(t *testing.T) {\n\tob := &bytes.Buffer{}\n\teb := &bytes.Buffer{}\n\n\tnopw := &StubPasswordReader{\n\t\tpassword: []byte(\"\"),\n\t\terr:      nil,\n\t}\n\n\terrpw := &StubPasswordReader{\n\t\tpassword: []byte(\"\"),\n\t\terr:      errors.New(\"stub error\"),\n\t}\n\n\tpassphrase := []byte(\"DO NOT USE THIS KEY\")\n\ttestpw := &StubPasswordReader{\n\t\tpassword: passphrase,\n\t\terr:      nil,\n\t}\n\n\tpwPromptOb := \"Enter passphrase: \"\n\n\t// required args\n\tassertHelpError(t, ca(\n\t\t[]string{\"-version\", \"1\", \"-out-key\", \"nope\", \"-out-crt\", \"nope\", \"duration\", \"100m\"}, ob, eb, nopw,\n\t), \"-name is required\")\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\n\t// ipv4 only ips\n\tassertHelpError(t, ca([]string{\"-version\", \"1\", \"-name\", \"ipv6\", \"-ips\", \"100::100/100\"}, ob, eb, nopw), \"invalid -networks definition: v1 certificates can only be ipv4, have 100::100/100\")\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\n\t// ipv4 only subnets\n\tassertHelpError(t, ca([]string{\"-version\", \"1\", \"-name\", \"ipv6\", \"-subnets\", \"100::100/100\"}, ob, eb, nopw), \"invalid -unsafe-networks definition: v1 certificates can only be ipv4, have 100::100/100\")\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\n\t// failed key write\n\tob.Reset()\n\teb.Reset()\n\targs := []string{\"-version\", \"1\", \"-name\", \"test\", \"-duration\", \"100m\", \"-out-crt\", \"/do/not/write/pleasecrt\", \"-out-key\", \"/do/not/write/pleasekey\"}\n\trequire.EqualError(t, ca(args, ob, eb, nopw), \"error while writing out-key: open /do/not/write/pleasekey: \"+NoSuchDirError)\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\n\t// create temp key file\n\tkeyF, err := os.CreateTemp(\"\", \"test.key\")\n\trequire.NoError(t, err)\n\trequire.NoError(t, os.Remove(keyF.Name()))\n\n\t// failed cert write\n\tob.Reset()\n\teb.Reset()\n\targs = []string{\"-version\", \"1\", \"-name\", \"test\", \"-duration\", \"100m\", \"-out-crt\", \"/do/not/write/pleasecrt\", \"-out-key\", keyF.Name()}\n\trequire.EqualError(t, ca(args, ob, eb, nopw), \"error while writing out-crt: open /do/not/write/pleasecrt: \"+NoSuchDirError)\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\n\t// create temp cert file\n\tcrtF, err := os.CreateTemp(\"\", \"test.crt\")\n\trequire.NoError(t, err)\n\trequire.NoError(t, os.Remove(crtF.Name()))\n\trequire.NoError(t, os.Remove(keyF.Name()))\n\n\t// test proper cert with removed empty groups and subnets\n\tob.Reset()\n\teb.Reset()\n\targs = []string{\"-version\", \"1\", \"-name\", \"test\", \"-duration\", \"100m\", \"-groups\", \"1,,   2    ,        ,,,3,4,5\", \"-out-crt\", crtF.Name(), \"-out-key\", keyF.Name()}\n\trequire.NoError(t, ca(args, ob, eb, nopw))\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\n\t// read cert and key files\n\trb, _ := os.ReadFile(keyF.Name())\n\tlKey, b, c, err := cert.UnmarshalSigningPrivateKeyFromPEM(rb)\n\tassert.Equal(t, cert.Curve_CURVE25519, c)\n\tassert.Empty(t, b)\n\trequire.NoError(t, err)\n\tassert.Len(t, lKey, 64)\n\n\trb, _ = os.ReadFile(crtF.Name())\n\tlCrt, b, err := cert.UnmarshalCertificateFromPEM(rb)\n\tassert.Empty(t, b)\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, \"test\", lCrt.Name())\n\tassert.Empty(t, lCrt.Networks())\n\tassert.True(t, lCrt.IsCA())\n\tassert.Equal(t, []string{\"1\", \"2\", \"3\", \"4\", \"5\"}, lCrt.Groups())\n\tassert.Empty(t, lCrt.UnsafeNetworks())\n\tassert.Len(t, lCrt.PublicKey(), 32)\n\tassert.Equal(t, time.Duration(time.Minute*100), lCrt.NotAfter().Sub(lCrt.NotBefore()))\n\tassert.Empty(t, lCrt.Issuer())\n\tassert.True(t, lCrt.CheckSignature(lCrt.PublicKey()))\n\n\t// test encrypted key\n\tos.Remove(keyF.Name())\n\tos.Remove(crtF.Name())\n\tob.Reset()\n\teb.Reset()\n\targs = []string{\"-version\", \"1\", \"-encrypt\", \"-name\", \"test\", \"-duration\", \"100m\", \"-groups\", \"1,2,3,4,5\", \"-out-crt\", crtF.Name(), \"-out-key\", keyF.Name()}\n\trequire.NoError(t, ca(args, ob, eb, testpw))\n\tassert.Equal(t, pwPromptOb, ob.String())\n\tassert.Empty(t, eb.String())\n\n\t// test encrypted key with passphrase environment variable\n\tos.Remove(keyF.Name())\n\tos.Remove(crtF.Name())\n\tob.Reset()\n\teb.Reset()\n\targs = []string{\"-version\", \"1\", \"-encrypt\", \"-name\", \"test\", \"-duration\", \"100m\", \"-groups\", \"1,2,3,4,5\", \"-out-crt\", crtF.Name(), \"-out-key\", keyF.Name()}\n\tos.Setenv(\"NEBULA_CA_PASSPHRASE\", string(passphrase))\n\trequire.NoError(t, ca(args, ob, eb, testpw))\n\tassert.Empty(t, eb.String())\n\tos.Setenv(\"NEBULA_CA_PASSPHRASE\", \"\")\n\n\t// read encrypted key file and verify default params\n\trb, _ = os.ReadFile(keyF.Name())\n\tk, _ := pem.Decode(rb)\n\tned, err := cert.UnmarshalNebulaEncryptedData(k.Bytes)\n\trequire.NoError(t, err)\n\t// we won't know salt in advance, so just check start of string\n\tassert.Equal(t, uint32(2*1024*1024), ned.EncryptionMetadata.Argon2Parameters.Memory)\n\tassert.Equal(t, uint8(4), ned.EncryptionMetadata.Argon2Parameters.Parallelism)\n\tassert.Equal(t, uint32(1), ned.EncryptionMetadata.Argon2Parameters.Iterations)\n\n\t// verify the key is valid and decrypt-able\n\tvar curve cert.Curve\n\tcurve, lKey, b, err = cert.DecryptAndUnmarshalSigningPrivateKey(passphrase, rb)\n\tassert.Equal(t, cert.Curve_CURVE25519, curve)\n\trequire.NoError(t, err)\n\tassert.Empty(t, b)\n\tassert.Len(t, lKey, 64)\n\n\t// test when reading password results in an error\n\tos.Remove(keyF.Name())\n\tos.Remove(crtF.Name())\n\tob.Reset()\n\teb.Reset()\n\targs = []string{\"-version\", \"1\", \"-encrypt\", \"-name\", \"test\", \"-duration\", \"100m\", \"-groups\", \"1,2,3,4,5\", \"-out-crt\", crtF.Name(), \"-out-key\", keyF.Name()}\n\trequire.Error(t, ca(args, ob, eb, errpw))\n\tassert.Equal(t, pwPromptOb, ob.String())\n\tassert.Empty(t, eb.String())\n\n\t// test when user fails to enter a password\n\tos.Remove(keyF.Name())\n\tos.Remove(crtF.Name())\n\tob.Reset()\n\teb.Reset()\n\targs = []string{\"-version\", \"1\", \"-encrypt\", \"-name\", \"test\", \"-duration\", \"100m\", \"-groups\", \"1,2,3,4,5\", \"-out-crt\", crtF.Name(), \"-out-key\", keyF.Name()}\n\trequire.EqualError(t, ca(args, ob, eb, nopw), \"no passphrase specified, remove -encrypt flag to write out-key in plaintext\")\n\tassert.Equal(t, strings.Repeat(pwPromptOb, 5), ob.String()) // prompts 5 times before giving up\n\tassert.Empty(t, eb.String())\n\n\t// create valid cert/key for overwrite tests\n\tos.Remove(keyF.Name())\n\tos.Remove(crtF.Name())\n\tob.Reset()\n\teb.Reset()\n\targs = []string{\"-version\", \"1\", \"-name\", \"test\", \"-duration\", \"100m\", \"-groups\", \"1,,   2    ,        ,,,3,4,5\", \"-out-crt\", crtF.Name(), \"-out-key\", keyF.Name()}\n\trequire.NoError(t, ca(args, ob, eb, nopw))\n\n\t// test that we won't overwrite existing certificate file\n\tob.Reset()\n\teb.Reset()\n\targs = []string{\"-version\", \"1\", \"-name\", \"test\", \"-duration\", \"100m\", \"-groups\", \"1,,   2    ,        ,,,3,4,5\", \"-out-crt\", crtF.Name(), \"-out-key\", keyF.Name()}\n\trequire.EqualError(t, ca(args, ob, eb, nopw), \"refusing to overwrite existing CA key: \"+keyF.Name())\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\n\t// test that we won't overwrite existing key file\n\tos.Remove(keyF.Name())\n\tob.Reset()\n\teb.Reset()\n\targs = []string{\"-version\", \"1\", \"-name\", \"test\", \"-duration\", \"100m\", \"-groups\", \"1,,   2    ,        ,,,3,4,5\", \"-out-crt\", crtF.Name(), \"-out-key\", keyF.Name()}\n\trequire.EqualError(t, ca(args, ob, eb, nopw), \"refusing to overwrite existing CA cert: \"+crtF.Name())\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\tos.Remove(keyF.Name())\n\n}\n"
  },
  {
    "path": "cmd/nebula-cert/keygen.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/slackhq/nebula/pkclient\"\n\n\t\"github.com/slackhq/nebula/cert\"\n)\n\ntype keygenFlags struct {\n\tset        *flag.FlagSet\n\toutKeyPath *string\n\toutPubPath *string\n\tcurve      *string\n\tp11url     *string\n}\n\nfunc newKeygenFlags() *keygenFlags {\n\tcf := keygenFlags{set: flag.NewFlagSet(\"keygen\", flag.ContinueOnError)}\n\tcf.set.Usage = func() {}\n\tcf.outPubPath = cf.set.String(\"out-pub\", \"\", \"Required: path to write the public key to\")\n\tcf.outKeyPath = cf.set.String(\"out-key\", \"\", \"Required: path to write the private key to\")\n\tcf.curve = cf.set.String(\"curve\", \"25519\", \"ECDH Curve (25519, P256)\")\n\tcf.p11url = p11Flag(cf.set)\n\treturn &cf\n}\n\nfunc keygen(args []string, out io.Writer, errOut io.Writer) error {\n\tcf := newKeygenFlags()\n\terr := cf.set.Parse(args)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tisP11 := len(*cf.p11url) > 0\n\n\tif !isP11 {\n\t\tif err = mustFlagString(\"out-key\", cf.outKeyPath); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif err = mustFlagString(\"out-pub\", cf.outPubPath); err != nil {\n\t\treturn err\n\t}\n\n\tvar pub, rawPriv []byte\n\tvar curve cert.Curve\n\tif isP11 {\n\t\tswitch *cf.curve {\n\t\tcase \"P256\":\n\t\t\tcurve = cert.Curve_P256\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"invalid curve for PKCS#11: %s\", *cf.curve)\n\t\t}\n\t} else {\n\t\tswitch *cf.curve {\n\t\tcase \"25519\", \"X25519\", \"Curve25519\", \"CURVE25519\":\n\t\t\tpub, rawPriv = x25519Keypair()\n\t\t\tcurve = cert.Curve_CURVE25519\n\t\tcase \"P256\":\n\t\t\tpub, rawPriv = p256Keypair()\n\t\t\tcurve = cert.Curve_P256\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"invalid curve: %s\", *cf.curve)\n\t\t}\n\t}\n\n\tif isP11 {\n\t\tp11Client, err := pkclient.FromUrl(*cf.p11url)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error while creating PKCS#11 client: %w\", err)\n\t\t}\n\t\tdefer func(client *pkclient.PKClient) {\n\t\t\t_ = client.Close()\n\t\t}(p11Client)\n\t\tpub, err = p11Client.GetPubKey()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error while getting public key: %w\", err)\n\t\t}\n\t} else {\n\t\terr = os.WriteFile(*cf.outKeyPath, cert.MarshalPrivateKeyToPEM(curve, rawPriv), 0600)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error while writing out-key: %s\", err)\n\t\t}\n\t}\n\terr = os.WriteFile(*cf.outPubPath, cert.MarshalPublicKeyToPEM(curve, pub), 0600)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error while writing out-pub: %s\", err)\n\t}\n\n\treturn nil\n}\n\nfunc keygenSummary() string {\n\treturn \"keygen <flags>: create a public/private key pair. the public key can be passed to `nebula-cert sign`\"\n}\n\nfunc keygenHelp(out io.Writer) {\n\tcf := newKeygenFlags()\n\t_, _ = out.Write([]byte(\"Usage of \" + os.Args[0] + \" \" + keygenSummary() + \"\\n\"))\n\tcf.set.SetOutput(out)\n\tcf.set.PrintDefaults()\n}\n"
  },
  {
    "path": "cmd/nebula-cert/keygen_test.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/slackhq/nebula/cert\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_keygenSummary(t *testing.T) {\n\tassert.Equal(t, \"keygen <flags>: create a public/private key pair. the public key can be passed to `nebula-cert sign`\", keygenSummary())\n}\n\nfunc Test_keygenHelp(t *testing.T) {\n\tob := &bytes.Buffer{}\n\tkeygenHelp(ob)\n\tassert.Equal(\n\t\tt,\n\t\t\"Usage of \"+os.Args[0]+\" keygen <flags>: create a public/private key pair. the public key can be passed to `nebula-cert sign`\\n\"+\n\t\t\t\"  -curve string\\n\"+\n\t\t\t\"    \\tECDH Curve (25519, P256) (default \\\"25519\\\")\\n\"+\n\t\t\t\"  -out-key string\\n\"+\n\t\t\t\"    \\tRequired: path to write the private key to\\n\"+\n\t\t\t\"  -out-pub string\\n\"+\n\t\t\t\"    \\tRequired: path to write the public key to\\n\"+\n\t\t\toptionalPkcs11String(\"  -pkcs11 string\\n    \\tOptional: PKCS#11 URI to an existing private key\\n\"),\n\t\tob.String(),\n\t)\n}\n\nfunc Test_keygen(t *testing.T) {\n\tob := &bytes.Buffer{}\n\teb := &bytes.Buffer{}\n\n\t// required args\n\tassertHelpError(t, keygen([]string{\"-out-pub\", \"nope\"}, ob, eb), \"-out-key is required\")\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\n\tassertHelpError(t, keygen([]string{\"-out-key\", \"nope\"}, ob, eb), \"-out-pub is required\")\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\n\t// failed key write\n\tob.Reset()\n\teb.Reset()\n\targs := []string{\"-out-pub\", \"/do/not/write/pleasepub\", \"-out-key\", \"/do/not/write/pleasekey\"}\n\trequire.EqualError(t, keygen(args, ob, eb), \"error while writing out-key: open /do/not/write/pleasekey: \"+NoSuchDirError)\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\n\t// create temp key file\n\tkeyF, err := os.CreateTemp(\"\", \"test.key\")\n\trequire.NoError(t, err)\n\tdefer os.Remove(keyF.Name())\n\n\t// failed pub write\n\tob.Reset()\n\teb.Reset()\n\targs = []string{\"-out-pub\", \"/do/not/write/pleasepub\", \"-out-key\", keyF.Name()}\n\trequire.EqualError(t, keygen(args, ob, eb), \"error while writing out-pub: open /do/not/write/pleasepub: \"+NoSuchDirError)\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\n\t// create temp pub file\n\tpubF, err := os.CreateTemp(\"\", \"test.pub\")\n\trequire.NoError(t, err)\n\tdefer os.Remove(pubF.Name())\n\n\t// test proper keygen\n\tob.Reset()\n\teb.Reset()\n\targs = []string{\"-out-pub\", pubF.Name(), \"-out-key\", keyF.Name()}\n\trequire.NoError(t, keygen(args, ob, eb))\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\n\t// read cert and key files\n\trb, _ := os.ReadFile(keyF.Name())\n\tlKey, b, curve, err := cert.UnmarshalPrivateKeyFromPEM(rb)\n\tassert.Equal(t, cert.Curve_CURVE25519, curve)\n\tassert.Empty(t, b)\n\trequire.NoError(t, err)\n\tassert.Len(t, lKey, 32)\n\n\trb, _ = os.ReadFile(pubF.Name())\n\tlPub, b, curve, err := cert.UnmarshalPublicKeyFromPEM(rb)\n\tassert.Equal(t, cert.Curve_CURVE25519, curve)\n\tassert.Empty(t, b)\n\trequire.NoError(t, err)\n\tassert.Len(t, lPub, 32)\n}\n"
  },
  {
    "path": "cmd/nebula-cert/main.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"runtime/debug\"\n\t\"strings\"\n)\n\n// A version string that can be set with\n//\n//\t-ldflags \"-X main.Build=SOMEVERSION\"\n//\n// at compile-time.\nvar Build string\n\nfunc init() {\n\tif Build == \"\" {\n\t\tinfo, ok := debug.ReadBuildInfo()\n\t\tif !ok {\n\t\t\treturn\n\t\t}\n\n\t\tBuild = strings.TrimPrefix(info.Main.Version, \"v\")\n\t}\n}\n\ntype helpError struct {\n\ts string\n}\n\nfunc (he *helpError) Error() string {\n\treturn he.s\n}\n\nfunc newHelpErrorf(s string, v ...any) error {\n\treturn &helpError{s: fmt.Sprintf(s, v...)}\n}\n\nfunc main() {\n\tflag.Usage = func() {\n\t\thelp(\"\", os.Stderr)\n\t\tos.Exit(1)\n\t}\n\n\tprintVersion := flag.Bool(\"version\", false, \"Print version\")\n\tflagHelp := flag.Bool(\"help\", false, \"Print command line usage\")\n\tflagH := flag.Bool(\"h\", false, \"Print command line usage\")\n\tprintUsage := false\n\n\tflag.Parse()\n\n\tif *flagH || *flagHelp {\n\t\tprintUsage = true\n\t}\n\n\targs := flag.Args()\n\n\tif *printVersion {\n\t\tfmt.Printf(\"Version: %v\\n\", Build)\n\t\tos.Exit(0)\n\t}\n\n\tif len(args) < 1 {\n\t\tif printUsage {\n\t\t\thelp(\"\", os.Stderr)\n\t\t\tos.Exit(0)\n\t\t}\n\n\t\thelp(\"No mode was provided\", os.Stderr)\n\t\tos.Exit(1)\n\t} else if printUsage {\n\t\thandleError(args[0], &helpError{}, os.Stderr)\n\t\tos.Exit(0)\n\t}\n\n\tvar err error\n\n\tswitch args[0] {\n\tcase \"ca\":\n\t\terr = ca(args[1:], os.Stdout, os.Stderr, StdinPasswordReader{})\n\tcase \"keygen\":\n\t\terr = keygen(args[1:], os.Stdout, os.Stderr)\n\tcase \"sign\":\n\t\terr = signCert(args[1:], os.Stdout, os.Stderr, StdinPasswordReader{})\n\tcase \"print\":\n\t\terr = printCert(args[1:], os.Stdout, os.Stderr)\n\tcase \"verify\":\n\t\terr = verify(args[1:], os.Stdout, os.Stderr)\n\tdefault:\n\t\terr = fmt.Errorf(\"unknown mode: %s\", args[0])\n\t}\n\n\tif err != nil {\n\t\tos.Exit(handleError(args[0], err, os.Stderr))\n\t}\n}\n\nfunc handleError(mode string, e error, out io.Writer) int {\n\tcode := 1\n\n\t// Handle -help, -h flags properly\n\tif e == flag.ErrHelp {\n\t\tcode = 0\n\t\te = &helpError{}\n\t} else if e != nil && e.Error() != \"\" {\n\t\tfmt.Fprintln(out, \"Error:\", e)\n\t}\n\n\tswitch e.(type) {\n\tcase *helpError:\n\t\tswitch mode {\n\t\tcase \"ca\":\n\t\t\tcaHelp(out)\n\t\tcase \"keygen\":\n\t\t\tkeygenHelp(out)\n\t\tcase \"sign\":\n\t\t\tsignHelp(out)\n\t\tcase \"print\":\n\t\t\tprintHelp(out)\n\t\tcase \"verify\":\n\t\t\tverifyHelp(out)\n\t\t}\n\t}\n\n\treturn code\n}\n\nfunc help(err string, out io.Writer) {\n\tif err != \"\" {\n\t\tfmt.Fprintln(out, \"Error:\", err)\n\t\tfmt.Fprintln(out, \"\")\n\t}\n\n\tfmt.Fprintf(out, \"Usage of %s <global flags> <mode>:\\n\", os.Args[0])\n\tfmt.Fprintln(out, \"  Global flags:\")\n\tfmt.Fprintln(out, \"    -version: Prints the version\")\n\tfmt.Fprintln(out, \"    -h, -help: Prints this help message\")\n\tfmt.Fprintln(out, \"\")\n\tfmt.Fprintln(out, \"  Modes:\")\n\tfmt.Fprintln(out, \"    \"+caSummary())\n\tfmt.Fprintln(out, \"    \"+keygenSummary())\n\tfmt.Fprintln(out, \"    \"+signSummary())\n\tfmt.Fprintln(out, \"    \"+printSummary())\n\tfmt.Fprintln(out, \"    \"+verifySummary())\n\tfmt.Fprintln(out, \"\")\n\tfmt.Fprintf(out, \"  To see usage for a given mode, use %s <mode> -h\\n\", os.Args[0])\n}\n\nfunc mustFlagString(name string, val *string) error {\n\tif *val == \"\" {\n\t\treturn newHelpErrorf(\"-%s is required\", name)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/nebula-cert/main_test.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_help(t *testing.T) {\n\texpected := \"Usage of \" + os.Args[0] + \" <global flags> <mode>:\\n\" +\n\t\t\"  Global flags:\\n\" +\n\t\t\"    -version: Prints the version\\n\" +\n\t\t\"    -h, -help: Prints this help message\\n\\n\" +\n\t\t\"  Modes:\\n\" +\n\t\t\"    \" + caSummary() + \"\\n\" +\n\t\t\"    \" + keygenSummary() + \"\\n\" +\n\t\t\"    \" + signSummary() + \"\\n\" +\n\t\t\"    \" + printSummary() + \"\\n\" +\n\t\t\"    \" + verifySummary() + \"\\n\" +\n\t\t\"\\n\" +\n\t\t\"  To see usage for a given mode, use \" + os.Args[0] + \" <mode> -h\\n\"\n\n\tob := &bytes.Buffer{}\n\n\t// No error test\n\thelp(\"\", ob)\n\tassert.Equal(\n\t\tt,\n\t\texpected,\n\t\tob.String(),\n\t)\n\n\t// Error test\n\tob.Reset()\n\thelp(\"test error\", ob)\n\tassert.Equal(\n\t\tt,\n\t\t\"Error: test error\\n\\n\"+expected,\n\t\tob.String(),\n\t)\n}\n\nfunc Test_handleError(t *testing.T) {\n\tob := &bytes.Buffer{}\n\n\t// normal error\n\thandleError(\"\", errors.New(\"test error\"), ob)\n\tassert.Equal(t, \"Error: test error\\n\", ob.String())\n\n\t// unknown mode help error\n\tob.Reset()\n\thandleError(\"\", newHelpErrorf(\"test %s\", \"error\"), ob)\n\tassert.Equal(t, \"Error: test error\\n\", ob.String())\n\n\t// test all modes with help error\n\tmodes := map[string]func(io.Writer){\"ca\": caHelp, \"print\": printHelp, \"sign\": signHelp, \"verify\": verifyHelp}\n\teb := &bytes.Buffer{}\n\tfor mode, fn := range modes {\n\t\tob.Reset()\n\t\teb.Reset()\n\t\tfn(eb)\n\n\t\thandleError(mode, newHelpErrorf(\"test %s\", \"error\"), ob)\n\t\tassert.Equal(t, \"Error: test error\\n\"+eb.String(), ob.String())\n\t}\n\n}\n\nfunc assertHelpError(t *testing.T, err error, msg string) {\n\tswitch err.(type) {\n\tcase *helpError:\n\t\t// good\n\tdefault:\n\t\tt.Fatal(fmt.Sprintf(\"err was not a helpError: %q, expected %q\", err, msg))\n\t}\n\n\trequire.EqualError(t, err, msg)\n}\n\nfunc optionalPkcs11String(msg string) string {\n\tif p11Supported() {\n\t\treturn msg\n\t} else {\n\t\treturn \"\"\n\t}\n}\n"
  },
  {
    "path": "cmd/nebula-cert/p11_cgo.go",
    "content": "//go:build cgo && pkcs11\n\npackage main\n\nimport (\n\t\"flag\"\n)\n\nfunc p11Supported() bool {\n\treturn true\n}\n\nfunc p11Flag(set *flag.FlagSet) *string {\n\treturn set.String(\"pkcs11\", \"\", \"Optional: PKCS#11 URI to an existing private key\")\n}\n"
  },
  {
    "path": "cmd/nebula-cert/p11_stub.go",
    "content": "//go:build !cgo || !pkcs11\n\npackage main\n\nimport (\n\t\"flag\"\n)\n\nfunc p11Supported() bool {\n\treturn false\n}\n\nfunc p11Flag(set *flag.FlagSet) *string {\n\tvar ret = \"\"\n\treturn &ret\n}\n"
  },
  {
    "path": "cmd/nebula-cert/passwords.go",
    "content": "package main\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"golang.org/x/term\"\n)\n\nvar ErrNoTerminal = errors.New(\"cannot read password from nonexistent terminal\")\n\ntype PasswordReader interface {\n\tReadPassword() ([]byte, error)\n}\n\ntype StdinPasswordReader struct{}\n\nfunc (pr StdinPasswordReader) ReadPassword() ([]byte, error) {\n\tif !term.IsTerminal(int(os.Stdin.Fd())) {\n\t\treturn nil, ErrNoTerminal\n\t}\n\n\tpassword, err := term.ReadPassword(int(os.Stdin.Fd()))\n\tfmt.Println()\n\n\treturn password, err\n}\n"
  },
  {
    "path": "cmd/nebula-cert/passwords_test.go",
    "content": "package main\n\ntype StubPasswordReader struct {\n\tpassword []byte\n\terr      error\n}\n\nfunc (pr *StubPasswordReader) ReadPassword() ([]byte, error) {\n\treturn pr.password, pr.err\n}\n"
  },
  {
    "path": "cmd/nebula-cert/print.go",
    "content": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/skip2/go-qrcode\"\n\t\"github.com/slackhq/nebula/cert\"\n)\n\ntype printFlags struct {\n\tset       *flag.FlagSet\n\tjson      *bool\n\toutQRPath *string\n\tpath      *string\n}\n\nfunc newPrintFlags() *printFlags {\n\tpf := printFlags{set: flag.NewFlagSet(\"print\", flag.ContinueOnError)}\n\tpf.set.Usage = func() {}\n\tpf.json = pf.set.Bool(\"json\", false, \"Optional: outputs certificates in json format\")\n\tpf.outQRPath = pf.set.String(\"out-qr\", \"\", \"Optional: output a qr code image (png) of the certificate\")\n\tpf.path = pf.set.String(\"path\", \"\", \"Required: path to the certificate\")\n\n\treturn &pf\n}\n\nfunc printCert(args []string, out io.Writer, errOut io.Writer) error {\n\tpf := newPrintFlags()\n\terr := pf.set.Parse(args)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif err := mustFlagString(\"path\", pf.path); err != nil {\n\t\treturn err\n\t}\n\n\trawCert, err := os.ReadFile(*pf.path)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to read cert; %s\", err)\n\t}\n\n\tvar c cert.Certificate\n\tvar qrBytes []byte\n\tpart := 0\n\n\tvar jsonCerts []cert.Certificate\n\n\tfor {\n\t\tc, rawCert, err = cert.UnmarshalCertificateFromPEM(rawCert)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error while unmarshaling cert: %s\", err)\n\t\t}\n\n\t\tif *pf.json {\n\t\t\tjsonCerts = append(jsonCerts, c)\n\t\t} else {\n\t\t\t_, _ = out.Write([]byte(c.String()))\n\t\t\t_, _ = out.Write([]byte(\"\\n\"))\n\t\t}\n\n\t\tif *pf.outQRPath != \"\" {\n\t\t\tb, err := c.MarshalPEM()\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error while marshalling cert to PEM: %s\", err)\n\t\t\t}\n\t\t\tqrBytes = append(qrBytes, b...)\n\t\t}\n\n\t\tif rawCert == nil || len(rawCert) == 0 || strings.TrimSpace(string(rawCert)) == \"\" {\n\t\t\tbreak\n\t\t}\n\n\t\tpart++\n\t}\n\n\tif *pf.json {\n\t\tb, _ := json.Marshal(jsonCerts)\n\t\t_, _ = out.Write(b)\n\t\t_, _ = out.Write([]byte(\"\\n\"))\n\t}\n\n\tif *pf.outQRPath != \"\" {\n\t\tb, err := qrcode.Encode(string(qrBytes), qrcode.Medium, -5)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error while generating qr code: %s\", err)\n\t\t}\n\n\t\terr = os.WriteFile(*pf.outQRPath, b, 0600)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error while writing out-qr: %s\", err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc printSummary() string {\n\treturn \"print <flags>: prints details about a certificate\"\n}\n\nfunc printHelp(out io.Writer) {\n\tpf := newPrintFlags()\n\tout.Write([]byte(\"Usage of \" + os.Args[0] + \" \" + printSummary() + \"\\n\"))\n\tpf.set.SetOutput(out)\n\tpf.set.PrintDefaults()\n}\n"
  },
  {
    "path": "cmd/nebula-cert/print_test.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"crypto/ed25519\"\n\t\"crypto/rand\"\n\t\"encoding/hex\"\n\t\"net/netip\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/slackhq/nebula/cert\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_printSummary(t *testing.T) {\n\tassert.Equal(t, \"print <flags>: prints details about a certificate\", printSummary())\n}\n\nfunc Test_printHelp(t *testing.T) {\n\tob := &bytes.Buffer{}\n\tprintHelp(ob)\n\tassert.Equal(\n\t\tt,\n\t\t\"Usage of \"+os.Args[0]+\" print <flags>: prints details about a certificate\\n\"+\n\t\t\t\"  -json\\n\"+\n\t\t\t\"    \\tOptional: outputs certificates in json format\\n\"+\n\t\t\t\"  -out-qr string\\n\"+\n\t\t\t\"    \\tOptional: output a qr code image (png) of the certificate\\n\"+\n\t\t\t\"  -path string\\n\"+\n\t\t\t\"    \\tRequired: path to the certificate\\n\",\n\t\tob.String(),\n\t)\n}\n\nfunc Test_printCert(t *testing.T) {\n\t// Orient our local time and avoid headaches\n\ttime.Local = time.UTC\n\tob := &bytes.Buffer{}\n\teb := &bytes.Buffer{}\n\n\t// no path\n\terr := printCert([]string{}, ob, eb)\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\tassertHelpError(t, err, \"-path is required\")\n\n\t// no cert at path\n\tob.Reset()\n\teb.Reset()\n\terr = printCert([]string{\"-path\", \"does_not_exist\"}, ob, eb)\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\trequire.EqualError(t, err, \"unable to read cert; open does_not_exist: \"+NoSuchFileError)\n\n\t// invalid cert at path\n\tob.Reset()\n\teb.Reset()\n\ttf, err := os.CreateTemp(\"\", \"print-cert\")\n\trequire.NoError(t, err)\n\tdefer os.Remove(tf.Name())\n\n\ttf.WriteString(\"-----BEGIN NOPE-----\")\n\terr = printCert([]string{\"-path\", tf.Name()}, ob, eb)\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\trequire.EqualError(t, err, \"error while unmarshaling cert: input did not contain a valid PEM encoded block\")\n\n\t// test multiple certs\n\tob.Reset()\n\teb.Reset()\n\ttf.Truncate(0)\n\ttf.Seek(0, 0)\n\tca, caKey := NewTestCaCert(\"test ca\", nil, nil, time.Time{}, time.Time{}, nil, nil, nil)\n\tc, _ := NewTestCert(ca, caKey, \"test\", time.Time{}, time.Time{}, []netip.Prefix{netip.MustParsePrefix(\"10.0.0.123/8\")}, nil, []string{\"hi\"})\n\n\tp, _ := c.MarshalPEM()\n\ttf.Write(p)\n\ttf.Write(p)\n\ttf.Write(p)\n\n\terr = printCert([]string{\"-path\", tf.Name()}, ob, eb)\n\tfp, _ := c.Fingerprint()\n\tpk := hex.EncodeToString(c.PublicKey())\n\tsig := hex.EncodeToString(c.Signature())\n\trequire.NoError(t, err)\n\tassert.Equal(\n\t\tt,\n\t\t//\"NebulaCertificate {\\n\\tDetails {\\n\\t\\tName: test\\n\\t\\tIps: []\\n\\t\\tSubnets: []\\n\\t\\tGroups: [\\n\\t\\t\\t\\\"hi\\\"\\n\\t\\t]\\n\\t\\tNot before: 0001-01-01 00:00:00 +0000 UTC\\n\\t\\tNot After: 0001-01-01 00:00:00 +0000 UTC\\n\\t\\tIs CA: false\\n\\t\\tIssuer: \"+c.Issuer()+\"\\n\\t\\tPublic key: \"+pk+\"\\n\\t\\tCurve: CURVE25519\\n\\t}\\n\\tFingerprint: \"+fp+\"\\n\\tSignature: \"+sig+\"\\n}\\nNebulaCertificate {\\n\\tDetails {\\n\\t\\tName: test\\n\\t\\tIps: []\\n\\t\\tSubnets: []\\n\\t\\tGroups: [\\n\\t\\t\\t\\\"hi\\\"\\n\\t\\t]\\n\\t\\tNot before: 0001-01-01 00:00:00 +0000 UTC\\n\\t\\tNot After: 0001-01-01 00:00:00 +0000 UTC\\n\\t\\tIs CA: false\\n\\t\\tIssuer: \"+c.Issuer()+\"\\n\\t\\tPublic key: \"+pk+\"\\n\\t\\tCurve: CURVE25519\\n\\t}\\n\\tFingerprint: \"+fp+\"\\n\\tSignature: \"+sig+\"\\n}\\nNebulaCertificate {\\n\\tDetails {\\n\\t\\tName: test\\n\\t\\tIps: []\\n\\t\\tSubnets: []\\n\\t\\tGroups: [\\n\\t\\t\\t\\\"hi\\\"\\n\\t\\t]\\n\\t\\tNot before: 0001-01-01 00:00:00 +0000 UTC\\n\\t\\tNot After: 0001-01-01 00:00:00 +0000 UTC\\n\\t\\tIs CA: false\\n\\t\\tIssuer: \"+c.Issuer()+\"\\n\\t\\tPublic key: \"+pk+\"\\n\\t\\tCurve: CURVE25519\\n\\t}\\n\\tFingerprint: \"+fp+\"\\n\\tSignature: \"+sig+\"\\n}\\n\",\n\t\t`{\n\t\"details\": {\n\t\t\"curve\": \"CURVE25519\",\n\t\t\"groups\": [\n\t\t\t\"hi\"\n\t\t],\n\t\t\"isCa\": false,\n\t\t\"issuer\": \"`+c.Issuer()+`\",\n\t\t\"name\": \"test\",\n\t\t\"networks\": [\n\t\t\t\"10.0.0.123/8\"\n\t\t],\n\t\t\"notAfter\": \"0001-01-01T00:00:00Z\",\n\t\t\"notBefore\": \"0001-01-01T00:00:00Z\",\n\t\t\"publicKey\": \"`+pk+`\",\n\t\t\"unsafeNetworks\": []\n\t},\n\t\"fingerprint\": \"`+fp+`\",\n\t\"signature\": \"`+sig+`\",\n\t\"version\": 1\n}\n{\n\t\"details\": {\n\t\t\"curve\": \"CURVE25519\",\n\t\t\"groups\": [\n\t\t\t\"hi\"\n\t\t],\n\t\t\"isCa\": false,\n\t\t\"issuer\": \"`+c.Issuer()+`\",\n\t\t\"name\": \"test\",\n\t\t\"networks\": [\n\t\t\t\"10.0.0.123/8\"\n\t\t],\n\t\t\"notAfter\": \"0001-01-01T00:00:00Z\",\n\t\t\"notBefore\": \"0001-01-01T00:00:00Z\",\n\t\t\"publicKey\": \"`+pk+`\",\n\t\t\"unsafeNetworks\": []\n\t},\n\t\"fingerprint\": \"`+fp+`\",\n\t\"signature\": \"`+sig+`\",\n\t\"version\": 1\n}\n{\n\t\"details\": {\n\t\t\"curve\": \"CURVE25519\",\n\t\t\"groups\": [\n\t\t\t\"hi\"\n\t\t],\n\t\t\"isCa\": false,\n\t\t\"issuer\": \"`+c.Issuer()+`\",\n\t\t\"name\": \"test\",\n\t\t\"networks\": [\n\t\t\t\"10.0.0.123/8\"\n\t\t],\n\t\t\"notAfter\": \"0001-01-01T00:00:00Z\",\n\t\t\"notBefore\": \"0001-01-01T00:00:00Z\",\n\t\t\"publicKey\": \"`+pk+`\",\n\t\t\"unsafeNetworks\": []\n\t},\n\t\"fingerprint\": \"`+fp+`\",\n\t\"signature\": \"`+sig+`\",\n\t\"version\": 1\n}\n`,\n\t\tob.String(),\n\t)\n\tassert.Empty(t, eb.String())\n\n\t// test json\n\tob.Reset()\n\teb.Reset()\n\ttf.Truncate(0)\n\ttf.Seek(0, 0)\n\ttf.Write(p)\n\ttf.Write(p)\n\ttf.Write(p)\n\n\terr = printCert([]string{\"-json\", \"-path\", tf.Name()}, ob, eb)\n\tfp, _ = c.Fingerprint()\n\tpk = hex.EncodeToString(c.PublicKey())\n\tsig = hex.EncodeToString(c.Signature())\n\trequire.NoError(t, err)\n\tassert.Equal(\n\t\tt,\n\t\t`[{\"details\":{\"curve\":\"CURVE25519\",\"groups\":[\"hi\"],\"isCa\":false,\"issuer\":\"`+c.Issuer()+`\",\"name\":\"test\",\"networks\":[\"10.0.0.123/8\"],\"notAfter\":\"0001-01-01T00:00:00Z\",\"notBefore\":\"0001-01-01T00:00:00Z\",\"publicKey\":\"`+pk+`\",\"unsafeNetworks\":[]},\"fingerprint\":\"`+fp+`\",\"signature\":\"`+sig+`\",\"version\":1},{\"details\":{\"curve\":\"CURVE25519\",\"groups\":[\"hi\"],\"isCa\":false,\"issuer\":\"`+c.Issuer()+`\",\"name\":\"test\",\"networks\":[\"10.0.0.123/8\"],\"notAfter\":\"0001-01-01T00:00:00Z\",\"notBefore\":\"0001-01-01T00:00:00Z\",\"publicKey\":\"`+pk+`\",\"unsafeNetworks\":[]},\"fingerprint\":\"`+fp+`\",\"signature\":\"`+sig+`\",\"version\":1},{\"details\":{\"curve\":\"CURVE25519\",\"groups\":[\"hi\"],\"isCa\":false,\"issuer\":\"`+c.Issuer()+`\",\"name\":\"test\",\"networks\":[\"10.0.0.123/8\"],\"notAfter\":\"0001-01-01T00:00:00Z\",\"notBefore\":\"0001-01-01T00:00:00Z\",\"publicKey\":\"`+pk+`\",\"unsafeNetworks\":[]},\"fingerprint\":\"`+fp+`\",\"signature\":\"`+sig+`\",\"version\":1}]\n`,\n\t\tob.String(),\n\t)\n\tassert.Empty(t, eb.String())\n}\n\n// NewTestCaCert will generate a CA cert\nfunc NewTestCaCert(name string, pubKey, privKey []byte, before, after time.Time, networks, unsafeNetworks []netip.Prefix, groups []string) (cert.Certificate, []byte) {\n\tvar err error\n\tif pubKey == nil || privKey == nil {\n\t\tpubKey, privKey, err = ed25519.GenerateKey(rand.Reader)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n\n\tt := &cert.TBSCertificate{\n\t\tVersion:        cert.Version1,\n\t\tName:           name,\n\t\tNotBefore:      time.Unix(before.Unix(), 0),\n\t\tNotAfter:       time.Unix(after.Unix(), 0),\n\t\tPublicKey:      pubKey,\n\t\tNetworks:       networks,\n\t\tUnsafeNetworks: unsafeNetworks,\n\t\tGroups:         groups,\n\t\tIsCA:           true,\n\t}\n\n\tc, err := t.Sign(nil, cert.Curve_CURVE25519, privKey)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn c, privKey\n}\n\nfunc NewTestCert(ca cert.Certificate, signerKey []byte, name string, before, after time.Time, networks, unsafeNetworks []netip.Prefix, groups []string) (cert.Certificate, []byte) {\n\tif before.IsZero() {\n\t\tbefore = ca.NotBefore()\n\t}\n\n\tif after.IsZero() {\n\t\tafter = ca.NotAfter()\n\t}\n\n\tif len(networks) == 0 {\n\t\tnetworks = []netip.Prefix{netip.MustParsePrefix(\"10.0.0.123/8\")}\n\t}\n\n\tpub, rawPriv := x25519Keypair()\n\tnc := &cert.TBSCertificate{\n\t\tVersion:        cert.Version1,\n\t\tName:           name,\n\t\tNetworks:       networks,\n\t\tUnsafeNetworks: unsafeNetworks,\n\t\tGroups:         groups,\n\t\tNotBefore:      time.Unix(before.Unix(), 0),\n\t\tNotAfter:       time.Unix(after.Unix(), 0),\n\t\tPublicKey:      pub,\n\t\tIsCA:           false,\n\t}\n\n\tc, err := nc.Sign(ca, ca.Curve(), signerKey)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn c, rawPriv\n}\n"
  },
  {
    "path": "cmd/nebula-cert/sign.go",
    "content": "package main\n\nimport (\n\t\"crypto/ecdh\"\n\t\"crypto/rand\"\n\t\"errors\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/netip\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/skip2/go-qrcode\"\n\t\"github.com/slackhq/nebula/cert\"\n\t\"github.com/slackhq/nebula/pkclient\"\n\t\"golang.org/x/crypto/curve25519\"\n)\n\ntype signFlags struct {\n\tset            *flag.FlagSet\n\tversion        *uint\n\tcaKeyPath      *string\n\tcaCertPath     *string\n\tname           *string\n\tnetworks       *string\n\tunsafeNetworks *string\n\tduration       *time.Duration\n\tinPubPath      *string\n\toutKeyPath     *string\n\toutCertPath    *string\n\toutQRPath      *string\n\tgroups         *string\n\n\tp11url *string\n\n\t// Deprecated options\n\tip      *string\n\tsubnets *string\n}\n\nfunc newSignFlags() *signFlags {\n\tsf := signFlags{set: flag.NewFlagSet(\"sign\", flag.ContinueOnError)}\n\tsf.set.Usage = func() {}\n\tsf.version = sf.set.Uint(\"version\", 0, \"Optional: version of the certificate format to use. The default is to match the version of the signing CA\")\n\tsf.caKeyPath = sf.set.String(\"ca-key\", \"ca.key\", \"Optional: path to the signing CA key\")\n\tsf.caCertPath = sf.set.String(\"ca-crt\", \"ca.crt\", \"Optional: path to the signing CA cert\")\n\tsf.name = sf.set.String(\"name\", \"\", \"Required: name of the cert, usually a hostname\")\n\tsf.networks = sf.set.String(\"networks\", \"\", \"Required: comma separated list of ip address and network in CIDR notation to assign to this cert\")\n\tsf.unsafeNetworks = sf.set.String(\"unsafe-networks\", \"\", \"Optional: comma separated list of ip address and network in CIDR notation. Unsafe networks this cert can route for\")\n\tsf.duration = sf.set.Duration(\"duration\", 0, \"Optional: how long the cert should be valid for. The default is 1 second before the signing cert expires. Valid time units are seconds: \\\"s\\\", minutes: \\\"m\\\", hours: \\\"h\\\"\")\n\tsf.inPubPath = sf.set.String(\"in-pub\", \"\", \"Optional (if out-key not set): path to read a previously generated public key\")\n\tsf.outKeyPath = sf.set.String(\"out-key\", \"\", \"Optional (if in-pub not set): path to write the private key to\")\n\tsf.outCertPath = sf.set.String(\"out-crt\", \"\", \"Optional: path to write the certificate to\")\n\tsf.outQRPath = sf.set.String(\"out-qr\", \"\", \"Optional: output a qr code image (png) of the certificate\")\n\tsf.groups = sf.set.String(\"groups\", \"\", \"Optional: comma separated list of groups\")\n\tsf.p11url = p11Flag(sf.set)\n\n\tsf.ip = sf.set.String(\"ip\", \"\", \"Deprecated, see -networks\")\n\tsf.subnets = sf.set.String(\"subnets\", \"\", \"Deprecated, see -unsafe-networks\")\n\treturn &sf\n}\n\nfunc signCert(args []string, out io.Writer, errOut io.Writer, pr PasswordReader) error {\n\tsf := newSignFlags()\n\terr := sf.set.Parse(args)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tisP11 := len(*sf.p11url) > 0\n\n\tif !isP11 {\n\t\tif err := mustFlagString(\"ca-key\", sf.caKeyPath); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif err := mustFlagString(\"ca-crt\", sf.caCertPath); err != nil {\n\t\treturn err\n\t}\n\tif err := mustFlagString(\"name\", sf.name); err != nil {\n\t\treturn err\n\t}\n\tif !isP11 && *sf.inPubPath != \"\" && *sf.outKeyPath != \"\" {\n\t\treturn newHelpErrorf(\"cannot set both -in-pub and -out-key\")\n\t}\n\n\tvar v4Networks []netip.Prefix\n\tvar v6Networks []netip.Prefix\n\tif *sf.networks == \"\" && *sf.ip != \"\" {\n\t\t// Pull up deprecated -ip flag if needed\n\t\t*sf.networks = *sf.ip\n\t}\n\n\tif len(*sf.networks) == 0 {\n\t\treturn newHelpErrorf(\"-networks is required\")\n\t}\n\n\tversion := cert.Version(*sf.version)\n\tif version != 0 && version != cert.Version1 && version != cert.Version2 {\n\t\treturn newHelpErrorf(\"-version must be either %v or %v\", cert.Version1, cert.Version2)\n\t}\n\n\tvar curve cert.Curve\n\tvar caKey []byte\n\n\tif !isP11 {\n\t\tvar rawCAKey []byte\n\t\trawCAKey, err := os.ReadFile(*sf.caKeyPath)\n\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error while reading ca-key: %s\", err)\n\t\t}\n\n\t\t// naively attempt to decode the private key as though it is not encrypted\n\t\tcaKey, _, curve, err = cert.UnmarshalSigningPrivateKeyFromPEM(rawCAKey)\n\t\tif errors.Is(err, cert.ErrPrivateKeyEncrypted) {\n\t\t\tvar passphrase []byte\n\t\t\tpassphrase = []byte(os.Getenv(\"NEBULA_CA_PASSPHRASE\"))\n\t\t\tif len(passphrase) == 0 {\n\t\t\t\t// ask for a passphrase until we get one\n\t\t\t\tfor i := 0; i < 5; i++ {\n\t\t\t\t\tout.Write([]byte(\"Enter passphrase: \"))\n\t\t\t\t\tpassphrase, err = pr.ReadPassword()\n\n\t\t\t\t\tif errors.Is(err, ErrNoTerminal) {\n\t\t\t\t\t\treturn fmt.Errorf(\"ca-key is encrypted and must be decrypted interactively\")\n\t\t\t\t\t} else if err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"error reading password: %s\", err)\n\t\t\t\t\t}\n\n\t\t\t\t\tif len(passphrase) > 0 {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif len(passphrase) == 0 {\n\t\t\t\t\treturn fmt.Errorf(\"cannot open encrypted ca-key without passphrase\")\n\t\t\t\t}\n\t\t\t}\n\t\t\tcurve, caKey, _, err = cert.DecryptAndUnmarshalSigningPrivateKey(passphrase, rawCAKey)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error while parsing encrypted ca-key: %s\", err)\n\t\t\t}\n\t\t} else if err != nil {\n\t\t\treturn fmt.Errorf(\"error while parsing ca-key: %s\", err)\n\t\t}\n\t}\n\n\trawCACert, err := os.ReadFile(*sf.caCertPath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error while reading ca-crt: %s\", err)\n\t}\n\n\tcaCert, _, err := cert.UnmarshalCertificateFromPEM(rawCACert)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error while parsing ca-crt: %s\", err)\n\t}\n\n\tif !isP11 {\n\t\tif err := caCert.VerifyPrivateKey(curve, caKey); err != nil {\n\t\t\treturn fmt.Errorf(\"refusing to sign, root certificate does not match private key\")\n\t\t}\n\t}\n\n\tif caCert.Expired(time.Now()) {\n\t\treturn fmt.Errorf(\"ca certificate is expired\")\n\t}\n\n\tif version == 0 {\n\t\tversion = caCert.Version()\n\t}\n\n\t// if no duration is given, expire one second before the root expires\n\tif *sf.duration <= 0 {\n\t\t*sf.duration = time.Until(caCert.NotAfter()) - time.Second*1\n\t}\n\n\tif *sf.networks != \"\" {\n\t\tfor _, rs := range strings.Split(*sf.networks, \",\") {\n\t\t\trs := strings.Trim(rs, \" \")\n\t\t\tif rs != \"\" {\n\t\t\t\tn, err := netip.ParsePrefix(rs)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn newHelpErrorf(\"invalid -networks definition: %s\", rs)\n\t\t\t\t}\n\n\t\t\t\tif n.Addr().Is4() {\n\t\t\t\t\tv4Networks = append(v4Networks, n)\n\t\t\t\t} else {\n\t\t\t\t\tv6Networks = append(v6Networks, n)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tvar v4UnsafeNetworks []netip.Prefix\n\tvar v6UnsafeNetworks []netip.Prefix\n\tif *sf.unsafeNetworks == \"\" && *sf.subnets != \"\" {\n\t\t// Pull up deprecated -subnets flag if needed\n\t\t*sf.unsafeNetworks = *sf.subnets\n\t}\n\n\tif *sf.unsafeNetworks != \"\" {\n\t\tfor _, rs := range strings.Split(*sf.unsafeNetworks, \",\") {\n\t\t\trs := strings.Trim(rs, \" \")\n\t\t\tif rs != \"\" {\n\t\t\t\tn, err := netip.ParsePrefix(rs)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn newHelpErrorf(\"invalid -unsafe-networks definition: %s\", rs)\n\t\t\t\t}\n\n\t\t\t\tif n.Addr().Is4() {\n\t\t\t\t\tv4UnsafeNetworks = append(v4UnsafeNetworks, n)\n\t\t\t\t} else {\n\t\t\t\t\tv6UnsafeNetworks = append(v6UnsafeNetworks, n)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tvar groups []string\n\tif *sf.groups != \"\" {\n\t\tfor _, rg := range strings.Split(*sf.groups, \",\") {\n\t\t\tg := strings.TrimSpace(rg)\n\t\t\tif g != \"\" {\n\t\t\t\tgroups = append(groups, g)\n\t\t\t}\n\t\t}\n\t}\n\n\tvar pub, rawPriv []byte\n\tvar p11Client *pkclient.PKClient\n\n\tif isP11 {\n\t\tcurve = cert.Curve_P256\n\t\tp11Client, err = pkclient.FromUrl(*sf.p11url)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error while creating PKCS#11 client: %w\", err)\n\t\t}\n\t\tdefer func(client *pkclient.PKClient) {\n\t\t\t_ = client.Close()\n\t\t}(p11Client)\n\t}\n\n\tif *sf.inPubPath != \"\" {\n\t\tvar pubCurve cert.Curve\n\t\trawPub, err := os.ReadFile(*sf.inPubPath)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error while reading in-pub: %s\", err)\n\t\t}\n\n\t\tpub, _, pubCurve, err = cert.UnmarshalPublicKeyFromPEM(rawPub)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error while parsing in-pub: %s\", err)\n\t\t}\n\t\tif pubCurve != curve {\n\t\t\treturn fmt.Errorf(\"curve of in-pub does not match ca\")\n\t\t}\n\t} else if isP11 {\n\t\tpub, err = p11Client.GetPubKey()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error while getting public key with PKCS#11: %w\", err)\n\t\t}\n\t} else {\n\t\tpub, rawPriv = newKeypair(curve)\n\t}\n\n\tif *sf.outKeyPath == \"\" {\n\t\t*sf.outKeyPath = *sf.name + \".key\"\n\t}\n\n\tif *sf.outCertPath == \"\" {\n\t\t*sf.outCertPath = *sf.name + \".crt\"\n\t}\n\n\tif _, err := os.Stat(*sf.outCertPath); err == nil {\n\t\treturn fmt.Errorf(\"refusing to overwrite existing cert: %s\", *sf.outCertPath)\n\t}\n\n\tvar crts []cert.Certificate\n\n\tnotBefore := time.Now()\n\tnotAfter := notBefore.Add(*sf.duration)\n\n\tswitch version {\n\tcase cert.Version1:\n\t\t// Make sure we have only one ipv4 address\n\t\tif len(v4Networks) != 1 {\n\t\t\treturn newHelpErrorf(\"invalid -networks definition: v1 certificates can only have a single ipv4 address\")\n\t\t}\n\n\t\tif len(v6Networks) > 0 {\n\t\t\treturn newHelpErrorf(\"invalid -networks definition: v1 certificates can only contain ipv4 addresses\")\n\t\t}\n\n\t\tif len(v6UnsafeNetworks) > 0 {\n\t\t\treturn newHelpErrorf(\"invalid -unsafe-networks definition: v1 certificates can only contain ipv4 addresses\")\n\t\t}\n\n\t\tt := &cert.TBSCertificate{\n\t\t\tVersion:        cert.Version1,\n\t\t\tName:           *sf.name,\n\t\t\tNetworks:       []netip.Prefix{v4Networks[0]},\n\t\t\tGroups:         groups,\n\t\t\tUnsafeNetworks: v4UnsafeNetworks,\n\t\t\tNotBefore:      notBefore,\n\t\t\tNotAfter:       notAfter,\n\t\t\tPublicKey:      pub,\n\t\t\tIsCA:           false,\n\t\t\tCurve:          curve,\n\t\t}\n\n\t\tvar nc cert.Certificate\n\t\tif p11Client == nil {\n\t\t\tnc, err = t.Sign(caCert, curve, caKey)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error while signing: %w\", err)\n\t\t\t}\n\t\t} else {\n\t\t\tnc, err = t.SignWith(caCert, curve, p11Client.SignASN1)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error while signing with PKCS#11: %w\", err)\n\t\t\t}\n\t\t}\n\n\t\tcrts = append(crts, nc)\n\n\tcase cert.Version2:\n\t\tt := &cert.TBSCertificate{\n\t\t\tVersion:        cert.Version2,\n\t\t\tName:           *sf.name,\n\t\t\tNetworks:       append(v4Networks, v6Networks...),\n\t\t\tGroups:         groups,\n\t\t\tUnsafeNetworks: append(v4UnsafeNetworks, v6UnsafeNetworks...),\n\t\t\tNotBefore:      notBefore,\n\t\t\tNotAfter:       notAfter,\n\t\t\tPublicKey:      pub,\n\t\t\tIsCA:           false,\n\t\t\tCurve:          curve,\n\t\t}\n\n\t\tvar nc cert.Certificate\n\t\tif p11Client == nil {\n\t\t\tnc, err = t.Sign(caCert, curve, caKey)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error while signing: %w\", err)\n\t\t\t}\n\t\t} else {\n\t\t\tnc, err = t.SignWith(caCert, curve, p11Client.SignASN1)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error while signing with PKCS#11: %w\", err)\n\t\t\t}\n\t\t}\n\n\t\tcrts = append(crts, nc)\n\tdefault:\n\t\t// this should be unreachable\n\t\treturn fmt.Errorf(\"invalid version: %d\", version)\n\t}\n\n\tif !isP11 && *sf.inPubPath == \"\" {\n\t\tif _, err := os.Stat(*sf.outKeyPath); err == nil {\n\t\t\treturn fmt.Errorf(\"refusing to overwrite existing key: %s\", *sf.outKeyPath)\n\t\t}\n\n\t\terr = os.WriteFile(*sf.outKeyPath, cert.MarshalPrivateKeyToPEM(curve, rawPriv), 0600)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error while writing out-key: %s\", err)\n\t\t}\n\t}\n\n\tvar b []byte\n\tfor _, c := range crts {\n\t\tsb, err := c.MarshalPEM()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error while marshalling certificate: %s\", err)\n\t\t}\n\t\tb = append(b, sb...)\n\t}\n\n\terr = os.WriteFile(*sf.outCertPath, b, 0600)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error while writing out-crt: %s\", err)\n\t}\n\n\tif *sf.outQRPath != \"\" {\n\t\tb, err = qrcode.Encode(string(b), qrcode.Medium, -5)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error while generating qr code: %s\", err)\n\t\t}\n\n\t\terr = os.WriteFile(*sf.outQRPath, b, 0600)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error while writing out-qr: %s\", err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc newKeypair(curve cert.Curve) ([]byte, []byte) {\n\tswitch curve {\n\tcase cert.Curve_CURVE25519:\n\t\treturn x25519Keypair()\n\tcase cert.Curve_P256:\n\t\treturn p256Keypair()\n\tdefault:\n\t\treturn nil, nil\n\t}\n}\n\nfunc x25519Keypair() ([]byte, []byte) {\n\tprivkey := make([]byte, 32)\n\tif _, err := io.ReadFull(rand.Reader, privkey); err != nil {\n\t\tpanic(err)\n\t}\n\n\tpubkey, err := curve25519.X25519(privkey, curve25519.Basepoint)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn pubkey, privkey\n}\n\nfunc p256Keypair() ([]byte, []byte) {\n\tprivkey, err := ecdh.P256().GenerateKey(rand.Reader)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tpubkey := privkey.PublicKey()\n\treturn pubkey.Bytes(), privkey.Bytes()\n}\n\nfunc signSummary() string {\n\treturn \"sign <flags>: create and sign a certificate\"\n}\n\nfunc signHelp(out io.Writer) {\n\tsf := newSignFlags()\n\tout.Write([]byte(\"Usage of \" + os.Args[0] + \" \" + signSummary() + \"\\n\"))\n\tsf.set.SetOutput(out)\n\tsf.set.PrintDefaults()\n}\n"
  },
  {
    "path": "cmd/nebula-cert/sign_test.go",
    "content": "//go:build !windows\n// +build !windows\n\npackage main\n\nimport (\n\t\"bytes\"\n\t\"crypto/rand\"\n\t\"errors\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/slackhq/nebula/cert\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"golang.org/x/crypto/ed25519\"\n)\n\nfunc Test_signSummary(t *testing.T) {\n\tassert.Equal(t, \"sign <flags>: create and sign a certificate\", signSummary())\n}\n\nfunc Test_signHelp(t *testing.T) {\n\tob := &bytes.Buffer{}\n\tsignHelp(ob)\n\tassert.Equal(\n\t\tt,\n\t\t\"Usage of \"+os.Args[0]+\" sign <flags>: create and sign a certificate\\n\"+\n\t\t\t\"  -ca-crt string\\n\"+\n\t\t\t\"    \\tOptional: path to the signing CA cert (default \\\"ca.crt\\\")\\n\"+\n\t\t\t\"  -ca-key string\\n\"+\n\t\t\t\"    \\tOptional: path to the signing CA key (default \\\"ca.key\\\")\\n\"+\n\t\t\t\"  -duration duration\\n\"+\n\t\t\t\"    \\tOptional: how long the cert should be valid for. The default is 1 second before the signing cert expires. Valid time units are seconds: \\\"s\\\", minutes: \\\"m\\\", hours: \\\"h\\\"\\n\"+\n\t\t\t\"  -groups string\\n\"+\n\t\t\t\"    \\tOptional: comma separated list of groups\\n\"+\n\t\t\t\"  -in-pub string\\n\"+\n\t\t\t\"    \\tOptional (if out-key not set): path to read a previously generated public key\\n\"+\n\t\t\t\"  -ip string\\n\"+\n\t\t\t\"    \\tDeprecated, see -networks\\n\"+\n\t\t\t\"  -name string\\n\"+\n\t\t\t\"    \\tRequired: name of the cert, usually a hostname\\n\"+\n\t\t\t\"  -networks string\\n\"+\n\t\t\t\"    \\tRequired: comma separated list of ip address and network in CIDR notation to assign to this cert\\n\"+\n\t\t\t\"  -out-crt string\\n\"+\n\t\t\t\"    \\tOptional: path to write the certificate to\\n\"+\n\t\t\t\"  -out-key string\\n\"+\n\t\t\t\"    \\tOptional (if in-pub not set): path to write the private key to\\n\"+\n\t\t\t\"  -out-qr string\\n\"+\n\t\t\t\"    \\tOptional: output a qr code image (png) of the certificate\\n\"+\n\t\t\toptionalPkcs11String(\"  -pkcs11 string\\n    \\tOptional: PKCS#11 URI to an existing private key\\n\")+\n\t\t\t\"  -subnets string\\n\"+\n\t\t\t\"    \\tDeprecated, see -unsafe-networks\\n\"+\n\t\t\t\"  -unsafe-networks string\\n\"+\n\t\t\t\"    \\tOptional: comma separated list of ip address and network in CIDR notation. Unsafe networks this cert can route for\\n\"+\n\t\t\t\"  -version uint\\n\"+\n\t\t\t\"    \\tOptional: version of the certificate format to use. The default is to match the version of the signing CA\\n\",\n\t\tob.String(),\n\t)\n}\n\nfunc Test_signCert(t *testing.T) {\n\tob := &bytes.Buffer{}\n\teb := &bytes.Buffer{}\n\n\tnopw := &StubPasswordReader{\n\t\tpassword: []byte(\"\"),\n\t\terr:      nil,\n\t}\n\n\terrpw := &StubPasswordReader{\n\t\tpassword: []byte(\"\"),\n\t\terr:      errors.New(\"stub error\"),\n\t}\n\n\tpassphrase := []byte(\"DO NOT USE THIS KEY\")\n\ttestpw := &StubPasswordReader{\n\t\tpassword: passphrase,\n\t\terr:      nil,\n\t}\n\n\t// required args\n\tassertHelpError(t, signCert(\n\t\t[]string{\"-version\", \"1\", \"-ca-crt\", \"./nope\", \"-ca-key\", \"./nope\", \"-ip\", \"1.1.1.1/24\", \"-out-key\", \"nope\", \"-out-crt\", \"nope\"}, ob, eb, nopw,\n\t), \"-name is required\")\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\n\tassertHelpError(t, signCert(\n\t\t[]string{\"-version\", \"1\", \"-ca-crt\", \"./nope\", \"-ca-key\", \"./nope\", \"-name\", \"test\", \"-out-key\", \"nope\", \"-out-crt\", \"nope\"}, ob, eb, nopw,\n\t), \"-networks is required\")\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\n\t// cannot set -in-pub and -out-key\n\tassertHelpError(t, signCert(\n\t\t[]string{\"-version\", \"1\", \"-ca-crt\", \"./nope\", \"-ca-key\", \"./nope\", \"-name\", \"test\", \"-in-pub\", \"nope\", \"-ip\", \"1.1.1.1/24\", \"-out-crt\", \"nope\", \"-out-key\", \"nope\"}, ob, eb, nopw,\n\t), \"cannot set both -in-pub and -out-key\")\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\n\t// failed to read key\n\tob.Reset()\n\teb.Reset()\n\targs := []string{\"-version\", \"1\", \"-ca-crt\", \"./nope\", \"-ca-key\", \"./nope\", \"-name\", \"test\", \"-ip\", \"1.1.1.1/24\", \"-out-crt\", \"nope\", \"-out-key\", \"nope\", \"-duration\", \"100m\"}\n\trequire.EqualError(t, signCert(args, ob, eb, nopw), \"error while reading ca-key: open ./nope: \"+NoSuchFileError)\n\n\t// failed to unmarshal key\n\tob.Reset()\n\teb.Reset()\n\tcaKeyF, err := os.CreateTemp(\"\", \"sign-cert.key\")\n\trequire.NoError(t, err)\n\tdefer os.Remove(caKeyF.Name())\n\n\targs = []string{\"-version\", \"1\", \"-ca-crt\", \"./nope\", \"-ca-key\", caKeyF.Name(), \"-name\", \"test\", \"-ip\", \"1.1.1.1/24\", \"-out-crt\", \"nope\", \"-out-key\", \"nope\", \"-duration\", \"100m\"}\n\trequire.EqualError(t, signCert(args, ob, eb, nopw), \"error while parsing ca-key: input did not contain a valid PEM encoded block\")\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\n\t// Write a proper ca key for later\n\tob.Reset()\n\teb.Reset()\n\tcaPub, caPriv, _ := ed25519.GenerateKey(rand.Reader)\n\tcaKeyF.Write(cert.MarshalSigningPrivateKeyToPEM(cert.Curve_CURVE25519, caPriv))\n\n\t// failed to read cert\n\targs = []string{\"-version\", \"1\", \"-ca-crt\", \"./nope\", \"-ca-key\", caKeyF.Name(), \"-name\", \"test\", \"-ip\", \"1.1.1.1/24\", \"-out-crt\", \"nope\", \"-out-key\", \"nope\", \"-duration\", \"100m\"}\n\trequire.EqualError(t, signCert(args, ob, eb, nopw), \"error while reading ca-crt: open ./nope: \"+NoSuchFileError)\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\n\t// failed to unmarshal cert\n\tob.Reset()\n\teb.Reset()\n\tcaCrtF, err := os.CreateTemp(\"\", \"sign-cert.crt\")\n\trequire.NoError(t, err)\n\tdefer os.Remove(caCrtF.Name())\n\n\targs = []string{\"-version\", \"1\", \"-ca-crt\", caCrtF.Name(), \"-ca-key\", caKeyF.Name(), \"-name\", \"test\", \"-ip\", \"1.1.1.1/24\", \"-out-crt\", \"nope\", \"-out-key\", \"nope\", \"-duration\", \"100m\"}\n\trequire.EqualError(t, signCert(args, ob, eb, nopw), \"error while parsing ca-crt: input did not contain a valid PEM encoded block\")\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\n\t// write a proper ca cert for later\n\tca, _ := NewTestCaCert(\"ca\", caPub, caPriv, time.Now(), time.Now().Add(time.Minute*200), nil, nil, nil)\n\tb, _ := ca.MarshalPEM()\n\tcaCrtF.Write(b)\n\n\t// failed to read pub\n\targs = []string{\"-version\", \"1\", \"-ca-crt\", caCrtF.Name(), \"-ca-key\", caKeyF.Name(), \"-name\", \"test\", \"-ip\", \"1.1.1.1/24\", \"-out-crt\", \"nope\", \"-in-pub\", \"./nope\", \"-duration\", \"100m\"}\n\trequire.EqualError(t, signCert(args, ob, eb, nopw), \"error while reading in-pub: open ./nope: \"+NoSuchFileError)\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\n\t// failed to unmarshal pub\n\tob.Reset()\n\teb.Reset()\n\tinPubF, err := os.CreateTemp(\"\", \"in.pub\")\n\trequire.NoError(t, err)\n\tdefer os.Remove(inPubF.Name())\n\n\targs = []string{\"-version\", \"1\", \"-ca-crt\", caCrtF.Name(), \"-ca-key\", caKeyF.Name(), \"-name\", \"test\", \"-ip\", \"1.1.1.1/24\", \"-out-crt\", \"nope\", \"-in-pub\", inPubF.Name(), \"-duration\", \"100m\"}\n\trequire.EqualError(t, signCert(args, ob, eb, nopw), \"error while parsing in-pub: input did not contain a valid PEM encoded block\")\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\n\t// write a proper pub for later\n\tob.Reset()\n\teb.Reset()\n\tinPub, _ := x25519Keypair()\n\tinPubF.Write(cert.MarshalPublicKeyToPEM(cert.Curve_CURVE25519, inPub))\n\n\t// bad ip cidr\n\tob.Reset()\n\teb.Reset()\n\targs = []string{\"-version\", \"1\", \"-ca-crt\", caCrtF.Name(), \"-ca-key\", caKeyF.Name(), \"-name\", \"test\", \"-ip\", \"a1.1.1.1/24\", \"-out-crt\", \"nope\", \"-out-key\", \"nope\", \"-duration\", \"100m\"}\n\tassertHelpError(t, signCert(args, ob, eb, nopw), \"invalid -networks definition: a1.1.1.1/24\")\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\n\tob.Reset()\n\teb.Reset()\n\targs = []string{\"-version\", \"1\", \"-ca-crt\", caCrtF.Name(), \"-ca-key\", caKeyF.Name(), \"-name\", \"test\", \"-ip\", \"100::100/100\", \"-out-crt\", \"nope\", \"-out-key\", \"nope\", \"-duration\", \"100m\"}\n\tassertHelpError(t, signCert(args, ob, eb, nopw), \"invalid -networks definition: v1 certificates can only have a single ipv4 address\")\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\n\tob.Reset()\n\teb.Reset()\n\targs = []string{\"-version\", \"1\", \"-ca-crt\", caCrtF.Name(), \"-ca-key\", caKeyF.Name(), \"-name\", \"test\", \"-ip\", \"1.1.1.1/24,1.1.1.2/24\", \"-out-crt\", \"nope\", \"-out-key\", \"nope\", \"-duration\", \"100m\"}\n\tassertHelpError(t, signCert(args, ob, eb, nopw), \"invalid -networks definition: v1 certificates can only have a single ipv4 address\")\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\n\t// bad subnet cidr\n\tob.Reset()\n\teb.Reset()\n\targs = []string{\"-version\", \"1\", \"-ca-crt\", caCrtF.Name(), \"-ca-key\", caKeyF.Name(), \"-name\", \"test\", \"-ip\", \"1.1.1.1/24\", \"-out-crt\", \"nope\", \"-out-key\", \"nope\", \"-duration\", \"100m\", \"-subnets\", \"a\"}\n\tassertHelpError(t, signCert(args, ob, eb, nopw), \"invalid -unsafe-networks definition: a\")\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\n\tob.Reset()\n\teb.Reset()\n\targs = []string{\"-version\", \"1\", \"-ca-crt\", caCrtF.Name(), \"-ca-key\", caKeyF.Name(), \"-name\", \"test\", \"-ip\", \"1.1.1.1/24\", \"-out-crt\", \"nope\", \"-out-key\", \"nope\", \"-duration\", \"100m\", \"-subnets\", \"100::100/100\"}\n\tassertHelpError(t, signCert(args, ob, eb, nopw), \"invalid -unsafe-networks definition: v1 certificates can only contain ipv4 addresses\")\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\n\t// mismatched ca key\n\t_, caPriv2, _ := ed25519.GenerateKey(rand.Reader)\n\tcaKeyF2, err := os.CreateTemp(\"\", \"sign-cert-2.key\")\n\trequire.NoError(t, err)\n\tdefer os.Remove(caKeyF2.Name())\n\tcaKeyF2.Write(cert.MarshalSigningPrivateKeyToPEM(cert.Curve_CURVE25519, caPriv2))\n\n\tob.Reset()\n\teb.Reset()\n\targs = []string{\"-version\", \"1\", \"-ca-crt\", caCrtF.Name(), \"-ca-key\", caKeyF2.Name(), \"-name\", \"test\", \"-ip\", \"1.1.1.1/24\", \"-out-crt\", \"nope\", \"-out-key\", \"nope\", \"-duration\", \"100m\", \"-subnets\", \"a\"}\n\trequire.EqualError(t, signCert(args, ob, eb, nopw), \"refusing to sign, root certificate does not match private key\")\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\n\t// failed key write\n\tob.Reset()\n\teb.Reset()\n\targs = []string{\"-version\", \"1\", \"-ca-crt\", caCrtF.Name(), \"-ca-key\", caKeyF.Name(), \"-name\", \"test\", \"-ip\", \"1.1.1.1/24\", \"-out-crt\", \"/do/not/write/pleasecrt\", \"-out-key\", \"/do/not/write/pleasekey\", \"-duration\", \"100m\", \"-subnets\", \"10.1.1.1/32\"}\n\trequire.EqualError(t, signCert(args, ob, eb, nopw), \"error while writing out-key: open /do/not/write/pleasekey: \"+NoSuchDirError)\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\n\t// create temp key file\n\tkeyF, err := os.CreateTemp(\"\", \"test.key\")\n\trequire.NoError(t, err)\n\tos.Remove(keyF.Name())\n\n\t// failed cert write\n\tob.Reset()\n\teb.Reset()\n\targs = []string{\"-version\", \"1\", \"-ca-crt\", caCrtF.Name(), \"-ca-key\", caKeyF.Name(), \"-name\", \"test\", \"-ip\", \"1.1.1.1/24\", \"-out-crt\", \"/do/not/write/pleasecrt\", \"-out-key\", keyF.Name(), \"-duration\", \"100m\", \"-subnets\", \"10.1.1.1/32\"}\n\trequire.EqualError(t, signCert(args, ob, eb, nopw), \"error while writing out-crt: open /do/not/write/pleasecrt: \"+NoSuchDirError)\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\tos.Remove(keyF.Name())\n\n\t// create temp cert file\n\tcrtF, err := os.CreateTemp(\"\", \"test.crt\")\n\trequire.NoError(t, err)\n\tos.Remove(crtF.Name())\n\n\t// test proper cert with removed empty groups and subnets\n\tob.Reset()\n\teb.Reset()\n\targs = []string{\"-version\", \"1\", \"-ca-crt\", caCrtF.Name(), \"-ca-key\", caKeyF.Name(), \"-name\", \"test\", \"-ip\", \"1.1.1.1/24\", \"-out-crt\", crtF.Name(), \"-out-key\", keyF.Name(), \"-duration\", \"100m\", \"-subnets\", \"10.1.1.1/32, ,   10.2.2.2/32   ,   ,  ,, 10.5.5.5/32\", \"-groups\", \"1,,   2    ,        ,,,3,4,5\"}\n\trequire.NoError(t, signCert(args, ob, eb, nopw))\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\n\t// read cert and key files\n\trb, _ := os.ReadFile(keyF.Name())\n\tlKey, b, curve, err := cert.UnmarshalPrivateKeyFromPEM(rb)\n\tassert.Equal(t, cert.Curve_CURVE25519, curve)\n\tassert.Empty(t, b)\n\trequire.NoError(t, err)\n\tassert.Len(t, lKey, 32)\n\n\trb, _ = os.ReadFile(crtF.Name())\n\tlCrt, b, err := cert.UnmarshalCertificateFromPEM(rb)\n\tassert.Empty(t, b)\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, \"test\", lCrt.Name())\n\tassert.Equal(t, \"1.1.1.1/24\", lCrt.Networks()[0].String())\n\tassert.Len(t, lCrt.Networks(), 1)\n\tassert.False(t, lCrt.IsCA())\n\tassert.Equal(t, []string{\"1\", \"2\", \"3\", \"4\", \"5\"}, lCrt.Groups())\n\tassert.Len(t, lCrt.UnsafeNetworks(), 3)\n\tassert.Len(t, lCrt.PublicKey(), 32)\n\tassert.Equal(t, time.Duration(time.Minute*100), lCrt.NotAfter().Sub(lCrt.NotBefore()))\n\n\tsns := []string{}\n\tfor _, sn := range lCrt.UnsafeNetworks() {\n\t\tsns = append(sns, sn.String())\n\t}\n\tassert.Equal(t, []string{\"10.1.1.1/32\", \"10.2.2.2/32\", \"10.5.5.5/32\"}, sns)\n\n\tissuer, _ := ca.Fingerprint()\n\tassert.Equal(t, issuer, lCrt.Issuer())\n\n\tassert.True(t, lCrt.CheckSignature(caPub))\n\n\t// test proper cert with in-pub\n\tos.Remove(keyF.Name())\n\tos.Remove(crtF.Name())\n\tob.Reset()\n\teb.Reset()\n\targs = []string{\"-version\", \"1\", \"-ca-crt\", caCrtF.Name(), \"-ca-key\", caKeyF.Name(), \"-name\", \"test\", \"-ip\", \"1.1.1.1/24\", \"-out-crt\", crtF.Name(), \"-in-pub\", inPubF.Name(), \"-duration\", \"100m\", \"-groups\", \"1\"}\n\trequire.NoError(t, signCert(args, ob, eb, nopw))\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\n\t// read cert file and check pub key matches in-pub\n\trb, _ = os.ReadFile(crtF.Name())\n\tlCrt, b, err = cert.UnmarshalCertificateFromPEM(rb)\n\tassert.Empty(t, b)\n\trequire.NoError(t, err)\n\tassert.Equal(t, lCrt.PublicKey(), inPub)\n\n\t// test refuse to sign cert with duration beyond root\n\tob.Reset()\n\teb.Reset()\n\tos.Remove(keyF.Name())\n\tos.Remove(crtF.Name())\n\targs = []string{\"-version\", \"1\", \"-ca-crt\", caCrtF.Name(), \"-ca-key\", caKeyF.Name(), \"-name\", \"test\", \"-ip\", \"1.1.1.1/24\", \"-out-crt\", crtF.Name(), \"-out-key\", keyF.Name(), \"-duration\", \"1000m\", \"-subnets\", \"10.1.1.1/32, ,   10.2.2.2/32   ,   ,  ,, 10.5.5.5/32\", \"-groups\", \"1,,   2    ,        ,,,3,4,5\"}\n\trequire.EqualError(t, signCert(args, ob, eb, nopw), \"error while signing: certificate expires after signing certificate\")\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\n\t// create valid cert/key for overwrite tests\n\tos.Remove(keyF.Name())\n\tos.Remove(crtF.Name())\n\targs = []string{\"-version\", \"1\", \"-ca-crt\", caCrtF.Name(), \"-ca-key\", caKeyF.Name(), \"-name\", \"test\", \"-ip\", \"1.1.1.1/24\", \"-out-crt\", crtF.Name(), \"-out-key\", keyF.Name(), \"-duration\", \"100m\", \"-subnets\", \"10.1.1.1/32, ,   10.2.2.2/32   ,   ,  ,, 10.5.5.5/32\", \"-groups\", \"1,,   2    ,        ,,,3,4,5\"}\n\trequire.NoError(t, signCert(args, ob, eb, nopw))\n\n\t// test that we won't overwrite existing key file\n\tos.Remove(crtF.Name())\n\tob.Reset()\n\teb.Reset()\n\targs = []string{\"-version\", \"1\", \"-ca-crt\", caCrtF.Name(), \"-ca-key\", caKeyF.Name(), \"-name\", \"test\", \"-ip\", \"1.1.1.1/24\", \"-out-crt\", crtF.Name(), \"-out-key\", keyF.Name(), \"-duration\", \"100m\", \"-subnets\", \"10.1.1.1/32, ,   10.2.2.2/32   ,   ,  ,, 10.5.5.5/32\", \"-groups\", \"1,,   2    ,        ,,,3,4,5\"}\n\trequire.EqualError(t, signCert(args, ob, eb, nopw), \"refusing to overwrite existing key: \"+keyF.Name())\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\n\t// create valid cert/key for overwrite tests\n\tos.Remove(keyF.Name())\n\tos.Remove(crtF.Name())\n\targs = []string{\"-version\", \"1\", \"-ca-crt\", caCrtF.Name(), \"-ca-key\", caKeyF.Name(), \"-name\", \"test\", \"-ip\", \"1.1.1.1/24\", \"-out-crt\", crtF.Name(), \"-out-key\", keyF.Name(), \"-duration\", \"100m\", \"-subnets\", \"10.1.1.1/32, ,   10.2.2.2/32   ,   ,  ,, 10.5.5.5/32\", \"-groups\", \"1,,   2    ,        ,,,3,4,5\"}\n\trequire.NoError(t, signCert(args, ob, eb, nopw))\n\n\t// test that we won't overwrite existing certificate file\n\tos.Remove(keyF.Name())\n\tob.Reset()\n\teb.Reset()\n\targs = []string{\"-version\", \"1\", \"-ca-crt\", caCrtF.Name(), \"-ca-key\", caKeyF.Name(), \"-name\", \"test\", \"-ip\", \"1.1.1.1/24\", \"-out-crt\", crtF.Name(), \"-out-key\", keyF.Name(), \"-duration\", \"100m\", \"-subnets\", \"10.1.1.1/32, ,   10.2.2.2/32   ,   ,  ,, 10.5.5.5/32\", \"-groups\", \"1,,   2    ,        ,,,3,4,5\"}\n\trequire.EqualError(t, signCert(args, ob, eb, nopw), \"refusing to overwrite existing cert: \"+crtF.Name())\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\n\t// create valid cert/key using encrypted CA key\n\tos.Remove(caKeyF.Name())\n\tos.Remove(caCrtF.Name())\n\tos.Remove(keyF.Name())\n\tos.Remove(crtF.Name())\n\tob.Reset()\n\teb.Reset()\n\n\tcaKeyF, err = os.CreateTemp(\"\", \"sign-cert.key\")\n\trequire.NoError(t, err)\n\tdefer os.Remove(caKeyF.Name())\n\n\tcaCrtF, err = os.CreateTemp(\"\", \"sign-cert.crt\")\n\trequire.NoError(t, err)\n\tdefer os.Remove(caCrtF.Name())\n\n\t// generate the encrypted key\n\tcaPub, caPriv, _ = ed25519.GenerateKey(rand.Reader)\n\tkdfParams := cert.NewArgon2Parameters(64*1024, 4, 3)\n\tb, _ = cert.EncryptAndMarshalSigningPrivateKey(cert.Curve_CURVE25519, caPriv, passphrase, kdfParams)\n\tcaKeyF.Write(b)\n\n\tca, _ = NewTestCaCert(\"ca\", caPub, caPriv, time.Now(), time.Now().Add(time.Minute*200), nil, nil, nil)\n\tb, _ = ca.MarshalPEM()\n\tcaCrtF.Write(b)\n\n\t// test with the proper password\n\targs = []string{\"-version\", \"1\", \"-ca-crt\", caCrtF.Name(), \"-ca-key\", caKeyF.Name(), \"-name\", \"test\", \"-ip\", \"1.1.1.1/24\", \"-out-crt\", crtF.Name(), \"-out-key\", keyF.Name(), \"-duration\", \"100m\", \"-subnets\", \"10.1.1.1/32, ,   10.2.2.2/32   ,   ,  ,, 10.5.5.5/32\", \"-groups\", \"1,,   2    ,        ,,,3,4,5\"}\n\trequire.NoError(t, signCert(args, ob, eb, testpw))\n\tassert.Equal(t, \"Enter passphrase: \", ob.String())\n\tassert.Empty(t, eb.String())\n\n\t// test with the proper password in the environment\n\tos.Remove(crtF.Name())\n\tos.Remove(keyF.Name())\n\targs = []string{\"-version\", \"1\", \"-ca-crt\", caCrtF.Name(), \"-ca-key\", caKeyF.Name(), \"-name\", \"test\", \"-ip\", \"1.1.1.1/24\", \"-out-crt\", crtF.Name(), \"-out-key\", keyF.Name(), \"-duration\", \"100m\", \"-subnets\", \"10.1.1.1/32, ,   10.2.2.2/32   ,   ,  ,, 10.5.5.5/32\", \"-groups\", \"1,,   2    ,        ,,,3,4,5\"}\n\tos.Setenv(\"NEBULA_CA_PASSPHRASE\", string(passphrase))\n\trequire.NoError(t, signCert(args, ob, eb, testpw))\n\tassert.Empty(t, eb.String())\n\tos.Setenv(\"NEBULA_CA_PASSPHRASE\", \"\")\n\n\t// test with the wrong password\n\tob.Reset()\n\teb.Reset()\n\n\ttestpw.password = []byte(\"invalid password\")\n\targs = []string{\"-version\", \"1\", \"-ca-crt\", caCrtF.Name(), \"-ca-key\", caKeyF.Name(), \"-name\", \"test\", \"-ip\", \"1.1.1.1/24\", \"-out-crt\", crtF.Name(), \"-out-key\", keyF.Name(), \"-duration\", \"100m\", \"-subnets\", \"10.1.1.1/32, ,   10.2.2.2/32   ,   ,  ,, 10.5.5.5/32\", \"-groups\", \"1,,   2    ,        ,,,3,4,5\"}\n\trequire.Error(t, signCert(args, ob, eb, testpw))\n\tassert.Equal(t, \"Enter passphrase: \", ob.String())\n\tassert.Empty(t, eb.String())\n\n\t// test with the wrong password in environment\n\tob.Reset()\n\teb.Reset()\n\n\tos.Setenv(\"NEBULA_CA_PASSPHRASE\", \"invalid password\")\n\targs = []string{\"-version\", \"1\", \"-ca-crt\", caCrtF.Name(), \"-ca-key\", caKeyF.Name(), \"-name\", \"test\", \"-ip\", \"1.1.1.1/24\", \"-out-crt\", crtF.Name(), \"-out-key\", keyF.Name(), \"-duration\", \"100m\", \"-subnets\", \"10.1.1.1/32, ,   10.2.2.2/32   ,   ,  ,, 10.5.5.5/32\", \"-groups\", \"1,,   2    ,        ,,,3,4,5\"}\n\trequire.EqualError(t, signCert(args, ob, eb, nopw), \"error while parsing encrypted ca-key: invalid passphrase or corrupt private key\")\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\tos.Setenv(\"NEBULA_CA_PASSPHRASE\", \"\")\n\n\t// test with the user not entering a password\n\tob.Reset()\n\teb.Reset()\n\n\targs = []string{\"-version\", \"1\", \"-ca-crt\", caCrtF.Name(), \"-ca-key\", caKeyF.Name(), \"-name\", \"test\", \"-ip\", \"1.1.1.1/24\", \"-out-crt\", crtF.Name(), \"-out-key\", keyF.Name(), \"-duration\", \"100m\", \"-subnets\", \"10.1.1.1/32, ,   10.2.2.2/32   ,   ,  ,, 10.5.5.5/32\", \"-groups\", \"1,,   2    ,        ,,,3,4,5\"}\n\trequire.Error(t, signCert(args, ob, eb, nopw))\n\t// normally the user hitting enter on the prompt would add newlines between these\n\tassert.Equal(t, \"Enter passphrase: Enter passphrase: Enter passphrase: Enter passphrase: Enter passphrase: \", ob.String())\n\tassert.Empty(t, eb.String())\n\n\t// test an error condition\n\tob.Reset()\n\teb.Reset()\n\n\targs = []string{\"-version\", \"1\", \"-ca-crt\", caCrtF.Name(), \"-ca-key\", caKeyF.Name(), \"-name\", \"test\", \"-ip\", \"1.1.1.1/24\", \"-out-crt\", crtF.Name(), \"-out-key\", keyF.Name(), \"-duration\", \"100m\", \"-subnets\", \"10.1.1.1/32, ,   10.2.2.2/32   ,   ,  ,, 10.5.5.5/32\", \"-groups\", \"1,,   2    ,        ,,,3,4,5\"}\n\trequire.Error(t, signCert(args, ob, eb, errpw))\n\tassert.Equal(t, \"Enter passphrase: \", ob.String())\n\tassert.Empty(t, eb.String())\n}\n"
  },
  {
    "path": "cmd/nebula-cert/test_darwin.go",
    "content": "package main\n\nconst NoSuchFileError = \"no such file or directory\"\nconst NoSuchDirError = \"no such file or directory\"\n"
  },
  {
    "path": "cmd/nebula-cert/test_linux.go",
    "content": "package main\n\nconst NoSuchFileError = \"no such file or directory\"\nconst NoSuchDirError = \"no such file or directory\"\n"
  },
  {
    "path": "cmd/nebula-cert/test_windows.go",
    "content": "package main\n\nconst NoSuchFileError = \"The system cannot find the file specified.\"\nconst NoSuchDirError = \"The system cannot find the path specified.\"\n"
  },
  {
    "path": "cmd/nebula-cert/verify.go",
    "content": "package main\n\nimport (\n\t\"errors\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/slackhq/nebula/cert\"\n)\n\ntype verifyFlags struct {\n\tset      *flag.FlagSet\n\tcaPath   *string\n\tcertPath *string\n}\n\nfunc newVerifyFlags() *verifyFlags {\n\tvf := verifyFlags{set: flag.NewFlagSet(\"verify\", flag.ContinueOnError)}\n\tvf.set.Usage = func() {}\n\tvf.caPath = vf.set.String(\"ca\", \"\", \"Required: path to a file containing one or more ca certificates\")\n\tvf.certPath = vf.set.String(\"crt\", \"\", \"Required: path to a file containing a single certificate\")\n\treturn &vf\n}\n\nfunc verify(args []string, out io.Writer, errOut io.Writer) error {\n\tvf := newVerifyFlags()\n\terr := vf.set.Parse(args)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif err := mustFlagString(\"ca\", vf.caPath); err != nil {\n\t\treturn err\n\t}\n\tif err := mustFlagString(\"crt\", vf.certPath); err != nil {\n\t\treturn err\n\t}\n\n\trawCACert, err := os.ReadFile(*vf.caPath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error while reading ca: %w\", err)\n\t}\n\n\tcaPool := cert.NewCAPool()\n\tfor {\n\t\trawCACert, err = caPool.AddCAFromPEM(rawCACert)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error while adding ca cert to pool: %w\", err)\n\t\t}\n\n\t\tif rawCACert == nil || len(rawCACert) == 0 || strings.TrimSpace(string(rawCACert)) == \"\" {\n\t\t\tbreak\n\t\t}\n\t}\n\n\trawCert, err := os.ReadFile(*vf.certPath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to read crt: %w\", err)\n\t}\n\tvar errs []error\n\tfor {\n\t\tif len(rawCert) == 0 {\n\t\t\tbreak\n\t\t}\n\t\tc, extra, err := cert.UnmarshalCertificateFromPEM(rawCert)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error while parsing crt: %w\", err)\n\t\t}\n\t\trawCert = extra\n\t\t_, err = caPool.VerifyCertificate(time.Now(), c)\n\t\tif err != nil {\n\t\t\tswitch {\n\t\t\tcase errors.Is(err, cert.ErrCaNotFound):\n\t\t\t\terrs = append(errs, fmt.Errorf(\"error while verifying certificate v%d %s with issuer %s: %w\", c.Version(), c.Name(), c.Issuer(), err))\n\t\t\tdefault:\n\t\t\t\terrs = append(errs, fmt.Errorf(\"error while verifying certificate %+v: %w\", c, err))\n\t\t\t}\n\t\t}\n\t}\n\n\treturn errors.Join(errs...)\n}\n\nfunc verifySummary() string {\n\treturn \"verify <flags>: verifies a certificate isn't expired and was signed by a trusted authority.\"\n}\n\nfunc verifyHelp(out io.Writer) {\n\tvf := newVerifyFlags()\n\t_, _ = out.Write([]byte(\"Usage of \" + os.Args[0] + \" \" + verifySummary() + \"\\n\"))\n\tvf.set.SetOutput(out)\n\tvf.set.PrintDefaults()\n}\n"
  },
  {
    "path": "cmd/nebula-cert/verify_test.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"crypto/rand\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/slackhq/nebula/cert\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"golang.org/x/crypto/ed25519\"\n)\n\nfunc Test_verifySummary(t *testing.T) {\n\tassert.Equal(t, \"verify <flags>: verifies a certificate isn't expired and was signed by a trusted authority.\", verifySummary())\n}\n\nfunc Test_verifyHelp(t *testing.T) {\n\tob := &bytes.Buffer{}\n\tverifyHelp(ob)\n\tassert.Equal(\n\t\tt,\n\t\t\"Usage of \"+os.Args[0]+\" verify <flags>: verifies a certificate isn't expired and was signed by a trusted authority.\\n\"+\n\t\t\t\"  -ca string\\n\"+\n\t\t\t\"    \\tRequired: path to a file containing one or more ca certificates\\n\"+\n\t\t\t\"  -crt string\\n\"+\n\t\t\t\"    \\tRequired: path to a file containing a single certificate\\n\",\n\t\tob.String(),\n\t)\n}\n\nfunc Test_verify(t *testing.T) {\n\ttime.Local = time.UTC\n\tob := &bytes.Buffer{}\n\teb := &bytes.Buffer{}\n\n\t// required args\n\tassertHelpError(t, verify([]string{\"-ca\", \"derp\"}, ob, eb), \"-crt is required\")\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\n\tassertHelpError(t, verify([]string{\"-crt\", \"derp\"}, ob, eb), \"-ca is required\")\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\n\t// no ca at path\n\tob.Reset()\n\teb.Reset()\n\terr := verify([]string{\"-ca\", \"does_not_exist\", \"-crt\", \"does_not_exist\"}, ob, eb)\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\trequire.EqualError(t, err, \"error while reading ca: open does_not_exist: \"+NoSuchFileError)\n\n\t// invalid ca at path\n\tob.Reset()\n\teb.Reset()\n\tcaFile, err := os.CreateTemp(\"\", \"verify-ca\")\n\trequire.NoError(t, err)\n\tdefer os.Remove(caFile.Name())\n\n\tcaFile.WriteString(\"-----BEGIN NOPE-----\")\n\terr = verify([]string{\"-ca\", caFile.Name(), \"-crt\", \"does_not_exist\"}, ob, eb)\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\trequire.EqualError(t, err, \"error while adding ca cert to pool: input did not contain a valid PEM encoded block\")\n\n\t// make a ca for later\n\tcaPub, caPriv, _ := ed25519.GenerateKey(rand.Reader)\n\tca, _ := NewTestCaCert(\"test-ca\", caPub, caPriv, time.Now().Add(time.Hour*-1), time.Now().Add(time.Hour*2), nil, nil, nil)\n\tb, _ := ca.MarshalPEM()\n\tcaFile.Truncate(0)\n\tcaFile.Seek(0, 0)\n\tcaFile.Write(b)\n\n\t// no crt at path\n\terr = verify([]string{\"-ca\", caFile.Name(), \"-crt\", \"does_not_exist\"}, ob, eb)\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\trequire.EqualError(t, err, \"unable to read crt: open does_not_exist: \"+NoSuchFileError)\n\n\t// invalid crt at path\n\tob.Reset()\n\teb.Reset()\n\tcertFile, err := os.CreateTemp(\"\", \"verify-cert\")\n\trequire.NoError(t, err)\n\tdefer os.Remove(certFile.Name())\n\n\tcertFile.WriteString(\"-----BEGIN NOPE-----\")\n\terr = verify([]string{\"-ca\", caFile.Name(), \"-crt\", certFile.Name()}, ob, eb)\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\trequire.EqualError(t, err, \"error while parsing crt: input did not contain a valid PEM encoded block\")\n\n\t// unverifiable cert at path\n\tcrt, _ := NewTestCert(ca, caPriv, \"test-cert\", time.Now().Add(time.Hour*-1), time.Now().Add(time.Hour), nil, nil, nil)\n\t// Slightly evil hack to modify the certificate after it was sealed to generate an invalid signature\n\tpub := crt.PublicKey()\n\tfor i, _ := range pub {\n\t\tpub[i] = 0\n\t}\n\tb, _ = crt.MarshalPEM()\n\tcertFile.Truncate(0)\n\tcertFile.Seek(0, 0)\n\tcertFile.Write(b)\n\n\terr = verify([]string{\"-ca\", caFile.Name(), \"-crt\", certFile.Name()}, ob, eb)\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\trequire.ErrorIs(t, err, cert.ErrSignatureMismatch)\n\n\t// verified cert at path\n\tcrt, _ = NewTestCert(ca, caPriv, \"test-cert\", time.Now().Add(time.Hour*-1), time.Now().Add(time.Hour), nil, nil, nil)\n\tb, _ = crt.MarshalPEM()\n\tcertFile.Truncate(0)\n\tcertFile.Seek(0, 0)\n\tcertFile.Write(b)\n\n\terr = verify([]string{\"-ca\", caFile.Name(), \"-crt\", certFile.Name()}, ob, eb)\n\tassert.Empty(t, ob.String())\n\tassert.Empty(t, eb.String())\n\trequire.NoError(t, err)\n}\n"
  },
  {
    "path": "cmd/nebula-service/logs_generic.go",
    "content": "//go:build !windows\n// +build !windows\n\npackage main\n\nimport \"github.com/sirupsen/logrus\"\n\nfunc HookLogger(l *logrus.Logger) {\n\t// Do nothing, let the logs flow to stdout/stderr\n}\n"
  },
  {
    "path": "cmd/nebula-service/logs_windows.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"os\"\n\n\t\"github.com/kardianos/service\"\n\t\"github.com/sirupsen/logrus\"\n)\n\n// HookLogger routes the logrus logs through the service logger so that they end up in the Windows Event Viewer\n// logrus output will be discarded\nfunc HookLogger(l *logrus.Logger) {\n\tl.AddHook(newLogHook(logger))\n\tl.SetOutput(ioutil.Discard)\n}\n\ntype logHook struct {\n\tsl service.Logger\n}\n\nfunc newLogHook(sl service.Logger) *logHook {\n\treturn &logHook{sl: sl}\n}\n\nfunc (h *logHook) Fire(entry *logrus.Entry) error {\n\tline, err := entry.String()\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"Unable to read entry, %v\", err)\n\t\treturn err\n\t}\n\n\tswitch entry.Level {\n\tcase logrus.PanicLevel:\n\t\treturn h.sl.Error(line)\n\tcase logrus.FatalLevel:\n\t\treturn h.sl.Error(line)\n\tcase logrus.ErrorLevel:\n\t\treturn h.sl.Error(line)\n\tcase logrus.WarnLevel:\n\t\treturn h.sl.Warning(line)\n\tcase logrus.InfoLevel:\n\t\treturn h.sl.Info(line)\n\tcase logrus.DebugLevel:\n\t\treturn h.sl.Info(line)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc (h *logHook) Levels() []logrus.Level {\n\treturn logrus.AllLevels\n}\n"
  },
  {
    "path": "cmd/nebula-service/main.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\t\"runtime/debug\"\n\t\"strings\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula\"\n\t\"github.com/slackhq/nebula/config\"\n\t\"github.com/slackhq/nebula/util\"\n)\n\n// A version string that can be set with\n//\n//\t-ldflags \"-X main.Build=SOMEVERSION\"\n//\n// at compile-time.\nvar Build string\n\nfunc init() {\n\tif Build == \"\" {\n\t\tinfo, ok := debug.ReadBuildInfo()\n\t\tif !ok {\n\t\t\treturn\n\t\t}\n\n\t\tBuild = strings.TrimPrefix(info.Main.Version, \"v\")\n\t}\n}\n\nfunc main() {\n\tserviceFlag := flag.String(\"service\", \"\", \"Control the system service.\")\n\tconfigPath := flag.String(\"config\", \"\", \"Path to either a file or directory to load configuration from\")\n\tconfigTest := flag.Bool(\"test\", false, \"Test the config and print the end result. Non zero exit indicates a faulty config\")\n\tprintVersion := flag.Bool(\"version\", false, \"Print version\")\n\tprintUsage := flag.Bool(\"help\", false, \"Print command line usage\")\n\n\tflag.Parse()\n\n\tif *printVersion {\n\t\tfmt.Printf(\"Version: %s\\n\", Build)\n\t\tos.Exit(0)\n\t}\n\n\tif *printUsage {\n\t\tflag.Usage()\n\t\tos.Exit(0)\n\t}\n\n\tif *serviceFlag != \"\" {\n\t\tdoService(configPath, configTest, Build, serviceFlag)\n\t\tos.Exit(1)\n\t}\n\n\tif *configPath == \"\" {\n\t\tfmt.Println(\"-config flag must be set\")\n\t\tflag.Usage()\n\t\tos.Exit(1)\n\t}\n\n\tl := logrus.New()\n\tl.Out = os.Stdout\n\n\tc := config.NewC(l)\n\terr := c.Load(*configPath)\n\tif err != nil {\n\t\tfmt.Printf(\"failed to load config: %s\", err)\n\t\tos.Exit(1)\n\t}\n\n\tctrl, err := nebula.Main(c, *configTest, Build, l, nil)\n\tif err != nil {\n\t\tutil.LogWithContextIfNeeded(\"Failed to start\", err, l)\n\t\tos.Exit(1)\n\t}\n\n\tif !*configTest {\n\t\tctrl.Start()\n\t\tctrl.ShutdownBlock()\n\t}\n\n\tos.Exit(0)\n}\n"
  },
  {
    "path": "cmd/nebula-service/service.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/kardianos/service\"\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula\"\n\t\"github.com/slackhq/nebula/config\"\n)\n\nvar logger service.Logger\n\ntype program struct {\n\tconfigPath *string\n\tconfigTest *bool\n\tbuild      string\n\tcontrol    *nebula.Control\n}\n\nfunc (p *program) Start(s service.Service) error {\n\t// Start should not block.\n\tlogger.Info(\"Nebula service starting.\")\n\n\tl := logrus.New()\n\tHookLogger(l)\n\n\tc := config.NewC(l)\n\terr := c.Load(*p.configPath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to load config: %s\", err)\n\t}\n\n\tp.control, err = nebula.Main(c, *p.configTest, Build, l, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tp.control.Start()\n\treturn nil\n}\n\nfunc (p *program) Stop(s service.Service) error {\n\tlogger.Info(\"Nebula service stopping.\")\n\tp.control.Stop()\n\treturn nil\n}\n\nfunc fileExists(filename string) bool {\n\t_, err := os.Stat(filename)\n\tif os.IsNotExist(err) {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc doService(configPath *string, configTest *bool, build string, serviceFlag *string) {\n\tif *configPath == \"\" {\n\t\tex, err := os.Executable()\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\t*configPath = filepath.Dir(ex) + \"/config.yaml\"\n\t\tif !fileExists(*configPath) {\n\t\t\t*configPath = filepath.Dir(ex) + \"/config.yml\"\n\t\t}\n\t}\n\n\tsvcConfig := &service.Config{\n\t\tName:        \"Nebula\",\n\t\tDisplayName: \"Nebula Network Service\",\n\t\tDescription: \"Nebula network connectivity daemon for encrypted communications\",\n\t\tArguments:   []string{\"-service\", \"run\", \"-config\", *configPath},\n\t}\n\n\tprg := &program{\n\t\tconfigPath: configPath,\n\t\tconfigTest: configTest,\n\t\tbuild:      build,\n\t}\n\n\t// Here are what the different loggers are doing:\n\t// - `log` is the standard go log utility, meant to be used while the process is still attached to stdout/stderr\n\t// - `logger` is the service log utility that may be attached to a special place depending on OS (Windows will have it attached to the event log)\n\t// - above, in `Run` we create a `logrus.Logger` which is what nebula expects to use\n\ts, err := service.New(prg, svcConfig)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\terrs := make(chan error, 5)\n\tlogger, err = s.Logger(errs)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tgo func() {\n\t\tfor {\n\t\t\terr := <-errs\n\t\t\tif err != nil {\n\t\t\t\t// Route any errors from the system logger to stdout as a best effort to notice issues there\n\t\t\t\tlog.Print(err)\n\t\t\t}\n\t\t}\n\t}()\n\n\tswitch *serviceFlag {\n\tcase \"run\":\n\t\terr = s.Run()\n\t\tif err != nil {\n\t\t\t// Route any errors to the system logger\n\t\t\tlogger.Error(err)\n\t\t}\n\tdefault:\n\t\terr := service.Control(s, *serviceFlag)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Valid actions: %q\\n\", service.ControlAction)\n\t\t\tlog.Fatal(err)\n\t\t}\n\t\treturn\n\t}\n\n}\n"
  },
  {
    "path": "config/config.go",
    "content": "package config\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"dario.cat/mergo\"\n\t\"github.com/sirupsen/logrus\"\n\t\"go.yaml.in/yaml/v3\"\n)\n\ntype C struct {\n\tpath        string\n\tfiles       []string\n\tSettings    map[string]any\n\toldSettings map[string]any\n\tcallbacks   []func(*C)\n\tl           *logrus.Logger\n\treloadLock  sync.Mutex\n}\n\nfunc NewC(l *logrus.Logger) *C {\n\treturn &C{\n\t\tSettings: make(map[string]any),\n\t\tl:        l,\n\t}\n}\n\n// Load will find all yaml files within path and load them in lexical order\nfunc (c *C) Load(path string) error {\n\tc.path = path\n\tc.files = make([]string, 0)\n\n\terr := c.resolve(path, true)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif len(c.files) == 0 {\n\t\treturn fmt.Errorf(\"no config files found at %s\", path)\n\t}\n\n\tsort.Strings(c.files)\n\n\terr = c.parse()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (c *C) LoadString(raw string) error {\n\tif raw == \"\" {\n\t\treturn errors.New(\"Empty configuration\")\n\t}\n\treturn c.parseRaw([]byte(raw))\n}\n\n// RegisterReloadCallback stores a function to be called when a config reload is triggered. The functions registered\n// here should decide if they need to make a change to the current process before making the change. HasChanged can be\n// used to help decide if a change is necessary.\n// These functions should return quickly or spawn their own go routine if they will take a while\nfunc (c *C) RegisterReloadCallback(f func(*C)) {\n\tc.callbacks = append(c.callbacks, f)\n}\n\n// InitialLoad returns true if this is the first load of the config, and ReloadConfig has not been called yet.\nfunc (c *C) InitialLoad() bool {\n\treturn c.oldSettings == nil\n}\n\n// HasChanged checks if the underlying structure of the provided key has changed after a config reload. The value of\n// k in both the old and new settings will be serialized, the result of the string comparison is returned.\n// If k is an empty string the entire config is tested.\n// It's important to note that this is very rudimentary and susceptible to configuration ordering issues indicating\n// there is change when there actually wasn't any.\nfunc (c *C) HasChanged(k string) bool {\n\tif c.oldSettings == nil {\n\t\treturn false\n\t}\n\n\tvar (\n\t\tnv any\n\t\tov any\n\t)\n\n\tif k == \"\" {\n\t\tnv = c.Settings\n\t\tov = c.oldSettings\n\t\tk = \"all settings\"\n\t} else {\n\t\tnv = c.get(k, c.Settings)\n\t\tov = c.get(k, c.oldSettings)\n\t}\n\n\tnewVals, err := yaml.Marshal(nv)\n\tif err != nil {\n\t\tc.l.WithField(\"config_path\", k).WithError(err).Error(\"Error while marshaling new config\")\n\t}\n\n\toldVals, err := yaml.Marshal(ov)\n\tif err != nil {\n\t\tc.l.WithField(\"config_path\", k).WithError(err).Error(\"Error while marshaling old config\")\n\t}\n\n\treturn string(newVals) != string(oldVals)\n}\n\n// CatchHUP will listen for the HUP signal in a go routine and reload all configs found in the\n// original path provided to Load. The old settings are shallow copied for change detection after the reload.\nfunc (c *C) CatchHUP(ctx context.Context) {\n\tif c.path == \"\" {\n\t\treturn\n\t}\n\n\tch := make(chan os.Signal, 1)\n\tsignal.Notify(ch, syscall.SIGHUP)\n\n\tgo func() {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\tsignal.Stop(ch)\n\t\t\t\tclose(ch)\n\t\t\t\treturn\n\t\t\tcase <-ch:\n\t\t\t\tc.l.Info(\"Caught HUP, reloading config\")\n\t\t\t\tc.ReloadConfig()\n\t\t\t}\n\t\t}\n\t}()\n}\n\nfunc (c *C) ReloadConfig() {\n\tc.reloadLock.Lock()\n\tdefer c.reloadLock.Unlock()\n\n\tc.oldSettings = make(map[string]any)\n\tfor k, v := range c.Settings {\n\t\tc.oldSettings[k] = v\n\t}\n\n\terr := c.Load(c.path)\n\tif err != nil {\n\t\tc.l.WithField(\"config_path\", c.path).WithError(err).Error(\"Error occurred while reloading config\")\n\t\treturn\n\t}\n\n\tfor _, v := range c.callbacks {\n\t\tv(c)\n\t}\n}\n\nfunc (c *C) ReloadConfigString(raw string) error {\n\tc.reloadLock.Lock()\n\tdefer c.reloadLock.Unlock()\n\n\tc.oldSettings = make(map[string]any)\n\tfor k, v := range c.Settings {\n\t\tc.oldSettings[k] = v\n\t}\n\n\terr := c.LoadString(raw)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, v := range c.callbacks {\n\t\tv(c)\n\t}\n\n\treturn nil\n}\n\n// GetString will get the string for k or return the default d if not found or invalid\nfunc (c *C) GetString(k, d string) string {\n\tr := c.Get(k)\n\tif r == nil {\n\t\treturn d\n\t}\n\n\treturn fmt.Sprintf(\"%v\", r)\n}\n\n// GetStringSlice will get the slice of strings for k or return the default d if not found or invalid\nfunc (c *C) GetStringSlice(k string, d []string) []string {\n\tr := c.Get(k)\n\tif r == nil {\n\t\treturn d\n\t}\n\n\trv, ok := r.([]any)\n\tif !ok {\n\t\treturn d\n\t}\n\n\tv := make([]string, len(rv))\n\tfor i := 0; i < len(v); i++ {\n\t\tv[i] = fmt.Sprintf(\"%v\", rv[i])\n\t}\n\n\treturn v\n}\n\n// GetMap will get the map for k or return the default d if not found or invalid\nfunc (c *C) GetMap(k string, d map[string]any) map[string]any {\n\tr := c.Get(k)\n\tif r == nil {\n\t\treturn d\n\t}\n\n\tv, ok := r.(map[string]any)\n\tif !ok {\n\t\treturn d\n\t}\n\n\treturn v\n}\n\n// GetInt will get the int for k or return the default d if not found or invalid\nfunc (c *C) GetInt(k string, d int) int {\n\tr := c.GetString(k, strconv.Itoa(d))\n\tv, err := strconv.Atoi(r)\n\tif err != nil {\n\t\treturn d\n\t}\n\n\treturn v\n}\n\n// GetUint32 will get the uint32 for k or return the default d if not found or invalid\nfunc (c *C) GetUint32(k string, d uint32) uint32 {\n\tr := c.GetInt(k, int(d))\n\tif r < 0 || uint64(r) > uint64(math.MaxUint32) {\n\t\treturn d\n\t}\n\treturn uint32(r)\n}\n\n// GetBool will get the bool for k or return the default d if not found or invalid\nfunc (c *C) GetBool(k string, d bool) bool {\n\tr := strings.ToLower(c.GetString(k, fmt.Sprintf(\"%v\", d)))\n\tv, err := strconv.ParseBool(r)\n\tif err != nil {\n\t\tswitch r {\n\t\tcase \"y\", \"yes\":\n\t\t\treturn true\n\t\tcase \"n\", \"no\":\n\t\t\treturn false\n\t\t}\n\t\treturn d\n\t}\n\n\treturn v\n}\n\nfunc AsBool(v any) (value bool, ok bool) {\n\tswitch x := v.(type) {\n\tcase bool:\n\t\treturn x, true\n\tcase string:\n\t\tswitch x {\n\t\tcase \"y\", \"yes\":\n\t\t\treturn true, true\n\t\tcase \"n\", \"no\":\n\t\t\treturn false, true\n\t\t}\n\t}\n\n\treturn false, false\n}\n\n// GetDuration will get the duration for k or return the default d if not found or invalid\nfunc (c *C) GetDuration(k string, d time.Duration) time.Duration {\n\tr := c.GetString(k, \"\")\n\tv, err := time.ParseDuration(r)\n\tif err != nil {\n\t\treturn d\n\t}\n\treturn v\n}\n\nfunc (c *C) Get(k string) any {\n\treturn c.get(k, c.Settings)\n}\n\nfunc (c *C) IsSet(k string) bool {\n\treturn c.get(k, c.Settings) != nil\n}\n\nfunc (c *C) get(k string, v any) any {\n\tparts := strings.Split(k, \".\")\n\tfor _, p := range parts {\n\t\tm, ok := v.(map[string]any)\n\t\tif !ok {\n\t\t\treturn nil\n\t\t}\n\n\t\tv, ok = m[p]\n\t\tif !ok {\n\t\t\treturn nil\n\t\t}\n\t}\n\n\treturn v\n}\n\n// direct signifies if this is the config path directly specified by the user,\n// versus a file/dir found by recursing into that path\nfunc (c *C) resolve(path string, direct bool) error {\n\ti, err := os.Stat(path)\n\tif err != nil {\n\t\treturn nil\n\t}\n\n\tif !i.IsDir() {\n\t\tc.addFile(path, direct)\n\t\treturn nil\n\t}\n\n\tpaths, err := readDirNames(path)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"problem while reading directory %s: %s\", path, err)\n\t}\n\n\tfor _, p := range paths {\n\t\terr := c.resolve(filepath.Join(path, p), false)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (c *C) addFile(path string, direct bool) error {\n\text := filepath.Ext(path)\n\n\tif !direct && ext != \".yaml\" && ext != \".yml\" {\n\t\treturn nil\n\t}\n\n\tap, err := filepath.Abs(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tc.files = append(c.files, ap)\n\treturn nil\n}\n\nfunc (c *C) parseRaw(b []byte) error {\n\tvar m map[string]any\n\n\terr := yaml.Unmarshal(b, &m)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tc.Settings = m\n\treturn nil\n}\n\nfunc (c *C) parse() error {\n\tvar m map[string]any\n\n\tfor _, path := range c.files {\n\t\tb, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tvar nm map[string]any\n\t\terr = yaml.Unmarshal(b, &nm)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// We need to use WithAppendSlice so that firewall rules in separate\n\t\t// files are appended together\n\t\terr = mergo.Merge(&nm, m, mergo.WithAppendSlice)\n\t\tm = nm\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tc.Settings = m\n\treturn nil\n}\n\nfunc readDirNames(path string) ([]string, error) {\n\tf, err := os.Open(path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpaths, err := f.Readdirnames(-1)\n\tf.Close()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsort.Strings(paths)\n\treturn paths, nil\n}\n"
  },
  {
    "path": "config/config_test.go",
    "content": "package config\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t\"dario.cat/mergo\"\n\t\"github.com/slackhq/nebula/test\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.yaml.in/yaml/v3\"\n)\n\nfunc TestConfig_Load(t *testing.T) {\n\tl := test.NewLogger()\n\tdir, err := os.MkdirTemp(\"\", \"config-test\")\n\t// invalid yaml\n\tc := NewC(l)\n\tos.WriteFile(filepath.Join(dir, \"01.yaml\"), []byte(\" invalid yaml\"), 0644)\n\trequire.EqualError(t, c.Load(dir), \"yaml: unmarshal errors:\\n  line 1: cannot unmarshal !!str `invalid...` into map[string]interface {}\")\n\n\t// simple multi config merge\n\tc = NewC(l)\n\tos.RemoveAll(dir)\n\tos.Mkdir(dir, 0755)\n\n\trequire.NoError(t, err)\n\n\tos.WriteFile(filepath.Join(dir, \"01.yaml\"), []byte(\"outer:\\n  inner: hi\"), 0644)\n\tos.WriteFile(filepath.Join(dir, \"02.yml\"), []byte(\"outer:\\n  inner: override\\nnew: hi\"), 0644)\n\trequire.NoError(t, c.Load(dir))\n\texpected := map[string]any{\n\t\t\"outer\": map[string]any{\n\t\t\t\"inner\": \"override\",\n\t\t},\n\t\t\"new\": \"hi\",\n\t}\n\tassert.Equal(t, expected, c.Settings)\n}\n\nfunc TestConfig_Get(t *testing.T) {\n\tl := test.NewLogger()\n\t// test simple type\n\tc := NewC(l)\n\tc.Settings[\"firewall\"] = map[string]any{\"outbound\": \"hi\"}\n\tassert.Equal(t, \"hi\", c.Get(\"firewall.outbound\"))\n\n\t// test complex type\n\tinner := []map[string]any{{\"port\": \"1\", \"code\": \"2\"}}\n\tc.Settings[\"firewall\"] = map[string]any{\"outbound\": inner}\n\tassert.EqualValues(t, inner, c.Get(\"firewall.outbound\"))\n\n\t// test missing\n\tassert.Nil(t, c.Get(\"firewall.nope\"))\n}\n\nfunc TestConfig_GetStringSlice(t *testing.T) {\n\tl := test.NewLogger()\n\tc := NewC(l)\n\tc.Settings[\"slice\"] = []any{\"one\", \"two\"}\n\tassert.Equal(t, []string{\"one\", \"two\"}, c.GetStringSlice(\"slice\", []string{}))\n}\n\nfunc TestConfig_GetBool(t *testing.T) {\n\tl := test.NewLogger()\n\tc := NewC(l)\n\tc.Settings[\"bool\"] = true\n\tassert.True(t, c.GetBool(\"bool\", false))\n\n\tc.Settings[\"bool\"] = \"true\"\n\tassert.True(t, c.GetBool(\"bool\", false))\n\n\tc.Settings[\"bool\"] = false\n\tassert.False(t, c.GetBool(\"bool\", true))\n\n\tc.Settings[\"bool\"] = \"false\"\n\tassert.False(t, c.GetBool(\"bool\", true))\n\n\tc.Settings[\"bool\"] = \"Y\"\n\tassert.True(t, c.GetBool(\"bool\", false))\n\n\tc.Settings[\"bool\"] = \"yEs\"\n\tassert.True(t, c.GetBool(\"bool\", false))\n\n\tc.Settings[\"bool\"] = \"N\"\n\tassert.False(t, c.GetBool(\"bool\", true))\n\n\tc.Settings[\"bool\"] = \"nO\"\n\tassert.False(t, c.GetBool(\"bool\", true))\n}\n\nfunc TestConfig_HasChanged(t *testing.T) {\n\tl := test.NewLogger()\n\t// No reload has occurred, return false\n\tc := NewC(l)\n\tc.Settings[\"test\"] = \"hi\"\n\tassert.False(t, c.HasChanged(\"\"))\n\n\t// Test key change\n\tc = NewC(l)\n\tc.Settings[\"test\"] = \"hi\"\n\tc.oldSettings = map[string]any{\"test\": \"no\"}\n\tassert.True(t, c.HasChanged(\"test\"))\n\tassert.True(t, c.HasChanged(\"\"))\n\n\t// No key change\n\tc = NewC(l)\n\tc.Settings[\"test\"] = \"hi\"\n\tc.oldSettings = map[string]any{\"test\": \"hi\"}\n\tassert.False(t, c.HasChanged(\"test\"))\n\tassert.False(t, c.HasChanged(\"\"))\n}\n\nfunc TestConfig_ReloadConfig(t *testing.T) {\n\tl := test.NewLogger()\n\tdone := make(chan bool, 1)\n\tdir, err := os.MkdirTemp(\"\", \"config-test\")\n\trequire.NoError(t, err)\n\tos.WriteFile(filepath.Join(dir, \"01.yaml\"), []byte(\"outer:\\n  inner: hi\"), 0644)\n\n\tc := NewC(l)\n\trequire.NoError(t, c.Load(dir))\n\n\tassert.False(t, c.HasChanged(\"outer.inner\"))\n\tassert.False(t, c.HasChanged(\"outer\"))\n\tassert.False(t, c.HasChanged(\"\"))\n\n\tos.WriteFile(filepath.Join(dir, \"01.yaml\"), []byte(\"outer:\\n  inner: ho\"), 0644)\n\n\tc.RegisterReloadCallback(func(c *C) {\n\t\tdone <- true\n\t})\n\n\tc.ReloadConfig()\n\tassert.True(t, c.HasChanged(\"outer.inner\"))\n\tassert.True(t, c.HasChanged(\"outer\"))\n\tassert.True(t, c.HasChanged(\"\"))\n\n\t// Make sure we call the callbacks\n\tselect {\n\tcase <-done:\n\tcase <-time.After(1 * time.Second):\n\t\tpanic(\"timeout\")\n\t}\n\n}\n\n// Ensure mergo merges are done the way we expect.\n// This is needed to test for potential regressions, like:\n// - https://github.com/imdario/mergo/issues/187\nfunc TestConfig_MergoMerge(t *testing.T) {\n\tconfigs := [][]byte{\n\t\t[]byte(`\nlisten:\n  port: 1234\n`),\n\t\t[]byte(`\nfirewall:\n  inbound:\n    - port: 443\n      proto: tcp\n      groups:\n        - server\n    - port: 443\n      proto: tcp\n      groups:\n        - webapp\n`),\n\t\t[]byte(`\nlisten:\n  host: 0.0.0.0\n  port: 4242\nfirewall:\n  outbound:\n    - port: any\n      proto: any\n      host: any\n  inbound:\n    - port: any\n      proto: icmp\n      host: any\n`),\n\t}\n\n\tvar m map[string]any\n\n\t// merge the same way config.parse() merges\n\tfor _, b := range configs {\n\t\tvar nm map[string]any\n\t\terr := yaml.Unmarshal(b, &nm)\n\t\trequire.NoError(t, err)\n\n\t\t// We need to use WithAppendSlice so that firewall rules in separate\n\t\t// files are appended together\n\t\terr = mergo.Merge(&nm, m, mergo.WithAppendSlice)\n\t\tm = nm\n\t\trequire.NoError(t, err)\n\t}\n\n\tt.Logf(\"Merged Config: %#v\", m)\n\tmYaml, err := yaml.Marshal(m)\n\trequire.NoError(t, err)\n\tt.Logf(\"Merged Config as YAML:\\n%s\", mYaml)\n\n\t// If a bug is present, some items might be replaced instead of merged like we expect\n\texpected := map[string]any{\n\t\t\"firewall\": map[string]any{\n\t\t\t\"inbound\": []any{\n\t\t\t\tmap[string]any{\"host\": \"any\", \"port\": \"any\", \"proto\": \"icmp\"},\n\t\t\t\tmap[string]any{\"groups\": []any{\"server\"}, \"port\": 443, \"proto\": \"tcp\"},\n\t\t\t\tmap[string]any{\"groups\": []any{\"webapp\"}, \"port\": 443, \"proto\": \"tcp\"}},\n\t\t\t\"outbound\": []any{\n\t\t\t\tmap[string]any{\"host\": \"any\", \"port\": \"any\", \"proto\": \"any\"}}},\n\t\t\"listen\": map[string]any{\n\t\t\t\"host\": \"0.0.0.0\",\n\t\t\t\"port\": 4242,\n\t\t},\n\t}\n\tassert.Equal(t, expected, m)\n}\n"
  },
  {
    "path": "connection_manager.go",
    "content": "package nebula\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/rcrowley/go-metrics\"\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/cert\"\n\t\"github.com/slackhq/nebula/config\"\n\t\"github.com/slackhq/nebula/header\"\n)\n\ntype trafficDecision int\n\nconst (\n\tdoNothing      trafficDecision = 0\n\tdeleteTunnel   trafficDecision = 1 // delete the hostinfo on our side, do not notify the remote\n\tcloseTunnel    trafficDecision = 2 // delete the hostinfo and notify the remote\n\tswapPrimary    trafficDecision = 3\n\tmigrateRelays  trafficDecision = 4\n\ttryRehandshake trafficDecision = 5\n\tsendTestPacket trafficDecision = 6\n)\n\ntype connectionManager struct {\n\t// relayUsed holds which relay localIndexs are in use\n\trelayUsed     map[uint32]struct{}\n\trelayUsedLock *sync.RWMutex\n\n\thostMap      *HostMap\n\ttrafficTimer *LockingTimerWheel[uint32]\n\tintf         *Interface\n\tpunchy       *Punchy\n\n\t// Configuration settings\n\tcheckInterval           time.Duration\n\tpendingDeletionInterval time.Duration\n\tinactivityTimeout       atomic.Int64\n\tdropInactive            atomic.Bool\n\n\tmetricsTxPunchy metrics.Counter\n\n\tl *logrus.Logger\n}\n\nfunc newConnectionManagerFromConfig(l *logrus.Logger, c *config.C, hm *HostMap, p *Punchy) *connectionManager {\n\tcm := &connectionManager{\n\t\thostMap:         hm,\n\t\tl:               l,\n\t\tpunchy:          p,\n\t\trelayUsed:       make(map[uint32]struct{}),\n\t\trelayUsedLock:   &sync.RWMutex{},\n\t\tmetricsTxPunchy: metrics.GetOrRegisterCounter(\"messages.tx.punchy\", nil),\n\t}\n\n\tcm.reload(c, true)\n\tc.RegisterReloadCallback(func(c *config.C) {\n\t\tcm.reload(c, false)\n\t})\n\n\treturn cm\n}\n\nfunc (cm *connectionManager) reload(c *config.C, initial bool) {\n\tif initial {\n\t\tcm.checkInterval = time.Duration(c.GetInt(\"timers.connection_alive_interval\", 5)) * time.Second\n\t\tcm.pendingDeletionInterval = time.Duration(c.GetInt(\"timers.pending_deletion_interval\", 10)) * time.Second\n\n\t\t// We want at least a minimum resolution of 500ms per tick so that we can hit these intervals\n\t\t// pretty close to their configured duration.\n\t\t// The inactivity duration is checked each time a hostinfo ticks through so we don't need the wheel to contain it.\n\t\tminDuration := min(time.Millisecond*500, cm.checkInterval, cm.pendingDeletionInterval)\n\t\tmaxDuration := max(cm.checkInterval, cm.pendingDeletionInterval)\n\t\tcm.trafficTimer = NewLockingTimerWheel[uint32](minDuration, maxDuration)\n\t}\n\n\tif initial || c.HasChanged(\"tunnels.inactivity_timeout\") {\n\t\told := cm.getInactivityTimeout()\n\t\tcm.inactivityTimeout.Store((int64)(c.GetDuration(\"tunnels.inactivity_timeout\", 10*time.Minute)))\n\t\tif !initial {\n\t\t\tcm.l.WithField(\"oldDuration\", old).\n\t\t\t\tWithField(\"newDuration\", cm.getInactivityTimeout()).\n\t\t\t\tInfo(\"Inactivity timeout has changed\")\n\t\t}\n\t}\n\n\tif initial || c.HasChanged(\"tunnels.drop_inactive\") {\n\t\told := cm.dropInactive.Load()\n\t\tcm.dropInactive.Store(c.GetBool(\"tunnels.drop_inactive\", false))\n\t\tif !initial {\n\t\t\tcm.l.WithField(\"oldBool\", old).\n\t\t\t\tWithField(\"newBool\", cm.dropInactive.Load()).\n\t\t\t\tInfo(\"Drop inactive setting has changed\")\n\t\t}\n\t}\n}\n\nfunc (cm *connectionManager) getInactivityTimeout() time.Duration {\n\treturn (time.Duration)(cm.inactivityTimeout.Load())\n}\n\nfunc (cm *connectionManager) In(h *HostInfo) {\n\th.in.Store(true)\n}\n\nfunc (cm *connectionManager) Out(h *HostInfo) {\n\th.out.Store(true)\n}\n\nfunc (cm *connectionManager) RelayUsed(localIndex uint32) {\n\tcm.relayUsedLock.RLock()\n\t// If this already exists, return\n\tif _, ok := cm.relayUsed[localIndex]; ok {\n\t\tcm.relayUsedLock.RUnlock()\n\t\treturn\n\t}\n\tcm.relayUsedLock.RUnlock()\n\tcm.relayUsedLock.Lock()\n\tcm.relayUsed[localIndex] = struct{}{}\n\tcm.relayUsedLock.Unlock()\n}\n\n// getAndResetTrafficCheck returns if there was any inbound or outbound traffic within the last tick and\n// resets the state for this local index\nfunc (cm *connectionManager) getAndResetTrafficCheck(h *HostInfo, now time.Time) (bool, bool) {\n\tin := h.in.Swap(false)\n\tout := h.out.Swap(false)\n\tif in || out {\n\t\th.lastUsed = now\n\t}\n\treturn in, out\n}\n\n// AddTrafficWatch must be called for every new HostInfo.\n// We will continue to monitor the HostInfo until the tunnel is dropped.\nfunc (cm *connectionManager) AddTrafficWatch(h *HostInfo) {\n\tif h.out.Swap(true) == false {\n\t\tcm.trafficTimer.Add(h.localIndexId, cm.checkInterval)\n\t}\n}\n\nfunc (cm *connectionManager) Start(ctx context.Context) {\n\tclockSource := time.NewTicker(cm.trafficTimer.t.tickDuration)\n\tdefer clockSource.Stop()\n\n\tp := []byte(\"\")\n\tnb := make([]byte, 12, 12)\n\tout := make([]byte, mtu)\n\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\n\t\tcase now := <-clockSource.C:\n\t\t\tcm.trafficTimer.Advance(now)\n\t\t\tfor {\n\t\t\t\tlocalIndex, has := cm.trafficTimer.Purge()\n\t\t\t\tif !has {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tcm.doTrafficCheck(localIndex, p, nb, out, now)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (cm *connectionManager) doTrafficCheck(localIndex uint32, p, nb, out []byte, now time.Time) {\n\tdecision, hostinfo, primary := cm.makeTrafficDecision(localIndex, now)\n\n\tswitch decision {\n\tcase deleteTunnel:\n\t\tif cm.hostMap.DeleteHostInfo(hostinfo) {\n\t\t\t// Only clearing the lighthouse cache if this is the last hostinfo for this vpn ip in the hostmap\n\t\t\tcm.intf.lightHouse.DeleteVpnAddrs(hostinfo.vpnAddrs)\n\t\t}\n\n\tcase closeTunnel:\n\t\tcm.intf.sendCloseTunnel(hostinfo)\n\t\tcm.intf.closeTunnel(hostinfo)\n\n\tcase swapPrimary:\n\t\tcm.swapPrimary(hostinfo, primary)\n\n\tcase migrateRelays:\n\t\tcm.migrateRelayUsed(hostinfo, primary)\n\n\tcase tryRehandshake:\n\t\tcm.tryRehandshake(hostinfo)\n\n\tcase sendTestPacket:\n\t\tcm.intf.SendMessageToHostInfo(header.Test, header.TestRequest, hostinfo, p, nb, out)\n\t}\n\n\tcm.resetRelayTrafficCheck(hostinfo)\n}\n\nfunc (cm *connectionManager) resetRelayTrafficCheck(hostinfo *HostInfo) {\n\tif hostinfo != nil {\n\t\tcm.relayUsedLock.Lock()\n\t\tdefer cm.relayUsedLock.Unlock()\n\t\t// No need to migrate any relays, delete usage info now.\n\t\tfor _, idx := range hostinfo.relayState.CopyRelayForIdxs() {\n\t\t\tdelete(cm.relayUsed, idx)\n\t\t}\n\t}\n}\n\nfunc (cm *connectionManager) migrateRelayUsed(oldhostinfo, newhostinfo *HostInfo) {\n\trelayFor := oldhostinfo.relayState.CopyAllRelayFor()\n\n\tfor _, r := range relayFor {\n\t\texisting, ok := newhostinfo.relayState.QueryRelayForByIp(r.PeerAddr)\n\n\t\tvar index uint32\n\t\tvar relayFrom netip.Addr\n\t\tvar relayTo netip.Addr\n\t\tswitch {\n\t\tcase ok:\n\t\t\tswitch existing.State {\n\t\t\tcase Established, PeerRequested, Disestablished:\n\t\t\t\t// This relay already exists in newhostinfo, then do nothing.\n\t\t\t\tcontinue\n\t\t\tcase Requested:\n\t\t\t\t// The relay exists in a Requested state; re-send the request\n\t\t\t\tindex = existing.LocalIndex\n\t\t\t\tswitch r.Type {\n\t\t\t\tcase TerminalType:\n\t\t\t\t\trelayFrom = cm.intf.myVpnAddrs[0]\n\t\t\t\t\trelayTo = existing.PeerAddr\n\t\t\t\tcase ForwardingType:\n\t\t\t\t\trelayFrom = existing.PeerAddr\n\t\t\t\t\trelayTo = newhostinfo.vpnAddrs[0]\n\t\t\t\tdefault:\n\t\t\t\t\t// should never happen\n\t\t\t\t\tpanic(fmt.Sprintf(\"Migrating unknown relay type: %v\", r.Type))\n\t\t\t\t}\n\t\t\t}\n\t\tcase !ok:\n\t\t\tcm.relayUsedLock.RLock()\n\t\t\tif _, relayUsed := cm.relayUsed[r.LocalIndex]; !relayUsed {\n\t\t\t\t// The relay hasn't been used; don't migrate it.\n\t\t\t\tcm.relayUsedLock.RUnlock()\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tcm.relayUsedLock.RUnlock()\n\t\t\t// The relay doesn't exist at all; create some relay state and send the request.\n\t\t\tvar err error\n\t\t\tindex, err = AddRelay(cm.l, newhostinfo, cm.hostMap, r.PeerAddr, nil, r.Type, Requested)\n\t\t\tif err != nil {\n\t\t\t\tcm.l.WithError(err).Error(\"failed to migrate relay to new hostinfo\")\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tswitch r.Type {\n\t\t\tcase TerminalType:\n\t\t\t\trelayFrom = cm.intf.myVpnAddrs[0]\n\t\t\t\trelayTo = r.PeerAddr\n\t\t\tcase ForwardingType:\n\t\t\t\trelayFrom = r.PeerAddr\n\t\t\t\trelayTo = newhostinfo.vpnAddrs[0]\n\t\t\tdefault:\n\t\t\t\t// should never happen\n\t\t\t\tpanic(fmt.Sprintf(\"Migrating unknown relay type: %v\", r.Type))\n\t\t\t}\n\t\t}\n\n\t\t// Send a CreateRelayRequest to the peer.\n\t\treq := NebulaControl{\n\t\t\tType:                NebulaControl_CreateRelayRequest,\n\t\t\tInitiatorRelayIndex: index,\n\t\t}\n\n\t\tswitch newhostinfo.GetCert().Certificate.Version() {\n\t\tcase cert.Version1:\n\t\t\tif !relayFrom.Is4() {\n\t\t\t\tcm.l.Error(\"can not migrate v1 relay with a v6 network because the relay is not running a current nebula version\")\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif !relayTo.Is4() {\n\t\t\t\tcm.l.Error(\"can not migrate v1 relay with a v6 remote network because the relay is not running a current nebula version\")\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tb := relayFrom.As4()\n\t\t\treq.OldRelayFromAddr = binary.BigEndian.Uint32(b[:])\n\t\t\tb = relayTo.As4()\n\t\t\treq.OldRelayToAddr = binary.BigEndian.Uint32(b[:])\n\t\tcase cert.Version2:\n\t\t\treq.RelayFromAddr = netAddrToProtoAddr(relayFrom)\n\t\t\treq.RelayToAddr = netAddrToProtoAddr(relayTo)\n\t\tdefault:\n\t\t\tnewhostinfo.logger(cm.l).Error(\"Unknown certificate version found while attempting to migrate relay\")\n\t\t\tcontinue\n\t\t}\n\n\t\tmsg, err := req.Marshal()\n\t\tif err != nil {\n\t\t\tcm.l.WithError(err).Error(\"failed to marshal Control message to migrate relay\")\n\t\t} else {\n\t\t\tcm.intf.SendMessageToHostInfo(header.Control, 0, newhostinfo, msg, make([]byte, 12), make([]byte, mtu))\n\t\t\tcm.l.WithFields(logrus.Fields{\n\t\t\t\t\"relayFrom\":           req.RelayFromAddr,\n\t\t\t\t\"relayTo\":             req.RelayToAddr,\n\t\t\t\t\"initiatorRelayIndex\": req.InitiatorRelayIndex,\n\t\t\t\t\"responderRelayIndex\": req.ResponderRelayIndex,\n\t\t\t\t\"vpnAddrs\":            newhostinfo.vpnAddrs}).\n\t\t\t\tInfo(\"send CreateRelayRequest\")\n\t\t}\n\t}\n}\n\nfunc (cm *connectionManager) makeTrafficDecision(localIndex uint32, now time.Time) (trafficDecision, *HostInfo, *HostInfo) {\n\t// Read lock the main hostmap to order decisions based on tunnels being the primary tunnel\n\tcm.hostMap.RLock()\n\tdefer cm.hostMap.RUnlock()\n\n\thostinfo := cm.hostMap.Indexes[localIndex]\n\tif hostinfo == nil {\n\t\tcm.l.WithField(\"localIndex\", localIndex).Debugln(\"Not found in hostmap\")\n\t\treturn doNothing, nil, nil\n\t}\n\n\tif cm.isInvalidCertificate(now, hostinfo) {\n\t\treturn closeTunnel, hostinfo, nil\n\t}\n\n\tprimary := cm.hostMap.Hosts[hostinfo.vpnAddrs[0]]\n\tmainHostInfo := true\n\tif primary != nil && primary != hostinfo {\n\t\tmainHostInfo = false\n\t}\n\n\t// Check for traffic on this hostinfo\n\tinTraffic, outTraffic := cm.getAndResetTrafficCheck(hostinfo, now)\n\n\t// A hostinfo is determined alive if there is incoming traffic\n\tif inTraffic {\n\t\tdecision := doNothing\n\t\tif cm.l.Level >= logrus.DebugLevel {\n\t\t\thostinfo.logger(cm.l).\n\t\t\t\tWithField(\"tunnelCheck\", m{\"state\": \"alive\", \"method\": \"passive\"}).\n\t\t\t\tDebug(\"Tunnel status\")\n\t\t}\n\t\thostinfo.pendingDeletion.Store(false)\n\n\t\tif mainHostInfo {\n\t\t\tdecision = tryRehandshake\n\t\t} else {\n\t\t\tif cm.shouldSwapPrimary(hostinfo) {\n\t\t\t\tdecision = swapPrimary\n\t\t\t} else {\n\t\t\t\t// migrate the relays to the primary, if in use.\n\t\t\t\tdecision = migrateRelays\n\t\t\t}\n\t\t}\n\n\t\tcm.trafficTimer.Add(hostinfo.localIndexId, cm.checkInterval)\n\n\t\tif !outTraffic {\n\t\t\t// Send a punch packet to keep the NAT state alive\n\t\t\tcm.sendPunch(hostinfo)\n\t\t}\n\n\t\treturn decision, hostinfo, primary\n\t}\n\n\tif hostinfo.pendingDeletion.Load() {\n\t\t// We have already sent a test packet and nothing was returned, this hostinfo is dead\n\t\thostinfo.logger(cm.l).\n\t\t\tWithField(\"tunnelCheck\", m{\"state\": \"dead\", \"method\": \"active\"}).\n\t\t\tInfo(\"Tunnel status\")\n\n\t\treturn deleteTunnel, hostinfo, nil\n\t}\n\n\tdecision := doNothing\n\tif hostinfo != nil && hostinfo.ConnectionState != nil && mainHostInfo {\n\t\tif !outTraffic {\n\t\t\tinactiveFor, isInactive := cm.isInactive(hostinfo, now)\n\t\t\tif isInactive {\n\t\t\t\t// Tunnel is inactive, tear it down\n\t\t\t\thostinfo.logger(cm.l).\n\t\t\t\t\tWithField(\"inactiveDuration\", inactiveFor).\n\t\t\t\t\tWithField(\"primary\", mainHostInfo).\n\t\t\t\t\tInfo(\"Dropping tunnel due to inactivity\")\n\n\t\t\t\treturn closeTunnel, hostinfo, primary\n\t\t\t}\n\n\t\t\t// If we aren't sending or receiving traffic then its an unused tunnel and we don't to test the tunnel.\n\t\t\t// Just maintain NAT state if configured to do so.\n\t\t\tcm.sendPunch(hostinfo)\n\t\t\tcm.trafficTimer.Add(hostinfo.localIndexId, cm.checkInterval)\n\t\t\treturn doNothing, nil, nil\n\t\t}\n\n\t\tif cm.punchy.GetTargetEverything() {\n\t\t\t// This is similar to the old punchy behavior with a slight optimization.\n\t\t\t// We aren't receiving traffic but we are sending it, punch on all known\n\t\t\t// ips in case we need to re-prime NAT state\n\t\t\tcm.sendPunch(hostinfo)\n\t\t}\n\n\t\tif cm.l.Level >= logrus.DebugLevel {\n\t\t\thostinfo.logger(cm.l).\n\t\t\t\tWithField(\"tunnelCheck\", m{\"state\": \"testing\", \"method\": \"active\"}).\n\t\t\t\tDebug(\"Tunnel status\")\n\t\t}\n\n\t\t// Send a test packet to trigger an authenticated tunnel test, this should suss out any lingering tunnel issues\n\t\tdecision = sendTestPacket\n\n\t} else {\n\t\tif cm.l.Level >= logrus.DebugLevel {\n\t\t\thostinfo.logger(cm.l).Debugf(\"Hostinfo sadness\")\n\t\t}\n\t}\n\n\thostinfo.pendingDeletion.Store(true)\n\tcm.trafficTimer.Add(hostinfo.localIndexId, cm.pendingDeletionInterval)\n\treturn decision, hostinfo, nil\n}\n\nfunc (cm *connectionManager) isInactive(hostinfo *HostInfo, now time.Time) (time.Duration, bool) {\n\tif cm.dropInactive.Load() == false {\n\t\t// We aren't configured to drop inactive tunnels\n\t\treturn 0, false\n\t}\n\n\tinactiveDuration := now.Sub(hostinfo.lastUsed)\n\tif inactiveDuration < cm.getInactivityTimeout() {\n\t\t// It's not considered inactive\n\t\treturn inactiveDuration, false\n\t}\n\n\t// The tunnel is inactive\n\treturn inactiveDuration, true\n}\n\nfunc (cm *connectionManager) shouldSwapPrimary(current *HostInfo) bool {\n\t// The primary tunnel is the most recent handshake to complete locally and should work entirely fine.\n\t// If we are here then we have multiple tunnels for a host pair and neither side believes the same tunnel is primary.\n\t// Let's sort this out.\n\n\t// Only one side should swap because if both swap then we may never resolve to a single tunnel.\n\t// vpn addr is static across all tunnels for this host pair so lets\n\t// use that to determine if we should consider swapping.\n\tif current.vpnAddrs[0].Compare(cm.intf.myVpnAddrs[0]) < 0 {\n\t\t// Their primary vpn addr is less than mine. Do not swap.\n\t\treturn false\n\t}\n\n\tcrt := cm.intf.pki.getCertState().getCertificate(current.ConnectionState.myCert.Version())\n\tif crt == nil {\n\t\t//my cert was reloaded away. We should definitely swap from this tunnel\n\t\treturn true\n\t}\n\t// If this tunnel is using the latest certificate then we should swap it to primary for a bit and see if things\n\t// settle down.\n\treturn bytes.Equal(current.ConnectionState.myCert.Signature(), crt.Signature())\n}\n\nfunc (cm *connectionManager) swapPrimary(current, primary *HostInfo) {\n\tcm.hostMap.Lock()\n\t// Make sure the primary is still the same after the write lock. This avoids a race with a rehandshake.\n\tif cm.hostMap.Hosts[current.vpnAddrs[0]] == primary {\n\t\tcm.hostMap.unlockedMakePrimary(current)\n\t}\n\tcm.hostMap.Unlock()\n}\n\n// isInvalidCertificate decides if we should destroy a tunnel.\n// returns true if pki.disconnect_invalid is true and the certificate is no longer valid.\n// Blocklisted certificates will skip the pki.disconnect_invalid check and return true.\nfunc (cm *connectionManager) isInvalidCertificate(now time.Time, hostinfo *HostInfo) bool {\n\tremoteCert := hostinfo.GetCert()\n\tif remoteCert == nil {\n\t\treturn false //don't tear down tunnels for handshakes in progress\n\t}\n\n\tcaPool := cm.intf.pki.GetCAPool()\n\terr := caPool.VerifyCachedCertificate(now, remoteCert)\n\tif err == nil {\n\t\treturn false //cert is still valid! yay!\n\t} else if err == cert.ErrBlockListed { //avoiding errors.Is for speed\n\t\t// Block listed certificates should always be disconnected\n\t\thostinfo.logger(cm.l).WithError(err).\n\t\t\tWithField(\"fingerprint\", remoteCert.Fingerprint).\n\t\t\tInfo(\"Remote certificate is blocked, tearing down the tunnel\")\n\t\treturn true\n\t} else if cm.intf.disconnectInvalid.Load() {\n\t\thostinfo.logger(cm.l).WithError(err).\n\t\t\tWithField(\"fingerprint\", remoteCert.Fingerprint).\n\t\t\tInfo(\"Remote certificate is no longer valid, tearing down the tunnel\")\n\t\treturn true\n\t} else {\n\t\t//if we reach here, the cert is no longer valid, but we're configured to keep tunnels from now-invalid certs open\n\t\treturn false\n\t}\n}\n\nfunc (cm *connectionManager) sendPunch(hostinfo *HostInfo) {\n\tif !cm.punchy.GetPunch() {\n\t\t// Punching is disabled\n\t\treturn\n\t}\n\n\tif cm.intf.lightHouse.IsAnyLighthouseAddr(hostinfo.vpnAddrs) {\n\t\t// Do not punch to lighthouses, we assume our lighthouse update interval is good enough.\n\t\t// In the event the update interval is not sufficient to maintain NAT state then a publicly available lighthouse\n\t\t// would lose the ability to notify us and punchy.respond would become unreliable.\n\t\treturn\n\t}\n\n\tif cm.punchy.GetTargetEverything() {\n\t\thostinfo.remotes.ForEach(cm.hostMap.GetPreferredRanges(), func(addr netip.AddrPort, preferred bool) {\n\t\t\tcm.metricsTxPunchy.Inc(1)\n\t\t\tcm.intf.outside.WriteTo([]byte{1}, addr)\n\t\t})\n\n\t} else if hostinfo.remote.IsValid() {\n\t\tcm.metricsTxPunchy.Inc(1)\n\t\tcm.intf.outside.WriteTo([]byte{1}, hostinfo.remote)\n\t}\n}\n\nfunc (cm *connectionManager) tryRehandshake(hostinfo *HostInfo) {\n\tcs := cm.intf.pki.getCertState()\n\tcurCrt := hostinfo.ConnectionState.myCert\n\tcurCrtVersion := curCrt.Version()\n\tmyCrt := cs.getCertificate(curCrtVersion)\n\tif myCrt == nil {\n\t\tcm.l.WithField(\"vpnAddrs\", hostinfo.vpnAddrs).\n\t\t\tWithField(\"version\", curCrtVersion).\n\t\t\tWithField(\"reason\", \"local certificate removed\").\n\t\t\tInfo(\"Re-handshaking with remote\")\n\t\tcm.intf.handshakeManager.StartHandshake(hostinfo.vpnAddrs[0], nil)\n\t\treturn\n\t}\n\tpeerCrt := hostinfo.ConnectionState.peerCert\n\tif peerCrt != nil && curCrtVersion < peerCrt.Certificate.Version() {\n\t\t// if our certificate version is less than theirs, and we have a matching version available, rehandshake?\n\t\tif cs.getCertificate(peerCrt.Certificate.Version()) != nil {\n\t\t\tcm.l.WithField(\"vpnAddrs\", hostinfo.vpnAddrs).\n\t\t\t\tWithField(\"version\", curCrtVersion).\n\t\t\t\tWithField(\"peerVersion\", peerCrt.Certificate.Version()).\n\t\t\t\tWithField(\"reason\", \"local certificate version lower than peer, attempting to correct\").\n\t\t\t\tInfo(\"Re-handshaking with remote\")\n\t\t\tcm.intf.handshakeManager.StartHandshake(hostinfo.vpnAddrs[0], func(hh *HandshakeHostInfo) {\n\t\t\t\thh.initiatingVersionOverride = peerCrt.Certificate.Version()\n\t\t\t})\n\t\t\treturn\n\t\t}\n\t}\n\tif !bytes.Equal(curCrt.Signature(), myCrt.Signature()) {\n\t\tcm.l.WithField(\"vpnAddrs\", hostinfo.vpnAddrs).\n\t\t\tWithField(\"reason\", \"local certificate is not current\").\n\t\t\tInfo(\"Re-handshaking with remote\")\n\n\t\tcm.intf.handshakeManager.StartHandshake(hostinfo.vpnAddrs[0], nil)\n\t\treturn\n\t}\n\tif curCrtVersion < cs.initiatingVersion {\n\t\tcm.l.WithField(\"vpnAddrs\", hostinfo.vpnAddrs).\n\t\t\tWithField(\"reason\", \"current cert version < pki.initiatingVersion\").\n\t\t\tInfo(\"Re-handshaking with remote\")\n\n\t\tcm.intf.handshakeManager.StartHandshake(hostinfo.vpnAddrs[0], nil)\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "connection_manager_test.go",
    "content": "package nebula\n\nimport (\n\t\"crypto/ed25519\"\n\t\"crypto/rand\"\n\t\"net/netip\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/flynn/noise\"\n\t\"github.com/slackhq/nebula/cert\"\n\t\"github.com/slackhq/nebula/config\"\n\t\"github.com/slackhq/nebula/test\"\n\t\"github.com/slackhq/nebula/udp\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc newTestLighthouse() *LightHouse {\n\tlh := &LightHouse{\n\t\tl:         test.NewLogger(),\n\t\taddrMap:   map[netip.Addr]*RemoteList{},\n\t\tqueryChan: make(chan netip.Addr, 10),\n\t}\n\tlighthouses := []netip.Addr{}\n\tstaticList := map[netip.Addr]struct{}{}\n\n\tlh.lighthouses.Store(&lighthouses)\n\tlh.staticList.Store(&staticList)\n\n\treturn lh\n}\n\nfunc Test_NewConnectionManagerTest(t *testing.T) {\n\tl := test.NewLogger()\n\t//_, tuncidr, _ := net.ParseCIDR(\"1.1.1.1/24\")\n\tlocalrange := netip.MustParsePrefix(\"10.1.1.1/24\")\n\tvpnIp := netip.MustParseAddr(\"172.1.1.2\")\n\tpreferredRanges := []netip.Prefix{localrange}\n\n\t// Very incomplete mock objects\n\thostMap := newHostMap(l)\n\thostMap.preferredRanges.Store(&preferredRanges)\n\n\tcs := &CertState{\n\t\tinitiatingVersion: cert.Version1,\n\t\tprivateKey:        []byte{},\n\t\tv1Cert:            &dummyCert{version: cert.Version1},\n\t\tv1HandshakeBytes:  []byte{},\n\t}\n\n\tlh := newTestLighthouse()\n\tifce := &Interface{\n\t\thostMap:          hostMap,\n\t\tinside:           &test.NoopTun{},\n\t\toutside:          &udp.NoopConn{},\n\t\tfirewall:         &Firewall{},\n\t\tlightHouse:       lh,\n\t\tpki:              &PKI{},\n\t\thandshakeManager: NewHandshakeManager(l, hostMap, lh, &udp.NoopConn{}, defaultHandshakeConfig),\n\t\tl:                l,\n\t}\n\tifce.pki.cs.Store(cs)\n\n\t// Create manager\n\tconf := config.NewC(l)\n\tpunchy := NewPunchyFromConfig(l, conf)\n\tnc := newConnectionManagerFromConfig(l, conf, hostMap, punchy)\n\tnc.intf = ifce\n\tp := []byte(\"\")\n\tnb := make([]byte, 12, 12)\n\tout := make([]byte, mtu)\n\n\t// Add an ip we have established a connection w/ to hostmap\n\thostinfo := &HostInfo{\n\t\tvpnAddrs:      []netip.Addr{vpnIp},\n\t\tlocalIndexId:  1099,\n\t\tremoteIndexId: 9901,\n\t}\n\thostinfo.ConnectionState = &ConnectionState{\n\t\tmyCert: &dummyCert{version: cert.Version1},\n\t\tH:      &noise.HandshakeState{},\n\t}\n\tnc.hostMap.unlockedAddHostInfo(hostinfo, ifce)\n\n\t// We saw traffic out to vpnIp\n\tnc.Out(hostinfo)\n\tnc.In(hostinfo)\n\tassert.False(t, hostinfo.pendingDeletion.Load())\n\tassert.Contains(t, nc.hostMap.Hosts, hostinfo.vpnAddrs[0])\n\tassert.Contains(t, nc.hostMap.Indexes, hostinfo.localIndexId)\n\tassert.True(t, hostinfo.out.Load())\n\tassert.True(t, hostinfo.in.Load())\n\n\t// Do a traffic check tick, should not be pending deletion but should not have any in/out packets recorded\n\tnc.doTrafficCheck(hostinfo.localIndexId, p, nb, out, time.Now())\n\tassert.False(t, hostinfo.pendingDeletion.Load())\n\tassert.False(t, hostinfo.out.Load())\n\tassert.False(t, hostinfo.in.Load())\n\n\t// Do another traffic check tick, this host should be pending deletion now\n\tnc.Out(hostinfo)\n\tassert.True(t, hostinfo.out.Load())\n\tnc.doTrafficCheck(hostinfo.localIndexId, p, nb, out, time.Now())\n\tassert.True(t, hostinfo.pendingDeletion.Load())\n\tassert.False(t, hostinfo.out.Load())\n\tassert.False(t, hostinfo.in.Load())\n\tassert.Contains(t, nc.hostMap.Indexes, hostinfo.localIndexId)\n\tassert.Contains(t, nc.hostMap.Hosts, hostinfo.vpnAddrs[0])\n\n\t// Do a final traffic check tick, the host should now be removed\n\tnc.doTrafficCheck(hostinfo.localIndexId, p, nb, out, time.Now())\n\tassert.NotContains(t, nc.hostMap.Hosts, hostinfo.vpnAddrs)\n\tassert.NotContains(t, nc.hostMap.Indexes, hostinfo.localIndexId)\n}\n\nfunc Test_NewConnectionManagerTest2(t *testing.T) {\n\tl := test.NewLogger()\n\t//_, tuncidr, _ := net.ParseCIDR(\"1.1.1.1/24\")\n\tlocalrange := netip.MustParsePrefix(\"10.1.1.1/24\")\n\tvpnIp := netip.MustParseAddr(\"172.1.1.2\")\n\tpreferredRanges := []netip.Prefix{localrange}\n\n\t// Very incomplete mock objects\n\thostMap := newHostMap(l)\n\thostMap.preferredRanges.Store(&preferredRanges)\n\n\tcs := &CertState{\n\t\tinitiatingVersion: cert.Version1,\n\t\tprivateKey:        []byte{},\n\t\tv1Cert:            &dummyCert{version: cert.Version1},\n\t\tv1HandshakeBytes:  []byte{},\n\t}\n\n\tlh := newTestLighthouse()\n\tifce := &Interface{\n\t\thostMap:          hostMap,\n\t\tinside:           &test.NoopTun{},\n\t\toutside:          &udp.NoopConn{},\n\t\tfirewall:         &Firewall{},\n\t\tlightHouse:       lh,\n\t\tpki:              &PKI{},\n\t\thandshakeManager: NewHandshakeManager(l, hostMap, lh, &udp.NoopConn{}, defaultHandshakeConfig),\n\t\tl:                l,\n\t}\n\tifce.pki.cs.Store(cs)\n\n\t// Create manager\n\tconf := config.NewC(l)\n\tpunchy := NewPunchyFromConfig(l, conf)\n\tnc := newConnectionManagerFromConfig(l, conf, hostMap, punchy)\n\tnc.intf = ifce\n\tp := []byte(\"\")\n\tnb := make([]byte, 12, 12)\n\tout := make([]byte, mtu)\n\n\t// Add an ip we have established a connection w/ to hostmap\n\thostinfo := &HostInfo{\n\t\tvpnAddrs:      []netip.Addr{vpnIp},\n\t\tlocalIndexId:  1099,\n\t\tremoteIndexId: 9901,\n\t}\n\thostinfo.ConnectionState = &ConnectionState{\n\t\tmyCert: &dummyCert{version: cert.Version1},\n\t\tH:      &noise.HandshakeState{},\n\t}\n\tnc.hostMap.unlockedAddHostInfo(hostinfo, ifce)\n\n\t// We saw traffic out to vpnIp\n\tnc.Out(hostinfo)\n\tnc.In(hostinfo)\n\tassert.True(t, hostinfo.in.Load())\n\tassert.True(t, hostinfo.out.Load())\n\tassert.False(t, hostinfo.pendingDeletion.Load())\n\tassert.Contains(t, nc.hostMap.Hosts, hostinfo.vpnAddrs[0])\n\tassert.Contains(t, nc.hostMap.Indexes, hostinfo.localIndexId)\n\n\t// Do a traffic check tick, should not be pending deletion but should not have any in/out packets recorded\n\tnc.doTrafficCheck(hostinfo.localIndexId, p, nb, out, time.Now())\n\tassert.False(t, hostinfo.pendingDeletion.Load())\n\tassert.False(t, hostinfo.out.Load())\n\tassert.False(t, hostinfo.in.Load())\n\n\t// Do another traffic check tick, this host should be pending deletion now\n\tnc.Out(hostinfo)\n\tnc.doTrafficCheck(hostinfo.localIndexId, p, nb, out, time.Now())\n\tassert.True(t, hostinfo.pendingDeletion.Load())\n\tassert.False(t, hostinfo.out.Load())\n\tassert.False(t, hostinfo.in.Load())\n\tassert.Contains(t, nc.hostMap.Indexes, hostinfo.localIndexId)\n\tassert.Contains(t, nc.hostMap.Hosts, hostinfo.vpnAddrs[0])\n\n\t// We saw traffic, should no longer be pending deletion\n\tnc.In(hostinfo)\n\tnc.doTrafficCheck(hostinfo.localIndexId, p, nb, out, time.Now())\n\tassert.False(t, hostinfo.pendingDeletion.Load())\n\tassert.False(t, hostinfo.out.Load())\n\tassert.False(t, hostinfo.in.Load())\n\tassert.Contains(t, nc.hostMap.Indexes, hostinfo.localIndexId)\n\tassert.Contains(t, nc.hostMap.Hosts, hostinfo.vpnAddrs[0])\n}\n\nfunc Test_NewConnectionManager_DisconnectInactive(t *testing.T) {\n\tl := test.NewLogger()\n\tlocalrange := netip.MustParsePrefix(\"10.1.1.1/24\")\n\tvpnAddrs := []netip.Addr{netip.MustParseAddr(\"172.1.1.2\")}\n\tpreferredRanges := []netip.Prefix{localrange}\n\n\t// Very incomplete mock objects\n\thostMap := newHostMap(l)\n\thostMap.preferredRanges.Store(&preferredRanges)\n\n\tcs := &CertState{\n\t\tinitiatingVersion: cert.Version1,\n\t\tprivateKey:        []byte{},\n\t\tv1Cert:            &dummyCert{version: cert.Version1},\n\t\tv1HandshakeBytes:  []byte{},\n\t}\n\n\tlh := newTestLighthouse()\n\tifce := &Interface{\n\t\thostMap:          hostMap,\n\t\tinside:           &test.NoopTun{},\n\t\toutside:          &udp.NoopConn{},\n\t\tfirewall:         &Firewall{},\n\t\tlightHouse:       lh,\n\t\tpki:              &PKI{},\n\t\thandshakeManager: NewHandshakeManager(l, hostMap, lh, &udp.NoopConn{}, defaultHandshakeConfig),\n\t\tl:                l,\n\t}\n\tifce.pki.cs.Store(cs)\n\n\t// Create manager\n\tconf := config.NewC(l)\n\tconf.Settings[\"tunnels\"] = map[string]any{\n\t\t\"drop_inactive\": true,\n\t}\n\tpunchy := NewPunchyFromConfig(l, conf)\n\tnc := newConnectionManagerFromConfig(l, conf, hostMap, punchy)\n\tassert.True(t, nc.dropInactive.Load())\n\tnc.intf = ifce\n\n\t// Add an ip we have established a connection w/ to hostmap\n\thostinfo := &HostInfo{\n\t\tvpnAddrs:      vpnAddrs,\n\t\tlocalIndexId:  1099,\n\t\tremoteIndexId: 9901,\n\t}\n\thostinfo.ConnectionState = &ConnectionState{\n\t\tmyCert: &dummyCert{version: cert.Version1},\n\t\tH:      &noise.HandshakeState{},\n\t}\n\tnc.hostMap.unlockedAddHostInfo(hostinfo, ifce)\n\n\t// Do a traffic check tick, in and out should be cleared but should not be pending deletion\n\tnc.Out(hostinfo)\n\tnc.In(hostinfo)\n\tassert.True(t, hostinfo.out.Load())\n\tassert.True(t, hostinfo.in.Load())\n\n\tnow := time.Now()\n\tdecision, _, _ := nc.makeTrafficDecision(hostinfo.localIndexId, now)\n\tassert.Equal(t, tryRehandshake, decision)\n\tassert.Equal(t, now, hostinfo.lastUsed)\n\tassert.False(t, hostinfo.pendingDeletion.Load())\n\tassert.False(t, hostinfo.out.Load())\n\tassert.False(t, hostinfo.in.Load())\n\n\tdecision, _, _ = nc.makeTrafficDecision(hostinfo.localIndexId, now.Add(time.Second*5))\n\tassert.Equal(t, doNothing, decision)\n\tassert.Equal(t, now, hostinfo.lastUsed)\n\tassert.False(t, hostinfo.pendingDeletion.Load())\n\tassert.False(t, hostinfo.out.Load())\n\tassert.False(t, hostinfo.in.Load())\n\n\t// Do another traffic check tick, should still not be pending deletion\n\tdecision, _, _ = nc.makeTrafficDecision(hostinfo.localIndexId, now.Add(time.Second*10))\n\tassert.Equal(t, doNothing, decision)\n\tassert.Equal(t, now, hostinfo.lastUsed)\n\tassert.False(t, hostinfo.pendingDeletion.Load())\n\tassert.False(t, hostinfo.out.Load())\n\tassert.False(t, hostinfo.in.Load())\n\tassert.Contains(t, nc.hostMap.Indexes, hostinfo.localIndexId)\n\tassert.Contains(t, nc.hostMap.Hosts, hostinfo.vpnAddrs[0])\n\n\t// Finally advance beyond the inactivity timeout\n\tdecision, _, _ = nc.makeTrafficDecision(hostinfo.localIndexId, now.Add(time.Minute*10))\n\tassert.Equal(t, closeTunnel, decision)\n\tassert.Equal(t, now, hostinfo.lastUsed)\n\tassert.False(t, hostinfo.pendingDeletion.Load())\n\tassert.False(t, hostinfo.out.Load())\n\tassert.False(t, hostinfo.in.Load())\n\tassert.Contains(t, nc.hostMap.Indexes, hostinfo.localIndexId)\n\tassert.Contains(t, nc.hostMap.Hosts, hostinfo.vpnAddrs[0])\n}\n\n// Check if we can disconnect the peer.\n// Validate if the peer's certificate is invalid (expired, etc.)\n// Disconnect only if disconnectInvalid: true is set.\nfunc Test_NewConnectionManagerTest_DisconnectInvalid(t *testing.T) {\n\tnow := time.Now()\n\tl := test.NewLogger()\n\n\tvpncidr := netip.MustParsePrefix(\"172.1.1.1/24\")\n\tlocalrange := netip.MustParsePrefix(\"10.1.1.1/24\")\n\tvpnIp := netip.MustParseAddr(\"172.1.1.2\")\n\tpreferredRanges := []netip.Prefix{localrange}\n\thostMap := newHostMap(l)\n\thostMap.preferredRanges.Store(&preferredRanges)\n\n\t// Generate keys for CA and peer's cert.\n\tpubCA, privCA, _ := ed25519.GenerateKey(rand.Reader)\n\ttbs := &cert.TBSCertificate{\n\t\tVersion:   1,\n\t\tName:      \"ca\",\n\t\tIsCA:      true,\n\t\tNotBefore: now,\n\t\tNotAfter:  now.Add(1 * time.Hour),\n\t\tPublicKey: pubCA,\n\t}\n\n\tcaCert, err := tbs.Sign(nil, cert.Curve_CURVE25519, privCA)\n\trequire.NoError(t, err)\n\tncp := cert.NewCAPool()\n\trequire.NoError(t, ncp.AddCA(caCert))\n\n\tpubCrt, _, _ := ed25519.GenerateKey(rand.Reader)\n\ttbs = &cert.TBSCertificate{\n\t\tVersion:   1,\n\t\tName:      \"host\",\n\t\tNetworks:  []netip.Prefix{vpncidr},\n\t\tNotBefore: now,\n\t\tNotAfter:  now.Add(60 * time.Second),\n\t\tPublicKey: pubCrt,\n\t}\n\tpeerCert, err := tbs.Sign(caCert, cert.Curve_CURVE25519, privCA)\n\trequire.NoError(t, err)\n\n\tcachedPeerCert, err := ncp.VerifyCertificate(now.Add(time.Second), peerCert)\n\n\tcs := &CertState{\n\t\tprivateKey:       []byte{},\n\t\tv1Cert:           &dummyCert{},\n\t\tv1HandshakeBytes: []byte{},\n\t}\n\n\tlh := newTestLighthouse()\n\tifce := &Interface{\n\t\thostMap:          hostMap,\n\t\tinside:           &test.NoopTun{},\n\t\toutside:          &udp.NoopConn{},\n\t\tfirewall:         &Firewall{},\n\t\tlightHouse:       lh,\n\t\thandshakeManager: NewHandshakeManager(l, hostMap, lh, &udp.NoopConn{}, defaultHandshakeConfig),\n\t\tl:                l,\n\t\tpki:              &PKI{},\n\t}\n\tifce.pki.cs.Store(cs)\n\tifce.pki.caPool.Store(ncp)\n\tifce.disconnectInvalid.Store(true)\n\n\t// Create manager\n\tconf := config.NewC(l)\n\tpunchy := NewPunchyFromConfig(l, conf)\n\tnc := newConnectionManagerFromConfig(l, conf, hostMap, punchy)\n\tnc.intf = ifce\n\tifce.connectionManager = nc\n\n\thostinfo := &HostInfo{\n\t\tvpnAddrs: []netip.Addr{vpnIp},\n\t\tConnectionState: &ConnectionState{\n\t\t\tmyCert:   &dummyCert{},\n\t\t\tpeerCert: cachedPeerCert,\n\t\t\tH:        &noise.HandshakeState{},\n\t\t},\n\t}\n\tnc.hostMap.unlockedAddHostInfo(hostinfo, ifce)\n\n\t// Move ahead 45s.\n\t// Check if to disconnect with invalid certificate.\n\t// Should be alive.\n\tnextTick := now.Add(45 * time.Second)\n\tinvalid := nc.isInvalidCertificate(nextTick, hostinfo)\n\tassert.False(t, invalid)\n\n\t// Move ahead 61s.\n\t// Check if to disconnect with invalid certificate.\n\t// Should be disconnected.\n\tnextTick = now.Add(61 * time.Second)\n\tinvalid = nc.isInvalidCertificate(nextTick, hostinfo)\n\tassert.True(t, invalid)\n}\n\ntype dummyCert struct {\n\tversion        cert.Version\n\tcurve          cert.Curve\n\tgroups         []string\n\tisCa           bool\n\tissuer         string\n\tname           string\n\tnetworks       []netip.Prefix\n\tnotAfter       time.Time\n\tnotBefore      time.Time\n\tpublicKey      []byte\n\tsignature      []byte\n\tunsafeNetworks []netip.Prefix\n}\n\nfunc (d *dummyCert) Version() cert.Version {\n\treturn d.version\n}\n\nfunc (d *dummyCert) Curve() cert.Curve {\n\treturn d.curve\n}\n\nfunc (d *dummyCert) Groups() []string {\n\treturn d.groups\n}\n\nfunc (d *dummyCert) IsCA() bool {\n\treturn d.isCa\n}\n\nfunc (d *dummyCert) Issuer() string {\n\treturn d.issuer\n}\n\nfunc (d *dummyCert) Name() string {\n\treturn d.name\n}\n\nfunc (d *dummyCert) Networks() []netip.Prefix {\n\treturn d.networks\n}\n\nfunc (d *dummyCert) NotAfter() time.Time {\n\treturn d.notAfter\n}\n\nfunc (d *dummyCert) NotBefore() time.Time {\n\treturn d.notBefore\n}\n\nfunc (d *dummyCert) PublicKey() []byte {\n\treturn d.publicKey\n}\n\nfunc (d *dummyCert) MarshalPublicKeyPEM() []byte {\n\treturn cert.MarshalPublicKeyToPEM(d.curve, d.publicKey)\n}\n\nfunc (d *dummyCert) Signature() []byte {\n\treturn d.signature\n}\n\nfunc (d *dummyCert) UnsafeNetworks() []netip.Prefix {\n\treturn d.unsafeNetworks\n}\n\nfunc (d *dummyCert) MarshalForHandshakes() ([]byte, error) {\n\treturn nil, nil\n}\n\nfunc (d *dummyCert) Sign(curve cert.Curve, key []byte) error {\n\treturn nil\n}\n\nfunc (d *dummyCert) CheckSignature(key []byte) bool {\n\treturn true\n}\n\nfunc (d *dummyCert) Expired(t time.Time) bool {\n\treturn false\n}\n\nfunc (d *dummyCert) CheckRootConstraints(signer cert.Certificate) error {\n\treturn nil\n}\n\nfunc (d *dummyCert) VerifyPrivateKey(curve cert.Curve, key []byte) error {\n\treturn nil\n}\n\nfunc (d *dummyCert) String() string {\n\treturn \"\"\n}\n\nfunc (d *dummyCert) Marshal() ([]byte, error) {\n\treturn nil, nil\n}\n\nfunc (d *dummyCert) MarshalPEM() ([]byte, error) {\n\treturn nil, nil\n}\n\nfunc (d *dummyCert) Fingerprint() (string, error) {\n\treturn \"\", nil\n}\n\nfunc (d *dummyCert) MarshalJSON() ([]byte, error) {\n\treturn nil, nil\n}\n\nfunc (d *dummyCert) Copy() cert.Certificate {\n\treturn d\n}\n"
  },
  {
    "path": "connection_state.go",
    "content": "package nebula\n\nimport (\n\t\"crypto/rand\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"sync\"\n\t\"sync/atomic\"\n\n\t\"github.com/flynn/noise\"\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/cert\"\n\t\"github.com/slackhq/nebula/noiseutil\"\n)\n\nconst ReplayWindow = 1024\n\ntype ConnectionState struct {\n\teKey           *NebulaCipherState\n\tdKey           *NebulaCipherState\n\tH              *noise.HandshakeState\n\tmyCert         cert.Certificate\n\tpeerCert       *cert.CachedCertificate\n\tinitiator      bool\n\tmessageCounter atomic.Uint64\n\twindow         *Bits\n\twriteLock      sync.Mutex\n}\n\nfunc NewConnectionState(l *logrus.Logger, cs *CertState, crt cert.Certificate, initiator bool, pattern noise.HandshakePattern) (*ConnectionState, error) {\n\tvar dhFunc noise.DHFunc\n\tswitch crt.Curve() {\n\tcase cert.Curve_CURVE25519:\n\t\tdhFunc = noise.DH25519\n\tcase cert.Curve_P256:\n\t\tif cs.pkcs11Backed {\n\t\t\tdhFunc = noiseutil.DHP256PKCS11\n\t\t} else {\n\t\t\tdhFunc = noiseutil.DHP256\n\t\t}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"invalid curve: %s\", crt.Curve())\n\t}\n\n\tvar ncs noise.CipherSuite\n\tif cs.cipher == \"chachapoly\" {\n\t\tncs = noise.NewCipherSuite(dhFunc, noise.CipherChaChaPoly, noise.HashSHA256)\n\t} else {\n\t\tncs = noise.NewCipherSuite(dhFunc, noiseutil.CipherAESGCM, noise.HashSHA256)\n\t}\n\n\tstatic := noise.DHKey{Private: cs.privateKey, Public: crt.PublicKey()}\n\ths, err := noise.NewHandshakeState(noise.Config{\n\t\tCipherSuite:   ncs,\n\t\tRandom:        rand.Reader,\n\t\tPattern:       pattern,\n\t\tInitiator:     initiator,\n\t\tStaticKeypair: static,\n\t\t//NOTE: These should come from CertState (pki.go) when we finally implement it\n\t\tPresharedKey:          []byte{},\n\t\tPresharedKeyPlacement: 0,\n\t})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"NewConnectionState: %s\", err)\n\t}\n\n\t// The queue and ready params prevent a counter race that would happen when\n\t// sending stored packets and simultaneously accepting new traffic.\n\tci := &ConnectionState{\n\t\tH:         hs,\n\t\tinitiator: initiator,\n\t\twindow:    NewBits(ReplayWindow),\n\t\tmyCert:    crt,\n\t}\n\t// always start the counter from 2, as packet 1 and packet 2 are handshake packets.\n\tci.messageCounter.Add(2)\n\n\treturn ci, nil\n}\n\nfunc (cs *ConnectionState) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(m{\n\t\t\"certificate\":     cs.peerCert,\n\t\t\"initiator\":       cs.initiator,\n\t\t\"message_counter\": cs.messageCounter.Load(),\n\t})\n}\n\nfunc (cs *ConnectionState) Curve() cert.Curve {\n\treturn cs.myCert.Curve()\n}\n"
  },
  {
    "path": "control.go",
    "content": "package nebula\n\nimport (\n\t\"context\"\n\t\"net/netip\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/cert\"\n\t\"github.com/slackhq/nebula/header\"\n\t\"github.com/slackhq/nebula/overlay\"\n)\n\n// Every interaction here needs to take extra care to copy memory and not return or use arguments \"as is\" when touching\n// core. This means copying IP objects, slices, de-referencing pointers and taking the actual value, etc\n\ntype controlEach func(h *HostInfo)\n\ntype controlHostLister interface {\n\tQueryVpnAddr(vpnAddr netip.Addr) *HostInfo\n\tForEachIndex(each controlEach)\n\tForEachVpnAddr(each controlEach)\n\tGetPreferredRanges() []netip.Prefix\n}\n\ntype Control struct {\n\tf                      *Interface\n\tl                      *logrus.Logger\n\tctx                    context.Context\n\tcancel                 context.CancelFunc\n\tsshStart               func()\n\tstatsStart             func()\n\tdnsStart               func()\n\tlighthouseStart        func()\n\tconnectionManagerStart func(context.Context)\n}\n\ntype ControlHostInfo struct {\n\tVpnAddrs               []netip.Addr     `json:\"vpnAddrs\"`\n\tLocalIndex             uint32           `json:\"localIndex\"`\n\tRemoteIndex            uint32           `json:\"remoteIndex\"`\n\tRemoteAddrs            []netip.AddrPort `json:\"remoteAddrs\"`\n\tCert                   cert.Certificate `json:\"cert\"`\n\tMessageCounter         uint64           `json:\"messageCounter\"`\n\tCurrentRemote          netip.AddrPort   `json:\"currentRemote\"`\n\tCurrentRelaysToMe      []netip.Addr     `json:\"currentRelaysToMe\"`\n\tCurrentRelaysThroughMe []netip.Addr     `json:\"currentRelaysThroughMe\"`\n}\n\n// Start actually runs nebula, this is a nonblocking call. To block use Control.ShutdownBlock()\nfunc (c *Control) Start() {\n\t// Activate the interface\n\tc.f.activate()\n\n\t// Call all the delayed funcs that waited patiently for the interface to be created.\n\tif c.sshStart != nil {\n\t\tgo c.sshStart()\n\t}\n\tif c.statsStart != nil {\n\t\tgo c.statsStart()\n\t}\n\tif c.dnsStart != nil {\n\t\tgo c.dnsStart()\n\t}\n\tif c.connectionManagerStart != nil {\n\t\tgo c.connectionManagerStart(c.ctx)\n\t}\n\tif c.lighthouseStart != nil {\n\t\tc.lighthouseStart()\n\t}\n\n\t// Start reading packets.\n\tc.f.run()\n}\n\nfunc (c *Control) Context() context.Context {\n\treturn c.ctx\n}\n\n// Stop signals nebula to shutdown and close all tunnels, returns after the shutdown is complete\nfunc (c *Control) Stop() {\n\t// Stop the handshakeManager (and other services), to prevent new tunnels from\n\t// being created while we're shutting them all down.\n\tc.cancel()\n\n\tc.CloseAllTunnels(false)\n\tif err := c.f.Close(); err != nil {\n\t\tc.l.WithError(err).Error(\"Close interface failed\")\n\t}\n\tc.l.Info(\"Goodbye\")\n}\n\n// ShutdownBlock will listen for and block on term and interrupt signals, calling Control.Stop() once signalled\nfunc (c *Control) ShutdownBlock() {\n\tsigChan := make(chan os.Signal, 1)\n\tsignal.Notify(sigChan, syscall.SIGTERM)\n\tsignal.Notify(sigChan, syscall.SIGINT)\n\n\trawSig := <-sigChan\n\tsig := rawSig.String()\n\tc.l.WithField(\"signal\", sig).Info(\"Caught signal, shutting down\")\n\tc.Stop()\n}\n\n// RebindUDPServer asks the UDP listener to rebind it's listener. Mainly used on mobile clients when interfaces change\nfunc (c *Control) RebindUDPServer() {\n\t_ = c.f.outside.Rebind()\n\n\t// Trigger a lighthouse update, useful for mobile clients that should have an update interval of 0\n\tc.f.lightHouse.SendUpdate()\n\n\t// Let the main interface know that we rebound so that underlying tunnels know to trigger punches from their remotes\n\tc.f.rebindCount++\n}\n\n// ListHostmapHosts returns details about the actual or pending (handshaking) hostmap by vpn ip\nfunc (c *Control) ListHostmapHosts(pendingMap bool) []ControlHostInfo {\n\tif pendingMap {\n\t\treturn listHostMapHosts(c.f.handshakeManager)\n\t} else {\n\t\treturn listHostMapHosts(c.f.hostMap)\n\t}\n}\n\n// ListHostmapIndexes returns details about the actual or pending (handshaking) hostmap by local index id\nfunc (c *Control) ListHostmapIndexes(pendingMap bool) []ControlHostInfo {\n\tif pendingMap {\n\t\treturn listHostMapIndexes(c.f.handshakeManager)\n\t} else {\n\t\treturn listHostMapIndexes(c.f.hostMap)\n\t}\n}\n\n// GetCertByVpnIp returns the authenticated certificate of the given vpn IP, or nil if not found\nfunc (c *Control) GetCertByVpnIp(vpnIp netip.Addr) cert.Certificate {\n\tif c.f.myVpnAddrsTable.Contains(vpnIp) {\n\t\t// Only returning the default certificate since its impossible\n\t\t// for any other host but ourselves to have more than 1\n\t\treturn c.f.pki.getCertState().GetDefaultCertificate().Copy()\n\t}\n\thi := c.f.hostMap.QueryVpnAddr(vpnIp)\n\tif hi == nil {\n\t\treturn nil\n\t}\n\treturn hi.GetCert().Certificate.Copy()\n}\n\n// CreateTunnel creates a new tunnel to the given vpn ip.\nfunc (c *Control) CreateTunnel(vpnIp netip.Addr) {\n\tc.f.handshakeManager.StartHandshake(vpnIp, nil)\n}\n\n// PrintTunnel creates a new tunnel to the given vpn ip.\nfunc (c *Control) PrintTunnel(vpnIp netip.Addr) *ControlHostInfo {\n\thi := c.f.hostMap.QueryVpnAddr(vpnIp)\n\tif hi == nil {\n\t\treturn nil\n\t}\n\tchi := copyHostInfo(hi, c.f.hostMap.GetPreferredRanges())\n\treturn &chi\n}\n\n// QueryLighthouse queries the lighthouse.\nfunc (c *Control) QueryLighthouse(vpnIp netip.Addr) *CacheMap {\n\thi := c.f.lightHouse.Query(vpnIp)\n\tif hi == nil {\n\t\treturn nil\n\t}\n\treturn hi.CopyCache()\n}\n\n// GetHostInfoByVpnAddr returns a single tunnels hostInfo, or nil if not found\n// Caller should take care to Unmap() any 4in6 addresses prior to calling.\nfunc (c *Control) GetHostInfoByVpnAddr(vpnAddr netip.Addr, pending bool) *ControlHostInfo {\n\tvar hl controlHostLister\n\tif pending {\n\t\thl = c.f.handshakeManager\n\t} else {\n\t\thl = c.f.hostMap\n\t}\n\n\th := hl.QueryVpnAddr(vpnAddr)\n\tif h == nil {\n\t\treturn nil\n\t}\n\n\tch := copyHostInfo(h, c.f.hostMap.GetPreferredRanges())\n\treturn &ch\n}\n\n// SetRemoteForTunnel forces a tunnel to use a specific remote\n// Caller should take care to Unmap() any 4in6 addresses prior to calling.\nfunc (c *Control) SetRemoteForTunnel(vpnIp netip.Addr, addr netip.AddrPort) *ControlHostInfo {\n\thostInfo := c.f.hostMap.QueryVpnAddr(vpnIp)\n\tif hostInfo == nil {\n\t\treturn nil\n\t}\n\n\thostInfo.SetRemote(addr)\n\tch := copyHostInfo(hostInfo, c.f.hostMap.GetPreferredRanges())\n\treturn &ch\n}\n\n// CloseTunnel closes a fully established tunnel. If localOnly is false it will notify the remote end as well.\n// Caller should take care to Unmap() any 4in6 addresses prior to calling.\nfunc (c *Control) CloseTunnel(vpnIp netip.Addr, localOnly bool) bool {\n\thostInfo := c.f.hostMap.QueryVpnAddr(vpnIp)\n\tif hostInfo == nil {\n\t\treturn false\n\t}\n\n\tif !localOnly {\n\t\tc.f.send(\n\t\t\theader.CloseTunnel,\n\t\t\t0,\n\t\t\thostInfo.ConnectionState,\n\t\t\thostInfo,\n\t\t\t[]byte{},\n\t\t\tmake([]byte, 12, 12),\n\t\t\tmake([]byte, mtu),\n\t\t)\n\t}\n\n\tc.f.closeTunnel(hostInfo)\n\treturn true\n}\n\n// CloseAllTunnels is just like CloseTunnel except it goes through and shuts them all down, optionally you can avoid shutting down lighthouse tunnels\n// the int returned is a count of tunnels closed\nfunc (c *Control) CloseAllTunnels(excludeLighthouses bool) (closed int) {\n\tshutdown := func(h *HostInfo) {\n\t\tif excludeLighthouses && c.f.lightHouse.IsAnyLighthouseAddr(h.vpnAddrs) {\n\t\t\treturn\n\t\t}\n\t\tc.f.send(header.CloseTunnel, 0, h.ConnectionState, h, []byte{}, make([]byte, 12, 12), make([]byte, mtu))\n\t\tc.f.closeTunnel(h)\n\n\t\tc.l.WithField(\"vpnAddrs\", h.vpnAddrs).WithField(\"udpAddr\", h.remote).\n\t\t\tDebug(\"Sending close tunnel message\")\n\t\tclosed++\n\t}\n\n\t// Learn which hosts are being used as relays, so we can shut them down last.\n\trelayingHosts := map[netip.Addr]*HostInfo{}\n\t// Grab the hostMap lock to access the Relays map\n\tc.f.hostMap.Lock()\n\tfor _, relayingHost := range c.f.hostMap.Relays {\n\t\trelayingHosts[relayingHost.vpnAddrs[0]] = relayingHost\n\t}\n\tc.f.hostMap.Unlock()\n\n\thostInfos := []*HostInfo{}\n\t// Grab the hostMap lock to access the Hosts map\n\tc.f.hostMap.Lock()\n\tfor _, relayHost := range c.f.hostMap.Indexes {\n\t\tif _, ok := relayingHosts[relayHost.vpnAddrs[0]]; !ok {\n\t\t\thostInfos = append(hostInfos, relayHost)\n\t\t}\n\t}\n\tc.f.hostMap.Unlock()\n\n\tfor _, h := range hostInfos {\n\t\tshutdown(h)\n\t}\n\tfor _, h := range relayingHosts {\n\t\tshutdown(h)\n\t}\n\treturn\n}\n\nfunc (c *Control) Device() overlay.Device {\n\treturn c.f.inside\n}\n\nfunc copyHostInfo(h *HostInfo, preferredRanges []netip.Prefix) ControlHostInfo {\n\tchi := ControlHostInfo{\n\t\tVpnAddrs:               make([]netip.Addr, len(h.vpnAddrs)),\n\t\tLocalIndex:             h.localIndexId,\n\t\tRemoteIndex:            h.remoteIndexId,\n\t\tRemoteAddrs:            h.remotes.CopyAddrs(preferredRanges),\n\t\tCurrentRelaysToMe:      h.relayState.CopyRelayIps(),\n\t\tCurrentRelaysThroughMe: h.relayState.CopyRelayForIps(),\n\t\tCurrentRemote:          h.remote,\n\t}\n\n\tfor i, a := range h.vpnAddrs {\n\t\tchi.VpnAddrs[i] = a\n\t}\n\n\tif h.ConnectionState != nil {\n\t\tchi.MessageCounter = h.ConnectionState.messageCounter.Load()\n\t}\n\n\tif c := h.GetCert(); c != nil {\n\t\tchi.Cert = c.Certificate.Copy()\n\t}\n\n\treturn chi\n}\n\nfunc listHostMapHosts(hl controlHostLister) []ControlHostInfo {\n\thosts := make([]ControlHostInfo, 0)\n\tpr := hl.GetPreferredRanges()\n\thl.ForEachVpnAddr(func(hostinfo *HostInfo) {\n\t\thosts = append(hosts, copyHostInfo(hostinfo, pr))\n\t})\n\treturn hosts\n}\n\nfunc listHostMapIndexes(hl controlHostLister) []ControlHostInfo {\n\thosts := make([]ControlHostInfo, 0)\n\tpr := hl.GetPreferredRanges()\n\thl.ForEachIndex(func(hostinfo *HostInfo) {\n\t\thosts = append(hosts, copyHostInfo(hostinfo, pr))\n\t})\n\treturn hosts\n}\n"
  },
  {
    "path": "control_test.go",
    "content": "package nebula\n\nimport (\n\t\"net\"\n\t\"net/netip\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/cert\"\n\t\"github.com/slackhq/nebula/test\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestControl_GetHostInfoByVpnIp(t *testing.T) {\n\t//TODO: CERT-V2 with multiple certificate versions we have a problem with this test\n\t// Some certs versions have different characteristics and each version implements their own Copy() func\n\t// which means this is not a good place to test for exposing memory\n\tl := test.NewLogger()\n\t// Special care must be taken to re-use all objects provided to the hostmap and certificate in the expectedInfo object\n\t// To properly ensure we are not exposing core memory to the caller\n\thm := newHostMap(l)\n\thm.preferredRanges.Store(&[]netip.Prefix{})\n\n\tremote1 := netip.MustParseAddrPort(\"0.0.0.100:4444\")\n\tremote2 := netip.MustParseAddrPort(\"[1:2:3:4:5:6:7:8]:4444\")\n\n\tipNet := net.IPNet{\n\t\tIP:   remote1.Addr().AsSlice(),\n\t\tMask: net.IPMask{255, 255, 255, 0},\n\t}\n\n\tipNet2 := net.IPNet{\n\t\tIP:   remote2.Addr().AsSlice(),\n\t\tMask: net.IPMask{255, 255, 255, 0},\n\t}\n\n\tremotes := NewRemoteList([]netip.Addr{netip.IPv4Unspecified()}, nil)\n\tremotes.unlockedPrependV4(netip.IPv4Unspecified(), netAddrToProtoV4AddrPort(remote1.Addr(), remote1.Port()))\n\tremotes.unlockedPrependV6(netip.IPv4Unspecified(), netAddrToProtoV6AddrPort(remote2.Addr(), remote2.Port()))\n\n\tvpnIp, ok := netip.AddrFromSlice(ipNet.IP)\n\tassert.True(t, ok)\n\n\tcrt := &dummyCert{}\n\thm.unlockedAddHostInfo(&HostInfo{\n\t\tremote:  remote1,\n\t\tremotes: remotes,\n\t\tConnectionState: &ConnectionState{\n\t\t\tpeerCert: &cert.CachedCertificate{Certificate: crt},\n\t\t},\n\t\tremoteIndexId: 200,\n\t\tlocalIndexId:  201,\n\t\tvpnAddrs:      []netip.Addr{vpnIp},\n\t\trelayState: RelayState{\n\t\t\trelays:         nil,\n\t\t\trelayForByAddr: map[netip.Addr]*Relay{},\n\t\t\trelayForByIdx:  map[uint32]*Relay{},\n\t\t},\n\t}, &Interface{})\n\n\tvpnIp2, ok := netip.AddrFromSlice(ipNet2.IP)\n\tassert.True(t, ok)\n\n\thm.unlockedAddHostInfo(&HostInfo{\n\t\tremote:  remote1,\n\t\tremotes: remotes,\n\t\tConnectionState: &ConnectionState{\n\t\t\tpeerCert: nil,\n\t\t},\n\t\tremoteIndexId: 200,\n\t\tlocalIndexId:  201,\n\t\tvpnAddrs:      []netip.Addr{vpnIp2},\n\t\trelayState: RelayState{\n\t\t\trelays:         nil,\n\t\t\trelayForByAddr: map[netip.Addr]*Relay{},\n\t\t\trelayForByIdx:  map[uint32]*Relay{},\n\t\t},\n\t}, &Interface{})\n\n\tc := Control{\n\t\tf: &Interface{\n\t\t\thostMap: hm,\n\t\t},\n\t\tl: logrus.New(),\n\t}\n\n\tthi := c.GetHostInfoByVpnAddr(vpnIp, false)\n\n\texpectedInfo := ControlHostInfo{\n\t\tVpnAddrs:               []netip.Addr{vpnIp},\n\t\tLocalIndex:             201,\n\t\tRemoteIndex:            200,\n\t\tRemoteAddrs:            []netip.AddrPort{remote2, remote1},\n\t\tCert:                   crt.Copy(),\n\t\tMessageCounter:         0,\n\t\tCurrentRemote:          remote1,\n\t\tCurrentRelaysToMe:      []netip.Addr{},\n\t\tCurrentRelaysThroughMe: []netip.Addr{},\n\t}\n\n\t// Make sure we don't have any unexpected fields\n\tassertFields(t, []string{\"VpnAddrs\", \"LocalIndex\", \"RemoteIndex\", \"RemoteAddrs\", \"Cert\", \"MessageCounter\", \"CurrentRemote\", \"CurrentRelaysToMe\", \"CurrentRelaysThroughMe\"}, thi)\n\tassert.Equal(t, &expectedInfo, thi)\n\ttest.AssertDeepCopyEqual(t, &expectedInfo, thi)\n\n\t// Make sure we don't panic if the host info doesn't have a cert yet\n\tassert.NotPanics(t, func() {\n\t\tthi = c.GetHostInfoByVpnAddr(vpnIp2, false)\n\t})\n}\n\nfunc assertFields(t *testing.T, expected []string, actualStruct any) {\n\tval := reflect.ValueOf(actualStruct).Elem()\n\tfields := make([]string, val.NumField())\n\tfor i := 0; i < val.NumField(); i++ {\n\t\tfields[i] = val.Type().Field(i).Name\n\t}\n\n\tassert.Equal(t, expected, fields)\n}\n"
  },
  {
    "path": "control_tester.go",
    "content": "//go:build e2e_testing\n\npackage nebula\n\nimport (\n\t\"net/netip\"\n\n\t\"github.com/google/gopacket\"\n\t\"github.com/google/gopacket/layers\"\n\t\"github.com/slackhq/nebula/header\"\n\t\"github.com/slackhq/nebula/overlay\"\n\t\"github.com/slackhq/nebula/udp\"\n)\n\n// WaitForType will pipe all messages from this control device into the pipeTo control device\n// returning after a message matching the criteria has been piped\nfunc (c *Control) WaitForType(msgType header.MessageType, subType header.MessageSubType, pipeTo *Control) {\n\th := &header.H{}\n\tfor {\n\t\tp := c.f.outside.(*udp.TesterConn).Get(true)\n\t\tif err := h.Parse(p.Data); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tpipeTo.InjectUDPPacket(p)\n\t\tif h.Type == msgType && h.Subtype == subType {\n\t\t\treturn\n\t\t}\n\t}\n}\n\n// WaitForTypeByIndex is similar to WaitForType except it adds an index check\n// Useful if you have many nodes communicating and want to wait to find a specific nodes packet\nfunc (c *Control) WaitForTypeByIndex(toIndex uint32, msgType header.MessageType, subType header.MessageSubType, pipeTo *Control) {\n\th := &header.H{}\n\tfor {\n\t\tp := c.f.outside.(*udp.TesterConn).Get(true)\n\t\tif err := h.Parse(p.Data); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tpipeTo.InjectUDPPacket(p)\n\t\tif h.RemoteIndex == toIndex && h.Type == msgType && h.Subtype == subType {\n\t\t\treturn\n\t\t}\n\t}\n}\n\n// InjectLightHouseAddr will push toAddr into the local lighthouse cache for the vpnIp\n// This is necessary if you did not configure static hosts or are not running a lighthouse\nfunc (c *Control) InjectLightHouseAddr(vpnIp netip.Addr, toAddr netip.AddrPort) {\n\tc.f.lightHouse.Lock()\n\tremoteList := c.f.lightHouse.unlockedGetRemoteList([]netip.Addr{vpnIp})\n\tremoteList.Lock()\n\tdefer remoteList.Unlock()\n\tc.f.lightHouse.Unlock()\n\n\tif toAddr.Addr().Is4() {\n\t\tremoteList.unlockedPrependV4(vpnIp, netAddrToProtoV4AddrPort(toAddr.Addr(), toAddr.Port()))\n\t} else {\n\t\tremoteList.unlockedPrependV6(vpnIp, netAddrToProtoV6AddrPort(toAddr.Addr(), toAddr.Port()))\n\t}\n}\n\n// InjectRelays will push relayVpnIps into the local lighthouse cache for the vpnIp\n// This is necessary to inform an initiator of possible relays for communicating with a responder\nfunc (c *Control) InjectRelays(vpnIp netip.Addr, relayVpnIps []netip.Addr) {\n\tc.f.lightHouse.Lock()\n\tremoteList := c.f.lightHouse.unlockedGetRemoteList([]netip.Addr{vpnIp})\n\tremoteList.Lock()\n\tdefer remoteList.Unlock()\n\tc.f.lightHouse.Unlock()\n\n\tremoteList.unlockedSetRelay(vpnIp, relayVpnIps)\n}\n\n// GetFromTun will pull a packet off the tun side of nebula\nfunc (c *Control) GetFromTun(block bool) []byte {\n\treturn c.f.inside.(*overlay.TestTun).Get(block)\n}\n\n// GetFromUDP will pull a udp packet off the udp side of nebula\nfunc (c *Control) GetFromUDP(block bool) *udp.Packet {\n\treturn c.f.outside.(*udp.TesterConn).Get(block)\n}\n\nfunc (c *Control) GetUDPTxChan() <-chan *udp.Packet {\n\treturn c.f.outside.(*udp.TesterConn).TxPackets\n}\n\nfunc (c *Control) GetTunTxChan() <-chan []byte {\n\treturn c.f.inside.(*overlay.TestTun).TxPackets\n}\n\n// InjectUDPPacket will inject a packet into the udp side of nebula\nfunc (c *Control) InjectUDPPacket(p *udp.Packet) {\n\tc.f.outside.(*udp.TesterConn).Send(p)\n}\n\n// InjectTunUDPPacket puts a udp packet on the tun interface. Using UDP here because it's a simpler protocol\nfunc (c *Control) InjectTunUDPPacket(toAddr netip.Addr, toPort uint16, fromAddr netip.Addr, fromPort uint16, data []byte) {\n\tserialize := make([]gopacket.SerializableLayer, 0)\n\tvar netLayer gopacket.NetworkLayer\n\tif toAddr.Is6() {\n\t\tif !fromAddr.Is6() {\n\t\t\tpanic(\"Cant send ipv6 to ipv4\")\n\t\t}\n\t\tip := &layers.IPv6{\n\t\t\tVersion:    6,\n\t\t\tNextHeader: layers.IPProtocolUDP,\n\t\t\tSrcIP:      fromAddr.Unmap().AsSlice(),\n\t\t\tDstIP:      toAddr.Unmap().AsSlice(),\n\t\t}\n\t\tserialize = append(serialize, ip)\n\t\tnetLayer = ip\n\t} else {\n\t\tif !fromAddr.Is4() {\n\t\t\tpanic(\"Cant send ipv4 to ipv6\")\n\t\t}\n\n\t\tip := &layers.IPv4{\n\t\t\tVersion:  4,\n\t\t\tTTL:      64,\n\t\t\tProtocol: layers.IPProtocolUDP,\n\t\t\tSrcIP:    fromAddr.Unmap().AsSlice(),\n\t\t\tDstIP:    toAddr.Unmap().AsSlice(),\n\t\t}\n\t\tserialize = append(serialize, ip)\n\t\tnetLayer = ip\n\t}\n\n\tudp := layers.UDP{\n\t\tSrcPort: layers.UDPPort(fromPort),\n\t\tDstPort: layers.UDPPort(toPort),\n\t}\n\terr := udp.SetNetworkLayerForChecksum(netLayer)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tbuffer := gopacket.NewSerializeBuffer()\n\topt := gopacket.SerializeOptions{\n\t\tComputeChecksums: true,\n\t\tFixLengths:       true,\n\t}\n\n\tserialize = append(serialize, &udp, gopacket.Payload(data))\n\terr = gopacket.SerializeLayers(buffer, opt, serialize...)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tc.f.inside.(*overlay.TestTun).Send(buffer.Bytes())\n}\n\nfunc (c *Control) GetVpnAddrs() []netip.Addr {\n\treturn c.f.myVpnAddrs\n}\n\nfunc (c *Control) GetUDPAddr() netip.AddrPort {\n\treturn c.f.outside.(*udp.TesterConn).Addr\n}\n\nfunc (c *Control) KillPendingTunnel(vpnIp netip.Addr) bool {\n\thostinfo := c.f.handshakeManager.QueryVpnAddr(vpnIp)\n\tif hostinfo == nil {\n\t\treturn false\n\t}\n\n\tc.f.handshakeManager.DeleteHostInfo(hostinfo)\n\treturn true\n}\n\nfunc (c *Control) GetHostmap() *HostMap {\n\treturn c.f.hostMap\n}\n\nfunc (c *Control) GetF() *Interface {\n\treturn c.f\n}\n\nfunc (c *Control) GetCertState() *CertState {\n\treturn c.f.pki.getCertState()\n}\n\nfunc (c *Control) ReHandshake(vpnIp netip.Addr) {\n\tc.f.handshakeManager.StartHandshake(vpnIp, nil)\n}\n"
  },
  {
    "path": "dist/windows/wintun/LICENSE.txt",
    "content": "Prebuilt Binaries License\r\n-------------------------\r\n\r\n1. DEFINITIONS. \"Software\" means the precise contents of the \"wintun.dll\"\r\n   files that are included in the .zip file that contains this document as\r\n   downloaded from wintun.net/builds.\r\n\r\n2. LICENSE GRANT. WireGuard LLC grants to you a non-exclusive and\r\n   non-transferable right to use Software for lawful purposes under certain\r\n   obligations and limited rights as set forth in this agreement.\r\n\r\n3. RESTRICTIONS. Software is owned and copyrighted by WireGuard LLC. It is\r\n   licensed, not sold. Title to Software and all associated intellectual\r\n   property rights are retained by WireGuard. You must not:\r\n   a. reverse engineer, decompile, disassemble, extract from, or otherwise\r\n      modify the Software;\r\n   b. modify or create derivative work based upon Software in whole or in\r\n      parts, except insofar as only the API interfaces of the \"wintun.h\" file\r\n      distributed alongside the Software (the \"Permitted API\") are used;\r\n   c. remove any proprietary notices, labels, or copyrights from the Software;\r\n   d. resell, redistribute, lease, rent, transfer, sublicense, or otherwise\r\n      transfer rights of the Software without the prior written consent of\r\n      WireGuard LLC, except insofar as the Software is distributed alongside\r\n      other software that uses the Software only via the Permitted API;\r\n   e. use the name of WireGuard LLC, the WireGuard project, the Wintun\r\n      project, or the names of its contributors to endorse or promote products\r\n      derived from the Software without specific prior written consent.\r\n\r\n4. LIMITED WARRANTY. THE SOFTWARE IS PROVIDED \"AS IS\" AND WITHOUT WARRANTY OF\r\n   ANY KIND. WIREGUARD LLC HEREBY EXCLUDES AND DISCLAIMS ALL IMPLIED OR\r\n   STATUTORY WARRANTIES, INCLUDING ANY WARRANTIES OF MERCHANTABILITY, FITNESS\r\n   FOR A PARTICULAR PURPOSE, QUALITY, NON-INFRINGEMENT, TITLE, RESULTS,\r\n   EFFORTS, OR QUIET ENJOYMENT. THERE IS NO WARRANTY THAT THE PRODUCT WILL BE\r\n   ERROR-FREE OR WILL FUNCTION WITHOUT INTERRUPTION. YOU ASSUME THE ENTIRE\r\n   RISK FOR THE RESULTS OBTAINED USING THE PRODUCT. TO THE EXTENT THAT\r\n   WIREGUARD LLC MAY NOT DISCLAIM ANY WARRANTY AS A MATTER OF APPLICABLE LAW,\r\n   THE SCOPE AND DURATION OF SUCH WARRANTY WILL BE THE MINIMUM PERMITTED UNDER\r\n   SUCH LAW. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND\r\n   WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR\r\n   A PARTICULAR PURPOSE OR NON-INFRINGEMENT ARE DISCLAIMED, EXCEPT TO THE\r\n   EXTENT THAT THESE DISCLAIMERS ARE HELD TO BE LEGALLY INVALID.\r\n\r\n5. LIMITATION OF LIABILITY. To the extent not prohibited by law, in no event\r\n   WireGuard LLC or any third-party-developer will be liable for any lost\r\n   revenue, profit or data or for special, indirect, consequential, incidental\r\n   or punitive damages, however caused regardless of the theory of liability,\r\n   arising out of or related to the use of or inability to use Software, even\r\n   if WireGuard LLC has been advised of the possibility of such damages.\r\n   Solely you are responsible for determining the appropriateness of using\r\n   Software and accept full responsibility for all risks associated with its\r\n   exercise of rights under this agreement, including but not limited to the\r\n   risks and costs of program errors, compliance with applicable laws, damage\r\n   to or loss of data, programs or equipment, and unavailability or\r\n   interruption of operations. The foregoing limitations will apply even if\r\n   the above stated warranty fails of its essential purpose. You acknowledge,\r\n   that it is in the nature of software that software is complex and not\r\n   completely free of errors. In no event shall WireGuard LLC or any\r\n   third-party-developer be liable to you under any theory for any damages\r\n   suffered by you or any user of Software or for any special, incidental,\r\n   indirect, consequential or similar damages (including without limitation\r\n   damages for loss of business profits, business interruption, loss of\r\n   business information or any other pecuniary loss) arising out of the use or\r\n   inability to use Software, even if WireGuard LLC has been advised of the\r\n   possibility of such damages and regardless of the legal or quitable theory\r\n   (contract, tort, or otherwise) upon which the claim is based.\r\n\r\n6. TERMINATION. This agreement is affected until terminated. You may\r\n   terminate this agreement at any time. This agreement will terminate\r\n   immediately without notice from WireGuard LLC if you fail to comply with\r\n   the terms and conditions of this agreement. Upon termination, you must\r\n   delete Software and all copies of Software and cease all forms of\r\n   distribution of Software.\r\n\r\n7. SEVERABILITY. If any provision of this agreement is held to be\r\n   unenforceable, this agreement will remain in effect with the provision\r\n   omitted, unless omission would frustrate the intent of the parties, in\r\n   which case this agreement will immediately terminate.\r\n\r\n8. RESERVATION OF RIGHTS. All rights not expressly granted in this agreement\r\n   are reserved by WireGuard LLC. For example, WireGuard LLC reserves the\r\n   right at any time to cease development of Software, to alter distribution\r\n   details, features, specifications, capabilities, functions, licensing\r\n   terms, release dates, APIs, ABIs, general availability, or other\r\n   characteristics of the Software.\r\n"
  },
  {
    "path": "dist/windows/wintun/README.md",
    "content": "# [Wintun Network Adapter](https://www.wintun.net/)\r\n### TUN Device Driver for Windows\r\n\r\nThis is a layer 3 TUN driver for Windows 7, 8, 8.1, and 10. Originally created for [WireGuard](https://www.wireguard.com/), it is intended to be useful to a wide variety of projects that require layer 3 tunneling devices with implementations primarily in userspace.\r\n\r\n## Installation\r\n\r\nWintun is deployed as a platform-specific `wintun.dll` file. Install the `wintun.dll` file side-by-side with your application. Download the dll from [wintun.net](https://www.wintun.net/), alongside the header file for your application described below.\r\n\r\n## Usage\r\n\r\nInclude the [`wintun.h` file](https://git.zx2c4.com/wintun/tree/api/wintun.h) in your project simply by copying it there and dynamically load the `wintun.dll` using [`LoadLibraryEx()`](https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryexa) and [`GetProcAddress()`](https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getprocaddress) to resolve each function, using the typedefs provided in the header file. The [`InitializeWintun` function in the example.c code](https://git.zx2c4.com/wintun/tree/example/example.c) provides this in a function that you can simply copy and paste.\r\n\r\nWith the library setup, Wintun can then be used by first creating an adapter, configuring it, and then setting its status to \"up\". Adapters have names (e.g. \"OfficeNet\") and types (e.g. \"Wintun\").\r\n\r\n```C\r\nWINTUN_ADAPTER_HANDLE Adapter1 = WintunCreateAdapter(L\"OfficeNet\", L\"Wintun\", &SomeFixedGUID1);\r\nWINTUN_ADAPTER_HANDLE Adapter2 = WintunCreateAdapter(L\"HomeNet\", L\"Wintun\", &SomeFixedGUID2);\r\nWINTUN_ADAPTER_HANDLE Adapter3 = WintunCreateAdapter(L\"Data Center\", L\"Wintun\", &SomeFixedGUID3);\r\n```\r\n\r\nAfter creating an adapter, we can use it by starting a session:\r\n\r\n```C\r\nWINTUN_SESSION_HANDLE Session = WintunStartSession(Adapter2, 0x400000);\r\n```\r\n\r\nThen, the `WintunAllocateSendPacket` and `WintunSendPacket` functions can be used for sending packets ([used by `SendPackets` in the example.c code](https://git.zx2c4.com/wintun/tree/example/example.c)):\r\n\r\n```C\r\nBYTE *OutgoingPacket = WintunAllocateSendPacket(Session, PacketDataSize);\r\nif (OutgoingPacket)\r\n{\r\n    memcpy(OutgoingPacket, PacketData, PacketDataSize);\r\n    WintunSendPacket(Session, OutgoingPacket);\r\n}\r\nelse if (GetLastError() != ERROR_BUFFER_OVERFLOW) // Silently drop packets if the ring is full\r\n    Log(L\"Packet write failed\");\r\n```\r\n\r\nAnd the `WintunReceivePacket` and `WintunReleaseReceivePacket` functions can be used for receiving packets ([used by `ReceivePackets` in the example.c code](https://git.zx2c4.com/wintun/tree/example/example.c)):\r\n\r\n```C\r\nfor (;;)\r\n{\r\n    DWORD IncomingPacketSize;\r\n    BYTE *IncomingPacket = WintunReceivePacket(Session, &IncomingPacketSize);\r\n    if (IncomingPacket)\r\n    {\r\n        DoSomethingWithPacket(IncomingPacket, IncomingPacketSize);\r\n        WintunReleaseReceivePacket(Session, IncomingPacket);\r\n    }\r\n    else if (GetLastError() == ERROR_NO_MORE_ITEMS)\r\n        WaitForSingleObject(WintunGetReadWaitEvent(Session), INFINITE);\r\n    else\r\n    {\r\n        Log(L\"Packet read failed\");\r\n        break;\r\n    }\r\n}\r\n```\r\n\r\nSome high performance use cases may want to spin on `WintunReceivePackets` for a number of cycles before falling back to waiting on the read-wait event.\r\n\r\nYou are **highly encouraged** to read the [**example.c short example**](https://git.zx2c4.com/wintun/tree/example/example.c) to see how to put together a simple userspace network tunnel.\r\n\r\nThe various functions and definitions are [documented in the reference below](#Reference).\r\n\r\n## Reference\r\n\r\n### Macro Definitions\r\n\r\n#### WINTUN\\_MAX\\_POOL\r\n\r\n`#define WINTUN_MAX_POOL   256`\r\n\r\nMaximum pool name length including zero terminator\r\n\r\n#### WINTUN\\_MIN\\_RING\\_CAPACITY\r\n\r\n`#define WINTUN_MIN_RING_CAPACITY   0x20000 /* 128kiB */`\r\n\r\nMinimum ring capacity.\r\n\r\n#### WINTUN\\_MAX\\_RING\\_CAPACITY\r\n\r\n`#define WINTUN_MAX_RING_CAPACITY   0x4000000 /* 64MiB */`\r\n\r\nMaximum ring capacity.\r\n\r\n#### WINTUN\\_MAX\\_IP\\_PACKET\\_SIZE\r\n\r\n`#define WINTUN_MAX_IP_PACKET_SIZE   0xFFFF`\r\n\r\nMaximum IP packet size\r\n\r\n### Typedefs\r\n\r\n#### WINTUN\\_ADAPTER\\_HANDLE\r\n\r\n`typedef void* WINTUN_ADAPTER_HANDLE`\r\n\r\nA handle representing Wintun adapter\r\n\r\n#### WINTUN\\_ENUM\\_CALLBACK\r\n\r\n`typedef BOOL(* WINTUN_ENUM_CALLBACK) (WINTUN_ADAPTER_HANDLE Adapter, LPARAM Param)`\r\n\r\nCalled by WintunEnumAdapters for each adapter in the pool.\r\n\r\n**Parameters**\r\n\r\n- *Adapter*: Adapter handle, which will be freed when this function returns.\r\n- *Param*: An application-defined value passed to the WintunEnumAdapters.\r\n\r\n**Returns**\r\n\r\nNon-zero to continue iterating adapters; zero to stop.\r\n\r\n#### WINTUN\\_LOGGER\\_CALLBACK\r\n\r\n`typedef void(* WINTUN_LOGGER_CALLBACK) (WINTUN_LOGGER_LEVEL Level, DWORD64 Timestamp, const WCHAR *Message)`\r\n\r\nCalled by internal logger to report diagnostic messages\r\n\r\n**Parameters**\r\n\r\n- *Level*: Message level.\r\n- *Timestamp*: Message timestamp in in 100ns intervals since 1601-01-01 UTC.\r\n- *Message*: Message text.\r\n\r\n#### WINTUN\\_SESSION\\_HANDLE\r\n\r\n`typedef void* WINTUN_SESSION_HANDLE`\r\n\r\nA handle representing Wintun session\r\n\r\n### Enumeration Types\r\n\r\n#### WINTUN\\_LOGGER\\_LEVEL\r\n\r\n`enum WINTUN_LOGGER_LEVEL`\r\n\r\nDetermines the level of logging, passed to WINTUN\\_LOGGER\\_CALLBACK.\r\n\r\n- *WINTUN\\_LOG\\_INFO*: Informational\r\n- *WINTUN\\_LOG\\_WARN*: Warning\r\n- *WINTUN\\_LOG\\_ERR*: Error\r\n\r\nEnumerator\r\n\r\n### Functions\r\n\r\n#### WintunCreateAdapter()\r\n\r\n`WINTUN_ADAPTER_HANDLE WintunCreateAdapter (const WCHAR * Name, const WCHAR * TunnelType, const GUID * RequestedGUID)`\r\n\r\nCreates a new Wintun adapter.\r\n\r\n**Parameters**\r\n\r\n- *Name*: The requested name of the adapter. Zero-terminated string of up to MAX\\_ADAPTER\\_NAME-1 characters.\r\n- *Name*: Name of the adapter tunnel type. Zero-terminated string of up to MAX\\_ADAPTER\\_NAME-1 characters.\r\n- *RequestedGUID*: The GUID of the created network adapter, which then influences NLA generation deterministically. If it is set to NULL, the GUID is chosen by the system at random, and hence a new NLA entry is created for each new adapter. It is called \"requested\" GUID because the API it uses is completely undocumented, and so there could be minor interesting complications with its usage.\r\n\r\n**Returns**\r\n\r\nIf the function succeeds, the return value is the adapter handle. Must be released with WintunCloseAdapter. If the function fails, the return value is NULL. To get extended error information, call GetLastError.\r\n\r\n#### WintunOpenAdapter()\r\n\r\n`WINTUN_ADAPTER_HANDLE WintunOpenAdapter (const WCHAR * Name)`\r\n\r\nOpens an existing Wintun adapter.\r\n\r\n**Parameters**\r\n\r\n- *Name*: The requested name of the adapter. Zero-terminated string of up to MAX\\_ADAPTER\\_NAME-1 characters.\r\n\r\n**Returns**\r\n\r\nIf the function succeeds, the return value is adapter handle. Must be released with WintunCloseAdapter. If the function fails, the return value is NULL. To get extended error information, call GetLastError.\r\n\r\n#### WintunCloseAdapter()\r\n\r\n`void WintunCloseAdapter (WINTUN_ADAPTER_HANDLE Adapter)`\r\n\r\nReleases Wintun adapter resources and, if adapter was created with WintunCreateAdapter, removes adapter.\r\n\r\n**Parameters**\r\n\r\n- *Adapter*: Adapter handle obtained with WintunCreateAdapter or WintunOpenAdapter.\r\n\r\n#### WintunDeleteDriver()\r\n\r\n`BOOL WintunDeleteDriver ()`\r\n\r\nDeletes the Wintun driver if there are no more adapters in use.\r\n\r\n**Returns**\r\n\r\nIf the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To get extended error information, call GetLastError.\r\n\r\n#### WintunGetAdapterLuid()\r\n\r\n`void WintunGetAdapterLuid (WINTUN_ADAPTER_HANDLE Adapter, NET_LUID * Luid)`\r\n\r\nReturns the LUID of the adapter.\r\n\r\n**Parameters**\r\n\r\n- *Adapter*: Adapter handle obtained with WintunOpenAdapter or WintunCreateAdapter\r\n- *Luid*: Pointer to LUID to receive adapter LUID.\r\n\r\n#### WintunGetRunningDriverVersion()\r\n\r\n`DWORD WintunGetRunningDriverVersion (void )`\r\n\r\nDetermines the version of the Wintun driver currently loaded.\r\n\r\n**Returns**\r\n\r\nIf the function succeeds, the return value is the version number. If the function fails, the return value is zero. To get extended error information, call GetLastError. Possible errors include the following: ERROR\\_FILE\\_NOT\\_FOUND Wintun not loaded\r\n\r\n#### WintunSetLogger()\r\n\r\n`void WintunSetLogger (WINTUN_LOGGER_CALLBACK NewLogger)`\r\n\r\nSets logger callback function.\r\n\r\n**Parameters**\r\n\r\n- *NewLogger*: Pointer to callback function to use as a new global logger. NewLogger may be called from various threads concurrently. Should the logging require serialization, you must handle serialization in NewLogger. Set to NULL to disable.\r\n\r\n#### WintunStartSession()\r\n\r\n`WINTUN_SESSION_HANDLE WintunStartSession (WINTUN_ADAPTER_HANDLE Adapter, DWORD Capacity)`\r\n\r\nStarts Wintun session.\r\n\r\n**Parameters**\r\n\r\n- *Adapter*: Adapter handle obtained with WintunOpenAdapter or WintunCreateAdapter\r\n- *Capacity*: Rings capacity. Must be between WINTUN\\_MIN\\_RING\\_CAPACITY and WINTUN\\_MAX\\_RING\\_CAPACITY (incl.) Must be a power of two.\r\n\r\n**Returns**\r\n\r\nWintun session handle. Must be released with WintunEndSession. If the function fails, the return value is NULL. To get extended error information, call GetLastError.\r\n\r\n#### WintunEndSession()\r\n\r\n`void WintunEndSession (WINTUN_SESSION_HANDLE Session)`\r\n\r\nEnds Wintun session.\r\n\r\n**Parameters**\r\n\r\n- *Session*: Wintun session handle obtained with WintunStartSession\r\n\r\n#### WintunGetReadWaitEvent()\r\n\r\n`HANDLE WintunGetReadWaitEvent (WINTUN_SESSION_HANDLE Session)`\r\n\r\nGets Wintun session's read-wait event handle.\r\n\r\n**Parameters**\r\n\r\n- *Session*: Wintun session handle obtained with WintunStartSession\r\n\r\n**Returns**\r\n\r\nPointer to receive event handle to wait for available data when reading. Should WintunReceivePackets return ERROR\\_NO\\_MORE\\_ITEMS (after spinning on it for a while under heavy load), wait for this event to become signaled before retrying WintunReceivePackets. Do not call CloseHandle on this event - it is managed by the session.\r\n\r\n#### WintunReceivePacket()\r\n\r\n`BYTE* WintunReceivePacket (WINTUN_SESSION_HANDLE Session, DWORD * PacketSize)`\r\n\r\nRetrieves one or packet. After the packet content is consumed, call WintunReleaseReceivePacket with Packet returned from this function to release internal buffer. This function is thread-safe.\r\n\r\n**Parameters**\r\n\r\n- *Session*: Wintun session handle obtained with WintunStartSession\r\n- *PacketSize*: Pointer to receive packet size.\r\n\r\n**Returns**\r\n\r\nPointer to layer 3 IPv4 or IPv6 packet. Client may modify its content at will. If the function fails, the return value is NULL. To get extended error information, call GetLastError. Possible errors include the following: ERROR\\_HANDLE\\_EOF Wintun adapter is terminating; ERROR\\_NO\\_MORE\\_ITEMS Wintun buffer is exhausted; ERROR\\_INVALID\\_DATA Wintun buffer is corrupt\r\n\r\n#### WintunReleaseReceivePacket()\r\n\r\n`void WintunReleaseReceivePacket (WINTUN_SESSION_HANDLE Session, const BYTE * Packet)`\r\n\r\nReleases internal buffer after the received packet has been processed by the client. This function is thread-safe.\r\n\r\n**Parameters**\r\n\r\n- *Session*: Wintun session handle obtained with WintunStartSession\r\n- *Packet*: Packet obtained with WintunReceivePacket\r\n\r\n#### WintunAllocateSendPacket()\r\n\r\n`BYTE* WintunAllocateSendPacket (WINTUN_SESSION_HANDLE Session, DWORD PacketSize)`\r\n\r\nAllocates memory for a packet to send. After the memory is filled with packet data, call WintunSendPacket to send and release internal buffer. WintunAllocateSendPacket is thread-safe and the WintunAllocateSendPacket order of calls define the packet sending order.\r\n\r\n**Parameters**\r\n\r\n- *Session*: Wintun session handle obtained with WintunStartSession\r\n- *PacketSize*: Exact packet size. Must be less or equal to WINTUN\\_MAX\\_IP\\_PACKET\\_SIZE.\r\n\r\n**Returns**\r\n\r\nReturns pointer to memory where to prepare layer 3 IPv4 or IPv6 packet for sending. If the function fails, the return value is NULL. To get extended error information, call GetLastError. Possible errors include the following: ERROR\\_HANDLE\\_EOF Wintun adapter is terminating; ERROR\\_BUFFER\\_OVERFLOW Wintun buffer is full;\r\n\r\n#### WintunSendPacket()\r\n\r\n`void WintunSendPacket (WINTUN_SESSION_HANDLE Session, const BYTE * Packet)`\r\n\r\nSends the packet and releases internal buffer. WintunSendPacket is thread-safe, but the WintunAllocateSendPacket order of calls define the packet sending order. This means the packet is not guaranteed to be sent in the WintunSendPacket yet.\r\n\r\n**Parameters**\r\n\r\n- *Session*: Wintun session handle obtained with WintunStartSession\r\n- *Packet*: Packet obtained with WintunAllocateSendPacket\r\n\r\n## Building\r\n\r\n**Do not distribute drivers or files named \"Wintun\", as they will most certainly clash with official deployments. Instead distribute [`wintun.dll` as downloaded from wintun.net](https://www.wintun.net).**\r\n\r\nGeneral requirements:\r\n\r\n- [Visual Studio 2019](https://visualstudio.microsoft.com/downloads/) with Windows SDK\r\n- [Windows Driver Kit](https://docs.microsoft.com/en-us/windows-hardware/drivers/download-the-wdk)\r\n\r\n`wintun.sln` may be opened in Visual Studio for development and building. Be sure to run `bcdedit /set testsigning on` and then reboot before to enable unsigned driver loading. The default run sequence (F5) in Visual Studio will build the example project and its dependencies.\r\n\r\n## License\r\n\r\nThe entire contents of [the repository](https://git.zx2c4.com/wintun/), including all documentation and example code, is \"Copyright © 2018-2021 WireGuard LLC. All Rights Reserved.\" Source code is licensed under the [GPLv2](COPYING). Prebuilt binaries from [wintun.net](https://www.wintun.net/) are released under a more permissive license suitable for more forms of software contained inside of the .zip files distributed there.\r\n"
  },
  {
    "path": "dist/windows/wintun/include/wintun.h",
    "content": "/* SPDX-License-Identifier: GPL-2.0 OR MIT\r\n *\r\n * Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved.\r\n */\r\n\r\n#pragma once\r\n\r\n#include <winsock2.h>\r\n#include <windows.h>\r\n#include <ipexport.h>\r\n#include <ifdef.h>\r\n#include <ws2ipdef.h>\r\n\r\n#ifdef __cplusplus\r\nextern \"C\" {\r\n#endif\r\n\r\n#ifndef ALIGNED\r\n#    if defined(_MSC_VER)\r\n#        define ALIGNED(n) __declspec(align(n))\r\n#    elif defined(__GNUC__)\r\n#        define ALIGNED(n) __attribute__((aligned(n)))\r\n#    else\r\n#        error \"Unable to define ALIGNED\"\r\n#    endif\r\n#endif\r\n\r\n/* MinGW is missing this one, unfortunately. */\r\n#ifndef _Post_maybenull_\r\n#    define _Post_maybenull_\r\n#endif\r\n\r\n#pragma warning(push)\r\n#pragma warning(disable : 4324) /* structure was padded due to alignment specifier */\r\n\r\n/**\r\n * A handle representing Wintun adapter\r\n */\r\ntypedef struct _WINTUN_ADAPTER *WINTUN_ADAPTER_HANDLE;\r\n\r\n/**\r\n * Creates a new Wintun adapter.\r\n *\r\n * @param Name          The requested name of the adapter. Zero-terminated string of up to MAX_ADAPTER_NAME-1\r\n *                      characters.\r\n *\r\n * @param TunnelType    Name of the adapter tunnel type. Zero-terminated string of up to MAX_ADAPTER_NAME-1\r\n *                      characters.\r\n *\r\n * @param RequestedGUID The GUID of the created network adapter, which then influences NLA generation deterministically.\r\n *                      If it is set to NULL, the GUID is chosen by the system at random, and hence a new NLA entry is\r\n *                      created for each new adapter. It is called \"requested\" GUID because the API it uses is\r\n *                      completely undocumented, and so there could be minor interesting complications with its usage.\r\n *\r\n * @return If the function succeeds, the return value is the adapter handle. Must be released with\r\n * WintunCloseAdapter. If the function fails, the return value is NULL. To get extended error information, call\r\n * GetLastError.\r\n */\r\ntypedef _Must_inspect_result_\r\n_Return_type_success_(return != NULL)\r\n_Post_maybenull_\r\nWINTUN_ADAPTER_HANDLE(WINAPI WINTUN_CREATE_ADAPTER_FUNC)\r\n(_In_z_ LPCWSTR Name, _In_z_ LPCWSTR TunnelType, _In_opt_ const GUID *RequestedGUID);\r\n\r\n/**\r\n * Opens an existing Wintun adapter.\r\n *\r\n * @param Name          The requested name of the adapter. Zero-terminated string of up to MAX_ADAPTER_NAME-1\r\n *                      characters.\r\n *\r\n * @return If the function succeeds, the return value is the adapter handle. Must be released with\r\n * WintunCloseAdapter. If the function fails, the return value is NULL. To get extended error information, call\r\n * GetLastError.\r\n */\r\ntypedef _Must_inspect_result_\r\n_Return_type_success_(return != NULL)\r\n_Post_maybenull_\r\nWINTUN_ADAPTER_HANDLE(WINAPI WINTUN_OPEN_ADAPTER_FUNC)(_In_z_ LPCWSTR Name);\r\n\r\n/**\r\n * Releases Wintun adapter resources and, if adapter was created with WintunCreateAdapter, removes adapter.\r\n *\r\n * @param Adapter       Adapter handle obtained with WintunCreateAdapter or WintunOpenAdapter.\r\n */\r\ntypedef VOID(WINAPI WINTUN_CLOSE_ADAPTER_FUNC)(_In_opt_ WINTUN_ADAPTER_HANDLE Adapter);\r\n\r\n/**\r\n * Deletes the Wintun driver if there are no more adapters in use.\r\n *\r\n * @return If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To\r\n *         get extended error information, call GetLastError.\r\n */\r\ntypedef _Return_type_success_(return != FALSE)\r\nBOOL(WINAPI WINTUN_DELETE_DRIVER_FUNC)(VOID);\r\n\r\n/**\r\n * Returns the LUID of the adapter.\r\n *\r\n * @param Adapter       Adapter handle obtained with WintunCreateAdapter or WintunOpenAdapter\r\n *\r\n * @param Luid          Pointer to LUID to receive adapter LUID.\r\n */\r\ntypedef VOID(WINAPI WINTUN_GET_ADAPTER_LUID_FUNC)(_In_ WINTUN_ADAPTER_HANDLE Adapter, _Out_ NET_LUID *Luid);\r\n\r\n/**\r\n * Determines the version of the Wintun driver currently loaded.\r\n *\r\n * @return If the function succeeds, the return value is the version number. If the function fails, the return value is\r\n *         zero. To get extended error information, call GetLastError. Possible errors include the following:\r\n *         ERROR_FILE_NOT_FOUND  Wintun not loaded\r\n */\r\ntypedef _Return_type_success_(return != 0)\r\nDWORD(WINAPI WINTUN_GET_RUNNING_DRIVER_VERSION_FUNC)(VOID);\r\n\r\n/**\r\n * Determines the level of logging, passed to WINTUN_LOGGER_CALLBACK.\r\n */\r\ntypedef enum\r\n{\r\n    WINTUN_LOG_INFO, /**< Informational */\r\n    WINTUN_LOG_WARN, /**< Warning */\r\n    WINTUN_LOG_ERR   /**< Error */\r\n} WINTUN_LOGGER_LEVEL;\r\n\r\n/**\r\n * Called by internal logger to report diagnostic messages\r\n *\r\n * @param Level         Message level.\r\n *\r\n * @param Timestamp     Message timestamp in in 100ns intervals since 1601-01-01 UTC.\r\n *\r\n * @param Message       Message text.\r\n */\r\ntypedef VOID(CALLBACK *WINTUN_LOGGER_CALLBACK)(\r\n    _In_ WINTUN_LOGGER_LEVEL Level,\r\n    _In_ DWORD64 Timestamp,\r\n    _In_z_ LPCWSTR Message);\r\n\r\n/**\r\n * Sets logger callback function.\r\n *\r\n * @param NewLogger     Pointer to callback function to use as a new global logger. NewLogger may be called from various\r\n *                      threads concurrently. Should the logging require serialization, you must handle serialization in\r\n *                      NewLogger. Set to NULL to disable.\r\n */\r\ntypedef VOID(WINAPI WINTUN_SET_LOGGER_FUNC)(_In_ WINTUN_LOGGER_CALLBACK NewLogger);\r\n\r\n/**\r\n * Minimum ring capacity.\r\n */\r\n#define WINTUN_MIN_RING_CAPACITY 0x20000 /* 128kiB */\r\n\r\n/**\r\n * Maximum ring capacity.\r\n */\r\n#define WINTUN_MAX_RING_CAPACITY 0x4000000 /* 64MiB */\r\n\r\n/**\r\n * A handle representing Wintun session\r\n */\r\ntypedef struct _TUN_SESSION *WINTUN_SESSION_HANDLE;\r\n\r\n/**\r\n * Starts Wintun session.\r\n *\r\n * @param Adapter       Adapter handle obtained with WintunOpenAdapter or WintunCreateAdapter\r\n *\r\n * @param Capacity      Rings capacity. Must be between WINTUN_MIN_RING_CAPACITY and WINTUN_MAX_RING_CAPACITY (incl.)\r\n *                      Must be a power of two.\r\n *\r\n * @return Wintun session handle. Must be released with WintunEndSession. If the function fails, the return value is\r\n *         NULL. To get extended error information, call GetLastError.\r\n */\r\ntypedef _Must_inspect_result_\r\n_Return_type_success_(return != NULL)\r\n_Post_maybenull_\r\nWINTUN_SESSION_HANDLE(WINAPI WINTUN_START_SESSION_FUNC)(_In_ WINTUN_ADAPTER_HANDLE Adapter, _In_ DWORD Capacity);\r\n\r\n/**\r\n * Ends Wintun session.\r\n *\r\n * @param Session       Wintun session handle obtained with WintunStartSession\r\n */\r\ntypedef VOID(WINAPI WINTUN_END_SESSION_FUNC)(_In_ WINTUN_SESSION_HANDLE Session);\r\n\r\n/**\r\n * Gets Wintun session's read-wait event handle.\r\n *\r\n * @param Session       Wintun session handle obtained with WintunStartSession\r\n *\r\n * @return Pointer to receive event handle to wait for available data when reading. Should\r\n *         WintunReceivePackets return ERROR_NO_MORE_ITEMS (after spinning on it for a while under heavy\r\n *         load), wait for this event to become signaled before retrying WintunReceivePackets. Do not call\r\n *         CloseHandle on this event - it is managed by the session.\r\n */\r\ntypedef HANDLE(WINAPI WINTUN_GET_READ_WAIT_EVENT_FUNC)(_In_ WINTUN_SESSION_HANDLE Session);\r\n\r\n/**\r\n * Maximum IP packet size\r\n */\r\n#define WINTUN_MAX_IP_PACKET_SIZE 0xFFFF\r\n\r\n/**\r\n * Retrieves one or packet. After the packet content is consumed, call WintunReleaseReceivePacket with Packet returned\r\n * from this function to release internal buffer. This function is thread-safe.\r\n *\r\n * @param Session       Wintun session handle obtained with WintunStartSession\r\n *\r\n * @param PacketSize    Pointer to receive packet size.\r\n *\r\n * @return Pointer to layer 3 IPv4 or IPv6 packet. Client may modify its content at will. If the function fails, the\r\n *         return value is NULL. To get extended error information, call GetLastError. Possible errors include the\r\n *         following:\r\n *         ERROR_HANDLE_EOF     Wintun adapter is terminating;\r\n *         ERROR_NO_MORE_ITEMS  Wintun buffer is exhausted;\r\n *         ERROR_INVALID_DATA   Wintun buffer is corrupt\r\n */\r\ntypedef _Must_inspect_result_\r\n_Return_type_success_(return != NULL)\r\n_Post_maybenull_\r\n_Post_writable_byte_size_(*PacketSize)\r\nBYTE *(WINAPI WINTUN_RECEIVE_PACKET_FUNC)(_In_ WINTUN_SESSION_HANDLE Session, _Out_ DWORD *PacketSize);\r\n\r\n/**\r\n * Releases internal buffer after the received packet has been processed by the client. This function is thread-safe.\r\n *\r\n * @param Session       Wintun session handle obtained with WintunStartSession\r\n *\r\n * @param Packet        Packet obtained with WintunReceivePacket\r\n */\r\ntypedef VOID(\r\n    WINAPI WINTUN_RELEASE_RECEIVE_PACKET_FUNC)(_In_ WINTUN_SESSION_HANDLE Session, _In_ const BYTE *Packet);\r\n\r\n/**\r\n * Allocates memory for a packet to send. After the memory is filled with packet data, call WintunSendPacket to send\r\n * and release internal buffer. WintunAllocateSendPacket is thread-safe and the WintunAllocateSendPacket order of\r\n * calls define the packet sending order.\r\n *\r\n * @param Session       Wintun session handle obtained with WintunStartSession\r\n *\r\n * @param PacketSize    Exact packet size. Must be less or equal to WINTUN_MAX_IP_PACKET_SIZE.\r\n *\r\n * @return Returns pointer to memory where to prepare layer 3 IPv4 or IPv6 packet for sending. If the function fails,\r\n *         the return value is NULL. To get extended error information, call GetLastError. Possible errors include the\r\n *         following:\r\n *         ERROR_HANDLE_EOF       Wintun adapter is terminating;\r\n *         ERROR_BUFFER_OVERFLOW  Wintun buffer is full;\r\n */\r\ntypedef _Must_inspect_result_\r\n_Return_type_success_(return != NULL)\r\n_Post_maybenull_\r\n_Post_writable_byte_size_(PacketSize)\r\nBYTE *(WINAPI WINTUN_ALLOCATE_SEND_PACKET_FUNC)(_In_ WINTUN_SESSION_HANDLE Session, _In_ DWORD PacketSize);\r\n\r\n/**\r\n * Sends the packet and releases internal buffer. WintunSendPacket is thread-safe, but the WintunAllocateSendPacket\r\n * order of calls define the packet sending order. This means the packet is not guaranteed to be sent in the\r\n * WintunSendPacket yet.\r\n *\r\n * @param Session       Wintun session handle obtained with WintunStartSession\r\n *\r\n * @param Packet        Packet obtained with WintunAllocateSendPacket\r\n */\r\ntypedef VOID(WINAPI WINTUN_SEND_PACKET_FUNC)(_In_ WINTUN_SESSION_HANDLE Session, _In_ const BYTE *Packet);\r\n\r\n#pragma warning(pop)\r\n\r\n#ifdef __cplusplus\r\n}\r\n#endif\r\n"
  },
  {
    "path": "dist/wireshark/nebula.lua",
    "content": "local nebula = Proto(\"nebula\", \"nebula\")\n\nlocal default_settings = {\n    port      = 4242,\n    all_ports = false,\n}\n\nnebula.prefs.port  = Pref.uint(\"Port number\", default_settings.port, \"The UDP port number for Nebula\")\nnebula.prefs.all_ports  = Pref.bool(\"All ports\", default_settings.all_ports, \"Assume nebula packets on any port, useful when dealing with hole punching\")\n\nlocal pf_version  = ProtoField.new(\"version\", \"nebula.version\", ftypes.UINT8, nil, base.DEC, 0xF0)\nlocal pf_type     = ProtoField.new(\"type\", \"nebula.type\", ftypes.UINT8, {\n    [0] = \"handshake\",\n    [1] = \"message\",\n    [2] = \"recvError\",\n    [3] = \"lightHouse\",\n    [4] = \"test\",\n    [5] = \"closeTunnel\",\n}, base.DEC, 0x0F)\n\nlocal pf_subtype = ProtoField.new(\"subtype\", \"nebula.subtype\", ftypes.UINT8, nil, base.DEC)\nlocal pf_subtype_test = ProtoField.new(\"subtype\", \"nebula.subtype\", ftypes.UINT8, {\n    [0] = \"request\",\n    [1] = \"reply\",\n}, base.DEC)\n\nlocal pf_subtype_handshake = ProtoField.new(\"subtype\", \"nebula.subtype\", ftypes.UINT8, {\n    [0] = \"ix_psk0\",\n}, base.DEC)\n\nlocal pf_reserved        = ProtoField.new(\"reserved\",     \"nebula.reserved\",     ftypes.UINT16, nil, base.HEX)\nlocal pf_remote_index    = ProtoField.new(\"remote index\", \"nebula.remote_index\", ftypes.UINT32, nil, base.DEC)\nlocal pf_message_counter = ProtoField.new(\"counter\",      \"nebula.counter\",      ftypes.UINT64, nil, base.DEC)\nlocal pf_payload         = ProtoField.new(\"payload\",      \"nebula.payload\",      ftypes.BYTES,  nil, base.NONE)\n\nnebula.fields = { pf_version, pf_type, pf_subtype, pf_subtype_handshake, pf_subtype_test, pf_reserved, pf_remote_index, pf_message_counter, pf_payload }\n\nlocal ef_holepunch = ProtoExpert.new(\"nebula.holepunch.expert\", \"Nebula hole punch packet\", expert.group.PROTOCOL, expert.severity.NOTE)\nlocal ef_punchy = ProtoExpert.new(\"nebula.punchy.expert\", \"Nebula punchy keepalive packet\", expert.group.PROTOCOL, expert.severity.NOTE)\n\nnebula.experts = { ef_holepunch, ef_punchy }\nlocal type_field = Field.new(\"nebula.type\")\nlocal subtype_field = Field.new(\"nebula.subtype\")\n\nfunction nebula.dissector(tvbuf, pktinfo, root)\n    -- set the protocol column to show our protocol name\n    pktinfo.cols.protocol:set(\"NEBULA\")\n\n    local pktlen = tvbuf:reported_length_remaining()\n    local tree = root:add(nebula, tvbuf:range(0,pktlen))\n\n    if pktlen == 0 then\n        tree:add_proto_expert_info(ef_holepunch)\n        pktinfo.cols.info:append(\" (holepunch)\")\n        return\n    elseif pktlen == 1 then\n        tree:add_proto_expert_info(ef_punchy)\n        pktinfo.cols.info:append(\" (punchy)\")\n        return\n    end\n\n    tree:add(pf_version, tvbuf:range(0,1))\n    local type = tree:add(pf_type,    tvbuf:range(0,1))\n\n    local nebula_type = bit32.band(tvbuf:range(0,1):uint(), 0x0F)\n    if nebula_type == 0 then\n        local stage = tvbuf(8,8):uint64()\n        tree:add(pf_subtype_handshake, tvbuf:range(1,1))\n        type:append_text(\" stage \" .. stage)\n        pktinfo.cols.info:append(\" (\" .. type_field().display .. \", stage \" .. stage .. \", \" .. subtype_field().display .. \")\")\n    elseif nebula_type == 4 then\n        tree:add(pf_subtype_test, tvbuf:range(1,1))\n        pktinfo.cols.info:append(\" (\" .. type_field().display .. \", \" .. subtype_field().display .. \")\")\n    else\n        tree:add(pf_subtype, tvbuf:range(1,1))\n        pktinfo.cols.info:append(\" (\" .. type_field().display .. \")\")\n    end\n\n    tree:add(pf_reserved,        tvbuf:range(2,2))\n    tree:add(pf_remote_index,    tvbuf:range(4,4))\n    tree:add(pf_message_counter, tvbuf:range(8,8))\n    tree:add(pf_payload,         tvbuf:range(16,tvbuf:len() - 16))\nend\n\nfunction nebula.prefs_changed()\n    if default_settings.all_ports == nebula.prefs.all_ports and default_settings.port == nebula.prefs.port then\n        -- Nothing changed, bail\n        return\n    end\n\n    -- Remove our old dissector\n    DissectorTable.get(\"udp.port\"):remove_all(nebula)\n\n    if nebula.prefs.all_ports and default_settings.all_ports ~= nebula.prefs.all_ports then\n        default_settings.all_port = nebula.prefs.all_ports\n\n        for i=0, 65535 do\n            DissectorTable.get(\"udp.port\"):add(i, nebula)\n        end\n\n        -- no need to establish again on specific ports\n        return\n    end\n\n\n    if default_settings.all_ports ~= nebula.prefs.all_ports then\n        -- Add our new port dissector\n        default_settings.port = nebula.prefs.port\n        DissectorTable.get(\"udp.port\"):add(default_settings.port, nebula)\n    end\nend\n\nDissectorTable.get(\"udp.port\"):add(default_settings.port, nebula)\n"
  },
  {
    "path": "dns_server.go",
    "content": "package nebula\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/gaissmai/bart\"\n\t\"github.com/miekg/dns\"\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/config\"\n)\n\n// This whole thing should be rewritten to use context\n\nvar dnsR *dnsRecords\nvar dnsServer *dns.Server\nvar dnsAddr string\n\ntype dnsRecords struct {\n\tsync.RWMutex\n\tl               *logrus.Logger\n\tdnsMap4         map[string]netip.Addr\n\tdnsMap6         map[string]netip.Addr\n\thostMap         *HostMap\n\tmyVpnAddrsTable *bart.Lite\n}\n\nfunc newDnsRecords(l *logrus.Logger, cs *CertState, hostMap *HostMap) *dnsRecords {\n\treturn &dnsRecords{\n\t\tl:               l,\n\t\tdnsMap4:         make(map[string]netip.Addr),\n\t\tdnsMap6:         make(map[string]netip.Addr),\n\t\thostMap:         hostMap,\n\t\tmyVpnAddrsTable: cs.myVpnAddrsTable,\n\t}\n}\n\nfunc (d *dnsRecords) Query(q uint16, data string) netip.Addr {\n\tdata = strings.ToLower(data)\n\td.RLock()\n\tdefer d.RUnlock()\n\tswitch q {\n\tcase dns.TypeA:\n\t\tif r, ok := d.dnsMap4[data]; ok {\n\t\t\treturn r\n\t\t}\n\tcase dns.TypeAAAA:\n\t\tif r, ok := d.dnsMap6[data]; ok {\n\t\t\treturn r\n\t\t}\n\t}\n\n\treturn netip.Addr{}\n}\n\nfunc (d *dnsRecords) QueryCert(data string) string {\n\tip, err := netip.ParseAddr(data[:len(data)-1])\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\n\thostinfo := d.hostMap.QueryVpnAddr(ip)\n\tif hostinfo == nil {\n\t\treturn \"\"\n\t}\n\n\tq := hostinfo.GetCert()\n\tif q == nil {\n\t\treturn \"\"\n\t}\n\n\tb, err := q.Certificate.MarshalJSON()\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\treturn string(b)\n}\n\n// Add adds the first IPv4 and IPv6 address that appears in `addresses` as the record for `host`\nfunc (d *dnsRecords) Add(host string, addresses []netip.Addr) {\n\thost = strings.ToLower(host)\n\td.Lock()\n\tdefer d.Unlock()\n\thaveV4 := false\n\thaveV6 := false\n\tfor _, addr := range addresses {\n\t\tif addr.Is4() && !haveV4 {\n\t\t\td.dnsMap4[host] = addr\n\t\t\thaveV4 = true\n\t\t} else if addr.Is6() && !haveV6 {\n\t\t\td.dnsMap6[host] = addr\n\t\t\thaveV6 = true\n\t\t}\n\t\tif haveV4 && haveV6 {\n\t\t\tbreak\n\t\t}\n\t}\n}\n\nfunc (d *dnsRecords) isSelfNebulaOrLocalhost(addr string) bool {\n\ta, _, _ := net.SplitHostPort(addr)\n\tb, err := netip.ParseAddr(a)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tif b.IsLoopback() {\n\t\treturn true\n\t}\n\n\t//if we found it in this table, it's good\n\treturn d.myVpnAddrsTable.Contains(b)\n}\n\nfunc (d *dnsRecords) parseQuery(m *dns.Msg, w dns.ResponseWriter) {\n\tfor _, q := range m.Question {\n\t\tswitch q.Qtype {\n\t\tcase dns.TypeA, dns.TypeAAAA:\n\t\t\tqType := dns.TypeToString[q.Qtype]\n\t\t\td.l.Debugf(\"Query for %s %s\", qType, q.Name)\n\t\t\tip := d.Query(q.Qtype, q.Name)\n\t\t\tif ip.IsValid() {\n\t\t\t\trr, err := dns.NewRR(fmt.Sprintf(\"%s %s %s\", q.Name, qType, ip))\n\t\t\t\tif err == nil {\n\t\t\t\t\tm.Answer = append(m.Answer, rr)\n\t\t\t\t}\n\t\t\t}\n\t\tcase dns.TypeTXT:\n\t\t\t// We only answer these queries from nebula nodes or localhost\n\t\t\tif !d.isSelfNebulaOrLocalhost(w.RemoteAddr().String()) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\td.l.Debugf(\"Query for TXT %s\", q.Name)\n\t\t\tip := d.QueryCert(q.Name)\n\t\t\tif ip != \"\" {\n\t\t\t\trr, err := dns.NewRR(fmt.Sprintf(\"%s TXT %s\", q.Name, ip))\n\t\t\t\tif err == nil {\n\t\t\t\t\tm.Answer = append(m.Answer, rr)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(m.Answer) == 0 {\n\t\tm.Rcode = dns.RcodeNameError\n\t}\n}\n\nfunc (d *dnsRecords) handleDnsRequest(w dns.ResponseWriter, r *dns.Msg) {\n\tm := new(dns.Msg)\n\tm.SetReply(r)\n\tm.Compress = false\n\n\tswitch r.Opcode {\n\tcase dns.OpcodeQuery:\n\t\td.parseQuery(m, w)\n\t}\n\n\tw.WriteMsg(m)\n}\n\nfunc dnsMain(l *logrus.Logger, cs *CertState, hostMap *HostMap, c *config.C) func() {\n\tdnsR = newDnsRecords(l, cs, hostMap)\n\n\t// attach request handler func\n\tdns.HandleFunc(\".\", dnsR.handleDnsRequest)\n\n\tc.RegisterReloadCallback(func(c *config.C) {\n\t\treloadDns(l, c)\n\t})\n\n\treturn func() {\n\t\tstartDns(l, c)\n\t}\n}\n\nfunc getDnsServerAddr(c *config.C) string {\n\tdnsHost := strings.TrimSpace(c.GetString(\"lighthouse.dns.host\", \"\"))\n\t// Old guidance was to provide the literal `[::]` in `lighthouse.dns.host` but that won't resolve.\n\tif dnsHost == \"[::]\" {\n\t\tdnsHost = \"::\"\n\t}\n\treturn net.JoinHostPort(dnsHost, strconv.Itoa(c.GetInt(\"lighthouse.dns.port\", 53)))\n}\n\nfunc startDns(l *logrus.Logger, c *config.C) {\n\tdnsAddr = getDnsServerAddr(c)\n\tdnsServer = &dns.Server{Addr: dnsAddr, Net: \"udp\"}\n\tl.WithField(\"dnsListener\", dnsAddr).Info(\"Starting DNS responder\")\n\terr := dnsServer.ListenAndServe()\n\tdefer dnsServer.Shutdown()\n\tif err != nil {\n\t\tl.Errorf(\"Failed to start server: %s\\n \", err.Error())\n\t}\n}\n\nfunc reloadDns(l *logrus.Logger, c *config.C) {\n\tif dnsAddr == getDnsServerAddr(c) {\n\t\tl.Debug(\"No DNS server config change detected\")\n\t\treturn\n\t}\n\n\tl.Debug(\"Restarting DNS server\")\n\tdnsServer.Shutdown()\n\tgo startDns(l, c)\n}\n"
  },
  {
    "path": "dns_server_test.go",
    "content": "package nebula\n\nimport (\n\t\"net/netip\"\n\t\"testing\"\n\n\t\"github.com/miekg/dns\"\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/config\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestParsequery(t *testing.T) {\n\tl := logrus.New()\n\thostMap := &HostMap{}\n\tds := newDnsRecords(l, &CertState{}, hostMap)\n\taddrs := []netip.Addr{\n\t\tnetip.MustParseAddr(\"1.2.3.4\"),\n\t\tnetip.MustParseAddr(\"1.2.3.5\"),\n\t\tnetip.MustParseAddr(\"fd01::24\"),\n\t\tnetip.MustParseAddr(\"fd01::25\"),\n\t}\n\tds.Add(\"test.com.com\", addrs)\n\n\tm := &dns.Msg{}\n\tm.SetQuestion(\"test.com.com\", dns.TypeA)\n\tds.parseQuery(m, nil)\n\tassert.NotNil(t, m.Answer)\n\tassert.Equal(t, \"1.2.3.4\", m.Answer[0].(*dns.A).A.String())\n\n\tm = &dns.Msg{}\n\tm.SetQuestion(\"test.com.com\", dns.TypeAAAA)\n\tds.parseQuery(m, nil)\n\tassert.NotNil(t, m.Answer)\n\tassert.Equal(t, \"fd01::24\", m.Answer[0].(*dns.AAAA).AAAA.String())\n}\n\nfunc Test_getDnsServerAddr(t *testing.T) {\n\tc := config.NewC(nil)\n\n\tc.Settings[\"lighthouse\"] = map[string]any{\n\t\t\"dns\": map[string]any{\n\t\t\t\"host\": \"0.0.0.0\",\n\t\t\t\"port\": \"1\",\n\t\t},\n\t}\n\tassert.Equal(t, \"0.0.0.0:1\", getDnsServerAddr(c))\n\n\tc.Settings[\"lighthouse\"] = map[string]any{\n\t\t\"dns\": map[string]any{\n\t\t\t\"host\": \"::\",\n\t\t\t\"port\": \"1\",\n\t\t},\n\t}\n\tassert.Equal(t, \"[::]:1\", getDnsServerAddr(c))\n\n\tc.Settings[\"lighthouse\"] = map[string]any{\n\t\t\"dns\": map[string]any{\n\t\t\t\"host\": \"[::]\",\n\t\t\t\"port\": \"1\",\n\t\t},\n\t}\n\tassert.Equal(t, \"[::]:1\", getDnsServerAddr(c))\n\n\t// Make sure whitespace doesn't mess us up\n\tc.Settings[\"lighthouse\"] = map[string]any{\n\t\t\"dns\": map[string]any{\n\t\t\t\"host\": \"[::] \",\n\t\t\t\"port\": \"1\",\n\t\t},\n\t}\n\tassert.Equal(t, \"[::]:1\", getDnsServerAddr(c))\n}\n"
  },
  {
    "path": "docker/Dockerfile",
    "content": "FROM gcr.io/distroless/static:latest\n\nARG TARGETOS TARGETARCH\nCOPY build/$TARGETOS-$TARGETARCH/nebula /nebula\nCOPY build/$TARGETOS-$TARGETARCH/nebula-cert /nebula-cert\n\nVOLUME [\"/config\"]\n\nENTRYPOINT [\"/nebula\"]\n# Allow users to override the args passed to nebula\nCMD [\"-config\", \"/config/config.yml\"]\n"
  },
  {
    "path": "docker/README.md",
    "content": "# NebulaOSS/nebula Docker Image\n\n## Building\n\nFrom the root of the repository, run `make docker`.\n\n## Running\n\nTo run the built image, use the following command:\n\n```\ndocker run \\\n    --name nebula \\\n    --network host \\\n    --cap-add NET_ADMIN \\\n    --volume ./config:/config \\\n    --rm \\\n    nebulaoss/nebula\n```\n\nA few notes:\n\n- The `NET_ADMIN` capability is necessary to create the tun adapter on the host (this is unnecessary if the tun device is disabled.)\n- `--volume ./config:/config` should point to a directory that contains your `config.yml` and any other necessary files.\n"
  },
  {
    "path": "e2e/doc.go",
    "content": "package e2e\n\n// This file exists to allow `go fmt` to traverse here on its own. The build tags were keeping it out before\n"
  },
  {
    "path": "e2e/handshakes_test.go",
    "content": "//go:build e2e_testing\n// +build e2e_testing\n\npackage e2e\n\nimport (\n\t\"net/netip\"\n\t\"slices\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/gopacket\"\n\t\"github.com/google/gopacket/layers\"\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula\"\n\t\"github.com/slackhq/nebula/cert\"\n\t\"github.com/slackhq/nebula/cert_test\"\n\t\"github.com/slackhq/nebula/e2e/router\"\n\t\"github.com/slackhq/nebula/header\"\n\t\"github.com/slackhq/nebula/udp\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.yaml.in/yaml/v3\"\n)\n\nfunc BenchmarkHotPath(b *testing.B) {\n\tca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})\n\tmyControl, myVpnIpNet, myUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, \"me\", \"10.128.0.1/24\", nil)\n\ttheirControl, theirVpnIpNet, theirUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, \"them\", \"10.128.0.2/24\", nil)\n\n\t// Put their info in our lighthouse\n\tmyControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)\n\ttheirControl.InjectLightHouseAddr(myVpnIpNet[0].Addr(), myUdpAddr)\n\n\t// Start the servers\n\tmyControl.Start()\n\ttheirControl.Start()\n\n\tr := router.NewR(b, myControl, theirControl)\n\tr.CancelFlowLogs()\n\n\tassertTunnel(b, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)\n\tb.ResetTimer()\n\n\tfor n := 0; n < b.N; n++ {\n\t\tmyControl.InjectTunUDPPacket(theirVpnIpNet[0].Addr(), 80, myVpnIpNet[0].Addr(), 80, []byte(\"Hi from me\"))\n\t\t_ = r.RouteForAllUntilTxTun(theirControl)\n\t}\n\n\tmyControl.Stop()\n\ttheirControl.Stop()\n}\n\nfunc BenchmarkHotPathRelay(b *testing.B) {\n\tca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})\n\tmyControl, myVpnIpNet, _, _ := newSimpleServer(cert.Version1, ca, caKey, \"me     \", \"10.128.0.1/24\", m{\"relay\": m{\"use_relays\": true}})\n\trelayControl, relayVpnIpNet, relayUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, \"relay  \", \"10.128.0.128/24\", m{\"relay\": m{\"am_relay\": true}})\n\ttheirControl, theirVpnIpNet, theirUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, \"them   \", \"10.128.0.2/24\", m{\"relay\": m{\"use_relays\": true}})\n\n\t// Teach my how to get to the relay and that their can be reached via the relay\n\tmyControl.InjectLightHouseAddr(relayVpnIpNet[0].Addr(), relayUdpAddr)\n\tmyControl.InjectRelays(theirVpnIpNet[0].Addr(), []netip.Addr{relayVpnIpNet[0].Addr()})\n\trelayControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)\n\n\t// Build a router so we don't have to reason who gets which packet\n\tr := router.NewR(b, myControl, relayControl, theirControl)\n\tr.CancelFlowLogs()\n\n\t// Start the servers\n\tmyControl.Start()\n\trelayControl.Start()\n\ttheirControl.Start()\n\n\tassertTunnel(b, theirVpnIpNet[0].Addr(), myVpnIpNet[0].Addr(), theirControl, myControl, r)\n\tb.ResetTimer()\n\n\tfor n := 0; n < b.N; n++ {\n\t\tmyControl.InjectTunUDPPacket(theirVpnIpNet[0].Addr(), 80, myVpnIpNet[0].Addr(), 80, []byte(\"Hi from me\"))\n\t\t_ = r.RouteForAllUntilTxTun(theirControl)\n\t}\n\n\tmyControl.Stop()\n\ttheirControl.Stop()\n\trelayControl.Stop()\n}\n\nfunc TestGoodHandshake(t *testing.T) {\n\tca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})\n\tmyControl, myVpnIpNet, myUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, \"me\", \"10.128.0.1/24\", nil)\n\ttheirControl, theirVpnIpNet, theirUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, \"them\", \"10.128.0.2/24\", nil)\n\n\t// Put their info in our lighthouse\n\tmyControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)\n\n\t// Start the servers\n\tmyControl.Start()\n\ttheirControl.Start()\n\n\tt.Log(\"Send a udp packet through to begin standing up the tunnel, this should come out the other side\")\n\tmyControl.InjectTunUDPPacket(theirVpnIpNet[0].Addr(), 80, myVpnIpNet[0].Addr(), 80, []byte(\"Hi from me\"))\n\n\tt.Log(\"Have them consume my stage 0 packet. They have a tunnel now\")\n\ttheirControl.InjectUDPPacket(myControl.GetFromUDP(true))\n\n\tt.Log(\"Get their stage 1 packet so that we can play with it\")\n\tstage1Packet := theirControl.GetFromUDP(true)\n\n\tt.Log(\"I consume a garbage packet with a proper nebula header for our tunnel\")\n\t// this should log a statement and get ignored, allowing the real handshake packet to complete the tunnel\n\tbadPacket := stage1Packet.Copy()\n\tbadPacket.Data = badPacket.Data[:len(badPacket.Data)-header.Len]\n\tmyControl.InjectUDPPacket(badPacket)\n\n\tt.Log(\"Have me consume their real stage 1 packet. I have a tunnel now\")\n\tmyControl.InjectUDPPacket(stage1Packet)\n\n\tt.Log(\"Wait until we see my cached packet come through\")\n\tmyControl.WaitForType(1, 0, theirControl)\n\n\tt.Log(\"Make sure our host infos are correct\")\n\tassertHostInfoPair(t, myUdpAddr, theirUdpAddr, myVpnIpNet, theirVpnIpNet, myControl, theirControl)\n\n\tt.Log(\"Get that cached packet and make sure it looks right\")\n\tmyCachedPacket := theirControl.GetFromTun(true)\n\tassertUdpPacket(t, []byte(\"Hi from me\"), myCachedPacket, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), 80, 80)\n\n\tt.Log(\"Do a bidirectional tunnel test\")\n\tr := router.NewR(t, myControl, theirControl)\n\tdefer r.RenderFlow()\n\tassertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)\n\n\tr.RenderHostmaps(\"Final hostmaps\", myControl, theirControl)\n\tmyControl.Stop()\n\ttheirControl.Stop()\n}\n\nfunc TestGoodHandshakeNoOverlap(t *testing.T) {\n\tca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})\n\tmyControl, myVpnIpNet, myUdpAddr, _ := newSimpleServer(cert.Version2, ca, caKey, \"me\", \"10.128.0.1/24\", nil)\n\ttheirControl, theirVpnIpNet, theirUdpAddr, _ := newSimpleServer(cert.Version2, ca, caKey, \"them\", \"2001::69/24\", nil) //look ma, cross-stack!\n\n\t// Put their info in our lighthouse\n\tmyControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)\n\n\t// Start the servers\n\tmyControl.Start()\n\ttheirControl.Start()\n\n\tempty := []byte{}\n\tt.Log(\"do something to cause a handshake\")\n\tmyControl.GetF().SendMessageToVpnAddr(header.Test, header.MessageNone, theirVpnIpNet[0].Addr(), empty, empty, empty)\n\n\tt.Log(\"Have them consume my stage 0 packet. They have a tunnel now\")\n\ttheirControl.InjectUDPPacket(myControl.GetFromUDP(true))\n\n\tt.Log(\"Get their stage 1 packet\")\n\tstage1Packet := theirControl.GetFromUDP(true)\n\n\tt.Log(\"Have me consume their stage 1 packet. I have a tunnel now\")\n\tmyControl.InjectUDPPacket(stage1Packet)\n\n\tt.Log(\"Wait until we see a test packet come through to make sure we give the tunnel time to complete\")\n\tmyControl.WaitForType(header.Test, 0, theirControl)\n\n\tt.Log(\"Make sure our host infos are correct\")\n\tassertHostInfoPair(t, myUdpAddr, theirUdpAddr, myVpnIpNet, theirVpnIpNet, myControl, theirControl)\n\n\tmyControl.Stop()\n\ttheirControl.Stop()\n}\n\nfunc TestWrongResponderHandshake(t *testing.T) {\n\tca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})\n\n\tmyControl, myVpnIpNet, myUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, \"me\", \"10.128.0.100/24\", nil)\n\ttheirControl, theirVpnIpNet, theirUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, \"them\", \"10.128.0.99/24\", nil)\n\tevilControl, evilVpnIp, evilUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, \"evil\", \"10.128.0.2/24\", nil)\n\n\t// Put the evil udp addr in for their vpn Ip, this is a case of being lied to by the lighthouse.\n\tmyControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), evilUdpAddr)\n\n\t// Build a router so we don't have to reason who gets which packet\n\tr := router.NewR(t, myControl, theirControl, evilControl)\n\tdefer r.RenderFlow()\n\n\t// Start the servers\n\tmyControl.Start()\n\ttheirControl.Start()\n\tevilControl.Start()\n\n\tt.Log(\"Start the handshake process, we will route until we see the evil tunnel closed\")\n\tmyControl.InjectTunUDPPacket(theirVpnIpNet[0].Addr(), 80, myVpnIpNet[0].Addr(), 80, []byte(\"Hi from me\"))\n\n\th := &header.H{}\n\tr.RouteForAllExitFunc(func(p *udp.Packet, c *nebula.Control) router.ExitType {\n\t\terr := h.Parse(p.Data)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tif h.Type == header.CloseTunnel && p.To == evilUdpAddr {\n\t\t\treturn router.RouteAndExit\n\t\t}\n\n\t\treturn router.KeepRouting\n\t})\n\n\tt.Log(\"Evil tunnel is closed, inject the correct udp addr for them\")\n\tmyControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)\n\tpendingHi := myControl.GetHostInfoByVpnAddr(theirVpnIpNet[0].Addr(), true)\n\tassert.NotContains(t, pendingHi.RemoteAddrs, evilUdpAddr)\n\n\tt.Log(\"Route until we see the cached packet\")\n\tr.RouteForAllExitFunc(func(p *udp.Packet, c *nebula.Control) router.ExitType {\n\t\terr := h.Parse(p.Data)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tif p.To == theirUdpAddr && h.Type == 1 {\n\t\t\treturn router.RouteAndExit\n\t\t}\n\n\t\treturn router.KeepRouting\n\t})\n\n\tt.Log(\"My cached packet should be received by them\")\n\tmyCachedPacket := theirControl.GetFromTun(true)\n\tassertUdpPacket(t, []byte(\"Hi from me\"), myCachedPacket, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), 80, 80)\n\n\tt.Log(\"Test the tunnel with them\")\n\tassertHostInfoPair(t, myUdpAddr, theirUdpAddr, myVpnIpNet, theirVpnIpNet, myControl, theirControl)\n\tassertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)\n\n\tt.Log(\"Flush all packets from all controllers\")\n\tr.FlushAll()\n\n\tt.Log(\"Ensure ensure I don't have any hostinfo artifacts from evil\")\n\tassert.Nil(t, myControl.GetHostInfoByVpnAddr(evilVpnIp[0].Addr(), true), \"My pending hostmap should not contain evil\")\n\tassert.Nil(t, myControl.GetHostInfoByVpnAddr(evilVpnIp[0].Addr(), false), \"My main hostmap should not contain evil\")\n\n\tr.RenderHostmaps(\"Final hostmaps\", myControl, theirControl, evilControl)\n\tt.Log(\"Success!\")\n\tmyControl.Stop()\n\ttheirControl.Stop()\n}\n\nfunc TestWrongResponderHandshakeStaticHostMap(t *testing.T) {\n\tca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})\n\n\ttheirControl, theirVpnIpNet, theirUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, \"them\", \"10.128.0.99/24\", nil)\n\tevilControl, evilVpnIp, evilUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, \"evil\", \"10.128.0.2/24\", nil)\n\to := m{\n\t\t\"static_host_map\": m{\n\t\t\ttheirVpnIpNet[0].Addr().String(): []string{evilUdpAddr.String()},\n\t\t},\n\t}\n\tmyControl, myVpnIpNet, myUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, \"me\", \"10.128.0.100/24\", o)\n\n\t// Put the evil udp addr in for their vpn addr, this is a case of a remote at a static entry changing its vpn addr.\n\tmyControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), evilUdpAddr)\n\n\t// Build a router so we don't have to reason who gets which packet\n\tr := router.NewR(t, myControl, theirControl, evilControl)\n\tdefer r.RenderFlow()\n\n\t// Start the servers\n\tmyControl.Start()\n\ttheirControl.Start()\n\tevilControl.Start()\n\n\tt.Log(\"Start the handshake process, we will route until we see the evil tunnel closed\")\n\tmyControl.InjectTunUDPPacket(theirVpnIpNet[0].Addr(), 80, myVpnIpNet[0].Addr(), 80, []byte(\"Hi from me\"))\n\n\th := &header.H{}\n\tr.RouteForAllExitFunc(func(p *udp.Packet, c *nebula.Control) router.ExitType {\n\t\terr := h.Parse(p.Data)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tif h.Type == header.CloseTunnel && p.To == evilUdpAddr {\n\t\t\treturn router.RouteAndExit\n\t\t}\n\n\t\treturn router.KeepRouting\n\t})\n\n\tt.Log(\"Evil tunnel is closed, inject the correct udp addr for them\")\n\tmyControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)\n\tpendingHi := myControl.GetHostInfoByVpnAddr(theirVpnIpNet[0].Addr(), true)\n\tassert.NotContains(t, pendingHi.RemoteAddrs, evilUdpAddr)\n\n\tt.Log(\"Route until we see the cached packet\")\n\tr.RouteForAllExitFunc(func(p *udp.Packet, c *nebula.Control) router.ExitType {\n\t\terr := h.Parse(p.Data)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tif p.To == theirUdpAddr && h.Type == 1 {\n\t\t\treturn router.RouteAndExit\n\t\t}\n\n\t\treturn router.KeepRouting\n\t})\n\n\tt.Log(\"My cached packet should be received by them\")\n\tmyCachedPacket := theirControl.GetFromTun(true)\n\tassertUdpPacket(t, []byte(\"Hi from me\"), myCachedPacket, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), 80, 80)\n\n\tt.Log(\"Test the tunnel with them\")\n\tassertHostInfoPair(t, myUdpAddr, theirUdpAddr, myVpnIpNet, theirVpnIpNet, myControl, theirControl)\n\tassertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)\n\n\tt.Log(\"Flush all packets from all controllers\")\n\tr.FlushAll()\n\n\tt.Log(\"Ensure ensure I don't have any hostinfo artifacts from evil\")\n\tassert.Nil(t, myControl.GetHostInfoByVpnAddr(evilVpnIp[0].Addr(), true), \"My pending hostmap should not contain evil\")\n\tassert.Nil(t, myControl.GetHostInfoByVpnAddr(evilVpnIp[0].Addr(), false), \"My main hostmap should not contain evil\")\n\t//NOTE: if evil lost the handshake race it may still have a tunnel since me would reject the handshake since the tunnel is complete\n\n\tr.RenderHostmaps(\"Final hostmaps\", myControl, theirControl, evilControl)\n\tt.Log(\"Success!\")\n\tmyControl.Stop()\n\ttheirControl.Stop()\n}\n\nfunc TestStage1Race(t *testing.T) {\n\t// This tests ensures that two hosts handshaking with each other at the same time will allow traffic to flow\n\t// But will eventually collapse down to a single tunnel\n\n\tca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})\n\tmyControl, myVpnIpNet, myUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, \"me  \", \"10.128.0.1/24\", nil)\n\ttheirControl, theirVpnIpNet, theirUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, \"them\", \"10.128.0.2/24\", nil)\n\n\t// Put their info in our lighthouse and vice versa\n\tmyControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)\n\ttheirControl.InjectLightHouseAddr(myVpnIpNet[0].Addr(), myUdpAddr)\n\n\t// Build a router so we don't have to reason who gets which packet\n\tr := router.NewR(t, myControl, theirControl)\n\tdefer r.RenderFlow()\n\n\t// Start the servers\n\tmyControl.Start()\n\ttheirControl.Start()\n\n\tt.Log(\"Trigger a handshake to start on both me and them\")\n\tmyControl.InjectTunUDPPacket(theirVpnIpNet[0].Addr(), 80, myVpnIpNet[0].Addr(), 80, []byte(\"Hi from me\"))\n\ttheirControl.InjectTunUDPPacket(myVpnIpNet[0].Addr(), 80, theirVpnIpNet[0].Addr(), 80, []byte(\"Hi from them\"))\n\n\tt.Log(\"Get both stage 1 handshake packets\")\n\tmyHsForThem := myControl.GetFromUDP(true)\n\ttheirHsForMe := theirControl.GetFromUDP(true)\n\n\tr.Log(\"Now inject both stage 1 handshake packets\")\n\tr.InjectUDPPacket(theirControl, myControl, theirHsForMe)\n\tr.InjectUDPPacket(myControl, theirControl, myHsForThem)\n\n\tr.Log(\"Route until they receive a message packet\")\n\tmyCachedPacket := r.RouteForAllUntilTxTun(theirControl)\n\tassertUdpPacket(t, []byte(\"Hi from me\"), myCachedPacket, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), 80, 80)\n\n\tr.Log(\"Their cached packet should be received by me\")\n\ttheirCachedPacket := r.RouteForAllUntilTxTun(myControl)\n\tassertUdpPacket(t, []byte(\"Hi from them\"), theirCachedPacket, theirVpnIpNet[0].Addr(), myVpnIpNet[0].Addr(), 80, 80)\n\n\tr.Log(\"Do a bidirectional tunnel test\")\n\tassertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)\n\n\tmyHostmapHosts := myControl.ListHostmapHosts(false)\n\tmyHostmapIndexes := myControl.ListHostmapIndexes(false)\n\ttheirHostmapHosts := theirControl.ListHostmapHosts(false)\n\ttheirHostmapIndexes := theirControl.ListHostmapIndexes(false)\n\n\t// We should have two tunnels on both sides\n\tassert.Len(t, myHostmapHosts, 1)\n\tassert.Len(t, theirHostmapHosts, 1)\n\tassert.Len(t, myHostmapIndexes, 2)\n\tassert.Len(t, theirHostmapIndexes, 2)\n\n\tr.RenderHostmaps(\"Starting hostmaps\", myControl, theirControl)\n\n\tr.Log(\"Spin until connection manager tears down a tunnel\")\n\n\tfor len(myControl.GetHostmap().Indexes)+len(theirControl.GetHostmap().Indexes) > 2 {\n\t\tassertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)\n\t\tt.Log(\"Connection manager hasn't ticked yet\")\n\t\ttime.Sleep(time.Second)\n\t}\n\n\tmyFinalHostmapHosts := myControl.ListHostmapHosts(false)\n\tmyFinalHostmapIndexes := myControl.ListHostmapIndexes(false)\n\ttheirFinalHostmapHosts := theirControl.ListHostmapHosts(false)\n\ttheirFinalHostmapIndexes := theirControl.ListHostmapIndexes(false)\n\n\t// We should only have a single tunnel now on both sides\n\tassert.Len(t, myFinalHostmapHosts, 1)\n\tassert.Len(t, theirFinalHostmapHosts, 1)\n\tassert.Len(t, myFinalHostmapIndexes, 1)\n\tassert.Len(t, theirFinalHostmapIndexes, 1)\n\n\tr.RenderHostmaps(\"Final hostmaps\", myControl, theirControl)\n\tmyControl.Stop()\n\ttheirControl.Stop()\n}\n\nfunc TestUncleanShutdownRaceLoser(t *testing.T) {\n\tca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})\n\tmyControl, myVpnIpNet, myUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, \"me  \", \"10.128.0.1/24\", nil)\n\ttheirControl, theirVpnIpNet, theirUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, \"them\", \"10.128.0.2/24\", nil)\n\n\t// Teach my how to get to the relay and that their can be reached via the relay\n\tmyControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)\n\ttheirControl.InjectLightHouseAddr(myVpnIpNet[0].Addr(), myUdpAddr)\n\n\t// Build a router so we don't have to reason who gets which packet\n\tr := router.NewR(t, myControl, theirControl)\n\tdefer r.RenderFlow()\n\n\t// Start the servers\n\tmyControl.Start()\n\ttheirControl.Start()\n\n\tr.Log(\"Trigger a handshake from me to them\")\n\tmyControl.InjectTunUDPPacket(theirVpnIpNet[0].Addr(), 80, myVpnIpNet[0].Addr(), 80, []byte(\"Hi from me\"))\n\n\tp := r.RouteForAllUntilTxTun(theirControl)\n\tassertUdpPacket(t, []byte(\"Hi from me\"), p, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), 80, 80)\n\n\tr.Log(\"Nuke my hostmap\")\n\tmyHostmap := myControl.GetHostmap()\n\tmyHostmap.Hosts = map[netip.Addr]*nebula.HostInfo{}\n\tmyHostmap.Indexes = map[uint32]*nebula.HostInfo{}\n\tmyHostmap.RemoteIndexes = map[uint32]*nebula.HostInfo{}\n\n\tmyControl.InjectTunUDPPacket(theirVpnIpNet[0].Addr(), 80, myVpnIpNet[0].Addr(), 80, []byte(\"Hi from me again\"))\n\tp = r.RouteForAllUntilTxTun(theirControl)\n\tassertUdpPacket(t, []byte(\"Hi from me again\"), p, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), 80, 80)\n\n\tr.Log(\"Assert the tunnel works\")\n\tassertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)\n\n\tr.Log(\"Wait for the dead index to go away\")\n\tstart := len(theirControl.GetHostmap().Indexes)\n\tfor {\n\t\tassertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)\n\t\tif len(theirControl.GetHostmap().Indexes) < start {\n\t\t\tbreak\n\t\t}\n\t\ttime.Sleep(time.Second)\n\t}\n\n\tr.RenderHostmaps(\"Final hostmaps\", myControl, theirControl)\n}\n\nfunc TestUncleanShutdownRaceWinner(t *testing.T) {\n\tca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})\n\tmyControl, myVpnIpNet, myUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, \"me  \", \"10.128.0.1/24\", nil)\n\ttheirControl, theirVpnIpNet, theirUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, \"them\", \"10.128.0.2/24\", nil)\n\n\t// Teach my how to get to the relay and that their can be reached via the relay\n\tmyControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)\n\ttheirControl.InjectLightHouseAddr(myVpnIpNet[0].Addr(), myUdpAddr)\n\n\t// Build a router so we don't have to reason who gets which packet\n\tr := router.NewR(t, myControl, theirControl)\n\tdefer r.RenderFlow()\n\n\t// Start the servers\n\tmyControl.Start()\n\ttheirControl.Start()\n\n\tr.Log(\"Trigger a handshake from me to them\")\n\tmyControl.InjectTunUDPPacket(theirVpnIpNet[0].Addr(), 80, myVpnIpNet[0].Addr(), 80, []byte(\"Hi from me\"))\n\n\tp := r.RouteForAllUntilTxTun(theirControl)\n\tassertUdpPacket(t, []byte(\"Hi from me\"), p, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), 80, 80)\n\tr.RenderHostmaps(\"Final hostmaps\", myControl, theirControl)\n\n\tr.Log(\"Nuke my hostmap\")\n\ttheirHostmap := theirControl.GetHostmap()\n\ttheirHostmap.Hosts = map[netip.Addr]*nebula.HostInfo{}\n\ttheirHostmap.Indexes = map[uint32]*nebula.HostInfo{}\n\ttheirHostmap.RemoteIndexes = map[uint32]*nebula.HostInfo{}\n\n\ttheirControl.InjectTunUDPPacket(myVpnIpNet[0].Addr(), 80, theirVpnIpNet[0].Addr(), 80, []byte(\"Hi from them again\"))\n\tp = r.RouteForAllUntilTxTun(myControl)\n\tassertUdpPacket(t, []byte(\"Hi from them again\"), p, theirVpnIpNet[0].Addr(), myVpnIpNet[0].Addr(), 80, 80)\n\tr.RenderHostmaps(\"Derp hostmaps\", myControl, theirControl)\n\n\tr.Log(\"Assert the tunnel works\")\n\tassertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)\n\n\tr.Log(\"Wait for the dead index to go away\")\n\tstart := len(myControl.GetHostmap().Indexes)\n\tfor {\n\t\tassertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)\n\t\tif len(myControl.GetHostmap().Indexes) < start {\n\t\t\tbreak\n\t\t}\n\t\ttime.Sleep(time.Second)\n\t}\n\n\tr.RenderHostmaps(\"Final hostmaps\", myControl, theirControl)\n}\n\nfunc TestRelays(t *testing.T) {\n\tca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})\n\tmyControl, myVpnIpNet, _, _ := newSimpleServer(cert.Version1, ca, caKey, \"me     \", \"10.128.0.1/24\", m{\"relay\": m{\"use_relays\": true}})\n\trelayControl, relayVpnIpNet, relayUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, \"relay  \", \"10.128.0.128/24\", m{\"relay\": m{\"am_relay\": true}})\n\ttheirControl, theirVpnIpNet, theirUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, \"them   \", \"10.128.0.2/24\", m{\"relay\": m{\"use_relays\": true}})\n\n\t// Teach my how to get to the relay and that their can be reached via the relay\n\tmyControl.InjectLightHouseAddr(relayVpnIpNet[0].Addr(), relayUdpAddr)\n\tmyControl.InjectRelays(theirVpnIpNet[0].Addr(), []netip.Addr{relayVpnIpNet[0].Addr()})\n\trelayControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)\n\n\t// Build a router so we don't have to reason who gets which packet\n\tr := router.NewR(t, myControl, relayControl, theirControl)\n\tdefer r.RenderFlow()\n\n\t// Start the servers\n\tmyControl.Start()\n\trelayControl.Start()\n\ttheirControl.Start()\n\n\tt.Log(\"Trigger a handshake from me to them via the relay\")\n\tmyControl.InjectTunUDPPacket(theirVpnIpNet[0].Addr(), 80, myVpnIpNet[0].Addr(), 80, []byte(\"Hi from me\"))\n\n\tp := r.RouteForAllUntilTxTun(theirControl)\n\tr.Log(\"Assert the tunnel works\")\n\tassertUdpPacket(t, []byte(\"Hi from me\"), p, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), 80, 80)\n\tr.RenderHostmaps(\"Final hostmaps\", myControl, relayControl, theirControl)\n}\n\nfunc TestRelaysDontCareAboutIps(t *testing.T) {\n\tca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})\n\tmyControl, myVpnIpNet, _, _ := newSimpleServer(cert.Version2, ca, caKey, \"me     \", \"10.128.0.1/24\", m{\"relay\": m{\"use_relays\": true}})\n\trelayControl, relayVpnIpNet, relayUdpAddr, _ := newSimpleServer(cert.Version2, ca, caKey, \"relay  \", \"2001::9999/24\", m{\"relay\": m{\"am_relay\": true}})\n\ttheirControl, theirVpnIpNet, theirUdpAddr, _ := newSimpleServer(cert.Version2, ca, caKey, \"them   \", \"10.128.0.2/24\", m{\"relay\": m{\"use_relays\": true}})\n\n\t// Teach my how to get to the relay and that their can be reached via the relay\n\tmyControl.InjectLightHouseAddr(relayVpnIpNet[0].Addr(), relayUdpAddr)\n\tmyControl.InjectRelays(theirVpnIpNet[0].Addr(), []netip.Addr{relayVpnIpNet[0].Addr()})\n\trelayControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)\n\n\t// Build a router so we don't have to reason who gets which packet\n\tr := router.NewR(t, myControl, relayControl, theirControl)\n\tdefer r.RenderFlow()\n\n\t// Start the servers\n\tmyControl.Start()\n\trelayControl.Start()\n\ttheirControl.Start()\n\n\tt.Log(\"Trigger a handshake from me to them via the relay\")\n\tmyControl.InjectTunUDPPacket(theirVpnIpNet[0].Addr(), 80, myVpnIpNet[0].Addr(), 80, []byte(\"Hi from me\"))\n\n\tp := r.RouteForAllUntilTxTun(theirControl)\n\tr.Log(\"Assert the tunnel works\")\n\tassertUdpPacket(t, []byte(\"Hi from me\"), p, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), 80, 80)\n\tr.RenderHostmaps(\"Final hostmaps\", myControl, relayControl, theirControl)\n}\n\nfunc TestReestablishRelays(t *testing.T) {\n\tca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})\n\tmyControl, myVpnIpNet, _, _ := newSimpleServer(cert.Version1, ca, caKey, \"me     \", \"10.128.0.1/24\", m{\"relay\": m{\"use_relays\": true}})\n\trelayControl, relayVpnIpNet, relayUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, \"relay  \", \"10.128.0.128/24\", m{\"relay\": m{\"am_relay\": true}})\n\ttheirControl, theirVpnIpNet, theirUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, \"them   \", \"10.128.0.2/24\", m{\"relay\": m{\"use_relays\": true}})\n\n\t// Teach my how to get to the relay and that their can be reached via the relay\n\tmyControl.InjectLightHouseAddr(relayVpnIpNet[0].Addr(), relayUdpAddr)\n\tmyControl.InjectRelays(theirVpnIpNet[0].Addr(), []netip.Addr{relayVpnIpNet[0].Addr()})\n\trelayControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)\n\n\t// Build a router so we don't have to reason who gets which packet\n\tr := router.NewR(t, myControl, relayControl, theirControl)\n\tdefer r.RenderFlow()\n\n\t// Start the servers\n\tmyControl.Start()\n\trelayControl.Start()\n\ttheirControl.Start()\n\n\tt.Log(\"Trigger a handshake from me to them via the relay\")\n\tmyControl.InjectTunUDPPacket(theirVpnIpNet[0].Addr(), 80, myVpnIpNet[0].Addr(), 80, []byte(\"Hi from me\"))\n\n\tp := r.RouteForAllUntilTxTun(theirControl)\n\tr.Log(\"Assert the tunnel works\")\n\tassertUdpPacket(t, []byte(\"Hi from me\"), p, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), 80, 80)\n\n\tt.Log(\"Ensure packet traversal from them to me via the relay\")\n\ttheirControl.InjectTunUDPPacket(myVpnIpNet[0].Addr(), 80, theirVpnIpNet[0].Addr(), 80, []byte(\"Hi from them\"))\n\n\tp = r.RouteForAllUntilTxTun(myControl)\n\tr.Log(\"Assert the tunnel works\")\n\tassertUdpPacket(t, []byte(\"Hi from them\"), p, theirVpnIpNet[0].Addr(), myVpnIpNet[0].Addr(), 80, 80)\n\n\t// If we break the relay's connection to 'them', 'me' needs to detect and recover the connection\n\tr.Log(\"Close the tunnel\")\n\trelayControl.CloseTunnel(theirVpnIpNet[0].Addr(), true)\n\n\tstart := len(myControl.GetHostmap().Indexes)\n\tcurIndexes := len(myControl.GetHostmap().Indexes)\n\tfor curIndexes >= start {\n\t\tcurIndexes = len(myControl.GetHostmap().Indexes)\n\t\tr.Logf(\"Wait for the dead index to go away:start=%v indexes, current=%v indexes\", start, curIndexes)\n\t\tmyControl.InjectTunUDPPacket(theirVpnIpNet[0].Addr(), 80, myVpnIpNet[0].Addr(), 80, []byte(\"Hi from me should fail\"))\n\n\t\tr.RouteForAllExitFunc(func(p *udp.Packet, c *nebula.Control) router.ExitType {\n\t\t\treturn router.RouteAndExit\n\t\t})\n\t\ttime.Sleep(2 * time.Second)\n\t}\n\tr.Log(\"Dead index went away. Woot!\")\n\tr.RenderHostmaps(\"Me removed hostinfo\", myControl, relayControl, theirControl)\n\t// Next packet should re-establish a relayed connection and work just great.\n\n\tt.Logf(\"Assert the tunnel...\")\n\tfor {\n\t\tt.Log(\"RouteForAllUntilTxTun\")\n\t\tmyControl.InjectLightHouseAddr(relayVpnIpNet[0].Addr(), relayUdpAddr)\n\t\tmyControl.InjectRelays(theirVpnIpNet[0].Addr(), []netip.Addr{relayVpnIpNet[0].Addr()})\n\t\trelayControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)\n\t\tmyControl.InjectTunUDPPacket(theirVpnIpNet[0].Addr(), 80, myVpnIpNet[0].Addr(), 80, []byte(\"Hi from me\"))\n\n\t\tp = r.RouteForAllUntilTxTun(theirControl)\n\t\tr.Log(\"Assert the tunnel works\")\n\t\tpacket := gopacket.NewPacket(p, layers.LayerTypeIPv4, gopacket.Lazy)\n\t\tv4 := packet.Layer(layers.LayerTypeIPv4).(*layers.IPv4)\n\t\tif slices.Compare(v4.SrcIP, myVpnIpNet[0].Addr().AsSlice()) != 0 {\n\t\t\tt.Logf(\"SrcIP is unexpected...this is not the packet I'm looking for. Keep looking\")\n\t\t\tcontinue\n\t\t}\n\t\tif slices.Compare(v4.DstIP, theirVpnIpNet[0].Addr().AsSlice()) != 0 {\n\t\t\tt.Logf(\"DstIP is unexpected...this is not the packet I'm looking for. Keep looking\")\n\t\t\tcontinue\n\t\t}\n\n\t\tudp := packet.Layer(layers.LayerTypeUDP).(*layers.UDP)\n\t\tif udp == nil {\n\t\t\tt.Log(\"Not a UDP packet. This is not the packet I'm looking for. Keep looking\")\n\t\t\tcontinue\n\t\t}\n\t\tdata := packet.ApplicationLayer()\n\t\tif data == nil {\n\t\t\tt.Log(\"No data found in packet. This is not the packet I'm looking for. Keep looking.\")\n\t\t\tcontinue\n\t\t}\n\t\tif string(data.Payload()) != \"Hi from me\" {\n\t\t\tt.Logf(\"Unexpected payload: '%v', keep looking\", string(data.Payload()))\n\t\t\tcontinue\n\t\t}\n\t\tt.Log(\"I found my lost packet. I am so happy.\")\n\t\tbreak\n\t}\n\tt.Log(\"Assert the tunnel works the other way, too\")\n\tfor {\n\t\tt.Log(\"RouteForAllUntilTxTun\")\n\t\ttheirControl.InjectTunUDPPacket(myVpnIpNet[0].Addr(), 80, theirVpnIpNet[0].Addr(), 80, []byte(\"Hi from them\"))\n\n\t\tp = r.RouteForAllUntilTxTun(myControl)\n\t\tr.Log(\"Assert the tunnel works\")\n\t\tpacket := gopacket.NewPacket(p, layers.LayerTypeIPv4, gopacket.Lazy)\n\t\tv4 := packet.Layer(layers.LayerTypeIPv4).(*layers.IPv4)\n\t\tif slices.Compare(v4.DstIP, myVpnIpNet[0].Addr().AsSlice()) != 0 {\n\t\t\tt.Logf(\"Dst is unexpected...this is not the packet I'm looking for. Keep looking\")\n\t\t\tcontinue\n\t\t}\n\t\tif slices.Compare(v4.SrcIP, theirVpnIpNet[0].Addr().AsSlice()) != 0 {\n\t\t\tt.Logf(\"SrcIP is unexpected...this is not the packet I'm looking for. Keep looking\")\n\t\t\tcontinue\n\t\t}\n\n\t\tudp := packet.Layer(layers.LayerTypeUDP).(*layers.UDP)\n\t\tif udp == nil {\n\t\t\tt.Log(\"Not a UDP packet. This is not the packet I'm looking for. Keep looking\")\n\t\t\tcontinue\n\t\t}\n\t\tdata := packet.ApplicationLayer()\n\t\tif data == nil {\n\t\t\tt.Log(\"No data found in packet. This is not the packet I'm looking for. Keep looking.\")\n\t\t\tcontinue\n\t\t}\n\t\tif string(data.Payload()) != \"Hi from them\" {\n\t\t\tt.Logf(\"Unexpected payload: '%v', keep looking\", string(data.Payload()))\n\t\t\tcontinue\n\t\t}\n\t\tt.Log(\"I found my lost packet. I am so happy.\")\n\t\tbreak\n\t}\n\tr.RenderHostmaps(\"Final hostmaps\", myControl, relayControl, theirControl)\n\n}\n\nfunc TestStage1RaceRelays(t *testing.T) {\n\t//NOTE: this is a race between me and relay resulting in a full tunnel from me to them via relay\n\tca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})\n\tmyControl, myVpnIpNet, myUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, \"me     \", \"10.128.0.1/24\", m{\"relay\": m{\"use_relays\": true}})\n\trelayControl, relayVpnIpNet, relayUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, \"relay  \", \"10.128.0.128/24\", m{\"relay\": m{\"am_relay\": true}})\n\ttheirControl, theirVpnIpNet, theirUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, \"them   \", \"10.128.0.2/24\", m{\"relay\": m{\"use_relays\": true}})\n\n\t// Teach my how to get to the relay and that their can be reached via the relay\n\tmyControl.InjectLightHouseAddr(relayVpnIpNet[0].Addr(), relayUdpAddr)\n\ttheirControl.InjectLightHouseAddr(relayVpnIpNet[0].Addr(), relayUdpAddr)\n\n\tmyControl.InjectRelays(theirVpnIpNet[0].Addr(), []netip.Addr{relayVpnIpNet[0].Addr()})\n\ttheirControl.InjectRelays(myVpnIpNet[0].Addr(), []netip.Addr{relayVpnIpNet[0].Addr()})\n\n\trelayControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)\n\trelayControl.InjectLightHouseAddr(myVpnIpNet[0].Addr(), myUdpAddr)\n\n\t// Build a router so we don't have to reason who gets which packet\n\tr := router.NewR(t, myControl, relayControl, theirControl)\n\tdefer r.RenderFlow()\n\n\t// Start the servers\n\tmyControl.Start()\n\trelayControl.Start()\n\ttheirControl.Start()\n\n\tr.Log(\"Get a tunnel between me and relay\")\n\tassertTunnel(t, myVpnIpNet[0].Addr(), relayVpnIpNet[0].Addr(), myControl, relayControl, r)\n\n\tr.Log(\"Get a tunnel between them and relay\")\n\tassertTunnel(t, theirVpnIpNet[0].Addr(), relayVpnIpNet[0].Addr(), theirControl, relayControl, r)\n\n\tr.Log(\"Trigger a handshake from both them and me via relay to them and me\")\n\tmyControl.InjectTunUDPPacket(theirVpnIpNet[0].Addr(), 80, myVpnIpNet[0].Addr(), 80, []byte(\"Hi from me\"))\n\ttheirControl.InjectTunUDPPacket(myVpnIpNet[0].Addr(), 80, theirVpnIpNet[0].Addr(), 80, []byte(\"Hi from them\"))\n\n\tr.Log(\"Wait for a packet from them to me\")\n\tp := r.RouteForAllUntilTxTun(myControl)\n\t_ = p\n\n\tr.FlushAll()\n\n\tmyControl.Stop()\n\ttheirControl.Stop()\n\trelayControl.Stop()\n}\n\nfunc TestStage1RaceRelays2(t *testing.T) {\n\t//NOTE: this is a race between me and relay resulting in a full tunnel from me to them via relay\n\tca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})\n\tmyControl, myVpnIpNet, myUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, \"me     \", \"10.128.0.1/24\", m{\"relay\": m{\"use_relays\": true}})\n\trelayControl, relayVpnIpNet, relayUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, \"relay  \", \"10.128.0.128/24\", m{\"relay\": m{\"am_relay\": true}})\n\ttheirControl, theirVpnIpNet, theirUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, \"them   \", \"10.128.0.2/24\", m{\"relay\": m{\"use_relays\": true}})\n\tl := NewTestLogger()\n\n\t// Teach my how to get to the relay and that their can be reached via the relay\n\tmyControl.InjectLightHouseAddr(relayVpnIpNet[0].Addr(), relayUdpAddr)\n\ttheirControl.InjectLightHouseAddr(relayVpnIpNet[0].Addr(), relayUdpAddr)\n\n\tmyControl.InjectRelays(theirVpnIpNet[0].Addr(), []netip.Addr{relayVpnIpNet[0].Addr()})\n\ttheirControl.InjectRelays(myVpnIpNet[0].Addr(), []netip.Addr{relayVpnIpNet[0].Addr()})\n\n\trelayControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)\n\trelayControl.InjectLightHouseAddr(myVpnIpNet[0].Addr(), myUdpAddr)\n\n\t// Build a router so we don't have to reason who gets which packet\n\tr := router.NewR(t, myControl, relayControl, theirControl)\n\tdefer r.RenderFlow()\n\n\t// Start the servers\n\tmyControl.Start()\n\trelayControl.Start()\n\ttheirControl.Start()\n\n\tr.Log(\"Get a tunnel between me and relay\")\n\tl.Info(\"Get a tunnel between me and relay\")\n\tassertTunnel(t, myVpnIpNet[0].Addr(), relayVpnIpNet[0].Addr(), myControl, relayControl, r)\n\n\tr.Log(\"Get a tunnel between them and relay\")\n\tl.Info(\"Get a tunnel between them and relay\")\n\tassertTunnel(t, theirVpnIpNet[0].Addr(), relayVpnIpNet[0].Addr(), theirControl, relayControl, r)\n\n\tr.Log(\"Trigger a handshake from both them and me via relay to them and me\")\n\tl.Info(\"Trigger a handshake from both them and me via relay to them and me\")\n\tmyControl.InjectTunUDPPacket(theirVpnIpNet[0].Addr(), 80, myVpnIpNet[0].Addr(), 80, []byte(\"Hi from me\"))\n\ttheirControl.InjectTunUDPPacket(myVpnIpNet[0].Addr(), 80, theirVpnIpNet[0].Addr(), 80, []byte(\"Hi from them\"))\n\n\t//r.RouteUntilAfterMsgType(myControl, header.Control, header.MessageNone)\n\t//r.RouteUntilAfterMsgType(theirControl, header.Control, header.MessageNone)\n\n\tr.Log(\"Wait for a packet from them to me\")\n\tl.Info(\"Wait for a packet from them to me; myControl\")\n\tr.RouteForAllUntilTxTun(myControl)\n\tl.Info(\"Wait for a packet from them to me; theirControl\")\n\tr.RouteForAllUntilTxTun(theirControl)\n\n\tr.Log(\"Assert the tunnel works\")\n\tl.Info(\"Assert the tunnel works\")\n\tassertTunnel(t, theirVpnIpNet[0].Addr(), myVpnIpNet[0].Addr(), theirControl, myControl, r)\n\n\tt.Log(\"Wait until we remove extra tunnels\")\n\tl.Info(\"Wait until we remove extra tunnels\")\n\tl.WithFields(\n\t\tlogrus.Fields{\n\t\t\t\"myControl\":    len(myControl.GetHostmap().Indexes),\n\t\t\t\"theirControl\": len(theirControl.GetHostmap().Indexes),\n\t\t\t\"relayControl\": len(relayControl.GetHostmap().Indexes),\n\t\t}).Info(\"Waiting for hostinfos to be removed...\")\n\thostInfos := len(myControl.GetHostmap().Indexes) + len(theirControl.GetHostmap().Indexes) + len(relayControl.GetHostmap().Indexes)\n\tretries := 60\n\tfor hostInfos > 6 && retries > 0 {\n\t\thostInfos = len(myControl.GetHostmap().Indexes) + len(theirControl.GetHostmap().Indexes) + len(relayControl.GetHostmap().Indexes)\n\t\tl.WithFields(\n\t\t\tlogrus.Fields{\n\t\t\t\t\"myControl\":    len(myControl.GetHostmap().Indexes),\n\t\t\t\t\"theirControl\": len(theirControl.GetHostmap().Indexes),\n\t\t\t\t\"relayControl\": len(relayControl.GetHostmap().Indexes),\n\t\t\t}).Info(\"Waiting for hostinfos to be removed...\")\n\t\tassertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)\n\t\tt.Log(\"Connection manager hasn't ticked yet\")\n\t\ttime.Sleep(time.Second)\n\t\tretries--\n\t}\n\n\tr.Log(\"Assert the tunnel works\")\n\tl.Info(\"Assert the tunnel works\")\n\tassertTunnel(t, theirVpnIpNet[0].Addr(), myVpnIpNet[0].Addr(), theirControl, myControl, r)\n\n\tmyControl.Stop()\n\ttheirControl.Stop()\n\trelayControl.Stop()\n}\n\nfunc TestRehandshakingRelays(t *testing.T) {\n\tca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})\n\tmyControl, myVpnIpNet, _, _ := newSimpleServer(cert.Version1, ca, caKey, \"me     \", \"10.128.0.1/24\", m{\"relay\": m{\"use_relays\": true}})\n\trelayControl, relayVpnIpNet, relayUdpAddr, relayConfig := newSimpleServer(cert.Version1, ca, caKey, \"relay  \", \"10.128.0.128/24\", m{\"relay\": m{\"am_relay\": true}})\n\ttheirControl, theirVpnIpNet, theirUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, \"them   \", \"10.128.0.2/24\", m{\"relay\": m{\"use_relays\": true}})\n\n\t// Teach my how to get to the relay and that their can be reached via the relay\n\tmyControl.InjectLightHouseAddr(relayVpnIpNet[0].Addr(), relayUdpAddr)\n\tmyControl.InjectRelays(theirVpnIpNet[0].Addr(), []netip.Addr{relayVpnIpNet[0].Addr()})\n\trelayControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)\n\n\t// Build a router so we don't have to reason who gets which packet\n\tr := router.NewR(t, myControl, relayControl, theirControl)\n\tdefer r.RenderFlow()\n\n\t// Start the servers\n\tmyControl.Start()\n\trelayControl.Start()\n\ttheirControl.Start()\n\n\tt.Log(\"Trigger a handshake from me to them via the relay\")\n\tmyControl.InjectTunUDPPacket(theirVpnIpNet[0].Addr(), 80, myVpnIpNet[0].Addr(), 80, []byte(\"Hi from me\"))\n\n\tp := r.RouteForAllUntilTxTun(theirControl)\n\tr.Log(\"Assert the tunnel works\")\n\tassertUdpPacket(t, []byte(\"Hi from me\"), p, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), 80, 80)\n\tr.RenderHostmaps(\"working hostmaps\", myControl, relayControl, theirControl)\n\n\t// When I update the certificate for the relay, both me and them will have 2 host infos for the relay,\n\t// and the main host infos will not have any relay state to handle the me<->relay<->them tunnel.\n\tr.Log(\"Renew relay certificate and spin until me and them sees it\")\n\t_, _, myNextPrivKey, myNextPEM := cert_test.NewTestCert(cert.Version1, cert.Curve_CURVE25519, ca, caKey, \"relay\", time.Now(), time.Now().Add(5*time.Minute), relayVpnIpNet, nil, []string{\"new group\"})\n\n\tcaB, err := ca.MarshalPEM()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\trelayConfig.Settings[\"pki\"] = m{\n\t\t\"ca\":   string(caB),\n\t\t\"cert\": string(myNextPEM),\n\t\t\"key\":  string(myNextPrivKey),\n\t}\n\trc, err := yaml.Marshal(relayConfig.Settings)\n\trequire.NoError(t, err)\n\trelayConfig.ReloadConfigString(string(rc))\n\n\tfor {\n\t\tr.Log(\"Assert the tunnel works between myVpnIpNet and relayVpnIpNet\")\n\t\tassertTunnel(t, myVpnIpNet[0].Addr(), relayVpnIpNet[0].Addr(), myControl, relayControl, r)\n\t\tc := myControl.GetHostInfoByVpnAddr(relayVpnIpNet[0].Addr(), false)\n\t\tif len(c.Cert.Groups()) != 0 {\n\t\t\t// We have a new certificate now\n\t\t\tr.Log(\"Certificate between my and relay is updated!\")\n\t\t\tbreak\n\t\t}\n\n\t\ttime.Sleep(time.Second)\n\t}\n\n\tfor {\n\t\tr.Log(\"Assert the tunnel works between theirVpnIpNet and relayVpnIpNet\")\n\t\tassertTunnel(t, theirVpnIpNet[0].Addr(), relayVpnIpNet[0].Addr(), theirControl, relayControl, r)\n\t\tc := theirControl.GetHostInfoByVpnAddr(relayVpnIpNet[0].Addr(), false)\n\t\tif len(c.Cert.Groups()) != 0 {\n\t\t\t// We have a new certificate now\n\t\t\tr.Log(\"Certificate between their and relay is updated!\")\n\t\t\tbreak\n\t\t}\n\n\t\ttime.Sleep(time.Second)\n\t}\n\n\tr.Log(\"Assert the relay tunnel still works\")\n\tassertTunnel(t, theirVpnIpNet[0].Addr(), myVpnIpNet[0].Addr(), theirControl, myControl, r)\n\tr.RenderHostmaps(\"working hostmaps\", myControl, relayControl, theirControl)\n\t// We should have two hostinfos on all sides\n\tfor len(myControl.GetHostmap().Indexes) != 2 {\n\t\tt.Logf(\"Waiting for myControl hostinfos (%v != 2) to get cleaned up from lack of use...\", len(myControl.GetHostmap().Indexes))\n\t\tr.Log(\"Assert the relay tunnel still works\")\n\t\tassertTunnel(t, theirVpnIpNet[0].Addr(), myVpnIpNet[0].Addr(), theirControl, myControl, r)\n\t\tr.Log(\"yupitdoes\")\n\t\ttime.Sleep(time.Second)\n\t}\n\tt.Logf(\"myControl hostinfos got cleaned up!\")\n\tfor len(theirControl.GetHostmap().Indexes) != 2 {\n\t\tt.Logf(\"Waiting for theirControl hostinfos (%v != 2) to get cleaned up from lack of use...\", len(theirControl.GetHostmap().Indexes))\n\t\tr.Log(\"Assert the relay tunnel still works\")\n\t\tassertTunnel(t, theirVpnIpNet[0].Addr(), myVpnIpNet[0].Addr(), theirControl, myControl, r)\n\t\tr.Log(\"yupitdoes\")\n\t\ttime.Sleep(time.Second)\n\t}\n\tt.Logf(\"theirControl hostinfos got cleaned up!\")\n\tfor len(relayControl.GetHostmap().Indexes) != 2 {\n\t\tt.Logf(\"Waiting for relayControl hostinfos (%v != 2) to get cleaned up from lack of use...\", len(relayControl.GetHostmap().Indexes))\n\t\tr.Log(\"Assert the relay tunnel still works\")\n\t\tassertTunnel(t, theirVpnIpNet[0].Addr(), myVpnIpNet[0].Addr(), theirControl, myControl, r)\n\t\tr.Log(\"yupitdoes\")\n\t\ttime.Sleep(time.Second)\n\t}\n\tt.Logf(\"relayControl hostinfos got cleaned up!\")\n}\n\nfunc TestRehandshakingRelaysPrimary(t *testing.T) {\n\t// This test is the same as TestRehandshakingRelays but one of the terminal types is a primary swap winner\n\tca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})\n\tmyControl, myVpnIpNet, _, _ := newSimpleServer(cert.Version1, ca, caKey, \"me     \", \"10.128.0.128/24\", m{\"relay\": m{\"use_relays\": true}})\n\trelayControl, relayVpnIpNet, relayUdpAddr, relayConfig := newSimpleServer(cert.Version1, ca, caKey, \"relay  \", \"10.128.0.1/24\", m{\"relay\": m{\"am_relay\": true}})\n\ttheirControl, theirVpnIpNet, theirUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, \"them   \", \"10.128.0.2/24\", m{\"relay\": m{\"use_relays\": true}})\n\n\t// Teach my how to get to the relay and that their can be reached via the relay\n\tmyControl.InjectLightHouseAddr(relayVpnIpNet[0].Addr(), relayUdpAddr)\n\tmyControl.InjectRelays(theirVpnIpNet[0].Addr(), []netip.Addr{relayVpnIpNet[0].Addr()})\n\trelayControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)\n\n\t// Build a router so we don't have to reason who gets which packet\n\tr := router.NewR(t, myControl, relayControl, theirControl)\n\tdefer r.RenderFlow()\n\n\t// Start the servers\n\tmyControl.Start()\n\trelayControl.Start()\n\ttheirControl.Start()\n\n\tt.Log(\"Trigger a handshake from me to them via the relay\")\n\tmyControl.InjectTunUDPPacket(theirVpnIpNet[0].Addr(), 80, myVpnIpNet[0].Addr(), 80, []byte(\"Hi from me\"))\n\n\tp := r.RouteForAllUntilTxTun(theirControl)\n\tr.Log(\"Assert the tunnel works\")\n\tassertUdpPacket(t, []byte(\"Hi from me\"), p, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), 80, 80)\n\tr.RenderHostmaps(\"working hostmaps\", myControl, relayControl, theirControl)\n\n\t// When I update the certificate for the relay, both me and them will have 2 host infos for the relay,\n\t// and the main host infos will not have any relay state to handle the me<->relay<->them tunnel.\n\tr.Log(\"Renew relay certificate and spin until me and them sees it\")\n\t_, _, myNextPrivKey, myNextPEM := cert_test.NewTestCert(cert.Version1, cert.Curve_CURVE25519, ca, caKey, \"relay\", time.Now(), time.Now().Add(5*time.Minute), relayVpnIpNet, nil, []string{\"new group\"})\n\n\tcaB, err := ca.MarshalPEM()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\trelayConfig.Settings[\"pki\"] = m{\n\t\t\"ca\":   string(caB),\n\t\t\"cert\": string(myNextPEM),\n\t\t\"key\":  string(myNextPrivKey),\n\t}\n\trc, err := yaml.Marshal(relayConfig.Settings)\n\trequire.NoError(t, err)\n\trelayConfig.ReloadConfigString(string(rc))\n\n\tfor {\n\t\tr.Log(\"Assert the tunnel works between myVpnIpNet and relayVpnIpNet\")\n\t\tassertTunnel(t, myVpnIpNet[0].Addr(), relayVpnIpNet[0].Addr(), myControl, relayControl, r)\n\t\tc := myControl.GetHostInfoByVpnAddr(relayVpnIpNet[0].Addr(), false)\n\t\tif len(c.Cert.Groups()) != 0 {\n\t\t\t// We have a new certificate now\n\t\t\tr.Log(\"Certificate between my and relay is updated!\")\n\t\t\tbreak\n\t\t}\n\n\t\ttime.Sleep(time.Second)\n\t}\n\n\tfor {\n\t\tr.Log(\"Assert the tunnel works between theirVpnIpNet and relayVpnIpNet\")\n\t\tassertTunnel(t, theirVpnIpNet[0].Addr(), relayVpnIpNet[0].Addr(), theirControl, relayControl, r)\n\t\tc := theirControl.GetHostInfoByVpnAddr(relayVpnIpNet[0].Addr(), false)\n\t\tif len(c.Cert.Groups()) != 0 {\n\t\t\t// We have a new certificate now\n\t\t\tr.Log(\"Certificate between their and relay is updated!\")\n\t\t\tbreak\n\t\t}\n\n\t\ttime.Sleep(time.Second)\n\t}\n\n\tr.Log(\"Assert the relay tunnel still works\")\n\tassertTunnel(t, theirVpnIpNet[0].Addr(), myVpnIpNet[0].Addr(), theirControl, myControl, r)\n\tr.RenderHostmaps(\"working hostmaps\", myControl, relayControl, theirControl)\n\t// We should have two hostinfos on all sides\n\tfor len(myControl.GetHostmap().Indexes) != 2 {\n\t\tt.Logf(\"Waiting for myControl hostinfos (%v != 2) to get cleaned up from lack of use...\", len(myControl.GetHostmap().Indexes))\n\t\tr.Log(\"Assert the relay tunnel still works\")\n\t\tassertTunnel(t, theirVpnIpNet[0].Addr(), myVpnIpNet[0].Addr(), theirControl, myControl, r)\n\t\tr.Log(\"yupitdoes\")\n\t\ttime.Sleep(time.Second)\n\t}\n\tt.Logf(\"myControl hostinfos got cleaned up!\")\n\tfor len(theirControl.GetHostmap().Indexes) != 2 {\n\t\tt.Logf(\"Waiting for theirControl hostinfos (%v != 2) to get cleaned up from lack of use...\", len(theirControl.GetHostmap().Indexes))\n\t\tr.Log(\"Assert the relay tunnel still works\")\n\t\tassertTunnel(t, theirVpnIpNet[0].Addr(), myVpnIpNet[0].Addr(), theirControl, myControl, r)\n\t\tr.Log(\"yupitdoes\")\n\t\ttime.Sleep(time.Second)\n\t}\n\tt.Logf(\"theirControl hostinfos got cleaned up!\")\n\tfor len(relayControl.GetHostmap().Indexes) != 2 {\n\t\tt.Logf(\"Waiting for relayControl hostinfos (%v != 2) to get cleaned up from lack of use...\", len(relayControl.GetHostmap().Indexes))\n\t\tr.Log(\"Assert the relay tunnel still works\")\n\t\tassertTunnel(t, theirVpnIpNet[0].Addr(), myVpnIpNet[0].Addr(), theirControl, myControl, r)\n\t\tr.Log(\"yupitdoes\")\n\t\ttime.Sleep(time.Second)\n\t}\n\tt.Logf(\"relayControl hostinfos got cleaned up!\")\n}\n\nfunc TestRehandshaking(t *testing.T) {\n\tca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})\n\tmyControl, myVpnIpNet, myUdpAddr, myConfig := newSimpleServer(cert.Version1, ca, caKey, \"me  \", \"10.128.0.2/24\", nil)\n\ttheirControl, theirVpnIpNet, theirUdpAddr, theirConfig := newSimpleServer(cert.Version1, ca, caKey, \"them\", \"10.128.0.1/24\", nil)\n\n\t// Put their info in our lighthouse and vice versa\n\tmyControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)\n\ttheirControl.InjectLightHouseAddr(myVpnIpNet[0].Addr(), myUdpAddr)\n\n\t// Build a router so we don't have to reason who gets which packet\n\tr := router.NewR(t, myControl, theirControl)\n\tdefer r.RenderFlow()\n\n\t// Start the servers\n\tmyControl.Start()\n\ttheirControl.Start()\n\n\tt.Log(\"Stand up a tunnel between me and them\")\n\tassertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)\n\n\tr.RenderHostmaps(\"Starting hostmaps\", myControl, theirControl)\n\n\tr.Log(\"Renew my certificate and spin until their sees it\")\n\t_, _, myNextPrivKey, myNextPEM := cert_test.NewTestCert(cert.Version1, cert.Curve_CURVE25519, ca, caKey, \"me\", time.Now(), time.Now().Add(5*time.Minute), myVpnIpNet, nil, []string{\"new group\"})\n\n\tcaB, err := ca.MarshalPEM()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tmyConfig.Settings[\"pki\"] = m{\n\t\t\"ca\":   string(caB),\n\t\t\"cert\": string(myNextPEM),\n\t\t\"key\":  string(myNextPrivKey),\n\t}\n\trc, err := yaml.Marshal(myConfig.Settings)\n\trequire.NoError(t, err)\n\tmyConfig.ReloadConfigString(string(rc))\n\n\tfor {\n\t\tassertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)\n\t\tc := theirControl.GetHostInfoByVpnAddr(myVpnIpNet[0].Addr(), false)\n\t\tif len(c.Cert.Groups()) != 0 {\n\t\t\t// We have a new certificate now\n\t\t\tbreak\n\t\t}\n\n\t\ttime.Sleep(time.Second)\n\t}\n\n\tr.Log(\"Got the new cert\")\n\t// Flip their firewall to only allowing the new group to catch the tunnels reverting incorrectly\n\trc, err = yaml.Marshal(theirConfig.Settings)\n\trequire.NoError(t, err)\n\tvar theirNewConfig m\n\trequire.NoError(t, yaml.Unmarshal(rc, &theirNewConfig))\n\ttheirFirewall := theirNewConfig[\"firewall\"].(map[string]any)\n\ttheirFirewall[\"inbound\"] = []m{{\n\t\t\"proto\": \"any\",\n\t\t\"port\":  \"any\",\n\t\t\"group\": \"new group\",\n\t}}\n\trc, err = yaml.Marshal(theirNewConfig)\n\trequire.NoError(t, err)\n\ttheirConfig.ReloadConfigString(string(rc))\n\n\tr.Log(\"Spin until there is only 1 tunnel\")\n\tfor len(myControl.GetHostmap().Indexes)+len(theirControl.GetHostmap().Indexes) > 2 {\n\t\tassertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)\n\t\tt.Log(\"Connection manager hasn't ticked yet\")\n\t\ttime.Sleep(time.Second)\n\t}\n\n\tassertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)\n\tmyFinalHostmapHosts := myControl.ListHostmapHosts(false)\n\tmyFinalHostmapIndexes := myControl.ListHostmapIndexes(false)\n\ttheirFinalHostmapHosts := theirControl.ListHostmapHosts(false)\n\ttheirFinalHostmapIndexes := theirControl.ListHostmapIndexes(false)\n\n\t// Make sure the correct tunnel won\n\tc := theirControl.GetHostInfoByVpnAddr(myVpnIpNet[0].Addr(), false)\n\tassert.Contains(t, c.Cert.Groups(), \"new group\")\n\n\t// We should only have a single tunnel now on both sides\n\tassert.Len(t, myFinalHostmapHosts, 1)\n\tassert.Len(t, theirFinalHostmapHosts, 1)\n\tassert.Len(t, myFinalHostmapIndexes, 1)\n\tassert.Len(t, theirFinalHostmapIndexes, 1)\n\n\tr.RenderHostmaps(\"Final hostmaps\", myControl, theirControl)\n\n\tmyControl.Stop()\n\ttheirControl.Stop()\n}\n\nfunc TestRehandshakingLoser(t *testing.T) {\n\t// The purpose of this test is that the race loser renews their certificate and rehandshakes. The final tunnel\n\t// Should be the one with the new certificate\n\tca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})\n\tmyControl, myVpnIpNet, myUdpAddr, myConfig := newSimpleServer(cert.Version1, ca, caKey, \"me  \", \"10.128.0.2/24\", nil)\n\ttheirControl, theirVpnIpNet, theirUdpAddr, theirConfig := newSimpleServer(cert.Version1, ca, caKey, \"them\", \"10.128.0.1/24\", nil)\n\n\t// Put their info in our lighthouse and vice versa\n\tmyControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)\n\ttheirControl.InjectLightHouseAddr(myVpnIpNet[0].Addr(), myUdpAddr)\n\n\t// Build a router so we don't have to reason who gets which packet\n\tr := router.NewR(t, myControl, theirControl)\n\tdefer r.RenderFlow()\n\n\t// Start the servers\n\tmyControl.Start()\n\ttheirControl.Start()\n\n\tt.Log(\"Stand up a tunnel between me and them\")\n\tassertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)\n\n\tmyControl.GetHostInfoByVpnAddr(theirVpnIpNet[0].Addr(), false)\n\ttheirControl.GetHostInfoByVpnAddr(myVpnIpNet[0].Addr(), false)\n\n\tr.RenderHostmaps(\"Starting hostmaps\", myControl, theirControl)\n\n\tr.Log(\"Renew their certificate and spin until mine sees it\")\n\t_, _, theirNextPrivKey, theirNextPEM := cert_test.NewTestCert(cert.Version1, cert.Curve_CURVE25519, ca, caKey, \"them\", time.Now(), time.Now().Add(5*time.Minute), theirVpnIpNet, nil, []string{\"their new group\"})\n\n\tcaB, err := ca.MarshalPEM()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\ttheirConfig.Settings[\"pki\"] = m{\n\t\t\"ca\":   string(caB),\n\t\t\"cert\": string(theirNextPEM),\n\t\t\"key\":  string(theirNextPrivKey),\n\t}\n\trc, err := yaml.Marshal(theirConfig.Settings)\n\trequire.NoError(t, err)\n\ttheirConfig.ReloadConfigString(string(rc))\n\n\tfor {\n\t\tassertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)\n\t\ttheirCertInMe := myControl.GetHostInfoByVpnAddr(theirVpnIpNet[0].Addr(), false)\n\n\t\tif slices.Contains(theirCertInMe.Cert.Groups(), \"their new group\") {\n\t\t\tbreak\n\t\t}\n\n\t\ttime.Sleep(time.Second)\n\t}\n\n\t// Flip my firewall to only allowing the new group to catch the tunnels reverting incorrectly\n\trc, err = yaml.Marshal(myConfig.Settings)\n\trequire.NoError(t, err)\n\tvar myNewConfig m\n\trequire.NoError(t, yaml.Unmarshal(rc, &myNewConfig))\n\ttheirFirewall := myNewConfig[\"firewall\"].(map[string]any)\n\ttheirFirewall[\"inbound\"] = []m{{\n\t\t\"proto\": \"any\",\n\t\t\"port\":  \"any\",\n\t\t\"group\": \"their new group\",\n\t}}\n\trc, err = yaml.Marshal(myNewConfig)\n\trequire.NoError(t, err)\n\tmyConfig.ReloadConfigString(string(rc))\n\n\tr.Log(\"Spin until there is only 1 tunnel\")\n\tfor len(myControl.GetHostmap().Indexes)+len(theirControl.GetHostmap().Indexes) > 2 {\n\t\tassertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)\n\t\tt.Log(\"Connection manager hasn't ticked yet\")\n\t\ttime.Sleep(time.Second)\n\t}\n\n\tassertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)\n\tmyFinalHostmapHosts := myControl.ListHostmapHosts(false)\n\tmyFinalHostmapIndexes := myControl.ListHostmapIndexes(false)\n\ttheirFinalHostmapHosts := theirControl.ListHostmapHosts(false)\n\ttheirFinalHostmapIndexes := theirControl.ListHostmapIndexes(false)\n\n\t// Make sure the correct tunnel won\n\ttheirCertInMe := myControl.GetHostInfoByVpnAddr(theirVpnIpNet[0].Addr(), false)\n\tassert.Contains(t, theirCertInMe.Cert.Groups(), \"their new group\")\n\n\t// We should only have a single tunnel now on both sides\n\tassert.Len(t, myFinalHostmapHosts, 1)\n\tassert.Len(t, theirFinalHostmapHosts, 1)\n\tassert.Len(t, myFinalHostmapIndexes, 1)\n\tassert.Len(t, theirFinalHostmapIndexes, 1)\n\n\tr.RenderHostmaps(\"Final hostmaps\", myControl, theirControl)\n\tmyControl.Stop()\n\ttheirControl.Stop()\n}\n\nfunc TestRaceRegression(t *testing.T) {\n\t// This test forces stage 1, stage 2, stage 1 to be received by me from them\n\t// We had a bug where we were not finding the duplicate handshake and responding to the final stage 1 which\n\t// caused a cross-linked hostinfo\n\tca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})\n\tmyControl, myVpnIpNet, myUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, \"me\", \"10.128.0.1/24\", nil)\n\ttheirControl, theirVpnIpNet, theirUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, \"them\", \"10.128.0.2/24\", nil)\n\n\t// Put their info in our lighthouse\n\tmyControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)\n\ttheirControl.InjectLightHouseAddr(myVpnIpNet[0].Addr(), myUdpAddr)\n\n\t// Start the servers\n\tmyControl.Start()\n\ttheirControl.Start()\n\n\t//them rx stage:1 initiatorIndex=642843150 responderIndex=0\n\t//me rx   stage:1 initiatorIndex=120607833 responderIndex=0\n\t//them rx stage:1 initiatorIndex=642843150 responderIndex=0\n\t//me rx   stage:2 initiatorIndex=642843150 responderIndex=3701775874\n\t//me rx   stage:1 initiatorIndex=120607833 responderIndex=0\n\t//them rx stage:2 initiatorIndex=120607833 responderIndex=4209862089\n\n\tt.Log(\"Start both handshakes\")\n\tmyControl.InjectTunUDPPacket(theirVpnIpNet[0].Addr(), 80, myVpnIpNet[0].Addr(), 80, []byte(\"Hi from me\"))\n\ttheirControl.InjectTunUDPPacket(myVpnIpNet[0].Addr(), 80, theirVpnIpNet[0].Addr(), 80, []byte(\"Hi from them\"))\n\n\tt.Log(\"Get both stage 1\")\n\tmyStage1ForThem := myControl.GetFromUDP(true)\n\ttheirStage1ForMe := theirControl.GetFromUDP(true)\n\n\tt.Log(\"Inject them in a special way\")\n\ttheirControl.InjectUDPPacket(myStage1ForThem)\n\tmyControl.InjectUDPPacket(theirStage1ForMe)\n\ttheirControl.InjectUDPPacket(myStage1ForThem)\n\n\tt.Log(\"Get both stage 2\")\n\tmyStage2ForThem := myControl.GetFromUDP(true)\n\ttheirStage2ForMe := theirControl.GetFromUDP(true)\n\n\tt.Log(\"Inject them in a special way again\")\n\tmyControl.InjectUDPPacket(theirStage2ForMe)\n\tmyControl.InjectUDPPacket(theirStage1ForMe)\n\ttheirControl.InjectUDPPacket(myStage2ForThem)\n\n\tr := router.NewR(t, myControl, theirControl)\n\tdefer r.RenderFlow()\n\n\tt.Log(\"Flush the packets\")\n\tr.RouteForAllUntilTxTun(myControl)\n\tr.RouteForAllUntilTxTun(theirControl)\n\tr.RenderHostmaps(\"Starting hostmaps\", myControl, theirControl)\n\n\tt.Log(\"Make sure the tunnel still works\")\n\tassertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)\n\n\tmyControl.Stop()\n\ttheirControl.Stop()\n}\n\nfunc TestV2NonPrimaryWithLighthouse(t *testing.T) {\n\tca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})\n\tlhControl, lhVpnIpNet, lhUdpAddr, _ := newSimpleServer(cert.Version2, ca, caKey, \"lh  \", \"10.128.0.1/24, ff::1/64\", m{\"lighthouse\": m{\"am_lighthouse\": true}})\n\n\to := m{\n\t\t\"static_host_map\": m{\n\t\t\tlhVpnIpNet[1].Addr().String(): []string{lhUdpAddr.String()},\n\t\t},\n\t\t\"lighthouse\": m{\n\t\t\t\"hosts\": []string{lhVpnIpNet[1].Addr().String()},\n\t\t\t\"local_allow_list\": m{\n\t\t\t\t// Try and block our lighthouse updates from using the actual addresses assigned to this computer\n\t\t\t\t// If we start discovering addresses the test router doesn't know about then test traffic cant flow\n\t\t\t\t\"10.0.0.0/24\": true,\n\t\t\t\t\"::/0\":        false,\n\t\t\t},\n\t\t},\n\t}\n\tmyControl, myVpnIpNet, _, _ := newSimpleServer(cert.Version2, ca, caKey, \"me  \", \"10.128.0.2/24, ff::2/64\", o)\n\ttheirControl, theirVpnIpNet, _, _ := newSimpleServer(cert.Version2, ca, caKey, \"them\", \"10.128.0.3/24, ff::3/64\", o)\n\n\t// Build a router so we don't have to reason who gets which packet\n\tr := router.NewR(t, lhControl, myControl, theirControl)\n\tdefer r.RenderFlow()\n\n\t// Start the servers\n\tlhControl.Start()\n\tmyControl.Start()\n\ttheirControl.Start()\n\n\tt.Log(\"Stand up an ipv6 tunnel between me and them\")\n\tassert.True(t, myVpnIpNet[1].Addr().Is6())\n\tassert.True(t, theirVpnIpNet[1].Addr().Is6())\n\tassertTunnel(t, myVpnIpNet[1].Addr(), theirVpnIpNet[1].Addr(), myControl, theirControl, r)\n\n\tlhControl.Stop()\n\tmyControl.Stop()\n\ttheirControl.Stop()\n}\n\nfunc TestV2NonPrimaryWithOffNetLighthouse(t *testing.T) {\n\tca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})\n\tlhControl, lhVpnIpNet, lhUdpAddr, _ := newSimpleServer(cert.Version2, ca, caKey, \"lh  \", \"2001::1/64\", m{\"lighthouse\": m{\"am_lighthouse\": true}})\n\n\to := m{\n\t\t\"static_host_map\": m{\n\t\t\tlhVpnIpNet[0].Addr().String(): []string{lhUdpAddr.String()},\n\t\t},\n\t\t\"lighthouse\": m{\n\t\t\t\"hosts\": []string{lhVpnIpNet[0].Addr().String()},\n\t\t\t\"local_allow_list\": m{\n\t\t\t\t// Try and block our lighthouse updates from using the actual addresses assigned to this computer\n\t\t\t\t// If we start discovering addresses the test router doesn't know about then test traffic cant flow\n\t\t\t\t\"10.0.0.0/24\": true,\n\t\t\t\t\"::/0\":        false,\n\t\t\t},\n\t\t},\n\t}\n\tmyControl, myVpnIpNet, _, _ := newSimpleServer(cert.Version2, ca, caKey, \"me  \", \"10.128.0.2/24, ff::2/64\", o)\n\ttheirControl, theirVpnIpNet, _, _ := newSimpleServer(cert.Version2, ca, caKey, \"them\", \"10.128.0.3/24, ff::3/64\", o)\n\n\t// Build a router so we don't have to reason who gets which packet\n\tr := router.NewR(t, lhControl, myControl, theirControl)\n\tdefer r.RenderFlow()\n\n\t// Start the servers\n\tlhControl.Start()\n\tmyControl.Start()\n\ttheirControl.Start()\n\n\tt.Log(\"Stand up an ipv6 tunnel between me and them\")\n\tassert.True(t, myVpnIpNet[1].Addr().Is6())\n\tassert.True(t, theirVpnIpNet[1].Addr().Is6())\n\tassertTunnel(t, myVpnIpNet[1].Addr(), theirVpnIpNet[1].Addr(), myControl, theirControl, r)\n\n\tlhControl.Stop()\n\tmyControl.Stop()\n\ttheirControl.Stop()\n}\n\nfunc TestGoodHandshakeUnsafeDest(t *testing.T) {\n\tunsafePrefix := \"192.168.6.0/24\"\n\tca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})\n\ttheirControl, theirVpnIpNet, theirUdpAddr, _ := newSimpleServerWithUdpAndUnsafeNetworks(cert.Version2, ca, caKey, \"spooky\", \"10.128.0.2/24\", netip.MustParseAddrPort(\"10.64.0.2:4242\"), unsafePrefix, nil)\n\troute := m{\"route\": unsafePrefix, \"via\": theirVpnIpNet[0].Addr().String()}\n\tmyCfg := m{\n\t\t\"tun\": m{\n\t\t\t\"unsafe_routes\": []m{route},\n\t\t},\n\t}\n\tmyControl, myVpnIpNet, myUdpAddr, myConfig := newSimpleServer(cert.Version2, ca, caKey, \"me\", \"10.128.0.1/24\", myCfg)\n\tt.Logf(\"my config %v\", myConfig)\n\t// Put their info in our lighthouse\n\tmyControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)\n\n\tspookyDest := netip.MustParseAddr(\"192.168.6.4\")\n\n\t// Start the servers\n\tmyControl.Start()\n\ttheirControl.Start()\n\n\tt.Log(\"Send a udp packet through to begin standing up the tunnel, this should come out the other side\")\n\tmyControl.InjectTunUDPPacket(spookyDest, 80, myVpnIpNet[0].Addr(), 80, []byte(\"Hi from me\"))\n\n\tt.Log(\"Have them consume my stage 0 packet. They have a tunnel now\")\n\ttheirControl.InjectUDPPacket(myControl.GetFromUDP(true))\n\n\tt.Log(\"Get their stage 1 packet so that we can play with it\")\n\tstage1Packet := theirControl.GetFromUDP(true)\n\n\tt.Log(\"I consume a garbage packet with a proper nebula header for our tunnel\")\n\t// this should log a statement and get ignored, allowing the real handshake packet to complete the tunnel\n\tbadPacket := stage1Packet.Copy()\n\tbadPacket.Data = badPacket.Data[:len(badPacket.Data)-header.Len]\n\tmyControl.InjectUDPPacket(badPacket)\n\n\tt.Log(\"Have me consume their real stage 1 packet. I have a tunnel now\")\n\tmyControl.InjectUDPPacket(stage1Packet)\n\n\tt.Log(\"Wait until we see my cached packet come through\")\n\tmyControl.WaitForType(1, 0, theirControl)\n\n\tt.Log(\"Make sure our host infos are correct\")\n\tassertHostInfoPair(t, myUdpAddr, theirUdpAddr, myVpnIpNet, theirVpnIpNet, myControl, theirControl)\n\n\tt.Log(\"Get that cached packet and make sure it looks right\")\n\tmyCachedPacket := theirControl.GetFromTun(true)\n\tassertUdpPacket(t, []byte(\"Hi from me\"), myCachedPacket, myVpnIpNet[0].Addr(), spookyDest, 80, 80)\n\n\t//reply\n\ttheirControl.InjectTunUDPPacket(myVpnIpNet[0].Addr(), 80, spookyDest, 80, []byte(\"Hi from the spookyman\"))\n\t//wait for reply\n\ttheirControl.WaitForType(1, 0, myControl)\n\ttheirCachedPacket := myControl.GetFromTun(true)\n\tassertUdpPacket(t, []byte(\"Hi from the spookyman\"), theirCachedPacket, spookyDest, myVpnIpNet[0].Addr(), 80, 80)\n\n\tt.Log(\"Do a bidirectional tunnel test\")\n\tr := router.NewR(t, myControl, theirControl)\n\tdefer r.RenderFlow()\n\tassertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)\n\n\tr.RenderHostmaps(\"Final hostmaps\", myControl, theirControl)\n\tmyControl.Stop()\n\ttheirControl.Stop()\n}\n"
  },
  {
    "path": "e2e/helpers_test.go",
    "content": "//go:build e2e_testing\n// +build e2e_testing\n\npackage e2e\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/netip\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"dario.cat/mergo\"\n\t\"github.com/google/gopacket\"\n\t\"github.com/google/gopacket/layers\"\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula\"\n\t\"github.com/slackhq/nebula/cert\"\n\t\"github.com/slackhq/nebula/cert_test\"\n\t\"github.com/slackhq/nebula/config\"\n\t\"github.com/slackhq/nebula/e2e/router\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.yaml.in/yaml/v3\"\n)\n\ntype m = map[string]any\n\n// newSimpleServer creates a nebula instance with many assumptions\nfunc newSimpleServer(v cert.Version, caCrt cert.Certificate, caKey []byte, name string, sVpnNetworks string, overrides m) (*nebula.Control, []netip.Prefix, netip.AddrPort, *config.C) {\n\tvar vpnNetworks []netip.Prefix\n\tfor _, sn := range strings.Split(sVpnNetworks, \",\") {\n\t\tvpnIpNet, err := netip.ParsePrefix(strings.TrimSpace(sn))\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tvpnNetworks = append(vpnNetworks, vpnIpNet)\n\t}\n\n\tif len(vpnNetworks) == 0 {\n\t\tpanic(\"no vpn networks\")\n\t}\n\n\tvar udpAddr netip.AddrPort\n\tif vpnNetworks[0].Addr().Is4() {\n\t\tbudpIp := vpnNetworks[0].Addr().As4()\n\t\tbudpIp[1] -= 128\n\t\tudpAddr = netip.AddrPortFrom(netip.AddrFrom4(budpIp), 4242)\n\t} else {\n\t\tbudpIp := vpnNetworks[0].Addr().As16()\n\t\t// beef for funsies\n\t\tbudpIp[2] = 190\n\t\tbudpIp[3] = 239\n\t\tudpAddr = netip.AddrPortFrom(netip.AddrFrom16(budpIp), 4242)\n\t}\n\treturn newSimpleServerWithUdp(v, caCrt, caKey, name, sVpnNetworks, udpAddr, overrides)\n}\n\nfunc newSimpleServerWithUdp(v cert.Version, caCrt cert.Certificate, caKey []byte, name string, sVpnNetworks string, udpAddr netip.AddrPort, overrides m) (*nebula.Control, []netip.Prefix, netip.AddrPort, *config.C) {\n\treturn newSimpleServerWithUdpAndUnsafeNetworks(v, caCrt, caKey, name, sVpnNetworks, udpAddr, \"\", overrides)\n}\n\nfunc newSimpleServerWithUdpAndUnsafeNetworks(v cert.Version, caCrt cert.Certificate, caKey []byte, name string, sVpnNetworks string, udpAddr netip.AddrPort, sUnsafeNetworks string, overrides m) (*nebula.Control, []netip.Prefix, netip.AddrPort, *config.C) {\n\tl := NewTestLogger()\n\n\tvar vpnNetworks []netip.Prefix\n\tfor _, sn := range strings.Split(sVpnNetworks, \",\") {\n\t\tvpnIpNet, err := netip.ParsePrefix(strings.TrimSpace(sn))\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tvpnNetworks = append(vpnNetworks, vpnIpNet)\n\t}\n\n\tif len(vpnNetworks) == 0 {\n\t\tpanic(\"no vpn networks\")\n\t}\n\n\tfirewallInbound := []m{{\n\t\t\"proto\": \"any\",\n\t\t\"port\":  \"any\",\n\t\t\"host\":  \"any\",\n\t}}\n\n\tvar unsafeNetworks []netip.Prefix\n\tif sUnsafeNetworks != \"\" {\n\t\tfirewallInbound = []m{{\n\t\t\t\"proto\":      \"any\",\n\t\t\t\"port\":       \"any\",\n\t\t\t\"host\":       \"any\",\n\t\t\t\"local_cidr\": \"0.0.0.0/0\",\n\t\t}}\n\n\t\tfor _, sn := range strings.Split(sUnsafeNetworks, \",\") {\n\t\t\tx, err := netip.ParsePrefix(strings.TrimSpace(sn))\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\tunsafeNetworks = append(unsafeNetworks, x)\n\t\t}\n\t}\n\n\t_, _, myPrivKey, myPEM := cert_test.NewTestCert(v, cert.Curve_CURVE25519, caCrt, caKey, name, time.Now(), time.Now().Add(5*time.Minute), vpnNetworks, unsafeNetworks, []string{})\n\n\tcaB, err := caCrt.MarshalPEM()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tmc := m{\n\t\t\"pki\": m{\n\t\t\t\"ca\":   string(caB),\n\t\t\t\"cert\": string(myPEM),\n\t\t\t\"key\":  string(myPrivKey),\n\t\t},\n\t\t//\"tun\": m{\"disabled\": true},\n\t\t\"firewall\": m{\n\t\t\t\"outbound\": []m{{\n\t\t\t\t\"proto\": \"any\",\n\t\t\t\t\"port\":  \"any\",\n\t\t\t\t\"host\":  \"any\",\n\t\t\t}},\n\t\t\t\"inbound\": firewallInbound,\n\t\t},\n\t\t//\"handshakes\": m{\n\t\t//\t\"try_interval\": \"1s\",\n\t\t//},\n\t\t\"listen\": m{\n\t\t\t\"host\": udpAddr.Addr().String(),\n\t\t\t\"port\": udpAddr.Port(),\n\t\t},\n\t\t\"logging\": m{\n\t\t\t\"timestamp_format\": fmt.Sprintf(\"%v 15:04:05.000000\", name),\n\t\t\t\"level\":            l.Level.String(),\n\t\t},\n\t\t\"timers\": m{\n\t\t\t\"pending_deletion_interval\": 2,\n\t\t\t\"connection_alive_interval\": 2,\n\t\t},\n\t}\n\n\tif overrides != nil {\n\t\tfinal := m{}\n\t\terr = mergo.Merge(&final, overrides, mergo.WithAppendSlice)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\terr = mergo.Merge(&final, mc, mergo.WithAppendSlice)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tmc = final\n\t}\n\n\tcb, err := yaml.Marshal(mc)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tc := config.NewC(l)\n\tc.LoadString(string(cb))\n\n\tcontrol, err := nebula.Main(c, false, \"e2e-test\", l, nil)\n\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn control, vpnNetworks, udpAddr, c\n}\n\n// newServer creates a nebula instance with fewer assumptions\nfunc newServer(caCrt []cert.Certificate, certs []cert.Certificate, key []byte, overrides m) (*nebula.Control, []netip.Prefix, netip.AddrPort, *config.C) {\n\tl := NewTestLogger()\n\n\tvpnNetworks := certs[len(certs)-1].Networks()\n\n\tvar udpAddr netip.AddrPort\n\tif vpnNetworks[0].Addr().Is4() {\n\t\tbudpIp := vpnNetworks[0].Addr().As4()\n\t\tbudpIp[1] -= 128\n\t\tudpAddr = netip.AddrPortFrom(netip.AddrFrom4(budpIp), 4242)\n\t} else {\n\t\tbudpIp := vpnNetworks[0].Addr().As16()\n\t\t// beef for funsies\n\t\tbudpIp[2] = 190\n\t\tbudpIp[3] = 239\n\t\tudpAddr = netip.AddrPortFrom(netip.AddrFrom16(budpIp), 4242)\n\t}\n\n\tcaStr := \"\"\n\tfor _, ca := range caCrt {\n\t\tx, err := ca.MarshalPEM()\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tcaStr += string(x)\n\t}\n\tcertStr := \"\"\n\tfor _, c := range certs {\n\t\tx, err := c.MarshalPEM()\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tcertStr += string(x)\n\t}\n\n\tmc := m{\n\t\t\"pki\": m{\n\t\t\t\"ca\":   caStr,\n\t\t\t\"cert\": certStr,\n\t\t\t\"key\":  string(key),\n\t\t},\n\t\t//\"tun\": m{\"disabled\": true},\n\t\t\"firewall\": m{\n\t\t\t\"outbound\": []m{{\n\t\t\t\t\"proto\": \"any\",\n\t\t\t\t\"port\":  \"any\",\n\t\t\t\t\"host\":  \"any\",\n\t\t\t}},\n\t\t\t\"inbound\": []m{{\n\t\t\t\t\"proto\": \"any\",\n\t\t\t\t\"port\":  \"any\",\n\t\t\t\t\"host\":  \"any\",\n\t\t\t}},\n\t\t},\n\t\t//\"handshakes\": m{\n\t\t//\t\"try_interval\": \"1s\",\n\t\t//},\n\t\t\"listen\": m{\n\t\t\t\"host\": udpAddr.Addr().String(),\n\t\t\t\"port\": udpAddr.Port(),\n\t\t},\n\t\t\"logging\": m{\n\t\t\t\"timestamp_format\": fmt.Sprintf(\"%v 15:04:05.000000\", certs[0].Name()),\n\t\t\t\"level\":            l.Level.String(),\n\t\t},\n\t\t\"timers\": m{\n\t\t\t\"pending_deletion_interval\": 2,\n\t\t\t\"connection_alive_interval\": 2,\n\t\t},\n\t}\n\n\tif overrides != nil {\n\t\tfinal := m{}\n\t\terr := mergo.Merge(&final, overrides, mergo.WithAppendSlice)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\terr = mergo.Merge(&final, mc, mergo.WithAppendSlice)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tmc = final\n\t}\n\n\tcb, err := yaml.Marshal(mc)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tc := config.NewC(l)\n\tcStr := string(cb)\n\tc.LoadString(cStr)\n\n\tcontrol, err := nebula.Main(c, false, \"e2e-test\", l, nil)\n\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn control, vpnNetworks, udpAddr, c\n}\n\ntype doneCb func()\n\nfunc deadline(t *testing.T, seconds time.Duration) doneCb {\n\ttimeout := time.After(seconds * time.Second)\n\tdone := make(chan bool)\n\tgo func() {\n\t\tselect {\n\t\tcase <-timeout:\n\t\t\tt.Fatal(\"Test did not finish in time\")\n\t\tcase <-done:\n\t\t}\n\t}()\n\n\treturn func() {\n\t\tdone <- true\n\t}\n}\n\nfunc assertTunnel(t testing.TB, vpnIpA, vpnIpB netip.Addr, controlA, controlB *nebula.Control, r *router.R) {\n\t// Send a packet from them to me\n\tcontrolB.InjectTunUDPPacket(vpnIpA, 80, vpnIpB, 90, []byte(\"Hi from B\"))\n\tbPacket := r.RouteForAllUntilTxTun(controlA)\n\tassertUdpPacket(t, []byte(\"Hi from B\"), bPacket, vpnIpB, vpnIpA, 90, 80)\n\n\t// And once more from me to them\n\tcontrolA.InjectTunUDPPacket(vpnIpB, 80, vpnIpA, 90, []byte(\"Hello from A\"))\n\taPacket := r.RouteForAllUntilTxTun(controlB)\n\tassertUdpPacket(t, []byte(\"Hello from A\"), aPacket, vpnIpA, vpnIpB, 90, 80)\n}\n\nfunc assertHostInfoPair(t testing.TB, addrA, addrB netip.AddrPort, vpnNetsA, vpnNetsB []netip.Prefix, controlA, controlB *nebula.Control) {\n\t// Get both host infos\n\t//TODO: CERT-V2 we may want to loop over each vpnAddr and assert all the things\n\thBinA := controlA.GetHostInfoByVpnAddr(vpnNetsB[0].Addr(), false)\n\trequire.NotNil(t, hBinA, \"Host B was not found by vpnAddr in controlA\")\n\n\thAinB := controlB.GetHostInfoByVpnAddr(vpnNetsA[0].Addr(), false)\n\trequire.NotNil(t, hAinB, \"Host A was not found by vpnAddr in controlB\")\n\n\t// Check that both vpn and real addr are correct\n\tassert.EqualValues(t, getAddrs(vpnNetsB), hBinA.VpnAddrs, \"Host B VpnIp is wrong in control A\")\n\tassert.EqualValues(t, getAddrs(vpnNetsA), hAinB.VpnAddrs, \"Host A VpnIp is wrong in control B\")\n\n\tassert.Equal(t, addrB, hBinA.CurrentRemote, \"Host B remote is wrong in control A\")\n\tassert.Equal(t, addrA, hAinB.CurrentRemote, \"Host A remote is wrong in control B\")\n\n\t// Check that our indexes match\n\tassert.Equal(t, hBinA.LocalIndex, hAinB.RemoteIndex, \"Host B local index does not match host A remote index\")\n\tassert.Equal(t, hBinA.RemoteIndex, hAinB.LocalIndex, \"Host B remote index does not match host A local index\")\n}\n\nfunc assertUdpPacket(t testing.TB, expected, b []byte, fromIp, toIp netip.Addr, fromPort, toPort uint16) {\n\tif toIp.Is6() {\n\t\tassertUdpPacket6(t, expected, b, fromIp, toIp, fromPort, toPort)\n\t} else {\n\t\tassertUdpPacket4(t, expected, b, fromIp, toIp, fromPort, toPort)\n\t}\n}\n\nfunc assertUdpPacket6(t testing.TB, expected, b []byte, fromIp, toIp netip.Addr, fromPort, toPort uint16) {\n\tpacket := gopacket.NewPacket(b, layers.LayerTypeIPv6, gopacket.Lazy)\n\tv6 := packet.Layer(layers.LayerTypeIPv6).(*layers.IPv6)\n\tassert.NotNil(t, v6, \"No ipv6 data found\")\n\n\tassert.Equal(t, fromIp.AsSlice(), []byte(v6.SrcIP), \"Source ip was incorrect\")\n\tassert.Equal(t, toIp.AsSlice(), []byte(v6.DstIP), \"Dest ip was incorrect\")\n\n\tudp := packet.Layer(layers.LayerTypeUDP).(*layers.UDP)\n\tassert.NotNil(t, udp, \"No udp data found\")\n\n\tassert.Equal(t, fromPort, uint16(udp.SrcPort), \"Source port was incorrect\")\n\tassert.Equal(t, toPort, uint16(udp.DstPort), \"Dest port was incorrect\")\n\n\tdata := packet.ApplicationLayer()\n\tassert.NotNil(t, data)\n\tassert.Equal(t, expected, data.Payload(), \"Data was incorrect\")\n}\n\nfunc assertUdpPacket4(t testing.TB, expected, b []byte, fromIp, toIp netip.Addr, fromPort, toPort uint16) {\n\tpacket := gopacket.NewPacket(b, layers.LayerTypeIPv4, gopacket.Lazy)\n\tv4 := packet.Layer(layers.LayerTypeIPv4).(*layers.IPv4)\n\tassert.NotNil(t, v4, \"No ipv4 data found\")\n\n\tassert.Equal(t, fromIp.AsSlice(), []byte(v4.SrcIP), \"Source ip was incorrect\")\n\tassert.Equal(t, toIp.AsSlice(), []byte(v4.DstIP), \"Dest ip was incorrect\")\n\n\tudp := packet.Layer(layers.LayerTypeUDP).(*layers.UDP)\n\tassert.NotNil(t, udp, \"No udp data found\")\n\n\tassert.Equal(t, fromPort, uint16(udp.SrcPort), \"Source port was incorrect\")\n\tassert.Equal(t, toPort, uint16(udp.DstPort), \"Dest port was incorrect\")\n\n\tdata := packet.ApplicationLayer()\n\tassert.NotNil(t, data)\n\tassert.Equal(t, expected, data.Payload(), \"Data was incorrect\")\n}\n\nfunc getAddrs(ns []netip.Prefix) []netip.Addr {\n\tvar a []netip.Addr\n\tfor _, n := range ns {\n\t\ta = append(a, n.Addr())\n\t}\n\treturn a\n}\n\nfunc NewTestLogger() *logrus.Logger {\n\tl := logrus.New()\n\n\tv := os.Getenv(\"TEST_LOGS\")\n\tif v == \"\" {\n\t\tl.SetOutput(io.Discard)\n\t\tl.SetLevel(logrus.PanicLevel)\n\t\treturn l\n\t}\n\n\tswitch v {\n\tcase \"2\":\n\t\tl.SetLevel(logrus.DebugLevel)\n\tcase \"3\":\n\t\tl.SetLevel(logrus.TraceLevel)\n\tdefault:\n\t\tl.SetLevel(logrus.InfoLevel)\n\t}\n\n\treturn l\n}\n"
  },
  {
    "path": "e2e/router/doc.go",
    "content": "package router\n\n// This file exists to allow `go fmt` to traverse here on its own. The build tags were keeping it out before\n"
  },
  {
    "path": "e2e/router/hostmap.go",
    "content": "//go:build e2e_testing\n// +build e2e_testing\n\npackage router\n\nimport (\n\t\"fmt\"\n\t\"net/netip\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/slackhq/nebula\"\n)\n\ntype edge struct {\n\tfrom string\n\tto   string\n\tdual bool\n}\n\nfunc renderHostmaps(controls ...*nebula.Control) string {\n\tvar lines []*edge\n\tr := \"graph TB\\n\"\n\tfor _, c := range controls {\n\t\tsr, se := renderHostmap(c)\n\t\tr += sr\n\t\tfor _, e := range se {\n\t\t\tadd := true\n\n\t\t\t// Collapse duplicate edges into a bi-directionally connected edge\n\t\t\tfor _, ge := range lines {\n\t\t\t\tif e.to == ge.from && e.from == ge.to {\n\t\t\t\t\tadd = false\n\t\t\t\t\tge.dual = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif add {\n\t\t\t\tlines = append(lines, e)\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, line := range lines {\n\t\tif line.dual {\n\t\t\tr += fmt.Sprintf(\"\\t%v <--> %v\\n\", line.from, line.to)\n\t\t} else {\n\t\t\tr += fmt.Sprintf(\"\\t%v --> %v\\n\", line.from, line.to)\n\t\t}\n\n\t}\n\n\treturn r\n}\n\nfunc renderHostmap(c *nebula.Control) (string, []*edge) {\n\tvar lines []string\n\tvar globalLines []*edge\n\n\tcrt := c.GetCertState().GetDefaultCertificate()\n\tclusterName := strings.Trim(crt.Name(), \" \")\n\tclusterVpnIp := crt.Networks()[0].Addr()\n\tr := fmt.Sprintf(\"\\tsubgraph %s[\\\"%s (%s)\\\"]\\n\", clusterName, clusterName, clusterVpnIp)\n\n\thm := c.GetHostmap()\n\thm.RLock()\n\tdefer hm.RUnlock()\n\n\t// Draw the vpn to index nodes\n\tr += fmt.Sprintf(\"\\t\\tsubgraph %s.hosts[\\\"Hosts (vpn ip to index)\\\"]\\n\", clusterName)\n\thosts := sortedHosts(hm.Hosts)\n\tfor _, vpnIp := range hosts {\n\t\thi := hm.Hosts[vpnIp]\n\t\tr += fmt.Sprintf(\"\\t\\t\\t%v.%v[\\\"%v\\\"]\\n\", clusterName, vpnIp, vpnIp)\n\t\tlines = append(lines, fmt.Sprintf(\"%v.%v --> %v.%v\", clusterName, vpnIp, clusterName, hi.GetLocalIndex()))\n\n\t\trs := hi.GetRelayState()\n\t\tfor _, relayIp := range rs.CopyRelayIps() {\n\t\t\tlines = append(lines, fmt.Sprintf(\"%v.%v --> %v.%v\", clusterName, vpnIp, clusterName, relayIp))\n\t\t}\n\n\t\tfor _, relayIp := range rs.CopyRelayForIdxs() {\n\t\t\tlines = append(lines, fmt.Sprintf(\"%v.%v --> %v.%v\", clusterName, vpnIp, clusterName, relayIp))\n\t\t}\n\t}\n\tr += \"\\t\\tend\\n\"\n\n\t// Draw the relay hostinfos\n\tif len(hm.Relays) > 0 {\n\t\tr += fmt.Sprintf(\"\\t\\tsubgraph %s.relays[\\\"Relays (relay index to hostinfo)\\\"]\\n\", clusterName)\n\t\tfor relayIndex, hi := range hm.Relays {\n\t\t\tr += fmt.Sprintf(\"\\t\\t\\t%v.%v[\\\"%v\\\"]\\n\", clusterName, relayIndex, relayIndex)\n\t\t\tlines = append(lines, fmt.Sprintf(\"%v.%v --> %v.%v\", clusterName, relayIndex, clusterName, hi.GetLocalIndex()))\n\t\t}\n\t\tr += \"\\t\\tend\\n\"\n\t}\n\n\t// Draw the local index to relay or remote index nodes\n\tr += fmt.Sprintf(\"\\t\\tsubgraph indexes.%s[\\\"Indexes (index to hostinfo)\\\"]\\n\", clusterName)\n\tindexes := sortedIndexes(hm.Indexes)\n\tfor _, idx := range indexes {\n\t\thi, ok := hm.Indexes[idx]\n\t\tif ok {\n\t\t\tr += fmt.Sprintf(\"\\t\\t\\t%v.%v[\\\"%v (%v)\\\"]\\n\", clusterName, idx, idx, hi.GetVpnAddrs())\n\t\t\tremoteClusterName := strings.Trim(hi.GetCert().Certificate.Name(), \" \")\n\t\t\tglobalLines = append(globalLines, &edge{from: fmt.Sprintf(\"%v.%v\", clusterName, idx), to: fmt.Sprintf(\"%v.%v\", remoteClusterName, hi.GetRemoteIndex())})\n\t\t\t_ = hi\n\t\t}\n\t}\n\tr += \"\\t\\tend\\n\"\n\n\t// Add the edges inside this host\n\tfor _, line := range lines {\n\t\tr += fmt.Sprintf(\"\\t\\t%v\\n\", line)\n\t}\n\n\tr += \"\\tend\\n\"\n\treturn r, globalLines\n}\n\nfunc sortedHosts(hosts map[netip.Addr]*nebula.HostInfo) []netip.Addr {\n\tkeys := make([]netip.Addr, 0, len(hosts))\n\tfor key := range hosts {\n\t\tkeys = append(keys, key)\n\t}\n\n\tsort.SliceStable(keys, func(i, j int) bool {\n\t\treturn keys[i].Compare(keys[j]) > 0\n\t})\n\n\treturn keys\n}\n\nfunc sortedIndexes(indexes map[uint32]*nebula.HostInfo) []uint32 {\n\tkeys := make([]uint32, 0, len(indexes))\n\tfor key := range indexes {\n\t\tkeys = append(keys, key)\n\t}\n\n\tsort.SliceStable(keys, func(i, j int) bool {\n\t\treturn keys[i] > keys[j]\n\t})\n\n\treturn keys\n}\n"
  },
  {
    "path": "e2e/router/router.go",
    "content": "//go:build e2e_testing\n// +build e2e_testing\n\npackage router\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"sort\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/gopacket\"\n\t\"github.com/google/gopacket/layers\"\n\t\"github.com/slackhq/nebula\"\n\t\"github.com/slackhq/nebula/header\"\n\t\"github.com/slackhq/nebula/udp\"\n\t\"golang.org/x/exp/maps\"\n)\n\ntype R struct {\n\t// Simple map of the ip:port registered on a control to the control\n\t// Basically a router, right?\n\tcontrols map[netip.AddrPort]*nebula.Control\n\n\t// A map for inbound packets for a control that doesn't know about this address\n\tinNat map[netip.AddrPort]*nebula.Control\n\n\t// A last used map, if an inbound packet hit the inNat map then\n\t// all return packets should use the same last used inbound address for the outbound sender\n\t// map[from address + \":\" + to address] => ip:port to rewrite in the udp packet to receiver\n\toutNat map[string]netip.AddrPort\n\n\t// A map of vpn ip to the nebula control it belongs to\n\tvpnControls map[netip.Addr]*nebula.Control\n\n\tignoreFlows []ignoreFlow\n\tflow        []flowEntry\n\n\t// A set of additional mermaid graphs to draw in the flow log markdown file\n\t// Currently consisting only of hostmap renders\n\tadditionalGraphs []mermaidGraph\n\n\t// All interactions are locked to help serialize behavior\n\tsync.Mutex\n\n\tfn           string\n\tcancelRender context.CancelFunc\n\tt            testing.TB\n}\n\ntype ignoreFlow struct {\n\ttun         NullBool\n\tmessageType header.MessageType\n\tsubType     header.MessageSubType\n\t//from\n\t//to\n}\n\ntype mermaidGraph struct {\n\ttitle   string\n\tcontent string\n}\n\ntype NullBool struct {\n\tHasValue bool\n\tIsTrue   bool\n}\n\ntype flowEntry struct {\n\tnote   string\n\tpacket *packet\n}\n\ntype packet struct {\n\tfrom   *nebula.Control\n\tto     *nebula.Control\n\tpacket *udp.Packet\n\ttun    bool // a packet pulled off a tun device\n\trx     bool // the packet was received by a udp device\n}\n\nfunc (p *packet) WasReceived() {\n\tif p != nil {\n\t\tp.rx = true\n\t}\n}\n\ntype ExitType int\n\nconst (\n\t// KeepRouting the function will get called again on the next packet\n\tKeepRouting ExitType = 0\n\t// ExitNow does not route this packet and exits immediately\n\tExitNow ExitType = 1\n\t// RouteAndExit routes this packet and exits immediately afterwards\n\tRouteAndExit ExitType = 2\n)\n\ntype ExitFunc func(packet *udp.Packet, receiver *nebula.Control) ExitType\n\n// NewR creates a new router to pass packets in a controlled fashion between the provided controllers.\n// The packet flow will be recorded in a file within the mermaid directory under the same name as the test.\n// Renders will occur automatically, roughly every 100ms, until a call to RenderFlow() is made\nfunc NewR(t testing.TB, controls ...*nebula.Control) *R {\n\tctx, cancel := context.WithCancel(context.Background())\n\n\tif err := os.MkdirAll(\"mermaid\", 0755); err != nil {\n\t\tpanic(err)\n\t}\n\n\tr := &R{\n\t\tcontrols:     make(map[netip.AddrPort]*nebula.Control),\n\t\tvpnControls:  make(map[netip.Addr]*nebula.Control),\n\t\tinNat:        make(map[netip.AddrPort]*nebula.Control),\n\t\toutNat:       make(map[string]netip.AddrPort),\n\t\tflow:         []flowEntry{},\n\t\tignoreFlows:  []ignoreFlow{},\n\t\tfn:           filepath.Join(\"mermaid\", fmt.Sprintf(\"%s.md\", t.Name())),\n\t\tt:            t,\n\t\tcancelRender: cancel,\n\t}\n\n\t// Try to remove our render file\n\tos.Remove(r.fn)\n\n\tfor _, c := range controls {\n\t\taddr := c.GetUDPAddr()\n\t\tif _, ok := r.controls[addr]; ok {\n\t\t\tpanic(\"Duplicate listen address: \" + addr.String())\n\t\t}\n\n\t\tfor _, vpnAddr := range c.GetVpnAddrs() {\n\t\t\tr.vpnControls[vpnAddr] = c\n\t\t}\n\n\t\tr.controls[addr] = c\n\t}\n\n\t// Spin the renderer in case we go nuts and the test never completes\n\tgo func() {\n\t\tclockSource := time.NewTicker(time.Millisecond * 100)\n\t\tdefer clockSource.Stop()\n\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn\n\t\t\tcase <-clockSource.C:\n\t\t\t\tr.renderHostmaps(\"clock tick\")\n\t\t\t\tr.renderFlow()\n\t\t\t}\n\t\t}\n\t}()\n\n\treturn r\n}\n\n// AddRoute will place the nebula controller at the ip and port specified.\n// It does not look at the addr attached to the instance.\n// If a route is used, this will behave like a NAT for the return path.\n// Rewriting the source ip:port to what was last sent to from the origin\nfunc (r *R) AddRoute(ip netip.Addr, port uint16, c *nebula.Control) {\n\tr.Lock()\n\tdefer r.Unlock()\n\n\tinAddr := netip.AddrPortFrom(ip, port)\n\tif _, ok := r.inNat[inAddr]; ok {\n\t\tpanic(\"Duplicate listen address inNat: \" + inAddr.String())\n\t}\n\tr.inNat[inAddr] = c\n}\n\n// RenderFlow renders the packet flow seen up until now and stops further automatic renders from happening.\nfunc (r *R) RenderFlow() {\n\tr.cancelRender()\n\tr.renderFlow()\n}\n\n// CancelFlowLogs stops flow logs from being tracked and destroys any logs already collected\nfunc (r *R) CancelFlowLogs() {\n\tr.cancelRender()\n\tr.flow = nil\n}\n\nfunc (r *R) renderFlow() {\n\tif r.flow == nil {\n\t\treturn\n\t}\n\n\tf, err := os.OpenFile(r.fn, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0644)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tvar participants = map[netip.AddrPort]struct{}{}\n\tvar participantsVals []string\n\n\tfmt.Fprintln(f, \"```mermaid\")\n\tfmt.Fprintln(f, \"sequenceDiagram\")\n\n\t// Assemble participants\n\tfor _, e := range r.flow {\n\t\tif e.packet == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\taddr := e.packet.from.GetUDPAddr()\n\t\tif _, ok := participants[addr]; ok {\n\t\t\tcontinue\n\t\t}\n\t\tparticipants[addr] = struct{}{}\n\t\tsanAddr := normalizeName(addr.String())\n\t\tparticipantsVals = append(participantsVals, sanAddr)\n\t\tfmt.Fprintf(\n\t\t\tf, \"    participant %s as Nebula: %s<br/>UDP: %s\\n\",\n\t\t\tsanAddr, e.packet.from.GetVpnAddrs(), sanAddr,\n\t\t)\n\t}\n\n\tif len(participantsVals) > 2 {\n\t\t// Get the first and last participantVals for notes\n\t\tparticipantsVals = []string{participantsVals[0], participantsVals[len(participantsVals)-1]}\n\t}\n\n\t// Print packets\n\th := &header.H{}\n\tfor _, e := range r.flow {\n\t\tif e.packet == nil {\n\t\t\t//fmt.Fprintf(f, \"    note over %s: %s\\n\", strings.Join(participantsVals, \", \"), e.note)\n\t\t\tcontinue\n\t\t}\n\n\t\tp := e.packet\n\t\tif p.tun {\n\t\t\tfmt.Fprintln(f, r.formatUdpPacket(p))\n\n\t\t} else {\n\t\t\tif err := h.Parse(p.packet.Data); err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\n\t\t\tline := \"--x\"\n\t\t\tif p.rx {\n\t\t\t\tline = \"->>\"\n\t\t\t}\n\n\t\t\tfmt.Fprintf(f,\n\t\t\t\t\"    %s%s%s: %s(%s), index %v, counter: %v\\n\",\n\t\t\t\tnormalizeName(p.from.GetUDPAddr().String()),\n\t\t\t\tline,\n\t\t\t\tnormalizeName(p.to.GetUDPAddr().String()),\n\t\t\t\th.TypeName(), h.SubTypeName(), h.RemoteIndex, h.MessageCounter,\n\t\t\t)\n\t\t}\n\t}\n\tfmt.Fprintln(f, \"```\")\n\n\tfor _, g := range r.additionalGraphs {\n\t\tfmt.Fprintf(f, \"## %s\\n\", g.title)\n\t\tfmt.Fprintln(f, \"```mermaid\")\n\t\tfmt.Fprintln(f, g.content)\n\t\tfmt.Fprintln(f, \"```\")\n\t}\n}\n\nfunc normalizeName(s string) string {\n\trx := regexp.MustCompile(\"[\\\\[\\\\]\\\\:]\")\n\treturn rx.ReplaceAllLiteralString(s, \"_\")\n}\n\n// IgnoreFlow tells the router to stop recording future flows that matches the provided criteria.\n// messageType and subType will target nebula underlay packets while tun will target nebula overlay packets\n// NOTE: This is a very broad system, if you set tun to true then no more tun traffic will be rendered\nfunc (r *R) IgnoreFlow(messageType header.MessageType, subType header.MessageSubType, tun NullBool) {\n\tr.Lock()\n\tdefer r.Unlock()\n\tr.ignoreFlows = append(r.ignoreFlows, ignoreFlow{\n\t\ttun,\n\t\tmessageType,\n\t\tsubType,\n\t})\n}\n\nfunc (r *R) RenderHostmaps(title string, controls ...*nebula.Control) {\n\tr.Lock()\n\tdefer r.Unlock()\n\n\ts := renderHostmaps(controls...)\n\tif len(r.additionalGraphs) > 0 {\n\t\tlastGraph := r.additionalGraphs[len(r.additionalGraphs)-1]\n\t\tif lastGraph.content == s && lastGraph.title == title {\n\t\t\t// Ignore this rendering if it matches the last rendering added\n\t\t\t// This is useful if you want to track rendering changes\n\t\t\treturn\n\t\t}\n\t}\n\n\tr.additionalGraphs = append(r.additionalGraphs, mermaidGraph{\n\t\ttitle:   title,\n\t\tcontent: s,\n\t})\n}\n\nfunc (r *R) renderHostmaps(title string) {\n\tc := maps.Values(r.controls)\n\tsort.SliceStable(c, func(i, j int) bool {\n\t\treturn c[i].GetVpnAddrs()[0].Compare(c[j].GetVpnAddrs()[0]) > 0\n\t})\n\n\ts := renderHostmaps(c...)\n\tif len(r.additionalGraphs) > 0 {\n\t\tlastGraph := r.additionalGraphs[len(r.additionalGraphs)-1]\n\t\tif lastGraph.content == s {\n\t\t\t// Ignore this rendering if it matches the last rendering added\n\t\t\t// This is useful if you want to track rendering changes\n\t\t\treturn\n\t\t}\n\t}\n\n\tr.additionalGraphs = append(r.additionalGraphs, mermaidGraph{\n\t\ttitle:   title,\n\t\tcontent: s,\n\t})\n}\n\n// InjectFlow can be used to record packet flow if the test is handling the routing on its own.\n// The packet is assumed to have been received\nfunc (r *R) InjectFlow(from, to *nebula.Control, p *udp.Packet) {\n\tr.Lock()\n\tdefer r.Unlock()\n\tr.unlockedInjectFlow(from, to, p, false)\n}\n\nfunc (r *R) Log(arg ...any) {\n\tif r.flow == nil {\n\t\treturn\n\t}\n\n\tr.Lock()\n\tr.flow = append(r.flow, flowEntry{note: fmt.Sprint(arg...)})\n\tr.t.Log(arg...)\n\tr.Unlock()\n}\n\nfunc (r *R) Logf(format string, arg ...any) {\n\tif r.flow == nil {\n\t\treturn\n\t}\n\n\tr.Lock()\n\tr.flow = append(r.flow, flowEntry{note: fmt.Sprintf(format, arg...)})\n\tr.t.Logf(format, arg...)\n\tr.Unlock()\n}\n\n// unlockedInjectFlow is used by the router to record a packet has been transmitted, the packet is returned and\n// should be marked as received AFTER it has been placed on the receivers channel.\n// If flow logs have been disabled this function will return nil\nfunc (r *R) unlockedInjectFlow(from, to *nebula.Control, p *udp.Packet, tun bool) *packet {\n\tif r.flow == nil {\n\t\treturn nil\n\t}\n\n\tr.renderHostmaps(fmt.Sprintf(\"Packet %v\", len(r.flow)))\n\n\tif len(r.ignoreFlows) > 0 {\n\t\tvar h header.H\n\t\terr := h.Parse(p.Data)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tfor _, i := range r.ignoreFlows {\n\t\t\tif !tun {\n\t\t\t\tif i.messageType == h.Type && i.subType == h.Subtype {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t} else if i.tun.HasValue && i.tun.IsTrue {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\n\tfp := &packet{\n\t\tfrom:   from,\n\t\tto:     to,\n\t\tpacket: p.Copy(),\n\t\ttun:    tun,\n\t}\n\n\tr.flow = append(r.flow, flowEntry{packet: fp})\n\treturn fp\n}\n\n// OnceFrom will route a single packet from sender then return\n// If the router doesn't have the nebula controller for that address, we panic\nfunc (r *R) OnceFrom(sender *nebula.Control) {\n\tr.RouteExitFunc(sender, func(*udp.Packet, *nebula.Control) ExitType {\n\t\treturn RouteAndExit\n\t})\n}\n\n// RouteUntilTxTun will route for sender and return when a packet is seen on receivers tun\n// If the router doesn't have the nebula controller for that address, we panic\nfunc (r *R) RouteUntilTxTun(sender *nebula.Control, receiver *nebula.Control) []byte {\n\ttunTx := receiver.GetTunTxChan()\n\tudpTx := sender.GetUDPTxChan()\n\n\tfor {\n\t\tselect {\n\t\t// Maybe we already have something on the tun for us\n\t\tcase b := <-tunTx:\n\t\t\tr.Lock()\n\t\t\tnp := udp.Packet{Data: make([]byte, len(b))}\n\t\t\tcopy(np.Data, b)\n\t\t\tr.unlockedInjectFlow(receiver, receiver, &np, true)\n\t\t\tr.Unlock()\n\t\t\treturn b\n\n\t\t// Nope, lets push the sender along\n\t\tcase p := <-udpTx:\n\t\t\tr.Lock()\n\t\t\ta := sender.GetUDPAddr()\n\t\t\tc := r.getControl(a, p.To, p)\n\t\t\tif c == nil {\n\t\t\t\tr.Unlock()\n\t\t\t\tpanic(\"No control for udp tx \" + a.String())\n\t\t\t}\n\t\t\tfp := r.unlockedInjectFlow(sender, c, p, false)\n\t\t\tc.InjectUDPPacket(p)\n\t\t\tfp.WasReceived()\n\t\t\tr.Unlock()\n\t\t}\n\t}\n}\n\n// RouteForAllUntilTxTun will route for everyone and return when a packet is seen on receivers tun\n// If the router doesn't have the nebula controller for that address, we panic\nfunc (r *R) RouteForAllUntilTxTun(receiver *nebula.Control) []byte {\n\tsc := make([]reflect.SelectCase, len(r.controls)+1)\n\tcm := make([]*nebula.Control, len(r.controls)+1)\n\n\ti := 0\n\tsc[i] = reflect.SelectCase{\n\t\tDir:  reflect.SelectRecv,\n\t\tChan: reflect.ValueOf(receiver.GetTunTxChan()),\n\t\tSend: reflect.Value{},\n\t}\n\tcm[i] = receiver\n\n\ti++\n\tfor _, c := range r.controls {\n\t\tsc[i] = reflect.SelectCase{\n\t\t\tDir:  reflect.SelectRecv,\n\t\t\tChan: reflect.ValueOf(c.GetUDPTxChan()),\n\t\t\tSend: reflect.Value{},\n\t\t}\n\n\t\tcm[i] = c\n\t\ti++\n\t}\n\n\tfor {\n\t\tx, rx, _ := reflect.Select(sc)\n\t\tr.Lock()\n\n\t\tif x == 0 {\n\t\t\t// we are the tun tx, we can exit\n\t\t\tp := rx.Interface().([]byte)\n\t\t\tnp := udp.Packet{Data: make([]byte, len(p))}\n\t\t\tcopy(np.Data, p)\n\n\t\t\tr.unlockedInjectFlow(cm[x], cm[x], &np, true)\n\t\t\tr.Unlock()\n\t\t\treturn p\n\n\t\t} else {\n\t\t\t// we are a udp tx, route and continue\n\t\t\tp := rx.Interface().(*udp.Packet)\n\t\t\ta := cm[x].GetUDPAddr()\n\t\t\tc := r.getControl(a, p.To, p)\n\t\t\tif c == nil {\n\t\t\t\tr.Unlock()\n\t\t\t\tpanic(fmt.Sprintf(\"No control for udp tx %s\", p.To))\n\t\t\t}\n\t\t\tfp := r.unlockedInjectFlow(cm[x], c, p, false)\n\t\t\tc.InjectUDPPacket(p)\n\t\t\tfp.WasReceived()\n\t\t}\n\t\tr.Unlock()\n\t}\n}\n\n// RouteExitFunc will call the whatDo func with each udp packet from sender.\n// whatDo can return:\n//   - exitNow: the packet will not be routed and this call will return immediately\n//   - routeAndExit: this call will return immediately after routing the last packet from sender\n//   - keepRouting: the packet will be routed and whatDo will be called again on the next packet from sender\nfunc (r *R) RouteExitFunc(sender *nebula.Control, whatDo ExitFunc) {\n\th := &header.H{}\n\tfor {\n\t\tp := sender.GetFromUDP(true)\n\t\tr.Lock()\n\t\tif err := h.Parse(p.Data); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\treceiver := r.getControl(sender.GetUDPAddr(), p.To, p)\n\t\tif receiver == nil {\n\t\t\tr.Unlock()\n\t\t\tpanic(\"Can't RouteExitFunc for host: \" + p.To.String())\n\t\t}\n\n\t\te := whatDo(p, receiver)\n\t\tswitch e {\n\t\tcase ExitNow:\n\t\t\tr.Unlock()\n\t\t\treturn\n\n\t\tcase RouteAndExit:\n\t\t\tfp := r.unlockedInjectFlow(sender, receiver, p, false)\n\t\t\treceiver.InjectUDPPacket(p)\n\t\t\tfp.WasReceived()\n\t\t\tr.Unlock()\n\t\t\treturn\n\n\t\tcase KeepRouting:\n\t\t\tfp := r.unlockedInjectFlow(sender, receiver, p, false)\n\t\t\treceiver.InjectUDPPacket(p)\n\t\t\tfp.WasReceived()\n\n\t\tdefault:\n\t\t\tpanic(fmt.Sprintf(\"Unknown exitFunc return: %v\", e))\n\t\t}\n\n\t\tr.Unlock()\n\t}\n}\n\n// RouteUntilAfterMsgType will route for sender until a message type is seen and sent from sender\n// If the router doesn't have the nebula controller for that address, we panic\nfunc (r *R) RouteUntilAfterMsgType(sender *nebula.Control, msgType header.MessageType, subType header.MessageSubType) {\n\th := &header.H{}\n\tr.RouteExitFunc(sender, func(p *udp.Packet, r *nebula.Control) ExitType {\n\t\tif err := h.Parse(p.Data); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tif h.Type == msgType && h.Subtype == subType {\n\t\t\treturn RouteAndExit\n\t\t}\n\n\t\treturn KeepRouting\n\t})\n}\n\nfunc (r *R) RouteForAllUntilAfterMsgTypeTo(receiver *nebula.Control, msgType header.MessageType, subType header.MessageSubType) {\n\th := &header.H{}\n\tr.RouteForAllExitFunc(func(p *udp.Packet, r *nebula.Control) ExitType {\n\t\tif r != receiver {\n\t\t\treturn KeepRouting\n\t\t}\n\n\t\tif err := h.Parse(p.Data); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tif h.Type == msgType && h.Subtype == subType {\n\t\t\treturn RouteAndExit\n\t\t}\n\n\t\treturn KeepRouting\n\t})\n}\n\nfunc (r *R) InjectUDPPacket(sender, receiver *nebula.Control, packet *udp.Packet) {\n\tr.Lock()\n\tdefer r.Unlock()\n\n\tfp := r.unlockedInjectFlow(sender, receiver, packet, false)\n\treceiver.InjectUDPPacket(packet)\n\tfp.WasReceived()\n}\n\n// RouteForUntilAfterToAddr will route for sender and return only after it sees and sends a packet destined for toAddr\n// finish can be any of the exitType values except `keepRouting`, the default value is `routeAndExit`\n// If the router doesn't have the nebula controller for that address, we panic\nfunc (r *R) RouteForUntilAfterToAddr(sender *nebula.Control, toAddr netip.AddrPort, finish ExitType) {\n\tif finish == KeepRouting {\n\t\tfinish = RouteAndExit\n\t}\n\n\tr.RouteExitFunc(sender, func(p *udp.Packet, r *nebula.Control) ExitType {\n\t\tif p.To == toAddr {\n\t\t\treturn finish\n\t\t}\n\n\t\treturn KeepRouting\n\t})\n}\n\n// RouteForAllExitFunc will route for every registered controller and calls the whatDo func with each udp packet from\n// whatDo can return:\n//   - exitNow: the packet will not be routed and this call will return immediately\n//   - routeAndExit: this call will return immediately after routing the last packet from sender\n//   - keepRouting: the packet will be routed and whatDo will be called again on the next packet from sender\nfunc (r *R) RouteForAllExitFunc(whatDo ExitFunc) {\n\tsc := make([]reflect.SelectCase, len(r.controls))\n\tcm := make([]*nebula.Control, len(r.controls))\n\n\ti := 0\n\tfor _, c := range r.controls {\n\t\tsc[i] = reflect.SelectCase{\n\t\t\tDir:  reflect.SelectRecv,\n\t\t\tChan: reflect.ValueOf(c.GetUDPTxChan()),\n\t\t\tSend: reflect.Value{},\n\t\t}\n\n\t\tcm[i] = c\n\t\ti++\n\t}\n\n\tfor {\n\t\tx, rx, _ := reflect.Select(sc)\n\t\tr.Lock()\n\n\t\tp := rx.Interface().(*udp.Packet)\n\t\treceiver := r.getControl(cm[x].GetUDPAddr(), p.To, p)\n\t\tif receiver == nil {\n\t\t\tr.Unlock()\n\t\t\tpanic(\"Can't RouteForAllExitFunc for host: \" + p.To.String())\n\t\t}\n\n\t\te := whatDo(p, receiver)\n\t\tswitch e {\n\t\tcase ExitNow:\n\t\t\tr.Unlock()\n\t\t\treturn\n\n\t\tcase RouteAndExit:\n\t\t\tfp := r.unlockedInjectFlow(cm[x], receiver, p, false)\n\t\t\treceiver.InjectUDPPacket(p)\n\t\t\tfp.WasReceived()\n\t\t\tr.Unlock()\n\t\t\treturn\n\n\t\tcase KeepRouting:\n\t\t\tfp := r.unlockedInjectFlow(cm[x], receiver, p, false)\n\t\t\treceiver.InjectUDPPacket(p)\n\t\t\tfp.WasReceived()\n\n\t\tdefault:\n\t\t\tpanic(fmt.Sprintf(\"Unknown exitFunc return: %v\", e))\n\t\t}\n\t\tr.Unlock()\n\t}\n}\n\n// FlushAll will route for every registered controller, exiting once there are no packets left to route\nfunc (r *R) FlushAll() {\n\tsc := make([]reflect.SelectCase, len(r.controls))\n\tcm := make([]*nebula.Control, len(r.controls))\n\n\ti := 0\n\tfor _, c := range r.controls {\n\t\tsc[i] = reflect.SelectCase{\n\t\t\tDir:  reflect.SelectRecv,\n\t\t\tChan: reflect.ValueOf(c.GetUDPTxChan()),\n\t\t\tSend: reflect.Value{},\n\t\t}\n\n\t\tcm[i] = c\n\t\ti++\n\t}\n\n\t// Add a default case to exit when nothing is left to send\n\tsc = append(sc, reflect.SelectCase{\n\t\tDir:  reflect.SelectDefault,\n\t\tChan: reflect.Value{},\n\t\tSend: reflect.Value{},\n\t})\n\n\tfor {\n\t\tx, rx, ok := reflect.Select(sc)\n\t\tif !ok {\n\t\t\treturn\n\t\t}\n\t\tr.Lock()\n\n\t\tp := rx.Interface().(*udp.Packet)\n\n\t\treceiver := r.getControl(cm[x].GetUDPAddr(), p.To, p)\n\t\tif receiver == nil {\n\t\t\tr.Unlock()\n\t\t\tpanic(\"Can't FlushAll for host: \" + p.To.String())\n\t\t}\n\t\treceiver.InjectUDPPacket(p)\n\t\tr.Unlock()\n\t}\n}\n\n// getControl performs or seeds NAT translation and returns the control for toAddr, p from fields may change\n// This is an internal router function, the caller must hold the lock\nfunc (r *R) getControl(fromAddr, toAddr netip.AddrPort, p *udp.Packet) *nebula.Control {\n\tif newAddr, ok := r.outNat[fromAddr.String()+\":\"+toAddr.String()]; ok {\n\t\tp.From = newAddr\n\t}\n\n\tc, ok := r.inNat[toAddr]\n\tif ok {\n\t\tr.outNat[c.GetUDPAddr().String()+\":\"+fromAddr.String()] = toAddr\n\t\treturn c\n\t}\n\n\treturn r.controls[toAddr]\n}\n\nfunc (r *R) formatUdpPacket(p *packet) string {\n\tvar packet gopacket.Packet\n\tvar srcAddr netip.Addr\n\n\tpacket = gopacket.NewPacket(p.packet.Data, layers.LayerTypeIPv6, gopacket.Lazy)\n\tif packet.ErrorLayer() == nil {\n\t\tv6 := packet.Layer(layers.LayerTypeIPv6).(*layers.IPv6)\n\t\tif v6 == nil {\n\t\t\tpanic(\"not an ipv6 packet\")\n\t\t}\n\t\tsrcAddr, _ = netip.AddrFromSlice(v6.SrcIP)\n\t} else {\n\t\tpacket = gopacket.NewPacket(p.packet.Data, layers.LayerTypeIPv4, gopacket.Lazy)\n\t\tv6 := packet.Layer(layers.LayerTypeIPv4).(*layers.IPv4)\n\t\tif v6 == nil {\n\t\t\tpanic(\"not an ipv6 packet\")\n\t\t}\n\t\tsrcAddr, _ = netip.AddrFromSlice(v6.SrcIP)\n\t}\n\n\tfrom := \"unknown\"\n\tif c, ok := r.vpnControls[srcAddr]; ok {\n\t\tfrom = c.GetUDPAddr().String()\n\t}\n\n\tudpLayer := packet.Layer(layers.LayerTypeUDP).(*layers.UDP)\n\tif udpLayer == nil {\n\t\tpanic(\"not a udp packet\")\n\t}\n\n\tdata := packet.ApplicationLayer()\n\treturn fmt.Sprintf(\n\t\t\"    %s-->>%s: src port: %v<br/>dest port: %v<br/>data: \\\"%v\\\"\\n\",\n\t\tnormalizeName(from),\n\t\tnormalizeName(p.to.GetUDPAddr().String()),\n\t\tudpLayer.SrcPort,\n\t\tudpLayer.DstPort,\n\t\tstring(data.Payload()),\n\t)\n}\n"
  },
  {
    "path": "e2e/tunnels_test.go",
    "content": "//go:build e2e_testing\n// +build e2e_testing\n\npackage e2e\n\nimport (\n\t\"fmt\"\n\t\"net/netip\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/slackhq/nebula/cert\"\n\t\"github.com/slackhq/nebula/cert_test\"\n\t\"github.com/slackhq/nebula/e2e/router\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"gopkg.in/yaml.v3\"\n)\n\nfunc TestDropInactiveTunnels(t *testing.T) {\n\t// The goal of this test is to ensure the shortest inactivity timeout will close the tunnel on both sides\n\t// under ideal conditions\n\tca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})\n\tmyControl, myVpnIpNet, myUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, \"me\", \"10.128.0.1/24\", m{\"tunnels\": m{\"drop_inactive\": true, \"inactivity_timeout\": \"5s\"}})\n\ttheirControl, theirVpnIpNet, theirUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, \"them\", \"10.128.0.2/24\", m{\"tunnels\": m{\"drop_inactive\": true, \"inactivity_timeout\": \"10m\"}})\n\n\t// Share our underlay information\n\tmyControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)\n\ttheirControl.InjectLightHouseAddr(myVpnIpNet[0].Addr(), myUdpAddr)\n\n\t// Start the servers\n\tmyControl.Start()\n\ttheirControl.Start()\n\n\tr := router.NewR(t, myControl, theirControl)\n\n\tr.Log(\"Assert the tunnel between me and them works\")\n\tassertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)\n\n\tr.Log(\"Go inactive and wait for the tunnels to get dropped\")\n\twaitStart := time.Now()\n\tfor {\n\t\tmyIndexes := len(myControl.GetHostmap().Indexes)\n\t\ttheirIndexes := len(theirControl.GetHostmap().Indexes)\n\t\tif myIndexes == 0 && theirIndexes == 0 {\n\t\t\tbreak\n\t\t}\n\n\t\tsince := time.Since(waitStart)\n\t\tr.Logf(\"my tunnels: %v; their tunnels: %v; duration: %v\", myIndexes, theirIndexes, since)\n\t\tif since > time.Second*30 {\n\t\t\tt.Fatal(\"Tunnel should have been declared inactive after 5 seconds and before 30 seconds\")\n\t\t}\n\n\t\ttime.Sleep(1 * time.Second)\n\t\tr.FlushAll()\n\t}\n\n\tr.Logf(\"Inactive tunnels were dropped within %v\", time.Since(waitStart))\n\tmyControl.Stop()\n\ttheirControl.Stop()\n}\n\nfunc TestCertUpgrade(t *testing.T) {\n\t// The goal of this test is to ensure the shortest inactivity timeout will close the tunnel on both sides\n\t// under ideal conditions\n\tca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})\n\tcaB, err := ca.MarshalPEM()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tca2, _, caKey2, _ := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})\n\n\tca2B, err := ca2.MarshalPEM()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tcaStr := fmt.Sprintf(\"%s\\n%s\", caB, ca2B)\n\n\tmyCert, _, myPrivKey, _ := cert_test.NewTestCert(cert.Version1, cert.Curve_CURVE25519, ca, caKey, \"me\", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{netip.MustParsePrefix(\"10.128.0.1/24\")}, nil, []string{})\n\t_, myCert2Pem := cert_test.NewTestCertDifferentVersion(myCert, cert.Version2, ca2, caKey2)\n\n\ttheirCert, _, theirPrivKey, _ := cert_test.NewTestCert(cert.Version1, cert.Curve_CURVE25519, ca, caKey, \"them\", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{netip.MustParsePrefix(\"10.128.0.2/24\")}, nil, []string{})\n\ttheirCert2, _ := cert_test.NewTestCertDifferentVersion(theirCert, cert.Version2, ca2, caKey2)\n\n\tmyControl, myVpnIpNet, myUdpAddr, myC := newServer([]cert.Certificate{ca, ca2}, []cert.Certificate{myCert}, myPrivKey, m{})\n\ttheirControl, theirVpnIpNet, theirUdpAddr, _ := newServer([]cert.Certificate{ca, ca2}, []cert.Certificate{theirCert, theirCert2}, theirPrivKey, m{})\n\n\t// Share our underlay information\n\tmyControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)\n\ttheirControl.InjectLightHouseAddr(myVpnIpNet[0].Addr(), myUdpAddr)\n\n\t// Start the servers\n\tmyControl.Start()\n\ttheirControl.Start()\n\n\tr := router.NewR(t, myControl, theirControl)\n\tdefer r.RenderFlow()\n\n\tr.Log(\"Assert the tunnel between me and them works\")\n\tassertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)\n\tr.Log(\"yay\")\n\t//todo ???\n\ttime.Sleep(1 * time.Second)\n\tr.FlushAll()\n\n\tmc := m{\n\t\t\"pki\": m{\n\t\t\t\"ca\":   caStr,\n\t\t\t\"cert\": string(myCert2Pem),\n\t\t\t\"key\":  string(myPrivKey),\n\t\t},\n\t\t//\"tun\": m{\"disabled\": true},\n\t\t\"firewall\": myC.Settings[\"firewall\"],\n\t\t//\"handshakes\": m{\n\t\t//\t\"try_interval\": \"1s\",\n\t\t//},\n\t\t\"listen\":  myC.Settings[\"listen\"],\n\t\t\"logging\": myC.Settings[\"logging\"],\n\t\t\"timers\":  myC.Settings[\"timers\"],\n\t}\n\n\tcb, err := yaml.Marshal(mc)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tr.Logf(\"reload new v2-only config\")\n\terr = myC.ReloadConfigString(string(cb))\n\tassert.NoError(t, err)\n\tr.Log(\"yay, spin until their sees it\")\n\twaitStart := time.Now()\n\tfor {\n\t\tassertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)\n\t\tc := theirControl.GetHostInfoByVpnAddr(myVpnIpNet[0].Addr(), false)\n\t\tif c == nil {\n\t\t\tr.Log(\"nil\")\n\t\t} else {\n\t\t\tversion := c.Cert.Version()\n\t\t\tr.Logf(\"version %d\", version)\n\t\t\tif version == cert.Version2 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tsince := time.Since(waitStart)\n\t\tif since > time.Second*10 {\n\t\t\tt.Fatal(\"Cert should be new by now\")\n\t\t}\n\t\ttime.Sleep(time.Second)\n\t}\n\n\tr.RenderHostmaps(\"Final hostmaps\", myControl, theirControl)\n\n\tmyControl.Stop()\n\ttheirControl.Stop()\n}\n\nfunc TestCertDowngrade(t *testing.T) {\n\t// The goal of this test is to ensure the shortest inactivity timeout will close the tunnel on both sides\n\t// under ideal conditions\n\tca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})\n\tcaB, err := ca.MarshalPEM()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tca2, _, caKey2, _ := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})\n\n\tca2B, err := ca2.MarshalPEM()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tcaStr := fmt.Sprintf(\"%s\\n%s\", caB, ca2B)\n\n\tmyCert, _, myPrivKey, myCertPem := cert_test.NewTestCert(cert.Version1, cert.Curve_CURVE25519, ca, caKey, \"me\", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{netip.MustParsePrefix(\"10.128.0.1/24\")}, nil, []string{})\n\tmyCert2, _ := cert_test.NewTestCertDifferentVersion(myCert, cert.Version2, ca2, caKey2)\n\n\ttheirCert, _, theirPrivKey, _ := cert_test.NewTestCert(cert.Version1, cert.Curve_CURVE25519, ca, caKey, \"them\", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{netip.MustParsePrefix(\"10.128.0.2/24\")}, nil, []string{})\n\ttheirCert2, _ := cert_test.NewTestCertDifferentVersion(theirCert, cert.Version2, ca2, caKey2)\n\n\tmyControl, myVpnIpNet, myUdpAddr, myC := newServer([]cert.Certificate{ca, ca2}, []cert.Certificate{myCert2}, myPrivKey, m{})\n\ttheirControl, theirVpnIpNet, theirUdpAddr, _ := newServer([]cert.Certificate{ca, ca2}, []cert.Certificate{theirCert, theirCert2}, theirPrivKey, m{})\n\n\t// Share our underlay information\n\tmyControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)\n\ttheirControl.InjectLightHouseAddr(myVpnIpNet[0].Addr(), myUdpAddr)\n\n\t// Start the servers\n\tmyControl.Start()\n\ttheirControl.Start()\n\n\tr := router.NewR(t, myControl, theirControl)\n\tdefer r.RenderFlow()\n\n\tr.Log(\"Assert the tunnel between me and them works\")\n\t//assertTunnel(t, theirVpnIpNet[0].Addr(), myVpnIpNet[0].Addr(), theirControl, myControl, r)\n\t//r.Log(\"yay\")\n\tassertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)\n\tr.Log(\"yay\")\n\t//todo ???\n\ttime.Sleep(1 * time.Second)\n\tr.FlushAll()\n\n\tmc := m{\n\t\t\"pki\": m{\n\t\t\t\"ca\":   caStr,\n\t\t\t\"cert\": string(myCertPem),\n\t\t\t\"key\":  string(myPrivKey),\n\t\t},\n\t\t\"firewall\": myC.Settings[\"firewall\"],\n\t\t\"listen\":   myC.Settings[\"listen\"],\n\t\t\"logging\":  myC.Settings[\"logging\"],\n\t\t\"timers\":   myC.Settings[\"timers\"],\n\t}\n\n\tcb, err := yaml.Marshal(mc)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tr.Logf(\"reload new v1-only config\")\n\terr = myC.ReloadConfigString(string(cb))\n\tassert.NoError(t, err)\n\tr.Log(\"yay, spin until their sees it\")\n\twaitStart := time.Now()\n\tfor {\n\t\tassertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)\n\t\tc := theirControl.GetHostInfoByVpnAddr(myVpnIpNet[0].Addr(), false)\n\t\tc2 := myControl.GetHostInfoByVpnAddr(theirVpnIpNet[0].Addr(), false)\n\t\tif c == nil || c2 == nil {\n\t\t\tr.Log(\"nil\")\n\t\t} else {\n\t\t\tversion := c.Cert.Version()\n\t\t\ttheirVersion := c2.Cert.Version()\n\t\t\tr.Logf(\"version %d,%d\", version, theirVersion)\n\t\t\tif version == cert.Version1 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tsince := time.Since(waitStart)\n\t\tif since > time.Second*5 {\n\t\t\tr.Log(\"it is unusual that the cert is not new yet, but not a failure yet\")\n\t\t}\n\t\tif since > time.Second*10 {\n\t\t\tr.Log(\"wtf\")\n\t\t\tt.Fatal(\"Cert should be new by now\")\n\t\t}\n\t\ttime.Sleep(time.Second)\n\t}\n\n\tr.RenderHostmaps(\"Final hostmaps\", myControl, theirControl)\n\n\tmyControl.Stop()\n\ttheirControl.Stop()\n}\n\nfunc TestCertMismatchCorrection(t *testing.T) {\n\t// The goal of this test is to ensure the shortest inactivity timeout will close the tunnel on both sides\n\t// under ideal conditions\n\tca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})\n\tca2, _, caKey2, _ := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})\n\n\tmyCert, _, myPrivKey, _ := cert_test.NewTestCert(cert.Version1, cert.Curve_CURVE25519, ca, caKey, \"me\", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{netip.MustParsePrefix(\"10.128.0.1/24\")}, nil, []string{})\n\tmyCert2, _ := cert_test.NewTestCertDifferentVersion(myCert, cert.Version2, ca2, caKey2)\n\n\ttheirCert, _, theirPrivKey, _ := cert_test.NewTestCert(cert.Version1, cert.Curve_CURVE25519, ca, caKey, \"them\", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{netip.MustParsePrefix(\"10.128.0.2/24\")}, nil, []string{})\n\ttheirCert2, _ := cert_test.NewTestCertDifferentVersion(theirCert, cert.Version2, ca2, caKey2)\n\n\tmyControl, myVpnIpNet, myUdpAddr, _ := newServer([]cert.Certificate{ca, ca2}, []cert.Certificate{myCert2}, myPrivKey, m{})\n\ttheirControl, theirVpnIpNet, theirUdpAddr, _ := newServer([]cert.Certificate{ca, ca2}, []cert.Certificate{theirCert, theirCert2}, theirPrivKey, m{})\n\n\t// Share our underlay information\n\tmyControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)\n\ttheirControl.InjectLightHouseAddr(myVpnIpNet[0].Addr(), myUdpAddr)\n\n\t// Start the servers\n\tmyControl.Start()\n\ttheirControl.Start()\n\n\tr := router.NewR(t, myControl, theirControl)\n\tdefer r.RenderFlow()\n\n\tr.Log(\"Assert the tunnel between me and them works\")\n\t//assertTunnel(t, theirVpnIpNet[0].Addr(), myVpnIpNet[0].Addr(), theirControl, myControl, r)\n\t//r.Log(\"yay\")\n\tassertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)\n\tr.Log(\"yay\")\n\t//todo ???\n\ttime.Sleep(1 * time.Second)\n\tr.FlushAll()\n\n\twaitStart := time.Now()\n\tfor {\n\t\tassertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)\n\t\tc := theirControl.GetHostInfoByVpnAddr(myVpnIpNet[0].Addr(), false)\n\t\tc2 := myControl.GetHostInfoByVpnAddr(theirVpnIpNet[0].Addr(), false)\n\t\tif c == nil || c2 == nil {\n\t\t\tr.Log(\"nil\")\n\t\t} else {\n\t\t\tversion := c.Cert.Version()\n\t\t\ttheirVersion := c2.Cert.Version()\n\t\t\tr.Logf(\"version %d,%d\", version, theirVersion)\n\t\t\tif version == theirVersion {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tsince := time.Since(waitStart)\n\t\tif since > time.Second*5 {\n\t\t\tr.Log(\"wtf\")\n\t\t}\n\t\tif since > time.Second*10 {\n\t\t\tr.Log(\"wtf\")\n\t\t\tt.Fatal(\"Cert should be new by now\")\n\t\t}\n\t\ttime.Sleep(time.Second)\n\t}\n\n\tr.RenderHostmaps(\"Final hostmaps\", myControl, theirControl)\n\n\tmyControl.Stop()\n\ttheirControl.Stop()\n}\n\nfunc TestCrossStackRelaysWork(t *testing.T) {\n\tca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})\n\tmyControl, myVpnIpNet, _, _ := newSimpleServer(cert.Version2, ca, caKey, \"me     \", \"10.128.0.1/24,fc00::1/64\", m{\"relay\": m{\"use_relays\": true}})\n\trelayControl, relayVpnIpNet, relayUdpAddr, _ := newSimpleServer(cert.Version2, ca, caKey, \"relay  \", \"10.128.0.128/24,fc00::128/64\", m{\"relay\": m{\"am_relay\": true}})\n\ttheirUdp := netip.MustParseAddrPort(\"10.0.0.2:4242\")\n\ttheirControl, theirVpnIpNet, theirUdpAddr, _ := newSimpleServerWithUdp(cert.Version2, ca, caKey, \"them   \", \"fc00::2/64\", theirUdp, m{\"relay\": m{\"use_relays\": true}})\n\n\t//myVpnV4 := myVpnIpNet[0]\n\tmyVpnV6 := myVpnIpNet[1]\n\trelayVpnV4 := relayVpnIpNet[0]\n\trelayVpnV6 := relayVpnIpNet[1]\n\ttheirVpnV6 := theirVpnIpNet[0]\n\n\t// Teach my how to get to the relay and that their can be reached via the relay\n\tmyControl.InjectLightHouseAddr(relayVpnV4.Addr(), relayUdpAddr)\n\tmyControl.InjectLightHouseAddr(relayVpnV6.Addr(), relayUdpAddr)\n\tmyControl.InjectRelays(theirVpnV6.Addr(), []netip.Addr{relayVpnV6.Addr()})\n\trelayControl.InjectLightHouseAddr(theirVpnV6.Addr(), theirUdpAddr)\n\n\t// Build a router so we don't have to reason who gets which packet\n\tr := router.NewR(t, myControl, relayControl, theirControl)\n\tdefer r.RenderFlow()\n\n\t// Start the servers\n\tmyControl.Start()\n\trelayControl.Start()\n\ttheirControl.Start()\n\n\tt.Log(\"Trigger a handshake from me to them via the relay\")\n\tmyControl.InjectTunUDPPacket(theirVpnV6.Addr(), 80, myVpnV6.Addr(), 80, []byte(\"Hi from me\"))\n\n\tp := r.RouteForAllUntilTxTun(theirControl)\n\tr.Log(\"Assert the tunnel works\")\n\tassertUdpPacket(t, []byte(\"Hi from me\"), p, myVpnV6.Addr(), theirVpnV6.Addr(), 80, 80)\n\n\tt.Log(\"reply?\")\n\ttheirControl.InjectTunUDPPacket(myVpnV6.Addr(), 80, theirVpnV6.Addr(), 80, []byte(\"Hi from them\"))\n\tp = r.RouteForAllUntilTxTun(myControl)\n\tassertUdpPacket(t, []byte(\"Hi from them\"), p, theirVpnV6.Addr(), myVpnV6.Addr(), 80, 80)\n\n\tr.RenderHostmaps(\"Final hostmaps\", myControl, relayControl, theirControl)\n\t//t.Log(\"finish up\")\n\t//myControl.Stop()\n\t//theirControl.Stop()\n\t//relayControl.Stop()\n}\n"
  },
  {
    "path": "examples/config.yml",
    "content": "# This is the nebula example configuration file. You must edit, at a minimum, the static_host_map, lighthouse, and firewall sections\n# Some options in this file are HUPable, including the pki section. (A HUP will reload credentials from disk without affecting existing tunnels)\n\n# PKI defines the location of credentials for this node. Each of these can also be inlined by using the yaml \": |\" syntax.\npki:\n  # The CAs that are accepted by this node. Must contain one or more certificates created by 'nebula-cert ca'\n  ca: /etc/nebula/ca.crt\n  cert: /etc/nebula/host.crt\n  key: /etc/nebula/host.key\n  # blocklist is a list of certificate fingerprints that we will refuse to talk to\n  #blocklist:\n  #  - c99d4e650533b92061b09918e838a5a0a6aaee21eed1d12fd937682865936c72\n  # disconnect_invalid is a toggle to force a client to be disconnected if the certificate is expired or invalid.\n  #disconnect_invalid: true\n\n  # initiating_version controls which certificate version is used when initiating handshakes.\n  # This setting only applies if both a v1 and a v2 certificate are configured, in which case it will default to `1`.\n  # Once all hosts in the mesh are configured with both a v1 and v2 certificate then this should be changed to `2`.\n  # After all hosts in the mesh are using a v2 certificate then v1 certificates are no longer needed.\n  # initiating_version: 1\n\n# The static host map defines a set of hosts with fixed IP addresses on the internet (or any network).\n# A host can have multiple fixed IP addresses defined here, and nebula will try each when establishing a tunnel.\n# The syntax is:\n#   \"{nebula ip}\": [\"{routable ip/dns name}:{routable port}\"]\n# Example, if your lighthouse has the nebula IP of 192.168.100.1 and has the real ip address of 100.64.22.11 and runs on port 4242:\nstatic_host_map:\n  \"192.168.100.1\": [\"100.64.22.11:4242\"]\n\n# The static_map config stanza can be used to configure how the static_host_map behaves.\n#static_map:\n  # cadence determines how frequently DNS is re-queried for updated IP addresses when a static_host_map entry contains\n  # a DNS name.\n  #cadence: 30s\n\n  # network determines the type of IP addresses to ask the DNS server for. The default is \"ip4\" because nodes typically\n  # do not know their public IPv4 address. Connecting to the Lighthouse via IPv4 allows the Lighthouse to detect the\n  # public address. Other valid options are \"ip6\" and \"ip\" (returns both.)\n  #network: ip4\n\n  # lookup_timeout is the DNS query timeout.\n  #lookup_timeout: 250ms\n\nlighthouse:\n  # am_lighthouse is used to enable lighthouse functionality for a node. This should ONLY be true on nodes\n  # you have configured to be lighthouses in your network\n  am_lighthouse: false\n  # serve_dns optionally starts a dns listener that responds to various queries and can even be\n  # delegated to for resolution\n  #serve_dns: false\n  #dns:\n    # The DNS host defines the IP to bind the dns listener to. This also allows binding to the nebula node IP.\n    #host: 0.0.0.0\n    #port: 53\n  # interval is the number of seconds between updates from this node to a lighthouse.\n  # during updates, a node sends information about its current IP addresses to each node.\n  interval: 60\n  # hosts is a list of lighthouse hosts this node should report to and query from\n  # IMPORTANT: THIS SHOULD BE EMPTY ON LIGHTHOUSE NODES\n  # IMPORTANT2: THIS SHOULD BE LIGHTHOUSES' NEBULA IPs, NOT LIGHTHOUSES' REAL ROUTABLE IPs\n  hosts:\n    - \"192.168.100.1\"\n\n  # remote_allow_list allows you to control ip ranges that this node will\n  # consider when handshaking to another node. By default, any remote IPs are\n  # allowed. You can provide CIDRs here with `true` to allow and `false` to\n  # deny. The most specific CIDR rule applies to each remote. If all rules are\n  # \"allow\", the default will be \"deny\", and vice-versa. If both \"allow\" and\n  # \"deny\" IPv4 rules are present, then you MUST set a rule for \"0.0.0.0/0\" as\n  # the default. Similarly if both \"allow\" and \"deny\" IPv6 rules are present,\n  # then you MUST set a rule for \"::/0\" as the default.\n  #remote_allow_list:\n    # Example to block IPs from this subnet from being used for remote IPs.\n    #\"172.16.0.0/12\": false\n\n    # A more complicated example, allow public IPs but only private IPs from a specific subnet\n    #\"0.0.0.0/0\": true\n    #\"10.0.0.0/8\": false\n    #\"10.42.42.0/24\": true\n\n  # EXPERIMENTAL: This option may change or disappear in the future.\n  # Optionally allows the definition of remote_allow_list blocks\n  # specific to an inside VPN IP CIDR.\n  #remote_allow_ranges:\n    # This rule would only allow only private IPs for this VPN range\n    #\"10.42.42.0/24\":\n      #\"192.168.0.0/16\": true\n\n  # local_allow_list allows you to filter which local IP addresses we advertise\n  # to the lighthouses. This uses the same logic as `remote_allow_list`, but\n  # additionally, you can specify an `interfaces` map of regular expressions\n  # to match against interface names. The regexp must match the entire name.\n  # All interface rules must be either true or false (and the default will be\n  # the inverse). CIDR rules are matched after interface name rules.\n  # Default is all local IP addresses.\n  #local_allow_list:\n    # Example to block tun0 and all docker interfaces.\n    #interfaces:\n      #tun0: false\n      #'docker.*': false\n    # Example to only advertise this subnet to the lighthouse.\n    #\"10.0.0.0/8\": true\n\n  # advertise_addrs are routable addresses that will be included along with discovered addresses to report to the\n  # lighthouse, the format is \"ip:port\". `port` can be `0`, in which case the actual listening port will be used in its\n  # place, useful if `listen.port` is set to 0.\n  # This option is mainly useful when there are static ip addresses the host can be reached at that nebula can not\n  # typically discover on its own. Examples being port forwarding or multiple paths to the internet.\n  #advertise_addrs:\n    #- \"1.1.1.1:4242\"\n    #- \"1.2.3.4:0\" # port will be replaced with the real listening port\n\n  # EXPERIMENTAL: This option may change or disappear in the future.\n  # This setting allows us to \"guess\" what the remote might be for a host\n  # while we wait for the lighthouse response.\n  #calculated_remotes:\n    # For any Nebula IPs in 10.0.10.0/24, this will apply the mask and add\n    # the calculated IP as an initial remote (while we wait for the response\n    # from the lighthouse). Both CIDRs must have the same mask size.\n    # For example, Nebula IP 10.0.10.123 will have a calculated remote of\n    # 192.168.1.123\n    #10.0.10.0/24:\n      #- mask: 192.168.1.0/24\n      #  port: 4242\n\n# Port Nebula will be listening on. The default here is 4242. For a lighthouse node, the port should be defined,\n# however using port 0 will dynamically assign a port and is recommended for roaming nodes.\nlisten:\n  # To listen on only ipv4, use \"0.0.0.0\"\n  host: \"::\"\n  port: 4242\n  # Sets the max number of packets to pull from the kernel for each syscall (under systems that support recvmmsg)\n  # default is 64, does not support reload\n  #batch: 64\n  # Configure socket buffers for the udp side (outside), leave unset to use the system defaults. Values will be doubled by the kernel\n  # Default is net.core.rmem_default and net.core.wmem_default (/proc/sys/net/core/rmem_default and /proc/sys/net/core/rmem_default)\n  # Maximum is limited by memory in the system, SO_RCVBUFFORCE and SO_SNDBUFFORCE is used to avoid having to raise the system wide\n  # max, net.core.rmem_max and net.core.wmem_max\n  #read_buffer: 10485760\n  #write_buffer: 10485760\n  # By default, Nebula replies to packets it has no tunnel for with a \"recv_error\" packet. This packet helps speed up reconnection\n  # in the case that Nebula on either side did not shut down cleanly. This response can be abused as a way to discover if Nebula is running\n  # on a host though. This option lets you configure if you want to send \"recv_error\" packets always, never, or only to private network remotes.\n  # valid values: always, never, private\n  # This setting is reloadable.\n  #send_recv_error: always\n  # Similar to send_recv_error, this option lets you configure if you want to accept \"recv_error\" packets from remote hosts.\n  # valid values: always, never, private\n  # This setting is reloadable.\n  #accept_recv_error: always\n  # The so_sock option is a Linux-specific feature that allows all outgoing Nebula packets to be tagged with a specific identifier.\n  # This tagging enables IP rule-based filtering. For example, it supports 0.0.0.0/0 unsafe_routes,\n  # allowing for more precise routing decisions based on the packet tags. Default is 0 meaning no mark is set.\n  # This setting is reloadable.\n  #so_mark: 0\n\n# Routines is the number of thread pairs to run that consume from the tun and UDP queues.\n# Currently, this defaults to 1 which means we have 1 tun queue reader and 1\n# UDP queue reader. Setting this above one will set IFF_MULTI_QUEUE on the tun\n# device and SO_REUSEPORT on the UDP socket to allow multiple queues.\n# This option is only supported on Linux.\n#routines: 1\n\npunchy:\n  # Continues to punch inbound/outbound at a regular interval to avoid expiration of firewall nat mappings\n  punch: true\n\n  # respond means that a node you are trying to reach will connect back out to you if your hole punching fails\n  # this is extremely useful if one node is behind a difficult nat, such as a symmetric NAT\n  # Default is false\n  #respond: true\n\n  # delays a punch response for misbehaving NATs, default is 1 second.\n  #delay: 1s\n\n  # set the delay before attempting punchy.respond. Default is 5 seconds. respond must be true to take effect.\n  #respond_delay: 5s\n\n# Cipher allows you to choose between the available ciphers for your network. Options are chachapoly or aes\n# IMPORTANT: this value must be identical on ALL NODES/LIGHTHOUSES. We do not/will not support use of different ciphers simultaneously!\n#cipher: aes\n\n# Preferred ranges is used to define a hint about the local network ranges, which speeds up discovering the fastest\n# path to a network adjacent nebula node.\n# This setting is reloadable.\n#preferred_ranges: [\"172.16.0.0/24\"]\n\n# sshd can expose informational and administrative functions via ssh. This can expose informational and administrative\n# functions, and allows manual tweaking of various network settings when debugging or testing.\n#sshd:\n  # Toggles the feature\n  #enabled: true\n  # Host and port to listen on, port 22 is not allowed for your safety\n  #listen: 127.0.0.1:2222\n  # A file containing the ssh host private key to use\n  # A decent way to generate one: ssh-keygen -t ed25519 -f ssh_host_ed25519_key -N \"\" < /dev/null\n  #host_key: ./ssh_host_ed25519_key\n  # Authorized users and their public keys\n  #authorized_users:\n    #- user: steeeeve\n      # keys can be an array of strings or single string\n      #keys:\n        #- \"ssh public key string\"\n  # Trusted SSH CA public keys. These are the public keys of the CAs that are allowed to sign SSH keys for access.\n  #trusted_cas:\n    #- \"ssh public key string\"\n\n# EXPERIMENTAL: relay support for networks that can't establish direct connections.\nrelay:\n  # Relays are a list of Nebula IP's that peers can use to relay packets to me.\n  # IPs in this list must have am_relay set to true in their configs, otherwise\n  # they will reject relay requests.\n  #relays:\n    #- 192.168.100.1\n    #- <other Nebula VPN IPs of hosts used as relays to access me>\n  # Set am_relay to true to permit other hosts to list my IP in their relays config. Default false.\n  am_relay: false\n  # Set use_relays to false to prevent this instance from attempting to establish connections through relays.\n  # default true\n  use_relays: true\n\n# Configure the private interface. Note: addr is baked into the nebula certificate\ntun:\n  # When tun is disabled, a lighthouse can be started without a local tun interface (and therefore without root)\n  disabled: false\n  # Name of the device. If not set, a default will be chosen by the OS.\n  # For macOS: if set, must be in the form `utun[0-9]+`.\n  # For NetBSD: Required to be set, must be in the form `tun[0-9]+`\n  dev: nebula1\n  # Toggles forwarding of local broadcast packets, the address of which depends on the ip/mask encoded in pki.cert\n  drop_local_broadcast: false\n  # Toggles forwarding of multicast packets\n  drop_multicast: false\n  # Sets the transmit queue length, if you notice lots of transmit drops on the tun it may help to raise this number. Default is 500\n  tx_queue: 500\n  # Default MTU for every packet, safe setting is (and the default) 1300 for internet based traffic\n  mtu: 1300\n\n  # Route based MTU overrides, you have known vpn ip paths that can support larger MTUs you can increase/decrease them here\n  routes:\n    #- mtu: 8800\n    #  route: 10.0.0.0/16\n\n  # Unsafe routes allows you to route traffic over nebula to non-nebula nodes\n  # Unsafe routes should be avoided unless you have hosts/services that cannot run nebula\n  # Supports weighted ECMP if you define a list of gateways, this can be used for load balancing or redundancy to hosts outside of nebula\n  # NOTES:\n  # * You will only see a single gateway in the routing table if you are not on linux\n  # * If a gateway is not reachable through the overlay another gateway will be selected to send the traffic through, ignoring weights\n  #\n  # unsafe_routes:\n  # # Multiple gateways without defining a weight defaults to a weight of 1, this will balance traffic equally between the three gateways\n  # - route: 192.168.87.0/24\n  #   via:\n  #     - gateway: 10.0.0.1\n  #     - gateway: 10.0.0.2\n  #     - gateway: 10.0.0.3\n  # # Multiple gateways with a weight, this will balance traffic accordingly\n  # - route: 192.168.87.0/24\n  #   via:\n  #     - gateway: 10.0.0.1\n  #       weight: 10\n  #     - gateway: 10.0.0.2\n  #       weight: 5\n  #\n  # NOTE: The nebula certificate of the \"via\" node(s) *MUST* have the \"route\" defined as a subnet in its certificate\n  # `via`: single node or list of gateways to use for this route\n  # `mtu`: will default to tun mtu if this option is not specified\n  # `metric`: will default to 0 if this option is not specified\n  # `install`: will default to true, controls whether this route is installed in the systems routing table.\n  # This setting is reloadable.\n  unsafe_routes:\n    #- route: 172.16.1.0/24\n    #  via: 192.168.100.99\n    #  mtu: 1300\n    #  metric: 100\n    #  install: true\n\n  # On linux only, set to true to manage unsafe routes directly on the system route table with gateway routes instead of\n  # in nebula configuration files. Default false, not reloadable.\n  #use_system_route_table: false\n  # Buffer size for reading routes updates. 0 means default system buffer size. (/proc/sys/net/core/rmem_default).\n  # If using massive routes updates, for example BGP, you may need to increase this value to avoid packet loss.\n  # SO_RCVBUFFORCE is used to avoid having to raise the system wide max\n  #use_system_route_table_buffer_size: 0\n\n# Configure logging level\nlogging:\n  # panic, fatal, error, warning, info, or debug. Default is info and is reloadable.\n  #NOTE: Debug mode can log remotely controlled/untrusted data which can quickly fill a disk in some\n  # scenarios. Debug logging is also CPU intensive and will decrease performance overall.\n  # Only enable debug logging while actively investigating an issue.\n  level: info\n  # json or text formats currently available. Default is text\n  format: text\n  # Disable timestamp logging. useful when output is redirected to logging system that already adds timestamps. Default is false\n  #disable_timestamp: true\n  # timestamp format is specified in Go time format, see:\n  #     https://golang.org/pkg/time/#pkg-constants\n  # default when `format: json`: \"2006-01-02T15:04:05Z07:00\" (RFC3339)\n  # default when `format: text`:\n  #     when TTY attached: seconds since beginning of execution\n  #     otherwise: \"2006-01-02T15:04:05Z07:00\" (RFC3339)\n  # As an example, to log as RFC3339 with millisecond precision, set to:\n  #timestamp_format: \"2006-01-02T15:04:05.000Z07:00\"\n\n#stats:\n  #type: graphite\n  #prefix: nebula\n  #protocol: tcp\n  #host: 127.0.0.1:9999\n  #interval: 10s\n\n  #type: prometheus\n  #listen: 127.0.0.1:8080\n  #path: /metrics\n  #namespace: prometheusns\n  #subsystem: nebula\n  #interval: 10s\n\n  # enables counter metrics for meta packets\n  #   e.g.: `messages.tx.handshake`\n  # NOTE: `message.{tx,rx}.recv_error` is always emitted\n  #message_metrics: false\n\n  # enables detailed counter metrics for lighthouse packets\n  #   e.g.: `lighthouse.rx.HostQuery`\n  #lighthouse_metrics: false\n\n# Handshake Manager Settings\n#handshakes:\n  # Handshakes are sent to all known addresses at each interval with a linear backoff,\n  # Wait try_interval after the 1st attempt, 2 * try_interval after the 2nd, etc, until the handshake is older than timeout\n  # A 100ms interval with the default 10 retries will give a handshake 5.5 seconds to resolve before timing out\n  #try_interval: 100ms\n  #retries: 20\n\n  # query_buffer is the size of the buffer channel for querying lighthouses\n  #query_buffer: 64\n\n  # trigger_buffer is the size of the buffer channel for quickly sending handshakes\n  # after receiving the response for lighthouse queries\n  #trigger_buffer: 64\n\n# Tunnel manager settings\n#tunnels:\n  # drop_inactive controls whether inactive tunnels are maintained or dropped after the inactive_timeout period has\n  # elapsed.\n  # In general, it is a good idea to enable this setting. It will be enabled by default in a future release.\n  # This setting is reloadable\n  #drop_inactive: false\n\n  # inactivity_timeout controls how long a tunnel MUST NOT see any inbound or outbound traffic before being considered\n  # inactive and eligible to be dropped.\n  # This setting is reloadable\n  #inactivity_timeout: 10m\n\n# Nebula security group configuration\nfirewall:\n  # Action to take when a packet is not allowed by the firewall rules.\n  # Can be one of:\n  #   `drop` (default): silently drop the packet.\n  #   `reject`: send a reject reply.\n  #     - For TCP, this will be a RST \"Connection Reset\" packet.\n  #     - For other protocols, this will be an ICMP port unreachable packet.\n  outbound_action: drop\n  inbound_action: drop\n\n  # THIS FLAG IS DEPRECATED AND WILL BE REMOVED IN A FUTURE RELEASE. (Defaults to false.)\n  # This setting only affects nebula hosts exposing unsafe_routes. When set to false, each inbound rule must contain a\n  # `local_cidr` if the intention is to allow traffic to flow to an unsafe route. When set to true, every firewall rule\n  # will apply to all configured unsafe_routes regardless of the actual destination of the packet, unless `local_cidr`\n  # is explicitly defined. This is usually not the desired behavior and should be avoided!\n  #default_local_cidr_any: false\n\n  conntrack:\n    tcp_timeout: 12m\n    udp_timeout: 3m\n    default_timeout: 10m\n\n  # The firewall is default deny. There is no way to write a deny rule.\n  # Rules are comprised of a protocol, port, and one or more of host, group, or CIDR\n  # Logical evaluation is roughly: port AND proto AND (ca_sha OR ca_name) AND (host OR group OR groups OR cidr) AND (local cidr)\n  # - port: Takes `0` or `any` as any, a single number `80`, a range `200-901`, or `fragment` to match second and further fragments of fragmented packets (since there is no port available).\n  #   proto: `any`, `tcp`, `udp`, or `icmp`\n  #          a port specification is ignored if proto is `icmp`\n  #   host: `any` or a literal hostname, ie `test-host`\n  #   group: `any` or a literal group name, ie `default-group`\n  #   groups: Same as group but accepts a list of values. Multiple values are AND'd together and a certificate would have to contain all groups to pass\n  #   cidr: a remote CIDR, `0.0.0.0/0` is any ipv4 and `::/0` is any ipv6. `any` means any ip family and address.\n  #   local_cidr: a local CIDR, `0.0.0.0/0` is any ipv4 and `::/0` is any ipv6. `any` means any ip family and address.\n  #     This can be used to filter destinations when using unsafe_routes.\n  #     By default, this is set to only the VPN (overlay) networks assigned via the certificate networks field unless `default_local_cidr_any` is set to true.\n  #     If there are unsafe_routes present in this config file, `local_cidr` should be set appropriately for the intended us case.\n  #   ca_name: An issuing CA name\n  #   ca_sha: An issuing CA shasum\n\n  outbound:\n    # Allow all outbound traffic from this node\n    - port: any\n      proto: any\n      host: any\n\n  inbound:\n    # Allow icmp between any nebula hosts\n    - port: any\n      proto: icmp\n      host: any\n\n    # Allow tcp/443 from any host with BOTH laptop and home group\n    - port: 443\n      proto: tcp\n      groups:\n        - laptop\n        - home\n\n    # Expose a subnet (unsafe route) to hosts with the group remote_client\n    # This example assume you have a subnet of 192.168.100.1/24 or larger encoded in the certificate\n    - port: 8080\n      proto: tcp\n      group: remote_client\n      local_cidr: 192.168.100.1/24\n"
  },
  {
    "path": "examples/go_service/main.go",
    "content": "package main\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"log\"\n\t\"net\"\n\t\"os\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula\"\n\t\"github.com/slackhq/nebula/config\"\n\t\"github.com/slackhq/nebula/overlay\"\n\t\"github.com/slackhq/nebula/service\"\n)\n\nfunc main() {\n\tif err := run(); err != nil {\n\t\tlog.Fatalf(\"%+v\", err)\n\t}\n}\n\nfunc run() error {\n\tconfigStr := `\ntun:\n  user: true\n\nstatic_host_map:\n  '192.168.100.1': ['localhost:4242']\n\nlisten:\n  host: 0.0.0.0\n  port: 4241\n\nlighthouse:\n  am_lighthouse: false\n  interval: 60\n  hosts:\n    - '192.168.100.1'\n\nfirewall:\n  outbound:\n    # Allow all outbound traffic from this node\n    - port: any\n      proto: any\n      host: any\n\n  inbound:\n    # Allow icmp between any nebula hosts\n    - port: any\n      proto: icmp\n      host: any\n    - port: any\n      proto: any\n      host: any\n\npki:\n  ca: /home/rice/Developer/nebula-config/ca.crt\n  cert: /home/rice/Developer/nebula-config/app.crt\n  key: /home/rice/Developer/nebula-config/app.key\n`\n\tvar cfg config.C\n\tif err := cfg.LoadString(configStr); err != nil {\n\t\treturn err\n\t}\n\n\tlogger := logrus.New()\n\tlogger.Out = os.Stdout\n\n\tctrl, err := nebula.Main(&cfg, false, \"custom-app\", logger, overlay.NewUserDeviceFromConfig)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tsvc, err := service.New(ctrl)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tln, err := svc.Listen(\"tcp\", \":1234\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor {\n\t\tconn, err := ln.Accept()\n\t\tif err != nil {\n\t\t\tlog.Printf(\"accept error: %s\", err)\n\t\t\tbreak\n\t\t}\n\t\tdefer func(conn net.Conn) {\n\t\t\t_ = conn.Close()\n\t\t}(conn)\n\n\t\tlog.Printf(\"got connection\")\n\n\t\t_, err = conn.Write([]byte(\"hello world\\n\"))\n\t\tif err != nil {\n\t\t\tlog.Printf(\"write error: %s\", err)\n\t\t}\n\n\t\tscanner := bufio.NewScanner(conn)\n\t\tfor scanner.Scan() {\n\t\t\tmessage := scanner.Text()\n\t\t\t_, err = fmt.Fprintf(conn, \"echo: %q\\n\", message)\n\t\t\tif err != nil {\n\t\t\t\tlog.Printf(\"write error: %s\", err)\n\t\t\t}\n\t\t\tlog.Printf(\"got message %q\", message)\n\t\t}\n\n\t\tif err := scanner.Err(); err != nil {\n\t\t\tlog.Printf(\"scanner error: %s\", err)\n\t\t\tbreak\n\t\t}\n\t}\n\n\t_ = svc.Close()\n\tif err := svc.Wait(); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "examples/service_scripts/nebula.init.d.sh",
    "content": "#!/bin/sh\n### BEGIN INIT INFO\n# Provides:          nebula\n# Required-Start:    $local_fs $network\n# Required-Stop:     $local_fs $network\n# Default-Start:     2 3 4 5\n# Default-Stop:      0 1 6\n# Description:       nebula mesh vpn client\n### END INIT INFO\n\nSCRIPT=\"/usr/local/bin/nebula -config /etc/nebula/config.yml\"\nRUNAS=root\n\nPIDFILE=/var/run/nebula.pid\nLOGFILE=/var/log/nebula.log\n\nstart() {\n  if [ -f $PIDFILE ] && kill -0 $(cat $PIDFILE); then\n    echo 'Service already running' >&2\n    return 1\n  fi\n  echo 'Starting nebula service…' >&2\n  local CMD=\"$SCRIPT &> \\\"$LOGFILE\\\" & echo \\$!\"\n  su -c \"$CMD\" $RUNAS  > \"$PIDFILE\"\n  echo 'Service started' >&2\n}\n\nstop() {\n  if [ ! -f \"$PIDFILE\" ] || ! kill -0 $(cat \"$PIDFILE\"); then\n    echo 'Service not running' >&2\n    return 1\n  fi\n  echo 'Stopping nebula service…' >&2\n  kill -15 $(cat \"$PIDFILE\") && rm -f \"$PIDFILE\"\n  echo 'Service stopped' >&2\n}\n\ncase \"$1\" in\n  start)\n    start\n    ;;\n  stop)\n    stop\n    ;;\n  restart)\n    stop\n    start\n    ;;\n  *)\n    echo \"Usage: $0 {start|stop|restart}\"\nesac\n"
  },
  {
    "path": "examples/service_scripts/nebula.open-rc",
    "content": "#!/sbin/openrc-run\n#\n# nebula service for open-rc systems\n\nextra_commands=\"checkconfig\"\n\n: ${NEBULA_CONFDIR:=${RC_PREFIX%/}/etc/nebula}\n: ${NEBULA_CONFIG:=${NEBULA_CONFDIR}/config.yml}\n: ${NEBULA_BINARY:=${NEBULA_BINARY}${RC_PREFIX%/}/usr/local/sbin/nebula}\n\ncommand=\"${NEBULA_BINARY}\"\ncommand_args=\"${NEBULA_OPTS} -config ${NEBULA_CONFIG}\"\n\nsupervisor=\"supervise-daemon\"\n\ndescription=\"A scalable overlay networking tool with a focus on performance, simplicity and security\"\n\nrequired_dirs=\"${NEBULA_CONFDIR}\"\nrequired_files=\"${NEBULA_CONFIG}\"\n\ncheckconfig() {\n        \"${command}\" -test ${command_args} || return 1\n}\n\nstart_pre() {\n        if [ \"${RC_CMD}\" != \"restart\" ] ; then\n                checkconfig || return $?\n        fi\n}\n\nstop_pre() {\n        if [ \"${RC_CMD}\" = \"restart\" ] ; then\n                checkconfig || return $?\n        fi\n}\n"
  },
  {
    "path": "examples/service_scripts/nebula.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n    <dict>\n        <key>KeepAlive</key>\n        <true/>\n        <key>Label</key>\n        <string>net.defined.nebula</string>\n        <key>WorkingDirectory</key>\n        <string>/Users/{username}/.local/bin/nebula</string>\n        <key>LimitLoadToSessionType</key>\n        <array>\n            <string>Aqua</string>\n            <string>Background</string>\n            <string>LoginWindow</string>\n            <string>StandardIO</string>\n            <string>System</string>\n        </array>\n        <key>ProgramArguments</key>\n        <array>\n            <string>./nebula</string>\n            <string>-config</string>\n            <string>./config.yml</string>\n        </array>\n        <key>RunAtLoad</key>\n        <true/>\n        <key>StandardErrorPath</key>\n        <string>./nebula.log</string>\n        <key>StandardOutPath</key>\n        <string>./nebula.log</string>\n        <key>UserName</key>\n        <string>root</string>\n    </dict>\n</plist>"
  },
  {
    "path": "examples/service_scripts/nebula.service",
    "content": "[Unit]\nDescription=Nebula overlay networking tool\nWants=basic.target network-online.target nss-lookup.target time-sync.target\nAfter=basic.target network.target network-online.target\nBefore=sshd.service\n\n[Service]\nType=notify\nNotifyAccess=main\nSyslogIdentifier=nebula\nExecReload=/bin/kill -HUP $MAINPID\nExecStart=/usr/local/bin/nebula -config /etc/nebula/config.yml\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "firewall/cache.go",
    "content": "package firewall\n\nimport (\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/sirupsen/logrus\"\n)\n\n// ConntrackCache is used as a local routine cache to know if a given flow\n// has been seen in the conntrack table.\ntype ConntrackCache map[Packet]struct{}\n\ntype ConntrackCacheTicker struct {\n\tcacheV    uint64\n\tcacheTick atomic.Uint64\n\n\tcache ConntrackCache\n}\n\nfunc NewConntrackCacheTicker(d time.Duration) *ConntrackCacheTicker {\n\tif d == 0 {\n\t\treturn nil\n\t}\n\n\tc := &ConntrackCacheTicker{\n\t\tcache: ConntrackCache{},\n\t}\n\n\tgo c.tick(d)\n\n\treturn c\n}\n\nfunc (c *ConntrackCacheTicker) tick(d time.Duration) {\n\tfor {\n\t\ttime.Sleep(d)\n\t\tc.cacheTick.Add(1)\n\t}\n}\n\n// Get checks if the cache ticker has moved to the next version before returning\n// the map. If it has moved, we reset the map.\nfunc (c *ConntrackCacheTicker) Get(l *logrus.Logger) ConntrackCache {\n\tif c == nil {\n\t\treturn nil\n\t}\n\tif tick := c.cacheTick.Load(); tick != c.cacheV {\n\t\tc.cacheV = tick\n\t\tif ll := len(c.cache); ll > 0 {\n\t\t\tif l.Level == logrus.DebugLevel {\n\t\t\t\tl.WithField(\"len\", ll).Debug(\"resetting conntrack cache\")\n\t\t\t}\n\t\t\tc.cache = make(ConntrackCache, ll)\n\t\t}\n\t}\n\n\treturn c.cache\n}\n"
  },
  {
    "path": "firewall/packet.go",
    "content": "package firewall\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/netip\"\n)\n\ntype m = map[string]any\n\nconst (\n\tProtoAny    = 0 // When we want to handle HOPOPT (0) we can change this, if ever\n\tProtoTCP    = 6\n\tProtoUDP    = 17\n\tProtoICMP   = 1\n\tProtoICMPv6 = 58\n\n\tPortAny      = 0  // Special value for matching `port: any`\n\tPortFragment = -1 // Special value for matching `port: fragment`\n)\n\ntype Packet struct {\n\tLocalAddr  netip.Addr\n\tRemoteAddr netip.Addr\n\t// LocalPort is the destination port for incoming traffic, or the source port for outgoing. Zero for ICMP.\n\tLocalPort uint16\n\t// RemotePort is the source port for incoming traffic, or the destination port for outgoing.\n\t// For ICMP, it's the \"identifier\". This is only used for connection tracking, actual firewall rules will not filter on ICMP identifier\n\tRemotePort uint16\n\tProtocol   uint8\n\tFragment   bool\n}\n\nfunc (fp *Packet) Copy() *Packet {\n\treturn &Packet{\n\t\tLocalAddr:  fp.LocalAddr,\n\t\tRemoteAddr: fp.RemoteAddr,\n\t\tLocalPort:  fp.LocalPort,\n\t\tRemotePort: fp.RemotePort,\n\t\tProtocol:   fp.Protocol,\n\t\tFragment:   fp.Fragment,\n\t}\n}\n\nfunc (fp Packet) MarshalJSON() ([]byte, error) {\n\tvar proto string\n\tswitch fp.Protocol {\n\tcase ProtoTCP:\n\t\tproto = \"tcp\"\n\tcase ProtoICMP:\n\t\tproto = \"icmp\"\n\tcase ProtoICMPv6:\n\t\tproto = \"icmpv6\"\n\tcase ProtoUDP:\n\t\tproto = \"udp\"\n\tdefault:\n\t\tproto = fmt.Sprintf(\"unknown %v\", fp.Protocol)\n\t}\n\treturn json.Marshal(m{\n\t\t\"LocalAddr\":  fp.LocalAddr.String(),\n\t\t\"RemoteAddr\": fp.RemoteAddr.String(),\n\t\t\"LocalPort\":  fp.LocalPort,\n\t\t\"RemotePort\": fp.RemotePort,\n\t\t\"Protocol\":   proto,\n\t\t\"Fragment\":   fp.Fragment,\n\t})\n}\n"
  },
  {
    "path": "firewall.go",
    "content": "package nebula\n\nimport (\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"fmt\"\n\t\"hash/fnv\"\n\t\"net/netip\"\n\t\"reflect\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/gaissmai/bart\"\n\t\"github.com/rcrowley/go-metrics\"\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/cert\"\n\t\"github.com/slackhq/nebula/config\"\n\t\"github.com/slackhq/nebula/firewall\"\n)\n\ntype FirewallInterface interface {\n\tAddRule(incoming bool, proto uint8, startPort int32, endPort int32, groups []string, host string, cidr, localCidr string, caName string, caSha string) error\n}\n\ntype conn struct {\n\tExpires time.Time // Time when this conntrack entry will expire\n\n\t// record why the original connection passed the firewall, so we can re-validate\n\t// after ruleset changes. Note, rulesVersion is a uint16 so that these two\n\t// fields pack for free after the uint32 above\n\tincoming     bool\n\trulesVersion uint16\n}\n\n// TODO: need conntrack max tracked connections handling\ntype Firewall struct {\n\tConntrack *FirewallConntrack\n\n\tInRules  *FirewallTable\n\tOutRules *FirewallTable\n\n\tInSendReject  bool\n\tOutSendReject bool\n\n\t//TODO: we should have many more options for TCP, an option for ICMP, and mimic the kernel a bit better\n\t// https://www.kernel.org/doc/Documentation/networking/nf_conntrack-sysctl.txt\n\tTCPTimeout     time.Duration //linux: 5 days max\n\tUDPTimeout     time.Duration //linux: 180s max\n\tDefaultTimeout time.Duration //linux: 600s\n\n\t// routableNetworks describes the vpn addresses as well as any unsafe networks issued to us in the certificate.\n\t// The vpn addresses are a full bit match while the unsafe networks only match the prefix\n\troutableNetworks *bart.Lite\n\n\t// assignedNetworks is a list of vpn networks assigned to us in the certificate.\n\tassignedNetworks  []netip.Prefix\n\thasUnsafeNetworks bool\n\n\trules        string\n\trulesVersion uint16\n\n\tdefaultLocalCIDRAny bool\n\tincomingMetrics     firewallMetrics\n\toutgoingMetrics     firewallMetrics\n\n\tl *logrus.Logger\n}\n\ntype firewallMetrics struct {\n\tdroppedLocalAddr  metrics.Counter\n\tdroppedRemoteAddr metrics.Counter\n\tdroppedNoRule     metrics.Counter\n}\n\ntype FirewallConntrack struct {\n\tsync.Mutex\n\n\tConns      map[firewall.Packet]*conn\n\tTimerWheel *TimerWheel[firewall.Packet]\n}\n\n// FirewallTable is the entry point for a rule, the evaluation order is:\n// Proto AND port AND (CA SHA or CA name) AND local CIDR AND (group OR groups OR name OR remote CIDR)\ntype FirewallTable struct {\n\tTCP      firewallPort\n\tUDP      firewallPort\n\tICMP     firewallPort\n\tAnyProto firewallPort\n}\n\nfunc newFirewallTable() *FirewallTable {\n\treturn &FirewallTable{\n\t\tTCP:      firewallPort{},\n\t\tUDP:      firewallPort{},\n\t\tICMP:     firewallPort{},\n\t\tAnyProto: firewallPort{},\n\t}\n}\n\ntype FirewallCA struct {\n\tAny     *FirewallRule\n\tCANames map[string]*FirewallRule\n\tCAShas  map[string]*FirewallRule\n}\n\ntype FirewallRule struct {\n\t// Any makes Hosts, Groups, and CIDR irrelevant\n\tAny    *firewallLocalCIDR\n\tHosts  map[string]*firewallLocalCIDR\n\tGroups []*firewallGroups\n\tCIDR   *bart.Table[*firewallLocalCIDR]\n}\n\ntype firewallGroups struct {\n\tGroups    []string\n\tLocalCIDR *firewallLocalCIDR\n}\n\n// Even though ports are uint16, int32 maps are faster for lookup\n// Plus we can use `-1` for fragment rules\ntype firewallPort map[int32]*FirewallCA\n\ntype firewallLocalCIDR struct {\n\tAny       bool\n\tLocalCIDR *bart.Lite\n}\n\n// NewFirewall creates a new Firewall object. A TimerWheel is created for you from the provided timeouts.\n// The certificate provided should be the highest version loaded in memory.\nfunc NewFirewall(l *logrus.Logger, tcpTimeout, UDPTimeout, defaultTimeout time.Duration, c cert.Certificate) *Firewall {\n\t//TODO: error on 0 duration\n\tvar tmin, tmax time.Duration\n\n\tif tcpTimeout < UDPTimeout {\n\t\ttmin = tcpTimeout\n\t\ttmax = UDPTimeout\n\t} else {\n\t\ttmin = UDPTimeout\n\t\ttmax = tcpTimeout\n\t}\n\n\tif defaultTimeout < tmin {\n\t\ttmin = defaultTimeout\n\t} else if defaultTimeout > tmax {\n\t\ttmax = defaultTimeout\n\t}\n\n\troutableNetworks := new(bart.Lite)\n\tvar assignedNetworks []netip.Prefix\n\tfor _, network := range c.Networks() {\n\t\tnprefix := netip.PrefixFrom(network.Addr(), network.Addr().BitLen())\n\t\troutableNetworks.Insert(nprefix)\n\t\tassignedNetworks = append(assignedNetworks, network)\n\t}\n\n\thasUnsafeNetworks := false\n\tfor _, n := range c.UnsafeNetworks() {\n\t\troutableNetworks.Insert(n)\n\t\thasUnsafeNetworks = true\n\t}\n\n\treturn &Firewall{\n\t\tConntrack: &FirewallConntrack{\n\t\t\tConns:      make(map[firewall.Packet]*conn),\n\t\t\tTimerWheel: NewTimerWheel[firewall.Packet](tmin, tmax),\n\t\t},\n\t\tInRules:           newFirewallTable(),\n\t\tOutRules:          newFirewallTable(),\n\t\tTCPTimeout:        tcpTimeout,\n\t\tUDPTimeout:        UDPTimeout,\n\t\tDefaultTimeout:    defaultTimeout,\n\t\troutableNetworks:  routableNetworks,\n\t\tassignedNetworks:  assignedNetworks,\n\t\thasUnsafeNetworks: hasUnsafeNetworks,\n\t\tl:                 l,\n\n\t\tincomingMetrics: firewallMetrics{\n\t\t\tdroppedLocalAddr:  metrics.GetOrRegisterCounter(\"firewall.incoming.dropped.local_addr\", nil),\n\t\t\tdroppedRemoteAddr: metrics.GetOrRegisterCounter(\"firewall.incoming.dropped.remote_addr\", nil),\n\t\t\tdroppedNoRule:     metrics.GetOrRegisterCounter(\"firewall.incoming.dropped.no_rule\", nil),\n\t\t},\n\t\toutgoingMetrics: firewallMetrics{\n\t\t\tdroppedLocalAddr:  metrics.GetOrRegisterCounter(\"firewall.outgoing.dropped.local_addr\", nil),\n\t\t\tdroppedRemoteAddr: metrics.GetOrRegisterCounter(\"firewall.outgoing.dropped.remote_addr\", nil),\n\t\t\tdroppedNoRule:     metrics.GetOrRegisterCounter(\"firewall.outgoing.dropped.no_rule\", nil),\n\t\t},\n\t}\n}\n\nfunc NewFirewallFromConfig(l *logrus.Logger, cs *CertState, c *config.C) (*Firewall, error) {\n\tcertificate := cs.getCertificate(cert.Version2)\n\tif certificate == nil {\n\t\tcertificate = cs.getCertificate(cert.Version1)\n\t}\n\n\tif certificate == nil {\n\t\tpanic(\"No certificate available to reconfigure the firewall\")\n\t}\n\n\tfw := NewFirewall(\n\t\tl,\n\t\tc.GetDuration(\"firewall.conntrack.tcp_timeout\", time.Minute*12),\n\t\tc.GetDuration(\"firewall.conntrack.udp_timeout\", time.Minute*3),\n\t\tc.GetDuration(\"firewall.conntrack.default_timeout\", time.Minute*10),\n\t\tcertificate,\n\t\t//TODO: max_connections\n\t)\n\n\tfw.defaultLocalCIDRAny = c.GetBool(\"firewall.default_local_cidr_any\", false)\n\n\tinboundAction := c.GetString(\"firewall.inbound_action\", \"drop\")\n\tswitch inboundAction {\n\tcase \"reject\":\n\t\tfw.InSendReject = true\n\tcase \"drop\":\n\t\tfw.InSendReject = false\n\tdefault:\n\t\tl.WithField(\"action\", inboundAction).Warn(\"invalid firewall.inbound_action, defaulting to `drop`\")\n\t\tfw.InSendReject = false\n\t}\n\n\toutboundAction := c.GetString(\"firewall.outbound_action\", \"drop\")\n\tswitch outboundAction {\n\tcase \"reject\":\n\t\tfw.OutSendReject = true\n\tcase \"drop\":\n\t\tfw.OutSendReject = false\n\tdefault:\n\t\tl.WithField(\"action\", outboundAction).Warn(\"invalid firewall.outbound_action, defaulting to `drop`\")\n\t\tfw.OutSendReject = false\n\t}\n\n\terr := AddFirewallRulesFromConfig(l, false, c, fw)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = AddFirewallRulesFromConfig(l, true, c, fw)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn fw, nil\n}\n\n// AddRule properly creates the in memory rule structure for a firewall table.\nfunc (f *Firewall) AddRule(incoming bool, proto uint8, startPort int32, endPort int32, groups []string, host string, cidr, localCidr, caName string, caSha string) error {\n\tvar (\n\t\tft *FirewallTable\n\t\tfp firewallPort\n\t)\n\n\tif incoming {\n\t\tft = f.InRules\n\t} else {\n\t\tft = f.OutRules\n\t}\n\n\tswitch proto {\n\tcase firewall.ProtoTCP:\n\t\tfp = ft.TCP\n\tcase firewall.ProtoUDP:\n\t\tfp = ft.UDP\n\tcase firewall.ProtoICMP, firewall.ProtoICMPv6:\n\t\t//ICMP traffic doesn't have ports, so we always coerce to \"any\", even if a value is provided\n\t\tif startPort != firewall.PortAny {\n\t\t\tf.l.WithField(\"startPort\", startPort).Warn(\"ignoring port specification for ICMP firewall rule\")\n\t\t}\n\t\tstartPort = firewall.PortAny\n\t\tendPort = firewall.PortAny\n\t\tfp = ft.ICMP\n\tcase firewall.ProtoAny:\n\t\tfp = ft.AnyProto\n\tdefault:\n\t\treturn fmt.Errorf(\"unknown protocol %v\", proto)\n\t}\n\n\t// We need this rule string because we generate a hash. Removing this will break firewall reload.\n\truleString := fmt.Sprintf(\n\t\t\"incoming: %v, proto: %v, startPort: %v, endPort: %v, groups: %v, host: %v, ip: %v, localIp: %v, caName: %v, caSha: %s\",\n\t\tincoming, proto, startPort, endPort, groups, host, cidr, localCidr, caName, caSha,\n\t)\n\tf.rules += ruleString + \"\\n\"\n\n\tdirection := \"incoming\"\n\tif !incoming {\n\t\tdirection = \"outgoing\"\n\t}\n\tf.l.WithField(\"firewallRule\", m{\"direction\": direction, \"proto\": proto, \"startPort\": startPort, \"endPort\": endPort, \"groups\": groups, \"host\": host, \"cidr\": cidr, \"localCidr\": localCidr, \"caName\": caName, \"caSha\": caSha}).\n\t\tInfo(\"Firewall rule added\")\n\n\treturn fp.addRule(f, startPort, endPort, groups, host, cidr, localCidr, caName, caSha)\n}\n\n// GetRuleHash returns a hash representation of all inbound and outbound rules\nfunc (f *Firewall) GetRuleHash() string {\n\tsum := sha256.Sum256([]byte(f.rules))\n\treturn hex.EncodeToString(sum[:])\n}\n\n// GetRuleHashFNV returns a uint32 FNV-1 hash representation the rules, for use as a metric value\nfunc (f *Firewall) GetRuleHashFNV() uint32 {\n\th := fnv.New32a()\n\th.Write([]byte(f.rules))\n\treturn h.Sum32()\n}\n\n// GetRuleHashes returns both the sha256 and FNV-1 hashes, suitable for logging\nfunc (f *Firewall) GetRuleHashes() string {\n\treturn \"SHA:\" + f.GetRuleHash() + \",FNV:\" + strconv.FormatUint(uint64(f.GetRuleHashFNV()), 10)\n}\n\nfunc AddFirewallRulesFromConfig(l *logrus.Logger, inbound bool, c *config.C, fw FirewallInterface) error {\n\tvar table string\n\tif inbound {\n\t\ttable = \"firewall.inbound\"\n\t} else {\n\t\ttable = \"firewall.outbound\"\n\t}\n\n\tr := c.Get(table)\n\tif r == nil {\n\t\treturn nil\n\t}\n\n\trs, ok := r.([]any)\n\tif !ok {\n\t\treturn fmt.Errorf(\"%s failed to parse, should be an array of rules\", table)\n\t}\n\n\tfor i, t := range rs {\n\t\tr, err := convertRule(l, t, table, i)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"%s rule #%v; %s\", table, i, err)\n\t\t}\n\n\t\tif r.Code != \"\" && r.Port != \"\" {\n\t\t\treturn fmt.Errorf(\"%s rule #%v; only one of port or code should be provided\", table, i)\n\t\t}\n\n\t\tif r.Host == \"\" && len(r.Groups) == 0 && r.Cidr == \"\" && r.LocalCidr == \"\" && r.CAName == \"\" && r.CASha == \"\" {\n\t\t\treturn fmt.Errorf(\"%s rule #%v; at least one of host, group, cidr, local_cidr, ca_name, or ca_sha must be provided\", table, i)\n\t\t}\n\n\t\tvar sPort, errPort string\n\t\tif r.Code != \"\" {\n\t\t\terrPort = \"code\"\n\t\t\tsPort = r.Code\n\t\t} else {\n\t\t\terrPort = \"port\"\n\t\t\tsPort = r.Port\n\t\t}\n\n\t\tvar proto uint8\n\t\tvar startPort, endPort int32\n\t\tswitch r.Proto {\n\t\tcase \"any\":\n\t\t\tproto = firewall.ProtoAny\n\t\t\tstartPort, endPort, err = parsePort(sPort)\n\t\tcase \"tcp\":\n\t\t\tproto = firewall.ProtoTCP\n\t\t\tstartPort, endPort, err = parsePort(sPort)\n\t\tcase \"udp\":\n\t\t\tproto = firewall.ProtoUDP\n\t\t\tstartPort, endPort, err = parsePort(sPort)\n\t\tcase \"icmp\":\n\t\t\tproto = firewall.ProtoICMP\n\t\t\tstartPort = firewall.PortAny\n\t\t\tendPort = firewall.PortAny\n\t\t\tif sPort != \"\" {\n\t\t\t\tl.WithField(\"port\", sPort).Warn(\"ignoring port specification for ICMP firewall rule\")\n\t\t\t}\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"%s rule #%v; proto was not understood; `%s`\", table, i, r.Proto)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"%s rule #%v; %s %s\", table, i, errPort, err)\n\t\t}\n\n\t\tif r.Cidr != \"\" && r.Cidr != \"any\" {\n\t\t\t_, err = netip.ParsePrefix(r.Cidr)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"%s rule #%v; cidr did not parse; %s\", table, i, err)\n\t\t\t}\n\t\t}\n\n\t\tif r.LocalCidr != \"\" && r.LocalCidr != \"any\" {\n\t\t\t_, err = netip.ParsePrefix(r.LocalCidr)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"%s rule #%v; local_cidr did not parse; %s\", table, i, err)\n\t\t\t}\n\t\t}\n\n\t\tif warning := r.sanity(); warning != nil {\n\t\t\tl.Warnf(\"%s rule #%v; %s\", table, i, warning)\n\t\t}\n\n\t\terr = fw.AddRule(inbound, proto, startPort, endPort, r.Groups, r.Host, r.Cidr, r.LocalCidr, r.CAName, r.CASha)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"%s rule #%v; `%s`\", table, i, err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nvar ErrUnknownNetworkType = errors.New(\"unknown network type\")\nvar ErrPeerRejected = errors.New(\"remote address is not within a network that we handle\")\nvar ErrInvalidRemoteIP = errors.New(\"remote address is not in remote certificate networks\")\nvar ErrInvalidLocalIP = errors.New(\"local address is not in list of handled local addresses\")\nvar ErrNoMatchingRule = errors.New(\"no matching rule in firewall table\")\n\n// Drop returns an error if the packet should be dropped, explaining why. It\n// returns nil if the packet should not be dropped.\nfunc (f *Firewall) Drop(fp firewall.Packet, incoming bool, h *HostInfo, caPool *cert.CAPool, localCache firewall.ConntrackCache) error {\n\t// Check if we spoke to this tuple, if we did then allow this packet\n\tif f.inConns(fp, h, caPool, localCache) {\n\t\treturn nil\n\t}\n\n\t// Make sure remote address matches nebula certificate, and determine how to treat it\n\tif h.networks == nil {\n\t\t// Simple case: Certificate has one address and no unsafe networks\n\t\tif h.vpnAddrs[0] != fp.RemoteAddr {\n\t\t\tf.metrics(incoming).droppedRemoteAddr.Inc(1)\n\t\t\treturn ErrInvalidRemoteIP\n\t\t}\n\t} else {\n\t\tnwType, ok := h.networks.Lookup(fp.RemoteAddr)\n\t\tif !ok {\n\t\t\tf.metrics(incoming).droppedRemoteAddr.Inc(1)\n\t\t\treturn ErrInvalidRemoteIP\n\t\t}\n\t\tswitch nwType {\n\t\tcase NetworkTypeVPN:\n\t\t\tbreak // nothing special\n\t\tcase NetworkTypeVPNPeer:\n\t\t\tf.metrics(incoming).droppedRemoteAddr.Inc(1)\n\t\t\treturn ErrPeerRejected // reject for now, one day this may have different FW rules\n\t\tcase NetworkTypeUnsafe:\n\t\t\tbreak // nothing special, one day this may have different FW rules\n\t\tdefault:\n\t\t\tf.metrics(incoming).droppedRemoteAddr.Inc(1)\n\t\t\treturn ErrUnknownNetworkType //should never happen\n\t\t}\n\t}\n\n\t// Make sure we are supposed to be handling this local ip address\n\tif !f.routableNetworks.Contains(fp.LocalAddr) {\n\t\tf.metrics(incoming).droppedLocalAddr.Inc(1)\n\t\treturn ErrInvalidLocalIP\n\t}\n\n\ttable := f.OutRules\n\tif incoming {\n\t\ttable = f.InRules\n\t}\n\n\t// We now know which firewall table to check against\n\tif !table.match(fp, incoming, h.ConnectionState.peerCert, caPool) {\n\t\tf.metrics(incoming).droppedNoRule.Inc(1)\n\t\treturn ErrNoMatchingRule\n\t}\n\n\t// We always want to conntrack since it is a faster operation\n\tf.addConn(fp, incoming)\n\n\treturn nil\n}\n\nfunc (f *Firewall) metrics(incoming bool) firewallMetrics {\n\tif incoming {\n\t\treturn f.incomingMetrics\n\t} else {\n\t\treturn f.outgoingMetrics\n\t}\n}\n\n// Destroy cleans up any known cyclical references so the object can be freed by GC. This should be called if a new\n// firewall object is created\nfunc (f *Firewall) Destroy() {\n\t//TODO: clean references if/when needed\n}\n\nfunc (f *Firewall) EmitStats() {\n\tconntrack := f.Conntrack\n\tconntrack.Lock()\n\tconntrackCount := len(conntrack.Conns)\n\tconntrack.Unlock()\n\tmetrics.GetOrRegisterGauge(\"firewall.conntrack.count\", nil).Update(int64(conntrackCount))\n\tmetrics.GetOrRegisterGauge(\"firewall.rules.version\", nil).Update(int64(f.rulesVersion))\n\tmetrics.GetOrRegisterGauge(\"firewall.rules.hash\", nil).Update(int64(f.GetRuleHashFNV()))\n}\n\nfunc (f *Firewall) inConns(fp firewall.Packet, h *HostInfo, caPool *cert.CAPool, localCache firewall.ConntrackCache) bool {\n\tif localCache != nil {\n\t\tif _, ok := localCache[fp]; ok {\n\t\t\treturn true\n\t\t}\n\t}\n\tconntrack := f.Conntrack\n\tconntrack.Lock()\n\n\t// Purge every time we test\n\tep, has := conntrack.TimerWheel.Purge()\n\tif has {\n\t\tf.evict(ep)\n\t}\n\n\tc, ok := conntrack.Conns[fp]\n\n\tif !ok {\n\t\tconntrack.Unlock()\n\t\treturn false\n\t}\n\n\tif c.rulesVersion != f.rulesVersion {\n\t\t// This conntrack entry was for an older rule set, validate\n\t\t// it still passes with the current rule set\n\t\ttable := f.OutRules\n\t\tif c.incoming {\n\t\t\ttable = f.InRules\n\t\t}\n\n\t\t// We now know which firewall table to check against\n\t\tif !table.match(fp, c.incoming, h.ConnectionState.peerCert, caPool) {\n\t\t\tif f.l.Level >= logrus.DebugLevel {\n\t\t\t\th.logger(f.l).\n\t\t\t\t\tWithField(\"fwPacket\", fp).\n\t\t\t\t\tWithField(\"incoming\", c.incoming).\n\t\t\t\t\tWithField(\"rulesVersion\", f.rulesVersion).\n\t\t\t\t\tWithField(\"oldRulesVersion\", c.rulesVersion).\n\t\t\t\t\tDebugln(\"dropping old conntrack entry, does not match new ruleset\")\n\t\t\t}\n\t\t\tdelete(conntrack.Conns, fp)\n\t\t\tconntrack.Unlock()\n\t\t\treturn false\n\t\t}\n\n\t\tif f.l.Level >= logrus.DebugLevel {\n\t\t\th.logger(f.l).\n\t\t\t\tWithField(\"fwPacket\", fp).\n\t\t\t\tWithField(\"incoming\", c.incoming).\n\t\t\t\tWithField(\"rulesVersion\", f.rulesVersion).\n\t\t\t\tWithField(\"oldRulesVersion\", c.rulesVersion).\n\t\t\t\tDebugln(\"keeping old conntrack entry, does match new ruleset\")\n\t\t}\n\n\t\tc.rulesVersion = f.rulesVersion\n\t}\n\n\tswitch fp.Protocol {\n\tcase firewall.ProtoTCP:\n\t\tc.Expires = time.Now().Add(f.TCPTimeout)\n\tcase firewall.ProtoUDP:\n\t\tc.Expires = time.Now().Add(f.UDPTimeout)\n\tdefault:\n\t\tc.Expires = time.Now().Add(f.DefaultTimeout)\n\t}\n\n\tconntrack.Unlock()\n\n\tif localCache != nil {\n\t\tlocalCache[fp] = struct{}{}\n\t}\n\n\treturn true\n}\n\nfunc (f *Firewall) addConn(fp firewall.Packet, incoming bool) {\n\tvar timeout time.Duration\n\tc := &conn{}\n\n\tswitch fp.Protocol {\n\tcase firewall.ProtoTCP:\n\t\ttimeout = f.TCPTimeout\n\tcase firewall.ProtoUDP:\n\t\ttimeout = f.UDPTimeout\n\tdefault:\n\t\ttimeout = f.DefaultTimeout\n\t}\n\n\tconntrack := f.Conntrack\n\tconntrack.Lock()\n\tif _, ok := conntrack.Conns[fp]; !ok {\n\t\tconntrack.TimerWheel.Advance(time.Now())\n\t\tconntrack.TimerWheel.Add(fp, timeout)\n\t}\n\n\t// Record which rulesVersion allowed this connection, so we can retest after\n\t// firewall reload\n\tc.incoming = incoming\n\tc.rulesVersion = f.rulesVersion\n\tc.Expires = time.Now().Add(timeout)\n\tconntrack.Conns[fp] = c\n\tconntrack.Unlock()\n}\n\n// Evict checks if a conntrack entry has expired, if so it is removed, if not it is re-added to the wheel\n// Caller must own the connMutex lock!\nfunc (f *Firewall) evict(p firewall.Packet) {\n\t// Are we still tracking this conn?\n\tconntrack := f.Conntrack\n\tt, ok := conntrack.Conns[p]\n\tif !ok {\n\t\treturn\n\t}\n\n\tnewT := t.Expires.Sub(time.Now())\n\n\t// Timeout is in the future, re-add the timer\n\tif newT > 0 {\n\t\tconntrack.TimerWheel.Advance(time.Now())\n\t\tconntrack.TimerWheel.Add(p, newT)\n\t\treturn\n\t}\n\n\t// This conn is done\n\tdelete(conntrack.Conns, p)\n}\n\nfunc (ft *FirewallTable) match(p firewall.Packet, incoming bool, c *cert.CachedCertificate, caPool *cert.CAPool) bool {\n\tif ft.AnyProto.match(p, incoming, c, caPool) {\n\t\treturn true\n\t}\n\n\tswitch p.Protocol {\n\tcase firewall.ProtoTCP:\n\t\tif ft.TCP.match(p, incoming, c, caPool) {\n\t\t\treturn true\n\t\t}\n\tcase firewall.ProtoUDP:\n\t\tif ft.UDP.match(p, incoming, c, caPool) {\n\t\t\treturn true\n\t\t}\n\tcase firewall.ProtoICMP, firewall.ProtoICMPv6:\n\t\tif ft.ICMP.match(p, incoming, c, caPool) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc (fp firewallPort) addRule(f *Firewall, startPort int32, endPort int32, groups []string, host string, cidr, localCidr, caName string, caSha string) error {\n\tif startPort > endPort {\n\t\treturn fmt.Errorf(\"start port was lower than end port\")\n\t}\n\n\tfor i := startPort; i <= endPort; i++ {\n\t\tif _, ok := fp[i]; !ok {\n\t\t\tfp[i] = &FirewallCA{\n\t\t\t\tCANames: make(map[string]*FirewallRule),\n\t\t\t\tCAShas:  make(map[string]*FirewallRule),\n\t\t\t}\n\t\t}\n\n\t\tif err := fp[i].addRule(f, groups, host, cidr, localCidr, caName, caSha); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (fp firewallPort) match(p firewall.Packet, incoming bool, c *cert.CachedCertificate, caPool *cert.CAPool) bool {\n\t// We don't have any allowed ports, bail\n\tif fp == nil {\n\t\treturn false\n\t}\n\n\t// this branch is here to catch traffic from FirewallTable.Any.match and FirewallTable.ICMP.match\n\tif p.Protocol == firewall.ProtoICMP || p.Protocol == firewall.ProtoICMPv6 {\n\t\t// port numbers are re-used for connection tracking of ICMP,\n\t\t// but we don't want to actually filter on them.\n\t\treturn fp[firewall.PortAny].match(p, c, caPool)\n\t}\n\n\tvar port int32\n\n\tif p.Fragment {\n\t\tport = firewall.PortFragment\n\t} else if incoming {\n\t\tport = int32(p.LocalPort)\n\t} else {\n\t\tport = int32(p.RemotePort)\n\t}\n\n\tif fp[port].match(p, c, caPool) {\n\t\treturn true\n\t}\n\n\treturn fp[firewall.PortAny].match(p, c, caPool)\n}\n\nfunc (fc *FirewallCA) addRule(f *Firewall, groups []string, host string, cidr, localCidr, caName, caSha string) error {\n\tfr := func() *FirewallRule {\n\t\treturn &FirewallRule{\n\t\t\tHosts:  make(map[string]*firewallLocalCIDR),\n\t\t\tGroups: make([]*firewallGroups, 0),\n\t\t\tCIDR:   new(bart.Table[*firewallLocalCIDR]),\n\t\t}\n\t}\n\n\tif caSha == \"\" && caName == \"\" {\n\t\tif fc.Any == nil {\n\t\t\tfc.Any = fr()\n\t\t}\n\n\t\treturn fc.Any.addRule(f, groups, host, cidr, localCidr)\n\t}\n\n\tif caSha != \"\" {\n\t\tif _, ok := fc.CAShas[caSha]; !ok {\n\t\t\tfc.CAShas[caSha] = fr()\n\t\t}\n\t\terr := fc.CAShas[caSha].addRule(f, groups, host, cidr, localCidr)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif caName != \"\" {\n\t\tif _, ok := fc.CANames[caName]; !ok {\n\t\t\tfc.CANames[caName] = fr()\n\t\t}\n\t\terr := fc.CANames[caName].addRule(f, groups, host, cidr, localCidr)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (fc *FirewallCA) match(p firewall.Packet, c *cert.CachedCertificate, caPool *cert.CAPool) bool {\n\tif fc == nil {\n\t\treturn false\n\t}\n\n\tif fc.Any.match(p, c) {\n\t\treturn true\n\t}\n\n\tif t, ok := fc.CAShas[c.Certificate.Issuer()]; ok {\n\t\tif t.match(p, c) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\ts, err := caPool.GetCAForCert(c.Certificate)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\treturn fc.CANames[s.Certificate.Name()].match(p, c)\n}\n\nfunc (fr *FirewallRule) addRule(f *Firewall, groups []string, host, cidr, localCidr string) error {\n\tflc := func() *firewallLocalCIDR {\n\t\treturn &firewallLocalCIDR{\n\t\t\tLocalCIDR: new(bart.Lite),\n\t\t}\n\t}\n\n\tif fr.isAny(groups, host, cidr) {\n\t\tif fr.Any == nil {\n\t\t\tfr.Any = flc()\n\t\t}\n\n\t\treturn fr.Any.addRule(f, localCidr)\n\t}\n\n\tif len(groups) > 0 {\n\t\tnlc := flc()\n\t\terr := nlc.addRule(f, localCidr)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfr.Groups = append(fr.Groups, &firewallGroups{\n\t\t\tGroups:    groups,\n\t\t\tLocalCIDR: nlc,\n\t\t})\n\t}\n\n\tif host != \"\" {\n\t\tnlc := fr.Hosts[host]\n\t\tif nlc == nil {\n\t\t\tnlc = flc()\n\t\t}\n\t\terr := nlc.addRule(f, localCidr)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfr.Hosts[host] = nlc\n\t}\n\n\tif cidr != \"\" {\n\t\tc, err := netip.ParsePrefix(cidr)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tnlc, _ := fr.CIDR.Get(c)\n\t\tif nlc == nil {\n\t\t\tnlc = flc()\n\t\t}\n\t\terr = nlc.addRule(f, localCidr)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfr.CIDR.Insert(c, nlc)\n\t}\n\n\treturn nil\n}\n\nfunc (fr *FirewallRule) isAny(groups []string, host string, cidr string) bool {\n\tif len(groups) == 0 && host == \"\" && cidr == \"\" {\n\t\treturn true\n\t}\n\n\tif slices.Contains(groups, \"any\") {\n\t\treturn true\n\t}\n\n\tif host == \"any\" {\n\t\treturn true\n\t}\n\n\tif cidr == \"any\" {\n\t\treturn true\n\t}\n\n\treturn false\n}\n\nfunc (fr *FirewallRule) match(p firewall.Packet, c *cert.CachedCertificate) bool {\n\tif fr == nil {\n\t\treturn false\n\t}\n\n\t// Shortcut path for if groups, hosts, or cidr contained an `any`\n\tif fr.Any.match(p, c) {\n\t\treturn true\n\t}\n\n\t// Need any of group, host, or cidr to match\n\tfor _, sg := range fr.Groups {\n\t\tfound := false\n\n\t\tfor _, g := range sg.Groups {\n\t\t\tif _, ok := c.InvertedGroups[g]; !ok {\n\t\t\t\tfound = false\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tfound = true\n\t\t}\n\n\t\tif found && sg.LocalCIDR.match(p, c) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\tif fr.Hosts != nil {\n\t\tif flc, ok := fr.Hosts[c.Certificate.Name()]; ok {\n\t\t\tif flc.match(p, c) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, v := range fr.CIDR.Supernets(netip.PrefixFrom(p.RemoteAddr, p.RemoteAddr.BitLen())) {\n\t\tif v.match(p, c) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc (flc *firewallLocalCIDR) addRule(f *Firewall, localCidr string) error {\n\tif localCidr == \"any\" {\n\t\tflc.Any = true\n\t\treturn nil\n\t}\n\n\tif localCidr == \"\" {\n\t\tif !f.hasUnsafeNetworks || f.defaultLocalCIDRAny {\n\t\t\tflc.Any = true\n\t\t\treturn nil\n\t\t}\n\n\t\tfor _, network := range f.assignedNetworks {\n\t\t\tflc.LocalCIDR.Insert(network)\n\t\t}\n\t\treturn nil\n\n\t}\n\n\tc, err := netip.ParsePrefix(localCidr)\n\tif err != nil {\n\t\treturn err\n\t}\n\tflc.LocalCIDR.Insert(c)\n\treturn nil\n}\n\nfunc (flc *firewallLocalCIDR) match(p firewall.Packet, c *cert.CachedCertificate) bool {\n\tif flc == nil {\n\t\treturn false\n\t}\n\n\tif flc.Any {\n\t\treturn true\n\t}\n\n\treturn flc.LocalCIDR.Contains(p.LocalAddr)\n}\n\ntype rule struct {\n\tPort      string\n\tCode      string\n\tProto     string\n\tHost      string\n\tGroups    []string\n\tCidr      string\n\tLocalCidr string\n\tCAName    string\n\tCASha     string\n}\n\nfunc convertRule(l *logrus.Logger, p any, table string, i int) (rule, error) {\n\tr := rule{}\n\n\tm, ok := p.(map[string]any)\n\tif !ok {\n\t\treturn r, errors.New(\"could not parse rule\")\n\t}\n\n\ttoString := func(k string, m map[string]any) string {\n\t\tv, ok := m[k]\n\t\tif !ok {\n\t\t\treturn \"\"\n\t\t}\n\t\treturn fmt.Sprintf(\"%v\", v)\n\t}\n\n\tr.Port = toString(\"port\", m)\n\tr.Code = toString(\"code\", m)\n\tr.Proto = toString(\"proto\", m)\n\tr.Host = toString(\"host\", m)\n\tr.Cidr = toString(\"cidr\", m)\n\tr.LocalCidr = toString(\"local_cidr\", m)\n\tr.CAName = toString(\"ca_name\", m)\n\tr.CASha = toString(\"ca_sha\", m)\n\n\t// Make sure group isn't an array\n\tif v, ok := m[\"group\"].([]any); ok {\n\t\tif len(v) > 1 {\n\t\t\treturn r, errors.New(\"group should contain a single value, an array with more than one entry was provided\")\n\t\t}\n\n\t\tl.Warnf(\"%s rule #%v; group was an array with a single value, converting to simple value\", table, i)\n\t\tm[\"group\"] = v[0]\n\t}\n\n\tsingleGroup := toString(\"group\", m)\n\n\tif rg, ok := m[\"groups\"]; ok {\n\t\tswitch reflect.TypeOf(rg).Kind() {\n\t\tcase reflect.Slice:\n\t\t\tv := reflect.ValueOf(rg)\n\t\t\tr.Groups = make([]string, v.Len())\n\t\t\tfor i := 0; i < v.Len(); i++ {\n\t\t\t\tr.Groups[i] = v.Index(i).Interface().(string)\n\t\t\t}\n\t\tcase reflect.String:\n\t\t\tr.Groups = []string{rg.(string)}\n\t\tdefault:\n\t\t\tr.Groups = []string{fmt.Sprintf(\"%v\", rg)}\n\t\t}\n\t}\n\n\t//flatten group vs groups\n\tif singleGroup != \"\" {\n\t\t// Check if we have both groups and group provided in the rule config\n\t\tif len(r.Groups) > 0 {\n\t\t\treturn r, fmt.Errorf(\"only one of group or groups should be defined, both provided\")\n\t\t}\n\t\tr.Groups = []string{singleGroup}\n\t}\n\n\treturn r, nil\n}\n\n// sanity returns an error if the rule would be evaluated in a way that would short-circuit a configured check on a wildcard value\n// rules are evaluated as \"port AND proto AND (ca_sha OR ca_name) AND (host OR group OR groups OR cidr) AND local_cidr\"\nfunc (r *rule) sanity() error {\n\t//port, proto, local_cidr are AND, no need to check here\n\t//ca_sha and ca_name don't have a wildcard value, no need to check here\n\tgroupsEmpty := len(r.Groups) == 0\n\thostEmpty := r.Host == \"\"\n\tcidrEmpty := r.Cidr == \"\"\n\n\tif (groupsEmpty && hostEmpty && cidrEmpty) == true {\n\t\treturn nil //no content!\n\t}\n\n\tgroupsHasAny := slices.Contains(r.Groups, \"any\")\n\tif groupsHasAny && len(r.Groups) > 1 {\n\t\treturn fmt.Errorf(\"groups spec [%s] contains the group '\\\"any\\\". This rule will ignore the other groups specified\", r.Groups)\n\t}\n\n\tif r.Host == \"any\" {\n\t\tif !groupsEmpty {\n\t\t\treturn fmt.Errorf(\"groups specified as %s, but host=any will match any host, regardless of groups\", r.Groups)\n\t\t}\n\n\t\tif !cidrEmpty {\n\t\t\treturn fmt.Errorf(\"cidr specified as %s, but host=any will match any host, regardless of cidr\", r.Cidr)\n\t\t}\n\t}\n\n\tif groupsHasAny {\n\t\tif !hostEmpty && r.Host != \"any\" {\n\t\t\treturn fmt.Errorf(\"groups spec [%s] contains the group '\\\"any\\\". This rule will ignore the specified host %s\", r.Groups, r.Host)\n\t\t}\n\t\tif !cidrEmpty {\n\t\t\treturn fmt.Errorf(\"groups spec [%s] contains the group '\\\"any\\\". This rule will ignore the specified cidr %s\", r.Groups, r.Cidr)\n\t\t}\n\t}\n\n\tif r.Code != \"\" {\n\t\treturn fmt.Errorf(\"code specified as [%s]. Support for 'code' will be dropped in a future release, as it has never been functional\", r.Code)\n\t}\n\n\t//todo alert on cidr-any\n\n\treturn nil\n}\n\nfunc parsePort(s string) (int32, int32, error) {\n\tvar err error\n\tconst notAPort int32 = -2\n\tif s == \"any\" {\n\t\treturn firewall.PortAny, firewall.PortAny, nil\n\t}\n\tif s == \"fragment\" {\n\t\treturn firewall.PortFragment, firewall.PortFragment, nil\n\t}\n\tif !strings.Contains(s, `-`) {\n\t\trPort, err := strconv.Atoi(s)\n\t\tif err != nil {\n\t\t\treturn notAPort, notAPort, fmt.Errorf(\"was not a number; `%s`\", s)\n\t\t}\n\t\treturn int32(rPort), int32(rPort), nil\n\t}\n\n\tsPorts := strings.SplitN(s, `-`, 2)\n\tfor i := range sPorts {\n\t\tsPorts[i] = strings.Trim(sPorts[i], \" \")\n\t}\n\tif len(sPorts) != 2 || sPorts[0] == \"\" || sPorts[1] == \"\" {\n\t\treturn notAPort, notAPort, fmt.Errorf(\"appears to be a range but could not be parsed; `%s`\", s)\n\t}\n\n\trStartPort, err := strconv.Atoi(sPorts[0])\n\tif err != nil {\n\t\treturn notAPort, notAPort, fmt.Errorf(\"beginning range was not a number; `%s`\", sPorts[0])\n\t}\n\n\trEndPort, err := strconv.Atoi(sPorts[1])\n\tif err != nil {\n\t\treturn notAPort, notAPort, fmt.Errorf(\"ending range was not a number; `%s`\", sPorts[1])\n\t}\n\n\tstartPort := int32(rStartPort)\n\tendPort := int32(rEndPort)\n\n\tif startPort == firewall.PortAny {\n\t\tendPort = firewall.PortAny\n\t}\n\n\treturn startPort, endPort, nil\n}\n"
  },
  {
    "path": "firewall_test.go",
    "content": "package nebula\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"math\"\n\t\"net/netip\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gaissmai/bart\"\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/cert\"\n\t\"github.com/slackhq/nebula/config\"\n\t\"github.com/slackhq/nebula/firewall\"\n\t\"github.com/slackhq/nebula/test\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNewFirewall(t *testing.T) {\n\tl := test.NewLogger()\n\tc := &dummyCert{}\n\tfw := NewFirewall(l, time.Second, time.Minute, time.Hour, c)\n\tconntrack := fw.Conntrack\n\tassert.NotNil(t, conntrack)\n\tassert.NotNil(t, conntrack.Conns)\n\tassert.NotNil(t, conntrack.TimerWheel)\n\tassert.NotNil(t, fw.InRules)\n\tassert.NotNil(t, fw.OutRules)\n\tassert.Equal(t, time.Second, fw.TCPTimeout)\n\tassert.Equal(t, time.Minute, fw.UDPTimeout)\n\tassert.Equal(t, time.Hour, fw.DefaultTimeout)\n\n\tassert.Equal(t, time.Hour, conntrack.TimerWheel.wheelDuration)\n\tassert.Equal(t, time.Hour, conntrack.TimerWheel.wheelDuration)\n\tassert.Equal(t, 3602, conntrack.TimerWheel.wheelLen)\n\n\tfw = NewFirewall(l, time.Second, time.Hour, time.Minute, c)\n\tassert.Equal(t, time.Hour, conntrack.TimerWheel.wheelDuration)\n\tassert.Equal(t, 3602, conntrack.TimerWheel.wheelLen)\n\n\tfw = NewFirewall(l, time.Hour, time.Second, time.Minute, c)\n\tassert.Equal(t, time.Hour, conntrack.TimerWheel.wheelDuration)\n\tassert.Equal(t, 3602, conntrack.TimerWheel.wheelLen)\n\n\tfw = NewFirewall(l, time.Hour, time.Minute, time.Second, c)\n\tassert.Equal(t, time.Hour, conntrack.TimerWheel.wheelDuration)\n\tassert.Equal(t, 3602, conntrack.TimerWheel.wheelLen)\n\n\tfw = NewFirewall(l, time.Minute, time.Hour, time.Second, c)\n\tassert.Equal(t, time.Hour, conntrack.TimerWheel.wheelDuration)\n\tassert.Equal(t, 3602, conntrack.TimerWheel.wheelLen)\n\n\tfw = NewFirewall(l, time.Minute, time.Second, time.Hour, c)\n\tassert.Equal(t, time.Hour, conntrack.TimerWheel.wheelDuration)\n\tassert.Equal(t, 3602, conntrack.TimerWheel.wheelLen)\n}\n\nfunc TestFirewall_AddRule(t *testing.T) {\n\tl := test.NewLogger()\n\tob := &bytes.Buffer{}\n\tl.SetOutput(ob)\n\n\tc := &dummyCert{}\n\tfw := NewFirewall(l, time.Second, time.Minute, time.Hour, c)\n\tassert.NotNil(t, fw.InRules)\n\tassert.NotNil(t, fw.OutRules)\n\n\tti, err := netip.ParsePrefix(\"1.2.3.4/32\")\n\trequire.NoError(t, err)\n\n\tti6, err := netip.ParsePrefix(\"fd12::34/128\")\n\trequire.NoError(t, err)\n\n\trequire.NoError(t, fw.AddRule(true, firewall.ProtoTCP, 1, 1, []string{}, \"\", \"\", \"\", \"\", \"\"))\n\t// An empty rule is any\n\tassert.True(t, fw.InRules.TCP[1].Any.Any.Any)\n\tassert.Empty(t, fw.InRules.TCP[1].Any.Groups)\n\tassert.Empty(t, fw.InRules.TCP[1].Any.Hosts)\n\n\tfw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)\n\trequire.NoError(t, fw.AddRule(true, firewall.ProtoUDP, 1, 1, []string{\"g1\"}, \"\", \"\", \"\", \"\", \"\"))\n\tassert.Nil(t, fw.InRules.UDP[1].Any.Any)\n\tassert.Contains(t, fw.InRules.UDP[1].Any.Groups[0].Groups, \"g1\")\n\tassert.Empty(t, fw.InRules.UDP[1].Any.Hosts)\n\n\tfw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)\n\trequire.NoError(t, fw.AddRule(true, firewall.ProtoICMP, 1, 1, []string{}, \"h1\", \"\", \"\", \"\", \"\"))\n\t//no matter what port is given for icmp, it should end up as \"any\"\n\tassert.Nil(t, fw.InRules.ICMP[firewall.PortAny].Any.Any)\n\tassert.Empty(t, fw.InRules.ICMP[firewall.PortAny].Any.Groups)\n\tassert.Contains(t, fw.InRules.ICMP[firewall.PortAny].Any.Hosts, \"h1\")\n\n\tfw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)\n\trequire.NoError(t, fw.AddRule(false, firewall.ProtoAny, 1, 1, []string{}, \"\", ti.String(), \"\", \"\", \"\"))\n\tassert.Nil(t, fw.OutRules.AnyProto[1].Any.Any)\n\t_, ok := fw.OutRules.AnyProto[1].Any.CIDR.Get(ti)\n\tassert.True(t, ok)\n\n\tfw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)\n\trequire.NoError(t, fw.AddRule(false, firewall.ProtoAny, 1, 1, []string{}, \"\", ti6.String(), \"\", \"\", \"\"))\n\tassert.Nil(t, fw.OutRules.AnyProto[1].Any.Any)\n\t_, ok = fw.OutRules.AnyProto[1].Any.CIDR.Get(ti6)\n\tassert.True(t, ok)\n\n\tfw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)\n\trequire.NoError(t, fw.AddRule(false, firewall.ProtoAny, 1, 1, []string{}, \"\", \"\", ti.String(), \"\", \"\"))\n\tassert.NotNil(t, fw.OutRules.AnyProto[1].Any.Any)\n\tok = fw.OutRules.AnyProto[1].Any.Any.LocalCIDR.Get(ti)\n\tassert.True(t, ok)\n\n\tfw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)\n\trequire.NoError(t, fw.AddRule(false, firewall.ProtoAny, 1, 1, []string{}, \"\", \"\", ti6.String(), \"\", \"\"))\n\tassert.NotNil(t, fw.OutRules.AnyProto[1].Any.Any)\n\tok = fw.OutRules.AnyProto[1].Any.Any.LocalCIDR.Get(ti6)\n\tassert.True(t, ok)\n\n\tfw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)\n\trequire.NoError(t, fw.AddRule(true, firewall.ProtoUDP, 1, 1, []string{\"g1\"}, \"\", \"\", \"\", \"ca-name\", \"\"))\n\tassert.Contains(t, fw.InRules.UDP[1].CANames, \"ca-name\")\n\n\tfw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)\n\trequire.NoError(t, fw.AddRule(true, firewall.ProtoUDP, 1, 1, []string{\"g1\"}, \"\", \"\", \"\", \"\", \"ca-sha\"))\n\tassert.Contains(t, fw.InRules.UDP[1].CAShas, \"ca-sha\")\n\n\tfw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)\n\trequire.NoError(t, fw.AddRule(false, firewall.ProtoAny, 0, 0, []string{}, \"any\", \"\", \"\", \"\", \"\"))\n\tassert.True(t, fw.OutRules.AnyProto[0].Any.Any.Any)\n\n\tfw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)\n\tanyIp, err := netip.ParsePrefix(\"0.0.0.0/0\")\n\trequire.NoError(t, err)\n\n\trequire.NoError(t, fw.AddRule(false, firewall.ProtoAny, 0, 0, []string{}, \"\", anyIp.String(), \"\", \"\", \"\"))\n\tassert.Nil(t, fw.OutRules.AnyProto[0].Any.Any)\n\ttable, ok := fw.OutRules.AnyProto[0].Any.CIDR.Lookup(netip.MustParseAddr(\"1.1.1.1\"))\n\tassert.True(t, table.Any)\n\ttable, ok = fw.OutRules.AnyProto[0].Any.CIDR.Lookup(netip.MustParseAddr(\"9::9\"))\n\tassert.False(t, ok)\n\n\tfw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)\n\tanyIp6, err := netip.ParsePrefix(\"::/0\")\n\trequire.NoError(t, err)\n\n\trequire.NoError(t, fw.AddRule(false, firewall.ProtoAny, 0, 0, []string{}, \"\", anyIp6.String(), \"\", \"\", \"\"))\n\tassert.Nil(t, fw.OutRules.AnyProto[0].Any.Any)\n\ttable, ok = fw.OutRules.AnyProto[0].Any.CIDR.Lookup(netip.MustParseAddr(\"9::9\"))\n\tassert.True(t, table.Any)\n\ttable, ok = fw.OutRules.AnyProto[0].Any.CIDR.Lookup(netip.MustParseAddr(\"1.1.1.1\"))\n\tassert.False(t, ok)\n\n\tfw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)\n\trequire.NoError(t, fw.AddRule(false, firewall.ProtoAny, 0, 0, []string{}, \"\", \"any\", \"\", \"\", \"\"))\n\tassert.True(t, fw.OutRules.AnyProto[0].Any.Any.Any)\n\n\tfw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)\n\trequire.NoError(t, fw.AddRule(false, firewall.ProtoAny, 0, 0, []string{}, \"\", \"\", anyIp.String(), \"\", \"\"))\n\tassert.False(t, fw.OutRules.AnyProto[0].Any.Any.Any)\n\tassert.True(t, fw.OutRules.AnyProto[0].Any.Any.LocalCIDR.Lookup(netip.MustParseAddr(\"1.1.1.1\")))\n\tassert.False(t, fw.OutRules.AnyProto[0].Any.Any.LocalCIDR.Lookup(netip.MustParseAddr(\"9::9\")))\n\n\tfw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)\n\trequire.NoError(t, fw.AddRule(false, firewall.ProtoAny, 0, 0, []string{}, \"\", \"\", anyIp6.String(), \"\", \"\"))\n\tassert.False(t, fw.OutRules.AnyProto[0].Any.Any.Any)\n\tassert.True(t, fw.OutRules.AnyProto[0].Any.Any.LocalCIDR.Lookup(netip.MustParseAddr(\"9::9\")))\n\tassert.False(t, fw.OutRules.AnyProto[0].Any.Any.LocalCIDR.Lookup(netip.MustParseAddr(\"1.1.1.1\")))\n\n\tfw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)\n\trequire.NoError(t, fw.AddRule(false, firewall.ProtoAny, 0, 0, []string{}, \"\", \"\", \"any\", \"\", \"\"))\n\tassert.True(t, fw.OutRules.AnyProto[0].Any.Any.Any)\n\n\t// Test error conditions\n\tfw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)\n\trequire.Error(t, fw.AddRule(true, math.MaxUint8, 0, 0, []string{}, \"\", \"\", \"\", \"\", \"\"))\n\trequire.Error(t, fw.AddRule(true, firewall.ProtoAny, 10, 0, []string{}, \"\", \"\", \"\", \"\", \"\"))\n}\n\nfunc TestFirewall_Drop(t *testing.T) {\n\tl := test.NewLogger()\n\tob := &bytes.Buffer{}\n\tl.SetOutput(ob)\n\tmyVpnNetworksTable := new(bart.Lite)\n\tmyVpnNetworksTable.Insert(netip.MustParsePrefix(\"1.1.1.1/8\"))\n\tp := firewall.Packet{\n\t\tLocalAddr:  netip.MustParseAddr(\"1.2.3.4\"),\n\t\tRemoteAddr: netip.MustParseAddr(\"1.2.3.4\"),\n\t\tLocalPort:  10,\n\t\tRemotePort: 90,\n\t\tProtocol:   firewall.ProtoUDP,\n\t\tFragment:   false,\n\t}\n\n\tc := dummyCert{\n\t\tname:     \"host1\",\n\t\tnetworks: []netip.Prefix{netip.MustParsePrefix(\"1.2.3.4/24\")},\n\t\tgroups:   []string{\"default-group\"},\n\t\tissuer:   \"signer-shasum\",\n\t}\n\th := HostInfo{\n\t\tConnectionState: &ConnectionState{\n\t\t\tpeerCert: &cert.CachedCertificate{\n\t\t\t\tCertificate:    &c,\n\t\t\t\tInvertedGroups: map[string]struct{}{\"default-group\": {}},\n\t\t\t},\n\t\t},\n\t\tvpnAddrs: []netip.Addr{netip.MustParseAddr(\"1.2.3.4\")},\n\t}\n\th.buildNetworks(myVpnNetworksTable, &c)\n\n\tfw := NewFirewall(l, time.Second, time.Minute, time.Hour, &c)\n\trequire.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{\"any\"}, \"\", \"\", \"\", \"\", \"\"))\n\tcp := cert.NewCAPool()\n\n\t// Drop outbound\n\tassert.Equal(t, ErrNoMatchingRule, fw.Drop(p, false, &h, cp, nil))\n\t// Allow inbound\n\tresetConntrack(fw)\n\trequire.NoError(t, fw.Drop(p, true, &h, cp, nil))\n\t// Allow outbound because conntrack\n\trequire.NoError(t, fw.Drop(p, false, &h, cp, nil))\n\n\t// test remote mismatch\n\toldRemote := p.RemoteAddr\n\tp.RemoteAddr = netip.MustParseAddr(\"1.2.3.10\")\n\tassert.Equal(t, fw.Drop(p, false, &h, cp, nil), ErrInvalidRemoteIP)\n\tp.RemoteAddr = oldRemote\n\n\t// ensure signer doesn't get in the way of group checks\n\tfw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)\n\trequire.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{\"nope\"}, \"\", \"\", \"\", \"\", \"signer-shasum\"))\n\trequire.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{\"default-group\"}, \"\", \"\", \"\", \"\", \"signer-shasum-bad\"))\n\tassert.Equal(t, fw.Drop(p, true, &h, cp, nil), ErrNoMatchingRule)\n\n\t// test caSha doesn't drop on match\n\tfw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)\n\trequire.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{\"nope\"}, \"\", \"\", \"\", \"\", \"signer-shasum-bad\"))\n\trequire.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{\"default-group\"}, \"\", \"\", \"\", \"\", \"signer-shasum\"))\n\trequire.NoError(t, fw.Drop(p, true, &h, cp, nil))\n\n\t// ensure ca name doesn't get in the way of group checks\n\tcp.CAs[\"signer-shasum\"] = &cert.CachedCertificate{Certificate: &dummyCert{name: \"ca-good\"}}\n\tfw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)\n\trequire.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{\"nope\"}, \"\", \"\", \"\", \"ca-good\", \"\"))\n\trequire.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{\"default-group\"}, \"\", \"\", \"\", \"ca-good-bad\", \"\"))\n\tassert.Equal(t, fw.Drop(p, true, &h, cp, nil), ErrNoMatchingRule)\n\n\t// test caName doesn't drop on match\n\tcp.CAs[\"signer-shasum\"] = &cert.CachedCertificate{Certificate: &dummyCert{name: \"ca-good\"}}\n\tfw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)\n\trequire.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{\"nope\"}, \"\", \"\", \"\", \"ca-good-bad\", \"\"))\n\trequire.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{\"default-group\"}, \"\", \"\", \"\", \"ca-good\", \"\"))\n\trequire.NoError(t, fw.Drop(p, true, &h, cp, nil))\n}\n\nfunc TestFirewall_DropV6(t *testing.T) {\n\tl := test.NewLogger()\n\tob := &bytes.Buffer{}\n\tl.SetOutput(ob)\n\n\tmyVpnNetworksTable := new(bart.Lite)\n\tmyVpnNetworksTable.Insert(netip.MustParsePrefix(\"fd00::/7\"))\n\n\tp := firewall.Packet{\n\t\tLocalAddr:  netip.MustParseAddr(\"fd12::34\"),\n\t\tRemoteAddr: netip.MustParseAddr(\"fd12::34\"),\n\t\tLocalPort:  10,\n\t\tRemotePort: 90,\n\t\tProtocol:   firewall.ProtoUDP,\n\t\tFragment:   false,\n\t}\n\n\tc := dummyCert{\n\t\tname:     \"host1\",\n\t\tnetworks: []netip.Prefix{netip.MustParsePrefix(\"fd12::34/120\")},\n\t\tgroups:   []string{\"default-group\"},\n\t\tissuer:   \"signer-shasum\",\n\t}\n\th := HostInfo{\n\t\tConnectionState: &ConnectionState{\n\t\t\tpeerCert: &cert.CachedCertificate{\n\t\t\t\tCertificate:    &c,\n\t\t\t\tInvertedGroups: map[string]struct{}{\"default-group\": {}},\n\t\t\t},\n\t\t},\n\t\tvpnAddrs: []netip.Addr{netip.MustParseAddr(\"fd12::34\")},\n\t}\n\th.buildNetworks(myVpnNetworksTable, &c)\n\n\tfw := NewFirewall(l, time.Second, time.Minute, time.Hour, &c)\n\trequire.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{\"any\"}, \"\", \"\", \"\", \"\", \"\"))\n\tcp := cert.NewCAPool()\n\n\t// Drop outbound\n\tassert.Equal(t, ErrNoMatchingRule, fw.Drop(p, false, &h, cp, nil))\n\t// Allow inbound\n\tresetConntrack(fw)\n\trequire.NoError(t, fw.Drop(p, true, &h, cp, nil))\n\t// Allow outbound because conntrack\n\trequire.NoError(t, fw.Drop(p, false, &h, cp, nil))\n\n\t// test remote mismatch\n\toldRemote := p.RemoteAddr\n\tp.RemoteAddr = netip.MustParseAddr(\"fd12::56\")\n\tassert.Equal(t, fw.Drop(p, false, &h, cp, nil), ErrInvalidRemoteIP)\n\tp.RemoteAddr = oldRemote\n\n\t// ensure signer doesn't get in the way of group checks\n\tfw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)\n\trequire.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{\"nope\"}, \"\", \"\", \"\", \"\", \"signer-shasum\"))\n\trequire.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{\"default-group\"}, \"\", \"\", \"\", \"\", \"signer-shasum-bad\"))\n\tassert.Equal(t, fw.Drop(p, true, &h, cp, nil), ErrNoMatchingRule)\n\n\t// test caSha doesn't drop on match\n\tfw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)\n\trequire.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{\"nope\"}, \"\", \"\", \"\", \"\", \"signer-shasum-bad\"))\n\trequire.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{\"default-group\"}, \"\", \"\", \"\", \"\", \"signer-shasum\"))\n\trequire.NoError(t, fw.Drop(p, true, &h, cp, nil))\n\n\t// ensure ca name doesn't get in the way of group checks\n\tcp.CAs[\"signer-shasum\"] = &cert.CachedCertificate{Certificate: &dummyCert{name: \"ca-good\"}}\n\tfw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)\n\trequire.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{\"nope\"}, \"\", \"\", \"\", \"ca-good\", \"\"))\n\trequire.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{\"default-group\"}, \"\", \"\", \"\", \"ca-good-bad\", \"\"))\n\tassert.Equal(t, fw.Drop(p, true, &h, cp, nil), ErrNoMatchingRule)\n\n\t// test caName doesn't drop on match\n\tcp.CAs[\"signer-shasum\"] = &cert.CachedCertificate{Certificate: &dummyCert{name: \"ca-good\"}}\n\tfw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)\n\trequire.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{\"nope\"}, \"\", \"\", \"\", \"ca-good-bad\", \"\"))\n\trequire.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{\"default-group\"}, \"\", \"\", \"\", \"ca-good\", \"\"))\n\trequire.NoError(t, fw.Drop(p, true, &h, cp, nil))\n}\n\nfunc BenchmarkFirewallTable_match(b *testing.B) {\n\tf := &Firewall{}\n\tft := FirewallTable{\n\t\tTCP: firewallPort{},\n\t}\n\n\tpfix := netip.MustParsePrefix(\"172.1.1.1/32\")\n\t_ = ft.TCP.addRule(f, 10, 10, []string{\"good-group\"}, \"good-host\", pfix.String(), \"\", \"\", \"\")\n\t_ = ft.TCP.addRule(f, 100, 100, []string{\"good-group\"}, \"good-host\", \"\", pfix.String(), \"\", \"\")\n\n\tpfix6 := netip.MustParsePrefix(\"fd11::11/128\")\n\t_ = ft.TCP.addRule(f, 10, 10, []string{\"good-group\"}, \"good-host\", pfix6.String(), \"\", \"\", \"\")\n\t_ = ft.TCP.addRule(f, 100, 100, []string{\"good-group\"}, \"good-host\", \"\", pfix6.String(), \"\", \"\")\n\tcp := cert.NewCAPool()\n\n\tb.Run(\"fail on proto\", func(b *testing.B) {\n\t\t// This benchmark is showing us the cost of failing to match the protocol\n\t\tc := &cert.CachedCertificate{\n\t\t\tCertificate: &dummyCert{},\n\t\t}\n\t\tfor n := 0; n < b.N; n++ {\n\t\t\tassert.False(b, ft.match(firewall.Packet{Protocol: firewall.ProtoUDP}, true, c, cp))\n\t\t}\n\t})\n\n\tb.Run(\"pass proto, fail on port\", func(b *testing.B) {\n\t\t// This benchmark is showing us the cost of matching a specific protocol but failing to match the port\n\t\tc := &cert.CachedCertificate{\n\t\t\tCertificate: &dummyCert{},\n\t\t}\n\t\tfor n := 0; n < b.N; n++ {\n\t\t\tassert.False(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 1}, true, c, cp))\n\t\t}\n\t})\n\n\tb.Run(\"pass proto, port, fail on local CIDR\", func(b *testing.B) {\n\t\tc := &cert.CachedCertificate{\n\t\t\tCertificate: &dummyCert{},\n\t\t}\n\t\tip := netip.MustParsePrefix(\"9.254.254.254/32\")\n\t\tfor n := 0; n < b.N; n++ {\n\t\t\tassert.False(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 100, LocalAddr: ip.Addr()}, true, c, cp))\n\t\t}\n\t})\n\tb.Run(\"pass proto, port, fail on local CIDRv6\", func(b *testing.B) {\n\t\tc := &cert.CachedCertificate{\n\t\t\tCertificate: &dummyCert{},\n\t\t}\n\t\tip := netip.MustParsePrefix(\"fd99::99/128\")\n\t\tfor n := 0; n < b.N; n++ {\n\t\t\tassert.False(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 100, LocalAddr: ip.Addr()}, true, c, cp))\n\t\t}\n\t})\n\n\tb.Run(\"pass proto, port, any local CIDR, fail all group, name, and cidr\", func(b *testing.B) {\n\t\tc := &cert.CachedCertificate{\n\t\t\tCertificate: &dummyCert{\n\t\t\t\tname:     \"nope\",\n\t\t\t\tnetworks: []netip.Prefix{netip.MustParsePrefix(\"9.254.254.245/32\")},\n\t\t\t},\n\t\t\tInvertedGroups: map[string]struct{}{\"nope\": {}},\n\t\t}\n\t\tfor n := 0; n < b.N; n++ {\n\t\t\tassert.False(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 10}, true, c, cp))\n\t\t}\n\t})\n\tb.Run(\"pass proto, port, any local CIDRv6, fail all group, name, and cidr\", func(b *testing.B) {\n\t\tc := &cert.CachedCertificate{\n\t\t\tCertificate: &dummyCert{\n\t\t\t\tname:     \"nope\",\n\t\t\t\tnetworks: []netip.Prefix{netip.MustParsePrefix(\"fd99::99/128\")},\n\t\t\t},\n\t\t\tInvertedGroups: map[string]struct{}{\"nope\": {}},\n\t\t}\n\t\tfor n := 0; n < b.N; n++ {\n\t\t\tassert.False(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 10}, true, c, cp))\n\t\t}\n\t})\n\n\tb.Run(\"pass proto, port, specific local CIDR, fail all group, name, and cidr\", func(b *testing.B) {\n\t\tc := &cert.CachedCertificate{\n\t\t\tCertificate: &dummyCert{\n\t\t\t\tname:     \"nope\",\n\t\t\t\tnetworks: []netip.Prefix{netip.MustParsePrefix(\"9.254.254.245/32\")},\n\t\t\t},\n\t\t\tInvertedGroups: map[string]struct{}{\"nope\": {}},\n\t\t}\n\t\tfor n := 0; n < b.N; n++ {\n\t\t\tassert.False(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 100, LocalAddr: pfix.Addr()}, true, c, cp))\n\t\t}\n\t})\n\tb.Run(\"pass proto, port, specific local CIDRv6, fail all group, name, and cidr\", func(b *testing.B) {\n\t\tc := &cert.CachedCertificate{\n\t\t\tCertificate: &dummyCert{\n\t\t\t\tname:     \"nope\",\n\t\t\t\tnetworks: []netip.Prefix{netip.MustParsePrefix(\"fd99::99/128\")},\n\t\t\t},\n\t\t\tInvertedGroups: map[string]struct{}{\"nope\": {}},\n\t\t}\n\t\tfor n := 0; n < b.N; n++ {\n\t\t\tassert.False(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 100, LocalAddr: pfix6.Addr()}, true, c, cp))\n\t\t}\n\t})\n\n\tb.Run(\"pass on group on any local cidr\", func(b *testing.B) {\n\t\tc := &cert.CachedCertificate{\n\t\t\tCertificate: &dummyCert{\n\t\t\t\tname: \"nope\",\n\t\t\t},\n\t\t\tInvertedGroups: map[string]struct{}{\"good-group\": {}},\n\t\t}\n\t\tfor n := 0; n < b.N; n++ {\n\t\t\tassert.True(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 10}, true, c, cp))\n\t\t}\n\t})\n\n\tb.Run(\"pass on group on specific local cidr\", func(b *testing.B) {\n\t\tc := &cert.CachedCertificate{\n\t\t\tCertificate: &dummyCert{\n\t\t\t\tname: \"nope\",\n\t\t\t},\n\t\t\tInvertedGroups: map[string]struct{}{\"good-group\": {}},\n\t\t}\n\t\tfor n := 0; n < b.N; n++ {\n\t\t\tassert.True(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 100, LocalAddr: pfix.Addr()}, true, c, cp))\n\t\t}\n\t})\n\tb.Run(\"pass on group on specific local cidr6\", func(b *testing.B) {\n\t\tc := &cert.CachedCertificate{\n\t\t\tCertificate: &dummyCert{\n\t\t\t\tname: \"nope\",\n\t\t\t},\n\t\t\tInvertedGroups: map[string]struct{}{\"good-group\": {}},\n\t\t}\n\t\tfor n := 0; n < b.N; n++ {\n\t\t\tassert.True(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 100, LocalAddr: pfix6.Addr()}, true, c, cp))\n\t\t}\n\t})\n\n\tb.Run(\"pass on name\", func(b *testing.B) {\n\t\tc := &cert.CachedCertificate{\n\t\t\tCertificate: &dummyCert{\n\t\t\t\tname: \"good-host\",\n\t\t\t},\n\t\t\tInvertedGroups: map[string]struct{}{\"nope\": {}},\n\t\t}\n\t\tfor n := 0; n < b.N; n++ {\n\t\t\tft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 10}, true, c, cp)\n\t\t}\n\t})\n}\n\nfunc TestFirewall_Drop2(t *testing.T) {\n\tl := test.NewLogger()\n\tob := &bytes.Buffer{}\n\tl.SetOutput(ob)\n\tmyVpnNetworksTable := new(bart.Lite)\n\tmyVpnNetworksTable.Insert(netip.MustParsePrefix(\"1.1.1.1/8\"))\n\n\tp := firewall.Packet{\n\t\tLocalAddr:  netip.MustParseAddr(\"1.2.3.4\"),\n\t\tRemoteAddr: netip.MustParseAddr(\"1.2.3.4\"),\n\t\tLocalPort:  10,\n\t\tRemotePort: 90,\n\t\tProtocol:   firewall.ProtoUDP,\n\t\tFragment:   false,\n\t}\n\n\tnetwork := netip.MustParsePrefix(\"1.2.3.4/24\")\n\n\tc := cert.CachedCertificate{\n\t\tCertificate: &dummyCert{\n\t\t\tname:     \"host1\",\n\t\t\tnetworks: []netip.Prefix{network},\n\t\t},\n\t\tInvertedGroups: map[string]struct{}{\"default-group\": {}, \"test-group\": {}},\n\t}\n\th := HostInfo{\n\t\tConnectionState: &ConnectionState{\n\t\t\tpeerCert: &c,\n\t\t},\n\t\tvpnAddrs: []netip.Addr{network.Addr()},\n\t}\n\th.buildNetworks(myVpnNetworksTable, c.Certificate)\n\n\tc1 := cert.CachedCertificate{\n\t\tCertificate: &dummyCert{\n\t\t\tname:     \"host1\",\n\t\t\tnetworks: []netip.Prefix{network},\n\t\t},\n\t\tInvertedGroups: map[string]struct{}{\"default-group\": {}, \"test-group-not\": {}},\n\t}\n\th1 := HostInfo{\n\t\tvpnAddrs: []netip.Addr{network.Addr()},\n\t\tConnectionState: &ConnectionState{\n\t\t\tpeerCert: &c1,\n\t\t},\n\t}\n\th1.buildNetworks(myVpnNetworksTable, c1.Certificate)\n\n\tfw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)\n\trequire.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{\"default-group\", \"test-group\"}, \"\", \"\", \"\", \"\", \"\"))\n\tcp := cert.NewCAPool()\n\n\t// h1/c1 lacks the proper groups\n\trequire.ErrorIs(t, fw.Drop(p, true, &h1, cp, nil), ErrNoMatchingRule)\n\t// c has the proper groups\n\tresetConntrack(fw)\n\trequire.NoError(t, fw.Drop(p, true, &h, cp, nil))\n}\n\nfunc TestFirewall_Drop3(t *testing.T) {\n\tl := test.NewLogger()\n\tob := &bytes.Buffer{}\n\tl.SetOutput(ob)\n\tmyVpnNetworksTable := new(bart.Lite)\n\tmyVpnNetworksTable.Insert(netip.MustParsePrefix(\"1.1.1.1/8\"))\n\n\tp := firewall.Packet{\n\t\tLocalAddr:  netip.MustParseAddr(\"1.2.3.4\"),\n\t\tRemoteAddr: netip.MustParseAddr(\"1.2.3.4\"),\n\t\tLocalPort:  1,\n\t\tRemotePort: 1,\n\t\tProtocol:   firewall.ProtoUDP,\n\t\tFragment:   false,\n\t}\n\n\tnetwork := netip.MustParsePrefix(\"1.2.3.4/24\")\n\tc := cert.CachedCertificate{\n\t\tCertificate: &dummyCert{\n\t\t\tname:     \"host-owner\",\n\t\t\tnetworks: []netip.Prefix{network},\n\t\t},\n\t}\n\n\tc1 := cert.CachedCertificate{\n\t\tCertificate: &dummyCert{\n\t\t\tname:     \"host1\",\n\t\t\tnetworks: []netip.Prefix{network},\n\t\t\tissuer:   \"signer-sha-bad\",\n\t\t},\n\t}\n\th1 := HostInfo{\n\t\tConnectionState: &ConnectionState{\n\t\t\tpeerCert: &c1,\n\t\t},\n\t\tvpnAddrs: []netip.Addr{network.Addr()},\n\t}\n\th1.buildNetworks(myVpnNetworksTable, c1.Certificate)\n\n\tc2 := cert.CachedCertificate{\n\t\tCertificate: &dummyCert{\n\t\t\tname:     \"host2\",\n\t\t\tnetworks: []netip.Prefix{network},\n\t\t\tissuer:   \"signer-sha\",\n\t\t},\n\t}\n\th2 := HostInfo{\n\t\tConnectionState: &ConnectionState{\n\t\t\tpeerCert: &c2,\n\t\t},\n\t\tvpnAddrs: []netip.Addr{network.Addr()},\n\t}\n\th2.buildNetworks(myVpnNetworksTable, c2.Certificate)\n\n\tc3 := cert.CachedCertificate{\n\t\tCertificate: &dummyCert{\n\t\t\tname:     \"host3\",\n\t\t\tnetworks: []netip.Prefix{network},\n\t\t\tissuer:   \"signer-sha-bad\",\n\t\t},\n\t}\n\th3 := HostInfo{\n\t\tConnectionState: &ConnectionState{\n\t\t\tpeerCert: &c3,\n\t\t},\n\t\tvpnAddrs: []netip.Addr{network.Addr()},\n\t}\n\th3.buildNetworks(myVpnNetworksTable, c3.Certificate)\n\n\tfw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)\n\trequire.NoError(t, fw.AddRule(true, firewall.ProtoAny, 1, 1, []string{}, \"host1\", \"\", \"\", \"\", \"\"))\n\trequire.NoError(t, fw.AddRule(true, firewall.ProtoAny, 1, 1, []string{}, \"\", \"\", \"\", \"\", \"signer-sha\"))\n\tcp := cert.NewCAPool()\n\n\t// c1 should pass because host match\n\trequire.NoError(t, fw.Drop(p, true, &h1, cp, nil))\n\t// c2 should pass because ca sha match\n\tresetConntrack(fw)\n\trequire.NoError(t, fw.Drop(p, true, &h2, cp, nil))\n\t// c3 should fail because no match\n\tresetConntrack(fw)\n\tassert.Equal(t, fw.Drop(p, true, &h3, cp, nil), ErrNoMatchingRule)\n\n\t// Test a remote address match\n\tfw = NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)\n\trequire.NoError(t, fw.AddRule(true, firewall.ProtoAny, 1, 1, []string{}, \"\", \"1.2.3.4/24\", \"\", \"\", \"\"))\n\trequire.NoError(t, fw.Drop(p, true, &h1, cp, nil))\n}\n\nfunc TestFirewall_Drop3V6(t *testing.T) {\n\tl := test.NewLogger()\n\tob := &bytes.Buffer{}\n\tl.SetOutput(ob)\n\tmyVpnNetworksTable := new(bart.Lite)\n\tmyVpnNetworksTable.Insert(netip.MustParsePrefix(\"fd00::/7\"))\n\n\tp := firewall.Packet{\n\t\tLocalAddr:  netip.MustParseAddr(\"fd12::34\"),\n\t\tRemoteAddr: netip.MustParseAddr(\"fd12::34\"),\n\t\tLocalPort:  1,\n\t\tRemotePort: 1,\n\t\tProtocol:   firewall.ProtoUDP,\n\t\tFragment:   false,\n\t}\n\n\tnetwork := netip.MustParsePrefix(\"fd12::34/120\")\n\tc := cert.CachedCertificate{\n\t\tCertificate: &dummyCert{\n\t\t\tname:     \"host-owner\",\n\t\t\tnetworks: []netip.Prefix{network},\n\t\t},\n\t}\n\th := HostInfo{\n\t\tConnectionState: &ConnectionState{\n\t\t\tpeerCert: &c,\n\t\t},\n\t\tvpnAddrs: []netip.Addr{network.Addr()},\n\t}\n\th.buildNetworks(myVpnNetworksTable, c.Certificate)\n\n\t// Test a remote address match\n\tfw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)\n\tcp := cert.NewCAPool()\n\trequire.NoError(t, fw.AddRule(true, firewall.ProtoAny, 1, 1, []string{}, \"\", \"fd12::34/120\", \"\", \"\", \"\"))\n\trequire.NoError(t, fw.Drop(p, true, &h, cp, nil))\n}\n\nfunc TestFirewall_DropConntrackReload(t *testing.T) {\n\tl := test.NewLogger()\n\tob := &bytes.Buffer{}\n\tl.SetOutput(ob)\n\tmyVpnNetworksTable := new(bart.Lite)\n\tmyVpnNetworksTable.Insert(netip.MustParsePrefix(\"1.1.1.1/8\"))\n\n\tp := firewall.Packet{\n\t\tLocalAddr:  netip.MustParseAddr(\"1.2.3.4\"),\n\t\tRemoteAddr: netip.MustParseAddr(\"1.2.3.4\"),\n\t\tLocalPort:  10,\n\t\tRemotePort: 90,\n\t\tProtocol:   firewall.ProtoUDP,\n\t\tFragment:   false,\n\t}\n\tnetwork := netip.MustParsePrefix(\"1.2.3.4/24\")\n\n\tc := cert.CachedCertificate{\n\t\tCertificate: &dummyCert{\n\t\t\tname:     \"host1\",\n\t\t\tnetworks: []netip.Prefix{network},\n\t\t\tgroups:   []string{\"default-group\"},\n\t\t\tissuer:   \"signer-shasum\",\n\t\t},\n\t\tInvertedGroups: map[string]struct{}{\"default-group\": {}},\n\t}\n\th := HostInfo{\n\t\tConnectionState: &ConnectionState{\n\t\t\tpeerCert: &c,\n\t\t},\n\t\tvpnAddrs: []netip.Addr{network.Addr()},\n\t}\n\th.buildNetworks(myVpnNetworksTable, c.Certificate)\n\n\tfw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)\n\trequire.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{\"any\"}, \"\", \"\", \"\", \"\", \"\"))\n\tcp := cert.NewCAPool()\n\n\t// Drop outbound\n\tassert.Equal(t, fw.Drop(p, false, &h, cp, nil), ErrNoMatchingRule)\n\t// Allow inbound\n\tresetConntrack(fw)\n\trequire.NoError(t, fw.Drop(p, true, &h, cp, nil))\n\t// Allow outbound because conntrack\n\trequire.NoError(t, fw.Drop(p, false, &h, cp, nil))\n\n\toldFw := fw\n\tfw = NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)\n\trequire.NoError(t, fw.AddRule(true, firewall.ProtoAny, 10, 10, []string{\"any\"}, \"\", \"\", \"\", \"\", \"\"))\n\tfw.Conntrack = oldFw.Conntrack\n\tfw.rulesVersion = oldFw.rulesVersion + 1\n\n\t// Allow outbound because conntrack and new rules allow port 10\n\trequire.NoError(t, fw.Drop(p, false, &h, cp, nil))\n\n\toldFw = fw\n\tfw = NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)\n\trequire.NoError(t, fw.AddRule(true, firewall.ProtoAny, 11, 11, []string{\"any\"}, \"\", \"\", \"\", \"\", \"\"))\n\tfw.Conntrack = oldFw.Conntrack\n\tfw.rulesVersion = oldFw.rulesVersion + 1\n\n\t// Drop outbound because conntrack doesn't match new ruleset\n\tassert.Equal(t, fw.Drop(p, false, &h, cp, nil), ErrNoMatchingRule)\n}\n\nfunc TestFirewall_ICMPPortBehavior(t *testing.T) {\n\tl := test.NewLogger()\n\tob := &bytes.Buffer{}\n\tl.SetOutput(ob)\n\tmyVpnNetworksTable := new(bart.Lite)\n\tmyVpnNetworksTable.Insert(netip.MustParsePrefix(\"1.1.1.1/8\"))\n\n\tnetwork := netip.MustParsePrefix(\"1.2.3.4/24\")\n\n\tc := cert.CachedCertificate{\n\t\tCertificate: &dummyCert{\n\t\t\tname:     \"host1\",\n\t\t\tnetworks: []netip.Prefix{network},\n\t\t\tgroups:   []string{\"default-group\"},\n\t\t\tissuer:   \"signer-shasum\",\n\t\t},\n\t\tInvertedGroups: map[string]struct{}{\"default-group\": {}},\n\t}\n\th := HostInfo{\n\t\tConnectionState: &ConnectionState{\n\t\t\tpeerCert: &c,\n\t\t},\n\t\tvpnAddrs: []netip.Addr{network.Addr()},\n\t}\n\th.buildNetworks(myVpnNetworksTable, c.Certificate)\n\n\tcp := cert.NewCAPool()\n\n\ttempl := firewall.Packet{\n\t\tLocalAddr:  netip.MustParseAddr(\"1.2.3.4\"),\n\t\tRemoteAddr: netip.MustParseAddr(\"1.2.3.4\"),\n\t\tProtocol:   firewall.ProtoICMP,\n\t\tFragment:   false,\n\t}\n\n\tt.Run(\"ICMP allowed\", func(t *testing.T) {\n\t\tfw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)\n\t\trequire.NoError(t, fw.AddRule(true, firewall.ProtoICMP, 0, 0, []string{\"any\"}, \"\", \"\", \"\", \"\", \"\"))\n\t\tt.Run(\"zero ports\", func(t *testing.T) {\n\t\t\tp := templ.Copy()\n\t\t\tp.LocalPort = 0\n\t\t\tp.RemotePort = 0\n\t\t\t// Drop outbound\n\t\t\tassert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)\n\t\t\t// Allow inbound\n\t\t\tresetConntrack(fw)\n\t\t\trequire.NoError(t, fw.Drop(*p, true, &h, cp, nil))\n\t\t\t//now also allow outbound\n\t\t\trequire.NoError(t, fw.Drop(*p, false, &h, cp, nil))\n\t\t})\n\n\t\tt.Run(\"nonzero ports\", func(t *testing.T) {\n\t\t\tp := templ.Copy()\n\t\t\tp.LocalPort = 0xabcd\n\t\t\tp.RemotePort = 0x1234\n\t\t\t// Drop outbound\n\t\t\tassert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)\n\t\t\t// Allow inbound\n\t\t\tresetConntrack(fw)\n\t\t\trequire.NoError(t, fw.Drop(*p, true, &h, cp, nil))\n\t\t\t//now also allow outbound\n\t\t\trequire.NoError(t, fw.Drop(*p, false, &h, cp, nil))\n\t\t})\n\t})\n\n\tt.Run(\"Any proto, some ports allowed\", func(t *testing.T) {\n\t\tfw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)\n\t\trequire.NoError(t, fw.AddRule(true, firewall.ProtoAny, 80, 444, []string{\"any\"}, \"\", \"\", \"\", \"\", \"\"))\n\t\tt.Run(\"zero ports, still blocked\", func(t *testing.T) {\n\t\t\tp := templ.Copy()\n\t\t\tp.LocalPort = 0\n\t\t\tp.RemotePort = 0\n\t\t\t// Drop outbound\n\t\t\tassert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)\n\t\t\t// Allow inbound\n\t\t\tresetConntrack(fw)\n\t\t\tassert.Equal(t, fw.Drop(*p, true, &h, cp, nil), ErrNoMatchingRule)\n\t\t\t//now also allow outbound\n\t\t\tassert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)\n\t\t})\n\n\t\tt.Run(\"nonzero ports, still blocked\", func(t *testing.T) {\n\t\t\tp := templ.Copy()\n\t\t\tp.LocalPort = 0xabcd\n\t\t\tp.RemotePort = 0x1234\n\t\t\t// Drop outbound\n\t\t\tassert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)\n\t\t\t// Allow inbound\n\t\t\tresetConntrack(fw)\n\t\t\tassert.Equal(t, fw.Drop(*p, true, &h, cp, nil), ErrNoMatchingRule)\n\t\t\t//now also allow outbound\n\t\t\tassert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)\n\t\t})\n\n\t\tt.Run(\"nonzero, matching ports, still blocked\", func(t *testing.T) {\n\t\t\tp := templ.Copy()\n\t\t\tp.LocalPort = 80\n\t\t\tp.RemotePort = 80\n\t\t\t// Drop outbound\n\t\t\tassert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)\n\t\t\t// Allow inbound\n\t\t\tresetConntrack(fw)\n\t\t\tassert.Equal(t, fw.Drop(*p, true, &h, cp, nil), ErrNoMatchingRule)\n\t\t\t//now also allow outbound\n\t\t\tassert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)\n\t\t})\n\t})\n\tt.Run(\"Any proto, any port\", func(t *testing.T) {\n\t\tfw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)\n\t\trequire.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{\"any\"}, \"\", \"\", \"\", \"\", \"\"))\n\t\tt.Run(\"zero ports, allowed\", func(t *testing.T) {\n\t\t\tresetConntrack(fw)\n\t\t\tp := templ.Copy()\n\t\t\tp.LocalPort = 0\n\t\t\tp.RemotePort = 0\n\t\t\t// Drop outbound\n\t\t\tassert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)\n\t\t\t// Allow inbound\n\t\t\tresetConntrack(fw)\n\t\t\trequire.NoError(t, fw.Drop(*p, true, &h, cp, nil))\n\t\t\t//now also allow outbound\n\t\t\trequire.NoError(t, fw.Drop(*p, false, &h, cp, nil))\n\t\t})\n\n\t\tt.Run(\"nonzero ports, allowed\", func(t *testing.T) {\n\t\t\tresetConntrack(fw)\n\t\t\tp := templ.Copy()\n\t\t\tp.LocalPort = 0xabcd\n\t\t\tp.RemotePort = 0x1234\n\t\t\t// Drop outbound\n\t\t\tassert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)\n\t\t\t// Allow inbound\n\t\t\tresetConntrack(fw)\n\t\t\trequire.NoError(t, fw.Drop(*p, true, &h, cp, nil))\n\t\t\t//now also allow outbound\n\t\t\trequire.NoError(t, fw.Drop(*p, false, &h, cp, nil))\n\t\t\t//different ID is blocked\n\t\t\tp.RemotePort++\n\t\t\trequire.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)\n\t\t})\n\t})\n\n}\n\nfunc TestFirewall_DropIPSpoofing(t *testing.T) {\n\tl := test.NewLogger()\n\tob := &bytes.Buffer{}\n\tl.SetOutput(ob)\n\tmyVpnNetworksTable := new(bart.Lite)\n\tmyVpnNetworksTable.Insert(netip.MustParsePrefix(\"192.0.2.1/24\"))\n\n\tc := cert.CachedCertificate{\n\t\tCertificate: &dummyCert{\n\t\t\tname:     \"host-owner\",\n\t\t\tnetworks: []netip.Prefix{netip.MustParsePrefix(\"192.0.2.1/24\")},\n\t\t},\n\t}\n\n\tc1 := cert.CachedCertificate{\n\t\tCertificate: &dummyCert{\n\t\t\tname:           \"host\",\n\t\t\tnetworks:       []netip.Prefix{netip.MustParsePrefix(\"192.0.2.2/24\")},\n\t\t\tunsafeNetworks: []netip.Prefix{netip.MustParsePrefix(\"198.51.100.0/24\")},\n\t\t},\n\t}\n\th1 := HostInfo{\n\t\tConnectionState: &ConnectionState{\n\t\t\tpeerCert: &c1,\n\t\t},\n\t\tvpnAddrs: []netip.Addr{c1.Certificate.Networks()[0].Addr()},\n\t}\n\th1.buildNetworks(myVpnNetworksTable, c1.Certificate)\n\n\tfw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)\n\n\trequire.NoError(t, fw.AddRule(true, firewall.ProtoAny, 1, 1, []string{}, \"\", \"\", \"\", \"\", \"\"))\n\tcp := cert.NewCAPool()\n\n\t// Packet spoofed by `c1`. Note that the remote addr is not a valid one.\n\tp := firewall.Packet{\n\t\tLocalAddr:  netip.MustParseAddr(\"192.0.2.1\"),\n\t\tRemoteAddr: netip.MustParseAddr(\"192.0.2.3\"),\n\t\tLocalPort:  1,\n\t\tRemotePort: 1,\n\t\tProtocol:   firewall.ProtoUDP,\n\t\tFragment:   false,\n\t}\n\tassert.Equal(t, fw.Drop(p, true, &h1, cp, nil), ErrInvalidRemoteIP)\n}\n\nfunc BenchmarkLookup(b *testing.B) {\n\tml := func(m map[string]struct{}, a [][]string) {\n\t\tfor n := 0; n < b.N; n++ {\n\t\t\tfor _, sg := range a {\n\t\t\t\tfound := false\n\n\t\t\t\tfor _, g := range sg {\n\t\t\t\t\tif _, ok := m[g]; !ok {\n\t\t\t\t\t\tfound = false\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\n\t\t\t\t\tfound = true\n\t\t\t\t}\n\n\t\t\t\tif found {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tb.Run(\"array to map best\", func(b *testing.B) {\n\t\tm := map[string]struct{}{\n\t\t\t\"1ne\": {},\n\t\t\t\"2wo\": {},\n\t\t\t\"3hr\": {},\n\t\t\t\"4ou\": {},\n\t\t\t\"5iv\": {},\n\t\t\t\"6ix\": {},\n\t\t}\n\n\t\ta := [][]string{\n\t\t\t{\"1ne\", \"2wo\", \"3hr\", \"4ou\", \"5iv\", \"6ix\"},\n\t\t\t{\"one\", \"2wo\", \"3hr\", \"4ou\", \"5iv\", \"6ix\"},\n\t\t\t{\"one\", \"two\", \"3hr\", \"4ou\", \"5iv\", \"6ix\"},\n\t\t\t{\"one\", \"two\", \"thr\", \"4ou\", \"5iv\", \"6ix\"},\n\t\t\t{\"one\", \"two\", \"thr\", \"fou\", \"5iv\", \"6ix\"},\n\t\t\t{\"one\", \"two\", \"thr\", \"fou\", \"fiv\", \"6ix\"},\n\t\t\t{\"one\", \"two\", \"thr\", \"fou\", \"fiv\", \"six\"},\n\t\t}\n\n\t\tfor n := 0; n < b.N; n++ {\n\t\t\tml(m, a)\n\t\t}\n\t})\n\n\tb.Run(\"array to map worst\", func(b *testing.B) {\n\t\tm := map[string]struct{}{\n\t\t\t\"one\": {},\n\t\t\t\"two\": {},\n\t\t\t\"thr\": {},\n\t\t\t\"fou\": {},\n\t\t\t\"fiv\": {},\n\t\t\t\"six\": {},\n\t\t}\n\n\t\ta := [][]string{\n\t\t\t{\"1ne\", \"2wo\", \"3hr\", \"4ou\", \"5iv\", \"6ix\"},\n\t\t\t{\"one\", \"2wo\", \"3hr\", \"4ou\", \"5iv\", \"6ix\"},\n\t\t\t{\"one\", \"two\", \"3hr\", \"4ou\", \"5iv\", \"6ix\"},\n\t\t\t{\"one\", \"two\", \"thr\", \"4ou\", \"5iv\", \"6ix\"},\n\t\t\t{\"one\", \"two\", \"thr\", \"fou\", \"5iv\", \"6ix\"},\n\t\t\t{\"one\", \"two\", \"thr\", \"fou\", \"fiv\", \"6ix\"},\n\t\t\t{\"one\", \"two\", \"thr\", \"fou\", \"fiv\", \"six\"},\n\t\t}\n\n\t\tfor n := 0; n < b.N; n++ {\n\t\t\tml(m, a)\n\t\t}\n\t})\n}\n\nfunc Test_parsePort(t *testing.T) {\n\t_, _, err := parsePort(\"\")\n\trequire.EqualError(t, err, \"was not a number; ``\")\n\n\t_, _, err = parsePort(\"  \")\n\trequire.EqualError(t, err, \"was not a number; `  `\")\n\n\t_, _, err = parsePort(\"-\")\n\trequire.EqualError(t, err, \"appears to be a range but could not be parsed; `-`\")\n\n\t_, _, err = parsePort(\" - \")\n\trequire.EqualError(t, err, \"appears to be a range but could not be parsed; ` - `\")\n\n\t_, _, err = parsePort(\"a-b\")\n\trequire.EqualError(t, err, \"beginning range was not a number; `a`\")\n\n\t_, _, err = parsePort(\"1-b\")\n\trequire.EqualError(t, err, \"ending range was not a number; `b`\")\n\n\ts, e, err := parsePort(\" 1 - 2    \")\n\tassert.Equal(t, int32(1), s)\n\tassert.Equal(t, int32(2), e)\n\trequire.NoError(t, err)\n\n\ts, e, err = parsePort(\"0-1\")\n\tassert.Equal(t, int32(0), s)\n\tassert.Equal(t, int32(0), e)\n\trequire.NoError(t, err)\n\n\ts, e, err = parsePort(\"9919\")\n\tassert.Equal(t, int32(9919), s)\n\tassert.Equal(t, int32(9919), e)\n\trequire.NoError(t, err)\n\n\ts, e, err = parsePort(\"any\")\n\tassert.Equal(t, int32(0), s)\n\tassert.Equal(t, int32(0), e)\n\trequire.NoError(t, err)\n}\n\nfunc TestNewFirewallFromConfig(t *testing.T) {\n\tl := test.NewLogger()\n\t// Test a bad rule definition\n\tc := &dummyCert{}\n\tcs, err := newCertState(cert.Version2, nil, c, false, cert.Curve_CURVE25519, nil)\n\trequire.NoError(t, err)\n\n\tconf := config.NewC(l)\n\tconf.Settings[\"firewall\"] = map[string]any{\"outbound\": \"asdf\"}\n\t_, err = NewFirewallFromConfig(l, cs, conf)\n\trequire.EqualError(t, err, \"firewall.outbound failed to parse, should be an array of rules\")\n\n\t// Test both port and code\n\tconf = config.NewC(l)\n\tconf.Settings[\"firewall\"] = map[string]any{\"outbound\": []any{map[string]any{\"port\": \"1\", \"code\": \"2\"}}}\n\t_, err = NewFirewallFromConfig(l, cs, conf)\n\trequire.EqualError(t, err, \"firewall.outbound rule #0; only one of port or code should be provided\")\n\n\t// Test missing host, group, cidr, ca_name and ca_sha\n\tconf = config.NewC(l)\n\tconf.Settings[\"firewall\"] = map[string]any{\"outbound\": []any{map[string]any{}}}\n\t_, err = NewFirewallFromConfig(l, cs, conf)\n\trequire.EqualError(t, err, \"firewall.outbound rule #0; at least one of host, group, cidr, local_cidr, ca_name, or ca_sha must be provided\")\n\n\t// Test code/port error\n\tconf = config.NewC(l)\n\tconf.Settings[\"firewall\"] = map[string]any{\"outbound\": []any{map[string]any{\"code\": \"a\", \"host\": \"testh\", \"proto\": \"any\"}}}\n\t_, err = NewFirewallFromConfig(l, cs, conf)\n\trequire.EqualError(t, err, \"firewall.outbound rule #0; code was not a number; `a`\")\n\n\tconf.Settings[\"firewall\"] = map[string]any{\"outbound\": []any{map[string]any{\"port\": \"a\", \"host\": \"testh\", \"proto\": \"any\"}}}\n\t_, err = NewFirewallFromConfig(l, cs, conf)\n\trequire.EqualError(t, err, \"firewall.outbound rule #0; port was not a number; `a`\")\n\n\t// Test proto error\n\tconf = config.NewC(l)\n\tconf.Settings[\"firewall\"] = map[string]any{\"outbound\": []any{map[string]any{\"code\": \"1\", \"host\": \"testh\"}}}\n\t_, err = NewFirewallFromConfig(l, cs, conf)\n\trequire.EqualError(t, err, \"firewall.outbound rule #0; proto was not understood; ``\")\n\n\t// Test cidr parse error\n\tconf = config.NewC(l)\n\tconf.Settings[\"firewall\"] = map[string]any{\"outbound\": []any{map[string]any{\"code\": \"1\", \"cidr\": \"testh\", \"proto\": \"any\"}}}\n\t_, err = NewFirewallFromConfig(l, cs, conf)\n\trequire.EqualError(t, err, \"firewall.outbound rule #0; cidr did not parse; netip.ParsePrefix(\\\"testh\\\"): no '/'\")\n\n\t// Test local_cidr parse error\n\tconf = config.NewC(l)\n\tconf.Settings[\"firewall\"] = map[string]any{\"outbound\": []any{map[string]any{\"code\": \"1\", \"local_cidr\": \"testh\", \"proto\": \"any\"}}}\n\t_, err = NewFirewallFromConfig(l, cs, conf)\n\trequire.EqualError(t, err, \"firewall.outbound rule #0; local_cidr did not parse; netip.ParsePrefix(\\\"testh\\\"): no '/'\")\n\n\t// Test both group and groups\n\tconf = config.NewC(l)\n\tconf.Settings[\"firewall\"] = map[string]any{\"inbound\": []any{map[string]any{\"port\": \"1\", \"proto\": \"any\", \"group\": \"a\", \"groups\": []string{\"b\", \"c\"}}}}\n\t_, err = NewFirewallFromConfig(l, cs, conf)\n\trequire.EqualError(t, err, \"firewall.inbound rule #0; only one of group or groups should be defined, both provided\")\n}\n\nfunc TestAddFirewallRulesFromConfig(t *testing.T) {\n\tl := test.NewLogger()\n\t// Test adding tcp rule\n\tconf := config.NewC(l)\n\tmf := &mockFirewall{}\n\tconf.Settings[\"firewall\"] = map[string]any{\"outbound\": []any{map[string]any{\"port\": \"1\", \"proto\": \"tcp\", \"host\": \"a\"}}}\n\trequire.NoError(t, AddFirewallRulesFromConfig(l, false, conf, mf))\n\tassert.Equal(t, addRuleCall{incoming: false, proto: firewall.ProtoTCP, startPort: 1, endPort: 1, groups: nil, host: \"a\", ip: \"\", localIp: \"\"}, mf.lastCall)\n\n\t// Test adding udp rule\n\tconf = config.NewC(l)\n\tmf = &mockFirewall{}\n\tconf.Settings[\"firewall\"] = map[string]any{\"outbound\": []any{map[string]any{\"port\": \"1\", \"proto\": \"udp\", \"host\": \"a\"}}}\n\trequire.NoError(t, AddFirewallRulesFromConfig(l, false, conf, mf))\n\tassert.Equal(t, addRuleCall{incoming: false, proto: firewall.ProtoUDP, startPort: 1, endPort: 1, groups: nil, host: \"a\", ip: \"\", localIp: \"\"}, mf.lastCall)\n\n\t// Test adding icmp rule\n\tconf = config.NewC(l)\n\tmf = &mockFirewall{}\n\tconf.Settings[\"firewall\"] = map[string]any{\"outbound\": []any{map[string]any{\"port\": \"1\", \"proto\": \"icmp\", \"host\": \"a\"}}}\n\trequire.NoError(t, AddFirewallRulesFromConfig(l, false, conf, mf))\n\tassert.Equal(t, addRuleCall{incoming: false, proto: firewall.ProtoICMP, startPort: firewall.PortAny, endPort: firewall.PortAny, groups: nil, host: \"a\", ip: \"\", localIp: \"\"}, mf.lastCall)\n\n\t// Test adding icmp rule no port\n\tconf = config.NewC(l)\n\tmf = &mockFirewall{}\n\tconf.Settings[\"firewall\"] = map[string]any{\"outbound\": []any{map[string]any{\"proto\": \"icmp\", \"host\": \"a\"}}}\n\trequire.NoError(t, AddFirewallRulesFromConfig(l, false, conf, mf))\n\tassert.Equal(t, addRuleCall{incoming: false, proto: firewall.ProtoICMP, startPort: firewall.PortAny, endPort: firewall.PortAny, groups: nil, host: \"a\", ip: \"\", localIp: \"\"}, mf.lastCall)\n\n\t// Test adding any rule\n\tconf = config.NewC(l)\n\tmf = &mockFirewall{}\n\tconf.Settings[\"firewall\"] = map[string]any{\"inbound\": []any{map[string]any{\"port\": \"1\", \"proto\": \"any\", \"host\": \"a\"}}}\n\trequire.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))\n\tassert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, host: \"a\", ip: \"\", localIp: \"\"}, mf.lastCall)\n\n\t// Test adding rule with cidr\n\tcidr := netip.MustParsePrefix(\"10.0.0.0/8\")\n\tconf = config.NewC(l)\n\tmf = &mockFirewall{}\n\tconf.Settings[\"firewall\"] = map[string]any{\"inbound\": []any{map[string]any{\"port\": \"1\", \"proto\": \"any\", \"cidr\": cidr.String()}}}\n\trequire.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))\n\tassert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: cidr.String(), localIp: \"\"}, mf.lastCall)\n\n\t// Test adding rule with local_cidr\n\tconf = config.NewC(l)\n\tmf = &mockFirewall{}\n\tconf.Settings[\"firewall\"] = map[string]any{\"inbound\": []any{map[string]any{\"port\": \"1\", \"proto\": \"any\", \"local_cidr\": cidr.String()}}}\n\trequire.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))\n\tassert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: \"\", localIp: cidr.String()}, mf.lastCall)\n\n\t// Test adding rule with cidr ipv6\n\tcidr6 := netip.MustParsePrefix(\"fd00::/8\")\n\tconf = config.NewC(l)\n\tmf = &mockFirewall{}\n\tconf.Settings[\"firewall\"] = map[string]any{\"inbound\": []any{map[string]any{\"port\": \"1\", \"proto\": \"any\", \"cidr\": cidr6.String()}}}\n\trequire.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))\n\tassert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: cidr6.String(), localIp: \"\"}, mf.lastCall)\n\n\t// Test adding rule with any cidr\n\tconf = config.NewC(l)\n\tmf = &mockFirewall{}\n\tconf.Settings[\"firewall\"] = map[string]any{\"inbound\": []any{map[string]any{\"port\": \"1\", \"proto\": \"any\", \"cidr\": \"any\"}}}\n\trequire.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))\n\tassert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: \"any\", localIp: \"\"}, mf.lastCall)\n\n\t// Test adding rule with junk cidr\n\tconf = config.NewC(l)\n\tmf = &mockFirewall{}\n\tconf.Settings[\"firewall\"] = map[string]any{\"inbound\": []any{map[string]any{\"port\": \"1\", \"proto\": \"any\", \"cidr\": \"junk/junk\"}}}\n\trequire.EqualError(t, AddFirewallRulesFromConfig(l, true, conf, mf), \"firewall.inbound rule #0; cidr did not parse; netip.ParsePrefix(\\\"junk/junk\\\"): ParseAddr(\\\"junk\\\"): unable to parse IP\")\n\n\t// Test adding rule with local_cidr ipv6\n\tconf = config.NewC(l)\n\tmf = &mockFirewall{}\n\tconf.Settings[\"firewall\"] = map[string]any{\"inbound\": []any{map[string]any{\"port\": \"1\", \"proto\": \"any\", \"local_cidr\": cidr6.String()}}}\n\trequire.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))\n\tassert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: \"\", localIp: cidr6.String()}, mf.lastCall)\n\n\t// Test adding rule with any local_cidr\n\tconf = config.NewC(l)\n\tmf = &mockFirewall{}\n\tconf.Settings[\"firewall\"] = map[string]any{\"inbound\": []any{map[string]any{\"port\": \"1\", \"proto\": \"any\", \"local_cidr\": \"any\"}}}\n\trequire.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))\n\tassert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, localIp: \"any\"}, mf.lastCall)\n\n\t// Test adding rule with junk local_cidr\n\tconf = config.NewC(l)\n\tmf = &mockFirewall{}\n\tconf.Settings[\"firewall\"] = map[string]any{\"inbound\": []any{map[string]any{\"port\": \"1\", \"proto\": \"any\", \"local_cidr\": \"junk/junk\"}}}\n\trequire.EqualError(t, AddFirewallRulesFromConfig(l, true, conf, mf), \"firewall.inbound rule #0; local_cidr did not parse; netip.ParsePrefix(\\\"junk/junk\\\"): ParseAddr(\\\"junk\\\"): unable to parse IP\")\n\n\t// Test adding rule with ca_sha\n\tconf = config.NewC(l)\n\tmf = &mockFirewall{}\n\tconf.Settings[\"firewall\"] = map[string]any{\"inbound\": []any{map[string]any{\"port\": \"1\", \"proto\": \"any\", \"ca_sha\": \"12312313123\"}}}\n\trequire.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))\n\tassert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: \"\", localIp: \"\", caSha: \"12312313123\"}, mf.lastCall)\n\n\t// Test adding rule with ca_name\n\tconf = config.NewC(l)\n\tmf = &mockFirewall{}\n\tconf.Settings[\"firewall\"] = map[string]any{\"inbound\": []any{map[string]any{\"port\": \"1\", \"proto\": \"any\", \"ca_name\": \"root01\"}}}\n\trequire.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))\n\tassert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: \"\", localIp: \"\", caName: \"root01\"}, mf.lastCall)\n\n\t// Test single group\n\tconf = config.NewC(l)\n\tmf = &mockFirewall{}\n\tconf.Settings[\"firewall\"] = map[string]any{\"inbound\": []any{map[string]any{\"port\": \"1\", \"proto\": \"any\", \"group\": \"a\"}}}\n\trequire.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))\n\tassert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: []string{\"a\"}, ip: \"\", localIp: \"\"}, mf.lastCall)\n\n\t// Test single groups\n\tconf = config.NewC(l)\n\tmf = &mockFirewall{}\n\tconf.Settings[\"firewall\"] = map[string]any{\"inbound\": []any{map[string]any{\"port\": \"1\", \"proto\": \"any\", \"groups\": \"a\"}}}\n\trequire.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))\n\tassert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: []string{\"a\"}, ip: \"\", localIp: \"\"}, mf.lastCall)\n\n\t// Test multiple AND groups\n\tconf = config.NewC(l)\n\tmf = &mockFirewall{}\n\tconf.Settings[\"firewall\"] = map[string]any{\"inbound\": []any{map[string]any{\"port\": \"1\", \"proto\": \"any\", \"groups\": []string{\"a\", \"b\"}}}}\n\trequire.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))\n\tassert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: []string{\"a\", \"b\"}, ip: \"\", localIp: \"\"}, mf.lastCall)\n\n\t// Test Add error\n\tconf = config.NewC(l)\n\tmf = &mockFirewall{}\n\tmf.nextCallReturn = errors.New(\"test error\")\n\tconf.Settings[\"firewall\"] = map[string]any{\"inbound\": []any{map[string]any{\"port\": \"1\", \"proto\": \"any\", \"host\": \"a\"}}}\n\trequire.EqualError(t, AddFirewallRulesFromConfig(l, true, conf, mf), \"firewall.inbound rule #0; `test error`\")\n}\n\nfunc TestFirewall_convertRule(t *testing.T) {\n\tl := test.NewLogger()\n\tob := &bytes.Buffer{}\n\tl.SetOutput(ob)\n\n\t// Ensure group array of 1 is converted and a warning is printed\n\tc := map[string]any{\n\t\t\"group\": []any{\"group1\"},\n\t}\n\n\tr, err := convertRule(l, c, \"test\", 1)\n\tassert.Contains(t, ob.String(), \"test rule #1; group was an array with a single value, converting to simple value\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, []string{\"group1\"}, r.Groups)\n\n\t// Ensure group array of > 1 is errord\n\tob.Reset()\n\tc = map[string]any{\n\t\t\"group\": []any{\"group1\", \"group2\"},\n\t}\n\n\tr, err = convertRule(l, c, \"test\", 1)\n\tassert.Empty(t, ob.String())\n\trequire.Error(t, err, \"group should contain a single value, an array with more than one entry was provided\")\n\n\t// Make sure a well formed group is alright\n\tob.Reset()\n\tc = map[string]any{\n\t\t\"group\": \"group1\",\n\t}\n\n\tr, err = convertRule(l, c, \"test\", 1)\n\trequire.NoError(t, err)\n\tassert.Equal(t, []string{\"group1\"}, r.Groups)\n}\n\nfunc TestFirewall_convertRuleSanity(t *testing.T) {\n\tl := test.NewLogger()\n\tob := &bytes.Buffer{}\n\tl.SetOutput(ob)\n\n\tnoWarningPlease := []map[string]any{\n\t\t{\"group\": \"group1\"},\n\t\t{\"groups\": []any{\"group2\"}},\n\t\t{\"host\": \"bob\"},\n\t\t{\"cidr\": \"1.1.1.1/1\"},\n\t\t{\"groups\": []any{\"group2\"}, \"host\": \"bob\"},\n\t\t{\"cidr\": \"1.1.1.1/1\", \"host\": \"bob\"},\n\t\t{\"groups\": []any{\"group2\"}, \"cidr\": \"1.1.1.1/1\"},\n\t\t{\"groups\": []any{\"group2\"}, \"cidr\": \"1.1.1.1/1\", \"host\": \"bob\"},\n\t}\n\tfor _, c := range noWarningPlease {\n\t\tr, err := convertRule(l, c, \"test\", 1)\n\t\trequire.NoError(t, err)\n\t\trequire.NoError(t, r.sanity(), \"should not generate a sanity warning, %+v\", c)\n\t}\n\n\tyesWarningPlease := []map[string]any{\n\t\t{\"group\": \"group1\"},\n\t\t{\"groups\": []any{\"group2\"}},\n\t\t{\"cidr\": \"1.1.1.1/1\"},\n\t\t{\"groups\": []any{\"group2\"}, \"host\": \"bob\"},\n\t\t{\"cidr\": \"1.1.1.1/1\", \"host\": \"bob\"},\n\t\t{\"groups\": []any{\"group2\"}, \"cidr\": \"1.1.1.1/1\"},\n\t\t{\"groups\": []any{\"group2\"}, \"cidr\": \"1.1.1.1/1\", \"host\": \"bob\"},\n\t}\n\tfor _, c := range yesWarningPlease {\n\t\tc[\"host\"] = \"any\"\n\t\tr, err := convertRule(l, c, \"test\", 1)\n\t\trequire.NoError(t, err)\n\t\terr = r.sanity()\n\t\trequire.Error(t, err, \"I wanted a warning: %+v\", c)\n\t}\n\t//reset the list\n\tyesWarningPlease = []map[string]any{\n\t\t{\"group\": \"group1\"},\n\t\t{\"groups\": []any{\"group2\"}},\n\t\t{\"cidr\": \"1.1.1.1/1\"},\n\t\t{\"groups\": []any{\"group2\"}, \"host\": \"bob\"},\n\t\t{\"cidr\": \"1.1.1.1/1\", \"host\": \"bob\"},\n\t\t{\"groups\": []any{\"group2\"}, \"cidr\": \"1.1.1.1/1\"},\n\t\t{\"groups\": []any{\"group2\"}, \"cidr\": \"1.1.1.1/1\", \"host\": \"bob\"},\n\t}\n\tfor _, c := range yesWarningPlease {\n\t\tr, err := convertRule(l, c, \"test\", 1)\n\t\trequire.NoError(t, err)\n\t\tr.Groups = append(r.Groups, \"any\")\n\t\terr = r.sanity()\n\t\trequire.Error(t, err, \"I wanted a warning: %+v\", c)\n\t}\n}\n\ntype testcase struct {\n\th   *HostInfo\n\tp   firewall.Packet\n\tc   cert.Certificate\n\terr error\n}\n\nfunc (c *testcase) Test(t *testing.T, fw *Firewall) {\n\tt.Helper()\n\tcp := cert.NewCAPool()\n\tresetConntrack(fw)\n\terr := fw.Drop(c.p, true, c.h, cp, nil)\n\tif c.err == nil {\n\t\trequire.NoError(t, err, \"failed to not drop remote address %s\", c.p.RemoteAddr)\n\t} else {\n\t\trequire.ErrorIs(t, c.err, err, \"failed to drop remote address %s\", c.p.RemoteAddr)\n\t}\n}\n\nfunc buildTestCase(setup testsetup, err error, theirPrefixes ...netip.Prefix) testcase {\n\tc1 := dummyCert{\n\t\tname:     \"host1\",\n\t\tnetworks: theirPrefixes,\n\t\tgroups:   []string{\"default-group\"},\n\t\tissuer:   \"signer-shasum\",\n\t}\n\th := HostInfo{\n\t\tConnectionState: &ConnectionState{\n\t\t\tpeerCert: &cert.CachedCertificate{\n\t\t\t\tCertificate:    &c1,\n\t\t\t\tInvertedGroups: map[string]struct{}{\"default-group\": {}},\n\t\t\t},\n\t\t},\n\t\tvpnAddrs: make([]netip.Addr, len(theirPrefixes)),\n\t}\n\tfor i := range theirPrefixes {\n\t\th.vpnAddrs[i] = theirPrefixes[i].Addr()\n\t}\n\th.buildNetworks(setup.myVpnNetworksTable, &c1)\n\tp := firewall.Packet{\n\t\tLocalAddr:  setup.c.Networks()[0].Addr(), //todo?\n\t\tRemoteAddr: theirPrefixes[0].Addr(),\n\t\tLocalPort:  10,\n\t\tRemotePort: 90,\n\t\tProtocol:   firewall.ProtoUDP,\n\t\tFragment:   false,\n\t}\n\treturn testcase{\n\t\th:   &h,\n\t\tp:   p,\n\t\tc:   &c1,\n\t\terr: err,\n\t}\n}\n\ntype testsetup struct {\n\tc                  dummyCert\n\tmyVpnNetworksTable *bart.Lite\n\tfw                 *Firewall\n}\n\nfunc newSetup(t *testing.T, l *logrus.Logger, myPrefixes ...netip.Prefix) testsetup {\n\tc := dummyCert{\n\t\tname:     \"me\",\n\t\tnetworks: myPrefixes,\n\t\tgroups:   []string{\"default-group\"},\n\t\tissuer:   \"signer-shasum\",\n\t}\n\n\treturn newSetupFromCert(t, l, c)\n}\n\nfunc newSetupFromCert(t *testing.T, l *logrus.Logger, c dummyCert) testsetup {\n\tmyVpnNetworksTable := new(bart.Lite)\n\tfor _, prefix := range c.Networks() {\n\t\tmyVpnNetworksTable.Insert(prefix)\n\t}\n\tfw := NewFirewall(l, time.Second, time.Minute, time.Hour, &c)\n\trequire.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{\"any\"}, \"\", \"\", \"\", \"\", \"\"))\n\n\treturn testsetup{\n\t\tc:                  c,\n\t\tfw:                 fw,\n\t\tmyVpnNetworksTable: myVpnNetworksTable,\n\t}\n}\n\nfunc TestFirewall_Drop_EnforceIPMatch(t *testing.T) {\n\tt.Parallel()\n\tl := test.NewLogger()\n\tob := &bytes.Buffer{}\n\tl.SetOutput(ob)\n\n\tmyPrefix := netip.MustParsePrefix(\"1.1.1.1/8\")\n\t// for now, it's okay that these are all \"incoming\", the logic this test tries to check doesn't care about in/out\n\tt.Run(\"allow inbound all matching\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\tsetup := newSetup(t, l, myPrefix)\n\t\ttc := buildTestCase(setup, nil, netip.MustParsePrefix(\"1.2.3.4/24\"))\n\t\ttc.Test(t, setup.fw)\n\t})\n\tt.Run(\"allow inbound local matching\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\tsetup := newSetup(t, l, myPrefix)\n\t\ttc := buildTestCase(setup, ErrInvalidLocalIP, netip.MustParsePrefix(\"1.2.3.4/24\"))\n\t\ttc.p.LocalAddr = netip.MustParseAddr(\"1.2.3.8\")\n\t\ttc.Test(t, setup.fw)\n\t})\n\tt.Run(\"block inbound remote mismatched\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\tsetup := newSetup(t, l, myPrefix)\n\t\ttc := buildTestCase(setup, ErrInvalidRemoteIP, netip.MustParsePrefix(\"1.2.3.4/24\"))\n\t\ttc.p.RemoteAddr = netip.MustParseAddr(\"9.9.9.9\")\n\t\ttc.Test(t, setup.fw)\n\t})\n\tt.Run(\"Block a vpn peer packet\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\tsetup := newSetup(t, l, myPrefix)\n\t\ttc := buildTestCase(setup, ErrPeerRejected, netip.MustParsePrefix(\"2.2.2.2/24\"))\n\t\ttc.Test(t, setup.fw)\n\t})\n\ttwoPrefixes := []netip.Prefix{\n\t\tnetip.MustParsePrefix(\"1.2.3.4/24\"), netip.MustParsePrefix(\"2.2.2.2/24\"),\n\t}\n\tt.Run(\"allow inbound one matching\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\tsetup := newSetup(t, l, myPrefix)\n\t\ttc := buildTestCase(setup, nil, twoPrefixes...)\n\t\ttc.Test(t, setup.fw)\n\t})\n\tt.Run(\"block inbound multimismatch\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\tsetup := newSetup(t, l, myPrefix)\n\t\ttc := buildTestCase(setup, ErrInvalidRemoteIP, twoPrefixes...)\n\t\ttc.p.RemoteAddr = netip.MustParseAddr(\"9.9.9.9\")\n\t\ttc.Test(t, setup.fw)\n\t})\n\tt.Run(\"allow inbound 2nd one matching\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\tsetup2 := newSetup(t, l, netip.MustParsePrefix(\"2.2.2.1/24\"))\n\t\ttc := buildTestCase(setup2, nil, twoPrefixes...)\n\t\ttc.p.RemoteAddr = twoPrefixes[1].Addr()\n\t\ttc.Test(t, setup2.fw)\n\t})\n\tt.Run(\"allow inbound unsafe route\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\tunsafePrefix := netip.MustParsePrefix(\"192.168.0.0/24\")\n\t\tc := dummyCert{\n\t\t\tname:           \"me\",\n\t\t\tnetworks:       []netip.Prefix{myPrefix},\n\t\t\tunsafeNetworks: []netip.Prefix{unsafePrefix},\n\t\t\tgroups:         []string{\"default-group\"},\n\t\t\tissuer:         \"signer-shasum\",\n\t\t}\n\t\tunsafeSetup := newSetupFromCert(t, l, c)\n\t\ttc := buildTestCase(unsafeSetup, nil, twoPrefixes...)\n\t\ttc.p.LocalAddr = netip.MustParseAddr(\"192.168.0.3\")\n\t\ttc.err = ErrNoMatchingRule\n\t\ttc.Test(t, unsafeSetup.fw) //should hit firewall and bounce off\n\t\trequire.NoError(t, unsafeSetup.fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{\"any\"}, \"\", \"\", unsafePrefix.String(), \"\", \"\"))\n\t\ttc.err = nil\n\t\ttc.Test(t, unsafeSetup.fw) //should pass\n\t})\n}\n\ntype addRuleCall struct {\n\tincoming  bool\n\tproto     uint8\n\tstartPort int32\n\tendPort   int32\n\tgroups    []string\n\thost      string\n\tip        string\n\tlocalIp   string\n\tcaName    string\n\tcaSha     string\n}\n\ntype mockFirewall struct {\n\tlastCall       addRuleCall\n\tnextCallReturn error\n}\n\nfunc (mf *mockFirewall) AddRule(incoming bool, proto uint8, startPort int32, endPort int32, groups []string, host string, ip, localIp, caName string, caSha string) error {\n\tmf.lastCall = addRuleCall{\n\t\tincoming:  incoming,\n\t\tproto:     proto,\n\t\tstartPort: startPort,\n\t\tendPort:   endPort,\n\t\tgroups:    groups,\n\t\thost:      host,\n\t\tip:        ip,\n\t\tlocalIp:   localIp,\n\t\tcaName:    caName,\n\t\tcaSha:     caSha,\n\t}\n\n\terr := mf.nextCallReturn\n\tmf.nextCallReturn = nil\n\treturn err\n}\n\nfunc resetConntrack(fw *Firewall) {\n\tfw.Conntrack.Lock()\n\tfw.Conntrack.Conns = map[firewall.Packet]*conn{}\n\tfw.Conntrack.Unlock()\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/slackhq/nebula\n\ngo 1.25\n\nrequire (\n\tdario.cat/mergo v1.0.2\n\tfilippo.io/bigmod v0.1.0\n\tgithub.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be\n\tgithub.com/armon/go-radix v1.0.0\n\tgithub.com/cyberdelia/go-metrics-graphite v0.0.0-20161219230853-39f87cc3b432\n\tgithub.com/flynn/noise v1.1.0\n\tgithub.com/gaissmai/bart v0.26.0\n\tgithub.com/gogo/protobuf v1.3.2\n\tgithub.com/google/gopacket v1.1.19\n\tgithub.com/kardianos/service v1.2.4\n\tgithub.com/miekg/dns v1.1.70\n\tgithub.com/miekg/pkcs11 v1.1.2-0.20231115102856-9078ad6b9d4b\n\tgithub.com/nbrownus/go-metrics-prometheus v0.0.0-20210712211119-974a6260965f\n\tgithub.com/prometheus/client_golang v1.23.2\n\tgithub.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475\n\tgithub.com/sirupsen/logrus v1.9.4\n\tgithub.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e\n\tgithub.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6\n\tgithub.com/stretchr/testify v1.11.1\n\tgithub.com/vishvananda/netlink v1.3.1\n\tgo.yaml.in/yaml/v3 v3.0.4\n\tgolang.org/x/crypto v0.47.0\n\tgolang.org/x/exp v0.0.0-20230725093048-515e97ebf090\n\tgolang.org/x/net v0.49.0\n\tgolang.org/x/sync v0.19.0\n\tgolang.org/x/sys v0.40.0\n\tgolang.org/x/term v0.39.0\n\tgolang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2\n\tgolang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b\n\tgolang.zx2c4.com/wireguard/windows v0.5.3\n\tgoogle.golang.org/protobuf v1.36.11\n\tgopkg.in/yaml.v3 v3.0.1\n\tgvisor.dev/gvisor v0.0.0-20240423190808-9d7a357edefe\n)\n\nrequire (\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/google/btree v1.1.2 // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgithub.com/prometheus/client_model v0.6.2 // indirect\n\tgithub.com/prometheus/common v0.66.1 // indirect\n\tgithub.com/prometheus/procfs v0.16.1 // indirect\n\tgithub.com/vishvananda/netns v0.0.5 // indirect\n\tgo.yaml.in/yaml/v2 v2.4.2 // indirect\n\tgolang.org/x/mod v0.31.0 // indirect\n\tgolang.org/x/time v0.5.0 // indirect\n\tgolang.org/x/tools v0.40.0 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ndario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=\ndario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=\nfilippo.io/bigmod v0.1.0 h1:UNzDk7y9ADKST+axd9skUpBQeW7fG2KrTZyOE4uGQy8=\nfilippo.io/bigmod v0.1.0/go.mod h1:OjOXDNlClLblvXdwgFFOQFJEocLhhtai8vGLy0JCZlI=\ngithub.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=\ngithub.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=\ngithub.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=\ngithub.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=\ngithub.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=\ngithub.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=\ngithub.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=\ngithub.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=\ngithub.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=\ngithub.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=\ngithub.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=\ngithub.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cyberdelia/go-metrics-graphite v0.0.0-20161219230853-39f87cc3b432 h1:M5QgkYacWj0Xs8MhpIK/5uwU02icXpEoSo9sM2aRCps=\ngithub.com/cyberdelia/go-metrics-graphite v0.0.0-20161219230853-39f87cc3b432/go.mod h1:xwIwAxMvYnVrGJPe2FKx5prTrnAjGOD8zvDOnxnrrkM=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg=\ngithub.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag=\ngithub.com/gaissmai/bart v0.26.0 h1:xOZ57E9hJLBiQaSyeZa9wgWhGuzfGACgqp4BE77OkO0=\ngithub.com/gaissmai/bart v0.26.0/go.mod h1:GREWQfTLRWz/c5FTOsIw+KkscuFkIV5t8Rp7Nd1Td5c=\ngithub.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=\ngithub.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=\ngithub.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=\ngithub.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=\ngithub.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=\ngithub.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=\ngithub.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=\ngithub.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=\ngithub.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=\ngithub.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=\ngithub.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=\ngithub.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=\ngithub.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=\ngithub.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=\ngithub.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=\ngithub.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=\ngithub.com/kardianos/service v1.2.4 h1:XNlGtZOYNx2u91urOdg/Kfmc+gfmuIo1Dd3rEi2OgBk=\ngithub.com/kardianos/service v1.2.4/go.mod h1:E4V9ufUuY82F7Ztlu1eN9VXWIQxg8NoLQlmFe0MtrXc=\ngithub.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=\ngithub.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=\ngithub.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=\ngithub.com/miekg/dns v1.1.70 h1:DZ4u2AV35VJxdD9Fo9fIWm119BsQL5cZU1cQ9s0LkqA=\ngithub.com/miekg/dns v1.1.70/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=\ngithub.com/miekg/pkcs11 v1.1.2-0.20231115102856-9078ad6b9d4b h1:J/AzCvg5z0Hn1rqZUJjpbzALUmkKX0Zwbc/i4fw7Sfk=\ngithub.com/miekg/pkcs11 v1.1.2-0.20231115102856-9078ad6b9d4b/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=\ngithub.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=\ngithub.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=\ngithub.com/nbrownus/go-metrics-prometheus v0.0.0-20210712211119-974a6260965f h1:8dM0ilqKL0Uzl42GABzzC4Oqlc3kGRILz0vgoff7nwg=\ngithub.com/nbrownus/go-metrics-prometheus v0.0.0-20210712211119-974a6260965f/go.mod h1:nwPd6pDNId/Xi16qtKrFHrauSwMNuvk+zcjk89wrnlA=\ngithub.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=\ngithub.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=\ngithub.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=\ngithub.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=\ngithub.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=\ngithub.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=\ngithub.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=\ngithub.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=\ngithub.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=\ngithub.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=\ngithub.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=\ngithub.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=\ngithub.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=\ngithub.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=\ngithub.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=\ngithub.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=\ngithub.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=\ngithub.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=\ngithub.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=\ngithub.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=\ngithub.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=\ngithub.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=\ngithub.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=\ngithub.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=\ngithub.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=\ngithub.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=\ngithub.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=\ngithub.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=\ngithub.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=\ngithub.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=\ngithub.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=\ngithub.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 h1:pnnLyeX7o/5aX8qUQ69P/mLojDqwda8hFOCBTmP/6hw=\ngithub.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6/go.mod h1:39R/xuhNgVhi+K0/zst4TLrJrVmbm6LVgl4A0+ZFS5M=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=\ngithub.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=\ngithub.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=\ngithub.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngo.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=\ngo.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=\ngo.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=\ngo.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=\ngolang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=\ngolang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=\ngolang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=\ngolang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY=\ngolang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=\ngolang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=\ngolang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=\ngolang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=\ngolang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=\ngolang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=\ngolang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=\ngolang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=\ngolang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=\ngolang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=\ngolang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=\ngolang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b h1:J1CaxgLerRR5lgx3wnr6L04cJFbWoceSK9JWBdglINo=\ngolang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b/go.mod h1:tqur9LnfstdR9ep2LaJT4lFUl0EjlHtge+gAjmsHUG4=\ngolang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=\ngolang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=\ngoogle.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=\ngoogle.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=\ngopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngvisor.dev/gvisor v0.0.0-20240423190808-9d7a357edefe h1:fre4i6mv4iBuz5lCMOzHD1rH1ljqHWSICFmZRbbgp3g=\ngvisor.dev/gvisor v0.0.0-20240423190808-9d7a357edefe/go.mod h1:sxc3Uvk/vHcd3tj7/DHVBoR5wvWT/MmRq2pj7HRJnwU=\n"
  },
  {
    "path": "handshake_ix.go",
    "content": "package nebula\n\nimport (\n\t\"bytes\"\n\t\"net/netip\"\n\t\"time\"\n\n\t\"github.com/flynn/noise\"\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/cert\"\n\t\"github.com/slackhq/nebula/header\"\n)\n\n// NOISE IX Handshakes\n\n// This function constructs a handshake packet, but does not actually send it\n// Sending is done by the handshake manager\nfunc ixHandshakeStage0(f *Interface, hh *HandshakeHostInfo) bool {\n\terr := f.handshakeManager.allocateIndex(hh)\n\tif err != nil {\n\t\tf.l.WithError(err).WithField(\"vpnAddrs\", hh.hostinfo.vpnAddrs).\n\t\t\tWithField(\"handshake\", m{\"stage\": 0, \"style\": \"ix_psk0\"}).Error(\"Failed to generate index\")\n\t\treturn false\n\t}\n\n\tcs := f.pki.getCertState()\n\tv := cs.initiatingVersion\n\tif hh.initiatingVersionOverride != cert.VersionPre1 {\n\t\tv = hh.initiatingVersionOverride\n\t} else if v < cert.Version2 {\n\t\t// If we're connecting to a v6 address we should encourage use of a V2 cert\n\t\tfor _, a := range hh.hostinfo.vpnAddrs {\n\t\t\tif a.Is6() {\n\t\t\t\tv = cert.Version2\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tcrt := cs.getCertificate(v)\n\tif crt == nil {\n\t\tf.l.WithField(\"vpnAddrs\", hh.hostinfo.vpnAddrs).\n\t\t\tWithField(\"handshake\", m{\"stage\": 0, \"style\": \"ix_psk0\"}).\n\t\t\tWithField(\"certVersion\", v).\n\t\t\tError(\"Unable to handshake with host because no certificate is available\")\n\t\treturn false\n\t}\n\n\tcrtHs := cs.getHandshakeBytes(v)\n\tif crtHs == nil {\n\t\tf.l.WithField(\"vpnAddrs\", hh.hostinfo.vpnAddrs).\n\t\t\tWithField(\"handshake\", m{\"stage\": 0, \"style\": \"ix_psk0\"}).\n\t\t\tWithField(\"certVersion\", v).\n\t\t\tError(\"Unable to handshake with host because no certificate handshake bytes is available\")\n\t\treturn false\n\t}\n\n\tci, err := NewConnectionState(f.l, cs, crt, true, noise.HandshakeIX)\n\tif err != nil {\n\t\tf.l.WithError(err).WithField(\"vpnAddrs\", hh.hostinfo.vpnAddrs).\n\t\t\tWithField(\"handshake\", m{\"stage\": 0, \"style\": \"ix_psk0\"}).\n\t\t\tWithField(\"certVersion\", v).\n\t\t\tError(\"Failed to create connection state\")\n\t\treturn false\n\t}\n\thh.hostinfo.ConnectionState = ci\n\n\ths := &NebulaHandshake{\n\t\tDetails: &NebulaHandshakeDetails{\n\t\t\tInitiatorIndex: hh.hostinfo.localIndexId,\n\t\t\tTime:           uint64(time.Now().UnixNano()),\n\t\t\tCert:           crtHs,\n\t\t\tCertVersion:    uint32(v),\n\t\t},\n\t}\n\n\thsBytes, err := hs.Marshal()\n\tif err != nil {\n\t\tf.l.WithError(err).WithField(\"vpnAddrs\", hh.hostinfo.vpnAddrs).\n\t\t\tWithField(\"certVersion\", v).\n\t\t\tWithField(\"handshake\", m{\"stage\": 0, \"style\": \"ix_psk0\"}).Error(\"Failed to marshal handshake message\")\n\t\treturn false\n\t}\n\n\th := header.Encode(make([]byte, header.Len), header.Version, header.Handshake, header.HandshakeIXPSK0, 0, 1)\n\n\tmsg, _, _, err := ci.H.WriteMessage(h, hsBytes)\n\tif err != nil {\n\t\tf.l.WithError(err).WithField(\"vpnAddrs\", hh.hostinfo.vpnAddrs).\n\t\t\tWithField(\"handshake\", m{\"stage\": 0, \"style\": \"ix_psk0\"}).Error(\"Failed to call noise.WriteMessage\")\n\t\treturn false\n\t}\n\n\t// We are sending handshake packet 1, so we don't expect to receive\n\t// handshake packet 1 from the responder\n\tci.window.Update(f.l, 1)\n\n\thh.hostinfo.HandshakePacket[0] = msg\n\thh.ready = true\n\treturn true\n}\n\nfunc ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H) {\n\tcs := f.pki.getCertState()\n\tcrt := cs.GetDefaultCertificate()\n\tif crt == nil {\n\t\tf.l.WithField(\"from\", via).\n\t\t\tWithField(\"handshake\", m{\"stage\": 0, \"style\": \"ix_psk0\"}).\n\t\t\tWithField(\"certVersion\", cs.initiatingVersion).\n\t\t\tError(\"Unable to handshake with host because no certificate is available\")\n\t\treturn\n\t}\n\n\tci, err := NewConnectionState(f.l, cs, crt, false, noise.HandshakeIX)\n\tif err != nil {\n\t\tf.l.WithError(err).WithField(\"from\", via).\n\t\t\tWithField(\"handshake\", m{\"stage\": 1, \"style\": \"ix_psk0\"}).\n\t\t\tError(\"Failed to create connection state\")\n\t\treturn\n\t}\n\n\t// Mark packet 1 as seen so it doesn't show up as missed\n\tci.window.Update(f.l, 1)\n\n\tmsg, _, _, err := ci.H.ReadMessage(nil, packet[header.Len:])\n\tif err != nil {\n\t\tf.l.WithError(err).WithField(\"from\", via).\n\t\t\tWithField(\"handshake\", m{\"stage\": 1, \"style\": \"ix_psk0\"}).\n\t\t\tError(\"Failed to call noise.ReadMessage\")\n\t\treturn\n\t}\n\n\ths := &NebulaHandshake{}\n\terr = hs.Unmarshal(msg)\n\tif err != nil || hs.Details == nil {\n\t\tf.l.WithError(err).WithField(\"from\", via).\n\t\t\tWithField(\"handshake\", m{\"stage\": 1, \"style\": \"ix_psk0\"}).\n\t\t\tError(\"Failed unmarshal handshake message\")\n\t\treturn\n\t}\n\n\trc, err := cert.Recombine(cert.Version(hs.Details.CertVersion), hs.Details.Cert, ci.H.PeerStatic(), ci.Curve())\n\tif err != nil {\n\t\tf.l.WithError(err).WithField(\"from\", via).\n\t\t\tWithField(\"handshake\", m{\"stage\": 1, \"style\": \"ix_psk0\"}).\n\t\t\tInfo(\"Handshake did not contain a certificate\")\n\t\treturn\n\t}\n\n\tremoteCert, err := f.pki.GetCAPool().VerifyCertificate(time.Now(), rc)\n\tif err != nil {\n\t\tfp, fperr := rc.Fingerprint()\n\t\tif fperr != nil {\n\t\t\tfp = \"<error generating certificate fingerprint>\"\n\t\t}\n\n\t\te := f.l.WithError(err).WithField(\"from\", via).\n\t\t\tWithField(\"handshake\", m{\"stage\": 1, \"style\": \"ix_psk0\"}).\n\t\t\tWithField(\"certVpnNetworks\", rc.Networks()).\n\t\t\tWithField(\"certFingerprint\", fp)\n\n\t\tif f.l.Level >= logrus.DebugLevel {\n\t\t\te = e.WithField(\"cert\", rc)\n\t\t}\n\n\t\te.Info(\"Invalid certificate from host\")\n\t\treturn\n\t}\n\n\tif !bytes.Equal(remoteCert.Certificate.PublicKey(), ci.H.PeerStatic()) {\n\t\tf.l.WithField(\"from\", via).\n\t\t\tWithField(\"handshake\", m{\"stage\": 1, \"style\": \"ix_psk0\"}).\n\t\t\tWithField(\"cert\", remoteCert).Info(\"public key mismatch between certificate and handshake\")\n\t\treturn\n\t}\n\n\tif remoteCert.Certificate.Version() != ci.myCert.Version() {\n\t\t// We started off using the wrong certificate version, lets see if we can match the version that was sent to us\n\t\tmyCertOtherVersion := cs.getCertificate(remoteCert.Certificate.Version())\n\t\tif myCertOtherVersion == nil {\n\t\t\tif f.l.Level >= logrus.DebugLevel {\n\t\t\t\tf.l.WithError(err).WithFields(m{\n\t\t\t\t\t\"from\":      via,\n\t\t\t\t\t\"handshake\": m{\"stage\": 1, \"style\": \"ix_psk0\"},\n\t\t\t\t\t\"cert\":      remoteCert,\n\t\t\t\t}).Debug(\"Might be unable to handshake with host due to missing certificate version\")\n\t\t\t}\n\t\t} else {\n\t\t\t// Record the certificate we are actually using\n\t\t\tci.myCert = myCertOtherVersion\n\t\t}\n\t}\n\n\tif len(remoteCert.Certificate.Networks()) == 0 {\n\t\tf.l.WithError(err).WithField(\"from\", via).\n\t\t\tWithField(\"cert\", remoteCert).\n\t\t\tWithField(\"handshake\", m{\"stage\": 1, \"style\": \"ix_psk0\"}).\n\t\t\tInfo(\"No networks in certificate\")\n\t\treturn\n\t}\n\n\tcertName := remoteCert.Certificate.Name()\n\tcertVersion := remoteCert.Certificate.Version()\n\tfingerprint := remoteCert.Fingerprint\n\tissuer := remoteCert.Certificate.Issuer()\n\tvpnNetworks := remoteCert.Certificate.Networks()\n\n\tanyVpnAddrsInCommon := false\n\tvpnAddrs := make([]netip.Addr, len(vpnNetworks))\n\tfor i, network := range vpnNetworks {\n\t\tif f.myVpnAddrsTable.Contains(network.Addr()) {\n\t\t\tf.l.WithField(\"vpnNetworks\", vpnNetworks).WithField(\"from\", via).\n\t\t\t\tWithField(\"certName\", certName).\n\t\t\t\tWithField(\"certVersion\", certVersion).\n\t\t\t\tWithField(\"fingerprint\", fingerprint).\n\t\t\t\tWithField(\"issuer\", issuer).\n\t\t\t\tWithField(\"handshake\", m{\"stage\": 1, \"style\": \"ix_psk0\"}).Error(\"Refusing to handshake with myself\")\n\t\t\treturn\n\t\t}\n\t\tvpnAddrs[i] = network.Addr()\n\t\tif f.myVpnNetworksTable.Contains(network.Addr()) {\n\t\t\tanyVpnAddrsInCommon = true\n\t\t}\n\t}\n\n\tif !via.IsRelayed {\n\t\t// We only want to apply the remote allow list for direct tunnels here\n\t\tif !f.lightHouse.GetRemoteAllowList().AllowAll(vpnAddrs, via.UdpAddr.Addr()) {\n\t\t\tf.l.WithField(\"vpnAddrs\", vpnAddrs).WithField(\"from\", via).\n\t\t\t\tDebug(\"lighthouse.remote_allow_list denied incoming handshake\")\n\t\t\treturn\n\t\t}\n\t}\n\n\tmyIndex, err := generateIndex(f.l)\n\tif err != nil {\n\t\tf.l.WithError(err).WithField(\"vpnAddrs\", vpnAddrs).WithField(\"from\", via).\n\t\t\tWithField(\"certName\", certName).\n\t\t\tWithField(\"certVersion\", certVersion).\n\t\t\tWithField(\"fingerprint\", fingerprint).\n\t\t\tWithField(\"issuer\", issuer).\n\t\t\tWithField(\"handshake\", m{\"stage\": 1, \"style\": \"ix_psk0\"}).Error(\"Failed to generate index\")\n\t\treturn\n\t}\n\n\thostinfo := &HostInfo{\n\t\tConnectionState:   ci,\n\t\tlocalIndexId:      myIndex,\n\t\tremoteIndexId:     hs.Details.InitiatorIndex,\n\t\tvpnAddrs:          vpnAddrs,\n\t\tHandshakePacket:   make(map[uint8][]byte, 0),\n\t\tlastHandshakeTime: hs.Details.Time,\n\t\trelayState: RelayState{\n\t\t\trelays:         nil,\n\t\t\trelayForByAddr: map[netip.Addr]*Relay{},\n\t\t\trelayForByIdx:  map[uint32]*Relay{},\n\t\t},\n\t}\n\n\tmsgRxL := f.l.WithFields(m{\n\t\t\"vpnAddrs\":       vpnAddrs,\n\t\t\"from\":           via,\n\t\t\"certName\":       certName,\n\t\t\"certVersion\":    certVersion,\n\t\t\"fingerprint\":    fingerprint,\n\t\t\"issuer\":         issuer,\n\t\t\"initiatorIndex\": hs.Details.InitiatorIndex,\n\t\t\"responderIndex\": hs.Details.ResponderIndex,\n\t\t\"remoteIndex\":    h.RemoteIndex,\n\t\t\"handshake\":      m{\"stage\": 1, \"style\": \"ix_psk0\"},\n\t})\n\n\tif anyVpnAddrsInCommon {\n\t\tmsgRxL.Info(\"Handshake message received\")\n\t} else {\n\t\t//todo warn if not lighthouse or relay?\n\t\tmsgRxL.Info(\"Handshake message received, but no vpnNetworks in common.\")\n\t}\n\n\ths.Details.ResponderIndex = myIndex\n\ths.Details.Cert = cs.getHandshakeBytes(ci.myCert.Version())\n\tif hs.Details.Cert == nil {\n\t\tmsgRxL.WithField(\"myCertVersion\", ci.myCert.Version()).\n\t\t\tError(\"Unable to handshake with host because no certificate handshake bytes is available\")\n\t\treturn\n\t}\n\n\ths.Details.CertVersion = uint32(ci.myCert.Version())\n\t// Update the time in case their clock is way off from ours\n\ths.Details.Time = uint64(time.Now().UnixNano())\n\n\thsBytes, err := hs.Marshal()\n\tif err != nil {\n\t\tf.l.WithError(err).WithField(\"vpnAddrs\", hostinfo.vpnAddrs).WithField(\"from\", via).\n\t\t\tWithField(\"certName\", certName).\n\t\t\tWithField(\"certVersion\", certVersion).\n\t\t\tWithField(\"fingerprint\", fingerprint).\n\t\t\tWithField(\"issuer\", issuer).\n\t\t\tWithField(\"handshake\", m{\"stage\": 1, \"style\": \"ix_psk0\"}).Error(\"Failed to marshal handshake message\")\n\t\treturn\n\t}\n\n\tnh := header.Encode(make([]byte, header.Len), header.Version, header.Handshake, header.HandshakeIXPSK0, hs.Details.InitiatorIndex, 2)\n\tmsg, dKey, eKey, err := ci.H.WriteMessage(nh, hsBytes)\n\tif err != nil {\n\t\tf.l.WithError(err).WithField(\"vpnAddrs\", hostinfo.vpnAddrs).WithField(\"from\", via).\n\t\t\tWithField(\"certName\", certName).\n\t\t\tWithField(\"certVersion\", certVersion).\n\t\t\tWithField(\"fingerprint\", fingerprint).\n\t\t\tWithField(\"issuer\", issuer).\n\t\t\tWithField(\"handshake\", m{\"stage\": 1, \"style\": \"ix_psk0\"}).Error(\"Failed to call noise.WriteMessage\")\n\t\treturn\n\t} else if dKey == nil || eKey == nil {\n\t\tf.l.WithField(\"vpnAddrs\", hostinfo.vpnAddrs).WithField(\"from\", via).\n\t\t\tWithField(\"certName\", certName).\n\t\t\tWithField(\"certVersion\", certVersion).\n\t\t\tWithField(\"fingerprint\", fingerprint).\n\t\t\tWithField(\"issuer\", issuer).\n\t\t\tWithField(\"handshake\", m{\"stage\": 1, \"style\": \"ix_psk0\"}).Error(\"Noise did not arrive at a key\")\n\t\treturn\n\t}\n\n\thostinfo.HandshakePacket[0] = make([]byte, len(packet[header.Len:]))\n\tcopy(hostinfo.HandshakePacket[0], packet[header.Len:])\n\n\t// Regardless of whether you are the sender or receiver, you should arrive here\n\t// and complete standing up the connection.\n\thostinfo.HandshakePacket[2] = make([]byte, len(msg))\n\tcopy(hostinfo.HandshakePacket[2], msg)\n\n\t// We are sending handshake packet 2, so we don't expect to receive\n\t// handshake packet 2 from the initiator.\n\tci.window.Update(f.l, 2)\n\n\tci.peerCert = remoteCert\n\tci.dKey = NewNebulaCipherState(dKey)\n\tci.eKey = NewNebulaCipherState(eKey)\n\n\thostinfo.remotes = f.lightHouse.QueryCache(vpnAddrs)\n\tif !via.IsRelayed {\n\t\thostinfo.SetRemote(via.UdpAddr)\n\t}\n\thostinfo.buildNetworks(f.myVpnNetworksTable, remoteCert.Certificate)\n\n\texisting, err := f.handshakeManager.CheckAndComplete(hostinfo, 0, f)\n\tif err != nil {\n\t\tswitch err {\n\t\tcase ErrAlreadySeen:\n\t\t\t// Update remote if preferred\n\t\t\tif existing.SetRemoteIfPreferred(f.hostMap, via) {\n\t\t\t\t// Send a test packet to ensure the other side has also switched to\n\t\t\t\t// the preferred remote\n\t\t\t\tf.SendMessageToVpnAddr(header.Test, header.TestRequest, vpnAddrs[0], []byte(\"\"), make([]byte, 12, 12), make([]byte, mtu))\n\t\t\t}\n\n\t\t\tmsg = existing.HandshakePacket[2]\n\t\t\tf.messageMetrics.Tx(header.Handshake, header.MessageSubType(msg[1]), 1)\n\t\t\tif !via.IsRelayed {\n\t\t\t\terr := f.outside.WriteTo(msg, via.UdpAddr)\n\t\t\t\tif err != nil {\n\t\t\t\t\tf.l.WithField(\"vpnAddrs\", existing.vpnAddrs).WithField(\"from\", via).\n\t\t\t\t\t\tWithField(\"handshake\", m{\"stage\": 2, \"style\": \"ix_psk0\"}).WithField(\"cached\", true).\n\t\t\t\t\t\tWithError(err).Error(\"Failed to send handshake message\")\n\t\t\t\t} else {\n\t\t\t\t\tf.l.WithField(\"vpnAddrs\", existing.vpnAddrs).WithField(\"from\", via).\n\t\t\t\t\t\tWithField(\"handshake\", m{\"stage\": 2, \"style\": \"ix_psk0\"}).WithField(\"cached\", true).\n\t\t\t\t\t\tInfo(\"Handshake message sent\")\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t} else {\n\t\t\t\tif via.relay == nil {\n\t\t\t\t\tf.l.Error(\"Handshake send failed: both addr and via.relay are nil.\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\thostinfo.relayState.InsertRelayTo(via.relayHI.vpnAddrs[0])\n\t\t\t\tf.SendVia(via.relayHI, via.relay, msg, make([]byte, 12), make([]byte, mtu), false)\n\t\t\t\tf.l.WithField(\"vpnAddrs\", existing.vpnAddrs).WithField(\"relay\", via.relayHI.vpnAddrs[0]).\n\t\t\t\t\tWithField(\"handshake\", m{\"stage\": 2, \"style\": \"ix_psk0\"}).WithField(\"cached\", true).\n\t\t\t\t\tInfo(\"Handshake message sent\")\n\t\t\t\treturn\n\t\t\t}\n\t\tcase ErrExistingHostInfo:\n\t\t\t// This means there was an existing tunnel and this handshake was older than the one we are currently based on\n\t\t\tf.l.WithField(\"vpnAddrs\", vpnAddrs).WithField(\"from\", via).\n\t\t\t\tWithField(\"certName\", certName).\n\t\t\t\tWithField(\"certVersion\", certVersion).\n\t\t\t\tWithField(\"oldHandshakeTime\", existing.lastHandshakeTime).\n\t\t\t\tWithField(\"newHandshakeTime\", hostinfo.lastHandshakeTime).\n\t\t\t\tWithField(\"fingerprint\", fingerprint).\n\t\t\t\tWithField(\"issuer\", issuer).\n\t\t\t\tWithField(\"initiatorIndex\", hs.Details.InitiatorIndex).WithField(\"responderIndex\", hs.Details.ResponderIndex).\n\t\t\t\tWithField(\"remoteIndex\", h.RemoteIndex).WithField(\"handshake\", m{\"stage\": 1, \"style\": \"ix_psk0\"}).\n\t\t\t\tInfo(\"Handshake too old\")\n\n\t\t\t// Send a test packet to trigger an authenticated tunnel test, this should suss out any lingering tunnel issues\n\t\t\tf.SendMessageToVpnAddr(header.Test, header.TestRequest, vpnAddrs[0], []byte(\"\"), make([]byte, 12, 12), make([]byte, mtu))\n\t\t\treturn\n\t\tcase ErrLocalIndexCollision:\n\t\t\t// This means we failed to insert because of collision on localIndexId. Just let the next handshake packet retry\n\t\t\tf.l.WithField(\"vpnAddrs\", vpnAddrs).WithField(\"from\", via).\n\t\t\t\tWithField(\"certName\", certName).\n\t\t\t\tWithField(\"certVersion\", certVersion).\n\t\t\t\tWithField(\"fingerprint\", fingerprint).\n\t\t\t\tWithField(\"issuer\", issuer).\n\t\t\t\tWithField(\"initiatorIndex\", hs.Details.InitiatorIndex).WithField(\"responderIndex\", hs.Details.ResponderIndex).\n\t\t\t\tWithField(\"remoteIndex\", h.RemoteIndex).WithField(\"handshake\", m{\"stage\": 1, \"style\": \"ix_psk0\"}).\n\t\t\t\tWithField(\"localIndex\", hostinfo.localIndexId).WithField(\"collision\", existing.vpnAddrs).\n\t\t\t\tError(\"Failed to add HostInfo due to localIndex collision\")\n\t\t\treturn\n\t\tdefault:\n\t\t\t// Shouldn't happen, but just in case someone adds a new error type to CheckAndComplete\n\t\t\t// And we forget to update it here\n\t\t\tf.l.WithError(err).WithField(\"vpnAddrs\", vpnAddrs).WithField(\"from\", via).\n\t\t\t\tWithField(\"certName\", certName).\n\t\t\t\tWithField(\"certVersion\", certVersion).\n\t\t\t\tWithField(\"fingerprint\", fingerprint).\n\t\t\t\tWithField(\"issuer\", issuer).\n\t\t\t\tWithField(\"initiatorIndex\", hs.Details.InitiatorIndex).WithField(\"responderIndex\", hs.Details.ResponderIndex).\n\t\t\t\tWithField(\"remoteIndex\", h.RemoteIndex).WithField(\"handshake\", m{\"stage\": 1, \"style\": \"ix_psk0\"}).\n\t\t\t\tError(\"Failed to add HostInfo to HostMap\")\n\t\t\treturn\n\t\t}\n\t}\n\n\t// Do the send\n\tf.messageMetrics.Tx(header.Handshake, header.MessageSubType(msg[1]), 1)\n\tif !via.IsRelayed {\n\t\terr = f.outside.WriteTo(msg, via.UdpAddr)\n\t\tlog := f.l.WithField(\"vpnAddrs\", vpnAddrs).WithField(\"from\", via).\n\t\t\tWithField(\"certName\", certName).\n\t\t\tWithField(\"certVersion\", certVersion).\n\t\t\tWithField(\"fingerprint\", fingerprint).\n\t\t\tWithField(\"issuer\", issuer).\n\t\t\tWithField(\"initiatorIndex\", hs.Details.InitiatorIndex).WithField(\"responderIndex\", hs.Details.ResponderIndex).\n\t\t\tWithField(\"remoteIndex\", h.RemoteIndex).WithField(\"handshake\", m{\"stage\": 2, \"style\": \"ix_psk0\"})\n\t\tif err != nil {\n\t\t\tlog.WithError(err).Error(\"Failed to send handshake\")\n\t\t} else {\n\t\t\tlog.Info(\"Handshake message sent\")\n\t\t}\n\t} else {\n\t\tif via.relay == nil {\n\t\t\tf.l.Error(\"Handshake send failed: both addr and via.relay are nil.\")\n\t\t\treturn\n\t\t}\n\t\thostinfo.relayState.InsertRelayTo(via.relayHI.vpnAddrs[0])\n\t\t// I successfully received a handshake. Just in case I marked this tunnel as 'Disestablished', ensure\n\t\t// it's correctly marked as working.\n\t\tvia.relayHI.relayState.UpdateRelayForByIdxState(via.remoteIdx, Established)\n\t\tf.SendVia(via.relayHI, via.relay, msg, make([]byte, 12), make([]byte, mtu), false)\n\t\tf.l.WithField(\"vpnAddrs\", vpnAddrs).WithField(\"relay\", via.relayHI.vpnAddrs[0]).\n\t\t\tWithField(\"certName\", certName).\n\t\t\tWithField(\"certVersion\", certVersion).\n\t\t\tWithField(\"fingerprint\", fingerprint).\n\t\t\tWithField(\"issuer\", issuer).\n\t\t\tWithField(\"initiatorIndex\", hs.Details.InitiatorIndex).WithField(\"responderIndex\", hs.Details.ResponderIndex).\n\t\t\tWithField(\"remoteIndex\", h.RemoteIndex).WithField(\"handshake\", m{\"stage\": 2, \"style\": \"ix_psk0\"}).\n\t\t\tInfo(\"Handshake message sent\")\n\t}\n\n\tf.connectionManager.AddTrafficWatch(hostinfo)\n\n\thostinfo.remotes.RefreshFromHandshake(vpnAddrs)\n\n\treturn\n}\n\nfunc ixHandshakeStage2(f *Interface, via ViaSender, hh *HandshakeHostInfo, packet []byte, h *header.H) bool {\n\tif hh == nil {\n\t\t// Nothing here to tear down, got a bogus stage 2 packet\n\t\treturn true\n\t}\n\n\thh.Lock()\n\tdefer hh.Unlock()\n\n\thostinfo := hh.hostinfo\n\tif !via.IsRelayed {\n\t\t// The vpnAddr we know about is the one we tried to handshake with, use it to apply the remote allow list.\n\t\tif !f.lightHouse.GetRemoteAllowList().AllowAll(hostinfo.vpnAddrs, via.UdpAddr.Addr()) {\n\t\t\tf.l.WithField(\"vpnAddrs\", hostinfo.vpnAddrs).WithField(\"from\", via).Debug(\"lighthouse.remote_allow_list denied incoming handshake\")\n\t\t\treturn false\n\t\t}\n\t}\n\n\tci := hostinfo.ConnectionState\n\tmsg, eKey, dKey, err := ci.H.ReadMessage(nil, packet[header.Len:])\n\tif err != nil {\n\t\tf.l.WithError(err).WithField(\"vpnAddrs\", hostinfo.vpnAddrs).WithField(\"from\", via).\n\t\t\tWithField(\"handshake\", m{\"stage\": 2, \"style\": \"ix_psk0\"}).WithField(\"header\", h).\n\t\t\tError(\"Failed to call noise.ReadMessage\")\n\n\t\t// We don't want to tear down the connection on a bad ReadMessage because it could be an attacker trying\n\t\t// to DOS us. Every other error condition after should to allow a possible good handshake to complete in the\n\t\t// near future\n\t\treturn false\n\t} else if dKey == nil || eKey == nil {\n\t\tf.l.WithField(\"vpnAddrs\", hostinfo.vpnAddrs).WithField(\"from\", via).\n\t\t\tWithField(\"handshake\", m{\"stage\": 2, \"style\": \"ix_psk0\"}).\n\t\t\tError(\"Noise did not arrive at a key\")\n\n\t\t// This should be impossible in IX but just in case, if we get here then there is no chance to recover\n\t\t// the handshake state machine. Tear it down\n\t\treturn true\n\t}\n\n\ths := &NebulaHandshake{}\n\terr = hs.Unmarshal(msg)\n\tif err != nil || hs.Details == nil {\n\t\tf.l.WithError(err).WithField(\"vpnAddrs\", hostinfo.vpnAddrs).WithField(\"from\", via).\n\t\t\tWithField(\"handshake\", m{\"stage\": 2, \"style\": \"ix_psk0\"}).Error(\"Failed unmarshal handshake message\")\n\n\t\t// The handshake state machine is complete, if things break now there is no chance to recover. Tear down and start again\n\t\treturn true\n\t}\n\n\trc, err := cert.Recombine(cert.Version(hs.Details.CertVersion), hs.Details.Cert, ci.H.PeerStatic(), ci.Curve())\n\tif err != nil {\n\t\tf.l.WithError(err).WithField(\"from\", via).\n\t\t\tWithField(\"vpnAddrs\", hostinfo.vpnAddrs).\n\t\t\tWithField(\"handshake\", m{\"stage\": 2, \"style\": \"ix_psk0\"}).\n\t\t\tInfo(\"Handshake did not contain a certificate\")\n\t\treturn true\n\t}\n\n\tremoteCert, err := f.pki.GetCAPool().VerifyCertificate(time.Now(), rc)\n\tif err != nil {\n\t\tfp, err := rc.Fingerprint()\n\t\tif err != nil {\n\t\t\tfp = \"<error generating certificate fingerprint>\"\n\t\t}\n\n\t\te := f.l.WithError(err).WithField(\"from\", via).\n\t\t\tWithField(\"vpnAddrs\", hostinfo.vpnAddrs).\n\t\t\tWithField(\"handshake\", m{\"stage\": 2, \"style\": \"ix_psk0\"}).\n\t\t\tWithField(\"certFingerprint\", fp).\n\t\t\tWithField(\"certVpnNetworks\", rc.Networks())\n\n\t\tif f.l.Level >= logrus.DebugLevel {\n\t\t\te = e.WithField(\"cert\", rc)\n\t\t}\n\n\t\te.Info(\"Invalid certificate from host\")\n\t\treturn true\n\t}\n\tif !bytes.Equal(remoteCert.Certificate.PublicKey(), ci.H.PeerStatic()) {\n\t\tf.l.WithField(\"from\", via).\n\t\t\tWithField(\"handshake\", m{\"stage\": 2, \"style\": \"ix_psk0\"}).\n\t\t\tWithField(\"cert\", remoteCert).Info(\"public key mismatch between certificate and handshake\")\n\t\treturn true\n\t}\n\n\tif len(remoteCert.Certificate.Networks()) == 0 {\n\t\tf.l.WithError(err).WithField(\"from\", via).\n\t\t\tWithField(\"vpnAddrs\", hostinfo.vpnAddrs).\n\t\t\tWithField(\"cert\", remoteCert).\n\t\t\tWithField(\"handshake\", m{\"stage\": 2, \"style\": \"ix_psk0\"}).\n\t\t\tInfo(\"No networks in certificate\")\n\t\treturn true\n\t}\n\n\tvpnNetworks := remoteCert.Certificate.Networks()\n\tcertName := remoteCert.Certificate.Name()\n\tcertVersion := remoteCert.Certificate.Version()\n\tfingerprint := remoteCert.Fingerprint\n\tissuer := remoteCert.Certificate.Issuer()\n\n\thostinfo.remoteIndexId = hs.Details.ResponderIndex\n\thostinfo.lastHandshakeTime = hs.Details.Time\n\n\t// Store their cert and our symmetric keys\n\tci.peerCert = remoteCert\n\tci.dKey = NewNebulaCipherState(dKey)\n\tci.eKey = NewNebulaCipherState(eKey)\n\n\t// Make sure the current udpAddr being used is set for responding\n\tif !via.IsRelayed {\n\t\thostinfo.SetRemote(via.UdpAddr)\n\t} else {\n\t\thostinfo.relayState.InsertRelayTo(via.relayHI.vpnAddrs[0])\n\t}\n\n\tcorrectHostResponded := false\n\tanyVpnAddrsInCommon := false\n\tvpnAddrs := make([]netip.Addr, len(vpnNetworks))\n\tfor i, network := range vpnNetworks {\n\t\tvpnAddrs[i] = network.Addr()\n\t\tif f.myVpnNetworksTable.Contains(network.Addr()) {\n\t\t\tanyVpnAddrsInCommon = true\n\t\t}\n\t\tif hostinfo.vpnAddrs[0] == network.Addr() {\n\t\t\t// todo is it more correct to see if any of hostinfo.vpnAddrs are in the cert? it should have len==1, but one day it might not?\n\t\t\tcorrectHostResponded = true\n\t\t}\n\t}\n\n\t// Ensure the right host responded\n\tif !correctHostResponded {\n\t\tf.l.WithField(\"intendedVpnAddrs\", hostinfo.vpnAddrs).WithField(\"haveVpnNetworks\", vpnNetworks).\n\t\t\tWithField(\"from\", via).\n\t\t\tWithField(\"certName\", certName).\n\t\t\tWithField(\"certVersion\", certVersion).\n\t\t\tWithField(\"handshake\", m{\"stage\": 2, \"style\": \"ix_psk0\"}).\n\t\t\tInfo(\"Incorrect host responded to handshake\")\n\n\t\t// Release our old handshake from pending, it should not continue\n\t\tf.handshakeManager.DeleteHostInfo(hostinfo)\n\n\t\t// Create a new hostinfo/handshake for the intended vpn ip\n\t\t//TODO is hostinfo.vpnAddrs[0] always the address to use?\n\t\tf.handshakeManager.StartHandshake(hostinfo.vpnAddrs[0], func(newHH *HandshakeHostInfo) {\n\t\t\t// Block the current used address\n\t\t\tnewHH.hostinfo.remotes = hostinfo.remotes\n\t\t\tnewHH.hostinfo.remotes.BlockRemote(via)\n\n\t\t\tf.l.WithField(\"blockedUdpAddrs\", newHH.hostinfo.remotes.CopyBlockedRemotes()).\n\t\t\t\tWithField(\"vpnNetworks\", vpnNetworks).\n\t\t\t\tWithField(\"remotes\", newHH.hostinfo.remotes.CopyAddrs(f.hostMap.GetPreferredRanges())).\n\t\t\t\tInfo(\"Blocked addresses for handshakes\")\n\n\t\t\t// Swap the packet store to benefit the original intended recipient\n\t\t\tnewHH.packetStore = hh.packetStore\n\t\t\thh.packetStore = []*cachedPacket{}\n\n\t\t\t// Finally, put the correct vpn addrs in the host info, tell them to close the tunnel, and return true to tear down\n\t\t\thostinfo.vpnAddrs = vpnAddrs\n\t\t\tf.sendCloseTunnel(hostinfo)\n\t\t})\n\n\t\treturn true\n\t}\n\n\t// Mark packet 2 as seen so it doesn't show up as missed\n\tci.window.Update(f.l, 2)\n\n\tduration := time.Since(hh.startTime).Nanoseconds()\n\tmsgRxL := f.l.WithField(\"vpnAddrs\", vpnAddrs).WithField(\"from\", via).\n\t\tWithField(\"certName\", certName).\n\t\tWithField(\"certVersion\", certVersion).\n\t\tWithField(\"fingerprint\", fingerprint).\n\t\tWithField(\"issuer\", issuer).\n\t\tWithField(\"initiatorIndex\", hs.Details.InitiatorIndex).WithField(\"responderIndex\", hs.Details.ResponderIndex).\n\t\tWithField(\"remoteIndex\", h.RemoteIndex).WithField(\"handshake\", m{\"stage\": 2, \"style\": \"ix_psk0\"}).\n\t\tWithField(\"durationNs\", duration).\n\t\tWithField(\"sentCachedPackets\", len(hh.packetStore))\n\tif anyVpnAddrsInCommon {\n\t\tmsgRxL.Info(\"Handshake message received\")\n\t} else {\n\t\t//todo warn if not lighthouse or relay?\n\t\tmsgRxL.Info(\"Handshake message received, but no vpnNetworks in common.\")\n\t}\n\n\t// Build up the radix for the firewall if we have subnets in the cert\n\thostinfo.vpnAddrs = vpnAddrs\n\thostinfo.buildNetworks(f.myVpnNetworksTable, remoteCert.Certificate)\n\n\t// Complete our handshake and update metrics, this will replace any existing tunnels for the vpnAddrs here\n\tf.handshakeManager.Complete(hostinfo, f)\n\tf.connectionManager.AddTrafficWatch(hostinfo)\n\n\tif f.l.Level >= logrus.DebugLevel {\n\t\thostinfo.logger(f.l).Debugf(\"Sending %d stored packets\", len(hh.packetStore))\n\t}\n\n\tif len(hh.packetStore) > 0 {\n\t\tnb := make([]byte, 12, 12)\n\t\tout := make([]byte, mtu)\n\t\tfor _, cp := range hh.packetStore {\n\t\t\tcp.callback(cp.messageType, cp.messageSubType, hostinfo, cp.packet, nb, out)\n\t\t}\n\t\tf.cachedPacketMetrics.sent.Inc(int64(len(hh.packetStore)))\n\t}\n\n\thostinfo.remotes.RefreshFromHandshake(vpnAddrs)\n\tf.metricHandshakes.Update(duration)\n\n\treturn false\n}\n"
  },
  {
    "path": "handshake_manager.go",
    "content": "package nebula\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/rand\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"net/netip\"\n\t\"slices\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/rcrowley/go-metrics\"\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/cert\"\n\t\"github.com/slackhq/nebula/header\"\n\t\"github.com/slackhq/nebula/udp\"\n)\n\nconst (\n\tDefaultHandshakeTryInterval   = time.Millisecond * 100\n\tDefaultHandshakeRetries       = 10\n\tDefaultHandshakeTriggerBuffer = 64\n\tDefaultUseRelays              = true\n)\n\nvar (\n\tdefaultHandshakeConfig = HandshakeConfig{\n\t\ttryInterval:   DefaultHandshakeTryInterval,\n\t\tretries:       DefaultHandshakeRetries,\n\t\ttriggerBuffer: DefaultHandshakeTriggerBuffer,\n\t\tuseRelays:     DefaultUseRelays,\n\t}\n)\n\ntype HandshakeConfig struct {\n\ttryInterval   time.Duration\n\tretries       int64\n\ttriggerBuffer int\n\tuseRelays     bool\n\n\tmessageMetrics *MessageMetrics\n}\n\ntype HandshakeManager struct {\n\t// Mutex for interacting with the vpnIps and indexes maps\n\tsync.RWMutex\n\n\tvpnIps  map[netip.Addr]*HandshakeHostInfo\n\tindexes map[uint32]*HandshakeHostInfo\n\n\tmainHostMap            *HostMap\n\tlightHouse             *LightHouse\n\toutside                udp.Conn\n\tconfig                 HandshakeConfig\n\tOutboundHandshakeTimer *LockingTimerWheel[netip.Addr]\n\tmessageMetrics         *MessageMetrics\n\tmetricInitiated        metrics.Counter\n\tmetricTimedOut         metrics.Counter\n\tf                      *Interface\n\tl                      *logrus.Logger\n\n\t// can be used to trigger outbound handshake for the given vpnIp\n\ttrigger chan netip.Addr\n}\n\ntype HandshakeHostInfo struct {\n\tsync.Mutex\n\n\tstartTime                 time.Time        // Time that we first started trying with this handshake\n\tready                     bool             // Is the handshake ready\n\tinitiatingVersionOverride cert.Version     // Should we use a non-default cert version for this handshake?\n\tcounter                   int64            // How many attempts have we made so far\n\tlastRemotes               []netip.AddrPort // Remotes that we sent to during the previous attempt\n\tpacketStore               []*cachedPacket  // A set of packets to be transmitted once the handshake completes\n\n\thostinfo *HostInfo\n}\n\nfunc (hh *HandshakeHostInfo) cachePacket(l *logrus.Logger, t header.MessageType, st header.MessageSubType, packet []byte, f packetCallback, m *cachedPacketMetrics) {\n\tif len(hh.packetStore) < 100 {\n\t\ttempPacket := make([]byte, len(packet))\n\t\tcopy(tempPacket, packet)\n\n\t\thh.packetStore = append(hh.packetStore, &cachedPacket{t, st, f, tempPacket})\n\t\tif l.Level >= logrus.DebugLevel {\n\t\t\thh.hostinfo.logger(l).\n\t\t\t\tWithField(\"length\", len(hh.packetStore)).\n\t\t\t\tWithField(\"stored\", true).\n\t\t\t\tDebugf(\"Packet store\")\n\t\t}\n\n\t} else {\n\t\tm.dropped.Inc(1)\n\n\t\tif l.Level >= logrus.DebugLevel {\n\t\t\thh.hostinfo.logger(l).\n\t\t\t\tWithField(\"length\", len(hh.packetStore)).\n\t\t\t\tWithField(\"stored\", false).\n\t\t\t\tDebugf(\"Packet store\")\n\t\t}\n\t}\n}\n\nfunc NewHandshakeManager(l *logrus.Logger, mainHostMap *HostMap, lightHouse *LightHouse, outside udp.Conn, config HandshakeConfig) *HandshakeManager {\n\treturn &HandshakeManager{\n\t\tvpnIps:                 map[netip.Addr]*HandshakeHostInfo{},\n\t\tindexes:                map[uint32]*HandshakeHostInfo{},\n\t\tmainHostMap:            mainHostMap,\n\t\tlightHouse:             lightHouse,\n\t\toutside:                outside,\n\t\tconfig:                 config,\n\t\ttrigger:                make(chan netip.Addr, config.triggerBuffer),\n\t\tOutboundHandshakeTimer: NewLockingTimerWheel[netip.Addr](config.tryInterval, hsTimeout(config.retries, config.tryInterval)),\n\t\tmessageMetrics:         config.messageMetrics,\n\t\tmetricInitiated:        metrics.GetOrRegisterCounter(\"handshake_manager.initiated\", nil),\n\t\tmetricTimedOut:         metrics.GetOrRegisterCounter(\"handshake_manager.timed_out\", nil),\n\t\tl:                      l,\n\t}\n}\n\nfunc (hm *HandshakeManager) Run(ctx context.Context) {\n\tclockSource := time.NewTicker(hm.config.tryInterval)\n\tdefer clockSource.Stop()\n\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase vpnIP := <-hm.trigger:\n\t\t\thm.handleOutbound(vpnIP, true)\n\t\tcase now := <-clockSource.C:\n\t\t\thm.NextOutboundHandshakeTimerTick(now)\n\t\t}\n\t}\n}\n\nfunc (hm *HandshakeManager) HandleIncoming(via ViaSender, packet []byte, h *header.H) {\n\t// First remote allow list check before we know the vpnIp\n\tif !via.IsRelayed {\n\t\tif !hm.lightHouse.GetRemoteAllowList().AllowUnknownVpnAddr(via.UdpAddr.Addr()) {\n\t\t\thm.l.WithField(\"from\", via).Debug(\"lighthouse.remote_allow_list denied incoming handshake\")\n\t\t\treturn\n\t\t}\n\t}\n\n\tswitch h.Subtype {\n\tcase header.HandshakeIXPSK0:\n\t\tswitch h.MessageCounter {\n\t\tcase 1:\n\t\t\tixHandshakeStage1(hm.f, via, packet, h)\n\n\t\tcase 2:\n\t\t\tnewHostinfo := hm.queryIndex(h.RemoteIndex)\n\t\t\ttearDown := ixHandshakeStage2(hm.f, via, newHostinfo, packet, h)\n\t\t\tif tearDown && newHostinfo != nil {\n\t\t\t\thm.DeleteHostInfo(newHostinfo.hostinfo)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (hm *HandshakeManager) NextOutboundHandshakeTimerTick(now time.Time) {\n\thm.OutboundHandshakeTimer.Advance(now)\n\tfor {\n\t\tvpnIp, has := hm.OutboundHandshakeTimer.Purge()\n\t\tif !has {\n\t\t\tbreak\n\t\t}\n\t\thm.handleOutbound(vpnIp, false)\n\t}\n}\n\nfunc (hm *HandshakeManager) handleOutbound(vpnIp netip.Addr, lighthouseTriggered bool) {\n\thh := hm.queryVpnIp(vpnIp)\n\tif hh == nil {\n\t\treturn\n\t}\n\thh.Lock()\n\tdefer hh.Unlock()\n\n\thostinfo := hh.hostinfo\n\t// If we are out of time, clean up\n\tif hh.counter >= hm.config.retries {\n\t\thh.hostinfo.logger(hm.l).WithField(\"udpAddrs\", hh.hostinfo.remotes.CopyAddrs(hm.mainHostMap.GetPreferredRanges())).\n\t\t\tWithField(\"initiatorIndex\", hh.hostinfo.localIndexId).\n\t\t\tWithField(\"remoteIndex\", hh.hostinfo.remoteIndexId).\n\t\t\tWithField(\"handshake\", m{\"stage\": 1, \"style\": \"ix_psk0\"}).\n\t\t\tWithField(\"durationNs\", time.Since(hh.startTime).Nanoseconds()).\n\t\t\tInfo(\"Handshake timed out\")\n\t\thm.metricTimedOut.Inc(1)\n\t\thm.DeleteHostInfo(hostinfo)\n\t\treturn\n\t}\n\n\t// Increment the counter to increase our delay, linear backoff\n\thh.counter++\n\n\t// Check if we have a handshake packet to transmit yet\n\tif !hh.ready {\n\t\tif !ixHandshakeStage0(hm.f, hh) {\n\t\t\thm.OutboundHandshakeTimer.Add(vpnIp, hm.config.tryInterval*time.Duration(hh.counter))\n\t\t\treturn\n\t\t}\n\t}\n\n\t// Get a remotes object if we don't already have one.\n\t// This is mainly to protect us as this should never be the case\n\t// NB ^ This comment doesn't jive. It's how the thing gets initialized.\n\t// It's the common path. Should it update every time, in case a future LH query/queries give us more info?\n\tif hostinfo.remotes == nil {\n\t\thostinfo.remotes = hm.lightHouse.QueryCache([]netip.Addr{vpnIp})\n\t}\n\n\tremotes := hostinfo.remotes.CopyAddrs(hm.mainHostMap.GetPreferredRanges())\n\tremotesHaveChanged := !slices.Equal(remotes, hh.lastRemotes)\n\n\t// We only care about a lighthouse trigger if we have new remotes to send to.\n\t// This is a very specific optimization for a fast lighthouse reply.\n\tif lighthouseTriggered && !remotesHaveChanged {\n\t\t// If we didn't return here a lighthouse could cause us to aggressively send handshakes\n\t\treturn\n\t}\n\n\thh.lastRemotes = remotes\n\n\t// This will generate a load of queries for hosts with only 1 ip\n\t// (such as ones registered to the lighthouse with only a private IP)\n\t// So we only do it one time after attempting 5 handshakes already.\n\tif len(remotes) <= 1 && hh.counter == 5 {\n\t\t// If we only have 1 remote it is highly likely our query raced with the other host registered within the lighthouse\n\t\t// Our vpnIp here has a tunnel with a lighthouse but has yet to send a host update packet there so we only know about\n\t\t// the learned public ip for them. Query again to short circuit the promotion counter\n\t\thm.lightHouse.QueryServer(vpnIp)\n\t}\n\n\t// Send the handshake to all known ips, stage 2 takes care of assigning the hostinfo.remote based on the first to reply\n\tvar sentTo []netip.AddrPort\n\thostinfo.remotes.ForEach(hm.mainHostMap.GetPreferredRanges(), func(addr netip.AddrPort, _ bool) {\n\t\thm.messageMetrics.Tx(header.Handshake, header.MessageSubType(hostinfo.HandshakePacket[0][1]), 1)\n\t\terr := hm.outside.WriteTo(hostinfo.HandshakePacket[0], addr)\n\t\tif err != nil {\n\t\t\thostinfo.logger(hm.l).WithField(\"udpAddr\", addr).\n\t\t\t\tWithField(\"initiatorIndex\", hostinfo.localIndexId).\n\t\t\t\tWithField(\"handshake\", m{\"stage\": 1, \"style\": \"ix_psk0\"}).\n\t\t\t\tWithError(err).Error(\"Failed to send handshake message\")\n\n\t\t} else {\n\t\t\tsentTo = append(sentTo, addr)\n\t\t}\n\t})\n\n\t// Don't be too noisy or confusing if we fail to send a handshake - if we don't get through we'll eventually log a timeout,\n\t// so only log when the list of remotes has changed\n\tif remotesHaveChanged {\n\t\thostinfo.logger(hm.l).WithField(\"udpAddrs\", sentTo).\n\t\t\tWithField(\"initiatorIndex\", hostinfo.localIndexId).\n\t\t\tWithField(\"handshake\", m{\"stage\": 1, \"style\": \"ix_psk0\"}).\n\t\t\tInfo(\"Handshake message sent\")\n\t} else if hm.l.Level >= logrus.DebugLevel {\n\t\thostinfo.logger(hm.l).WithField(\"udpAddrs\", sentTo).\n\t\t\tWithField(\"initiatorIndex\", hostinfo.localIndexId).\n\t\t\tWithField(\"handshake\", m{\"stage\": 1, \"style\": \"ix_psk0\"}).\n\t\t\tDebug(\"Handshake message sent\")\n\t}\n\n\tif hm.config.useRelays && len(hostinfo.remotes.relays) > 0 {\n\t\thostinfo.logger(hm.l).WithField(\"relays\", hostinfo.remotes.relays).Info(\"Attempt to relay through hosts\")\n\t\t// Send a RelayRequest to all known Relay IP's\n\t\tfor _, relay := range hostinfo.remotes.relays {\n\t\t\t// Don't relay through the host I'm trying to connect to\n\t\t\tif relay == vpnIp {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Don't relay to myself\n\t\t\tif hm.f.myVpnAddrsTable.Contains(relay) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trelayHostInfo := hm.mainHostMap.QueryVpnAddr(relay)\n\t\t\tif relayHostInfo == nil || !relayHostInfo.remote.IsValid() {\n\t\t\t\thostinfo.logger(hm.l).WithField(\"relay\", relay.String()).Info(\"Establish tunnel to relay target\")\n\t\t\t\thm.f.Handshake(relay)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// Check the relay HostInfo to see if we already established a relay through\n\t\t\texistingRelay, ok := relayHostInfo.relayState.QueryRelayForByIp(vpnIp)\n\t\t\tif !ok {\n\t\t\t\t// No relays exist or requested yet.\n\t\t\t\tif relayHostInfo.remote.IsValid() {\n\t\t\t\t\tidx, err := AddRelay(hm.l, relayHostInfo, hm.mainHostMap, vpnIp, nil, TerminalType, Requested)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\thostinfo.logger(hm.l).WithField(\"relay\", relay.String()).WithError(err).Info(\"Failed to add relay to hostmap\")\n\t\t\t\t\t}\n\n\t\t\t\t\tm := NebulaControl{\n\t\t\t\t\t\tType:                NebulaControl_CreateRelayRequest,\n\t\t\t\t\t\tInitiatorRelayIndex: idx,\n\t\t\t\t\t}\n\n\t\t\t\t\tswitch relayHostInfo.GetCert().Certificate.Version() {\n\t\t\t\t\tcase cert.Version1:\n\t\t\t\t\t\tif !hm.f.myVpnAddrs[0].Is4() {\n\t\t\t\t\t\t\thostinfo.logger(hm.l).Error(\"can not establish v1 relay with a v6 network because the relay is not running a current nebula version\")\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif !vpnIp.Is4() {\n\t\t\t\t\t\t\thostinfo.logger(hm.l).Error(\"can not establish v1 relay with a v6 remote network because the relay is not running a current nebula version\")\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tb := hm.f.myVpnAddrs[0].As4()\n\t\t\t\t\t\tm.OldRelayFromAddr = binary.BigEndian.Uint32(b[:])\n\t\t\t\t\t\tb = vpnIp.As4()\n\t\t\t\t\t\tm.OldRelayToAddr = binary.BigEndian.Uint32(b[:])\n\t\t\t\t\tcase cert.Version2:\n\t\t\t\t\t\tm.RelayFromAddr = netAddrToProtoAddr(hm.f.myVpnAddrs[0])\n\t\t\t\t\t\tm.RelayToAddr = netAddrToProtoAddr(vpnIp)\n\t\t\t\t\tdefault:\n\t\t\t\t\t\thostinfo.logger(hm.l).Error(\"Unknown certificate version found while creating relay\")\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tmsg, err := m.Marshal()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\thostinfo.logger(hm.l).\n\t\t\t\t\t\t\tWithError(err).\n\t\t\t\t\t\t\tError(\"Failed to marshal Control message to create relay\")\n\t\t\t\t\t} else {\n\t\t\t\t\t\thm.f.SendMessageToHostInfo(header.Control, 0, relayHostInfo, msg, make([]byte, 12), make([]byte, mtu))\n\t\t\t\t\t\thm.l.WithFields(logrus.Fields{\n\t\t\t\t\t\t\t\"relayFrom\":           hm.f.myVpnAddrs[0],\n\t\t\t\t\t\t\t\"relayTo\":             vpnIp,\n\t\t\t\t\t\t\t\"initiatorRelayIndex\": idx,\n\t\t\t\t\t\t\t\"relay\":               relay}).\n\t\t\t\t\t\t\tInfo(\"send CreateRelayRequest\")\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tswitch existingRelay.State {\n\t\t\tcase Established:\n\t\t\t\thostinfo.logger(hm.l).WithField(\"relay\", relay.String()).Info(\"Send handshake via relay\")\n\t\t\t\thm.f.SendVia(relayHostInfo, existingRelay, hostinfo.HandshakePacket[0], make([]byte, 12), make([]byte, mtu), false)\n\t\t\tcase Disestablished:\n\t\t\t\t// Mark this relay as 'requested'\n\t\t\t\trelayHostInfo.relayState.UpdateRelayForByIpState(vpnIp, Requested)\n\t\t\t\tfallthrough\n\t\t\tcase Requested:\n\t\t\t\thostinfo.logger(hm.l).WithField(\"relay\", relay.String()).Info(\"Re-send CreateRelay request\")\n\t\t\t\t// Re-send the CreateRelay request, in case the previous one was lost.\n\t\t\t\tm := NebulaControl{\n\t\t\t\t\tType:                NebulaControl_CreateRelayRequest,\n\t\t\t\t\tInitiatorRelayIndex: existingRelay.LocalIndex,\n\t\t\t\t}\n\n\t\t\t\tswitch relayHostInfo.GetCert().Certificate.Version() {\n\t\t\t\tcase cert.Version1:\n\t\t\t\t\tif !hm.f.myVpnAddrs[0].Is4() {\n\t\t\t\t\t\thostinfo.logger(hm.l).Error(\"can not establish v1 relay with a v6 network because the relay is not running a current nebula version\")\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tif !vpnIp.Is4() {\n\t\t\t\t\t\thostinfo.logger(hm.l).Error(\"can not establish v1 relay with a v6 remote network because the relay is not running a current nebula version\")\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tb := hm.f.myVpnAddrs[0].As4()\n\t\t\t\t\tm.OldRelayFromAddr = binary.BigEndian.Uint32(b[:])\n\t\t\t\t\tb = vpnIp.As4()\n\t\t\t\t\tm.OldRelayToAddr = binary.BigEndian.Uint32(b[:])\n\t\t\t\tcase cert.Version2:\n\t\t\t\t\tm.RelayFromAddr = netAddrToProtoAddr(hm.f.myVpnAddrs[0])\n\t\t\t\t\tm.RelayToAddr = netAddrToProtoAddr(vpnIp)\n\t\t\t\tdefault:\n\t\t\t\t\thostinfo.logger(hm.l).Error(\"Unknown certificate version found while creating relay\")\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tmsg, err := m.Marshal()\n\t\t\t\tif err != nil {\n\t\t\t\t\thostinfo.logger(hm.l).\n\t\t\t\t\t\tWithError(err).\n\t\t\t\t\t\tError(\"Failed to marshal Control message to create relay\")\n\t\t\t\t} else {\n\t\t\t\t\t// This must send over the hostinfo, not over hm.Hosts[ip]\n\t\t\t\t\thm.f.SendMessageToHostInfo(header.Control, 0, relayHostInfo, msg, make([]byte, 12), make([]byte, mtu))\n\t\t\t\t\thm.l.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"relayFrom\":           hm.f.myVpnAddrs[0],\n\t\t\t\t\t\t\"relayTo\":             vpnIp,\n\t\t\t\t\t\t\"initiatorRelayIndex\": existingRelay.LocalIndex,\n\t\t\t\t\t\t\"relay\":               relay}).\n\t\t\t\t\t\tInfo(\"send CreateRelayRequest\")\n\t\t\t\t}\n\t\t\tcase PeerRequested:\n\t\t\t\t// PeerRequested only occurs in Forwarding relays, not Terminal relays, and this is a Terminal relay case.\n\t\t\t\tfallthrough\n\t\t\tdefault:\n\t\t\t\thostinfo.logger(hm.l).\n\t\t\t\t\tWithField(\"vpnIp\", vpnIp).\n\t\t\t\t\tWithField(\"state\", existingRelay.State).\n\t\t\t\t\tWithField(\"relay\", relay).\n\t\t\t\t\tErrorf(\"Relay unexpected state\")\n\n\t\t\t}\n\t\t}\n\t}\n\n\t// If a lighthouse triggered this attempt then we are still in the timer wheel and do not need to re-add\n\tif !lighthouseTriggered {\n\t\thm.OutboundHandshakeTimer.Add(vpnIp, hm.config.tryInterval*time.Duration(hh.counter))\n\t}\n}\n\n// GetOrHandshake will try to find a hostinfo with a fully formed tunnel or start a new handshake if one is not present\n// The 2nd argument will be true if the hostinfo is ready to transmit traffic\nfunc (hm *HandshakeManager) GetOrHandshake(vpnIp netip.Addr, cacheCb func(*HandshakeHostInfo)) (*HostInfo, bool) {\n\thm.mainHostMap.RLock()\n\th, ok := hm.mainHostMap.Hosts[vpnIp]\n\thm.mainHostMap.RUnlock()\n\n\tif ok {\n\t\t// Do not attempt promotion if you are a lighthouse\n\t\tif !hm.lightHouse.amLighthouse {\n\t\t\th.TryPromoteBest(hm.mainHostMap.GetPreferredRanges(), hm.f)\n\t\t}\n\t\treturn h, true\n\t}\n\n\treturn hm.StartHandshake(vpnIp, cacheCb), false\n}\n\n// StartHandshake will ensure a handshake is currently being attempted for the provided vpn ip\nfunc (hm *HandshakeManager) StartHandshake(vpnAddr netip.Addr, cacheCb func(*HandshakeHostInfo)) *HostInfo {\n\thm.Lock()\n\n\tif hh, ok := hm.vpnIps[vpnAddr]; ok {\n\t\t// We are already trying to handshake with this vpn ip\n\t\tif cacheCb != nil {\n\t\t\tcacheCb(hh)\n\t\t}\n\t\thm.Unlock()\n\t\treturn hh.hostinfo\n\t}\n\n\thostinfo := &HostInfo{\n\t\tvpnAddrs:        []netip.Addr{vpnAddr},\n\t\tHandshakePacket: make(map[uint8][]byte, 0),\n\t\trelayState: RelayState{\n\t\t\trelays:         nil,\n\t\t\trelayForByAddr: map[netip.Addr]*Relay{},\n\t\t\trelayForByIdx:  map[uint32]*Relay{},\n\t\t},\n\t}\n\n\thh := &HandshakeHostInfo{\n\t\thostinfo:  hostinfo,\n\t\tstartTime: time.Now(),\n\t}\n\thm.vpnIps[vpnAddr] = hh\n\thm.metricInitiated.Inc(1)\n\thm.OutboundHandshakeTimer.Add(vpnAddr, hm.config.tryInterval)\n\n\tif cacheCb != nil {\n\t\tcacheCb(hh)\n\t}\n\n\t// If this is a static host, we don't need to wait for the HostQueryReply\n\t// We can trigger the handshake right now\n\t_, doTrigger := hm.lightHouse.GetStaticHostList()[vpnAddr]\n\tif !doTrigger {\n\t\t// Add any calculated remotes, and trigger early handshake if one found\n\t\tdoTrigger = hm.lightHouse.addCalculatedRemotes(vpnAddr)\n\t}\n\n\tif doTrigger {\n\t\tselect {\n\t\tcase hm.trigger <- vpnAddr:\n\t\tdefault:\n\t\t}\n\t}\n\n\thm.Unlock()\n\thm.lightHouse.QueryServer(vpnAddr)\n\treturn hostinfo\n}\n\nvar (\n\tErrExistingHostInfo    = errors.New(\"existing hostinfo\")\n\tErrAlreadySeen         = errors.New(\"already seen\")\n\tErrLocalIndexCollision = errors.New(\"local index collision\")\n)\n\n// CheckAndComplete checks for any conflicts in the main and pending hostmap\n// before adding hostinfo to main. If err is nil, it was added. Otherwise err will be:\n//\n// ErrAlreadySeen if we already have an entry in the hostmap that has seen the\n// exact same handshake packet\n//\n// ErrExistingHostInfo if we already have an entry in the hostmap for this\n// VpnIp and the new handshake was older than the one we currently have\n//\n// ErrLocalIndexCollision if we already have an entry in the main or pending\n// hostmap for the hostinfo.localIndexId.\nfunc (hm *HandshakeManager) CheckAndComplete(hostinfo *HostInfo, handshakePacket uint8, f *Interface) (*HostInfo, error) {\n\thm.mainHostMap.Lock()\n\tdefer hm.mainHostMap.Unlock()\n\thm.Lock()\n\tdefer hm.Unlock()\n\n\t// Check if we already have a tunnel with this vpn ip\n\texistingHostInfo, found := hm.mainHostMap.Hosts[hostinfo.vpnAddrs[0]]\n\tif found && existingHostInfo != nil {\n\t\ttestHostInfo := existingHostInfo\n\t\tfor testHostInfo != nil {\n\t\t\t// Is it just a delayed handshake packet?\n\t\t\tif bytes.Equal(hostinfo.HandshakePacket[handshakePacket], testHostInfo.HandshakePacket[handshakePacket]) {\n\t\t\t\treturn testHostInfo, ErrAlreadySeen\n\t\t\t}\n\n\t\t\ttestHostInfo = testHostInfo.next\n\t\t}\n\n\t\t// Is this a newer handshake?\n\t\tif existingHostInfo.lastHandshakeTime >= hostinfo.lastHandshakeTime && !existingHostInfo.ConnectionState.initiator {\n\t\t\treturn existingHostInfo, ErrExistingHostInfo\n\t\t}\n\n\t\texistingHostInfo.logger(hm.l).Info(\"Taking new handshake\")\n\t}\n\n\texistingIndex, found := hm.mainHostMap.Indexes[hostinfo.localIndexId]\n\tif found {\n\t\t// We have a collision, but for a different hostinfo\n\t\treturn existingIndex, ErrLocalIndexCollision\n\t}\n\n\texistingPendingIndex, found := hm.indexes[hostinfo.localIndexId]\n\tif found && existingPendingIndex.hostinfo != hostinfo {\n\t\t// We have a collision, but for a different hostinfo\n\t\treturn existingPendingIndex.hostinfo, ErrLocalIndexCollision\n\t}\n\n\texistingRemoteIndex, found := hm.mainHostMap.RemoteIndexes[hostinfo.remoteIndexId]\n\tif found && existingRemoteIndex != nil && existingRemoteIndex.vpnAddrs[0] != hostinfo.vpnAddrs[0] {\n\t\t// We have a collision, but this can happen since we can't control\n\t\t// the remote ID. Just log about the situation as a note.\n\t\thostinfo.logger(hm.l).\n\t\t\tWithField(\"remoteIndex\", hostinfo.remoteIndexId).WithField(\"collision\", existingRemoteIndex.vpnAddrs).\n\t\t\tInfo(\"New host shadows existing host remoteIndex\")\n\t}\n\n\thm.mainHostMap.unlockedAddHostInfo(hostinfo, f)\n\treturn existingHostInfo, nil\n}\n\n// Complete is a simpler version of CheckAndComplete when we already know we\n// won't have a localIndexId collision because we already have an entry in the\n// pendingHostMap. An existing hostinfo is returned if there was one.\nfunc (hm *HandshakeManager) Complete(hostinfo *HostInfo, f *Interface) {\n\thm.mainHostMap.Lock()\n\tdefer hm.mainHostMap.Unlock()\n\thm.Lock()\n\tdefer hm.Unlock()\n\n\texistingRemoteIndex, found := hm.mainHostMap.RemoteIndexes[hostinfo.remoteIndexId]\n\tif found && existingRemoteIndex != nil {\n\t\t// We have a collision, but this can happen since we can't control\n\t\t// the remote ID. Just log about the situation as a note.\n\t\thostinfo.logger(hm.l).\n\t\t\tWithField(\"remoteIndex\", hostinfo.remoteIndexId).WithField(\"collision\", existingRemoteIndex.vpnAddrs).\n\t\t\tInfo(\"New host shadows existing host remoteIndex\")\n\t}\n\n\t// We need to remove from the pending hostmap first to avoid undoing work when after to the main hostmap.\n\thm.unlockedDeleteHostInfo(hostinfo)\n\thm.mainHostMap.unlockedAddHostInfo(hostinfo, f)\n}\n\n// allocateIndex generates a unique localIndexId for this HostInfo\n// and adds it to the pendingHostMap. Will error if we are unable to generate\n// a unique localIndexId\nfunc (hm *HandshakeManager) allocateIndex(hh *HandshakeHostInfo) error {\n\thm.mainHostMap.RLock()\n\tdefer hm.mainHostMap.RUnlock()\n\thm.Lock()\n\tdefer hm.Unlock()\n\n\tfor range 32 {\n\t\tindex, err := generateIndex(hm.l)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t_, inPending := hm.indexes[index]\n\t\t_, inMain := hm.mainHostMap.Indexes[index]\n\n\t\tif !inMain && !inPending {\n\t\t\thh.hostinfo.localIndexId = index\n\t\t\thm.indexes[index] = hh\n\t\t\treturn nil\n\t\t}\n\t}\n\n\treturn errors.New(\"failed to generate unique localIndexId\")\n}\n\nfunc (hm *HandshakeManager) DeleteHostInfo(hostinfo *HostInfo) {\n\thm.Lock()\n\tdefer hm.Unlock()\n\thm.unlockedDeleteHostInfo(hostinfo)\n}\n\nfunc (hm *HandshakeManager) unlockedDeleteHostInfo(hostinfo *HostInfo) {\n\tfor _, addr := range hostinfo.vpnAddrs {\n\t\tdelete(hm.vpnIps, addr)\n\t}\n\n\tif len(hm.vpnIps) == 0 {\n\t\thm.vpnIps = map[netip.Addr]*HandshakeHostInfo{}\n\t}\n\n\tdelete(hm.indexes, hostinfo.localIndexId)\n\tif len(hm.indexes) == 0 {\n\t\thm.indexes = map[uint32]*HandshakeHostInfo{}\n\t}\n\n\tif hm.l.Level >= logrus.DebugLevel {\n\t\thm.l.WithField(\"hostMap\", m{\"mapTotalSize\": len(hm.vpnIps),\n\t\t\t\"vpnAddrs\": hostinfo.vpnAddrs, \"indexNumber\": hostinfo.localIndexId, \"remoteIndexNumber\": hostinfo.remoteIndexId}).\n\t\t\tDebug(\"Pending hostmap hostInfo deleted\")\n\t}\n}\n\nfunc (hm *HandshakeManager) QueryVpnAddr(vpnIp netip.Addr) *HostInfo {\n\thh := hm.queryVpnIp(vpnIp)\n\tif hh != nil {\n\t\treturn hh.hostinfo\n\t}\n\treturn nil\n\n}\n\nfunc (hm *HandshakeManager) queryVpnIp(vpnIp netip.Addr) *HandshakeHostInfo {\n\thm.RLock()\n\tdefer hm.RUnlock()\n\treturn hm.vpnIps[vpnIp]\n}\n\nfunc (hm *HandshakeManager) QueryIndex(index uint32) *HostInfo {\n\thh := hm.queryIndex(index)\n\tif hh != nil {\n\t\treturn hh.hostinfo\n\t}\n\treturn nil\n}\n\nfunc (hm *HandshakeManager) queryIndex(index uint32) *HandshakeHostInfo {\n\thm.RLock()\n\tdefer hm.RUnlock()\n\treturn hm.indexes[index]\n}\n\nfunc (hm *HandshakeManager) GetPreferredRanges() []netip.Prefix {\n\treturn hm.mainHostMap.GetPreferredRanges()\n}\n\nfunc (hm *HandshakeManager) ForEachVpnAddr(f controlEach) {\n\thm.RLock()\n\tdefer hm.RUnlock()\n\n\tfor _, v := range hm.vpnIps {\n\t\tf(v.hostinfo)\n\t}\n}\n\nfunc (hm *HandshakeManager) ForEachIndex(f controlEach) {\n\thm.RLock()\n\tdefer hm.RUnlock()\n\n\tfor _, v := range hm.indexes {\n\t\tf(v.hostinfo)\n\t}\n}\n\nfunc (hm *HandshakeManager) EmitStats() {\n\thm.RLock()\n\thostLen := len(hm.vpnIps)\n\tindexLen := len(hm.indexes)\n\thm.RUnlock()\n\n\tmetrics.GetOrRegisterGauge(\"hostmap.pending.hosts\", nil).Update(int64(hostLen))\n\tmetrics.GetOrRegisterGauge(\"hostmap.pending.indexes\", nil).Update(int64(indexLen))\n\thm.mainHostMap.EmitStats()\n}\n\n// Utility functions below\n\nfunc generateIndex(l *logrus.Logger) (uint32, error) {\n\tb := make([]byte, 4)\n\n\t// Let zero mean we don't know the ID, so don't generate zero\n\tvar index uint32\n\tfor index == 0 {\n\t\t_, err := rand.Read(b)\n\t\tif err != nil {\n\t\t\tl.Errorln(err)\n\t\t\treturn 0, err\n\t\t}\n\n\t\tindex = binary.BigEndian.Uint32(b)\n\t}\n\n\tif l.Level >= logrus.DebugLevel {\n\t\tl.WithField(\"index\", index).\n\t\t\tDebug(\"Generated index\")\n\t}\n\treturn index, nil\n}\n\nfunc hsTimeout(tries int64, interval time.Duration) time.Duration {\n\treturn time.Duration(tries / 2 * ((2 * int64(interval)) + (tries-1)*int64(interval)))\n}\n"
  },
  {
    "path": "handshake_manager_test.go",
    "content": "package nebula\n\nimport (\n\t\"net/netip\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/slackhq/nebula/cert\"\n\t\"github.com/slackhq/nebula/header\"\n\t\"github.com/slackhq/nebula/test\"\n\t\"github.com/slackhq/nebula/udp\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc Test_NewHandshakeManagerVpnIp(t *testing.T) {\n\tl := test.NewLogger()\n\tlocalrange := netip.MustParsePrefix(\"10.1.1.1/24\")\n\tip := netip.MustParseAddr(\"172.1.1.2\")\n\n\tpreferredRanges := []netip.Prefix{localrange}\n\tmainHM := newHostMap(l)\n\tmainHM.preferredRanges.Store(&preferredRanges)\n\n\tlh := newTestLighthouse()\n\n\tcs := &CertState{\n\t\tinitiatingVersion: cert.Version1,\n\t\tprivateKey:        []byte{},\n\t\tv1Cert:            &dummyCert{version: cert.Version1},\n\t\tv1HandshakeBytes:  []byte{},\n\t}\n\n\tblah := NewHandshakeManager(l, mainHM, lh, &udp.NoopConn{}, defaultHandshakeConfig)\n\tblah.f = &Interface{handshakeManager: blah, pki: &PKI{}, l: l}\n\tblah.f.pki.cs.Store(cs)\n\n\tnow := time.Now()\n\tblah.NextOutboundHandshakeTimerTick(now)\n\n\ti := blah.StartHandshake(ip, nil)\n\ti2 := blah.StartHandshake(ip, nil)\n\tassert.Same(t, i, i2)\n\n\ti.remotes = NewRemoteList([]netip.Addr{}, nil)\n\n\t// Adding something to pending should not affect the main hostmap\n\tassert.Empty(t, mainHM.Hosts)\n\n\t// Confirm they are in the pending index list\n\tassert.Contains(t, blah.vpnIps, ip)\n\n\t// Jump ahead `HandshakeRetries` ticks, offset by one to get the sleep logic right\n\tfor i := 1; i <= DefaultHandshakeRetries+1; i++ {\n\t\tnow = now.Add(time.Duration(i) * DefaultHandshakeTryInterval)\n\t\tblah.NextOutboundHandshakeTimerTick(now)\n\t}\n\n\t// Confirm they are still in the pending index list\n\tassert.Contains(t, blah.vpnIps, ip)\n\n\t// Tick 1 more time, a minute will certainly flush it out\n\tblah.NextOutboundHandshakeTimerTick(now.Add(time.Minute))\n\n\t// Confirm they have been removed\n\tassert.NotContains(t, blah.vpnIps, ip)\n}\n\nfunc testCountTimerWheelEntries(tw *LockingTimerWheel[netip.Addr]) (c int) {\n\tfor _, i := range tw.t.wheel {\n\t\tn := i.Head\n\t\tfor n != nil {\n\t\t\tc++\n\t\t\tn = n.Next\n\t\t}\n\t}\n\treturn c\n}\n\ntype mockEncWriter struct {\n}\n\nfunc (mw *mockEncWriter) SendMessageToVpnAddr(_ header.MessageType, _ header.MessageSubType, _ netip.Addr, _, _, _ []byte) {\n\treturn\n}\n\nfunc (mw *mockEncWriter) SendVia(_ *HostInfo, _ *Relay, _, _, _ []byte, _ bool) {\n\treturn\n}\n\nfunc (mw *mockEncWriter) SendMessageToHostInfo(_ header.MessageType, _ header.MessageSubType, _ *HostInfo, _, _, _ []byte) {\n\treturn\n}\n\nfunc (mw *mockEncWriter) Handshake(_ netip.Addr) {}\n\nfunc (mw *mockEncWriter) GetHostInfo(_ netip.Addr) *HostInfo {\n\treturn nil\n}\n\nfunc (mw *mockEncWriter) GetCertState() *CertState {\n\treturn &CertState{initiatingVersion: cert.Version2}\n}\n"
  },
  {
    "path": "header/header.go",
    "content": "package header\n\nimport (\n\t\"encoding/binary\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n)\n\n//Version 1 header:\n// 0                                                                       31\n// |-----------------------------------------------------------------------|\n// | Version (uint4) | Type (uint4) |  Subtype (uint8) | Reserved (uint16) | 32\n// |-----------------------------------------------------------------------|\n// |                        Remote index (uint32)                          | 64\n// |-----------------------------------------------------------------------|\n// |                           Message counter                             | 96\n// |                               (uint64)                                | 128\n// |-----------------------------------------------------------------------|\n// |                               payload...                              |\n\ntype m = map[string]any\n\nconst (\n\tVersion uint8 = 1\n\tLen           = 16\n)\n\ntype MessageType uint8\ntype MessageSubType uint8\n\nconst (\n\tHandshake   MessageType = 0\n\tMessage     MessageType = 1\n\tRecvError   MessageType = 2\n\tLightHouse  MessageType = 3\n\tTest        MessageType = 4\n\tCloseTunnel MessageType = 5\n\tControl     MessageType = 6\n)\n\nvar typeMap = map[MessageType]string{\n\tHandshake:   \"handshake\",\n\tMessage:     \"message\",\n\tRecvError:   \"recvError\",\n\tLightHouse:  \"lightHouse\",\n\tTest:        \"test\",\n\tCloseTunnel: \"closeTunnel\",\n\tControl:     \"control\",\n}\n\nconst (\n\tMessageNone  MessageSubType = 0\n\tMessageRelay MessageSubType = 1\n)\n\nconst (\n\tTestRequest MessageSubType = 0\n\tTestReply   MessageSubType = 1\n)\n\nconst (\n\tHandshakeIXPSK0 MessageSubType = 0\n\tHandshakeXXPSK0 MessageSubType = 1\n)\n\nvar ErrHeaderTooShort = errors.New(\"header is too short\")\n\nvar subTypeTestMap = map[MessageSubType]string{\n\tTestRequest: \"testRequest\",\n\tTestReply:   \"testReply\",\n}\n\nvar subTypeNoneMap = map[MessageSubType]string{0: \"none\"}\n\nvar subTypeMap = map[MessageType]*map[MessageSubType]string{\n\tMessage: {\n\t\tMessageNone:  \"none\",\n\t\tMessageRelay: \"relay\",\n\t},\n\tRecvError:   &subTypeNoneMap,\n\tLightHouse:  &subTypeNoneMap,\n\tTest:        &subTypeTestMap,\n\tCloseTunnel: &subTypeNoneMap,\n\tHandshake: {\n\t\tHandshakeIXPSK0: \"ix_psk0\",\n\t},\n\tControl: &subTypeNoneMap,\n}\n\ntype H struct {\n\tVersion        uint8\n\tType           MessageType\n\tSubtype        MessageSubType\n\tReserved       uint16\n\tRemoteIndex    uint32\n\tMessageCounter uint64\n}\n\n// Encode uses the provided byte array to encode the provided header values into.\n// Byte array must be capped higher than HeaderLen or this will panic\nfunc Encode(b []byte, v uint8, t MessageType, st MessageSubType, ri uint32, c uint64) []byte {\n\tb = b[:Len]\n\tb[0] = v<<4 | byte(t&0x0f)\n\tb[1] = byte(st)\n\tbinary.BigEndian.PutUint16(b[2:4], 0)\n\tbinary.BigEndian.PutUint32(b[4:8], ri)\n\tbinary.BigEndian.PutUint64(b[8:16], c)\n\treturn b\n}\n\n// String creates a readable string representation of a header\nfunc (h *H) String() string {\n\tif h == nil {\n\t\treturn \"<nil>\"\n\t}\n\treturn fmt.Sprintf(\"ver=%d type=%s subtype=%s reserved=%#x remoteindex=%v messagecounter=%v\",\n\t\th.Version, h.TypeName(), h.SubTypeName(), h.Reserved, h.RemoteIndex, h.MessageCounter)\n}\n\n// MarshalJSON creates a json string representation of a header\nfunc (h *H) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(m{\n\t\t\"version\":        h.Version,\n\t\t\"type\":           h.TypeName(),\n\t\t\"subType\":        h.SubTypeName(),\n\t\t\"reserved\":       h.Reserved,\n\t\t\"remoteIndex\":    h.RemoteIndex,\n\t\t\"messageCounter\": h.MessageCounter,\n\t})\n}\n\n// Encode turns header into bytes\nfunc (h *H) Encode(b []byte) ([]byte, error) {\n\tif h == nil {\n\t\treturn nil, errors.New(\"nil header\")\n\t}\n\n\treturn Encode(b, h.Version, h.Type, h.Subtype, h.RemoteIndex, h.MessageCounter), nil\n}\n\n// Parse is a helper function to parses given bytes into new Header struct\nfunc (h *H) Parse(b []byte) error {\n\tif len(b) < Len {\n\t\treturn ErrHeaderTooShort\n\t}\n\t// get upper 4 bytes\n\th.Version = uint8((b[0] >> 4) & 0x0f)\n\t// get lower 4 bytes\n\th.Type = MessageType(b[0] & 0x0f)\n\th.Subtype = MessageSubType(b[1])\n\th.Reserved = binary.BigEndian.Uint16(b[2:4])\n\th.RemoteIndex = binary.BigEndian.Uint32(b[4:8])\n\th.MessageCounter = binary.BigEndian.Uint64(b[8:16])\n\treturn nil\n}\n\n// TypeName will transform the headers message type into a human string\nfunc (h *H) TypeName() string {\n\treturn TypeName(h.Type)\n}\n\n// TypeName will transform a nebula message type into a human string\nfunc TypeName(t MessageType) string {\n\tif n, ok := typeMap[t]; ok {\n\t\treturn n\n\t}\n\n\treturn \"unknown\"\n}\n\n// SubTypeName will transform the headers message sub type into a human string\nfunc (h *H) SubTypeName() string {\n\treturn SubTypeName(h.Type, h.Subtype)\n}\n\n// SubTypeName will transform a nebula message sub type into a human string\nfunc SubTypeName(t MessageType, s MessageSubType) string {\n\tif n, ok := subTypeMap[t]; ok {\n\t\tif x, ok := (*n)[s]; ok {\n\t\t\treturn x\n\t\t}\n\t}\n\n\treturn \"unknown\"\n}\n\n// NewHeader turns bytes into a header\nfunc NewHeader(b []byte) (*H, error) {\n\th := new(H)\n\tif err := h.Parse(b); err != nil {\n\t\treturn nil, err\n\t}\n\treturn h, nil\n}\n"
  },
  {
    "path": "header/header_test.go",
    "content": "package header\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype headerTest struct {\n\texpectedBytes []byte\n\t*H\n}\n\n// 0001 0010 00010010\nvar headerBigEndianTests = []headerTest{{\n\texpectedBytes: []byte{0x54, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9},\n\t// 1010 0000\n\tH: &H{\n\t\t// 1111 1+2+4+8 = 15\n\t\tVersion:        5,\n\t\tType:           4,\n\t\tSubtype:        0,\n\t\tReserved:       0,\n\t\tRemoteIndex:    10,\n\t\tMessageCounter: 9,\n\t},\n},\n}\n\nfunc TestEncode(t *testing.T) {\n\tfor _, tt := range headerBigEndianTests {\n\t\tb, err := tt.Encode(make([]byte, Len))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tassert.Equal(t, tt.expectedBytes, b)\n\t}\n}\n\nfunc TestParse(t *testing.T) {\n\tfor _, tt := range headerBigEndianTests {\n\t\tb := tt.expectedBytes\n\t\tparsedHeader := &H{}\n\t\tparsedHeader.Parse(b)\n\n\t\tif !reflect.DeepEqual(tt.H, parsedHeader) {\n\t\t\tt.Fatalf(\"got %#v; want %#v\", parsedHeader, tt.H)\n\t\t}\n\t}\n}\n\nfunc TestTypeName(t *testing.T) {\n\tassert.Equal(t, \"test\", TypeName(Test))\n\tassert.Equal(t, \"test\", (&H{Type: Test}).TypeName())\n\n\tassert.Equal(t, \"unknown\", TypeName(99))\n\tassert.Equal(t, \"unknown\", (&H{Type: 99}).TypeName())\n}\n\nfunc TestSubTypeName(t *testing.T) {\n\tassert.Equal(t, \"testRequest\", SubTypeName(Test, TestRequest))\n\tassert.Equal(t, \"testRequest\", (&H{Type: Test, Subtype: TestRequest}).SubTypeName())\n\n\tassert.Equal(t, \"unknown\", SubTypeName(99, TestRequest))\n\tassert.Equal(t, \"unknown\", (&H{Type: 99, Subtype: TestRequest}).SubTypeName())\n\n\tassert.Equal(t, \"unknown\", SubTypeName(Test, 99))\n\tassert.Equal(t, \"unknown\", (&H{Type: Test, Subtype: 99}).SubTypeName())\n\n\tassert.Equal(t, \"none\", SubTypeName(Message, 0))\n\tassert.Equal(t, \"none\", (&H{Type: Message, Subtype: 0}).SubTypeName())\n}\n\nfunc TestTypeMap(t *testing.T) {\n\t// Force people to document this stuff\n\tassert.Equal(t, map[MessageType]string{\n\t\tHandshake:   \"handshake\",\n\t\tMessage:     \"message\",\n\t\tRecvError:   \"recvError\",\n\t\tLightHouse:  \"lightHouse\",\n\t\tTest:        \"test\",\n\t\tCloseTunnel: \"closeTunnel\",\n\t\tControl:     \"control\",\n\t}, typeMap)\n\n\tassert.Equal(t, map[MessageType]*map[MessageSubType]string{\n\t\tMessage: {\n\t\t\tMessageNone:  \"none\",\n\t\t\tMessageRelay: \"relay\",\n\t\t},\n\t\tRecvError:   &subTypeNoneMap,\n\t\tLightHouse:  &subTypeNoneMap,\n\t\tTest:        &subTypeTestMap,\n\t\tCloseTunnel: &subTypeNoneMap,\n\t\tHandshake: {\n\t\t\tHandshakeIXPSK0: \"ix_psk0\",\n\t\t},\n\t\tControl: &subTypeNoneMap,\n\t}, subTypeMap)\n}\n\nfunc TestHeader_String(t *testing.T) {\n\tassert.Equal(\n\t\tt,\n\t\t\"ver=100 type=test subtype=testRequest reserved=0x63 remoteindex=98 messagecounter=97\",\n\t\t(&H{100, Test, TestRequest, 99, 98, 97}).String(),\n\t)\n}\n\nfunc TestHeader_MarshalJSON(t *testing.T) {\n\tb, err := (&H{100, Test, TestRequest, 99, 98, 97}).MarshalJSON()\n\trequire.NoError(t, err)\n\tassert.Equal(\n\t\tt,\n\t\t\"{\\\"messageCounter\\\":97,\\\"remoteIndex\\\":98,\\\"reserved\\\":99,\\\"subType\\\":\\\"testRequest\\\",\\\"type\\\":\\\"test\\\",\\\"version\\\":100}\",\n\t\tstring(b),\n\t)\n}\n"
  },
  {
    "path": "hostmap.go",
    "content": "package nebula\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"slices\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/gaissmai/bart\"\n\t\"github.com/rcrowley/go-metrics\"\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/cert\"\n\t\"github.com/slackhq/nebula/config\"\n\t\"github.com/slackhq/nebula/header\"\n)\n\nconst defaultPromoteEvery = 1000       // Count of packets sent before we try moving a tunnel to a preferred underlay ip address\nconst defaultReQueryEvery = 5000       // Count of packets sent before re-querying a hostinfo to the lighthouse\nconst defaultReQueryWait = time.Minute // Minimum amount of seconds to wait before re-querying a hostinfo the lighthouse. Evaluated every ReQueryEvery\nconst MaxRemotes = 10\n\n// MaxHostInfosPerVpnIp is the max number of hostinfos we will track for a given vpn ip\n// 5 allows for an initial handshake and each host pair re-handshaking twice\nconst MaxHostInfosPerVpnIp = 5\n\n// How long we should prevent roaming back to the previous IP.\n// This helps prevent flapping due to packets already in flight\nconst RoamingSuppressSeconds = 2\n\nconst (\n\tRequested = iota\n\tPeerRequested\n\tEstablished\n\tDisestablished\n)\n\nconst (\n\tUnknowntype = iota\n\tForwardingType\n\tTerminalType\n)\n\ntype Relay struct {\n\tType        int\n\tState       int\n\tLocalIndex  uint32\n\tRemoteIndex uint32\n\tPeerAddr    netip.Addr\n}\n\ntype HostMap struct {\n\tsync.RWMutex    //Because we concurrently read and write to our maps\n\tIndexes         map[uint32]*HostInfo\n\tRelays          map[uint32]*HostInfo // Maps a Relay IDX to a Relay HostInfo object\n\tRemoteIndexes   map[uint32]*HostInfo\n\tHosts           map[netip.Addr]*HostInfo\n\tpreferredRanges atomic.Pointer[[]netip.Prefix]\n\tl               *logrus.Logger\n}\n\n// For synchronization, treat the pointed-to Relay struct as immutable. To edit the Relay\n// struct, make a copy of an existing value, edit the fileds in the copy, and\n// then store a pointer to the new copy in both realyForBy* maps.\ntype RelayState struct {\n\tsync.RWMutex\n\n\trelays []netip.Addr // Ordered set of VpnAddrs of Hosts to use as relays to access this peer\n\t// For data race avoidance, the contents of a *Relay are treated immutably. To update a *Relay, copy the existing data,\n\t// modify what needs to be updated, and store the new modified copy in the relayForByIp and relayForByIdx maps (with\n\t// the RelayState Lock held)\n\trelayForByAddr map[netip.Addr]*Relay // Maps vpnAddr of peers for which this HostInfo is a relay to some Relay info\n\trelayForByIdx  map[uint32]*Relay     // Maps a local index to some Relay info\n}\n\nfunc (rs *RelayState) DeleteRelay(ip netip.Addr) {\n\trs.Lock()\n\tdefer rs.Unlock()\n\tfor idx, val := range rs.relays {\n\t\tif val == ip {\n\t\t\trs.relays = append(rs.relays[:idx], rs.relays[idx+1:]...)\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (rs *RelayState) UpdateRelayForByIpState(vpnIp netip.Addr, state int) {\n\trs.Lock()\n\tdefer rs.Unlock()\n\tif r, ok := rs.relayForByAddr[vpnIp]; ok {\n\t\tnewRelay := *r\n\t\tnewRelay.State = state\n\t\trs.relayForByAddr[newRelay.PeerAddr] = &newRelay\n\t\trs.relayForByIdx[newRelay.LocalIndex] = &newRelay\n\t}\n}\n\nfunc (rs *RelayState) UpdateRelayForByIdxState(idx uint32, state int) {\n\trs.Lock()\n\tdefer rs.Unlock()\n\tif r, ok := rs.relayForByIdx[idx]; ok {\n\t\tnewRelay := *r\n\t\tnewRelay.State = state\n\t\trs.relayForByAddr[newRelay.PeerAddr] = &newRelay\n\t\trs.relayForByIdx[newRelay.LocalIndex] = &newRelay\n\t}\n}\n\nfunc (rs *RelayState) CopyAllRelayFor() []*Relay {\n\trs.RLock()\n\tdefer rs.RUnlock()\n\tret := make([]*Relay, 0, len(rs.relayForByIdx))\n\tfor _, r := range rs.relayForByIdx {\n\t\tret = append(ret, r)\n\t}\n\treturn ret\n}\n\nfunc (rs *RelayState) GetRelayForByAddr(addr netip.Addr) (*Relay, bool) {\n\trs.RLock()\n\tdefer rs.RUnlock()\n\tr, ok := rs.relayForByAddr[addr]\n\treturn r, ok\n}\n\nfunc (rs *RelayState) InsertRelayTo(ip netip.Addr) {\n\trs.Lock()\n\tdefer rs.Unlock()\n\tif !slices.Contains(rs.relays, ip) {\n\t\trs.relays = append(rs.relays, ip)\n\t}\n}\n\nfunc (rs *RelayState) CopyRelayIps() []netip.Addr {\n\tret := make([]netip.Addr, len(rs.relays))\n\trs.RLock()\n\tdefer rs.RUnlock()\n\tcopy(ret, rs.relays)\n\treturn ret\n}\n\nfunc (rs *RelayState) CopyRelayForIps() []netip.Addr {\n\trs.RLock()\n\tdefer rs.RUnlock()\n\tcurrentRelays := make([]netip.Addr, 0, len(rs.relayForByAddr))\n\tfor relayIp := range rs.relayForByAddr {\n\t\tcurrentRelays = append(currentRelays, relayIp)\n\t}\n\treturn currentRelays\n}\n\nfunc (rs *RelayState) CopyRelayForIdxs() []uint32 {\n\trs.RLock()\n\tdefer rs.RUnlock()\n\tret := make([]uint32, 0, len(rs.relayForByIdx))\n\tfor i := range rs.relayForByIdx {\n\t\tret = append(ret, i)\n\t}\n\treturn ret\n}\n\nfunc (rs *RelayState) CompleteRelayByIP(vpnIp netip.Addr, remoteIdx uint32) bool {\n\trs.Lock()\n\tdefer rs.Unlock()\n\tr, ok := rs.relayForByAddr[vpnIp]\n\tif !ok {\n\t\treturn false\n\t}\n\tnewRelay := *r\n\tnewRelay.State = Established\n\tnewRelay.RemoteIndex = remoteIdx\n\trs.relayForByIdx[r.LocalIndex] = &newRelay\n\trs.relayForByAddr[r.PeerAddr] = &newRelay\n\treturn true\n}\n\nfunc (rs *RelayState) CompleteRelayByIdx(localIdx uint32, remoteIdx uint32) (*Relay, bool) {\n\trs.Lock()\n\tdefer rs.Unlock()\n\tr, ok := rs.relayForByIdx[localIdx]\n\tif !ok {\n\t\treturn nil, false\n\t}\n\tnewRelay := *r\n\tnewRelay.State = Established\n\tnewRelay.RemoteIndex = remoteIdx\n\trs.relayForByIdx[r.LocalIndex] = &newRelay\n\trs.relayForByAddr[r.PeerAddr] = &newRelay\n\treturn &newRelay, true\n}\n\nfunc (rs *RelayState) QueryRelayForByIp(vpnIp netip.Addr) (*Relay, bool) {\n\trs.RLock()\n\tdefer rs.RUnlock()\n\tr, ok := rs.relayForByAddr[vpnIp]\n\treturn r, ok\n}\n\nfunc (rs *RelayState) QueryRelayForByIdx(idx uint32) (*Relay, bool) {\n\trs.RLock()\n\tdefer rs.RUnlock()\n\tr, ok := rs.relayForByIdx[idx]\n\treturn r, ok\n}\n\nfunc (rs *RelayState) InsertRelay(ip netip.Addr, idx uint32, r *Relay) {\n\trs.Lock()\n\tdefer rs.Unlock()\n\trs.relayForByAddr[ip] = r\n\trs.relayForByIdx[idx] = r\n}\n\ntype NetworkType uint8\n\nconst (\n\tNetworkTypeUnknown NetworkType = iota\n\t// NetworkTypeVPN is a network that overlaps one or more of the vpnNetworks in our certificate\n\tNetworkTypeVPN\n\t// NetworkTypeVPNPeer is a network that does not overlap one of our networks\n\tNetworkTypeVPNPeer\n\t// NetworkTypeUnsafe is a network from Certificate.UnsafeNetworks()\n\tNetworkTypeUnsafe\n)\n\ntype HostInfo struct {\n\tremote          netip.AddrPort\n\tremotes         *RemoteList\n\tpromoteCounter  atomic.Uint32\n\tConnectionState *ConnectionState\n\tremoteIndexId   uint32\n\tlocalIndexId    uint32\n\n\t// vpnAddrs is a list of vpn addresses assigned to this host that are within our own vpn networks\n\t// The host may have other vpn addresses that are outside our\n\t// vpn networks but were removed because they are not usable\n\tvpnAddrs []netip.Addr\n\n\t// networks is a combination of specific vpn addresses (not prefixes!) and full unsafe networks assigned to this host.\n\tnetworks   *bart.Table[NetworkType]\n\trelayState RelayState\n\n\t// HandshakePacket records the packets used to create this hostinfo\n\t// We need these to avoid replayed handshake packets creating new hostinfos which causes churn\n\tHandshakePacket map[uint8][]byte\n\n\t// nextLHQuery is the earliest we can ask the lighthouse for new information.\n\t// This is used to limit lighthouse re-queries in chatty clients\n\tnextLHQuery atomic.Int64\n\n\t// lastRebindCount is the other side of Interface.rebindCount, if these values don't match then we need to ask LH\n\t// for a punch from the remote end of this tunnel. The goal being to prime their conntrack for our traffic just like\n\t// with a handshake\n\tlastRebindCount int8\n\n\t// lastHandshakeTime records the time the remote side told us about at the stage when the handshake was completed locally\n\t// Stage 1 packet will contain it if I am a responder, stage 2 packet if I am an initiator\n\t// This is used to avoid an attack where a handshake packet is replayed after some time\n\tlastHandshakeTime uint64\n\n\tlastRoam       time.Time\n\tlastRoamRemote netip.AddrPort\n\n\t// Used to track other hostinfos for this vpn ip since only 1 can be primary\n\t// Synchronised via hostmap lock and not the hostinfo lock.\n\tnext, prev *HostInfo\n\n\t//TODO: in, out, and others might benefit from being an atomic.Int32. We could collapse connectionManager pendingDeletion, relayUsed, and in/out into this 1 thing\n\tin, out, pendingDeletion atomic.Bool\n\n\t// lastUsed tracks the last time ConnectionManager checked the tunnel and it was in use.\n\t// This value will be behind against actual tunnel utilization in the hot path.\n\t// This should only be used by the ConnectionManagers ticker routine.\n\tlastUsed time.Time\n}\n\ntype ViaSender struct {\n\tUdpAddr   netip.AddrPort\n\trelayHI   *HostInfo // relayHI is the host info object of the relay\n\tremoteIdx uint32    // remoteIdx is the index included in the header of the received packet\n\trelay     *Relay    // relay contains the rest of the relay information, including the PeerIP of the host trying to communicate with us.\n\tIsRelayed bool      // IsRelayed is true if the packet was sent through a relay\n}\n\nfunc (v ViaSender) String() string {\n\tif v.IsRelayed {\n\t\treturn fmt.Sprintf(\"%s (relayed)\", v.UdpAddr)\n\t}\n\treturn v.UdpAddr.String()\n}\n\nfunc (v ViaSender) MarshalJSON() ([]byte, error) {\n\tif v.IsRelayed {\n\t\treturn json.Marshal(m{\"relay\": v.UdpAddr})\n\t}\n\treturn json.Marshal(m{\"direct\": v.UdpAddr})\n}\n\ntype cachedPacket struct {\n\tmessageType    header.MessageType\n\tmessageSubType header.MessageSubType\n\tcallback       packetCallback\n\tpacket         []byte\n}\n\ntype packetCallback func(t header.MessageType, st header.MessageSubType, h *HostInfo, p, nb, out []byte)\n\ntype cachedPacketMetrics struct {\n\tsent    metrics.Counter\n\tdropped metrics.Counter\n}\n\nfunc NewHostMapFromConfig(l *logrus.Logger, c *config.C) *HostMap {\n\thm := newHostMap(l)\n\n\thm.reload(c, true)\n\tc.RegisterReloadCallback(func(c *config.C) {\n\t\thm.reload(c, false)\n\t})\n\n\tl.WithField(\"preferredRanges\", hm.GetPreferredRanges()).\n\t\tInfo(\"Main HostMap created\")\n\n\treturn hm\n}\n\nfunc newHostMap(l *logrus.Logger) *HostMap {\n\treturn &HostMap{\n\t\tIndexes:       map[uint32]*HostInfo{},\n\t\tRelays:        map[uint32]*HostInfo{},\n\t\tRemoteIndexes: map[uint32]*HostInfo{},\n\t\tHosts:         map[netip.Addr]*HostInfo{},\n\t\tl:             l,\n\t}\n}\n\nfunc (hm *HostMap) reload(c *config.C, initial bool) {\n\tif initial || c.HasChanged(\"preferred_ranges\") {\n\t\tvar preferredRanges []netip.Prefix\n\t\trawPreferredRanges := c.GetStringSlice(\"preferred_ranges\", []string{})\n\n\t\tfor _, rawPreferredRange := range rawPreferredRanges {\n\t\t\tpreferredRange, err := netip.ParsePrefix(rawPreferredRange)\n\n\t\t\tif err != nil {\n\t\t\t\thm.l.WithError(err).WithField(\"range\", rawPreferredRanges).Warn(\"Failed to parse preferred ranges, ignoring\")\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tpreferredRanges = append(preferredRanges, preferredRange)\n\t\t}\n\n\t\toldRanges := hm.preferredRanges.Swap(&preferredRanges)\n\t\tif !initial {\n\t\t\thm.l.WithField(\"oldPreferredRanges\", *oldRanges).WithField(\"newPreferredRanges\", preferredRanges).Info(\"preferred_ranges changed\")\n\t\t}\n\t}\n}\n\n// EmitStats reports host, index, and relay counts to the stats collection system\nfunc (hm *HostMap) EmitStats() {\n\thm.RLock()\n\thostLen := len(hm.Hosts)\n\tindexLen := len(hm.Indexes)\n\tremoteIndexLen := len(hm.RemoteIndexes)\n\trelaysLen := len(hm.Relays)\n\thm.RUnlock()\n\n\tmetrics.GetOrRegisterGauge(\"hostmap.main.hosts\", nil).Update(int64(hostLen))\n\tmetrics.GetOrRegisterGauge(\"hostmap.main.indexes\", nil).Update(int64(indexLen))\n\tmetrics.GetOrRegisterGauge(\"hostmap.main.remoteIndexes\", nil).Update(int64(remoteIndexLen))\n\tmetrics.GetOrRegisterGauge(\"hostmap.main.relayIndexes\", nil).Update(int64(relaysLen))\n}\n\n// DeleteHostInfo will fully unlink the hostinfo and return true if it was the final hostinfo for this vpn ip\nfunc (hm *HostMap) DeleteHostInfo(hostinfo *HostInfo) bool {\n\t// Delete the host itself, ensuring it's not modified anymore\n\thm.Lock()\n\t// If we have a previous or next hostinfo then we are not the last one for this vpn ip\n\tfinal := (hostinfo.next == nil && hostinfo.prev == nil)\n\thm.unlockedDeleteHostInfo(hostinfo)\n\thm.Unlock()\n\n\treturn final\n}\n\nfunc (hm *HostMap) MakePrimary(hostinfo *HostInfo) {\n\thm.Lock()\n\tdefer hm.Unlock()\n\thm.unlockedMakePrimary(hostinfo)\n}\n\nfunc (hm *HostMap) unlockedMakePrimary(hostinfo *HostInfo) {\n\t// Get the current primary, if it exists\n\toldHostinfo := hm.Hosts[hostinfo.vpnAddrs[0]]\n\n\t// Every address in the hostinfo gets elevated to primary\n\tfor _, vpnAddr := range hostinfo.vpnAddrs {\n\t\t//NOTE: It is possible that we leave a dangling hostinfo here but connection manager works on\n\t\t// indexes so it should be fine.\n\t\thm.Hosts[vpnAddr] = hostinfo\n\t}\n\n\t// If we are already primary then we won't bother re-linking\n\tif oldHostinfo == hostinfo {\n\t\treturn\n\t}\n\n\t// Unlink this hostinfo\n\tif hostinfo.prev != nil {\n\t\thostinfo.prev.next = hostinfo.next\n\t}\n\tif hostinfo.next != nil {\n\t\thostinfo.next.prev = hostinfo.prev\n\t}\n\n\t// If there wasn't a previous primary then clear out any links\n\tif oldHostinfo == nil {\n\t\thostinfo.next = nil\n\t\thostinfo.prev = nil\n\t\treturn\n\t}\n\n\t// Relink the hostinfo as primary\n\thostinfo.next = oldHostinfo\n\toldHostinfo.prev = hostinfo\n\thostinfo.prev = nil\n}\n\nfunc (hm *HostMap) unlockedDeleteHostInfo(hostinfo *HostInfo) {\n\tfor _, addr := range hostinfo.vpnAddrs {\n\t\th := hm.Hosts[addr]\n\t\tfor h != nil {\n\t\t\tif h == hostinfo {\n\t\t\t\thm.unlockedInnerDeleteHostInfo(h, addr)\n\t\t\t}\n\t\t\th = h.next\n\t\t}\n\t}\n}\n\nfunc (hm *HostMap) unlockedInnerDeleteHostInfo(hostinfo *HostInfo, addr netip.Addr) {\n\tprimary, ok := hm.Hosts[addr]\n\tisLastHostinfo := hostinfo.next == nil && hostinfo.prev == nil\n\tif ok && primary == hostinfo {\n\t\t// The vpn addr pointer points to the same hostinfo as the local index id, we can remove it\n\t\tdelete(hm.Hosts, addr)\n\t\tif len(hm.Hosts) == 0 {\n\t\t\thm.Hosts = map[netip.Addr]*HostInfo{}\n\t\t}\n\n\t\tif hostinfo.next != nil {\n\t\t\t// We had more than 1 hostinfo at this vpn addr, promote the next in the list to primary\n\t\t\thm.Hosts[addr] = hostinfo.next\n\t\t\t// It is primary, there is no previous hostinfo now\n\t\t\thostinfo.next.prev = nil\n\t\t}\n\n\t} else {\n\t\t// Relink if we were in the middle of multiple hostinfos for this vpn addr\n\t\tif hostinfo.prev != nil {\n\t\t\thostinfo.prev.next = hostinfo.next\n\t\t}\n\n\t\tif hostinfo.next != nil {\n\t\t\thostinfo.next.prev = hostinfo.prev\n\t\t}\n\t}\n\n\thostinfo.next = nil\n\thostinfo.prev = nil\n\n\t// The remote index uses index ids outside our control so lets make sure we are only removing\n\t// the remote index pointer here if it points to the hostinfo we are deleting\n\thostinfo2, ok := hm.RemoteIndexes[hostinfo.remoteIndexId]\n\tif ok && hostinfo2 == hostinfo {\n\t\tdelete(hm.RemoteIndexes, hostinfo.remoteIndexId)\n\t\tif len(hm.RemoteIndexes) == 0 {\n\t\t\thm.RemoteIndexes = map[uint32]*HostInfo{}\n\t\t}\n\t}\n\n\tdelete(hm.Indexes, hostinfo.localIndexId)\n\tif len(hm.Indexes) == 0 {\n\t\thm.Indexes = map[uint32]*HostInfo{}\n\t}\n\n\tif hm.l.Level >= logrus.DebugLevel {\n\t\thm.l.WithField(\"hostMap\", m{\"mapTotalSize\": len(hm.Hosts),\n\t\t\t\"vpnAddrs\": hostinfo.vpnAddrs, \"indexNumber\": hostinfo.localIndexId, \"remoteIndexNumber\": hostinfo.remoteIndexId}).\n\t\t\tDebug(\"Hostmap hostInfo deleted\")\n\t}\n\n\tif isLastHostinfo {\n\t\t// I have lost connectivity to my peers. My relay tunnel is likely broken. Mark the next\n\t\t// hops as 'Requested' so that new relay tunnels are created in the future.\n\t\thm.unlockedDisestablishVpnAddrRelayFor(hostinfo)\n\t}\n\t// Clean up any local relay indexes for which I am acting as a relay hop\n\tfor _, localRelayIdx := range hostinfo.relayState.CopyRelayForIdxs() {\n\t\tdelete(hm.Relays, localRelayIdx)\n\t}\n}\n\nfunc (hm *HostMap) QueryIndex(index uint32) *HostInfo {\n\thm.RLock()\n\tif h, ok := hm.Indexes[index]; ok {\n\t\thm.RUnlock()\n\t\treturn h\n\t} else {\n\t\thm.RUnlock()\n\t\treturn nil\n\t}\n}\n\nfunc (hm *HostMap) QueryRelayIndex(index uint32) *HostInfo {\n\thm.RLock()\n\tif h, ok := hm.Relays[index]; ok {\n\t\thm.RUnlock()\n\t\treturn h\n\t} else {\n\t\thm.RUnlock()\n\t\treturn nil\n\t}\n}\n\nfunc (hm *HostMap) QueryReverseIndex(index uint32) *HostInfo {\n\thm.RLock()\n\tif h, ok := hm.RemoteIndexes[index]; ok {\n\t\thm.RUnlock()\n\t\treturn h\n\t} else {\n\t\thm.RUnlock()\n\t\treturn nil\n\t}\n}\n\nfunc (hm *HostMap) QueryVpnAddr(vpnIp netip.Addr) *HostInfo {\n\treturn hm.queryVpnAddr(vpnIp, nil)\n}\n\nfunc (hm *HostMap) QueryVpnAddrsRelayFor(targetIps []netip.Addr, relayHostIp netip.Addr) (*HostInfo, *Relay, error) {\n\thm.RLock()\n\tdefer hm.RUnlock()\n\n\th, ok := hm.Hosts[relayHostIp]\n\tif !ok {\n\t\treturn nil, nil, errors.New(\"unable to find host\")\n\t}\n\n\tfor h != nil {\n\t\tfor _, targetIp := range targetIps {\n\t\t\tr, ok := h.relayState.QueryRelayForByIp(targetIp)\n\t\t\tif ok && r.State == Established {\n\t\t\t\treturn h, r, nil\n\t\t\t}\n\t\t}\n\t\th = h.next\n\t}\n\n\treturn nil, nil, errors.New(\"unable to find host with relay\")\n}\n\nfunc (hm *HostMap) unlockedDisestablishVpnAddrRelayFor(hi *HostInfo) {\n\tfor _, relayHostIp := range hi.relayState.CopyRelayIps() {\n\t\tif h, ok := hm.Hosts[relayHostIp]; ok {\n\t\t\tfor h != nil {\n\t\t\t\th.relayState.UpdateRelayForByIpState(hi.vpnAddrs[0], Disestablished)\n\t\t\t\th = h.next\n\t\t\t}\n\t\t}\n\t}\n\tfor _, rs := range hi.relayState.CopyAllRelayFor() {\n\t\tif rs.Type == ForwardingType {\n\t\t\tif h, ok := hm.Hosts[rs.PeerAddr]; ok {\n\t\t\t\tfor h != nil {\n\t\t\t\t\th.relayState.UpdateRelayForByIpState(hi.vpnAddrs[0], Disestablished)\n\t\t\t\t\th = h.next\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (hm *HostMap) queryVpnAddr(vpnIp netip.Addr, promoteIfce *Interface) *HostInfo {\n\thm.RLock()\n\tif h, ok := hm.Hosts[vpnIp]; ok {\n\t\thm.RUnlock()\n\t\t// Do not attempt promotion if you are a lighthouse\n\t\tif promoteIfce != nil && !promoteIfce.lightHouse.amLighthouse {\n\t\t\th.TryPromoteBest(hm.GetPreferredRanges(), promoteIfce)\n\t\t}\n\t\treturn h\n\n\t}\n\n\thm.RUnlock()\n\treturn nil\n}\n\n// unlockedAddHostInfo assumes you have a write-lock and will add a hostinfo object to the hostmap Indexes and RemoteIndexes maps.\n// If an entry exists for the Hosts table (vpnIp -> hostinfo) then the provided hostinfo will be made primary\nfunc (hm *HostMap) unlockedAddHostInfo(hostinfo *HostInfo, f *Interface) {\n\tif f.serveDns {\n\t\tremoteCert := hostinfo.ConnectionState.peerCert\n\t\tdnsR.Add(remoteCert.Certificate.Name()+\".\", hostinfo.vpnAddrs)\n\t}\n\tfor _, addr := range hostinfo.vpnAddrs {\n\t\thm.unlockedInnerAddHostInfo(addr, hostinfo, f)\n\t}\n\n\thm.Indexes[hostinfo.localIndexId] = hostinfo\n\thm.RemoteIndexes[hostinfo.remoteIndexId] = hostinfo\n\n\tif hm.l.Level >= logrus.DebugLevel {\n\t\thm.l.WithField(\"hostMap\", m{\"vpnAddrs\": hostinfo.vpnAddrs, \"mapTotalSize\": len(hm.Hosts),\n\t\t\t\"hostinfo\": m{\"existing\": true, \"localIndexId\": hostinfo.localIndexId, \"vpnAddrs\": hostinfo.vpnAddrs}}).\n\t\t\tDebug(\"Hostmap vpnIp added\")\n\t}\n}\n\nfunc (hm *HostMap) unlockedInnerAddHostInfo(vpnAddr netip.Addr, hostinfo *HostInfo, f *Interface) {\n\texisting := hm.Hosts[vpnAddr]\n\thm.Hosts[vpnAddr] = hostinfo\n\n\tif existing != nil && existing != hostinfo {\n\t\thostinfo.next = existing\n\t\texisting.prev = hostinfo\n\t}\n\n\ti := 1\n\tcheck := hostinfo\n\tfor check != nil {\n\t\tif i > MaxHostInfosPerVpnIp {\n\t\t\thm.unlockedDeleteHostInfo(check)\n\t\t}\n\t\tcheck = check.next\n\t\ti++\n\t}\n}\n\nfunc (hm *HostMap) GetPreferredRanges() []netip.Prefix {\n\t//NOTE: if preferredRanges is ever not stored before a load this will fail to dereference a nil pointer\n\treturn *hm.preferredRanges.Load()\n}\n\nfunc (hm *HostMap) ForEachVpnAddr(f controlEach) {\n\thm.RLock()\n\tdefer hm.RUnlock()\n\n\tfor _, v := range hm.Hosts {\n\t\tf(v)\n\t}\n}\n\nfunc (hm *HostMap) ForEachIndex(f controlEach) {\n\thm.RLock()\n\tdefer hm.RUnlock()\n\n\tfor _, v := range hm.Indexes {\n\t\tf(v)\n\t}\n}\n\n// TryPromoteBest handles re-querying lighthouses and probing for better paths\n// NOTE: It is an error to call this if you are a lighthouse since they should not roam clients!\nfunc (i *HostInfo) TryPromoteBest(preferredRanges []netip.Prefix, ifce *Interface) {\n\tc := i.promoteCounter.Add(1)\n\tif c%ifce.tryPromoteEvery.Load() == 0 {\n\t\tremote := i.remote\n\n\t\t// return early if we are already on a preferred remote\n\t\tif remote.IsValid() {\n\t\t\trIP := remote.Addr()\n\t\t\tfor _, l := range preferredRanges {\n\t\t\t\tif l.Contains(rIP) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\ti.remotes.ForEach(preferredRanges, func(addr netip.AddrPort, preferred bool) {\n\t\t\tif remote.IsValid() && (!addr.IsValid() || !preferred) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Try to send a test packet to that host, this should\n\t\t\t// cause it to detect a roaming event and switch remotes\n\t\t\tifce.sendTo(header.Test, header.TestRequest, i.ConnectionState, i, addr, []byte(\"\"), make([]byte, 12, 12), make([]byte, mtu))\n\t\t})\n\t}\n\n\t// Re query our lighthouses for new remotes occasionally\n\tif c%ifce.reQueryEvery.Load() == 0 && ifce.lightHouse != nil {\n\t\tnow := time.Now().UnixNano()\n\t\tif now < i.nextLHQuery.Load() {\n\t\t\treturn\n\t\t}\n\n\t\ti.nextLHQuery.Store(now + ifce.reQueryWait.Load())\n\t\tifce.lightHouse.QueryServer(i.vpnAddrs[0])\n\t}\n}\n\nfunc (i *HostInfo) GetCert() *cert.CachedCertificate {\n\tif i.ConnectionState != nil {\n\t\treturn i.ConnectionState.peerCert\n\t}\n\treturn nil\n}\n\n// TODO: Maybe use ViaSender here?\nfunc (i *HostInfo) SetRemote(remote netip.AddrPort) {\n\t// We copy here because we likely got this remote from a source that reuses the object\n\tif i.remote != remote {\n\t\ti.remote = remote\n\t\ti.remotes.LearnRemote(i.vpnAddrs[0], remote)\n\t}\n}\n\n// SetRemoteIfPreferred returns true if the remote was changed. The lastRoam\n// time on the HostInfo will also be updated.\nfunc (i *HostInfo) SetRemoteIfPreferred(hm *HostMap, via ViaSender) bool {\n\tif via.IsRelayed {\n\t\treturn false\n\t}\n\n\tcurrentRemote := i.remote\n\tif !currentRemote.IsValid() {\n\t\ti.SetRemote(via.UdpAddr)\n\t\treturn true\n\t}\n\n\t// NOTE: We do this loop here instead of calling `isPreferred` in\n\t// remote_list.go so that we only have to loop over preferredRanges once.\n\tnewIsPreferred := false\n\tfor _, l := range hm.GetPreferredRanges() {\n\t\t// return early if we are already on a preferred remote\n\t\tif l.Contains(currentRemote.Addr()) {\n\t\t\treturn false\n\t\t}\n\n\t\tif l.Contains(via.UdpAddr.Addr()) {\n\t\t\tnewIsPreferred = true\n\t\t}\n\t}\n\n\tif newIsPreferred {\n\t\t// Consider this a roaming event\n\t\ti.lastRoam = time.Now()\n\t\ti.lastRoamRemote = currentRemote\n\n\t\ti.SetRemote(via.UdpAddr)\n\n\t\treturn true\n\t}\n\n\treturn false\n}\n\n// buildNetworks fills in the networks field of HostInfo. It accepts a cert.Certificate so you never ever mix the network types up.\nfunc (i *HostInfo) buildNetworks(myVpnNetworksTable *bart.Lite, c cert.Certificate) {\n\tif len(c.Networks()) == 1 && len(c.UnsafeNetworks()) == 0 {\n\t\tif myVpnNetworksTable.Contains(c.Networks()[0].Addr()) {\n\t\t\treturn // Simple case, no BART needed\n\t\t}\n\t}\n\n\ti.networks = new(bart.Table[NetworkType])\n\tfor _, network := range c.Networks() {\n\t\tnprefix := netip.PrefixFrom(network.Addr(), network.Addr().BitLen())\n\t\tif myVpnNetworksTable.Contains(network.Addr()) {\n\t\t\ti.networks.Insert(nprefix, NetworkTypeVPN)\n\t\t} else {\n\t\t\ti.networks.Insert(nprefix, NetworkTypeVPNPeer)\n\t\t}\n\t}\n\n\tfor _, network := range c.UnsafeNetworks() {\n\t\ti.networks.Insert(network, NetworkTypeUnsafe)\n\t}\n}\n\nfunc (i *HostInfo) logger(l *logrus.Logger) *logrus.Entry {\n\tif i == nil {\n\t\treturn logrus.NewEntry(l)\n\t}\n\n\tli := l.WithField(\"vpnAddrs\", i.vpnAddrs).\n\t\tWithField(\"localIndex\", i.localIndexId).\n\t\tWithField(\"remoteIndex\", i.remoteIndexId)\n\n\tif connState := i.ConnectionState; connState != nil {\n\t\tif peerCert := connState.peerCert; peerCert != nil {\n\t\t\tli = li.WithField(\"certName\", peerCert.Certificate.Name())\n\t\t}\n\t}\n\n\treturn li\n}\n\n// Utility functions\n\nfunc localAddrs(l *logrus.Logger, allowList *LocalAllowList) []netip.Addr {\n\t//FIXME: This function is pretty garbage\n\tvar finalAddrs []netip.Addr\n\tifaces, _ := net.Interfaces()\n\tfor _, i := range ifaces {\n\t\tallow := allowList.AllowName(i.Name)\n\t\tif l.Level >= logrus.TraceLevel {\n\t\t\tl.WithField(\"interfaceName\", i.Name).WithField(\"allow\", allow).Trace(\"localAllowList.AllowName\")\n\t\t}\n\n\t\tif !allow {\n\t\t\tcontinue\n\t\t}\n\t\taddrs, _ := i.Addrs()\n\t\tfor _, rawAddr := range addrs {\n\t\t\tvar addr netip.Addr\n\t\t\tswitch v := rawAddr.(type) {\n\t\t\tcase *net.IPNet:\n\t\t\t\t//continue\n\t\t\t\taddr, _ = netip.AddrFromSlice(v.IP)\n\t\t\tcase *net.IPAddr:\n\t\t\t\taddr, _ = netip.AddrFromSlice(v.IP)\n\t\t\t}\n\n\t\t\tif !addr.IsValid() {\n\t\t\t\tif l.Level >= logrus.DebugLevel {\n\t\t\t\t\tl.WithField(\"localAddr\", rawAddr).Debug(\"addr was invalid\")\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\taddr = addr.Unmap()\n\n\t\t\tif addr.IsLoopback() == false && addr.IsLinkLocalUnicast() == false {\n\t\t\t\tisAllowed := allowList.Allow(addr)\n\t\t\t\tif l.Level >= logrus.TraceLevel {\n\t\t\t\t\tl.WithField(\"localAddr\", addr).WithField(\"allowed\", isAllowed).Trace(\"localAllowList.Allow\")\n\t\t\t\t}\n\t\t\t\tif !isAllowed {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tfinalAddrs = append(finalAddrs, addr)\n\t\t\t}\n\t\t}\n\t}\n\treturn finalAddrs\n}\n"
  },
  {
    "path": "hostmap_test.go",
    "content": "package nebula\n\nimport (\n\t\"net/netip\"\n\t\"testing\"\n\n\t\"github.com/slackhq/nebula/config\"\n\t\"github.com/slackhq/nebula/test\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestHostMap_MakePrimary(t *testing.T) {\n\tl := test.NewLogger()\n\thm := newHostMap(l)\n\n\tf := &Interface{}\n\n\th1 := &HostInfo{vpnAddrs: []netip.Addr{netip.MustParseAddr(\"0.0.0.1\")}, localIndexId: 1}\n\th2 := &HostInfo{vpnAddrs: []netip.Addr{netip.MustParseAddr(\"0.0.0.1\")}, localIndexId: 2}\n\th3 := &HostInfo{vpnAddrs: []netip.Addr{netip.MustParseAddr(\"0.0.0.1\")}, localIndexId: 3}\n\th4 := &HostInfo{vpnAddrs: []netip.Addr{netip.MustParseAddr(\"0.0.0.1\")}, localIndexId: 4}\n\n\thm.unlockedAddHostInfo(h4, f)\n\thm.unlockedAddHostInfo(h3, f)\n\thm.unlockedAddHostInfo(h2, f)\n\thm.unlockedAddHostInfo(h1, f)\n\n\t// Make sure we go h1 -> h2 -> h3 -> h4\n\tprim := hm.QueryVpnAddr(netip.MustParseAddr(\"0.0.0.1\"))\n\tassert.Equal(t, h1.localIndexId, prim.localIndexId)\n\tassert.Equal(t, h2.localIndexId, prim.next.localIndexId)\n\tassert.Nil(t, prim.prev)\n\tassert.Equal(t, h1.localIndexId, h2.prev.localIndexId)\n\tassert.Equal(t, h3.localIndexId, h2.next.localIndexId)\n\tassert.Equal(t, h2.localIndexId, h3.prev.localIndexId)\n\tassert.Equal(t, h4.localIndexId, h3.next.localIndexId)\n\tassert.Equal(t, h3.localIndexId, h4.prev.localIndexId)\n\tassert.Nil(t, h4.next)\n\n\t// Swap h3/middle to primary\n\thm.MakePrimary(h3)\n\n\t// Make sure we go h3 -> h1 -> h2 -> h4\n\tprim = hm.QueryVpnAddr(netip.MustParseAddr(\"0.0.0.1\"))\n\tassert.Equal(t, h3.localIndexId, prim.localIndexId)\n\tassert.Equal(t, h1.localIndexId, prim.next.localIndexId)\n\tassert.Nil(t, prim.prev)\n\tassert.Equal(t, h2.localIndexId, h1.next.localIndexId)\n\tassert.Equal(t, h3.localIndexId, h1.prev.localIndexId)\n\tassert.Equal(t, h4.localIndexId, h2.next.localIndexId)\n\tassert.Equal(t, h1.localIndexId, h2.prev.localIndexId)\n\tassert.Equal(t, h2.localIndexId, h4.prev.localIndexId)\n\tassert.Nil(t, h4.next)\n\n\t// Swap h4/tail to primary\n\thm.MakePrimary(h4)\n\n\t// Make sure we go h4 -> h3 -> h1 -> h2\n\tprim = hm.QueryVpnAddr(netip.MustParseAddr(\"0.0.0.1\"))\n\tassert.Equal(t, h4.localIndexId, prim.localIndexId)\n\tassert.Equal(t, h3.localIndexId, prim.next.localIndexId)\n\tassert.Nil(t, prim.prev)\n\tassert.Equal(t, h1.localIndexId, h3.next.localIndexId)\n\tassert.Equal(t, h4.localIndexId, h3.prev.localIndexId)\n\tassert.Equal(t, h2.localIndexId, h1.next.localIndexId)\n\tassert.Equal(t, h3.localIndexId, h1.prev.localIndexId)\n\tassert.Equal(t, h1.localIndexId, h2.prev.localIndexId)\n\tassert.Nil(t, h2.next)\n\n\t// Swap h4 again should be no-op\n\thm.MakePrimary(h4)\n\n\t// Make sure we go h4 -> h3 -> h1 -> h2\n\tprim = hm.QueryVpnAddr(netip.MustParseAddr(\"0.0.0.1\"))\n\tassert.Equal(t, h4.localIndexId, prim.localIndexId)\n\tassert.Equal(t, h3.localIndexId, prim.next.localIndexId)\n\tassert.Nil(t, prim.prev)\n\tassert.Equal(t, h1.localIndexId, h3.next.localIndexId)\n\tassert.Equal(t, h4.localIndexId, h3.prev.localIndexId)\n\tassert.Equal(t, h2.localIndexId, h1.next.localIndexId)\n\tassert.Equal(t, h3.localIndexId, h1.prev.localIndexId)\n\tassert.Equal(t, h1.localIndexId, h2.prev.localIndexId)\n\tassert.Nil(t, h2.next)\n}\n\nfunc TestHostMap_DeleteHostInfo(t *testing.T) {\n\tl := test.NewLogger()\n\thm := newHostMap(l)\n\n\tf := &Interface{}\n\n\th1 := &HostInfo{vpnAddrs: []netip.Addr{netip.MustParseAddr(\"0.0.0.1\")}, localIndexId: 1}\n\th2 := &HostInfo{vpnAddrs: []netip.Addr{netip.MustParseAddr(\"0.0.0.1\")}, localIndexId: 2}\n\th3 := &HostInfo{vpnAddrs: []netip.Addr{netip.MustParseAddr(\"0.0.0.1\")}, localIndexId: 3}\n\th4 := &HostInfo{vpnAddrs: []netip.Addr{netip.MustParseAddr(\"0.0.0.1\")}, localIndexId: 4}\n\th5 := &HostInfo{vpnAddrs: []netip.Addr{netip.MustParseAddr(\"0.0.0.1\")}, localIndexId: 5}\n\th6 := &HostInfo{vpnAddrs: []netip.Addr{netip.MustParseAddr(\"0.0.0.1\")}, localIndexId: 6}\n\n\thm.unlockedAddHostInfo(h6, f)\n\thm.unlockedAddHostInfo(h5, f)\n\thm.unlockedAddHostInfo(h4, f)\n\thm.unlockedAddHostInfo(h3, f)\n\thm.unlockedAddHostInfo(h2, f)\n\thm.unlockedAddHostInfo(h1, f)\n\n\t// h6 should be deleted\n\tassert.Nil(t, h6.next)\n\tassert.Nil(t, h6.prev)\n\th := hm.QueryIndex(h6.localIndexId)\n\tassert.Nil(t, h)\n\n\t// Make sure we go h1 -> h2 -> h3 -> h4 -> h5\n\tprim := hm.QueryVpnAddr(netip.MustParseAddr(\"0.0.0.1\"))\n\tassert.Equal(t, h1.localIndexId, prim.localIndexId)\n\tassert.Equal(t, h2.localIndexId, prim.next.localIndexId)\n\tassert.Nil(t, prim.prev)\n\tassert.Equal(t, h1.localIndexId, h2.prev.localIndexId)\n\tassert.Equal(t, h3.localIndexId, h2.next.localIndexId)\n\tassert.Equal(t, h2.localIndexId, h3.prev.localIndexId)\n\tassert.Equal(t, h4.localIndexId, h3.next.localIndexId)\n\tassert.Equal(t, h3.localIndexId, h4.prev.localIndexId)\n\tassert.Equal(t, h5.localIndexId, h4.next.localIndexId)\n\tassert.Equal(t, h4.localIndexId, h5.prev.localIndexId)\n\tassert.Nil(t, h5.next)\n\n\t// Delete primary\n\thm.DeleteHostInfo(h1)\n\tassert.Nil(t, h1.prev)\n\tassert.Nil(t, h1.next)\n\n\t// Make sure we go h2 -> h3 -> h4 -> h5\n\tprim = hm.QueryVpnAddr(netip.MustParseAddr(\"0.0.0.1\"))\n\tassert.Equal(t, h2.localIndexId, prim.localIndexId)\n\tassert.Equal(t, h3.localIndexId, prim.next.localIndexId)\n\tassert.Nil(t, prim.prev)\n\tassert.Equal(t, h3.localIndexId, h2.next.localIndexId)\n\tassert.Equal(t, h2.localIndexId, h3.prev.localIndexId)\n\tassert.Equal(t, h4.localIndexId, h3.next.localIndexId)\n\tassert.Equal(t, h3.localIndexId, h4.prev.localIndexId)\n\tassert.Equal(t, h5.localIndexId, h4.next.localIndexId)\n\tassert.Equal(t, h4.localIndexId, h5.prev.localIndexId)\n\tassert.Nil(t, h5.next)\n\n\t// Delete in the middle\n\thm.DeleteHostInfo(h3)\n\tassert.Nil(t, h3.prev)\n\tassert.Nil(t, h3.next)\n\n\t// Make sure we go h2 -> h4 -> h5\n\tprim = hm.QueryVpnAddr(netip.MustParseAddr(\"0.0.0.1\"))\n\tassert.Equal(t, h2.localIndexId, prim.localIndexId)\n\tassert.Equal(t, h4.localIndexId, prim.next.localIndexId)\n\tassert.Nil(t, prim.prev)\n\tassert.Equal(t, h4.localIndexId, h2.next.localIndexId)\n\tassert.Equal(t, h2.localIndexId, h4.prev.localIndexId)\n\tassert.Equal(t, h5.localIndexId, h4.next.localIndexId)\n\tassert.Equal(t, h4.localIndexId, h5.prev.localIndexId)\n\tassert.Nil(t, h5.next)\n\n\t// Delete the tail\n\thm.DeleteHostInfo(h5)\n\tassert.Nil(t, h5.prev)\n\tassert.Nil(t, h5.next)\n\n\t// Make sure we go h2 -> h4\n\tprim = hm.QueryVpnAddr(netip.MustParseAddr(\"0.0.0.1\"))\n\tassert.Equal(t, h2.localIndexId, prim.localIndexId)\n\tassert.Equal(t, h4.localIndexId, prim.next.localIndexId)\n\tassert.Nil(t, prim.prev)\n\tassert.Equal(t, h4.localIndexId, h2.next.localIndexId)\n\tassert.Equal(t, h2.localIndexId, h4.prev.localIndexId)\n\tassert.Nil(t, h4.next)\n\n\t// Delete the head\n\thm.DeleteHostInfo(h2)\n\tassert.Nil(t, h2.prev)\n\tassert.Nil(t, h2.next)\n\n\t// Make sure we only have h4\n\tprim = hm.QueryVpnAddr(netip.MustParseAddr(\"0.0.0.1\"))\n\tassert.Equal(t, h4.localIndexId, prim.localIndexId)\n\tassert.Nil(t, prim.prev)\n\tassert.Nil(t, prim.next)\n\tassert.Nil(t, h4.next)\n\n\t// Delete the only item\n\thm.DeleteHostInfo(h4)\n\tassert.Nil(t, h4.prev)\n\tassert.Nil(t, h4.next)\n\n\t// Make sure we have nil\n\tprim = hm.QueryVpnAddr(netip.MustParseAddr(\"0.0.0.1\"))\n\tassert.Nil(t, prim)\n}\n\nfunc TestHostMap_reload(t *testing.T) {\n\tl := test.NewLogger()\n\tc := config.NewC(l)\n\n\thm := NewHostMapFromConfig(l, c)\n\n\ttoS := func(ipn []netip.Prefix) []string {\n\t\tvar s []string\n\t\tfor _, n := range ipn {\n\t\t\ts = append(s, n.String())\n\t\t}\n\t\treturn s\n\t}\n\n\tassert.Empty(t, hm.GetPreferredRanges())\n\n\tc.ReloadConfigString(\"preferred_ranges: [1.1.1.0/24, 10.1.1.0/24]\")\n\tassert.Equal(t, []string{\"1.1.1.0/24\", \"10.1.1.0/24\"}, toS(hm.GetPreferredRanges()))\n\n\tc.ReloadConfigString(\"preferred_ranges: [1.1.1.1/32]\")\n\tassert.Equal(t, []string{\"1.1.1.1/32\"}, toS(hm.GetPreferredRanges()))\n}\n\nfunc TestHostMap_RelayState(t *testing.T) {\n\th1 := &HostInfo{vpnAddrs: []netip.Addr{netip.MustParseAddr(\"0.0.0.1\")}, localIndexId: 1}\n\ta1 := netip.MustParseAddr(\"::1\")\n\ta2 := netip.MustParseAddr(\"2001::1\")\n\n\th1.relayState.InsertRelayTo(a1)\n\tassert.Equal(t, []netip.Addr{a1}, h1.relayState.relays)\n\th1.relayState.InsertRelayTo(a2)\n\tassert.Equal(t, []netip.Addr{a1, a2}, h1.relayState.relays)\n\t// Ensure that the first relay added is the first one returned in the copy\n\tcurrentRelays := h1.relayState.CopyRelayIps()\n\trequire.Len(t, currentRelays, 2)\n\tassert.Equal(t, a1, currentRelays[0])\n\n\t// Deleting the last one in the list works ok\n\th1.relayState.DeleteRelay(a2)\n\tassert.Equal(t, []netip.Addr{a1}, h1.relayState.relays)\n\n\t// Deleting an element not in the list works ok\n\th1.relayState.DeleteRelay(a2)\n\tassert.Equal(t, []netip.Addr{a1}, h1.relayState.relays)\n\n\t// Deleting the only element in the list works ok\n\th1.relayState.DeleteRelay(a1)\n\tassert.Equal(t, []netip.Addr{}, h1.relayState.relays)\n\n}\n"
  },
  {
    "path": "hostmap_tester.go",
    "content": "//go:build e2e_testing\n\npackage nebula\n\n// This file contains functions used to export information to the e2e testing framework\n\nimport (\n\t\"net/netip\"\n)\n\nfunc (i *HostInfo) GetVpnAddrs() []netip.Addr {\n\treturn i.vpnAddrs\n}\n\nfunc (i *HostInfo) GetLocalIndex() uint32 {\n\treturn i.localIndexId\n}\n\nfunc (i *HostInfo) GetRemoteIndex() uint32 {\n\treturn i.remoteIndexId\n}\n\nfunc (i *HostInfo) GetRelayState() *RelayState {\n\treturn &i.relayState\n}\n"
  },
  {
    "path": "inside.go",
    "content": "package nebula\n\nimport (\n\t\"net/netip\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/firewall\"\n\t\"github.com/slackhq/nebula/header\"\n\t\"github.com/slackhq/nebula/iputil\"\n\t\"github.com/slackhq/nebula/noiseutil\"\n\t\"github.com/slackhq/nebula/routing\"\n)\n\nfunc (f *Interface) consumeInsidePacket(packet []byte, fwPacket *firewall.Packet, nb, out []byte, q int, localCache firewall.ConntrackCache) {\n\terr := newPacket(packet, false, fwPacket)\n\tif err != nil {\n\t\tif f.l.Level >= logrus.DebugLevel {\n\t\t\tf.l.WithField(\"packet\", packet).Debugf(\"Error while validating outbound packet: %s\", err)\n\t\t}\n\t\treturn\n\t}\n\n\t// Ignore local broadcast packets\n\tif f.dropLocalBroadcast {\n\t\tif f.myBroadcastAddrsTable.Contains(fwPacket.RemoteAddr) {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif f.myVpnAddrsTable.Contains(fwPacket.RemoteAddr) {\n\t\t// Immediately forward packets from self to self.\n\t\t// This should only happen on Darwin-based and FreeBSD hosts, which\n\t\t// routes packets from the Nebula addr to the Nebula addr through the Nebula\n\t\t// TUN device.\n\t\tif immediatelyForwardToSelf {\n\t\t\t_, err := f.readers[q].Write(packet)\n\t\t\tif err != nil {\n\t\t\t\tf.l.WithError(err).Error(\"Failed to forward to tun\")\n\t\t\t}\n\t\t}\n\t\t// Otherwise, drop. On linux, we should never see these packets - Linux\n\t\t// routes packets from the nebula addr to the nebula addr through the loopback device.\n\t\treturn\n\t}\n\n\t// Ignore multicast packets\n\tif f.dropMulticast && fwPacket.RemoteAddr.IsMulticast() {\n\t\treturn\n\t}\n\n\thostinfo, ready := f.getOrHandshakeConsiderRouting(fwPacket, func(hh *HandshakeHostInfo) {\n\t\thh.cachePacket(f.l, header.Message, 0, packet, f.sendMessageNow, f.cachedPacketMetrics)\n\t})\n\n\tif hostinfo == nil {\n\t\tf.rejectInside(packet, out, q)\n\t\tif f.l.Level >= logrus.DebugLevel {\n\t\t\tf.l.WithField(\"vpnAddr\", fwPacket.RemoteAddr).\n\t\t\t\tWithField(\"fwPacket\", fwPacket).\n\t\t\t\tDebugln(\"dropping outbound packet, vpnAddr not in our vpn networks or in unsafe networks\")\n\t\t}\n\t\treturn\n\t}\n\n\tif !ready {\n\t\treturn\n\t}\n\n\tdropReason := f.firewall.Drop(*fwPacket, false, hostinfo, f.pki.GetCAPool(), localCache)\n\tif dropReason == nil {\n\t\tf.sendNoMetrics(header.Message, 0, hostinfo.ConnectionState, hostinfo, netip.AddrPort{}, packet, nb, out, q)\n\n\t} else {\n\t\tf.rejectInside(packet, out, q)\n\t\tif f.l.Level >= logrus.DebugLevel {\n\t\t\thostinfo.logger(f.l).\n\t\t\t\tWithField(\"fwPacket\", fwPacket).\n\t\t\t\tWithField(\"reason\", dropReason).\n\t\t\t\tDebugln(\"dropping outbound packet\")\n\t\t}\n\t}\n}\n\nfunc (f *Interface) rejectInside(packet []byte, out []byte, q int) {\n\tif !f.firewall.InSendReject {\n\t\treturn\n\t}\n\n\tout = iputil.CreateRejectPacket(packet, out)\n\tif len(out) == 0 {\n\t\treturn\n\t}\n\n\t_, err := f.readers[q].Write(out)\n\tif err != nil {\n\t\tf.l.WithError(err).Error(\"Failed to write to tun\")\n\t}\n}\n\nfunc (f *Interface) rejectOutside(packet []byte, ci *ConnectionState, hostinfo *HostInfo, nb, out []byte, q int) {\n\tif !f.firewall.OutSendReject {\n\t\treturn\n\t}\n\n\tout = iputil.CreateRejectPacket(packet, out)\n\tif len(out) == 0 {\n\t\treturn\n\t}\n\n\tif len(out) > iputil.MaxRejectPacketSize {\n\t\tif f.l.GetLevel() >= logrus.InfoLevel {\n\t\t\tf.l.\n\t\t\t\tWithField(\"packet\", packet).\n\t\t\t\tWithField(\"outPacket\", out).\n\t\t\t\tInfo(\"rejectOutside: packet too big, not sending\")\n\t\t}\n\t\treturn\n\t}\n\n\tf.sendNoMetrics(header.Message, 0, ci, hostinfo, netip.AddrPort{}, out, nb, packet, q)\n}\n\n// Handshake will attempt to initiate a tunnel with the provided vpn address. This is a no-op if the tunnel is already established or being established\n// it does not check if it is within our vpn networks!\nfunc (f *Interface) Handshake(vpnAddr netip.Addr) {\n\tf.handshakeManager.GetOrHandshake(vpnAddr, nil)\n}\n\n// getOrHandshakeNoRouting returns nil if the vpnAddr is not routable.\n// If the 2nd return var is false then the hostinfo is not ready to be used in a tunnel\nfunc (f *Interface) getOrHandshakeNoRouting(vpnAddr netip.Addr, cacheCallback func(*HandshakeHostInfo)) (*HostInfo, bool) {\n\tif f.myVpnNetworksTable.Contains(vpnAddr) {\n\t\treturn f.handshakeManager.GetOrHandshake(vpnAddr, cacheCallback)\n\t}\n\n\treturn nil, false\n}\n\n// getOrHandshakeConsiderRouting will try to find the HostInfo to handle this packet, starting a handshake if necessary.\n// If the 2nd return var is false then the hostinfo is not ready to be used in a tunnel.\nfunc (f *Interface) getOrHandshakeConsiderRouting(fwPacket *firewall.Packet, cacheCallback func(*HandshakeHostInfo)) (*HostInfo, bool) {\n\tdestinationAddr := fwPacket.RemoteAddr\n\n\thostinfo, ready := f.getOrHandshakeNoRouting(destinationAddr, cacheCallback)\n\n\t// Host is inside the mesh, no routing required\n\tif hostinfo != nil {\n\t\treturn hostinfo, ready\n\t}\n\n\tgateways := f.inside.RoutesFor(destinationAddr)\n\n\tswitch len(gateways) {\n\tcase 0:\n\t\treturn nil, false\n\tcase 1:\n\t\t// Single gateway route\n\t\treturn f.handshakeManager.GetOrHandshake(gateways[0].Addr(), cacheCallback)\n\tdefault:\n\t\t// Multi gateway route, perform ECMP categorization\n\t\tgatewayAddr, balancingOk := routing.BalancePacket(fwPacket, gateways)\n\n\t\tif !balancingOk {\n\t\t\t// This happens if the gateway buckets were not calculated, this _should_ never happen\n\t\t\tf.l.Error(\"Gateway buckets not calculated, fallback from ECMP to random routing. Please report this bug.\")\n\t\t}\n\n\t\tvar handshakeInfoForChosenGateway *HandshakeHostInfo\n\t\tvar hhReceiver = func(hh *HandshakeHostInfo) {\n\t\t\thandshakeInfoForChosenGateway = hh\n\t\t}\n\n\t\t// Store the handshakeHostInfo for later.\n\t\t// If this node is not reachable we will attempt other nodes, if none are reachable we will\n\t\t// cache the packet for this gateway.\n\t\tif hostinfo, ready = f.handshakeManager.GetOrHandshake(gatewayAddr, hhReceiver); ready {\n\t\t\treturn hostinfo, true\n\t\t}\n\n\t\t// It appears the selected gateway cannot be reached, find another gateway to fallback on.\n\t\t// The current implementation breaks ECMP but that seems better than no connectivity.\n\t\t// If ECMP is also required when a gateway is down then connectivity status\n\t\t// for each gateway needs to be kept and the weights recalculated when they go up or down.\n\t\t// This would also need to interact with unsafe_route updates through reloading the config or\n\t\t// use of the use_system_route_table option\n\n\t\tif f.l.Level >= logrus.DebugLevel {\n\t\t\tf.l.WithField(\"destination\", destinationAddr).\n\t\t\t\tWithField(\"originalGateway\", gatewayAddr).\n\t\t\t\tDebugln(\"Calculated gateway for ECMP not available, attempting other gateways\")\n\t\t}\n\n\t\tfor i := range gateways {\n\t\t\t// Skip the gateway that failed previously\n\t\t\tif gateways[i].Addr() == gatewayAddr {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// We do not need the HandshakeHostInfo since we cache the packet in the originally chosen gateway\n\t\t\tif hostinfo, ready = f.handshakeManager.GetOrHandshake(gateways[i].Addr(), nil); ready {\n\t\t\t\treturn hostinfo, true\n\t\t\t}\n\t\t}\n\n\t\t// No gateways reachable, cache the packet in the originally chosen gateway\n\t\tcacheCallback(handshakeInfoForChosenGateway)\n\t\treturn hostinfo, false\n\t}\n\n}\n\nfunc (f *Interface) sendMessageNow(t header.MessageType, st header.MessageSubType, hostinfo *HostInfo, p, nb, out []byte) {\n\tfp := &firewall.Packet{}\n\terr := newPacket(p, false, fp)\n\tif err != nil {\n\t\tf.l.Warnf(\"error while parsing outgoing packet for firewall check; %v\", err)\n\t\treturn\n\t}\n\n\t// check if packet is in outbound fw rules\n\tdropReason := f.firewall.Drop(*fp, false, hostinfo, f.pki.GetCAPool(), nil)\n\tif dropReason != nil {\n\t\tif f.l.Level >= logrus.DebugLevel {\n\t\t\tf.l.WithField(\"fwPacket\", fp).\n\t\t\t\tWithField(\"reason\", dropReason).\n\t\t\t\tDebugln(\"dropping cached packet\")\n\t\t}\n\t\treturn\n\t}\n\n\tf.sendNoMetrics(header.Message, st, hostinfo.ConnectionState, hostinfo, netip.AddrPort{}, p, nb, out, 0)\n}\n\n// SendMessageToVpnAddr handles real addr:port lookup and sends to the current best known address for vpnAddr.\n// This function ignores myVpnNetworksTable, and will always attempt to treat the address as a vpnAddr\nfunc (f *Interface) SendMessageToVpnAddr(t header.MessageType, st header.MessageSubType, vpnAddr netip.Addr, p, nb, out []byte) {\n\thostInfo, ready := f.handshakeManager.GetOrHandshake(vpnAddr, func(hh *HandshakeHostInfo) {\n\t\thh.cachePacket(f.l, t, st, p, f.SendMessageToHostInfo, f.cachedPacketMetrics)\n\t})\n\n\tif hostInfo == nil {\n\t\tif f.l.Level >= logrus.DebugLevel {\n\t\t\tf.l.WithField(\"vpnAddr\", vpnAddr).\n\t\t\t\tDebugln(\"dropping SendMessageToVpnAddr, vpnAddr not in our vpn networks or in unsafe routes\")\n\t\t}\n\t\treturn\n\t}\n\n\tif !ready {\n\t\treturn\n\t}\n\n\tf.SendMessageToHostInfo(t, st, hostInfo, p, nb, out)\n}\n\nfunc (f *Interface) SendMessageToHostInfo(t header.MessageType, st header.MessageSubType, hi *HostInfo, p, nb, out []byte) {\n\tf.send(t, st, hi.ConnectionState, hi, p, nb, out)\n}\n\nfunc (f *Interface) send(t header.MessageType, st header.MessageSubType, ci *ConnectionState, hostinfo *HostInfo, p, nb, out []byte) {\n\tf.messageMetrics.Tx(t, st, 1)\n\tf.sendNoMetrics(t, st, ci, hostinfo, netip.AddrPort{}, p, nb, out, 0)\n}\n\nfunc (f *Interface) sendTo(t header.MessageType, st header.MessageSubType, ci *ConnectionState, hostinfo *HostInfo, remote netip.AddrPort, p, nb, out []byte) {\n\tf.messageMetrics.Tx(t, st, 1)\n\tf.sendNoMetrics(t, st, ci, hostinfo, remote, p, nb, out, 0)\n}\n\n// SendVia sends a payload through a Relay tunnel. No authentication or encryption is done\n// to the payload for the ultimate target host, making this a useful method for sending\n// handshake messages to peers through relay tunnels.\n// via is the HostInfo through which the message is relayed.\n// ad is the plaintext data to authenticate, but not encrypt\n// nb is a buffer used to store the nonce value, re-used for performance reasons.\n// out is a buffer used to store the result of the Encrypt operation\n// q indicates which writer to use to send the packet.\nfunc (f *Interface) SendVia(via *HostInfo,\n\trelay *Relay,\n\tad,\n\tnb,\n\tout []byte,\n\tnocopy bool,\n) {\n\tif noiseutil.EncryptLockNeeded {\n\t\t// NOTE: for goboring AESGCMTLS we need to lock because of the nonce check\n\t\tvia.ConnectionState.writeLock.Lock()\n\t}\n\tc := via.ConnectionState.messageCounter.Add(1)\n\n\tout = header.Encode(out, header.Version, header.Message, header.MessageRelay, relay.RemoteIndex, c)\n\tf.connectionManager.Out(via)\n\n\t// Authenticate the header and payload, but do not encrypt for this message type.\n\t// The payload consists of the inner, unencrypted Nebula header, as well as the end-to-end encrypted payload.\n\tif len(out)+len(ad)+via.ConnectionState.eKey.Overhead() > cap(out) {\n\t\tif noiseutil.EncryptLockNeeded {\n\t\t\tvia.ConnectionState.writeLock.Unlock()\n\t\t}\n\t\tvia.logger(f.l).\n\t\t\tWithField(\"outCap\", cap(out)).\n\t\t\tWithField(\"payloadLen\", len(ad)).\n\t\t\tWithField(\"headerLen\", len(out)).\n\t\t\tWithField(\"cipherOverhead\", via.ConnectionState.eKey.Overhead()).\n\t\t\tError(\"SendVia out buffer not large enough for relay\")\n\t\treturn\n\t}\n\n\t// The header bytes are written to the 'out' slice; Grow the slice to hold the header and associated data payload.\n\toffset := len(out)\n\tout = out[:offset+len(ad)]\n\n\t// In one call path, the associated data _is_ already stored in out. In other call paths, the associated data must\n\t// be copied into 'out'.\n\tif !nocopy {\n\t\tcopy(out[offset:], ad)\n\t}\n\n\tvar err error\n\tout, err = via.ConnectionState.eKey.EncryptDanger(out, out, nil, c, nb)\n\tif noiseutil.EncryptLockNeeded {\n\t\tvia.ConnectionState.writeLock.Unlock()\n\t}\n\tif err != nil {\n\t\tvia.logger(f.l).WithError(err).Info(\"Failed to EncryptDanger in sendVia\")\n\t\treturn\n\t}\n\terr = f.writers[0].WriteTo(out, via.remote)\n\tif err != nil {\n\t\tvia.logger(f.l).WithError(err).Info(\"Failed to WriteTo in sendVia\")\n\t}\n\tf.connectionManager.RelayUsed(relay.LocalIndex)\n}\n\nfunc (f *Interface) sendNoMetrics(t header.MessageType, st header.MessageSubType, ci *ConnectionState, hostinfo *HostInfo, remote netip.AddrPort, p, nb, out []byte, q int) {\n\tif ci.eKey == nil {\n\t\treturn\n\t}\n\tuseRelay := !remote.IsValid() && !hostinfo.remote.IsValid()\n\tfullOut := out\n\n\tif useRelay {\n\t\tif len(out) < header.Len {\n\t\t\t// out always has a capacity of mtu, but not always a length greater than the header.Len.\n\t\t\t// Grow it to make sure the next operation works.\n\t\t\tout = out[:header.Len]\n\t\t}\n\t\t// Save a header's worth of data at the front of the 'out' buffer.\n\t\tout = out[header.Len:]\n\t}\n\n\tif noiseutil.EncryptLockNeeded {\n\t\t// NOTE: for goboring AESGCMTLS we need to lock because of the nonce check\n\t\tci.writeLock.Lock()\n\t}\n\tc := ci.messageCounter.Add(1)\n\n\t//l.WithField(\"trace\", string(debug.Stack())).Error(\"out Header \", &Header{Version, t, st, 0, hostinfo.remoteIndexId, c}, p)\n\tout = header.Encode(out, header.Version, t, st, hostinfo.remoteIndexId, c)\n\tf.connectionManager.Out(hostinfo)\n\n\t// Query our LH if we haven't since the last time we've been rebound, this will cause the remote to punch against\n\t// all our addrs and enable a faster roaming.\n\tif t != header.CloseTunnel && hostinfo.lastRebindCount != f.rebindCount {\n\t\t//NOTE: there is an update hole if a tunnel isn't used and exactly 256 rebinds occur before the tunnel is\n\t\t// finally used again. This tunnel would eventually be torn down and recreated if this action didn't help.\n\t\tf.lightHouse.QueryServer(hostinfo.vpnAddrs[0])\n\t\thostinfo.lastRebindCount = f.rebindCount\n\t\tif f.l.Level >= logrus.DebugLevel {\n\t\t\tf.l.WithField(\"vpnAddrs\", hostinfo.vpnAddrs).Debug(\"Lighthouse update triggered for punch due to rebind counter\")\n\t\t}\n\t}\n\n\tvar err error\n\tout, err = ci.eKey.EncryptDanger(out, out, p, c, nb)\n\tif noiseutil.EncryptLockNeeded {\n\t\tci.writeLock.Unlock()\n\t}\n\tif err != nil {\n\t\thostinfo.logger(f.l).WithError(err).\n\t\t\tWithField(\"udpAddr\", remote).WithField(\"counter\", c).\n\t\t\tWithField(\"attemptedCounter\", c).\n\t\t\tError(\"Failed to encrypt outgoing packet\")\n\t\treturn\n\t}\n\n\tif remote.IsValid() {\n\t\terr = f.writers[q].WriteTo(out, remote)\n\t\tif err != nil {\n\t\t\thostinfo.logger(f.l).WithError(err).\n\t\t\t\tWithField(\"udpAddr\", remote).Error(\"Failed to write outgoing packet\")\n\t\t}\n\t} else if hostinfo.remote.IsValid() {\n\t\terr = f.writers[q].WriteTo(out, hostinfo.remote)\n\t\tif err != nil {\n\t\t\thostinfo.logger(f.l).WithError(err).\n\t\t\t\tWithField(\"udpAddr\", remote).Error(\"Failed to write outgoing packet\")\n\t\t}\n\t} else {\n\t\t// Try to send via a relay\n\t\tfor _, relayIP := range hostinfo.relayState.CopyRelayIps() {\n\t\t\trelayHostInfo, relay, err := f.hostMap.QueryVpnAddrsRelayFor(hostinfo.vpnAddrs, relayIP)\n\t\t\tif err != nil {\n\t\t\t\thostinfo.relayState.DeleteRelay(relayIP)\n\t\t\t\thostinfo.logger(f.l).WithField(\"relay\", relayIP).WithError(err).Info(\"sendNoMetrics failed to find HostInfo\")\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tf.SendVia(relayHostInfo, relay, out, nb, fullOut[:header.Len+len(out)], true)\n\t\t\tbreak\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "inside_bsd.go",
    "content": "//go:build darwin || dragonfly || freebsd || netbsd || openbsd\n\npackage nebula\n\nconst immediatelyForwardToSelf bool = true\n"
  },
  {
    "path": "inside_generic.go",
    "content": "//go:build !darwin && !dragonfly && !freebsd && !netbsd && !openbsd\n\npackage nebula\n\nconst immediatelyForwardToSelf bool = false\n"
  },
  {
    "path": "interface.go",
    "content": "package nebula\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/netip\"\n\t\"os\"\n\t\"runtime\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/gaissmai/bart\"\n\t\"github.com/rcrowley/go-metrics\"\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/config\"\n\t\"github.com/slackhq/nebula/firewall\"\n\t\"github.com/slackhq/nebula/header\"\n\t\"github.com/slackhq/nebula/overlay\"\n\t\"github.com/slackhq/nebula/udp\"\n)\n\nconst mtu = 9001\n\ntype InterfaceConfig struct {\n\tHostMap            *HostMap\n\tOutside            udp.Conn\n\tInside             overlay.Device\n\tpki                *PKI\n\tCipher             string\n\tFirewall           *Firewall\n\tServeDns           bool\n\tHandshakeManager   *HandshakeManager\n\tlightHouse         *LightHouse\n\tconnectionManager  *connectionManager\n\tDropLocalBroadcast bool\n\tDropMulticast      bool\n\troutines           int\n\tMessageMetrics     *MessageMetrics\n\tversion            string\n\trelayManager       *relayManager\n\tpunchy             *Punchy\n\n\ttryPromoteEvery uint32\n\treQueryEvery    uint32\n\treQueryWait     time.Duration\n\n\tConntrackCacheTimeout time.Duration\n\tl                     *logrus.Logger\n}\n\ntype Interface struct {\n\thostMap               *HostMap\n\toutside               udp.Conn\n\tinside                overlay.Device\n\tpki                   *PKI\n\tfirewall              *Firewall\n\tconnectionManager     *connectionManager\n\thandshakeManager      *HandshakeManager\n\tserveDns              bool\n\tcreateTime            time.Time\n\tlightHouse            *LightHouse\n\tmyBroadcastAddrsTable *bart.Lite\n\tmyVpnAddrs            []netip.Addr // A list of addresses assigned to us via our certificate\n\tmyVpnAddrsTable       *bart.Lite\n\tmyVpnNetworks         []netip.Prefix // A list of networks assigned to us via our certificate\n\tmyVpnNetworksTable    *bart.Lite\n\tdropLocalBroadcast    bool\n\tdropMulticast         bool\n\troutines              int\n\tdisconnectInvalid     atomic.Bool\n\tclosed                atomic.Bool\n\trelayManager          *relayManager\n\n\ttryPromoteEvery atomic.Uint32\n\treQueryEvery    atomic.Uint32\n\treQueryWait     atomic.Int64\n\n\tsendRecvErrorConfig   recvErrorConfig\n\tacceptRecvErrorConfig recvErrorConfig\n\n\t// rebindCount is used to decide if an active tunnel should trigger a punch notification through a lighthouse\n\trebindCount int8\n\tversion     string\n\n\tconntrackCacheTimeout time.Duration\n\n\twriters []udp.Conn\n\treaders []io.ReadWriteCloser\n\n\tmetricHandshakes    metrics.Histogram\n\tmessageMetrics      *MessageMetrics\n\tcachedPacketMetrics *cachedPacketMetrics\n\n\tl *logrus.Logger\n}\n\ntype EncWriter interface {\n\tSendVia(via *HostInfo,\n\t\trelay *Relay,\n\t\tad,\n\t\tnb,\n\t\tout []byte,\n\t\tnocopy bool,\n\t)\n\tSendMessageToVpnAddr(t header.MessageType, st header.MessageSubType, vpnAddr netip.Addr, p, nb, out []byte)\n\tSendMessageToHostInfo(t header.MessageType, st header.MessageSubType, hostinfo *HostInfo, p, nb, out []byte)\n\tHandshake(vpnAddr netip.Addr)\n\tGetHostInfo(vpnAddr netip.Addr) *HostInfo\n\tGetCertState() *CertState\n}\n\ntype recvErrorConfig uint8\n\nconst (\n\trecvErrorAlways recvErrorConfig = iota\n\trecvErrorNever\n\trecvErrorPrivate\n)\n\nfunc (s recvErrorConfig) ShouldRecvError(endpoint netip.AddrPort) bool {\n\tswitch s {\n\tcase recvErrorPrivate:\n\t\treturn endpoint.Addr().IsPrivate()\n\tcase recvErrorAlways:\n\t\treturn true\n\tcase recvErrorNever:\n\t\treturn false\n\tdefault:\n\t\tpanic(fmt.Errorf(\"invalid recvErrorConfig value: %d\", s))\n\t}\n}\n\nfunc (s recvErrorConfig) String() string {\n\tswitch s {\n\tcase recvErrorAlways:\n\t\treturn \"always\"\n\tcase recvErrorNever:\n\t\treturn \"never\"\n\tcase recvErrorPrivate:\n\t\treturn \"private\"\n\tdefault:\n\t\treturn fmt.Sprintf(\"invalid(%d)\", s)\n\t}\n}\n\nfunc NewInterface(ctx context.Context, c *InterfaceConfig) (*Interface, error) {\n\tif c.Outside == nil {\n\t\treturn nil, errors.New(\"no outside connection\")\n\t}\n\tif c.Inside == nil {\n\t\treturn nil, errors.New(\"no inside interface (tun)\")\n\t}\n\tif c.pki == nil {\n\t\treturn nil, errors.New(\"no certificate state\")\n\t}\n\tif c.Firewall == nil {\n\t\treturn nil, errors.New(\"no firewall rules\")\n\t}\n\tif c.connectionManager == nil {\n\t\treturn nil, errors.New(\"no connection manager\")\n\t}\n\n\tcs := c.pki.getCertState()\n\tifce := &Interface{\n\t\tpki:                   c.pki,\n\t\thostMap:               c.HostMap,\n\t\toutside:               c.Outside,\n\t\tinside:                c.Inside,\n\t\tfirewall:              c.Firewall,\n\t\tserveDns:              c.ServeDns,\n\t\thandshakeManager:      c.HandshakeManager,\n\t\tcreateTime:            time.Now(),\n\t\tlightHouse:            c.lightHouse,\n\t\tdropLocalBroadcast:    c.DropLocalBroadcast,\n\t\tdropMulticast:         c.DropMulticast,\n\t\troutines:              c.routines,\n\t\tversion:               c.version,\n\t\twriters:               make([]udp.Conn, c.routines),\n\t\treaders:               make([]io.ReadWriteCloser, c.routines),\n\t\tmyVpnNetworks:         cs.myVpnNetworks,\n\t\tmyVpnNetworksTable:    cs.myVpnNetworksTable,\n\t\tmyVpnAddrs:            cs.myVpnAddrs,\n\t\tmyVpnAddrsTable:       cs.myVpnAddrsTable,\n\t\tmyBroadcastAddrsTable: cs.myVpnBroadcastAddrsTable,\n\t\trelayManager:          c.relayManager,\n\t\tconnectionManager:     c.connectionManager,\n\t\tconntrackCacheTimeout: c.ConntrackCacheTimeout,\n\n\t\tmetricHandshakes: metrics.GetOrRegisterHistogram(\"handshakes\", nil, metrics.NewExpDecaySample(1028, 0.015)),\n\t\tmessageMetrics:   c.MessageMetrics,\n\t\tcachedPacketMetrics: &cachedPacketMetrics{\n\t\t\tsent:    metrics.GetOrRegisterCounter(\"hostinfo.cached_packets.sent\", nil),\n\t\t\tdropped: metrics.GetOrRegisterCounter(\"hostinfo.cached_packets.dropped\", nil),\n\t\t},\n\n\t\tl: c.l,\n\t}\n\n\tifce.tryPromoteEvery.Store(c.tryPromoteEvery)\n\tifce.reQueryEvery.Store(c.reQueryEvery)\n\tifce.reQueryWait.Store(int64(c.reQueryWait))\n\n\tifce.connectionManager.intf = ifce\n\n\treturn ifce, nil\n}\n\n// activate creates the interface on the host. After the interface is created, any\n// other services that want to bind listeners to its IP may do so successfully. However,\n// the interface isn't going to process anything until run() is called.\nfunc (f *Interface) activate() {\n\t// actually turn on tun dev\n\n\taddr, err := f.outside.LocalAddr()\n\tif err != nil {\n\t\tf.l.WithError(err).Error(\"Failed to get udp listen address\")\n\t}\n\n\tf.l.WithField(\"interface\", f.inside.Name()).WithField(\"networks\", f.myVpnNetworks).\n\t\tWithField(\"build\", f.version).WithField(\"udpAddr\", addr).\n\t\tWithField(\"boringcrypto\", boringEnabled()).\n\t\tInfo(\"Nebula interface is active\")\n\n\tif f.routines > 1 {\n\t\tif !f.inside.SupportsMultiqueue() || !f.outside.SupportsMultipleReaders() {\n\t\t\tf.routines = 1\n\t\t\tf.l.Warn(\"routines is not supported on this platform, falling back to a single routine\")\n\t\t}\n\t}\n\n\tmetrics.GetOrRegisterGauge(\"routines\", nil).Update(int64(f.routines))\n\n\t// Prepare n tun queues\n\tvar reader io.ReadWriteCloser = f.inside\n\tfor i := 0; i < f.routines; i++ {\n\t\tif i > 0 {\n\t\t\treader, err = f.inside.NewMultiQueueReader()\n\t\t\tif err != nil {\n\t\t\t\tf.l.Fatal(err)\n\t\t\t}\n\t\t}\n\t\tf.readers[i] = reader\n\t}\n\n\tif err := f.inside.Activate(); err != nil {\n\t\tf.inside.Close()\n\t\tf.l.Fatal(err)\n\t}\n}\n\nfunc (f *Interface) run() {\n\t// Launch n queues to read packets from udp\n\tfor i := 0; i < f.routines; i++ {\n\t\tgo f.listenOut(i)\n\t}\n\n\t// Launch n queues to read packets from tun dev\n\tfor i := 0; i < f.routines; i++ {\n\t\tgo f.listenIn(f.readers[i], i)\n\t}\n}\n\nfunc (f *Interface) listenOut(i int) {\n\truntime.LockOSThread()\n\n\tvar li udp.Conn\n\tif i > 0 {\n\t\tli = f.writers[i]\n\t} else {\n\t\tli = f.outside\n\t}\n\n\tctCache := firewall.NewConntrackCacheTicker(f.conntrackCacheTimeout)\n\tlhh := f.lightHouse.NewRequestHandler()\n\tplaintext := make([]byte, udp.MTU)\n\th := &header.H{}\n\tfwPacket := &firewall.Packet{}\n\tnb := make([]byte, 12, 12)\n\n\tli.ListenOut(func(fromUdpAddr netip.AddrPort, payload []byte) {\n\t\tf.readOutsidePackets(ViaSender{UdpAddr: fromUdpAddr}, plaintext[:0], payload, h, fwPacket, lhh, nb, i, ctCache.Get(f.l))\n\t})\n}\n\nfunc (f *Interface) listenIn(reader io.ReadWriteCloser, i int) {\n\truntime.LockOSThread()\n\n\tpacket := make([]byte, mtu)\n\tout := make([]byte, mtu)\n\tfwPacket := &firewall.Packet{}\n\tnb := make([]byte, 12, 12)\n\n\tconntrackCache := firewall.NewConntrackCacheTicker(f.conntrackCacheTimeout)\n\n\tfor {\n\t\tn, err := reader.Read(packet)\n\t\tif err != nil {\n\t\t\tif errors.Is(err, os.ErrClosed) && f.closed.Load() {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tf.l.WithError(err).Error(\"Error while reading outbound packet\")\n\t\t\t// This only seems to happen when something fatal happens to the fd, so exit.\n\t\t\tos.Exit(2)\n\t\t}\n\n\t\tf.consumeInsidePacket(packet[:n], fwPacket, nb, out, i, conntrackCache.Get(f.l))\n\t}\n}\n\nfunc (f *Interface) RegisterConfigChangeCallbacks(c *config.C) {\n\tc.RegisterReloadCallback(f.reloadFirewall)\n\tc.RegisterReloadCallback(f.reloadSendRecvError)\n\tc.RegisterReloadCallback(f.reloadAcceptRecvError)\n\tc.RegisterReloadCallback(f.reloadDisconnectInvalid)\n\tc.RegisterReloadCallback(f.reloadMisc)\n\n\tfor _, udpConn := range f.writers {\n\t\tc.RegisterReloadCallback(udpConn.ReloadConfig)\n\t}\n}\n\nfunc (f *Interface) reloadDisconnectInvalid(c *config.C) {\n\tinitial := c.InitialLoad()\n\tif initial || c.HasChanged(\"pki.disconnect_invalid\") {\n\t\tf.disconnectInvalid.Store(c.GetBool(\"pki.disconnect_invalid\", true))\n\t\tif !initial {\n\t\t\tf.l.Infof(\"pki.disconnect_invalid changed to %v\", f.disconnectInvalid.Load())\n\t\t}\n\t}\n}\n\nfunc (f *Interface) reloadFirewall(c *config.C) {\n\t//TODO: need to trigger/detect if the certificate changed too\n\tif c.HasChanged(\"firewall\") == false {\n\t\tf.l.Debug(\"No firewall config change detected\")\n\t\treturn\n\t}\n\n\tfw, err := NewFirewallFromConfig(f.l, f.pki.getCertState(), c)\n\tif err != nil {\n\t\tf.l.WithError(err).Error(\"Error while creating firewall during reload\")\n\t\treturn\n\t}\n\n\toldFw := f.firewall\n\tconntrack := oldFw.Conntrack\n\tconntrack.Lock()\n\tdefer conntrack.Unlock()\n\n\tfw.rulesVersion = oldFw.rulesVersion + 1\n\t// If rulesVersion is back to zero, we have wrapped all the way around. Be\n\t// safe and just reset conntrack in this case.\n\tif fw.rulesVersion == 0 {\n\t\tf.l.WithField(\"firewallHashes\", fw.GetRuleHashes()).\n\t\t\tWithField(\"oldFirewallHashes\", oldFw.GetRuleHashes()).\n\t\t\tWithField(\"rulesVersion\", fw.rulesVersion).\n\t\t\tWarn(\"firewall rulesVersion has overflowed, resetting conntrack\")\n\t} else {\n\t\tfw.Conntrack = conntrack\n\t}\n\n\tf.firewall = fw\n\n\toldFw.Destroy()\n\tf.l.WithField(\"firewallHashes\", fw.GetRuleHashes()).\n\t\tWithField(\"oldFirewallHashes\", oldFw.GetRuleHashes()).\n\t\tWithField(\"rulesVersion\", fw.rulesVersion).\n\t\tInfo(\"New firewall has been installed\")\n}\n\nfunc (f *Interface) reloadSendRecvError(c *config.C) {\n\tif c.InitialLoad() || c.HasChanged(\"listen.send_recv_error\") {\n\t\tstringValue := c.GetString(\"listen.send_recv_error\", \"always\")\n\n\t\tswitch stringValue {\n\t\tcase \"always\":\n\t\t\tf.sendRecvErrorConfig = recvErrorAlways\n\t\tcase \"never\":\n\t\t\tf.sendRecvErrorConfig = recvErrorNever\n\t\tcase \"private\":\n\t\t\tf.sendRecvErrorConfig = recvErrorPrivate\n\t\tdefault:\n\t\t\tif c.GetBool(\"listen.send_recv_error\", true) {\n\t\t\t\tf.sendRecvErrorConfig = recvErrorAlways\n\t\t\t} else {\n\t\t\t\tf.sendRecvErrorConfig = recvErrorNever\n\t\t\t}\n\t\t}\n\n\t\tf.l.WithField(\"sendRecvError\", f.sendRecvErrorConfig.String()).\n\t\t\tInfo(\"Loaded send_recv_error config\")\n\t}\n}\n\nfunc (f *Interface) reloadAcceptRecvError(c *config.C) {\n\tif c.InitialLoad() || c.HasChanged(\"listen.accept_recv_error\") {\n\t\tstringValue := c.GetString(\"listen.accept_recv_error\", \"always\")\n\n\t\tswitch stringValue {\n\t\tcase \"always\":\n\t\t\tf.acceptRecvErrorConfig = recvErrorAlways\n\t\tcase \"never\":\n\t\t\tf.acceptRecvErrorConfig = recvErrorNever\n\t\tcase \"private\":\n\t\t\tf.acceptRecvErrorConfig = recvErrorPrivate\n\t\tdefault:\n\t\t\tif c.GetBool(\"listen.accept_recv_error\", true) {\n\t\t\t\tf.acceptRecvErrorConfig = recvErrorAlways\n\t\t\t} else {\n\t\t\t\tf.acceptRecvErrorConfig = recvErrorNever\n\t\t\t}\n\t\t}\n\n\t\tf.l.WithField(\"acceptRecvError\", f.acceptRecvErrorConfig.String()).\n\t\t\tInfo(\"Loaded accept_recv_error config\")\n\t}\n}\n\nfunc (f *Interface) reloadMisc(c *config.C) {\n\tif c.HasChanged(\"counters.try_promote\") {\n\t\tn := c.GetUint32(\"counters.try_promote\", defaultPromoteEvery)\n\t\tf.tryPromoteEvery.Store(n)\n\t\tf.l.Info(\"counters.try_promote has changed\")\n\t}\n\n\tif c.HasChanged(\"counters.requery_every_packets\") {\n\t\tn := c.GetUint32(\"counters.requery_every_packets\", defaultReQueryEvery)\n\t\tf.reQueryEvery.Store(n)\n\t\tf.l.Info(\"counters.requery_every_packets has changed\")\n\t}\n\n\tif c.HasChanged(\"timers.requery_wait_duration\") {\n\t\tn := c.GetDuration(\"timers.requery_wait_duration\", defaultReQueryWait)\n\t\tf.reQueryWait.Store(int64(n))\n\t\tf.l.Info(\"timers.requery_wait_duration has changed\")\n\t}\n}\n\nfunc (f *Interface) emitStats(ctx context.Context, i time.Duration) {\n\tticker := time.NewTicker(i)\n\tdefer ticker.Stop()\n\n\tudpStats := udp.NewUDPStatsEmitter(f.writers)\n\n\tcertExpirationGauge := metrics.GetOrRegisterGauge(\"certificate.ttl_seconds\", nil)\n\tcertInitiatingVersion := metrics.GetOrRegisterGauge(\"certificate.initiating_version\", nil)\n\tcertMaxVersion := metrics.GetOrRegisterGauge(\"certificate.max_version\", nil)\n\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase <-ticker.C:\n\t\t\tf.firewall.EmitStats()\n\t\t\tf.handshakeManager.EmitStats()\n\t\t\tudpStats()\n\n\t\t\tcertState := f.pki.getCertState()\n\t\t\tdefaultCrt := certState.GetDefaultCertificate()\n\t\t\tcertExpirationGauge.Update(int64(defaultCrt.NotAfter().Sub(time.Now()) / time.Second))\n\t\t\tcertInitiatingVersion.Update(int64(defaultCrt.Version()))\n\n\t\t\t// Report the max certificate version we are capable of using\n\t\t\tif certState.v2Cert != nil {\n\t\t\t\tcertMaxVersion.Update(int64(certState.v2Cert.Version()))\n\t\t\t} else {\n\t\t\t\tcertMaxVersion.Update(int64(certState.v1Cert.Version()))\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (f *Interface) GetHostInfo(vpnIp netip.Addr) *HostInfo {\n\treturn f.hostMap.QueryVpnAddr(vpnIp)\n}\n\nfunc (f *Interface) GetCertState() *CertState {\n\treturn f.pki.getCertState()\n}\n\nfunc (f *Interface) Close() error {\n\tf.closed.Store(true)\n\n\tfor _, u := range f.writers {\n\t\terr := u.Close()\n\t\tif err != nil {\n\t\t\tf.l.WithError(err).Error(\"Error while closing udp socket\")\n\t\t}\n\t}\n\tfor i, r := range f.readers {\n\t\tif i == 0 {\n\t\t\tcontinue // f.readers[0] is f.inside, which we want to save for last\n\t\t}\n\t\tif err := r.Close(); err != nil {\n\t\t\tf.l.WithError(err).Error(\"Error while closing tun reader\")\n\t\t}\n\t}\n\n\t// Release the tun device\n\treturn f.inside.Close()\n}\n"
  },
  {
    "path": "iputil/packet.go",
    "content": "package iputil\n\nimport (\n\t\"encoding/binary\"\n\n\t\"golang.org/x/net/ipv4\"\n)\n\nconst (\n\t// Need 96 bytes for the largest reject packet:\n\t// - 20 byte ipv4 header\n\t// - 8 byte icmpv4 header\n\t// - 68 byte body (60 byte max orig ipv4 header + 8 byte orig icmpv4 header)\n\tMaxRejectPacketSize = ipv4.HeaderLen + 8 + 60 + 8\n)\n\nfunc CreateRejectPacket(packet []byte, out []byte) []byte {\n\tif len(packet) < ipv4.HeaderLen || int(packet[0]>>4) != ipv4.Version {\n\t\treturn nil\n\t}\n\n\tswitch packet[9] {\n\tcase 6: // tcp\n\t\treturn ipv4CreateRejectTCPPacket(packet, out)\n\tdefault:\n\t\treturn ipv4CreateRejectICMPPacket(packet, out)\n\t}\n}\n\nfunc ipv4CreateRejectICMPPacket(packet []byte, out []byte) []byte {\n\tihl := int(packet[0]&0x0f) << 2\n\n\tif len(packet) < ihl {\n\t\t// We need at least this many bytes for this to be a valid packet\n\t\treturn nil\n\t}\n\n\t// ICMP reply includes original header and first 8 bytes of the packet\n\tpacketLen := len(packet)\n\tif packetLen > ihl+8 {\n\t\tpacketLen = ihl + 8\n\t}\n\n\toutLen := ipv4.HeaderLen + 8 + packetLen\n\tif outLen > cap(out) {\n\t\treturn nil\n\t}\n\n\tout = out[:outLen]\n\n\tipHdr := out[0:ipv4.HeaderLen]\n\tipHdr[0] = ipv4.Version<<4 | (ipv4.HeaderLen >> 2)    // version, ihl\n\tipHdr[1] = 0                                          // DSCP, ECN\n\tbinary.BigEndian.PutUint16(ipHdr[2:], uint16(outLen)) // Total Length\n\n\tipHdr[4] = 0  // id\n\tipHdr[5] = 0  //  .\n\tipHdr[6] = 0  // flags, fragment offset\n\tipHdr[7] = 0  //  .\n\tipHdr[8] = 64 // TTL\n\tipHdr[9] = 1  // protocol (icmp)\n\tipHdr[10] = 0 // checksum\n\tipHdr[11] = 0 //  .\n\n\t// Swap dest / src IPs\n\tcopy(ipHdr[12:16], packet[16:20])\n\tcopy(ipHdr[16:20], packet[12:16])\n\n\t// Calculate checksum\n\tbinary.BigEndian.PutUint16(ipHdr[10:], tcpipChecksum(ipHdr, 0))\n\n\t// ICMP Destination Unreachable\n\ticmpOut := out[ipv4.HeaderLen:]\n\ticmpOut[0] = 3 // type (Destination unreachable)\n\ticmpOut[1] = 3 // code (Port unreachable error)\n\ticmpOut[2] = 0 // checksum\n\ticmpOut[3] = 0 //  .\n\ticmpOut[4] = 0 // unused\n\ticmpOut[5] = 0 //  .\n\ticmpOut[6] = 0 //  .\n\ticmpOut[7] = 0 //  .\n\n\t// Copy original IP header and first 8 bytes as body\n\tcopy(icmpOut[8:], packet[:packetLen])\n\n\t// Calculate checksum\n\tbinary.BigEndian.PutUint16(icmpOut[2:], tcpipChecksum(icmpOut, 0))\n\n\treturn out\n}\n\nfunc ipv4CreateRejectTCPPacket(packet []byte, out []byte) []byte {\n\tconst tcpLen = 20\n\n\tihl := int(packet[0]&0x0f) << 2\n\toutLen := ipv4.HeaderLen + tcpLen\n\n\tif len(packet) < ihl+tcpLen {\n\t\t// We need at least this many bytes for this to be a valid packet\n\t\treturn nil\n\t}\n\tif outLen > cap(out) {\n\t\treturn nil\n\t}\n\n\tout = out[:outLen]\n\n\tipHdr := out[0:ipv4.HeaderLen]\n\tipHdr[0] = ipv4.Version<<4 | (ipv4.HeaderLen >> 2)    // version, ihl\n\tipHdr[1] = 0                                          // DSCP, ECN\n\tbinary.BigEndian.PutUint16(ipHdr[2:], uint16(outLen)) // Total Length\n\tipHdr[4] = 0                                          // id\n\tipHdr[5] = 0                                          //  .\n\tipHdr[6] = 0                                          // flags, fragment offset\n\tipHdr[7] = 0                                          //  .\n\tipHdr[8] = 64                                         // TTL\n\tipHdr[9] = 6                                          // protocol (tcp)\n\tipHdr[10] = 0                                         // checksum\n\tipHdr[11] = 0                                         //  .\n\n\t// Swap dest / src IPs\n\tcopy(ipHdr[12:16], packet[16:20])\n\tcopy(ipHdr[16:20], packet[12:16])\n\n\t// Calculate checksum\n\tbinary.BigEndian.PutUint16(ipHdr[10:], tcpipChecksum(ipHdr, 0))\n\n\t// TCP RST\n\ttcpIn := packet[ihl:]\n\tvar ackSeq, seq uint32\n\toutFlags := byte(0b00000100) // RST\n\n\t// Set seq and ackSeq based on how iptables/netfilter does it in Linux:\n\t// - https://github.com/torvalds/linux/blob/v5.19/net/ipv4/netfilter/nf_reject_ipv4.c#L193-L221\n\tinAck := tcpIn[13]&0b00010000 != 0\n\tif inAck {\n\t\tseq = binary.BigEndian.Uint32(tcpIn[8:])\n\t} else {\n\t\tinSyn := uint32((tcpIn[13] & 0b00000010) >> 1)\n\t\tinFin := uint32(tcpIn[13] & 0b00000001)\n\t\t// seq from the packet + syn + fin + tcp segment length\n\t\tackSeq = binary.BigEndian.Uint32(tcpIn[4:]) + inSyn + inFin + uint32(len(tcpIn)) - uint32(tcpIn[12]>>4)<<2\n\t\toutFlags |= 0b00010000 // ACK\n\t}\n\n\ttcpOut := out[ipv4.HeaderLen:]\n\t// Swap dest / src ports\n\tcopy(tcpOut[0:2], tcpIn[2:4])\n\tcopy(tcpOut[2:4], tcpIn[0:2])\n\tbinary.BigEndian.PutUint32(tcpOut[4:], seq)\n\tbinary.BigEndian.PutUint32(tcpOut[8:], ackSeq)\n\ttcpOut[12] = (tcpLen >> 2) << 4 // data offset,  reserved,  NS\n\ttcpOut[13] = outFlags           // CWR, ECE, URG, ACK, PSH, RST, SYN, FIN\n\ttcpOut[14] = 0                  // window size\n\ttcpOut[15] = 0                  //  .\n\ttcpOut[16] = 0                  // checksum\n\ttcpOut[17] = 0                  //  .\n\ttcpOut[18] = 0                  // URG Pointer\n\ttcpOut[19] = 0                  //  .\n\n\t// Calculate checksum\n\tcsum := ipv4PseudoheaderChecksum(ipHdr[12:16], ipHdr[16:20], 6, tcpLen)\n\tbinary.BigEndian.PutUint16(tcpOut[16:], tcpipChecksum(tcpOut, csum))\n\n\treturn out\n}\n\nfunc CreateICMPEchoResponse(packet, out []byte) []byte {\n\t// Return early if this is not a simple ICMP Echo Request\n\t//TODO: make constants out of these\n\tif !(len(packet) >= 28 && len(packet) <= 9001 && packet[0] == 0x45 && packet[9] == 0x01 && packet[20] == 0x08) {\n\t\treturn nil\n\t}\n\n\t// We don't support fragmented packets\n\tif packet[7] != 0 || (packet[6]&0x2F != 0) {\n\t\treturn nil\n\t}\n\n\tout = out[:len(packet)]\n\n\tcopy(out, packet)\n\n\t// Swap dest / src IPs and recalculate checksum\n\tipv4 := out[0:20]\n\tcopy(ipv4[12:16], packet[16:20])\n\tcopy(ipv4[16:20], packet[12:16])\n\tipv4[10] = 0\n\tipv4[11] = 0\n\tbinary.BigEndian.PutUint16(ipv4[10:], tcpipChecksum(ipv4, 0))\n\n\t// Change type to ICMP Echo Reply and recalculate checksum\n\ticmp := out[20:]\n\ticmp[0] = 0\n\ticmp[2] = 0\n\ticmp[3] = 0\n\tbinary.BigEndian.PutUint16(icmp[2:], tcpipChecksum(icmp, 0))\n\n\treturn out\n}\n\n// calculates the TCP/IP checksum defined in rfc1071. The passed-in\n// csum is any initial checksum data that's already been computed.\n//\n// based on:\n// - https://github.com/google/gopacket/blob/v1.1.19/layers/tcpip.go#L50-L70\nfunc tcpipChecksum(data []byte, csum uint32) uint16 {\n\t// to handle odd lengths, we loop to length - 1, incrementing by 2, then\n\t// handle the last byte specifically by checking against the original\n\t// length.\n\tlength := len(data) - 1\n\tfor i := 0; i < length; i += 2 {\n\t\t// For our test packet, doing this manually is about 25% faster\n\t\t// (740 ns vs. 1000ns) than doing it by calling binary.BigEndian.Uint16.\n\t\tcsum += uint32(data[i]) << 8\n\t\tcsum += uint32(data[i+1])\n\t}\n\tif len(data)%2 == 1 {\n\t\tcsum += uint32(data[length]) << 8\n\t}\n\tfor csum > 0xffff {\n\t\tcsum = (csum >> 16) + (csum & 0xffff)\n\t}\n\treturn ^uint16(csum)\n}\n\n// based on:\n// - https://github.com/google/gopacket/blob/v1.1.19/layers/tcpip.go#L26-L35\nfunc ipv4PseudoheaderChecksum(src, dst []byte, proto, length uint32) (csum uint32) {\n\tcsum += (uint32(src[0]) + uint32(src[2])) << 8\n\tcsum += uint32(src[1]) + uint32(src[3])\n\tcsum += (uint32(dst[0]) + uint32(dst[2])) << 8\n\tcsum += uint32(dst[1]) + uint32(dst[3])\n\tcsum += proto\n\tcsum += length & 0xffff\n\tcsum += length >> 16\n\treturn csum\n}\n"
  },
  {
    "path": "iputil/packet_test.go",
    "content": "package iputil\n\nimport (\n\t\"net\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"golang.org/x/net/ipv4\"\n)\n\nfunc Test_CreateRejectPacket(t *testing.T) {\n\th := ipv4.Header{\n\t\tLen:      20,\n\t\tSrc:      net.IPv4(10, 0, 0, 1),\n\t\tDst:      net.IPv4(10, 0, 0, 2),\n\t\tProtocol: 1, // ICMP\n\t}\n\n\tb, err := h.Marshal()\n\tif err != nil {\n\t\tt.Fatalf(\"h.Marhshal: %v\", err)\n\t}\n\tb = append(b, []byte{0, 3, 0, 4}...)\n\n\texpectedLen := ipv4.HeaderLen + 8 + h.Len + 4\n\tout := make([]byte, expectedLen)\n\trejectPacket := CreateRejectPacket(b, out)\n\tassert.NotNil(t, rejectPacket)\n\tassert.Len(t, rejectPacket, expectedLen)\n\n\t// ICMP with max header len\n\th = ipv4.Header{\n\t\tLen:      60,\n\t\tSrc:      net.IPv4(10, 0, 0, 1),\n\t\tDst:      net.IPv4(10, 0, 0, 2),\n\t\tProtocol: 1, // ICMP\n\t\tOptions:  make([]byte, 40),\n\t}\n\n\tb, err = h.Marshal()\n\tif err != nil {\n\t\tt.Fatalf(\"h.Marhshal: %v\", err)\n\t}\n\tb = append(b, []byte{0, 3, 0, 4, 0, 0, 0, 0}...)\n\n\texpectedLen = MaxRejectPacketSize\n\tout = make([]byte, MaxRejectPacketSize)\n\trejectPacket = CreateRejectPacket(b, out)\n\tassert.NotNil(t, rejectPacket)\n\tassert.Len(t, rejectPacket, expectedLen)\n\n\t// TCP with max header len\n\th = ipv4.Header{\n\t\tLen:      60,\n\t\tSrc:      net.IPv4(10, 0, 0, 1),\n\t\tDst:      net.IPv4(10, 0, 0, 2),\n\t\tProtocol: 6, // TCP\n\t\tOptions:  make([]byte, 40),\n\t}\n\n\tb, err = h.Marshal()\n\tif err != nil {\n\t\tt.Fatalf(\"h.Marhshal: %v\", err)\n\t}\n\tb = append(b, []byte{0, 3, 0, 4}...)\n\tb = append(b, make([]byte, 16)...)\n\n\texpectedLen = ipv4.HeaderLen + 20\n\tout = make([]byte, expectedLen)\n\trejectPacket = CreateRejectPacket(b, out)\n\tassert.NotNil(t, rejectPacket)\n\tassert.Len(t, rejectPacket, expectedLen)\n}\n"
  },
  {
    "path": "lighthouse.go",
    "content": "package nebula\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"slices\"\n\t\"strconv\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/gaissmai/bart\"\n\t\"github.com/rcrowley/go-metrics\"\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/cert\"\n\t\"github.com/slackhq/nebula/config\"\n\t\"github.com/slackhq/nebula/header\"\n\t\"github.com/slackhq/nebula/udp\"\n\t\"github.com/slackhq/nebula/util\"\n)\n\nvar ErrHostNotKnown = errors.New(\"host not known\")\nvar ErrBadDetailsVpnAddr = errors.New(\"invalid packet, malformed detailsVpnAddr\")\n\ntype LightHouse struct {\n\t//TODO: We need a timer wheel to kick out vpnAddrs that haven't reported in a long time\n\tsync.RWMutex //Because we concurrently read and write to our maps\n\tctx          context.Context\n\tamLighthouse bool\n\n\tmyVpnNetworks      []netip.Prefix\n\tmyVpnNetworksTable *bart.Lite\n\tpunchConn          udp.Conn\n\tpunchy             *Punchy\n\n\t// Local cache of answers from light houses\n\t// map of vpn addr to answers\n\taddrMap map[netip.Addr]*RemoteList\n\n\t// filters remote addresses allowed for each host\n\t// - When we are a lighthouse, this filters what addresses we store and\n\t// respond with.\n\t// - When we are not a lighthouse, this filters which addresses we accept\n\t// from lighthouses.\n\tremoteAllowList atomic.Pointer[RemoteAllowList]\n\n\t// filters local addresses that we advertise to lighthouses\n\tlocalAllowList atomic.Pointer[LocalAllowList]\n\n\t// used to trigger the HandshakeManager when we receive HostQueryReply\n\thandshakeTrigger chan<- netip.Addr\n\n\t// staticList exists to avoid having a bool in each addrMap entry\n\t// since static should be rare\n\tstaticList  atomic.Pointer[map[netip.Addr]struct{}]\n\tlighthouses atomic.Pointer[[]netip.Addr]\n\n\tinterval     atomic.Int64\n\tupdateCancel context.CancelFunc\n\tifce         EncWriter\n\tnebulaPort   uint32 // 32 bits because protobuf does not have a uint16\n\n\tadvertiseAddrs atomic.Pointer[[]netip.AddrPort]\n\n\t// Addr's of relays that can be used by peers to access me\n\trelaysForMe atomic.Pointer[[]netip.Addr]\n\n\tqueryChan chan netip.Addr\n\n\tcalculatedRemotes atomic.Pointer[bart.Table[[]*calculatedRemote]] // Maps VpnAddr to []*calculatedRemote\n\n\tmetrics           *MessageMetrics\n\tmetricHolepunchTx metrics.Counter\n\tl                 *logrus.Logger\n}\n\n// NewLightHouseFromConfig will build a Lighthouse struct from the values provided in the config object\n// addrMap should be nil unless this is during a config reload\nfunc NewLightHouseFromConfig(ctx context.Context, l *logrus.Logger, c *config.C, cs *CertState, pc udp.Conn, p *Punchy) (*LightHouse, error) {\n\tamLighthouse := c.GetBool(\"lighthouse.am_lighthouse\", false)\n\tnebulaPort := uint32(c.GetInt(\"listen.port\", 0))\n\tif amLighthouse && nebulaPort == 0 {\n\t\treturn nil, util.NewContextualError(\"lighthouse.am_lighthouse enabled on node but no port number is set in config\", nil, nil)\n\t}\n\n\t// If port is dynamic, discover it\n\tif nebulaPort == 0 && pc != nil {\n\t\tuPort, err := pc.LocalAddr()\n\t\tif err != nil {\n\t\t\treturn nil, util.NewContextualError(\"Failed to get listening port\", nil, err)\n\t\t}\n\t\tnebulaPort = uint32(uPort.Port())\n\t}\n\n\th := LightHouse{\n\t\tctx:                ctx,\n\t\tamLighthouse:       amLighthouse,\n\t\tmyVpnNetworks:      cs.myVpnNetworks,\n\t\tmyVpnNetworksTable: cs.myVpnNetworksTable,\n\t\taddrMap:            make(map[netip.Addr]*RemoteList),\n\t\tnebulaPort:         nebulaPort,\n\t\tpunchConn:          pc,\n\t\tpunchy:             p,\n\t\tqueryChan:          make(chan netip.Addr, c.GetUint32(\"handshakes.query_buffer\", 64)),\n\t\tl:                  l,\n\t}\n\tlighthouses := make([]netip.Addr, 0)\n\th.lighthouses.Store(&lighthouses)\n\tstaticList := make(map[netip.Addr]struct{})\n\th.staticList.Store(&staticList)\n\n\tif c.GetBool(\"stats.lighthouse_metrics\", false) {\n\t\th.metrics = newLighthouseMetrics()\n\t\th.metricHolepunchTx = metrics.GetOrRegisterCounter(\"messages.tx.holepunch\", nil)\n\t} else {\n\t\th.metricHolepunchTx = metrics.NilCounter{}\n\t}\n\n\terr := h.reload(c, true)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tc.RegisterReloadCallback(func(c *config.C) {\n\t\terr := h.reload(c, false)\n\t\tswitch v := err.(type) {\n\t\tcase *util.ContextualError:\n\t\t\tv.Log(l)\n\t\tcase error:\n\t\t\tl.WithError(err).Error(\"failed to reload lighthouse\")\n\t\t}\n\t})\n\n\th.startQueryWorker()\n\n\treturn &h, nil\n}\n\nfunc (lh *LightHouse) GetStaticHostList() map[netip.Addr]struct{} {\n\treturn *lh.staticList.Load()\n}\n\nfunc (lh *LightHouse) GetLighthouses() []netip.Addr {\n\treturn *lh.lighthouses.Load()\n}\n\nfunc (lh *LightHouse) GetRemoteAllowList() *RemoteAllowList {\n\treturn lh.remoteAllowList.Load()\n}\n\nfunc (lh *LightHouse) GetLocalAllowList() *LocalAllowList {\n\treturn lh.localAllowList.Load()\n}\n\nfunc (lh *LightHouse) GetAdvertiseAddrs() []netip.AddrPort {\n\treturn *lh.advertiseAddrs.Load()\n}\n\nfunc (lh *LightHouse) GetRelaysForMe() []netip.Addr {\n\treturn *lh.relaysForMe.Load()\n}\n\nfunc (lh *LightHouse) getCalculatedRemotes() *bart.Table[[]*calculatedRemote] {\n\treturn lh.calculatedRemotes.Load()\n}\n\nfunc (lh *LightHouse) GetUpdateInterval() int64 {\n\treturn lh.interval.Load()\n}\n\nfunc (lh *LightHouse) reload(c *config.C, initial bool) error {\n\tif initial || c.HasChanged(\"lighthouse.advertise_addrs\") {\n\t\trawAdvAddrs := c.GetStringSlice(\"lighthouse.advertise_addrs\", []string{})\n\t\tadvAddrs := make([]netip.AddrPort, 0)\n\n\t\tfor i, rawAddr := range rawAdvAddrs {\n\t\t\thost, sport, err := net.SplitHostPort(rawAddr)\n\t\t\tif err != nil {\n\t\t\t\treturn util.NewContextualError(\"Unable to parse lighthouse.advertise_addrs entry\", m{\"addr\": rawAddr, \"entry\": i + 1}, err)\n\t\t\t}\n\n\t\t\taddrs, err := net.DefaultResolver.LookupNetIP(context.Background(), \"ip\", host)\n\t\t\tif err != nil {\n\t\t\t\treturn util.NewContextualError(\"Unable to lookup lighthouse.advertise_addrs entry\", m{\"addr\": rawAddr, \"entry\": i + 1}, err)\n\t\t\t}\n\t\t\tif len(addrs) == 0 {\n\t\t\t\treturn util.NewContextualError(\"Unable to lookup lighthouse.advertise_addrs entry\", m{\"addr\": rawAddr, \"entry\": i + 1}, nil)\n\t\t\t}\n\n\t\t\tport, err := strconv.Atoi(sport)\n\t\t\tif err != nil {\n\t\t\t\treturn util.NewContextualError(\"Unable to parse port in lighthouse.advertise_addrs entry\", m{\"addr\": rawAddr, \"entry\": i + 1}, err)\n\t\t\t}\n\n\t\t\tif port == 0 {\n\t\t\t\tport = int(lh.nebulaPort)\n\t\t\t}\n\n\t\t\t//TODO: we could technically insert all returned addrs instead of just the first one if a dns lookup was used\n\t\t\taddr := addrs[0].Unmap()\n\t\t\tif lh.myVpnNetworksTable.Contains(addr) {\n\t\t\t\tlh.l.WithField(\"addr\", rawAddr).WithField(\"entry\", i+1).\n\t\t\t\t\tWarn(\"Ignoring lighthouse.advertise_addrs report because it is within the nebula network range\")\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tadvAddrs = append(advAddrs, netip.AddrPortFrom(addr, uint16(port)))\n\t\t}\n\n\t\tlh.advertiseAddrs.Store(&advAddrs)\n\n\t\tif !initial {\n\t\t\tlh.l.Info(\"lighthouse.advertise_addrs has changed\")\n\t\t}\n\t}\n\n\tif initial || c.HasChanged(\"lighthouse.interval\") {\n\t\tlh.interval.Store(int64(c.GetInt(\"lighthouse.interval\", 10)))\n\n\t\tif !initial {\n\t\t\tlh.l.Infof(\"lighthouse.interval changed to %v\", lh.interval.Load())\n\n\t\t\tif lh.updateCancel != nil {\n\t\t\t\t// May not always have a running routine\n\t\t\t\tlh.updateCancel()\n\t\t\t}\n\n\t\t\tlh.StartUpdateWorker()\n\t\t}\n\t}\n\n\tif initial || c.HasChanged(\"lighthouse.remote_allow_list\") || c.HasChanged(\"lighthouse.remote_allow_ranges\") {\n\t\tral, err := NewRemoteAllowListFromConfig(c, \"lighthouse.remote_allow_list\", \"lighthouse.remote_allow_ranges\")\n\t\tif err != nil {\n\t\t\treturn util.NewContextualError(\"Invalid lighthouse.remote_allow_list\", nil, err)\n\t\t}\n\n\t\tlh.remoteAllowList.Store(ral)\n\t\tif !initial {\n\t\t\tlh.l.Info(\"lighthouse.remote_allow_list and/or lighthouse.remote_allow_ranges has changed\")\n\t\t}\n\t}\n\n\tif initial || c.HasChanged(\"lighthouse.local_allow_list\") {\n\t\tlal, err := NewLocalAllowListFromConfig(c, \"lighthouse.local_allow_list\")\n\t\tif err != nil {\n\t\t\treturn util.NewContextualError(\"Invalid lighthouse.local_allow_list\", nil, err)\n\t\t}\n\n\t\tlh.localAllowList.Store(lal)\n\t\tif !initial {\n\t\t\tlh.l.Info(\"lighthouse.local_allow_list has changed\")\n\t\t}\n\t}\n\n\tif initial || c.HasChanged(\"lighthouse.calculated_remotes\") {\n\t\tcr, err := NewCalculatedRemotesFromConfig(c, \"lighthouse.calculated_remotes\")\n\t\tif err != nil {\n\t\t\treturn util.NewContextualError(\"Invalid lighthouse.calculated_remotes\", nil, err)\n\t\t}\n\n\t\tlh.calculatedRemotes.Store(cr)\n\t\tif !initial {\n\t\t\tlh.l.Info(\"lighthouse.calculated_remotes has changed\")\n\t\t}\n\t}\n\n\t//NOTE: many things will get much simpler when we combine static_host_map and lighthouse.hosts in config\n\tif initial || c.HasChanged(\"static_host_map\") || c.HasChanged(\"static_map.cadence\") || c.HasChanged(\"static_map.network\") || c.HasChanged(\"static_map.lookup_timeout\") {\n\t\t// Clean up. Entries still in the static_host_map will be re-built.\n\t\t// Entries no longer present must have their (possible) background DNS goroutines stopped.\n\t\tif existingStaticList := lh.staticList.Load(); existingStaticList != nil {\n\t\t\tlh.RLock()\n\t\t\tfor staticVpnAddr := range *existingStaticList {\n\t\t\t\tif am, ok := lh.addrMap[staticVpnAddr]; ok && am != nil {\n\t\t\t\t\tam.hr.Cancel()\n\t\t\t\t}\n\t\t\t}\n\t\t\tlh.RUnlock()\n\t\t}\n\t\t// Build a new list based on current config.\n\t\tstaticList := make(map[netip.Addr]struct{})\n\t\terr := lh.loadStaticMap(c, staticList)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tlh.staticList.Store(&staticList)\n\t\tif !initial {\n\t\t\tif c.HasChanged(\"static_host_map\") {\n\t\t\t\tlh.l.Info(\"static_host_map has changed\")\n\t\t\t}\n\t\t\tif c.HasChanged(\"static_map.cadence\") {\n\t\t\t\tlh.l.Info(\"static_map.cadence has changed\")\n\t\t\t}\n\t\t\tif c.HasChanged(\"static_map.network\") {\n\t\t\t\tlh.l.Info(\"static_map.network has changed\")\n\t\t\t}\n\t\t\tif c.HasChanged(\"static_map.lookup_timeout\") {\n\t\t\t\tlh.l.Info(\"static_map.lookup_timeout has changed\")\n\t\t\t}\n\t\t}\n\t}\n\n\tif initial || c.HasChanged(\"lighthouse.hosts\") {\n\t\tlhList, err := lh.parseLighthouses(c)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tlh.lighthouses.Store(&lhList)\n\t\tif !initial {\n\t\t\t//NOTE: we are not tearing down existing lighthouse connections because they might be used for non lighthouse traffic\n\t\t\tlh.l.Info(\"lighthouse.hosts has changed\")\n\t\t}\n\t}\n\n\tif initial || c.HasChanged(\"relay.relays\") {\n\t\tswitch c.GetBool(\"relay.am_relay\", false) {\n\t\tcase true:\n\t\t\t// Relays aren't allowed to specify other relays\n\t\t\tif len(c.GetStringSlice(\"relay.relays\", nil)) > 0 {\n\t\t\t\tlh.l.Info(\"Ignoring relays from config because am_relay is true\")\n\t\t\t}\n\t\t\trelaysForMe := []netip.Addr{}\n\t\t\tlh.relaysForMe.Store(&relaysForMe)\n\t\tcase false:\n\t\t\trelaysForMe := []netip.Addr{}\n\t\t\tfor _, v := range c.GetStringSlice(\"relay.relays\", nil) {\n\t\t\t\tconfigRIP, err := netip.ParseAddr(v)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlh.l.WithField(\"relay\", v).WithError(err).Warn(\"Parse relay from config failed\")\n\t\t\t\t} else {\n\t\t\t\t\tlh.l.WithField(\"relay\", v).Info(\"Read relay from config\")\n\t\t\t\t\trelaysForMe = append(relaysForMe, configRIP)\n\t\t\t\t}\n\t\t\t}\n\t\t\tlh.relaysForMe.Store(&relaysForMe)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (lh *LightHouse) parseLighthouses(c *config.C) ([]netip.Addr, error) {\n\tlhs := c.GetStringSlice(\"lighthouse.hosts\", []string{})\n\tif lh.amLighthouse && len(lhs) != 0 {\n\t\tlh.l.Warn(\"lighthouse.am_lighthouse enabled on node but upstream lighthouses exist in config\")\n\t}\n\tout := make([]netip.Addr, len(lhs))\n\n\tfor i, host := range lhs {\n\t\taddr, err := netip.ParseAddr(host)\n\t\tif err != nil {\n\t\t\treturn nil, util.NewContextualError(\"Unable to parse lighthouse host entry\", m{\"host\": host, \"entry\": i + 1}, err)\n\t\t}\n\n\t\tif !lh.myVpnNetworksTable.Contains(addr) {\n\t\t\tlh.l.WithFields(m{\"vpnAddr\": addr, \"networks\": lh.myVpnNetworks}).\n\t\t\t\tWarn(\"lighthouse host is not within our networks, lighthouse functionality will work but layer 3 network traffic to the lighthouse will not\")\n\t\t}\n\t\tout[i] = addr\n\t}\n\n\tif !lh.amLighthouse && len(out) == 0 {\n\t\tlh.l.Warn(\"No lighthouse.hosts configured, this host will only be able to initiate tunnels with static_host_map entries\")\n\t}\n\n\tstaticList := lh.GetStaticHostList()\n\tfor i := range out {\n\t\tif _, ok := staticList[out[i]]; !ok {\n\t\t\treturn nil, fmt.Errorf(\"lighthouse %s does not have a static_host_map entry\", out[i])\n\t\t}\n\t}\n\n\treturn out, nil\n}\n\nfunc getStaticMapCadence(c *config.C) (time.Duration, error) {\n\tcadence := c.GetString(\"static_map.cadence\", \"30s\")\n\td, err := time.ParseDuration(cadence)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn d, nil\n}\n\nfunc getStaticMapLookupTimeout(c *config.C) (time.Duration, error) {\n\tlookupTimeout := c.GetString(\"static_map.lookup_timeout\", \"250ms\")\n\td, err := time.ParseDuration(lookupTimeout)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn d, nil\n}\n\nfunc getStaticMapNetwork(c *config.C) (string, error) {\n\tnetwork := c.GetString(\"static_map.network\", \"ip4\")\n\tif network != \"ip\" && network != \"ip4\" && network != \"ip6\" {\n\t\treturn \"\", fmt.Errorf(\"static_map.network must be one of ip, ip4, or ip6\")\n\t}\n\treturn network, nil\n}\n\nfunc (lh *LightHouse) loadStaticMap(c *config.C, staticList map[netip.Addr]struct{}) error {\n\td, err := getStaticMapCadence(c)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tnetwork, err := getStaticMapNetwork(c)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlookupTimeout, err := getStaticMapLookupTimeout(c)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tshm := c.GetMap(\"static_host_map\", map[string]any{})\n\ti := 0\n\n\tfor k, v := range shm {\n\t\tvpnAddr, err := netip.ParseAddr(fmt.Sprintf(\"%v\", k))\n\t\tif err != nil {\n\t\t\treturn util.NewContextualError(\"Unable to parse static_host_map entry\", m{\"host\": k, \"entry\": i + 1}, err)\n\t\t}\n\n\t\tif !lh.myVpnNetworksTable.Contains(vpnAddr) {\n\t\t\tlh.l.WithFields(m{\"vpnAddr\": vpnAddr, \"networks\": lh.myVpnNetworks, \"entry\": i + 1}).\n\t\t\t\tWarn(\"static_host_map key is not within our networks, layer 3 network traffic to this host will not work\")\n\t\t}\n\n\t\tvals, ok := v.([]any)\n\t\tif !ok {\n\t\t\tvals = []any{v}\n\t\t}\n\t\tremoteAddrs := []string{}\n\t\tfor _, v := range vals {\n\t\t\tremoteAddrs = append(remoteAddrs, fmt.Sprintf(\"%v\", v))\n\t\t}\n\n\t\terr = lh.addStaticRemotes(i, d, network, lookupTimeout, vpnAddr, remoteAddrs, staticList)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ti++\n\t}\n\n\treturn nil\n}\n\nfunc (lh *LightHouse) Query(vpnAddr netip.Addr) *RemoteList {\n\tif !lh.IsLighthouseAddr(vpnAddr) {\n\t\tlh.QueryServer(vpnAddr)\n\t}\n\tlh.RLock()\n\tif v, ok := lh.addrMap[vpnAddr]; ok {\n\t\tlh.RUnlock()\n\t\treturn v\n\t}\n\tlh.RUnlock()\n\treturn nil\n}\n\n// QueryServer is asynchronous so no reply should be expected\nfunc (lh *LightHouse) QueryServer(vpnAddr netip.Addr) {\n\t// Don't put lighthouse addrs in the query channel because we can't query lighthouses about lighthouses\n\tif lh.amLighthouse || lh.IsLighthouseAddr(vpnAddr) {\n\t\treturn\n\t}\n\n\tlh.queryChan <- vpnAddr\n}\n\nfunc (lh *LightHouse) QueryCache(vpnAddrs []netip.Addr) *RemoteList {\n\tlh.RLock()\n\tif v, ok := lh.addrMap[vpnAddrs[0]]; ok {\n\t\tlh.RUnlock()\n\t\treturn v\n\t}\n\tlh.RUnlock()\n\n\tlh.Lock()\n\tdefer lh.Unlock()\n\t// Add an entry if we don't already have one\n\treturn lh.unlockedGetRemoteList(vpnAddrs) //todo CERT-V2 this contains addrmap lookups we could potentially skip\n}\n\n// queryAndPrepMessage is a lock helper on RemoteList, assisting the caller to build a lighthouse message containing\n// details from the remote list. It looks for a hit in the addrMap and a hit in the RemoteList under the owner vpnAddr\n// If one is found then f() is called with proper locking, f() must return result of n.MarshalTo()\nfunc (lh *LightHouse) queryAndPrepMessage(vpnAddr netip.Addr, f func(*cache) (int, error)) (bool, int, error) {\n\tlh.RLock()\n\t// Do we have an entry in the main cache?\n\tif v, ok := lh.addrMap[vpnAddr]; ok {\n\t\t// Swap lh lock for remote list lock\n\t\tv.RLock()\n\t\tdefer v.RUnlock()\n\n\t\tlh.RUnlock()\n\n\t\t// We may be asking about a non primary address so lets get the primary address\n\t\tif slices.Contains(v.vpnAddrs, vpnAddr) {\n\t\t\tvpnAddr = v.vpnAddrs[0]\n\t\t}\n\t\tc := v.cache[vpnAddr]\n\t\t// Make sure we have\n\t\tif c != nil {\n\t\t\tn, err := f(c)\n\t\t\treturn true, n, err\n\t\t}\n\t\treturn false, 0, nil\n\t}\n\tlh.RUnlock()\n\treturn false, 0, nil\n}\n\nfunc (lh *LightHouse) DeleteVpnAddrs(allVpnAddrs []netip.Addr) {\n\t// First we check the static host map. If any of the VpnAddrs to be deleted are present, do nothing.\n\tstaticList := lh.GetStaticHostList()\n\tfor _, addr := range allVpnAddrs {\n\t\tif _, ok := staticList[addr]; ok {\n\t\t\treturn\n\t\t}\n\t}\n\n\t// None of the VpnAddrs were present. Now we can do the deletes.\n\tlh.Lock()\n\trm, ok := lh.addrMap[allVpnAddrs[0]]\n\tif ok {\n\t\tfor _, addr := range allVpnAddrs {\n\t\t\tsrm := lh.addrMap[addr]\n\t\t\tif srm == rm {\n\t\t\t\tdelete(lh.addrMap, addr)\n\t\t\t\tif lh.l.Level >= logrus.DebugLevel {\n\t\t\t\t\tlh.l.Debugf(\"deleting %s from lighthouse.\", addr)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tlh.Unlock()\n}\n\n// AddStaticRemote adds a static host entry for vpnAddr as ourselves as the owner\n// We are the owner because we don't want a lighthouse server to advertise for static hosts it was configured with\n// And we don't want a lighthouse query reply to interfere with our learned cache if we are a client\n// NOTE: this function should not interact with any hot path objects, like lh.staticList, the caller should handle it\nfunc (lh *LightHouse) addStaticRemotes(i int, d time.Duration, network string, timeout time.Duration, vpnAddr netip.Addr, toAddrs []string, staticList map[netip.Addr]struct{}) error {\n\tlh.Lock()\n\tam := lh.unlockedGetRemoteList([]netip.Addr{vpnAddr})\n\tam.Lock()\n\tdefer am.Unlock()\n\tctx := lh.ctx\n\tlh.Unlock()\n\n\thr, err := NewHostnameResults(ctx, lh.l, d, network, timeout, toAddrs, func() {\n\t\t// This callback runs whenever the DNS hostname resolver finds a different set of addr's\n\t\t// in its resolution for hostnames.\n\t\tam.Lock()\n\t\tdefer am.Unlock()\n\t\tam.shouldRebuild = true\n\t})\n\tif err != nil {\n\t\treturn util.NewContextualError(\"Static host address could not be parsed\", m{\"vpnAddr\": vpnAddr, \"entry\": i + 1}, err)\n\t}\n\tam.unlockedSetHostnamesResults(hr)\n\n\tfor _, addrPort := range hr.GetAddrs() {\n\t\tif !lh.shouldAdd([]netip.Addr{vpnAddr}, addrPort.Addr()) {\n\t\t\tcontinue\n\t\t}\n\t\tswitch {\n\t\tcase addrPort.Addr().Is4():\n\t\t\tam.unlockedPrependV4(lh.myVpnNetworks[0].Addr(), netAddrToProtoV4AddrPort(addrPort.Addr(), addrPort.Port()))\n\t\tcase addrPort.Addr().Is6():\n\t\t\tam.unlockedPrependV6(lh.myVpnNetworks[0].Addr(), netAddrToProtoV6AddrPort(addrPort.Addr(), addrPort.Port()))\n\t\t}\n\t}\n\n\t// Mark it as static in the caller provided map\n\tstaticList[vpnAddr] = struct{}{}\n\treturn nil\n}\n\n// addCalculatedRemotes adds any calculated remotes based on the\n// lighthouse.calculated_remotes configuration. It returns true if any\n// calculated remotes were added\nfunc (lh *LightHouse) addCalculatedRemotes(vpnAddr netip.Addr) bool {\n\ttree := lh.getCalculatedRemotes()\n\tif tree == nil {\n\t\treturn false\n\t}\n\tcalculatedRemotes, ok := tree.Lookup(vpnAddr)\n\tif !ok {\n\t\treturn false\n\t}\n\n\tvar calculatedV4 []*V4AddrPort\n\tvar calculatedV6 []*V6AddrPort\n\tfor _, cr := range calculatedRemotes {\n\t\tif vpnAddr.Is4() {\n\t\t\tc := cr.ApplyV4(vpnAddr)\n\t\t\tif c != nil {\n\t\t\t\tcalculatedV4 = append(calculatedV4, c)\n\t\t\t}\n\t\t} else if vpnAddr.Is6() {\n\t\t\tc := cr.ApplyV6(vpnAddr)\n\t\t\tif c != nil {\n\t\t\t\tcalculatedV6 = append(calculatedV6, c)\n\t\t\t}\n\t\t}\n\t}\n\n\tlh.Lock()\n\tam := lh.unlockedGetRemoteList([]netip.Addr{vpnAddr})\n\tam.Lock()\n\tdefer am.Unlock()\n\tlh.Unlock()\n\n\tif len(calculatedV4) > 0 {\n\t\tam.unlockedSetV4(lh.myVpnNetworks[0].Addr(), vpnAddr, calculatedV4, lh.unlockedShouldAddV4)\n\t}\n\n\tif len(calculatedV6) > 0 {\n\t\tam.unlockedSetV6(lh.myVpnNetworks[0].Addr(), vpnAddr, calculatedV6, lh.unlockedShouldAddV6)\n\t}\n\n\treturn len(calculatedV4) > 0 || len(calculatedV6) > 0\n}\n\n// unlockedGetRemoteList assumes you have the lh lock\nfunc (lh *LightHouse) unlockedGetRemoteList(allAddrs []netip.Addr) *RemoteList {\n\t// before we go and make a new remotelist, we need to make sure we don't have one for any of this set of vpnaddrs yet\n\tfor i, addr := range allAddrs {\n\t\tam, ok := lh.addrMap[addr]\n\t\tif ok {\n\t\t\tif i != 0 {\n\t\t\t\tlh.addrMap[allAddrs[0]] = am\n\t\t\t}\n\t\t\treturn am\n\t\t}\n\t}\n\n\tam := NewRemoteList(allAddrs, lh.shouldAdd)\n\tfor _, addr := range allAddrs {\n\t\tlh.addrMap[addr] = am\n\t}\n\treturn am\n}\n\nfunc (lh *LightHouse) shouldAdd(vpnAddrs []netip.Addr, to netip.Addr) bool {\n\tallow := lh.GetRemoteAllowList().AllowAll(vpnAddrs, to)\n\tif lh.l.Level >= logrus.TraceLevel {\n\t\tlh.l.WithField(\"vpnAddrs\", vpnAddrs).WithField(\"udpAddr\", to).WithField(\"allow\", allow).\n\t\t\tTrace(\"remoteAllowList.Allow\")\n\t}\n\tif !allow {\n\t\treturn false\n\t}\n\n\tif lh.myVpnNetworksTable.Contains(to) {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// unlockedShouldAddV4 checks if to is allowed by our allow list\nfunc (lh *LightHouse) unlockedShouldAddV4(vpnAddr netip.Addr, to *V4AddrPort) bool {\n\tudpAddr := protoV4AddrPortToNetAddrPort(to)\n\tallow := lh.GetRemoteAllowList().Allow(vpnAddr, udpAddr.Addr())\n\tif lh.l.Level >= logrus.TraceLevel {\n\t\tlh.l.WithField(\"vpnAddr\", vpnAddr).WithField(\"udpAddr\", udpAddr).WithField(\"allow\", allow).\n\t\t\tTrace(\"remoteAllowList.Allow\")\n\t}\n\n\tif !allow {\n\t\treturn false\n\t}\n\n\tif lh.myVpnNetworksTable.Contains(udpAddr.Addr()) {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// unlockedShouldAddV6 checks if to is allowed by our allow list\nfunc (lh *LightHouse) unlockedShouldAddV6(vpnAddr netip.Addr, to *V6AddrPort) bool {\n\tudpAddr := protoV6AddrPortToNetAddrPort(to)\n\tallow := lh.GetRemoteAllowList().Allow(vpnAddr, udpAddr.Addr())\n\tif lh.l.Level >= logrus.TraceLevel {\n\t\tlh.l.WithField(\"vpnAddr\", vpnAddr).WithField(\"udpAddr\", udpAddr).WithField(\"allow\", allow).\n\t\t\tTrace(\"remoteAllowList.Allow\")\n\t}\n\n\tif !allow {\n\t\treturn false\n\t}\n\n\tif lh.myVpnNetworksTable.Contains(udpAddr.Addr()) {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\nfunc (lh *LightHouse) IsLighthouseAddr(vpnAddr netip.Addr) bool {\n\tl := lh.GetLighthouses()\n\treturn slices.Contains(l, vpnAddr)\n}\n\nfunc (lh *LightHouse) IsAnyLighthouseAddr(vpnAddrs []netip.Addr) bool {\n\tl := lh.GetLighthouses()\n\tfor i := range vpnAddrs {\n\t\tif slices.Contains(l, vpnAddrs[i]) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (lh *LightHouse) startQueryWorker() {\n\tif lh.amLighthouse {\n\t\treturn\n\t}\n\n\tgo func() {\n\t\tnb := make([]byte, 12, 12)\n\t\tout := make([]byte, mtu)\n\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-lh.ctx.Done():\n\t\t\t\treturn\n\t\t\tcase addr := <-lh.queryChan:\n\t\t\t\tlh.innerQueryServer(addr, nb, out)\n\t\t\t}\n\t\t}\n\t}()\n}\n\nfunc (lh *LightHouse) innerQueryServer(addr netip.Addr, nb, out []byte) {\n\tif lh.IsLighthouseAddr(addr) {\n\t\treturn\n\t}\n\n\tmsg := &NebulaMeta{\n\t\tType:    NebulaMeta_HostQuery,\n\t\tDetails: &NebulaMetaDetails{},\n\t}\n\n\tvar v1Query, v2Query []byte\n\tvar err error\n\tvar v cert.Version\n\tqueried := 0\n\tlighthouses := lh.GetLighthouses()\n\n\tfor _, lhVpnAddr := range lighthouses {\n\t\thi := lh.ifce.GetHostInfo(lhVpnAddr)\n\t\tif hi != nil {\n\t\t\tv = hi.ConnectionState.myCert.Version()\n\t\t} else {\n\t\t\tv = lh.ifce.GetCertState().initiatingVersion\n\t\t}\n\n\t\tif v == cert.Version1 {\n\t\t\tif !addr.Is4() {\n\t\t\t\tlh.l.WithField(\"queryVpnAddr\", addr).WithField(\"lighthouseAddr\", lhVpnAddr).\n\t\t\t\t\tError(\"Can't query lighthouse for v6 address using a v1 protocol\")\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif v1Query == nil {\n\t\t\t\tb := addr.As4()\n\t\t\t\tmsg.Details.VpnAddr = nil\n\t\t\t\tmsg.Details.OldVpnAddr = binary.BigEndian.Uint32(b[:])\n\n\t\t\t\tv1Query, err = msg.Marshal()\n\t\t\t\tif err != nil {\n\t\t\t\t\tlh.l.WithError(err).WithField(\"queryVpnAddr\", addr).\n\t\t\t\t\t\tWithField(\"lighthouseAddr\", lhVpnAddr).\n\t\t\t\t\t\tError(\"Failed to marshal lighthouse v1 query payload\")\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlh.ifce.SendMessageToVpnAddr(header.LightHouse, 0, lhVpnAddr, v1Query, nb, out)\n\t\t\tqueried++\n\n\t\t} else if v == cert.Version2 {\n\t\t\tif v2Query == nil {\n\t\t\t\tmsg.Details.OldVpnAddr = 0\n\t\t\t\tmsg.Details.VpnAddr = netAddrToProtoAddr(addr)\n\n\t\t\t\tv2Query, err = msg.Marshal()\n\t\t\t\tif err != nil {\n\t\t\t\t\tlh.l.WithError(err).WithField(\"queryVpnAddr\", addr).\n\t\t\t\t\t\tWithField(\"lighthouseAddr\", lhVpnAddr).\n\t\t\t\t\t\tError(\"Failed to marshal lighthouse v2 query payload\")\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlh.ifce.SendMessageToVpnAddr(header.LightHouse, 0, lhVpnAddr, v2Query, nb, out)\n\t\t\tqueried++\n\n\t\t} else {\n\t\t\tlh.l.Debugf(\"Can not query lighthouse for %v using unknown protocol version: %v\", addr, v)\n\t\t\tcontinue\n\t\t}\n\t}\n\n\tlh.metricTx(NebulaMeta_HostQuery, int64(queried))\n}\n\nfunc (lh *LightHouse) StartUpdateWorker() {\n\tinterval := lh.GetUpdateInterval()\n\tif lh.amLighthouse || interval == 0 {\n\t\treturn\n\t}\n\n\tclockSource := time.NewTicker(time.Second * time.Duration(interval))\n\tupdateCtx, cancel := context.WithCancel(lh.ctx)\n\tlh.updateCancel = cancel\n\n\tgo func() {\n\t\tdefer clockSource.Stop()\n\n\t\tfor {\n\t\t\tlh.SendUpdate()\n\n\t\t\tselect {\n\t\t\tcase <-updateCtx.Done():\n\t\t\t\treturn\n\t\t\tcase <-clockSource.C:\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t}()\n}\n\nfunc (lh *LightHouse) SendUpdate() {\n\tvar v4 []*V4AddrPort\n\tvar v6 []*V6AddrPort\n\n\tfor _, e := range lh.GetAdvertiseAddrs() {\n\t\tif e.Addr().Is4() {\n\t\t\tv4 = append(v4, netAddrToProtoV4AddrPort(e.Addr(), e.Port()))\n\t\t} else {\n\t\t\tv6 = append(v6, netAddrToProtoV6AddrPort(e.Addr(), e.Port()))\n\t\t}\n\t}\n\n\tlal := lh.GetLocalAllowList()\n\tfor _, e := range localAddrs(lh.l, lal) {\n\t\tif lh.myVpnNetworksTable.Contains(e) {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Only add addrs that aren't my VPN/tun networks\n\t\tif e.Is4() {\n\t\t\tv4 = append(v4, netAddrToProtoV4AddrPort(e, uint16(lh.nebulaPort)))\n\t\t} else {\n\t\t\tv6 = append(v6, netAddrToProtoV6AddrPort(e, uint16(lh.nebulaPort)))\n\t\t}\n\t}\n\n\tnb := make([]byte, 12, 12)\n\tout := make([]byte, mtu)\n\n\tvar v1Update, v2Update []byte\n\tvar err error\n\tupdated := 0\n\tlighthouses := lh.GetLighthouses()\n\n\tfor _, lhVpnAddr := range lighthouses {\n\t\tvar v cert.Version\n\t\thi := lh.ifce.GetHostInfo(lhVpnAddr)\n\t\tif hi != nil {\n\t\t\tv = hi.ConnectionState.myCert.Version()\n\t\t} else {\n\t\t\tv = lh.ifce.GetCertState().initiatingVersion\n\t\t}\n\t\tif v == cert.Version1 {\n\t\t\tif v1Update == nil {\n\t\t\t\tif !lh.myVpnNetworks[0].Addr().Is4() {\n\t\t\t\t\tlh.l.WithField(\"lighthouseAddr\", lhVpnAddr).\n\t\t\t\t\t\tWarn(\"cannot update lighthouse using v1 protocol without an IPv4 address\")\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tvar relays []uint32\n\t\t\t\tfor _, r := range lh.GetRelaysForMe() {\n\t\t\t\t\tif !r.Is4() {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tb := r.As4()\n\t\t\t\t\trelays = append(relays, binary.BigEndian.Uint32(b[:]))\n\t\t\t\t}\n\t\t\t\tb := lh.myVpnNetworks[0].Addr().As4()\n\t\t\t\tmsg := NebulaMeta{\n\t\t\t\t\tType: NebulaMeta_HostUpdateNotification,\n\t\t\t\t\tDetails: &NebulaMetaDetails{\n\t\t\t\t\t\tV4AddrPorts:      v4,\n\t\t\t\t\t\tV6AddrPorts:      v6,\n\t\t\t\t\t\tOldRelayVpnAddrs: relays,\n\t\t\t\t\t\tOldVpnAddr:       binary.BigEndian.Uint32(b[:]),\n\t\t\t\t\t},\n\t\t\t\t}\n\n\t\t\t\tv1Update, err = msg.Marshal()\n\t\t\t\tif err != nil {\n\t\t\t\t\tlh.l.WithError(err).WithField(\"lighthouseAddr\", lhVpnAddr).\n\t\t\t\t\t\tError(\"Error while marshaling for lighthouse v1 update\")\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlh.ifce.SendMessageToVpnAddr(header.LightHouse, 0, lhVpnAddr, v1Update, nb, out)\n\t\t\tupdated++\n\n\t\t} else if v == cert.Version2 {\n\t\t\tif v2Update == nil {\n\t\t\t\tvar relays []*Addr\n\t\t\t\tfor _, r := range lh.GetRelaysForMe() {\n\t\t\t\t\trelays = append(relays, netAddrToProtoAddr(r))\n\t\t\t\t}\n\n\t\t\t\tmsg := NebulaMeta{\n\t\t\t\t\tType: NebulaMeta_HostUpdateNotification,\n\t\t\t\t\tDetails: &NebulaMetaDetails{\n\t\t\t\t\t\tV4AddrPorts:   v4,\n\t\t\t\t\t\tV6AddrPorts:   v6,\n\t\t\t\t\t\tRelayVpnAddrs: relays,\n\t\t\t\t\t},\n\t\t\t\t}\n\n\t\t\t\tv2Update, err = msg.Marshal()\n\t\t\t\tif err != nil {\n\t\t\t\t\tlh.l.WithError(err).WithField(\"lighthouseAddr\", lhVpnAddr).\n\t\t\t\t\t\tError(\"Error while marshaling for lighthouse v2 update\")\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlh.ifce.SendMessageToVpnAddr(header.LightHouse, 0, lhVpnAddr, v2Update, nb, out)\n\t\t\tupdated++\n\n\t\t} else {\n\t\t\tlh.l.Debugf(\"Can not update lighthouse using unknown protocol version: %v\", v)\n\t\t\tcontinue\n\t\t}\n\t}\n\n\tlh.metricTx(NebulaMeta_HostUpdateNotification, int64(updated))\n}\n\ntype LightHouseHandler struct {\n\tlh   *LightHouse\n\tnb   []byte\n\tout  []byte\n\tpb   []byte\n\tmeta *NebulaMeta\n\tl    *logrus.Logger\n}\n\nfunc (lh *LightHouse) NewRequestHandler() *LightHouseHandler {\n\tlhh := &LightHouseHandler{\n\t\tlh:  lh,\n\t\tnb:  make([]byte, 12, 12),\n\t\tout: make([]byte, mtu),\n\t\tl:   lh.l,\n\t\tpb:  make([]byte, mtu),\n\n\t\tmeta: &NebulaMeta{\n\t\t\tDetails: &NebulaMetaDetails{},\n\t\t},\n\t}\n\n\treturn lhh\n}\n\nfunc (lh *LightHouse) metricRx(t NebulaMeta_MessageType, i int64) {\n\tlh.metrics.Rx(header.MessageType(t), 0, i)\n}\n\nfunc (lh *LightHouse) metricTx(t NebulaMeta_MessageType, i int64) {\n\tlh.metrics.Tx(header.MessageType(t), 0, i)\n}\n\n// This method is similar to Reset(), but it re-uses the pointer structs\n// so that we don't have to re-allocate them\nfunc (lhh *LightHouseHandler) resetMeta() *NebulaMeta {\n\tdetails := lhh.meta.Details\n\tlhh.meta.Reset()\n\n\t// Keep the array memory around\n\tdetails.V4AddrPorts = details.V4AddrPorts[:0]\n\tdetails.V6AddrPorts = details.V6AddrPorts[:0]\n\tdetails.RelayVpnAddrs = details.RelayVpnAddrs[:0]\n\tdetails.OldRelayVpnAddrs = details.OldRelayVpnAddrs[:0]\n\tdetails.OldVpnAddr = 0\n\tdetails.VpnAddr = nil\n\tlhh.meta.Details = details\n\n\treturn lhh.meta\n}\n\nfunc (lhh *LightHouseHandler) HandleRequest(rAddr netip.AddrPort, fromVpnAddrs []netip.Addr, p []byte, w EncWriter) {\n\tn := lhh.resetMeta()\n\terr := n.Unmarshal(p)\n\tif err != nil {\n\t\tlhh.l.WithError(err).WithField(\"vpnAddrs\", fromVpnAddrs).WithField(\"udpAddr\", rAddr).\n\t\t\tError(\"Failed to unmarshal lighthouse packet\")\n\t\treturn\n\t}\n\n\tif n.Details == nil {\n\t\tlhh.l.WithField(\"vpnAddrs\", fromVpnAddrs).WithField(\"udpAddr\", rAddr).\n\t\t\tError(\"Invalid lighthouse update\")\n\t\treturn\n\t}\n\n\tlhh.lh.metricRx(n.Type, 1)\n\n\tswitch n.Type {\n\tcase NebulaMeta_HostQuery:\n\t\tlhh.handleHostQuery(n, fromVpnAddrs, rAddr, w)\n\n\tcase NebulaMeta_HostQueryReply:\n\t\tlhh.handleHostQueryReply(n, fromVpnAddrs)\n\n\tcase NebulaMeta_HostUpdateNotification:\n\t\tlhh.handleHostUpdateNotification(n, fromVpnAddrs, w)\n\n\tcase NebulaMeta_HostMovedNotification:\n\tcase NebulaMeta_HostPunchNotification:\n\t\tlhh.handleHostPunchNotification(n, fromVpnAddrs, w)\n\n\tcase NebulaMeta_HostUpdateNotificationAck:\n\t\t// noop\n\t}\n}\n\nfunc (lhh *LightHouseHandler) handleHostQuery(n *NebulaMeta, fromVpnAddrs []netip.Addr, addr netip.AddrPort, w EncWriter) {\n\t// Exit if we don't answer queries\n\tif !lhh.lh.amLighthouse {\n\t\tif lhh.l.Level >= logrus.DebugLevel {\n\t\t\tlhh.l.Debugln(\"I don't answer queries, but received from: \", addr)\n\t\t}\n\t\treturn\n\t}\n\n\tqueryVpnAddr, useVersion, err := n.Details.GetVpnAddrAndVersion()\n\tif err != nil {\n\t\tif lhh.l.Level >= logrus.DebugLevel {\n\t\t\tlhh.l.WithField(\"from\", fromVpnAddrs).WithField(\"details\", n.Details).\n\t\t\t\tDebugln(\"Dropping malformed HostQuery\")\n\t\t}\n\t\treturn\n\t}\n\tif useVersion == cert.Version1 && queryVpnAddr.Is6() {\n\t\t// this case really shouldn't be possible to represent, but reject it anyway.\n\t\tif lhh.l.Level >= logrus.DebugLevel {\n\t\t\tlhh.l.WithField(\"vpnAddrs\", fromVpnAddrs).WithField(\"queryVpnAddr\", queryVpnAddr).\n\t\t\t\tDebugln(\"invalid vpn addr for v1 handleHostQuery\")\n\t\t}\n\t\treturn\n\t}\n\n\tfound, ln, err := lhh.lh.queryAndPrepMessage(queryVpnAddr, func(c *cache) (int, error) {\n\t\tn = lhh.resetMeta()\n\t\tn.Type = NebulaMeta_HostQueryReply\n\t\tif useVersion == cert.Version1 {\n\t\t\tb := queryVpnAddr.As4()\n\t\t\tn.Details.OldVpnAddr = binary.BigEndian.Uint32(b[:])\n\t\t} else {\n\t\t\tn.Details.VpnAddr = netAddrToProtoAddr(queryVpnAddr)\n\t\t}\n\n\t\tlhh.coalesceAnswers(useVersion, c, n)\n\n\t\treturn n.MarshalTo(lhh.pb)\n\t})\n\n\tif !found {\n\t\treturn\n\t}\n\n\tif err != nil {\n\t\tlhh.l.WithError(err).WithField(\"vpnAddrs\", fromVpnAddrs).Error(\"Failed to marshal lighthouse host query reply\")\n\t\treturn\n\t}\n\n\tlhh.lh.metricTx(NebulaMeta_HostQueryReply, 1)\n\tw.SendMessageToVpnAddr(header.LightHouse, 0, fromVpnAddrs[0], lhh.pb[:ln], lhh.nb, lhh.out[:0])\n\n\tlhh.sendHostPunchNotification(n, fromVpnAddrs, queryVpnAddr, w)\n}\n\n// sendHostPunchNotification signals the other side to punch some zero byte udp packets\nfunc (lhh *LightHouseHandler) sendHostPunchNotification(n *NebulaMeta, fromVpnAddrs []netip.Addr, punchNotifDest netip.Addr, w EncWriter) {\n\twhereToPunch := fromVpnAddrs[0]\n\tfound, ln, err := lhh.lh.queryAndPrepMessage(whereToPunch, func(c *cache) (int, error) {\n\t\tn = lhh.resetMeta()\n\t\tn.Type = NebulaMeta_HostPunchNotification\n\t\ttargetHI := lhh.lh.ifce.GetHostInfo(punchNotifDest)\n\t\tvar useVersion cert.Version\n\t\tif targetHI == nil {\n\t\t\tuseVersion = lhh.lh.ifce.GetCertState().initiatingVersion\n\t\t} else {\n\t\t\tcrt := targetHI.GetCert().Certificate\n\t\t\tuseVersion = crt.Version()\n\t\t\t// we can only retarget if we have a hostinfo\n\t\t\tnewDest, ok := findNetworkUnion(crt.Networks(), fromVpnAddrs)\n\t\t\tif ok {\n\t\t\t\twhereToPunch = newDest\n\t\t\t} else {\n\t\t\t\tif lhh.l.Level >= logrus.DebugLevel {\n\t\t\t\t\tlhh.l.WithField(\"to\", crt.Networks()).Debugln(\"unable to punch to host, no addresses in common\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif useVersion == cert.Version1 {\n\t\t\tif !whereToPunch.Is4() {\n\t\t\t\treturn 0, fmt.Errorf(\"invalid vpn addr for v1 handleHostQuery\")\n\t\t\t}\n\t\t\tb := whereToPunch.As4()\n\t\t\tn.Details.OldVpnAddr = binary.BigEndian.Uint32(b[:])\n\t\t} else if useVersion == cert.Version2 {\n\t\t\tn.Details.VpnAddr = netAddrToProtoAddr(whereToPunch)\n\t\t} else {\n\t\t\treturn 0, errors.New(\"unsupported version\")\n\t\t}\n\t\tlhh.coalesceAnswers(useVersion, c, n)\n\n\t\treturn n.MarshalTo(lhh.pb)\n\t})\n\n\tif !found {\n\t\treturn\n\t}\n\n\tif err != nil {\n\t\tlhh.l.WithError(err).WithField(\"vpnAddrs\", fromVpnAddrs).Error(\"Failed to marshal lighthouse host was queried for\")\n\t\treturn\n\t}\n\n\tlhh.lh.metricTx(NebulaMeta_HostPunchNotification, 1)\n\tw.SendMessageToVpnAddr(header.LightHouse, 0, punchNotifDest, lhh.pb[:ln], lhh.nb, lhh.out[:0])\n}\n\nfunc (lhh *LightHouseHandler) coalesceAnswers(v cert.Version, c *cache, n *NebulaMeta) {\n\tif c.v4 != nil {\n\t\tif c.v4.learned != nil {\n\t\t\tn.Details.V4AddrPorts = append(n.Details.V4AddrPorts, c.v4.learned)\n\t\t}\n\t\tif c.v4.reported != nil && len(c.v4.reported) > 0 {\n\t\t\tn.Details.V4AddrPorts = append(n.Details.V4AddrPorts, c.v4.reported...)\n\t\t}\n\t}\n\n\tif c.v6 != nil {\n\t\tif c.v6.learned != nil {\n\t\t\tn.Details.V6AddrPorts = append(n.Details.V6AddrPorts, c.v6.learned)\n\t\t}\n\t\tif c.v6.reported != nil && len(c.v6.reported) > 0 {\n\t\t\tn.Details.V6AddrPorts = append(n.Details.V6AddrPorts, c.v6.reported...)\n\t\t}\n\t}\n\n\tif c.relay != nil {\n\t\tif v == cert.Version1 {\n\t\t\tb := [4]byte{}\n\t\t\tfor _, r := range c.relay.relay {\n\t\t\t\tif !r.Is4() {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tb = r.As4()\n\t\t\t\tn.Details.OldRelayVpnAddrs = append(n.Details.OldRelayVpnAddrs, binary.BigEndian.Uint32(b[:]))\n\t\t\t}\n\t\t} else if v == cert.Version2 {\n\t\t\tfor _, r := range c.relay.relay {\n\t\t\t\tn.Details.RelayVpnAddrs = append(n.Details.RelayVpnAddrs, netAddrToProtoAddr(r))\n\t\t\t}\n\t\t} else {\n\t\t\tif lhh.l.Level >= logrus.DebugLevel {\n\t\t\t\tlhh.l.WithField(\"version\", v).Debug(\"unsupported protocol version\")\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (lhh *LightHouseHandler) handleHostQueryReply(n *NebulaMeta, fromVpnAddrs []netip.Addr) {\n\tif !lhh.lh.IsAnyLighthouseAddr(fromVpnAddrs) {\n\t\treturn\n\t}\n\n\tcertVpnAddr, _, err := n.Details.GetVpnAddrAndVersion()\n\tif err != nil {\n\t\tif lhh.l.Level >= logrus.DebugLevel {\n\t\t\tlhh.l.WithError(err).WithField(\"vpnAddrs\", fromVpnAddrs).Error(\"dropping malformed HostQueryReply\")\n\t\t}\n\t\treturn\n\t}\n\trelays := n.Details.GetRelays()\n\n\tlhh.lh.Lock()\n\tam := lhh.lh.unlockedGetRemoteList([]netip.Addr{certVpnAddr})\n\tam.Lock()\n\tlhh.lh.Unlock()\n\n\tam.unlockedSetV4(fromVpnAddrs[0], certVpnAddr, n.Details.V4AddrPorts, lhh.lh.unlockedShouldAddV4)\n\tam.unlockedSetV6(fromVpnAddrs[0], certVpnAddr, n.Details.V6AddrPorts, lhh.lh.unlockedShouldAddV6)\n\tam.unlockedSetRelay(fromVpnAddrs[0], relays)\n\tam.Unlock()\n\n\t// Non-blocking attempt to trigger, skip if it would block\n\tselect {\n\tcase lhh.lh.handshakeTrigger <- certVpnAddr:\n\tdefault:\n\t}\n}\n\nfunc (lhh *LightHouseHandler) handleHostUpdateNotification(n *NebulaMeta, fromVpnAddrs []netip.Addr, w EncWriter) {\n\tif !lhh.lh.amLighthouse {\n\t\tif lhh.l.Level >= logrus.DebugLevel {\n\t\t\tlhh.l.Debugln(\"I am not a lighthouse, do not take host updates: \", fromVpnAddrs)\n\t\t}\n\t\treturn\n\t}\n\n\t// not using GetVpnAddrAndVersion because we don't want to error on a blank detailsVpnAddr\n\tvar detailsVpnAddr netip.Addr\n\tvar useVersion cert.Version\n\tif n.Details.OldVpnAddr != 0 { //v1 always sets this field\n\t\tb := [4]byte{}\n\t\tbinary.BigEndian.PutUint32(b[:], n.Details.OldVpnAddr)\n\t\tdetailsVpnAddr = netip.AddrFrom4(b)\n\t\tuseVersion = cert.Version1\n\t} else if n.Details.VpnAddr != nil { //this field is \"optional\" in v2, but if it's set, we should enforce it\n\t\tdetailsVpnAddr = protoAddrToNetAddr(n.Details.VpnAddr)\n\t\tuseVersion = cert.Version2\n\t} else {\n\t\tdetailsVpnAddr = netip.Addr{}\n\t\tuseVersion = cert.Version2\n\t}\n\n\t//Simple check that the host sent this not someone else, if detailsVpnAddr is filled\n\tif detailsVpnAddr.IsValid() && !slices.Contains(fromVpnAddrs, detailsVpnAddr) {\n\t\tif lhh.l.Level >= logrus.DebugLevel {\n\t\t\tlhh.l.WithField(\"vpnAddrs\", fromVpnAddrs).WithField(\"answer\", detailsVpnAddr).Debugln(\"Host sent invalid update\")\n\t\t}\n\t\treturn\n\t}\n\n\trelays := n.Details.GetRelays()\n\n\tlhh.lh.Lock()\n\tam := lhh.lh.unlockedGetRemoteList(fromVpnAddrs)\n\tam.Lock()\n\tlhh.lh.Unlock()\n\n\tam.unlockedSetV4(fromVpnAddrs[0], fromVpnAddrs[0], n.Details.V4AddrPorts, lhh.lh.unlockedShouldAddV4)\n\tam.unlockedSetV6(fromVpnAddrs[0], fromVpnAddrs[0], n.Details.V6AddrPorts, lhh.lh.unlockedShouldAddV6)\n\tam.unlockedSetRelay(fromVpnAddrs[0], relays)\n\tam.Unlock()\n\n\tn = lhh.resetMeta()\n\tn.Type = NebulaMeta_HostUpdateNotificationAck\n\tswitch useVersion {\n\tcase cert.Version1:\n\t\tif !fromVpnAddrs[0].Is4() {\n\t\t\tlhh.l.WithField(\"vpnAddrs\", fromVpnAddrs).Error(\"Can not send HostUpdateNotificationAck for a ipv6 vpn ip in a v1 message\")\n\t\t\treturn\n\t\t}\n\t\tvpnAddrB := fromVpnAddrs[0].As4()\n\t\tn.Details.OldVpnAddr = binary.BigEndian.Uint32(vpnAddrB[:])\n\tcase cert.Version2:\n\t\t// do nothing, we want to send a blank message\n\tdefault:\n\t\tlhh.l.WithField(\"useVersion\", useVersion).Error(\"invalid protocol version\")\n\t\treturn\n\t}\n\n\tln, err := n.MarshalTo(lhh.pb)\n\tif err != nil {\n\t\tlhh.l.WithError(err).WithField(\"vpnAddrs\", fromVpnAddrs).Error(\"Failed to marshal lighthouse host update ack\")\n\t\treturn\n\t}\n\n\tlhh.lh.metricTx(NebulaMeta_HostUpdateNotificationAck, 1)\n\tw.SendMessageToVpnAddr(header.LightHouse, 0, fromVpnAddrs[0], lhh.pb[:ln], lhh.nb, lhh.out[:0])\n}\n\nfunc (lhh *LightHouseHandler) handleHostPunchNotification(n *NebulaMeta, fromVpnAddrs []netip.Addr, w EncWriter) {\n\t//It's possible the lighthouse is communicating with us using a non primary vpn addr,\n\t//which means we need to compare all fromVpnAddrs against all configured lighthouse vpn addrs.\n\tif !lhh.lh.IsAnyLighthouseAddr(fromVpnAddrs) {\n\t\treturn\n\t}\n\n\tdetailsVpnAddr, _, err := n.Details.GetVpnAddrAndVersion()\n\tif err != nil {\n\t\tif lhh.l.Level >= logrus.DebugLevel {\n\t\t\tlhh.l.WithField(\"details\", n.Details).WithError(err).Debugln(\"dropping invalid HostPunchNotification\")\n\t\t}\n\t\treturn\n\t}\n\n\tempty := []byte{0}\n\tpunch := func(vpnPeer netip.AddrPort, logVpnAddr netip.Addr) {\n\t\tif !vpnPeer.IsValid() {\n\t\t\treturn\n\t\t}\n\n\t\tgo func() {\n\t\t\ttime.Sleep(lhh.lh.punchy.GetDelay())\n\t\t\tlhh.lh.metricHolepunchTx.Inc(1)\n\t\t\tlhh.lh.punchConn.WriteTo(empty, vpnPeer)\n\t\t}()\n\n\t\tif lhh.l.Level >= logrus.DebugLevel {\n\t\t\tlhh.l.Debugf(\"Punching on %v for %v\", vpnPeer, logVpnAddr)\n\t\t}\n\t}\n\n\tremoteAllowList := lhh.lh.GetRemoteAllowList()\n\tfor _, a := range n.Details.V4AddrPorts {\n\t\tb := protoV4AddrPortToNetAddrPort(a)\n\t\tif remoteAllowList.Allow(detailsVpnAddr, b.Addr()) {\n\t\t\tpunch(b, detailsVpnAddr)\n\t\t}\n\t}\n\n\tfor _, a := range n.Details.V6AddrPorts {\n\t\tb := protoV6AddrPortToNetAddrPort(a)\n\t\tif remoteAllowList.Allow(detailsVpnAddr, b.Addr()) {\n\t\t\tpunch(b, detailsVpnAddr)\n\t\t}\n\t}\n\n\t// This sends a nebula test packet to the host trying to contact us. In the case\n\t// of a double nat or other difficult scenario, this may help establish\n\t// a tunnel.\n\tif lhh.lh.punchy.GetRespond() {\n\t\tgo func() {\n\t\t\ttime.Sleep(lhh.lh.punchy.GetRespondDelay())\n\t\t\tif lhh.l.Level >= logrus.DebugLevel {\n\t\t\t\tlhh.l.Debugf(\"Sending a nebula test packet to vpn addr %s\", detailsVpnAddr)\n\t\t\t}\n\t\t\t//NOTE: we have to allocate a new output buffer here since we are spawning a new goroutine\n\t\t\t// for each punchBack packet. We should move this into a timerwheel or a single goroutine\n\t\t\t// managed by a channel.\n\t\t\tw.SendMessageToVpnAddr(header.Test, header.TestRequest, detailsVpnAddr, []byte(\"\"), make([]byte, 12, 12), make([]byte, mtu))\n\t\t}()\n\t}\n}\n\nfunc protoAddrToNetAddr(addr *Addr) netip.Addr {\n\tb := [16]byte{}\n\tbinary.BigEndian.PutUint64(b[:8], addr.Hi)\n\tbinary.BigEndian.PutUint64(b[8:], addr.Lo)\n\treturn netip.AddrFrom16(b).Unmap()\n}\n\nfunc protoV4AddrPortToNetAddrPort(ap *V4AddrPort) netip.AddrPort {\n\tb := [4]byte{}\n\tbinary.BigEndian.PutUint32(b[:], ap.Addr)\n\treturn netip.AddrPortFrom(netip.AddrFrom4(b), uint16(ap.Port))\n}\n\nfunc protoV6AddrPortToNetAddrPort(ap *V6AddrPort) netip.AddrPort {\n\tb := [16]byte{}\n\tbinary.BigEndian.PutUint64(b[:8], ap.Hi)\n\tbinary.BigEndian.PutUint64(b[8:], ap.Lo)\n\treturn netip.AddrPortFrom(netip.AddrFrom16(b), uint16(ap.Port))\n}\n\nfunc netAddrToProtoAddr(addr netip.Addr) *Addr {\n\tb := addr.As16()\n\treturn &Addr{\n\t\tHi: binary.BigEndian.Uint64(b[:8]),\n\t\tLo: binary.BigEndian.Uint64(b[8:]),\n\t}\n}\n\nfunc netAddrToProtoV4AddrPort(addr netip.Addr, port uint16) *V4AddrPort {\n\tv4Addr := addr.As4()\n\treturn &V4AddrPort{\n\t\tAddr: binary.BigEndian.Uint32(v4Addr[:]),\n\t\tPort: uint32(port),\n\t}\n}\n\nfunc netAddrToProtoV6AddrPort(addr netip.Addr, port uint16) *V6AddrPort {\n\tv6Addr := addr.As16()\n\treturn &V6AddrPort{\n\t\tHi:   binary.BigEndian.Uint64(v6Addr[:8]),\n\t\tLo:   binary.BigEndian.Uint64(v6Addr[8:]),\n\t\tPort: uint32(port),\n\t}\n}\n\nfunc (d *NebulaMetaDetails) GetRelays() []netip.Addr {\n\tvar relays []netip.Addr\n\tif len(d.OldRelayVpnAddrs) > 0 {\n\t\tb := [4]byte{}\n\t\tfor _, r := range d.OldRelayVpnAddrs {\n\t\t\tbinary.BigEndian.PutUint32(b[:], r)\n\t\t\trelays = append(relays, netip.AddrFrom4(b))\n\t\t}\n\t}\n\n\tif len(d.RelayVpnAddrs) > 0 {\n\t\tfor _, r := range d.RelayVpnAddrs {\n\t\t\trelays = append(relays, protoAddrToNetAddr(r))\n\t\t}\n\t}\n\treturn relays\n}\n\n// FindNetworkUnion returns the first netip.Addr contained in the list of provided netip.Prefix, if able\nfunc findNetworkUnion(prefixes []netip.Prefix, addrs []netip.Addr) (netip.Addr, bool) {\n\tfor i := range prefixes {\n\t\tfor j := range addrs {\n\t\t\tif prefixes[i].Contains(addrs[j]) {\n\t\t\t\treturn addrs[j], true\n\t\t\t}\n\t\t}\n\t}\n\treturn netip.Addr{}, false\n}\n\nfunc (d *NebulaMetaDetails) GetVpnAddrAndVersion() (netip.Addr, cert.Version, error) {\n\tif d.OldVpnAddr != 0 {\n\t\tb := [4]byte{}\n\t\tbinary.BigEndian.PutUint32(b[:], d.OldVpnAddr)\n\t\tdetailsVpnAddr := netip.AddrFrom4(b)\n\t\treturn detailsVpnAddr, cert.Version1, nil\n\t} else if d.VpnAddr != nil {\n\t\tdetailsVpnAddr := protoAddrToNetAddr(d.VpnAddr)\n\t\treturn detailsVpnAddr, cert.Version2, nil\n\t} else {\n\t\treturn netip.Addr{}, cert.Version1, ErrBadDetailsVpnAddr\n\t}\n}\n"
  },
  {
    "path": "lighthouse_test.go",
    "content": "package nebula\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"testing\"\n\n\t\"github.com/gaissmai/bart\"\n\t\"github.com/slackhq/nebula/cert\"\n\t\"github.com/slackhq/nebula/config\"\n\t\"github.com/slackhq/nebula/header\"\n\t\"github.com/slackhq/nebula/test\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.yaml.in/yaml/v3\"\n)\n\nfunc TestOldIPv4Only(t *testing.T) {\n\t// This test ensures our new ipv6 enabled LH protobuf IpAndPorts works with the old style to enable backwards compatibility\n\tb := []byte{8, 129, 130, 132, 80, 16, 10}\n\tvar m V4AddrPort\n\terr := m.Unmarshal(b)\n\trequire.NoError(t, err)\n\tip := netip.MustParseAddr(\"10.1.1.1\")\n\tbp := ip.As4()\n\tassert.Equal(t, binary.BigEndian.Uint32(bp[:]), m.GetAddr())\n}\n\nfunc Test_lhStaticMapping(t *testing.T) {\n\tl := test.NewLogger()\n\tmyVpnNet := netip.MustParsePrefix(\"10.128.0.1/16\")\n\tnt := new(bart.Lite)\n\tnt.Insert(myVpnNet)\n\tcs := &CertState{\n\t\tmyVpnNetworks:      []netip.Prefix{myVpnNet},\n\t\tmyVpnNetworksTable: nt,\n\t}\n\tlh1 := \"10.128.0.2\"\n\n\tc := config.NewC(l)\n\tc.Settings[\"lighthouse\"] = map[string]any{\"hosts\": []any{lh1}}\n\tc.Settings[\"static_host_map\"] = map[string]any{lh1: []any{\"1.1.1.1:4242\"}}\n\t_, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)\n\trequire.NoError(t, err)\n\n\tlh2 := \"10.128.0.3\"\n\tc = config.NewC(l)\n\tc.Settings[\"lighthouse\"] = map[string]any{\"hosts\": []any{lh1, lh2}}\n\tc.Settings[\"static_host_map\"] = map[string]any{lh1: []any{\"100.1.1.1:4242\"}}\n\t_, err = NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)\n\trequire.EqualError(t, err, \"lighthouse 10.128.0.3 does not have a static_host_map entry\")\n}\n\nfunc TestReloadLighthouseInterval(t *testing.T) {\n\tl := test.NewLogger()\n\tmyVpnNet := netip.MustParsePrefix(\"10.128.0.1/16\")\n\tnt := new(bart.Lite)\n\tnt.Insert(myVpnNet)\n\tcs := &CertState{\n\t\tmyVpnNetworks:      []netip.Prefix{myVpnNet},\n\t\tmyVpnNetworksTable: nt,\n\t}\n\tlh1 := \"10.128.0.2\"\n\n\tc := config.NewC(l)\n\tc.Settings[\"lighthouse\"] = map[string]any{\n\t\t\"hosts\":    []any{lh1},\n\t\t\"interval\": \"1s\",\n\t}\n\n\tc.Settings[\"static_host_map\"] = map[string]any{lh1: []any{\"1.1.1.1:4242\"}}\n\tlh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)\n\trequire.NoError(t, err)\n\tlh.ifce = &mockEncWriter{}\n\n\t// The first one routine is kicked off by main.go currently, lets make sure that one dies\n\trequire.NoError(t, c.ReloadConfigString(\"lighthouse:\\n  interval: 5\"))\n\tassert.Equal(t, int64(5), lh.interval.Load())\n\n\t// Subsequent calls are killed off by the LightHouse.Reload function\n\trequire.NoError(t, c.ReloadConfigString(\"lighthouse:\\n  interval: 10\"))\n\tassert.Equal(t, int64(10), lh.interval.Load())\n\n\t// If this completes then nothing is stealing our reload routine\n\trequire.NoError(t, c.ReloadConfigString(\"lighthouse:\\n  interval: 11\"))\n\tassert.Equal(t, int64(11), lh.interval.Load())\n}\n\nfunc BenchmarkLighthouseHandleRequest(b *testing.B) {\n\tl := test.NewLogger()\n\tmyVpnNet := netip.MustParsePrefix(\"10.128.0.1/0\")\n\tnt := new(bart.Lite)\n\tnt.Insert(myVpnNet)\n\tcs := &CertState{\n\t\tmyVpnNetworks:      []netip.Prefix{myVpnNet},\n\t\tmyVpnNetworksTable: nt,\n\t}\n\n\tc := config.NewC(l)\n\tlh, err := NewLightHouseFromConfig(b.Context(), l, c, cs, nil, nil)\n\trequire.NoError(b, err)\n\n\thAddr := netip.MustParseAddrPort(\"4.5.6.7:12345\")\n\thAddr2 := netip.MustParseAddrPort(\"4.5.6.7:12346\")\n\n\tvpnIp3 := netip.MustParseAddr(\"0.0.0.3\")\n\tlh.addrMap[vpnIp3] = NewRemoteList([]netip.Addr{vpnIp3}, nil)\n\tlh.addrMap[vpnIp3].unlockedSetV4(\n\t\tvpnIp3,\n\t\tvpnIp3,\n\t\t[]*V4AddrPort{\n\t\t\tnetAddrToProtoV4AddrPort(hAddr.Addr(), hAddr.Port()),\n\t\t\tnetAddrToProtoV4AddrPort(hAddr2.Addr(), hAddr2.Port()),\n\t\t},\n\t\tfunc(netip.Addr, *V4AddrPort) bool { return true },\n\t)\n\n\trAddr := netip.MustParseAddrPort(\"1.2.2.3:12345\")\n\trAddr2 := netip.MustParseAddrPort(\"1.2.2.3:12346\")\n\tvpnIp2 := netip.MustParseAddr(\"0.0.0.3\")\n\tlh.addrMap[vpnIp2] = NewRemoteList([]netip.Addr{vpnIp2}, nil)\n\tlh.addrMap[vpnIp2].unlockedSetV4(\n\t\tvpnIp3,\n\t\tvpnIp3,\n\t\t[]*V4AddrPort{\n\t\t\tnetAddrToProtoV4AddrPort(rAddr.Addr(), rAddr.Port()),\n\t\t\tnetAddrToProtoV4AddrPort(rAddr2.Addr(), rAddr2.Port()),\n\t\t},\n\t\tfunc(netip.Addr, *V4AddrPort) bool { return true },\n\t)\n\n\tmw := &mockEncWriter{}\n\n\thi := []netip.Addr{vpnIp2}\n\tb.Run(\"notfound\", func(b *testing.B) {\n\t\tlhh := lh.NewRequestHandler()\n\t\treq := &NebulaMeta{\n\t\t\tType: NebulaMeta_HostQuery,\n\t\t\tDetails: &NebulaMetaDetails{\n\t\t\t\tOldVpnAddr:  4,\n\t\t\t\tV4AddrPorts: nil,\n\t\t\t},\n\t\t}\n\t\tp, err := req.Marshal()\n\t\trequire.NoError(b, err)\n\t\tfor n := 0; n < b.N; n++ {\n\t\t\tlhh.HandleRequest(rAddr, hi, p, mw)\n\t\t}\n\t})\n\tb.Run(\"found\", func(b *testing.B) {\n\t\tlhh := lh.NewRequestHandler()\n\t\treq := &NebulaMeta{\n\t\t\tType: NebulaMeta_HostQuery,\n\t\t\tDetails: &NebulaMetaDetails{\n\t\t\t\tOldVpnAddr:  3,\n\t\t\t\tV4AddrPorts: nil,\n\t\t\t},\n\t\t}\n\t\tp, err := req.Marshal()\n\t\trequire.NoError(b, err)\n\n\t\tfor n := 0; n < b.N; n++ {\n\t\t\tlhh.HandleRequest(rAddr, hi, p, mw)\n\t\t}\n\t})\n}\n\nfunc TestLighthouse_Memory(t *testing.T) {\n\tl := test.NewLogger()\n\n\tmyUdpAddr0 := netip.MustParseAddrPort(\"10.0.0.2:4242\")\n\tmyUdpAddr1 := netip.MustParseAddrPort(\"192.168.0.2:4242\")\n\tmyUdpAddr2 := netip.MustParseAddrPort(\"172.16.0.2:4242\")\n\tmyUdpAddr3 := netip.MustParseAddrPort(\"100.152.0.2:4242\")\n\tmyUdpAddr4 := netip.MustParseAddrPort(\"24.15.0.2:4242\")\n\tmyUdpAddr5 := netip.MustParseAddrPort(\"192.168.0.2:4243\")\n\tmyUdpAddr6 := netip.MustParseAddrPort(\"192.168.0.2:4244\")\n\tmyUdpAddr7 := netip.MustParseAddrPort(\"192.168.0.2:4245\")\n\tmyUdpAddr8 := netip.MustParseAddrPort(\"192.168.0.2:4246\")\n\tmyUdpAddr9 := netip.MustParseAddrPort(\"192.168.0.2:4247\")\n\tmyUdpAddr10 := netip.MustParseAddrPort(\"192.168.0.2:4248\")\n\tmyUdpAddr11 := netip.MustParseAddrPort(\"192.168.0.2:4249\")\n\tmyVpnIp := netip.MustParseAddr(\"10.128.0.2\")\n\n\ttheirUdpAddr0 := netip.MustParseAddrPort(\"10.0.0.3:4242\")\n\ttheirUdpAddr1 := netip.MustParseAddrPort(\"192.168.0.3:4242\")\n\ttheirUdpAddr2 := netip.MustParseAddrPort(\"172.16.0.3:4242\")\n\ttheirUdpAddr3 := netip.MustParseAddrPort(\"100.152.0.3:4242\")\n\ttheirUdpAddr4 := netip.MustParseAddrPort(\"24.15.0.3:4242\")\n\ttheirVpnIp := netip.MustParseAddr(\"10.128.0.3\")\n\n\tc := config.NewC(l)\n\tc.Settings[\"lighthouse\"] = map[string]any{\"am_lighthouse\": true}\n\tc.Settings[\"listen\"] = map[string]any{\"port\": 4242}\n\n\tmyVpnNet := netip.MustParsePrefix(\"10.128.0.1/24\")\n\tnt := new(bart.Lite)\n\tnt.Insert(myVpnNet)\n\tcs := &CertState{\n\t\tmyVpnNetworks:      []netip.Prefix{myVpnNet},\n\t\tmyVpnNetworksTable: nt,\n\t}\n\tlh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)\n\tlh.ifce = &mockEncWriter{}\n\trequire.NoError(t, err)\n\tlhh := lh.NewRequestHandler()\n\n\t// Test that my first update responds with just that\n\tnewLHHostUpdate(myUdpAddr0, myVpnIp, []netip.AddrPort{myUdpAddr1, myUdpAddr2}, lhh)\n\tr := newLHHostRequest(myUdpAddr0, myVpnIp, myVpnIp, lhh)\n\tassertIp4InArray(t, r.msg.Details.V4AddrPorts, myUdpAddr1, myUdpAddr2)\n\n\t// Ensure we don't accumulate addresses\n\tnewLHHostUpdate(myUdpAddr0, myVpnIp, []netip.AddrPort{myUdpAddr3}, lhh)\n\tr = newLHHostRequest(myUdpAddr0, myVpnIp, myVpnIp, lhh)\n\tassertIp4InArray(t, r.msg.Details.V4AddrPorts, myUdpAddr3)\n\n\t// Grow it back to 2\n\tnewLHHostUpdate(myUdpAddr0, myVpnIp, []netip.AddrPort{myUdpAddr1, myUdpAddr4}, lhh)\n\tr = newLHHostRequest(myUdpAddr0, myVpnIp, myVpnIp, lhh)\n\tassertIp4InArray(t, r.msg.Details.V4AddrPorts, myUdpAddr1, myUdpAddr4)\n\n\t// Update a different host and ask about it\n\tnewLHHostUpdate(theirUdpAddr0, theirVpnIp, []netip.AddrPort{theirUdpAddr1, theirUdpAddr2, theirUdpAddr3, theirUdpAddr4}, lhh)\n\tr = newLHHostRequest(theirUdpAddr0, theirVpnIp, theirVpnIp, lhh)\n\tassertIp4InArray(t, r.msg.Details.V4AddrPorts, theirUdpAddr1, theirUdpAddr2, theirUdpAddr3, theirUdpAddr4)\n\n\t// Have both hosts ask about the other\n\tr = newLHHostRequest(theirUdpAddr0, theirVpnIp, myVpnIp, lhh)\n\tassertIp4InArray(t, r.msg.Details.V4AddrPorts, myUdpAddr1, myUdpAddr4)\n\n\tr = newLHHostRequest(myUdpAddr0, myVpnIp, theirVpnIp, lhh)\n\tassertIp4InArray(t, r.msg.Details.V4AddrPorts, theirUdpAddr1, theirUdpAddr2, theirUdpAddr3, theirUdpAddr4)\n\n\t// Make sure we didn't get changed\n\tr = newLHHostRequest(myUdpAddr0, myVpnIp, myVpnIp, lhh)\n\tassertIp4InArray(t, r.msg.Details.V4AddrPorts, myUdpAddr1, myUdpAddr4)\n\n\t// Ensure proper ordering and limiting\n\t// Send 12 addrs, get 10 back, the last 2 removed, allowing the duplicate to remain (clients dedupe)\n\tnewLHHostUpdate(\n\t\tmyUdpAddr0,\n\t\tmyVpnIp,\n\t\t[]netip.AddrPort{\n\t\t\tmyUdpAddr1,\n\t\t\tmyUdpAddr2,\n\t\t\tmyUdpAddr3,\n\t\t\tmyUdpAddr4,\n\t\t\tmyUdpAddr5,\n\t\t\tmyUdpAddr5, //Duplicated on purpose\n\t\t\tmyUdpAddr6,\n\t\t\tmyUdpAddr7,\n\t\t\tmyUdpAddr8,\n\t\t\tmyUdpAddr9,\n\t\t\tmyUdpAddr10,\n\t\t\tmyUdpAddr11, // This should get cut\n\t\t}, lhh)\n\n\tr = newLHHostRequest(myUdpAddr0, myVpnIp, myVpnIp, lhh)\n\tassertIp4InArray(\n\t\tt,\n\t\tr.msg.Details.V4AddrPorts,\n\t\tmyUdpAddr1, myUdpAddr2, myUdpAddr3, myUdpAddr4, myUdpAddr5, myUdpAddr5, myUdpAddr6, myUdpAddr7, myUdpAddr8, myUdpAddr9,\n\t)\n\n\t// Make sure we won't add ips in our vpn network\n\tbad1 := netip.MustParseAddrPort(\"10.128.0.99:4242\")\n\tbad2 := netip.MustParseAddrPort(\"10.128.0.100:4242\")\n\tgood := netip.MustParseAddrPort(\"1.128.0.99:4242\")\n\tnewLHHostUpdate(myUdpAddr0, myVpnIp, []netip.AddrPort{bad1, bad2, good}, lhh)\n\tr = newLHHostRequest(myUdpAddr0, myVpnIp, myVpnIp, lhh)\n\tassertIp4InArray(t, r.msg.Details.V4AddrPorts, good)\n}\n\nfunc TestLighthouse_reload(t *testing.T) {\n\tl := test.NewLogger()\n\tc := config.NewC(l)\n\tc.Settings[\"lighthouse\"] = map[string]any{\"am_lighthouse\": true}\n\tc.Settings[\"listen\"] = map[string]any{\"port\": 4242}\n\n\tmyVpnNet := netip.MustParsePrefix(\"10.128.0.1/24\")\n\tnt := new(bart.Lite)\n\tnt.Insert(myVpnNet)\n\tcs := &CertState{\n\t\tmyVpnNetworks:      []netip.Prefix{myVpnNet},\n\t\tmyVpnNetworksTable: nt,\n\t}\n\n\tlh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)\n\trequire.NoError(t, err)\n\n\tnc := map[string]any{\n\t\t\"static_host_map\": map[string]any{\n\t\t\t\"10.128.0.2\": []any{\"1.1.1.1:4242\"},\n\t\t},\n\t}\n\trc, err := yaml.Marshal(nc)\n\trequire.NoError(t, err)\n\tc.ReloadConfigString(string(rc))\n\n\terr = lh.reload(c, false)\n\trequire.NoError(t, err)\n}\n\nfunc newLHHostRequest(fromAddr netip.AddrPort, myVpnIp, queryVpnIp netip.Addr, lhh *LightHouseHandler) testLhReply {\n\treq := &NebulaMeta{\n\t\tType:    NebulaMeta_HostQuery,\n\t\tDetails: &NebulaMetaDetails{},\n\t}\n\n\tif queryVpnIp.Is4() {\n\t\tbip := queryVpnIp.As4()\n\t\treq.Details.OldVpnAddr = binary.BigEndian.Uint32(bip[:])\n\t} else {\n\t\treq.Details.VpnAddr = netAddrToProtoAddr(queryVpnIp)\n\t}\n\n\tb, err := req.Marshal()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tfilter := NebulaMeta_HostQueryReply\n\tw := &testEncWriter{\n\t\tmetaFilter: &filter,\n\t}\n\tlhh.HandleRequest(fromAddr, []netip.Addr{myVpnIp}, b, w)\n\treturn w.lastReply\n}\n\nfunc newLHHostUpdate(fromAddr netip.AddrPort, vpnIp netip.Addr, addrs []netip.AddrPort, lhh *LightHouseHandler) {\n\treq := &NebulaMeta{\n\t\tType:    NebulaMeta_HostUpdateNotification,\n\t\tDetails: &NebulaMetaDetails{},\n\t}\n\n\tif vpnIp.Is4() {\n\t\tbip := vpnIp.As4()\n\t\treq.Details.OldVpnAddr = binary.BigEndian.Uint32(bip[:])\n\t} else {\n\t\treq.Details.VpnAddr = netAddrToProtoAddr(vpnIp)\n\t}\n\n\tfor _, v := range addrs {\n\t\tif v.Addr().Is4() {\n\t\t\treq.Details.V4AddrPorts = append(req.Details.V4AddrPorts, netAddrToProtoV4AddrPort(v.Addr(), v.Port()))\n\t\t} else {\n\t\t\treq.Details.V6AddrPorts = append(req.Details.V6AddrPorts, netAddrToProtoV6AddrPort(v.Addr(), v.Port()))\n\t\t}\n\t}\n\n\tb, err := req.Marshal()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tw := &testEncWriter{}\n\tlhh.HandleRequest(fromAddr, []netip.Addr{vpnIp}, b, w)\n}\n\ntype testLhReply struct {\n\tnebType    header.MessageType\n\tnebSubType header.MessageSubType\n\tvpnIp      netip.Addr\n\tmsg        *NebulaMeta\n}\n\ntype testEncWriter struct {\n\tlastReply       testLhReply\n\tmetaFilter      *NebulaMeta_MessageType\n\tprotocolVersion cert.Version\n}\n\nfunc (tw *testEncWriter) SendVia(via *HostInfo, relay *Relay, ad, nb, out []byte, nocopy bool) {\n}\nfunc (tw *testEncWriter) Handshake(vpnIp netip.Addr) {\n}\n\nfunc (tw *testEncWriter) SendMessageToHostInfo(t header.MessageType, st header.MessageSubType, hostinfo *HostInfo, p, _, _ []byte) {\n\tmsg := &NebulaMeta{}\n\terr := msg.Unmarshal(p)\n\tif tw.metaFilter == nil || msg.Type == *tw.metaFilter {\n\t\ttw.lastReply = testLhReply{\n\t\t\tnebType:    t,\n\t\t\tnebSubType: st,\n\t\t\tvpnIp:      hostinfo.vpnAddrs[0],\n\t\t\tmsg:        msg,\n\t\t}\n\t}\n\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc (tw *testEncWriter) SendMessageToVpnAddr(t header.MessageType, st header.MessageSubType, vpnIp netip.Addr, p, _, _ []byte) {\n\tmsg := &NebulaMeta{}\n\terr := msg.Unmarshal(p)\n\tif tw.metaFilter == nil || msg.Type == *tw.metaFilter {\n\t\ttw.lastReply = testLhReply{\n\t\t\tnebType:    t,\n\t\t\tnebSubType: st,\n\t\t\tvpnIp:      vpnIp,\n\t\t\tmsg:        msg,\n\t\t}\n\t}\n\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc (tw *testEncWriter) GetHostInfo(vpnIp netip.Addr) *HostInfo {\n\treturn nil\n}\n\nfunc (tw *testEncWriter) GetCertState() *CertState {\n\treturn &CertState{initiatingVersion: tw.protocolVersion}\n}\n\n// assertIp4InArray asserts every address in want is at the same position in have and that the lengths match\nfunc assertIp4InArray(t *testing.T, have []*V4AddrPort, want ...netip.AddrPort) {\n\tif !assert.Len(t, have, len(want)) {\n\t\treturn\n\t}\n\n\tfor k, w := range want {\n\t\th := protoV4AddrPortToNetAddrPort(have[k])\n\t\tif !(h == w) {\n\t\t\tassert.Fail(t, fmt.Sprintf(\"Response did not contain: %v at %v, found %v\", w, k, h))\n\t\t}\n\t}\n}\n\nfunc Test_findNetworkUnion(t *testing.T) {\n\tvar out netip.Addr\n\tvar ok bool\n\n\ttenDot := netip.MustParsePrefix(\"10.0.0.0/8\")\n\toneSevenTwo := netip.MustParsePrefix(\"172.16.0.0/16\")\n\tfe80 := netip.MustParsePrefix(\"fe80::/8\")\n\tfc00 := netip.MustParsePrefix(\"fc00::/7\")\n\n\ta1 := netip.MustParseAddr(\"10.0.0.1\")\n\tafe81 := netip.MustParseAddr(\"fe80::1\")\n\n\t//simple\n\tout, ok = findNetworkUnion([]netip.Prefix{tenDot}, []netip.Addr{a1})\n\tassert.True(t, ok)\n\tassert.Equal(t, out, a1)\n\n\t//mixed lengths\n\tout, ok = findNetworkUnion([]netip.Prefix{tenDot}, []netip.Addr{a1, afe81})\n\tassert.True(t, ok)\n\tassert.Equal(t, out, a1)\n\tout, ok = findNetworkUnion([]netip.Prefix{tenDot, oneSevenTwo}, []netip.Addr{a1})\n\tassert.True(t, ok)\n\tassert.Equal(t, out, a1)\n\n\t//mixed family\n\tout, ok = findNetworkUnion([]netip.Prefix{tenDot, oneSevenTwo, fe80}, []netip.Addr{a1})\n\tassert.True(t, ok)\n\tassert.Equal(t, out, a1)\n\tout, ok = findNetworkUnion([]netip.Prefix{tenDot, oneSevenTwo, fe80}, []netip.Addr{a1, afe81})\n\tassert.True(t, ok)\n\tassert.Equal(t, out, a1)\n\n\t//ordering\n\tout, ok = findNetworkUnion([]netip.Prefix{tenDot, oneSevenTwo, fe80}, []netip.Addr{afe81, a1})\n\tassert.True(t, ok)\n\tassert.Equal(t, out, a1)\n\tout, ok = findNetworkUnion([]netip.Prefix{fe80, tenDot, oneSevenTwo}, []netip.Addr{afe81, a1})\n\tassert.True(t, ok)\n\tassert.Equal(t, out, afe81)\n\n\t//some mismatches\n\tout, ok = findNetworkUnion([]netip.Prefix{tenDot, oneSevenTwo, fe80}, []netip.Addr{afe81})\n\tassert.True(t, ok)\n\tassert.Equal(t, out, afe81)\n\tout, ok = findNetworkUnion([]netip.Prefix{oneSevenTwo, fe80}, []netip.Addr{a1, afe81})\n\tassert.True(t, ok)\n\tassert.Equal(t, out, afe81)\n\n\t//falsey cases\n\tout, ok = findNetworkUnion([]netip.Prefix{oneSevenTwo, fe80}, []netip.Addr{a1})\n\tassert.False(t, ok)\n\tout, ok = findNetworkUnion([]netip.Prefix{fc00, fe80}, []netip.Addr{a1})\n\tassert.False(t, ok)\n\tout, ok = findNetworkUnion([]netip.Prefix{oneSevenTwo, fc00}, []netip.Addr{a1, afe81})\n\tassert.False(t, ok)\n\tout, ok = findNetworkUnion([]netip.Prefix{fc00}, []netip.Addr{a1, afe81})\n\tassert.False(t, ok)\n}\n\nfunc TestLighthouse_Dont_Delete_Static_Hosts(t *testing.T) {\n\tl := test.NewLogger()\n\n\tmyUdpAddr2 := netip.MustParseAddrPort(\"1.2.3.4:4242\")\n\n\ttestSameHostNotStatic := netip.MustParseAddr(\"10.128.0.41\")\n\ttestStaticHost := netip.MustParseAddr(\"10.128.0.42\")\n\t//myVpnIp := netip.MustParseAddr(\"10.128.0.2\")\n\n\tc := config.NewC(l)\n\tlh1 := \"10.128.0.2\"\n\tc.Settings[\"lighthouse\"] = map[string]any{\n\t\t\"hosts\":    []any{lh1},\n\t\t\"interval\": \"1s\",\n\t}\n\n\tc.Settings[\"listen\"] = map[string]any{\"port\": 4242}\n\tc.Settings[\"static_host_map\"] = map[string]any{\n\t\tlh1:           []any{\"1.1.1.1:4242\"},\n\t\t\"10.128.0.42\": []any{\"1.2.3.4:4242\"},\n\t}\n\n\tmyVpnNet := netip.MustParsePrefix(\"10.128.0.1/24\")\n\tnt := new(bart.Lite)\n\tnt.Insert(myVpnNet)\n\tcs := &CertState{\n\t\tmyVpnNetworks:      []netip.Prefix{myVpnNet},\n\t\tmyVpnNetworksTable: nt,\n\t}\n\tlh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)\n\trequire.NoError(t, err)\n\tlh.ifce = &mockEncWriter{}\n\n\t//test that we actually have the static entry:\n\tout := lh.Query(testStaticHost)\n\tassert.NotNil(t, out)\n\tassert.Equal(t, out.vpnAddrs[0], testStaticHost)\n\tout.Rebuild([]netip.Prefix{}) //why tho\n\tassert.Equal(t, out.addrs[0], myUdpAddr2)\n\n\t//bolt on a lower numbered primary IP\n\tam := lh.unlockedGetRemoteList([]netip.Addr{testStaticHost})\n\tam.vpnAddrs = []netip.Addr{testSameHostNotStatic, testStaticHost}\n\tlh.addrMap[testSameHostNotStatic] = am\n\tout.Rebuild([]netip.Prefix{}) //???\n\n\t//test that we actually have the static entry:\n\tout = lh.Query(testStaticHost)\n\tassert.NotNil(t, out)\n\tassert.Equal(t, out.vpnAddrs[0], testSameHostNotStatic)\n\tassert.Equal(t, out.vpnAddrs[1], testStaticHost)\n\tassert.Equal(t, out.addrs[0], myUdpAddr2)\n\n\t//test that we actually have the static entry for BOTH:\n\tout2 := lh.Query(testSameHostNotStatic)\n\tassert.Same(t, out2, out)\n\n\t//now do the delete\n\tlh.DeleteVpnAddrs([]netip.Addr{testSameHostNotStatic, testStaticHost})\n\t//verify\n\tout = lh.Query(testSameHostNotStatic)\n\tassert.NotNil(t, out)\n\tif out == nil {\n\t\tt.Fatal(\"expected non-nil query for the static host\")\n\t}\n\tassert.Equal(t, out.vpnAddrs[0], testSameHostNotStatic)\n\tassert.Equal(t, out.vpnAddrs[1], testStaticHost)\n\tassert.Equal(t, out.addrs[0], myUdpAddr2)\n}\n\nfunc TestLighthouse_DeletesWork(t *testing.T) {\n\tl := test.NewLogger()\n\n\tmyUdpAddr2 := netip.MustParseAddrPort(\"1.2.3.4:4242\")\n\ttestHost := netip.MustParseAddr(\"10.128.0.42\")\n\n\tc := config.NewC(l)\n\tlh1 := \"10.128.0.2\"\n\tc.Settings[\"lighthouse\"] = map[string]any{\n\t\t\"hosts\":    []any{lh1},\n\t\t\"interval\": \"1s\",\n\t}\n\n\tc.Settings[\"listen\"] = map[string]any{\"port\": 4242}\n\tc.Settings[\"static_host_map\"] = map[string]any{\n\t\tlh1: []any{\"1.1.1.1:4242\"},\n\t}\n\n\tmyVpnNet := netip.MustParsePrefix(\"10.128.0.1/24\")\n\tnt := new(bart.Lite)\n\tnt.Insert(myVpnNet)\n\tcs := &CertState{\n\t\tmyVpnNetworks:      []netip.Prefix{myVpnNet},\n\t\tmyVpnNetworksTable: nt,\n\t}\n\tlh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)\n\trequire.NoError(t, err)\n\tlh.ifce = &mockEncWriter{}\n\n\t//insert the host\n\tam := lh.unlockedGetRemoteList([]netip.Addr{testHost})\n\tam.vpnAddrs = []netip.Addr{testHost}\n\tam.addrs = []netip.AddrPort{myUdpAddr2}\n\tlh.addrMap[testHost] = am\n\tam.Rebuild([]netip.Prefix{}) //???\n\n\t//test that we actually have the entry:\n\tout := lh.Query(testHost)\n\tassert.NotNil(t, out)\n\tassert.Equal(t, out.vpnAddrs[0], testHost)\n\tout.Rebuild([]netip.Prefix{}) //why tho\n\tassert.Equal(t, out.addrs[0], myUdpAddr2)\n\n\t//now do the delete\n\tlh.DeleteVpnAddrs([]netip.Addr{testHost})\n\t//verify\n\tout = lh.Query(testHost)\n\tassert.Nil(t, out)\n}\n"
  },
  {
    "path": "logger.go",
    "content": "package nebula\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/config\"\n)\n\nfunc configLogger(l *logrus.Logger, c *config.C) error {\n\t// set up our logging level\n\tlogLevel, err := logrus.ParseLevel(strings.ToLower(c.GetString(\"logging.level\", \"info\")))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"%s; possible levels: %s\", err, logrus.AllLevels)\n\t}\n\tl.SetLevel(logLevel)\n\n\tdisableTimestamp := c.GetBool(\"logging.disable_timestamp\", false)\n\ttimestampFormat := c.GetString(\"logging.timestamp_format\", \"\")\n\tfullTimestamp := (timestampFormat != \"\")\n\tif timestampFormat == \"\" {\n\t\ttimestampFormat = time.RFC3339\n\t}\n\n\tlogFormat := strings.ToLower(c.GetString(\"logging.format\", \"text\"))\n\tswitch logFormat {\n\tcase \"text\":\n\t\tl.Formatter = &logrus.TextFormatter{\n\t\t\tTimestampFormat:  timestampFormat,\n\t\t\tFullTimestamp:    fullTimestamp,\n\t\t\tDisableTimestamp: disableTimestamp,\n\t\t}\n\tcase \"json\":\n\t\tl.Formatter = &logrus.JSONFormatter{\n\t\t\tTimestampFormat:  timestampFormat,\n\t\t\tDisableTimestamp: disableTimestamp,\n\t\t}\n\tdefault:\n\t\treturn fmt.Errorf(\"unknown log format `%s`. possible formats: %s\", logFormat, []string{\"text\", \"json\"})\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "main.go",
    "content": "package nebula\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"runtime/debug\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/config\"\n\t\"github.com/slackhq/nebula/overlay\"\n\t\"github.com/slackhq/nebula/sshd\"\n\t\"github.com/slackhq/nebula/udp\"\n\t\"github.com/slackhq/nebula/util\"\n\t\"go.yaml.in/yaml/v3\"\n)\n\ntype m = map[string]any\n\nfunc Main(c *config.C, configTest bool, buildVersion string, logger *logrus.Logger, deviceFactory overlay.DeviceFactory) (retcon *Control, reterr error) {\n\tctx, cancel := context.WithCancel(context.Background())\n\t// Automatically cancel the context if Main returns an error, to signal all created goroutines to quit.\n\tdefer func() {\n\t\tif reterr != nil {\n\t\t\tcancel()\n\t\t}\n\t}()\n\n\tif buildVersion == \"\" {\n\t\tbuildVersion = moduleVersion()\n\t}\n\n\tl := logger\n\tl.Formatter = &logrus.TextFormatter{\n\t\tFullTimestamp: true,\n\t}\n\n\t// Print the config if in test, the exit comes later\n\tif configTest {\n\t\tb, err := yaml.Marshal(c.Settings)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// Print the final config\n\t\tl.Println(string(b))\n\t}\n\n\terr := configLogger(l, c)\n\tif err != nil {\n\t\treturn nil, util.ContextualizeIfNeeded(\"Failed to configure the logger\", err)\n\t}\n\n\tc.RegisterReloadCallback(func(c *config.C) {\n\t\terr := configLogger(l, c)\n\t\tif err != nil {\n\t\t\tl.WithError(err).Error(\"Failed to configure the logger\")\n\t\t}\n\t})\n\n\tpki, err := NewPKIFromConfig(l, c)\n\tif err != nil {\n\t\treturn nil, util.ContextualizeIfNeeded(\"Failed to load PKI from config\", err)\n\t}\n\n\tfw, err := NewFirewallFromConfig(l, pki.getCertState(), c)\n\tif err != nil {\n\t\treturn nil, util.ContextualizeIfNeeded(\"Error while loading firewall rules\", err)\n\t}\n\tl.WithField(\"firewallHashes\", fw.GetRuleHashes()).Info(\"Firewall started\")\n\n\tssh, err := sshd.NewSSHServer(l.WithField(\"subsystem\", \"sshd\"))\n\tif err != nil {\n\t\treturn nil, util.ContextualizeIfNeeded(\"Error while creating SSH server\", err)\n\t}\n\twireSSHReload(l, ssh, c)\n\tvar sshStart func()\n\tif c.GetBool(\"sshd.enabled\", false) {\n\t\tsshStart, err = configSSH(l, ssh, c)\n\t\tif err != nil {\n\t\t\tl.WithError(err).Warn(\"Failed to configure sshd, ssh debugging will not be available\")\n\t\t\tsshStart = nil\n\t\t}\n\t}\n\n\t////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\t// All non system modifying configuration consumption should live above this line\n\t// tun config, listeners, anything modifying the computer should be below\n\t////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\n\tvar routines int\n\n\t// If `routines` is set, use that and ignore the specific values\n\tif routines = c.GetInt(\"routines\", 0); routines != 0 {\n\t\tif routines < 1 {\n\t\t\troutines = 1\n\t\t}\n\t\tif routines > 1 {\n\t\t\tl.WithField(\"routines\", routines).Info(\"Using multiple routines\")\n\t\t}\n\t} else {\n\t\t// deprecated and undocumented\n\t\ttunQueues := c.GetInt(\"tun.routines\", 1)\n\t\tudpQueues := c.GetInt(\"listen.routines\", 1)\n\t\troutines = max(tunQueues, udpQueues)\n\t\tif routines != 1 {\n\t\t\tl.WithField(\"routines\", routines).Warn(\"Setting tun.routines and listen.routines is deprecated. Use `routines` instead\")\n\t\t}\n\t}\n\n\t// EXPERIMENTAL\n\t// Intentionally not documented yet while we do more testing and determine\n\t// a good default value.\n\tconntrackCacheTimeout := c.GetDuration(\"firewall.conntrack.routine_cache_timeout\", 0)\n\tif routines > 1 && !c.IsSet(\"firewall.conntrack.routine_cache_timeout\") {\n\t\t// Use a different default if we are running with multiple routines\n\t\tconntrackCacheTimeout = 1 * time.Second\n\t}\n\tif conntrackCacheTimeout > 0 {\n\t\tl.WithField(\"duration\", conntrackCacheTimeout).Info(\"Using routine-local conntrack cache\")\n\t}\n\n\tvar tun overlay.Device\n\tif !configTest {\n\t\tc.CatchHUP(ctx)\n\n\t\tif deviceFactory == nil {\n\t\t\tdeviceFactory = overlay.NewDeviceFromConfig\n\t\t}\n\n\t\ttun, err = deviceFactory(c, l, pki.getCertState().myVpnNetworks, routines)\n\t\tif err != nil {\n\t\t\treturn nil, util.ContextualizeIfNeeded(\"Failed to get a tun/tap device\", err)\n\t\t}\n\n\t\tdefer func() {\n\t\t\tif reterr != nil {\n\t\t\t\ttun.Close()\n\t\t\t}\n\t\t}()\n\t}\n\n\t// set up our UDP listener\n\tudpConns := make([]udp.Conn, routines)\n\tport := c.GetInt(\"listen.port\", 0)\n\n\tif !configTest {\n\t\trawListenHost := c.GetString(\"listen.host\", \"0.0.0.0\")\n\t\tvar listenHost netip.Addr\n\t\tif rawListenHost == \"[::]\" {\n\t\t\t// Old guidance was to provide the literal `[::]` in `listen.host` but that won't resolve.\n\t\t\tlistenHost = netip.IPv6Unspecified()\n\n\t\t} else {\n\t\t\tips, err := net.DefaultResolver.LookupNetIP(context.Background(), \"ip\", rawListenHost)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, util.ContextualizeIfNeeded(\"Failed to resolve listen.host\", err)\n\t\t\t}\n\t\t\tif len(ips) == 0 {\n\t\t\t\treturn nil, util.ContextualizeIfNeeded(\"Failed to resolve listen.host\", err)\n\t\t\t}\n\t\t\tlistenHost = ips[0].Unmap()\n\t\t}\n\n\t\tfor i := 0; i < routines; i++ {\n\t\t\tl.Infof(\"listening on %v\", netip.AddrPortFrom(listenHost, uint16(port)))\n\t\t\tudpServer, err := udp.NewListener(l, listenHost, port, routines > 1, c.GetInt(\"listen.batch\", 64))\n\t\t\tif err != nil {\n\t\t\t\treturn nil, util.NewContextualError(\"Failed to open udp listener\", m{\"queue\": i}, err)\n\t\t\t}\n\t\t\tudpServer.ReloadConfig(c)\n\t\t\tudpConns[i] = udpServer\n\n\t\t\t// If port is dynamic, discover it before the next pass through the for loop\n\t\t\t// This way all routines will use the same port correctly\n\t\t\tif port == 0 {\n\t\t\t\tuPort, err := udpServer.LocalAddr()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, util.NewContextualError(\"Failed to get listening port\", nil, err)\n\t\t\t\t}\n\t\t\t\tport = int(uPort.Port())\n\t\t\t}\n\t\t}\n\t}\n\n\thostMap := NewHostMapFromConfig(l, c)\n\tpunchy := NewPunchyFromConfig(l, c)\n\tconnManager := newConnectionManagerFromConfig(l, c, hostMap, punchy)\n\tlightHouse, err := NewLightHouseFromConfig(ctx, l, c, pki.getCertState(), udpConns[0], punchy)\n\tif err != nil {\n\t\treturn nil, util.ContextualizeIfNeeded(\"Failed to initialize lighthouse handler\", err)\n\t}\n\n\tvar messageMetrics *MessageMetrics\n\tif c.GetBool(\"stats.message_metrics\", false) {\n\t\tmessageMetrics = newMessageMetrics()\n\t} else {\n\t\tmessageMetrics = newMessageMetricsOnlyRecvError()\n\t}\n\n\tuseRelays := c.GetBool(\"relay.use_relays\", DefaultUseRelays) && !c.GetBool(\"relay.am_relay\", false)\n\n\thandshakeConfig := HandshakeConfig{\n\t\ttryInterval:   c.GetDuration(\"handshakes.try_interval\", DefaultHandshakeTryInterval),\n\t\tretries:       int64(c.GetInt(\"handshakes.retries\", DefaultHandshakeRetries)),\n\t\ttriggerBuffer: c.GetInt(\"handshakes.trigger_buffer\", DefaultHandshakeTriggerBuffer),\n\t\tuseRelays:     useRelays,\n\n\t\tmessageMetrics: messageMetrics,\n\t}\n\n\thandshakeManager := NewHandshakeManager(l, hostMap, lightHouse, udpConns[0], handshakeConfig)\n\tlightHouse.handshakeTrigger = handshakeManager.trigger\n\n\tserveDns := false\n\tif c.GetBool(\"lighthouse.serve_dns\", false) {\n\t\tif c.GetBool(\"lighthouse.am_lighthouse\", false) {\n\t\t\tserveDns = true\n\t\t} else {\n\t\t\tl.Warn(\"DNS server refusing to run because this host is not a lighthouse.\")\n\t\t}\n\t}\n\n\tifConfig := &InterfaceConfig{\n\t\tHostMap:               hostMap,\n\t\tInside:                tun,\n\t\tOutside:               udpConns[0],\n\t\tpki:                   pki,\n\t\tFirewall:              fw,\n\t\tServeDns:              serveDns,\n\t\tHandshakeManager:      handshakeManager,\n\t\tconnectionManager:     connManager,\n\t\tlightHouse:            lightHouse,\n\t\ttryPromoteEvery:       c.GetUint32(\"counters.try_promote\", defaultPromoteEvery),\n\t\treQueryEvery:          c.GetUint32(\"counters.requery_every_packets\", defaultReQueryEvery),\n\t\treQueryWait:           c.GetDuration(\"timers.requery_wait_duration\", defaultReQueryWait),\n\t\tDropLocalBroadcast:    c.GetBool(\"tun.drop_local_broadcast\", false),\n\t\tDropMulticast:         c.GetBool(\"tun.drop_multicast\", false),\n\t\troutines:              routines,\n\t\tMessageMetrics:        messageMetrics,\n\t\tversion:               buildVersion,\n\t\trelayManager:          NewRelayManager(ctx, l, hostMap, c),\n\t\tpunchy:                punchy,\n\t\tConntrackCacheTimeout: conntrackCacheTimeout,\n\t\tl:                     l,\n\t}\n\n\tvar ifce *Interface\n\tif !configTest {\n\t\tifce, err = NewInterface(ctx, ifConfig)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to initialize interface: %s\", err)\n\t\t}\n\n\t\tifce.writers = udpConns\n\t\tlightHouse.ifce = ifce\n\n\t\tifce.RegisterConfigChangeCallbacks(c)\n\t\tifce.reloadDisconnectInvalid(c)\n\t\tifce.reloadSendRecvError(c)\n\t\tifce.reloadAcceptRecvError(c)\n\n\t\thandshakeManager.f = ifce\n\t\tgo handshakeManager.Run(ctx)\n\t}\n\n\tstatsStart, err := startStats(l, c, buildVersion, configTest)\n\tif err != nil {\n\t\treturn nil, util.ContextualizeIfNeeded(\"Failed to start stats emitter\", err)\n\t}\n\n\tif configTest {\n\t\treturn nil, nil\n\t}\n\n\tgo ifce.emitStats(ctx, c.GetDuration(\"stats.interval\", time.Second*10))\n\n\tattachCommands(l, c, ssh, ifce)\n\n\t// Start DNS server last to allow using the nebula IP as lighthouse.dns.host\n\tvar dnsStart func()\n\tif lightHouse.amLighthouse && serveDns {\n\t\tl.Debugln(\"Starting dns server\")\n\t\tdnsStart = dnsMain(l, pki.getCertState(), hostMap, c)\n\t}\n\n\treturn &Control{\n\t\tifce,\n\t\tl,\n\t\tctx,\n\t\tcancel,\n\t\tsshStart,\n\t\tstatsStart,\n\t\tdnsStart,\n\t\tlightHouse.StartUpdateWorker,\n\t\tconnManager.Start,\n\t}, nil\n}\n\nfunc moduleVersion() string {\n\tinfo, ok := debug.ReadBuildInfo()\n\tif !ok {\n\t\treturn \"\"\n\t}\n\n\tfor _, dep := range info.Deps {\n\t\tif dep.Path == \"github.com/slackhq/nebula\" {\n\t\t\treturn strings.TrimPrefix(dep.Version, \"v\")\n\t\t}\n\t}\n\n\treturn \"\"\n}\n"
  },
  {
    "path": "message_metrics.go",
    "content": "package nebula\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/rcrowley/go-metrics\"\n\t\"github.com/slackhq/nebula/header\"\n)\n\ntype MessageMetrics struct {\n\trx [][]metrics.Counter\n\ttx [][]metrics.Counter\n\n\trxUnknown metrics.Counter\n\ttxUnknown metrics.Counter\n}\n\nfunc (m *MessageMetrics) Rx(t header.MessageType, s header.MessageSubType, i int64) {\n\tif m != nil {\n\t\tif t >= 0 && int(t) < len(m.rx) && s >= 0 && int(s) < len(m.rx[t]) {\n\t\t\tm.rx[t][s].Inc(i)\n\t\t} else if m.rxUnknown != nil {\n\t\t\tm.rxUnknown.Inc(i)\n\t\t}\n\t}\n}\nfunc (m *MessageMetrics) Tx(t header.MessageType, s header.MessageSubType, i int64) {\n\tif m != nil {\n\t\tif t >= 0 && int(t) < len(m.tx) && s >= 0 && int(s) < len(m.tx[t]) {\n\t\t\tm.tx[t][s].Inc(i)\n\t\t} else if m.txUnknown != nil {\n\t\t\tm.txUnknown.Inc(i)\n\t\t}\n\t}\n}\n\nfunc newMessageMetrics() *MessageMetrics {\n\tgen := func(t string) [][]metrics.Counter {\n\t\treturn [][]metrics.Counter{\n\t\t\t{\n\t\t\t\tmetrics.GetOrRegisterCounter(fmt.Sprintf(\"messages.%s.handshake_ixpsk0\", t), nil),\n\t\t\t},\n\t\t\tnil,\n\t\t\t{metrics.GetOrRegisterCounter(fmt.Sprintf(\"messages.%s.recv_error\", t), nil)},\n\t\t\t{metrics.GetOrRegisterCounter(fmt.Sprintf(\"messages.%s.lighthouse\", t), nil)},\n\t\t\t{\n\t\t\t\tmetrics.GetOrRegisterCounter(fmt.Sprintf(\"messages.%s.test_request\", t), nil),\n\t\t\t\tmetrics.GetOrRegisterCounter(fmt.Sprintf(\"messages.%s.test_response\", t), nil),\n\t\t\t},\n\t\t\t{metrics.GetOrRegisterCounter(fmt.Sprintf(\"messages.%s.close_tunnel\", t), nil)},\n\t\t}\n\t}\n\treturn &MessageMetrics{\n\t\trx: gen(\"rx\"),\n\t\ttx: gen(\"tx\"),\n\n\t\trxUnknown: metrics.GetOrRegisterCounter(\"messages.rx.other\", nil),\n\t\ttxUnknown: metrics.GetOrRegisterCounter(\"messages.tx.other\", nil),\n\t}\n}\n\n// Historically we only recorded recv_error, so this is backwards compat\nfunc newMessageMetricsOnlyRecvError() *MessageMetrics {\n\tgen := func(t string) [][]metrics.Counter {\n\t\treturn [][]metrics.Counter{\n\t\t\tnil,\n\t\t\tnil,\n\t\t\t{metrics.GetOrRegisterCounter(fmt.Sprintf(\"messages.%s.recv_error\", t), nil)},\n\t\t}\n\t}\n\treturn &MessageMetrics{\n\t\trx: gen(\"rx\"),\n\t\ttx: gen(\"tx\"),\n\t}\n}\n\nfunc newLighthouseMetrics() *MessageMetrics {\n\tgen := func(t string) [][]metrics.Counter {\n\t\th := make([][]metrics.Counter, len(NebulaMeta_MessageType_name))\n\t\tused := []NebulaMeta_MessageType{\n\t\t\tNebulaMeta_HostQuery,\n\t\t\tNebulaMeta_HostQueryReply,\n\t\t\tNebulaMeta_HostUpdateNotification,\n\t\t\tNebulaMeta_HostPunchNotification,\n\t\t\tNebulaMeta_HostUpdateNotificationAck,\n\t\t}\n\t\tfor _, i := range used {\n\t\t\th[i] = []metrics.Counter{metrics.GetOrRegisterCounter(fmt.Sprintf(\"lighthouse.%s.%s\", t, i.String()), nil)}\n\t\t}\n\t\treturn h\n\t}\n\treturn &MessageMetrics{\n\t\trx: gen(\"rx\"),\n\t\ttx: gen(\"tx\"),\n\n\t\trxUnknown: metrics.GetOrRegisterCounter(\"lighthouse.rx.other\", nil),\n\t\ttxUnknown: metrics.GetOrRegisterCounter(\"lighthouse.tx.other\", nil),\n\t}\n}\n"
  },
  {
    "path": "nebula.pb.go",
    "content": "// Code generated by protoc-gen-gogo. DO NOT EDIT.\n// source: nebula.proto\n\npackage nebula\n\nimport (\n\tfmt \"fmt\"\n\tproto \"github.com/gogo/protobuf/proto\"\n\tio \"io\"\n\tmath \"math\"\n\tmath_bits \"math/bits\"\n)\n\n// Reference imports to suppress errors if they are not otherwise used.\nvar _ = proto.Marshal\nvar _ = fmt.Errorf\nvar _ = math.Inf\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the proto package it is being compiled against.\n// A compilation error at this line likely means your copy of the\n// proto package needs to be updated.\nconst _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package\n\ntype NebulaMeta_MessageType int32\n\nconst (\n\tNebulaMeta_None                      NebulaMeta_MessageType = 0\n\tNebulaMeta_HostQuery                 NebulaMeta_MessageType = 1\n\tNebulaMeta_HostQueryReply            NebulaMeta_MessageType = 2\n\tNebulaMeta_HostUpdateNotification    NebulaMeta_MessageType = 3\n\tNebulaMeta_HostMovedNotification     NebulaMeta_MessageType = 4\n\tNebulaMeta_HostPunchNotification     NebulaMeta_MessageType = 5\n\tNebulaMeta_HostWhoami                NebulaMeta_MessageType = 6\n\tNebulaMeta_HostWhoamiReply           NebulaMeta_MessageType = 7\n\tNebulaMeta_PathCheck                 NebulaMeta_MessageType = 8\n\tNebulaMeta_PathCheckReply            NebulaMeta_MessageType = 9\n\tNebulaMeta_HostUpdateNotificationAck NebulaMeta_MessageType = 10\n)\n\nvar NebulaMeta_MessageType_name = map[int32]string{\n\t0:  \"None\",\n\t1:  \"HostQuery\",\n\t2:  \"HostQueryReply\",\n\t3:  \"HostUpdateNotification\",\n\t4:  \"HostMovedNotification\",\n\t5:  \"HostPunchNotification\",\n\t6:  \"HostWhoami\",\n\t7:  \"HostWhoamiReply\",\n\t8:  \"PathCheck\",\n\t9:  \"PathCheckReply\",\n\t10: \"HostUpdateNotificationAck\",\n}\n\nvar NebulaMeta_MessageType_value = map[string]int32{\n\t\"None\":                      0,\n\t\"HostQuery\":                 1,\n\t\"HostQueryReply\":            2,\n\t\"HostUpdateNotification\":    3,\n\t\"HostMovedNotification\":     4,\n\t\"HostPunchNotification\":     5,\n\t\"HostWhoami\":                6,\n\t\"HostWhoamiReply\":           7,\n\t\"PathCheck\":                 8,\n\t\"PathCheckReply\":            9,\n\t\"HostUpdateNotificationAck\": 10,\n}\n\nfunc (x NebulaMeta_MessageType) String() string {\n\treturn proto.EnumName(NebulaMeta_MessageType_name, int32(x))\n}\n\nfunc (NebulaMeta_MessageType) EnumDescriptor() ([]byte, []int) {\n\treturn fileDescriptor_2d65afa7693df5ef, []int{0, 0}\n}\n\ntype NebulaPing_MessageType int32\n\nconst (\n\tNebulaPing_Ping  NebulaPing_MessageType = 0\n\tNebulaPing_Reply NebulaPing_MessageType = 1\n)\n\nvar NebulaPing_MessageType_name = map[int32]string{\n\t0: \"Ping\",\n\t1: \"Reply\",\n}\n\nvar NebulaPing_MessageType_value = map[string]int32{\n\t\"Ping\":  0,\n\t\"Reply\": 1,\n}\n\nfunc (x NebulaPing_MessageType) String() string {\n\treturn proto.EnumName(NebulaPing_MessageType_name, int32(x))\n}\n\nfunc (NebulaPing_MessageType) EnumDescriptor() ([]byte, []int) {\n\treturn fileDescriptor_2d65afa7693df5ef, []int{5, 0}\n}\n\ntype NebulaControl_MessageType int32\n\nconst (\n\tNebulaControl_None                NebulaControl_MessageType = 0\n\tNebulaControl_CreateRelayRequest  NebulaControl_MessageType = 1\n\tNebulaControl_CreateRelayResponse NebulaControl_MessageType = 2\n)\n\nvar NebulaControl_MessageType_name = map[int32]string{\n\t0: \"None\",\n\t1: \"CreateRelayRequest\",\n\t2: \"CreateRelayResponse\",\n}\n\nvar NebulaControl_MessageType_value = map[string]int32{\n\t\"None\":                0,\n\t\"CreateRelayRequest\":  1,\n\t\"CreateRelayResponse\": 2,\n}\n\nfunc (x NebulaControl_MessageType) String() string {\n\treturn proto.EnumName(NebulaControl_MessageType_name, int32(x))\n}\n\nfunc (NebulaControl_MessageType) EnumDescriptor() ([]byte, []int) {\n\treturn fileDescriptor_2d65afa7693df5ef, []int{8, 0}\n}\n\ntype NebulaMeta struct {\n\tType    NebulaMeta_MessageType `protobuf:\"varint,1,opt,name=Type,proto3,enum=nebula.NebulaMeta_MessageType\" json:\"Type,omitempty\"`\n\tDetails *NebulaMetaDetails     `protobuf:\"bytes,2,opt,name=Details,proto3\" json:\"Details,omitempty\"`\n}\n\nfunc (m *NebulaMeta) Reset()         { *m = NebulaMeta{} }\nfunc (m *NebulaMeta) String() string { return proto.CompactTextString(m) }\nfunc (*NebulaMeta) ProtoMessage()    {}\nfunc (*NebulaMeta) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_2d65afa7693df5ef, []int{0}\n}\nfunc (m *NebulaMeta) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *NebulaMeta) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_NebulaMeta.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *NebulaMeta) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_NebulaMeta.Merge(m, src)\n}\nfunc (m *NebulaMeta) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *NebulaMeta) XXX_DiscardUnknown() {\n\txxx_messageInfo_NebulaMeta.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_NebulaMeta proto.InternalMessageInfo\n\nfunc (m *NebulaMeta) GetType() NebulaMeta_MessageType {\n\tif m != nil {\n\t\treturn m.Type\n\t}\n\treturn NebulaMeta_None\n}\n\nfunc (m *NebulaMeta) GetDetails() *NebulaMetaDetails {\n\tif m != nil {\n\t\treturn m.Details\n\t}\n\treturn nil\n}\n\ntype NebulaMetaDetails struct {\n\tOldVpnAddr       uint32        `protobuf:\"varint,1,opt,name=OldVpnAddr,proto3\" json:\"OldVpnAddr,omitempty\"` // Deprecated: Do not use.\n\tVpnAddr          *Addr         `protobuf:\"bytes,6,opt,name=VpnAddr,proto3\" json:\"VpnAddr,omitempty\"`\n\tOldRelayVpnAddrs []uint32      `protobuf:\"varint,5,rep,packed,name=OldRelayVpnAddrs,proto3\" json:\"OldRelayVpnAddrs,omitempty\"` // Deprecated: Do not use.\n\tRelayVpnAddrs    []*Addr       `protobuf:\"bytes,7,rep,name=RelayVpnAddrs,proto3\" json:\"RelayVpnAddrs,omitempty\"`\n\tV4AddrPorts      []*V4AddrPort `protobuf:\"bytes,2,rep,name=V4AddrPorts,proto3\" json:\"V4AddrPorts,omitempty\"`\n\tV6AddrPorts      []*V6AddrPort `protobuf:\"bytes,4,rep,name=V6AddrPorts,proto3\" json:\"V6AddrPorts,omitempty\"`\n\tCounter          uint32        `protobuf:\"varint,3,opt,name=counter,proto3\" json:\"counter,omitempty\"`\n}\n\nfunc (m *NebulaMetaDetails) Reset()         { *m = NebulaMetaDetails{} }\nfunc (m *NebulaMetaDetails) String() string { return proto.CompactTextString(m) }\nfunc (*NebulaMetaDetails) ProtoMessage()    {}\nfunc (*NebulaMetaDetails) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_2d65afa7693df5ef, []int{1}\n}\nfunc (m *NebulaMetaDetails) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *NebulaMetaDetails) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_NebulaMetaDetails.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *NebulaMetaDetails) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_NebulaMetaDetails.Merge(m, src)\n}\nfunc (m *NebulaMetaDetails) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *NebulaMetaDetails) XXX_DiscardUnknown() {\n\txxx_messageInfo_NebulaMetaDetails.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_NebulaMetaDetails proto.InternalMessageInfo\n\n// Deprecated: Do not use.\nfunc (m *NebulaMetaDetails) GetOldVpnAddr() uint32 {\n\tif m != nil {\n\t\treturn m.OldVpnAddr\n\t}\n\treturn 0\n}\n\nfunc (m *NebulaMetaDetails) GetVpnAddr() *Addr {\n\tif m != nil {\n\t\treturn m.VpnAddr\n\t}\n\treturn nil\n}\n\n// Deprecated: Do not use.\nfunc (m *NebulaMetaDetails) GetOldRelayVpnAddrs() []uint32 {\n\tif m != nil {\n\t\treturn m.OldRelayVpnAddrs\n\t}\n\treturn nil\n}\n\nfunc (m *NebulaMetaDetails) GetRelayVpnAddrs() []*Addr {\n\tif m != nil {\n\t\treturn m.RelayVpnAddrs\n\t}\n\treturn nil\n}\n\nfunc (m *NebulaMetaDetails) GetV4AddrPorts() []*V4AddrPort {\n\tif m != nil {\n\t\treturn m.V4AddrPorts\n\t}\n\treturn nil\n}\n\nfunc (m *NebulaMetaDetails) GetV6AddrPorts() []*V6AddrPort {\n\tif m != nil {\n\t\treturn m.V6AddrPorts\n\t}\n\treturn nil\n}\n\nfunc (m *NebulaMetaDetails) GetCounter() uint32 {\n\tif m != nil {\n\t\treturn m.Counter\n\t}\n\treturn 0\n}\n\ntype Addr struct {\n\tHi uint64 `protobuf:\"varint,1,opt,name=Hi,proto3\" json:\"Hi,omitempty\"`\n\tLo uint64 `protobuf:\"varint,2,opt,name=Lo,proto3\" json:\"Lo,omitempty\"`\n}\n\nfunc (m *Addr) Reset()         { *m = Addr{} }\nfunc (m *Addr) String() string { return proto.CompactTextString(m) }\nfunc (*Addr) ProtoMessage()    {}\nfunc (*Addr) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_2d65afa7693df5ef, []int{2}\n}\nfunc (m *Addr) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *Addr) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_Addr.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *Addr) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_Addr.Merge(m, src)\n}\nfunc (m *Addr) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *Addr) XXX_DiscardUnknown() {\n\txxx_messageInfo_Addr.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_Addr proto.InternalMessageInfo\n\nfunc (m *Addr) GetHi() uint64 {\n\tif m != nil {\n\t\treturn m.Hi\n\t}\n\treturn 0\n}\n\nfunc (m *Addr) GetLo() uint64 {\n\tif m != nil {\n\t\treturn m.Lo\n\t}\n\treturn 0\n}\n\ntype V4AddrPort struct {\n\tAddr uint32 `protobuf:\"varint,1,opt,name=Addr,proto3\" json:\"Addr,omitempty\"`\n\tPort uint32 `protobuf:\"varint,2,opt,name=Port,proto3\" json:\"Port,omitempty\"`\n}\n\nfunc (m *V4AddrPort) Reset()         { *m = V4AddrPort{} }\nfunc (m *V4AddrPort) String() string { return proto.CompactTextString(m) }\nfunc (*V4AddrPort) ProtoMessage()    {}\nfunc (*V4AddrPort) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_2d65afa7693df5ef, []int{3}\n}\nfunc (m *V4AddrPort) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *V4AddrPort) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_V4AddrPort.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *V4AddrPort) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_V4AddrPort.Merge(m, src)\n}\nfunc (m *V4AddrPort) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *V4AddrPort) XXX_DiscardUnknown() {\n\txxx_messageInfo_V4AddrPort.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_V4AddrPort proto.InternalMessageInfo\n\nfunc (m *V4AddrPort) GetAddr() uint32 {\n\tif m != nil {\n\t\treturn m.Addr\n\t}\n\treturn 0\n}\n\nfunc (m *V4AddrPort) GetPort() uint32 {\n\tif m != nil {\n\t\treturn m.Port\n\t}\n\treturn 0\n}\n\ntype V6AddrPort struct {\n\tHi   uint64 `protobuf:\"varint,1,opt,name=Hi,proto3\" json:\"Hi,omitempty\"`\n\tLo   uint64 `protobuf:\"varint,2,opt,name=Lo,proto3\" json:\"Lo,omitempty\"`\n\tPort uint32 `protobuf:\"varint,3,opt,name=Port,proto3\" json:\"Port,omitempty\"`\n}\n\nfunc (m *V6AddrPort) Reset()         { *m = V6AddrPort{} }\nfunc (m *V6AddrPort) String() string { return proto.CompactTextString(m) }\nfunc (*V6AddrPort) ProtoMessage()    {}\nfunc (*V6AddrPort) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_2d65afa7693df5ef, []int{4}\n}\nfunc (m *V6AddrPort) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *V6AddrPort) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_V6AddrPort.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *V6AddrPort) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_V6AddrPort.Merge(m, src)\n}\nfunc (m *V6AddrPort) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *V6AddrPort) XXX_DiscardUnknown() {\n\txxx_messageInfo_V6AddrPort.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_V6AddrPort proto.InternalMessageInfo\n\nfunc (m *V6AddrPort) GetHi() uint64 {\n\tif m != nil {\n\t\treturn m.Hi\n\t}\n\treturn 0\n}\n\nfunc (m *V6AddrPort) GetLo() uint64 {\n\tif m != nil {\n\t\treturn m.Lo\n\t}\n\treturn 0\n}\n\nfunc (m *V6AddrPort) GetPort() uint32 {\n\tif m != nil {\n\t\treturn m.Port\n\t}\n\treturn 0\n}\n\ntype NebulaPing struct {\n\tType NebulaPing_MessageType `protobuf:\"varint,1,opt,name=Type,proto3,enum=nebula.NebulaPing_MessageType\" json:\"Type,omitempty\"`\n\tTime uint64                 `protobuf:\"varint,2,opt,name=Time,proto3\" json:\"Time,omitempty\"`\n}\n\nfunc (m *NebulaPing) Reset()         { *m = NebulaPing{} }\nfunc (m *NebulaPing) String() string { return proto.CompactTextString(m) }\nfunc (*NebulaPing) ProtoMessage()    {}\nfunc (*NebulaPing) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_2d65afa7693df5ef, []int{5}\n}\nfunc (m *NebulaPing) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *NebulaPing) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_NebulaPing.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *NebulaPing) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_NebulaPing.Merge(m, src)\n}\nfunc (m *NebulaPing) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *NebulaPing) XXX_DiscardUnknown() {\n\txxx_messageInfo_NebulaPing.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_NebulaPing proto.InternalMessageInfo\n\nfunc (m *NebulaPing) GetType() NebulaPing_MessageType {\n\tif m != nil {\n\t\treturn m.Type\n\t}\n\treturn NebulaPing_Ping\n}\n\nfunc (m *NebulaPing) GetTime() uint64 {\n\tif m != nil {\n\t\treturn m.Time\n\t}\n\treturn 0\n}\n\ntype NebulaHandshake struct {\n\tDetails *NebulaHandshakeDetails `protobuf:\"bytes,1,opt,name=Details,proto3\" json:\"Details,omitempty\"`\n\tHmac    []byte                  `protobuf:\"bytes,2,opt,name=Hmac,proto3\" json:\"Hmac,omitempty\"`\n}\n\nfunc (m *NebulaHandshake) Reset()         { *m = NebulaHandshake{} }\nfunc (m *NebulaHandshake) String() string { return proto.CompactTextString(m) }\nfunc (*NebulaHandshake) ProtoMessage()    {}\nfunc (*NebulaHandshake) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_2d65afa7693df5ef, []int{6}\n}\nfunc (m *NebulaHandshake) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *NebulaHandshake) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_NebulaHandshake.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *NebulaHandshake) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_NebulaHandshake.Merge(m, src)\n}\nfunc (m *NebulaHandshake) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *NebulaHandshake) XXX_DiscardUnknown() {\n\txxx_messageInfo_NebulaHandshake.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_NebulaHandshake proto.InternalMessageInfo\n\nfunc (m *NebulaHandshake) GetDetails() *NebulaHandshakeDetails {\n\tif m != nil {\n\t\treturn m.Details\n\t}\n\treturn nil\n}\n\nfunc (m *NebulaHandshake) GetHmac() []byte {\n\tif m != nil {\n\t\treturn m.Hmac\n\t}\n\treturn nil\n}\n\ntype NebulaHandshakeDetails struct {\n\tCert           []byte `protobuf:\"bytes,1,opt,name=Cert,proto3\" json:\"Cert,omitempty\"`\n\tInitiatorIndex uint32 `protobuf:\"varint,2,opt,name=InitiatorIndex,proto3\" json:\"InitiatorIndex,omitempty\"`\n\tResponderIndex uint32 `protobuf:\"varint,3,opt,name=ResponderIndex,proto3\" json:\"ResponderIndex,omitempty\"`\n\tCookie         uint64 `protobuf:\"varint,4,opt,name=Cookie,proto3\" json:\"Cookie,omitempty\"`\n\tTime           uint64 `protobuf:\"varint,5,opt,name=Time,proto3\" json:\"Time,omitempty\"`\n\tCertVersion    uint32 `protobuf:\"varint,8,opt,name=CertVersion,proto3\" json:\"CertVersion,omitempty\"`\n}\n\nfunc (m *NebulaHandshakeDetails) Reset()         { *m = NebulaHandshakeDetails{} }\nfunc (m *NebulaHandshakeDetails) String() string { return proto.CompactTextString(m) }\nfunc (*NebulaHandshakeDetails) ProtoMessage()    {}\nfunc (*NebulaHandshakeDetails) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_2d65afa7693df5ef, []int{7}\n}\nfunc (m *NebulaHandshakeDetails) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *NebulaHandshakeDetails) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_NebulaHandshakeDetails.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *NebulaHandshakeDetails) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_NebulaHandshakeDetails.Merge(m, src)\n}\nfunc (m *NebulaHandshakeDetails) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *NebulaHandshakeDetails) XXX_DiscardUnknown() {\n\txxx_messageInfo_NebulaHandshakeDetails.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_NebulaHandshakeDetails proto.InternalMessageInfo\n\nfunc (m *NebulaHandshakeDetails) GetCert() []byte {\n\tif m != nil {\n\t\treturn m.Cert\n\t}\n\treturn nil\n}\n\nfunc (m *NebulaHandshakeDetails) GetInitiatorIndex() uint32 {\n\tif m != nil {\n\t\treturn m.InitiatorIndex\n\t}\n\treturn 0\n}\n\nfunc (m *NebulaHandshakeDetails) GetResponderIndex() uint32 {\n\tif m != nil {\n\t\treturn m.ResponderIndex\n\t}\n\treturn 0\n}\n\nfunc (m *NebulaHandshakeDetails) GetCookie() uint64 {\n\tif m != nil {\n\t\treturn m.Cookie\n\t}\n\treturn 0\n}\n\nfunc (m *NebulaHandshakeDetails) GetTime() uint64 {\n\tif m != nil {\n\t\treturn m.Time\n\t}\n\treturn 0\n}\n\nfunc (m *NebulaHandshakeDetails) GetCertVersion() uint32 {\n\tif m != nil {\n\t\treturn m.CertVersion\n\t}\n\treturn 0\n}\n\ntype NebulaControl struct {\n\tType                NebulaControl_MessageType `protobuf:\"varint,1,opt,name=Type,proto3,enum=nebula.NebulaControl_MessageType\" json:\"Type,omitempty\"`\n\tInitiatorRelayIndex uint32                    `protobuf:\"varint,2,opt,name=InitiatorRelayIndex,proto3\" json:\"InitiatorRelayIndex,omitempty\"`\n\tResponderRelayIndex uint32                    `protobuf:\"varint,3,opt,name=ResponderRelayIndex,proto3\" json:\"ResponderRelayIndex,omitempty\"`\n\tOldRelayToAddr      uint32                    `protobuf:\"varint,4,opt,name=OldRelayToAddr,proto3\" json:\"OldRelayToAddr,omitempty\"`     // Deprecated: Do not use.\n\tOldRelayFromAddr    uint32                    `protobuf:\"varint,5,opt,name=OldRelayFromAddr,proto3\" json:\"OldRelayFromAddr,omitempty\"` // Deprecated: Do not use.\n\tRelayToAddr         *Addr                     `protobuf:\"bytes,6,opt,name=RelayToAddr,proto3\" json:\"RelayToAddr,omitempty\"`\n\tRelayFromAddr       *Addr                     `protobuf:\"bytes,7,opt,name=RelayFromAddr,proto3\" json:\"RelayFromAddr,omitempty\"`\n}\n\nfunc (m *NebulaControl) Reset()         { *m = NebulaControl{} }\nfunc (m *NebulaControl) String() string { return proto.CompactTextString(m) }\nfunc (*NebulaControl) ProtoMessage()    {}\nfunc (*NebulaControl) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_2d65afa7693df5ef, []int{8}\n}\nfunc (m *NebulaControl) XXX_Unmarshal(b []byte) error {\n\treturn m.Unmarshal(b)\n}\nfunc (m *NebulaControl) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\tif deterministic {\n\t\treturn xxx_messageInfo_NebulaControl.Marshal(b, m, deterministic)\n\t} else {\n\t\tb = b[:cap(b)]\n\t\tn, err := m.MarshalToSizedBuffer(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn b[:n], nil\n\t}\n}\nfunc (m *NebulaControl) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_NebulaControl.Merge(m, src)\n}\nfunc (m *NebulaControl) XXX_Size() int {\n\treturn m.Size()\n}\nfunc (m *NebulaControl) XXX_DiscardUnknown() {\n\txxx_messageInfo_NebulaControl.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_NebulaControl proto.InternalMessageInfo\n\nfunc (m *NebulaControl) GetType() NebulaControl_MessageType {\n\tif m != nil {\n\t\treturn m.Type\n\t}\n\treturn NebulaControl_None\n}\n\nfunc (m *NebulaControl) GetInitiatorRelayIndex() uint32 {\n\tif m != nil {\n\t\treturn m.InitiatorRelayIndex\n\t}\n\treturn 0\n}\n\nfunc (m *NebulaControl) GetResponderRelayIndex() uint32 {\n\tif m != nil {\n\t\treturn m.ResponderRelayIndex\n\t}\n\treturn 0\n}\n\n// Deprecated: Do not use.\nfunc (m *NebulaControl) GetOldRelayToAddr() uint32 {\n\tif m != nil {\n\t\treturn m.OldRelayToAddr\n\t}\n\treturn 0\n}\n\n// Deprecated: Do not use.\nfunc (m *NebulaControl) GetOldRelayFromAddr() uint32 {\n\tif m != nil {\n\t\treturn m.OldRelayFromAddr\n\t}\n\treturn 0\n}\n\nfunc (m *NebulaControl) GetRelayToAddr() *Addr {\n\tif m != nil {\n\t\treturn m.RelayToAddr\n\t}\n\treturn nil\n}\n\nfunc (m *NebulaControl) GetRelayFromAddr() *Addr {\n\tif m != nil {\n\t\treturn m.RelayFromAddr\n\t}\n\treturn nil\n}\n\nfunc init() {\n\tproto.RegisterEnum(\"nebula.NebulaMeta_MessageType\", NebulaMeta_MessageType_name, NebulaMeta_MessageType_value)\n\tproto.RegisterEnum(\"nebula.NebulaPing_MessageType\", NebulaPing_MessageType_name, NebulaPing_MessageType_value)\n\tproto.RegisterEnum(\"nebula.NebulaControl_MessageType\", NebulaControl_MessageType_name, NebulaControl_MessageType_value)\n\tproto.RegisterType((*NebulaMeta)(nil), \"nebula.NebulaMeta\")\n\tproto.RegisterType((*NebulaMetaDetails)(nil), \"nebula.NebulaMetaDetails\")\n\tproto.RegisterType((*Addr)(nil), \"nebula.Addr\")\n\tproto.RegisterType((*V4AddrPort)(nil), \"nebula.V4AddrPort\")\n\tproto.RegisterType((*V6AddrPort)(nil), \"nebula.V6AddrPort\")\n\tproto.RegisterType((*NebulaPing)(nil), \"nebula.NebulaPing\")\n\tproto.RegisterType((*NebulaHandshake)(nil), \"nebula.NebulaHandshake\")\n\tproto.RegisterType((*NebulaHandshakeDetails)(nil), \"nebula.NebulaHandshakeDetails\")\n\tproto.RegisterType((*NebulaControl)(nil), \"nebula.NebulaControl\")\n}\n\nfunc init() { proto.RegisterFile(\"nebula.proto\", fileDescriptor_2d65afa7693df5ef) }\n\nvar fileDescriptor_2d65afa7693df5ef = []byte{\n\t// 785 bytes of a gzipped FileDescriptorProto\n\t0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x55, 0xcd, 0x6e, 0xeb, 0x44,\n\t0x14, 0x8e, 0x1d, 0x27, 0x4e, 0x4f, 0x7e, 0xae, 0x39, 0x15, 0xc1, 0x41, 0x22, 0x0a, 0x5e, 0x54,\n\t0x57, 0x2c, 0x72, 0x51, 0x5a, 0xae, 0x58, 0x72, 0x1b, 0x84, 0xd2, 0xaa, 0x3f, 0x61, 0x54, 0x8a,\n\t0xc4, 0x06, 0xb9, 0xf6, 0xd0, 0x58, 0x71, 0x3c, 0xa9, 0x3d, 0x41, 0xcd, 0x5b, 0xf0, 0x30, 0x3c,\n\t0x04, 0xec, 0xba, 0x42, 0x2c, 0x51, 0xbb, 0x64, 0xc9, 0x0b, 0xa0, 0x19, 0xff, 0x27, 0x86, 0xbb,\n\t0x9b, 0x73, 0xbe, 0xef, 0x3b, 0x73, 0xe6, 0xf3, 0x9c, 0x31, 0x74, 0x02, 0x7a, 0xb7, 0xf1, 0xed,\n\t0xf1, 0x3a, 0x64, 0x9c, 0x61, 0x33, 0x8e, 0xac, 0xbf, 0x55, 0x80, 0x2b, 0xb9, 0xbc, 0xa4, 0xdc,\n\t0xc6, 0x09, 0x68, 0x37, 0xdb, 0x35, 0x35, 0x95, 0x91, 0xf2, 0xba, 0x37, 0x19, 0x8e, 0x13, 0x4d,\n\t0xce, 0x18, 0x5f, 0xd2, 0x28, 0xb2, 0xef, 0xa9, 0x60, 0x11, 0xc9, 0xc5, 0x63, 0xd0, 0xbf, 0xa6,\n\t0xdc, 0xf6, 0xfc, 0xc8, 0x54, 0x47, 0xca, 0xeb, 0xf6, 0x64, 0xb0, 0x2f, 0x4b, 0x08, 0x24, 0x65,\n\t0x5a, 0xff, 0x28, 0xd0, 0x2e, 0x94, 0xc2, 0x16, 0x68, 0x57, 0x2c, 0xa0, 0x46, 0x0d, 0xbb, 0x70,\n\t0x30, 0x63, 0x11, 0xff, 0x76, 0x43, 0xc3, 0xad, 0xa1, 0x20, 0x42, 0x2f, 0x0b, 0x09, 0x5d, 0xfb,\n\t0x5b, 0x43, 0xc5, 0x8f, 0xa1, 0x2f, 0x72, 0xdf, 0xad, 0x5d, 0x9b, 0xd3, 0x2b, 0xc6, 0xbd, 0x9f,\n\t0x3c, 0xc7, 0xe6, 0x1e, 0x0b, 0x8c, 0x3a, 0x0e, 0xe0, 0x43, 0x81, 0x5d, 0xb2, 0x9f, 0xa9, 0x5b,\n\t0x82, 0xb4, 0x14, 0x9a, 0x6f, 0x02, 0x67, 0x51, 0x82, 0x1a, 0xd8, 0x03, 0x10, 0xd0, 0xf7, 0x0b,\n\t0x66, 0xaf, 0x3c, 0xa3, 0x89, 0x87, 0xf0, 0x2a, 0x8f, 0xe3, 0x6d, 0x75, 0xd1, 0xd9, 0xdc, 0xe6,\n\t0x8b, 0xe9, 0x82, 0x3a, 0x4b, 0xa3, 0x25, 0x3a, 0xcb, 0xc2, 0x98, 0x72, 0x80, 0x9f, 0xc0, 0xa0,\n\t0xba, 0xb3, 0x77, 0xce, 0xd2, 0x00, 0xeb, 0x77, 0x15, 0x3e, 0xd8, 0x33, 0x05, 0x2d, 0x80, 0x6b,\n\t0xdf, 0xbd, 0x5d, 0x07, 0xef, 0x5c, 0x37, 0x94, 0xd6, 0x77, 0x4f, 0x55, 0x53, 0x21, 0x85, 0x2c,\n\t0x1e, 0x81, 0x9e, 0x12, 0x9a, 0xd2, 0xe4, 0x4e, 0x6a, 0xb2, 0xc8, 0x91, 0x14, 0xc4, 0x31, 0x18,\n\t0xd7, 0xbe, 0x4b, 0xa8, 0x6f, 0x6f, 0x93, 0x54, 0x64, 0x36, 0x46, 0xf5, 0xa4, 0xe2, 0x1e, 0x86,\n\t0x13, 0xe8, 0x96, 0xc9, 0xfa, 0xa8, 0xbe, 0x57, 0xbd, 0x4c, 0xc1, 0x13, 0x68, 0xdf, 0x9e, 0x88,\n\t0xe5, 0x9c, 0x85, 0x5c, 0x7c, 0x74, 0xa1, 0xc0, 0x54, 0x91, 0x43, 0xa4, 0x48, 0x93, 0xaa, 0xb7,\n\t0xb9, 0x4a, 0xdb, 0x51, 0xbd, 0x2d, 0xa8, 0x72, 0x1a, 0x9a, 0xa0, 0x3b, 0x6c, 0x13, 0x70, 0x1a,\n\t0x9a, 0x75, 0x61, 0x0c, 0x49, 0x43, 0xeb, 0x08, 0x34, 0x79, 0xe2, 0x1e, 0xa8, 0x33, 0x4f, 0xba,\n\t0xa6, 0x11, 0x75, 0xe6, 0x89, 0xf8, 0x82, 0xc9, 0x9b, 0xa8, 0x11, 0xf5, 0x82, 0x59, 0x27, 0x00,\n\t0x79, 0x1b, 0x88, 0xb1, 0x2a, 0x76, 0x99, 0xc4, 0x15, 0x10, 0x34, 0x81, 0x49, 0x4d, 0x97, 0xc8,\n\t0xb5, 0xf5, 0x15, 0x40, 0xde, 0xc6, 0xfb, 0xf6, 0xc8, 0x2a, 0xd4, 0x0b, 0x15, 0x1e, 0xd3, 0xc1,\n\t0x9a, 0x7b, 0xc1, 0xfd, 0xff, 0x0f, 0x96, 0x60, 0x54, 0x0c, 0x16, 0x82, 0x76, 0xe3, 0xad, 0x68,\n\t0xb2, 0x8f, 0x5c, 0x5b, 0xd6, 0xde, 0xd8, 0x08, 0xb1, 0x51, 0xc3, 0x03, 0x68, 0xc4, 0x97, 0x50,\n\t0xb1, 0x7e, 0x84, 0x57, 0x71, 0xdd, 0x99, 0x1d, 0xb8, 0xd1, 0xc2, 0x5e, 0x52, 0xfc, 0x32, 0x9f,\n\t0x51, 0x45, 0x5e, 0x9f, 0x9d, 0x0e, 0x32, 0xe6, 0xee, 0xa0, 0x8a, 0x26, 0x66, 0x2b, 0xdb, 0x91,\n\t0x4d, 0x74, 0x88, 0x5c, 0x5b, 0x7f, 0x28, 0xd0, 0xaf, 0xd6, 0x09, 0xfa, 0x94, 0x86, 0x5c, 0xee,\n\t0xd2, 0x21, 0x72, 0x8d, 0x47, 0xd0, 0x3b, 0x0b, 0x3c, 0xee, 0xd9, 0x9c, 0x85, 0x67, 0x81, 0x4b,\n\t0x1f, 0x13, 0xa7, 0x77, 0xb2, 0x82, 0x47, 0x68, 0xb4, 0x66, 0x81, 0x4b, 0x13, 0x5e, 0xec, 0xe7,\n\t0x4e, 0x16, 0xfb, 0xd0, 0x9c, 0x32, 0xb6, 0xf4, 0xa8, 0xa9, 0x49, 0x67, 0x92, 0x28, 0xf3, 0xab,\n\t0x91, 0xfb, 0x85, 0x23, 0x68, 0x8b, 0x1e, 0x6e, 0x69, 0x18, 0x79, 0x2c, 0x30, 0x5b, 0xb2, 0x60,\n\t0x31, 0x75, 0xae, 0xb5, 0x9a, 0x86, 0x7e, 0xae, 0xb5, 0x74, 0xa3, 0x65, 0xfd, 0x5a, 0x87, 0x6e,\n\t0x7c, 0xb0, 0x29, 0x0b, 0x78, 0xc8, 0x7c, 0xfc, 0xa2, 0xf4, 0xdd, 0x3e, 0x2d, 0xbb, 0x96, 0x90,\n\t0x2a, 0x3e, 0xdd, 0xe7, 0x70, 0x98, 0x1d, 0x4e, 0x0e, 0x4f, 0xf1, 0xdc, 0x55, 0x90, 0x50, 0x64,\n\t0xc7, 0x2c, 0x28, 0x62, 0x07, 0xaa, 0x20, 0xfc, 0x0c, 0x7a, 0xe9, 0x38, 0xdf, 0x30, 0x79, 0xa9,\n\t0xb5, 0xec, 0xe9, 0xd8, 0x41, 0x8a, 0xcf, 0xc2, 0x37, 0x21, 0x5b, 0x49, 0x76, 0x23, 0x63, 0xef,\n\t0x61, 0x38, 0x86, 0x76, 0xb1, 0x70, 0xd5, 0x93, 0x53, 0x24, 0x64, 0xcf, 0x48, 0x56, 0x5c, 0xaf,\n\t0x50, 0x94, 0x29, 0xd6, 0xec, 0xbf, 0xfe, 0x00, 0x7d, 0xc0, 0x69, 0x48, 0x6d, 0x4e, 0x25, 0x9f,\n\t0xd0, 0x87, 0x0d, 0x8d, 0xb8, 0xa1, 0xe0, 0x47, 0x70, 0x58, 0xca, 0x0b, 0x4b, 0x22, 0x6a, 0xa8,\n\t0xa7, 0xc7, 0xbf, 0x3d, 0x0f, 0x95, 0xa7, 0xe7, 0xa1, 0xf2, 0xd7, 0xf3, 0x50, 0xf9, 0xe5, 0x65,\n\t0x58, 0x7b, 0x7a, 0x19, 0xd6, 0xfe, 0x7c, 0x19, 0xd6, 0x7e, 0x18, 0xdc, 0x7b, 0x7c, 0xb1, 0xb9,\n\t0x1b, 0x3b, 0x6c, 0xf5, 0x26, 0xf2, 0x6d, 0x67, 0xb9, 0x78, 0x78, 0x13, 0xb7, 0x74, 0xd7, 0x94,\n\t0x3f, 0xc2, 0xe3, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0xea, 0x6f, 0xbc, 0x50, 0x18, 0x07, 0x00,\n\t0x00,\n}\n\nfunc (m *NebulaMeta) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *NebulaMeta) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *NebulaMeta) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.Details != nil {\n\t\t{\n\t\t\tsize, err := m.Details.MarshalToSizedBuffer(dAtA[:i])\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\ti -= size\n\t\t\ti = encodeVarintNebula(dAtA, i, uint64(size))\n\t\t}\n\t\ti--\n\t\tdAtA[i] = 0x12\n\t}\n\tif m.Type != 0 {\n\t\ti = encodeVarintNebula(dAtA, i, uint64(m.Type))\n\t\ti--\n\t\tdAtA[i] = 0x8\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *NebulaMetaDetails) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *NebulaMetaDetails) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *NebulaMetaDetails) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif len(m.RelayVpnAddrs) > 0 {\n\t\tfor iNdEx := len(m.RelayVpnAddrs) - 1; iNdEx >= 0; iNdEx-- {\n\t\t\t{\n\t\t\t\tsize, err := m.RelayVpnAddrs[iNdEx].MarshalToSizedBuffer(dAtA[:i])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t\ti -= size\n\t\t\t\ti = encodeVarintNebula(dAtA, i, uint64(size))\n\t\t\t}\n\t\t\ti--\n\t\t\tdAtA[i] = 0x3a\n\t\t}\n\t}\n\tif m.VpnAddr != nil {\n\t\t{\n\t\t\tsize, err := m.VpnAddr.MarshalToSizedBuffer(dAtA[:i])\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\ti -= size\n\t\t\ti = encodeVarintNebula(dAtA, i, uint64(size))\n\t\t}\n\t\ti--\n\t\tdAtA[i] = 0x32\n\t}\n\tif len(m.OldRelayVpnAddrs) > 0 {\n\t\tdAtA4 := make([]byte, len(m.OldRelayVpnAddrs)*10)\n\t\tvar j3 int\n\t\tfor _, num := range m.OldRelayVpnAddrs {\n\t\t\tfor num >= 1<<7 {\n\t\t\t\tdAtA4[j3] = uint8(uint64(num)&0x7f | 0x80)\n\t\t\t\tnum >>= 7\n\t\t\t\tj3++\n\t\t\t}\n\t\t\tdAtA4[j3] = uint8(num)\n\t\t\tj3++\n\t\t}\n\t\ti -= j3\n\t\tcopy(dAtA[i:], dAtA4[:j3])\n\t\ti = encodeVarintNebula(dAtA, i, uint64(j3))\n\t\ti--\n\t\tdAtA[i] = 0x2a\n\t}\n\tif len(m.V6AddrPorts) > 0 {\n\t\tfor iNdEx := len(m.V6AddrPorts) - 1; iNdEx >= 0; iNdEx-- {\n\t\t\t{\n\t\t\t\tsize, err := m.V6AddrPorts[iNdEx].MarshalToSizedBuffer(dAtA[:i])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t\ti -= size\n\t\t\t\ti = encodeVarintNebula(dAtA, i, uint64(size))\n\t\t\t}\n\t\t\ti--\n\t\t\tdAtA[i] = 0x22\n\t\t}\n\t}\n\tif m.Counter != 0 {\n\t\ti = encodeVarintNebula(dAtA, i, uint64(m.Counter))\n\t\ti--\n\t\tdAtA[i] = 0x18\n\t}\n\tif len(m.V4AddrPorts) > 0 {\n\t\tfor iNdEx := len(m.V4AddrPorts) - 1; iNdEx >= 0; iNdEx-- {\n\t\t\t{\n\t\t\t\tsize, err := m.V4AddrPorts[iNdEx].MarshalToSizedBuffer(dAtA[:i])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t\ti -= size\n\t\t\t\ti = encodeVarintNebula(dAtA, i, uint64(size))\n\t\t\t}\n\t\t\ti--\n\t\t\tdAtA[i] = 0x12\n\t\t}\n\t}\n\tif m.OldVpnAddr != 0 {\n\t\ti = encodeVarintNebula(dAtA, i, uint64(m.OldVpnAddr))\n\t\ti--\n\t\tdAtA[i] = 0x8\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *Addr) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *Addr) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *Addr) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.Lo != 0 {\n\t\ti = encodeVarintNebula(dAtA, i, uint64(m.Lo))\n\t\ti--\n\t\tdAtA[i] = 0x10\n\t}\n\tif m.Hi != 0 {\n\t\ti = encodeVarintNebula(dAtA, i, uint64(m.Hi))\n\t\ti--\n\t\tdAtA[i] = 0x8\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *V4AddrPort) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *V4AddrPort) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *V4AddrPort) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.Port != 0 {\n\t\ti = encodeVarintNebula(dAtA, i, uint64(m.Port))\n\t\ti--\n\t\tdAtA[i] = 0x10\n\t}\n\tif m.Addr != 0 {\n\t\ti = encodeVarintNebula(dAtA, i, uint64(m.Addr))\n\t\ti--\n\t\tdAtA[i] = 0x8\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *V6AddrPort) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *V6AddrPort) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *V6AddrPort) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.Port != 0 {\n\t\ti = encodeVarintNebula(dAtA, i, uint64(m.Port))\n\t\ti--\n\t\tdAtA[i] = 0x18\n\t}\n\tif m.Lo != 0 {\n\t\ti = encodeVarintNebula(dAtA, i, uint64(m.Lo))\n\t\ti--\n\t\tdAtA[i] = 0x10\n\t}\n\tif m.Hi != 0 {\n\t\ti = encodeVarintNebula(dAtA, i, uint64(m.Hi))\n\t\ti--\n\t\tdAtA[i] = 0x8\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *NebulaPing) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *NebulaPing) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *NebulaPing) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.Time != 0 {\n\t\ti = encodeVarintNebula(dAtA, i, uint64(m.Time))\n\t\ti--\n\t\tdAtA[i] = 0x10\n\t}\n\tif m.Type != 0 {\n\t\ti = encodeVarintNebula(dAtA, i, uint64(m.Type))\n\t\ti--\n\t\tdAtA[i] = 0x8\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *NebulaHandshake) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *NebulaHandshake) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *NebulaHandshake) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif len(m.Hmac) > 0 {\n\t\ti -= len(m.Hmac)\n\t\tcopy(dAtA[i:], m.Hmac)\n\t\ti = encodeVarintNebula(dAtA, i, uint64(len(m.Hmac)))\n\t\ti--\n\t\tdAtA[i] = 0x12\n\t}\n\tif m.Details != nil {\n\t\t{\n\t\t\tsize, err := m.Details.MarshalToSizedBuffer(dAtA[:i])\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\ti -= size\n\t\t\ti = encodeVarintNebula(dAtA, i, uint64(size))\n\t\t}\n\t\ti--\n\t\tdAtA[i] = 0xa\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *NebulaHandshakeDetails) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *NebulaHandshakeDetails) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *NebulaHandshakeDetails) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.CertVersion != 0 {\n\t\ti = encodeVarintNebula(dAtA, i, uint64(m.CertVersion))\n\t\ti--\n\t\tdAtA[i] = 0x40\n\t}\n\tif m.Time != 0 {\n\t\ti = encodeVarintNebula(dAtA, i, uint64(m.Time))\n\t\ti--\n\t\tdAtA[i] = 0x28\n\t}\n\tif m.Cookie != 0 {\n\t\ti = encodeVarintNebula(dAtA, i, uint64(m.Cookie))\n\t\ti--\n\t\tdAtA[i] = 0x20\n\t}\n\tif m.ResponderIndex != 0 {\n\t\ti = encodeVarintNebula(dAtA, i, uint64(m.ResponderIndex))\n\t\ti--\n\t\tdAtA[i] = 0x18\n\t}\n\tif m.InitiatorIndex != 0 {\n\t\ti = encodeVarintNebula(dAtA, i, uint64(m.InitiatorIndex))\n\t\ti--\n\t\tdAtA[i] = 0x10\n\t}\n\tif len(m.Cert) > 0 {\n\t\ti -= len(m.Cert)\n\t\tcopy(dAtA[i:], m.Cert)\n\t\ti = encodeVarintNebula(dAtA, i, uint64(len(m.Cert)))\n\t\ti--\n\t\tdAtA[i] = 0xa\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc (m *NebulaControl) Marshal() (dAtA []byte, err error) {\n\tsize := m.Size()\n\tdAtA = make([]byte, size)\n\tn, err := m.MarshalToSizedBuffer(dAtA[:size])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dAtA[:n], nil\n}\n\nfunc (m *NebulaControl) MarshalTo(dAtA []byte) (int, error) {\n\tsize := m.Size()\n\treturn m.MarshalToSizedBuffer(dAtA[:size])\n}\n\nfunc (m *NebulaControl) MarshalToSizedBuffer(dAtA []byte) (int, error) {\n\ti := len(dAtA)\n\t_ = i\n\tvar l int\n\t_ = l\n\tif m.RelayFromAddr != nil {\n\t\t{\n\t\t\tsize, err := m.RelayFromAddr.MarshalToSizedBuffer(dAtA[:i])\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\ti -= size\n\t\t\ti = encodeVarintNebula(dAtA, i, uint64(size))\n\t\t}\n\t\ti--\n\t\tdAtA[i] = 0x3a\n\t}\n\tif m.RelayToAddr != nil {\n\t\t{\n\t\t\tsize, err := m.RelayToAddr.MarshalToSizedBuffer(dAtA[:i])\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\ti -= size\n\t\t\ti = encodeVarintNebula(dAtA, i, uint64(size))\n\t\t}\n\t\ti--\n\t\tdAtA[i] = 0x32\n\t}\n\tif m.OldRelayFromAddr != 0 {\n\t\ti = encodeVarintNebula(dAtA, i, uint64(m.OldRelayFromAddr))\n\t\ti--\n\t\tdAtA[i] = 0x28\n\t}\n\tif m.OldRelayToAddr != 0 {\n\t\ti = encodeVarintNebula(dAtA, i, uint64(m.OldRelayToAddr))\n\t\ti--\n\t\tdAtA[i] = 0x20\n\t}\n\tif m.ResponderRelayIndex != 0 {\n\t\ti = encodeVarintNebula(dAtA, i, uint64(m.ResponderRelayIndex))\n\t\ti--\n\t\tdAtA[i] = 0x18\n\t}\n\tif m.InitiatorRelayIndex != 0 {\n\t\ti = encodeVarintNebula(dAtA, i, uint64(m.InitiatorRelayIndex))\n\t\ti--\n\t\tdAtA[i] = 0x10\n\t}\n\tif m.Type != 0 {\n\t\ti = encodeVarintNebula(dAtA, i, uint64(m.Type))\n\t\ti--\n\t\tdAtA[i] = 0x8\n\t}\n\treturn len(dAtA) - i, nil\n}\n\nfunc encodeVarintNebula(dAtA []byte, offset int, v uint64) int {\n\toffset -= sovNebula(v)\n\tbase := offset\n\tfor v >= 1<<7 {\n\t\tdAtA[offset] = uint8(v&0x7f | 0x80)\n\t\tv >>= 7\n\t\toffset++\n\t}\n\tdAtA[offset] = uint8(v)\n\treturn base\n}\nfunc (m *NebulaMeta) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.Type != 0 {\n\t\tn += 1 + sovNebula(uint64(m.Type))\n\t}\n\tif m.Details != nil {\n\t\tl = m.Details.Size()\n\t\tn += 1 + l + sovNebula(uint64(l))\n\t}\n\treturn n\n}\n\nfunc (m *NebulaMetaDetails) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.OldVpnAddr != 0 {\n\t\tn += 1 + sovNebula(uint64(m.OldVpnAddr))\n\t}\n\tif len(m.V4AddrPorts) > 0 {\n\t\tfor _, e := range m.V4AddrPorts {\n\t\t\tl = e.Size()\n\t\t\tn += 1 + l + sovNebula(uint64(l))\n\t\t}\n\t}\n\tif m.Counter != 0 {\n\t\tn += 1 + sovNebula(uint64(m.Counter))\n\t}\n\tif len(m.V6AddrPorts) > 0 {\n\t\tfor _, e := range m.V6AddrPorts {\n\t\t\tl = e.Size()\n\t\t\tn += 1 + l + sovNebula(uint64(l))\n\t\t}\n\t}\n\tif len(m.OldRelayVpnAddrs) > 0 {\n\t\tl = 0\n\t\tfor _, e := range m.OldRelayVpnAddrs {\n\t\t\tl += sovNebula(uint64(e))\n\t\t}\n\t\tn += 1 + sovNebula(uint64(l)) + l\n\t}\n\tif m.VpnAddr != nil {\n\t\tl = m.VpnAddr.Size()\n\t\tn += 1 + l + sovNebula(uint64(l))\n\t}\n\tif len(m.RelayVpnAddrs) > 0 {\n\t\tfor _, e := range m.RelayVpnAddrs {\n\t\t\tl = e.Size()\n\t\t\tn += 1 + l + sovNebula(uint64(l))\n\t\t}\n\t}\n\treturn n\n}\n\nfunc (m *Addr) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.Hi != 0 {\n\t\tn += 1 + sovNebula(uint64(m.Hi))\n\t}\n\tif m.Lo != 0 {\n\t\tn += 1 + sovNebula(uint64(m.Lo))\n\t}\n\treturn n\n}\n\nfunc (m *V4AddrPort) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.Addr != 0 {\n\t\tn += 1 + sovNebula(uint64(m.Addr))\n\t}\n\tif m.Port != 0 {\n\t\tn += 1 + sovNebula(uint64(m.Port))\n\t}\n\treturn n\n}\n\nfunc (m *V6AddrPort) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.Hi != 0 {\n\t\tn += 1 + sovNebula(uint64(m.Hi))\n\t}\n\tif m.Lo != 0 {\n\t\tn += 1 + sovNebula(uint64(m.Lo))\n\t}\n\tif m.Port != 0 {\n\t\tn += 1 + sovNebula(uint64(m.Port))\n\t}\n\treturn n\n}\n\nfunc (m *NebulaPing) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.Type != 0 {\n\t\tn += 1 + sovNebula(uint64(m.Type))\n\t}\n\tif m.Time != 0 {\n\t\tn += 1 + sovNebula(uint64(m.Time))\n\t}\n\treturn n\n}\n\nfunc (m *NebulaHandshake) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.Details != nil {\n\t\tl = m.Details.Size()\n\t\tn += 1 + l + sovNebula(uint64(l))\n\t}\n\tl = len(m.Hmac)\n\tif l > 0 {\n\t\tn += 1 + l + sovNebula(uint64(l))\n\t}\n\treturn n\n}\n\nfunc (m *NebulaHandshakeDetails) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tl = len(m.Cert)\n\tif l > 0 {\n\t\tn += 1 + l + sovNebula(uint64(l))\n\t}\n\tif m.InitiatorIndex != 0 {\n\t\tn += 1 + sovNebula(uint64(m.InitiatorIndex))\n\t}\n\tif m.ResponderIndex != 0 {\n\t\tn += 1 + sovNebula(uint64(m.ResponderIndex))\n\t}\n\tif m.Cookie != 0 {\n\t\tn += 1 + sovNebula(uint64(m.Cookie))\n\t}\n\tif m.Time != 0 {\n\t\tn += 1 + sovNebula(uint64(m.Time))\n\t}\n\tif m.CertVersion != 0 {\n\t\tn += 1 + sovNebula(uint64(m.CertVersion))\n\t}\n\treturn n\n}\n\nfunc (m *NebulaControl) Size() (n int) {\n\tif m == nil {\n\t\treturn 0\n\t}\n\tvar l int\n\t_ = l\n\tif m.Type != 0 {\n\t\tn += 1 + sovNebula(uint64(m.Type))\n\t}\n\tif m.InitiatorRelayIndex != 0 {\n\t\tn += 1 + sovNebula(uint64(m.InitiatorRelayIndex))\n\t}\n\tif m.ResponderRelayIndex != 0 {\n\t\tn += 1 + sovNebula(uint64(m.ResponderRelayIndex))\n\t}\n\tif m.OldRelayToAddr != 0 {\n\t\tn += 1 + sovNebula(uint64(m.OldRelayToAddr))\n\t}\n\tif m.OldRelayFromAddr != 0 {\n\t\tn += 1 + sovNebula(uint64(m.OldRelayFromAddr))\n\t}\n\tif m.RelayToAddr != nil {\n\t\tl = m.RelayToAddr.Size()\n\t\tn += 1 + l + sovNebula(uint64(l))\n\t}\n\tif m.RelayFromAddr != nil {\n\t\tl = m.RelayFromAddr.Size()\n\t\tn += 1 + l + sovNebula(uint64(l))\n\t}\n\treturn n\n}\n\nfunc sovNebula(x uint64) (n int) {\n\treturn (math_bits.Len64(x|1) + 6) / 7\n}\nfunc sozNebula(x uint64) (n int) {\n\treturn sovNebula(uint64((x << 1) ^ uint64((int64(x) >> 63))))\n}\nfunc (m *NebulaMeta) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: NebulaMeta: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: NebulaMeta: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Type\", wireType)\n\t\t\t}\n\t\t\tm.Type = 0\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tm.Type |= NebulaMeta_MessageType(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tcase 2:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Details\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthNebula\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthNebula\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif m.Details == nil {\n\t\t\t\tm.Details = &NebulaMetaDetails{}\n\t\t\t}\n\t\t\tif err := m.Details.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipNebula(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthNebula\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *NebulaMetaDetails) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: NebulaMetaDetails: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: NebulaMetaDetails: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field OldVpnAddr\", wireType)\n\t\t\t}\n\t\t\tm.OldVpnAddr = 0\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tm.OldVpnAddr |= uint32(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tcase 2:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field V4AddrPorts\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthNebula\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthNebula\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.V4AddrPorts = append(m.V4AddrPorts, &V4AddrPort{})\n\t\t\tif err := m.V4AddrPorts[len(m.V4AddrPorts)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tcase 3:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Counter\", wireType)\n\t\t\t}\n\t\t\tm.Counter = 0\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tm.Counter |= uint32(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tcase 4:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field V6AddrPorts\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthNebula\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthNebula\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.V6AddrPorts = append(m.V6AddrPorts, &V6AddrPort{})\n\t\t\tif err := m.V6AddrPorts[len(m.V6AddrPorts)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tcase 5:\n\t\t\tif wireType == 0 {\n\t\t\t\tvar v uint32\n\t\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\t\tif shift >= 64 {\n\t\t\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t\t\t}\n\t\t\t\t\tif iNdEx >= l {\n\t\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t\t}\n\t\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\t\tiNdEx++\n\t\t\t\t\tv |= uint32(b&0x7F) << shift\n\t\t\t\t\tif b < 0x80 {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tm.OldRelayVpnAddrs = append(m.OldRelayVpnAddrs, v)\n\t\t\t} else if wireType == 2 {\n\t\t\t\tvar packedLen int\n\t\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\t\tif shift >= 64 {\n\t\t\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t\t\t}\n\t\t\t\t\tif iNdEx >= l {\n\t\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t\t}\n\t\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\t\tiNdEx++\n\t\t\t\t\tpackedLen |= int(b&0x7F) << shift\n\t\t\t\t\tif b < 0x80 {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif packedLen < 0 {\n\t\t\t\t\treturn ErrInvalidLengthNebula\n\t\t\t\t}\n\t\t\t\tpostIndex := iNdEx + packedLen\n\t\t\t\tif postIndex < 0 {\n\t\t\t\t\treturn ErrInvalidLengthNebula\n\t\t\t\t}\n\t\t\t\tif postIndex > l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tvar elementCount int\n\t\t\t\tvar count int\n\t\t\t\tfor _, integer := range dAtA[iNdEx:postIndex] {\n\t\t\t\t\tif integer < 128 {\n\t\t\t\t\t\tcount++\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telementCount = count\n\t\t\t\tif elementCount != 0 && len(m.OldRelayVpnAddrs) == 0 {\n\t\t\t\t\tm.OldRelayVpnAddrs = make([]uint32, 0, elementCount)\n\t\t\t\t}\n\t\t\t\tfor iNdEx < postIndex {\n\t\t\t\t\tvar v uint32\n\t\t\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\t\t\tif shift >= 64 {\n\t\t\t\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif iNdEx >= l {\n\t\t\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t\t\t}\n\t\t\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\t\t\tiNdEx++\n\t\t\t\t\t\tv |= uint32(b&0x7F) << shift\n\t\t\t\t\t\tif b < 0x80 {\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tm.OldRelayVpnAddrs = append(m.OldRelayVpnAddrs, v)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field OldRelayVpnAddrs\", wireType)\n\t\t\t}\n\t\tcase 6:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field VpnAddr\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthNebula\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthNebula\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif m.VpnAddr == nil {\n\t\t\t\tm.VpnAddr = &Addr{}\n\t\t\t}\n\t\t\tif err := m.VpnAddr.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tcase 7:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field RelayVpnAddrs\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthNebula\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthNebula\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.RelayVpnAddrs = append(m.RelayVpnAddrs, &Addr{})\n\t\t\tif err := m.RelayVpnAddrs[len(m.RelayVpnAddrs)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipNebula(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthNebula\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *Addr) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: Addr: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: Addr: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Hi\", wireType)\n\t\t\t}\n\t\t\tm.Hi = 0\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tm.Hi |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tcase 2:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Lo\", wireType)\n\t\t\t}\n\t\t\tm.Lo = 0\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tm.Lo |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipNebula(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthNebula\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *V4AddrPort) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: V4AddrPort: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: V4AddrPort: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Addr\", wireType)\n\t\t\t}\n\t\t\tm.Addr = 0\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tm.Addr |= uint32(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tcase 2:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Port\", wireType)\n\t\t\t}\n\t\t\tm.Port = 0\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tm.Port |= uint32(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipNebula(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthNebula\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *V6AddrPort) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: V6AddrPort: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: V6AddrPort: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Hi\", wireType)\n\t\t\t}\n\t\t\tm.Hi = 0\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tm.Hi |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tcase 2:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Lo\", wireType)\n\t\t\t}\n\t\t\tm.Lo = 0\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tm.Lo |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tcase 3:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Port\", wireType)\n\t\t\t}\n\t\t\tm.Port = 0\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tm.Port |= uint32(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipNebula(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthNebula\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *NebulaPing) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: NebulaPing: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: NebulaPing: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Type\", wireType)\n\t\t\t}\n\t\t\tm.Type = 0\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tm.Type |= NebulaPing_MessageType(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tcase 2:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Time\", wireType)\n\t\t\t}\n\t\t\tm.Time = 0\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tm.Time |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipNebula(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthNebula\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *NebulaHandshake) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: NebulaHandshake: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: NebulaHandshake: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Details\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthNebula\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthNebula\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif m.Details == nil {\n\t\t\t\tm.Details = &NebulaHandshakeDetails{}\n\t\t\t}\n\t\t\tif err := m.Details.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tcase 2:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Hmac\", wireType)\n\t\t\t}\n\t\t\tvar byteLen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tbyteLen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif byteLen < 0 {\n\t\t\t\treturn ErrInvalidLengthNebula\n\t\t\t}\n\t\t\tpostIndex := iNdEx + byteLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthNebula\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Hmac = append(m.Hmac[:0], dAtA[iNdEx:postIndex]...)\n\t\t\tif m.Hmac == nil {\n\t\t\t\tm.Hmac = []byte{}\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipNebula(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthNebula\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *NebulaHandshakeDetails) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: NebulaHandshakeDetails: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: NebulaHandshakeDetails: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Cert\", wireType)\n\t\t\t}\n\t\t\tvar byteLen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tbyteLen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif byteLen < 0 {\n\t\t\t\treturn ErrInvalidLengthNebula\n\t\t\t}\n\t\t\tpostIndex := iNdEx + byteLen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthNebula\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tm.Cert = append(m.Cert[:0], dAtA[iNdEx:postIndex]...)\n\t\t\tif m.Cert == nil {\n\t\t\t\tm.Cert = []byte{}\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tcase 2:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field InitiatorIndex\", wireType)\n\t\t\t}\n\t\t\tm.InitiatorIndex = 0\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tm.InitiatorIndex |= uint32(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tcase 3:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field ResponderIndex\", wireType)\n\t\t\t}\n\t\t\tm.ResponderIndex = 0\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tm.ResponderIndex |= uint32(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tcase 4:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Cookie\", wireType)\n\t\t\t}\n\t\t\tm.Cookie = 0\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tm.Cookie |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tcase 5:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Time\", wireType)\n\t\t\t}\n\t\t\tm.Time = 0\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tm.Time |= uint64(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tcase 8:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field CertVersion\", wireType)\n\t\t\t}\n\t\t\tm.CertVersion = 0\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tm.CertVersion |= uint32(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipNebula(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthNebula\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc (m *NebulaControl) Unmarshal(dAtA []byte) error {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tfor iNdEx < l {\n\t\tpreIndex := iNdEx\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= uint64(b&0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfieldNum := int32(wire >> 3)\n\t\twireType := int(wire & 0x7)\n\t\tif wireType == 4 {\n\t\t\treturn fmt.Errorf(\"proto: NebulaControl: wiretype end group for non-group\")\n\t\t}\n\t\tif fieldNum <= 0 {\n\t\t\treturn fmt.Errorf(\"proto: NebulaControl: illegal tag %d (wire type %d)\", fieldNum, wire)\n\t\t}\n\t\tswitch fieldNum {\n\t\tcase 1:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field Type\", wireType)\n\t\t\t}\n\t\t\tm.Type = 0\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tm.Type |= NebulaControl_MessageType(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tcase 2:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field InitiatorRelayIndex\", wireType)\n\t\t\t}\n\t\t\tm.InitiatorRelayIndex = 0\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tm.InitiatorRelayIndex |= uint32(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tcase 3:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field ResponderRelayIndex\", wireType)\n\t\t\t}\n\t\t\tm.ResponderRelayIndex = 0\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tm.ResponderRelayIndex |= uint32(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tcase 4:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field OldRelayToAddr\", wireType)\n\t\t\t}\n\t\t\tm.OldRelayToAddr = 0\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tm.OldRelayToAddr |= uint32(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tcase 5:\n\t\t\tif wireType != 0 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field OldRelayFromAddr\", wireType)\n\t\t\t}\n\t\t\tm.OldRelayFromAddr = 0\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tm.OldRelayFromAddr |= uint32(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tcase 6:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field RelayToAddr\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthNebula\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthNebula\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif m.RelayToAddr == nil {\n\t\t\t\tm.RelayToAddr = &Addr{}\n\t\t\t}\n\t\t\tif err := m.RelayToAddr.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tcase 7:\n\t\t\tif wireType != 2 {\n\t\t\t\treturn fmt.Errorf(\"proto: wrong wireType = %d for field RelayFromAddr\", wireType)\n\t\t\t}\n\t\t\tvar msglen int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn ErrIntOverflowNebula\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tmsglen |= int(b&0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif msglen < 0 {\n\t\t\t\treturn ErrInvalidLengthNebula\n\t\t\t}\n\t\t\tpostIndex := iNdEx + msglen\n\t\t\tif postIndex < 0 {\n\t\t\t\treturn ErrInvalidLengthNebula\n\t\t\t}\n\t\t\tif postIndex > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tif m.RelayFromAddr == nil {\n\t\t\t\tm.RelayFromAddr = &Addr{}\n\t\t\t}\n\t\t\tif err := m.RelayFromAddr.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tiNdEx = postIndex\n\t\tdefault:\n\t\t\tiNdEx = preIndex\n\t\t\tskippy, err := skipNebula(dAtA[iNdEx:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif (skippy < 0) || (iNdEx+skippy) < 0 {\n\t\t\t\treturn ErrInvalidLengthNebula\n\t\t\t}\n\t\t\tif (iNdEx + skippy) > l {\n\t\t\t\treturn io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tiNdEx += skippy\n\t\t}\n\t}\n\n\tif iNdEx > l {\n\t\treturn io.ErrUnexpectedEOF\n\t}\n\treturn nil\n}\nfunc skipNebula(dAtA []byte) (n int, err error) {\n\tl := len(dAtA)\n\tiNdEx := 0\n\tdepth := 0\n\tfor iNdEx < l {\n\t\tvar wire uint64\n\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\tif shift >= 64 {\n\t\t\t\treturn 0, ErrIntOverflowNebula\n\t\t\t}\n\t\t\tif iNdEx >= l {\n\t\t\t\treturn 0, io.ErrUnexpectedEOF\n\t\t\t}\n\t\t\tb := dAtA[iNdEx]\n\t\t\tiNdEx++\n\t\t\twire |= (uint64(b) & 0x7F) << shift\n\t\t\tif b < 0x80 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\twireType := int(wire & 0x7)\n\t\tswitch wireType {\n\t\tcase 0:\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn 0, ErrIntOverflowNebula\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn 0, io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tiNdEx++\n\t\t\t\tif dAtA[iNdEx-1] < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tcase 1:\n\t\t\tiNdEx += 8\n\t\tcase 2:\n\t\t\tvar length int\n\t\t\tfor shift := uint(0); ; shift += 7 {\n\t\t\t\tif shift >= 64 {\n\t\t\t\t\treturn 0, ErrIntOverflowNebula\n\t\t\t\t}\n\t\t\t\tif iNdEx >= l {\n\t\t\t\t\treturn 0, io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tb := dAtA[iNdEx]\n\t\t\t\tiNdEx++\n\t\t\t\tlength |= (int(b) & 0x7F) << shift\n\t\t\t\tif b < 0x80 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif length < 0 {\n\t\t\t\treturn 0, ErrInvalidLengthNebula\n\t\t\t}\n\t\t\tiNdEx += length\n\t\tcase 3:\n\t\t\tdepth++\n\t\tcase 4:\n\t\t\tif depth == 0 {\n\t\t\t\treturn 0, ErrUnexpectedEndOfGroupNebula\n\t\t\t}\n\t\t\tdepth--\n\t\tcase 5:\n\t\t\tiNdEx += 4\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"proto: illegal wireType %d\", wireType)\n\t\t}\n\t\tif iNdEx < 0 {\n\t\t\treturn 0, ErrInvalidLengthNebula\n\t\t}\n\t\tif depth == 0 {\n\t\t\treturn iNdEx, nil\n\t\t}\n\t}\n\treturn 0, io.ErrUnexpectedEOF\n}\n\nvar (\n\tErrInvalidLengthNebula        = fmt.Errorf(\"proto: negative length found during unmarshaling\")\n\tErrIntOverflowNebula          = fmt.Errorf(\"proto: integer overflow\")\n\tErrUnexpectedEndOfGroupNebula = fmt.Errorf(\"proto: unexpected end of group\")\n)\n"
  },
  {
    "path": "nebula.proto",
    "content": "syntax = \"proto3\";\npackage nebula;\n\noption go_package = \"github.com/slackhq/nebula\";\n\nmessage NebulaMeta {\n  enum MessageType {\n    None = 0;\n    HostQuery = 1;\n    HostQueryReply = 2;\n    HostUpdateNotification = 3;\n    HostMovedNotification = 4;\n    HostPunchNotification = 5;\n    HostWhoami = 6;\n    HostWhoamiReply = 7;\n    PathCheck = 8;\n    PathCheckReply = 9;\n    HostUpdateNotificationAck = 10;\n  }\n\n  MessageType Type = 1;\n  NebulaMetaDetails Details = 2;\n}\n\nmessage NebulaMetaDetails {\n  uint32 OldVpnAddr = 1 [deprecated = true];\n  Addr VpnAddr = 6;\n\n  repeated uint32 OldRelayVpnAddrs = 5 [deprecated = true];\n  repeated Addr RelayVpnAddrs = 7;\n\n  repeated V4AddrPort V4AddrPorts = 2;\n  repeated V6AddrPort V6AddrPorts = 4;\n  uint32 counter = 3;\n}\n\nmessage Addr {\n  uint64 Hi = 1;\n  uint64 Lo = 2;\n}\n\nmessage V4AddrPort {\n  uint32 Addr = 1;\n  uint32 Port = 2;\n}\n\nmessage V6AddrPort {\n  uint64 Hi = 1;\n  uint64 Lo = 2;\n  uint32 Port = 3;\n}\n\nmessage NebulaPing {\n  enum MessageType {\n\t\tPing = 0;\n\t\tReply = 1;\n\t}\n\n\tMessageType Type = 1;\n\tuint64 Time = 2;\n}\n\nmessage NebulaHandshake {\n  NebulaHandshakeDetails Details = 1;\n  bytes Hmac = 2;\n}\n\nmessage NebulaHandshakeDetails {\n  bytes Cert = 1;\n  uint32 InitiatorIndex = 2;\n  uint32 ResponderIndex = 3;\n  uint64 Cookie = 4;\n  uint64 Time = 5;\n  uint32 CertVersion = 8;\n  // reserved for WIP multiport\n  reserved 6, 7;\n}\n\nmessage NebulaControl {\n  enum MessageType {\n    None = 0;\n    CreateRelayRequest = 1;\n    CreateRelayResponse = 2;\n  }\n  MessageType Type = 1;\n\n  uint32 InitiatorRelayIndex = 2;\n  uint32 ResponderRelayIndex = 3;\n\n  uint32 OldRelayToAddr = 4 [deprecated = true];\n  uint32 OldRelayFromAddr = 5 [deprecated = true];\n\n  Addr RelayToAddr = 6;\n  Addr RelayFromAddr = 7;\n}\n"
  },
  {
    "path": "noise.go",
    "content": "package nebula\n\nimport (\n\t\"crypto/cipher\"\n\t\"encoding/binary\"\n\t\"errors\"\n\n\t\"github.com/flynn/noise\"\n)\n\ntype endianness interface {\n\tPutUint64(b []byte, v uint64)\n}\n\nvar noiseEndianness endianness = binary.BigEndian\n\ntype NebulaCipherState struct {\n\tc noise.Cipher\n\t//k [32]byte\n\t//n uint64\n}\n\nfunc NewNebulaCipherState(s *noise.CipherState) *NebulaCipherState {\n\treturn &NebulaCipherState{c: s.Cipher()}\n\n}\n\n// EncryptDanger encrypts and authenticates a given payload.\n//\n// out is a destination slice to hold the output of the EncryptDanger operation.\n//   - ad is additional data, which will be authenticated and appended to out, but not encrypted.\n//   - plaintext is encrypted, authenticated and appended to out.\n//   - n is a nonce value which must never be re-used with this key.\n//   - nb is a buffer used for temporary storage in the implementation of this call, which should\n//     be re-used by callers to minimize garbage collection.\nfunc (s *NebulaCipherState) EncryptDanger(out, ad, plaintext []byte, n uint64, nb []byte) ([]byte, error) {\n\tif s != nil {\n\t\t// TODO: Is this okay now that we have made messageCounter atomic?\n\t\t// Alternative may be to split the counter space into ranges\n\t\t//if n <= s.n {\n\t\t//\treturn nil, errors.New(\"CRITICAL: a duplicate counter value was used\")\n\t\t//}\n\t\t//s.n = n\n\t\tnb[0] = 0\n\t\tnb[1] = 0\n\t\tnb[2] = 0\n\t\tnb[3] = 0\n\t\tnoiseEndianness.PutUint64(nb[4:], n)\n\t\tout = s.c.(cipher.AEAD).Seal(out, nb, plaintext, ad)\n\t\t//l.Debugf(\"Encryption: outlen: %d, nonce: %d, ad: %s, plainlen %d\", len(out), n, ad, len(plaintext))\n\t\treturn out, nil\n\t} else {\n\t\treturn nil, errors.New(\"no cipher state available to encrypt\")\n\t}\n}\n\nfunc (s *NebulaCipherState) DecryptDanger(out, ad, ciphertext []byte, n uint64, nb []byte) ([]byte, error) {\n\tif s != nil {\n\t\tnb[0] = 0\n\t\tnb[1] = 0\n\t\tnb[2] = 0\n\t\tnb[3] = 0\n\t\tnoiseEndianness.PutUint64(nb[4:], n)\n\t\treturn s.c.(cipher.AEAD).Open(out, nb, ciphertext, ad)\n\t} else {\n\t\treturn []byte{}, nil\n\t}\n}\n\nfunc (s *NebulaCipherState) Overhead() int {\n\tif s != nil {\n\t\treturn s.c.(cipher.AEAD).Overhead()\n\t}\n\treturn 0\n}\n"
  },
  {
    "path": "noiseutil/boring.go",
    "content": "//go:build boringcrypto\n// +build boringcrypto\n\npackage noiseutil\n\nimport (\n\t\"crypto/aes\"\n\t\"crypto/cipher\"\n\t\"encoding/binary\"\n\n\t// unsafe needed for go:linkname\n\t_ \"unsafe\"\n\n\t\"github.com/flynn/noise\"\n)\n\n// EncryptLockNeeded indicates if calls to Encrypt need a lock\n// This is true for boringcrypto because the Seal function verifies that the\n// nonce is strictly increasing.\nconst EncryptLockNeeded = true\n\n// NewGCMTLS is no longer exposed in go1.19+, so we need to link it in\n// See: https://github.com/golang/go/issues/56326\n//\n// NewGCMTLS is the internal method used with boringcrypto that provides a\n// validated mode of AES-GCM which enforces the nonce is strictly\n// monotonically increasing.  This is the TLS 1.2 specification for nonce\n// generation (which also matches the method used by the Noise Protocol)\n//\n// - https://github.com/golang/go/blob/go1.19/src/crypto/tls/cipher_suites.go#L520-L522\n// - https://github.com/golang/go/blob/go1.19/src/crypto/internal/boring/aes.go#L235-L237\n// - https://github.com/golang/go/blob/go1.19/src/crypto/internal/boring/aes.go#L250\n// - https://github.com/google/boringssl/blob/ae223d6138807a13006342edfeef32e813246b39/include/openssl/aead.h#L379-L381\n// - https://github.com/google/boringssl/blob/ae223d6138807a13006342edfeef32e813246b39/crypto/fipsmodule/cipher/e_aes.c#L1082-L1093\n//\n//go:linkname newGCMTLS crypto/internal/boring.NewGCMTLS\nfunc newGCMTLS(c cipher.Block) (cipher.AEAD, error)\n\ntype cipherFn struct {\n\tfn   func([32]byte) noise.Cipher\n\tname string\n}\n\nfunc (c cipherFn) Cipher(k [32]byte) noise.Cipher { return c.fn(k) }\nfunc (c cipherFn) CipherName() string             { return c.name }\n\n// CipherAESGCM is the AES256-GCM AEAD cipher (using NewGCMTLS when GoBoring is present)\nvar CipherAESGCM noise.CipherFunc = cipherFn{cipherAESGCMBoring, \"AESGCM\"}\n\nfunc cipherAESGCMBoring(k [32]byte) noise.Cipher {\n\tc, err := aes.NewCipher(k[:])\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tgcm, err := newGCMTLS(c)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn aeadCipher{\n\t\tgcm,\n\t\tfunc(n uint64) []byte {\n\t\t\tvar nonce [12]byte\n\t\t\tbinary.BigEndian.PutUint64(nonce[4:], n)\n\t\t\treturn nonce[:]\n\t\t},\n\t}\n}\n\ntype aeadCipher struct {\n\tcipher.AEAD\n\tnonce func(uint64) []byte\n}\n\nfunc (c aeadCipher) Encrypt(out []byte, n uint64, ad, plaintext []byte) []byte {\n\treturn c.Seal(out, c.nonce(n), plaintext, ad)\n}\n\nfunc (c aeadCipher) Decrypt(out []byte, n uint64, ad, ciphertext []byte) ([]byte, error) {\n\treturn c.Open(out, c.nonce(n), ciphertext, ad)\n}\n"
  },
  {
    "path": "noiseutil/boring_test.go",
    "content": "//go:build boringcrypto\n// +build boringcrypto\n\npackage noiseutil\n\nimport (\n\t\"crypto/boring\"\n\t\"encoding/hex\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestEncryptLockNeeded(t *testing.T) {\n\tassert.True(t, EncryptLockNeeded)\n}\n\n// Ensure NewGCMTLS validates the nonce is non-repeating\nfunc TestNewGCMTLS(t *testing.T) {\n\tassert.True(t, boring.Enabled())\n\n\t// Test Case 16 from GCM Spec:\n\t//  - (now dead link): http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-spec.pdf\n\t//  - as listed in boringssl tests: https://github.com/google/boringssl/blob/fips-20220613/crypto/cipher_extra/test/cipher_tests.txt#L412-L418\n\tkey, _ := hex.DecodeString(\"feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308\")\n\tiv, _ := hex.DecodeString(\"cafebabefacedbaddecaf888\")\n\tplaintext, _ := hex.DecodeString(\"d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39\")\n\taad, _ := hex.DecodeString(\"feedfacedeadbeeffeedfacedeadbeefabaddad2\")\n\texpected, _ := hex.DecodeString(\"522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662\")\n\texpectedTag, _ := hex.DecodeString(\"76fc6ece0f4e1768cddf8853bb2d551b\")\n\n\texpected = append(expected, expectedTag...)\n\n\tvar keyArray [32]byte\n\tcopy(keyArray[:], key)\n\tc := CipherAESGCM.Cipher(keyArray)\n\taead := c.(aeadCipher).AEAD\n\n\tdst := aead.Seal([]byte{}, iv, plaintext, aad)\n\tassert.Equal(t, expected, dst)\n\n\t// We expect this to fail since we are re-encrypting with a repeat IV\n\tassert.PanicsWithError(t, \"boringcrypto: EVP_AEAD_CTX_seal failed\", func() {\n\t\tdst = aead.Seal([]byte{}, iv, plaintext, aad)\n\t})\n}\n"
  },
  {
    "path": "noiseutil/nist.go",
    "content": "package noiseutil\n\nimport (\n\t\"crypto/ecdh\"\n\t\"crypto/rand\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/flynn/noise\"\n)\n\n// DHP256 is the NIST P-256 ECDH function\nvar DHP256 noise.DHFunc = newNISTCurve(\"P256\", ecdh.P256(), 32)\n\ntype nistCurve struct {\n\tname   string\n\tcurve  ecdh.Curve\n\tdhLen  int\n\tpubLen int\n}\n\nfunc newNISTCurve(name string, curve ecdh.Curve, byteLen int) nistCurve {\n\treturn nistCurve{\n\t\tname:  name,\n\t\tcurve: curve,\n\t\tdhLen: byteLen,\n\t\t// Standard uncompressed format, type (1 byte) plus both coordinates\n\t\tpubLen: 1 + 2*byteLen,\n\t}\n}\n\nfunc (c nistCurve) GenerateKeypair(rng io.Reader) (noise.DHKey, error) {\n\tif rng == nil {\n\t\trng = rand.Reader\n\t}\n\tprivkey, err := c.curve.GenerateKey(rng)\n\tif err != nil {\n\t\treturn noise.DHKey{}, err\n\t}\n\tpubkey := privkey.PublicKey()\n\treturn noise.DHKey{Private: privkey.Bytes(), Public: pubkey.Bytes()}, nil\n}\n\nfunc (c nistCurve) DH(privkey, pubkey []byte) ([]byte, error) {\n\tecdhPubKey, err := c.curve.NewPublicKey(pubkey)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to unmarshal pubkey: %w\", err)\n\t}\n\tecdhPrivKey, err := c.curve.NewPrivateKey(privkey)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to unmarshal private key: %w\", err)\n\t}\n\n\treturn ecdhPrivKey.ECDH(ecdhPubKey)\n}\n\nfunc (c nistCurve) DHLen() int {\n\t// NOTE: Noise Protocol specifies \"DHLen\" to represent two things:\n\t// - The size of the public key\n\t// - The return size of the DH() function\n\t// But for standard NIST ECDH, the sizes of these are different.\n\t// Luckily, the flynn/noise library actually only uses this DHLen()\n\t// value to represent the public key size, so that is what we are\n\t// returning here. The length of the DH() return bytes are unaffected by\n\t// this value here.\n\treturn c.pubLen\n}\nfunc (c nistCurve) DHName() string { return c.name }\n"
  },
  {
    "path": "noiseutil/notboring.go",
    "content": "//go:build !boringcrypto\n// +build !boringcrypto\n\npackage noiseutil\n\nimport (\n\t\"github.com/flynn/noise\"\n)\n\n// EncryptLockNeeded indicates if calls to Encrypt need a lock\nconst EncryptLockNeeded = false\n\n// CipherAESGCM is the standard noise.CipherAESGCM when boringcrypto is not enabled\nvar CipherAESGCM noise.CipherFunc = noise.CipherAESGCM\n"
  },
  {
    "path": "noiseutil/notboring_test.go",
    "content": "//go:build !boringcrypto\n// +build !boringcrypto\n\npackage noiseutil\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestEncryptLockNeeded(t *testing.T) {\n\tassert.False(t, EncryptLockNeeded)\n}\n"
  },
  {
    "path": "noiseutil/pkcs11.go",
    "content": "package noiseutil\n\nimport (\n\t\"crypto/ecdh\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/slackhq/nebula/pkclient\"\n\n\t\"github.com/flynn/noise\"\n)\n\n// DHP256PKCS11 is the NIST P-256 ECDH function\nvar DHP256PKCS11 noise.DHFunc = newNISTP11Curve(\"P256\", ecdh.P256(), 32)\n\ntype nistP11Curve struct {\n\tnistCurve\n}\n\nfunc newNISTP11Curve(name string, curve ecdh.Curve, byteLen int) nistP11Curve {\n\treturn nistP11Curve{\n\t\tnewNISTCurve(name, curve, byteLen),\n\t}\n}\n\nfunc (c nistP11Curve) DH(privkey, pubkey []byte) ([]byte, error) {\n\t//for this function \"privkey\" is actually a pkcs11 URI\n\tpkStr := string(privkey)\n\n\t//to set up a handshake, we need to also do non-pkcs11-DH. Handle that here.\n\tif !strings.HasPrefix(pkStr, \"pkcs11:\") {\n\t\treturn DHP256.DH(privkey, pubkey)\n\t}\n\tecdhPubKey, err := c.curve.NewPublicKey(pubkey)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to unmarshal pubkey: %w\", err)\n\t}\n\n\t//this is not the most performant way to do this (a long-lived client would be better)\n\t//but, it works, and helps avoid problems with stale sessions and HSMs used by multiple users.\n\tclient, err := pkclient.FromUrl(pkStr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer func(client *pkclient.PKClient) {\n\t\t_ = client.Close()\n\t}(client)\n\n\treturn client.DeriveNoise(ecdhPubKey.Bytes())\n}\n"
  },
  {
    "path": "notboring.go",
    "content": "//go:build !boringcrypto\n\npackage nebula\n\nvar boringEnabled = func() bool { return false }\n"
  },
  {
    "path": "outside.go",
    "content": "package nebula\n\nimport (\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"net/netip\"\n\t\"time\"\n\n\t\"github.com/google/gopacket/layers\"\n\t\"golang.org/x/net/ipv6\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/firewall\"\n\t\"github.com/slackhq/nebula/header\"\n\t\"golang.org/x/net/ipv4\"\n)\n\nconst (\n\tminFwPacketLen = 4\n)\n\nfunc (f *Interface) readOutsidePackets(via ViaSender, out []byte, packet []byte, h *header.H, fwPacket *firewall.Packet, lhf *LightHouseHandler, nb []byte, q int, localCache firewall.ConntrackCache) {\n\terr := h.Parse(packet)\n\tif err != nil {\n\t\t// Hole punch packets are 0 or 1 byte big, so lets ignore printing those errors\n\t\tif len(packet) > 1 {\n\t\t\tf.l.WithField(\"packet\", packet).Infof(\"Error while parsing inbound packet from %s: %s\", via, err)\n\t\t}\n\t\treturn\n\t}\n\n\t//l.Error(\"in packet \", header, packet[HeaderLen:])\n\tif !via.IsRelayed {\n\t\tif f.myVpnNetworksTable.Contains(via.UdpAddr.Addr()) {\n\t\t\tif f.l.Level >= logrus.DebugLevel {\n\t\t\t\tf.l.WithField(\"from\", via).Debug(\"Refusing to process double encrypted packet\")\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\tvar hostinfo *HostInfo\n\t// verify if we've seen this index before, otherwise respond to the handshake initiation\n\tif h.Type == header.Message && h.Subtype == header.MessageRelay {\n\t\thostinfo = f.hostMap.QueryRelayIndex(h.RemoteIndex)\n\t} else {\n\t\thostinfo = f.hostMap.QueryIndex(h.RemoteIndex)\n\t}\n\n\tvar ci *ConnectionState\n\tif hostinfo != nil {\n\t\tci = hostinfo.ConnectionState\n\t}\n\n\tswitch h.Type {\n\tcase header.Message:\n\t\tif !f.handleEncrypted(ci, via, h) {\n\t\t\treturn\n\t\t}\n\n\t\tswitch h.Subtype {\n\t\tcase header.MessageNone:\n\t\t\tif !f.decryptToTun(hostinfo, h.MessageCounter, out, packet, fwPacket, nb, q, localCache) {\n\t\t\t\treturn\n\t\t\t}\n\t\tcase header.MessageRelay:\n\t\t\t// The entire body is sent as AD, not encrypted.\n\t\t\t// The packet consists of a 16-byte parsed Nebula header, Associated Data-protected payload, and a trailing 16-byte AEAD signature value.\n\t\t\t// The packet is guaranteed to be at least 16 bytes at this point, b/c it got past the h.Parse() call above. If it's\n\t\t\t// otherwise malformed (meaning, there is no trailing 16 byte AEAD value), then this will result in at worst a 0-length slice\n\t\t\t// which will gracefully fail in the DecryptDanger call.\n\t\t\tsignedPayload := packet[:len(packet)-hostinfo.ConnectionState.dKey.Overhead()]\n\t\t\tsignatureValue := packet[len(packet)-hostinfo.ConnectionState.dKey.Overhead():]\n\t\t\tout, err = hostinfo.ConnectionState.dKey.DecryptDanger(out, signedPayload, signatureValue, h.MessageCounter, nb)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// Successfully validated the thing. Get rid of the Relay header.\n\t\t\tsignedPayload = signedPayload[header.Len:]\n\t\t\t// Pull the Roaming parts up here, and return in all call paths.\n\t\t\tf.handleHostRoaming(hostinfo, via)\n\t\t\t// Track usage of both the HostInfo and the Relay for the received & authenticated packet\n\t\t\tf.connectionManager.In(hostinfo)\n\t\t\tf.connectionManager.RelayUsed(h.RemoteIndex)\n\n\t\t\trelay, ok := hostinfo.relayState.QueryRelayForByIdx(h.RemoteIndex)\n\t\t\tif !ok {\n\t\t\t\t// The only way this happens is if hostmap has an index to the correct HostInfo, but the HostInfo is missing\n\t\t\t\t// its internal mapping. This should never happen.\n\t\t\t\thostinfo.logger(f.l).WithFields(logrus.Fields{\"vpnAddrs\": hostinfo.vpnAddrs, \"remoteIndex\": h.RemoteIndex}).Error(\"HostInfo missing remote relay index\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tswitch relay.Type {\n\t\t\tcase TerminalType:\n\t\t\t\t// If I am the target of this relay, process the unwrapped packet\n\t\t\t\t// From this recursive point, all these variables are 'burned'. We shouldn't rely on them again.\n\t\t\t\tvia = ViaSender{\n\t\t\t\t\tUdpAddr:   via.UdpAddr,\n\t\t\t\t\trelayHI:   hostinfo,\n\t\t\t\t\tremoteIdx: relay.RemoteIndex,\n\t\t\t\t\trelay:     relay,\n\t\t\t\t\tIsRelayed: true,\n\t\t\t\t}\n\t\t\t\tf.readOutsidePackets(via, out[:0], signedPayload, h, fwPacket, lhf, nb, q, localCache)\n\t\t\t\treturn\n\t\t\tcase ForwardingType:\n\t\t\t\t// Find the target HostInfo relay object\n\t\t\t\ttargetHI, targetRelay, err := f.hostMap.QueryVpnAddrsRelayFor(hostinfo.vpnAddrs, relay.PeerAddr)\n\t\t\t\tif err != nil {\n\t\t\t\t\thostinfo.logger(f.l).WithField(\"relayTo\", relay.PeerAddr).WithError(err).WithField(\"hostinfo.vpnAddrs\", hostinfo.vpnAddrs).Info(\"Failed to find target host info by ip\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t// If that relay is Established, forward the payload through it\n\t\t\t\tif targetRelay.State == Established {\n\t\t\t\t\tswitch targetRelay.Type {\n\t\t\t\t\tcase ForwardingType:\n\t\t\t\t\t\t// Forward this packet through the relay tunnel\n\t\t\t\t\t\t// Find the target HostInfo\n\t\t\t\t\t\tf.SendVia(targetHI, targetRelay, signedPayload, nb, out, false)\n\t\t\t\t\t\treturn\n\t\t\t\t\tcase TerminalType:\n\t\t\t\t\t\thostinfo.logger(f.l).Error(\"Unexpected Relay Type of Terminal\")\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\thostinfo.logger(f.l).WithFields(logrus.Fields{\"relayTo\": relay.PeerAddr, \"relayFrom\": hostinfo.vpnAddrs[0], \"targetRelayState\": targetRelay.State}).Info(\"Unexpected target relay state\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase header.LightHouse:\n\t\tf.messageMetrics.Rx(h.Type, h.Subtype, 1)\n\t\tif !f.handleEncrypted(ci, via, h) {\n\t\t\treturn\n\t\t}\n\n\t\td, err := f.decrypt(hostinfo, h.MessageCounter, out, packet, h, nb)\n\t\tif err != nil {\n\t\t\thostinfo.logger(f.l).WithError(err).WithField(\"from\", via).\n\t\t\t\tWithField(\"packet\", packet).\n\t\t\t\tError(\"Failed to decrypt lighthouse packet\")\n\t\t\treturn\n\t\t}\n\n\t\t//TODO: assert via is not relayed\n\t\tlhf.HandleRequest(via.UdpAddr, hostinfo.vpnAddrs, d, f)\n\n\t\t// Fallthrough to the bottom to record incoming traffic\n\n\tcase header.Test:\n\t\tf.messageMetrics.Rx(h.Type, h.Subtype, 1)\n\t\tif !f.handleEncrypted(ci, via, h) {\n\t\t\treturn\n\t\t}\n\n\t\td, err := f.decrypt(hostinfo, h.MessageCounter, out, packet, h, nb)\n\t\tif err != nil {\n\t\t\thostinfo.logger(f.l).WithError(err).WithField(\"from\", via).\n\t\t\t\tWithField(\"packet\", packet).\n\t\t\t\tError(\"Failed to decrypt test packet\")\n\t\t\treturn\n\t\t}\n\n\t\tif h.Subtype == header.TestRequest {\n\t\t\t// This testRequest might be from TryPromoteBest, so we should roam\n\t\t\t// to the new IP address before responding\n\t\t\tf.handleHostRoaming(hostinfo, via)\n\t\t\tf.send(header.Test, header.TestReply, ci, hostinfo, d, nb, out)\n\t\t}\n\n\t\t// Fallthrough to the bottom to record incoming traffic\n\n\t\t// Non encrypted messages below here, they should not fall through to avoid tracking incoming traffic since they\n\t\t// are unauthenticated\n\n\tcase header.Handshake:\n\t\tf.messageMetrics.Rx(h.Type, h.Subtype, 1)\n\t\tf.handshakeManager.HandleIncoming(via, packet, h)\n\t\treturn\n\n\tcase header.RecvError:\n\t\tf.messageMetrics.Rx(h.Type, h.Subtype, 1)\n\t\tf.handleRecvError(via.UdpAddr, h)\n\t\treturn\n\n\tcase header.CloseTunnel:\n\t\tf.messageMetrics.Rx(h.Type, h.Subtype, 1)\n\t\tif !f.handleEncrypted(ci, via, h) {\n\t\t\treturn\n\t\t}\n\n\t\thostinfo.logger(f.l).WithField(\"from\", via).\n\t\t\tInfo(\"Close tunnel received, tearing down.\")\n\n\t\tf.closeTunnel(hostinfo)\n\t\treturn\n\n\tcase header.Control:\n\t\tif !f.handleEncrypted(ci, via, h) {\n\t\t\treturn\n\t\t}\n\n\t\td, err := f.decrypt(hostinfo, h.MessageCounter, out, packet, h, nb)\n\t\tif err != nil {\n\t\t\thostinfo.logger(f.l).WithError(err).WithField(\"from\", via).\n\t\t\t\tWithField(\"packet\", packet).\n\t\t\t\tError(\"Failed to decrypt Control packet\")\n\t\t\treturn\n\t\t}\n\n\t\tf.relayManager.HandleControlMsg(hostinfo, d, f)\n\n\tdefault:\n\t\tf.messageMetrics.Rx(h.Type, h.Subtype, 1)\n\t\thostinfo.logger(f.l).Debugf(\"Unexpected packet received from %s\", via)\n\t\treturn\n\t}\n\n\tf.handleHostRoaming(hostinfo, via)\n\n\tf.connectionManager.In(hostinfo)\n}\n\n// closeTunnel closes a tunnel locally, it does not send a closeTunnel packet to the remote\nfunc (f *Interface) closeTunnel(hostInfo *HostInfo) {\n\tfinal := f.hostMap.DeleteHostInfo(hostInfo)\n\tif final {\n\t\t// We no longer have any tunnels with this vpn addr, clear learned lighthouse state to lower memory usage\n\t\tf.lightHouse.DeleteVpnAddrs(hostInfo.vpnAddrs)\n\t}\n}\n\n// sendCloseTunnel is a helper function to send a proper close tunnel packet to a remote\nfunc (f *Interface) sendCloseTunnel(h *HostInfo) {\n\tf.send(header.CloseTunnel, 0, h.ConnectionState, h, []byte{}, make([]byte, 12, 12), make([]byte, mtu))\n}\n\nfunc (f *Interface) handleHostRoaming(hostinfo *HostInfo, via ViaSender) {\n\tif !via.IsRelayed && hostinfo.remote != via.UdpAddr {\n\t\tif !f.lightHouse.GetRemoteAllowList().AllowAll(hostinfo.vpnAddrs, via.UdpAddr.Addr()) {\n\t\t\thostinfo.logger(f.l).WithField(\"newAddr\", via.UdpAddr).Debug(\"lighthouse.remote_allow_list denied roaming\")\n\t\t\treturn\n\t\t}\n\n\t\tif !hostinfo.lastRoam.IsZero() && via.UdpAddr == hostinfo.lastRoamRemote && time.Since(hostinfo.lastRoam) < RoamingSuppressSeconds*time.Second {\n\t\t\tif f.l.Level >= logrus.DebugLevel {\n\t\t\t\thostinfo.logger(f.l).WithField(\"udpAddr\", hostinfo.remote).WithField(\"newAddr\", via.UdpAddr).\n\t\t\t\t\tDebugf(\"Suppressing roam back to previous remote for %d seconds\", RoamingSuppressSeconds)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\thostinfo.logger(f.l).WithField(\"udpAddr\", hostinfo.remote).WithField(\"newAddr\", via.UdpAddr).\n\t\t\tInfo(\"Host roamed to new udp ip/port.\")\n\t\thostinfo.lastRoam = time.Now()\n\t\thostinfo.lastRoamRemote = hostinfo.remote\n\t\thostinfo.SetRemote(via.UdpAddr)\n\t}\n\n}\n\n// handleEncrypted returns true if a packet should be processed, false otherwise\nfunc (f *Interface) handleEncrypted(ci *ConnectionState, via ViaSender, h *header.H) bool {\n\t// If connectionstate does not exist, send a recv error, if possible, to encourage a fast reconnect\n\tif ci == nil {\n\t\tif !via.IsRelayed {\n\t\t\tf.maybeSendRecvError(via.UdpAddr, h.RemoteIndex)\n\t\t}\n\t\treturn false\n\t}\n\t// If the window check fails, refuse to process the packet, but don't send a recv error\n\tif !ci.window.Check(f.l, h.MessageCounter) {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\nvar (\n\tErrPacketTooShort          = errors.New(\"packet is too short\")\n\tErrUnknownIPVersion        = errors.New(\"packet is an unknown ip version\")\n\tErrIPv4InvalidHeaderLength = errors.New(\"invalid ipv4 header length\")\n\tErrIPv4PacketTooShort      = errors.New(\"ipv4 packet is too short\")\n\tErrIPv6PacketTooShort      = errors.New(\"ipv6 packet is too short\")\n\tErrIPv6CouldNotFindPayload = errors.New(\"could not find payload in ipv6 packet\")\n)\n\n// newPacket validates and parses the interesting bits for the firewall out of the ip and sub protocol headers\nfunc newPacket(data []byte, incoming bool, fp *firewall.Packet) error {\n\tif len(data) < 1 {\n\t\treturn ErrPacketTooShort\n\t}\n\n\tversion := int((data[0] >> 4) & 0x0f)\n\tswitch version {\n\tcase ipv4.Version:\n\t\treturn parseV4(data, incoming, fp)\n\tcase ipv6.Version:\n\t\treturn parseV6(data, incoming, fp)\n\t}\n\treturn ErrUnknownIPVersion\n}\n\nfunc parseV6(data []byte, incoming bool, fp *firewall.Packet) error {\n\tdataLen := len(data)\n\tif dataLen < ipv6.HeaderLen {\n\t\treturn ErrIPv6PacketTooShort\n\t}\n\n\tif incoming {\n\t\tfp.RemoteAddr, _ = netip.AddrFromSlice(data[8:24])\n\t\tfp.LocalAddr, _ = netip.AddrFromSlice(data[24:40])\n\t} else {\n\t\tfp.LocalAddr, _ = netip.AddrFromSlice(data[8:24])\n\t\tfp.RemoteAddr, _ = netip.AddrFromSlice(data[24:40])\n\t}\n\n\tprotoAt := 6             // NextHeader is at 6 bytes into the ipv6 header\n\toffset := ipv6.HeaderLen // Start at the end of the ipv6 header\n\tnext := 0\n\tfor {\n\t\tif protoAt >= dataLen {\n\t\t\tbreak\n\t\t}\n\t\tproto := layers.IPProtocol(data[protoAt])\n\n\t\tswitch proto {\n\t\tcase layers.IPProtocolESP, layers.IPProtocolNoNextHeader:\n\t\t\tfp.Protocol = uint8(proto)\n\t\t\tfp.RemotePort = 0\n\t\t\tfp.LocalPort = 0\n\t\t\tfp.Fragment = false\n\t\t\treturn nil\n\n\t\tcase layers.IPProtocolICMPv6:\n\t\t\tif dataLen < offset+6 {\n\t\t\t\treturn ErrIPv6PacketTooShort\n\t\t\t}\n\t\t\tfp.Protocol = uint8(proto)\n\t\t\tfp.LocalPort = 0 //incoming vs outgoing doesn't matter for icmpv6\n\t\t\ticmptype := data[offset+1]\n\t\t\tswitch icmptype {\n\t\t\tcase layers.ICMPv6TypeEchoRequest, layers.ICMPv6TypeEchoReply:\n\t\t\t\tfp.RemotePort = binary.BigEndian.Uint16(data[offset+4 : offset+6]) //identifier\n\t\t\tdefault:\n\t\t\t\tfp.RemotePort = 0\n\t\t\t}\n\t\t\tfp.Fragment = false\n\t\t\treturn nil\n\n\t\tcase layers.IPProtocolTCP, layers.IPProtocolUDP:\n\t\t\tif dataLen < offset+4 {\n\t\t\t\treturn ErrIPv6PacketTooShort\n\t\t\t}\n\n\t\t\tfp.Protocol = uint8(proto)\n\t\t\tif incoming {\n\t\t\t\tfp.RemotePort = binary.BigEndian.Uint16(data[offset : offset+2])\n\t\t\t\tfp.LocalPort = binary.BigEndian.Uint16(data[offset+2 : offset+4])\n\t\t\t} else {\n\t\t\t\tfp.LocalPort = binary.BigEndian.Uint16(data[offset : offset+2])\n\t\t\t\tfp.RemotePort = binary.BigEndian.Uint16(data[offset+2 : offset+4])\n\t\t\t}\n\n\t\t\tfp.Fragment = false\n\t\t\treturn nil\n\n\t\tcase layers.IPProtocolIPv6Fragment:\n\t\t\t// Fragment header is 8 bytes, need at least offset+4 to read the offset field\n\t\t\tif dataLen < offset+8 {\n\t\t\t\treturn ErrIPv6PacketTooShort\n\t\t\t}\n\n\t\t\t// Check if this is the first fragment\n\t\t\tfragmentOffset := binary.BigEndian.Uint16(data[offset+2:offset+4]) &^ uint16(0x7) // Remove the reserved and M flag bits\n\t\t\tif fragmentOffset != 0 {\n\t\t\t\t// Non-first fragment, use what we have now and stop processing\n\t\t\t\tfp.Protocol = data[offset]\n\t\t\t\tfp.Fragment = true\n\t\t\t\tfp.RemotePort = 0\n\t\t\t\tfp.LocalPort = 0\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\t// The next loop should be the transport layer since we are the first fragment\n\t\t\tnext = 8 // Fragment headers are always 8 bytes\n\n\t\tcase layers.IPProtocolAH:\n\t\t\t// Auth headers, used by IPSec, have a different meaning for header length\n\t\t\tif dataLen <= offset+1 {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tnext = int(data[offset+1]+2) << 2\n\n\t\tdefault:\n\t\t\t// Normal ipv6 header length processing\n\t\t\tif dataLen <= offset+1 {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tnext = int(data[offset+1]+1) << 3\n\t\t}\n\n\t\tif next <= 0 {\n\t\t\t// Safety check, each ipv6 header has to be at least 8 bytes\n\t\t\tnext = 8\n\t\t}\n\n\t\tprotoAt = offset\n\t\toffset = offset + next\n\t}\n\n\treturn ErrIPv6CouldNotFindPayload\n}\n\nfunc parseV4(data []byte, incoming bool, fp *firewall.Packet) error {\n\t// Do we at least have an ipv4 header worth of data?\n\tif len(data) < ipv4.HeaderLen {\n\t\treturn ErrIPv4PacketTooShort\n\t}\n\n\t// Adjust our start position based on the advertised ip header length\n\tihl := int(data[0]&0x0f) << 2\n\n\t// Well-formed ip header length?\n\tif ihl < ipv4.HeaderLen {\n\t\treturn ErrIPv4InvalidHeaderLength\n\t}\n\n\t// Check if this is the second or further fragment of a fragmented packet.\n\tflagsfrags := binary.BigEndian.Uint16(data[6:8])\n\tfp.Fragment = (flagsfrags & 0x1FFF) != 0\n\n\t// Firewall handles protocol checks\n\tfp.Protocol = data[9]\n\n\t// Accounting for a variable header length, do we have enough data for our src/dst tuples?\n\tminLen := ihl\n\tif !fp.Fragment {\n\t\tif fp.Protocol == firewall.ProtoICMP {\n\t\t\tminLen += minFwPacketLen + 2\n\t\t} else {\n\t\t\tminLen += minFwPacketLen\n\t\t}\n\t}\n\n\tif len(data) < minLen {\n\t\treturn ErrIPv4InvalidHeaderLength\n\t}\n\n\tif incoming { // Firewall packets are locally oriented\n\t\tfp.RemoteAddr, _ = netip.AddrFromSlice(data[12:16])\n\t\tfp.LocalAddr, _ = netip.AddrFromSlice(data[16:20])\n\t} else {\n\t\tfp.LocalAddr, _ = netip.AddrFromSlice(data[12:16])\n\t\tfp.RemoteAddr, _ = netip.AddrFromSlice(data[16:20])\n\t}\n\n\tif fp.Fragment {\n\t\tfp.RemotePort = 0\n\t\tfp.LocalPort = 0\n\t} else if fp.Protocol == firewall.ProtoICMP { //note that orientation doesn't matter on ICMP\n\t\tfp.RemotePort = binary.BigEndian.Uint16(data[ihl+4 : ihl+6]) //identifier\n\t\tfp.LocalPort = 0                                             //code would be uint16(data[ihl+1])\n\t} else if incoming {\n\t\tfp.RemotePort = binary.BigEndian.Uint16(data[ihl : ihl+2])  //src port\n\t\tfp.LocalPort = binary.BigEndian.Uint16(data[ihl+2 : ihl+4]) //dst port\n\t} else {\n\t\tfp.LocalPort = binary.BigEndian.Uint16(data[ihl : ihl+2])    //src port\n\t\tfp.RemotePort = binary.BigEndian.Uint16(data[ihl+2 : ihl+4]) //dst port\n\t}\n\n\treturn nil\n}\n\nfunc (f *Interface) decrypt(hostinfo *HostInfo, mc uint64, out []byte, packet []byte, h *header.H, nb []byte) ([]byte, error) {\n\tvar err error\n\tout, err = hostinfo.ConnectionState.dKey.DecryptDanger(out, packet[:header.Len], packet[header.Len:], mc, nb)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif !hostinfo.ConnectionState.window.Update(f.l, mc) {\n\t\thostinfo.logger(f.l).WithField(\"header\", h).\n\t\t\tDebugln(\"dropping out of window packet\")\n\t\treturn nil, errors.New(\"out of window packet\")\n\t}\n\n\treturn out, nil\n}\n\nfunc (f *Interface) decryptToTun(hostinfo *HostInfo, messageCounter uint64, out []byte, packet []byte, fwPacket *firewall.Packet, nb []byte, q int, localCache firewall.ConntrackCache) bool {\n\tvar err error\n\n\tout, err = hostinfo.ConnectionState.dKey.DecryptDanger(out, packet[:header.Len], packet[header.Len:], messageCounter, nb)\n\tif err != nil {\n\t\thostinfo.logger(f.l).WithError(err).Error(\"Failed to decrypt packet\")\n\t\treturn false\n\t}\n\n\terr = newPacket(out, true, fwPacket)\n\tif err != nil {\n\t\thostinfo.logger(f.l).WithError(err).WithField(\"packet\", out).\n\t\t\tWarnf(\"Error while validating inbound packet\")\n\t\treturn false\n\t}\n\n\tif !hostinfo.ConnectionState.window.Update(f.l, messageCounter) {\n\t\thostinfo.logger(f.l).WithField(\"fwPacket\", fwPacket).\n\t\t\tDebugln(\"dropping out of window packet\")\n\t\treturn false\n\t}\n\n\tdropReason := f.firewall.Drop(*fwPacket, true, hostinfo, f.pki.GetCAPool(), localCache)\n\tif dropReason != nil {\n\t\t// NOTE: We give `packet` as the `out` here since we already decrypted from it and we don't need it anymore\n\t\t// This gives us a buffer to build the reject packet in\n\t\tf.rejectOutside(out, hostinfo.ConnectionState, hostinfo, nb, packet, q)\n\t\tif f.l.Level >= logrus.DebugLevel {\n\t\t\thostinfo.logger(f.l).WithField(\"fwPacket\", fwPacket).\n\t\t\t\tWithField(\"reason\", dropReason).\n\t\t\t\tDebugln(\"dropping inbound packet\")\n\t\t}\n\t\treturn false\n\t}\n\n\tf.connectionManager.In(hostinfo)\n\t_, err = f.readers[q].Write(out)\n\tif err != nil {\n\t\tf.l.WithError(err).Error(\"Failed to write to tun\")\n\t}\n\treturn true\n}\n\nfunc (f *Interface) maybeSendRecvError(endpoint netip.AddrPort, index uint32) {\n\tif f.sendRecvErrorConfig.ShouldRecvError(endpoint) {\n\t\tf.sendRecvError(endpoint, index)\n\t}\n}\n\nfunc (f *Interface) sendRecvError(endpoint netip.AddrPort, index uint32) {\n\tf.messageMetrics.Tx(header.RecvError, 0, 1)\n\n\tb := header.Encode(make([]byte, header.Len), header.Version, header.RecvError, 0, index, 0)\n\t_ = f.outside.WriteTo(b, endpoint)\n\tif f.l.Level >= logrus.DebugLevel {\n\t\tf.l.WithField(\"index\", index).\n\t\t\tWithField(\"udpAddr\", endpoint).\n\t\t\tDebug(\"Recv error sent\")\n\t}\n}\n\nfunc (f *Interface) handleRecvError(addr netip.AddrPort, h *header.H) {\n\tif !f.acceptRecvErrorConfig.ShouldRecvError(addr) {\n\t\tf.l.WithField(\"index\", h.RemoteIndex).\n\t\t\tWithField(\"udpAddr\", addr).\n\t\t\tDebug(\"Recv error received, ignoring\")\n\t\treturn\n\t}\n\n\tif f.l.Level >= logrus.DebugLevel {\n\t\tf.l.WithField(\"index\", h.RemoteIndex).\n\t\t\tWithField(\"udpAddr\", addr).\n\t\t\tDebug(\"Recv error received\")\n\t}\n\n\thostinfo := f.hostMap.QueryReverseIndex(h.RemoteIndex)\n\tif hostinfo == nil {\n\t\tf.l.WithField(\"remoteIndex\", h.RemoteIndex).Debugln(\"Did not find remote index in main hostmap\")\n\t\treturn\n\t}\n\n\tif hostinfo.remote.IsValid() && hostinfo.remote != addr {\n\t\tf.l.Infoln(\"Someone spoofing recv_errors? \", addr, hostinfo.remote)\n\t\treturn\n\t}\n\n\tf.closeTunnel(hostinfo)\n\t// We also delete it from pending hostmap to allow for fast reconnect.\n\tf.handshakeManager.DeleteHostInfo(hostinfo)\n}\n"
  },
  {
    "path": "outside_test.go",
    "content": "package nebula\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"net\"\n\t\"net/netip\"\n\t\"testing\"\n\n\t\"github.com/google/gopacket\"\n\t\"github.com/google/gopacket/layers\"\n\n\t\"github.com/slackhq/nebula/firewall\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"golang.org/x/net/ipv4\"\n)\n\nfunc Test_newPacket(t *testing.T) {\n\tp := &firewall.Packet{}\n\n\t// length fails\n\terr := newPacket([]byte{}, true, p)\n\trequire.ErrorIs(t, err, ErrPacketTooShort)\n\n\terr = newPacket([]byte{0x40}, true, p)\n\trequire.ErrorIs(t, err, ErrIPv4PacketTooShort)\n\n\terr = newPacket([]byte{0x60}, true, p)\n\trequire.ErrorIs(t, err, ErrIPv6PacketTooShort)\n\n\t// length fail with ip options\n\th := ipv4.Header{\n\t\tVersion: 1,\n\t\tLen:     100,\n\t\tSrc:     net.IPv4(10, 0, 0, 1),\n\t\tDst:     net.IPv4(10, 0, 0, 2),\n\t\tOptions: []byte{0, 1, 0, 2},\n\t}\n\n\tb, _ := h.Marshal()\n\terr = newPacket(b, true, p)\n\trequire.ErrorIs(t, err, ErrIPv4InvalidHeaderLength)\n\n\t// not an ipv4 packet\n\terr = newPacket([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, true, p)\n\trequire.ErrorIs(t, err, ErrUnknownIPVersion)\n\n\t// invalid ihl\n\terr = newPacket([]byte{4<<4 | (8 >> 2 & 0x0f), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, true, p)\n\trequire.ErrorIs(t, err, ErrIPv4InvalidHeaderLength)\n\n\t// account for variable ip header length - incoming\n\th = ipv4.Header{\n\t\tVersion:  1,\n\t\tLen:      100,\n\t\tSrc:      net.IPv4(10, 0, 0, 1),\n\t\tDst:      net.IPv4(10, 0, 0, 2),\n\t\tOptions:  []byte{0, 1, 0, 2},\n\t\tProtocol: firewall.ProtoTCP,\n\t}\n\n\tb, _ = h.Marshal()\n\tb = append(b, []byte{0, 3, 0, 4}...)\n\terr = newPacket(b, true, p)\n\n\trequire.NoError(t, err)\n\tassert.Equal(t, uint8(firewall.ProtoTCP), p.Protocol)\n\tassert.Equal(t, netip.MustParseAddr(\"10.0.0.2\"), p.LocalAddr)\n\tassert.Equal(t, netip.MustParseAddr(\"10.0.0.1\"), p.RemoteAddr)\n\tassert.Equal(t, uint16(3), p.RemotePort)\n\tassert.Equal(t, uint16(4), p.LocalPort)\n\tassert.False(t, p.Fragment)\n\n\t// account for variable ip header length - outgoing\n\th = ipv4.Header{\n\t\tVersion:  1,\n\t\tProtocol: 2,\n\t\tLen:      100,\n\t\tSrc:      net.IPv4(10, 0, 0, 1),\n\t\tDst:      net.IPv4(10, 0, 0, 2),\n\t\tOptions:  []byte{0, 1, 0, 2},\n\t}\n\n\tb, _ = h.Marshal()\n\tb = append(b, []byte{0, 5, 0, 6}...)\n\terr = newPacket(b, false, p)\n\n\trequire.NoError(t, err)\n\tassert.Equal(t, uint8(2), p.Protocol)\n\tassert.Equal(t, netip.MustParseAddr(\"10.0.0.1\"), p.LocalAddr)\n\tassert.Equal(t, netip.MustParseAddr(\"10.0.0.2\"), p.RemoteAddr)\n\tassert.Equal(t, uint16(6), p.RemotePort)\n\tassert.Equal(t, uint16(5), p.LocalPort)\n\tassert.False(t, p.Fragment)\n}\n\nfunc Test_newPacket_v6(t *testing.T) {\n\tp := &firewall.Packet{}\n\n\t// invalid ipv6\n\tip := layers.IPv6{\n\t\tVersion:  6,\n\t\tHopLimit: 128,\n\t\tSrcIP:    net.IPv6linklocalallrouters,\n\t\tDstIP:    net.IPv6linklocalallnodes,\n\t}\n\n\tbuffer := gopacket.NewSerializeBuffer()\n\topt := gopacket.SerializeOptions{\n\t\tComputeChecksums: false,\n\t\tFixLengths:       false,\n\t}\n\terr := gopacket.SerializeLayers(buffer, opt, &ip)\n\trequire.NoError(t, err)\n\n\terr = newPacket(buffer.Bytes(), true, p)\n\trequire.ErrorIs(t, err, ErrIPv6CouldNotFindPayload)\n\n\t// A v6 packet with a hop-by-hop extension\n\t// ICMPv6 Payload (Echo Request)\n\ticmpLayer := layers.ICMPv6{\n\t\tTypeCode: layers.ICMPv6TypeEchoRequest,\n\t}\n\t// Hop-by-Hop Extension Header\n\thopOption := layers.IPv6HopByHopOption{}\n\thopOption.OptionData = []byte{0, 0, 0, 0}\n\thopByHop := layers.IPv6HopByHop{}\n\thopByHop.Options = append(hopByHop.Options, &hopOption)\n\n\tip = layers.IPv6{\n\t\tVersion:    6,\n\t\tHopLimit:   128,\n\t\tNextHeader: layers.IPProtocolIPv6Destination,\n\t\tSrcIP:      net.IPv6linklocalallrouters,\n\t\tDstIP:      net.IPv6linklocalallnodes,\n\t}\n\n\tbuffer.Clear()\n\terr = gopacket.SerializeLayers(buffer, gopacket.SerializeOptions{\n\t\tComputeChecksums: false,\n\t\tFixLengths:       true,\n\t}, &ip, &hopByHop, &icmpLayer)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t// Ensure buffer length checks during parsing with the next 2 tests.\n\n\t// A full IPv6 header and 1 byte in the first extension, but missing\n\t// the length byte.\n\terr = newPacket(buffer.Bytes()[:41], true, p)\n\trequire.ErrorIs(t, err, ErrIPv6CouldNotFindPayload)\n\n\t// A full IPv6 header plus 1 full extension, but only 1 byte of the\n\t// next layer, missing length byte\n\terr = newPacket(buffer.Bytes()[:49], true, p)\n\trequire.ErrorIs(t, err, ErrIPv6CouldNotFindPayload)\n\terr = nil\n\n\t// A good ICMP packet\n\tip = layers.IPv6{\n\t\tVersion:    6,\n\t\tNextHeader: layers.IPProtocolICMPv6,\n\t\tHopLimit:   128,\n\t\tSrcIP:      net.IPv6linklocalallrouters,\n\t\tDstIP:      net.IPv6linklocalallnodes,\n\t}\n\n\ticmp := layers.ICMPv6{\n\t\tTypeCode: layers.ICMPv6TypeEchoRequest,\n\t\tChecksum: 0x1234,\n\t}\n\n\tbuffer.Clear()\n\trequire.NoError(t, gopacket.SerializeLayers(buffer, opt, &ip, &icmp))\n\trequire.Error(t, newPacket(buffer.Bytes(), true, p))\n\n\tbuffer.Clear()\n\techo := layers.ICMPv6Echo{\n\t\tIdentifier: 0xabcd,\n\t\tSeqNumber:  1234,\n\t}\n\trequire.NoError(t, gopacket.SerializeLayers(buffer, opt, &ip, &icmp, &echo))\n\trequire.NoError(t, newPacket(buffer.Bytes(), true, p))\n\tassert.Equal(t, uint8(layers.IPProtocolICMPv6), p.Protocol)\n\tassert.Equal(t, netip.MustParseAddr(\"ff02::2\"), p.RemoteAddr)\n\tassert.Equal(t, netip.MustParseAddr(\"ff02::1\"), p.LocalAddr)\n\tassert.Equal(t, uint16(0xabcd), p.RemotePort)\n\tassert.Equal(t, uint16(0), p.LocalPort)\n\tassert.False(t, p.Fragment)\n\n\t// A good ESP packet\n\tb := buffer.Bytes()\n\tb[6] = byte(layers.IPProtocolESP)\n\terr = newPacket(b, true, p)\n\trequire.NoError(t, err)\n\tassert.Equal(t, uint8(layers.IPProtocolESP), p.Protocol)\n\tassert.Equal(t, netip.MustParseAddr(\"ff02::2\"), p.RemoteAddr)\n\tassert.Equal(t, netip.MustParseAddr(\"ff02::1\"), p.LocalAddr)\n\tassert.Equal(t, uint16(0), p.RemotePort)\n\tassert.Equal(t, uint16(0), p.LocalPort)\n\tassert.False(t, p.Fragment)\n\n\t// A good None packet\n\tb = buffer.Bytes()\n\tb[6] = byte(layers.IPProtocolNoNextHeader)\n\terr = newPacket(b, true, p)\n\trequire.NoError(t, err)\n\tassert.Equal(t, uint8(layers.IPProtocolNoNextHeader), p.Protocol)\n\tassert.Equal(t, netip.MustParseAddr(\"ff02::2\"), p.RemoteAddr)\n\tassert.Equal(t, netip.MustParseAddr(\"ff02::1\"), p.LocalAddr)\n\tassert.Equal(t, uint16(0), p.RemotePort)\n\tassert.Equal(t, uint16(0), p.LocalPort)\n\tassert.False(t, p.Fragment)\n\n\t// An unknown protocol packet\n\tb = buffer.Bytes()\n\tb[6] = 255 // 255 is a reserved protocol number\n\terr = newPacket(b, true, p)\n\trequire.ErrorIs(t, err, ErrIPv6CouldNotFindPayload)\n\n\t// A good UDP packet\n\tip = layers.IPv6{\n\t\tVersion:    6,\n\t\tNextHeader: firewall.ProtoUDP,\n\t\tHopLimit:   128,\n\t\tSrcIP:      net.IPv6linklocalallrouters,\n\t\tDstIP:      net.IPv6linklocalallnodes,\n\t}\n\n\tudp := layers.UDP{\n\t\tSrcPort: layers.UDPPort(36123),\n\t\tDstPort: layers.UDPPort(22),\n\t}\n\terr = udp.SetNetworkLayerForChecksum(&ip)\n\trequire.NoError(t, err)\n\n\tbuffer.Clear()\n\terr = gopacket.SerializeLayers(buffer, opt, &ip, &udp, gopacket.Payload([]byte{0xde, 0xad, 0xbe, 0xef}))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tb = buffer.Bytes()\n\n\t// incoming\n\terr = newPacket(b, true, p)\n\trequire.NoError(t, err)\n\tassert.Equal(t, uint8(firewall.ProtoUDP), p.Protocol)\n\tassert.Equal(t, netip.MustParseAddr(\"ff02::2\"), p.RemoteAddr)\n\tassert.Equal(t, netip.MustParseAddr(\"ff02::1\"), p.LocalAddr)\n\tassert.Equal(t, uint16(36123), p.RemotePort)\n\tassert.Equal(t, uint16(22), p.LocalPort)\n\tassert.False(t, p.Fragment)\n\n\t// outgoing\n\terr = newPacket(b, false, p)\n\trequire.NoError(t, err)\n\tassert.Equal(t, uint8(firewall.ProtoUDP), p.Protocol)\n\tassert.Equal(t, netip.MustParseAddr(\"ff02::2\"), p.LocalAddr)\n\tassert.Equal(t, netip.MustParseAddr(\"ff02::1\"), p.RemoteAddr)\n\tassert.Equal(t, uint16(36123), p.LocalPort)\n\tassert.Equal(t, uint16(22), p.RemotePort)\n\tassert.False(t, p.Fragment)\n\n\t// Too short UDP packet\n\terr = newPacket(b[:len(b)-10], false, p) // pull off the last 10 bytes\n\trequire.ErrorIs(t, err, ErrIPv6PacketTooShort)\n\n\t// A good TCP packet\n\tb[6] = byte(layers.IPProtocolTCP)\n\n\t// incoming\n\terr = newPacket(b, true, p)\n\trequire.NoError(t, err)\n\tassert.Equal(t, uint8(firewall.ProtoTCP), p.Protocol)\n\tassert.Equal(t, netip.MustParseAddr(\"ff02::2\"), p.RemoteAddr)\n\tassert.Equal(t, netip.MustParseAddr(\"ff02::1\"), p.LocalAddr)\n\tassert.Equal(t, uint16(36123), p.RemotePort)\n\tassert.Equal(t, uint16(22), p.LocalPort)\n\tassert.False(t, p.Fragment)\n\n\t// outgoing\n\terr = newPacket(b, false, p)\n\trequire.NoError(t, err)\n\tassert.Equal(t, uint8(firewall.ProtoTCP), p.Protocol)\n\tassert.Equal(t, netip.MustParseAddr(\"ff02::2\"), p.LocalAddr)\n\tassert.Equal(t, netip.MustParseAddr(\"ff02::1\"), p.RemoteAddr)\n\tassert.Equal(t, uint16(36123), p.LocalPort)\n\tassert.Equal(t, uint16(22), p.RemotePort)\n\tassert.False(t, p.Fragment)\n\n\t// Too short TCP packet\n\terr = newPacket(b[:len(b)-10], false, p) // pull off the last 10 bytes\n\trequire.ErrorIs(t, err, ErrIPv6PacketTooShort)\n\n\t// A good UDP packet with an AH header\n\tip = layers.IPv6{\n\t\tVersion:    6,\n\t\tNextHeader: layers.IPProtocolAH,\n\t\tHopLimit:   128,\n\t\tSrcIP:      net.IPv6linklocalallrouters,\n\t\tDstIP:      net.IPv6linklocalallnodes,\n\t}\n\n\tah := layers.IPSecAH{\n\t\tAuthenticationData: []byte{0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef},\n\t}\n\tah.NextHeader = layers.IPProtocolUDP\n\n\tudpHeader := []byte{\n\t\t0x8d, 0x1b, // Source port 36123\n\t\t0x00, 0x16, // Destination port 22\n\t\t0x00, 0x00, // Length\n\t\t0x00, 0x00, // Checksum\n\t}\n\n\tbuffer.Clear()\n\terr = ip.SerializeTo(buffer, opt)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tb = buffer.Bytes()\n\tahb := serializeAH(&ah)\n\tb = append(b, ahb...)\n\tb = append(b, udpHeader...)\n\n\terr = newPacket(b, true, p)\n\trequire.NoError(t, err)\n\tassert.Equal(t, uint8(firewall.ProtoUDP), p.Protocol)\n\tassert.Equal(t, netip.MustParseAddr(\"ff02::2\"), p.RemoteAddr)\n\tassert.Equal(t, netip.MustParseAddr(\"ff02::1\"), p.LocalAddr)\n\tassert.Equal(t, uint16(36123), p.RemotePort)\n\tassert.Equal(t, uint16(22), p.LocalPort)\n\tassert.False(t, p.Fragment)\n\n\t// Ensure buffer bounds checking during processing\n\terr = newPacket(b[:41], true, p)\n\trequire.ErrorIs(t, err, ErrIPv6PacketTooShort)\n\n\t// Invalid AH header\n\tb = buffer.Bytes()\n\terr = newPacket(b, true, p)\n\trequire.ErrorIs(t, err, ErrIPv6CouldNotFindPayload)\n}\n\nfunc Test_newPacket_ipv6Fragment(t *testing.T) {\n\tp := &firewall.Packet{}\n\n\tip := &layers.IPv6{\n\t\tVersion:    6,\n\t\tNextHeader: layers.IPProtocolIPv6Fragment,\n\t\tHopLimit:   64,\n\t\tSrcIP:      net.IPv6linklocalallrouters,\n\t\tDstIP:      net.IPv6linklocalallnodes,\n\t}\n\n\t// First fragment\n\tfragHeader1 := []byte{\n\t\tuint8(layers.IPProtocolUDP), // Next Header (UDP)\n\t\t0x00,                        // Reserved\n\t\t0x00,                        // Fragment Offset high byte (0)\n\t\t0x01,                        // Fragment Offset low byte & flags (M=1)\n\t\t0x00, 0x00, 0x00, 0x01,      // Identification\n\t}\n\n\tudpHeader := []byte{\n\t\t0x8d, 0x1b, // Source port 36123\n\t\t0x00, 0x16, // Destination port 22\n\t\t0x00, 0x00, // Length\n\t\t0x00, 0x00, // Checksum\n\t}\n\n\tbuffer := gopacket.NewSerializeBuffer()\n\topts := gopacket.SerializeOptions{\n\t\tComputeChecksums: true,\n\t\tFixLengths:       true,\n\t}\n\n\terr := ip.SerializeTo(buffer, opts)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfirstFrag := buffer.Bytes()\n\tfirstFrag = append(firstFrag, fragHeader1...)\n\tfirstFrag = append(firstFrag, udpHeader...)\n\tfirstFrag = append(firstFrag, []byte{0xde, 0xad, 0xbe, 0xef}...)\n\n\t// Test first fragment incoming\n\terr = newPacket(firstFrag, true, p)\n\trequire.NoError(t, err)\n\tassert.Equal(t, netip.MustParseAddr(\"ff02::2\"), p.RemoteAddr)\n\tassert.Equal(t, netip.MustParseAddr(\"ff02::1\"), p.LocalAddr)\n\tassert.Equal(t, uint8(layers.IPProtocolUDP), p.Protocol)\n\tassert.Equal(t, uint16(36123), p.RemotePort)\n\tassert.Equal(t, uint16(22), p.LocalPort)\n\tassert.False(t, p.Fragment)\n\n\t// Test first fragment outgoing\n\terr = newPacket(firstFrag, false, p)\n\trequire.NoError(t, err)\n\tassert.Equal(t, netip.MustParseAddr(\"ff02::2\"), p.LocalAddr)\n\tassert.Equal(t, netip.MustParseAddr(\"ff02::1\"), p.RemoteAddr)\n\tassert.Equal(t, uint8(layers.IPProtocolUDP), p.Protocol)\n\tassert.Equal(t, uint16(36123), p.LocalPort)\n\tassert.Equal(t, uint16(22), p.RemotePort)\n\tassert.False(t, p.Fragment)\n\n\t// Second fragment\n\tfragHeader2 := []byte{\n\t\tuint8(layers.IPProtocolUDP), // Next Header (UDP)\n\t\t0x00,                        // Reserved\n\t\t0xb9,                        // Fragment Offset high byte (185)\n\t\t0x01,                        // Fragment Offset low byte & flags (M=1)\n\t\t0x00, 0x00, 0x00, 0x01,      // Identification\n\t}\n\n\tbuffer.Clear()\n\terr = ip.SerializeTo(buffer, opts)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tsecondFrag := buffer.Bytes()\n\tsecondFrag = append(secondFrag, fragHeader2...)\n\tsecondFrag = append(secondFrag, []byte{0xde, 0xad, 0xbe, 0xef}...)\n\n\t// Test second fragment incoming\n\terr = newPacket(secondFrag, true, p)\n\trequire.NoError(t, err)\n\tassert.Equal(t, netip.MustParseAddr(\"ff02::2\"), p.RemoteAddr)\n\tassert.Equal(t, netip.MustParseAddr(\"ff02::1\"), p.LocalAddr)\n\tassert.Equal(t, uint8(layers.IPProtocolUDP), p.Protocol)\n\tassert.Equal(t, uint16(0), p.RemotePort)\n\tassert.Equal(t, uint16(0), p.LocalPort)\n\tassert.True(t, p.Fragment)\n\n\t// Test second fragment outgoing\n\terr = newPacket(secondFrag, false, p)\n\trequire.NoError(t, err)\n\tassert.Equal(t, netip.MustParseAddr(\"ff02::2\"), p.LocalAddr)\n\tassert.Equal(t, netip.MustParseAddr(\"ff02::1\"), p.RemoteAddr)\n\tassert.Equal(t, uint8(layers.IPProtocolUDP), p.Protocol)\n\tassert.Equal(t, uint16(0), p.LocalPort)\n\tassert.Equal(t, uint16(0), p.RemotePort)\n\tassert.True(t, p.Fragment)\n\n\t// Too short of a fragment packet\n\terr = newPacket(secondFrag[:len(secondFrag)-10], false, p)\n\trequire.ErrorIs(t, err, ErrIPv6PacketTooShort)\n}\n\nfunc BenchmarkParseV6(b *testing.B) {\n\t// Regular UDP packet\n\tip := &layers.IPv6{\n\t\tVersion:    6,\n\t\tNextHeader: layers.IPProtocolUDP,\n\t\tHopLimit:   64,\n\t\tSrcIP:      net.IPv6linklocalallrouters,\n\t\tDstIP:      net.IPv6linklocalallnodes,\n\t}\n\n\tudp := &layers.UDP{\n\t\tSrcPort: layers.UDPPort(36123),\n\t\tDstPort: layers.UDPPort(22),\n\t}\n\n\tbuffer := gopacket.NewSerializeBuffer()\n\topts := gopacket.SerializeOptions{\n\t\tComputeChecksums: false,\n\t\tFixLengths:       true,\n\t}\n\n\terr := gopacket.SerializeLayers(buffer, opts, ip, udp)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\tnormalPacket := buffer.Bytes()\n\n\t// First Fragment packet\n\tipFrag := &layers.IPv6{\n\t\tVersion:    6,\n\t\tNextHeader: layers.IPProtocolIPv6Fragment,\n\t\tHopLimit:   64,\n\t\tSrcIP:      net.IPv6linklocalallrouters,\n\t\tDstIP:      net.IPv6linklocalallnodes,\n\t}\n\n\tfragHeader := []byte{\n\t\tuint8(layers.IPProtocolUDP), // Next Header (UDP)\n\t\t0x00,                        // Reserved\n\t\t0x00,                        // Fragment Offset high byte (0)\n\t\t0x01,                        // Fragment Offset low byte & flags (M=1)\n\t\t0x00, 0x00, 0x00, 0x01,      // Identification\n\t}\n\n\tudpHeader := []byte{\n\t\t0x8d, 0x7b, // Source port 36123\n\t\t0x00, 0x16, // Destination port 22\n\t\t0x00, 0x00, // Length\n\t\t0x00, 0x00, // Checksum\n\t}\n\n\tbuffer.Clear()\n\terr = ipFrag.SerializeTo(buffer, opts)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\n\tfirstFrag := buffer.Bytes()\n\tfirstFrag = append(firstFrag, fragHeader...)\n\tfirstFrag = append(firstFrag, udpHeader...)\n\tfirstFrag = append(firstFrag, []byte{0xde, 0xad, 0xbe, 0xef}...)\n\n\t// Second Fragment packet\n\tfragHeader[2] = 0xb9 // offset 185\n\tbuffer.Clear()\n\terr = ipFrag.SerializeTo(buffer, opts)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\n\tsecondFrag := buffer.Bytes()\n\tsecondFrag = append(secondFrag, fragHeader...)\n\tsecondFrag = append(secondFrag, []byte{0xde, 0xad, 0xbe, 0xef}...)\n\n\tfp := &firewall.Packet{}\n\n\tb.Run(\"Normal\", func(b *testing.B) {\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\tif err = parseV6(normalPacket, true, fp); err != nil {\n\t\t\t\tb.Fatal(err)\n\t\t\t}\n\t\t}\n\t})\n\n\tb.Run(\"FirstFragment\", func(b *testing.B) {\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\tif err = parseV6(firstFrag, true, fp); err != nil {\n\t\t\t\tb.Fatal(err)\n\t\t\t}\n\t\t}\n\t})\n\n\tb.Run(\"SecondFragment\", func(b *testing.B) {\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\tif err = parseV6(secondFrag, true, fp); err != nil {\n\t\t\t\tb.Fatal(err)\n\t\t\t}\n\t\t}\n\t})\n\n\t// Evil packet\n\tevilPacket := &layers.IPv6{\n\t\tVersion:    6,\n\t\tNextHeader: layers.IPProtocolIPv6HopByHop,\n\t\tHopLimit:   64,\n\t\tSrcIP:      net.IPv6linklocalallrouters,\n\t\tDstIP:      net.IPv6linklocalallnodes,\n\t}\n\n\thopHeader := []byte{\n\t\tuint8(layers.IPProtocolIPv6HopByHop), // Next Header (HopByHop)\n\t\t0x00,                                 // Length\n\t\t0x00, 0x00,                           // Options and padding\n\t\t0x00, 0x00, 0x00, 0x00, // More options and padding\n\t}\n\n\tlastHopHeader := []byte{\n\t\tuint8(layers.IPProtocolUDP), // Next Header (UDP)\n\t\t0x00,                        // Length\n\t\t0x00, 0x00,                  // Options and padding\n\t\t0x00, 0x00, 0x00, 0x00, // More options and padding\n\t}\n\n\tbuffer.Clear()\n\terr = evilPacket.SerializeTo(buffer, opts)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\n\tevilBytes := buffer.Bytes()\n\tfor range 200 {\n\t\tevilBytes = append(evilBytes, hopHeader...)\n\t}\n\tevilBytes = append(evilBytes, lastHopHeader...)\n\tevilBytes = append(evilBytes, udpHeader...)\n\tevilBytes = append(evilBytes, []byte{0xde, 0xad, 0xbe, 0xef}...)\n\n\tb.Run(\"200 HopByHop headers\", func(b *testing.B) {\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\tif err = parseV6(evilBytes, false, fp); err != nil {\n\t\t\t\tb.Fatal(err)\n\t\t\t}\n\t\t}\n\t})\n}\n\n// Ensure authentication data is a multiple of 8 bytes by padding if necessary\nfunc padAuthData(authData []byte) []byte {\n\t// Length of Authentication Data must be a multiple of 8 bytes\n\tpaddingLength := (8 - (len(authData) % 8)) % 8 // Only pad if necessary\n\tif paddingLength > 0 {\n\t\tauthData = append(authData, make([]byte, paddingLength)...)\n\t}\n\treturn authData\n}\n\n// Custom function to manually serialize IPSecAH for both IPv4 and IPv6\nfunc serializeAH(ah *layers.IPSecAH) []byte {\n\tbuf := new(bytes.Buffer)\n\n\t// Ensure Authentication Data is a multiple of 8 bytes\n\tah.AuthenticationData = padAuthData(ah.AuthenticationData)\n\t// Calculate Payload Length (in 32-bit words, minus 2)\n\tpayloadLen := uint8((12+len(ah.AuthenticationData))/4) - 2\n\n\t// Serialize fields\n\tif err := binary.Write(buf, binary.BigEndian, ah.NextHeader); err != nil {\n\t\tpanic(err)\n\t}\n\tif err := binary.Write(buf, binary.BigEndian, payloadLen); err != nil {\n\t\tpanic(err)\n\t}\n\tif err := binary.Write(buf, binary.BigEndian, ah.Reserved); err != nil {\n\t\tpanic(err)\n\t}\n\tif err := binary.Write(buf, binary.BigEndian, ah.SPI); err != nil {\n\t\tpanic(err)\n\t}\n\tif err := binary.Write(buf, binary.BigEndian, ah.Seq); err != nil {\n\t\tpanic(err)\n\t}\n\tif len(ah.AuthenticationData) > 0 {\n\t\tif err := binary.Write(buf, binary.BigEndian, ah.AuthenticationData); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n\n\treturn buf.Bytes()\n}\n"
  },
  {
    "path": "overlay/device.go",
    "content": "package overlay\n\nimport (\n\t\"io\"\n\t\"net/netip\"\n\n\t\"github.com/slackhq/nebula/routing\"\n)\n\ntype Device interface {\n\tio.ReadWriteCloser\n\tActivate() error\n\tNetworks() []netip.Prefix\n\tName() string\n\tRoutesFor(netip.Addr) routing.Gateways\n\tSupportsMultiqueue() bool\n\tNewMultiQueueReader() (io.ReadWriteCloser, error)\n}\n"
  },
  {
    "path": "overlay/route.go",
    "content": "package overlay\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\t\"net\"\n\t\"net/netip\"\n\t\"runtime\"\n\t\"strconv\"\n\n\t\"github.com/gaissmai/bart\"\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/config\"\n\t\"github.com/slackhq/nebula/routing\"\n)\n\ntype Route struct {\n\tMTU     int\n\tMetric  int\n\tCidr    netip.Prefix\n\tVia     routing.Gateways\n\tInstall bool\n}\n\n// Equal determines if a route that could be installed in the system route table is equal to another\n// Via is ignored since that is only consumed within nebula itself\nfunc (r Route) Equal(t Route) bool {\n\tif r.Cidr != t.Cidr {\n\t\treturn false\n\t}\n\tif r.Metric != t.Metric {\n\t\treturn false\n\t}\n\tif r.MTU != t.MTU {\n\t\treturn false\n\t}\n\tif r.Install != t.Install {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc (r Route) String() string {\n\ts := r.Cidr.String()\n\tif r.Metric != 0 {\n\t\ts += fmt.Sprintf(\" metric: %v\", r.Metric)\n\t}\n\treturn s\n}\n\nfunc makeRouteTree(l *logrus.Logger, routes []Route, allowMTU bool) (*bart.Table[routing.Gateways], error) {\n\trouteTree := new(bart.Table[routing.Gateways])\n\tfor _, r := range routes {\n\t\tif !allowMTU && r.MTU > 0 {\n\t\t\tl.WithField(\"route\", r).Warnf(\"route MTU is not supported in %s\", runtime.GOOS)\n\t\t}\n\n\t\tgateways := r.Via\n\t\tif len(gateways) > 0 {\n\t\t\trouting.CalculateBucketsForGateways(gateways)\n\t\t\trouteTree.Insert(r.Cidr, gateways)\n\t\t}\n\t}\n\treturn routeTree, nil\n}\n\nfunc parseRoutes(c *config.C, networks []netip.Prefix) ([]Route, error) {\n\tvar err error\n\n\tr := c.Get(\"tun.routes\")\n\tif r == nil {\n\t\treturn []Route{}, nil\n\t}\n\n\trawRoutes, ok := r.([]any)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"tun.routes is not an array\")\n\t}\n\n\tif len(rawRoutes) < 1 {\n\t\treturn []Route{}, nil\n\t}\n\n\troutes := make([]Route, len(rawRoutes))\n\tfor i, r := range rawRoutes {\n\t\tm, ok := r.(map[string]any)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"entry %v in tun.routes is invalid\", i+1)\n\t\t}\n\n\t\trMtu, ok := m[\"mtu\"]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"entry %v.mtu in tun.routes is not present\", i+1)\n\t\t}\n\n\t\tmtu, ok := rMtu.(int)\n\t\tif !ok {\n\t\t\tmtu, err = strconv.Atoi(rMtu.(string))\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"entry %v.mtu in tun.routes is not an integer: %v\", i+1, err)\n\t\t\t}\n\t\t}\n\n\t\tif mtu < 500 {\n\t\t\treturn nil, fmt.Errorf(\"entry %v.mtu in tun.routes is below 500: %v\", i+1, mtu)\n\t\t}\n\n\t\trRoute, ok := m[\"route\"]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"entry %v.route in tun.routes is not present\", i+1)\n\t\t}\n\n\t\tr := Route{\n\t\t\tInstall: true,\n\t\t\tMTU:     mtu,\n\t\t}\n\n\t\tr.Cidr, err = netip.ParsePrefix(fmt.Sprintf(\"%v\", rRoute))\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"entry %v.route in tun.routes failed to parse: %v\", i+1, err)\n\t\t}\n\n\t\tfound := false\n\t\tfor _, network := range networks {\n\t\t\tif network.Contains(r.Cidr.Addr()) && r.Cidr.Bits() >= network.Bits() {\n\t\t\t\tfound = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif !found {\n\t\t\treturn nil, fmt.Errorf(\n\t\t\t\t\"entry %v.route in tun.routes is not contained within the configured vpn networks; route: %v, networks: %v\",\n\t\t\t\ti+1,\n\t\t\t\tr.Cidr.String(),\n\t\t\t\tnetworks,\n\t\t\t)\n\t\t}\n\n\t\troutes[i] = r\n\t}\n\n\treturn routes, nil\n}\n\nfunc parseUnsafeRoutes(c *config.C, networks []netip.Prefix) ([]Route, error) {\n\tvar err error\n\n\tr := c.Get(\"tun.unsafe_routes\")\n\tif r == nil {\n\t\treturn []Route{}, nil\n\t}\n\n\trawRoutes, ok := r.([]any)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"tun.unsafe_routes is not an array\")\n\t}\n\n\tif len(rawRoutes) < 1 {\n\t\treturn []Route{}, nil\n\t}\n\n\troutes := make([]Route, len(rawRoutes))\n\tfor i, r := range rawRoutes {\n\t\tm, ok := r.(map[string]any)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"entry %v in tun.unsafe_routes is invalid\", i+1)\n\t\t}\n\n\t\tvar mtu int\n\t\tif rMtu, ok := m[\"mtu\"]; ok {\n\t\t\tmtu, ok = rMtu.(int)\n\t\t\tif !ok {\n\t\t\t\tmtu, err = strconv.Atoi(rMtu.(string))\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\"entry %v.mtu in tun.unsafe_routes is not an integer: %v\", i+1, err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif mtu != 0 && mtu < 500 {\n\t\t\t\treturn nil, fmt.Errorf(\"entry %v.mtu in tun.unsafe_routes is below 500: %v\", i+1, mtu)\n\t\t\t}\n\t\t}\n\n\t\trMetric, ok := m[\"metric\"]\n\t\tif !ok {\n\t\t\trMetric = 0\n\t\t}\n\n\t\tmetric, ok := rMetric.(int)\n\t\tif !ok {\n\t\t\t_, err = strconv.ParseInt(rMetric.(string), 10, 32)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"entry %v.metric in tun.unsafe_routes is not an integer: %v\", i+1, err)\n\t\t\t}\n\t\t}\n\n\t\tif metric < 0 || metric > math.MaxInt32 {\n\t\t\treturn nil, fmt.Errorf(\"entry %v.metric in tun.unsafe_routes is not in range (0-%d) : %v\", i+1, math.MaxInt32, metric)\n\t\t}\n\n\t\trVia, ok := m[\"via\"]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"entry %v.via in tun.unsafe_routes is not present\", i+1)\n\t\t}\n\n\t\tvar gateways routing.Gateways\n\n\t\tswitch via := rVia.(type) {\n\t\tcase string:\n\t\t\tviaIp, err := netip.ParseAddr(via)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"entry %v.via in tun.unsafe_routes failed to parse address: %v\", i+1, err)\n\t\t\t}\n\n\t\t\tgateways = routing.Gateways{routing.NewGateway(viaIp, 1)}\n\n\t\tcase []any:\n\t\t\tgateways = make(routing.Gateways, len(via))\n\t\t\tfor ig, v := range via {\n\t\t\t\tgatewayMap, ok := v.(map[string]any)\n\t\t\t\tif !ok {\n\t\t\t\t\treturn nil, fmt.Errorf(\"entry %v in tun.unsafe_routes[%v].via is invalid\", i+1, ig+1)\n\t\t\t\t}\n\n\t\t\t\trGateway, ok := gatewayMap[\"gateway\"]\n\t\t\t\tif !ok {\n\t\t\t\t\treturn nil, fmt.Errorf(\"entry .gateway in tun.unsafe_routes[%v].via[%v] is not present\", i+1, ig+1)\n\t\t\t\t}\n\n\t\t\t\tparsedGateway, ok := rGateway.(string)\n\t\t\t\tif !ok {\n\t\t\t\t\treturn nil, fmt.Errorf(\"entry .gateway in tun.unsafe_routes[%v].via[%v] is not a string\", i+1, ig+1)\n\t\t\t\t}\n\n\t\t\t\tgatewayIp, err := netip.ParseAddr(parsedGateway)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\"entry .gateway in tun.unsafe_routes[%v].via[%v] failed to parse address: %v\", i+1, ig+1, err)\n\t\t\t\t}\n\n\t\t\t\trGatewayWeight, ok := gatewayMap[\"weight\"]\n\t\t\t\tif !ok {\n\t\t\t\t\trGatewayWeight = 1\n\t\t\t\t}\n\n\t\t\t\tgatewayWeight, ok := rGatewayWeight.(int)\n\t\t\t\tif !ok {\n\t\t\t\t\t_, err = strconv.ParseInt(rGatewayWeight.(string), 10, 32)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"entry .weight in tun.unsafe_routes[%v].via[%v] is not an integer\", i+1, ig+1)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif gatewayWeight < 1 || gatewayWeight > math.MaxInt32 {\n\t\t\t\t\treturn nil, fmt.Errorf(\"entry .weight in tun.unsafe_routes[%v].via[%v] is not in range (1-%d) : %v\", i+1, ig+1, math.MaxInt32, gatewayWeight)\n\t\t\t\t}\n\n\t\t\t\tgateways[ig] = routing.NewGateway(gatewayIp, gatewayWeight)\n\n\t\t\t}\n\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"entry %v.via in tun.unsafe_routes is not a string or list of gateways: found %T\", i+1, rVia)\n\t\t}\n\n\t\trRoute, ok := m[\"route\"]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"entry %v.route in tun.unsafe_routes is not present\", i+1)\n\t\t}\n\n\t\tinstall := true\n\t\trInstall, ok := m[\"install\"]\n\t\tif ok {\n\t\t\tinstall, err = strconv.ParseBool(fmt.Sprintf(\"%v\", rInstall))\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"entry %v.install in tun.unsafe_routes is not a boolean: %v\", i+1, err)\n\t\t\t}\n\t\t}\n\n\t\tr := Route{\n\t\t\tVia:     gateways,\n\t\t\tMTU:     mtu,\n\t\t\tMetric:  metric,\n\t\t\tInstall: install,\n\t\t}\n\n\t\tr.Cidr, err = netip.ParsePrefix(fmt.Sprintf(\"%v\", rRoute))\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"entry %v.route in tun.unsafe_routes failed to parse: %v\", i+1, err)\n\t\t}\n\n\t\tfor _, network := range networks {\n\t\t\tif network.Contains(r.Cidr.Addr()) {\n\t\t\t\treturn nil, fmt.Errorf(\n\t\t\t\t\t\"entry %v.route in tun.unsafe_routes is contained within the configured vpn networks; route: %v, network: %v\",\n\t\t\t\t\ti+1,\n\t\t\t\t\tr.Cidr.String(),\n\t\t\t\t\tnetwork.String(),\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\troutes[i] = r\n\t}\n\n\treturn routes, nil\n}\n\nfunc ipWithin(o *net.IPNet, i *net.IPNet) bool {\n\t// Make sure o contains the lowest form of i\n\tif !o.Contains(i.IP.Mask(i.Mask)) {\n\t\treturn false\n\t}\n\n\t// Find the max ip in i\n\tip4 := i.IP.To4()\n\tif ip4 == nil {\n\t\treturn false\n\t}\n\n\tlast := make(net.IP, len(ip4))\n\tcopy(last, ip4)\n\tfor x := range ip4 {\n\t\tlast[x] |= ^i.Mask[x]\n\t}\n\n\t// Make sure o contains the max\n\tif !o.Contains(last) {\n\t\treturn false\n\t}\n\n\treturn true\n}\n"
  },
  {
    "path": "overlay/route_test.go",
    "content": "package overlay\n\nimport (\n\t\"fmt\"\n\t\"net/netip\"\n\t\"testing\"\n\n\t\"github.com/slackhq/nebula/config\"\n\t\"github.com/slackhq/nebula/routing\"\n\t\"github.com/slackhq/nebula/test\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_parseRoutes(t *testing.T) {\n\tl := test.NewLogger()\n\tc := config.NewC(l)\n\tn, err := netip.ParsePrefix(\"10.0.0.0/24\")\n\trequire.NoError(t, err)\n\n\t// test no routes config\n\troutes, err := parseRoutes(c, []netip.Prefix{n})\n\trequire.NoError(t, err)\n\tassert.Empty(t, routes)\n\n\t// not an array\n\tc.Settings[\"tun\"] = map[string]any{\"routes\": \"hi\"}\n\troutes, err = parseRoutes(c, []netip.Prefix{n})\n\tassert.Nil(t, routes)\n\trequire.EqualError(t, err, \"tun.routes is not an array\")\n\n\t// no routes\n\tc.Settings[\"tun\"] = map[string]any{\"routes\": []any{}}\n\troutes, err = parseRoutes(c, []netip.Prefix{n})\n\trequire.NoError(t, err)\n\tassert.Empty(t, routes)\n\n\t// weird route\n\tc.Settings[\"tun\"] = map[string]any{\"routes\": []any{\"asdf\"}}\n\troutes, err = parseRoutes(c, []netip.Prefix{n})\n\tassert.Nil(t, routes)\n\trequire.EqualError(t, err, \"entry 1 in tun.routes is invalid\")\n\n\t// no mtu\n\tc.Settings[\"tun\"] = map[string]any{\"routes\": []any{map[string]any{}}}\n\troutes, err = parseRoutes(c, []netip.Prefix{n})\n\tassert.Nil(t, routes)\n\trequire.EqualError(t, err, \"entry 1.mtu in tun.routes is not present\")\n\n\t// bad mtu\n\tc.Settings[\"tun\"] = map[string]any{\"routes\": []any{map[string]any{\"mtu\": \"nope\"}}}\n\troutes, err = parseRoutes(c, []netip.Prefix{n})\n\tassert.Nil(t, routes)\n\trequire.EqualError(t, err, \"entry 1.mtu in tun.routes is not an integer: strconv.Atoi: parsing \\\"nope\\\": invalid syntax\")\n\n\t// low mtu\n\tc.Settings[\"tun\"] = map[string]any{\"routes\": []any{map[string]any{\"mtu\": \"499\"}}}\n\troutes, err = parseRoutes(c, []netip.Prefix{n})\n\tassert.Nil(t, routes)\n\trequire.EqualError(t, err, \"entry 1.mtu in tun.routes is below 500: 499\")\n\n\t// missing route\n\tc.Settings[\"tun\"] = map[string]any{\"routes\": []any{map[string]any{\"mtu\": \"500\"}}}\n\troutes, err = parseRoutes(c, []netip.Prefix{n})\n\tassert.Nil(t, routes)\n\trequire.EqualError(t, err, \"entry 1.route in tun.routes is not present\")\n\n\t// unparsable route\n\tc.Settings[\"tun\"] = map[string]any{\"routes\": []any{map[string]any{\"mtu\": \"500\", \"route\": \"nope\"}}}\n\troutes, err = parseRoutes(c, []netip.Prefix{n})\n\tassert.Nil(t, routes)\n\trequire.EqualError(t, err, \"entry 1.route in tun.routes failed to parse: netip.ParsePrefix(\\\"nope\\\"): no '/'\")\n\n\t// below network range\n\tc.Settings[\"tun\"] = map[string]any{\"routes\": []any{map[string]any{\"mtu\": \"500\", \"route\": \"1.0.0.0/8\"}}}\n\troutes, err = parseRoutes(c, []netip.Prefix{n})\n\tassert.Nil(t, routes)\n\trequire.EqualError(t, err, \"entry 1.route in tun.routes is not contained within the configured vpn networks; route: 1.0.0.0/8, networks: [10.0.0.0/24]\")\n\n\t// above network range\n\tc.Settings[\"tun\"] = map[string]any{\"routes\": []any{map[string]any{\"mtu\": \"500\", \"route\": \"10.0.1.0/24\"}}}\n\troutes, err = parseRoutes(c, []netip.Prefix{n})\n\tassert.Nil(t, routes)\n\trequire.EqualError(t, err, \"entry 1.route in tun.routes is not contained within the configured vpn networks; route: 10.0.1.0/24, networks: [10.0.0.0/24]\")\n\n\t// Not in multiple ranges\n\tc.Settings[\"tun\"] = map[string]any{\"routes\": []any{map[string]any{\"mtu\": \"500\", \"route\": \"192.0.0.0/24\"}}}\n\troutes, err = parseRoutes(c, []netip.Prefix{n, netip.MustParsePrefix(\"192.1.0.0/24\")})\n\tassert.Nil(t, routes)\n\trequire.EqualError(t, err, \"entry 1.route in tun.routes is not contained within the configured vpn networks; route: 192.0.0.0/24, networks: [10.0.0.0/24 192.1.0.0/24]\")\n\n\t// happy case\n\tc.Settings[\"tun\"] = map[string]any{\"routes\": []any{\n\t\tmap[string]any{\"mtu\": \"9000\", \"route\": \"10.0.0.0/29\"},\n\t\tmap[string]any{\"mtu\": \"8000\", \"route\": \"10.0.0.1/32\"},\n\t}}\n\troutes, err = parseRoutes(c, []netip.Prefix{n})\n\trequire.NoError(t, err)\n\tassert.Len(t, routes, 2)\n\n\ttested := 0\n\tfor _, r := range routes {\n\t\tassert.True(t, r.Install)\n\n\t\tif r.MTU == 8000 {\n\t\t\tassert.Equal(t, \"10.0.0.1/32\", r.Cidr.String())\n\t\t\ttested++\n\t\t} else {\n\t\t\tassert.Equal(t, 9000, r.MTU)\n\t\t\tassert.Equal(t, \"10.0.0.0/29\", r.Cidr.String())\n\t\t\ttested++\n\t\t}\n\t}\n\n\tif tested != 2 {\n\t\tt.Fatal(\"Did not see both routes\")\n\t}\n}\n\nfunc Test_parseUnsafeRoutes(t *testing.T) {\n\tl := test.NewLogger()\n\tc := config.NewC(l)\n\tn, err := netip.ParsePrefix(\"10.0.0.0/24\")\n\trequire.NoError(t, err)\n\n\t// test no routes config\n\troutes, err := parseUnsafeRoutes(c, []netip.Prefix{n})\n\trequire.NoError(t, err)\n\tassert.Empty(t, routes)\n\n\t// not an array\n\tc.Settings[\"tun\"] = map[string]any{\"unsafe_routes\": \"hi\"}\n\troutes, err = parseUnsafeRoutes(c, []netip.Prefix{n})\n\tassert.Nil(t, routes)\n\trequire.EqualError(t, err, \"tun.unsafe_routes is not an array\")\n\n\t// no routes\n\tc.Settings[\"tun\"] = map[string]any{\"unsafe_routes\": []any{}}\n\troutes, err = parseUnsafeRoutes(c, []netip.Prefix{n})\n\trequire.NoError(t, err)\n\tassert.Empty(t, routes)\n\n\t// weird route\n\tc.Settings[\"tun\"] = map[string]any{\"unsafe_routes\": []any{\"asdf\"}}\n\troutes, err = parseUnsafeRoutes(c, []netip.Prefix{n})\n\tassert.Nil(t, routes)\n\trequire.EqualError(t, err, \"entry 1 in tun.unsafe_routes is invalid\")\n\n\t// no via\n\tc.Settings[\"tun\"] = map[string]any{\"unsafe_routes\": []any{map[string]any{}}}\n\troutes, err = parseUnsafeRoutes(c, []netip.Prefix{n})\n\tassert.Nil(t, routes)\n\trequire.EqualError(t, err, \"entry 1.via in tun.unsafe_routes is not present\")\n\n\t// invalid via\n\tfor _, invalidValue := range []any{\n\t\t127, false, nil, 1.0, []string{\"1\", \"2\"},\n\t} {\n\t\tc.Settings[\"tun\"] = map[string]any{\"unsafe_routes\": []any{map[string]any{\"via\": invalidValue}}}\n\t\troutes, err = parseUnsafeRoutes(c, []netip.Prefix{n})\n\t\tassert.Nil(t, routes)\n\t\trequire.EqualError(t, err, fmt.Sprintf(\"entry 1.via in tun.unsafe_routes is not a string or list of gateways: found %T\", invalidValue))\n\t}\n\n\t// Unparsable list of via\n\tc.Settings[\"tun\"] = map[string]any{\"unsafe_routes\": []any{map[string]any{\"via\": []string{\"1\", \"2\"}}}}\n\troutes, err = parseUnsafeRoutes(c, []netip.Prefix{n})\n\tassert.Nil(t, routes)\n\trequire.EqualError(t, err, \"entry 1.via in tun.unsafe_routes is not a string or list of gateways: found []string\")\n\n\t// unparsable via\n\tc.Settings[\"tun\"] = map[string]any{\"unsafe_routes\": []any{map[string]any{\"mtu\": \"500\", \"via\": \"nope\"}}}\n\troutes, err = parseUnsafeRoutes(c, []netip.Prefix{n})\n\tassert.Nil(t, routes)\n\trequire.EqualError(t, err, \"entry 1.via in tun.unsafe_routes failed to parse address: ParseAddr(\\\"nope\\\"): unable to parse IP\")\n\n\t// unparsable gateway\n\tc.Settings[\"tun\"] = map[string]any{\"unsafe_routes\": []any{map[string]any{\"mtu\": \"500\", \"via\": []any{map[string]any{\"gateway\": \"1\"}}}}}\n\troutes, err = parseUnsafeRoutes(c, []netip.Prefix{n})\n\tassert.Nil(t, routes)\n\trequire.EqualError(t, err, \"entry .gateway in tun.unsafe_routes[1].via[1] failed to parse address: ParseAddr(\\\"1\\\"): unable to parse IP\")\n\n\t// missing gateway element\n\tc.Settings[\"tun\"] = map[string]any{\"unsafe_routes\": []any{map[string]any{\"mtu\": \"500\", \"via\": []any{map[string]any{\"weight\": \"1\"}}}}}\n\troutes, err = parseUnsafeRoutes(c, []netip.Prefix{n})\n\tassert.Nil(t, routes)\n\trequire.EqualError(t, err, \"entry .gateway in tun.unsafe_routes[1].via[1] is not present\")\n\n\t// unparsable weight element\n\tc.Settings[\"tun\"] = map[string]any{\"unsafe_routes\": []any{map[string]any{\"mtu\": \"500\", \"via\": []any{map[string]any{\"gateway\": \"10.0.0.1\", \"weight\": \"a\"}}}}}\n\troutes, err = parseUnsafeRoutes(c, []netip.Prefix{n})\n\tassert.Nil(t, routes)\n\trequire.EqualError(t, err, \"entry .weight in tun.unsafe_routes[1].via[1] is not an integer\")\n\n\t// missing route\n\tc.Settings[\"tun\"] = map[string]any{\"unsafe_routes\": []any{map[string]any{\"via\": \"127.0.0.1\", \"mtu\": \"500\"}}}\n\troutes, err = parseUnsafeRoutes(c, []netip.Prefix{n})\n\tassert.Nil(t, routes)\n\trequire.EqualError(t, err, \"entry 1.route in tun.unsafe_routes is not present\")\n\n\t// unparsable route\n\tc.Settings[\"tun\"] = map[string]any{\"unsafe_routes\": []any{map[string]any{\"via\": \"127.0.0.1\", \"mtu\": \"500\", \"route\": \"nope\"}}}\n\troutes, err = parseUnsafeRoutes(c, []netip.Prefix{n})\n\tassert.Nil(t, routes)\n\trequire.EqualError(t, err, \"entry 1.route in tun.unsafe_routes failed to parse: netip.ParsePrefix(\\\"nope\\\"): no '/'\")\n\n\t// within network range\n\tc.Settings[\"tun\"] = map[string]any{\"unsafe_routes\": []any{map[string]any{\"via\": \"127.0.0.1\", \"route\": \"10.0.0.0/24\"}}}\n\troutes, err = parseUnsafeRoutes(c, []netip.Prefix{n})\n\tassert.Nil(t, routes)\n\trequire.EqualError(t, err, \"entry 1.route in tun.unsafe_routes is contained within the configured vpn networks; route: 10.0.0.0/24, network: 10.0.0.0/24\")\n\n\t// below network range\n\tc.Settings[\"tun\"] = map[string]any{\"unsafe_routes\": []any{map[string]any{\"via\": \"127.0.0.1\", \"route\": \"1.0.0.0/8\"}}}\n\troutes, err = parseUnsafeRoutes(c, []netip.Prefix{n})\n\tassert.Len(t, routes, 1)\n\trequire.NoError(t, err)\n\n\t// above network range\n\tc.Settings[\"tun\"] = map[string]any{\"unsafe_routes\": []any{map[string]any{\"via\": \"127.0.0.1\", \"route\": \"10.0.1.0/24\"}}}\n\troutes, err = parseUnsafeRoutes(c, []netip.Prefix{n})\n\tassert.Len(t, routes, 1)\n\trequire.NoError(t, err)\n\n\t// no mtu\n\tc.Settings[\"tun\"] = map[string]any{\"unsafe_routes\": []any{map[string]any{\"via\": \"127.0.0.1\", \"route\": \"1.0.0.0/8\"}}}\n\troutes, err = parseUnsafeRoutes(c, []netip.Prefix{n})\n\tassert.Len(t, routes, 1)\n\tassert.Equal(t, 0, routes[0].MTU)\n\n\t// bad mtu\n\tc.Settings[\"tun\"] = map[string]any{\"unsafe_routes\": []any{map[string]any{\"via\": \"127.0.0.1\", \"mtu\": \"nope\"}}}\n\troutes, err = parseUnsafeRoutes(c, []netip.Prefix{n})\n\tassert.Nil(t, routes)\n\trequire.EqualError(t, err, \"entry 1.mtu in tun.unsafe_routes is not an integer: strconv.Atoi: parsing \\\"nope\\\": invalid syntax\")\n\n\t// low mtu\n\tc.Settings[\"tun\"] = map[string]any{\"unsafe_routes\": []any{map[string]any{\"via\": \"127.0.0.1\", \"mtu\": \"499\"}}}\n\troutes, err = parseUnsafeRoutes(c, []netip.Prefix{n})\n\tassert.Nil(t, routes)\n\trequire.EqualError(t, err, \"entry 1.mtu in tun.unsafe_routes is below 500: 499\")\n\n\t// bad install\n\tc.Settings[\"tun\"] = map[string]any{\"unsafe_routes\": []any{map[string]any{\"via\": \"127.0.0.1\", \"mtu\": \"9000\", \"route\": \"1.0.0.0/29\", \"install\": \"nope\"}}}\n\troutes, err = parseUnsafeRoutes(c, []netip.Prefix{n})\n\tassert.Nil(t, routes)\n\trequire.EqualError(t, err, \"entry 1.install in tun.unsafe_routes is not a boolean: strconv.ParseBool: parsing \\\"nope\\\": invalid syntax\")\n\n\t// happy case\n\tc.Settings[\"tun\"] = map[string]any{\"unsafe_routes\": []any{\n\t\tmap[string]any{\"via\": \"127.0.0.1\", \"mtu\": \"9000\", \"route\": \"1.0.0.0/29\", \"install\": \"t\"},\n\t\tmap[string]any{\"via\": \"127.0.0.1\", \"mtu\": \"8000\", \"route\": \"1.0.0.1/32\", \"install\": 0},\n\t\tmap[string]any{\"via\": \"127.0.0.1\", \"mtu\": \"1500\", \"metric\": 1234, \"route\": \"1.0.0.2/32\", \"install\": 1},\n\t\tmap[string]any{\"via\": \"127.0.0.1\", \"mtu\": \"1500\", \"metric\": 1234, \"route\": \"1.0.0.2/32\"},\n\t}}\n\troutes, err = parseUnsafeRoutes(c, []netip.Prefix{n})\n\trequire.NoError(t, err)\n\tassert.Len(t, routes, 4)\n\n\ttested := 0\n\tfor _, r := range routes {\n\t\tif r.MTU == 8000 {\n\t\t\tassert.Equal(t, \"1.0.0.1/32\", r.Cidr.String())\n\t\t\tassert.False(t, r.Install)\n\t\t\ttested++\n\t\t} else if r.MTU == 9000 {\n\t\t\tassert.Equal(t, 9000, r.MTU)\n\t\t\tassert.Equal(t, \"1.0.0.0/29\", r.Cidr.String())\n\t\t\tassert.True(t, r.Install)\n\t\t\ttested++\n\t\t} else {\n\t\t\tassert.Equal(t, 1500, r.MTU)\n\t\t\tassert.Equal(t, 1234, r.Metric)\n\t\t\tassert.Equal(t, \"1.0.0.2/32\", r.Cidr.String())\n\t\t\tassert.True(t, r.Install)\n\t\t\ttested++\n\t\t}\n\t}\n\n\tif tested != 4 {\n\t\tt.Fatal(\"Did not see all unsafe_routes\")\n\t}\n}\n\nfunc Test_makeRouteTree(t *testing.T) {\n\tl := test.NewLogger()\n\tc := config.NewC(l)\n\tn, err := netip.ParsePrefix(\"10.0.0.0/24\")\n\trequire.NoError(t, err)\n\n\tc.Settings[\"tun\"] = map[string]any{\"unsafe_routes\": []any{\n\t\tmap[string]any{\"via\": \"192.168.0.1\", \"route\": \"1.0.0.0/28\"},\n\t\tmap[string]any{\"via\": \"192.168.0.2\", \"route\": \"1.0.0.1/32\"},\n\t}}\n\troutes, err := parseUnsafeRoutes(c, []netip.Prefix{n})\n\trequire.NoError(t, err)\n\tassert.Len(t, routes, 2)\n\trouteTree, err := makeRouteTree(l, routes, true)\n\trequire.NoError(t, err)\n\n\tip, err := netip.ParseAddr(\"1.0.0.2\")\n\trequire.NoError(t, err)\n\tr, ok := routeTree.Lookup(ip)\n\tassert.True(t, ok)\n\n\tnip, err := netip.ParseAddr(\"192.168.0.1\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, nip, r[0].Addr())\n\n\tip, err = netip.ParseAddr(\"1.0.0.1\")\n\trequire.NoError(t, err)\n\tr, ok = routeTree.Lookup(ip)\n\tassert.True(t, ok)\n\n\tnip, err = netip.ParseAddr(\"192.168.0.2\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, nip, r[0].Addr())\n\n\tip, err = netip.ParseAddr(\"1.1.0.1\")\n\trequire.NoError(t, err)\n\tr, ok = routeTree.Lookup(ip)\n\tassert.False(t, ok)\n}\n\nfunc Test_makeMultipathUnsafeRouteTree(t *testing.T) {\n\tl := test.NewLogger()\n\tc := config.NewC(l)\n\tn, err := netip.ParsePrefix(\"10.0.0.0/24\")\n\trequire.NoError(t, err)\n\n\tc.Settings[\"tun\"] = map[string]any{\n\t\t\"unsafe_routes\": []any{\n\t\t\tmap[string]any{\n\t\t\t\t\"route\": \"192.168.86.0/24\",\n\t\t\t\t\"via\":   \"192.168.100.10\",\n\t\t\t},\n\t\t\tmap[string]any{\n\t\t\t\t\"route\": \"192.168.87.0/24\",\n\t\t\t\t\"via\": []any{\n\t\t\t\t\tmap[string]any{\n\t\t\t\t\t\t\"gateway\": \"10.0.0.1\",\n\t\t\t\t\t},\n\t\t\t\t\tmap[string]any{\n\t\t\t\t\t\t\"gateway\": \"10.0.0.2\",\n\t\t\t\t\t},\n\t\t\t\t\tmap[string]any{\n\t\t\t\t\t\t\"gateway\": \"10.0.0.3\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tmap[string]any{\n\t\t\t\t\"route\": \"192.168.89.0/24\",\n\t\t\t\t\"via\": []any{\n\t\t\t\t\tmap[string]any{\n\t\t\t\t\t\t\"gateway\": \"10.0.0.1\",\n\t\t\t\t\t\t\"weight\":  10,\n\t\t\t\t\t},\n\t\t\t\t\tmap[string]any{\n\t\t\t\t\t\t\"gateway\": \"10.0.0.2\",\n\t\t\t\t\t\t\"weight\":  5,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\troutes, err := parseUnsafeRoutes(c, []netip.Prefix{n})\n\trequire.NoError(t, err)\n\tassert.Len(t, routes, 3)\n\trouteTree, err := makeRouteTree(l, routes, true)\n\trequire.NoError(t, err)\n\n\tip, err := netip.ParseAddr(\"192.168.86.1\")\n\trequire.NoError(t, err)\n\tr, ok := routeTree.Lookup(ip)\n\tassert.True(t, ok)\n\n\tnip, err := netip.ParseAddr(\"192.168.100.10\")\n\trequire.NoError(t, err)\n\tassert.Equal(t, nip, r[0].Addr())\n\n\tip, err = netip.ParseAddr(\"192.168.87.1\")\n\trequire.NoError(t, err)\n\tr, ok = routeTree.Lookup(ip)\n\tassert.True(t, ok)\n\n\texpectedGateways := routing.Gateways{routing.NewGateway(netip.MustParseAddr(\"10.0.0.1\"), 1),\n\t\trouting.NewGateway(netip.MustParseAddr(\"10.0.0.2\"), 1),\n\t\trouting.NewGateway(netip.MustParseAddr(\"10.0.0.3\"), 1)}\n\n\trouting.CalculateBucketsForGateways(expectedGateways)\n\tassert.ElementsMatch(t, expectedGateways, r)\n\n\tip, err = netip.ParseAddr(\"192.168.89.1\")\n\trequire.NoError(t, err)\n\tr, ok = routeTree.Lookup(ip)\n\tassert.True(t, ok)\n\n\texpectedGateways = routing.Gateways{routing.NewGateway(netip.MustParseAddr(\"10.0.0.1\"), 10),\n\t\trouting.NewGateway(netip.MustParseAddr(\"10.0.0.2\"), 5)}\n\n\trouting.CalculateBucketsForGateways(expectedGateways)\n\tassert.ElementsMatch(t, expectedGateways, r)\n}\n"
  },
  {
    "path": "overlay/tun.go",
    "content": "package overlay\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/config\"\n\t\"github.com/slackhq/nebula/util\"\n)\n\nconst DefaultMTU = 1300\n\ntype NameError struct {\n\tName       string\n\tUnderlying error\n}\n\nfunc (e *NameError) Error() string {\n\treturn fmt.Sprintf(\"could not set tun device name: %s because %s\", e.Name, e.Underlying)\n}\n\n// TODO: We may be able to remove routines\ntype DeviceFactory func(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, routines int) (Device, error)\n\nfunc NewDeviceFromConfig(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, routines int) (Device, error) {\n\tswitch {\n\tcase c.GetBool(\"tun.disabled\", false):\n\t\ttun := newDisabledTun(vpnNetworks, c.GetInt(\"tun.tx_queue\", 500), c.GetBool(\"stats.message_metrics\", false), l)\n\t\treturn tun, nil\n\n\tdefault:\n\t\treturn newTun(c, l, vpnNetworks, routines > 1)\n\t}\n}\n\nfunc NewFdDeviceFromConfig(fd *int) DeviceFactory {\n\treturn func(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, routines int) (Device, error) {\n\t\treturn newTunFromFd(c, l, *fd, vpnNetworks)\n\t}\n}\n\nfunc getAllRoutesFromConfig(c *config.C, vpnNetworks []netip.Prefix, initial bool) (bool, []Route, error) {\n\tif !initial && !c.HasChanged(\"tun.routes\") && !c.HasChanged(\"tun.unsafe_routes\") {\n\t\treturn false, nil, nil\n\t}\n\n\troutes, err := parseRoutes(c, vpnNetworks)\n\tif err != nil {\n\t\treturn true, nil, util.NewContextualError(\"Could not parse tun.routes\", nil, err)\n\t}\n\n\tunsafeRoutes, err := parseUnsafeRoutes(c, vpnNetworks)\n\tif err != nil {\n\t\treturn true, nil, util.NewContextualError(\"Could not parse tun.unsafe_routes\", nil, err)\n\t}\n\n\troutes = append(routes, unsafeRoutes...)\n\treturn true, routes, nil\n}\n\n// findRemovedRoutes will return all routes that are not present in the newRoutes list and would affect the system route table.\n// Via is not used to evaluate since it does not affect the system route table.\nfunc findRemovedRoutes(newRoutes, oldRoutes []Route) []Route {\n\tvar removed []Route\n\thas := func(entry Route) bool {\n\t\tfor _, check := range newRoutes {\n\t\t\tif check.Equal(entry) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}\n\n\tfor _, oldEntry := range oldRoutes {\n\t\tif !has(oldEntry) {\n\t\t\tremoved = append(removed, oldEntry)\n\t\t}\n\t}\n\n\treturn removed\n}\n\nfunc prefixToMask(prefix netip.Prefix) netip.Addr {\n\tpLen := 128\n\tif prefix.Addr().Is4() {\n\t\tpLen = 32\n\t}\n\n\taddr, _ := netip.AddrFromSlice(net.CIDRMask(prefix.Bits(), pLen))\n\treturn addr\n}\n\nfunc flipBytes(b []byte) []byte {\n\tfor i := 0; i < len(b); i++ {\n\t\tb[i] ^= 0xFF\n\t}\n\treturn b\n}\nfunc orBytes(a []byte, b []byte) []byte {\n\tret := make([]byte, len(a))\n\tfor i := 0; i < len(a); i++ {\n\t\tret[i] = a[i] | b[i]\n\t}\n\treturn ret\n}\n\nfunc getBroadcast(cidr netip.Prefix) netip.Addr {\n\tbroadcast, _ := netip.AddrFromSlice(\n\t\torBytes(\n\t\t\tcidr.Addr().AsSlice(),\n\t\t\tflipBytes(prefixToMask(cidr).AsSlice()),\n\t\t),\n\t)\n\treturn broadcast\n}\n\nfunc selectGateway(dest netip.Prefix, gateways []netip.Prefix) (netip.Prefix, error) {\n\tfor _, gateway := range gateways {\n\t\tif dest.Addr().Is4() && gateway.Addr().Is4() {\n\t\t\treturn gateway, nil\n\t\t}\n\n\t\tif dest.Addr().Is6() && gateway.Addr().Is6() {\n\t\t\treturn gateway, nil\n\t\t}\n\t}\n\n\treturn netip.Prefix{}, fmt.Errorf(\"no gateway found for %v in the list of vpn networks\", dest)\n}\n"
  },
  {
    "path": "overlay/tun_android.go",
    "content": "//go:build !e2e_testing\n// +build !e2e_testing\n\npackage overlay\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/netip\"\n\t\"os\"\n\t\"sync/atomic\"\n\n\t\"github.com/gaissmai/bart\"\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/config\"\n\t\"github.com/slackhq/nebula/routing\"\n\t\"github.com/slackhq/nebula/util\"\n)\n\ntype tun struct {\n\tio.ReadWriteCloser\n\tfd          int\n\tvpnNetworks []netip.Prefix\n\tRoutes      atomic.Pointer[[]Route]\n\trouteTree   atomic.Pointer[bart.Table[routing.Gateways]]\n\tl           *logrus.Logger\n}\n\nfunc newTunFromFd(c *config.C, l *logrus.Logger, deviceFd int, vpnNetworks []netip.Prefix) (*tun, error) {\n\t// XXX Android returns an fd in non-blocking mode which is necessary for shutdown to work properly.\n\t// Be sure not to call file.Fd() as it will set the fd to blocking mode.\n\tfile := os.NewFile(uintptr(deviceFd), \"/dev/net/tun\")\n\n\tt := &tun{\n\t\tReadWriteCloser: file,\n\t\tfd:              deviceFd,\n\t\tvpnNetworks:     vpnNetworks,\n\t\tl:               l,\n\t}\n\n\terr := t.reload(c, true)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tc.RegisterReloadCallback(func(c *config.C) {\n\t\terr := t.reload(c, false)\n\t\tif err != nil {\n\t\t\tutil.LogWithContextIfNeeded(\"failed to reload tun device\", err, t.l)\n\t\t}\n\t})\n\n\treturn t, nil\n}\n\nfunc newTun(_ *config.C, _ *logrus.Logger, _ []netip.Prefix, _ bool) (*tun, error) {\n\treturn nil, fmt.Errorf(\"newTun not supported in Android\")\n}\n\nfunc (t *tun) RoutesFor(ip netip.Addr) routing.Gateways {\n\tr, _ := t.routeTree.Load().Lookup(ip)\n\treturn r\n}\n\nfunc (t tun) Activate() error {\n\treturn nil\n}\n\nfunc (t *tun) reload(c *config.C, initial bool) error {\n\tchange, routes, err := getAllRoutesFromConfig(c, t.vpnNetworks, initial)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !initial && !change {\n\t\treturn nil\n\t}\n\n\trouteTree, err := makeRouteTree(t.l, routes, false)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Teach nebula how to handle the routes\n\tt.Routes.Store(&routes)\n\tt.routeTree.Store(routeTree)\n\treturn nil\n}\n\nfunc (t *tun) Networks() []netip.Prefix {\n\treturn t.vpnNetworks\n}\n\nfunc (t *tun) Name() string {\n\treturn \"android\"\n}\n\nfunc (t *tun) SupportsMultiqueue() bool {\n\treturn false\n}\n\nfunc (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {\n\treturn nil, fmt.Errorf(\"TODO: multiqueue not implemented for android\")\n}\n"
  },
  {
    "path": "overlay/tun_darwin.go",
    "content": "//go:build !ios && !e2e_testing\n// +build !ios,!e2e_testing\n\npackage overlay\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/netip\"\n\t\"os\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"unsafe\"\n\n\t\"github.com/gaissmai/bart\"\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/config\"\n\t\"github.com/slackhq/nebula/routing\"\n\t\"github.com/slackhq/nebula/util\"\n\tnetroute \"golang.org/x/net/route\"\n\t\"golang.org/x/sys/unix\"\n)\n\ntype tun struct {\n\tio.ReadWriteCloser\n\tDevice      string\n\tvpnNetworks []netip.Prefix\n\tDefaultMTU  int\n\tRoutes      atomic.Pointer[[]Route]\n\trouteTree   atomic.Pointer[bart.Table[routing.Gateways]]\n\tlinkAddr    *netroute.LinkAddr\n\tl           *logrus.Logger\n\n\t// cache out buffer since we need to prepend 4 bytes for tun metadata\n\tout []byte\n}\n\ntype ifReq struct {\n\tName  [unix.IFNAMSIZ]byte\n\tFlags uint16\n\tpad   [8]byte\n}\n\nconst (\n\t_SIOCAIFADDR_IN6 = 2155899162\n\t_UTUN_OPT_IFNAME = 2\n\t_IN6_IFF_NODAD   = 0x0020\n\t_IN6_IFF_SECURED = 0x0400\n\tutunControlName  = \"com.apple.net.utun_control\"\n)\n\ntype ifreqMTU struct {\n\tName [16]byte\n\tMTU  int32\n\tpad  [8]byte\n}\n\ntype addrLifetime struct {\n\tExpire    float64\n\tPreferred float64\n\tVltime    uint32\n\tPltime    uint32\n}\n\ntype ifreqAlias4 struct {\n\tName     [unix.IFNAMSIZ]byte\n\tAddr     unix.RawSockaddrInet4\n\tDstAddr  unix.RawSockaddrInet4\n\tMaskAddr unix.RawSockaddrInet4\n}\n\ntype ifreqAlias6 struct {\n\tName       [unix.IFNAMSIZ]byte\n\tAddr       unix.RawSockaddrInet6\n\tDstAddr    unix.RawSockaddrInet6\n\tPrefixMask unix.RawSockaddrInet6\n\tFlags      uint32\n\tLifetime   addrLifetime\n}\n\nfunc newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (*tun, error) {\n\tname := c.GetString(\"tun.dev\", \"\")\n\tifIndex := -1\n\tif name != \"\" && name != \"utun\" {\n\t\t_, err := fmt.Sscanf(name, \"utun%d\", &ifIndex)\n\t\tif err != nil || ifIndex < 0 {\n\t\t\t// NOTE: we don't make this error so we don't break existing\n\t\t\t// configs that set a name before it was used.\n\t\t\tl.Warn(\"interface name must be utun[0-9]+ on Darwin, ignoring\")\n\t\t\tifIndex = -1\n\t\t}\n\t}\n\n\tfd, err := unix.Socket(unix.AF_SYSTEM, unix.SOCK_DGRAM, unix.AF_SYS_CONTROL)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"system socket: %v\", err)\n\t}\n\n\tvar ctlInfo = &unix.CtlInfo{}\n\tcopy(ctlInfo.Name[:], utunControlName)\n\n\terr = unix.IoctlCtlInfo(fd, ctlInfo)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"CTLIOCGINFO: %v\", err)\n\t}\n\n\terr = unix.Connect(fd, &unix.SockaddrCtl{\n\t\tID:   ctlInfo.Id,\n\t\tUnit: uint32(ifIndex) + 1,\n\t})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"SYS_CONNECT: %v\", err)\n\t}\n\n\tname, err = unix.GetsockoptString(fd, unix.AF_SYS_CONTROL, _UTUN_OPT_IFNAME)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to retrieve tun name: %w\", err)\n\t}\n\n\terr = unix.SetNonblock(fd, true)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"SetNonblock: %v\", err)\n\t}\n\n\tt := &tun{\n\t\tReadWriteCloser: os.NewFile(uintptr(fd), \"\"),\n\t\tDevice:          name,\n\t\tvpnNetworks:     vpnNetworks,\n\t\tDefaultMTU:      c.GetInt(\"tun.mtu\", DefaultMTU),\n\t\tl:               l,\n\t}\n\n\terr = t.reload(c, true)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tc.RegisterReloadCallback(func(c *config.C) {\n\t\terr := t.reload(c, false)\n\t\tif err != nil {\n\t\t\tutil.LogWithContextIfNeeded(\"failed to reload tun device\", err, t.l)\n\t\t}\n\t})\n\n\treturn t, nil\n}\n\nfunc (t *tun) deviceBytes() (o [16]byte) {\n\tfor i, c := range t.Device {\n\t\to[i] = byte(c)\n\t}\n\treturn\n}\n\nfunc newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ []netip.Prefix) (*tun, error) {\n\treturn nil, fmt.Errorf(\"newTunFromFd not supported in Darwin\")\n}\n\nfunc (t *tun) Close() error {\n\tif t.ReadWriteCloser != nil {\n\t\treturn t.ReadWriteCloser.Close()\n\t}\n\treturn nil\n}\n\nfunc (t *tun) Activate() error {\n\tdevName := t.deviceBytes()\n\n\ts, err := unix.Socket(\n\t\tunix.AF_INET,\n\t\tunix.SOCK_DGRAM,\n\t\tunix.IPPROTO_IP,\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer unix.Close(s)\n\n\tfd := uintptr(s)\n\n\t// Set the MTU on the device\n\tifm := ifreqMTU{Name: devName, MTU: int32(t.DefaultMTU)}\n\tif err = ioctl(fd, unix.SIOCSIFMTU, uintptr(unsafe.Pointer(&ifm))); err != nil {\n\t\treturn fmt.Errorf(\"failed to set tun mtu: %v\", err)\n\t}\n\n\t// Get the device flags\n\tifrf := ifReq{Name: devName}\n\tif err = ioctl(fd, unix.SIOCGIFFLAGS, uintptr(unsafe.Pointer(&ifrf))); err != nil {\n\t\treturn fmt.Errorf(\"failed to get tun flags: %s\", err)\n\t}\n\n\tlinkAddr, err := getLinkAddr(t.Device)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif linkAddr == nil {\n\t\treturn fmt.Errorf(\"unable to discover link_addr for tun interface\")\n\t}\n\tt.linkAddr = linkAddr\n\n\tfor _, network := range t.vpnNetworks {\n\t\tif network.Addr().Is4() {\n\t\t\terr = t.activate4(network)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t} else {\n\t\t\terr = t.activate6(network)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\t// Run the interface\n\tifrf.Flags = ifrf.Flags | unix.IFF_UP | unix.IFF_RUNNING\n\tif err = ioctl(fd, unix.SIOCSIFFLAGS, uintptr(unsafe.Pointer(&ifrf))); err != nil {\n\t\treturn fmt.Errorf(\"failed to run tun device: %s\", err)\n\t}\n\n\t// Unsafe path routes\n\treturn t.addRoutes(false)\n}\n\nfunc (t *tun) activate4(network netip.Prefix) error {\n\ts, err := unix.Socket(\n\t\tunix.AF_INET,\n\t\tunix.SOCK_DGRAM,\n\t\tunix.IPPROTO_IP,\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer unix.Close(s)\n\n\tifr := ifreqAlias4{\n\t\tName: t.deviceBytes(),\n\t\tAddr: unix.RawSockaddrInet4{\n\t\t\tLen:    unix.SizeofSockaddrInet4,\n\t\t\tFamily: unix.AF_INET,\n\t\t\tAddr:   network.Addr().As4(),\n\t\t},\n\t\tDstAddr: unix.RawSockaddrInet4{\n\t\t\tLen:    unix.SizeofSockaddrInet4,\n\t\t\tFamily: unix.AF_INET,\n\t\t\tAddr:   network.Addr().As4(),\n\t\t},\n\t\tMaskAddr: unix.RawSockaddrInet4{\n\t\t\tLen:    unix.SizeofSockaddrInet4,\n\t\t\tFamily: unix.AF_INET,\n\t\t\tAddr:   prefixToMask(network).As4(),\n\t\t},\n\t}\n\n\tif err := ioctl(uintptr(s), unix.SIOCAIFADDR, uintptr(unsafe.Pointer(&ifr))); err != nil {\n\t\treturn fmt.Errorf(\"failed to set tun v4 address: %s\", err)\n\t}\n\n\terr = addRoute(network, t.linkAddr)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (t *tun) activate6(network netip.Prefix) error {\n\ts, err := unix.Socket(\n\t\tunix.AF_INET6,\n\t\tunix.SOCK_DGRAM,\n\t\tunix.IPPROTO_IP,\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer unix.Close(s)\n\n\tifr := ifreqAlias6{\n\t\tName: t.deviceBytes(),\n\t\tAddr: unix.RawSockaddrInet6{\n\t\t\tLen:    unix.SizeofSockaddrInet6,\n\t\t\tFamily: unix.AF_INET6,\n\t\t\tAddr:   network.Addr().As16(),\n\t\t},\n\t\tPrefixMask: unix.RawSockaddrInet6{\n\t\t\tLen:    unix.SizeofSockaddrInet6,\n\t\t\tFamily: unix.AF_INET6,\n\t\t\tAddr:   prefixToMask(network).As16(),\n\t\t},\n\t\tLifetime: addrLifetime{\n\t\t\t// never expires\n\t\t\tVltime: 0xffffffff,\n\t\t\tPltime: 0xffffffff,\n\t\t},\n\t\tFlags: _IN6_IFF_NODAD,\n\t}\n\n\tif err := ioctl(uintptr(s), _SIOCAIFADDR_IN6, uintptr(unsafe.Pointer(&ifr))); err != nil {\n\t\treturn fmt.Errorf(\"failed to set tun address: %s\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (t *tun) reload(c *config.C, initial bool) error {\n\tchange, routes, err := getAllRoutesFromConfig(c, t.vpnNetworks, initial)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !initial && !change {\n\t\treturn nil\n\t}\n\n\trouteTree, err := makeRouteTree(t.l, routes, false)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Teach nebula how to handle the routes before establishing them in the system table\n\toldRoutes := t.Routes.Swap(&routes)\n\tt.routeTree.Store(routeTree)\n\n\tif !initial {\n\t\t// Remove first, if the system removes a wanted route hopefully it will be re-added next\n\t\terr := t.removeRoutes(findRemovedRoutes(routes, *oldRoutes))\n\t\tif err != nil {\n\t\t\tutil.LogWithContextIfNeeded(\"Failed to remove routes\", err, t.l)\n\t\t}\n\n\t\t// Ensure any routes we actually want are installed\n\t\terr = t.addRoutes(true)\n\t\tif err != nil {\n\t\t\t// Catch any stray logs\n\t\t\tutil.LogWithContextIfNeeded(\"Failed to add routes\", err, t.l)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (t *tun) RoutesFor(ip netip.Addr) routing.Gateways {\n\tr, ok := t.routeTree.Load().Lookup(ip)\n\tif ok {\n\t\treturn r\n\t}\n\treturn routing.Gateways{}\n}\n\n// Get the LinkAddr for the interface of the given name\n// Is there an easier way to fetch this when we create the interface?\n// Maybe SIOCGIFINDEX? but this doesn't appear to exist in the darwin headers.\nfunc getLinkAddr(name string) (*netroute.LinkAddr, error) {\n\trib, err := netroute.FetchRIB(unix.AF_UNSPEC, unix.NET_RT_IFLIST, 0)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tmsgs, err := netroute.ParseRIB(unix.NET_RT_IFLIST, rib)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, m := range msgs {\n\t\tswitch m := m.(type) {\n\t\tcase *netroute.InterfaceMessage:\n\t\t\tif m.Name == name {\n\t\t\t\tsa, ok := m.Addrs[unix.RTAX_IFP].(*netroute.LinkAddr)\n\t\t\t\tif ok {\n\t\t\t\t\treturn sa, nil\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil, nil\n}\n\nfunc (t *tun) addRoutes(logErrors bool) error {\n\troutes := *t.Routes.Load()\n\n\tfor _, r := range routes {\n\t\tif len(r.Via) == 0 || !r.Install {\n\t\t\t// We don't allow route MTUs so only install routes with a via\n\t\t\tcontinue\n\t\t}\n\n\t\terr := addRoute(r.Cidr, t.linkAddr)\n\t\tif err != nil {\n\t\t\tif errors.Is(err, unix.EEXIST) {\n\t\t\t\tt.l.WithField(\"route\", r.Cidr).\n\t\t\t\t\tWarnf(\"unable to add unsafe_route, identical route already exists\")\n\t\t\t} else {\n\t\t\t\tretErr := util.NewContextualError(\"Failed to add route\", map[string]any{\"route\": r}, err)\n\t\t\t\tif logErrors {\n\t\t\t\t\tretErr.Log(t.l)\n\t\t\t\t} else {\n\t\t\t\t\treturn retErr\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tt.l.WithField(\"route\", r).Info(\"Added route\")\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (t *tun) removeRoutes(routes []Route) error {\n\tfor _, r := range routes {\n\t\tif !r.Install {\n\t\t\tcontinue\n\t\t}\n\n\t\terr := delRoute(r.Cidr, t.linkAddr)\n\t\tif err != nil {\n\t\t\tt.l.WithError(err).WithField(\"route\", r).Error(\"Failed to remove route\")\n\t\t} else {\n\t\t\tt.l.WithField(\"route\", r).Info(\"Removed route\")\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc addRoute(prefix netip.Prefix, gateway netroute.Addr) error {\n\tsock, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to create AF_ROUTE socket: %v\", err)\n\t}\n\tdefer unix.Close(sock)\n\n\troute := &netroute.RouteMessage{\n\t\tVersion: unix.RTM_VERSION,\n\t\tType:    unix.RTM_ADD,\n\t\tFlags:   unix.RTF_UP,\n\t\tSeq:     1,\n\t}\n\n\tif prefix.Addr().Is4() {\n\t\troute.Addrs = []netroute.Addr{\n\t\t\tunix.RTAX_DST:     &netroute.Inet4Addr{IP: prefix.Masked().Addr().As4()},\n\t\t\tunix.RTAX_NETMASK: &netroute.Inet4Addr{IP: prefixToMask(prefix).As4()},\n\t\t\tunix.RTAX_GATEWAY: gateway,\n\t\t}\n\t} else {\n\t\troute.Addrs = []netroute.Addr{\n\t\t\tunix.RTAX_DST:     &netroute.Inet6Addr{IP: prefix.Masked().Addr().As16()},\n\t\t\tunix.RTAX_NETMASK: &netroute.Inet6Addr{IP: prefixToMask(prefix).As16()},\n\t\t\tunix.RTAX_GATEWAY: gateway,\n\t\t}\n\t}\n\n\tdata, err := route.Marshal()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create route.RouteMessage: %w\", err)\n\t}\n\n\t_, err = unix.Write(sock, data[:])\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to write route.RouteMessage to socket: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc delRoute(prefix netip.Prefix, gateway netroute.Addr) error {\n\tsock, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to create AF_ROUTE socket: %v\", err)\n\t}\n\tdefer unix.Close(sock)\n\n\troute := netroute.RouteMessage{\n\t\tVersion: unix.RTM_VERSION,\n\t\tType:    unix.RTM_DELETE,\n\t\tSeq:     1,\n\t}\n\n\tif prefix.Addr().Is4() {\n\t\troute.Addrs = []netroute.Addr{\n\t\t\tunix.RTAX_DST:     &netroute.Inet4Addr{IP: prefix.Masked().Addr().As4()},\n\t\t\tunix.RTAX_NETMASK: &netroute.Inet4Addr{IP: prefixToMask(prefix).As4()},\n\t\t\tunix.RTAX_GATEWAY: gateway,\n\t\t}\n\t} else {\n\t\troute.Addrs = []netroute.Addr{\n\t\t\tunix.RTAX_DST:     &netroute.Inet6Addr{IP: prefix.Masked().Addr().As16()},\n\t\t\tunix.RTAX_NETMASK: &netroute.Inet6Addr{IP: prefixToMask(prefix).As16()},\n\t\t\tunix.RTAX_GATEWAY: gateway,\n\t\t}\n\t}\n\n\tdata, err := route.Marshal()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create route.RouteMessage: %w\", err)\n\t}\n\t_, err = unix.Write(sock, data[:])\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to write route.RouteMessage to socket: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (t *tun) Read(to []byte) (int, error) {\n\tbuf := make([]byte, len(to)+4)\n\n\tn, err := t.ReadWriteCloser.Read(buf)\n\n\tcopy(to, buf[4:])\n\treturn n - 4, err\n}\n\n// Write is only valid for single threaded use\nfunc (t *tun) Write(from []byte) (int, error) {\n\tbuf := t.out\n\tif cap(buf) < len(from)+4 {\n\t\tbuf = make([]byte, len(from)+4)\n\t\tt.out = buf\n\t}\n\tbuf = buf[:len(from)+4]\n\n\tif len(from) == 0 {\n\t\treturn 0, syscall.EIO\n\t}\n\n\t// Determine the IP Family for the NULL L2 Header\n\tipVer := from[0] >> 4\n\tif ipVer == 4 {\n\t\tbuf[3] = syscall.AF_INET\n\t} else if ipVer == 6 {\n\t\tbuf[3] = syscall.AF_INET6\n\t} else {\n\t\treturn 0, fmt.Errorf(\"unable to determine IP version from packet\")\n\t}\n\n\tcopy(buf[4:], from)\n\n\tn, err := t.ReadWriteCloser.Write(buf)\n\treturn n - 4, err\n}\n\nfunc (t *tun) Networks() []netip.Prefix {\n\treturn t.vpnNetworks\n}\n\nfunc (t *tun) Name() string {\n\treturn t.Device\n}\n\nfunc (t *tun) SupportsMultiqueue() bool {\n\treturn false\n}\n\nfunc (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {\n\treturn nil, fmt.Errorf(\"TODO: multiqueue not implemented for darwin\")\n}\n"
  },
  {
    "path": "overlay/tun_disabled.go",
    "content": "package overlay\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/netip\"\n\t\"strings\"\n\n\t\"github.com/rcrowley/go-metrics\"\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/iputil\"\n\t\"github.com/slackhq/nebula/routing\"\n)\n\ntype disabledTun struct {\n\tread        chan []byte\n\tvpnNetworks []netip.Prefix\n\n\t// Track these metrics since we don't have the tun device to do it for us\n\ttx metrics.Counter\n\trx metrics.Counter\n\tl  *logrus.Logger\n}\n\nfunc newDisabledTun(vpnNetworks []netip.Prefix, queueLen int, metricsEnabled bool, l *logrus.Logger) *disabledTun {\n\ttun := &disabledTun{\n\t\tvpnNetworks: vpnNetworks,\n\t\tread:        make(chan []byte, queueLen),\n\t\tl:           l,\n\t}\n\n\tif metricsEnabled {\n\t\ttun.tx = metrics.GetOrRegisterCounter(\"messages.tx.message\", nil)\n\t\ttun.rx = metrics.GetOrRegisterCounter(\"messages.rx.message\", nil)\n\t} else {\n\t\ttun.tx = &metrics.NilCounter{}\n\t\ttun.rx = &metrics.NilCounter{}\n\t}\n\n\treturn tun\n}\n\nfunc (*disabledTun) Activate() error {\n\treturn nil\n}\n\nfunc (*disabledTun) RoutesFor(addr netip.Addr) routing.Gateways {\n\treturn routing.Gateways{}\n}\n\nfunc (t *disabledTun) Networks() []netip.Prefix {\n\treturn t.vpnNetworks\n}\n\nfunc (*disabledTun) Name() string {\n\treturn \"disabled\"\n}\n\nfunc (t *disabledTun) Read(b []byte) (int, error) {\n\tr, ok := <-t.read\n\tif !ok {\n\t\treturn 0, io.EOF\n\t}\n\n\tif len(r) > len(b) {\n\t\treturn 0, fmt.Errorf(\"packet larger than mtu: %d > %d bytes\", len(r), len(b))\n\t}\n\n\tt.tx.Inc(1)\n\tif t.l.Level >= logrus.DebugLevel {\n\t\tt.l.WithField(\"raw\", prettyPacket(r)).Debugf(\"Write payload\")\n\t}\n\n\treturn copy(b, r), nil\n}\n\nfunc (t *disabledTun) handleICMPEchoRequest(b []byte) bool {\n\tout := make([]byte, len(b))\n\tout = iputil.CreateICMPEchoResponse(b, out)\n\tif out == nil {\n\t\treturn false\n\t}\n\n\t// attempt to write it, but don't block\n\tselect {\n\tcase t.read <- out:\n\tdefault:\n\t\tt.l.Debugf(\"tun_disabled: dropped ICMP Echo Reply response\")\n\t}\n\n\treturn true\n}\n\nfunc (t *disabledTun) Write(b []byte) (int, error) {\n\tt.rx.Inc(1)\n\n\t// Check for ICMP Echo Request before spending time doing the full parsing\n\tif t.handleICMPEchoRequest(b) {\n\t\tif t.l.Level >= logrus.DebugLevel {\n\t\t\tt.l.WithField(\"raw\", prettyPacket(b)).Debugf(\"Disabled tun responded to ICMP Echo Request\")\n\t\t}\n\t} else if t.l.Level >= logrus.DebugLevel {\n\t\tt.l.WithField(\"raw\", prettyPacket(b)).Debugf(\"Disabled tun received unexpected payload\")\n\t}\n\treturn len(b), nil\n}\n\nfunc (t *disabledTun) SupportsMultiqueue() bool {\n\treturn true\n}\n\nfunc (t *disabledTun) NewMultiQueueReader() (io.ReadWriteCloser, error) {\n\treturn t, nil\n}\n\nfunc (t *disabledTun) Close() error {\n\tif t.read != nil {\n\t\tclose(t.read)\n\t\tt.read = nil\n\t}\n\treturn nil\n}\n\ntype prettyPacket []byte\n\nfunc (p prettyPacket) String() string {\n\tvar s strings.Builder\n\n\tfor i, b := range p {\n\t\tif i > 0 && i%8 == 0 {\n\t\t\ts.WriteString(\" \")\n\t\t}\n\t\ts.WriteString(fmt.Sprintf(\"%02x \", b))\n\t}\n\n\treturn s.String()\n}\n"
  },
  {
    "path": "overlay/tun_freebsd.go",
    "content": "//go:build !e2e_testing\n// +build !e2e_testing\n\npackage overlay\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/fs\"\n\t\"net/netip\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"time\"\n\t\"unsafe\"\n\n\t\"github.com/gaissmai/bart\"\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/config\"\n\t\"github.com/slackhq/nebula/routing\"\n\t\"github.com/slackhq/nebula/util\"\n\tnetroute \"golang.org/x/net/route\"\n\t\"golang.org/x/sys/unix\"\n)\n\nconst (\n\t// FIODGNAME is defined in sys/sys/filio.h on FreeBSD\n\t// For 32-bit systems, use FIODGNAME_32 (not defined in this file: 0x80086678)\n\tFIODGNAME        = 0x80106678\n\tTUNSIFMODE       = 0x8004745e\n\tTUNSIFHEAD       = 0x80047460\n\tOSIOCAIFADDR_IN6 = 0x8088691b\n\tIN6_IFF_NODAD    = 0x0020\n)\n\ntype fiodgnameArg struct {\n\tlength int32\n\tpad    [4]byte\n\tbuf    unsafe.Pointer\n}\n\ntype ifreqRename struct {\n\tName [unix.IFNAMSIZ]byte\n\tData uintptr\n}\n\ntype ifreqDestroy struct {\n\tName [unix.IFNAMSIZ]byte\n\tpad  [16]byte\n}\n\ntype ifReq struct {\n\tName  [unix.IFNAMSIZ]byte\n\tFlags uint16\n}\n\ntype ifreqMTU struct {\n\tName [unix.IFNAMSIZ]byte\n\tMTU  int32\n}\n\ntype addrLifetime struct {\n\tExpire    uint64\n\tPreferred uint64\n\tVltime    uint32\n\tPltime    uint32\n}\n\ntype ifreqAlias4 struct {\n\tName     [unix.IFNAMSIZ]byte\n\tAddr     unix.RawSockaddrInet4\n\tDstAddr  unix.RawSockaddrInet4\n\tMaskAddr unix.RawSockaddrInet4\n\tVHid     uint32\n}\n\ntype ifreqAlias6 struct {\n\tName       [unix.IFNAMSIZ]byte\n\tAddr       unix.RawSockaddrInet6\n\tDstAddr    unix.RawSockaddrInet6\n\tPrefixMask unix.RawSockaddrInet6\n\tFlags      uint32\n\tLifetime   addrLifetime\n\tVHid       uint32\n}\n\ntype tun struct {\n\tDevice      string\n\tvpnNetworks []netip.Prefix\n\tMTU         int\n\tRoutes      atomic.Pointer[[]Route]\n\trouteTree   atomic.Pointer[bart.Table[routing.Gateways]]\n\tlinkAddr    *netroute.LinkAddr\n\tl           *logrus.Logger\n\tdevFd       int\n}\n\nfunc (t *tun) Read(to []byte) (int, error) {\n\t// use readv() to read from the tunnel device, to eliminate the need for copying the buffer\n\tif t.devFd < 0 {\n\t\treturn -1, syscall.EINVAL\n\t}\n\n\t// first 4 bytes is protocol family, in network byte order\n\thead := make([]byte, 4)\n\n\tiovecs := []syscall.Iovec{\n\t\t{&head[0], 4},\n\t\t{&to[0], uint64(len(to))},\n\t}\n\n\tn, _, errno := syscall.Syscall(syscall.SYS_READV, uintptr(t.devFd), uintptr(unsafe.Pointer(&iovecs[0])), uintptr(2))\n\n\tvar err error\n\tif errno != 0 {\n\t\terr = syscall.Errno(errno)\n\t} else {\n\t\terr = nil\n\t}\n\t// fix bytes read number to exclude header\n\tbytesRead := int(n)\n\tif bytesRead < 0 {\n\t\treturn bytesRead, err\n\t} else if bytesRead < 4 {\n\t\treturn 0, err\n\t} else {\n\t\treturn bytesRead - 4, err\n\t}\n}\n\n// Write is only valid for single threaded use\nfunc (t *tun) Write(from []byte) (int, error) {\n\t// use writev() to write to the tunnel device, to eliminate the need for copying the buffer\n\tif t.devFd < 0 {\n\t\treturn -1, syscall.EINVAL\n\t}\n\n\tif len(from) <= 1 {\n\t\treturn 0, syscall.EIO\n\t}\n\tipVer := from[0] >> 4\n\tvar head []byte\n\t// first 4 bytes is protocol family, in network byte order\n\tif ipVer == 4 {\n\t\thead = []byte{0, 0, 0, syscall.AF_INET}\n\t} else if ipVer == 6 {\n\t\thead = []byte{0, 0, 0, syscall.AF_INET6}\n\t} else {\n\t\treturn 0, fmt.Errorf(\"unable to determine IP version from packet\")\n\t}\n\tiovecs := []syscall.Iovec{\n\t\t{&head[0], 4},\n\t\t{&from[0], uint64(len(from))},\n\t}\n\n\tn, _, errno := syscall.Syscall(syscall.SYS_WRITEV, uintptr(t.devFd), uintptr(unsafe.Pointer(&iovecs[0])), uintptr(2))\n\n\tvar err error\n\tif errno != 0 {\n\t\terr = syscall.Errno(errno)\n\t} else {\n\t\terr = nil\n\t}\n\n\treturn int(n) - 4, err\n}\n\nfunc (t *tun) Close() error {\n\tif t.devFd >= 0 {\n\t\terr := syscall.Close(t.devFd)\n\t\tif err != nil {\n\t\t\tt.l.WithError(err).Error(\"Error closing device\")\n\t\t}\n\t\tt.devFd = -1\n\n\t\tc := make(chan struct{})\n\t\tgo func() {\n\t\t\t// destroying the interface can block if a read() is still pending. Do this asynchronously.\n\t\t\tdefer close(c)\n\t\t\ts, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_IP)\n\t\t\tif err == nil {\n\t\t\t\tdefer syscall.Close(s)\n\t\t\t\tifreq := ifreqDestroy{Name: t.deviceBytes()}\n\t\t\t\terr = ioctl(uintptr(s), syscall.SIOCIFDESTROY, uintptr(unsafe.Pointer(&ifreq)))\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.l.WithError(err).Error(\"Error destroying tunnel\")\n\t\t\t}\n\t\t}()\n\n\t\t// wait up to 1 second so we start blocking at the ioctl\n\t\tselect {\n\t\tcase <-c:\n\t\tcase <-time.After(1 * time.Second):\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ []netip.Prefix) (*tun, error) {\n\treturn nil, fmt.Errorf(\"newTunFromFd not supported in FreeBSD\")\n}\n\nfunc newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (*tun, error) {\n\t// Try to open existing tun device\n\tvar fd int\n\tvar err error\n\tdeviceName := c.GetString(\"tun.dev\", \"\")\n\tif deviceName != \"\" {\n\t\tfd, err = syscall.Open(\"/dev/\"+deviceName, syscall.O_RDWR, 0)\n\t}\n\tif errors.Is(err, fs.ErrNotExist) || deviceName == \"\" {\n\t\t// If the device doesn't already exist, request a new one and rename it\n\t\tfd, err = syscall.Open(\"/dev/tun\", syscall.O_RDWR, 0)\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Read the name of the interface\n\tvar name [16]byte\n\targ := fiodgnameArg{length: 16, buf: unsafe.Pointer(&name)}\n\tctrlErr := ioctl(uintptr(fd), FIODGNAME, uintptr(unsafe.Pointer(&arg)))\n\n\tif ctrlErr == nil {\n\t\t// set broadcast mode and multicast\n\t\tifmode := uint32(unix.IFF_BROADCAST | unix.IFF_MULTICAST)\n\t\tctrlErr = ioctl(uintptr(fd), TUNSIFMODE, uintptr(unsafe.Pointer(&ifmode)))\n\t}\n\n\tif ctrlErr == nil {\n\t\t// turn on link-layer mode, to support ipv6\n\t\tifhead := uint32(1)\n\t\tctrlErr = ioctl(uintptr(fd), TUNSIFHEAD, uintptr(unsafe.Pointer(&ifhead)))\n\t}\n\n\tif ctrlErr != nil {\n\t\treturn nil, err\n\t}\n\n\tifName := string(bytes.TrimRight(name[:], \"\\x00\"))\n\tif deviceName == \"\" {\n\t\tdeviceName = ifName\n\t}\n\n\t// If the name doesn't match the desired interface name, rename it now\n\tif ifName != deviceName {\n\t\ts, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_IP)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdefer syscall.Close(s)\n\n\t\tfd := uintptr(s)\n\n\t\tvar fromName [16]byte\n\t\tvar toName [16]byte\n\t\tcopy(fromName[:], ifName)\n\t\tcopy(toName[:], deviceName)\n\n\t\tifrr := ifreqRename{\n\t\t\tName: fromName,\n\t\t\tData: uintptr(unsafe.Pointer(&toName)),\n\t\t}\n\n\t\t// Set the device name\n\t\t_ = ioctl(fd, syscall.SIOCSIFNAME, uintptr(unsafe.Pointer(&ifrr)))\n\t}\n\n\tt := &tun{\n\t\tDevice:      deviceName,\n\t\tvpnNetworks: vpnNetworks,\n\t\tMTU:         c.GetInt(\"tun.mtu\", DefaultMTU),\n\t\tl:           l,\n\t\tdevFd:       fd,\n\t}\n\n\terr = t.reload(c, true)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tc.RegisterReloadCallback(func(c *config.C) {\n\t\terr := t.reload(c, false)\n\t\tif err != nil {\n\t\t\tutil.LogWithContextIfNeeded(\"failed to reload tun device\", err, t.l)\n\t\t}\n\t})\n\n\treturn t, nil\n}\n\nfunc (t *tun) addIp(cidr netip.Prefix) error {\n\tif cidr.Addr().Is4() {\n\t\tifr := ifreqAlias4{\n\t\t\tName: t.deviceBytes(),\n\t\t\tAddr: unix.RawSockaddrInet4{\n\t\t\t\tLen:    unix.SizeofSockaddrInet4,\n\t\t\t\tFamily: unix.AF_INET,\n\t\t\t\tAddr:   cidr.Addr().As4(),\n\t\t\t},\n\t\t\tDstAddr: unix.RawSockaddrInet4{\n\t\t\t\tLen:    unix.SizeofSockaddrInet4,\n\t\t\t\tFamily: unix.AF_INET,\n\t\t\t\tAddr:   getBroadcast(cidr).As4(),\n\t\t\t},\n\t\t\tMaskAddr: unix.RawSockaddrInet4{\n\t\t\t\tLen:    unix.SizeofSockaddrInet4,\n\t\t\t\tFamily: unix.AF_INET,\n\t\t\t\tAddr:   prefixToMask(cidr).As4(),\n\t\t\t},\n\t\t\tVHid: 0,\n\t\t}\n\t\ts, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_IP)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer syscall.Close(s)\n\t\t// Note: unix.SIOCAIFADDR corresponds to FreeBSD's OSIOCAIFADDR\n\t\tif err := ioctl(uintptr(s), unix.SIOCAIFADDR, uintptr(unsafe.Pointer(&ifr))); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set tun address %s: %s\", cidr.Addr().String(), err)\n\t\t}\n\t\treturn nil\n\t}\n\n\tif cidr.Addr().Is6() {\n\t\tifr := ifreqAlias6{\n\t\t\tName: t.deviceBytes(),\n\t\t\tAddr: unix.RawSockaddrInet6{\n\t\t\t\tLen:    unix.SizeofSockaddrInet6,\n\t\t\t\tFamily: unix.AF_INET6,\n\t\t\t\tAddr:   cidr.Addr().As16(),\n\t\t\t},\n\t\t\tPrefixMask: unix.RawSockaddrInet6{\n\t\t\t\tLen:    unix.SizeofSockaddrInet6,\n\t\t\t\tFamily: unix.AF_INET6,\n\t\t\t\tAddr:   prefixToMask(cidr).As16(),\n\t\t\t},\n\t\t\tLifetime: addrLifetime{\n\t\t\t\tExpire:    0,\n\t\t\t\tPreferred: 0,\n\t\t\t\tVltime:    0xffffffff,\n\t\t\t\tPltime:    0xffffffff,\n\t\t\t},\n\t\t\tFlags: IN6_IFF_NODAD,\n\t\t}\n\t\ts, err := syscall.Socket(syscall.AF_INET6, syscall.SOCK_DGRAM, syscall.IPPROTO_IP)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer syscall.Close(s)\n\n\t\tif err := ioctl(uintptr(s), OSIOCAIFADDR_IN6, uintptr(unsafe.Pointer(&ifr))); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set tun address %s: %s\", cidr.Addr().String(), err)\n\t\t}\n\t\treturn nil\n\t}\n\n\treturn fmt.Errorf(\"unknown address type %v\", cidr)\n}\n\nfunc (t *tun) Activate() error {\n\t// Setup our default MTU\n\terr := t.setMTU()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlinkAddr, err := getLinkAddr(t.Device)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif linkAddr == nil {\n\t\treturn fmt.Errorf(\"unable to discover link_addr for tun interface\")\n\t}\n\tt.linkAddr = linkAddr\n\n\tfor i := range t.vpnNetworks {\n\t\terr := t.addIp(t.vpnNetworks[i])\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn t.addRoutes(false)\n}\n\nfunc (t *tun) setMTU() error {\n\t// Set the MTU on the device\n\ts, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_IP)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer syscall.Close(s)\n\n\tifm := ifreqMTU{Name: t.deviceBytes(), MTU: int32(t.MTU)}\n\terr = ioctl(uintptr(s), unix.SIOCSIFMTU, uintptr(unsafe.Pointer(&ifm)))\n\treturn err\n}\n\nfunc (t *tun) reload(c *config.C, initial bool) error {\n\tchange, routes, err := getAllRoutesFromConfig(c, t.vpnNetworks, initial)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !initial && !change {\n\t\treturn nil\n\t}\n\n\trouteTree, err := makeRouteTree(t.l, routes, false)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Teach nebula how to handle the routes before establishing them in the system table\n\toldRoutes := t.Routes.Swap(&routes)\n\tt.routeTree.Store(routeTree)\n\n\tif !initial {\n\t\t// Remove first, if the system removes a wanted route hopefully it will be re-added next\n\t\terr := t.removeRoutes(findRemovedRoutes(routes, *oldRoutes))\n\t\tif err != nil {\n\t\t\tutil.LogWithContextIfNeeded(\"Failed to remove routes\", err, t.l)\n\t\t}\n\n\t\t// Ensure any routes we actually want are installed\n\t\terr = t.addRoutes(true)\n\t\tif err != nil {\n\t\t\t// Catch any stray logs\n\t\t\tutil.LogWithContextIfNeeded(\"Failed to add routes\", err, t.l)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (t *tun) RoutesFor(ip netip.Addr) routing.Gateways {\n\tr, _ := t.routeTree.Load().Lookup(ip)\n\treturn r\n}\n\nfunc (t *tun) Networks() []netip.Prefix {\n\treturn t.vpnNetworks\n}\n\nfunc (t *tun) Name() string {\n\treturn t.Device\n}\n\nfunc (t *tun) SupportsMultiqueue() bool {\n\treturn false\n}\n\nfunc (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {\n\treturn nil, fmt.Errorf(\"TODO: multiqueue not implemented for freebsd\")\n}\n\nfunc (t *tun) addRoutes(logErrors bool) error {\n\troutes := *t.Routes.Load()\n\tfor _, r := range routes {\n\t\tif len(r.Via) == 0 || !r.Install {\n\t\t\t// We don't allow route MTUs so only install routes with a via\n\t\t\tcontinue\n\t\t}\n\n\t\terr := addRoute(r.Cidr, t.linkAddr)\n\t\tif err != nil {\n\t\t\tretErr := util.NewContextualError(\"Failed to add route\", map[string]any{\"route\": r}, err)\n\t\t\tif logErrors {\n\t\t\t\tretErr.Log(t.l)\n\t\t\t} else {\n\t\t\t\treturn retErr\n\t\t\t}\n\t\t} else {\n\t\t\tt.l.WithField(\"route\", r).Info(\"Added route\")\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (t *tun) removeRoutes(routes []Route) error {\n\tfor _, r := range routes {\n\t\tif !r.Install {\n\t\t\tcontinue\n\t\t}\n\n\t\terr := delRoute(r.Cidr, t.linkAddr)\n\t\tif err != nil {\n\t\t\tt.l.WithError(err).WithField(\"route\", r).Error(\"Failed to remove route\")\n\t\t} else {\n\t\t\tt.l.WithField(\"route\", r).Info(\"Removed route\")\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (t *tun) deviceBytes() (o [16]byte) {\n\tfor i, c := range t.Device {\n\t\to[i] = byte(c)\n\t}\n\treturn\n}\n\nfunc addRoute(prefix netip.Prefix, gateway netroute.Addr) error {\n\tsock, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to create AF_ROUTE socket: %v\", err)\n\t}\n\tdefer unix.Close(sock)\n\n\troute := &netroute.RouteMessage{\n\t\tVersion: unix.RTM_VERSION,\n\t\tType:    unix.RTM_ADD,\n\t\tFlags:   unix.RTF_UP,\n\t\tSeq:     1,\n\t}\n\n\tif prefix.Addr().Is4() {\n\t\troute.Addrs = []netroute.Addr{\n\t\t\tunix.RTAX_DST:     &netroute.Inet4Addr{IP: prefix.Masked().Addr().As4()},\n\t\t\tunix.RTAX_NETMASK: &netroute.Inet4Addr{IP: prefixToMask(prefix).As4()},\n\t\t\tunix.RTAX_GATEWAY: gateway,\n\t\t}\n\t} else {\n\t\troute.Addrs = []netroute.Addr{\n\t\t\tunix.RTAX_DST:     &netroute.Inet6Addr{IP: prefix.Masked().Addr().As16()},\n\t\t\tunix.RTAX_NETMASK: &netroute.Inet6Addr{IP: prefixToMask(prefix).As16()},\n\t\t\tunix.RTAX_GATEWAY: gateway,\n\t\t}\n\t}\n\n\tdata, err := route.Marshal()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create route.RouteMessage: %w\", err)\n\t}\n\n\t_, err = unix.Write(sock, data[:])\n\tif err != nil {\n\t\tif errors.Is(err, unix.EEXIST) {\n\t\t\t// Try to do a change\n\t\t\troute.Type = unix.RTM_CHANGE\n\t\t\tdata, err = route.Marshal()\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to create route.RouteMessage for change: %w\", err)\n\t\t\t}\n\t\t\t_, err = unix.Write(sock, data[:])\n\t\t\tfmt.Println(\"DOING CHANGE\")\n\t\t\treturn err\n\t\t}\n\t\treturn fmt.Errorf(\"failed to write route.RouteMessage to socket: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc delRoute(prefix netip.Prefix, gateway netroute.Addr) error {\n\tsock, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to create AF_ROUTE socket: %v\", err)\n\t}\n\tdefer unix.Close(sock)\n\n\troute := netroute.RouteMessage{\n\t\tVersion: unix.RTM_VERSION,\n\t\tType:    unix.RTM_DELETE,\n\t\tSeq:     1,\n\t}\n\n\tif prefix.Addr().Is4() {\n\t\troute.Addrs = []netroute.Addr{\n\t\t\tunix.RTAX_DST:     &netroute.Inet4Addr{IP: prefix.Masked().Addr().As4()},\n\t\t\tunix.RTAX_NETMASK: &netroute.Inet4Addr{IP: prefixToMask(prefix).As4()},\n\t\t\tunix.RTAX_GATEWAY: gateway,\n\t\t}\n\t} else {\n\t\troute.Addrs = []netroute.Addr{\n\t\t\tunix.RTAX_DST:     &netroute.Inet6Addr{IP: prefix.Masked().Addr().As16()},\n\t\t\tunix.RTAX_NETMASK: &netroute.Inet6Addr{IP: prefixToMask(prefix).As16()},\n\t\t\tunix.RTAX_GATEWAY: gateway,\n\t\t}\n\t}\n\n\tdata, err := route.Marshal()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create route.RouteMessage: %w\", err)\n\t}\n\t_, err = unix.Write(sock, data[:])\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to write route.RouteMessage to socket: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// getLinkAddr Gets the link address for the interface of the given name\nfunc getLinkAddr(name string) (*netroute.LinkAddr, error) {\n\trib, err := netroute.FetchRIB(unix.AF_UNSPEC, unix.NET_RT_IFLIST, 0)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tmsgs, err := netroute.ParseRIB(unix.NET_RT_IFLIST, rib)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, m := range msgs {\n\t\tswitch m := m.(type) {\n\t\tcase *netroute.InterfaceMessage:\n\t\t\tif m.Name == name {\n\t\t\t\tsa, ok := m.Addrs[unix.RTAX_IFP].(*netroute.LinkAddr)\n\t\t\t\tif ok {\n\t\t\t\t\treturn sa, nil\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil, nil\n}\n"
  },
  {
    "path": "overlay/tun_ios.go",
    "content": "//go:build ios && !e2e_testing\n// +build ios,!e2e_testing\n\npackage overlay\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/netip\"\n\t\"os\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\n\t\"github.com/gaissmai/bart\"\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/config\"\n\t\"github.com/slackhq/nebula/routing\"\n\t\"github.com/slackhq/nebula/util\"\n)\n\ntype tun struct {\n\tio.ReadWriteCloser\n\tvpnNetworks []netip.Prefix\n\tRoutes      atomic.Pointer[[]Route]\n\trouteTree   atomic.Pointer[bart.Table[routing.Gateways]]\n\tl           *logrus.Logger\n}\n\nfunc newTun(_ *config.C, _ *logrus.Logger, _ []netip.Prefix, _ bool) (*tun, error) {\n\treturn nil, fmt.Errorf(\"newTun not supported in iOS\")\n}\n\nfunc newTunFromFd(c *config.C, l *logrus.Logger, deviceFd int, vpnNetworks []netip.Prefix) (*tun, error) {\n\tfile := os.NewFile(uintptr(deviceFd), \"/dev/tun\")\n\tt := &tun{\n\t\tvpnNetworks:     vpnNetworks,\n\t\tReadWriteCloser: &tunReadCloser{f: file},\n\t\tl:               l,\n\t}\n\n\terr := t.reload(c, true)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tc.RegisterReloadCallback(func(c *config.C) {\n\t\terr := t.reload(c, false)\n\t\tif err != nil {\n\t\t\tutil.LogWithContextIfNeeded(\"failed to reload tun device\", err, t.l)\n\t\t}\n\t})\n\n\treturn t, nil\n}\n\nfunc (t *tun) Activate() error {\n\treturn nil\n}\n\nfunc (t *tun) reload(c *config.C, initial bool) error {\n\tchange, routes, err := getAllRoutesFromConfig(c, t.vpnNetworks, initial)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !initial && !change {\n\t\treturn nil\n\t}\n\n\trouteTree, err := makeRouteTree(t.l, routes, false)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Teach nebula how to handle the routes\n\tt.Routes.Store(&routes)\n\tt.routeTree.Store(routeTree)\n\treturn nil\n}\n\nfunc (t *tun) RoutesFor(ip netip.Addr) routing.Gateways {\n\tr, _ := t.routeTree.Load().Lookup(ip)\n\treturn r\n}\n\n// The following is hoisted up from water, we do this so we can inject our own fd on iOS\ntype tunReadCloser struct {\n\tf io.ReadWriteCloser\n\n\trMu  sync.Mutex\n\trBuf []byte\n\n\twMu  sync.Mutex\n\twBuf []byte\n}\n\nfunc (tr *tunReadCloser) Read(to []byte) (int, error) {\n\ttr.rMu.Lock()\n\tdefer tr.rMu.Unlock()\n\n\tif cap(tr.rBuf) < len(to)+4 {\n\t\ttr.rBuf = make([]byte, len(to)+4)\n\t}\n\ttr.rBuf = tr.rBuf[:len(to)+4]\n\n\tn, err := tr.f.Read(tr.rBuf)\n\tcopy(to, tr.rBuf[4:])\n\treturn n - 4, err\n}\n\nfunc (tr *tunReadCloser) Write(from []byte) (int, error) {\n\tif len(from) == 0 {\n\t\treturn 0, syscall.EIO\n\t}\n\n\ttr.wMu.Lock()\n\tdefer tr.wMu.Unlock()\n\n\tif cap(tr.wBuf) < len(from)+4 {\n\t\ttr.wBuf = make([]byte, len(from)+4)\n\t}\n\ttr.wBuf = tr.wBuf[:len(from)+4]\n\n\t// Determine the IP Family for the NULL L2 Header\n\tipVer := from[0] >> 4\n\tif ipVer == 4 {\n\t\ttr.wBuf[3] = syscall.AF_INET\n\t} else if ipVer == 6 {\n\t\ttr.wBuf[3] = syscall.AF_INET6\n\t} else {\n\t\treturn 0, errors.New(\"unable to determine IP version from packet\")\n\t}\n\n\tcopy(tr.wBuf[4:], from)\n\n\tn, err := tr.f.Write(tr.wBuf)\n\treturn n - 4, err\n}\n\nfunc (tr *tunReadCloser) Close() error {\n\treturn tr.f.Close()\n}\n\nfunc (t *tun) Networks() []netip.Prefix {\n\treturn t.vpnNetworks\n}\n\nfunc (t *tun) Name() string {\n\treturn \"iOS\"\n}\n\nfunc (t *tun) SupportsMultiqueue() bool {\n\treturn false\n}\n\nfunc (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {\n\treturn nil, fmt.Errorf(\"TODO: multiqueue not implemented for ios\")\n}\n"
  },
  {
    "path": "overlay/tun_linux.go",
    "content": "//go:build !android && !e2e_testing\n// +build !android,!e2e_testing\n\npackage overlay\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/netip\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\t\"unsafe\"\n\n\t\"github.com/gaissmai/bart\"\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/config\"\n\t\"github.com/slackhq/nebula/routing\"\n\t\"github.com/slackhq/nebula/util\"\n\t\"github.com/vishvananda/netlink\"\n\t\"golang.org/x/sys/unix\"\n)\n\ntype tun struct {\n\tio.ReadWriteCloser\n\tfd          int\n\tDevice      string\n\tvpnNetworks []netip.Prefix\n\tMaxMTU      int\n\tDefaultMTU  int\n\tTXQueueLen  int\n\tdeviceIndex int\n\tioctlFd     uintptr\n\n\tRoutes                    atomic.Pointer[[]Route]\n\trouteTree                 atomic.Pointer[bart.Table[routing.Gateways]]\n\trouteChan                 chan struct{}\n\tuseSystemRoutes           bool\n\tuseSystemRoutesBufferSize int\n\n\t// These are routes learned from `tun.use_system_route_table`\n\t// stored here to make it easier to restore them after a reload\n\troutesFromSystem     map[netip.Prefix]routing.Gateways\n\troutesFromSystemLock sync.Mutex\n\n\tl *logrus.Logger\n}\n\nfunc (t *tun) Networks() []netip.Prefix {\n\treturn t.vpnNetworks\n}\n\ntype ifReq struct {\n\tName  [16]byte\n\tFlags uint16\n\tpad   [8]byte\n}\n\ntype ifreqMTU struct {\n\tName [16]byte\n\tMTU  int32\n\tpad  [8]byte\n}\n\ntype ifreqQLEN struct {\n\tName  [16]byte\n\tValue int32\n\tpad   [8]byte\n}\n\nfunc newTunFromFd(c *config.C, l *logrus.Logger, deviceFd int, vpnNetworks []netip.Prefix) (*tun, error) {\n\tfile := os.NewFile(uintptr(deviceFd), \"/dev/net/tun\")\n\n\tt, err := newTunGeneric(c, l, file, vpnNetworks)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tt.Device = \"tun0\"\n\n\treturn t, nil\n}\n\nfunc newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, multiqueue bool) (*tun, error) {\n\tfd, err := unix.Open(\"/dev/net/tun\", os.O_RDWR, 0)\n\tif err != nil {\n\t\t// If /dev/net/tun doesn't exist, try to create it (will happen in docker)\n\t\tif os.IsNotExist(err) {\n\t\t\terr = os.MkdirAll(\"/dev/net\", 0755)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"/dev/net/tun doesn't exist, failed to mkdir -p /dev/net: %w\", err)\n\t\t\t}\n\t\t\terr = unix.Mknod(\"/dev/net/tun\", unix.S_IFCHR|0600, int(unix.Mkdev(10, 200)))\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"failed to create /dev/net/tun: %w\", err)\n\t\t\t}\n\n\t\t\tfd, err = unix.Open(\"/dev/net/tun\", os.O_RDWR, 0)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"created /dev/net/tun, but still failed: %w\", err)\n\t\t\t}\n\t\t} else {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tvar req ifReq\n\treq.Flags = uint16(unix.IFF_TUN | unix.IFF_NO_PI)\n\tif multiqueue {\n\t\treq.Flags |= unix.IFF_MULTI_QUEUE\n\t}\n\tnameStr := c.GetString(\"tun.dev\", \"\")\n\tcopy(req.Name[:], nameStr)\n\tif err = ioctl(uintptr(fd), uintptr(unix.TUNSETIFF), uintptr(unsafe.Pointer(&req))); err != nil {\n\t\treturn nil, &NameError{\n\t\t\tName:       nameStr,\n\t\t\tUnderlying: err,\n\t\t}\n\t}\n\tname := strings.Trim(string(req.Name[:]), \"\\x00\")\n\n\tfile := os.NewFile(uintptr(fd), \"/dev/net/tun\")\n\tt, err := newTunGeneric(c, l, file, vpnNetworks)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tt.Device = name\n\n\treturn t, nil\n}\n\nfunc newTunGeneric(c *config.C, l *logrus.Logger, file *os.File, vpnNetworks []netip.Prefix) (*tun, error) {\n\tt := &tun{\n\t\tReadWriteCloser:           file,\n\t\tfd:                        int(file.Fd()),\n\t\tvpnNetworks:               vpnNetworks,\n\t\tTXQueueLen:                c.GetInt(\"tun.tx_queue\", 500),\n\t\tuseSystemRoutes:           c.GetBool(\"tun.use_system_route_table\", false),\n\t\tuseSystemRoutesBufferSize: c.GetInt(\"tun.use_system_route_table_buffer_size\", 0),\n\t\troutesFromSystem:          map[netip.Prefix]routing.Gateways{},\n\t\tl:                         l,\n\t}\n\n\terr := t.reload(c, true)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tc.RegisterReloadCallback(func(c *config.C) {\n\t\terr := t.reload(c, false)\n\t\tif err != nil {\n\t\t\tutil.LogWithContextIfNeeded(\"failed to reload tun device\", err, t.l)\n\t\t}\n\t})\n\n\treturn t, nil\n}\n\nfunc (t *tun) reload(c *config.C, initial bool) error {\n\trouteChange, routes, err := getAllRoutesFromConfig(c, t.vpnNetworks, initial)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !initial && !routeChange && !c.HasChanged(\"tun.mtu\") {\n\t\treturn nil\n\t}\n\n\trouteTree, err := makeRouteTree(t.l, routes, true)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Bring along any routes learned from the system route table on reload\n\tt.routesFromSystemLock.Lock()\n\tfor dst, gw := range t.routesFromSystem {\n\t\trouteTree.Insert(dst, gw)\n\t}\n\tt.routesFromSystemLock.Unlock()\n\n\toldDefaultMTU := t.DefaultMTU\n\toldMaxMTU := t.MaxMTU\n\tnewDefaultMTU := c.GetInt(\"tun.mtu\", DefaultMTU)\n\tnewMaxMTU := newDefaultMTU\n\tfor i, r := range routes {\n\t\tif r.MTU == 0 {\n\t\t\troutes[i].MTU = newDefaultMTU\n\t\t}\n\n\t\tif r.MTU > t.MaxMTU {\n\t\t\tnewMaxMTU = r.MTU\n\t\t}\n\t}\n\n\tt.MaxMTU = newMaxMTU\n\tt.DefaultMTU = newDefaultMTU\n\n\t// Teach nebula how to handle the routes before establishing them in the system table\n\toldRoutes := t.Routes.Swap(&routes)\n\tt.routeTree.Store(routeTree)\n\n\tif !initial {\n\t\tif oldMaxMTU != newMaxMTU {\n\t\t\tt.setMTU()\n\t\t\tt.l.Infof(\"Set max MTU to %v was %v\", t.MaxMTU, oldMaxMTU)\n\t\t}\n\n\t\tif oldDefaultMTU != newDefaultMTU {\n\t\t\tfor i := range t.vpnNetworks {\n\t\t\t\terr := t.setDefaultRoute(t.vpnNetworks[i])\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.l.Warn(err)\n\t\t\t\t} else {\n\t\t\t\t\tt.l.Infof(\"Set default MTU to %v was %v\", t.DefaultMTU, oldDefaultMTU)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Remove first, if the system removes a wanted route hopefully it will be re-added next\n\t\tt.removeRoutes(findRemovedRoutes(routes, *oldRoutes))\n\n\t\t// Ensure any routes we actually want are installed\n\t\terr = t.addRoutes(true)\n\t\tif err != nil {\n\t\t\t// This should never be called since addRoutes should log its own errors in a reload condition\n\t\t\tutil.LogWithContextIfNeeded(\"Failed to refresh routes\", err, t.l)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (t *tun) SupportsMultiqueue() bool {\n\treturn true\n}\n\nfunc (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {\n\tfd, err := unix.Open(\"/dev/net/tun\", os.O_RDWR, 0)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar req ifReq\n\treq.Flags = uint16(unix.IFF_TUN | unix.IFF_NO_PI | unix.IFF_MULTI_QUEUE)\n\tcopy(req.Name[:], t.Device)\n\tif err = ioctl(uintptr(fd), uintptr(unix.TUNSETIFF), uintptr(unsafe.Pointer(&req))); err != nil {\n\t\treturn nil, err\n\t}\n\n\tfile := os.NewFile(uintptr(fd), \"/dev/net/tun\")\n\n\treturn file, nil\n}\n\nfunc (t *tun) RoutesFor(ip netip.Addr) routing.Gateways {\n\tr, _ := t.routeTree.Load().Lookup(ip)\n\treturn r\n}\n\nfunc (t *tun) Write(b []byte) (int, error) {\n\tvar nn int\n\tmaximum := len(b)\n\n\tfor {\n\t\tn, err := unix.Write(t.fd, b[nn:maximum])\n\t\tif n > 0 {\n\t\t\tnn += n\n\t\t}\n\t\tif nn == len(b) {\n\t\t\treturn nn, err\n\t\t}\n\n\t\tif err != nil {\n\t\t\treturn nn, err\n\t\t}\n\n\t\tif n == 0 {\n\t\t\treturn nn, io.ErrUnexpectedEOF\n\t\t}\n\t}\n}\n\nfunc (t *tun) deviceBytes() (o [16]byte) {\n\tfor i, c := range t.Device {\n\t\to[i] = byte(c)\n\t}\n\treturn\n}\n\nfunc hasNetlinkAddr(al []*netlink.Addr, x netlink.Addr) bool {\n\tfor i := range al {\n\t\tif al[i].Equal(x) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// addIPs uses netlink to add all addresses that don't exist, then it removes ones that should not be there\nfunc (t *tun) addIPs(link netlink.Link) error {\n\tnewAddrs := make([]*netlink.Addr, len(t.vpnNetworks))\n\tfor i := range t.vpnNetworks {\n\t\tnewAddrs[i] = &netlink.Addr{\n\t\t\tIPNet: &net.IPNet{\n\t\t\t\tIP:   t.vpnNetworks[i].Addr().AsSlice(),\n\t\t\t\tMask: net.CIDRMask(t.vpnNetworks[i].Bits(), t.vpnNetworks[i].Addr().BitLen()),\n\t\t\t},\n\t\t\tLabel: t.vpnNetworks[i].Addr().Zone(),\n\t\t}\n\t}\n\n\t//add all new addresses\n\tfor i := range newAddrs {\n\t\t//AddrReplace still adds new IPs, but if their properties change it will change them as well\n\t\tif err := netlink.AddrReplace(link, newAddrs[i]); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t//iterate over remainder, remove whoever shouldn't be there\n\tal, err := netlink.AddrList(link, netlink.FAMILY_ALL)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get tun address list: %s\", err)\n\t}\n\n\tfor i := range al {\n\t\tif hasNetlinkAddr(newAddrs, al[i]) {\n\t\t\tcontinue\n\t\t}\n\t\terr = netlink.AddrDel(link, &al[i])\n\t\tif err != nil {\n\t\t\tt.l.WithError(err).Error(\"failed to remove address from tun address list\")\n\t\t} else {\n\t\t\tt.l.WithField(\"removed\", al[i].String()).Info(\"removed address not listed in cert(s)\")\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (t *tun) Activate() error {\n\tdevName := t.deviceBytes()\n\n\tif t.useSystemRoutes {\n\t\tt.watchRoutes()\n\t}\n\n\ts, err := unix.Socket(\n\t\tunix.AF_INET, //because everything we use t.ioctlFd for is address family independent, this is fine\n\t\tunix.SOCK_DGRAM,\n\t\tunix.IPPROTO_IP,\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\tt.ioctlFd = uintptr(s)\n\n\t// Set the device name\n\tifrf := ifReq{Name: devName}\n\tif err = ioctl(t.ioctlFd, unix.SIOCGIFFLAGS, uintptr(unsafe.Pointer(&ifrf))); err != nil {\n\t\treturn fmt.Errorf(\"failed to set tun device name: %s\", err)\n\t}\n\n\tlink, err := netlink.LinkByName(t.Device)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get tun device link: %s\", err)\n\t}\n\n\tt.deviceIndex = link.Attrs().Index\n\n\t// Setup our default MTU\n\tt.setMTU()\n\n\t// Set the transmit queue length\n\tifrq := ifreqQLEN{Name: devName, Value: int32(t.TXQueueLen)}\n\tif err = ioctl(t.ioctlFd, unix.SIOCSIFTXQLEN, uintptr(unsafe.Pointer(&ifrq))); err != nil {\n\t\t// If we can't set the queue length nebula will still work but it may lead to packet loss\n\t\tt.l.WithError(err).Error(\"Failed to set tun tx queue length\")\n\t}\n\n\tconst modeNone = 1\n\tif err = netlink.LinkSetIP6AddrGenMode(link, modeNone); err != nil {\n\t\tt.l.WithError(err).Warn(\"Failed to disable link local address generation\")\n\t}\n\n\tif err = t.addIPs(link); err != nil {\n\t\treturn err\n\t}\n\n\t// Bring up the interface\n\tifrf.Flags = ifrf.Flags | unix.IFF_UP\n\tif err = ioctl(t.ioctlFd, unix.SIOCSIFFLAGS, uintptr(unsafe.Pointer(&ifrf))); err != nil {\n\t\treturn fmt.Errorf(\"failed to bring the tun device up: %s\", err)\n\t}\n\n\t//set route MTU\n\tfor i := range t.vpnNetworks {\n\t\tif err = t.setDefaultRoute(t.vpnNetworks[i]); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set default route MTU: %w\", err)\n\t\t}\n\t}\n\n\t// Set the routes\n\tif err = t.addRoutes(false); err != nil {\n\t\treturn err\n\t}\n\n\t// Run the interface\n\tifrf.Flags = ifrf.Flags | unix.IFF_UP | unix.IFF_RUNNING\n\tif err = ioctl(t.ioctlFd, unix.SIOCSIFFLAGS, uintptr(unsafe.Pointer(&ifrf))); err != nil {\n\t\treturn fmt.Errorf(\"failed to run tun device: %s\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (t *tun) setMTU() {\n\t// Set the MTU on the device\n\tifm := ifreqMTU{Name: t.deviceBytes(), MTU: int32(t.MaxMTU)}\n\tif err := ioctl(t.ioctlFd, unix.SIOCSIFMTU, uintptr(unsafe.Pointer(&ifm))); err != nil {\n\t\t// This is currently a non fatal condition because the route table must have the MTU set appropriately as well\n\t\tt.l.WithError(err).Error(\"Failed to set tun mtu\")\n\t}\n}\n\nfunc (t *tun) setDefaultRoute(cidr netip.Prefix) error {\n\tdr := &net.IPNet{\n\t\tIP:   cidr.Masked().Addr().AsSlice(),\n\t\tMask: net.CIDRMask(cidr.Bits(), cidr.Addr().BitLen()),\n\t}\n\n\tnr := netlink.Route{\n\t\tLinkIndex: t.deviceIndex,\n\t\tDst:       dr,\n\t\tMTU:       t.DefaultMTU,\n\t\tAdvMSS:    t.advMSS(Route{}),\n\t\tScope:     unix.RT_SCOPE_LINK,\n\t\tSrc:       net.IP(cidr.Addr().AsSlice()),\n\t\tProtocol:  unix.RTPROT_KERNEL,\n\t\tTable:     unix.RT_TABLE_MAIN,\n\t\tType:      unix.RTN_UNICAST,\n\t}\n\terr := netlink.RouteReplace(&nr)\n\tif err != nil {\n\t\tt.l.WithError(err).WithField(\"cidr\", cidr).Warn(\"Failed to set default route MTU, retrying\")\n\t\t//retry twice more -- on some systems there appears to be a race condition where if we set routes too soon, netlink says `invalid argument`\n\t\tfor i := 0; i < 2; i++ {\n\t\t\ttime.Sleep(100 * time.Millisecond)\n\t\t\terr = netlink.RouteReplace(&nr)\n\t\t\tif err == nil {\n\t\t\t\tbreak\n\t\t\t} else {\n\t\t\t\tt.l.WithError(err).WithField(\"cidr\", cidr).WithField(\"mtu\", t.DefaultMTU).Warn(\"Failed to set default route MTU, retrying\")\n\t\t\t}\n\t\t}\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set mtu %v on the default route %v; %v\", t.DefaultMTU, dr, err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (t *tun) addRoutes(logErrors bool) error {\n\t// Path routes\n\troutes := *t.Routes.Load()\n\tfor _, r := range routes {\n\t\tif !r.Install {\n\t\t\tcontinue\n\t\t}\n\n\t\tdr := &net.IPNet{\n\t\t\tIP:   r.Cidr.Masked().Addr().AsSlice(),\n\t\t\tMask: net.CIDRMask(r.Cidr.Bits(), r.Cidr.Addr().BitLen()),\n\t\t}\n\n\t\tnr := netlink.Route{\n\t\t\tLinkIndex: t.deviceIndex,\n\t\t\tDst:       dr,\n\t\t\tMTU:       r.MTU,\n\t\t\tAdvMSS:    t.advMSS(r),\n\t\t\tScope:     unix.RT_SCOPE_LINK,\n\t\t}\n\n\t\tif r.Metric > 0 {\n\t\t\tnr.Priority = r.Metric\n\t\t}\n\n\t\terr := netlink.RouteReplace(&nr)\n\t\tif err != nil {\n\t\t\tretErr := util.NewContextualError(\"Failed to add route\", map[string]any{\"route\": r}, err)\n\t\t\tif logErrors {\n\t\t\t\tretErr.Log(t.l)\n\t\t\t} else {\n\t\t\t\treturn retErr\n\t\t\t}\n\t\t} else {\n\t\t\tt.l.WithField(\"route\", r).Info(\"Added route\")\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (t *tun) removeRoutes(routes []Route) {\n\tfor _, r := range routes {\n\t\tif !r.Install {\n\t\t\tcontinue\n\t\t}\n\n\t\tdr := &net.IPNet{\n\t\t\tIP:   r.Cidr.Masked().Addr().AsSlice(),\n\t\t\tMask: net.CIDRMask(r.Cidr.Bits(), r.Cidr.Addr().BitLen()),\n\t\t}\n\n\t\tnr := netlink.Route{\n\t\t\tLinkIndex: t.deviceIndex,\n\t\t\tDst:       dr,\n\t\t\tMTU:       r.MTU,\n\t\t\tAdvMSS:    t.advMSS(r),\n\t\t\tScope:     unix.RT_SCOPE_LINK,\n\t\t}\n\n\t\tif r.Metric > 0 {\n\t\t\tnr.Priority = r.Metric\n\t\t}\n\n\t\terr := netlink.RouteDel(&nr)\n\t\tif err != nil {\n\t\t\tt.l.WithError(err).WithField(\"route\", r).Error(\"Failed to remove route\")\n\t\t} else {\n\t\t\tt.l.WithField(\"route\", r).Info(\"Removed route\")\n\t\t}\n\t}\n}\n\nfunc (t *tun) Name() string {\n\treturn t.Device\n}\n\nfunc (t *tun) advMSS(r Route) int {\n\tmtu := r.MTU\n\tif r.MTU == 0 {\n\t\tmtu = t.DefaultMTU\n\t}\n\n\t// We only need to set advmss if the route MTU does not match the device MTU\n\tif mtu != t.MaxMTU {\n\t\treturn mtu - 40\n\t}\n\treturn 0\n}\n\nfunc (t *tun) watchRoutes() {\n\trch := make(chan netlink.RouteUpdate)\n\tdoneChan := make(chan struct{})\n\n\tnetlinkOptions := netlink.RouteSubscribeOptions{\n\t\tReceiveBufferSize:      t.useSystemRoutesBufferSize,\n\t\tReceiveBufferForceSize: t.useSystemRoutesBufferSize != 0,\n\t\tErrorCallback:          func(e error) { t.l.WithError(e).Errorf(\"netlink error\") },\n\t}\n\n\tif err := netlink.RouteSubscribeWithOptions(rch, doneChan, netlinkOptions); err != nil {\n\t\tt.l.WithError(err).Errorf(\"failed to subscribe to system route changes\")\n\t\treturn\n\t}\n\n\tt.routeChan = doneChan\n\n\tgo func() {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase r, ok := <-rch:\n\t\t\t\tif ok {\n\t\t\t\t\tt.updateRoutes(r)\n\t\t\t\t} else {\n\t\t\t\t\t// may be should do something here as\n\t\t\t\t\t// netlink stops sending updates\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\tcase <-doneChan:\n\t\t\t\t// netlink.RouteSubscriber will close the rch for us\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n}\n\nfunc (t *tun) isGatewayInVpnNetworks(gwAddr netip.Addr) bool {\n\twithinNetworks := false\n\tfor i := range t.vpnNetworks {\n\t\tif t.vpnNetworks[i].Contains(gwAddr) {\n\t\t\twithinNetworks = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn withinNetworks\n}\n\nfunc (t *tun) getGatewaysFromRoute(r *netlink.Route) routing.Gateways {\n\tvar gateways routing.Gateways\n\n\tlink, err := netlink.LinkByName(t.Device)\n\tif err != nil {\n\t\tt.l.WithField(\"deviceName\", t.Device).Error(\"Ignoring route update: failed to get link by name\")\n\t\treturn gateways\n\t}\n\n\t// If this route is relevant to our interface and there is a gateway then add it\n\tif r.LinkIndex == link.Attrs().Index {\n\t\tgwAddr, ok := getGatewayAddr(r.Gw, r.Via)\n\t\tif ok {\n\t\t\tif t.isGatewayInVpnNetworks(gwAddr) {\n\t\t\t\tgateways = append(gateways, routing.NewGateway(gwAddr, 1))\n\t\t\t} else {\n\t\t\t\t// Gateway isn't in our overlay network, ignore\n\t\t\t\tt.l.WithField(\"route\", r).Debug(\"Ignoring route update, gateway is not in our network\")\n\t\t\t}\n\t\t} else {\n\t\t\tt.l.WithField(\"route\", r).Debug(\"Ignoring route update, invalid gateway or via address\")\n\t\t}\n\t}\n\n\tfor _, p := range r.MultiPath {\n\t\t// If this route is relevant to our interface and there is a gateway then add it\n\t\tif p.LinkIndex == link.Attrs().Index {\n\t\t\tgwAddr, ok := getGatewayAddr(p.Gw, p.Via)\n\t\t\tif ok {\n\t\t\t\tif t.isGatewayInVpnNetworks(gwAddr) {\n\t\t\t\t\tgateways = append(gateways, routing.NewGateway(gwAddr, p.Hops+1))\n\t\t\t\t} else {\n\t\t\t\t\t// Gateway isn't in our overlay network, ignore\n\t\t\t\t\tt.l.WithField(\"route\", r).Debug(\"Ignoring route update, gateway is not in our network\")\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tt.l.WithField(\"route\", r).Debug(\"Ignoring route update, invalid gateway or via address\")\n\t\t\t}\n\t\t}\n\t}\n\n\trouting.CalculateBucketsForGateways(gateways)\n\treturn gateways\n}\n\nfunc getGatewayAddr(gw net.IP, via netlink.Destination) (netip.Addr, bool) {\n\t// Try to use the old RTA_GATEWAY first\n\tgwAddr, ok := netip.AddrFromSlice(gw)\n\tif !ok {\n\t\t// Fallback to the new RTA_VIA\n\t\trVia, ok := via.(*netlink.Via)\n\t\tif ok {\n\t\t\tgwAddr, ok = netip.AddrFromSlice(rVia.Addr)\n\t\t}\n\t}\n\n\tif gwAddr.IsValid() {\n\t\tgwAddr = gwAddr.Unmap()\n\t\treturn gwAddr, true\n\t}\n\n\treturn netip.Addr{}, false\n}\n\nfunc (t *tun) updateRoutes(r netlink.RouteUpdate) {\n\tgateways := t.getGatewaysFromRoute(&r.Route)\n\tif len(gateways) == 0 {\n\t\t// No gateways relevant to our network, no routing changes required.\n\t\tt.l.WithField(\"route\", r).Debug(\"Ignoring route update, no gateways\")\n\t\treturn\n\t}\n\n\tif r.Dst == nil {\n\t\tt.l.WithField(\"route\", r).Debug(\"Ignoring route update, no destination address\")\n\t\treturn\n\t}\n\n\tdstAddr, ok := netip.AddrFromSlice(r.Dst.IP)\n\tif !ok {\n\t\tt.l.WithField(\"route\", r).Debug(\"Ignoring route update, invalid destination address\")\n\t\treturn\n\t}\n\n\tones, _ := r.Dst.Mask.Size()\n\tdst := netip.PrefixFrom(dstAddr, ones)\n\n\tnewTree := t.routeTree.Load().Clone()\n\n\tt.routesFromSystemLock.Lock()\n\tif r.Type == unix.RTM_NEWROUTE {\n\t\tt.l.WithField(\"destination\", dst).WithField(\"via\", gateways).Info(\"Adding route\")\n\t\tt.routesFromSystem[dst] = gateways\n\t\tnewTree.Insert(dst, gateways)\n\n\t} else {\n\t\tt.l.WithField(\"destination\", dst).WithField(\"via\", gateways).Info(\"Removing route\")\n\t\tdelete(t.routesFromSystem, dst)\n\t\tnewTree.Delete(dst)\n\t}\n\tt.routesFromSystemLock.Unlock()\n\tt.routeTree.Store(newTree)\n}\n\nfunc (t *tun) Close() error {\n\tif t.routeChan != nil {\n\t\tclose(t.routeChan)\n\t}\n\n\tif t.ReadWriteCloser != nil {\n\t\t_ = t.ReadWriteCloser.Close()\n\t}\n\n\tif t.ioctlFd > 0 {\n\t\t_ = os.NewFile(t.ioctlFd, \"ioctlFd\").Close()\n\t\tt.ioctlFd = 0\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "overlay/tun_linux_test.go",
    "content": "//go:build !e2e_testing\n// +build !e2e_testing\n\npackage overlay\n\nimport \"testing\"\n\nvar runAdvMSSTests = []struct {\n\tname     string\n\ttun      *tun\n\tr        Route\n\texpected int\n}{\n\t// Standard case, default MTU is the device max MTU\n\t{\"default\", &tun{DefaultMTU: 1440, MaxMTU: 1440}, Route{}, 0},\n\t{\"default-min\", &tun{DefaultMTU: 1440, MaxMTU: 1440}, Route{MTU: 1440}, 0},\n\t{\"default-low\", &tun{DefaultMTU: 1440, MaxMTU: 1440}, Route{MTU: 1200}, 1160},\n\n\t// Case where we have a route MTU set higher than the default\n\t{\"route\", &tun{DefaultMTU: 1440, MaxMTU: 8941}, Route{}, 1400},\n\t{\"route-min\", &tun{DefaultMTU: 1440, MaxMTU: 8941}, Route{MTU: 1440}, 1400},\n\t{\"route-high\", &tun{DefaultMTU: 1440, MaxMTU: 8941}, Route{MTU: 8941}, 0},\n}\n\nfunc TestTunAdvMSS(t *testing.T) {\n\tfor _, tt := range runAdvMSSTests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\to := tt.tun.advMSS(tt.r)\n\t\t\tif o != tt.expected {\n\t\t\t\tt.Errorf(\"got %d, want %d\", o, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "overlay/tun_netbsd.go",
    "content": "//go:build !e2e_testing\n// +build !e2e_testing\n\npackage overlay\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/netip\"\n\t\"os\"\n\t\"regexp\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"unsafe\"\n\n\t\"github.com/gaissmai/bart\"\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/config\"\n\t\"github.com/slackhq/nebula/routing\"\n\t\"github.com/slackhq/nebula/util\"\n\tnetroute \"golang.org/x/net/route\"\n\t\"golang.org/x/sys/unix\"\n)\n\nconst (\n\tSIOCAIFADDR_IN6 = 0x8080696b\n\tTUNSIFHEAD      = 0x80047442\n\tTUNSIFMODE      = 0x80047458\n)\n\ntype ifreqAlias4 struct {\n\tName     [unix.IFNAMSIZ]byte\n\tAddr     unix.RawSockaddrInet4\n\tDstAddr  unix.RawSockaddrInet4\n\tMaskAddr unix.RawSockaddrInet4\n}\n\ntype ifreqAlias6 struct {\n\tName       [unix.IFNAMSIZ]byte\n\tAddr       unix.RawSockaddrInet6\n\tDstAddr    unix.RawSockaddrInet6\n\tPrefixMask unix.RawSockaddrInet6\n\tFlags      uint32\n\tLifetime   addrLifetime\n}\n\ntype ifreq struct {\n\tName [unix.IFNAMSIZ]byte\n\tdata int\n}\n\ntype addrLifetime struct {\n\tExpire    uint64\n\tPreferred uint64\n\tVltime    uint32\n\tPltime    uint32\n}\n\ntype tun struct {\n\tDevice      string\n\tvpnNetworks []netip.Prefix\n\tMTU         int\n\tRoutes      atomic.Pointer[[]Route]\n\trouteTree   atomic.Pointer[bart.Table[routing.Gateways]]\n\tl           *logrus.Logger\n\tf           *os.File\n\tfd          int\n}\n\nvar deviceNameRE = regexp.MustCompile(`^tun[0-9]+$`)\n\nfunc newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ []netip.Prefix) (*tun, error) {\n\treturn nil, fmt.Errorf(\"newTunFromFd not supported in NetBSD\")\n}\n\nfunc newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (*tun, error) {\n\t// Try to open tun device\n\tvar err error\n\tdeviceName := c.GetString(\"tun.dev\", \"\")\n\tif deviceName == \"\" {\n\t\treturn nil, fmt.Errorf(\"a device name in the format of /dev/tunN must be specified\")\n\t}\n\tif !deviceNameRE.MatchString(deviceName) {\n\t\treturn nil, fmt.Errorf(\"a device name in the format of /dev/tunN must be specified\")\n\t}\n\n\tfd, err := unix.Open(\"/dev/\"+deviceName, os.O_RDWR, 0)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = unix.SetNonblock(fd, true)\n\tif err != nil {\n\t\tl.WithError(err).Warn(\"Failed to set the tun device as nonblocking\")\n\t}\n\n\tt := &tun{\n\t\tf:           os.NewFile(uintptr(fd), \"\"),\n\t\tfd:          fd,\n\t\tDevice:      deviceName,\n\t\tvpnNetworks: vpnNetworks,\n\t\tMTU:         c.GetInt(\"tun.mtu\", DefaultMTU),\n\t\tl:           l,\n\t}\n\n\terr = t.reload(c, true)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tc.RegisterReloadCallback(func(c *config.C) {\n\t\terr := t.reload(c, false)\n\t\tif err != nil {\n\t\t\tutil.LogWithContextIfNeeded(\"failed to reload tun device\", err, t.l)\n\t\t}\n\t})\n\n\treturn t, nil\n}\n\nfunc (t *tun) Close() error {\n\tif t.f != nil {\n\t\tif err := t.f.Close(); err != nil {\n\t\t\treturn fmt.Errorf(\"error closing tun file: %w\", err)\n\t\t}\n\n\t\t// t.f.Close should have handled it for us but let's be extra sure\n\t\t_ = unix.Close(t.fd)\n\n\t\ts, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_IP)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer syscall.Close(s)\n\n\t\tifr := ifreq{Name: t.deviceBytes()}\n\t\terr = ioctl(uintptr(s), syscall.SIOCIFDESTROY, uintptr(unsafe.Pointer(&ifr)))\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (t *tun) Read(to []byte) (int, error) {\n\trc, err := t.f.SyscallConn()\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"failed to get syscall conn for tun: %w\", err)\n\t}\n\n\tvar errno syscall.Errno\n\tvar n uintptr\n\terr = rc.Read(func(fd uintptr) bool {\n\t\t// first 4 bytes is protocol family, in network byte order\n\t\thead := [4]byte{}\n\t\tiovecs := []syscall.Iovec{\n\t\t\t{&head[0], 4},\n\t\t\t{&to[0], uint64(len(to))},\n\t\t}\n\n\t\tn, _, errno = syscall.Syscall(syscall.SYS_READV, fd, uintptr(unsafe.Pointer(&iovecs[0])), uintptr(2))\n\t\tif errno.Temporary() {\n\t\t\t// We got an EAGAIN, EINTR, or EWOULDBLOCK, go again\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t})\n\tif err != nil {\n\t\tif err == syscall.EBADF || err.Error() == \"use of closed file\" {\n\t\t\t// Go doesn't export poll.ErrFileClosing but happily reports it to us so here we are\n\t\t\t// https://github.com/golang/go/blob/master/src/internal/poll/fd_poll_runtime.go#L121\n\t\t\treturn 0, os.ErrClosed\n\t\t}\n\t\treturn 0, fmt.Errorf(\"failed to make read call for tun: %w\", err)\n\t}\n\n\tif errno != 0 {\n\t\treturn 0, fmt.Errorf(\"failed to make inner read call for tun: %w\", errno)\n\t}\n\n\t// fix bytes read number to exclude header\n\tbytesRead := int(n)\n\tif bytesRead < 0 {\n\t\treturn bytesRead, nil\n\t} else if bytesRead < 4 {\n\t\treturn 0, nil\n\t} else {\n\t\treturn bytesRead - 4, nil\n\t}\n}\n\n// Write is only valid for single threaded use\nfunc (t *tun) Write(from []byte) (int, error) {\n\tif len(from) <= 1 {\n\t\treturn 0, syscall.EIO\n\t}\n\n\tipVer := from[0] >> 4\n\tvar head [4]byte\n\t// first 4 bytes is protocol family, in network byte order\n\tif ipVer == 4 {\n\t\thead[3] = syscall.AF_INET\n\t} else if ipVer == 6 {\n\t\thead[3] = syscall.AF_INET6\n\t} else {\n\t\treturn 0, fmt.Errorf(\"unable to determine IP version from packet\")\n\t}\n\n\trc, err := t.f.SyscallConn()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tvar errno syscall.Errno\n\tvar n uintptr\n\terr = rc.Write(func(fd uintptr) bool {\n\t\tiovecs := []syscall.Iovec{\n\t\t\t{&head[0], 4},\n\t\t\t{&from[0], uint64(len(from))},\n\t\t}\n\n\t\tn, _, errno = syscall.Syscall(syscall.SYS_WRITEV, fd, uintptr(unsafe.Pointer(&iovecs[0])), uintptr(2))\n\t\t// According to NetBSD documentation for TUN, writes will only return errors in which\n\t\t// this packet will never be delivered so just go on living life.\n\t\treturn true\n\t})\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tif errno != 0 {\n\t\treturn 0, errno\n\t}\n\n\treturn int(n) - 4, err\n}\n\nfunc (t *tun) addIp(cidr netip.Prefix) error {\n\tif cidr.Addr().Is4() {\n\t\tvar req ifreqAlias4\n\t\treq.Name = t.deviceBytes()\n\t\treq.Addr = unix.RawSockaddrInet4{\n\t\t\tLen:    unix.SizeofSockaddrInet4,\n\t\t\tFamily: unix.AF_INET,\n\t\t\tAddr:   cidr.Addr().As4(),\n\t\t}\n\t\treq.DstAddr = unix.RawSockaddrInet4{\n\t\t\tLen:    unix.SizeofSockaddrInet4,\n\t\t\tFamily: unix.AF_INET,\n\t\t\tAddr:   cidr.Addr().As4(),\n\t\t}\n\t\treq.MaskAddr = unix.RawSockaddrInet4{\n\t\t\tLen:    unix.SizeofSockaddrInet4,\n\t\t\tFamily: unix.AF_INET,\n\t\t\tAddr:   prefixToMask(cidr).As4(),\n\t\t}\n\n\t\ts, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_IP)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer syscall.Close(s)\n\n\t\tif err := ioctl(uintptr(s), unix.SIOCAIFADDR, uintptr(unsafe.Pointer(&req))); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set tun address %s: %s\", cidr.Addr(), err)\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tif cidr.Addr().Is6() {\n\t\tvar req ifreqAlias6\n\t\treq.Name = t.deviceBytes()\n\t\treq.Addr = unix.RawSockaddrInet6{\n\t\t\tLen:    unix.SizeofSockaddrInet6,\n\t\t\tFamily: unix.AF_INET6,\n\t\t\tAddr:   cidr.Addr().As16(),\n\t\t}\n\t\treq.PrefixMask = unix.RawSockaddrInet6{\n\t\t\tLen:    unix.SizeofSockaddrInet6,\n\t\t\tFamily: unix.AF_INET6,\n\t\t\tAddr:   prefixToMask(cidr).As16(),\n\t\t}\n\t\treq.Lifetime = addrLifetime{\n\t\t\tVltime: 0xffffffff,\n\t\t\tPltime: 0xffffffff,\n\t\t}\n\n\t\ts, err := unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_IP)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer syscall.Close(s)\n\n\t\tif err := ioctl(uintptr(s), SIOCAIFADDR_IN6, uintptr(unsafe.Pointer(&req))); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set tun address %s: %s\", cidr.Addr().String(), err)\n\t\t}\n\t\treturn nil\n\t}\n\n\treturn fmt.Errorf(\"unknown address type %v\", cidr)\n}\n\nfunc (t *tun) Activate() error {\n\tmode := int32(unix.IFF_BROADCAST)\n\terr := ioctl(uintptr(t.fd), TUNSIFMODE, uintptr(unsafe.Pointer(&mode)))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to set tun device mode: %w\", err)\n\t}\n\n\tv := 1\n\terr = ioctl(uintptr(t.fd), TUNSIFHEAD, uintptr(unsafe.Pointer(&v)))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to set tun device head: %w\", err)\n\t}\n\n\terr = t.doIoctlByName(unix.SIOCSIFMTU, uint32(t.MTU))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to set tun mtu: %w\", err)\n\t}\n\n\tfor i := range t.vpnNetworks {\n\t\terr = t.addIp(t.vpnNetworks[i])\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn t.addRoutes(false)\n}\n\nfunc (t *tun) doIoctlByName(ctl uintptr, value uint32) error {\n\ts, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_IP)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer syscall.Close(s)\n\n\tir := ifreq{Name: t.deviceBytes(), data: int(value)}\n\terr = ioctl(uintptr(s), ctl, uintptr(unsafe.Pointer(&ir)))\n\treturn err\n}\n\nfunc (t *tun) reload(c *config.C, initial bool) error {\n\tchange, routes, err := getAllRoutesFromConfig(c, t.vpnNetworks, initial)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !initial && !change {\n\t\treturn nil\n\t}\n\n\trouteTree, err := makeRouteTree(t.l, routes, false)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Teach nebula how to handle the routes before establishing them in the system table\n\toldRoutes := t.Routes.Swap(&routes)\n\tt.routeTree.Store(routeTree)\n\n\tif !initial {\n\t\t// Remove first, if the system removes a wanted route hopefully it will be re-added next\n\t\terr := t.removeRoutes(findRemovedRoutes(routes, *oldRoutes))\n\t\tif err != nil {\n\t\t\tutil.LogWithContextIfNeeded(\"Failed to remove routes\", err, t.l)\n\t\t}\n\n\t\t// Ensure any routes we actually want are installed\n\t\terr = t.addRoutes(true)\n\t\tif err != nil {\n\t\t\t// Catch any stray logs\n\t\t\tutil.LogWithContextIfNeeded(\"Failed to add routes\", err, t.l)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (t *tun) RoutesFor(ip netip.Addr) routing.Gateways {\n\tr, _ := t.routeTree.Load().Lookup(ip)\n\treturn r\n}\n\nfunc (t *tun) Networks() []netip.Prefix {\n\treturn t.vpnNetworks\n}\n\nfunc (t *tun) Name() string {\n\treturn t.Device\n}\n\nfunc (t *tun) SupportsMultiqueue() bool {\n\treturn false\n}\n\nfunc (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {\n\treturn nil, fmt.Errorf(\"TODO: multiqueue not implemented for netbsd\")\n}\n\nfunc (t *tun) addRoutes(logErrors bool) error {\n\troutes := *t.Routes.Load()\n\n\tfor _, r := range routes {\n\t\tif len(r.Via) == 0 || !r.Install {\n\t\t\t// We don't allow route MTUs so only install routes with a via\n\t\t\tcontinue\n\t\t}\n\n\t\terr := addRoute(r.Cidr, t.vpnNetworks)\n\t\tif err != nil {\n\t\t\tretErr := util.NewContextualError(\"Failed to add route\", map[string]any{\"route\": r}, err)\n\t\t\tif logErrors {\n\t\t\t\tretErr.Log(t.l)\n\t\t\t} else {\n\t\t\t\treturn retErr\n\t\t\t}\n\t\t} else {\n\t\t\tt.l.WithField(\"route\", r).Info(\"Added route\")\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (t *tun) removeRoutes(routes []Route) error {\n\tfor _, r := range routes {\n\t\tif !r.Install {\n\t\t\tcontinue\n\t\t}\n\n\t\terr := delRoute(r.Cidr, t.vpnNetworks)\n\t\tif err != nil {\n\t\t\tt.l.WithError(err).WithField(\"route\", r).Error(\"Failed to remove route\")\n\t\t} else {\n\t\t\tt.l.WithField(\"route\", r).Info(\"Removed route\")\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (t *tun) deviceBytes() (o [16]byte) {\n\tfor i, c := range t.Device {\n\t\to[i] = byte(c)\n\t}\n\treturn\n}\n\nfunc addRoute(prefix netip.Prefix, gateways []netip.Prefix) error {\n\tsock, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to create AF_ROUTE socket: %v\", err)\n\t}\n\tdefer unix.Close(sock)\n\n\troute := &netroute.RouteMessage{\n\t\tVersion: unix.RTM_VERSION,\n\t\tType:    unix.RTM_ADD,\n\t\tFlags:   unix.RTF_UP | unix.RTF_GATEWAY,\n\t\tSeq:     1,\n\t}\n\n\tif prefix.Addr().Is4() {\n\t\tgw, err := selectGateway(prefix, gateways)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\troute.Addrs = []netroute.Addr{\n\t\t\tunix.RTAX_DST:     &netroute.Inet4Addr{IP: prefix.Masked().Addr().As4()},\n\t\t\tunix.RTAX_NETMASK: &netroute.Inet4Addr{IP: prefixToMask(prefix).As4()},\n\t\t\tunix.RTAX_GATEWAY: &netroute.Inet4Addr{IP: gw.Addr().As4()},\n\t\t}\n\t} else {\n\t\tgw, err := selectGateway(prefix, gateways)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\troute.Addrs = []netroute.Addr{\n\t\t\tunix.RTAX_DST:     &netroute.Inet6Addr{IP: prefix.Masked().Addr().As16()},\n\t\t\tunix.RTAX_NETMASK: &netroute.Inet6Addr{IP: prefixToMask(prefix).As16()},\n\t\t\tunix.RTAX_GATEWAY: &netroute.Inet6Addr{IP: gw.Addr().As16()},\n\t\t}\n\t}\n\n\tdata, err := route.Marshal()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create route.RouteMessage: %w\", err)\n\t}\n\n\t_, err = unix.Write(sock, data[:])\n\tif err != nil {\n\t\tif errors.Is(err, unix.EEXIST) {\n\t\t\t// Try to do a change\n\t\t\troute.Type = unix.RTM_CHANGE\n\t\t\tdata, err = route.Marshal()\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to create route.RouteMessage for change: %w\", err)\n\t\t\t}\n\t\t\t_, err = unix.Write(sock, data[:])\n\t\t\treturn err\n\t\t}\n\t\treturn fmt.Errorf(\"failed to write route.RouteMessage to socket: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc delRoute(prefix netip.Prefix, gateways []netip.Prefix) error {\n\tsock, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to create AF_ROUTE socket: %v\", err)\n\t}\n\tdefer unix.Close(sock)\n\n\troute := netroute.RouteMessage{\n\t\tVersion: unix.RTM_VERSION,\n\t\tType:    unix.RTM_DELETE,\n\t\tSeq:     1,\n\t}\n\n\tif prefix.Addr().Is4() {\n\t\tgw, err := selectGateway(prefix, gateways)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\troute.Addrs = []netroute.Addr{\n\t\t\tunix.RTAX_DST:     &netroute.Inet4Addr{IP: prefix.Masked().Addr().As4()},\n\t\t\tunix.RTAX_NETMASK: &netroute.Inet4Addr{IP: prefixToMask(prefix).As4()},\n\t\t\tunix.RTAX_GATEWAY: &netroute.Inet4Addr{IP: gw.Addr().As4()},\n\t\t}\n\t} else {\n\t\tgw, err := selectGateway(prefix, gateways)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\troute.Addrs = []netroute.Addr{\n\t\t\tunix.RTAX_DST:     &netroute.Inet6Addr{IP: prefix.Masked().Addr().As16()},\n\t\t\tunix.RTAX_NETMASK: &netroute.Inet6Addr{IP: prefixToMask(prefix).As16()},\n\t\t\tunix.RTAX_GATEWAY: &netroute.Inet6Addr{IP: gw.Addr().As16()},\n\t\t}\n\t}\n\n\tdata, err := route.Marshal()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create route.RouteMessage: %w\", err)\n\t}\n\t_, err = unix.Write(sock, data[:])\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to write route.RouteMessage to socket: %w\", err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "overlay/tun_notwin.go",
    "content": "//go:build !windows\n// +build !windows\n\npackage overlay\n\nimport \"syscall\"\n\nfunc ioctl(a1, a2, a3 uintptr) error {\n\t_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, a1, a2, a3)\n\tif errno != 0 {\n\t\treturn errno\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "overlay/tun_openbsd.go",
    "content": "//go:build !e2e_testing\n// +build !e2e_testing\n\npackage overlay\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/netip\"\n\t\"os\"\n\t\"regexp\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"unsafe\"\n\n\t\"github.com/gaissmai/bart\"\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/config\"\n\t\"github.com/slackhq/nebula/routing\"\n\t\"github.com/slackhq/nebula/util\"\n\tnetroute \"golang.org/x/net/route\"\n\t\"golang.org/x/sys/unix\"\n)\n\nconst (\n\tSIOCAIFADDR_IN6 = 0x8080691a\n)\n\ntype ifreqAlias4 struct {\n\tName     [unix.IFNAMSIZ]byte\n\tAddr     unix.RawSockaddrInet4\n\tDstAddr  unix.RawSockaddrInet4\n\tMaskAddr unix.RawSockaddrInet4\n}\n\ntype ifreqAlias6 struct {\n\tName       [unix.IFNAMSIZ]byte\n\tAddr       unix.RawSockaddrInet6\n\tDstAddr    unix.RawSockaddrInet6\n\tPrefixMask unix.RawSockaddrInet6\n\tFlags      uint32\n\tLifetime   [2]uint32\n}\n\ntype ifreq struct {\n\tName [unix.IFNAMSIZ]byte\n\tdata int\n}\n\ntype tun struct {\n\tDevice      string\n\tvpnNetworks []netip.Prefix\n\tMTU         int\n\tRoutes      atomic.Pointer[[]Route]\n\trouteTree   atomic.Pointer[bart.Table[routing.Gateways]]\n\tl           *logrus.Logger\n\tf           *os.File\n\tfd          int\n\t// cache out buffer since we need to prepend 4 bytes for tun metadata\n\tout []byte\n}\n\nvar deviceNameRE = regexp.MustCompile(`^tun[0-9]+$`)\n\nfunc newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ []netip.Prefix) (*tun, error) {\n\treturn nil, fmt.Errorf(\"newTunFromFd not supported in openbsd\")\n}\n\nfunc newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (*tun, error) {\n\t// Try to open tun device\n\tvar err error\n\tdeviceName := c.GetString(\"tun.dev\", \"\")\n\tif deviceName == \"\" {\n\t\treturn nil, fmt.Errorf(\"a device name in the format of /dev/tunN must be specified\")\n\t}\n\tif !deviceNameRE.MatchString(deviceName) {\n\t\treturn nil, fmt.Errorf(\"a device name in the format of /dev/tunN must be specified\")\n\t}\n\n\tfd, err := unix.Open(\"/dev/\"+deviceName, os.O_RDWR, 0)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = unix.SetNonblock(fd, true)\n\tif err != nil {\n\t\tl.WithError(err).Warn(\"Failed to set the tun device as nonblocking\")\n\t}\n\n\tt := &tun{\n\t\tf:           os.NewFile(uintptr(fd), \"\"),\n\t\tfd:          fd,\n\t\tDevice:      deviceName,\n\t\tvpnNetworks: vpnNetworks,\n\t\tMTU:         c.GetInt(\"tun.mtu\", DefaultMTU),\n\t\tl:           l,\n\t}\n\n\terr = t.reload(c, true)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tc.RegisterReloadCallback(func(c *config.C) {\n\t\terr := t.reload(c, false)\n\t\tif err != nil {\n\t\t\tutil.LogWithContextIfNeeded(\"failed to reload tun device\", err, t.l)\n\t\t}\n\t})\n\n\treturn t, nil\n}\n\nfunc (t *tun) Close() error {\n\tif t.f != nil {\n\t\tif err := t.f.Close(); err != nil {\n\t\t\treturn fmt.Errorf(\"error closing tun file: %w\", err)\n\t\t}\n\n\t\t// t.f.Close should have handled it for us but let's be extra sure\n\t\t_ = unix.Close(t.fd)\n\t}\n\treturn nil\n}\n\nfunc (t *tun) Read(to []byte) (int, error) {\n\tbuf := make([]byte, len(to)+4)\n\n\tn, err := t.f.Read(buf)\n\n\tcopy(to, buf[4:])\n\treturn n - 4, err\n}\n\n// Write is only valid for single threaded use\nfunc (t *tun) Write(from []byte) (int, error) {\n\tbuf := t.out\n\tif cap(buf) < len(from)+4 {\n\t\tbuf = make([]byte, len(from)+4)\n\t\tt.out = buf\n\t}\n\tbuf = buf[:len(from)+4]\n\n\tif len(from) == 0 {\n\t\treturn 0, syscall.EIO\n\t}\n\n\t// Determine the IP Family for the NULL L2 Header\n\tipVer := from[0] >> 4\n\tif ipVer == 4 {\n\t\tbuf[3] = syscall.AF_INET\n\t} else if ipVer == 6 {\n\t\tbuf[3] = syscall.AF_INET6\n\t} else {\n\t\treturn 0, fmt.Errorf(\"unable to determine IP version from packet\")\n\t}\n\n\tcopy(buf[4:], from)\n\n\tn, err := t.f.Write(buf)\n\treturn n - 4, err\n}\n\nfunc (t *tun) addIp(cidr netip.Prefix) error {\n\tif cidr.Addr().Is4() {\n\t\tvar req ifreqAlias4\n\t\treq.Name = t.deviceBytes()\n\t\treq.Addr = unix.RawSockaddrInet4{\n\t\t\tLen:    unix.SizeofSockaddrInet4,\n\t\t\tFamily: unix.AF_INET,\n\t\t\tAddr:   cidr.Addr().As4(),\n\t\t}\n\t\treq.DstAddr = unix.RawSockaddrInet4{\n\t\t\tLen:    unix.SizeofSockaddrInet4,\n\t\t\tFamily: unix.AF_INET,\n\t\t\tAddr:   cidr.Addr().As4(),\n\t\t}\n\t\treq.MaskAddr = unix.RawSockaddrInet4{\n\t\t\tLen:    unix.SizeofSockaddrInet4,\n\t\t\tFamily: unix.AF_INET,\n\t\t\tAddr:   prefixToMask(cidr).As4(),\n\t\t}\n\n\t\ts, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_IP)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer syscall.Close(s)\n\n\t\tif err := ioctl(uintptr(s), unix.SIOCAIFADDR, uintptr(unsafe.Pointer(&req))); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set tun address %s: %s\", cidr.Addr(), err)\n\t\t}\n\n\t\terr = addRoute(cidr, t.vpnNetworks)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set route for vpn network %v: %w\", cidr, err)\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tif cidr.Addr().Is6() {\n\t\tvar req ifreqAlias6\n\t\treq.Name = t.deviceBytes()\n\t\treq.Addr = unix.RawSockaddrInet6{\n\t\t\tLen:    unix.SizeofSockaddrInet6,\n\t\t\tFamily: unix.AF_INET6,\n\t\t\tAddr:   cidr.Addr().As16(),\n\t\t}\n\t\treq.PrefixMask = unix.RawSockaddrInet6{\n\t\t\tLen:    unix.SizeofSockaddrInet6,\n\t\t\tFamily: unix.AF_INET6,\n\t\t\tAddr:   prefixToMask(cidr).As16(),\n\t\t}\n\t\treq.Lifetime[0] = 0xffffffff\n\t\treq.Lifetime[1] = 0xffffffff\n\n\t\ts, err := unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_IP)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer syscall.Close(s)\n\n\t\tif err := ioctl(uintptr(s), SIOCAIFADDR_IN6, uintptr(unsafe.Pointer(&req))); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set tun address %s: %s\", cidr.Addr().String(), err)\n\t\t}\n\n\t\treturn nil\n\t}\n\n\treturn fmt.Errorf(\"unknown address type %v\", cidr)\n}\n\nfunc (t *tun) Activate() error {\n\terr := t.doIoctlByName(unix.SIOCSIFMTU, uint32(t.MTU))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to set tun mtu: %w\", err)\n\t}\n\n\tfor i := range t.vpnNetworks {\n\t\terr = t.addIp(t.vpnNetworks[i])\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn t.addRoutes(false)\n}\n\nfunc (t *tun) doIoctlByName(ctl uintptr, value uint32) error {\n\ts, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_IP)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer syscall.Close(s)\n\n\tir := ifreq{Name: t.deviceBytes(), data: int(value)}\n\terr = ioctl(uintptr(s), ctl, uintptr(unsafe.Pointer(&ir)))\n\treturn err\n}\n\nfunc (t *tun) reload(c *config.C, initial bool) error {\n\tchange, routes, err := getAllRoutesFromConfig(c, t.vpnNetworks, initial)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !initial && !change {\n\t\treturn nil\n\t}\n\n\trouteTree, err := makeRouteTree(t.l, routes, false)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Teach nebula how to handle the routes before establishing them in the system table\n\toldRoutes := t.Routes.Swap(&routes)\n\tt.routeTree.Store(routeTree)\n\n\tif !initial {\n\t\t// Remove first, if the system removes a wanted route hopefully it will be re-added next\n\t\terr := t.removeRoutes(findRemovedRoutes(routes, *oldRoutes))\n\t\tif err != nil {\n\t\t\tutil.LogWithContextIfNeeded(\"Failed to remove routes\", err, t.l)\n\t\t}\n\n\t\t// Ensure any routes we actually want are installed\n\t\terr = t.addRoutes(true)\n\t\tif err != nil {\n\t\t\t// Catch any stray logs\n\t\t\tutil.LogWithContextIfNeeded(\"Failed to add routes\", err, t.l)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (t *tun) RoutesFor(ip netip.Addr) routing.Gateways {\n\tr, _ := t.routeTree.Load().Lookup(ip)\n\treturn r\n}\n\nfunc (t *tun) Networks() []netip.Prefix {\n\treturn t.vpnNetworks\n}\n\nfunc (t *tun) Name() string {\n\treturn t.Device\n}\n\nfunc (t *tun) SupportsMultiqueue() bool {\n\treturn false\n}\n\nfunc (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {\n\treturn nil, fmt.Errorf(\"TODO: multiqueue not implemented for openbsd\")\n}\n\nfunc (t *tun) addRoutes(logErrors bool) error {\n\troutes := *t.Routes.Load()\n\n\tfor _, r := range routes {\n\t\tif len(r.Via) == 0 || !r.Install {\n\t\t\t// We don't allow route MTUs so only install routes with a via\n\t\t\tcontinue\n\t\t}\n\n\t\terr := addRoute(r.Cidr, t.vpnNetworks)\n\t\tif err != nil {\n\t\t\tretErr := util.NewContextualError(\"Failed to add route\", map[string]any{\"route\": r}, err)\n\t\t\tif logErrors {\n\t\t\t\tretErr.Log(t.l)\n\t\t\t} else {\n\t\t\t\treturn retErr\n\t\t\t}\n\t\t} else {\n\t\t\tt.l.WithField(\"route\", r).Info(\"Added route\")\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (t *tun) removeRoutes(routes []Route) error {\n\tfor _, r := range routes {\n\t\tif !r.Install {\n\t\t\tcontinue\n\t\t}\n\n\t\terr := delRoute(r.Cidr, t.vpnNetworks)\n\t\tif err != nil {\n\t\t\tt.l.WithError(err).WithField(\"route\", r).Error(\"Failed to remove route\")\n\t\t} else {\n\t\t\tt.l.WithField(\"route\", r).Info(\"Removed route\")\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (t *tun) deviceBytes() (o [16]byte) {\n\tfor i, c := range t.Device {\n\t\to[i] = byte(c)\n\t}\n\treturn\n}\n\nfunc addRoute(prefix netip.Prefix, gateways []netip.Prefix) error {\n\tsock, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to create AF_ROUTE socket: %v\", err)\n\t}\n\tdefer unix.Close(sock)\n\n\troute := &netroute.RouteMessage{\n\t\tVersion: unix.RTM_VERSION,\n\t\tType:    unix.RTM_ADD,\n\t\tFlags:   unix.RTF_UP | unix.RTF_GATEWAY,\n\t\tSeq:     1,\n\t}\n\n\tif prefix.Addr().Is4() {\n\t\tgw, err := selectGateway(prefix, gateways)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\troute.Addrs = []netroute.Addr{\n\t\t\tunix.RTAX_DST:     &netroute.Inet4Addr{IP: prefix.Masked().Addr().As4()},\n\t\t\tunix.RTAX_NETMASK: &netroute.Inet4Addr{IP: prefixToMask(prefix).As4()},\n\t\t\tunix.RTAX_GATEWAY: &netroute.Inet4Addr{IP: gw.Addr().As4()},\n\t\t}\n\t} else {\n\t\tgw, err := selectGateway(prefix, gateways)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\troute.Addrs = []netroute.Addr{\n\t\t\tunix.RTAX_DST:     &netroute.Inet6Addr{IP: prefix.Masked().Addr().As16()},\n\t\t\tunix.RTAX_NETMASK: &netroute.Inet6Addr{IP: prefixToMask(prefix).As16()},\n\t\t\tunix.RTAX_GATEWAY: &netroute.Inet6Addr{IP: gw.Addr().As16()},\n\t\t}\n\t}\n\n\tdata, err := route.Marshal()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create route.RouteMessage: %w\", err)\n\t}\n\n\t_, err = unix.Write(sock, data[:])\n\tif err != nil {\n\t\tif errors.Is(err, unix.EEXIST) {\n\t\t\t// Try to do a change\n\t\t\troute.Type = unix.RTM_CHANGE\n\t\t\tdata, err = route.Marshal()\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to create route.RouteMessage for change: %w\", err)\n\t\t\t}\n\t\t\t_, err = unix.Write(sock, data[:])\n\t\t\treturn err\n\t\t}\n\t\treturn fmt.Errorf(\"failed to write route.RouteMessage to socket: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc delRoute(prefix netip.Prefix, gateways []netip.Prefix) error {\n\tsock, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to create AF_ROUTE socket: %v\", err)\n\t}\n\tdefer unix.Close(sock)\n\n\troute := netroute.RouteMessage{\n\t\tVersion: unix.RTM_VERSION,\n\t\tType:    unix.RTM_DELETE,\n\t\tSeq:     1,\n\t}\n\n\tif prefix.Addr().Is4() {\n\t\tgw, err := selectGateway(prefix, gateways)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\troute.Addrs = []netroute.Addr{\n\t\t\tunix.RTAX_DST:     &netroute.Inet4Addr{IP: prefix.Masked().Addr().As4()},\n\t\t\tunix.RTAX_NETMASK: &netroute.Inet4Addr{IP: prefixToMask(prefix).As4()},\n\t\t\tunix.RTAX_GATEWAY: &netroute.Inet4Addr{IP: gw.Addr().As4()},\n\t\t}\n\t} else {\n\t\tgw, err := selectGateway(prefix, gateways)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\troute.Addrs = []netroute.Addr{\n\t\t\tunix.RTAX_DST:     &netroute.Inet6Addr{IP: prefix.Masked().Addr().As16()},\n\t\t\tunix.RTAX_NETMASK: &netroute.Inet6Addr{IP: prefixToMask(prefix).As16()},\n\t\t\tunix.RTAX_GATEWAY: &netroute.Inet6Addr{IP: gw.Addr().As16()},\n\t\t}\n\t}\n\n\tdata, err := route.Marshal()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create route.RouteMessage: %w\", err)\n\t}\n\t_, err = unix.Write(sock, data[:])\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to write route.RouteMessage to socket: %w\", err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "overlay/tun_tester.go",
    "content": "//go:build e2e_testing\n// +build e2e_testing\n\npackage overlay\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/netip\"\n\t\"os\"\n\t\"sync/atomic\"\n\n\t\"github.com/gaissmai/bart\"\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/config\"\n\t\"github.com/slackhq/nebula/routing\"\n)\n\ntype TestTun struct {\n\tDevice      string\n\tvpnNetworks []netip.Prefix\n\tRoutes      []Route\n\trouteTree   *bart.Table[routing.Gateways]\n\tl           *logrus.Logger\n\n\tclosed    atomic.Bool\n\trxPackets chan []byte // Packets to receive into nebula\n\tTxPackets chan []byte // Packets transmitted outside by nebula\n}\n\nfunc newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (*TestTun, error) {\n\t_, routes, err := getAllRoutesFromConfig(c, vpnNetworks, true)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\trouteTree, err := makeRouteTree(l, routes, false)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &TestTun{\n\t\tDevice:      c.GetString(\"tun.dev\", \"\"),\n\t\tvpnNetworks: vpnNetworks,\n\t\tRoutes:      routes,\n\t\trouteTree:   routeTree,\n\t\tl:           l,\n\t\trxPackets:   make(chan []byte, 10),\n\t\tTxPackets:   make(chan []byte, 10),\n\t}, nil\n}\n\nfunc newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ []netip.Prefix) (*TestTun, error) {\n\treturn nil, fmt.Errorf(\"newTunFromFd not supported\")\n}\n\n// Send will place a byte array onto the receive queue for nebula to consume\n// These are unencrypted ip layer frames destined for another nebula node.\n// packets should exit the udp side, capture them with udpConn.Get\nfunc (t *TestTun) Send(packet []byte) {\n\tif t.closed.Load() {\n\t\treturn\n\t}\n\n\tif t.l.Level >= logrus.DebugLevel {\n\t\tt.l.WithField(\"dataLen\", len(packet)).Debug(\"Tun receiving injected packet\")\n\t}\n\tt.rxPackets <- packet\n}\n\n// Get will pull an unencrypted ip layer frame from the transmit queue\n// nebula meant to send this message to some application on the local system\n// packets were ingested from the udp side, you can send them with udpConn.Send\nfunc (t *TestTun) Get(block bool) []byte {\n\tif block {\n\t\treturn <-t.TxPackets\n\t}\n\n\tselect {\n\tcase p := <-t.TxPackets:\n\t\treturn p\n\tdefault:\n\t\treturn nil\n\t}\n}\n\n//********************************************************************************************************************//\n// Below this is boilerplate implementation to make nebula actually work\n//********************************************************************************************************************//\n\nfunc (t *TestTun) RoutesFor(ip netip.Addr) routing.Gateways {\n\tr, _ := t.routeTree.Lookup(ip)\n\treturn r\n}\n\nfunc (t *TestTun) Activate() error {\n\treturn nil\n}\n\nfunc (t *TestTun) Networks() []netip.Prefix {\n\treturn t.vpnNetworks\n}\n\nfunc (t *TestTun) Name() string {\n\treturn t.Device\n}\n\nfunc (t *TestTun) Write(b []byte) (n int, err error) {\n\tif t.closed.Load() {\n\t\treturn 0, io.ErrClosedPipe\n\t}\n\n\tpacket := make([]byte, len(b), len(b))\n\tcopy(packet, b)\n\tt.TxPackets <- packet\n\treturn len(b), nil\n}\n\nfunc (t *TestTun) Close() error {\n\tif t.closed.CompareAndSwap(false, true) {\n\t\tclose(t.rxPackets)\n\t\tclose(t.TxPackets)\n\t}\n\treturn nil\n}\n\nfunc (t *TestTun) Read(b []byte) (int, error) {\n\tp, ok := <-t.rxPackets\n\tif !ok {\n\t\treturn 0, os.ErrClosed\n\t}\n\tcopy(b, p)\n\treturn len(p), nil\n}\n\nfunc (t *TestTun) SupportsMultiqueue() bool {\n\treturn false\n}\n\nfunc (t *TestTun) NewMultiQueueReader() (io.ReadWriteCloser, error) {\n\treturn nil, fmt.Errorf(\"TODO: multiqueue not implemented\")\n}\n"
  },
  {
    "path": "overlay/tun_windows.go",
    "content": "//go:build !e2e_testing\n// +build !e2e_testing\n\npackage overlay\n\nimport (\n\t\"crypto\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/netip\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"unsafe\"\n\n\t\"github.com/gaissmai/bart\"\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/config\"\n\t\"github.com/slackhq/nebula/routing\"\n\t\"github.com/slackhq/nebula/util\"\n\t\"github.com/slackhq/nebula/wintun\"\n\t\"golang.org/x/sys/windows\"\n\t\"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg\"\n)\n\nconst tunGUIDLabel = \"Fixed Nebula Windows GUID v1\"\n\ntype winTun struct {\n\tDevice      string\n\tvpnNetworks []netip.Prefix\n\tMTU         int\n\tRoutes      atomic.Pointer[[]Route]\n\trouteTree   atomic.Pointer[bart.Table[routing.Gateways]]\n\tl           *logrus.Logger\n\n\ttun *wintun.NativeTun\n}\n\nfunc newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ []netip.Prefix) (Device, error) {\n\treturn nil, fmt.Errorf(\"newTunFromFd not supported in Windows\")\n}\n\nfunc newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (*winTun, error) {\n\terr := checkWinTunExists()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"can not load the wintun driver: %w\", err)\n\t}\n\n\tdeviceName := c.GetString(\"tun.dev\", \"\")\n\tguid, err := generateGUIDByDeviceName(deviceName)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"generate GUID failed: %w\", err)\n\t}\n\n\tt := &winTun{\n\t\tDevice:      deviceName,\n\t\tvpnNetworks: vpnNetworks,\n\t\tMTU:         c.GetInt(\"tun.mtu\", DefaultMTU),\n\t\tl:           l,\n\t}\n\n\terr = t.reload(c, true)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar tunDevice wintun.Device\n\ttunDevice, err = wintun.CreateTUNWithRequestedGUID(deviceName, guid, t.MTU)\n\tif err != nil {\n\t\t// Windows 10 has an issue with unclean shutdowns not fully cleaning up the wintun device.\n\t\t// Trying a second time resolves the issue.\n\t\tl.WithError(err).Debug(\"Failed to create wintun device, retrying\")\n\t\ttunDevice, err = wintun.CreateTUNWithRequestedGUID(deviceName, guid, t.MTU)\n\t\tif err != nil {\n\t\t\treturn nil, &NameError{\n\t\t\t\tName:       deviceName,\n\t\t\t\tUnderlying: fmt.Errorf(\"create TUN device failed: %w\", err),\n\t\t\t}\n\t\t}\n\t}\n\tt.tun = tunDevice.(*wintun.NativeTun)\n\n\tc.RegisterReloadCallback(func(c *config.C) {\n\t\terr := t.reload(c, false)\n\t\tif err != nil {\n\t\t\tutil.LogWithContextIfNeeded(\"failed to reload tun device\", err, t.l)\n\t\t}\n\t})\n\n\treturn t, nil\n}\n\nfunc (t *winTun) reload(c *config.C, initial bool) error {\n\tchange, routes, err := getAllRoutesFromConfig(c, t.vpnNetworks, initial)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !initial && !change {\n\t\treturn nil\n\t}\n\n\trouteTree, err := makeRouteTree(t.l, routes, false)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Teach nebula how to handle the routes before establishing them in the system table\n\toldRoutes := t.Routes.Swap(&routes)\n\tt.routeTree.Store(routeTree)\n\n\tif !initial {\n\t\t// Remove first, if the system removes a wanted route hopefully it will be re-added next\n\t\terr := t.removeRoutes(findRemovedRoutes(routes, *oldRoutes))\n\t\tif err != nil {\n\t\t\tutil.LogWithContextIfNeeded(\"Failed to remove routes\", err, t.l)\n\t\t}\n\n\t\t// Ensure any routes we actually want are installed\n\t\terr = t.addRoutes(true)\n\t\tif err != nil {\n\t\t\t// Catch any stray logs\n\t\t\tutil.LogWithContextIfNeeded(\"Failed to add routes\", err, t.l)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (t *winTun) Activate() error {\n\tluid := winipcfg.LUID(t.tun.LUID())\n\n\terr := luid.SetIPAddresses(t.vpnNetworks)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to set address: %w\", err)\n\t}\n\n\terr = t.addRoutes(false)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (t *winTun) addRoutes(logErrors bool) error {\n\tluid := winipcfg.LUID(t.tun.LUID())\n\troutes := *t.Routes.Load()\n\tfoundDefault4 := false\n\n\tfor _, r := range routes {\n\t\tif len(r.Via) == 0 || !r.Install {\n\t\t\t// We don't allow route MTUs so only install routes with a via\n\t\t\tcontinue\n\t\t}\n\n\t\t// Add our unsafe route\n\t\t// Windows does not support multipath routes natively, so we install only a single route.\n\t\t// This is not a problem as traffic will always be sent to Nebula which handles the multipath routing internally.\n\t\t// In effect this provides multipath routing support to windows supporting loadbalancing and redundancy.\n\t\terr := luid.AddRoute(r.Cidr, r.Via[0].Addr(), uint32(r.Metric))\n\t\tif err != nil {\n\t\t\tretErr := util.NewContextualError(\"Failed to add route\", map[string]any{\"route\": r}, err)\n\t\t\tif logErrors {\n\t\t\t\tretErr.Log(t.l)\n\t\t\t\tcontinue\n\t\t\t} else {\n\t\t\t\treturn retErr\n\t\t\t}\n\t\t} else {\n\t\t\tt.l.WithField(\"route\", r).Info(\"Added route\")\n\t\t}\n\n\t\tif !foundDefault4 {\n\t\t\tif r.Cidr.Bits() == 0 && r.Cidr.Addr().BitLen() == 32 {\n\t\t\t\tfoundDefault4 = true\n\t\t\t}\n\t\t}\n\t}\n\n\tipif, err := luid.IPInterface(windows.AF_INET)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get ip interface: %w\", err)\n\t}\n\n\tipif.NLMTU = uint32(t.MTU)\n\tif foundDefault4 {\n\t\tipif.UseAutomaticMetric = false\n\t\tipif.Metric = 0\n\t}\n\n\tif err := ipif.Set(); err != nil {\n\t\treturn fmt.Errorf(\"failed to set ip interface: %w\", err)\n\t}\n\treturn nil\n}\n\nfunc (t *winTun) removeRoutes(routes []Route) error {\n\tluid := winipcfg.LUID(t.tun.LUID())\n\n\tfor _, r := range routes {\n\t\tif !r.Install {\n\t\t\tcontinue\n\t\t}\n\n\t\t// See comment on luid.AddRoute\n\t\terr := luid.DeleteRoute(r.Cidr, r.Via[0].Addr())\n\t\tif err != nil {\n\t\t\tt.l.WithError(err).WithField(\"route\", r).Error(\"Failed to remove route\")\n\t\t} else {\n\t\t\tt.l.WithField(\"route\", r).Info(\"Removed route\")\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (t *winTun) RoutesFor(ip netip.Addr) routing.Gateways {\n\tr, _ := t.routeTree.Load().Lookup(ip)\n\treturn r\n}\n\nfunc (t *winTun) Networks() []netip.Prefix {\n\treturn t.vpnNetworks\n}\n\nfunc (t *winTun) Name() string {\n\treturn t.Device\n}\n\nfunc (t *winTun) Read(b []byte) (int, error) {\n\treturn t.tun.Read(b, 0)\n}\n\nfunc (t *winTun) Write(b []byte) (int, error) {\n\treturn t.tun.Write(b, 0)\n}\n\nfunc (t *winTun) SupportsMultiqueue() bool {\n\treturn false\n}\n\nfunc (t *winTun) NewMultiQueueReader() (io.ReadWriteCloser, error) {\n\treturn nil, fmt.Errorf(\"TODO: multiqueue not implemented for windows\")\n}\n\nfunc (t *winTun) Close() error {\n\t// It seems that the Windows networking stack doesn't like it when we destroy interfaces that have active routes,\n\t// so to be certain, just remove everything before destroying.\n\tluid := winipcfg.LUID(t.tun.LUID())\n\t_ = luid.FlushRoutes(windows.AF_INET)\n\t_ = luid.FlushIPAddresses(windows.AF_INET)\n\n\t_ = luid.FlushRoutes(windows.AF_INET6)\n\t_ = luid.FlushIPAddresses(windows.AF_INET6)\n\n\t_ = luid.FlushDNS(windows.AF_INET)\n\t_ = luid.FlushDNS(windows.AF_INET6)\n\n\treturn t.tun.Close()\n}\n\nfunc generateGUIDByDeviceName(name string) (*windows.GUID, error) {\n\t// GUID is 128 bit\n\thash := crypto.MD5.New()\n\n\t_, err := hash.Write([]byte(tunGUIDLabel))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t_, err = hash.Write([]byte(name))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsum := hash.Sum(nil)\n\n\treturn (*windows.GUID)(unsafe.Pointer(&sum[0])), nil\n}\n\nfunc checkWinTunExists() error {\n\tmyPath, err := os.Executable()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tarch := runtime.GOARCH\n\tswitch arch {\n\tcase \"386\":\n\t\t//NOTE: wintun bundles 386 as x86\n\t\tarch = \"x86\"\n\t}\n\n\t_, err = syscall.LoadDLL(filepath.Join(filepath.Dir(myPath), \"dist\", \"windows\", \"wintun\", \"bin\", arch, \"wintun.dll\"))\n\treturn err\n}\n"
  },
  {
    "path": "overlay/user.go",
    "content": "package overlay\n\nimport (\n\t\"io\"\n\t\"net/netip\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/config\"\n\t\"github.com/slackhq/nebula/routing\"\n)\n\nfunc NewUserDeviceFromConfig(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, routines int) (Device, error) {\n\treturn NewUserDevice(vpnNetworks)\n}\n\nfunc NewUserDevice(vpnNetworks []netip.Prefix) (Device, error) {\n\t// these pipes guarantee each write/read will match 1:1\n\tor, ow := io.Pipe()\n\tir, iw := io.Pipe()\n\treturn &UserDevice{\n\t\tvpnNetworks:    vpnNetworks,\n\t\toutboundReader: or,\n\t\toutboundWriter: ow,\n\t\tinboundReader:  ir,\n\t\tinboundWriter:  iw,\n\t}, nil\n}\n\ntype UserDevice struct {\n\tvpnNetworks []netip.Prefix\n\n\toutboundReader *io.PipeReader\n\toutboundWriter *io.PipeWriter\n\n\tinboundReader *io.PipeReader\n\tinboundWriter *io.PipeWriter\n}\n\nfunc (d *UserDevice) Activate() error {\n\treturn nil\n}\n\nfunc (d *UserDevice) Networks() []netip.Prefix { return d.vpnNetworks }\nfunc (d *UserDevice) Name() string             { return \"faketun0\" }\nfunc (d *UserDevice) RoutesFor(ip netip.Addr) routing.Gateways {\n\treturn routing.Gateways{routing.NewGateway(ip, 1)}\n}\n\nfunc (d *UserDevice) SupportsMultiqueue() bool {\n\treturn true\n}\n\nfunc (d *UserDevice) NewMultiQueueReader() (io.ReadWriteCloser, error) {\n\treturn d, nil\n}\n\nfunc (d *UserDevice) Pipe() (*io.PipeReader, *io.PipeWriter) {\n\treturn d.inboundReader, d.outboundWriter\n}\n\nfunc (d *UserDevice) Read(p []byte) (n int, err error) {\n\treturn d.outboundReader.Read(p)\n}\nfunc (d *UserDevice) Write(p []byte) (n int, err error) {\n\treturn d.inboundWriter.Write(p)\n}\nfunc (d *UserDevice) Close() error {\n\td.inboundWriter.Close()\n\td.outboundWriter.Close()\n\treturn nil\n}\n"
  },
  {
    "path": "pkclient/pkclient.go",
    "content": "package pkclient\n\nimport (\n\t\"crypto/ecdsa\"\n\t\"crypto/x509\"\n\t\"fmt\"\n\t\"io\"\n\t\"strconv\"\n\n\t\"github.com/stefanberger/go-pkcs11uri\"\n)\n\ntype Client interface {\n\tio.Closer\n\tGetPubKey() ([]byte, error)\n\tDeriveNoise(peerPubKey []byte) ([]byte, error)\n\tTest() error\n}\n\nconst NoiseKeySize = 32\n\nfunc FromUrl(pkurl string) (*PKClient, error) {\n\turi := pkcs11uri.New()\n\turi.SetAllowAnyModule(true) //todo\n\terr := uri.Parse(pkurl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tmodule, err := uri.GetModule()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tslotid := 0\n\tslot, ok := uri.GetPathAttribute(\"slot-id\", false)\n\tif !ok {\n\t\tslotid = 0\n\t} else {\n\t\tslotid, err = strconv.Atoi(slot)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tpin, _ := uri.GetPIN()\n\tid, _ := uri.GetPathAttribute(\"id\", false)\n\tlabel, _ := uri.GetPathAttribute(\"object\", false)\n\n\treturn New(module, uint(slotid), pin, id, label)\n}\n\nfunc ecKeyToArray(key *ecdsa.PublicKey) []byte {\n\tx := make([]byte, 32)\n\ty := make([]byte, 32)\n\tkey.X.FillBytes(x)\n\tkey.Y.FillBytes(y)\n\treturn append([]byte{0x04}, append(x, y...)...)\n}\n\nfunc formatPubkeyFromPublicKeyInfoAttr(d []byte) ([]byte, error) {\n\te, err := x509.ParsePKIXPublicKey(d)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tswitch t := e.(type) {\n\tcase *ecdsa.PublicKey:\n\t\treturn ecKeyToArray(e.(*ecdsa.PublicKey)), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unknown public key type: %T\", t)\n\t}\n}\n\nfunc (c *PKClient) Test() error {\n\tpub, err := c.GetPubKey()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get public key: %w\", err)\n\t}\n\tout, err := c.DeriveNoise(pub) //do an ECDH with ourselves as a quick test\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(out) != NoiseKeySize {\n\t\treturn fmt.Errorf(\"got a key of %d bytes, expected %d\", len(out), NoiseKeySize)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkclient/pkclient_cgo.go",
    "content": "//go:build cgo && pkcs11\n\npackage pkclient\n\nimport (\n\t\"encoding/asn1\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log\"\n\t\"math/big\"\n\n\t\"github.com/miekg/pkcs11\"\n\t\"github.com/miekg/pkcs11/p11\"\n)\n\ntype PKClient struct {\n\tmodule     p11.Module\n\tsession    p11.Session\n\tid         []byte\n\tlabel      []byte\n\tprivKeyObj p11.Object\n\tpubKeyObj  p11.Object\n}\n\ntype ecdsaSignature struct {\n\tR, S *big.Int\n}\n\n// New tries to open a session with the HSM, select the slot and login to it\nfunc New(hsmPath string, slotId uint, pin string, id string, label string) (*PKClient, error) {\n\tmodule, err := p11.OpenModule(hsmPath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to load module library: %s\", hsmPath)\n\t}\n\n\tslots, err := module.Slots()\n\tif err != nil {\n\t\tmodule.Destroy()\n\t\treturn nil, err\n\t}\n\n\t// Try to open a session on the slot\n\tslotIdx := 0\n\tfor i, slot := range slots {\n\t\tif slot.ID() == slotId {\n\t\t\tslotIdx = i\n\t\t\tbreak\n\t\t}\n\t}\n\n\tclient := &PKClient{\n\t\tmodule: module,\n\t\tid:     []byte(id),\n\t\tlabel:  []byte(label),\n\t}\n\n\tclient.session, err = slots[slotIdx].OpenWriteSession()\n\tif err != nil {\n\t\tmodule.Destroy()\n\t\treturn nil, fmt.Errorf(\"failed to open session on slot %d\", slotId)\n\t}\n\n\tif len(pin) != 0 {\n\t\terr = client.session.Login(pin)\n\t\tif err != nil {\n\t\t\t// ignore \"already logged in\"\n\t\t\tif !errors.Is(err, pkcs11.Error(256)) {\n\t\t\t\t_ = client.session.Close()\n\t\t\t\treturn nil, fmt.Errorf(\"unable to login. error: %w\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Make sure the hsm has a private key for deriving\n\tclient.privKeyObj, err = client.findDeriveKey(client.id, client.label, true)\n\tif err != nil {\n\t\t_ = client.Close() //log out, close session, destroy module\n\t\treturn nil, fmt.Errorf(\"failed to find private key for deriving: %w\", err)\n\t}\n\n\treturn client, nil\n}\n\n// Close cleans up properly and logs out\nfunc (c *PKClient) Close() error {\n\tvar err error = nil\n\tif c.session != nil {\n\t\t_ = c.session.Logout() //if logout fails, we still want to close\n\t\terr = c.session.Close()\n\t}\n\n\tc.module.Destroy()\n\treturn err\n}\n\n// Try to find a suitable key on the hsm for key derivation\n// parameter GET_PUB_KEY sets the search pattern for a public or private key\nfunc (c *PKClient) findDeriveKey(id []byte, label []byte, private bool) (key p11.Object, err error) {\n\tkeyClass := pkcs11.CKO_PRIVATE_KEY\n\tif !private {\n\t\tkeyClass = pkcs11.CKO_PUBLIC_KEY\n\t}\n\tkeyAttrs := []*pkcs11.Attribute{\n\t\t//todo, not all HSMs seem to report this, even if its true: pkcs11.NewAttribute(pkcs11.CKA_DERIVE, true),\n\t\tpkcs11.NewAttribute(pkcs11.CKA_CLASS, keyClass),\n\t}\n\n\tif id != nil && len(id) != 0 {\n\t\tkeyAttrs = append(keyAttrs, pkcs11.NewAttribute(pkcs11.CKA_ID, id))\n\t}\n\tif label != nil && len(label) != 0 {\n\t\tkeyAttrs = append(keyAttrs, pkcs11.NewAttribute(pkcs11.CKA_LABEL, label))\n\t}\n\n\treturn c.session.FindObject(keyAttrs)\n}\n\nfunc (c *PKClient) listDeriveKeys(id []byte, label []byte, private bool) {\n\tkeyClass := pkcs11.CKO_PRIVATE_KEY\n\tif !private {\n\t\tkeyClass = pkcs11.CKO_PUBLIC_KEY\n\t}\n\tkeyAttrs := []*pkcs11.Attribute{\n\t\tpkcs11.NewAttribute(pkcs11.CKA_CLASS, keyClass),\n\t}\n\n\tif id != nil && len(id) != 0 {\n\t\tkeyAttrs = append(keyAttrs, pkcs11.NewAttribute(pkcs11.CKA_ID, id))\n\t}\n\tif label != nil && len(label) != 0 {\n\t\tkeyAttrs = append(keyAttrs, pkcs11.NewAttribute(pkcs11.CKA_LABEL, label))\n\t}\n\n\tobjects, err := c.session.FindObjects(keyAttrs)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, obj := range objects {\n\t\tl, err := obj.Label()\n\t\tlog.Printf(\"%s, %v\", l, err)\n\t\ta, err := obj.Attribute(pkcs11.CKA_DERIVE)\n\t\tlog.Printf(\"DERIVE: %s %v, %v\", l, a, err)\n\t}\n}\n\n// SignASN1 signs some data. Returns the ASN.1 encoded signature.\nfunc (c *PKClient) SignASN1(data []byte) ([]byte, error) {\n\tmech := pkcs11.NewMechanism(pkcs11.CKM_ECDSA_SHA256, nil)\n\tsk := p11.PrivateKey(c.privKeyObj)\n\trawSig, err := sk.Sign(*mech, data)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// PKCS #11 Mechanisms v2.30:\n\t// \"The signature octets correspond to the concatenation of the ECDSA values r and s,\n\t// both represented as an octet string of equal length of at most nLen with the most\n\t// significant byte first. If r and s have different octet length, the shorter of both\n\t// must be padded with leading zero octets such that both have the same octet length.\n\t// Loosely spoken, the first half of the signature is r and the second half is s.\"\n\tr := new(big.Int).SetBytes(rawSig[:len(rawSig)/2])\n\ts := new(big.Int).SetBytes(rawSig[len(rawSig)/2:])\n\treturn asn1.Marshal(ecdsaSignature{r, s})\n}\n\n// DeriveNoise derives a shared secret using the input public key against the private key that was found during setup.\n// Returns a fixed 32 byte array.\nfunc (c *PKClient) DeriveNoise(peerPubKey []byte) ([]byte, error) {\n\t// Before we call derive, we need to have an array of attributes which specify the type of\n\t// key to be returned, in our case, it's the shared secret key, produced via deriving\n\t// This template pulled from OpenSC pkclient-tool.c line 4038\n\tattrTemplate := []*pkcs11.Attribute{\n\t\tpkcs11.NewAttribute(pkcs11.CKA_TOKEN, false),\n\t\tpkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_SECRET_KEY),\n\t\tpkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_GENERIC_SECRET),\n\t\tpkcs11.NewAttribute(pkcs11.CKA_SENSITIVE, false),\n\t\tpkcs11.NewAttribute(pkcs11.CKA_EXTRACTABLE, true),\n\t\tpkcs11.NewAttribute(pkcs11.CKA_ENCRYPT, true),\n\t\tpkcs11.NewAttribute(pkcs11.CKA_DECRYPT, true),\n\t\tpkcs11.NewAttribute(pkcs11.CKA_WRAP, true),\n\t\tpkcs11.NewAttribute(pkcs11.CKA_UNWRAP, true),\n\t\tpkcs11.NewAttribute(pkcs11.CKA_VALUE_LEN, NoiseKeySize),\n\t}\n\n\t// Set up the parameters which include the peer's public key\n\tecdhParams := pkcs11.NewECDH1DeriveParams(pkcs11.CKD_NULL, nil, peerPubKey)\n\tmech := pkcs11.NewMechanism(pkcs11.CKM_ECDH1_DERIVE, ecdhParams)\n\tsk := p11.PrivateKey(c.privKeyObj)\n\n\ttmpKey, err := sk.Derive(*mech, attrTemplate)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif tmpKey == nil || len(tmpKey) == 0 {\n\t\treturn nil, fmt.Errorf(\"got an empty secret key\")\n\t}\n\tsecret := make([]byte, NoiseKeySize)\n\tcopy(secret[:], tmpKey[:NoiseKeySize])\n\treturn secret, nil\n}\n\nfunc (c *PKClient) GetPubKey() ([]byte, error) {\n\td, err := c.privKeyObj.Attribute(pkcs11.CKA_PUBLIC_KEY_INFO)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif d != nil && len(d) > 0 {\n\t\treturn formatPubkeyFromPublicKeyInfoAttr(d)\n\t}\n\tc.pubKeyObj, err = c.findDeriveKey(c.id, c.label, false)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"pkcs11 module gave us a nil CKA_PUBLIC_KEY_INFO, and looking up the public key also failed: %w\", err)\n\t}\n\td, err = c.pubKeyObj.Attribute(pkcs11.CKA_EC_POINT)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"pkcs11 module gave us a nil CKA_PUBLIC_KEY_INFO, and reading CKA_EC_POINT also failed: %w\", err)\n\t}\n\tif d == nil || len(d) < 1 {\n\t\treturn nil, fmt.Errorf(\"pkcs11 module gave us a nil or empty CKA_EC_POINT\")\n\t}\n\tswitch len(d) {\n\tcase 65: //length of 0x04 + len(X) + len(Y)\n\t\treturn d, nil\n\tcase 67: //as above, DER-encoded IIRC?\n\t\treturn d[2:], nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unknown public key length: %d\", len(d))\n\t}\n}\n"
  },
  {
    "path": "pkclient/pkclient_stub.go",
    "content": "//go:build !cgo || !pkcs11\n\npackage pkclient\n\nimport \"errors\"\n\ntype PKClient struct {\n}\n\nvar notImplemented = errors.New(\"not implemented\")\n\nfunc New(hsmPath string, slotId uint, pin string, id string, label string) (*PKClient, error) {\n\treturn nil, notImplemented\n}\n\nfunc (c *PKClient) Close() error {\n\treturn nil\n}\n\nfunc (c *PKClient) SignASN1(data []byte) ([]byte, error) {\n\treturn nil, notImplemented\n}\n\nfunc (c *PKClient) DeriveNoise(_ []byte) ([]byte, error) {\n\treturn nil, notImplemented\n}\n\nfunc (c *PKClient) GetPubKey() ([]byte, error) {\n\treturn nil, notImplemented\n}\n"
  },
  {
    "path": "pki.go",
    "content": "package nebula\n\nimport (\n\t\"encoding/binary\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"os\"\n\t\"slices\"\n\t\"strings\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/gaissmai/bart\"\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/cert\"\n\t\"github.com/slackhq/nebula/config\"\n\t\"github.com/slackhq/nebula/util\"\n)\n\ntype PKI struct {\n\tcs     atomic.Pointer[CertState]\n\tcaPool atomic.Pointer[cert.CAPool]\n\tl      *logrus.Logger\n}\n\ntype CertState struct {\n\tv1Cert           cert.Certificate\n\tv1HandshakeBytes []byte\n\n\tv2Cert           cert.Certificate\n\tv2HandshakeBytes []byte\n\n\tinitiatingVersion cert.Version\n\tprivateKey        []byte\n\tpkcs11Backed      bool\n\tcipher            string\n\n\tmyVpnNetworks            []netip.Prefix\n\tmyVpnNetworksTable       *bart.Lite\n\tmyVpnAddrs               []netip.Addr\n\tmyVpnAddrsTable          *bart.Lite\n\tmyVpnBroadcastAddrsTable *bart.Lite\n}\n\nfunc NewPKIFromConfig(l *logrus.Logger, c *config.C) (*PKI, error) {\n\tpki := &PKI{l: l}\n\terr := pki.reload(c, true)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tc.RegisterReloadCallback(func(c *config.C) {\n\t\trErr := pki.reload(c, false)\n\t\tif rErr != nil {\n\t\t\tutil.LogWithContextIfNeeded(\"Failed to reload PKI from config\", rErr, l)\n\t\t}\n\t})\n\n\treturn pki, nil\n}\n\nfunc (p *PKI) GetCAPool() *cert.CAPool {\n\treturn p.caPool.Load()\n}\n\nfunc (p *PKI) getCertState() *CertState {\n\treturn p.cs.Load()\n}\n\nfunc (p *PKI) reload(c *config.C, initial bool) error {\n\terr := p.reloadCerts(c, initial)\n\tif err != nil {\n\t\tif initial {\n\t\t\treturn err\n\t\t}\n\t\terr.Log(p.l)\n\t}\n\n\terr = p.reloadCAPool(c)\n\tif err != nil {\n\t\tif initial {\n\t\t\treturn err\n\t\t}\n\t\terr.Log(p.l)\n\t}\n\n\treturn nil\n}\n\nfunc (p *PKI) reloadCerts(c *config.C, initial bool) *util.ContextualError {\n\tnewState, err := newCertStateFromConfig(c)\n\tif err != nil {\n\t\treturn util.NewContextualError(\"Could not load client cert\", nil, err)\n\t}\n\n\tif !initial {\n\t\tcurrentState := p.cs.Load()\n\t\tif newState.v1Cert != nil {\n\t\t\tif currentState.v1Cert == nil {\n\t\t\t\t//adding certs is fine, actually. Networks-in-common confirmed in newCertState().\n\t\t\t} else {\n\t\t\t\t// did IP in cert change? if so, don't set\n\t\t\t\tif !slices.Equal(currentState.v1Cert.Networks(), newState.v1Cert.Networks()) {\n\t\t\t\t\treturn util.NewContextualError(\n\t\t\t\t\t\t\"Networks in new cert was different from old\",\n\t\t\t\t\t\tm{\"new_networks\": newState.v1Cert.Networks(), \"old_networks\": currentState.v1Cert.Networks(), \"cert_version\": cert.Version1},\n\t\t\t\t\t\tnil,\n\t\t\t\t\t)\n\t\t\t\t}\n\n\t\t\t\tif currentState.v1Cert.Curve() != newState.v1Cert.Curve() {\n\t\t\t\t\treturn util.NewContextualError(\n\t\t\t\t\t\t\"Curve in new v1 cert was different from old\",\n\t\t\t\t\t\tm{\"new_curve\": newState.v1Cert.Curve(), \"old_curve\": currentState.v1Cert.Curve(), \"cert_version\": cert.Version1},\n\t\t\t\t\t\tnil,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif newState.v2Cert != nil {\n\t\t\tif currentState.v2Cert == nil {\n\t\t\t\t//adding certs is fine, actually\n\t\t\t} else {\n\t\t\t\t// did IP in cert change? if so, don't set\n\t\t\t\tif !slices.Equal(currentState.v2Cert.Networks(), newState.v2Cert.Networks()) {\n\t\t\t\t\treturn util.NewContextualError(\n\t\t\t\t\t\t\"Networks in new cert was different from old\",\n\t\t\t\t\t\tm{\"new_networks\": newState.v2Cert.Networks(), \"old_networks\": currentState.v2Cert.Networks(), \"cert_version\": cert.Version2},\n\t\t\t\t\t\tnil,\n\t\t\t\t\t)\n\t\t\t\t}\n\n\t\t\t\tif currentState.v2Cert.Curve() != newState.v2Cert.Curve() {\n\t\t\t\t\treturn util.NewContextualError(\n\t\t\t\t\t\t\"Curve in new cert was different from old\",\n\t\t\t\t\t\tm{\"new_curve\": newState.v2Cert.Curve(), \"old_curve\": currentState.v2Cert.Curve(), \"cert_version\": cert.Version2},\n\t\t\t\t\t\tnil,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\n\t\t} else if currentState.v2Cert != nil {\n\t\t\t//newState.v1Cert is non-nil bc empty certstates aren't permitted\n\t\t\tif newState.v1Cert == nil {\n\t\t\t\treturn util.NewContextualError(\"v1 and v2 certs are nil, this should be impossible\", nil, err)\n\t\t\t}\n\t\t\t//if we're going to v1-only, we need to make sure we didn't orphan any v2-cert vpnaddrs\n\t\t\tif !slices.Equal(currentState.v2Cert.Networks(), newState.v1Cert.Networks()) {\n\t\t\t\treturn util.NewContextualError(\n\t\t\t\t\t\"Removing a V2 cert is not permitted unless it has identical networks to the new V1 cert\",\n\t\t\t\t\tm{\"new_v1_networks\": newState.v1Cert.Networks(), \"old_v2_networks\": currentState.v2Cert.Networks()},\n\t\t\t\t\tnil,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\t// Cipher cant be hot swapped so just leave it at what it was before\n\t\tnewState.cipher = currentState.cipher\n\n\t} else {\n\t\tnewState.cipher = c.GetString(\"cipher\", \"aes\")\n\t\t//TODO: this sucks and we should make it not a global\n\t\tswitch newState.cipher {\n\t\tcase \"aes\":\n\t\t\tnoiseEndianness = binary.BigEndian\n\t\tcase \"chachapoly\":\n\t\t\tnoiseEndianness = binary.LittleEndian\n\t\tdefault:\n\t\t\treturn util.NewContextualError(\n\t\t\t\t\"unknown cipher\",\n\t\t\t\tm{\"cipher\": newState.cipher},\n\t\t\t\tnil,\n\t\t\t)\n\t\t}\n\t}\n\n\tp.cs.Store(newState)\n\n\tif initial {\n\t\tp.l.WithField(\"cert\", newState).Debug(\"Client nebula certificate(s)\")\n\t} else {\n\t\tp.l.WithField(\"cert\", newState).Info(\"Client certificate(s) refreshed from disk\")\n\t}\n\treturn nil\n}\n\nfunc (p *PKI) reloadCAPool(c *config.C) *util.ContextualError {\n\tcaPool, err := loadCAPoolFromConfig(p.l, c)\n\tif err != nil {\n\t\treturn util.NewContextualError(\"Failed to load ca from config\", nil, err)\n\t}\n\n\tp.caPool.Store(caPool)\n\tp.l.WithField(\"fingerprints\", caPool.GetFingerprints()).Debug(\"Trusted CA fingerprints\")\n\treturn nil\n}\n\nfunc (cs *CertState) GetDefaultCertificate() cert.Certificate {\n\tc := cs.getCertificate(cs.initiatingVersion)\n\tif c == nil {\n\t\tpanic(\"No default certificate found\")\n\t}\n\treturn c\n}\n\nfunc (cs *CertState) getCertificate(v cert.Version) cert.Certificate {\n\tswitch v {\n\tcase cert.Version1:\n\t\treturn cs.v1Cert\n\tcase cert.Version2:\n\t\treturn cs.v2Cert\n\t}\n\n\treturn nil\n}\n\n// getHandshakeBytes returns the cached bytes to be used in a handshake message for the requested version.\n// Callers must check if the return []byte is nil.\nfunc (cs *CertState) getHandshakeBytes(v cert.Version) []byte {\n\tswitch v {\n\tcase cert.Version1:\n\t\treturn cs.v1HandshakeBytes\n\tcase cert.Version2:\n\t\treturn cs.v2HandshakeBytes\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc (cs *CertState) String() string {\n\tb, err := cs.MarshalJSON()\n\tif err != nil {\n\t\treturn fmt.Sprintf(\"error marshaling certificate state: %v\", err)\n\t}\n\treturn string(b)\n}\n\nfunc (cs *CertState) MarshalJSON() ([]byte, error) {\n\tmsg := []json.RawMessage{}\n\tif cs.v1Cert != nil {\n\t\tb, err := cs.v1Cert.MarshalJSON()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tmsg = append(msg, b)\n\t}\n\n\tif cs.v2Cert != nil {\n\t\tb, err := cs.v2Cert.MarshalJSON()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tmsg = append(msg, b)\n\t}\n\n\treturn json.Marshal(msg)\n}\n\nfunc newCertStateFromConfig(c *config.C) (*CertState, error) {\n\tvar err error\n\n\tprivPathOrPEM := c.GetString(\"pki.key\", \"\")\n\tif privPathOrPEM == \"\" {\n\t\treturn nil, errors.New(\"no pki.key path or PEM data provided\")\n\t}\n\n\trawKey, curve, isPkcs11, err := loadPrivateKey(privPathOrPEM)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar rawCert []byte\n\n\tpubPathOrPEM := c.GetString(\"pki.cert\", \"\")\n\tif pubPathOrPEM == \"\" {\n\t\treturn nil, errors.New(\"no pki.cert path or PEM data provided\")\n\t}\n\n\tif strings.Contains(pubPathOrPEM, \"-----BEGIN\") {\n\t\trawCert = []byte(pubPathOrPEM)\n\t\tpubPathOrPEM = \"<inline>\"\n\n\t} else {\n\t\trawCert, err = os.ReadFile(pubPathOrPEM)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"unable to read pki.cert file %s: %s\", pubPathOrPEM, err)\n\t\t}\n\t}\n\n\tvar crt, v1, v2 cert.Certificate\n\tfor {\n\t\t// Load the certificate\n\t\tcrt, rawCert, err = loadCertificate(rawCert)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tswitch crt.Version() {\n\t\tcase cert.Version1:\n\t\t\tif v1 != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"v1 certificate already found in pki.cert\")\n\t\t\t}\n\t\t\tv1 = crt\n\t\tcase cert.Version2:\n\t\t\tif v2 != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"v2 certificate already found in pki.cert\")\n\t\t\t}\n\t\t\tv2 = crt\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"unknown certificate version %v\", crt.Version())\n\t\t}\n\n\t\tif len(rawCert) == 0 || strings.TrimSpace(string(rawCert)) == \"\" {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif v1 == nil && v2 == nil {\n\t\treturn nil, errors.New(\"no certificates found in pki.cert\")\n\t}\n\n\tuseInitiatingVersion := uint32(1)\n\tif v1 == nil {\n\t\t// The only condition that requires v2 as the default is if only a v2 certificate is present\n\t\t// We do this to avoid having to configure it specifically in the config file\n\t\tuseInitiatingVersion = 2\n\t}\n\n\trawInitiatingVersion := c.GetUint32(\"pki.initiating_version\", useInitiatingVersion)\n\tvar initiatingVersion cert.Version\n\tswitch rawInitiatingVersion {\n\tcase 1:\n\t\tif v1 == nil {\n\t\t\treturn nil, fmt.Errorf(\"can not use pki.initiating_version 1 without a v1 certificate in pki.cert\")\n\t\t}\n\t\tinitiatingVersion = cert.Version1\n\tcase 2:\n\t\tinitiatingVersion = cert.Version2\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unknown pki.initiating_version: %v\", rawInitiatingVersion)\n\t}\n\n\treturn newCertState(initiatingVersion, v1, v2, isPkcs11, curve, rawKey)\n}\n\nfunc newCertState(dv cert.Version, v1, v2 cert.Certificate, pkcs11backed bool, privateKeyCurve cert.Curve, privateKey []byte) (*CertState, error) {\n\tcs := CertState{\n\t\tprivateKey:               privateKey,\n\t\tpkcs11Backed:             pkcs11backed,\n\t\tmyVpnNetworksTable:       new(bart.Lite),\n\t\tmyVpnAddrsTable:          new(bart.Lite),\n\t\tmyVpnBroadcastAddrsTable: new(bart.Lite),\n\t}\n\n\tif v1 != nil && v2 != nil {\n\t\tif !slices.Equal(v1.PublicKey(), v2.PublicKey()) {\n\t\t\treturn nil, util.NewContextualError(\"v1 and v2 public keys are not the same, ignoring\", nil, nil)\n\t\t}\n\n\t\tif v1.Curve() != v2.Curve() {\n\t\t\treturn nil, util.NewContextualError(\"v1 and v2 curve are not the same, ignoring\", nil, nil)\n\t\t}\n\n\t\tif v1.Networks()[0] != v2.Networks()[0] {\n\t\t\treturn nil, util.NewContextualError(\"v1 and v2 networks are not the same\", nil, nil)\n\t\t}\n\n\t\tcs.initiatingVersion = dv\n\t}\n\n\tif v1 != nil {\n\t\tif pkcs11backed {\n\t\t\t//NOTE: We do not currently have a method to verify a public private key pair when the private key is in an hsm\n\t\t} else {\n\t\t\tif err := v1.VerifyPrivateKey(privateKeyCurve, privateKey); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"private key is not a pair with public key in nebula cert\")\n\t\t\t}\n\t\t}\n\n\t\tv1hs, err := v1.MarshalForHandshakes()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error marshalling certificate for handshake: %w\", err)\n\t\t}\n\t\tcs.v1Cert = v1\n\t\tcs.v1HandshakeBytes = v1hs\n\n\t\tif cs.initiatingVersion == 0 {\n\t\t\tcs.initiatingVersion = cert.Version1\n\t\t}\n\t}\n\n\tif v2 != nil {\n\t\tif pkcs11backed {\n\t\t\t//NOTE: We do not currently have a method to verify a public private key pair when the private key is in an hsm\n\t\t} else {\n\t\t\tif err := v2.VerifyPrivateKey(privateKeyCurve, privateKey); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"private key is not a pair with public key in nebula cert\")\n\t\t\t}\n\t\t}\n\n\t\tv2hs, err := v2.MarshalForHandshakes()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error marshalling certificate for handshake: %w\", err)\n\t\t}\n\t\tcs.v2Cert = v2\n\t\tcs.v2HandshakeBytes = v2hs\n\n\t\tif cs.initiatingVersion == 0 {\n\t\t\tcs.initiatingVersion = cert.Version2\n\t\t}\n\t}\n\n\tvar crt cert.Certificate\n\tcrt = cs.getCertificate(cert.Version2)\n\tif crt == nil {\n\t\t// v2 certificates are a superset, only look at v1 if its all we have\n\t\tcrt = cs.getCertificate(cert.Version1)\n\t}\n\n\tfor _, network := range crt.Networks() {\n\t\tcs.myVpnNetworks = append(cs.myVpnNetworks, network)\n\t\tcs.myVpnNetworksTable.Insert(network)\n\n\t\tcs.myVpnAddrs = append(cs.myVpnAddrs, network.Addr())\n\t\tcs.myVpnAddrsTable.Insert(netip.PrefixFrom(network.Addr(), network.Addr().BitLen()))\n\n\t\tif network.Addr().Is4() {\n\t\t\taddr := network.Masked().Addr().As4()\n\t\t\tmask := net.CIDRMask(network.Bits(), network.Addr().BitLen())\n\t\t\tbinary.BigEndian.PutUint32(addr[:], binary.BigEndian.Uint32(addr[:])|^binary.BigEndian.Uint32(mask))\n\t\t\tcs.myVpnBroadcastAddrsTable.Insert(netip.PrefixFrom(netip.AddrFrom4(addr), network.Addr().BitLen()))\n\t\t}\n\t}\n\n\treturn &cs, nil\n}\n\nfunc loadPrivateKey(privPathOrPEM string) (rawKey []byte, curve cert.Curve, isPkcs11 bool, err error) {\n\tvar pemPrivateKey []byte\n\tif strings.Contains(privPathOrPEM, \"-----BEGIN\") {\n\t\tpemPrivateKey = []byte(privPathOrPEM)\n\t\tprivPathOrPEM = \"<inline>\"\n\t\trawKey, _, curve, err = cert.UnmarshalPrivateKeyFromPEM(pemPrivateKey)\n\t\tif err != nil {\n\t\t\treturn nil, curve, false, fmt.Errorf(\"error while unmarshaling pki.key %s: %s\", privPathOrPEM, err)\n\t\t}\n\t} else if strings.HasPrefix(privPathOrPEM, \"pkcs11:\") {\n\t\trawKey = []byte(privPathOrPEM)\n\t\treturn rawKey, cert.Curve_P256, true, nil\n\t} else {\n\t\tpemPrivateKey, err = os.ReadFile(privPathOrPEM)\n\t\tif err != nil {\n\t\t\treturn nil, curve, false, fmt.Errorf(\"unable to read pki.key file %s: %s\", privPathOrPEM, err)\n\t\t}\n\t\trawKey, _, curve, err = cert.UnmarshalPrivateKeyFromPEM(pemPrivateKey)\n\t\tif err != nil {\n\t\t\treturn nil, curve, false, fmt.Errorf(\"error while unmarshaling pki.key %s: %s\", privPathOrPEM, err)\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc loadCertificate(b []byte) (cert.Certificate, []byte, error) {\n\tc, b, err := cert.UnmarshalCertificateFromPEM(b)\n\tif err != nil {\n\t\treturn nil, b, fmt.Errorf(\"error while unmarshaling pki.cert: %w\", err)\n\t}\n\n\tif c.Expired(time.Now()) {\n\t\treturn nil, b, fmt.Errorf(\"nebula certificate for this host is expired\")\n\t}\n\n\tif len(c.Networks()) == 0 {\n\t\treturn nil, b, fmt.Errorf(\"no networks encoded in certificate\")\n\t}\n\n\tif c.IsCA() {\n\t\treturn nil, b, fmt.Errorf(\"host certificate is a CA certificate\")\n\t}\n\n\treturn c, b, nil\n}\n\nfunc loadCAPoolFromConfig(l *logrus.Logger, c *config.C) (*cert.CAPool, error) {\n\tvar rawCA []byte\n\tvar err error\n\n\tcaPathOrPEM := c.GetString(\"pki.ca\", \"\")\n\tif caPathOrPEM == \"\" {\n\t\treturn nil, errors.New(\"no pki.ca path or PEM data provided\")\n\t}\n\n\tif strings.Contains(caPathOrPEM, \"-----BEGIN\") {\n\t\trawCA = []byte(caPathOrPEM)\n\n\t} else {\n\t\trawCA, err = os.ReadFile(caPathOrPEM)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"unable to read pki.ca file %s: %s\", caPathOrPEM, err)\n\t\t}\n\t}\n\n\tcaPool, err := cert.NewCAPoolFromPEM(rawCA)\n\tif errors.Is(err, cert.ErrExpired) {\n\t\tvar expired int\n\t\tfor _, crt := range caPool.CAs {\n\t\t\tif crt.Certificate.Expired(time.Now()) {\n\t\t\t\texpired++\n\t\t\t\tl.WithField(\"cert\", crt).Warn(\"expired certificate present in CA pool\")\n\t\t\t}\n\t\t}\n\n\t\tif expired >= len(caPool.CAs) {\n\t\t\treturn nil, errors.New(\"no valid CA certificates present\")\n\t\t}\n\n\t} else if err != nil {\n\t\treturn nil, fmt.Errorf(\"error while adding CA certificate to CA trust store: %s\", err)\n\t}\n\n\tbl := c.GetStringSlice(\"pki.blocklist\", []string{})\n\tif len(bl) > 0 {\n\t\tfor _, fp := range bl {\n\t\t\tcaPool.BlocklistFingerprint(fp)\n\t\t}\n\n\t\tl.WithField(\"fingerprintCount\", len(bl)).Info(\"Blocklisted certificates\")\n\t}\n\n\treturn caPool, nil\n}\n"
  },
  {
    "path": "punchy.go",
    "content": "package nebula\n\nimport (\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/config\"\n)\n\ntype Punchy struct {\n\tpunch           atomic.Bool\n\trespond         atomic.Bool\n\tdelay           atomic.Int64\n\trespondDelay    atomic.Int64\n\tpunchEverything atomic.Bool\n\tl               *logrus.Logger\n}\n\nfunc NewPunchyFromConfig(l *logrus.Logger, c *config.C) *Punchy {\n\tp := &Punchy{l: l}\n\n\tp.reload(c, true)\n\tc.RegisterReloadCallback(func(c *config.C) {\n\t\tp.reload(c, false)\n\t})\n\n\treturn p\n}\n\nfunc (p *Punchy) reload(c *config.C, initial bool) {\n\tif initial {\n\t\tvar yes bool\n\t\tif c.IsSet(\"punchy.punch\") {\n\t\t\tyes = c.GetBool(\"punchy.punch\", false)\n\t\t} else {\n\t\t\t// Deprecated fallback\n\t\t\tyes = c.GetBool(\"punchy\", false)\n\t\t}\n\n\t\tp.punch.Store(yes)\n\t\tif yes {\n\t\t\tp.l.Info(\"punchy enabled\")\n\t\t} else {\n\t\t\tp.l.Info(\"punchy disabled\")\n\t\t}\n\n\t} else if c.HasChanged(\"punchy.punch\") || c.HasChanged(\"punchy\") {\n\t\t//TODO: it should be relatively easy to support this, just need to be able to cancel the goroutine and boot it up from here\n\t\tp.l.Warn(\"Changing punchy.punch with reload is not supported, ignoring.\")\n\t}\n\n\tif initial || c.HasChanged(\"punchy.respond\") || c.HasChanged(\"punch_back\") {\n\t\tvar yes bool\n\t\tif c.IsSet(\"punchy.respond\") {\n\t\t\tyes = c.GetBool(\"punchy.respond\", false)\n\t\t} else {\n\t\t\t// Deprecated fallback\n\t\t\tyes = c.GetBool(\"punch_back\", false)\n\t\t}\n\n\t\tp.respond.Store(yes)\n\n\t\tif !initial {\n\t\t\tp.l.Infof(\"punchy.respond changed to %v\", p.GetRespond())\n\t\t}\n\t}\n\n\t//NOTE: this will not apply to any in progress operations, only the next one\n\tif initial || c.HasChanged(\"punchy.delay\") {\n\t\tp.delay.Store((int64)(c.GetDuration(\"punchy.delay\", time.Second)))\n\t\tif !initial {\n\t\t\tp.l.Infof(\"punchy.delay changed to %s\", p.GetDelay())\n\t\t}\n\t}\n\n\tif initial || c.HasChanged(\"punchy.target_all_remotes\") {\n\t\tp.punchEverything.Store(c.GetBool(\"punchy.target_all_remotes\", false))\n\t\tif !initial {\n\t\t\tp.l.WithField(\"target_all_remotes\", p.GetTargetEverything()).Info(\"punchy.target_all_remotes changed\")\n\t\t}\n\t}\n\n\tif initial || c.HasChanged(\"punchy.respond_delay\") {\n\t\tp.respondDelay.Store((int64)(c.GetDuration(\"punchy.respond_delay\", 5*time.Second)))\n\t\tif !initial {\n\t\t\tp.l.Infof(\"punchy.respond_delay changed to %s\", p.GetRespondDelay())\n\t\t}\n\t}\n}\n\nfunc (p *Punchy) GetPunch() bool {\n\treturn p.punch.Load()\n}\n\nfunc (p *Punchy) GetRespond() bool {\n\treturn p.respond.Load()\n}\n\nfunc (p *Punchy) GetDelay() time.Duration {\n\treturn (time.Duration)(p.delay.Load())\n}\n\nfunc (p *Punchy) GetRespondDelay() time.Duration {\n\treturn (time.Duration)(p.respondDelay.Load())\n}\n\nfunc (p *Punchy) GetTargetEverything() bool {\n\treturn p.punchEverything.Load()\n}\n"
  },
  {
    "path": "punchy_test.go",
    "content": "package nebula\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/slackhq/nebula/config\"\n\t\"github.com/slackhq/nebula/test\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNewPunchyFromConfig(t *testing.T) {\n\tl := test.NewLogger()\n\tc := config.NewC(l)\n\n\t// Test defaults\n\tp := NewPunchyFromConfig(l, c)\n\tassert.False(t, p.GetPunch())\n\tassert.False(t, p.GetRespond())\n\tassert.Equal(t, time.Second, p.GetDelay())\n\tassert.Equal(t, 5*time.Second, p.GetRespondDelay())\n\n\t// punchy deprecation\n\tc.Settings[\"punchy\"] = true\n\tp = NewPunchyFromConfig(l, c)\n\tassert.True(t, p.GetPunch())\n\n\t// punchy.punch\n\tc.Settings[\"punchy\"] = map[string]any{\"punch\": true}\n\tp = NewPunchyFromConfig(l, c)\n\tassert.True(t, p.GetPunch())\n\n\t// punch_back deprecation\n\tc.Settings[\"punch_back\"] = true\n\tp = NewPunchyFromConfig(l, c)\n\tassert.True(t, p.GetRespond())\n\n\t// punchy.respond\n\tc.Settings[\"punchy\"] = map[string]any{\"respond\": true}\n\tc.Settings[\"punch_back\"] = false\n\tp = NewPunchyFromConfig(l, c)\n\tassert.True(t, p.GetRespond())\n\n\t// punchy.delay\n\tc.Settings[\"punchy\"] = map[string]any{\"delay\": \"1m\"}\n\tp = NewPunchyFromConfig(l, c)\n\tassert.Equal(t, time.Minute, p.GetDelay())\n\n\t// punchy.respond_delay\n\tc.Settings[\"punchy\"] = map[string]any{\"respond_delay\": \"1m\"}\n\tp = NewPunchyFromConfig(l, c)\n\tassert.Equal(t, time.Minute, p.GetRespondDelay())\n}\n\nfunc TestPunchy_reload(t *testing.T) {\n\tl := test.NewLogger()\n\tc := config.NewC(l)\n\tdelay, _ := time.ParseDuration(\"1m\")\n\trequire.NoError(t, c.LoadString(`\npunchy:\n  delay: 1m\n  respond: false\n`))\n\tp := NewPunchyFromConfig(l, c)\n\tassert.Equal(t, delay, p.GetDelay())\n\tassert.False(t, p.GetRespond())\n\n\tnewDelay, _ := time.ParseDuration(\"10m\")\n\trequire.NoError(t, c.ReloadConfigString(`\npunchy:\n  delay: 10m\n  respond: true\n`))\n\tp.reload(c, false)\n\tassert.Equal(t, newDelay, p.GetDelay())\n\tassert.True(t, p.GetRespond())\n}\n"
  },
  {
    "path": "relay_manager.go",
    "content": "package nebula\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"sync/atomic\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/cert\"\n\t\"github.com/slackhq/nebula/config\"\n\t\"github.com/slackhq/nebula/header\"\n)\n\ntype relayManager struct {\n\tl       *logrus.Logger\n\thostmap *HostMap\n\tamRelay atomic.Bool\n}\n\nfunc NewRelayManager(ctx context.Context, l *logrus.Logger, hostmap *HostMap, c *config.C) *relayManager {\n\trm := &relayManager{\n\t\tl:       l,\n\t\thostmap: hostmap,\n\t}\n\trm.reload(c, true)\n\tc.RegisterReloadCallback(func(c *config.C) {\n\t\terr := rm.reload(c, false)\n\t\tif err != nil {\n\t\t\tl.WithError(err).Error(\"Failed to reload relay_manager\")\n\t\t}\n\t})\n\treturn rm\n}\n\nfunc (rm *relayManager) reload(c *config.C, initial bool) error {\n\tif initial || c.HasChanged(\"relay.am_relay\") {\n\t\trm.setAmRelay(c.GetBool(\"relay.am_relay\", false))\n\t}\n\treturn nil\n}\n\nfunc (rm *relayManager) GetAmRelay() bool {\n\treturn rm.amRelay.Load()\n}\n\nfunc (rm *relayManager) setAmRelay(v bool) {\n\trm.amRelay.Store(v)\n}\n\n// AddRelay finds an available relay index on the hostmap, and associates the relay info with it.\n// relayHostInfo is the Nebula peer which can be used as a relay to access the target vpnIp.\nfunc AddRelay(l *logrus.Logger, relayHostInfo *HostInfo, hm *HostMap, vpnIp netip.Addr, remoteIdx *uint32, relayType int, state int) (uint32, error) {\n\thm.Lock()\n\tdefer hm.Unlock()\n\tfor range 32 {\n\t\tindex, err := generateIndex(l)\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\n\t\t_, inRelays := hm.Relays[index]\n\t\tif !inRelays {\n\t\t\t// Avoid standing up a relay that can't be used since only the primary hostinfo\n\t\t\t// will be pointed to by the relay logic\n\t\t\t//TODO: if there was an existing primary and it had relay state, should we merge?\n\t\t\thm.unlockedMakePrimary(relayHostInfo)\n\n\t\t\thm.Relays[index] = relayHostInfo\n\t\t\tnewRelay := Relay{\n\t\t\t\tType:       relayType,\n\t\t\t\tState:      state,\n\t\t\t\tLocalIndex: index,\n\t\t\t\tPeerAddr:   vpnIp,\n\t\t\t}\n\n\t\t\tif remoteIdx != nil {\n\t\t\t\tnewRelay.RemoteIndex = *remoteIdx\n\t\t\t}\n\t\t\trelayHostInfo.relayState.InsertRelay(vpnIp, index, &newRelay)\n\n\t\t\treturn index, nil\n\t\t}\n\t}\n\n\treturn 0, errors.New(\"failed to generate unique localIndexId\")\n}\n\n// EstablishRelay updates a Requested Relay to become an Established Relay, which can pass traffic.\nfunc (rm *relayManager) EstablishRelay(relayHostInfo *HostInfo, m *NebulaControl) (*Relay, error) {\n\trelay, ok := relayHostInfo.relayState.CompleteRelayByIdx(m.InitiatorRelayIndex, m.ResponderRelayIndex)\n\tif !ok {\n\t\tfields := logrus.Fields{\n\t\t\t\"relay\":               relayHostInfo.vpnAddrs[0],\n\t\t\t\"initiatorRelayIndex\": m.InitiatorRelayIndex,\n\t\t}\n\n\t\tif m.RelayFromAddr == nil {\n\t\t\tfields[\"relayFrom\"] = m.OldRelayFromAddr\n\t\t} else {\n\t\t\tfields[\"relayFrom\"] = m.RelayFromAddr\n\t\t}\n\n\t\tif m.RelayToAddr == nil {\n\t\t\tfields[\"relayTo\"] = m.OldRelayToAddr\n\t\t} else {\n\t\t\tfields[\"relayTo\"] = m.RelayToAddr\n\t\t}\n\n\t\trm.l.WithFields(fields).Info(\"relayManager failed to update relay\")\n\t\treturn nil, fmt.Errorf(\"unknown relay\")\n\t}\n\n\treturn relay, nil\n}\n\nfunc (rm *relayManager) HandleControlMsg(h *HostInfo, d []byte, f *Interface) {\n\tmsg := &NebulaControl{}\n\terr := msg.Unmarshal(d)\n\tif err != nil {\n\t\th.logger(f.l).WithError(err).Error(\"Failed to unmarshal control message\")\n\t\treturn\n\t}\n\n\tvar v cert.Version\n\tif msg.OldRelayFromAddr > 0 || msg.OldRelayToAddr > 0 {\n\t\tv = cert.Version1\n\n\t\tb := [4]byte{}\n\t\tbinary.BigEndian.PutUint32(b[:], msg.OldRelayFromAddr)\n\t\tmsg.RelayFromAddr = netAddrToProtoAddr(netip.AddrFrom4(b))\n\n\t\tbinary.BigEndian.PutUint32(b[:], msg.OldRelayToAddr)\n\t\tmsg.RelayToAddr = netAddrToProtoAddr(netip.AddrFrom4(b))\n\t} else {\n\t\tv = cert.Version2\n\t}\n\n\tswitch msg.Type {\n\tcase NebulaControl_CreateRelayRequest:\n\t\trm.handleCreateRelayRequest(v, h, f, msg)\n\tcase NebulaControl_CreateRelayResponse:\n\t\trm.handleCreateRelayResponse(v, h, f, msg)\n\t}\n}\n\nfunc (rm *relayManager) handleCreateRelayResponse(v cert.Version, h *HostInfo, f *Interface, m *NebulaControl) {\n\trm.l.WithFields(logrus.Fields{\n\t\t\"relayFrom\":           protoAddrToNetAddr(m.RelayFromAddr),\n\t\t\"relayTo\":             protoAddrToNetAddr(m.RelayToAddr),\n\t\t\"initiatorRelayIndex\": m.InitiatorRelayIndex,\n\t\t\"responderRelayIndex\": m.ResponderRelayIndex,\n\t\t\"vpnAddrs\":            h.vpnAddrs}).\n\t\tInfo(\"handleCreateRelayResponse\")\n\n\ttarget := m.RelayToAddr\n\ttargetAddr := protoAddrToNetAddr(target)\n\n\trelay, err := rm.EstablishRelay(h, m)\n\tif err != nil {\n\t\trm.l.WithError(err).Error(\"Failed to update relay for relayTo\")\n\t\treturn\n\t}\n\t// Do I need to complete the relays now?\n\tif relay.Type == TerminalType {\n\t\treturn\n\t}\n\t// I'm the middle man. Let the initiator know that the I've established the relay they requested.\n\tpeerHostInfo := rm.hostmap.QueryVpnAddr(relay.PeerAddr)\n\tif peerHostInfo == nil {\n\t\trm.l.WithField(\"relayTo\", relay.PeerAddr).Error(\"Can't find a HostInfo for peer\")\n\t\treturn\n\t}\n\tpeerRelay, ok := peerHostInfo.relayState.QueryRelayForByIp(targetAddr)\n\tif !ok {\n\t\trm.l.WithField(\"relayTo\", peerHostInfo.vpnAddrs[0]).Error(\"peerRelay does not have Relay state for relayTo\")\n\t\treturn\n\t}\n\tswitch peerRelay.State {\n\tcase Requested:\n\t\t// I initiated the request to this peer, but haven't heard back from the peer yet. I must wait for this peer\n\t\t// to respond to complete the connection.\n\tcase PeerRequested, Disestablished, Established:\n\t\tpeerHostInfo.relayState.UpdateRelayForByIpState(targetAddr, Established)\n\t\tresp := NebulaControl{\n\t\t\tType:                NebulaControl_CreateRelayResponse,\n\t\t\tResponderRelayIndex: peerRelay.LocalIndex,\n\t\t\tInitiatorRelayIndex: peerRelay.RemoteIndex,\n\t\t}\n\n\t\tif v == cert.Version1 {\n\t\t\tpeer := peerHostInfo.vpnAddrs[0]\n\t\t\tif !peer.Is4() {\n\t\t\t\trm.l.WithField(\"relayFrom\", peer).\n\t\t\t\t\tWithField(\"relayTo\", target).\n\t\t\t\t\tWithField(\"initiatorRelayIndex\", resp.InitiatorRelayIndex).\n\t\t\t\t\tWithField(\"responderRelayIndex\", resp.ResponderRelayIndex).\n\t\t\t\t\tWithField(\"vpnAddrs\", peerHostInfo.vpnAddrs).\n\t\t\t\t\tError(\"Refusing to CreateRelayResponse for a v1 relay with an ipv6 address\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tb := peer.As4()\n\t\t\tresp.OldRelayFromAddr = binary.BigEndian.Uint32(b[:])\n\t\t\tb = targetAddr.As4()\n\t\t\tresp.OldRelayToAddr = binary.BigEndian.Uint32(b[:])\n\t\t} else {\n\t\t\tresp.RelayFromAddr = netAddrToProtoAddr(peerHostInfo.vpnAddrs[0])\n\t\t\tresp.RelayToAddr = target\n\t\t}\n\n\t\tmsg, err := resp.Marshal()\n\t\tif err != nil {\n\t\t\trm.l.WithError(err).\n\t\t\t\tError(\"relayManager Failed to marshal Control CreateRelayResponse message to create relay\")\n\t\t} else {\n\t\t\tf.SendMessageToHostInfo(header.Control, 0, peerHostInfo, msg, make([]byte, 12), make([]byte, mtu))\n\t\t\trm.l.WithFields(logrus.Fields{\n\t\t\t\t\"relayFrom\":           resp.RelayFromAddr,\n\t\t\t\t\"relayTo\":             resp.RelayToAddr,\n\t\t\t\t\"initiatorRelayIndex\": resp.InitiatorRelayIndex,\n\t\t\t\t\"responderRelayIndex\": resp.ResponderRelayIndex,\n\t\t\t\t\"vpnAddrs\":            peerHostInfo.vpnAddrs}).\n\t\t\t\tInfo(\"send CreateRelayResponse\")\n\t\t}\n\t}\n}\n\nfunc (rm *relayManager) handleCreateRelayRequest(v cert.Version, h *HostInfo, f *Interface, m *NebulaControl) {\n\tfrom := protoAddrToNetAddr(m.RelayFromAddr)\n\ttarget := protoAddrToNetAddr(m.RelayToAddr)\n\n\tlogMsg := rm.l.WithFields(logrus.Fields{\n\t\t\"relayFrom\":           from,\n\t\t\"relayTo\":             target,\n\t\t\"initiatorRelayIndex\": m.InitiatorRelayIndex,\n\t\t\"vpnAddrs\":            h.vpnAddrs})\n\n\tlogMsg.Info(\"handleCreateRelayRequest\")\n\t// Is the source of the relay me? This should never happen, but did happen due to\n\t// an issue migrating relays over to newly re-handshaked host info objects.\n\tif f.myVpnAddrsTable.Contains(from) {\n\t\tlogMsg.WithField(\"myIP\", from).Error(\"Discarding relay request from myself\")\n\t\treturn\n\t}\n\n\t// Is the target of the relay me?\n\tif f.myVpnAddrsTable.Contains(target) {\n\t\texistingRelay, ok := h.relayState.QueryRelayForByIp(from)\n\t\tif ok {\n\t\t\tswitch existingRelay.State {\n\t\t\tcase Requested:\n\t\t\t\tok = h.relayState.CompleteRelayByIP(from, m.InitiatorRelayIndex)\n\t\t\t\tif !ok {\n\t\t\t\t\tlogMsg.Error(\"Relay State not found\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\tcase Established:\n\t\t\t\tif existingRelay.RemoteIndex != m.InitiatorRelayIndex {\n\t\t\t\t\t// We got a brand new Relay request, because its index is different than what we saw before.\n\t\t\t\t\t// This should never happen. The peer should never change an index, once created.\n\t\t\t\t\tlogMsg.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"existingRemoteIndex\": existingRelay.RemoteIndex}).Error(\"Existing relay mismatch with CreateRelayRequest\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\tcase Disestablished:\n\t\t\t\tif existingRelay.RemoteIndex != m.InitiatorRelayIndex {\n\t\t\t\t\t// We got a brand new Relay request, because its index is different than what we saw before.\n\t\t\t\t\t// This should never happen. The peer should never change an index, once created.\n\t\t\t\t\tlogMsg.WithFields(logrus.Fields{\n\t\t\t\t\t\t\"existingRemoteIndex\": existingRelay.RemoteIndex}).Error(\"Existing relay mismatch with CreateRelayRequest\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// Mark the relay as 'Established' because it's safe to use again\n\t\t\t\th.relayState.UpdateRelayForByIpState(from, Established)\n\t\t\tcase PeerRequested:\n\t\t\t\t// I should never be in this state, because I am terminal, not forwarding.\n\t\t\t\tlogMsg.WithFields(logrus.Fields{\n\t\t\t\t\t\"existingRemoteIndex\": existingRelay.RemoteIndex,\n\t\t\t\t\t\"state\":               existingRelay.State}).Error(\"Unexpected Relay State found\")\n\t\t\t}\n\t\t} else {\n\t\t\t_, err := AddRelay(rm.l, h, f.hostMap, from, &m.InitiatorRelayIndex, TerminalType, Established)\n\t\t\tif err != nil {\n\t\t\t\tlogMsg.WithError(err).Error(\"Failed to add relay\")\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\trelay, ok := h.relayState.QueryRelayForByIp(from)\n\t\tif !ok {\n\t\t\tlogMsg.WithField(\"from\", from).Error(\"Relay State not found\")\n\t\t\treturn\n\t\t}\n\n\t\tresp := NebulaControl{\n\t\t\tType:                NebulaControl_CreateRelayResponse,\n\t\t\tResponderRelayIndex: relay.LocalIndex,\n\t\t\tInitiatorRelayIndex: relay.RemoteIndex,\n\t\t}\n\n\t\tif v == cert.Version1 {\n\t\t\tb := from.As4()\n\t\t\tresp.OldRelayFromAddr = binary.BigEndian.Uint32(b[:])\n\t\t\tb = target.As4()\n\t\t\tresp.OldRelayToAddr = binary.BigEndian.Uint32(b[:])\n\t\t} else {\n\t\t\tresp.RelayFromAddr = netAddrToProtoAddr(from)\n\t\t\tresp.RelayToAddr = netAddrToProtoAddr(target)\n\t\t}\n\n\t\tmsg, err := resp.Marshal()\n\t\tif err != nil {\n\t\t\tlogMsg.\n\t\t\t\tWithError(err).Error(\"relayManager Failed to marshal Control CreateRelayResponse message to create relay\")\n\t\t} else {\n\t\t\tf.SendMessageToHostInfo(header.Control, 0, h, msg, make([]byte, 12), make([]byte, mtu))\n\t\t\trm.l.WithFields(logrus.Fields{\n\t\t\t\t\"relayFrom\":           from,\n\t\t\t\t\"relayTo\":             target,\n\t\t\t\t\"initiatorRelayIndex\": resp.InitiatorRelayIndex,\n\t\t\t\t\"responderRelayIndex\": resp.ResponderRelayIndex,\n\t\t\t\t\"vpnAddrs\":            h.vpnAddrs}).\n\t\t\t\tInfo(\"send CreateRelayResponse\")\n\t\t}\n\t\treturn\n\t} else {\n\t\t// the target is not me. Create a relay to the target, from me.\n\t\tif !rm.GetAmRelay() {\n\t\t\treturn\n\t\t}\n\t\tpeer := rm.hostmap.QueryVpnAddr(target)\n\t\tif peer == nil {\n\t\t\t// Try to establish a connection to this host. If we get a future relay request,\n\t\t\t// we'll be ready!\n\t\t\tf.Handshake(target)\n\t\t\treturn\n\t\t}\n\t\tif !peer.remote.IsValid() {\n\t\t\t// Only create relays to peers for whom I have a direct connection\n\t\t\treturn\n\t\t}\n\t\tvar index uint32\n\t\tvar err error\n\t\ttargetRelay, ok := peer.relayState.QueryRelayForByIp(from)\n\t\tif ok {\n\t\t\tindex = targetRelay.LocalIndex\n\t\t} else {\n\t\t\t// Allocate an index in the hostMap for this relay peer\n\t\t\tindex, err = AddRelay(rm.l, peer, f.hostMap, from, nil, ForwardingType, Requested)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tpeer.relayState.UpdateRelayForByIpState(from, Requested)\n\t\t// Send a CreateRelayRequest to the peer.\n\t\treq := NebulaControl{\n\t\t\tType:                NebulaControl_CreateRelayRequest,\n\t\t\tInitiatorRelayIndex: index,\n\t\t}\n\n\t\tif v == cert.Version1 {\n\t\t\tif !h.vpnAddrs[0].Is4() {\n\t\t\t\trm.l.WithField(\"relayFrom\", h.vpnAddrs[0]).\n\t\t\t\t\tWithField(\"relayTo\", target).\n\t\t\t\t\tWithField(\"initiatorRelayIndex\", req.InitiatorRelayIndex).\n\t\t\t\t\tWithField(\"responderRelayIndex\", req.ResponderRelayIndex).\n\t\t\t\t\tWithField(\"vpnAddr\", target).\n\t\t\t\t\tError(\"Refusing to CreateRelayRequest for a v1 relay with an ipv6 address\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tb := h.vpnAddrs[0].As4()\n\t\t\treq.OldRelayFromAddr = binary.BigEndian.Uint32(b[:])\n\t\t\tb = target.As4()\n\t\t\treq.OldRelayToAddr = binary.BigEndian.Uint32(b[:])\n\t\t} else {\n\t\t\treq.RelayFromAddr = netAddrToProtoAddr(h.vpnAddrs[0])\n\t\t\treq.RelayToAddr = netAddrToProtoAddr(target)\n\t\t}\n\n\t\tmsg, err := req.Marshal()\n\t\tif err != nil {\n\t\t\tlogMsg.\n\t\t\t\tWithError(err).Error(\"relayManager Failed to marshal Control message to create relay\")\n\t\t} else {\n\t\t\tf.SendMessageToHostInfo(header.Control, 0, peer, msg, make([]byte, 12), make([]byte, mtu))\n\t\t\trm.l.WithFields(logrus.Fields{\n\t\t\t\t\"relayFrom\":           h.vpnAddrs[0],\n\t\t\t\t\"relayTo\":             target,\n\t\t\t\t\"initiatorRelayIndex\": req.InitiatorRelayIndex,\n\t\t\t\t\"responderRelayIndex\": req.ResponderRelayIndex,\n\t\t\t\t\"vpnAddr\":             target}).\n\t\t\t\tInfo(\"send CreateRelayRequest\")\n\t\t}\n\n\t\t// Also track the half-created Relay state just received\n\t\t_, ok = h.relayState.QueryRelayForByIp(target)\n\t\tif !ok {\n\t\t\t_, err := AddRelay(rm.l, h, f.hostMap, target, &m.InitiatorRelayIndex, ForwardingType, PeerRequested)\n\t\t\tif err != nil {\n\t\t\t\tlogMsg.\n\t\t\t\t\tWithError(err).Error(\"relayManager Failed to allocate a local index for relay\")\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "remote_list.go",
    "content": "package nebula\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/netip\"\n\t\"slices\"\n\t\"sort\"\n\t\"strconv\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/sirupsen/logrus\"\n)\n\n// forEachFunc is used to benefit folks that want to do work inside the lock\ntype forEachFunc func(addr netip.AddrPort, preferred bool)\n\n// The checkFuncs here are to simplify bulk importing LH query response logic into a single function (reset slice and iterate)\ntype checkFuncV4 func(vpnIp netip.Addr, to *V4AddrPort) bool\ntype checkFuncV6 func(vpnIp netip.Addr, to *V6AddrPort) bool\n\n// CacheMap is a struct that better represents the lighthouse cache for humans\n// The string key is the owners vpnIp\ntype CacheMap map[string]*Cache\n\n// Cache is the other part of CacheMap to better represent the lighthouse cache for humans\n// We don't reason about ipv4 vs ipv6 here\ntype Cache struct {\n\tLearned  []netip.AddrPort `json:\"learned,omitempty\"`\n\tReported []netip.AddrPort `json:\"reported,omitempty\"`\n\tRelay    []netip.Addr     `json:\"relay\"`\n}\n\n// cache is an internal struct that splits v4 and v6 addresses inside the cache map\ntype cache struct {\n\tv4    *cacheV4\n\tv6    *cacheV6\n\trelay *cacheRelay\n}\n\ntype cacheRelay struct {\n\trelay []netip.Addr\n}\n\n// cacheV4 stores learned and reported ipv4 records under cache\ntype cacheV4 struct {\n\tlearned  *V4AddrPort\n\treported []*V4AddrPort\n}\n\n// cacheV4 stores learned and reported ipv6 records under cache\ntype cacheV6 struct {\n\tlearned  *V6AddrPort\n\treported []*V6AddrPort\n}\n\ntype hostnamePort struct {\n\tname string\n\tport uint16\n}\n\ntype hostnamesResults struct {\n\thostnames     []hostnamePort\n\tnetwork       string\n\tlookupTimeout time.Duration\n\tcancelFn      func()\n\tl             *logrus.Logger\n\tips           atomic.Pointer[map[netip.AddrPort]struct{}]\n}\n\nfunc NewHostnameResults(ctx context.Context, l *logrus.Logger, d time.Duration, network string, timeout time.Duration, hostPorts []string, onUpdate func()) (*hostnamesResults, error) {\n\tr := &hostnamesResults{\n\t\thostnames:     make([]hostnamePort, len(hostPorts)),\n\t\tnetwork:       network,\n\t\tlookupTimeout: timeout,\n\t\tl:             l,\n\t}\n\n\t// Fastrack IP addresses to ensure they're immediately available for use.\n\t// DNS lookups for hostnames that aren't hardcoded IP's will happen in a background goroutine.\n\tperformBackgroundLookup := false\n\tips := map[netip.AddrPort]struct{}{}\n\tfor idx, hostPort := range hostPorts {\n\n\t\trIp, sPort, err := net.SplitHostPort(hostPort)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tiPort, err := strconv.Atoi(sPort)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tr.hostnames[idx] = hostnamePort{name: rIp, port: uint16(iPort)}\n\t\taddr, err := netip.ParseAddr(rIp)\n\t\tif err != nil {\n\t\t\t// This address is a hostname, not an IP address\n\t\t\tperformBackgroundLookup = true\n\t\t\tcontinue\n\t\t}\n\n\t\t// Save the IP address immediately\n\t\tips[netip.AddrPortFrom(addr, uint16(iPort))] = struct{}{}\n\t}\n\tr.ips.Store(&ips)\n\n\t// Time for the DNS lookup goroutine\n\tif performBackgroundLookup {\n\t\tnewCtx, cancel := context.WithCancel(ctx)\n\t\tr.cancelFn = cancel\n\t\tticker := time.NewTicker(d)\n\t\tgo func() {\n\t\t\tdefer ticker.Stop()\n\t\t\tfor {\n\t\t\t\tnetipAddrs := map[netip.AddrPort]struct{}{}\n\t\t\t\tfor _, hostPort := range r.hostnames {\n\t\t\t\t\ttimeoutCtx, timeoutCancel := context.WithTimeout(ctx, r.lookupTimeout)\n\t\t\t\t\taddrs, err := net.DefaultResolver.LookupNetIP(timeoutCtx, r.network, hostPort.name)\n\t\t\t\t\ttimeoutCancel()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tl.WithFields(logrus.Fields{\"hostname\": hostPort.name, \"network\": r.network}).WithError(err).Error(\"DNS resolution failed for static_map host\")\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tfor _, a := range addrs {\n\t\t\t\t\t\tnetipAddrs[netip.AddrPortFrom(a.Unmap(), hostPort.port)] = struct{}{}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\torigSet := r.ips.Load()\n\t\t\t\tdifferent := false\n\t\t\t\tfor a := range *origSet {\n\t\t\t\t\tif _, ok := netipAddrs[a]; !ok {\n\t\t\t\t\t\tdifferent = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif !different {\n\t\t\t\t\tfor a := range netipAddrs {\n\t\t\t\t\t\tif _, ok := (*origSet)[a]; !ok {\n\t\t\t\t\t\t\tdifferent = true\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif different {\n\t\t\t\t\tl.WithFields(logrus.Fields{\"origSet\": origSet, \"newSet\": netipAddrs}).Info(\"DNS results changed for host list\")\n\t\t\t\t\tr.ips.Store(&netipAddrs)\n\t\t\t\t\tonUpdate()\n\t\t\t\t}\n\t\t\t\tselect {\n\t\t\t\tcase <-newCtx.Done():\n\t\t\t\t\treturn\n\t\t\t\tcase <-ticker.C:\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t}\n\n\treturn r, nil\n}\n\nfunc (hr *hostnamesResults) Cancel() {\n\tif hr != nil && hr.cancelFn != nil {\n\t\thr.cancelFn()\n\t}\n}\n\nfunc (hr *hostnamesResults) GetAddrs() []netip.AddrPort {\n\tvar retSlice []netip.AddrPort\n\tif hr != nil {\n\t\tp := hr.ips.Load()\n\t\tif p != nil {\n\t\t\tfor k := range *p {\n\t\t\t\tretSlice = append(retSlice, k)\n\t\t\t}\n\t\t}\n\t}\n\treturn retSlice\n}\n\n// RemoteList is a unifying concept for lighthouse servers and clients as well as hostinfos.\n// It serves as a local cache of query replies, host update notifications, and locally learned addresses\ntype RemoteList struct {\n\t// Every interaction with internals requires a lock!\n\tsync.RWMutex\n\n\t// The full list of vpn addresses assigned to this host\n\tvpnAddrs []netip.Addr\n\n\t// A deduplicated set of underlay addresses. Any accessor should lock beforehand.\n\taddrs []netip.AddrPort\n\n\t// A set of relay addresses. VpnIp addresses that the remote identified as relays.\n\trelays []netip.Addr\n\n\t// These are maps to store v4 and v6 addresses per lighthouse\n\t// Map key is the vpnIp of the person that told us about this the cached entries underneath.\n\t// For learned addresses, this is the vpnIp that sent the packet\n\tcache map[netip.Addr]*cache\n\n\thr *hostnamesResults\n\n\t// shouldAdd is a nillable function that decides if x should be added to addrs.\n\tshouldAdd func(vpnAddrs []netip.Addr, x netip.Addr) bool\n\n\t// This is a list of remotes that we have tried to handshake with and have returned from the wrong vpn ip.\n\t// They should not be tried again during a handshake\n\tbadRemotes []netip.AddrPort\n\n\t// A flag that the cache may have changed and addrs needs to be rebuilt\n\tshouldRebuild bool\n}\n\n// NewRemoteList creates a new empty RemoteList\nfunc NewRemoteList(vpnAddrs []netip.Addr, shouldAdd func([]netip.Addr, netip.Addr) bool) *RemoteList {\n\tr := &RemoteList{\n\t\tvpnAddrs:  make([]netip.Addr, len(vpnAddrs)),\n\t\taddrs:     make([]netip.AddrPort, 0),\n\t\trelays:    make([]netip.Addr, 0),\n\t\tcache:     make(map[netip.Addr]*cache),\n\t\tshouldAdd: shouldAdd,\n\t}\n\tcopy(r.vpnAddrs, vpnAddrs)\n\treturn r\n}\n\nfunc (r *RemoteList) unlockedSetHostnamesResults(hr *hostnamesResults) {\n\t// Cancel any existing hostnamesResults DNS goroutine to release resources\n\tr.hr.Cancel()\n\tr.hr = hr\n}\n\n// Len locks and reports the size of the deduplicated address list\n// The deduplication work may need to occur here, so you must pass preferredRanges\nfunc (r *RemoteList) Len(preferredRanges []netip.Prefix) int {\n\tr.Rebuild(preferredRanges)\n\tr.RLock()\n\tdefer r.RUnlock()\n\treturn len(r.addrs)\n}\n\n// ForEach locks and will call the forEachFunc for every deduplicated address in the list\n// The deduplication work may need to occur here, so you must pass preferredRanges\nfunc (r *RemoteList) ForEach(preferredRanges []netip.Prefix, forEach forEachFunc) {\n\tr.Rebuild(preferredRanges)\n\tr.RLock()\n\tfor _, v := range r.addrs {\n\t\tforEach(v, isPreferred(v.Addr(), preferredRanges))\n\t}\n\tr.RUnlock()\n}\n\n// CopyAddrs locks and makes a deep copy of the deduplicated address list\n// The deduplication work may need to occur here, so you must pass preferredRanges\nfunc (r *RemoteList) CopyAddrs(preferredRanges []netip.Prefix) []netip.AddrPort {\n\tif r == nil {\n\t\treturn nil\n\t}\n\n\tr.Rebuild(preferredRanges)\n\n\tr.RLock()\n\tdefer r.RUnlock()\n\tc := make([]netip.AddrPort, len(r.addrs))\n\tfor i, v := range r.addrs {\n\t\tc[i] = v\n\t}\n\treturn c\n}\n\n// LearnRemote locks and sets the learned slot for the owner vpn ip to the provided addr\n// Currently this is only needed when HostInfo.SetRemote is called as that should cover both handshaking and roaming.\n// It will mark the deduplicated address list as dirty, so do not call it unless new information is available\nfunc (r *RemoteList) LearnRemote(ownerVpnIp netip.Addr, remote netip.AddrPort) {\n\tr.Lock()\n\tdefer r.Unlock()\n\tif remote.Addr().Is4() {\n\t\tr.unlockedSetLearnedV4(ownerVpnIp, netAddrToProtoV4AddrPort(remote.Addr(), remote.Port()))\n\t} else {\n\t\tr.unlockedSetLearnedV6(ownerVpnIp, netAddrToProtoV6AddrPort(remote.Addr(), remote.Port()))\n\t}\n}\n\n// CopyCache locks and creates a more human friendly form of the internal address cache.\n// This may contain duplicates and blocked addresses\nfunc (r *RemoteList) CopyCache() *CacheMap {\n\tr.RLock()\n\tdefer r.RUnlock()\n\n\tcm := make(CacheMap)\n\tgetOrMake := func(vpnIp string) *Cache {\n\t\tc := cm[vpnIp]\n\t\tif c == nil {\n\t\t\tc = &Cache{\n\t\t\t\tLearned:  make([]netip.AddrPort, 0),\n\t\t\t\tReported: make([]netip.AddrPort, 0),\n\t\t\t\tRelay:    make([]netip.Addr, 0),\n\t\t\t}\n\t\t\tcm[vpnIp] = c\n\t\t}\n\t\treturn c\n\t}\n\n\tfor owner, mc := range r.cache {\n\t\tc := getOrMake(owner.String())\n\n\t\tif mc.v4 != nil {\n\t\t\tif mc.v4.learned != nil {\n\t\t\t\tc.Learned = append(c.Learned, protoV4AddrPortToNetAddrPort(mc.v4.learned))\n\t\t\t}\n\n\t\t\tfor _, a := range mc.v4.reported {\n\t\t\t\tc.Reported = append(c.Reported, protoV4AddrPortToNetAddrPort(a))\n\t\t\t}\n\t\t}\n\n\t\tif mc.v6 != nil {\n\t\t\tif mc.v6.learned != nil {\n\t\t\t\tc.Learned = append(c.Learned, protoV6AddrPortToNetAddrPort(mc.v6.learned))\n\t\t\t}\n\n\t\t\tfor _, a := range mc.v6.reported {\n\t\t\t\tc.Reported = append(c.Reported, protoV6AddrPortToNetAddrPort(a))\n\t\t\t}\n\t\t}\n\n\t\tif mc.relay != nil {\n\t\t\tfor _, a := range mc.relay.relay {\n\t\t\t\tc.Relay = append(c.Relay, a)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn &cm\n}\n\n// BlockRemote locks and records the address as bad, it will be excluded from the deduplicated address list\nfunc (r *RemoteList) BlockRemote(bad ViaSender) {\n\tif bad.IsRelayed {\n\t\treturn\n\t}\n\n\tr.Lock()\n\tdefer r.Unlock()\n\n\t// Check if we already blocked this addr\n\tif r.unlockedIsBad(bad.UdpAddr) {\n\t\treturn\n\t}\n\n\t// We copy here because we are taking something else's memory and we can't trust everything\n\tr.badRemotes = append(r.badRemotes, bad.UdpAddr)\n\n\t// Mark the next interaction must recollect/dedupe\n\tr.shouldRebuild = true\n}\n\n// CopyBlockedRemotes locks and makes a deep copy of the blocked remotes list\nfunc (r *RemoteList) CopyBlockedRemotes() []netip.AddrPort {\n\tr.RLock()\n\tdefer r.RUnlock()\n\n\tc := make([]netip.AddrPort, len(r.badRemotes))\n\tfor i, v := range r.badRemotes {\n\t\tc[i] = v\n\t}\n\treturn c\n}\n\n// RefreshFromHandshake locks and updates the RemoteList to account for data learned upon a completed handshake\nfunc (r *RemoteList) RefreshFromHandshake(vpnAddrs []netip.Addr) {\n\tr.Lock()\n\tr.badRemotes = nil\n\tr.vpnAddrs = make([]netip.Addr, len(vpnAddrs))\n\tcopy(r.vpnAddrs, vpnAddrs)\n\tr.Unlock()\n}\n\n// ResetBlockedRemotes locks and clears the blocked remotes list\nfunc (r *RemoteList) ResetBlockedRemotes() {\n\tr.Lock()\n\tr.badRemotes = nil\n\tr.Unlock()\n}\n\n// Rebuild locks and generates the deduplicated address list only if there is work to be done\n// There is generally no reason to call this directly but it is safe to do so\nfunc (r *RemoteList) Rebuild(preferredRanges []netip.Prefix) {\n\tr.Lock()\n\tdefer r.Unlock()\n\n\t// Only rebuild if the cache changed\n\tif r.shouldRebuild {\n\t\tr.unlockedCollect()\n\t\tr.shouldRebuild = false\n\t}\n\n\t// Always re-sort, preferredRanges can change via HUP\n\tr.unlockedSort(preferredRanges)\n}\n\n// unlockedIsBad assumes you have the write lock and checks if the remote matches any entry in the blocked address list\nfunc (r *RemoteList) unlockedIsBad(remote netip.AddrPort) bool {\n\treturn slices.Contains(r.badRemotes, remote)\n}\n\n// unlockedSetLearnedV4 assumes you have the write lock and sets the current learned address for this owner and marks the\n// deduplicated address list as dirty\nfunc (r *RemoteList) unlockedSetLearnedV4(ownerVpnIp netip.Addr, to *V4AddrPort) {\n\tr.shouldRebuild = true\n\tr.unlockedGetOrMakeV4(ownerVpnIp).learned = to\n}\n\n// unlockedSetV4 assumes you have the write lock and resets the reported list of ips for this owner to the list provided\n// and marks the deduplicated address list as dirty\nfunc (r *RemoteList) unlockedSetV4(ownerVpnIp, vpnIp netip.Addr, to []*V4AddrPort, check checkFuncV4) {\n\tr.shouldRebuild = true\n\tc := r.unlockedGetOrMakeV4(ownerVpnIp)\n\n\t// Reset the slice\n\tc.reported = c.reported[:0]\n\n\t// We can't take their array but we can take their pointers\n\tfor _, v := range to[:minInt(len(to), MaxRemotes)] {\n\t\tif check(vpnIp, v) {\n\t\t\tc.reported = append(c.reported, v)\n\t\t}\n\t}\n}\n\nfunc (r *RemoteList) unlockedSetRelay(ownerVpnIp netip.Addr, to []netip.Addr) {\n\tr.shouldRebuild = true\n\tc := r.unlockedGetOrMakeRelay(ownerVpnIp)\n\n\t// Reset the slice\n\tc.relay = c.relay[:0]\n\n\t// We can't take their array but we can take their pointers\n\tc.relay = append(c.relay, to[:minInt(len(to), MaxRemotes)]...)\n}\n\n// unlockedPrependV4 assumes you have the write lock and prepends the address in the reported list for this owner\n// This is only useful for establishing static hosts\nfunc (r *RemoteList) unlockedPrependV4(ownerVpnIp netip.Addr, to *V4AddrPort) {\n\tr.shouldRebuild = true\n\tc := r.unlockedGetOrMakeV4(ownerVpnIp)\n\n\t// We are doing the easy append because this is rarely called\n\tc.reported = append([]*V4AddrPort{to}, c.reported...)\n\tif len(c.reported) > MaxRemotes {\n\t\tc.reported = c.reported[:MaxRemotes]\n\t}\n}\n\n// unlockedSetLearnedV6 assumes you have the write lock and sets the current learned address for this owner and marks the\n// deduplicated address list as dirty\nfunc (r *RemoteList) unlockedSetLearnedV6(ownerVpnIp netip.Addr, to *V6AddrPort) {\n\tr.shouldRebuild = true\n\tr.unlockedGetOrMakeV6(ownerVpnIp).learned = to\n}\n\n// unlockedSetV6 assumes you have the write lock and resets the reported list of ips for this owner to the list provided\n// and marks the deduplicated address list as dirty\nfunc (r *RemoteList) unlockedSetV6(ownerVpnIp, vpnIp netip.Addr, to []*V6AddrPort, check checkFuncV6) {\n\tr.shouldRebuild = true\n\tc := r.unlockedGetOrMakeV6(ownerVpnIp)\n\n\t// Reset the slice\n\tc.reported = c.reported[:0]\n\n\t// We can't take their array but we can take their pointers\n\tfor _, v := range to[:minInt(len(to), MaxRemotes)] {\n\t\tif check(vpnIp, v) {\n\t\t\tc.reported = append(c.reported, v)\n\t\t}\n\t}\n}\n\n// unlockedPrependV6 assumes you have the write lock and prepends the address in the reported list for this owner\n// This is only useful for establishing static hosts\nfunc (r *RemoteList) unlockedPrependV6(ownerVpnIp netip.Addr, to *V6AddrPort) {\n\tr.shouldRebuild = true\n\tc := r.unlockedGetOrMakeV6(ownerVpnIp)\n\n\t// We are doing the easy append because this is rarely called\n\tc.reported = append([]*V6AddrPort{to}, c.reported...)\n\tif len(c.reported) > MaxRemotes {\n\t\tc.reported = c.reported[:MaxRemotes]\n\t}\n}\n\nfunc (r *RemoteList) unlockedGetOrMakeRelay(ownerVpnIp netip.Addr) *cacheRelay {\n\tam := r.cache[ownerVpnIp]\n\tif am == nil {\n\t\tam = &cache{}\n\t\tr.cache[ownerVpnIp] = am\n\t}\n\t// Avoid occupying memory for relay if we never have any\n\tif am.relay == nil {\n\t\tam.relay = &cacheRelay{}\n\t}\n\treturn am.relay\n}\n\n// unlockedGetOrMakeV4 assumes you have the write lock and builds the cache and owner entry. Only the v4 pointer is established.\n// The caller must dirty the learned address cache if required\nfunc (r *RemoteList) unlockedGetOrMakeV4(ownerVpnIp netip.Addr) *cacheV4 {\n\tam := r.cache[ownerVpnIp]\n\tif am == nil {\n\t\tam = &cache{}\n\t\tr.cache[ownerVpnIp] = am\n\t}\n\t// Avoid occupying memory for v6 addresses if we never have any\n\tif am.v4 == nil {\n\t\tam.v4 = &cacheV4{}\n\t}\n\treturn am.v4\n}\n\n// unlockedGetOrMakeV6 assumes you have the write lock and builds the cache and owner entry. Only the v6 pointer is established.\n// The caller must dirty the learned address cache if required\nfunc (r *RemoteList) unlockedGetOrMakeV6(ownerVpnIp netip.Addr) *cacheV6 {\n\tam := r.cache[ownerVpnIp]\n\tif am == nil {\n\t\tam = &cache{}\n\t\tr.cache[ownerVpnIp] = am\n\t}\n\t// Avoid occupying memory for v4 addresses if we never have any\n\tif am.v6 == nil {\n\t\tam.v6 = &cacheV6{}\n\t}\n\treturn am.v6\n}\n\n// unlockedCollect assumes you have the write lock and collects/transforms the cache into the deduped address list.\n// The result of this function can contain duplicates. unlockedSort handles cleaning it.\nfunc (r *RemoteList) unlockedCollect() {\n\taddrs := r.addrs[:0]\n\trelays := r.relays[:0]\n\n\tfor _, c := range r.cache {\n\t\tif c.v4 != nil {\n\t\t\tif c.v4.learned != nil {\n\t\t\t\tu := protoV4AddrPortToNetAddrPort(c.v4.learned)\n\t\t\t\tif !r.unlockedIsBad(u) {\n\t\t\t\t\taddrs = append(addrs, u)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor _, v := range c.v4.reported {\n\t\t\t\tu := protoV4AddrPortToNetAddrPort(v)\n\t\t\t\tif !r.unlockedIsBad(u) {\n\t\t\t\t\taddrs = append(addrs, u)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif c.v6 != nil {\n\t\t\tif c.v6.learned != nil {\n\t\t\t\tu := protoV6AddrPortToNetAddrPort(c.v6.learned)\n\t\t\t\tif !r.unlockedIsBad(u) {\n\t\t\t\t\taddrs = append(addrs, u)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor _, v := range c.v6.reported {\n\t\t\t\tu := protoV6AddrPortToNetAddrPort(v)\n\t\t\t\tif !r.unlockedIsBad(u) {\n\t\t\t\t\taddrs = append(addrs, u)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif c.relay != nil {\n\t\t\tfor _, v := range c.relay.relay {\n\t\t\t\trelays = append(relays, v)\n\t\t\t}\n\t\t}\n\t}\n\n\tdnsAddrs := r.hr.GetAddrs()\n\tfor _, addr := range dnsAddrs {\n\t\tif r.shouldAdd == nil || r.shouldAdd(r.vpnAddrs, addr.Addr()) {\n\t\t\tif !r.unlockedIsBad(addr) {\n\t\t\t\taddrs = append(addrs, addr)\n\t\t\t}\n\t\t}\n\t}\n\n\tr.addrs = addrs\n\tr.relays = relays\n\n}\n\n// unlockedSort assumes you have the write lock and performs the deduping and sorting of the address list\nfunc (r *RemoteList) unlockedSort(preferredRanges []netip.Prefix) {\n\t// Use a map to deduplicate any relay addresses\n\tdedupedRelays := map[netip.Addr]struct{}{}\n\tfor _, relay := range r.relays {\n\t\tdedupedRelays[relay] = struct{}{}\n\t}\n\tr.relays = r.relays[:0]\n\tfor relay := range dedupedRelays {\n\t\tr.relays = append(r.relays, relay)\n\t}\n\t// Put them in a somewhat consistent order after de-duplication\n\tslices.SortFunc(r.relays, func(a, b netip.Addr) int {\n\t\treturn a.Compare(b)\n\t})\n\n\t// Now the addrs\n\tn := len(r.addrs)\n\tif n < 2 {\n\t\treturn\n\t}\n\n\tlessFunc := func(i, j int) bool {\n\t\ta := r.addrs[i]\n\t\tb := r.addrs[j]\n\t\t// Preferred addresses first\n\n\t\taPref := isPreferred(a.Addr(), preferredRanges)\n\t\tbPref := isPreferred(b.Addr(), preferredRanges)\n\t\tswitch {\n\t\tcase aPref && !bPref:\n\t\t\t// If i is preferred and j is not, i is less than j\n\t\t\treturn true\n\n\t\tcase !aPref && bPref:\n\t\t\t// If j is preferred then i is not due to the else, i is not less than j\n\t\t\treturn false\n\n\t\tdefault:\n\t\t\t// Both i an j are either preferred or not, sort within that\n\t\t}\n\n\t\t// ipv6 addresses 2nd\n\t\ta4 := a.Addr().Is4()\n\t\tb4 := b.Addr().Is4()\n\t\tswitch {\n\t\tcase a4 == false && b4 == true:\n\t\t\t// If i is v6 and j is v4, i is less than j\n\t\t\treturn true\n\n\t\tcase a4 == true && b4 == false:\n\t\t\t// If j is v6 and i is v4, i is not less than j\n\t\t\treturn false\n\n\t\tcase a4 == true && b4 == true:\n\t\t\t// i and j are both ipv4\n\t\t\taPrivate := a.Addr().IsPrivate()\n\t\t\tbPrivate := b.Addr().IsPrivate()\n\t\t\tswitch {\n\t\t\tcase !aPrivate && bPrivate:\n\t\t\t\t// If i is a public ip (not private) and j is a private ip, i is less then j\n\t\t\t\treturn true\n\n\t\t\tcase aPrivate && !bPrivate:\n\t\t\t\t// If j is public (not private) then i is private due to the else, i is not less than j\n\t\t\t\treturn false\n\n\t\t\tdefault:\n\t\t\t\t// Both i an j are either public or private, sort within that\n\t\t\t}\n\n\t\tdefault:\n\t\t\t// Both i an j are either ipv4 or ipv6, sort within that\n\t\t}\n\n\t\t// lexical order of ips 3rd\n\t\tc := a.Addr().Compare(b.Addr())\n\t\tif c == 0 {\n\t\t\t// Ips are the same, Lexical order of ports 4th\n\t\t\treturn a.Port() < b.Port()\n\t\t}\n\n\t\t// Ip wasn't the same\n\t\treturn c < 0\n\t}\n\n\t// Sort it\n\tsort.Slice(r.addrs, lessFunc)\n\n\t// Deduplicate\n\ta, b := 0, 1\n\tfor b < n {\n\t\tif r.addrs[a] != r.addrs[b] {\n\t\t\ta++\n\t\t\tif a != b {\n\t\t\t\tr.addrs[a], r.addrs[b] = r.addrs[b], r.addrs[a]\n\t\t\t}\n\t\t}\n\t\tb++\n\t}\n\n\tr.addrs = r.addrs[:a+1]\n\treturn\n}\n\n// minInt returns the minimum integer of a or b\nfunc minInt(a, b int) int {\n\tif a < b {\n\t\treturn a\n\t}\n\treturn b\n}\n\n// isPreferred returns true of the ip is contained in the preferredRanges list\nfunc isPreferred(ip netip.Addr, preferredRanges []netip.Prefix) bool {\n\tfor _, p := range preferredRanges {\n\t\tif p.Contains(ip) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "remote_list_test.go",
    "content": "package nebula\n\nimport (\n\t\"encoding/binary\"\n\t\"net/netip\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestRemoteList_Rebuild(t *testing.T) {\n\trl := NewRemoteList([]netip.Addr{netip.MustParseAddr(\"0.0.0.0\")}, nil)\n\trl.unlockedSetV4(\n\t\tnetip.MustParseAddr(\"0.0.0.0\"),\n\t\tnetip.MustParseAddr(\"0.0.0.0\"),\n\t\t[]*V4AddrPort{\n\t\t\tnewIp4AndPortFromString(\"70.199.182.92:1475\"), // this is duped\n\t\t\tnewIp4AndPortFromString(\"172.17.0.182:10101\"),\n\t\t\tnewIp4AndPortFromString(\"172.17.1.1:10101\"), // this is duped\n\t\t\tnewIp4AndPortFromString(\"172.18.0.1:10101\"), // this is duped\n\t\t\tnewIp4AndPortFromString(\"172.18.0.1:10101\"), // this is a dupe\n\t\t\tnewIp4AndPortFromString(\"172.19.0.1:10101\"),\n\t\t\tnewIp4AndPortFromString(\"172.31.0.1:10101\"),\n\t\t\tnewIp4AndPortFromString(\"172.17.1.1:10101\"),   // this is a dupe\n\t\t\tnewIp4AndPortFromString(\"70.199.182.92:1476\"), // almost dupe of 0 with a diff port\n\t\t\tnewIp4AndPortFromString(\"70.199.182.92:1475\"), // this is a dupe\n\t\t},\n\t\tfunc(netip.Addr, *V4AddrPort) bool { return true },\n\t)\n\n\trl.unlockedSetV6(\n\t\tnetip.MustParseAddr(\"0.0.0.1\"),\n\t\tnetip.MustParseAddr(\"0.0.0.1\"),\n\t\t[]*V6AddrPort{\n\t\t\tnewIp6AndPortFromString(\"[1::1]:1\"), // this is duped\n\t\t\tnewIp6AndPortFromString(\"[1::1]:2\"), // almost dupe of 0 with a diff port, also gets duped\n\t\t\tnewIp6AndPortFromString(\"[1:100::1]:1\"),\n\t\t\tnewIp6AndPortFromString(\"[1::1]:1\"), // this is a dupe\n\t\t\tnewIp6AndPortFromString(\"[1::1]:2\"), // this is a dupe\n\t\t},\n\t\tfunc(netip.Addr, *V6AddrPort) bool { return true },\n\t)\n\n\trl.unlockedSetRelay(\n\t\tnetip.MustParseAddr(\"0.0.0.1\"),\n\t\t[]netip.Addr{\n\t\t\tnetip.MustParseAddr(\"1::1\"),\n\t\t\tnetip.MustParseAddr(\"1.2.3.4\"),\n\t\t\tnetip.MustParseAddr(\"1.2.3.4\"),\n\t\t\tnetip.MustParseAddr(\"1::1\"),\n\t\t},\n\t)\n\n\trl.Rebuild([]netip.Prefix{})\n\tassert.Len(t, rl.addrs, 10, \"addrs contains too many entries\")\n\n\t// ipv6 first, sorted lexically within\n\tassert.Equal(t, \"[1::1]:1\", rl.addrs[0].String())\n\tassert.Equal(t, \"[1::1]:2\", rl.addrs[1].String())\n\tassert.Equal(t, \"[1:100::1]:1\", rl.addrs[2].String())\n\n\t// ipv4 last, sorted by public first, then private, lexically within them\n\tassert.Equal(t, \"70.199.182.92:1475\", rl.addrs[3].String())\n\tassert.Equal(t, \"70.199.182.92:1476\", rl.addrs[4].String())\n\tassert.Equal(t, \"172.17.0.182:10101\", rl.addrs[5].String())\n\tassert.Equal(t, \"172.17.1.1:10101\", rl.addrs[6].String())\n\tassert.Equal(t, \"172.18.0.1:10101\", rl.addrs[7].String())\n\tassert.Equal(t, \"172.19.0.1:10101\", rl.addrs[8].String())\n\tassert.Equal(t, \"172.31.0.1:10101\", rl.addrs[9].String())\n\n\t// Now ensure we can hoist ipv4 up\n\trl.Rebuild([]netip.Prefix{netip.MustParsePrefix(\"0.0.0.0/0\")})\n\tassert.Len(t, rl.addrs, 10, \"addrs contains too many entries\")\n\n\t// ipv4 first, public then private, lexically within them\n\tassert.Equal(t, \"70.199.182.92:1475\", rl.addrs[0].String())\n\tassert.Equal(t, \"70.199.182.92:1476\", rl.addrs[1].String())\n\tassert.Equal(t, \"172.17.0.182:10101\", rl.addrs[2].String())\n\tassert.Equal(t, \"172.17.1.1:10101\", rl.addrs[3].String())\n\tassert.Equal(t, \"172.18.0.1:10101\", rl.addrs[4].String())\n\tassert.Equal(t, \"172.19.0.1:10101\", rl.addrs[5].String())\n\tassert.Equal(t, \"172.31.0.1:10101\", rl.addrs[6].String())\n\n\t// ipv6 last, sorted by public first, then private, lexically within them\n\tassert.Equal(t, \"[1::1]:1\", rl.addrs[7].String())\n\tassert.Equal(t, \"[1::1]:2\", rl.addrs[8].String())\n\tassert.Equal(t, \"[1:100::1]:1\", rl.addrs[9].String())\n\n\t// assert relay deduplicated\n\tassert.Len(t, rl.relays, 2)\n\tassert.Equal(t, \"1.2.3.4\", rl.relays[0].String())\n\tassert.Equal(t, \"1::1\", rl.relays[1].String())\n\n\t// Ensure we can hoist a specific ipv4 range over anything else\n\trl.Rebuild([]netip.Prefix{netip.MustParsePrefix(\"172.17.0.0/16\")})\n\tassert.Len(t, rl.addrs, 10, \"addrs contains too many entries\")\n\n\t// Preferred ipv4 first\n\tassert.Equal(t, \"172.17.0.182:10101\", rl.addrs[0].String())\n\tassert.Equal(t, \"172.17.1.1:10101\", rl.addrs[1].String())\n\n\t// ipv6 next\n\tassert.Equal(t, \"[1::1]:1\", rl.addrs[2].String())\n\tassert.Equal(t, \"[1::1]:2\", rl.addrs[3].String())\n\tassert.Equal(t, \"[1:100::1]:1\", rl.addrs[4].String())\n\n\t// the remaining ipv4 last\n\tassert.Equal(t, \"70.199.182.92:1475\", rl.addrs[5].String())\n\tassert.Equal(t, \"70.199.182.92:1476\", rl.addrs[6].String())\n\tassert.Equal(t, \"172.18.0.1:10101\", rl.addrs[7].String())\n\tassert.Equal(t, \"172.19.0.1:10101\", rl.addrs[8].String())\n\tassert.Equal(t, \"172.31.0.1:10101\", rl.addrs[9].String())\n}\n\nfunc BenchmarkFullRebuild(b *testing.B) {\n\trl := NewRemoteList([]netip.Addr{netip.MustParseAddr(\"0.0.0.0\")}, nil)\n\trl.unlockedSetV4(\n\t\tnetip.MustParseAddr(\"0.0.0.0\"),\n\t\tnetip.MustParseAddr(\"0.0.0.0\"),\n\t\t[]*V4AddrPort{\n\t\t\tnewIp4AndPortFromString(\"70.199.182.92:1475\"),\n\t\t\tnewIp4AndPortFromString(\"172.17.0.182:10101\"),\n\t\t\tnewIp4AndPortFromString(\"172.17.1.1:10101\"),\n\t\t\tnewIp4AndPortFromString(\"172.18.0.1:10101\"),\n\t\t\tnewIp4AndPortFromString(\"172.19.0.1:10101\"),\n\t\t\tnewIp4AndPortFromString(\"172.31.0.1:10101\"),\n\t\t\tnewIp4AndPortFromString(\"172.17.1.1:10101\"),   // this is a dupe\n\t\t\tnewIp4AndPortFromString(\"70.199.182.92:1476\"), // dupe of 0 with a diff port\n\t\t},\n\t\tfunc(netip.Addr, *V4AddrPort) bool { return true },\n\t)\n\n\trl.unlockedSetV6(\n\t\tnetip.MustParseAddr(\"0.0.0.0\"),\n\t\tnetip.MustParseAddr(\"0.0.0.0\"),\n\t\t[]*V6AddrPort{\n\t\t\tnewIp6AndPortFromString(\"[1::1]:1\"),\n\t\t\tnewIp6AndPortFromString(\"[1::1]:2\"), // dupe of 0 with a diff port\n\t\t\tnewIp6AndPortFromString(\"[1:100::1]:1\"),\n\t\t\tnewIp6AndPortFromString(\"[1::1]:1\"), // this is a dupe\n\t\t},\n\t\tfunc(netip.Addr, *V6AddrPort) bool { return true },\n\t)\n\n\tb.Run(\"no preferred\", func(b *testing.B) {\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\trl.shouldRebuild = true\n\t\t\trl.Rebuild([]netip.Prefix{})\n\t\t}\n\t})\n\n\tipNet1 := netip.MustParsePrefix(\"172.17.0.0/16\")\n\tb.Run(\"1 preferred\", func(b *testing.B) {\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\trl.shouldRebuild = true\n\t\t\trl.Rebuild([]netip.Prefix{ipNet1})\n\t\t}\n\t})\n\n\tipNet2 := netip.MustParsePrefix(\"70.0.0.0/8\")\n\tb.Run(\"2 preferred\", func(b *testing.B) {\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\trl.shouldRebuild = true\n\t\t\trl.Rebuild([]netip.Prefix{ipNet2})\n\t\t}\n\t})\n\n\tipNet3 := netip.MustParsePrefix(\"0.0.0.0/0\")\n\tb.Run(\"3 preferred\", func(b *testing.B) {\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\trl.shouldRebuild = true\n\t\t\trl.Rebuild([]netip.Prefix{ipNet1, ipNet2, ipNet3})\n\t\t}\n\t})\n}\n\nfunc BenchmarkSortRebuild(b *testing.B) {\n\trl := NewRemoteList([]netip.Addr{netip.MustParseAddr(\"0.0.0.0\")}, nil)\n\trl.unlockedSetV4(\n\t\tnetip.MustParseAddr(\"0.0.0.0\"),\n\t\tnetip.MustParseAddr(\"0.0.0.0\"),\n\t\t[]*V4AddrPort{\n\t\t\tnewIp4AndPortFromString(\"70.199.182.92:1475\"),\n\t\t\tnewIp4AndPortFromString(\"172.17.0.182:10101\"),\n\t\t\tnewIp4AndPortFromString(\"172.17.1.1:10101\"),\n\t\t\tnewIp4AndPortFromString(\"172.18.0.1:10101\"),\n\t\t\tnewIp4AndPortFromString(\"172.19.0.1:10101\"),\n\t\t\tnewIp4AndPortFromString(\"172.31.0.1:10101\"),\n\t\t\tnewIp4AndPortFromString(\"172.17.1.1:10101\"),   // this is a dupe\n\t\t\tnewIp4AndPortFromString(\"70.199.182.92:1476\"), // dupe of 0 with a diff port\n\t\t},\n\t\tfunc(netip.Addr, *V4AddrPort) bool { return true },\n\t)\n\n\trl.unlockedSetV6(\n\t\tnetip.MustParseAddr(\"0.0.0.0\"),\n\t\tnetip.MustParseAddr(\"0.0.0.0\"),\n\t\t[]*V6AddrPort{\n\t\t\tnewIp6AndPortFromString(\"[1::1]:1\"),\n\t\t\tnewIp6AndPortFromString(\"[1::1]:2\"), // dupe of 0 with a diff port\n\t\t\tnewIp6AndPortFromString(\"[1:100::1]:1\"),\n\t\t\tnewIp6AndPortFromString(\"[1::1]:1\"), // this is a dupe\n\t\t},\n\t\tfunc(netip.Addr, *V6AddrPort) bool { return true },\n\t)\n\n\tb.Run(\"no preferred\", func(b *testing.B) {\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\trl.shouldRebuild = true\n\t\t\trl.Rebuild([]netip.Prefix{})\n\t\t}\n\t})\n\n\tipNet1 := netip.MustParsePrefix(\"172.17.0.0/16\")\n\trl.Rebuild([]netip.Prefix{ipNet1})\n\n\tb.Run(\"1 preferred\", func(b *testing.B) {\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\trl.Rebuild([]netip.Prefix{ipNet1})\n\t\t}\n\t})\n\n\tipNet2 := netip.MustParsePrefix(\"70.0.0.0/8\")\n\trl.Rebuild([]netip.Prefix{ipNet1, ipNet2})\n\n\tb.Run(\"2 preferred\", func(b *testing.B) {\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\trl.Rebuild([]netip.Prefix{ipNet1, ipNet2})\n\t\t}\n\t})\n\n\tipNet3 := netip.MustParsePrefix(\"0.0.0.0/0\")\n\trl.Rebuild([]netip.Prefix{ipNet1, ipNet2, ipNet3})\n\n\tb.Run(\"3 preferred\", func(b *testing.B) {\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\trl.Rebuild([]netip.Prefix{ipNet1, ipNet2, ipNet3})\n\t\t}\n\t})\n}\n\nfunc newIp4AndPortFromString(s string) *V4AddrPort {\n\ta := netip.MustParseAddrPort(s)\n\tv4Addr := a.Addr().As4()\n\treturn &V4AddrPort{\n\t\tAddr: binary.BigEndian.Uint32(v4Addr[:]),\n\t\tPort: uint32(a.Port()),\n\t}\n}\n\nfunc newIp6AndPortFromString(s string) *V6AddrPort {\n\ta := netip.MustParseAddrPort(s)\n\tv6Addr := a.Addr().As16()\n\treturn &V6AddrPort{\n\t\tHi:   binary.BigEndian.Uint64(v6Addr[:8]),\n\t\tLo:   binary.BigEndian.Uint64(v6Addr[8:]),\n\t\tPort: uint32(a.Port()),\n\t}\n}\n"
  },
  {
    "path": "routing/balance.go",
    "content": "package routing\n\nimport (\n\t\"net/netip\"\n\n\t\"github.com/slackhq/nebula/firewall\"\n)\n\n// Hashes the packet source and destination port and always returns a positive integer\n// Based on 'Prospecting for Hash Functions'\n//   - https://nullprogram.com/blog/2018/07/31/\n//   - https://github.com/skeeto/hash-prospector\n//     [16 21f0aaad 15 d35a2d97 15] = 0.10760229515479501\nfunc hashPacket(p *firewall.Packet) int {\n\tx := (uint32(p.LocalPort) << 16) | uint32(p.RemotePort)\n\tx ^= x >> 16\n\tx *= 0x21f0aaad\n\tx ^= x >> 15\n\tx *= 0xd35a2d97\n\tx ^= x >> 15\n\n\treturn int(x) & 0x7FFFFFFF\n}\n\n// For this function to work correctly it requires that the buckets for the gateways have been calculated\n// If the contract is violated balancing will not work properly and the second return value will return false\nfunc BalancePacket(fwPacket *firewall.Packet, gateways []Gateway) (netip.Addr, bool) {\n\thash := hashPacket(fwPacket)\n\n\tfor i := range gateways {\n\t\tif hash <= gateways[i].BucketUpperBound() {\n\t\t\treturn gateways[i].Addr(), true\n\t\t}\n\t}\n\n\t// If you land here then the buckets for the gateways are not properly calculated\n\t// Fallback to random routing and let the caller know\n\treturn gateways[hash%len(gateways)].Addr(), false\n}\n"
  },
  {
    "path": "routing/balance_test.go",
    "content": "package routing\n\nimport (\n\t\"net/netip\"\n\t\"testing\"\n\n\t\"github.com/slackhq/nebula/firewall\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestPacketsAreBalancedEqually(t *testing.T) {\n\n\tgateways := []Gateway{}\n\n\tgw1Addr := netip.MustParseAddr(\"1.0.0.1\")\n\tgw2Addr := netip.MustParseAddr(\"1.0.0.2\")\n\tgw3Addr := netip.MustParseAddr(\"1.0.0.3\")\n\n\tgateways = append(gateways, NewGateway(gw1Addr, 1))\n\tgateways = append(gateways, NewGateway(gw2Addr, 1))\n\tgateways = append(gateways, NewGateway(gw3Addr, 1))\n\n\tCalculateBucketsForGateways(gateways)\n\n\tgw1count := 0\n\tgw2count := 0\n\tgw3count := 0\n\n\titerationCount := uint16(65535)\n\tfor i := uint16(0); i < iterationCount; i++ {\n\t\tpacket := firewall.Packet{\n\t\t\tLocalAddr:  netip.MustParseAddr(\"192.168.1.1\"),\n\t\t\tRemoteAddr: netip.MustParseAddr(\"10.0.0.1\"),\n\t\t\tLocalPort:  i,\n\t\t\tRemotePort: 65535 - i,\n\t\t\tProtocol:   6, // TCP\n\t\t\tFragment:   false,\n\t\t}\n\n\t\tselectedGw, ok := BalancePacket(&packet, gateways)\n\t\tassert.True(t, ok)\n\n\t\tswitch selectedGw {\n\t\tcase gw1Addr:\n\t\t\tgw1count += 1\n\t\tcase gw2Addr:\n\t\t\tgw2count += 1\n\t\tcase gw3Addr:\n\t\t\tgw3count += 1\n\t\t}\n\n\t}\n\n\t// Assert packets are balanced, allow variation of up to 100 packets per gateway\n\tassert.InDeltaf(t, iterationCount/3, gw1count, 100, \"Expected %d +/- 100, but got %d\", iterationCount/3, gw1count)\n\tassert.InDeltaf(t, iterationCount/3, gw2count, 100, \"Expected %d +/- 100, but got %d\", iterationCount/3, gw1count)\n\tassert.InDeltaf(t, iterationCount/3, gw3count, 100, \"Expected %d +/- 100, but got %d\", iterationCount/3, gw1count)\n\n}\n\nfunc TestPacketsAreBalancedByPriority(t *testing.T) {\n\n\tgateways := []Gateway{}\n\n\tgw1Addr := netip.MustParseAddr(\"1.0.0.1\")\n\tgw2Addr := netip.MustParseAddr(\"1.0.0.2\")\n\n\tgateways = append(gateways, NewGateway(gw1Addr, 10))\n\tgateways = append(gateways, NewGateway(gw2Addr, 5))\n\n\tCalculateBucketsForGateways(gateways)\n\n\tgw1count := 0\n\tgw2count := 0\n\n\titerationCount := uint16(65535)\n\tfor i := uint16(0); i < iterationCount; i++ {\n\t\tpacket := firewall.Packet{\n\t\t\tLocalAddr:  netip.MustParseAddr(\"192.168.1.1\"),\n\t\t\tRemoteAddr: netip.MustParseAddr(\"10.0.0.1\"),\n\t\t\tLocalPort:  i,\n\t\t\tRemotePort: 65535 - i,\n\t\t\tProtocol:   6, // TCP\n\t\t\tFragment:   false,\n\t\t}\n\n\t\tselectedGw, ok := BalancePacket(&packet, gateways)\n\t\tassert.True(t, ok)\n\n\t\tswitch selectedGw {\n\t\tcase gw1Addr:\n\t\t\tgw1count += 1\n\t\tcase gw2Addr:\n\t\t\tgw2count += 1\n\t\t}\n\n\t}\n\n\titerationCountAsFloat := float32(iterationCount)\n\n\tassert.InDeltaf(t, iterationCountAsFloat*(2.0/3.0), gw1count, 100, \"Expected %d +/- 100, but got %d\", iterationCountAsFloat*(2.0/3.0), gw1count)\n\tassert.InDeltaf(t, iterationCountAsFloat*(1.0/3.0), gw2count, 100, \"Expected %d +/- 100, but got %d\", iterationCountAsFloat*(1.0/3.0), gw2count)\n}\n\nfunc TestBalancePacketDistributsRandomlyAndReturnsFalseIfBucketsNotCalculated(t *testing.T) {\n\tgateways := []Gateway{}\n\n\tgw1Addr := netip.MustParseAddr(\"1.0.0.1\")\n\tgw2Addr := netip.MustParseAddr(\"1.0.0.2\")\n\n\tgateways = append(gateways, NewGateway(gw1Addr, 10))\n\tgateways = append(gateways, NewGateway(gw2Addr, 5))\n\n\titerationCount := uint16(65535)\n\tgw1count := 0\n\tgw2count := 0\n\n\tfor i := uint16(0); i < iterationCount; i++ {\n\t\tpacket := firewall.Packet{\n\t\t\tLocalAddr:  netip.MustParseAddr(\"192.168.1.1\"),\n\t\t\tRemoteAddr: netip.MustParseAddr(\"10.0.0.1\"),\n\t\t\tLocalPort:  i,\n\t\t\tRemotePort: 65535 - i,\n\t\t\tProtocol:   6, // TCP\n\t\t\tFragment:   false,\n\t\t}\n\n\t\tselectedGw, ok := BalancePacket(&packet, gateways)\n\t\tassert.False(t, ok)\n\n\t\tswitch selectedGw {\n\t\tcase gw1Addr:\n\t\t\tgw1count += 1\n\t\tcase gw2Addr:\n\t\t\tgw2count += 1\n\t\t}\n\n\t}\n\n\tassert.Equal(t, int(iterationCount), (gw1count + gw2count))\n\tassert.NotEqual(t, 0, gw1count)\n\tassert.NotEqual(t, 0, gw2count)\n\n}\n"
  },
  {
    "path": "routing/gateway.go",
    "content": "package routing\n\nimport (\n\t\"fmt\"\n\t\"net/netip\"\n)\n\nconst (\n\t// Sentinel value\n\tBucketNotCalculated = -1\n)\n\ntype Gateways []Gateway\n\nfunc (g Gateways) String() string {\n\tstr := \"\"\n\tfor i, gw := range g {\n\t\tstr += gw.String()\n\t\tif i < len(g)-1 {\n\t\t\tstr += \", \"\n\t\t}\n\t}\n\treturn str\n}\n\ntype Gateway struct {\n\taddr             netip.Addr\n\tweight           int\n\tbucketUpperBound int\n}\n\nfunc NewGateway(addr netip.Addr, weight int) Gateway {\n\treturn Gateway{addr: addr, weight: weight, bucketUpperBound: BucketNotCalculated}\n}\n\nfunc (g *Gateway) BucketUpperBound() int {\n\treturn g.bucketUpperBound\n}\n\nfunc (g *Gateway) Addr() netip.Addr {\n\treturn g.addr\n}\n\nfunc (g *Gateway) String() string {\n\treturn fmt.Sprintf(\"{addr: %s, weight: %d}\", g.addr, g.weight)\n}\n\n// Divide and round to nearest integer\nfunc divideAndRound(v uint64, d uint64) uint64 {\n\tvar tmp uint64 = v + d/2\n\treturn tmp / d\n}\n\n// Implements Hash-Threshold mapping, equivalent to the implementation in the linux kernel.\n// After this function returns each gateway will have a\n// positive bucketUpperBound with a maximum value of 2147483647 (INT_MAX)\nfunc CalculateBucketsForGateways(gateways []Gateway) {\n\n\tvar totalWeight int = 0\n\tfor i := range gateways {\n\t\ttotalWeight += gateways[i].weight\n\t}\n\n\tvar loopWeight int = 0\n\tfor i := range gateways {\n\t\tloopWeight += gateways[i].weight\n\t\tgateways[i].bucketUpperBound = int(divideAndRound(uint64(loopWeight)<<31, uint64(totalWeight))) - 1\n\t}\n\n}\n"
  },
  {
    "path": "routing/gateway_test.go",
    "content": "package routing\n\nimport (\n\t\"net/netip\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestRebalance3_2Split(t *testing.T) {\n\tgateways := []Gateway{}\n\n\tgateways = append(gateways, Gateway{addr: netip.Addr{}, weight: 10})\n\tgateways = append(gateways, Gateway{addr: netip.Addr{}, weight: 5})\n\n\tCalculateBucketsForGateways(gateways)\n\n\tassert.Equal(t, 1431655764, gateways[0].bucketUpperBound) // INT_MAX/3*2\n\tassert.Equal(t, 2147483647, gateways[1].bucketUpperBound) // INT_MAX\n}\n\nfunc TestRebalanceEqualSplit(t *testing.T) {\n\tgateways := []Gateway{}\n\n\tgateways = append(gateways, Gateway{addr: netip.Addr{}, weight: 1})\n\tgateways = append(gateways, Gateway{addr: netip.Addr{}, weight: 1})\n\tgateways = append(gateways, Gateway{addr: netip.Addr{}, weight: 1})\n\n\tCalculateBucketsForGateways(gateways)\n\n\tassert.Equal(t, 715827882, gateways[0].bucketUpperBound)  // INT_MAX/3\n\tassert.Equal(t, 1431655764, gateways[1].bucketUpperBound) // INT_MAX/3*2\n\tassert.Equal(t, 2147483647, gateways[2].bucketUpperBound) // INT_MAX\n}\n"
  },
  {
    "path": "service/listener.go",
    "content": "package service\n\nimport (\n\t\"io\"\n\t\"net\"\n)\n\ntype tcpListener struct {\n\tport   uint16\n\ts      *Service\n\taddr   *net.TCPAddr\n\taccept chan net.Conn\n}\n\nfunc (l *tcpListener) Accept() (net.Conn, error) {\n\tconn, ok := <-l.accept\n\tif !ok {\n\t\treturn nil, io.EOF\n\t}\n\treturn conn, nil\n}\n\nfunc (l *tcpListener) Close() error {\n\tl.s.mu.Lock()\n\tdefer l.s.mu.Unlock()\n\tdelete(l.s.mu.listeners, uint16(l.addr.Port))\n\n\tclose(l.accept)\n\n\treturn nil\n}\n\n// Addr returns the listener's network address.\nfunc (l *tcpListener) Addr() net.Addr {\n\treturn l.addr\n}\n"
  },
  {
    "path": "service/service.go",
    "content": "package service\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log\"\n\t\"math\"\n\t\"net\"\n\t\"net/netip\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/slackhq/nebula\"\n\t\"github.com/slackhq/nebula/overlay\"\n\t\"golang.org/x/sync/errgroup\"\n\t\"gvisor.dev/gvisor/pkg/buffer\"\n\t\"gvisor.dev/gvisor/pkg/tcpip\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/header\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/link/channel\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/network/ipv4\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/network/ipv6\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/stack\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/transport/icmp\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/transport/tcp\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/transport/udp\"\n\t\"gvisor.dev/gvisor/pkg/waiter\"\n)\n\nconst nicID = 1\n\ntype Service struct {\n\teg      *errgroup.Group\n\tcontrol *nebula.Control\n\tipstack *stack.Stack\n\n\tmu struct {\n\t\tsync.Mutex\n\n\t\tlisteners map[uint16]*tcpListener\n\t}\n}\n\nfunc New(control *nebula.Control) (*Service, error) {\n\tcontrol.Start()\n\n\tctx := control.Context()\n\teg, ctx := errgroup.WithContext(ctx)\n\ts := Service{\n\t\teg:      eg,\n\t\tcontrol: control,\n\t}\n\ts.mu.listeners = map[uint16]*tcpListener{}\n\n\tdevice, ok := control.Device().(*overlay.UserDevice)\n\tif !ok {\n\t\treturn nil, errors.New(\"must be using user device\")\n\t}\n\n\ts.ipstack = stack.New(stack.Options{\n\t\tNetworkProtocols:   []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},\n\t\tTransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol, icmp.NewProtocol4, icmp.NewProtocol6},\n\t})\n\tsackEnabledOpt := tcpip.TCPSACKEnabled(true) // TCP SACK is disabled by default\n\ttcpipErr := s.ipstack.SetTransportProtocolOption(tcp.ProtocolNumber, &sackEnabledOpt)\n\tif tcpipErr != nil {\n\t\treturn nil, fmt.Errorf(\"could not enable TCP SACK: %v\", tcpipErr)\n\t}\n\tlinkEP := channel.New( /*size*/ 512 /*mtu*/, 1280, \"\")\n\tif tcpipProblem := s.ipstack.CreateNIC(nicID, linkEP); tcpipProblem != nil {\n\t\treturn nil, fmt.Errorf(\"could not create netstack NIC: %v\", tcpipProblem)\n\t}\n\tipv4Subnet, _ := tcpip.NewSubnet(tcpip.AddrFrom4([4]byte{0x00, 0x00, 0x00, 0x00}), tcpip.MaskFrom(strings.Repeat(\"\\x00\", 4)))\n\ts.ipstack.SetRouteTable([]tcpip.Route{\n\t\t{\n\t\t\tDestination: ipv4Subnet,\n\t\t\tNIC:         nicID,\n\t\t},\n\t})\n\n\tipNet := device.Networks()\n\tpa := tcpip.ProtocolAddress{\n\t\tAddressWithPrefix: tcpip.AddrFromSlice(ipNet[0].Addr().AsSlice()).WithPrefix(),\n\t\tProtocol:          ipv4.ProtocolNumber,\n\t}\n\tif err := s.ipstack.AddProtocolAddress(nicID, pa, stack.AddressProperties{\n\t\tPEB:        stack.CanBePrimaryEndpoint, // zero value default\n\t\tConfigType: stack.AddressConfigStatic,  // zero value default\n\t}); err != nil {\n\t\treturn nil, fmt.Errorf(\"error creating IP: %s\", err)\n\t}\n\n\tconst tcpReceiveBufferSize = 0\n\tconst maxInFlightConnectionAttempts = 1024\n\ttcpFwd := tcp.NewForwarder(s.ipstack, tcpReceiveBufferSize, maxInFlightConnectionAttempts, s.tcpHandler)\n\ts.ipstack.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpFwd.HandlePacket)\n\n\treader, writer := device.Pipe()\n\n\tgo func() {\n\t\t<-ctx.Done()\n\t\treader.Close()\n\t\twriter.Close()\n\t}()\n\n\t// create Goroutines to forward packets between Nebula and Gvisor\n\teg.Go(func() error {\n\t\tbuf := make([]byte, header.IPv4MaximumHeaderSize+header.IPv4MaximumPayloadSize)\n\t\tfor {\n\t\t\t// this will read exactly one packet\n\t\t\tn, err := reader.Read(buf)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tpacketBuf := stack.NewPacketBuffer(stack.PacketBufferOptions{\n\t\t\t\tPayload: buffer.MakeWithData(bytes.Clone(buf[:n])),\n\t\t\t})\n\t\t\tlinkEP.InjectInbound(header.IPv4ProtocolNumber, packetBuf)\n\n\t\t\tif err := ctx.Err(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t})\n\teg.Go(func() error {\n\t\tfor {\n\t\t\tpacket := linkEP.ReadContext(ctx)\n\t\t\tif packet == nil {\n\t\t\t\tif err := ctx.Err(); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tbufView := packet.ToView()\n\t\t\tif _, err := bufView.WriteTo(writer); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tbufView.Release()\n\t\t}\n\t})\n\n\treturn &s, nil\n}\n\nfunc getProtocolNumber(addr netip.Addr) tcpip.NetworkProtocolNumber {\n\tif addr.Is6() {\n\t\treturn ipv6.ProtocolNumber\n\t}\n\treturn ipv4.ProtocolNumber\n}\n\n// DialContext dials the provided address.\nfunc (s *Service) DialContext(ctx context.Context, network, address string) (net.Conn, error) {\n\tswitch network {\n\tcase \"udp\", \"udp4\", \"udp6\":\n\t\taddr, err := net.ResolveUDPAddr(network, address)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tfullAddr := tcpip.FullAddress{\n\t\t\tNIC:  nicID,\n\t\t\tAddr: tcpip.AddrFromSlice(addr.IP),\n\t\t\tPort: uint16(addr.Port),\n\t\t}\n\t\tnum := getProtocolNumber(addr.AddrPort().Addr())\n\t\treturn gonet.DialUDP(s.ipstack, nil, &fullAddr, num)\n\tcase \"tcp\", \"tcp4\", \"tcp6\":\n\t\taddr, err := net.ResolveTCPAddr(network, address)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tfullAddr := tcpip.FullAddress{\n\t\t\tNIC:  nicID,\n\t\t\tAddr: tcpip.AddrFromSlice(addr.IP),\n\t\t\tPort: uint16(addr.Port),\n\t\t}\n\t\tnum := getProtocolNumber(addr.AddrPort().Addr())\n\t\treturn gonet.DialContextTCP(ctx, s.ipstack, fullAddr, num)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unknown network type: %s\", network)\n\t}\n}\n\n// Dial dials the provided address\nfunc (s *Service) Dial(network, address string) (net.Conn, error) {\n\treturn s.DialContext(context.Background(), network, address)\n}\n\n// Listen listens on the provided address. Currently only TCP with wildcard\n// addresses are supported.\nfunc (s *Service) Listen(network, address string) (net.Listener, error) {\n\tif network != \"tcp\" && network != \"tcp4\" {\n\t\treturn nil, errors.New(\"only tcp is supported\")\n\t}\n\taddr, err := net.ResolveTCPAddr(network, address)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif addr.IP != nil && !bytes.Equal(addr.IP, []byte{0, 0, 0, 0}) {\n\t\treturn nil, fmt.Errorf(\"only wildcard address supported, got %q %v\", address, addr.IP)\n\t}\n\tif addr.Port == 0 {\n\t\treturn nil, errors.New(\"specific port required, got 0\")\n\t}\n\tif addr.Port < 0 || addr.Port >= math.MaxUint16 {\n\t\treturn nil, fmt.Errorf(\"invalid port %d\", addr.Port)\n\t}\n\tport := uint16(addr.Port)\n\n\tl := &tcpListener{\n\t\tport:   port,\n\t\ts:      s,\n\t\taddr:   addr,\n\t\taccept: make(chan net.Conn),\n\t}\n\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\n\tif _, ok := s.mu.listeners[port]; ok {\n\t\treturn nil, fmt.Errorf(\"already listening on port %d\", port)\n\t}\n\ts.mu.listeners[port] = l\n\n\treturn l, nil\n}\n\nfunc (s *Service) Wait() error {\n\treturn s.eg.Wait()\n}\n\nfunc (s *Service) Close() error {\n\ts.control.Stop()\n\treturn nil\n}\n\nfunc (s *Service) tcpHandler(r *tcp.ForwarderRequest) {\n\tendpointID := r.ID()\n\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\n\tl, ok := s.mu.listeners[endpointID.LocalPort]\n\tif !ok {\n\t\tr.Complete(true)\n\t\treturn\n\t}\n\n\tvar wq waiter.Queue\n\tep, err := r.CreateEndpoint(&wq)\n\tif err != nil {\n\t\tlog.Printf(\"got error creating endpoint %q\", err)\n\t\tr.Complete(true)\n\t\treturn\n\t}\n\tr.Complete(false)\n\tep.SocketOptions().SetKeepAlive(true)\n\n\tconn := gonet.NewTCPConn(&wq, ep)\n\tl.accept <- conn\n}\n"
  },
  {
    "path": "service/service_test.go",
    "content": "package service\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"net/netip\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"dario.cat/mergo\"\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula\"\n\t\"github.com/slackhq/nebula/cert\"\n\t\"github.com/slackhq/nebula/cert_test\"\n\t\"github.com/slackhq/nebula/config\"\n\t\"github.com/slackhq/nebula/overlay\"\n\t\"go.yaml.in/yaml/v3\"\n\t\"golang.org/x/sync/errgroup\"\n)\n\ntype m = map[string]any\n\nfunc newSimpleService(caCrt cert.Certificate, caKey []byte, name string, udpIp netip.Addr, overrides m) *Service {\n\t_, _, myPrivKey, myPEM := cert_test.NewTestCert(cert.Version2, cert.Curve_CURVE25519, caCrt, caKey, \"a\", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{netip.PrefixFrom(udpIp, 24)}, nil, []string{})\n\tcaB, err := caCrt.MarshalPEM()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tmc := m{\n\t\t\"pki\": m{\n\t\t\t\"ca\":   string(caB),\n\t\t\t\"cert\": string(myPEM),\n\t\t\t\"key\":  string(myPrivKey),\n\t\t},\n\t\t//\"tun\": m{\"disabled\": true},\n\t\t\"firewall\": m{\n\t\t\t\"outbound\": []m{{\n\t\t\t\t\"proto\": \"any\",\n\t\t\t\t\"port\":  \"any\",\n\t\t\t\t\"host\":  \"any\",\n\t\t\t}},\n\t\t\t\"inbound\": []m{{\n\t\t\t\t\"proto\": \"any\",\n\t\t\t\t\"port\":  \"any\",\n\t\t\t\t\"host\":  \"any\",\n\t\t\t}},\n\t\t},\n\t\t\"timers\": m{\n\t\t\t\"pending_deletion_interval\": 2,\n\t\t\t\"connection_alive_interval\": 2,\n\t\t},\n\t\t\"handshakes\": m{\n\t\t\t\"try_interval\": \"200ms\",\n\t\t},\n\t}\n\n\tif overrides != nil {\n\t\terr = mergo.Merge(&overrides, mc, mergo.WithAppendSlice)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tmc = overrides\n\t}\n\n\tcb, err := yaml.Marshal(mc)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tvar c config.C\n\tif err := c.LoadString(string(cb)); err != nil {\n\t\tpanic(err)\n\t}\n\n\tlogger := logrus.New()\n\tlogger.Out = os.Stdout\n\n\tcontrol, err := nebula.Main(&c, false, \"custom-app\", logger, overlay.NewUserDeviceFromConfig)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\ts, err := New(control)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn s\n}\n\nfunc TestService(t *testing.T) {\n\tca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})\n\ta := newSimpleService(ca, caKey, \"a\", netip.MustParseAddr(\"10.0.0.1\"), m{\n\t\t\"static_host_map\": m{},\n\t\t\"lighthouse\": m{\n\t\t\t\"am_lighthouse\": true,\n\t\t},\n\t\t\"listen\": m{\n\t\t\t\"host\": \"0.0.0.0\",\n\t\t\t\"port\": 4243,\n\t\t},\n\t})\n\tb := newSimpleService(ca, caKey, \"b\", netip.MustParseAddr(\"10.0.0.2\"), m{\n\t\t\"static_host_map\": m{\n\t\t\t\"10.0.0.1\": []string{\"localhost:4243\"},\n\t\t},\n\t\t\"lighthouse\": m{\n\t\t\t\"hosts\":    []string{\"10.0.0.1\"},\n\t\t\t\"interval\": 1,\n\t\t},\n\t})\n\n\tln, err := a.Listen(\"tcp\", \":1234\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tvar eg errgroup.Group\n\teg.Go(func() error {\n\t\tconn, err := ln.Accept()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer conn.Close()\n\n\t\tt.Log(\"accepted connection\")\n\n\t\tif _, err := conn.Write([]byte(\"server msg\")); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tt.Log(\"server: wrote message\")\n\n\t\tdata := make([]byte, 100)\n\t\tn, err := conn.Read(data)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdata = data[:n]\n\t\tif !bytes.Equal(data, []byte(\"client msg\")) {\n\t\t\treturn errors.New(\"got invalid message from client\")\n\t\t}\n\t\tt.Log(\"server: read message\")\n\t\treturn conn.Close()\n\t})\n\n\tc, err := b.DialContext(context.Background(), \"tcp\", \"10.0.0.1:1234\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif _, err := c.Write([]byte(\"client msg\")); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdata := make([]byte, 100)\n\tn, err := c.Read(data)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdata = data[:n]\n\tif !bytes.Equal(data, []byte(\"server msg\")) {\n\t\tt.Fatal(\"got invalid message from client\")\n\t}\n\n\tif err := c.Close(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif err := eg.Wait(); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n"
  },
  {
    "path": "ssh.go",
    "content": "package nebula\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"flag\"\n\t\"fmt\"\n\t\"maps\"\n\t\"net\"\n\t\"net/netip\"\n\t\"os\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"runtime/pprof\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/config\"\n\t\"github.com/slackhq/nebula/header\"\n\t\"github.com/slackhq/nebula/sshd\"\n)\n\ntype sshListHostMapFlags struct {\n\tJson    bool\n\tPretty  bool\n\tByIndex bool\n}\n\ntype sshPrintCertFlags struct {\n\tJson   bool\n\tPretty bool\n\tRaw    bool\n}\n\ntype sshPrintTunnelFlags struct {\n\tPretty bool\n}\n\ntype sshChangeRemoteFlags struct {\n\tAddress string\n}\n\ntype sshCloseTunnelFlags struct {\n\tLocalOnly bool\n}\n\ntype sshCreateTunnelFlags struct {\n\tAddress string\n}\n\ntype sshDeviceInfoFlags struct {\n\tJson   bool\n\tPretty bool\n}\n\nfunc wireSSHReload(l *logrus.Logger, ssh *sshd.SSHServer, c *config.C) {\n\tc.RegisterReloadCallback(func(c *config.C) {\n\t\tif c.GetBool(\"sshd.enabled\", false) {\n\t\t\tsshRun, err := configSSH(l, ssh, c)\n\t\t\tif err != nil {\n\t\t\t\tl.WithError(err).Error(\"Failed to reconfigure the sshd\")\n\t\t\t\tssh.Stop()\n\t\t\t}\n\t\t\tif sshRun != nil {\n\t\t\t\tgo sshRun()\n\t\t\t}\n\t\t} else {\n\t\t\tssh.Stop()\n\t\t}\n\t})\n}\n\n// configSSH reads the ssh info out of the passed-in Config and\n// updates the passed-in SSHServer. On success, it returns a function\n// that callers may invoke to run the configured ssh server. On\n// failure, it returns nil, error.\nfunc configSSH(l *logrus.Logger, ssh *sshd.SSHServer, c *config.C) (func(), error) {\n\tlisten := c.GetString(\"sshd.listen\", \"\")\n\tif listen == \"\" {\n\t\treturn nil, fmt.Errorf(\"sshd.listen must be provided\")\n\t}\n\n\t_, port, err := net.SplitHostPort(listen)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid sshd.listen address: %s\", err)\n\t}\n\tif port == \"22\" {\n\t\treturn nil, fmt.Errorf(\"sshd.listen can not use port 22\")\n\t}\n\n\thostKeyPathOrKey := c.GetString(\"sshd.host_key\", \"\")\n\tif hostKeyPathOrKey == \"\" {\n\t\treturn nil, fmt.Errorf(\"sshd.host_key must be provided\")\n\t}\n\n\tvar hostKeyBytes []byte\n\tif strings.Contains(hostKeyPathOrKey, \"-----BEGIN\") {\n\t\thostKeyBytes = []byte(hostKeyPathOrKey)\n\t} else {\n\t\thostKeyBytes, err = os.ReadFile(hostKeyPathOrKey)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error while loading sshd.host_key file: %s\", err)\n\t\t}\n\t}\n\n\terr = ssh.SetHostKey(hostKeyBytes)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error while adding sshd.host_key: %s\", err)\n\t}\n\n\t// Clear existing trusted CAs and authorized keys\n\tssh.ClearTrustedCAs()\n\tssh.ClearAuthorizedKeys()\n\n\trawCAs := c.GetStringSlice(\"sshd.trusted_cas\", []string{})\n\tfor _, caAuthorizedKey := range rawCAs {\n\t\terr := ssh.AddTrustedCA(caAuthorizedKey)\n\t\tif err != nil {\n\t\t\tl.WithError(err).WithField(\"sshCA\", caAuthorizedKey).Warn(\"SSH CA had an error, ignoring\")\n\t\t\tcontinue\n\t\t}\n\t}\n\n\trawKeys := c.Get(\"sshd.authorized_users\")\n\tkeys, ok := rawKeys.([]any)\n\tif ok {\n\t\tfor _, rk := range keys {\n\t\t\tkDef, ok := rk.(map[string]any)\n\t\t\tif !ok {\n\t\t\t\tl.WithField(\"sshKeyConfig\", rk).Warn(\"Authorized user had an error, ignoring\")\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tuser, ok := kDef[\"user\"].(string)\n\t\t\tif !ok {\n\t\t\t\tl.WithField(\"sshKeyConfig\", rk).Warn(\"Authorized user is missing the user field\")\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tk := kDef[\"keys\"]\n\t\t\tswitch v := k.(type) {\n\t\t\tcase string:\n\t\t\t\terr := ssh.AddAuthorizedKey(user, v)\n\t\t\t\tif err != nil {\n\t\t\t\t\tl.WithError(err).WithField(\"sshKeyConfig\", rk).WithField(\"sshKey\", v).Warn(\"Failed to authorize key\")\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\tcase []any:\n\t\t\t\tfor _, subK := range v {\n\t\t\t\t\tsk, ok := subK.(string)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\tl.WithField(\"sshKeyConfig\", rk).WithField(\"sshKey\", subK).Warn(\"Did not understand ssh key\")\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\terr := ssh.AddAuthorizedKey(user, sk)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tl.WithError(err).WithField(\"sshKeyConfig\", sk).Warn(\"Failed to authorize key\")\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\tdefault:\n\t\t\t\tl.WithField(\"sshKeyConfig\", rk).Warn(\"Authorized user is missing the keys field or was not understood\")\n\t\t\t}\n\t\t}\n\t} else {\n\t\tl.Info(\"no ssh users to authorize\")\n\t}\n\n\tvar runner func()\n\tif c.GetBool(\"sshd.enabled\", false) {\n\t\tssh.Stop()\n\t\trunner = func() {\n\t\t\tif err := ssh.Run(listen); err != nil {\n\t\t\t\tl.WithField(\"err\", err).Warn(\"Failed to run the SSH server\")\n\t\t\t}\n\t\t}\n\t} else {\n\t\tssh.Stop()\n\t}\n\n\treturn runner, nil\n}\n\nfunc attachCommands(l *logrus.Logger, c *config.C, ssh *sshd.SSHServer, f *Interface) {\n\tssh.RegisterCommand(&sshd.Command{\n\t\tName:             \"list-hostmap\",\n\t\tShortDescription: \"List all known previously connected hosts\",\n\t\tFlags: func() (*flag.FlagSet, any) {\n\t\t\tfl := flag.NewFlagSet(\"\", flag.ContinueOnError)\n\t\t\ts := sshListHostMapFlags{}\n\t\t\tfl.BoolVar(&s.Json, \"json\", false, \"outputs as json with more information\")\n\t\t\tfl.BoolVar(&s.Pretty, \"pretty\", false, \"pretty prints json, assumes -json\")\n\t\t\tfl.BoolVar(&s.ByIndex, \"by-index\", false, \"gets all hosts in the hostmap from the index table\")\n\t\t\treturn fl, &s\n\t\t},\n\t\tCallback: func(fs any, a []string, w sshd.StringWriter) error {\n\t\t\treturn sshListHostMap(f.hostMap, fs, w)\n\t\t},\n\t})\n\n\tssh.RegisterCommand(&sshd.Command{\n\t\tName:             \"list-pending-hostmap\",\n\t\tShortDescription: \"List all handshaking hosts\",\n\t\tFlags: func() (*flag.FlagSet, any) {\n\t\t\tfl := flag.NewFlagSet(\"\", flag.ContinueOnError)\n\t\t\ts := sshListHostMapFlags{}\n\t\t\tfl.BoolVar(&s.Json, \"json\", false, \"outputs as json with more information\")\n\t\t\tfl.BoolVar(&s.Pretty, \"pretty\", false, \"pretty prints json, assumes -json\")\n\t\t\tfl.BoolVar(&s.ByIndex, \"by-index\", false, \"gets all hosts in the hostmap from the index table\")\n\t\t\treturn fl, &s\n\t\t},\n\t\tCallback: func(fs any, a []string, w sshd.StringWriter) error {\n\t\t\treturn sshListHostMap(f.handshakeManager, fs, w)\n\t\t},\n\t})\n\n\tssh.RegisterCommand(&sshd.Command{\n\t\tName:             \"list-lighthouse-addrmap\",\n\t\tShortDescription: \"List all lighthouse map entries\",\n\t\tFlags: func() (*flag.FlagSet, any) {\n\t\t\tfl := flag.NewFlagSet(\"\", flag.ContinueOnError)\n\t\t\ts := sshListHostMapFlags{}\n\t\t\tfl.BoolVar(&s.Json, \"json\", false, \"outputs as json with more information\")\n\t\t\tfl.BoolVar(&s.Pretty, \"pretty\", false, \"pretty prints json, assumes -json\")\n\t\t\treturn fl, &s\n\t\t},\n\t\tCallback: func(fs any, a []string, w sshd.StringWriter) error {\n\t\t\treturn sshListLighthouseMap(f.lightHouse, fs, w)\n\t\t},\n\t})\n\n\tssh.RegisterCommand(&sshd.Command{\n\t\tName:             \"reload\",\n\t\tShortDescription: \"Reloads configuration from disk, same as sending HUP to the process\",\n\t\tCallback: func(fs any, a []string, w sshd.StringWriter) error {\n\t\t\treturn sshReload(c, w)\n\t\t},\n\t})\n\n\tssh.RegisterCommand(&sshd.Command{\n\t\tName:             \"start-cpu-profile\",\n\t\tShortDescription: \"Starts a cpu profile and write output to the provided file, ex: `cpu-profile.pb.gz`\",\n\t\tCallback:         sshStartCpuProfile,\n\t})\n\n\tssh.RegisterCommand(&sshd.Command{\n\t\tName:             \"stop-cpu-profile\",\n\t\tShortDescription: \"Stops a cpu profile and writes output to the previously provided file\",\n\t\tCallback: func(fs any, a []string, w sshd.StringWriter) error {\n\t\t\tpprof.StopCPUProfile()\n\t\t\treturn w.WriteLine(\"If a CPU profile was running it is now stopped\")\n\t\t},\n\t})\n\n\tssh.RegisterCommand(&sshd.Command{\n\t\tName:             \"save-heap-profile\",\n\t\tShortDescription: \"Saves a heap profile to the provided path, ex: `heap-profile.pb.gz`\",\n\t\tCallback:         sshGetHeapProfile,\n\t})\n\n\tssh.RegisterCommand(&sshd.Command{\n\t\tName:             \"mutex-profile-fraction\",\n\t\tShortDescription: \"Gets or sets runtime.SetMutexProfileFraction\",\n\t\tCallback:         sshMutexProfileFraction,\n\t})\n\n\tssh.RegisterCommand(&sshd.Command{\n\t\tName:             \"save-mutex-profile\",\n\t\tShortDescription: \"Saves a mutex profile to the provided path, ex: `mutex-profile.pb.gz`\",\n\t\tCallback:         sshGetMutexProfile,\n\t})\n\n\tssh.RegisterCommand(&sshd.Command{\n\t\tName:             \"log-level\",\n\t\tShortDescription: \"Gets or sets the current log level\",\n\t\tCallback: func(fs any, a []string, w sshd.StringWriter) error {\n\t\t\treturn sshLogLevel(l, fs, a, w)\n\t\t},\n\t})\n\n\tssh.RegisterCommand(&sshd.Command{\n\t\tName:             \"log-format\",\n\t\tShortDescription: \"Gets or sets the current log format\",\n\t\tCallback: func(fs any, a []string, w sshd.StringWriter) error {\n\t\t\treturn sshLogFormat(l, fs, a, w)\n\t\t},\n\t})\n\n\tssh.RegisterCommand(&sshd.Command{\n\t\tName:             \"version\",\n\t\tShortDescription: \"Prints the currently running version of nebula\",\n\t\tCallback: func(fs any, a []string, w sshd.StringWriter) error {\n\t\t\treturn sshVersion(f, fs, a, w)\n\t\t},\n\t})\n\n\tssh.RegisterCommand(&sshd.Command{\n\t\tName:             \"device-info\",\n\t\tShortDescription: \"Prints information about the network device.\",\n\t\tFlags: func() (*flag.FlagSet, any) {\n\t\t\tfl := flag.NewFlagSet(\"\", flag.ContinueOnError)\n\t\t\ts := sshDeviceInfoFlags{}\n\t\t\tfl.BoolVar(&s.Json, \"json\", false, \"outputs as json with more information\")\n\t\t\tfl.BoolVar(&s.Pretty, \"pretty\", false, \"pretty prints json, assumes -json\")\n\t\t\treturn fl, &s\n\t\t},\n\t\tCallback: func(fs any, a []string, w sshd.StringWriter) error {\n\t\t\treturn sshDeviceInfo(f, fs, w)\n\t\t},\n\t})\n\n\tssh.RegisterCommand(&sshd.Command{\n\t\tName:             \"print-cert\",\n\t\tShortDescription: \"Prints the current certificate being used or the certificate for the provided vpn addr\",\n\t\tFlags: func() (*flag.FlagSet, any) {\n\t\t\tfl := flag.NewFlagSet(\"\", flag.ContinueOnError)\n\t\t\ts := sshPrintCertFlags{}\n\t\t\tfl.BoolVar(&s.Json, \"json\", false, \"outputs as json\")\n\t\t\tfl.BoolVar(&s.Pretty, \"pretty\", false, \"pretty prints json, assumes -json\")\n\t\t\tfl.BoolVar(&s.Raw, \"raw\", false, \"raw prints the PEM encoded certificate, not compatible with -json or -pretty\")\n\t\t\treturn fl, &s\n\t\t},\n\t\tCallback: func(fs any, a []string, w sshd.StringWriter) error {\n\t\t\treturn sshPrintCert(f, fs, a, w)\n\t\t},\n\t})\n\n\tssh.RegisterCommand(&sshd.Command{\n\t\tName:             \"print-tunnel\",\n\t\tShortDescription: \"Prints json details about a tunnel for the provided vpn addr\",\n\t\tFlags: func() (*flag.FlagSet, any) {\n\t\t\tfl := flag.NewFlagSet(\"\", flag.ContinueOnError)\n\t\t\ts := sshPrintTunnelFlags{}\n\t\t\tfl.BoolVar(&s.Pretty, \"pretty\", false, \"pretty prints json\")\n\t\t\treturn fl, &s\n\t\t},\n\t\tCallback: func(fs any, a []string, w sshd.StringWriter) error {\n\t\t\treturn sshPrintTunnel(f, fs, a, w)\n\t\t},\n\t})\n\n\tssh.RegisterCommand(&sshd.Command{\n\t\tName:             \"print-relays\",\n\t\tShortDescription: \"Prints json details about all relay info\",\n\t\tFlags: func() (*flag.FlagSet, any) {\n\t\t\tfl := flag.NewFlagSet(\"\", flag.ContinueOnError)\n\t\t\ts := sshPrintTunnelFlags{}\n\t\t\tfl.BoolVar(&s.Pretty, \"pretty\", false, \"pretty prints json\")\n\t\t\treturn fl, &s\n\t\t},\n\t\tCallback: func(fs any, a []string, w sshd.StringWriter) error {\n\t\t\treturn sshPrintRelays(f, fs, a, w)\n\t\t},\n\t})\n\n\tssh.RegisterCommand(&sshd.Command{\n\t\tName:             \"change-remote\",\n\t\tShortDescription: \"Changes the remote address used in the tunnel for the provided vpn addr\",\n\t\tFlags: func() (*flag.FlagSet, any) {\n\t\t\tfl := flag.NewFlagSet(\"\", flag.ContinueOnError)\n\t\t\ts := sshChangeRemoteFlags{}\n\t\t\tfl.StringVar(&s.Address, \"address\", \"\", \"The new remote address, ip:port\")\n\t\t\treturn fl, &s\n\t\t},\n\t\tCallback: func(fs any, a []string, w sshd.StringWriter) error {\n\t\t\treturn sshChangeRemote(f, fs, a, w)\n\t\t},\n\t})\n\n\tssh.RegisterCommand(&sshd.Command{\n\t\tName:             \"close-tunnel\",\n\t\tShortDescription: \"Closes a tunnel for the provided vpn addr\",\n\t\tFlags: func() (*flag.FlagSet, any) {\n\t\t\tfl := flag.NewFlagSet(\"\", flag.ContinueOnError)\n\t\t\ts := sshCloseTunnelFlags{}\n\t\t\tfl.BoolVar(&s.LocalOnly, \"local-only\", false, \"Disables notifying the remote that the tunnel is shutting down\")\n\t\t\treturn fl, &s\n\t\t},\n\t\tCallback: func(fs any, a []string, w sshd.StringWriter) error {\n\t\t\treturn sshCloseTunnel(f, fs, a, w)\n\t\t},\n\t})\n\n\tssh.RegisterCommand(&sshd.Command{\n\t\tName:             \"create-tunnel\",\n\t\tShortDescription: \"Creates a tunnel for the provided vpn address\",\n\t\tHelp:             \"The lighthouses will be queried for real addresses but you can provide one as well.\",\n\t\tFlags: func() (*flag.FlagSet, any) {\n\t\t\tfl := flag.NewFlagSet(\"\", flag.ContinueOnError)\n\t\t\ts := sshCreateTunnelFlags{}\n\t\t\tfl.StringVar(&s.Address, \"address\", \"\", \"Optionally provide a real remote address, ip:port \")\n\t\t\treturn fl, &s\n\t\t},\n\t\tCallback: func(fs any, a []string, w sshd.StringWriter) error {\n\t\t\treturn sshCreateTunnel(f, fs, a, w)\n\t\t},\n\t})\n\n\tssh.RegisterCommand(&sshd.Command{\n\t\tName:             \"query-lighthouse\",\n\t\tShortDescription: \"Query the lighthouses for the provided vpn address\",\n\t\tHelp:             \"This command is asynchronous. Only currently known udp addresses will be printed.\",\n\t\tCallback: func(fs any, a []string, w sshd.StringWriter) error {\n\t\t\treturn sshQueryLighthouse(f, fs, a, w)\n\t\t},\n\t})\n}\n\nfunc sshListHostMap(hl controlHostLister, a any, w sshd.StringWriter) error {\n\tfs, ok := a.(*sshListHostMapFlags)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tvar hm []ControlHostInfo\n\tif fs.ByIndex {\n\t\thm = listHostMapIndexes(hl)\n\t} else {\n\t\thm = listHostMapHosts(hl)\n\t}\n\n\tsort.Slice(hm, func(i, j int) bool {\n\t\treturn hm[i].VpnAddrs[0].Compare(hm[j].VpnAddrs[0]) < 0\n\t})\n\n\tif fs.Json || fs.Pretty {\n\t\tjs := json.NewEncoder(w.GetWriter())\n\t\tif fs.Pretty {\n\t\t\tjs.SetIndent(\"\", \"    \")\n\t\t}\n\n\t\terr := js.Encode(hm)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\n\t} else {\n\t\tfor _, v := range hm {\n\t\t\terr := w.WriteLine(fmt.Sprintf(\"%s: %s\", v.VpnAddrs, v.RemoteAddrs))\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc sshListLighthouseMap(lightHouse *LightHouse, a any, w sshd.StringWriter) error {\n\tfs, ok := a.(*sshListHostMapFlags)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\ttype lighthouseInfo struct {\n\t\tVpnAddr string    `json:\"vpnAddr\"`\n\t\tAddrs   *CacheMap `json:\"addrs\"`\n\t}\n\n\tlightHouse.RLock()\n\taddrMap := make([]lighthouseInfo, len(lightHouse.addrMap))\n\tx := 0\n\tfor k, v := range lightHouse.addrMap {\n\t\taddrMap[x] = lighthouseInfo{\n\t\t\tVpnAddr: k.String(),\n\t\t\tAddrs:   v.CopyCache(),\n\t\t}\n\t\tx++\n\t}\n\tlightHouse.RUnlock()\n\n\tsort.Slice(addrMap, func(i, j int) bool {\n\t\treturn strings.Compare(addrMap[i].VpnAddr, addrMap[j].VpnAddr) < 0\n\t})\n\n\tif fs.Json || fs.Pretty {\n\t\tjs := json.NewEncoder(w.GetWriter())\n\t\tif fs.Pretty {\n\t\t\tjs.SetIndent(\"\", \"    \")\n\t\t}\n\n\t\terr := js.Encode(addrMap)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\n\t} else {\n\t\tfor _, v := range addrMap {\n\t\t\tb, err := json.Marshal(v.Addrs)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terr = w.WriteLine(fmt.Sprintf(\"%s: %s\", v.VpnAddr, string(b)))\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc sshStartCpuProfile(fs any, a []string, w sshd.StringWriter) error {\n\tif len(a) == 0 {\n\t\terr := w.WriteLine(\"No path to write profile provided\")\n\t\treturn err\n\t}\n\n\tfile, err := os.Create(a[0])\n\tif err != nil {\n\t\terr = w.WriteLine(fmt.Sprintf(\"Unable to create profile file: %s\", err))\n\t\treturn err\n\t}\n\n\terr = pprof.StartCPUProfile(file)\n\tif err != nil {\n\t\terr = w.WriteLine(fmt.Sprintf(\"Unable to start cpu profile: %s\", err))\n\t\treturn err\n\t}\n\n\terr = w.WriteLine(fmt.Sprintf(\"Started cpu profile, issue stop-cpu-profile to write the output to %s\", a))\n\treturn err\n}\n\nfunc sshVersion(ifce *Interface, fs any, a []string, w sshd.StringWriter) error {\n\treturn w.WriteLine(fmt.Sprintf(\"%s\", ifce.version))\n}\n\nfunc sshQueryLighthouse(ifce *Interface, fs any, a []string, w sshd.StringWriter) error {\n\tif len(a) == 0 {\n\t\treturn w.WriteLine(\"No vpn address was provided\")\n\t}\n\n\tvpnAddr, err := netip.ParseAddr(a[0])\n\tif err != nil {\n\t\treturn w.WriteLine(fmt.Sprintf(\"The provided vpn address could not be parsed: %s\", a[0]))\n\t}\n\n\tif !vpnAddr.IsValid() {\n\t\treturn w.WriteLine(fmt.Sprintf(\"The provided vpn address could not be parsed: %s\", a[0]))\n\t}\n\n\tvar cm *CacheMap\n\trl := ifce.lightHouse.Query(vpnAddr)\n\tif rl != nil {\n\t\tcm = rl.CopyCache()\n\t}\n\treturn json.NewEncoder(w.GetWriter()).Encode(cm)\n}\n\nfunc sshCloseTunnel(ifce *Interface, fs any, a []string, w sshd.StringWriter) error {\n\tflags, ok := fs.(*sshCloseTunnelFlags)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tif len(a) == 0 {\n\t\treturn w.WriteLine(\"No vpn address was provided\")\n\t}\n\n\tvpnAddr, err := netip.ParseAddr(a[0])\n\tif err != nil {\n\t\treturn w.WriteLine(fmt.Sprintf(\"The provided vpn address could not be parsed: %s\", a[0]))\n\t}\n\n\tif !vpnAddr.IsValid() {\n\t\treturn w.WriteLine(fmt.Sprintf(\"The provided vpn address could not be parsed: %s\", a[0]))\n\t}\n\n\thostInfo := ifce.hostMap.QueryVpnAddr(vpnAddr)\n\tif hostInfo == nil {\n\t\treturn w.WriteLine(fmt.Sprintf(\"Could not find tunnel for vpn address: %v\", a[0]))\n\t}\n\n\tif !flags.LocalOnly {\n\t\tifce.send(\n\t\t\theader.CloseTunnel,\n\t\t\t0,\n\t\t\thostInfo.ConnectionState,\n\t\t\thostInfo,\n\t\t\t[]byte{},\n\t\t\tmake([]byte, 12, 12),\n\t\t\tmake([]byte, mtu),\n\t\t)\n\t}\n\n\tifce.closeTunnel(hostInfo)\n\treturn w.WriteLine(\"Closed\")\n}\n\nfunc sshCreateTunnel(ifce *Interface, fs any, a []string, w sshd.StringWriter) error {\n\tflags, ok := fs.(*sshCreateTunnelFlags)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tif len(a) == 0 {\n\t\treturn w.WriteLine(\"No vpn address was provided\")\n\t}\n\n\tvpnAddr, err := netip.ParseAddr(a[0])\n\tif err != nil {\n\t\treturn w.WriteLine(fmt.Sprintf(\"The provided vpn address could not be parsed: %s\", a[0]))\n\t}\n\n\tif !vpnAddr.IsValid() {\n\t\treturn w.WriteLine(fmt.Sprintf(\"The provided vpn address could not be parsed: %s\", a[0]))\n\t}\n\n\thostInfo := ifce.hostMap.QueryVpnAddr(vpnAddr)\n\tif hostInfo != nil {\n\t\treturn w.WriteLine(fmt.Sprintf(\"Tunnel already exists\"))\n\t}\n\n\thostInfo = ifce.handshakeManager.QueryVpnAddr(vpnAddr)\n\tif hostInfo != nil {\n\t\treturn w.WriteLine(fmt.Sprintf(\"Tunnel already handshaking\"))\n\t}\n\n\tvar addr netip.AddrPort\n\tif flags.Address != \"\" {\n\t\taddr, err = netip.ParseAddrPort(flags.Address)\n\t\tif err != nil {\n\t\t\treturn w.WriteLine(\"Address could not be parsed\")\n\t\t}\n\t}\n\n\thostInfo = ifce.handshakeManager.StartHandshake(vpnAddr, nil)\n\tif addr.IsValid() {\n\t\thostInfo.SetRemote(addr)\n\t}\n\n\treturn w.WriteLine(\"Created\")\n}\n\nfunc sshChangeRemote(ifce *Interface, fs any, a []string, w sshd.StringWriter) error {\n\tflags, ok := fs.(*sshChangeRemoteFlags)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tif len(a) == 0 {\n\t\treturn w.WriteLine(\"No vpn address was provided\")\n\t}\n\n\tif flags.Address == \"\" {\n\t\treturn w.WriteLine(\"No address was provided\")\n\t}\n\n\taddr, err := netip.ParseAddrPort(flags.Address)\n\tif err != nil {\n\t\treturn w.WriteLine(\"Address could not be parsed\")\n\t}\n\n\tvpnAddr, err := netip.ParseAddr(a[0])\n\tif err != nil {\n\t\treturn w.WriteLine(fmt.Sprintf(\"The provided vpn address could not be parsed: %s\", a[0]))\n\t}\n\n\tif !vpnAddr.IsValid() {\n\t\treturn w.WriteLine(fmt.Sprintf(\"The provided vpn address could not be parsed: %s\", a[0]))\n\t}\n\n\thostInfo := ifce.hostMap.QueryVpnAddr(vpnAddr)\n\tif hostInfo == nil {\n\t\treturn w.WriteLine(fmt.Sprintf(\"Could not find tunnel for vpn address: %v\", a[0]))\n\t}\n\n\thostInfo.SetRemote(addr)\n\treturn w.WriteLine(\"Changed\")\n}\n\nfunc sshGetHeapProfile(fs any, a []string, w sshd.StringWriter) error {\n\tif len(a) == 0 {\n\t\treturn w.WriteLine(\"No path to write profile provided\")\n\t}\n\n\tfile, err := os.Create(a[0])\n\tif err != nil {\n\t\terr = w.WriteLine(fmt.Sprintf(\"Unable to create profile file: %s\", err))\n\t\treturn err\n\t}\n\n\terr = pprof.WriteHeapProfile(file)\n\tif err != nil {\n\t\terr = w.WriteLine(fmt.Sprintf(\"Unable to write profile: %s\", err))\n\t\treturn err\n\t}\n\n\terr = w.WriteLine(fmt.Sprintf(\"Mem profile created at %s\", a))\n\treturn err\n}\n\nfunc sshMutexProfileFraction(fs any, a []string, w sshd.StringWriter) error {\n\tif len(a) == 0 {\n\t\trate := runtime.SetMutexProfileFraction(-1)\n\t\treturn w.WriteLine(fmt.Sprintf(\"Current value: %d\", rate))\n\t}\n\n\tnewRate, err := strconv.Atoi(a[0])\n\tif err != nil {\n\t\treturn w.WriteLine(fmt.Sprintf(\"Invalid argument: %s\", a[0]))\n\t}\n\n\toldRate := runtime.SetMutexProfileFraction(newRate)\n\treturn w.WriteLine(fmt.Sprintf(\"New value: %d. Old value: %d\", newRate, oldRate))\n}\n\nfunc sshGetMutexProfile(fs any, a []string, w sshd.StringWriter) error {\n\tif len(a) == 0 {\n\t\treturn w.WriteLine(\"No path to write profile provided\")\n\t}\n\n\tfile, err := os.Create(a[0])\n\tif err != nil {\n\t\treturn w.WriteLine(fmt.Sprintf(\"Unable to create profile file: %s\", err))\n\t}\n\tdefer file.Close()\n\n\tmutexProfile := pprof.Lookup(\"mutex\")\n\tif mutexProfile == nil {\n\t\treturn w.WriteLine(\"Unable to get pprof.Lookup(\\\"mutex\\\")\")\n\t}\n\n\terr = mutexProfile.WriteTo(file, 0)\n\tif err != nil {\n\t\treturn w.WriteLine(fmt.Sprintf(\"Unable to write profile: %s\", err))\n\t}\n\n\treturn w.WriteLine(fmt.Sprintf(\"Mutex profile created at %s\", a))\n}\n\nfunc sshLogLevel(l *logrus.Logger, fs any, a []string, w sshd.StringWriter) error {\n\tif len(a) == 0 {\n\t\treturn w.WriteLine(fmt.Sprintf(\"Log level is: %s\", l.Level))\n\t}\n\n\tlevel, err := logrus.ParseLevel(a[0])\n\tif err != nil {\n\t\treturn w.WriteLine(fmt.Sprintf(\"Unknown log level %s. Possible log levels: %s\", a, logrus.AllLevels))\n\t}\n\n\tl.SetLevel(level)\n\treturn w.WriteLine(fmt.Sprintf(\"Log level is: %s\", l.Level))\n}\n\nfunc sshLogFormat(l *logrus.Logger, fs any, a []string, w sshd.StringWriter) error {\n\tif len(a) == 0 {\n\t\treturn w.WriteLine(fmt.Sprintf(\"Log format is: %s\", reflect.TypeOf(l.Formatter)))\n\t}\n\n\tlogFormat := strings.ToLower(a[0])\n\tswitch logFormat {\n\tcase \"text\":\n\t\tl.Formatter = &logrus.TextFormatter{}\n\tcase \"json\":\n\t\tl.Formatter = &logrus.JSONFormatter{}\n\tdefault:\n\t\treturn fmt.Errorf(\"unknown log format `%s`. possible formats: %s\", logFormat, []string{\"text\", \"json\"})\n\t}\n\n\treturn w.WriteLine(fmt.Sprintf(\"Log format is: %s\", reflect.TypeOf(l.Formatter)))\n}\n\nfunc sshPrintCert(ifce *Interface, fs any, a []string, w sshd.StringWriter) error {\n\targs, ok := fs.(*sshPrintCertFlags)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tcert := ifce.pki.getCertState().GetDefaultCertificate()\n\tif len(a) > 0 {\n\t\tvpnAddr, err := netip.ParseAddr(a[0])\n\t\tif err != nil {\n\t\t\treturn w.WriteLine(fmt.Sprintf(\"The provided vpn addr could not be parsed: %s\", a[0]))\n\t\t}\n\n\t\tif !vpnAddr.IsValid() {\n\t\t\treturn w.WriteLine(fmt.Sprintf(\"The provided vpn addr could not be parsed: %s\", a[0]))\n\t\t}\n\n\t\thostInfo := ifce.hostMap.QueryVpnAddr(vpnAddr)\n\t\tif hostInfo == nil {\n\t\t\treturn w.WriteLine(fmt.Sprintf(\"Could not find tunnel for vpn addr: %v\", a[0]))\n\t\t}\n\n\t\tcert = hostInfo.GetCert().Certificate\n\t}\n\n\tif args.Json || args.Pretty {\n\t\tb, err := cert.MarshalJSON()\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\n\t\tif args.Pretty {\n\t\t\tbuf := new(bytes.Buffer)\n\t\t\terr := json.Indent(buf, b, \"\", \"    \")\n\t\t\tb = buf.Bytes()\n\t\t\tif err != nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\n\t\treturn w.WriteBytes(b)\n\t}\n\n\tif args.Raw {\n\t\tb, err := cert.MarshalPEM()\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\n\t\treturn w.WriteBytes(b)\n\t}\n\n\treturn w.WriteLine(cert.String())\n}\n\nfunc sshPrintRelays(ifce *Interface, fs any, a []string, w sshd.StringWriter) error {\n\targs, ok := fs.(*sshPrintTunnelFlags)\n\tif !ok {\n\t\tw.WriteLine(fmt.Sprintf(\"sshPrintRelays failed to convert args type\"))\n\t\treturn nil\n\t}\n\n\trelays := map[uint32]*HostInfo{}\n\tifce.hostMap.Lock()\n\tmaps.Copy(relays, ifce.hostMap.Relays)\n\tifce.hostMap.Unlock()\n\n\ttype RelayFor struct {\n\t\tError          error\n\t\tType           string\n\t\tState          string\n\t\tPeerAddr       netip.Addr\n\t\tLocalIndex     uint32\n\t\tRemoteIndex    uint32\n\t\tRelayedThrough []netip.Addr\n\t}\n\n\ttype RelayOutput struct {\n\t\tNebulaAddr    netip.Addr\n\t\tRelayForAddrs []RelayFor\n\t}\n\n\ttype CmdOutput struct {\n\t\tRelays []*RelayOutput\n\t}\n\n\tco := CmdOutput{}\n\n\tenc := json.NewEncoder(w.GetWriter())\n\n\tif args.Pretty {\n\t\tenc.SetIndent(\"\", \"    \")\n\t}\n\n\tfor k, v := range relays {\n\t\tro := RelayOutput{NebulaAddr: v.vpnAddrs[0]}\n\t\tco.Relays = append(co.Relays, &ro)\n\t\trelayHI := ifce.hostMap.QueryVpnAddr(v.vpnAddrs[0])\n\t\tif relayHI == nil {\n\t\t\tro.RelayForAddrs = append(ro.RelayForAddrs, RelayFor{Error: errors.New(\"could not find hostinfo\")})\n\t\t\tcontinue\n\t\t}\n\t\tfor _, vpnAddr := range relayHI.relayState.CopyRelayForIps() {\n\t\t\trf := RelayFor{Error: nil}\n\t\t\tr, ok := relayHI.relayState.GetRelayForByAddr(vpnAddr)\n\t\t\tif ok {\n\t\t\t\tt := \"\"\n\t\t\t\tswitch r.Type {\n\t\t\t\tcase ForwardingType:\n\t\t\t\t\tt = \"forwarding\"\n\t\t\t\tcase TerminalType:\n\t\t\t\t\tt = \"terminal\"\n\t\t\t\tdefault:\n\t\t\t\t\tt = \"unknown\"\n\t\t\t\t}\n\n\t\t\t\ts := \"\"\n\t\t\t\tswitch r.State {\n\t\t\t\tcase Requested:\n\t\t\t\t\ts = \"requested\"\n\t\t\t\tcase Established:\n\t\t\t\t\ts = \"established\"\n\t\t\t\tdefault:\n\t\t\t\t\ts = \"unknown\"\n\t\t\t\t}\n\n\t\t\t\trf.LocalIndex = r.LocalIndex\n\t\t\t\trf.RemoteIndex = r.RemoteIndex\n\t\t\t\trf.PeerAddr = r.PeerAddr\n\t\t\t\trf.Type = t\n\t\t\t\trf.State = s\n\t\t\t\tif rf.LocalIndex != k {\n\t\t\t\t\trf.Error = fmt.Errorf(\"hostmap LocalIndex '%v' does not match RelayState LocalIndex\", k)\n\t\t\t\t}\n\t\t\t}\n\t\t\trelayedHI := ifce.hostMap.QueryVpnAddr(vpnAddr)\n\t\t\tif relayedHI != nil {\n\t\t\t\trf.RelayedThrough = append(rf.RelayedThrough, relayedHI.relayState.CopyRelayIps()...)\n\t\t\t}\n\n\t\t\tro.RelayForAddrs = append(ro.RelayForAddrs, rf)\n\t\t}\n\t}\n\terr := enc.Encode(co)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc sshPrintTunnel(ifce *Interface, fs any, a []string, w sshd.StringWriter) error {\n\targs, ok := fs.(*sshPrintTunnelFlags)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tif len(a) == 0 {\n\t\treturn w.WriteLine(\"No vpn address was provided\")\n\t}\n\n\tvpnAddr, err := netip.ParseAddr(a[0])\n\tif err != nil {\n\t\treturn w.WriteLine(fmt.Sprintf(\"The provided vpn addr could not be parsed: %s\", a[0]))\n\t}\n\n\tif !vpnAddr.IsValid() {\n\t\treturn w.WriteLine(fmt.Sprintf(\"The provided vpn addr could not be parsed: %s\", a[0]))\n\t}\n\n\thostInfo := ifce.hostMap.QueryVpnAddr(vpnAddr)\n\tif hostInfo == nil {\n\t\treturn w.WriteLine(fmt.Sprintf(\"Could not find tunnel for vpn addr: %v\", a[0]))\n\t}\n\n\tenc := json.NewEncoder(w.GetWriter())\n\tif args.Pretty {\n\t\tenc.SetIndent(\"\", \"    \")\n\t}\n\n\treturn enc.Encode(copyHostInfo(hostInfo, ifce.hostMap.GetPreferredRanges()))\n}\n\nfunc sshDeviceInfo(ifce *Interface, fs any, w sshd.StringWriter) error {\n\n\tdata := struct {\n\t\tName string         `json:\"name\"`\n\t\tCidr []netip.Prefix `json:\"cidr\"`\n\t}{\n\t\tName: ifce.inside.Name(),\n\t\tCidr: make([]netip.Prefix, len(ifce.inside.Networks())),\n\t}\n\n\tcopy(data.Cidr, ifce.inside.Networks())\n\n\tflags, ok := fs.(*sshDeviceInfoFlags)\n\tif !ok {\n\t\treturn fmt.Errorf(\"internal error: expected flags to be sshDeviceInfoFlags but was %+v\", fs)\n\t}\n\n\tif flags.Json || flags.Pretty {\n\t\tjs := json.NewEncoder(w.GetWriter())\n\t\tif flags.Pretty {\n\t\t\tjs.SetIndent(\"\", \"    \")\n\t\t}\n\n\t\treturn js.Encode(data)\n\t} else {\n\t\treturn w.WriteLine(fmt.Sprintf(\"name=%v cidr=%v\", data.Name, data.Cidr))\n\t}\n}\n\nfunc sshReload(c *config.C, w sshd.StringWriter) error {\n\terr := w.WriteLine(\"Reloading config\")\n\tc.ReloadConfig()\n\treturn err\n}\n"
  },
  {
    "path": "sshd/command.go",
    "content": "package sshd\n\nimport (\n\t\"errors\"\n\t\"flag\"\n\t\"fmt\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/armon/go-radix\"\n)\n\n// CommandFlags is a function called before help or command execution to parse command line flags\n// It should return a flag.FlagSet instance and a pointer to the struct that will contain parsed flags\ntype CommandFlags func() (*flag.FlagSet, any)\n\n// CommandCallback is the function called when your command should execute.\n// fs will be a a pointer to the struct provided by Command.Flags callback, if there was one. -h and -help are reserved\n// and handled automatically for you.\n// a will be any unconsumed arguments, if no Command.Flags was available this will be all the flags passed in.\n// w is the writer to use when sending messages back to the client.\n// If an error is returned by the callback it is logged locally, the callback should handle messaging errors to the user\n// where appropriate\ntype CommandCallback func(fs any, a []string, w StringWriter) error\n\ntype Command struct {\n\tName             string\n\tShortDescription string\n\tHelp             string\n\tFlags            CommandFlags\n\tCallback         CommandCallback\n}\n\nfunc execCommand(c *Command, args []string, w StringWriter) error {\n\tvar (\n\t\tfl *flag.FlagSet\n\t\tfs any\n\t)\n\n\tif c.Flags != nil {\n\t\tfl, fs = c.Flags()\n\t\tif fl != nil {\n\t\t\t// SetOutput() here in case fl.Parse dumps usage.\n\t\t\tfl.SetOutput(w.GetWriter())\n\t\t\terr := fl.Parse(args)\n\t\t\tif err != nil {\n\t\t\t\t// fl.Parse has dumped error information to the user via the w writer.\n\t\t\t\treturn err\n\t\t\t}\n\t\t\targs = fl.Args()\n\t\t}\n\t}\n\n\treturn c.Callback(fs, args, w)\n}\n\nfunc dumpCommands(c *radix.Tree, w StringWriter) {\n\terr := w.WriteLine(\"Available commands:\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\tcmds := make([]string, 0)\n\tfor _, l := range allCommands(c) {\n\t\tcmds = append(cmds, fmt.Sprintf(\"%s - %s\", l.Name, l.ShortDescription))\n\t}\n\n\tsort.Strings(cmds)\n\t_ = w.Write(strings.Join(cmds, \"\\n\") + \"\\n\\n\")\n}\n\nfunc lookupCommand(c *radix.Tree, sCmd string) (*Command, error) {\n\tcmd, ok := c.Get(sCmd)\n\tif !ok {\n\t\treturn nil, nil\n\t}\n\n\tcommand, ok := cmd.(*Command)\n\tif !ok {\n\t\treturn nil, errors.New(\"failed to cast command\")\n\t}\n\n\treturn command, nil\n}\n\nfunc matchCommand(c *radix.Tree, cmd string) []string {\n\tcmds := make([]string, 0)\n\tc.WalkPrefix(cmd, func(found string, v any) bool {\n\t\tcmds = append(cmds, found)\n\t\treturn false\n\t})\n\tsort.Strings(cmds)\n\treturn cmds\n}\n\nfunc allCommands(c *radix.Tree) []*Command {\n\tcmds := make([]*Command, 0)\n\tc.WalkPrefix(\"\", func(found string, v any) bool {\n\t\tcmd, ok := v.(*Command)\n\t\tif ok {\n\t\t\tcmds = append(cmds, cmd)\n\t\t}\n\t\treturn false\n\t})\n\treturn cmds\n}\n\nfunc helpCallback(commands *radix.Tree, a []string, w StringWriter) (err error) {\n\t// Just typed help\n\tif len(a) == 0 {\n\t\tdumpCommands(commands, w)\n\t\treturn nil\n\t}\n\n\t// We are printing a specific commands help text\n\tcmd, err := lookupCommand(commands, a[0])\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif cmd != nil {\n\t\terr = w.WriteLine(fmt.Sprintf(\"%s - %s\", cmd.Name, cmd.ShortDescription))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif cmd.Help != \"\" {\n\t\t\terr = w.WriteLine(fmt.Sprintf(\"  %s\", cmd.Help))\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\tif cmd.Flags != nil {\n\t\t\tfs, _ := cmd.Flags()\n\t\t\tif fs != nil {\n\t\t\t\tfs.SetOutput(w.GetWriter())\n\t\t\t\tfs.PrintDefaults()\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t}\n\n\terr = w.WriteLine(\"Command not available \" + a[0])\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc checkHelpArgs(args []string) bool {\n\tfor _, a := range args {\n\t\tif a == \"-h\" || a == \"-help\" {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n"
  },
  {
    "path": "sshd/server.go",
    "content": "package sshd\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"sync\"\n\n\t\"github.com/armon/go-radix\"\n\t\"github.com/sirupsen/logrus\"\n\t\"golang.org/x/crypto/ssh\"\n)\n\ntype SSHServer struct {\n\tconfig *ssh.ServerConfig\n\tl      *logrus.Entry\n\n\tcertChecker *ssh.CertChecker\n\n\t// Map of user -> authorized keys\n\ttrustedKeys map[string]map[string]bool\n\ttrustedCAs  []ssh.PublicKey\n\n\t// List of available commands\n\thelpCommand *Command\n\tcommands    *radix.Tree\n\tlistener    net.Listener\n\n\t// Locks the conns/counter to avoid concurrent map access\n\tconnsLock sync.Mutex\n\tconns     map[int]*session\n\tcounter   int\n}\n\n// NewSSHServer creates a new ssh server rigged with default commands and prepares to listen\nfunc NewSSHServer(l *logrus.Entry) (*SSHServer, error) {\n\n\ts := &SSHServer{\n\t\ttrustedKeys: make(map[string]map[string]bool),\n\t\tl:           l,\n\t\tcommands:    radix.New(),\n\t\tconns:       make(map[int]*session),\n\t}\n\n\tcc := ssh.CertChecker{\n\t\tIsUserAuthority: func(auth ssh.PublicKey) bool {\n\t\t\tfor _, ca := range s.trustedCAs {\n\t\t\t\tif bytes.Equal(ca.Marshal(), auth.Marshal()) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn false\n\t\t},\n\t\tUserKeyFallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {\n\t\t\tpk := string(pubKey.Marshal())\n\t\t\tfp := ssh.FingerprintSHA256(pubKey)\n\n\t\t\ttk, ok := s.trustedKeys[c.User()]\n\t\t\tif !ok {\n\t\t\t\treturn nil, fmt.Errorf(\"unknown user %s\", c.User())\n\t\t\t}\n\n\t\t\t_, ok = tk[pk]\n\t\t\tif !ok {\n\t\t\t\treturn nil, fmt.Errorf(\"unknown public key for %s (%s)\", c.User(), fp)\n\t\t\t}\n\n\t\t\treturn &ssh.Permissions{\n\t\t\t\t// Record the public key used for authentication.\n\t\t\t\tExtensions: map[string]string{\n\t\t\t\t\t\"fp\":   fp,\n\t\t\t\t\t\"user\": c.User(),\n\t\t\t\t},\n\t\t\t}, nil\n\n\t\t},\n\t}\n\n\ts.config = &ssh.ServerConfig{\n\t\tPublicKeyCallback: cc.Authenticate,\n\t\tServerVersion:     fmt.Sprintf(\"SSH-2.0-Nebula???\"),\n\t}\n\n\ts.RegisterCommand(&Command{\n\t\tName:             \"help\",\n\t\tShortDescription: \"prints available commands or help <command> for specific usage info\",\n\t\tCallback: func(a any, args []string, w StringWriter) error {\n\t\t\treturn helpCallback(s.commands, args, w)\n\t\t},\n\t})\n\n\treturn s, nil\n}\n\nfunc (s *SSHServer) SetHostKey(hostPrivateKey []byte) error {\n\tprivate, err := ssh.ParsePrivateKey(hostPrivateKey)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to parse private key: %s\", err)\n\t}\n\n\ts.config.AddHostKey(private)\n\treturn nil\n}\n\nfunc (s *SSHServer) ClearTrustedCAs() {\n\ts.trustedCAs = []ssh.PublicKey{}\n}\n\nfunc (s *SSHServer) ClearAuthorizedKeys() {\n\ts.trustedKeys = make(map[string]map[string]bool)\n}\n\n// AddTrustedCA adds a trusted CA for user certificates\nfunc (s *SSHServer) AddTrustedCA(pubKey string) error {\n\tpk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(pubKey))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.trustedCAs = append(s.trustedCAs, pk)\n\ts.l.WithField(\"sshKey\", pubKey).Info(\"Trusted CA key\")\n\treturn nil\n}\n\n// AddAuthorizedKey adds an ssh public key for a user\nfunc (s *SSHServer) AddAuthorizedKey(user, pubKey string) error {\n\tpk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(pubKey))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttk, ok := s.trustedKeys[user]\n\tif !ok {\n\t\ttk = make(map[string]bool)\n\t\ts.trustedKeys[user] = tk\n\t}\n\n\ttk[string(pk.Marshal())] = true\n\ts.l.WithField(\"sshKey\", pubKey).WithField(\"sshUser\", user).Info(\"Authorized ssh key\")\n\treturn nil\n}\n\n// RegisterCommand adds a command that can be run by a user, by default only `help` is available\nfunc (s *SSHServer) RegisterCommand(c *Command) {\n\ts.commands.Insert(c.Name, c)\n}\n\n// Run begins listening and accepting connections\nfunc (s *SSHServer) Run(addr string) error {\n\tvar err error\n\ts.listener, err = net.Listen(\"tcp\", addr)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.l.WithField(\"sshListener\", addr).Info(\"SSH server is listening\")\n\n\t// Run loops until there is an error\n\ts.run()\n\ts.closeSessions()\n\n\ts.l.Info(\"SSH server stopped listening\")\n\t// We don't return an error because run logs for us\n\treturn nil\n}\n\nfunc (s *SSHServer) run() {\n\tfor {\n\t\tc, err := s.listener.Accept()\n\t\tif err != nil {\n\t\t\tif !errors.Is(err, net.ErrClosed) {\n\t\t\t\ts.l.WithError(err).Warn(\"Error in listener, shutting down\")\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tconn, chans, reqs, err := ssh.NewServerConn(c, s.config)\n\t\tfp := \"\"\n\t\tif conn != nil {\n\t\t\tfp = conn.Permissions.Extensions[\"fp\"]\n\t\t}\n\n\t\tif err != nil {\n\t\t\tl := s.l.WithError(err).WithField(\"remoteAddress\", c.RemoteAddr())\n\t\t\tif conn != nil {\n\t\t\t\tl = l.WithField(\"sshUser\", conn.User())\n\t\t\t\tconn.Close()\n\t\t\t}\n\t\t\tif fp != \"\" {\n\t\t\t\tl = l.WithField(\"sshFingerprint\", fp)\n\t\t\t}\n\t\t\tl.Warn(\"failed to handshake\")\n\t\t\tcontinue\n\t\t}\n\n\t\tl := s.l.WithField(\"sshUser\", conn.User())\n\t\tl.WithField(\"remoteAddress\", c.RemoteAddr()).WithField(\"sshFingerprint\", fp).Info(\"ssh user logged in\")\n\n\t\tsession := NewSession(s.commands, conn, chans, l.WithField(\"subsystem\", \"sshd.session\"))\n\t\ts.connsLock.Lock()\n\t\ts.counter++\n\t\tcounter := s.counter\n\t\ts.conns[counter] = session\n\t\ts.connsLock.Unlock()\n\n\t\tgo ssh.DiscardRequests(reqs)\n\t\tgo func() {\n\t\t\t<-session.exitChan\n\t\t\ts.l.WithField(\"id\", counter).Debug(\"closing conn\")\n\t\t\ts.connsLock.Lock()\n\t\t\tdelete(s.conns, counter)\n\t\t\ts.connsLock.Unlock()\n\t\t}()\n\t}\n}\n\nfunc (s *SSHServer) Stop() {\n\t// Close the listener, this will cause all session to terminate as well, see SSHServer.Run\n\tif s.listener != nil {\n\t\tif err := s.listener.Close(); err != nil {\n\t\t\ts.l.WithError(err).Warn(\"Failed to close the sshd listener\")\n\t\t}\n\t}\n}\n\nfunc (s *SSHServer) closeSessions() {\n\ts.connsLock.Lock()\n\tfor _, c := range s.conns {\n\t\tc.Close()\n\t}\n\ts.connsLock.Unlock()\n}\n"
  },
  {
    "path": "sshd/session.go",
    "content": "package sshd\n\nimport (\n\t\"fmt\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/anmitsu/go-shlex\"\n\t\"github.com/armon/go-radix\"\n\t\"github.com/sirupsen/logrus\"\n\t\"golang.org/x/crypto/ssh\"\n\t\"golang.org/x/term\"\n)\n\ntype session struct {\n\tl        *logrus.Entry\n\tc        *ssh.ServerConn\n\tterm     *term.Terminal\n\tcommands *radix.Tree\n\texitChan chan bool\n}\n\nfunc NewSession(commands *radix.Tree, conn *ssh.ServerConn, chans <-chan ssh.NewChannel, l *logrus.Entry) *session {\n\ts := &session{\n\t\tcommands: radix.NewFromMap(commands.ToMap()),\n\t\tl:        l,\n\t\tc:        conn,\n\t\texitChan: make(chan bool),\n\t}\n\n\ts.commands.Insert(\"logout\", &Command{\n\t\tName:             \"logout\",\n\t\tShortDescription: \"Ends the current session\",\n\t\tCallback: func(a any, args []string, w StringWriter) error {\n\t\t\ts.Close()\n\t\t\treturn nil\n\t\t},\n\t})\n\n\tgo s.handleChannels(chans)\n\treturn s\n}\n\nfunc (s *session) handleChannels(chans <-chan ssh.NewChannel) {\n\tfor newChannel := range chans {\n\t\tif newChannel.ChannelType() != \"session\" {\n\t\t\ts.l.WithField(\"sshChannelType\", newChannel.ChannelType()).Error(\"unknown channel type\")\n\t\t\tnewChannel.Reject(ssh.UnknownChannelType, \"unknown channel type\")\n\t\t\tcontinue\n\t\t}\n\n\t\tchannel, requests, err := newChannel.Accept()\n\t\tif err != nil {\n\t\t\ts.l.WithError(err).Warn(\"could not accept channel\")\n\t\t\tcontinue\n\t\t}\n\n\t\tgo s.handleRequests(requests, channel)\n\t}\n}\n\nfunc (s *session) handleRequests(in <-chan *ssh.Request, channel ssh.Channel) {\n\tfor req := range in {\n\t\tvar err error\n\t\tswitch req.Type {\n\t\tcase \"shell\":\n\t\t\tif s.term == nil {\n\t\t\t\ts.term = s.createTerm(channel)\n\t\t\t\terr = req.Reply(true, nil)\n\t\t\t} else {\n\t\t\t\terr = req.Reply(false, nil)\n\t\t\t}\n\n\t\tcase \"pty-req\":\n\t\t\terr = req.Reply(true, nil)\n\n\t\tcase \"window-change\":\n\t\t\terr = req.Reply(true, nil)\n\n\t\tcase \"exec\":\n\t\t\tvar payload = struct{ Value string }{}\n\t\t\tcErr := ssh.Unmarshal(req.Payload, &payload)\n\t\t\tif cErr != nil {\n\t\t\t\treq.Reply(false, nil)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\treq.Reply(true, nil)\n\t\t\ts.dispatchCommand(payload.Value, &stringWriter{channel})\n\n\t\t\tstatus := struct{ Status uint32 }{uint32(0)}\n\t\t\tchannel.SendRequest(\"exit-status\", false, ssh.Marshal(status))\n\t\t\tchannel.Close()\n\t\t\treturn\n\n\t\tdefault:\n\t\t\ts.l.WithField(\"sshRequest\", req.Type).Debug(\"Rejected unknown request\")\n\t\t\terr = req.Reply(false, nil)\n\t\t}\n\n\t\tif err != nil {\n\t\t\ts.l.WithError(err).Info(\"Error handling ssh session requests\")\n\t\t\ts.Close()\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (s *session) createTerm(channel ssh.Channel) *term.Terminal {\n\tterm := term.NewTerminal(channel, s.c.User()+\"@nebula > \")\n\tterm.AutoCompleteCallback = func(line string, pos int, key rune) (newLine string, newPos int, ok bool) {\n\t\t// key 9 is tab\n\t\tif key == 9 {\n\t\t\tcmds := matchCommand(s.commands, line)\n\t\t\tif len(cmds) == 1 {\n\t\t\t\treturn cmds[0] + \" \", len(cmds[0]) + 1, true\n\t\t\t}\n\n\t\t\tsort.Strings(cmds)\n\t\t\tterm.Write([]byte(strings.Join(cmds, \"\\n\") + \"\\n\\n\"))\n\t\t}\n\n\t\treturn \"\", 0, false\n\t}\n\n\tgo s.handleInput(channel)\n\treturn term\n}\n\nfunc (s *session) handleInput(channel ssh.Channel) {\n\tdefer s.Close()\n\tw := &stringWriter{w: s.term}\n\tfor {\n\t\tline, err := s.term.ReadLine()\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\n\t\ts.dispatchCommand(line, w)\n\t}\n}\n\nfunc (s *session) dispatchCommand(line string, w StringWriter) {\n\targs, err := shlex.Split(line, true)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif len(args) == 0 {\n\t\tdumpCommands(s.commands, w)\n\t\treturn\n\t}\n\n\tc, err := lookupCommand(s.commands, args[0])\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif c == nil {\n\t\terr := w.WriteLine(fmt.Sprintf(\"did not understand: %s\", line))\n\t\t_ = err\n\n\t\tdumpCommands(s.commands, w)\n\t\treturn\n\t}\n\n\tif checkHelpArgs(args) {\n\t\ts.dispatchCommand(fmt.Sprintf(\"%s %s\", \"help\", c.Name), w)\n\t\treturn\n\t}\n\n\t_ = execCommand(c, args[1:], w)\n\treturn\n}\n\nfunc (s *session) Close() {\n\ts.c.Close()\n\ts.exitChan <- true\n}\n"
  },
  {
    "path": "sshd/writer.go",
    "content": "package sshd\n\nimport \"io\"\n\ntype StringWriter interface {\n\tWriteLine(string) error\n\tWrite(string) error\n\tWriteBytes([]byte) error\n\tGetWriter() io.Writer\n}\n\ntype stringWriter struct {\n\tw io.Writer\n}\n\nfunc (w *stringWriter) WriteLine(s string) error {\n\treturn w.Write(s + \"\\n\")\n}\n\nfunc (w *stringWriter) Write(s string) error {\n\t_, err := w.w.Write([]byte(s))\n\treturn err\n}\n\nfunc (w *stringWriter) WriteBytes(b []byte) error {\n\t_, err := w.w.Write(b)\n\treturn err\n}\n\nfunc (w *stringWriter) GetWriter() io.Writer {\n\treturn w.w\n}\n"
  },
  {
    "path": "stats.go",
    "content": "package nebula\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"time\"\n\n\tgraphite \"github.com/cyberdelia/go-metrics-graphite\"\n\tmp \"github.com/nbrownus/go-metrics-prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promhttp\"\n\t\"github.com/rcrowley/go-metrics\"\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/config\"\n)\n\n// startStats initializes stats from config. On success, if any further work\n// is needed to serve stats, it returns a func to handle that work. If no\n// work is needed, it'll return nil. On failure, it returns nil, error.\nfunc startStats(l *logrus.Logger, c *config.C, buildVersion string, configTest bool) (func(), error) {\n\tmType := c.GetString(\"stats.type\", \"\")\n\tif mType == \"\" || mType == \"none\" {\n\t\treturn nil, nil\n\t}\n\n\tinterval := c.GetDuration(\"stats.interval\", 0)\n\tif interval == 0 {\n\t\treturn nil, fmt.Errorf(\"stats.interval was an invalid duration: %s\", c.GetString(\"stats.interval\", \"\"))\n\t}\n\n\tvar startFn func()\n\tswitch mType {\n\tcase \"graphite\":\n\t\terr := startGraphiteStats(l, interval, c, configTest)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\tcase \"prometheus\":\n\t\tvar err error\n\t\tstartFn, err = startPrometheusStats(l, interval, c, buildVersion, configTest)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"stats.type was not understood: %s\", mType)\n\t}\n\n\tmetrics.RegisterDebugGCStats(metrics.DefaultRegistry)\n\tmetrics.RegisterRuntimeMemStats(metrics.DefaultRegistry)\n\n\tgo metrics.CaptureDebugGCStats(metrics.DefaultRegistry, interval)\n\tgo metrics.CaptureRuntimeMemStats(metrics.DefaultRegistry, interval)\n\n\treturn startFn, nil\n}\n\nfunc startGraphiteStats(l *logrus.Logger, i time.Duration, c *config.C, configTest bool) error {\n\tproto := c.GetString(\"stats.protocol\", \"tcp\")\n\thost := c.GetString(\"stats.host\", \"\")\n\tif host == \"\" {\n\t\treturn errors.New(\"stats.host can not be empty\")\n\t}\n\n\tprefix := c.GetString(\"stats.prefix\", \"nebula\")\n\taddr, err := net.ResolveTCPAddr(proto, host)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error while setting up graphite sink: %s\", err)\n\t}\n\n\tif !configTest {\n\t\tl.Infof(\"Starting graphite. Interval: %s, prefix: %s, addr: %s\", i, prefix, addr)\n\t\tgo graphite.Graphite(metrics.DefaultRegistry, i, prefix, addr)\n\t}\n\treturn nil\n}\n\nfunc startPrometheusStats(l *logrus.Logger, i time.Duration, c *config.C, buildVersion string, configTest bool) (func(), error) {\n\tnamespace := c.GetString(\"stats.namespace\", \"\")\n\tsubsystem := c.GetString(\"stats.subsystem\", \"\")\n\n\tlisten := c.GetString(\"stats.listen\", \"\")\n\tif listen == \"\" {\n\t\treturn nil, fmt.Errorf(\"stats.listen should not be empty\")\n\t}\n\n\tpath := c.GetString(\"stats.path\", \"\")\n\tif path == \"\" {\n\t\treturn nil, fmt.Errorf(\"stats.path should not be empty\")\n\t}\n\n\tpr := prometheus.NewRegistry()\n\tpClient := mp.NewPrometheusProvider(metrics.DefaultRegistry, namespace, subsystem, pr, i)\n\tif !configTest {\n\t\tgo pClient.UpdatePrometheusMetrics()\n\t}\n\n\t// Export our version information as labels on a static gauge\n\tg := prometheus.NewGauge(prometheus.GaugeOpts{\n\t\tNamespace: namespace,\n\t\tSubsystem: subsystem,\n\t\tName:      \"info\",\n\t\tHelp:      \"Version information for the Nebula binary\",\n\t\tConstLabels: prometheus.Labels{\n\t\t\t\"version\":      buildVersion,\n\t\t\t\"goversion\":    runtime.Version(),\n\t\t\t\"boringcrypto\": strconv.FormatBool(boringEnabled()),\n\t\t},\n\t})\n\tpr.MustRegister(g)\n\tg.Set(1)\n\n\tvar startFn func()\n\tif !configTest {\n\t\tstartFn = func() {\n\t\t\tl.Infof(\"Prometheus stats listening on %s at %s\", listen, path)\n\t\t\thttp.Handle(path, promhttp.HandlerFor(pr, promhttp.HandlerOpts{ErrorLog: l}))\n\t\t\tlog.Fatal(http.ListenAndServe(listen, nil))\n\t\t}\n\t}\n\n\treturn startFn, nil\n}\n"
  },
  {
    "path": "test/assert.go",
    "content": "package test\n\nimport (\n\t\"fmt\"\n\t\"net/netip\"\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n\t\"unsafe\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\n// AssertDeepCopyEqual checks to see if two variables have the same values but DO NOT share any memory\n// There is currently a special case for `time.loc` (as this code traverses into unexported fields)\nfunc AssertDeepCopyEqual(t *testing.T, a any, b any) {\n\tv1 := reflect.ValueOf(a)\n\tv2 := reflect.ValueOf(b)\n\n\tif !assert.Equal(t, v1.Type(), v2.Type()) {\n\t\treturn\n\t}\n\n\ttraverseDeepCopy(t, v1, v2, v1.Type().String())\n}\n\nfunc traverseDeepCopy(t *testing.T, v1 reflect.Value, v2 reflect.Value, name string) bool {\n\tif v1.Type() == v2.Type() && v1.Type() == reflect.TypeOf(netip.Addr{}) {\n\t\t// Ignore netip.Addr types since they reuse an interned global value\n\t\treturn false\n\t}\n\n\tswitch v1.Kind() {\n\tcase reflect.Array:\n\t\tfor i := 0; i < v1.Len(); i++ {\n\t\t\tif !traverseDeepCopy(t, v1.Index(i), v2.Index(i), fmt.Sprintf(\"%s[%v]\", name, i)) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\n\tcase reflect.Slice:\n\t\tif v1.IsNil() || v2.IsNil() {\n\t\t\treturn assert.Equal(t, v1.IsNil(), v2.IsNil(), \"%s are not both nil %+v, %+v\", name, v1, v2)\n\t\t}\n\n\t\tif !assert.Equal(t, v1.Len(), v2.Len(), \"%s did not have the same length\", name) {\n\t\t\treturn false\n\t\t}\n\n\t\t// A slice with cap 0\n\t\tif v1.Cap() != 0 && !assert.NotEqual(t, v1.Pointer(), v2.Pointer(), \"%s point to the same slice %v == %v\", name, v1.Pointer(), v2.Pointer()) {\n\t\t\treturn false\n\t\t}\n\n\t\tv1c := v1.Cap()\n\t\tv2c := v2.Cap()\n\t\tif v1c > 0 && v2c > 0 && v1.Slice(0, v1c).Slice(v1c-1, v1c-1).Pointer() == v2.Slice(0, v2c).Slice(v2c-1, v2c-1).Pointer() {\n\t\t\treturn assert.Fail(t, \"\", \"%s share some underlying memory\", name)\n\t\t}\n\n\t\tfor i := 0; i < v1.Len(); i++ {\n\t\t\tif !traverseDeepCopy(t, v1.Index(i), v2.Index(i), fmt.Sprintf(\"%s[%v]\", name, i)) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\n\tcase reflect.Interface:\n\t\tif v1.IsNil() || v2.IsNil() {\n\t\t\treturn assert.Equal(t, v1.IsNil(), v2.IsNil(), \"%s are not both nil\", name)\n\t\t}\n\t\treturn traverseDeepCopy(t, v1.Elem(), v2.Elem(), name)\n\n\tcase reflect.Ptr:\n\t\tlocal := reflect.ValueOf(time.Local).Pointer()\n\t\tif local == v1.Pointer() && local == v2.Pointer() {\n\t\t\treturn true\n\t\t}\n\n\t\tif !assert.NotEqual(t, v1.Pointer(), v2.Pointer(), \"%s points to the same memory\", name) {\n\t\t\treturn false\n\t\t}\n\n\t\treturn traverseDeepCopy(t, v1.Elem(), v2.Elem(), name)\n\n\tcase reflect.Struct:\n\t\tfor i, n := 0, v1.NumField(); i < n; i++ {\n\t\t\tif !traverseDeepCopy(t, v1.Field(i), v2.Field(i), name+\".\"+v1.Type().Field(i).Name) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\n\tcase reflect.Map:\n\t\tif v1.IsNil() || v2.IsNil() {\n\t\t\treturn assert.Equal(t, v1.IsNil(), v2.IsNil(), \"%s are not both nil\", name)\n\t\t}\n\n\t\tif !assert.Equal(t, v1.Len(), v2.Len(), \"%s are not the same length\", name) {\n\t\t\treturn false\n\t\t}\n\n\t\tif !assert.NotEqual(t, v1.Pointer(), v2.Pointer(), \"%s point to the same memory\", name) {\n\t\t\treturn false\n\t\t}\n\n\t\tfor _, k := range v1.MapKeys() {\n\t\t\tval1 := v1.MapIndex(k)\n\t\t\tval2 := v2.MapIndex(k)\n\t\t\tif !assert.True(t, val1.IsValid(), \"%s is an invalid key in %s\", k, name) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tif !assert.True(t, val2.IsValid(), \"%s is an invalid key in %s\", k, name) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tif !traverseDeepCopy(t, val1, val2, name+fmt.Sprintf(\"%s[%s]\", name, k)) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\n\t\treturn true\n\n\tdefault:\n\t\tif v1.CanInterface() && v2.CanInterface() {\n\t\t\treturn assert.Equal(t, v1.Interface(), v2.Interface(), \"%s was not equal\", name)\n\t\t}\n\n\t\te1 := reflect.NewAt(v1.Type(), unsafe.Pointer(v1.UnsafeAddr())).Elem().Interface()\n\t\te2 := reflect.NewAt(v2.Type(), unsafe.Pointer(v2.UnsafeAddr())).Elem().Interface()\n\n\t\treturn assert.Equal(t, e1, e2, \"%s (unexported) was not equal\", name)\n\t}\n}\n"
  },
  {
    "path": "test/logger.go",
    "content": "package test\n\nimport (\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc NewLogger() *logrus.Logger {\n\tl := logrus.New()\n\n\tv := os.Getenv(\"TEST_LOGS\")\n\tif v == \"\" {\n\t\tl.SetOutput(io.Discard)\n\t\treturn l\n\t}\n\n\tswitch v {\n\tcase \"2\":\n\t\tl.SetLevel(logrus.DebugLevel)\n\tcase \"3\":\n\t\tl.SetLevel(logrus.TraceLevel)\n\tdefault:\n\t\tl.SetLevel(logrus.InfoLevel)\n\t}\n\n\treturn l\n}\n"
  },
  {
    "path": "test/tun.go",
    "content": "package test\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"net/netip\"\n\n\t\"github.com/slackhq/nebula/routing\"\n)\n\ntype NoopTun struct{}\n\nfunc (NoopTun) RoutesFor(addr netip.Addr) routing.Gateways {\n\treturn routing.Gateways{}\n}\n\nfunc (NoopTun) Activate() error {\n\treturn nil\n}\n\nfunc (NoopTun) Networks() []netip.Prefix {\n\treturn []netip.Prefix{}\n}\n\nfunc (NoopTun) Name() string {\n\treturn \"noop\"\n}\n\nfunc (NoopTun) Read([]byte) (int, error) {\n\treturn 0, nil\n}\n\nfunc (NoopTun) Write([]byte) (int, error) {\n\treturn 0, nil\n}\n\nfunc (NoopTun) SupportsMultiqueue() bool {\n\treturn false\n}\n\nfunc (NoopTun) NewMultiQueueReader() (io.ReadWriteCloser, error) {\n\treturn nil, errors.New(\"unsupported\")\n}\n\nfunc (NoopTun) Close() error {\n\treturn nil\n}\n"
  },
  {
    "path": "timeout.go",
    "content": "package nebula\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\n// How many timer objects should be cached\nconst timerCacheMax = 50000\n\ntype TimerWheel[T any] struct {\n\t// Current tick\n\tcurrent int\n\n\t// Cheat on finding the length of the wheel\n\twheelLen int\n\n\t// Last time we ticked, since we are lazy ticking\n\tlastTick *time.Time\n\n\t// Durations of a tick and the entire wheel\n\ttickDuration  time.Duration\n\twheelDuration time.Duration\n\n\t// The actual wheel which is just a set of singly linked lists, head/tail pointers\n\twheel []*TimeoutList[T]\n\n\t// Singly linked list of items that have timed out of the wheel\n\texpired *TimeoutList[T]\n\n\t// Item cache to avoid garbage collect\n\titemCache   *TimeoutItem[T]\n\titemsCached int\n}\n\ntype LockingTimerWheel[T any] struct {\n\tm sync.Mutex\n\tt *TimerWheel[T]\n}\n\n// TimeoutList Represents a tick in the wheel\ntype TimeoutList[T any] struct {\n\tHead *TimeoutItem[T]\n\tTail *TimeoutItem[T]\n}\n\n// TimeoutItem Represents an item within a tick\ntype TimeoutItem[T any] struct {\n\tItem T\n\tNext *TimeoutItem[T]\n}\n\n// NewTimerWheel Builds a timer wheel and identifies the tick duration and wheel duration from the provided values\n// Purge must be called once per entry to actually remove anything\n// The TimerWheel does not handle concurrency on its own.\n// Locks around access to it must be used if multiple routines are manipulating it.\nfunc NewTimerWheel[T any](min, max time.Duration) *TimerWheel[T] {\n\t//TODO provide an error\n\t//if min >= max {\n\t//\treturn nil\n\t//}\n\n\t// Round down and add 2 so we can have the smallest # of ticks in the wheel and still account for a full\n\t// max duration, even if our current tick is at the maximum position and the next item to be added is at maximum\n\t// timeout\n\twLen := int((max / min) + 2)\n\n\ttw := TimerWheel[T]{\n\t\twheelLen:      wLen,\n\t\twheel:         make([]*TimeoutList[T], wLen),\n\t\ttickDuration:  min,\n\t\twheelDuration: max,\n\t\texpired:       &TimeoutList[T]{},\n\t}\n\n\tfor i := range tw.wheel {\n\t\ttw.wheel[i] = &TimeoutList[T]{}\n\t}\n\n\treturn &tw\n}\n\n// NewLockingTimerWheel is version of TimerWheel that is safe for concurrent use with a small performance penalty\nfunc NewLockingTimerWheel[T any](min, max time.Duration) *LockingTimerWheel[T] {\n\treturn &LockingTimerWheel[T]{\n\t\tt: NewTimerWheel[T](min, max),\n\t}\n}\n\n// Add will add an item to the wheel in its proper timeout.\n// Caller should Advance the wheel prior to ensure the proper slot is used.\nfunc (tw *TimerWheel[T]) Add(v T, timeout time.Duration) *TimeoutItem[T] {\n\ti := tw.findWheel(timeout)\n\n\t// Try to fetch off the cache\n\tti := tw.itemCache\n\tif ti != nil {\n\t\ttw.itemCache = ti.Next\n\t\ttw.itemsCached--\n\t\tti.Next = nil\n\t} else {\n\t\tti = &TimeoutItem[T]{}\n\t}\n\n\t// Relink and return\n\tti.Item = v\n\tif tw.wheel[i].Tail == nil {\n\t\ttw.wheel[i].Head = ti\n\t\ttw.wheel[i].Tail = ti\n\t} else {\n\t\ttw.wheel[i].Tail.Next = ti\n\t\ttw.wheel[i].Tail = ti\n\t}\n\n\treturn ti\n}\n\n// Purge removes and returns the first available expired item from the wheel and the 2nd argument is true.\n// If no item is available then an empty T is returned and the 2nd argument is false.\nfunc (tw *TimerWheel[T]) Purge() (T, bool) {\n\tif tw.expired.Head == nil {\n\t\tvar na T\n\t\treturn na, false\n\t}\n\n\tti := tw.expired.Head\n\ttw.expired.Head = ti.Next\n\n\tif tw.expired.Head == nil {\n\t\ttw.expired.Tail = nil\n\t}\n\n\t// Clear out the items references\n\tti.Next = nil\n\n\t// Maybe cache it for later\n\tif tw.itemsCached < timerCacheMax {\n\t\tti.Next = tw.itemCache\n\t\ttw.itemCache = ti\n\t\ttw.itemsCached++\n\t}\n\n\treturn ti.Item, true\n}\n\n// findWheel find the next position in the wheel for the provided timeout given the current tick\nfunc (tw *TimerWheel[T]) findWheel(timeout time.Duration) (i int) {\n\tif timeout < tw.tickDuration {\n\t\t// Can't track anything below the set resolution\n\t\ttimeout = tw.tickDuration\n\t} else if timeout > tw.wheelDuration {\n\t\t// We aren't handling timeouts greater than the wheels duration\n\t\ttimeout = tw.wheelDuration\n\t}\n\n\t// Find the next highest, rounding up\n\ttick := int(((timeout - 1) / tw.tickDuration) + 1)\n\n\t// Add another tick since the current tick may almost be over then map it to the wheel from our\n\t// current position\n\ttick += tw.current + 1\n\tif tick >= tw.wheelLen {\n\t\ttick -= tw.wheelLen\n\t}\n\n\treturn tick\n}\n\n// Advance will move the wheel forward by the appropriate number of ticks for the provided time and all items\n// passed over will be moved to the expired list. Calling Purge is necessary to remove them entirely.\nfunc (tw *TimerWheel[T]) Advance(now time.Time) {\n\tif tw.lastTick == nil {\n\t\ttw.lastTick = &now\n\t}\n\n\t// We want to round down\n\tticks := int(now.Sub(*tw.lastTick) / tw.tickDuration)\n\tadv := ticks\n\tif ticks > tw.wheelLen {\n\t\tticks = tw.wheelLen\n\t}\n\n\tfor i := 0; i < ticks; i++ {\n\t\ttw.current++\n\t\tif tw.current >= tw.wheelLen {\n\t\t\ttw.current = 0\n\t\t}\n\n\t\tif tw.wheel[tw.current].Head != nil {\n\t\t\t// We need to append the expired items as to not starve evicting the oldest ones\n\t\t\tif tw.expired.Tail == nil {\n\t\t\t\ttw.expired.Head = tw.wheel[tw.current].Head\n\t\t\t\ttw.expired.Tail = tw.wheel[tw.current].Tail\n\t\t\t} else {\n\t\t\t\ttw.expired.Tail.Next = tw.wheel[tw.current].Head\n\t\t\t\ttw.expired.Tail = tw.wheel[tw.current].Tail\n\t\t\t}\n\n\t\t\ttw.wheel[tw.current].Head = nil\n\t\t\ttw.wheel[tw.current].Tail = nil\n\t\t}\n\t}\n\n\t// Advance the tick based on duration to avoid losing some accuracy\n\tnewTick := tw.lastTick.Add(tw.tickDuration * time.Duration(adv))\n\ttw.lastTick = &newTick\n}\n\nfunc (lw *LockingTimerWheel[T]) Add(v T, timeout time.Duration) *TimeoutItem[T] {\n\tlw.m.Lock()\n\tdefer lw.m.Unlock()\n\treturn lw.t.Add(v, timeout)\n}\n\nfunc (lw *LockingTimerWheel[T]) Purge() (T, bool) {\n\tlw.m.Lock()\n\tdefer lw.m.Unlock()\n\treturn lw.t.Purge()\n}\n\nfunc (lw *LockingTimerWheel[T]) Advance(now time.Time) {\n\tlw.m.Lock()\n\tdefer lw.m.Unlock()\n\tlw.t.Advance(now)\n}\n"
  },
  {
    "path": "timeout_test.go",
    "content": "package nebula\n\nimport (\n\t\"net/netip\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/slackhq/nebula/firewall\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestNewTimerWheel(t *testing.T) {\n\t// Make sure we get an object we expect\n\ttw := NewTimerWheel[firewall.Packet](time.Second, time.Second*10)\n\tassert.Equal(t, 12, tw.wheelLen)\n\tassert.Equal(t, 0, tw.current)\n\tassert.Nil(t, tw.lastTick)\n\tassert.Equal(t, time.Second*1, tw.tickDuration)\n\tassert.Equal(t, time.Second*10, tw.wheelDuration)\n\tassert.Len(t, tw.wheel, 12)\n\n\t// Assert the math is correct\n\ttw = NewTimerWheel[firewall.Packet](time.Second*3, time.Second*10)\n\tassert.Equal(t, 5, tw.wheelLen)\n\n\ttw = NewTimerWheel[firewall.Packet](time.Second*120, time.Minute*10)\n\tassert.Equal(t, 7, tw.wheelLen)\n\n\t// Test empty purge of non nil items\n\ti, ok := tw.Purge()\n\tassert.Equal(t, firewall.Packet{}, i)\n\tassert.False(t, ok)\n\n\t// Test empty purges of nil items\n\ttw2 := NewTimerWheel[*int](time.Second, time.Second*10)\n\ti2, ok := tw2.Purge()\n\tassert.Nil(t, i2)\n\tassert.False(t, ok)\n\n}\n\nfunc TestTimerWheel_findWheel(t *testing.T) {\n\ttw := NewTimerWheel[firewall.Packet](time.Second, time.Second*10)\n\tassert.Len(t, tw.wheel, 12)\n\n\t// Current + tick + 1 since we don't know how far into current we are\n\tassert.Equal(t, 2, tw.findWheel(time.Second*1))\n\n\t// Scale up to min duration\n\tassert.Equal(t, 2, tw.findWheel(time.Millisecond*1))\n\n\t// Make sure we hit that last index\n\tassert.Equal(t, 11, tw.findWheel(time.Second*10))\n\n\t// Scale down to max duration\n\tassert.Equal(t, 11, tw.findWheel(time.Second*11))\n\n\ttw.current = 1\n\t// Make sure we account for the current position properly\n\tassert.Equal(t, 3, tw.findWheel(time.Second*1))\n\tassert.Equal(t, 0, tw.findWheel(time.Second*10))\n}\n\nfunc TestTimerWheel_Add(t *testing.T) {\n\ttw := NewTimerWheel[firewall.Packet](time.Second, time.Second*10)\n\n\tfp1 := firewall.Packet{}\n\ttw.Add(fp1, time.Second*1)\n\n\t// Make sure we set head and tail properly\n\tassert.NotNil(t, tw.wheel[2])\n\tassert.Equal(t, fp1, tw.wheel[2].Head.Item)\n\tassert.Nil(t, tw.wheel[2].Head.Next)\n\tassert.Equal(t, fp1, tw.wheel[2].Tail.Item)\n\tassert.Nil(t, tw.wheel[2].Tail.Next)\n\n\t// Make sure we only modify head\n\tfp2 := firewall.Packet{}\n\ttw.Add(fp2, time.Second*1)\n\tassert.Equal(t, fp2, tw.wheel[2].Head.Item)\n\tassert.Equal(t, fp1, tw.wheel[2].Head.Next.Item)\n\tassert.Equal(t, fp1, tw.wheel[2].Tail.Item)\n\tassert.Nil(t, tw.wheel[2].Tail.Next)\n\n\t// Make sure we use free'd items first\n\ttw.itemCache = &TimeoutItem[firewall.Packet]{}\n\ttw.itemsCached = 1\n\ttw.Add(fp2, time.Second*1)\n\tassert.Nil(t, tw.itemCache)\n\tassert.Equal(t, 0, tw.itemsCached)\n\n\t// Ensure that all configurations of a wheel does not result in calculating an overflow of the wheel\n\tfor min := time.Duration(1); min < 100; min++ {\n\t\tfor max := min; max < 100; max++ {\n\t\t\ttw = NewTimerWheel[firewall.Packet](min, max)\n\n\t\t\tfor current := 0; current < tw.wheelLen; current++ {\n\t\t\t\ttw.current = current\n\t\t\t\tfor timeout := time.Duration(0); timeout <= tw.wheelDuration; timeout++ {\n\t\t\t\t\ttick := tw.findWheel(timeout)\n\t\t\t\t\tif tick >= tw.wheelLen {\n\t\t\t\t\t\tt.Errorf(\"Min: %v; Max: %v; Wheel len: %v; Current Tick: %v; Insert timeout: %v; Calc tick: %v\", min, max, tw.wheelLen, current, timeout, tick)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestTimerWheel_Purge(t *testing.T) {\n\t// First advance should set the lastTick and do nothing else\n\ttw := NewTimerWheel[firewall.Packet](time.Second, time.Second*10)\n\tassert.Nil(t, tw.lastTick)\n\ttw.Advance(time.Now())\n\tassert.NotNil(t, tw.lastTick)\n\tassert.Equal(t, 0, tw.current)\n\n\tfps := []firewall.Packet{\n\t\t{LocalAddr: netip.MustParseAddr(\"0.0.0.1\")},\n\t\t{LocalAddr: netip.MustParseAddr(\"0.0.0.2\")},\n\t\t{LocalAddr: netip.MustParseAddr(\"0.0.0.3\")},\n\t\t{LocalAddr: netip.MustParseAddr(\"0.0.0.4\")},\n\t}\n\n\ttw.Add(fps[0], time.Second*1)\n\ttw.Add(fps[1], time.Second*1)\n\ttw.Add(fps[2], time.Second*2)\n\ttw.Add(fps[3], time.Second*2)\n\n\tta := time.Now().Add(time.Second * 3)\n\tlastTick := *tw.lastTick\n\ttw.Advance(ta)\n\tassert.Equal(t, 3, tw.current)\n\tassert.True(t, tw.lastTick.After(lastTick))\n\n\t// Make sure we get all 4 packets back\n\tfor i := range 4 {\n\t\tp, has := tw.Purge()\n\t\tassert.True(t, has)\n\t\tassert.Equal(t, fps[i], p)\n\t}\n\n\t// Make sure there aren't any leftover\n\t_, ok := tw.Purge()\n\tassert.False(t, ok)\n\tassert.Nil(t, tw.expired.Head)\n\tassert.Nil(t, tw.expired.Tail)\n\n\t// Make sure we cached the free'd items\n\tassert.Equal(t, 4, tw.itemsCached)\n\tci := tw.itemCache\n\tfor range 4 {\n\t\tassert.NotNil(t, ci)\n\t\tci = ci.Next\n\t}\n\tassert.Nil(t, ci)\n\n\t// Let's make sure we roll over properly\n\tta = ta.Add(time.Second * 5)\n\ttw.Advance(ta)\n\tassert.Equal(t, 8, tw.current)\n\n\tta = ta.Add(time.Second * 2)\n\ttw.Advance(ta)\n\tassert.Equal(t, 10, tw.current)\n\n\tta = ta.Add(time.Second * 1)\n\ttw.Advance(ta)\n\tassert.Equal(t, 11, tw.current)\n\n\tta = ta.Add(time.Second * 1)\n\ttw.Advance(ta)\n\tassert.Equal(t, 0, tw.current)\n}\n"
  },
  {
    "path": "udp/conn.go",
    "content": "package udp\n\nimport (\n\t\"net/netip\"\n\n\t\"github.com/slackhq/nebula/config\"\n)\n\nconst MTU = 9001\n\ntype EncReader func(\n\taddr netip.AddrPort,\n\tpayload []byte,\n)\n\ntype Conn interface {\n\tRebind() error\n\tLocalAddr() (netip.AddrPort, error)\n\tListenOut(r EncReader)\n\tWriteTo(b []byte, addr netip.AddrPort) error\n\tReloadConfig(c *config.C)\n\tSupportsMultipleReaders() bool\n\tClose() error\n}\n\ntype NoopConn struct{}\n\nfunc (NoopConn) Rebind() error {\n\treturn nil\n}\nfunc (NoopConn) LocalAddr() (netip.AddrPort, error) {\n\treturn netip.AddrPort{}, nil\n}\nfunc (NoopConn) ListenOut(_ EncReader) {\n\treturn\n}\nfunc (NoopConn) SupportsMultipleReaders() bool {\n\treturn false\n}\nfunc (NoopConn) WriteTo(_ []byte, _ netip.AddrPort) error {\n\treturn nil\n}\nfunc (NoopConn) ReloadConfig(_ *config.C) {\n\treturn\n}\nfunc (NoopConn) Close() error {\n\treturn nil\n}\n"
  },
  {
    "path": "udp/errors.go",
    "content": "package udp\n\nimport \"errors\"\n\nvar ErrInvalidIPv6RemoteForSocket = errors.New(\"listener is IPv4, but writing to IPv6 remote\")\n"
  },
  {
    "path": "udp/udp_android.go",
    "content": "//go:build !e2e_testing\n// +build !e2e_testing\n\npackage udp\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"syscall\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc NewListener(l *logrus.Logger, ip netip.Addr, port int, multi bool, batch int) (Conn, error) {\n\treturn NewGenericListener(l, ip, port, multi, batch)\n}\n\nfunc NewListenConfig(multi bool) net.ListenConfig {\n\treturn net.ListenConfig{\n\t\tControl: func(network, address string, c syscall.RawConn) error {\n\t\t\tif multi {\n\t\t\t\tvar controlErr error\n\t\t\t\terr := c.Control(func(fd uintptr) {\n\t\t\t\t\tif err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, unix.SO_REUSEPORT, 1); err != nil {\n\t\t\t\t\t\tcontrolErr = fmt.Errorf(\"SO_REUSEPORT failed: %v\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif controlErr != nil {\n\t\t\t\t\treturn controlErr\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n}\n\nfunc (u *GenericConn) Rebind() error {\n\treturn nil\n}\n"
  },
  {
    "path": "udp/udp_bsd.go",
    "content": "//go:build (openbsd || freebsd) && !e2e_testing\n// +build openbsd freebsd\n// +build !e2e_testing\n\npackage udp\n\n// FreeBSD support is primarily implemented in udp_generic, besides NewListenConfig\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"syscall\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc NewListener(l *logrus.Logger, ip netip.Addr, port int, multi bool, batch int) (Conn, error) {\n\treturn NewGenericListener(l, ip, port, multi, batch)\n}\n\nfunc NewListenConfig(multi bool) net.ListenConfig {\n\treturn net.ListenConfig{\n\t\tControl: func(network, address string, c syscall.RawConn) error {\n\t\t\tif multi {\n\t\t\t\tvar controlErr error\n\t\t\t\terr := c.Control(func(fd uintptr) {\n\t\t\t\t\tif err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, unix.SO_REUSEPORT, 1); err != nil {\n\t\t\t\t\t\tcontrolErr = fmt.Errorf(\"SO_REUSEPORT failed: %v\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif controlErr != nil {\n\t\t\t\t\treturn controlErr\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n}\n\nfunc (u *GenericConn) Rebind() error {\n\treturn nil\n}\n"
  },
  {
    "path": "udp/udp_darwin.go",
    "content": "//go:build !e2e_testing\n// +build !e2e_testing\n\npackage udp\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"syscall\"\n\t\"unsafe\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/config\"\n\t\"golang.org/x/sys/unix\"\n)\n\ntype StdConn struct {\n\t*net.UDPConn\n\tisV4  bool\n\tsysFd uintptr\n\tl     *logrus.Logger\n}\n\nvar _ Conn = &StdConn{}\n\nfunc NewListener(l *logrus.Logger, ip netip.Addr, port int, multi bool, batch int) (Conn, error) {\n\tlc := NewListenConfig(multi)\n\tpc, err := lc.ListenPacket(context.TODO(), \"udp\", net.JoinHostPort(ip.String(), fmt.Sprintf(\"%v\", port)))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif uc, ok := pc.(*net.UDPConn); ok {\n\t\tc := &StdConn{UDPConn: uc, l: l}\n\n\t\trc, err := uc.SyscallConn()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to open udp socket: %w\", err)\n\t\t}\n\n\t\terr = rc.Control(func(fd uintptr) {\n\t\t\tc.sysFd = fd\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to get udp fd: %w\", err)\n\t\t}\n\n\t\tla, err := c.LocalAddr()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tc.isV4 = la.Addr().Is4()\n\n\t\treturn c, nil\n\t}\n\n\treturn nil, fmt.Errorf(\"unexpected PacketConn: %T %#v\", pc, pc)\n}\n\nfunc NewListenConfig(multi bool) net.ListenConfig {\n\treturn net.ListenConfig{\n\t\tControl: func(network, address string, c syscall.RawConn) error {\n\t\t\tif multi {\n\t\t\t\tvar controlErr error\n\t\t\t\terr := c.Control(func(fd uintptr) {\n\t\t\t\t\tif err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, unix.SO_REUSEPORT, 1); err != nil {\n\t\t\t\t\t\tcontrolErr = fmt.Errorf(\"SO_REUSEPORT failed: %v\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif controlErr != nil {\n\t\t\t\t\treturn controlErr\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn nil\n\t\t},\n\t}\n}\n\n//go:linkname sendto golang.org/x/sys/unix.sendto\n//go:noescape\nfunc sendto(s int, buf []byte, flags int, to unsafe.Pointer, addrlen int32) (err error)\n\nfunc (u *StdConn) WriteTo(b []byte, ap netip.AddrPort) error {\n\tvar sa unsafe.Pointer\n\tvar addrLen int32\n\n\tif u.isV4 {\n\t\tif ap.Addr().Is6() {\n\t\t\treturn ErrInvalidIPv6RemoteForSocket\n\t\t}\n\n\t\tvar rsa unix.RawSockaddrInet4\n\t\trsa.Family = unix.AF_INET\n\t\trsa.Addr = ap.Addr().As4()\n\t\tbinary.BigEndian.PutUint16((*[2]byte)(unsafe.Pointer(&rsa.Port))[:], ap.Port())\n\t\tsa = unsafe.Pointer(&rsa)\n\t\taddrLen = syscall.SizeofSockaddrInet4\n\t} else {\n\t\tvar rsa unix.RawSockaddrInet6\n\t\trsa.Family = unix.AF_INET6\n\t\trsa.Addr = ap.Addr().As16()\n\t\tbinary.BigEndian.PutUint16((*[2]byte)(unsafe.Pointer(&rsa.Port))[:], ap.Port())\n\t\tsa = unsafe.Pointer(&rsa)\n\t\taddrLen = syscall.SizeofSockaddrInet6\n\t}\n\n\t// Golang stdlib doesn't handle EAGAIN correctly in some situations so we do writes ourselves\n\t// See https://github.com/golang/go/issues/73919\n\tfor {\n\t\t//_, _, err := unix.Syscall6(unix.SYS_SENDTO, u.sysFd, uintptr(unsafe.Pointer(&b[0])), uintptr(len(b)), 0, sa, addrLen)\n\t\terr := sendto(int(u.sysFd), b, 0, sa, addrLen)\n\t\tif err == nil {\n\t\t\t// Written, get out before the error handling\n\t\t\treturn nil\n\t\t}\n\n\t\tif errors.Is(err, syscall.EINTR) {\n\t\t\t// Write was interrupted, retry\n\t\t\tcontinue\n\t\t}\n\n\t\tif errors.Is(err, syscall.EAGAIN) {\n\t\t\treturn &net.OpError{Op: \"sendto\", Err: unix.EWOULDBLOCK}\n\t\t}\n\n\t\tif errors.Is(err, syscall.EBADF) {\n\t\t\treturn net.ErrClosed\n\t\t}\n\n\t\treturn &net.OpError{Op: \"sendto\", Err: err}\n\t}\n}\n\nfunc (u *StdConn) LocalAddr() (netip.AddrPort, error) {\n\ta := u.UDPConn.LocalAddr()\n\n\tswitch v := a.(type) {\n\tcase *net.UDPAddr:\n\t\taddr, ok := netip.AddrFromSlice(v.IP)\n\t\tif !ok {\n\t\t\treturn netip.AddrPort{}, fmt.Errorf(\"LocalAddr returned invalid IP address: %s\", v.IP)\n\t\t}\n\t\treturn netip.AddrPortFrom(addr, uint16(v.Port)), nil\n\n\tdefault:\n\t\treturn netip.AddrPort{}, fmt.Errorf(\"LocalAddr returned: %#v\", a)\n\t}\n}\n\nfunc (u *StdConn) ReloadConfig(c *config.C) {\n\t// TODO\n}\n\nfunc NewUDPStatsEmitter(udpConns []Conn) func() {\n\t// No UDP stats for non-linux\n\treturn func() {}\n}\n\nfunc (u *StdConn) ListenOut(r EncReader) {\n\tbuffer := make([]byte, MTU)\n\n\tfor {\n\t\t// Just read one packet at a time\n\t\tn, rua, err := u.ReadFromUDPAddrPort(buffer)\n\t\tif err != nil {\n\t\t\tif errors.Is(err, net.ErrClosed) {\n\t\t\t\tu.l.WithError(err).Debug(\"udp socket is closed, exiting read loop\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tu.l.WithError(err).Error(\"unexpected udp socket receive error\")\n\t\t}\n\n\t\tr(netip.AddrPortFrom(rua.Addr().Unmap(), rua.Port()), buffer[:n])\n\t}\n}\n\nfunc (u *StdConn) SupportsMultipleReaders() bool {\n\treturn false\n}\n\nfunc (u *StdConn) Rebind() error {\n\tvar err error\n\tif u.isV4 {\n\t\terr = syscall.SetsockoptInt(int(u.sysFd), syscall.IPPROTO_IP, syscall.IP_BOUND_IF, 0)\n\t} else {\n\t\terr = syscall.SetsockoptInt(int(u.sysFd), syscall.IPPROTO_IPV6, syscall.IPV6_BOUND_IF, 0)\n\t}\n\n\tif err != nil {\n\t\tu.l.WithError(err).Error(\"Failed to rebind udp socket\")\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "udp/udp_generic.go",
    "content": "//go:build (!linux || android) && !e2e_testing && !darwin\n// +build !linux android\n// +build !e2e_testing\n// +build !darwin\n\n// udp_generic implements the nebula UDP interface in pure Go stdlib. This\n// means it can be used on platforms like Darwin and Windows.\n\npackage udp\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"time\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/config\"\n)\n\ntype GenericConn struct {\n\t*net.UDPConn\n\tl *logrus.Logger\n}\n\nvar _ Conn = &GenericConn{}\n\nfunc NewGenericListener(l *logrus.Logger, ip netip.Addr, port int, multi bool, batch int) (Conn, error) {\n\tlc := NewListenConfig(multi)\n\tpc, err := lc.ListenPacket(context.TODO(), \"udp\", net.JoinHostPort(ip.String(), fmt.Sprintf(\"%v\", port)))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif uc, ok := pc.(*net.UDPConn); ok {\n\t\treturn &GenericConn{UDPConn: uc, l: l}, nil\n\t}\n\treturn nil, fmt.Errorf(\"Unexpected PacketConn: %T %#v\", pc, pc)\n}\n\nfunc (u *GenericConn) WriteTo(b []byte, addr netip.AddrPort) error {\n\t_, err := u.UDPConn.WriteToUDPAddrPort(b, addr)\n\treturn err\n}\n\nfunc (u *GenericConn) LocalAddr() (netip.AddrPort, error) {\n\ta := u.UDPConn.LocalAddr()\n\n\tswitch v := a.(type) {\n\tcase *net.UDPAddr:\n\t\taddr, ok := netip.AddrFromSlice(v.IP)\n\t\tif !ok {\n\t\t\treturn netip.AddrPort{}, fmt.Errorf(\"LocalAddr returned invalid IP address: %s\", v.IP)\n\t\t}\n\t\treturn netip.AddrPortFrom(addr, uint16(v.Port)), nil\n\n\tdefault:\n\t\treturn netip.AddrPort{}, fmt.Errorf(\"LocalAddr returned: %#v\", a)\n\t}\n}\n\nfunc (u *GenericConn) ReloadConfig(c *config.C) {\n\n}\n\nfunc NewUDPStatsEmitter(udpConns []Conn) func() {\n\t// No UDP stats for non-linux\n\treturn func() {}\n}\n\ntype rawMessage struct {\n\tLen uint32\n}\n\nfunc (u *GenericConn) ListenOut(r EncReader) {\n\tbuffer := make([]byte, MTU)\n\n\tvar lastRecvErr time.Time\n\n\tfor {\n\t\t// Just read one packet at a time\n\t\tn, rua, err := u.ReadFromUDPAddrPort(buffer)\n\t\tif err != nil {\n\t\t\tif errors.Is(err, net.ErrClosed) {\n\t\t\t\tu.l.WithError(err).Debug(\"udp socket is closed, exiting read loop\")\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// Dampen unexpected message warns to once per minute\n\t\t\tif lastRecvErr.IsZero() || time.Since(lastRecvErr) > time.Minute {\n\t\t\t\tlastRecvErr = time.Now()\n\t\t\t\tu.l.WithError(err).Warn(\"unexpected udp socket receive error\")\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tr(netip.AddrPortFrom(rua.Addr().Unmap(), rua.Port()), buffer[:n])\n\t}\n}\n\nfunc (u *GenericConn) SupportsMultipleReaders() bool {\n\treturn false\n}\n"
  },
  {
    "path": "udp/udp_linux.go",
    "content": "//go:build !android && !e2e_testing\n// +build !android,!e2e_testing\n\npackage udp\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"syscall\"\n\t\"unsafe\"\n\n\t\"github.com/rcrowley/go-metrics\"\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/config\"\n\t\"golang.org/x/sys/unix\"\n)\n\ntype StdConn struct {\n\tsysFd int\n\tisV4  bool\n\tl     *logrus.Logger\n\tbatch int\n}\n\nfunc maybeIPV4(ip net.IP) (net.IP, bool) {\n\tip4 := ip.To4()\n\tif ip4 != nil {\n\t\treturn ip4, true\n\t}\n\treturn ip, false\n}\n\nfunc NewListener(l *logrus.Logger, ip netip.Addr, port int, multi bool, batch int) (Conn, error) {\n\taf := unix.AF_INET6\n\tif ip.Is4() {\n\t\taf = unix.AF_INET\n\t}\n\tsyscall.ForkLock.RLock()\n\tfd, err := unix.Socket(af, unix.SOCK_DGRAM, unix.IPPROTO_UDP)\n\tif err == nil {\n\t\tunix.CloseOnExec(fd)\n\t}\n\tsyscall.ForkLock.RUnlock()\n\n\tif err != nil {\n\t\tunix.Close(fd)\n\t\treturn nil, fmt.Errorf(\"unable to open socket: %s\", err)\n\t}\n\n\tif multi {\n\t\tif err = unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_REUSEPORT, 1); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"unable to set SO_REUSEPORT: %s\", err)\n\t\t}\n\t}\n\n\tvar sa unix.Sockaddr\n\tif ip.Is4() {\n\t\tsa4 := &unix.SockaddrInet4{Port: port}\n\t\tsa4.Addr = ip.As4()\n\t\tsa = sa4\n\t} else {\n\t\tsa6 := &unix.SockaddrInet6{Port: port}\n\t\tsa6.Addr = ip.As16()\n\t\tsa = sa6\n\t}\n\tif err = unix.Bind(fd, sa); err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to bind to socket: %s\", err)\n\t}\n\n\treturn &StdConn{sysFd: fd, isV4: ip.Is4(), l: l, batch: batch}, err\n}\n\nfunc (u *StdConn) SupportsMultipleReaders() bool {\n\treturn true\n}\n\nfunc (u *StdConn) Rebind() error {\n\treturn nil\n}\n\nfunc (u *StdConn) SetRecvBuffer(n int) error {\n\treturn unix.SetsockoptInt(u.sysFd, unix.SOL_SOCKET, unix.SO_RCVBUFFORCE, n)\n}\n\nfunc (u *StdConn) SetSendBuffer(n int) error {\n\treturn unix.SetsockoptInt(u.sysFd, unix.SOL_SOCKET, unix.SO_SNDBUFFORCE, n)\n}\n\nfunc (u *StdConn) SetSoMark(mark int) error {\n\treturn unix.SetsockoptInt(u.sysFd, unix.SOL_SOCKET, unix.SO_MARK, mark)\n}\n\nfunc (u *StdConn) GetRecvBuffer() (int, error) {\n\treturn unix.GetsockoptInt(int(u.sysFd), unix.SOL_SOCKET, unix.SO_RCVBUF)\n}\n\nfunc (u *StdConn) GetSendBuffer() (int, error) {\n\treturn unix.GetsockoptInt(int(u.sysFd), unix.SOL_SOCKET, unix.SO_SNDBUF)\n}\n\nfunc (u *StdConn) GetSoMark() (int, error) {\n\treturn unix.GetsockoptInt(int(u.sysFd), unix.SOL_SOCKET, unix.SO_MARK)\n}\n\nfunc (u *StdConn) LocalAddr() (netip.AddrPort, error) {\n\tsa, err := unix.Getsockname(u.sysFd)\n\tif err != nil {\n\t\treturn netip.AddrPort{}, err\n\t}\n\n\tswitch sa := sa.(type) {\n\tcase *unix.SockaddrInet4:\n\t\treturn netip.AddrPortFrom(netip.AddrFrom4(sa.Addr), uint16(sa.Port)), nil\n\n\tcase *unix.SockaddrInet6:\n\t\treturn netip.AddrPortFrom(netip.AddrFrom16(sa.Addr), uint16(sa.Port)), nil\n\n\tdefault:\n\t\treturn netip.AddrPort{}, fmt.Errorf(\"unsupported sock type: %T\", sa)\n\t}\n}\n\nfunc (u *StdConn) ListenOut(r EncReader) {\n\tvar ip netip.Addr\n\n\tmsgs, buffers, names := u.PrepareRawMessages(u.batch)\n\tread := u.ReadMulti\n\tif u.batch == 1 {\n\t\tread = u.ReadSingle\n\t}\n\n\tfor {\n\t\tn, err := read(msgs)\n\t\tif err != nil {\n\t\t\tu.l.WithError(err).Debug(\"udp socket is closed, exiting read loop\")\n\t\t\treturn\n\t\t}\n\n\t\tfor i := 0; i < n; i++ {\n\t\t\t// Its ok to skip the ok check here, the slicing is the only error that can occur and it will panic\n\t\t\tif u.isV4 {\n\t\t\t\tip, _ = netip.AddrFromSlice(names[i][4:8])\n\t\t\t} else {\n\t\t\t\tip, _ = netip.AddrFromSlice(names[i][8:24])\n\t\t\t}\n\t\t\tr(netip.AddrPortFrom(ip.Unmap(), binary.BigEndian.Uint16(names[i][2:4])), buffers[i][:msgs[i].Len])\n\t\t}\n\t}\n}\n\nfunc (u *StdConn) ReadSingle(msgs []rawMessage) (int, error) {\n\tfor {\n\t\tn, _, err := unix.Syscall6(\n\t\t\tunix.SYS_RECVMSG,\n\t\t\tuintptr(u.sysFd),\n\t\t\tuintptr(unsafe.Pointer(&(msgs[0].Hdr))),\n\t\t\t0,\n\t\t\t0,\n\t\t\t0,\n\t\t\t0,\n\t\t)\n\n\t\tif err != 0 {\n\t\t\treturn 0, &net.OpError{Op: \"recvmsg\", Err: err}\n\t\t}\n\n\t\tmsgs[0].Len = uint32(n)\n\t\treturn 1, nil\n\t}\n}\n\nfunc (u *StdConn) ReadMulti(msgs []rawMessage) (int, error) {\n\tfor {\n\t\tn, _, err := unix.Syscall6(\n\t\t\tunix.SYS_RECVMMSG,\n\t\t\tuintptr(u.sysFd),\n\t\t\tuintptr(unsafe.Pointer(&msgs[0])),\n\t\t\tuintptr(len(msgs)),\n\t\t\tunix.MSG_WAITFORONE,\n\t\t\t0,\n\t\t\t0,\n\t\t)\n\n\t\tif err != 0 {\n\t\t\treturn 0, &net.OpError{Op: \"recvmmsg\", Err: err}\n\t\t}\n\n\t\treturn int(n), nil\n\t}\n}\n\nfunc (u *StdConn) WriteTo(b []byte, ip netip.AddrPort) error {\n\tif u.isV4 {\n\t\treturn u.writeTo4(b, ip)\n\t}\n\treturn u.writeTo6(b, ip)\n}\n\nfunc (u *StdConn) writeTo6(b []byte, ip netip.AddrPort) error {\n\tvar rsa unix.RawSockaddrInet6\n\trsa.Family = unix.AF_INET6\n\trsa.Addr = ip.Addr().As16()\n\tbinary.BigEndian.PutUint16((*[2]byte)(unsafe.Pointer(&rsa.Port))[:], ip.Port())\n\n\tfor {\n\t\t_, _, err := unix.Syscall6(\n\t\t\tunix.SYS_SENDTO,\n\t\t\tuintptr(u.sysFd),\n\t\t\tuintptr(unsafe.Pointer(&b[0])),\n\t\t\tuintptr(len(b)),\n\t\t\tuintptr(0),\n\t\t\tuintptr(unsafe.Pointer(&rsa)),\n\t\t\tuintptr(unix.SizeofSockaddrInet6),\n\t\t)\n\n\t\tif err != 0 {\n\t\t\treturn &net.OpError{Op: \"sendto\", Err: err}\n\t\t}\n\n\t\treturn nil\n\t}\n}\n\nfunc (u *StdConn) writeTo4(b []byte, ip netip.AddrPort) error {\n\tif !ip.Addr().Is4() {\n\t\treturn ErrInvalidIPv6RemoteForSocket\n\t}\n\n\tvar rsa unix.RawSockaddrInet4\n\trsa.Family = unix.AF_INET\n\trsa.Addr = ip.Addr().As4()\n\tbinary.BigEndian.PutUint16((*[2]byte)(unsafe.Pointer(&rsa.Port))[:], ip.Port())\n\n\tfor {\n\t\t_, _, err := unix.Syscall6(\n\t\t\tunix.SYS_SENDTO,\n\t\t\tuintptr(u.sysFd),\n\t\t\tuintptr(unsafe.Pointer(&b[0])),\n\t\t\tuintptr(len(b)),\n\t\t\tuintptr(0),\n\t\t\tuintptr(unsafe.Pointer(&rsa)),\n\t\t\tuintptr(unix.SizeofSockaddrInet4),\n\t\t)\n\n\t\tif err != 0 {\n\t\t\treturn &net.OpError{Op: \"sendto\", Err: err}\n\t\t}\n\n\t\treturn nil\n\t}\n}\n\nfunc (u *StdConn) ReloadConfig(c *config.C) {\n\tb := c.GetInt(\"listen.read_buffer\", 0)\n\tif b > 0 {\n\t\terr := u.SetRecvBuffer(b)\n\t\tif err == nil {\n\t\t\ts, err := u.GetRecvBuffer()\n\t\t\tif err == nil {\n\t\t\t\tu.l.WithField(\"size\", s).Info(\"listen.read_buffer was set\")\n\t\t\t} else {\n\t\t\t\tu.l.WithError(err).Warn(\"Failed to get listen.read_buffer\")\n\t\t\t}\n\t\t} else {\n\t\t\tu.l.WithError(err).Error(\"Failed to set listen.read_buffer\")\n\t\t}\n\t}\n\n\tb = c.GetInt(\"listen.write_buffer\", 0)\n\tif b > 0 {\n\t\terr := u.SetSendBuffer(b)\n\t\tif err == nil {\n\t\t\ts, err := u.GetSendBuffer()\n\t\t\tif err == nil {\n\t\t\t\tu.l.WithField(\"size\", s).Info(\"listen.write_buffer was set\")\n\t\t\t} else {\n\t\t\t\tu.l.WithError(err).Warn(\"Failed to get listen.write_buffer\")\n\t\t\t}\n\t\t} else {\n\t\t\tu.l.WithError(err).Error(\"Failed to set listen.write_buffer\")\n\t\t}\n\t}\n\n\tb = c.GetInt(\"listen.so_mark\", 0)\n\ts, err := u.GetSoMark()\n\tif b > 0 || (err == nil && s != 0) {\n\t\terr := u.SetSoMark(b)\n\t\tif err == nil {\n\t\t\ts, err := u.GetSoMark()\n\t\t\tif err == nil {\n\t\t\t\tu.l.WithField(\"mark\", s).Info(\"listen.so_mark was set\")\n\t\t\t} else {\n\t\t\t\tu.l.WithError(err).Warn(\"Failed to get listen.so_mark\")\n\t\t\t}\n\t\t} else {\n\t\t\tu.l.WithError(err).Error(\"Failed to set listen.so_mark\")\n\t\t}\n\t}\n}\n\nfunc (u *StdConn) getMemInfo(meminfo *[unix.SK_MEMINFO_VARS]uint32) error {\n\tvar vallen uint32 = 4 * unix.SK_MEMINFO_VARS\n\t_, _, err := unix.Syscall6(unix.SYS_GETSOCKOPT, uintptr(u.sysFd), uintptr(unix.SOL_SOCKET), uintptr(unix.SO_MEMINFO), uintptr(unsafe.Pointer(meminfo)), uintptr(unsafe.Pointer(&vallen)), 0)\n\tif err != 0 {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (u *StdConn) Close() error {\n\treturn syscall.Close(u.sysFd)\n}\n\nfunc NewUDPStatsEmitter(udpConns []Conn) func() {\n\t// Check if our kernel supports SO_MEMINFO before registering the gauges\n\tvar udpGauges [][unix.SK_MEMINFO_VARS]metrics.Gauge\n\tvar meminfo [unix.SK_MEMINFO_VARS]uint32\n\tif err := udpConns[0].(*StdConn).getMemInfo(&meminfo); err == nil {\n\t\tudpGauges = make([][unix.SK_MEMINFO_VARS]metrics.Gauge, len(udpConns))\n\t\tfor i := range udpConns {\n\t\t\tudpGauges[i] = [unix.SK_MEMINFO_VARS]metrics.Gauge{\n\t\t\t\tmetrics.GetOrRegisterGauge(fmt.Sprintf(\"udp.%d.rmem_alloc\", i), nil),\n\t\t\t\tmetrics.GetOrRegisterGauge(fmt.Sprintf(\"udp.%d.rcvbuf\", i), nil),\n\t\t\t\tmetrics.GetOrRegisterGauge(fmt.Sprintf(\"udp.%d.wmem_alloc\", i), nil),\n\t\t\t\tmetrics.GetOrRegisterGauge(fmt.Sprintf(\"udp.%d.sndbuf\", i), nil),\n\t\t\t\tmetrics.GetOrRegisterGauge(fmt.Sprintf(\"udp.%d.fwd_alloc\", i), nil),\n\t\t\t\tmetrics.GetOrRegisterGauge(fmt.Sprintf(\"udp.%d.wmem_queued\", i), nil),\n\t\t\t\tmetrics.GetOrRegisterGauge(fmt.Sprintf(\"udp.%d.optmem\", i), nil),\n\t\t\t\tmetrics.GetOrRegisterGauge(fmt.Sprintf(\"udp.%d.backlog\", i), nil),\n\t\t\t\tmetrics.GetOrRegisterGauge(fmt.Sprintf(\"udp.%d.drops\", i), nil),\n\t\t\t}\n\t\t}\n\t}\n\n\treturn func() {\n\t\tfor i, gauges := range udpGauges {\n\t\t\tif err := udpConns[i].(*StdConn).getMemInfo(&meminfo); err == nil {\n\t\t\t\tfor j := 0; j < unix.SK_MEMINFO_VARS; j++ {\n\t\t\t\t\tgauges[j].Update(int64(meminfo[j]))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "udp/udp_linux_32.go",
    "content": "//go:build linux && (386 || amd64p32 || arm || mips || mipsle) && !android && !e2e_testing\n// +build linux\n// +build 386 amd64p32 arm mips mipsle\n// +build !android\n// +build !e2e_testing\n\npackage udp\n\nimport (\n\t\"golang.org/x/sys/unix\"\n)\n\ntype iovec struct {\n\tBase *byte\n\tLen  uint32\n}\n\ntype msghdr struct {\n\tName       *byte\n\tNamelen    uint32\n\tIov        *iovec\n\tIovlen     uint32\n\tControl    *byte\n\tControllen uint32\n\tFlags      int32\n}\n\ntype rawMessage struct {\n\tHdr msghdr\n\tLen uint32\n}\n\nfunc (u *StdConn) PrepareRawMessages(n int) ([]rawMessage, [][]byte, [][]byte) {\n\tmsgs := make([]rawMessage, n)\n\tbuffers := make([][]byte, n)\n\tnames := make([][]byte, n)\n\n\tfor i := range msgs {\n\t\tbuffers[i] = make([]byte, MTU)\n\t\tnames[i] = make([]byte, unix.SizeofSockaddrInet6)\n\n\t\tvs := []iovec{\n\t\t\t{Base: &buffers[i][0], Len: uint32(len(buffers[i]))},\n\t\t}\n\n\t\tmsgs[i].Hdr.Iov = &vs[0]\n\t\tmsgs[i].Hdr.Iovlen = uint32(len(vs))\n\n\t\tmsgs[i].Hdr.Name = &names[i][0]\n\t\tmsgs[i].Hdr.Namelen = uint32(len(names[i]))\n\t}\n\n\treturn msgs, buffers, names\n}\n"
  },
  {
    "path": "udp/udp_linux_64.go",
    "content": "//go:build linux && (amd64 || arm64 || ppc64 || ppc64le || mips64 || mips64le || s390x || riscv64 || loong64) && !android && !e2e_testing\n// +build linux\n// +build amd64 arm64 ppc64 ppc64le mips64 mips64le s390x riscv64 loong64\n// +build !android\n// +build !e2e_testing\n\npackage udp\n\nimport (\n\t\"golang.org/x/sys/unix\"\n)\n\ntype iovec struct {\n\tBase *byte\n\tLen  uint64\n}\n\ntype msghdr struct {\n\tName       *byte\n\tNamelen    uint32\n\tPad0       [4]byte\n\tIov        *iovec\n\tIovlen     uint64\n\tControl    *byte\n\tControllen uint64\n\tFlags      int32\n\tPad1       [4]byte\n}\n\ntype rawMessage struct {\n\tHdr  msghdr\n\tLen  uint32\n\tPad0 [4]byte\n}\n\nfunc (u *StdConn) PrepareRawMessages(n int) ([]rawMessage, [][]byte, [][]byte) {\n\tmsgs := make([]rawMessage, n)\n\tbuffers := make([][]byte, n)\n\tnames := make([][]byte, n)\n\n\tfor i := range msgs {\n\t\tbuffers[i] = make([]byte, MTU)\n\t\tnames[i] = make([]byte, unix.SizeofSockaddrInet6)\n\n\t\tvs := []iovec{\n\t\t\t{Base: &buffers[i][0], Len: uint64(len(buffers[i]))},\n\t\t}\n\n\t\tmsgs[i].Hdr.Iov = &vs[0]\n\t\tmsgs[i].Hdr.Iovlen = uint64(len(vs))\n\n\t\tmsgs[i].Hdr.Name = &names[i][0]\n\t\tmsgs[i].Hdr.Namelen = uint32(len(names[i]))\n\t}\n\n\treturn msgs, buffers, names\n}\n"
  },
  {
    "path": "udp/udp_netbsd.go",
    "content": "//go:build !e2e_testing\n// +build !e2e_testing\n\npackage udp\n\n// FreeBSD support is primarily implemented in udp_generic, besides NewListenConfig\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"syscall\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc NewListener(l *logrus.Logger, ip netip.Addr, port int, multi bool, batch int) (Conn, error) {\n\treturn NewGenericListener(l, ip, port, multi, batch)\n}\n\nfunc NewListenConfig(multi bool) net.ListenConfig {\n\treturn net.ListenConfig{\n\t\tControl: func(network, address string, c syscall.RawConn) error {\n\t\t\tif multi {\n\t\t\t\tvar controlErr error\n\t\t\t\terr := c.Control(func(fd uintptr) {\n\t\t\t\t\tif err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, unix.SO_REUSEPORT, 1); err != nil {\n\t\t\t\t\t\tcontrolErr = fmt.Errorf(\"SO_REUSEPORT failed: %v\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif controlErr != nil {\n\t\t\t\t\treturn controlErr\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n}\n\nfunc (u *GenericConn) Rebind() error {\n\treturn nil\n}\n"
  },
  {
    "path": "udp/udp_rio_windows.go",
    "content": "//go:build !e2e_testing\n// +build !e2e_testing\n\n// Inspired by https://git.zx2c4.com/wireguard-go/tree/conn/bind_windows.go\n\npackage udp\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/netip\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"time\"\n\t\"unsafe\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/config\"\n\t\"golang.org/x/sys/windows\"\n\t\"golang.zx2c4.com/wireguard/conn/winrio\"\n)\n\n// Assert we meet the standard conn interface\nvar _ Conn = &RIOConn{}\n\n//go:linkname procyield runtime.procyield\nfunc procyield(cycles uint32)\n\nconst (\n\tpacketsPerRing = 1024\n\tbytesPerPacket = 2048 - 32\n\treceiveSpins   = 15\n)\n\ntype ringPacket struct {\n\taddr windows.RawSockaddrInet6\n\tdata [bytesPerPacket]byte\n}\n\ntype ringBuffer struct {\n\tpackets    uintptr\n\thead, tail uint32\n\tid         winrio.BufferId\n\tiocp       windows.Handle\n\tisFull     bool\n\tcq         winrio.Cq\n\tmu         sync.Mutex\n\toverlapped windows.Overlapped\n}\n\ntype RIOConn struct {\n\tisOpen  atomic.Bool\n\tl       *logrus.Logger\n\tsock    windows.Handle\n\trx, tx  ringBuffer\n\trq      winrio.Rq\n\tresults [packetsPerRing]winrio.Result\n}\n\nfunc NewRIOListener(l *logrus.Logger, addr netip.Addr, port int) (*RIOConn, error) {\n\tif !winrio.Initialize() {\n\t\treturn nil, errors.New(\"could not initialize winrio\")\n\t}\n\n\tu := &RIOConn{l: l}\n\n\terr := u.bind(l, &windows.SockaddrInet6{Addr: addr.As16(), Port: port})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"bind: %w\", err)\n\t}\n\n\tfor i := 0; i < packetsPerRing; i++ {\n\t\terr = u.insertReceiveRequest()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"init rx ring: %w\", err)\n\t\t}\n\t}\n\n\tu.isOpen.Store(true)\n\treturn u, nil\n}\n\nfunc (u *RIOConn) bind(l *logrus.Logger, sa windows.Sockaddr) error {\n\tvar err error\n\tu.sock, err = winrio.Socket(windows.AF_INET6, windows.SOCK_DGRAM, windows.IPPROTO_UDP)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"winrio.Socket error: %w\", err)\n\t}\n\n\t// Enable v4 for this socket\n\tsyscall.SetsockoptInt(syscall.Handle(u.sock), syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY, 0)\n\n\t// Disable reporting of PORT_UNREACHABLE and NET_UNREACHABLE errors from the UDP socket receive call.\n\t// These errors are returned on Windows during UDP receives based on the receipt of ICMP packets. Disable\n\t// the UDP receive error returns with these ioctl calls.\n\tret := uint32(0)\n\tflag := uint32(0)\n\tsize := uint32(unsafe.Sizeof(flag))\n\terr = syscall.WSAIoctl(syscall.Handle(u.sock), syscall.SIO_UDP_CONNRESET, (*byte)(unsafe.Pointer(&flag)), size, nil, 0, &ret, nil, 0)\n\tif err != nil {\n\t\t// This is a best-effort to prevent errors from being returned by the udp recv operation.\n\t\t// Quietly log a failure and continue.\n\t\tl.WithError(err).Debug(\"failed to set UDP_CONNRESET ioctl\")\n\t}\n\n\tret = 0\n\tflag = 0\n\tsize = uint32(unsafe.Sizeof(flag))\n\tSIO_UDP_NETRESET := uint32(syscall.IOC_IN | syscall.IOC_VENDOR | 15)\n\terr = syscall.WSAIoctl(syscall.Handle(u.sock), SIO_UDP_NETRESET, (*byte)(unsafe.Pointer(&flag)), size, nil, 0, &ret, nil, 0)\n\tif err != nil {\n\t\t// This is a best-effort to prevent errors from being returned by the udp recv operation.\n\t\t// Quietly log a failure and continue.\n\t\tl.WithError(err).Debug(\"failed to set UDP_NETRESET ioctl\")\n\t}\n\n\terr = u.rx.Open()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error rx.Open(): %w\", err)\n\t}\n\n\terr = u.tx.Open()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error tx.Open(): %w\", err)\n\t}\n\n\tu.rq, err = winrio.CreateRequestQueue(u.sock, packetsPerRing, 1, packetsPerRing, 1, u.rx.cq, u.tx.cq, 0)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error CreateRequestQueue: %w\", err)\n\t}\n\n\terr = windows.Bind(u.sock, sa)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error windows.Bind(): %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (u *RIOConn) ListenOut(r EncReader) {\n\tbuffer := make([]byte, MTU)\n\n\tvar lastRecvErr time.Time\n\n\tfor {\n\t\t// Just read one packet at a time\n\t\tn, rua, err := u.receive(buffer)\n\n\t\tif err != nil {\n\t\t\tif errors.Is(err, net.ErrClosed) {\n\t\t\t\tu.l.WithError(err).Debug(\"udp socket is closed, exiting read loop\")\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// Dampen unexpected message warns to once per minute\n\t\t\tif lastRecvErr.IsZero() || time.Since(lastRecvErr) > time.Minute {\n\t\t\t\tlastRecvErr = time.Now()\n\t\t\t\tu.l.WithError(err).Warn(\"unexpected udp socket receive error\")\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tr(netip.AddrPortFrom(netip.AddrFrom16(rua.Addr).Unmap(), (rua.Port>>8)|((rua.Port&0xff)<<8)), buffer[:n])\n\t}\n}\n\nfunc (u *RIOConn) insertReceiveRequest() error {\n\tpacket := u.rx.Push()\n\tdataBuffer := &winrio.Buffer{\n\t\tId:     u.rx.id,\n\t\tOffset: uint32(uintptr(unsafe.Pointer(&packet.data[0])) - u.rx.packets),\n\t\tLength: uint32(len(packet.data)),\n\t}\n\taddressBuffer := &winrio.Buffer{\n\t\tId:     u.rx.id,\n\t\tOffset: uint32(uintptr(unsafe.Pointer(&packet.addr)) - u.rx.packets),\n\t\tLength: uint32(unsafe.Sizeof(packet.addr)),\n\t}\n\n\treturn winrio.ReceiveEx(u.rq, dataBuffer, 1, nil, addressBuffer, nil, nil, 0, uintptr(unsafe.Pointer(packet)))\n}\n\nfunc (u *RIOConn) receive(buf []byte) (int, windows.RawSockaddrInet6, error) {\n\tif !u.isOpen.Load() {\n\t\treturn 0, windows.RawSockaddrInet6{}, net.ErrClosed\n\t}\n\n\tu.rx.mu.Lock()\n\tdefer u.rx.mu.Unlock()\n\n\tvar err error\n\tvar count uint32\n\tvar results [1]winrio.Result\n\nretry:\n\tcount = 0\n\tfor tries := 0; count == 0 && tries < receiveSpins; tries++ {\n\t\tif tries > 0 {\n\t\t\tif !u.isOpen.Load() {\n\t\t\t\treturn 0, windows.RawSockaddrInet6{}, net.ErrClosed\n\t\t\t}\n\t\t\tprocyield(1)\n\t\t}\n\n\t\tcount = winrio.DequeueCompletion(u.rx.cq, results[:])\n\t}\n\n\tif count == 0 {\n\t\terr = winrio.Notify(u.rx.cq)\n\t\tif err != nil {\n\t\t\treturn 0, windows.RawSockaddrInet6{}, err\n\t\t}\n\t\tvar bytes uint32\n\t\tvar key uintptr\n\t\tvar overlapped *windows.Overlapped\n\t\terr = windows.GetQueuedCompletionStatus(u.rx.iocp, &bytes, &key, &overlapped, windows.INFINITE)\n\t\tif err != nil {\n\t\t\treturn 0, windows.RawSockaddrInet6{}, err\n\t\t}\n\n\t\tif !u.isOpen.Load() {\n\t\t\treturn 0, windows.RawSockaddrInet6{}, net.ErrClosed\n\t\t}\n\n\t\tcount = winrio.DequeueCompletion(u.rx.cq, results[:])\n\t\tif count == 0 {\n\t\t\treturn 0, windows.RawSockaddrInet6{}, io.ErrNoProgress\n\n\t\t}\n\t}\n\n\tu.rx.Return(1)\n\terr = u.insertReceiveRequest()\n\tif err != nil {\n\t\treturn 0, windows.RawSockaddrInet6{}, err\n\t}\n\n\t// We limit the MTU well below the 65k max for practicality, but this means a remote host can still send us\n\t// huge packets. Just try again when this happens. The infinite loop this could cause is still limited to\n\t// attacker bandwidth, just like the rest of the receive path.\n\tif windows.Errno(results[0].Status) == windows.WSAEMSGSIZE {\n\t\tgoto retry\n\t}\n\n\tif results[0].Status != 0 {\n\t\treturn 0, windows.RawSockaddrInet6{}, windows.Errno(results[0].Status)\n\t}\n\n\tpacket := (*ringPacket)(unsafe.Pointer(uintptr(results[0].RequestContext)))\n\tep := packet.addr\n\tn := copy(buf, packet.data[:results[0].BytesTransferred])\n\treturn n, ep, nil\n}\n\nfunc (u *RIOConn) WriteTo(buf []byte, ip netip.AddrPort) error {\n\tif !u.isOpen.Load() {\n\t\treturn net.ErrClosed\n\t}\n\n\tif len(buf) > bytesPerPacket {\n\t\treturn io.ErrShortBuffer\n\t}\n\n\tu.tx.mu.Lock()\n\tdefer u.tx.mu.Unlock()\n\n\tcount := winrio.DequeueCompletion(u.tx.cq, u.results[:])\n\tif count == 0 && u.tx.isFull {\n\t\terr := winrio.Notify(u.tx.cq)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tvar bytes uint32\n\t\tvar key uintptr\n\t\tvar overlapped *windows.Overlapped\n\t\terr = windows.GetQueuedCompletionStatus(u.tx.iocp, &bytes, &key, &overlapped, windows.INFINITE)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif !u.isOpen.Load() {\n\t\t\treturn net.ErrClosed\n\t\t}\n\n\t\tcount = winrio.DequeueCompletion(u.tx.cq, u.results[:])\n\t\tif count == 0 {\n\t\t\treturn io.ErrNoProgress\n\t\t}\n\t}\n\n\tif count > 0 {\n\t\tu.tx.Return(count)\n\t}\n\n\tpacket := u.tx.Push()\n\tpacket.addr.Family = windows.AF_INET6\n\tpacket.addr.Addr = ip.Addr().As16()\n\tport := ip.Port()\n\tpacket.addr.Port = (port >> 8) | ((port & 0xff) << 8)\n\tcopy(packet.data[:], buf)\n\n\tdataBuffer := &winrio.Buffer{\n\t\tId:     u.tx.id,\n\t\tOffset: uint32(uintptr(unsafe.Pointer(&packet.data[0])) - u.tx.packets),\n\t\tLength: uint32(len(buf)),\n\t}\n\n\taddressBuffer := &winrio.Buffer{\n\t\tId:     u.tx.id,\n\t\tOffset: uint32(uintptr(unsafe.Pointer(&packet.addr)) - u.tx.packets),\n\t\tLength: uint32(unsafe.Sizeof(packet.addr)),\n\t}\n\n\treturn winrio.SendEx(u.rq, dataBuffer, 1, nil, addressBuffer, nil, nil, 0, 0)\n}\n\nfunc (u *RIOConn) LocalAddr() (netip.AddrPort, error) {\n\tsa, err := windows.Getsockname(u.sock)\n\tif err != nil {\n\t\treturn netip.AddrPort{}, err\n\t}\n\n\tv6 := sa.(*windows.SockaddrInet6)\n\treturn netip.AddrPortFrom(netip.AddrFrom16(v6.Addr).Unmap(), uint16(v6.Port)), nil\n\n}\n\nfunc (u *RIOConn) SupportsMultipleReaders() bool {\n\treturn false\n}\n\nfunc (u *RIOConn) Rebind() error {\n\treturn nil\n}\n\nfunc (u *RIOConn) ReloadConfig(*config.C) {}\n\nfunc (u *RIOConn) Close() error {\n\tif !u.isOpen.CompareAndSwap(true, false) {\n\t\treturn nil\n\t}\n\n\twindows.PostQueuedCompletionStatus(u.rx.iocp, 0, 0, nil)\n\twindows.PostQueuedCompletionStatus(u.tx.iocp, 0, 0, nil)\n\n\tu.rx.CloseAndZero()\n\tu.tx.CloseAndZero()\n\tif u.sock != 0 {\n\t\twindows.CloseHandle(u.sock)\n\t}\n\treturn nil\n}\n\nfunc (ring *ringBuffer) Push() *ringPacket {\n\tfor ring.isFull {\n\t\tpanic(\"ring is full\")\n\t}\n\tret := (*ringPacket)(unsafe.Pointer(ring.packets + (uintptr(ring.tail%packetsPerRing) * unsafe.Sizeof(ringPacket{}))))\n\tring.tail += 1\n\tif ring.tail%packetsPerRing == ring.head%packetsPerRing {\n\t\tring.isFull = true\n\t}\n\treturn ret\n}\n\nfunc (ring *ringBuffer) Return(count uint32) {\n\tif ring.head%packetsPerRing == ring.tail%packetsPerRing && !ring.isFull {\n\t\treturn\n\t}\n\tring.head += count\n\tring.isFull = false\n}\n\nfunc (ring *ringBuffer) CloseAndZero() {\n\tif ring.cq != 0 {\n\t\twinrio.CloseCompletionQueue(ring.cq)\n\t\tring.cq = 0\n\t}\n\n\tif ring.iocp != 0 {\n\t\twindows.CloseHandle(ring.iocp)\n\t\tring.iocp = 0\n\t}\n\n\tif ring.id != 0 {\n\t\twinrio.DeregisterBuffer(ring.id)\n\t\tring.id = 0\n\t}\n\n\tif ring.packets != 0 {\n\t\twindows.VirtualFree(ring.packets, 0, windows.MEM_RELEASE)\n\t\tring.packets = 0\n\t}\n\n\tring.head = 0\n\tring.tail = 0\n\tring.isFull = false\n}\n\nfunc (ring *ringBuffer) Open() error {\n\tvar err error\n\tpacketsLen := unsafe.Sizeof(ringPacket{}) * packetsPerRing\n\tring.packets, err = windows.VirtualAlloc(0, packetsLen, windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_READWRITE)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tring.id, err = winrio.RegisterPointer(unsafe.Pointer(ring.packets), uint32(packetsLen))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tring.iocp, err = windows.CreateIoCompletionPort(windows.InvalidHandle, 0, 0, 0)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tring.cq, err = winrio.CreateIOCPCompletionQueue(packetsPerRing, ring.iocp, 0, &ring.overlapped)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "udp/udp_tester.go",
    "content": "//go:build e2e_testing\n// +build e2e_testing\n\npackage udp\n\nimport (\n\t\"io\"\n\t\"net/netip\"\n\t\"sync/atomic\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/slackhq/nebula/config\"\n\t\"github.com/slackhq/nebula/header\"\n)\n\ntype Packet struct {\n\tTo   netip.AddrPort\n\tFrom netip.AddrPort\n\tData []byte\n}\n\nfunc (u *Packet) Copy() *Packet {\n\tn := &Packet{\n\t\tTo:   u.To,\n\t\tFrom: u.From,\n\t\tData: make([]byte, len(u.Data)),\n\t}\n\n\tcopy(n.Data, u.Data)\n\treturn n\n}\n\ntype TesterConn struct {\n\tAddr netip.AddrPort\n\n\tRxPackets chan *Packet // Packets to receive into nebula\n\tTxPackets chan *Packet // Packets transmitted outside by nebula\n\n\tclosed atomic.Bool\n\tl      *logrus.Logger\n}\n\nfunc NewListener(l *logrus.Logger, ip netip.Addr, port int, _ bool, _ int) (Conn, error) {\n\treturn &TesterConn{\n\t\tAddr:      netip.AddrPortFrom(ip, uint16(port)),\n\t\tRxPackets: make(chan *Packet, 10),\n\t\tTxPackets: make(chan *Packet, 10),\n\t\tl:         l,\n\t}, nil\n}\n\n// Send will place a UdpPacket onto the receive queue for nebula to consume\n// this is an encrypted packet or a handshake message in most cases\n// packets were transmitted from another nebula node, you can send them with Tun.Send\nfunc (u *TesterConn) Send(packet *Packet) {\n\tif u.closed.Load() {\n\t\treturn\n\t}\n\n\th := &header.H{}\n\tif err := h.Parse(packet.Data); err != nil {\n\t\tpanic(err)\n\t}\n\tif u.l.Level >= logrus.DebugLevel {\n\t\tu.l.WithField(\"header\", h).\n\t\t\tWithField(\"udpAddr\", packet.From).\n\t\t\tWithField(\"dataLen\", len(packet.Data)).\n\t\t\tDebug(\"UDP receiving injected packet\")\n\t}\n\tu.RxPackets <- packet\n}\n\n// Get will pull a UdpPacket from the transmit queue\n// nebula meant to send this message on the network, it will be encrypted\n// packets were ingested from the tun side (in most cases), you can send them with Tun.Send\nfunc (u *TesterConn) Get(block bool) *Packet {\n\tif block {\n\t\treturn <-u.TxPackets\n\t}\n\n\tselect {\n\tcase p := <-u.TxPackets:\n\t\treturn p\n\tdefault:\n\t\treturn nil\n\t}\n}\n\n//********************************************************************************************************************//\n// Below this is boilerplate implementation to make nebula actually work\n//********************************************************************************************************************//\n\nfunc (u *TesterConn) WriteTo(b []byte, addr netip.AddrPort) error {\n\tif u.closed.Load() {\n\t\treturn io.ErrClosedPipe\n\t}\n\n\tp := &Packet{\n\t\tData: make([]byte, len(b), len(b)),\n\t\tFrom: u.Addr,\n\t\tTo:   addr,\n\t}\n\n\tcopy(p.Data, b)\n\tu.TxPackets <- p\n\treturn nil\n}\n\nfunc (u *TesterConn) ListenOut(r EncReader) {\n\tfor {\n\t\tp, ok := <-u.RxPackets\n\t\tif !ok {\n\t\t\treturn\n\t\t}\n\t\tr(p.From, p.Data)\n\t}\n}\n\nfunc (u *TesterConn) ReloadConfig(*config.C) {}\n\nfunc NewUDPStatsEmitter(_ []Conn) func() {\n\t// No UDP stats for non-linux\n\treturn func() {}\n}\n\nfunc (u *TesterConn) LocalAddr() (netip.AddrPort, error) {\n\treturn u.Addr, nil\n}\n\nfunc (u *TesterConn) SupportsMultipleReaders() bool {\n\treturn false\n}\n\nfunc (u *TesterConn) Rebind() error {\n\treturn nil\n}\n\nfunc (u *TesterConn) Close() error {\n\tif u.closed.CompareAndSwap(false, true) {\n\t\tclose(u.RxPackets)\n\t\tclose(u.TxPackets)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "udp/udp_windows.go",
    "content": "//go:build !e2e_testing\n// +build !e2e_testing\n\npackage udp\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"syscall\"\n\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc NewListener(l *logrus.Logger, ip netip.Addr, port int, multi bool, batch int) (Conn, error) {\n\tif multi {\n\t\t//NOTE: Technically we can support it with RIO but it wouldn't be at the socket level\n\t\t// The udp stack would need to be reworked to hide away the implementation differences between\n\t\t// Windows and Linux\n\t\treturn nil, fmt.Errorf(\"multiple udp listeners not supported on windows\")\n\t}\n\n\trc, err := NewRIOListener(l, ip, port)\n\tif err == nil {\n\t\treturn rc, nil\n\t}\n\n\tl.WithError(err).Error(\"Falling back to standard udp sockets\")\n\treturn NewGenericListener(l, ip, port, multi, batch)\n}\n\nfunc NewListenConfig(multi bool) net.ListenConfig {\n\treturn net.ListenConfig{\n\t\tControl: func(network, address string, c syscall.RawConn) error {\n\t\t\tif multi {\n\t\t\t\t// There is no way to support multiple listeners safely on Windows:\n\t\t\t\t// https://docs.microsoft.com/en-us/windows/desktop/winsock/using-so-reuseaddr-and-so-exclusiveaddruse\n\t\t\t\treturn fmt.Errorf(\"multiple udp listeners not supported on windows\")\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n}\n\nfunc (u *GenericConn) Rebind() error {\n\treturn nil\n}\n"
  },
  {
    "path": "util/error.go",
    "content": "package util\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype ContextualError struct {\n\tRealError error\n\tFields    map[string]any\n\tContext   string\n}\n\nfunc NewContextualError(msg string, fields map[string]any, realError error) *ContextualError {\n\treturn &ContextualError{Context: msg, Fields: fields, RealError: realError}\n}\n\n// ContextualizeIfNeeded is a helper function to turn an error into a ContextualError if it is not already one\nfunc ContextualizeIfNeeded(msg string, err error) error {\n\tswitch err.(type) {\n\tcase *ContextualError:\n\t\treturn err\n\tdefault:\n\t\treturn NewContextualError(msg, nil, err)\n\t}\n}\n\n// LogWithContextIfNeeded is a helper function to log an error line for an error or ContextualError\nfunc LogWithContextIfNeeded(msg string, err error, l *logrus.Logger) {\n\tswitch v := err.(type) {\n\tcase *ContextualError:\n\t\tv.Log(l)\n\tdefault:\n\t\tl.WithError(err).Error(msg)\n\t}\n}\n\nfunc (ce *ContextualError) Error() string {\n\tif ce.RealError == nil {\n\t\treturn ce.Context\n\t}\n\treturn fmt.Errorf(\"%s (%v): %w\", ce.Context, ce.Fields, ce.RealError).Error()\n}\n\nfunc (ce *ContextualError) Unwrap() error {\n\tif ce.RealError == nil {\n\t\treturn errors.New(ce.Context)\n\t}\n\treturn ce.RealError\n}\n\nfunc (ce *ContextualError) Log(lr *logrus.Logger) {\n\tif ce.RealError != nil {\n\t\tlr.WithFields(ce.Fields).WithError(ce.RealError).Error(ce.Context)\n\t} else {\n\t\tlr.WithFields(ce.Fields).Error(ce.Context)\n\t}\n}\n"
  },
  {
    "path": "util/error_test.go",
    "content": "package util\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\ntype m = map[string]any\n\ntype TestLogWriter struct {\n\tLogs []string\n}\n\nfunc NewTestLogWriter() *TestLogWriter {\n\treturn &TestLogWriter{Logs: make([]string, 0)}\n}\n\nfunc (tl *TestLogWriter) Write(p []byte) (n int, err error) {\n\ttl.Logs = append(tl.Logs, string(p))\n\treturn len(p), nil\n}\n\nfunc (tl *TestLogWriter) Reset() {\n\ttl.Logs = tl.Logs[:0]\n}\n\nfunc TestContextualError_Log(t *testing.T) {\n\tl := logrus.New()\n\tl.Formatter = &logrus.TextFormatter{\n\t\tDisableTimestamp: true,\n\t\tDisableColors:    true,\n\t}\n\n\ttl := NewTestLogWriter()\n\tl.Out = tl\n\n\t// Test a full context line\n\ttl.Reset()\n\te := NewContextualError(\"test message\", m{\"field\": \"1\"}, errors.New(\"error\"))\n\te.Log(l)\n\tassert.Equal(t, []string{\"level=error msg=\\\"test message\\\" error=error field=1\\n\"}, tl.Logs)\n\n\t// Test a line with an error and msg but no fields\n\ttl.Reset()\n\te = NewContextualError(\"test message\", nil, errors.New(\"error\"))\n\te.Log(l)\n\tassert.Equal(t, []string{\"level=error msg=\\\"test message\\\" error=error\\n\"}, tl.Logs)\n\n\t// Test just a context and fields\n\ttl.Reset()\n\te = NewContextualError(\"test message\", m{\"field\": \"1\"}, nil)\n\te.Log(l)\n\tassert.Equal(t, []string{\"level=error msg=\\\"test message\\\" field=1\\n\"}, tl.Logs)\n\n\t// Test just a context\n\ttl.Reset()\n\te = NewContextualError(\"test message\", nil, nil)\n\te.Log(l)\n\tassert.Equal(t, []string{\"level=error msg=\\\"test message\\\"\\n\"}, tl.Logs)\n\n\t// Test just an error\n\ttl.Reset()\n\te = NewContextualError(\"\", nil, errors.New(\"error\"))\n\te.Log(l)\n\tassert.Equal(t, []string{\"level=error error=error\\n\"}, tl.Logs)\n}\n\nfunc TestLogWithContextIfNeeded(t *testing.T) {\n\tl := logrus.New()\n\tl.Formatter = &logrus.TextFormatter{\n\t\tDisableTimestamp: true,\n\t\tDisableColors:    true,\n\t}\n\n\ttl := NewTestLogWriter()\n\tl.Out = tl\n\n\t// Test ignoring fallback context\n\ttl.Reset()\n\te := NewContextualError(\"test message\", m{\"field\": \"1\"}, errors.New(\"error\"))\n\tLogWithContextIfNeeded(\"This should get thrown away\", e, l)\n\tassert.Equal(t, []string{\"level=error msg=\\\"test message\\\" error=error field=1\\n\"}, tl.Logs)\n\n\t// Test using fallback context\n\ttl.Reset()\n\terr := fmt.Errorf(\"this is a normal error\")\n\tLogWithContextIfNeeded(\"Fallback context woo\", err, l)\n\tassert.Equal(t, []string{\"level=error msg=\\\"Fallback context woo\\\" error=\\\"this is a normal error\\\"\\n\"}, tl.Logs)\n}\n\nfunc TestContextualizeIfNeeded(t *testing.T) {\n\t// Test ignoring fallback context\n\te := NewContextualError(\"test message\", m{\"field\": \"1\"}, errors.New(\"error\"))\n\tassert.Same(t, e, ContextualizeIfNeeded(\"should be ignored\", e))\n\n\t// Test using fallback context\n\terr := fmt.Errorf(\"this is a normal error\")\n\tcErr := ContextualizeIfNeeded(\"Fallback context woo\", err)\n\n\tswitch v := cErr.(type) {\n\tcase *ContextualError:\n\t\tassert.Equal(t, err, v.RealError)\n\tdefault:\n\t\tt.Error(\"Error was not wrapped\")\n\t\tt.Fail()\n\t}\n}\n"
  },
  {
    "path": "wintun/device.go",
    "content": "//go:build windows\n// +build windows\n\n/* SPDX-License-Identifier: MIT\n *\n * Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.\n */\n\n//NOTE: this file was forked from https://git.zx2c4.com/wireguard-go/tree/tun/tun.go?id=851efb1bb65555e0f765a3361c8eb5ac47435b19\n\npackage wintun\n\nimport (\n\t\"os\"\n)\n\ntype Device interface {\n\tFile() *os.File                 // returns the file descriptor of the device\n\tRead([]byte, int) (int, error)  // read a packet from the device (without any additional headers)\n\tWrite([]byte, int) (int, error) // writes a packet to the device (without any additional headers)\n\tFlush() error                   // flush all previous writes to the device\n\tName() (string, error)          // fetches and returns the current name\n\tClose() error                   // stops the device and closes the event channel\n}\n"
  },
  {
    "path": "wintun/tun.go",
    "content": "//go:build windows\n// +build windows\n\n/* SPDX-License-Identifier: MIT\n *\n * Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved.\n */\n\n//NOTE: This file was forked from https://git.zx2c4.com/wireguard-go/tree/tun/tun_windows.go?id=851efb1bb65555e0f765a3361c8eb5ac47435b19\n// Mainly to shed functionality we won't be using and to fix names that display in the system\n\npackage wintun\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\t_ \"unsafe\"\n\n\t\"golang.org/x/sys/windows\"\n\n\t\"golang.zx2c4.com/wintun\"\n)\n\nconst (\n\trateMeasurementGranularity = uint64((time.Second / 2) / time.Nanosecond)\n\tspinloopRateThreshold      = 800000000 / 8                                   // 800mbps\n\tspinloopDuration           = uint64(time.Millisecond / 80 / time.Nanosecond) // ~1gbit/s\n)\n\ntype rateJuggler struct {\n\tcurrent       uint64\n\tnextByteCount uint64\n\tnextStartTime int64\n\tchanging      int32\n}\n\ntype NativeTun struct {\n\twt        *wintun.Adapter\n\tname      string\n\thandle    windows.Handle\n\trate      rateJuggler\n\tsession   wintun.Session\n\treadWait  windows.Handle\n\trunning   sync.WaitGroup\n\tcloseOnce sync.Once\n\tclose     int32\n}\n\nvar WintunTunnelType = \"Nebula\"\nvar WintunStaticRequestedGUID *windows.GUID\n\n//go:linkname procyield runtime.procyield\nfunc procyield(cycles uint32)\n\n//go:linkname nanotime runtime.nanotime\nfunc nanotime() int64\n\n// CreateTUN creates a Wintun interface with the given name. Should a Wintun\n// interface with the same name exist, it is reused.\nfunc CreateTUN(ifname string, mtu int) (Device, error) {\n\treturn CreateTUNWithRequestedGUID(ifname, WintunStaticRequestedGUID, mtu)\n}\n\n// CreateTUNWithRequestedGUID creates a Wintun interface with the given name and\n// a requested GUID. Should a Wintun interface with the same name exist, it is reused.\nfunc CreateTUNWithRequestedGUID(ifname string, requestedGUID *windows.GUID, mtu int) (Device, error) {\n\twt, err := wintun.CreateAdapter(ifname, WintunTunnelType, requestedGUID)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Error creating interface: %w\", err)\n\t}\n\n\ttun := &NativeTun{\n\t\twt:     wt,\n\t\tname:   ifname,\n\t\thandle: windows.InvalidHandle,\n\t}\n\n\ttun.session, err = wt.StartSession(0x800000) // Ring capacity, 8 MiB\n\tif err != nil {\n\t\ttun.wt.Close()\n\t\treturn nil, fmt.Errorf(\"Error starting session: %w\", err)\n\t}\n\ttun.readWait = tun.session.ReadWaitEvent()\n\treturn tun, nil\n}\n\nfunc (tun *NativeTun) Name() (string, error) {\n\treturn tun.name, nil\n}\n\nfunc (tun *NativeTun) File() *os.File {\n\treturn nil\n}\n\nfunc (tun *NativeTun) Close() error {\n\tvar err error\n\ttun.closeOnce.Do(func() {\n\t\tatomic.StoreInt32(&tun.close, 1)\n\t\twindows.SetEvent(tun.readWait)\n\t\ttun.running.Wait()\n\t\ttun.session.End()\n\t\tif tun.wt != nil {\n\t\t\ttun.wt.Close()\n\t\t}\n\t})\n\treturn err\n}\n\n// Note: Read() and Write() assume the caller comes only from a single thread; there's no locking.\n\nfunc (tun *NativeTun) Read(buff []byte, offset int) (int, error) {\n\ttun.running.Add(1)\n\tdefer tun.running.Done()\nretry:\n\tif atomic.LoadInt32(&tun.close) == 1 {\n\t\treturn 0, os.ErrClosed\n\t}\n\tstart := nanotime()\n\tshouldSpin := atomic.LoadUint64(&tun.rate.current) >= spinloopRateThreshold && uint64(start-atomic.LoadInt64(&tun.rate.nextStartTime)) <= rateMeasurementGranularity*2\n\tfor {\n\t\tif atomic.LoadInt32(&tun.close) == 1 {\n\t\t\treturn 0, os.ErrClosed\n\t\t}\n\t\tpacket, err := tun.session.ReceivePacket()\n\t\tswitch err {\n\t\tcase nil:\n\t\t\tpacketSize := len(packet)\n\t\t\tcopy(buff[offset:], packet)\n\t\t\ttun.session.ReleaseReceivePacket(packet)\n\t\t\ttun.rate.update(uint64(packetSize))\n\t\t\treturn packetSize, nil\n\t\tcase windows.ERROR_NO_MORE_ITEMS:\n\t\t\tif !shouldSpin || uint64(nanotime()-start) >= spinloopDuration {\n\t\t\t\twindows.WaitForSingleObject(tun.readWait, windows.INFINITE)\n\t\t\t\tgoto retry\n\t\t\t}\n\t\t\tprocyield(1)\n\t\t\tcontinue\n\t\tcase windows.ERROR_HANDLE_EOF:\n\t\t\treturn 0, os.ErrClosed\n\t\tcase windows.ERROR_INVALID_DATA:\n\t\t\treturn 0, errors.New(\"Send ring corrupt\")\n\t\t}\n\t\treturn 0, fmt.Errorf(\"Read failed: %w\", err)\n\t}\n}\n\nfunc (tun *NativeTun) Flush() error {\n\treturn nil\n}\n\nfunc (tun *NativeTun) Write(buff []byte, offset int) (int, error) {\n\ttun.running.Add(1)\n\tdefer tun.running.Done()\n\tif atomic.LoadInt32(&tun.close) == 1 {\n\t\treturn 0, os.ErrClosed\n\t}\n\n\tpacketSize := len(buff) - offset\n\ttun.rate.update(uint64(packetSize))\n\n\tpacket, err := tun.session.AllocateSendPacket(packetSize)\n\tif err == nil {\n\t\tcopy(packet, buff[offset:])\n\t\ttun.session.SendPacket(packet)\n\t\treturn packetSize, nil\n\t}\n\tswitch err {\n\tcase windows.ERROR_HANDLE_EOF:\n\t\treturn 0, os.ErrClosed\n\tcase windows.ERROR_BUFFER_OVERFLOW:\n\t\treturn 0, nil // Dropping when ring is full.\n\t}\n\treturn 0, fmt.Errorf(\"Write failed: %w\", err)\n}\n\n// LUID returns Windows interface instance ID.\nfunc (tun *NativeTun) LUID() uint64 {\n\ttun.running.Add(1)\n\tdefer tun.running.Done()\n\tif atomic.LoadInt32(&tun.close) == 1 {\n\t\treturn 0\n\t}\n\treturn tun.wt.LUID()\n}\n\n// RunningVersion returns the running version of the Wintun driver.\nfunc (tun *NativeTun) RunningVersion() (version uint32, err error) {\n\treturn wintun.RunningVersion()\n}\n\nfunc (rate *rateJuggler) update(packetLen uint64) {\n\tnow := nanotime()\n\ttotal := atomic.AddUint64(&rate.nextByteCount, packetLen)\n\tperiod := uint64(now - atomic.LoadInt64(&rate.nextStartTime))\n\tif period >= rateMeasurementGranularity {\n\t\tif !atomic.CompareAndSwapInt32(&rate.changing, 0, 1) {\n\t\t\treturn\n\t\t}\n\t\tatomic.StoreInt64(&rate.nextStartTime, now)\n\t\tatomic.StoreUint64(&rate.current, total*uint64(time.Second/time.Nanosecond)/period)\n\t\tatomic.StoreUint64(&rate.nextByteCount, 0)\n\t\tatomic.StoreInt32(&rate.changing, 0)\n\t}\n}\n"
  }
]