[ovs-dev] [tests 11/22] netdev-dummy: Add a packet generator.

Ben Pfaff blp at nicira.com
Fri Oct 26 00:02:07 UTC 2012


This is useful for unit tests.

Signed-off-by: Ben Pfaff <blp at nicira.com>
---
 lib/netdev-dummy.c |  328 +++++++++++++++++++++++++++++++++++++++++++++++++---
 lib/packets.h      |    3 +
 2 files changed, 317 insertions(+), 14 deletions(-)

diff --git a/lib/netdev-dummy.c b/lib/netdev-dummy.c
index 5636854..9776edc 100644
--- a/lib/netdev-dummy.c
+++ b/lib/netdev-dummy.c
@@ -19,7 +19,11 @@
 #include "dummy.h"
 
 #include <errno.h>
+#include <netinet/in.h>
+#include <netinet/icmp6.h>
+#include <netinet/ip6.h>
 
+#include "csum.h"
 #include "flow.h"
 #include "list.h"
 #include "netdev-provider.h"
@@ -44,9 +48,22 @@ struct netdev_dev_dummy {
     unsigned int change_seq;
     char *peer;
 
+    struct pktgen **pktgens;
+    size_t n_pktgens;
+
     struct list devs;           /* List of child "netdev_dummy"s. */
 };
 
+static struct pktgen *pktgen_create(unsigned int kbps, unsigned int pktsize,
+                                    unsigned int duration_ms, uint16_t proto,
+                                    const uint8_t dst[ETH_ADDR_LEN],
+                                    const uint8_t src[ETH_ADDR_LEN],
+                                    uint16_t id);
+static void pktgen_destroy(struct pktgen *);
+static struct ofpbuf *pktgen_recv(struct pktgen *);
+static void pktgen_wait(struct pktgen *);
+static bool pktgen_is_done(struct pktgen *);
+
 /* Max 'recv_queue_len' in struct netdev_dummy. */
 #define NETDEV_DUMMY_MAX_QUEUE 100
 
@@ -68,8 +85,8 @@ static int netdev_dev_dummy_update_flags(struct netdev_dev_dummy *,
                                          enum netdev_flags off,
                                          enum netdev_flags on,
                                          enum netdev_flags *old_flagsp);
-static int netdev_dummy_queue_packet(struct netdev_dev_dummy *,
-                                     const void *data, size_t size);
+static void netdev_dummy_queue_packet(struct netdev_dev_dummy *,
+                                      struct ofpbuf *);
 
 static bool
 is_dummy_class(const struct netdev_class *class)
@@ -92,6 +109,49 @@ netdev_dummy_cast(const struct netdev *netdev)
     return CONTAINER_OF(netdev, struct netdev_dummy, netdev);
 }
 
+static void
+netdev_dummy_run(void)
+{
+    struct shash_node *node;
+
+    SHASH_FOR_EACH (node, &dummy_netdev_devs) {
+        struct netdev_dev_dummy *dev = node->data;
+        size_t i;
+
+        for (i = 0; i < dev->n_pktgens; i++) {
+            struct pktgen *pg = dev->pktgens[i];
+
+            for (;;) {
+                struct ofpbuf *p = pktgen_recv(pg);
+                if (!p) {
+                    break;
+                }
+                netdev_dummy_queue_packet(dev, p);
+            }
+
+            if (pktgen_is_done(pg)) {
+                pktgen_destroy(pg);
+                dev->pktgens[i--] = dev->pktgens[--dev->n_pktgens];
+            }
+        }
+    }
+}
+
+static void
+netdev_dummy_wait(void)
+{
+    struct shash_node *node;
+
+    SHASH_FOR_EACH (node, &dummy_netdev_devs) {
+        struct netdev_dev_dummy *dev = node->data;
+        size_t i;
+
+        for (i = 0; i < dev->n_pktgens; i++) {
+            pktgen_wait(dev->pktgens[i]);
+        }
+    }
+}
+
 static int
 netdev_dummy_create(const struct netdev_class *class, const char *name,
                     struct netdev_dev **netdev_devp)
