Compare commits
9 Commits
8cc255135e
...
aebe198913
Author | SHA1 | Date |
---|---|---|
Neels Hofmeyr | aebe198913 | |
Neels Hofmeyr | ab401260ef | |
Neels Hofmeyr | 97ba7b152e | |
Neels Hofmeyr | 8dcaf21c68 | |
Neels Hofmeyr | d7d4ca7b83 | |
Max | 7449635520 | |
Pau Espin | 8fd95f3e74 | |
Harald Welte | cd58308915 | |
Harald Welte | e3cc5ddf1d |
12
configure.ac
12
configure.ac
|
@ -49,7 +49,7 @@ AC_SEARCH_LIBS([sctp_recvmsg], [sctp], [
|
|||
LIBS=$old_LIBS
|
||||
|
||||
PKG_CHECK_MODULES(LIBASN1C, libasn1c >= 0.9.30)
|
||||
PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.9.0)
|
||||
PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore > 1.9.0)
|
||||
PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.9.0)
|
||||
PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 1.9.0)
|
||||
PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 1.9.0)
|
||||
|
@ -70,6 +70,16 @@ fi
|
|||
AM_CONDITIONAL(ENABLE_PFCP, test "x$osmo_ac_pfcp" = "xyes")
|
||||
AC_SUBST(osmo_ac_pfcp)
|
||||
|
||||
# Enable libnftables support for traffic counters using nft
|
||||
AC_ARG_ENABLE([nftables], [AS_HELP_STRING([--enable-nftables], [Build with libnftables support, for traffic counters using nft])],
|
||||
[osmo_ac_nftables="$enableval"],[osmo_ac_nftables="no"])
|
||||
if test "x$osmo_ac_nftables" = "xyes" ; then
|
||||
PKG_CHECK_MODULES(LIBNFTABLES, libnftables >= 1.0.2)
|
||||
AC_DEFINE(ENABLE_NFTABLES, 1, [Define to build with libnftables support])
|
||||
fi
|
||||
AM_CONDITIONAL(ENABLE_NFTABLES, test "x$osmo_ac_nftables" = "xyes")
|
||||
AC_SUBST(osmo_ac_nftables)
|
||||
|
||||
dnl checks for header files
|
||||
AC_HEADER_STDC
|
||||
|
||||
|
|
|
@ -4,9 +4,11 @@
|
|||
# environment variables:
|
||||
# * PFCP: configure PFCP support if set to "1" (default)
|
||||
# * WITH_MANUALS: build manual PDFs if set to "1"
|
||||
# * NFTABLES: configure nftables support if set to "1" (default)
|
||||
# * PUBLISH: upload manuals after building if set to "1" (ignored without WITH_MANUALS = "1")
|
||||
#
|
||||
PFCP=${PFCP:-1}
|
||||
NFTABLES=${NFTABLES:-1}
|
||||
|
||||
if ! [ -x "$(command -v osmo-build-dep.sh)" ]; then
|
||||
echo "Error: We need to have scripts/osmo-deps.sh from http://git.osmocom.org/osmo-ci/ in PATH !"
|
||||
|
@ -45,6 +47,9 @@ if [ "$PFCP" = "1" ]; then
|
|||
osmo-build-dep.sh libosmo-pfcp
|
||||
CONFIG="$CONFIG --enable-pfcp"
|
||||
fi
|
||||
if [ "$NFTABLES" = "1" ]; then
|
||||
CONFIG="$CONFIG --enable-nftables"
|
||||
fi
|
||||
if [ "$WITH_MANUALS" = "1" ]; then
|
||||
CONFIG="$CONFIG --enable-manuals"
|
||||
fi
|
||||
|
|
|
@ -68,19 +68,32 @@ make %{?_smp_mflags}
|
|||
%install
|
||||
%make_install
|
||||
|
||||
%if 0%{?suse_version}
|
||||
%preun
|
||||
%if 0%{?suse_version}
|
||||
%service_del_preun %{name}.service
|
||||
%endif
|
||||
|
||||
%postun
|
||||
%if 0%{?suse_version}
|
||||
%service_del_postun %{name}.service
|
||||
%endif
|
||||
|
||||
%pre
|
||||
getent group osmocom >/dev/null || groupadd --system osmocom
|
||||
getent passwd osmocom >/dev/null || useradd --system --gid osmocom --home-dir /var/lib/osmocom \
|
||||
--shell /sbin/nologin --comment "Open Source Mobile Communications" osmocom
|
||||
%if 0%{?suse_version}
|
||||
%service_add_pre %{name}.service
|
||||
%endif
|
||||
|
||||
%post
|
||||
%if 0%{?suse_version}
|
||||
%service_add_post %{name}.service
|
||||
%endif
|
||||
chown osmocom:osmocom /etc/osmocom/osmo-hnbgw.cfg
|
||||
chmod 0660 /etc/osmocom/osmo-hnbgw.cfg
|
||||
chown root:osmocom /etc/osmocom
|
||||
chmod 2775 /etc/osmocom
|
||||
|
||||
%check
|
||||
make %{?_smp_mflags} check || (find . -name testsuite.log -exec cat {} +)
|
||||
|
|
|
@ -9,6 +9,8 @@ Restart=always
|
|||
LimitNOFILE=65536
|
||||
StateDirectory=osmocom
|
||||
WorkingDirectory=%S/osmocom
|
||||
User=osmocom
|
||||
Group=osmocom
|
||||
ExecStart=/usr/bin/osmo-hnbgw -c /etc/osmocom/osmo-hnbgw.cfg
|
||||
RestartSec=2
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ Build-Depends: debhelper (>= 10),
|
|||
libosmo-ranap-dev (>= 1.5.0),
|
||||
libosmo-rua-dev (>= 1.5.0),
|
||||
libosmo-pfcp-dev (>= 0.3.0),
|
||||
libnftables-dev,
|
||||
osmo-gsm-manuals-dev (>= 1.5.0)
|
||||
Standards-Version: 3.9.8
|
||||
Vcs-Git: https://gitea.osmocom.org/cellular-infrastructure/osmo-hnbgw
|
||||
|
@ -31,7 +32,7 @@ Homepage: https://projects.osmocom.org/projects/osmo-hnbgw
|
|||
Package: osmo-hnbgw
|
||||
Architecture: any
|
||||
Multi-Arch: foreign
|
||||
Depends: ${misc:Depends}, ${shlibs:Depends}
|
||||
Depends: ${misc:Depends}, ${shlibs:Depends}, adduser
|
||||
Recommends: osmo-mgw
|
||||
Description: OsmoHNBGW: Osmocom Home Node B Gateway
|
||||
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
#!/bin/sh -e
|
||||
# Create 'osmocom' user and group (if it doesn't exist yet) and adjust permissions
|
||||
# of directories which are not automatically adjusted by systemd from previous (root-owned)
|
||||
# install.
|
||||
|
||||
# N. B: the user is intentionally NOT removed during package uninstall:
|
||||
# see https://wiki.debian.org/AccountHandlingInMaintainerScripts for reasoning.
|
||||
chperms() {
|
||||
# chperms <user> <group> <perms> <file>
|
||||
if ! OVERRIDE=`dpkg-statoverride --list $4 2>&1`; then
|
||||
if [ -e $4 ]; then
|
||||
chown $1:$2 $4
|
||||
chmod $3 $4
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
configure)
|
||||
if ! getent passwd osmocom > /dev/null; then
|
||||
adduser --quiet \
|
||||
--system \
|
||||
--group \
|
||||
--no-create-home \
|
||||
--disabled-password \
|
||||
--home /var/lib/osmocom \
|
||||
--gecos "Open Source Mobile Communications" \
|
||||
osmocom
|
||||
fi
|
||||
# Set permissions according to https://www.debian.org/doc/debian-policy/ch-files.html#s-permissions-owners
|
||||
chperms osmocom osmocom 0660 /etc/osmocom/osmo-hnbgw.cfg
|
||||
chperms root osmocom 2775 /etc/osmocom
|
||||
|
||||
;;
|
||||
esac
|
||||
|
||||
# dh_installdeb(1) will replace this with shell code automatically
|
||||
# generated by other debhelper scripts.
|
||||
#DEBHELPER#
|
|
@ -44,11 +44,18 @@
|
|||
%:
|
||||
dh $@ --with autoreconf
|
||||
|
||||
# debmake generated override targets
|
||||
CONFIGURE_FLAGS += --with-systemdsystemunitdir=/lib/systemd/system --enable-manuals
|
||||
CONFIGURE_FLAGS += --enable-pfcp
|
||||
# libnftables is too old in Debian 10 (OS#6425)
|
||||
override_dh_auto_configure:
|
||||
dh_auto_configure -- $(CONFIGURE_FLAGS)
|
||||
CONFIGURE_FLAGS=" \
|
||||
--enable-manuals \
|
||||
--enable-pfcp \
|
||||
--with-systemdsystemunitdir=/lib/systemd/system \
|
||||
"; \
|
||||
if pkg-config --exists libnftables --atleast-version=1.0.2; then \
|
||||
CONFIGURE_FLAGS="$$CONFIGURE_FLAGS --enable-nftables"; \
|
||||
fi; \
|
||||
dh_auto_configure -- $$CONFIGURE_FLAGS
|
||||
|
||||
#
|
||||
# Do not install libtool archive, python .pyc .pyo
|
||||
#override_dh_install:
|
||||
|
|
|
@ -3,6 +3,7 @@ noinst_HEADERS = \
|
|||
context_map.h hnbgw.h hnbgw_cn.h \
|
||||
hnbgw_hnbap.h hnbgw_rua.h hnbgw_ranap.h \
|
||||
kpi.h \
|
||||
nft_kpi.h \
|
||||
ranap_rab_ass.h mgw_fsm.h tdefs.h \
|
||||
hnbgw_pfcp.h \
|
||||
ps_rab_ass_fsm.h \
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <osmocom/core/write_queue.h>
|
||||
#include <osmocom/core/timer.h>
|
||||
#include <osmocom/core/rate_ctr.h>
|
||||
#include <osmocom/core/sockaddr_str.h>
|
||||
#include <osmocom/gsm/gsm23003.h>
|
||||
#include <osmocom/sigtran/sccp_sap.h>
|
||||
#include <osmocom/sigtran/osmo_ss7.h>
|
||||
|
@ -18,6 +19,8 @@
|
|||
#include <osmocom/mgcp_client/mgcp_client.h>
|
||||
#include <osmocom/mgcp_client/mgcp_client_pool.h>
|
||||
|
||||
#include <osmocom/hnbgw/nft_kpi.h>
|
||||
|
||||
#define STORE_UPTIME_INTERVAL 10 /* seconds */
|
||||
#define HNB_STORE_RAB_DURATIONS_INTERVAL 1 /* seconds */
|
||||
|
||||
|
@ -29,6 +32,7 @@ enum {
|
|||
DMGW,
|
||||
DHNB,
|
||||
DCN,
|
||||
DNFT,
|
||||
};
|
||||
|
||||
extern const struct log_info hnbgw_log_info;
|
||||
|
@ -100,6 +104,8 @@ enum hnb_rate_ctr {
|
|||
|
||||
HNB_CTR_RANAP_PS_RAB_REL_REQ,
|
||||
HNB_CTR_RANAP_CS_RAB_REL_REQ,
|
||||
HNB_CTR_RANAP_PS_RAB_REL_REQ_ABNORMAL,
|
||||
HNB_CTR_RANAP_CS_RAB_REL_REQ_ABNORMAL,
|
||||
|
||||
HNB_CTR_RANAP_PS_RAB_REL_CNF,
|
||||
HNB_CTR_RANAP_CS_RAB_REL_CNF,
|
||||
|
@ -109,6 +115,8 @@ enum hnb_rate_ctr {
|
|||
|
||||
HNB_CTR_RANAP_PS_RAB_REL_IMPLICIT,
|
||||
HNB_CTR_RANAP_CS_RAB_REL_IMPLICIT,
|
||||
HNB_CTR_RANAP_PS_RAB_REL_IMPLICIT_ABNORMAL,
|
||||
HNB_CTR_RANAP_CS_RAB_REL_IMPLICIT_ABNORMAL,
|
||||
|
||||
HNB_CTR_RUA_ERR_IND,
|
||||
|
||||
|
@ -132,6 +140,13 @@ enum hnb_rate_ctr {
|
|||
HNB_CTR_CS_PAGING_ATTEMPTED,
|
||||
|
||||
HNB_CTR_RAB_ACTIVE_MILLISECONDS_TOTAL,
|
||||
|
||||
HNB_CTR_GTPU_PACKETS_UL,
|
||||
HNB_CTR_GTPU_TOTAL_BYTES_UL,
|
||||
HNB_CTR_GTPU_UE_BYTES_UL,
|
||||
HNB_CTR_GTPU_PACKETS_DL,
|
||||
HNB_CTR_GTPU_TOTAL_BYTES_DL,
|
||||
HNB_CTR_GTPU_UE_BYTES_DL,
|
||||
};
|
||||
|
||||
enum hnb_stat {
|
||||
|
@ -353,6 +368,14 @@ struct hnb_persistent {
|
|||
|
||||
struct rate_ctr_group *ctrs;
|
||||
struct osmo_stat_item_group *statg;
|
||||
|
||||
struct {
|
||||
struct osmo_sockaddr_str addr_remote;
|
||||
struct {
|
||||
struct nft_kpi_val ul;
|
||||
struct nft_kpi_val dl;
|
||||
} last;
|
||||
} nft_kpi;
|
||||
};
|
||||
|
||||
struct ue_context {
|
||||
|
@ -391,6 +414,10 @@ struct hnbgw {
|
|||
char *core;
|
||||
} netinst;
|
||||
} pfcp;
|
||||
struct {
|
||||
bool enable;
|
||||
char *table_name;
|
||||
} nft_kpi;
|
||||
} config;
|
||||
/*! SCTP listen socket for incoming connections */
|
||||
struct osmo_stream_srv_link *iuh;
|
||||
|
@ -451,6 +478,8 @@ void hnb_context_release_ue_state(struct hnb_context *ctx);
|
|||
|
||||
struct hnb_persistent *hnb_persistent_alloc(const struct umts_cell_id *id);
|
||||
struct hnb_persistent *hnb_persistent_find_by_id(const struct umts_cell_id *id);
|
||||
struct hnb_persistent *hnb_persistent_find_by_id_str(const char *id_str);
|
||||
void hnb_persistent_update_addr(struct hnb_persistent *hnbp, int new_fd);
|
||||
void hnb_persistent_free(struct hnb_persistent *hnbp);
|
||||
|
||||
void hnbgw_vty_init(void);
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
struct hnb_persistent;
|
||||
|
||||
struct nft_kpi_val {
|
||||
uint64_t packets;
|
||||
uint64_t total_bytes;
|
||||
uint64_t ue_bytes;
|
||||
|
||||
bool handle_present;
|
||||
int64_t handle;
|
||||
};
|
||||
|
||||
int nft_kpi_init(const char *table_name);
|
||||
int hnb_nft_kpi_start(struct hnb_persistent *hnbp, const struct osmo_sockaddr_str *gtpu_remote);
|
||||
int hnb_nft_kpi_end(struct hnb_persistent *hnbp);
|
|
@ -20,6 +20,7 @@ AM_CFLAGS = \
|
|||
$(LIBOSMORANAP_CFLAGS) \
|
||||
$(LIBOSMOHNBAP_CFLAGS) \
|
||||
$(LIBOSMOMGCPCLIENT_CFLAGS) \
|
||||
$(LIBNFTABLES_CFLAGS) \
|
||||
$(NULL)
|
||||
|
||||
AM_LDFLAGS = \
|
||||
|
@ -46,6 +47,7 @@ libhnbgw_la_SOURCES = \
|
|||
mgw_fsm.c \
|
||||
kpi_ranap.c \
|
||||
tdefs.c \
|
||||
nft_kpi.c \
|
||||
$(NULL)
|
||||
|
||||
libhnbgw_la_LIBADD = \
|
||||
|
@ -62,6 +64,7 @@ libhnbgw_la_LIBADD = \
|
|||
$(LIBOSMOHNBAP_LIBS) \
|
||||
$(LIBSCTP_LIBS) \
|
||||
$(LIBOSMOMGCPCLIENT_LIBS) \
|
||||
$(LIBNFTABLES_LIBS) \
|
||||
$(NULL)
|
||||
|
||||
if ENABLE_PFCP
|
||||
|
|
|
@ -342,8 +342,10 @@ void hnb_context_release(struct hnb_context *ctx)
|
|||
}
|
||||
|
||||
/* remove back reference from hnb_persistent to context */
|
||||
if (ctx->persistent)
|
||||
if (ctx->persistent) {
|
||||
hnb_nft_kpi_end(ctx->persistent);
|
||||
ctx->persistent->ctx = NULL;
|
||||
}
|
||||
|
||||
talloc_free(ctx);
|
||||
}
|
||||
|
@ -398,11 +400,14 @@ const struct rate_ctr_desc hnb_ctr_description[] = {
|
|||
[HNB_CTR_RANAP_CS_RAB_MOD_FAIL] = {
|
||||
"ranap:cs:rab_mod:fail", "CS RAB Modifications failed" },
|
||||
|
||||
|
||||
[HNB_CTR_RANAP_PS_RAB_REL_REQ] = {
|
||||
"ranap:ps:rab_rel:req", "PS RAB Release requested" },
|
||||
"ranap:ps:rab_rel:req:normal", "PS RAB Release requested (by CN), normal" },
|
||||
[HNB_CTR_RANAP_CS_RAB_REL_REQ] = {
|
||||
"ranap:cs:rab_rel:req", "CS RAB Release requested" },
|
||||
"ranap:cs:rab_rel:req:normal", "CS RAB Release requested (by CN), normal" },
|
||||
[HNB_CTR_RANAP_PS_RAB_REL_REQ_ABNORMAL] = {
|
||||
"ranap:ps:rab_rel:req:abnormal", "PS RAB Release requested (by CN), abnormal" },
|
||||
[HNB_CTR_RANAP_CS_RAB_REL_REQ_ABNORMAL] = {
|
||||
"ranap:cs:rab_rel:req:abnormal", "CS RAB Release requested (by CN), abnormal" },
|
||||
|
||||
[HNB_CTR_RANAP_PS_RAB_REL_CNF] = {
|
||||
"ranap:ps:rab_rel:cnf", "PS RAB Release confirmed" },
|
||||
|
@ -415,9 +420,13 @@ const struct rate_ctr_desc hnb_ctr_description[] = {
|
|||
"ranap:cs:rab_rel:fail", "CS RAB Release failed" },
|
||||
|
||||
[HNB_CTR_RANAP_PS_RAB_REL_IMPLICIT] = {
|
||||
"ranap:ps:rab_rel:implicit", "PS RAB Release implicit (during Iu Release)" },
|
||||
"ranap:ps:rab_rel:implicit:normal", "PS RAB Release implicit (during Iu Release), normal" },
|
||||
[HNB_CTR_RANAP_CS_RAB_REL_IMPLICIT] = {
|
||||
"ranap:cs:rab_rel:implicit", "CS RAB Release implicit (during Iu Release)" },
|
||||
"ranap:cs:rab_rel:implicit:normal", "CS RAB Release implicit (during Iu Release), normal" },
|
||||
[HNB_CTR_RANAP_PS_RAB_REL_IMPLICIT_ABNORMAL] = {
|
||||
"ranap:ps:rab_rel:implicit:abnormal", "PS RAB Release implicit (during Iu Release), abnormal" },
|
||||
[HNB_CTR_RANAP_CS_RAB_REL_IMPLICIT_ABNORMAL] = {
|
||||
"ranap:cs:rab_rel:implicit:abnormal", "CS RAB Release implicit (during Iu Release), abnormal" },
|
||||
|
||||
[HNB_CTR_RUA_ERR_IND] = {
|
||||
"rua:error_ind", "Received RUA Error Indications" },
|
||||
|
@ -457,6 +466,32 @@ const struct rate_ctr_desc hnb_ctr_description[] = {
|
|||
|
||||
[HNB_CTR_RAB_ACTIVE_MILLISECONDS_TOTAL] = {
|
||||
"rab:cs:active_milliseconds:total", "Cumulative number of milliseconds of CS RAB activity" },
|
||||
|
||||
[HNB_CTR_GTPU_PACKETS_UL] = {
|
||||
"gtpu:packets:ul",
|
||||
"Count of GTP-U packets received from the HNB",
|
||||
},
|
||||
[HNB_CTR_GTPU_TOTAL_BYTES_UL] = {
|
||||
"gtpu:total_bytes:ul",
|
||||
"Count of total GTP-U bytes received from the HNB, including the GTP-U/UDP/IP headers",
|
||||
},
|
||||
[HNB_CTR_GTPU_UE_BYTES_UL] = {
|
||||
"gtpu:ue_bytes:ul",
|
||||
"Assuming an IP header length of 20 bytes, GTP-U bytes received from the HNB, excluding the GTP-U/UDP/IP headers",
|
||||
},
|
||||
[HNB_CTR_GTPU_PACKETS_DL] = {
|
||||
"gtpu:packets:dl",
|
||||
"Count of GTP-U packets sent to the HNB",
|
||||
},
|
||||
[HNB_CTR_GTPU_TOTAL_BYTES_DL] = {
|
||||
"gtpu:total_bytes:dl",
|
||||
"Count of total GTP-U bytes sent to the HNB, including the GTP-U/UDP/IP headers",
|
||||
},
|
||||
[HNB_CTR_GTPU_UE_BYTES_DL] = {
|
||||
"gtpu:ue_bytes:dl",
|
||||
"Assuming an IP header length of 20 bytes, GTP-U bytes sent to the HNB, excluding the GTP-U/UDP/IP headers",
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
const struct rate_ctr_group_desc hnb_ctrg_desc = {
|
||||
|
@ -485,6 +520,12 @@ struct hnb_persistent *hnb_persistent_alloc(const struct umts_cell_id *id)
|
|||
if (!hnbp)
|
||||
return NULL;
|
||||
|
||||
/* nft_kpi.c updates hnb stats, and it locks osmo_stats_report_lock() while it does so. This here should run
|
||||
* in the same thread as stats reporting, so there should be no conflict with stats. But to avoid a hnb in flux
|
||||
* while nft_kpi.c looks up hnb, also obtain the osmo_stats_report_lock() while manipulating hnb records. */
|
||||
osmo_stats_report_lock();
|
||||
/* { */
|
||||
|
||||
hnbp->id = *id;
|
||||
hnbp->id_str = talloc_strdup(hnbp, umts_cell_id_name(id));
|
||||
hnbp->ctrs = rate_ctr_group_alloc(hnbp, &hnb_ctrg_desc, 0);
|
||||
|
@ -497,14 +538,21 @@ struct hnb_persistent *hnb_persistent_alloc(const struct umts_cell_id *id)
|
|||
osmo_stat_item_group_set_name(hnbp->statg, hnbp->id_str);
|
||||
|
||||
llist_add(&hnbp->list, &g_hnbgw->hnb_persistent_list);
|
||||
/* success */
|
||||
goto out_unlock;
|
||||
|
||||
return hnbp;
|
||||
|
||||
/* failure */
|
||||
out_free_ctrs:
|
||||
rate_ctr_group_free(hnbp->ctrs);
|
||||
out_free:
|
||||
talloc_free(hnbp);
|
||||
return NULL;
|
||||
hnbp = NULL;
|
||||
|
||||
/* for both success and failure: */
|
||||
out_unlock:
|
||||
/* } */
|
||||
osmo_stats_report_unlock();
|
||||
return hnbp;
|
||||
}
|
||||
|
||||
struct hnb_persistent *hnb_persistent_find_by_id(const struct umts_cell_id *id)
|
||||
|
@ -519,9 +567,46 @@ struct hnb_persistent *hnb_persistent_find_by_id(const struct umts_cell_id *id)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
struct hnb_persistent *hnb_persistent_find_by_id_str(const char *id_str)
|
||||
{
|
||||
struct hnb_persistent *hnbp;
|
||||
llist_for_each_entry (hnbp, &g_hnbgw->hnb_persistent_list, list) {
|
||||
if (strcmp(hnbp->id_str, id_str))
|
||||
continue;
|
||||
return hnbp;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Read the peer's remote IP address from the Iuh conn's fd, and set up GTP-U counters for that remote address. */
|
||||
void hnb_persistent_update_addr(struct hnb_persistent *hnbp, int new_fd)
|
||||
{
|
||||
int rc;
|
||||
socklen_t socklen;
|
||||
struct osmo_sockaddr osa;
|
||||
struct osmo_sockaddr_str remote_str;
|
||||
|
||||
socklen = sizeof(struct osmo_sockaddr);
|
||||
rc = getpeername(new_fd, &osa.u.sa, &socklen);
|
||||
if (!rc)
|
||||
rc = osmo_sockaddr_str_from_osa(&remote_str, &osa);
|
||||
if (rc < 0) {
|
||||
LOGP(DHNB, LOGL_ERROR, "cannot read remote hNodeB address from Iuh file descriptor\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* We got the remote address from the RUA link, and now we are blatantly assuming that the hNodeB has its GTP
|
||||
* endpoint on the same IP address, just with UDP port 2152 (the fixed GTP port as per 3GPP spec). */
|
||||
remote_str.port = 2152;
|
||||
|
||||
if (g_hnbgw->config.nft_kpi.enable)
|
||||
hnb_nft_kpi_start(hnbp, &remote_str);
|
||||
}
|
||||
|
||||
void hnb_persistent_free(struct hnb_persistent *hnbp)
|
||||
{
|
||||
/* FIXME: check if in use? */
|
||||
hnb_nft_kpi_end(hnbp);
|
||||
llist_del(&hnbp->list);
|
||||
talloc_free(hnbp);
|
||||
}
|
||||
|
@ -847,6 +932,11 @@ static const struct log_info_cat hnbgw_log_cat[] = {
|
|||
.color = OSMO_LOGCOLOR_DARKYELLOW,
|
||||
.description = "Core Network side (via SCCP)",
|
||||
},
|
||||
[DNFT] = {
|
||||
.name = "DNFT", .loglevel = LOGL_NOTICE, .enabled = 1,
|
||||
.color = OSMO_LOGCOLOR_BLUE,
|
||||
.description = "nftables interaction for retrieving stats",
|
||||
},
|
||||
};
|
||||
|
||||
const struct log_info hnbgw_log_info = {
|
||||
|
|
|
@ -211,6 +211,7 @@ static int hnbgw_tx_ue_register_rej(struct hnb_context *hnb, HNBAP_UE_Identity_t
|
|||
{
|
||||
HNBAP_UERegisterReject_t reject_out;
|
||||
HNBAP_UERegisterRejectIEs_t reject;
|
||||
char imsi[GSM23003_IMSI_MAX_DIGITS+1];
|
||||
struct msgb *msg;
|
||||
int rc;
|
||||
|
||||
|
@ -271,6 +272,14 @@ static int hnbgw_tx_ue_register_rej(struct hnb_context *hnb, HNBAP_UE_Identity_t
|
|||
ue_id->choice.pTMSIRAI.rAI.rAC.size);
|
||||
break;
|
||||
|
||||
case HNBAP_UE_Identity_PR_iMSI:
|
||||
ranap_bcd_decode(imsi, sizeof(imsi), ue_id->choice.iMSI.buf, ue_id->choice.iMSI.size);
|
||||
LOGHNB(hnb, DHNBAP, LOGL_DEBUG, "REJ UE_Id IMSI %s\n", imsi);
|
||||
|
||||
OCTET_STRING_fromBuf(&reject.uE_Identity.choice.iMSI,
|
||||
(const char *)ue_id->choice.iMSI.buf, ue_id->choice.iMSI.size);
|
||||
break;
|
||||
|
||||
default:
|
||||
LOGHNB(hnb, DHNBAP, LOGL_ERROR, "Cannot compose UE Register Reject:"
|
||||
" unsupported UE ID (present=%d)\n", ue_id->present);
|
||||
|
@ -312,6 +321,9 @@ static int hnbgw_tx_ue_register_rej(struct hnb_context *hnb, HNBAP_UE_Identity_t
|
|||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_OCTET_STRING,
|
||||
&reject.uE_Identity.choice.pTMSIRAI.rAI.rAC);
|
||||
break;
|
||||
case HNBAP_UE_Identity_PR_iMSI:
|
||||
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_OCTET_STRING,
|
||||
&reject.uE_Identity.choice.iMSI);
|
||||
|
||||
default:
|
||||
/* should never happen after above switch() */
|
||||
|
@ -466,6 +478,8 @@ static int hnbgw_rx_hnb_register_req(struct hnb_context *ctx, ANY_t *in)
|
|||
const char *cell_id_str;
|
||||
struct timespec tp;
|
||||
HNBAP_Cause_t cause;
|
||||
struct osmo_sockaddr cur_osa = {};
|
||||
socklen_t len = sizeof(cur_osa);
|
||||
|
||||
rc = hnbap_decode_hnbregisterrequesties(&ies, in);
|
||||
if (rc < 0) {
|
||||
|
@ -487,6 +501,15 @@ static int hnbgw_rx_hnb_register_req(struct hnb_context *ctx, ANY_t *in)
|
|||
ctx->id.mnc = plmn.mnc;
|
||||
cell_id_str = umts_cell_id_name(&ctx->id);
|
||||
|
||||
if (getpeername(ofd->fd, &cur_osa.u.sa, &len) < 0) {
|
||||
LOGHNB(ctx, DHNBAP, LOGL_ERROR, "HNB-REGISTER-REQ %s: rejecting due to getpeername() error: %s\n",
|
||||
cell_id_str, strerror(errno));
|
||||
hnbap_free_hnbregisterrequesties(&ies);
|
||||
cause.present = HNBAP_Cause_PR_radioNetwork;
|
||||
cause.choice.radioNetwork = HNBAP_CauseRadioNetwork_hNB_parameter_mismatch;
|
||||
return hnbgw_tx_hnb_register_rej(ctx, &cause);
|
||||
}
|
||||
|
||||
hnbp = hnb_persistent_find_by_id(&ctx->id);
|
||||
if (!hnbp && g_hnbgw->config.accept_all_hnb)
|
||||
hnbp = hnb_persistent_alloc(&ctx->id);
|
||||
|
@ -498,6 +521,7 @@ static int hnbgw_rx_hnb_register_req(struct hnb_context *ctx, ANY_t *in)
|
|||
cause.choice.radioNetwork = HNBAP_CauseRadioNetwork_unauthorised_HNB;
|
||||
return hnbgw_tx_hnb_register_rej(ctx, &cause);
|
||||
}
|
||||
|
||||
ctx->persistent = hnbp;
|
||||
hnbp->ctx = ctx;
|
||||
|
||||
|
@ -511,22 +535,13 @@ static int hnbgw_rx_hnb_register_req(struct hnb_context *ctx, ANY_t *in)
|
|||
* fault (bug), and we release the old context to keep going... */
|
||||
struct osmo_fd *other_fd = osmo_stream_srv_get_ofd(hnb->conn);
|
||||
struct osmo_sockaddr other_osa = {};
|
||||
struct osmo_sockaddr cur_osa = {};
|
||||
socklen_t len = sizeof(other_osa);
|
||||
if (getpeername(other_fd->fd, &other_osa.u.sa, &len) < 0) {
|
||||
LOGHNB(ctx, DHNBAP, LOGL_ERROR, "BUG! Found old registered HNB with invalid socket, releasing it\n");
|
||||
hnb_context_release(hnb);
|
||||
continue;
|
||||
}
|
||||
len = sizeof(cur_osa);
|
||||
if (getpeername(ofd->fd, &cur_osa.u.sa, &len) < 0) {
|
||||
LOGHNB(ctx, DHNBAP, LOGL_ERROR, "Error getpeername(): %s\n", strerror(errno));
|
||||
if (osmo_sockaddr_cmp(&cur_osa, &other_osa) == 0) {
|
||||
LOGHNB(ctx, DHNBAP, LOGL_ERROR, "BUG! Found old registered HNB with same remote address, releasing it\n");
|
||||
hnb_context_release(hnb);
|
||||
continue;
|
||||
}
|
||||
} else if (osmo_sockaddr_cmp(&cur_osa, &other_osa) == 0) {
|
||||
if (osmo_sockaddr_cmp(&cur_osa, &other_osa) == 0) {
|
||||
LOGHNB(ctx, DHNBAP, LOGL_ERROR, "BUG! Found old registered HNB with same remote address, releasing it\n");
|
||||
hnb_context_release(hnb);
|
||||
continue;
|
||||
|
@ -553,6 +568,8 @@ static int hnbgw_rx_hnb_register_req(struct hnb_context *ctx, ANY_t *in)
|
|||
|
||||
ctx->hnb_registered = true;
|
||||
|
||||
hnb_persistent_update_addr(ctx->persistent, osmo_stream_srv_get_fd(ctx->conn));
|
||||
|
||||
/* Send HNBRegisterAccept */
|
||||
rc = hnbgw_tx_hnb_register_acc(ctx);
|
||||
hnbap_free_hnbregisterrequesties(&ies);
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
#include <osmocom/hnbgw/hnbgw_cn.h>
|
||||
#include <osmocom/hnbgw/context_map.h>
|
||||
#include <osmocom/hnbgw/tdefs.h>
|
||||
#include <osmocom/hnbgw/nft_kpi.h>
|
||||
#include <osmocom/sigtran/protocol/sua.h>
|
||||
#include <osmocom/sigtran/sccp_helpers.h>
|
||||
#include <osmocom/netif/stream.h>
|
||||
|
@ -878,6 +879,38 @@ DEFUN(cfg_hnbgw_no_hnb, cfg_hnbgw_no_hnb_cmd,
|
|||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
#define NFT_KPI_STR "Retrieve traffic counters from nftables\n"
|
||||
|
||||
DEFUN(cfg_hnbgw_nft_kpi, cfg_hnbgw_nft_kpi_cmd,
|
||||
"nft-kpi [TABLE_NAME]",
|
||||
NFT_KPI_STR
|
||||
"Set a custom nft table name to use, instead of 'osmo-hnbgw'\n")
|
||||
{
|
||||
const char *set_table_name = NULL;
|
||||
if (argc > 0)
|
||||
set_table_name = argv[0];
|
||||
|
||||
if (vty->type == VTY_TERM)
|
||||
vty_out(vty, "%% WARNING: nft configuration changes need a restart of osmo-hnbw%s", VTY_NEWLINE);
|
||||
|
||||
g_hnbgw->config.nft_kpi.enable = true;
|
||||
if (g_hnbgw->config.nft_kpi.table_name)
|
||||
talloc_free(g_hnbgw->config.nft_kpi.table_name);
|
||||
g_hnbgw->config.nft_kpi.table_name = talloc_strdup(g_hnbgw, set_table_name);
|
||||
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_hnbgw_no_nft_kpi, cfg_hnbgw_no_nft_kpi_cmd,
|
||||
"no nft-kpi",
|
||||
NO_STR NFT_KPI_STR)
|
||||
{
|
||||
if (vty->type == VTY_TERM)
|
||||
vty_out(vty, "%% WARNING: nft configuration changes need a restart of osmo-hnbw%s", VTY_NEWLINE);
|
||||
g_hnbgw->config.nft_kpi.enable = false;
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
#if ENABLE_PFCP
|
||||
|
||||
static struct cmd_node pfcp_node = {
|
||||
|
@ -1001,6 +1034,12 @@ static int config_write_hnbgw(struct vty *vty)
|
|||
_config_write_cnpool(vty, &g_hnbgw->sccp.cnpool_iucs);
|
||||
_config_write_cnpool(vty, &g_hnbgw->sccp.cnpool_iups);
|
||||
|
||||
if (g_hnbgw->config.nft_kpi.enable)
|
||||
vty_out(vty, " nft-kpi%s%s%s",
|
||||
g_hnbgw->config.nft_kpi.table_name ? " " : "",
|
||||
g_hnbgw->config.nft_kpi.table_name ? : "",
|
||||
VTY_NEWLINE);
|
||||
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
|
@ -1125,6 +1164,9 @@ void hnbgw_vty_init(void)
|
|||
install_element(HNBGW_NODE, &cfg_hnbgw_no_hnb_cmd);
|
||||
install_node(&hnb_node, NULL);
|
||||
|
||||
install_element(HNBGW_NODE, &cfg_hnbgw_nft_kpi_cmd);
|
||||
install_element(HNBGW_NODE, &cfg_hnbgw_no_nft_kpi_cmd);
|
||||
|
||||
install_element_ve(&show_cnlink_cmd);
|
||||
install_element_ve(&show_hnb_cmd);
|
||||
install_element_ve(&show_one_hnb_cmd);
|
||||
|
|
|
@ -36,14 +36,26 @@
|
|||
static void kpi_ranap_process_dl_iu_rel_cmd(struct hnbgw_context_map *map, const ranap_message *ranap)
|
||||
{
|
||||
struct hnb_persistent *hnbp = map->hnb_ctx->persistent;
|
||||
const RANAP_Cause_t *cause;
|
||||
|
||||
OSMO_ASSERT(ranap->procedureCode == RANAP_ProcedureCode_id_Iu_Release);
|
||||
|
||||
cause = &ranap->msg.iu_ReleaseCommandIEs.cause;
|
||||
|
||||
/* When Iu is released, all RABs are released implicitly */
|
||||
for (unsigned int i = 0; i < ARRAY_SIZE(map->rab_state); i++) {
|
||||
unsigned int ctr_num;
|
||||
switch (map->rab_state[i]) {
|
||||
case RAB_STATE_ACTIVE:
|
||||
HNBP_CTR_INC(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_IMPLICIT : HNB_CTR_RANAP_CS_RAB_REL_IMPLICIT);
|
||||
if (cause->present == RANAP_Cause_PR_nAS ||
|
||||
cause->choice.nAS == RANAP_CauseNAS_normal_release) {
|
||||
ctr_num = HNB_CTR_RANAP_PS_RAB_REL_IMPLICIT;
|
||||
} else {
|
||||
ctr_num = HNB_CTR_RANAP_PS_RAB_REL_IMPLICIT_ABNORMAL;
|
||||
}
|
||||
if (!map->is_ps)
|
||||
ctr_num++;
|
||||
HNBP_CTR_INC(hnbp, ctr_num);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -106,15 +118,12 @@ static void kpi_ranap_process_dl_rab_ass_req(struct hnbgw_context_map *map, rana
|
|||
|
||||
if (ies->presenceMask & RAB_ASSIGNMENTREQUESTIES_RANAP_RAB_RELEASELIST_PRESENT) {
|
||||
RANAP_RAB_ReleaseList_t *r_list = &ies->raB_ReleaseList;
|
||||
/* increment number of released RABs, we don't need to do that individually during iteration */
|
||||
HNBP_CTR_ADD(hnbp, map->is_ps ? HNB_CTR_RANAP_PS_RAB_REL_REQ : HNB_CTR_RANAP_CS_RAB_REL_REQ,
|
||||
r_list->raB_ReleaseList_ies.list.count);
|
||||
|
||||
for (unsigned int i = 0; i < r_list->raB_ReleaseList_ies.list.count; i++) {
|
||||
RANAP_IE_t *release_list_ie = r_list->raB_ReleaseList_ies.list.array[i];
|
||||
RANAP_RAB_ReleaseItemIEs_t _rab_rel_item_ies = {};
|
||||
RANAP_RAB_ReleaseItemIEs_t *rab_rel_item_ies = &_rab_rel_item_ies;
|
||||
RANAP_RAB_ReleaseItem_t *rab_rel_item;
|
||||
unsigned int ctr_num;
|
||||
uint8_t rab_id;
|
||||
|
||||
if (!release_list_ie)
|
||||
|
@ -133,6 +142,15 @@ static void kpi_ranap_process_dl_rab_ass_req(struct hnbgw_context_map *map, rana
|
|||
|
||||
switch (map->rab_state[rab_id]) {
|
||||
case RAB_STATE_ACTIVE:
|
||||
if (rab_rel_item->cause.present == RANAP_Cause_PR_nAS &&
|
||||
rab_rel_item->cause.choice.nAS == RANAP_CauseNAS_normal_release) {
|
||||
ctr_num = HNB_CTR_RANAP_PS_RAB_REL_REQ;
|
||||
} else {
|
||||
ctr_num = HNB_CTR_RANAP_PS_RAB_REL_REQ_ABNORMAL;
|
||||
}
|
||||
if (!map->is_ps)
|
||||
ctr_num++;
|
||||
HNBP_CTR_INC(hnbp, ctr_num);
|
||||
break;
|
||||
default:
|
||||
LOG_MAP(map, DRANAP, LOGL_NOTICE,
|
||||
|
|
|
@ -175,7 +175,7 @@ static void mgw_fsm_crcx_hnb_onenter(struct osmo_fsm_inst *fi, uint32_t prev_sta
|
|||
mgw_info = (struct mgcp_conn_peer) {
|
||||
.call_id = (map->rua_ctx_id << 8) | mgw_fsm_priv->rab_id,
|
||||
.ptime = 20,
|
||||
.conn_mode = MGCP_CONN_LOOPBACK,
|
||||
.conn_mode = MGCP_CONN_RECV_SEND,
|
||||
};
|
||||
mgw_info.codecs[0] = CODEC_IUFP;
|
||||
mgw_info.codecs_len = 1;
|
||||
|
|
|
@ -0,0 +1,533 @@
|
|||
/* Set up and read internet traffic counters using netfilter */
|
||||
/* Copyright (C) 2024 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
|
||||
*
|
||||
* 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 Affero General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/core/stats.h>
|
||||
#include <osmocom/hnbgw/hnbgw.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#if !ENABLE_NFTABLES
|
||||
|
||||
int hnb_nft_kpi_start(struct hnb_persistent *hnbp, const struct osmo_sockaddr_str *gtpu_remote)
|
||||
{
|
||||
LOGP(DNFT, LOGL_INFO, "Built without libnftables support, not starting nft based counters for HNB %s\n",
|
||||
hnbp->id_str);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hnb_nft_kpi_end(struct hnb_persistent *hnbp)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <nftables/libnftables.h>
|
||||
|
||||
#include <osmocom/core/timer.h>
|
||||
#include <osmocom/hnbgw/nft_kpi.h>
|
||||
#include <osmocom/hnbgw/tdefs.h>
|
||||
|
||||
/* Threading and locks in this implementation:
|
||||
*
|
||||
* - osmo_stats_report_lock() held while updating rate_ctr from nft results.
|
||||
* - g_nft_kpi_state.lock held while running an nftables command buffer.
|
||||
*
|
||||
* contrived example:
|
||||
*
|
||||
* Main Thread
|
||||
* |
|
||||
* osmo_stats_report_use_lock(true)
|
||||
* |
|
||||
* nft_kpi_init()
|
||||
* create nft ctx, create table
|
||||
* |
|
||||
* +---------------------------> NFT thread
|
||||
* | |
|
||||
* | sleep(X34)
|
||||
* | |
|
||||
* | LOCK(g_nft_kpi_state.lock)
|
||||
* | |
|
||||
* osmo_stats_report() query all nft counters
|
||||
* LOCK(osmo_stats_report_lock) |
|
||||
* | LOCK(osmo_stats_report_lock)
|
||||
* collect stats : wait because libosmocore stats reporting is busy
|
||||
* | :
|
||||
* UNLOCK(osmo_stats_report_lock) LOCK------|
|
||||
* send out stats for all hnbp: rate_ctr_add2()
|
||||
* | |
|
||||
* | UNLOCK(osmo_stats_report_lock)
|
||||
* | |
|
||||
* | UNLOCK(g_nft_kpi_state.lock)
|
||||
* | |
|
||||
* hnbgw_rx_hnb_register_req() sleep(X34)
|
||||
* hnb_nft_kpi_start() |
|
||||
* LOCK(g_nft_kpi_state.lock) ...
|
||||
* |
|
||||
* nftables: add new rule
|
||||
* |
|
||||
* UNLOCK(g_nft_kpi_state.lock)
|
||||
* |
|
||||
* ...
|
||||
*
|
||||
* So the NFT thread only retrieves counters. The main thread adds and removes NFT rules for counters. It is possible
|
||||
* that a HNBAP HNB Register or HNB De-Register occurrs while the NFT thread holds the g_nft_kpi_state.lock, so that the
|
||||
* main thread blocks until the NFT thread is done reading counters. Note, this happens only for HNB attach or detach.
|
||||
*
|
||||
* A more scalable solution is to move all NFT interaction to the thread. Instead of submitting rules from the main
|
||||
* thread, we could submit instructions to an inter-thread queue that the NFT thread works off. This would add
|
||||
* considerable complexity -- for now we accept the possible but rarely occurring short delay for HNB de/registration.
|
||||
*/
|
||||
|
||||
struct nft_kpi_state {
|
||||
/* lock this while modifying g_nft_kpi_state */
|
||||
pthread_mutex_t lock;
|
||||
|
||||
struct {
|
||||
struct nft_ctx *nft_ctx;
|
||||
char *table_name;
|
||||
bool table_created;
|
||||
} nft;
|
||||
|
||||
pthread_t thread;
|
||||
};
|
||||
|
||||
static struct nft_kpi_state g_nft_kpi_state = {};
|
||||
|
||||
static struct nft_ctx *g_nft_ctx(void)
|
||||
{
|
||||
struct nft_kpi_state *s = &g_nft_kpi_state;
|
||||
|
||||
if (s->nft.nft_ctx)
|
||||
return s->nft.nft_ctx;
|
||||
|
||||
s->nft.nft_ctx = nft_ctx_new(NFT_CTX_DEFAULT);
|
||||
if (!s->nft.nft_ctx) {
|
||||
LOGP(DNFT, LOGL_ERROR, "cannot allocate libnftables nft_ctx\n");
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
|
||||
nft_ctx_output_set_flags(s->nft.nft_ctx, NFT_CTX_OUTPUT_HANDLE);
|
||||
|
||||
return s->nft.nft_ctx;
|
||||
}
|
||||
|
||||
static int nft_run_now(const char *buffer)
|
||||
{
|
||||
int rc;
|
||||
const int logmax = 256;
|
||||
|
||||
rc = nft_run_cmd_from_buffer(g_nft_ctx(), buffer);
|
||||
if (rc < 0) {
|
||||
LOGP(DNFT, LOGL_ERROR, "error running nft buffer: rc=%d buffer=%s\n",
|
||||
rc, osmo_quote_str_c(OTC_SELECT, buffer, -1));
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (log_check_level(DNFT, LOGL_DEBUG)) {
|
||||
size_t l = strlen(buffer);
|
||||
LOGP(DNFT, LOGL_DEBUG, "ran nft buffer, %zu chars: \"%s%s\"\n",
|
||||
l,
|
||||
osmo_escape_cstr_c(OTC_SELECT, buffer, OSMO_MIN(logmax, l)),
|
||||
l > logmax ? "..." : "");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void nft_kpi_period_cb(void);
|
||||
|
||||
void *nft_kpi_thread(void *arg)
|
||||
{
|
||||
OSMO_ASSERT(osmo_ctx_init(__func__) == 0);
|
||||
while (1) {
|
||||
/* Let's just hope that the unsigned long in the hnbgw_T_defs is not modified non-atomically while
|
||||
* reading the timeout value. */
|
||||
unsigned long period = osmo_tdef_get(hnbgw_T_defs, -34, OSMO_TDEF_US, 1000000);
|
||||
if (period < 1)
|
||||
period = 1;
|
||||
usleep(period);
|
||||
|
||||
nft_kpi_period_cb();
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int nft_kpi_init(const char *table_name)
|
||||
{
|
||||
struct nft_kpi_state *s = &g_nft_kpi_state;
|
||||
char cmd[1024];
|
||||
struct osmo_strbuf sb = { .buf = cmd, .len = sizeof(cmd) };
|
||||
|
||||
if (s->nft.table_created)
|
||||
return 0;
|
||||
|
||||
if (!table_name || !*table_name)
|
||||
table_name = "osmo-hnbgw";
|
||||
osmo_talloc_replace_string(g_hnbgw, &s->nft.table_name, table_name);
|
||||
|
||||
OSMO_STRBUF_PRINTF(sb, "add table inet %s { flags owner; };\n", s->nft.table_name);
|
||||
OSMO_STRBUF_PRINTF(sb,
|
||||
"add chain inet %s gtpu-ul {"
|
||||
" type filter hook prerouting priority 0; policy accept;"
|
||||
" ip protocol != udp accept;"
|
||||
" udp sport != 2152 accept;"
|
||||
" udp dport != 2152 accept;"
|
||||
"};\n",
|
||||
s->nft.table_name);
|
||||
OSMO_STRBUF_PRINTF(sb,
|
||||
"add chain inet %s gtpu-dl {"
|
||||
" type filter hook postrouting priority 0; policy accept;"
|
||||
" ip protocol != udp accept;"
|
||||
" udp sport != 2152 accept;"
|
||||
" udp dport != 2152 accept;"
|
||||
"};\n",
|
||||
s->nft.table_name);
|
||||
OSMO_ASSERT(sb.chars_needed < sizeof(cmd));
|
||||
|
||||
if (nft_run_now(cmd))
|
||||
return -EIO;
|
||||
|
||||
s->nft.table_created = true;
|
||||
|
||||
/* Up to here, it was fine to dispatch nft without locking, because this is the initialization from the main
|
||||
* thread. From now on, whoever wants to use the g_nft_ctx must lock this mutex first. */
|
||||
pthread_mutex_init(&s->lock, NULL);
|
||||
pthread_create(&s->thread, NULL, nft_kpi_thread, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Set up counters for the hNodeB's remote address */
|
||||
int hnb_nft_kpi_start(struct hnb_persistent *hnbp, const struct osmo_sockaddr_str *gtpu_remote)
|
||||
{
|
||||
struct nft_kpi_state *s = &g_nft_kpi_state;
|
||||
char cmd[1024];
|
||||
struct osmo_strbuf sb = { .buf = cmd, .len = sizeof(cmd) };
|
||||
int rc;
|
||||
|
||||
if (!osmo_sockaddr_str_is_nonzero(gtpu_remote)) {
|
||||
LOGP(DNFT, LOGL_ERROR, "HNB %s: invalid remote GTP-U address: " OSMO_SOCKADDR_STR_FMT "\n",
|
||||
hnbp->id_str, OSMO_SOCKADDR_STR_FMT_ARGS(gtpu_remote));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Manipulating nft state, obtain lock */
|
||||
pthread_mutex_lock(&s->lock);
|
||||
/* { */
|
||||
|
||||
if (!osmo_sockaddr_str_cmp(gtpu_remote, &hnbp->nft_kpi.addr_remote)) {
|
||||
/* The remote address is unchanged, no need to update the nft probe */
|
||||
rc = 0;
|
||||
goto unlock_return;
|
||||
}
|
||||
|
||||
/* When there is no table created, it means nft is disabled. Do not attempt to set up counters. */
|
||||
if (!s->nft.table_created)
|
||||
goto unlock_return;
|
||||
|
||||
/* The remote address has changed. Cancel previous probe, if any, and start a new one. */
|
||||
if (osmo_sockaddr_str_is_nonzero(&hnbp->nft_kpi.addr_remote))
|
||||
hnb_nft_kpi_end(hnbp);
|
||||
|
||||
hnbp->nft_kpi.last.ul = (struct nft_kpi_val){};
|
||||
hnbp->nft_kpi.last.dl = (struct nft_kpi_val){};
|
||||
|
||||
hnbp->nft_kpi.addr_remote = *gtpu_remote;
|
||||
|
||||
OSMO_STRBUF_PRINTF(sb, "add rule inet %s gtpu-ul ip saddr %s counter comment \"ul:%s\";\n",
|
||||
s->nft.table_name,
|
||||
hnbp->nft_kpi.addr_remote.ip,
|
||||
hnbp->id_str);
|
||||
OSMO_STRBUF_PRINTF(sb, "add rule inet %s gtpu-dl ip daddr %s counter comment \"dl:%s\";\n",
|
||||
s->nft.table_name,
|
||||
hnbp->nft_kpi.addr_remote.ip,
|
||||
hnbp->id_str);
|
||||
OSMO_ASSERT(sb.chars_needed < sizeof(cmd));
|
||||
|
||||
rc = nft_run_now(cmd);
|
||||
if (rc) {
|
||||
/* There was an error running the rule, clear addr_remote to indicate that no rule exists. */
|
||||
hnbp->nft_kpi.addr_remote = (struct osmo_sockaddr_str){};
|
||||
}
|
||||
|
||||
unlock_return:
|
||||
/* } */
|
||||
pthread_mutex_unlock(&s->lock);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void nft_kpi_read_counters(void);
|
||||
|
||||
/* Terminate nft based counters for this HNB */
|
||||
int hnb_nft_kpi_end(struct hnb_persistent *hnbp)
|
||||
{
|
||||
struct nft_kpi_state *s = &g_nft_kpi_state;
|
||||
char *cmd;
|
||||
int rc = 0;
|
||||
|
||||
/* When there is no table created, neither can there be any rules to be deleted.
|
||||
* The rules get removed, but the table remains present for as long as osmo-hnbgw runs. */
|
||||
if (!s->nft.table_created)
|
||||
return 0;
|
||||
|
||||
pthread_mutex_lock(&s->lock);
|
||||
/* { */
|
||||
|
||||
/* presence of addr_remote indicates whether an nft rule has been submitted and still needs to be removed */
|
||||
if (!osmo_sockaddr_str_is_nonzero(&hnbp->nft_kpi.addr_remote))
|
||||
goto unlock_return;
|
||||
|
||||
if (!hnbp->nft_kpi.last.ul.handle_present
|
||||
|| !hnbp->nft_kpi.last.dl.handle_present) {
|
||||
/* We get to know the nft handles only after creating the rule, when querying the counters. If the
|
||||
* handle is not known here yet, then it means we haven't read the counters yet. We have to find out the
|
||||
* handle now. */
|
||||
nft_kpi_read_counters();
|
||||
}
|
||||
|
||||
/* clear the addr to indicate that the nft rule no longer exists. Even if below 'delete rule' fails, just
|
||||
* attempt to delete the rule once. */
|
||||
hnbp->nft_kpi.addr_remote = (struct osmo_sockaddr_str){};
|
||||
|
||||
cmd = talloc_asprintf(OTC_SELECT,
|
||||
"delete rule inet %s gtpu-ul handle %"PRId64";\n"
|
||||
"delete rule inet %s gtpu-dl handle %"PRId64";\n",
|
||||
s->nft.table_name,
|
||||
hnbp->nft_kpi.last.ul.handle,
|
||||
s->nft.table_name,
|
||||
hnbp->nft_kpi.last.dl.handle);
|
||||
rc = nft_run_now(cmd);
|
||||
|
||||
unlock_return:
|
||||
/* } */
|
||||
pthread_mutex_unlock(&s->lock);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void update_ctr(struct rate_ctr_group *cg, int cgidx, uint64_t *last_val, uint64_t new_val)
|
||||
{
|
||||
/* Because an hNodeB may re-connect, or even change the address it connects from, we need to store the last seen
|
||||
* value and add the difference to the rate counter. For example, the rate_ctr that lives in hnb_persistent has
|
||||
* seen 100 GTP-U packets. The hNodeB disconnects for ten seconds and then comes back. Now the nft ruleset has
|
||||
* been deleted and re-created, so the counters we read are back at 0, but we want to continue showing 100. When
|
||||
* the ruleset detects 10, we want to show 110. Hence this last_val stuff here.
|
||||
* last_val is also back to zero whenever the nft counters are restarted, see hnb_nft_kpi_start(), where
|
||||
* hnbp->nft_kpi.last.ul and last.dl are zeroed.
|
||||
*/
|
||||
if (new_val > *last_val)
|
||||
rate_ctr_add2(cg, cgidx, new_val - *last_val);
|
||||
*last_val = new_val;
|
||||
}
|
||||
|
||||
static void hnb_update_counters(struct hnb_persistent *hnbp, bool ul, int64_t packets, int64_t bytes, int64_t handle)
|
||||
{
|
||||
struct nft_kpi_val *val = (ul ? &hnbp->nft_kpi.last.ul : &hnbp->nft_kpi.last.dl);
|
||||
|
||||
/* Remember the nftables handle, which is needed to remove a rule when a hNodeB disconnects. */
|
||||
if (handle) {
|
||||
val->handle_present = true;
|
||||
val->handle = handle;
|
||||
}
|
||||
|
||||
update_ctr(hnbp->ctrs,
|
||||
ul ? HNB_CTR_GTPU_PACKETS_UL : HNB_CTR_GTPU_PACKETS_DL,
|
||||
&val->packets, packets);
|
||||
update_ctr(hnbp->ctrs,
|
||||
ul ? HNB_CTR_GTPU_TOTAL_BYTES_UL : HNB_CTR_GTPU_TOTAL_BYTES_DL,
|
||||
&val->total_bytes, bytes);
|
||||
|
||||
/* Assuming an IP header of 20 bytes, derive the GTP-U payload size:
|
||||
*
|
||||
* [...] \ \
|
||||
* [ UDP ][ TCP ] | UE payload | nft reports these bytes
|
||||
* [ IP ] / |
|
||||
* -- payload -- |
|
||||
* [ GTP-U 8 bytes ] | \
|
||||
* [ UDP 8 bytes ] | | need to subtract these, ~20 + 8 + 8
|
||||
* [ IP 20 bytes ] / /
|
||||
*/
|
||||
update_ctr(hnbp->ctrs,
|
||||
ul ? HNB_CTR_GTPU_UE_BYTES_UL : HNB_CTR_GTPU_UE_BYTES_DL,
|
||||
&val->ue_bytes, bytes - OSMO_MIN(bytes, packets * (20 + 8 + 8)));
|
||||
}
|
||||
|
||||
/* In the string section *pos .. end, find the first occurrence of after_str and return the following token, which ends
|
||||
* by a space or at end. If end is NULL, search until the '\0' termination of *pos.
|
||||
* Return true if after_str was found, copy the following token into buf, and in *pos, return the position just after
|
||||
* that token. */
|
||||
static bool get_token_after(char *buf, size_t buflen, const char **pos, const char *end, const char *after_str)
|
||||
{
|
||||
const char *found = strstr(*pos, after_str);
|
||||
const char *token_end;
|
||||
size_t token_len;
|
||||
if (!found)
|
||||
return false;
|
||||
if (end && found >= end) {
|
||||
*pos = end;
|
||||
return false;
|
||||
}
|
||||
found += strlen(after_str);
|
||||
while (*found && *found == ' ' && (!end || found < end))
|
||||
found++;
|
||||
token_end = found;
|
||||
while (*token_end != ' ' && (!end || token_end < end))
|
||||
token_end++;
|
||||
if (token_end <= found) {
|
||||
*pos = found;
|
||||
return false;
|
||||
}
|
||||
if (*found == '"' && token_end > found + 1 && *(token_end - 1) == '"') {
|
||||
found++;
|
||||
token_end--;
|
||||
}
|
||||
token_len = token_end - found;
|
||||
token_len = OSMO_MIN(token_len, buflen - 1);
|
||||
memcpy(buf, found, token_len);
|
||||
buf[token_len] = '\0';
|
||||
*pos = token_end;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void decode_nft_response(const char *response)
|
||||
{
|
||||
struct nft_kpi_state *s = &g_nft_kpi_state;
|
||||
const char *pos;
|
||||
char buf[128];
|
||||
int count = 0;
|
||||
|
||||
/* find and parse all occurences of strings like:
|
||||
* [...] counter packets 3 bytes 129 comment "ul:001-01-L2-R3-S4-C1" # handle 10
|
||||
*/
|
||||
pos = response;
|
||||
while (*pos) {
|
||||
const char *line_end;
|
||||
int64_t packets;
|
||||
int64_t bytes;
|
||||
int64_t handle = 0;
|
||||
bool ul;
|
||||
struct hnb_persistent *hnbp;
|
||||
|
||||
if (!get_token_after(buf, sizeof(buf), &pos, NULL, "counter packets "))
|
||||
break;
|
||||
if (osmo_str_to_int64(&packets, buf, 10, 0, INT64_MAX))
|
||||
break;
|
||||
line_end = strchr(pos, '\n');
|
||||
if (!line_end)
|
||||
line_end = pos + strlen(pos);
|
||||
|
||||
if (!get_token_after(buf, sizeof(buf), &pos, line_end, "bytes "))
|
||||
break;
|
||||
if (osmo_str_to_int64(&bytes, buf, 10, 0, INT64_MAX))
|
||||
break;
|
||||
|
||||
if (!get_token_after(buf, sizeof(buf), &pos, line_end, "comment "))
|
||||
break;
|
||||
|
||||
if (osmo_str_startswith(buf, "ul:"))
|
||||
ul = true;
|
||||
else if (osmo_str_startswith(buf, "dl:"))
|
||||
ul = false;
|
||||
else
|
||||
break;
|
||||
|
||||
hnbp = hnb_persistent_find_by_id_str(buf + 3);
|
||||
if (!hnbp)
|
||||
break;
|
||||
|
||||
if (!get_token_after(buf, sizeof(buf), &pos, line_end, "# handle "))
|
||||
break;
|
||||
if (osmo_str_to_int64(&handle, buf, 10, 0, INT64_MAX))
|
||||
break;
|
||||
|
||||
hnb_update_counters(hnbp, ul, packets, bytes, handle);
|
||||
count++;
|
||||
}
|
||||
|
||||
LOGP(DNFT, LOGL_DEBUG, "read %d counters from nft table %s\n", count, s->nft.table_name);
|
||||
}
|
||||
|
||||
/* The caller must hold the g_nft_kpi_state.lock! */
|
||||
static void nft_kpi_read_counters(void)
|
||||
{
|
||||
int rc;
|
||||
const int logmax = 256;
|
||||
struct nft_kpi_state *s = &g_nft_kpi_state;
|
||||
struct nft_ctx *nft = s->nft.nft_ctx;
|
||||
char cmd[256];
|
||||
struct osmo_strbuf sb = { .buf = cmd, .len = sizeof(cmd) };
|
||||
const char *output;
|
||||
|
||||
if (!nft)
|
||||
return;
|
||||
|
||||
OSMO_STRBUF_PRINTF(sb, "list table inet %s", s->nft.table_name);
|
||||
OSMO_ASSERT(sb.chars_needed < sizeof(cmd));
|
||||
|
||||
size_t l = strlen(cmd);
|
||||
LOGP(DNFT, LOGL_DEBUG, "running nft request, %zu chars: \"%s%s\"\n",
|
||||
l,
|
||||
osmo_escape_cstr_c(OTC_SELECT, cmd, OSMO_MIN(logmax, l)),
|
||||
l > logmax ? "..." : "");
|
||||
|
||||
rc = nft_ctx_buffer_output(nft);
|
||||
if (rc) {
|
||||
LOGP(DNFT, LOGL_ERROR, "error: nft_ctx_buffer_output() returned failure: rc=%d cmd=%s\n",
|
||||
rc, osmo_quote_str_c(OTC_SELECT, cmd, -1));
|
||||
goto unbuffer_and_exit;
|
||||
}
|
||||
rc = nft_run_cmd_from_buffer(nft, cmd);
|
||||
if (rc < 0) {
|
||||
LOGP(DNFT, LOGL_ERROR, "error running nft cmd: rc=%d cmd=%s\n",
|
||||
rc, osmo_quote_str_c(OTC_SELECT, cmd, -1));
|
||||
goto unbuffer_and_exit;
|
||||
}
|
||||
|
||||
output = nft_ctx_get_output_buffer(nft);
|
||||
l = strlen(output);
|
||||
LOGP(DNFT, LOGL_DEBUG, "got nft response, %zu chars: \"%s%s\"\n",
|
||||
l,
|
||||
osmo_escape_cstr_c(OTC_SELECT, output, OSMO_MIN(logmax, l)),
|
||||
l > logmax ? "..." : "");
|
||||
|
||||
osmo_stats_report_lock();
|
||||
/* { */
|
||||
decode_nft_response(output);
|
||||
/* } */
|
||||
osmo_stats_report_unlock();
|
||||
|
||||
unbuffer_and_exit:
|
||||
nft_ctx_unbuffer_output(nft);
|
||||
}
|
||||
|
||||
static void nft_kpi_period_cb(void)
|
||||
{
|
||||
pthread_mutex_lock(&g_nft_kpi_state.lock);
|
||||
/* { */
|
||||
nft_kpi_read_counters();
|
||||
/* } */
|
||||
pthread_mutex_unlock(&g_nft_kpi_state.lock);
|
||||
}
|
||||
|
||||
#endif // ENABLE_NFTABLES
|
|
@ -329,6 +329,23 @@ int main(int argc, char **argv)
|
|||
hnbgw_pfcp_init();
|
||||
#endif
|
||||
|
||||
/* If nftables is enabled, initialize the nft table now or fail startup. This is important to immediately let
|
||||
* the user know if cap_net_admin privileges are missing, and not only when the first hNodeB connects. */
|
||||
if (g_hnbgw->config.nft_kpi.enable) {
|
||||
#if ENABLE_NFTABLES
|
||||
if (nft_kpi_init(g_hnbgw->config.nft_kpi.table_name)) {
|
||||
perror("ERROR: Failed to initialize nft KPI, probably missing cap_net_admin");
|
||||
exit(1);
|
||||
}
|
||||
/* nft_kpi.c manipulates rate_ctr state directly. Enable the mutex lock around stats reporting, so
|
||||
* nft_kpi.c can make use of it. */
|
||||
osmo_stats_report_use_lock(true);
|
||||
#else
|
||||
fprintf(stderr, "ERROR: Cannot enable nft KPI, this binary was built without nftables support\n");
|
||||
exit(1);
|
||||
#endif
|
||||
}
|
||||
|
||||
hnbgw_cnpool_start(&g_hnbgw->sccp.cnpool_iucs);
|
||||
hnbgw_cnpool_start(&g_hnbgw->sccp.cnpool_iups);
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ struct osmo_tdef hnbgw_T_defs[] = {
|
|||
{.T = 3113, .default_val = 15, .desc = "Time to keep Paging record, for CN pools with more than one link" },
|
||||
{.T = 4, .default_val = 5, .desc = "Timeout to receive RANAP RESET ACKNOWLEDGE from an MSC/SGSN" },
|
||||
{.T = -31, .default_val = 15, .desc = "Timeout for establishing and releasing context maps (RUA <-> SCCP)" },
|
||||
{.T = -34, .default_val = 1000, .unit = OSMO_TDEF_MS, .desc = "Period to query network traffic stats from netfilter" },
|
||||
{.T = -1002, .default_val = 10, .desc = "Timeout for the HNB to respond to PS RAB Assignment Request" },
|
||||
{ }
|
||||
};
|
||||
|
|
|
@ -20,6 +20,7 @@ OsmoHNBGW(config-hnbgw)# list
|
|||
iups
|
||||
hnb UMTS_CELL_ID
|
||||
no hnb IDENTITY_INFO
|
||||
nft-kpi [TABLE_NAME]
|
||||
...
|
||||
|
||||
OsmoHNBGW(config-hnbgw)# plmn?
|
||||
|
@ -82,3 +83,39 @@ hnbgw
|
|||
...
|
||||
rnc-id 42
|
||||
...
|
||||
|
||||
OsmoHNBGW(config-hnbgw)# nft-kpi?
|
||||
nft-kpi Retrieve traffic counters from nftables
|
||||
OsmoHNBGW(config-hnbgw)# nft-kpi ?
|
||||
[TABLE_NAME] Set a custom nft table name to use, instead of 'osmo-hnbgw'
|
||||
|
||||
OsmoHNBGW(config-hnbgw)# show running-config
|
||||
... !nft-kpi
|
||||
|
||||
OsmoHNBGW(config-hnbgw)# nft-kpi
|
||||
% WARNING: nft configuration changes need a restart of osmo-hnbw
|
||||
OsmoHNBGW(config-hnbgw)# show running-config
|
||||
...
|
||||
hnbgw
|
||||
...
|
||||
nft-kpi
|
||||
...
|
||||
|
||||
OsmoHNBGW(config-hnbgw)# no nft-kpi
|
||||
% WARNING: nft configuration changes need a restart of osmo-hnbw
|
||||
OsmoHNBGW(config-hnbgw)# show running-config
|
||||
... !nft-kpi
|
||||
|
||||
OsmoHNBGW(config-hnbgw)# nft-kpi maple
|
||||
% WARNING: nft configuration changes need a restart of osmo-hnbw
|
||||
OsmoHNBGW(config-hnbgw)# show running-config
|
||||
...
|
||||
hnbgw
|
||||
...
|
||||
nft-kpi maple
|
||||
...
|
||||
|
||||
OsmoHNBGW(config-hnbgw)# no nft-kpi
|
||||
% WARNING: nft configuration changes need a restart of osmo-hnbw
|
||||
OsmoHNBGW(config-hnbgw)# show running-config
|
||||
... !nft-kpi
|
||||
|
|
Loading…
Reference in New Issue