[ovs-dev,v4,4/4] ovn: Add tests for ovn dhcp
diff mbox

Message ID 1448364276-18467-1-git-send-email-nusiddiq@redhat.com
State Deferred
Headers show

Commit Message

Numan Siddique Nov. 24, 2015, 11:24 a.m. UTC
Signed-off-by: Numan Siddique <nusiddiq@redhat.com>
---
 tests/automake.mk     |   1 +
 tests/ovn.at          | 184 ++++++++++++++++++++++++++++++++++++++++
 tests/test-ovn-dhcp.c | 228 ++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 413 insertions(+)
 create mode 100644 tests/test-ovn-dhcp.c

Patch
diff mbox

diff --git a/tests/automake.mk b/tests/automake.mk
index 5267be1..9c19b1e 100644
--- a/tests/automake.mk
+++ b/tests/automake.mk
@@ -305,6 +305,7 @@  tests_ovstest_SOURCES = \
 	tests/test-odp.c \
 	tests/test-ofpbuf.c \
 	tests/test-ovn.c \
+	tests/test-ovn-dhcp.c \
 	tests/test-packets.c \
 	tests/test-random.c \
 	tests/test-reconnect.c \
diff --git a/tests/ovn.at b/tests/ovn.at
index 68fcc9a..34b9913 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -1104,3 +1104,187 @@  for i in 1 2 3; do
     done
 done
 AT_CLEANUP
