diff --git a/test/case/infix_dhcp/Readme.adoc b/test/case/infix_dhcp/Readme.adoc
index 451910b5..e16f5503 100644
--- a/test/case/infix_dhcp/Readme.adoc
+++ b/test/case/infix_dhcp/Readme.adoc
@@ -12,3 +12,5 @@ include::client_routes/Readme.adoc[]
include::server_basic/Readme.adoc[]
include::server_host/Readme.adoc[]
+
+include::server_subnets/Readme.adoc[]
diff --git a/test/case/infix_dhcp/infix_dhcp.yaml b/test/case/infix_dhcp/infix_dhcp.yaml
index 8be7ca02..22f3acd2 100644
--- a/test/case/infix_dhcp/infix_dhcp.yaml
+++ b/test/case/infix_dhcp/infix_dhcp.yaml
@@ -13,3 +13,6 @@
- name: server_host
case: server_host/test.py
+
+- name: server_subnets
+ case: server_subnets/test.py
diff --git a/test/case/infix_dhcp/server_subnets/Readme.adoc b/test/case/infix_dhcp/server_subnets/Readme.adoc
new file mode 120000
index 00000000..fdf35ad8
--- /dev/null
+++ b/test/case/infix_dhcp/server_subnets/Readme.adoc
@@ -0,0 +1 @@
+server_subnets.adoc
\ No newline at end of file
diff --git a/test/case/infix_dhcp/server_subnets/dhcp-subnets.svg b/test/case/infix_dhcp/server_subnets/dhcp-subnets.svg
new file mode 100644
index 00000000..e48ed774
--- /dev/null
+++ b/test/case/infix_dhcp/server_subnets/dhcp-subnets.svg
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/test/case/infix_dhcp/server_subnets/server_subnets.adoc b/test/case/infix_dhcp/server_subnets/server_subnets.adoc
new file mode 100644
index 00000000..aa4e0e54
--- /dev/null
+++ b/test/case/infix_dhcp/server_subnets/server_subnets.adoc
@@ -0,0 +1,59 @@
+=== DHCP Server Multiple Subnets
+==== Description
+Verify that the DHCP server is capble of acting on more than one subnet,
+handing out leases from a pool and static host lease, ensuring global,
+subnet, and host-specific options are honored and do not leak between
+subnets.
+
+.Internal network setup, client2 and client3 are on the same LAN
+ifdef::topdoc[]
+image::../../test/case/infix_dhcp/server_subnets/dhcp-subnets.svg[Internal networks]
+endif::topdoc[]
+ifndef::topdoc[]
+ifdef::testgroup[]
+image::server_subnets/dhcp-subnets.svg[Internal networks]
+endif::testgroup[]
+ifndef::testgroup[]
+image::dhcp-subnets.svg[Internal networks]
+endif::testgroup[]
+endif::topdoc[]
+
+To verify isolation of subnet settings, a few "decoys" are added to the
+configuration of each subnet. These are then checked for on each of the
+clients. E.g., both subnets have static host configurations, but only
+one client should match.
+
+Both DNS and NTP servers are handed out to clients. Some clients have
+a static DNS and NTP server configured already.
+
+The test is concluded by the server trying to reach each client using
+ping of the hostname.
+
+==== Topology
+ifdef::topdoc[]
+image::{topdoc}../../test/case/infix_dhcp/server_subnets/topology.svg[DHCP Server Multiple Subnets topology]
+endif::topdoc[]
+ifndef::topdoc[]
+ifdef::testgroup[]
+image::server_subnets/topology.svg[DHCP Server Multiple Subnets topology]
+endif::testgroup[]
+ifndef::testgroup[]
+image::topology.svg[DHCP Server Multiple Subnets topology]
+endif::testgroup[]
+endif::topdoc[]
+==== Test sequence
+. Set up topology and attach to client and server DUTs
+. Configure DHCP server and clients
+. Verify DHCP client1 get correct lease
+. Verify DHCP client1 has default route via server
+. Verify DHCP client1 has correct DNS server(s)
+. Verify DHCP client2 get correct static lease
+. Verify DHCP client2 has default route via classless-static-route
+. Verify DHCP client2 has correct DNS and NTP server(s)
+. Verify DHCP client3 get correct lease
+. Verify DHCP client3 has default route via server
+. Verify DHCP client3 has correct DNS and NTP server(s)
+
+
+<<<
+
diff --git a/test/case/infix_dhcp/server_subnets/test.py b/test/case/infix_dhcp/server_subnets/test.py
new file mode 100755
index 00000000..0ccbffbf
--- /dev/null
+++ b/test/case/infix_dhcp/server_subnets/test.py
@@ -0,0 +1,313 @@
+#!/usr/bin/env python3
+"""DHCP Server Multiple Subnets
+
+Verify that the DHCP server is capble of acting on more than one subnet,
+handing out leases from a pool and static host lease, ensuring global,
+subnet, and host-specific options are honored and do not leak between
+subnets.
+
+.Internal network setup, client2 and client3 are on the same LAN
+image::dhcp-subnets.svg[Internal networks]
+
+To verify isolation of subnet settings, a few "decoys" are added to the
+configuration of each subnet. These are then checked for on each of the
+clients. E.g., both subnets have static host configurations, but only
+one client should match.
+
+Both DNS and NTP servers are handed out to clients. Some clients have
+a static DNS and NTP server configured already.
+
+The test is concluded by the server trying to reach each client using
+ping of the hostname.
+
+"""
+import infamy
+import infamy.iface as iface
+import infamy.route as route
+from infamy.util import until
+
+
+def has_dns_server(data, dns_servers):
+ """Verify system have all the given DNS servers"""
+ servers = data.get('system-state', {}) \
+ .get('dns-resolver', {}) \
+ .get('server')
+ if not servers:
+ return False
+
+ configured = {server['address'] for server in servers}
+ return all(server in configured for server in dns_servers)
+
+
+def has_ntp_server(data, ntp_server):
+ """Verify system has, or does *not* have*, an NTP server"""
+ sources = data.get('system-state', {}) \
+ .get('ntp', {}) \
+ .get('sources', {}) \
+ .get('source')
+ if not ntp_server:
+ # This system should *not* have any NTP server
+ return not sources
+
+ if not sources:
+ return False
+
+ for source in sources:
+ if source['address'] == ntp_server:
+ return True
+
+ return False
+
+
+def has_system_servers(dut, dns, ntp=None):
+ """Verify DUT have all DNS and, if given, NTP server(s)"""
+ data = dut.get_data("/ietf-system:system-state")
+ if data is None:
+ return False
+
+ return has_dns_server(data, dns) and has_ntp_server(data, ntp)
+
+
+with infamy.Test() as test:
+ SERVER1 = '192.168.1.1'
+ POOL1 = '192.168.1.100'
+ ADDR1 = '192.168.1.11'
+ SERVER2 = '192.168.2.1'
+ POOL2 = '192.168.2.200'
+ ADDR2 = '192.168.2.22'
+ GW1 = '192.168.1.11'
+ GW2 = '192.168.2.2'
+ HOSTNM1 = 'client1'
+ HOSTNM2 = 'client2'
+ HOSTNM3 = 'client3'
+
+ with test.step("Set up topology and attach to client and server DUTs"):
+ env = infamy.Env()
+ server = env.attach("server", "mgmt")
+ client1 = env.attach("client1", "mgmt")
+ client2 = env.attach("client2", "mgmt")
+ client3 = env.attach("client3", "mgmt")
+
+ with test.step("Configure DHCP server and clients"):
+ server.put_config_dicts({
+ "ietf-interfaces": {
+ "interfaces": {
+ "interface": [
+ {
+ "name": server["client1"],
+ "ipv4": {
+ "address": [{
+ "ip": SERVER1,
+ "prefix-length": 24
+ }]
+ }
+ }, {
+ "name": "br0",
+ "type": "infix-if-type:bridge",
+ "ipv4": {
+ "address": [{
+ "ip": SERVER2,
+ "prefix-length": 24,
+ }]
+ }
+ }, {
+ "name": server["client2"],
+ "infix-interfaces:bridge-port": {
+ "bridge": "br0"
+ }
+ }, {
+ "name": server["client3"],
+ "infix-interfaces:bridge-port": {
+ "bridge": "br0"
+ }
+ },
+ ]
+ }
+ },
+ "infix-dhcp-server": {
+ "dhcp-server": {
+ "option": [
+ {
+ "id": "dns-server",
+ "address": "auto"
+ },
+ ],
+ # No client should get the static host lease on this
+ # subnet. Only a pool address and global options.
+ "subnet": [
+ {
+ "subnet": "192.168.1.0/24",
+ "option": [{
+ "id": "router",
+ "address": "auto"
+ }],
+ "pool": {
+ "start-address": POOL1,
+ "end-address": POOL1
+ },
+ # Decoy, client2 should not get this lease!
+ "host": [{
+ "address": ADDR1,
+ "match": {
+ "hostname": HOSTNM2
+ },
+ "option": [{
+ "id": "classless-static-route",
+ "static-route": [{
+ "destination": "0.0.0.0/0",
+ "next-hop": GW1
+ }]
+ }]
+ }]
+ },
+ {
+ "subnet": "192.168.2.0/24",
+ "option": [
+ {
+ "id": "ntp-server",
+ "address": "auto"
+ }, {
+ # Verify correct option selection in
+ # client: option 3 < option 121
+ "id": "router",
+ "address": "auto"
+ }
+ ],
+ "pool": {
+ "start-address": POOL2,
+ "end-address": POOL2
+ },
+ "host": [{
+ "address": ADDR2,
+ "match": {
+ "hostname": HOSTNM2
+ },
+ "option": [{
+ "id": "classless-static-route",
+ "static-route": [{
+ "destination": "0.0.0.0/0",
+ "next-hop": GW2
+ }]
+ }]
+ }]
+ },
+ ]
+ }
+ }})
+
+ # All clients request/accept the same options. We do this to
+ # both keep fleet configuration simple but also to verify that
+ # the server is behaving correctly.
+
+ client1.put_config_dicts({
+ "infix-dhcp-client": {
+ "dhcp-client": {
+ "client-if": [{
+ "if-name": client1["server"],
+ "option": [
+ {"id": "hostname", "value": "auto"},
+ {"id": "router"},
+ {"id": "dns"},
+ {"id": "ntpsrv"},
+ {"id": 121}
+ ]
+ }]
+ }
+ },
+ "ietf-system": {
+ "system": {
+ "hostname": HOSTNM1,
+ "ntp": {"enabled": True},
+ }
+ }})
+
+ client2.put_config_dicts({
+ "infix-dhcp-client": {
+ "dhcp-client": {
+ "client-if": [{
+ "if-name": client2["server"],
+ "option": [
+ {"id": "hostname", "value": "auto"},
+ {"id": "router"},
+ {"id": "dns"},
+ {"id": "ntpsrv"},
+ {"id": 121}
+ ]
+ }]
+ }
+ },
+ "ietf-system": {
+ "system": {
+ "hostname": HOSTNM2,
+ "ntp": {"enabled": True},
+ }
+ }})
+
+ client3.put_config_dicts({
+ "infix-dhcp-client": {
+ "dhcp-client": {
+ "client-if": [{
+ "if-name": client3["server"],
+ "option": [
+ {"id": "hostname", "value": "auto"},
+ {"id": "router"},
+ {"id": "dns"},
+ {"id": "ntpsrv"},
+ {"id": 121}
+ ]
+ }]
+ }
+ },
+ "ietf-system": {
+ "system": {
+ "hostname": HOSTNM3,
+ "ntp": {"enabled": True},
+ "dns-resolver": {
+ "search": [
+ "example.com",
+ "kernelkit.org"
+ ],
+ "server": [
+ {
+ "name": "static",
+ "udp-and-tcp": {
+ "address": "1.2.3.4"
+ }
+ }
+ ],
+ "options": {
+ "timeout": 3,
+ "attempts": 5
+ }
+ },
+ }
+ }})
+
+ with test.step("Verify DHCP client1 get correct lease"):
+ until(lambda: iface.address_exist(client1, client1["server"], POOL1))
+
+ with test.step("Verify DHCP client1 has default route via server"):
+ until(lambda: route.ipv4_route_exist(client1, "0.0.0.0/0", nexthop=SERVER1))
+
+ with test.step("Verify DHCP client1 has correct DNS server(s)"):
+ until(lambda: has_system_servers(client1, ["192.168.1.1"]))
+
+ with test.step("Verify DHCP client2 get correct static lease"):
+ until(lambda: iface.address_exist(client2, client2["server"], ADDR2))
+
+ with test.step("Verify DHCP client2 has default route via classless-static-route"):
+ until(lambda: route.ipv4_route_exist(client2, "0.0.0.0/0", nexthop=GW2))
+
+ with test.step("Verify DHCP client2 has correct DNS and NTP server(s)"):
+ until(lambda: has_system_servers(client2, ["192.168.2.1"], "192.168.2.1"))
+
+ with test.step("Verify DHCP client3 get correct lease"):
+ until(lambda: iface.address_exist(client3, client3["server"], POOL2))
+
+ with test.step("Verify DHCP client3 has default route via server"):
+ until(lambda: route.ipv4_route_exist(client3, "0.0.0.0/0", nexthop=SERVER2))
+
+ with test.step("Verify DHCP client3 has correct DNS and NTP server(s)"):
+ until(lambda: has_system_servers(client3, ["1.2.3.4", "192.168.2.1"], "192.168.2.1"))
+
+ test.succeed()
diff --git a/test/case/infix_dhcp/server_subnets/topology.dot b/test/case/infix_dhcp/server_subnets/topology.dot
new file mode 100644
index 00000000..08d8aed7
--- /dev/null
+++ b/test/case/infix_dhcp/server_subnets/topology.dot
@@ -0,0 +1,51 @@
+graph "server subnets" {
+ layout="neato";
+ overlap="false";
+ esep="+40";
+
+ node [shape=record, fontname="DejaVu Sans Mono, Book"];
+ edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
+
+ host [
+ label="host | { client1 | server | client3 | client2 }",
+ pos="0,120!",
+ requires="controller",
+ ];
+
+ client1 [
+ label="{ mgmt | server} | client1",
+ pos="20,130!",
+
+ requires="infix",
+ ];
+
+ server [
+ label="{ client1 | mgmt | { client2 | client3 } } | server ",
+ pos="20,120!",
+
+ requires="infix",
+ ];
+
+ client2 [
+ label="{ server | mgmt } | client2",
+ pos="10,110!",
+
+ requires="infix",
+ ];
+
+ client3 [
+ label="{ server | mgmt } | client3",
+ pos="30,110!",
+
+ requires="infix",
+ ];
+
+ host:server -- server:mgmt [requires="mgmt", color=lightgrey]
+ host:client1 -- client1:mgmt [requires="mgmt", color=lightgrey]
+ host:client2 -- client2:mgmt [requires="mgmt", color=lightgrey]
+ host:client3 -- client3:mgmt [requires="mgmt", color=lightgrey]
+
+ server:client1 -- client1:server [color=blue, fontcolor=black, taillabel="192.168.1.1/24"]
+ server:client2 -- client2:server [color=green]
+ server:client3 -- client3:server [color=green, fontcolor=black, taillabel="192.168.2.1/24"]
+}
diff --git a/test/case/infix_dhcp/server_subnets/topology.svg b/test/case/infix_dhcp/server_subnets/topology.svg
new file mode 100644
index 00000000..bbd96e3c
--- /dev/null
+++ b/test/case/infix_dhcp/server_subnets/topology.svg
@@ -0,0 +1,107 @@
+
+
+
+
+