]> granicus.if.org Git - apache/blob - server/listen.c
* server/listen.c (open_listeners): Fix logic error caught by
[apache] / server / listen.c
1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2  * contributor license agreements.  See the NOTICE file distributed with
3  * this work for additional information regarding copyright ownership.
4  * The ASF licenses this file to You under the Apache License, Version 2.0
5  * (the "License"); you may not use this file except in compliance with
6  * the License.  You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include "apr_network_io.h"
18 #include "apr_strings.h"
19
20 #define APR_WANT_STRFUNC
21 #include "apr_want.h"
22
23 #include "ap_config.h"
24 #include "httpd.h"
25 #include "http_config.h"
26 #include "http_core.h"
27 #include "ap_listen.h"
28 #include "http_log.h"
29 #include "mpm_common.h"
30
31 /* we know core's module_index is 0 */
32 #undef APLOG_MODULE_INDEX
33 #define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX
34
35 AP_DECLARE_DATA ap_listen_rec *ap_listeners = NULL;
36
37 static ap_listen_rec *old_listeners;
38 static int ap_listenbacklog;
39 static int send_buffer_size;
40 static int receive_buffer_size;
41
42 /* TODO: make_sock is just begging and screaming for APR abstraction */
43 static apr_status_t make_sock(apr_pool_t *p, ap_listen_rec *server)
44 {
45     apr_socket_t *s = server->sd;
46     int one = 1;
47 #if APR_HAVE_IPV6
48 #ifdef AP_ENABLE_V4_MAPPED
49     int v6only_setting = 0;
50 #else
51     int v6only_setting = 1;
52 #endif
53 #endif
54     apr_status_t stat;
55
56 #ifndef WIN32
57     stat = apr_socket_opt_set(s, APR_SO_REUSEADDR, one);
58     if (stat != APR_SUCCESS && stat != APR_ENOTIMPL) {
59         ap_log_perror(APLOG_MARK, APLOG_CRIT, stat, p,
60                       "make_sock: for address %pI, apr_socket_opt_set: (SO_REUSEADDR)",
61                       server->bind_addr);
62         apr_socket_close(s);
63         return stat;
64     }
65 #endif
66
67     stat = apr_socket_opt_set(s, APR_SO_KEEPALIVE, one);
68     if (stat != APR_SUCCESS && stat != APR_ENOTIMPL) {
69         ap_log_perror(APLOG_MARK, APLOG_CRIT, stat, p,
70                       "make_sock: for address %pI, apr_socket_opt_set: (SO_KEEPALIVE)",
71                       server->bind_addr);
72         apr_socket_close(s);
73         return stat;
74     }
75
76 #if APR_HAVE_IPV6
77     if (server->bind_addr->family == APR_INET6) {
78         stat = apr_socket_opt_set(s, APR_IPV6_V6ONLY, v6only_setting);
79         if (stat != APR_SUCCESS && stat != APR_ENOTIMPL) {
80             ap_log_perror(APLOG_MARK, APLOG_CRIT, stat, p,
81                           "make_sock: for address %pI, apr_socket_opt_set: "
82                           "(IPV6_V6ONLY)",
83                           server->bind_addr);
84             apr_socket_close(s);
85             return stat;
86         }
87     }
88 #endif
89
90     /*
91      * To send data over high bandwidth-delay connections at full
92      * speed we must force the TCP window to open wide enough to keep the
93      * pipe full.  The default window size on many systems
94      * is only 4kB.  Cross-country WAN connections of 100ms
95      * at 1Mb/s are not impossible for well connected sites.
96      * If we assume 100ms cross-country latency,
97      * a 4kB buffer limits throughput to 40kB/s.
98      *
99      * To avoid this problem I've added the SendBufferSize directive
100      * to allow the web master to configure send buffer size.
101      *
102      * The trade-off of larger buffers is that more kernel memory
103      * is consumed.  YMMV, know your customers and your network!
104      *
105      * -John Heidemann <johnh@isi.edu> 25-Oct-96
106      *
107      * If no size is specified, use the kernel default.
108      */
109     if (send_buffer_size) {
110         stat = apr_socket_opt_set(s, APR_SO_SNDBUF,  send_buffer_size);
111         if (stat != APR_SUCCESS && stat != APR_ENOTIMPL) {
112             ap_log_perror(APLOG_MARK, APLOG_WARNING, stat, p,
113                           "make_sock: failed to set SendBufferSize for "
114                           "address %pI, using default",
115                           server->bind_addr);
116             /* not a fatal error */
117         }
118     }
119     if (receive_buffer_size) {
120         stat = apr_socket_opt_set(s, APR_SO_RCVBUF, receive_buffer_size);
121         if (stat != APR_SUCCESS && stat != APR_ENOTIMPL) {
122             ap_log_perror(APLOG_MARK, APLOG_WARNING, stat, p,
123                           "make_sock: failed to set ReceiveBufferSize for "
124                           "address %pI, using default",
125                           server->bind_addr);
126             /* not a fatal error */
127         }
128     }
129
130 #if APR_TCP_NODELAY_INHERITED
131     ap_sock_disable_nagle(s);
132 #endif
133
134     if ((stat = apr_socket_bind(s, server->bind_addr)) != APR_SUCCESS) {
135         ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_CRIT, stat, p,
136                       "make_sock: could not bind to address %pI",
137                       server->bind_addr);
138         apr_socket_close(s);
139         return stat;
140     }
141
142     if ((stat = apr_socket_listen(s, ap_listenbacklog)) != APR_SUCCESS) {
143         ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_ERR, stat, p,
144                       "make_sock: unable to listen for connections "
145                       "on address %pI",
146                       server->bind_addr);
147         apr_socket_close(s);
148         return stat;
149     }
150
151 #ifdef WIN32
152     /* I seriously doubt that this would work on Unix; I have doubts that
153      * it entirely solves the problem on Win32.  However, since setting
154      * reuseaddr on the listener -prior- to binding the socket has allowed
155      * us to attach to the same port as an already running instance of
156      * Apache, or even another web server, we cannot identify that this
157      * port was exclusively granted to this instance of Apache.
158      *
159      * So set reuseaddr, but do not attempt to do so until we have the
160      * parent listeners successfully bound.
161      */
162     stat = apr_socket_opt_set(s, APR_SO_REUSEADDR, one);
163     if (stat != APR_SUCCESS && stat != APR_ENOTIMPL) {
164         ap_log_perror(APLOG_MARK, APLOG_CRIT, stat, p,
165                     "make_sock: for address %pI, apr_socket_opt_set: (SO_REUSEADDR)",
166                      server->bind_addr);
167         apr_socket_close(s);
168         return stat;
169     }
170 #endif
171
172     server->sd = s;
173     server->active = 1;
174
175     server->accept_func = NULL;
176
177     return APR_SUCCESS;
178 }
179
180 static const char* find_accf_name(server_rec *s, const char *proto)
181 {
182     const char* accf;
183     core_server_config *conf = ap_get_core_module_config(s->module_config);
184     if (!proto) {
185         return NULL;
186     }
187
188     accf = apr_table_get(conf->accf_map, proto);
189
190     if (accf && !strcmp("none", accf)) {
191         return NULL;
192     }
193
194     return accf;
195 }
196
197 static void ap_apply_accept_filter(apr_pool_t *p, ap_listen_rec *lis,
198                                            server_rec *server)
199 {
200     apr_socket_t *s = lis->sd;
201     const char *accf;
202     apr_status_t rv;
203     const char *proto;
204
205     proto = lis->protocol;
206
207     if (!proto) {
208         proto = ap_get_server_protocol(server);
209     }
210
211
212     accf = find_accf_name(server, proto);
213
214     if (accf) {
215 #if APR_HAS_SO_ACCEPTFILTER
216         rv = apr_socket_accept_filter(s, apr_pstrdup(p, accf),
217                                       apr_pstrdup(p,""));
218         if (rv != APR_SUCCESS && !APR_STATUS_IS_ENOTIMPL(rv)) {
219             ap_log_perror(APLOG_MARK, APLOG_WARNING, rv, p,
220                           "Failed to enable the '%s' Accept Filter",
221                           accf);
222         }
223 #else
224         rv = apr_socket_opt_set(s, APR_TCP_DEFER_ACCEPT, 30);
225         if (rv != APR_SUCCESS && !APR_STATUS_IS_ENOTIMPL(rv)) {
226             ap_log_perror(APLOG_MARK, APLOG_WARNING, rv, p,
227                               "Failed to enable APR_TCP_DEFER_ACCEPT");
228         }
229 #endif
230     }
231 }
232
233 static apr_status_t close_listeners_on_exec(void *v)
234 {
235     ap_close_listeners();
236     return APR_SUCCESS;
237 }
238
239 static const char *alloc_listener(process_rec *process, char *addr,
240                                   apr_port_t port, const char* proto,
241                                   void *dummy)
242 {
243     ap_listen_rec **walk, *last;
244     apr_status_t status;
245     apr_sockaddr_t *sa;
246     int found_listener = 0;
247
248     /* see if we've got an old listener for this address:port */
249     for (walk = &old_listeners; *walk;) {
250         sa = (*walk)->bind_addr;
251         /* Some listeners are not real so they will not have a bind_addr. */
252         if (sa) {
253             ap_listen_rec *new;
254             apr_port_t oldport;
255
256             oldport = sa->port;
257             /* If both ports are equivalent, then if their names are equivalent,
258              * then we will re-use the existing record.
259              */
260             if (port == oldport &&
261                 ((!addr && !sa->hostname) ||
262                  ((addr && sa->hostname) && !strcmp(sa->hostname, addr)))) {
263                 new = *walk;
264                 *walk = new->next;
265                 new->next = ap_listeners;
266                 ap_listeners = new;
267                 found_listener = 1;
268                 continue;
269             }
270         }
271
272         walk = &(*walk)->next;
273     }
274
275     if (found_listener) {
276         if (ap_listeners->slave != dummy) {
277             return "Cannot define a slave on the same IP:port as a Listener";
278         }
279         return NULL;
280     }
281
282     if ((status = apr_sockaddr_info_get(&sa, addr, APR_UNSPEC, port, 0,
283                                         process->pool))
284         != APR_SUCCESS) {
285         ap_log_perror(APLOG_MARK, APLOG_CRIT, status, process->pool,
286                       "alloc_listener: failed to set up sockaddr for %s",
287                       addr);
288         return "Listen setup failed";
289     }
290
291     /* Initialize to our last configured ap_listener. */
292     last = ap_listeners;
293     while (last && last->next) {
294         last = last->next;
295     }
296
297     while (sa) {
298         ap_listen_rec *new;
299
300         /* this has to survive restarts */
301         new = apr_palloc(process->pool, sizeof(ap_listen_rec));
302         new->active = 0;
303         new->next = 0;
304         new->bind_addr = sa;
305         new->protocol = apr_pstrdup(process->pool, proto);
306
307         /* Go to the next sockaddr. */
308         sa = sa->next;
309
310         status = apr_socket_create(&new->sd, new->bind_addr->family,
311                                     SOCK_STREAM, 0, process->pool);
312
313 #if APR_HAVE_IPV6
314         /* What could happen is that we got an IPv6 address, but this system
315          * doesn't actually support IPv6.  Try the next address.
316          */
317         if (status != APR_SUCCESS && !addr &&
318             new->bind_addr->family == APR_INET6) {
319             continue;
320         }
321 #endif
322         if (status != APR_SUCCESS) {
323             ap_log_perror(APLOG_MARK, APLOG_CRIT, status, process->pool,
324                           "alloc_listener: failed to get a socket for %s",
325                           addr);
326             return "Listen setup failed";
327         }
328
329         /* We need to preserve the order returned by getaddrinfo() */
330         if (last == NULL) {
331             ap_listeners = last = new;
332         } else {
333             last->next = new;
334             last = new;
335         }
336         new->slave = dummy;
337     }
338
339     return NULL;
340 }
341 /* Evaluates to true if the (apr_sockaddr_t *) addr argument is the
342  * IPv4 match-any-address, 0.0.0.0. */
343 #define IS_INADDR_ANY(addr) ((addr)->family == APR_INET \
344                              && (addr)->sa.sin.sin_addr.s_addr == INADDR_ANY)
345
346 /* Evaluates to true if the (apr_sockaddr_t *) addr argument is the
347  * IPv6 match-any-address, [::]. */
348 #define IS_IN6ADDR_ANY(addr) ((addr)->family == APR_INET6 \
349                               && IN6_IS_ADDR_UNSPECIFIED(&(addr)->sa.sin6.sin6_addr))
350
351 /**
352  * Create, open, listen, and bind all sockets.
353  * @param process The process record for the currently running server
354  * @return The number of open sockets
355  */
356 static int open_listeners(apr_pool_t *pool)
357 {
358     ap_listen_rec *lr;
359     ap_listen_rec *next;
360     ap_listen_rec *previous;
361     int num_open;
362     const char *userdata_key = "ap_open_listeners";
363     void *data;
364 #if AP_NONBLOCK_WHEN_MULTI_LISTEN
365     int use_nonblock;
366 #endif
367
368     /* Don't allocate a default listener.  If we need to listen to a
369      * port, then the user needs to have a Listen directive in their
370      * config file.
371      */
372     num_open = 0;
373     previous = NULL;
374     for (lr = ap_listeners; lr; previous = lr, lr = lr->next) {
375         if (lr->active) {
376             ++num_open;
377         }
378         else {
379 #if APR_HAVE_IPV6
380             ap_listen_rec *cur;
381             int v6only_setting;
382             int skip = 0;
383
384             /* If we have the unspecified IPv4 address (0.0.0.0) and
385              * the unspecified IPv6 address (::) is next, we need to
386              * swap the order of these in the list. We always try to
387              * bind to IPv6 first, then IPv4, since an IPv6 socket
388              * might be able to receive IPv4 packets if V6ONLY is not
389              * enabled, but never the other way around.
390              * Note: In some configurations, the unspecified IPv6 address
391              * could be even later in the list.  This logic only corrects
392              * the situation where it is next in the list, such as when
393              * apr_sockaddr_info_get() returns an IPv4 and an IPv6 address,
394              * in that order.
395              */
396             if (lr->next != NULL
397                 && IS_INADDR_ANY(lr->bind_addr)
398                 && lr->bind_addr->port == lr->next->bind_addr->port
399                 && IS_IN6ADDR_ANY(lr->next->bind_addr)) {
400                 /* Exchange lr and lr->next */
401                 next = lr->next;
402                 lr->next = next->next;
403                 next->next = lr;
404                 if (previous) {
405                     previous->next = next;
406                 }
407                 else {
408                     ap_listeners = next;
409                 }
410                 lr = next;
411             }
412
413             /* If we are trying to bind to 0.0.0.0 and a previous listener
414              * was :: on the same port and in turn that socket does not have
415              * the IPV6_V6ONLY flag set; we must skip the current attempt to
416              * listen (which would generate an error). IPv4 will be handled
417              * on the established IPv6 socket.
418              */
419             if (IS_INADDR_ANY(lr->bind_addr) && previous) {
420                 for (cur = ap_listeners; cur != lr; cur = cur->next) {
421                     if (lr->bind_addr->port == cur->bind_addr->port
422                         && IS_IN6ADDR_ANY(cur->bind_addr)
423                         && apr_socket_opt_get(cur->sd, APR_IPV6_V6ONLY,
424                                               &v6only_setting) == APR_SUCCESS
425                         && v6only_setting == 0) {
426
427                         /* Remove the current listener from the list */
428                         previous->next = lr->next;
429                         lr = previous; /* maintain current value of previous after
430                                         * post-loop expression is evaluated
431                                         */
432                         skip = 1;
433                         break;
434                     }
435                 }
436                 if (skip) {
437                     continue;
438                 }
439             }
440 #endif
441             if (make_sock(pool, lr) == APR_SUCCESS) {
442                 ++num_open;
443             }
444             else {
445 #if APR_HAVE_IPV6
446                 /* If we tried to bind to ::, and the next listener is
447                  * on 0.0.0.0 with the same port, don't give a fatal
448                  * error. The user will still get a warning from make_sock
449                  * though.
450                  */
451                 if (lr->next != NULL
452                     && IS_IN6ADDR_ANY(lr->bind_addr)
453                     && lr->bind_addr->port == lr->next->bind_addr->port
454                     && IS_INADDR_ANY(lr->next->bind_addr)) {
455
456                     /* Remove the current listener from the list */
457                     if (previous) {
458                         previous->next = lr->next;
459                     }
460                     else {
461                         ap_listeners = lr->next;
462                     }
463
464                     /* Although we've removed ourselves from the list,
465                      * we need to make sure that the next iteration won't
466                      * consider "previous" a working IPv6 '::' socket.
467                      * Changing the family is enough to make sure the
468                      * conditions before make_sock() fail.
469                      */
470                     lr->bind_addr->family = AF_INET;
471
472                     continue;
473                 }
474 #endif
475                 /* fatal error */
476                 return -1;
477             }
478         }
479     }
480
481     /* close the old listeners */
482     for (lr = old_listeners; lr; lr = next) {
483         apr_socket_close(lr->sd);
484         lr->active = 0;
485         next = lr->next;
486     }
487     old_listeners = NULL;
488
489 #if AP_NONBLOCK_WHEN_MULTI_LISTEN
490     /* if multiple listening sockets, make them non-blocking so that
491      * if select()/poll() reports readability for a reset connection that
492      * is already forgotten about by the time we call accept, we won't
493      * be hung until another connection arrives on that port
494      */
495     use_nonblock = (ap_listeners && ap_listeners->next);
496     for (lr = ap_listeners; lr; lr = lr->next) {
497         apr_status_t status;
498
499         status = apr_socket_opt_set(lr->sd, APR_SO_NONBLOCK, use_nonblock);
500         if (status != APR_SUCCESS) {
501             ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_ERR, status, pool,
502                           "unable to control socket non-blocking status");
503             return -1;
504         }
505     }
506 #endif /* AP_NONBLOCK_WHEN_MULTI_LISTEN */
507
508     /* we come through here on both passes of the open logs phase
509      * only register the cleanup once... otherwise we try to close
510      * listening sockets twice when cleaning up prior to exec
511      */
512     apr_pool_userdata_get(&data, userdata_key, pool);
513     if (!data) {
514         apr_pool_userdata_set((const void *)1, userdata_key,
515                               apr_pool_cleanup_null, pool);
516         apr_pool_cleanup_register(pool, NULL, apr_pool_cleanup_null,
517                                   close_listeners_on_exec);
518     }
519
520     return num_open ? 0 : -1;
521 }
522
523 AP_DECLARE(int) ap_setup_listeners(server_rec *s)
524 {
525     server_rec *ls;
526     server_addr_rec *addr;
527     ap_listen_rec *lr;
528     int num_listeners = 0;
529     const char* proto;
530     int found;
531
532     for (ls = s; ls; ls = ls->next) {
533         proto = ap_get_server_protocol(ls);
534         if (!proto) {
535             found = 0;
536             /* No protocol was set for this vhost,
537              * use the default for this listener.
538              */
539             for (addr = ls->addrs; addr && !found; addr = addr->next) {
540                 for (lr = ap_listeners; lr; lr = lr->next) {
541                     if (apr_sockaddr_equal(lr->bind_addr, addr->host_addr) &&
542                         lr->bind_addr->port == addr->host_port) {
543                         ap_set_server_protocol(ls, lr->protocol);
544                         found = 1;
545                         break;
546                     }
547                 }
548             }
549
550             if (!found) {
551                 /* TODO: set protocol defaults per-Port, eg 25=smtp */
552                 ap_set_server_protocol(ls, "http");
553             }
554         }
555     }
556
557     if (open_listeners(s->process->pool)) {
558        return 0;
559     }
560
561     for (lr = ap_listeners; lr; lr = lr->next) {
562         num_listeners++;
563         found = 0;
564         for (ls = s; ls && !found; ls = ls->next) {
565             for (addr = ls->addrs; addr && !found; addr = addr->next) {
566                 if (apr_sockaddr_equal(lr->bind_addr, addr->host_addr) &&
567                     lr->bind_addr->port == addr->host_port) {
568                     found = 1;
569                     ap_apply_accept_filter(s->process->pool, lr, ls);
570                 }
571             }
572         }
573
574         if (!found) {
575             ap_apply_accept_filter(s->process->pool, lr, s);
576         }
577     }
578
579     return num_listeners;
580 }
581
582 AP_DECLARE_NONSTD(void) ap_close_listeners(void)
583 {
584     ap_listen_rec *lr;
585
586     for (lr = ap_listeners; lr; lr = lr->next) {
587         apr_socket_close(lr->sd);
588         lr->active = 0;
589     }
590 }
591 AP_DECLARE_NONSTD(int) ap_close_selected_listeners(ap_slave_t *slave)
592 {
593     ap_listen_rec *lr;
594     int n = 0;
595
596     for (lr = ap_listeners; lr; lr = lr->next) {
597         if (lr->slave != slave) {
598             apr_socket_close(lr->sd);
599             lr->active = 0;
600         }
601         else {
602             ++n;
603         }
604     }
605     return n;
606 }
607
608 AP_DECLARE(void) ap_listen_pre_config(void)
609 {
610     old_listeners = ap_listeners;
611     ap_listeners = NULL;
612     ap_listenbacklog = DEFAULT_LISTENBACKLOG;
613 }
614
615 /* Hack: populate an extra field
616  * When this gets called from a Listen directive, dummy is null.
617  * So we can use non-null dummy to pass a data pointer without conflict
618  */
619 AP_DECLARE_NONSTD(const char *) ap_set_listener(cmd_parms *cmd, void *dummy,
620                                                 int argc, char *const argv[])
621 {
622     char *host, *scope_id, *proto;
623     apr_port_t port;
624     apr_status_t rv;
625     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
626
627     if (err != NULL) {
628         return err;
629     }
630
631     if (argc < 1 || argc > 2) {
632         return "Listen requires 1 or 2 arguments.";
633     }
634
635     rv = apr_parse_addr_port(&host, &scope_id, &port, argv[0], cmd->pool);
636     if (rv != APR_SUCCESS) {
637         return "Invalid address or port";
638     }
639
640     if (host && !strcmp(host, "*")) {
641         host = NULL;
642     }
643
644     if (scope_id) {
645         /* XXX scope id support is useful with link-local IPv6 addresses */
646         return "Scope id is not supported";
647     }
648
649     if (!port) {
650         return "Port must be specified";
651     }
652
653     if (argc != 2) {
654         if (port == 443) {
655             proto = "https";
656         } else {
657             proto = "http";
658         }
659     }
660     else {
661         proto = apr_pstrdup(cmd->pool, argv[1]);
662         ap_str_tolower(proto);
663     }
664
665     return alloc_listener(cmd->server->process, host, port, proto, dummy);
666 }
667
668 AP_DECLARE_NONSTD(const char *) ap_set_listenbacklog(cmd_parms *cmd,
669                                                      void *dummy,
670                                                      const char *arg)
671 {
672     int b;
673     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
674
675     if (err != NULL) {
676         return err;
677     }
678
679     b = atoi(arg);
680     if (b < 1) {
681         return "ListenBacklog must be > 0";
682     }
683
684     ap_listenbacklog = b;
685     return NULL;
686 }
687
688 AP_DECLARE_NONSTD(const char *) ap_set_send_buffer_size(cmd_parms *cmd,
689                                                         void *dummy,
690                                                         const char *arg)
691 {
692     int s = atoi(arg);
693     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
694
695     if (err != NULL) {
696         return err;
697     }
698
699     if (s < 512 && s != 0) {
700         return "SendBufferSize must be >= 512 bytes, or 0 for system default.";
701     }
702
703     send_buffer_size = s;
704     return NULL;
705 }
706
707 AP_DECLARE_NONSTD(const char *) ap_set_receive_buffer_size(cmd_parms *cmd,
708                                                            void *dummy,
709                                                            const char *arg)
710 {
711     int s = atoi(arg);
712     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
713
714     if (err != NULL) {
715         return err;
716     }
717
718     if (s < 512 && s != 0) {
719         return "ReceiveBufferSize must be >= 512 bytes, or 0 for system default.";
720     }
721
722     receive_buffer_size = s;
723     return NULL;
724 }