mirror of
https://gitea.osmocom.org/cellular-infrastructure/osmo-upf.git
synced 2025-10-23 08:12:03 +00:00
add osmo-upf
Related: SYS#5599 Change-Id: I745bcbde6859004c41ddbfd2558036bf9a2d1de2
This commit is contained in:
16
doc/examples/osmo-upf/osmo-upf-create-dev.cfg
Normal file
16
doc/examples/osmo-upf/osmo-upf-create-dev.cfg
Normal file
@@ -0,0 +1,16 @@
|
||||
log stderr
|
||||
logging filter all 1
|
||||
logging color 1
|
||||
logging print level 1
|
||||
logging print category 1
|
||||
logging print category-hex 0
|
||||
logging print file basename last
|
||||
logging print extended-timestamp 1
|
||||
logging level set-all info
|
||||
#logging level set-all debug
|
||||
|
||||
timer pfcp x24 5000
|
||||
pfcp
|
||||
local-addr 127.0.0.1
|
||||
gtp
|
||||
dev create apn23
|
@@ -6,4 +6,10 @@ log stderr
|
||||
logging print category-hex 0
|
||||
logging print file basename last
|
||||
logging print extended-timestamp 1
|
||||
logging level set-all debug
|
||||
logging level set-all notice
|
||||
logging level set-all info
|
||||
|
||||
timer pfcp x24 5000
|
||||
pfcp
|
||||
local-addr 127.0.0.1
|
||||
|
@@ -1,3 +1,9 @@
|
||||
noinst_HEADERS = \
|
||||
up_endpoint.h \
|
||||
up_peer.h \
|
||||
up_session.h \
|
||||
upf.h \
|
||||
upf_gtp.h \
|
||||
upf_nft.h \
|
||||
up_gtp_action.h \
|
||||
$(NULL)
|
||||
|
48
include/osmocom/upf/up_endpoint.h
Normal file
48
include/osmocom/upf/up_endpoint.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
|
||||
struct osmo_pfcp_msg;
|
||||
struct osmo_pfcp_endpoint;
|
||||
struct osmo_sockaddr;
|
||||
|
||||
#define UP_USE_MSG_RX "msg-rx"
|
||||
#define UP_USE_MSG_TX "msg-tx"
|
||||
|
||||
struct up_endpoint {
|
||||
struct osmo_pfcp_endpoint *pfcp_ep;
|
||||
|
||||
struct llist_head peers;
|
||||
|
||||
uint64_t next_seid_state;
|
||||
uint32_t next_teid_state;
|
||||
};
|
||||
|
||||
struct up_endpoint *up_endpoint_init(void *ctx, const struct osmo_sockaddr *local_addr);
|
||||
void up_endpoint_free(struct up_endpoint **ep);
|
||||
|
||||
uint64_t up_endpoint_next_seid(struct up_endpoint *ep);
|
||||
uint32_t up_endpoint_next_teid(struct up_endpoint *ep);
|
73
include/osmocom/upf/up_gtp_action.h
Normal file
73
include/osmocom/upf/up_gtp_action.h
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
#include <osmocom/core/socket.h>
|
||||
#include <osmocom/core/fsm.h>
|
||||
|
||||
#include <osmocom/upf/up_session.h>
|
||||
#include <osmocom/upf/upf_gtp.h>
|
||||
#include <osmocom/upf/upf_nft.h>
|
||||
|
||||
#define LOG_UP_GTP_ACTION(A, LEVEL, FMT, ARGS...) \
|
||||
LOGP(DGTP, LEVEL, "%s: " FMT, up_gtp_action_to_str_c(OTC_SELECT, A), ##ARGS)
|
||||
|
||||
struct up_session;
|
||||
|
||||
enum up_gtp_action_kind {
|
||||
UP_GTP_DROP,
|
||||
UP_GTP_U_ENDECAPS,
|
||||
UP_GTP_U_TUNMAP,
|
||||
};
|
||||
|
||||
struct up_gtp_action {
|
||||
struct llist_head entry;
|
||||
struct up_session *session;
|
||||
|
||||
uint16_t pdr_core;
|
||||
uint16_t pdr_access;
|
||||
|
||||
enum up_gtp_action_kind kind;
|
||||
union {
|
||||
/* En-/De-capsulate GTP: add/remove a GTP header and forward the GTP payload from/to plain IP. */
|
||||
struct upf_gtp_tun_desc endecaps;
|
||||
|
||||
/* Tunnel-map GTP: translate from one TEID to another and forward */
|
||||
struct upf_nft_tunmap_desc tunmap;
|
||||
};
|
||||
|
||||
/* volatile loop variable to match up wanted and actually present GTP actions */
|
||||
void *handle;
|
||||
};
|
||||
|
||||
int up_gtp_action_cmp(const struct up_gtp_action *a, const struct up_gtp_action *b);
|
||||
|
||||
int up_gtp_action_enable(struct up_gtp_action *a);
|
||||
int up_gtp_action_disable(struct up_gtp_action *a);
|
||||
|
||||
int up_gtp_action_to_str_buf(char *buf, size_t buflen, const struct up_gtp_action *a);
|
||||
char *up_gtp_action_to_str_c(void *ctx, const struct up_gtp_action *a);
|
78
include/osmocom/upf/up_peer.h
Normal file
78
include/osmocom/upf/up_peer.h
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
#include <osmocom/core/hashtable.h>
|
||||
#include <osmocom/core/socket.h>
|
||||
#include <osmocom/core/use_count.h>
|
||||
|
||||
#include <osmocom/pfcp/pfcp_msg.h>
|
||||
|
||||
enum up_peer_event {
|
||||
UP_PEER_EV_RX_ASSOC_SETUP_REQ,
|
||||
UP_PEER_EV_RX_ASSOC_UPD_REQ,
|
||||
UP_PEER_EV_RX_ASSOC_REL_REQ,
|
||||
UP_PEER_EV_RX_SESSION_EST_REQ,
|
||||
UP_PEER_EV_HEARTBEAT_FAILURE,
|
||||
UP_PEER_EV_USE_COUNT_ZERO,
|
||||
UP_PEER_EV_SESSION_TERM,
|
||||
};
|
||||
|
||||
struct up_peer {
|
||||
struct llist_head entry;
|
||||
|
||||
struct osmo_fsm_inst *fi;
|
||||
struct up_endpoint *up_endpoint;
|
||||
|
||||
/* peer's remote address */
|
||||
struct osmo_sockaddr remote_addr;
|
||||
struct osmo_pfcp_ie_node_id remote_node_id;
|
||||
uint32_t remote_recovery_timestamp;
|
||||
|
||||
struct osmo_pfcp_ie_up_function_features local_up_features;
|
||||
struct osmo_pfcp_ie_cp_function_features peer_cp_features;
|
||||
|
||||
uint32_t next_seq_nr;
|
||||
|
||||
struct osmo_fsm_inst *heartbeat_fi;
|
||||
|
||||
struct osmo_use_count use_count;
|
||||
struct osmo_use_count_entry use_count_buf[5];
|
||||
|
||||
DECLARE_HASHTABLE(sessions_by_up_seid, 6);
|
||||
DECLARE_HASHTABLE(sessions_by_cp_seid, 6);
|
||||
};
|
||||
|
||||
struct up_peer *up_peer_find_or_add(struct up_endpoint *up_ep, const struct osmo_sockaddr *remote_addr);
|
||||
struct up_peer *up_peer_find(struct up_endpoint *up_ep, const struct osmo_sockaddr *remote_addr);
|
||||
|
||||
void up_peer_set_msg_ctx(struct up_peer *peer, struct osmo_pfcp_msg *m);
|
||||
|
||||
char *up_peer_remote_addr_str(struct up_peer *peer);
|
||||
|
||||
struct osmo_pfcp_msg *up_peer_init_tx(struct up_peer *peer, struct osmo_pfcp_msg *in_reply_to,
|
||||
enum osmo_pfcp_message_type message_type);
|
||||
|
||||
void up_peer_free(struct up_peer *peer);
|
117
include/osmocom/upf/up_session.h
Normal file
117
include/osmocom/upf/up_session.h
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <osmocom/core/hashtable.h>
|
||||
#include <osmocom/core/use_count.h>
|
||||
|
||||
#include <osmocom/pfcp/pfcp_msg.h>
|
||||
|
||||
struct osmo_fsm_inst;
|
||||
struct osmo_pfcp_msg;
|
||||
struct up_peer;
|
||||
|
||||
enum up_session_fsm_event {
|
||||
UP_SESSION_EV_RX_SESSION_EST_REQ,
|
||||
UP_SESSION_EV_RX_SESSION_MOD_REQ,
|
||||
UP_SESSION_EV_RX_SESSION_DEL_REQ,
|
||||
UP_SESSION_EV_USE_COUNT_ZERO,
|
||||
};
|
||||
|
||||
enum up_session_kind {
|
||||
UP_SESSION_DROP,
|
||||
UP_SESSION_GTP_U_ENDECAPS,
|
||||
UP_SESSION_GTP_U_FORW,
|
||||
};
|
||||
|
||||
struct up_session {
|
||||
struct hlist_node node_by_up_seid;
|
||||
struct hlist_node node_by_cp_seid;
|
||||
|
||||
struct osmo_fsm_inst *fi;
|
||||
struct up_peer *up_peer;
|
||||
|
||||
struct osmo_pfcp_ie_f_seid cp_f_seid;
|
||||
uint64_t up_seid;
|
||||
|
||||
struct osmo_use_count use_count;
|
||||
struct osmo_use_count_entry use_count_buf[8];
|
||||
|
||||
struct llist_head pdrs;
|
||||
struct llist_head fars;
|
||||
struct llist_head chosen_f_teids;
|
||||
|
||||
struct llist_head active_gtp_actions;
|
||||
};
|
||||
|
||||
struct up_session *up_session_find_or_add(struct up_peer *peer, const struct osmo_pfcp_ie_f_seid *cp_f_seid,
|
||||
const struct osmo_pfcp_ie_f_seid *up_f_seid);
|
||||
struct up_session *up_session_find_by_up_seid(struct up_peer *peer, uint64_t up_seid);
|
||||
struct up_session *up_session_find_by_cp_f_seid(struct up_peer *peer, const struct osmo_pfcp_ie_f_seid *cp_f_seid);
|
||||
struct up_session *up_session_find_by_local_teid(struct up_peer *peer, uint32_t teid);
|
||||
|
||||
void up_session_set_msg_ctx(struct up_session *session, struct osmo_pfcp_msg *m);
|
||||
|
||||
char *up_session_gtp_status(struct up_session *session);
|
||||
bool up_session_is_active(struct up_session *session);
|
||||
bool up_session_is_fully_active(struct up_session *session, int *active_p, int *inactive_p);
|
||||
|
||||
int up_session_discard(struct up_session *session);
|
||||
|
||||
int up_session_to_str_buf(char *buf, size_t buflen, struct up_session *session);
|
||||
char *up_session_to_str_c(void *ctx, struct up_session *session);
|
||||
|
||||
struct pdr {
|
||||
struct llist_head entry;
|
||||
struct up_session *session;
|
||||
|
||||
struct osmo_pfcp_ie_create_pdr desc;
|
||||
struct osmo_pfcp_ie_f_teid *local_f_teid;
|
||||
struct osmo_pfcp_ie_f_teid _local_f_teid_buf;
|
||||
|
||||
struct far *far;
|
||||
|
||||
bool rx_decaps;
|
||||
bool forw_encaps;
|
||||
bool forw_to_core;
|
||||
bool forw_from_core;
|
||||
|
||||
struct pdr *reverse_pdr;
|
||||
bool active;
|
||||
|
||||
char *inactive_reason;
|
||||
};
|
||||
|
||||
int pdr_to_str_buf(char *buf, size_t buflen, const struct pdr *pdr);
|
||||
char *pdr_to_str_c(void *ctx, const struct pdr *pdr);
|
||||
|
||||
struct far {
|
||||
struct llist_head entry;
|
||||
struct up_session *session;
|
||||
|
||||
struct osmo_pfcp_ie_create_far desc;
|
||||
bool active;
|
||||
};
|
23
include/osmocom/upf/up_session_to_gtp.c
Normal file
23
include/osmocom/upf/up_session_to_gtp.c
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
@@ -24,12 +24,78 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <osmocom/core/tdef.h>
|
||||
#include <osmocom/core/socket.h>
|
||||
#include <osmocom/core/select.h>
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
|
||||
struct osmo_tdef;
|
||||
struct ctrl_handle;
|
||||
|
||||
struct upf_gtp_dev;
|
||||
|
||||
#define UPF_PFCP_LISTEN_DEFAULT "0.0.0.0"
|
||||
|
||||
extern struct osmo_tdef_group g_upf_tdef_groups[];
|
||||
|
||||
struct pfcp_vty_cfg {
|
||||
char *local_addr;
|
||||
uint16_t local_port;
|
||||
};
|
||||
|
||||
struct gtp_vty_cfg_dev {
|
||||
struct llist_head entry;
|
||||
|
||||
/* If true, osmo-upf creates the GTP device on startup. If false, the GTP device was created by the user, and we
|
||||
* just plug into it. */
|
||||
bool create;
|
||||
|
||||
/* GTP device name as shown by 'ip link', e.g. 'apn0' */
|
||||
char *dev_name;
|
||||
|
||||
/* Which address the GTP device should listen on.
|
||||
* When create == true, the GTP kernel module is created to listen on this address.
|
||||
* Also used to listen for GTP ECHO requests using libgtp. */
|
||||
char *local_addr;
|
||||
};
|
||||
|
||||
struct gtp_vty_cfg {
|
||||
/* list of struct gtp_vty_cfg_dev, GTP devices as in the config file. The actual GTP devices in use are in
|
||||
* g_upf->gtp.devs. */
|
||||
struct llist_head devs;
|
||||
};
|
||||
|
||||
struct g_upf {
|
||||
struct ctrl_handle *ctrl;
|
||||
|
||||
struct {
|
||||
struct pfcp_vty_cfg vty_cfg;
|
||||
struct up_endpoint *ep;
|
||||
} pfcp;
|
||||
struct {
|
||||
/* GTP devices as in osmo-upf.cfg */
|
||||
struct gtp_vty_cfg vty_cfg;
|
||||
|
||||
/* GTP devices actually in use, list of struct upf_gtp_dev. */
|
||||
struct llist_head devs;
|
||||
|
||||
struct mnl_socket *nl;
|
||||
int32_t genl_id;
|
||||
} gtp;
|
||||
};
|
||||
|
||||
extern struct g_upf *g_upf;
|
||||
|
||||
enum upf_log_subsys {
|
||||
DREF,
|
||||
DPEER,
|
||||
DSESSION,
|
||||
DGTP,
|
||||
};
|
||||
|
||||
void g_upf_alloc(void *ctx);
|
||||
void upf_vty_init();
|
||||
int upf_pfcp_listen();
|
||||
|
||||
int upf_gtp_devs_open();
|
||||
void upf_gtp_devs_close();
|
||||
|
85
include/osmocom/upf/upf_gtp.h
Normal file
85
include/osmocom/upf/upf_gtp.h
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <osmocom/core/linuxlist.h>
|
||||
#include <osmocom/core/select.h>
|
||||
#include <osmocom/core/logging.h>
|
||||
|
||||
#define LOG_GTP_DEV(DEV, LEVEL, FMT, ARGS...) \
|
||||
LOGP(DGTP, LEVEL, "%s: " FMT, upf_gtp_dev_to_str_c(OTC_SELECT, (DEV)), ##ARGS)
|
||||
|
||||
#define PORT_GTP0_C 3386
|
||||
#define PORT_GTP0_U 3386
|
||||
|
||||
#define PORT_GTP1_C 2123
|
||||
#define PORT_GTP1_U 2152
|
||||
|
||||
struct upf_gtp_dev {
|
||||
struct llist_head entry;
|
||||
|
||||
/* If true, osmo-upf created this GTP device on startup and will destroy it on program exit. If false, the user
|
||||
* has created the device and osmo-upf will not destroy it. */
|
||||
bool created;
|
||||
|
||||
char *name;
|
||||
struct {
|
||||
bool enabled;
|
||||
struct osmo_sockaddr local_addr;
|
||||
struct osmo_fd ofd;
|
||||
} gtpv0;
|
||||
struct {
|
||||
struct osmo_sockaddr local_addr;
|
||||
struct osmo_fd ofd;
|
||||
} gtpv1;
|
||||
bool sgsn_mode;
|
||||
|
||||
uint32_t ifidx;
|
||||
|
||||
struct llist_head tunnels;
|
||||
};
|
||||
|
||||
struct upf_gtp_tun_desc {
|
||||
uint32_t local_teid;
|
||||
uint32_t remote_teid;
|
||||
struct osmo_sockaddr ue_addr;
|
||||
struct osmo_sockaddr gtp_remote_addr;
|
||||
};
|
||||
|
||||
int upf_gtp_tun_desc_cmp(const struct upf_gtp_tun_desc *a, const struct upf_gtp_tun_desc *b);
|
||||
|
||||
int upf_gtp_genl_open();
|
||||
void upf_gtp_genl_close();
|
||||
|
||||
int upf_gtp_dev_open(const char *name, bool create_gtp_dev, const char *local_addr, bool listen_for_gtpv0,
|
||||
bool sgsn_mode);
|
||||
struct upf_gtp_dev *upf_gtp_dev_find_by_name(const char *name);
|
||||
struct upf_gtp_dev *upf_gtp_dev_first();
|
||||
|
||||
int upf_gtp_dev_tunnel_add(struct upf_gtp_dev *dev, const struct upf_gtp_tun_desc *t);
|
||||
bool upf_gtp_dev_is_tunnel_active(struct upf_gtp_dev *dev, const struct upf_gtp_tun_desc *t);
|
||||
int upf_gtp_dev_tunnel_del(struct upf_gtp_dev *dev, const struct upf_gtp_tun_desc *t);
|
||||
|
||||
int upf_gtp_dev_to_str_buf(char *buf, size_t buflen, const struct upf_gtp_dev *dev);
|
||||
char *upf_gtp_dev_to_str_c(void *ctx, const struct upf_gtp_dev *dev);
|
48
include/osmocom/upf/upf_nft.h
Normal file
48
include/osmocom/upf/upf_nft.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <osmocom/core/socket.h>
|
||||
|
||||
struct upf_nft_tunmap_desc {
|
||||
struct {
|
||||
struct osmo_sockaddr gtp_remote_addr;
|
||||
uint32_t local_teid;
|
||||
uint32_t remote_teid;
|
||||
} access;
|
||||
struct {
|
||||
struct osmo_sockaddr gtp_remote_addr;
|
||||
uint32_t local_teid;
|
||||
uint32_t remote_teid;
|
||||
} core;
|
||||
uint32_t id;
|
||||
};
|
||||
|
||||
int upf_nft_init();
|
||||
int upf_nft_free();
|
||||
|
||||
int upf_nft_tunmap_create(struct upf_nft_tunmap_desc *tunmap);
|
||||
int upf_nft_tunmap_delete(struct upf_nft_tunmap_desc *tunmap);
|
@@ -1,6 +1,7 @@
|
||||
AM_CPPFLAGS = \
|
||||
$(all_includes) \
|
||||
-I$(top_srcdir)/include \
|
||||
-I$(top_builddir)/include \
|
||||
-I$(top_builddir) \
|
||||
$(NULL)
|
||||
|
||||
@@ -9,10 +10,14 @@ AM_CFLAGS = \
|
||||
$(LIBOSMOCORE_CFLAGS) \
|
||||
$(LIBOSMOVTY_CFLAGS) \
|
||||
$(LIBOSMOCTRL_CFLAGS) \
|
||||
$(LIBOSMOGTLV_CFLAGS) \
|
||||
$(LIBOSMOPFCP_CFLAGS) \
|
||||
$(LIBGTPNL_CFLAGS) \
|
||||
$(COVERAGE_CFLAGS) \
|
||||
$(NULL)
|
||||
|
||||
AM_LDFLAGS = \
|
||||
$(LIBGTPNL_LDFLAGS) \
|
||||
$(COVERAGE_LDFLAGS) \
|
||||
$(NULL)
|
||||
|
||||
@@ -22,12 +27,21 @@ bin_PROGRAMS = \
|
||||
|
||||
osmo_upf_SOURCES = \
|
||||
osmo_upf_main.c \
|
||||
up_endpoint.c \
|
||||
up_gtp_action.c \
|
||||
up_peer.c \
|
||||
up_session.c \
|
||||
upf.c \
|
||||
upf_gtp.c \
|
||||
upf_vty.c \
|
||||
$(NULL)
|
||||
|
||||
osmo_upf_LDADD = \
|
||||
$(LIBOSMOCORE_LIBS) \
|
||||
$(LIBOSMOVTY_LIBS) \
|
||||
$(LIBOSMOCTRL_LIBS) \
|
||||
$(LIBOSMOGTLV_LIBS) \
|
||||
$(LIBOSMOPFCP_LIBS) \
|
||||
$(LIBGTPNL_LIBS) \
|
||||
$(COVERAGE_LDFLAGS) \
|
||||
$(NULL)
|
||||
|
@@ -34,11 +34,16 @@
|
||||
#include <osmocom/vty/cpu_sched_vty.h>
|
||||
#include <osmocom/vty/telnet_interface.h>
|
||||
#include <osmocom/vty/ports.h>
|
||||
#include <osmocom/vty/tdef_vty.h>
|
||||
#include <osmocom/ctrl/control_if.h>
|
||||
#include <osmocom/ctrl/control_vty.h>
|
||||
#include <osmocom/ctrl/ports.h>
|
||||
|
||||
#include <osmocom/pfcp/pfcp_endpoint.h>
|
||||
|
||||
#include <osmocom/upf/upf.h>
|
||||
#include <osmocom/upf/up_endpoint.h>
|
||||
#include <osmocom/upf/upf_gtp.h>
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <getopt.h>
|
||||
@@ -48,6 +53,7 @@
|
||||
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
extern void *tall_vty_ctx;
|
||||
|
||||
@@ -207,6 +213,30 @@ static struct vty_app_info upf_vty_app_info = {
|
||||
};
|
||||
|
||||
static const struct log_info_cat upf_default_categories[] = {
|
||||
[DREF] = {
|
||||
.name = "DREF",
|
||||
.description = "Reference Counting",
|
||||
.enabled = 0, .loglevel = LOGL_NOTICE,
|
||||
.color = OSMO_LOGCOLOR_DARKGREY,
|
||||
},
|
||||
[DPEER] = {
|
||||
.name = "DPEER",
|
||||
.description = "PFCP peer association",
|
||||
.enabled = 0, .loglevel = LOGL_NOTICE,
|
||||
.color = OSMO_LOGCOLOR_YELLOW,
|
||||
},
|
||||
[DSESSION] = {
|
||||
.name = "DSESSION",
|
||||
.description = "PFCP sessions",
|
||||
.enabled = 0, .loglevel = LOGL_NOTICE,
|
||||
.color = OSMO_LOGCOLOR_BLUE,
|
||||
},
|
||||
[DGTP] = {
|
||||
.name = "DGTP",
|
||||
.description = "GTP tunneling",
|
||||
.enabled = 0, .loglevel = LOGL_NOTICE,
|
||||
.color = OSMO_LOGCOLOR_PURPLE,
|
||||
},
|
||||
};
|
||||
|
||||
const struct log_info log_info = {
|
||||
@@ -217,19 +247,27 @@ const struct log_info log_info = {
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int rc;
|
||||
void *tall_infra_ctx;
|
||||
|
||||
/* Track the use of talloc NULL memory contexts */
|
||||
talloc_enable_null_tracking();
|
||||
|
||||
osmo_fsm_set_dealloc_ctx(OTC_SELECT);
|
||||
|
||||
tall_upf_ctx = talloc_named_const(NULL, 1, "osmo-upf");
|
||||
tall_infra_ctx = talloc_named_const(NULL, 1, "osmo-upf");
|
||||
tall_upf_ctx = talloc_named_const(tall_infra_ctx, 1, "osmo-upf-main");
|
||||
upf_vty_app_info.tall_ctx = tall_upf_ctx;
|
||||
|
||||
msgb_talloc_ctx_init(tall_upf_ctx, 0);
|
||||
osmo_signal_talloc_ctx_init(tall_upf_ctx);
|
||||
|
||||
osmo_init_logging2(tall_upf_ctx, &log_info);
|
||||
osmo_init_logging2(tall_infra_ctx, &log_info);
|
||||
log_set_print_category_hex(osmo_stderr_target, 0);
|
||||
log_set_print_category(osmo_stderr_target, 1);
|
||||
log_set_print_level(osmo_stderr_target, 1);
|
||||
log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_BASENAME);
|
||||
log_set_print_filename_pos(osmo_stderr_target, LOG_FILENAME_POS_LINE_END);
|
||||
log_set_print_extended_timestamp(osmo_stderr_target, 1);
|
||||
|
||||
osmo_fsm_log_timeouts(true);
|
||||
osmo_fsm_log_addr(true);
|
||||
@@ -246,6 +284,9 @@ int main(int argc, char **argv)
|
||||
osmo_talloc_vty_add_cmds();
|
||||
osmo_cpu_sched_vty_init(tall_upf_ctx);
|
||||
|
||||
upf_vty_init();
|
||||
osmo_tdef_vty_groups_init(CONFIG_NODE, g_upf_tdef_groups);
|
||||
|
||||
/* Parse options */
|
||||
handle_options(argc, argv);
|
||||
|
||||
@@ -283,6 +324,15 @@ int main(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
if (upf_gtp_genl_open())
|
||||
return -1;
|
||||
|
||||
if (upf_gtp_devs_open())
|
||||
return -1;
|
||||
|
||||
if (upf_pfcp_listen())
|
||||
return -1;
|
||||
|
||||
do {
|
||||
log_reset_context();
|
||||
osmo_select_main_ctx(0);
|
||||
@@ -301,12 +351,20 @@ int main(int argc, char **argv)
|
||||
}
|
||||
} while (!osmo_select_shutdown_done());
|
||||
|
||||
log_fini();
|
||||
up_endpoint_free(&g_upf->pfcp.ep);
|
||||
upf_gtp_devs_close();
|
||||
|
||||
upf_gtp_genl_close();
|
||||
|
||||
/* Report the heap state of talloc contexts, then free, so both ASAN and Valgrind are happy... */
|
||||
talloc_report_full(tall_upf_ctx, stderr);
|
||||
talloc_free(tall_upf_ctx);
|
||||
|
||||
log_fini();
|
||||
|
||||
talloc_report_full(tall_infra_ctx, stderr);
|
||||
talloc_free(tall_infra_ctx);
|
||||
|
||||
talloc_report_full(tall_vty_ctx, stderr);
|
||||
talloc_free(tall_vty_ctx);
|
||||
|
||||
|
322
src/osmo-upf/up_endpoint.c
Normal file
322
src/osmo-upf/up_endpoint.c
Normal file
@@ -0,0 +1,322 @@
|
||||
/*
|
||||
* (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <osmocom/pfcp/pfcp_endpoint.h>
|
||||
#include <osmocom/pfcp/pfcp_msg.h>
|
||||
|
||||
#include <osmocom/upf/up_endpoint.h>
|
||||
#include <osmocom/upf/up_peer.h>
|
||||
#include <osmocom/upf/up_session.h>
|
||||
|
||||
static void up_endpoint_set_msg_ctx(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m, struct osmo_pfcp_msg *req)
|
||||
{
|
||||
struct up_endpoint *up_ep = ep->priv;
|
||||
struct up_peer *peer;
|
||||
|
||||
/* If this is a response to an earlier request, just take the msg context from the request message. */
|
||||
if (req) {
|
||||
if (!m->ctx.peer_fi && req->ctx.peer_fi)
|
||||
up_peer_set_msg_ctx(req->ctx.peer_fi->priv, m);
|
||||
if (!m->ctx.session_fi && req->ctx.session_fi)
|
||||
up_session_set_msg_ctx(req->ctx.peer_fi->priv, m);
|
||||
}
|
||||
|
||||
/* From the remote address, find the matching peer instance */
|
||||
if (!m->ctx.peer_fi) {
|
||||
peer = up_peer_find(up_ep, &m->remote_addr);
|
||||
if (peer) {
|
||||
up_peer_set_msg_ctx(peer, m);
|
||||
}
|
||||
} else {
|
||||
peer = m->ctx.peer_fi->priv;
|
||||
}
|
||||
|
||||
/* Find a session, if the header is parsed yet and contains a SEID */
|
||||
if (peer && !m->ctx.session_fi && m->h.seid_present) {
|
||||
struct up_session *session;
|
||||
session = up_session_find_by_up_seid(peer, m->h.seid);
|
||||
if (session) {
|
||||
up_session_set_msg_ctx(session, m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void up_ep_rx_not_impl_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m,
|
||||
enum osmo_pfcp_message_type resp_msgt,
|
||||
const struct osmo_pfcp_ie_node_id *node_id, enum osmo_pfcp_cause cause)
|
||||
{
|
||||
struct osmo_pfcp_msg *tx;
|
||||
enum osmo_pfcp_cause *tx_cause;
|
||||
OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "message type not implemented\n");
|
||||
tx = osmo_pfcp_msg_alloc_tx(OTC_SELECT, NULL, &up_ep->pfcp_ep->cfg.local_node_id, m, resp_msgt);
|
||||
tx_cause = osmo_pfcp_msg_cause(tx);
|
||||
if (tx_cause)
|
||||
*tx_cause = cause;
|
||||
osmo_pfcp_endpoint_tx(up_ep->pfcp_ep, tx);
|
||||
}
|
||||
|
||||
static void up_ep_rx_pfd_mgmt_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
|
||||
{
|
||||
up_ep_rx_not_impl_req(up_ep, m, OSMO_PFCP_MSGT_PFD_MGMT_RESP, NULL, OSMO_PFCP_CAUSE_SERVICE_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
static void up_ep_rx_assoc_setup_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
|
||||
{
|
||||
struct up_peer *peer = m->ctx.peer_fi ? m->ctx.peer_fi->priv : NULL;
|
||||
if (!peer) {
|
||||
peer = up_peer_find_or_add(up_ep, &m->remote_addr);
|
||||
OSMO_ASSERT(peer);
|
||||
}
|
||||
osmo_fsm_inst_dispatch(peer->fi, UP_PEER_EV_RX_ASSOC_SETUP_REQ, (void *)m);
|
||||
}
|
||||
|
||||
static void up_ep_rx_assoc_upd_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
|
||||
{
|
||||
if (!m->ctx.peer_fi) {
|
||||
struct osmo_pfcp_msg *tx;
|
||||
OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Peer is not associated, cannot update association\n");
|
||||
tx = osmo_pfcp_msg_alloc_tx(OTC_SELECT, NULL, &up_ep->pfcp_ep->cfg.local_node_id, m, OSMO_PFCP_MSGT_ASSOC_UPDATE_RESP);
|
||||
/* FIXME set node_id, cause */
|
||||
osmo_pfcp_endpoint_tx(up_ep->pfcp_ep, tx);
|
||||
return;
|
||||
}
|
||||
osmo_fsm_inst_dispatch(m->ctx.peer_fi, UP_PEER_EV_RX_ASSOC_UPD_REQ, (void *)m);
|
||||
}
|
||||
|
||||
static void up_ep_rx_assoc_rel_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
|
||||
{
|
||||
if (!m->ctx.peer_fi) {
|
||||
struct osmo_pfcp_msg *tx;
|
||||
OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Peer is not associated. Sending ACK response anyway\n");
|
||||
tx = osmo_pfcp_msg_alloc_tx(OTC_SELECT, NULL, &up_ep->pfcp_ep->cfg.local_node_id, m, OSMO_PFCP_MSGT_ASSOC_RELEASE_RESP);
|
||||
/* FIXME set node_id, cause */
|
||||
osmo_pfcp_endpoint_tx(up_ep->pfcp_ep, tx);
|
||||
return;
|
||||
}
|
||||
osmo_fsm_inst_dispatch(m->ctx.peer_fi, UP_PEER_EV_RX_ASSOC_REL_REQ, (void *)m);
|
||||
}
|
||||
|
||||
static void up_ep_rx_node_report_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
|
||||
{
|
||||
up_ep_rx_not_impl_req(up_ep, m, OSMO_PFCP_MSGT_NODE_REPORT_RESP, NULL /* FIXME? */,
|
||||
OSMO_PFCP_CAUSE_SERVICE_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
static void up_ep_rx_session_set_del_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
|
||||
{
|
||||
up_ep_rx_not_impl_req(up_ep, m, OSMO_PFCP_MSGT_SESSION_SET_DEL_RESP,
|
||||
&up_ep->pfcp_ep->cfg.local_node_id, OSMO_PFCP_CAUSE_SERVICE_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
static void up_ep_rx_session_est_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
|
||||
{
|
||||
if (!m->ctx.peer_fi) {
|
||||
struct osmo_pfcp_msg *tx;
|
||||
OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Peer is not associated, cannot establish session\n");
|
||||
tx = osmo_pfcp_msg_alloc_tx(OTC_SELECT, NULL, &up_ep->pfcp_ep->cfg.local_node_id, m, OSMO_PFCP_MSGT_SESSION_EST_RESP);
|
||||
tx->ies.session_est_resp.cause = OSMO_PFCP_CAUSE_NO_ESTABLISHED_PFCP_ASSOC;
|
||||
osmo_pfcp_endpoint_tx(up_ep->pfcp_ep, tx);
|
||||
return;
|
||||
}
|
||||
osmo_fsm_inst_dispatch(m->ctx.peer_fi, UP_PEER_EV_RX_SESSION_EST_REQ, (void *)m);
|
||||
}
|
||||
|
||||
static void up_ep_rx_session_mod_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
|
||||
{
|
||||
if (!m->ctx.session_fi) {
|
||||
/* Session not found. */
|
||||
struct osmo_pfcp_msg *tx;
|
||||
tx = osmo_pfcp_msg_alloc_tx(OTC_SELECT, NULL, &up_ep->pfcp_ep->cfg.local_node_id, m,
|
||||
OSMO_PFCP_MSGT_SESSION_MOD_RESP);
|
||||
if (!m->ctx.peer_fi) {
|
||||
/* Not even the peer is associated. */
|
||||
OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Peer is not associated, cannot modify session\n");
|
||||
tx->ies.session_mod_resp.cause = OSMO_PFCP_CAUSE_NO_ESTABLISHED_PFCP_ASSOC;
|
||||
} else {
|
||||
OSMO_LOG_PFCP_MSG(m, LOGL_ERROR,
|
||||
"No established session with SEID=0x%"PRIx64", cannot modify\n",
|
||||
m->h.seid);
|
||||
tx->ies.session_mod_resp.cause = OSMO_PFCP_CAUSE_SESSION_CTX_NOT_FOUND;
|
||||
}
|
||||
osmo_pfcp_endpoint_tx(up_ep->pfcp_ep, tx);
|
||||
return;
|
||||
}
|
||||
osmo_fsm_inst_dispatch(m->ctx.session_fi, UP_SESSION_EV_RX_SESSION_MOD_REQ, (void *)m);
|
||||
}
|
||||
|
||||
static void up_ep_rx_session_del_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
|
||||
{
|
||||
if (!m->ctx.session_fi) {
|
||||
/* Session not found. */
|
||||
struct osmo_pfcp_msg *tx;
|
||||
tx = osmo_pfcp_msg_alloc_tx(OTC_SELECT, NULL, &up_ep->pfcp_ep->cfg.local_node_id, m, OSMO_PFCP_MSGT_SESSION_DEL_RESP);
|
||||
if (!m->ctx.peer_fi) {
|
||||
/* Not even the peer is associated. */
|
||||
OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Peer is not associated, cannot delete session\n");
|
||||
tx->ies.session_del_resp.cause = OSMO_PFCP_CAUSE_NO_ESTABLISHED_PFCP_ASSOC;
|
||||
} else {
|
||||
OSMO_LOG_PFCP_MSG(m, LOGL_ERROR,
|
||||
"No established session with SEID=0x%"PRIx64", cannot delete\n",
|
||||
m->h.seid);
|
||||
tx->ies.session_del_resp.cause = OSMO_PFCP_CAUSE_SESSION_CTX_NOT_FOUND;
|
||||
}
|
||||
osmo_pfcp_endpoint_tx(up_ep->pfcp_ep, tx);
|
||||
return;
|
||||
}
|
||||
osmo_fsm_inst_dispatch(m->ctx.session_fi, UP_SESSION_EV_RX_SESSION_DEL_REQ, (void *)m);
|
||||
}
|
||||
|
||||
static void up_ep_rx_session_rep_req(struct up_endpoint *up_ep, const struct osmo_pfcp_msg *m)
|
||||
{
|
||||
up_ep_rx_not_impl_req(up_ep, m, OSMO_PFCP_MSGT_SESSION_REP_RESP, NULL, OSMO_PFCP_CAUSE_SERVICE_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
static void up_endpoint_rx_cb(struct osmo_pfcp_endpoint *ep, struct osmo_pfcp_msg *m, struct osmo_pfcp_msg *req)
|
||||
{
|
||||
struct up_endpoint *up_ep = ep->priv;
|
||||
|
||||
switch (m->h.message_type) {
|
||||
case OSMO_PFCP_MSGT_PFD_MGMT_REQ:
|
||||
up_ep_rx_pfd_mgmt_req(up_ep, m);
|
||||
return;
|
||||
case OSMO_PFCP_MSGT_ASSOC_SETUP_REQ:
|
||||
up_ep_rx_assoc_setup_req(up_ep, m);
|
||||
return;
|
||||
case OSMO_PFCP_MSGT_ASSOC_UPDATE_REQ:
|
||||
up_ep_rx_assoc_upd_req(up_ep, m);
|
||||
return;
|
||||
case OSMO_PFCP_MSGT_ASSOC_RELEASE_REQ:
|
||||
up_ep_rx_assoc_rel_req(up_ep, m);
|
||||
return;
|
||||
case OSMO_PFCP_MSGT_NODE_REPORT_REQ:
|
||||
up_ep_rx_node_report_req(up_ep, m);
|
||||
return;
|
||||
case OSMO_PFCP_MSGT_SESSION_SET_DEL_REQ:
|
||||
up_ep_rx_session_set_del_req(up_ep, m);
|
||||
return;
|
||||
case OSMO_PFCP_MSGT_SESSION_EST_REQ:
|
||||
up_ep_rx_session_est_req(up_ep, m);
|
||||
return;
|
||||
case OSMO_PFCP_MSGT_SESSION_MOD_REQ:
|
||||
up_ep_rx_session_mod_req(up_ep, m);
|
||||
return;
|
||||
case OSMO_PFCP_MSGT_SESSION_DEL_REQ:
|
||||
up_ep_rx_session_del_req(up_ep, m);
|
||||
return;
|
||||
case OSMO_PFCP_MSGT_SESSION_REP_REQ:
|
||||
up_ep_rx_session_rep_req(up_ep, m);
|
||||
return;
|
||||
default:
|
||||
OSMO_LOG_PFCP_MSG(m, LOGL_ERROR, "Unknown message type\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
struct up_endpoint *up_endpoint_init(void *ctx, const struct osmo_sockaddr *local_addr)
|
||||
{
|
||||
int rc;
|
||||
struct up_endpoint *up_ep;
|
||||
up_ep = talloc_zero(ctx, struct up_endpoint);
|
||||
INIT_LLIST_HEAD(&up_ep->peers);
|
||||
|
||||
up_ep->pfcp_ep = osmo_pfcp_endpoint_create(up_ep, up_ep);
|
||||
up_ep->pfcp_ep->cfg.local_addr = *local_addr;
|
||||
|
||||
up_ep->pfcp_ep->set_msg_ctx = up_endpoint_set_msg_ctx;
|
||||
up_ep->pfcp_ep->rx_msg = up_endpoint_rx_cb;
|
||||
|
||||
osmo_pfcp_ie_node_id_from_osmo_sockaddr(&up_ep->pfcp_ep->cfg.local_node_id, local_addr);
|
||||
|
||||
rc = osmo_pfcp_endpoint_bind(up_ep->pfcp_ep);
|
||||
if (rc) {
|
||||
talloc_free(up_ep);
|
||||
return NULL;
|
||||
}
|
||||
return up_ep;
|
||||
}
|
||||
|
||||
static struct up_session *up_endpoint_find_session(struct up_endpoint *ep, uint64_t up_seid)
|
||||
{
|
||||
struct up_peer *peer;
|
||||
llist_for_each_entry(peer, &ep->peers, entry) {
|
||||
struct up_session *session = up_session_find_by_up_seid(peer, up_seid);
|
||||
if (session)
|
||||
return session;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct up_session *up_endpoint_find_session_by_local_teid(struct up_endpoint *ep, uint32_t teid)
|
||||
{
|
||||
struct up_peer *peer;
|
||||
llist_for_each_entry(peer, &ep->peers, entry) {
|
||||
struct up_session *session = up_session_find_by_local_teid(peer, teid);
|
||||
if (session)
|
||||
return session;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint64_t up_endpoint_next_seid(struct up_endpoint *ep)
|
||||
{
|
||||
uint64_t sanity;
|
||||
for (sanity = 2342; sanity; sanity--) {
|
||||
uint64_t next_seid = osmo_pfcp_next_seid(&ep->next_seid_state);
|
||||
if (up_endpoint_find_session(ep, next_seid))
|
||||
continue;
|
||||
return next_seid;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint32_t up_endpoint_inc_teid(struct up_endpoint *ep)
|
||||
{
|
||||
ep->next_teid_state++;
|
||||
if (!ep->next_teid_state)
|
||||
ep->next_teid_state++;
|
||||
return ep->next_teid_state;
|
||||
}
|
||||
|
||||
uint32_t up_endpoint_next_teid(struct up_endpoint *ep)
|
||||
{
|
||||
uint32_t sanity;
|
||||
for (sanity = 2342; sanity; sanity--) {
|
||||
uint32_t next_teid = up_endpoint_inc_teid(ep);
|
||||
if (up_endpoint_find_session_by_local_teid(ep, next_teid))
|
||||
continue;
|
||||
return next_teid;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void up_endpoint_free(struct up_endpoint **_ep)
|
||||
{
|
||||
struct up_peer *peer;
|
||||
struct up_endpoint *ep = *_ep;
|
||||
|
||||
while ((peer = llist_first_entry_or_null(&ep->peers, struct up_peer, entry)))
|
||||
up_peer_free(peer);
|
||||
|
||||
osmo_pfcp_endpoint_free(&ep->pfcp_ep);
|
||||
*_ep = NULL;
|
||||
}
|
162
src/osmo-upf/up_gtp_action.c
Normal file
162
src/osmo-upf/up_gtp_action.c
Normal file
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <osmocom/core/utils.h>
|
||||
|
||||
#include <osmocom/upf/upf.h>
|
||||
#include <osmocom/upf/up_gtp_action.h>
|
||||
#include <osmocom/upf/up_peer.h>
|
||||
#include <osmocom/upf/up_session.h>
|
||||
|
||||
int up_gtp_action_cmp(const struct up_gtp_action *a, const struct up_gtp_action *b)
|
||||
{
|
||||
int cmp;
|
||||
if (a == b)
|
||||
return 0;
|
||||
if (!a)
|
||||
return -1;
|
||||
if (!b)
|
||||
return 1;
|
||||
|
||||
#define CMP_MEMB(MEMB) OSMO_CMP(a->MEMB, b->MEMB)
|
||||
|
||||
if ((cmp = CMP_MEMB(kind)))
|
||||
return cmp;
|
||||
|
||||
switch (a->kind) {
|
||||
case UP_GTP_U_ENDECAPS:
|
||||
if ((cmp = CMP_MEMB(endecaps.local_teid)))
|
||||
return cmp;
|
||||
if ((cmp = CMP_MEMB(endecaps.remote_teid)))
|
||||
return cmp;
|
||||
cmp = osmo_sockaddr_cmp(&a->endecaps.gtp_remote_addr, &b->endecaps.gtp_remote_addr);
|
||||
if (cmp)
|
||||
return cmp;
|
||||
cmp = osmo_sockaddr_cmp(&a->endecaps.ue_addr, &b->endecaps.ue_addr);
|
||||
if (cmp)
|
||||
return cmp;
|
||||
break;
|
||||
|
||||
case UP_GTP_U_TUNMAP:
|
||||
if ((cmp = CMP_MEMB(tunmap.access.local_teid)))
|
||||
return cmp;
|
||||
if ((cmp = CMP_MEMB(tunmap.access.remote_teid)))
|
||||
return cmp;
|
||||
if ((cmp = CMP_MEMB(tunmap.core.local_teid)))
|
||||
return cmp;
|
||||
if ((cmp = CMP_MEMB(tunmap.core.remote_teid)))
|
||||
return cmp;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int up_gtp_action_enable_disable(struct up_gtp_action *a, bool enable)
|
||||
{
|
||||
struct upf_gtp_dev *gtp_dev;
|
||||
int rc;
|
||||
|
||||
switch (a->kind) {
|
||||
case UP_GTP_U_ENDECAPS:
|
||||
/* use the first available GTP device.
|
||||
* TODO: select by interface name?
|
||||
*/
|
||||
gtp_dev = upf_gtp_dev_first();
|
||||
if (!gtp_dev) {
|
||||
LOG_UP_GTP_ACTION(a, LOGL_ERROR, "No GTP device open, cannot %s\n", enable ? "enable" : "disable");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (enable)
|
||||
rc = upf_gtp_dev_tunnel_add(gtp_dev, &a->endecaps);
|
||||
else
|
||||
rc = upf_gtp_dev_tunnel_del(gtp_dev, &a->endecaps);
|
||||
if (rc) {
|
||||
LOG_UP_GTP_ACTION(a, LOGL_ERROR, "Failed to %s GTP tunnel: %d %s\n",
|
||||
enable ? "enable" : "disable", rc, strerror(-rc));
|
||||
return rc;
|
||||
}
|
||||
LOG_UP_GTP_ACTION(a, LOGL_NOTICE, "%s GTP tunnel\n", enable ? "Enabled" : "Disabled");
|
||||
return 0;
|
||||
case UP_GTP_U_TUNMAP:
|
||||
LOG_UP_GTP_ACTION(a, LOGL_ERROR, "TEID translation not yet implemented\n");
|
||||
return -ENOTSUP;
|
||||
default:
|
||||
LOG_UP_GTP_ACTION(a, LOGL_ERROR, "Invalid action\n");
|
||||
return -ENOTSUP;
|
||||
}
|
||||
}
|
||||
|
||||
int up_gtp_action_enable(struct up_gtp_action *a)
|
||||
{
|
||||
return up_gtp_action_enable_disable(a, true);
|
||||
}
|
||||
|
||||
int up_gtp_action_disable(struct up_gtp_action *a)
|
||||
{
|
||||
return up_gtp_action_enable_disable(a, false);
|
||||
}
|
||||
|
||||
int up_gtp_action_to_str_buf(char *buf, size_t buflen, const struct up_gtp_action *a)
|
||||
{
|
||||
struct osmo_strbuf sb = { .buf = buf, .len = buflen };
|
||||
switch (a->kind) {
|
||||
case UP_GTP_U_ENDECAPS:
|
||||
OSMO_STRBUF_PRINTF(sb, "GTP:endecaps GTP-access:");
|
||||
OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &a->endecaps.gtp_remote_addr);
|
||||
OSMO_STRBUF_PRINTF(sb, " TEID-r:0x%"PRIx32" TEID-l:0x%"PRIx32" IP-core:",
|
||||
a->endecaps.remote_teid, a->endecaps.local_teid);
|
||||
OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &a->endecaps.ue_addr);
|
||||
break;
|
||||
case UP_GTP_U_TUNMAP:
|
||||
OSMO_STRBUF_PRINTF(sb, "GTP:tunmap GTP-access:");
|
||||
OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &a->tunmap.access.gtp_remote_addr);
|
||||
OSMO_STRBUF_PRINTF(sb, " TEID-access-r:0x%"PRIx32" TEID-access-l:0x%"PRIx32" GTP-core:",
|
||||
a->tunmap.access.remote_teid, a->tunmap.access.local_teid);
|
||||
OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &a->tunmap.core.gtp_remote_addr);
|
||||
OSMO_STRBUF_PRINTF(sb, " TEID-core-r:0x%"PRIx32" TEID-core-l:0x%"PRIx32,
|
||||
a->tunmap.core.remote_teid, a->tunmap.core.local_teid);
|
||||
break;
|
||||
case UP_GTP_DROP:
|
||||
OSMO_STRBUF_PRINTF(sb, "GTP:drop");
|
||||
break;
|
||||
default:
|
||||
OSMO_STRBUF_PRINTF(sb, "GTP:?");
|
||||
break;
|
||||
}
|
||||
if (a->session)
|
||||
OSMO_STRBUF_PRINTF(sb, " PFCP-peer:%s SEID-l:0x%"PRIx64" PDR:%d,%d",
|
||||
up_peer_remote_addr_str(a->session->up_peer),
|
||||
a->session->up_seid, a->pdr_core, a->pdr_access);
|
||||
return sb.chars_needed;
|
||||
}
|
||||
|
||||
char *up_gtp_action_to_str_c(void *ctx, const struct up_gtp_action *a)
|
||||
{
|
||||
OSMO_NAME_C_IMPL(ctx, 128, "ERROR", up_gtp_action_to_str_buf, a)
|
||||
}
|
545
src/osmo-upf/up_peer.c
Normal file
545
src/osmo-upf/up_peer.c
Normal file
@@ -0,0 +1,545 @@
|
||||
/*
|
||||
* (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include <osmocom/core/fsm.h>
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/core/tdef.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
|
||||
#include <osmocom/pfcp/pfcp_msg.h>
|
||||
#include <osmocom/pfcp/pfcp_endpoint.h>
|
||||
|
||||
#include <osmocom/upf/upf.h>
|
||||
#include <osmocom/upf/up_peer.h>
|
||||
#include <osmocom/upf/up_endpoint.h>
|
||||
#include <osmocom/upf/up_session.h>
|
||||
|
||||
enum up_peer_fsm_state {
|
||||
UP_PEER_ST_NOT_ASSOCIATED,
|
||||
UP_PEER_ST_ASSOCIATED,
|
||||
UP_PEER_ST_GRACEFUL_RELEASE,
|
||||
UP_PEER_ST_WAIT_USE_COUNT,
|
||||
};
|
||||
|
||||
static const struct value_string up_peer_fsm_event_names[] = {
|
||||
OSMO_VALUE_STRING(UP_PEER_EV_RX_ASSOC_SETUP_REQ),
|
||||
OSMO_VALUE_STRING(UP_PEER_EV_RX_ASSOC_UPD_REQ),
|
||||
OSMO_VALUE_STRING(UP_PEER_EV_RX_ASSOC_REL_REQ),
|
||||
OSMO_VALUE_STRING(UP_PEER_EV_RX_SESSION_EST_REQ),
|
||||
OSMO_VALUE_STRING(UP_PEER_EV_HEARTBEAT_FAILURE),
|
||||
OSMO_VALUE_STRING(UP_PEER_EV_USE_COUNT_ZERO),
|
||||
OSMO_VALUE_STRING(UP_PEER_EV_SESSION_TERM),
|
||||
{}
|
||||
};
|
||||
|
||||
static struct osmo_fsm up_peer_fsm;
|
||||
|
||||
static const struct osmo_tdef_state_timeout up_peer_fsm_timeouts[32] = {
|
||||
[UP_PEER_ST_GRACEFUL_RELEASE] = { .T = -21 },
|
||||
};
|
||||
|
||||
/* Transition to a state, using the T timer defined in up_peer_fsm_timeouts.
|
||||
* Assumes local variable fi exists. */
|
||||
#define up_peer_fsm_state_chg(state) \
|
||||
osmo_tdef_fsm_inst_state_chg(fi, state, \
|
||||
up_peer_fsm_timeouts, \
|
||||
osmo_pfcp_tdefs, \
|
||||
5)
|
||||
|
||||
static int up_peer_use_cb(struct osmo_use_count_entry *e, int32_t old_use_count, const char *file, int line)
|
||||
{
|
||||
struct up_peer *peer = e->use_count->talloc_object;
|
||||
int32_t total;
|
||||
int level;
|
||||
|
||||
if (!e->use)
|
||||
return -EINVAL;
|
||||
|
||||
total = osmo_use_count_total(&peer->use_count);
|
||||
|
||||
if (total == 0
|
||||
|| (total == 1 && old_use_count == 0 && e->count == 1))
|
||||
level = LOGL_INFO;
|
||||
else
|
||||
level = LOGL_DEBUG;
|
||||
|
||||
LOGPFSMSLSRC(peer->fi, DREF, level, file, line,
|
||||
"%s %s: now used by %s\n",
|
||||
(e->count - old_use_count) > 0 ? "+" : "-", e->use,
|
||||
osmo_use_count_to_str_c(OTC_SELECT, &peer->use_count));
|
||||
|
||||
if (e->count < 0)
|
||||
return -ERANGE;
|
||||
|
||||
if (total == 0)
|
||||
osmo_fsm_inst_dispatch(peer->fi, UP_PEER_EV_USE_COUNT_ZERO, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *up_peer_remote_addr_str(struct up_peer *peer)
|
||||
{
|
||||
struct osmo_sockaddr remote_addr = peer->remote_addr;
|
||||
|
||||
/* Zero the port, it is not interesting information. The port for PFCP is defined fixed, and there is no use
|
||||
* printing it in the logs */
|
||||
osmo_sockaddr_set_port(&remote_addr.u.sa, 0);
|
||||
|
||||
return osmo_sockaddr_to_str_c(OTC_SELECT, &remote_addr);
|
||||
}
|
||||
|
||||
static void up_peer_update_id(struct up_peer *peer)
|
||||
{
|
||||
osmo_fsm_inst_update_id_f_sanitize(peer->fi, '-', "%s", up_peer_remote_addr_str(peer));
|
||||
LOGPFSML(peer->fi, LOGL_DEBUG, "Updated id\n");
|
||||
}
|
||||
|
||||
static struct up_peer *up_peer_add(struct up_endpoint *up_endpoint, const struct osmo_sockaddr *remote_addr)
|
||||
{
|
||||
struct up_peer *peer;
|
||||
|
||||
struct osmo_fsm_inst *fi = osmo_fsm_inst_alloc(&up_peer_fsm, up_endpoint, NULL, LOGL_DEBUG, NULL);
|
||||
OSMO_ASSERT(fi);
|
||||
|
||||
peer = talloc(fi, struct up_peer);
|
||||
OSMO_ASSERT(peer);
|
||||
fi->priv = peer;
|
||||
|
||||
*peer = (struct up_peer) {
|
||||
.fi = fi,
|
||||
.up_endpoint = up_endpoint,
|
||||
.remote_addr = *remote_addr,
|
||||
.heartbeat_fi = NULL /* FIXME */,
|
||||
.use_count = {
|
||||
.talloc_object = peer,
|
||||
.use_cb = up_peer_use_cb,
|
||||
},
|
||||
};
|
||||
osmo_use_count_make_static_entries(&peer->use_count, peer->use_count_buf, ARRAY_SIZE(peer->use_count_buf));
|
||||
hash_init(peer->sessions_by_up_seid);
|
||||
hash_init(peer->sessions_by_cp_seid);
|
||||
|
||||
osmo_pfcp_bits_set(peer->local_up_features.bits, OSMO_PFCP_UP_FEAT_BUNDL, true);
|
||||
osmo_pfcp_bits_set(peer->local_up_features.bits, OSMO_PFCP_UP_FEAT_RTTL, true);
|
||||
osmo_pfcp_bits_set(peer->local_up_features.bits, OSMO_PFCP_UP_FEAT_FTUP, true);
|
||||
|
||||
up_peer_update_id(peer);
|
||||
|
||||
llist_add(&peer->entry, &up_endpoint->peers);
|
||||
return peer;
|
||||
}
|
||||
|
||||
struct up_peer *up_peer_find(struct up_endpoint *up_endpoint, const struct osmo_sockaddr *remote_addr)
|
||||
{
|
||||
struct up_peer *peer;
|
||||
llist_for_each_entry(peer, &up_endpoint->peers, entry) {
|
||||
if (osmo_sockaddr_cmp(&peer->remote_addr, remote_addr))
|
||||
continue;
|
||||
return peer;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct up_peer *up_peer_find_or_add(struct up_endpoint *up_endpoint, const struct osmo_sockaddr *remote_addr)
|
||||
{
|
||||
struct up_peer *peer = up_peer_find(up_endpoint, remote_addr);
|
||||
if (peer)
|
||||
return peer;
|
||||
return up_peer_add(up_endpoint, remote_addr);
|
||||
}
|
||||
|
||||
int up_peer_tx(struct up_peer *peer, struct osmo_pfcp_msg *m)
|
||||
{
|
||||
return osmo_pfcp_endpoint_tx(peer->up_endpoint->pfcp_ep, m);
|
||||
}
|
||||
|
||||
static int up_peer_fsm_timer_cb(struct osmo_fsm_inst *fi)
|
||||
{
|
||||
//struct up_peer *peer = fi->priv;
|
||||
/* Return 1 to terminate FSM instance, 0 to keep running */
|
||||
return 1;
|
||||
}
|
||||
|
||||
void up_peer_set_msg_ctx(struct up_peer *peer, struct osmo_pfcp_msg *m)
|
||||
{
|
||||
OSMO_ASSERT(!m->ctx.peer_fi);
|
||||
|
||||
m->ctx.peer_fi = peer->fi;
|
||||
m->ctx.peer_use_count = &peer->use_count;
|
||||
m->ctx.peer_use_token = (m->rx ? UP_USE_MSG_RX : UP_USE_MSG_TX);
|
||||
osmo_use_count_get_put(m->ctx.peer_use_count, m->ctx.peer_use_token, 1);
|
||||
}
|
||||
|
||||
struct osmo_pfcp_msg *up_peer_init_tx(struct up_peer *peer, struct osmo_pfcp_msg *in_reply_to,
|
||||
enum osmo_pfcp_message_type message_type)
|
||||
{
|
||||
struct osmo_pfcp_msg *tx = osmo_pfcp_msg_alloc_tx(OTC_SELECT, &peer->remote_addr,
|
||||
&peer->up_endpoint->pfcp_ep->cfg.local_node_id,
|
||||
in_reply_to, message_type);
|
||||
up_peer_set_msg_ctx(peer, tx);
|
||||
return tx;
|
||||
}
|
||||
|
||||
static int up_peer_tx_assoc_setup_resp(struct up_peer *peer, struct osmo_pfcp_msg *m, enum osmo_pfcp_cause cause)
|
||||
{
|
||||
struct osmo_pfcp_msg *resp;
|
||||
|
||||
resp = up_peer_init_tx(peer, m, OSMO_PFCP_MSGT_ASSOC_SETUP_RESP);
|
||||
|
||||
resp->ies.assoc_setup_resp = (struct osmo_pfcp_msg_assoc_setup_resp) {
|
||||
.cause = cause,
|
||||
.recovery_time_stamp = g_upf->pfcp.ep->pfcp_ep->recovery_time_stamp,
|
||||
.up_function_features_present = true,
|
||||
.up_function_features = peer->local_up_features,
|
||||
};
|
||||
resp->ies.assoc_setup_resp.recovery_time_stamp = g_upf->pfcp.ep->pfcp_ep->recovery_time_stamp;
|
||||
|
||||
if (osmo_pfcp_endpoint_tx(peer->up_endpoint->pfcp_ep, resp)) {
|
||||
OSMO_LOG_PFCP_MSG(resp, LOGL_ERROR, "Error sending response, cannot associate with peer\n");
|
||||
return -EIO;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int up_peer_tx_assoc_rel_resp(struct up_peer *peer, struct osmo_pfcp_msg *m, enum osmo_pfcp_cause cause)
|
||||
{
|
||||
struct osmo_pfcp_msg *resp;
|
||||
|
||||
resp = up_peer_init_tx(peer, m, OSMO_PFCP_MSGT_ASSOC_RELEASE_RESP);
|
||||
|
||||
resp->ies.assoc_release_resp = (struct osmo_pfcp_msg_assoc_release_resp) {
|
||||
.cause = cause,
|
||||
};
|
||||
|
||||
if (osmo_pfcp_endpoint_tx(peer->up_endpoint->pfcp_ep, resp)) {
|
||||
OSMO_LOG_PFCP_MSG(resp, LOGL_ERROR, "Error sending response\n");
|
||||
return -EIO;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void up_peer_clear_sessions(struct up_peer *peer)
|
||||
{
|
||||
struct up_session *session;
|
||||
int bkt;
|
||||
struct hlist_node *tmp;
|
||||
int count = 0;
|
||||
hash_for_each_safe(peer->sessions_by_up_seid, bkt, tmp, session, node_by_up_seid) {
|
||||
count += up_session_discard(session);
|
||||
}
|
||||
if (count)
|
||||
LOGPFSML(peer->fi, LOGL_NOTICE, "terminated %d sessions\n", count);
|
||||
}
|
||||
|
||||
static void up_peer_rx_assoc_setup_req(struct up_peer *peer, struct osmo_pfcp_msg *m)
|
||||
{
|
||||
struct osmo_fsm_inst *fi = peer->fi;
|
||||
enum osmo_pfcp_cause cause = OSMO_PFCP_CAUSE_REQUEST_ACCEPTED;
|
||||
|
||||
if (fi->state == UP_PEER_ST_ASSOCIATED) {
|
||||
/* Retransmissions of the ACK response happen in pfcp_endpoint.c. So if we get this, it is a genuine
|
||||
* duplicate association setup request. We could reject it. But why. Just "replace" with the new
|
||||
* association. Continue. */
|
||||
/* If the peer has restarted, it has forgotten about all sessions. */
|
||||
if (peer->remote_recovery_timestamp != m->ies.assoc_setup_req.recovery_time_stamp) {
|
||||
LOGPFSML(fi, LOGL_NOTICE, "another Association Setup Request, with different Recovery Timestamp."
|
||||
" Clearing sessions, sending ACK.\n");
|
||||
up_peer_clear_sessions(peer);
|
||||
} else {
|
||||
LOGPFSML(fi, LOGL_NOTICE, "another Association Setup Request, with same Recovery Timestamp."
|
||||
" Keeping sessions, sending ACK.\n");
|
||||
}
|
||||
} else if (up_peer_fsm_state_chg(UP_PEER_ST_ASSOCIATED)) {
|
||||
/* Not allowed to transition to ST_ASSOCIATED */
|
||||
cause = OSMO_PFCP_CAUSE_REQUEST_REJECTED;
|
||||
} else {
|
||||
/* Successfully transitioned to ST_ASSOCIATED */
|
||||
peer->remote_recovery_timestamp = m->ies.assoc_setup_req.recovery_time_stamp;
|
||||
peer->remote_node_id = m->ies.assoc_setup_req.node_id;
|
||||
if (m->ies.assoc_setup_req.cp_function_features_present)
|
||||
peer->peer_cp_features = m->ies.assoc_setup_req.cp_function_features;
|
||||
}
|
||||
|
||||
if (up_peer_tx_assoc_setup_resp(peer, m, cause)
|
||||
|| cause != OSMO_PFCP_CAUSE_REQUEST_ACCEPTED)
|
||||
up_peer_fsm_state_chg(UP_PEER_ST_WAIT_USE_COUNT);
|
||||
|
||||
LOGPFSML(fi, LOGL_NOTICE, "Peer associated, Node-Id=%s. Local UP features: [%s]; Peer CP features: [%s]\n",
|
||||
osmo_pfcp_ie_node_id_to_str_c(OTC_SELECT, &peer->remote_node_id),
|
||||
osmo_pfcp_bits_to_str_c(OTC_SELECT, peer->local_up_features.bits, osmo_pfcp_up_feature_strs),
|
||||
osmo_pfcp_bits_to_str_c(OTC_SELECT, peer->peer_cp_features.bits, osmo_pfcp_cp_feature_strs));
|
||||
}
|
||||
|
||||
static void up_peer_rx_assoc_rel_req(struct up_peer *peer, struct osmo_pfcp_msg *m)
|
||||
{
|
||||
struct osmo_fsm_inst *fi = peer->fi;
|
||||
up_peer_tx_assoc_rel_resp(peer, m, OSMO_PFCP_CAUSE_REQUEST_ACCEPTED);
|
||||
up_peer_fsm_state_chg(UP_PEER_ST_WAIT_USE_COUNT);
|
||||
}
|
||||
|
||||
static void up_peer_rx_session_est_req(struct up_peer *peer, struct osmo_pfcp_msg *m)
|
||||
{
|
||||
enum osmo_pfcp_cause cause = OSMO_PFCP_CAUSE_REQUEST_ACCEPTED;
|
||||
struct osmo_pfcp_msg *resp;
|
||||
struct up_session *session = up_session_find_or_add(peer, &m->ies.session_est_req.cp_f_seid, NULL);
|
||||
|
||||
if (!session) {
|
||||
cause = OSMO_PFCP_CAUSE_NO_RESOURCES_AVAILABLE;
|
||||
goto nack_response;
|
||||
}
|
||||
|
||||
up_session_set_msg_ctx(session, m);
|
||||
|
||||
if (osmo_fsm_inst_dispatch(session->fi, UP_SESSION_EV_RX_SESSION_EST_REQ, m)) {
|
||||
cause = OSMO_PFCP_CAUSE_REQUEST_REJECTED;
|
||||
goto nack_response;
|
||||
}
|
||||
return;
|
||||
|
||||
nack_response:
|
||||
resp = up_peer_init_tx(peer, m, OSMO_PFCP_MSGT_SESSION_EST_RESP);
|
||||
resp->h.seid = m->ies.session_est_req.cp_f_seid.seid;
|
||||
resp->h.seid_present = true;
|
||||
resp->ies.session_est_resp = (struct osmo_pfcp_msg_session_est_resp){
|
||||
.cause = cause,
|
||||
};
|
||||
osmo_pfcp_endpoint_tx(peer->up_endpoint->pfcp_ep, resp);
|
||||
}
|
||||
|
||||
static void up_peer_not_associated_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
struct up_peer *peer = fi->priv;
|
||||
|
||||
switch (event) {
|
||||
|
||||
case UP_PEER_EV_RX_ASSOC_SETUP_REQ:
|
||||
up_peer_rx_assoc_setup_req(peer, data);
|
||||
break;
|
||||
|
||||
case UP_PEER_EV_USE_COUNT_ZERO:
|
||||
/* Not associated and no pending messages. discard peer. */
|
||||
up_peer_fsm_state_chg(UP_PEER_ST_WAIT_USE_COUNT);
|
||||
return;
|
||||
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
static void up_peer_associated_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
struct up_peer *peer = fi->priv;
|
||||
|
||||
switch (event) {
|
||||
|
||||
case UP_PEER_EV_RX_ASSOC_SETUP_REQ:
|
||||
up_peer_rx_assoc_setup_req(peer, data);
|
||||
break;
|
||||
|
||||
case UP_PEER_EV_RX_ASSOC_UPD_REQ:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
case UP_PEER_EV_RX_SESSION_EST_REQ:
|
||||
up_peer_rx_session_est_req(peer, data);
|
||||
break;
|
||||
|
||||
case UP_PEER_EV_HEARTBEAT_FAILURE:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
case UP_PEER_EV_USE_COUNT_ZERO:
|
||||
/* Stay associated. */
|
||||
return;
|
||||
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
static void up_peer_associated_onleave(struct osmo_fsm_inst *fi, uint32_t next_state)
|
||||
{
|
||||
struct up_peer *peer = fi->priv;
|
||||
if (next_state != UP_PEER_ST_ASSOCIATED)
|
||||
LOGPFSML(fi, LOGL_NOTICE, "Peer %s released\n",
|
||||
osmo_pfcp_ie_node_id_to_str_c(OTC_SELECT, &peer->remote_node_id));
|
||||
}
|
||||
|
||||
static void up_peer_graceful_release_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
||||
{
|
||||
//struct up_peer *peer = fi->priv;
|
||||
// FIXME
|
||||
}
|
||||
|
||||
static void up_peer_graceful_release_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
struct up_peer *peer = fi->priv;
|
||||
|
||||
switch (event) {
|
||||
|
||||
case UP_PEER_EV_HEARTBEAT_FAILURE:
|
||||
up_peer_fsm_state_chg(UP_PEER_ST_WAIT_USE_COUNT);
|
||||
break;
|
||||
|
||||
case UP_PEER_EV_USE_COUNT_ZERO:
|
||||
/* When there are still sessions, stay around. */
|
||||
if (!hash_empty(peer->sessions_by_up_seid))
|
||||
return;
|
||||
up_peer_fsm_state_chg(UP_PEER_ST_WAIT_USE_COUNT);
|
||||
return;
|
||||
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
static void up_peer_wait_use_count_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
||||
{
|
||||
struct up_peer *peer = fi->priv;
|
||||
up_peer_clear_sessions(peer);
|
||||
if (!osmo_use_count_total(&peer->use_count))
|
||||
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
|
||||
}
|
||||
|
||||
static void up_peer_wait_use_count_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
struct up_peer *peer = fi->priv;
|
||||
switch (event) {
|
||||
|
||||
case UP_PEER_EV_USE_COUNT_ZERO:
|
||||
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
|
||||
return;
|
||||
|
||||
case UP_PEER_EV_RX_ASSOC_SETUP_REQ:
|
||||
up_peer_rx_assoc_setup_req(peer, data);
|
||||
break;
|
||||
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
#define S(x) (1 << (x))
|
||||
|
||||
static const struct osmo_fsm_state up_peer_fsm_states[] = {
|
||||
[UP_PEER_ST_NOT_ASSOCIATED] = {
|
||||
.name = "NOT_ASSOCIATED",
|
||||
.in_event_mask = 0
|
||||
| S(UP_PEER_EV_RX_ASSOC_SETUP_REQ)
|
||||
| S(UP_PEER_EV_USE_COUNT_ZERO)
|
||||
,
|
||||
.out_state_mask = 0
|
||||
| S(UP_PEER_ST_ASSOCIATED)
|
||||
| S(UP_PEER_ST_WAIT_USE_COUNT)
|
||||
,
|
||||
.action = up_peer_not_associated_action,
|
||||
},
|
||||
[UP_PEER_ST_ASSOCIATED] = {
|
||||
.name = "ASSOCIATED",
|
||||
.in_event_mask = 0
|
||||
| S(UP_PEER_EV_RX_ASSOC_SETUP_REQ)
|
||||
| S(UP_PEER_EV_RX_ASSOC_UPD_REQ)
|
||||
| S(UP_PEER_EV_RX_SESSION_EST_REQ)
|
||||
| S(UP_PEER_EV_HEARTBEAT_FAILURE)
|
||||
| S(UP_PEER_EV_USE_COUNT_ZERO)
|
||||
,
|
||||
.out_state_mask = 0
|
||||
| S(UP_PEER_ST_ASSOCIATED)
|
||||
| S(UP_PEER_ST_GRACEFUL_RELEASE)
|
||||
| S(UP_PEER_ST_WAIT_USE_COUNT)
|
||||
,
|
||||
.action = up_peer_associated_action,
|
||||
.onleave = up_peer_associated_onleave,
|
||||
},
|
||||
[UP_PEER_ST_GRACEFUL_RELEASE] = {
|
||||
.name = "GRACEFUL_RELEASE",
|
||||
.in_event_mask = 0
|
||||
| S(UP_PEER_EV_HEARTBEAT_FAILURE)
|
||||
| S(UP_PEER_EV_USE_COUNT_ZERO)
|
||||
,
|
||||
.out_state_mask = 0
|
||||
| S(UP_PEER_ST_WAIT_USE_COUNT)
|
||||
,
|
||||
.onenter = up_peer_graceful_release_onenter,
|
||||
.action = up_peer_graceful_release_action,
|
||||
},
|
||||
[UP_PEER_ST_WAIT_USE_COUNT] = {
|
||||
.name = "WAIT_USE_COUNT",
|
||||
.in_event_mask = 0
|
||||
| S(UP_PEER_EV_USE_COUNT_ZERO)
|
||||
| S(UP_PEER_EV_RX_ASSOC_SETUP_REQ)
|
||||
,
|
||||
.out_state_mask = 0
|
||||
| S(UP_PEER_ST_ASSOCIATED)
|
||||
,
|
||||
.onenter = up_peer_wait_use_count_onenter,
|
||||
.action = up_peer_wait_use_count_action,
|
||||
},
|
||||
};
|
||||
|
||||
void up_peer_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
|
||||
{
|
||||
struct up_peer *peer = fi->priv;
|
||||
LOGPFSML(fi, LOGL_NOTICE, "Peer removed\n");
|
||||
llist_del(&peer->entry);
|
||||
}
|
||||
|
||||
static void up_peer_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
switch (event) {
|
||||
case UP_PEER_EV_SESSION_TERM:
|
||||
/* ignore */
|
||||
return;
|
||||
case UP_PEER_EV_RX_ASSOC_REL_REQ:
|
||||
up_peer_rx_assoc_rel_req(fi->priv, data);
|
||||
return;
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
static struct osmo_fsm up_peer_fsm = {
|
||||
.name = "up_peer",
|
||||
.log_subsys = DPEER,
|
||||
.states = up_peer_fsm_states,
|
||||
.num_states = ARRAY_SIZE(up_peer_fsm_states),
|
||||
.event_names = up_peer_fsm_event_names,
|
||||
.timer_cb = up_peer_fsm_timer_cb,
|
||||
.cleanup = up_peer_fsm_cleanup,
|
||||
.allstate_event_mask = 0
|
||||
| S(UP_PEER_EV_RX_ASSOC_REL_REQ)
|
||||
| S(UP_PEER_EV_SESSION_TERM)
|
||||
,
|
||||
.allstate_action = up_peer_allstate_action,
|
||||
};
|
||||
|
||||
static __attribute__((constructor)) void up_peer_fsm_register(void)
|
||||
{
|
||||
OSMO_ASSERT(osmo_fsm_register(&up_peer_fsm) == 0);
|
||||
}
|
||||
|
||||
void up_peer_free(struct up_peer *peer)
|
||||
{
|
||||
osmo_fsm_inst_term(peer->fi, OSMO_FSM_TERM_REGULAR, NULL);
|
||||
}
|
196
src/osmo-upf/up_peer_fsm.c
Normal file
196
src/osmo-upf/up_peer_fsm.c
Normal file
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/core/fsm.h>
|
||||
|
||||
#include <osmocom/upf/up_peer.h>
|
||||
|
||||
enum up_peer_fsm_state {
|
||||
UP_PEER_ST_NOT_ASSOCIATED,
|
||||
UP_PEER_ST_ASSOCIATED,
|
||||
UP_PEER_ST_GRACEFUL_RELEASE,
|
||||
};
|
||||
|
||||
static const struct value_string up_peer_fsm_event_names[] = {
|
||||
OSMO_VALUE_STRING(UP_PEER_EV_RX_ASSOC_SETUP_REQ),
|
||||
OSMO_VALUE_STRING(UP_PEER_EV_RX_ASSOC_UPD_REQ),
|
||||
OSMO_VALUE_STRING(UP_PEER_EV_RX_SESSION_EST_REQ),
|
||||
OSMO_VALUE_STRING(UP_PEER_EV_HEARTBEAT_FAILURE),
|
||||
{}
|
||||
};
|
||||
|
||||
static struct osmo_fsm up_peer_fsm;
|
||||
|
||||
static const struct osmo_tdef_state_timeout up_peer_fsm_timeouts[32] = {
|
||||
[UP_PEER_ST_GRACEFUL_RELEASE] = { .T = -21 },
|
||||
};
|
||||
|
||||
/* Transition to a state, using the T timer defined in up_peer_fsm_timeouts.
|
||||
* Assumes local variable fi exists. */
|
||||
#define up_peer_fsm_state_chg(state) \
|
||||
osmo_tdef_fsm_inst_state_chg(fi, state, \
|
||||
up_peer_fsm_timeouts, \
|
||||
g_upf_tdefs, \
|
||||
5)
|
||||
|
||||
struct up_peer *up_peer_alloc(struct osmo_fsm_inst *parent_fi, uint32_t parent_event_term)
|
||||
{
|
||||
struct up_peer *up_peer;
|
||||
|
||||
struct osmo_fsm_inst *fi = osmo_fsm_inst_alloc_child(&up_peer_fsm, parent_fi, parent_event_term);
|
||||
OSMO_ASSERT(fi);
|
||||
|
||||
up_peer = talloc(fi, struct up_peer);
|
||||
OSMO_ASSERT(up_peer);
|
||||
fi->priv = up_peer;
|
||||
*up_peer = (struct up_peer){
|
||||
.fi = fi,
|
||||
};
|
||||
|
||||
return up_peer;
|
||||
}
|
||||
|
||||
static int up_peer_fsm_timer_cb(struct osmo_fsm_inst *fi)
|
||||
{
|
||||
//struct up_peer *up_peer = fi->priv;
|
||||
/* Return 1 to terminate FSM instance, 0 to keep running */
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void up_peer_not_associated_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
//struct up_peer *up_peer = fi->priv;
|
||||
|
||||
switch (event) {
|
||||
|
||||
case UP_PEER_EV_RX_ASSOC_SETUP_REQ:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
static void up_peer_associated_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
||||
{
|
||||
//struct up_peer *up_peer = fi->priv;
|
||||
// FIXME
|
||||
}
|
||||
|
||||
static void up_peer_associated_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
//struct up_peer *up_peer = fi->priv;
|
||||
|
||||
switch (event) {
|
||||
|
||||
case UP_PEER_EV_RX_ASSOC_UPD_REQ:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
case UP_PEER_EV_RX_SESSION_EST_REQ:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
case UP_PEER_EV_HEARTBEAT_FAILURE:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
static void up_peer_graceful_release_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
||||
{
|
||||
//struct up_peer *up_peer = fi->priv;
|
||||
// FIXME
|
||||
}
|
||||
|
||||
static void up_peer_graceful_release_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||||
{
|
||||
//struct up_peer *up_peer = fi->priv;
|
||||
|
||||
switch (event) {
|
||||
|
||||
case UP_PEER_EV_HEARTBEAT_FAILURE:
|
||||
// FIXME
|
||||
break;
|
||||
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
#define S(x) (1 << (x))
|
||||
|
||||
static const struct osmo_fsm_state up_peer_fsm_states[] = {
|
||||
[UP_PEER_ST_NOT_ASSOCIATED] = {
|
||||
.name = "not_associated",
|
||||
.in_event_mask = 0
|
||||
| S(UP_PEER_EV_RX_ASSOC_SETUP_REQ)
|
||||
,
|
||||
.out_state_mask = 0
|
||||
| S(UP_PEER_ST_ASSOCIATED)
|
||||
,
|
||||
.action = up_peer_not_associated_action,
|
||||
},
|
||||
[UP_PEER_ST_ASSOCIATED] = {
|
||||
.name = "associated",
|
||||
.in_event_mask = 0
|
||||
| S(UP_PEER_EV_RX_ASSOC_UPD_REQ)
|
||||
| S(UP_PEER_EV_RX_SESSION_EST_REQ)
|
||||
| S(UP_PEER_EV_HEARTBEAT_FAILURE)
|
||||
,
|
||||
.out_state_mask = 0
|
||||
| S(UP_PEER_ST_GRACEFUL_RELEASE)
|
||||
,
|
||||
.onenter = up_peer_associated_onenter,
|
||||
.action = up_peer_associated_action,
|
||||
},
|
||||
[UP_PEER_ST_GRACEFUL_RELEASE] = {
|
||||
.name = "graceful_release",
|
||||
.in_event_mask = 0
|
||||
| S(UP_PEER_EV_HEARTBEAT_FAILURE)
|
||||
,
|
||||
.out_state_mask = 0
|
||||
,
|
||||
.onenter = up_peer_graceful_release_onenter,
|
||||
.action = up_peer_graceful_release_action,
|
||||
},
|
||||
};
|
||||
|
||||
static struct osmo_fsm up_peer_fsm = {
|
||||
.name = "up_peer",
|
||||
.states = up_peer_fsm_states,
|
||||
.num_states = ARRAY_SIZE(up_peer_fsm_states),
|
||||
.log_subsys = DSESSION,
|
||||
.event_names = up_peer_fsm_event_names,
|
||||
.timer_cb = up_peer_fsm_timer_cb,
|
||||
};
|
||||
|
||||
static __attribute__((constructor)) void up_peer_fsm_register(void)
|
||||
{
|
||||
OSMO_ASSERT(osmo_fsm_register(&up_peer_fsm) == 0);
|
||||
}
|
1358
src/osmo-upf/up_session.c
Normal file
1358
src/osmo-upf/up_session.c
Normal file
File diff suppressed because it is too large
Load Diff
@@ -23,13 +23,69 @@
|
||||
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/core/talloc.h>
|
||||
#include <osmocom/core/sockaddr_str.h>
|
||||
|
||||
#include <osmocom/pfcp/pfcp_endpoint.h>
|
||||
|
||||
#include <osmocom/upf/upf.h>
|
||||
#include <osmocom/upf/up_endpoint.h>
|
||||
#include <osmocom/upf/upf_gtp.h>
|
||||
|
||||
struct g_upf *g_upf = NULL;
|
||||
|
||||
struct osmo_tdef_group g_upf_tdef_groups[] = {
|
||||
{ "pfcp", "PFCP endpoint timers", osmo_pfcp_tdefs, },
|
||||
{}
|
||||
};
|
||||
|
||||
void g_upf_alloc(void *ctx)
|
||||
{
|
||||
OSMO_ASSERT(g_upf == NULL);
|
||||
g_upf = talloc_zero(ctx, struct g_upf);
|
||||
|
||||
*g_upf = (struct g_upf){
|
||||
.pfcp = {
|
||||
.vty_cfg = {
|
||||
.local_addr = talloc_strdup(g_upf, UPF_PFCP_LISTEN_DEFAULT),
|
||||
.local_port = OSMO_PFCP_PORT,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
INIT_LLIST_HEAD(&g_upf->gtp.vty_cfg.devs);
|
||||
INIT_LLIST_HEAD(&g_upf->gtp.devs);
|
||||
}
|
||||
|
||||
int upf_pfcp_listen()
|
||||
{
|
||||
struct osmo_sockaddr_str local_addr_str;
|
||||
struct osmo_sockaddr local_addr;
|
||||
|
||||
OSMO_ASSERT(g_upf);
|
||||
OSMO_ASSERT(g_upf->pfcp.ep == NULL);
|
||||
|
||||
/* Translate address string from VTY config to osmo_sockaddr: first read into osmo_sockaddr_str, then write to
|
||||
* osmo_sockaddr. */
|
||||
osmo_sockaddr_str_from_str(&local_addr_str, g_upf->pfcp.vty_cfg.local_addr, g_upf->pfcp.vty_cfg.local_port);
|
||||
osmo_sockaddr_str_to_sockaddr(&local_addr_str, &local_addr.u.sas);
|
||||
LOGP(DLPFCP, LOGL_NOTICE, "PFCP: Listening on %s\n", osmo_sockaddr_to_str_c(OTC_SELECT, &local_addr));
|
||||
|
||||
g_upf->pfcp.ep = up_endpoint_init(g_upf, &local_addr);
|
||||
if (!g_upf->pfcp.ep) {
|
||||
fprintf(stderr, "Failed to allocate PFCP endpoint.\n");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int upf_gtp_devs_open()
|
||||
{
|
||||
struct gtp_vty_cfg *c = &g_upf->gtp.vty_cfg;
|
||||
struct gtp_vty_cfg_dev *d;
|
||||
|
||||
llist_for_each_entry(d, &c->devs, entry) {
|
||||
if (upf_gtp_dev_open(d->dev_name, d->create, d->local_addr, false, false))
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
457
src/osmo-upf/upf_gtp.c
Normal file
457
src/osmo-upf/upf_gtp.c
Normal file
@@ -0,0 +1,457 @@
|
||||
/*
|
||||
* (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <net/if.h>
|
||||
#include <linux/gtp.h>
|
||||
|
||||
#include <libgtpnl/gtpnl.h>
|
||||
#include <libgtpnl/gtp.h>
|
||||
|
||||
#include <osmocom/core/socket.h>
|
||||
#include <osmocom/core/logging.h>
|
||||
#include <osmocom/core/sockaddr_str.h>
|
||||
|
||||
#include <osmocom/upf/upf.h>
|
||||
#include <osmocom/upf/upf_gtp.h>
|
||||
|
||||
#define LOG_GTP_TUN(TUN, LEVEL, FMT, ARGS...) \
|
||||
LOGP(DGTP, LEVEL, "%s: " FMT, upf_gtp_tun_to_str_c(OTC_SELECT, (TUN)), ##ARGS)
|
||||
|
||||
int upf_gtp_dev_to_str_buf(char *buf, size_t buflen, const struct upf_gtp_dev *dev)
|
||||
{
|
||||
uint16_t v0_port;
|
||||
struct osmo_strbuf sb = { .buf = buf, .len = buflen };
|
||||
OSMO_STRBUF_PRINTF(sb, "%s", dev->name ? : "null");
|
||||
if (dev->name && dev->ifidx)
|
||||
OSMO_STRBUF_PRINTF(sb, " [%u]", dev->ifidx);
|
||||
if (dev->sgsn_mode)
|
||||
OSMO_STRBUF_PRINTF(sb, " (SGSN)");
|
||||
OSMO_STRBUF_PRINTF(sb, " ");
|
||||
OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &dev->gtpv1.local_addr);
|
||||
v0_port = osmo_sockaddr_port(&dev->gtpv0.local_addr.u.sa);
|
||||
if (dev->gtpv0.enabled && v0_port)
|
||||
OSMO_STRBUF_PRINTF(sb, "/%u", v0_port);
|
||||
return sb.chars_needed;
|
||||
}
|
||||
|
||||
char *upf_gtp_dev_to_str_c(void *ctx, const struct upf_gtp_dev *dev)
|
||||
{
|
||||
OSMO_NAME_C_IMPL(ctx, 64, "ERROR", upf_gtp_dev_to_str_buf, dev)
|
||||
}
|
||||
|
||||
struct upf_gtp_dev *upf_gtp_dev_find_by_name(const char *name)
|
||||
{
|
||||
struct upf_gtp_dev *dev;
|
||||
llist_for_each_entry(dev, &g_upf->gtp.devs, entry) {
|
||||
if (!strcmp(name, dev->name))
|
||||
return dev;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct upf_gtp_dev *upf_gtp_dev_first()
|
||||
{
|
||||
return llist_first_entry_or_null(&g_upf->gtp.devs, struct upf_gtp_dev, entry);
|
||||
}
|
||||
|
||||
/* Tell the kernel to remove the GTP device. Called implicitly by talloc_free() (see upf_gtp_dev_destruct()). */
|
||||
static int upf_gtp_dev_delete(struct upf_gtp_dev *dev)
|
||||
{
|
||||
int rc;
|
||||
if (!dev->name)
|
||||
return 0;
|
||||
rc = gtp_dev_destroy(dev->name);
|
||||
if (rc < 0) {
|
||||
LOG_GTP_DEV(dev, LOGL_ERROR, "Error while deleting device: %s\n", strerror(errno));
|
||||
return rc;
|
||||
}
|
||||
LOG_GTP_DEV(dev, LOGL_NOTICE, "Deleted GTP device\n");
|
||||
dev->name = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int upf_gtp_dev_destruct(struct upf_gtp_dev *dev);
|
||||
|
||||
static struct upf_gtp_dev *upf_gtp_dev_alloc(const char *name, const char *local_addr)
|
||||
{
|
||||
struct upf_gtp_dev *dev = upf_gtp_dev_find_by_name(name);
|
||||
struct osmo_sockaddr_str addr_conv;
|
||||
local_addr = local_addr ? : "0.0.0.0";
|
||||
if (dev) {
|
||||
LOG_GTP_DEV(dev, LOGL_ERROR, "Device already exists. Cannot create %s %s\n", name, local_addr);
|
||||
return NULL;
|
||||
}
|
||||
dev = talloc(g_upf, struct upf_gtp_dev);
|
||||
*dev = (struct upf_gtp_dev){
|
||||
.name = talloc_strdup(dev, name),
|
||||
.gtpv0.ofd.fd = -1,
|
||||
.gtpv1.ofd.fd = -1,
|
||||
};
|
||||
INIT_LLIST_HEAD(&dev->tunnels);
|
||||
|
||||
osmo_sockaddr_str_from_str(&addr_conv, local_addr, PORT_GTP0_U);
|
||||
|
||||
osmo_sockaddr_str_to_sockaddr(&addr_conv, &dev->gtpv0.local_addr.u.sas);
|
||||
|
||||
addr_conv.port = PORT_GTP1_U;
|
||||
osmo_sockaddr_str_to_sockaddr(&addr_conv, &dev->gtpv1.local_addr.u.sas);
|
||||
|
||||
/* Need to add to list before setting up the destructor. A talloc_free() does automagically remove from the
|
||||
* list. */
|
||||
llist_add(&dev->entry, &g_upf->gtp.devs);
|
||||
|
||||
talloc_set_destructor(dev, upf_gtp_dev_destruct);
|
||||
|
||||
return dev;
|
||||
}
|
||||
|
||||
static int dev_resolve_ifidx(struct upf_gtp_dev *dev)
|
||||
{
|
||||
int rc;
|
||||
|
||||
dev->ifidx = if_nametoindex(dev->name);
|
||||
if (dev->ifidx == 0) {
|
||||
LOG_GTP_DEV(dev, LOGL_ERROR, "No such device: '%s'\n", dev->name);
|
||||
talloc_free(dev);
|
||||
return -EIO;
|
||||
}
|
||||
/* Let's try something to see if talking to the device works. */
|
||||
errno = 0;
|
||||
rc = gtp_list_tunnel(g_upf->gtp.genl_id, g_upf->gtp.nl);
|
||||
if (errno)
|
||||
rc = -errno;
|
||||
else if (rc)
|
||||
rc = -EINVAL;
|
||||
if (rc) {
|
||||
LOG_GTP_DEV(dev, LOGL_ERROR, "Failed to open GTP device: %s\n", strerror(-rc));
|
||||
talloc_free(dev);
|
||||
return rc;
|
||||
}
|
||||
|
||||
LOG_GTP_DEV(dev, LOGL_NOTICE, "GTP device ready (ifidx=%u)\n", dev->ifidx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int upf_gtp_dev_open(const char *name, bool create_gtp_dev, const char *local_addr, bool listen_for_gtpv0, bool sgsn_mode)
|
||||
{
|
||||
struct osmo_sockaddr any = {
|
||||
.u.sin = {
|
||||
.sin_family = AF_INET,
|
||||
.sin_port = 0,
|
||||
.sin_addr = {
|
||||
.s_addr = INADDR_ANY,
|
||||
},
|
||||
},
|
||||
};
|
||||
int rc;
|
||||
struct upf_gtp_dev *dev = upf_gtp_dev_alloc(name, local_addr);
|
||||
if (!dev)
|
||||
return -EIO;
|
||||
|
||||
dev->sgsn_mode = sgsn_mode;
|
||||
|
||||
if (listen_for_gtpv0) {
|
||||
rc = osmo_sock_init_osa_ofd(&dev->gtpv0.ofd, SOCK_DGRAM, 0, &dev->gtpv0.local_addr, &any,
|
||||
OSMO_SOCK_F_BIND);
|
||||
if (rc < 0) {
|
||||
LOG_GTP_DEV(dev, LOGL_ERROR, "Cannot bind GTPv0 on %s (rc=%d)\n",
|
||||
osmo_sockaddr_to_str_c(OTC_SELECT, &dev->gtpv0.local_addr), rc);
|
||||
talloc_free(dev);
|
||||
return -EIO;
|
||||
}
|
||||
dev->gtpv0.enabled = true;
|
||||
LOG_GTP_DEV(dev, LOGL_DEBUG, "GTPv0 bound\n");
|
||||
}
|
||||
|
||||
/* GTPv1 */
|
||||
rc = osmo_sock_init_osa_ofd(&dev->gtpv1.ofd, SOCK_DGRAM, 0, &dev->gtpv1.local_addr, &any,
|
||||
OSMO_SOCK_F_BIND);
|
||||
if (rc < 0) {
|
||||
LOG_GTP_DEV(dev, LOGL_ERROR, "Cannot bind GTPv1 (rc=%d)\n", rc);
|
||||
talloc_free(dev);
|
||||
return -EIO;
|
||||
}
|
||||
LOG_GTP_DEV(dev, LOGL_DEBUG, "GTPv1 bound\n");
|
||||
|
||||
if (create_gtp_dev) {
|
||||
int gtp0_fd = listen_for_gtpv0 ? dev->gtpv0.ofd.fd : -1;
|
||||
int gtp1_fd = dev->gtpv1.ofd.fd;
|
||||
if (dev->sgsn_mode)
|
||||
rc = gtp_dev_create_sgsn(-1, dev->name, gtp0_fd, gtp1_fd);
|
||||
else
|
||||
rc = gtp_dev_create(-1, dev->name, gtp0_fd, gtp1_fd);
|
||||
if (rc < 0) {
|
||||
LOG_GTP_DEV(dev, LOGL_ERROR, "Cannot create GTP device: rc=%d\n", rc);
|
||||
/* name = NULL: signal to the destructor that it does not need to delete the device */
|
||||
dev->name = NULL;
|
||||
talloc_free(dev);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
LOG_GTP_DEV(dev, LOGL_NOTICE, "created GTP device\n");
|
||||
dev->created = true;
|
||||
}
|
||||
|
||||
rc = dev_resolve_ifidx(dev);
|
||||
if (rc) {
|
||||
talloc_free(dev);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void upf_gtp_devs_close()
|
||||
{
|
||||
struct upf_gtp_dev *dev;
|
||||
while ((dev = llist_first_entry_or_null(&g_upf->gtp.devs, struct upf_gtp_dev, entry)))
|
||||
talloc_free(dev);
|
||||
}
|
||||
|
||||
void upf_gtp_genl_close()
|
||||
{
|
||||
if (!g_upf->gtp.nl)
|
||||
return;
|
||||
genl_socket_close(g_upf->gtp.nl);
|
||||
g_upf->gtp.nl = NULL;
|
||||
g_upf->gtp.genl_id = -1;
|
||||
|
||||
LOGP(DGTP, LOGL_NOTICE, "Closed mnl_socket\n");
|
||||
}
|
||||
|
||||
/* Open an MNL socket which allows to create and remove GTP devices (requires CAP_NET_ADMIN). */
|
||||
int upf_gtp_genl_open()
|
||||
{
|
||||
if (g_upf->gtp.nl && g_upf->gtp.genl_id >= 0)
|
||||
return 0;
|
||||
|
||||
if (g_upf->gtp.nl)
|
||||
upf_gtp_genl_close();
|
||||
|
||||
g_upf->gtp.nl = genl_socket_open();
|
||||
if (!g_upf->gtp.nl) {
|
||||
LOGP(DGTP, LOGL_ERROR, "Cannot open mnl_socket: %s\n", strerror(errno));
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
g_upf->gtp.genl_id = genl_lookup_family(g_upf->gtp.nl, "gtp");
|
||||
if (g_upf->gtp.genl_id < 0) {
|
||||
LOGP(DGTP, LOGL_ERROR, "genl family 'gtp' not found\n");
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
LOGP(DGTP, LOGL_NOTICE, "Opened mnl_socket\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct upf_gtp_tun {
|
||||
struct llist_head entry;
|
||||
|
||||
struct upf_gtp_dev *dev;
|
||||
struct upf_gtp_tun_desc desc;
|
||||
bool active;
|
||||
};
|
||||
|
||||
static int upf_gtp_tun_to_str_buf(char *buf, size_t buflen, const struct upf_gtp_tun *tun)
|
||||
{
|
||||
struct osmo_strbuf sb = { .buf = buf, .len = buflen };
|
||||
OSMO_STRBUF_PRINTF(sb, "%s:tun{TEID=l:0x%x,r:0x%x UE=", tun->dev->name, tun->desc.local_teid,
|
||||
tun->desc.remote_teid);
|
||||
OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &tun->desc.ue_addr);
|
||||
OSMO_STRBUF_PRINTF(sb, " GTP-dst=");
|
||||
OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &tun->desc.gtp_remote_addr);
|
||||
OSMO_STRBUF_PRINTF(sb, "}");
|
||||
return sb.chars_needed;
|
||||
}
|
||||
|
||||
static char *upf_gtp_tun_to_str_c(void *ctx, const struct upf_gtp_tun *tun)
|
||||
{
|
||||
OSMO_NAME_C_IMPL(ctx, 64, "ERROR", upf_gtp_tun_to_str_buf, tun)
|
||||
}
|
||||
|
||||
static int upf_gtp_tun_deactivate(struct upf_gtp_tun *tun);
|
||||
|
||||
static int upf_gtp_tun_destruct(struct upf_gtp_tun *tun)
|
||||
{
|
||||
if (tun->active)
|
||||
upf_gtp_tun_deactivate(tun);
|
||||
llist_del(&tun->entry);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct upf_gtp_tun *upf_gtp_tun_alloc(struct upf_gtp_dev *dev, const struct upf_gtp_tun_desc *desc)
|
||||
{
|
||||
struct upf_gtp_tun *tun = talloc(dev, struct upf_gtp_tun);
|
||||
OSMO_ASSERT(tun);
|
||||
*tun = (struct upf_gtp_tun){
|
||||
.dev = dev,
|
||||
.desc = *desc,
|
||||
};
|
||||
llist_add(&tun->entry, &dev->tunnels);
|
||||
talloc_set_destructor(tun, upf_gtp_tun_destruct);
|
||||
return tun;
|
||||
}
|
||||
|
||||
static struct gtp_tunnel *upf_gtp_tun_to_gtp_tunnel(struct upf_gtp_tun *tun)
|
||||
{
|
||||
struct gtp_tunnel *t;
|
||||
|
||||
if (tun->desc.ue_addr.u.sas.ss_family != AF_INET || tun->desc.gtp_remote_addr.u.sas.ss_family != AF_INET) {
|
||||
LOG_GTP_TUN(tun, LOGL_ERROR, "Only capabale of IPv4\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
t = gtp_tunnel_alloc();
|
||||
OSMO_ASSERT(t);
|
||||
gtp_tunnel_set_ifidx(t, tun->dev->ifidx);
|
||||
gtp_tunnel_set_version(t, GTP_V1);
|
||||
gtp_tunnel_set_i_tei(t, tun->desc.local_teid);
|
||||
gtp_tunnel_set_o_tei(t, tun->desc.remote_teid);
|
||||
gtp_tunnel_set_ms_ip4(t, &tun->desc.ue_addr.u.sin.sin_addr);
|
||||
gtp_tunnel_set_sgsn_ip4(t, &tun->desc.gtp_remote_addr.u.sin.sin_addr);
|
||||
return t;
|
||||
}
|
||||
|
||||
int upf_gtp_tun_activate(struct upf_gtp_tun *tun)
|
||||
{
|
||||
int rc;
|
||||
struct gtp_tunnel *t;
|
||||
|
||||
if (tun->active)
|
||||
return -EALREADY;
|
||||
|
||||
t = upf_gtp_tun_to_gtp_tunnel(tun);
|
||||
if (!t)
|
||||
return -ENOTSUP;
|
||||
|
||||
errno = 0;
|
||||
rc = gtp_add_tunnel(g_upf->gtp.genl_id, g_upf->gtp.nl, t);
|
||||
if (errno) {
|
||||
rc = -errno;
|
||||
} else if (rc) {
|
||||
rc = -EINVAL;
|
||||
} else {
|
||||
tun->active = true;
|
||||
}
|
||||
|
||||
gtp_tunnel_free(t);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static struct upf_gtp_tun *upf_gtp_dev_tunnel_find(struct upf_gtp_dev *dev, const struct upf_gtp_tun_desc *tun_desc)
|
||||
{
|
||||
struct upf_gtp_tun *tun;
|
||||
llist_for_each_entry(tun, &dev->tunnels, entry) {
|
||||
if (upf_gtp_tun_desc_cmp(tun_desc, &tun->desc))
|
||||
continue;
|
||||
return tun;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int upf_gtp_dev_tunnel_add(struct upf_gtp_dev *dev, const struct upf_gtp_tun_desc *tun_desc)
|
||||
{
|
||||
struct upf_gtp_tun *tun;
|
||||
tun = upf_gtp_dev_tunnel_find(dev, tun_desc);
|
||||
if (!tun)
|
||||
tun = upf_gtp_tun_alloc(dev, tun_desc);
|
||||
if (tun->active)
|
||||
return 0;
|
||||
return upf_gtp_tun_activate(tun);
|
||||
}
|
||||
|
||||
int upf_gtp_dev_tunnel_del(struct upf_gtp_dev *dev, const struct upf_gtp_tun_desc *tun_desc)
|
||||
{
|
||||
struct upf_gtp_tun *tun;
|
||||
int rc;
|
||||
tun = upf_gtp_dev_tunnel_find(dev, tun_desc);
|
||||
if (!tun)
|
||||
return 0;
|
||||
if (tun->active) {
|
||||
rc = upf_gtp_tun_deactivate(tun);
|
||||
if (rc)
|
||||
return rc;
|
||||
}
|
||||
talloc_free(tun);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int upf_gtp_tun_deactivate(struct upf_gtp_tun *tun)
|
||||
{
|
||||
int rc;
|
||||
struct gtp_tunnel *t;
|
||||
|
||||
if (!tun->active) {
|
||||
LOG_GTP_TUN(tun, LOGL_ERROR, "Cannot deactivate, not active\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
t = upf_gtp_tun_to_gtp_tunnel(tun);
|
||||
if (!t)
|
||||
return -EINVAL;
|
||||
|
||||
rc = gtp_del_tunnel(g_upf->gtp.genl_id, g_upf->gtp.nl, t);
|
||||
if (rc)
|
||||
LOG_GTP_TUN(tun, LOGL_ERROR, "Failed to delete tunnel: %d %s\n", rc, strerror(rc));
|
||||
else
|
||||
tun->active = false;
|
||||
|
||||
gtp_tunnel_free(t);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int upf_gtp_dev_destruct(struct upf_gtp_dev *dev)
|
||||
{
|
||||
struct upf_gtp_tun *t;
|
||||
/* Destruct and clean up all active tunnels before deleting the device */
|
||||
while ((t = llist_first_entry_or_null(&dev->tunnels, struct upf_gtp_tun, entry)))
|
||||
talloc_free(t);
|
||||
llist_del(&dev->entry);
|
||||
osmo_fd_close(&dev->gtpv0.ofd);
|
||||
osmo_fd_close(&dev->gtpv1.ofd);
|
||||
if (dev->created)
|
||||
upf_gtp_dev_delete(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int upf_gtp_tun_desc_cmp(const struct upf_gtp_tun_desc *a, const struct upf_gtp_tun_desc *b)
|
||||
{
|
||||
int r;
|
||||
|
||||
if (a == b)
|
||||
return 0;
|
||||
if (!a)
|
||||
return -1;
|
||||
if (!b)
|
||||
return 1;
|
||||
|
||||
#define CMP_MEMB(MEMB) OSMO_CMP(a->MEMB, b->MEMB)
|
||||
if ((r = CMP_MEMB(local_teid)))
|
||||
return r;
|
||||
if ((r = CMP_MEMB(remote_teid)))
|
||||
return r;
|
||||
return osmo_sockaddr_cmp(&a->gtp_remote_addr, &b->gtp_remote_addr);
|
||||
}
|
286
src/osmo-upf/upf_vty.c
Normal file
286
src/osmo-upf/upf_vty.c
Normal file
@@ -0,0 +1,286 @@
|
||||
/* OsmoUpf interface to quagga VTY */
|
||||
/*
|
||||
* (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <osmocom/core/sockaddr_str.h>
|
||||
#include <osmocom/core/socket.h>
|
||||
|
||||
#include <osmocom/pfcp/pfcp_endpoint.h>
|
||||
#include <osmocom/pfcp/pfcp_msg.h>
|
||||
|
||||
#include <osmocom/vty/vty.h>
|
||||
#include <osmocom/vty/command.h>
|
||||
|
||||
#include <osmocom/upf/upf.h>
|
||||
#include <osmocom/upf/upf_gtp.h>
|
||||
#include <osmocom/upf/up_endpoint.h>
|
||||
#include <osmocom/upf/up_peer.h>
|
||||
#include <osmocom/upf/up_session.h>
|
||||
#include <osmocom/upf/up_gtp_action.h>
|
||||
|
||||
enum upf_vty_node {
|
||||
PFCP_NODE = _LAST_OSMOVTY_NODE + 1,
|
||||
GTP_NODE,
|
||||
};
|
||||
|
||||
static struct cmd_node cfg_pfcp_node = {
|
||||
PFCP_NODE,
|
||||
"%s(config-pfcp)# ",
|
||||
1,
|
||||
};
|
||||
|
||||
#define pfcp_vty (g_upf->pfcp.vty_cfg)
|
||||
#define gtp_vty (g_upf->gtp.vty_cfg)
|
||||
|
||||
DEFUN(cfg_pfcp, cfg_pfcp_cmd,
|
||||
"pfcp",
|
||||
"Enter the PFCP configuration node\n")
|
||||
{
|
||||
vty->node = PFCP_NODE;
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
static int config_write_pfcp(struct vty *vty)
|
||||
{
|
||||
vty_out(vty, "pfcp%s", VTY_NEWLINE);
|
||||
if (strcmp(UPF_PFCP_LISTEN_DEFAULT, pfcp_vty.local_addr))
|
||||
vty_out(vty, " local-addr %s%s", pfcp_vty.local_addr, VTY_NEWLINE);
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_pfcp_local_addr, cfg_pfcp_local_addr_cmd,
|
||||
"local-addr IP_ADDR",
|
||||
"Set the local IP address to bind on for PFCP\n"
|
||||
"IP address\n")
|
||||
{
|
||||
osmo_talloc_replace_string(g_upf, &pfcp_vty.local_addr, argv[0]);
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
static struct cmd_node cfg_gtp_node = {
|
||||
GTP_NODE,
|
||||
"%s(config-gtp)# ",
|
||||
1,
|
||||
};
|
||||
|
||||
DEFUN(cfg_gtp, cfg_gtp_cmd,
|
||||
"gtp",
|
||||
"Enter the GTP configuration node\n")
|
||||
{
|
||||
vty->node = GTP_NODE;
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
static int config_write_gtp(struct vty *vty)
|
||||
{
|
||||
struct gtp_vty_cfg_dev *d;
|
||||
vty_out(vty, "gtp%s", VTY_NEWLINE);
|
||||
|
||||
llist_for_each_entry(d, >p_vty.devs, entry) {
|
||||
if (d->create) {
|
||||
vty_out(vty, " dev create %s", d->dev_name);
|
||||
if (d->local_addr)
|
||||
vty_out(vty, " %s", d->local_addr);
|
||||
vty_out(vty, "%s", VTY_NEWLINE);
|
||||
} else {
|
||||
vty_out(vty, " dev use %s%s", d->dev_name, VTY_NEWLINE);
|
||||
}
|
||||
}
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
#define DEV_STR "Configure the GTP device to use for encaps/decaps.\n"
|
||||
|
||||
DEFUN(cfg_gtp_dev_create, cfg_gtp_dev_create_cmd,
|
||||
"dev create DEVNAME [LISTEN_ADDR]",
|
||||
DEV_STR
|
||||
"create a new GTP device. Will listen on GTPv1 port " OSMO_STRINGIFY_VAL(PORT_GTP1_U)
|
||||
" and GTPv0 port " OSMO_STRINGIFY_VAL(PORT_GTP0_U) " on the specified interface, or on ANY if LISTEN_ADDR is"
|
||||
" omitted.\n"
|
||||
"device name, e.g. 'apn0'\n"
|
||||
"IPv4 or IPv6 address to listen on, omit for ANY\n")
|
||||
{
|
||||
struct gtp_vty_cfg_dev *d = talloc_zero(g_upf, struct gtp_vty_cfg_dev);
|
||||
d->create = true;
|
||||
d->dev_name = talloc_strdup(d, argv[0]);
|
||||
if (argc > 1)
|
||||
d->local_addr = talloc_strdup(d, argv[1]);
|
||||
llist_add(&d->entry, >p_vty.devs);
|
||||
vty_out(vty, "Added GTP device %s (create new)%s", d->dev_name, VTY_NEWLINE);
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_gtp_dev_use, cfg_gtp_dev_use_cmd,
|
||||
"dev use DEVNAME",
|
||||
DEV_STR
|
||||
"use an existing GTP device, e.g. created by 'gtp-link'\n"
|
||||
"device name, e.g. 'apn0'\n")
|
||||
{
|
||||
struct gtp_vty_cfg_dev *d = talloc_zero(g_upf, struct gtp_vty_cfg_dev);
|
||||
d->create = false;
|
||||
d->dev_name = talloc_strdup(d, argv[0]);
|
||||
llist_add(&d->entry, >p_vty.devs);
|
||||
vty_out(vty, "Added GTP device %s (use existing)%s", d->dev_name, VTY_NEWLINE);
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_gtp_dev_del, cfg_gtp_dev_del_cmd,
|
||||
"dev delete DEVNAME",
|
||||
DEV_STR
|
||||
"Remove a GTP device from the configuration, and delete the device if it was created here.\n"
|
||||
"device name, e.g. 'apn0'\n")
|
||||
{
|
||||
const char *dev_name = argv[0];
|
||||
struct gtp_vty_cfg_dev *d;
|
||||
struct upf_gtp_dev *dev;
|
||||
|
||||
/* remove from VTY cfg */
|
||||
llist_for_each_entry(d, >p_vty.devs, entry) {
|
||||
if (strcmp(d->dev_name, dev_name))
|
||||
continue;
|
||||
llist_del(&d->entry);
|
||||
break;
|
||||
}
|
||||
|
||||
/* close device (and possibly delete from system, via talloc destructor) */
|
||||
dev = upf_gtp_dev_find_by_name(dev_name);
|
||||
if (dev)
|
||||
talloc_free(dev);
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(show_pdr, show_pdr_cmd,
|
||||
"show pdr",
|
||||
SHOW_STR
|
||||
"List all sessions' PDR and FAR status\n")
|
||||
{
|
||||
struct up_peer *peer;
|
||||
int active_count = 0;
|
||||
int inactive_count = 0;
|
||||
llist_for_each_entry(peer, &g_upf->pfcp.ep->peers, entry) {
|
||||
struct up_session *session;
|
||||
int bkt;
|
||||
hash_for_each(peer->sessions_by_up_seid, bkt, session, node_by_up_seid) {
|
||||
struct pdr *pdr;
|
||||
llist_for_each_entry(pdr, &session->pdrs, entry) {
|
||||
if (!pdr->active) {
|
||||
vty_out(vty, "%s: inactive: %s%s%s%s",
|
||||
session->fi->id, pdr_to_str_c(OTC_SELECT, pdr),
|
||||
pdr->inactive_reason ? ": " : "",
|
||||
pdr->inactive_reason ? : "",
|
||||
VTY_NEWLINE);
|
||||
inactive_count++;
|
||||
} else {
|
||||
vty_out(vty, "%s: active: %s%s",
|
||||
session->fi->id, pdr_to_str_c(OTC_SELECT, pdr),
|
||||
VTY_NEWLINE);
|
||||
active_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
vty_out(vty, "(%d of %d active)%s", active_count, active_count + inactive_count, VTY_NEWLINE);
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(show_gtp, show_gtp_cmd,
|
||||
"show gtp",
|
||||
SHOW_STR
|
||||
"Active GTP tunnels and forwardings\n")
|
||||
{
|
||||
struct up_peer *peer;
|
||||
int count = 0;
|
||||
|
||||
if (!upf_gtp_dev_first()) {
|
||||
vty_out(vty, "No GTP device open%s", VTY_NEWLINE);
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
llist_for_each_entry(peer, &g_upf->pfcp.ep->peers, entry) {
|
||||
struct up_session *session;
|
||||
int bkt;
|
||||
hash_for_each(peer->sessions_by_up_seid, bkt, session, node_by_up_seid) {
|
||||
struct up_gtp_action *a;
|
||||
llist_for_each_entry(a, &session->active_gtp_actions, entry) {
|
||||
vty_out(vty, "%s%s", up_gtp_action_to_str_c(OTC_SELECT, a), VTY_NEWLINE);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
vty_out(vty, "(%d active)%s", count, VTY_NEWLINE);
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(show_session, show_session_cmd,
|
||||
"show session",
|
||||
SHOW_STR
|
||||
"PFCP Session status\n")
|
||||
{
|
||||
struct up_peer *peer;
|
||||
int inactive_count = 0;
|
||||
int active_count = 0;
|
||||
int fully_active_count = 0;
|
||||
|
||||
llist_for_each_entry(peer, &g_upf->pfcp.ep->peers, entry) {
|
||||
struct up_session *session;
|
||||
int bkt;
|
||||
hash_for_each(peer->sessions_by_up_seid, bkt, session, node_by_up_seid) {
|
||||
vty_out(vty, "%s %s%s",
|
||||
up_session_to_str_c(OTC_SELECT, session),
|
||||
up_session_gtp_status(session), VTY_NEWLINE);
|
||||
if (up_session_is_active(session)) {
|
||||
if (up_session_is_fully_active(session, NULL, NULL))
|
||||
fully_active_count++;
|
||||
else
|
||||
active_count++;
|
||||
} else {
|
||||
inactive_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
vty_out(vty, "(%d fully-active + %d partially active + %d inactive)%s",
|
||||
fully_active_count, active_count, inactive_count, VTY_NEWLINE);
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
void upf_vty_init()
|
||||
{
|
||||
OSMO_ASSERT(g_upf != NULL);
|
||||
|
||||
install_element_ve(&show_pdr_cmd);
|
||||
install_element_ve(&show_gtp_cmd);
|
||||
install_element_ve(&show_session_cmd);
|
||||
|
||||
install_node(&cfg_pfcp_node, config_write_pfcp);
|
||||
install_element(CONFIG_NODE, &cfg_pfcp_cmd);
|
||||
|
||||
install_element(PFCP_NODE, &cfg_pfcp_local_addr_cmd);
|
||||
|
||||
install_node(&cfg_gtp_node, config_write_gtp);
|
||||
install_element(CONFIG_NODE, &cfg_gtp_cmd);
|
||||
|
||||
install_element(GTP_NODE, &cfg_gtp_dev_create_cmd);
|
||||
install_element(GTP_NODE, &cfg_gtp_dev_use_cmd);
|
||||
install_element(GTP_NODE, &cfg_gtp_dev_del_cmd);
|
||||
}
|
||||
|
@@ -2,3 +2,34 @@ OsmoUPF> enable
|
||||
OsmoUPF# configure terminal
|
||||
OsmoUPF(config)# show running-config
|
||||
...
|
||||
|
||||
OsmoUPF(config)# pfcp
|
||||
OsmoUPF(config-pfcp)# list
|
||||
...
|
||||
local-addr IP_ADDR
|
||||
OsmoUPF(config-pfcp)# local-addr?
|
||||
local-addr Set the local IP address to bind on for PFCP
|
||||
OsmoUPF(config-pfcp)# local-addr ?
|
||||
IP_ADDR IP address
|
||||
OsmoUPF(config-pfcp)# exit
|
||||
|
||||
OsmoUPF(config)# gtp
|
||||
OsmoUPF(config-gtp)# list
|
||||
...
|
||||
dev create DEVNAME [LISTEN_ADDR]
|
||||
dev use DEVNAME
|
||||
dev delete DEVNAME
|
||||
|
||||
OsmoUPF(config-gtp)# dev?
|
||||
dev Configure the GTP device to use for encaps/decaps.
|
||||
OsmoUPF(config-gtp)# dev ?
|
||||
create create a new GTP device. Will listen on GTPv1 port 2152 and GTPv0 port 3386 on the specified interface, or on ANY if LISTEN_ADDR is omitted.
|
||||
use use an existing GTP device, e.g. created by 'gtp-link'
|
||||
delete Remove a GTP device from the configuration, and delete the device if it was created here.
|
||||
OsmoUPF(config-gtp)# dev create ?
|
||||
DEVNAME device name, e.g. 'apn0'
|
||||
OsmoUPF(config-gtp)# dev create foo ?
|
||||
[LISTEN_ADDR] IPv4 or IPv6 address to listen on, omit for ANY
|
||||
OsmoUPF(config-gtp)# dev delete ?
|
||||
DEVNAME device name, e.g. 'apn0'
|
||||
OsmoUPF(config-gtp)# exit
|
||||
|
Reference in New Issue
Block a user