From e1ee59f176c2acb01eefa8b83e54e1d0377930b1 Mon Sep 17 00:00:00 2001
From: Jacek Kowalski <Jacek@jacekk.info>
Date: Thu, 12 Mar 2026 23:58:38 +0000
Subject: [PATCH] Add nogw/nogw4/nogw6 options to prevent assignment of gateway

---
 requirements.txt     |    2 
 test_integration.sh  |  108 ++++++++++++++++++++++++++++++++++++
 lib/NetworkDriver.py |   15 +++++
 README.md            |   23 ++++++-
 4 files changed, 144 insertions(+), 4 deletions(-)

diff --git a/README.md b/README.md
index 29ebfc9..ff65675 100644
--- a/README.md
+++ b/README.md
@@ -31,14 +31,31 @@
 One will be pushed inside the container and another will remain on host
 (without any IP assigned).
 
-Plugin accepts optional `parent` parameter, which is a name of bridge
-interface that the second interface should be added to:
+## Options
+
+To use options, add `--opt option=value` as an argument of `docker network create`:
 
 ```bash
 docker network create --driver jacekkow/pyveth:latest --opt parent=br0 new-network
 ```
 
-This way host interface will be automatically attached to the specified bridge.
+Available options:
+
+`parent=brname`
+
+Automatically attach host interface to the bridge interface `brname`.
+
+`nogw=1`
+
+Disable assignment of gateway IP.
+
+`nogw4=1`
+
+Disable assignment of IPv4 gateway IP.
+
+`nogw6=1`
+
+Disable assignment of IPv6 gateway IP.
 
 ## Manual packaging
 
diff --git a/lib/NetworkDriver.py b/lib/NetworkDriver.py
index b6cdc6b..948a6a6 100644
--- a/lib/NetworkDriver.py
+++ b/lib/NetworkDriver.py
@@ -57,6 +57,21 @@
     return {
         'Scope': 'local',
         'ConnectivityScope': 'global',
+        'GwAllocChecker': True,
+    }
+
+
+@app.route('/NetworkDriver.GwAllocCheck', methods=['POST'])
+def GwAllocCheck():
+    request = GwAllocCheckEntity(**flask.request.get_json(force=True))
+    skip_ipv4 = skip_ipv6 = request.Options.get('com.docker.network.generic', {}).get('nogw') == '1'
+    if request.Options.get('com.docker.network.generic', {}).get('nogw4') == '1':
+        skip_ipv4 = True
+    if request.Options.get('com.docker.network.generic', {}).get('nogw6') == '1':
+        skip_ipv6 = True
+    return {
+        'SkipIPv4': skip_ipv4,
+        'SkipIPv6': skip_ipv6,
     }
 
 
diff --git a/requirements.txt b/requirements.txt
index f2bb7be..dfa6b2e 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
-docker-plugin-api>=0.3
+docker-plugin-api>=0.4
 Flask
 pyroute2
 waitress
diff --git a/test_integration.sh b/test_integration.sh
index d3acf7c..9f1744c 100755
--- a/test_integration.sh
+++ b/test_integration.sh
@@ -12,6 +12,10 @@
 
 docker plugin install jacekkow/pyipam:latest || true
 
+
+##########################
+# Test address assignment
+
 docker network create \
   --internal \
   --driver "${PLUGIN}" \
@@ -56,6 +60,9 @@
 docker network rm test1
 
 
+######################
+# Test routing config
+
 docker network create \
   --driver "${PLUGIN}" \
   --ipam-driver jacekkow/pyipam:latest \
@@ -85,3 +92,104 @@
 fi
 
 docker network rm test2
+
+
+##############
+# Test nogw=1
+
+docker network create \
+  --driver "${PLUGIN}" \
+  --opt nogw=1 \
+  --ipam-driver jacekkow/pyipam:latest \
+  --ipv6 \
+  --subnet 192.168.255.0/24 \
+  --subnet 2001:db8::/32 \
+  test2
+
+ROUTES=$(docker run --rm --network test2 \
+  alpine \
+  /sbin/ip route show
+)
+if echo "${ROUTES}" | grep 192.168.255.254; then
+	echo "ERROR: IPv4 route assigned with nogw=1"
+	exit 1
+fi
+
+ROUTES=$(docker run --rm --network test2 \
+  alpine \
+  /sbin/ip -6 route show
+)
+if echo "${ROUTES}" | grep 2001:db8:ffff:ffff:ffff:ffff:ffff:ffff; then
+	echo "ERROR: IPv6 route assigned with nogw=1"
+	exit 1
+fi
+
+docker network rm test2
+
+
+###############
+# Test nogw4=1
+
+docker network create \
+  --driver "${PLUGIN}" \
+  --opt nogw4=1 \
+  --ipam-driver jacekkow/pyipam:latest \
+  --ipv6 \
+  --subnet 192.168.255.0/24 \
+  --subnet 2001:db8::/32 \
+  --gateway 2001:db8:ffff:ffff:ffff:ffff:ffff:ffff \
+  test2
+
+ROUTES=$(docker run --rm --network test2 \
+  alpine \
+  /sbin/ip route show
+)
+if echo "${ROUTES}" | grep 192.168.255.254; then
+	echo "ERROR: IPv4 route assigned with nogw4=1"
+	exit 1
+fi
+
+ROUTES=$(docker run --rm --network test2 \
+  alpine \
+  /sbin/ip -6 route show
+)
+if ! echo "${ROUTES}" | grep 2001:db8:ffff:ffff:ffff:ffff:ffff:ffff; then
+	echo "ERROR: IPv6 route not assigned with nogw4=1"
+	exit 1
+fi
+
+docker network rm test2
+
+
+################
+# Test nogw6=1
+
+docker network create \
+  --driver "${PLUGIN}" \
+  --opt nogw6=1 \
+  --ipam-driver jacekkow/pyipam:latest \
+  --ipv6 \
+  --subnet 192.168.255.0/24 \
+  --gateway 192.168.255.254 \
+  --subnet 2001:db8::/32 \
+  test2
+
+ROUTES=$(docker run --rm --network test2 \
+  alpine \
+  /sbin/ip route show
+)
+if ! echo "${ROUTES}" | grep 192.168.255.254; then
+	echo "ERROR: IPv4 route not assigned with nogw6=1"
+	exit 1
+fi
+
+ROUTES=$(docker run --rm --network test2 \
+  alpine \
+  /sbin/ip -6 route show
+)
+if echo "${ROUTES}" | grep 2001:db8:ffff:ffff:ffff:ffff:ffff:ffff; then
+	echo "ERROR: IPv6 route assigned with nogw6=1"
+	exit 1
+fi
+
+docker network rm test2

--
Gitblit v1.10.0