ci: cram: add dhcpv4-client-options test for TR-181 Client.Option
Add two dhcpv4 client tests. first one check if option provided by a client is correctly provided in the DHCPv4.Server DM. Second one check that if multiple client provide the same option with different value, the value are not mixed up. References: PCF-2262 Signed-off-by: Alexandre Fiset <alexandre.fiset@softathome.com>
This commit is contained in:
parent
bad40d104e
commit
20902bec03
@ -28,6 +28,7 @@ Create pool option with Force=true:
|
||||
Force=true, tag 42 absent from DHCPREQUEST PRL -> tag must be present in final DHCPACK:
|
||||
|
||||
$ sudo -E python3 "$TESTDIR/dhcpv4_option_probe.py" --iface "$TESTBED_LAN_INTERFACE" --request-options "$REQ_WITHOUT_TAG" --timeout "$PROBE_TIMEOUT" 2>/dev/null
|
||||
CLIENT_MAC=aa:bb:cc:dd:ee:ff
|
||||
RECEIVED_TAGS=.*(=|,)42(,|$).* (re)
|
||||
|
||||
Set Force=false:
|
||||
@ -40,11 +41,13 @@ Set Force=false:
|
||||
Force=false, tag 42 absent from DHCPREQUEST PRL -> tag must be absent from final DHCPACK:
|
||||
|
||||
$ sudo -E python3 "$TESTDIR/dhcpv4_option_probe.py" --iface "$TESTBED_LAN_INTERFACE" --request-options "$REQ_WITHOUT_TAG" --timeout "$PROBE_TIMEOUT" 2>/dev/null
|
||||
CLIENT_MAC=aa:bb:cc:dd:ee:ff
|
||||
RECEIVED_TAGS=(?!.*(=|,)42(,|$)).* (re)
|
||||
|
||||
Force=false, tag 42 present in DHCPREQUEST PRL -> tag must be present in final DHCPACK:
|
||||
|
||||
$ sudo -E python3 "$TESTDIR/dhcpv4_option_probe.py" --iface "$TESTBED_LAN_INTERFACE" --request-options "$REQ_WITH_TAG" --timeout "$PROBE_TIMEOUT" 2>/dev/null
|
||||
CLIENT_MAC=aa:bb:cc:dd:ee:ff
|
||||
RECEIVED_TAGS=.*(=|,)42(,|$).* (re)
|
||||
|
||||
|
||||
@ -0,0 +1,118 @@
|
||||
Create R alias:
|
||||
|
||||
$ alias R="${CRAM_REMOTE_COMMAND:-}"
|
||||
|
||||
Install scapy with pip:
|
||||
|
||||
$ pip install scapy >/dev/null 2>&1
|
||||
|
||||
Verify python can import scapy:
|
||||
|
||||
$ python3 -c "import scapy.all" >/dev/null 2>&1
|
||||
|
||||
Pick test constants:
|
||||
|
||||
$ PROBE_TIMEOUT=12
|
||||
$ MAC_A="aa:bb:cc:dd:ee:01"
|
||||
$ MAC_B="aa:bb:cc:dd:ee:02"
|
||||
$ HOSTNAME_A="cramhostA"
|
||||
$ HOSTNAME_B="cramhostB"
|
||||
$ HOSTNAME_A_HEX="6372616d686f737441"
|
||||
$ HOSTNAME_B_HEX="6372616d686f737442"
|
||||
|
||||
Test 1 - basic client options (option 12 hostname):
|
||||
|
||||
$ sudo -E python3 "$TESTDIR/dhcpv4_option_probe.py" --iface "$TESTBED_LAN_INTERFACE" --chaddr "$MAC_A" --hostname "$HOSTNAME_A" --timeout "$PROBE_TIMEOUT" 2>/dev/null
|
||||
CLIENT_MAC=aa:bb:cc:dd:ee:01
|
||||
SENT_HOSTNAME=cramhostA
|
||||
RECEIVED_TAGS=.* (re)
|
||||
|
||||
$ sleep 3
|
||||
|
||||
Verify client A appears in DHCPv4.Server.Pool.Client:
|
||||
|
||||
$ R "ba-cli 'Device.DHCPv4.Server.Pool.lan.Client.[Chaddr==\"$MAC_A\"].Chaddr?'" | grep -Ev '^(>|$)'
|
||||
Device\.DHCPv4\.Server\.Pool\.[0-9]+\.Client\.[0-9]+\.Chaddr=.* (re)
|
||||
|
||||
Verify option 12 (hostname) is recorded under client A:
|
||||
|
||||
$ R "ba-cli 'Device.DHCPv4.Server.Pool.lan.Client.[Chaddr==\"$MAC_A\"].Option.[Tag==12].Tag?'" | grep -Ev '^(>|$)'
|
||||
Device\.DHCPv4\.Server\.Pool\.[0-9]+\.Client\.[0-9]+\.Option\.[0-9]+\.Tag=12 (re)
|
||||
|
||||
Verify option 12 value matches hostname A hex encoding:
|
||||
|
||||
$ R "ba-cli 'Device.DHCPv4.Server.Pool.lan.Client.[Chaddr==\"$MAC_A\"].Option.[Tag==12].Value?'" | grep -Ev '^(>|$)'
|
||||
Device\.DHCPv4\.Server\.Pool\.[0-9]+\.Client\.[0-9]+\.Option\.[0-9]+\.Value="6372616d686f737441" (re)
|
||||
|
||||
Verify option 53 (DHCP Message Type) is recorded under client A:
|
||||
|
||||
$ R "ba-cli 'Device.DHCPv4.Server.Pool.lan.Client.[Chaddr==\"$MAC_A\"].Option.[Tag==53].Tag?'" | grep -Ev '^(>|$)'
|
||||
Device\.DHCPv4\.Server\.Pool\.[0-9]+\.Client\.[0-9]+\.Option\.[0-9]+\.Tag=53 (re)
|
||||
|
||||
$ R "ba-cli 'Device.DHCPv4.Server.Pool.lan.Client.[Chaddr==\"$MAC_A\"].Option.[Tag==53].Value?'" | grep -Ev '^(>|$)'
|
||||
Device\.DHCPv4\.Server\.Pool\.[0-9]+\.Client\.[0-9]+\.Option\.[0-9]+\.Value="03" (re)
|
||||
|
||||
Test 2 - multiple clients each with their own options (C-2 per FEAT-13 spec):
|
||||
|
||||
$ sudo -E python3 "$TESTDIR/dhcpv4_option_probe.py" --iface "$TESTBED_LAN_INTERFACE" --chaddr "$MAC_B" --hostname "$HOSTNAME_B" --timeout "$PROBE_TIMEOUT" 2>/dev/null
|
||||
CLIENT_MAC=aa:bb:cc:dd:ee:02
|
||||
SENT_HOSTNAME=cramhostB
|
||||
RECEIVED_TAGS=.* (re)
|
||||
|
||||
$ sleep 3
|
||||
|
||||
Verify client B appears in DHCPv4.Server.Pool.Client:
|
||||
|
||||
$ R "ba-cli 'Device.DHCPv4.Server.Pool.lan.Client.[Chaddr==\"$MAC_B\"].Chaddr?'" | grep -Ev '^(>|$)'
|
||||
Device\.DHCPv4\.Server\.Pool\.[0-9]+\.Client\.[0-9]+\.Chaddr=.* (re)
|
||||
|
||||
Both clients must appear simultaneously:
|
||||
|
||||
$ R "ba-cli 'Device.DHCPv4.Server.Pool.lan.Client.[Chaddr==\"$MAC_A\"].Chaddr?'" | grep -Ev '^(>|$)'
|
||||
Device\.DHCPv4\.Server\.Pool\.[0-9]+\.Client\.[0-9]+\.Chaddr=.* (re)
|
||||
|
||||
$ R "ba-cli 'Device.DHCPv4.Server.Pool.lan.Client.[Chaddr==\"$MAC_B\"].Chaddr?'" | grep -Ev '^(>|$)'
|
||||
Device\.DHCPv4\.Server\.Pool\.[0-9]+\.Client\.[0-9]+\.Chaddr=.* (re)
|
||||
|
||||
Client A must carry hostname A hex (option 12), not hostname B hex:
|
||||
|
||||
$ R "ba-cli 'Device.DHCPv4.Server.Pool.lan.Client.[Chaddr==\"$MAC_A\"].Option.[Tag==12].Value?'" | grep -Ev '^(>|$)'
|
||||
Device\.DHCPv4\.Server\.Pool\.[0-9]+\.Client\.[0-9]+\.Option\.[0-9]+\.Value="6372616d686f737441" (re)
|
||||
|
||||
$ R "ba-cli 'Device.DHCPv4.Server.Pool.lan.Client.[Chaddr==\"$MAC_A\"].Option.[Value==\"${HOSTNAME_B_HEX}\"].Value?'" | grep -Ev '^(>|$)'
|
||||
No data found
|
||||
|
||||
Client B must carry hostname B hex (option 12), not hostname A hex:
|
||||
|
||||
$ R "ba-cli 'Device.DHCPv4.Server.Pool.lan.Client.[Chaddr==\"$MAC_B\"].Option.[Tag==12].Value?'" | grep -Ev '^(>|$)'
|
||||
Device\.DHCPv4\.Server\.Pool\.[0-9]+\.Client\.[0-9]+\.Option\.[0-9]+\.Value="6372616d686f737442" (re)
|
||||
|
||||
$ R "ba-cli 'Device.DHCPv4.Server.Pool.lan.Client.[Chaddr==\"$MAC_B\"].Option.[Value==\"${HOSTNAME_A_HEX}\"].Value?'" | grep -Ev '^(>|$)'
|
||||
No data found
|
||||
|
||||
Verify option 53 (DHCP Message Type) is recorded under client B (C-1 per FEAT-13 spec):
|
||||
|
||||
$ R "ba-cli 'Device.DHCPv4.Server.Pool.lan.Client.[Chaddr==\"$MAC_B\"].Option.[Tag==53].Tag?'" | grep -Ev '^(>|$)'
|
||||
Device\.DHCPv4\.Server\.Pool\.[0-9]+\.Client\.[0-9]+\.Option\.[0-9]+\.Tag=53 (re)
|
||||
|
||||
Cleanup - release both leases and verify clients are marked inactive in DM:
|
||||
|
||||
$ sudo -E python3 "$TESTDIR/dhcpv4_option_probe.py" --iface "$TESTBED_LAN_INTERFACE" --chaddr "$MAC_A" --hostname "$HOSTNAME_A" --release --timeout "$PROBE_TIMEOUT" 2>/dev/null
|
||||
CLIENT_MAC=aa:bb:cc:dd:ee:01
|
||||
SENT_HOSTNAME=cramhostA
|
||||
RECEIVED_TAGS=.* (re)
|
||||
RELEASED=.* (re)
|
||||
|
||||
$ sudo -E python3 "$TESTDIR/dhcpv4_option_probe.py" --iface "$TESTBED_LAN_INTERFACE" --chaddr "$MAC_B" --hostname "$HOSTNAME_B" --release --timeout "$PROBE_TIMEOUT" 2>/dev/null
|
||||
CLIENT_MAC=aa:bb:cc:dd:ee:02
|
||||
SENT_HOSTNAME=cramhostB
|
||||
RECEIVED_TAGS=.* (re)
|
||||
RELEASED=.* (re)
|
||||
|
||||
$ sleep 3
|
||||
|
||||
$ R "ba-cli 'Device.DHCPv4.Server.Pool.lan.Client.[Chaddr==\"$MAC_A\"].Active?'" | grep -Ev '^(>|$)'
|
||||
Device\.DHCPv4\.Server\.Pool\.[0-9]+\.Client\.[0-9]+\.Active=0 (re)
|
||||
|
||||
$ R "ba-cli 'Device.DHCPv4.Server.Pool.lan.Client.[Chaddr==\"$MAC_B\"].Active?'" | grep -Ev '^(>|$)'
|
||||
Device\.DHCPv4\.Server\.Pool\.[0-9]+\.Client\.[0-9]+\.Active=0 (re)
|
||||
@ -3,16 +3,15 @@
|
||||
Minimal DHCPv4 probe for Cram:
|
||||
- Sends DHCPDISCOVER with configurable Parameter Request List (PRL)
|
||||
- Sends DHCPREQUEST using the offered address and server identifier
|
||||
- Prints received option tags from final DHCPACK as:
|
||||
RECEIVED_TAGS=1,3,6,...
|
||||
- Optional debug output can print the PRL sent in DHCPREQUEST
|
||||
- Prints CLIENT_MAC=<mac> then RECEIVED_TAGS=1,3,6,...
|
||||
- Optional: custom MAC (--chaddr) and hostname option 12 (--hostname)
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import random
|
||||
import sys
|
||||
|
||||
from scapy.all import BOOTP, DHCP, Ether, IP, UDP, conf, srp1 # type: ignore
|
||||
from scapy.all import BOOTP, DHCP, Ether, IP, UDP, conf, sendp, srp1 # type: ignore
|
||||
from scapy.layers.dhcp import DHCPRevOptions # type: ignore
|
||||
|
||||
|
||||
@ -22,6 +21,9 @@ def parse_args():
|
||||
parser.add_argument("--request-options", default="", help="CSV option tags in PRL, e.g. 1,3,6,15,42")
|
||||
parser.add_argument("--timeout", type=int, default=8)
|
||||
parser.add_argument("--debug", action="store_true")
|
||||
parser.add_argument("--chaddr", default="", help="MAC as aa:bb:cc:dd:ee:ff (default: aa:bb:cc:dd:ee:ff)")
|
||||
parser.add_argument("--hostname", default="", help="Hostname string for DHCP option 12")
|
||||
parser.add_argument("--release", action="store_true", help="Send DHCPRELEASE after DHCPACK")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
@ -31,25 +33,38 @@ def parse_prl(csv_text):
|
||||
return [int(value.strip()) for value in csv_text.split(",") if value.strip()]
|
||||
|
||||
|
||||
def parse_chaddr(mac_str):
|
||||
if not mac_str.strip():
|
||||
return b"\xaa\xbb\xcc\xdd\xee\xff"
|
||||
try:
|
||||
octets = [int(b, 16) for b in mac_str.strip().split(":")]
|
||||
except ValueError:
|
||||
print("ERROR: invalid --chaddr value: " + mac_str, file=sys.stderr)
|
||||
sys.exit(2)
|
||||
if len(octets) != 6:
|
||||
print("ERROR: --chaddr must have exactly 6 octets, got " + str(len(octets)), file=sys.stderr)
|
||||
sys.exit(2)
|
||||
if any(o < 0 or o > 255 for o in octets):
|
||||
print("ERROR: --chaddr octet out of range 0-255: " + mac_str, file=sys.stderr)
|
||||
sys.exit(2)
|
||||
return bytes(octets)
|
||||
|
||||
|
||||
def extract_option_tags(dhcp_options):
|
||||
tags = []
|
||||
for opt in dhcp_options:
|
||||
if not (isinstance(opt, tuple) and len(opt) >= 1):
|
||||
continue
|
||||
|
||||
name = opt[0]
|
||||
|
||||
if isinstance(name, int):
|
||||
tags.append(name)
|
||||
continue
|
||||
|
||||
if isinstance(name, str):
|
||||
code = DHCPRevOptions.get(name)
|
||||
if isinstance(code, int):
|
||||
tags.append(code)
|
||||
elif isinstance(code, tuple) and len(code) >= 1 and isinstance(code[0], int):
|
||||
tags.append(code[0])
|
||||
|
||||
return sorted(set(tags))
|
||||
|
||||
|
||||
@ -66,14 +81,8 @@ def get_message_type(dhcp_options):
|
||||
return value
|
||||
if isinstance(value, str):
|
||||
value_map = {
|
||||
"discover": 1,
|
||||
"offer": 2,
|
||||
"request": 3,
|
||||
"decline": 4,
|
||||
"ack": 5,
|
||||
"nak": 6,
|
||||
"release": 7,
|
||||
"inform": 8,
|
||||
"discover": 1, "offer": 2, "request": 3, "decline": 4,
|
||||
"ack": 5, "nak": 6, "release": 7, "inform": 8,
|
||||
}
|
||||
return value_map.get(value.lower())
|
||||
return None
|
||||
@ -85,17 +94,24 @@ def main():
|
||||
|
||||
xid = random.randint(1, 0xFFFFFFFF)
|
||||
prl = parse_prl(args.request_options)
|
||||
chaddr = parse_chaddr(args.chaddr)
|
||||
mac_str = args.chaddr.strip() if args.chaddr.strip() else "aa:bb:cc:dd:ee:ff"
|
||||
|
||||
print("CLIENT_MAC=" + mac_str)
|
||||
if args.hostname:
|
||||
print("SENT_HOSTNAME=" + args.hostname)
|
||||
|
||||
discover_options = [("message-type", "discover"), ("param_req_list", prl)]
|
||||
if args.hostname:
|
||||
discover_options.append(("hostname", args.hostname.encode()))
|
||||
discover_options.append("end")
|
||||
|
||||
discover_packet = (
|
||||
Ether(dst="ff:ff:ff:ff:ff:ff")
|
||||
/ IP(src="0.0.0.0", dst="255.255.255.255")
|
||||
/ UDP(sport=68, dport=67)
|
||||
/ BOOTP(chaddr=b"\xaa\xbb\xcc\xdd\xee\xff", xid=xid, flags=0x8000)
|
||||
/ DHCP(options=[
|
||||
("message-type", "discover"),
|
||||
("param_req_list", prl),
|
||||
"end",
|
||||
])
|
||||
/ BOOTP(chaddr=chaddr, xid=xid, flags=0x8000)
|
||||
/ DHCP(options=discover_options)
|
||||
)
|
||||
|
||||
if args.debug:
|
||||
@ -123,8 +139,10 @@ def main():
|
||||
("requested_addr", offered_ip),
|
||||
("param_req_list", prl),
|
||||
]
|
||||
if args.hostname:
|
||||
request_options.append(("hostname", args.hostname.encode()))
|
||||
if args.debug:
|
||||
print("DEBUG_DHCPREQUEST_PRL=" + ",".join(str(tag) for tag in prl))
|
||||
print("DEBUG_DHCPREQUEST_PRL=" + ",".join(str(tag) for tag in prl), file=sys.stderr)
|
||||
if server_identifier is not None:
|
||||
request_options.append(("server_id", server_identifier))
|
||||
request_options.append("end")
|
||||
@ -133,7 +151,7 @@ def main():
|
||||
Ether(dst="ff:ff:ff:ff:ff:ff")
|
||||
/ IP(src="0.0.0.0", dst="255.255.255.255")
|
||||
/ UDP(sport=68, dport=67)
|
||||
/ BOOTP(chaddr=b"\xaa\xbb\xcc\xdd\xee\xff", xid=xid, flags=0x8000)
|
||||
/ BOOTP(chaddr=chaddr, xid=xid, flags=0x8000)
|
||||
/ DHCP(options=request_options)
|
||||
)
|
||||
|
||||
@ -160,6 +178,25 @@ def main():
|
||||
|
||||
tags = extract_option_tags(final_reply[DHCP].options)
|
||||
print("RECEIVED_TAGS=" + ",".join(str(tag) for tag in tags))
|
||||
|
||||
if args.release:
|
||||
release_packet = (
|
||||
Ether(dst="ff:ff:ff:ff:ff:ff")
|
||||
/ IP(src=offered_ip, dst=server_identifier or "255.255.255.255")
|
||||
/ UDP(sport=68, dport=67)
|
||||
/ BOOTP(chaddr=chaddr, xid=xid, ciaddr=offered_ip)
|
||||
/ DHCP(options=[
|
||||
("message-type", "release"),
|
||||
("server_id", server_identifier),
|
||||
"end",
|
||||
])
|
||||
)
|
||||
if args.debug:
|
||||
print("--- DHCPRELEASE sent ---", file=sys.stderr)
|
||||
print(release_packet.show(dump=True), file=sys.stderr)
|
||||
sendp(release_packet, iface=args.iface, verbose=False)
|
||||
print("RELEASED=" + offered_ip)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user