@@ -125,9 +185,14 @@ static void
 netdev_dummy_destroy(struct netdev_dev *netdev_dev_)
 {
     struct netdev_dev_dummy *netdev_dev = netdev_dev_dummy_cast(netdev_dev_);
+    size_t i;
 
     shash_find_and_delete(&dummy_netdev_devs,
                           netdev_dev_get_name(netdev_dev_));
+    for (i = 0; i < netdev_dev->n_pktgens; i++) {
+        pktgen_destroy(netdev_dev->pktgens[i]);
+    }
+    free(netdev_dev->pktgens);
     free(netdev_dev->peer);
     free(netdev_dev);
 }
@@ -267,7 +332,7 @@ netdev_dummy_send(struct netdev *netdev, const void *buffer, size_t size)
 
         peer = shash_find_data(&dummy_netdev_devs, dev->peer);
         if (peer) {
-            netdev_dummy_queue_packet(peer, buffer, size);
+            netdev_dummy_queue_packet(peer, ofpbuf_clone_data(buffer, size));
         } else {
             static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);
 
@@ -394,8 +459,8 @@ netdev_dev_dummy_poll_notify(struct netdev_dev_dummy *dev)
 static const struct netdev_class dummy_class = {
     "dummy",
     NULL,                       /* init */
-    NULL,                       /* run */
-    NULL,                       /* wait */
+    netdev_dummy_run,           /* run */
+    netdev_dummy_wait,          /* wait */
 
     netdev_dummy_create,
     netdev_dummy_destroy,
@@ -493,18 +558,32 @@ eth_from_packet_or_flow(const char *s)
 }
 
 static void
+netdev_dummy_queue_packet__(struct netdev_dummy *dev, struct ofpbuf *packet)
+{
+    list_push_back(&dev->recv_queue, &packet->list_node);
+    dev->recv_queue_len++;
+}
+
+static void
 netdev_dummy_queue_packet(struct netdev_dev_dummy *dummy_dev,
-                          const void *data, size_t size)
+                          struct ofpbuf *packet)
 {
-    struct netdev_dummy *dev;
+    struct netdev_dummy *dev, *prev;
 
+    prev = NULL;
     LIST_FOR_EACH (dev, node, &dummy_dev->devs) {
         if (dev->listening && dev->recv_queue_len < NETDEV_DUMMY_MAX_QUEUE) {
-            struct ofpbuf *packet = ofpbuf_clone_data(data, size);
-            list_push_back(&dev->recv_queue, &packet->list_node);
-            dev->recv_queue_len++;
+            if (prev) {
+                netdev_dummy_queue_packet__(prev, ofpbuf_clone(packet));
+            }
+            prev = dev;
         }
     }
+    if (prev) {
+        netdev_dummy_queue_packet__(prev, packet);
+    } else {
+        ofpbuf_delete(packet);
+    }
 }
 
 static void
