Compare commits

..
3 Commits
Author SHA1 Message Date
retoor df0c215ff9 Progress. 2024-12-31 15:26:13 +01:00
retoor e781dfa22d progress. 2024-12-31 06:44:19 +01:00
retoor d5a2dbfc49 Cleanup. 2024-12-31 02:57:48 +01:00
10 changed files with 279 additions and 631 deletions
+10 -10
View File
@@ -42,7 +42,7 @@ DOXYFILE_ENCODING = UTF-8
# title of most generated pages and in a few other places. # title of most generated pages and in a few other places.
# The default value is: My Project. # The default value is: My Project.
PROJECT_NAME = "Pretty Good Server" PROJECT_NAME = "My Project"
# The PROJECT_NUMBER tag can be used to enter a project or revision number. This # The PROJECT_NUMBER tag can be used to enter a project or revision number. This
# could be handy for archiving the generated documentation or if some version # could be handy for archiving the generated documentation or if some version
@@ -157,7 +157,7 @@ ABBREVIATE_BRIEF = "The $name class" \
# description. # description.
# The default value is: NO. # The default value is: NO.
ALWAYS_DETAILED_SEC = YES ALWAYS_DETAILED_SEC = NO
# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
# inherited members of a class in the documentation of that class as if those # inherited members of a class in the documentation of that class as if those
@@ -165,7 +165,7 @@ ALWAYS_DETAILED_SEC = YES
# operators of the base classes will not be shown. # operators of the base classes will not be shown.
# The default value is: NO. # The default value is: NO.
INLINE_INHERITED_MEMB = YES INLINE_INHERITED_MEMB = NO
# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path # If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
# before files name in the file list and in the header files. If set to NO the # before files name in the file list and in the header files. If set to NO the
@@ -342,7 +342,7 @@ OPTIMIZE_OUTPUT_SLICE = NO
# #
# Note see also the list of default file extension mappings. # Note see also the list of default file extension mappings.
EXTENSION_MAPPING = *.py *.h *.c EXTENSION_MAPPING =
# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
# according to the Markdown format, which allows for more readable # according to the Markdown format, which allows for more readable
@@ -504,7 +504,7 @@ NUM_PROC_THREADS = 1
# Possible values are: YES, NO, DATETIME and DATE. # Possible values are: YES, NO, DATETIME and DATE.
# The default value is: NO. # The default value is: NO.
TIMESTAMP = YES TIMESTAMP = NO
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
# Build related configuration options # Build related configuration options
@@ -518,7 +518,7 @@ TIMESTAMP = YES
# normally produced when WARNINGS is set to YES. # normally produced when WARNINGS is set to YES.
# The default value is: NO. # The default value is: NO.
EXTRACT_ALL = YES EXTRACT_ALL = NO
# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will # If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
# be included in the documentation. # be included in the documentation.
@@ -1038,7 +1038,7 @@ FILE_PATTERNS = *.c \
# be searched for input files as well. # be searched for input files as well.
# The default value is: NO. # The default value is: NO.
RECURSIVE = YES RECURSIVE = NO
# The EXCLUDE tag can be used to specify files and/or directories that should be # The EXCLUDE tag can be used to specify files and/or directories that should be
# excluded from the INPUT source files. This way you can easily exclude a # excluded from the INPUT source files. This way you can easily exclude a
@@ -1158,7 +1158,7 @@ FILTER_SOURCE_PATTERNS =
# (index.html). This can be useful if you have a project on for instance GitHub # (index.html). This can be useful if you have a project on for instance GitHub
# and want to reuse the introduction page also for the doxygen output. # and want to reuse the introduction page also for the doxygen output.
USE_MDFILE_AS_MAINPAGE = YES USE_MDFILE_AS_MAINPAGE =
# The Fortran standard specifies that for fixed formatted Fortran code all # The Fortran standard specifies that for fixed formatted Fortran code all
# characters from position 72 are to be considered as comment. A common # characters from position 72 are to be considered as comment. A common
@@ -2412,7 +2412,7 @@ SEARCH_INCLUDES = YES
# RECURSIVE has no effect here. # RECURSIVE has no effect here.
# This tag requires that the tag SEARCH_INCLUDES is set to YES. # This tag requires that the tag SEARCH_INCLUDES is set to YES.
INCLUDE_PATH = . INCLUDE_PATH =
# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
# patterns (like *.h and *.hpp) to filter out the header-files in the # patterns (like *.h and *.hpp) to filter out the header-files in the
@@ -2420,7 +2420,7 @@ INCLUDE_PATH = .
# used. # used.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
INCLUDE_FILE_PATTERNS = *.c *.h *.py INCLUDE_FILE_PATTERNS =
# The PREDEFINED tag can be used to specify one or more macro names that are # The PREDEFINED tag can be used to specify one or more macro names that are
# defined before the preprocessor is started (similar to the -D option of e.g. # defined before the preprocessor is started (similar to the -D option of e.g.
+1 -6
View File
@@ -1,15 +1,10 @@
CC=gcc CC=gcc
CFLAGS=-Wall -g -I/usr/include/python3.14 -lpython3.14 CFLAGS=-Wall -g
LDFLAGS=-lm LDFLAGS=-lm
OBJS=pgs.o OBJS=pgs.o
TARGET=pgs TARGET=pgs
upstreams:
ssh -f -N -L 3028:127.0.0.1:3028 molodetz.nl
ssh -f -N -L 8082:127.0.0.1:8082 molodetz.nl
build: build:
$(CC) pgs.c $(CFLAGS) $(LDFLAGS) -o $(TARGET) $(CC) pgs.c $(CFLAGS) $(LDFLAGS) -o $(TARGET)
+2 -40
View File
@@ -1,40 +1,2 @@
# Pretty Good Server pip uninstall pycrypto
pip install pycryptodome
Multipurpose load balancing intercepting proxy written in C.
<img width="100px" style="width:100px;" src="static/C_Logo.png" />
## Introduction
This application is a load balancing intercepting proxy.
Key features:
- Security against DDoS attacks on your upstream server.
- Distribute incoming connections over several servers.
- Host multiple protocols/services on one port as a security measure.
- Advanced routing based on hostname or any header data.
- Interception of content. Modifying content passing through this server is possible.
## Features
### AI Support
Thanks to the Python implementation, it's possible to use AI in this server in the way most people are accustomed to.
### Interception
All upstream and downstream data will go through Python code provided by the user. A user can use this capability to modify headers and content. With this functionality, you can enhance security. Additionally, you can apply corrections, translations, and remove emojis. In a commercial sense, you could replace certain words with links to advertised content.
### Advanced Routing
Routing is more advanced than any other web server provides. For routing, Python is integrated, giving it unlimited options. You could even write custom responses with Python, add webhooks, or override an existing page of software where you normally don't have control. Other features include, but are not limited to:
- Force the usage of authentication tokens (or base64 authentication).
- Validate authentication tokens by connecting to a database.
- Implement rate limiting.
- Execute calls to AI to modify intercepted content.
### Load Balancing
Using Python, you can apply your own dynamic load balancing rules. You know exactly which servers have which connections. A good idea could be to include the server load or other statistics in the response header of servers. Using interception, you can remove this data for the client.
### Hostname Redirection
You can redirect based on hostname dynamically. You could use the same source code and, based on hostname, determine if you use production or development settings.
### Application Server
Because of its huge concurrency capabilities, it's perfectly suitable as an application server. It's not only a load balancer, it's also a reverse proxy. It's a complete solution to your needs.
+236 -14
View File
@@ -1,15 +1,5 @@
// Written by retoor@molodetz.nl #define PY_SSIZE_T_CLEAN 1
// This source code sets up a simple TCP server that listens for connections and
// handles cleanup on exit. The server is intended to interact with an upstream
// server defined by its host and port.
// Imports: Custom includes 'py.h' and 'sock.h' for additional functionality.
// MIT License
#include "py.h" #include "py.h"
#include "sock.h"
#include <Python.h> #include <Python.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <errno.h> #include <errno.h>
@@ -22,18 +12,141 @@
#include <sys/epoll.h> #include <sys/epoll.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <unistd.h> #include <unistd.h>
#define PY_SSIZE_T_CLEAN 1
#define LISTEN_PORT 2222 #define LISTEN_PORT 2222
#define UPSTREAM_HOST "127.0.0.1" #define UPSTREAM_HOST "127.0.0.1"
#define UPSTREAM_PORT 9999 #define UPSTREAM_PORT 9999
#define MAX_EVENTS 8096
#define BUFFER_SIZE 1024
void set_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) {
perror("fcntl get");
exit(EXIT_FAILURE);
}
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
perror("fcntl set");
exit(EXIT_FAILURE);
}
}
int prepare_upstream() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
return sockfd;
}
int connect_upstream(const char *host, int port) {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("socket");
return -1;
}
set_nonblocking(sockfd);
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
if (inet_pton(AF_INET, host, &server_addr.sin_addr) <= 0) {
perror("inet_pton");
close(sockfd);
return -1;
}
if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) ==
-1) {
if (errno != EINPROGRESS) {
perror("connect");
close(sockfd);
return -1;
}
}
return sockfd;
}
int create_listening_socket(int port) {
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd == -1) {
perror("socket");
return -1;
}
int opt = 1;
if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) ==
-1) {
perror("setsockopt");
close(listen_fd);
return -1;
}
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(port);
if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) ==
-1) {
perror("bind");
close(listen_fd);
return -1;
}
if (listen(listen_fd, SOMAXCONN) == -1) {
perror("listen");
close(listen_fd);
return -1;
}
set_nonblocking(listen_fd);
return listen_fd;
}
typedef struct {
int client_fd;
int upstream_fd;
} connection_t;
void close_connection(int epoll_fd, connection_t *conn) {
if (conn->client_fd != -1) {
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, conn->client_fd, NULL);
close(conn->client_fd);
}
if (conn->upstream_fd != -1) {
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, conn->upstream_fd, NULL);
close(conn->upstream_fd);
}
}
int forward_data(int from_fd, int to_fd) {
static char buffer[BUFFER_SIZE];
// Feels great to do somehow. Better safe than sorry.
memset(buffer, 0, BUFFER_SIZE);
ssize_t bytes_read = recv(from_fd, buffer, sizeof(buffer), 0);
if (bytes_read > 0) {
ssize_t bytes_written = send(to_fd, buffer, bytes_read, 0);
if (bytes_written == -1) {
perror("write");
}
} else if (bytes_read == 0) {
printf("Connection closed by remote (fd=%d)\n", from_fd);
} else {
perror("read");
}
return (int)bytes_read;
}
int listen_fd = 0;
int epoll_fd = 0;
void cleanup() { void cleanup() {
close(epoll_fd); close(epoll_fd);
close(listen_fd); close(listen_fd);
py_destruct(); py_destruct();
printf("Graceful exit.\n"); printf("Graceful exit.\n");
} }
void handle_sigint(int sig) { void handle_sigint(int sig) {
printf("\nCtrl+C pressed.\n"); printf("\nCtrl+C pressed.\n");
exit(0); exit(0);
@@ -44,9 +157,118 @@ int main() {
perror("Failed to register signal handler"); perror("Failed to register signal handler");
return EXIT_FAILURE; return EXIT_FAILURE;
} }
listen_fd = create_listening_socket(LISTEN_PORT);
if (listen_fd == -1) {
fprintf(stderr, "Failed to create listening socket\n");
return EXIT_FAILURE;
}
atexit(cleanup); atexit(cleanup);
epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
perror("epoll_create1");
close(listen_fd);
return EXIT_FAILURE;
}
serve(LISTEN_PORT); struct epoll_event event;
event.events = EPOLLIN;
event.data.fd = listen_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &event) == -1) {
perror("epoll_ctl");
close(listen_fd);
close(epoll_fd);
return EXIT_FAILURE;
}
struct epoll_event events[MAX_EVENTS];
memset(events, 0, sizeof(events));
printf("Pretty Good Server listening on port %d\n", LISTEN_PORT);
connection_t connections[MAX_EVENTS][sizeof(connection_t)] = {0};
while (1) {
int num_events = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
if (num_events == -1) {
perror("epoll_wait");
break;
}
for (int i = 0; i < num_events; i++) {
if (events[i].data.fd == listen_fd) {
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_fd =
accept(listen_fd, (struct sockaddr *)&client_addr, &client_len);
if (client_fd == -1) {
perror("accept");
continue;
}
set_nonblocking(client_fd);
struct epoll_event client_event;
client_event.events = EPOLLIN | EPOLLOUT | EPOLLERR | EPOLLHUP;
client_event.data.ptr = connections[client_fd];
client_event.data.fd = client_fd;
connections[client_fd]->upstream_fd = -1;
connections[client_fd]->client_fd = client_fd;
// connections[client_fd]->upstream_fd = upstream_fd;
// connections[conn->client_fd] = conn;
// connections[upstream_fd]->client_fd = client_fd;
// connections[upstream_fd]->upstream_fd = upstream_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &client_event);
} else {
connection_t *conn = connections[events[i].data.fd];
if (events[i].events & (EPOLLHUP | EPOLLERR)) {
printf("Connection closed: client_fd=%d, upstream_fd=%d\n",
conn->client_fd, conn->upstream_fd);
close_connection(epoll_fd, conn);
} else if (events[i].events & EPOLLIN) {
if (conn->upstream_fd == -1) {
conn->upstream_fd = prepare_upstream();
int upstream_fd = py_route(conn->client_fd, conn->upstream_fd);
if (upstream_fd == -1) {
close_connection(epoll_fd, conn);
continue;
}
set_nonblocking(upstream_fd);
struct epoll_event upstream_event;
upstream_event.events = EPOLLIN | EPOLLOUT | EPOLLERR | EPOLLHUP;
upstream_event.data.ptr = connections[upstream_fd];
upstream_event.data.fd = upstream_fd;
connections[conn->client_fd]->upstream_fd = upstream_fd;
connections[upstream_fd]->client_fd = conn->client_fd;
connections[upstream_fd]->upstream_fd = upstream_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, upstream_fd, &upstream_event);
printf("Connected: client_fd=%d, upstream_fd=%d\n", conn->client_fd,
conn->upstream_fd);
continue;
}
if (events[i].data.fd == conn->client_fd) {
if (forward_data(conn->client_fd, conn->upstream_fd) < 1) {
close_connection(epoll_fd, conn);
}
} else if (events[i].data.fd == conn->upstream_fd) {
if (forward_data(conn->upstream_fd, conn->client_fd) < 1) {
close_connection(epoll_fd, conn);
}
}
}
}
}
}
close(listen_fd);
close(epoll_fd);
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
+4 -29
View File
@@ -1,8 +1,6 @@
#define PY_SSIZE_T_CLEAN 1 #define PY_SSIZE_T_CLEAN 1
#include "pgs.h"
#include <Python.h> #include <Python.h>
#include "py.h"
#include "sock.h"
#include <arpa/inet.h> #include <arpa/inet.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdio.h> #include <stdio.h>
@@ -10,18 +8,6 @@
#include <string.h> #include <string.h>
#include <sys/socket.h> #include <sys/socket.h>
static PyObject *pgs_api_connect(PyObject *self, PyObject *args) {
char *host;
int port;
int fd;
if (!PyArg_ParseTuple(args, "si", &host, &port)) {
return PyLong_FromLong(-1);
}
if ((fd = connect_upstream(host, port)) == -1) {
return PyLong_FromLong(-1);
}
return PyLong_FromLong(fd);
}
static PyObject *pgs_api_is_http(PyObject *self, PyObject *args) { static PyObject *pgs_api_is_http(PyObject *self, PyObject *args) {
const char *py_bytes; const char *py_bytes;
if (!PyArg_ParseTuple(args, "y", &py_bytes)) { if (!PyArg_ParseTuple(args, "y", &py_bytes)) {
@@ -49,18 +35,9 @@ static PyObject *pgs_api_read(PyObject *self, PyObject *args) {
char buffer[length + 1]; char buffer[length + 1];
ssize_t bytes_read = read(fd, buffer, length); ssize_t bytes_read = read(fd, buffer, length);
buffer[bytes_read] = 0; buffer[bytes_read] = 0;
return PyBytes_FromString(buffer); Py_buffer *pybuffer = (Py_buffer *)malloc(bytes_read);
} PyBuffer_FillInfo(pybuffer, 0, &buffer, bytes_read, false, PyBUF_CONTIG);
return PyMemoryView_FromBuffer(pybuffer);
static PyObject *pgs_api_peek(PyObject *self, PyObject *args) {
int fd, length;
if (!PyArg_ParseTuple(args, "ii", &fd, &length)) {
return NULL;
}
char buffer[length + 1];
ssize_t bytes_read = recv(fd, buffer, length, MSG_PEEK);
buffer[bytes_read] = 0;
return PyBytes_FromString(buffer);
} }
static PyObject *pgs_api_write(PyObject *self, PyObject *args) { static PyObject *pgs_api_write(PyObject *self, PyObject *args) {
@@ -99,9 +76,7 @@ static PyObject *mymodule_add(PyObject *self, PyObject *args) {
// Method table for the module // Method table for the module
static PyMethodDef MyModuleMethods[] = { static PyMethodDef MyModuleMethods[] = {
{"add", mymodule_add, METH_VARARGS, "Add two numbers"}, {"add", mymodule_add, METH_VARARGS, "Add two numbers"},
{"connect", pgs_api_connect, METH_VARARGS, "Connect to upstream"},
{"read", pgs_api_read, METH_VARARGS, "Read fd"}, {"read", pgs_api_read, METH_VARARGS, "Read fd"},
{"peek", pgs_api_peek, METH_VARARGS, "Peek fd"},
{"write", pgs_api_write, METH_VARARGS, "Write fd"}, {"write", pgs_api_write, METH_VARARGS, "Write fd"},
{"is_ssh", pgs_api_is_ssh, METH_VARARGS, {"is_ssh", pgs_api_is_ssh, METH_VARARGS,
"Check if header contains SSH data."}, "Check if header contains SSH data."},
+18 -31
View File
@@ -39,7 +39,7 @@ def is_http(header_bytes):
def is_https(header_bytes): def is_https(header_bytes):
return not any([is_ssh(header_bytes), is_http(header_bytes)]) return not any([is_ssh(header_bytes), is_http(header_bytes)])
def on_connect(downstream): def route(downstream,upstream):
""" """
This is a connection router which will be called by the server every This is a connection router which will be called by the server every
time a client connects. This function will be used to determine time a client connects. This function will be used to determine
@@ -80,17 +80,15 @@ def on_connect(downstream):
counter += 1 counter += 1
print("Connection nr.", counter) print("Connection nr.", counter)
u = socket.fromfd(upstream, socket.AF_INET, socket.SOCK_STREAM)
#u = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #u = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#print("FD:",u.fileno()) #print("FD:",u.fileno())
peek = pgs.read(downstream, 4096) peek = pgs.read(downstream, 4096).tobytes()
redirect_to = []
if pgs.is_ssh(peek): if pgs.is_ssh(peek):
print("Forwarding to ssh molodetz") print("Forwarding to ssh molodetz")
redirect_to = "molodetz.nl",22 u.connect(("molodetz.nl", 22))
u = socket.fromfd(pgs.connect(*redirect_to), socket.AF_INET, socket.SOCK_STREAM)
elif pgs.is_http(peek): elif pgs.is_http(peek):
if b'/random' in peek or b'random.' in peek: if b'/random' in peek or b'random.' in peek:
@@ -98,13 +96,11 @@ def on_connect(downstream):
print("Forwarding to 127.0.0.1:3028.") print("Forwarding to 127.0.0.1:3028.")
peek = peek.replace(b'/random', b'/') peek = peek.replace(b'/random', b'/')
peek = peek.replace(b'random.', b'') peek = peek.replace(b'random.', b'')
redirect_to = "127.0.0.1",3028 u.connect(("127.0.0.1", 3028))
u = socket.fromfd(pgs.connect(*redirect_to), socket.AF_INET, socket.SOCK_STREAM)
elif b'molodetz.local' in peek: elif b'molodetz.local' in peek:
print("Forwarding to 127.0.0.1:8082.") print("Forwarding to 127.0.0.1:8082.")
peek = peek.replace(b'molodetz.local', b'localhost') peek = peek.replace(b'molodetz.local', b'localhost')
redirect_to = "127.0.0.1",8082 u.connect(("127.0.0.1", 8082))
u = socket.fromfd(pgs.connect(*redirect_to), socket.AF_INET, socket.SOCK_STREAM)
elif b'bench.local' in peek: elif b'bench.local' in peek:
print("Responding with bench page.") print("Responding with bench page.")
body = f"""<html>\n<head>\n<title>Benchmark page.</title>\n</head>\n<body>\n<h1>Bench</h1>\n<p>{counter}</p>\n</body>\n</html>\n""".encode() body = f"""<html>\n<head>\n<title>Benchmark page.</title>\n</head>\n<body>\n<h1>Bench</h1>\n<p>{counter}</p>\n</body>\n</html>\n""".encode()
@@ -131,6 +127,7 @@ Environment: {env}
Total connections: {counter} Total connections: {counter}
Local hostname: {hostname} Local hostname: {hostname}
Downstream FD: {downstream} Downstream FD: {downstream}
Upstream FD: {upstream}
Current time server: {datetime.now()} Current time server: {datetime.now()}
Server started on: {server_start} Server started on: {server_start}
Server uptime: {get_server_uptime()} Server uptime: {get_server_uptime()}
@@ -148,8 +145,8 @@ Server uptime: {get_server_uptime()}
"" ""
] ]
headers = "\r\n".join(headers) headers = "\r\n".join(headers)
#response = f"{headers}{body}" response = f"{headers}{body}"
response = f"{headers}\r\n\r\n{body}"
pgs.write(downstream,response) pgs.write(downstream,response)
# Unset socket so the server will close it. # Unset socket so the server will close it.
@@ -160,8 +157,9 @@ Server uptime: {get_server_uptime()}
elif is_https(peek) and env == "prod": elif is_https(peek) and env == "prod":
print("Forwarding to dev.to") print("Forwarding to dev.to")
redirect_to = "devrant.com", 443 u.connect(("devrant.com", 443))
u = socket.fromfd(pgs.connect(*redirect_to), socket.AF_INET, socket.SOCK_STREAM) peek = peek.replace(b'localhost', b'devrant.com')
peek = peek.replace(b'molodetz.nl', b'devrant.com')
else: else:
# Error. # Error.
print("Could not find upstream for header content.") print("Could not find upstream for header content.")
@@ -173,26 +171,15 @@ Server uptime: {get_server_uptime()}
if not u: if not u:
return -1 return -1
#os.write(upstream,peek)
# Keep track of connections. Not sure if this is needed.
upstream = u.fileno()
# Remove reference to the socket so it doesn't get garbage collected. # Remove reference to the socket so it doesn't get garbage collected.
# This could break the connection. This way, it stays open. # This could break the connection. This way, it stays open.
u = None u = None
streams[downstream] = dict(upstream=upstream,upstream_host=redirect_to[0],upstream_port=redirect_to[1])
streams[upstream] = dict(dowstream=downstream, upstream_host=redirect_to[0],upstream_port=redirect_to[1]) os.write(upstream,peek)
# Keep track of connections. Not sure if this is needed.
streams[downstream] = upstream
streams[upstream] = downstream
# Return exact same value as what is given as parameter. # Return exact same value as what is given as parameter.
return upstream return upstream
def on_headers(downstream, headers):
stream = streams[downstream]
if stream['upstream_host'] == b'devrant.com':
headers = headers.replace(b'localhost', b'devrant.com')
headers = headers.replace(b'molodetz.nl', b'devrant.com')
if stream['upstream_host'] == b'molodetz.nl':
headers = headers.replace(b'localhost', b'molodetz.nl')
return headers
-74
View File
@@ -1,74 +0,0 @@
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
typedef enum PROTOCOL_STATUS {
PS_NONE,
PS_SNIFF,
PS_HTTP_READ_HEADER,
PS_HTTP_READ_BODY,
PS_STREAM,
PS_ERROR
} PROTOCOL_STATUS;
typedef enum PROTOCOL_NAME {
PN_NONE,
PN_HTTP,
PN_HTTP_CHUNKED,
PN_HTTP_WEBSOCKET,
PN_HTTP_KEEP_ALIVE,
PN_HTTP_REQUEST,
PN_SSH,
PN_RAW,
PN_ERROR
} PROTOCOL_NAME ;
char * http_get_header_value(char * headers, char * key){
char * result = NULL;
char * start = strstr(headers, key);
if(!start){
return NULL;
}
start += strlen(key);
start += 2;
char * end = strstr(start, "\r\n");
if(!end){
return NULL;
}
result = (char *)malloc(end - start + 1);
strncpy(result, start, end - start);
result[end-start] = 0;
return result;
}
PROTOCOL_NAME protocol_sniff(int fd){
char buffer[4096] = {0};
ssize_t bytes_received = recv(fd, buffer,sizeof(buffer),MSG_PEEK);
buffer[bytes_received] = 0;
if(bytes_received <= 0){
return PN_ERROR;
}
if(strncmp(buffer,"HTTP",4) == 0){
if(strstr(buffer,"Transfer-Encoding: chunked"))
return PN_HTTP_CHUNKED;
if(strstr(buffer,"Connection: keep-alive")){
return PN_HTTP_KEEP_ALIVE;
}
return PN_HTTP;
}
if(!strncmp(buffer,"GET ",4) && strstr(buffer,"Upgrade: websocket")){
return PN_HTTP_WEBSOCKET;
}
if(!strncmp(buffer,"SSH ",4)){
return PN_SSH;
}
if(!strncmp(buffer,"GET ", 4)){
return PN_HTTP_REQUEST;
}
return PN_RAW;
}
+5 -42
View File
@@ -1,5 +1,3 @@
#ifndef PGS_PY_H
#define PGS_PY_H
#define PY_SSIZE_T_CLEAN 1 #define PY_SSIZE_T_CLEAN 1
#include "pgs_api.h" #include "pgs_api.h"
#include <Python.h> #include <Python.h>
@@ -66,47 +64,14 @@ void py_destruct() {
python_initialized = false; python_initialized = false;
} }
char py_on_headers(int downstream, char *headers) { int py_route(int downstream, int upstream) {
PyObject *pModule = py_construct(); PyObject *pModule = py_construct();
char *new_headers = NULL; long upstream_fd = 0;
if (pModule != NULL) { if (pModule != NULL) {
PyObject *pFunc = PyObject_GetAttrString(pModule, "on_headers"); PyObject *pFunc = PyObject_GetAttrString(pModule, "route");
if (PyCallable_Check(pFunc)) { if (PyCallable_Check(pFunc)) {
PyObject *pArgs = PyTuple_Pack(2, PyLong_FromLong(downstream), PyObject *pArgs = PyTuple_Pack(2, PyLong_FromLong(downstream),
PyUnicode_FromString(headers)); PyLong_FromLong(upstream));
PyGILState_STATE gstate = PyGILState_Ensure();
new_headers = PyBytes_AsString(PyObject_CallObject(pFunc, pArgs));
PyGILState_Release(gstate);
Py_DECREF(pArgs);
}
}
return new_headers ? new_headers : headers;
}
char * py_http_intercept_headers(int sock, char * headers){
PyObject *pModule = py_construct();
char *new_headers = NULL;
if (pModule != NULL) {
PyObject *pFunc = PyObject_GetAttrString(pModule, "http_intercept_headers");
if (PyCallable_Check(pFunc)) {
PyObject *pArgs = PyTuple_Pack(2, PyLong_FromLong(sock),
PyUnicode_FromString(headers));
PyGILState_STATE gstate = PyGILState_Ensure();
new_headers = PyBytes_AsString(PyObject_CallObject(pFunc, pArgs));
PyGILState_Release(gstate);
Py_DECREF(pArgs);
}
}
return new_headers ? new_headers : headers;
}
int py_on_connect(int downstream) {
PyObject *pModule = py_construct();
int upstream_fd = -1;
if (pModule != NULL) {
PyObject *pFunc = PyObject_GetAttrString(pModule, "on_connect");
if (PyCallable_Check(pFunc)) {
PyObject *pArgs = PyTuple_Pack(1, PyLong_FromLong(downstream));
PyGILState_STATE gstate = PyGILState_Ensure(); PyGILState_STATE gstate = PyGILState_Ensure();
@@ -132,7 +97,5 @@ int py_on_connect(int downstream) {
fprintf(stderr, "Failed to load 'script'\n"); fprintf(stderr, "Failed to load 'script'\n");
} }
return upstream_fd; return (int)upstream_fd;
} }
#endif
-382
View File
@@ -1,382 +0,0 @@
#ifndef PGS_SOCK_H
#define PGS_SOCK_H
#include "protocol.h"
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include "py.h"
#define MAX_EVENTS 8096
#define BUFFER_SIZE 1024
typedef struct {
PROTOCOL_STATUS status;
PROTOCOL_NAME protocol_name;
int client_fd;
int upstream_fd;
char *buffer;
size_t buffer_size;
size_t buffer_offset;
char *http_intercepted_headers;
} connection_t;
int listen_fd = 0;
int epoll_fd = 0;
connection_t connections[MAX_EVENTS][sizeof(connection_t)] = {0};
int sock_init(void);
void sock_exit(void);
void set_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) {
perror("fcntl get");
exit(EXIT_FAILURE);
}
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
perror("fcntl set");
exit(EXIT_FAILURE);
}
}
int prepare_upstream() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
return sockfd;
}
int connect_upstream(const char *host, int port) {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("socket");
return -1;
}
set_nonblocking(sockfd);
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
if (inet_pton(AF_INET, host, &server_addr.sin_addr) <= 0) {
perror("inet_pton");
close(sockfd);
return -1;
}
if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) ==
-1) {
if (errno != EINPROGRESS) {
perror("connect");
close(sockfd);
return -1;
}
}
return sockfd;
}
int create_listening_socket(int port) {
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd == -1) {
perror("socket");
return -1;
}
int opt = 1;
if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) ==
-1) {
perror("setsockopt");
close(listen_fd);
return -1;
}
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(port);
if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) ==
-1) {
perror("bind");
close(listen_fd);
return -1;
}
if (listen(listen_fd, SOMAXCONN) == -1) {
perror("listen");
close(listen_fd);
return -1;
}
set_nonblocking(listen_fd);
return listen_fd;
}
char *sock_read(int fd, char *buf, size_t size) {
connection_t *conn = connections[fd];
size_t left_in_buffer = conn->buffer_size - conn->buffer_offset;
size_t bytes_to_read = size > left_in_buffer ? left_in_buffer : size;
ssize_t bytes_read = 0;
char *buffer;
buffer[size] = 0;
if (bytes_to_read) {
bytes_read = recv(fd, buffer, size, 0);
buffer[bytes_read] = 0;
}
memcpy(buf, conn->buffer + conn->buffer_offset, bytes_to_read);
if (bytes_read > 0) {
return buf;
} else if (bytes_read == 0) {
printf("Connection closed by remote (fd=%d)\n", fd);
} else {
perror("read");
}
return NULL;
}
void close_connection(int epoll_fd, connection_t *conn) {
if (conn->client_fd != -1) {
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, conn->client_fd, NULL);
close(conn->client_fd);
}
if (conn->upstream_fd != -1) {
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, conn->upstream_fd, NULL);
close(conn->upstream_fd);
}
}
int forward_data(int from_fd, int to_fd) {
static char buffer[BUFFER_SIZE];
// Feels great to do somehow. Better safe than sorry.
memset(buffer, 0, BUFFER_SIZE);
ssize_t bytes_read = recv(from_fd, buffer, sizeof(buffer), 0);
if (bytes_read > 0) {
ssize_t bytes_written = send(to_fd, buffer, bytes_read, 0);
if (bytes_written == -1) {
perror("write");
}
} else if (bytes_read == 0) {
printf("Connection closed by remote (fd=%d)\n", from_fd);
} else {
perror("read");
}
return (int)bytes_read;
}
ssize_t sock_send_all(int fd, char * content, ssize_t length) {
ssize_t bytes_total_sent = 0;
while(bytes_total_sent < length) {
ssize_t bytes_sent = send(fd, content + bytes_total_sent, length, 0);
if(bytes_sent <= 0){
return -1;
}
bytes_total_sent += bytes_sent;
}
return bytes_total_sent;
}
bool handle_connect(struct epoll_event event, int epoll_fd) {
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_fd =
accept(listen_fd, (struct sockaddr *)&client_addr, &client_len);
if (client_fd == -1) {
perror("accept");
return false;
}
set_nonblocking(client_fd);
struct epoll_event client_event;
client_event.events = EPOLLIN | EPOLLOUT | EPOLLERR | EPOLLHUP;
client_event.data.ptr = connections[client_fd];
client_event.data.fd = client_fd;
connections[client_fd]->upstream_fd = -1;
connections[client_fd]->client_fd = client_fd;
connections[client_fd]->status = PS_SNIFF;
connections[client_fd]->protocol_name = PS_NONE;
printf("New connection: client_fd=%d\n", client_fd);
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &client_event);
return true;
}
void handle_close(int epoll_fd, connection_t *conn) {
printf("Connection closed: client_fd=%d, upstream_fd=%d\n", conn->client_fd,
conn->upstream_fd);
close_connection(epoll_fd, conn);
}
char * sock_read_until(int fd, char * until, int until_len) {
char * result = (char *)malloc(BUFFER_SIZE);
result[0] = 0;
int bytes;
int bytes_total = 0;
char buffer[2];
while((bytes = recv(fd, buffer, 1,0)) > 0){
result[bytes_total] = buffer[0];
bytes_total += bytes;
if(bytes_total >= until_len){
if(!strncmp(buffer + bytes_total - until_len, until, until_len)){
break;
}
}
}
result[bytes_total] = 0;
return result;
}
void http_intercept_headers(int epoll_fd, connection_t *conn) {
char * headers = sock_read_until(conn->client_fd, "\r\n\r\n", 4);
char * intercepted_headers = py_http_intercept_headers(conn->client_fd,headers);
if(!intercepted_headers){
close_connection(epoll_fd, conn);
return;
}
sock_send_all(conn->client_fd, intercepted_headers, strlen(intercepted_headers));
printf("Intercepted headers: %s\n", intercepted_headers);
conn->http_intercepted_headers = intercepted_headers;
conn->status = PS_NONE;
connections[conn->upstream_fd]->status = PS_HTTP_READ_HEADER;
}
void http_intercept_body(int epoll_fd, connection_t *conn) {
printf("HIERR\n");
}
void handle_stream(struct epoll_event event, int epoll_fd, connection_t *conn) {
if (conn->upstream_fd == -1) {
conn->upstream_fd = py_on_connect(conn->client_fd);
if(conn->upstream_fd == -1){
close_connection(epoll_fd, conn);
return;
}
conn->protocol_name = protocol_sniff(conn->client_fd);
if(conn->protocol_name == PN_ERROR){
close_connection(epoll_fd, conn);
return;
}else if(conn->protocol_name == PN_HTTP){
conn->status = PS_HTTP_READ_HEADER;
}else if(conn->protocol_name == PN_HTTP_CHUNKED){
conn->status = PS_HTTP_READ_HEADER;
}else if(conn->protocol_name == PN_HTTP_KEEP_ALIVE){
conn->status = PS_HTTP_READ_HEADER;
}else if(conn->protocol_name == PN_HTTP_REQUEST){
conn->status = PS_HTTP_READ_HEADER;
}else if(conn->protocol_name == PN_HTTP_WEBSOCKET){
conn->status = PS_HTTP_READ_HEADER;
}else if(conn->protocol_name == PN_SSH){
conn->status = PS_STREAM;
}else if(conn->protocol_name == PN_RAW){
conn->status = PS_STREAM;
}else {
conn->status = PS_ERROR;
}
if (conn->upstream_fd == -1 || conn->status == PS_ERROR) {
close_connection(epoll_fd, conn);
return;
}
set_nonblocking(conn->upstream_fd);
struct epoll_event upstream_event;
upstream_event.events = EPOLLIN | EPOLLOUT | EPOLLERR | EPOLLHUP;
upstream_event.data.ptr = connections[conn->upstream_fd];
upstream_event.data.fd = conn->upstream_fd;
connections[conn->client_fd]->upstream_fd = conn->upstream_fd;
connections[conn->upstream_fd]->client_fd = conn->client_fd;
connections[conn->upstream_fd]->upstream_fd = conn->upstream_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn->upstream_fd, &upstream_event);
printf("Connected: client_fd=%d, upstream_fd=%d\n", conn->client_fd,
conn->upstream_fd);
return;
}
if (event.data.fd == conn->client_fd) {
if(conn->status = PS_HTTP_READ_HEADER){
http_intercept_headers(epoll_fd, conn);
}
if(conn->status == PS_HTTP_READ_BODY){
http_intercept_body(epoll_fd, conn);
}
if (forward_data(conn->client_fd, conn->upstream_fd) < 1) {
close_connection(epoll_fd, conn);
}
} else if (event.data.fd == conn->upstream_fd) {
if (forward_data(conn->upstream_fd, conn->client_fd) < 1) {
close_connection(epoll_fd, conn);
}
}
}
void serve(int port) {
listen_fd = create_listening_socket(port);
if (listen_fd == -1) {
fprintf(stderr, "Failed to create listening socket\n");
return;
}
epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
perror("epoll_create1");
close(listen_fd);
return;
}
struct epoll_event event;
event.events = EPOLLIN;
event.data.fd = listen_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &event) == -1) {
perror("epoll_ctl");
close(listen_fd);
close(epoll_fd);
return;
}
struct epoll_event events[MAX_EVENTS];
memset(events, 0, sizeof(events));
printf("Pretty Good Server listening on port %d\n", port);
while (1) {
int num_events = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
if (num_events == -1) {
perror("epoll_wait");
break;
}
for (int i = 0; i < num_events; i++) {
if (events[i].data.fd == listen_fd) {
handle_connect(events[i], epoll_fd);
} else {
connection_t *conn = connections[events[i].data.fd];
if (events[i].events & (EPOLLHUP | EPOLLERR)) {
handle_close(epoll_fd, conn);
} else if (events[i].events & EPOLLIN) {
handle_stream(events[i], epoll_fd, conn);
}
}
}
}
}
#endif
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB