blob: 83604393a9ba6c8323bc2b0875a297a5ea76a331 [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];
119
120 inet_ntop(AF_INET, &sgsn->addr, buf, sizeof(buf));
121
122 struct pdp_priv_t *pdp, *pdp2;
123 llist_for_each_entry_safe(pdp, pdp2, &sgsn->pdp_list, entry) {
124 if (pdp == except)
125 continue;
126 ggsn_close_one_pdp(pdp->lib);
127 num++;
128 }
129
130 /* Note: if except is NULL, all pdp contexts are freed and sgsn is
131 already freed at this point */
132 LOGP(DGGSN, LOGL_INFO, "SGSN(%s) Dropped %u PDP contexts\n", buf, num);
133
134 return num;
135}
136
137unsigned int sgsn_peer_drop_all_pdp(struct sgsn_peer *sgsn)
138{
139 return sgsn_peer_drop_all_pdp_except(sgsn, NULL);
140}
141
142int sgsn_peer_handle_recovery(struct sgsn_peer *sgsn, struct pdp_t *pdp, uint8_t recovery)
143{
144 struct pdp_priv_t *pdp_priv = NULL;
145
146 if (sgsn->remote_restart_ctr == -1) {
147 /* First received ECHO RESPONSE, note the restart ctr */
148 sgsn->remote_restart_ctr = recovery;
149 } else if (sgsn->remote_restart_ctr != recovery) {
150 /* counter has changed (SGSN restart): release all PDP */
151 LOGSGSN(LOGL_NOTICE, sgsn, "SGSN recovery (%u->%u) pdp=%p, "
152 "releasing all%s PDP contexts\n",
153 sgsn->remote_restart_ctr, recovery, pdp, pdp ? " other" : "");
154 sgsn->remote_restart_ctr = recovery;
155 if (pdp)
156 pdp_priv = pdp->priv;
157 sgsn_peer_drop_all_pdp_except(sgsn, pdp_priv);
158 }
159 return 0;
160}