@@ -512,7 +591,6 @@ netdev_dummy_receive(struct unixctl_conn *conn,
                      int argc, const char *argv[], void *aux OVS_UNUSED)
 {
     struct netdev_dev_dummy *dummy_dev;
-    int n_listeners;
     int i;
 
     dummy_dev = shash_find_data(&dummy_netdev_devs, argv[1]);
@@ -521,7 +599,6 @@ netdev_dummy_receive(struct unixctl_conn *conn,
         return;
     }
 
-    n_listeners = 0;
     for (i = 2; i < argc; i++) {
         struct ofpbuf *packet;
 
@@ -531,10 +608,90 @@ netdev_dummy_receive(struct unixctl_conn *conn,
             return;
         }
 
-        netdev_dummy_queue_packet(dummy_dev, packet->data, packet->size);
-        ofpbuf_delete(packet);
+        netdev_dummy_queue_packet(dummy_dev, packet);
+    }
+
+    unixctl_command_reply(conn, NULL);
+}
+
+static void
+netdev_dummy_pktgen(struct unixctl_conn *conn,
+                    int argc, const char *argv[], void *aux OVS_UNUSED)
+{
+    struct netdev_dev_dummy *dummy_dev;
+
+    unsigned int duration_ms;
+    unsigned int pktsize;
+    unsigned int kbps;
+    uint8_t dst[ETH_ADDR_LEN];
+    uint8_t src[ETH_ADDR_LEN];
+    int min_pktsize;
+    uint16_t proto;
+    uint16_t id;
+
+    dummy_dev = shash_find_data(&dummy_netdev_devs, argv[1]);
+    if (!dummy_dev) {
+        unixctl_command_reply_error(conn, "no such dummy netdev");
+        return;
+    }
+
+    kbps = atoi(argv[2]);
+    if (kbps <= 0) {
+        unixctl_command_reply_error(conn, "bad kbps");
+        return;
+    }
+
+    pktsize = atoi(argv[3]);
+
+    duration_ms = atoi(argv[4]);
+    if (duration_ms <= 0) {
+        unixctl_command_reply_error(conn, "bad duration");
+        return;
+    }
+
+    if (!strcmp(argv[5], "icmp")) {
+        proto = ETH_TYPE_IP;
+        min_pktsize = ETH_HEADER_LEN + IP_HEADER_LEN + ICMP_HEADER_LEN;
+    } else if (!strcmp(argv[5], "icmpv6")) {
+        proto = ETH_TYPE_IPV6;
+        min_pktsize = (ETH_HEADER_LEN + sizeof(struct ip6_hdr)
+                       + sizeof(struct icmp6_hdr));
+    } else {
+        unixctl_command_reply_error(conn, "bad protocol");
+        return;
+    }
+    if (pktsize <= min_pktsize) {
+        unixctl_command_reply_error(conn, "bad pktsize");
+        return;
+    }
+
+    id = atoi(argv[6]);
+
+    if (argc > 8) {
+        if (!eth_addr_from_string(argv[8], src)) {
+            unixctl_command_reply_error(conn, "bad src");
+            return;
+        }
+    } else {
+        memcpy(src, dummy_dev->hwaddr, ETH_ADDR_LEN);
+    }
+
+    if (argc > 7) {
+        if (!eth_addr_from_string(argv[7], dst)) {
+            unixctl_command_reply_error(conn, "bad dst");
+            return;
+        }
+    } else {
+        eth_addr_from_uint64(~eth_addr_to_uint64(src), dst);
+        eth_addr_mark_random(dst);
     }
 
+    dummy_dev->pktgens = xrealloc(dummy_dev->pktgens,
+                                  (sizeof *dummy_dev->pktgens *
+                                   (dummy_dev->n_pktgens + 1)));
+    dummy_dev->pktgens[dummy_dev->n_pktgens++] =
+        pktgen_create(kbps, pktsize, duration_ms, proto, dst, src, id);
+
     unixctl_command_reply(conn, NULL);
 }
 
@@ -591,6 +748,10 @@ netdev_dummy_register(bool override)
 {
     unixctl_command_register("netdev-dummy/receive", "NAME PACKET|FLOW...",
                              2, INT_MAX, netdev_dummy_receive, NULL);
+    unixctl_command_register("netdev-dummy/pktgen",
+                             "NAME KBPS PKTSIZE DURATION_MS icmp|icmpv6 "
+                             "ICMP_ID [DST_MAC [SRC_MAC]]",
+                             6, 8, netdev_dummy_pktgen, NULL);
     unixctl_command_register("netdev-dummy/set-admin-state",
                              "[netdev] up|down", 1, 2,
                              netdev_dummy_set_admin_state, NULL);
@@ -615,3 +776,142 @@ netdev_dummy_register(bool override)
     }
     netdev_register_provider(&dummy_class);
 }
