#include <fcntl.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include "proxy.h"
#include "include.h"
EMBED_PRINT_FUNCTIONS(http_proxy, "Inprocess proxy")
static const guint MAX_LISTENING_BUFFER = 256;
/*void fd_unblock(int fd)
{
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}*/
struct http_proxy
{
int in_socket;
int out_socket;
guint16 listening_port;
size_t max_threads;
GThreadPool* pool;
GThread* run_thread;
};
typedef struct http_proxy http_proxy;
static http_proxy the_proxy =
{
-1,
-1,
49152, //using only private/dynamic ports
10,
NULL,
NULL
};
guint16 http_proxy_get_port()
{
return the_proxy.listening_port;
}
GString* get_request(int fd)
{
GIOChannel* io = g_io_channel_unix_new(fd);
GError* error;
GIOStatus status;
GString* line;
while((status = g_io_channel_read_line_string(io, line, NULL, &error)) == G_IO_STATUS_AGAIN)
{
http_proxy_print("TRYING AGAIN");
}
if(error)
{
http_proxy_printerr(error->message);
return NULL;
}
return line;
}
gboolean check_socket_valid(int socket)
{
if(socket != -1)
return TRUE;
switch(errno)
{
case EAFNOSUPPORT:
{
http_proxy_printerr("The implementation does not support the specified address family.");
break;
}
case EMFILE:
{
http_proxy_printerr("No more file descriptors are available for this process.");
break;
}
case ENFILE:
{
http_proxy_printerr("No more file descriptors are available for the system.");
break;
}
case EPROTONOSUPPORT:
{
http_proxy_printerr("The protocol is not supported by the address family, or the protocol is not supported by the implementation.");
break;
}
case EPROTOTYPE:
{
http_proxy_printerr("The socket type is not supported by the protocol.");
break;
}
case EACCES:
{
http_proxy_printerr("The process does not have appropriate privileges.");
break;
}
case ENOBUFS:
{
http_proxy_printerr("Insufficient resources were available in the system to perform the operation.");
break;
}
case ENOMEM:
{
http_proxy_printerr("Insufficient memory was available to fulfill the request.");
break;
}
default:
{
http_proxy_printerr("Unrecognized error on socket allocation.");
break;
}
}
return FALSE;
}
gboolean check_bind_valid(int socket, int bind)
{
if(bind != -1)
return TRUE;
switch(errno)
{
case EACCES:
{
http_proxy_printerr("The address is protected, and the user is not the superuser.");
break;
}
case EADDRINUSE:
{
http_proxy_printerr("The given address is already in use");
break;
}
case EBADF:
{
http_proxy_printerr("sockfd '%d' is not a valid descriptor. ", socket);
break;
}
case EINVAL:
{
http_proxy_printerr("The socket '%d' is already bound to an address", socket);
break;
}
case ENOTSOCK:
{
http_proxy_printerr("sockfd '%d' is a descriptor for a file, not a socket.", socket);
break;
}
default:
{
http_proxy_printerr("Unrecognized error '%d' on socket '%d'", errno, socket);
break;
}
}
return FALSE;
}
gboolean check_listen_valid(int listen)
{
if(listen != -1)
return TRUE;
switch(errno)
{
case EADDRINUSE:
{
http_proxy_printerr("Another socket is already listening on the same port.");
break;
}
case EBADF:
{
http_proxy_printerr("The argument sockfd '%d' is not a valid descriptor.", the_proxy.in_socket);
break;
}
case ENOTSOCK:
{
http_proxy_printerr("The argument sockfd '%d' is not a socket.", the_proxy.in_socket);
break;
}
case EOPNOTSUPP:
{
http_proxy_printerr("The socket '%d' is not of a type that supports the listen () operation.", the_proxy.in_socket);
break;
}
default:
{
http_proxy_printerr("Unrecognized error '%d' on listening on socket '%d'", errno, the_proxy.in_socket);
break;
}
}
return FALSE;
}
gboolean check_connect_valid(int connect)
{
if(connect != -1)
return TRUE;
switch(errno)
{
case EADDRNOTAVAIL:
{
http_proxy_printerr("The specified address is not available from the local machine.");
break;
}
case EAFNOSUPPORT:
{
http_proxy_printerr("The specified address is not a valid address for the address family of the specified socket.");
break;
}
case EALREADY:
{
http_proxy_printerr("A connection request is already in progress for the specified socket.");
break;
}
case EBADF:
{
http_proxy_printerr("The socket argument is not a valid file descriptor.");
break;
}
case ECONNREFUSED:
{
http_proxy_printerr("The target address was not listening for connections or refused the connection request.");
break;
}
case EINPROGRESS:
{
http_proxy_printerr("O_NONBLOCK is set for the file descriptor for the socket and the connection cannot be immediately established; the connection shall be established asynchronously.");
break;
}
case EINTR:
{
http_proxy_printerr("The attempt to establish a connection was interrupted by delivery of a signal that was caught; the connection shall be established asynchronously.");
break;
}
case EISCONN:
{
http_proxy_printerr("The specified socket is connection-mode and is already connected.");
break;
}
case ENETUNREACH:
{
http_proxy_printerr("No route to the network is present.");
break;
}
case ENOTSOCK:
{
http_proxy_printerr("The socket argument does not refer to a socket.");
break;
}
case EPROTOTYPE:
{
http_proxy_printerr("The specified address has a different type than the socket bound to the specified peer address.");
break;
}
case ETIMEDOUT:
{
http_proxy_printerr("The attempt to connect timed out before a connection was made.");
break;
}
case EACCES:
{
http_proxy_printerr("Search permission is denied for a component of the path prefix; or write access to the named socket is denied.");
break;
}
case EADDRINUSE:
{
http_proxy_printerr("Attempt to establish a connection that uses addresses that are already in use.");
break;
}
case ECONNRESET:
{
http_proxy_printerr("Remote host reset the connection request.");
break;
}
case EHOSTUNREACH:
{
http_proxy_printerr("The destination host cannot be reached (probably because the host is down or a remote router cannot reach it).");
break;
}
case EINVAL:
{
http_proxy_printerr("The address_len argument is not a valid length for the address family; or invalid address family in the sockaddr structure.");
break;
}
case ELOOP:
{
http_proxy_printerr("More than {SYMLOOP_MAX} symbolic links were encountered during resolution of the pathname in address.");
break;
}
case ENAMETOOLONG:
{
http_proxy_printerr("Pathname resolution of a symbolic link produced an intermediate result whose length exceeds {PATH_MAX}.");
break;
}
case ENETDOWN:
{
http_proxy_printerr("The local network interface used to reach the destination is down.");
break;
}
case ENOBUFS:
{
http_proxy_printerr("No buffer space is available.");
break;
}
case EOPNOTSUPP:
{
http_proxy_printerr("The socket is listening and cannot be connected.");
break;
}
default:
{
http_proxy_printerr("Unrecognized connect error '%d'", errno);
break;
}
}
return FALSE;
}
gboolean connect_request(int request)
{
uint16_t port;
struct sockaddr_in destination;
destination.sin_family = AF_INET;
destination.sin_port = htons(port);
/* Lookup and return the address if possible */
//ret = lookup_domain(&port_info.sin_addr, ip_addr);
int output = socket(AF_INET, SOCK_STREAM, 0);
gboolean success = check_socket_valid(output);
if(!success)
return FALSE;
/*
struct sockaddr_in bind_addr;
bind_addr.sin_family = AF_INET;
bind_addr.sin_addr.s_addr = inet_addr(config.bind_address);
ret = bind(sock_fd, (struct sockaddr *)&bind_addr, sizeof(bind_addr));
*/
int passalong = connect(output, (struct sockaddr*)&destination, sizeof(destination));
success = check_connect_valid(passalong);
if(!success)
return FALSE;
/*
fd_set rset, wset;
struct timeval tv;
time_t last_access;
int ret;
double tdiff;
int maxfd = max(connptr->client_fd, connptr->server_fd) + 1;
ssize_t bytes_received;
socket_nonblocking(connptr->client_fd);
socket_nonblocking(connptr->server_fd);
last_access = time(NULL);
for (;;) {
FD_ZERO(&rset);
FD_ZERO(&wset);
tv.tv_sec =
config.idletimeout - difftime(time(NULL), last_access);
tv.tv_usec = 0;
if (buffer_size(connptr->sbuffer) > 0)
FD_SET(connptr->client_fd, &wset);
if (buffer_size(connptr->cbuffer) > 0)
FD_SET(connptr->server_fd, &wset);
if (buffer_size(connptr->sbuffer) < MAXBUFFSIZE)
FD_SET(connptr->server_fd, &rset);
if (buffer_size(connptr->cbuffer) < MAXBUFFSIZE)
FD_SET(connptr->client_fd, &rset);
ret = select(maxfd, &rset, &wset, NULL, &tv);
if (ret == 0) {
tdiff = difftime(time(NULL), last_access);
if (tdiff > config.idletimeout) {
log_message(LOG_INFO,
"Idle Timeout (after select) as %g > %u.",
tdiff, config.idletimeout);
return;
} else {
continue;
}
} else if (ret < 0) {
log_message(LOG_ERR,
"relay_connection: select() error \"%s\". Closing connection (client_fd:%d, server_fd:%d)",
strerror(errno), connptr->client_fd,
connptr->server_fd);
return;
} else {
// All right, something was actually selected so mark it.
last_access = time(NULL);
}
if (FD_ISSET(connptr->server_fd, &rset)) {
bytes_received = read_buffer(connptr->server_fd, connptr->sbuffer);
if (bytes_received < 0)
break;
connptr->content_length.server -= bytes_received;
if (connptr->content_length.server == 0)
break;
}
if (FD_ISSET(connptr->client_fd, &rset)
&& read_buffer(connptr->client_fd, connptr->cbuffer) < 0) {
break;
}
if (FD_ISSET(connptr->server_fd, &wset)
&& write_buffer(connptr->server_fd, connptr->cbuffer) < 0) {
break;
}
if (FD_ISSET(connptr->client_fd, &wset)
&& write_buffer(connptr->client_fd, connptr->sbuffer) < 0) {
break;
}
}
// Here the server has closed the connection... write the
// remainder to the client and then exit.
socket_blocking(connptr->client_fd);
while (buffer_size(connptr->sbuffer) > 0) {
if (write_buffer(connptr->client_fd, connptr->sbuffer) < 0)
break;
}
shutdown(connptr->client_fd, SHUT_WR);
// Try to send any remaining data to the server if we can.
socket_blocking(connptr->server_fd);
while (buffer_size(connptr->cbuffer) > 0) {
if (write_buffer(connptr->server_fd, connptr->cbuffer) < 0)
break;
}
*/
return TRUE;
}
void process_request(gpointer data, gpointer user_data)
{
int request = *((int*)user_data);
GString* string = get_request(request);
http_proxy_print("Request gotten: %s", string->str);
}
gpointer http_proxy_run(gpointer data)
{
http_proxy* proxy = data;
GError* error = NULL;
proxy->pool = g_thread_pool_new(process_request, proxy, proxy->max_threads, FALSE, &error);
gboolean success = TRUE;
while(success)
{
struct sockaddr_in address;
socklen_t address_size = sizeof(struct sockaddr_in);
int request = accept(the_proxy.in_socket, (struct sockaddr*)&address, &address_size);
success = (request == -1);
if(!success)
{
switch(errno)
{
case EAGAIN: //EWOULDBLOCK
{
//The socket is marked non-blocking and no connections are present to be accepted.
success = TRUE;
continue;
}
case EBADF:
{
http_proxy_printerr("The descriptor is invalid.");
break;
}
case ECONNABORTED:
{
http_proxy_printerr("Aproxy connection has been aborted.");
break;
}
case EINTR:
{
http_proxy_printerr("The system call was interrupted by a signal that was caught before a valid connection arrived.");
break;
}
case EINVAL:
{
http_proxy_printerr("Socket is not listening for connections, or addrlen is invalid (e.g., is negative).");
break;
}
case EMFILE:
{
http_proxy_printerr("The per-process limit of open file descriptors has been reached.");
break;
}
case ENFILE:
{
http_proxy_printerr("The system limit on the total number of open files has been reached.");
break;
}
case ENOTSOCK:
{
http_proxy_printerr("The descriptor references a file, not a socket.");
break;
}
case EOPNOTSUPP:
{
http_proxy_printerr("The referenced socket is not of type SOCK_STREAM.");
break;
}
case EFAULT:
{
http_proxy_printerr("The addr argument is not in a writable part of the user address space.");
break;
}
case ENOBUFS:
case ENOMEM:
{
http_proxy_printerr("Not enough free memory. This often means that the memory allocation is limited by the socket buffer limits, not by the system memory.");
break;
}
case EPROTO:
{
http_proxy_printerr("Protocol error.");
break;
}
case EPERM:
{
http_proxy_printerr("Firewall rules forbid connection.");
break;
}
default:
{
http_proxy_printerr("Unrecognized error when trying to accept.");
break;
}
}
}
if(success)
{
GError* error = NULL;
g_thread_pool_push(proxy->pool, &request, &error);
if(error)
{
http_proxy_printerr(error->message);
success = FALSE;
}
}
}
g_thread_pool_free(the_proxy.pool, TRUE, TRUE);
http_proxy_print("Stopped listening");
return NULL;
}
gboolean http_proxy_init()
{
//AF_INET??
the_proxy.in_socket = socket(PF_INET, SOCK_STREAM, 0);
//fd_unblock(the_proxy.in_socket);
if(!check_socket_valid(the_proxy.in_socket))
return FALSE;
struct sockaddr_in in_address;
in_address.sin_family = AF_INET;
in_address.sin_addr.s_addr = INADDR_ANY;
in_address.sin_port = the_proxy.listening_port;
gboolean success = FALSE;
while(!success)
{
int result = bind(the_proxy.in_socket, (struct sockaddr*)&in_address, sizeof(struct sockaddr_in));
success = (result != -1);
if(!success)
{
switch(errno)
{
case EACCES: //The address is protected, and the user is not the superuser.
case EADDRINUSE: //The given address is already in use
break; //ignore
case EINVAL:
{
success = TRUE; //The socket is already bound to an address
break;
}
default:
{
check_bind_valid(the_proxy.in_socket, result);
break;
}
}
}
if(success)
{
result = listen(the_proxy.in_socket, MAX_LISTENING_BUFFER);
success = (result != -1);
if(!success)
{
switch(errno)
{
case EADDRINUSE: //Another socket is already listening on the same port.
break; //ignore
default:
{
check_listen_valid(result);
break;
}
}
}
}
if(!success)
{
//cannot try any other port
if(the_proxy.listening_port == G_MAXUINT16)
return FALSE;
the_proxy.listening_port++;
in_address.sin_port = the_proxy.listening_port;
}
}
http_proxy_print("Starting up on Port %u", the_proxy.listening_port);
GError* error = NULL;
if(!g_thread_supported())
g_thread_init(NULL);
the_proxy.run_thread = g_thread_create(http_proxy_run, &the_proxy, FALSE, &error);
if(error)
{
http_proxy_printerr(error->message);
return FALSE;
}
return TRUE;
}
gboolean http_proxy_set_requests(size_t request_count)
{
GError* error = NULL;
g_thread_pool_set_max_threads(the_proxy.pool, request_count, &error);
if(error)
{
http_proxy_printerr(error->message);
return FALSE;
}
return TRUE;
}
guint get_peer_address(int fd)
{
struct sockaddr_in address;
guint address_size;
gboolean success = getpeername(fd, (struct sockaddr *)&address, &address_size) != -1;
if(!success)
{
switch(errno)
{
case EBADF:
{
http_proxy_printerr("The socket argument is not a valid file descriptor.");
break;
}
case EFAULT:
{
http_proxy_printerr("The address or address_len parameter can not be accessed or written.");
break;
}
case EINVAL:
{
http_proxy_printerr("The socket has been shut down.");
break;
}
case ENOTCONN:
{
http_proxy_printerr("The socket is not connected or otherwise has not had the peer prespecified.");
break;
}
case ENOTSOCK:
{
http_proxy_printerr("The socket argument does not refer to a socket.");
break;
}
case EOPNOTSUPP:
{
http_proxy_printerr("The operation is not supported for the socket protocol.");
break;
}
case ENOBUFS:
{
http_proxy_printerr("Insufficient resources were available in the system to complete the call.");
break;
}
case ENOSR:
{
http_proxy_printerr("There were insufficient STREAMS resources available for the operation to complete.");
break;
}
default:
{
http_proxy_printerr("Unrecognized error '%d' when retrieving peer name.", errno);
break;
}
}
}
if(success)
return address.sin_addr.s_addr;
return 0;
}
void http_proxy_destroy()
{
close(the_proxy.in_socket);
close(the_proxy.out_socket);
}