Faking an input device for testing purpose - c

What I want to do
I'm writing a daemon which listen to the input devices for keys presses and send signals via D-Bus. The main goal is to manage audio volume and screen backlight level by requesting changes or informing about changes.
I use libevdev to handle the input device events.
I wrote a function for opening an input device located at a specified path:
Device device_open(const char *path);
That function works well, but while I'm writing unit tests for it, I wanted to create file fixtures with different properties (existence of the file, read access, etc.) to check the error handling of my function and memory management (as I store data in a structure).
What I have already done
But testing it with a real input device (located at /dev/input/event*) needs root access rights. Setting read access for everyone on /dev/input/event* files works but seems risky to me. Executing my tests as root is worse !
Creating a device using mknod works but needs to be done as root.
I also tried to use character special files (because input devices are one of those) allowing read for everyone (like /dev/random, /dev/zero, /dev/null and even the terminal device i'm currently using: /dev/tty2).
But those devices does not handles ioctl requests needed by libevdev: EVIOCGBIT is the first request returning an error "Inappropriate ioctl for device".
What I'm looking for
I want to be able to create device files as a regular user (the user executing the unit tests). Then, by setting access rights I should be able to test my function behavior for different kinds of file (read only, no read allowed, bad device type, etc.).
If it appears to be impossible, I will certainly refactor my function using private helpers. But how to do it. Any examples ?
Thanks.
Edit: I tried to express better my needs.

Create a group for users who are allowed to access the device, and an udev rule to set the ownership of that input event device to that group.
I use teensy (system) group:
sudo groupadd -r teensy
and add each user into it using e.g.
sudo usermod -a -g teensy my-user-name
or whatever graphical user interface I have available.
By managing which users and service daemons belong to the teensy group, you can easily manage the access to the devices.
For my Teensy microcontrollers (that have native USB, and I use for HID testing), I have the following /lib/udev/rules.d/49-teensy.rules:
ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789B]?", ENV{ID_MM_DEVICE_IGNORE}="1"
ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789A]?", ENV{MTP_NO_PROBE}="1"
SUBSYSTEMS=="usb", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789ABCD]?", GROUP:="teensy", MODE:="0660"
KERNEL=="ttyACM*", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789B]?", GROUP:="teensy", MODE:="0660"
You only need the third line (SUBSYSTEMS=="usb", one) for HID devices, though. Make sure the idVendor and idProduct match your USB HID device. You can use lsusb to list the currently connected USB devices vendor and product numbers. The matching uses glob patterns, just like file names.
After adding the above, don't forget running sudo udevadm control --reload-rules && sudo udevadm trigger to reload the rules. Next time you plug in your USB HID device, all members of your group (teensy in the above) can access it directly.
Note that by default in most distributions, udev also creates persistent symlinks in /dev/input/by-id/ using the USB device type and serial. In my case, one of my Teensy LC's (serial 4298820) with a combined keyboard-mouse-joystic device provides /dev/input/by-id/usb-Teensyduino_Keyboard_Mouse_Joystick_4298820-event-kbd for the keyboard event device, /dev/input/by-id/usb-Teensyduino_Keyboard_Mouse_Joystick_4298820-if01-event-mouse for the mouse event device, and /dev/input/by-id/usb-Teensyduino_Keyboard_Mouse_Joystick_4298820-if03-event-joystick and /dev/input/by-id/usb-Teensyduino_Keyboard_Mouse_Joystick_4298820-if04-event-joystick for the two joystick interfaces.
(By "persistent", I do not mean these symlinks always exist; I mean that whenever that particular device is plugged in, the symlink of exactly that name exists, and points to the actual Linux input event character device.)
The Linux uinput device can be used to implement a virtual input event device using a simple privileged daemon.
The process to create a new virtual USB input event device goes as follows.
Open /dev/uinput for writing (or reading and writing):
fd = open("/dev/uinput", O_RDWR);
if (fd == -1) {
fprintf(stderr, "Cannot open /dev/uinput: %s.\n", strerror(errno));
exit(EXIT_FAILURE);
}
The above requires superuser privileges. However, immediately after opening the device, you can drop all privileges, and have your daemon/service run as a dedicated user instead.
Use the UI_SET_EVBIT ioctl for each event type allowed.
You will want to allow at least EV_SYN; and EV_KEY for keyboards and mouse buttons, and EV_REL for mouse movement, and so on.
if (ioctl(fd, UI_SET_EVBIT, EV_SYN) == -1 ||
ioctl(fd, UI_SET_EVBIT, EV_KEY) == -1 ||
ioctl(fd, UI_SET_EVBIT, EV_REL) == -1) {
fprintf(stderr, "Uinput event types not allowed: %s.\n", strerror(errno));
close(fd);
exit(EXIT_FAILURE);
}
I personally use a static constant array with the codes, for easier management.
Use the UI_SET_KEYBIT ioctl for each key code the device may emit, and UI_SET_RELBIT ioctl for each relative movement code (mouse code). For example, to allow space, left mouse button, horizontal and vertical mouse movement, and mouse wheel:
if (ioctl(fd, UI_SET_KEYBIT, KEY_SPACE) == -1 ||
ioctl(fd, UI_SET_KEYBIT, BTN_LEFT) == -1 ||
ioctl(fd, UI_SET_RELBIT, REL_X) == -1 ||
ioctl(fd, UI_SET_RELBIT, REL_Y) == -1 ||
ioctl(fd, UI_SET_RELBIT, REL_WHEEL) == -1) {
fprintf(stderr, "Uinput event types not allowed: %s.\n", strerror(errno));
close(fd);
exit(EXIT_FAILURE);
}
Again, static const arrays (one for UI_SET_KEYBIT and one for UI_SET_RELBIT codes) is much easier to maintain.
Define a struct uinput_user_dev, and write it to the device.
If you have name containing the device name string, vendor and product with the USB vendor and product ID numbers, version with a version number (0 is fine), use
struct uinput_user_dev dev;
memset(&dev, 0, sizeof dev);
strncpy(dev.name, name, UINPUT_MAX_NAME_SIZE);
dev.id.bustype = BUS_USB;
dev.id.vendor = vendor;
dev.id.product = product;
dev.id.version = version;
if (write(fd, &dev, sizeof dev) != sizeof dev) {
fprintf(stderr, "Cannot write an uinput device description: %s.\n", strerror(errno));
close(fd);
exit(EXIT_FAILURE);
}
Later kernels have an ioctl to do the same thing (apparently being involved in systemd development causes this kind of drain bamage);
struct uinput_setup dev;
memset(&dev, 0, sizeof dev);
strncpy(dev.name, name, UINPUT_MAX_NAME_SIZE);
dev.id.bustype = BUS_USB;
dev.id.vendor = vendor;
dev.id.product = product;
dev.id.version = version;
if (ioctl(fd, UI_DEV_SETUP, &dev) == -1) {
fprintf(stderr, "Cannot write an uinput device description: %s.\n", strerror(errno));
close(fd);
exit(EXIT_FAILURE);
}
The idea seems to be that instead of using the former, you can try the latter first, and if it fails, do the former instead. You know, because a single interface might some day not be enough. (That's what the documentation and commit say, anyway.)
I might sound a bit cranky, here, but that's just because I do subscribe to both the Unix philosophy and the KISS principle (or minimalist approach), and see such warts completely unnecessary. And too often coming from the same loosely related group of developers. Ahem. No personal insult intended; I just think they are doing poor job.
Create the virtual device, by issuing an UI_DEV_CREATE ioctl:
if (ioctl(fd, UI_DEV_CREATE) == -1) {
fprintf(stderr, "Cannot create the virtual uinput device: %s.\n", strerror(errno));
close(fd);
exit(EXIT_FAILURE);
}
At this point, the kernel will construct the device, provide the corresponding event to the udev daemon, and the udev daemon will construct the device node and symlink(s) according to its configuration. All this will take a bit of time -- a fraction of a second in the real world, but enough that trying to emit events immediately might cause some of them to be lost.
Emit the input events (struct input_event) by writing to the uinput device.
You can write one or more struct input_events at a time, and should never see short writes (unless you try to write a partial event structure). Partial event structures are completely ignored. (See drivers/input/misc/uinput.c:uinput_write() uinput_inject_events() for how the kernel handles such writes.)
Many actions consists of more than one struct input_event. For example, you might move the mouse diagonally (emitting both { .type == EV_REL, .code == REL_X, .value = xdelta } and { .type == EV_REL, .code == REL_Y, .value = ydelta } for that single movement). The synchronization events ({ .type == EV_SYN, .code == 0, .value == 0 }) are used as a sentinel or separator, denoting the end of related events.
Because of this, you'll need to append an { .type == EV_SYN, .code == 0, .value == 0 } input event after each individual action (mouse movement, key press, key release, and so on). Think of it as the equivalent of a newline, for line-buffered input.
For example, the following code moves the mouse diagonally down right by a single pixel.
struct input_event event[3];
memset(event, 0, sizeof event);
event[0].type = EV_REL;
event[0].code = REL_X;
event[0].value = +1; /* Right */
event[1].type = EV_REL;
event[1].code = REL_Y;
event[1].value = +1; /* Down */
event[2].type = EV_SYN;
event[2].code = 0;
event[2].value = 0;
if (write(fd, event, sizeof event) != sizeof event)
fprintf(stderr, "Failed to inject mouse movement event.\n");
The failure case is not fatal; it only means the events were not injected (although I don't see how that could happen in current kernels; better be defensive, just in case). You can simply retry the same again, or ignore the failure (but letting the user know, so they can investigate, if it ever happens). So log it or output a warning, but no need for it to cause the daemon/service to exit.
Destroy the device:
ioctl(fd, UI_DEV_DESTROY);
close(fd);
The device does get automatically destroyed when the last duplicate of the original opened descriptor gets closed, but I recommend doing it explicitly as above.
Putting steps 1-5 in a function, you get something like
#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/uinput.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
static const unsigned int allow_event_type[] = {
EV_KEY,
EV_SYN,
EV_REL,
};
#define ALLOWED_EVENT_TYPES (sizeof allow_event_type / sizeof allow_event_type[0])
static const unsigned int allow_key_code[] = {
KEY_SPACE,
BTN_LEFT,
BTN_MIDDLE,
BTN_RIGHT,
};
#define ALLOWED_KEY_CODES (sizeof allow_key_code / sizeof allow_key_code[0])
static const unsigned int allow_rel_code[] = {
REL_X,
REL_Y,
REL_WHEEL,
};
#define ALLOWED_REL_CODES (sizeof allow_rel_code / sizeof allow_rel_code[0])
static int uinput_open(const char *name, const unsigned int vendor, const unsigned int product, const unsigned int version)
{
struct uinput_user_dev dev;
int fd;
size_t i;
if (!name || strlen(name) < 1 || strlen(name) >= UINPUT_MAX_NAME_SIZE) {
errno = EINVAL;
return -1;
}
fd = open("/dev/uinput", O_RDWR);
if (fd == -1)
return -1;
memset(&dev, 0, sizeof dev);
strncpy(dev.name, name, UINPUT_MAX_NAME_SIZE);
dev.id.bustype = BUS_USB;
dev.id.vendor = vendor;
dev.id.product = product;
dev.id.version = version;
do {
for (i = 0; i < ALLOWED_EVENT_TYPES; i++)
if (ioctl(fd, UI_SET_EVBIT, allow_event_type[i]) == -1)
break;
if (i < ALLOWED_EVENT_TYPES)
break;
for (i = 0; i < ALLOWED_KEY_CODES; i++)
if (ioctl(fd, UI_SET_KEYBIT, allow_key_code[i]) == -1)
break;
if (i < ALLOWED_KEY_CODES)
break;
for (i = 0; i < ALLOWED_REL_CODES; i++)
if (ioctl(fd, UI_SET_RELBIT, allow_rel_code[i]) == -1)
break;
if (i < ALLOWED_REL_CODES)
break;
if (write(fd, &dev, sizeof dev) != sizeof dev)
break;
if (ioctl(fd, UI_DEV_CREATE) == -1)
break;
/* Success. */
return fd;
} while (0);
/* FAILED: */
{
const int saved_errno = errno;
close(fd);
errno = saved_errno;
return -1;
}
}
static void uinput_close(const int fd)
{
ioctl(fd, UI_DEV_DESTROY);
close(fd);
}
which seem to work fine, and requires no libraries (other than the standard C library).
It is important to realize that the Linux input subsystem, including uinput and struct input_event, are binary interfaces to the Linux kernel, and therefore will be kept backwards compatible (except for pressing technical reasons, like security issues or serious conflicts with other parts of the kernel). (The desire to wrap everything under the freedesktop.org or systemd umbrella is not one.)

Related

pcap_set_rfmon return 0 as success but the interface is not set to monitor mode

I'm trying to write a small program which set my network interface to monitor mode using C, the function pcap_set_rfmon returns 0 as success but the interface is still in mange mode. I'm sure my network card supports Monitor mode because i have checked using ng-airmon and iwconfig wlp3s0 mode monitor the wlp3s0 is my network interface's name.
Here's my code:
#include <pcap.h>
main()
{
char error_buffer[PCAP_ERRBUF_SIZE];
pcap_t *handle = pcap_create("wlp3s0", error_buffer);
int result = pcap_set_rfmon(handle, 1);
if (result != 0)
{
printf("failed to set pcap rfmon");
}
}
Since the code output nothing and just returns 0, i don't know what has gone wrong and where to look at, can you guys tell me what i should check or something is missing
To quote the documentation for pcap_set_rfmon():
pcap_set_rfmon() sets whether monitor mode should be set on a capture handle when the handle is activated. ...
I've emphasized part of that - "when the handle is activated". All pcap_set_rfmon() does is set a flag in the pcap_t to indicate that, when the program calls pcap_activate(), the adapter would be put in monitor mode (if pcap_activate() succeeds).
You aren't calling pcap_activate(), so nothing happens.
You will also have to keep the pcap_t open - even a program that does
#include <pcap.h>
main()
{
char error_buffer[PCAP_ERRBUF_SIZE];
pcap_t *handle;
int result;
handle = pcap_create("wlp3s0", error_buffer);
if (handle == NULL)
{
printf("failed to create a handle: %s\n",
error_buffer);
return 2;
}
result = pcap_set_rfmon(handle, 1);
if (result != 0)
{
printf("failed to set pcap rfmon: %s (%s)\n",
pcap_statustostr(result),
pcap_geterr(handle));
return 2;
}
result = pcap_activate(handle);
{
printf("failed to activate handle: %s (%s)\n",
pcap_statustostr(result),
pcap_geterr(handle));
return 2;
}
}
will just let the adapter revert to managed mode when it exits. You will need to add something such as
for (;;)
pause();
at the end of main(), so the program doesn't exit unless you interrupt or terminate it.
(Note: I added more error checking and reporting to the program. This Is A Good Thing, as it means that, if something doesn't work, the program will give a detailed error report, helping you - or whoever you ask for help - try to fix the problem, rather than just silently failing or, if pcap_create() fails, crashing.)

How can I Execute/Call a user-space defined function from Linux kernel space module?

I am developing a Linux module which I want to use to run my C program from kernel mode.
My problem here, in function read() of the module, I need to use a function named eval_keycode(), which is defined in my user space program.
When I try to compile my module, this error occurs :
error: implicit declaration of function ‘eval_keycode’
which is confirming my problem described above.
This is the read() function of my module :
ssize_t exer_read(struct file *pfile, char __user *buffer, size_t length, loff_t *offset) {
struct file *f = pfile->private_data;
enum { MAX_BUF_SIZE = 4096 };
size_t buf_size = 0;
char *buf = NULL;
ssize_t total = 0;
ssize_t rc = 0;
struct input_event *ev;
int yalv;
/* Allocate temporary buffer. */
if (length) {
buf_size = min_t(size_t, MAX_BUF_SIZE, length);
ev = kmalloc(buf_size, GFP_KERNEL);
if (ev == NULL) {
return -ENOMEM;
}
}
/* Read file to buffer in chunks. */
do {
size_t amount = min_t(size_t, length, buf_size);
rc = kernel_read(f, ev, amount, offset);
if (rc > 0) {
/* Have read some data from file. */
if (copy_to_user(buffer, ev, rc) != 0) {
/* Bad user memory! */
rc = -EFAULT;
} else {
/* Update totals. */
total += rc;
buffer += rc;
*offset += rc;
length -= rc;
for (yalv = 0; yalv < (int) (rc / sizeof(struct input_event)); yalv++) {
if (ev[yalv].type == EV_KEY) {
if (ev[yalv].value == 0)
eval_keycode(ev[yalv].code);
}
}
if (rc < amount) {
/* Didn't read the full amount, so terminate early. */
rc = 0;
}
}
}
}
while (rc > 0 && length > 0);
/* Free temporary buffer. */
kfree(buf);
if (total > 0) {
return total;
}
return rc;
}
This is my user space eval_keycode() defined function :
void eval_keycode(int code)
{
static int red_state = 0;
static int green_state = 0;
switch (code) {
case 260:
printf("BTN left pressed\n");
/* figure out red state */
red_state = red_state ? 0 : 1;
change_led_state(LED_PATH "/" red "/brightness", red_state);
break;
case BTN_RIGHT:
printf("BTN right pressed\n");
/* figure out green state */
green_state = green_state ? 0 : 1;
change_led_state(LED_PATH "/" green "/brightness", green_state);
break;
}
}
How can call the eval_keycode function from user space in order to solve this problem ?
Thank you.
You can, but it is a really bad idea. You need to establish a pointer to your user mode function, arrange for the process containing that function to be running (in the kernel) when you invoke it. That is a lot of work, and is fundamentally malware due to the security holes it creates. Additionally, in the mad dash to lock the door to the now empty barn in the wake of spectre et al, new layers of hackery are being deployed in newer CPUs to make this even harder.
A different approach:
In your original query, you are running this driver as a "tee"; that is, you take the input you receive from the device, give a copy to the caller, and call eval_keycode with each input. Eval_keycode doesn't modify the data, and the kernel module discards it afterwards. So Eval_keycode doesn't really need to be a function; or rather, there could be a user function:
void ProcessEvents(int fd) {
struct input_event ev;
while (read(fd, &ev, sizeof ev) == sizeof ev) {
eval_keycode(&ev);
}
}
if you could arrange for all the events to be fed into that fd. With this setup, your problem becomes more plumbing than kernel renovation. The user creates a pipe/socket/fifo/... and passes the write end to your kernel module (yay more ioctl()s). Your kernel module can then carefully use kernel_write() ( or vfs_write if you are stuck in the past ) to make these events available to the user handler. It wants to be careful about where its blocking points are.
You could extend this to work as a transform; that is where your driver transforms the events via a user mode handler; but at that point, you might really consider FUSE a better solution.
There is no traditional (in the way a library works) way to "call" a user space "function".
Your user space code should be running in its' own process (or another user space process), in which you would implement communications (through shared memory, interprocess calls [IPC], device files, interrupts..) where you handle the exchange of data, and act on the data (e.g. calling your eval_keycode function).
You basically want an upcall. You can find some explanation about that here, but it doesn't seem like Linux has an official upcall API.
However, as others have already mentioned, this isn't very good design. Upcalls are useful to servers implemented in the kernel.
If your exer_read() is only called for your own code (on your files for which you're implementing the driver), then perhaps inotify would be a better design.
If your exer_read() can be called for any file (e.g. you want any file write on the machine to change the LED state), then you want your userspace process containing eval_keycode() to poll some character device, and you want your module to write the code to this character device instead of calling eval_keycode().
If, however, change_led_state() is synchronous, and you actually need the read to block until it returns, then you are advised to reconsider your design... but that's a valid use case for upcalls.

Using C to simulate a keystroke in linux with the uinput library

I've been using the following C code to try to simulate keystrokes on a CentOS 6.0 machine:
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#inlcude <linux/input.h>
#include <linux/uinput.h>
#include <sys/time.h>
static int fd = -1;
struct uinput_user_dev uidev;
struct input_event event;
int main()
{
int i;
fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
memset(&uidev, 0, sizeof(uidev));
snrpintf(uidev.name, UINPUT_MAX_NAME_SIZE, "uinput-kbd");
uidev.id.version = 1;
uidev.id.vendor = 0x1;
uidev.id.product = 0x1;
uidev.id.bustype = BUS_USB;
ioctl(fd, UI_SET_EVBIT, EV_KEY);
for(i = 0; i < 256; i++)
{
ioctl(fd, UI_SET_KEYBIT, i);
}
ioctl(fd, UI_SET_EVBIT, EV_SYN);
write(fd, &uidev, sizeof(uidev));
ioctl(fd, UI_DEV_CREATE));
memset(&event, 0, sizeof(event));
gettimeofday(&event.time, NULL);
event.type = EV_KEY;
event.code = KEY_1;
event.value = 1;
write(fd, &event, sizeof(event));
event.type = EV_SYN;
event.code = SYN_REPORT;
event.value = 0;
write(fd, &event, sizeof(event));
memset(&event, 0, sizeof(event));
gettimeofday(&event.time, NULL);
event.type = EV_KEY;
event.code = KEY_1;
event.value = 0;
write(fd, &event, sizeof(event));
event.type = EV_SYN;
event.code = SYN_REPORT;
event.value = 0;
write(fd, &event, sizeof(event));
ioctl(fd, UI_DEV_DESTROY);
close(fd);
return 0;
}
If I'm correct, this code should create a virtual input device on the machine and then press the "1" key on that device. And when I execute the code, it seems to run without any issues (I haven't include the code that checks to make sure the device is being created and the keystrokes are being written, etc, in my example code, because it would have gotten way too long), but I can't see any sign of the actual keystroke.
My impression was that if I run this from a terminal window while logged directly into the machine, I should see a "1" character appear on the terminal window that I'm running it from. And if I log into the machine via ssh and run it that way, the keystroke should register on the machine rather than the ssh session. But I'm not getting anything in either situation.
Am I misunderstanding the purpose of this code? Have I done it wrong? Or is the more that I need to add to properly simulate a keystroke?
Wow, I was having the same issue as you just a few minutes ago, but now I don't have the issue anymore, and according to evtest it seems to work. I don't know what I did that resolved the problem unfortunately.
Here's the steps I used to troubleshoot, hopefully that at least gets you somewhere:
Verify that dmesg reports input device when connected.
Example:
[Fri Jul 22 22:38:55 2022] input: Custom Tourbox TBG_H Driver as /devices/virtual/input/input25
Verify that program can write to /dev/uinput file. Can be done via checking return value of write() call (the example code you listed doesn't).
If you have any issues with the above, double-check permissions. ( I did the hack of running my program as root and then changing the permission on the /dev/uinput file to be world-writable, via:
$ chmod +0666 /dev/uinput
This probably isn't recommended though, it would probably be better to create a group and then add your user to that group )
Verify that evtest recognizes your new driver.
Run:
$ sudo evtest
And you should see your device (probably at the bottom):
/dev/input/event24: Custom Tourbox TBG_H Driver
Verify that evtest recognizes that your device has registered an event handler for the keys you wish to use. In my case, I need to simulate key presses for 'a', so I made sure to verify that key was listed as one of the possible event types:
This is what the example output looked like:
Input driver version is 1.0.1
Input device ID: bus 0x3 vendor 0x483 product 0xbeef version 0x0
Input device name: "Custom Tourbox TBG_H Driver"
Supported events:
Event type 0 (EV_SYN)
Event type 1 (EV_KEY)
Event code 30 (KEY_A)
From there, all I did was press the key and it worked.
Here now I found why this can't work, I spend two days finding the problem. Luckily and finally, I found it in linux kernel 5.60 source example which is in chapter 7.4. Here is the picture .Solution
the document said that we'd better wait for some time so that there is a space for userspace to detect event, because creating a device node in kernel state will spend lots of time compared to sending a event to fd, hopes that the answer is not too late.
Here is the link 7.Uinput-module

How to properly put network interface into promiscuous mode on Linux

So how do you do this properly?
I know how to do it by creating socket, then setting IFF_PROMISC flag using ioctl (as explained in "howto check a network devices status in C?" and elsewhere), but this looks flawed at least in theory.
you read flags via ioctl
you update flags
someone else modified flags
you set updated flags via ioctl
Is there a better way or do I simply worry too much?
Later I found that one should add interface to PACKET_MR_PROMISC via setsockopt (which also does not have a race) like this:
void set_promisc(const char *ifname, bool enable)
{
struct packet_mreq mreq = {0};
int sfd;
int action;
if ((sfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) == -1) {
perror("unable to open socket");
return;
}
mreq.mr_ifindex = if_nametoindex(ifname);
mreq.mr_type = PACKET_MR_PROMISC;
if (mreq.mr_ifindex == 0) {
perror("unable to get interface index");
return;
}
if (enable)
action = PACKET_ADD_MEMBERSHIP;
else
action = PACKET_DROP_MEMBERSHIP;
if (setsockopt(sfd, SOL_PACKET, action, &mreq, sizeof(mreq)) != 0) {
perror("unable to enter promiscouous mode");
return;
}
close(sfd);
}
Unfortunately this has no effect whatsoever on interface, although it should, if I unserstand the doc correctly. Possibly broken since 2001 (tm)?
Comments in pcap source also complain about this.
PACKET_MR_PROMISC turns on promiscuous mode for the device. That will not be reflected in the status shown by ifconfig as it does not modify the state of the global IFF_PROMISC flag on the device. That does not mean it hasn't been done though. This is how the pcap library works now and the fact that wireshark (and a dozen other utilities) can open a device and see packets not addressed to the local system shows that it works.
There is an internal counter on each device that is incremented each time a process uses PACKET_MR_PROMISC, and decremented when that process goes away. That solves the race you originally described.
From the last link you provided:
> IFF_PROMISC is not set,
It's not supposed to be set.
The correct way to put into promiscuous mode the device to which a
PF_PACKET socket is to do a SOL_PACKET/PACKET_ADD_MEMBERSHIP
"setsockopt()" call with PACKET_MR_PROMISC as the argument (see the
"packet(7)" man page), and that's what libpcap is doing.
The old way of directly setting IFF_PROMISC had problems - to quote the
comment at the front of "pcap-linux.c":
[snipped]

Discover multiple USB-IrDA devices, open and connect a socket for each

For any good sockets programmers out there:
Is it possible to enumerate multiple active IrDA devices connected to the PC through USB ports using sockets?
If so, how? This is really my main question. The remainder of the post rounds out the details and describes what I have tried.
I am developing an application on Windows 7, using Microsoft SDK and an ANSI C compiler.
The application design requires it to detect any IrDA devices in range, connect using sockets, and communicate to multiple devices through multiple IrDA dongles (one dongle per device), each dongle is connected to the PC via USB. Note: Using virtual COM ports is to be avoided.
I have successfully used sockets calls to enumerate, create a socket, connect and communicate to a single IrDA device. This works well.
However, I am not sure how to setup the code to successfully enumerate multiple IrDA devices.
So far, I am only able to connect to one device at a time. Even though It is clear that Windows is "Discovering" three IrDA dongles, as seen in the following images - overall scenario (1st image), device manager (2nd image), device properties (3rd image):
Illustration 1: Overall scenario:
Illustration 2: From Device Manager:
Illustration 3: Properties of each 'found' IrDA device
(second two arrows are included to show Port-Hub address of all three devices)
The simple scenario I am using:
(some of the variable names are different from scenario description above, but are consistent within the code)
1st - I place two active IrDA devices sitting in front of two dongles (separated by several feet, so there is no signal ambiguity between the two devices) Using only 2 for now to keep it simple.
2nd - I create 2 IrDA sockets, get two handles using: socket(AF_IRDA, SOCK_STREAM, 0);
3rd - I call:
if ((errorCode = getsockopt(gSocketHandle[0],
SOL_IRLMP,
IRLMP_ENUMDEVICES,
(char*)pDeviceList,
&deviceListLength)) == SOCKET_ERROR)
4th - I then populate the destinIrdaDeviceAddr with the new information in pDeviceList and
memcpy(&destinIrdaDeviceAddr.irdaDeviceID[0], &pDevList->Device[0].irdaDeviceID[0], 4);
5th - call:
connect(gSocketHandle[0],(const struct sockaddr*)&destinIrdaDeviceAddr,(int) sizeof(SOCKADDR_IRDA))
After this call completes, I repeat (step 3 ) using the next socket handle (gSocketHandle1). But instead of getting another enumerated device, I just get the same device. This second call results in the same information in pDeviceList as was in the first device. Stated another way, I am only enumerating a single device.
Here is the code attempting to enumerate two IrDA devices:
// adapted from http://msdn.microsoft.com/en-us/library/windows/desktop/ms691754(v=vs.85).aspx
#include <windows.h>
#include <winsock2.h>
#include <ansi_c.h>
#include <af_irda.h>
#pragma comment(lib, "ws2_32.lib")
#define DEVICE_LIST_LEN 5
#define MAX_RETRIES 10
SOCKET conn1, conn2;
char* iGetLastErrorText(DWORD nErrorCode);
void main()
{
WSADATA wsaData;
// discovery buffer
BYTE DevListBuff[sizeof(DEVICELIST) - sizeof(IRDA_DEVICE_INFO) +
(sizeof(IRDA_DEVICE_INFO) * DEVICE_LIST_LEN)];
int DevListLen = sizeof(DevListBuff);
PDEVICELIST pDevList = (PDEVICELIST) &DevListBuff;
int DevNum, i;
if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0)
{
printf("IR-Client: Unable to load the Winsock library!\n");
getchar();
}
else printf("IR-Client: Winsock library 2.2 loaded!\n");
SOCKADDR_IRDA DstAddrIR = { AF_IRDA, 0, 0, 0, 0, "IrDA:IrCOMM" };
//Create socket handles
if ((conn1 = socket(AF_IRDA, SOCK_STREAM, 0)) == INVALID_SOCKET)
{
// WSAGetLastError
}
if ((conn2 = socket(AF_IRDA, SOCK_STREAM, 0)) == INVALID_SOCKET)
{
// WSAGetLastError
}
/////////////////////////
//enumerate devices - socket conn1
/////////////////////////
// search for the peer device
pDevList->numDevice = 0;
if (getsockopt(conn1, SOL_IRLMP, IRLMP_ENUMDEVICES, (CHAR *) pDevList, &DevListLen)
== SOCKET_ERROR)
{
// WSAGetLastError
}
if (pDevList->numDevice == 0)
{
printf("no devices found\n");
}
// assume first device, we should have a common dialog here
memcpy(&DstAddrIR.irdaDeviceID[0], &pDevList->Device[0].irdaDeviceID[0], 4);
printf("%x\n", &DstAddrIR.irdaDeviceID[0]);
// nothing special for IrCOMM from now on...
if (connect(conn1, (const struct sockaddr *) &DstAddrIR, sizeof(SOCKADDR_IRDA))
== SOCKET_ERROR)
{
// WSAGetLastError
}
/////////////////////////
//enumerate devices - socket conn2
/////////////////////////
// search for the peer device
pDevList->numDevice = 0;
if (getsockopt(conn2, SOL_IRLMP, IRLMP_ENUMDEVICES, (CHAR *) pDevList, &DevListLen) == SOCKET_ERROR)
{
// WSAGetLastError
}
if (pDevList->numDevice == 0)
{
printf("no devices found\n");
}
// assume first device, we should have a common dialog here
memcpy(&DstAddrIR.irdaDeviceID[0], &pDevList->Device[0].irdaDeviceID[0], 4);
printf("%x\n", &DstAddrIR.irdaDeviceID[0]);
// nothing special for IrCOMM from now on...
if (connect(conn2, (const struct sockaddr *) &DstAddrIR, sizeof(SOCKADDR_IRDA))
== SOCKET_ERROR)
{
// WSAGetLastError
}
getchar(); //to view irdaDeviceID
}
char* iGetLastErrorText(DWORD nErrorCode)
{
char* msg;
// Ask Windows to prepare a standard message for a GetLastError() code:
FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, nErrorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&msg, 0, NULL);
// Return the message
if (!msg)
return("Unknown error");
else
return(msg);
}
The following image shows values for ->irdaDeviceID[0]. Both values are the same indicating only one device was enumerated.

Resources