477 lines
11 KiB
C
477 lines
11 KiB
C
/* ip.access nanoBTS configuration tool */
|
|
|
|
/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
|
|
* (C) 2017 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 <unistd.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <getopt.h>
|
|
#include <time.h>
|
|
#include <talloc.h>
|
|
#include <errno.h>
|
|
|
|
#include <osmocom/core/select.h>
|
|
#include <osmocom/core/timer.h>
|
|
#include <osmocom/core/linuxlist.h>
|
|
#include <osmocom/gsm/protocol/ipaccess.h>
|
|
#include <osmocom/gsm/ipa.h>
|
|
#include <osmocom/bsc/gsm_data.h>
|
|
|
|
static struct {
|
|
const char *ifname;
|
|
const char *bind_ip;
|
|
int send_interval;
|
|
bool list_view;
|
|
time_t list_view_timeout;
|
|
bool format_json;
|
|
} cmdline_opts = {
|
|
.ifname = NULL,
|
|
.bind_ip = NULL,
|
|
.send_interval = 5,
|
|
.list_view = false,
|
|
.list_view_timeout = 10,
|
|
.format_json = false,
|
|
};
|
|
|
|
static void print_help()
|
|
{
|
|
printf("\n");
|
|
printf("Usage: abisip-find [-l] [<interface-name>]\n");
|
|
printf(" <interface-name> Specify the outgoing network interface,\n"
|
|
" e.g. 'eth0'\n");
|
|
printf(" -b --bind-ip <ip> Specify the local IP to bind to,\n"
|
|
" e.g. '192.168.1.10'\n");
|
|
printf(" -i --interval <s> Send broadcast frames every <s> seconds.\n");
|
|
printf(" -l --list-view Instead of printing received responses,\n"
|
|
" output a sorted list of currently present\n"
|
|
" base stations and change events.\n");
|
|
printf(" -t --timeout <s> Drop base stations after <s> seconds of\n"
|
|
" receiving no more replies from it.\n"
|
|
" Implies --list-view.\n");
|
|
printf(" -j --format-json Print BTS information using json syntax.\n");
|
|
}
|
|
|
|
static void handle_options(int argc, char **argv)
|
|
{
|
|
while (1) {
|
|
int option_index = 0, c;
|
|
static struct option long_options[] = {
|
|
{"help", 0, 0, 'h'},
|
|
{"bind-ip", 1, 0, 'b'},
|
|
{"send-interval", 1, 0, 'i'},
|
|
{"list-view", 0, 0, 'l'},
|
|
{"timeout", 1, 0, 't'},
|
|
{"format-json", 0, 0, 'j'},
|
|
{0, 0, 0, 0}
|
|
};
|
|
|
|
c = getopt_long(argc, argv, "hb:i:lt:j",
|
|
long_options, &option_index);
|
|
if (c == -1)
|
|
break;
|
|
|
|
switch (c) {
|
|
case 'h':
|
|
print_help();
|
|
exit(EXIT_SUCCESS);
|
|
case 'b':
|
|
cmdline_opts.bind_ip = optarg;
|
|
break;
|
|
case 'i':
|
|
errno = 0;
|
|
cmdline_opts.send_interval = strtoul(optarg, NULL, 10);
|
|
if (errno || cmdline_opts.send_interval < 1) {
|
|
fprintf(stderr, "Invalid interval value: %s\n", optarg);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
break;
|
|
case 't':
|
|
errno = 0;
|
|
cmdline_opts.list_view_timeout = strtoul(optarg, NULL, 10);
|
|
if (errno) {
|
|
fprintf(stderr, "Invalid timeout value: %s\n", optarg);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
/* fall through to imply list-view: */
|
|
case 'l':
|
|
cmdline_opts.list_view = true;
|
|
break;
|
|
case 'j':
|
|
cmdline_opts.format_json = true;
|
|
break;
|
|
default:
|
|
/* catch unknown options *as well as* missing arguments. */
|
|
fprintf(stderr, "Error in command line options. Exiting. Try --help.\n");
|
|
exit(EXIT_FAILURE);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (argc - optind > 0)
|
|
cmdline_opts.ifname = argv[optind++];
|
|
|
|
if (argc - optind > 0) {
|
|
fprintf(stderr, "Error: too many arguments\n");
|
|
print_help();
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
static int udp_sock(const char *ifname, const char *bind_ip)
|
|
{
|
|
int fd, rc, bc = 1;
|
|
struct sockaddr_in sa;
|
|
|
|
fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
if (fd < 0)
|
|
return fd;
|
|
|
|
if (ifname) {
|
|
#ifdef __FreeBSD__
|
|
rc = setsockopt(fd, SOL_SOCKET, IP_RECVIF, ifname,
|
|
strlen(ifname));
|
|
#else
|
|
rc = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, ifname,
|
|
strlen(ifname));
|
|
#endif
|
|
if (rc < 0)
|
|
goto err;
|
|
}
|
|
|
|
memset(&sa, 0, sizeof(sa));
|
|
sa.sin_family = AF_INET;
|
|
sa.sin_port = htons(3006);
|
|
if (bind_ip) {
|
|
rc = inet_pton(AF_INET, bind_ip, &sa.sin_addr);
|
|
if (rc != 1) {
|
|
fprintf(stderr, "bind ip addr: inet_pton failed, returned %d\n", rc);
|
|
goto err;
|
|
}
|
|
} else {
|
|
sa.sin_addr.s_addr = INADDR_ANY;
|
|
}
|
|
|
|
rc = bind(fd, (struct sockaddr *)&sa, sizeof(sa));
|
|
if (rc < 0)
|
|
goto err;
|
|
|
|
rc = setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &bc, sizeof(bc));
|
|
if (rc < 0)
|
|
goto err;
|
|
|
|
#if 0
|
|
/* we cannot bind, since the response packets don't come from
|
|
* the broadcast address */
|
|
sa.sin_family = AF_INET;
|
|
sa.sin_port = htons(3006);
|
|
inet_aton("255.255.255.255", &sa.sin_addr);
|
|
|
|
rc = connect(fd, (struct sockaddr *)&sa, sizeof(sa));
|
|
if (rc < 0)
|
|
goto err;
|
|
#endif
|
|
return fd;
|
|
|
|
err:
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
const unsigned char find_pkt[] = { 0x00, 0x0b+8, IPAC_PROTO_IPACCESS, 0x00,
|
|
IPAC_MSGT_ID_GET,
|
|
0x01, IPAC_IDTAG_MACADDR,
|
|
0x01, IPAC_IDTAG_IPADDR,
|
|
0x01, IPAC_IDTAG_UNIT,
|
|
0x01, IPAC_IDTAG_LOCATION1,
|
|
0x01, IPAC_IDTAG_LOCATION2,
|
|
0x01, IPAC_IDTAG_EQUIPVERS,
|
|
0x01, IPAC_IDTAG_SWVERSION,
|
|
0x01, IPAC_IDTAG_UNITNAME,
|
|
0x01, IPAC_IDTAG_SERNR,
|
|
};
|
|
|
|
|
|
static int bcast_find(int fd)
|
|
{
|
|
struct sockaddr_in sa;
|
|
|
|
sa.sin_family = AF_INET;
|
|
sa.sin_port = htons(3006);
|
|
inet_aton("255.255.255.255", &sa.sin_addr);
|
|
|
|
return sendto(fd, find_pkt, sizeof(find_pkt), 0, (struct sockaddr *) &sa, sizeof(sa));
|
|
}
|
|
|
|
static char *parse_response(void *ctx, unsigned char *buf, int len)
|
|
{
|
|
unsigned int out_len;
|
|
uint8_t t_len;
|
|
uint8_t t_tag;
|
|
uint8_t *cur = buf;
|
|
char *out = talloc_zero_size(ctx, 512);
|
|
|
|
if (cmdline_opts.format_json)
|
|
out = talloc_asprintf_append(out,"{ ");
|
|
|
|
while (cur < buf + len) {
|
|
t_len = *cur++;
|
|
t_tag = *cur++;
|
|
|
|
if (cmdline_opts.format_json)
|
|
out = talloc_asprintf_append(out, "\"%s\": \"%s\", ", ipa_ccm_idtag_name(t_tag), cur);
|
|
else
|
|
out = talloc_asprintf_append(out, "%s='%s' ", ipa_ccm_idtag_name(t_tag), cur);
|
|
|
|
cur += t_len;
|
|
}
|
|
|
|
if (cmdline_opts.format_json) {
|
|
out_len = strlen(out);
|
|
if (out[out_len-2] == ',')
|
|
out[out_len-2] = ' ';
|
|
out[out_len-1] = '}';
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
struct base_station {
|
|
struct llist_head entry;
|
|
char *line;
|
|
time_t timestamp;
|
|
};
|
|
|
|
LLIST_HEAD(base_stations);
|
|
|
|
void *ctx = NULL;
|
|
|
|
void print_timestamp()
|
|
{
|
|
time_t now = time(NULL);
|
|
printf("\n\n----- %s\n", ctime(&now));
|
|
}
|
|
|
|
struct base_station *base_station_parse(unsigned char *buf, int len)
|
|
{
|
|
struct base_station *new_bs = talloc_zero(ctx, struct base_station);
|
|
new_bs->line = parse_response(new_bs, buf, len);
|
|
new_bs->timestamp = time(NULL);
|
|
return new_bs;
|
|
}
|
|
|
|
bool base_stations_add(struct base_station *new_bs)
|
|
{
|
|
struct base_station *bs;
|
|
|
|
llist_for_each_entry(bs, &base_stations, entry) {
|
|
int c = strcmp(new_bs->line, bs->line);
|
|
if (!c) {
|
|
/* entry already exists. */
|
|
bs->timestamp = new_bs->timestamp;
|
|
return false;
|
|
}
|
|
|
|
if (c < 0) {
|
|
/* found the place to add the entry */
|
|
break;
|
|
}
|
|
}
|
|
|
|
print_timestamp();
|
|
printf("New:\n%s\n", new_bs->line);
|
|
|
|
llist_add_tail(&new_bs->entry, &bs->entry);
|
|
return true;
|
|
}
|
|
|
|
bool base_stations_timeout()
|
|
{
|
|
struct base_station *bs, *next_bs;
|
|
time_t now = time(NULL);
|
|
bool changed = false;
|
|
|
|
llist_for_each_entry_safe(bs, next_bs, &base_stations, entry) {
|
|
if (now - bs->timestamp < cmdline_opts.list_view_timeout)
|
|
continue;
|
|
print_timestamp();
|
|
printf("LOST:\n%s\n", bs->line);
|
|
|
|
llist_del(&bs->entry);
|
|
talloc_free(bs);
|
|
changed = true;
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
void base_stations_print()
|
|
{
|
|
struct base_station *bs;
|
|
int count = 0;
|
|
|
|
print_timestamp();
|
|
if (cmdline_opts.format_json)
|
|
printf("[");
|
|
|
|
llist_for_each_entry(bs, &base_stations, entry) {
|
|
if (cmdline_opts.format_json) {
|
|
if (count)
|
|
printf(",");
|
|
printf("\n%s", bs->line);
|
|
} else {
|
|
printf("%3d: %s\n", count, bs->line);
|
|
}
|
|
count++;
|
|
}
|
|
|
|
if (cmdline_opts.format_json)
|
|
printf("%c]\n", count ? '\n': ' ');
|
|
|
|
printf("\nTotal: %d\n", count);
|
|
}
|
|
|
|
static void base_stations_bump(bool known_changed)
|
|
{
|
|
bool changed = known_changed;
|
|
if (base_stations_timeout())
|
|
changed = true;
|
|
|
|
if (changed)
|
|
base_stations_print();
|
|
}
|
|
|
|
static void handle_response(unsigned char *buf, int len)
|
|
{
|
|
static unsigned int responses = 0;
|
|
responses++;
|
|
|
|
if (cmdline_opts.list_view) {
|
|
bool changed = false;
|
|
struct base_station *bs = base_station_parse(buf, len);
|
|
if (base_stations_add(bs))
|
|
changed = true;
|
|
else
|
|
talloc_free(bs);
|
|
base_stations_bump(changed);
|
|
printf("RX: %u \r", responses);
|
|
} else {
|
|
printf("%s\n", parse_response(ctx, buf, len));
|
|
}
|
|
fflush(stdout);
|
|
}
|
|
|
|
static int read_response(int fd)
|
|
{
|
|
unsigned char buf[255];
|
|
struct sockaddr_in sa;
|
|
int len;
|
|
socklen_t sa_len = sizeof(sa);
|
|
|
|
len = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&sa, &sa_len);
|
|
if (len < 0)
|
|
return len;
|
|
|
|
/* 2 bytes length, 1 byte protocol */
|
|
if (buf[2] != IPAC_PROTO_IPACCESS)
|
|
return 0;
|
|
|
|
if (buf[4] != IPAC_MSGT_ID_RESP)
|
|
return 0;
|
|
|
|
handle_response(buf+6, len-6);
|
|
return 0;
|
|
}
|
|
|
|
static int bfd_cb(struct osmo_fd *bfd, unsigned int flags)
|
|
{
|
|
if (flags & BSC_FD_READ)
|
|
return read_response(bfd->fd);
|
|
if (flags & BSC_FD_WRITE) {
|
|
bfd->when &= ~BSC_FD_WRITE;
|
|
return bcast_find(bfd->fd);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static struct osmo_timer_list timer;
|
|
|
|
static void timer_cb(void *_data)
|
|
{
|
|
struct osmo_fd *bfd = _data;
|
|
|
|
bfd->when |= BSC_FD_WRITE;
|
|
|
|
base_stations_bump(false);
|
|
|
|
osmo_timer_schedule(&timer, cmdline_opts.send_interval, 0);
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
struct osmo_fd bfd;
|
|
int rc;
|
|
|
|
printf("abisip-find (C) 2009-2010 by Harald Welte\n");
|
|
printf(" (C) 2017 by sysmocom - s.f.m.c. GmbH\n");
|
|
printf("This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY\n\n");
|
|
|
|
handle_options(argc, argv);
|
|
|
|
if (!cmdline_opts.ifname && !cmdline_opts.bind_ip)
|
|
fprintf(stdout, "- You might need to specify the outgoing network interface,\n"
|
|
" e.g. ``%s eth0'' (requires root permissions),\n"
|
|
" or alternatively use -b to bind to the source address\n"
|
|
" assigned to that interface\n", argv[0]);
|
|
if (!cmdline_opts.list_view)
|
|
fprintf(stdout, "- You may find the --list-view option convenient.\n");
|
|
else if (cmdline_opts.send_interval >= cmdline_opts.list_view_timeout)
|
|
fprintf(stdout, "\nWARNING: the --timeout should be larger than --interval.\n\n");
|
|
|
|
bfd.cb = bfd_cb;
|
|
bfd.when = BSC_FD_READ | BSC_FD_WRITE;
|
|
bfd.fd = udp_sock(cmdline_opts.ifname, cmdline_opts.bind_ip);
|
|
if (bfd.fd < 0) {
|
|
perror("Cannot create local socket for broadcast udp");
|
|
exit(1);
|
|
}
|
|
|
|
rc = osmo_fd_register(&bfd);
|
|
if (rc < 0) {
|
|
fprintf(stderr, "Cannot register FD\n");
|
|
exit(1);
|
|
}
|
|
|
|
osmo_timer_setup(&timer, timer_cb, &bfd);
|
|
osmo_timer_schedule(&timer, cmdline_opts.send_interval, 0);
|
|
|
|
printf("Trying to find ip.access BTS by broadcast UDP...\n");
|
|
|
|
while (1) {
|
|
rc = osmo_select_main(0);
|
|
if (rc < 0)
|
|
exit(3);
|
|
}
|
|
|
|
exit(0);
|
|
}
|
|
|