diff options
-rw-r--r-- | resources/libraries/python/Routing.py | 25 | ||||
-rw-r--r-- | resources/libraries/python/TrafficScriptExecutor.py | 2 | ||||
-rw-r--r-- | resources/libraries/robot/dhcp_client.robot | 75 | ||||
-rw-r--r-- | resources/templates/vat/add_route.vat | 2 | ||||
-rwxr-xr-x | resources/traffic_scripts/dhcp/check_dhcp_request.py | 218 | ||||
-rw-r--r-- | tests/suites/dhcp/dhcp_client.robot | 38 |
6 files changed, 345 insertions, 15 deletions
diff --git a/resources/libraries/python/Routing.py b/resources/libraries/python/Routing.py index 7bb41cbfad..2097734af1 100644 --- a/resources/libraries/python/Routing.py +++ b/resources/libraries/python/Routing.py @@ -21,7 +21,8 @@ class Routing(object): """Routing utilities.""" @staticmethod - def vpp_route_add(node, network, prefix_len, gateway, interface): + def vpp_route_add(node, network, prefix_len, gateway=None, interface=None, + use_sw_index=True, resolve_attempts=10): """Add route to the VPP node. :param node: Node to add route on. @@ -29,19 +30,35 @@ class Routing(object): :param prefix_len: Route destination network prefix length. :param gateway: Route gateway address. :param interface: Route interface. + :param use_sw_index: Use sw_if_index in VAT command. + :param resolve_attempts: Resolve attempts IP route add parameter. + If None, then is not used. :type node: dict :type network: str :type prefix_len: int :type gateway: str :type interface: str + :type use_sw_index: bool + :type resolve_attempts: int """ - sw_if_index = Topology.get_interface_sw_index(node, interface) + if use_sw_index: + int_cmd = ('sw_if_index {}'. + format(Topology.get_interface_sw_index(node, interface))) + else: + int_cmd = interface + + rap = 'resolve-attempts {}'.format(resolve_attempts) \ + if resolve_attempts else '' + + via = 'via {}'.format(gateway) if gateway else '' + with VatTerminal(node) as vat: vat.vat_terminal_exec_cmd_from_template('add_route.vat', network=network, prefix_length=prefix_len, - gateway=gateway, - sw_if_index=sw_if_index) + via=via, + interface=int_cmd, + resolve_attempts=rap) @staticmethod def add_fib_table(node, network, prefix_len, fib_id, place): diff --git a/resources/libraries/python/TrafficScriptExecutor.py b/resources/libraries/python/TrafficScriptExecutor.py index 108b2b9815..e7b851e733 100644 --- a/resources/libraries/python/TrafficScriptExecutor.py +++ b/resources/libraries/python/TrafficScriptExecutor.py @@ -67,6 +67,8 @@ class TrafficScriptExecutor(object): if ret_code != 0: if "RuntimeError: ICMP echo Rx timeout" in stderr: raise Exception("ICMP echo Rx timeout") + elif "RuntimeError: DHCP REQUEST Rx timeout" in stderr: + raise RuntimeError("DHCP REQUEST Rx timeout") else: raise Exception("Traffic script execution failed") diff --git a/resources/libraries/robot/dhcp_client.robot b/resources/libraries/robot/dhcp_client.robot index 115646d42e..99a772e273 100644 --- a/resources/libraries/robot/dhcp_client.robot +++ b/resources/libraries/robot/dhcp_client.robot @@ -20,18 +20,19 @@ *** Keywords *** | Check DHCP DISCOVER header -| | [Documentation] | Check if DHCP message contains all required fields. +| | [Documentation] | Check if DHCP DISCOVER message contains all required +| | ... | fields. | | ... | | ... | *Arguments:* | | ... | - tg_node - TG node. Type: dictionary -| | ... | - interface - TGs interface where listen for DHCP DISCOVER message. +| | ... | - interface - TG interface where listen for DHCP DISCOVER message. | | ... | Type: string -| | ... | - src_mac - DHCP clients MAC address. Type: string -| | ... | - hostname - DHCP clients hostname (Optional, Default="", if not -| | ... | specified, the hostneme is not configured). Type: string +| | ... | - src_mac - DHCP client MAC address. Type: string +| | ... | - hostname - DHCP client hostname (Optional, Default="", if not +| | ... | specified, the hostname is not checked). Type: string | | ... | | ... | *Return:* -| | ... | - No value returned +| | ... | - No value returned. | | ... | | ... | *Example:* | | ... @@ -41,9 +42,63 @@ | | ... | \| eth2 \| 08:00:27:66:b8:57 \| client-hostname \| | | ... | | [Arguments] | ${tg_node} | ${interface} | ${src_mac} | ${hostname}=${EMPTY} -| | ${args}= | Run Keyword If | "${hostname}" == "" | Catenate -| | | ... | --rx_if | ${interface} | --rx_src_mac | ${src_mac} -| | ... | ELSE | Catenate | --rx_if | ${interface} | --rx_src_mac -| | | ... | ${src_mac} | --hostname | ${hostname} +| | ${args}= | Catenate | --rx_if | ${interface} | --rx_src_mac | ${src_mac} +| | ${args}= | Run Keyword If | "${hostname}" == "" | Set Variable | ${args} +| | ... | ELSE | Catenate | ${args} | --hostname | ${hostname} | | Run Traffic Script On Node | dhcp/check_dhcp_discover.py | | ... | ${tg_node} | ${args} + + +| Check DHCP REQUEST after OFFER +| | [Documentation] | Check if DHCP REQUEST message contains all required +| | ... | fields. DHCP REQUEST should be send by a client after DHCP OFFER +| | ... | message sent by a server. +| | ... +| | ... | *Arguments:* +| | ... | - tg_node - TG node. Type: dictionary +| | ... | - tg_interface - TG interface where listen for DHCP DISCOVER, +| | ... | send DHCP OFFER and listen for DHCP REQUEST messages. Type: string +| | ... | - server_mac - DHCP server MAC address. Type: string +| | ... | - server_ip - DHCP server IP address. Type: string +| | ... | - client_mac - DHCP client MAC address. Type: string +| | ... | - client_ip - IP address that should be offered to client. +| | ... | Type: string +| | ... | - client_mask - IP netmask that should be offered to client. +| | ... | Type: string +| | ... | - hostname - DHCP client hostname (Optional, Default="", if not +| | ... | specified, the hostname is not checked). Type: string +| | ... | - offer_xid - Transaction ID (Optional, Default="", if not specified +| | ... | xid field in DHCP OFFER is same as in DHCP DISCOVER message). +| | ... | Type: integer +| | ... +| | ... | *Return:* +| | ... | - No value returned. +| | ... +| | ... | *Raises:* +| | ... | - DHCP REQUEST Rx timeout - if no DHCP REQUEST is received. +| | ... +| | ... | *Example:* +| | ... +| | ... | \| Check DHCP REQUEST after OFFER \| ${nodes['TG']} \ +| | ... | \| eth2 \| 08:00:27:66:b8:57 \| 192.168.23.1 \ +| | ... | \| 08:00:27:46:2b:4c \| 192.168.23.10 \| 255.255.255.0 \| +| | ... +| | ... | \| Run Keyword And Expect Error \| DHCP REQUEST Rx timeout \ +| | ... | \| Check DHCP REQUEST after OFFER \ +| | ... | \| ${nodes['TG']} \| eth2 \| 08:00:27:66:b8:57 \| 192.168.23.1 \ +| | ... | \| 08:00:27:46:2b:4c \| 192.168.23.10 \| 255.255.255.0 \ +| | ... | \| offer_xid=11113333 \| +| | ... +| | [Arguments] | ${tg_node} | ${tg_interface} | ${server_mac} | ${server_ip} +| | ... | ${client_mac} | ${client_ip} | ${client_mask} +| | ... | ${hostname}=${EMPTY} | ${offer_xid}=${EMPTY} +| | ${args}= | Catenate | --rx_if | ${tg_interface} | --server_mac +| | ... | ${server_mac} | --server_ip | ${server_ip} | --client_mac +| | ... | ${client_mac} | --client_ip | ${client_ip} | --client_mask +| | ... | ${client_mask} +| | ${args}= | Run Keyword If | "${hostname}" == "" | Set Variable | ${args} +| | ... | ELSE | Catenate | ${args} | --hostname | ${hostname} +| | ${args}= | Run Keyword If | "${offer_xid}" == "" | Set Variable | ${args} +| | ... | ELSE | Catenate | ${args} | --offer_xid | ${offer_xid} +| | Run Traffic Script On Node | dhcp/check_dhcp_request.py +| | ... | ${tg_node} | ${args} diff --git a/resources/templates/vat/add_route.vat b/resources/templates/vat/add_route.vat index 77b3cc9232..96e39ba87a 100644 --- a/resources/templates/vat/add_route.vat +++ b/resources/templates/vat/add_route.vat @@ -1 +1 @@ -ip_add_del_route {network}/{prefix_length} via {gateway} sw_if_index {sw_if_index} resolve-attempts 10 +ip_add_del_route {network}/{prefix_length} {via} {interface} {resolve_attempts} diff --git a/resources/traffic_scripts/dhcp/check_dhcp_request.py b/resources/traffic_scripts/dhcp/check_dhcp_request.py new file mode 100755 index 0000000000..522f2f507c --- /dev/null +++ b/resources/traffic_scripts/dhcp/check_dhcp_request.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python +# Copyright (c) 2016 Cisco and/or its affiliates. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Traffic script that sends an DHCP OFFER message and checks if the DHCP +REQUEST contains all required fields.""" + +import sys + +from scapy.layers.l2 import Ether +from scapy.layers.inet import IP, UDP, UDP_SERVICES +from scapy.layers.dhcp import BOOTP, DHCP + +from resources.libraries.python.PacketVerifier import RxQueue, TxQueue +from resources.libraries.python.TrafficScriptArg import TrafficScriptArg + + +def is_discover(pkt): + """If DHCP message type option is set to dhcp discover return True, + else return False. False is returned also if exception occurs.""" + dhcp_discover = 1 + try: + dhcp_options = pkt['BOOTP']['DHCP options'].options + message_type = filter(lambda x: x[0] == 'message-type', + dhcp_options) + message_type = message_type[0][1] + return message_type == dhcp_discover + except: + return False + + +def is_request(pkt): + """If DHCP message type option is DHCP REQUEST return True, + else return False. False is returned also if exception occurs.""" + dhcp_request = 3 + try: + dhcp_options = pkt['BOOTP']['DHCP options'].options + message_type = filter(lambda x: x[0] == 'message-type', + dhcp_options) + message_type = message_type[0][1] + return message_type == dhcp_request + except: + return False + + +def main(): + """Main function of the script file.""" + args = TrafficScriptArg(['client_mac', 'server_mac', 'server_ip', + 'client_ip', 'client_mask'], + ['hostname', 'offer_xid']) + + server_if = args.get_arg('rx_if') + server_mac = args.get_arg('server_mac') + server_ip = args.get_arg('server_ip') + + client_mac = args.get_arg('client_mac') + client_ip = args.get_arg('client_ip') + client_mask = args.get_arg('client_mask') + + hostname = args.get_arg('hostname') + offer_xid = args.get_arg('offer_xid') + + rx_src_ip = '0.0.0.0' + rx_dst_ip = '255.255.255.255' + + rxq = RxQueue(server_if) + txq = TxQueue(server_if) + sent_packets = [] + + for _ in range(10): + dhcp_discover = rxq.recv(10) + if is_discover(dhcp_discover): + break + else: + raise RuntimeError("DHCP DISCOVER Rx error.") + + dhcp_offer = Ether(src=server_mac, dst=dhcp_discover.src) + dhcp_offer /= IP(src=server_ip, dst="255.255.255.255") + dhcp_offer /= UDP(sport=67, dport=68) + dhcp_offer /= BOOTP(op=2, + # if offer_xid differs from xid value in DHCP DISCOVER + # the DHCP OFFER has to be discarded + xid=int(offer_xid) if offer_xid + else dhcp_discover['BOOTP'].xid, + yiaddr=client_ip, + siaddr=server_ip, + chaddr=dhcp_discover['BOOTP'].chaddr) + dhcp_offer_options = [("message-type", "offer"), # Option 53 + ("subnet_mask", client_mask), # Option 1 + ("server_id", server_ip), # Option 54, dhcp server + ("lease_time", 43200), # Option 51 + "end"] + dhcp_offer /= DHCP(options=dhcp_offer_options) + + txq.send(dhcp_offer) + sent_packets.append(dhcp_offer) + + max_other_pkts = 10 + for _ in range(0, max_other_pkts): + dhcp_request = rxq.recv(5, sent_packets) + if not dhcp_request: + raise RuntimeError("DHCP REQUEST Rx timeout.") + if is_request(dhcp_request): + break + else: + raise RuntimeError("Max RX packet limit reached.") + + if offer_xid: + # if offer_xid differs from xid value in DHCP DISCOVER the DHCP OFFER + # has to be discarded + raise RuntimeError("DHCP REQUEST received. DHCP OFFER with wrong XID " + "has not been discarded.") + + # CHECK ETHER, IP, UDP + if dhcp_request.dst != dhcp_discover.dst: + raise RuntimeError("Destination MAC error.") + print "Destination MAC: OK." + + if dhcp_request.src != dhcp_discover.src: + raise RuntimeError("Source MAC error.") + print "Source MAC: OK." + + if dhcp_request['IP'].dst != rx_dst_ip: + raise RuntimeError("Destination IP error.") + print "Destination IP: OK." + + if dhcp_request['IP'].src != rx_src_ip: + raise RuntimeError("Source IP error.") + print "Source IP: OK." + + if dhcp_request['IP']['UDP'].dport != UDP_SERVICES.bootps: + raise RuntimeError("BOOTPs error.") + print "BOOTPs: OK." + + if dhcp_request['IP']['UDP'].sport != UDP_SERVICES.bootpc: + raise RuntimeError("BOOTPc error.") + print "BOOTPc: OK." + + # CHECK BOOTP + if dhcp_request['BOOTP'].op != dhcp_discover['BOOTP'].op: + raise RuntimeError("BOOTP operation error.") + print "BOOTP operation: OK" + + if dhcp_request['BOOTP'].xid != dhcp_discover['BOOTP'].xid: + raise RuntimeError("BOOTP XID error.") + print "BOOTP XID: OK" + + if dhcp_request['BOOTP'].ciaddr != '0.0.0.0': + raise RuntimeError("BOOTP ciaddr error.") + print "BOOTP ciaddr: OK" + + ca = dhcp_request['BOOTP'].chaddr[:dhcp_request['BOOTP'].hlen].encode('hex') + if ca != client_mac.replace(':', ''): + raise RuntimeError("BOOTP client hardware address error.") + print "BOOTP client hardware address: OK" + + if dhcp_request['BOOTP'].options != dhcp_discover['BOOTP'].options: + raise RuntimeError("DHCP options error.") + print "DHCP options: OK" + + # CHECK DHCP OPTIONS + dhcp_options = dhcp_request['DHCP options'].options + + hn = filter(lambda x: x[0] == 'hostname', dhcp_options) + if hostname: + try: + if hn[0][1] != hostname: + raise RuntimeError("Client's hostname doesn't match.") + except IndexError: + raise RuntimeError("Option list doesn't contain hostname option.") + else: + if len(hn) != 0: + raise RuntimeError("Option list contains hostname option.") + print "Option 12 hostname: OK" + + # Option 50 + ra = filter(lambda x: x[0] == 'requested_addr', dhcp_options)[0][1] + if ra != client_ip: + raise RuntimeError("Option 50 requested_addr error.") + print "Option 50 requested_addr: OK" + + # Option 53 + mt = filter(lambda x: x[0] == 'message-type', dhcp_options)[0][1] + if mt != 3: # request + raise RuntimeError("Option 53 message-type error.") + print "Option 53 message-type: OK" + + # Option 54 + sid = filter(lambda x: x[0] == 'server_id', dhcp_options)[0][1] + if sid != server_ip: + raise RuntimeError("Option 54 server_id error.") + print "Option 54 server_id: OK" + + # Option 55 + prl = filter(lambda x: x[0] == 'param_req_list', dhcp_options)[0][1] + if prl != '\x01\x1c\x02\x03\x0f\x06w\x0c,/\x1ay*': + raise RuntimeError("Option 55 param_req_list error.") + print "Option 55 param_req_list: OK" + + # Option 255 + if 'end' not in dhcp_options: + raise RuntimeError("end option error.") + print "end option: OK" + + sys.exit(0) + +if __name__ == "__main__": + main() diff --git a/tests/suites/dhcp/dhcp_client.robot b/tests/suites/dhcp/dhcp_client.robot index 693be26b2a..44a6d54c2a 100644 --- a/tests/suites/dhcp/dhcp_client.robot +++ b/tests/suites/dhcp/dhcp_client.robot @@ -15,14 +15,20 @@ | Resource | resources/libraries/robot/default.robot | Resource | resources/libraries/robot/testing_path.robot | Resource | resources/libraries/robot/dhcp_client.robot +| Resource | resources/libraries/robot/ipv4.robot | Library | resources.libraries.python.Trace | Force Tags | HW_ENV | VM_ENV | 3_NODE_DOUBLE_LINK_TOPO | Test Setup | Run Keywords | Setup all DUTs before test | ... | AND | Setup all TGs before traffic script +| Test Teardown | Show Packet Trace on All DUTs | ${nodes} | Documentation | *DHCP Client related test cases* *** Variables *** | ${client_hostname}= | dhcp-client +| ${client_ip}= | 192.168.23.10 +| ${client_mask}= | 255.255.255.0 +| ${server_ip}= | 192.168.23.1 +| ${own_xid}= | 11112222 *** Test Cases *** | VPP sends a DHCP DISCOVER @@ -49,3 +55,35 @@ | | ... | ${client_hostname} | | Then Check DHCP DISCOVER header | ${tg_node} | | ... | ${tg_to_dut_if1} | ${dut_to_tg_if1_mac} | ${client_hostname} + +| VPP sends DHCP REQUEST after OFFER +| | [Documentation] | Configure DHCP client on interface to TG and check if +| | ... | DHCP REQUEST message contains all required fields. +| | ... +| | Given Path for 2-node testing is set +| | ... | ${nodes['TG']} | ${nodes['DUT1']} | ${nodes['TG']} +| | And Interfaces in 2-node path are up +| | And VPP Route Add | ${dut_node} | 255.255.255.255 | 32 | ${NONE} | local +| | ... | ${FALSE} | ${NONE} +| | When Set DHCP client on Interface | ${dut_node} | ${dut_to_tg_if1} +| | Then Check DHCP REQUEST after OFFER | ${tg_node} | ${tg_to_dut_if1} +| | ... | ${tg_to_dut_if1_mac} | ${server_ip} +| | ... | ${dut_to_tg_if1_mac} | ${client_ip} | ${client_mask} + +| VPP doesn't send DHCP REQUEST after OFFER with wrong XID +| | [ Tags ] | EXPECTED_FAILING +| | [Documentation] | Configure DHCP client on interface to TG. If server sends +| | ... | DHCP OFFER with different XID as in DHCP DISCOVER, +| | ... | DHCP REQUEST message shouldn't be sent. +| | ... +| | Given Path for 2-node testing is set +| | ... | ${nodes['TG']} | ${nodes['DUT1']} | ${nodes['TG']} +| | And Interfaces in 2-node path are up +| | And VPP Route Add | ${dut_node} | 255.255.255.255 | 32 | ${NONE} | local +| | ... | ${FALSE} | ${NONE} +| | When Set DHCP client on Interface | ${dut_node} | ${dut_to_tg_if1} +| | Then Run Keyword And Expect Error | DHCP REQUEST Rx timeout +| | ... | Check DHCP REQUEST after OFFER | ${tg_node} | ${tg_to_dut_if1} +| | ... | ${tg_to_dut_if1_mac} | ${server_ip} +| | ... | ${dut_to_tg_if1_mac} | ${client_ip} | ${client_mask} +| | ... | offer_xid=${own_xid} |