aboutsummaryrefslogtreecommitdiffstats
path: root/openbsc/include/openbsc/gtphub.h
blob: 9cb7605f85f2880960eb2f1b08996f04830328a9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
/* 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>
#include <osmocom/core/rate_ctr.h>

#include <openbsc/gprs_sgsn.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 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
};

enum gtphub_side_idx {
	GTPH_SIDE_SGSN = 0,
	GTPH_SIDE_GGSN = 1,
	GTPH_SIDE_N
};

#define for_each_side(I)	for (I = 0; I < GTPH_SIDE_N; I++)
#define for_each_plane(I)	for (I = 0; I < GTPH_PLANE_N; I++)
#define for_each_side_and_plane(I,J)	for_each_side(I) for_each_plane(J)

static inline int other_side_idx(int side_idx)
{
	return (side_idx + 1) & 1;
}

extern const char* const gtphub_plane_idx_names[GTPH_PLANE_N];
extern const uint16_t gtphub_plane_idx_default_port[GTPH_PLANE_N];

extern const char* const gtphub_side_idx_names[GTPH_SIDE_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);

/* Decode sa to gsna. Return 0 on success. If port is non-NULL, the port number
 * from sa is also returned. */
int gsn_addr_from_sockaddr(struct gsn_addr *gsna, uint16_t *port,
			   const struct osmo_sockaddr *sa);

/* 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 *item, time_t now);

/* Initialize to all-empty; must be called before using the item in any way. */
void expiring_item_init(struct expiring_item *item);

/* 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);

/* Expire all items. */
void expiry_clear(struct expiry *exq);


/* 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).
 */

typedef unsigned 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;
	nr_t nr_min;
	nr_t nr_max;
};

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, nr_t nr_min, nr_t nr_max);

/* 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);

/* Restart the timeout for the given mapping. mapping must be a member of map.
 */
void nr_map_refresh(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_EXPIRE_QUICKLY_SECS = 30; /* TODO is there a spec for this? */
static const int GTPH_EXPIRE_SLOWLY_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_gsns[GTPH_SIDE_N][GTPH_PLANE_N];
	struct gtphub_cfg_addr proxy[GTPH_SIDE_N][GTPH_PLANE_N];
	int sgsn_use_sender; /* Use sender, not GSN addr IE with std ports */
};


/* 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; /* a "cache" for (peer_addr->addr, port) */
	int last_restart_count; /* 0..255 = valid, all else means unknown */

	struct rate_ctr_group *counters_io;
};

struct gtphub_tunnel_endpoint {
	struct gtphub_peer_port *peer;
	uint32_t tei_orig; /* from/to peer */

	struct rate_ctr_group *counters_io;
};

struct gtphub_tunnel {
	struct llist_head entry;
	struct expiring_item expiry_entry;

	uint32_t tei_repl; /* unique TEI to replace peers' TEIs */
	struct gtphub_tunnel_endpoint endpoint[GTPH_SIDE_N][GTPH_PLANE_N];
};

struct gtphub_bind {
	struct gsn_addr local_addr;
	uint16_t local_port;
	struct osmo_fd ofd;

	/* list of struct gtphub_peer */
	struct llist_head peers;

	const char *label; /* For logging */
	struct rate_ctr_group *counters_io;
};

struct gtphub_resolved_ggsn {
	struct llist_head entry;
	struct expiring_item expiry_entry;

	/* The APN OI, the Operator Identifier, is the combined address,
	 * including parts of the IMSI and APN NI, and ending with ".gprs". */
	char apn_oi_str[GSM_APN_LENGTH];

	/* Which address and port we resolved that to. */
	struct gtphub_peer_port *peer;
};

struct gtphub {
	struct gtphub_bind to_gsns[GTPH_SIDE_N][GTPH_PLANE_N];

	/* pointers to an entry of to_gsns[s][p].peers */
	struct gtphub_peer_port *proxy[GTPH_SIDE_N][GTPH_PLANE_N];

	/* The TEI numbers will simply wrap and be reused, which will work out
	 * in practice. Problems would arise if one given peer maintained the
	 * same TEI for a time long enough for the TEI nr map to wrap an entire
	 * uint32_t; if a new TEI were mapped every second, this would take
	 * more than 100 years (in which a single given TEI must not time out)
	 * to cause a problem. */
	struct nr_pool tei_pool;

	struct llist_head tunnels; /* struct gtphub_tunnel */
	struct llist_head pending_deletes; /* opaque (gtphub.c) */

	struct llist_head ggsn_lookups; /* opaque (gtphub_ares.c) */
	struct llist_head resolved_ggsns; /* struct gtphub_resolved_ggsn */

	struct osmo_timer_list gc_timer;
	struct expiry expire_quickly;
	struct expiry expire_slowly;

	uint8_t restart_counter;

	int sgsn_use_sender;
};

struct gtp_packet_desc;


/* api */

int gtphub_vty_init(struct gtphub *global_hub, struct gtphub_cfg *global_cfg);
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,
		 uint8_t restart_counter);

/* Close all sockets, expire all maps and peers and free all allocations. The
 * struct is then unusable, unless gtphub_start() is run on it again. */
void gtphub_stop(struct gtphub *hub);

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);

/* Return a human readable description of tun in a static buffer. */
const char *gtphub_tunnel_str(struct gtphub_tunnel *tun);

/* Return 1 if all of tun's endpoints are fully established, 0 otherwise. */
int gtphub_tunnel_complete(struct gtphub_tunnel *tun);

int gtphub_handle_buf(struct gtphub *hub,
		      unsigned int side_idx,
		      unsigned int port_idx,
		      const struct osmo_sockaddr *from_addr,
		      uint8_t *buf,
		      size_t received,
		      time_t now,
		      uint8_t **reply_buf,
		      struct osmo_fd **to_ofd,
		      struct osmo_sockaddr *to_addr);

struct gtphub_peer_port *gtphub_port_have(struct gtphub *hub,
					  struct gtphub_bind *bind,
					  const struct gsn_addr *addr,
					  uint16_t port);

struct gtphub_peer_port *gtphub_port_find_sa(const struct gtphub_bind *bind,
					     const struct osmo_sockaddr *addr);

void gtphub_resolved_ggsn(struct gtphub *hub, const char *apn_oi_str,
			  struct gsn_addr *resolved_addr,
			  time_t now);

const char *gtphub_port_str(struct gtphub_peer_port *port);

int gtphub_write(const struct osmo_fd *to,
		 const struct osmo_sockaddr *to_addr,
		 const uint8_t *buf, size_t buf_len);