relicense to ISC
[project/uhttpd.git] / listen.c
1 /*
2 * uhttpd - Tiny single-threaded httpd
3 *
4 * Copyright (C) 2010-2013 Jo-Philipp Wich <xm@subsignal.org>
5 * Copyright (C) 2013 Felix Fietkau <nbd@openwrt.org>
6 *
7 * Permission to use, copy, modify, and/or distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20 #include <sys/types.h>
21 #include <sys/socket.h>
22 #include <netinet/tcp.h>
23 #include <netdb.h>
24
25 #include "uhttpd.h"
26
27 struct listener {
28 struct list_head list;
29 struct uloop_fd fd;
30 int socket;
31 int n_clients;
32 struct sockaddr_in6 addr;
33 bool tls;
34 bool blocked;
35 };
36
37 static LIST_HEAD(listeners);
38 static int n_blocked;
39
40 void uh_close_listen_fds(void)
41 {
42 struct listener *l;
43
44 list_for_each_entry(l, &listeners, list)
45 close(l->fd.fd);
46 }
47
48 static void uh_block_listener(struct listener *l)
49 {
50 uloop_fd_delete(&l->fd);
51 n_blocked++;
52 l->blocked = true;
53 }
54
55 void uh_unblock_listeners(void)
56 {
57 struct listener *l;
58
59 if ((!n_blocked && conf.max_requests) ||
60 n_clients >= conf.max_requests)
61 return;
62
63 list_for_each_entry(l, &listeners, list) {
64 if (!l->blocked)
65 continue;
66
67 l->fd.cb(&l->fd, ULOOP_READ);
68 if (n_clients >= conf.max_requests)
69 break;
70
71 n_blocked--;
72 l->blocked = false;
73 uloop_fd_add(&l->fd, ULOOP_READ);
74 }
75 }
76
77 static void listener_cb(struct uloop_fd *fd, unsigned int events)
78 {
79 struct listener *l = container_of(fd, struct listener, fd);
80
81 while (1) {
82 if (!uh_accept_client(fd->fd, l->tls))
83 break;
84 }
85
86 if (conf.max_requests && n_clients >= conf.max_requests)
87 uh_block_listener(l);
88 }
89
90 void uh_setup_listeners(void)
91 {
92 struct listener *l;
93 int yes = 1;
94
95 list_for_each_entry(l, &listeners, list) {
96 int sock = l->fd.fd;
97
98 /* TCP keep-alive */
99 if (conf.tcp_keepalive > 0) {
100 #ifdef linux
101 int tcp_ka_idl, tcp_ka_int, tcp_ka_cnt;
102
103 tcp_ka_idl = 1;
104 tcp_ka_cnt = 3;
105 tcp_ka_int = conf.tcp_keepalive;
106
107 setsockopt(sock, SOL_TCP, TCP_KEEPIDLE, &tcp_ka_idl, sizeof(tcp_ka_idl));
108 setsockopt(sock, SOL_TCP, TCP_KEEPINTVL, &tcp_ka_int, sizeof(tcp_ka_int));
109 setsockopt(sock, SOL_TCP, TCP_KEEPCNT, &tcp_ka_cnt, sizeof(tcp_ka_cnt));
110 #endif
111
112 setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes));
113 }
114
115 l->fd.cb = listener_cb;
116 uloop_fd_add(&l->fd, ULOOP_READ);
117 }
118 }
119
120 int uh_socket_bind(const char *host, const char *port, bool tls)
121 {
122 int sock = -1;
123 int yes = 1;
124 int status;
125 int bound = 0;
126 struct listener *l = NULL;
127 struct addrinfo *addrs = NULL, *p = NULL;
128 static struct addrinfo hints = {
129 .ai_family = AF_UNSPEC,
130 .ai_socktype = SOCK_STREAM,
131 .ai_flags = AI_PASSIVE,
132 };
133
134 if ((status = getaddrinfo(host, port, &hints, &addrs)) != 0) {
135 fprintf(stderr, "getaddrinfo(): %s\n", gai_strerror(status));
136 return 0;
137 }
138
139 /* try to bind a new socket to each found address */
140 for (p = addrs; p; p = p->ai_next) {
141 /* get the socket */
142 sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
143 if (sock < 0) {
144 perror("socket()");
145 goto error;
146 }
147
148 /* "address already in use" */
149 if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes))) {
150 perror("setsockopt()");
151 goto error;
152 }
153
154 /* required to get parallel v4 + v6 working */
155 if (p->ai_family == AF_INET6 &&
156 setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes)) < 0) {
157 perror("setsockopt()");
158 goto error;
159 }
160
161 /* bind */
162 if (bind(sock, p->ai_addr, p->ai_addrlen) < 0) {
163 perror("bind()");
164 goto error;
165 }
166
167 /* listen */
168 if (listen(sock, UH_LIMIT_CLIENTS) < 0) {
169 perror("listen()");
170 goto error;
171 }
172
173 fd_cloexec(sock);
174
175 l = calloc(1, sizeof(*l));
176 if (!l)
177 goto error;
178
179 l->fd.fd = sock;
180 l->tls = tls;
181 list_add_tail(&l->list, &listeners);
182 bound++;
183
184 continue;
185
186 error:
187 if (sock > -1)
188 close(sock);
189 }
190
191 freeaddrinfo(addrs);
192
193 return bound;
194 }