1 // SPDX-License-Identifier: GPL-2.0
2
3 /* Usage: ./udpclash <IP> <PORT>
4 *
5 * Emit THREAD_COUNT UDP packets sharing the same saddr:daddr pair.
6 *
7 * This mimics DNS resolver libraries that emit A and AAAA requests
8 * in parallel.
9 *
10 * This exercises conntrack clash resolution logic added and later
11 * refined in
12 *
13 * 71d8c47fc653 ("netfilter: conntrack: introduce clash resolution on insertion race")
14 * ed07d9a021df ("netfilter: nf_conntrack: resolve clash for matching conntracks")
15 * 6a757c07e51f ("netfilter: conntrack: allow insertion of clashing entries")
16 */
17 #include <stdio.h>
18 #include <string.h>
19 #include <stdlib.h>
20 #include <unistd.h>
21 #include <arpa/inet.h>
22 #include <sys/socket.h>
23 #include <pthread.h>
24
25 #define THREAD_COUNT 128
26
27 struct thread_args {
28 const struct sockaddr_in *si_remote;
29 int sockfd;
30 };
31
32 static int wait = 1;
33
thread_main(void * varg)34 static void *thread_main(void *varg)
35 {
36 const struct sockaddr_in *si_remote;
37 const struct thread_args *args = varg;
38 static const char msg[] = "foo";
39
40 si_remote = args->si_remote;
41
42 while (wait == 1)
43 ;
44
45 if (sendto(args->sockfd, msg, strlen(msg), MSG_NOSIGNAL,
46 (struct sockaddr *)si_remote, sizeof(*si_remote)) < 0)
47 exit(111);
48
49 return varg;
50 }
51
run_test(int fd,const struct sockaddr_in * si_remote)52 static int run_test(int fd, const struct sockaddr_in *si_remote)
53 {
54 struct thread_args thread_args = {
55 .si_remote = si_remote,
56 .sockfd = fd,
57 };
58 pthread_t *tid = calloc(THREAD_COUNT, sizeof(pthread_t));
59 unsigned int repl_count = 0, timeout = 0;
60 int i;
61
62 if (!tid) {
63 perror("calloc");
64 return 1;
65 }
66
67 for (i = 0; i < THREAD_COUNT; i++) {
68 int err = pthread_create(&tid[i], NULL, &thread_main, &thread_args);
69
70 if (err != 0) {
71 perror("pthread_create");
72 exit(1);
73 }
74 }
75
76 wait = 0;
77
78 for (i = 0; i < THREAD_COUNT; i++)
79 pthread_join(tid[i], NULL);
80
81 while (repl_count < THREAD_COUNT) {
82 struct sockaddr_in si_repl;
83 socklen_t si_repl_len = sizeof(si_repl);
84 char repl[512];
85 ssize_t ret;
86
87 ret = recvfrom(fd, repl, sizeof(repl), MSG_NOSIGNAL,
88 (struct sockaddr *) &si_repl, &si_repl_len);
89 if (ret < 0) {
90 if (timeout++ > 5000) {
91 fputs("timed out while waiting for reply from thread\n", stderr);
92 break;
93 }
94
95 /* give reply time to pass though the stack */
96 usleep(1000);
97 continue;
98 }
99
100 if (si_repl_len != sizeof(*si_remote)) {
101 fprintf(stderr, "warning: reply has unexpected repl_len %d vs %d\n",
102 (int)si_repl_len, (int)sizeof(si_repl));
103 } else if (si_remote->sin_addr.s_addr != si_repl.sin_addr.s_addr ||
104 si_remote->sin_port != si_repl.sin_port) {
105 char a[64], b[64];
106
107 inet_ntop(AF_INET, &si_remote->sin_addr, a, sizeof(a));
108 inet_ntop(AF_INET, &si_repl.sin_addr, b, sizeof(b));
109
110 fprintf(stderr, "reply from wrong source: want %s:%d got %s:%d\n",
111 a, ntohs(si_remote->sin_port), b, ntohs(si_repl.sin_port));
112 }
113
114 repl_count++;
115 }
116
117 printf("got %d of %d replies\n", repl_count, THREAD_COUNT);
118
119 free(tid);
120
121 return repl_count == THREAD_COUNT ? 0 : 1;
122 }
123
main(int argc,char * argv[])124 int main(int argc, char *argv[])
125 {
126 struct sockaddr_in si_local = {
127 .sin_family = AF_INET,
128 };
129 struct sockaddr_in si_remote = {
130 .sin_family = AF_INET,
131 };
132 int fd, ret;
133
134 if (argc < 3) {
135 fputs("Usage: send_udp <daddr> <dport>\n", stderr);
136 return 1;
137 }
138
139 si_remote.sin_port = htons(atoi(argv[2]));
140 si_remote.sin_addr.s_addr = inet_addr(argv[1]);
141
142 fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_UDP);
143 if (fd < 0) {
144 perror("socket");
145 return 1;
146 }
147
148 if (bind(fd, (struct sockaddr *)&si_local, sizeof(si_local)) < 0) {
149 perror("bind");
150 return 1;
151 }
152
153 ret = run_test(fd, &si_remote);
154
155 close(fd);
156
157 return ret;
158 }
159