First steps towards a new GTP hub. The aim is to mux GTP connections, so that multiple SGSN <--> GGSN links can pass through a single point. Background: allow having more than one SGSN, possibly in various remote locations. The recent addition of OAP to GSUP is related to the same background idea. (This is a collapsed patch of various changes that do not make sense to review in chronological order anymore, since a lot of it has thorougly transmorphed after it was first committed.) Sponsored-by: On-Waves ehfchanges/88/3188/1
parent
65482c919f
commit
c8a614d2e9
@ -0,0 +1,424 @@ |
||||
/* GTP Hub Implementation */ |
||||
|
||||
/* (C) 2015 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved |
||||
* |
||||
* Author: Neels Hofmeyr |
||||
* |
||||
* 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/>.
|
||||
*/ |
||||
|
||||
#pragma once |
||||
|
||||
#include <stdint.h> |
||||
#include <sys/socket.h> |
||||
|
||||
#include <osmocom/core/select.h> |
||||
#include <osmocom/core/timer.h> |
||||
|
||||
|
||||
/* support */ |
||||
|
||||
/* TODO move to osmocom/core/socket.c ? */ |
||||
#include <netdb.h> /* for IPPROTO_* etc */ |
||||
struct osmo_sockaddr { |
||||
struct sockaddr_storage a; |
||||
socklen_t l; |
||||
}; |
||||
|
||||
/* TODO move to osmocom/core/socket.c ? */ |
||||
/*! \brief Initialize a sockaddr
|
||||
* \param[out] addr Valid osmo_sockaddr pointer to write result to |
||||
* \param[in] family Address Family like AF_INET, AF_INET6, AF_UNSPEC |
||||
* \param[in] type Socket type like SOCK_DGRAM, SOCK_STREAM |
||||
* \param[in] proto Protocol like IPPROTO_TCP, IPPROTO_UDP |
||||
* \param[in] host Remote host name or IP address in string form |
||||
* \param[in] port Remote port number in host byte order |
||||
* \returns 0 on success, otherwise an error code (from getaddrinfo()). |
||||
* |
||||
* Copy the first result from a getaddrinfo() call with the given parameters to |
||||
* *addr and *addr_len. On error, do not change *addr and return nonzero. |
||||
*/ |
||||
int osmo_sockaddr_init(struct osmo_sockaddr *addr, |
||||
uint16_t family, uint16_t type, uint8_t proto, |
||||
const char *host, uint16_t port); |
||||
|
||||
/* Conveniently pass AF_UNSPEC, SOCK_DGRAM and IPPROTO_UDP to
|
||||
* osmo_sockaddr_init(). */ |
||||
static inline int osmo_sockaddr_init_udp(struct osmo_sockaddr *addr, |
||||
const char *host, uint16_t port) |
||||
{ |
||||
return osmo_sockaddr_init(addr, AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, host, port); |
||||
} |
||||
|
||||
/*! \brief convert sockaddr to human readable string.
|
||||
* \param[out] addr_str Valid pointer to a buffer of length addr_str_len. |
||||
* \param[in] addr_str_len Size of buffer addr_str points at. |
||||
* \param[out] port_str Valid pointer to a buffer of length port_str_len. |
||||
* \param[in] port_str_len Size of buffer port_str points at. |
||||
* \param[in] addr Binary representation as returned by osmo_sockaddr_init(). |
||||
* \param[in] flags flags as passed to getnameinfo(). |
||||
* \returns 0 on success, an error code on error. |
||||
* |
||||
* Return the IPv4 or IPv6 address string and the port (a.k.a. service) string |
||||
* representations of the given struct osmo_sockaddr in two caller provided |
||||
* char buffers. Flags of (NI_NUMERICHOST | NI_NUMERICSERV) return numeric |
||||
* address and port. Either one of addr_str or port_str may be NULL, in which |
||||
* case nothing is returned there. |
||||
* |
||||
* See also osmo_sockaddr_to_str() (less flexible, but much more convenient). */ |
||||
int osmo_sockaddr_to_strs(char *addr_str, size_t addr_str_len, |
||||
char *port_str, size_t port_str_len, |
||||
const struct osmo_sockaddr *addr, |
||||
int flags); |
||||
|
||||
|
||||
/*! \brief conveniently concatenate the parts returned by osmo_sockaddr_to_strs().
|
||||
* \param[in] addr Binary representation as returned by osmo_sockaddr_init(). |
||||
* \param[in] buf A buffer to use for string operations. |
||||
* \param[in] buf_len Length of the buffer. |
||||
* \returns Address string (in buffer). |
||||
* |
||||
* Compose a string of the numeric IP-address and port represented by *addr of |
||||
* the form "<ip-addr> port <port>". The returned string is valid until the |
||||
* next invocation of this function. |
||||
*/ |
||||
const char *osmo_sockaddr_to_strb(const struct osmo_sockaddr *addr, |
||||
char *buf, size_t buf_len); |
||||
|
||||
/*! \brief conveniently return osmo_sockaddr_to_strb() in a static buffer.
|
||||
* \param[in] addr Binary representation as returned by osmo_sockaddr_init(). |
||||
* \returns Address string in static buffer. |
||||
* |
||||
* See osmo_sockaddr_to_strb(). |
||||
* |
||||
* Note: only one osmo_sockaddr_to_str() call will work per print/log |
||||
* statement. For two or more, use osmo_sockaddr_to_strb() with a separate |
||||
* buffer each. |
||||
*/ |
||||
const char *osmo_sockaddr_to_str(const struct osmo_sockaddr *addr); |
||||
|
||||
/*! \brief compare two osmo_sockaddr.
|
||||
* \param[in] a The first address to compare. |
||||
* \param[in] b The other address to compare. |
||||
* \returns 0 if equal, otherwise -1 or 1. |
||||
*/ |
||||
int osmo_sockaddr_cmp(const struct osmo_sockaddr *a, const struct osmo_sockaddr *b); |
||||
|
||||
/*! \brief Overwrite *dst with *src.
|
||||
* Like memcpy(), but copy only the valid bytes. */ |
||||
void osmo_sockaddr_copy(struct osmo_sockaddr *dst, const struct osmo_sockaddr *src); |
||||
|
||||
|
||||
/* general */ |
||||
|
||||
enum gtphub_plane_idx { |
||||
GTPH_PLANE_CTRL = 0, |
||||
GTPH_PLANE_USER = 1, |
||||
GTPH_PLANE_N |
||||
}; |
||||
|
||||
extern const char* const gtphub_plane_idx_names[GTPH_PLANE_N]; |
||||
extern const uint16_t gtphub_plane_idx_default_port[GTPH_PLANE_N]; |
||||
|
||||
/* A host address in the form that is expected in the 7.7.32 GSN Address IE.
|
||||
* len is either 4 (IPv4) or 16 (IPv6), any other value is invalid. If no |
||||
* address is set, len shall be 0. */ |
||||
struct gsn_addr { |
||||
uint16_t len; |
||||
uint8_t buf[16]; |
||||
}; |
||||
|
||||
void gsn_addr_copy(struct gsn_addr *gsna, const struct gsn_addr *src); |
||||
int gsn_addr_from_str(struct gsn_addr *gsna, const char *numeric_addr_str); |
||||
|
||||
/* Return gsna in numeric string form, in a static buffer. */ |
||||
const char *gsn_addr_to_str(const struct gsn_addr *gsna); |
||||
|
||||
/* note: strbuf_len doesn't need to be larger than INET6_ADDRSTRLEN + 1. */ |
||||
const char *gsn_addr_to_strb(const struct gsn_addr *gsna, |
||||
char *strbuf, int strbuf_len); |
||||
|
||||
/* Return 1 on match, zero otherwise. */ |
||||
int gsn_addr_same(const struct gsn_addr *a, const struct gsn_addr *b); |
||||
|
||||
|
||||
/* expiry */ |
||||
|
||||
struct expiring_item; |
||||
typedef void (*del_cb_t)(struct expiring_item *); |
||||
|
||||
struct expiring_item { |
||||
struct llist_head entry; |
||||
time_t expiry; |
||||
del_cb_t del_cb; |
||||
}; |
||||
|
||||
struct expiry { |
||||
int expiry_in_seconds; |
||||
struct llist_head items; |
||||
}; |
||||
|
||||
/* Initialize an expiry queue. */ |
||||
void expiry_init(struct expiry *exq, int expiry_in_seconds); |
||||
|
||||
/* Add a new mapping, or restart the expiry timeout for an already listed mapping. */ |
||||
void expiry_add(struct expiry *exq, struct expiring_item *mapping, time_t now); |
||||
|
||||
/* Remove the given item from its expiry queue, and call item->del_cb, if set.
|
||||
* This sets item->del_cb to NULL and is harmless when run a second time on the |
||||
* same item, so the del_cb may choose to call this function, too, to allow |
||||
* deleting items from several code paths. */ |
||||
void expiring_item_del(struct expiring_item *item); |
||||
|
||||
/* Carry out due expiry of mappings. Must be invoked regularly.
|
||||
* 'now' is the current clock count in seconds and must correspond to the clock |
||||
* count passed to nr_map_add(). A monotonous clock counter should be used. */ |
||||
int expiry_tick(struct expiry *exq, time_t now); |
||||
|
||||
|
||||
/* number map */ |
||||
|
||||
/* A number map assigns a "random" mapped number to each user provided number.
|
||||
* If the same number is requested multiple times, the same mapped number is |
||||
* returned. |
||||
* |
||||
* Number maps plug into possibly shared pools and expiry queues, for example: |
||||
* |
||||
* mapA -----------+-> pool1 <-+-- mapB |
||||
* {10->1, 11->5} | {1, 2, 3, ...} | {10->2, 11->3} |
||||
* | | |
||||
* | | |
||||
* /-> \-> expiry1 <-/ |
||||
* | (30 seconds) |
||||
* | |
||||
* mapC -------+-----> pool2 <-+-- mapD |
||||
* {10->1, 11->3} {1, 2, 3, ...} | {10->2, 11->5} |
||||
* | |
||||
* expiry2 <-/ |
||||
* (60 seconds) |
||||
* |
||||
* A map contains mappings ("10->1"). Each map needs a number pool, which can |
||||
* be shared with other maps. Each new mapping receives a number from the pool, |
||||
* which is then unavailable to any other map using the same pool. |
||||
* |
||||
* A map may point at an expiry queue, in which case all mappings added to it |
||||
* are also appended to the expiry queue (using a separate llist entry in the |
||||
* mapping). Any number of maps may submit to the same expiry queue, if they |
||||
* desire the same expiry timeout. An expiry queue stores the mappings in |
||||
* chronological order, so that expiry checking is needed only from the start |
||||
* of the queue; hence only mappings with identical expiry timeout can be added |
||||
* to the same expiry queue. Upon expiry, a mapping is dropped from the map it |
||||
* was submitted at. expiry_tick() needs to be called regularly for each expiry |
||||
* queue. |
||||
* |
||||
* A nr_mapping can be embedded in a larger struct: each mapping can have a |
||||
* distinct destructor (del_cb), and each del_cb can figure out the container |
||||
* struct's address and free that upon expiry or manual deletion. So in expiry |
||||
* queues (and even maps), mappings of different container types can be mixed. |
||||
* This can help to drastically reduce the amount of unnecessary visits during |
||||
* expiry checking, for the case that no expiry is pending. An expiry queue |
||||
* always knows which mappings to expire next, because they are right at the |
||||
* start of its list. |
||||
* |
||||
* Mapping allocation and a del_cb are provided by the caller. If del_cb is |
||||
* NULL, no deallocation will be done (allowing statically allocated entries). |
||||
*/ |
||||
/* TODO at some point I thought the allocation & del_cb complexity was
|
||||
* needed/helpful, but by now it seems like overkill. Maybe lose that again. */ |
||||
|
||||
typedef int nr_t; |
||||
|
||||
/* Generator for unused numbers. So far this counts upwards from zero, but the
|
||||
* implementation may change in the future. Treat this like an opaque struct. |
||||
* If this becomes random, the tests need to be fixed. */ |
||||
struct nr_pool { |
||||
nr_t last_nr; |
||||
/* TODO add min, max, for safe wrapping */ |
||||
}; |
||||
|
||||
struct nr_mapping { |
||||
struct llist_head entry; |
||||
struct expiring_item expiry_entry; |
||||
|
||||
void *origin; |
||||
nr_t orig; |
||||
nr_t repl; |
||||
}; |
||||
|
||||
struct nr_map { |
||||
struct nr_pool *pool; /* multiple nr_maps can share a nr_pool. */ |
||||
struct expiry *add_items_to_expiry; |
||||
struct llist_head mappings; |
||||
}; |
||||
|
||||
|
||||
void nr_pool_init(struct nr_pool *pool); |
||||
|
||||
/* Return the next unused number from the nr_pool. */ |
||||
nr_t nr_pool_next(struct nr_pool *pool); |
||||
|
||||
/* Initialize the nr_mapping to zero/empty values. */ |
||||
void nr_mapping_init(struct nr_mapping *mapping); |
||||
|
||||
/* Remove the given mapping from its parent map and expiry queue, and call
|
||||
* mapping->del_cb, if set. */ |
||||
void nr_mapping_del(struct nr_mapping *mapping); |
||||
|
||||
/* Initialize an (already allocated) nr_map, and set the map's number pool.
|
||||
* Multiple nr_map instances may use the same nr_pool. Set the nr_map's expiry |
||||
* queue to exq, so that all added mappings are automatically expired after the |
||||
* time configured in exq. exq may be NULL to disable automatic expiry. */ |
||||
void nr_map_init(struct nr_map *map, struct nr_pool *pool, |
||||
struct expiry *exq); |
||||
|
||||
/* Add a new entry to the map. mapping->orig, mapping->origin and
|
||||
* mapping->del_cb must be set before calling this function. The remaining |
||||
* fields of *mapping will be overwritten. mapping->repl is set to the next |
||||
* available mapped number from map->pool. 'now' is the current clock count in |
||||
* seconds; if no map->expiry is used, just pass 0 for 'now'. */ |
||||
void nr_map_add(struct nr_map *map, struct nr_mapping *mapping, |
||||
time_t now); |
||||
|
||||
/* Return a known mapping from nr_orig and the given origin. If nr_orig is
|
||||
* unknown, return NULL. */ |
||||
struct nr_mapping *nr_map_get(const struct nr_map *map, |
||||
void *origin, nr_t nr_orig); |
||||
|
||||
/* Return a known mapping to nr_repl. If nr_repl is unknown, return NULL. */ |
||||
struct nr_mapping *nr_map_get_inv(const struct nr_map *map, nr_t nr_repl); |
||||
|
||||
/* Remove all mappings from map. */ |
||||
void nr_map_clear(struct nr_map *map); |
||||
|
||||
/* Return 1 if map has no entries, 0 otherwise. */ |
||||
int nr_map_empty(const struct nr_map *map); |
||||
|
||||
|
||||
/* config */ |
||||
|
||||
static const int GTPH_SEQ_MAPPING_EXPIRY_SECS = 30; /* TODO is there a spec for this? */ |
||||
static const int GTPH_TEI_MAPPING_EXPIRY_MINUTES = 6 * 60; /* TODO is there a spec for this? */ |
||||
|
||||
struct gtphub_cfg_addr { |
||||
const char *addr_str; |
||||
uint16_t port; |
||||
}; |
||||
|
||||
struct gtphub_cfg_bind { |
||||
struct gtphub_cfg_addr bind; |
||||
}; |
||||
|
||||
struct gtphub_cfg { |
||||
struct gtphub_cfg_bind to_sgsns[GTPH_PLANE_N]; |
||||
struct gtphub_cfg_bind to_ggsns[GTPH_PLANE_N]; |
||||
struct gtphub_cfg_addr sgsn_proxy[GTPH_PLANE_N]; |
||||
struct gtphub_cfg_addr ggsn_proxy[GTPH_PLANE_N]; |
||||
}; |
||||
|
||||
|
||||
/* state */ |
||||
|
||||
struct gtphub_peer { |
||||
struct llist_head entry; |
||||
|
||||
struct llist_head addresses; /* Alternatives, not load balancing. */ |
||||
struct nr_pool seq_pool; |
||||
struct nr_map seq_map; |
||||
}; |
||||
|
||||
struct gtphub_peer_addr { |
||||
struct llist_head entry; |
||||
|
||||
struct gtphub_peer *peer; |
||||
struct gsn_addr addr; |
||||
struct llist_head ports; |
||||
}; |
||||
|
||||
struct gtphub_peer_port { |
||||
struct llist_head entry; |
||||
|
||||
struct gtphub_peer_addr *peer_addr; |
||||
uint16_t port; |
||||
unsigned int ref_count; /* references from other peers' seq_maps */ |
||||
struct osmo_sockaddr sa; |
||||
}; |
||||
|
||||
struct gtphub_bind { |
||||
struct gsn_addr local_addr; |
||||
struct osmo_fd ofd; |
||||
|
||||
/* list of struct gtphub_peer */ |
||||
struct llist_head peers; |
||||
}; |
||||
|
||||
struct gtphub { |
||||
struct gtphub_bind to_sgsns[GTPH_PLANE_N]; |
||||
struct gtphub_bind to_ggsns[GTPH_PLANE_N]; |
||||
|
||||
/* pointers to an entry of to_sgsns[x].peers */ |
||||
struct gtphub_peer_port *sgsn_proxy[GTPH_PLANE_N]; |
||||
|
||||
/* pointers to an entry of to_ggsns[x].peers */ |
||||
struct gtphub_peer_port *ggsn_proxy[GTPH_PLANE_N]; |
||||
|
||||
struct nr_map tei_map[GTPH_PLANE_N]; |
||||
struct nr_pool tei_pool[GTPH_PLANE_N]; |
||||
|
||||
struct osmo_timer_list gc_timer; |
||||
struct expiry expire_seq_maps; |
||||
struct expiry expire_tei_maps; |
||||
}; |
||||
|
||||
struct gtp_packet_desc; |
||||
|
||||
|
||||
/* api */ |
||||
|
||||
int gtphub_vty_init(void); |
||||
int gtphub_cfg_read(struct gtphub_cfg *cfg, const char *config_file); |
||||
|
||||
/* Initialize and start gtphub: bind to ports, run expiry timers. */ |
||||
int gtphub_start(struct gtphub *hub, struct gtphub_cfg *cfg); |
||||
|
||||
time_t gtphub_now(void); |
||||
|
||||
/* Remove expired items, empty peers, ... */ |
||||
void gtphub_gc(struct gtphub *hub, time_t now); |
||||
|
||||
/* Return the string of the first address for this peer. */ |
||||
const char *gtphub_peer_str(struct gtphub_peer *peer); |
||||
/* Same with a different static buffer. We often want to print two peers. */ |
||||
const char *gtphub_peer_str2(struct gtphub_peer *peer); |
||||
|
||||
int gtphub_from_sgsns_handle_buf(struct gtphub *hub, |
||||
unsigned int port_idx, |
||||
const struct osmo_sockaddr *from_addr, |
||||
uint8_t *buf, |
||||
size_t received, |
||||
time_t now, |
||||
struct osmo_fd **to_ofd, |
||||
struct osmo_sockaddr *to_addr); |
||||
|
||||
int gtphub_from_ggsns_handle_buf(struct gtphub *hub, |
||||
unsigned int port_idx, |
||||
const struct osmo_sockaddr *from_addr, |
||||
uint8_t *buf, |
||||
size_t received, |
||||
time_t now, |
||||
struct osmo_fd **to_ofd, |
||||
struct osmo_sockaddr *to_addr); |
||||
|
||||
struct gtphub_peer_port *gtphub_port_find_sa(const struct gtphub_bind *bind, |
||||
const struct osmo_sockaddr *addr); |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,58 @@ |
||||
/* GTP Hub Implementation */ |
||||
|
||||
/* (C) 2015 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved |
||||
* |
||||
* gtphub_ext.c -- ext means extern. This file is kept separate so that these |
||||
* functions can be wrapped for gtphub_test.c. When a function and its callers |
||||
* are in the same compilational unit, the wrappability may be optimized away. |
||||
* |
||||
* Author: Neels Hofmeyr |
||||
* |
||||
* 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 <string.h> |
||||
|
||||
#include <openbsc/gtphub.h> |
||||
#include <osmocom/core/utils.h> |
||||
|
||||
#define __llist_first(head) (((head)->next == (head)) ? NULL : (head)->next) |
||||
#define llist_first(head, type, entry) llist_entry(__llist_first(head), type, entry) |
||||
|
||||
int gtphub_resolve_ggsn_addr(struct gtphub *hub, |
||||
struct osmo_sockaddr *result, |
||||
struct gtp_packet_desc *p) |
||||
{ |
||||
/* TODO This is just hardcodedly returning the first known address.
|
||||
* Should resolve from actual subscriber data. */ |
||||
struct gtphub_peer *peer = llist_first(&hub->to_ggsns[GTPH_PLANE_CTRL].peers, |
||||
struct gtphub_peer, entry); |
||||
if (!peer) |
||||
return -1; |
||||
|
||||
struct gtphub_peer_addr *pa = llist_first(&peer->addresses, |
||||
struct gtphub_peer_addr, entry); |
||||
if (!pa) |
||||
return -1; |
||||
|
||||
struct gtphub_peer_port *pp = llist_first(&pa->ports, |
||||
struct gtphub_peer_port, entry); |
||||
if (!pp) |
||||
return -1; |
||||
|
||||
*result = pp->sa; |
||||
return 0; |
||||
} |
||||
|
@ -0,0 +1,282 @@ |
||||
/* GTP Hub main program */ |
||||
|
||||
/* (C) 2015 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
|
||||
* All Rights Reserved |
||||
* |
||||
* Author: Neels Hofmeyr |
||||
* |
||||
* 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 <unistd.h> |
||||
#include <signal.h> |
||||
#include <string.h> |
||||
#include <errno.h> |
||||
|
||||
#define _GNU_SOURCE |
||||
#include <getopt.h> |
||||
|
||||
#include <osmocom/core/signal.h> |
||||
#include <osmocom/core/application.h> |
||||
#include <osmocom/core/logging.h> |
||||
#include <osmocom/core/utils.h> |
||||
#include <osmocom/core/rate_ctr.h> |
||||
|
||||
#include <osmocom/vty/logging.h> |
||||
#include <osmocom/vty/telnet_interface.h> |
||||
|
||||
#include <openbsc/debug.h> |
||||
#include <openbsc/gtphub.h> |
||||
#include <openbsc/vty.h> |
||||
|
||||
#include "../../bscconfig.h" |
||||
|
||||
#define LOGERR(fmt, args...) \ |
||||
LOGP(DGTPHUB, LOGL_ERROR, fmt, ##args) |
||||
|
||||
#define LOG(fmt, args...) \ |
||||
LOGP(DGTPHUB, LOGL_NOTICE, fmt, ##args) |
||||
|
||||
#ifndef OSMO_VTY_PORT_GTPHUB |
||||
/* should come from libosmocore */ |
||||
#define OSMO_VTY_PORT_GTPHUB 4253 |
||||
#endif |
||||
|
||||
extern void *osmo_gtphub_ctx; |
||||
|
||||
|
||||
const char *gtphub_copyright = |
||||
"Copyright (C) 2015 sysmocom s.f.m.c GmbH <info@sysmocom.de>\r\n" |
||||
"License AGPLv3+: GNU AGPL version 2 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n" |
||||
"This is free software: you are free to change and redistribute it.\r\n" |
||||
"There is NO WARRANTY, to the extent permitted by law.\r\n"; |
||||
|
||||
static struct log_info_cat gtphub_categories[] = { |
||||
[DGTPHUB] = { |
||||
.name = "DGTPHUB", |
||||
.description = "GTP Hub", |
||||
.color = "\033[1;33m", |
||||
.enabled = 1, .loglevel = LOGL_NOTICE, |
||||
}, |
||||
}; |
||||
|
||||
int gtphub_log_filter_fn(const struct log_context *ctx, |
||||
struct log_target *tar) |
||||
{ |
||||
return 0; |
||||
} |
||||
|
||||
static const struct log_info gtphub_log_info = { |
||||
.filter_fn = gtphub_log_filter_fn, |
||||
.cat = gtphub_categories, |
||||
.num_cat = ARRAY_SIZE(gtphub_categories), |
||||
}; |
||||
|
||||
void log_cfg(struct gtphub_cfg *cfg) |
||||
{ |
||||
struct gtphub_cfg_addr *a; |
||||
a = &cfg->to_sgsns[GTPH_PLANE_CTRL].bind; |
||||
LOG("to-SGSNs bind, Control: %s port %d\n", |
||||
a->addr_str, a->port); |
||||
a = &cfg->to_sgsns[GTPH_PLANE_USER].bind; |
||||
LOG("to-SGSNs bind, User: %s port %d\n", |
||||
a->addr_str, a->port); |
||||
a = &cfg->to_ggsns[GTPH_PLANE_CTRL].bind; |
||||
LOG("to-GGSNs bind, Control: %s port %d\n", |
||||
a->addr_str, a->port); |
||||
a = &cfg->to_ggsns[GTPH_PLANE_USER].bind; |
||||
LOG("to-GGSNs bind, User: %s port %d\n", |
||||
a->addr_str, a->port); |
||||
} |
||||
|
||||
static void signal_handler(int signal) |
||||
{ |
||||
fprintf(stdout, "signal %u received\n", signal); |
||||
|
||||
switch (signal) { |
||||
case SIGINT: |
||||
osmo_signal_dispatch(SS_L_GLOBAL, S_L_GLOBAL_SHUTDOWN, NULL); |
||||
sleep(1); |
||||
exit(0); |
||||
break; |
||||
case SIGABRT: |
||||
/* in case of abort, we want to obtain a talloc report
|
||||
* and then return to the caller, who will abort the process */ |
||||
case SIGUSR1: |
||||
case SIGUSR2: |
||||
talloc_report_full(osmo_gtphub_ctx, stderr); |
||||
break; |
||||
default: |
||||
break; |
||||
} |
||||
} |
||||
|
||||
extern int bsc_vty_go_parent(struct vty *vty); |
||||
|
||||
static struct vty_app_info vty_info = { |
||||
.name = "OsmoGTPhub", |
||||
.version = PACKAGE_VERSION, |
||||
.go_parent_cb = bsc_vty_go_parent, |
||||
.is_config_node = bsc_vty_is_config_node, |
||||
}; |
||||
|
||||
struct cmdline_cfg { |
||||
const char *config_file; |
||||
int daemonize; |
||||
}; |
||||
|
||||
static void print_help(struct cmdline_cfg *ccfg) |
||||
{ |
||||
printf("gtphub commandline options\n"); |
||||
printf(" -h --help This text.\n"); |
||||
printf(" -D --daemonize Fork the process into a background daemon.\n"); |
||||
printf(" -d,--debug <cat> Enable Debugging for this category.\n"); |
||||
printf(" Pass '-d list' to get a category listing.\n"); |
||||
printf(" -s --disable-color"); |
||||
printf(" -c --config-file The config file to use [%s].\n", ccfg->config_file); |
||||
printf(" -e,--log-level <nr> Set a global log level.\n"); |
||||
} |
||||
|
||||
static void list_categories(void) |
||||
{ |
||||
printf("Avaliable debug categories:\n"); |
||||
int i; |
||||
for (i = 0; i < gtphub_log_info.num_cat; ++i) { |
||||
if (!gtphub_log_info.cat[i].name) |
||||
continue; |
||||
|
||||
printf("%s\n", gtphub_log_info.cat[i].name); |
||||
} |
||||
} |
||||
|
||||
static void handle_options(struct cmdline_cfg *ccfg, int argc, char **argv) |
||||
{ |
||||
while (1) { |
||||
int option_index = 0, c; |
||||
static struct option long_options[] = { |
||||
{"help", 0, 0, 'h'}, |
||||
{"debug", 1, 0, 'd'}, |
||||
{"daemonize", 0, 0, 'D'}, |
||||
{"config-file", 1, 0, 'c'}, |
||||
{"disable-color", 0, 0, 's'}, |
||||
{"timestamp", 0, 0, 'T'}, |
||||
{"log-level", 1, 0, 'e'}, |
||||
{NULL, 0, 0, 0} |
||||
}; |
||||
|
||||
c = getopt_long(argc, argv, "hd:Dc:sTe:", |
||||
long_options, &option_index); |
||||
if (c == -1) |
||||
break; |
||||
|
||||
switch (c) { |
||||
case 'h': |
||||
//print_usage();
|
||||
print_help(ccfg); |
||||
exit(0); |
||||
case 's': |
||||
log_set_use_color(osmo_stderr_target, 0); |
||||
break; |
||||
case 'd': |
||||
if (strcmp("list", optarg) == 0) { |
||||
list_categories(); |
||||
exit(0); |
||||
} else |
||||
log_parse_category_mask(osmo_stderr_target, optarg); |
||||
break; |
||||
case 'D': |
||||
ccfg->daemonize = 1; |
||||
break; |
||||
case 'c': |
||||
ccfg->config_file = optarg; |
||||
break; |
||||
case 'T': |
||||
log_set_print_timestamp(osmo_stderr_target, 1); |
||||
break; |
||||
case 'e': |
||||
log_set_log_level(osmo_stderr_target, atoi(optarg)); |
||||
break; |
||||
default: |
||||
/* ignore */ |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
int main(int argc, char **argv) |
||||
{ |
||||
int rc; |
||||
|
||||
osmo_gtphub_ctx = talloc_named_const(NULL, 0, "osmo_gtphub"); |
||||
|
||||
signal(SIGINT, &signal_handler); |
||||
signal(SIGABRT, &signal_handler); |
||||
signal(SIGUSR1, &signal_handler); |
||||
signal(SIGUSR2, &signal_handler); |
||||
osmo_init_ignore_signals(); |
||||
|
||||
osmo_init_logging(>phub_log_info); |
||||
|
||||
vty_info.copyright = gtphub_copyright; |
||||
vty_init(&vty_info); |
||||
logging_vty_add_cmds(>phub_log_info); |
||||
gtphub_vty_init(); |
||||
|
||||
rate_ctr_init(osmo_gtphub_ctx); |
||||
rc = telnet_init(osmo_gtphub_ctx, 0, OSMO_VTY_PORT_GTPHUB); |
||||
if (rc < 0) |
||||
exit(1); |
||||
|
||||
struct cmdline_cfg _ccfg; |
||||
struct cmdline_cfg *ccfg = &_ccfg; |
||||
memset(ccfg, '\0', sizeof(*ccfg)); |
||||
ccfg->config_file = "./gtphub.conf"; |
||||
|
||||
struct gtphub_cfg _cfg; |
||||
struct gtphub_cfg *cfg = &_cfg; |
||||
memset(cfg, '\0', sizeof(*cfg)); |
||||
|
||||
struct gtphub _hub; |
||||
struct gtphub *hub = &_hub; |
||||
|
||||
handle_options(ccfg, argc, argv); |
||||
|
||||
rc = gtphub_cfg_read(cfg, ccfg->config_file); |
||||
if (rc < 0) { |
||||
LOGP(DGTPHUB, LOGL_FATAL, "Cannot parse config file '%s'\n", ccfg->config_file); |
||||
exit(2); |
||||
} |
||||
|
||||
if (gtphub_start(hub, cfg) != 0) |
||||
return -1; |
||||
|
||||
log_cfg(cfg); |
||||
|
||||
if (ccfg->daemonize) { |
||||
rc = osmo_daemonize(); |
||||
if (rc < 0) { |
||||
LOGERR("Error during daemonize"); |
||||
exit(1); |
||||
} |
||||
} |
||||
|
||||
while (1) { |
||||
rc = osmo_select_main(0); |
||||
if (rc < 0) |
||||
exit(3); |
||||
} |
||||
|
||||
/* not reached */ |
||||
exit(0); |
||||
} |
@ -0,0 +1,258 @@ |
||||
/* (C) 2015 by sysmocom s.f.m.c. GmbH
|
||||
* All Rights Reserved |
||||
* |
||||
* Author: Neels Hofmeyr |
||||
* |
||||
* 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 <string.h> |
||||
|
||||
#include <osmocom/core/talloc.h> |
||||
#include <osmocom/vty/command.h> |
||||
|
||||
#include <openbsc/vty.h> |
||||
#include <openbsc/gtphub.h> |
||||
|
||||
static struct gtphub_cfg *g_cfg = 0; |
||||
|
||||
static struct cmd_node gtphub_node = { |
||||
GTPHUB_NODE, |
||||
"%s(config-gtphub)# ", |
||||
1, |
||||
}; |
||||
|
||||
#define GTPH_DEFAULT_CONTROL_PORT 2123 |
||||
#define GTPH_DEFAULT_USER_PORT 2152 |
||||
|
||||
static void write_addrs(struct vty *vty, const char *name, |
||||
struct gtphub_cfg_addr *c, struct gtphub_cfg_addr *u) |
||||
{ |
||||
if ((c->port == GTPH_DEFAULT_CONTROL_PORT) |
||||
&& (u->port == GTPH_DEFAULT_USER_PORT) |
||||
&& (strcmp(c->addr_str, u->addr_str) == 0)) { |
||||
/* Default port numbers and same IP address: write "short"
|
||||
* variant. */ |
||||
vty_out(vty, " %s %s%s", |
||||
name, |
||||
c->addr_str, |
||||
VTY_NEWLINE); |
||||
return; |
||||
} |
||||
|
||||
vty_out(vty, " %s ctrl %s %d user %s %d%s", |
||||
name, |
||||
c->addr_str, (int)c->port, |
||||
u->addr_str, (int)u->port, |
||||
VTY_NEWLINE); |
||||
} |
||||
|
||||
static int config_write_gtphub(struct vty *vty) |
||||
{ |
||||
vty_out(vty, "gtphub%s", VTY_NEWLINE); |
||||
|
||||
write_addrs(vty, "bind-to-sgsns", |
||||
&g_cfg->to_sgsns[GTPH_PLANE_CTRL].bind, |
||||
&g_cfg->to_sgsns[GTPH_PLANE_USER].bind); |
||||
|
||||
write_addrs(vty, "bind-to-ggsns", |
||||
&g_cfg->to_ggsns[GTPH_PLANE_CTRL].bind, |
||||
&g_cfg->to_ggsns[GTPH_PLANE_USER].bind); |
||||
|
||||
if (g_cfg->ggsn_proxy[GTPH_PLANE_CTRL].addr_str) { |
||||
write_addrs(vty, "ggsn-proxy", |
||||
&g_cfg->ggsn_proxy[GTPH_PLANE_CTRL], |
||||
&g_cfg->ggsn_proxy[GTPH_PLANE_USER]); |
||||
} |
||||
|
||||
return CMD_SUCCESS; |
||||
} |
||||
|
||||
DEFUN(cfg_gtphub, cfg_gtphub_cmd, |
||||
"gtphub", |
||||
"Configure the GTP hub") |
||||
{ |
||||
vty->node = GTPHUB_NODE; |
||||
return CMD_SUCCESS; |
||||
} |
||||
|
||||
#define BIND_ARGS "ctrl ADDR <0-65535> user ADDR <0-65535>" |
||||
#define BIND_DOCS \ |
||||
"Set GTP-C bind\n" \
|
||||
"GTP-C local IP address (v4 or v6)\n" \
|
||||
"GTP-C local port\n" \
|
||||
"Set GTP-U bind\n" \
|
||||
"GTP-U local IP address (v4 or v6)\n" \
|
||||
"GTP-U local port\n" |
||||
|
||||
|
||||
DEFUN(cfg_gtphub_bind_to_sgsns_short, cfg_gtphub_bind_to_sgsns_short_cmd, |
||||
"bind-to-sgsns ADDR", |
||||
"GTP Hub Parameters\n" |
||||
"Set the local bind address to listen for SGSNs, for both GTP-C and GTP-U\n" |
||||
"Local IP address (v4 or v6)\n" |
||||
) |
||||
{ |
||||
int i; |
||||
for (i = 0; i < GTPH_PLANE_N; i++) |
||||
g_cfg->to_sgsns[i].bind.addr_str = talloc_strdup(tall_vty_ctx, argv[0]); |
||||
g_cfg->to_sgsns[GTPH_PLANE_CTRL].bind.port = GTPH_DEFAULT_CONTROL_PORT; |
||||
g_cfg->to_sgsns[GTPH_PLANE_USER].bind.port = GTPH_DEFAULT_USER_PORT; |
||||
return CMD_SUCCESS; |
||||
} |
||||
|
||||
DEFUN(cfg_gtphub_bind_to_ggsns_short, cfg_gtphub_bind_to_ggsns_short_cmd, |
||||
"bind-to-ggsns ADDR", |
||||
"GTP Hub Parameters\n" |
||||
"Set the local bind address to listen for GGSNs, for both GTP-C and GTP-U\n" |
||||
"Local IP address (v4 or v6)\n" |
||||
) |
||||
{ |
||||
int i; |
||||
for (i = 0; i < GTPH_PLANE_N; i++) |
||||
g_cfg->to_ggsns[i].bind.addr_str = talloc_strdup(tall_vty_ctx, argv[0]); |
||||
g_cfg->to_ggsns[GTPH_PLANE_CTRL].bind.port = GTPH_DEFAULT_CONTROL_PORT; |
||||
g_cfg->to_ggsns[GTPH_PLANE_USER].bind.port = GTPH_DEFAULT_USER_PORT; |
||||
return CMD_SUCCESS; |
||||
} |
||||
|
||||
|
||||
static int handle_binds(struct gtphub_cfg_bind *b, const char **argv) |
||||
{ |
||||
b[GTPH_PLANE_CTRL].bind.addr_str = talloc_strdup(tall_vty_ctx, argv[0]); |
||||
b[GTPH_PLANE_CTRL].bind.port = atoi(argv[1]); |
||||
b[GTPH_PLANE_USER].bind.addr_str = talloc_strdup(tall_vty_ctx, argv[2]); |
||||
b[GTPH_PLANE_USER].bind.port = atoi(argv[3]); |
||||
return CMD_SUCCESS; |
||||
} |
||||
|
||||
DEFUN(cfg_gtphub_bind_to_sgsns, cfg_gtphub_bind_to_sgsns_cmd, |
||||
"bind-to-sgsns " BIND_ARGS, |
||||
"GTP Hub Parameters\n" |
||||
"Set the local bind addresses and ports to listen for SGSNs\n" |
||||
BIND_DOCS |
||||
) |
||||
{ |
||||
return handle_binds(g_cfg->to_sgsns, argv); |
||||
} |
||||
|
||||
DEFUN(cfg_gtphub_bind_to_ggsns, cfg_gtphub_bind_to_ggsns_cmd, |
||||
"bind-to-ggsns " BIND_ARGS, |
||||
"GTP Hub Parameters\n" |
||||
"Set the local bind addresses and ports to listen for GGSNs\n" |
||||
BIND_DOCS |
||||
) |
||||
{ |
||||
return handle_binds(g_cfg->to_ggsns, argv); |
||||
} |
||||
|
||||
DEFUN(cfg_gtphub_ggsn_proxy_short, cfg_gtphub_ggsn_proxy_short_cmd, |
||||
"ggsn-proxy ADDR", |
||||
"GTP Hub Parameters\n" |
||||
"Redirect all GGSN bound traffic to default ports on this address (another gtphub)\n" |
||||
"Remote IP address (v4 or v6)\n" |
||||
) |
||||
{ |
||||
g_cfg->ggsn_proxy[GTPH_PLANE_CTRL].addr_str = talloc_strdup(tall_vty_ctx, argv[0]); |
||||
g_cfg->ggsn_proxy[GTPH_PLANE_CTRL].port = GTPH_DEFAULT_CONTROL_PORT; |
||||
g_cfg->ggsn_proxy[GTPH_PLANE_USER].addr_str = talloc_strdup(tall_vty_ctx, argv[0]); |
||||
g_cfg->ggsn_proxy[GTPH_PLANE_USER].port = GTPH_DEFAULT_USER_PORT; |
||||
return CMD_SUCCESS; |
||||
} |
||||
|
||||
DEFUN(cfg_gtphub_ggsn_proxy, cfg_gtphub_ggsn_proxy_cmd, |
||||
"ggsn-proxy " BIND_ARGS, |
||||
"GTP Hub Parameters\n" |
||||
"Redirect all GGSN bound traffic to these addresses and ports (another gtphub)\n" |
||||
BIND_DOCS |
||||
) |
||||
{ |
||||
g_cfg->ggsn_proxy[GTPH_PLANE_CTRL].addr_str = talloc_strdup(tall_vty_ctx, argv[0]); |
||||
g_cfg->ggsn_proxy[GTPH_PLANE_CTRL].port = atoi(argv[1]); |
||||
g_cfg->ggsn_proxy[GTPH_PLANE_USER].addr_str = talloc_strdup(tall_vty_ctx, argv[2]); |
||||
g_cfg->ggsn_proxy[GTPH_PLANE_USER].port = atoi(argv[3]); |
||||
return CMD_SUCCESS; |
||||
} |
||||
|
||||
DEFUN(cfg_gtphub_sgsn_proxy_short, cfg_gtphub_sgsn_proxy_short_cmd, |
||||
"sgsn-proxy ADDR", |
||||
"GTP Hub Parameters\n" |
||||
"Redirect all SGSN bound traffic to default ports on this address (another gtphub)\n" |
||||
"Remote IP address (v4 or v6)\n" |
||||
) |
||||
{ |
||||
g_cfg->sgsn_proxy[GTPH_PLANE_CTRL].addr_str = talloc_strdup(tall_vty_ctx, argv[0]); |
||||
g_cfg->sgsn_proxy[GTPH_PLANE_CTRL].port = GTPH_DEFAULT_CONTROL_PORT; |
||||
g_cfg->sgsn_proxy[GTPH_PLANE_USER].addr_str = talloc_strdup(tall_vty_ctx, argv[0]); |
||||
g_cfg->sgsn_proxy[GTPH_PLANE_USER].port = GTPH_DEFAULT_USER_PORT; |
||||
return CMD_SUCCESS; |
||||
} |
||||
|
||||
DEFUN(cfg_gtphub_sgsn_proxy, cfg_gtphub_sgsn_proxy_cmd, |
||||
"sgsn-proxy " BIND_ARGS, |
||||
"GTP Hub Parameters\n" |
||||
"Redirect all SGSN bound traffic to these addresses and ports (another gtphub)\n" |
||||
BIND_DOCS |
||||
) |
||||
{ |
||||
g_cfg->sgsn_proxy[GTPH_PLANE_CTRL].addr_str = talloc_strdup(tall_vty_ctx, argv[0]); |
||||
g_cfg->sgsn_proxy[GTPH_PLANE_CTRL].port = atoi(argv[1]); |
||||
g_cfg->sgsn_proxy[GTPH_PLANE_USER].addr_str = talloc_strdup(tall_vty_ctx, argv[2]); |
||||
g_cfg->sgsn_proxy[GTPH_PLANE_USER].port = atoi(argv[3]); |
||||
return CMD_SUCCESS; |
||||
} |
||||
|
||||
DEFUN(show_gtphub, show_gtphub_cmd, "show gtphub", |
||||
SHOW_STR "Display information about the GTP hub") |
||||
{ |
||||
vty_out(vty, "gtphub has nothing to say yet%s", VTY_NEWLINE); |
||||
return CMD_SUCCESS; |
||||
} |
||||
|
||||
|
||||
int gtphub_vty_init(void) |
||||
{ |
||||
install_element_ve(&show_gtphub_cmd); |
||||
|
||||
install_element(CONFIG_NODE, &cfg_gtphub_cmd); |
||||
install_node(>phub_node, config_write_gtphub); |
||||
vty_install_default(GTPHUB_NODE); |
||||
|
||||
install_element(GTPHUB_NODE, &cfg_gtphub_bind_to_sgsns_short_cmd); |
||||
install_element(GTPHUB_NODE, &cfg_gtphub_bind_to_sgsns_cmd); |
||||
install_element(GTPHUB_NODE, &cfg_gtphub_bind_to_ggsns_short_cmd); |
||||
install_element(GTPHUB_NODE, &cfg_gtphub_bind_to_ggsns_cmd); |
||||
install_element(GTPHUB_NODE, &cfg_gtphub_ggsn_proxy_short_cmd); |
||||
install_element(GTPHUB_NODE, &cfg_gtphub_ggsn_proxy_cmd); |
||||
install_element(GTPHUB_NODE, &cfg_gtphub_sgsn_proxy_short_cmd); |
||||
install_element(GTPHUB_NODE, &cfg_gtphub_sgsn_proxy_cmd); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
int gtphub_cfg_read(struct gtphub_cfg *cfg, const char *config_file) |
||||
{ |
||||
int rc; |
||||
|
||||
g_cfg = cfg; |
||||
|
||||
rc = vty_read_config_file(config_file, NULL); |
||||
if (rc < 0) { |
||||
fprintf(stderr, "Failed to parse the config file: '%s'\n", config_file); |
||||
return rc; |
||||
} |
||||
|
||||
return 0; |
||||
} |
@ -0,0 +1,20 @@ |
||||
AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include
|
||||
AM_CFLAGS=-Wall -ggdb3 $(LIBOSMOCORE_CFLAGS)
|
||||
|
||||
EXTRA_DIST = \
|
||||
gtphub_test.ok \
|
||||
gtphub_nc_test.sh \
|
||||
gtphub_nc_test.ok \
|
||||
hex2bin.py
|
||||
|
||||
noinst_PROGRAMS = gtphub_test
|
||||
|
||||
gtphub_test_SOURCES = gtphub_test.c
|
||||
gtphub_test_LDFLAGS = \
|
||||
-Wl,--wrap=gtphub_resolve_ggsn_addr
|
||||
|
||||
gtphub_test_LDADD = \
|
||||
$(top_builddir)/src/gprs/gtphub.o \
|
||||
$(LIBOSMOCORE_LIBS) \
|
||||
-lgtp -lrt
|
||||
|
@ -0,0 +1,709 @@ |
||||
/* Test the GTP hub */ |
||||
|
||||
/* (C) 2015 by sysmocom s.f.m.c. GmbH
|
||||
* All Rights Reserved |
||||
* |
||||
* Author: Neels Hofmeyr <nhofmeyr@sysmcom.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/licenses/>.
|
||||
* |
||||
*/ |
||||
|
||||
#include <stdio.h> |
||||
#include <string.h> |
||||
#include <limits.h> |
||||
#include <unistd.h> |
||||
|
||||
#include <osmocom/core/utils.h> |
||||
#include <osmocom/core/msgb.h> |
||||
#include <osmocom/core/application.h> |
||||
|
||||
#include <openbsc/debug.h> |
||||
|
||||
#include <openbsc/gtphub.h> |
||||
#include <gtp.h> |
||||
#include <gtpie.h> |
||||
|
||||
#define EXPIRE_ALL ((60 * GTPH_TEI_MAPPING_EXPIRY_MINUTES) + 1) |
||||
|
||||
/* Make non-public API accessible */ |
||||
|
||||
void gtphub_init(struct gtphub *hub); |
||||
|
||||
void *osmo_gtphub_ctx; |
||||
|
||||
/* TODO copied from libosmo-abis/src/subchan_demux.c, remove dup */ |
||||
static int llist_len(struct llist_head *head) |
||||
{ |
||||
struct llist_head *entry; |
||||
int i = 0; |
||||
|
||||
llist_for_each(entry, head) |
||||
i++; |
||||
|
||||
return i; |
||||
} |
||||
|
||||
static void nr_mapping_free(struct expiring_item *e) |
||||
{ |
||||
struct nr_mapping *m = container_of(e, struct nr_mapping, |
||||
expiry_entry); |
||||
nr_mapping_del(m); |
||||
talloc_free(m); |
||||
} |
||||
|
||||
static struct nr_mapping *nr_mapping_alloc(void) |
||||
{ |
||||
struct nr_mapping *m; |
||||
m = talloc(osmo_gtphub_ctx, struct nr_mapping); |
||||
nr_mapping_init(m); |
||||
m->expiry_entry.del_cb = nr_mapping_free; |
||||
return m; |
||||
} |
||||
|
||||
static struct nr_mapping *nr_map_have(struct nr_map *map, void *origin, nr_t orig, time_t now) |
||||
{ |
||||
struct nr_mapping *mapping; |
||||
|
||||
mapping = nr_map_get(map, origin, orig); |
||||
if (!mapping) { |
||||
mapping = nr_mapping_alloc(); |
||||
mapping->origin = origin; |
||||
mapping->orig = orig; |
||||
nr_map_add(map, mapping, now); |
||||
} |
||||
|
||||
return mapping; |
||||
} |
||||
|
||||
static nr_t nr_map_verify(const struct nr_map *map, void *origin, nr_t orig, nr_t expect_repl) |
||||
{ |
||||
struct nr_mapping *m; |
||||
m = nr_map_get(map, origin, orig); |
||||
|
||||
if (!m) { |
||||
printf("mapping not found for %p %d\n", origin, orig); |
||||
return 0; |
||||
} |
||||
|
||||
if (m->repl != expect_repl) { |
||||
printf("mapping found, but nr mismatches: expect %d, got %d\n", |
||||
(int)expect_repl, (int)m->repl); |
||||
return 0; |
||||
} |
||||
|
||||
return 1; |
||||
} |
||||
|
||||
static int nr_map_verify_inv(const struct nr_map *map, nr_t repl, |
||||
void *expect_origin, nr_t expect_orig) |
||||
{ |
||||
struct nr_mapping *m; |
||||
m = nr_map_get_inv(map, repl); |
||||
if (!m) { |
||||
printf("mapping not found for %d\n", (int)repl); |
||||
return 0; |
||||
} |
||||
|
||||
if (m->origin != expect_origin) { |
||||
printf("mapping found, but origin mismatches: expect %p, got %p\n", |
||||
expect_origin, m->origin); |
||||
return 0; |
||||
} |
||||
|
||||
if (m->orig != expect_orig) { |
||||
printf("mapping found, but nr mismatches: expect %d, got %d\n", |
||||
(int)expect_orig, (int)m->orig); |
||||
return 0; |
||||
} |
||||
|
||||
return 1; |
||||
} |
||||
|
||||
|
||||
static void test_nr_map_basic(void) |
||||
{ |
||||
struct nr_pool _pool; |
||||
struct nr_pool *pool = &_pool; |
||||
struct nr_map _map; |
||||
struct nr_map *map = &_map; |
||||
|
||||
nr_pool_init(pool); |
||||
nr_map_init(map, pool, NULL); |
||||
|
||||
OSMO_ASSERT(llist_empty(&map->mappings)); |
||||
|
||||
#define TEST_N_HALF 100 |
||||
#define TEST_N (2*TEST_N_HALF) |
||||
#define TEST_I 123 |
||||
uint32_t i, check_i; |
||||
uint32_t m[TEST_N]; |
||||
struct nr_mapping *mapping; |
||||
|
||||
/* create half of TEST_N mappings from one origin */ |
||||
void *origin1 = (void*)0x1234; |
||||
for (i = 0; i < TEST_N_HALF; i++) { |
||||
nr_t orig = TEST_I + i; |
||||
mapping = nr_map_have(map, origin1, orig, 0); |
||||
m[i] = mapping->repl; |
||||
OSMO_ASSERT(m[i] != 0); |
||||
OSMO_ASSERT(llist_len(&map->mappings) == (i+1)); |
||||
for (check_i = 0; check_i < i; check_i++) |
||||
OSMO_ASSERT(m[check_i] != m[i]); |
||||
} |
||||
OSMO_ASSERT(llist_len(&map->mappings) == TEST_N_HALF); |
||||
|
||||
/* create another TEST_N mappings with the same original numbers, but
|
||||
* from a different origin */ |
||||
void *origin2 = (void*)0x5678; |
||||
for (i = 0; i < TEST_N_HALF; i++) { |
||||
int i2 = TEST_N_HALF + i; |
||||
nr_t orig = TEST_I + i; |
||||
mapping = nr_map_have(map, origin2, orig, 0); |
||||
m[i2] = mapping->repl; |
||||
OSMO_ASSERT(m[i2] != 0); |
||||
OSMO_ASSERT(llist_len(&map->mappings) == (i2+1)); |
||||
for (check_i = 0; check_i < i2; check_i++) |
||||
OSMO_ASSERT(m[check_i] != m[i2]); |
||||
} |
||||
OSMO_ASSERT(llist_len(&map->mappings) == TEST_N); |
||||
|
||||
/* verify mappings */ |
||||
for (i = 0; i < TEST_N_HALF; i++) { |
||||
nr_t orig = TEST_I + i; |
||||
{ |
||||
OSMO_ASSERT(nr_map_verify(map, origin1, orig, m[i])); |
||||
OSMO_ASSERT(nr_map_verify_inv(map, m[i], origin1, orig)); |
||||
} |
||||
{ |
||||
int i2 = TEST_N_HALF + i; |
||||
OSMO_ASSERT(nr_map_verify(map, origin2, orig, m[i2])); |
||||
OSMO_ASSERT(nr_map_verify_inv(map, m[i2], origin2, orig)); |
||||
} |
||||
} |
||||
|
||||
/* remove all mappings */ |
||||
for (i = 0; i < TEST_N_HALF; i++) { |
||||
OSMO_ASSERT(llist_len(&map->mappings) == (TEST_N - 2*i)); |
||||
|
||||
nr_t orig = TEST_I + i; |
||||
nr_mapping_del(nr_map_get(map, origin1, orig)); |
||||
nr_mapping_del(nr_map_get(map, origin2, orig)); |
||||
} |
||||
OSMO_ASSERT(llist_empty(&map->mappings)); |
||||
#undef TEST_N |
||||
#undef TEST_I |
||||
} |
||||
|
||||
static int nr_map_is(struct nr_map *map, const char *str) |
||||
{ |
||||
static char buf[4096]; |
||||
char *pos = buf; |
||||
size_t len = sizeof(buf); |
||||
struct nr_mapping *m; |
||||
llist_for_each_entry(m, &map->mappings, entry) { |
||||
size_t wrote = snprintf(pos, len, "(%d->%d@%d), ", |
||||
(int)m->orig, |
||||
(int)m->repl, |
||||
(int)m->expiry_entry.expiry); |
||||
OSMO_ASSERT(wrote < len); |
||||
pos += wrote; |
||||
len -= wrote; |
||||
} |
||||
*pos = '\0'; |
||||
|
||||
if (strncmp(buf, str, sizeof(buf)) != 0) { |
||||
printf("FAILURE: nr_map_is() mismatches expected value:\n" |
||||
"expected: \"%s\"\n" |
||||
"is: \"%s\"\n", |
||||
str, buf); |
||||
return 0; |
||||
} |
||||
return 1; |
||||
} |
||||
|
||||
static void test_expiry(void) |
||||
{ |
||||
struct expiry expiry; |
||||
struct nr_pool pool; |
||||
struct nr_map map; |
||||
int i; |
||||
|
||||
expiry_init(&expiry, 30); |
||||
nr_pool_init(&pool); |
||||
nr_map_init(&map, &pool, &expiry); |
||||
|