1 /*
2 * $Id$
3 *
4 * Copyright (c) 2008, 2009, 2010
5 * Sten Spans <sten@blinkenlights.nl>
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20 #include "common.h"
21 #include "util.h"
22 #include "proto/protos.h"
23 #include "cli.h"
24 #include <sys/file.h>
25 #include <sys/un.h>
26 #include <netdb.h>
27 #include <termios.h>
28
29 extern struct proto protos[];
30 int status = EXIT_SUCCESS;
31 static void usage() __noreturn;
32
33 static struct mode modes[] = {
34 { &cli_header, &cli_write, NULL },
35 { NULL, NULL, NULL },
36 { NULL, &batch_write, NULL },
37 { &debug_header, &debug_write, &debug_close },
38 #if HAVE_EVHTTP_H
39 { &http_connect, &http_request, &http_dispatch },
40 #endif /* HAVE_EVHTTP_H */
41 };
42
43 #define MODE_CLI 0
44 #define MODE_PRINT 1
45 #define MODE_BATCH 2
46 #define MODE_DEBUG 3
47 #define MODE_HTTP 4
48
49 #define TERM_DEFAULT 80
50 static int host_width = 20;
51 static int port_width = 10;
52
53 #if HAVE_EVHTTP_H
54 char *http_host = NULL;
55 char *http_path = NULL;
56 char *hostname = NULL;
57 short http_port = 0;
58
59 struct evhttp_connection *evcon = NULL;
60 struct evhttp_request *lreq = NULL;
61 #endif /* HAVE_EVHTTP_H */
62
63 __noreturn
cli_main(int argc,char * argv[])64 void cli_main(int argc, char *argv[]) {
65 int ch, i;
66 uint8_t proto = 0, mode = MODE_CLI;
67 uint32_t *indexes = NULL;
68 struct sockaddr_un usock = {};
69 int fd = -1;
70 time_t now;
71 struct parent_msg *msg;
72 uint16_t holdtime;
73
74 options = 0;
75
76 while ((ch = getopt(argc, argv, "LCEFNbdfp:ovh")) != -1) {
77 switch(ch) {
78 case 'L':
79 proto |= (1 << PROTO_LLDP);
80 break;
81 case 'C':
82 proto |= (1 << PROTO_CDP);
83 break;
84 case 'E':
85 proto |= (1 << PROTO_EDP);
86 break;
87 case 'F':
88 proto |= (1 << PROTO_FDP);
89 break;
90 case 'N':
91 proto |= (1 << PROTO_NDP);
92 break;
93 case 'b':
94 mode = MODE_BATCH;
95 break;
96 case 'd':
97 mode = MODE_DEBUG;
98 break;
99 case 'f':
100 mode = MODE_PRINT;
101 break;
102 #if HAVE_EVHTTP_H
103 case 'p':
104 if (http_host)
105 usage();
106 mode = MODE_HTTP;
107 if (strncmp(optarg, "http://", 7) == 0)
108 optarg += 7;
109 char *host = my_strdup(optarg);
110 char *path = strchr(host, '/');
111 if (path) {
112 http_path = my_strdup(path);
113 *path = '\0';
114 } else {
115 http_path = my_strdup("/");
116 }
117 http_host = my_strdup(host);
118 free(host);
119 break;
120 #endif /* HAVE_EVHTTP_H */
121 case 'o':
122 options |= OPT_ONCE;
123 break;
124 case 'v':
125 loglevel++;
126 break;
127 default:
128 usage();
129 }
130 }
131
132 argc -= optind;
133 argv += optind;
134
135 // default to all protocols
136 if (!proto)
137 proto = UINT8_MAX;
138
139 if (argc) {
140 indexes = my_calloc(argc, sizeof(msg->index));
141 for (i = 0; i < argc; i++) {
142 indexes[i] = if_nametoindex(argv[i]);
143 if (!indexes[i])
144 usage();
145 }
146 }
147
148 // open socket connection
149 fd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
150 // XXX: make do with a stream and hope for the best
151 if ((fd == -1) && (errno == EPROTONOSUPPORT))
152 fd = my_socket(AF_UNIX, SOCK_STREAM, 0);
153 if (fd == -1)
154 my_fatale("failed to create socket");
155
156 usock.sun_family = AF_UNIX;
157 strlcpy(usock.sun_path, PACKAGE_SOCKET, sizeof(usock.sun_path));
158
159 if (connect(fd, (struct sockaddr *)&usock, sizeof(usock)) == -1) {
160 if (errno == EACCES)
161 my_fatal("please add yourself to the " PACKAGE_USER " group");
162 else if (errno == ECONNREFUSED)
163 my_fatal("please enable receive mode before using " PACKAGE_CLI);
164 else
165 my_fatale("failed to open " PACKAGE_SOCKET);
166 }
167 if ((now = time(NULL)) == (time_t)-1)
168 my_fatale("failed to fetch time");
169
170 if (modes[mode].init)
171 modes[mode].init();
172
173 msg = my_malloc(PARENT_MSG_SIZ);
174
175 while (read(fd, msg, PARENT_MSG_MAX) == PARENT_MSG_MAX) {
176
177 if (msg->proto >= PROTO_MAX)
178 continue;
179 if ((msg->len < (ETHER_MIN_LEN - ETHER_VLAN_ENCAP_LEN)) ||
180 (msg->len > ETHER_MAX_LEN))
181 continue;
182
183 // skip unwanted interfaces
184 if (indexes) {
185 for (i = 0; i < argc; i++) {
186 if (indexes[i] == msg->index)
187 break;
188 }
189 if (i == argc)
190 continue;
191 }
192
193 // skip unwanted protocols
194 if (!(proto & (1 << msg->proto)))
195 continue;
196
197 // decode packet
198 msg->decode = DECODE_STR;
199 if (mode == MODE_PRINT)
200 msg->decode = DECODE_PRINT;
201
202 if (protos[msg->proto].decode(msg) == 0) {
203 peer_free(msg->peer);
204 continue;
205 }
206 // skip expired packets
207 if (msg->ttl < (now - msg->received))
208 continue;
209
210 holdtime = msg->ttl - (now - msg->received);
211
212 if (modes[mode].write)
213 modes[mode].write(msg, holdtime);
214
215 peer_free(msg->peer);
216 memset(msg, 0, PARENT_MSG_SIZ);
217
218 if (options & OPT_ONCE)
219 goto out;
220
221 if (mode == MODE_PRINT)
222 printf("\n");
223 }
224
225 out:
226 if (modes[mode].dispatch)
227 modes[mode].dispatch();
228
229 free(msg);
230 exit(status);
231 }
232
swapchr(char * str,const int c,const int d)233 static inline void swapchr(char *str, const int c, const int d) {
234 if (!str)
235 return;
236
237 while ((str = strchr(str, c)) != NULL) {
238 *str = d;
239 str++;
240 }
241 }
242
243 #define STR(x) (x) ? x : ""
244
batch_write(struct parent_msg * msg,const uint16_t holdtime)245 void batch_write(struct parent_msg *msg, const uint16_t holdtime) {
246 char *peer_host = msg->peer[PEER_HOSTNAME];
247 char *peer_portname = msg->peer[PEER_PORTNAME];
248 char *peer_portdescr = msg->peer[PEER_PORTDESCR];
249 char *cap = msg->peer[PEER_CAP];
250 static unsigned int count = 0;
251
252 swapchr(peer_host, '\'', '\"');
253 swapchr(peer_portname, '\'', '\"');
254 swapchr(peer_portdescr, '\'', '\"');
255
256 printf("INTERFACE_%u='%s'\n", count, STR(msg->name));
257 printf("HOSTNAME_%u='%s'\n", count, STR(peer_host));
258 printf("PORTNAME_%u='%s'\n", count, STR(peer_portname));
259 printf("PORTDESCR_%u='%s'\n", count, STR(peer_portdescr));
260 printf("PROTOCOL_%u='%s'\n", count, protos[msg->proto].name);
261 printf("ADDR_INET4_%u='%s'\n", count, STR(msg->peer[PEER_ADDR_INET4]));
262 printf("ADDR_INET6_%u='%s'\n", count, STR(msg->peer[PEER_ADDR_INET6]));
263 printf("ADDR_802_%u='%s'\n", count, STR(msg->peer[PEER_ADDR_802]));
264 printf("VLAN_ID_%u='%s'\n", count, STR(msg->peer[PEER_VLAN_ID]));
265 printf("CAPABILITIES_%u='%s'\n", count, STR(cap));
266 printf("TTL_%u='%" PRIu16 "'\n", count, msg->ttl);
267 printf("HOLDTIME_%u='%" PRIu16 "'\n", count, holdtime);
268
269 count++;
270 }
271
cli_header()272 void cli_header() {
273 int twidth = TERM_DEFAULT;
274 struct winsize ws = {};
275
276 // Try to fetch the terminal width
277 if (ioctl(0, TIOCGWINSZ, &ws) == 0)
278 twidth = ws.ws_col;
279
280 twidth -= TERM_DEFAULT;
281 if (twidth > 0) {
282 if (twidth > 20) {
283 host_width += 10;
284 port_width = twidth;
285 } else {
286 twidth /= 2;
287 host_width += twidth;
288 port_width += twidth;
289 }
290 }
291
292 printf("Capability Codes:\n"
293 "\tr - Repeater, B - Bridge, H - Host, R - Router, S - Switch,\n"
294 "\tW - WLAN Access Point, C - DOCSIS Device, T - Telephone, "
295 "O - Other\n\n");
296 printf("%-*s Local Intf Proto "
297 "Hold-time Capability Port ID\n", host_width, "Device ID");
298 }
299
cli_write(struct parent_msg * msg,const uint16_t holdtime)300 void cli_write(struct parent_msg *msg, const uint16_t holdtime) {
301 char *peer_host = msg->peer[PEER_HOSTNAME];
302 char *peer_portname = msg->peer[PEER_PORTNAME];
303 char *peer_portdescr = msg->peer[PEER_PORTDESCR];
304 char *peer_suffix = NULL;
305 char *cap = msg->peer[PEER_CAP];
306
307 // shorten
308 if (peer_host && (strlen(peer_host) > host_width))
309 peer_host[strcspn(peer_host, ".")] = '\0';
310
311 peer_suffix = peer_portname;
312 if (!peer_suffix)
313 peer_suffix = peer_portdescr;
314 if (peer_suffix)
315 portname_abbr(peer_suffix);
316
317 printf("%-*.*s %-13.13s %-7.7s %-12" PRIu16 " %-13.13s %-*.*s\n",
318 host_width, host_width, STR(peer_host), STR(msg->name), protos[msg->proto].name,
319 holdtime, STR(cap), port_width, port_width, STR(peer_suffix));
320 }
321
debug_header()322 void debug_header() {
323 my_pcap_init(STDOUT_FILENO);
324 }
325
debug_write(struct parent_msg * msg,const uint16_t holdtime)326 void debug_write(struct parent_msg *msg, const uint16_t holdtime) {
327 my_pcap_write(msg);
328 }
329
debug_close()330 void debug_close() {
331 my_pcap_close();
332 }
333
334 #if HAVE_EVHTTP_H
http_connect()335 void http_connect() {
336 struct servent *sp;
337 struct hostent *hp;
338
339 if (!http_port) {
340 if ((sp = getservbyname("http", "tcp")) == NULL)
341 my_fatal("HTTP port not found");
342 http_port = ntohs(sp->s_port);
343 }
344
345 // initalize the event library
346 event_init();
347
348 evcon = evhttp_connection_new(http_host, http_port);
349 if (evcon == NULL)
350 my_fatal("HTTP connection failed");
351
352 hostname = my_malloc(_POSIX_HOST_NAME_MAX);
353 if (gethostname(hostname, _POSIX_HOST_NAME_MAX) == -1)
354 my_fatale("gethostname failed");
355 if (((hp = gethostbyname(hostname)) != NULL) &&
356 (strcmp(hp->h_name, "localhost") != 0))
357 strlcpy(hostname, hp->h_name, _POSIX_HOST_NAME_MAX);
358 }
359
http_request(struct parent_msg * msg,const uint16_t holdtime)360 void http_request(struct parent_msg *msg, const uint16_t holdtime) {
361 int ret;
362 char *peer_host, *peer_port, *data;
363 char *cap = msg->peer[PEER_CAP];
364 struct evhttp_request *req = NULL;
365
366 // url-encode the received strings
367 peer_host = evhttp_encode_uri(STR(msg->peer[PEER_HOSTNAME]));
368 peer_port = evhttp_encode_uri(STR(msg->peer[PEER_PORTNAME]));
369
370 ret = asprintf(&data,
371 "hostname=%s&interface=%s&peer_hostname=%s&peer_portname=%s&"
372 "protocol=%s&capabilities=%s&ttl=%" PRIu16 "&holdtime=%" PRIu16
373 "\r\n\r\n",
374 hostname, STR(msg->name), peer_host, peer_port,
375 protos[msg->proto].name, STR(cap), msg->ttl, holdtime);
376 if (ret == -1)
377 my_fatal("asprintf failed");
378
379 free(peer_host);
380 free(peer_port);
381
382 req = evhttp_request_new(http_reply, NULL);
383 if (ret == -1)
384 my_fatal("failed to allocate HTTP request");
385
386 evhttp_add_header(req->output_headers, "Host", http_host);
387 evhttp_add_header(req->output_headers, "User-Agent",
388 PACKAGE_CLI "/" PACKAGE_VERSION);
389 evhttp_add_header(req->output_headers, "Content-Type",
390 "application/x-www-form-urlencoded");
391 evbuffer_add(req->output_buffer, data, strlen(data));
392
393 if (evhttp_make_request(evcon, req, EVHTTP_REQ_POST, http_path) == -1)
394 my_fatal("failed to create HTTP request");
395 lreq = req;
396
397 free(data);
398 }
399
http_reply(struct evhttp_request * req,void * arg)400 void http_reply(struct evhttp_request *req, void *arg) {
401 if ((req == NULL) || (req->response_code == 0))
402 my_fatal("HTTP request failed");
403
404 if (req->response_code < HTTP_BADREQUEST)
405 return;
406
407 my_log(CRIT, "HTTP error %d received", req->response_code);
408 status = EXIT_FAILURE;
409 }
410
http_dispatch()411 void http_dispatch() {
412 // auto-close the connection after the last request
413 if (lreq)
414 evhttp_add_header(lreq->output_headers, "Connection", "close");
415
416 event_dispatch();
417 evhttp_connection_free(evcon);
418 }
419 #endif /* HAVE_EVHTTP_H */
420
421 __noreturn
usage()422 static void usage() {
423 extern char *__progname;
424
425 fprintf(stderr, PACKAGE_NAME " version " PACKAGE_VERSION "\n"
426 "Usage: %s [-LCEFN] [INTERFACE] [INTERFACE]\n"
427 "\t-L = Print LLDP\n"
428 "\t-C = Print CDP\n"
429 "\t-E = Print EDP\n"
430 "\t-F = Print FDP\n"
431 "\t-N = Print NDP\n"
432 "\t-b = Print scriptable output\n"
433 "\t-d = Dump pcap-compatible packets to stdout\n"
434 "\t-f = Print full decode\n"
435 "\t-o = Decode only one packet\n"
436 #if HAVE_EVHTTP_H
437 "\t-p <url> = Post decode to url\n"
438 #endif /* HAVE_EVHTTP_H */
439 "\t-v = Increase logging verbosity\n"
440 "\t-h = Print this message\n",
441 __progname);
442
443 exit(EXIT_FAILURE);
444 }
445
446