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/array.h>
21 #include <wsutil/strtoi.h>
22 #include <wsutil/filesystem.h>
23 #include <wsutil/privileges.h>
24 #include <wsutil/wslog.h>
25 #include <writecap/pcapio.h>
27 #include <netlink/netlink.h>
28 #include <netlink/genl/genl.h>
29 #include <netlink/genl/ctrl.h>
30 #include <netlink/genl/mngt.h>
34 #include <linux/genetlink.h>
36 #include "dpauxmon_user.h"
38 #define PCAP_SNAPLEN 128
40 #define DPAUXMON_EXTCAP_INTERFACE "dpauxmon"
41 #define DPAUXMON_VERSION_MAJOR "0"
42 #define DPAUXMON_VERSION_MINOR "1"
43 #define DPAUXMON_VERSION_RELEASE "0"
48 EXTCAP_BASE_OPTIONS_ENUM
,
54 static const struct ws_option longopts
[] = {
56 /* Generic application options */
57 { "help", ws_no_argument
, NULL
, OPT_HELP
},
58 { "version", ws_no_argument
, NULL
, OPT_VERSION
},
59 /* Interfaces options */
60 { "interface_id", ws_required_argument
, NULL
, OPT_INTERFACE_ID
},
64 static struct nla_policy dpauxmon_attr_policy
[DPAUXMON_ATTR_MAX
+ 1] = {
65 [DPAUXMON_ATTR_IFINDEX
] = { .type
= NLA_U32
},
66 [DPAUXMON_ATTR_FROM_SOURCE
] = { .type
= NLA_FLAG
},
67 [DPAUXMON_ATTR_TIMESTAMP
] = { .type
= NLA_MSECS
},
70 struct family_handler_args
{
75 static int list_config(char *interface
)
80 ws_warning("No interface specified.");
84 if (g_strcmp0(interface
, DPAUXMON_EXTCAP_INTERFACE
)) {
85 ws_warning("interface must be %s", DPAUXMON_EXTCAP_INTERFACE
);
89 printf("arg {number=%u}{call=--interface_id}{display=Interface index}"
90 "{type=unsigned}{range=1,65535}{default=%u}{tooltip=The dpauxmon interface index}\n",
93 extcap_config_debug(&inc
);
98 static int setup_dumpfile(const char* fifo
, FILE** fp
)
100 uint64_t bytes_written
= 0;
103 if (!g_strcmp0(fifo
, "-")) {
108 *fp
= fopen(fifo
, "wb");
110 ws_warning("Error creating output file: %s", g_strerror(errno
));
114 if (!libpcap_write_file_header(*fp
, 275, PCAP_SNAPLEN
, false, &bytes_written
, &err
)) {
115 ws_warning("Can't write pcap file header");
124 static int dump_packet(FILE* fp
, const char* buf
, const uint32_t buflen
, uint64_t ts_usecs
)
126 uint64_t bytes_written
= 0;
128 int ret
= EXIT_SUCCESS
;
130 if (!libpcap_write_packet(fp
, ts_usecs
/ 1000000, ts_usecs
% 1000000, buflen
, buflen
, buf
, &bytes_written
, &err
)) {
131 ws_warning("Can't write packet");
140 static int error_handler(struct sockaddr_nl
*nla _U_
, struct nlmsgerr
*err
,
143 int *ret
= (int*)arg
;
148 static int ack_handler(struct nl_msg
*msg _U_
, void *arg
)
150 int *ret
= (int*)arg
;
155 static int family_handler(struct nl_msg
*msg
, void *arg
)
157 struct family_handler_args
*grp
= (struct family_handler_args
*)arg
;
158 struct nlattr
*tb
[CTRL_ATTR_MAX
+ 1];
159 struct genlmsghdr
*gnlh
= (struct genlmsghdr
*)nlmsg_data(nlmsg_hdr(msg
));
160 struct nlattr
*mcgrp
;
163 nla_parse(tb
, CTRL_ATTR_MAX
, genlmsg_attrdata(gnlh
, 0),
164 genlmsg_attrlen(gnlh
, 0), NULL
);
166 if (!tb
[CTRL_ATTR_MCAST_GROUPS
])
169 nla_for_each_nested(mcgrp
, tb
[CTRL_ATTR_MCAST_GROUPS
], rem_mcgrp
) {
170 struct nlattr
*tb_mcgrp
[CTRL_ATTR_MCAST_GRP_MAX
+ 1];
172 nla_parse(tb_mcgrp
, CTRL_ATTR_MCAST_GRP_MAX
,
173 (struct nlattr
*)nla_data(mcgrp
), nla_len(mcgrp
), NULL
);
175 if (!tb_mcgrp
[CTRL_ATTR_MCAST_GRP_NAME
] ||
176 !tb_mcgrp
[CTRL_ATTR_MCAST_GRP_ID
])
179 if (strncmp((const char*)nla_data(tb_mcgrp
[CTRL_ATTR_MCAST_GRP_NAME
]),
181 nla_len(tb_mcgrp
[CTRL_ATTR_MCAST_GRP_NAME
])))
184 grp
->id
= nla_get_u32(tb_mcgrp
[CTRL_ATTR_MCAST_GRP_ID
]);
192 static int nl_get_multicast_id(struct nl_sock
*sock
, int family
,
198 struct family_handler_args grp
= {
207 cb
= nl_cb_alloc(NL_CB_DEFAULT
);
213 ctrlid
= genl_ctrl_resolve(sock
, "nlctrl");
215 genlmsg_put(msg
, 0, 0, ctrlid
, 0, 0, CTRL_CMD_GETFAMILY
, 0);
218 NLA_PUT_U16(msg
, CTRL_ATTR_FAMILY_ID
, family
);
220 ret
= nl_send_auto_complete(sock
, msg
);
222 goto nla_put_failure
;
226 nl_cb_err(cb
, NL_CB_CUSTOM
, error_handler
, &ret
);
227 nl_cb_set(cb
, NL_CB_ACK
, NL_CB_CUSTOM
, ack_handler
, &ret
);
228 nl_cb_set(cb
, NL_CB_VALID
, NL_CB_CUSTOM
, family_handler
, &grp
);
231 nl_recvmsgs(sock
, cb
);
243 * netlink callback handlers
246 static int nl_receive_timeout(struct nl_sock
* sk
, struct sockaddr_nl
* nla
, unsigned char** buf
, struct ucred
** creds
)
248 struct pollfd fds
= {nl_socket_get_fd(sk
), POLLIN
, 0};
249 int poll_res
= poll(&fds
, 1, 500);
252 ws_debug("poll() failed in nl_receive_timeout");
254 return -nl_syserr2nlerr(errno
);
257 return poll_res
? nl_recv(sk
, nla
, buf
, creds
) : 0;
260 static int send_start(struct nl_sock
*sock
, int family
, unsigned int interface_id
)
269 ws_critical("Unable to allocate netlink message");
273 hdr
= genlmsg_put(msg
, NL_AUTO_PID
, NL_AUTO_SEQ
, family
, 0, 0,
274 DPAUXMON_CMD_START
, 1);
276 ws_critical("Unable to write genl header");
281 if ((err
= nla_put_u32(msg
, DPAUXMON_ATTR_IFINDEX
, interface_id
)) < 0) {
282 ws_critical("Unable to add attribute: %s", nl_geterror(err
));
287 if ((err
= nl_send_auto_complete(sock
, msg
)) < 0)
288 ws_debug("Starting monitor failed, already running? :%s", nl_geterror(err
));
295 static void send_stop(struct nl_sock
*sock
, int family
, unsigned int interface_id
)
303 ws_critical("Unable to allocate netlink message");
307 hdr
= genlmsg_put(msg
, NL_AUTO_PID
, NL_AUTO_SEQ
, family
, 0, 0,
308 DPAUXMON_CMD_STOP
, 1);
310 ws_critical("Unable to write genl header");
314 if ((err
= nla_put_u32(msg
, DPAUXMON_ATTR_IFINDEX
, interface_id
)) < 0) {
315 ws_critical("Unable to add attribute: %s", nl_geterror(err
));
319 if ((err
= nl_send_auto_complete(sock
, msg
)) < 0) {
320 ws_critical("Unable to send message: %s", nl_geterror(err
));
328 static int handle_data(struct nl_cache_ops
*unused _U_
, struct genl_cmd
*cmd _U_
,
329 struct genl_info
*info
, void *arg _U_
)
334 uint8_t packet
[21] = { 0x00 };
336 if (!info
->attrs
[DPAUXMON_ATTR_DATA
])
339 data
= (unsigned char*)nla_data(info
->attrs
[DPAUXMON_ATTR_DATA
]);
340 data_size
= nla_len(info
->attrs
[DPAUXMON_ATTR_DATA
]);
342 if (data_size
> 19) {
343 ws_debug("Invalid packet size %u", data_size
);
347 if (info
->attrs
[DPAUXMON_ATTR_TIMESTAMP
])
348 ts
= nla_get_msecs(info
->attrs
[DPAUXMON_ATTR_TIMESTAMP
]);
350 packet
[1] = info
->attrs
[DPAUXMON_ATTR_FROM_SOURCE
] ? 0x01 : 0x00;
352 memcpy(&packet
[2], data
, data_size
);
354 if (dump_packet(pcap_fp
, packet
, data_size
+ 2, ts
) == EXIT_FAILURE
)
355 extcap_end_application
= true;
360 static int parse_cb(struct nl_msg
*msg
, void *arg _U_
)
362 return genl_handle_msg(msg
, NULL
);
365 static struct genl_cmd cmds
[] = {
368 .c_id
= DPAUXMON_CMD_START
,
369 .c_name
= "dpauxmon start",
370 .c_maxattr
= DPAUXMON_ATTR_MAX
,
371 .c_attr_policy
= dpauxmon_attr_policy
,
372 .c_msg_parser
= &handle_start
,
375 .c_id
= DPAUXMON_CMD_STOP
,
376 .c_name
= "dpauxmon stop",
377 .c_maxattr
= DPAUXMON_ATTR_MAX
,
378 .c_attr_policy
= dpauxmon_attr_policy
,
379 .c_msg_parser
= &handle_stop
,
383 .c_id
= DPAUXMON_CMD_DATA
,
384 .c_name
= "dpauxmon data",
385 .c_maxattr
= DPAUXMON_ATTR_MAX
,
386 .c_attr_policy
= dpauxmon_attr_policy
,
387 .c_msg_parser
= &handle_data
,
391 static struct genl_ops ops
= {
392 .o_name
= "dpauxmon",
394 .o_ncmds
= array_length(cmds
),
397 struct nl_sock
*sock
;
399 static void run_listener(const char* fifo
, unsigned int interface_id
)
403 struct nl_cb
*socket_cb
;
405 if (setup_dumpfile(fifo
, &pcap_fp
) == EXIT_FAILURE
) {
410 if (!(sock
= nl_socket_alloc())) {
411 ws_critical("Unable to allocate netlink socket");
415 if ((err
= nl_connect(sock
, NETLINK_GENERIC
)) < 0) {
416 ws_critical("Unable to connect netlink socket: %s",
421 if ((err
= genl_register_family(&ops
)) < 0) {
422 ws_critical("Unable to register Generic Netlink family: %s",
427 if ((err
= genl_ops_resolve(sock
, &ops
)) < 0) {
428 ws_critical("Unable to resolve family name: %s",
433 /* register notification handler callback */
434 if ((err
= nl_socket_modify_cb(sock
, NL_CB_VALID
, NL_CB_CUSTOM
,
435 parse_cb
, NULL
)) < 0) {
436 ws_critical("Unable to modify valid message callback %s",
441 grp
= nl_get_multicast_id(sock
, ops
.o_id
, "notify");
442 nl_socket_add_membership(sock
, grp
);
444 if (!(socket_cb
= nl_socket_get_cb(sock
))) {
445 ws_warning("Can't overwrite recv callback");
447 nl_cb_overwrite_recv(socket_cb
, nl_receive_timeout
);
448 nl_cb_put(socket_cb
);
451 err
= send_start(sock
, ops
.o_id
, interface_id
);
455 nl_socket_disable_seq_check(sock
);
457 ws_debug("DisplayPort AUX monitor running on interface %u", interface_id
);
459 while(!extcap_end_application
) {
460 if ((err
= nl_recvmsgs_default(sock
)) < 0)
461 ws_warning("Unable to receive message: %s", nl_geterror(err
));
464 send_stop(sock
, ops
.o_id
, interface_id
);
469 nl_socket_free(sock
);
474 int main(int argc
, char *argv
[])
476 char* configuration_init_error
;
479 unsigned int interface_id
= 0;
480 int ret
= EXIT_FAILURE
;
481 extcap_parameters
* extcap_conf
= g_new0(extcap_parameters
, 1);
482 char* help_header
= NULL
;
484 /* Initialize log handler early so we can have proper logging during startup. */
485 extcap_log_init("dpauxmon");
488 * Get credential information for later use.
490 init_process_policies();
493 * Attempt to get the pathname of the directory containing the
496 configuration_init_error
= configuration_init(argv
[0], NULL
);
497 if (configuration_init_error
!= NULL
) {
498 ws_warning("Can't get pathname of directory containing the extcap program: %s.",
499 configuration_init_error
);
500 g_free(configuration_init_error
);
503 extcap_base_set_util_info(extcap_conf
, argv
[0], DPAUXMON_VERSION_MAJOR
, DPAUXMON_VERSION_MINOR
, DPAUXMON_VERSION_RELEASE
,
505 extcap_base_register_interface(extcap_conf
, DPAUXMON_EXTCAP_INTERFACE
, "DisplayPort AUX channel monitor capture", 275, "DisplayPort AUX channel monitor");
507 help_header
= ws_strdup_printf(
508 " %s --extcap-interfaces\n"
509 " %s --extcap-interface=%s --extcap-dlts\n"
510 " %s --extcap-interface=%s --extcap-config\n"
511 " %s --extcap-interface=%s --interface_id 0 --fifo myfifo --capture",
512 argv
[0], argv
[0], DPAUXMON_EXTCAP_INTERFACE
, argv
[0], DPAUXMON_EXTCAP_INTERFACE
, argv
[0], DPAUXMON_EXTCAP_INTERFACE
);
513 extcap_help_add_header(extcap_conf
, help_header
);
515 extcap_help_add_option(extcap_conf
, "--help", "print this help");
516 extcap_help_add_option(extcap_conf
, "--version", "print the version");
517 extcap_help_add_option(extcap_conf
, "--port <port> ", "the dpauxmon interface index");
523 extcap_help_print(extcap_conf
);
527 while ((result
= ws_getopt_long(argc
, argv
, ":", longopts
, &option_idx
)) != -1) {
531 extcap_help_print(extcap_conf
);
536 extcap_version_print(extcap_conf
);
539 case OPT_INTERFACE_ID
:
540 if (!ws_strtou32(ws_optarg
, NULL
, &interface_id
)) {
541 ws_warning("Invalid interface id: %s", ws_optarg
);
547 /* missing option argument */
548 ws_warning("Option '%s' requires an argument", argv
[ws_optind
- 1]);
552 if (!extcap_base_parse_options(extcap_conf
, result
- EXTCAP_OPT_LIST_INTERFACES
, ws_optarg
)) {
553 ws_warning("Invalid option: %s", argv
[ws_optind
- 1]);
559 extcap_cmdline_debug(argv
, argc
);
561 if (ws_optind
!= argc
) {
562 ws_warning("Unexpected extra option: %s", argv
[ws_optind
]);
566 if (extcap_base_handle_interface(extcap_conf
)) {
571 if (!extcap_base_register_graceful_shutdown_cb(extcap_conf
, NULL
)) {
576 if (extcap_conf
->show_config
) {
577 ret
= list_config(extcap_conf
->interface
);
581 if (extcap_conf
->capture
)
582 run_listener(extcap_conf
->fifo
, interface_id
);
586 extcap_base_cleanup(&extcap_conf
);