Merge pull request #1410 from saba8814/issue-782-dhcp-ntp-dns

Issue 782 dhcp ntp dns
This commit is contained in:
Joachim Wiberg
2026-03-04 13:52:12 +01:00
committed by GitHub
7 changed files with 313 additions and 0 deletions
+2
View File
@@ -5,5 +5,7 @@ Integration tests verifying multiple software components working
together:
- OSPF routing with containerized applications and network segmentation
- Combined static and DHCP-provided DNS and NTP servers
include::dhcp_ntp_dns_combination/Readme.adoc[]
include::ospf_container/Readme.adoc[]
+3
View File
@@ -1,3 +1,6 @@
---
- name: DHCP NTP DNS combination
case: dhcp_ntp_dns_combination/test.py
- name: OSPF Container
case: ospf_container/test.py
@@ -0,0 +1 @@
test.adoc
@@ -0,0 +1,21 @@
=== DHCP NTP DNS combination
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/use_case/dhcp_ntp_dns_combination]
==== Description
Verify statically configured DNS and NTP servers are not lost when receiving
servers from a DHCP server.
==== Topology
image::topology.svg[DHCP NTP DNS combination topology, align=center, scaledwidth=75%]
==== Sequence
. Set up topology and configure static IP on client
. Configure static DNS and NTP on client and verify
. Set up DHCP server with NTP and DNS options
. Configure client to use DHCP and verify combined servers
+198
View File
@@ -0,0 +1,198 @@
#!/usr/bin/env python3
"""
Combined test of DHCP + NTP + DNS
Verify statically configured DNS and NTP servers are not lost when receiving
servers from a DHCP server.
"""
import infamy
import infamy.iface as iface
from infamy.util import until
def any_dhcp_address(target, iface_name):
try:
addrs = iface.get_ipv4_address(target, iface_name)
if addrs:
for addr in addrs:
# Origin can be 'dhcp' or 'ietf-ip:dhcp' depending on how it's reported
if addr.get('origin') in ['dhcp', 'ietf-ip:dhcp']:
return True
except Exception:
return False
return False
def has_dns_server(target, dns_servers):
"""Verify system has all the given DNS servers in operational state"""
data = target.get_data("/ietf-system:system-state/infix-system:dns-resolver")
if not data:
return False
# In Infix, augmented nodes show up under their name in system-state
resolver = data.get('system-state', {}).get('dns-resolver', {})
servers = resolver.get('server', [])
current_addresses = {s.get('address') for s in servers}
return all(addr in current_addresses for addr in dns_servers)
def has_ntp_server(target, ntp_servers):
"""Verify system has all the given NTP servers in operational state"""
data = target.get_data("/ietf-system:system-state/infix-system:ntp")
if not data:
return False
ntp_state = data.get('system-state', {}).get('ntp', {})
sources = ntp_state.get('sources', {}).get('source', [])
current_addresses = {s.get('address') for s in sources}
return all(addr in current_addresses for addr in ntp_servers)
def has_system_servers(target, dns, ntp_list):
"""Verify DUT has all DNS and NTP server(s)"""
return has_dns_server(target, dns) and has_ntp_server(target, ntp_list)
with infamy.Test() as test:
SERVER_IP = '192.168.3.1'
CLIENT_STATIC_IP = '192.168.3.10'
STATIC_DNS = '1.1.1.1'
STATIC_NTP = '2.2.2.2'
DHCP_DNS = SERVER_IP
DHCP_NTP = SERVER_IP
with test.step("Set up topology and configure static IP on client"):
env = infamy.Env()
server = env.attach("server", "mgmt")
client = env.attach("client", "mgmt")
client_data = client["server"]
server_data = server["client"]
# Configure server with static IP and NTP server
server.put_config_dicts({
"ietf-interfaces": {
"interfaces": {
"interface": [{
"name": server_data,
"type": "infix-if-type:ethernet",
"enabled": True,
"ipv4": {
"address": [{"ip": SERVER_IP, "prefix-length": 24}]
}
}]
}
},
"ietf-ntp": {
"ntp": {
"refclock-master": {"master-stratum": 8}
}
}
})
# Configure client with static IP
client.put_config_dicts({
"ietf-interfaces": {
"interfaces": {
"interface": [{
"name": client_data,
"type": "infix-if-type:ethernet",
"enabled": True,
"ipv4": {
"address": [{"ip": CLIENT_STATIC_IP, "prefix-length": 24}]
}
}]
}
}
})
until(lambda: iface.address_exist(client, client_data, CLIENT_STATIC_IP, proto="static"))
print("Initial IP connectivity established")
with test.step("Configure static DNS and NTP on client and verify"):
client.put_config_dicts({
"ietf-system": {
"system": {
"ntp": {
"enabled": True,
"server": [{
"name": "static-ntp",
"udp": {"address": STATIC_NTP},
"iburst": True
}]
},
"dns-resolver": {
"server": [{
"name": "static-dns",
"udp-and-tcp": {"address": STATIC_DNS}
}]
}
}
}
})
print("Waiting for static servers to appear in operational state...")
until(lambda: has_system_servers(client, [STATIC_DNS], [STATIC_NTP]),
attempts=30)
print("Static DNS and NTP servers verified")
with test.step("Set up DHCP server with NTP and DNS options"):
server.put_config_dicts({
"infix-dhcp-server": {
"dhcp-server": {
"subnet": [{
"subnet": "192.168.3.0/24",
"interface": server_data,
"pool": {
"start-address": "192.168.3.100",
"end-address": "192.168.3.150"
},
"option": [
{"id": "router", "address": SERVER_IP},
{"id": "dns-server", "address": DHCP_DNS},
{"id": "ntp-server", "address": DHCP_NTP}
]
}]
}
}
})
print("DHCP server configured on server DUT")
with test.step("Configure client to use DHCP and verify combined servers"):
# Explicitly delete static config using full path
try:
# Try to delete the entire ipv4 container from ietf-ip
client.delete_xpath(f"ietf-interfaces:interfaces/interface[name='{client_data}']/ietf-ip:ipv4")
except Exception:
pass
# Enable DHCP
client.put_config_dicts({
"ietf-interfaces": {
"interfaces": {
"interface": [{
"name": client_data,
"ipv4": {
"infix-dhcp-client:dhcp": {
"client-id": "client-1",
"option": [
{"id": "dns-server"},
{"id": "ntp-server"}
]
}
}
}]
}
}
})
print("Waiting for DHCP lease...")
until(lambda: any_dhcp_address(client, client_data), attempts=120)
print("Client got DHCP address")
print("Verifying combined DNS and NTP servers...")
# Expected DNS: STATIC_DNS (1.1.1.1) AND DHCP_DNS (192.168.3.1)
# Expected NTP: STATIC_NTP (2.2.2.2) AND DHCP_NTP (192.168.3.1)
until(lambda: has_system_servers(client, [STATIC_DNS, DHCP_DNS], [STATIC_NTP, DHCP_NTP]),
attempts=60)
print("SUCCESS: Combined static and DHCP servers verified")
test.succeed()
@@ -0,0 +1,31 @@
graph "dhcp_ntp_dns_combination" {
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 | { <client> client | <server> server }",
pos="0,120!",
requires="controller",
];
client [
label="{ <mgmt> mgmt | <server> server} | client",
pos="20,130!",
requires="infix",
];
server [
label="{ <client> client | <mgmt> mgmt } | server",
pos="20,120!",
requires="infix",
];
host:server -- server:mgmt [requires="mgmt", color=lightgrey]
host:client -- client:mgmt [requires="mgmt", color=lightgrey]
server:client -- client:server [color=blue, fontcolor=black]
}
@@ -0,0 +1,57 @@
<?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: dhcp_ntp_dns_combination Pages: 1 -->
<svg width="426pt" height="202pt"
viewBox="0.00 0.00 426.03 202.01" 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 198.01)">
<title>dhcp_ntp_dns_combination</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-198.01 422.03,-198.01 422.03,4 -4,4"/>
<!-- host -->
<g id="node1" class="node">
<title>host</title>
<polygon fill="none" stroke="black" points="0,-0.5 0,-46.5 116,-46.5 116,-0.5 0,-0.5"/>
<text text-anchor="middle" x="25" y="-19.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
<polyline fill="none" stroke="black" points="50,-0.5 50,-46.5 "/>
<text text-anchor="middle" x="83" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">client</text>
<polyline fill="none" stroke="black" points="50,-23.5 116,-23.5 "/>
<text text-anchor="middle" x="83" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">server</text>
</g>
<!-- client -->
<g id="node2" class="node">
<title>client</title>
<polygon fill="none" stroke="black" points="286.03,-147.51 286.03,-193.51 418.03,-193.51 418.03,-147.51 286.03,-147.51"/>
<text text-anchor="middle" x="319.03" y="-178.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="286.03,-170.51 352.03,-170.51 "/>
<text text-anchor="middle" x="319.03" y="-155.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">server</text>
<polyline fill="none" stroke="black" points="352.03,-147.51 352.03,-193.51 "/>
<text text-anchor="middle" x="385.03" y="-166.81" font-family="DejaVu Sans Mono, Book" font-size="14.00">client</text>
</g>
<!-- host&#45;&#45;client -->
<g id="edge2" class="edge">
<title>host:client&#45;&#45;client:mgmt</title>
<path fill="none" stroke="lightgrey" stroke-width="2" d="M116,-35.5C116,-35.5 286.03,-182.51 286.03,-182.51"/>
</g>
<!-- server -->
<g id="node3" class="node">
<title>server</title>
<polygon fill="none" stroke="black" points="286.03,-0.5 286.03,-46.5 418.03,-46.5 418.03,-0.5 286.03,-0.5"/>
<text text-anchor="middle" x="319.03" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">client</text>
<polyline fill="none" stroke="black" points="286.03,-23.5 352.03,-23.5 "/>
<text text-anchor="middle" x="319.03" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="352.03,-0.5 352.03,-46.5 "/>
<text text-anchor="middle" x="385.03" y="-19.8" 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="M116,-11.5C116,-11.5 286.03,-11.5 286.03,-11.5"/>
</g>
<!-- server&#45;&#45;client -->
<g id="edge3" class="edge">
<title>server:client&#45;&#45;client:server</title>
<path fill="none" stroke="blue" stroke-width="2" d="M319.03,-46.5C319.03,-46.5 319.03,-147.51 319.03,-147.51"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB