Project

General

Profile

Bug #4995 ยป packet.c

reproducer program - laforge, 01/30/2021 10:33 AM

 
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
}
    (1-1/1)
    Add picture from clipboard (Maximum size: 48.8 MB)