blob: d399e3a8ce9952eb30176d2a221d451f0bea7be3 [file] [log] [blame]
Oliver Smithbf7deda2019-11-20 10:56:35 +01001/* Copyright 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
2 *
3 * All Rights Reserved
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19#include <string.h>
20#include <errno.h>
21#include <osmocom/gsm/gsm23003.h>
22#include <osmocom/mslookup/mslookup.h>
23
24/*! \addtogroup mslookup
25 *
26 * Distributed GSM: finding subscribers
27 *
28 * There are various aspects of the D-GSM code base in osmo-hlr.git, here is an overview:
29 *
30 * mslookup is the main enabler of D-GSM, a concept for connecting services between independent core network stacks.
31 *
32 * D-GSM consists of:
33 * (1) mslookup client to find subscribers:
34 * (a) external clients like ESME, SIP PBX, ... ask osmo-hlr to tell where to send SMS, voice calls, ...
35 * (b) osmo-hlr's own mslookup client asks remote osmo-hlrs whether they know a given IMSI.
36 * (2) when a subscriber was found at a remote HLR, GSUP gets forwarded there:
37 * (a) to deliver messages for the GSUP proxy, osmo-hlr manages many GSUP clients to establish links to remote HLRs.
38 * (b) osmo-hlr has a GSUP proxy layer that caches data of IMSIs that get proxied to a remote HLR.
39 * (c) decision making to distinguish local IMSIs from ones proxied to a remote HLR.
40 *
41 * (1) mslookup is a method of finding subscribers using (multicast) queries, by MSISDN or by IMSI.
42 * It is open to various lookup methods, the first one being multicast DNS.
43 * An mslookup client sends a request, and an mslookup server responds.
44 * The mslookup server is implemented by osmo-hlr. mslookup clients are arbitrary programs, like an ESME or a SIP PBX.
45 * Hence the mslookup client is public API, while the mslookup server is implemented "privately" in osmo-hlr.
46 *
47 * (1a) Public mslookup client: libosmo-mslookup
48 * src/mslookup/mslookup.c Things useful for both client and server.
49 * src/mslookup/mslookup_client.c The client API, which can use various lookup methods,
50 * and consolidates results from various responders.
51 * src/mslookup/mslookup_client_mdns.c lookup method implementing multicast DNS, client side.
52 *
53 * src/mslookup/osmo-mslookup-client.c Utility program to ease invocation for (blocking) mslookup clients.
54 *
55 * src/mslookup/mslookup_client_fake.c lookup method generating fake results, for testing client implementations.
56 *
57 * src/mslookup/mdns*.c implementation of DNS to be used by mslookup_client_mdns.c,
58 * and the mslookup_server.c.
59 *
60 * contrib/dgsm/esme_dgsm.py Example implementation for an mslookup enabled SMS handler.
61 * contrib/dgsm/freeswitch_dialplan_dgsm.py Example implementation for an mslookup enabled FreeSWITCH dialplan.
62 * contrib/dgsm/osmo-mslookup-pipe.py Example for writing a python client using the osmo-mslookup-client
63 * cmdline.
64 * contrib/dgsm/osmo-mslookup-socket.py Example for writing a python client using the osmo-mslookup-client
65 * unix domain socket.
66 *
67 * (1b) "Private" mslookup server in osmo-hlr:
68 * src/mslookup_server.c Respond to mslookup queries, independent from the particular lookup method.
69 * src/mslookup_server_mdns.c mDNS specific implementation for mslookup_server.c.
70 * src/dgsm_vty.c Configure services that mslookup server sends to remote requests.
71 *
72 * (2) Proxy and GSUP clients to remote HLR instances:
73 *
74 * (a) Be a GSUP client to forward to a remote HLR:
75 * src/gsupclient/ The same API that is used by osmo-{msc,sgsn} is also used to forward GSUP to remote osmo-hlrs.
76 * src/remote_hlr.c Establish links to remote osmo-hlrs, where this osmo-hlr is a client (proxying e.g. for an MSC).
77 *
78 * (b) Keep track of remotely handled IMSIs:
79 * src/proxy.c Keep track of proxied IMSIs and cache important subscriber data.
80 *
81 * (c) Direct GSUP request to the right destination: either the local or a remote HLR:
82 * src/dgsm.c The glue that makes osmo-hlr distinguish between local IMSIs and those that are proxied to another
83 * osmo-hlr.
84 * src/dgsm_vty.c Config.
85 *
86 * @{
87 * \file mslookup.c
88 */
89
90const struct value_string osmo_mslookup_id_type_names[] = {
91 { OSMO_MSLOOKUP_ID_NONE, "none" },
92 { OSMO_MSLOOKUP_ID_IMSI, "imsi" },
93 { OSMO_MSLOOKUP_ID_MSISDN, "msisdn" },
94 {}
95};
96
97const struct value_string osmo_mslookup_result_code_names[] = {
98 { OSMO_MSLOOKUP_RC_NONE, "none" },
99 { OSMO_MSLOOKUP_RC_RESULT, "result" },
100 { OSMO_MSLOOKUP_RC_NOT_FOUND, "not-found" },
101 {}
102};
103
104/*! Compare two struct osmo_mslookup_id.
105 * \returns 0 if a and b are equal,
106 * < 0 if a (or the ID type / start of ID) is < b,
107 * > 0 if a (or the ID type / start of ID) is > b.
108 */
109int osmo_mslookup_id_cmp(const struct osmo_mslookup_id *a, const struct osmo_mslookup_id *b)
110{
111 int cmp;
112 if (a == b)
113 return 0;
114 if (!a)
115 return -1;
116 if (!b)
117 return 1;
118
119 cmp = OSMO_CMP(a->type, b->type);
120 if (cmp)
121 return cmp;
122
123 switch (a->type) {
124 case OSMO_MSLOOKUP_ID_IMSI:
125 return strncmp(a->imsi, b->imsi, sizeof(a->imsi));
126 case OSMO_MSLOOKUP_ID_MSISDN:
127 return strncmp(a->msisdn, b->msisdn, sizeof(a->msisdn));
128 default:
129 return 0;
130 }
131}
132
133bool osmo_mslookup_id_valid(const struct osmo_mslookup_id *id)
134{
135 switch (id->type) {
136 case OSMO_MSLOOKUP_ID_IMSI:
137 return osmo_imsi_str_valid(id->imsi);
138 case OSMO_MSLOOKUP_ID_MSISDN:
139 return osmo_msisdn_str_valid(id->msisdn);
140 default:
141 return false;
142 }
143}
144
145bool osmo_mslookup_service_valid(const char *service)
146{
147 return strlen(service) > 0;
148}
149
150/*! Write ID and ID type to a buffer.
151 * \param[out] buf nul-terminated {id}.{id_type} string (e.g. "1234.msisdn") or
152* "?.none" if the ID type is invalid.
153 * \returns amount of bytes written to buf.
154 */
155size_t osmo_mslookup_id_name_buf(char *buf, size_t buflen, const struct osmo_mslookup_id *id)
156{
157 struct osmo_strbuf sb = { .buf = buf, .len = buflen };
158 switch (id->type) {
159 case OSMO_MSLOOKUP_ID_IMSI:
160 OSMO_STRBUF_PRINTF(sb, "%s", id->imsi);
161 break;
162 case OSMO_MSLOOKUP_ID_MSISDN:
163 OSMO_STRBUF_PRINTF(sb, "%s", id->msisdn);
164 break;
165 default:
166 OSMO_STRBUF_PRINTF(sb, "?");
167 break;
168 }
169 OSMO_STRBUF_PRINTF(sb, ".%s", osmo_mslookup_id_type_name(id->type));
170 return sb.chars_needed;
171}
172
173/*! Same as osmo_mslookup_id_name_buf(), but return a talloc allocated string of sufficient size. */
174char *osmo_mslookup_id_name_c(void *ctx, const struct osmo_mslookup_id *id)
175{
176 OSMO_NAME_C_IMPL(ctx, 64, "ERROR", osmo_mslookup_id_name_buf, id)
177}
178
179/*! Same as osmo_mslookup_id_name_buf(), but directly return the char* (for printf-like string formats). */
180char *osmo_mslookup_id_name_b(char *buf, size_t buflen, const struct osmo_mslookup_id *id)
181{
182 int rc = osmo_mslookup_id_name_buf(buf, buflen, id);
183 if (rc < 0 && buflen)
184 buf[0] = '\0';
185 return buf;
186}
187
188/*! Write mslookup result string to buffer.
189 * \param[in] query with the service, ID and ID type to be written to buf like a domain string, or NULL to omit.
190 * \param[in] result with the result code, IPv4/v6 and age to be written to buf or NULL to omit.
191 * \param[out] buf result as flat string, which looks like the following for a valid query and result with IPv4 and v6
192 * answer: "sip.voice.1234.msisdn -> ipv4: 42.42.42.42:1337 -> ipv6: [1234:5678:9ABC::]:1338 (age=1)",
193 * the result part can also be " -> timeout" or " -> rc=5" depending on the result code.
194 * \returns amount of bytes written to buf.
195 */
196size_t osmo_mslookup_result_to_str_buf(char *buf, size_t buflen,
197 const struct osmo_mslookup_query *query,
198 const struct osmo_mslookup_result *result)
199{
200 struct osmo_strbuf sb = { .buf = buf, .len = buflen };
201 if (query) {
202 OSMO_STRBUF_PRINTF(sb, "%s.", query->service);
203 OSMO_STRBUF_APPEND(sb, osmo_mslookup_id_name_buf, &query->id);
204 }
205 if (result && result->rc == OSMO_MSLOOKUP_RC_NONE)
206 result = NULL;
207 if (result) {
208 if (result->rc != OSMO_MSLOOKUP_RC_RESULT) {
209 OSMO_STRBUF_PRINTF(sb, " %s", osmo_mslookup_result_code_name(result->rc));
210 } else {
211 if (result->host_v4.ip[0]) {
212 OSMO_STRBUF_PRINTF(sb, " -> ipv4: " OSMO_SOCKADDR_STR_FMT,
213 OSMO_SOCKADDR_STR_FMT_ARGS(&result->host_v4));
214 }
215 if (result->host_v6.ip[0]) {
216 OSMO_STRBUF_PRINTF(sb, " -> ipv6: " OSMO_SOCKADDR_STR_FMT,
217 OSMO_SOCKADDR_STR_FMT_ARGS(&result->host_v6));
218 }
219 OSMO_STRBUF_PRINTF(sb, " (age=%u)", result->age);
220 }
221 OSMO_STRBUF_PRINTF(sb, " %s", result->last ? "(last)" : "(not-last)");
222 }
223 return sb.chars_needed;
224}
225
226/*! Same as osmo_mslookup_result_to_str_buf(), but return a talloc allocated string of sufficient size. */
227char *osmo_mslookup_result_name_c(void *ctx,
228 const struct osmo_mslookup_query *query,
229 const struct osmo_mslookup_result *result)
230{
231 OSMO_NAME_C_IMPL(ctx, 64, "ERROR", osmo_mslookup_result_to_str_buf, query, result)
232}
233
234/*! Same as osmo_mslookup_result_to_str_buf(), but directly return the char* (for printf-like string formats). */
235char *osmo_mslookup_result_name_b(char *buf, size_t buflen,
236 const struct osmo_mslookup_query *query,
237 const struct osmo_mslookup_result *result)
238{
239 int rc = osmo_mslookup_result_to_str_buf(buf, buflen, query, result);
240 if (rc < 0 && buflen)
241 buf[0] = '\0';
242 return buf;
243}
244
245/*! Copy part of a string to a buffer and nul-terminate it.
246 * \returns 0 on success, negative on error.
247 */
248static int token(char *dest, size_t dest_size, const char *start, const char *end)
249{
250 int len;
251 if (start >= end)
252 return -10;
253 len = end - start;
254 if (len >= dest_size)
255 return -11;
256 strncpy(dest, start, len);
257 dest[len] = '\0';
258 return 0;
259}
260
261/*! Parse a string like "foo.moo.goo.123456789012345.msisdn" into service="foo.moo.goo", id="123456789012345" and
262 * id_type="msisdn", placed in a struct osmo_mslookup_query.
263 * \param q Write parsed query to this osmo_mslookup_query.
264 * \param domain Human readable domain string like "sip.voice.12345678.msisdn".
265 * \returns 0 on success, negative on error.
266 */
267int osmo_mslookup_query_init_from_domain_str(struct osmo_mslookup_query *q, const char *domain)
268{
269 const char *last_dot;
270 const char *second_last_dot;
271 const char *id_type;
272 const char *id;
273 int rc;
274
275 *q = (struct osmo_mslookup_query){};
276
277 if (!domain)
278 return -1;
279
280 last_dot = strrchr(domain, '.');
281
282 if (!last_dot)
283 return -2;
284
285 if (last_dot <= domain)
286 return -3;
287
288 for (second_last_dot = last_dot - 1; second_last_dot > domain && *second_last_dot != '.'; second_last_dot--);
289 if (second_last_dot == domain || *second_last_dot != '.')
290 return -3;
291
292 id_type = last_dot + 1;
293 if (!*id_type)
294 return -4;
295
296 q->id.type = get_string_value(osmo_mslookup_id_type_names, id_type);
297
298 id = second_last_dot + 1;
299 switch (q->id.type) {
300 case OSMO_MSLOOKUP_ID_IMSI:
301 rc = token(q->id.imsi, sizeof(q->id.imsi), id, last_dot);
302 if (rc)
303 return rc;
304 if (!osmo_imsi_str_valid(q->id.imsi))
305 return -5;
306 break;
307 case OSMO_MSLOOKUP_ID_MSISDN:
308 rc = token(q->id.msisdn, sizeof(q->id.msisdn), id, last_dot);
309 if (rc)
310 return rc;
311 if (!osmo_msisdn_str_valid(q->id.msisdn))
312 return -6;
313 break;
314 default:
315 return -7;
316 }
317
318 return token(q->service, sizeof(q->service), domain, second_last_dot);
319}
320
321/*! @} */