+
+AT_SETUP([ovn dhcp -- 3 HVs, 3 LS, 3 lports/LS, 1 LR])
+AT_SKIP_IF([test $HAVE_PYTHON = no])
+ovn_start
+
+# Logical network:
+#
+# Three logical switches ls1, ls2, ls3.
+# One logical router lr0 connected to ls[123],
+# with nine subnets, three per logical switch:
+#
+#    lrp11 on ls1 for subnet 192.168.11.0/24
+#    lrp12 on ls1 for subnet 192.168.12.0/24
+#    lrp13 on ls1 for subnet 192.168.13.0/24
+#    ...
+#    lrp33 on ls3 for subnet 192.168.33.0/24
+#
+# 27 VIFs, 9 per LS, 3 per subnet: lp[123][123][123], where the first two
+# digits are the subnet and the last digit distinguishes the VIF.
+for i in 1 2 3; do
+    ovn-nbctl lswitch-add ls$i
+    for j in 1 2 3; do
+        for k in 1 2 3; do
+        ovn-nbctl \
+        -- lport-add ls$i lp$i$j$k \
+        -- lport-set-addresses lp$i$j$k "f0:00:00:00:0$i:$j$k 192.168.$i$j.$k"
+        done
+    done
+done
+
+# Physical network:
+#
+# Three hypervisors hv[123].
+# lp?1[123] spread across hv[123]: lp?11 on hv1, lp?12 on hv2, lp?13 on hv3.
+# lp?2[123] spread across hv[23]: lp?21 and lp?22 on hv2, lp?23 on hv3.
+# lp?3[123] all on hv3.
+
+
+# Given the name of a logical port, prints the name of the hypervisor
+# on which it is located.
+vif_to_hv() {
+    case $1 in dnl (
+        ?11) echo 1 ;; dnl (
+        ?12 | ?21 | ?22) echo 2 ;; dnl (
+        ?13 | ?23 | ?3?) echo 3 ;;
+    esac
+}
+
+# Given the name of a logical port, prints the name of its logical router
+# port, e.g. "vif_to_lrp 123" yields 12.
+vif_to_lrp() {
+    echo ${1%?}
+}
+
+# Given the name of a logical port, prints the name of its logical
+# switch, e.g. "vif_to_ls 123" yields 1.
+vif_to_ls() {
+    echo ${1%??}
+}
+
+net_add n1
+for i in 1 2 3; do
+    sim_add hv$i
+    as hv$i
+    ovs-vsctl add-br br-phys
+    ovn_attach n1 br-phys 192.168.0.$i
+done
+for i in 1 2 3; do
+    for j in 1 2 3; do
+        for k in 1 2 3; do
+        hv=`vif_to_hv $i$j$k`
+        as hv$hv ovs-vsctl \
+        -- add-port br-int vif$i$j$k \
+        -- set Interface vif$i$j$k \
+               external-ids:iface-id=lp$i$j$k \
+               options:tx_pcap=hv$hv/vif$i$j$k-tx.pcap \
+               options:rxq_pcap=hv$hv/vif$i$j$k-rx.pcap \
+               ofport-request=$i$j$k
+        done
+    done
+done
+
+# Pre-populate the hypervisors' ARP tables so that we don't lose any
+# packets for ARP resolution (native tunneling doesn't queue packets
+# for ARP resolution).
+ovn_populate_arp
+
+# Allow some time for ovn-northd and ovn-controller to catch up.
+# XXX This should be more systematic.
+sleep 1
+
+send_dhcp_packet() {
+    local inport=$1 src_mac=$2 dhcp_type=$3
+    local request=ffffffffffff${src_mac}080045100110000000008011000000000000ffffffff
+    # udp header and dhcp header
+    request+=0044004300FC0000
+    request+=010106006359aa760000000000000000000000000000000000000000${src_mac}
+    # client hardware padding
+    request+=00000000000000000000
+    # server hostname
+    request+=0000000000000000000000000000000000000000000000000000000000000000
+    request+=0000000000000000000000000000000000000000000000000000000000000000
+    # boot file name
+    request+=0000000000000000000000000000000000000000000000000000000000000000
+    request+=0000000000000000000000000000000000000000000000000000000000000000
+    request+=0000000000000000000000000000000000000000000000000000000000000000
+    request+=0000000000000000000000000000000000000000000000000000000000000000
+    # dhcp magic cookie
+    request+=63825363
+    # dhcp message type
+    request+=3501${dhcp_type}ff
+    shift; shift; shift; shift; shift
+    hv=`vif_to_hv $inport`
+    as hv$hv ovs-appctl netdev-dummy/receive vif$inport $request
+}
+
+ip_to_hex() {
+    printf "%02x%02x%02x%02x" "$@"
+}
+
+run_dhcp_test() {
+    local i=$1 j=$2 k=$3 set_option=$4 dhcp_type=$5
+    netmask=255.255.255.0
+    gw_ip=0.0.0.0
+    if $set_option = 'true'; then
+        netmask=255.255.252.0
+        gw_ip=192.168.$i$j.254
+        ovn-nbctl \
+            -- lport-set-options lp$i$j$k \
+            "dhcp-opt-1=$netmask" "dhcp-opt-3=$gw_ip"
+        sleep 1
+    fi
+
+    echo $gw_ip
+    echo $netmask
+    hv=`vif_to_hv $i$j$k`
+    as hv$hv ovs-ofctl dump-flows br-int
+    inport=$i$j$k
+    send_dhcp_packet $inport f00000000$i$j$k $dhcp_type
+    sleep 2
+    if $set_option = 'true'; then
+        AT_CHECK([ovstest test-ovn-dhcp hv$hv/vif$i$j$k-tx.pcap \
+                  192.168.$i$j.$k $netmask $gw_ip $dhcp_type], [0], [ignore])
+    else
+        #dhcp options are not set. So the dhcp packet should be recieved
+        # by other ports attached to the br-int since its flooded.
+        # verify if the dhcp packet was flooded or not.
+        for a in 1 2 3; do
+            for b in 1 2 3; do
+                for c in 1 2 3; do
+                    if test $a$b$c = $inport; then
+                        continue
+                    fi
+                thv=`vif_to_hv $a$b$c`
+                if test $thv = $hv; then
+                    AT_CHECK([ovstest test-ovn-dhcp hv$hv/vif$a$b$c-tx.pcap],
+                              [0], [ignore])
+                fi
+                done
+            done
+        done
+
+    fi
+}
+
+#send DHCP DISCOVER
+run_dhcp_test 1 1 1 true 01
+
+#send DHCP OFFER
+run_dhcp_test 2 1 1 true 03
+
+# don not set the dhcp options
+# the dhcp packet should be flooded.
+run_dhcp_test 1 2 1 false 01
+
+# remove the ip address from the port
+# the dhcp packet should be flooded.
+ovn-nbctl \
+    -- lport-set-addresses lp231 "f0:00:00:00:02:31"
+sleep 1
+
+run_dhcp_test 2 3 1 false 01
+
+AT_CLEANUP
diff --git a/tests/test-ovn-dhcp.c b/tests/test-ovn-dhcp.c
new file mode 100644
index 0000000..8aae53b
--- /dev/null
+++ b/tests/test-ovn-dhcp.c
@@ -0,0 +1,228 @@ 
+/* Copyright (c) 2015 Red Hat, Inc.
+ *
+ * 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.
+ */
+
+#include <config.h>
+#include "command-line.h"
+#include "flow.h"
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include "classifier.h"
+#include "dhcp.h"
+#include "ofpbuf.h"
+#include "ofp-print.h"
+#include "ofp-util.h"
+#include "openflow/openflow.h"
+#include "ovstest.h"
+#include "dp-packet.h"
+#include "pcap-file.h"
+#include "timeval.h"
+#include "util.h"
+#include "openvswitch/vlog.h"
+#include "lib/packets.h"
+
+
+#define DHCP_CLIENT_PORT 68
+#define DHCP_SERVER_PORT 67
+
+#define DHCP_MAGIC_COOKIE (uint32_t)0x63825363
+#define DHCP_OPT_MSG_TYPE    ((uint8_t)53)
+
+/* Verify the dhcp option type */
+struct dhcp_option_header {
+    uint8_t option;
+    uint8_t len;
+};
+
+#define OPTION_PAYLOAD(opt) ((char *)opt + sizeof(struct dhcp_option_header))
+
+static void
+test_ovn_dhcp_main(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+    int retval = 1;
+    FILE *pcap;
+    bool verify_response = true;
+
+    if (argc == 2) {
+        verify_response = false;
+    }
+    else if (argc < 6) {
+        printf("Usage : test_ovn_dhcp_main pcap-file expected ip"
+               " expected-netmask expected-gw-ip dhcp-reply-type\n");
+        exit(1);
+    }
+
+
+    set_program_name(argv[0]);
+
+    pcap = fopen(argv[1], "rb");
+    if (!pcap) {
+        ovs_fatal(errno, "failed to open %s", argv[1]);
+    }
+
+    retval = ovs_pcap_read_header(pcap);
+    if (retval) {
+        ovs_fatal(retval > 0 ? retval : 0, "reading pcap header failed");
+    }
+
+    struct dp_packet *packet;
+    retval = ovs_pcap_read(pcap, &packet, NULL);
+    if (retval == EOF) {
+        ovs_fatal(0, "unexpected end of file reading pcap file : [%s]\n",
+                  argv[1]);
+    } else if (retval) {
+        ovs_fatal(retval, "error reading pcap file");
+    }
+
+    struct flow flow;
+    flow_extract(packet, &flow);
+
+    if (verify_response) {
+        if (flow.tp_src != htons(DHCP_SERVER_PORT) &&
+            flow.tp_dst != htons(DHCP_CLIENT_PORT)) {
+            printf("Error. Not a dhcp response packet \n");
+            retval = 1;
+            goto exit;
+        }
+    }
+    else {
+        if (flow.dl_type == htons(ETH_TYPE_IP) && \
+            flow.nw_proto == IPPROTO_UDP && \
+            flow.nw_src == INADDR_ANY && \
+            flow.nw_dst == INADDR_BROADCAST && \
+            flow.tp_src == htons(DHCP_CLIENT_PORT) && \
+            flow.tp_dst == htons(DHCP_SERVER_PORT)) {
+            retval = 0;
+            goto exit;
+        }
+        else {
+            printf("Error.. Not a dhcp discover/request packet \n");
+            retval = 1;
+            goto exit;
+        }
+    }
+    /* verify if the dst ip is as expected */
+    ovs_be32 expected_offer_ip;
+    if (!ovs_scan(argv[2], IP_SCAN_FMT, IP_SCAN_ARGS(&expected_offer_ip))) {
+        ovs_fatal(1, "invalid expected offer ip");
+    }
+
+    ovs_be32 expected_netmask;
+    if (!ovs_scan(argv[3], IP_SCAN_FMT, IP_SCAN_ARGS(&expected_netmask))) {
+        ovs_fatal(1, "invalid expected netmask");
+    }
+
+    ovs_be32 expected_gw_ip;
+    if (!ovs_scan(argv[4], IP_SCAN_FMT, IP_SCAN_ARGS(&expected_gw_ip))) {
+        ovs_fatal(1, "invalid expected gw ip");
+    }
+
+    if (flow.nw_dst != expected_offer_ip) {
+        printf("Error. Offered ip : "IP_FMT " : Expected ip : %s\n",
+        IP_ARGS(flow.nw_dst), argv[2]);
+        retval = 1;
+        goto exit;
+    }
+
+    /* verify the dhcp reply type */
+    struct dhcp_header const *dhcp_data = dp_packet_get_udp_payload(packet);
+    if (dhcp_data->op != (uint8_t)2) {
+        printf("Invalid dhcp op reply code : %d\n", dhcp_data->op);
+        retval = 1;
+        goto exit;
+    }
+
+    if(dhcp_data->yiaddr != expected_offer_ip) {
+        printf("Error. Offered yiaddr : "IP_FMT " : Expected ip : %s\n",
+        IP_ARGS(dhcp_data->yiaddr), argv[2]);
+        retval = 1;
+        goto exit;
+    }
+
+    /* Verify the dhcp option cookie */
+    char const *footer = (char *)dhcp_data + sizeof(*dhcp_data);
+    uint32_t cookie = *(uint32_t *)footer;
+    if (cookie != htonl(DHCP_MAGIC_COOKIE)) {
+        printf("Error. Invalid dhcp magic cookie\n");
+        retval = 1;
+        goto exit;
+    }
+
+    footer += sizeof(uint32_t);
+    struct dhcp_option_header const *opt;
+    uint8_t dhcp_msg_type = 0;
+    ovs_be32 netmask = 0;
+    ovs_be32 gw_ip = 0xffffffff;
+
+    size_t dhcp_data_size = dp_packet_l4_size(packet);
+    for (opt = (struct dhcp_option_header *)footer;
+         footer < (char *)dhcp_data + dhcp_data_size;
+         footer += (sizeof(*opt) + opt->len)) {
+        opt = (struct dhcp_option_header *)footer;
+        switch(opt->option) {
+        case 53: /* DHCP OPT MESSAGE TYPE */
+            dhcp_msg_type = *(uint8_t *)OPTION_PAYLOAD(opt);
+            break;
+        case 1: /* DHCP OPT NETMASK */
+            netmask = *(ovs_be32 *)OPTION_PAYLOAD(opt);
+            break;
+        case 3: /* DHCP OPT ROUTER */
+            gw_ip = *(ovs_be32 *)OPTION_PAYLOAD(opt);
+            break;
+        }
+    }
+
+    uint8_t expected_msg_type = (uint8_t)atoi(argv[5]);
+    if (expected_msg_type == 1) {
+        expected_msg_type = 2;
+    }
+    else {
+        expected_msg_type = 5;
+    }
+
+
+    if (dhcp_msg_type != expected_msg_type) {
+        printf("Error. dhcp message type = [%d] : "
+               "Expected dhcp message type = [%d]\n",
+        dhcp_msg_type, expected_msg_type);
+        retval = 1;
+        goto exit;
+    }
+
+    if (netmask != expected_netmask) {
+        printf("Error. Offered netmask : "IP_FMT " : Expected netmask : %s\n",
+               IP_ARGS(netmask), argv[3]);
+        retval = 1;
+        goto exit;
+    }
+    if (gw_ip != expected_gw_ip) {
+        printf("Error. Offered gateway ip : "IP_FMT " : Expected gateway ip : %s\n",
+               IP_ARGS(gw_ip), argv[4]);
+        retval = 1;
+        goto exit;
+    }
+
+    retval = 0;
+exit:
+    /* Flush the processed received packets of the pcap file */
+    fclose(pcap);
+    pcap = fopen(argv[1], "wb");
+    ovs_pcap_write_header(pcap);
+    fclose(pcap);
+    exit(retval);
+}
+
+OVSTEST_REGISTER("test-ovn-dhcp", test_ovn_dhcp_main);