Fix testsuite build for non-TFO platforms
[exim.git] / src / src / ip.c
CommitLineData
059ec3d9
PH
1/*************************************************
2* Exim - an Internet mail transport agent *
3*************************************************/
4
d4e5e70b 5/* Copyright (c) University of Cambridge 1995 - 2017 */
059ec3d9
PH
6/* See the file NOTICE for conditions of use and distribution. */
7
8/* Functions for doing things with sockets. With the advent of IPv6 this has
9got messier, so that it's worth pulling out the code into separate functions
4c04137d 10that other parts of Exim can call, especially as there are now several
059ec3d9
PH
11different places in the code where sockets are used. */
12
13
14#include "exim.h"
15
16
17/*************************************************
18* Create a socket *
19*************************************************/
20
21/* Socket creation happens in a number of places so it's packaged here for
22convenience.
23
24Arguments:
25 type SOCK_DGRAM or SOCK_STREAM
26 af AF_INET or AF_INET6
27
28Returns: socket number or -1 on failure
29*/
30
31int
32ip_socket(int type, int af)
33{
34int sock = socket(af, type, 0);
35if (sock < 0)
36 log_write(0, LOG_MAIN, "IPv%c socket creation failed: %s",
37 (af == AF_INET6)? '6':'4', strerror(errno));
38return sock;
39}
40
41
42
43
44#if HAVE_IPV6
45/*************************************************
46* Convert printing address to numeric *
47*************************************************/
48
49/* This function converts the textual form of an IP address into a numeric form
50in an appropriate structure in an IPv6 environment. The getaddrinfo() function
51can (apparently) handle more complicated addresses (e.g. those containing
52scopes) than inet_pton() in some environments. We use hints to tell it that the
53input must be a numeric address.
54
55However, apparently some operating systems (or libraries) don't support
56getaddrinfo(), so there is a build-time option to revert to inet_pton() (which
57does not support scopes).
58
59Arguments:
60 address textual form of the address
61 addr where to copy back the answer
62
63Returns: nothing - failure provokes a panic-die
64*/
65
66static void
a56cc2b8 67ip_addrinfo(const uschar *address, struct sockaddr_in6 *saddr)
059ec3d9
PH
68{
69#ifdef IPV6_USE_INET_PTON
70
a56cc2b8 71 if (inet_pton(AF_INET6, CCS address, &saddr->sin6_addr) != 1)
059ec3d9
PH
72 log_write(0, LOG_MAIN|LOG_PANIC_DIE, "unable to parse \"%s\" as an "
73 "IP address", address);
74 saddr->sin6_family = AF_INET6;
75
76#else
77
78 int rc;
79 struct addrinfo hints, *res;
80 memset(&hints, 0, sizeof(hints));
81 hints.ai_family = AF_INET6;
82 hints.ai_socktype = SOCK_STREAM;
83 hints.ai_flags = AI_NUMERICHOST;
a56cc2b8 84 if ((rc = getaddrinfo(CCS address, NULL, &hints, &res)) != 0 || res == NULL)
059ec3d9
PH
85 log_write(0, LOG_MAIN|LOG_PANIC_DIE, "unable to parse \"%s\" as an "
86 "IP address: %s", address,
87 (rc == 0)? "NULL result returned" : gai_strerror(rc));
88 memcpy(saddr, res->ai_addr, res->ai_addrlen);
89 freeaddrinfo(res);
90
91#endif
92}
93#endif /* HAVE_IPV6 */
94
95
96/*************************************************
97* Bind socket to interface and port *
98*************************************************/
99
059ec3d9 100int
7eb6c37c 101ip_addr(void * sin_, int af, const uschar * address, int port)
059ec3d9 102{
7eb6c37c 103union sockaddr_46 * sin = sin_;
69cbeaec 104memset(sin, 0, sizeof(*sin));
059ec3d9
PH
105
106/* Setup code when using an IPv6 socket. The wildcard address is ":", to
107ensure an IPv6 socket is used. */
108
109#if HAVE_IPV6
110if (af == AF_INET6)
111 {
112 if (address[0] == ':' && address[1] == 0)
113 {
7eb6c37c
JH
114 sin->v6.sin6_family = AF_INET6;
115 sin->v6.sin6_addr = in6addr_any;
059ec3d9
PH
116 }
117 else
7eb6c37c
JH
118 ip_addrinfo(address, &sin->v6); /* Panic-dies on error */
119 sin->v6.sin6_port = htons(port);
120 return sizeof(sin->v6);
059ec3d9
PH
121 }
122else
123#else /* HAVE_IPv6 */
124af = af; /* Avoid compiler warning */
125#endif /* HAVE_IPV6 */
126
127/* Setup code when using IPv4 socket. The wildcard address is "". */
128
129 {
7eb6c37c
JH
130 sin->v4.sin_family = AF_INET;
131 sin->v4.sin_port = htons(port);
132 sin->v4.sin_addr.s_addr = address[0] == 0
133 ? (S_ADDR_TYPE)INADDR_ANY
134 : (S_ADDR_TYPE)inet_addr(CS address);
135 return sizeof(sin->v4);
059ec3d9 136 }
7eb6c37c 137}
059ec3d9 138
059ec3d9 139
7eb6c37c
JH
140
141/* This function binds a socket to a local interface address and port. For a
142wildcard IPv6 bind, the address is ":".
143
144Arguments:
145 sock the socket
146 af AF_INET or AF_INET6 - the socket type
147 address the IP address, in text form
148 port the IP port (host order)
149
150Returns: the result of bind()
151*/
152
153int
154ip_bind(int sock, int af, uschar *address, int port)
155{
156union sockaddr_46 sin;
157int s_len = ip_addr(&sin, af, address, port);
059ec3d9
PH
158return bind(sock, (struct sockaddr *)&sin, s_len);
159}
160
161
162
163/*************************************************
164* Connect socket to remote host *
165*************************************************/
166
167/* This function connects a socket to a remote address and port. The socket may
d515a917
PH
168or may not have previously been bound to a local interface. The socket is not
169closed, even in cases of error. It is expected that the calling function, which
170created the socket, will be the one that closes it.
059ec3d9
PH
171
172Arguments:
173 sock the socket
174 af AF_INET6 or AF_INET for the socket type
175 address the remote address, in text form
176 port the remote port
b1f8e4f8 177 timeout a timeout (zero for indefinite timeout)
0ab63f3d
JH
178 fastopen non-null iff TCP_FASTOPEN can be used; may indicate early-data to
179 be sent in SYN segment
059ec3d9
PH
180
181Returns: 0 on success; -1 on failure, with errno set
182*/
183
184int
fb05276a 185ip_connect(int sock, int af, const uschar *address, int port, int timeout,
0ab63f3d 186 const blob * fastopen)
059ec3d9
PH
187{
188struct sockaddr_in s_in4;
189struct sockaddr *s_ptr;
190int s_len, rc, save_errno;
191
192/* For an IPv6 address, use an IPv6 sockaddr structure. */
193
194#if HAVE_IPV6
195struct sockaddr_in6 s_in6;
196if (af == AF_INET6)
197 {
198 memset(&s_in6, 0, sizeof(s_in6));
199 ip_addrinfo(address, &s_in6); /* Panic-dies on error */
200 s_in6.sin6_port = htons(port);
201 s_ptr = (struct sockaddr *)&s_in6;
202 s_len = sizeof(s_in6);
203 }
204else
205#else /* HAVE_IPV6 */
206af = af; /* Avoid compiler warning */
207#endif /* HAVE_IPV6 */
208
209/* For an IPv4 address, use an IPv4 sockaddr structure, even on a system with
210IPv6 support. */
211
212 {
213 memset(&s_in4, 0, sizeof(s_in4));
214 s_in4.sin_family = AF_INET;
215 s_in4.sin_port = htons(port);
a56cc2b8 216 s_in4.sin_addr.s_addr = (S_ADDR_TYPE)inet_addr(CCS address);
059ec3d9
PH
217 s_ptr = (struct sockaddr *)&s_in4;
218 s_len = sizeof(s_in4);
219 }
220
221/* If no connection timeout is set, just call connect() without setting a
222timer, thereby allowing the inbuilt OS timeout to operate. */
223
af483912 224callout_address = string_sprintf("[%s]:%d", address, port);
059ec3d9
PH
225sigalrm_seen = FALSE;
226if (timeout > 0) alarm(timeout);
fb05276a 227
18f1b2f3 228#if defined(TCP_FASTOPEN) && defined(MSG_FASTOPEN)
fb05276a
JH
229/* TCP Fast Open, if the system has a cookie from a previous call to
230this peer, can send data in the SYN packet. The peer can send data
231before it gets our ACK of its SYN,ACK - the latter is useful for
232the SMTP banner. Is there any usage where the former might be?
233We might extend the ip_connect() args for data if so. For now,
234connect in FASTOPEN mode but with zero data.
235*/
236
237if (fastopen)
238 {
0ab63f3d
JH
239 if ((rc = sendto(sock, fastopen->data, fastopen->len,
240 MSG_FASTOPEN | MSG_DONTWAIT, s_ptr, s_len)) < 0)
241 if (errno == EINPROGRESS) /* expected for nonready peer */
242 { /* queue the data */
243 if ( (rc = send(sock, fastopen->data, fastopen->len, 0)) < 0
244 && errno == EINPROGRESS) /* expected for nonready peer */
245 rc = 0;
246 }
a2673768
JH
247 else if(errno == EOPNOTSUPP)
248 {
249 DEBUG(D_transport)
250 debug_printf("Tried TCP Fast Open but apparently not enabled by sysctl\n");
0ab63f3d 251 goto legacy_connect;
a2673768 252 }
fb05276a
JH
253 }
254else
255#endif
0ab63f3d
JH
256 {
257legacy_connect:
258 if ((rc = connect(sock, s_ptr, s_len)) >= 0)
259 if ( fastopen && fastopen->data && fastopen->len
260 && send(sock, fastopen->data, fastopen->len, 0) < 0)
261 rc = -1;
262 }
fb05276a 263
059ec3d9
PH
264save_errno = errno;
265alarm(0);
266
267/* There is a testing facility for simulating a connection timeout, as I
268can't think of any other way of doing this. It converts a connection refused
75e0e026 269into a timeout if the timeout is set to 999999. */
059ec3d9 270
a39bd74d 271if (running_in_test_harness && save_errno == ECONNREFUSED && timeout == 999999)
059ec3d9 272 {
a39bd74d
JB
273 rc = -1;
274 save_errno = EINTR;
275 sigalrm_seen = TRUE;
059ec3d9
PH
276 }
277
278/* Success */
279
055e2cb4 280if (rc >= 0)
055e2cb4 281 return 0;
059ec3d9
PH
282
283/* A failure whose error code is "Interrupted system call" is in fact
284an externally applied timeout if the signal handler has been run. */
285
a39bd74d 286errno = save_errno == EINTR && sigalrm_seen ? ETIMEDOUT : save_errno;
059ec3d9
PH
287return -1;
288}
289
290
a6d4c44e
TF
291
292/*************************************************
293* Create connected socket to remote host *
294*************************************************/
295
b1f8e4f8
JH
296/* Create a socket and connect to host (name or number, ipv6 ok)
297 at one of port-range.
a6d4c44e 298
b1f8e4f8
JH
299Arguments:
300 type SOCK_DGRAM or SOCK_STREAM
301 af AF_INET6 or AF_INET for the socket type
302 address the remote address, in text form
303 portlo,porthi the remote port range
304 timeout a timeout
305 connhost if not NULL, host_item filled in with connection details
306 errstr pointer for allocated string on error
0ab63f3d 307XXX could add early-data support
b1f8e4f8
JH
308
309Return:
310 socket fd, or -1 on failure (having allocated an error string)
311*/
312int
313ip_connectedsocket(int type, const uschar * hostname, int portlo, int porthi,
314 int timeout, host_item * connhost, uschar ** errstr)
315{
316int namelen, port;
317host_item shost;
318host_item *h;
319int af = 0, fd, fd4 = -1, fd6 = -1;
0ab63f3d
JH
320blob * fastopen = tcp_fastopen_ok && type == SOCK_STREAM
321 ? &tcp_fastopen_nodata : NULL;
b1f8e4f8
JH
322
323shost.next = NULL;
324shost.address = NULL;
325shost.port = portlo;
326shost.mx = -1;
327
328namelen = Ustrlen(hostname);
329
330/* Anything enclosed in [] must be an IP address. */
331
332if (hostname[0] == '[' &&
333 hostname[namelen - 1] == ']')
334 {
af483912 335 uschar * host = string_copyn(hostname+1, namelen-2);
b1f8e4f8
JH
336 if (string_is_ip_address(host, NULL) == 0)
337 {
338 *errstr = string_sprintf("malformed IP address \"%s\"", hostname);
339 return -1;
340 }
341 shost.name = shost.address = host;
342 }
343
344/* Otherwise check for an unadorned IP address */
345
346else if (string_is_ip_address(hostname, NULL) != 0)
af483912 347 shost.name = shost.address = string_copyn(hostname, namelen);
b1f8e4f8
JH
348
349/* Otherwise lookup IP address(es) from the name */
350
351else
352 {
af483912 353 shost.name = string_copyn(hostname, namelen);
1f155f8e
JH
354 if (host_find_byname(&shost, NULL, HOST_FIND_QUALIFY_SINGLE,
355 NULL, FALSE) != HOST_FOUND)
b1f8e4f8
JH
356 {
357 *errstr = string_sprintf("no IP address found for host %s", shost.name);
358 return -1;
359 }
360 }
361
362/* Try to connect to the server - test each IP till one works */
363
fb05276a 364for (h = &shost; h; h = h->next)
b1f8e4f8 365 {
af483912
JH
366 fd = Ustrchr(h->address, ':') != 0
367 ? fd6 < 0 ? (fd6 = ip_socket(type, af = AF_INET6)) : fd6
368 : fd4 < 0 ? (fd4 = ip_socket(type, af = AF_INET )) : fd4;
b1f8e4f8
JH
369
370 if (fd < 0)
371 {
372 *errstr = string_sprintf("failed to create socket: %s", strerror(errno));
373 goto bad;
374 }
375
376 for(port = portlo; port <= porthi; port++)
6af7e591 377 if (ip_connect(fd, af, h->address, port, timeout, fastopen) == 0)
b1f8e4f8
JH
378 {
379 if (fd != fd6) close(fd6);
380 if (fd != fd4) close(fd4);
8a512ed5
JH
381 if (connhost)
382 {
b1f8e4f8
JH
383 h->port = port;
384 *connhost = *h;
385 connhost->next = NULL;
386 }
387 return fd;
388 }
389 }
390
a9764ac5
JH
391*errstr = string_sprintf("failed to connect to any address for %s: %s",
392 hostname, strerror(errno));
b1f8e4f8
JH
393
394bad:
395 close(fd4); close(fd6); return -1;
396}
397
059ec3d9 398
3e60dd41
JH
399int
400ip_tcpsocket(const uschar * hostport, uschar ** errstr, int tmo)
401{
a39bd74d
JB
402int scan;
403uschar hostname[256];
404unsigned int portlow, porthigh;
405
406/* extract host and port part */
407scan = sscanf(CS hostport, "%255s %u-%u", hostname, &portlow, &porthigh);
408if (scan != 3)
409 {
410 if (scan != 2)
411 {
412 *errstr = string_sprintf("invalid socket '%s'", hostport);
413 return -1;
3e60dd41 414 }
a39bd74d 415 porthigh = portlow;
3e60dd41
JH
416 }
417
a39bd74d
JB
418return ip_connectedsocket(SOCK_STREAM, hostname, portlow, porthigh,
419 tmo, NULL, errstr);
3e60dd41
JH
420}
421
422int
423ip_unixsocket(const uschar * path, uschar ** errstr)
424{
a39bd74d
JB
425int sock;
426struct sockaddr_un server;
3e60dd41 427
a39bd74d
JB
428if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
429 {
430 *errstr = US"can't open UNIX socket.";
431 return -1;
3e60dd41
JH
432 }
433
af483912 434callout_address = string_copy(path);
a39bd74d
JB
435server.sun_family = AF_UNIX;
436Ustrncpy(server.sun_path, path, sizeof(server.sun_path)-1);
437server.sun_path[sizeof(server.sun_path)-1] = '\0';
438if (connect(sock, (struct sockaddr *) &server, sizeof(server)) < 0)
439 {
440 int err = errno;
441 (void)close(sock);
442 *errstr = string_sprintf("unable to connect to UNIX socket (%s): %s",
443 path, strerror(err));
444 return -1;
445 }
446return sock;
3e60dd41
JH
447}
448
449int
450ip_streamsocket(const uschar * spec, uschar ** errstr, int tmo)
451{
a39bd74d
JB
452return *spec == '/'
453 ? ip_unixsocket(spec, errstr) : ip_tcpsocket(spec, errstr, tmo);
3e60dd41
JH
454}
455
059ec3d9
PH
456/*************************************************
457* Set keepalive on a socket *
458*************************************************/
459
460/* Can be called for both incoming and outgoing sockets.
461
462Arguments:
463 sock the socket
464 address the remote host address, for failure logging
465 torf true for outgoing connection, false for incoming
466
467Returns: nothing
468*/
469
470void
55414b25 471ip_keepalive(int sock, const uschar *address, BOOL torf)
059ec3d9
PH
472{
473int fodder = 1;
474if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE,
5903c6ff 475 US (&fodder), sizeof(fodder)) != 0)
059ec3d9
PH
476 log_write(0, LOG_MAIN, "setsockopt(SO_KEEPALIVE) on connection %s %s "
477 "failed: %s", torf? "to":"from", address, strerror(errno));
478}
479
480
481
482/*************************************************
483* Receive from a socket with timeout *
484*************************************************/
485
4e71661f 486/*
059ec3d9 487Arguments:
4e71661f
JH
488 fd the file descriptor
489 timeout the timeout, seconds
490Returns: TRUE => ready for i/o
491 FALSE => timed out, or other error
059ec3d9 492*/
4e71661f
JH
493BOOL
494fd_ready(int fd, int timeout)
059ec3d9
PH
495{
496fd_set select_inset;
19050083 497time_t start_recv = time(NULL);
c528cec4 498int time_left = timeout;
059ec3d9
PH
499int rc;
500
85ff3cf9 501if (time_left <= 0)
4e71661f
JH
502 {
503 errno = ETIMEDOUT;
504 return FALSE;
505 }
059ec3d9
PH
506/* Wait until the socket is ready */
507
a39bd74d 508do
059ec3d9 509 {
f2ed27cf 510 struct timeval tv = { .tv_sec = time_left, .tv_usec = 0 };
059ec3d9 511 FD_ZERO (&select_inset);
4e71661f 512 FD_SET (fd, &select_inset);
059ec3d9 513
0f0c8159 514 /*DEBUG(D_transport) debug_printf("waiting for data on fd\n");*/
4e71661f 515 rc = select(fd + 1, (SELECT_ARG2_TYPE *)&select_inset, NULL, NULL, &tv);
059ec3d9
PH
516
517 /* If some interrupt arrived, just retry. We presume this to be rare,
518 but it can happen (e.g. the SIGUSR1 signal sent by exiwhat causes
519 select() to exit).
520
521 Aug 2004: Somebody set up a cron job that ran exiwhat every 2 minutes, making
522 the interrupt not at all rare. Since the timeout is typically more than 2
523 minutes, the effect was to block the timeout completely. To prevent this
c528cec4
HSHR
524 happening again, we do an explicit time test and adjust the timeout
525 accordingly */
059ec3d9
PH
526
527 if (rc < 0 && errno == EINTR)
528 {
529 DEBUG(D_transport) debug_printf("EINTR while waiting for socket data\n");
85ff3cf9 530
c528cec4 531 /* Watch out, 'continue' jumps to the condition, not to the loops top */
85ff3cf9
HSHR
532 time_left = timeout - (time(NULL) - start_recv);
533 if (time_left > 0) continue;
059ec3d9
PH
534 }
535
059ec3d9
PH
536 if (rc <= 0)
537 {
538 errno = ETIMEDOUT;
4e71661f 539 return FALSE;
059ec3d9
PH
540 }
541
c528cec4
HSHR
542 /* Checking the FD_ISSET is not enough, if we're interrupted, the
543 select_inset may still contain the 'input'. */
059ec3d9 544 }
4bd6107d 545while (rc < 0 || !FD_ISSET(fd, &select_inset));
4e71661f
JH
546return TRUE;
547}
548
549/* The timeout is implemented using select(), and we loop to cover select()
550getting interrupted, and the possibility of select() returning with a positive
551result but no ready descriptor. Is this in fact possible?
552
553Arguments:
554 sock the socket
555 buffer to read into
556 bufsize the buffer size
557 timeout the timeout
558
559Returns: > 0 => that much data read
560 <= 0 on error or EOF; errno set - zero for EOF
561*/
562
563int
564ip_recv(int sock, uschar *buffer, int buffsize, int timeout)
565{
566int rc;
567
568if (!fd_ready(sock, timeout))
569 return -1;
059ec3d9
PH
570
571/* The socket is ready, read from it (via TLS if it's active). On EOF (i.e.
572close down of the connection), set errno to zero; otherwise leave it alone. */
573
574#ifdef SUPPORT_TLS
817d9f57
JH
575if (tls_out.active == sock)
576 rc = tls_read(FALSE, buffer, buffsize);
577else if (tls_in.active == sock)
578 rc = tls_read(TRUE, buffer, buffsize);
059ec3d9
PH
579else
580#endif
581 rc = recv(sock, buffer, buffsize, 0);
582
583if (rc > 0) return rc;
584if (rc == 0) errno = 0;
585return -1;
586}
587
588
9e4f5962
PP
589
590
13363eba
PP
591/*************************************************
592* Lookup address family of potential socket *
593*************************************************/
594
595/* Given a file-descriptor, check to see if it's a socket and, if so,
596return the address family; detects IPv4 vs IPv6. If not a socket then
597return -1.
598
599The value 0 is typically AF_UNSPEC, which should not be seen on a connected
600fd. If the return is -1, the errno will be from getsockname(); probably
601ENOTSOCK or ECONNRESET.
602
603Arguments: socket-or-not fd
604Returns: address family or -1
605*/
606
607int
608ip_get_address_family(int fd)
609{
610struct sockaddr_storage ss;
611socklen_t sslen = sizeof(ss);
612
613if (getsockname(fd, (struct sockaddr *) &ss, &sslen) < 0)
614 return -1;
615
616return (int) ss.ss_family;
617}
618
619
620
621
9e4f5962
PP
622/*************************************************
623* Lookup DSCP settings for a socket *
624*************************************************/
625
626struct dscp_name_tableentry {
627 const uschar *name;
628 int value;
629};
630/* Keep both of these tables sorted! */
631static struct dscp_name_tableentry dscp_table[] = {
632#ifdef IPTOS_DSCP_AF11
36a3ae5f
PP
633 { CUS"af11", IPTOS_DSCP_AF11 },
634 { CUS"af12", IPTOS_DSCP_AF12 },
635 { CUS"af13", IPTOS_DSCP_AF13 },
636 { CUS"af21", IPTOS_DSCP_AF21 },
637 { CUS"af22", IPTOS_DSCP_AF22 },
638 { CUS"af23", IPTOS_DSCP_AF23 },
639 { CUS"af31", IPTOS_DSCP_AF31 },
640 { CUS"af32", IPTOS_DSCP_AF32 },
641 { CUS"af33", IPTOS_DSCP_AF33 },
642 { CUS"af41", IPTOS_DSCP_AF41 },
643 { CUS"af42", IPTOS_DSCP_AF42 },
644 { CUS"af43", IPTOS_DSCP_AF43 },
645 { CUS"ef", IPTOS_DSCP_EF },
9e4f5962
PP
646#endif
647#ifdef IPTOS_LOWCOST
36a3ae5f 648 { CUS"lowcost", IPTOS_LOWCOST },
9e4f5962 649#endif
36a3ae5f 650 { CUS"lowdelay", IPTOS_LOWDELAY },
9e4f5962 651#ifdef IPTOS_MINCOST
36a3ae5f 652 { CUS"mincost", IPTOS_MINCOST },
9e4f5962 653#endif
36a3ae5f
PP
654 { CUS"reliability", IPTOS_RELIABILITY },
655 { CUS"throughput", IPTOS_THROUGHPUT }
9e4f5962
PP
656};
657static int dscp_table_size =
658 sizeof(dscp_table) / sizeof(struct dscp_name_tableentry);
659
660/* DSCP values change by protocol family, and so do the options used for
2a1b36b3
PP
661setsockopt(); this utility does all the lookups. It takes an unexpanded
662option string, expands it, strips off affix whitespace, then checks if it's
663a number. If all of what's left is a number, then that's how the option will
664be parsed and success/failure is a range check. If it's not all a number,
665then it must be a supported keyword.
9e4f5962
PP
666
667Arguments:
668 dscp_name a string, so far unvalidated
669 af address_family in use
670 level setsockopt level to use
671 optname setsockopt name to use
672 dscp_value value for dscp_name
673
674Returns: TRUE if okay to setsockopt(), else FALSE
2a1b36b3
PP
675
676*level and *optname may be set even if FALSE is returned
9e4f5962
PP
677*/
678
679BOOL
680dscp_lookup(const uschar *dscp_name, int af,
681 int *level, int *optname, int *dscp_value)
682{
2a1b36b3 683uschar *dscp_lookup, *p;
9e4f5962 684int first, last;
2a1b36b3 685long rawlong;
9e4f5962
PP
686
687if (af == AF_INET)
688 {
689 *level = IPPROTO_IP;
690 *optname = IP_TOS;
691 }
bb7b9411 692#if HAVE_IPV6 && defined(IPV6_TCLASS)
9e4f5962
PP
693else if (af == AF_INET6)
694 {
695 *level = IPPROTO_IPV6;
696 *optname = IPV6_TCLASS;
697 }
b301a50b 698#endif
9e4f5962
PP
699else
700 {
701 DEBUG(D_transport)
702 debug_printf("Unhandled address family %d in dscp_lookup()\n", af);
703 return FALSE;
704 }
705if (!dscp_name)
706 {
707 DEBUG(D_transport)
708 debug_printf("[empty DSCP]\n");
709 return FALSE;
710 }
711dscp_lookup = expand_string(US dscp_name);
712if (dscp_lookup == NULL || *dscp_lookup == '\0')
713 return FALSE;
714
2a1b36b3
PP
715p = dscp_lookup + Ustrlen(dscp_lookup) - 1;
716while (isspace(*p)) *p-- = '\0';
717while (isspace(*dscp_lookup) && dscp_lookup < p) dscp_lookup++;
718if (*dscp_lookup == '\0')
719 return FALSE;
720
721rawlong = Ustrtol(dscp_lookup, &p, 0);
722if (p != dscp_lookup && *p == '\0')
723 {
724 /* We have six bits available, which will end up shifted to fit in 0xFC mask.
725 RFC 2597 defines the values unshifted. */
726 if (rawlong < 0 || rawlong > 0x3F)
727 {
728 DEBUG(D_transport)
729 debug_printf("DSCP value %ld out of range, ignored.\n", rawlong);
730 return FALSE;
731 }
732 *dscp_value = rawlong << 2;
733 return TRUE;
734 }
735
9e4f5962
PP
736first = 0;
737last = dscp_table_size;
738while (last > first)
739 {
740 int middle = (first + last)/2;
741 int c = Ustrcmp(dscp_lookup, dscp_table[middle].name);
742 if (c == 0)
743 {
744 *dscp_value = dscp_table[middle].value;
745 return TRUE;
746 }
747 else if (c > 0)
9e4f5962 748 first = middle + 1;
9e4f5962 749 else
9e4f5962 750 last = middle;
9e4f5962
PP
751 }
752return FALSE;
753}
754
36a3ae5f
PP
755void
756dscp_list_to_stream(FILE *stream)
757{
758int i;
759for (i=0; i < dscp_table_size; ++i)
760 fprintf(stream, "%s\n", dscp_table[i].name);
761}
762
9e4f5962 763
059ec3d9 764/* End of ip.c */
8a512ed5
JH
765/* vi: aw ai sw=2
766*/