blob: 11b2851aeb174350a5321c910e718046c20fb59f [file] [log] [blame]
Harald Welte9af6ddf2011-01-01 15:25:50 +01001/* ip.access nanoBTS configuration tool */
2
3/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
Neels Hofmeyr89ade632017-12-25 19:13:13 +01004 * (C) 2017 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
Harald Welte9af6ddf2011-01-01 15:25:50 +01005 * All Rights Reserved
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
16 *
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 *
20 */
Harald Welte923a3bd2009-02-14 12:51:36 +000021
22#include <unistd.h>
23#include <stdio.h>
24#include <stdlib.h>
Harald Welte923a3bd2009-02-14 12:51:36 +000025#include <sys/socket.h>
26#include <netinet/in.h>
27#include <arpa/inet.h>
Neels Hofmeyr2a9ac192017-12-25 18:43:40 +010028#include <getopt.h>
Neels Hofmeyr5bf1e152017-12-25 18:22:18 +010029#include <time.h>
30#include <talloc.h>
Neels Hofmeyr77a9d4e2017-12-25 19:10:10 +010031#include <errno.h>
Harald Welte923a3bd2009-02-14 12:51:36 +000032
Pablo Neira Ayuso136f4532011-03-22 16:47:59 +010033#include <osmocom/core/select.h>
34#include <osmocom/core/timer.h>
Neels Hofmeyr5bf1e152017-12-25 18:22:18 +010035#include <osmocom/core/linuxlist.h>
Harald Welte4a88a492014-08-20 23:46:40 +020036#include <osmocom/gsm/protocol/ipaccess.h>
37#include <osmocom/gsm/ipa.h>
Neels Hofmeyrc0164792017-09-04 15:15:32 +020038#include <osmocom/bsc/gsm_data.h>
Harald Welte37881962009-04-30 15:15:37 +000039
Neels Hofmeyr2a9ac192017-12-25 18:43:40 +010040static struct {
41 const char *ifname;
Pau Espin Pedrol4becc842018-03-12 15:19:27 +010042 const char *bind_ip;
Neels Hofmeyrff8ab0a2017-12-25 19:21:36 +010043 int send_interval;
Neels Hofmeyr5bf1e152017-12-25 18:22:18 +010044 bool list_view;
Neels Hofmeyr77a9d4e2017-12-25 19:10:10 +010045 time_t list_view_timeout;
Pau Espin Pedrol34b59ac2018-03-12 18:02:05 +010046 bool format_json;
Neels Hofmeyr2a9ac192017-12-25 18:43:40 +010047} cmdline_opts = {
48 .ifname = NULL,
Pau Espin Pedrol4becc842018-03-12 15:19:27 +010049 .bind_ip = NULL,
Neels Hofmeyrff8ab0a2017-12-25 19:21:36 +010050 .send_interval = 5,
Neels Hofmeyr5bf1e152017-12-25 18:22:18 +010051 .list_view = false,
Neels Hofmeyr77a9d4e2017-12-25 19:10:10 +010052 .list_view_timeout = 10,
Pau Espin Pedrol34b59ac2018-03-12 18:02:05 +010053 .format_json = false,
Neels Hofmeyr2a9ac192017-12-25 18:43:40 +010054};
55
56static void print_help()
57{
58 printf("\n");
59 printf("Usage: abisip-find [-l] [<interface-name>]\n");
60 printf(" <interface-name> Specify the outgoing network interface,\n"
61 " e.g. 'eth0'\n");
Pau Espin Pedrol4becc842018-03-12 15:19:27 +010062 printf(" -b --bind-ip <ip> Specify the local IP to bind to,\n"
63 " e.g. '192.168.1.10'\n");
Neels Hofmeyrff8ab0a2017-12-25 19:21:36 +010064 printf(" -i --interval <s> Send broadcast frames every <s> seconds.\n");
Neels Hofmeyr5bf1e152017-12-25 18:22:18 +010065 printf(" -l --list-view Instead of printing received responses,\n"
66 " output a sorted list of currently present\n"
67 " base stations and change events.\n");
Neels Hofmeyr77a9d4e2017-12-25 19:10:10 +010068 printf(" -t --timeout <s> Drop base stations after <s> seconds of\n"
69 " receiving no more replies from it.\n"
70 " Implies --list-view.\n");
Pau Espin Pedrol34b59ac2018-03-12 18:02:05 +010071 printf(" -j --format-json Print BTS information using json syntax.\n");
Neels Hofmeyr2a9ac192017-12-25 18:43:40 +010072}
73
74static void handle_options(int argc, char **argv)
75{
76 while (1) {
77 int option_index = 0, c;
78 static struct option long_options[] = {
79 {"help", 0, 0, 'h'},
Pau Espin Pedrol4becc842018-03-12 15:19:27 +010080 {"bind-ip", 1, 0, 'b'},
Neels Hofmeyrff8ab0a2017-12-25 19:21:36 +010081 {"send-interval", 1, 0, 'i'},
Neels Hofmeyr5bf1e152017-12-25 18:22:18 +010082 {"list-view", 0, 0, 'l'},
Neels Hofmeyr77a9d4e2017-12-25 19:10:10 +010083 {"timeout", 1, 0, 't'},
Pau Espin Pedrol34b59ac2018-03-12 18:02:05 +010084 {"format-json", 0, 0, 'j'},
Neels Hofmeyr2a9ac192017-12-25 18:43:40 +010085 {0, 0, 0, 0}
86 };
87
Pau Espin Pedrol34b59ac2018-03-12 18:02:05 +010088 c = getopt_long(argc, argv, "hb:i:lt:j",
Neels Hofmeyr2a9ac192017-12-25 18:43:40 +010089 long_options, &option_index);
90 if (c == -1)
91 break;
92
93 switch (c) {
94 case 'h':
95 print_help();
96 exit(EXIT_SUCCESS);
Pau Espin Pedrol4becc842018-03-12 15:19:27 +010097 case 'b':
98 cmdline_opts.bind_ip = optarg;
99 break;
Neels Hofmeyrff8ab0a2017-12-25 19:21:36 +0100100 case 'i':
101 errno = 0;
102 cmdline_opts.send_interval = strtoul(optarg, NULL, 10);
103 if (errno || cmdline_opts.send_interval < 1) {
104 fprintf(stderr, "Invalid interval value: %s\n", optarg);
105 exit(EXIT_FAILURE);
106 }
107 break;
Neels Hofmeyr77a9d4e2017-12-25 19:10:10 +0100108 case 't':
109 errno = 0;
110 cmdline_opts.list_view_timeout = strtoul(optarg, NULL, 10);
111 if (errno) {
112 fprintf(stderr, "Invalid timeout value: %s\n", optarg);
113 exit(EXIT_FAILURE);
114 }
115 /* fall through to imply list-view: */
Neels Hofmeyr5bf1e152017-12-25 18:22:18 +0100116 case 'l':
117 cmdline_opts.list_view = true;
118 break;
Pau Espin Pedrol34b59ac2018-03-12 18:02:05 +0100119 case 'j':
120 cmdline_opts.format_json = true;
121 break;
Neels Hofmeyr2a9ac192017-12-25 18:43:40 +0100122 default:
123 /* catch unknown options *as well as* missing arguments. */
124 fprintf(stderr, "Error in command line options. Exiting. Try --help.\n");
125 exit(EXIT_FAILURE);
126 break;
127 }
128 }
129
130 if (argc - optind > 0)
131 cmdline_opts.ifname = argv[optind++];
132
133 if (argc - optind > 0) {
134 fprintf(stderr, "Error: too many arguments\n");
135 print_help();
136 exit(EXIT_FAILURE);
137 }
138}
139
Pau Espin Pedrol4becc842018-03-12 15:19:27 +0100140static int udp_sock(const char *ifname, const char *bind_ip)
Harald Welte923a3bd2009-02-14 12:51:36 +0000141{
142 int fd, rc, bc = 1;
143 struct sockaddr_in sa;
144
145 fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
146 if (fd < 0)
147 return fd;
148
Harald Weltee26d0792009-08-08 11:47:20 +0200149 if (ifname) {
Nikola Kolev10bad102014-05-08 12:45:20 +0300150#ifdef __FreeBSD__
151 rc = setsockopt(fd, SOL_SOCKET, IP_RECVIF, ifname,
152 strlen(ifname));
153#else
Harald Weltee26d0792009-08-08 11:47:20 +0200154 rc = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, ifname,
155 strlen(ifname));
Nikola Kolev10bad102014-05-08 12:45:20 +0300156#endif
Harald Weltee26d0792009-08-08 11:47:20 +0200157 if (rc < 0)
158 goto err;
159 }
160
Holger Hans Peter Freytherd5c270e2013-07-03 10:19:37 +0200161 memset(&sa, 0, sizeof(sa));
Harald Welte923a3bd2009-02-14 12:51:36 +0000162 sa.sin_family = AF_INET;
163 sa.sin_port = htons(3006);
Pau Espin Pedrol4becc842018-03-12 15:19:27 +0100164 if (bind_ip) {
165 rc = inet_pton(AF_INET, bind_ip, &sa.sin_addr);
166 if (rc != 1) {
167 fprintf(stderr, "bind ip addr: inet_pton failed, returned %d\n", rc);
168 goto err;
169 }
170 } else {
171 sa.sin_addr.s_addr = INADDR_ANY;
172 }
Harald Welte923a3bd2009-02-14 12:51:36 +0000173
174 rc = bind(fd, (struct sockaddr *)&sa, sizeof(sa));
175 if (rc < 0)
176 goto err;
177
178 rc = setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &bc, sizeof(bc));
179 if (rc < 0)
180 goto err;
181
182#if 0
Harald Welte81cff3c2009-08-08 12:14:53 +0200183 /* we cannot bind, since the response packets don't come from
184 * the broadcast address */
185 sa.sin_family = AF_INET;
186 sa.sin_port = htons(3006);
187 inet_aton("255.255.255.255", &sa.sin_addr);
188
Harald Welte923a3bd2009-02-14 12:51:36 +0000189 rc = connect(fd, (struct sockaddr *)&sa, sizeof(sa));
190 if (rc < 0)
191 goto err;
Holger Hans Peter Freytheracf8a0c2010-03-29 08:47:44 +0200192#endif
Harald Welte923a3bd2009-02-14 12:51:36 +0000193 return fd;
194
195err:
196 close(fd);
197 return rc;
198}
199
Holger Hans Peter Freytheracf8a0c2010-03-29 08:47:44 +0200200const unsigned char find_pkt[] = { 0x00, 0x0b+8, IPAC_PROTO_IPACCESS, 0x00,
Harald Welte4f361fc2009-02-15 15:32:53 +0000201 IPAC_MSGT_ID_GET,
202 0x01, IPAC_IDTAG_MACADDR,
203 0x01, IPAC_IDTAG_IPADDR,
204 0x01, IPAC_IDTAG_UNIT,
205 0x01, IPAC_IDTAG_LOCATION1,
206 0x01, IPAC_IDTAG_LOCATION2,
207 0x01, IPAC_IDTAG_EQUIPVERS,
208 0x01, IPAC_IDTAG_SWVERSION,
209 0x01, IPAC_IDTAG_UNITNAME,
210 0x01, IPAC_IDTAG_SERNR,
211 };
Harald Welte923a3bd2009-02-14 12:51:36 +0000212
213
214static int bcast_find(int fd)
215{
216 struct sockaddr_in sa;
217
218 sa.sin_family = AF_INET;
219 sa.sin_port = htons(3006);
220 inet_aton("255.255.255.255", &sa.sin_addr);
221
222 return sendto(fd, find_pkt, sizeof(find_pkt), 0, (struct sockaddr *) &sa, sizeof(sa));
223}
224
Neels Hofmeyr5bf1e152017-12-25 18:22:18 +0100225static char *parse_response(void *ctx, unsigned char *buf, int len)
Harald Welte923a3bd2009-02-14 12:51:36 +0000226{
Pau Espin Pedrol34b59ac2018-03-12 18:02:05 +0100227 unsigned int out_len;
Holger Hans Peter Freytherc42ad8b2011-04-18 17:04:00 +0200228 uint8_t t_len;
229 uint8_t t_tag;
230 uint8_t *cur = buf;
Neels Hofmeyr5bf1e152017-12-25 18:22:18 +0100231 char *out = talloc_zero_size(ctx, 512);
Harald Welte923a3bd2009-02-14 12:51:36 +0000232
Pau Espin Pedrol34b59ac2018-03-12 18:02:05 +0100233 if (cmdline_opts.format_json)
234 out = talloc_asprintf_append(out,"{ ");
235
Harald Welte923a3bd2009-02-14 12:51:36 +0000236 while (cur < buf + len) {
237 t_len = *cur++;
238 t_tag = *cur++;
Pau Espin Pedrol34b59ac2018-03-12 18:02:05 +0100239
240 if (cmdline_opts.format_json)
241 out = talloc_asprintf_append(out, "\"%s\": \"%s\", ", ipa_ccm_idtag_name(t_tag), cur);
242 else
243 out = talloc_asprintf_append(out, "%s='%s' ", ipa_ccm_idtag_name(t_tag), cur);
Harald Welte923a3bd2009-02-14 12:51:36 +0000244
245 cur += t_len;
246 }
Neels Hofmeyr5bf1e152017-12-25 18:22:18 +0100247
Pau Espin Pedrol34b59ac2018-03-12 18:02:05 +0100248 if (cmdline_opts.format_json) {
249 out_len = strlen(out);
250 if (out[out_len-2] == ',')
251 out[out_len-2] = ' ';
252 out[out_len-1] = '}';
253 }
254
Neels Hofmeyr5bf1e152017-12-25 18:22:18 +0100255 return out;
256}
257
258struct base_station {
259 struct llist_head entry;
260 char *line;
261 time_t timestamp;
262};
263
264LLIST_HEAD(base_stations);
265
266void *ctx = NULL;
267
268void print_timestamp()
269{
270 time_t now = time(NULL);
271 printf("\n\n----- %s\n", ctime(&now));
272}
273
274struct base_station *base_station_parse(unsigned char *buf, int len)
275{
276 struct base_station *new_bs = talloc_zero(ctx, struct base_station);
277 new_bs->line = parse_response(new_bs, buf, len);
278 new_bs->timestamp = time(NULL);
279 return new_bs;
280}
281
282bool base_stations_add(struct base_station *new_bs)
283{
284 struct base_station *bs;
285
286 llist_for_each_entry(bs, &base_stations, entry) {
287 int c = strcmp(new_bs->line, bs->line);
288 if (!c) {
289 /* entry already exists. */
290 bs->timestamp = new_bs->timestamp;
291 return false;
292 }
293
294 if (c < 0) {
295 /* found the place to add the entry */
296 break;
297 }
298 }
299
300 print_timestamp();
301 printf("New:\n%s\n", new_bs->line);
302
303 llist_add_tail(&new_bs->entry, &bs->entry);
304 return true;
305}
306
307bool base_stations_timeout()
308{
309 struct base_station *bs, *next_bs;
310 time_t now = time(NULL);
311 bool changed = false;
312
313 llist_for_each_entry_safe(bs, next_bs, &base_stations, entry) {
Neels Hofmeyr77a9d4e2017-12-25 19:10:10 +0100314 if (now - bs->timestamp < cmdline_opts.list_view_timeout)
Neels Hofmeyr5bf1e152017-12-25 18:22:18 +0100315 continue;
316 print_timestamp();
317 printf("LOST:\n%s\n", bs->line);
318
319 llist_del(&bs->entry);
320 talloc_free(bs);
321 changed = true;
322 }
323 return changed;
324}
325
326void base_stations_print()
327{
328 struct base_station *bs;
329 int count = 0;
330
331 print_timestamp();
Pau Espin Pedrol34b59ac2018-03-12 18:02:05 +0100332 if (cmdline_opts.format_json)
333 printf("[");
334
Neels Hofmeyr5bf1e152017-12-25 18:22:18 +0100335 llist_for_each_entry(bs, &base_stations, entry) {
Pau Espin Pedrol34b59ac2018-03-12 18:02:05 +0100336 if (cmdline_opts.format_json) {
337 if (count)
338 printf(",");
339 printf("\n%s", bs->line);
340 } else {
341 printf("%3d: %s\n", count, bs->line);
342 }
Neels Hofmeyr5bf1e152017-12-25 18:22:18 +0100343 count++;
344 }
Pau Espin Pedrol34b59ac2018-03-12 18:02:05 +0100345
346 if (cmdline_opts.format_json)
347 printf("%c]\n", count ? '\n': ' ');
348
Neels Hofmeyr5bf1e152017-12-25 18:22:18 +0100349 printf("\nTotal: %d\n", count);
350}
351
352static void base_stations_bump(bool known_changed)
353{
354 bool changed = known_changed;
355 if (base_stations_timeout())
356 changed = true;
357
358 if (changed)
359 base_stations_print();
360}
361
362static void handle_response(unsigned char *buf, int len)
363{
364 static unsigned int responses = 0;
365 responses++;
366
367 if (cmdline_opts.list_view) {
368 bool changed = false;
369 struct base_station *bs = base_station_parse(buf, len);
370 if (base_stations_add(bs))
371 changed = true;
372 else
373 talloc_free(bs);
374 base_stations_bump(changed);
375 printf("RX: %u \r", responses);
Pau Espin Pedrole845b0f2018-03-12 16:11:58 +0100376 } else {
Maxec54dab2017-12-29 14:12:06 +0100377 printf("%s\n", parse_response(ctx, buf, len));
Pau Espin Pedrole845b0f2018-03-12 16:11:58 +0100378 }
379 fflush(stdout);
Harald Welte923a3bd2009-02-14 12:51:36 +0000380}
381
382static int read_response(int fd)
383{
384 unsigned char buf[255];
385 struct sockaddr_in sa;
386 int len;
387 socklen_t sa_len = sizeof(sa);
388
389 len = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&sa, &sa_len);
390 if (len < 0)
391 return len;
392
Maxc04c6ed2016-11-10 18:29:50 +0100393 /* 2 bytes length, 1 byte protocol */
394 if (buf[2] != IPAC_PROTO_IPACCESS)
Harald Welte81cff3c2009-08-08 12:14:53 +0200395 return 0;
396
397 if (buf[4] != IPAC_MSGT_ID_RESP)
398 return 0;
399
Neels Hofmeyr5bf1e152017-12-25 18:22:18 +0100400 handle_response(buf+6, len-6);
401 return 0;
Harald Welte923a3bd2009-02-14 12:51:36 +0000402}
403
Pablo Neira Ayuso4db92992011-05-06 12:11:23 +0200404static int bfd_cb(struct osmo_fd *bfd, unsigned int flags)
Harald Welte923a3bd2009-02-14 12:51:36 +0000405{
Pau Espin Pedrol8e9f40a2020-05-09 19:17:12 +0200406 if (flags & OSMO_FD_READ)
Harald Welte923a3bd2009-02-14 12:51:36 +0000407 return read_response(bfd->fd);
Pau Espin Pedrol8e9f40a2020-05-09 19:17:12 +0200408 if (flags & OSMO_FD_WRITE) {
409 bfd->when &= ~OSMO_FD_WRITE;
Harald Welte923a3bd2009-02-14 12:51:36 +0000410 return bcast_find(bfd->fd);
411 }
412 return 0;
413}
414
Pablo Neira Ayusobf540cb2011-05-06 12:11:06 +0200415static struct osmo_timer_list timer;
Harald Welte923a3bd2009-02-14 12:51:36 +0000416
417static void timer_cb(void *_data)
418{
Pablo Neira Ayuso4db92992011-05-06 12:11:23 +0200419 struct osmo_fd *bfd = _data;
Harald Welte923a3bd2009-02-14 12:51:36 +0000420
Pau Espin Pedrol8e9f40a2020-05-09 19:17:12 +0200421 bfd->when |= OSMO_FD_WRITE;
Harald Welte923a3bd2009-02-14 12:51:36 +0000422
Neels Hofmeyr5bf1e152017-12-25 18:22:18 +0100423 base_stations_bump(false);
424
Neels Hofmeyrff8ab0a2017-12-25 19:21:36 +0100425 osmo_timer_schedule(&timer, cmdline_opts.send_interval, 0);
Harald Welte923a3bd2009-02-14 12:51:36 +0000426}
427
428int main(int argc, char **argv)
429{
Pablo Neira Ayuso4db92992011-05-06 12:11:23 +0200430 struct osmo_fd bfd;
Harald Welte923a3bd2009-02-14 12:51:36 +0000431 int rc;
432
Neels Hofmeyr89ade632017-12-25 19:13:13 +0100433 printf("abisip-find (C) 2009-2010 by Harald Welte\n");
434 printf(" (C) 2017 by sysmocom - s.f.m.c. GmbH\n");
Harald Welte923a3bd2009-02-14 12:51:36 +0000435 printf("This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY\n\n");
436
Neels Hofmeyr2a9ac192017-12-25 18:43:40 +0100437 handle_options(argc, argv);
438
Pau Espin Pedrol8972f3f2018-03-16 17:27:07 +0100439 if (!cmdline_opts.ifname && !cmdline_opts.bind_ip)
440 fprintf(stdout, "- You might need to specify the outgoing network interface,\n"
441 " e.g. ``%s eth0'' (requires root permissions),\n"
442 " or alternatively use -b to bind to the source address\n"
443 " assigned to that interface\n", argv[0]);
Neels Hofmeyr5bf1e152017-12-25 18:22:18 +0100444 if (!cmdline_opts.list_view)
445 fprintf(stdout, "- You may find the --list-view option convenient.\n");
Neels Hofmeyrff8ab0a2017-12-25 19:21:36 +0100446 else if (cmdline_opts.send_interval >= cmdline_opts.list_view_timeout)
447 fprintf(stdout, "\nWARNING: the --timeout should be larger than --interval.\n\n");
Harald Welte042401c2009-06-29 10:43:04 +0200448
Harald Welte923a3bd2009-02-14 12:51:36 +0000449 bfd.cb = bfd_cb;
Pau Espin Pedrol8e9f40a2020-05-09 19:17:12 +0200450 bfd.when = OSMO_FD_READ | OSMO_FD_WRITE;
Pau Espin Pedrol4becc842018-03-12 15:19:27 +0100451 bfd.fd = udp_sock(cmdline_opts.ifname, cmdline_opts.bind_ip);
Harald Welte042401c2009-06-29 10:43:04 +0200452 if (bfd.fd < 0) {
453 perror("Cannot create local socket for broadcast udp");
454 exit(1);
455 }
Harald Welte923a3bd2009-02-14 12:51:36 +0000456
Harald Welte8d359652016-11-26 14:57:23 +0100457 rc = osmo_fd_register(&bfd);
458 if (rc < 0) {
459 fprintf(stderr, "Cannot register FD\n");
460 exit(1);
461 }
Harald Welte923a3bd2009-02-14 12:51:36 +0000462
Pablo Neira Ayuso51215762017-05-08 20:57:52 +0200463 osmo_timer_setup(&timer, timer_cb, &bfd);
Neels Hofmeyrff8ab0a2017-12-25 19:21:36 +0100464 osmo_timer_schedule(&timer, cmdline_opts.send_interval, 0);
Harald Welte923a3bd2009-02-14 12:51:36 +0000465
466 printf("Trying to find ip.access BTS by broadcast UDP...\n");
467
468 while (1) {
Pablo Neira Ayuso4db92992011-05-06 12:11:23 +0200469 rc = osmo_select_main(0);
Harald Welte923a3bd2009-02-14 12:51:36 +0000470 if (rc < 0)
471 exit(3);
472 }
473
474 exit(0);
475}
476