Teach switchd(8) how to negotiate protocol version using the hello bitmap
header. This way switchd(8) is able to fallback or use higher version using
the bitmap.

This diff also prevents connections from switching version in the middle of
the operation.

This is the first step before adding a state machine to switchd(8): move the
hello to a common function and make it step into the first state: HELLO_WAIT.
(next diff)

ok?

Index: ofp.c
===================================================================
RCS file: /home/obsdcvs/src/usr.sbin/switchd/ofp.c,v
retrieving revision 1.15
diff -u -p -r1.15 ofp.c
--- ofp.c       4 Nov 2016 22:27:08 -0000       1.15
+++ ofp.c       22 Nov 2016 14:55:12 -0000
@@ -132,6 +132,13 @@ ofp_input(struct switch_connection *con,
                return (-1);
        }
 
+       if (con->con_version != OFP_V_0 &&
+           oh->oh_version != con->con_version) {
+               log_debug("wrong version %d, expected %d",
+                   oh->oh_version, con->con_version);
+               return (-1);
+       }
+
        switch (oh->oh_version) {
        case OFP_V_1_0:
                if (ofp10_input(sc, con, oh, ibuf) != 0)
@@ -165,6 +172,10 @@ ofp_open(struct privsep *ps, struct swit
        log_info("%s: new connection %u.%u from switch %u",
            __func__, con->con_id, con->con_instance,
            sw == NULL ? 0 : sw->sw_id);
+
+       /* Send the hello with the latest version we support. */
+       if (ofp_send_hello(ps->ps_env, con, OFP_V_1_3) == -1)
+               return (-1);
 
        return (0);
 }
Index: ofp10.c
===================================================================
RCS file: /home/obsdcvs/src/usr.sbin/switchd/ofp10.c,v
retrieving revision 1.16
diff -u -p -r1.16 ofp10.c
--- ofp10.c     21 Nov 2016 18:19:51 -0000      1.16
+++ ofp10.c     22 Nov 2016 15:08:02 -0000
@@ -60,7 +60,7 @@ int    ofp10_validate_packet_out(struct sw
            struct ofp_header *, struct ibuf *);
 
 struct ofp_callback ofp10_callbacks[] = {
-       { OFP10_T_HELLO,                ofp10_hello, NULL },
+       { OFP10_T_HELLO,                ofp10_hello, ofp_validate_hello },
        { OFP10_T_ERROR,                NULL, ofp10_validate_error },
        { OFP10_T_ECHO_REQUEST,         ofp10_echo_request, NULL },
        { OFP10_T_ECHO_REPLY,           NULL, NULL },
@@ -262,13 +262,8 @@ ofp10_hello(struct switchd *sc, struct s
                return (-1);
        }
 
-       /* Echo back the received Hello packet */
-       oh->oh_version = OFP_V_1_0;
-       oh->oh_length = htons(sizeof(*oh));
-       oh->oh_xid = htonl(con->con_xidnxt++);
-       if (ofp10_validate(sc, &con->con_local, &con->con_peer, oh, NULL) != 0)
+       if (ofp_recv_hello(sc, con, oh, ibuf) == -1)
                return (-1);
-       ofp_output(con, oh, NULL);
 
 #if 0
        (void)write(fd, &oh, sizeof(oh));
Index: ofp13.c
===================================================================
RCS file: /home/obsdcvs/src/usr.sbin/switchd/ofp13.c,v
retrieving revision 1.39
diff -u -p -r1.39 ofp13.c
--- ofp13.c     21 Nov 2016 19:33:12 -0000      1.39
+++ ofp13.c     22 Nov 2016 14:49:27 -0000
@@ -109,7 +109,7 @@ int  ofp13_tablemiss_sendctrl(struct swi
            uint8_t);
 
 struct ofp_callback ofp13_callbacks[] = {
-       { OFP_T_HELLO,                  ofp13_hello, NULL },
+       { OFP_T_HELLO,                  ofp13_hello, ofp_validate_hello },
        { OFP_T_ERROR,                  NULL, ofp13_validate_error },
        { OFP_T_ECHO_REQUEST,           ofp13_echo_request, NULL },
        { OFP_T_ECHO_REPLY,             NULL, NULL },
@@ -639,13 +639,8 @@ ofp13_hello(struct switchd *sc, struct s
                return (-1);
        }
 
-       /* Echo back the received Hello packet */
-       oh->oh_version = OFP_V_1_3;
-       oh->oh_length = htons(sizeof(*oh));
-       oh->oh_xid = htonl(con->con_xidnxt++);
-       if (ofp13_validate(sc, &con->con_local, &con->con_peer, oh, NULL) != 0)
+       if (ofp_recv_hello(sc, con, oh, ibuf) == -1)
                return (-1);
-       ofp_output(con, oh, NULL);
 
        /* Ask for switch features so we can get more information. */
        if (ofp13_featuresrequest(sc, con) == -1)
Index: ofp_common.c
===================================================================
RCS file: /home/obsdcvs/src/usr.sbin/switchd/ofp_common.c,v
retrieving revision 1.7
diff -u -p -r1.7 ofp_common.c
--- ofp_common.c        17 Nov 2016 13:10:26 -0000      1.7
+++ ofp_common.c        22 Nov 2016 14:53:45 -0000
@@ -43,6 +43,8 @@
 #include "switchd.h"
 #include "ofp_map.h"
 
+int            ofp_setversion(struct switch_connection *, int);
+
 int
 ofp_validate_header(struct switchd *sc,
     struct sockaddr_storage *src, struct sockaddr_storage *dst,
@@ -114,6 +116,177 @@ ofp_output(struct switch_connection *con
        }
 
        ofrelay_write(con, buf);
+
+       return (0);
+}
+
+int
+ofp_send_hello(struct switchd *sc, struct switch_connection *con, int version)
+{
+       struct ofp_hello_element_header *he;
+       struct ofp_header               *oh;
+       struct ibuf                     *ibuf;
+       size_t                           hstart, hend;
+       uint32_t                        *bmp;
+       int                              rv = -1;
+
+       if ((ibuf = ibuf_static()) == NULL ||
+           (oh = ibuf_advance(ibuf, sizeof(*oh))) == NULL ||
+           (he = ibuf_advance(ibuf, sizeof(*he))) == NULL)
+               goto done;
+
+       /* Write down all versions we support. */
+       hstart = ibuf->wpos;
+       if ((bmp = ibuf_advance(ibuf, sizeof(*bmp))) == NULL)
+               goto done;
+
+       *bmp = htonl((1 << OFP_V_1_0) | (1 << OFP_V_1_3));
+       hend = ibuf->wpos;
+
+       /* Fill the headers. */
+       oh->oh_version = version;
+       oh->oh_type = OFP_T_HELLO;
+       oh->oh_length = htons(ibuf_length(ibuf));
+       oh->oh_xid = htonl(con->con_xidnxt++);
+       he->he_type = htons(OFP_HELLO_T_VERSION_BITMAP);
+       he->he_length = htons(sizeof(*he) + (hend - hstart));
+
+       if (ofp_validate(sc, &con->con_local, &con->con_peer, oh, ibuf,
+           version) != 0)
+               goto done;
+
+       rv = ofp_output(con, NULL, ibuf);
+
+ done:
+       ibuf_free(ibuf);
+       return (rv);
+}
+
+int
+ofp_validate_hello(struct switchd *sc,
+    struct sockaddr_storage *src, struct sockaddr_storage *dst,
+    struct ofp_header *oh, struct ibuf *ibuf)
+{
+       struct ofp_hello_element_header *he;
+       uint32_t                        *bmp;
+       off_t                            poff;
+       int                              helen, i, ver, vmajor, vminor;
+
+       /* No extra element headers. */
+       if (ntohs(oh->oh_length) == sizeof(*oh))
+               return (0);
+
+       /* Test for supported element headers. */
+       if ((he = ibuf_seek(ibuf, sizeof(*oh), sizeof(*he))) == NULL)
+               return (-1);
+       if (he->he_type != htons(OFP_HELLO_T_VERSION_BITMAP))
+               return (-1);
+
+       log_debug("\tversion bitmap:");
+
+       /* Validate header sizes. */
+       helen = ntohs(he->he_length);
+       if (helen < (int)sizeof(*he))
+               return (-1);
+       else if (helen == sizeof(*he))
+               return (0);
+
+       helen -= sizeof(*he);
+       /* Invalid bitmap size. */
+       if ((helen % sizeof(*bmp)) != 0)
+               return (-1);
+
+       ver = 9;
+       poff = sizeof(*oh) + sizeof(*he);
+       while (helen > 0) {
+               if ((bmp = ibuf_seek(ibuf, poff, sizeof(*bmp))) == NULL)
+                       return (-1);
+
+               for (i = 0; i < 32; i++, ver++) {
+                       if ((ntohl(*bmp) & (1 << i)) == 0)
+                               continue;
+
+                       vmajor = (ver / 10);
+                       vminor = ver % 10;
+                       log_debug("\t\tv%d.%d", vmajor, vminor);
+               }
+
+               helen -= sizeof(*bmp);
+               poff += sizeof(*bmp);
+       }
+
+       return (0);
+}
+
+int
+ofp_setversion(struct switch_connection *con, int version)
+{
+       switch (version) {
+       case OFP_V_1_0:
+       case OFP_V_1_3:
+               con->con_version = version;
+               return (0);
+
+       default:
+               return (-1);
+       }
+}
+
+int
+ofp_recv_hello(struct switchd *sc, struct switch_connection *con,
+    struct ofp_header *oh, struct ibuf *ibuf)
+{
+       struct ofp_hello_element_header *he;
+       uint32_t                        *bmp;
+       off_t                            poff;
+       int                              helen, i, ver;
+
+       /* No extra element headers, just use the header version. */
+       if (ntohs(oh->oh_length) == sizeof(*oh))
+               return (ofp_setversion(con, oh->oh_version));
+
+       /* Read the element header. */
+       if ((he = ibuf_seek(ibuf, sizeof(*oh), sizeof(*he))) == NULL)
+               return (-1);
+
+       /* We don't support anything else than the version bitmap. */
+       if (he->he_type != htons(OFP_HELLO_T_VERSION_BITMAP))
+               return (-1);
+
+       /* Validate header sizes. */
+       helen = ntohs(he->he_length);
+       if (helen < (int)sizeof(*he))
+               return (-1);
+       else if (helen == sizeof(*he))
+               return (ofp_setversion(con, oh->oh_version));
+
+       helen -= sizeof(*he);
+       /* Invalid bitmap size. */
+       if ((helen % sizeof(*bmp)) != 0)
+               return (-1);
+
+       ver = 0;
+       poff = sizeof(*oh) + sizeof(*he);
+
+       /* Loop through the bitmaps and choose the higher version. */
+       while (helen > 0) {
+               if ((bmp = ibuf_seek(ibuf, poff, sizeof(*bmp))) == NULL)
+                       return (-1);
+
+               for (i = 0; i < 32; i++, ver++) {
+                       if ((ntohl(*bmp) & (1 << i)) == 0)
+                               continue;
+
+                       ofp_setversion(con, ver);
+               }
+
+               helen -= sizeof(*bmp);
+               poff += sizeof(*bmp);
+       }
+
+       /* Check if we have set any version, otherwise fallback. */
+       if (con->con_version == OFP_V_0)
+               return (ofp_setversion(con, oh->oh_version));
 
        return (0);
 }
Index: switchd.h
===================================================================
RCS file: /home/obsdcvs/src/usr.sbin/switchd/switchd.h,v
retrieving revision 1.25
diff -u -p -r1.25 switchd.h
--- switchd.h   18 Nov 2016 16:49:35 -0000      1.25
+++ switchd.h   22 Nov 2016 14:54:36 -0000
@@ -93,6 +93,7 @@ struct switch_connection {
        struct sockaddr_storage  con_local;
        in_port_t                con_port;
        uint32_t                 con_xidnxt;
+       int                      con_version;
 
        struct event             con_ev;
        struct ibuf             *con_rbuf;
@@ -347,6 +348,13 @@ int                 oflowmod_instructionclose(struct o
 int             oflowmod_state(struct oflowmod_ctx *,
                    unsigned int, unsigned int);
 int             oflowmod_err(struct oflowmod_ctx *, const char *, int);
+int             ofp_validate_hello(struct switchd *,
+                   struct sockaddr_storage *, struct sockaddr_storage *,
+                   struct ofp_header *, struct ibuf *);
+int             ofp_recv_hello(struct switchd *, struct switch_connection *,
+                   struct ofp_header *, struct ibuf *);
+int             ofp_send_hello(struct switchd *, struct switch_connection *,
+                   int);
 
 /* ofcconn.c */
 void            ofcconn(struct privsep *, struct privsep_proc *);

Reply via email to