+
+static void
+process_port_status(struct lswitch *sw, struct rconn *rconn, void *ops_)
+{
+ struct ofp_port_status *ops = ops_;
+ process_phy_port(sw, rconn, &ops->desc);
+}
+
+static void
+process_phy_port(struct lswitch *sw, struct rconn *rconn, void *opp_)
+{
+ const struct ofp_phy_port *opp = opp_;
+ uint16_t port_no = ntohs(opp->port_no);
+ if (sw->capabilities & OFPC_STP && port_no < STP_MAX_PORTS) {
+ uint32_t config = ntohl(opp->config);
+ uint32_t state = ntohl(opp->state);
+ unsigned int *port_state = &sw->port_states[port_no];
+ unsigned int new_port_state;
+
+ if (!(config & (OFPPC_NO_STP | OFPPC_PORT_DOWN))
+ && !(state & OFPPS_LINK_DOWN))
+ {
+ switch (state & OFPPS_STP_MASK) {
+ case OFPPS_STP_LISTEN:
+ new_port_state = P_LISTENING;
+ break;
+ case OFPPS_STP_LEARN:
+ new_port_state = P_LEARNING;
+ break;
+ case OFPPS_STP_FORWARD:
+ new_port_state = P_FORWARDING;
+ break;
+ case OFPPS_STP_BLOCK:
+ new_port_state = P_BLOCKING;
+ break;
+ default:
+ new_port_state = P_DISABLED;
+ break;
+ }
+ } else {
+ new_port_state = P_FORWARDING;
+ }
+ if (*port_state != new_port_state) {
+ *port_state = new_port_state;
+ schedule_query(sw, 1000);
+ }
+ }
+}
+
+static unsigned int
+get_port_state(const struct lswitch *sw, uint16_t port_no)
+{
+ return (port_no >= STP_MAX_PORTS || !(sw->capabilities & OFPC_STP)
+ ? P_FORWARDING
+ : sw->port_states[port_no]);
+}
+
+static bool
+may_learn(const struct lswitch *sw, uint16_t port_no)
+{
+ return get_port_state(sw, port_no) & (P_LEARNING | P_FORWARDING);
+}
+
+static bool
+may_recv(const struct lswitch *sw, uint16_t port_no, bool any_actions)
+{
+ unsigned int state = get_port_state(sw, port_no);
+ return !(any_actions
+ ? state & (P_DISABLED | P_LISTENING | P_BLOCKING)
+ : state & (P_DISABLED | P_LISTENING | P_BLOCKING | P_LEARNING));
+}
+
+static bool
+may_send(const struct lswitch *sw, uint16_t port_no)
+{
+ return get_port_state(sw, port_no) & P_FORWARDING;
+}
+
+static void
+process_flow_stats(struct lswitch *sw, struct rconn *rconn,
+ const struct ofp_flow_stats *ofs)
+{
+ const char *end = (char *) ofs + ntohs(ofs->length);
+ bool delete = false;
+
+ /* Decide to delete the flow if it matches on an STP-disabled physical
+ * port. But don't delete it if the flow just drops all received packets,
+ * because that's a perfectly reasonable thing to do for disabled physical
+ * ports. */
+ if (!(ofs->match.wildcards & htonl(OFPFW_IN_PORT))) {
+ if (!may_recv(sw, ntohs(ofs->match.in_port),
+ end > (char *) ofs->actions)) {
+ delete = true;
+ sw->n_no_recv++;
+ }
+ }
+
+ /* Decide to delete the flow if it forwards to an STP-disabled physical
+ * port. */
+ if (!delete) {
+ const struct ofp_action_header *a;
+ size_t len;
+
+ for (a = ofs->actions; (char *) a < end; a += len / 8) {
+ uint16_t type;
+
+ len = ntohs(a->len);
+ if (len > end - (char *) a) {
+ VLOG_DBG_RL(&rl, "%012llx: action exceeds available space "
+ "(%zu > %td)",
+ sw->datapath_id, len, end - (char *) a);
+ break;
+ } else if (len % 8) {
+ VLOG_DBG_RL(&rl, "%012llx: action length (%zu) not multiple "
+ "of 8 bytes", sw->datapath_id, len);
+ break;
+ }
+
+ type = ntohs(a->type);
+ if (a->type == htons(OFPAT_OUTPUT)) {
+ struct ofp_action_output *oao = (struct ofp_action_output *) a;
+ if (!may_send(sw, ntohs(oao->port))) {
+ delete = true;
+ sw->n_no_send++;
+ break;
+ }
+ }
+ }
+ }
+
+ /* Delete the flow. */
+ if (delete) {
+ struct ofp_flow_mod *ofm;
+ struct ofpbuf *b;
+
+ ofm = make_openflow(offsetof(struct ofp_flow_mod, actions),
+ OFPT_FLOW_MOD, &b);
+ ofm->match = ofs->match;
+ ofm->command = OFPFC_DELETE_STRICT;
+ rconn_send(rconn, b, NULL);
+ }
+}
+
+static void
+process_stats_reply(struct lswitch *sw, struct rconn *rconn, void *osr_)
+{
+ struct ofp_stats_reply *osr = osr_;
+ const uint8_t *body = osr->body;
+ const uint8_t *pos = body;
+ size_t body_len;
+
+ if (sw->last_query == LLONG_MIN
+ || osr->type != htons(OFPST_FLOW)
+ || osr->header.xid != sw->query_xid) {
+ return;
+ }
+ body_len = (ntohs(osr->header.length)
+ - offsetof(struct ofp_stats_reply, body));
+ for (;;) {
+ const struct ofp_flow_stats *fs;
+ ptrdiff_t bytes_left = body + body_len - pos;
+ size_t length;
+
+ if (bytes_left < sizeof *fs) {
+ if (bytes_left != 0) {
+ VLOG_WARN_RL(&rl, "%012llx: %td leftover bytes in flow "
+ "stats reply", sw->datapath_id, bytes_left);
+ }
+ break;
+ }
+
+ fs = (const void *) pos;
+ length = ntohs(fs->length);
+ if (length < sizeof *fs) {
+ VLOG_WARN_RL(&rl, "%012llx: flow stats length %zu is shorter than "
+ "min %zu", sw->datapath_id, length, sizeof *fs);
+ break;
+ } else if (length > bytes_left) {
+ VLOG_WARN_RL(&rl, "%012llx: flow stats length %zu but only %td "
+ "bytes left", sw->datapath_id, length, bytes_left);
+ break;
+ } else if ((length - sizeof *fs) % sizeof fs->actions[0]) {
+ VLOG_WARN_RL(&rl, "%012llx: flow stats length %zu has %zu bytes "
+ "left over in final action", sw->datapath_id, length,
+ (length - sizeof *fs) % sizeof fs->actions[0]);
+ break;
+ }
+
+ sw->n_flows++;
+ process_flow_stats(sw, rconn, fs);
+
+ pos += length;
+ }
+ if (!(osr->flags & htons(OFPSF_REPLY_MORE))) {
+ VLOG_DBG("%012llx: Deleted %d of %d received flows to "
+ "implement STP, %d because of no-recv, %d because of "
+ "no-send", sw->datapath_id,
+ sw->n_no_recv + sw->n_no_send, sw->n_flows,
+ sw->n_no_recv, sw->n_no_send);
+ sw->last_query = LLONG_MIN;
+ sw->last_reply = LLONG_MIN;
+ } else {
+ sw->last_reply = time_msec();
+ }
+}
+