/* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #ifdef APR_HAVE_UNISTD_H # include #endif /* HAVE_UNISTD_H */ #ifdef APR_HAVE_FCNTL_H # include #endif /* HAVE_FCNTL_H */ #include #include #ifdef APR_HAVE_SYS_SOCKET_H # include #endif /* HAVE_SYS_SOCKET_H */ #ifdef APR_HAVE_NETDB_H # include #endif /* HAVE_NETDB_H */ #ifdef APR_HAVE_NETINET_IN_H # include #endif /* HAVE_NETINET_IN_H */ #include #include #include #include #include #include #include #include #define MAKE_NV(NAME, VALUE) \ { \ (uint8_t *)NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, sizeof(VALUE) - 1, \ NGHTTP2_NV_FLAG_NONE \ } #define MAKE_NV_CS(NAME, VALUE) \ { \ (uint8_t *)NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, strlen(VALUE), \ NGHTTP2_NV_FLAG_NONE \ } static int verbose; static const char *cmd; static void log_out(const char *level, const char *where, const char *msg) { struct timespec tp; struct tm tm; char timebuf[128]; clock_gettime(CLOCK_REALTIME, &tp); localtime_r(&tp.tv_sec, &tm); strftime(timebuf, sizeof(timebuf)-1, "%H:%M:%S", &tm); fprintf(stderr, "[%s.%09lu][%s][%s] %s\n", timebuf, tp.tv_nsec, level, where, msg); } static void log_err(const char *where, const char *msg) { log_out("ERROR", where, msg); } static void log_info(const char *where, const char *msg) { if (verbose) log_out("INFO", where, msg); } static void log_debug(const char *where, const char *msg) { if (verbose > 1) log_out("DEBUG", where, msg); } #if defined(__GNUC__) __attribute__((format(printf, 2, 3))) #endif static void log_errf(const char *where, const char *msg, ...) { char buffer[8*1024]; va_list ap; va_start(ap, msg); vsnprintf(buffer, sizeof(buffer), msg, ap); va_end(ap); log_err(where, buffer); } #if defined(__GNUC__) __attribute__((format(printf, 2, 3))) #endif static void log_infof(const char *where, const char *msg, ...) { if (verbose) { char buffer[8*1024]; va_list ap; va_start(ap, msg); vsnprintf(buffer, sizeof(buffer), msg, ap); va_end(ap); log_info(where, buffer); } } #if defined(__GNUC__) __attribute__((format(printf, 2, 3))) #endif static void log_debugf(const char *where, const char *msg, ...) { if (verbose > 1) { char buffer[8*1024]; va_list ap; va_start(ap, msg); vsnprintf(buffer, sizeof(buffer), msg, ap); va_end(ap); log_debug(where, buffer); } } static int parse_host_port(const char **phost, uint16_t *pport, int *pipv6, size_t *pconsumed, const char *s, size_t len, uint16_t def_port) { size_t i, offset; char *host = NULL; int port = 0; int rv = 1, ipv6 = 0; if (!len) goto leave; offset = 0; if (s[offset] == '[') { ipv6 = 1; for (i = offset++; i < len; ++i) { if (s[i] == ']') break; } if (i >= len || i == offset) goto leave; host = strndup(s + offset, i - offset); offset = i + 1; } else { for (i = offset; i < len; ++i) { if (strchr(":/?#", s[i])) break; } if (i == offset) { log_debugf("parse_uri", "empty host name in '%.*s", (int)len, s); goto leave; } host = strndup(s + offset, i - offset); offset = i; } if (offset < len && s[offset] == ':') { port = 0; ++offset; for (i = offset; i < len; ++i) { if (strchr("/?#", s[i])) break; if (s[i] < '0' || s[i] > '9') { log_debugf("parse_uri", "invalid port char '%c'", s[i]); goto leave; } port *= 10; port += s[i] - '0'; if (port > 65535) { log_debugf("parse_uri", "invalid port number '%d'", port); goto leave; } } offset = i; } rv = 0; leave: *phost = rv? NULL : host; *pport = rv? 0 : (port? (uint16_t)port : def_port); if (pipv6) *pipv6 = ipv6; if (pconsumed) *pconsumed = offset; return rv; } struct uri { const char *scheme; const char *host; const char *authority; const char *path; uint16_t port; int ipv6; }; static int parse_uri(struct uri *uri, const char *s, size_t len) { char tmp[8192]; size_t n, offset = 0; uint16_t def_port = 0; int rv = 1; /* NOT A REAL URI PARSER */ memset(uri, 0, sizeof(*uri)); if (len > 5 && !memcmp("ws://", s, 5)) { uri->scheme = "ws"; def_port = 80; offset = 5; } else if (len > 6 && !memcmp("wss://", s, 6)) { uri->scheme = "wss"; def_port = 443; offset = 6; } else { /* not a scheme we process */ goto leave; } if (parse_host_port(&uri->host, &uri->port, &uri->ipv6, &n, s + offset, len - offset, def_port)) goto leave; offset += n; if (uri->port == def_port) uri->authority = uri->host; else if (uri->ipv6) { snprintf(tmp, sizeof(tmp), "[%s]:%u", uri->host, uri->port); uri->authority = strdup(tmp); } else { snprintf(tmp, sizeof(tmp), "%s:%u", uri->host, uri->port); uri->authority = strdup(tmp); } if (offset < len) { uri->path = strndup(s + offset, len - offset); } rv = 0; leave: return rv; } static int sock_nonblock_nodelay(int fd) { int flags, rv; int val = 1; while ((flags = fcntl(fd, F_GETFL, 0)) == -1 && errno == EINTR) ; if (flags == -1) { log_errf("sock_nonblock_nodelay", "fcntl get error %d (%s)", errno, strerror(errno)); return -1; } while ((rv = fcntl(fd, F_SETFL, flags | O_NONBLOCK)) == -1 && errno == EINTR) ; if (rv == -1) { log_errf("sock_nonblock_nodelay", "fcntl set error %d (%s)", errno, strerror(errno)); return -1; } rv = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &val, (socklen_t)sizeof(val)); if (rv == -1) { log_errf("sock_nonblock_nodelay", "set nodelay error %d (%s)", errno, strerror(errno)); return -1; } return 0; } static int open_connection(const char *host, uint16_t port) { char service[NI_MAXSERV]; struct addrinfo hints; struct addrinfo *res = NULL, *rp; int rv, fd = -1; memset(&hints, 0, sizeof(hints)); snprintf(service, sizeof(service), "%u", port); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; rv = getaddrinfo(host, service, &hints, &res); if (rv) { log_err("getaddrinfo", gai_strerror(rv)); goto leave; } for (rp = res; rp; rp = rp->ai_next) { fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (fd == -1) { continue; } while ((rv = connect(fd, rp->ai_addr, rp->ai_addrlen)) == -1 && errno == EINTR) ; if (!rv) /* connected */ break; close(fd); fd = -1; } leave: if (res) freeaddrinfo(res); return fd; } struct h2_stream; #define IO_WANT_NONE 0 #define IO_WANT_READ 1 #define IO_WANT_WRITE 2 struct h2_session { const char *server_name; const char *connect_host; uint16_t connect_port; int fd; nghttp2_session *ngh2; struct h2_stream *streams; int aborted; int want_io; }; typedef void h2_stream_closed_cb(struct h2_stream *stream); typedef void h2_stream_recv_data(struct h2_stream *stream, const uint8_t *data, size_t len); struct h2_stream { struct h2_stream *next; struct uri *uri; int32_t id; int fdin; int http_status; uint32_t error_code; unsigned input_closed : 1; unsigned closed : 1; unsigned reset : 1; h2_stream_closed_cb *on_close; h2_stream_recv_data *on_recv_data; }; static void h2_session_stream_add(struct h2_session *session, struct h2_stream *stream) { struct h2_stream *s; for (s = session->streams; s; s = s->next) { if (s == stream) /* already there? */ return; } stream->next = session->streams; session->streams = stream; } static void h2_session_stream_remove(struct h2_session *session, struct h2_stream *stream) { struct h2_stream *s, **pnext; pnext = &session->streams; s = session->streams; while (s) { if (s == stream) { *pnext = s->next; s->next = NULL; break; } pnext = &s->next; s = s->next; } } static struct h2_stream *h2_session_stream_get(struct h2_session *session, int32_t id) { struct h2_stream *s; for (s = session->streams; s; s = s->next) { if (s->id == id) return s; } return NULL; } static ssize_t h2_session_send(nghttp2_session *ngh2, const uint8_t *data, size_t length, int flags, void *user_data) { struct h2_session *session = user_data; ssize_t nwritten; (void)ngh2; (void)flags; session->want_io = IO_WANT_NONE; nwritten = send(session->fd, data, length, 0); if (nwritten < 0) { int err = errno; if ((EWOULDBLOCK == err) || (EAGAIN == err) || (EINTR == err) || (EINPROGRESS == err)) { return NGHTTP2_ERR_WOULDBLOCK; } log_errf("h2_session_send", "error sending %ld bytes: %d (%s)", (long)length, err, strerror(err)); return NGHTTP2_ERR_CALLBACK_FAILURE; } return nwritten; } static ssize_t h2_session_recv(nghttp2_session *ngh2, uint8_t *buf, size_t length, int flags, void *user_data) { struct h2_session *session = user_data; ssize_t nread; (void)ngh2; (void)flags; session->want_io = IO_WANT_NONE; nread = recv(session->fd, buf, length, 0); if (nread < 0) { int err = errno; if ((EWOULDBLOCK == err) || (EAGAIN == err) || (EINTR == err)) { return NGHTTP2_ERR_WOULDBLOCK; } log_errf("h2_session_recv", "error reading %ld bytes: %d (%s)", (long)length, err, strerror(err)); return NGHTTP2_ERR_CALLBACK_FAILURE; } return nread; } static int h2_session_on_frame_send(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) { size_t i; (void)user_data; switch (frame->hd.type) { case NGHTTP2_HEADERS: if (nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)) { const nghttp2_nv *nva = frame->headers.nva; log_infof("frame send", "FRAME[HEADERS, stream=%d", frame->hd.stream_id); for (i = 0; i < frame->headers.nvlen; ++i) { log_infof("frame send", " %.*s: %.*s", (int)nva[i].namelen, nva[i].name, (int)nva[i].valuelen, nva[i].value); } log_infof("frame send", "]"); } break; case NGHTTP2_DATA: log_infof("frame send", "FRAME[DATA, stream=%d, length=%d, flags=%d]", frame->hd.stream_id, (int)frame->hd.length, (int)frame->hd.flags); break; case NGHTTP2_RST_STREAM: log_infof("frame send", "FRAME[RST, stream=%d]", frame->hd.stream_id); break; case NGHTTP2_WINDOW_UPDATE: log_infof("frame send", "FRAME[WINDOW_UPDATE, stream=%d]", frame->hd.stream_id); break; case NGHTTP2_GOAWAY: log_infof("frame send", "FRAME[GOAWAY]"); break; } return 0; } static int h2_session_on_frame_recv(nghttp2_session *ngh2, const nghttp2_frame *frame, void *user_data) { (void)user_data; switch (frame->hd.type) { case NGHTTP2_HEADERS: if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE) { log_infof("frame recv", "FRAME[HEADERS, stream=%d]", frame->hd.stream_id); } break; case NGHTTP2_DATA: log_infof("frame recv", "FRAME[DATA, stream=%d, len=%lu, eof=%d]", frame->hd.stream_id, frame->hd.length, (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) != 0); break; case NGHTTP2_RST_STREAM: log_infof("frame recv", "FRAME[RST, stream=%d]", frame->hd.stream_id); fprintf(stdout, "[%d] RST\n", frame->hd.stream_id); break; case NGHTTP2_GOAWAY: log_infof("frame recv", "FRAME[GOAWAY]"); break; } return 0; } static int h2_session_on_header(nghttp2_session *ngh2, const nghttp2_frame *frame, const uint8_t *name, size_t namelen, const uint8_t *value, size_t valuelen, uint8_t flags, void *user_data) { struct h2_session *session = user_data; struct h2_stream *stream; (void)flags; (void)user_data; log_infof("frame recv", "stream=%d, HEADER %.*s: %.*s", frame->hd.stream_id, (int)namelen, name, (int)valuelen, value); stream = h2_session_stream_get(session, frame->hd.stream_id); if (stream) { if (namelen == 7 && !strncmp(":status", (const char *)name, namelen)) { stream->http_status = 0; if (valuelen < 10) { char tmp[10], *endp; memcpy(tmp, value, valuelen); tmp[valuelen] = 0; stream->http_status = (int)strtol(tmp, &endp, 10); } if (stream->http_status < 100 || stream->http_status >= 600) { log_errf("on header recv", "stream=%d, invalid :status: %.*s", frame->hd.stream_id, (int)valuelen, value); return NGHTTP2_ERR_CALLBACK_FAILURE; } else { fprintf(stdout, "[%d] :status: %d\n", stream->id, stream->http_status); } } } return 0; } static int h2_session_on_stream_close(nghttp2_session *ngh2, int32_t stream_id, uint32_t error_code, void *user_data) { struct h2_session *session = user_data; struct h2_stream *stream; stream = h2_session_stream_get(session, stream_id); if (stream) { /* closed known stream */ stream->error_code = error_code; stream->closed = 1; if (error_code) stream->reset = 1; if (error_code) { log_errf("stream close", "stream %d closed with error %d", stream_id, error_code); } h2_session_stream_remove(session, stream); if (stream->on_close) stream->on_close(stream); /* last one? */ if (!session->streams) { int rv; rv = nghttp2_session_terminate_session(ngh2, NGHTTP2_NO_ERROR); if (rv) { log_errf("terminate session", "error %d (%s)", rv, nghttp2_strerror(rv)); session->aborted = 1; } } } return 0; } static int h2_session_on_data_chunk_recv(nghttp2_session *ngh2, uint8_t flags, int32_t stream_id, const uint8_t *data, size_t len, void *user_data) { struct h2_session *session = user_data; struct h2_stream *stream; stream = h2_session_stream_get(session, stream_id); if (stream && stream->on_recv_data) { stream->on_recv_data(stream, data, len); } return 0; } static int h2_session_open(struct h2_session *session, const char *server_name, const char *host, uint16_t port) { nghttp2_session_callbacks *cbs = NULL; nghttp2_settings_entry settings[2]; int rv = -1; memset(session, 0, sizeof(*session)); session->server_name = server_name; session->connect_host = host; session->connect_port = port; /* establish socket */ session->fd = open_connection(session->connect_host, session->connect_port); if (session->fd < 0) { log_errf(cmd, "could not connect to %s:%u", session->connect_host, session->connect_port); goto leave; } if (sock_nonblock_nodelay(session->fd)) goto leave; session->want_io = IO_WANT_NONE; log_infof(cmd, "connected to %s via %s:%u", session->server_name, session->connect_host, session->connect_port); rv = nghttp2_session_callbacks_new(&cbs); if (rv) { log_errf("setup callbacks", "error_code=%d, msg=%s\n", rv, nghttp2_strerror(rv)); rv = -1; goto leave; } /* setup session callbacks */ nghttp2_session_callbacks_set_send_callback(cbs, h2_session_send); nghttp2_session_callbacks_set_recv_callback(cbs, h2_session_recv); nghttp2_session_callbacks_set_on_frame_send_callback( cbs, h2_session_on_frame_send); nghttp2_session_callbacks_set_on_frame_recv_callback( cbs, h2_session_on_frame_recv); nghttp2_session_callbacks_set_on_header_callback( cbs, h2_session_on_header); nghttp2_session_callbacks_set_on_stream_close_callback( cbs, h2_session_on_stream_close); nghttp2_session_callbacks_set_on_data_chunk_recv_callback( cbs, h2_session_on_data_chunk_recv); /* create the ngh2 session */ rv = nghttp2_session_client_new(&session->ngh2, cbs, session); if (rv) { log_errf("client new", "error_code=%d, msg=%s\n", rv, nghttp2_strerror(rv)); rv = -1; goto leave; } /* submit initial settings */ settings[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; settings[0].value = 100; settings[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; settings[1].value = 10 * 1024 * 1024; rv = nghttp2_submit_settings(session->ngh2, NGHTTP2_FLAG_NONE, settings, 2); if (rv) { log_errf("submit settings", "error_code=%d, msg=%s\n", rv, nghttp2_strerror(rv)); rv = -1; goto leave; } rv = nghttp2_session_set_local_window_size(session->ngh2, NGHTTP2_FLAG_NONE, 0, 10 * 1024 * 1024); if (rv) { log_errf("set connection window size", "error_code=%d, msg=%s\n", rv, nghttp2_strerror(rv)); rv = -1; goto leave; } rv = 0; leave: if (cbs) nghttp2_session_callbacks_del(cbs); return rv; } static int h2_session_io(struct h2_session *session) { int rv; rv = nghttp2_session_recv(session->ngh2); if (rv) { log_errf("session recv", "error_code=%d, msg=%s\n", rv, nghttp2_strerror(rv)); return 1; } rv = nghttp2_session_send(session->ngh2); if (rv) { log_errf("session send", "error_code=%d, msg=%s\n", rv, nghttp2_strerror(rv)); } return 0; } struct h2_poll_ctx; typedef int h2_poll_ev_cb(struct h2_poll_ctx *pctx, struct pollfd *pfd); struct h2_poll_ctx { struct h2_session *session; struct h2_stream *stream; h2_poll_ev_cb *on_ev; }; static int h2_session_ev(struct h2_poll_ctx *pctx, struct pollfd *pfd) { if (pfd->revents & (POLLIN | POLLOUT)) { h2_session_io(pctx->session); } else if (pfd->revents & POLLHUP) { log_errf("session run", "connection closed"); return -1; } else if (pfd->revents & POLLERR) { log_errf("session run", "connection error"); return -1; } return 0; } static int h2_stream_ev(struct h2_poll_ctx *pctx, struct pollfd *pfd) { if (pfd->revents & (POLLIN | POLLHUP)) { nghttp2_session_resume_data(pctx->session->ngh2, pctx->stream->id); } else if (pfd->revents & (POLLERR)) { nghttp2_submit_rst_stream(pctx->session->ngh2, NGHTTP2_FLAG_NONE, pctx->stream->id, NGHTTP2_STREAM_CLOSED); } return 0; } static nfds_t h2_session_set_poll(struct h2_session *session, struct h2_poll_ctx *pollctxs, struct pollfd *pfds) { nfds_t n = 0; int want_read, want_write; struct h2_stream *stream; want_read = (nghttp2_session_want_read(session->ngh2) || session->want_io == IO_WANT_READ); want_write = (nghttp2_session_want_write(session->ngh2) || session->want_io == IO_WANT_WRITE); if (want_read || want_write) { pollctxs[n].session = session; pollctxs[n].stream = NULL; pollctxs[n].on_ev = h2_session_ev; pfds[n].fd = session->fd; pfds[n].events = pfds[n].revents = 0; if (want_read) pfds[n].events |= (POLLIN | POLLHUP); if (want_write) pfds[n].events |= (POLLOUT | POLLERR); ++n; } for (stream = session->streams; stream; stream = stream->next) { if (stream->fdin >= 0 && !stream->input_closed && !stream->closed) { pollctxs[n].session = session; pollctxs[n].stream = stream; pollctxs[n].on_ev = h2_stream_ev; pfds[n].fd = stream->fdin; pfds[n].revents = 0; pfds[n].events = (POLLIN | POLLHUP); ++n; } } return n; } static void h2_session_run(struct h2_session *session) { struct h2_poll_ctx pollctxs[5]; struct pollfd pfds[5]; nfds_t npollfds, i; npollfds = h2_session_set_poll(session, pollctxs, pfds); while (npollfds) { if (poll(pfds, npollfds, -1) == -1) { log_errf("session run", "poll error %d (%s)", errno, strerror(errno)); break; } for (i = 0; i < npollfds; ++i) { if (pfds[i].revents) { if (pollctxs[i].on_ev(&pollctxs[i], &pfds[i])) { break; } } } npollfds = h2_session_set_poll(session, pollctxs, pfds); if (!session->streams) break; } } static void h2_session_close(struct h2_session *session) { log_infof(cmd, "closed session to %s:%u", session->connect_host, session->connect_port); } /* websocket stream */ struct ws_stream { struct h2_stream s; }; static void ws_stream_on_close(struct h2_stream *stream) { log_infof("ws stream", "stream %d closed", stream->id); if (!stream->reset) fprintf(stdout, "[%d] EOF\n", stream->id); } static void ws_stream_on_recv_data(struct h2_stream *stream, const uint8_t *data, size_t len) { size_t i; log_infof("ws stream", "stream %d recv %lu data bytes", stream->id, (unsigned long)len); for (i = 0; i < len; ++i) { fprintf(stdout, "%s%02x", (i&0xf)? " " : (i? "\n" : ""), data[i]); } fprintf(stdout, "\n"); } static int ws_stream_create(struct ws_stream **pstream, struct uri *uri) { struct ws_stream *stream; stream = calloc(1, sizeof(*stream)); if (!stream) { log_errf("ws stream create", "out of memory"); *pstream = NULL; return -1; } stream->s.uri = uri; stream->s.id = -1; stream->s.on_close = ws_stream_on_close; stream->s.on_recv_data = ws_stream_on_recv_data; *pstream = stream; return 0; } static ssize_t ws_stream_read_req_body(nghttp2_session *ngh2, int32_t stream_id, uint8_t *buf, size_t buflen, uint32_t *pflags, nghttp2_data_source *source, void *user_data) { struct h2_session *session = user_data; struct ws_stream *stream; ssize_t nread = 0; int eof = 0; stream = (struct ws_stream *)h2_session_stream_get(session, stream_id); if (!stream) { log_errf("stream req body", "stream not known"); return NGHTTP2_ERR_CALLBACK_FAILURE; } (void)source; assert(stream->s.fdin >= 0); nread = read(stream->s.fdin, buf, buflen); log_debugf("stream req body", "fread(len=%lu) -> %ld", (unsigned long)buflen, (long)nread); if (nread < 0) { if (errno == EAGAIN) { nread = 0; } else { log_errf("stream req body", "error on input"); return NGHTTP2_ERR_CALLBACK_FAILURE; } } else if (nread == 0) { eof = 1; stream->s.input_closed = 1; } *pflags = stream->s.input_closed? NGHTTP2_DATA_FLAG_EOF : 0; if (nread == 0 && !eof) { return NGHTTP2_ERR_DEFERRED; } return nread; } static int ws_stream_submit(struct ws_stream *stream, struct h2_session *session, const nghttp2_nv *nva, size_t nvalen, int fdin) { nghttp2_data_provider provider, *req_body = NULL; if (fdin >= 0) { sock_nonblock_nodelay(fdin); stream->s.fdin = fdin; provider.read_callback = ws_stream_read_req_body; provider.source.ptr = NULL; req_body = &provider; } else { stream->s.input_closed = 1; } stream->s.id = nghttp2_submit_request(session->ngh2, NULL, nva, nvalen, req_body, stream); if (stream->s.id < 0) { log_errf("ws stream submit", "nghttp2_submit_request: error %d", stream->s.id); return -1; } h2_session_stream_add(session, &stream->s); log_infof("ws stream submit", "stream %d opened for %s%s", stream->s.id, stream->s.uri->authority, stream->s.uri->path); return 0; } static void usage(const char *msg) { if(msg) fprintf(stderr, "%s\n", msg); fprintf(stderr, "usage: [options] ws-uri scenario\n" " run a websocket scenario to the ws-uri, options:\n" " -c host:port connect to host:port\n" " -v increase verbosity\n" "scenarios are:\n" " * fail-proto: CONNECT using wrong :protocol\n" " * miss-authority: CONNECT without :authority header\n" " * miss-path: CONNECT without :path header\n" " * miss-scheme: CONNECT without :scheme header\n" " * miss-version: CONNECT without sec-webSocket-version header\n" " * ws-empty: open valid websocket, do not send anything\n" ); } int main(int argc, char *argv[]) { const char *host = NULL, *scenario; uint16_t port = 80; struct uri uri; struct h2_session session; struct ws_stream *stream; char ch; cmd = argv[0]; while((ch = getopt(argc, argv, "c:vh")) != -1) { switch(ch) { case 'c': if (parse_host_port(&host, &port, NULL, NULL, optarg, strlen(optarg), 80)) { log_errf(cmd, "could not parse connect '%s'", optarg); return 1; } break; case 'h': usage(NULL); return 2; break; case 'v': ++verbose; break; default: usage("invalid option"); return 1; } } argc -= optind; argv += optind; if (argc < 1) { usage("need URL"); return 1; } if (argc < 2) { usage("need scenario"); return 1; } if (parse_uri(&uri, argv[0], strlen(argv[0]))) { log_errf(cmd, "could not parse uri '%s'", argv[0]); return 1; } log_debugf(cmd, "normalized uri: %s://%s:%u%s", uri.scheme, uri.host, uri.port, uri.path? uri.path : ""); scenario = argv[1]; if (!host) { host = uri.host; port = uri.port; } if (h2_session_open(&session, uri.host, host, port)) return 1; if (ws_stream_create(&stream, &uri)) return 1; if (!strcmp(scenario, "ws-stdin")) { const nghttp2_nv nva[] = { MAKE_NV(":method", "CONNECT"), MAKE_NV_CS(":path", stream->s.uri->path), MAKE_NV_CS(":scheme", "http"), MAKE_NV_CS(":authority", stream->s.uri->authority), MAKE_NV_CS(":protocol", "websocket"), MAKE_NV("accept", "*/*"), MAKE_NV("user-agent", "mod_h2/h2ws-test"), MAKE_NV("sec-webSocket-version", "13"), MAKE_NV("sec-webSocket-protocol", "chat"), }; if (ws_stream_submit(stream, &session, nva, sizeof(nva) / sizeof(nva[0]), 0)) return 1; } else if (!strcmp(scenario, "fail-proto")) { const nghttp2_nv nva[] = { MAKE_NV(":method", "CONNECT"), MAKE_NV_CS(":path", stream->s.uri->path), MAKE_NV_CS(":scheme", "http"), MAKE_NV_CS(":authority", stream->s.uri->authority), MAKE_NV_CS(":protocol", "websockets"), MAKE_NV("accept", "*/*"), MAKE_NV("user-agent", "mod_h2/h2ws-test"), MAKE_NV("sec-webSocket-version", "13"), MAKE_NV("sec-webSocket-protocol", "chat"), }; if (ws_stream_submit(stream, &session, nva, sizeof(nva) / sizeof(nva[0]), -1)) return 1; } else if (!strcmp(scenario, "miss-version")) { const nghttp2_nv nva[] = { MAKE_NV(":method", "CONNECT"), MAKE_NV_CS(":path", stream->s.uri->path), MAKE_NV_CS(":scheme", "http"), MAKE_NV_CS(":authority", stream->s.uri->authority), MAKE_NV_CS(":protocol", "websocket"), MAKE_NV("accept", "*/*"), MAKE_NV("user-agent", "mod_h2/h2ws-test"), MAKE_NV("sec-webSocket-protocol", "chat"), }; if (ws_stream_submit(stream, &session, nva, sizeof(nva) / sizeof(nva[0]), -1)) return 1; } else if (!strcmp(scenario, "miss-path")) { const nghttp2_nv nva[] = { MAKE_NV(":method", "CONNECT"), MAKE_NV_CS(":scheme", "http"), MAKE_NV_CS(":authority", stream->s.uri->authority), MAKE_NV_CS(":protocol", "websocket"), MAKE_NV("accept", "*/*"), MAKE_NV("user-agent", "mod_h2/h2ws-test"), MAKE_NV("sec-webSocket-version", "13"), MAKE_NV("sec-webSocket-protocol", "chat"), }; if (ws_stream_submit(stream, &session, nva, sizeof(nva) / sizeof(nva[0]), -1)) return 1; } else if (!strcmp(scenario, "miss-scheme")) { const nghttp2_nv nva[] = { MAKE_NV(":method", "CONNECT"), MAKE_NV_CS(":path", stream->s.uri->path), MAKE_NV_CS(":authority", stream->s.uri->authority), MAKE_NV_CS(":protocol", "websocket"), MAKE_NV("accept", "*/*"), MAKE_NV("user-agent", "mod_h2/h2ws-test"), MAKE_NV("sec-webSocket-version", "13"), MAKE_NV("sec-webSocket-protocol", "chat"), }; if (ws_stream_submit(stream, &session, nva, sizeof(nva) / sizeof(nva[0]), -1)) return 1; } else if (!strcmp(scenario, "miss-authority")) { const nghttp2_nv nva[] = { MAKE_NV(":method", "CONNECT"), MAKE_NV_CS(":path", stream->s.uri->path), MAKE_NV_CS(":scheme", "http"), MAKE_NV_CS(":protocol", "websocket"), MAKE_NV("accept", "*/*"), MAKE_NV("user-agent", "mod_h2/h2ws-test"), MAKE_NV("sec-webSocket-version", "13"), MAKE_NV("sec-webSocket-protocol", "chat"), }; if (ws_stream_submit(stream, &session, nva, sizeof(nva) / sizeof(nva[0]), -1)) return 1; } else { log_errf(cmd, "unknown scenario: %s", scenario); return 1; } h2_session_run(&session); h2_session_close(&session); return 0; }