mirror of
				https://gitea.osmocom.org/cellular-infrastructure/osmo-upf.git
				synced 2025-10-31 20:13:40 +00:00 
			
		
		
		
	add osmo-upf
Related: SYS#5599 Change-Id: I745bcbde6859004c41ddbfd2558036bf9a2d1de2
This commit is contained in:
		
							
								
								
									
										17
									
								
								doc/examples/osmo-upf/osmo-upf-create-dev.cfg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								doc/examples/osmo-upf/osmo-upf-create-dev.cfg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | 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 notice | ||||||
|  |  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 category-hex 0 | ||||||
|  logging print file basename last |  logging print file basename last | ||||||
|  logging print extended-timestamp 1 |  logging print extended-timestamp 1 | ||||||
|  |  logging level set-all debug | ||||||
|  logging level set-all notice |  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 = \ | noinst_HEADERS = \ | ||||||
|  | 	up_endpoint.h \ | ||||||
|  | 	up_peer.h \ | ||||||
|  | 	up_session.h \ | ||||||
| 	upf.h \ | 	upf.h \ | ||||||
|  | 	upf_gtp.h \ | ||||||
|  | 	upf_nft.h \ | ||||||
|  | 	up_gtp_action.h \ | ||||||
| 	$(NULL) | 	$(NULL) | ||||||
|   | |||||||
							
								
								
									
										45
									
								
								include/osmocom/upf/up_endpoint.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								include/osmocom/upf/up_endpoint.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | |||||||
