From f6cae73ec398e34f7634b40bbacc598be8511b5f Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Fri, 4 Apr 2008 16:17:46 -0700 Subject: [PATCH] Implement OpenFlow statistics in switches and in dpctl. This has two notable omissions. First, only at most 4k of flow statistics are reported. Second, aggregate statistics are not yet supported. Both of these are fairly easily fixable, just not fixed yet. --- datapath/datapath.c | 505 +++++++++++-------------------------- datapath/datapath.h | 3 + datapath/forward.c | 39 +++ include/dpif.h | 3 - include/ofp-print.h | 3 - include/openflow-netlink.h | 23 -- include/openflow.h | 55 +++- include/vconn.h | 1 + lib/dpif.c | 133 ---------- lib/ofp-print.c | 147 +++++++++-- lib/vconn.c | 14 + switch/chain.c | 7 - switch/chain.h | 7 + switch/datapath.c | 169 ++++++++++++- switch/switch.c | 1 + utilities/dpctl.c | 218 +++++++++++----- 16 files changed, 712 insertions(+), 616 deletions(-) diff --git a/datapath/datapath.c b/datapath/datapath.c index ab3f8666..0a1cb5ca 100644 --- a/datapath/datapath.c +++ b/datapath/datapath.c @@ -748,6 +748,155 @@ dp_send_flow_expired(struct datapath *dp, struct sw_flow *flow) return send_openflow_skb(skb, NULL); } +static void +fill_flow_stats(struct ofp_flow_stats *ofs, struct sw_flow *flow, + int table_idx) +{ + int duration; + + ofs->match.wildcards = htons(flow->key.wildcards); + ofs->match.in_port = flow->key.in_port; + memcpy(ofs->match.dl_src, flow->key.dl_src, ETH_ALEN); + memcpy(ofs->match.dl_dst, flow->key.dl_dst, ETH_ALEN); + ofs->match.dl_vlan = flow->key.dl_vlan; + ofs->match.dl_type = flow->key.dl_type; + ofs->match.nw_src = flow->key.nw_src; + ofs->match.nw_dst = flow->key.nw_dst; + ofs->match.nw_proto = flow->key.nw_proto; + memset(ofs->match.pad, 0, sizeof ofs->match.pad); + ofs->match.tp_src = flow->key.tp_src; + ofs->match.tp_dst = flow->key.tp_dst; + duration = (jiffies - flow->init_time) / HZ; + ofs->duration = htons(min(65535, duration)); + ofs->table_id = htons(table_idx); + ofs->packet_count = cpu_to_be64(flow->packet_count); + ofs->byte_count = cpu_to_be64(flow->byte_count); +} + +int +dp_send_flow_stats(struct datapath *dp, const struct sender *sender, + const struct ofp_match *match) +{ + struct sk_buff *skb; + struct ofp_flow_stat_reply *fsr; + size_t header_size, fudge, flow_size; + struct sw_flow_key match_key; + int table_idx, n_flows, max_flows; + + header_size = offsetof(struct ofp_flow_stat_reply, flows); + fudge = 128; + flow_size = sizeof fsr->flows[0]; + max_flows = (NLMSG_GOODSIZE - header_size - fudge) / flow_size; + fsr = alloc_openflow_skb(dp, header_size + max_flows * flow_size, + OFPT_FLOW_STAT_REPLY, sender, &skb); + if (!fsr) + return -ENOMEM; + + n_flows = 0; + flow_extract_match(&match_key, match); + for (table_idx = 0; table_idx < dp->chain->n_tables; table_idx++) { + struct sw_table *table = dp->chain->tables[table_idx]; + struct swt_iterator iter; + + if (n_flows >= max_flows) { + break; + } + + if (!table->iterator(table, &iter)) { + if (net_ratelimit()) + printk("iterator failed for table %d\n", + table_idx); + continue; + } + + for (; iter.flow; table->iterator_next(&iter)) { + if (flow_matches(&match_key, &iter.flow->key)) { + fill_flow_stats(&fsr->flows[n_flows], + iter.flow, table_idx); + if (++n_flows >= max_flows) { + break; + } + } + } + table->iterator_destroy(&iter); + } + resize_openflow_skb(skb, &fsr->header, + header_size + flow_size * n_flows); + return send_openflow_skb(skb, sender); +} + +static int +fill_port_stat_reply(struct datapath *dp, struct ofp_port_stat_reply *psr) +{ + struct net_bridge_port *p; + int port_count = 0; + + list_for_each_entry_rcu (p, &dp->port_list, node) { + struct ofp_port_stats *ps = &psr->ports[port_count++]; + struct net_device_stats *stats = p->dev->get_stats(p->dev); + ps->port_no = htons(p->port_no); + memset(ps->pad, 0, sizeof ps->pad); + ps->rx_count = cpu_to_be64(stats->rx_packets); + ps->tx_count = cpu_to_be64(stats->tx_packets); + ps->drop_count = cpu_to_be64(stats->rx_dropped + + stats->tx_dropped); + } + + return port_count; +} + +int +dp_send_port_stats(struct datapath *dp, const struct sender *sender) +{ + struct sk_buff *skb; + struct ofp_port_stat_reply *psr; + size_t psr_len, port_max_len; + int port_count; + + /* Overallocate. */ + port_max_len = sizeof(struct ofp_port_stats) * OFPP_MAX; + psr = alloc_openflow_skb(dp, sizeof *psr + port_max_len, + OFPT_PORT_STAT_REPLY, sender, &skb); + if (!psr) + return -ENOMEM; + + /* Fill. */ + port_count = fill_port_stat_reply(dp, psr); + + /* Shrink to fit. */ + psr_len = sizeof *psr + sizeof(struct ofp_port_stats) * port_count; + resize_openflow_skb(skb, &psr->header, psr_len); + return send_openflow_skb(skb, sender); +} + +int +dp_send_table_stats(struct datapath *dp, const struct sender *sender) +{ + struct sk_buff *skb; + struct ofp_table_stat_reply *tsr; + int i, n_tables; + + n_tables = dp->chain->n_tables; + tsr = alloc_openflow_skb(dp, (offsetof(struct ofp_table_stat_reply, + tables) + + sizeof tsr->tables[0] * n_tables), + OFPT_TABLE_STAT_REPLY, sender, &skb); + if (!tsr) + return -ENOMEM; + for (i = 0; i < n_tables; i++) { + struct ofp_table_stats *ots = &tsr->tables[i]; + struct sw_table_stats stats; + dp->chain->tables[i]->stats(dp->chain->tables[i], &stats); + strncpy(ots->name, stats.name, sizeof ots->name); + ots->table_id = htons(i); + ots->pad[0] = ots->pad[1] = 0; + ots->max_entries = htonl(stats.max_flows); + ots->active_count = htonl(stats.n_flows); + ots->matched_count = cpu_to_be64(0); /* FIXME */ + } + return send_openflow_skb(skb, sender); +} + /* Generic Netlink interface. * * See netlink(7) for an introduction to netlink. See @@ -875,360 +1024,6 @@ nla_put_failure: return err; } -/* - * Fill flow entry for nl flow query. Called with rcu_lock - * - */ -static -int -dp_fill_flow(struct ofp_flow_mod* ofm, struct swt_iterator* iter) -{ - ofm->header.version = OFP_VERSION; - ofm->header.type = OFPT_FLOW_MOD; - ofm->header.length = htons(sizeof(struct ofp_flow_mod) - + sizeof(ofm->actions[0])); - ofm->header.xid = htonl(0); - - ofm->match.wildcards = htons(iter->flow->key.wildcards); - ofm->match.in_port = iter->flow->key.in_port; - ofm->match.dl_vlan = iter->flow->key.dl_vlan; - memcpy(ofm->match.dl_src, iter->flow->key.dl_src, ETH_ALEN); - memcpy(ofm->match.dl_dst, iter->flow->key.dl_dst, ETH_ALEN); - ofm->match.dl_type = iter->flow->key.dl_type; - ofm->match.nw_src = iter->flow->key.nw_src; - ofm->match.nw_dst = iter->flow->key.nw_dst; - ofm->match.nw_proto = iter->flow->key.nw_proto; - ofm->match.tp_src = iter->flow->key.tp_src; - ofm->match.tp_dst = iter->flow->key.tp_dst; - ofm->group_id = iter->flow->group_id; - ofm->max_idle = iter->flow->max_idle; - /* TODO support multiple actions */ - ofm->actions[0] = iter->flow->actions[0]; - - return 0; -} - -/* Convenience function */ -static -void* -dp_init_nl_flow_msg(uint32_t dp_idx, uint16_t table_idx, - struct genl_info *info, struct sk_buff* skb) -{ - void* data; - - data = genlmsg_put_reply(skb, info, &dp_genl_family, 0, - DP_GENL_C_QUERY_FLOW); - if (data == NULL) - return NULL; - NLA_PUT_U32(skb, DP_GENL_A_DP_IDX, dp_idx); - NLA_PUT_U16(skb, DP_GENL_A_TABLEIDX, table_idx); - - return data; - -nla_put_failure: - return NULL; -} - -/* Iterate through the specified table and send all flow entries over - * netlink to userspace. Each flow message has the following format: - * - * 32bit dpix - * 16bit tabletype - * 32bit number of flows - * openflow-flow-entries - * - * The full table may require multiple messages. A message with 0 flows - * signifies end-of message. - */ - -static -int -dp_dump_table(struct datapath *dp, uint16_t table_idx, struct genl_info *info, struct ofp_flow_mod* matchme) -{ - struct sk_buff *skb = 0; - struct sw_table *table = 0; - struct swt_iterator iter; - struct sw_flow_key in_flow; - struct nlattr *attr; - int count = 0, sum_count = 0; - void *data; - uint8_t* ofm_ptr = 0; - struct nlattr *num_attr; - int err = -ENOMEM; - - table = dp->chain->tables[table_idx]; - if ( table == NULL ) { - dprintk("dp::dp_dump_table error, non-existant table at position %d\n", table_idx); - return -EINVAL; - } - - if (!table->iterator(table, &iter)) { - dprintk("dp::dp_dump_table couldn't initialize empty table iterator\n"); - return -ENOMEM; - } - - while (iter.flow) { - - /* verify that we can fit all NL_FLOWS_PER_MESSAGE in a single - * sk_buf */ - if( (sizeof(dp_genl_family) + sizeof(uint32_t) + sizeof(uint16_t) + sizeof(uint32_t) + - (NL_FLOWS_PER_MESSAGE * sizeof(struct ofp_flow_mod))) > (8192 - 64)){ - dprintk("dp::dp_dump_table NL_FLOWS_PER_MESSAGE may cause overrun in skbuf\n"); - return -ENOMEM; - } - - skb = nlmsg_new(NLMSG_GOODSIZE, GFP_ATOMIC); - if (skb == NULL) { - return -ENOMEM; - } - - data = dp_init_nl_flow_msg(dp->dp_idx, table_idx, info, skb); - if (data == NULL){ - err= -ENOMEM; - goto error_free_skb; - } - - /* reserve space to put the number of flows for this message, to - * be filled after the loop*/ - num_attr = nla_reserve(skb, DP_GENL_A_NUMFLOWS, sizeof(uint32_t)); - if(!num_attr){ - err = -ENOMEM; - goto error_free_skb; - } - - /* Only load NL_FLOWS_PER_MESSAGE flows at a time */ - attr = nla_reserve(skb, DP_GENL_A_FLOW, - (sizeof(struct ofp_flow_mod) + sizeof(struct ofp_action)) * NL_FLOWS_PER_MESSAGE); - if (!attr){ - err = -ENOMEM; - goto error_free_skb; - } - - /* internal loop to fill NL_FLOWS_PER_MESSAGE flows */ - ofm_ptr = nla_data(attr); - flow_extract_match(&in_flow, &matchme->match); - while (iter.flow && count < NL_FLOWS_PER_MESSAGE) { - if(flow_matches(&in_flow, &iter.flow->key)){ - if((err = dp_fill_flow((struct ofp_flow_mod*)ofm_ptr, &iter))) - goto error_free_skb; - count++; - /* TODO support multiple actions */ - ofm_ptr += sizeof(struct ofp_flow_mod) + sizeof(struct ofp_action); - } - table->iterator_next(&iter); - } - - *((uint32_t*)nla_data(num_attr)) = count; - genlmsg_end(skb, data); - - sum_count += count; - count = 0; - - err = genlmsg_unicast(skb, info->snd_pid); - skb = 0; - } - - /* send a sentinal message saying we're done */ - skb = nlmsg_new(NLMSG_GOODSIZE, GFP_ATOMIC); - if (skb == NULL) { - return -ENOMEM; - } - data = dp_init_nl_flow_msg(dp->dp_idx, table_idx, info, skb); - if (data == NULL){ - err= -ENOMEM; - goto error_free_skb; - } - - NLA_PUT_U32(skb, DP_GENL_A_NUMFLOWS, 0); - /* dummy flow so nl doesn't complain */ - attr = nla_reserve(skb, DP_GENL_A_FLOW, sizeof(struct ofp_flow_mod)); - if (!attr){ - err = -ENOMEM; - goto error_free_skb; - } - genlmsg_end(skb, data); - err = genlmsg_reply(skb, info); skb = 0; - -nla_put_failure: -error_free_skb: - if(skb) - kfree_skb(skb); - return err; -} - -/* Helper function to query_table which creates and sends a message packed with - * table stats. Message form is: - * - * u32 DP_IDX - * u32 NUM_TABLES - * OFP_TABLE (list of OFP_TABLES) - * - */ - -static -int -dp_dump_table_stats(struct datapath *dp, int dp_idx, struct genl_info *info) -{ - struct sk_buff *skb = 0; - struct ofp_table *ot = 0; - struct nlattr *attr; - struct sw_table_stats stats; - size_t len; - void *data; - int err = -ENOMEM; - int i = 0; - int nt = dp->chain->n_tables; - - len = 4 + 4 + (sizeof(struct ofp_table) * nt); - - /* u32 IDX, u32 NUMTABLES, list-of-tables */ - skb = nlmsg_new(MAX(len, NLMSG_GOODSIZE), GFP_ATOMIC); - if (skb == NULL) { - return -ENOMEM; - } - - data = genlmsg_put_reply(skb, info, &dp_genl_family, 0, - DP_GENL_C_QUERY_TABLE); - if (data == NULL){ - return -ENOMEM; - } - - NLA_PUT_U32(skb, DP_GENL_A_DP_IDX, dp_idx); - NLA_PUT_U32(skb, DP_GENL_A_NUMTABLES, nt); - - /* ... we assume that all tables can fit in a single message. - * Probably a reasonable assumption seeing that we only have - * 3 atm */ - attr = nla_reserve(skb, DP_GENL_A_TABLE, (sizeof(struct ofp_table) * nt)); - if (!attr){ - err = -ENOMEM; - goto error_free_skb; - } - - ot = nla_data(attr); - - for (i = 0; i < nt; ++i) { - dp->chain->tables[i]->stats(dp->chain->tables[i], &stats); - ot->header.version = OFP_VERSION; - ot->header.type = OFPT_TABLE; - ot->header.length = htons(sizeof(struct ofp_table)); - ot->header.xid = htonl(0); - - strncpy(ot->name, stats.name, OFP_MAX_TABLE_NAME_LEN); - ot->table_id = htons(i); - ot->n_flows = htonl(stats.n_flows); - ot->max_flows = htonl(stats.max_flows); - ot++; - } - - genlmsg_end(skb, data); - err = genlmsg_reply(skb, info); skb = 0; - -nla_put_failure: -error_free_skb: - if(skb) - kfree_skb(skb); - return err; -} - -/* - * Queries a datapath for flow-table statistics - */ - - -static int dp_genl_table_query(struct sk_buff *skb, struct genl_info *info) -{ - struct datapath* dp; - int err = 0; - - if (!info->attrs[DP_GENL_A_DP_IDX]) { - dprintk("dp::dp_genl_table_query received message with missing attributes\n"); - return -EINVAL; - } - - rcu_read_lock(); - dp = dp_get(nla_get_u32(info->attrs[DP_GENL_A_DP_IDX])); - if (!dp) { - err = -ENOENT; - goto err_out; - } - - err = dp_dump_table_stats(dp, nla_get_u32(info->attrs[DP_GENL_A_DP_IDX]), info); - -err_out: - rcu_read_unlock(); - return err; -} - -/* - * Queries a datapath for flow-table entries. - */ - -static int dp_genl_flow_query(struct sk_buff *skb, struct genl_info *info) -{ - struct datapath* dp; - struct ofp_flow_mod* ofm; - u16 table_idx; - int err = 0; - - if (!info->attrs[DP_GENL_A_DP_IDX] - || !info->attrs[DP_GENL_A_TABLEIDX] - || !info->attrs[DP_GENL_A_FLOW]) { - dprintk("dp::dp_genl_flow_query received message with missing attributes\n"); - return -EINVAL; - } - - rcu_read_lock(); - dp = dp_get(nla_get_u32(info->attrs[DP_GENL_A_DP_IDX])); - if (!dp) { - err = -ENOENT; - goto err_out; - } - - table_idx = nla_get_u16(info->attrs[DP_GENL_A_TABLEIDX]); - - if (dp->chain->n_tables <= table_idx){ - printk("table index %d invalid (dp has %d tables)\n", - table_idx, dp->chain->n_tables); - err = -EINVAL; - goto err_out; - } - - ofm = nla_data(info->attrs[DP_GENL_A_FLOW]); - err = dp_dump_table(dp, table_idx, info, ofm); - -err_out: - rcu_read_unlock(); - return err; -} - -static struct nla_policy dp_genl_flow_policy[DP_GENL_A_MAX + 1] = { - [DP_GENL_A_DP_IDX] = { .type = NLA_U32 }, - [DP_GENL_A_TABLEIDX] = { .type = NLA_U16 }, - [DP_GENL_A_NUMFLOWS] = { .type = NLA_U32 }, -}; - -static struct genl_ops dp_genl_ops_query_flow = { - .cmd = DP_GENL_C_QUERY_FLOW, - .flags = GENL_ADMIN_PERM, /* Requires CAP_NET_ADMIN privilege. */ - .policy = dp_genl_flow_policy, - .doit = dp_genl_flow_query, - .dumpit = NULL, -}; - -static struct nla_policy dp_genl_table_policy[DP_GENL_A_MAX + 1] = { - [DP_GENL_A_DP_IDX] = { .type = NLA_U32 }, -}; - -static struct genl_ops dp_genl_ops_query_table = { - .cmd = DP_GENL_C_QUERY_TABLE, - .flags = GENL_ADMIN_PERM, /* Requires CAP_NET_ADMIN privilege. */ - .policy = dp_genl_table_policy, - .doit = dp_genl_table_query, - .dumpit = NULL, -}; - - static struct genl_ops dp_genl_ops_query_dp = { .cmd = DP_GENL_C_QUERY_DP, .flags = GENL_ADMIN_PERM, /* Requires CAP_NET_ADMIN privilege. */ @@ -1363,8 +1158,6 @@ static struct genl_ops *dp_genl_all_ops[] = { * front. */ &dp_genl_ops_openflow, - &dp_genl_ops_query_flow, - &dp_genl_ops_query_table, &dp_genl_ops_add_dp, &dp_genl_ops_del_dp, &dp_genl_ops_query_dp, diff --git a/datapath/datapath.h b/datapath/datapath.h index 6dec8812..d81e2f4c 100644 --- a/datapath/datapath.h +++ b/datapath/datapath.h @@ -69,6 +69,9 @@ int dp_set_origin(struct datapath *, uint16_t, struct sk_buff *); int dp_send_features_reply(struct datapath *, const struct sender *); int dp_send_config_reply(struct datapath *, const struct sender *); int dp_send_flow_expired(struct datapath *, struct sw_flow *); +int dp_send_flow_stats(struct datapath *, const struct sender *, + const struct ofp_match *); +int dp_send_table_stats(struct datapath *, const struct sender *); int dp_send_port_stats(struct datapath *, const struct sender *); int dp_update_port_flags(struct datapath *dp, const struct ofp_phy_port *opp); diff --git a/datapath/forward.c b/datapath/forward.c index 9c6b697f..02a1dd4b 100644 --- a/datapath/forward.c +++ b/datapath/forward.c @@ -423,6 +423,33 @@ recv_flow(struct sw_chain *chain, const struct sender *sender, const void *msg) } } +static int +recv_flow_status_request(struct sw_chain *chain, const struct sender *sender, + const void *msg) +{ + const struct ofp_flow_stat_request *fsr = msg; + if (fsr->type == OFPFS_INDIV) { + return dp_send_flow_stats(chain->dp, sender, &fsr->match); + } else { + /* FIXME */ + return -ENOTSUPP; + } +} + +static int +recv_port_status_request(struct sw_chain *chain, const struct sender *sender, + const void *msg) +{ + return dp_send_port_stats(chain->dp, sender); +} + +static int +recv_table_status_request(struct sw_chain *chain, const struct sender *sender, + const void *msg) +{ + return dp_send_table_stats(chain->dp, sender); +} + /* 'msg', which is 'length' bytes long, was received across Netlink from * 'sender'. Apply it to 'chain'. */ int @@ -461,6 +488,18 @@ fwd_control_input(struct sw_chain *chain, const struct sender *sender, sizeof (struct ofp_port_mod), recv_port_mod, }, + [OFPT_FLOW_STAT_REQUEST] = { + sizeof (struct ofp_flow_stat_request), + recv_flow_status_request, + }, + [OFPT_PORT_STAT_REQUEST] = { + sizeof (struct ofp_port_stat_request), + recv_port_status_request, + }, + [OFPT_TABLE_STAT_REQUEST] = { + sizeof (struct ofp_table_stat_request), + recv_table_status_request, + }, }; const struct openflow_packet *pkt; diff --git a/include/dpif.h b/include/dpif.h index 3fd3f612..4f806d7a 100644 --- a/include/dpif.h +++ b/include/dpif.h @@ -60,9 +60,6 @@ int dpif_add_dp(struct dpif *); int dpif_del_dp(struct dpif *); int dpif_add_port(struct dpif *, const char *netdev); int dpif_del_port(struct dpif *, const char *netdev); -int dpif_show(struct dpif *); -int dpif_dump_tables(struct dpif *); -int dpif_dump_flows(struct dpif *, int table, struct ofp_match *); int dpif_benchmark_nl(struct dpif *, uint32_t, uint32_t); #endif /* dpif.h */ diff --git a/include/ofp-print.h b/include/ofp-print.h index 167809ad..76ff9248 100644 --- a/include/ofp-print.h +++ b/include/ofp-print.h @@ -39,18 +39,15 @@ #include struct ofp_flow_mod; -struct ofp_table; #ifdef __cplusplus extern "C" { #endif void ofp_print(FILE *, const void *, size_t, int verbosity); -void ofp_print_table(FILE *stream, const struct ofp_table* ot); void ofp_print_packet(FILE *stream, const void *data, size_t len, size_t total_len); char *ofp_to_string(const void *, size_t, int verbosity); -char *ofp_table_to_string(const struct ofp_table* ot); char *ofp_packet_to_string(const void *data, size_t len, size_t total_len); #ifdef __cplusplus diff --git a/include/openflow-netlink.h b/include/openflow-netlink.h index cad7b47b..0fd739b3 100644 --- a/include/openflow-netlink.h +++ b/include/openflow-netlink.h @@ -41,21 +41,11 @@ /* Attributes that can be attached to the datapath's netlink messages. */ enum { DP_GENL_A_UNSPEC, - DP_GENL_A_OFPHEADER, /* OFP header information */ DP_GENL_A_DP_IDX, /* Datapath Ethernet device name. */ DP_GENL_A_PORTNAME, /* Device name for datapath port. */ DP_GENL_A_MC_GROUP, /* Generic netlink multicast group. */ DP_GENL_A_OPENFLOW, /* OpenFlow packet. */ - DP_GENL_A_DP_INFO, /* OpenFlow datapath information */ - - DP_GENL_A_FLOW, /* OpenFlow flow entry */ - DP_GENL_A_NUMFLOWS, /* Number of flows */ - DP_GENL_A_TABLEIDX, /* Flow table index */ - - DP_GENL_A_TABLE, /* OpenFlow table entry */ - DP_GENL_A_NUMTABLES, /* Number of tables in a table query */ - DP_GENL_A_NPACKETS, /* Number of packets to send up netlink */ DP_GENL_A_PSIZE, /* Size of packets to send up netlink */ @@ -69,27 +59,14 @@ enum dp_genl_command { DP_GENL_C_ADD_DP, /* Create datapath. */ DP_GENL_C_DEL_DP, /* Destroy datapath. */ DP_GENL_C_QUERY_DP, /* Get multicast group for datapath. */ - DP_GENL_C_SHOW_DP, /* Show information about datapath. */ DP_GENL_C_ADD_PORT, /* Add port to datapath. */ DP_GENL_C_DEL_PORT, /* Remove port from datapath. */ DP_GENL_C_OPENFLOW, /* Encapsulated OpenFlow protocol. */ - DP_GENL_C_QUERY_FLOW, /* Request flow entries. */ - DP_GENL_C_QUERY_TABLE, /* Request table entries. */ - DP_GENL_C_BENCHMARK_NL, /* Benchmark netlink connection */ __DP_GENL_C_MAX, DP_GENL_C_MAX = __DP_GENL_C_MAX - 1 }; -/* Table */ -enum { - TBL_MACONLY, - TBL_HASH, - TBL_LINEAR, - __TBL_MAX, - TBL_MAX = __TBL_MAX - 1 -}; - #endif /* openflow_netlink_h */ diff --git a/include/openflow.h b/include/openflow.h index 8b8513ed..7a85cf55 100644 --- a/include/openflow.h +++ b/include/openflow.h @@ -348,9 +348,10 @@ struct ofp_flow_expired { /* Statistics about flows that match the "match" field */ struct ofp_flow_stats { struct ofp_match match; /* Description of fields */ - uint32_t duration; /* Time flow has been alive in seconds. Only + uint16_t duration; /* Time flow has been alive in seconds. Only used for non-aggregated results. */ - uint64_t packet_count; + uint16_t table_id; /* ID of table flow came from. */ + uint64_t packet_count; uint64_t byte_count; }; @@ -363,8 +364,10 @@ enum ofp_stat_type { struct ofp_flow_stat_request { struct ofp_header header; struct ofp_match match; /* Fields to match */ + uint16_t table_id; /* ID of table to read (from ofp_table_stats) + or 0xffff for all tables. */ uint8_t type; /* One of OFPFS_ */ - uint8_t pad[3]; /* Align to 32-bits */ + uint8_t pad; /* Align to 32-bits */ }; /* Current flow statistics reply */ @@ -381,13 +384,47 @@ struct ofp_flow_stat_reply { struct ofp_flow_stats flows[0]; }; -/* Table attributes collected at runtime */ -struct ofp_table { +/* Current table statistics request */ +struct ofp_table_stat_request { + struct ofp_header header; +}; + +/* Statistics about a particular table */ +struct ofp_table_stats { + uint16_t table_id; + uint8_t pad[2]; /* Align to 32-bits */ + char name[OFP_MAX_TABLE_NAME_LEN]; + uint32_t max_entries; /* Max number of entries supported */ + uint32_t active_count; /* Number of active entries */ + uint64_t matched_count; /* Number of packets that hit table */ +}; + +/* Current table statistics reply */ +struct ofp_table_stat_reply { + struct ofp_header header; + struct ofp_table_stats tables[]; /* The number of entries is inferred from + the length field in the header. */ +}; + +/* Statistics about a particular port */ +struct ofp_port_stats { + uint16_t port_no; + uint8_t pad[2]; /* Align to 32-bits */ + uint64_t rx_count; /* Number of received packets */ + uint64_t tx_count; /* Number of transmitted packets */ + uint64_t drop_count; /* Number of packets dropped by interface */ +}; + +/* Current port statistics request */ +struct ofp_port_stat_request { + struct ofp_header header; +}; + +/* Current port statistics reply */ +struct ofp_port_stat_reply { struct ofp_header header; - char name[OFP_MAX_TABLE_NAME_LEN]; - uint16_t table_id; - unsigned long int n_flows; - unsigned long int max_flows; + struct ofp_port_stats ports[]; /* The number of entries is inferred from + the length field in the header. */ }; #endif /* openflow.h */ diff --git a/include/vconn.h b/include/vconn.h index 46c43e56..ecaab0ec 100644 --- a/include/vconn.h +++ b/include/vconn.h @@ -60,6 +60,7 @@ int vconn_send(struct vconn *, struct buffer *); int vconn_open_block(const char *name, struct vconn **); int vconn_send_block(struct vconn *, struct buffer *); +int vconn_recv_block(struct vconn *, struct buffer **); enum vconn_wait_type { WAIT_CONNECT, diff --git a/lib/dpif.c b/lib/dpif.c index f9c7a0af..63611e93 100644 --- a/lib/dpif.c +++ b/lib/dpif.c @@ -258,139 +258,6 @@ dpif_del_port(struct dpif *dp, const char *netdev) return send_mgmt_command(dp, DP_GENL_C_DEL_PORT, netdev); } -static const struct nl_policy table_policy[] = { - [DP_GENL_A_NUMTABLES] = { .type = NL_A_U32 }, - [DP_GENL_A_TABLE] = { .type = NL_A_UNSPEC }, -}; - -/* Writes a description of 'dp''s tables to stdout. Returns 0 if successful, - * otherwise a positive errno value. */ -int -dpif_dump_tables(struct dpif *dp) -{ - struct buffer request, *reply; - struct nlattr *attrs[ARRAY_SIZE(table_policy)]; - const struct ofp_table *tables; - int n_tables; - int i; - int retval; - - buffer_init(&request, 0); - nl_msg_put_genlmsghdr(&request, dp->sock, 0, openflow_family, - NLM_F_REQUEST, DP_GENL_C_QUERY_TABLE, 1); - nl_msg_put_u32(&request, DP_GENL_A_DP_IDX, dp->dp_idx); - retval = nl_sock_transact(dp->sock, &request, &reply); - buffer_uninit(&request); - if (retval) { - return retval; - } - if (!nl_policy_parse(reply, table_policy, attrs, - ARRAY_SIZE(table_policy))) { - buffer_delete(reply); - return EPROTO; - } - - tables = nl_attr_get(attrs[DP_GENL_A_TABLE]); - n_tables = (nl_attr_get_size(attrs[DP_GENL_A_TABLE]) - / sizeof(struct ofp_table)); - n_tables = MIN(n_tables, nl_attr_get_u32(attrs[DP_GENL_A_NUMTABLES])); - for (i = 0; i < n_tables; i++) { - const struct ofp_table *ot = &tables[i]; - if (ot->header.version != 1 || ot->header.type != OFPT_TABLE) { - VLOG_DBG("bad table query response (%"PRIu8",%"PRIu8")", - ot->header.version, ot->header.type); - retval = EPROTO; - break; - } - - ofp_print_table(stdout, ot); - fprintf(stdout,"\n"); - } - buffer_delete(reply); - - return retval; -} - -static const struct nl_policy flow_policy[] = { - [DP_GENL_A_TABLEIDX] = { .type = NL_A_U16 }, - [DP_GENL_A_NUMFLOWS] = { .type = NL_A_U32 }, - [DP_GENL_A_FLOW] = { .type = NL_A_UNSPEC }, -}; - -/* Writes a description of flows in the given 'table' in 'dp' to stdout. If - * 'match' is null, all flows in the table are written; otherwise, only - * matching flows are written. Returns 0 if successful, otherwise a positive - * errno value. */ -int -dpif_dump_flows(struct dpif *dp, int table, struct ofp_match *match) -{ - struct buffer request, *reply; - struct ofp_flow_mod *ofm; - int retval; - - buffer_init(&request, 0); - nl_msg_put_genlmsghdr(&request, dp->sock, 0, openflow_family, NLM_F_REQUEST, - DP_GENL_C_QUERY_FLOW, 1); - nl_msg_put_u32(&request, DP_GENL_A_DP_IDX, dp->dp_idx); - nl_msg_put_u16(&request, DP_GENL_A_TABLEIDX, table); - ofm = nl_msg_put_unspec_uninit(&request, DP_GENL_A_FLOW, sizeof *ofm); - memset(ofm, 0, sizeof *ofm); - ofm->header.version = 1; - ofm->header.type = OFPT_FLOW_MOD; - ofm->header.length = htons(sizeof ofm); - if (match) { - ofm->match = *match; - } else { - ofm->match.wildcards = htons(OFPFW_ALL); - } - retval = nl_sock_transact(dp->sock, &request, &reply); - buffer_uninit(&request); - if (retval) { - return retval; - } - - for (;;) { - struct nlattr *attrs[ARRAY_SIZE(flow_policy)]; - const struct ofp_flow_mod *flows, *ofm; - int n_flows; - - if (!nl_policy_parse(reply, flow_policy, attrs, - ARRAY_SIZE(flow_policy))) { - buffer_delete(reply); - return EPROTO; - } - n_flows = (nl_attr_get_size(attrs[DP_GENL_A_FLOW]) - / sizeof(struct ofp_flow_mod)); - n_flows = MIN(n_flows, nl_attr_get_u32(attrs[DP_GENL_A_NUMFLOWS])); - if (n_flows <= 0) { - break; - } - - flows = nl_attr_get(attrs[DP_GENL_A_FLOW]); - for (ofm = flows; ofm < &flows[n_flows]; ofm++) { - if (ofm->header.version != 1){ - VLOG_DBG("recv_dp_flow incorrect version"); - buffer_delete(reply); - return EPROTO; - } else if (ofm->header.type != OFPT_FLOW_MOD) { - VLOG_DBG("recv_fp_flow bad return message type"); - buffer_delete(reply); - return EPROTO; - } - - ofp_print(stdout, ofm, sizeof *ofm, 1); - putc('\n', stdout); - } - - buffer_delete(reply); - retval = nl_sock_recv(dp->sock, &reply, true); - if (retval) { - return retval; - } - } - return 0; -} - /* Tells dp to send num_packets up through netlink for benchmarking*/ int dpif_benchmark_nl(struct dpif *dp, uint32_t num_packets, uint32_t packet_size) diff --git a/lib/ofp-print.c b/lib/ofp-print.c index bfd90bfb..c728b5d4 100644 --- a/lib/ofp-print.c +++ b/lib/ofp-print.c @@ -427,6 +427,102 @@ void ofp_print_port_status(struct ds *string, const void *oh, size_t len, ofp_print_phy_port(string, &ops->desc); } +static void +ofp_flow_stat_request(struct ds *string, const void *oh, size_t len, + int verbosity) +{ + const struct ofp_flow_stat_request *fsr = oh; + + if (fsr->table_id == htons(0xffff)) { + ds_put_format(string, " table_id=any, "); + } else { + ds_put_format(string, " table_id=%"PRIu16", ", ntohs(fsr->table_id)); + } + + if (fsr->type == OFPFS_INDIV) { + ds_put_cstr(string, " type=indiv, "); + } else if (fsr->type == OFPFS_AGGREGATE) { + ds_put_cstr(string, " type=aggregate, "); + } else { + ds_put_format(string, " ***type=%"PRIu8"***, ", fsr->type); + } + ofp_print_match(string, &fsr->match); +} + +static void +ofp_flow_stat_reply(struct ds *string, const void *oh, size_t len, + int verbosity) +{ + const struct ofp_flow_stat_reply *fsr = oh; + const struct ofp_flow_stats *fs; + size_t n; + + n = (len - offsetof(struct ofp_flow_stat_reply, flows)) / sizeof *fs; + ds_put_format(string, " %zu flows\n", n); + if (verbosity < 1) { + return; + } + + for (fs = &fsr->flows[0]; fs < &fsr->flows[n]; fs++) { + ds_put_format(string, " duration=%"PRIu16" s, ", ntohs(fs->duration)); + ds_put_format(string, "table_id=%"PRIu16", ", ntohs(fs->table_id)); + ds_put_format(string, "n_packets=%"PRIu64", ", + ntohll(fs->packet_count)); + ds_put_format(string, "n_bytes=%"PRIu64", ", ntohll(fs->byte_count)); + ofp_print_match(string, &fs->match); + } +} + +static void +ofp_port_stat_reply(struct ds *string, const void *oh, size_t len, + int verbosity) +{ + const struct ofp_port_stat_reply *psr = oh; + const struct ofp_port_stats *ps; + size_t n; + + n = (len - offsetof(struct ofp_port_stat_reply, ports)) / sizeof *ps; + ds_put_format(string, " %zu ports\n", n); + if (verbosity < 1) { + return; + } + + for (ps = &psr->ports[0]; ps < &psr->ports[n]; ps++) { + ds_put_format(string, " port %"PRIu16": ", ntohs(ps->port_no)); + ds_put_format(string, "rx %"PRIu64", ", ntohll(ps->rx_count)); + ds_put_format(string, "tx %"PRIu64", ", ntohll(ps->tx_count)); + ds_put_format(string, "dropped %"PRIu64"\n", ntohll(ps->drop_count)); + } +} + +static void +ofp_table_stat_reply(struct ds *string, const void *oh, size_t len, + int verbosity) +{ + const struct ofp_table_stat_reply *tsr = oh; + const struct ofp_table_stats *ts; + size_t n; + + n = (len - offsetof(struct ofp_table_stat_reply, tables)) / sizeof *ts; + ds_put_format(string, " %zu tables\n", n); + if (verbosity < 1) { + return; + } + + for (ts = &tsr->tables[0]; ts < &tsr->tables[n]; ts++) { + char name[OFP_MAX_TABLE_NAME_LEN + 1]; + strncpy(name, ts->name, sizeof name); + name[OFP_MAX_TABLE_NAME_LEN] = '\0'; + + ds_put_format(string, " table %"PRIu16": ", ntohs(ts->table_id)); + ds_put_format(string, "name %-8s, ", name); + ds_put_format(string, "max %6"PRIu32", ", ntohl(ts->max_entries)); + ds_put_format(string, "active %6"PRIu32", ", ntohl(ts->active_count)); + ds_put_format(string, "matched %6"PRIu64"\n", + ntohll(ts->matched_count)); + } +} + struct openflow_packet { const char *name; size_t min_size; @@ -489,6 +585,36 @@ static const struct openflow_packet packets[] = { sizeof (struct ofp_port_status), ofp_print_port_status }, + [OFPT_FLOW_STAT_REQUEST] = { + "flow_stat_request", + sizeof (struct ofp_flow_stat_request), + ofp_flow_stat_request, + }, + [OFPT_FLOW_STAT_REPLY] = { + "flow_stat_reply", + sizeof (struct ofp_flow_stat_reply), + ofp_flow_stat_reply, + }, + [OFPT_PORT_STAT_REQUEST] = { + "port_stat_request", + sizeof (struct ofp_port_stat_request), + NULL, + }, + [OFPT_PORT_STAT_REPLY] = { + "port_stat_reply", + sizeof (struct ofp_port_stat_reply), + ofp_port_stat_reply, + }, + [OFPT_TABLE_STAT_REQUEST] = { + "table_stat_request", + sizeof (struct ofp_table_stat_request), + NULL, + }, + [OFPT_TABLE_STAT_REPLY] = { + "table_stat_reply", + sizeof (struct ofp_table_stat_reply), + ofp_table_stat_reply, + }, }; /* Composes and returns a string representing the OpenFlow packet of 'len' @@ -533,8 +659,10 @@ ofp_to_string(const void *oh_, size_t len, int verbosity) ds_put_format(&string, " (***length=%zu < min_size=%zu***)\n", len, pkt->min_size); } else if (!pkt->printer) { - ds_put_format(&string, " length=%zu (decoder not implemented)\n", - ntohs(oh->length)); + if (len > sizeof *oh) { + ds_put_format(&string, " length=%zu (decoder not implemented)\n", + ntohs(oh->length)); + } } else { pkt->printer(&string, oh, len, verbosity); } @@ -543,14 +671,6 @@ ofp_to_string(const void *oh_, size_t len, int verbosity) } return ds_cstr(&string); } - -char * -ofp_table_to_string(const struct ofp_table* ot) -{ - return xasprintf("id: %d name: %-8s n_flows: %6d max_flows: %6d", - ntohs(ot->table_id), ot->name, ntohl(ot->n_flows), - ntohl(ot->max_flows)); -} static void print_and_free(FILE *stream, char *string) @@ -568,13 +688,6 @@ ofp_print(FILE *stream, const void *oh, size_t len, int verbosity) print_and_free(stream, ofp_to_string(oh, len, verbosity)); } -/* Pretty print a openflow table */ -void -ofp_print_table(FILE *stream, const struct ofp_table *ot) -{ - print_and_free(stream, ofp_table_to_string(ot)); -} - /* Dumps the contents of the Ethernet frame in the 'len' bytes starting at * 'data' to 'stream' using tcpdump. 'total_len' specifies the full length of * the Ethernet frame (of which 'len' bytes were captured). diff --git a/lib/vconn.c b/lib/vconn.c index e2511432..f9c3ec00 100644 --- a/lib/vconn.c +++ b/lib/vconn.c @@ -303,6 +303,20 @@ vconn_send_block(struct vconn *vconn, struct buffer *msg) int retval; while ((retval = vconn_send(vconn, msg)) == EAGAIN) { vconn_send_wait(vconn); + VLOG_DBG("blocking on vconn send"); + poll_block(); + } + return retval; +} + +/* Same as vconn_recv, except that it waits until a message is received. */ +int +vconn_recv_block(struct vconn *vconn, struct buffer **msgp) +{ + int retval; + while ((retval = vconn_recv(vconn, msgp)) == EAGAIN) { + vconn_recv_wait(vconn); + VLOG_DBG("blocking on vconn receive"); poll_block(); } return retval; diff --git a/switch/chain.c b/switch/chain.c index e2ac53c6..2a19df02 100644 --- a/switch/chain.c +++ b/switch/chain.c @@ -41,13 +41,6 @@ #define THIS_MODULE VLM_chain #include "vlog.h" -/* Set of tables chained together in sequence from cheap to expensive. */ -#define CHAIN_MAX_TABLES 4 -struct sw_chain { - int n_tables; - struct sw_table *tables[CHAIN_MAX_TABLES]; -}; - /* Attempts to append 'table' to the set of tables in 'chain'. Returns 0 or * negative error. If 'table' is null it is assumed that table creation failed * due to out-of-memory. */ diff --git a/switch/chain.h b/switch/chain.h index 3c930aa7..2d751d03 100644 --- a/switch/chain.h +++ b/switch/chain.h @@ -43,6 +43,13 @@ struct list; #define TABLE_MAC_MAX_FLOWS 1024 #define TABLE_MAC_NUM_BUCKETS 1024 +/* Set of tables chained together in sequence from cheap to expensive. */ +#define CHAIN_MAX_TABLES 4 +struct sw_chain { + int n_tables; + struct sw_table *tables[CHAIN_MAX_TABLES]; +}; + struct sw_chain *chain_create(void); struct sw_flow *chain_lookup(struct sw_chain *, const struct sw_flow_key *); int chain_insert(struct sw_chain *, struct sw_flow *); diff --git a/switch/datapath.c b/switch/datapath.c index 6dd7c357..29f5dcf7 100644 --- a/switch/datapath.c +++ b/switch/datapath.c @@ -71,6 +71,7 @@ struct sw_port { struct datapath *dp; struct netdev *netdev; struct list node; /* Element in datapath.ports. */ + unsigned long long int rx_count, tx_count, drop_count; }; /* A connection to a controller or a management device. */ @@ -224,6 +225,9 @@ dp_add_port(struct datapath *dp, const char *name) p->dp = dp; p->netdev = netdev; + p->tx_count = 0; + p->rx_count = 0; + p->drop_count = 0; list_push_back(&dp->port_list, &p->node); /* Notify the ctlpath that this port has been added */ @@ -276,6 +280,7 @@ dp_run(struct datapath *dp) } error = netdev_recv(p->netdev, buffer); if (!error) { + p->rx_count++; fwd_port_input(dp, buffer, port_no(dp, p)); buffer = NULL; } else if (error != EAGAIN) { @@ -442,7 +447,11 @@ output_packet(struct datapath *dp, struct buffer *buffer, int out_port) if (out_port >= 0 && out_port < OFPP_MAX) { struct sw_port *p = &dp->ports[out_port]; if (p->netdev != NULL) { - netdev_send(p->netdev, buffer); + if (!netdev_send(p->netdev, buffer)) { + p->tx_count++; + } else { + p->drop_count++; + } return; } } @@ -618,6 +627,125 @@ send_flow_expired(struct datapath *dp, struct sw_flow *flow) ofe->byte_count = htonll(flow->byte_count); send_openflow_buffer(dp, buffer, NULL); } + +static void +fill_flow_stats(struct ofp_flow_stats *ofs, struct sw_flow *flow, + int table_idx, time_t now) +{ + int duration; + + ofs->match.wildcards = htons(flow->key.wildcards); + ofs->match.in_port = flow->key.flow.in_port; + memcpy(ofs->match.dl_src, flow->key.flow.dl_src, ETH_ADDR_LEN); + memcpy(ofs->match.dl_dst, flow->key.flow.dl_dst, ETH_ADDR_LEN); + ofs->match.dl_vlan = flow->key.flow.dl_vlan; + ofs->match.dl_type = flow->key.flow.dl_type; + ofs->match.nw_src = flow->key.flow.nw_src; + ofs->match.nw_dst = flow->key.flow.nw_dst; + ofs->match.nw_proto = flow->key.flow.nw_proto; + memset(ofs->match.pad, 0, sizeof ofs->match.pad); + ofs->match.tp_src = flow->key.flow.tp_src; + ofs->match.tp_dst = flow->key.flow.tp_dst; + duration = now - flow->created; + ofs->duration = htons(MIN(65535, duration)); + ofs->table_id = htons(table_idx); + ofs->packet_count = htonll(flow->packet_count); + ofs->byte_count = htonll(flow->byte_count); +} + +int +dp_send_flow_stats(struct datapath *dp, const struct sender *sender, + const struct ofp_match *match) +{ + struct buffer *buffer; + struct ofp_flow_stat_reply *fsr; + size_t header_size, fudge, flow_size; + struct sw_flow_key match_key; + int table_idx, n_flows, max_flows; + time_t now; + + header_size = offsetof(struct ofp_flow_stat_reply, flows); + fudge = 128; + flow_size = sizeof fsr->flows[0]; + max_flows = (65536 - header_size - fudge) / flow_size; + fsr = alloc_openflow_buffer(dp, header_size, + OFPT_FLOW_STAT_REPLY, sender, &buffer); + + n_flows = 0; + flow_extract_match(&match_key, match); + now = time(0); + for (table_idx = 0; table_idx < dp->chain->n_tables; table_idx++) { + struct sw_table *table = dp->chain->tables[table_idx]; + struct swt_iterator iter; + + if (n_flows >= max_flows) { + break; + } + + if (!table->iterator(table, &iter)) { + printf("iterator failed for table %d\n", table_idx); + continue; + } + + for (; iter.flow; table->iterator_next(&iter)) { + if (flow_matches(&match_key, &iter.flow->key)) { + struct ofp_flow_stats *ofs = buffer_put_uninit(buffer, + sizeof *ofs); + fill_flow_stats(ofs, iter.flow, table_idx, now); + if (++n_flows >= max_flows) { + break; + } + } + } + table->iterator_destroy(&iter); + } + return send_openflow_buffer(dp, buffer, sender); +} + +int +dp_send_port_stats(struct datapath *dp, const struct sender *sender) +{ + struct buffer *buffer; + struct ofp_port_stat_reply *psr; + struct sw_port *p; + + psr = alloc_openflow_buffer(dp, offsetof(struct ofp_port_stat_reply, + ports), + OFPT_PORT_STAT_REPLY, sender, &buffer); + LIST_FOR_EACH (p, struct sw_port, node, &dp->port_list) { + struct ofp_port_stats *ps = buffer_put_uninit(buffer, sizeof *ps); + ps->port_no = htons(port_no(dp, p)); + memset(ps->pad, 0, sizeof ps->pad); + ps->rx_count = htonll(p->rx_count); + ps->tx_count = htonll(p->tx_count); + ps->drop_count = htonll(p->drop_count); + } + return send_openflow_buffer(dp, buffer, sender); +} + +int +dp_send_table_stats(struct datapath *dp, const struct sender *sender) +{ + struct buffer *buffer; + struct ofp_table_stat_reply *tsr; + int i; + + tsr = alloc_openflow_buffer(dp, offsetof(struct ofp_table_stat_reply, + tables), + OFPT_TABLE_STAT_REPLY, sender, &buffer); + for (i = 0; i < dp->chain->n_tables; i++) { + struct ofp_table_stats *ots = buffer_put_uninit(buffer, sizeof *ots); + struct sw_table_stats stats; + dp->chain->tables[i]->stats(dp->chain->tables[i], &stats); + strncpy(ots->name, stats.name, sizeof ots->name); + ots->table_id = htons(i); + ots->pad[0] = ots->pad[1] = 0; + ots->max_entries = htonl(stats.max_flows); + ots->active_count = htonl(stats.n_flows); + ots->matched_count = htonll(0); /* FIXME */ + } + return send_openflow_buffer(dp, buffer, sender); +} /* 'buffer' was received on 'in_port', a physical switch port between 0 and * OFPP_MAX. Process it according to 'chain'. */ @@ -994,6 +1122,33 @@ recv_flow(struct datapath *dp, const struct sender *sender UNUSED, } } +static int +recv_flow_status_request(struct datapath *dp, const struct sender *sender, + const void *msg) +{ + const struct ofp_flow_stat_request *fsr = msg; + if (fsr->type == OFPFS_INDIV) { + return dp_send_flow_stats(dp, sender, &fsr->match); + } else { + /* FIXME */ + return -ENOSYS; + } +} + +static int +recv_port_status_request(struct datapath *dp, const struct sender *sender, + const void *msg) +{ + return dp_send_port_stats(dp, sender); +} + +static int +recv_table_status_request(struct datapath *dp, const struct sender *sender, + const void *msg) +{ + return dp_send_table_stats(dp, sender); +} + /* 'msg', which is 'length' bytes long, was received from the control path. * Apply it to 'chain'. */ int @@ -1030,6 +1185,18 @@ fwd_control_input(struct datapath *dp, const struct sender *sender, sizeof (struct ofp_port_mod), recv_port_mod, }, + [OFPT_FLOW_STAT_REQUEST] = { + sizeof (struct ofp_flow_stat_request), + recv_flow_status_request, + }, + [OFPT_PORT_STAT_REQUEST] = { + sizeof (struct ofp_port_stat_request), + recv_port_status_request, + }, + [OFPT_TABLE_STAT_REQUEST] = { + sizeof (struct ofp_table_stat_request), + recv_table_status_request, + }, }; const struct openflow_packet *pkt; diff --git a/switch/switch.c b/switch/switch.c index f2f107aa..f8f45bc3 100644 --- a/switch/switch.c +++ b/switch/switch.c @@ -135,6 +135,7 @@ parse_options(int argc, char *argv[]) static struct option long_options[] = { {"interfaces", required_argument, 0, 'i'}, {"datapath-id", required_argument, 0, 'd'}, + {"listen", required_argument, 0, 'l'}, {"verbose", optional_argument, 0, 'v'}, {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'V'}, diff --git a/utilities/dpctl.c b/utilities/dpctl.c index c78c9b19..f4404259 100644 --- a/utilities/dpctl.c +++ b/utilities/dpctl.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -56,7 +57,7 @@ #include "vconn-ssl.h" #include "vlog.h" -#define THIS_MODULE VLM_DPCTL +#define THIS_MODULE VLM_dpctl static const char* ifconfigbin = "/sbin/ifconfig"; @@ -161,16 +162,17 @@ usage(void) " deldp nl:DP_ID delete local datapath DP_ID\n" " addif nl:DP_ID IFACE add IFACE as a port on DP_ID\n" " delif nl:DP_ID IFACE delete IFACE as a port on DP_ID\n" + " monitor nl:DP_ID print packets received\n" " benchmark-nl nl:DP_ID N SIZE send N packets of SIZE bytes\n" #endif "\nCommands that apply to local datapaths and remote switches:\n" - " show METHOD show information about METHOD\n" - " monitor METHOD print packets received on METHOD\n" - " dump-tables METHOD print table stats for METHOD\n" - " dump-flows METHOD T_ID print all flow entries in table T_ID of METHOD\n" - " dump-flows METHOD T_ID FLOW print matching FLOWs in table T_ID of METHOD\n" - " add-flows METHOD FILE add flows from FILE to METHOD\n" - "where each METHOD is an active OpenFlow connection method.\n", + " show SWITCH show information\n" + " dump-tables SWITCH print table stats\n" + " dump-ports SWITCH print port statistics\n" + " dump-flows SWITCH print all flow entries\n" + " dump-flows SWITCH FLOW print matching FLOWs\n" + " add-flows SWITCH FILE add flows from FILE\n" + "where each SWITCH is an active OpenFlow connection method.\n", program_name, program_name); vconn_usage(true, false); printf("\nOptions:\n" @@ -180,10 +182,25 @@ usage(void) exit(EXIT_SUCCESS); } -static void run(int retval, const char *name) +static void run(int retval, const char *message, ...) + PRINTF_FORMAT(2, 3); + +static void run(int retval, const char *message, ...) { if (retval) { - fatal(retval, "%s", name); + va_list args; + + fprintf(stderr, "%s: ", program_name); + va_start(args, message); + vfprintf(stderr, message, args); + va_end(args); + if (retval == EOF) { + fputs(": unexpected end of file\n", stderr); + } else { + vfprintf(stderr, ": %s\n", strerror(retval)); + } + + exit(EXIT_FAILURE); } } @@ -241,6 +258,18 @@ static void do_del_port(int argc UNUSED, char *argv[]) dpif_close(&dp); } +static void do_monitor(int argc UNUSED, char *argv[]) +{ + struct dpif dp; + open_nl_vconn(argv[1], false, &dp); + for (;;) { + struct buffer *b; + run(dpif_recv_openflow(&dp, &b, true), "dpif_recv_openflow"); + ofp_print(stderr, b->data, b->size, 2); + buffer_delete(b); + } +} + #define BENCHMARK_INCR 100 static void do_benchmark_nl(int argc UNUSED, char *argv[]) @@ -282,34 +311,95 @@ static void do_benchmark_nl(int argc UNUSED, char *argv[]) /* Generic commands. */ -static void do_show(int argc UNUSED, char *argv[]) +static uint32_t +random_xid(void) { -#if 0 - struct dpif dp; - run(dpif_open(atoi(argv[1]), false, &dp), "dpif_open"); - run(dpif_show(&dp), "show"); - dpif_close(&dp); -#endif + static bool inited = false; + if (!inited) { + struct timeval tv; + inited = true; + if (gettimeofday(&tv, NULL) < 0) { + fatal(errno, "gettimeofday"); + } + srand(tv.tv_sec ^ tv.tv_usec); + } + return rand(); } -static void do_monitor(int argc UNUSED, char *argv[]) +static void * +alloc_openflow_buffer(size_t openflow_len, uint8_t type, + struct buffer **bufferp) { - struct dpif dp; - run(dpif_open(atoi(argv[1]), true, &dp), "dpif_open"); + struct buffer *buffer; + struct ofp_header *oh; + + buffer = *bufferp = buffer_new(openflow_len); + oh = buffer_put_uninit(buffer, openflow_len); + memset(oh, 0, openflow_len); + oh->version = OFP_VERSION; + oh->type = type; + oh->length = 0; + oh->xid = random_xid(); + return oh; +} + +static void +send_openflow_buffer(struct vconn *vconn, struct buffer *buffer) +{ + struct ofp_header *oh; + + oh = buffer_at_assert(buffer, 0, sizeof *oh); + oh->length = htons(buffer->size); + + run(vconn_send_block(vconn, buffer), "failed to send packet to switch"); +} + +static struct buffer * +transact_openflow(struct vconn *vconn, struct buffer *request) +{ + uint32_t send_xid = ((struct ofp_header *) request->data)->xid; + + send_openflow_buffer(vconn, request); for (;;) { - struct buffer *b; - run(dpif_recv_openflow(&dp, &b, true), "dpif_recv_openflow"); - ofp_print(stderr, b->data, b->size, 2); - buffer_delete(b); + uint32_t recv_xid; + struct buffer *reply; + + run(vconn_recv_block(vconn, &reply), "OpenFlow packet receive failed"); + recv_xid = ((struct ofp_header *) reply->data)->xid; + if (send_xid == recv_xid) { + return reply; + } + + VLOG_DBG("received reply with xid %08"PRIx32" != expected %08"PRIx32, + recv_xid, send_xid); + buffer_delete(reply); } } -static void do_dump_tables(int argc, char *argv[]) +static void +dump_transaction(const char *vconn_name, uint8_t request_type) { - struct dpif dp; - run(dpif_open(atoi(argv[1]), false, &dp), "dpif_open"); - run(dpif_dump_tables(&dp), "dump_tables"); - dpif_close(&dp); + struct vconn *vconn; + struct buffer *request, *reply; + + run(vconn_open_block(vconn_name, &vconn), "connecting to %s", vconn_name); + alloc_openflow_buffer(sizeof(struct ofp_header), request_type, &request); + reply = transact_openflow(vconn, request); + ofp_print(stdout, reply->data, reply->size, 1); + vconn_close(vconn); +} + +static void +do_show(int argc UNUSED, char *argv[]) +{ + dump_transaction(argv[1], OFPT_FEATURES_REQUEST); +} + + +static void +do_dump_tables(int argc, char *argv[]) +{ + dump_transaction(argv[1], OFPT_TABLE_STAT_REQUEST); } @@ -364,7 +454,8 @@ str_to_action(const char *str, struct ofp_action *action) } static void -str_to_flow(char *string, struct ofp_match *match, struct ofp_action *action) +str_to_flow(char *string, struct ofp_match *match, struct ofp_action *action, + uint16_t *table_idx) { struct field { const char *name; @@ -391,6 +482,9 @@ str_to_flow(char *string, struct ofp_match *match, struct ofp_action *action) uint32_t wildcards; bool got_action = false; + if (table_idx) { + *table_idx = htons(0xffff); + } memset(match, 0, sizeof *match); wildcards = OFPFW_ALL; for (name = strtok(string, "="), value = strtok(NULL, " \t\n"); @@ -406,6 +500,11 @@ str_to_flow(char *string, struct ofp_match *match, struct ofp_action *action) continue; } + if (table_idx && !strcmp(name, "table")) { + *table_idx = htons(atoi(value)); + continue; + } + for (f = fields; f < &fields[ARRAY_SIZE(fields)]; f++) { if (!strcmp(f->name, name)) { goto found; @@ -452,17 +551,18 @@ str_to_flow(char *string, struct ofp_match *match, struct ofp_action *action) static void do_dump_flows(int argc, char *argv[]) { - struct dpif dp; - struct ofp_match match, *matchp; - run(dpif_open(atoi(argv[1]), false, &dp), "dpif_open"); - if (argc == 4) { - str_to_flow(argv[3], &match, NULL); - matchp = &match; - } else { - matchp = NULL; - } - run(dpif_dump_flows(&dp, atoi(argv[2]), matchp), "dump_flows"); - dpif_close(&dp); + struct vconn *vconn; + struct buffer *request, *reply; + struct ofp_flow_stat_request *fsr; + + run(vconn_open_block(argv[1], &vconn), "connecting to %s", argv[1]); + fsr = alloc_openflow_buffer(sizeof *fsr, OFPT_FLOW_STAT_REQUEST, &request); + str_to_flow(argc > 2 ? argv[2] : "", &fsr->match, NULL, &fsr->table_id); + fsr->type = OFPFS_INDIV; + fsr->pad = 0; + reply = transact_openflow(vconn, request); + ofp_print(stdout, reply->data, reply->size, 1); + vconn_close(vconn); } static void do_add_flows(int argc, char *argv[]) @@ -473,19 +573,12 @@ static void do_add_flows(int argc, char *argv[]) FILE *file; char line[1024]; - int retval; - file = fopen(argv[2], "r"); if (file == NULL) { fatal(errno, "%s: open", argv[2]); } - sprintf(vconn_name, "nl:%d", atoi(argv[1])); - retval = vconn_open(vconn_name, &vconn); - if (retval) { - fatal(retval, "opening datapath"); - } - + run(vconn_open_block(vconn_name, &vconn), "connecting to %s", argv[1]); while (fgets(line, sizeof line, file)) { struct buffer *buffer; struct ofp_flow_mod *ofm; @@ -504,30 +597,26 @@ static void do_add_flows(int argc, char *argv[]) continue; } + /* Parse and send. */ size = sizeof *ofm + sizeof ofm->actions[0]; - buffer = buffer_new(size); - ofm = buffer_put_uninit(buffer, size); - - /* Parse. */ - memset(ofm, 0, size); - ofm->header.type = OFPT_FLOW_MOD; - ofm->header.version = OFP_VERSION; - ofm->header.length = htons(size); + ofm = alloc_openflow_buffer(size, OFPT_FLOW_MOD, &buffer); ofm->command = htons(OFPFC_ADD); ofm->max_idle = htons(50); ofm->buffer_id = htonl(UINT32_MAX); ofm->group_id = htonl(0); - str_to_flow(line, &ofm->match, &ofm->actions[0]); - - retval = vconn_send_block(vconn, buffer); - if (retval) { - fatal(retval, "sending to datapath"); - } + str_to_flow(line, &ofm->match, &ofm->actions[0], NULL); + run(vconn_send_block(vconn, buffer), "send OpenFlow packet"); } vconn_close(vconn); fclose(file); } +static void +do_dump_ports(int argc, char *argv[]) +{ + dump_transaction(argv[1], OFPT_PORT_STAT_REQUEST); +} + static void do_help(int argc UNUSED, char *argv[] UNUSED) { usage(); @@ -547,6 +636,7 @@ static struct command all_commands[] = { { "help", 0, INT_MAX, do_help }, { "monitor", 1, 1, do_monitor }, { "dump-tables", 1, 1, do_dump_tables }, - { "dump-flows", 2, 3, do_dump_flows }, + { "dump-flows", 1, 2, do_dump_flows }, { "add-flows", 2, 2, do_add_flows }, + { "dump-ports", 1, 1, do_dump_ports }, }; -- 2.30.2