1
|
/* Copyright (c) 2020 by sysmocom - s.f.m.c. GmbH
|
2
|
* Author: Harald Welte <hwelte@sysmocom.de>
|
3
|
* SPDX-Identifier: MIT */
|
4
|
|
5
|
/* Demo program to show the incredibly unusual semantics of AF_PACKET sockets
|
6
|
*
|
7
|
* - they return ENOBUFS in blocking mode, rather than blocking
|
8
|
* - they are marked 'writeable' in select, but still return ENOBUFS when you actually want to write
|
9
|
*/
|
10
|
|
11
|
#include <errno.h>
|
12
|
#include <stdint.h>
|
13
|
#include <inttypes.h>
|
14
|
#include <unistd.h>
|
15
|
#include <stdlib.h>
|
16
|
#include <string.h>
|
17
|
#include <stdio.h>
|
18
|
#include <stdbool.h>
|
19
|
#include <sys/types.h>
|
20
|
#include <sys/ioctl.h>
|
21
|
|
22
|
#include <netpacket/packet.h>
|
23
|
#include <netinet/in.h>
|
24
|
|
25
|
#include <linux/if_ether.h>
|
26
|
#include <linux/if.h>
|
27
|
|
28
|
#include <time.h>
|
29
|
#include <bsd/sys/time.h>
|
30
|
|
31
|
static int devname2ifindex(const char *ifname)
|
32
|
{
|
33
|
struct ifreq ifr;
|
34
|
int sk, rc;
|
35
|
|
36
|
sk = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
37
|
if (sk < 0)
|
38
|
return sk;
|
39
|
|
40
|
|
41
|
memset(&ifr, 0, sizeof(ifr));
|
42
|
strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
|
43
|
ifr.ifr_name[sizeof(ifr.ifr_name)-1] = 0;
|
44
|
|
45
|
rc = ioctl(sk, SIOCGIFINDEX, &ifr);
|
46
|
close(sk);
|
47
|
if (rc < 0)
|
48
|
return rc;
|
49
|
|
50
|
return ifr.ifr_ifindex;
|
51
|
}
|
52
|
|
53
|
static int open_socket(int ifindex)
|
54
|
{
|
55
|
struct sockaddr_ll addr;
|
56
|
int fd, rc;
|
57
|
|
58
|
memset(&addr, 0, sizeof(addr));
|
59
|
addr.sll_family = AF_PACKET;
|
60
|
addr.sll_protocol = htons(ETH_P_ALL);
|
61
|
addr.sll_ifindex = ifindex;
|
62
|
|
63
|
fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
|
64
|
if (fd < 0)
|
65
|
return fd;
|
66
|
|
67
|
rc = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
|
68
|
if (rc < 0) {
|
69
|
close(fd);
|
70
|
return rc;
|
71
|
}
|
72
|
|
73
|
return fd;
|
74
|
}
|
75
|
|
76
|
|
77
|
static int open_socket_devname(const char *netdev_name)
|
78
|
{
|
79
|
/* resolve ifindex; open the socket; register filedescriptor */
|
80
|
int ifindex = devname2ifindex(netdev_name);
|
81
|
if (ifindex < 0) {
|
82
|
fprintf(stderr, "Cannot resolve interface index of netdev `%s': Does it exist?", netdev_name);
|
83
|
exit(1);
|
84
|
}
|
85
|
|
86
|
int fd = open_socket(ifindex);
|
87
|
if (fd < 0) {
|
88
|
fprintf(stderr, "Cannot create/bind AF_PACKET socket: %s\n", strerror(errno));
|
89
|
exit(1);
|
90
|
}
|
91
|
return fd;
|
92
|
}
|
93
|
|
94
|
|
95
|
|
96
|
static int write_flood(int fd, int num_cycles, int size, bool use_select)
|
97
|
{
|
98
|
int i = 0, ret = 0;
|
99
|
char buf[2048];
|
100
|
struct timespec ts_write, ts_write_fail;
|
101
|
bool recovering = false;
|
102
|
fd_set writefds;
|
103
|
|
104
|
memset(buf, 0, sizeof(buf));
|
105
|
|
106
|
FD_ZERO(&writefds);
|
107
|
|
108
|
while (i < num_cycles) {
|
109
|
int rc;
|
110
|
|
111
|
/* optionally: let's wait until the socket is marked write-able.
|
112
|
* Covnentional wisdom would mean this happens when we can actually write.
|
113
|
* Unfortunately, in reality this is completely useless as it always returns writable */
|
114
|
if (use_select) {
|
115
|
FD_SET(fd, &writefds);
|
116
|
rc = select(fd+1, NULL, &writefds, NULL, NULL);
|
117
|
if (!FD_ISSET(fd, &writefds)) {
|
118
|
fprintf(stderr, "select() returned with %d, but fd not set!\n", rc);
|
119
|
continue;
|
120
|
}
|
121
|
}
|
122
|
|
123
|
/* Then actually write to it */
|
124
|
clock_gettime(CLOCK_MONOTONIC, &ts_write);
|
125
|
rc = write(fd, buf, size);
|
126
|
if (rc == size) {
|
127
|
if (recovering) {
|
128
|
struct timespec diff;
|
129
|
timespecsub(&ts_write, &ts_write_fail, &diff);
|
130
|
/* PRINT time between last write */
|
131
|
printf("%d: time since last success = %lu s, %lu us\n", i,
|
132
|
diff.tv_sec, diff.tv_nsec/1000);
|
133
|
recovering = false;
|
134
|
}
|
135
|
i++;
|
136
|
} else if (rc == -1 && errno == ENOBUFS) {
|
137
|
ret++;
|
138
|
//fprintf(stderr, "write #%d failed with %d (%s)\n", i, rc, strerror(errno));
|
139
|
if (!recovering) {
|
140
|
recovering = true;
|
141
|
ts_write_fail = ts_write;
|
142
|
}
|
143
|
/* HACK: this is the only work-around I could find: sleep some time and retry:
|
144
|
* sleep about as much time as is needed by the underlying net-device to transmit
|
145
|
* one 1400 byete packet */
|
146
|
//usleep(5400);
|
147
|
} else {
|
148
|
fprintf(stderr, "write #%d failed with %d (%s)\n", i, rc, strerror(errno));
|
149
|
exit(1);
|
150
|
}
|
151
|
}
|
152
|
|
153
|
return ret;
|
154
|
}
|
155
|
|
156
|
|
157
|
int main(int argc, char **argv)
|
158
|
{
|
159
|
int fd = open_socket_devname("hdlc1");
|
160
|
int num_err;
|
161
|
|
162
|
/* write 1000 packets of 1400 bytes each; last paramter determines if a select()
|
163
|
* should be done ahead of each write() or not. */
|
164
|
num_err = write_flood(fd, 1000, 1400, true);
|
165
|
|
166
|
printf("total number of ENOBUFS: %d\n", num_err);
|
167
|
}
|