]> git.meshlink.io Git - meshlink/blob - test/pmtu.c
Don't send UDP probes to tiny nodes.
[meshlink] / test / pmtu.c
1 #ifdef NDEBUG
2 #undef NDEBUG
3 #endif
4
5 #include <assert.h>
6 #include <stdio.h>
7 #include <unistd.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <time.h>
11 #include <sys/types.h>
12 #include <sys/stat.h>
13 #include <fcntl.h>
14
15 #include "meshlink.h"
16 #include "devtools.h"
17 #include "utils.h"
18
19 #define nnodes 3
20
21 static const struct info {
22         const char *name;
23         const char *confdir;
24         const char *netns;
25         dev_class_t devclass;
26 } nodes[nnodes] = {
27         {"relay", "pmtu_conf.1", "/run/netns/pmtu_r", DEV_CLASS_BACKBONE},
28         {"peer", "pmtu_conf.2", "/run/netns/pmtu_p", DEV_CLASS_STATIONARY},
29         {"nut", "pmtu_conf.3", "/run/netns/pmtu_n", DEV_CLASS_STATIONARY},
30 };
31
32 static struct state {
33         meshlink_handle_t *mesh;
34         int netns;
35         struct sync_flag up_flag;
36         int pmtu;
37         int probe_count;
38         int probe_bytes;
39 } states[nnodes];
40
41 static void relay_up_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) {
42         struct state *state = mesh->priv;
43
44         // Check that we are connected to another peer besides the relay
45         if(reachable && node != meshlink_get_self(mesh) && strcmp(node->name, "relay")) {
46                 set_sync_flag(&state->up_flag, true);
47                 meshlink_set_node_status_cb(mesh, NULL);
48         }
49 }
50
51 static void receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len) {
52         if(!data && !len) {
53                 meshlink_channel_close(mesh, channel);
54         }
55 }
56
57 static bool accept_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint16_t port, const void *data, size_t len) {
58         (void)port;
59         (void)data;
60         (void)len;
61         meshlink_set_channel_receive_cb(mesh, channel, receive_cb);
62         return true;
63 }
64
65 static void wait_for_pmtu(void) {
66         // Set up a channel from peer to nut
67         meshlink_set_channel_accept_cb(states[2].mesh, accept_cb);
68         meshlink_handle_t *mesh = states[1].mesh;
69         meshlink_node_t *peer = meshlink_get_node(mesh, nodes[2].name);
70         meshlink_channel_t *channel = meshlink_channel_open(mesh, peer, 1, NULL, NULL, 0);
71         assert(channel);
72
73         // While sending regular data, wait for PMTU discovery to finish
74         for(int i = 0; i < 30; i++) {
75                 sleep(1);
76
77                 devtool_node_status_t status;
78                 devtool_get_node_status(mesh, peer, &status);
79                 states[1].pmtu = status.minmtu;
80
81                 if(status.minmtu == status.maxmtu) {
82                         break;
83                 }
84
85                 assert(meshlink_channel_send(mesh, channel, "ping", 4) == 4);
86         }
87
88         meshlink_channel_close(mesh, channel);
89 }
90
91 static void wait_for_udp_timeout(void) {
92         // Set up a channel from peer to nut
93         meshlink_set_channel_accept_cb(states[2].mesh, accept_cb);
94         meshlink_handle_t *mesh = states[1].mesh;
95         meshlink_node_t *peer = meshlink_get_node(mesh, nodes[2].name);
96         meshlink_channel_t *channel = meshlink_channel_open(mesh, peer, 1, NULL, NULL, 0);
97         assert(channel);
98
99         // While sending regular data, wait for UDP to time out
100         for(int i = 0; i < 20; i++) {
101                 sleep(1);
102
103                 devtool_node_status_t status;
104                 devtool_get_node_status(mesh, peer, &status);
105                 states[1].pmtu = status.minmtu;
106
107                 if(!status.minmtu) {
108                         break;
109                 }
110
111                 assert(meshlink_channel_send(mesh, channel, "ping", 4) == 4);
112         }
113
114         meshlink_channel_close(mesh, channel);
115 }
116
117 static void start_peer_nut(void) {
118         // Start peer and nut
119         for(int i = 1; i < nnodes; i++) {
120                 meshlink_set_node_status_cb(states[i].mesh, relay_up_cb);
121                 assert(meshlink_start(states[i].mesh));
122         }
123
124         // Wait for the peer and nut to see each other
125         for(int i = 1; i < nnodes; i++) {
126                 assert(wait_sync_flag(&states[i].up_flag, 5));
127         }
128 }
129
130 static void stop_peer_nut(void) {
131         // Stop peer and nut, reset counters
132         for(int i = 1; i < nnodes; i++) {
133                 meshlink_stop(states[i].mesh);
134                 states[i].up_flag.flag = false;
135                 states[i].pmtu = 0;
136                 states[i].probe_count = 0;
137                 states[i].probe_bytes = 0;
138         }
139 }
140
141 int main(void) {
142         // This test requires root access due to the use of network namespaces
143         if(getuid() != 0) {
144                 return 77;
145         }
146
147         // Set up namespaces
148         assert(system("./pmtu-setup") == 0);
149
150         // Bring up the nodes
151         for(int i = 0; i < nnodes; i++) {
152                 assert(meshlink_destroy(nodes[i].confdir));
153
154                 // Open the network namespace
155                 states[i].netns = open(nodes[i].netns, O_RDONLY);
156                 assert(states[i].netns != -1);
157
158                 // Open the MeshLink instance
159                 meshlink_open_params_t *params;
160                 assert(params = meshlink_open_params_init(nodes[i].confdir, nodes[i].name, "pmtu", nodes[i].devclass));
161                 assert(meshlink_open_params_set_netns(params, states[i].netns));
162                 assert(states[i].mesh = meshlink_open_ex(params));
163                 free(params);
164
165                 states[i].mesh->priv = &states[i];
166                 meshlink_enable_discovery(states[i].mesh, false);
167                 init_sync_flag(&states[i].up_flag);
168
169                 meshlink_set_log_cb(states[i].mesh, MESHLINK_INFO, log_cb);
170
171                 // Link the relay node to the other nodes
172                 if(i == 0) {
173                         assert(meshlink_set_canonical_address(states[i].mesh, meshlink_get_self(states[i].mesh), "203.0.113.1", NULL));
174                 } else {
175                         assert(meshlink_set_canonical_address(states[i].mesh, meshlink_get_self(states[i].mesh), "localhost", NULL));
176
177                         char *data = meshlink_export(states[0].mesh);
178                         assert(data);
179                         assert(meshlink_import(states[i].mesh, data));
180                         free(data);
181
182                         data = meshlink_export(states[i].mesh);
183                         assert(data);
184                         assert(meshlink_import(states[0].mesh, data));
185                         free(data);
186                 }
187
188         }
189
190         // Start the relay
191         assert(meshlink_start(states[0].mesh));
192
193         // Start peers and wait for them to connect
194         start_peer_nut();
195
196         // Wait for PMTU discovery to finish
197         wait_for_pmtu();
198
199         assert(states[1].pmtu >= 1400 && states[1].pmtu <= 1500);
200         assert(states[1].probe_count <= 10);
201         assert(states[1].probe_bytes <= 1500 * 10);
202
203         // Drop the MTU to 800
204         stop_peer_nut();
205
206         assert(system("ip netns exec pmtu_p ip link set eth0 mtu 800") == 0);
207         assert(system("ip netns exec pmtu_n ip link set eth0 mtu 800") == 0);
208
209         // Workaround for autoconnect algorithm throttling reconnects
210         sleep(15);
211
212         start_peer_nut();
213         wait_for_pmtu();
214
215         assert(states[1].pmtu >= 700 && states[1].pmtu <= 800);
216         assert(states[1].probe_count <= 20);
217         assert(states[1].probe_bytes <= 800 * 20);
218
219         // Block UDP
220
221         assert(system("ip netns exec pmtu_p iptables -A INPUT -p udp -j DROP") == 0);
222         assert(system("ip netns exec pmtu_n iptables -A INPUT -p udp -j DROP") == 0);
223
224         // Wait
225
226         wait_for_udp_timeout();
227         assert(states[1].pmtu == 0);
228
229         // Cleanup
230         for(int i = 0; i < nnodes; i++) {
231                 meshlink_close(states[i].mesh);
232         }
233 }