blob: a472f05f99a8f90e596059762db1b82b31f69c88 [file] [log] [blame]
/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <arpa/inet.h>
#include <endian.h>
#include <net/if.h>
#include <netinet/in.h>
#include <string.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <unistd.h>
#include "l2utils.h"
#define ASUS_DISCOVERY_PORT 9999
#define PACKET_LENGTH 512
/*
* The packet format and magic numbers come from
* asuswrt/release/src/router/networkmap/asusdiscovery.c
* in GPL_RT-N66U_3.0.0.4.374.5517-g302e4dc.tgz,
* available from
* http://support.asus.com/Download.aspx?p=11&m=RT-N66U%20(VER.B1)&os=8
*/
typedef struct __attribute__((packed)) {
uint8_t service_id;
#define SERVICE_ID_IBOX_INFO 12
uint8_t packet_type;
#define PACKET_TYPE_REQUEST 21
#define PACKET_TYPE_RESULT 22
uint16_t opcode; // always little-endian
#define OPCODE_GETINFO 31
uint32_t transaction_id;
uint8_t printer_info[128];
uint8_t SSID[32];
uint8_t netmask[32];
uint8_t product_id[32];
uint8_t firmware_version[16];
uint8_t operation_mode;
uint8_t mac_address[6];
uint8_t regulation;
} asus_discovery_packet_t;
int make_socket(const char *ifname)
{
int s;
struct ifreq ifr;
struct sockaddr_in sin;
int broadcast = 1;
if ((s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
perror("socket");
exit(1);
}
memset(&ifr, 0, sizeof(ifr));
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", ifname);
if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr))) {
perror("SO_BINDTODEVICE");
exit(1);
}
if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast))) {
perror("SO_BROADCAST");
exit(1);
}
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(ASUS_DISCOVERY_PORT);
sin.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(s, (struct sockaddr *)&sin, sizeof(sin))) {
perror("bind");
exit(1);
}
return s;
}
void send_discovery(int s)
{
struct sockaddr_in sin;
uint8_t buf[PACKET_LENGTH];
asus_discovery_packet_t *discovery = (asus_discovery_packet_t *)buf;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(ASUS_DISCOVERY_PORT);
sin.sin_addr.s_addr = htonl(INADDR_BROADCAST);
memset(buf, 0, sizeof(buf));
discovery->service_id = SERVICE_ID_IBOX_INFO;
discovery->packet_type = PACKET_TYPE_REQUEST;
discovery->opcode = htole16(OPCODE_GETINFO);
if (sendto(s, buf, sizeof(buf), MSG_DONTROUTE,
(struct sockaddr *)&sin, sizeof(sin)) < 0) {
perror("sendto");
exit(1);
}
}
static void strncpy_limited(char *dst, size_t dstsiz,
const char *src, size_t srclen)
{
size_t i;
size_t lim = (srclen >= (dstsiz - 1)) ? (dstsiz - 2) : srclen;
for (i = 0; i < lim; ++i) {
unsigned char s = src[i];
if (s == ' ' || s == '\t') {
dst[i] = ' ';
} else if (isspace(s) || s == ';') {
dst[i] = '.'; // deliberately convert newline to dot
} else if (isprint(s)) {
dst[i] = s;
} else {
dst[i] = '_';
}
}
dst[lim] = '\0';
}
static void extract_modelname(const char *src, int srclen,
char *genus, int genuslen, char *species, int specieslen)
{
/* ASUS devices often (though not always) send just their
* model number like "RT-AC68U". In the string to be displayed
* to the user we want it to at least include "ASUS", so prepend
* it if necessary. */
if (strcasestr(src, "asus") == NULL && genuslen > 5) {
snprintf(genus, genuslen, "ASUS ");
genus += 5;
genuslen -= 5;
}
strncpy_limited(genus, genuslen, src, srclen);
strncpy_limited(species, specieslen, src, srclen);
}
int receive_response(int s, L2Map *l2map, char *response, int responselen)
{
struct timeval tv;
fd_set rfds;
if (l2map == NULL || response == NULL) {
fprintf(stderr, "%s: l2map=%p response=%p\n", __FUNCTION__,
l2map, response);
exit(1);
}
memset(&tv, 0, sizeof(tv));
tv.tv_sec = 1;
tv.tv_usec = 0;
FD_ZERO(&rfds);
FD_SET(s, &rfds);
if (select(s + 1, &rfds, NULL, NULL, &tv) < 0) {
perror("select");
exit(1);
}
if (FD_ISSET(s, &rfds)) {
uint8_t buf[PACKET_LENGTH + 64];
char addrbuf[INET_ADDRSTRLEN];
char genus[80], species[80];
const char *mac;
struct sockaddr_in from;
socklen_t fromlen = sizeof(from);
asus_discovery_packet_t *discovery = (asus_discovery_packet_t *)buf;
int id_len;
if (recvfrom(s, buf, sizeof(buf), 0,
(struct sockaddr *)&from, &fromlen) != PACKET_LENGTH) {
/* Not an ASUS discovery response */
return 1;
}
inet_ntop(AF_INET, &(from.sin_addr), addrbuf, sizeof(addrbuf));
if (discovery->packet_type != PACKET_TYPE_RESULT) {
/* We receive our own broadcast, and we send packet_type
* PACKET_TYPE_REQUEST. Ignore our own packet. */
return 1;
}
if ((discovery->service_id != SERVICE_ID_IBOX_INFO) ||
(strlen((char *)discovery->product_id) == 0)) {
/* Malformed packet, or isn't an ASUS response at all. */
return 1;
}
id_len = strnlen((char *)discovery->product_id,
sizeof(discovery->product_id));
extract_modelname((const char *)discovery->product_id, id_len,
genus, sizeof(genus), species, sizeof(species));
L2Map::iterator ii = l2map->find(std::string(addrbuf));
if (ii != l2map->end()) {
mac = ii->second.c_str();
} else {
mac = "00:00:00:00:00:00";
}
snprintf(response, responselen, "asus %s %s;%s", mac, genus, species);
return 0;
} else {
return -1;
}
}
#ifndef UNIT_TESTS
static void usage(char *progname)
{
fprintf(stderr, "usage: %s [-i ifname]\n", progname);
fprintf(stderr, "\t-i ifname - interface to use (default: lan0)\n");
exit(1);
}
int main(int argc, char **argv)
{
int s, opt, i;
const char *ifname = "br0";
setlinebuf(stdout);
alarm(30);
while ((opt = getopt(argc, argv, "i:")) != -1) {
switch (opt) {
case 'i':
ifname = optarg;
break;
default:
usage(argv[0]);
break;
}
}
if ((s = make_socket(ifname)) < 0) {
exit(1);
}
send_discovery(s);
for (i = 0; i < 128; i++) {
char response[128];
L2Map l2map;
get_l2_map(&l2map);
int rc = receive_response(s, &l2map, response, sizeof(response));
if (rc < 0) {
break;
} else if (rc == 0) {
printf("%s\n", response);
}
}
}
#endif