|  | /* (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> | ||||||
|  |  * All Rights Reserved | ||||||
|  |  * | ||||||
|  |  * Author: Neels Hofmeyr <nhofmeyr@sysmocom.de> | ||||||
|  |  * | ||||||
|  |  * This program is free software; you can redistribute it and/or modify | ||||||
|  |  * it under the terms of the GNU Affero General Public License as published by | ||||||
|  |  * the Free Software Foundation; either version 3 of the License, or | ||||||
|  |  * (at your option) any later version. | ||||||
|  |  * | ||||||
|  |  * This program is distributed in the hope that it will be useful, | ||||||
|  |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  |  * GNU Affero General Public License for more details. | ||||||
|  |  * | ||||||
|  |  * You should have received a copy of the GNU Affero General Public License | ||||||
|  |  * along with this program.  If not, see <http://www.gnu.org/lienses/>. | ||||||
|  |  * | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #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); | ||||||
							
								
								
									
										50
									
								
								include/osmocom/upf/up_gtp_action.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								include/osmocom/upf/up_gtp_action.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | |||||||
|  | #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_FORW, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  |  | ||||||
|  | 		/* Forward GTP: translate from one TEID to another and forward */ | ||||||
|  | 		struct upf_nft_forw_desc forw; | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	/* 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); | ||||||
							
								
								
									
										75
									
								
								include/osmocom/upf/up_peer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								include/osmocom/upf/up_peer.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,75 @@ | |||||||
|  | /* (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> | ||||||
|  |  * All Rights Reserved | ||||||
|  |  * | ||||||
|  |  * Author: Neels Hofmeyr <nhofmeyr@sysmocom.de> | ||||||
|  |  * | ||||||
|  |  * This program is free software; you can redistribute it and/or modify | ||||||
|  |  * it under the terms of the GNU Affero General Public License as published by | ||||||
|  |  * the Free Software Foundation; either version 3 of the License, or | ||||||
|  |  * (at your option) any later version. | ||||||
|  |  * | ||||||
|  |  * This program is distributed in the hope that it will be useful, | ||||||
|  |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  |  * GNU Affero General Public License for more details. | ||||||
|  |  * | ||||||
|  |  * You should have received a copy of the GNU Affero General Public License | ||||||
|  |  * along with this program.  If not, see <http://www.gnu.org/lienses/>. | ||||||
|  |  * | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #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); | ||||||
							
								
								
									
										94
									
								
								include/osmocom/upf/up_session.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								include/osmocom/upf/up_session.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,94 @@ | |||||||
|  | #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; | ||||||
|  | }; | ||||||
							
								
								
									
										0
									
								
								include/osmocom/upf/up_session_to_gtp.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								include/osmocom/upf/up_session_to_gtp.c
									
									
									
									
									
										Normal file
									
								
							| @@ -1,12 +1,65 @@ | |||||||
| /* Global definitions for OsmoUPF */ | /* Global definitions for OsmoUPF */ | ||||||
| #pragma once | #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 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; | ||||||
|  |  | ||||||
|  | 	bool create; | ||||||
|  | 	char *dev_name; | ||||||
|  | 	char *local_addr; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | struct gtp_vty_cfg { | ||||||
|  | 	struct llist_head devs; | ||||||
|  | }; | ||||||
|  |  | ||||||
| struct g_upf { | struct g_upf { | ||||||
| 	struct ctrl_handle *ctrl; | 	struct ctrl_handle *ctrl; | ||||||
|  |  | ||||||
|  | 	struct { | ||||||
|  | 		struct pfcp_vty_cfg vty_cfg; | ||||||
|  | 		struct up_endpoint *ep; | ||||||
|  | 	} pfcp; | ||||||
|  | 	struct { | ||||||
|  | 		struct gtp_vty_cfg vty_cfg; | ||||||
|  | 		struct llist_head devs; | ||||||
|  |  | ||||||
|  | 		struct mnl_socket *nl; | ||||||
|  | 		int32_t genl_id; | ||||||
|  | 	} gtp; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| extern struct g_upf *g_upf; | extern struct g_upf *g_upf; | ||||||
|  |  | ||||||
|  | enum upf_log_subsys { | ||||||
|  | 	DREF, | ||||||
|  | 	DPEER, | ||||||
|  | 	DSESSION, | ||||||
|  | 	DGTP, | ||||||
|  | }; | ||||||
|  |  | ||||||
| void g_upf_alloc(void *ctx); | void g_upf_alloc(void *ctx); | ||||||
|  | void upf_vty_init(); | ||||||
|  | int upf_pfcp_listen(); | ||||||
|  |  | ||||||
|  | int upf_gtp_devs_open(); | ||||||
|  | void upf_gtp_devs_close(); | ||||||
|   | |||||||
							
								
								
									
										53
									
								
								include/osmocom/upf/upf_gtp.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								include/osmocom/upf/upf_gtp.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #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; | ||||||
|  |  | ||||||
|  | 	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(); | ||||||
|  |  | ||||||
|  | struct upf_gtp_dev *upf_gtp_dev_create(const char *name, const char *local_addr, bool listen_for_gtpv0, bool sgsn_mode); | ||||||
|  | struct upf_gtp_dev *upf_gtp_dev_use(const char *name); | ||||||
|  | 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); | ||||||
							
								
								
									
										25
									
								
								include/osmocom/upf/upf_nft.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								include/osmocom/upf/upf_nft.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include <stdint.h> | ||||||
|  |  | ||||||
|  | #include <osmocom/core/socket.h> | ||||||
|  |  | ||||||
|  | struct upf_nft_forw_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_forward_create(struct upf_nft_forw_desc *forw); | ||||||
|  | int upf_nft_forward_delete(struct upf_nft_forw_desc *forw); | ||||||
| @@ -1,6 +1,7 @@ | |||||||
| AM_CPPFLAGS = \ | AM_CPPFLAGS = \ | ||||||
| 	$(all_includes) \ | 	$(all_includes) \ | ||||||
| 	-I$(top_srcdir)/include \ | 	-I$(top_srcdir)/include \ | ||||||
|  | 	-I$(top_builddir)/include \ | ||||||
| 	-I$(top_builddir) \ | 	-I$(top_builddir) \ | ||||||
| 	$(NULL) | 	$(NULL) | ||||||
|  |  | ||||||
| @@ -9,10 +10,12 @@ AM_CFLAGS = \ | |||||||
| 	$(LIBOSMOCORE_CFLAGS) \ | 	$(LIBOSMOCORE_CFLAGS) \ | ||||||
| 	$(LIBOSMOVTY_CFLAGS) \ | 	$(LIBOSMOVTY_CFLAGS) \ | ||||||
| 	$(LIBOSMOCTRL_CFLAGS) \ | 	$(LIBOSMOCTRL_CFLAGS) \ | ||||||
|  | 	$(LIBGTPNL_CFLAGS) \ | ||||||
| 	$(COVERAGE_CFLAGS) \ | 	$(COVERAGE_CFLAGS) \ | ||||||
| 	$(NULL) | 	$(NULL) | ||||||
|  |  | ||||||
| AM_LDFLAGS = \ | AM_LDFLAGS = \ | ||||||
|  | 	$(LIBGTPNL_LDFLAGS) \ | ||||||
| 	$(COVERAGE_LDFLAGS) \ | 	$(COVERAGE_LDFLAGS) \ | ||||||
| 	$(NULL) | 	$(NULL) | ||||||
|  |  | ||||||
| @@ -22,12 +25,21 @@ bin_PROGRAMS = \ | |||||||
|  |  | ||||||
| osmo_upf_SOURCES = \ | osmo_upf_SOURCES = \ | ||||||
| 	osmo_upf_main.c \ | 	osmo_upf_main.c \ | ||||||
|  | 	up_endpoint.c \ | ||||||
|  | 	up_gtp_action.c \ | ||||||
|  | 	up_peer.c \ | ||||||
|  | 	up_session.c \ | ||||||
| 	upf.c \ | 	upf.c \ | ||||||
|  | 	upf_gtp.c \ | ||||||
|  | 	upf_vty.c \ | ||||||
| 	$(NULL) | 	$(NULL) | ||||||
|  |  | ||||||
| osmo_upf_LDADD = \ | osmo_upf_LDADD = \ | ||||||
|  | 	$(top_builddir)/src/libosmo-pfcp/libosmo-pfcp.a \ | ||||||
|  | 	$(top_builddir)/src/libosmo-tlv/libosmo-tlv.a \ | ||||||
| 	$(LIBOSMOCORE_LIBS) \ | 	$(LIBOSMOCORE_LIBS) \ | ||||||
| 	$(LIBOSMOVTY_LIBS) \ | 	$(LIBOSMOVTY_LIBS) \ | ||||||
| 	$(LIBOSMOCTRL_LIBS) \ | 	$(LIBOSMOCTRL_LIBS) \ | ||||||
|  | 	$(LIBGTPNL_LIBS) \ | ||||||
| 	$(COVERAGE_LDFLAGS) \ | 	$(COVERAGE_LDFLAGS) \ | ||||||
| 	$(NULL) | 	$(NULL) | ||||||
|   | |||||||
| @@ -31,11 +31,16 @@ | |||||||
| #include <osmocom/vty/cpu_sched_vty.h> | #include <osmocom/vty/cpu_sched_vty.h> | ||||||
| #include <osmocom/vty/telnet_interface.h> | #include <osmocom/vty/telnet_interface.h> | ||||||
| #include <osmocom/vty/ports.h> | #include <osmocom/vty/ports.h> | ||||||
|  | #include <osmocom/vty/tdef_vty.h> | ||||||
| #include <osmocom/ctrl/control_if.h> | #include <osmocom/ctrl/control_if.h> | ||||||
| #include <osmocom/ctrl/control_vty.h> | #include <osmocom/ctrl/control_vty.h> | ||||||
| #include <osmocom/ctrl/ports.h> | #include <osmocom/ctrl/ports.h> | ||||||
|  |  | ||||||
|  | #include <osmocom/pfcp/pfcp_endpoint.h> | ||||||
|  |  | ||||||
| #include <osmocom/upf/upf.h> | #include <osmocom/upf/upf.h> | ||||||
|  | #include <osmocom/upf/up_endpoint.h> | ||||||
|  | #include <osmocom/upf/upf_gtp.h> | ||||||
|  |  | ||||||
| #define _GNU_SOURCE | #define _GNU_SOURCE | ||||||
| #include <getopt.h> | #include <getopt.h> | ||||||
| @@ -45,6 +50,7 @@ | |||||||
|  |  | ||||||
| #include <signal.h> | #include <signal.h> | ||||||
| #include <stdio.h> | #include <stdio.h> | ||||||
|  | #include <string.h> | ||||||
|  |  | ||||||
| extern void *tall_vty_ctx; | extern void *tall_vty_ctx; | ||||||
|  |  | ||||||
| @@ -175,7 +181,7 @@ static void signal_handler(int signum) | |||||||
| 		 * return, but program wouldn't exit if an external SIGABRT is | 		 * return, but program wouldn't exit if an external SIGABRT is | ||||||
| 		 * received. | 		 * received. | ||||||
| 		 */ | 		 */ | ||||||
| 		talloc_report(tall_vty_ctx, stderr); | 		//talloc_report(tall_vty_ctx, stderr); | ||||||
| 		talloc_report_full(tall_upf_ctx, stderr); | 		talloc_report_full(tall_upf_ctx, stderr); | ||||||
| 		signal(SIGABRT, SIG_DFL); | 		signal(SIGABRT, SIG_DFL); | ||||||
| 		raise(SIGABRT); | 		raise(SIGABRT); | ||||||
| @@ -204,6 +210,30 @@ static struct vty_app_info upf_vty_app_info = { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| static const struct log_info_cat upf_default_categories[] = { | 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 = { | const struct log_info log_info = { | ||||||
| @@ -214,19 +244,27 @@ const struct log_info log_info = { | |||||||
| int main(int argc, char **argv) | int main(int argc, char **argv) | ||||||
| { | { | ||||||
| 	int rc; | 	int rc; | ||||||
|  | 	void *tall_infra_ctx; | ||||||
|  |  | ||||||
| 	/* Track the use of talloc NULL memory contexts */ | 	/* Track the use of talloc NULL memory contexts */ | ||||||
| 	talloc_enable_null_tracking(); | 	talloc_enable_null_tracking(); | ||||||
|  |  | ||||||
| 	osmo_fsm_set_dealloc_ctx(OTC_SELECT); | 	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; | 	upf_vty_app_info.tall_ctx = tall_upf_ctx; | ||||||
|  |  | ||||||
| 	msgb_talloc_ctx_init(tall_upf_ctx, 0); | 	msgb_talloc_ctx_init(tall_upf_ctx, 0); | ||||||
| 	osmo_signal_talloc_ctx_init(tall_upf_ctx); | 	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_timeouts(true); | ||||||
| 	osmo_fsm_log_addr(true); | 	osmo_fsm_log_addr(true); | ||||||
| @@ -243,6 +281,9 @@ int main(int argc, char **argv) | |||||||
| 	osmo_talloc_vty_add_cmds(); | 	osmo_talloc_vty_add_cmds(); | ||||||
| 	osmo_cpu_sched_vty_init(tall_upf_ctx); | 	osmo_cpu_sched_vty_init(tall_upf_ctx); | ||||||
|  |  | ||||||
|  | 	upf_vty_init(); | ||||||
|  | 	osmo_tdef_vty_groups_init(CONFIG_NODE, g_upf_tdef_groups); | ||||||
|  |  | ||||||
| 	/* Parse options */ | 	/* Parse options */ | ||||||
| 	handle_options(argc, argv); | 	handle_options(argc, argv); | ||||||
|  |  | ||||||
| @@ -280,12 +321,21 @@ 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 { | 	do { | ||||||
| 		log_reset_context(); | 		log_reset_context(); | ||||||
| 		osmo_select_main_ctx(0); | 		osmo_select_main_ctx(0); | ||||||
|  |  | ||||||
| 		/* If the user hits Ctrl-C the third time, just terminate immediately. */ | 		/* If the user hits Ctrl-C the third time, just terminate immediately. */ | ||||||
| 		if (quit >= 3) | 		if (quit >= 1) //3) | ||||||
| 			break; | 			break; | ||||||
|  |  | ||||||
| 		/* Has SIGTERM been received (and not yet been handled)? */ | 		/* Has SIGTERM been received (and not yet been handled)? */ | ||||||
| @@ -298,16 +348,24 @@ int main(int argc, char **argv) | |||||||
| 		} | 		} | ||||||
| 	} while (!osmo_select_shutdown_done()); | 	} 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... */ | 	/* Report the heap state of talloc contexts, then free, so both ASAN and Valgrind are happy... */ | ||||||
| 	talloc_report_full(tall_upf_ctx, stderr); | 	talloc_report_full(tall_upf_ctx, stderr); | ||||||
| 	talloc_free(tall_upf_ctx); | 	talloc_free(tall_upf_ctx); | ||||||
|  |  | ||||||
| 	talloc_report_full(tall_vty_ctx, stderr); | 	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); | 	talloc_free(tall_vty_ctx); | ||||||
|  |  | ||||||
| 	talloc_report_full(NULL, stderr); | 	//talloc_report_full(NULL, stderr); | ||||||
| 	talloc_disable_null_tracking(); | 	talloc_disable_null_tracking(); | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										353
									
								
								src/osmo-upf/up_endpoint.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										353
									
								
								src/osmo-upf/up_endpoint.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,353 @@ | |||||||
|  | /* (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> | ||||||
|  |  * All Rights Reserved | ||||||
|  |  * | ||||||
|  |  * Author: Neels Hofmeyr <nhofmeyr@sysmocom.de> | ||||||
|  |  * | ||||||
|  |  * This program is free software; you can redistribute it and/or modify | ||||||
|  |  * it under the terms of the GNU Affero General Public License as published by | ||||||
|  |  * the Free Software Foundation; either version 3 of the License, or | ||||||
|  |  * (at your option) any later version. | ||||||
|  |  * | ||||||
|  |  * This program is distributed in the hope that it will be useful, | ||||||
|  |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  |  * GNU Affero General Public License for more details. | ||||||
|  |  * | ||||||
|  |  * You should have received a copy of the GNU Affero General Public License | ||||||
|  |  * along with this program.  If not, see <http://www.gnu.org/lienses/>. | ||||||
|  |  * | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #include <osmocom/pfcp/pfcp_endpoint.h> | ||||||
|  | #include <osmocom/pfcp/pfcp_msg.h> | ||||||
|  | #include <osmocom/pfcp/pfcp_heartbeat_fsm.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 up_endpoint *up_ep = ep->priv; | ||||||
|  | 	struct up_peer *peer; | ||||||
|  |  | ||||||
|  | 	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); | ||||||
|  | 	return; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void up_ep_rx_heartbeat_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; | ||||||
|  | 	/* osmo_pfcp_endpoint_handle_rx() has already taken care of the heartbeat response. Just dispatch the event | ||||||
|  | 	 * here. */ | ||||||
|  | 	/* If the peer is not associated / not known, we don't care that a heartbeat happened. */ | ||||||
|  | 	if (!peer || !peer->heartbeat_fi) | ||||||
|  | 		return; | ||||||
|  | 	osmo_fsm_inst_dispatch(peer->heartbeat_fi, OSMO_PFCP_HEARTBEAT_EV_RX_REQ, (void*)m); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void up_ep_rx_heartbeat_resp(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) { | ||||||
|  | 		OSMO_LOG_PFCP_MSG(m, LOGL_INFO, "Heartbeat response from unknown peer %s\n", | ||||||
|  | 				  osmo_sockaddr_to_str_c(OTC_SELECT, &m->remote_addr)); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 	if (!peer->heartbeat_fi) { | ||||||
|  | 		OSMO_LOG_PFCP_MSG(m, LOGL_INFO, "Heartbeat response, but peer is not associated %s\n", | ||||||
|  | 				  osmo_sockaddr_to_str_c(OTC_SELECT, &m->remote_addr)); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 	osmo_fsm_inst_dispatch(peer->heartbeat_fi, OSMO_PFCP_HEARTBEAT_EV_RX_RESP, (void*)m); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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 up_endpoint *up_ep = ep->priv; | ||||||
|  |  | ||||||
|  | 	switch (m->h.message_type) { | ||||||
|  | 	case OSMO_PFCP_MSGT_HEARTBEAT_REQ: | ||||||
|  | 		up_ep_rx_heartbeat_req(up_ep, m); | ||||||
|  | 		return; | ||||||
|  | 	case OSMO_PFCP_MSGT_HEARTBEAT_RESP: | ||||||
|  | 		up_ep_rx_heartbeat_resp(up_ep, m); | ||||||
|  | 		return; | ||||||
|  | 	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; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static uint64_t up_endpoint_inc_seid(struct up_endpoint *ep) | ||||||
|  | { | ||||||
|  | 	ep->next_seid_state++; | ||||||
|  | 	if (!ep->next_seid_state) | ||||||
|  | 		ep->next_seid_state++; | ||||||
|  | 	return ep->next_seid_state; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | uint64_t up_endpoint_next_seid(struct up_endpoint *ep) | ||||||
|  | { | ||||||
|  | 	uint64_t sanity; | ||||||
|  | 	for (sanity = 2342; sanity; sanity--) { | ||||||
|  | 		uint64_t next_seid = up_endpoint_inc_seid(ep); | ||||||
|  | 		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; | ||||||
|  | } | ||||||
							
								
								
									
										160
									
								
								src/osmo-upf/up_gtp_action.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								src/osmo-upf/up_gtp_action.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,160 @@ | |||||||
|  | /* | ||||||
|  |  * (C) 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, write to the Free Software | ||||||
|  |  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, | ||||||
|  |  *  MA  02110-1301, USA. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #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_RET(MEMB) do { \ | ||||||
|  | 		int _cmp = OSMO_CMP(a->MEMB, b->MEMB); \ | ||||||
|  | 		if (_cmp) \ | ||||||
|  | 			return _cmp; \ | ||||||
|  | 	} while (0) | ||||||
|  |  | ||||||
|  | 	CMP_RET(kind); | ||||||
|  |  | ||||||
|  | 	switch (a->kind) { | ||||||
|  | 	case UP_GTP_U_ENDECAPS: | ||||||
|  | 		CMP_RET(endecaps.local_teid); | ||||||
|  | 		CMP_RET(endecaps.remote_teid); | ||||||
|  | 		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_FORW: | ||||||
|  | 		CMP_RET(forw.access.local_teid); | ||||||
|  | 		CMP_RET(forw.access.remote_teid); | ||||||
|  | 		CMP_RET(forw.core.local_teid); | ||||||
|  | 		CMP_RET(forw.core.remote_teid); | ||||||
|  | 		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_FORW: | ||||||
|  | 		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_FORW: | ||||||
|  | 		OSMO_STRBUF_PRINTF(sb, "GTP:forw GTP-access:"); | ||||||
|  | 		OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &a->forw.access.gtp_remote_addr); | ||||||
|  | 		OSMO_STRBUF_PRINTF(sb, " TEID-access-r:0x%"PRIx32" TEID-access-l:0x%"PRIx32" GTP-core:", | ||||||
|  | 				   a->forw.access.remote_teid, a->forw.access.local_teid); | ||||||
|  | 		OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_buf2, &a->forw.core.gtp_remote_addr); | ||||||
|  | 		OSMO_STRBUF_PRINTF(sb, " TEID-core-r:0x%"PRIx32" TEID-core-l:0x%"PRIx32, | ||||||
|  | 				   a->forw.core.remote_teid, a->forw.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) | ||||||
|  | } | ||||||
							
								
								
									
										550
									
								
								src/osmo-upf/up_peer.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										550
									
								
								src/osmo-upf/up_peer.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,550 @@ | |||||||
|  | /* (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> | ||||||
|  |  * All Rights Reserved | ||||||
|  |  * | ||||||
|  |  * Author: Neels Hofmeyr <nhofmeyr@sysmocom.de> | ||||||
|  |  * | ||||||
|  |  * This program is free software; you can redistribute it and/or modify | ||||||
|  |  * it under the terms of the GNU Affero General Public License as published by | ||||||
|  |  * the Free Software Foundation; either version 3 of the License, or | ||||||
|  |  * (at your option) any later version. | ||||||
|  |  * | ||||||
|  |  * This program is distributed in the hope that it will be useful, | ||||||
|  |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  |  * GNU Affero General Public License for more details. | ||||||
|  |  * | ||||||
|  |  * You should have received a copy of the GNU Affero General Public License | ||||||
|  |  * along with this program.  If not, see <http://www.gnu.org/lienses/>. | ||||||
|  |  * | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #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), | ||||||
|  | 	{0} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  | #if 1 | ||||||
|  | 	/* 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); | ||||||
|  | #endif | ||||||
|  | 	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 (m->ies.assoc_setup_req.cp_function_features_present) | ||||||
|  | 		peer->peer_cp_features = m->ies.assoc_setup_req.cp_function_features; | ||||||
|  |  | ||||||
|  | 	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"); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	peer->remote_recovery_timestamp = m->ies.assoc_setup_req.recovery_time_stamp; | ||||||
|  |  | ||||||
|  | 	if (up_peer_fsm_state_chg(UP_PEER_ST_ASSOCIATED)) { | ||||||
|  | 		/* Not allowed to transition to ST_ASSOCIATED */ | ||||||
|  | 		cause = OSMO_PFCP_CAUSE_REQUEST_REJECTED; | ||||||
|  | 	} else { | ||||||
|  | 		/* Remember the Node ID that the peer sent */ | ||||||
|  | 		struct osmo_pfcp_ie_node_id *m_node_id = osmo_pfcp_msg_node_id(m); | ||||||
|  | 		OSMO_ASSERT(m_node_id); | ||||||
|  | 		peer->remote_node_id = *m_node_id; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	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); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) | ||||||
|  | { | ||||||
|  | 	struct up_peer *peer = fi->priv; | ||||||
|  | 	LOGPFSML(fi, LOGL_NOTICE, "Peer associated. Local UP features: [%s]; Peer CP features: [%s]\n", | ||||||
|  | 		 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_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) | ||||||
|  | { | ||||||
|  | 	if (next_state != UP_PEER_ST_ASSOCIATED) | ||||||
|  | 		LOGPFSML(fi, LOGL_NOTICE, "Peer released\n"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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) | ||||||
|  | 			, | ||||||
|  | 		.onenter = up_peer_associated_onenter, | ||||||
|  | 		.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); | ||||||
|  | } | ||||||
							
								
								
									
										193
									
								
								src/osmo-upf/up_peer_fsm.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								src/osmo-upf/up_peer_fsm.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,193 @@ | |||||||
|  | /* (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> | ||||||
|  |  * All Rights Reserved | ||||||
|  |  * | ||||||
|  |  * Author: Neels Hofmeyr <nhofmeyr@sysmocom.de> | ||||||
|  |  * | ||||||
|  |  * This program is free software; you can redistribute it and/or modify | ||||||
|  |  * it under the terms of the GNU Affero General Public License as published by | ||||||
|  |  * the Free Software Foundation; either version 3 of the License, or | ||||||
|  |  * (at your option) any later version. | ||||||
|  |  * | ||||||
|  |  * This program is distributed in the hope that it will be useful, | ||||||
|  |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  |  * GNU Affero General Public License for more details. | ||||||
|  |  * | ||||||
|  |  * You should have received a copy of the GNU Affero General Public License | ||||||
|  |  * along with this program.  If not, see <http://www.gnu.org/lienses/>. | ||||||
|  |  * | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #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), | ||||||
|  | 	{0} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | 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); | ||||||
|  | } | ||||||
							
								
								
									
										1352
									
								
								src/osmo-upf/up_session.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1352
									
								
								src/osmo-upf/up_session.c
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -18,13 +18,74 @@ | |||||||
|  |  | ||||||
| #include <osmocom/core/utils.h> | #include <osmocom/core/utils.h> | ||||||
| #include <osmocom/core/talloc.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/upf.h> | ||||||
|  | #include <osmocom/upf/up_endpoint.h> | ||||||
|  | #include <osmocom/upf/upf_gtp.h> | ||||||
|  |  | ||||||
| struct g_upf *g_upf = NULL; | struct g_upf *g_upf = NULL; | ||||||
|  |  | ||||||
|  | struct osmo_tdef_group g_upf_tdef_groups[] = { | ||||||
|  | 	{ "pfcp", "PFCP endpoint timers", osmo_pfcp_tdefs, }, | ||||||
|  | 	{0} | ||||||
|  | }; | ||||||
|  |  | ||||||
| void g_upf_alloc(void *ctx) | void g_upf_alloc(void *ctx) | ||||||
| { | { | ||||||
| 	OSMO_ASSERT(g_upf == NULL); | 	OSMO_ASSERT(g_upf == NULL); | ||||||
| 	g_upf = talloc_zero(ctx, struct g_upf); | 	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 (d->create) { | ||||||
|  | 			if (!upf_gtp_dev_create(d->dev_name, d->local_addr, false, false)) | ||||||
|  | 				return -1; | ||||||
|  | 		} else { | ||||||
|  | 			if (!upf_gtp_dev_use(d->dev_name)) | ||||||
|  | 				return -1; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return 0; | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										454
									
								
								src/osmo-upf/upf_gtp.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										454
									
								
								src/osmo-upf/upf_gtp.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,454 @@ | |||||||
|  | /* (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> | ||||||
|  |  * All Rights Reserved | ||||||
|  |  * | ||||||
|  |  * This program is free software; you can redistribute it and/or modify | ||||||
|  |  * it under the terms of the GNU Affero General Public License as published by | ||||||
|  |  * the Free Software Foundation; either version 3 of the License, or | ||||||
|  |  * (at your option) any later version. | ||||||
|  |  * | ||||||
|  |  * This program is distributed in the hope that it will be useful, | ||||||
|  |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  |  * GNU Affero General Public License for more details. | ||||||
|  |  * | ||||||
|  |  * You should have received a copy of the GNU Affero 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_DEV(DEV, LEVEL, FMT, ARGS...) \ | ||||||
|  | 	LOGP(DGTP, LEVEL, "%s: " FMT, upf_gtp_dev_to_str_c(OTC_SELECT, (DEV)), ##ARGS) | ||||||
|  |  | ||||||
|  | #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); | ||||||
|  |  | ||||||
|  | 	llist_add(&dev->entry, &g_upf->gtp.devs); | ||||||
|  | 	talloc_set_destructor(dev, upf_gtp_dev_destruct); | ||||||
|  |  | ||||||
|  | 	return dev; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static struct upf_gtp_dev *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 NULL; | ||||||
|  | 	} | ||||||
|  | 	/* 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 NULL; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	LOG_GTP_DEV(dev, LOGL_NOTICE, "GTP device ready (ifidx=%u)\n", dev->ifidx); | ||||||
|  | 	return dev; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* To clean up and deallocate, just call talloc_free() on the returned upf_gtp_dev*. */ | ||||||
|  | struct upf_gtp_dev *upf_gtp_dev_create(const char *name, 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 gtp0_fd; | ||||||
|  | 	int gtp1_fd; | ||||||
|  | 	int rc; | ||||||
|  | 	struct upf_gtp_dev *dev = upf_gtp_dev_alloc(name, local_addr); | ||||||
|  | 	if (!dev) | ||||||
|  | 		return NULL; | ||||||
|  | 	dev->sgsn_mode = sgsn_mode; | ||||||
|  |  | ||||||
|  | 	if (listen_for_gtpv0) { | ||||||
|  | 		dev->gtpv0.enabled = true; | ||||||
|  | 		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); | ||||||
|  | 			return NULL; | ||||||
|  | 		} | ||||||
|  | 		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); | ||||||
|  | 		return NULL; | ||||||
|  | 	} | ||||||
|  | 	LOG_GTP_DEV(dev, LOGL_DEBUG, "GTPv1 bound\n"); | ||||||
|  |  | ||||||
|  | 	gtp0_fd = listen_for_gtpv0 ? dev->gtpv0.ofd.fd : -1; | ||||||
|  | 	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 NULL; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	LOG_GTP_DEV(dev, LOGL_NOTICE, "created GTP device\n"); | ||||||
|  | 	dev->created = true; | ||||||
|  |  | ||||||
|  | 	return dev_resolve_ifidx(dev); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | struct upf_gtp_dev *upf_gtp_dev_use(const char *name) | ||||||
|  | { | ||||||
|  | 	struct upf_gtp_dev *dev = upf_gtp_dev_alloc(name, NULL); | ||||||
|  | 	if (!dev) | ||||||
|  | 		return NULL; | ||||||
|  |  | ||||||
|  | 	return dev_resolve_ifidx(dev); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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) | ||||||
|  | { | ||||||
|  | 	if (a == b) | ||||||
|  | 		return 0; | ||||||
|  | 	if (!a) | ||||||
|  | 		return -1; | ||||||
|  | 	if (!b) | ||||||
|  | 		return 1; | ||||||
|  |  | ||||||
|  | #define CMP_RET(MEMB) do { \ | ||||||
|  | 		int _cmp = OSMO_CMP(a->MEMB, b->MEMB); \ | ||||||
|  | 		if (_cmp) \ | ||||||
|  | 			return _cmp; \ | ||||||
|  | 	} while (0) | ||||||
|  |  | ||||||
|  | 	CMP_RET(local_teid); | ||||||
|  | 	CMP_RET(remote_teid); | ||||||
|  | 	return osmo_sockaddr_cmp(&a->gtp_remote_addr, &b->gtp_remote_addr); | ||||||
|  | } | ||||||
							
								
								
									
										281
									
								
								src/osmo-upf/upf_vty.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										281
									
								
								src/osmo-upf/upf_vty.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,281 @@ | |||||||
|  | /* OsmoUpf interface to quagga VTY */ | ||||||
|  | /* (C) 2021-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> | ||||||
|  |  * All Rights Reserved | ||||||
|  |  * | ||||||
|  |  * This program is free software; you can redistribute it and/or modify | ||||||
|  |  * it under the terms of the GNU Affero General Public License as published by | ||||||
|  |  * the Free Software Foundation; either version 3 of the License, or | ||||||
|  |  * (at your option) any later version. | ||||||
|  |  * | ||||||
|  |  * This program is distributed in the hope that it will be useful, | ||||||
|  |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  |  * GNU Affero General Public License for more details. | ||||||
|  |  * | ||||||
|  |  * You should have received a copy of the GNU Affero 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# configure terminal | ||||||
| OsmoUPF(config)# show running-config | 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