+
+ ofe->priority = htons(flow->priority);
+ ofe->reason = reason;
+ memset(ofe->pad, 0, sizeof ofe->pad);
+
+ ofe->duration = htonl(time_now() - flow->created);
+ memset(ofe->pad2, 0, sizeof ofe->pad2);
+ ofe->packet_count = htonll(flow->packet_count);
+ ofe->byte_count = htonll(flow->byte_count);
+ send_openflow_buffer(dp, buffer, NULL);
+}
+
+void
+dp_send_error_msg(struct datapath *dp, const struct sender *sender,
+ uint16_t type, uint16_t code, const uint8_t *data, size_t len)
+{
+ struct buffer *buffer;
+ struct ofp_error_msg *oem;
+ oem = make_openflow_reply(sizeof(*oem)+len, OFPT_ERROR_MSG,
+ sender, &buffer);
+ oem->type = htons(type);
+ oem->code = htons(code);
+ memcpy(oem->data, data, len);
+ send_openflow_buffer(dp, buffer, sender);
+}
+
+static void
+fill_flow_stats(struct buffer *buffer, struct sw_flow *flow,
+ int table_idx, time_t now)
+{
+ struct ofp_flow_stats *ofs;
+ int length = sizeof *ofs + sizeof *ofs->actions * flow->n_actions;
+ ofs = buffer_put_uninit(buffer, length);
+ ofs->length = htons(length);
+ ofs->table_id = table_idx;
+ ofs->pad = 0;
+ ofs->match.wildcards = htonl(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;
+ ofs->match.pad = 0;
+ ofs->match.tp_src = flow->key.flow.tp_src;
+ ofs->match.tp_dst = flow->key.flow.tp_dst;
+ ofs->duration = htonl(now - flow->created);
+ ofs->priority = htons(flow->priority);
+ ofs->idle_timeout = htons(flow->idle_timeout);
+ ofs->hard_timeout = htons(flow->hard_timeout);
+ memset(ofs->pad2, 0, sizeof ofs->pad2);
+ ofs->packet_count = htonll(flow->packet_count);
+ ofs->byte_count = htonll(flow->byte_count);
+ memcpy(ofs->actions, flow->actions,
+ sizeof *ofs->actions * flow->n_actions);
+}
+
+\f
+/* 'buffer' was received on 'in_port', a physical switch port between 0 and
+ * OFPP_MAX. Process it according to 'dp''s flow table. Returns 0 if
+ * successful, in which case 'buffer' is destroyed, or -ESRCH if there is no
+ * matching flow, in which case 'buffer' still belongs to the caller. */
+int run_flow_through_tables(struct datapath *dp, struct buffer *buffer,
+ int in_port)
+{
+ struct sw_flow_key key;
+ struct sw_flow *flow;
+
+ key.wildcards = 0;
+ if (flow_extract(buffer, in_port, &key.flow)
+ && (dp->flags & OFPC_FRAG_MASK) == OFPC_FRAG_DROP) {
+ /* Drop fragment. */
+ buffer_delete(buffer);
+ return 0;
+ }
+
+ flow = chain_lookup(dp->chain, &key);
+ if (flow != NULL) {
+ flow_used(flow, buffer);
+ execute_actions(dp, buffer, in_port, &key,
+ flow->actions, flow->n_actions);
+ return 0;
+ } else {
+ return -ESRCH;
+ }
+}
+
+/* 'buffer' was received on 'in_port', a physical switch port between 0 and
+ * OFPP_MAX. Process it according to 'dp''s flow table, sending it up to the
+ * controller if no flow matches. Takes ownership of 'buffer'. */
+void fwd_port_input(struct datapath *dp, struct buffer *buffer, int in_port)
+{
+ if (run_flow_through_tables(dp, buffer, in_port)) {
+ dp_output_control(dp, buffer, in_port, dp->miss_send_len,
+ OFPR_NO_MATCH);
+ }
+}
+
+static void
+do_output(struct datapath *dp, struct buffer *buffer, int in_port,
+ size_t max_len, int out_port)
+{
+ if (out_port != OFPP_CONTROLLER) {
+ dp_output_port(dp, buffer, in_port, out_port);
+ } else {
+ dp_output_control(dp, buffer, in_port, max_len, OFPR_ACTION);
+ }
+}
+
+static void
+execute_actions(struct datapath *dp, struct buffer *buffer,
+ int in_port, const struct sw_flow_key *key,
+ const struct ofp_action *actions, int n_actions)
+{
+ /* Every output action needs a separate clone of 'buffer', but the common
+ * case is just a single output action, so that doing a clone and then
+ * freeing the original buffer is wasteful. So the following code is
+ * slightly obscure just to avoid that. */
+ int prev_port;
+ size_t max_len=0; /* Initialze to make compiler happy */
+ uint16_t eth_proto;
+ int i;
+
+ prev_port = -1;
+ eth_proto = ntohs(key->flow.dl_type);
+
+ for (i = 0; i < n_actions; i++) {
+ const struct ofp_action *a = &actions[i];
+ struct eth_header *eh = buffer->l2;
+
+ if (prev_port != -1) {
+ do_output(dp, buffer_clone(buffer), in_port, max_len, prev_port);
+ prev_port = -1;
+ }
+
+ switch (ntohs(a->type)) {
+ case OFPAT_OUTPUT:
+ prev_port = ntohs(a->arg.output.port);
+ max_len = ntohs(a->arg.output.max_len);
+ break;
+
+ case OFPAT_SET_DL_VLAN:
+ modify_vlan(buffer, key, a);
+ break;
+
+ case OFPAT_SET_DL_SRC:
+ memcpy(eh->eth_src, a->arg.dl_addr, sizeof eh->eth_src);
+ break;
+
+ case OFPAT_SET_DL_DST:
+ memcpy(eh->eth_dst, a->arg.dl_addr, sizeof eh->eth_dst);
+ break;
+
+ case OFPAT_SET_NW_SRC:
+ case OFPAT_SET_NW_DST:
+ modify_nh(buffer, eth_proto, key->flow.nw_proto, a);
+ break;
+
+ case OFPAT_SET_TP_SRC:
+ case OFPAT_SET_TP_DST:
+ modify_th(buffer, eth_proto, key->flow.nw_proto, a);
+ break;
+
+ default:
+ NOT_REACHED();
+ }
+ }
+ if (prev_port != -1)
+ do_output(dp, buffer, in_port, max_len, prev_port);
+ else
+ buffer_delete(buffer);
+}
+
+static void modify_nh(struct buffer *buffer, uint16_t eth_proto,
+ uint8_t nw_proto, const struct ofp_action *a)
+{
+ if (eth_proto == ETH_TYPE_IP) {
+ struct ip_header *nh = buffer->l3;
+ uint32_t new, *field;
+
+ new = a->arg.nw_addr;
+ field = a->type == OFPAT_SET_NW_SRC ? &nh->ip_src : &nh->ip_dst;
+ if (nw_proto == IP_TYPE_TCP) {
+ struct tcp_header *th = buffer->l4;
+ th->tcp_csum = recalc_csum32(th->tcp_csum, *field, new);
+ } else if (nw_proto == IP_TYPE_UDP) {
+ struct udp_header *th = buffer->l4;
+ if (th->udp_csum) {
+ th->udp_csum = recalc_csum32(th->udp_csum, *field, new);
+ if (!th->udp_csum) {
+ th->udp_csum = 0xffff;
+ }
+ }
+ }
+ nh->ip_csum = recalc_csum32(nh->ip_csum, *field, new);
+ *field = new;
+ }
+}
+
+static void modify_th(struct buffer *buffer, uint16_t eth_proto,
+ uint8_t nw_proto, const struct ofp_action *a)
+{
+ if (eth_proto == ETH_TYPE_IP) {
+ uint16_t new, *field;
+
+ new = a->arg.tp;
+
+ if (nw_proto == IP_TYPE_TCP) {
+ struct tcp_header *th = buffer->l4;
+ field = a->type == OFPAT_SET_TP_SRC ? &th->tcp_src : &th->tcp_dst;
+ th->tcp_csum = recalc_csum16(th->tcp_csum, *field, new);
+ *field = new;
+ } else if (nw_proto == IP_TYPE_UDP) {
+ struct udp_header *th = buffer->l4;
+ field = a->type == OFPAT_SET_TP_SRC ? &th->udp_src : &th->udp_dst;
+ th->udp_csum = recalc_csum16(th->udp_csum, *field, new);
+ *field = new;
+ }
+ }
+}
+
+static void
+modify_vlan(struct buffer *buffer,
+ const struct sw_flow_key *key, const struct ofp_action *a)
+{
+ uint16_t new_id = a->arg.vlan_id;
+ struct vlan_eth_header *veh;
+
+ if (new_id != htons(OFP_VLAN_NONE)) {
+ if (key->flow.dl_vlan != htons(OFP_VLAN_NONE)) {
+ /* Modify vlan id, but maintain other TCI values */
+ veh = buffer->l2;
+ veh->veth_tci &= ~htons(VLAN_VID);
+ veh->veth_tci |= new_id;
+ } else {
+ /* Insert new vlan id. */
+ struct eth_header *eh = buffer->l2;
+ struct vlan_eth_header tmp;
+ memcpy(tmp.veth_dst, eh->eth_dst, ETH_ADDR_LEN);
+ memcpy(tmp.veth_src, eh->eth_src, ETH_ADDR_LEN);
+ tmp.veth_type = htons(ETH_TYPE_VLAN);
+ tmp.veth_tci = new_id;
+ tmp.veth_next_type = eh->eth_type;
+
+ veh = buffer_push_uninit(buffer, VLAN_HEADER_LEN);
+ memcpy(veh, &tmp, sizeof tmp);
+ buffer->l2 -= VLAN_HEADER_LEN;
+ }
+ } else {
+ /* Remove an existing vlan header if it exists */
+ veh = buffer->l2;
+ if (veh->veth_type == htons(ETH_TYPE_VLAN)) {
+ struct eth_header tmp;
+
+ memcpy(tmp.eth_dst, veh->veth_dst, ETH_ADDR_LEN);
+ memcpy(tmp.eth_src, veh->veth_src, ETH_ADDR_LEN);
+ tmp.eth_type = veh->veth_next_type;
+
+ buffer->size -= VLAN_HEADER_LEN;
+ buffer->data += VLAN_HEADER_LEN;
+ buffer->l2 += VLAN_HEADER_LEN;
+ memcpy(buffer->data, &tmp, sizeof tmp);
+ }
+ }
+}
+
+static int
+recv_features_request(struct datapath *dp, const struct sender *sender,
+ const void *msg)
+{
+ dp_send_features_reply(dp, sender);
+ return 0;
+}
+
+static int
+recv_get_config_request(struct datapath *dp, const struct sender *sender,
+ const void *msg)
+{
+ struct buffer *buffer;
+ struct ofp_switch_config *osc;
+
+ osc = make_openflow_reply(sizeof *osc, OFPT_GET_CONFIG_REPLY,
+ sender, &buffer);
+
+ osc->flags = htons(dp->flags);
+ osc->miss_send_len = htons(dp->miss_send_len);
+
+ return send_openflow_buffer(dp, buffer, sender);
+}
+
+static int
+recv_set_config(struct datapath *dp, const struct sender *sender UNUSED,
+ const void *msg)
+{
+ const struct ofp_switch_config *osc = msg;
+ int flags;
+
+ flags = ntohs(osc->flags) & (OFPC_SEND_FLOW_EXP | OFPC_FRAG_MASK);
+ if ((flags & OFPC_FRAG_MASK) != OFPC_FRAG_NORMAL
+ && (flags & OFPC_FRAG_MASK) != OFPC_FRAG_DROP) {
+ flags = (flags & ~OFPC_FRAG_MASK) | OFPC_FRAG_DROP;
+ }
+ dp->flags = flags;
+ dp->miss_send_len = ntohs(osc->miss_send_len);
+ return 0;
+}
+
+static int
+recv_packet_out(struct datapath *dp, const struct sender *sender UNUSED,
+ const void *msg)
+{
+ const struct ofp_packet_out *opo = msg;
+ struct sw_flow_key key;
+ struct buffer *buffer;
+ int n_actions = ntohs(opo->n_actions);
+ int act_len = n_actions * sizeof opo->actions[0];
+
+ if (act_len > (ntohs(opo->header.length) - sizeof *opo)) {
+ VLOG_DBG_RL(&rl, "message too short for number of actions");
+ return -EINVAL;
+ }
+
+ if (ntohl(opo->buffer_id) == (uint32_t) -1) {
+ /* FIXME: can we avoid copying data here? */
+ int data_len = ntohs(opo->header.length) - sizeof *opo - act_len;
+ buffer = buffer_new(data_len);
+ buffer_put(buffer, &opo->actions[n_actions], data_len);
+ } else {
+ buffer = retrieve_buffer(ntohl(opo->buffer_id));
+ if (!buffer) {
+ return -ESRCH;
+ }
+ }
+
+ flow_extract(buffer, ntohs(opo->in_port), &key.flow);
+ execute_actions(dp, buffer, ntohs(opo->in_port),
+ &key, opo->actions, n_actions);
+
+ return 0;
+}
+
+static int
+recv_port_mod(struct datapath *dp, const struct sender *sender UNUSED,
+ const void *msg)
+{
+ const struct ofp_port_mod *opm = msg;
+
+ dp_update_port_flags(dp, opm);
+
+ return 0;
+}
+
+static int
+add_flow(struct datapath *dp, const struct ofp_flow_mod *ofm)
+{
+ int error = -ENOMEM;
+ int n_actions;
+ int i;
+ struct sw_flow *flow;
+
+
+ /* To prevent loops, make sure there's no action to send to the
+ * OFP_TABLE virtual port.
+ */
+ n_actions = (ntohs(ofm->header.length) - sizeof *ofm)
+ / sizeof *ofm->actions;
+ for (i=0; i<n_actions; i++) {
+ const struct ofp_action *a = &ofm->actions[i];
+
+ if (a->type == htons(OFPAT_OUTPUT)
+ && (a->arg.output.port == htons(OFPP_TABLE)
+ || a->arg.output.port == htons(OFPP_NONE)
+ || a->arg.output.port == ofm->match.in_port)) {
+ /* xxx Send fancy new error message? */
+ goto error;
+ }
+ }
+
+ /* Allocate memory. */
+ flow = flow_alloc(n_actions);
+ if (flow == NULL)
+ goto error;
+
+ /* Fill out flow. */
+ flow_extract_match(&flow->key, &ofm->match);
+ flow->priority = flow->key.wildcards ? ntohs(ofm->priority) : -1;
+ flow->idle_timeout = ntohs(ofm->idle_timeout);
+ flow->hard_timeout = ntohs(ofm->hard_timeout);
+ flow->used = flow->created = time_now();
+ flow->n_actions = n_actions;
+ flow->byte_count = 0;
+ flow->packet_count = 0;
+ memcpy(flow->actions, ofm->actions, n_actions * sizeof *flow->actions);
+
+ /* Act. */
+ error = chain_insert(dp->chain, flow);
+ if (error) {
+ goto error_free_flow;
+ }
+ error = 0;
+ if (ntohl(ofm->buffer_id) != UINT32_MAX) {
+ struct buffer *buffer = retrieve_buffer(ntohl(ofm->buffer_id));
+ if (buffer) {
+ struct sw_flow_key key;
+ uint16_t in_port = ntohs(ofm->match.in_port);
+ flow_used(flow, buffer);
+ flow_extract(buffer, in_port, &key.flow);
+ execute_actions(dp, buffer, in_port, &key, ofm->actions, n_actions);
+ } else {
+ error = -ESRCH;
+ }
+ }
+ return error;
+
+error_free_flow:
+ flow_free(flow);
+error:
+ if (ntohl(ofm->buffer_id) != (uint32_t) -1)
+ discard_buffer(ntohl(ofm->buffer_id));
+ return error;
+}
+
+static int
+recv_flow(struct datapath *dp, const struct sender *sender UNUSED,
+ const void *msg)
+{
+ const struct ofp_flow_mod *ofm = msg;
+ uint16_t command = ntohs(ofm->command);
+
+ if (command == OFPFC_ADD) {
+ return add_flow(dp, ofm);
+ } else if (command == OFPFC_DELETE) {
+ struct sw_flow_key key;
+ flow_extract_match(&key, &ofm->match);
+ return chain_delete(dp->chain, &key, 0, 0) ? 0 : -ESRCH;
+ } else if (command == OFPFC_DELETE_STRICT) {
+ struct sw_flow_key key;
+ uint16_t priority;
+ flow_extract_match(&key, &ofm->match);
+ priority = key.wildcards ? ntohs(ofm->priority) : -1;
+ return chain_delete(dp->chain, &key, priority, 1) ? 0 : -ESRCH;
+ } else {
+ return -ENODEV;
+ }
+}
+
+static int desc_stats_dump(struct datapath *dp, void *state,
+ struct buffer *buffer)
+{
+ struct ofp_desc_stats *ods = buffer_put_uninit(buffer, sizeof *ods);
+
+ strncpy(ods->mfr_desc, &mfr_desc, sizeof ods->mfr_desc);
+ strncpy(ods->hw_desc, &hw_desc, sizeof ods->hw_desc);
+ strncpy(ods->sw_desc, &sw_desc, sizeof ods->sw_desc);
+ strncpy(ods->serial_num, &serial_num, sizeof ods->serial_num);
+
+ return 0;
+}
+
+struct flow_stats_state {
+ int table_idx;
+ struct sw_table_position position;
+ struct ofp_flow_stats_request rq;
+ time_t now;
+
+ struct buffer *buffer;
+};
+
+#define MAX_FLOW_STATS_BYTES 4096
+
+static int flow_stats_init(struct datapath *dp, const void *body, int body_len,
+ void **state)
+{
+ const struct ofp_flow_stats_request *fsr = body;
+ struct flow_stats_state *s = xmalloc(sizeof *s);
+ s->table_idx = fsr->table_id == 0xff ? 0 : fsr->table_id;
+ memset(&s->position, 0, sizeof s->position);
+ s->rq = *fsr;
+ *state = s;
+ return 0;
+}
+
+static int flow_stats_dump_callback(struct sw_flow *flow, void *private)
+{
+ struct flow_stats_state *s = private;
+ fill_flow_stats(s->buffer, flow, s->table_idx, s->now);
+ return s->buffer->size >= MAX_FLOW_STATS_BYTES;
+}
+
+static int flow_stats_dump(struct datapath *dp, void *state,
+ struct buffer *buffer)
+{
+ struct flow_stats_state *s = state;
+ struct sw_flow_key match_key;
+
+ flow_extract_match(&match_key, &s->rq.match);
+ s->buffer = buffer;
+ s->now = time_now();
+ while (s->table_idx < dp->chain->n_tables
+ && (s->rq.table_id == 0xff || s->rq.table_id == s->table_idx))
+ {
+ struct sw_table *table = dp->chain->tables[s->table_idx];
+
+ if (table->iterate(table, &match_key, &s->position,
+ flow_stats_dump_callback, s))
+ break;
+
+ s->table_idx++;
+ memset(&s->position, 0, sizeof s->position);
+ }
+ return s->buffer->size >= MAX_FLOW_STATS_BYTES;
+}
+
+static void flow_stats_done(void *state)
+{
+ free(state);
+}
+
+struct aggregate_stats_state {
+ struct ofp_aggregate_stats_request rq;
+};
+
+static int aggregate_stats_init(struct datapath *dp,
+ const void *body, int body_len,
+ void **state)
+{
+ const struct ofp_aggregate_stats_request *rq = body;
+ struct aggregate_stats_state *s = xmalloc(sizeof *s);
+ s->rq = *rq;
+ *state = s;
+ return 0;
+}
+
+static int aggregate_stats_dump_callback(struct sw_flow *flow, void *private)
+{
+ struct ofp_aggregate_stats_reply *rpy = private;
+ rpy->packet_count += flow->packet_count;
+ rpy->byte_count += flow->byte_count;
+ rpy->flow_count++;
+ return 0;
+}
+
+static int aggregate_stats_dump(struct datapath *dp, void *state,
+ struct buffer *buffer)
+{
+ struct aggregate_stats_state *s = state;
+ struct ofp_aggregate_stats_request *rq = &s->rq;
+ struct ofp_aggregate_stats_reply *rpy;
+ struct sw_table_position position;
+ struct sw_flow_key match_key;
+ int table_idx;
+
+ rpy = buffer_put_uninit(buffer, sizeof *rpy);
+ memset(rpy, 0, sizeof *rpy);
+
+ flow_extract_match(&match_key, &rq->match);
+ table_idx = rq->table_id == 0xff ? 0 : rq->table_id;
+ memset(&position, 0, sizeof position);
+ while (table_idx < dp->chain->n_tables
+ && (rq->table_id == 0xff || rq->table_id == table_idx))
+ {
+ struct sw_table *table = dp->chain->tables[table_idx];
+ int error;
+
+ error = table->iterate(table, &match_key, &position,
+ aggregate_stats_dump_callback, rpy);
+ if (error)
+ return error;
+
+ table_idx++;
+ memset(&position, 0, sizeof position);
+ }
+
+ rpy->packet_count = htonll(rpy->packet_count);
+ rpy->byte_count = htonll(rpy->byte_count);
+ rpy->flow_count = htonl(rpy->flow_count);
+ return 0;
+}
+
+static void aggregate_stats_done(void *state)
+{
+ free(state);
+}
+
+static int table_stats_dump(struct datapath *dp, void *state,
+ struct buffer *buffer)
+{
+ int i;
+ 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 = i;
+ memset(ots->pad, 0, sizeof ots->pad);
+ ots->max_entries = htonl(stats.max_flows);
+ ots->active_count = htonl(stats.n_flows);
+ ots->matched_count = htonll(stats.n_matched);
+ }
+ return 0;
+}
+
+struct port_stats_state {
+ int port;
+};
+
+static int port_stats_init(struct datapath *dp, const void *body, int body_len,
+ void **state)
+{
+ struct port_stats_state *s = xmalloc(sizeof *s);
+ s->port = 0;
+ *state = s;
+ return 0;
+}
+
+static int port_stats_dump(struct datapath *dp, void *state,
+ struct buffer *buffer)
+{
+ struct port_stats_state *s = state;
+ int i;
+
+ for (i = s->port; i < OFPP_MAX; i++) {
+ struct sw_port *p = &dp->ports[i];
+ struct ofp_port_stats *ops;
+ if (!p->netdev) {
+ continue;
+ }
+ ops = buffer_put_uninit(buffer, sizeof *ops);
+ ops->port_no = htons(port_no(dp, p));
+ memset(ops->pad, 0, sizeof ops->pad);
+ ops->rx_packets = htonll(p->rx_packets);
+ ops->tx_packets = htonll(p->tx_packets);
+ ops->rx_bytes = htonll(p->rx_bytes);
+ ops->tx_bytes = htonll(p->tx_bytes);
+ ops->rx_dropped = htonll(-1);
+ ops->tx_dropped = htonll(p->tx_dropped);
+ ops->rx_errors = htonll(-1);
+ ops->tx_errors = htonll(-1);
+ ops->rx_frame_err = htonll(-1);
+ ops->rx_over_err = htonll(-1);
+ ops->rx_crc_err = htonll(-1);
+ ops->collisions = htonll(-1);
+ ops++;
+ }
+ s->port = i;
+ return 0;
+}
+
+static void port_stats_done(void *state)
+{
+ free(state);
+}
+
+struct stats_type {
+ /* Minimum and maximum acceptable number of bytes in body member of
+ * struct ofp_stats_request. */
+ size_t min_body, max_body;
+
+ /* Prepares to dump some kind of statistics on 'dp'. 'body' and
+ * 'body_len' are the 'body' member of the struct ofp_stats_request.
+ * Returns zero if successful, otherwise a negative error code.
+ * May initialize '*state' to state information. May be null if no
+ * initialization is required.*/
+ int (*init)(struct datapath *dp, const void *body, int body_len,
+ void **state);
+
+ /* Appends statistics for 'dp' to 'buffer', which initially contains a
+ * struct ofp_stats_reply. On success, it should return 1 if it should be
+ * called again later with another buffer, 0 if it is done, or a negative
+ * errno value on failure. */
+ int (*dump)(struct datapath *dp, void *state, struct buffer *buffer);
+
+ /* Cleans any state created by the init or dump functions. May be null
+ * if no cleanup is required. */
+ void (*done)(void *state);
+};
+
+static const struct stats_type stats[] = {
+ [OFPST_DESC] = {
+ 0,
+ 0,
+ NULL,
+ desc_stats_dump,
+ NULL
+ },
+ [OFPST_FLOW] = {
+ sizeof(struct ofp_flow_stats_request),
+ sizeof(struct ofp_flow_stats_request),
+ flow_stats_init,
+ flow_stats_dump,
+ flow_stats_done
+ },
+ [OFPST_AGGREGATE] = {
+ sizeof(struct ofp_aggregate_stats_request),
+ sizeof(struct ofp_aggregate_stats_request),
+ aggregate_stats_init,
+ aggregate_stats_dump,
+ aggregate_stats_done
+ },
+ [OFPST_TABLE] = {
+ 0,
+ 0,
+ NULL,
+ table_stats_dump,
+ NULL
+ },
+ [OFPST_PORT] = {
+ 0,
+ 0,
+ port_stats_init,
+ port_stats_dump,
+ port_stats_done
+ },
+};
+
+struct stats_dump_cb {
+ bool done;
+ struct ofp_stats_request *rq;
+ struct sender sender;
+ const struct stats_type *s;
+ void *state;
+};
+
+static int
+stats_dump(struct datapath *dp, void *cb_)
+{
+ struct stats_dump_cb *cb = cb_;
+ struct ofp_stats_reply *osr;
+ struct buffer *buffer;
+ int err;
+
+ if (cb->done) {
+ return 0;
+ }
+
+ osr = make_openflow_reply(sizeof *osr, OFPT_STATS_REPLY, &cb->sender,
+ &buffer);
+ osr->type = htons(cb->s - stats);
+ osr->flags = 0;
+
+ err = cb->s->dump(dp, cb->state, buffer);
+ if (err >= 0) {
+ int err2;
+ if (!err) {
+ cb->done = true;
+ } else {
+ /* Buffer might have been reallocated, so find our data again. */
+ osr = buffer_at_assert(buffer, 0, sizeof *osr);
+ osr->flags = ntohs(OFPSF_REPLY_MORE);
+ }
+ err2 = send_openflow_buffer(dp, buffer, &cb->sender);
+ if (err2) {
+ err = err2;
+ }
+ }
+
+ return err;
+}
+
+static void
+stats_done(void *cb_)
+{
+ struct stats_dump_cb *cb = cb_;
+ if (cb) {
+ if (cb->s->done) {
+ cb->s->done(cb->state);
+ }
+ free(cb);
+ }
+}
+
+static int
+recv_stats_request(struct datapath *dp, const struct sender *sender,
+ const void *oh)
+{
+ const struct ofp_stats_request *rq = oh;
+ size_t rq_len = ntohs(rq->header.length);
+ struct stats_dump_cb *cb;
+ int type, body_len;
+ int err;
+
+ type = ntohs(rq->type);
+ if (type >= ARRAY_SIZE(stats) || !stats[type].dump) {
+ VLOG_WARN_RL(&rl, "received stats request of unknown type %d", type);
+ return -EINVAL;
+ }
+
+ cb = xmalloc(sizeof *cb);
+ cb->done = false;
+ cb->rq = xmemdup(rq, rq_len);
+ cb->sender = *sender;
+ cb->s = &stats[type];
+ cb->state = NULL;
+
+ body_len = rq_len - offsetof(struct ofp_stats_request, body);
+ if (body_len < cb->s->min_body || body_len > cb->s->max_body) {
+ VLOG_WARN_RL(&rl, "stats request type %d with bad body length %d",
+ type, body_len);
+ err = -EINVAL;
+ goto error;
+ }
+
+ if (cb->s->init) {
+ err = cb->s->init(dp, rq->body, body_len, &cb->state);
+ if (err) {
+ VLOG_WARN_RL(&rl,
+ "failed initialization of stats request type %d: %s",
+ type, strerror(-err));
+ goto error;
+ }
+ }
+
+ remote_start_dump(sender->remote, stats_dump, stats_done, cb);
+ return 0;
+
+error:
+ free(cb->rq);
+ free(cb);
+ return err;
+}
+
+static int
+recv_echo_request(struct datapath *dp, const struct sender *sender,
+ const void *oh)
+{
+ return send_openflow_buffer(dp, make_echo_reply(oh), sender);
+}
+
+static int
+recv_echo_reply(struct datapath *dp UNUSED, const struct sender *sender UNUSED,
+ const void *oh UNUSED)
+{
+ return 0;
+}
+
+/* 'msg', which is 'length' bytes long, was received from the control path.
+ * Apply it to 'chain'. */
+int
+fwd_control_input(struct datapath *dp, const struct sender *sender,
+ const void *msg, size_t length)
+{
+ struct openflow_packet {
+ size_t min_size;
+ int (*handler)(struct datapath *, const struct sender *, const void *);
+ };
+
+ static const struct openflow_packet packets[] = {
+ [OFPT_FEATURES_REQUEST] = {
+ sizeof (struct ofp_header),
+ recv_features_request,
+ },
+ [OFPT_GET_CONFIG_REQUEST] = {
+ sizeof (struct ofp_header),
+ recv_get_config_request,
+ },
+ [OFPT_SET_CONFIG] = {
+ sizeof (struct ofp_switch_config),
+ recv_set_config,
+ },
+ [OFPT_PACKET_OUT] = {
+ sizeof (struct ofp_packet_out),
+ recv_packet_out,
+ },
+ [OFPT_FLOW_MOD] = {
+ sizeof (struct ofp_flow_mod),
+ recv_flow,
+ },
+ [OFPT_PORT_MOD] = {
+ sizeof (struct ofp_port_mod),
+ recv_port_mod,
+ },
+ [OFPT_STATS_REQUEST] = {
+ sizeof (struct ofp_stats_request),
+ recv_stats_request,
+ },
+ [OFPT_ECHO_REQUEST] = {
+ sizeof (struct ofp_header),
+ recv_echo_request,
+ },
+ [OFPT_ECHO_REPLY] = {
+ sizeof (struct ofp_header),
+ recv_echo_reply,
+ },
+ };
+
+ const struct openflow_packet *pkt;
+ struct ofp_header *oh;
+
+ oh = (struct ofp_header *) msg;
+ assert(oh->version == OFP_VERSION);
+ if (oh->type >= ARRAY_SIZE(packets) || ntohs(oh->length) > length)
+ return -EINVAL;
+
+ pkt = &packets[oh->type];
+ if (!pkt->handler)
+ return -ENOSYS;
+ if (length < pkt->min_size)
+ return -EFAULT;
+
+ return pkt->handler(dp, sender, msg);
+}
+\f
+/* Packet buffering. */
+
+#define OVERWRITE_SECS 1
+
+struct packet_buffer {
+ struct buffer *buffer;
+ uint32_t cookie;
+ time_t timeout;
+};
+
+static struct packet_buffer buffers[N_PKT_BUFFERS];
+static unsigned int buffer_idx;
+
+uint32_t save_buffer(struct buffer *buffer)
+{
+ struct packet_buffer *p;
+ uint32_t id;
+
+ buffer_idx = (buffer_idx + 1) & PKT_BUFFER_MASK;
+ p = &buffers[buffer_idx];
+ if (p->buffer) {
+ /* Don't buffer packet if existing entry is less than
+ * OVERWRITE_SECS old. */
+ if (time_now() < p->timeout) { /* FIXME */
+ return -1;
+ } else {
+ buffer_delete(p->buffer);
+ }
+ }
+ /* Don't use maximum cookie value since the all-bits-1 id is
+ * special. */
+ if (++p->cookie >= (1u << PKT_COOKIE_BITS) - 1)
+ p->cookie = 0;
+ p->buffer = buffer_clone(buffer); /* FIXME */
+ p->timeout = time_now() + OVERWRITE_SECS; /* FIXME */
+ id = buffer_idx | (p->cookie << PKT_BUFFER_BITS);
+
+ return id;
+}
+
+static struct buffer *retrieve_buffer(uint32_t id)
+{
+ struct buffer *buffer = NULL;
+ struct packet_buffer *p;
+
+ p = &buffers[id & PKT_BUFFER_MASK];
+ if (p->cookie == id >> PKT_BUFFER_BITS) {
+ buffer = p->buffer;
+ p->buffer = NULL;
+ } else {
+ printf("cookie mismatch: %x != %x\n",
+ id >> PKT_BUFFER_BITS, p->cookie);
+ }
+
+ return buffer;
+}
+
+static void discard_buffer(uint32_t id)
+{
+ struct packet_buffer *p;
+
+ p = &buffers[id & PKT_BUFFER_MASK];
+ if (p->cookie == id >> PKT_BUFFER_BITS) {
+ buffer_delete(p->buffer);
+ p->buffer = NULL;
+ }