-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathifchange.c
315 lines (286 loc) · 9.75 KB
/
ifchange.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
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
// Copyright 2004-2018 Nicholas J. Kain <njkain at gmail dot com>
// SPDX-License-Identifier: MIT
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <errno.h>
#include <limits.h>
#include "nk/log.h"
#include "nk/io.h"
#include "options.h"
#include "ndhc.h"
#include "dhcp.h"
#include "options.h"
#include "arp.h"
#include "ifchange.h"
static struct dhcpmsg cfg_packet; // Copy of the current configuration packet.
static int ifcmd_raw(char *buf, size_t buflen, const char *optname,
char *optdata, size_t optlen)
{
if (!optdata) {
log_line("%s: (%s) '%s' option has no data\n",
client_config.interface, __func__, optname);
return -1;
}
if (optlen > INT_MAX) {
log_line("%s: (%s) '%s' option optlen out of bounds\n",
client_config.interface, __func__, optname);
return -1;
}
if (buflen < strlen(optname) + optlen + 3) {
log_line("%s: (%s) '%s' option buf too short\n",
client_config.interface, __func__, optname);
return -1;
}
int ioptlen = (int)optlen;
ssize_t olen = snprintf(buf, buflen, "%s:%.*s;", optname, ioptlen, optdata);
if (olen < 0 || (size_t)olen > buflen) {
log_line("%s: (%s) '%s' option would truncate, so it was dropped.\n",
client_config.interface, __func__, optname);
memset(buf, 0, buflen);
return -1;
}
return olen;
}
static int ifcmd_bytes(char *buf, size_t buflen, const char *optname,
uint8_t *optdata, size_t optlen)
{
return ifcmd_raw(buf, buflen, optname, (char *)optdata, optlen);
}
static int ifcmd_u8(char *buf, size_t buflen, const char *optname,
uint8_t *optdata, size_t optlen)
{
if (!optdata || optlen < 1)
return -1;
char numbuf[16];
uint8_t c = optdata[0];
ssize_t olen = snprintf(numbuf, sizeof numbuf, "%c", c);
if (olen < 0 || (size_t)olen > sizeof numbuf)
return -1;
return ifcmd_raw(buf, buflen, optname, numbuf, strlen(numbuf));
}
static int ifcmd_u16(char *buf, size_t buflen, const char *optname,
uint8_t *optdata, size_t optlen)
{
if (!optdata || optlen < 2)
return -1;
char numbuf[16];
uint16_t v;
memcpy(&v, optdata, 2);
v = ntohs(v);
ssize_t olen = snprintf(numbuf, sizeof numbuf, "%hu", v);
if (olen < 0 || (size_t)olen > sizeof numbuf)
return -1;
return ifcmd_raw(buf, buflen, optname, numbuf, strlen(numbuf));
}
static int ifcmd_s32(char *buf, size_t buflen, const char *optname,
uint8_t *optdata, size_t optlen)
{
if (!optdata || optlen < 4)
return -1;
char numbuf[16];
uint32_t v;
memcpy(&v, optdata, 4);
v = ntohl(v);
ssize_t olen = snprintf(numbuf, sizeof numbuf, "%d", v);
if (olen < 0 || (size_t)olen > sizeof numbuf)
return -1;
return ifcmd_raw(buf, buflen, optname, numbuf, strlen(numbuf));
}
static int ifcmd_ip(char *buf, size_t buflen, const char *optname,
uint8_t *optdata, size_t optlen)
{
if (!optdata || optlen < 4)
return -1;
char ipbuf[INET_ADDRSTRLEN];
inet_ntop(AF_INET, optdata, ipbuf, sizeof ipbuf);
return ifcmd_raw(buf, buflen, optname, ipbuf, strlen(ipbuf));
}
static int ifcmd_iplist(char *out, size_t outlen, const char *optname,
uint8_t *optdata, size_t optlen)
{
char buf[2048];
char ipbuf[INET_ADDRSTRLEN];
size_t bufoff = 0;
size_t optoff = 0;
if (!optdata || optlen < 4)
return -1;
inet_ntop(AF_INET, optdata + optoff, ipbuf, sizeof ipbuf);
ssize_t wc = snprintf(buf + bufoff, sizeof buf, "%s", ipbuf);
if (wc < 0 || (size_t)wc > sizeof buf)
return -1;
optoff += 4;
bufoff += (size_t)wc;
while (optlen >= 4 + optoff) {
inet_ntop(AF_INET, optdata + optoff, ipbuf, sizeof ipbuf);
wc = snprintf(buf + bufoff, sizeof buf, ",%s", ipbuf);
if (wc < 0 || (size_t)wc > sizeof buf)
return -1;
optoff += 4;
bufoff += (size_t)wc;
}
return ifcmd_raw(out, outlen, optname, buf, strlen(buf));
}
static int ifchd_cmd(char *b, size_t bl, uint8_t *od,
size_t ol, uint8_t code)
{
switch (code) {
case DCODE_ROUTER: return ifcmd_ip(b, bl, "routr", od, ol);
case DCODE_DNS: return ifcmd_iplist(b, bl, "dns", od, ol);
case DCODE_LPRSVR: return ifcmd_iplist(b, bl, "lpr", od, ol);
case DCODE_NTPSVR: return ifcmd_iplist(b, bl, "ntp", od, ol);
case DCODE_WINS: return ifcmd_iplist(b, bl, "wins", od, ol);
case DCODE_HOSTNAME: return ifcmd_bytes(b, bl, "host", od, ol);
case DCODE_DOMAIN: return ifcmd_bytes(b, bl, "dom", od, ol);
case DCODE_TIMEZONE: return ifcmd_s32(b, bl, "tzone", od, ol);
case DCODE_MTU: return ifcmd_u16(b, bl, "mtu", od, ol);
case DCODE_IPTTL: return ifcmd_u8(b, bl, "ipttl", od, ol);
default: break;
}
log_line("%s: Invalid option code (%c) for ifchd cmd.\n",
client_config.interface, code);
return -1;
}
static int ifchwrite(const char *buf, size_t count)
{
ssize_t r = safe_write(ifchSock[0], buf, count);
if (r < 0 || (size_t)r != count) {
log_line("%s: (%s) write failed: %zd\n", client_config.interface, __func__, r);
return -1;
}
char data[256], control[256];
struct iovec iov = {
.iov_base = data,
.iov_len = sizeof data - 1,
};
struct msghdr msg = {
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = control,
.msg_controllen = sizeof control
};
r = safe_recvmsg(ifchSock[0], &msg, 0);
if (r == 0) {
// Remote end hung up.
exit(EXIT_SUCCESS);
} else if (r < 0) {
suicide("%s: (%s) recvmsg failed: %s\n", client_config.interface,
__func__, strerror(errno));
}
data[iov.iov_len] = '\0';
if (r == 1 && data[0] == '+')
return 0;
return -1;
}
bool ifchange_carrier_isup(void)
{
const char buf[] = "carrier:;";
return ifchwrite(buf, strlen(buf)) == 0;
}
int ifchange_deconfig(struct client_state_t *cs)
{
const char buf[] = "ip4:0.0.0.0,255.255.255.255;";
int ret = -1;
if (cs->ifDeconfig)
return 0;
log_line("%s: Resetting IP configuration.\n", client_config.interface);
ret = ifchwrite(buf, strlen(buf));
if (ret >= 0) {
cs->ifDeconfig = 1;
memset(&cfg_packet, 0, sizeof cfg_packet);
}
return ret;
}
static size_t send_client_ip(char *out, size_t olen,
struct dhcpmsg *packet)
{
char ip[INET_ADDRSTRLEN], sn[INET_ADDRSTRLEN], bc[INET_ADDRSTRLEN];
bool change_ipaddr = false;
bool have_subnet = false;
bool change_subnet = false;
bool have_bcast = false;
bool change_bcast = false;
if (memcmp(&packet->yiaddr, &cfg_packet.yiaddr, sizeof packet->yiaddr))
change_ipaddr = true;
inet_ntop(AF_INET, &packet->yiaddr, ip, sizeof ip);
int found;
uint32_t s32n = get_option_subnet_mask(packet, &found);
if (found) {
have_subnet = true;
inet_ntop(AF_INET, &s32n, sn, sizeof sn);
uint32_t s32o = get_option_subnet_mask(&cfg_packet, &found);
if (!found || s32n != s32o)
change_subnet = true;
}
uint32_t b32n = get_option_broadcast(packet, &found);
if (found) {
have_bcast = true;
inet_ntop(AF_INET, &b32n, bc, sizeof bc);
uint32_t b32o = get_option_broadcast(&cfg_packet, &found);
if (!found || b32n != b32o)
change_bcast = true;
}
// Nothing to change.
if (!change_ipaddr && !change_subnet && !change_bcast)
return 0;
if (!have_subnet) {
static char snClassC[] = "255.255.255.0";
log_line("%s: Server did not send a subnet mask. Assuming 255.255.255.0.\n",
client_config.interface);
memcpy(sn, snClassC, sizeof snClassC);
}
int snlen;
if (have_bcast) {
snlen = snprintf(out, olen, "ip4:%s,%s,%s;", ip, sn, bc);
} else {
snlen = snprintf(out, olen, "ip4:%s,%s;", ip, sn);
}
if (snlen < 0 || (size_t)snlen > olen) {
log_line("%s: (%s) ip4 command would truncate so it was dropped.\n",
client_config.interface, __func__);
memset(out, 0, olen);
return 0;
}
return (size_t)snlen;
}
static size_t send_cmd(char *out, size_t olen,
struct dhcpmsg *packet, uint8_t code)
{
uint8_t optdata[MAX_DOPT_SIZE], olddata[MAX_DOPT_SIZE];
size_t optlen, oldlen;
optlen = get_dhcp_opt(packet, code, optdata, sizeof optdata);
if (!optlen)
return 0;
oldlen = get_dhcp_opt(&cfg_packet, code, olddata, sizeof olddata);
if (oldlen == optlen && !memcmp(optdata, olddata, optlen))
return 0;
int r = ifchd_cmd(out, olen, optdata, optlen, code);
return r > 0 ? (size_t)r : 0;
}
int ifchange_bind(struct client_state_t *cs, struct dhcpmsg *packet)
{
char buf[2048];
size_t bo;
int ret = -1;
memset(buf, 0, sizeof buf);
bo = send_client_ip(buf, sizeof buf, packet);
bo += send_cmd(buf + bo, sizeof buf - bo, packet, DCODE_ROUTER);
bo += send_cmd(buf + bo, sizeof buf - bo, packet, DCODE_DNS);
bo += send_cmd(buf + bo, sizeof buf - bo, packet, DCODE_HOSTNAME);
bo += send_cmd(buf + bo, sizeof buf - bo, packet, DCODE_DOMAIN);
bo += send_cmd(buf + bo, sizeof buf - bo, packet, DCODE_MTU);
bo += send_cmd(buf + bo, sizeof buf - bo, packet, DCODE_WINS);
if (bo) {
log_line("%s: bind command: '%s'\n", client_config.interface, buf);
ret = ifchwrite(buf, bo);
}
if (ret >= 0) {
cs->ifDeconfig = 0;
memcpy(&cfg_packet, packet, sizeof cfg_packet);
}
return ret;
}