Compare commits
16
Commits
52e12af440
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc9d0aae35 | ||
|
|
cdcce02fec | ||
|
|
3684c4781b | ||
|
|
53dd81d90a | ||
|
|
0cd6439f8f | ||
|
|
f39a82fa6a | ||
|
|
7c7ecd2e76 | ||
|
|
c7d70eaf75 | ||
|
|
f9102d99fc | ||
|
|
a8e4d54c3b | ||
|
|
d3c02826ce | ||
|
|
1961912c21 | ||
|
|
bec0ad44f2 | ||
|
|
35b0a51a97 | ||
|
|
ed16a3fc4a | ||
|
|
e39d34f0f9 |
@@ -1,10 +1,15 @@
|
|||||||
CC=gcc
|
CC=gcc
|
||||||
CFLAGS=-Wall -g
|
CFLAGS=-Wall -g -I/usr/include/python3.14 -lpython3.14
|
||||||
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)
|
||||||
|
|
||||||
|
|||||||
@@ -1,38 +1,40 @@
|
|||||||
# Pretty Good Server
|
# Pretty Good Server
|
||||||
|
|
||||||
Multipurpose load balancing intercepting proxy written in C.
|
Multipurpose load balancing intercepting proxy written in C.
|
||||||

|
|
||||||
|
<img width="100px" style="width:100px;" src="static/C_Logo.png" />
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
This application is load balancing intercepting proxy.
|
This application is a load balancing intercepting proxy.
|
||||||
|
|
||||||
Key features:
|
Key features:
|
||||||
- security against ddos of your upstream server.
|
- Security against DDoS attacks on your upstream server.
|
||||||
- distribute incomming connections over several servers.
|
- Distribute incoming connections over several servers.
|
||||||
- host multiple protocols / services on one port as a security measurement.
|
- Host multiple protocols/services on one port as a security measure.
|
||||||
- advanced routing based on host name or any header data.
|
- Advanced routing based on hostname or any header data.
|
||||||
- interception of content. Modifying content going trough this server is possible.
|
- Interception of content. Modifying content passing through this server is possible.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
### AI Support
|
### AI Support
|
||||||
Thanks to the Python implementation it's possible to use AI in this server in the way most people are used to.
|
Thanks to the Python implementation, it's possible to use AI in this server in the way most people are accustomed to.
|
||||||
|
|
||||||
### Interception
|
### Interception
|
||||||
All upstream and downstream data will go trough Python code provided by user. A user is using this capable to modify headers and content. With this functionality you can enhance security. Also, you could apply corrections, translations, removing emoji's. In commercial sense, you could replace certain words with links to advertised content.
|
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
|
### Advanced Routing
|
||||||
Routing is more advanced than any other webserver provides. For routing Python is integrated giving it unlimited options. You could even write custom responses with Python, adding webhooks or you could override an existing page of software where you normally can't control over. Other features include, but not limited to:
|
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).
|
- Force the usage of authentication tokens (or base64 authentication).
|
||||||
- Validate authentication tokens by connecting to a database.
|
- Validate authentication tokens by connecting to a database.
|
||||||
- Do rate limiting.
|
- Implement rate limiting.
|
||||||
- Execute calls to AI to modify intercepted content.
|
- Execute calls to AI to modify intercepted content.
|
||||||
|
|
||||||
### Load balancing
|
### Load Balancing
|
||||||
Using Python you can apply your own dynamic load balancing rules. You know exactly what 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 remove this data for the client.
|
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
|
### Hostname Redirection
|
||||||
You can redirect based on hostname dynamically. You could use the same source code and base on hostname for example if you use production or development settings.
|
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
|
### Application Server
|
||||||
Because of its huge concurrency capabillities it's perfectly fine 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.
|
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.
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#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>
|
||||||
@@ -8,6 +10,18 @@
|
|||||||
#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)) {
|
||||||
@@ -35,9 +49,18 @@ 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;
|
||||||
Py_buffer *pybuffer = (Py_buffer *)malloc(bytes_read);
|
return PyBytes_FromString(buffer);
|
||||||
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) {
|
||||||
@@ -76,7 +99,9 @@ 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."},
|
||||||
|
|||||||
+31
-18
@@ -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 route(downstream,upstream):
|
def on_connect(downstream):
|
||||||
"""
|
"""
|
||||||
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,15 +80,17 @@ def route(downstream,upstream):
|
|||||||
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).tobytes()
|
peek = pgs.read(downstream, 4096)
|
||||||
|
|
||||||
|
redirect_to = []
|
||||||
|
|
||||||
if pgs.is_ssh(peek):
|
if pgs.is_ssh(peek):
|
||||||
print("Forwarding to ssh molodetz")
|
print("Forwarding to ssh molodetz")
|
||||||
u.connect(("molodetz.nl", 22))
|
redirect_to = "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:
|
||||||
@@ -96,11 +98,13 @@ def route(downstream,upstream):
|
|||||||
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'')
|
||||||
u.connect(("127.0.0.1", 3028))
|
redirect_to = "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')
|
||||||
u.connect(("127.0.0.1", 8082))
|
redirect_to = "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()
|
||||||
@@ -127,7 +131,6 @@ 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()}
|
||||||
@@ -145,8 +148,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.
|
||||||
@@ -157,9 +160,8 @@ 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")
|
||||||
u.connect(("devrant.com", 443))
|
redirect_to = "devrant.com", 443
|
||||||
peek = peek.replace(b'localhost', b'devrant.com')
|
u = socket.fromfd(pgs.connect(*redirect_to), socket.AF_INET, socket.SOCK_STREAM)
|
||||||
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.")
|
||||||
@@ -171,15 +173,26 @@ 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])
|
||||||
os.write(upstream,peek)
|
streams[upstream] = dict(dowstream=downstream, upstream_host=redirect_to[0],upstream_port=redirect_to[1])
|
||||||
|
|
||||||
# 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
|
||||||
|
|||||||
+70
@@ -2,3 +2,73 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.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;
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#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>
|
||||||
@@ -64,14 +66,47 @@ void py_destruct() {
|
|||||||
python_initialized = false;
|
python_initialized = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int py_route(int downstream, int upstream) {
|
char py_on_headers(int downstream, char *headers) {
|
||||||
PyObject *pModule = py_construct();
|
PyObject *pModule = py_construct();
|
||||||
long upstream_fd = 0;
|
char *new_headers = NULL;
|
||||||
if (pModule != NULL) {
|
if (pModule != NULL) {
|
||||||
PyObject *pFunc = PyObject_GetAttrString(pModule, "route");
|
PyObject *pFunc = PyObject_GetAttrString(pModule, "on_headers");
|
||||||
if (PyCallable_Check(pFunc)) {
|
if (PyCallable_Check(pFunc)) {
|
||||||
PyObject *pArgs = PyTuple_Pack(2, PyLong_FromLong(downstream),
|
PyObject *pArgs = PyTuple_Pack(2, PyLong_FromLong(downstream),
|
||||||
PyLong_FromLong(upstream));
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
@@ -97,5 +132,7 @@ int py_route(int downstream, int upstream) {
|
|||||||
fprintf(stderr, "Failed to load 'script'\n");
|
fprintf(stderr, "Failed to load 'script'\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
return (int)upstream_fd;
|
return upstream_fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
#ifndef PGS_SOCK_H
|
||||||
|
#define PGS_SOCK_H
|
||||||
|
#include "protocol.h"
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
@@ -12,16 +15,19 @@
|
|||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include "py.h"
|
||||||
#define MAX_EVENTS 8096
|
#define MAX_EVENTS 8096
|
||||||
#define BUFFER_SIZE 1024
|
#define BUFFER_SIZE 1024
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
PROTOCOL_STATUS status;
|
||||||
|
PROTOCOL_NAME protocol_name;
|
||||||
int client_fd;
|
int client_fd;
|
||||||
int upstream_fd;
|
int upstream_fd;
|
||||||
char *buffer;
|
char *buffer;
|
||||||
size_t buffer_size;
|
size_t buffer_size;
|
||||||
size_t buffer_offset;
|
size_t buffer_offset;
|
||||||
|
char *http_intercepted_headers;
|
||||||
} connection_t;
|
} connection_t;
|
||||||
|
|
||||||
int listen_fd = 0;
|
int listen_fd = 0;
|
||||||
@@ -124,7 +130,7 @@ char *sock_read(int fd, char *buf, size_t size) {
|
|||||||
size_t bytes_to_read = size > left_in_buffer ? left_in_buffer : size;
|
size_t bytes_to_read = size > left_in_buffer ? left_in_buffer : size;
|
||||||
ssize_t bytes_read = 0;
|
ssize_t bytes_read = 0;
|
||||||
char *buffer;
|
char *buffer;
|
||||||
buffer[size];
|
buffer[size] = 0;
|
||||||
if (bytes_to_read) {
|
if (bytes_to_read) {
|
||||||
bytes_read = recv(fd, buffer, size, 0);
|
bytes_read = recv(fd, buffer, size, 0);
|
||||||
buffer[bytes_read] = 0;
|
buffer[bytes_read] = 0;
|
||||||
@@ -170,6 +176,18 @@ int forward_data(int from_fd, int to_fd) {
|
|||||||
return (int)bytes_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) {
|
bool handle_connect(struct epoll_event event, int epoll_fd) {
|
||||||
struct sockaddr_in client_addr;
|
struct sockaddr_in client_addr;
|
||||||
socklen_t client_len = sizeof(client_addr);
|
socklen_t client_len = sizeof(client_addr);
|
||||||
@@ -187,7 +205,8 @@ bool handle_connect(struct epoll_event event, int epoll_fd) {
|
|||||||
client_event.data.fd = client_fd;
|
client_event.data.fd = client_fd;
|
||||||
connections[client_fd]->upstream_fd = -1;
|
connections[client_fd]->upstream_fd = -1;
|
||||||
connections[client_fd]->client_fd = client_fd;
|
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);
|
printf("New connection: client_fd=%d\n", client_fd);
|
||||||
|
|
||||||
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &client_event);
|
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &client_event);
|
||||||
@@ -200,34 +219,103 @@ void handle_close(int epoll_fd, connection_t *conn) {
|
|||||||
close_connection(epoll_fd, conn);
|
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) {
|
void handle_stream(struct epoll_event event, int epoll_fd, connection_t *conn) {
|
||||||
if (conn->upstream_fd == -1) {
|
if (conn->upstream_fd == -1) {
|
||||||
conn->upstream_fd = prepare_upstream();
|
conn->upstream_fd = py_on_connect(conn->client_fd);
|
||||||
int upstream_fd = py_route(conn->client_fd, conn->upstream_fd);
|
if(conn->upstream_fd == -1){
|
||||||
|
|
||||||
if (upstream_fd == -1) {
|
|
||||||
close_connection(epoll_fd, conn);
|
close_connection(epoll_fd, conn);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
set_nonblocking(upstream_fd);
|
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;
|
struct epoll_event upstream_event;
|
||||||
upstream_event.events = EPOLLIN | EPOLLOUT | EPOLLERR | EPOLLHUP;
|
upstream_event.events = EPOLLIN | EPOLLOUT | EPOLLERR | EPOLLHUP;
|
||||||
upstream_event.data.ptr = connections[upstream_fd];
|
upstream_event.data.ptr = connections[conn->upstream_fd];
|
||||||
upstream_event.data.fd = 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;
|
||||||
|
|
||||||
connections[conn->client_fd]->upstream_fd = upstream_fd;
|
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn->upstream_fd, &upstream_event);
|
||||||
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,
|
printf("Connected: client_fd=%d, upstream_fd=%d\n", conn->client_fd,
|
||||||
conn->upstream_fd);
|
conn->upstream_fd);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.data.fd == conn->client_fd) {
|
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) {
|
if (forward_data(conn->client_fd, conn->upstream_fd) < 1) {
|
||||||
close_connection(epoll_fd, conn);
|
close_connection(epoll_fd, conn);
|
||||||
}
|
}
|
||||||
@@ -291,3 +379,4 @@ void serve(int port) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
Reference in New Issue
Block a user