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('''