+
+/* A simple packet generator. */
+
+struct pktgen {
+    long long int start;
+    long long int end;
+    unsigned int kbps;
+    unsigned int pktsize;
+    uint8_t src[ETH_ADDR_LEN];
+    uint8_t dst[ETH_ADDR_LEN];
+    uint16_t proto;
+    uint16_t id;
+
+    unsigned long long int generated_bytes;
+    unsigned long long int target_bytes;
+    int spacing_ms;
+    uint16_t seq;
+};
+
+static struct pktgen *
+pktgen_create(unsigned int kbps, unsigned int pktsize,
+              unsigned int duration_ms, uint16_t proto,
+              const uint8_t dst[ETH_ADDR_LEN],const uint8_t src[ETH_ADDR_LEN],
+              uint16_t id)
+{
+    struct pktgen *pg;
+
+    pg = xmalloc(sizeof *pg);
+
+    pg->start = time_msec();
+    pg->end = pg->start + duration_ms;
+    pg->kbps = kbps;
+    pg->pktsize = pktsize;
+    memcpy(pg->src, src, ETH_ADDR_LEN);
+    memcpy(pg->dst, dst, ETH_ADDR_LEN);
+    pg->proto = proto;
+    pg->id = id;
+
+    pg->generated_bytes = 0;
+    pg->target_bytes = (unsigned long long int) kbps * duration_ms;
+    pg->spacing_ms = MAX(1, pktsize / kbps);
+    pg->seq = 1;
+
+    return pg;
+}
+
+static void
+pktgen_destroy(struct pktgen *pg)
+{
+    free(pg);
+}
+
+static bool
+pktgen_is_due(const struct pktgen *pg)
+{
+    long long int now = time_msec();
+    return (now < pg->end
+            ? (now - pg->start) * pg->kbps >= pg->generated_bytes + pg->pktsize
+            : pg->target_bytes > pg->generated_bytes);
+}
+
+static struct ofpbuf *
+pktgen_recv(struct pktgen *pg)
+{
+    struct icmp_header *icmp;
+    struct eth_header *eth;
+    struct ofpbuf *p;
+
+    unsigned int icmp_len;
+    uint8_t icmp_type;
+
+    if (!pktgen_is_due(pg)) {
+        return NULL;
+    }
+    p = ofpbuf_new(pg->pktsize);
+
+    eth = ofpbuf_put_zeros(p, sizeof *eth);
+    memcpy(eth->eth_dst, pg->dst, ETH_ADDR_LEN);
+    memcpy(eth->eth_src, pg->src, ETH_ADDR_LEN);
+    eth->eth_type = htons(pg->proto);
+
+    if (pg->proto == ETH_TYPE_IP) {
+        unsigned int ip_len = pg->pktsize - p->size;
+        struct ip_header *ip;
+
+        ip = ofpbuf_put_zeros(p, sizeof *ip);
+        ip->ip_ihl_ver = IP_IHL_VER(5, 4);
+        ip->ip_tot_len = htons(ip_len);
+        ip->ip_id = htons(pg->seq);
+        ip->ip_ttl = 64;
+        ip->ip_proto = IPPROTO_ICMP;
+        ip->ip_src = htonl(eth_addr_to_uint64(pg->src));
+        ip->ip_dst = htonl(eth_addr_to_uint64(pg->dst));
+        ip->ip_csum = csum(ip, sizeof *ip);
+
+        icmp_type = ICMP_TYPE_ECHO_REQUEST;
+    } else if (pg->proto == ETH_TYPE_IPV6) {
+        struct ip6_hdr *ip6;
+
+        ip6 = ofpbuf_put_zeros(p, sizeof *ip6);
+        ip6->ip6_vfc = 6 << 4;
+        ip6->ip6_plen = htons(pg->pktsize - p->size);
+        ip6->ip6_nxt = IPPROTO_ICMPV6;
+        ip6->ip6_hlim = 64;
+
+        icmp_type = ICMP6_ECHO_REQUEST;
+    } else {
+        NOT_REACHED();
+    }
+
+    icmp_len = pg->pktsize - p->size;
+    icmp = ofpbuf_put_zeros(p, icmp_len);
+    icmp->icmp_type = icmp_type;
+    icmp->icmp_code = 0;
+    icmp->icmp_fields.echo.id = htons(pg->id);
+    icmp->icmp_fields.echo.seq = htons(pg->seq);
+    icmp->icmp_csum = csum(icmp, icmp_len);
+
+    pg->generated_bytes += pg->pktsize;
+    pg->seq++;
+
+    return p;
+}
+
+static void
+pktgen_wait(struct pktgen *pg)
+{
+    if (pktgen_is_due(pg)) {
+        poll_immediate_wake();
+    } else {
+        poll_timer_wait(pg->spacing_ms);
+    }
+}
+
+static bool
+pktgen_is_done(struct pktgen *pg)
+{
+    return pg->generated_bytes >= pg->target_bytes;
+}
diff --git a/lib/packets.h b/lib/packets.h
index 24b51da..2b580db 100644
--- a/lib/packets.h
+++ b/lib/packets.h
@@ -355,6 +355,9 @@ struct ip_header {
 };
 BUILD_ASSERT_DECL(IP_HEADER_LEN == sizeof(struct ip_header));
 
+#define ICMP_TYPE_ECHO_REPLY   0
+#define ICMP_TYPE_ECHO_REQUEST 8
+
 #define ICMP_HEADER_LEN 8
 struct icmp_header {
     uint8_t icmp_type;
-- 
1.7.2.5




More information about the dev mailing list