[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