/* * Copyright (c) 2014, Cisco Systems, Inc. All rights reserved. * * LICENSE_BEGIN * * This software is available to you under a choice of one of two * licenses. You may choose to be licensed under the terms of the GNU * General Public License (GPL) Version 2, available from the file * COPYING in the main directory of this source tree, or the * BSD license below: * * Redistribution and use in source and binary forms, with or * without modification, are permitted provided that the following * conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the following * disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * LICENSE_END * * */ #include #include #include #include #include #include #include #include #include #include #include "usnic_ip_utils.h" #include "libnl_utils.h" #include "usnic_direct.h" #include "usd.h" #include "usd_queue.h" #include "usd_time.h" #include "usd_dest.h" #include "usd_socket.h" extern TAILQ_HEAD(, usd_device) usd_device_list; static struct usd_dest_params usd_dest_params = { .dp_arp_timeout = 1000, .dp_max_arps = 10 }; int usd_get_dest_distance( struct usd_device *dev, uint32_t daddr_be, int *metric_o) { uint32_t nh_ip_addr; int ret; ret = usnic_nl_rt_lookup(dev->ud_attrs.uda_ipaddr_be, daddr_be, dev->ud_attrs.uda_ifindex, &nh_ip_addr); if (ret != 0) { *metric_o = -1; ret = 0; } else if (nh_ip_addr == 0) { *metric_o = 0; } else { *metric_o = 1; } return ret; } static void usd_dest_set_complete( struct usd_device *dev, struct usd_dest_req *req) { req->udr_complete = 1; if (req->udr_status != 0 && req->udr_dest != NULL) { free(req->udr_dest); req->udr_dest = NULL; } TAILQ_REMOVE(&dev->ud_pending_reqs, req, udr_link); TAILQ_INSERT_TAIL(&dev->ud_completed_reqs, req, udr_link); } static int usd_dest_trigger_arp( struct usd_device *dev, struct usd_dest_req *req) { int ret; usd_get_time(&req->udr_last_arp); req->udr_arps_sent++; ret = usnic_arp_request(req->udr_daddr_be, dev->ud_arp_sockfd); return ret; } static int usd_check_dest_resolved( struct usd_device *dev, struct usd_dest_req *req) { struct ether_header *eth; int ret; eth = &req->udr_dest->ds_dest.ds_udp.u_hdr.uh_eth; ret = usnic_arp_lookup(dev->ud_attrs.uda_ifname, req->udr_daddr_be, dev->ud_arp_sockfd, ð->ether_dhost[0]); if (ret == EAGAIN) return -EAGAIN; /* for better or worse, resolution is complete */ req->udr_status = -ret; return 0; } /* * Loop through the ordered pending create_dest request queue. * If an entry is complete, move it to the completed queue. * If the retry timeout for an entry has arrived, re-trigger the ARP */ static void usd_dest_progress_dev( struct usd_device *dev) { struct usd_dest_req *req; struct usd_dest_req *tmpreq; usd_time_t now; int delta; int ret; usd_get_time(&now); TAILQ_FOREACH_SAFE(req, tmpreq, &dev->ud_pending_reqs, udr_link) { /* resolution complete? */ ret = usd_check_dest_resolved(dev, req); if (ret == 0) { usd_dest_set_complete(dev, req); continue; } /* time for next ARP trigger? */ delta = usd_time_diff(req->udr_last_arp, now); if (delta > (int) usd_dest_params.dp_arp_timeout) { if (req->udr_arps_sent >= usd_dest_params.dp_max_arps) { req->udr_status = -EHOSTUNREACH; usd_dest_set_complete(dev, req); continue; } ret = usd_dest_trigger_arp(dev, req); if (ret != 0) { req->udr_status = ret; usd_dest_set_complete(dev, req); } } } } static void usd_dest_progress(void) { struct usd_device *dev; TAILQ_FOREACH(dev, &usd_device_list, ud_link) { usd_dest_progress_dev(dev); } } /* * Fill in all of a header except the dest MAC and the UDP ports * specified remote host */ void usd_fill_udp_dest( struct usd_dest *dest, struct usd_device_attrs *dap, uint32_t daddr_be, uint16_t dport_be) { struct ether_header eth = { .ether_type = htons(0x0800) }; struct udphdr udp = { .dest = dport_be }; struct iphdr ip = { .saddr = dap->uda_ipaddr_be, .daddr = daddr_be, .protocol = IPPROTO_UDP, .version = 4, .frag_off = 0, .ihl = 5, /* no options */ .tos = 0, .ttl = 8 }; /* Workaround taking a pointer to an element of a packed structure due to * warnings in Clang 4.0.1 and beyond. */ memcpy(eth.ether_shost, dap->uda_mac_addr, ETH_ALEN); dest->ds_dest.ds_udp.u_hdr.uh_eth = eth; dest->ds_dest.ds_udp.u_hdr.uh_udp = udp; dest->ds_dest.ds_udp.u_hdr.uh_ip = ip; } static int usd_create_udp_dest_start( struct usd_device *dev, uint32_t daddr_be, uint16_t dport_be, struct usd_dest_req **req_o) { struct usd_dest_req *req; struct usd_dest *dest; uint32_t first_hop_daddr_be; int ret; /* catch a mistake that will almost always lead to hung programs */ if (daddr_be == 0 || dport_be == 0) { return -EINVAL; } req = calloc(sizeof(*req), 1); dest = calloc(sizeof(*dest), 1); if (req == NULL || dest == NULL) { ret = -errno; goto fail; } ret = usnic_nl_rt_lookup(dev->ud_attrs.uda_ipaddr_be, daddr_be, dev->ud_attrs.uda_ifindex, &first_hop_daddr_be); if (ret != 0) { /* EHOSTUNREACH is non-fatal, but we are done with resolution */ if (ret == EHOSTUNREACH) { req->udr_status = -EHOSTUNREACH; free(dest); goto complete; } else { ret = -ret; } goto fail; } if (first_hop_daddr_be == 0) first_hop_daddr_be = daddr_be; /* Fill in dest as much as we can */ usd_fill_udp_dest(dest, &dev->ud_attrs, daddr_be, dport_be); /* initiate request and add to tail of pending list */ req->udr_daddr_be = first_hop_daddr_be; req->udr_dest = dest; ret = usd_dest_trigger_arp(dev, req); if (ret != 0) goto fail; complete: TAILQ_INSERT_TAIL(&dev->ud_pending_reqs, req, udr_link); if (req->udr_status != 0) { usd_dest_set_complete(dev, req); } *req_o = req; return 0; fail: if (req != NULL) free(req); if (dest != NULL) free(dest); return ret; } /* * synchronously create a UDP destination by initiating the * resolution, then waiting for it to complete */ static int usd_create_udp_dest( struct usd_device *dev, uint32_t daddr_be, uint16_t dport_be, struct usd_dest **dest_o) { struct usd_dest_req *req; int ret; ret = usd_create_udp_dest_start(dev, daddr_be, dport_be, &req); if (ret != 0) return ret; /* loop until request completes or times out */ while (req->udr_complete == 0) { usd_dest_progress(); } ret = req->udr_status; if (ret == 0) *dest_o = req->udr_dest; TAILQ_REMOVE(&dev->ud_completed_reqs, req, udr_link); free(req); return ret; } /* * Build and save a IP header appropriate for sending to the * specified remote host */ int usd_create_ip_dest( struct usd_device *dev, uint32_t daddr_be, struct usd_dest **dest_o) { int ret; ret = usd_create_udp_dest(dev, daddr_be, 0, dest_o); return ret; } void usd_dest_set_udp_ports( struct usd_dest *dest, struct usd_qp *src_uqp, uint16_t dest_port_be) { struct usd_qp_impl *qp = to_qpi(src_uqp); struct udphdr udp = { .source = qp->uq_attrs.uqa_local_addr.ul_addr.ul_udp.u_addr.sin_port, .dest = dest_port_be }; /* Workaround taking a pointer to an element of a packed structure due to * warnings in Clang 4.0.1 and beyond. */ dest->ds_dest.ds_udp.u_hdr.uh_udp = udp; } /* * Synchronously creates a destination */ int usd_create_dest( struct usd_device *dev, uint32_t daddr_be, uint16_t dport_be, struct usd_dest **dest_o) { int ret; ret = usd_create_udp_dest(dev, daddr_be, dport_be, dest_o); return ret; } int usd_destroy_dest( struct usd_dest *dest) { if (dest != NULL) { free(dest); } return 0; } /* * Get address resolution settings */ int usd_get_dest_params( struct usd_dest_params *params) { if (params == NULL) return -EINVAL; *params = usd_dest_params; return 0; } /* * Set address resolution settings * Settings may not be changed while any resolution requests are in progress. */ int usd_set_dest_params( struct usd_dest_params *params) { if (params == NULL) return -EINVAL; /* blindly set parameters, allowing user to shoot self if desired */ usd_dest_params.dp_arp_timeout = params->dp_arp_timeout; usd_dest_params.dp_max_arps = params->dp_max_arps; return 0; } /* * Start destination creation * Resolution progress is performed in usd_create_dest_query() and * usd_create_dest_poll() */ int usd_create_dest_start( struct usd_device *dev, uint32_t daddr_be, uint16_t dport_be, void *context) { struct usd_dest_req *req; int ret; req = NULL; ret = usd_create_udp_dest_start(dev, daddr_be, dport_be, &req); if (ret == 0) { req->udr_context = context; } return ret; } /* * Return first completed destinatin request */ int usd_create_dest_poll( struct usd_device *dev, void **context_o, int *status, struct usd_dest **dest_o) { struct usd_dest_req *req; usd_dest_progress(); if (!TAILQ_EMPTY(&dev->ud_completed_reqs)) { req = TAILQ_FIRST(&dev->ud_completed_reqs); TAILQ_REMOVE(&dev->ud_completed_reqs, req, udr_link); *context_o = req->udr_context; *status = req->udr_status; if (*status == 0) *dest_o = req->udr_dest; free(req); return 0; } else { return -EAGAIN; } } /* * Check completion of a particular request */ int usd_create_dest_query( struct usd_device *dev, void *context, int *status, struct usd_dest **dest_o) { struct usd_dest_req *req; usd_dest_progress(); TAILQ_FOREACH(req, &dev->ud_completed_reqs, udr_link) { if (req->udr_context == context) { TAILQ_REMOVE(&dev->ud_completed_reqs, req, udr_link); *status = req->udr_status; if (*status == 0) *dest_o = req->udr_dest; free(req); return 0; } } return -EAGAIN; } /* * Cancel a destination creation in progress * Look through both the pending and completed queues, simply * squash the record if we find it. */ int usd_create_dest_cancel( struct usd_device *dev, void *context) { struct usd_dest_req *req; TAILQ_FOREACH(req, &dev->ud_pending_reqs, udr_link) { if (req->udr_context == context) { TAILQ_REMOVE(&dev->ud_pending_reqs, req, udr_link); goto found; } } TAILQ_FOREACH(req, &dev->ud_completed_reqs, udr_link) { if (req->udr_context == context) { TAILQ_REMOVE(&dev->ud_completed_reqs, req, udr_link); goto found; } } return -EINVAL; found: free(req->udr_dest); free(req); return 0; } /* * Create a destination given a MAC address */ int usd_create_dest_with_mac( struct usd_device *dev, uint32_t daddr_be, uint16_t dport_be, uint8_t * dmac, struct usd_dest **dest_o) { struct ether_header *eth; struct usd_dest *dest; dest = calloc(sizeof(*dest), 1); if (dest == NULL) return -errno; /* Fill in dest as much as we can */ usd_fill_udp_dest(dest, &dev->ud_attrs, daddr_be, dport_be); /* copy in MAC from caller */ eth = &dest->ds_dest.ds_udp.u_hdr.uh_eth; memcpy(ð->ether_dhost[0], dmac, ETH_ALEN); *dest_o = dest; return 0; } /* * Expand a destination */ int usd_expand_dest( struct usd_dest *dest, uint32_t *ip_be_o, uint16_t *port_be_o) { if (ip_be_o != NULL) { *ip_be_o = dest->ds_dest.ds_udp.u_hdr.uh_ip.daddr; } if (port_be_o != NULL) { *port_be_o = dest->ds_dest.ds_udp.u_hdr.uh_udp.dest; } return 0; }