Learn remote switch's flow table properties so we can use this information to decide where to install the default table-miss flow for OpenFlow 1.3. This is not needed by OpenFlow 1.0 since it already does this by default.
This diff implements the functions to ask the remote switch for tables information and to parse them into data structures that we can use to decide where can the switchd(8) install flows and what kind. After the tables information are parsed and stored we use that to select the first table with the capabilies we need to send packets to controller. Even though this is already enough to make switchd(8) to work with switchd(4) and HP 3800 by default, it doesn't consider the possibility of having installed flows that changes the normal table processing. So the next implementation step is to obtain flow information for all table and make switchd(8) also consider this when choosing the table. ok? Index: usr.sbin/switchd/ofp.c =================================================================== RCS file: /cvs/src/usr.sbin/switchd/ofp.c,v retrieving revision 1.17 diff -u -p -r1.17 ofp.c --- usr.sbin/switchd/ofp.c 2 Dec 2016 14:39:46 -0000 1.17 +++ usr.sbin/switchd/ofp.c 2 Dec 2016 15:03:51 -0000 @@ -231,14 +231,11 @@ ofp_nextstate(struct switchd *sc, struct /* Let's not ask this while we don't use it. */ ofp13_flow_stats(sc, con, OFP_PORT_ANY, OFP_GROUP_ID_ANY, OFP_TABLE_ID_ALL); - ofp13_table_features(sc, con, 0); ofp13_desc(sc, con); #endif + rv |= ofp13_table_features(sc, con, 0); rv |= ofp13_setconfig(sc, con, OFP_CONFIG_FRAG_NORMAL, OFP_CONTROLLER_MAXLEN_NO_BUFFER); - - /* Use table '0' for switch(4) and '100' for HP 3800. */ - rv |= ofp13_tablemiss_sendctrl(sc, con, 0); break; Index: usr.sbin/switchd/ofp13.c =================================================================== RCS file: /cvs/src/usr.sbin/switchd/ofp13.c,v retrieving revision 1.41 diff -u -p -r1.41 ofp13.c --- usr.sbin/switchd/ofp13.c 2 Dec 2016 14:39:46 -0000 1.41 +++ usr.sbin/switchd/ofp13.c 2 Dec 2016 15:03:52 -0000 @@ -70,10 +70,8 @@ int ofp13_packet_in(struct switchd *, s struct ofp_header *, struct ibuf *); int ofp13_flow_removed(struct switchd *, struct switch_connection *, struct ofp_header *, struct ibuf *); -int ofp13_parse_instruction(struct ibuf *, struct ofp_instruction *); -int ofp13_parse_action(struct ibuf *, struct ofp_action_header *); -int ofp13_parse_oxm(struct ibuf *, struct ofp_ox_match *); -int ofp13_parse_tableproperties(struct ibuf *, struct ofp_table_features *); +int ofp13_tableproperties(struct switch_connection *, struct ibuf *, + off_t, size_t, int); int ofp13_multipart_reply(struct switchd *, struct switch_connection *, struct ofp_header *, struct ibuf *); int ofp13_validate_tableproperty(struct ibuf *, off_t, int); @@ -104,6 +102,9 @@ int ofp13_setconfig_validate(struct swi struct sockaddr_storage *, struct sockaddr_storage *, struct ofp_header *, struct ibuf *); +int ofp13_switchconfigure(struct switchd *, struct switch_connection *); +int ofp13_getflowtable(struct switch_connection *); + struct ofp_callback ofp13_callbacks[] = { { OFP_T_HELLO, ofp13_hello, ofp_validate_hello }, { OFP_T_ERROR, NULL, ofp13_validate_error }, @@ -1013,7 +1014,7 @@ ofp13_packet_in(struct switchd *sc, stru struct ofp_ox_match *oxm; struct packet pkt; struct ibuf *obuf = NULL; - int ret = -1; + int table, ret = -1; ssize_t len, mlen; uint32_t srcport = 0, dstport; int addflow = 0, sendbuffer = 0; @@ -1091,6 +1092,13 @@ ofp13_packet_in(struct switchd *sc, stru again: if (addflow) { + table = ofp13_getflowtable(con); + if (table > OFP_TABLE_ID_MAX || table < 0) { + /* This switch doesn't support installing flows. */ + addflow = 0; + goto again; + } + if ((fm = ibuf_advance(obuf, sizeof(*fm))) == NULL) goto done; @@ -1101,6 +1109,7 @@ ofp13_packet_in(struct switchd *sc, stru fm->fm_hard_timeout = 0; /* permanent */ fm->fm_priority = 0; fm->fm_buffer_id = pin->pin_buffer_id; + fm->fm_table_id = table; fm->fm_flags = htons(OFP_FLOWFLAG_SEND_FLOW_REMOVED); if (pin->pin_buffer_id == htonl(OFP_PKTOUT_NO_BUFFER)) sendbuffer = 1; @@ -1191,83 +1200,50 @@ ofp13_flow_removed(struct switchd *sc, s } int -ofp13_parse_instruction(struct ibuf *ibuf, struct ofp_instruction *i) -{ - int type; - int len; - - type = ntohs(i->i_type); - len = ntohs(i->i_len); - - log_debug("\t\t%s", print_map(type, ofp_instruction_t_map)); - - return (len); -} - -int -ofp13_parse_action(struct ibuf *ibuf, struct ofp_action_header *ah) -{ - int len, type; - - len = htons(ah->ah_len); - type = htons(ah->ah_type); - - log_debug("\t\t%s", print_map(type, ofp_action_map)); - - return (len); -} - -int -ofp13_parse_oxm(struct ibuf *ibuf, struct ofp_ox_match *oxm) +ofp13_tableproperties(struct switch_connection *con, struct ibuf *ibuf, + off_t off, size_t total, int new) { - int length, type, class, hasmask; + struct ofp_table_features *tf; + struct ofp_table_feature_property *tp; + struct ofp_instruction *i; + struct ofp_action_header *ah; + struct ofp_ox_match *oxm; + struct switch_table *st; + uint8_t *next_table; + int remaining, type, length; + int hlen, padsize; + int class, dtype, dlen; - class = ntohs(oxm->oxm_class); - type = OFP_OXM_GET_FIELD(oxm); - hasmask = OFP_OXM_GET_HASMASK(oxm); /* - * XXX the OpenFlow 1.3.5 specification says this field is only - * 4 bytes long, however the experimental type is 8 bytes. + * This is a new table features reply, free our previous tables + * to get the updated ones. */ - length = sizeof(*oxm); + if (new) + switch_freetables(con); - log_debug("\t\t%s hasmask %s type %s", - print_map(class, ofp_oxm_c_map), hasmask ? "yes" : "no", - print_map(type, ofp_xm_t_map)); + next_table: + if ((tf = ibuf_seek(ibuf, off, sizeof(*tf))) == NULL) + return (-1); - if (class == OFP_OXM_C_OPENFLOW_EXPERIMENTER) { - /* Get the last bytes. */ - if (ibuf_getdata(ibuf, 4) == NULL) - return (-1); + hlen = htons(tf->tf_length); + total -= hlen; + remaining = hlen - sizeof(*tf); + off += sizeof(*tf); - return (8); + st = switch_tablelookup(con, tf->tf_tableid); + if (st == NULL) { + st = switch_newtable(con, tf->tf_tableid); + if (st == NULL) + return (-1); } - return (length); -} - -int -ofp13_parse_tableproperties(struct ibuf *ibuf, struct ofp_table_features *tf) -{ - struct ofp_table_feature_property *tp; - struct ofp_instruction *i; - struct ofp_action_header *ah; - struct ofp_ox_match *oxm; - uint8_t *next_table; - int remaining, type, length; - int totallen, padsize, rv; - - log_debug("Table %s (%d) max_entries %u config %u " - "metadata match %#016llx write %#016llx", - tf->tf_name, tf->tf_tableid, ntohl(tf->tf_max_entries), - ntohl(tf->tf_config), be64toh(tf->tf_metadata_match), - be64toh(tf->tf_metadata_write)); - totallen = htons(tf->tf_length); - remaining = totallen - sizeof(*tf); + st->st_maxentries = ntohl(tf->tf_max_entries); next_table_property: - if ((tp = ibuf_getdata(ibuf, sizeof(*tp))) == NULL) + if ((tp = ibuf_seek(ibuf, off, sizeof(*tp))) == NULL) { + switch_deltable(con, st); return (-1); + } type = ntohs(tp->tp_type); length = ntohs(tp->tp_length); @@ -1276,32 +1252,63 @@ ofp13_parse_tableproperties(struct ibuf padsize = OFP_ALIGN(length) - length; remaining -= OFP_ALIGN(length); length -= sizeof(*tp); - - log_debug("\t%s:", print_map(type, ofp_table_featprop_map)); - if (length == 0) - log_debug("\t\tNONE"); + off += sizeof(*tp); switch (type) { case OFP_TABLE_FEATPROP_INSTRUCTION: case OFP_TABLE_FEATPROP_INSTRUCTION_MISS: + if (type == OFP_TABLE_FEATPROP_INSTRUCTION) + st->st_instructions = 0; + else + st->st_instructionsmiss = 0; + while (length) { - if ((i = ibuf_getdata(ibuf, sizeof(*i))) == NULL) - return (-1); - if ((rv = ofp13_parse_instruction(ibuf, i)) == -1) + if ((i = ibuf_seek(ibuf, off, sizeof(*i))) == NULL) { + switch_deltable(con, st); return (-1); - length -= rv; + } + + dtype = ntohs(i->i_type); + dlen = ntohs(i->i_len); + if (type == OFP_TABLE_FEATPROP_INSTRUCTION) + st->st_instructions |= 1ULL << dtype; + else + st->st_instructionsmiss |= 1ULL << dtype; + + if (dtype == OFP_INSTRUCTION_T_EXPERIMENTER) { + length -= dlen; + off += dlen; + } else { + length -= sizeof(*i); + off += sizeof(*i); + } } break; case OFP_TABLE_FEATPROP_NEXT_TABLES: case OFP_TABLE_FEATPROP_NEXT_TABLES_MISS: + if (type == OFP_TABLE_FEATPROP_NEXT_TABLES) + memset(st->st_nexttable, 0, sizeof(st->st_nexttable)); + else + memset(st->st_nexttablemiss, 0, + sizeof(st->st_nexttablemiss)); + while (length) { - if ((next_table = ibuf_getdata(ibuf, - sizeof(*next_table))) == NULL) + if ((next_table = ibuf_seek(ibuf, off, + sizeof(*next_table))) == NULL) { + switch_deltable(con, st); return (-1); + } + + if (type == OFP_TABLE_FEATPROP_NEXT_TABLES) + st->st_nexttable[(*next_table) / 64] |= + 1ULL << ((*next_table) % 64); + else + st->st_nexttablemiss[(*next_table) / 64] |= + 1ULL << ((*next_table) % 64); - log_debug("\t\t%d", *next_table); length -= sizeof(*next_table); + off += sizeof(*next_table); } break; @@ -1309,16 +1316,37 @@ ofp13_parse_tableproperties(struct ibuf case OFP_TABLE_FEATPROP_WRITE_ACTIONS_MISS: case OFP_TABLE_FEATPROP_APPLY_ACTIONS: case OFP_TABLE_FEATPROP_APPLY_ACTIONS_MISS: + if (type == OFP_TABLE_FEATPROP_WRITE_ACTIONS || + type == OFP_TABLE_FEATPROP_APPLY_ACTIONS) + st->st_actions = 0; + else + st->st_actionsmiss = 0; + while (length) { /* - * XXX the OpenFlow 1.3.5 specs says that we only - * get 4 bytes here instead of the full OXM. + * NOTE the OpenFlow 1.3.5 specs says that we only + * get 4 bytes here instead of the full action header. */ - if ((ah = ibuf_getdata(ibuf, 4)) == NULL) - return (-1); - if ((rv = ofp13_parse_action(ibuf, ah)) == -1) + if ((ah = ibuf_seek(ibuf, off, 4)) == NULL) { + switch_deltable(con, st); return (-1); - length -= rv; + } + + dtype = ntohs(ah->ah_type); + dlen = ntohs(ah->ah_len); + if (type == OFP_TABLE_FEATPROP_WRITE_ACTIONS || + type == OFP_TABLE_FEATPROP_APPLY_ACTIONS) + st->st_actions |= 1ULL << dtype; + else + st->st_actionsmiss |= 1ULL << dtype; + + if (dtype == OFP_ACTION_EXPERIMENTER) { + length -= dlen; + off += dlen; + } else { + length -= 4; + off += 4; + } } break; @@ -1328,32 +1356,72 @@ ofp13_parse_tableproperties(struct ibuf case OFP_TABLE_FEATPROP_WRITE_SETFIELD_MISS: case OFP_TABLE_FEATPROP_APPLY_SETFIELD: case OFP_TABLE_FEATPROP_APPLY_SETFIELD_MISS: + if (type == OFP_TABLE_FEATPROP_WRITE_SETFIELD || + type == OFP_TABLE_FEATPROP_APPLY_SETFIELD) + st->st_setfield = 0; + else if (type == OFP_TABLE_FEATPROP_WRITE_SETFIELD_MISS || + type == OFP_TABLE_FEATPROP_APPLY_SETFIELD_MISS) + st->st_setfieldmiss = 0; + else if (type == OFP_TABLE_FEATPROP_MATCH) + st->st_match = 0; + else + st->st_wildcard = 0; + while (length) { - if ((oxm = ibuf_getdata(ibuf, sizeof(*oxm))) == NULL) - return (-1); - if ((rv = ofp13_parse_oxm(ibuf, oxm)) == -1) + if ((oxm = ibuf_seek(ibuf, off, + sizeof(*oxm))) == NULL) { + switch_deltable(con, st); return (-1); - length -= rv; - } - break; + } - default: - log_debug("%s: unsupported property type: %d", __func__, type); + class = ntohs(oxm->oxm_class); + if (class != OFP_OXM_C_OPENFLOW_BASIC) { + if (class == OFP_OXM_C_OPENFLOW_EXPERIMENTER) { + length -= sizeof(*oxm) + 4; + off += sizeof(*oxm) + 4; + } else { + length -= sizeof(*oxm); + off += sizeof(*oxm); + } + continue; + } - /* Skip this field and try to continue otherwise fail. */ - if (ibuf_getdata(ibuf, length) == NULL) - return (-1); + dtype = OFP_OXM_GET_FIELD(oxm); + if (type == OFP_TABLE_FEATPROP_WRITE_SETFIELD || + type == OFP_TABLE_FEATPROP_APPLY_SETFIELD) + st->st_setfield |= 1ULL << dtype; + else if + (type == OFP_TABLE_FEATPROP_WRITE_SETFIELD_MISS || + type == OFP_TABLE_FEATPROP_APPLY_SETFIELD_MISS) + st->st_setfieldmiss |= 1ULL << dtype; + else if (type == OFP_TABLE_FEATPROP_MATCH) + st->st_match |= 1ULL << dtype; + else + st->st_wildcard |= 1ULL << dtype; + length -= sizeof(*oxm); + off += sizeof(*oxm); + } break; - } - /* Skip the padding and read the next property if any. */ - if (padsize && ibuf_getdata(ibuf, padsize) == NULL) + case OFP_TABLE_FEATPROP_EXPERIMENTER: + case OFP_TABLE_FEATPROP_EXPERIMENTER_MISS: + off += length; + break; + + default: + log_debug("Unsupported table property %d", type); return (-1); - if (remaining) + } + + if (padsize) + off += padsize; + if (remaining > 0) goto next_table_property; + if (total > 0) + goto next_table; - return (totallen); + return (0); } int @@ -1361,18 +1429,25 @@ ofp13_multipart_reply(struct switchd *sc struct ofp_header *oh, struct ibuf *ibuf) { struct ofp_multipart *mp; - struct ofp_table_features *tf; - int readlen, type, flags; + int type, flags, more, new = 0; int remaining; + off_t off; - if ((mp = ibuf_getdata(ibuf, sizeof(*mp))) == NULL) + off = 0; + if ((mp = ibuf_seek(ibuf, 0, sizeof(*mp))) == NULL) return (-1); type = ntohs(mp->mp_type); flags = ntohs(mp->mp_flags); remaining = ntohs(oh->oh_length) - sizeof(*mp); + off += sizeof(*mp); + + more = (flags & OFP_MP_FLAG_REPLY_MORE) == OFP_MP_FLAG_REPLY_MORE; + /* Signalize new requests. */ + if (ofp_multipart_lookup(con, oh->oh_xid) == NULL) + new = 1; - if (flags & OFP_MP_FLAG_REPLY_MORE) { + if (more) { if (ofp_multipart_add(con, oh->oh_xid, type) == -1) { ofp13_error(sc, con, oh, ibuf, OFP_ERRTYPE_BAD_REQUEST, @@ -1393,15 +1468,12 @@ ofp13_multipart_reply(struct switchd *sc switch (type) { case OFP_MP_T_TABLE_FEATURES: - read_next_table: - if ((tf = ibuf_getdata(ibuf, sizeof(*tf))) == NULL) - return (-1); - if ((readlen = ofp13_parse_tableproperties(ibuf, tf)) == -1) + if (ofp13_tableproperties(con, ibuf, off, remaining, new)) return (-1); - remaining -= readlen; - if (remaining) - goto read_next_table; + /* We finished receiving tables, configure the switch. */ + if (more == 0) + return (ofp13_switchconfigure(sc, con)); break; } @@ -2136,4 +2208,49 @@ ofp13_tablemiss_sendctrl(struct switchd err: (void)oflowmod_err(&ctx, __func__, __LINE__); return (-1); +} + +int +ofp13_switchconfigure(struct switchd *sc, struct switch_connection *con) +{ + struct switch_table *st; + + /* Look for a table with 'apply' and 'output' support for miss. */ + TAILQ_FOREACH(st, &con->con_stlist, st_entry) { + if ((st->st_instructionsmiss & + (1ULL << OFP_INSTRUCTION_T_APPLY_ACTIONS)) == 0) + continue; + if ((st->st_actionsmiss & (1ULL << OFP_ACTION_OUTPUT)) == 0) + continue; + + break; + } + if (st == NULL) { + log_warn("No apply output for this switch"); + return (-1); + } + + /* Install the flow to receive packets from the switch. */ + return (ofp13_tablemiss_sendctrl(sc, con, st->st_table)); +} + +int +ofp13_getflowtable(struct switch_connection *con) +{ + struct switch_table *st; + + /* Look for a table with 'apply' and 'output' support. */ + TAILQ_FOREACH(st, &con->con_stlist, st_entry) { + if ((st->st_instructions & + (1ULL << OFP_INSTRUCTION_T_APPLY_ACTIONS)) == 0) + continue; + if ((st->st_actions & (1ULL << OFP_ACTION_OUTPUT)) == 0) + continue; + + break; + } + if (st == NULL) + return (-1); + + return (st->st_table); } Index: usr.sbin/switchd/ofp_common.c =================================================================== RCS file: /cvs/src/usr.sbin/switchd/ofp_common.c,v retrieving revision 1.9 diff -u -p -r1.9 ofp_common.c --- usr.sbin/switchd/ofp_common.c 2 Dec 2016 14:39:46 -0000 1.9 +++ usr.sbin/switchd/ofp_common.c 2 Dec 2016 15:03:52 -0000 @@ -1115,15 +1115,31 @@ ofp_instruction(struct ibuf *ibuf, uint1 return (oi); } -int -ofp_multipart_add(struct switch_connection *con, uint32_t xid, uint8_t type) +struct multipart_message * +ofp_multipart_lookup(struct switch_connection *con, uint32_t xid) { struct multipart_message *mm; - /* A multipart reply have the same xid and type in all parts. */ SLIST_FOREACH(mm, &con->con_mmlist, mm_entry) { if (mm->mm_xid != xid) continue; + + return (mm); + } + + return (NULL); +} + +int +ofp_multipart_add(struct switch_connection *con, uint32_t xid, uint8_t type) +{ + struct multipart_message *mm; + + if ((mm = ofp_multipart_lookup(con, xid)) != NULL) { + /* + * A multipart reply has the same xid and type, otherwise + * something went wrong. + */ if (mm->mm_type != type) return (-1); @@ -1170,6 +1186,51 @@ ofp_multipart_clear(struct switch_connec while (!SLIST_EMPTY(&con->con_mmlist)) { mm = SLIST_FIRST(&con->con_mmlist); ofp_multipart_free(con, mm); + } +} + +struct switch_table * +switch_tablelookup(struct switch_connection *con, int table) +{ + struct switch_table *st; + + TAILQ_FOREACH(st, &con->con_stlist, st_entry) { + if (st->st_table == table) + return (st); + } + + return (NULL); +} + +struct switch_table * +switch_newtable(struct switch_connection *con, int table) +{ + struct switch_table *st; + + if ((st = calloc(1, sizeof(*st))) == NULL) + return (NULL); + + st->st_table = table; + TAILQ_INSERT_TAIL(&con->con_stlist, st, st_entry); + + return (st); +} + +void +switch_deltable(struct switch_connection *con, struct switch_table *st) +{ + TAILQ_REMOVE(&con->con_stlist, st, st_entry); + free(st); +} + +void +switch_freetables(struct switch_connection *con) +{ + struct switch_table *st; + + while (!TAILQ_EMPTY(&con->con_stlist)) { + st = TAILQ_FIRST(&con->con_stlist); + switch_deltable(con, st); } } Index: usr.sbin/switchd/ofrelay.c =================================================================== RCS file: /cvs/src/usr.sbin/switchd/ofrelay.c,v retrieving revision 1.9 diff -u -p -r1.9 ofrelay.c --- usr.sbin/switchd/ofrelay.c 2 Dec 2016 14:39:46 -0000 1.9 +++ usr.sbin/switchd/ofrelay.c 2 Dec 2016 15:03:52 -0000 @@ -98,6 +98,7 @@ ofrelay_close(struct switch_connection * TAILQ_REMOVE(&sc->sc_conns, con, con_entry); ofrelay_sessions--; + switch_freetables(con); ofp_multipart_clear(con); switch_remove(con->con_sc, con->con_switch); msgbuf_clear(&con->con_wbuf); @@ -405,6 +406,7 @@ ofrelay_attach(struct switch_server *srv con->con_srv = srv; con->con_state = OFP_STATE_CLOSED; SLIST_INIT(&con->con_mmlist); + TAILQ_INIT(&con->con_stlist); memcpy(&con->con_peer, sa, sa->sa_len); con->con_port = htons(socket_getport(&con->con_peer)); Index: usr.sbin/switchd/switchd.h =================================================================== RCS file: /cvs/src/usr.sbin/switchd/switchd.h,v retrieving revision 1.27 diff -u -p -r1.27 switchd.h --- usr.sbin/switchd/switchd.h 2 Dec 2016 14:39:46 -0000 1.27 +++ usr.sbin/switchd/switchd.h 2 Dec 2016 15:03:52 -0000 @@ -68,6 +68,30 @@ struct switch_control { }; RB_HEAD(switch_head, switch_control); +struct switch_table { + TAILQ_ENTRY(switch_table) st_entry; + + int st_table; + unsigned int st_entries; + unsigned int st_maxentries; + + uint32_t st_actions; + uint32_t st_actionsmiss; + + uint32_t st_instructions; + uint32_t st_instructionsmiss; + + uint64_t st_setfield; + uint64_t st_setfieldmiss; + uint64_t st_match; + uint64_t st_wildcard; + + /* Maximum of 256 tables (64 * 4). */ + uint64_t st_nexttable[4]; + uint64_t st_nexttablemiss[4]; +}; +TAILQ_HEAD(switch_table_list, switch_table); + struct multipart_message { SLIST_ENTRY(multipart_message) mm_entry; @@ -105,6 +129,8 @@ struct switch_connection { struct switch_server *con_srv; struct multipart_list con_mmlist; + struct switch_table_list + con_stlist; TAILQ_ENTRY(switch_connection) con_entry; @@ -247,6 +273,13 @@ void ofp_accept(int, short, void *); int ofp_input(struct switch_connection *, struct ibuf *); int ofp_nextstate(struct switchd *, struct switch_connection *, enum ofp_state); +struct switch_table * + switch_tablelookup(struct switch_connection *, int); +struct switch_table * + switch_newtable(struct switch_connection *, int); +void switch_deltable(struct switch_connection *, + struct switch_table *); +void switch_freetables(struct switch_connection *); /* ofp10.c */ int ofp10_hello(struct switchd *, struct switch_connection *, @@ -289,6 +322,8 @@ int ofp_validate(struct switchd *, struct ofp_header *, struct ibuf *, uint8_t); int ofp_output(struct switch_connection *, struct ofp_header *, struct ibuf *); +struct multipart_message * + ofp_multipart_lookup(struct switch_connection *, uint32_t); int ofp_multipart_add(struct switch_connection *, uint32_t, uint8_t); void ofp_multipart_del(struct switch_connection *, uint32_t); @@ -364,7 +399,6 @@ int ofp_send_hello(struct switchd *, s int); int ofp_send_featuresrequest(struct switchd *, struct switch_connection *); - /* ofcconn.c */ void ofcconn(struct privsep *, struct privsep_proc *); void ofcconn_shutdown(void);