Support TAP interfaces, with ARP/ND.

This commit is contained in:
Josh Bailey 2021-05-25 10:20:01 +00:00
parent 64508c2e5c
commit caa73e1f5e
9 changed files with 305 additions and 11 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -15,7 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
project('open5gs', 'c',
project('open5gs', 'c', 'cpp',
version : '2.2.9',
license : 'AGPL-3.0-or-later',
meson_version : '>= 0.43.0',

96
src/upf/arp-nd.cpp Normal file
View File

@ -0,0 +1,96 @@
/*
* Copyright (C) 2019 by Sukchan Lee <acetcom@gmail.com>
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <tins/arp.h>
#include <tins/ethernetII.h>
#include <tins/hw_address.h>
#include <tins/icmpv6.h>
#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<ARP>();
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<ETHER_ADDR_LEN> source_mac(mac);
const ARP& arp = pdu.rfind_pdu<ARP>();
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<ICMPv6>();
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<ETHER_ADDR_LEN> source_mac(mac);
const ICMPv6& icmp6 = pdu.rfind_pdu<ICMPv6>();
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;
}

49
src/upf/arp-nd.h Normal file
View File

@ -0,0 +1,49 @@
/*
* Copyright (C) 2019 by Sukchan Lee <acetcom@gmail.com>
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include "upf-config.h"
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_NET_ETHERNET_H
#include <net/ethernet.h>
#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

View File

@ -35,6 +35,19 @@
#include <netinet/icmp6.h>
#endif
#if HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#if HAVE_NET_IF_DL_H
#include <net/if_dl.h>
#endif
#if HAVE_IFADDRS_H
#include <ifaddrs.h>
#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, &eth_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)
}
}
}
}
}

View File

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