aboutsummaryrefslogtreecommitdiff
path: root/src/microhttpd/mhd_send.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/microhttpd/mhd_send.c')
-rw-r--r--src/microhttpd/mhd_send.c637
1 files changed, 637 insertions, 0 deletions
diff --git a/src/microhttpd/mhd_send.c b/src/microhttpd/mhd_send.c
new file mode 100644
index 00000000..928e92cb
--- /dev/null
+++ b/src/microhttpd/mhd_send.c
@@ -0,0 +1,637 @@
1/*
2 This file is part of libmicrohttpd
3 Copyright (C) 2019 ng0 <ng0@n0.is>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with this library; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
19 */
20
21/**
22 * @file microhttpd/mhd_send.c
23 * @brief Implementation of send() wrappers.
24 * @author ng0 (N. Gillmann)
25 * @author Christian Grothoff
26 * @author Evgeny Grin
27 */
28
29/* Worth considering for future improvements and additions:
30 * NetBSD has no sendfile or sendfile64. The way to work
31 * with this seems to be to mmap the file and write(2) as
32 * large a chunk as possible to the socket. Alternatively,
33 * use madvise(..., MADV_SEQUENTIAL). */
34
35/* Functions to be used in: send_param_adapter, MHD_send_
36 * and every place where sendfile(), sendfile64(), setsockopt()
37 * are used. */
38
39#include "mhd_send.h"
40
41/**
42 * Handle setsockopt calls.
43 *
44 * @param connection the MHD_Connection structure
45 * @param want_cork cork state, boolean
46 */
47static void
48pre_cork_setsockopt (struct MHD_Connection *connection,
49 bool want_cork)
50{
51#if HAVE_MSG_MORE
52 /* We use the MSG_MORE option for corking, no need for extra syscalls! */
53#elif defined(MHD_TCP_CORK_NOPUSH)
54 int ret;
55
56 /* If sk_cork_on is already what we pass in, return. */
57 if (connection->sk_cork_on == want_cork)
58 {
59 /* nothing to do, success! */
60 return;
61 }
62 if (! want_cork)
63 return; /* nothing to do *pre* syscall! */
64 ret = MHD_socket_cork_ (connection->socket_fd,
65 true);
66 if (0 == ret)
67 {
68 connection->sk_cork_on = true;
69 return;
70 }
71 switch (errno)
72 {
73 case ENOTSOCK:
74 /* FIXME: Could be we are talking to a pipe, maybe remember this
75 and avoid all setsockopt() in the future? */
76 break;
77 case EBADF:
78 /* FIXME: should we die hard here? */
79 break;
80 case EINVAL:
81 /* FIXME: optlen invalid, should at least log this, maybe die */
82 break;
83 case EFAULT:
84 /* wopsie, should at leats log this, FIXME: maybe die */
85#ifdef HAVE_MESSAGES
86 MHD_DLOG (daemon,
87 _("The addresss pointed to by optval is not a valid part of the process address space: %s\n"),
88 MHD_socket_last_strerr_());
89#endif
90 break;
91 case ENOPROTOOPT:
92 /* optlen unknown, should at least log this */
93#ifdef HAVE_MESSAGES
94 MHD_DLOG (daemon,
95 _("The option is unknown: %s\n"),
96 MHD_socket_last_strerr_());
97#endif
98 break;
99 default:
100 /* any others? man page does not list more... */
101 break;
102 }
103#else
104 /* CORK/NOPUSH/MSG_MORE do not exist on this platform,
105 so we must toggle Naggle's algorithm on/off instead
106 (otherwise we keep it always off) */
107 if (connection->sk_cork_on == want_cork)
108 {
109 /* nothing to do, success! */
110 return;
111 }
112 if ( (want_cork) &&
113 (0 == MHD_socket_set_nodelay_ (connection->socket_fd,
114 false)) )
115 connection->sk_cork_on = true;
116#endif
117}
118
119
120/**
121 * Handle setsockopt calls.
122 *
123 * @param connection the MHD_Connection structure
124 * @param want_cork cork state, boolean
125 */
126static void
127post_cork_setsockopt (struct MHD_Connection *connection,
128 bool want_cork)
129{
130#if HAVE_MSG_MORE
131 /* We use the MSG_MORE option for corking, no need for extra syscalls! */
132#elif defined(MHD_TCP_CORK_NOPUSH)
133 int ret;
134
135 /* If sk_cork_on is already what we pass in, return. */
136 if (connection->sk_cork_on == want_cork)
137 {
138 /* nothing to do, success! */
139 return;
140 }
141 if (want_cork)
142 return; /* nothing to do *post* syscall (in fact, we should never
143 get here, as sk_cork_on should have succeeded in the
144 pre-syscall) */
145 ret = MHD_socket_cork_ (connection->socket_fd,
146 false);
147 if (0 == ret)
148 {
149 connection->sk_cork_on = false;
150 return;
151 }
152 switch (errno)
153 {
154 case ENOTSOCK:
155 /* FIXME: Could be we are talking to a pipe, maybe remember this
156 and avoid all setsockopt() in the future? */
157 break;
158 case EBADF:
159 /* FIXME: should we die hard here? */
160 break;
161 case EINVAL:
162 /* FIXME: optlen invalid, should at least log this, maybe die */
163 break;
164 case EFAULT:
165 /* wopsie, should at leats log this, FIXME: maybe die */
166#ifdef HAVE_MESSAGES
167 MHD_DLOG (daemon,
168 _("The addresss pointed to by optval is not a valid part of the process address space: %s\n"),
169 MHD_socket_last_strerr_());
170#endif
171 break;
172 case ENOPROTOOPT:
173 /* optlen unknown, should at least log this */
174#ifdef HAVE_MESSAGES
175 MHD_DLOG (daemon,
176 _("The option is unknown: %s\n"),
177 MHD_socket_last_strerr_());
178#endif
179 break;
180 default:
181 /* any others? man page does not list more... */
182 break;
183 }
184#else
185 /* CORK/NOPUSH/MSG_MORE do not exist on this platform,
186 so we must toggle Naggle's algorithm on/off instead
187 (otherwise we keep it always off) */
188 if (connection->sk_cork_on == want_cork)
189 {
190 /* nothing to do, success! */
191 return;
192 }
193 if ( (! want_cork) &&
194 (0 == MHD_socket_set_nodelay_ (connection->socket_fd,
195 true)) )
196 connection->sk_cork_on = false;
197#endif
198}
199
200
201/**
202 * Send buffer on connection, and remember the current state of
203 * the socket options; only call setsockopt when absolutely
204 * necessary.
205 *
206 * @param connection the MHD_Connection structure
207 * @param buffer content of the buffer to send
208 * @param buffer_size the size of the buffer (in bytes)
209 * @param options the #MHD_SendSocketOptions enum,
210 * #MHD_SSO_NO_CORK: definitely no corking (use NODELAY, or explicitly disable cork),
211 * #MHD_SSO_MAY_CORK: should enable corking (use MSG_MORE, or explicitly enable cork),
212 * #MHD_SSO_HDR_CORK: consider tcpi_snd_mss and consider not corking for the header
213 * part if the size of the header is close to the MSS.
214 * Only used if we are NOT doing 100 Continue and are still sending the
215 * header (provided in full as the buffer to #MHD_send_on_connection_ or as
216 * the header to #MHD_send_on_connection2_).
217 * @return sum of the number of bytes sent from both buffers or
218 * -1 on error
219 */
220ssize_t
221MHD_send_on_connection_ (struct MHD_Connection *connection,
222 const char *buffer,
223 size_t buffer_size,
224 enum MHD_SendSocketOptions options)
225{
226 bool want_cork;
227 MHD_socket s = connection->socket_fd;
228 ssize_t ret;
229
230 /* error handling from send_param_adapter() */
231 if ((MHD_INVALID_SOCKET == s) || (MHD_CONNECTION_CLOSED == connection->state))
232 {
233 return MHD_ERR_NOTCONN_;
234 }
235
236 /* from send_param_adapter() */
237 if (buffer_size > MHD_SCKT_SEND_MAX_SIZE_)
238 buffer_size = MHD_SCKT_SEND_MAX_SIZE_; /* return value limit */
239
240 /* Get socket options, change/set options if necessary. */
241 switch (options)
242 {
243 /* No corking */
244 case MHD_SSO_NO_CORK:
245 want_cork = false;
246 break;
247 /* Do corking, consider MSG_MORE instead if available. */
248 case MHD_SSO_MAY_CORK:
249 want_cork = true;
250 break;
251 /* Cork the header. */
252 case MHD_SSO_HDR_CORK:
253 want_cork = (buffer_size <= 1024);
254 break;
255 }
256
257#ifdef HTTPS_SUPPORT
258 if (0 != (connection->daemon->options & MHD_USE_TLS))
259 {
260 bool have_cork = connection->sk_cork_on;
261
262 if (want_cork && ! have_cork)
263 {
264 gnutls_record_cork (connection->tls_session);
265 connection->sk_cork_on = true;
266 }
267 if (buffer_size > SSIZE_MAX)
268 buffer_size = SSIZE_MAX;
269 ret = gnutls_record_send (connection->tls_session,
270 buffer,
271 buffer_size);
272 if ( (GNUTLS_E_AGAIN == ret) ||
273 (GNUTLS_E_INTERRUPTED == ret) )
274 {
275#ifdef EPOLL_SUPPORT
276 if (GNUTLS_E_AGAIN == ret)
277 connection->epoll_state &= ~MHD_EPOLL_STATE_WRITE_READY;
278#endif
279 return MHD_ERR_AGAIN_;
280 }
281 if (ret < 0)
282 {
283 /* Likely 'GNUTLS_E_INVALID_SESSION' (client communication
284 disrupted); interpret as a hard error */
285 return MHD_ERR_NOTCONN_;
286 }
287#ifdef EPOLL_SUPPORT
288 /* Unlike non-TLS connections, do not reset "write-ready" if
289 * sent amount smaller than provided amount, as TLS
290 * connections may break data into smaller parts for sending. */
291#endif /* EPOLL_SUPPORT */
292
293 if (! want_cork && have_cork)
294 {
295 (void) gnutls_record_uncork (connection->tls_session, 0);
296 connection->sk_cork_on = false;
297 }
298 }
299 else
300#endif /* HTTPS_SUPPORT */
301 {
302 /* plaintext transmission */
303 pre_cork_setsockopt (connection, want_cork);
304#if HAVE_MSG_MORE
305 ret = send (s,
306 buffer,
307 buffer_size,
308 MAYBE_MSG_NOSIGNAL | (want_cork ? MSG_MORE : 0));
309#else
310 ret = send (connection->socket_fd,
311 buffer,
312 buffer_size,
313 MAYBE_MSG_NOSIGNAL);
314#endif
315
316 if (0 > ret)
317 {
318 const int err = MHD_socket_get_error_ ();
319
320 if (MHD_SCKT_ERR_IS_EAGAIN_ (err))
321 {
322#if EPOLL_SUPPORT
323 /* EAGAIN, no longer write-ready */
324 connection->epoll_state &= ~MHD_EPOLL_STATE_WRITE_READY;
325#endif /* EPOLL_SUPPORT */
326 return MHD_ERR_AGAIN_;
327 }
328 if (MHD_SCKT_ERR_IS_EINTR_ (err))
329 return MHD_ERR_AGAIN_;
330 if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_ECONNRESET_))
331 return MHD_ERR_CONNRESET_;
332 /* Treat any other error as hard error. */
333 return MHD_ERR_NOTCONN_;
334 }
335#if EPOLL_SUPPORT
336 else if (buffer_size > (size_t) ret)
337 connection->epoll_state &= ~MHD_EPOLL_STATE_WRITE_READY;
338#endif /* EPOLL_SUPPORT */
339 if (ret == buffer_size)
340 post_cork_setsockopt (connection, want_cork);
341 }
342
343 return ret;
344}
345
346
347/**
348 * Send header followed by buffer on connection.
349 * Uses writev if possible to send both at once
350 * and returns the sum of the number of bytes sent from
351 * both buffers, or -1 on error;
352 * if writev is unavailable, this call MUST only send from 'header'
353 * (as we cannot handle the case that the first write
354 * succeeds and the 2nd fails!).
355 *
356 * @param connection the MHD_Connection structure
357 * @param header content of header to send
358 * @param header_size the size of the header (in bytes)
359 * @param buffer content of the buffer to send
360 * @param buffer_size the size of the buffer (in bytes)
361 * @return sum of the number of bytes sent from both buffers or
362 * -1 on error
363 */
364ssize_t
365MHD_send_on_connection2_ (struct MHD_Connection *connection,
366 const char *header,
367 size_t header_size,
368 const char *buffer,
369 size_t buffer_size)
370{
371#ifdef HTTPS_SUPPORT
372 if (0 != (connection->daemon->options & MHD_USE_TLS))
373 return MHD_send_on_connection_ (connection,
374 header,
375 header_size,
376 MHD_SSO_HDR_CORK);
377#endif
378#if defined(HAVE_SENDMSG) || defined(HAVE_WRITEV)
379 MHD_socket s = connection->socket_fd;
380 ssize_t ret;
381 struct iovec vector[2];
382
383 /* Since we generally give the fully answer, we do not want
384 corking to happen */
385 pre_cork_setsockopt (connection, false);
386
387 vector[0].iov_base = (void *) header;
388 vector[0].iov_len = header_size;
389 vector[1].iov_base = (void *) buffer;
390 vector[1].iov_len = buffer_size;
391
392#if HAVE_SENDMSG
393 {
394 struct msghdr msg;
395
396 memset(&msg, 0, sizeof(struct msghdr));
397 msg.msg_iov = vector;
398 msg.msg_iovlen = 2;
399
400 ret = sendmsg (s, &msg, MAYBE_MSG_NOSIGNAL);
401 }
402#elif HAVE_WRITEV
403 {
404 int iovcnt;
405
406 iovcnt = sizeof (vector) / sizeof (struct iovec);
407 ret = writev (s, vector, iovcnt);
408 }
409#endif
410
411 /* Only if we succeeded sending the full buffer, we need to make sure that
412 the OS flushes at the end */
413 if (ret == header_size + buffer_size)
414 post_cork_setsockopt (connection, false);
415
416 return ret;
417
418#else
419 return MHD_send_on_connection_ (connection,
420 header,
421 header_size,
422 MHD_SSO_HDR_CORK);
423#endif
424}
425
426/**
427 * sendfile() chuck size
428 */
429#define MHD_SENFILE_CHUNK_ (0x20000)
430
431/**
432 * sendfile() chuck size for thread-per-connection
433 */
434#define MHD_SENFILE_CHUNK_THR_P_C_ (0x200000)
435
436#ifdef HAVE_FREEBSD_SENDFILE
437#ifdef SF_FLAGS
438/**
439 * FreeBSD sendfile() flags
440 */
441static int freebsd_sendfile_flags_;
442
443/**
444 * FreeBSD sendfile() flags for thread-per-connection
445 */
446static int freebsd_sendfile_flags_thd_p_c_;
447#endif /* SF_FLAGS */
448
449#endif /* HAVE_FREEBSD_SENDFILE */
450
451#if defined(_MHD_HAVE_SENDFILE)
452/**
453 * Function for sending responses backed by file FD.
454 *
455 * @param connection the MHD connection structure
456 * @return actual number of bytes sent
457 */
458ssize_t
459MHD_send_sendfile_ (struct MHD_Connection *connection)
460{
461 ssize_t ret;
462 const int file_fd = connection->response->fd;
463 uint64_t left;
464 uint64_t offsetu64;
465#ifndef HAVE_SENDFILE64
466 const uint64_t max_off_t = (uint64_t)OFF_T_MAX;
467#else /* HAVE_SENDFILE64 */
468 const uint64_t max_off_t = (uint64_t)OFF64_T_MAX;
469#endif /* HAVE_SENDFILE64 */
470#ifdef MHD_LINUX_SOLARIS_SENDFILE
471#ifndef HAVE_SENDFILE64
472 off_t offset;
473#else /* HAVE_SENDFILE64 */
474 off64_t offset;
475#endif /* HAVE_SENDFILE64 */
476#endif /* MHD_LINUX_SOLARIS_SENDFILE */
477#ifdef HAVE_FREEBSD_SENDFILE
478 off_t sent_bytes;
479 int flags = 0;
480#endif
481#ifdef HAVE_DARWIN_SENDFILE
482 off_t len;
483#endif /* HAVE_DARWIN_SENDFILE */
484 const bool used_thr_p_c = (0 != (connection->daemon->options & MHD_USE_THREAD_PER_CONNECTION));
485 const size_t chunk_size = used_thr_p_c ? MHD_SENFILE_CHUNK_THR_P_C_ : MHD_SENFILE_CHUNK_;
486 size_t send_size = 0;
487 mhd_assert (MHD_resp_sender_sendfile == connection->resp_sender);
488
489 pre_cork_setsockopt (connection, false);
490
491 offsetu64 = connection->response_write_position + connection->response->fd_off;
492 left = connection->response->total_size - connection->response_write_position;
493 /* Do not allow system to stick sending on single fast connection:
494 * use 128KiB chunks (2MiB for thread-per-connection). */
495 send_size = (left > chunk_size) ? chunk_size : (size_t) left;
496 if (max_off_t < offsetu64)
497 { /* Retry to send with standard 'send()'. */
498 connection->resp_sender = MHD_resp_sender_std;
499 return MHD_ERR_AGAIN_;
500 }
501#ifdef MHD_LINUX_SOLARIS_SENDFILE
502#ifndef HAVE_SENDFILE64
503 offset = (off_t) offsetu64;
504 ret = sendfile (connection->socket_fd,
505 file_fd,
506 &offset,
507 send_size);
508#else /* HAVE_SENDFILE64 */
509 offset = (off64_t) offsetu64;
510 ret = sendfile64 (connection->socket_fd,
511 file_fd,
512 &offset,
513 send_size);
514#endif /* HAVE_SENDFILE64 */
515 if (0 > ret)
516 {
517 const int err = MHD_socket_get_error_();
518 if (MHD_SCKT_ERR_IS_EAGAIN_(err))
519 {
520#ifdef EPOLL_SUPPORT
521 /* EAGAIN --- no longer write-ready */
522 connection->epoll_state &= ~MHD_EPOLL_STATE_WRITE_READY;
523#endif /* EPOLL_SUPPORT */
524 return MHD_ERR_AGAIN_;
525 }
526 if (MHD_SCKT_ERR_IS_EINTR_ (err))
527 return MHD_ERR_AGAIN_;
528#ifdef HAVE_LINUX_SENDFILE
529 if (MHD_SCKT_ERR_IS_(err,
530 MHD_SCKT_EBADF_))
531 return MHD_ERR_BADF_;
532 /* sendfile() failed with EINVAL if mmap()-like operations are not
533 supported for FD or other 'unusual' errors occurred, so we should try
534 to fall back to 'SEND'; see also this thread for info on
535 odd libc/Linux behavior with sendfile:
536 http://lists.gnu.org/archive/html/libmicrohttpd/2011-02/msg00015.html */
537 connection->resp_sender = MHD_resp_sender_std;
538 return MHD_ERR_AGAIN_;
539#else /* HAVE_SOLARIS_SENDFILE */
540 if ( (EAFNOSUPPORT == err) ||
541 (EINVAL == err) ||
542 (EOPNOTSUPP == err) )
543 { /* Retry with standard file reader. */
544 connection->resp_sender = MHD_resp_sender_std;
545 return MHD_ERR_AGAIN_;
546 }
547 if ( (ENOTCONN == err) ||
548 (EPIPE == err) )
549 {
550 return MHD_ERR_CONNRESET_;
551 }
552 return MHD_ERR_BADF_; /* Fail hard */
553#endif /* HAVE_SOLARIS_SENDFILE */
554 }
555#ifdef EPOLL_SUPPORT
556 else if (send_size > (size_t)ret)
557 connection->epoll_state &= ~MHD_EPOLL_STATE_WRITE_READY;
558#endif /* EPOLL_SUPPORT */
559#elif defined(HAVE_FREEBSD_SENDFILE)
560#ifdef SF_FLAGS
561 flags = used_thr_p_c ?
562 freebsd_sendfile_flags_thd_p_c_ : freebsd_sendfile_flags_;
563#endif /* SF_FLAGS */
564 if (0 != sendfile (file_fd,
565 connection->socket_fd,
566 (off_t) offsetu64,
567 send_size,
568 NULL,
569 &sent_bytes,
570 flags))
571 {
572 const int err = MHD_socket_get_error_();
573 if (MHD_SCKT_ERR_IS_EAGAIN_(err) ||
574 MHD_SCKT_ERR_IS_EINTR_(err) ||
575 EBUSY == err)
576 {
577 mhd_assert (SSIZE_MAX >= sent_bytes);
578 if (0 != sent_bytes)
579 return (ssize_t)sent_bytes;
580
581 return MHD_ERR_AGAIN_;
582 }
583 /* Some unrecoverable error. Possibly file FD is not suitable
584 * for sendfile(). Retry with standard send(). */
585 connection->resp_sender = MHD_resp_sender_std;
586 return MHD_ERR_AGAIN_;
587 }
588 mhd_assert (0 < sent_bytes);
589 mhd_assert (SSIZE_MAX >= sent_bytes);
590 ret = (ssize_t)sent_bytes;
591#elif defined(HAVE_DARWIN_SENDFILE)
592 len = (off_t)send_size; /* chunk always fit */
593 if (0 != sendfile (file_fd,
594 connection->socket_fd,
595 (off_t) offsetu64,
596 &len,
597 NULL,
598 0))
599 {
600 const int err = MHD_socket_get_error_();
601 if (MHD_SCKT_ERR_IS_EAGAIN_(err) ||
602 MHD_SCKT_ERR_IS_EINTR_(err))
603 {
604 mhd_assert (0 <= len);
605 mhd_assert (SSIZE_MAX >= len);
606 mhd_assert (send_size >= (size_t)len);
607 if (0 != len)
608 return (ssize_t)len;
609
610 return MHD_ERR_AGAIN_;
611 }
612 if (ENOTCONN == err ||
613 EPIPE == err)
614 return MHD_ERR_CONNRESET_;
615 if (ENOTSUP == err ||
616 EOPNOTSUPP == err)
617 { /* This file FD is not suitable for sendfile().
618 * Retry with standard send(). */
619 connection->resp_sender = MHD_resp_sender_std;
620 return MHD_ERR_AGAIN_;
621 }
622 return MHD_ERR_BADF_; /* Return hard error. */
623 }
624 mhd_assert (0 <= len);
625 mhd_assert (SSIZE_MAX >= len);
626 mhd_assert (send_size >= (size_t)len);
627 ret = (ssize_t)len;
628#endif /* HAVE_FREEBSD_SENDFILE */
629
630 /* Make sure we send the data without delay ONLY if we
631 provided the complete response (not on partial write) */
632 if (ret == left)
633 post_cork_setsockopt (connection, false);
634
635 return ret;
636}
637#endif /* _MHD_HAVE_SENDFILE */