aboutsummaryrefslogtreecommitdiffstats
path: root/metis/ccnx/forwarder/metis/platforms
diff options
context:
space:
mode:
Diffstat (limited to 'metis/ccnx/forwarder/metis/platforms')
-rw-r--r--metis/ccnx/forwarder/metis/platforms/README.txt20
-rw-r--r--metis/ccnx/forwarder/metis/platforms/android/ifaddrs.c600
-rw-r--r--metis/ccnx/forwarder/metis/platforms/android/ifaddrs.h54
-rw-r--r--metis/ccnx/forwarder/metis/platforms/android/metis_GenericEther.c368
-rw-r--r--metis/ccnx/forwarder/metis/platforms/android/metis_System.c193
-rw-r--r--metis/ccnx/forwarder/metis/platforms/darwin/metis_GenericEther.c618
-rw-r--r--metis/ccnx/forwarder/metis/platforms/darwin/metis_System.c149
-rw-r--r--metis/ccnx/forwarder/metis/platforms/darwin/test/.gitignore2
-rw-r--r--metis/ccnx/forwarder/metis/platforms/darwin/test/test_metis_GenericEther.c606
-rw-r--r--metis/ccnx/forwarder/metis/platforms/darwin/test/test_metis_System.c149
-rw-r--r--metis/ccnx/forwarder/metis/platforms/linux/metis_GenericEther.c374
-rw-r--r--metis/ccnx/forwarder/metis/platforms/linux/metis_System.c190
-rw-r--r--metis/ccnx/forwarder/metis/platforms/linux/test/.gitignore2
-rw-r--r--metis/ccnx/forwarder/metis/platforms/linux/test/test_metis_GenericEther.c566
-rw-r--r--metis/ccnx/forwarder/metis/platforms/linux/test/test_metis_System.c148
-rw-r--r--metis/ccnx/forwarder/metis/platforms/test/testrig_metis_System.c106
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(&ether->macAddress);
+ }
+
+ metisLogger_Release(&ether->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(&ether);
+ }
+ } 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(&ether->macAddress);
+ }
+
+ metisLogger_Release(&ether->logger);
+ parcEventBuffer_Destroy(&ether->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, &ether->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(&ether);
+ }
+
+ 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(&ether);
+}
+
+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(&ether);
+ 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(&ether);
+}
+
+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(&ether);
+}
+
+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(&ether);
+}
+
+
+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(&ether);
+ 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(&ether->macAddress);
+ }
+
+ metisLogger_Release(&ether->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(&ether);
+ }
+ } 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(&ether);
+}
+
+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(&ether);
+ 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(&ether);
+}
+
+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(&ether);
+}
+
+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(&ether);
+}
+
+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(&ether);
+
+ 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(&ether);
+ }
+}
+
+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);
+}
+