test: new test, verify DHCP server with multiple subnets

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2025-01-31 13:56:40 +01:00
parent 27dae1edf0
commit e45fcfca41
8 changed files with 540 additions and 0 deletions
+2
View File
@@ -12,3 +12,5 @@ include::client_routes/Readme.adoc[]
include::server_basic/Readme.adoc[]
include::server_host/Readme.adoc[]
include::server_subnets/Readme.adoc[]
+3
View File
@@ -13,3 +13,6 @@
- name: server_host
case: server_host/test.py
- name: server_subnets
case: server_subnets/test.py
+1
View File
@@ -0,0 +1 @@
server_subnets.adoc
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 519 KiB

@@ -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)
<<<
+313
View File
@@ -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()
@@ -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> client1 | <server> server | <client3> client3 | <client2> client2 }",
pos="0,120!",
requires="controller",
];
client1 [
label="{ <mgmt> mgmt | <server> server} | client1",
pos="20,130!",
requires="infix",
];
server [
label="{ <client1> client1 | <mgmt> mgmt | { <client2> client2 | <client3> client3 } } | server&nbsp;",
pos="20,120!",
requires="infix",
];
client2 [
label="{ <server> server | <mgmt> mgmt } | client2",
pos="10,110!",
requires="infix",
];
client3 [
label="{ <server> server | <mgmt> 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"]
}
@@ -0,0 +1,107 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Title: server subnets Pages: 1 -->
<svg width="650pt" height="395pt"
viewBox="0.00 0.00 650.05 395.03" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 391.03)">
<title>server subnets</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-391.03 646.05,-391.03 646.05,4 -4,4"/>
<!-- host -->
<g id="node1" class="node">
<title>host</title>
<polygon fill="none" stroke="black" points="0,-147.52 0,-239.52 124,-239.52 124,-147.52 0,-147.52"/>
<text text-anchor="middle" x="25" y="-189.82" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
<polyline fill="none" stroke="black" points="50,-147.52 50,-239.52 "/>
<text text-anchor="middle" x="87" y="-224.32" font-family="DejaVu Sans Mono, Book" font-size="14.00">client1</text>
<polyline fill="none" stroke="black" points="50,-216.52 124,-216.52 "/>
<text text-anchor="middle" x="87" y="-201.32" font-family="DejaVu Sans Mono, Book" font-size="14.00">server</text>
<polyline fill="none" stroke="black" points="50,-193.52 124,-193.52 "/>
<text text-anchor="middle" x="87" y="-178.32" font-family="DejaVu Sans Mono, Book" font-size="14.00">client3</text>
<polyline fill="none" stroke="black" points="50,-170.52 124,-170.52 "/>
<text text-anchor="middle" x="87" y="-155.32" font-family="DejaVu Sans Mono, Book" font-size="14.00">client2</text>
</g>
<!-- client1 -->
<g id="node2" class="node">
<title>client1</title>
<polygon fill="none" stroke="black" points="332.03,-340.53 332.03,-386.53 472.03,-386.53 472.03,-340.53 332.03,-340.53"/>
<text text-anchor="middle" x="365.03" y="-371.33" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="332.03,-363.53 398.03,-363.53 "/>
<text text-anchor="middle" x="365.03" y="-348.33" font-family="DejaVu Sans Mono, Book" font-size="14.00">server</text>
<polyline fill="none" stroke="black" points="398.03,-340.53 398.03,-386.53 "/>
<text text-anchor="middle" x="435.03" y="-359.83" font-family="DejaVu Sans Mono, Book" font-size="14.00">client1</text>
</g>
<!-- host&#45;&#45;client1 -->
<g id="edge2" class="edge">
<title>host:client1&#45;&#45;client1:mgmt</title>
<path fill="none" stroke="lightgrey" stroke-width="2" d="M124,-228.52C124,-228.52 332.03,-375.53 332.03,-375.53"/>
</g>
<!-- server -->
<g id="node3" class="node">
<title>server</title>
<polygon fill="none" stroke="black" points="291.03,-159.02 291.03,-228.02 513.03,-228.02 513.03,-159.02 291.03,-159.02"/>
<text text-anchor="middle" x="365.03" y="-212.82" font-family="DejaVu Sans Mono, Book" font-size="14.00">client1</text>
<polyline fill="none" stroke="black" points="291.03,-205.02 439.03,-205.02 "/>
<text text-anchor="middle" x="365.03" y="-189.82" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="291.03,-182.02 439.03,-182.02 "/>
<text text-anchor="middle" x="328.03" y="-166.82" font-family="DejaVu Sans Mono, Book" font-size="14.00">client2</text>
<polyline fill="none" stroke="black" points="365.03,-159.02 365.03,-182.02 "/>
<text text-anchor="middle" x="402.03" y="-166.82" font-family="DejaVu Sans Mono, Book" font-size="14.00">client3</text>
<polyline fill="none" stroke="black" points="439.03,-159.02 439.03,-228.02 "/>
<text text-anchor="middle" x="476.03" y="-189.82" font-family="DejaVu Sans Mono, Book" font-size="14.00">server </text>
</g>
<!-- host&#45;&#45;server -->
<g id="edge1" class="edge">
<title>host:server&#45;&#45;server:mgmt</title>
<path fill="none" stroke="lightgrey" stroke-width="2" d="M124,-205.52C124,-205.52 291.03,-193.52 291.03,-193.52"/>
</g>
<!-- client2 -->
<g id="node4" class="node">
<title>client2</title>
<polygon fill="none" stroke="black" points="162.02,-0.5 162.02,-46.5 302.02,-46.5 302.02,-0.5 162.02,-0.5"/>
<text text-anchor="middle" x="195.02" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">server</text>
<polyline fill="none" stroke="black" points="162.02,-23.5 228.02,-23.5 "/>
<text text-anchor="middle" x="195.02" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="228.02,-0.5 228.02,-46.5 "/>
<text text-anchor="middle" x="265.02" y="-19.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">client2</text>
</g>
<!-- host&#45;&#45;client2 -->
<g id="edge3" class="edge">
<title>host:client2&#45;&#45;client2:mgmt</title>
<path fill="none" stroke="lightgrey" stroke-width="2" d="M124,-158.52C124,-158.52 162.02,-11.5 162.02,-11.5"/>
</g>
<!-- client3 -->
<g id="node5" class="node">
<title>client3</title>
<polygon fill="none" stroke="black" points="502.05,-0.5 502.05,-46.5 642.05,-46.5 642.05,-0.5 502.05,-0.5"/>
<text text-anchor="middle" x="535.05" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">server</text>
<polyline fill="none" stroke="black" points="502.05,-23.5 568.05,-23.5 "/>
<text text-anchor="middle" x="535.05" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="568.05,-0.5 568.05,-46.5 "/>
<text text-anchor="middle" x="605.05" y="-19.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">client3</text>
</g>
<!-- host&#45;&#45;client3 -->
<g id="edge4" class="edge">
<title>host:client3&#45;&#45;client3:mgmt</title>
<path fill="none" stroke="lightgrey" stroke-width="2" d="M124,-181.52C124,-181.52 502.05,-11.5 502.05,-11.5"/>
</g>
<!-- server&#45;&#45;client1 -->
<g id="edge5" class="edge">
<title>server:client1&#45;&#45;client1:server</title>
<path fill="none" stroke="blue" stroke-width="2" d="M365.03,-228.52C365.03,-228.52 365.03,-340.53 365.03,-340.53"/>
<text text-anchor="middle" x="310.53" y="-232.32" font-family="DejaVu Serif, Book" font-size="14.00">192.168.1.1/24</text>
</g>
<!-- server&#45;&#45;client2 -->
<g id="edge6" class="edge">
<title>server:client2&#45;&#45;client2:server</title>
<path fill="none" stroke="green" stroke-width="2" d="M291.03,-170.52C291.03,-170.52 195.02,-46.5 195.02,-46.5"/>
</g>
<!-- server&#45;&#45;client3 -->
<g id="edge7" class="edge">
<title>server:client3&#45;&#45;client3:server</title>
<path fill="none" stroke="green" stroke-width="2" d="M402.03,-158.52C402.03,-158.52 502.05,-35.5 502.05,-35.5"/>
<text text-anchor="middle" x="347.53" y="-147.32" font-family="DejaVu Serif, Book" font-size="14.00">192.168.2.1/24</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.5 KiB