diff options
Diffstat (limited to '3rdparty/openpgm-svn-r1135/pgm/http.c')
-rw-r--r-- | 3rdparty/openpgm-svn-r1135/pgm/http.c | 1735 |
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\">" + "©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 */ |