forked from erluko/socat
-
Notifications
You must be signed in to change notification settings - Fork 0
/
xio-listen.c
358 lines (311 loc) · 11.2 KB
/
xio-listen.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
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
/* source: xio-listen.c */
/* Copyright Gerhard Rieger */
/* Published under the GNU General Public License V.2, see file COPYING */
/* this file contains the source for listen socket options */
#include "xiosysincludes.h"
#if WITH_LISTEN
#include "xioopen.h"
#include "xio-named.h"
#include "xio-socket.h"
#include "xio-ip.h"
#include "xio-ip4.h"
#include "xio-listen.h"
#include "xio-tcpwrap.h"
/***** LISTEN options *****/
const struct optdesc opt_backlog = { "backlog", NULL, OPT_BACKLOG, GROUP_LISTEN, PH_LISTEN, TYPE_INT, OFUNC_SPEC };
const struct optdesc opt_fork = { "fork", NULL, OPT_FORK, GROUP_CHILD, PH_PASTACCEPT, TYPE_BOOL, OFUNC_SPEC };
const struct optdesc opt_max_children = { "max-children", NULL, OPT_MAX_CHILDREN, GROUP_CHILD, PH_PASTACCEPT, TYPE_INT, OFUNC_SPEC };
/**/
#if (WITH_UDP || WITH_TCP)
const struct optdesc opt_range = { "range", NULL, OPT_RANGE, GROUP_RANGE, PH_ACCEPT, TYPE_STRING, OFUNC_SPEC };
#endif
/*
applies and consumes the following option:
PH_INIT, PH_PASTSOCKET, PH_PREBIND, PH_BIND, PH_PASTBIND, PH_EARLY,
PH_PREOPEN, PH_FD, PH_CONNECTED, PH_LATE, PH_LATE2
OPT_FORK, OPT_SO_TYPE, OPT_SO_PROTOTYPE, OPT_BACKLOG, OPT_RANGE, tcpwrap,
OPT_SOURCEPORT, OPT_LOWPORT, cloexec
*/
int
xioopen_listen(struct single *xfd, int xioflags,
struct sockaddr *us, socklen_t uslen,
struct opt *opts, struct opt *opts0,
int pf, int socktype, int proto) {
int level;
int result;
#if WITH_RETRY
if (xfd->forever || xfd->retry) {
level = E_INFO;
} else
#endif /* WITH_RETRY */
level = E_ERROR;
while (true) { /* loop over failed attempts */
/* tcp listen; this can fork() for us; it only returns on error or on
successful establishment of tcp connection */
result = _xioopen_listen(xfd, xioflags,
(struct sockaddr *)us, uslen,
opts, pf, socktype, proto, level);
/*! not sure if we should try again on retry/forever */
switch (result) {
case STAT_OK: break;
#if WITH_RETRY
case STAT_RETRYLATER:
case STAT_RETRYNOW:
if (xfd->forever || xfd->retry) {
dropopts(opts, PH_ALL); opts = copyopts(opts0, GROUP_ALL);
if (result == STAT_RETRYLATER) {
Nanosleep(&xfd->intervall, NULL);
}
dropopts(opts, PH_ALL); opts = copyopts(opts0, GROUP_ALL);
--xfd->retry;
continue;
}
return STAT_NORETRY;
#endif /* WITH_RETRY */
default:
return result;
}
break;
} /* drop out on success */
return result;
}
/* creates the listening socket, bind, applies options; waits for incoming
connection, checks its source address and port. Depending on fork option, it
may fork a subprocess.
pf specifies the syntax expected for range option. In the case of generic
socket it is 0 (expecting raw binary data), and the real pf can be obtained
from us->af_family; for other socket types pf == us->af_family
Returns 0 if a connection was accepted; with fork option, this is always in
a subprocess!
Other return values indicate a problem; this can happen in the master
process or in a subprocess.
This function does not retry. If you need retries, handle this in a
loop in the calling function (and always provide the options...)
After fork, we set the forever/retry of the child process to 0
applies and consumes the following option:
PH_INIT, PH_PASTSOCKET, PH_PREBIND, PH_BIND, PH_PASTBIND, PH_EARLY,
PH_PREOPEN, PH_FD, PH_CONNECTED, PH_LATE, PH_LATE2
OPT_FORK, OPT_SO_TYPE, OPT_SO_PROTOTYPE, OPT_BACKLOG, OPT_RANGE, tcpwrap,
OPT_SOURCEPORT, OPT_LOWPORT, cloexec
*/
int _xioopen_listen(struct single *xfd, int xioflags, struct sockaddr *us, socklen_t uslen,
struct opt *opts, int pf, int socktype, int proto, int level) {
struct sockaddr sa;
socklen_t salen;
int backlog = 5; /* why? 1 seems to cause problems under some load */
char *rangename;
bool dofork = false;
int maxchildren = 0;
char infobuff[256];
char lisname[256];
union sockaddr_union _peername;
union sockaddr_union _sockname;
union sockaddr_union *pa = &_peername; /* peer address */
union sockaddr_union *la = &_sockname; /* local address */
socklen_t pas = sizeof(_peername); /* peer address size */
socklen_t las = sizeof(_sockname); /* local address size */
int result;
retropt_bool(opts, OPT_FORK, &dofork);
if (dofork) {
if (!(xioflags & XIO_MAYFORK)) {
Error("option fork not allowed here");
return STAT_NORETRY;
}
xfd->flags |= XIO_DOESFORK;
}
retropt_int(opts, OPT_MAX_CHILDREN, &maxchildren);
if (! dofork && maxchildren) {
Error("option max-children not allowed without option fork");
return STAT_NORETRY;
}
if (applyopts_single(xfd, opts, PH_INIT) < 0) return -1;
if (dofork) {
xiosetchilddied(); /* set SIGCHLD handler */
}
if ((xfd->fd = xiosocket(opts, us->sa_family, socktype, proto, level)) < 0) {
return STAT_RETRYLATER;
}
applyopts_cloexec(xfd->fd, opts);
applyopts(xfd->fd, opts, PH_PREBIND);
applyopts(xfd->fd, opts, PH_BIND);
if (Bind(xfd->fd, (struct sockaddr *)us, uslen) < 0) {
Msg4(level, "bind(%d, {%s}, "F_socklen"): %s", xfd->fd,
sockaddr_info(us, uslen, infobuff, sizeof(infobuff)), uslen,
strerror(errno));
Close(xfd->fd);
return STAT_RETRYLATER;
}
#if WITH_UNIX
if (us->sa_family == AF_UNIX) {
applyopts_named(((struct sockaddr_un *)us)->sun_path, opts, PH_FD);
}
#endif
/* under some circumstances (e.g., TCP listen on port 0) bind() fills empty
fields that we want to know. */
salen = sizeof(sa);
if (Getsockname(xfd->fd, us, &uslen) < 0) {
Warn4("getsockname(%d, %p, {%d}): %s",
xfd->fd, &us, uslen, strerror(errno));
}
applyopts(xfd->fd, opts, PH_PASTBIND);
#if WITH_UNIX
if (us->sa_family == AF_UNIX) {
/*applyopts_early(((struct sockaddr_un *)us)->sun_path, opts);*/
applyopts_named(((struct sockaddr_un *)us)->sun_path, opts, PH_EARLY);
applyopts_named(((struct sockaddr_un *)us)->sun_path, opts, PH_PREOPEN);
}
#endif /* WITH_UNIX */
#if WITH_IP4 /*|| WITH_IP6*/
if (retropt_string(opts, OPT_RANGE, &rangename) >= 0) {
if (xioparserange(rangename, pf, &xfd->para.socket.range)
< 0) {
free(rangename);
return STAT_NORETRY;
}
free(rangename);
xfd->para.socket.dorange = true;
}
#endif
#if (WITH_TCP || WITH_UDP) && WITH_LIBWRAP
xio_retropt_tcpwrap(xfd, opts);
#endif /* && (WITH_TCP || WITH_UDP) && WITH_LIBWRAP */
#if WITH_TCP || WITH_UDP
if (retropt_ushort(opts, OPT_SOURCEPORT, &xfd->para.socket.ip.sourceport) >= 0) {
xfd->para.socket.ip.dosourceport = true;
}
retropt_bool(opts, OPT_LOWPORT, &xfd->para.socket.ip.lowport);
#endif /* WITH_TCP || WITH_UDP */
applyopts(xfd->fd, opts, PH_PRELISTEN);
retropt_int(opts, OPT_BACKLOG, &backlog);
if (Listen(xfd->fd, backlog) < 0) {
Error3("listen(%d, %d): %s", xfd->fd, backlog, strerror(errno));
return STAT_RETRYLATER;
}
if (xioopts.logopt == 'm') {
Info("starting accept loop, switching to syslog");
diag_set('y', xioopts.syslogfac); xioopts.logopt = 'y';
} else {
Info("starting accept loop");
}
while (true) { /* but we only loop if fork option is set */
char peername[256];
char sockname[256];
int ps; /* peer socket */
pa = &_peername;
la = &_sockname;
salen = sizeof(struct sockaddr);
do {
/*? int level = E_ERROR;*/
Notice1("listening on %s", sockaddr_info(us, uslen, lisname, sizeof(lisname)));
ps = Accept(xfd->fd, (struct sockaddr *)&sa, &salen);
if (ps >= 0) {
/*0 Info4("accept(%d, %p, {"F_Zu"}) -> %d", xfd->fd, &sa, salen, ps);*/
break; /* success, break out of loop */
}
if (errno == EINTR) {
continue;
}
if (errno == ECONNABORTED) {
Notice4("accept(%d, %p, {"F_socklen"}): %s",
xfd->fd, &sa, salen, strerror(errno));
continue;
}
Msg4(level, "accept(%d, %p, {"F_socklen"}): %s",
xfd->fd, &sa, salen, strerror(errno));
Close(xfd->fd);
return STAT_RETRYLATER;
} while (true);
applyopts_cloexec(ps, opts);
if (Getpeername(ps, &pa->soa, &pas) < 0) {
Warn4("getpeername(%d, %p, {"F_socklen"}): %s",
ps, pa, pas, strerror(errno));
pa = NULL;
}
if (Getsockname(ps, &la->soa, &las) < 0) {
Warn4("getsockname(%d, %p, {"F_socklen"}): %s",
ps, la, las, strerror(errno));
la = NULL;
}
Notice2("accepting connection from %s on %s",
pa?
sockaddr_info(&pa->soa, pas, peername, sizeof(peername)):"NULL",
la?
sockaddr_info(&la->soa, las, sockname, sizeof(sockname)):"NULL");
if (pa != NULL && la != NULL && xiocheckpeer(xfd, pa, la) < 0) {
if (Shutdown(ps, 2) < 0) {
Info2("shutdown(%d, 2): %s", ps, strerror(errno));
}
Close(ps);
continue;
}
if (pa != NULL)
Info1("permitting connection from %s",
sockaddr_info((struct sockaddr *)pa, pas,
infobuff, sizeof(infobuff)));
if (dofork) {
pid_t pid; /* mostly int; only used with fork */
sigset_t mask_sigchld;
/* we must prevent that the current packet triggers another fork;
therefore we wait for a signal from the recent child: USR1
indicates that is has consumed the last packet; CHLD means it has
terminated */
/* block SIGCHLD and SIGUSR1 until parent is ready to react */
sigemptyset(&mask_sigchld);
sigaddset(&mask_sigchld, SIGCHLD);
Sigprocmask(SIG_BLOCK, &mask_sigchld, NULL);
if ((pid = xio_fork(false, level==E_ERROR?level:E_WARN)) < 0) {
Close(xfd->fd);
Sigprocmask(SIG_UNBLOCK, &mask_sigchld, NULL);
return STAT_RETRYLATER;
}
if (pid == 0) { /* child */
pid_t cpid = Getpid();
Sigprocmask(SIG_UNBLOCK, &mask_sigchld, NULL);
Info1("just born: child process "F_pid, cpid);
xiosetenvulong("PID", cpid, 1);
if (Close(xfd->fd) < 0) {
Info2("close(%d): %s", xfd->fd, strerror(errno));
}
xfd->fd = ps;
#if WITH_RETRY
/* !? */
xfd->forever = false; xfd->retry = 0;
level = E_ERROR;
#endif /* WITH_RETRY */
break;
}
/* server: continue loop with listen */
/* shutdown() closes the socket even for the child process, but
close() does what we want */
if (Close(ps) < 0) {
Info2("close(%d): %s", ps, strerror(errno));
}
/* now we are ready to handle signals */
Sigprocmask(SIG_UNBLOCK, &mask_sigchld, NULL);
while (maxchildren) {
if (num_child < maxchildren) break;
Notice("maxchildren are active, waiting");
/* UINT_MAX would even be nicer, but Openindiana works only
with 31 bits */
while (!Sleep(INT_MAX)) ; /* any signal lets us continue */
}
Info("still listening");
} else {
if (Close(xfd->fd) < 0) {
Info2("close(%d): %s", xfd->fd, strerror(errno));
}
xfd->fd = ps;
break;
}
}
applyopts(xfd->fd, opts, PH_FD);
applyopts(xfd->fd, opts, PH_PASTSOCKET);
applyopts(xfd->fd, opts, PH_CONNECTED);
if ((result = _xio_openlate(xfd, opts)) < 0)
return result;
/* set the env vars describing the local and remote sockets */
if (la != NULL) xiosetsockaddrenv("SOCK", la, las, proto);
if (pa != NULL) xiosetsockaddrenv("PEER", pa, pas, proto);
return 0;
}
#endif /* WITH_LISTEN */