-/* In-band control. */
-
-struct in_band_data {
- const struct settings *s;
- struct mac_learning *ml;
- struct netdev *of_device;
- uint8_t mac[ETH_ADDR_LEN];
-};
-
-static void
-queue_tx(struct rconn *rc, struct buffer *b)
-{
- if (rconn_force_send(rc, b)) {
- buffer_delete(b);
- }
-}
-
-static const uint8_t *
-get_controller_mac(struct netdev *netdev, struct rconn *controller)
-{
- static uint32_t ip, last_nonzero_ip;
- static uint8_t mac[ETH_ADDR_LEN], last_nonzero_mac[ETH_ADDR_LEN];
- static time_t next_refresh = 0;
-
- uint32_t last_ip = ip;
-
- time_t now = time(0);
-
- ip = rconn_get_ip(controller);
- if (last_ip != ip || !next_refresh || now >= next_refresh) {
- bool have_mac;
-
- /* Look up MAC address. */
- memset(mac, 0, sizeof mac);
- if (ip) {
- int retval = netdev_arp_lookup(netdev, ip, mac);
- if (retval) {
- VLOG_DBG("cannot look up controller hw address ("IP_FMT"): %s",
- IP_ARGS(&ip), strerror(retval));
- }
- }
- have_mac = !eth_addr_is_zero(mac);
-
- /* Log changes in IP, MAC addresses. */
- if (ip && ip != last_nonzero_ip) {
- VLOG_DBG("controller IP address changed from "IP_FMT
- " to "IP_FMT, IP_ARGS(&last_nonzero_ip), IP_ARGS(&ip));
- last_nonzero_ip = ip;
- }
- if (have_mac && memcmp(last_nonzero_mac, mac, ETH_ADDR_LEN)) {
- VLOG_DBG("controller MAC address changed from "ETH_ADDR_FMT" to "
- ETH_ADDR_FMT,
- ETH_ADDR_ARGS(last_nonzero_mac), ETH_ADDR_ARGS(mac));
- memcpy(last_nonzero_mac, mac, ETH_ADDR_LEN);
- }
-
- /* Schedule next refresh.
- *
- * If we have an IP address but not a MAC address, then refresh
- * quickly, since we probably will get a MAC address soon (via ARP).
- * Otherwise, we can afford to wait a little while. */
- next_refresh = now + (!ip || have_mac ? 10 : 1);
- }
- return !eth_addr_is_zero(mac) ? mac : NULL;
-}
-
-static bool
-is_controller_mac(const uint8_t mac[ETH_ADDR_LEN],
- const uint8_t *controller_mac)
-{
- return controller_mac && eth_addr_equals(mac, controller_mac);
-}
-
-static bool
-in_band_packet_cb(struct relay *r, int half, void *in_band_)
-{
- struct in_band_data *in_band = in_band_;
- struct rconn *rc = r->halves[HALF_LOCAL].rconn;
- struct buffer *msg = r->halves[HALF_LOCAL].rxbuf;
- struct ofp_packet_in *opi;
- struct ofp_header *oh;
- size_t pkt_ofs, pkt_len;
- struct buffer pkt;
- struct flow flow;
- uint16_t in_port, out_port;
- const uint8_t *controller_mac;
-
- if (half != HALF_LOCAL || r->is_mgmt_conn) {
- return false;
- }
-
- oh = msg->data;
- if (oh->type != OFPT_PACKET_IN) {
- return false;
- }
- if (msg->size < offsetof (struct ofp_packet_in, data)) {
- VLOG_WARN("packet too short (%zu bytes) for packet_in", msg->size);
- return false;
- }
-
- /* Extract flow data from 'opi' into 'flow'. */
- opi = msg->data;
- in_port = ntohs(opi->in_port);
- pkt_ofs = offsetof(struct ofp_packet_in, data);
- pkt_len = ntohs(opi->header.length) - pkt_ofs;
- pkt.data = opi->data;
- pkt.size = pkt_len;
- flow_extract(&pkt, in_port, &flow);
-
- /* Deal with local stuff. */
- controller_mac = get_controller_mac(in_band->of_device,
- r->halves[HALF_REMOTE].rconn);
- if (in_port == OFPP_LOCAL) {
- /* Sent by secure channel. */
- out_port = mac_learning_lookup(in_band->ml, flow.dl_dst);
- } else if (eth_addr_equals(flow.dl_dst, in_band->mac)) {
- /* Sent to secure channel. */
- out_port = OFPP_LOCAL;
- if (mac_learning_learn(in_band->ml, flow.dl_src, in_port)) {
- VLOG_DBG("learned that "ETH_ADDR_FMT" is on port %"PRIu16,
- ETH_ADDR_ARGS(flow.dl_src), in_port);
- }
- } else if (flow.dl_type == htons(ETH_TYPE_ARP)
- && eth_addr_is_broadcast(flow.dl_dst)
- && is_controller_mac(flow.dl_src, controller_mac)) {
- /* ARP sent by controller. */
- out_port = OFPP_FLOOD;
- } else if (is_controller_mac(flow.dl_dst, controller_mac)
- && in_port == mac_learning_lookup(in_band->ml,
- controller_mac)) {
- /* Drop controller traffic that arrives on the controller port. */
- queue_tx(rc, make_add_flow(&flow, ntohl(opi->buffer_id),
- in_band->s->max_idle, 0));
- return true;
- } else {
- return false;
- }
-
- if (out_port != OFPP_FLOOD) {
- /* The output port is known, so add a new flow. */
- queue_tx(rc, make_add_simple_flow(&flow, ntohl(opi->buffer_id),
- out_port, in_band->s->max_idle));
-
- /* If the switch didn't buffer the packet, we need to send a copy. */
- if (ntohl(opi->buffer_id) == UINT32_MAX) {
- queue_tx(rc, make_unbuffered_packet_out(&pkt, in_port, out_port));
- }
- } else {
- /* We don't know that MAC. Send along the packet without setting up a
- * flow. */
- struct buffer *b;
- if (ntohl(opi->buffer_id) == UINT32_MAX) {
- b = make_unbuffered_packet_out(&pkt, in_port, out_port);
- } else {
- b = make_buffered_packet_out(ntohl(opi->buffer_id),
- in_port, out_port);
- }
- queue_tx(rc, b);
- }
- return true;
-}
-
-static struct hook
-in_band_hook_create(const struct settings *s)
-{
- struct in_band_data *in_band;
- int retval;
-
- in_band = xmalloc(sizeof *in_band);
- in_band->s = s;
- in_band->ml = mac_learning_create();
- retval = netdev_open(s->of_name, NETDEV_ETH_TYPE_NONE,
- &in_band->of_device);
- if (retval) {
- fatal(retval, "Could not open %s device", s->of_name);
- }
- memcpy(in_band->mac, netdev_get_etheraddr(in_band->of_device),
- ETH_ADDR_LEN);
-
- return make_hook(in_band_packet_cb, NULL, in_band);
-}
-\f
-/* Fail open support. */
-
-struct fail_open_data {
- const struct settings *s;
- struct rconn *local_rconn;
- struct rconn *remote_rconn;
- struct lswitch *lswitch;
- int last_disconn_secs;
-};
-
-/* Causes 'r' to enter or leave fail-open mode, if appropriate. */
-static void
-fail_open_periodic_cb(void *fail_open_)
-{
- struct fail_open_data *fail_open = fail_open_;
- int disconn_secs;
- bool open;
-
- disconn_secs = rconn_disconnected_duration(fail_open->remote_rconn);
- open = disconn_secs >= fail_open->s->probe_interval * 3;
- if (open != (fail_open->lswitch != NULL)) {
- if (!open) {
- VLOG_WARN("No longer in fail-open mode");
- lswitch_destroy(fail_open->lswitch);
- fail_open->lswitch = NULL;
- } else {
- VLOG_WARN("Could not connect to controller for %d seconds, "
- "failing open", disconn_secs);
- fail_open->lswitch = lswitch_create(fail_open->local_rconn, true,
- fail_open->s->max_idle);
- fail_open->last_disconn_secs = disconn_secs;
- }
- } else if (open && disconn_secs > fail_open->last_disconn_secs + 60) {
- VLOG_WARN("Still in fail-open mode after %d seconds disconnected "
- "from controller", disconn_secs);
- fail_open->last_disconn_secs = disconn_secs;
- }
-}
-
-static bool
-fail_open_packet_cb(struct relay *r, int half, void *fail_open_)
-{
- struct fail_open_data *fail_open = fail_open_;
- if (half != HALF_LOCAL || r->is_mgmt_conn || !fail_open->lswitch) {
- return false;
- } else {
- lswitch_process_packet(fail_open->lswitch, fail_open->local_rconn,
- r->halves[HALF_LOCAL].rxbuf);
- rconn_run(fail_open->local_rconn);
- return true;
- }
-}
-
-static struct hook
-fail_open_hook_create(const struct settings *s, struct rconn *local_rconn,
- struct rconn *remote_rconn)
-{
- struct fail_open_data *fail_open = xmalloc(sizeof *fail_open);
- fail_open->s = s;
- fail_open->local_rconn = local_rconn;
- fail_open->remote_rconn = remote_rconn;
- fail_open->lswitch = NULL;
- return make_hook(fail_open_packet_cb, fail_open_periodic_cb, fail_open);
-}
-\f
-/* Controller discovery. */
-
-struct discovery
-{
- const struct settings *s;
- struct dhclient *dhcp;
- bool ever_successful;
-};
-
-static struct discovery *
-discovery_init(const struct settings *s)
-{
- struct netdev *netdev;
- struct discovery *d;
- struct dhclient *dhcp;
- int retval;
-
- /* Bring ofX network device up. */
- retval = netdev_open(s->of_name, NETDEV_ETH_TYPE_NONE, &netdev);
- if (retval) {
- fatal(retval, "Could not open %s device", s->of_name);
- }
- retval = netdev_turn_flags_on(netdev, NETDEV_UP, true);
- if (retval) {
- fatal(retval, "Could not bring %s device up", s->of_name);
- }
- netdev_close(netdev);
-
- /* Initialize DHCP client. */
- retval = dhclient_create(s->of_name, modify_dhcp_request,
- validate_dhcp_offer, (void *) s, &dhcp);
- if (retval) {
- fatal(retval, "Failed to initialize DHCP client");
- }
- dhclient_init(dhcp, 0);
-
- d = xmalloc(sizeof *d);
- d->s = s;
- d->dhcp = dhcp;
- d->ever_successful = false;
- return d;
-}
-
-static void
-discovery_question_connectivity(struct discovery *d)
-{
- dhclient_force_renew(d->dhcp, 15);
-}
-
-static bool
-discovery_run(struct discovery *d, char **controller_name)
-{
- dhclient_run(d->dhcp);
- if (!dhclient_changed(d->dhcp)) {
- return false;
- }
-
- dhclient_configure_netdev(d->dhcp);
- if (d->s->update_resolv_conf) {
- dhclient_update_resolv_conf(d->dhcp);
- }
-
- if (dhclient_is_bound(d->dhcp)) {
- *controller_name = dhcp_msg_get_string(dhclient_get_config(d->dhcp),
- DHCP_CODE_OFP_CONTROLLER_VCONN);
- VLOG_WARN("%s: discovered controller", *controller_name);
- d->ever_successful = true;
- } else if (controller_name) {
- *controller_name = NULL;
- if (d->ever_successful) {
- VLOG_WARN("discovered controller no longer available");
- }
- }
- return true;
-}
-
-static void
-discovery_wait(struct discovery *d)
-{
- dhclient_wait(d->dhcp);
-}
-
-static void
-modify_dhcp_request(struct dhcp_msg *msg, void *aux)
-{
- dhcp_msg_put_string(msg, DHCP_CODE_VENDOR_CLASS, "OpenFlow");
-}
-
-static bool
-validate_dhcp_offer(const struct dhcp_msg *msg, void *s_)
-{
- const struct settings *s = s_;
- char *vconn_name;
- bool accept;
-
- vconn_name = dhcp_msg_get_string(msg, DHCP_CODE_OFP_CONTROLLER_VCONN);
- if (!vconn_name) {
- VLOG_WARN("rejecting DHCP offer missing controller vconn");
- return false;
- }
- accept = !regexec(&s->accept_controller_regex, vconn_name, 0, NULL, 0);
- if (!accept) {
- VLOG_WARN("rejecting controller vconn that fails to match %s",
- s->accept_controller_re);
- }
- free(vconn_name);
- return accept;
-}
-\f