aboutsummaryrefslogtreecommitdiffstats
path: root/src/dgsm.c
blob: bfa5df833d033f72ad290a2972ba95e7c9a6a7d5 (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
/* Copyright 2019 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 <errno.h>
#include <osmocom/core/logging.h>
#include <osmocom/mslookup/mslookup_client.h>
#include <osmocom/mslookup/mslookup_client_mdns.h>
#include <osmocom/gsupclient/gsup_client.h>
#include <osmocom/gsupclient/cni_peer_id.h>
#include <osmocom/hlr/logging.h>
#include <osmocom/hlr/hlr.h>
#include <osmocom/hlr/db.h>
#include <osmocom/hlr/gsup_router.h>
#include <osmocom/hlr/gsup_server.h>
#include <osmocom/hlr/dgsm.h>
#include <osmocom/hlr/proxy.h>
#include <osmocom/hlr/remote_hlr.h>
#include <osmocom/hlr/mslookup_server.h>
#include <osmocom/hlr/mslookup_server_mdns.h>
#include <osmocom/hlr/dgsm.h>

void *dgsm_ctx = NULL;

static void resolve_hlr_result_cb(struct osmo_mslookup_client *client,
				  uint32_t request_handle,
				  const struct osmo_mslookup_query *query,
				  const struct osmo_mslookup_result *result)
{
	struct proxy *proxy = g_hlr->gs->proxy;
	struct proxy_subscr proxy_subscr;
	const struct osmo_sockaddr_str *remote_hlr_addr;

	/* A remote HLR is answering back, indicating that it is the home HLR for a given IMSI.
	 * There should be a mostly empty proxy entry for that IMSI.
	 * Add the remote address data in the proxy. */
	if (query->id.type != OSMO_MSLOOKUP_ID_IMSI) {
		LOGP(DDGSM, LOGL_ERROR, "Expected IMSI ID type in mslookup query+result: %s\n",
		     osmo_mslookup_result_name_c(OTC_SELECT, query, result));
		return;
	}

	if (result->rc != OSMO_MSLOOKUP_RC_RESULT) {
		LOG_DGSM(query->id.imsi, LOGL_ERROR, "Failed to resolve remote HLR: %s\n",
			 osmo_mslookup_result_name_c(OTC_SELECT, query, result));
		proxy_subscr_del(proxy, query->id.imsi);
		return;
	}

	if (osmo_sockaddr_str_is_nonzero(&result->host_v4))
		remote_hlr_addr = &result->host_v4;
	else if (osmo_sockaddr_str_is_nonzero(&result->host_v6))
		remote_hlr_addr = &result->host_v6;
	else {
		LOG_DGSM(query->id.imsi, LOGL_ERROR, "Invalid address for remote HLR: %s\n",
			 osmo_mslookup_result_name_c(OTC_SELECT, query, result));
		proxy_subscr_del(proxy, query->id.imsi);
		return;
	}

	if (proxy_subscr_get_by_imsi(&proxy_subscr, proxy, query->id.imsi)) {
		LOG_DGSM(query->id.imsi, LOGL_ERROR, "No proxy entry for mslookup result: %s\n",
			 osmo_mslookup_result_name_c(OTC_SELECT, query, result));
		return;
	}

	proxy_subscr_remote_hlr_resolved(proxy, &proxy_subscr, remote_hlr_addr);
}

