X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=lib%2Flearning-switch.c;h=842af37d0311e05ee456bb366ef7e809a3f8810a;hb=cd13e4cbc5f056a83e1e1542e0b34c0eb0ec08d1;hp=5768fd4d6f61a15c38048a26c605d0017c343029;hpb=7b351ea0762fc73e1e92059e5f16b8269db44bd2;p=sliver-openvswitch.git diff --git a/lib/learning-switch.c b/lib/learning-switch.c index 5768fd4d6..842af37d0 100644 --- a/lib/learning-switch.c +++ b/lib/learning-switch.c @@ -31,6 +31,7 @@ * derivatives without specific, written prior permission. */ +#include #include "learning-switch.h" #include @@ -39,13 +40,14 @@ #include #include -#include "buffer.h" #include "flow.h" #include "mac-learning.h" +#include "ofpbuf.h" #include "ofp-print.h" #include "openflow.h" #include "queue.h" #include "rconn.h" +#include "timeval.h" #include "vconn.h" #include "xtoxll.h" @@ -53,34 +55,54 @@ #include "vlog.h" struct lswitch { - bool setup_flows; /* Set up flows? (or controller processes all packets) */ + /* If nonnegative, the switch sets up flows that expire after the given + * number of seconds (or never expire, if the value is OFP_FLOW_PERMANENT). + * Otherwise, the switch processes every packet. */ + int max_idle; + uint64_t datapath_id; + uint32_t capabilities; time_t last_features_request; struct mac_learning *ml; /* NULL to act as hub instead of switch. */ + + /* Number of outgoing queued packets on the rconn. */ + int n_queued; }; -static void queue_tx(struct lswitch *, struct rconn *, struct buffer *); +/* The log messages here could actually be useful in debugging, so keep the + * rate limit relatively high. */ +static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(30, 300); + +static void queue_tx(struct lswitch *, struct rconn *, struct ofpbuf *); static void send_features_request(struct lswitch *, struct rconn *); +static void process_switch_features(struct lswitch *, struct rconn *, + struct ofp_switch_features *); static void process_packet_in(struct lswitch *, struct rconn *, struct ofp_packet_in *); +static void process_echo_request(struct lswitch *, struct rconn *, + struct ofp_header *); +static void process_port_status(struct lswitch *, struct rconn *, + struct ofp_port_status *); +static void process_phy_port(struct lswitch *, struct rconn *, + const struct ofp_phy_port *); /* Creates and returns a new learning switch. * * If 'learn_macs' is true, the new switch will learn the ports on which MAC * addresses appear. Otherwise, the new switch will flood all packets. * - * If 'setup_flows' is true, the new switch will set up flows. Otherwise, the - * new switch will process every packet. + * If 'max_idle' is nonnegative, the new switch will set up flows that expire + * after the given number of seconds (or never expire, if 'max_idle' is + * OFP_FLOW_PERMANENT). Otherwise, the new switch will process every packet. * * 'rconn' is used to send out an OpenFlow features request. */ struct lswitch * -lswitch_create(struct rconn *rconn, bool learn_macs, bool setup_flows) +lswitch_create(struct rconn *rconn, bool learn_macs, int max_idle) { - struct lswitch *sw = xmalloc(sizeof *sw); - memset(sw, 0, sizeof *sw); - sw->setup_flows = setup_flows; + struct lswitch *sw = xcalloc(1, sizeof *sw); + sw->max_idle = max_idle; sw->datapath_id = 0; - sw->last_features_request = 0; + sw->last_features_request = time_now() - 1; sw->ml = learn_macs ? mac_learning_create() : NULL; send_features_request(sw, rconn); return sw; @@ -96,40 +118,48 @@ lswitch_destroy(struct lswitch *sw) } } +static size_t +min_size(uint8_t type) +{ + return (type == OFPT_FEATURES_REPLY ? sizeof(struct ofp_switch_features) + : type == OFPT_PACKET_IN ? offsetof (struct ofp_packet_in, data) + : type == OFPT_PORT_STATUS ? sizeof(struct ofp_port_status) + : sizeof(struct ofp_header)); +} + /* Processes 'msg', which should be an OpenFlow received on 'rconn', according * to the learning switch state in 'sw'. The most likely result of processing * is that flow-setup and packet-out OpenFlow messages will be sent out on * 'rconn'. */ void lswitch_process_packet(struct lswitch *sw, struct rconn *rconn, - const struct buffer *msg) + const struct ofpbuf *msg) { - static const size_t min_size[UINT8_MAX + 1] = { - [0 ... UINT8_MAX] = sizeof (struct ofp_header), - [OFPT_FEATURES_REPLY] = sizeof (struct ofp_switch_features), - [OFPT_PACKET_IN] = offsetof (struct ofp_packet_in, data), - }; struct ofp_header *oh; oh = msg->data; - if (msg->size < min_size[oh->type]) { - VLOG_WARN("%s: too short (%zu bytes) for type %"PRIu8" (min %zu)", - rconn_get_name(rconn), - msg->size, oh->type, min_size[oh->type]); + if (msg->size < min_size(oh->type)) { + VLOG_WARN_RL(&rl, + "%s: too short (%zu bytes) for type %"PRIu8" (min %zu)", + rconn_get_name(rconn), + msg->size, oh->type, min_size(oh->type)); return; } - if (oh->type == OFPT_FEATURES_REPLY) { - struct ofp_switch_features *osf = msg->data; - sw->datapath_id = osf->datapath_id; + if (oh->type == OFPT_ECHO_REQUEST) { + process_echo_request(sw, rconn, msg->data); + } else if (oh->type == OFPT_FEATURES_REPLY) { + process_switch_features(sw, rconn, msg->data); } else if (sw->datapath_id == 0) { send_features_request(sw, rconn); } else if (oh->type == OFPT_PACKET_IN) { process_packet_in(sw, rconn, msg->data); + } else if (oh->type == OFPT_PORT_STATUS) { + process_port_status(sw, rconn, msg->data); } else { if (VLOG_IS_DBG_ENABLED()) { char *p = ofp_to_string(msg->data, msg->size, 2); - VLOG_DBG("OpenFlow packet ignored: %s", p); + VLOG_DBG_RL(&rl, "OpenFlow packet ignored: %s", p); free(p); } } @@ -138,28 +168,17 @@ lswitch_process_packet(struct lswitch *sw, struct rconn *rconn, static void send_features_request(struct lswitch *sw, struct rconn *rconn) { - time_t now = time(0); + time_t now = time_now(); if (now >= sw->last_features_request + 1) { - struct buffer *b; - struct ofp_header *ofr; + struct ofpbuf *b; struct ofp_switch_config *osc; /* Send OFPT_FEATURES_REQUEST. */ - b = buffer_new(0); - ofr = buffer_put_uninit(b, sizeof *ofr); - memset(ofr, 0, sizeof *ofr); - ofr->type = OFPT_FEATURES_REQUEST; - ofr->version = OFP_VERSION; - ofr->length = htons(sizeof *ofr); + make_openflow(sizeof(struct ofp_header), OFPT_FEATURES_REQUEST, &b); queue_tx(sw, rconn, b); /* Send OFPT_SET_CONFIG. */ - b = buffer_new(0); - osc = buffer_put_uninit(b, sizeof *osc); - memset(osc, 0, sizeof *osc); - osc->header.type = OFPT_SET_CONFIG; - osc->header.version = OFP_VERSION; - osc->header.length = htons(sizeof *osc); + osc = make_openflow(sizeof *osc, OFPT_SET_CONFIG, &b); osc->flags = htons(OFPC_SEND_FLOW_EXP); osc->miss_send_len = htons(OFP_DEFAULT_MISS_SEND_LEN); queue_tx(sw, rconn, b); @@ -169,20 +188,32 @@ send_features_request(struct lswitch *sw, struct rconn *rconn) } static void -queue_tx(struct lswitch *sw, struct rconn *rconn, struct buffer *b) +queue_tx(struct lswitch *sw, struct rconn *rconn, struct ofpbuf *b) { - int retval = rconn_send(rconn, b); - if (retval) { + int retval = rconn_send_with_limit(rconn, b, &sw->n_queued, 10); + if (retval && retval != ENOTCONN) { if (retval == EAGAIN) { - /* FIXME: ratelimit. */ - VLOG_WARN("%s: tx queue overflow", rconn_get_name(rconn)); - } else if (retval == ENOTCONN) { - /* Ignore. */ + VLOG_WARN_RL(&rl, "%s: tx queue overflow", rconn_get_name(rconn)); } else { - /* FIXME: ratelimit. */ - VLOG_WARN("%s: send: %s", rconn_get_name(rconn), strerror(retval)); + VLOG_WARN_RL(&rl, "%s: send: %s", + rconn_get_name(rconn), strerror(retval)); } - buffer_delete(b); + } +} + +static void +process_switch_features(struct lswitch *sw, struct rconn *rconn, + struct ofp_switch_features *osf) +{ + size_t n_ports = ((ntohs(osf->header.length) + - offsetof(struct ofp_switch_features, ports)) + / sizeof *osf->ports); + size_t i; + + sw->datapath_id = osf->datapath_id; + sw->capabilities = ntohl(osf->capabilities); + for (i = 0; i < n_ports; i++) { + process_phy_port(sw, rconn, &osf->ports[i]); } } @@ -194,7 +225,7 @@ process_packet_in(struct lswitch *sw, struct rconn *rconn, uint16_t out_port = OFPP_FLOOD; size_t pkt_ofs, pkt_len; - struct buffer pkt; + struct ofpbuf pkt; struct flow flow; /* Extract flow data from 'opi' into 'flow'. */ @@ -206,18 +237,22 @@ process_packet_in(struct lswitch *sw, struct rconn *rconn, if (sw->ml) { if (mac_learning_learn(sw->ml, flow.dl_src, in_port)) { - VLOG_DBG("learned that "ETH_ADDR_FMT" is on datapath %" - PRIx64" port %"PRIu16, ETH_ADDR_ARGS(flow.dl_src), - ntohll(sw->datapath_id), in_port); + VLOG_DBG_RL(&rl, "learned that "ETH_ADDR_FMT" is on datapath %" + PRIx64" port %"PRIu16, ETH_ADDR_ARGS(flow.dl_src), + ntohll(sw->datapath_id), in_port); } out_port = mac_learning_lookup(sw->ml, flow.dl_dst); } - if (sw->setup_flows && (!sw->ml || out_port != OFPP_FLOOD)) { + if (in_port == out_port) { + /* The input and output port match. Set up a flow to drop packets. */ + queue_tx(sw, rconn, make_add_flow(&flow, ntohl(opi->buffer_id), + sw->max_idle, 0)); + } else if (sw->max_idle >= 0 && (!sw->ml || out_port != OFPP_FLOOD)) { /* The output port is known, or we always flood everything, so add a * new flow. */ queue_tx(sw, rconn, make_add_simple_flow(&flow, ntohl(opi->buffer_id), - out_port)); + out_port, sw->max_idle)); /* If the switch didn't buffer the packet, we need to send a copy. */ if (ntohl(opi->buffer_id) == UINT32_MAX) { @@ -227,7 +262,7 @@ process_packet_in(struct lswitch *sw, struct rconn *rconn, } else { /* We don't know that MAC, or we don't set up flows. Send along the * packet without setting up a flow. */ - struct buffer *b; + struct ofpbuf *b; if (ntohl(opi->buffer_id) == UINT32_MAX) { b = make_unbuffered_packet_out(&pkt, in_port, out_port); } else { @@ -237,3 +272,73 @@ process_packet_in(struct lswitch *sw, struct rconn *rconn, queue_tx(sw, rconn, b); } } + +static void +process_echo_request(struct lswitch *sw, struct rconn *rconn, + struct ofp_header *rq) +{ + queue_tx(sw, rconn, make_echo_reply(rq)); +} + +static void +process_port_status(struct lswitch *sw, struct rconn *rconn, + struct ofp_port_status *ops) +{ + process_phy_port(sw, rconn, &ops->desc); +} + +static void +process_phy_port(struct lswitch *sw, struct rconn *rconn, + const struct ofp_phy_port *opp) +{ + if (sw->capabilities & OFPC_STP && ntohs(opp->port_no) < OFPP_MAX) { + uint32_t config = ntohl(opp->config); + uint32_t state = ntohl(opp->state); + uint32_t new_config = config & ~(OFPPC_NO_RECV | OFPPC_NO_RECV_STP + | OFPPC_NO_FWD | OFPPC_NO_PACKET_IN); + if (!(config & (OFPPC_NO_STP | OFPPC_PORT_DOWN)) + && !(state & OFPPS_LINK_DOWN)) { + bool forward = false; + bool learn = false; + switch (state & OFPPS_STP_MASK) { + case OFPPS_STP_LISTEN: + case OFPPS_STP_BLOCK: + break; + case OFPPS_STP_LEARN: + learn = true; + break; + case OFPPS_STP_FORWARD: + forward = learn = true; + break; + } + if (!forward) { + new_config |= OFPPC_NO_RECV | OFPPC_NO_FWD; + } + if (!learn) { + new_config |= OFPPC_NO_PACKET_IN; + } + } + if (config != new_config) { + struct ofp_port_mod *opm; + struct ofpbuf *b; + int retval; + + VLOG_WARN("port %d: config=%x new_config=%x", + ntohs(opp->port_no), config, new_config); + opm = make_openflow(sizeof *opm, OFPT_PORT_MOD, &b); + opm->port_no = opp->port_no; + memcpy(opm->hw_addr, opp->hw_addr, OFP_ETH_ALEN); + opm->config = htonl(new_config); + opm->mask = htonl(config ^ new_config); + opm->advertise = htonl(0); + retval = rconn_send(rconn, b, NULL); + if (retval) { + if (retval != ENOTCONN) { + VLOG_WARN_RL(&rl, "%s: send: %s", + rconn_get_name(rconn), strerror(retval)); + } + ofpbuf_delete(b); + } + } + } +}