blob: 96ba81f2f57de36c8f8bdbfbb7bcd7b95624db81 [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>
#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 const char *replace_newlines(const uint8_t *src, int srclen,
char *dst, int dstlen)
{
int i, j;
for (i = 0, j = 0; i < srclen && j < (dstlen - 1); i++) {
dst[j++] = (src[i] == '\n') ? '.' : src[i];
}
dst[j] = '\0';
return dst;
}
int receive_response(int s, char *response, int responselen)
{
struct timeval tv;
fd_set rfds;
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[16], namebuf[80];
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));
replace_newlines(discovery->product_id, id_len, namebuf, sizeof(namebuf));
snprintf(response, responselen, "%s|%s", addrbuf, namebuf);
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;
char *ifname = "br0";
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];
int rc = receive_response(s, response, sizeof(response));
if (rc < 0) {
break;
} else if (rc == 0) {
printf("%s\n", response);
}
}
}
#endif