[ovs-dev] [PATCH v4 4/4] ovn: Add tests for ovn dhcp
nusiddiq at redhat.com
nusiddiq at redhat.com
Tue Nov 24 11:24:36 UTC 2015
Signed-off-by: Numan Siddique <nusiddiq at 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
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);
--
2.5.0
More information about the dev
mailing list