1 |
|
|
/* $OpenBSD: bgpd.c,v 1.185 2016/06/20 20:12:52 benno Exp $ */ |
2 |
|
|
|
3 |
|
|
/* |
4 |
|
|
* Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org> |
5 |
|
|
* |
6 |
|
|
* Permission to use, copy, modify, and distribute this software for any |
7 |
|
|
* purpose with or without fee is hereby granted, provided that the above |
8 |
|
|
* copyright notice and this permission notice appear in all copies. |
9 |
|
|
* |
10 |
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
11 |
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
12 |
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
13 |
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
14 |
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
15 |
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
16 |
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 |
|
|
*/ |
18 |
|
|
|
19 |
|
|
#include <sys/types.h> |
20 |
|
|
#include <sys/socket.h> |
21 |
|
|
#include <sys/wait.h> |
22 |
|
|
#include <netinet/in.h> |
23 |
|
|
#include <arpa/inet.h> |
24 |
|
|
#include <err.h> |
25 |
|
|
#include <errno.h> |
26 |
|
|
#include <fcntl.h> |
27 |
|
|
#include <poll.h> |
28 |
|
|
#include <pwd.h> |
29 |
|
|
#include <signal.h> |
30 |
|
|
#include <stdio.h> |
31 |
|
|
#include <stdlib.h> |
32 |
|
|
#include <string.h> |
33 |
|
|
#include <unistd.h> |
34 |
|
|
|
35 |
|
|
#include "bgpd.h" |
36 |
|
|
#include "mrt.h" |
37 |
|
|
#include "session.h" |
38 |
|
|
|
39 |
|
|
void sighdlr(int); |
40 |
|
|
__dead void usage(void); |
41 |
|
|
int main(int, char *[]); |
42 |
|
|
pid_t start_child(enum bgpd_process, char *, int, int, int); |
43 |
|
|
int check_child(pid_t, const char *); |
44 |
|
|
int send_filterset(struct imsgbuf *, struct filter_set_head *); |
45 |
|
|
int reconfigure(char *, struct bgpd_config *, struct peer **); |
46 |
|
|
int dispatch_imsg(struct imsgbuf *, int, struct bgpd_config *); |
47 |
|
|
int control_setup(struct bgpd_config *); |
48 |
|
|
int imsg_send_sockets(struct imsgbuf *, struct imsgbuf *); |
49 |
|
|
|
50 |
|
|
int rfd = -1; |
51 |
|
|
int cflags; |
52 |
|
|
volatile sig_atomic_t mrtdump; |
53 |
|
|
volatile sig_atomic_t quit; |
54 |
|
|
volatile sig_atomic_t sigchld; |
55 |
|
|
volatile sig_atomic_t reconfig; |
56 |
|
|
pid_t reconfpid; |
57 |
|
|
int reconfpending; |
58 |
|
|
struct imsgbuf *ibuf_se; |
59 |
|
|
struct imsgbuf *ibuf_rde; |
60 |
|
|
struct rib_names ribnames = SIMPLEQ_HEAD_INITIALIZER(ribnames); |
61 |
|
|
char *cname; |
62 |
|
|
char *rcname; |
63 |
|
|
|
64 |
|
|
void |
65 |
|
|
sighdlr(int sig) |
66 |
|
|
{ |
67 |
|
|
switch (sig) { |
68 |
|
|
case SIGTERM: |
69 |
|
|
case SIGINT: |
70 |
|
|
quit = 1; |
71 |
|
|
break; |
72 |
|
|
case SIGCHLD: |
73 |
|
|
sigchld = 1; |
74 |
|
|
break; |
75 |
|
|
case SIGHUP: |
76 |
|
|
reconfig = 1; |
77 |
|
|
break; |
78 |
|
|
case SIGALRM: |
79 |
|
|
case SIGUSR1: |
80 |
|
|
mrtdump = 1; |
81 |
|
|
break; |
82 |
|
|
} |
83 |
|
|
} |
84 |
|
|
|
85 |
|
|
__dead void |
86 |
|
|
usage(void) |
87 |
|
|
{ |
88 |
|
|
extern char *__progname; |
89 |
|
|
|
90 |
|
|
fprintf(stderr, "usage: %s [-cdnv] [-D macro=value] [-f file]\n", |
91 |
|
|
__progname); |
92 |
|
|
exit(1); |
93 |
|
|
} |
94 |
|
|
|
95 |
|
|
#define PFD_PIPE_SESSION 0 |
96 |
|
|
#define PFD_PIPE_ROUTE 1 |
97 |
|
|
#define PFD_SOCK_ROUTE 2 |
98 |
|
|
#define POLL_MAX 3 |
99 |
|
|
#define MAX_TIMEOUT 3600 |
100 |
|
|
|
101 |
|
|
int cmd_opts; |
102 |
|
|
|
103 |
|
|
int |
104 |
|
|
main(int argc, char *argv[]) |
105 |
|
|
{ |
106 |
|
|
struct bgpd_config *conf; |
107 |
|
|
struct peer *peer_l, *p; |
108 |
|
|
struct pollfd pfd[POLL_MAX]; |
109 |
|
|
pid_t io_pid = 0, rde_pid = 0, pid; |
110 |
|
|
char *conffile; |
111 |
|
|
char *saved_argv0; |
112 |
|
|
int debug = 0; |
113 |
|
|
int rflag = 0, sflag = 0; |
114 |
|
|
int ch, timeout; |
115 |
|
|
int pipe_m2s[2]; |
116 |
|
|
int pipe_m2r[2]; |
117 |
|
|
|
118 |
|
|
conffile = CONFFILE; |
119 |
|
|
bgpd_process = PROC_MAIN; |
120 |
|
|
|
121 |
|
|
log_init(1); /* log to stderr until daemonized */ |
122 |
|
|
log_verbose(1); |
123 |
|
|
|
124 |
|
|
saved_argv0 = argv[0]; |
125 |
|
|
if (saved_argv0 == NULL) |
126 |
|
|
saved_argv0 = "bgpd"; |
127 |
|
|
|
128 |
|
|
conf = new_config(); |
129 |
|
|
peer_l = NULL; |
130 |
|
|
|
131 |
|
|
while ((ch = getopt(argc, argv, "cdD:f:nRSv")) != -1) { |
132 |
|
|
switch (ch) { |
133 |
|
|
case 'c': |
134 |
|
|
cmd_opts |= BGPD_OPT_FORCE_DEMOTE; |
135 |
|
|
break; |
136 |
|
|
case 'd': |
137 |
|
|
debug = 1; |
138 |
|
|
break; |
139 |
|
|
case 'D': |
140 |
|
|
if (cmdline_symset(optarg) < 0) |
141 |
|
|
log_warnx("could not parse macro definition %s", |
142 |
|
|
optarg); |
143 |
|
|
break; |
144 |
|
|
case 'f': |
145 |
|
|
conffile = optarg; |
146 |
|
|
break; |
147 |
|
|
case 'n': |
148 |
|
|
cmd_opts |= BGPD_OPT_NOACTION; |
149 |
|
|
break; |
150 |
|
|
case 'v': |
151 |
|
|
if (cmd_opts & BGPD_OPT_VERBOSE) |
152 |
|
|
cmd_opts |= BGPD_OPT_VERBOSE2; |
153 |
|
|
cmd_opts |= BGPD_OPT_VERBOSE; |
154 |
|
|
log_verbose(1); |
155 |
|
|
break; |
156 |
|
|
case 'R': |
157 |
|
|
rflag = 1; |
158 |
|
|
break; |
159 |
|
|
case 'S': |
160 |
|
|
sflag = 1; |
161 |
|
|
break; |
162 |
|
|
default: |
163 |
|
|
usage(); |
164 |
|
|
/* NOTREACHED */ |
165 |
|
|
} |
166 |
|
|
} |
167 |
|
|
|
168 |
|
|
argc -= optind; |
169 |
|
|
argv += optind; |
170 |
|
|
if (argc > 0 || (sflag && rflag)) |
171 |
|
|
usage(); |
172 |
|
|
|
173 |
|
|
if (cmd_opts & BGPD_OPT_NOACTION) { |
174 |
|
|
if (parse_config(conffile, conf, &peer_l)) |
175 |
|
|
exit(1); |
176 |
|
|
|
177 |
|
|
if (cmd_opts & BGPD_OPT_VERBOSE) |
178 |
|
|
print_config(conf, &ribnames, &conf->networks, peer_l, |
179 |
|
|
conf->filters, conf->mrt, &conf->rdomains); |
180 |
|
|
else |
181 |
|
|
fprintf(stderr, "configuration OK\n"); |
182 |
|
|
exit(0); |
183 |
|
|
} |
184 |
|
|
|
185 |
|
|
if (rflag) |
186 |
|
|
rde_main(debug, cmd_opts & BGPD_OPT_VERBOSE); |
187 |
|
|
else if (sflag) |
188 |
|
|
session_main(debug, cmd_opts & BGPD_OPT_VERBOSE); |
189 |
|
|
|
190 |
|
|
if (geteuid()) |
191 |
|
|
errx(1, "need root privileges"); |
192 |
|
|
|
193 |
|
|
if (getpwnam(BGPD_USER) == NULL) |
194 |
|
|
errx(1, "unknown user %s", BGPD_USER); |
195 |
|
|
|
196 |
|
|
log_init(debug); |
197 |
|
|
log_verbose(cmd_opts & BGPD_OPT_VERBOSE); |
198 |
|
|
|
199 |
|
|
if (!debug) |
200 |
|
|
daemon(1, 0); |
201 |
|
|
|
202 |
|
|
log_info("startup"); |
203 |
|
|
|
204 |
|
|
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, |
205 |
|
|
PF_UNSPEC, pipe_m2s) == -1) |
206 |
|
|
fatal("socketpair"); |
207 |
|
|
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, |
208 |
|
|
PF_UNSPEC, pipe_m2r) == -1) |
209 |
|
|
fatal("socketpair"); |
210 |
|
|
|
211 |
|
|
/* fork children */ |
212 |
|
|
rde_pid = start_child(PROC_RDE, saved_argv0, pipe_m2r[1], debug, |
213 |
|
|
cmd_opts & BGPD_OPT_VERBOSE); |
214 |
|
|
io_pid = start_child(PROC_SE, saved_argv0, pipe_m2s[1], debug, |
215 |
|
|
cmd_opts & BGPD_OPT_VERBOSE); |
216 |
|
|
|
217 |
|
|
signal(SIGTERM, sighdlr); |
218 |
|
|
signal(SIGINT, sighdlr); |
219 |
|
|
signal(SIGCHLD, sighdlr); |
220 |
|
|
signal(SIGHUP, sighdlr); |
221 |
|
|
signal(SIGALRM, sighdlr); |
222 |
|
|
signal(SIGUSR1, sighdlr); |
223 |
|
|
signal(SIGPIPE, SIG_IGN); |
224 |
|
|
|
225 |
|
|
if ((ibuf_se = malloc(sizeof(struct imsgbuf))) == NULL || |
226 |
|
|
(ibuf_rde = malloc(sizeof(struct imsgbuf))) == NULL) |
227 |
|
|
fatal(NULL); |
228 |
|
|
imsg_init(ibuf_se, pipe_m2s[0]); |
229 |
|
|
imsg_init(ibuf_rde, pipe_m2r[0]); |
230 |
|
|
mrt_init(ibuf_rde, ibuf_se); |
231 |
|
|
if ((rfd = kr_init()) == -1) |
232 |
|
|
quit = 1; |
233 |
|
|
|
234 |
|
|
/* |
235 |
|
|
* rpath, read config file |
236 |
|
|
* cpath, unlink control socket |
237 |
|
|
* fattr, chmod on control socket |
238 |
|
|
* wpath, needed if we are doing mrt dumps |
239 |
|
|
* proc, for kill() when shutting down |
240 |
|
|
* |
241 |
|
|
* pledge placed here because kr_init() does a setsockopt on the |
242 |
|
|
* routing socket thats not allowed at all. |
243 |
|
|
*/ |
244 |
|
|
#if 0 |
245 |
|
|
/* |
246 |
|
|
* disabled because we do ioctls on /dev/pf and SIOCSIFGATTR |
247 |
|
|
* this needs some redesign of bgpd to be fixed. |
248 |
|
|
*/ |
249 |
|
|
if (pledge("stdio rpath wpath cpath fattr unix route recvfd sendfd " |
250 |
|
|
"proc", NULL) == -1) |
251 |
|
|
fatal("pledge"); |
252 |
|
|
#endif |
253 |
|
|
|
254 |
|
|
if (imsg_send_sockets(ibuf_se, ibuf_rde)) |
255 |
|
|
fatal("could not establish imsg links"); |
256 |
|
|
quit = reconfigure(conffile, conf, &peer_l); |
257 |
|
|
if (pftable_clear_all() != 0) |
258 |
|
|
quit = 1; |
259 |
|
|
|
260 |
|
|
while (quit == 0) { |
261 |
|
|
bzero(pfd, sizeof(pfd)); |
262 |
|
|
|
263 |
|
|
set_pollfd(&pfd[PFD_PIPE_SESSION], ibuf_se); |
264 |
|
|
set_pollfd(&pfd[PFD_PIPE_ROUTE], ibuf_rde); |
265 |
|
|
|
266 |
|
|
pfd[PFD_SOCK_ROUTE].fd = rfd; |
267 |
|
|
pfd[PFD_SOCK_ROUTE].events = POLLIN; |
268 |
|
|
|
269 |
|
|
timeout = mrt_timeout(conf->mrt); |
270 |
|
|
if (timeout > MAX_TIMEOUT) |
271 |
|
|
timeout = MAX_TIMEOUT; |
272 |
|
|
|
273 |
|
|
if (poll(pfd, POLL_MAX, timeout * 1000) == -1) |
274 |
|
|
if (errno != EINTR) { |
275 |
|
|
log_warn("poll error"); |
276 |
|
|
quit = 1; |
277 |
|
|
} |
278 |
|
|
|
279 |
|
|
if (handle_pollfd(&pfd[PFD_PIPE_SESSION], ibuf_se) == -1) { |
280 |
|
|
log_warnx("main: Lost connection to SE"); |
281 |
|
|
msgbuf_clear(&ibuf_se->w); |
282 |
|
|
free(ibuf_se); |
283 |
|
|
ibuf_se = NULL; |
284 |
|
|
quit = 1; |
285 |
|
|
} else { |
286 |
|
|
if (dispatch_imsg(ibuf_se, PFD_PIPE_SESSION, conf) == |
287 |
|
|
-1) |
288 |
|
|
quit = 1; |
289 |
|
|
} |
290 |
|
|
|
291 |
|
|
if (handle_pollfd(&pfd[PFD_PIPE_ROUTE], ibuf_rde) == -1) { |
292 |
|
|
log_warnx("main: Lost connection to RDE"); |
293 |
|
|
msgbuf_clear(&ibuf_rde->w); |
294 |
|
|
free(ibuf_rde); |
295 |
|
|
ibuf_rde = NULL; |
296 |
|
|
quit = 1; |
297 |
|
|
} else { |
298 |
|
|
if (dispatch_imsg(ibuf_rde, PFD_PIPE_ROUTE, conf) == |
299 |
|
|
-1) |
300 |
|
|
quit = 1; |
301 |
|
|
} |
302 |
|
|
|
303 |
|
|
if (pfd[PFD_SOCK_ROUTE].revents & POLLIN) { |
304 |
|
|
if (kr_dispatch_msg() == -1) |
305 |
|
|
quit = 1; |
306 |
|
|
} |
307 |
|
|
|
308 |
|
|
if (reconfig) { |
309 |
|
|
u_int error; |
310 |
|
|
|
311 |
|
|
reconfig = 0; |
312 |
|
|
switch (reconfigure(conffile, conf, &peer_l)) { |
313 |
|
|
case -1: /* fatal error */ |
314 |
|
|
quit = 1; |
315 |
|
|
break; |
316 |
|
|
case 0: /* all OK */ |
317 |
|
|
error = 0; |
318 |
|
|
break; |
319 |
|
|
case 2: |
320 |
|
|
error = CTL_RES_PENDING; |
321 |
|
|
break; |
322 |
|
|
default: /* parse error */ |
323 |
|
|
error = CTL_RES_PARSE_ERROR; |
324 |
|
|
break; |
325 |
|
|
} |
326 |
|
|
if (reconfpid != 0) { |
327 |
|
|
send_imsg_session(IMSG_CTL_RESULT, reconfpid, |
328 |
|
|
&error, sizeof(error)); |
329 |
|
|
reconfpid = 0; |
330 |
|
|
} |
331 |
|
|
} |
332 |
|
|
|
333 |
|
|
if (sigchld) { |
334 |
|
|
sigchld = 0; |
335 |
|
|
if (check_child(io_pid, "session engine")) { |
336 |
|
|
quit = 1; |
337 |
|
|
io_pid = 0; |
338 |
|
|
} |
339 |
|
|
if (check_child(rde_pid, "route decision engine")) { |
340 |
|
|
quit = 1; |
341 |
|
|
rde_pid = 0; |
342 |
|
|
} |
343 |
|
|
} |
344 |
|
|
|
345 |
|
|
if (mrtdump) { |
346 |
|
|
mrtdump = 0; |
347 |
|
|
mrt_handler(conf->mrt); |
348 |
|
|
} |
349 |
|
|
} |
350 |
|
|
|
351 |
|
|
signal(SIGCHLD, SIG_IGN); |
352 |
|
|
|
353 |
|
|
if (io_pid) |
354 |
|
|
kill(io_pid, SIGTERM); |
355 |
|
|
|
356 |
|
|
if (rde_pid) |
357 |
|
|
kill(rde_pid, SIGTERM); |
358 |
|
|
|
359 |
|
|
while ((p = peer_l) != NULL) { |
360 |
|
|
peer_l = p->next; |
361 |
|
|
free(p); |
362 |
|
|
} |
363 |
|
|
|
364 |
|
|
control_cleanup(conf->csock); |
365 |
|
|
control_cleanup(conf->rcsock); |
366 |
|
|
carp_demote_shutdown(); |
367 |
|
|
kr_shutdown(conf->fib_priority); |
368 |
|
|
pftable_clear_all(); |
369 |
|
|
|
370 |
|
|
free_config(conf); |
371 |
|
|
|
372 |
|
|
do { |
373 |
|
|
if ((pid = wait(NULL)) == -1 && |
374 |
|
|
errno != EINTR && errno != ECHILD) |
375 |
|
|
fatal("wait"); |
376 |
|
|
} while (pid != -1 || (pid == -1 && errno == EINTR)); |
377 |
|
|
|
378 |
|
|
msgbuf_clear(&ibuf_se->w); |
379 |
|
|
free(ibuf_se); |
380 |
|
|
msgbuf_clear(&ibuf_rde->w); |
381 |
|
|
free(ibuf_rde); |
382 |
|
|
free(rcname); |
383 |
|
|
free(cname); |
384 |
|
|
|
385 |
|
|
log_info("Terminating"); |
386 |
|
|
return (0); |
387 |
|
|
} |
388 |
|
|
|
389 |
|
|
pid_t |
390 |
|
|
start_child(enum bgpd_process p, char *argv0, int fd, int debug, int verbose) |
391 |
|
|
{ |
392 |
|
|
char *argv[5]; |
393 |
|
|
int argc = 0; |
394 |
|
|
pid_t pid; |
395 |
|
|
|
396 |
|
|
switch (pid = fork()) { |
397 |
|
|
case -1: |
398 |
|
|
fatal("cannot fork"); |
399 |
|
|
case 0: |
400 |
|
|
break; |
401 |
|
|
default: |
402 |
|
|
close(fd); |
403 |
|
|
return (pid); |
404 |
|
|
} |
405 |
|
|
|
406 |
|
|
if (dup2(fd, 3) == -1) |
407 |
|
|
fatal("cannot setup imsg fd"); |
408 |
|
|
|
409 |
|
|
argv[argc++] = argv0; |
410 |
|
|
switch (p) { |
411 |
|
|
case PROC_MAIN: |
412 |
|
|
fatalx("Can not start main process"); |
413 |
|
|
case PROC_RDE: |
414 |
|
|
argv[argc++] = "-R"; |
415 |
|
|
break; |
416 |
|
|
case PROC_SE: |
417 |
|
|
argv[argc++] = "-S"; |
418 |
|
|
break; |
419 |
|
|
} |
420 |
|
|
if (debug) |
421 |
|
|
argv[argc++] = "-d"; |
422 |
|
|
if (verbose) |
423 |
|
|
argv[argc++] = "-v"; |
424 |
|
|
argv[argc++] = NULL; |
425 |
|
|
|
426 |
|
|
execvp(argv0, argv); |
427 |
|
|
fatal("execvp"); |
428 |
|
|
} |
429 |
|
|
|
430 |
|
|
int |
431 |
|
|
check_child(pid_t pid, const char *pname) |
432 |
|
|
{ |
433 |
|
|
int status; |
434 |
|
|
|
435 |
|
|
if (waitpid(pid, &status, WNOHANG) > 0) { |
436 |
|
|
if (WIFEXITED(status)) { |
437 |
|
|
log_warnx("Lost child: %s exited", pname); |
438 |
|
|
return (1); |
439 |
|
|
} |
440 |
|
|
if (WIFSIGNALED(status)) { |
441 |
|
|
log_warnx("Lost child: %s terminated; signal %d", |
442 |
|
|
pname, WTERMSIG(status)); |
443 |
|
|
return (1); |
444 |
|
|
} |
445 |
|
|
} |
446 |
|
|
|
447 |
|
|
return (0); |
448 |
|
|
} |
449 |
|
|
|
450 |
|
|
int |
451 |
|
|
send_filterset(struct imsgbuf *i, struct filter_set_head *set) |
452 |
|
|
{ |
453 |
|
|
struct filter_set *s; |
454 |
|
|
|
455 |
|
|
TAILQ_FOREACH(s, set, entry) |
456 |
|
|
if (imsg_compose(i, IMSG_FILTER_SET, 0, 0, -1, s, |
457 |
|
|
sizeof(struct filter_set)) == -1) |
458 |
|
|
return (-1); |
459 |
|
|
return (0); |
460 |
|
|
} |
461 |
|
|
|
462 |
|
|
int |
463 |
|
|
reconfigure(char *conffile, struct bgpd_config *conf, struct peer **peer_l) |
464 |
|
|
{ |
465 |
|
|
struct peer *p; |
466 |
|
|
struct filter_rule *r; |
467 |
|
|
struct listen_addr *la; |
468 |
|
|
struct rde_rib *rr; |
469 |
|
|
struct rdomain *rd; |
470 |
|
|
|
471 |
|
|
if (reconfpending) { |
472 |
|
|
log_info("previous reload still running"); |
473 |
|
|
return (2); |
474 |
|
|
} |
475 |
|
|
reconfpending = 2; /* one per child */ |
476 |
|
|
|
477 |
|
|
log_info("rereading config"); |
478 |
|
|
if (parse_config(conffile, conf, peer_l)) { |
479 |
|
|
log_warnx("config file %s has errors, not reloading", |
480 |
|
|
conffile); |
481 |
|
|
reconfpending = 0; |
482 |
|
|
return (1); |
483 |
|
|
} |
484 |
|
|
|
485 |
|
|
cflags = conf->flags; |
486 |
|
|
prepare_listeners(conf); |
487 |
|
|
|
488 |
|
|
/* start reconfiguration */ |
489 |
|
|
if (imsg_compose(ibuf_se, IMSG_RECONF_CONF, 0, 0, -1, |
490 |
|
|
conf, sizeof(struct bgpd_config)) == -1) |
491 |
|
|
return (-1); |
492 |
|
|
if (imsg_compose(ibuf_rde, IMSG_RECONF_CONF, 0, 0, -1, |
493 |
|
|
conf, sizeof(struct bgpd_config)) == -1) |
494 |
|
|
return (-1); |
495 |
|
|
|
496 |
|
|
TAILQ_FOREACH(la, conf->listen_addrs, entry) { |
497 |
|
|
if (imsg_compose(ibuf_se, IMSG_RECONF_LISTENER, 0, 0, la->fd, |
498 |
|
|
la, sizeof(struct listen_addr)) == -1) |
499 |
|
|
return (-1); |
500 |
|
|
la->fd = -1; |
501 |
|
|
} |
502 |
|
|
|
503 |
|
|
if (control_setup(conf) == -1) |
504 |
|
|
return (-1); |
505 |
|
|
|
506 |
|
|
/* adjust fib syncing on reload */ |
507 |
|
|
ktable_preload(); |
508 |
|
|
|
509 |
|
|
/* RIBs for the RDE */ |
510 |
|
|
while ((rr = SIMPLEQ_FIRST(&ribnames))) { |
511 |
|
|
SIMPLEQ_REMOVE_HEAD(&ribnames, entry); |
512 |
|
|
if (ktable_update(rr->rtableid, rr->name, NULL, |
513 |
|
|
rr->flags, conf->fib_priority) == -1) { |
514 |
|
|
log_warnx("failed to load rdomain %d", |
515 |
|
|
rr->rtableid); |
516 |
|
|
return (-1); |
517 |
|
|
} |
518 |
|
|
if (imsg_compose(ibuf_rde, IMSG_RECONF_RIB, 0, 0, -1, |
519 |
|
|
rr, sizeof(struct rde_rib)) == -1) |
520 |
|
|
return (-1); |
521 |
|
|
free(rr); |
522 |
|
|
} |
523 |
|
|
|
524 |
|
|
/* send peer list to the SE */ |
525 |
|
|
for (p = *peer_l; p != NULL; p = p->next) { |
526 |
|
|
if (imsg_compose(ibuf_se, IMSG_RECONF_PEER, p->conf.id, 0, -1, |
527 |
|
|
&p->conf, sizeof(struct peer_config)) == -1) |
528 |
|
|
return (-1); |
529 |
|
|
} |
530 |
|
|
|
531 |
|
|
/* networks go via kroute to the RDE */ |
532 |
|
|
if (kr_net_reload(0, &conf->networks)) |
533 |
|
|
return (-1); |
534 |
|
|
|
535 |
|
|
/* filters for the RDE */ |
536 |
|
|
while ((r = TAILQ_FIRST(conf->filters)) != NULL) { |
537 |
|
|
TAILQ_REMOVE(conf->filters, r, entry); |
538 |
|
|
if (imsg_compose(ibuf_rde, IMSG_RECONF_FILTER, 0, 0, -1, |
539 |
|
|
r, sizeof(struct filter_rule)) == -1) |
540 |
|
|
return (-1); |
541 |
|
|
if (send_filterset(ibuf_rde, &r->set) == -1) |
542 |
|
|
return (-1); |
543 |
|
|
filterset_free(&r->set); |
544 |
|
|
free(r); |
545 |
|
|
} |
546 |
|
|
|
547 |
|
|
while ((rd = SIMPLEQ_FIRST(&conf->rdomains)) != NULL) { |
548 |
|
|
SIMPLEQ_REMOVE_HEAD(&conf->rdomains, entry); |
549 |
|
|
if (ktable_update(rd->rtableid, rd->descr, rd->ifmpe, |
550 |
|
|
rd->flags, conf->fib_priority) == -1) { |
551 |
|
|
log_warnx("failed to load rdomain %d", |
552 |
|
|
rd->rtableid); |
553 |
|
|
return (-1); |
554 |
|
|
} |
555 |
|
|
/* networks go via kroute to the RDE */ |
556 |
|
|
if (kr_net_reload(rd->rtableid, &rd->net_l)) |
557 |
|
|
return (-1); |
558 |
|
|
|
559 |
|
|
if (imsg_compose(ibuf_rde, IMSG_RECONF_RDOMAIN, 0, 0, -1, |
560 |
|
|
rd, sizeof(*rd)) == -1) |
561 |
|
|
return (-1); |
562 |
|
|
|
563 |
|
|
/* export targets */ |
564 |
|
|
if (imsg_compose(ibuf_rde, IMSG_RECONF_RDOMAIN_EXPORT, 0, 0, |
565 |
|
|
-1, NULL, 0) == -1) |
566 |
|
|
return (-1); |
567 |
|
|
if (send_filterset(ibuf_rde, &rd->export) == -1) |
568 |
|
|
return (-1); |
569 |
|
|
filterset_free(&rd->export); |
570 |
|
|
|
571 |
|
|
/* import targets */ |
572 |
|
|
if (imsg_compose(ibuf_rde, IMSG_RECONF_RDOMAIN_IMPORT, 0, 0, |
573 |
|
|
-1, NULL, 0) == -1) |
574 |
|
|
return (-1); |
575 |
|
|
if (send_filterset(ibuf_rde, &rd->import) == -1) |
576 |
|
|
return (-1); |
577 |
|
|
filterset_free(&rd->import); |
578 |
|
|
|
579 |
|
|
if (imsg_compose(ibuf_rde, IMSG_RECONF_RDOMAIN_DONE, 0, 0, |
580 |
|
|
-1, NULL, 0) == -1) |
581 |
|
|
return (-1); |
582 |
|
|
|
583 |
|
|
free(rd); |
584 |
|
|
} |
585 |
|
|
|
586 |
|
|
/* signal the SE first then the RDE to activate the new config */ |
587 |
|
|
if (imsg_compose(ibuf_se, IMSG_RECONF_DONE, 0, 0, -1, NULL, 0) == -1) |
588 |
|
|
return (-1); |
589 |
|
|
|
590 |
|
|
/* mrt changes can be sent out of bound */ |
591 |
|
|
mrt_reconfigure(conf->mrt); |
592 |
|
|
return (0); |
593 |
|
|
} |
594 |
|
|
|
595 |
|
|
int |
596 |
|
|
dispatch_imsg(struct imsgbuf *ibuf, int idx, struct bgpd_config *conf) |
597 |
|
|
{ |
598 |
|
|
struct imsg imsg; |
599 |
|
|
ssize_t n; |
600 |
|
|
int rv, verbose; |
601 |
|
|
|
602 |
|
|
rv = 0; |
603 |
|
|
while (ibuf) { |
604 |
|
|
if ((n = imsg_get(ibuf, &imsg)) == -1) |
605 |
|
|
return (-1); |
606 |
|
|
|
607 |
|
|
if (n == 0) |
608 |
|
|
break; |
609 |
|
|
|
610 |
|
|
switch (imsg.hdr.type) { |
611 |
|
|
case IMSG_KROUTE_CHANGE: |
612 |
|
|
if (idx != PFD_PIPE_ROUTE) |
613 |
|
|
log_warnx("route request not from RDE"); |
614 |
|
|
else if (imsg.hdr.len != IMSG_HEADER_SIZE + |
615 |
|
|
sizeof(struct kroute_full)) |
616 |
|
|
log_warnx("wrong imsg len"); |
617 |
|
|
else if (kr_change(imsg.hdr.peerid, imsg.data, |
618 |
|
|
conf->fib_priority)) |
619 |
|
|
rv = -1; |
620 |
|
|
break; |
621 |
|
|
case IMSG_KROUTE_DELETE: |
622 |
|
|
if (idx != PFD_PIPE_ROUTE) |
623 |
|
|
log_warnx("route request not from RDE"); |
624 |
|
|
else if (imsg.hdr.len != IMSG_HEADER_SIZE + |
625 |
|
|
sizeof(struct kroute_full)) |
626 |
|
|
log_warnx("wrong imsg len"); |
627 |
|
|
else if (kr_delete(imsg.hdr.peerid, imsg.data, |
628 |
|
|
conf->fib_priority)) |
629 |
|
|
rv = -1; |
630 |
|
|
break; |
631 |
|
|
case IMSG_NEXTHOP_ADD: |
632 |
|
|
if (idx != PFD_PIPE_ROUTE) |
633 |
|
|
log_warnx("nexthop request not from RDE"); |
634 |
|
|
else if (imsg.hdr.len != IMSG_HEADER_SIZE + |
635 |
|
|
sizeof(struct bgpd_addr)) |
636 |
|
|
log_warnx("wrong imsg len"); |
637 |
|
|
else if (kr_nexthop_add(imsg.hdr.peerid, imsg.data) == |
638 |
|
|
-1) |
639 |
|
|
rv = -1; |
640 |
|
|
break; |
641 |
|
|
case IMSG_NEXTHOP_REMOVE: |
642 |
|
|
if (idx != PFD_PIPE_ROUTE) |
643 |
|
|
log_warnx("nexthop request not from RDE"); |
644 |
|
|
else if (imsg.hdr.len != IMSG_HEADER_SIZE + |
645 |
|
|
sizeof(struct bgpd_addr)) |
646 |
|
|
log_warnx("wrong imsg len"); |
647 |
|
|
else |
648 |
|
|
kr_nexthop_delete(imsg.hdr.peerid, imsg.data); |
649 |
|
|
break; |
650 |
|
|
case IMSG_PFTABLE_ADD: |
651 |
|
|
if (idx != PFD_PIPE_ROUTE) |
652 |
|
|
log_warnx("pftable request not from RDE"); |
653 |
|
|
else |
654 |
|
|
if (imsg.hdr.len != IMSG_HEADER_SIZE + |
655 |
|
|
sizeof(struct pftable_msg)) |
656 |
|
|
log_warnx("wrong imsg len"); |
657 |
|
|
else if (pftable_addr_add(imsg.data) != 0) |
658 |
|
|
rv = -1; |
659 |
|
|
break; |
660 |
|
|
case IMSG_PFTABLE_REMOVE: |
661 |
|
|
if (idx != PFD_PIPE_ROUTE) |
662 |
|
|
log_warnx("pftable request not from RDE"); |
663 |
|
|
else |
664 |
|
|
if (imsg.hdr.len != IMSG_HEADER_SIZE + |
665 |
|
|
sizeof(struct pftable_msg)) |
666 |
|
|
log_warnx("wrong imsg len"); |
667 |
|
|
else if (pftable_addr_remove(imsg.data) != 0) |
668 |
|
|
rv = -1; |
669 |
|
|
break; |
670 |
|
|
case IMSG_PFTABLE_COMMIT: |
671 |
|
|
if (idx != PFD_PIPE_ROUTE) |
672 |
|
|
log_warnx("pftable request not from RDE"); |
673 |
|
|
else |
674 |
|
|
if (imsg.hdr.len != IMSG_HEADER_SIZE) |
675 |
|
|
log_warnx("wrong imsg len"); |
676 |
|
|
else if (pftable_commit() != 0) |
677 |
|
|
rv = -1; |
678 |
|
|
break; |
679 |
|
|
case IMSG_CTL_RELOAD: |
680 |
|
|
if (idx != PFD_PIPE_SESSION) |
681 |
|
|
log_warnx("reload request not from SE"); |
682 |
|
|
else { |
683 |
|
|
reconfig = 1; |
684 |
|
|
reconfpid = imsg.hdr.pid; |
685 |
|
|
} |
686 |
|
|
break; |
687 |
|
|
case IMSG_CTL_FIB_COUPLE: |
688 |
|
|
if (idx != PFD_PIPE_SESSION) |
689 |
|
|
log_warnx("couple request not from SE"); |
690 |
|
|
else |
691 |
|
|
kr_fib_couple(imsg.hdr.peerid, |
692 |
|
|
conf->fib_priority); |
693 |
|
|
break; |
694 |
|
|
case IMSG_CTL_FIB_DECOUPLE: |
695 |
|
|
if (idx != PFD_PIPE_SESSION) |
696 |
|
|
log_warnx("decouple request not from SE"); |
697 |
|
|
else |
698 |
|
|
kr_fib_decouple(imsg.hdr.peerid, |
699 |
|
|
conf->fib_priority); |
700 |
|
|
break; |
701 |
|
|
case IMSG_CTL_KROUTE: |
702 |
|
|
case IMSG_CTL_KROUTE_ADDR: |
703 |
|
|
case IMSG_CTL_SHOW_NEXTHOP: |
704 |
|
|
case IMSG_CTL_SHOW_INTERFACE: |
705 |
|
|
case IMSG_CTL_SHOW_FIB_TABLES: |
706 |
|
|
if (idx != PFD_PIPE_SESSION) |
707 |
|
|
log_warnx("kroute request not from SE"); |
708 |
|
|
else |
709 |
|
|
kr_show_route(&imsg); |
710 |
|
|
break; |
711 |
|
|
case IMSG_IFINFO: |
712 |
|
|
if (idx != PFD_PIPE_SESSION) |
713 |
|
|
log_warnx("IFINFO request not from SE"); |
714 |
|
|
else if (imsg.hdr.len != IMSG_HEADER_SIZE + IFNAMSIZ) |
715 |
|
|
log_warnx("IFINFO request with wrong len"); |
716 |
|
|
else |
717 |
|
|
kr_ifinfo(imsg.data); |
718 |
|
|
break; |
719 |
|
|
case IMSG_DEMOTE: |
720 |
|
|
if (idx != PFD_PIPE_SESSION) |
721 |
|
|
log_warnx("demote request not from SE"); |
722 |
|
|
else if (imsg.hdr.len != IMSG_HEADER_SIZE + |
723 |
|
|
sizeof(struct demote_msg)) |
724 |
|
|
log_warnx("DEMOTE request with wrong len"); |
725 |
|
|
else { |
726 |
|
|
struct demote_msg *msg; |
727 |
|
|
|
728 |
|
|
msg = imsg.data; |
729 |
|
|
carp_demote_set(msg->demote_group, msg->level); |
730 |
|
|
} |
731 |
|
|
break; |
732 |
|
|
case IMSG_CTL_LOG_VERBOSE: |
733 |
|
|
/* already checked by SE */ |
734 |
|
|
memcpy(&verbose, imsg.data, sizeof(verbose)); |
735 |
|
|
log_verbose(verbose); |
736 |
|
|
break; |
737 |
|
|
case IMSG_RECONF_DONE: |
738 |
|
|
if (reconfpending == 0) |
739 |
|
|
log_warnx("unexpected RECONF_DONE received"); |
740 |
|
|
else if (reconfpending == 2) { |
741 |
|
|
imsg_compose(ibuf_rde, IMSG_RECONF_DONE, 0, |
742 |
|
|
0, -1, NULL, 0); |
743 |
|
|
|
744 |
|
|
/* finally fix kroute information */ |
745 |
|
|
ktable_postload(conf->fib_priority); |
746 |
|
|
|
747 |
|
|
/* redistribute list needs to be reloaded too */ |
748 |
|
|
kr_reload(); |
749 |
|
|
} |
750 |
|
|
reconfpending--; |
751 |
|
|
break; |
752 |
|
|
default: |
753 |
|
|
break; |
754 |
|
|
} |
755 |
|
|
imsg_free(&imsg); |
756 |
|
|
if (rv != 0) |
757 |
|
|
return (rv); |
758 |
|
|
} |
759 |
|
|
return (0); |
760 |
|
|
} |
761 |
|
|
|
762 |
|
|
void |
763 |
|
|
send_nexthop_update(struct kroute_nexthop *msg) |
764 |
|
|
{ |
765 |
|
|
char *gw = NULL; |
766 |
|
|
|
767 |
|
|
if (msg->gateway.aid) |
768 |
|
|
if (asprintf(&gw, ": via %s", |
769 |
|
|
log_addr(&msg->gateway)) == -1) { |
770 |
|
|
log_warn("send_nexthop_update"); |
771 |
|
|
quit = 1; |
772 |
|
|
} |
773 |
|
|
|
774 |
|
|
log_debug("nexthop %s now %s%s%s", log_addr(&msg->nexthop), |
775 |
|
|
msg->valid ? "valid" : "invalid", |
776 |
|
|
msg->connected ? ": directly connected" : "", |
777 |
|
|
msg->gateway.aid ? gw : ""); |
778 |
|
|
|
779 |
|
|
free(gw); |
780 |
|
|
|
781 |
|
|
if (imsg_compose(ibuf_rde, IMSG_NEXTHOP_UPDATE, 0, 0, -1, |
782 |
|
|
msg, sizeof(struct kroute_nexthop)) == -1) |
783 |
|
|
quit = 1; |
784 |
|
|
} |
785 |
|
|
|
786 |
|
|
void |
787 |
|
|
send_imsg_session(int type, pid_t pid, void *data, u_int16_t datalen) |
788 |
|
|
{ |
789 |
|
|
imsg_compose(ibuf_se, type, 0, pid, -1, data, datalen); |
790 |
|
|
} |
791 |
|
|
|
792 |
|
|
int |
793 |
|
|
send_network(int type, struct network_config *net, struct filter_set_head *h) |
794 |
|
|
{ |
795 |
|
|
if (imsg_compose(ibuf_rde, type, 0, 0, -1, net, |
796 |
|
|
sizeof(struct network_config)) == -1) |
797 |
|
|
return (-1); |
798 |
|
|
/* networks that get deleted don't need to send the filter set */ |
799 |
|
|
if (type == IMSG_NETWORK_REMOVE) |
800 |
|
|
return (0); |
801 |
|
|
if (send_filterset(ibuf_rde, h) == -1) |
802 |
|
|
return (-1); |
803 |
|
|
if (imsg_compose(ibuf_rde, IMSG_NETWORK_DONE, 0, 0, -1, NULL, 0) == -1) |
804 |
|
|
return (-1); |
805 |
|
|
|
806 |
|
|
return (0); |
807 |
|
|
} |
808 |
|
|
|
809 |
|
|
int |
810 |
|
|
bgpd_filternexthop(struct kroute *kr, struct kroute6 *kr6) |
811 |
|
|
{ |
812 |
|
|
/* kernel routes are never filtered */ |
813 |
|
|
if (kr && kr->flags & F_KERNEL && kr->prefixlen != 0) |
814 |
|
|
return (0); |
815 |
|
|
if (kr6 && kr6->flags & F_KERNEL && kr6->prefixlen != 0) |
816 |
|
|
return (0); |
817 |
|
|
|
818 |
|
|
if (cflags & BGPD_FLAG_NEXTHOP_BGP) { |
819 |
|
|
if (kr && kr->flags & F_BGPD_INSERTED) |
820 |
|
|
return (0); |
821 |
|
|
if (kr6 && kr6->flags & F_BGPD_INSERTED) |
822 |
|
|
return (0); |
823 |
|
|
} |
824 |
|
|
|
825 |
|
|
if (cflags & BGPD_FLAG_NEXTHOP_DEFAULT) { |
826 |
|
|
if (kr && kr->prefixlen == 0) |
827 |
|
|
return (0); |
828 |
|
|
if (kr6 && kr6->prefixlen == 0) |
829 |
|
|
return (0); |
830 |
|
|
} |
831 |
|
|
|
832 |
|
|
return (1); |
833 |
|
|
} |
834 |
|
|
|
835 |
|
|
int |
836 |
|
|
control_setup(struct bgpd_config *conf) |
837 |
|
|
{ |
838 |
|
|
int fd, restricted; |
839 |
|
|
|
840 |
|
|
/* control socket is outside chroot */ |
841 |
|
|
if (!cname || strcmp(cname, conf->csock)) { |
842 |
|
|
if (cname) { |
843 |
|
|
control_cleanup(cname); |
844 |
|
|
free(cname); |
845 |
|
|
} |
846 |
|
|
if ((cname = strdup(conf->csock)) == NULL) |
847 |
|
|
fatal("strdup"); |
848 |
|
|
if ((fd = control_init(0, cname)) == -1) |
849 |
|
|
fatalx("control socket setup failed"); |
850 |
|
|
if (control_listen(fd) == -1) |
851 |
|
|
fatalx("control socket setup failed"); |
852 |
|
|
restricted = 0; |
853 |
|
|
if (imsg_compose(ibuf_se, IMSG_RECONF_CTRL, 0, 0, fd, |
854 |
|
|
&restricted, sizeof(restricted)) == -1) |
855 |
|
|
return (-1); |
856 |
|
|
} |
857 |
|
|
if (!conf->rcsock) { |
858 |
|
|
/* remove restricted socket */ |
859 |
|
|
control_cleanup(rcname); |
860 |
|
|
free(rcname); |
861 |
|
|
rcname = NULL; |
862 |
|
|
} else if (!rcname || strcmp(rcname, conf->rcsock)) { |
863 |
|
|
if (rcname) { |
864 |
|
|
control_cleanup(rcname); |
865 |
|
|
free(rcname); |
866 |
|
|
} |
867 |
|
|
if ((rcname = strdup(conf->rcsock)) == NULL) |
868 |
|
|
fatal("strdup"); |
869 |
|
|
if ((fd = control_init(1, rcname)) == -1) |
870 |
|
|
fatalx("control socket setup failed"); |
871 |
|
|
if (control_listen(fd) == -1) |
872 |
|
|
fatalx("control socket setup failed"); |
873 |
|
|
restricted = 1; |
874 |
|
|
if (imsg_compose(ibuf_se, IMSG_RECONF_CTRL, 0, 0, fd, |
875 |
|
|
&restricted, sizeof(restricted)) == -1) |
876 |
|
|
return (-1); |
877 |
|
|
} |
878 |
|
|
return (0); |
879 |
|
|
} |
880 |
|
|
|
881 |
|
|
void |
882 |
|
|
set_pollfd(struct pollfd *pfd, struct imsgbuf *i) |
883 |
|
|
{ |
884 |
|
|
if (i == NULL || i->fd == -1) { |
885 |
|
|
pfd->fd = -1; |
886 |
|
|
return; |
887 |
|
|
} |
888 |
|
|
pfd->fd = i->fd; |
889 |
|
|
pfd->events = POLLIN; |
890 |
|
|
if (i->w.queued > 0) |
891 |
|
|
pfd->events |= POLLOUT; |
892 |
|
|
} |
893 |
|
|
|
894 |
|
|
int |
895 |
|
|
handle_pollfd(struct pollfd *pfd, struct imsgbuf *i) |
896 |
|
|
{ |
897 |
|
|
ssize_t n; |
898 |
|
|
|
899 |
|
|
if (i == NULL) |
900 |
|
|
return (0); |
901 |
|
|
|
902 |
|
|
if (pfd->revents & POLLOUT) |
903 |
|
|
if (msgbuf_write(&i->w) <= 0 && errno != EAGAIN) { |
904 |
|
|
log_warn("imsg write error"); |
905 |
|
|
close(i->fd); |
906 |
|
|
i->fd = -1; |
907 |
|
|
return (-1); |
908 |
|
|
} |
909 |
|
|
|
910 |
|
|
if (pfd->revents & POLLIN) { |
911 |
|
|
if ((n = imsg_read(i)) == -1 && errno != EAGAIN) { |
912 |
|
|
log_warn("imsg read error"); |
913 |
|
|
close(i->fd); |
914 |
|
|
i->fd = -1; |
915 |
|
|
return (-1); |
916 |
|
|
} |
917 |
|
|
if (n == 0) { |
918 |
|
|
log_warnx("peer closed imsg connection"); |
919 |
|
|
close(i->fd); |
920 |
|
|
i->fd = -1; |
921 |
|
|
return (-1); |
922 |
|
|
} |
923 |
|
|
} |
924 |
|
|
return (0); |
925 |
|
|
} |
926 |
|
|
|
927 |
|
|
int |
928 |
|
|
imsg_send_sockets(struct imsgbuf *se, struct imsgbuf *rde) |
929 |
|
|
{ |
930 |
|
|
int pipe_s2r[2]; |
931 |
|
|
int pipe_s2r_ctl[2]; |
932 |
|
|
|
933 |
|
|
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, |
934 |
|
|
PF_UNSPEC, pipe_s2r) == -1) |
935 |
|
|
return (-1); |
936 |
|
|
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, |
937 |
|
|
PF_UNSPEC, pipe_s2r_ctl) == -1) |
938 |
|
|
return (-1); |
939 |
|
|
|
940 |
|
|
if (imsg_compose(se, IMSG_SOCKET_CONN, 0, 0, pipe_s2r[0], |
941 |
|
|
NULL, 0) == -1) |
942 |
|
|
return (-1); |
943 |
|
|
if (imsg_compose(rde, IMSG_SOCKET_CONN, 0, 0, pipe_s2r[1], |
944 |
|
|
NULL, 0) == -1) |
945 |
|
|
return (-1); |
946 |
|
|
|
947 |
|
|
if (imsg_compose(se, IMSG_SOCKET_CONN_CTL, 0, 0, pipe_s2r_ctl[0], |
948 |
|
|
NULL, 0) == -1) |
949 |
|
|
return (-1); |
950 |
|
|
if (imsg_compose(rde, IMSG_SOCKET_CONN_CTL, 0, 0, pipe_s2r_ctl[1], |
951 |
|
|
NULL, 0) == -1) |
952 |
|
|
return (-1); |
953 |
|
|
|
954 |
|
|
return (0); |
955 |
|
|
} |