summaryrefslogtreecommitdiffstats
path: root/3rdparty/openpgm-svn-r1135/pgm/http.c
diff options
context:
space:
mode:
Diffstat (limited to '3rdparty/openpgm-svn-r1135/pgm/http.c')
-rw-r--r--3rdparty/openpgm-svn-r1135/pgm/http.c1735
1 files changed, 1735 insertions, 0 deletions
diff --git a/3rdparty/openpgm-svn-r1135/pgm/http.c b/3rdparty/openpgm-svn-r1135/pgm/http.c
new file mode 100644
index 0000000..442f6f3
--- /dev/null
+++ b/3rdparty/openpgm-svn-r1135/pgm/http.c
@@ -0,0 +1,1735 @@
+/* vim:ts=8:sts=8:sw=4:noai:noexpandtab
+ *
+ * HTTP administrative interface
+ *
+ * Copyright (c) 2006-2010 Miru Limited.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <errno.h>
+#include <inttypes.h>
+#ifndef _WIN32
+# include <netdb.h>
+#else
+# include <io.h>
+# include <lmcons.h>
+# include <process.h>
+#endif
+#include <stdio.h>
+#include <time.h>
+#include <impl/i18n.h>
+#include <impl/framework.h>
+#include <impl/receiver.h>
+#include <impl/socket.h>
+#include <pgm/if.h>
+#include <pgm/version.h>
+
+#include "pgm/http.h"
+#include "htdocs/404.html.h"
+#include "htdocs/base.css.h"
+#include "htdocs/robots.txt.h"
+#include "htdocs/xhtml10_strict.doctype.h"
+
+
+/* OpenSolaris */
+#ifndef LOGIN_NAME_MAX
+# ifdef _WIN32
+# define LOGIN_NAME_MAX (UNLEN + 1)
+# else
+# define LOGIN_NAME_MAX 256
+# endif
+#endif
+
+#ifdef _WIN32
+# define getpid _getpid
+# define read _read
+# define write _write
+# define SHUT_WR SD_SEND
+#endif
+
+#ifdef CONFIG_HAVE_SPRINTF_GROUPING
+# define GROUP_FORMAT "'"
+#else
+# define GROUP_FORMAT ""
+#endif
+
+#define HTTP_BACKLOG 10 /* connections */
+#define HTTP_TIMEOUT 60 /* seconds */
+
+
+/* locals */
+
+struct http_connection_t {
+ pgm_list_t link_;
+ int sock;
+ enum {
+ HTTP_STATE_READ,
+ HTTP_STATE_WRITE,
+ HTTP_STATE_FINWAIT
+ } state;
+
+ char* buf;
+ size_t buflen;
+ size_t bufoff;
+ unsigned status_code;
+ const char* status_text;
+ const char* content_type;
+};
+
+enum {
+ HTTP_MEMORY_STATIC,
+ HTTP_MEMORY_TAKE
+};
+
+static char http_hostname[NI_MAXHOST + 1];
+static char http_address[INET6_ADDRSTRLEN];
+static char http_username[LOGIN_NAME_MAX + 1];
+static int http_pid;
+
+#ifndef _WIN32
+static int http_sock = -1;
+static pthread_t http_thread;
+static void* http_routine (void*);
+#else
+static int http_sock = INVALID_SOCKET;
+static HANDLE http_thread;
+static unsigned __stdcall http_routine (void*);
+#endif
+static int http_max_sock = -1;
+static fd_set http_readfds, http_writefds, http_exceptfds;
+static pgm_list_t* http_socks = NULL;
+static pgm_notify_t http_notify = PGM_NOTIFY_INIT;
+static volatile uint32_t http_ref_count = 0;
+
+
+static int http_tsi_response (struct http_connection_t*, pgm_tsi_t*);
+static void http_each_receiver (pgm_peer_t*, pgm_string_t*);
+static int http_receiver_response (struct http_connection_t*, pgm_peer_t*);
+
+static void default_callback (struct http_connection_t*, const char*);
+static void robots_callback (struct http_connection_t*, const char*);
+static void css_callback (struct http_connection_t*, const char*);
+static void index_callback (struct http_connection_t*, const char*);
+static void interfaces_callback (struct http_connection_t*, const char*);
+static void transports_callback (struct http_connection_t*, const char*);
+static void histograms_callback (struct http_connection_t*, const char*);
+
+static struct {
+ const char* path;
+ void (*callback) (struct http_connection_t*, const char*);
+} http_directory[] = {
+ { "/robots.txt", robots_callback },
+ { "/base.css", css_callback },
+ { "/", index_callback },
+ { "/interfaces", interfaces_callback },
+ { "/transports", transports_callback }
+#ifdef CONFIG_HISTOGRAMS
+ ,{ "/histograms", histograms_callback }
+#endif
+};
+
+
+static
+int
+http_sock_rcvtimeo (
+ int sock,
+ int seconds
+ )
+{
+#if defined( sun )
+ return 0;
+#elif !defined( _WIN32 )
+ const struct timeval timeout = { .tv_sec = seconds, .tv_usec = 0 };
+ return setsockopt (sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout));
+#else
+ const int optval = seconds * 1000;
+ return setsockopt (sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&optval, sizeof(optval));
+#endif
+}
+
+static
+int
+http_sock_sndtimeo (
+ int sock,
+ int seconds
+ )
+{
+#if defined( sun )
+ return 0;
+#elif !defined( _WIN32 )
+ const struct timeval timeout = { .tv_sec = seconds, .tv_usec = 0 };
+ return setsockopt (sock, SOL_SOCKET, SO_SNDTIMEO, (const char*)&timeout, sizeof(timeout));
+#else
+ const int optval = seconds * 1000;
+ return setsockopt (sock, SOL_SOCKET, SO_SNDTIMEO, (const char*)&optval, sizeof(optval));
+#endif
+}
+
+bool
+pgm_http_init (
+ uint16_t http_port,
+ pgm_error_t** error
+ )
+{
+ int e;
+
+ if (pgm_atomic_exchange_and_add32 (&http_ref_count, 1) > 0)
+ return TRUE;
+
+/* resolve and store relatively constant runtime information */
+ if (0 != gethostname (http_hostname, sizeof(http_hostname))) {
+ const int save_errno = errno;
+ pgm_set_error (error,
+ PGM_ERROR_DOMAIN_HTTP,
+ pgm_error_from_errno (save_errno),
+ _("Resolving hostname: %s"),
+ strerror (save_errno));
+ goto err_cleanup;
+ }
+ struct addrinfo hints = {
+ .ai_family = AF_UNSPEC,
+ .ai_socktype = SOCK_STREAM,
+ .ai_protocol = IPPROTO_TCP,
+ .ai_flags = AI_ADDRCONFIG
+ }, *res = NULL;
+ e = getaddrinfo (http_hostname, NULL, &hints, &res);
+ if (0 != e) {
+ pgm_set_error (error,
+ PGM_ERROR_DOMAIN_HTTP,
+ pgm_error_from_eai_errno (e, errno),
+ _("Resolving hostname address: %s"),
+ gai_strerror (e));
+ goto err_cleanup;
+ }
+ e = getnameinfo (res->ai_addr, res->ai_addrlen,
+ http_address, sizeof(http_address),
+ NULL, 0,
+ NI_NUMERICHOST);
+ if (0 != e) {
+ pgm_set_error (error,
+ PGM_ERROR_DOMAIN_HTTP,
+ pgm_error_from_eai_errno (e, errno),
+ _("Resolving numeric hostname: %s"),
+ gai_strerror (e));
+ goto err_cleanup;
+ }
+ freeaddrinfo (res);
+#ifndef _WIN32
+ e = getlogin_r (http_username, sizeof(http_username));
+ if (0 != e) {
+ pgm_set_error (error,
+ PGM_ERROR_DOMAIN_HTTP,
+ pgm_error_from_errno (errno),
+ _("Retrieving user name: %s"),
+ strerror (errno));
+ goto err_cleanup;
+ }
+#else
+ wchar_t wusername[UNLEN + 1];
+ DWORD nSize = PGM_N_ELEMENTS( wusername );
+ if (!GetUserNameW (wusername, &nSize)) {
+ const DWORD save_errno = GetLastError();
+ char winstr[1024];
+ pgm_set_error (error,
+ PGM_ERROR_DOMAIN_HTTP,
+ pgm_error_from_win_errno (save_errno),
+ _("Retrieving user name: %s"),
+ pgm_win_strerror (winstr, sizeof(winstr), save_errno));
+ goto err_cleanup;
+ }
+ WideCharToMultiByte (CP_UTF8, 0, wusername, nSize + 1, http_username, sizeof(http_username), NULL, NULL);
+#endif /* _WIN32 */
+ http_pid = getpid();
+
+/* create HTTP listen socket */
+ if ((http_sock = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
+#ifndef _WIN32
+ pgm_set_error (error,
+ PGM_ERROR_DOMAIN_HTTP,
+ pgm_error_from_errno (errno),
+ _("Creating HTTP socket: %s"),
+ strerror (errno));
+#else
+ const int save_errno = WSAGetLastError();
+ pgm_set_error (error,
+ PGM_ERROR_DOMAIN_HTTP,
+ pgm_error_from_wsa_errno (save_errno),
+ _("Creating HTTP socket: %s"),
+ pgm_wsastrerror (save_errno));
+#endif
+ goto err_cleanup;
+ }
+ const int v = 1;
+ if (0 != setsockopt (http_sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&v, sizeof(v))) {
+#ifndef _WIN32
+ pgm_set_error (error,
+ PGM_ERROR_DOMAIN_HTTP,
+ pgm_error_from_errno (errno),
+ _("Enabling reuse of socket local address: %s"),
+ strerror (errno));
+#else
+ const int save_errno = WSAGetLastError();
+ pgm_set_error (error,
+ PGM_ERROR_DOMAIN_HTTP,
+ pgm_error_from_wsa_errno (save_errno),
+ _("Enabling reuse of socket local address: %s"),
+ pgm_wsastrerror (save_errno));
+#endif
+ goto err_cleanup;
+ }
+ if (0 != http_sock_rcvtimeo (http_sock, HTTP_TIMEOUT) ||
+ 0 != http_sock_sndtimeo (http_sock, HTTP_TIMEOUT)) {
+#ifndef _WIN32
+ pgm_set_error (error,
+ PGM_ERROR_DOMAIN_HTTP,
+ pgm_error_from_errno (errno),
+ _("Setting socket timeout: %s"),
+ strerror (errno));
+#else
+ const int save_errno = WSAGetLastError();
+ pgm_set_error (error,
+ PGM_ERROR_DOMAIN_HTTP,
+ pgm_error_from_wsa_errno (save_errno),
+ _("Setting socket timeout: %s"),
+ pgm_wsastrerror (save_errno));
+#endif
+ goto err_cleanup;
+ }
+ struct sockaddr_in http_addr;
+ memset (&http_addr, 0, sizeof(http_addr));
+ http_addr.sin_family = AF_INET;
+ http_addr.sin_addr.s_addr = INADDR_ANY;
+ http_addr.sin_port = htons (http_port);
+ if (0 != bind (http_sock, (struct sockaddr*)&http_addr, sizeof(http_addr))) {
+ char addr[INET6_ADDRSTRLEN];
+ pgm_sockaddr_ntop ((struct sockaddr*)&http_addr, addr, sizeof(addr));
+#ifndef _WIN32
+ pgm_set_error (error,
+ PGM_ERROR_DOMAIN_HTTP,
+ pgm_error_from_errno (errno),
+ _("Binding HTTP socket to address %s: %s"),
+ addr,
+ strerror (errno));
+#else
+ const int save_errno = WSAGetLastError();
+ pgm_set_error (error,
+ PGM_ERROR_DOMAIN_HTTP,
+ pgm_error_from_wsa_errno (save_errno),
+ _("Binding HTTP socket to address %s: %s"),
+ addr,
+ pgm_wsastrerror (save_errno));
+#endif
+ goto err_cleanup;
+ }
+ if (listen (http_sock, HTTP_BACKLOG) < 0) {
+#ifndef _WIN32
+ pgm_set_error (error,
+ PGM_ERROR_DOMAIN_HTTP,
+ pgm_error_from_errno (errno),
+ _("Listening to HTTP socket: %s"),
+ strerror (errno));
+#else
+ const int save_errno = WSAGetLastError();
+ pgm_set_error (error,
+ PGM_ERROR_DOMAIN_HTTP,
+ pgm_error_from_wsa_errno (save_errno),
+ _("Listening to HTTP socket: %s"),
+ pgm_wsastrerror (save_errno));
+#endif
+ goto err_cleanup;
+ }
+
+/* non-blocking notification of new connections */
+ pgm_sockaddr_nonblocking (http_sock, TRUE);
+
+/* create notification channel */
+ if (0 != pgm_notify_init (&http_notify)) {
+ pgm_set_error (error,
+ PGM_ERROR_DOMAIN_HTTP,
+ pgm_error_from_errno (errno),
+ _("Creating HTTP notification channel: %s"),
+ strerror (errno));
+ goto err_cleanup;
+ }
+
+/* spawn thread to handle HTTP requests */
+#ifndef _WIN32
+ const int status = pthread_create (&http_thread, NULL, &http_routine, NULL);
+ if (0 != status) {
+ pgm_set_error (error,
+ PGM_ERROR_DOMAIN_HTTP,
+ pgm_error_from_errno (errno),
+ _("Creating HTTP thread: %s"),
+ strerror (errno));
+ goto err_cleanup;
+ }
+#else
+ http_thread = (HANDLE)_beginthreadex (NULL, 0, &http_routine, NULL, 0, NULL);
+ const int save_errno = errno;
+ if (0 == http_thread) {
+ pgm_set_error (error,
+ PGM_ERROR_DOMAIN_HTTP,
+ pgm_error_from_errno (save_errno),
+ _("Creating HTTP thread: %s"),
+ strerror (save_errno));
+ goto err_cleanup;
+ }
+#endif /* _WIN32 */
+ pgm_minor (_("Web interface: http://%s:%i"),
+ http_hostname,
+ http_port);
+ return TRUE;
+
+err_cleanup:
+#ifndef _WIN32
+ if (-1 != http_sock) {
+ close (http_sock);
+ http_sock = -1;
+ }
+#else
+ if (INVALID_SOCKET != http_sock) {
+ closesocket (http_sock);
+ http_sock = INVALID_SOCKET;
+ }
+#endif /* _WIN32 */
+ if (pgm_notify_is_valid (&http_notify)) {
+ pgm_notify_destroy (&http_notify);
+ }
+ pgm_atomic_dec32 (&http_ref_count);
+ return FALSE;
+}
+
+/* notify HTTP thread to shutdown, wait for shutdown and cleanup.
+ */
+
+bool
+pgm_http_shutdown (void)
+{
+ pgm_return_val_if_fail (pgm_atomic_read32 (&http_ref_count) > 0, FALSE);
+
+ if (pgm_atomic_exchange_and_add32 (&http_ref_count, (uint32_t)-1) != 1)
+ return TRUE;
+
+ pgm_notify_send (&http_notify);
+#ifndef _WIN32
+ pthread_join (http_thread, NULL);
+#else
+ CloseHandle (http_thread);
+#endif
+#ifndef _WIN32
+ if (-1 != http_sock) {
+ close (http_sock);
+ http_sock = -1;
+ }
+#else
+ if (INVALID_SOCKET != http_sock) {
+ closesocket (http_sock);
+ http_sock = INVALID_SOCKET;
+ }
+#endif /* _WIN32 */
+ pgm_notify_destroy (&http_notify);
+ return TRUE;
+}
+
+/* accept a new incoming HTTP connection.
+ */
+
+static
+void
+http_accept (
+ int listen_sock
+ )
+{
+/* new connection */
+ struct sockaddr_storage addr;
+ socklen_t addrlen = sizeof(addr);
+ int new_sock = accept (listen_sock, (struct sockaddr*)&addr, &addrlen);
+ if (-1 == new_sock) {
+ if (EAGAIN == errno)
+ return;
+#ifndef _WIN32
+ pgm_warn (_("HTTP accept: %s"), strerror (errno));
+#else
+ const int save_errno = WSAGetLastError();
+ pgm_warn (_("HTTP accept: %s"), pgm_wsastrerror (save_errno));
+#endif
+ return;
+ }
+
+#ifndef _WIN32
+/* out of bounds file descriptor for select() */
+ if (new_sock >= FD_SETSIZE) {
+ close (new_sock);
+ pgm_warn (_("Rejected new HTTP client socket due to out of bounds file descriptor."));
+ return;
+ }
+#endif
+
+ pgm_sockaddr_nonblocking (new_sock, TRUE);
+
+ struct http_connection_t* connection = pgm_new0 (struct http_connection_t, 1);
+ connection->sock = new_sock;
+ connection->state = HTTP_STATE_READ;
+ http_socks = pgm_list_prepend_link (http_socks, &connection->link_);
+ FD_SET( new_sock, &http_readfds );
+ FD_SET( new_sock, &http_exceptfds );
+ if (new_sock > http_max_sock)
+ http_max_sock = new_sock;
+}
+
+static
+void
+http_close (
+ struct http_connection_t* connection
+ )
+{
+#ifndef _WIN32
+ if (0 != close (connection->sock)) {
+ pgm_warn (_("Close HTTP client socket: %s"), strerror (errno));
+ }
+#else
+ if (0 != closesocket (connection->sock)) {
+ const int save_errno = WSAGetLastError();
+ pgm_warn (_("Close HTTP client socket: %s"), pgm_wsastrerror (save_errno));
+ }
+#endif
+ switch (connection->state) {
+ case HTTP_STATE_READ:
+ case HTTP_STATE_FINWAIT:
+ FD_CLR( connection->sock, &http_readfds );
+ break;
+ case HTTP_STATE_WRITE:
+ FD_CLR( connection->sock, &http_writefds );
+ break;
+ }
+ FD_CLR( connection->sock, &http_exceptfds );
+ http_socks = pgm_list_remove_link (http_socks, &connection->link_);
+ if (connection->buflen > 0) {
+ pgm_free (connection->buf);
+ connection->buf = NULL;
+ connection->buflen = 0;
+ }
+/* find new highest fd */
+ if (connection->sock == http_max_sock)
+ {
+ http_max_sock = -1;
+ for (pgm_list_t* list = http_socks; list; list = list->next)
+ {
+ struct http_connection_t* c = (void*)list;
+ if (c->sock > http_max_sock)
+ http_max_sock = c->sock;
+ }
+ }
+ pgm_free (connection);
+}
+
+/* non-blocking read an incoming HTTP request
+ */
+
+static
+void
+http_read (
+ struct http_connection_t* connection
+ )
+{
+ for (;;)
+ {
+/* grow buffer as needed */
+ if (connection->bufoff + 1024 > connection->buflen) {
+ connection->buf = pgm_realloc (connection->buf, connection->buflen + 1024);
+ connection->buflen += 1024;
+ }
+ const ssize_t bytes_read = recv (connection->sock, &connection->buf[ connection->bufoff ], connection->buflen - connection->bufoff, 0);
+ if (bytes_read < 0) {
+ if (EINTR == errno || EAGAIN == errno)
+ return;
+#ifndef _WIN32
+ pgm_warn (_("HTTP client read: %s"), strerror (errno));
+#else
+ const int save_errno = WSAGetLastError();
+ pgm_warn (_("HTTP client read: %s"), pgm_wsastrerror (save_errno));
+#endif
+ http_close (connection);
+ return;
+ }
+
+/* complete */
+ if (strstr (connection->buf, "\r\n\r\n"))
+ break;
+ }
+
+/* process request, e.g. GET /index.html HTTP/1.1\r\n
+ */
+ connection->buf[ connection->buflen - 1 ] = '\0';
+ if (0 != memcmp (connection->buf, "GET ", strlen("GET "))) {
+/* 501 (not implemented) */
+ http_close (connection);
+ return;
+ }
+
+ char* request_uri = connection->buf + strlen("GET ");
+ char* p = request_uri;
+ do {
+ if (*p == '?' || *p == ' ') {
+ *p = '\0';
+ break;
+ }
+ } while (*(++p));
+
+ connection->status_code = 200; /* OK */
+ connection->status_text = "OK";
+ connection->content_type = "text/html";
+ connection->bufoff = 0;
+ for (unsigned i = 0; i < PGM_N_ELEMENTS(http_directory); i++)
+ {
+ if (0 == strcmp (request_uri, http_directory[i].path))
+ {
+ http_directory[i].callback (connection, request_uri);
+ goto complete;
+ }
+ }
+ default_callback (connection, request_uri);
+
+complete:
+ connection->state = HTTP_STATE_WRITE;
+ FD_CLR( connection->sock, &http_readfds );
+ FD_SET( connection->sock, &http_writefds );
+}
+
+/* non-blocking write a HTTP response
+ */
+
+static
+void
+http_write (
+ struct http_connection_t* connection
+ )
+{
+ do {
+ const ssize_t bytes_written = send (connection->sock, &connection->buf[ connection->bufoff ], connection->buflen - connection->bufoff, 0);
+ if (bytes_written < 0) {
+ if (EINTR == errno || EAGAIN == errno)
+ return;
+#ifndef _WIN32
+ pgm_warn (_("HTTP client write: %s"), strerror (errno));
+#else
+ const int save_errno = WSAGetLastError();
+ pgm_warn (_("HTTP client write: %s"), pgm_wsastrerror (save_errno));
+#endif
+ http_close (connection);
+ return;
+ }
+ connection->bufoff += bytes_written;
+ } while (connection->bufoff < connection->buflen);
+
+ if (0 == shutdown (connection->sock, SHUT_WR)) {
+ http_close (connection);
+ } else {
+ pgm_debug ("HTTP socket entering finwait state.");
+ connection->state = HTTP_STATE_FINWAIT;
+ FD_CLR( connection->sock, &http_writefds );
+ FD_SET( connection->sock, &http_readfds );
+ }
+}
+
+/* read and discard pending data waiting for FIN
+ */
+
+static
+void
+http_finwait (
+ struct http_connection_t* connection
+ )
+{
+ char buf[1024];
+ const ssize_t bytes_read = read (connection->sock, buf, sizeof(buf));
+ if (bytes_read < 0 && (EINTR == errno || EAGAIN == errno))
+ return;
+ http_close (connection);
+}
+
+static
+void
+http_process (
+ struct http_connection_t* connection
+ )
+{
+ switch (connection->state) {
+ case HTTP_STATE_READ: http_read (connection); break;
+ case HTTP_STATE_WRITE: http_write (connection); break;
+ case HTTP_STATE_FINWAIT: http_finwait (connection); break;
+ }
+}
+
+static
+void
+http_set_status (
+ struct http_connection_t* connection,
+ int status_code,
+ const char* status_text
+ )
+{
+ connection->status_code = status_code;
+ connection->status_text = status_text;
+}
+
+static
+void
+http_set_content_type (
+ struct http_connection_t* connection,
+ const char* content_type
+ )
+{
+ connection->content_type = content_type;
+}
+
+/* finalise response buffer with headers and content */
+
+static
+void
+http_set_static_response (
+ struct http_connection_t* connection,
+ const char* content,
+ size_t content_length
+ )
+{
+ pgm_string_t* response = pgm_string_new (NULL);
+ pgm_string_printf (response, "HTTP/1.0 %d %s\r\n"
+ "Server: OpenPGM HTTP Server %u.%u.%u\r\n"
+ "Last-Modified: Fri, 1 Jan 2010, 00:00:01 GMT\r\n"
+#ifndef _MSC_VER
+ "Content-Length: %zd\r\n"
+#else
+ "Content-Length: %d\r\n"
+#endif
+ "Content-Type: %s\r\n"
+ "Connection: close\r\n"
+ "\r\n",
+ connection->status_code,
+ connection->status_text,
+ pgm_major_version, pgm_minor_version, pgm_micro_version,
+#ifndef _MSC_VER
+ content_length,
+#else
+ (int)content_length,
+#endif
+ connection->content_type
+ );
+ pgm_string_append (response, content);
+ if (connection->buflen)
+ pgm_free (connection->buf);
+ connection->buflen = response->len;
+ connection->buf = pgm_string_free (response, FALSE);
+}
+
+static
+void
+http_set_response (
+ struct http_connection_t* connection,
+ char* content,
+ size_t content_length
+ )
+{
+ pgm_string_t* response = pgm_string_new (NULL);
+ pgm_string_printf (response, "HTTP/1.0 %d %s\r\n"
+ "Server: OpenPGM HTTP Server %u.%u.%u\r\n"
+#ifndef _MSC_VER
+ "Content-Length: %zd\r\n"
+#else
+ "Content-Length: %d\r\n"
+#endif
+ "Content-Type: %s\r\n"
+ "Connection: close\r\n"
+ "\r\n",
+ connection->status_code,
+ connection->status_text,
+ pgm_major_version, pgm_minor_version, pgm_micro_version,
+#ifndef _MSC_VER
+ content_length,
+#else
+ (int)content_length,
+#endif
+ connection->content_type
+ );
+ pgm_string_append (response, content);
+ pgm_free (content);
+ if (connection->buflen)
+ pgm_free (connection->buf);
+ connection->buflen = response->len;
+ connection->buf = pgm_string_free (response, FALSE);
+}
+
+/* Thread routine for processing HTTP requests
+ */
+
+static
+#ifndef _WIN32
+void*
+#else
+unsigned
+__stdcall
+#endif
+http_routine (
+ PGM_GNUC_UNUSED void* arg
+ )
+{
+ const int notify_fd = pgm_notify_get_fd (&http_notify);
+ const int max_fd = MAX( notify_fd, http_sock );
+
+ FD_ZERO( &http_readfds );
+ FD_ZERO( &http_writefds );
+ FD_ZERO( &http_exceptfds );
+ FD_SET( notify_fd, &http_readfds );
+ FD_SET( http_sock, &http_readfds );
+
+ for (;;)
+ {
+ int fds = MAX( http_max_sock, max_fd ) + 1;
+ fd_set readfds = http_readfds, writefds = http_writefds, exceptfds = http_exceptfds;
+
+ fds = select (fds, &readfds, &writefds, &exceptfds, NULL);
+/* signal interrupt */
+ if (PGM_UNLIKELY(fds < 0 && EINTR == errno))
+ continue;
+/* terminate */
+ if (PGM_UNLIKELY(FD_ISSET( notify_fd, &readfds )))
+ break;
+/* new connection */
+ if (FD_ISSET( http_sock, &readfds )) {
+ http_accept (http_sock);
+ continue;
+ }
+/* existing connection */
+ for (pgm_list_t* list = http_socks; list;)
+ {
+ struct http_connection_t* c = (void*)list;
+ list = list->next;
+ if ((FD_ISSET( c->sock, &readfds ) && HTTP_STATE_READ == c->state) ||
+ (FD_ISSET( c->sock, &writefds ) && HTTP_STATE_WRITE == c->state) ||
+ (FD_ISSET( c->sock, &exceptfds )))
+ {
+ http_process (c);
+ }
+ }
+ }
+
+/* cleanup */
+#ifndef _WIN32
+ return NULL;
+#else
+ _endthread();
+ return 0;
+#endif /* WIN32 */
+}
+
+/* add xhtml doctype and head, populate with runtime values
+ */
+
+typedef enum {
+ HTTP_TAB_GENERAL_INFORMATION,
+ HTTP_TAB_INTERFACES,
+ HTTP_TAB_TRANSPORTS,
+ HTTP_TAB_HISTOGRAMS
+} http_tab_e;
+
+static
+pgm_string_t*
+http_create_response (
+ const char* subtitle,
+ http_tab_e tab
+ )
+{
+ pgm_assert (NULL != subtitle);
+ pgm_assert (tab == HTTP_TAB_GENERAL_INFORMATION ||
+ tab == HTTP_TAB_INTERFACES ||
+ tab == HTTP_TAB_TRANSPORTS ||
+ tab == HTTP_TAB_HISTOGRAMS);
+
+/* surprising deficiency of GLib is no support of display locale time */
+ char timestamp[100];
+ time_t now;
+ time (&now);
+ const struct tm* time_ptr = localtime (&now);
+#ifndef _WIN32
+ strftime (timestamp, sizeof(timestamp), "%c", time_ptr);
+#else
+ wchar_t wtimestamp[100];
+ const size_t slen = strftime (timestamp, sizeof(timestamp), "%c", time_ptr);
+ const size_t wslen = MultiByteToWideChar (CP_ACP, 0, timestamp, slen, wtimestamp, 100);
+ WideCharToMultiByte (CP_UTF8, 0, wtimestamp, wslen + 1, timestamp, sizeof(timestamp), NULL, NULL);
+#endif
+
+ pgm_string_t* response = pgm_string_new (WWW_XHTML10_STRICT_DOCTYPE);
+ pgm_string_append_printf (response, "\n<head>"
+ "<title>%s - %s</title>"
+ "<link rel=\"stylesheet\" href=\"/base.css\" type=\"text/css\" charset=\"utf-8\" />"
+ "</head>\n"
+ "<body>"
+ "<div id=\"header\">"
+ "<span id=\"hostname\">%s</span>"
+ " | <span id=\"banner\"><a href=\"http://code.google.com/p/openpgm/\">OpenPGM</a> %u.%u.%u</span>"
+ " | <span id=\"timestamp\">%s</span>"
+ "</div>"
+ "<div id=\"navigation\">"
+ "<a href=\"/\"><span class=\"tab\" id=\"tab%s\">General Information</span></a>"
+ "<a href=\"/interfaces\"><span class=\"tab\" id=\"tab%s\">Interfaces</span></a>"
+ "<a href=\"/transports\"><span class=\"tab\" id=\"tab%s\">Transports</span></a>"
+#ifdef CONFIG_HISTOGRAMS
+ "<a href=\"/histograms\"><span class=\"tab\" id=\"tab%s\">Histograms</span></a>"
+#endif
+ "<div id=\"tabline\"></div>"
+ "</div>"
+ "<div id=\"content\">",
+ http_hostname,
+ subtitle,
+ http_hostname,
+ pgm_major_version, pgm_minor_version, pgm_micro_version,
+ timestamp,
+ tab == HTTP_TAB_GENERAL_INFORMATION ? "top" : "bottom",
+ tab == HTTP_TAB_INTERFACES ? "top" : "bottom",
+ tab == HTTP_TAB_TRANSPORTS ? "top" : "bottom"
+#ifdef CONFIG_HISTOGRAMS
+ ,tab == HTTP_TAB_HISTOGRAMS ? "top" : "bottom"
+#endif
+ );
+
+ return response;
+}
+
+static
+void
+http_finalize_response (
+ struct http_connection_t* connection,
+ pgm_string_t* response
+ )
+{
+ pgm_string_append (response, "</div>"
+ "<div id=\"footer\">"
+ "&copy;2010 Miru"
+ "</div>"
+ "</body>\n"
+ "</html>");
+
+ char* buf = pgm_string_free (response, FALSE);
+ http_set_response (connection, buf, strlen (buf));
+}
+
+static
+void
+robots_callback (
+ struct http_connection_t* connection,
+ PGM_GNUC_UNUSED const char* path
+ )
+{
+ http_set_content_type (connection, "text/plain");
+ http_set_static_response (connection, WWW_ROBOTS_TXT, strlen(WWW_ROBOTS_TXT));
+}
+
+static
+void
+css_callback (
+ struct http_connection_t* connection,
+ PGM_GNUC_UNUSED const char* path
+ )
+{
+ http_set_content_type (connection, "text/css");
+ http_set_static_response (connection, WWW_BASE_CSS, strlen(WWW_BASE_CSS));
+}
+
+static
+void
+index_callback (
+ struct http_connection_t* connection,
+ const char* path
+ )
+{
+ if (strlen (path) > 1) {
+ default_callback (connection, path);
+ return;
+ }
+ pgm_rwlock_reader_lock (&pgm_sock_list_lock);
+ const unsigned transport_count = pgm_slist_length (pgm_sock_list);
+ pgm_rwlock_reader_unlock (&pgm_sock_list_lock);
+
+ pgm_string_t* response = http_create_response ("OpenPGM", HTTP_TAB_GENERAL_INFORMATION);
+ pgm_string_append_printf (response, "<table>"
+ "<tr>"
+ "<th>host name:</th><td>%s</td>"
+ "</tr><tr>"
+ "<th>user name:</th><td>%s</td>"
+ "</tr><tr>"
+ "<th>IP address:</th><td>%s</td>"
+ "</tr><tr>"
+ "<th>transports:</th><td><a href=\"/transports\">%i</a></td>"
+ "</tr><tr>"
+ "<th>process ID:</th><td>%i</td>"
+ "</tr>"
+ "</table>\n",
+ http_hostname,
+ http_username,
+ http_address,
+ transport_count,
+ http_pid);
+ http_finalize_response (connection, response);
+}
+
+static
+void
+interfaces_callback (
+ struct http_connection_t* connection,
+ PGM_GNUC_UNUSED const char* path
+ )
+{
+ pgm_string_t* response = http_create_response ("Interfaces", HTTP_TAB_INTERFACES);
+ pgm_string_append (response, "<PRE>");
+ struct pgm_ifaddrs_t *ifap, *ifa;
+ pgm_error_t* err = NULL;
+ if (!pgm_getifaddrs (&ifap, &err)) {
+ pgm_string_append_printf (response, "pgm_getifaddrs(): %s", (err && err->message) ? err->message : "(null)");
+ http_finalize_response (connection, response);
+ return;
+ }
+ for (ifa = ifap; ifa; ifa = ifa->ifa_next)
+ {
+ int i = NULL == ifa->ifa_addr ? 0 : pgm_if_nametoindex (ifa->ifa_addr->sa_family, ifa->ifa_name);
+ char rname[IF_NAMESIZE * 2 + 3];
+ char b[IF_NAMESIZE * 2 + 3];
+
+ pgm_if_indextoname (i, rname);
+ sprintf (b, "%s (%s)", ifa->ifa_name, rname);
+
+ if (NULL == ifa->ifa_addr ||
+ (ifa->ifa_addr->sa_family != AF_INET &&
+ ifa->ifa_addr->sa_family != AF_INET6) )
+ {
+ pgm_string_append_printf (response,
+ "#%d name %-15.15s ---- %-46.46s scope 0 status %s loop %s b/c %s m/c %s<BR/>\n",
+ i,
+ b,
+ "",
+ ifa->ifa_flags & IFF_UP ? "UP " : "DOWN",
+ ifa->ifa_flags & IFF_LOOPBACK ? "YES" : "NO ",
+ ifa->ifa_flags & IFF_BROADCAST ? "YES" : "NO ",
+ ifa->ifa_flags & IFF_MULTICAST ? "YES" : "NO "
+ );
+ continue;
+ }
+
+ char s[INET6_ADDRSTRLEN];
+ getnameinfo (ifa->ifa_addr, pgm_sockaddr_len(ifa->ifa_addr),
+ s, sizeof(s),
+ NULL, 0,
+ NI_NUMERICHOST);
+ pgm_string_append_printf (response,
+ "#%d name %-15.15s IPv%i %-46.46s scope %u status %s loop %s b/c %s m/c %s<BR/>\n",
+ i,
+ b,
+ ifa->ifa_addr->sa_family == AF_INET ? 4 : 6,
+ s,
+ (unsigned)pgm_sockaddr_scope_id(ifa->ifa_addr),
+ ifa->ifa_flags & IFF_UP ? "UP " : "DOWN",
+ ifa->ifa_flags & IFF_LOOPBACK ? "YES" : "NO ",
+ ifa->ifa_flags & IFF_BROADCAST ? "YES" : "NO ",
+ ifa->ifa_flags & IFF_MULTICAST ? "YES" : "NO "
+ );
+ }
+ pgm_freeifaddrs (ifap);
+ pgm_string_append (response, "</PRE>\n");
+ http_finalize_response (connection, response);
+}
+
+static
+void
+transports_callback (
+ struct http_connection_t* connection,
+ PGM_GNUC_UNUSED const char* path
+ )
+{
+ pgm_string_t* response = http_create_response ("Transports", HTTP_TAB_TRANSPORTS);
+ pgm_string_append (response, "<div class=\"bubbly\">"
+ "\n<table cellspacing=\"0\">"
+ "<tr>"
+ "<th>Group address</th>"
+ "<th>Dest port</th>"
+ "<th>Source GSI</th>"
+ "<th>Source port</th>"
+ "</tr>"
+ );
+
+ if (pgm_sock_list)
+ {
+ pgm_rwlock_reader_lock (&pgm_sock_list_lock);
+
+ pgm_slist_t* list = pgm_sock_list;
+ while (list)
+ {
+ pgm_slist_t* next = list->next;
+ pgm_sock_t* sock = list->data;
+
+ char group_address[INET6_ADDRSTRLEN];
+ getnameinfo ((struct sockaddr*)&sock->send_gsr.gsr_group, pgm_sockaddr_len ((struct sockaddr*)&sock->send_gsr.gsr_group),
+ group_address, sizeof(group_address),
+ NULL, 0,
+ NI_NUMERICHOST);
+ char gsi[ PGM_GSISTRLEN ];
+ pgm_gsi_print_r (&sock->tsi.gsi, gsi, sizeof(gsi));
+ const uint16_t sport = ntohs (sock->tsi.sport);
+ const uint16_t dport = ntohs (sock->dport);
+ pgm_string_append_printf (response, "<tr>"
+ "<td>%s</td>"
+ "<td>%i</td>"
+ "<td><a href=\"/%s.%u\">%s</a></td>"
+ "<td><a href=\"/%s.%u\">%u</a></td>"
+ "</tr>",
+ group_address,
+ dport,
+ gsi, sport,
+ gsi,
+ gsi, sport,
+ sport);
+ list = next;
+ }
+ pgm_rwlock_reader_unlock (&pgm_sock_list_lock);
+ }
+ else
+ {
+/* no transports */
+ pgm_string_append (response, "<tr>"
+ "<td colspan=\"6\"><div class=\"empty\">This transport has no peers.</div></td>"
+ "</tr>"
+ );
+ }
+
+ pgm_string_append (response, "</table>\n"
+ "</div>");
+ http_finalize_response (connection, response);
+}
+
+static
+void
+histograms_callback (
+ struct http_connection_t* connection,
+ PGM_GNUC_UNUSED const char* path
+ )
+{
+ pgm_string_t* response = http_create_response ("Histograms", HTTP_TAB_HISTOGRAMS);
+ pgm_histogram_write_html_graph_all (response);
+ http_finalize_response (connection, response);
+}
+
+static
+void
+default_callback (
+ struct http_connection_t* connection,
+ const char* path
+ )
+{
+ pgm_tsi_t tsi;
+ const int count = sscanf (path, "/%hhu.%hhu.%hhu.%hhu.%hhu.%hhu.%hu",
+ (unsigned char*)&tsi.gsi.identifier[0],
+ (unsigned char*)&tsi.gsi.identifier[1],
+ (unsigned char*)&tsi.gsi.identifier[2],
+ (unsigned char*)&tsi.gsi.identifier[3],
+ (unsigned char*)&tsi.gsi.identifier[4],
+ (unsigned char*)&tsi.gsi.identifier[5],
+ &tsi.sport);
+ tsi.sport = htons (tsi.sport);
+ if (count == 7)
+ {
+ int retval = http_tsi_response (connection, &tsi);
+ if (!retval) return;
+ }
+
+ http_set_status (connection, 404, "Not Found");
+ http_set_static_response (connection, WWW_404_HTML, strlen(WWW_404_HTML));
+}
+
+static
+int
+http_tsi_response (
+ struct http_connection_t* connection,
+ pgm_tsi_t* tsi
+ )
+{
+/* first verify this is a valid TSI */
+ pgm_rwlock_reader_lock (&pgm_sock_list_lock);
+
+ pgm_sock_t* sock = NULL;
+ pgm_slist_t* list = pgm_sock_list;
+ while (list)
+ {
+ pgm_sock_t* list_sock = (pgm_sock_t*)list->data;
+ pgm_slist_t* next = list->next;
+
+/* check source */
+ if (pgm_tsi_equal (tsi, &list_sock->tsi))
+ {
+ sock = list_sock;
+ break;
+ }
+
+/* check receivers */
+ pgm_rwlock_reader_lock (&list_sock->peers_lock);
+ pgm_peer_t* receiver = pgm_hashtable_lookup (list_sock->peers_hashtable, tsi);
+ if (receiver) {
+ int retval = http_receiver_response (connection, receiver);
+ pgm_rwlock_reader_unlock (&list_sock->peers_lock);
+ pgm_rwlock_reader_unlock (&pgm_sock_list_lock);
+ return retval;
+ }
+ pgm_rwlock_reader_unlock (&list_sock->peers_lock);
+
+ list = next;
+ }
+
+ if (!sock) {
+ pgm_rwlock_reader_unlock (&pgm_sock_list_lock);
+ return -1;
+ }
+
+/* transport now contains valid matching TSI */
+ char gsi[ PGM_GSISTRLEN ];
+ pgm_gsi_print_r (&sock->tsi.gsi, gsi, sizeof(gsi));
+
+ char title[ sizeof("Transport .00000") + PGM_GSISTRLEN ];
+ sprintf (title, "Transport %s.%hu",
+ gsi,
+ ntohs (sock->tsi.sport));
+
+ char source_address[INET6_ADDRSTRLEN];
+ getnameinfo ((struct sockaddr*)&sock->send_gsr.gsr_source, pgm_sockaddr_len ((struct sockaddr*)&sock->send_gsr.gsr_source),
+ source_address, sizeof(source_address),
+ NULL, 0,
+ NI_NUMERICHOST);
+
+ char group_address[INET6_ADDRSTRLEN];
+ getnameinfo ((struct sockaddr*)&sock->send_gsr.gsr_group, pgm_sockaddr_len ((struct sockaddr*)&sock->send_gsr.gsr_group),
+ group_address, sizeof(group_address),
+ NULL, 0,
+ NI_NUMERICHOST);
+
+ const uint16_t dport = ntohs (sock->dport);
+ const uint16_t sport = ntohs (sock->tsi.sport);
+
+ const pgm_time_t ihb_min = sock->spm_heartbeat_len ? sock->spm_heartbeat_interval[ 1 ] : 0;
+ const pgm_time_t ihb_max = sock->spm_heartbeat_len ? sock->spm_heartbeat_interval[ sock->spm_heartbeat_len - 1 ] : 0;
+
+ char spm_path[INET6_ADDRSTRLEN];
+ getnameinfo ((struct sockaddr*)&sock->recv_gsr[0].gsr_source, pgm_sockaddr_len ((struct sockaddr*)&sock->recv_gsr[0].gsr_source),
+ spm_path, sizeof(spm_path),
+ NULL, 0,
+ NI_NUMERICHOST);
+
+ pgm_string_t* response = http_create_response (title, HTTP_TAB_TRANSPORTS);
+ pgm_string_append_printf (response, "<div class=\"heading\">"
+ "<strong>Transport: </strong>"
+ "%s.%hu"
+ "</div>",
+ gsi, sport);
+
+/* peers */
+
+ pgm_string_append (response, "<div class=\"bubbly\">"
+ "\n<table cellspacing=\"0\">"
+ "<tr>"
+ "<th>Group address</th>"
+ "<th>Dest port</th>"
+ "<th>Source address</th>"
+ "<th>Last hop</th>"
+ "<th>Source GSI</th>"
+ "<th>Source port</th>"
+ "</tr>"
+ );
+
+ if (sock->peers_list)
+ {
+ pgm_rwlock_reader_lock (&sock->peers_lock);
+ pgm_list_t* peers_list = sock->peers_list;
+ while (peers_list) {
+ pgm_list_t* next = peers_list->next;
+ http_each_receiver (peers_list->data, response);
+ peers_list = next;
+ }
+ pgm_rwlock_reader_unlock (&sock->peers_lock);
+ }
+ else
+ {
+/* no peers */
+
+ pgm_string_append (response, "<tr>"
+ "<td colspan=\"6\"><div class=\"empty\">This transport has no peers.</div></td>"
+ "</tr>"
+ );
+
+ }
+
+ pgm_string_append (response, "</table>\n"
+ "</div>");
+
+/* source and configuration information */
+
+ pgm_string_append_printf (response, "<div class=\"rounded\" id=\"information\">"
+ "\n<table>"
+ "<tr>"
+ "<th>Source address</th><td>%s</td>"
+ "</tr><tr>"
+ "<th>Group address</th><td>%s</td>"
+ "</tr><tr>"
+ "<th>Dest port</th><td>%u</td>"
+ "</tr><tr>"
+ "<th>Source GSI</th><td>%s</td>"
+ "</tr><tr>"
+ "<th>Source port</th><td>%u</td>"
+ "</tr>",
+ source_address,
+ group_address,
+ dport,
+ gsi,
+ sport);
+
+/* continue with source information */
+
+ pgm_string_append_printf (response, "<tr>"
+ "<td colspan=\"2\"><div class=\"break\"></div></td>"
+ "</tr><tr>"
+ "<th>Ttl</th><td>%u</td>"
+ "</tr><tr>"
+ "<th>Adv Mode</th><td>%s</td>"
+ "</tr><tr>"
+ "<th>Late join</th><td>disable(2)</td>"
+ "</tr><tr>"
+ "<th>TXW_MAX_RTE</th><td>%" GROUP_FORMAT "zd</td>"
+ "</tr><tr>"
+ "<th>TXW_SECS</th><td>%" GROUP_FORMAT "u</td>"
+ "</tr><tr>"
+ "<th>TXW_ADV_SECS</th><td>0</td>"
+ "</tr><tr>"
+ "<th>Ambient SPM interval</th><td>%" GROUP_FORMAT PGM_TIME_FORMAT " ms</td>"
+ "</tr><tr>"
+ "<th>IHB_MIN</th><td>%" GROUP_FORMAT PGM_TIME_FORMAT " ms</td>"
+ "</tr><tr>"
+ "<th>IHB_MAX</th><td>%" GROUP_FORMAT PGM_TIME_FORMAT " ms</td>"
+ "</tr><tr>"
+ "<th>NAK_BO_IVL</th><td>%" GROUP_FORMAT PGM_TIME_FORMAT " ms</td>"
+ "</tr><tr>"
+ "<th>FEC</th><td>disabled(1)</td>"
+ "</tr><tr>"
+ "<th>Source Path Address</th><td>%s</td>"
+ "</tr>"
+ "</table>\n"
+ "</div>",
+ sock->hops,
+ 0 == sock->adv_mode ? "time(0)" : "data(1)",
+ sock->txw_max_rte,
+ sock->txw_secs,
+ pgm_to_msecs(sock->spm_ambient_interval),
+ ihb_min,
+ ihb_max,
+ pgm_to_msecs(sock->nak_bo_ivl),
+ spm_path);
+
+/* performance information */
+
+ const pgm_txw_t* window = sock->window;
+ pgm_string_append_printf (response, "\n<h2>Performance information</h2>"
+ "\n<table>"
+ "<tr>"
+ "<th>Data bytes sent</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>Data packets sent</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>Bytes buffered</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>Packets buffered</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>Bytes sent</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>Raw NAKs received</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>Checksum errors</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>Malformed NAKs</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>Packets discarded</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>Bytes retransmitted</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>Packets retransmitted</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>NAKs received</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>NAKs ignored</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>Transmission rate</th><td>%" GROUP_FORMAT PRIu32 " bps</td>"
+ "</tr><tr>"
+ "<th>NNAK packets received</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>NNAKs received</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>Malformed NNAKs</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr>"
+ "</table>\n",
+ sock->cumulative_stats[PGM_PC_SOURCE_DATA_BYTES_SENT],
+ sock->cumulative_stats[PGM_PC_SOURCE_DATA_MSGS_SENT],
+ window ? pgm_txw_size (window) : 0, /* minus IP & any UDP header */
+ window ? pgm_txw_length (window) : 0,
+ sock->cumulative_stats[PGM_PC_SOURCE_BYTES_SENT],
+ sock->cumulative_stats[PGM_PC_SOURCE_SELECTIVE_NAKS_RECEIVED],
+ sock->cumulative_stats[PGM_PC_SOURCE_CKSUM_ERRORS],
+ sock->cumulative_stats[PGM_PC_SOURCE_MALFORMED_NAKS],
+ sock->cumulative_stats[PGM_PC_SOURCE_PACKETS_DISCARDED],
+ sock->cumulative_stats[PGM_PC_SOURCE_SELECTIVE_BYTES_RETRANSMITTED],
+ sock->cumulative_stats[PGM_PC_SOURCE_SELECTIVE_MSGS_RETRANSMITTED],
+ sock->cumulative_stats[PGM_PC_SOURCE_SELECTIVE_NAKS_RECEIVED],
+ sock->cumulative_stats[PGM_PC_SOURCE_SELECTIVE_NAKS_IGNORED],
+ sock->cumulative_stats[PGM_PC_SOURCE_TRANSMISSION_CURRENT_RATE],
+ sock->cumulative_stats[PGM_PC_SOURCE_SELECTIVE_NNAK_PACKETS_RECEIVED],
+ sock->cumulative_stats[PGM_PC_SOURCE_SELECTIVE_NNAKS_RECEIVED],
+ sock->cumulative_stats[PGM_PC_SOURCE_NNAK_ERRORS]);
+
+ pgm_rwlock_reader_unlock (&pgm_sock_list_lock);
+ http_finalize_response (connection, response);
+ return 0;
+}
+
+static
+void
+http_each_receiver (
+ pgm_peer_t* peer,
+ pgm_string_t* response
+ )
+{
+ char group_address[INET6_ADDRSTRLEN];
+ getnameinfo ((struct sockaddr*)&peer->group_nla, pgm_sockaddr_len ((struct sockaddr*)&peer->group_nla),
+ group_address, sizeof(group_address),
+ NULL, 0,
+ NI_NUMERICHOST);
+
+ char source_address[INET6_ADDRSTRLEN];
+ getnameinfo ((struct sockaddr*)&peer->nla, pgm_sockaddr_len ((struct sockaddr*)&peer->nla),
+ source_address, sizeof(source_address),
+ NULL, 0,
+ NI_NUMERICHOST);
+
+ char last_hop[INET6_ADDRSTRLEN];
+ getnameinfo ((struct sockaddr*)&peer->local_nla, pgm_sockaddr_len ((struct sockaddr*)&peer->local_nla),
+ last_hop, sizeof(last_hop),
+ NULL, 0,
+ NI_NUMERICHOST);
+
+ char gsi[ PGM_GSISTRLEN + sizeof(".00000") ];
+ pgm_gsi_print_r (&peer->tsi.gsi, gsi, sizeof(gsi));
+
+ const int sport = ntohs (peer->tsi.sport);
+ const int dport = ntohs (peer->sock->dport); /* by definition must be the same */
+ pgm_string_append_printf (response, "<tr>"
+ "<td>%s</td>"
+ "<td>%u</td>"
+ "<td>%s</td>"
+ "<td>%s</td>"
+ "<td><a href=\"/%s.%u\">%s</a></td>"
+ "<td><a href=\"/%s.%u\">%u</a></td>"
+ "</tr>",
+ group_address,
+ dport,
+ source_address,
+ last_hop,
+ gsi, sport, gsi,
+ gsi, sport, sport
+ );
+}
+
+static
+int
+http_time_summary (
+ const time_t* activity_time,
+ char* sz
+ )
+{
+ time_t now_time = time (NULL);
+
+ if (*activity_time > now_time) {
+ return sprintf (sz, "clock skew");
+ }
+
+ struct tm* activity_tm = localtime (activity_time);
+
+ now_time -= *activity_time;
+
+ if (now_time < (24 * 60 * 60))
+ {
+ char hourmin[6];
+ strftime (hourmin, sizeof(hourmin), "%H:%M", activity_tm);
+
+ if (now_time < 60) {
+ return sprintf (sz, "%s (%li second%s ago)",
+ hourmin, now_time, now_time > 1 ? "s" : "");
+ }
+ now_time /= 60;
+ if (now_time < 60) {
+ return sprintf (sz, "%s (%li minute%s ago)",
+ hourmin, now_time, now_time > 1 ? "s" : "");
+ }
+ now_time /= 60;
+ return sprintf (sz, "%s (%li hour%s ago)",
+ hourmin, now_time, now_time > 1 ? "s" : "");
+ }
+ else
+ {
+ char daymonth[32];
+#ifndef _WIN32
+ strftime (daymonth, sizeof(daymonth), "%d %b", activity_tm);
+#else
+ wchar_t wdaymonth[32];
+ const size_t slen = strftime (daymonth, sizeof(daymonth), "%d %b", &activity_tm);
+ const size_t wslen = MultiByteToWideChar (CP_ACP, 0, daymonth, slen, wdaymonth, 32);
+ WideCharToMultiByte (CP_UTF8, 0, wdaymonth, wslen + 1, daymonth, sizeof(daymonth), NULL, NULL);
+#endif
+ now_time /= 24;
+ if (now_time < 14) {
+ return sprintf (sz, "%s (%li day%s ago)",
+ daymonth, now_time, now_time > 1 ? "s" : "");
+ } else {
+ return sprintf (sz, "%s", daymonth);
+ }
+ }
+}
+
+static
+int
+http_receiver_response (
+ struct http_connection_t* connection,
+ pgm_peer_t* peer
+ )
+{
+ char gsi[ PGM_GSISTRLEN ];
+ pgm_gsi_print_r (&peer->tsi.gsi, gsi, sizeof(gsi));
+ char title[ sizeof("Peer .00000") + PGM_GSISTRLEN ];
+ sprintf (title, "Peer %s.%u",
+ gsi,
+ ntohs (peer->tsi.sport));
+
+ char group_address[INET6_ADDRSTRLEN];
+ getnameinfo ((struct sockaddr*)&peer->group_nla, pgm_sockaddr_len ((struct sockaddr*)&peer->group_nla),
+ group_address, sizeof(group_address),
+ NULL, 0,
+ NI_NUMERICHOST);
+
+ char source_address[INET6_ADDRSTRLEN];
+ getnameinfo ((struct sockaddr*)&peer->nla, pgm_sockaddr_len ((struct sockaddr*)&peer->nla),
+ source_address, sizeof(source_address),
+ NULL, 0,
+ NI_NUMERICHOST);
+
+ char last_hop[INET6_ADDRSTRLEN];
+ getnameinfo ((struct sockaddr*)&peer->local_nla, pgm_sockaddr_len ((struct sockaddr*)&peer->local_nla),
+ last_hop, sizeof(last_hop),
+ NULL, 0,
+ NI_NUMERICHOST);
+
+ const uint16_t sport = ntohs (peer->tsi.sport);
+ const uint16_t dport = ntohs (peer->sock->dport); /* by definition must be the same */
+ const pgm_rxw_t* window = peer->window;
+ const uint32_t outstanding_naks = window->nak_backoff_queue.length +
+ window->wait_ncf_queue.length +
+ window->wait_data_queue.length;
+
+ time_t last_activity_time;
+ pgm_time_since_epoch (&peer->last_packet, &last_activity_time);
+
+ char last_activity[100];
+ http_time_summary (&last_activity_time, last_activity);
+
+ pgm_string_t* response = http_create_response (title, HTTP_TAB_TRANSPORTS);
+ pgm_string_append_printf (response, "<div class=\"heading\">"
+ "<strong>Peer: </strong>"
+ "%s.%u"
+ "</div>",
+ gsi, sport);
+
+
+/* peer information */
+ pgm_string_append_printf (response, "<div class=\"rounded\" id=\"information\">"
+ "\n<table>"
+ "<tr>"
+ "<th>Group address</th><td>%s</td>"
+ "</tr><tr>"
+ "<th>Dest port</th><td>%u</td>"
+ "</tr><tr>"
+ "<th>Source address</th><td>%s</td>"
+ "</tr><tr>"
+ "<th>Last hop</th><td>%s</td>"
+ "</tr><tr>"
+ "<th>Source GSI</th><td>%s</td>"
+ "</tr><tr>"
+ "<th>Source port</th><td>%u</td>"
+ "</tr>",
+ group_address,
+ dport,
+ source_address,
+ last_hop,
+ gsi,
+ sport);
+
+ pgm_string_append_printf (response, "<tr>"
+ "<td colspan=\"2\"><div class=\"break\"></div></td>"
+ "</tr><tr>"
+ "<th>NAK_BO_IVL</th><td>%" GROUP_FORMAT PGM_TIME_FORMAT " ms</td>"
+ "</tr><tr>"
+ "<th>NAK_RPT_IVL</th><td>%" GROUP_FORMAT PGM_TIME_FORMAT " ms</td>"
+ "</tr><tr>"
+ "<th>NAK_NCF_RETRIES</th><td>%" GROUP_FORMAT "u</td>"
+ "</tr><tr>"
+ "<th>NAK_RDATA_IVL</th><td>%" GROUP_FORMAT PGM_TIME_FORMAT " ms</td>"
+ "</tr><tr>"
+ "<th>NAK_DATA_RETRIES</th><td>%" GROUP_FORMAT "u</td>"
+ "</tr><tr>"
+ "<th>Send NAKs</th><td>enabled(1)</td>"
+ "</tr><tr>"
+ "<th>Late join</th><td>disabled(2)</td>"
+ "</tr><tr>"
+ "<th>NAK TTL</th><td>%u</td>"
+ "</tr><tr>"
+ "<th>Delivery order</th><td>ordered(2)</td>"
+ "</tr><tr>"
+ "<th>Multicast NAKs</th><td>disabled(2)</td>"
+ "</tr>"
+ "</table>\n"
+ "</div>",
+ pgm_to_msecs(peer->sock->nak_bo_ivl),
+ pgm_to_msecs(peer->sock->nak_rpt_ivl),
+ peer->sock->nak_ncf_retries,
+ pgm_to_msecs(peer->sock->nak_rdata_ivl),
+ peer->sock->nak_data_retries,
+ peer->sock->hops);
+
+ pgm_string_append_printf (response, "\n<h2>Performance information</h2>"
+ "\n<table>"
+ "<tr>"
+ "<th>Data bytes received</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>Data packets received</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>NAK failures</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>Bytes received</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>Checksum errors</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>Malformed SPMs</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>Malformed ODATA</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>Malformed RDATA</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>Malformed NCFs</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>Packets discarded</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>Losses</th><td>%" GROUP_FORMAT PRIu32 "</td>" /* detected missed packets */
+ "</tr><tr>"
+ "<th>Bytes delivered to app</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>Packets delivered to app</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>Duplicate SPMs</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>Duplicate ODATA/RDATA</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>NAK packets sent</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>NAKs sent</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>NAKs retransmitted</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>NAKs failed</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>NAKs failed due to RXW advance</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>NAKs failed due to NCF retries</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>NAKs failed due to DATA retries</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>NAK failures delivered to app</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>NAKs suppressed</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>Malformed NAKs</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>Outstanding NAKs</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>Last activity</th><td>%s</td>"
+ "</tr><tr>"
+ "<th>NAK repair min time</th><td>%" GROUP_FORMAT PRIu32 " μs</td>"
+ "</tr><tr>"
+ "<th>NAK repair mean time</th><td>%" GROUP_FORMAT PRIu32 " μs</td>"
+ "</tr><tr>"
+ "<th>NAK repair max time</th><td>%" GROUP_FORMAT PRIu32 " μs</td>"
+ "</tr><tr>"
+ "<th>NAK fail min time</th><td>%" GROUP_FORMAT PRIu32 " μs</td>"
+ "</tr><tr>"
+ "<th>NAK fail mean time</th><td>%" GROUP_FORMAT PRIu32 " μs</td>"
+ "</tr><tr>"
+ "<th>NAK fail max time</th><td>%" GROUP_FORMAT PRIu32 " μs</td>"
+ "</tr><tr>"
+ "<th>NAK min retransmit count</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>NAK mean retransmit count</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr><tr>"
+ "<th>NAK max retransmit count</th><td>%" GROUP_FORMAT PRIu32 "</td>"
+ "</tr>"
+ "</table>\n",
+ peer->cumulative_stats[PGM_PC_RECEIVER_DATA_BYTES_RECEIVED],
+ peer->cumulative_stats[PGM_PC_RECEIVER_DATA_MSGS_RECEIVED],
+ peer->cumulative_stats[PGM_PC_RECEIVER_NAK_FAILURES],
+ peer->cumulative_stats[PGM_PC_RECEIVER_BYTES_RECEIVED],
+ peer->sock->cumulative_stats[PGM_PC_SOURCE_CKSUM_ERRORS],
+ peer->cumulative_stats[PGM_PC_RECEIVER_MALFORMED_SPMS],
+ peer->cumulative_stats[PGM_PC_RECEIVER_MALFORMED_ODATA],
+ peer->cumulative_stats[PGM_PC_RECEIVER_MALFORMED_RDATA],
+ peer->cumulative_stats[PGM_PC_RECEIVER_MALFORMED_NCFS],
+ peer->cumulative_stats[PGM_PC_RECEIVER_PACKETS_DISCARDED],
+ window->cumulative_losses,
+ window->bytes_delivered,
+ window->msgs_delivered,
+ peer->cumulative_stats[PGM_PC_RECEIVER_DUP_SPMS],
+ peer->cumulative_stats[PGM_PC_RECEIVER_DUP_DATAS],
+ peer->cumulative_stats[PGM_PC_RECEIVER_SELECTIVE_NAK_PACKETS_SENT],
+ peer->cumulative_stats[PGM_PC_RECEIVER_SELECTIVE_NAKS_SENT],
+ peer->cumulative_stats[PGM_PC_RECEIVER_SELECTIVE_NAKS_RETRANSMITTED],
+ peer->cumulative_stats[PGM_PC_RECEIVER_SELECTIVE_NAKS_FAILED],
+ peer->cumulative_stats[PGM_PC_RECEIVER_NAKS_FAILED_RXW_ADVANCED],
+ peer->cumulative_stats[PGM_PC_RECEIVER_NAKS_FAILED_NCF_RETRIES_EXCEEDED],
+ peer->cumulative_stats[PGM_PC_RECEIVER_NAKS_FAILED_DATA_RETRIES_EXCEEDED],
+ peer->cumulative_stats[PGM_PC_RECEIVER_NAK_FAILURES_DELIVERED],
+ peer->cumulative_stats[PGM_PC_RECEIVER_SELECTIVE_NAKS_SUPPRESSED],
+ peer->cumulative_stats[PGM_PC_RECEIVER_NAK_ERRORS],
+ outstanding_naks,
+ last_activity,
+ window->min_fill_time,
+ peer->cumulative_stats[PGM_PC_RECEIVER_NAK_SVC_TIME_MEAN],
+ window->max_fill_time,
+ peer->min_fail_time,
+ peer->cumulative_stats[PGM_PC_RECEIVER_NAK_FAIL_TIME_MEAN],
+ peer->max_fail_time,
+ window->min_nak_transmit_count,
+ peer->cumulative_stats[PGM_PC_RECEIVER_TRANSMIT_MEAN],
+ window->max_nak_transmit_count);
+ http_finalize_response (connection, response);
+ return 0;
+}
+
+/* eof */