Use libnl to add/remove IPs on Linux
[oweals/nmrpflash.git] / nmrp.c
1 /**
2  * nmrpflash - Netgear Unbrick Utility
3  * Copyright (C) 2016 Joseph Lehner <joseph.c.lehner@gmail.com>
4  *
5  * nmrpflash 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 3 of the License, or
8  * (at your option) any later version.
9  *
10  * nmrpflash 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
16  * along with nmrpflash.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  */
19
20 #include <signal.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <unistd.h>
24 #include <errno.h>
25 #include <stdio.h>
26 #include <time.h>
27 #include "nmrpd.h"
28
29 #define NMRP_HDR_LEN 6
30 #define NMRP_OPT_HDR_LEN 4
31 #define NMRP_MIN_PKT_LEN (sizeof(struct eth_hdr) +  NMRP_HDR_LEN)
32
33 #define ETH_P_NMRP 0x0912
34
35 #ifndef PACKED
36 #define PACKED __attribute__((__packed__))
37 #endif
38
39 #ifdef NMRPFLASH_WINDOWS
40 #define setenv(name, value, overwrite) SetEnvironmentVariable(name, value)
41 #endif
42
43 enum nmrp_code {
44         NMRP_C_NONE = 0,
45         NMRP_C_ADVERTISE = 1,
46         NMRP_C_CONF_REQ = 2,
47         NMRP_C_CONF_ACK = 3,
48         NMRP_C_CLOSE_REQ = 4,
49         NMRP_C_CLOSE_ACK = 5,
50         NMRP_C_KEEP_ALIVE_REQ = 6,
51         NMRP_C_KEEP_ALIVE_ACK = 7,
52         NMRP_C_TFTP_UL_REQ = 16
53 };
54
55 enum nmrp_opt_type {
56         NMRP_O_MAGIC_NO = 0x0001,
57         NMRP_O_DEV_IP = 0x0002,
58         NMRP_O_DEV_REGION = 0x0004,
59         NMRP_O_FW_UP = 0x0101,
60         NMRP_O_ST_UP = 0x0102,
61         NMRP_O_FILE_NAME = 0x0181
62 };
63
64 struct nmrp_opt {
65         uint16_t type;
66         uint16_t len;
67         char val[1];
68 } PACKED;
69
70 struct nmrp_msg {
71         uint16_t reserved;
72         uint8_t code;
73         uint8_t id;
74         uint16_t len;
75         char opts[44];
76 } PACKED;
77
78 struct nmrp_pkt {
79         struct eth_hdr eh;
80         struct nmrp_msg msg;
81 } PACKED;
82
83 static const char *msg_code_str(uint16_t code)
84 {
85 #define MSG_CODE(x) case NMRP_C_ ## x: return #x
86         static char buf[16];
87
88         switch (code) {
89                 MSG_CODE(ADVERTISE);
90                 MSG_CODE(CONF_REQ);
91                 MSG_CODE(CONF_ACK);
92                 MSG_CODE(CLOSE_REQ);
93                 MSG_CODE(CLOSE_ACK);
94                 MSG_CODE(KEEP_ALIVE_REQ);
95                 MSG_CODE(KEEP_ALIVE_ACK);
96                 MSG_CODE(TFTP_UL_REQ);
97                 default:
98                         snprintf(buf, sizeof(buf), "%04x", ntohs(code));
99                         return buf;
100         }
101 #undef MSG_CODE
102 }
103
104 static uint16_t to_region_code(const char *region)
105 {
106 #define REGION_CODE(r, c) if (!strcasecmp(region, r)) return htons(c)
107         REGION_CODE("NA", 0x0001);
108         REGION_CODE("WW", 0x0002);
109         REGION_CODE("GR", 0x0003);
110         REGION_CODE("PR", 0x0004);
111         REGION_CODE("RU", 0x0005);
112         REGION_CODE("BZ", 0x0006);
113         REGION_CODE("IN", 0x0007);
114         REGION_CODE("KO", 0x0008);
115         REGION_CODE("JP", 0x0009);
116 #undef REGION_CODE
117         return 0;
118 }
119
120 static void msg_dump(struct nmrp_msg *msg)
121 {
122         int rem;
123
124         fprintf(stderr, "res=0x%04x, code=0x%02x, id=0x%02x, len=%u",
125                         ntohs(msg->reserved), msg->code, msg->id, ntohs(msg->len));
126
127         rem = ntohs(msg->len) - NMRP_HDR_LEN;
128         fprintf(stderr, "%s\n", rem ? "" : " (no opts)");
129 }
130
131 static void *msg_opt(struct nmrp_msg *msg, uint16_t type, uint16_t* len)
132 {
133         struct nmrp_opt* opt = (struct nmrp_opt*)msg->opts;
134         size_t rem = ntohs(msg->len) - NMRP_HDR_LEN;
135         uint16_t olen;
136
137         do {
138                 olen = ntohs(opt->len);
139                 if (olen < NMRP_OPT_HDR_LEN || olen > rem) {
140                         break;
141                 }
142
143                 if (ntohs(opt->type) == type) {
144                         if (len) {
145                                 *len = olen;
146                         }
147
148                         return opt->val;
149                 }
150
151                 opt = (struct nmrp_opt*)(((char *)opt) + olen);
152                 rem -= olen;
153         } while (rem);
154
155         return NULL;
156 }
157
158 static char *msg_filename(struct nmrp_msg *msg)
159 {
160         static char buf[256];
161         uint16_t len;
162         char *p = msg_opt(msg, NMRP_O_FILE_NAME, &len);
163         if (p) {
164                 len = MIN(sizeof(buf) - 1, len);
165                 memcpy(buf, p, len);
166                 buf[len] = '\0';
167                 return buf;
168         }
169
170         return NULL;
171 }
172
173 static inline void msg_init(struct nmrp_msg *msg, uint16_t code)
174 {
175         memset(msg, 0, sizeof(*msg));
176         msg->len = htons(NMRP_HDR_LEN);
177         msg->code = code;
178 }
179
180 static char *msg_mkopt(struct nmrp_msg *msg, char *p, uint16_t type, const void *val, size_t len)
181 {
182         struct nmrp_opt* opt = (struct nmrp_opt*)p;
183
184         len &= 0xffff;
185
186         msg->len = ntohs(msg->len);
187
188         if ((msg->len + len > sizeof(*msg))) {
189                 fprintf(stderr, "Error: invalid option - this is a bug\n");
190                 exit(1);
191         }
192
193         opt->type = htons(type);
194         opt->len = NMRP_OPT_HDR_LEN + len;
195
196         if (val) {
197                 memcpy(opt->val, val, len);
198         }
199
200         msg->len += opt->len;
201         p += opt->len;
202
203         msg->len = htons(msg->len);
204         opt->len = htons(opt->len);
205
206         return p;
207 }
208
209 static void msg_mkadvertise(struct nmrp_msg *msg, const char *magic)
210 {
211         msg_init(msg, NMRP_C_ADVERTISE);
212         msg_mkopt(msg, msg->opts, NMRP_O_MAGIC_NO, magic, strlen(magic));
213 }
214
215 static void msg_mkconfack(struct nmrp_msg *msg, uint32_t ipaddr, uint32_t ipmask, uint16_t region)
216 {
217         char *p;
218         uint32_t ip[2] = { ipaddr, ipmask };
219
220         msg_init(msg, NMRP_C_CONF_ACK);
221         p = msg_mkopt(msg, msg->opts, NMRP_O_DEV_IP, &ip, 8);
222         p = msg_mkopt(msg, p, NMRP_O_FW_UP, NULL, 0);
223
224 #ifdef NMRPFLASH_SET_REGION
225         if (region) {
226                 p = msg_mkopt(msg, p, NMRP_O_DEV_REGION, &region, 2);
227         }
228 #endif
229 }
230
231 #ifdef NMRPFLASH_FUZZ
232 #define NMRP_INITIAL_TIMEOUT 0
233 #define ethsock_create(a, b) ((struct ethsock*)1)
234 #define ethsock_get_hwaddr(a) ethsock_get_hwaddr_fake(a)
235 #define ethsock_recv(sock, buf, len) read(STDIN_FILENO, buf, len)
236 #define ethsock_send(a, b, c) (0)
237 #define ethsock_set_timeout(a, b) (0)
238 #define ethsock_ip_add(a, b, c, d) (0)
239 #define ethsock_ip_del(a, b) (0)
240 #define ethsock_close(a) (0)
241 #define tftp_put(a) (0)
242
243 static uint8_t *ethsock_get_hwaddr_fake(struct ethsock* sock)
244 {
245         static uint8_t hwaddr[6] = { 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa };
246         return hwaddr;
247 }
248 #else
249 #define NMRP_INITIAL_TIMEOUT 60
250 #endif
251
252 static int pkt_send(struct ethsock *sock, struct nmrp_pkt *pkt)
253 {
254         return ethsock_send(sock, pkt, sizeof(*pkt));
255 }
256
257 static int pkt_recv(struct ethsock *sock, struct nmrp_pkt *pkt)
258 {
259         ssize_t bytes, mlen;
260
261         memset(pkt, 0, sizeof(*pkt));
262         bytes = ethsock_recv(sock, pkt, sizeof(*pkt));
263         if (bytes < 0) {
264                 return 1;
265         } else if (!bytes) {
266                 return 2;
267         }
268
269         mlen = ntohs(pkt->msg.len);
270
271         if (bytes < (mlen + sizeof(pkt->eh))
272                         || bytes < NMRP_MIN_PKT_LEN
273                         || mlen < NMRP_HDR_LEN) {
274                 fprintf(stderr, "Short packet (%d raw, %d message)\n",
275                                 (int)bytes, (int)mlen);
276                 return 1;
277         } else if (mlen > sizeof(pkt->msg)) {
278                 printf("Truncating %d byte message.\n", (int)mlen);
279                 pkt->msg.len = htons(sizeof(pkt->msg));
280         }
281
282         return 0;
283 }
284
285 static int mac_parse(const char *str, uint8_t *hwaddr)
286 {
287         int i;
288         unsigned data[6];
289
290         sscanf(str, "%02x:%02x:%02x:%02x:%02x:%02x%n",
291                         data, data + 1, data + 2, data + 3, data + 4, data + 5, &i);
292
293         if (i == strlen(str)) {
294                 for (i = 0; i != 6; ++i) {
295                         if (data[i] > 255) {
296                                 break;
297                         }
298
299                         hwaddr[i] = data[i] & 0xff;
300                 }
301
302                 if (i == 6) {
303                         return 1;
304                 }
305         }
306         return 0;
307 }
308
309 struct is_valid_ip_arg
310 {
311         struct in_addr *ipaddr;
312         struct in_addr *ipmask;
313         int result;
314 };
315
316 static int is_valid_ip_cb(struct ethsock_ip_callback_args *args)
317 {
318 #define SUBNET(x) ((x)->ipaddr->s_addr & (x)->ipmask->s_addr)
319         struct is_valid_ip_arg *arg = args->arg;
320         if (SUBNET(args) == SUBNET(arg)) {
321                 arg->result = args->ipaddr->s_addr != arg->ipaddr->s_addr;
322                 return 0;
323         }
324
325         return 1;
326 #undef SUBNET
327 }
328
329 static int is_valid_ip(struct ethsock *sock, struct in_addr *ipaddr,
330                 struct in_addr *ipmask)
331 {
332         int status;
333         struct is_valid_ip_arg arg = {
334                 .ipaddr = ipaddr,
335                 .ipmask = ipmask,
336                 .result = 0
337         };
338
339         status = ethsock_for_each_ip(sock, is_valid_ip_cb, &arg);
340         return status < 0 ? status : arg.result;
341 }
342
343 static void sigh(int sig)
344 {
345         g_interrupted = 1;
346 }
347
348 static const char *spinner = "\\|/-";
349
350 int nmrp_do(struct nmrpd_args *args)
351 {
352         struct nmrp_pkt tx, rx;
353         uint8_t *src, dest[6];
354         uint16_t region;
355         char *filename;
356         time_t beg;
357         int i, status, ulreqs, expect, upload_ok, autoip;
358         struct ethsock *sock;
359         struct ethsock_ip_undo *ip_undo = NULL;
360         struct ethsock_arp_undo *arp_undo = NULL;
361         uint32_t intf_addr;
362         void (*sigh_orig)(int);
363         struct in_addr ipaddr;
364         struct in_addr ipmask;
365
366         if (args->op != NMRP_UPLOAD_FW) {
367                 fprintf(stderr, "Operation not implemented.\n");
368                 return 1;
369         }
370
371         if (!mac_parse(args->mac, dest)) {
372                 fprintf(stderr, "Invalid MAC address '%s'.\n", args->mac);
373                 return 1;
374         }
375
376         ipmask.s_addr = inet_addr(args->ipmask);
377         if (ipmask.s_addr == INADDR_NONE
378                         || netmask(bitcount(ipmask.s_addr)) != ipmask.s_addr) {
379                 fprintf(stderr, "Invalid subnet mask '%s'.\n", args->ipmask);
380                 return 1;
381         }
382
383         if (!args->ipaddr) {
384                 autoip = true;
385                 /* The MAC of the device that was used to test this utility starts
386                  * with a4:2b:8c, hence 164 (0xa4) and 183 (0x2b + 0x8c)
387                  */
388                 args->ipaddr = "10.164.183.252";
389
390                 if (!args->ipaddr_intf) {
391                         args->ipaddr_intf = "10.164.183.253";
392                 }
393         } else if (args->ipaddr_intf) {
394                 autoip = true;
395         } else {
396                 autoip = false;
397         }
398
399         if ((ipaddr.s_addr = inet_addr(args->ipaddr)) == INADDR_NONE) {
400                 fprintf(stderr, "Invalid IP address '%s'.\n", args->ipaddr);
401                 return 1;
402         }
403
404         if (args->ipaddr_intf && (intf_addr = inet_addr(args->ipaddr_intf)) == INADDR_NONE) {
405                 fprintf(stderr, "Invalid IP address '%s'.\n", args->ipaddr_intf);
406                 return 1;
407         }
408
409         if (args->file_local && strcmp(args->file_local, "-") && access(args->file_local, R_OK) == -1) {
410                 fprintf(stderr, "Error accessing file '%s'.\n", args->file_local);
411                 return 1;
412         }
413
414         if (args->file_remote) {
415                 if (!tftp_is_valid_filename(args->file_remote)) {
416                         fprintf(stderr, "Invalid remote filename '%s'.\n",
417                                         args->file_remote);
418                         return 1;
419                 }
420         }
421
422         if (args->region) {
423                 region = to_region_code(args->region);
424                 if (!region) {
425                         fprintf(stderr, "Invalid region code '%s'.\n", args->region);
426                         return 1;
427                 }
428         } else {
429                 region = 0;
430         }
431
432         status = 1;
433
434         sock = ethsock_create(args->intf, ETH_P_NMRP);
435         if (!sock) {
436                 return 1;
437         }
438
439         sigh_orig = signal(SIGINT, sigh);
440
441         if (!autoip) {
442                 status = is_valid_ip(sock, &ipaddr, &ipmask);
443                 if (status <= 0) {
444                         if (!status) {
445                                 fprintf(stderr, "Address %s/%s cannot be used on interface %s.\n",
446                                                 args->ipaddr, args->ipmask, args->intf);
447                         }
448                         goto out;
449                 }
450         } else {
451                 if (verbosity) {
452                         printf("Adding %s to interface %s.\n", args->ipaddr_intf, args->intf);
453                 }
454
455                 if (ethsock_ip_add(sock, intf_addr, ipmask.s_addr, &ip_undo) != 0) {
456                         goto out;
457                 }
458         }
459
460         if (ethsock_set_timeout(sock, args->rx_timeout)) {
461                 goto out;
462         }
463
464         src = ethsock_get_hwaddr(sock);
465         if (!src) {
466                 goto out;
467         }
468
469         memcpy(tx.eh.ether_shost, src, 6);
470         memcpy(tx.eh.ether_dhost, dest, 6);
471         tx.eh.ether_type = htons(ETH_P_NMRP);
472
473         msg_mkadvertise(&tx.msg, "NTGR");
474
475         i = 0;
476         upload_ok = 0;
477         beg = time_monotonic();
478
479         while (!g_interrupted) {
480                 printf("\rAdvertising NMRP server on %s ... %c",
481                                 args->intf, spinner[i]);
482                 fflush(stdout);
483                 i = (i + 1) & 3;
484
485                 if (pkt_send(sock, &tx) < 0) {
486                         xperror("sendto");
487                         goto out;
488                 }
489
490                 status = pkt_recv(sock, &rx);
491                 if (status == 0) {
492                         if (memcmp(rx.eh.ether_dhost, src, 6) == 0) {
493                                 break;
494                         } else if (verbosity) {
495                                 printf("\nIgnoring bogus response: %s -> %s.\n",
496                                                 mac_to_str(rx.eh.ether_shost),
497                                                 mac_to_str(rx.eh.ether_dhost));
498                         }
499                 } else if (status == 1) {
500                         goto out;
501                 } else {
502                         /* because we don't want nmrpflash's exit status to be zero */
503                         status = 1;
504                         if ((time_monotonic() - beg) >= NMRP_INITIAL_TIMEOUT) {
505                                 printf("\nNo response after 60 seconds. Bailing out.\n");
506                                 goto out;
507                         }
508                 }
509         }
510
511         printf("\n");
512
513         expect = NMRP_C_CONF_REQ;
514         ulreqs = 0;
515
516         while (!g_interrupted) {
517                 if (expect != NMRP_C_NONE && rx.msg.code != expect) {
518                         fprintf(stderr, "Received %s while waiting for %s!\n",
519                                         msg_code_str(rx.msg.code), msg_code_str(expect));
520                 }
521
522                 msg_init(&tx.msg, NMRP_C_NONE);
523
524                 status = 1;
525
526                 switch (rx.msg.code) {
527                         case NMRP_C_ADVERTISE:
528                                 printf("Received NMRP advertisement from %s.\n",
529                                                 mac_to_str(rx.eh.ether_shost));
530                                 status = 1;
531                                 goto out;
532                         case NMRP_C_CONF_REQ:
533                                 msg_mkconfack(&tx.msg, ipaddr.s_addr, ipmask.s_addr, region);
534                                 expect = NMRP_C_TFTP_UL_REQ;
535
536                                 printf("Received configuration request from %s.\n",
537                                                 mac_to_str(rx.eh.ether_shost));
538
539                                 memcpy(tx.eh.ether_dhost, rx.eh.ether_shost, 6);
540
541                                 printf("Sending configuration: %s, netmask %s.\n",
542                                                 args->ipaddr, args->ipmask);
543
544                                 if (ethsock_arp_add(sock, rx.eh.ether_shost, ipaddr.s_addr, &arp_undo) != 0) {
545                                         goto out;
546                                 }
547
548                                 break;
549                         case NMRP_C_TFTP_UL_REQ:
550                                 if (!upload_ok) {
551                                         if (++ulreqs > 5) {
552                                                 printf("Bailing out after %d upload requests.\n",
553                                                                 ulreqs);
554                                                 tx.msg.code = NMRP_C_CLOSE_REQ;
555                                                 break;
556                                         }
557                                 } else {
558                                         if (verbosity) {
559                                                 printf("Ignoring extra upload request.\n");
560                                         }
561                                         ethsock_set_timeout(sock, args->ul_timeout);
562                                         tx.msg.code = NMRP_C_KEEP_ALIVE_REQ;
563                                         break;
564                                 }
565
566                                 filename = msg_filename(&rx.msg);
567                                 if (filename) {
568                                         if (!args->file_remote) {
569                                                 args->file_remote = filename;
570                                         }
571                                         printf("Received upload request: filename '%s'.\n", filename);
572                                 } else if (!args->file_remote) {
573                                         args->file_remote = args->file_local;
574                                         printf("Received upload request with empty filename.\n");
575                                 }
576
577                                 status = 0;
578
579                                 if (args->tftpcmd) {
580                                         printf("Executing '%s' ... \n", args->tftpcmd);
581                                         setenv("IP", inet_ntoa(ipaddr), 1);
582                                         setenv("PORT", lltostr(args->port, 10), 1);
583                                         setenv("MAC", mac_to_str(rx.eh.ether_shost), 1);
584                                         setenv("NETMASK", inet_ntoa(ipmask), 1);
585                                         //setenv("FILENAME", args->file_remote ? args->file_remote : "", 1);
586                                         status = system(args->tftpcmd);
587                                 }
588
589                                 if (!status && args->file_local) {
590                                         if (!autoip) {
591                                                 status = is_valid_ip(sock, &ipaddr, &ipmask);
592                                                 if (status < 0) {
593                                                         goto out;
594                                                 } else if (!status) {
595                                                         printf("IP address of %s has changed. Please assign a "
596                                                                         "static ip to the interface.\n", args->intf);
597                                                         tx.msg.code = NMRP_C_CLOSE_REQ;
598                                                         break;
599                                                 }
600                                         }
601
602                                         if (verbosity) {
603                                                 printf("Using remote filename '%s'.\n",
604                                                                 args->file_remote);
605                                         }
606
607                                         if (!strcmp(args->file_local, "-")) {
608                                                 printf("Uploading from stdin ... ");
609                                         } else {
610                                                 printf("Uploading %s ... ", leafname(args->file_local));
611                                         }
612                                         fflush(stdout);
613                                         if (!(status = tftp_put(args))) {
614                                                 printf("OK\n");
615                                         }
616
617                                 }
618
619                                 if (!status) {
620                                         printf("Waiting for remote to respond.\n");
621                                         upload_ok = 1;
622                                         ethsock_set_timeout(sock, args->ul_timeout);
623                                         tx.msg.code = NMRP_C_KEEP_ALIVE_REQ;
624                                         expect = NMRP_C_NONE;
625                                 } else if (status == -2) {
626                                         expect = NMRP_C_TFTP_UL_REQ;
627                                 } else {
628                                         goto out;
629                                 }
630
631                                 break;
632                         case NMRP_C_KEEP_ALIVE_REQ:
633                                 tx.msg.code = NMRP_C_KEEP_ALIVE_ACK;
634                                 ethsock_set_timeout(sock, args->ul_timeout);
635                                 printf("Received keep-alive request.\n");
636                                 break;
637                         case NMRP_C_CLOSE_REQ:
638                                 tx.msg.code = NMRP_C_CLOSE_ACK;
639                                 break;
640                         case NMRP_C_CLOSE_ACK:
641                                 status = 0;
642                                 goto out;
643                         default:
644                                 fprintf(stderr, "Unknown message code 0x%02x!\n",
645                                                 rx.msg.code);
646                                 msg_dump(&rx.msg);
647                 }
648
649                 if (tx.msg.code != NMRP_C_NONE) {
650                         if (pkt_send(sock, &tx) < 0) {
651                                 xperror("sendto");
652                                 goto out;
653                         }
654
655                         if (tx.msg.code == NMRP_C_CLOSE_REQ) {
656                                 goto out;
657                         }
658                 }
659
660                 if (rx.msg.code == NMRP_C_CLOSE_REQ) {
661                         printf("Remote finished. Closing connection.\n");
662                         break;
663                 }
664
665                 status = pkt_recv(sock, &rx);
666                 if (status) {
667                         if (status == 2) {
668                                 fprintf(stderr, "Timeout while waiting for %s.\n",
669                                                 msg_code_str(expect));
670                         }
671                         goto out;
672                 }
673
674                 ethsock_set_timeout(sock, args->rx_timeout);
675
676         }
677
678         if (!g_interrupted) {
679                 status = 0;
680                 if (ulreqs) {
681                         printf("Reboot your device now.\n");
682                 } else {
683                         printf("No upload request received.\n");
684                 }
685         }
686
687 out:
688         signal(SIGINT, sigh_orig);
689         ethsock_arp_del(sock, &arp_undo);
690         ethsock_ip_del(sock, &ip_undo);
691         ethsock_close(sock);
692         return status;
693 }