Edit

IABSD.fr/src/usr.sbin/switchd/ofp10.c

Branch :

  • Show log

    Commit

  • Author : akoshibe
    Date : 2019-05-05 21:33:00
    Hash : f03c03ed
    Message : switchd(8) will sometimes not save a copy of a packet needing forwarding even when a switch(4) instance requires it to supply it. Cases where this can happen are: if the copy of the packet recieved from the switch is too short (source/destination pairs can't be recovered), is non-unicast, or when switchd has to fall back to flooding traffic. Factor out the check for short packets, stopping before forwarding decisions are made if the full packet is needed by the switch. Set the packet buffer early for cases where it is needed otherwise. Also replace a few bzero's with memset's. Diff based on one by guenther@ OK phessler@

  • usr.sbin/switchd/ofp10.c
  • /*	$OpenBSD: ofp10.c,v 1.21 2019/05/05 21:33:00 akoshibe Exp $	*/
    
    /*
     * Copyright (c) 2013-2016 Reyk Floeter <reyk@openbsd.org>
     *
     * Permission to use, copy, modify, and distribute this software for any
     * purpose with or without fee is hereby granted, provided that the above
     * copyright notice and this permission notice appear in all copies.
     *
     * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     */
    
    #include <sys/types.h>
    #include <sys/queue.h>
    #include <sys/socket.h>
    
    #include <net/if.h>
    #include <net/if_arp.h>
    #include <net/ofp.h>
    
    #include <netinet/in.h>
    #include <netinet/if_ether.h>
    #include <netinet/tcp.h>
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <fcntl.h>
    #include <imsg.h>
    #include <event.h>
    
    #include "ofp10.h"
    #include "switchd.h"
    #include "ofp_map.h"
    
    
    int	 ofp10_packet_match(struct packet *, struct ofp10_match *, unsigned int);
    
    int	 ofp10_features_reply(struct switchd *, struct switch_connection *,
    	    struct ofp_header *, struct ibuf *);
    int	 ofp10_validate_features_reply(struct switchd *,
    	    struct sockaddr_storage *, struct sockaddr_storage *,
    	    struct ofp_header *, struct ibuf *);
    int	 ofp10_echo_request(struct switchd *, struct switch_connection *,
    	    struct ofp_header *, struct ibuf *);
    int	 ofp10_validate_error(struct switchd *,
    	    struct sockaddr_storage *, struct sockaddr_storage *,
    	    struct ofp_header *, struct ibuf *);
    int	 ofp10_error(struct switchd *, struct switch_connection *,
    	    struct ofp_header *, struct ibuf *);
    int	 ofp10_validate_packet_in(struct switchd *,
    	    struct sockaddr_storage *, struct sockaddr_storage *,
    	    struct ofp_header *, struct ibuf *);
    int	 ofp10_packet_in(struct switchd *, struct switch_connection *,
    	    struct ofp_header *, struct ibuf *);
    int	 ofp10_validate_packet_out(struct switchd *,
    	    struct sockaddr_storage *, struct sockaddr_storage *,
    	    struct ofp_header *, struct ibuf *);
    
    struct ofp_callback ofp10_callbacks[] = {
    	{ 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 },
    	{ OFP10_T_EXPERIMENTER,		NULL, NULL },
    	{ OFP10_T_FEATURES_REQUEST,	NULL, NULL },
    	{ OFP10_T_FEATURES_REPLY,	ofp10_features_reply,
    					ofp10_validate_features_reply },
    	{ OFP10_T_GET_CONFIG_REQUEST,	NULL, NULL },
    	{ OFP10_T_GET_CONFIG_REPLY,	NULL, NULL },
    	{ OFP10_T_SET_CONFIG,		NULL, NULL },
    	{ OFP10_T_PACKET_IN,		ofp10_packet_in, ofp10_validate_packet_in },
    	{ OFP10_T_FLOW_REMOVED,		NULL, NULL },
    	{ OFP10_T_PORT_STATUS,		NULL, NULL },
    	{ OFP10_T_PACKET_OUT,		NULL, ofp10_validate_packet_out },
    	{ OFP10_T_FLOW_MOD,		NULL, NULL },
    	{ OFP10_T_PORT_MOD,		NULL, NULL },
    	{ OFP10_T_STATS_REQUEST,	NULL, NULL },
    	{ OFP10_T_STATS_REPLY,		NULL, NULL },
    	{ OFP10_T_BARRIER_REQUEST,	NULL, NULL },
    	{ OFP10_T_BARRIER_REPLY,	NULL, NULL },
    	{ OFP10_T_QUEUE_GET_CONFIG_REQUEST, NULL, NULL },
    	{ OFP10_T_QUEUE_GET_CONFIG_REPLY, NULL, NULL }
    };
    
    int
    ofp10_validate(struct switchd *sc,
        struct sockaddr_storage *src, struct sockaddr_storage *dst,
        struct ofp_header *oh, struct ibuf *ibuf)
    {
    	uint8_t	type;
    
    	if (ofp_validate_header(sc, src, dst, oh, OFP_V_1_0) != 0) {
    		log_debug("\tinvalid header");
    		return (-1);
    	}
    	if (ibuf == NULL) {
    		/* The response packet buffer is optional */
    		return (0);
    	}
    	type = oh->oh_type;
    	if (ofp10_callbacks[type].validate != NULL &&
    	    ofp10_callbacks[type].validate(sc, src, dst, oh, ibuf) != 0) {
    		log_debug("\tinvalid packet");
    		return (-1);
    	}
    	return (0);
    }
    
    int
    ofp10_validate_packet_in(struct switchd *sc,
        struct sockaddr_storage *src, struct sockaddr_storage *dst,
        struct ofp_header *oh, struct ibuf *ibuf)
    {
    	struct ofp10_packet_in	*pin;
    	uint8_t			*p;
    	size_t			 len, plen;
    	off_t			 off;
    
    	off = 0;
    	if ((pin = ibuf_seek(ibuf, off, sizeof(*pin))) == NULL)
    		return (-1);
    	log_debug("\tbuffer %d port %s "
    	    "length %u reason %u",
    	    ntohl(pin->pin_buffer_id),
    	    print_map(ntohs(pin->pin_port), ofp10_port_map),
    	    ntohs(pin->pin_total_len),
    	    pin->pin_reason);
    	off += sizeof(*pin);
    
    	len = ntohs(pin->pin_total_len);
    	plen = ibuf_length(ibuf) - off;
    
    	if (plen < len) {
    		log_debug("\ttruncated packet %zu < %zu", plen, len);
    
    		/* Buffered packets can be truncated */
    		if (pin->pin_buffer_id != OFP_PKTOUT_NO_BUFFER)
    			len = plen;
    		else
    			return (-1);
    	}
    	if ((p = ibuf_seek(ibuf, off, len)) == NULL)
    		return (-1);
    	if (sc->sc_tap != -1)
    		(void)write(sc->sc_tap, p, len);
    
    	return (0);
    }
    
    int
    ofp10_validate_packet_out(struct switchd *sc,
        struct sockaddr_storage *src, struct sockaddr_storage *dst,
        struct ofp_header *oh, struct ibuf *ibuf)
    {
    	struct ofp10_packet_out		*pout;
    	size_t				 len;
    	off_t				 off;
    	struct ofp_action_header	*ah;
    	struct ofp10_action_output	*ao;
    
    	off = 0;
    	if ((pout = ibuf_seek(ibuf, off, sizeof(*pout))) == NULL) {
    		log_debug("%s: seek failed: length %zd",
    		    __func__, ibuf_length(ibuf));
    		return (-1);
    	}
    	log_debug("\tbuffer %d port %s "
    	    "actions length %u",
    	    ntohl(pout->pout_buffer_id),
    	    print_map(ntohs(pout->pout_port), ofp10_port_map),
    	    ntohs(pout->pout_actions_len));
    	len = ntohs(pout->pout_actions_len);
    
    	off += sizeof(*pout);
    	while ((ah = ibuf_seek(ibuf, off, len)) != NULL &&
    	    ntohs(ah->ah_len) >= (uint16_t)sizeof(*ah)) {
    		switch (ntohs(ah->ah_type)) {
    		case OFP10_ACTION_OUTPUT:
    			ao = (struct ofp10_action_output *)ah;
    			log_debug("\t\taction type %s length %d "
    			    "port %s max length %d",
    			    print_map(ntohs(ao->ao_type), ofp10_action_map),
    			    ntohs(ao->ao_len),
    			    print_map(ntohs(ao->ao_port), ofp10_port_map),
    			    ntohs(ao->ao_max_len));
    			break;
    		default:
    			log_debug("\t\taction type %s length %d",
    			    print_map(ntohs(ah->ah_type), ofp10_action_map),
    			    ntohs(ah->ah_len));
    			break;
    		}
    		if (pout->pout_buffer_id == (uint32_t)-1)
    			break;
    		off += ntohs(ah->ah_len);
    	}
    
    	return (0);
    }
    
    int
    ofp10_validate_error(struct switchd *sc,
        struct sockaddr_storage *src, struct sockaddr_storage *dst,
        struct ofp_header *oh, struct ibuf *ibuf)
    {
    	struct ofp_error		*err;
    	off_t				 off;
    	const char			*code;
    
    	off = 0;
    	if ((err = ibuf_seek(ibuf, off, sizeof(*err))) == NULL) {
    		log_debug("%s: seek failed: length %zd",
    		    __func__, ibuf_length(ibuf));
    		return (-1);
    	}
    
    	switch (ntohs(err->err_type)) {
    	case OFP10_ERRTYPE_FLOW_MOD_FAILED:
    		code = print_map(ntohs(err->err_code), ofp10_errflowmod_map);
    		break;
    	default:
    		code = NULL;
    		break;
    	}
    
    	log_debug("\terror type %s code %u%s%s",
    	    print_map(ntohs(err->err_type), ofp10_errtype_map),
    	    ntohs(err->err_code),
    	    code == NULL ? "" : ": ",
    	    code == NULL ? "" : code);
    
    	return (0);
    }
    
    int
    ofp10_input(struct switchd *sc, struct switch_connection *con,
        struct ofp_header *oh, struct ibuf *ibuf)
    {
    	if (ofp10_validate(sc, &con->con_peer, &con->con_local, oh, ibuf) != 0)
    		return (-1);
    
    	if (ofp10_callbacks[oh->oh_type].cb == NULL) {
    		log_debug("message not supported: %s",
    		    print_map(oh->oh_type, ofp10_t_map));
    		return (-1);
    	}
    	if (ofp10_callbacks[oh->oh_type].cb(sc, con, oh, ibuf) != 0)
    		return (-1);
    
    	return (0);
    }
    
    int
    ofp10_hello(struct switchd *sc, struct switch_connection *con,
        struct ofp_header *oh, struct ibuf *ibuf)
    {
    	if (switch_add(con) == NULL) {
    		log_debug("%s: failed to add switch", __func__);
    		return (-1);
    	}
    
    	if (ofp_recv_hello(sc, con, oh, ibuf) == -1)
    		return (-1);
    
    	return (ofp_nextstate(sc, con, OFP_STATE_FEATURE_WAIT));
    }
    
    int
    ofp10_features_reply(struct switchd *sc, struct switch_connection *con,
        struct ofp_header *oh, struct ibuf *ibuf)
    {
    	return (ofp_nextstate(sc, con, OFP_STATE_ESTABLISHED));
    }
    
    int
    ofp10_validate_features_reply(struct switchd *sc,
        struct sockaddr_storage *src, struct sockaddr_storage *dst,
        struct ofp_header *oh, struct ibuf *ibuf)
    {
    	struct ofp_switch_features	*swf;
    	struct ofp10_phy_port		*swp;
    	off_t				 poff;
    	int				 portslen;
    	char				*mac;
    
    	if ((swf = ibuf_seek(ibuf, 0, sizeof(*swf))) == NULL)
    		return (-1);
    
    	log_debug("\tdatapath_id %#016llx nbuffers %u ntables %d "
    	    "capabilities %#08x actions %#08x",
    	    be64toh(swf->swf_datapath_id), ntohl(swf->swf_nbuffers),
    	    swf->swf_ntables, ntohl(swf->swf_capabilities),
    	    ntohl(swf->swf_actions));
    
    	poff = sizeof(*swf);
    	portslen = ntohs(oh->oh_length) - sizeof(*swf);
    	if (portslen <= 0)
    		return (0);
    
    	while (portslen > 0) {
    		if ((swp = ibuf_seek(ibuf, poff, sizeof(*swp))) == NULL)
    			return (-1);
    
    		mac = ether_ntoa((void *)swp->swp_macaddr);
    		log_debug("no %s macaddr %s name %s config %#08x state %#08x "
    		    "cur %#08x advertised %#08x supported %#08x peer %#08x",
    		    print_map(ntohs(swp->swp_number), ofp10_port_map), mac,
    		    swp->swp_name, swp->swp_config, swp->swp_state,
    		    swp->swp_cur, swp->swp_advertised, swp->swp_supported,
    		    swp->swp_peer);
    
    		portslen -= sizeof(*swp);
    		poff += sizeof(*swp);
    	}
    
    	return (0);
    }
    
    int
    ofp10_echo_request(struct switchd *sc, struct switch_connection *con,
        struct ofp_header *oh, struct ibuf *ibuf)
    {
    	/* Echo reply */
    	oh->oh_type = OFP10_T_ECHO_REPLY;
    	if (ofp10_validate(sc, &con->con_local, &con->con_peer, oh, NULL) != 0)
    		return (-1);
    	ofp_output(con, oh, NULL);
    
    	return (0);
    }
    
    int
    ofp10_packet_match(struct packet *pkt, struct ofp10_match *m, uint32_t flags)
    {
    	struct ether_header	*eh = pkt->pkt_eh;
    
    	memset(m, 0, sizeof(*m));
    	m->m_wildcards = htonl(~flags);
    
    	if ((flags & (OFP10_WILDCARD_DL_SRC|OFP10_WILDCARD_DL_DST)) &&
    	    (eh == NULL))
    		return (-1);
    
    	if (flags & OFP10_WILDCARD_DL_SRC)
    		memcpy(m->m_dl_src, eh->ether_shost, ETHER_ADDR_LEN);
    	if (flags & OFP10_WILDCARD_DL_DST)
    		memcpy(m->m_dl_dst, eh->ether_dhost, ETHER_ADDR_LEN);
    
    	return (0);
    }
    
    int
    ofp10_packet_in(struct switchd *sc, struct switch_connection *con,
        struct ofp_header *ih, struct ibuf *ibuf)
    {
    	struct ofp10_packet_in		*pin;
    	struct ofp10_packet_out		*pout;
    	struct ofp10_action_output	*ao;
    	struct ofp10_flow_mod		*fm;
    	struct ofp_header		*oh;
    	struct packet			 pkt;
    	struct ibuf			*obuf = NULL;
    	int				 ret = -1;
    	size_t				 len;
    	uint32_t			 srcport, dstport;
    	int				 addflow = 0;
    	int				 addpacket = 0;
    
    	if ((pin = ibuf_getdata(ibuf, sizeof(*pin))) == NULL)
    		return (-1);
    
    	memset(&pkt, 0, sizeof(pkt));
    	len = ntohs(pin->pin_total_len);
    
    	srcport = ntohs(pin->pin_port);
    
    	if (packet_ether_input(ibuf, len, &pkt) == -1 &&
    	    pin->pin_buffer_id == htonl(OFP_PKTOUT_NO_BUFFER))
    		return(-1);
    
    	if (packet_input(sc, con->con_switch,
    	    srcport, &dstport, &pkt) == -1 ||
    	    (dstport > OFP10_PORT_MAX &&
    	    dstport != OFP10_PORT_LOCAL &&
    	    dstport != OFP10_PORT_CONTROLLER)) {
    		/* fallback to flooding */
    		dstport = OFP10_PORT_FLOOD;
    	} else if (srcport == dstport) {
    		/*
    		 * silently drop looping packet
    		 * (don't use OFP10_PORT_INPUT here)
    		 */
    		ret = 0;
    		goto done;
    	} else {
    		addflow = 1;
    	}
    
    	if ((obuf = ibuf_static()) == NULL)
    		goto done;
    
     again:
    	if (addflow) {
    		if ((fm = ibuf_advance(obuf, sizeof(*fm))) == NULL)
    			goto done;
    
    		ofp10_packet_match(&pkt, &fm->fm_match, OFP10_WILDCARD_DL_DST);
    
    		oh = &fm->fm_oh;
    		fm->fm_cookie = 0; /* XXX should we set a cookie? */
    		fm->fm_command = htons(OFP_FLOWCMD_ADD);
    		fm->fm_idle_timeout = htons(sc->sc_cache_timeout);
    		fm->fm_hard_timeout = 0; /* permanent */
    		fm->fm_priority = 0;
    		fm->fm_buffer_id = pin->pin_buffer_id;
    		fm->fm_flags = htons(OFP_FLOWFLAG_SEND_FLOW_REMOVED);
    		if (pin->pin_buffer_id == htonl(OFP_PKTOUT_NO_BUFFER))
    			addpacket = 1;
    	} else {
    		if ((pout = ibuf_advance(obuf, sizeof(*pout))) == NULL)
    			goto done;
    
    		oh = &pout->pout_oh;
    		pout->pout_buffer_id = pin->pin_buffer_id;
    		pout->pout_port = pin->pin_port;
    		pout->pout_actions_len = htons(sizeof(*ao));
    
    		if (pin->pin_buffer_id == htonl(OFP_PKTOUT_NO_BUFFER))
    			addpacket = 1;
    	}
    
    	if ((ao = ibuf_advance(obuf, sizeof(*ao))) == NULL)
    		goto done;
    	ao->ao_type = htons(OFP_ACTION_OUTPUT);
    	ao->ao_len =  htons(sizeof(*ao));
    	ao->ao_port = htons((uint16_t)dstport);
    	ao->ao_max_len = 0;
    
    	/* Add optional packet payload to packet-out. */
    	if (addflow == 0 && addpacket &&
    	    imsg_add(obuf, pkt.pkt_buf, pkt.pkt_len) == -1)
    		goto done;
    
    	/* Set output header */
    	memcpy(oh, ih, sizeof(*oh));
    	oh->oh_length = htons(ibuf_length(obuf));
    	oh->oh_type = addflow ? OFP10_T_FLOW_MOD : OFP10_T_PACKET_OUT;
    	oh->oh_xid = htonl(con->con_xidnxt++);
    
    	if (ofp10_validate(sc, &con->con_local, &con->con_peer, oh, obuf) != 0)
    		goto done;
    
    	ofp_output(con, NULL, obuf);
    
    	if (addflow && addpacket) {
    		/* loop to output the packet again */
    		addflow = 0;
    		if ((obuf = ibuf_static()) == NULL)
    			goto done;
    		goto again;
    	}
    
    	ret = 0;
     done:
    	ibuf_release(obuf);
    	return (ret);
    }