diff options
author | Luca Muscariello <lumuscar+fdio@cisco.com> | 2017-02-24 07:59:45 +0000 |
---|---|---|
committer | Gerrit Code Review <gerrit@fd.io> | 2017-02-24 07:59:45 +0000 |
commit | e0ec678c016fbce560e7f94973d4fada3ba5e799 (patch) | |
tree | a171ed331a830510f3064a6ad99b296ac1b2d2bc /metis/ccnx/forwarder/metis/platforms | |
parent | f28308bd99381ef5f1e178e2e1f870f245e35873 (diff) | |
parent | c580a00aac271a524e5a75b35f4b91c174ed227b (diff) |
Merge "Initial commit: sb-forwarder, metis." into sb-forwarder/master
Diffstat (limited to 'metis/ccnx/forwarder/metis/platforms')
16 files changed, 4145 insertions, 0 deletions
diff --git a/metis/ccnx/forwarder/metis/platforms/README.txt b/metis/ccnx/forwarder/metis/platforms/README.txt new file mode 100644 index 00000000..4670bd02 --- /dev/null +++ b/metis/ccnx/forwarder/metis/platforms/README.txt @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +Operating system dependent modules. + +We currently do not support BSD, so its module is not part of the build. +It's also not finished. + diff --git a/metis/ccnx/forwarder/metis/platforms/android/ifaddrs.c b/metis/ccnx/forwarder/metis/platforms/android/ifaddrs.c new file mode 100644 index 00000000..338fff88 --- /dev/null +++ b/metis/ccnx/forwarder/metis/platforms/android/ifaddrs.c @@ -0,0 +1,600 @@ +/* +Copyright (c) 2013, Kenneth MacKay +All rights reserved. + +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. +*/ + +#include "ifaddrs.h" + +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> +#include <sys/socket.h> +#include <net/if_arp.h> +#include <netinet/in.h> +#include <linux/netlink.h> +#include <linux/rtnetlink.h> + +typedef struct NetlinkList +{ + struct NetlinkList *m_next; + struct nlmsghdr *m_data; + unsigned int m_size; +} NetlinkList; + +static int netlink_socket(void) +{ + int l_socket = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if(l_socket < 0) + { + return -1; + } + + struct sockaddr_nl l_addr; + memset(&l_addr, 0, sizeof(l_addr)); + l_addr.nl_family = AF_NETLINK; + if(bind(l_socket, (struct sockaddr *)&l_addr, sizeof(l_addr)) < 0) + { + close(l_socket); + return -1; + } + + return l_socket; +} + +static int netlink_send(int p_socket, int p_request) +{ + char l_buffer[NLMSG_ALIGN(sizeof(struct nlmsghdr)) + NLMSG_ALIGN(sizeof(struct rtgenmsg))]; + memset(l_buffer, 0, sizeof(l_buffer)); + struct nlmsghdr *l_hdr = (struct nlmsghdr *)l_buffer; + struct rtgenmsg *l_msg = (struct rtgenmsg *)NLMSG_DATA(l_hdr); + + l_hdr->nlmsg_len = NLMSG_LENGTH(sizeof(*l_msg)); + l_hdr->nlmsg_type = p_request; + l_hdr->nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST; + l_hdr->nlmsg_pid = 0; + l_hdr->nlmsg_seq = p_socket; + l_msg->rtgen_family = AF_UNSPEC; + + struct sockaddr_nl l_addr; + memset(&l_addr, 0, sizeof(l_addr)); + l_addr.nl_family = AF_NETLINK; + return (sendto(p_socket, l_hdr, l_hdr->nlmsg_len, 0, (struct sockaddr *)&l_addr, sizeof(l_addr))); +} + +static int netlink_recv(int p_socket, void *p_buffer, size_t p_len) +{ + struct msghdr l_msg; + struct iovec l_iov = { p_buffer, p_len }; + struct sockaddr_nl l_addr; + int l_result; + + for(;;) + { + l_msg.msg_name = (void *)&l_addr; + l_msg.msg_namelen = sizeof(l_addr); + l_msg.msg_iov = &l_iov; + l_msg.msg_iovlen = 1; + l_msg.msg_control = NULL; + l_msg.msg_controllen = 0; + l_msg.msg_flags = 0; + int l_result = recvmsg(p_socket, &l_msg, 0); + + if(l_result < 0) + { + if(errno == EINTR) + { + continue; + } + return -2; + } + + if(l_msg.msg_flags & MSG_TRUNC) + { // buffer was too small + return -1; + } + return l_result; + } +} + +static struct nlmsghdr *getNetlinkResponse(int p_socket, int *p_size, int *p_done) +{ + size_t l_size = 4096; + void *l_buffer = NULL; + + for(;;) + { + free(l_buffer); + l_buffer = malloc(l_size); + + int l_read = netlink_recv(p_socket, l_buffer, l_size); + *p_size = l_read; + if(l_read == -2) + { + free(l_buffer); + return NULL; + } + if(l_read >= 0) + { + pid_t l_pid = getpid(); + struct nlmsghdr *l_hdr; + for(l_hdr = (struct nlmsghdr *)l_buffer; NLMSG_OK(l_hdr, (unsigned int)l_read); l_hdr = (struct nlmsghdr *)NLMSG_NEXT(l_hdr, l_read)) + { + if((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket) + { + continue; + } + + if(l_hdr->nlmsg_type == NLMSG_DONE) + { + *p_done = 1; + break; + } + + if(l_hdr->nlmsg_type == NLMSG_ERROR) + { + free(l_buffer); + return NULL; + } + } + return l_buffer; + } + + l_size *= 2; + } +} + +static NetlinkList *newListItem(struct nlmsghdr *p_data, unsigned int p_size) +{ + NetlinkList *l_item = malloc(sizeof(NetlinkList)); + l_item->m_next = NULL; + l_item->m_data = p_data; + l_item->m_size = p_size; + return l_item; +} + +static void freeResultList(NetlinkList *p_list) +{ + NetlinkList *l_cur; + while(p_list) + { + l_cur = p_list; + p_list = p_list->m_next; + free(l_cur->m_data); + free(l_cur); + } +} + +static NetlinkList *getResultList(int p_socket, int p_request) +{ + if(netlink_send(p_socket, p_request) < 0) + { + return NULL; + } + + NetlinkList *l_list = NULL; + NetlinkList *l_end = NULL; + int l_size; + int l_done = 0; + while(!l_done) + { + struct nlmsghdr *l_hdr = getNetlinkResponse(p_socket, &l_size, &l_done); + if(!l_hdr) + { // error + freeResultList(l_list); + return NULL; + } + + NetlinkList *l_item = newListItem(l_hdr, l_size); + if(!l_list) + { + l_list = l_item; + } + else + { + l_end->m_next = l_item; + } + l_end = l_item; + } + return l_list; +} + +static size_t maxSize(size_t a, size_t b) +{ + return (a > b ? a : b); +} + +static size_t calcAddrLen(sa_family_t p_family, int p_dataSize) +{ + switch(p_family) + { + case AF_INET: + return sizeof(struct sockaddr_in); + case AF_INET6: + return sizeof(struct sockaddr_in6); + case AF_PACKET: + return maxSize(sizeof(struct sockaddr_ll), offsetof(struct sockaddr_ll, sll_addr) + p_dataSize); + default: + return maxSize(sizeof(struct sockaddr), offsetof(struct sockaddr, sa_data) + p_dataSize); + } +} + +static void makeSockaddr(sa_family_t p_family, struct sockaddr *p_dest, void *p_data, size_t p_size) +{ + switch(p_family) + { + case AF_INET: + memcpy(&((struct sockaddr_in*)p_dest)->sin_addr, p_data, p_size); + break; + case AF_INET6: + memcpy(&((struct sockaddr_in6*)p_dest)->sin6_addr, p_data, p_size); + break; + case AF_PACKET: + memcpy(((struct sockaddr_ll*)p_dest)->sll_addr, p_data, p_size); + ((struct sockaddr_ll*)p_dest)->sll_halen = p_size; + break; + default: + memcpy(p_dest->sa_data, p_data, p_size); + break; + } + p_dest->sa_family = p_family; +} + +static void addToEnd(struct ifaddrs **p_resultList, struct ifaddrs *p_entry) +{ + if(!*p_resultList) + { + *p_resultList = p_entry; + } + else + { + struct ifaddrs *l_cur = *p_resultList; + while(l_cur->ifa_next) + { + l_cur = l_cur->ifa_next; + } + l_cur->ifa_next = p_entry; + } +} + +static void interpretLink(struct nlmsghdr *p_hdr, struct ifaddrs **p_links, struct ifaddrs **p_resultList) +{ + struct ifinfomsg *l_info = (struct ifinfomsg *)NLMSG_DATA(p_hdr); + + size_t l_nameSize = 0; + size_t l_addrSize = 0; + size_t l_dataSize = 0; + + size_t l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifinfomsg)); + struct rtattr *l_rta; + for(l_rta = (struct rtattr *)(((char *)l_info) + NLMSG_ALIGN(sizeof(struct ifinfomsg))); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize)) + { + void *l_rtaData = RTA_DATA(l_rta); + size_t l_rtaDataSize = RTA_PAYLOAD(l_rta); + switch(l_rta->rta_type) + { + case IFLA_ADDRESS: + case IFLA_BROADCAST: + l_addrSize += NLMSG_ALIGN(calcAddrLen(AF_PACKET, l_rtaDataSize)); + break; + case IFLA_IFNAME: + l_nameSize += NLMSG_ALIGN(l_rtaSize + 1); + break; + case IFLA_STATS: + l_dataSize += NLMSG_ALIGN(l_rtaSize); + break; + default: + break; + } + } + + struct ifaddrs *l_entry = malloc(sizeof(struct ifaddrs) + l_nameSize + l_addrSize + l_dataSize); + memset(l_entry, 0, sizeof(struct ifaddrs)); + l_entry->ifa_name = ""; + + char *l_name = ((char *)l_entry) + sizeof(struct ifaddrs); + char *l_addr = l_name + l_nameSize; + char *l_data = l_addr + l_addrSize; + + l_entry->ifa_flags = l_info->ifi_flags; + + l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifinfomsg)); + for(l_rta = (struct rtattr *)(((char *)l_info) + NLMSG_ALIGN(sizeof(struct ifinfomsg))); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize)) + { + void *l_rtaData = RTA_DATA(l_rta); + size_t l_rtaDataSize = RTA_PAYLOAD(l_rta); + switch(l_rta->rta_type) + { + case IFLA_ADDRESS: + case IFLA_BROADCAST: + { + size_t l_addrLen = calcAddrLen(AF_PACKET, l_rtaDataSize); + makeSockaddr(AF_PACKET, (struct sockaddr *)l_addr, l_rtaData, l_rtaDataSize); + ((struct sockaddr_ll *)l_addr)->sll_ifindex = l_info->ifi_index; + ((struct sockaddr_ll *)l_addr)->sll_hatype = l_info->ifi_type; + if(l_rta->rta_type == IFLA_ADDRESS) + { + l_entry->ifa_addr = (struct sockaddr *)l_addr; + } + else + { + l_entry->ifa_broadaddr = (struct sockaddr *)l_addr; + } + l_addr += NLMSG_ALIGN(l_addrLen); + break; + } + case IFLA_IFNAME: + strncpy(l_name, l_rtaData, l_rtaDataSize); + l_name[l_rtaDataSize] = '\0'; + l_entry->ifa_name = l_name; + break; + case IFLA_STATS: + memcpy(l_data, l_rtaData, l_rtaDataSize); + l_entry->ifa_data = l_data; + break; + default: + break; + } + } + + addToEnd(p_resultList, l_entry); + p_links[l_info->ifi_index - 1] = l_entry; +} + +static void interpretAddr(struct nlmsghdr *p_hdr, struct ifaddrs **p_links, struct ifaddrs **p_resultList) +{ + struct ifaddrmsg *l_info = (struct ifaddrmsg *)NLMSG_DATA(p_hdr); + + size_t l_nameSize = 0; + size_t l_addrSize = 0; + + int l_addedNetmask = 0; + + size_t l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifaddrmsg)); + struct rtattr *l_rta; + for(l_rta = (struct rtattr *)(((char *)l_info) + NLMSG_ALIGN(sizeof(struct ifaddrmsg))); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize)) + { + void *l_rtaData = RTA_DATA(l_rta); + size_t l_rtaDataSize = RTA_PAYLOAD(l_rta); + if(l_info->ifa_family == AF_PACKET) + { + continue; + } + + switch(l_rta->rta_type) + { + case IFA_ADDRESS: + case IFA_LOCAL: + if((l_info->ifa_family == AF_INET || l_info->ifa_family == AF_INET6) && !l_addedNetmask) + { // make room for netmask + l_addrSize += NLMSG_ALIGN(calcAddrLen(l_info->ifa_family, l_rtaDataSize)); + l_addedNetmask = 1; + } + case IFA_BROADCAST: + l_addrSize += NLMSG_ALIGN(calcAddrLen(l_info->ifa_family, l_rtaDataSize)); + break; + case IFA_LABEL: + l_nameSize += NLMSG_ALIGN(l_rtaSize + 1); + break; + default: + break; + } + } + + struct ifaddrs *l_entry = malloc(sizeof(struct ifaddrs) + l_nameSize + l_addrSize); + memset(l_entry, 0, sizeof(struct ifaddrs)); + l_entry->ifa_name = p_links[l_info->ifa_index - 1]->ifa_name; + + char *l_name = ((char *)l_entry) + sizeof(struct ifaddrs); + char *l_addr = l_name + l_nameSize; + + l_entry->ifa_flags = l_info->ifa_flags | p_links[l_info->ifa_index - 1]->ifa_flags; + + l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifaddrmsg)); + for(l_rta = (struct rtattr *)(((char *)l_info) + NLMSG_ALIGN(sizeof(struct ifaddrmsg))); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize)) + { + void *l_rtaData = RTA_DATA(l_rta); + size_t l_rtaDataSize = RTA_PAYLOAD(l_rta); + switch(l_rta->rta_type) + { + case IFA_ADDRESS: + case IFA_BROADCAST: + case IFA_LOCAL: + { + size_t l_addrLen = calcAddrLen(l_info->ifa_family, l_rtaDataSize); + makeSockaddr(l_info->ifa_family, (struct sockaddr *)l_addr, l_rtaData, l_rtaDataSize); + if(l_info->ifa_family == AF_INET6) + { + if(IN6_IS_ADDR_LINKLOCAL((struct in6_addr *)l_rtaData) || IN6_IS_ADDR_MC_LINKLOCAL((struct in6_addr *)l_rtaData)) + { + ((struct sockaddr_in6 *)l_addr)->sin6_scope_id = l_info->ifa_index; + } + } + + if(l_rta->rta_type == IFA_ADDRESS) + { // apparently in a point-to-point network IFA_ADDRESS contains the dest address and IFA_LOCAL contains the local address + if(l_entry->ifa_addr) + { + l_entry->ifa_dstaddr = (struct sockaddr *)l_addr; + } + else + { + l_entry->ifa_addr = (struct sockaddr *)l_addr; + } + } + else if(l_rta->rta_type == IFA_LOCAL) + { + if(l_entry->ifa_addr) + { + l_entry->ifa_dstaddr = l_entry->ifa_addr; + } + l_entry->ifa_addr = (struct sockaddr *)l_addr; + } + else + { + l_entry->ifa_broadaddr = (struct sockaddr *)l_addr; + } + l_addr += NLMSG_ALIGN(l_addrLen); + break; + } + case IFA_LABEL: + strncpy(l_name, l_rtaData, l_rtaDataSize); + l_name[l_rtaDataSize] = '\0'; + l_entry->ifa_name = l_name; + break; + default: + break; + } + } + + if(l_entry->ifa_addr && (l_entry->ifa_addr->sa_family == AF_INET || l_entry->ifa_addr->sa_family == AF_INET6)) + { + unsigned l_maxPrefix = (l_entry->ifa_addr->sa_family == AF_INET ? 32 : 128); + unsigned l_prefix = (l_info->ifa_prefixlen > l_maxPrefix ? l_maxPrefix : l_info->ifa_prefixlen); + char l_mask[16] = {0}; + unsigned i; + for(i=0; i<(l_prefix/8); ++i) + { + l_mask[i] = 0xff; + } + l_mask[i] = 0xff << (8 - (l_prefix % 8)); + + makeSockaddr(l_entry->ifa_addr->sa_family, (struct sockaddr *)l_addr, l_mask, l_maxPrefix / 8); + l_entry->ifa_netmask = (struct sockaddr *)l_addr; + } + + addToEnd(p_resultList, l_entry); +} + +static void interpret(int p_socket, NetlinkList *p_netlinkList, struct ifaddrs **p_links, struct ifaddrs **p_resultList) +{ + pid_t l_pid = getpid(); + for(; p_netlinkList; p_netlinkList = p_netlinkList->m_next) + { + unsigned int l_nlsize = p_netlinkList->m_size; + struct nlmsghdr *l_hdr; + for(l_hdr = p_netlinkList->m_data; NLMSG_OK(l_hdr, l_nlsize); l_hdr = NLMSG_NEXT(l_hdr, l_nlsize)) + { + if((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket) + { + continue; + } + + if(l_hdr->nlmsg_type == NLMSG_DONE) + { + break; + } + + if(l_hdr->nlmsg_type == RTM_NEWLINK) + { + interpretLink(l_hdr, p_links, p_resultList); + } + else if(l_hdr->nlmsg_type == RTM_NEWADDR) + { + interpretAddr(l_hdr, p_links, p_resultList); + } + } + } +} + +static unsigned countLinks(int p_socket, NetlinkList *p_netlinkList) +{ + unsigned l_links = 0; + pid_t l_pid = getpid(); + for(; p_netlinkList; p_netlinkList = p_netlinkList->m_next) + { + unsigned int l_nlsize = p_netlinkList->m_size; + struct nlmsghdr *l_hdr; + for(l_hdr = p_netlinkList->m_data; NLMSG_OK(l_hdr, l_nlsize); l_hdr = NLMSG_NEXT(l_hdr, l_nlsize)) + { + if((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket) + { + continue; + } + + if(l_hdr->nlmsg_type == NLMSG_DONE) + { + break; + } + + if(l_hdr->nlmsg_type == RTM_NEWLINK) + { + ++l_links; + } + } + } + + return l_links; +} + +int getifaddrs(struct ifaddrs **ifap) +{ + if(!ifap) + { + return -1; + } + *ifap = NULL; + + int l_socket = netlink_socket(); + if(l_socket < 0) + { + return -1; + } + + NetlinkList *l_linkResults = getResultList(l_socket, RTM_GETLINK); + if(!l_linkResults) + { + close(l_socket); + return -1; + } + + NetlinkList *l_addrResults = getResultList(l_socket, RTM_GETADDR); + if(!l_addrResults) + { + close(l_socket); + freeResultList(l_linkResults); + return -1; + } + + unsigned l_numLinks = countLinks(l_socket, l_linkResults) + countLinks(l_socket, l_addrResults); + struct ifaddrs *l_links[l_numLinks]; + memset(l_links, 0, l_numLinks * sizeof(struct ifaddrs *)); + + interpret(l_socket, l_linkResults, l_links, ifap); + interpret(l_socket, l_addrResults, l_links, ifap); + + freeResultList(l_linkResults); + freeResultList(l_addrResults); + close(l_socket); + return 0; +} + +void freeifaddrs(struct ifaddrs *ifa) +{ + struct ifaddrs *l_cur; + while(ifa) + { + l_cur = ifa; + ifa = ifa->ifa_next; + free(l_cur); + } +} diff --git a/metis/ccnx/forwarder/metis/platforms/android/ifaddrs.h b/metis/ccnx/forwarder/metis/platforms/android/ifaddrs.h new file mode 100644 index 00000000..9cd19fec --- /dev/null +++ b/metis/ccnx/forwarder/metis/platforms/android/ifaddrs.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 1995, 1999 + * Berkeley Software Design, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY Berkeley Software Design, Inc. ``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 Berkeley Software Design, Inc. 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. + * + * BSDI ifaddrs.h,v 2.5 2000/02/23 14:51:59 dab Exp + */ + +#ifndef _IFADDRS_H_ +#define _IFADDRS_H_ + +struct ifaddrs { + struct ifaddrs *ifa_next; + char *ifa_name; + unsigned int ifa_flags; + struct sockaddr *ifa_addr; + struct sockaddr *ifa_netmask; + struct sockaddr *ifa_dstaddr; + void *ifa_data; +}; + +/* + * This may have been defined in <net/if.h>. Note that if <net/if.h> is + * to be included it must be included before this header file. + */ +#ifndef ifa_broadaddr +#define ifa_broadaddr ifa_dstaddr /* broadcast address interface */ +#endif + +#include <sys/cdefs.h> + +__BEGIN_DECLS +extern int getifaddrs(struct ifaddrs **ifap); +extern void freeifaddrs(struct ifaddrs *ifa); +__END_DECLS + +#endif diff --git a/metis/ccnx/forwarder/metis/platforms/android/metis_GenericEther.c b/metis/ccnx/forwarder/metis/platforms/android/metis_GenericEther.c new file mode 100644 index 00000000..e2d108e0 --- /dev/null +++ b/metis/ccnx/forwarder/metis/platforms/android/metis_GenericEther.c @@ -0,0 +1,368 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <config.h> +#include <stdio.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> + +#include <arpa/inet.h> +#include <linux/if_packet.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <net/if.h> +#include <netinet/ether.h> +#include <net/ethernet.h> +#include <linux/if_arp.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> +#include <ccnx/forwarder/metis/io/metis_GenericEther.h> +#include <ccnx/forwarder/metis/core/metis_System.h> +#include <parc/algol/parc_Object.h> +#include <ccnx/forwarder/metis/tlv/metis_Tlv.h> +#include <ccnx/forwarder/metis/io/metis_Ethernet.h> + +struct metis_generic_ether { + uint16_t ethertype; + int etherSocket; + + int linuxInterfaceIndex; + + PARCBuffer *macAddress; + MetisLogger *logger; + + // MTU set on interface when we are created + unsigned mtu; +}; + +static bool _linuxEthernet_SetupSocket(MetisGenericEther *ether, const char *devstr); + +static void +_metisGenericEther_Destroy(MetisGenericEther **etherPtr) +{ + MetisGenericEther *ether = *etherPtr; + + if (ether->etherSocket > 0) { + close(ether->etherSocket); + } + + if (ether->macAddress) { + parcBuffer_Release(ðer->macAddress); + } + + metisLogger_Release(ðer->logger); +} + +parcObject_ExtendPARCObject(MetisGenericEther, _metisGenericEther_Destroy, NULL, NULL, NULL, NULL, NULL, NULL); + +parcObject_ImplementAcquire(metisGenericEther, MetisGenericEther); + +parcObject_ImplementRelease(metisGenericEther, MetisGenericEther); + +// ========================= +// PUBLIC API +// ========================= + +MetisGenericEther * +metisGenericEther_Create(MetisForwarder *metis, const char *deviceName, uint16_t etherType) +{ + assertNotNull(metis, "Parameter metis must be non-null"); + assertNotNull(deviceName, "Parameter deviceName must be non-null"); + + MetisGenericEther *ether = NULL; + + if (metisEthernet_IsValidEthertype(etherType)) { + ether = parcObject_CreateInstance(MetisGenericEther); + ether->ethertype = etherType; + ether->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis)); + ether->mtu = metisSystem_InterfaceMtu(metis, deviceName); + + ether->etherSocket = -1; // invalid valid + ether->macAddress = NULL; + + bool success = _linuxEthernet_SetupSocket(ether, deviceName); + + if (success) { + if (metisLogger_IsLoggable(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + char *str = parcBuffer_ToHexString(ether->macAddress); + metisLogger_Log(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "GenericEther %p created on device %s (%s) for ethertype 0x%04x fd %d ifindex %u mtu %u", + (void *) ether, deviceName, str, etherType, ether->etherSocket, ether->linuxInterfaceIndex, + ether->mtu); + parcMemory_Deallocate((void **) &str); + } + } else { + if (metisLogger_IsLoggable(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "GenericEther failed to created on device %s for ethertype 0x%04x", + deviceName, etherType); + } + + // this will also null ether + metisGenericEther_Release(ðer); + } + } else { + if (metisLogger_IsLoggable(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "GenericEther failed to created on device %s for ethertype 0x%04x, invalid ethertype", + deviceName, etherType); + } + } + + return ether; +} + +int +metisGenericEther_GetDescriptor(const MetisGenericEther *ether) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + return ether->etherSocket; +} + +/** + * Based on the fixed header, trim the buffer + * + * Some platforms do not strip the ethernet CRC from the raw packet. Trim the buffer to + * the right sized based on the fixed header. + * + * @param [in] ether An allocated ethernet interface + * @param [in] readBuffer The buffer to trim + * + * Example: + * @code + * <#example#> + * @endcode + */ +static void +_linuxEthernet_TrimBuffer(MetisGenericEther *ether, PARCEventBuffer *readBuffer) +{ + // read the fixed header + uint8_t *etherHeader = parcEventBuffer_Pullup(readBuffer, ETHER_HDR_LEN + metisTlv_FixedHeaderLength()); + + if (etherHeader) { + uint8_t *fixedHeader = etherHeader + ETHER_HDR_LEN; + size_t totalLength = metisTlv_TotalPacketLength(fixedHeader) + ETHER_HDR_LEN; + + if (parcEventBuffer_GetLength(readBuffer) > totalLength) { + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "%s buffer length %zu, actual length %zu (ether header + ccnx packet), trimming %zu bytes", + __func__, parcEventBuffer_GetLength(readBuffer), totalLength, + parcEventBuffer_GetLength(readBuffer) - totalLength); + } + + PARCEventBuffer *temp = parcEventBuffer_Create(); + + // there's no way to drain from the end, so we move to a tempororary buffer, + // drain the unwanted part, then move back. + + int movedBytes = parcEventBuffer_ReadIntoBuffer(readBuffer, temp, totalLength); + assertTrue(movedBytes == totalLength, "Failed to move all the bytes, got %d expected %zu", movedBytes, totalLength); + + // flush all the bytes out of the read buffer + parcEventBuffer_Read(readBuffer, NULL, -1); + + // now put back what we want + int failure = parcEventBuffer_AppendBuffer(temp, readBuffer); + assertFalse(failure, "parcEventBuffer_AppendBuffer failed"); + + parcEventBuffer_Destroy(&temp); + } + } +} + +/* + * Reading a raw socket, on some systems, may include the FCS trailer + */ +bool +metisGenericEther_ReadNextFrame(MetisGenericEther *ether, PARCEventBuffer *readBuffer) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + assertNotNull(readBuffer, "Parameter readBuffer must be non-null"); + + bool success = false; + + int evread_length = parcEventBuffer_ReadFromFileDescriptor(readBuffer, ether->etherSocket, (int) -1); + + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "%s read length %d", __func__, evread_length); + } + + if (evread_length > 0) { + _linuxEthernet_TrimBuffer(ether, readBuffer); + success = true; + } + + return success; +} + +bool +metisGenericEther_SendFrame(MetisGenericEther *ether, PARCEventBuffer *buffer) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + + // cannot use parcEventBuffer_WriteToFileDescriptor because we need to write the length in one go, not use the + // iovec approach in parcEventBuffer_WriteToFileDescriptor. It can cause problems on some platforms. + + uint8_t *linear = parcEventBuffer_Pullup(buffer, -1); + size_t length = parcEventBuffer_GetLength(buffer); + + ssize_t written = write(ether->etherSocket, linear, length); + if (written == length) { + return true; + } + return false; +} + +PARCBuffer * +metisGenericEther_GetMacAddress(const MetisGenericEther *ether) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + return ether->macAddress; +} + +uint16_t +metisGenericEther_GetEtherType(const MetisGenericEther *ether) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + return ether->ethertype; +} + +unsigned +metisGenericEther_GetMTU(const MetisGenericEther *ether) +{ + return ether->mtu; +} + +// ================== +// PRIVATE API + +static bool +_linuxEthernet_SetInterfaceIndex(MetisGenericEther *ether, const char *devstr) +{ + // get the interface index of the desired device + bool success = false; + + struct ifreq if_idx; + memset(&if_idx, 0, sizeof(if_idx)); + strncpy(if_idx.ifr_name, devstr, IFNAMSIZ - 1); + if (!ioctl(ether->etherSocket, SIOCGIFINDEX, &if_idx)) { + ether->linuxInterfaceIndex = if_idx.ifr_ifindex; + success = true; + } else { + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "ioctl SIOCGIFINDEX error: (%d) %s", errno, strerror(errno)); + } + } + + return success; +} + +static bool +_linuxEthernet_SetInterfaceAddress(MetisGenericEther *ether, const char *devstr) +{ + bool success = false; + + assertNull(ether->macAddress, "Should only be called once with null macAddress"); + + // get the MAC address of the device + struct ifreq if_mac; + memset(&if_mac, 0, sizeof(if_mac)); + strncpy(if_mac.ifr_name, devstr, IFNAMSIZ - 1); + if (!ioctl(ether->etherSocket, SIOCGIFHWADDR, &if_mac)) { + if (if_mac.ifr_hwaddr.sa_family == ARPHRD_ETHER) { + ether->macAddress = parcBuffer_Allocate(ETHER_ADDR_LEN); + parcBuffer_PutArray(ether->macAddress, ETHER_ADDR_LEN, (uint8_t *) if_mac.ifr_hwaddr.sa_data); + parcBuffer_Flip(ether->macAddress); + success = true; + } else { + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "Device %s does not have an Ethernet hardware address", devstr); + } + } + } + return success; +} + +static bool +_linuxEthernet_Bind(MetisGenericEther *ether) +{ + bool success = false; + + // we need to bind to the ethertype to receive packets + struct sockaddr_ll my_addr; + my_addr.sll_family = PF_PACKET; + my_addr.sll_protocol = htons(ether->ethertype); + my_addr.sll_ifindex = ether->linuxInterfaceIndex; + + if (!bind(ether->etherSocket, (struct sockaddr *) &my_addr, sizeof(struct sockaddr_ll))) { + success = true; + } else { + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "bind error: (%d) %s", errno, strerror(errno)); + } + } + return success; +} + +static bool +_linuxEthernet_SetNonBlocking(MetisGenericEther *ether) +{ + bool success = false; + uint32_t on = 1; + if (!ioctl(ether->etherSocket, FIONBIO, &on)) { + success = true; + } + return success; +} + +static bool +_linuxEthernet_SetupSocket(MetisGenericEther *ether, const char *devstr) +{ + bool success = false; + ether->etherSocket = socket(AF_PACKET, SOCK_RAW, htons(ether->ethertype)); + if (ether->etherSocket > 0) { + if (_linuxEthernet_SetInterfaceIndex(ether, devstr)) { + if (_linuxEthernet_SetInterfaceAddress(ether, devstr)) { + if (_linuxEthernet_Bind(ether)) { + // set non-blocking + if (_linuxEthernet_SetNonBlocking(ether)) { + success = true; + } + } + } + } + } + + if (!success) { + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "setup socket error: (%d) %s", errno, strerror(errno)); + } + } + + return success; +} + + + diff --git a/metis/ccnx/forwarder/metis/platforms/android/metis_System.c b/metis/ccnx/forwarder/metis/platforms/android/metis_System.c new file mode 100644 index 00000000..a8f42854 --- /dev/null +++ b/metis/ccnx/forwarder/metis/platforms/android/metis_System.c @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/ioctl.h> + + +#include <errno.h> +#include <string.h> + +//#define __USE_MISC +#include <net/if.h> + +// to get the list of arp types +#include <net/if_arp.h> + +// for the mac address +#include <netpacket/packet.h> + +#include <ccnx/api/control/cpi_InterfaceSet.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> + +#include <LongBow/runtime.h> + +#include "ifaddrs.h" + +/** + * Returns the MTU for a named interface + * + * On linux, we get the MTU by opening a socket and reading SIOCGIFMTU + * + * @param [in] ifname Interface name (e.g. "eth0") + * + * @retval number The MTU in bytes + * + * Example: + * @code + * <#example#> + * @endcode + */ +static int +getMtu(const char *ifname) +{ + struct ifreq ifr; + int fd; + + fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); + + strcpy(ifr.ifr_name, ifname); + ioctl(fd, SIOCGIFMTU, &ifr); + + close(fd); + return ifr.ifr_mtu; +} + +CPIInterfaceSet * +metisSystem_Interfaces(MetisForwarder *metis) +{ + CPIInterfaceSet *set = cpiInterfaceSet_Create(); + + MetisLogger *logger = metisForwarder_GetLogger(metis); + + // this is the dynamically allocated head of the list + struct ifaddrs *ifaddr; + int failure = getifaddrs(&ifaddr); + assertFalse(failure, "Error getifaddrs: (%d) %s", errno, strerror(errno)); + + struct ifaddrs *next; + for (next = ifaddr; next != NULL; next = next->ifa_next) { + if ((next->ifa_addr == NULL) || ((next->ifa_flags & IFF_UP) == 0)) { + continue; + } + + CPIInterface *iface = cpiInterfaceSet_GetByName(set, next->ifa_name); + if (iface == NULL) { + unsigned mtu = (unsigned) getMtu(next->ifa_name); + + iface = cpiInterface_Create(next->ifa_name, + metisForwarder_GetNextConnectionId(metis), + next->ifa_flags & IFF_LOOPBACK, + next->ifa_flags & IFF_MULTICAST, + mtu); + + cpiInterfaceSet_Add(set, iface); + } + + int family = next->ifa_addr->sa_family; + switch (family) { + case AF_INET: { + CPIAddress *address = cpiAddress_CreateFromInet((struct sockaddr_in *) next->ifa_addr); + cpiInterface_AddAddress(iface, address); + break; + } + + case AF_INET6: { + CPIAddress *address = cpiAddress_CreateFromInet6((struct sockaddr_in6 *) next->ifa_addr); + cpiInterface_AddAddress(iface, address); + break; + } + + case AF_PACKET: { + struct sockaddr_ll *addr_ll = (struct sockaddr_ll *) next->ifa_addr; + + if (metisLogger_IsLoggable(logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "sockaddr_ll family %d proto %d ifindex %d hatype %d pkttype %d halen %d", + addr_ll->sll_family, + addr_ll->sll_protocol, + addr_ll->sll_ifindex, + addr_ll->sll_hatype, + addr_ll->sll_pkttype, + addr_ll->sll_halen); + } + + switch (addr_ll->sll_hatype) { + // list of the ARP hatypes we can extract a MAC address from + case ARPHRD_ETHER: + // fallthrough + case ARPHRD_IEEE802: { + CPIAddress *address = cpiAddress_CreateFromLink((uint8_t *) addr_ll->sll_addr, addr_ll->sll_halen); + cpiInterface_AddAddress(iface, address); + break; + } + default: + break; + } + + break; + } + } + } + + freeifaddrs(ifaddr); + return set; +} + +CPIAddress * +metisSystem_GetMacAddressByName(MetisForwarder *metis, const char *interfaceName) +{ + CPIAddress *linkAddress = NULL; + + CPIInterfaceSet *interfaceSet = metisSystem_Interfaces(metis); + CPIInterface *interface = cpiInterfaceSet_GetByName(interfaceSet, interfaceName); + + if (interface) { + const CPIAddressList *addressList = cpiInterface_GetAddresses(interface); + + size_t length = cpiAddressList_Length(addressList); + for (size_t i = 0; i < length && !linkAddress; i++) { + const CPIAddress *a = cpiAddressList_GetItem(addressList, i); + if (cpiAddress_GetType(a) == cpiAddressType_LINK) { + linkAddress = cpiAddress_Copy(a); + } + } + } + + cpiInterfaceSet_Destroy(&interfaceSet); + + return linkAddress; +} + +unsigned +metisSystem_InterfaceMtu(MetisForwarder *metis, const char *interfaceName) +{ + unsigned mtu = 0; + + CPIInterfaceSet *interfaceSet = metisSystem_Interfaces(metis); + CPIInterface *interface = cpiInterfaceSet_GetByName(interfaceSet, interfaceName); + + if (interface) { + mtu = cpiInterface_GetMTU(interface); + } + + cpiInterfaceSet_Destroy(&interfaceSet); + + return mtu; +} diff --git a/metis/ccnx/forwarder/metis/platforms/darwin/metis_GenericEther.c b/metis/ccnx/forwarder/metis/platforms/darwin/metis_GenericEther.c new file mode 100644 index 00000000..b49b6579 --- /dev/null +++ b/metis/ccnx/forwarder/metis/platforms/darwin/metis_GenericEther.c @@ -0,0 +1,618 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Implements the platform-specific code for working with an Ethernet interface. + * + * Uses the Berkeley Packet Filter (BPF) approach to reading the Ethernet device. + */ + +#include <config.h> +#include <stdio.h> +#include <net/bpf.h> +#include <sys/ioctl.h> +#include <net/if.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> +#include <net/if_arp.h> +#include <net/if.h> +#include <net/if_dl.h> +#include <net/if_types.h> +#include <ifaddrs.h> + +#include <net/ethernet.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> +#include <ccnx/forwarder/metis/io/metis_GenericEther.h> +#include <ccnx/forwarder/metis/core/metis_System.h> +#include <parc/algol/parc_Object.h> +#include <ccnx/forwarder/metis/io/metis_Ethernet.h> + +struct metis_generic_ether { + uint16_t ethertype; + int etherSocket; + + // what size do the read buffers need to be? ioctl BIOCGBLEN will tell us. + unsigned etherBufferLength; + + // MTU set on interface when we are created + unsigned mtu; + + PARCEventBuffer *workBuffer; + + PARCBuffer *macAddress; + MetisLogger *logger; +}; + +static void +_metisGenericEther_Destroy(MetisGenericEther **etherPtr) +{ + MetisGenericEther *ether = *etherPtr; + + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "GenericEther %p destroyed", (void *) ether); + } + + if (ether->etherSocket > 0) { + close(ether->etherSocket); + } + + if (ether->macAddress) { + parcBuffer_Release(ðer->macAddress); + } + + metisLogger_Release(ðer->logger); + parcEventBuffer_Destroy(ðer->workBuffer); +} + + +// ========================= +// PRIVATE API +// ========================= + +/* + * Returns the total legth of the good data: the ethernet header plus ccnx packet. + * If there is a FCS, it will be excluded. + * + * PRECONDITION: You have drained off any BPF headers and the first byte + * of the work buffer points to the first byte of the Ethernet header + */ +static uint16_t +_getFrameLengthFromWorkBuffer(MetisGenericEther *ether) +{ + uint16_t frameLength = 0; + + // read the fixed header + uint8_t *etherHeader = parcEventBuffer_Pullup(ether->workBuffer, ETHER_HDR_LEN + metisTlv_FixedHeaderLength()); + + if (etherHeader) { + uint8_t *fixedHeader = etherHeader + ETHER_HDR_LEN; + frameLength = metisTlv_TotalPacketLength(fixedHeader) + ETHER_HDR_LEN; + } + + return frameLength; +} + +/* + * An attempt to read from the workbuffer to the readbuffer can succeed (ok), fail because + * the work buffer does not have enough bytes (empty), or an error causes a frame to be + * discarded (tryagain). + */ +typedef enum { + ReadWorkBufferResult_Ok, + ReadWorkBufferResult_Empty, + ReadWorkBufferResult_TryAgain +} _ReadWorkBufferResult; + +/** + * Parses the work buffer to extract packets + * + * The work buffer should be filled in with a set of tuples (bh_hdrlen, frame, pad). + * The pad extends each packet out to BPF_WORDALIGN. + * + * If the CCNxMessage PacketLength says it is larger than the read capture length (caplen), + * then this is an invalid packet and it will be discarded. This error will result in a + * ReadWorkBufferResult_TryAgain condition. + * + * struct bpf_hdr { + * struct BPF_TIMEVAL bh_tstamp; // time stamp + * bpf_u_int32 bh_caplen; // length of captured portion + * bpf_u_int32 bh_datalen; // original length of packet + * u_short bh_hdrlen; // length of bpf header (this struct plus alignment padding) + * } + * + * @param [in] ether An allocated Darwin ethernet. + * @param [in] readbuffer A user-provided read buffer. + * + * @retval ReadWorkBufferResult_Ok A frame was moved from workbuffer to readbuffer + * @retval ReadWorkBufferResult_Empty There's not enough bytes in the workbuffer + * @retval ReadWorkBufferResult_TryAgain (likely discard) caused this call to fail, but you should try again + * + * Example: + * @code + * <#example#> + * @endcode + */ +static _ReadWorkBufferResult +_darwinEthernet_ReadWorkBuffer(MetisGenericEther *ether, PARCEventBuffer *readbuffer) +{ + _ReadWorkBufferResult result = ReadWorkBufferResult_Empty; + + // Make sure we have linear memory for the BPF header + struct bpf_hdr *bpfHeader = (struct bpf_hdr *) parcEventBuffer_Pullup(ether->workBuffer, sizeof(struct bpf_hdr)); + + // make sure we have enough bytes to process the frame + // bpfHeader may be NULL if there are not sizeof(struct bpf_hdr) bytes available. + if (bpfHeader && parcEventBuffer_GetLength(ether->workBuffer) >= bpfHeader->bh_hdrlen + bpfHeader->bh_caplen) { + // (0) Save the needed fields from the bpf header + // (1) pop off the bpf header + // (2) move the iovec from work buffer to readBuffer. + // (3) remove any BPF_WORDALIGN padding to the start of the next packet + + // (0) Save the needed fields from the bpf header + uint16_t hdrlen = bpfHeader->bh_hdrlen; + uint32_t caplen = bpfHeader->bh_caplen; + + // (1) pop off the bpf header + parcEventBuffer_Read(ether->workBuffer, NULL, hdrlen); + + // (1a) Determine the packet length from the fixed header and only transfer that many bytes + uint16_t packetlen = _getFrameLengthFromWorkBuffer(ether); + + if (packetlen <= caplen) { + // (2) move the iovec from work buffer to readBuffer. + parcEventBuffer_ReadIntoBuffer(ether->workBuffer, readbuffer, packetlen); + + // (2a) drain off any trailer (i.e. FCS) + parcEventBuffer_Read(ether->workBuffer, NULL, caplen - packetlen); + + result = ReadWorkBufferResult_Ok; + } else { + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Warning, __func__, + "%s reading fd %d discard packetlen %u greater than caplen %u", + __func__, ether->etherSocket, packetlen, caplen); + } + + // discard all of caplen + parcEventBuffer_Read(ether->workBuffer, NULL, caplen); + + // tell the caller that this read failed, but they could try again. + result = ReadWorkBufferResult_TryAgain; + } + + // (3) remove any BPF_WORDALIGN padding to the start of the next packet + size_t alignedLength = BPF_WORDALIGN(hdrlen + caplen); + size_t pad = alignedLength - hdrlen - caplen; + parcEventBuffer_Read(ether->workBuffer, NULL, pad); + } + return result; +} + +/** + * Reads at most a single frame from the BPF queue in the workbuffer + * + * Reads at most a single frame from the BPF queue in the workbuffer to the user-provided + * readbuffer. Returns true if one frame moved to readbuffer or false otherwise. + * + * The frame will be appended to the readbuffer. If return failure, the readbuffer will + * be unmodified. + * + * The returned frame will not contain a FCS, even if one was read. + * + * @param [in] ether An allocated Darwin ethernet. + * @param [in] readbuffer A user-provided read buffer. + * + * @retval true A frame was added to the readbuffer + * @retval false No frame is available at this time + * + * Example: + * @code + * <#example#> + * @endcode + */ +static bool +_darwinEthernet_WorkBufferToReadBuffer(MetisGenericEther *ether, PARCEventBuffer *readbuffer) +{ + _ReadWorkBufferResult result = ReadWorkBufferResult_Empty; + do { + result = _darwinEthernet_ReadWorkBuffer(ether, readbuffer); + } while (result == ReadWorkBufferResult_TryAgain); + + return (result == ReadWorkBufferResult_Ok); +} + +/** + * Reads from the socket to fill in the work buffer + * + * Reads one or more packets from the socket to the work buffer It will append to the work buffer. + * The BFP socket is non-blocking. The BPF interface may return multiple packets in one read + * that need to be parsed as in _darwinEthernet_ReadWorkBuffer(). + * + * @param [in] ether The Darwin ethernet interface + * + * @retval true We added something to the work buffer + * @retval false Nothing was read + * + * Example: + * @code + * <#example#> + * @endcode + */ +static bool +_darwinEthernet_ReadSocket(MetisGenericEther *ether) +{ + bool success = false; + + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "%s reading fd %d bufferLength %u", __func__, ether->etherSocket, ether->etherBufferLength); + } + + // The buffer we're reading must be exactly ether->etherBufferLength + // TODO: Fix the parcEventBuffer_ReadFromFileDescriptor call to reserve that space so it's all there. + + uint8_t tempBuffer[ether->etherBufferLength]; + ssize_t read_length = read(ether->etherSocket, tempBuffer, ether->etherBufferLength); + if (read_length > 0) { + parcEventBuffer_Append(ether->workBuffer, tempBuffer, read_length); + if (read_length > 0) { + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "%s read %zd bytes from fd %d", + __func__, + read_length, + ether->etherSocket); + } + success = true; + } + } + + return success; +} + + +// open the first available /dev/bpf. Returns +// the fd or -1 on none found +static int +_darwinEthernet_OpenBfpDevice(void) +{ + for (int i = 0; i < 255; i++) { + char bpfstr[255]; + snprintf(bpfstr, sizeof(bpfstr), "/dev/bpf%d", i); + + int fd = open(bpfstr, O_RDWR); + if (fd > -1) { + return fd; + } + + if (errno == EBUSY) { + continue; + } + + if (errno == EBADF) { + continue; + } + + // its an error + return -1; + } + + errno = ENOENT; + return -1; +} + +/** + * Set the ioctrl for the BPF socket + * + * (a) bind to a specific name + * (b) The kernel will fill in the source MAC address + * (c) We will see out-going packets [turn off?] + * (d) immediate read() [don't wait to queue multiple packets] + * (e) Include the buffer length + * + * Settings BIOCIMMEDIATE means that read() calls will not wait for several packets + * to accumulate. It does not, however, guarantee only one packet per read(). + * + * @param [in] fd BPF socket + * @param [in] devstr The device name + * + * @retval true Set all needed ioctrl + * @retval false An error + * + * Example: + * @code + * <#example#> + * @endcode + */ +static bool +_darwinEthernet_SetDeviceOptions(int fd, const char *devstr) +{ + struct ifreq ifr; + uint32_t on = 1; + + bool success = false; + + // Always null terminate. If devstr did not fit in ifr_name, then + // the ioctl call will fail anyway. + if (devstr != NULL) { + (void) strncpy(ifr.ifr_name, devstr, IF_NAMESIZE - 1); + ifr.ifr_name[IF_NAMESIZE - 1] = 0; + + if (ioctl(fd, BIOCSETIF, &ifr)) { + return false; + } + } + + // on = immediate read on packet reception + if (!ioctl(fd, BIOCIMMEDIATE, &on)) { + // on = request the buffer length prepended to each packet + if (!ioctl(fd, BIOCGBLEN, &on)) { + // set non-blocking + if (!ioctl(fd, FIONBIO, &on)) { + success = true; + } + } + } + + return success; +} + +/** + * Create the berkeley packet filter for our ethertype + * + * Creates a BPF for our ether type. + * + * @param [in,out] ether The MetisGenericEther to modify + * + * @retval true success + * @retval false failure + * + * Example: + * @code + * <#example#> + * @endcode + */ +static bool +_darwinEthernet_SetFilter(MetisGenericEther *ether) +{ + struct bpf_program filterCode = { 0 }; + + // BPF instructions: + // Load 12 to accumulator (offset of ethertype) + // Jump Equal to netbyteorder_ethertype 0 instructions, otherwise 1 instruction + // 0: return a length of -1 (meaning whole packet) + // 1: return a length of 0 (meaning skip packet) + struct bpf_insn instructions[] = { + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0801, 0, 1), + BPF_STMT(BPF_RET + BPF_K, (u_int) - 1), + BPF_STMT(BPF_RET + BPF_K, 0), + }; + // BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ether->ethertype, 0, 1), + + /* Set the filter */ + filterCode.bf_len = sizeof(instructions) / sizeof(struct bpf_insn); + filterCode.bf_insns = &instructions[0]; + + if (ioctl(ether->etherSocket, BIOCSETF, &filterCode) < 0) { + return false; + } + + return true; +} + +static bool +_darwinEthernet_SetupReceive(MetisGenericEther *ether, const char *devstr) +{ + // If we cannot open the Ethernet BPF (likely due to permissions), return + // a soft error so we can move past this failure. + if ((ether->etherSocket = _darwinEthernet_OpenBfpDevice()) < 0) { + return false; + } + + if (!_darwinEthernet_SetDeviceOptions(ether->etherSocket, devstr)) { + trapUnrecoverableState("error setting options: %s", strerror(errno)); + } + + + if (!_darwinEthernet_SetFilter(ether)) { + trapUnrecoverableState("error setting filter: %s", strerror(errno)); + } + + if (ioctl(ether->etherSocket, BIOCGBLEN, ðer->etherBufferLength)) { + trapUnrecoverableState("error getting buffer length: %s", strerror(errno)); + } + + return true; +} + +/** + * If the user specified a device name, set the MAC address in ether->macAddress + * + * <#Paragraphs Of Explanation#> + * + * @param [in] ether An allocated MetisGenericEther + * @param [in] devstr A C-String of the device name + * + * Example: + * @code + * <#example#> + * @endcode + */ +static void +_darwinEthernet_SetInterfaceAddress(MetisGenericEther *ether, const char *devstr) +{ + if (devstr) { + struct ifaddrs *ifaddr; + int failure = getifaddrs(&ifaddr); + assertFalse(failure, "Error getifaddrs: (%d) %s", errno, strerror(errno)); + + struct ifaddrs *next; + for (next = ifaddr; next != NULL; next = next->ifa_next) { + if (strcmp(next->ifa_name, devstr) == 0) { + if (next->ifa_addr->sa_family == AF_LINK) { + struct sockaddr_dl *addr_dl = (struct sockaddr_dl *) next->ifa_addr; + + // addr_dl->sdl_data[12] contains the interface name followed by the MAC address, so + // need to offset in to the array past the interface name. + PARCBuffer *addr = parcBuffer_Allocate(addr_dl->sdl_alen); + parcBuffer_PutArray(addr, addr_dl->sdl_alen, (uint8_t *) &addr_dl->sdl_data[ addr_dl->sdl_nlen]); + parcBuffer_Flip(addr); + ether->macAddress = addr; + + // break out of loop and freeifaddrs + break; + } + } + } + freeifaddrs(ifaddr); + } +} + +parcObject_ExtendPARCObject(MetisGenericEther, _metisGenericEther_Destroy, NULL, NULL, NULL, NULL, NULL, NULL); + +parcObject_ImplementAcquire(metisGenericEther, MetisGenericEther); + +parcObject_ImplementRelease(metisGenericEther, MetisGenericEther); + +// ========================= +// PUBLIC API +// ========================= + +MetisGenericEther * +metisGenericEther_Create(MetisForwarder *metis, const char *deviceName, uint16_t etherType) +{ + assertNotNull(metis, "Parameter metis must be non-null"); + + // The Darwin generic ether allows a NULL device name, it is used in the unit tests. + + MetisGenericEther *ether = NULL; + + if (metisEthernet_IsValidEthertype(etherType)) { + ether = parcObject_CreateInstance(MetisGenericEther); + ether->ethertype = etherType; + ether->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis)); + ether->etherSocket = -1; // invalid valid + ether->workBuffer = parcEventBuffer_Create(); + ether->macAddress = NULL; + ether->mtu = metisSystem_InterfaceMtu(metis, deviceName); + + _darwinEthernet_SetInterfaceAddress(ether, deviceName); + + bool success = _darwinEthernet_SetupReceive(ether, deviceName); + + if (success) { + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Info)) { + char *str = parcBuffer_ToHexString(ether->macAddress); + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Info, __func__, + "GenericEther %p created on device %s (%s) for ethertype 0x%04x fd %d bufferLength %u mtu %u", + (void *) ether, deviceName, str, etherType, ether->etherSocket, ether->etherBufferLength, ether->mtu); + parcMemory_Deallocate((void **) &str); + } + } else { + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "GenericEther failed to created on device %s for ethertype 0x%04x", + deviceName, etherType); + } + + // this will also null ether + metisGenericEther_Release(ðer); + } + + assertTrue(ether->etherBufferLength < 65536, "Buffer length way too big, expected less than 65536 got %u", ether->etherBufferLength); + } else { + if (metisLogger_IsLoggable(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "GenericEther failed to created on device %s for ethertype 0x%04x, invalid ethertype", + deviceName, etherType); + } + } + + return ether; +} + +int +metisGenericEther_GetDescriptor(const MetisGenericEther *ether) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + return ether->etherSocket; +} + + +/* + * Reading a BPF packet will include the BPF header. Frame may include the FCS. + */ +bool +metisGenericEther_ReadNextFrame(MetisGenericEther *ether, PARCEventBuffer *readbuffer) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + assertNotNull(readbuffer, "Parameter readbuffer must be non-null"); + + bool success = false; + + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "Workbuffer length %zu", __func__, parcEventBuffer_GetLength(ether->workBuffer)); + } + + // Do we already have something in our work buffer? If not, try to read something. + if (parcEventBuffer_GetLength(ether->workBuffer) == 0) { + _darwinEthernet_ReadSocket(ether); + } + + success = _darwinEthernet_WorkBufferToReadBuffer(ether, readbuffer); + return success; +} + +bool +metisGenericEther_SendFrame(MetisGenericEther *ether, PARCEventBuffer *buffer) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + + size_t length = parcEventBuffer_GetLength(buffer); + int written = parcEventBuffer_WriteToFileDescriptor(buffer, ether->etherSocket, -1); + if (written == length) { + return true; + } + return false; +} + +PARCBuffer * +metisGenericEther_GetMacAddress(const MetisGenericEther *ether) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + return ether->macAddress; +} + +uint16_t +metisGenericEther_GetEtherType(const MetisGenericEther *ether) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + return ether->ethertype; +} + +unsigned +metisGenericEther_GetMTU(const MetisGenericEther *ether) +{ + return ether->mtu; +} + diff --git a/metis/ccnx/forwarder/metis/platforms/darwin/metis_System.c b/metis/ccnx/forwarder/metis/platforms/darwin/metis_System.c new file mode 100644 index 00000000..f4261e8a --- /dev/null +++ b/metis/ccnx/forwarder/metis/platforms/darwin/metis_System.c @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <ifaddrs.h> +#include <errno.h> +#include <string.h> + +#include <sys/socket.h> +#include <net/if.h> +#include <net/if_dl.h> +#include <net/if_types.h> + +#include <LongBow/runtime.h> + +#include <ccnx/api/control/cpi_InterfaceSet.h> + +#include <ccnx/forwarder/metis/core/metis_System.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> + +CPIInterfaceSet * +metisSystem_Interfaces(MetisForwarder *metis) +{ + CPIInterfaceSet *set = cpiInterfaceSet_Create(); + + // this is the dynamically allocated head of the list + struct ifaddrs *ifaddr; + int failure = getifaddrs(&ifaddr); + assertFalse(failure, "Error getifaddrs: (%d) %s", errno, strerror(errno)); + + struct ifaddrs *next; + for (next = ifaddr; next != NULL; next = next->ifa_next) { + if ((next->ifa_addr == NULL) || ((next->ifa_flags & IFF_UP) == 0)) { + continue; + } + + // This assumes the LINK address comes first so we can get the MTU + // when the interface is created. + + CPIInterface *iface = cpiInterfaceSet_GetByName(set, next->ifa_name); + if (iface == NULL) { + unsigned mtu = 0; + + if (next->ifa_data != NULL) { + struct if_data *ifdata = (struct if_data *) next->ifa_data; + mtu = ifdata->ifi_mtu; + } + + iface = cpiInterface_Create(next->ifa_name, + metisForwarder_GetNextConnectionId(metis), + next->ifa_flags & IFF_LOOPBACK, + next->ifa_flags & IFF_MULTICAST, + mtu); + + cpiInterfaceSet_Add(set, iface); + } + + int family = next->ifa_addr->sa_family; + switch (family) { + case AF_INET: { + CPIAddress *address = cpiAddress_CreateFromInet((struct sockaddr_in *) next->ifa_addr); + cpiInterface_AddAddress(iface, address); + break; + } + + case AF_INET6: { + CPIAddress *address = cpiAddress_CreateFromInet6((struct sockaddr_in6 *) next->ifa_addr); + cpiInterface_AddAddress(iface, address); + break; + } + + case AF_LINK: { + struct sockaddr_dl *addr_dl = (struct sockaddr_dl *) next->ifa_addr; + + // skip links with 0-length address + if (addr_dl->sdl_alen > 0) { + // addr_dl->sdl_data[12] contains the interface name followed by the MAC address, so + // need to offset in to the array past the interface name. + CPIAddress *address = cpiAddress_CreateFromLink((uint8_t *) &addr_dl->sdl_data[ addr_dl->sdl_nlen], addr_dl->sdl_alen); + cpiInterface_AddAddress(iface, address); + } + break; + } + } + } + + freeifaddrs(ifaddr); + + return set; +} + +CPIAddress * +metisSystem_GetMacAddressByName(MetisForwarder *metis, const char *interfaceName) +{ + CPIAddress *linkAddress = NULL; + + CPIInterfaceSet *interfaceSet = metisSystem_Interfaces(metis); + CPIInterface *interface = cpiInterfaceSet_GetByName(interfaceSet, interfaceName); + + if (interface) { + const CPIAddressList *addressList = cpiInterface_GetAddresses(interface); + + size_t length = cpiAddressList_Length(addressList); + for (size_t i = 0; i < length && !linkAddress; i++) { + const CPIAddress *a = cpiAddressList_GetItem(addressList, i); + if (cpiAddress_GetType(a) == cpiAddressType_LINK) { + linkAddress = cpiAddress_Copy(a); + } + } + } + + cpiInterfaceSet_Destroy(&interfaceSet); + + return linkAddress; +} + +unsigned +metisSystem_InterfaceMtu(MetisForwarder *metis, const char *interfaceName) +{ + unsigned mtu = 0; + + if (interfaceName) { + CPIInterfaceSet *interfaceSet = metisSystem_Interfaces(metis); + CPIInterface *interface = cpiInterfaceSet_GetByName(interfaceSet, interfaceName); + + if (interface) { + mtu = cpiInterface_GetMTU(interface); + } + + cpiInterfaceSet_Destroy(&interfaceSet); + } + return mtu; +} + diff --git a/metis/ccnx/forwarder/metis/platforms/darwin/test/.gitignore b/metis/ccnx/forwarder/metis/platforms/darwin/test/.gitignore new file mode 100644 index 00000000..44d3cf6d --- /dev/null +++ b/metis/ccnx/forwarder/metis/platforms/darwin/test/.gitignore @@ -0,0 +1,2 @@ +test_metis_GenericEther +test_metis_System diff --git a/metis/ccnx/forwarder/metis/platforms/darwin/test/test_metis_GenericEther.c b/metis/ccnx/forwarder/metis/platforms/darwin/test/test_metis_GenericEther.c new file mode 100644 index 00000000..fd6bdaa4 --- /dev/null +++ b/metis/ccnx/forwarder/metis/platforms/darwin/test/test_metis_GenericEther.c @@ -0,0 +1,606 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis_GenericEther.c" + +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> +#include <parc/algol/parc_Memory.h> +#include <parc/logging/parc_LogReporterTextStdout.h> +#include <ccnx/forwarder/metis/testdata/metis_TestDataV1.h> + +typedef struct __attribute__ ((__packed__)) metis_tlv_fixed_header { + uint8_t version; + uint8_t packetType; + uint16_t packetLength; + uint8_t interestHopLimit; + uint8_t returnCode; + uint8_t flags; + uint8_t headerLength; +} _MetisTlvFixedHeaderV1; + +static char * +getInterfaceName(void) +{ + // Lookup the MAC address of an interface that is up, then ask for it. Don't use loopback. + struct ifaddrs *ifaddr; + int failure = getifaddrs(&ifaddr); + assertFalse(failure, "Error getifaddrs: (%d) %s", errno, strerror(errno)); + + char *ifname = NULL; + + struct ifaddrs *next; + for (next = ifaddr; next != NULL && ifname == NULL; next = next->ifa_next) { + if ((next->ifa_addr == NULL) || ((next->ifa_flags & IFF_UP) == 0)) { + continue; + } + + if (next->ifa_flags & IFF_LOOPBACK) { + continue; + } + + if (next->ifa_addr->sa_family == AF_INET) { + ifname = strdup(next->ifa_name); + } + } + freeifaddrs(ifaddr); + return ifname; +} + +LONGBOW_TEST_RUNNER(darwin_Ethernet) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +static bool +_testPermissions(void) +{ + int fd = _darwinEthernet_OpenBfpDevice(); + if (fd > 0) { + close(fd); + return true; + } + return false; +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(darwin_Ethernet) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + + if (!_testPermissions()) { + fprintf(stderr, "Could not open a /dev/bpf device. Check permissions.\n"); + exit(77); + return LONGBOW_STATUS_SETUP_SKIPTESTS; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(darwin_Ethernet) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +/* + * Creates a BPF-encapsulated ethernet frame. + * + * @param [in] frameLength The capture length (ether header + ccnx packet) + * @param [out] totalLengthPtr The total read length, including BPF header and padding + * + * @return the parcMemory allocted byte array + */ +static uint8_t * +createBpfFrame(uint32_t frameLength, size_t *totalLengthPtr) +{ + uint16_t hdrlen = BPF_WORDALIGN(sizeof(struct bpf_hdr)); + uint32_t actualLength = BPF_WORDALIGN(frameLength + hdrlen); + uint32_t caplen = frameLength; + + uint8_t *buffer = parcMemory_AllocateAndClear(actualLength); + + struct bpf_hdr *hdr = (struct bpf_hdr *) buffer; + uint8_t *frame = buffer + hdrlen; + + memset(hdr, 0, hdrlen); + hdr->bh_hdrlen = hdrlen; + hdr->bh_caplen = caplen; + + for (int i = 0; i < caplen; i++) { + frame[i] = i * frameLength; + } + + // we need a valid FixedHeader length + struct ether_header *etherHeader = (struct ether_header *) frame; + _MetisTlvFixedHeaderV1 *fixedHeader = (_MetisTlvFixedHeaderV1 *)(frame + sizeof(struct ether_header)); + + etherHeader->ether_type = htons(0x0801); + fixedHeader->version = 1; + fixedHeader->packetLength = htons(caplen - sizeof(struct ether_header)); + + *totalLengthPtr = actualLength; + + return buffer; +} + +/* + * Create a BPF frame from a given Ethernet frame + * + * @param [out] totalLengthPtr The total frame length including the BPF header + */ +static uint8_t * +createBpfFrameFromEthernet(uint32_t length, const uint8_t etherframe[length], size_t *totalLengthPtr) +{ + uint16_t hdrlen = BPF_WORDALIGN(sizeof(struct bpf_hdr)); + uint32_t actualLength = BPF_WORDALIGN(length + hdrlen); + uint32_t caplen = length; + + uint8_t *buffer = parcMemory_AllocateAndClear(actualLength); + + struct bpf_hdr *hdr = (struct bpf_hdr *) buffer; + uint8_t *frame = buffer + hdrlen; + + memset(hdr, 0, hdrlen); + hdr->bh_hdrlen = hdrlen; + hdr->bh_caplen = caplen; + + memcpy(frame, etherframe, length); + + *totalLengthPtr = actualLength; + + return buffer; +} + +// ================================================================== + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisGenericEther_Create); + LONGBOW_RUN_TEST_CASE(Global, metisGenericEther_Create_BadEtherType); + LONGBOW_RUN_TEST_CASE(Global, metisGenericEther_Release); + LONGBOW_RUN_TEST_CASE(Global, metisGenericEther_GetDescriptor); + LONGBOW_RUN_TEST_CASE(Global, metisGenericEther_ReadNextFrame); + LONGBOW_RUN_TEST_CASE(Global, metisGenericEther_ReadNextFrame_WithPadding); + LONGBOW_RUN_TEST_CASE(Global, metisGenericEther_SendFrame); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisGenericEther_Create) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + uint16_t ethertype = 0x0801; + MetisGenericEther *ether = metisGenericEther_Create(metis, NULL, ethertype); + metisForwarder_Destroy(&metis); + + assertNotNull(ether, "Got null ether"); + assertTrue(ether->ethertype == ethertype, "Wrong ethertype, got %x expected %x", ether->ethertype, ethertype); + assertNotNull(ether->workBuffer, "Work buffer is null"); + assertTrue(ether->etherSocket > 0, "Invalid etherSocket, got %d", ether->etherSocket); + + metisGenericEther_Release(ðer); +} + +LONGBOW_TEST_CASE(Global, metisGenericEther_Create_BadEtherType) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + uint16_t ethertype = 0; + MetisGenericEther *ether = metisGenericEther_Create(metis, NULL, ethertype); + metisForwarder_Destroy(&metis); + + assertNull(ether, "Should have gotten NULL for bad ethertype"); +} + +LONGBOW_TEST_CASE(Global, metisGenericEther_Release) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + uint16_t ethertype = 0x0801; + MetisGenericEther *ether = metisGenericEther_Create(metis, NULL, ethertype); + metisForwarder_Destroy(&metis); + + metisGenericEther_Release(ðer); + assertTrue(parcMemory_Outstanding() == 0, "Memory imbalance after release"); + assertNull(ether, "release did not null the pointer"); +} + +LONGBOW_TEST_CASE(Global, metisGenericEther_GetDescriptor) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + uint16_t ethertype = 0x0801; + MetisGenericEther *ether = metisGenericEther_Create(metis, NULL, ethertype); + metisForwarder_Destroy(&metis); + + int fd = metisGenericEther_GetDescriptor(ether); + assertTrue(fd == ether->etherSocket, "Returned wrong descriptor"); + metisGenericEther_Release(ðer); +} + +static void +assertFrameEquals(uint8_t *frame, PARCEventBuffer *test, size_t caplen) +{ + assertTrue(parcEventBuffer_GetLength(test) == caplen, "Wrong length, got %zu expected %zu", parcEventBuffer_GetLength(test), caplen); + + uint8_t *linear = parcEventBuffer_Pullup(test, -1); + assertTrue(memcmp(linear, frame, caplen) == 0, "Buffers do not compare"); +} + +LONGBOW_TEST_CASE(Global, metisGenericEther_ReadNextFrame) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + uint16_t ethertype = 0x0801; + MetisGenericEther *ether = metisGenericEther_Create(metis, NULL, ethertype); + metisForwarder_Destroy(&metis); + + size_t length_a; + uint8_t *buffer_a = createBpfFrame(129, &length_a); + struct bpf_hdr *hdr_a = (struct bpf_hdr *) buffer_a; + uint8_t *frame_a = buffer_a + hdr_a->bh_hdrlen; + + parcEventBuffer_Append(ether->workBuffer, buffer_a, length_a); + + size_t length_b; + uint8_t *buffer_b = createBpfFrame(777, &length_b); + struct bpf_hdr *hdr_b = (struct bpf_hdr *) buffer_b; + uint8_t *frame_b = buffer_b + hdr_b->bh_hdrlen; + + parcEventBuffer_Append(ether->workBuffer, buffer_b, length_b); + + bool success; + + PARCEventBuffer *output = parcEventBuffer_Create(); + + success = metisGenericEther_ReadNextFrame(ether, output); + assertTrue(success, "Failed to read frame A"); + assertFrameEquals(frame_a, output, hdr_a->bh_caplen); + + // clear the buffer before next packet + parcEventBuffer_Read(output, NULL, -1); + + success = metisGenericEther_ReadNextFrame(ether, output); + assertTrue(success, "Failed to read frame B"); + assertFrameEquals(frame_b, output, hdr_b->bh_caplen); + + + parcMemory_Deallocate((void **) &buffer_a); + parcMemory_Deallocate((void **) &buffer_b); + parcEventBuffer_Destroy(&output); + metisGenericEther_Release(ðer); +} + +LONGBOW_TEST_CASE(Global, metisGenericEther_ReadNextFrame_WithPadding) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + uint16_t ethertype = 0x0801; + MetisGenericEther *ether = metisGenericEther_Create(metis, NULL, ethertype); + metisForwarder_Destroy(&metis); + + size_t totalLength = 0; + uint8_t *bpf = createBpfFrameFromEthernet(sizeof(metisTestDataV1_InterestWithEthernetPadding), metisTestDataV1_InterestWithEthernetPadding, &totalLength); + parcEventBuffer_Append(ether->workBuffer, bpf, totalLength); + + PARCEventBuffer *output = parcEventBuffer_Create(); + + bool success; + + success = metisGenericEther_ReadNextFrame(ether, output); + assertTrue(success, "Failed to read frame A"); + assertFrameEquals(metisTestDataV1_InterestWithEthernetPaddingStripped, output, sizeof(metisTestDataV1_InterestWithEthernetPaddingStripped)); + + parcMemory_Deallocate((void **) &bpf); + parcEventBuffer_Destroy(&output); + metisGenericEther_Release(ðer); +} + + +LONGBOW_TEST_CASE(Global, metisGenericEther_SendFrame) +{ + char *interfaceName = getInterfaceName(); + uint16_t etherType = 0x0801; + + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + MetisGenericEther *ether = metisGenericEther_Create(metis, interfaceName, etherType); + metisForwarder_Destroy(&metis); + + PARCEventBuffer *parcEventBuffer = parcEventBuffer_Create(); + char dataBuffer[1024 * 1024]; + parcEventBuffer_Append(parcEventBuffer, dataBuffer, 16); + + bool result = metisGenericEther_SendFrame(ether, parcEventBuffer); + assertTrue(result, "metisGenericEther_Sendframe failed to send smallest packet"); + + parcEventBuffer_Append(parcEventBuffer, dataBuffer, 1024 * 1024); + + result = metisGenericEther_SendFrame(ether, parcEventBuffer); + assertFalse(result, "metisGenericEther_Sendframe should have failed to send packet larger than our MTU"); + + parcEventBuffer_Destroy(&parcEventBuffer); + + metisGenericEther_Release(ðer); + free(interfaceName); +} + +// ================================================================== + +typedef struct test_data { + MetisGenericEther *ether; +} TestData; + +static void +commonSetup(const LongBowTestCase *testCase, const char *device, uint16_t ethertype) +{ + TestData *data = parcMemory_AllocateAndClear(sizeof(TestData)); + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + data->ether = metisGenericEther_Create(metis, device, ethertype); + metisForwarder_Destroy(&metis); + longBowTestCase_SetClipBoardData(testCase, data); +} + +static void +commonTeardown(const LongBowTestCase *testCase) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + metisGenericEther_Release(&data->ether); + parcMemory_Deallocate((void **) &data); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, _darwinEthernet_SetupReceive); + LONGBOW_RUN_TEST_CASE(Local, _darwinEthernet_SetFilter); + LONGBOW_RUN_TEST_CASE(Local, _darwinEthernet_SetDeviceOptions); + LONGBOW_RUN_TEST_CASE(Local, _darwinEthernet_OpenBfpDevice); + LONGBOW_RUN_TEST_CASE(Local, _darwinEthernet_ReadSocket_True); + LONGBOW_RUN_TEST_CASE(Local, _darwinEthernet_ReadSocket_False); + LONGBOW_RUN_TEST_CASE(Local, _darwinEthernet_ReadWorkBuffer); + LONGBOW_RUN_TEST_CASE(Local, _darwinEthernet_ReadWorkBuffer_Short); + + LONGBOW_RUN_TEST_CASE(Local, _darwinEthernet_SetInterfaceAddress); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + commonSetup(testCase, NULL, 0x0801); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + commonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, _darwinEthernet_SetupReceive) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, _darwinEthernet_SetFilter) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, _darwinEthernet_SetDeviceOptions) +{ + int fd = _darwinEthernet_OpenBfpDevice(); + bool success = _darwinEthernet_SetDeviceOptions(fd, NULL); + assertTrue(success, "Error setting device options"); + close(fd); +} + +LONGBOW_TEST_CASE(Local, _darwinEthernet_OpenBfpDevice) +{ + int fd = _darwinEthernet_OpenBfpDevice(); + assertTrue(fd > -1, "Error opening device"); + close(fd); +} + +LONGBOW_TEST_CASE(Local, _darwinEthernet_ReadSocket_True) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // Create a socket pair and pump some test data in + int fd[2]; + int failure = socketpair(PF_LOCAL, SOCK_DGRAM, 0, fd); + assertFalse(failure, "Error on socketpair"); + + // Set non-blocking flag + int flags = fcntl(fd[1], F_GETFL, NULL); + assertTrue(flags != -1, "fcntl failed to obtain file descriptor flags (%d)\n", errno); + failure = fcntl(fd[1], F_SETFL, flags | O_NONBLOCK); + assertFalse(failure, "fcntl failed to set file descriptor flags (%d)\n", errno); + + // swap out the file descriptor from the BPF to our sockpair + close(data->ether->etherSocket); + data->ether->etherSocket = fd[1]; + + size_t totalLength; + uint8_t *buffer = createBpfFrame(129, &totalLength); + + ssize_t bytesWritten = write(fd[0], buffer, totalLength); + assertTrue(bytesWritten == totalLength, "Error on write, got %zd bytes expected %zu bytes", bytesWritten, totalLength); + + bool success = _darwinEthernet_ReadSocket(data->ether); + assertTrue(success, "Did not read buffer even though we put data in socket"); + + // The buffer should contain the whole frame with the BPF header + assertTrue(parcEventBuffer_GetLength(data->ether->workBuffer) == totalLength, "Wrong lenght, got %zu expected %zu", parcEventBuffer_GetLength(data->ether->workBuffer), totalLength); + + uint8_t *test = parcEventBuffer_Pullup(data->ether->workBuffer, -1); + assertTrue(memcmp(test, buffer, totalLength) == 0, "Buffers do not match"); + + parcMemory_Deallocate((void **) &buffer); + close(fd[0]); +} + +LONGBOW_TEST_CASE(Local, _darwinEthernet_ReadSocket_False) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + // Create a socket pair and pump some test data in + int fd[2]; + int failure = socketpair(PF_LOCAL, SOCK_DGRAM, 0, fd); + assertFalse(failure, "Error on socketpair"); + + // Set non-blocking flag + int flags = fcntl(fd[1], F_GETFL, NULL); + assertTrue(flags != -1, "fcntl failed to obtain file descriptor flags (%d)\n", errno); + failure = fcntl(fd[1], F_SETFL, flags | O_NONBLOCK); + assertFalse(failure, "fcntl failed to set file descriptor flags (%d)\n", errno); + + // swap out the file descriptor from the BPF to our sockpair + close(data->ether->etherSocket); + data->ether->etherSocket = fd[1]; + + bool success = _darwinEthernet_ReadSocket(data->ether); + assertFalse(success, "Should have failed to read when no data present"); + + close(fd[0]); +} + +LONGBOW_TEST_CASE(Local, _darwinEthernet_ReadWorkBuffer) +{ + // Put a BFP packet in to the work buffer and make sure it is read correctly + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + size_t totalLength; + uint8_t *buffer = createBpfFrame(129, &totalLength); + struct bpf_hdr *hdr = (struct bpf_hdr *) buffer; + uint8_t *frame = buffer + hdr->bh_hdrlen; + + parcEventBuffer_Append(data->ether->workBuffer, buffer, totalLength); + + // now read the work buffer and make sure we get the right frame + PARCEventBuffer *output = parcEventBuffer_Create(); + _ReadWorkBufferResult result = _darwinEthernet_ReadWorkBuffer(data->ether, output); + assertTrue(result == ReadWorkBufferResult_Ok, "Failed on ReadWorkBuffer"); + + uint8_t *test = parcEventBuffer_Pullup(output, -1); + assertTrue(memcmp(test, frame, hdr->bh_caplen) == 0, "Frames do not match"); + + parcMemory_Deallocate((void **) &buffer); + parcEventBuffer_Destroy(&output); +} + +LONGBOW_TEST_CASE(Local, _darwinEthernet_ReadWorkBuffer_Short) +{ + // put an incomplete frame in to the work buffer + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + size_t totalLength; + uint8_t *buffer = createBpfFrame(129, &totalLength); + + parcEventBuffer_Append(data->ether->workBuffer, buffer, 5); + + // now read the work buffer and make sure we get the right frame + PARCEventBuffer *output = parcEventBuffer_Create(); + _ReadWorkBufferResult result = _darwinEthernet_ReadWorkBuffer(data->ether, output); + assertTrue(result == ReadWorkBufferResult_Empty, "Failed on ReadWorkBuffer"); + + parcMemory_Deallocate((void **) &buffer); + parcEventBuffer_Destroy(&output); +} + +LONGBOW_TEST_CASE(Local, _darwinEthernet_SetInterfaceAddress) +{ + PARCBuffer *addr = NULL; + char ifname[1024]; + + // Lookup the MAC address of an interface that is up, then ask for it. Don't use loopback. + struct ifaddrs *ifaddr; + int failure = getifaddrs(&ifaddr); + assertFalse(failure, "Error getifaddrs: (%d) %s", errno, strerror(errno)); + + struct ifaddrs *next; + for (next = ifaddr; next != NULL; next = next->ifa_next) { + if ((next->ifa_addr == NULL) || ((next->ifa_flags & IFF_UP) == 0)) { + continue; + } + + if (next->ifa_flags & IFF_LOOPBACK) { + continue; + } + + if (next->ifa_addr->sa_family == AF_LINK) { + strcpy(ifname, next->ifa_name); + + struct sockaddr_dl *addr_dl = (struct sockaddr_dl *) next->ifa_addr; + + // addr_dl->sdl_data[12] contains the interface name followed by the MAC address, so + // need to offset in to the array past the interface name. + addr = parcBuffer_Allocate(addr_dl->sdl_alen); + parcBuffer_PutArray(addr, addr_dl->sdl_alen, (uint8_t *) &addr_dl->sdl_data[ addr_dl->sdl_nlen]); + parcBuffer_Flip(addr); + break; + } + } + freeifaddrs(ifaddr); + + + // If we could find an address, try to get it + TestData *data = longBowTestCase_GetClipBoardData(testCase); + _darwinEthernet_SetInterfaceAddress(data->ether, ifname); + + assertTrue(parcBuffer_Equals(addr, data->ether->macAddress), "Addresses do not match") + { + parcBuffer_Display(addr, 0); + parcBuffer_Display(data->ether->macAddress, 0); + } + + parcBuffer_Release(&addr); +} + + +// ================================================================== + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(darwin_Ethernet); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/platforms/darwin/test/test_metis_System.c b/metis/ccnx/forwarder/metis/platforms/darwin/test/test_metis_System.c new file mode 100644 index 00000000..126a5bf7 --- /dev/null +++ b/metis/ccnx/forwarder/metis/platforms/darwin/test/test_metis_System.c @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. + +#pragma weak metisSystem_Interfaces + +#include "../metis_System.c" + +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> +#include <parc/algol/parc_Memory.h> + + +// Include the generic tests of MetisSystem +#include "../../test/testrig_metis_System.c" + +LONGBOW_TEST_RUNNER(darwin_Interface) +{ + LONGBOW_RUN_TEST_FIXTURE(Global); + + // these are defined in testrig_metis_System.c + LONGBOW_RUN_TEST_FIXTURE(PublicApi); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(darwin_Interface) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(darwin_Interface) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ================================================================== + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisSystem_Interfaces); + LONGBOW_RUN_TEST_CASE(Global, metisSystem_InterfaceMTU); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisSystem_Interfaces) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + CPIInterfaceSet *set = metisSystem_Interfaces(metis); + assertNotNull(set, "metisSystem_Interfaces return null set"); + + // XXX we need some sort of validation test. e.g. open a socket, then ioctl to + // XXX get the interface name, then verify its in the list. + + size_t length = cpiInterfaceSet_Length(set); + assertTrue(length > 0, "metisSystem_Interfaces returned no interfaces"); + + for (size_t i = 0; i < length; i++) { + CPIInterface *iface = cpiInterfaceSet_GetByOrdinalIndex(set, i); + printf("Interface Index %u\n", cpiInterface_GetInterfaceIndex(iface)); + const CPIAddressList *list = cpiInterface_GetAddresses(iface); + PARCJSONArray *jsonArray = cpiAddressList_ToJson(list); + char *str = parcJSONArray_ToString(jsonArray); + printf("%s\n", str); + parcMemory_Deallocate((void **) &str); + parcJSONArray_Release(&jsonArray); + } + + cpiInterfaceSet_Destroy(&set); + metisForwarder_Destroy(&metis); +} + +// returns a strdup() of the interface name, use free(3) +static char * +_pickInterfaceName(MetisForwarder *metis) +{ + char *ifname = NULL; + + CPIInterfaceSet *set = metisSystem_Interfaces(metis); + size_t length = cpiInterfaceSet_Length(set); + assertTrue(length > 0, "metisSystem_Interfaces returned no interfaces"); + + for (size_t i = 0; i < length; i++) { + CPIInterface *iface = cpiInterfaceSet_GetByOrdinalIndex(set, i); + const CPIAddressList *addressList = cpiInterface_GetAddresses(iface); + + size_t length = cpiAddressList_Length(addressList); + for (size_t i = 0; i < length && !ifname; i++) { + const CPIAddress *a = cpiAddressList_GetItem(addressList, i); + if (cpiAddress_GetType(a) == cpiAddressType_LINK) { + ifname = strdup(cpiInterface_GetName(iface)); + } + } + } + + cpiInterfaceSet_Destroy(&set); + return ifname; +} + +LONGBOW_TEST_CASE(Global, metisSystem_InterfaceMTU) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + + char *deviceName = _pickInterfaceName(metis); + unsigned mtu = metisSystem_InterfaceMtu(metis, deviceName); + + assertTrue(mtu > 0, "Did not get mtu for interface %s", deviceName); + free(deviceName); + metisForwarder_Destroy(&metis); +} + +// ================================================================== + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(darwin_Interface); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/platforms/linux/metis_GenericEther.c b/metis/ccnx/forwarder/metis/platforms/linux/metis_GenericEther.c new file mode 100644 index 00000000..82542983 --- /dev/null +++ b/metis/ccnx/forwarder/metis/platforms/linux/metis_GenericEther.c @@ -0,0 +1,374 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Implements the platform-specific code for working with an Ethernet interface. + * + * The Linux Ethernet device uses an AF_PACKET socket SOCK_RAW. + * + */ + +#include <config.h> +#include <stdio.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> + +#include <arpa/inet.h> +#include <linux/if_packet.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <net/if.h> +#include <netinet/ether.h> +#include <net/ethernet.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> +#include <ccnx/forwarder/metis/io/metis_GenericEther.h> +#include <ccnx/forwarder/metis/core/metis_System.h> +#include <parc/algol/parc_Object.h> +#include <ccnx/forwarder/metis/tlv/metis_Tlv.h> +#include <ccnx/forwarder/metis/io/metis_Ethernet.h> + +struct metis_generic_ether { + uint16_t ethertype; + int etherSocket; + + int linuxInterfaceIndex; + + PARCBuffer *macAddress; + MetisLogger *logger; + + // MTU set on interface when we are created + unsigned mtu; +}; + +static bool _linuxEthernet_SetupSocket(MetisGenericEther *ether, const char *devstr); + +static void +_metisGenericEther_Destroy(MetisGenericEther **etherPtr) +{ + MetisGenericEther *ether = *etherPtr; + + if (ether->etherSocket > 0) { + close(ether->etherSocket); + } + + if (ether->macAddress) { + parcBuffer_Release(ðer->macAddress); + } + + metisLogger_Release(ðer->logger); +} + +parcObject_ExtendPARCObject(MetisGenericEther, _metisGenericEther_Destroy, NULL, NULL, NULL, NULL, NULL, NULL); + +parcObject_ImplementAcquire(metisGenericEther, MetisGenericEther); + +parcObject_ImplementRelease(metisGenericEther, MetisGenericEther); + +// ========================= +// PUBLIC API +// ========================= + +MetisGenericEther * +metisGenericEther_Create(MetisForwarder *metis, const char *deviceName, uint16_t etherType) +{ + assertNotNull(metis, "Parameter metis must be non-null"); + assertNotNull(deviceName, "Parameter deviceName must be non-null"); + + MetisGenericEther *ether = NULL; + + if (metisEthernet_IsValidEthertype(etherType)) { + ether = parcObject_CreateInstance(MetisGenericEther); + ether->ethertype = etherType; + ether->logger = metisLogger_Acquire(metisForwarder_GetLogger(metis)); + ether->mtu = metisSystem_InterfaceMtu(metis, deviceName); + + ether->etherSocket = -1; // invalid valid + ether->macAddress = NULL; + + bool success = _linuxEthernet_SetupSocket(ether, deviceName); + + if (success) { + if (metisLogger_IsLoggable(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + char *str = parcBuffer_ToHexString(ether->macAddress); + metisLogger_Log(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "GenericEther %p created on device %s (%s) for ethertype 0x%04x fd %d ifindex %u mtu %u", + (void *) ether, deviceName, str, etherType, ether->etherSocket, ether->linuxInterfaceIndex, + ether->mtu); + parcMemory_Deallocate((void **) &str); + } + } else { + if (metisLogger_IsLoggable(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "GenericEther failed to created on device %s for ethertype 0x%04x", + deviceName, etherType); + } + + // this will also null ether + metisGenericEther_Release(ðer); + } + } else { + if (metisLogger_IsLoggable(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "GenericEther failed to created on device %s for ethertype 0x%04x, invalid ethertype", + deviceName, etherType); + } + } + + return ether; +} + +int +metisGenericEther_GetDescriptor(const MetisGenericEther *ether) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + return ether->etherSocket; +} + +/** + * Based on the fixed header, trim the buffer + * + * Some platforms do not strip the ethernet CRC from the raw packet. Trim the buffer to + * the right sized based on the fixed header. + * + * @param [in] ether An allocated ethernet interface + * @param [in] readBuffer The buffer to trim + * + * Example: + * @code + * <#example#> + * @endcode + */ +static void +_linuxEthernet_TrimBuffer(MetisGenericEther *ether, PARCEventBuffer *readBuffer) +{ + // read the fixed header + uint8_t *etherHeader = parcEventBuffer_Pullup(readBuffer, ETHER_HDR_LEN + metisTlv_FixedHeaderLength()); + + if (etherHeader) { + uint8_t *fixedHeader = etherHeader + ETHER_HDR_LEN; + size_t totalLength = metisTlv_TotalPacketLength(fixedHeader) + ETHER_HDR_LEN; + + if (parcEventBuffer_GetLength(readBuffer) > totalLength) { + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "%s buffer length %zu, actual length %zu (ether header + ccnx packet), trimming %zu bytes", + __func__, parcEventBuffer_GetLength(readBuffer), totalLength, + parcEventBuffer_GetLength(readBuffer) - totalLength); + } + + PARCEventBuffer *temp = parcEventBuffer_Create(); + + // there's no way to drain from the end, so we move to a tempororary buffer, + // drain the unwanted part, then move back. + + int movedBytes = parcEventBuffer_ReadIntoBuffer(readBuffer, temp, totalLength); + assertTrue(movedBytes == totalLength, "Failed to move all the bytes, got %d expected %zu", movedBytes, totalLength); + + // flush all the bytes out of the read buffer + parcEventBuffer_Read(readBuffer, NULL, -1); + + // now put back what we want + int failure = parcEventBuffer_AppendBuffer(temp, readBuffer); + assertFalse(failure, "parcEventBuffer_AppendBuffer failed"); + + parcEventBuffer_Destroy(&temp); + } + } +} + +/* + * Reading a raw socket, on some systems, may include the FCS trailer + */ +bool +metisGenericEther_ReadNextFrame(MetisGenericEther *ether, PARCEventBuffer *readBuffer) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + assertNotNull(readBuffer, "Parameter readBuffer must be non-null"); + + bool success = false; + + int evread_length = parcEventBuffer_ReadFromFileDescriptor(readBuffer, ether->etherSocket, (int) -1); + + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "%s read length %d", __func__, evread_length); + } + + if (evread_length > 0) { + _linuxEthernet_TrimBuffer(ether, readBuffer); + success = true; + } + + return success; +} + +bool +metisGenericEther_SendFrame(MetisGenericEther *ether, PARCEventBuffer *buffer) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + + // cannot use parcEventBuffer_WriteToFileDescriptor because we need to write the length in one go, not use the + // iovec approach in parcEventBuffer_WriteToFileDescriptor. It can cause problems on some platforms. + + uint8_t *linear = parcEventBuffer_Pullup(buffer, -1); + size_t length = parcEventBuffer_GetLength(buffer); + + ssize_t written = write(ether->etherSocket, linear, length); + if (written == length) { + return true; + } + return false; +} + +PARCBuffer * +metisGenericEther_GetMacAddress(const MetisGenericEther *ether) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + return ether->macAddress; +} + +uint16_t +metisGenericEther_GetEtherType(const MetisGenericEther *ether) +{ + assertNotNull(ether, "Parameter ether must be non-null"); + return ether->ethertype; +} + +unsigned +metisGenericEther_GetMTU(const MetisGenericEther *ether) +{ + return ether->mtu; +} + +// ================== +// PRIVATE API + +static bool +_linuxEthernet_SetInterfaceIndex(MetisGenericEther *ether, const char *devstr) +{ + // get the interface index of the desired device + bool success = false; + + struct ifreq if_idx; + memset(&if_idx, 0, sizeof(if_idx)); + strncpy(if_idx.ifr_name, devstr, IFNAMSIZ - 1); + if (!ioctl(ether->etherSocket, SIOCGIFINDEX, &if_idx)) { + ether->linuxInterfaceIndex = if_idx.ifr_ifindex; + success = true; + } else { + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "ioctl SIOCGIFINDEX error: (%d) %s", errno, strerror(errno)); + } + } + + return success; +} + +static bool +_linuxEthernet_SetInterfaceAddress(MetisGenericEther *ether, const char *devstr) +{ + bool success = false; + + assertNull(ether->macAddress, "Should only be called once with null macAddress"); + + // get the MAC address of the device + struct ifreq if_mac; + memset(&if_mac, 0, sizeof(if_mac)); + strncpy(if_mac.ifr_name, devstr, IFNAMSIZ - 1); + if (!ioctl(ether->etherSocket, SIOCGIFHWADDR, &if_mac)) { + if (if_mac.ifr_hwaddr.sa_family == ARPHRD_ETHER) { + ether->macAddress = parcBuffer_Allocate(ETHER_ADDR_LEN); + parcBuffer_PutArray(ether->macAddress, ETHER_ADDR_LEN, (uint8_t *) if_mac.ifr_hwaddr.sa_data); + parcBuffer_Flip(ether->macAddress); + success = true; + } else { + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "Device %s does not have an Ethernet hardware address", devstr); + } + } + } + return success; +} + +static bool +_linuxEthernet_Bind(MetisGenericEther *ether) +{ + bool success = false; + + // we need to bind to the ethertype to receive packets + struct sockaddr_ll my_addr; + my_addr.sll_family = PF_PACKET; + my_addr.sll_protocol = htons(ether->ethertype); + my_addr.sll_ifindex = ether->linuxInterfaceIndex; + + if (!bind(ether->etherSocket, (struct sockaddr *) &my_addr, sizeof(struct sockaddr_ll))) { + success = true; + } else { + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "bind error: (%d) %s", errno, strerror(errno)); + } + } + return success; +} + +static bool +_linuxEthernet_SetNonBlocking(MetisGenericEther *ether) +{ + bool success = false; + uint32_t on = 1; + if (!ioctl(ether->etherSocket, FIONBIO, &on)) { + success = true; + } + return success; +} + +static bool +_linuxEthernet_SetupSocket(MetisGenericEther *ether, const char *devstr) +{ + bool success = false; + ether->etherSocket = socket(AF_PACKET, SOCK_RAW, htons(ether->ethertype)); + if (ether->etherSocket > 0) { + if (_linuxEthernet_SetInterfaceIndex(ether, devstr)) { + if (_linuxEthernet_SetInterfaceAddress(ether, devstr)) { + if (_linuxEthernet_Bind(ether)) { + // set non-blocking + if (_linuxEthernet_SetNonBlocking(ether)) { + success = true; + } + } + } + } + } + + if (!success) { + if (metisLogger_IsLoggable(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error)) { + metisLogger_Log(ether->logger, MetisLoggerFacility_IO, PARCLogLevel_Error, __func__, + "setup socket error: (%d) %s", errno, strerror(errno)); + } + } + + return success; +} + + + diff --git a/metis/ccnx/forwarder/metis/platforms/linux/metis_System.c b/metis/ccnx/forwarder/metis/platforms/linux/metis_System.c new file mode 100644 index 00000000..828566d8 --- /dev/null +++ b/metis/ccnx/forwarder/metis/platforms/linux/metis_System.c @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/ioctl.h> +#include <ifaddrs.h> +#include <errno.h> +#include <string.h> + +//#define __USE_MISC +#include <net/if.h> + +// to get the list of arp types +#include <net/if_arp.h> + +// for the mac address +#include <netpacket/packet.h> + +#include <ccnx/api/control/cpi_InterfaceSet.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> + +#include <LongBow/runtime.h> + +/** + * Returns the MTU for a named interface + * + * On linux, we get the MTU by opening a socket and reading SIOCGIFMTU + * + * @param [in] ifname Interface name (e.g. "eth0") + * + * @retval number The MTU in bytes + * + * Example: + * @code + * <#example#> + * @endcode + */ +static int +getMtu(const char *ifname) +{ + struct ifreq ifr; + int fd; + + fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); + + strcpy(ifr.ifr_name, ifname); + ioctl(fd, SIOCGIFMTU, &ifr); + + close(fd); + return ifr.ifr_mtu; +} + +CPIInterfaceSet * +metisSystem_Interfaces(MetisForwarder *metis) +{ + CPIInterfaceSet *set = cpiInterfaceSet_Create(); + + MetisLogger *logger = metisForwarder_GetLogger(metis); + + // this is the dynamically allocated head of the list + struct ifaddrs *ifaddr; + int failure = getifaddrs(&ifaddr); + assertFalse(failure, "Error getifaddrs: (%d) %s", errno, strerror(errno)); + + struct ifaddrs *next; + for (next = ifaddr; next != NULL; next = next->ifa_next) { + if ((next->ifa_addr == NULL) || ((next->ifa_flags & IFF_UP) == 0)) { + continue; + } + + CPIInterface *iface = cpiInterfaceSet_GetByName(set, next->ifa_name); + if (iface == NULL) { + unsigned mtu = (unsigned) getMtu(next->ifa_name); + + iface = cpiInterface_Create(next->ifa_name, + metisForwarder_GetNextConnectionId(metis), + next->ifa_flags & IFF_LOOPBACK, + next->ifa_flags & IFF_MULTICAST, + mtu); + + cpiInterfaceSet_Add(set, iface); + } + + int family = next->ifa_addr->sa_family; + switch (family) { + case AF_INET: { + CPIAddress *address = cpiAddress_CreateFromInet((struct sockaddr_in *) next->ifa_addr); + cpiInterface_AddAddress(iface, address); + break; + } + + case AF_INET6: { + CPIAddress *address = cpiAddress_CreateFromInet6((struct sockaddr_in6 *) next->ifa_addr); + cpiInterface_AddAddress(iface, address); + break; + } + + case AF_PACKET: { + struct sockaddr_ll *addr_ll = (struct sockaddr_ll *) next->ifa_addr; + + if (metisLogger_IsLoggable(logger, MetisLoggerFacility_IO, PARCLogLevel_Debug)) { + metisLogger_Log(logger, MetisLoggerFacility_IO, PARCLogLevel_Debug, __func__, + "sockaddr_ll family %d proto %d ifindex %d hatype %d pkttype %d halen %d", + addr_ll->sll_family, + addr_ll->sll_protocol, + addr_ll->sll_ifindex, + addr_ll->sll_hatype, + addr_ll->sll_pkttype, + addr_ll->sll_halen); + } + + switch (addr_ll->sll_hatype) { + // list of the ARP hatypes we can extract a MAC address from + case ARPHRD_ETHER: + // fallthrough + case ARPHRD_IEEE802: { + CPIAddress *address = cpiAddress_CreateFromLink((uint8_t *) addr_ll->sll_addr, addr_ll->sll_halen); + cpiInterface_AddAddress(iface, address); + break; + } + default: + break; + } + + break; + } + } + } + + freeifaddrs(ifaddr); + return set; +} + +CPIAddress * +metisSystem_GetMacAddressByName(MetisForwarder *metis, const char *interfaceName) +{ + CPIAddress *linkAddress = NULL; + + CPIInterfaceSet *interfaceSet = metisSystem_Interfaces(metis); + CPIInterface *interface = cpiInterfaceSet_GetByName(interfaceSet, interfaceName); + + if (interface) { + const CPIAddressList *addressList = cpiInterface_GetAddresses(interface); + + size_t length = cpiAddressList_Length(addressList); + for (size_t i = 0; i < length && !linkAddress; i++) { + const CPIAddress *a = cpiAddressList_GetItem(addressList, i); + if (cpiAddress_GetType(a) == cpiAddressType_LINK) { + linkAddress = cpiAddress_Copy(a); + } + } + } + + cpiInterfaceSet_Destroy(&interfaceSet); + + return linkAddress; +} + +unsigned +metisSystem_InterfaceMtu(MetisForwarder *metis, const char *interfaceName) +{ + unsigned mtu = 0; + + CPIInterfaceSet *interfaceSet = metisSystem_Interfaces(metis); + CPIInterface *interface = cpiInterfaceSet_GetByName(interfaceSet, interfaceName); + + if (interface) { + mtu = cpiInterface_GetMTU(interface); + } + + cpiInterfaceSet_Destroy(&interfaceSet); + + return mtu; +} diff --git a/metis/ccnx/forwarder/metis/platforms/linux/test/.gitignore b/metis/ccnx/forwarder/metis/platforms/linux/test/.gitignore new file mode 100644 index 00000000..44d3cf6d --- /dev/null +++ b/metis/ccnx/forwarder/metis/platforms/linux/test/.gitignore @@ -0,0 +1,2 @@ +test_metis_GenericEther +test_metis_System diff --git a/metis/ccnx/forwarder/metis/platforms/linux/test/test_metis_GenericEther.c b/metis/ccnx/forwarder/metis/platforms/linux/test/test_metis_GenericEther.c new file mode 100644 index 00000000..aedc5363 --- /dev/null +++ b/metis/ccnx/forwarder/metis/platforms/linux/test/test_metis_GenericEther.c @@ -0,0 +1,566 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis_GenericEther.c" + +#include <ifaddrs.h> +#include <poll.h> + +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> +#include <parc/algol/parc_Memory.h> +#include <parc/logging/parc_LogReporterTextStdout.h> + +#include <ccnx/forwarder/metis/tlv/metis_Tlv.h> + +#include <ccnx/forwarder/metis/testdata/metis_TestDataV0.h> +#include <ccnx/forwarder/metis/testdata/metis_TestDataV1.h> + +/* + * This is no longer gobally exported, so reproduce here for tests + */ +typedef struct __attribute__ ((__packed__)) metis_tlv_fixed_header { + uint8_t version; + uint8_t packetType; + uint16_t payloadLength; + uint16_t reserved; + uint16_t headerLength; +} _MetisTlvFixedHeaderV0; + +static char * +getInterfaceName(void) +{ + // Lookup the MAC address of an interface that is up, then ask for it. Don't use loopback. + struct ifaddrs *ifaddr; + int failure = getifaddrs(&ifaddr); + assertFalse(failure, "Error getifaddrs: (%d) %s", errno, strerror(errno)); + + char *ifname = NULL; + + struct ifaddrs *next; + for (next = ifaddr; next != NULL && ifname == NULL; next = next->ifa_next) { + if ((next->ifa_addr == NULL) || ((next->ifa_flags & IFF_UP) == 0)) { + continue; + } + + if (next->ifa_flags & IFF_LOOPBACK) { + continue; + } + + if (next->ifa_addr->sa_family == AF_PACKET) { + ifname = strdup(next->ifa_name); + } + } + freeifaddrs(ifaddr); + return ifname; +} + +static char *interfaceName = NULL; + +// ===================================== + + +LONGBOW_TEST_RUNNER(linux_Ethernet) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + LONGBOW_RUN_TEST_FIXTURE(Local); +} + +/* + * If we cannot open a raw socket, we cannot run any of these tests. + */ +static bool +_checkForRawAbility(void) +{ + bool success = false; + uint16_t ethertype = 0x0801; + int fd = socket(AF_PACKET, SOCK_RAW, htons(ethertype)); + if (fd > 0) { + success = true; + close(fd); + } + + return success; +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(linux_Ethernet) +{ + parcMemory_SetInterface(&PARCSafeMemoryAsPARCMemory); + if (_checkForRawAbility()) { + interfaceName = getInterfaceName(); + return LONGBOW_STATUS_SUCCEEDED; + } + + fprintf(stderr, "%s failed to open an AF_PACKET SOCK_RAW socket, cannot execute tests\n", __func__); + // exit here with autoconf SKIP code until LongBow exits that way for skipped tests + exit(77); + return LONGBOW_STATUS_SETUP_SKIPTESTS; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(linux_Ethernet) +{ + free(interfaceName); + + return LONGBOW_STATUS_SUCCEEDED; +} + +static uint8_t * +createEtherFrame(uint32_t frameLength) +{ + uint8_t *frame = parcMemory_AllocateAndClear(frameLength); + + for (int i = 0; i < frameLength; i++) { + frame[i] = i * frameLength; + } + + // Create a proper header + size_t messageLength = frameLength - ETHER_HDR_LEN; + size_t payloadLength = messageLength - metisTlv_FixedHeaderLength(); + + _MetisTlvFixedHeaderV0 *fixedHeader = (_MetisTlvFixedHeaderV0 *) (frame + ETHER_HDR_LEN); + fixedHeader->version = 0; + fixedHeader->packetType = 1; + fixedHeader->payloadLength = payloadLength; + fixedHeader->headerLength = 0; + + return frame; +} + +static PARCBuffer * +createInterestFrame(size_t extrabytes) +{ + size_t totalLength = sizeof(metisTestDataV0_EncodedInterest) + extrabytes + ETHER_HDR_LEN; + uint8_t *frame = createEtherFrame(totalLength); + + memcpy(frame + ETHER_HDR_LEN, metisTestDataV0_EncodedInterest, sizeof(metisTestDataV0_EncodedInterest)); + + PARCBuffer *buffer = parcBuffer_Allocate(totalLength); + parcBuffer_PutArray(buffer, totalLength, frame); + parcBuffer_Flip(buffer); + parcMemory_Deallocate((void **) &frame); + + return buffer; +} + +// ================================================================== + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisGenericEther_Create); + LONGBOW_RUN_TEST_CASE(Global, metisGenericEther_Create_BadEtherType); + LONGBOW_RUN_TEST_CASE(Global, metisGenericEther_Release); + LONGBOW_RUN_TEST_CASE(Global, metisGenericEther_GetDescriptor); + LONGBOW_RUN_TEST_CASE(Global, metisGenericEther_ReadNextFrame); + LONGBOW_RUN_TEST_CASE(Global, metisGenericEther_ReadNextFrame_WithPadding); + LONGBOW_RUN_TEST_CASE(Global, metisGenericEther_SendFrame); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisGenericEther_Create) +{ + uint16_t ethertype = 0x0801; + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + MetisGenericEther *ether = metisGenericEther_Create(metis, interfaceName, ethertype); + metisForwarder_Destroy(&metis); + + assertNotNull(ether, "Got null ether"); + assertTrue(ether->ethertype == ethertype, "Wrong ethertype, got %x expected %x", ether->ethertype, ethertype); + assertTrue(ether->etherSocket > 0, "Invalid etherSocket, got %d", ether->etherSocket); + + metisGenericEther_Release(ðer); +} + +LONGBOW_TEST_CASE(Global, metisGenericEther_Create_BadEtherType) +{ + uint16_t ethertype = 0x0000; + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + MetisGenericEther *ether = metisGenericEther_Create(metis, interfaceName, ethertype); + metisForwarder_Destroy(&metis); + + assertNull(ether, "Should have gotten NULL for bad ethertype"); +} + + +LONGBOW_TEST_CASE(Global, metisGenericEther_Release) +{ + uint16_t ethertype = 0x0801; + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + MetisGenericEther *ether = metisGenericEther_Create(metis, interfaceName, ethertype); + metisForwarder_Destroy(&metis); + metisGenericEther_Release(ðer); + assertTrue(parcMemory_Outstanding() == 0, "Memory imbalance after release"); + assertNull(ether, "release did not null the pointer"); +} + +LONGBOW_TEST_CASE(Global, metisGenericEther_GetDescriptor) +{ + uint16_t ethertype = 0x0801; + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + MetisGenericEther *ether = metisGenericEther_Create(metis, interfaceName, ethertype); + metisForwarder_Destroy(&metis); + int fd = metisGenericEther_GetDescriptor(ether); + assertTrue(fd == ether->etherSocket, "Returned wrong descriptor"); + metisGenericEther_Release(ðer); +} + +static void +assertFrameEquals(uint8_t *frame, PARCEventBuffer *test, size_t caplen) +{ + assertTrue(parcEventBuffer_GetLength(test) == caplen, "Wrong length, got %zu expected %zu", parcEventBuffer_GetLength(test), caplen); + + uint8_t *linear = parcEventBuffer_Pullup(test, -1); + assertTrue(memcmp(linear, frame, caplen) == 0, "Buffers do not compare"); +} + +LONGBOW_TEST_CASE(Global, metisGenericEther_ReadNextFrame) +{ + uint16_t ethertype = 0x0801; + + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + MetisGenericEther *ether = metisGenericEther_Create(metis, interfaceName, ethertype); + metisForwarder_Destroy(&metis); + + // swap out the PF_PACKET socket for a socket pair + close(ether->etherSocket); + + int fd[2]; + socketpair(PF_LOCAL, SOCK_DGRAM, 0, fd); + + int localSocket = fd[0]; + ether->etherSocket = fd[1]; + _linuxEthernet_SetNonBlocking(ether); + + size_t length_a = 129; + uint8_t *frame_a = createEtherFrame(length_a); + + size_t length_b = 777; + uint8_t *frame_b = createEtherFrame(length_b); + + ssize_t nwritten = write(localSocket, frame_a, length_a); + assertTrue(nwritten == length_a, "Error on write, expected %zu got %zd", length_a, nwritten); + + nwritten = write(localSocket, frame_b, length_b); + assertTrue(nwritten == length_b, "Error on write, expected %zu got %zd", length_b, nwritten); + + + // wait for it to become available + struct pollfd pfd; + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = ether->etherSocket; + pfd.events = POLLIN | POLLERR; + + poll(&pfd, 1, 10); + + // Something is ready to read + + bool success; + + PARCEventBuffer *output = parcEventBuffer_Create(); + + success = metisGenericEther_ReadNextFrame(ether, output); + assertTrue(success, "Failed to read frame A"); + assertFrameEquals(frame_a, output, length_a); + + // clear the buffer before next packet + parcEventBuffer_Read(output, NULL, -1); + + success = metisGenericEther_ReadNextFrame(ether, output); + assertTrue(success, "Failed to read frame B"); + assertFrameEquals(frame_b, output, length_b); + + close(localSocket); + + parcMemory_Deallocate((void **) &frame_a); + parcMemory_Deallocate((void **) &frame_b); + parcEventBuffer_Destroy(&output); + metisGenericEther_Release(ðer); +} + +LONGBOW_TEST_CASE(Global, metisGenericEther_ReadNextFrame_WithPadding) +{ + uint16_t ethertype = 0x0801; + + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + MetisGenericEther *ether = metisGenericEther_Create(metis, interfaceName, ethertype); + metisForwarder_Destroy(&metis); + + // swap out the PF_PACKET socket for a socket pair + close(ether->etherSocket); + + int fd[2]; + socketpair(PF_LOCAL, SOCK_DGRAM, 0, fd); + + int localSocket = fd[0]; + ether->etherSocket = fd[1]; + _linuxEthernet_SetNonBlocking(ether); + + ssize_t nwritten = write(localSocket, metisTestDataV1_InterestWithEthernetPadding, sizeof(metisTestDataV1_InterestWithEthernetPadding)); + assertTrue(nwritten == sizeof(metisTestDataV1_InterestWithEthernetPadding), + "Error on write, expected %zu got %zd", + sizeof(metisTestDataV1_InterestWithEthernetPadding), nwritten); + + // wait for it to become available + struct pollfd pfd; + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = ether->etherSocket; + pfd.events = POLLIN | POLLERR; + + poll(&pfd, 1, 10); + + // Something is ready to read + + bool success; + + PARCEventBuffer *output = parcEventBuffer_Create(); + + success = metisGenericEther_ReadNextFrame(ether, output); + assertTrue(success, "Failed to read frame A"); + assertFrameEquals(metisTestDataV1_InterestWithEthernetPaddingStripped, output, sizeof(metisTestDataV1_InterestWithEthernetPaddingStripped)); + + close(localSocket); + + parcEventBuffer_Destroy(&output); + metisGenericEther_Release(ðer); +} + +LONGBOW_TEST_CASE(Global, metisGenericEther_SendFrame) +{ + char *interfaceName = getInterfaceName(); + uint16_t ethertype = 0x0801; + + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + MetisGenericEther *ether = metisGenericEther_Create(metis, interfaceName, ethertype); + metisForwarder_Destroy(&metis); + + PARCEventBuffer *parcEventBuffer = parcEventBuffer_Create(); + char dataBuffer[1024 * 1024]; + parcEventBuffer_Append(parcEventBuffer, dataBuffer, 16); + + bool result = metisGenericEther_SendFrame(ether, parcEventBuffer); + assertTrue(result, "metisGenericEther_Sendframe failed to send smallest packet"); + + parcEventBuffer_Append(parcEventBuffer, dataBuffer, 1024 * 1024); + + result = metisGenericEther_SendFrame(ether, parcEventBuffer); + assertFalse(result, "metisGenericEther_Sendframe should have failed to send packet larger than our MTU"); + + parcEventBuffer_Destroy(&parcEventBuffer); + + metisGenericEther_Release(ðer); + + free(interfaceName); +} + +// ================================================================== + +typedef struct test_data { + MetisGenericEther *ether; +} TestData; + +static void +commonSetup(const LongBowTestCase *testCase, const char *device, uint16_t ethertype) +{ + TestData *data = parcMemory_AllocateAndClear(sizeof(TestData)); + + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + data->ether = metisGenericEther_Create(metis, interfaceName, ethertype); + metisForwarder_Destroy(&metis); + + longBowTestCase_SetClipBoardData(testCase, data); +} + +static void +commonTeardown(const LongBowTestCase *testCase) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + metisGenericEther_Release(&data->ether); + parcMemory_Deallocate((void **) &data); +} + +LONGBOW_TEST_FIXTURE(Local) +{ + LONGBOW_RUN_TEST_CASE(Local, _linuxEthernet_SetInterfaceIndex); + LONGBOW_RUN_TEST_CASE(Local, _linuxEthernet_SetInterfaceAddress); + LONGBOW_RUN_TEST_CASE(Local, _linuxEthernet_Bind); + LONGBOW_RUN_TEST_CASE(Local, _linuxEthernet_SetNonBlocking); + LONGBOW_RUN_TEST_CASE(Local, _linuxEthernet_SetupSocket); + LONGBOW_RUN_TEST_CASE(Local, _linuxEthernet_TrimBuffer_Length_OK); + LONGBOW_RUN_TEST_CASE(Local, _linuxEthernet_TrimBuffer_Length_Trim); +} + +LONGBOW_TEST_FIXTURE_SETUP(Local) +{ + commonSetup(testCase, interfaceName, 0x0801); + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Local) +{ + commonTeardown(testCase); + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Local, _linuxEthernet_SetInterfaceIndex) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, _linuxEthernet_Bind) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, _linuxEthernet_SetNonBlocking) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, _linuxEthernet_SetupSocket) +{ + testUnimplemented(""); +} + +LONGBOW_TEST_CASE(Local, _linuxEthernet_SetInterfaceAddress) +{ + PARCBuffer *addr = NULL; + char ifname[1024]; + + // Lookup the MAC address of the interface we chose back in the fixture setup + struct ifaddrs *ifaddr; + int failure = getifaddrs(&ifaddr); + assertFalse(failure, "Error getifaddrs: (%d) %s", errno, strerror(errno)); + + struct ifaddrs *next; + for (next = ifaddr; next != NULL && addr == NULL; next = next->ifa_next) { + if (strcmp(interfaceName, next->ifa_name) == 0) { + if (next->ifa_addr->sa_family == AF_PACKET) { + strcpy(ifname, next->ifa_name); + + struct sockaddr_ll *addr_ll = (struct sockaddr_ll *) next->ifa_addr; + + switch (addr_ll->sll_hatype) { + // list of the ARP hatypes we can extract a MAC address from + case ARPHRD_ETHER: + // fallthrough + case ARPHRD_IEEE802: { + addr = parcBuffer_Allocate(addr_ll->sll_halen); + parcBuffer_PutArray(addr, addr_ll->sll_halen, (uint8_t *) addr_ll->sll_addr); + parcBuffer_Flip(addr); + break; + } + default: + break; + } + } + } + } + freeifaddrs(ifaddr); + + if (addr) { + uint16_t ethertype = 0x0801; + MetisForwarder *metis = metisForwarder_Create(NULL); + metisLogger_SetLogLevel(metisForwarder_GetLogger(metis), MetisLoggerFacility_IO, PARCLogLevel_Debug); + MetisGenericEther *ether = metisGenericEther_Create(metis, interfaceName, ethertype); + metisForwarder_Destroy(&metis); + + assertTrue(parcBuffer_Equals(addr, ether->macAddress), "Addresses do not match") + { + parcBuffer_Display(addr, 0); + parcBuffer_Display(ether->macAddress, 0); + } + + parcBuffer_Release(&addr); + metisGenericEther_Release(ðer); + } +} + +static void +trimBufferTest(const LongBowTestCase *testCase, size_t extraBytes) +{ + TestData *data = longBowTestCase_GetClipBoardData(testCase); + + PARCEventBuffer *buffer = parcEventBuffer_Create(); + + PARCBuffer *frameBuffer = createInterestFrame(extraBytes); + size_t expectedSize = parcBuffer_Remaining(frameBuffer) - extraBytes; + + parcEventBuffer_Append(buffer, parcBuffer_Overlay(frameBuffer, 0), parcBuffer_Remaining(frameBuffer)); + + _linuxEthernet_TrimBuffer(data->ether, buffer); + + assertTrue(parcEventBuffer_GetLength(buffer) == expectedSize, + "Buffer incorrect size got %zu expected %zu", + parcEventBuffer_GetLength(buffer), expectedSize); + + parcBuffer_Release(&frameBuffer); + parcEventBuffer_Destroy(&buffer); +} + +LONGBOW_TEST_CASE(Local, _linuxEthernet_TrimBuffer_Length_OK) +{ + trimBufferTest(testCase, 0); +} + +LONGBOW_TEST_CASE(Local, _linuxEthernet_TrimBuffer_Length_Trim) +{ + trimBufferTest(testCase, 4); +} + + +// ================================================================== + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(linux_Ethernet); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/platforms/linux/test/test_metis_System.c b/metis/ccnx/forwarder/metis/platforms/linux/test/test_metis_System.c new file mode 100644 index 00000000..d65d2e2f --- /dev/null +++ b/metis/ccnx/forwarder/metis/platforms/linux/test/test_metis_System.c @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis_System.c" + +#include <LongBow/unit-test.h> +#include <parc/algol/parc_SafeMemory.h> +#include <parc/algol/parc_Memory.h> + +// Include the generic tests of MetisSystem +#include "../../test/testrig_metis_System.c" + +LONGBOW_TEST_RUNNER(linux_Interface) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); + + // these are defined in testrig_metis_System.c + LONGBOW_RUN_TEST_FIXTURE(PublicApi); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(linux_Interface) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(linux_Interface) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// ================================================================== + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, metisSystem_Interfaces); + LONGBOW_RUN_TEST_CASE(Global, metisSystem_InterfaceMTU); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, metisSystem_Interfaces) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + CPIInterfaceSet *set = metisSystem_Interfaces(metis); + assertNotNull(set, "metisSystem_Interfaces return null set"); + + // XXX we need some sort of validation test. e.g. open a socket, then ioctl to + // XXX get the interface name, then verify its in the list. + + size_t length = cpiInterfaceSet_Length(set); + assertTrue(length > 0, "metisSystem_Interfaces returned no interfaces"); + + for (size_t i = 0; i < length; i++) { + CPIInterface *iface = cpiInterfaceSet_GetByOrdinalIndex(set, i); + printf("Interface Index %u\n", cpiInterface_GetInterfaceIndex(iface)); + const CPIAddressList *list = cpiInterface_GetAddresses(iface); + PARCJSONArray *json = cpiAddressList_ToJson(list); + char *str = parcJSONArray_ToString(json); + printf("%s\n", str); + parcMemory_Deallocate((void **) &str); + parcJSONArray_Release(&json); + } + + cpiInterfaceSet_Destroy(&set); + metisForwarder_Destroy(&metis); +} + +// returns a strdup() of the interface name, use free(3) +static char * +_pickInterfaceName(MetisForwarder *metis) +{ + char *ifname = NULL; + + CPIInterfaceSet *set = metisSystem_Interfaces(metis); + size_t length = cpiInterfaceSet_Length(set); + assertTrue(length > 0, "metisSystem_Interfaces returned no interfaces"); + + for (size_t i = 0; i < length; i++) { + CPIInterface *iface = cpiInterfaceSet_GetByOrdinalIndex(set, i); + const CPIAddressList *addressList = cpiInterface_GetAddresses(iface); + + size_t length = cpiAddressList_Length(addressList); + for (size_t i = 0; i < length && !ifname; i++) { + const CPIAddress *a = cpiAddressList_GetItem(addressList, i); + if (cpiAddress_GetType(a) == cpiAddressType_LINK) { + ifname = strdup(cpiInterface_GetName(iface)); + } + } + } + + cpiInterfaceSet_Destroy(&set); + return ifname; +} + +LONGBOW_TEST_CASE(Global, metisSystem_InterfaceMTU) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + + char *deviceName = _pickInterfaceName(metis); + unsigned mtu = metisSystem_InterfaceMtu(metis, deviceName); + + assertTrue(mtu > 0, "Did not get mtu for interface %s", deviceName); + free(deviceName); + metisForwarder_Destroy(&metis); +} + +// ================================================================== + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(linux_Interface); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} diff --git a/metis/ccnx/forwarder/metis/platforms/test/testrig_metis_System.c b/metis/ccnx/forwarder/metis/platforms/test/testrig_metis_System.c new file mode 100644 index 00000000..6302303c --- /dev/null +++ b/metis/ccnx/forwarder/metis/platforms/test/testrig_metis_System.c @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Tests the public API for the SystemInterfaces calls. + * + */ + +// ================================================================== + +LONGBOW_TEST_FIXTURE(PublicApi) +{ + LONGBOW_RUN_TEST_CASE(PublicApi, metisSystem_Interfaces); + LONGBOW_RUN_TEST_CASE(PublicApi, metisSystem_GetMacAddressByName); +} + +LONGBOW_TEST_FIXTURE_SETUP(PublicApi) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(PublicApi) +{ + uint32_t outstandingAllocations = parcSafeMemory_ReportAllocation(STDERR_FILENO); + if (outstandingAllocations != 0) { + printf("%s leaks memory by %d allocations\n", longBowTestCase_GetName(testCase), outstandingAllocations); + return LONGBOW_STATUS_MEMORYLEAK; + } + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(PublicApi, metisSystem_Interfaces) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + CPIInterfaceSet *set = metisSystem_Interfaces(metis); + assertNotNull(set, "metisSystem_Interfaces return null set"); + + size_t length = cpiInterfaceSet_Length(set); + assertTrue(length > 0, "metisSystem_Interfaces returned no interfaces"); + + for (size_t i = 0; i < length; i++) { + CPIInterface *iface = cpiInterfaceSet_GetByOrdinalIndex(set, i); + printf("Interface Index %u\n", cpiInterface_GetInterfaceIndex(iface)); + const CPIAddressList *list = cpiInterface_GetAddresses(iface); + PARCJSONArray *jsonArray = cpiAddressList_ToJson(list); + char *str = parcJSONArray_ToString(jsonArray); + printf("%s\n", str); + parcMemory_Deallocate((void **) &str); + parcJSONArray_Release(&jsonArray); + } + + cpiInterfaceSet_Destroy(&set); + metisForwarder_Destroy(&metis); +} + +// returns a strdup() of the interface name, use free(3) +char * +pickInterfaceName(MetisForwarder *metis) +{ + char *ifname = NULL; + + CPIInterfaceSet *set = metisSystem_Interfaces(metis); + size_t length = cpiInterfaceSet_Length(set); + assertTrue(length > 0, "metisSystem_Interfaces returned no interfaces"); + + for (size_t i = 0; i < length; i++) { + CPIInterface *iface = cpiInterfaceSet_GetByOrdinalIndex(set, i); + const CPIAddressList *addressList = cpiInterface_GetAddresses(iface); + + size_t length = cpiAddressList_Length(addressList); + for (size_t i = 0; i < length && !ifname; i++) { + const CPIAddress *a = cpiAddressList_GetItem(addressList, i); + if (cpiAddress_GetType(a) == cpiAddressType_LINK) { + ifname = strdup(cpiInterface_GetName(iface)); + } + } + } + + cpiInterfaceSet_Destroy(&set); + return ifname; +} + +LONGBOW_TEST_CASE(PublicApi, metisSystem_GetMacAddressByName) +{ + MetisForwarder *metis = metisForwarder_Create(NULL); + char *ifname = pickInterfaceName(metis); + + CPIAddress *a = metisSystem_GetMacAddressByName(metis, ifname); + assertNotNull(a, "Got null mac address for interface %s", ifname); + cpiAddress_Destroy(&a); + metisForwarder_Destroy(&metis); + free(ifname); +} + |