/* Return true when the message has been handled by D-GSM. */
bool dgsm_check_forward_gsup_msg(struct osmo_gsup_req *req)
{
	struct proxy_subscr proxy_subscr;
	struct proxy *proxy = g_hlr->gs->proxy;
	struct osmo_mslookup_query query;
	struct osmo_mslookup_query_handling handling;
	uint32_t request_handle;

	/* If the IMSI is known in the local HLR, then we won't proxy. */
	if (db_subscr_exists_by_imsi(g_hlr->dbc, req->gsup.imsi) == 0)
		return false;

	/* Are we already forwarding this IMSI to a remote HLR? */
	if (proxy_subscr_get_by_imsi(&proxy_subscr, proxy, req->gsup.imsi) == 0) {
		proxy_subscr_forward_to_remote_hlr(proxy, &proxy_subscr, req);
		return true;
	}

	/* The IMSI is not known locally, so we want to proxy to a remote HLR, but no proxy entry exists yet. We need to
	 * look up the subscriber in remote HLRs via D-GSM mslookup, forward GSUP and reply once a result is back from
	 * there.  Defer message and kick off MS lookup. */

	/* Add a proxy entry without a remote address to indicate that we are busy querying for a remote HLR. */
	proxy_subscr = (struct proxy_subscr){};
	OSMO_STRLCPY_ARRAY(proxy_subscr.imsi, req->gsup.imsi);
	if (proxy_subscr_create_or_update(proxy, &proxy_subscr)) {
		osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "Failed to create proxy entry\n");
		return true;
	}

	/* Is a fixed gateway proxy configured? */
	if (osmo_sockaddr_str_is_nonzero(&g_hlr->mslookup.client.gsup_gateway_proxy)) {
		proxy_subscr_remote_hlr_resolved(proxy, &proxy_subscr, &g_hlr->mslookup.client.gsup_gateway_proxy);

		/* Proxy database modified, update info */
		if (proxy_subscr_get_by_imsi(&proxy_subscr, proxy, req->gsup.imsi)) {
			osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "Internal proxy error\n");
			return true;
		}

		proxy_subscr_forward_to_remote_hlr(proxy, &proxy_subscr, req);
		return true;
	}

	/* Kick off an mslookup for the remote HLR?  This check could be up first on the top, but do it only now so that
	 * if the mslookup client disconnected, we still continue to service open proxy entries. */
	if (!osmo_mslookup_client_active(g_hlr->mslookup.client.client)) {
		LOG_GSUP_REQ(req, LOGL_DEBUG, "mslookup client not running, cannot query remote home HLR\n");
		return false;
	}

	/* First spool message, then kick off mslookup. If the proxy denies this message type, then don't do anything. */
	if (proxy_subscr_forward_to_remote_hlr(proxy, &proxy_subscr, req)) {
		/* If the proxy denied forwarding, an error response was already generated. */
		return true;
	}

	query = (struct osmo_mslookup_query){
		.id = {
			.type = OSMO_MSLOOKUP_ID_IMSI,
		},
	};
	OSMO_STRLCPY_ARRAY(query.id.imsi, req->gsup.imsi);
	OSMO_STRLCPY_ARRAY(query.service, OSMO_MSLOOKUP_SERVICE_HLR_GSUP);
	handling = (struct osmo_mslookup_query_handling){
		.min_wait_milliseconds = g_hlr->mslookup.client.result_timeout_milliseconds,
		.result_cb = resolve_hlr_result_cb,
	};
	request_handle = osmo_mslookup_client_request(g_hlr->mslookup.client.client, &query, &handling);
	if (!request_handle) {
		LOG_DGSM(req->gsup.imsi, LOGL_ERROR, "Error dispatching mslookup query for home HLR: %s\n",
			 osmo_mslookup_result_name_c(OTC_SELECT, &query, NULL));
		proxy_subscr_del(proxy, req->gsup.imsi);
		/* mslookup seems to not be working. Try handling it locally. */
		return false;
	}

	return true;
}

void dgsm_init(void *ctx)
{
	dgsm_ctx = talloc_named_const(ctx, 0, "dgsm");
	INIT_LLIST_HEAD(&g_hlr->mslookup.server.local_site_services);

	g_hlr->mslookup.server.local_attach_max_age = 60 * 60;

	g_hlr->mslookup.client.result_timeout_milliseconds = 2000;

	g_hlr->gsup_unit_name.unit_name = "HLR";
	g_hlr->gsup_unit_name.serno = "unnamed-HLR";
	g_hlr->gsup_unit_name.swversion = PACKAGE_NAME "-" PACKAGE_VERSION;

	osmo_sockaddr_str_from_str(&g_hlr->mslookup.server.mdns.bind_addr,
				   OSMO_MSLOOKUP_MDNS_IP4, OSMO_MSLOOKUP_MDNS_PORT);
	osmo_sockaddr_str_from_str(&g_hlr->mslookup.client.mdns.query_addr,
				   OSMO_MSLOOKUP_MDNS_IP4, OSMO_MSLOOKUP_MDNS_PORT);
}

