diff --git a/Sanity/client-ipv4-tests/Makefile b/Sanity/client-ipv4-tests/Makefile new file mode 100644 index 0000000..c1c6f33 --- /dev/null +++ b/Sanity/client-ipv4-tests/Makefile @@ -0,0 +1,36 @@ +# SPDX-License-Identifier: LGPL-2.1+ +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Makefile of /CoreOS/dhcp-client +# Description: Test if dhclient working ok +# Author: Susant Sahani +# +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +export TEST=/CoreOS/dhcpcd-client +export TESTVERSION=1.0 +BUILT_FILES= +FILES=$(METADATA) runtest.sh Makefile PURPOSE dhclient-tests.py test-dhclient.conf tsharkd.service test-dnsmasq.conf +.PHONY: all install download clean +run: $(FILES) build + ./runtest.sh +build: $(BUILT_FILES) + test -x runtest.sh || chmod a+x runtest.sh +clean: + rm -f *~ $(BUILT_FILES) +include /usr/share/rhts/lib/rhts-make.include +$(METADATA): Makefile + @echo "Owner: Susant Sahani" > $(METADATA) + @echo "Name: $(TEST)" >> $(METADATA) + @echo "TestVersion: $(TESTVERSION)" >> $(METADATA) + @echo "Path: $(TEST_DIR)" >> $(METADATA) + @echo "Description: Test if the ABI hasn't changed" >> $(METADATA) + @echo "Type: Sanity" >> $(METADATA) + @echo "TestTime: 5m" >> $(METADATA) + @echo "RunFor: dhcp-client" >> $(METADATA) + @echo "Requires: dhcp-client dnsmasq wireshark python3-pyroute2" >> $(METADATA) + @echo "Priority: Normal" >> $(METADATA) + @echo "License: GPLv2" >> $(METADATA) + @echo "Confidential: no" >> $(METADATA) + @echo "Destructive: no" >> $(METADATA) + @echo "Releases: -Fedora 28" >> $(METADATA) + rhts-lint $(METADATA) diff --git a/Sanity/client-ipv4-tests/PURPOSE b/Sanity/client-ipv4-tests/PURPOSE new file mode 100644 index 0000000..21de826 --- /dev/null +++ b/Sanity/client-ipv4-tests/PURPOSE @@ -0,0 +1,3 @@ +PURPOSE of /CoreOS/dhcpd-client +Description: tests for dh client +Author: Susant Sahani diff --git a/Sanity/client-ipv4-tests/dhclient-tests.py b/Sanity/client-ipv4-tests/dhclient-tests.py new file mode 100755 index 0000000..0eec085 --- /dev/null +++ b/Sanity/client-ipv4-tests/dhclient-tests.py @@ -0,0 +1,234 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: LGPL-2.1+ +# ~~~ +# Description: Tests for dhclient - a DHCP client +# +# Author: Susant Sahani +# Copyright (c) 2018 Red Hat, Inc. +# ~~~ + +import errno +import os +import sys +import time +import unittest +import subprocess +import signal +import shutil +import socket +from pyroute2 import IPRoute + +DHCLIENT_CI_DIR='/var/run/dhclient-ci' + +DHCLIENT_CONFIG_FILE='/var/run/dhclient-ci/test-dhclient.conf' +DHCLIENT_PID_FILE='/var/run/dhclient-ci/test-dhclient-test.pid' +DHCLIENT_LEASE_FILE='/var/run/dhclient-ci/test-dhclient.leases' +DHCLIENT_LOG_FILE='/var/run/dhclient-ci/test-dhclient-test-log' +DHCLIENT_TCP_DUMP_FILE='/var/run/dhclient-ci/test-dhclient-tcp-dump.pcap' + +DNSMASQ_CONFIG_FILE='/var/run/dhclient-ci/test-dnsmasq.conf' +DNSMASQ_PID_FILE='/var/run/dhclient-ci/test-test-dnsmasq.pid' +DNSMASQ_LOG_FILE='/var/run/dhclient-ci/test-dnsmasq-log-file' + +RESOLV_CONF='/etc/resolv.conf' + +def setUpModule(): + """Initialize the environment, and perform sanity checks on it.""" + + if shutil.which('dhclient') is None: + raise OSError(errno.ENOENT, 'dhclient not found') + + if shutil.which('dnsmasq') is None: + raise OSError(errno.ENOENT, 'dnsmasq not found') + +def tearDownModule(): + pass + +class GenericUtilities(): + """Provide a set of utility functions start stop daemons. write config files etc """ + + def StartDnsMasq(self): + """Start dnsmaq""" + subprocess.check_output(['dnsmasq', '-8', DNSMASQ_LOG_FILE, '--log-dhcp', '--pid-file=/var/run/dhclient-ci/test-test-dnsmasq.pid', + '--conf-file=/var/run/dhclient-ci/test-dnsmasq.conf', '-i', 'veth-peer', '-R']) + + def StartDhClient(self): + """Start dhclient """ + subprocess.check_output(['dhclient', '-4', '-v', '-nw', '-pf', DHCLIENT_PID_FILE, '-cf', DHCLIENT_CONFIG_FILE, '-lf', DHCLIENT_LEASE_FILE, 'veth-test']) + + def StopDaemon(self, pid_file): + with open(pid_file, 'r') as f: + pid = f.read().rstrip(' \t\r\n\0') + os.kill(int(pid), signal.SIGTERM) + + os.remove(pid_file) + + def SearchWordsInFile(self, log_file, **kwargs): + """dnsmasq server logs.""" + + if kwargs is not None: + with open (log_file, 'rt') as in_file: + contents = in_file.read() + for key in kwargs: + self.assertRegex(contents, kwargs[key]) + + def FindProtocolFieldsinTCPDump(self, **kwargs): + """Look attributes in tshark.""" + + contents = subprocess.check_output(['tshark', '-V', '-r', DHCLIENT_TCP_DUMP_FILE]).rstrip().decode('utf-8') + if kwargs is not None: + for key in kwargs: + self.assertRegex(contents, kwargs[key]) + + def SetupVethInterface(self): + """Setup veth interface""" + + ip = IPRoute() + + ip.link('add', ifname='veth-test', peer='veth-peer', kind='veth') + idx_veth_test = ip.link_lookup(ifname='veth-test')[0] + idx_veth_peer = ip.link_lookup(ifname='veth-peer')[0] + + ip.link('set', index=idx_veth_test, address='02:01:02:03:04:08') + ip.link('set', index=idx_veth_peer, address='02:01:02:03:04:09') + ip.link('set', index=idx_veth_test, state='up') + ip.link('set', index=idx_veth_peer, state='up') + ip.addr('add', index=idx_veth_peer, address='192.168.111.50') + + ip.close() + + def TearDownVethInterface(self): + + ip = IPRoute() + + ip.link('del', index=ip.link_lookup(ifname='veth-test')[0]) + ip.close() + + def StartCaptureBootpPackets(self): + """Start thark to capture dhcp packets""" + subprocess.check_output(['systemctl','start', 'tsharkd.service']) + + def StopCapturingPackets(self): + subprocess.check_output(['systemctl', 'stop', 'tsharkd.service']) + +class DhclientTests(unittest.TestCase, GenericUtilities): + + def setUp(self): + """ setup veth and write radvd and dhcpv6configs """ + self.SetupVethInterface() + + def tearDown(self): + self.StopDaemon(DHCLIENT_PID_FILE) + self.StopDaemon(DNSMASQ_PID_FILE) + + os.remove(DHCLIENT_LEASE_FILE) + + self.TearDownVethInterface() + + def test_dhclient_clientid_vendorclassid_request_parameters(self): + """ verify dhclient sends custom client-id, vendorclass and request parameters""" + + self.StartDnsMasq() + self.StartCaptureBootpPackets() + + time.sleep(3) + + self.StartDhClient() + + time.sleep(10) + self.StopCapturingPackets() + time.sleep(3) + + output=subprocess.check_output(['ip','address', 'show', 'veth-test']).rstrip().decode('utf-8') + + # Address prefix + self.assertRegex(output, "192.168.111.*") + + host='Host Name:.*%s' % socket.gethostname() + self.FindProtocolFieldsinTCPDump(vendor_class='Vendor class identifier: Zeus_dhclient_vendorclass_id', + client_mac_address='Client MAC address:.*\(02:01:02:03:04:08\)', + host_name=host, + client_identifier='\\\\021\\\\021\\\\022\\\\022\\\\023\\\\023\\\\024\\\\024\\\\025\\\\025\\\\026\\\\026', + req_param1='Parameter Request List Item: \(1\) Subnet Mask', + req_param28='Parameter Request List Item: \(28\) Broadcast Address', + req_param2='Parameter Request List Item: \(2\) Time Offset', + req_param3='Parameter Request List Item: \(3\) Router', + req_param15='Parameter Request List Item: \(15\) Domain Name', + req_param6='Parameter Request List Item: \(6\) Domain Name Server', + req_param119='Parameter Request List Item: \(119\) Domain Search', + req_param12='Parameter Request List Item: \(12\) Host Name', + req_param44='Parameter Request List Item: \(44\) NetBIOS over TCP/IP Name Server', + req_param47='Parameter Request List Item: \(47\) NetBIOS over TCP/IP Scope', + req_param26='Parameter Request List Item: \(26\) Interface MTU', + req_param121='Parameter Request List Item: \(121\) Classless Static Route', + req_param42='Parameter Request List Item: \(42\) Network Time Protocol Servers') + os.remove(DHCLIENT_TCP_DUMP_FILE) + + def test_dhclient_sets_dns_domain(self): + """ dhclient request DNS and domain name and sets to resolv.conf """ + + self.StartDnsMasq() + + time.sleep(1) + + self.StartDhClient() + + time.sleep(5) + + output=subprocess.check_output(['ip','address', 'show', 'veth-test']).rstrip().decode('utf-8') + + # Address prefix + self.assertRegex(output, "192.168.111.*") + + # Default route + output=subprocess.check_output(['ip','route', 'show', 'dev', 'veth-test']).rstrip().decode('utf-8') + self.assertRegex(output, "default via 192.168.1.1*") + + # search example-test.com\nnameserver 8.8.8.8\nnameserver 8.8.4.4\n' + # resolv.conf has configs + self.SearchWordsInFile(RESOLV_CONF, + domain_name='search example-test.com', + nnameserver1='nameserver 8.8.8.8', + nnameserver2='nameserver 8.8.4.4') + + def test_dhclient_sets_mtu(self): + """ dhclient gets MTU 1400 """ + + self.StartDnsMasq() + + time.sleep(1) + + self.StartDhClient() + + time.sleep(5) + + output=subprocess.check_output(['ip','address', 'show', 'veth-test']).rstrip().decode('utf-8') + + # Address prefix + self.assertRegex(output, "192.168.111.*") + # MTU + self.assertRegex(output, 'mtu 1400') + + def test_dhclient_ipv4(self): + """ dhclient gets address """ + + self.StartDnsMasq() + + time.sleep(1) + + self.StartDhClient() + + time.sleep(5) + output=subprocess.check_output(['ip','address', 'show', 'veth-test']).rstrip().decode('utf-8') + + # Address prefix + self.assertRegex(output, "192.168.111.*") + + # Default route + output=subprocess.check_output(['ip','route', 'show', 'dev', 'veth-test']).rstrip().decode('utf-8') + self.assertRegex(output, "default via 192.168.1.1*") + + +if __name__ == '__main__': + unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, + verbosity=3)) diff --git a/Sanity/client-ipv4-tests/main.fmf b/Sanity/client-ipv4-tests/main.fmf new file mode 100644 index 0000000..1468bd5 --- /dev/null +++ b/Sanity/client-ipv4-tests/main.fmf @@ -0,0 +1,16 @@ +summary: Test if the ABI hasn't changed +description: '' +contact: Susant Sahani +component: + - dhcp-client +test: ./runtest.sh +framework: beakerlib +recommend: + - dhcp-client + - dnsmasq + - wireshark + - python3-pyroute2 +duration: 15m +extra-summary: /CoreOS/dhcpcd-client +extra-task: /CoreOS/dhcpcd-client +tier: '1' diff --git a/Sanity/client-ipv4-tests/runtest.sh b/Sanity/client-ipv4-tests/runtest.sh new file mode 100755 index 0000000..ad522e3 --- /dev/null +++ b/Sanity/client-ipv4-tests/runtest.sh @@ -0,0 +1,55 @@ +#!/bin/bash +# SPDX-License-Identifier: LGPL-2.1+ +# ~~~ +# runtest.sh of dhclient +# Description: dhclient - Dynamic Host Configuration Protocol Client. +# +# Author: Susant Sahani +# Copyright (c) 2018 Red Hat, Inc. +# ~~~ + +# Include Beaker environment +. /usr/share/beakerlib/beakerlib.sh || exit 1 + +PACKAGE="dhcp-client" + +DHCLIENT_CI_DIR="/var/run/dhclient-ci" +RESOLVE_CONF="/etc/resolv.conf" + +rlJournalStart + rlPhaseStartSetup + rlAssertRpm $PACKAGE + + rlRun "systemctl stop firewalld" 0,5 + rlRun "setenforce 0" 0,1 + rlFileBackup "$RESOLVE_CONF" + + rlRun "[ -e /sys/class/net/veth-test ] && ip link del veth-test" 0,1 + + rlRun "cp dhclient-tests.py /usr/bin/" + rlRun "cp tsharkd.service /var/run/systemd/system" + + rlRun "mkdir -p $DHCLIENT_CI_DIR" + rlRun "cp *.conf $DHCLIENT_CI_DIR" + rlRun "systemctl daemon-reload" + rlPhaseEnd + + rlPhaseStartTest + rlLog "Starting dhclient tests ..." + rlRun "/usr/bin/python3 /usr/bin/dhclient-tests.py" + rlPhaseEnd + + rlPhaseStartCleanup + rlRun "rm /usr/bin/dhclient-tests.py /var/run/systemd/system/tsharkd.service" + rlrun "rm -rf $DHCLIENT_CI_DIR" + + rlRun "setenforce 1" 0,1 + rlRun "systemctl daemon-reload" + rlRun "[ -e /sys/class/net/veth-test ] && ip link del veth-test" 0,1 + rlFileRestore + rlLog "dhclient tests done" + rlPhaseEnd +rlJournalPrintText +rlJournalEnd + +rlGetTestState diff --git a/Sanity/client-ipv4-tests/test-dhclient.conf b/Sanity/client-ipv4-tests/test-dhclient.conf new file mode 100644 index 0000000..50eeaea --- /dev/null +++ b/Sanity/client-ipv4-tests/test-dhclient.conf @@ -0,0 +1,12 @@ +option rfc3442-classless-static-routes code 121 = array of unsigned integer 8; + +send host-name = gethostname(); +send dhcp-client-identifier 00:11:11:12:12:13:13:14:14:15:15:16:16; +send dhcp-lease-time 3600; +send vendor-class-identifier "Zeus_dhclient_vendorclass_id"; +request subnet-mask, broadcast-address, time-offset, routers, + domain-name, domain-name-servers, domain-search, host-name, + domain-search, host-name, + dhcp6.name-servers, dhcp6.domain-search, + netbios-name-servers, netbios-scope, interface-mtu, + rfc3442-classless-static-routes, ntp-servers; diff --git a/Sanity/client-ipv4-tests/test-dnsmasq.conf b/Sanity/client-ipv4-tests/test-dnsmasq.conf new file mode 100644 index 0000000..7a40185 --- /dev/null +++ b/Sanity/client-ipv4-tests/test-dnsmasq.conf @@ -0,0 +1,15 @@ +interface=veth-peer +bind-interfaces + +# Optionally set a domain name +domain=example-test.com + +# Set default gateway +dhcp-option=3,192.168.1.1 + +# Set DNS servers to announce +dhcp-option=6,8.8.8.8,8.8.4.4 +dhcp-option=26,1400 +# Dynamic range of IPs to make available to LAN PC and the lease time. +# Ideally set the lease time to 5m only at first to test everything works okay before you set long-lasting records. +dhcp-range=192.168.111.50,192.168.111.100,255.255.255.0,1h diff --git a/Sanity/client-ipv4-tests/tsharkd.service b/Sanity/client-ipv4-tests/tsharkd.service new file mode 100644 index 0000000..c604b21 --- /dev/null +++ b/Sanity/client-ipv4-tests/tsharkd.service @@ -0,0 +1,10 @@ +[Unit] +Description=Tshark daemon +After=multi-user.target network.target + +[Service] +Type=simple +GuessMainPID=true +ExecStart=/usr/bin/tshark -w /var/run/dhclient-ci/test-dhclient-tcp-dump.pcap -f "port 67 or port 68" -i veth-peer -P +[Install] +WantedBy=multi-user.target diff --git a/main.fmf b/main.fmf new file mode 100644 index 0000000..41e2fab --- /dev/null +++ b/main.fmf @@ -0,0 +1,2 @@ +# QE owner +contact: Petr Sklenar