-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Implements the project skeleton and closes #2 - Implements the TCPListener and closes #3 - Implmenets the SimpleHTTPConnectionHandler and closes #4 - Implements the SimpleHTTPApplication and closes #5 - Implements the SimpleHTTPNode and closes #6 - Implements a CI action to run automated tests and closes #8
- Loading branch information
Showing
43 changed files
with
2,924 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[flake8] | ||
max-line-length = 89 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# This workflow will install Python dependencies, run tests and lint with a single version of Python | ||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python | ||
|
||
name: Python application | ||
|
||
on: | ||
push: | ||
branches: | ||
- '*' | ||
pull_request: | ||
branches: | ||
- main | ||
|
||
jobs: | ||
build: | ||
|
||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- uses: actions/checkout@v3 | ||
- name: Set up Python 3.11 | ||
uses: actions/setup-python@v3 | ||
with: | ||
python-version: "3.11" | ||
- name: Upgrade pip | ||
run: | | ||
python -m pip install --upgrade pip | ||
- name: Install and run poetry | ||
run: | | ||
pip install poetry | ||
poetry install | ||
- name: Install and run tox | ||
run: | | ||
pip install tox | ||
SKIP_LOAD_TEST=true tox |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,67 @@ | ||
# edunet | ||
Having fun with networks! | ||
# EduNet | ||
This application allows you to simulate different network operations in a simple plug-in manner. | ||
|
||
The motivation with this project was to simply learn more about sockets, protocols, and making them all talk to one another. | ||
|
||
The end result of this project is to be able to craft together any network entities and have them communicate as intended. | ||
|
||
## Simple Supported Use Case | ||
|
||
There is a built-in simple supported use case called SimpleHTTPNode. | ||
|
||
Bringing this up will start a TCP listener on localhost on port 9999 accepting valid HTTP requests. | ||
What will be sent back are valid HTTP responses. | ||
|
||
## Complex Use Cases | ||
|
||
As it will be supported soon, the intent is by using the core architecture, | ||
we will be able to implement a router with basic features like: | ||
|
||
- Routing table | ||
- DHCP assignment | ||
- Packet forwarding | ||
- Network Access Translation (NAT) | ||
|
||
## Simple Example to put it all together | ||
|
||
### Installing | ||
```shell | ||
pip install edunet | ||
``` | ||
|
||
### Usage | ||
Going back to the Simple use case, as is you can simply do something like: | ||
|
||
#### Starting service | ||
|
||
```python | ||
from edunet.core.applications.simple_http_application import SimpleHTTPApplication | ||
from edunet.core.networking.handlers.simple_http_connection_handler import SimpleHTTPConnectionHandler | ||
from edunet.core.networking.listeners.tcp_listener import TCPListener | ||
|
||
# create an HTTP application | ||
app = SimpleHTTPApplication() | ||
|
||
# create a Connection Handler that takes an application | ||
handler = SimpleHTTPConnectionHandler(app) | ||
|
||
# A listener is needed to allow connections to come through | ||
listener = TCPListener("127.0.0.1", 9999, handler) | ||
|
||
listener.start() | ||
``` | ||
|
||
#### Calling service | ||
You can now communicate with it over HTTP by any means | ||
|
||
### curl | ||
```shell | ||
curl -X GET http://127.0.0.1:9999 | ||
``` | ||
|
||
### python | ||
```python | ||
import urllib.request | ||
response = urllib.request.urlopen("http://localhost:9999") | ||
print(response.read().decode("utf-8")) | ||
``` |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
[tool.poetry] | ||
name = "edunet" | ||
version = "0.1.0" | ||
description = "Simple Network Simulation For The Funs!" | ||
authors = ["Wajdi Al-Hawari <[email protected]>"] | ||
readme = "README.md" | ||
|
||
[tool.poetry.dependencies] | ||
python = "~3.11" | ||
scapy = "^2.5.0" | ||
|
||
[tool.poetry.group.dev.dependencies] | ||
pytest = "^7.4.4" | ||
pytest-asyncio = "^0.23.4" | ||
coverage = "^7.4.1" | ||
pytest-cov = "^4.1.0" | ||
mypy = "^1.8.0" | ||
black = "^24.1.1" | ||
isort = "^5.13.2" | ||
flake8 = "^7.0.0" | ||
pylint = "^3.0.3" | ||
tox = "^4.12.1" | ||
hypothesis = "^6.92.9" | ||
|
||
[build-system] | ||
requires = ["poetry-core"] | ||
build-backend = "poetry.core.masonry.api" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[pytest] | ||
pythonpath = src/edunet |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import asyncio | ||
import urllib.request | ||
import uuid | ||
|
||
|
||
async def send_request_and_collect_response(identifier): | ||
url = 'http://localhost:9999' | ||
headers = {'X-UUID': str(identifier)} # Include the UUID in the request headers | ||
|
||
with urllib.request.urlopen(urllib.request.Request(url, headers=headers)) as response: | ||
# Collect and return the response along with the identifier and response UUID | ||
response_data = response.read().decode('utf-8') | ||
print(f"Received response for identifier {identifier}: {response_data}") | ||
return identifier, response_data | ||
|
||
|
||
async def main(): | ||
identifiers = [uuid.uuid4() for _ in range(5000)] | ||
expected_responses = {} # Dictionary to store expected responses with identifiers | ||
|
||
# Send concurrent requests using asyncio.gather | ||
tasks = [send_request_and_collect_response(identifier) for identifier in identifiers] | ||
responses = await asyncio.gather(*tasks) | ||
|
||
# Update expected_responses dictionary with the collected responses | ||
print(responses) | ||
for identifier, response in responses: | ||
expected_responses[identifier] = response | ||
|
||
# Perform assertions to ensure each client received the correct response | ||
for identifier, expected_response in expected_responses.items(): | ||
# Assert that the received response contains the UUID | ||
assert str(identifier) in expected_response, f"UUID mismatch for identifier {identifier}" | ||
|
||
asyncio.run(main()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import logging | ||
|
||
from core.applications.simple_http_application import SimpleHTTPApplication | ||
from core.networking.handlers.simple_http_connection_handler import ( | ||
SimpleHTTPConnectionHandler, | ||
) | ||
from core.networking.listeners.tcp_listener import TCPListener | ||
from core.nodes.simple_http_node import SimpleHTTPNode | ||
|
||
if __name__ == "__main__": | ||
logging.basicConfig(level=logging.DEBUG) | ||
# Instantiate an HTTP application | ||
http_application = SimpleHTTPApplication() | ||
# Wrap the HTTP application with a Connection Handler | ||
http_connection_handler = SimpleHTTPConnectionHandler(http_application) | ||
# Wrap the Connection handler with a TCP Listener | ||
tcp_server = TCPListener("127.0.0.1", 9999, http_connection_handler) | ||
|
||
http_node = SimpleHTTPNode(tcp_server) | ||
|
||
# Start the listener to start allowing communication to your application | ||
http_node.start() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import socket | ||
import threading | ||
from time import sleep | ||
|
||
error_count = 0 | ||
|
||
|
||
def send_requests(call_number): | ||
global error_count | ||
# Connect to the server | ||
server_address = ('127.0.0.1', 9999) | ||
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | ||
client_socket.connect(server_address) | ||
|
||
try: | ||
# Send a simple HTTP GET request | ||
request = b"GET / HTTP/1.1\r\nHost: localhost\r\nConnection: " + str(call_number).encode() + b"\r\n\r\n" | ||
client_socket.sendall(request) | ||
|
||
# Receive and print the server's response | ||
response = client_socket.recv(4096) | ||
print("Received response:", response.decode()) | ||
except Exception as e: | ||
print(f"fail {e}") | ||
error_count += 1 | ||
finally: | ||
# Close the client socket | ||
client_socket.close() | ||
|
||
# Number of concurrent connections to create | ||
num_connections = 500 | ||
|
||
# Create threads to send requests concurrently | ||
threads = [] | ||
for i in range(num_connections): | ||
#sleep(0.08) | ||
thread = threading.Thread(target=send_requests, args=(i,)) | ||
thread.start() | ||
threads.append(thread) | ||
|
||
# Wait for all threads to finish | ||
for thread in threads: | ||
thread.join() | ||
|
||
print(error_count) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
from abc import abstractmethod, ABC | ||
|
||
from models.base_types import Request, Response | ||
|
||
|
||
class Application(ABC): | ||
""" | ||
Base Application implementation for any type of application that simply handles | ||
request and responses | ||
class MyApplication(Application): | ||
def handle_request(self, request): | ||
# implementation using request object that might be of type HTTP, TCP, UDP | ||
return some_response | ||
""" | ||
|
||
@abstractmethod | ||
def handle_request(self, request_data: Request) -> Response: | ||
""" | ||
Handling the request intended coming from a ConnectionHandler implementation | ||
e.g. | ||
class HTTPApplication(Application): | ||
... | ||
def handle_request(self, foo, bar): | ||
self.custom_logic(foo, bar) | ||
""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import logging | ||
|
||
from core.applications.application import Application | ||
from models.http import HTTPRequest, HTTPResponse | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class SimpleHTTPApplication(Application): | ||
|
||
def handle_request(self, request_data: HTTPRequest) -> HTTPResponse: | ||
""" | ||
Provide a HTTPRequest object to receive a HTTPResponse object. | ||
""" | ||
|
||
logger.info(f"Request data: {request_data}") | ||
|
||
try: | ||
return HTTPResponse( | ||
status_code=200, | ||
status_text="OK", | ||
body=f"Message received: {request_data}", | ||
) | ||
except (SyntaxError, TypeError, ValueError, AttributeError) as e: | ||
logger.error(f"Could not construct data: {e}") | ||
return HTTPResponse( | ||
status_code=500, status_text="Internal Server Error", body="" | ||
) | ||
except Exception as e: | ||
logger.exception(f"Unexpected server error: {e}") | ||
return HTTPResponse( | ||
status_code=500, status_text="Internal Server Error", body="" | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
from abc import ABC, abstractmethod | ||
|
||
|
||
class ConnectionHandler(ABC): | ||
""" | ||
Interface for implementing a connection handler used by the TCPListener | ||
""" | ||
|
||
@abstractmethod | ||
def handle_connection(self, *args, **kwargs): | ||
""" | ||
This provides the interface that has to be implemented that will be used by the | ||
TCPListener. The main idea is that a connection handler is provided to the | ||
TCPListener to handle the socket data as at comes in. | ||
As an example, you can implement a wsgi handler to handle connections. | ||
class WSGIHandler(ConnectionHandler): | ||
def handle_connection(foo, bar): | ||
pass | ||
""" |
16 changes: 16 additions & 0 deletions
16
src/edunet/core/networking/handlers/simple_http_connection_handler.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import logging | ||
import socket | ||
|
||
from core.applications.application import Application | ||
from core.networking.handlers.connection_handler import ConnectionHandler | ||
from models.http import HTTPRequest | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class SimpleHTTPConnectionHandler(ConnectionHandler): | ||
def __init__(self, application: Application): | ||
self.application = application | ||
|
||
def handle_connection(self, data: bytes, client_socket: socket.socket) -> bytes: | ||
return self.application.handle_request(HTTPRequest.from_bytes(data)).to_bytes() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import socket | ||
|
||
from core.networking.handlers.connection_handler import ConnectionHandler | ||
|
||
|
||
class WSGIHandler(ConnectionHandler): | ||
def handle_connection(self, client_socket: socket.socket): | ||
pass |
Oops, something went wrong.