From: Ben Pfaff Date: Mon, 15 Sep 2008 18:19:23 +0000 (-0700) Subject: Implement OFPT_HELLO simple version negotiation. X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9c1b16772cfd8d346be89963ddf6a6d6a87f0cc9;p=openvswitch Implement OFPT_HELLO simple version negotiation. --- diff --git a/controller/controller.c b/controller/controller.c index 48420c86..e894cf41 100644 --- a/controller/controller.c +++ b/controller/controller.c @@ -111,7 +111,7 @@ main(int argc, char *argv[]) struct vconn *vconn; int retval; - retval = vconn_open(name, &vconn); + retval = vconn_open(name, OFP_VERSION, &vconn); if (!retval) { if (n_switches >= MAX_SWITCHES) { fatal(0, "max %d switch connections", n_switches); @@ -128,7 +128,9 @@ main(int argc, char *argv[]) listeners[n_listeners++] = pvconn; } } - VLOG_ERR("%s: connect: %s", name, strerror(retval)); + if (retval) { + VLOG_ERR("%s: connect: %s", name, strerror(retval)); + } } if (n_switches == 0 && n_listeners == 0) { fatal(0, "no active or passive switch connections"); @@ -146,7 +148,7 @@ main(int argc, char *argv[]) struct vconn *new_vconn; int retval; - retval = pvconn_accept(listeners[i], &new_vconn); + retval = pvconn_accept(listeners[i], OFP_VERSION, &new_vconn); if (!retval || retval == EAGAIN) { if (!retval) { new_switch(&switches[n_switches++], new_vconn, "tcp"); diff --git a/datapath/datapath.c b/datapath/datapath.c index 428f25e5..8eb06601 100644 --- a/datapath/datapath.c +++ b/datapath/datapath.c @@ -798,6 +798,29 @@ dp_send_config_reply(struct datapath *dp, const struct sender *sender) return send_openflow_skb(skb, sender); } +int +dp_send_hello(struct datapath *dp, const struct sender *sender, + const struct ofp_header *request) +{ + if (request->version < OFP_VERSION) { + char err[64]; + sprintf(err, "Only version 0x%02x supported", OFP_VERSION); + dp_send_error_msg(dp, sender, OFPET_HELLO_FAILED, + OFPHFC_INCOMPATIBLE, err, strlen(err)); + return -EINVAL; + } else { + struct sk_buff *skb; + struct ofp_header *reply; + + reply = alloc_openflow_skb(dp, sizeof *reply, + OFPT_HELLO, sender, &skb); + if (!reply) + return -ENOMEM; + + return send_openflow_skb(skb, sender); + } +} + /* Callback function for a workqueue to disable an interface */ static void down_port_cb(struct work_struct *work) @@ -953,7 +976,7 @@ dp_send_error_msg(struct datapath *dp, const struct sender *sender, struct ofp_error_msg *oem; - oem = alloc_openflow_skb(dp, sizeof(*oem)+len, OFPT_ERROR_MSG, + oem = alloc_openflow_skb(dp, sizeof(*oem)+len, OFPT_ERROR, sender, &skb); if (!oem) return -ENOMEM; diff --git a/datapath/datapath.h b/datapath/datapath.h index 1e1f2bf5..0b26b4a0 100644 --- a/datapath/datapath.h +++ b/datapath/datapath.h @@ -100,6 +100,8 @@ int dp_send_error_msg(struct datapath *, const struct sender *, int dp_update_port_flags(struct datapath *dp, const struct ofp_port_mod *opm); int dp_send_echo_reply(struct datapath *, const struct sender *, const struct ofp_header *); +int dp_send_hello(struct datapath *, const struct sender *, + const struct ofp_header *); /* Should hold at least RCU read lock when calling */ struct datapath *dp_get(int dp_idx); diff --git a/datapath/forward.c b/datapath/forward.c index 5d57fb16..43346553 100644 --- a/datapath/forward.c +++ b/datapath/forward.c @@ -300,6 +300,13 @@ struct sk_buff *execute_setter(struct sk_buff *skb, uint16_t eth_proto, return skb; } +static int +recv_hello(struct sw_chain *chain, const struct sender *sender, + const void *msg) +{ + return dp_send_hello(chain->dp, sender, msg); +} + static int recv_features_request(struct sw_chain *chain, const struct sender *sender, const void *msg) @@ -517,6 +524,10 @@ fwd_control_input(struct sw_chain *chain, const struct sender *sender, }; static const struct openflow_packet packets[] = { + [OFPT_HELLO] = { + sizeof (struct ofp_header), + recv_hello, + }, [OFPT_FEATURES_REQUEST] = { sizeof (struct ofp_header), recv_features_request, @@ -554,7 +565,13 @@ fwd_control_input(struct sw_chain *chain, const struct sender *sender, struct ofp_header *oh; oh = (struct ofp_header *) msg; - if (oh->version != OFP_VERSION) { + if (oh->version != OFP_VERSION + && oh->type != OFPT_HELLO + && oh->type != OFPT_ERROR + && oh->type != OFPT_ECHO_REQUEST + && oh->type != OFPT_ECHO_REPLY + && oh->type != OFPT_VENDOR) + { dp_send_error_msg(chain->dp, sender, OFPET_BAD_REQUEST, OFPBRC_BAD_VERSION, msg, length); return -EINVAL; diff --git a/include/openflow.h b/include/openflow.h index 6f38ec8b..77dbc52a 100644 --- a/include/openflow.h +++ b/include/openflow.h @@ -63,7 +63,7 @@ /* The most significant bit being set in the version field indicates an * experimental OpenFlow version. */ -#define OFP_VERSION 0x90 +#define OFP_VERSION 0x91 #define OFP_MAX_TABLE_NAME_LEN 32 #define OFP_MAX_PORT_NAME_LEN 16 @@ -96,24 +96,34 @@ enum ofp_port { }; enum ofp_type { - OFPT_FEATURES_REQUEST, /* 0 Controller/switch message */ - OFPT_FEATURES_REPLY, /* 1 Controller/switch message */ - OFPT_GET_CONFIG_REQUEST, /* 2 Controller/switch message */ - OFPT_GET_CONFIG_REPLY, /* 3 Controller/switch message */ - OFPT_SET_CONFIG, /* 4 Controller/switch message */ - OFPT_PACKET_IN, /* 5 Async message */ - OFPT_PACKET_OUT, /* 6 Controller/switch message */ - OFPT_FLOW_MOD, /* 7 Controller/switch message */ - OFPT_FLOW_EXPIRED, /* 8 Async message */ - OFPT_TABLE, /* 9 Controller/switch message */ - OFPT_PORT_MOD, /* 10 Controller/switch message */ - OFPT_PORT_STATUS, /* 11 Async message */ - OFPT_ERROR_MSG, /* 12 Async message */ - OFPT_STATS_REQUEST, /* 13 Controller/switch message */ - OFPT_STATS_REPLY, /* 14 Controller/switch message */ - OFPT_ECHO_REQUEST, /* 15 Symmetric message */ - OFPT_ECHO_REPLY, /* 16 Symmetric message */ - OFPT_VENDOR = 0xff /* 255 Vendor extension */ + /* Immutable messages. */ + OFPT_HELLO, /* Symmetric message */ + OFPT_ERROR, /* Symmetric message */ + OFPT_ECHO_REQUEST, /* Symmetric message */ + OFPT_ECHO_REPLY, /* Symmetric message */ + OFPT_VENDOR, /* Symmetric message */ + + /* Switch configuration messages. */ + OFPT_FEATURES_REQUEST, /* Controller/switch message */ + OFPT_FEATURES_REPLY, /* Controller/switch message */ + OFPT_GET_CONFIG_REQUEST, /* Controller/switch message */ + OFPT_GET_CONFIG_REPLY, /* Controller/switch message */ + OFPT_SET_CONFIG, /* Controller/switch message */ + + /* Asynchronous messages. */ + OFPT_PACKET_IN, /* Async message */ + OFPT_FLOW_EXPIRED, /* Async message */ + OFPT_PORT_STATUS, /* Async message */ + + /* Controller command messages. */ + OFPT_PACKET_OUT, /* Controller/switch message */ + OFPT_FLOW_MOD, /* Controller/switch message */ + OFPT_PORT_MOD, /* Controller/switch message */ + OFPT_TABLE, /* Controller/switch message */ + + /* Statistics messages. */ + OFPT_STATS_REQUEST, /* Controller/switch message */ + OFPT_STATS_REPLY /* Controller/switch message */ }; /* Header on all OpenFlow packets. */ @@ -127,6 +137,12 @@ struct ofp_header { }; OFP_ASSERT(sizeof(struct ofp_header) == 8); +/* OFPT_HELLO. This message has an empty body, but implementations must + * ignore any data included in the body, to allow for future extensions. */ +struct ofp_hello { + struct ofp_header header; +}; + #define OFP_DEFAULT_MISS_SEND_LEN 128 enum ofp_config_flags { @@ -446,10 +462,22 @@ struct ofp_flow_expired { }; OFP_ASSERT(sizeof(struct ofp_flow_expired) == 72); +/* Values for 'type' in ofp_error_message. These values are immutable: they + * will not change in future versions of the protocol (although new values may + * be added). */ enum ofp_error_type { + OFPET_HELLO_FAILED, /* Hello protocol failed. */ OFPET_BAD_REQUEST /* Request was not understood. */ }; +/* ofp_error_msg 'code' values for OFPET_HELLO_FAILED. 'data' contains an + * ASCII text string that may give failure details. */ +enum ofp_hello_failed_code { + OFPHFC_INCOMPATIBLE /* No compatible version. */ +}; + +/* ofp_error_msg 'code' values for OFPET_BAD_REQUEST. 'data' contains at least + * the first 64 bytes of the failed request. */ enum ofp_bad_request_code { OFPBRC_BAD_VERSION, /* ofp_header.version not supported. */ OFPBRC_BAD_TYPE, /* ofp_header.type not supported. */ @@ -458,7 +486,7 @@ enum ofp_bad_request_code { * ofp_stats_request or ofp_stats_reply). */ }; -/* Error message (datapath -> controller). */ +/* OFPT_ERROR: Error message (datapath -> controller). */ struct ofp_error_msg { struct ofp_header header; diff --git a/include/vconn-provider.h b/include/vconn-provider.h index f7c47e89..a33fa492 100644 --- a/include/vconn-provider.h +++ b/include/vconn-provider.h @@ -40,12 +40,17 @@ #include #include "vconn.h" +/* Active virtual connection to an OpenFlow device. */ + /* Active virtual connection to an OpenFlow device. * * This structure should be treated as opaque by vconn implementations. */ struct vconn { struct vconn_class *class; - int connect_status; + int state; + int error; + int min_version; + int version; uint32_t ip; char *name; }; diff --git a/include/vconn.h b/include/vconn.h index e13e21a4..e9eab5e4 100644 --- a/include/vconn.h +++ b/include/vconn.h @@ -47,7 +47,7 @@ struct vconn; void vconn_usage(bool active, bool passive); /* Active vconns: virtual connections to OpenFlow devices. */ -int vconn_open(const char *name, struct vconn **); +int vconn_open(const char *name, int min_version, struct vconn **); void vconn_close(struct vconn *); const char *vconn_get_name(const struct vconn *); uint32_t vconn_get_ip(const struct vconn *); @@ -56,7 +56,7 @@ int vconn_recv(struct vconn *, struct buffer **); int vconn_send(struct vconn *, struct buffer *); int vconn_transact(struct vconn *, struct buffer *, struct buffer **); -int vconn_open_block(const char *name, struct vconn **); +int vconn_open_block(const char *name, int min_version, struct vconn **); int vconn_send_block(struct vconn *, struct buffer *); int vconn_recv_block(struct vconn *, struct buffer **); @@ -73,7 +73,7 @@ void vconn_send_wait(struct vconn *); /* Passive vconns: virtual listeners for incoming OpenFlow connections. */ int pvconn_open(const char *name, struct pvconn **); void pvconn_close(struct pvconn *); -int pvconn_accept(struct pvconn *, struct vconn **); +int pvconn_accept(struct pvconn *, int min_version, struct vconn **); void pvconn_wait(struct pvconn *); /* OpenFlow protocol utility functions. */ diff --git a/lib/ofp-print.c b/lib/ofp-print.c index c6076478..1f16396a 100644 --- a/lib/ofp-print.c +++ b/lib/ofp-print.c @@ -596,16 +596,82 @@ ofp_print_flow_expired(struct ds *string, const void *oh, size_t len, ntohll(ofe->byte_count)); } -/* Pretty-print the OFPT_ERROR_MSG packet of 'len' bytes at 'oh' to 'string' +struct error_type { + int type; + int code; + const char *name; +}; + +static const struct error_type error_types[] = { +#define ERROR_TYPE(TYPE) {TYPE, -1, #TYPE} +#define ERROR_CODE(TYPE, CODE) {TYPE, CODE, #CODE} + ERROR_TYPE(OFPET_HELLO_FAILED), + ERROR_CODE(OFPET_HELLO_FAILED, OFPHFC_INCOMPATIBLE), + + ERROR_TYPE(OFPET_BAD_REQUEST), + ERROR_CODE(OFPET_BAD_REQUEST, OFPBRC_BAD_VERSION), + ERROR_CODE(OFPET_BAD_REQUEST, OFPBRC_BAD_TYPE), + ERROR_CODE(OFPET_BAD_REQUEST, OFPBRC_BAD_STAT), + ERROR_CODE(OFPET_BAD_REQUEST, OFPBRC_BAD_VERSION), +}; +#define N_ERROR_TYPES ARRAY_SIZE(error_types) + +static const char * +lookup_error_type(int type) +{ + const struct error_type *t; + + for (t = error_types; t < &error_types[N_ERROR_TYPES]; t++) { + if (t->type == type && t->code == -1) { + return t->name; + } + } + return "?"; +} + +static const char * +lookup_error_code(int type, int code) +{ + const struct error_type *t; + + for (t = error_types; t < &error_types[N_ERROR_TYPES]; t++) { + if (t->type == type && t->code == code) { + return t->name; + } + } + return "?"; +} + +/* Pretty-print the OFPT_ERROR packet of 'len' bytes at 'oh' to 'string' * at the given 'verbosity' level. */ static void ofp_print_error_msg(struct ds *string, const void *oh, size_t len, int verbosity) { const struct ofp_error_msg *oem = oh; + int type = ntohs(oem->type); + int code = ntohs(oem->code); + char *s; - ds_put_format(string, - " type%d code%d\n", ntohs(oem->type), ntohs(oem->code)); + ds_put_format(string, " type%d(%s) code%d(%s) payload:\n", + type, lookup_error_type(type), + code, lookup_error_code(type, code)); + + switch (type) { + case OFPET_HELLO_FAILED: + ds_put_printable(string, (char *) oem->data, len - sizeof *oem); + break; + + case OFPET_BAD_REQUEST: + s = ofp_to_string(oem->data, len - sizeof *oem, 1); + ds_put_cstr(string, s); + free(s); + break; + + default: + ds_put_hex_dump(string, oem->data, len - sizeof *oem, 0, true); + break; + } } /* Pretty-print the OFPT_PORT_STATUS packet of 'len' bytes at 'oh' to 'string' @@ -959,6 +1025,11 @@ struct openflow_packet { }; static const struct openflow_packet packets[] = { + [OFPT_HELLO] = { + "hello", + sizeof (struct ofp_header), + NULL, + }, [OFPT_FEATURES_REQUEST] = { "features_request", sizeof (struct ofp_header), @@ -1014,7 +1085,7 @@ static const struct openflow_packet packets[] = { sizeof (struct ofp_port_status), ofp_print_port_status }, - [OFPT_ERROR_MSG] = { + [OFPT_ERROR] = { "error_msg", sizeof (struct ofp_error_msg), ofp_print_error_msg, diff --git a/lib/rconn.c b/lib/rconn.c index f0ee2bf6..c29e69a1 100644 --- a/lib/rconn.c +++ b/lib/rconn.c @@ -39,6 +39,7 @@ #include #include #include "buffer.h" +#include "openflow.h" #include "poll-loop.h" #include "ofp-print.h" #include "sat-math.h" @@ -273,7 +274,7 @@ reconnect(struct rconn *rc) VLOG_WARN("%s: connecting...", rc->name); rc->n_attempted_connections++; - retval = vconn_open(rc->name, &rc->vconn); + retval = vconn_open(rc->name, OFP_VERSION, &rc->vconn); if (!retval) { rc->backoff_deadline = time_now() + rc->backoff; state_transition(rc, S_CONNECTING); diff --git a/lib/vconn.c b/lib/vconn.c index eba46c82..e136e963 100644 --- a/lib/vconn.c +++ b/lib/vconn.c @@ -41,6 +41,7 @@ #include #include #include "buffer.h" +#include "dynamic-string.h" #include "flow.h" #include "ofp-print.h" #include "openflow.h" @@ -51,6 +52,19 @@ #define THIS_MODULE VLM_vconn #include "vlog.h" +/* State of an active vconn.*/ +enum vconn_state { + /* This is the ordinary progression of states. */ + VCS_CONNECTING, /* Underlying vconn is not connected. */ + VCS_SEND_HELLO, /* Waiting to send OFPT_HELLO message. */ + VCS_RECV_HELLO, /* Waiting to receive OFPT_HELLO message. */ + VCS_CONNECTED, /* Connection established. */ + + /* These states are entered only when something goes wrong. */ + VCS_SEND_ERROR, /* Sending OFPT_ERROR message. */ + VCS_DISCONNECTED /* Connection failed or connection closed. */ +}; + static struct vconn_class *vconn_classes[] = { &tcp_vconn_class, &unix_vconn_class, @@ -75,6 +89,9 @@ static struct pvconn_class *pvconn_classes[] = { * really need to see them. */ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(600, 600); +static int do_recv(struct vconn *, struct buffer **); +static int do_send(struct vconn *, struct buffer *); + /* Check the validity of the vconn class structures. */ static void check_vconn_classes(void) @@ -162,11 +179,15 @@ vconn_usage(bool active, bool passive) * the form "TYPE:ARGS", where TYPE is an active vconn class's name and ARGS * are vconn class-specific. * + * The vconn will automatically negotiate an OpenFlow protocol version + * acceptable to both peers on the connection. The version negotiated will be + * no lower than 'min_version' and no higher than OFP_VERSION. + * * Returns 0 if successful, otherwise a positive errno value. If successful, * stores a pointer to the new connection in '*vconnp', otherwise a null * pointer. */ int -vconn_open(const char *name, struct vconn **vconnp) +vconn_open(const char *name, int min_version, struct vconn **vconnp) { size_t prefix_len; size_t i; @@ -187,8 +208,9 @@ vconn_open(const char *name, struct vconn **vconnp) int retval = class->open(name, suffix_copy, &vconn); free(suffix_copy); if (!retval) { - assert(vconn->connect_status != EAGAIN + assert(vconn->state != VCS_CONNECTING || vconn->class->connect); + vconn->min_version = min_version; *vconnp = vconn; } return retval; @@ -198,12 +220,12 @@ vconn_open(const char *name, struct vconn **vconnp) } int -vconn_open_block(const char *name, struct vconn **vconnp) +vconn_open_block(const char *name, int min_version, struct vconn **vconnp) { struct vconn *vconn; int error; - error = vconn_open(name, &vconn); + error = vconn_open(name, min_version, &vconn); while (error == EAGAIN) { vconn_connect_wait(vconn); poll_block(); @@ -245,6 +267,116 @@ vconn_get_ip(const struct vconn *vconn) return vconn->ip; } +static void +vcs_connecting(struct vconn *vconn) +{ + int retval = (vconn->class->connect)(vconn); + assert(retval != EINPROGRESS); + if (!retval) { + vconn->state = VCS_SEND_HELLO; + } else if (retval != EAGAIN) { + vconn->state = VCS_DISCONNECTED; + vconn->error = retval; + } +} + +static void +vcs_send_hello(struct vconn *vconn) +{ + struct buffer *b; + int retval; + + make_openflow(sizeof(struct ofp_header), OFPT_HELLO, &b); + retval = do_send(vconn, b); + if (!retval) { + vconn->state = VCS_RECV_HELLO; + } else { + buffer_delete(b); + if (retval != EAGAIN) { + vconn->state = VCS_DISCONNECTED; + vconn->error = retval; + } + } +} + +static void +vcs_recv_hello(struct vconn *vconn) +{ + struct buffer *b; + int retval; + + retval = do_recv(vconn, &b); + if (!retval) { + struct ofp_header *oh = b->data; + + if (oh->type == OFPT_HELLO) { + if (b->size > sizeof *oh) { + struct ds msg = DS_EMPTY_INITIALIZER; + ds_put_format(&msg, "%s: extra-long hello:\n", vconn->name); + ds_put_hex_dump(&msg, b->data, b->size, 0, true); + VLOG_WARN_RL(&rl, ds_cstr(&msg)); + ds_destroy(&msg); + } + + vconn->version = MIN(OFP_VERSION, oh->version); + if (vconn->version < vconn->min_version) { + VLOG_WARN_RL(&rl, "%s: version negotiation failed: we support " + "versions 0x%02x to 0x%02x inclusive but peer " + "supports no later than version 0x%02"PRIx8, + vconn->name, vconn->min_version, OFP_VERSION, + oh->version); + vconn->state = VCS_SEND_ERROR; + } else { + VLOG_DBG("%s: negotiated OpenFlow version 0x%02x " + "(we support versions 0x%02x to 0x%02x inclusive, " + "peer no later than version 0x%02"PRIx8")", + vconn->name, vconn->version, vconn->min_version, + OFP_VERSION, oh->version); + vconn->state = VCS_CONNECTED; + } + buffer_delete(b); + return; + } else { + char *s = ofp_to_string(b->data, b->size, 1); + VLOG_WARN_RL(&rl, "%s: received message while expecting hello: %s", + vconn->name, s); + free(s); + retval = EPROTO; + buffer_delete(b); + } + } + + if (retval != EAGAIN) { + vconn->state = VCS_DISCONNECTED; + vconn->error = retval; + } +} + +static void +vcs_send_error(struct vconn *vconn) +{ + struct ofp_error_msg *error; + struct buffer *b; + char s[128]; + int retval; + + snprintf(s, sizeof s, "We support versions 0x%02x to 0x%02x inclusive but " + "you support no later than version 0x%02"PRIx8".", + vconn->min_version, OFP_VERSION, vconn->version); + error = make_openflow(sizeof *error, OFPT_ERROR, &b); + error->type = htons(OFPET_HELLO_FAILED); + error->code = htons(OFPHFC_INCOMPATIBLE); + buffer_put(b, s, strlen(s)); + retval = do_send(vconn, b); + if (retval) { + buffer_delete(b); + } + if (retval != EAGAIN) { + vconn->state = VCS_DISCONNECTED; + vconn->error = retval ? retval : EPROTO; + } +} + /* Tries to complete the connection on 'vconn', which must be an active * vconn. If 'vconn''s connection is complete, returns 0 if the connection * was successful or a positive errno value if it failed. If the @@ -252,11 +384,40 @@ vconn_get_ip(const struct vconn *vconn) int vconn_connect(struct vconn *vconn) { - if (vconn->connect_status == EAGAIN) { - vconn->connect_status = (vconn->class->connect)(vconn); - assert(vconn->connect_status != EINPROGRESS); - } - return vconn->connect_status; + enum vconn_state last_state; + + assert(vconn->min_version >= 0); + do { + last_state = vconn->state; + switch (vconn->state) { + case VCS_CONNECTING: + vcs_connecting(vconn); + break; + + case VCS_SEND_HELLO: + vcs_send_hello(vconn); + break; + + case VCS_RECV_HELLO: + vcs_recv_hello(vconn); + break; + + case VCS_CONNECTED: + return 0; + + case VCS_SEND_ERROR: + vcs_send_error(vconn); + break; + + case VCS_DISCONNECTED: + return vconn->error; + + default: + NOT_REACHED(); + } + } while (vconn->state != last_state); + + return EAGAIN; } /* Tries to receive an OpenFlow message from 'vconn', which must be an active @@ -272,25 +433,45 @@ vconn_recv(struct vconn *vconn, struct buffer **msgp) { int retval = vconn_connect(vconn); if (!retval) { - retval = (vconn->class->recv)(vconn, msgp); - if (!retval) { - struct ofp_header *oh; - - if (VLOG_IS_DBG_ENABLED()) { - char *s = ofp_to_string((*msgp)->data, (*msgp)->size, 1); - VLOG_DBG_RL(&rl, "%s: received: %s", vconn->name, s); - free(s); - } + retval = do_recv(vconn, msgp); + } + return retval; +} + +static int +do_recv(struct vconn *vconn, struct buffer **msgp) +{ + int retval; + + retval = (vconn->class->recv)(vconn, msgp); + if (!retval) { + struct ofp_header *oh; + + if (VLOG_IS_DBG_ENABLED()) { + char *s = ofp_to_string((*msgp)->data, (*msgp)->size, 1); + VLOG_DBG_RL(&rl, "%s: received: %s", vconn->name, s); + free(s); + } - oh = buffer_at_assert(*msgp, 0, sizeof *oh); - if (oh->version != OFP_VERSION) { + oh = buffer_at_assert(*msgp, 0, sizeof *oh); + if (oh->version != vconn->version + && oh->type != OFPT_HELLO + && oh->type != OFPT_ERROR + && oh->type != OFPT_ECHO_REQUEST + && oh->type != OFPT_ECHO_REPLY + && oh->type != OFPT_VENDOR) + { + if (vconn->version < 0) { + VLOG_ERR_RL(&rl, "%s: received OpenFlow version %02"PRIx8" " + "before version negotiation complete", + vconn->name, oh->version); + } else { VLOG_ERR_RL(&rl, "%s: received OpenFlow version %02"PRIx8" " "!= expected %02x", - vconn->name, oh->version, OFP_VERSION); - buffer_delete(*msgp); - *msgp = NULL; - return EPROTO; + vconn->name, oh->version, vconn->version); } + buffer_delete(*msgp); + retval = EPROTO; } } if (retval) { @@ -315,18 +496,28 @@ vconn_send(struct vconn *vconn, struct buffer *msg) { int retval = vconn_connect(vconn); if (!retval) { - assert(msg->size >= sizeof(struct ofp_header)); - assert(((struct ofp_header *) msg->data)->length == htons(msg->size)); - if (!VLOG_IS_DBG_ENABLED()) { - retval = (vconn->class->send)(vconn, msg); - } else { - char *s = ofp_to_string(msg->data, msg->size, 1); - retval = (vconn->class->send)(vconn, msg); - if (retval != EAGAIN) { - VLOG_DBG_RL(&rl, "%s: sent (%s): %s", vconn->name, strerror(retval), s); - } - free(s); + retval = do_send(vconn, msg); + } + return retval; +} + +static int +do_send(struct vconn *vconn, struct buffer *msg) +{ + int retval; + + assert(msg->size >= sizeof(struct ofp_header)); + assert(((struct ofp_header *) msg->data)->length == htons(msg->size)); + if (!VLOG_IS_DBG_ENABLED()) { + retval = (vconn->class->send)(vconn, msg); + } else { + char *s = ofp_to_string(msg->data, msg->size, 1); + retval = (vconn->class->send)(vconn, msg); + if (retval != EAGAIN) { + VLOG_DBG_RL(&rl, "%s: sent (%s): %s", + vconn->name, strerror(retval), s); } + free(s); } return retval; } @@ -397,20 +588,29 @@ vconn_transact(struct vconn *vconn, struct buffer *request, void vconn_wait(struct vconn *vconn, enum vconn_wait_type wait) { - int connect_status; - assert(wait == WAIT_CONNECT || wait == WAIT_RECV || wait == WAIT_SEND); - connect_status = vconn_connect(vconn); - if (connect_status) { - if (connect_status == EAGAIN) { - wait = WAIT_CONNECT; - } else { - poll_immediate_wake(); - return; - } - } + switch (vconn->state) { + case VCS_CONNECTING: + wait = WAIT_CONNECT; + break; + case VCS_SEND_HELLO: + case VCS_SEND_ERROR: + wait = WAIT_SEND; + break; + + case VCS_RECV_HELLO: + wait = WAIT_RECV; + break; + + case VCS_CONNECTED: + break; + + case VCS_DISCONNECTED: + poll_immediate_wake(); + return; + } (vconn->class->wait)(vconn, wait); } @@ -483,17 +683,22 @@ pvconn_close(struct pvconn *pvconn) * connection in '*new_vconn' and returns 0. Otherwise, returns a positive * errno value. * + * The new vconn will automatically negotiate an OpenFlow protocol version + * acceptable to both peers on the connection. The version negotiated will be + * no lower than 'min_version' and no higher than OFP_VERSION. + * * pvconn_accept() will not block waiting for a connection. If no connection * is ready to be accepted, it returns EAGAIN immediately. */ int -pvconn_accept(struct pvconn *pvconn, struct vconn **new_vconn) +pvconn_accept(struct pvconn *pvconn, int min_version, struct vconn **new_vconn) { int retval = (pvconn->class->accept)(pvconn, new_vconn); if (retval) { *new_vconn = NULL; } else { - assert((*new_vconn)->connect_status == 0 + assert((*new_vconn)->state != VCS_CONNECTING || (*new_vconn)->class->connect); + (*new_vconn)->min_version = min_version; } return retval; } @@ -663,7 +868,12 @@ vconn_init(struct vconn *vconn, struct vconn_class *class, int connect_status, uint32_t ip, const char *name) { vconn->class = class; - vconn->connect_status = connect_status; + vconn->state = (connect_status == EAGAIN ? VCS_CONNECTING + : !connect_status ? VCS_SEND_HELLO + : VCS_DISCONNECTED); + vconn->error = connect_status; + vconn->version = -1; + vconn->min_version = -1; vconn->ip = ip; vconn->name = xstrdup(name); } diff --git a/secchan/secchan.c b/secchan/secchan.c index e69a314c..c0a1c564 100644 --- a/secchan/secchan.c +++ b/secchan/secchan.c @@ -388,7 +388,7 @@ accept_vconn(struct pvconn *pvconn) struct vconn *new; int retval; - retval = pvconn_accept(pvconn, &new); + retval = pvconn_accept(pvconn, OFP_VERSION, &new); if (retval && retval != EAGAIN) { VLOG_WARN_RL(&vrl, "accept failed (%s)", strerror(retval)); } @@ -464,7 +464,7 @@ relay_accept(const struct settings *s, struct pvconn *pvconn) * obtaining a subscription for ofp_packet_in or ofp_flow_expired * messages.*/ nl_name_without_subscription = xasprintf("%s:0", s->nl_name); - retval = vconn_open(nl_name_without_subscription, &new_local); + retval = vconn_open(nl_name_without_subscription, OFP_VERSION, &new_local); if (retval) { VLOG_ERR_RL(&vrl, "could not connect to %s (%s)", nl_name_without_subscription, strerror(retval)); diff --git a/switch/datapath.c b/switch/datapath.c index 24744aa8..7ed4f2d8 100644 --- a/switch/datapath.c +++ b/switch/datapath.c @@ -346,7 +346,7 @@ dp_run(struct datapath *dp) struct vconn *new_vconn; int retval; - retval = pvconn_accept(dp->listen_pvconn, &new_vconn); + retval = pvconn_accept(dp->listen_pvconn, OFP_VERSION, &new_vconn); if (retval) { if (retval != EAGAIN) { VLOG_WARN_RL(&rl, "accept failed (%s)", strerror(retval)); @@ -797,8 +797,7 @@ dp_send_error_msg(struct datapath *dp, const struct sender *sender, { struct buffer *buffer; struct ofp_error_msg *oem; - oem = make_openflow_reply(sizeof(*oem)+len, OFPT_ERROR_MSG, - sender, &buffer); + oem = make_openflow_reply(sizeof(*oem)+len, OFPT_ERROR, sender, &buffer); oem->type = htons(type); oem->code = htons(code); memcpy(oem->data, data, len); @@ -1682,7 +1681,6 @@ fwd_control_input(struct datapath *dp, const struct sender *sender, struct ofp_header *oh; oh = (struct ofp_header *) msg; - assert(oh->version == OFP_VERSION); if (ntohs(oh->length) > length) return -EINVAL; diff --git a/utilities/dpctl.c b/utilities/dpctl.c index 33a51f85..af0593f5 100644 --- a/utilities/dpctl.c +++ b/utilities/dpctl.c @@ -328,6 +328,12 @@ static void do_del_port(int argc UNUSED, char *argv[]) /* Generic commands. */ +static void +open_vconn(const char *name, struct vconn **vconnp) +{ + run(vconn_open_block(name, OFP_VERSION, vconnp), "connecting to %s", name); +} + static void * alloc_stats_request(size_t body_len, uint16_t type, struct buffer **bufferp) { @@ -353,7 +359,7 @@ dump_transaction(const char *vconn_name, struct buffer *request) struct buffer *reply; update_openflow_length(request); - run(vconn_open_block(vconn_name, &vconn), "connecting to %s", vconn_name); + open_vconn(vconn_name, &vconn); run(vconn_transact(vconn, request, &reply), "talking to %s", vconn_name); ofp_print(stdout, reply->data, reply->size, 1); vconn_close(vconn); @@ -374,7 +380,7 @@ dump_stats_transaction(const char *vconn_name, struct buffer *request) struct vconn *vconn; bool done = false; - run(vconn_open_block(vconn_name, &vconn), "connecting to %s", vconn_name); + open_vconn(vconn_name, &vconn); send_openflow_buffer(vconn, request); while (!done) { uint32_t recv_xid; @@ -426,7 +432,7 @@ do_status(int argc, char *argv[]) if (argc > 2) { buffer_put(b, argv[2], strlen(argv[2])); } - run(vconn_open_block(argv[1], &vconn), "connecting to %s", argv[1]); + open_vconn(argv[1], &vconn); run(vconn_transact(vconn, b, &b), "talking to %s", argv[1]); vconn_close(vconn); @@ -793,7 +799,7 @@ static void do_add_flow(int argc, char *argv[]) size_t size; int n_actions = MAX_ADD_ACTS; - run(vconn_open_block(argv[1], &vconn), "connecting to %s", argv[1]); + open_vconn(argv[1], &vconn); /* Parse and send. */ size = sizeof *ofm + (sizeof ofm->actions[0] * MAX_ADD_ACTS); @@ -826,7 +832,7 @@ static void do_add_flows(int argc, char *argv[]) fatal(errno, "%s: open", argv[2]); } - run(vconn_open_block(argv[1], &vconn), "connecting to %s", argv[1]); + open_vconn(argv[1], &vconn); while (fgets(line, sizeof line, file)) { struct buffer *buffer; struct ofp_flow_mod *ofm; @@ -873,7 +879,7 @@ static void do_del_flows(int argc, char *argv[]) struct vconn *vconn; uint16_t priority; - run(vconn_open_block(argv[1], &vconn), "connecting to %s", argv[1]); + open_vconn(argv[1], &vconn); struct buffer *buffer; struct ofp_flow_mod *ofm; size_t size; @@ -910,7 +916,7 @@ do_monitor(int argc UNUSED, char *argv[]) } else { name = argv[1]; } - run(vconn_open_block(argv[1], &vconn), "connecting to %s", name); + open_vconn(argv[1], &vconn); for (;;) { struct buffer *b; run(vconn_recv_block(vconn, &b), "vconn_recv"); @@ -933,7 +939,7 @@ do_probe(int argc, char *argv[]) struct buffer *reply; make_openflow(sizeof(struct ofp_header), OFPT_ECHO_REQUEST, &request); - run(vconn_open_block(argv[1], &vconn), "connecting to %s", argv[1]); + open_vconn(argv[1], &vconn); run(vconn_transact(vconn, request, &reply), "talking to %s", argv[1]); if (reply->size != request->size) { fatal(0, "reply does not match request"); @@ -965,7 +971,7 @@ do_mod_port(int argc, char *argv[]) /* Send a "Features Request" to get the information we need in order * to modify the port. */ make_openflow(sizeof(struct ofp_header), OFPT_FEATURES_REQUEST, &request); - run(vconn_open_block(argv[1], &vconn), "connecting to %s", argv[1]); + open_vconn(argv[1], &vconn); run(vconn_transact(vconn, request, &reply), "talking to %s", argv[1]); osf = reply->data; @@ -1033,7 +1039,7 @@ do_ping(int argc, char *argv[]) fatal(0, "payload must be between 0 and %zu bytes", max_payload); } - run(vconn_open_block(argv[1], &vconn), "connecting to %s", argv[1]); + open_vconn(argv[1], &vconn); for (i = 0; i < 10; i++) { struct timeval start, end; struct buffer *request, *reply; @@ -1089,7 +1095,7 @@ do_benchmark(int argc, char *argv[]) printf("Sending %d packets * %u bytes (with header) = %u bytes total\n", count, message_size, count * message_size); - run(vconn_open_block(argv[1], &vconn), "connecting to %s", argv[1]); + open_vconn(argv[1], &vconn); gettimeofday(&start, NULL); for (i = 0; i < count; i++) { struct buffer *request, *reply;