2 * dpauxmon is an extcap tool used to monitor DisplayPort AUX channel traffic
3 * coming in from the kernel via generic netlink
4 * Copyright 2018, Dirk Eibach, Guntermann & Drunck GmbH <dirk.eibach@gdsys.cc>
6 * Wireshark - Network traffic analyzer
7 * By Gerald Combs <gerald@wireshark.org>
8 * Copyright 1998 Gerald Combs
10 * SPDX-License-Identifier: GPL-2.0-or-later
14 #define WS_LOG_DOMAIN "dpauxmon"
16 #include <wireshark.h>
18 #include "extcap-base.h"
20 #include <wsutil/strtoi.h>
21 #include <wsutil/filesystem.h>
22 #include <wsutil/privileges.h>
23 #include <wsutil/wslog.h>
24 #include <writecap/pcapio.h>
26 #include <netlink/netlink.h>
27 #include <netlink/genl/genl.h>
28 #include <netlink/genl/ctrl.h>
29 #include <netlink/genl/mngt.h>
33 #include <linux/genetlink.h>
35 #include "dpauxmon_user.h"
37 #define PCAP_SNAPLEN 128
39 #define DPAUXMON_EXTCAP_INTERFACE "dpauxmon"
40 #define DPAUXMON_VERSION_MAJOR "0"
41 #define DPAUXMON_VERSION_MINOR "1"
42 #define DPAUXMON_VERSION_RELEASE "0"
47 EXTCAP_BASE_OPTIONS_ENUM
,
53 static struct ws_option longopts
[] = {
55 /* Generic application options */
56 { "help", ws_no_argument
, NULL
, OPT_HELP
},
57 { "version", ws_no_argument
, NULL
, OPT_VERSION
},
58 /* Interfaces options */
59 { "interface_id", ws_required_argument
, NULL
, OPT_INTERFACE_ID
},
63 static struct nla_policy dpauxmon_attr_policy
[DPAUXMON_ATTR_MAX
+ 1] = {
64 [DPAUXMON_ATTR_IFINDEX
] = { .type
= NLA_U32
},
65 [DPAUXMON_ATTR_FROM_SOURCE
] = { .type
= NLA_FLAG
},
66 [DPAUXMON_ATTR_TIMESTAMP
] = { .type
= NLA_MSECS
},
69 struct family_handler_args
{
74 static int list_config(char *interface
)
79 ws_warning("No interface specified.");
83 if (g_strcmp0(interface
, DPAUXMON_EXTCAP_INTERFACE
)) {
84 ws_warning("interface must be %s", DPAUXMON_EXTCAP_INTERFACE
);
88 printf("arg {number=%u}{call=--interface_id}{display=Interface index}"
89 "{type=unsigned}{range=1,65535}{default=%u}{tooltip=The dpauxmon interface index}\n",
92 extcap_config_debug(&inc
);
97 static int setup_dumpfile(const char* fifo
, FILE** fp
)
99 uint64_t bytes_written
= 0;
102 if (!g_strcmp0(fifo
, "-")) {
107 *fp
= fopen(fifo
, "wb");
109 ws_warning("Error creating output file: %s", g_strerror(errno
));
113 if (!libpcap_write_file_header(*fp
, 275, PCAP_SNAPLEN
, false, &bytes_written
, &err
)) {
114 ws_warning("Can't write pcap file header");
123 static int dump_packet(FILE* fp
, const char* buf
, const uint32_t buflen
, uint64_t ts_usecs
)
125 uint64_t bytes_written
= 0;
127 int ret
= EXIT_SUCCESS
;
129 if (!libpcap_write_packet(fp
, ts_usecs
/ 1000000, ts_usecs
% 1000000, buflen
, buflen
, buf
, &bytes_written
, &err
)) {
130 ws_warning("Can't write packet");
139 static int error_handler(struct sockaddr_nl
*nla _U_
, struct nlmsgerr
*err
,
142 int *ret
= (int*)arg
;
147 static int ack_handler(struct nl_msg
*msg _U_
, void *arg
)
149 int *ret
= (int*)arg
;
154 static int family_handler(struct nl_msg
*msg
, void *arg
)
156 struct family_handler_args
*grp
= (struct family_handler_args
*)arg
;
157 struct nlattr
*tb
[CTRL_ATTR_MAX
+ 1];
158 struct genlmsghdr
*gnlh
= (struct genlmsghdr
*)nlmsg_data(nlmsg_hdr(msg
));
159 struct nlattr
*mcgrp
;
162 nla_parse(tb
, CTRL_ATTR_MAX
, genlmsg_attrdata(gnlh
, 0),
163 genlmsg_attrlen(gnlh
, 0), NULL
);
165 if (!tb
[CTRL_ATTR_MCAST_GROUPS
])
168 nla_for_each_nested(mcgrp
, tb
[CTRL_ATTR_MCAST_GROUPS
], rem_mcgrp
) {
169 struct nlattr
*tb_mcgrp
[CTRL_ATTR_MCAST_GRP_MAX
+ 1];
171 nla_parse(tb_mcgrp
, CTRL_ATTR_MCAST_GRP_MAX
,
172 (struct nlattr
*)nla_data(mcgrp
), nla_len(mcgrp
), NULL
);
174 if (!tb_mcgrp
[CTRL_ATTR_MCAST_GRP_NAME
] ||
175 !tb_mcgrp
[CTRL_ATTR_MCAST_GRP_ID
])
178 if (strncmp((const char*)nla_data(tb_mcgrp
[CTRL_ATTR_MCAST_GRP_NAME
]),
180 nla_len(tb_mcgrp
[CTRL_ATTR_MCAST_GRP_NAME
])))
183 grp
->id
= nla_get_u32(tb_mcgrp
[CTRL_ATTR_MCAST_GRP_ID
]);
191 static int nl_get_multicast_id(struct nl_sock
*sock
, int family
,
197 struct family_handler_args grp
= {
206 cb
= nl_cb_alloc(NL_CB_DEFAULT
);
212 ctrlid
= genl_ctrl_resolve(sock
, "nlctrl");
214 genlmsg_put(msg
, 0, 0, ctrlid
, 0, 0, CTRL_CMD_GETFAMILY
, 0);
217 NLA_PUT_U16(msg
, CTRL_ATTR_FAMILY_ID
, family
);
219 ret
= nl_send_auto_complete(sock
, msg
);
221 goto nla_put_failure
;
225 nl_cb_err(cb
, NL_CB_CUSTOM
, error_handler
, &ret
);
226 nl_cb_set(cb
, NL_CB_ACK
, NL_CB_CUSTOM
, ack_handler
, &ret
);
227 nl_cb_set(cb
, NL_CB_VALID
, NL_CB_CUSTOM
, family_handler
, &grp
);
230 nl_recvmsgs(sock
, cb
);
242 * netlink callback handlers
245 static int nl_receive_timeout(struct nl_sock
* sk
, struct sockaddr_nl
* nla
, unsigned char** buf
, struct ucred
** creds
)
247 struct pollfd fds
= {nl_socket_get_fd(sk
), POLLIN
, 0};
248 int poll_res
= poll(&fds
, 1, 500);
251 ws_debug("poll() failed in nl_receive_timeout");
253 return -nl_syserr2nlerr(errno
);
256 return poll_res
? nl_recv(sk
, nla
, buf
, creds
) : 0;
259 static int send_start(struct nl_sock
*sock
, int family
, unsigned int interface_id
)
268 ws_critical("Unable to allocate netlink message");
272 hdr
= genlmsg_put(msg
, NL_AUTO_PID
, NL_AUTO_SEQ
, family
, 0, 0,
273 DPAUXMON_CMD_START
, 1);
275 ws_critical("Unable to write genl header");
280 if ((err
= nla_put_u32(msg
, DPAUXMON_ATTR_IFINDEX
, interface_id
)) < 0) {
281 ws_critical("Unable to add attribute: %s", nl_geterror(err
));
286 if ((err
= nl_send_auto_complete(sock
, msg
)) < 0)
287 ws_debug("Starting monitor failed, already running? :%s", nl_geterror(err
));
294 static void send_stop(struct nl_sock
*sock
, int family
, unsigned int interface_id
)
302 ws_critical("Unable to allocate netlink message");
306 hdr
= genlmsg_put(msg
, NL_AUTO_PID
, NL_AUTO_SEQ
, family
, 0, 0,
307 DPAUXMON_CMD_STOP
, 1);
309 ws_critical("Unable to write genl header");
313 if ((err
= nla_put_u32(msg
, DPAUXMON_ATTR_IFINDEX
, interface_id
)) < 0) {
314 ws_critical("Unable to add attribute: %s", nl_geterror(err
));
318 if ((err
= nl_send_auto_complete(sock
, msg
)) < 0) {
319 ws_critical("Unable to send message: %s", nl_geterror(err
));
327 static int handle_data(struct nl_cache_ops
*unused _U_
, struct genl_cmd
*cmd _U_
,
328 struct genl_info
*info
, void *arg _U_
)
333 uint8_t packet
[21] = { 0x00 };
335 if (!info
->attrs
[DPAUXMON_ATTR_DATA
])
338 data
= (unsigned char*)nla_data(info
->attrs
[DPAUXMON_ATTR_DATA
]);
339 data_size
= nla_len(info
->attrs
[DPAUXMON_ATTR_DATA
]);
341 if (data_size
> 19) {
342 ws_debug("Invalid packet size %u", data_size
);
346 if (info
->attrs
[DPAUXMON_ATTR_TIMESTAMP
])
347 ts
= nla_get_msecs(info
->attrs
[DPAUXMON_ATTR_TIMESTAMP
]);
349 packet
[1] = info
->attrs
[DPAUXMON_ATTR_FROM_SOURCE
] ? 0x01 : 0x00;
351 memcpy(&packet
[2], data
, data_size
);
353 if (dump_packet(pcap_fp
, packet
, data_size
+ 2, ts
) == EXIT_FAILURE
)
354 extcap_end_application
= true;
359 static int parse_cb(struct nl_msg
*msg
, void *arg _U_
)
361 return genl_handle_msg(msg
, NULL
);
364 static struct genl_cmd cmds
[] = {
367 .c_id
= DPAUXMON_CMD_START
,
368 .c_name
= "dpauxmon start",
369 .c_maxattr
= DPAUXMON_ATTR_MAX
,
370 .c_attr_policy
= dpauxmon_attr_policy
,
371 .c_msg_parser
= &handle_start
,
374 .c_id
= DPAUXMON_CMD_STOP
,
375 .c_name
= "dpauxmon stop",
376 .c_maxattr
= DPAUXMON_ATTR_MAX
,
377 .c_attr_policy
= dpauxmon_attr_policy
,
378 .c_msg_parser
= &handle_stop
,
382 .c_id
= DPAUXMON_CMD_DATA
,
383 .c_name
= "dpauxmon data",
384 .c_maxattr
= DPAUXMON_ATTR_MAX
,
385 .c_attr_policy
= dpauxmon_attr_policy
,
386 .c_msg_parser
= &handle_data
,
390 #define ARRAY_SIZE(X) (sizeof(X) / sizeof((X)[0]))
392 static struct genl_ops ops
= {
393 .o_name
= "dpauxmon",
395 .o_ncmds
= ARRAY_SIZE(cmds
),
398 struct nl_sock
*sock
;
400 static void run_listener(const char* fifo
, unsigned int interface_id
)
404 struct nl_cb
*socket_cb
;
406 if (setup_dumpfile(fifo
, &pcap_fp
) == EXIT_FAILURE
) {
411 if (!(sock
= nl_socket_alloc())) {
412 ws_critical("Unable to allocate netlink socket");
416 if ((err
= nl_connect(sock
, NETLINK_GENERIC
)) < 0) {
417 ws_critical("Unable to connect netlink socket: %s",
422 if ((err
= genl_register_family(&ops
)) < 0) {
423 ws_critical("Unable to register Generic Netlink family: %s",
428 if ((err
= genl_ops_resolve(sock
, &ops
)) < 0) {
429 ws_critical("Unable to resolve family name: %s",
434 /* register notification handler callback */
435 if ((err
= nl_socket_modify_cb(sock
, NL_CB_VALID
, NL_CB_CUSTOM
,
436 parse_cb
, NULL
)) < 0) {
437 ws_critical("Unable to modify valid message callback %s",
442 grp
= nl_get_multicast_id(sock
, ops
.o_id
, "notify");
443 nl_socket_add_membership(sock
, grp
);
445 if (!(socket_cb
= nl_socket_get_cb(sock
))) {
446 ws_warning("Can't overwrite recv callback");
448 nl_cb_overwrite_recv(socket_cb
, nl_receive_timeout
);
449 nl_cb_put(socket_cb
);
452 err
= send_start(sock
, ops
.o_id
, interface_id
);
456 nl_socket_disable_seq_check(sock
);
458 ws_debug("DisplayPort AUX monitor running on interface %u", interface_id
);
460 while(!extcap_end_application
) {
461 if ((err
= nl_recvmsgs_default(sock
)) < 0)
462 ws_warning("Unable to receive message: %s", nl_geterror(err
));
465 send_stop(sock
, ops
.o_id
, interface_id
);
470 nl_socket_free(sock
);
475 int main(int argc
, char *argv
[])
477 char* configuration_init_error
;
480 unsigned int interface_id
= 0;
481 int ret
= EXIT_FAILURE
;
482 extcap_parameters
* extcap_conf
= g_new0(extcap_parameters
, 1);
483 char* help_header
= NULL
;
485 /* Initialize log handler early so we can have proper logging during startup. */
486 extcap_log_init("dpauxmon");
489 * Get credential information for later use.
491 init_process_policies();
494 * Attempt to get the pathname of the directory containing the
497 configuration_init_error
= configuration_init(argv
[0], NULL
);
498 if (configuration_init_error
!= NULL
) {
499 ws_warning("Can't get pathname of directory containing the extcap program: %s.",
500 configuration_init_error
);
501 g_free(configuration_init_error
);
504 extcap_base_set_util_info(extcap_conf
, argv
[0], DPAUXMON_VERSION_MAJOR
, DPAUXMON_VERSION_MINOR
, DPAUXMON_VERSION_RELEASE
,
506 extcap_base_register_interface(extcap_conf
, DPAUXMON_EXTCAP_INTERFACE
, "DisplayPort AUX channel monitor capture", 275, "DisplayPort AUX channel monitor");
508 help_header
= ws_strdup_printf(
509 " %s --extcap-interfaces\n"
510 " %s --extcap-interface=%s --extcap-dlts\n"
511 " %s --extcap-interface=%s --extcap-config\n"
512 " %s --extcap-interface=%s --interface_id 0 --fifo myfifo --capture",
513 argv
[0], argv
[0], DPAUXMON_EXTCAP_INTERFACE
, argv
[0], DPAUXMON_EXTCAP_INTERFACE
, argv
[0], DPAUXMON_EXTCAP_INTERFACE
);
514 extcap_help_add_header(extcap_conf
, help_header
);
516 extcap_help_add_option(extcap_conf
, "--help", "print this help");
517 extcap_help_add_option(extcap_conf
, "--version", "print the version");
518 extcap_help_add_option(extcap_conf
, "--port <port> ", "the dpauxmon interface index");
524 extcap_help_print(extcap_conf
);
528 while ((result
= ws_getopt_long(argc
, argv
, ":", longopts
, &option_idx
)) != -1) {
532 extcap_help_print(extcap_conf
);
537 extcap_version_print(extcap_conf
);
540 case OPT_INTERFACE_ID
:
541 if (!ws_strtou32(ws_optarg
, NULL
, &interface_id
)) {
542 ws_warning("Invalid interface id: %s", ws_optarg
);
548 /* missing option argument */
549 ws_warning("Option '%s' requires an argument", argv
[ws_optind
- 1]);
553 if (!extcap_base_parse_options(extcap_conf
, result
- EXTCAP_OPT_LIST_INTERFACES
, ws_optarg
)) {
554 ws_warning("Invalid option: %s", argv
[ws_optind
- 1]);
560 extcap_cmdline_debug(argv
, argc
);
562 if (ws_optind
!= argc
) {
563 ws_warning("Unexpected extra option: %s", argv
[ws_optind
]);
567 if (extcap_base_handle_interface(extcap_conf
)) {
572 if (!extcap_base_register_graceful_shutdown_cb(extcap_conf
, NULL
)) {
577 if (extcap_conf
->show_config
) {
578 ret
= list_config(extcap_conf
->interface
);
582 if (extcap_conf
->capture
)
583 run_listener(extcap_conf
->fifo
, interface_id
);
587 extcap_base_cleanup(&extcap_conf
);