* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
- * Copyright (C) 1998 - 2013, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2017, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
- * are also available at http://curl.haxx.se/docs/copyright.html.
+ * are also available at https://curl.haxx.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
+#ifdef HAVE_NETINET_IN6_H
+#include <netinet/in6.h>
+#endif
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#define ERANGE 34 /* errno.h value */
#endif
+static enum {
+ socket_domain_inet = AF_INET
#ifdef ENABLE_IPV6
-static bool use_ipv6 = FALSE;
+ , socket_domain_inet6 = AF_INET6
+#endif
+#ifdef USE_UNIX_SOCKETS
+ , socket_domain_unix = AF_UNIX
#endif
+} socket_domain = AF_INET;
static bool use_gopher = FALSE;
-static const char *ipv_inuse = "IPv4";
static int serverlogslocked = 0;
static bool is_proxy = FALSE;
#define REQBUFSIZ 150000
#define REQBUFSIZ_TXT "149999"
-static long prevtestno=-1; /* previous test number we served */
-static long prevpartno=-1; /* previous part number we served */
-static bool prevbounce=FALSE; /* instructs the server to increase the part
- number for a test in case the identical
- testno+partno request shows up again */
+static long prevtestno = -1; /* previous test number we served */
+static long prevpartno = -1; /* previous part number we served */
+static bool prevbounce = FALSE; /* instructs the server to increase the part
+ number for a test in case the identical
+ testno+partno request shows up again */
#define RCMD_NORMALREQ 0 /* default request, use the tests file normally */
#define RCMD_IDLE 1 /* told to sit idle */
bool pipelining; /* true if request is pipelined */
int callcount; /* times ProcessRequest() gets called */
bool connmon; /* monitor the state of the connection, log disconnects */
+ bool upgrade; /* test case allows upgrade to http2 */
+ bool upgrade_request; /* upgrade request found and allowed */
int done_processing;
};
static size_t num_sockets = 0;
static int ProcessRequest(struct httprequest *req);
-static void storerequest(char *reqbuf, size_t totalsize);
+static void storerequest(const char *reqbuf, size_t totalsize);
#define DEFAULT_PORT 8999
const char *serverlogfile = DEFAULT_LOGFILE;
-#define SWSVERSION "cURL test suite HTTP server/0.1"
+#define SWSVERSION "curl test suite HTTP server/0.1"
#define REQUEST_DUMP "log/server.input"
#define RESPONSE_DUMP "log/server.response"
proper point - like with NTLM */
#define CMD_CONNECTIONMONITOR "connection-monitor"
+/* upgrade to http2 */
+#define CMD_UPGRADE "upgrade"
+
#define END_OF_HEADERS "\r\n\r\n"
enum {
{
#ifdef SIGHUP
/* ignore SIGHUP signal */
- if((old_sighup_handler = signal(SIGHUP, SIG_IGN)) == SIG_ERR)
+ old_sighup_handler = signal(SIGHUP, SIG_IGN);
+ if(old_sighup_handler == SIG_ERR)
logmsg("cannot install SIGHUP handler: %s", strerror(errno));
#endif
#ifdef SIGPIPE
/* ignore SIGPIPE signal */
- if((old_sigpipe_handler = signal(SIGPIPE, SIG_IGN)) == SIG_ERR)
+ old_sigpipe_handler = signal(SIGPIPE, SIG_IGN);
+ if(old_sigpipe_handler == SIG_ERR)
logmsg("cannot install SIGPIPE handler: %s", strerror(errno));
#endif
#ifdef SIGALRM
/* ignore SIGALRM signal */
- if((old_sigalrm_handler = signal(SIGALRM, SIG_IGN)) == SIG_ERR)
+ old_sigalrm_handler = signal(SIGALRM, SIG_IGN);
+ if(old_sigalrm_handler == SIG_ERR)
logmsg("cannot install SIGALRM handler: %s", strerror(errno));
#endif
#ifdef SIGINT
/* handle SIGINT signal with our exit_signal_handler */
- if((old_sigint_handler = signal(SIGINT, exit_signal_handler)) == SIG_ERR)
+ old_sigint_handler = signal(SIGINT, exit_signal_handler);
+ if(old_sigint_handler == SIG_ERR)
logmsg("cannot install SIGINT handler: %s", strerror(errno));
else
siginterrupt(SIGINT, 1);
#endif
#ifdef SIGTERM
/* handle SIGTERM signal with our exit_signal_handler */
- if((old_sigterm_handler = signal(SIGTERM, exit_signal_handler)) == SIG_ERR)
+ old_sigterm_handler = signal(SIGTERM, exit_signal_handler);
+ if(old_sigterm_handler == SIG_ERR)
logmsg("cannot install SIGTERM handler: %s", strerror(errno));
else
siginterrupt(SIGTERM, 1);
#endif
#if defined(SIGBREAK) && defined(WIN32)
/* handle SIGBREAK signal with our exit_signal_handler */
- if((old_sigbreak_handler = signal(SIGBREAK, exit_signal_handler)) == SIG_ERR)
+ old_sigbreak_handler = signal(SIGBREAK, exit_signal_handler);
+ if(old_sigbreak_handler == SIG_ERR)
logmsg("cannot install SIGBREAK handler: %s", strerror(errno));
else
siginterrupt(SIGBREAK, 1);
#endif
}
+/* returns true if the current socket is an IP one */
+static bool socket_domain_is_ip(void)
+{
+ switch(socket_domain) {
+ case AF_INET:
+#ifdef ENABLE_IPV6
+ case AF_INET6:
+#endif
+ return true;
+ default:
+ /* case AF_UNIX: */
+ return false;
+ }
+}
+
/* based on the testno, parse the correct server commands */
static int parse_servercmd(struct httprequest *req)
{
filename = test2file(req->testno);
- stream=fopen(filename, "rb");
+ stream = fopen(filename, "rb");
if(!stream) {
error = errno;
logmsg("fopen() failed with error: %d %s", error, strerror(error));
char *orgcmd = NULL;
char *cmd = NULL;
size_t cmdsize = 0;
- int num=0;
+ int num = 0;
/* get the custom server control "commands" */
error = getpart(&orgcmd, &cmdsize, "reply", "servercmd", stream);
logmsg("enabled connection monitoring");
req->connmon = TRUE;
}
+ else if(!strncmp(CMD_UPGRADE, cmd, strlen(CMD_UPGRADE))) {
+ logmsg("enabled upgrade to http2");
+ req->upgrade = TRUE;
+ }
else if(1 == sscanf(cmd, "pipe: %d", &num)) {
logmsg("instructed to allow a pipe size of %d", num);
if(num < 0)
else
break;
}
- if(orgcmd)
- free(orgcmd);
+ free(orgcmd);
}
return 0; /* OK! */
static int ProcessRequest(struct httprequest *req)
{
- char *line=&req->reqbuf[req->checkindex];
+ char *line = &req->reqbuf[req->checkindex];
bool chunked = FALSE;
static char request[REQUEST_KEYWORD_SIZE];
static char doc[MAXDOCNAMELEN];
/* get the number after it */
if(ptr) {
if((strlen(doc) + strlen(request)) < 400)
- sprintf(logbuf, "Got request: %s %s HTTP/%d.%d",
- request, doc, prot_major, prot_minor);
+ snprintf(logbuf, sizeof(logbuf), "Got request: %s %s HTTP/%d.%d",
+ request, doc, prot_major, prot_minor);
else
- sprintf(logbuf, "Got a *HUGE* request HTTP/%d.%d",
- prot_major, prot_minor);
+ snprintf(logbuf, sizeof(logbuf), "Got a *HUGE* request HTTP/%d.%d",
+ prot_major, prot_minor);
logmsg("%s", logbuf);
if(!strncmp("/verifiedserver", ptr, 15)) {
if(req->testno) {
- sprintf(logbuf, "Requested test number %ld part %ld",
- req->testno, req->partno);
+ snprintf(logbuf, sizeof(logbuf), "Requested test number %ld part %ld",
+ req->testno, req->partno);
logmsg("%s", logbuf);
/* find and parse <servercmd> for this test */
if(sscanf(req->reqbuf, "CONNECT %" MAXDOCNAMELEN_TXT "s HTTP/%d.%d",
doc, &prot_major, &prot_minor) == 3) {
char *portp = NULL;
- unsigned long part=0;
+ unsigned long part = 0;
- sprintf(logbuf, "Received a CONNECT %s HTTP/%d.%d request",
- doc, prot_major, prot_minor);
+ snprintf(logbuf, sizeof(logbuf),
+ "Received a CONNECT %s HTTP/%d.%d request",
+ doc, prot_major, prot_minor);
logmsg("%s", logbuf);
req->connect_request = TRUE;
}
if(*p != ']')
logmsg("Invalid CONNECT IPv6 address format");
- else if (*(p+1) != ':')
+ else if(*(p + 1) != ':')
logmsg("Invalid CONNECT IPv6 port format");
else
- portp = p+1;
+ portp = p + 1;
req->testno = part;
}
else
portp = strchr(doc, ':');
- if(portp && (*(portp+1) != '\0') && ISDIGIT(*(portp+1))) {
- unsigned long ulnum = strtoul(portp+1, NULL, 10);
+ if(portp && (*(portp + 1) != '\0') && ISDIGIT(*(portp + 1))) {
+ unsigned long ulnum = strtoul(portp + 1, NULL, 10);
if(!ulnum || (ulnum > 65535UL))
logmsg("Invalid CONNECT port received");
else
}
if(req->testno == DOCNUMBER_NOTHING) {
+ /* check for a Testno: header with the test case number */
+ char *testno = strstr(line, "\nTestno: ");
+ if(testno) {
+ req->testno = strtol(&testno[9], NULL, 10);
+ logmsg("Found test number %d in Testno: header!", req->testno);
+ }
+ }
+ if(req->testno == DOCNUMBER_NOTHING) {
/* Still no test case number. Try to get the the number off the last dot
instead, IE we consider the TLD to be the test number. Test 123 can
then be written as "example.com.123". */
else
req->partno = 0;
- sprintf(logbuf, "Requested test number %ld part %ld (from host name)",
- req->testno, req->partno);
+ snprintf(logbuf, sizeof(logbuf),
+ "Requested test number %ld part %ld (from host name)",
+ req->testno, req->partno);
logmsg("%s", logbuf);
}
else
req->partno = 0;
- sprintf(logbuf, "Requested GOPHER test number %ld part %ld",
- req->testno, req->partno);
+ snprintf(logbuf, sizeof(logbuf),
+ "Requested GOPHER test number %ld part %ld",
+ req->testno, req->partno);
logmsg("%s", logbuf);
}
}
if(got_exit_signal)
return 1; /* done */
- if((req->cl==0) && curlx_strnequal("Content-Length:", line, 15)) {
+ if((req->cl == 0) && strncasecompare("Content-Length:", line, 15)) {
/* If we don't ignore content-length, we read it and we read the whole
request including the body before we return. If we've been told to
ignore the content-length, we will return as soon as all headers
logmsg("... but will abort after %zu bytes", req->cl);
break;
}
- else if(curlx_strnequal("Transfer-Encoding: chunked", line,
+ else if(strncasecompare("Transfer-Encoding: chunked", line,
strlen("Transfer-Encoding: chunked"))) {
/* chunked data coming in */
chunked = TRUE;
logmsg("Authorization header found, as required");
}
- if(!req->digest && strstr(req->reqbuf, "Authorization: Digest")) {
+ if(strstr(req->reqbuf, "Authorization: Negotiate")) {
+ /* Negotiate iterations */
+ static long prev_testno = -1;
+ static long prev_partno = -1;
+ logmsg("Negotiate: prev_testno: %d, prev_partno: %d",
+ prev_testno, prev_partno);
+ if(req->testno != prev_testno) {
+ prev_testno = req->testno;
+ prev_partno = req->partno;
+ }
+ prev_partno += 1;
+ req->partno = prev_partno;
+ }
+ else if(!req->digest && strstr(req->reqbuf, "Authorization: Digest")) {
/* If the client is passing this Digest-header, we set the part number
to 1000. Not only to spice up the complexity of this, but to make
Digest stuff to work in the test suite. */
return 1; /* done */
}
+ if(req->upgrade && strstr(req->reqbuf, "Upgrade:")) {
+ /* we allow upgrade and there was one! */
+ logmsg("Found Upgrade: in request and allows it");
+ req->upgrade_request = TRUE;
+ }
+
if(req->cl > 0) {
if(req->cl <= req->offset - (end - req->reqbuf) - strlen(end_of_headers))
return 1; /* done */
}
/* store the entire request in a file */
-static void storerequest(char *reqbuf, size_t totalsize)
+static void storerequest(const char *reqbuf, size_t totalsize)
{
int res;
int error = 0;
size_t written;
size_t writeleft;
FILE *dump;
- const char *dumpfile=is_proxy?REQUEST_PROXY_DUMP:REQUEST_DUMP;
+ const char *dumpfile = is_proxy?REQUEST_PROXY_DUMP:REQUEST_DUMP;
- if (reqbuf == NULL)
+ if(reqbuf == NULL)
return;
- if (totalsize == 0)
+ if(totalsize == 0)
return;
do {
dump = fopen(dumpfile, "ab");
- } while ((dump == NULL) && ((error = errno) == EINTR));
- if (dump == NULL) {
+ } while((dump == NULL) && ((error = errno) == EINTR));
+ if(dump == NULL) {
logmsg("[2] Error opening file %s error: %d %s",
dumpfile, error, strerror(error));
logmsg("Failed to write request input ");
goto storerequest_cleanup;
if(written > 0)
writeleft -= written;
- } while ((writeleft > 0) && ((error = errno) == EINTR));
+ } while((writeleft > 0) && ((error = errno) == EINTR));
if(writeleft == 0)
logmsg("Wrote request (%zu bytes) input to %s", totalsize, dumpfile);
req->callcount = 0;
req->connect_port = 0;
req->done_processing = 0;
+ req->upgrade = 0;
+ req->upgrade_request = 0;
}
/* returns 1 if the connection should be serviced again immediately, 0 if there
}
else if(got < 0) {
error = SOCKERRNO;
- if (EAGAIN == error || EWOULDBLOCK == error) {
+ if(EAGAIN == error || EWOULDBLOCK == error) {
/* nothing to read at the moment */
return 0;
}
reqbuf[req->offset] = '\0';
/* at the end of a request dump it to an external file */
- if (fail || req->done_processing)
+ if(fail || req->done_processing)
storerequest(reqbuf, req->pipelining ? req->checkindex : req->offset);
if(got_exit_signal)
return -1;
ssize_t written;
size_t count;
const char *buffer;
- char *ptr=NULL;
+ char *ptr = NULL;
FILE *stream;
- char *cmd=NULL;
- size_t cmdsize=0;
+ char *cmd = NULL;
+ size_t cmdsize = 0;
FILE *dump;
bool persistant = TRUE;
bool sendfailure = FALSE;
case RCMD_STREAM:
#define STREAMTHIS "a string to stream 01234567890\n"
count = strlen(STREAMTHIS);
- for (;;) {
+ for(;;) {
written = swrite(sock, STREAMTHIS, count);
if(got_exit_signal)
return -1;
case DOCNUMBER_WERULEZ:
/* we got a "friends?" question, reply back that we sure are */
logmsg("Identifying ourselves as friends");
- sprintf(msgbuf, "WE ROOLZ: %ld\r\n", (long)getpid());
+ snprintf(msgbuf, sizeof(msgbuf), "WE ROOLZ: %ld\r\n", (long)getpid());
msglen = strlen(msgbuf);
if(use_gopher)
- sprintf(weare, "%s", msgbuf);
+ snprintf(weare, sizeof(weare), "%s", msgbuf);
else
- sprintf(weare, "HTTP/1.1 200 OK\r\nContent-Length: %zu\r\n\r\n%s",
- msglen, msgbuf);
+ snprintf(weare, sizeof(weare),
+ "HTTP/1.1 200 OK\r\nContent-Length: %zu\r\n\r\n%s",
+ msglen, msgbuf);
buffer = weare;
break;
case DOCNUMBER_404:
/* select the <data> tag for "normal" requests and the <connect> one
for CONNECT requests (within the <reply> section) */
- const char *section= req->connect_request?"connect":"data";
+ const char *section = req->connect_request?"connect":"data";
if(req->partno)
- sprintf(partbuf, "%s%ld", section, req->partno);
+ snprintf(partbuf, sizeof(partbuf), "%s%ld", section, req->partno);
else
- sprintf(partbuf, "%s", section);
+ snprintf(partbuf, sizeof(partbuf), "%s", section);
logmsg("Send response test%ld section <%s>", req->testno, partbuf);
- stream=fopen(filename, "rb");
+ stream = fopen(filename, "rb");
if(!stream) {
error = errno;
logmsg("fopen() failed with error: %d %s", error, strerror(error));
}
if(got_exit_signal) {
- if(ptr)
- free(ptr);
+ free(ptr);
return -1;
}
/* re-open the same file again */
- stream=fopen(filename, "rb");
+ stream = fopen(filename, "rb");
if(!stream) {
error = errno;
logmsg("fopen() failed with error: %d %s", error, strerror(error));
logmsg(" [4] Error opening file: %s", filename);
- if(ptr)
- free(ptr);
+ free(ptr);
return 0;
}
else {
fclose(stream);
if(error) {
logmsg("getpart() failed with error: %d", error);
- if(ptr)
- free(ptr);
+ free(ptr);
return 0;
}
}
}
if(got_exit_signal) {
- if(ptr)
- free(ptr);
- if(cmd)
- free(cmd);
+ free(ptr);
+ free(cmd);
return -1;
}
error = errno;
logmsg("fopen() failed with error: %d %s", error, strerror(error));
logmsg(" [5] Error opening file: %s", responsedump);
- if(ptr)
- free(ptr);
- if(cmd)
- free(cmd);
+ free(ptr);
+ free(cmd);
return -1;
}
responsesize = count;
do {
- /* Ok, we send no more than 200 bytes at a time, just to make sure that
+ /* Ok, we send no more than N bytes at a time, just to make sure that
larger chunks are split up so that the client will need to do multiple
recv() calls to get it and thus we exercise that code better */
size_t num = count;
- if(num > 200)
- num = 200;
+ if(num > 20)
+ num = 20;
+
+ retry:
written = swrite(sock, buffer, num);
- if (written < 0) {
+ if(written < 0) {
+ if((EWOULDBLOCK == SOCKERRNO) || (EAGAIN == SOCKERRNO)) {
+ wait_ms(10);
+ goto retry;
+ }
sendfailure = TRUE;
break;
}
- else {
- logmsg("Sent off %zd bytes", written);
- }
+
/* write to file as well */
fwrite(buffer, 1, (size_t)written, dump);
responsedump, error, strerror(error));
if(got_exit_signal) {
- if(ptr)
- free(ptr);
- if(cmd)
- free(cmd);
+ free(ptr);
+ free(cmd);
return -1;
}
if(sendfailure) {
- logmsg("Sending response failed. Only (%zu bytes) of (%zu bytes) were sent",
+ logmsg("Sending response failed. Only (%zu bytes) of (%zu bytes) "
+ "were sent",
responsesize-count, responsesize);
- if(ptr)
- free(ptr);
- if(cmd)
- free(cmd);
+ free(ptr);
+ free(cmd);
return -1;
}
logmsg("Response sent (%zu bytes) and written to %s",
responsesize, responsedump);
+ free(ptr);
- if(ptr)
- free(ptr);
-
- if(cmdsize > 0 ) {
+ if(cmdsize > 0) {
char command[32];
int quarters;
int num;
- ptr=cmd;
+ ptr = cmd;
do {
if(2 == sscanf(ptr, "%31s %d", command, &num)) {
if(!strcmp("wait", command)) {
ptr = NULL;
} while(ptr && *ptr);
}
- if(cmd)
- free(cmd);
-
+ free(cmd);
req->open = use_gopher?FALSE:persistant;
prevtestno = req->testno;
srvr_sockaddr_union_t serveraddr;
curl_socket_t serverfd;
int error;
- int rc;
+ int rc = 0;
const char *op_br = "";
const char *cl_br = "";
-#ifdef TCP_NODELAY
- curl_socklen_t flag;
-#endif
#ifdef ENABLE_IPV6
- if(use_ipv6) {
+ if(socket_domain == AF_INET6) {
op_br = "[";
cl_br = "]";
}
logmsg("about to connect to %s%s%s:%hu",
op_br, ipaddr, cl_br, port);
-#ifdef ENABLE_IPV6
- if(!use_ipv6)
-#endif
- serverfd = socket(AF_INET, SOCK_STREAM, 0);
-#ifdef ENABLE_IPV6
- else
- serverfd = socket(AF_INET6, SOCK_STREAM, 0);
-#endif
+
+ serverfd = socket(socket_domain, SOCK_STREAM, 0);
if(CURL_SOCKET_BAD == serverfd) {
error = SOCKERRNO;
- logmsg("Error creating socket for server conection: (%d) %s",
+ logmsg("Error creating socket for server connection: (%d) %s",
error, strerror(error));
return CURL_SOCKET_BAD;
}
#ifdef TCP_NODELAY
- /* Disable the Nagle algorithm */
- flag = 1;
- if(0 != setsockopt(serverfd, IPPROTO_TCP, TCP_NODELAY,
- (void *)&flag, sizeof(flag)))
- logmsg("====> TCP_NODELAY for server conection failed");
- else
- logmsg("TCP_NODELAY set for server conection");
+ if(socket_domain_is_ip()) {
+ /* Disable the Nagle algorithm */
+ curl_socklen_t flag = 1;
+ if(0 != setsockopt(serverfd, IPPROTO_TCP, TCP_NODELAY,
+ (void *)&flag, sizeof(flag)))
+ logmsg("====> TCP_NODELAY for server connection failed");
+ }
#endif
-#ifdef ENABLE_IPV6
- if(!use_ipv6) {
-#endif
+ switch(socket_domain) {
+ case AF_INET:
memset(&serveraddr.sa4, 0, sizeof(serveraddr.sa4));
serveraddr.sa4.sin_family = AF_INET;
serveraddr.sa4.sin_port = htons(port);
}
rc = connect(serverfd, &serveraddr.sa, sizeof(serveraddr.sa4));
+ break;
#ifdef ENABLE_IPV6
- }
- else {
+ case AF_INET6:
memset(&serveraddr.sa6, 0, sizeof(serveraddr.sa6));
serveraddr.sa6.sin6_family = AF_INET6;
serveraddr.sa6.sin6_port = htons(port);
}
rc = connect(serverfd, &serveraddr.sa, sizeof(serveraddr.sa6));
- }
+ break;
#endif /* ENABLE_IPV6 */
+#ifdef USE_UNIX_SOCKETS
+ case AF_UNIX:
+ logmsg("Proxying through Unix socket is not (yet?) supported.");
+ return CURL_SOCKET_BAD;
+#endif /* USE_UNIX_SOCKETS */
+ }
if(got_exit_signal) {
sclose(serverfd);
* either end.
*
* When doing FTP through a CONNECT proxy, we expect that the data connection
- * will be setup while the first connect is still being kept up. Therefor we
+ * will be setup while the first connect is still being kept up. Therefore we
* must accept a new connection and deal with it appropriately.
*/
bool poll_server_rd[2] = { TRUE, TRUE };
bool poll_client_wr[2] = { TRUE, TRUE };
bool poll_server_wr[2] = { TRUE, TRUE };
-#ifdef TCP_NODELAY
- curl_socklen_t flag;
-#endif
bool primary = FALSE;
bool secondary = FALSE;
int max_tunnel_idx; /* CTRL or DATA */
int loop;
int i;
+ int timeout_count = 0;
/* primary tunnel client endpoint already connected */
clientfd[CTRL] = *infdp;
fd_set input;
fd_set output;
- struct timeval timeout = {0, 250000L}; /* 250 ms */
+ struct timeval timeout = {1, 0}; /* 1000 ms */
ssize_t rc;
curl_socket_t maxfd = (curl_socket_t)-1;
maxfd = clientfd[i];
}
if(poll_client_wr[i] && toc[i]) {
- /* unless told not to do so, monitor writeability
+ /* unless told not to do so, monitor writability
if there is data ready to be sent to client */
FD_SET(clientfd[i], &output);
if(clientfd[i] > maxfd)
maxfd = serverfd[i];
}
if(poll_server_wr[i] && tos[i]) {
- /* unless told not to do so, monitor writeability
+ /* unless told not to do so, monitor writability
if there is data ready to be sent to server */
FD_SET(serverfd[i], &output);
if(serverfd[i] > maxfd)
if(rc > 0) {
/* socket action */
bool tcp_fin_wr;
+ timeout_count = 0;
if(got_exit_signal)
break;
memset(&req2, 0, sizeof(req2));
logmsg("====> Client connect DATA");
#ifdef TCP_NODELAY
- /* Disable the Nagle algorithm */
- flag = 1;
- if(0 != setsockopt(datafd, IPPROTO_TCP, TCP_NODELAY,
- (void *)&flag, sizeof(flag)))
- logmsg("====> TCP_NODELAY for client DATA conection failed");
- else
- logmsg("TCP_NODELAY set for client DATA conection");
+ if(socket_domain_is_ip()) {
+ /* Disable the Nagle algorithm */
+ curl_socklen_t flag = 1;
+ if(0 != setsockopt(datafd, IPPROTO_TCP, TCP_NODELAY,
+ (void *)&flag, sizeof(flag)))
+ logmsg("====> TCP_NODELAY for client DATA connection failed");
+ }
#endif
req2.pipelining = FALSE;
init_httprequest(&req2);
/* connect to the server */
serverfd[DATA] = connect_to(ipaddr, req2.connect_port);
if(serverfd[DATA] != CURL_SOCKET_BAD) {
- /* secondary tunnel established, now we have two connections */
+ /* secondary tunnel established, now we have two
+ connections */
poll_client_rd[DATA] = TRUE;
poll_client_wr[DATA] = TRUE;
poll_server_rd[DATA] = TRUE;
/* ---------------------------------------------------------- */
- /* react to tunnel endpoint readable/writeable notifications */
+ /* react to tunnel endpoint readable/writable notifications */
for(i = 0; i <= max_tunnel_idx; i++) {
size_t len;
if(clientfd[i] != CURL_SOCKET_BAD) {
break;
} /* (rc > 0) */
-
+ else {
+ timeout_count++;
+ if(timeout_count > 5) {
+ logmsg("CONNECT proxy timeout after %d idle seconds!", timeout_count);
+ break;
+ }
+ }
}
http_connect_cleanup:
*infdp = CURL_SOCKET_BAD;
}
+static void http2(struct httprequest *req)
+{
+ (void)req;
+ logmsg("switched to http2");
+ /* left to implement */
+}
+
+
/* returns a socket handle, or 0 if there are no more waiting sockets,
or < 0 if there was an error */
static curl_socket_t accept_connection(curl_socket_t sock)
num_sockets += 1;
#ifdef TCP_NODELAY
- /*
- * Disable the Nagle algorithm to make it easier to send out a large
- * response in many small segments to torture the clients more.
- */
- if(0 != setsockopt(msgsock, IPPROTO_TCP, TCP_NODELAY,
- (void *)&flag, sizeof(flag)))
- logmsg("====> TCP_NODELAY failed");
- else
- logmsg("TCP_NODELAY set");
+ if(socket_domain_is_ip()) {
+ /*
+ * Disable the Nagle algorithm to make it easier to send out a large
+ * response in many small segments to torture the clients more.
+ */
+ if(0 != setsockopt(msgsock, IPPROTO_TCP, TCP_NODELAY,
+ (void *)&flag, sizeof(flag)))
+ logmsg("====> TCP_NODELAY failed");
+ }
#endif
return msgsock;
while(!req->done_processing) {
int rc = get_request(msgsock, req);
- if (rc <= 0) {
- /* Nothing further to read now (possibly because the socket was closed */
+ if(rc <= 0) {
+ /* Nothing further to read now, possibly because the socket was closed */
return rc;
}
}
}
}
+ if(req->upgrade_request) {
+ /* an upgrade request, switch to http2 here */
+ http2(req);
+ return -1;
+ }
+
/* if we got a CONNECT, loop and get another request as well! */
if(req->open) {
int wrotepidfile = 0;
int flag;
unsigned short port = DEFAULT_PORT;
- char *pidname= (char *)".http.pid";
+#ifdef USE_UNIX_SOCKETS
+ const char *unix_socket = NULL;
+ bool unlink_socket = false;
+#endif
+ const char *pidname = ".http.pid";
struct httprequest req;
- int rc;
+ int rc = 0;
int error;
- int arg=1;
+ int arg = 1;
long pid;
const char *connecthost = "127.0.0.1";
+ const char *socket_type = "IPv4";
+ char port_str[11];
+ const char *location_str = port_str;
/* a default CONNECT port is basically pointless but still ... */
size_t socket_idx;
while(argc>arg) {
if(!strcmp("--version", argv[arg])) {
- printf("sws IPv4%s"
- "\n"
- ,
+ puts("sws IPv4"
#ifdef ENABLE_IPV6
"/IPv6"
-#else
- ""
#endif
- );
+#ifdef USE_UNIX_SOCKETS
+ "/unix"
+#endif
+ );
return 0;
}
else if(!strcmp("--pidfile", argv[arg])) {
end_of_headers = "\r\n"; /* gopher style is much simpler */
}
else if(!strcmp("--ipv4", argv[arg])) {
-#ifdef ENABLE_IPV6
- ipv_inuse = "IPv4";
- use_ipv6 = FALSE;
-#endif
+ socket_type = "IPv4";
+ socket_domain = AF_INET;
+ location_str = port_str;
arg++;
}
else if(!strcmp("--ipv6", argv[arg])) {
#ifdef ENABLE_IPV6
- ipv_inuse = "IPv6";
- use_ipv6 = TRUE;
+ socket_type = "IPv6";
+ socket_domain = AF_INET6;
+ location_str = port_str;
#endif
arg++;
}
+ else if(!strcmp("--unix-socket", argv[arg])) {
+ arg++;
+ if(argc>arg) {
+#ifdef USE_UNIX_SOCKETS
+ unix_socket = argv[arg];
+ if(strlen(unix_socket) >= sizeof(me.sau.sun_path)) {
+ fprintf(stderr, "sws: socket path must be shorter than %zu chars\n",
+ sizeof(me.sau.sun_path));
+ return 0;
+ }
+ socket_type = "unix";
+ socket_domain = AF_UNIX;
+ location_str = unix_socket;
+#endif
+ arg++;
+ }
+ }
else if(!strcmp("--port", argv[arg])) {
arg++;
if(argc>arg) {
" --pidfile [file]\n"
" --ipv4\n"
" --ipv6\n"
+ " --unix-socket [file]\n"
" --port [port]\n"
" --srcdir [path]\n"
" --connect [ip4-addr]\n"
}
}
+ snprintf(port_str, sizeof(port_str), "port %hu", port);
+
#ifdef WIN32
win32_init();
atexit(win32_cleanup);
pid = (long)getpid();
-#ifdef ENABLE_IPV6
- if(!use_ipv6)
-#endif
- sock = socket(AF_INET, SOCK_STREAM, 0);
-#ifdef ENABLE_IPV6
- else
- sock = socket(AF_INET6, SOCK_STREAM, 0);
-#endif
+ sock = socket(socket_domain, SOCK_STREAM, 0);
all_sockets[0] = sock;
num_sockets = 1;
goto sws_cleanup;
}
-#ifdef ENABLE_IPV6
- if(!use_ipv6) {
-#endif
+ switch(socket_domain) {
+ case AF_INET:
memset(&me.sa4, 0, sizeof(me.sa4));
me.sa4.sin_family = AF_INET;
me.sa4.sin_addr.s_addr = INADDR_ANY;
me.sa4.sin_port = htons(port);
rc = bind(sock, &me.sa, sizeof(me.sa4));
+ break;
#ifdef ENABLE_IPV6
- }
- else {
+ case AF_INET6:
memset(&me.sa6, 0, sizeof(me.sa6));
me.sa6.sin6_family = AF_INET6;
me.sa6.sin6_addr = in6addr_any;
me.sa6.sin6_port = htons(port);
rc = bind(sock, &me.sa, sizeof(me.sa6));
- }
+ break;
#endif /* ENABLE_IPV6 */
+#ifdef USE_UNIX_SOCKETS
+ case AF_UNIX:
+ memset(&me.sau, 0, sizeof(me.sau));
+ me.sau.sun_family = AF_UNIX;
+ strncpy(me.sau.sun_path, unix_socket, sizeof(me.sau.sun_path));
+ rc = bind(sock, &me.sa, sizeof(me.sau));
+ if(0 != rc && errno == EADDRINUSE) {
+ struct stat statbuf;
+ /* socket already exists. Perhaps it is stale? */
+ int unixfd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if(CURL_SOCKET_BAD == unixfd) {
+ error = SOCKERRNO;
+ logmsg("Error binding socket, failed to create socket at %s: (%d) %s",
+ unix_socket, error, strerror(error));
+ goto sws_cleanup;
+ }
+ /* check whether the server is alive */
+ rc = connect(unixfd, &me.sa, sizeof(me.sau));
+ error = errno;
+ close(unixfd);
+ if(ECONNREFUSED != error) {
+ logmsg("Error binding socket, failed to connect to %s: (%d) %s",
+ unix_socket, error, strerror(error));
+ goto sws_cleanup;
+ }
+ /* socket server is not alive, now check if it was actually a socket.
+ * Systems which have Unix sockets will also have lstat */
+ rc = lstat(unix_socket, &statbuf);
+ if(0 != rc) {
+ logmsg("Error binding socket, failed to stat %s: (%d) %s",
+ unix_socket, errno, strerror(errno));
+ goto sws_cleanup;
+ }
+ if((statbuf.st_mode & S_IFSOCK) != S_IFSOCK) {
+ logmsg("Error binding socket, failed to stat %s: (%d) %s",
+ unix_socket, error, strerror(error));
+ goto sws_cleanup;
+ }
+ /* dead socket, cleanup and retry bind */
+ rc = unlink(unix_socket);
+ if(0 != rc) {
+ logmsg("Error binding socket, failed to unlink %s: (%d) %s",
+ unix_socket, errno, strerror(errno));
+ goto sws_cleanup;
+ }
+ /* stale socket is gone, retry bind */
+ rc = bind(sock, &me.sa, sizeof(me.sau));
+ }
+ break;
+#endif /* USE_UNIX_SOCKETS */
+ }
if(0 != rc) {
error = SOCKERRNO;
- logmsg("Error binding socket on port %hu: (%d) %s",
- port, error, strerror(error));
+ logmsg("Error binding socket on %s: (%d) %s",
+ location_str, error, strerror(error));
goto sws_cleanup;
}
- logmsg("Running %s %s version on port %d",
- use_gopher?"GOPHER":"HTTP", ipv_inuse, (int)port);
+ logmsg("Running %s %s version on %s",
+ use_gopher?"GOPHER":"HTTP", socket_type, location_str);
/* start accepting connections */
rc = listen(sock, 5);
goto sws_cleanup;
}
+#ifdef USE_UNIX_SOCKETS
+ /* listen succeeds, so let's assume a valid listening Unix socket */
+ unlink_socket = true;
+#endif
+
/*
** As soon as this server writes its pid file the test harness will
** attempt to connect to this server and initiate its verification.
curl_socket_t maxfd = (curl_socket_t)-1;
/* Clear out closed sockets */
- for (socket_idx = num_sockets - 1; socket_idx >= 1; --socket_idx) {
- if (CURL_SOCKET_BAD == all_sockets[socket_idx]) {
- char* dst = (char *) (all_sockets + socket_idx);
- char* src = (char *) (all_sockets + socket_idx + 1);
- char* end = (char *) (all_sockets + num_sockets);
+ for(socket_idx = num_sockets - 1; socket_idx >= 1; --socket_idx) {
+ if(CURL_SOCKET_BAD == all_sockets[socket_idx]) {
+ char *dst = (char *) (all_sockets + socket_idx);
+ char *src = (char *) (all_sockets + socket_idx + 1);
+ char *end = (char *) (all_sockets + num_sockets);
memmove(dst, src, end - src);
num_sockets -= 1;
}
if(got_exit_signal)
goto sws_cleanup;
- /* Set up for select*/
+ /* Set up for select */
FD_ZERO(&input);
FD_ZERO(&output);
- for (socket_idx = 0; socket_idx < num_sockets; ++socket_idx) {
+ for(socket_idx = 0; socket_idx < num_sockets; ++socket_idx) {
/* Listen on all sockets */
FD_SET(all_sockets[socket_idx], &input);
if(all_sockets[socket_idx] > maxfd)
goto sws_cleanup;
rc = select((int)maxfd + 1, &input, &output, NULL, &timeout);
- if (rc < 0) {
+ if(rc < 0) {
error = SOCKERRNO;
logmsg("select() failed with error: (%d) %s",
error, strerror(error));
if(got_exit_signal)
goto sws_cleanup;
- if (rc == 0) {
- /* Timed out - try again*/
+ if(rc == 0) {
+ /* Timed out - try again */
continue;
}
/* Check if the listening socket is ready to accept */
- if (FD_ISSET(all_sockets[0], &input)) {
+ if(FD_ISSET(all_sockets[0], &input)) {
/* Service all queued connections */
curl_socket_t msgsock;
do {
msgsock = accept_connection(sock);
logmsg("accept_connection %d returned %d", sock, msgsock);
- if (CURL_SOCKET_BAD == msgsock)
+ if(CURL_SOCKET_BAD == msgsock)
goto sws_cleanup;
- } while (msgsock > 0);
+ } while(msgsock > 0);
}
/* Service all connections that are ready */
- for (socket_idx = 1; socket_idx < num_sockets; ++socket_idx) {
- if (FD_ISSET(all_sockets[socket_idx], &input)) {
+ for(socket_idx = 1; socket_idx < num_sockets; ++socket_idx) {
+ if(FD_ISSET(all_sockets[socket_idx], &input)) {
if(got_exit_signal)
goto sws_cleanup;
if(got_exit_signal)
goto sws_cleanup;
- if (rc < 0) {
+ if(rc < 0) {
logmsg("====> Client disconnect %d", req.connmon);
if(req.connmon) {
- const char *keepopen="[DISCONNECT]\n";
- storerequest((char *)keepopen, strlen(keepopen));
+ const char *keepopen = "[DISCONNECT]\n";
+ storerequest(keepopen, strlen(keepopen));
}
if(!req.open)
if(!serverlogslocked)
clear_advisor_read_lock(SERVERLOGS_LOCK);
- if (req.testno == DOCNUMBER_QUIT)
+ if(req.testno == DOCNUMBER_QUIT)
goto sws_cleanup;
}
/* Reset the request, unless we're still in the middle of reading */
- if (rc != 0)
+ if(rc != 0)
init_httprequest(&req);
- } while (rc > 0);
+ } while(rc > 0);
}
}
sws_cleanup:
- for (socket_idx = 1; socket_idx < num_sockets; ++socket_idx)
+ for(socket_idx = 1; socket_idx < num_sockets; ++socket_idx)
if((all_sockets[socket_idx] != sock) &&
(all_sockets[socket_idx] != CURL_SOCKET_BAD))
sclose(all_sockets[socket_idx]);
if(sock != CURL_SOCKET_BAD)
sclose(sock);
+#ifdef USE_UNIX_SOCKETS
+ if(unlink_socket && socket_domain == AF_UNIX) {
+ rc = unlink(unix_socket);
+ logmsg("unlink(%s) = %d (%s)", unix_socket, rc, strerror(rc));
+ }
+#endif
+
if(got_exit_signal)
logmsg("signalled to die");
restore_signal_handlers();
if(got_exit_signal) {
- logmsg("========> %s sws (port: %d pid: %ld) exits with signal (%d)",
- ipv_inuse, (int)port, pid, exit_signal);
+ logmsg("========> %s sws (%s pid: %ld) exits with signal (%d)",
+ socket_type, location_str, pid, exit_signal);
/*
* To properly set the return status of the process we
* must raise the same signal SIGINT or SIGTERM that we