void dgsm_start(void *ctx)
{
	g_hlr->mslookup.client.client = osmo_mslookup_client_new(dgsm_ctx);
	OSMO_ASSERT(g_hlr->mslookup.client.client);
	g_hlr->mslookup.allow_startup = true;
	mslookup_server_mdns_config_apply();
	dgsm_mdns_client_config_apply();
}

void dgsm_stop()
{
	g_hlr->mslookup.allow_startup = false;
	mslookup_server_mdns_config_apply();
	dgsm_mdns_client_config_apply();
}

void dgsm_mdns_client_config_apply(void)
{
	/* Check whether to start/stop/restart mDNS client */
	const struct osmo_sockaddr_str *current_bind_addr;
	const char *current_domain_suffix;
	current_bind_addr = osmo_mslookup_client_method_mdns_get_bind_addr(g_hlr->mslookup.client.mdns.running);
	current_domain_suffix = osmo_mslookup_client_method_mdns_get_domain_suffix(g_hlr->mslookup.client.mdns.running);

	bool should_run = g_hlr->mslookup.allow_startup
		&& g_hlr->mslookup.client.enable && g_hlr->mslookup.client.mdns.enable;

	bool should_stop = g_hlr->mslookup.client.mdns.running &&
		(!should_run
		 || osmo_sockaddr_str_cmp(&g_hlr->mslookup.client.mdns.query_addr,
					  current_bind_addr)
		 || strcmp(g_hlr->mslookup.client.mdns.domain_suffix,
			   current_domain_suffix));

	if (should_stop) {
		osmo_mslookup_client_method_del(g_hlr->mslookup.client.client, g_hlr->mslookup.client.mdns.running);
		g_hlr->mslookup.client.mdns.running = NULL;
		LOGP(DDGSM, LOGL_NOTICE, "Stopped mslookup mDNS client\n");
	}

	if (should_run && !g_hlr->mslookup.client.mdns.running) {
		g_hlr->mslookup.client.mdns.running =
			osmo_mslookup_client_add_mdns(g_hlr->mslookup.client.client,
						      g_hlr->mslookup.client.mdns.query_addr.ip,
						      g_hlr->mslookup.client.mdns.query_addr.port,
						      -1,
						      g_hlr->mslookup.client.mdns.domain_suffix);
		if (!g_hlr->mslookup.client.mdns.running)
			LOGP(DDGSM, LOGL_ERROR, "Failed to start mslookup mDNS client with target "
			     OSMO_SOCKADDR_STR_FMT "\n",
			     OSMO_SOCKADDR_STR_FMT_ARGS(&g_hlr->mslookup.client.mdns.query_addr));
		else
			LOGP(DDGSM, LOGL_NOTICE, "Started mslookup mDNS client, sending mDNS requests to multicast "
			     OSMO_SOCKADDR_STR_FMT "\n",
			     OSMO_SOCKADDR_STR_FMT_ARGS(&g_hlr->mslookup.client.mdns.query_addr));
	}

	if (g_hlr->mslookup.client.enable && osmo_sockaddr_str_is_nonzero(&g_hlr->mslookup.client.gsup_gateway_proxy))
			LOGP(DDGSM, LOGL_NOTICE,
			     "mslookup client: all GSUP requests for unknown IMSIs will be forwarded to"
			     " gateway-proxy " OSMO_SOCKADDR_STR_FMT "\n",
			     OSMO_SOCKADDR_STR_FMT_ARGS(&g_hlr->mslookup.client.gsup_gateway_proxy));
}