-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(hole-punch): add hole-punch interoperability test suite (#304)
- Loading branch information
1 parent
4ed47fe
commit 1e37b93
Showing
32 changed files
with
9,183 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
name: "libp2p hole-punch interop test" | ||
description: "Run the libp2p hole-punch interoperability test suite" | ||
inputs: | ||
test-filter: | ||
description: "Filter which tests to run out of the created matrix" | ||
required: false | ||
default: "" | ||
test-ignore: | ||
description: "Exclude tests from the created matrix that include this string in their name" | ||
required: false | ||
default: "" | ||
extra-versions: | ||
description: "Space-separated paths to JSON files describing additional images" | ||
required: false | ||
default: "" | ||
s3-cache-bucket: | ||
description: "Which S3 bucket to use for container layer caching" | ||
required: false | ||
default: "" | ||
s3-access-key-id: | ||
description: "S3 Access key id for the cache" | ||
required: false | ||
default: "" | ||
s3-secret-access-key: | ||
description: "S3 secret key id for the cache" | ||
required: false | ||
default: "" | ||
aws-region: | ||
description: "Which AWS region to use" | ||
required: false | ||
default: "us-east-1" | ||
worker-count: | ||
description: "How many workers to use for the test" | ||
required: false | ||
default: "2" | ||
runs: | ||
using: "composite" | ||
steps: | ||
- name: Configure AWS credentials for S3 build cache | ||
if: inputs.s3-access-key-id != '' && inputs.s3-secret-access-key != '' | ||
run: | | ||
echo "PUSH_CACHE=true" >> $GITHUB_ENV | ||
shell: bash | ||
|
||
# This depends on where this file is within this repository. This walks up | ||
# from here to the hole-punch-interop folder | ||
- run: | | ||
WORK_DIR=$(realpath "$GITHUB_ACTION_PATH/../../../hole-punch-interop") | ||
echo "WORK_DIR=$WORK_DIR" >> $GITHUB_OUTPUT | ||
shell: bash | ||
id: find-workdir | ||
- uses: actions/setup-node@v3 | ||
with: | ||
node-version: 18 | ||
|
||
# Existence of /etc/buildkit/buildkitd.toml indicates that this is a | ||
# self-hosted runner. If so, we need to pass the config to the buildx | ||
# action. The config enables docker.io proxy which is required to | ||
# work around docker hub rate limiting. | ||
- run: | | ||
if test -f /etc/buildkit/buildkitd.toml; then | ||
echo "config=/etc/buildkit/buildkitd.toml" >> $GITHUB_OUTPUT | ||
fi | ||
shell: bash | ||
id: buildkit | ||
- name: Install more recent docker-compose version # https://stackoverflow.com/questions/54331949/having-networking-issues-with-docker-compose | ||
shell: bash | ||
run: | | ||
mkdir -p $HOME/.docker/cli-plugins | ||
wget -q -O- https://github.com/docker/compose/releases/download/v2.21.0/docker-compose-linux-x86_64 > $HOME/.docker/cli-plugins/docker-compose | ||
chmod +x $HOME/.docker/cli-plugins/docker-compose | ||
docker compose version | ||
- name: Set up Docker Buildx | ||
id: buildx | ||
uses: docker/setup-buildx-action@v2 | ||
with: | ||
config: ${{ steps.buildkit.outputs.config }} | ||
|
||
- name: Install deps | ||
working-directory: ${{ steps.find-workdir.outputs.WORK_DIR }} | ||
run: npm ci | ||
shell: bash | ||
|
||
- name: Load cache and build | ||
working-directory: ${{ steps.find-workdir.outputs.WORK_DIR }} | ||
run: npm run cache -- load | ||
shell: bash | ||
|
||
- name: Assert Git tree is clean. | ||
working-directory: ${{ steps.find-workdir.outputs.WORK_DIR }} | ||
shell: bash | ||
run: | | ||
if [[ -n "$(git status --porcelain)" ]]; then | ||
echo "Git tree is dirty. This means that building an impl generated something that should probably be .gitignore'd" | ||
git status | ||
exit 1 | ||
fi | ||
- name: Push the image cache | ||
if: env.PUSH_CACHE == 'true' | ||
working-directory: ${{ steps.find-workdir.outputs.WORK_DIR }} | ||
env: | ||
AWS_BUCKET: ${{ inputs.s3-cache-bucket }} | ||
AWS_REGION: ${{ inputs.aws-region }} | ||
AWS_ACCESS_KEY_ID: ${{ inputs.s3-access-key-id }} | ||
AWS_SECRET_ACCESS_KEY: ${{ inputs.s3-secret-access-key }} | ||
run: npm run cache -- push | ||
shell: bash | ||
|
||
- name: Run the test | ||
working-directory: ${{ steps.find-workdir.outputs.WORK_DIR }} | ||
env: | ||
WORKER_COUNT: ${{ inputs.worker-count }} | ||
EXTRA_VERSION: ${{ inputs.extra-versions }} | ||
NAME_FILTER: ${{ inputs.test-filter }} | ||
NAME_IGNORE: ${{ inputs.test-ignore }} | ||
run: npm run test -- --extra-version=$EXTRA_VERSION --name-filter=$NAME_FILTER --name-ignore=$NAME_IGNORE | ||
shell: bash | ||
|
||
- name: Print the results | ||
working-directory: ${{ steps.find-workdir.outputs.WORK_DIR }} | ||
run: cat results.csv | ||
shell: bash | ||
|
||
- name: Render results | ||
working-directory: ${{ steps.find-workdir.outputs.WORK_DIR }} | ||
run: npm run renderResults > ./dashboard.md | ||
shell: bash | ||
|
||
- name: Show Dashboard Output | ||
working-directory: ${{ steps.find-workdir.outputs.WORK_DIR }} | ||
run: cat ./dashboard.md >> $GITHUB_STEP_SUMMARY | ||
shell: bash | ||
|
||
- name: Exit with Error | ||
working-directory: ${{ steps.find-workdir.outputs.WORK_DIR }} | ||
run: | | ||
if grep -q ":red_circle:" ./dashboard.md; then | ||
exit 1 | ||
else | ||
exit 0 | ||
fi | ||
shell: bash | ||
|
||
- uses: actions/upload-artifact@v3 | ||
if: ${{ always() }} | ||
with: | ||
name: test-plans-output | ||
path: | | ||
${{ steps.find-workdir.outputs.WORK_DIR }}/results.csv | ||
${{ steps.find-workdir.outputs.WORK_DIR }}/dashboard.md | ||
${{ steps.find-workdir.outputs.WORK_DIR }}/runs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
on: | ||
workflow_dispatch: | ||
pull_request: | ||
paths: | ||
- 'hole-punch-interop/**' | ||
push: | ||
branches: | ||
- "master" | ||
paths: | ||
- 'hole-punch-interop/**' | ||
|
||
name: libp2p holepunching interop test | ||
|
||
jobs: | ||
run-hole-punch-interop: | ||
runs-on: ['self-hosted', 'linux', 'x64', '4xlarge'] # https://github.com/pl-strflt/tf-aws-gh-runner/blob/main/runners.tf | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- uses: ./.github/actions/run-interop-hole-punch-test | ||
with: | ||
s3-cache-bucket: libp2p-by-tf-aws-bootstrap | ||
s3-access-key-id: ${{ vars.S3_AWS_ACCESS_KEY_ID }} | ||
s3-secret-access-key: ${{ secrets.S3_AWS_SECRET_ACCESS_KEY }} | ||
worker-count: 16 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# For now, not committing image.json files | ||
image.json | ||
|
||
results.csv | ||
runs/ | ||
|
||
node_modules/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
RUST_SUBDIRS := $(wildcard impl/rust/*/.) | ||
GO_SUBDIRS := $(wildcard impl/go/*/.) | ||
|
||
all: rust-relay router $(RUST_SUBDIRS) $(GO_SUBDIRS) | ||
rust-relay: | ||
$(MAKE) -C rust-relay | ||
router: | ||
$(MAKE) -C router | ||
$(RUST_SUBDIRS): | ||
$(MAKE) -C $@ | ||
$(GO_SUBDIRS): | ||
$(MAKE) -C $@ | ||
clean: | ||
$(MAKE) -C rust-relay clean | ||
$(MAKE) -C router clean | ||
$(MAKE) -C $(RUST_SUBDIRS) clean | ||
$(MAKE) -C $(GO_SUBDIRS) clean | ||
|
||
.PHONY: rust-relay router all $(RUST_SUBDIRS) $(GO_SUBDIRS) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
# Hole punch tests | ||
|
||
## How to run locally | ||
|
||
1. `npm install` | ||
2. `make` | ||
3. `npm run test` | ||
|
||
## Client configuration | ||
|
||
| env variable | possible values | | ||
|--------------|-----------------| | ||
| MODE | listen \| dial | | ||
| TRANSPORT | tcp \| quic | | ||
|
||
- For TCP, the client MUST use noise + yamux to upgrade the connection. | ||
- The relayed connection MUST use noise + yamux. | ||
|
||
## Test flow | ||
|
||
1. The relay starts and pushes its address to the following redis keys: | ||
- `RELAY_TCP_ADDRESS` for the TCP test | ||
- `RELAY_QUIC_ADDRESS` for the QUIC test | ||
1. Upon start-up, clients connect to a redis server at `redis:6379` and block until this redis key comes available. | ||
They then dial the relay on the provided address. | ||
1. The relay supports identify. | ||
Implementations SHOULD use that to figure out their external address next. | ||
1. Once connected to the relay, a client in `MODE=listen` should listen on the relay and make a reservation. | ||
Once the reservation is made, it pushes its `PeerId` to the redis key `LISTEN_CLIENT_PEER_ID`. | ||
1. A client in `MODE=dial` blocks on the availability of `LISTEN_CLIENT_PEER_ID`. | ||
Once available, it dials `<relay_addr>/p2p-circuit/<listen-client-peer-id>`. | ||
1. Upon a successful hole-punch, the peer in `MODE=dial` measures the RTT across the newly established connection. | ||
1. The RTT MUST be printed to stdout in the following format: | ||
```json | ||
{ "rtt_to_holepunched_peer_millis": 12 } | ||
``` | ||
1. Once printed, the dialer MUST exit with `0`. | ||
|
||
## Requirements for implementations | ||
|
||
- Docker containers MUST have a binary called `hole-punch-client` in their $PATH | ||
- MUST have `dig`, `curl`, `jq` and `tcpdump` installed | ||
- Listener MUST NOT early-exit but wait to be killed by test runner | ||
- Logs MUST go to stderr, RTT json MUST go to stdout | ||
- Dialer and lister both MUST use 0RTT negotiation for protocols | ||
- Implementations SHOULD disable timeouts on the redis client, i.e. use `0` | ||
- Implementations SHOULD exit early with a non-zero exit code if anything goes wrong | ||
- Implementations MUST set `TCP_NODELAY` for the TCP transport | ||
- Implements MUST make sure connections are being kept alive | ||
|
||
## Design notes | ||
|
||
The design of this test runner is heavily influenced by [multidim-interop](../multidim-interop) but differs in several ways. | ||
|
||
All files related to test runs will be written to the [./runs](./runs) directory. | ||
This includes the `docker-compose.yml` files of each individual run as well as logs and `tcpdump`'s for the dialer and listener. | ||
|
||
The docker-compose file uses 6 containers in total: | ||
|
||
- 1 redis container for orchestrating the test | ||
- 1 [relay](./rust-relay) | ||
- 1 hole-punch client in `MODE=dial` | ||
- 1 hole-punch client in `MODE=listen` | ||
- 2 [routers](./router): 1 per client | ||
|
||
The networks are allocated by docker-compose. | ||
We dynamically fetch the IPs and subnets as part of a startup script to set the correct IP routes. | ||
|
||
In total, we have three networks: | ||
|
||
1. `lan_dialer` | ||
2. `lan_listener` | ||
3. `internet` | ||
|
||
The two LANs host a router and a client each whereas the relay is connected (without a router) to the `internet` network. | ||
On startup of the clients, we add an `ip route` that redirects all traffic to the corresponding `router` container. | ||
The router container masquerades all traffic upon forwarding, see the [README](./router/README.md) for details. | ||
|
||
## Running a single test | ||
|
||
1. Build all containers using `make` | ||
1. Generate all test definitions using `npm run test -- --no-run` | ||
1. Pick the desired test from the [runs](./runs) directory | ||
1. Execute it using `docker compose up` |
Oops, something went wrong.