diff --git a/.gitignore b/.gitignore
index 03d8b25f..0b8e5e9b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,8 +12,7 @@ go.mod.*
 vendor
 
 # testing
-testing
-spn/testing/simple/testdata
+testdata
 
 # Compiled Object files, Static and Dynamic libs (Shared Objects)
 *.a
diff --git a/spn/testing/README.md b/spn/testing/README.md
new file mode 100644
index 00000000..72c7ff74
--- /dev/null
+++ b/spn/testing/README.md
@@ -0,0 +1,25 @@
+# Testing Port17
+
+## Simple Docker Setup
+
+Run `run.sh` to start the docker compose test network.
+Then, connect to the test network, by starting the core with the "test" spn map and the correct bootstrap file.
+
+Run `stop.sh` to remove all docker resources again.
+
+Setup Guide can be found in the directory.
+
+## Advanced Setup with Shadow
+
+For advanced testing we use [shadow](https://github.com/shadow/shadow).
+The following section will help you set up shadow and will guide you how to test Port17 in a local Shadow environment.
+
+### Setting up
+
+Download the docker version from here: [https://security.cs.georgetown.edu/shadow-docker-images/shadow-standalone.tar.gz](https://security.cs.georgetown.edu/shadow-docker-images/shadow-standalone.tar.gz)
+
+Then import the image into docker with `gunzip -c shadow-standalone.tar.gz | sudo docker load`.
+
+### Running
+
+Execute `sudo docker run -t -i -u shadow shadow-standalone /bin/bash` to start an interactive container with shadow.
diff --git a/spn/testing/simple/README.md b/spn/testing/simple/README.md
new file mode 100644
index 00000000..678e4e96
--- /dev/null
+++ b/spn/testing/simple/README.md
@@ -0,0 +1,48 @@
+# Setup Guide
+
+1. Build SPN Hub
+
+```
+cd ../../../cmds/hub/
+./build
+```
+
+2. Reset any previous state (for a fresh test)
+
+```
+./reset-databases.sh
+```
+
+3. Change compose file and config template as required
+
+Files:
+- `docker-compose.yml`
+- `config-template.json`
+
+4. Start test network
+
+```
+./run.sh
+```
+
+5. Option 1: Join as Hub
+
+For testing just one Hub with a different build or config, you can simply use `./join.sh` to join the network with the most recently build hub binary.
+
+6. Option 2: Join as Portmaster
+
+For connecting to the SPN test network with Portmaster, execute portmaster like this:
+
+sudo ../../../cmds/portmaster-core/portmaster-core --disable-shutdown-event --devmode --log debug --data /opt/safing/portmaster
+
+Note:
+This uses the same portmaster data and config as your installed version.
+As the SPN Test net operates under a different ID ("test" instead of "main"), this will not pollute the SPN state of your installed Portmaster.
+
+7. Stop the test net
+
+This is important, as just stopping the `./run.sh` script will leave you with interfaces with public IPs!
+
+```
+./stop.sh
+```
diff --git a/spn/testing/simple/clientsim.sh b/spn/testing/simple/clientsim.sh
new file mode 100755
index 00000000..a25cf3c4
--- /dev/null
+++ b/spn/testing/simple/clientsim.sh
@@ -0,0 +1,41 @@
+#!/bin/bash
+
+cd "$( dirname "${BASH_SOURCE[0]}" )"
+
+realpath() {
+    path=`eval echo "$1"`
+    folder=$(dirname "$path")
+    echo $(cd "$folder"; pwd)/$(basename "$path"); 
+}
+
+if [[ ! -f "../../client" ]]; then
+  echo "please compile client.go in main directory (output: client)"
+  exit 1
+fi
+
+bin_path="$(realpath ../../client)"
+data_path="$(realpath ./testdata)"
+if [[ ! -d "$data_path" ]]; then
+  mkdir "$data_path"
+fi
+shared_path="$(realpath ./testdata/shared)"
+if [[ ! -d "$shared_path" ]]; then
+  mkdir "$shared_path"
+fi
+
+docker network ls | grep spn-simpletest-network >/dev/null 2>&1
+if [[ $? -ne 0 ]]; then
+  docker network create spn-simpletest-network --subnet 6.0.0.0/24
+fi
+
+docker run -ti --rm \
+--name spn-simpletest-clientsim \
+--network spn-simpletest-network \
+-v $bin_path:/opt/client:ro \
+-v $data_path/clientsim:/opt/data \
+-v $shared_path:/opt/shared \
+--entrypoint /opt/client \
+toolset.safing.network/dev \
+--data /opt/data \
+--bootstrap-file /opt/shared/bootstrap.dsd \
+--log trace $*
\ No newline at end of file
diff --git a/spn/testing/simple/config-template.json b/spn/testing/simple/config-template.json
new file mode 100644
index 00000000..c9baca7e
--- /dev/null
+++ b/spn/testing/simple/config-template.json
@@ -0,0 +1,19 @@
+{
+  "core": {
+    "log": {
+      "level": "trace"
+    },
+    "metrics": {
+      "instance": "test_$HUBNAME",
+      "push": ""
+    }
+  },
+  "spn": {
+    "publicHub": {
+      "name": "test-$HUBNAME",
+      "transports": ["http:80", "http:8080", "tcp:17"],
+      "allowUnencrypted": true,
+      "bindToAdvertised": true
+    }
+  }
+}
diff --git a/spn/testing/simple/docker-compose.yml b/spn/testing/simple/docker-compose.yml
new file mode 100644
index 00000000..3d48eb10
--- /dev/null
+++ b/spn/testing/simple/docker-compose.yml
@@ -0,0 +1,139 @@
+version: "2.4"
+
+networks:
+  default:
+    ipam:
+      driver: default
+      config:
+        - subnet: 6.0.0.0/24
+
+services:
+  hub1:
+    container_name: spn-test-simple-hub1
+    hostname: hub1
+    image: toolset.safing.network/dev
+    entrypoint: "/opt/shared/entrypoint.sh"
+    volumes:
+      - ${SPN_TEST_BIN}:/opt/hub1:ro
+      - ${SPN_TEST_DATA_DIR}/hub1:/opt/data
+      - ${SPN_TEST_SHARED_DATA_DIR}:/opt/shared
+    networks:
+      default:
+        ipv4_address: 6.0.0.11
+
+  hub2:
+    container_name: spn-test-simple-hub2
+    hostname: hub2
+    image: alpine
+    entrypoint: "/opt/shared/entrypoint.sh"
+    volumes:
+      - ${SPN_TEST_BIN}:/opt/hub2:ro
+      - ${SPN_TEST_DATA_DIR}/hub2:/opt/data
+      - ${SPN_TEST_SHARED_DATA_DIR}:/opt/shared
+    networks:
+      default:
+        ipv4_address: 6.0.0.12
+
+  hub3:
+    container_name: spn-test-simple-hub3
+    hostname: hub3
+    image: toolset.safing.network/dev
+    entrypoint: "/opt/shared/entrypoint.sh"
+    volumes:
+      - ${SPN_TEST_BIN}:/opt/hub3:ro
+      - ${SPN_TEST_DATA_DIR}/hub3:/opt/data
+      - ${SPN_TEST_SHARED_DATA_DIR}:/opt/shared
+    networks:
+      default:
+        ipv4_address: 6.0.0.13
+
+  hub4:
+    container_name: spn-test-simple-hub4
+    hostname: hub4
+    image: toolset.safing.network/dev
+    entrypoint: "/opt/shared/entrypoint.sh"
+    volumes:
+      - ${SPN_TEST_BIN}:/opt/hub4:ro
+      - ${SPN_TEST_DATA_DIR}/hub4:/opt/data
+      - ${SPN_TEST_SHARED_DATA_DIR}:/opt/shared
+    networks:
+      default:
+        ipv4_address: 6.0.0.14
+
+  hub5:
+    container_name: spn-test-simple-hub5
+    hostname: hub5
+    image: toolset.safing.network/dev
+    entrypoint: "/opt/shared/entrypoint.sh"
+    volumes:
+      - ${SPN_TEST_BIN}:/opt/hub5:ro
+      - ${SPN_TEST_DATA_DIR}/hub5:/opt/data
+      - ${SPN_TEST_SHARED_DATA_DIR}:/opt/shared
+    networks:
+      default:
+        ipv4_address: 6.0.0.15
+
+  hub6:
+    container_name: spn-test-simple-hub6
+    hostname: hub6
+    image: toolset.safing.network/dev
+    entrypoint: "/opt/shared/entrypoint.sh"
+    volumes:
+      - ${SPN_TEST_OLD_BIN}:/opt/hub6:ro
+      - ${SPN_TEST_DATA_DIR}/hub6:/opt/data
+      - ${SPN_TEST_SHARED_DATA_DIR}:/opt/shared
+    networks:
+      default:
+        ipv4_address: 6.0.0.16
+
+  hub7:
+    container_name: spn-test-simple-hub7
+    hostname: hub7
+    image: toolset.safing.network/dev
+    entrypoint: "/opt/shared/entrypoint.sh"
+    volumes:
+      - ${SPN_TEST_OLD_BIN}:/opt/hub7:ro
+      - ${SPN_TEST_DATA_DIR}/hub7:/opt/data
+      - ${SPN_TEST_SHARED_DATA_DIR}:/opt/shared
+    networks:
+      default:
+        ipv4_address: 6.0.0.17
+
+  hub8:
+    container_name: spn-test-simple-hub8
+    hostname: hub8
+    image: toolset.safing.network/dev
+    entrypoint: "/opt/shared/entrypoint.sh"
+    volumes:
+      - ${SPN_TEST_OLD_BIN}:/opt/hub8:ro
+      - ${SPN_TEST_DATA_DIR}/hub8:/opt/data
+      - ${SPN_TEST_SHARED_DATA_DIR}:/opt/shared
+    networks:
+      default:
+        ipv4_address: 6.0.0.18
+
+  hub9:
+    container_name: spn-test-simple-hub9
+    hostname: hub9
+    image: toolset.safing.network/dev
+    entrypoint: "/opt/shared/entrypoint.sh"
+    volumes:
+      - ${SPN_TEST_OLD_BIN}:/opt/hub9:ro
+      - ${SPN_TEST_DATA_DIR}/hub9:/opt/data
+      - ${SPN_TEST_SHARED_DATA_DIR}:/opt/shared
+    networks:
+      default:
+        ipv4_address: 6.0.0.19
+
+  hub10:
+    container_name: spn-test-simple-hub10
+    hostname: hub10
+    image: toolset.safing.network/dev
+    entrypoint: "/opt/shared/entrypoint.sh"
+    volumes:
+      - ${SPN_TEST_OLD_BIN}:/opt/hub10:ro
+      - ${SPN_TEST_DATA_DIR}/hub10:/opt/data
+      - ${SPN_TEST_SHARED_DATA_DIR}:/opt/shared
+    networks:
+      default:
+        ipv4_address: 6.0.0.20
diff --git a/spn/testing/simple/entrypoint.sh b/spn/testing/simple/entrypoint.sh
new file mode 100755
index 00000000..5fe516e0
--- /dev/null
+++ b/spn/testing/simple/entrypoint.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+# Get hostname.
+HUBNAME=$HOSTNAME
+if [ "$HUBNAME" = "" ]; then
+  HUBNAME=$(cat /etc/hostname)
+fi
+export HUBNAME
+
+# Read, process and write config.
+cat /opt/shared/config-template.json | sed "s/\$HUBNAME/$HUBNAME/g" > /opt/data/config.json
+
+# Get binary to start.
+BIN=$(ls /opt/ | grep hub)
+
+# Start Hub.
+/opt/$BIN --data /opt/data --log trace --spn-map test --bootstrap-file /opt/shared/bootstrap.dsd --api-address 0.0.0.0:817 --devmode
diff --git a/spn/testing/simple/inject-intel.sh b/spn/testing/simple/inject-intel.sh
new file mode 100755
index 00000000..a57cd72b
--- /dev/null
+++ b/spn/testing/simple/inject-intel.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+
+cd "$( dirname "${BASH_SOURCE[0]}" )"
+
+MAIN_INTEL_FILE="intel-testnet.json"
+
+if [[ ! -f $MAIN_INTEL_FILE ]]; then
+  echo "missing $MAIN_INTEL_FILE"
+  exit 1
+fi
+
+echo "if the containing directory cannot be created, you might need to adjust permissions, as nodes are run with root in test containers..."
+echo "$ sudo chmod -R 777 data/hub*/updates"
+echo "starting to update..."
+
+for hubDir in data/hub*; do
+  # Build destination path
+  hubIntelFile="${hubDir}/updates/all/intel/spn/main-intel_v0-0-0.dsd"
+
+  # Copy file
+  mkdir -p "${hubDir}/updates/all/intel/spn"
+  echo -n "J" > "$hubIntelFile"
+  cat $MAIN_INTEL_FILE >> "$hubIntelFile"
+
+  echo "updated $hubIntelFile"
+done
+
+if [[ -d /var/lib/portmaster ]]; then
+  echo "updating intel for local portmaster installation..."
+
+  portmasterSPNIntelFile="/var/lib/portmaster/updates/all/intel/spn/main-intel_v0-0-0.dsd"
+  echo -n "J" > "$portmasterSPNIntelFile"
+  cat $MAIN_INTEL_FILE >> "$portmasterSPNIntelFile"
+  echo "updated $portmasterSPNIntelFile"
+fi
diff --git a/spn/testing/simple/intel-client.yaml b/spn/testing/simple/intel-client.yaml
new file mode 100644
index 00000000..28f0a685
--- /dev/null
+++ b/spn/testing/simple/intel-client.yaml
@@ -0,0 +1,25 @@
+# Get current list of IDs from test net:
+#   curl http://127.0.0.1:817/api/v1/spn/map/test/pins | jq ".[] | .ID"
+# Then import into test client with:
+#   curl -X POST --upload-file intel-client.yaml http://127.0.0.1:817/api/v1/spn/map/test/intel/update
+Hubs:
+  Zwm48YWWFGdwXjhE1MyEkWfqxPr9DiUBoXpusTZ1FMQnuK:
+    Trusted: true
+  Zwu5LkkbfCbAcYxWG3vtWF1VvWjgWpc1GJfkwRdLFNtytV:
+    Trusted: true
+  ZwuQpz5CqYmYoLnt9KXQ8oxnmosBzfrCYwCGhxT4fsG1Dz:
+    Trusted: true
+  ZwwmC3dHzr7J6XW9mc2KD6FDNuXwPVJUFi9dLnDSNMyjLk:
+    Trusted: true
+  ZwxSBdvqtJyz8zRWKZe6QyK51KH9av6VFay2GQvpFrWKHq:
+    Trusted: true
+  ZwxnuL6zMLj4AxJX8Bj369w2tNrVtYxzffVcXZuMxdxbGj:
+    Trusted: true
+  ZwyXdnC8JkC7m796skGD7QWGoYycByR3KVntkXMY8CxRZQ:
+    Trusted: true
+  Zwz7AHiH1EevD9eYFqvQQPbVWyBBcksTRxxafbRx5Cvc4F:
+    Trusted: true
+  ZwzMtc65t9XBMwmLm2xNSL69FvqHGPLiqeNBZ3eEN5a9sS:
+    Trusted: true
+  ZwzjnCUNGsuWnkYmN3QEj8JPLxU6V1QQFk9b47AigmPKiH:
+    Trusted: true
diff --git a/spn/testing/simple/intel-testnet.json b/spn/testing/simple/intel-testnet.json
new file mode 100644
index 00000000..388fa0e1
--- /dev/null
+++ b/spn/testing/simple/intel-testnet.json
@@ -0,0 +1,17 @@
+{
+	"BootstrapHubs": [
+	],
+	"TrustedHubs": [
+		"ZwrY9G9HDo1J3qQrrQs8VF2KD99bj7KyWesJ5kWFUDBU6r",
+		"Zwj56ZFXrsud8gc1Rw3zuxRwMLhGkwvtvnTxCVtJ8EWLhQ",
+		"ZwpdW87ityD9i3N9x8oweCJnbZEqo346VBg4mCsCvTr1Zo",
+		"ZwpJ6ebddk1sccUVpo92JUqicBfKzBN2w4pEGoEY7UsNhX",
+		"Zwte3Jffp9PWmeWfrn8RyGuvZZFCg3v7XR3tpQjdo9TpVt",
+		"ZwrTcdiPF5zR5h9q9EdjHCrrXzYVBdQe5HmEYUWXdLkke3",
+		"Zwv7tSn5iU6bYKn53NaGCxPtL8vSxSK7F9VdQezDaDCLBt",
+		"Zwvtdq3K9knP9iNaRS1Ju8CETWTqy7oRwbScjBtJGBpqhB"
+	],
+	"AdviseOnlyTrustedHubs": true,
+	"AdviseOnlyTrustedHomeHubs": true,
+	"AdviseOnlyTrustedDestinationHubs": true
+}
diff --git a/spn/testing/simple/join.sh b/spn/testing/simple/join.sh
new file mode 100755
index 00000000..b5ddf912
--- /dev/null
+++ b/spn/testing/simple/join.sh
@@ -0,0 +1,42 @@
+#!/bin/bash
+
+cd "$( dirname "${BASH_SOURCE[0]}" )"
+
+realpath() {
+    path=`eval echo "$1"`
+    folder=$(dirname "$path")
+    echo $(cd "$folder"; pwd)/$(basename "$path"); 
+}
+
+leftover=$(docker ps -a | grep spn-test-simple-me | cut -d" " -f1)
+if [[ $leftover != "" ]]; then
+  docker stop $leftover
+  docker rm $leftover
+fi
+
+if [[ ! -f "../../../cmds/hub/hub" ]]; then
+  echo "please build the hub cmd using cmds/hub/build"
+  exit 1
+fi
+
+SPN_TEST_BIN="$(realpath ../../../cmds/hub/hub)"
+SPN_TEST_DATA_DIR="$(realpath ./testdata)"
+if [[ ! -d "$SPN_TEST_DATA_DIR" ]]; then
+  mkdir "$SPN_TEST_DATA_DIR"
+fi
+SPN_TEST_SHARED_DATA_DIR="$(realpath ./testdata/shared)"
+if [[ ! -d "$SPN_TEST_SHARED_DATA_DIR" ]]; then
+  mkdir "$SPN_TEST_SHARED_DATA_DIR"
+fi
+
+docker run -ti \
+--name spn-test-simple-me \
+--hostname me \
+--network spn-test-simple_default \
+-v $SPN_TEST_BIN:/opt/hub_me:ro \
+-v $SPN_TEST_DATA_DIR/me:/opt/data \
+-v $SPN_TEST_SHARED_DATA_DIR:/opt/shared \
+--entrypoint /opt/hub_me \
+toolset.safing.network/dev \
+--devmode --api-address 0.0.0.0:8081 \
+--data /opt/data -log trace --spn-map test --bootstrap-file /opt/shared/bootstrap.dsd
diff --git a/spn/testing/simple/reset-databases.sh b/spn/testing/simple/reset-databases.sh
new file mode 100755
index 00000000..79be6890
--- /dev/null
+++ b/spn/testing/simple/reset-databases.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+cd "$( dirname "${BASH_SOURCE[0]}" )"
+
+rm -rf data/me/*
+rm -rf data/shared/*
+rm -rf data/hub*/databases
diff --git a/spn/testing/simple/run.sh b/spn/testing/simple/run.sh
new file mode 100755
index 00000000..728cad3f
--- /dev/null
+++ b/spn/testing/simple/run.sh
@@ -0,0 +1,52 @@
+#!/bin/bash
+
+cd "$( dirname "${BASH_SOURCE[0]}" )"
+
+realpath() {
+    path=`eval echo "$1"`
+    folder=$(dirname "$path")
+    echo $(cd "$folder"; pwd)/$(basename "$path"); 
+}
+
+leftovers=$(docker ps -a | grep spn-test-simple | cut -d" " -f1)
+if [[ $leftovers != "" ]]; then
+  docker stop $leftovers
+  docker rm $leftovers
+fi
+
+if [[ ! -f "../../../cmds/hub/hub" ]]; then
+  echo "please build the hub cmd using cmds/hub/build"
+  exit 1
+fi
+
+# Create variables.
+SPN_TEST_BIN="$(realpath ../../../cmds/hub/hub)"
+SPN_TEST_DATA_DIR="$(realpath ./testdata)"
+if [[ ! -d "$SPN_TEST_DATA_DIR" ]]; then
+  mkdir "$SPN_TEST_DATA_DIR"
+fi
+SPN_TEST_SHARED_DATA_DIR="$(realpath ./testdata/shared)"
+if [[ ! -d "$SPN_TEST_SHARED_DATA_DIR" ]]; then
+  mkdir "$SPN_TEST_SHARED_DATA_DIR"
+fi
+
+# Check if there is an old binary for testing.
+SPN_TEST_OLD_BIN=$SPN_TEST_BIN
+if [[ -f "./testdata/old-hub" ]]; then
+  SPN_TEST_OLD_BIN="$(realpath ./testdata/old-hub)"
+  echo "WARNING: running in hybrid mode with old version at $SPN_TEST_OLD_BIN"
+fi
+
+# Export variables
+export SPN_TEST_BIN
+export SPN_TEST_OLD_BIN
+export SPN_TEST_DATA_DIR
+export SPN_TEST_SHARED_DATA_DIR
+
+# Copy files.
+cp config-template.json ./testdata/shared/config-template.json
+cp entrypoint.sh ./testdata/shared/entrypoint.sh
+chmod 555 ./testdata/shared/entrypoint.sh
+
+# Run!
+docker compose -p spn-test-simple up --remove-orphans
diff --git a/spn/testing/simple/stop.sh b/spn/testing/simple/stop.sh
new file mode 100755
index 00000000..f5af89a4
--- /dev/null
+++ b/spn/testing/simple/stop.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+cd "$( dirname "${BASH_SOURCE[0]}" )"
+
+docker compose -p spn-test-simple stop
+docker compose -p spn-test-simple rm
+
+oldnet=$(docker network ls | grep spn-test-simple | cut -d" " -f1)
+if [[ $oldnet != "" ]]; then
+  docker network rm $oldnet
+fi
+
+if [[ -d "data/shared" ]]; then
+  rm -r "data/shared"
+fi