-
Notifications
You must be signed in to change notification settings - Fork 17
/
Copy pathping.c
279 lines (248 loc) · 7.77 KB
/
ping.c
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
/* pingcheck - Check connectivity of interfaces in OpenWRT
*
* Copyright (C) 2015 Bruno Randolf <[email protected]>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "log.h"
#include "main.h"
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <time.h>
#include <unistd.h>
static void ping_uloop_fd_close(struct uloop_fd* ufd)
{
if (ufd != NULL && ufd->fd > 0) {
uloop_fd_delete(ufd);
close(ufd->fd);
ufd->fd = 0;
}
}
/* uloop callback when received something on a ping socket */
static void ping_fd_handler(struct uloop_fd* fd,
__attribute__((unused)) unsigned int events)
{
struct ping_intf* pi = container_of(fd, struct ping_intf, ufd);
if (pi->conf_proto == ICMP) {
int received_fd = icmp_echo_receive(fd->fd);
if (received_fd == -1) {
return;
} else {
if (fd->fd != received_fd) {
struct ping_intf* interface = get_interface_by_fd(received_fd);
if (interface != NULL) {
pi = container_of(&interface->ufd, struct ping_intf, ufd);
} else {
printf("ICMP echo received for different handle that was not found %d\n", received_fd);
return;
}
}
}
} else if (pi->conf_proto == TCP) {
/* with TCP, the handler is called when connect() succeds or fails.
*
* if the connect takes longer than the ping interval, it is timed
* out and assumed failed before we open the next regular connection,
* and this handler is not called. but if the interval is large and
* in other cases, this handler can be called for failed connections,
* and to be sure we need to check if connect was successful or not.
*
* after that we just close the socket, as we don't need to send or
* receive any data */
bool succ = tcp_check_connect(fd->fd);
ping_uloop_fd_close(fd);
// printf("TCP connected %d\n", succ);
if (!succ) {
return;
}
}
// LOG_DBG("Received pong on '%s'", pi->name);
pi->cnt_succ++;
/* calculate round trip time */
struct timespec time_recv;
clock_gettime(CLOCK_MONOTONIC, &time_recv);
pi->last_rtt = timespec_diff_ms(pi->time_sent, time_recv);
if (pi->last_rtt > pi->max_rtt) {
pi->max_rtt = pi->last_rtt;
}
/* online just confirmed: move timeout for offline to later
* and give the next reply an extra window of two times the last RTT */
uloop_timeout_set(&pi->timeout_offline,
pi->conf_timeout * 1000 + pi->last_rtt * 2);
state_change(ONLINE, pi);
}
/* uloop timeout callback when we did not receive a ping reply for a certain
* time */
static void uto_offline_cb(struct uloop_timeout* t)
{
struct ping_intf* pi = container_of(t, struct ping_intf, timeout_offline);
state_change(OFFLINE, pi);
}
/* uloop timeout callback when it's time to send a ping */
static void uto_ping_send_cb(struct uloop_timeout* t)
{
struct ping_intf* pi = container_of(t, struct ping_intf, timeout_send);
ping_send(pi);
/* re-schedule next sending */
uloop_timeout_set(t, pi->conf_interval * 1000);
}
bool ping_init(struct ping_intf* pi)
{
int ret;
if (pi->ufd.fd != 0) {
LOG_ERR("Ping on '%s' already init", pi->name);
return true;
}
if (!pi->conf_ignore_ubus) {
ret = ubus_interface_get_status(pi->name, pi->device, MAX_IFNAME_LEN);
if (ret < 0) {
LOG_INF("Interface '%s' not found or error", pi->name);
pi->state = UNKNOWN;
return false;
} else if (ret == 0) {
LOG_INF("Interface '%s' not up", pi->name);
pi->state = DOWN;
return false;
} else if (ret == 1) {
LOG_INF("Interface '%s' (%s) has no default route but local one",
pi->name, pi->device);
pi->state = UP_WITHOUT_DEFAULT_ROUTE;
} else if (ret == 2) {
pi->state = UP;
}
} else {
pi->state = UP;
}
LOG_INF("Init %s ping on '%s' (%s)", pi->conf_proto == TCP ? "TCP" : "ICMP",
pi->name, pi->device);
/* init ICMP socket. for TCP we open a new socket every time */
if (pi->conf_proto == ICMP) {
ret = icmp_init(pi->device);
if (ret < 0) {
return false;
}
/* add socket handler to uloop */
pi->ufd.fd = ret;
pi->ufd.cb = ping_fd_handler;
ret = uloop_fd_add(&pi->ufd, ULOOP_READ);
if (ret < 0) {
LOG_ERR("Could not add uloop fd %d for '%s'", pi->ufd.fd, pi->name);
return false;
}
}
/* regular sending of ping (start first in 1 sec) */
pi->timeout_send.cb = uto_ping_send_cb;
ret = uloop_timeout_set(&pi->timeout_send, 1000);
if (ret < 0) {
LOG_ERR("Could not add uloop send timeout for '%s'", pi->name);
return false;
}
/* timeout for offline state, if no reply has been received
*
* add 900ms to the timeout to give the last reply a chance to arrive
* before the timeout triggers, in case the timout is a multiple of
* interval. this will later be adjusted to the last RTT
*/
pi->timeout_offline.cb = uto_offline_cb;
ret = uloop_timeout_set(&pi->timeout_offline,
pi->conf_timeout * 1000 + 900);
if (ret < 0) {
LOG_ERR("Could not add uloop offline timeout for '%s'", pi->name);
return false;
}
/* reset counters */
pi->cnt_sent = 0;
pi->cnt_succ = 0;
pi->last_rtt = 0;
pi->max_rtt = 0;
return true;
}
static bool ping_resolve(struct ping_intf* pi)
{
struct addrinfo hints;
struct addrinfo* addr;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_INET;
hints.ai_socktype = pi->conf_proto == ICMP ? SOCK_DGRAM : SOCK_STREAM;
int r = getaddrinfo(pi->conf_hostname, NULL, &hints, &addr);
if (r < 0 || addr == NULL) {
LOG_ERR("Failed to resolve '%s'", pi->conf_hostname);
return false;
}
/* use only first address */
struct sockaddr_in* sa = (struct sockaddr_in*)addr->ai_addr;
printf("Resolved %s to %s\n", pi->conf_hostname,
inet_ntoa((struct in_addr)sa->sin_addr));
pi->conf_host = sa->sin_addr.s_addr;
freeaddrinfo(addr);
return true;
}
/* for "ping" using TCP it's enough to just open a connection and see if the
* connect fails or succeeds. If the host is unavailable or there is no
* connectivity the connect will fail, otherwise it will succeed. This will be
* checked in the uloop socket callback above */
static bool ping_send_tcp(struct ping_intf* pi)
{
if (pi->ufd.fd > 0) {
// LOG_DBG("TCP connection timed out '%s'", pi->name);
ping_uloop_fd_close(&pi->ufd);
}
int ret = tcp_connect(pi->device, pi->conf_host, pi->conf_tcp_port);
if (ret > 0) {
/* add socket handler to uloop.
* when connect() finishes, select indicates writability */
pi->ufd.fd = ret;
pi->ufd.cb = ping_fd_handler;
ret = uloop_fd_add(&pi->ufd, ULOOP_WRITE);
if (ret < 0) {
LOG_ERR("Could not add uloop fd %d for '%s'", pi->ufd.fd, pi->name);
return false;
}
}
return true;
}
bool ping_send(struct ping_intf* pi)
{
bool ret = false;
/* resolve at least every 10th time */
if (pi->conf_host == 0 || pi->state != ONLINE || pi->cnt_sent % 10 == 0) {
if (!ping_resolve(pi)) {
return false;
}
}
/* either send ICMP ping or start TCP connection */
if (pi->conf_proto == ICMP) {
if (pi->ufd.fd <= 0) {
LOG_ERR("ping not init on '%s'", pi->name);
return false;
}
ret = icmp_echo_send(pi->ufd.fd, pi->conf_host, pi->cnt_sent);
} else if (pi->conf_proto == TCP) {
ret = ping_send_tcp(pi);
}
/* common code */
if (ret) {
pi->cnt_sent++;
clock_gettime(CLOCK_MONOTONIC, &pi->time_sent);
} else {
LOG_ERR("Could not send ping on '%s'", pi->name);
}
return ret;
}
void ping_stop(struct ping_intf* pi)
{
uloop_timeout_cancel(&pi->timeout_offline);
uloop_timeout_cancel(&pi->timeout_send);
ping_uloop_fd_close(&pi->ufd);
}