diff --git a/docs/_docs/platform/05-macosx-apple-silicon.md b/docs/_docs/platform/05-macosx-apple-silicon.md
index 389419ec7..dd95989c6 100644
--- a/docs/_docs/platform/05-macosx-apple-silicon.md
+++ b/docs/_docs/platform/05-macosx-apple-silicon.md
@@ -104,6 +104,10 @@ Install Meson using Homebrew.
$ brew install meson
```
+Install libtins.
+
+Follow the instructions at https://libtins.github.io to install libtins.
+
Git clone.
```bash
diff --git a/docs/_docs/platform/06-macosx-intel.md b/docs/_docs/platform/06-macosx-intel.md
index 45d924bdf..e1b48df8b 100644
--- a/docs/_docs/platform/06-macosx-intel.md
+++ b/docs/_docs/platform/06-macosx-intel.md
@@ -103,6 +103,10 @@ Install Meson using Homebrew.
$ brew install meson
```
+Install libtins.
+
+Follow the instructions at https://libtins.github.io to install libtins.
+
Git clone.
```bash
diff --git a/docs/_docs/platform/07-freebsd.md b/docs/_docs/platform/07-freebsd.md
index 84d6defb9..630827d67 100644
--- a/docs/_docs/platform/07-freebsd.md
+++ b/docs/_docs/platform/07-freebsd.md
@@ -121,6 +121,10 @@ $ export LIBRARY_PATH=/usr/local/lib
$ export C_INCLUDE_PATH=/usr/local/include
```
+Install libtins.
+
+Follow the instructions at https://libtins.github.io to install libtins.
+
Git clone.
```bash
diff --git a/lib/pfcp/context.h b/lib/pfcp/context.h
index 8d27652f6..b00d95ec8 100644
--- a/lib/pfcp/context.h
+++ b/lib/pfcp/context.h
@@ -289,6 +289,8 @@ typedef struct ogs_pfcp_dev_s {
ogs_sockaddr_t *link_local_addr;
ogs_poll_t *poll;
+ bool is_tap;
+ uint8_t mac_addr[6];
} ogs_pfcp_dev_t;
typedef struct ogs_pfcp_subnet_s {
diff --git a/meson.build b/meson.build
index f9c7c11d3..8927b2055 100644
--- a/meson.build
+++ b/meson.build
@@ -15,7 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-project('open5gs', 'c',
+project('open5gs', 'c', 'cpp',
version : '2.2.9',
license : 'AGPL-3.0-or-later',
meson_version : '>= 0.43.0',
diff --git a/src/upf/arp-nd.cpp b/src/upf/arp-nd.cpp
new file mode 100644
index 000000000..2b72e75eb
--- /dev/null
+++ b/src/upf/arp-nd.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2019 by Sukchan Lee
+ *
+ * This file is part of Open5GS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include
+#include
+#include
+#include
+
+#include "arp-nd.h"
+
+using namespace::Tins;
+
+
+void _serialize_reply(uint8_t *reply_data, EthernetII &reply) {
+ PDU::serialization_type serialized = reply.serialize();
+ memcpy(reply_data, serialized.data(), reply.size());
+}
+
+bool _parse_arp(EthernetII &pdu) {
+ if (pdu.payload_type() == ETHERTYPE_ARP) {
+ const ARP& arp = pdu.rfind_pdu();
+ return arp.opcode() == ARP::REQUEST && pdu.dst_addr().is_broadcast();
+ }
+ return false;
+}
+
+bool is_arp_req(uint8_t *data, uint len)
+{
+ EthernetII pdu(data, len);
+ return _parse_arp(pdu);
+}
+
+bool arp_reply(uint8_t *reply_data, uint8_t *request_data, uint len, const uint8_t *mac) {
+ EthernetII pdu(request_data, len);
+ if (_parse_arp(pdu)) {
+ HWAddress source_mac(mac);
+ const ARP& arp = pdu.rfind_pdu();
+ EthernetII reply = ARP::make_arp_reply(
+ arp.sender_ip_addr(),
+ arp.target_ip_addr(),
+ arp.sender_hw_addr(),
+ source_mac);
+ _serialize_reply(reply_data, reply);
+ return true;
+ }
+ return false;
+}
+
+bool _parse_nd(EthernetII &pdu) {
+ if (pdu.payload_type() == ETHERTYPE_IPV6) {
+ const ICMPv6& icmp6 = pdu.rfind_pdu();
+ return icmp6.type() == ICMPv6::NEIGHBOUR_SOLICIT;
+ }
+ return false;
+}
+
+bool is_nd_req(uint8_t *data, uint len)
+{
+ if (len < MAX_ND_SIZE) {
+ EthernetII pdu(data, len);
+ return _parse_nd(pdu);
+ }
+ return false;
+}
+
+bool nd_reply(uint8_t *reply_data, uint8_t *request_data, uint len, const uint8_t *mac) {
+ EthernetII pdu(request_data, len);
+ if (_parse_nd(pdu)) {
+ HWAddress source_mac(mac);
+ const ICMPv6& icmp6 = pdu.rfind_pdu();
+ EthernetII reply(pdu.src_addr(), pdu.dst_addr());
+ ICMPv6 nd_reply(ICMPv6::NEIGHBOUR_ADVERT);
+ nd_reply.target_link_layer_addr(source_mac);
+ nd_reply.target_addr(icmp6.target_addr());
+ reply /= nd_reply;
+ _serialize_reply(reply_data, reply);
+ return true;
+ }
+ return false;
+}
diff --git a/src/upf/arp-nd.h b/src/upf/arp-nd.h
new file mode 100644
index 000000000..cca57130b
--- /dev/null
+++ b/src/upf/arp-nd.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2019 by Sukchan Lee
+ *
+ * This file is part of Open5GS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "upf-config.h"
+
+#ifdef HAVE_SYS_SOCKET_H
+#include
+#endif
+
+#ifdef HAVE_NETINET_IN_H
+#include
+#endif
+
+#ifdef HAVE_NET_ETHERNET_H
+#include
+#endif
+
+#define MAX_ND_SIZE 128
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void set_source_mac(uint8_t *data);
+bool is_arp_req(uint8_t *data, uint len);
+bool arp_reply(uint8_t *reply_data, uint8_t *request_data, uint len, const uint8_t *mac);
+bool is_nd_req(uint8_t *data, uint len);
+bool nd_reply(uint8_t *reply_data, uint8_t *request_data, uint len, const uint8_t *mac);
+
+#ifdef __cplusplus
+}
+#endif
+
diff --git a/src/upf/gtp-path.c b/src/upf/gtp-path.c
index b717f6df1..c00acf7a1 100644
--- a/src/upf/gtp-path.c
+++ b/src/upf/gtp-path.c
@@ -35,6 +35,19 @@
#include
#endif
+#if HAVE_SYS_IOCTL_H
+#include
+#endif
+
+#if HAVE_NET_IF_DL_H
+#include
+#endif
+
+#if HAVE_IFADDRS_H
+#include
+#endif
+
+#include "arp-nd.h"
#include "event.h"
#include "gtp-path.h"
#include "pfcp-path.h"
@@ -42,11 +55,21 @@
#define UPF_GTP_HANDLED 1
+const uint8_t proxy_mac_addr[] = { 0x0e, 0x00, 0x00, 0x00, 0x00, 0x01 };
+
static ogs_pkbuf_pool_t *packet_pool = NULL;
static void upf_gtp_handle_multicast(ogs_pkbuf_t *recvbuf);
-static void _gtpv1_tun_recv_cb(short when, ogs_socket_t fd, void *data)
+static uint16_t _get_eth_type(uint8_t *data, uint len) {
+ if (len > ETHER_HDR_LEN) {
+ struct ether_header *hdr = (struct ether_header*)data;
+ return htobe16(hdr->ether_type);
+ }
+ return 0;
+}
+
+static void _gtpv1_tun_recv_common_cb(short when, ogs_socket_t fd, bool has_eth, void *data)
{
ogs_pkbuf_t *recvbuf = NULL;
@@ -62,6 +85,42 @@ static void _gtpv1_tun_recv_cb(short when, ogs_socket_t fd, void *data)
return;
}
+ if (has_eth) {
+ ogs_pkbuf_t *replybuf = NULL;
+ uint16_t eth_type = _get_eth_type(recvbuf->data, recvbuf->len);
+
+ if (eth_type == ETHERTYPE_ARP) {
+ if (is_arp_req(recvbuf->data, recvbuf->len)) {
+ replybuf = ogs_pkbuf_alloc(packet_pool, OGS_MAX_PKT_LEN);
+ ogs_assert(replybuf);
+ ogs_pkbuf_reserve(replybuf, OGS_TUN_MAX_HEADROOM);
+ ogs_pkbuf_put(replybuf, OGS_MAX_PKT_LEN-OGS_TUN_MAX_HEADROOM);
+ arp_reply(replybuf->data, recvbuf->data, recvbuf->len, proxy_mac_addr);
+ ogs_debug("[SEND] reply to ARP request");
+ } else {
+ goto cleanup;
+ }
+ } else if (eth_type == ETHERTYPE_IPV6 && is_nd_req(recvbuf->data, recvbuf->len)) {
+ replybuf = ogs_pkbuf_alloc(packet_pool, OGS_MAX_PKT_LEN);
+ ogs_assert(replybuf);
+ ogs_pkbuf_reserve(replybuf, OGS_TUN_MAX_HEADROOM);
+ ogs_pkbuf_put(replybuf, OGS_MAX_PKT_LEN-OGS_TUN_MAX_HEADROOM);
+ nd_reply(replybuf->data, recvbuf->data, recvbuf->len, proxy_mac_addr);
+ ogs_debug("[SEND] reply to ND solicit");
+ }
+ if (replybuf) {
+ if (ogs_tun_write(fd, replybuf) != OGS_OK)
+ ogs_warn("ogs_tun_write() for reply failed");
+ goto cleanup;
+ }
+ if (eth_type != ETHERTYPE_IP && eth_type != ETHERTYPE_IPV6) {
+ ogs_error("[DROP] Invalid eth_type [%x]]", eth_type);
+ ogs_log_hexdump(OGS_LOG_ERROR, recvbuf->data, recvbuf->len);
+ goto cleanup;
+ }
+ ogs_pkbuf_pull(recvbuf, ETHER_HDR_LEN);
+ }
+
sess = upf_sess_find_by_ue_ip_address(recvbuf);
if (!sess)
goto cleanup;
@@ -127,6 +186,14 @@ cleanup:
ogs_pkbuf_free(recvbuf);
}
+static void _gtpv1_tun_recv_cb(short when, ogs_socket_t fd, void *data) {
+ _gtpv1_tun_recv_common_cb(when, fd, false, data);
+}
+
+static void _gtpv1_tun_recv_eth_cb(short when, ogs_socket_t fd, void *data) {
+ _gtpv1_tun_recv_common_cb(when, fd, true, data);
+}
+
static void _gtpv1_u_recv_cb(short when, ogs_socket_t fd, void *data)
{
int len;
@@ -326,10 +393,15 @@ static void _gtpv1_u_recv_cb(short when, ogs_socket_t fd, void *data)
ogs_assert(far);
if (far->dst_if == OGS_PFCP_INTERFACE_CORE) {
- if (ip_h->ip_v == 4 && sess->ipv4)
+ uint16_t eth_type = 0;
+
+ if (ip_h->ip_v == 4 && sess->ipv4) {
subnet = sess->ipv4->subnet;
- else if (ip_h->ip_v == 6 && sess->ipv6)
+ eth_type = ETHERTYPE_IP;
+ } else if (ip_h->ip_v == 6 && sess->ipv6) {
subnet = sess->ipv6->subnet;
+ eth_type = ETHERTYPE_IPV6;
+ }
if (!subnet) {
#if 0 /* It's redundant log message */
@@ -342,6 +414,19 @@ static void _gtpv1_u_recv_cb(short when, ogs_socket_t fd, void *data)
dev = subnet->dev;
ogs_assert(dev);
+
+ if (dev->is_tap) {
+ ogs_assert(eth_type);
+ eth_type = htobe16(eth_type);
+ ogs_pkbuf_push(pkbuf, sizeof(eth_type));
+ memcpy(pkbuf->data, ð_type, sizeof(eth_type));
+ ogs_pkbuf_push(pkbuf, ETHER_ADDR_LEN);
+ memcpy(pkbuf->data, proxy_mac_addr, ETHER_ADDR_LEN);
+ ogs_pkbuf_push(pkbuf, ETHER_ADDR_LEN);
+ memcpy(pkbuf->data, dev->mac_addr, ETHER_ADDR_LEN);
+ }
+
+ /* TODO: if destined to another UE, hairpin back out. */
if (ogs_tun_write(dev->fd, pkbuf) != OGS_OK)
ogs_warn("ogs_tun_write() failed");
@@ -389,7 +474,6 @@ cleanup:
ogs_pkbuf_free(pkbuf);
}
-
int upf_gtp_init(void)
{
ogs_pkbuf_config_t config;
@@ -407,6 +491,32 @@ void upf_gtp_final(void)
ogs_pkbuf_pool_destroy(packet_pool);
}
+static void _get_dev_mac_addr(char *ifname, uint8_t *mac_addr)
+{
+#ifdef SIOCGIFHWADDR
+ int fd = socket(PF_INET, SOCK_DGRAM, 0);
+ ogs_assert(fd);
+ struct ifreq req;
+ memset(&req, 0, sizeof(req));
+ strncpy(req.ifr_name, ifname, IF_NAMESIZE-1);
+ ogs_assert(ioctl(fd, SIOCGIFHWADDR, &req) == 0);
+ memcpy(mac_addr, req.ifr_hwaddr.sa_data, ETHER_ADDR_LEN);
+#else
+ struct ifaddrs *ifap;
+ ogs_assert(getifaddrs(&ifap) == 0);
+ struct ifaddrs *p;
+ for (p = ifap; p; p = p->ifa_next) {
+ if (strncmp(ifname, p->ifa_name, IF_NAMESIZE-1) == 0) {
+ struct sockaddr_dl* sdp = (struct sockaddr_dl*) p->ifa_addr;
+ memcpy(mac_addr, sdp->sdl_data + sdp->sdl_nlen, ETHER_ADDR_LEN);
+ freeifaddrs(ifap);
+ return;
+ }
+ }
+ ogs_assert(0); /* interface not found. */
+#endif
+}
+
int upf_gtp_open(void)
{
ogs_pfcp_dev_t *dev = NULL;
@@ -444,14 +554,22 @@ int upf_gtp_open(void)
/* Open Tun interface */
ogs_list_for_each(&ogs_pfcp_self()->dev_list, dev) {
- dev->fd = ogs_tun_open(dev->ifname, OGS_MAX_IFNAME_LEN, 0);
+ dev->is_tap = strstr(dev->ifname, "tap");
+ dev->fd = ogs_tun_open(dev->ifname, OGS_MAX_IFNAME_LEN, dev->is_tap);
if (dev->fd == INVALID_SOCKET) {
ogs_error("tun_open(dev:%s) failed", dev->ifname);
return OGS_ERROR;
}
- dev->poll = ogs_pollset_add(ogs_app()->pollset,
- OGS_POLLIN, dev->fd, _gtpv1_tun_recv_cb, NULL);
+ if (dev->is_tap) {
+ _get_dev_mac_addr(dev->ifname, dev->mac_addr);
+ dev->poll = ogs_pollset_add(ogs_app()->pollset,
+ OGS_POLLIN, dev->fd, _gtpv1_tun_recv_eth_cb, NULL);
+ } else {
+ dev->poll = ogs_pollset_add(ogs_app()->pollset,
+ OGS_POLLIN, dev->fd, _gtpv1_tun_recv_cb, NULL);
+ }
+
ogs_assert(dev->poll);
}
@@ -533,4 +651,4 @@ static void upf_gtp_handle_multicast(ogs_pkbuf_t *recvbuf)
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/upf/meson.build b/src/upf/meson.build
index a8ddfe1f2..6d720c702 100644
--- a/src/upf/meson.build
+++ b/src/upf/meson.build
@@ -18,11 +18,17 @@
upf_conf = configuration_data()
upf_headers = ('''
+ ifaddrs.h
+ net/ethernet.h
net/if.h
+ net/if_dl.h
+ netinet/in.h
netinet/ip.h
netinet/ip6.h
netinet/ip_icmp.h
netinet/icmp6.h
+ sys/ioctl.h
+ sys/socket.h
'''.split())
foreach h : upf_headers
@@ -60,19 +66,30 @@ libupf_sources = files('''
pfcp-path.c
n4-build.c
n4-handler.c
+ arp-nd.h
'''.split())
+tins_dep = cc.find_library('tins', required : true)
+libarp_nd = static_library('arp_nd',
+ sources : files('''
+ arp-nd.cpp
+ arp-nd.h
+ '''.split()),
+ dependencies : tins_dep,
+ install : false)
+libarp_nd_dep = declare_dependency(link_with : libarp_nd, dependencies : tins_dep)
+
libupf = static_library('upf',
sources : libupf_sources,
dependencies : [
- libapp_dep, libdiameter_gx_dep, libgtp_dep, libpfcp_dep, libtun_dep
+ libapp_dep, libdiameter_gx_dep, libgtp_dep, libpfcp_dep, libtun_dep, libarp_nd_dep,
],
install : false)
libupf_dep = declare_dependency(
link_with : libupf,
dependencies : [
- libapp_dep, libdiameter_gx_dep, libgtp_dep, libpfcp_dep, libtun_dep
+ libapp_dep, libdiameter_gx_dep, libgtp_dep, libpfcp_dep, libtun_dep, libarp_nd_dep,
])
upf_sources = files('''