This is useful for unit tests. Signed-off-by: Ben Pfaff <b...@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 _______________________________________________ dev mailing list dev@openvswitch.org http://openvswitch.org/mailman/listinfo/dev