1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
|
Joining IP multicast fails when emulating 64-bit Linux
Description of problem:
I have some code that joins IP multicast groups and I'd like to use QEMU to test it on big-endian and/or 32-bit platforms. But when I compile it for 64-bit big-endian platforms (e.g. PowerPC64) and run it under QEMU user-mode emulation, the setsockopt(IP_ADD_MEMBERSHIP) call fails with ENODEV.
This appears to refer to the imr_ifindex ("interface index") field in struct ip_mreqn not being valid, which in turn appears to be because it's not being correctly marshalled from the binary under emulation, to the host's *actual* setsockopt system call.
I *think* this may be because linux-user/syscall_defs.h (https://github.com/qemu/qemu/blob/master/linux-user/syscall_defs.h) contains the following at line 210:
```
struct target_ip_mreqn {
struct target_in_addr imr_multiaddr;
struct target_in_addr imr_address;
abi_long imr_ifindex;
};
```
but the actual Linux ip_mreqn has imr_ifindex as an int (32-bit everywhere) not a long (64-bit on PPC64); the size of this structure is 12 on all Linux platforms.
I opted to submit an issue instead of just patching it, in case there was some wider context I hadn't seen?
Steps to reproduce:
1. take the following C program (distilled from a larger program):
```
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0) {
perror("socket");
return 1;
}
struct ip_mreqn mreq;
mreq.imr_multiaddr.s_addr = inet_addr("239.255.255.250");
mreq.imr_address.s_addr = htonl(INADDR_ANY);
mreq.imr_ifindex = 1;
int size = sizeof(mreq);
printf("size=%u\n", size);
if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
(char*) &mreq, sizeof(mreq)) < 0) {
perror("setsockopt");
return 1;
}
printf("OK\n");
return 0;
}
```
2. confirm it works compiled native on amd64/x86_64:
```
[peter@amd64 misc]$ gcc mcast.c -o mcast
[peter@amd64 misc]$ ./mcast
size=12
OK
```
3. watch it *not* work emulated:
```
[peter@amd64 misc]$ powerpc64-linux-gnu-gcc mcast.c -o mcast.ppc64
[peter@amd64 misc]$ QEMU_LD_PREFIX=/usr/powerpc64-linux-gnu qemu-ppc64 ./mcast.ppc64
size=12
setsockopt: No such device
```
Additional information:
If the target_ip_mreqn issue is real, the following code in syscall.c helped conceal it:
if (optlen < sizeof (struct target_ip_mreq) ||
optlen > sizeof (struct target_ip_mreqn)) {
return -TARGET_EINVAL;
}
Should this instead be testing for size equal to target_ip_mreq or equal to target_ip_mreqn, not anywhere in between? in this case target_ip_mreq is 8 bytes, target_ip_mreqn is 16 bytes, but optlen is 12. The end result is that QEMU passes 4 bytes of uninitialised stack memory as imr_ifindex!
The actual kernel behaviour appears to be: smaller than ip_mreq, EINVAL; between ip_mreq and ip_mreqn, silently treat as ip_mreq; larger or equal to ip_mreqn, silently treat as ip_mreqn. see https://github.com/torvalds/linux/blob/b31c4492884252a8360f312a0ac2049349ddf603/net/ipv4/ip_sockglue.c#L1234
|