blob: 15548efd8fdd75223e93ebf8317c861b55f194b0 [file] [log] [blame]
Pau Espin Pedrolf5fbb412019-08-21 18:49:44 +02001#include "sgsn.h"
2#include "ggsn.h"
3
4
5static bool sgsn_peer_attempt_free(struct sgsn_peer *sgsn)
6{
7 /* We have to be careful here, since if all pdp ctx for that sgsn were
8 deactivated in-between we sent the Echo Req and receivied the timeout
9 indication, the sgsn (cbp) may be already gone. We need to add some
10 counter reference of echo requets in flight and only free sgsn
11 structures when it goes to zero decreased for all Echo Resp. We do it
12 this way because currently in libgtp there's no understanding of "gsn
13 peer" for which messages are grouped and hence we cannot request
14 libgtp to drop all queued messages for a specific peer. */
15 if (sgsn->tx_msgs_queued) {
16 LOGSGSN(LOGL_INFO, sgsn, "Delaying delete, still %u echo messages queued\n",
17 sgsn->tx_msgs_queued);
18 return false;
19 }
20 llist_del(&sgsn->entry);
21 LOGSGSN(LOGL_INFO, sgsn, "Deleting SGSN\n");
22 talloc_free(sgsn);
23 return true;
24}
25
26static void sgsn_peer_echo_req(struct sgsn_peer *sgsn)
27{
28 struct ggsn_ctx *ggsn = sgsn->ggsn;
29 LOGSGSN(LOGL_INFO, sgsn, "Tx Echo Request\n");
30 gtp_echo_req(ggsn->gsn, sgsn->gtp_version, sgsn, &sgsn->addr);
31 sgsn->tx_msgs_queued++;
32}
33
34void sgsn_peer_echo_resp(struct sgsn_peer *sgsn, bool timeout)
35{
36 if (timeout) {
37 LOGSGSN(LOGL_NOTICE, sgsn, "Rx Echo Request timed out!\n");
38 sgsn_peer_drop_all_pdp(sgsn);
39 } else {
40 LOGSGSN(LOGL_INFO, sgsn, "Rx Echo Response\n");
41 }
42
43 /* We decrement it here after dropping all pdps to make sure sgsn was
44 not freed upon last pdp ctx deleted and is still alive now */
45 sgsn->tx_msgs_queued--;
46 if (llist_empty(&sgsn->pdp_list))
47 sgsn_peer_attempt_free(sgsn);
48}
49
50void sgsn_echo_timer_start(struct sgsn_peer *sgsn)
51{
52 if (sgsn->ggsn->cfg.echo_interval == 0)
53 return;
54 sgsn_peer_echo_req(sgsn);
55 osmo_timer_schedule(&sgsn->echo_timer, sgsn->ggsn->cfg.echo_interval, 0);
56}
57
58void sgsn_echo_timer_stop(struct sgsn_peer *sgsn)
59{
60 osmo_timer_del(&sgsn->echo_timer);
61}
62
63static void sgsn_echo_timer_cb(void *data)
64{
65 struct sgsn_peer *sgsn = (struct sgsn_peer *) data;
66 sgsn_echo_timer_start(sgsn);
67}
68
69struct sgsn_peer *sgsn_peer_allocate(struct ggsn_ctx *ggsn, struct in_addr *ia, unsigned int gtp_version)
70{
71 struct sgsn_peer *sgsn;
72
73 sgsn = talloc_zero_size(ggsn, sizeof(struct sgsn_peer));
74 sgsn->ggsn = ggsn;
75 sgsn->addr = *ia;
76 sgsn->gtp_version = gtp_version;
77 sgsn->remote_restart_ctr = -1;
78 INIT_LLIST_HEAD(&sgsn->pdp_list);
79 INIT_LLIST_HEAD(&sgsn->entry);
80
81 osmo_timer_setup(&sgsn->echo_timer, sgsn_echo_timer_cb, sgsn);
82
83 LOGSGSN(LOGL_INFO, sgsn, "Discovered\n");
84 return sgsn;
85}
86
87void sgsn_peer_add_pdp_priv(struct sgsn_peer *sgsn, struct pdp_priv_t *pdp_priv)
88{
89 bool was_empty = llist_empty(&sgsn->pdp_list);
90 pdp_priv->sgsn = sgsn;
91 llist_add(&pdp_priv->entry, &sgsn->pdp_list);
92 if (was_empty)
93 sgsn_echo_timer_start(sgsn);
94}
95
96void sgsn_peer_remove_pdp_priv(struct pdp_priv_t* pdp_priv)
97{
98 struct sgsn_peer *sgsn = pdp_priv->sgsn;
99 llist_del(&pdp_priv->entry);
100 if (sgsn && llist_empty(&sgsn->pdp_list)) {
101 /* No PDP contexts associated to this SGSN, no need to keep it */
102 sgsn_echo_timer_stop(sgsn);
103 /* sgsn may not be freed if there are some messages still queued
104 in libgtp which could return a pointer to it */
105 sgsn_peer_attempt_free(sgsn);
106 }
107
108 pdp_priv->sgsn = NULL;
109}
110
111/* High-level function to be called in case a GGSN has disappeared or
112 * otherwise lost state (recovery procedure). It will detach all related pdp ctx
113 * from a ggsn and communicate deact to MS. Optionally (!NULL), one pdp ctx can
114 * be kept alive to allow handling later message which contained the Recovery IE. */
115static unsigned int sgsn_peer_drop_all_pdp_except(struct sgsn_peer *sgsn, struct pdp_priv_t *except)
116{
117 unsigned int num = 0;
118 char buf[INET_ADDRSTRLEN];
Pau Espin Pedrolb6a0e3f2021-06-10 19:40:48 +0200119 unsigned int count = llist_count(&sgsn->pdp_list);
Pau Espin Pedrolf5fbb412019-08-21 18:49:44 +0200120
121 inet_ntop(AF_INET, &sgsn->addr, buf, sizeof(buf));
122
123 struct pdp_priv_t *pdp, *pdp2;
124 llist_for_each_entry_safe(pdp, pdp2, &sgsn->pdp_list, entry) {
125 if (pdp == except)
126 continue;
127 ggsn_close_one_pdp(pdp->lib);
128 num++;
Pau Espin Pedrolb6a0e3f2021-06-10 19:40:48 +0200129 if (num == count) {
130 /* Note: if except is NULL, all pdp contexts are freed and sgsn
131 * is most probably already freed at this point.
132 * As a result, last access to sgsn->pdp_list before exiting
133 * loop would access already freed memory. Avoid it by exiting
134 * the loop without the last check, and make sure sgsn is not
135 * accessed after this loop. */
136 break;
137 }
Pau Espin Pedrolf5fbb412019-08-21 18:49:44 +0200138 }
139
Pau Espin Pedrolf5fbb412019-08-21 18:49:44 +0200140 LOGP(DGGSN, LOGL_INFO, "SGSN(%s) Dropped %u PDP contexts\n", buf, num);
141
142 return num;
143}
144
145unsigned int sgsn_peer_drop_all_pdp(struct sgsn_peer *sgsn)
146{
147 return sgsn_peer_drop_all_pdp_except(sgsn, NULL);
148}
149
150int sgsn_peer_handle_recovery(struct sgsn_peer *sgsn, struct pdp_t *pdp, uint8_t recovery)
151{
152 struct pdp_priv_t *pdp_priv = NULL;
153
154 if (sgsn->remote_restart_ctr == -1) {
155 /* First received ECHO RESPONSE, note the restart ctr */
156 sgsn->remote_restart_ctr = recovery;
157 } else if (sgsn->remote_restart_ctr != recovery) {
158 /* counter has changed (SGSN restart): release all PDP */
159 LOGSGSN(LOGL_NOTICE, sgsn, "SGSN recovery (%u->%u) pdp=%p, "
160 "releasing all%s PDP contexts\n",
161 sgsn->remote_restart_ctr, recovery, pdp, pdp ? " other" : "");
162 sgsn->remote_restart_ctr = recovery;
163 if (pdp)
164 pdp_priv = pdp->priv;
165 sgsn_peer_drop_all_pdp_except(sgsn, pdp_priv);
166 }
167 return 0;
168}