Factor Studios commited on
Commit
aaaaa79
·
verified ·
1 Parent(s): a48b91b

Upload 73 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. Dockerfile +52 -0
  2. __init__.py +0 -0
  3. __pycache__/__init__.cpython-311.pyc +0 -0
  4. __pycache__/app.cpython-311.pyc +0 -0
  5. __pycache__/main.cpython-311.pyc +0 -0
  6. __pycache__/main_isp.cpython-311.pyc +0 -0
  7. app.py +213 -0
  8. app_status.json +755 -0
  9. config.json +98 -0
  10. core/__init__.py +2 -0
  11. core/__pycache__/__init__.cpython-311.pyc +0 -0
  12. core/__pycache__/dhcp_server.cpython-311.pyc +0 -0
  13. core/__pycache__/firewall.cpython-311.pyc +0 -0
  14. core/__pycache__/ip_parser.cpython-311.pyc +0 -0
  15. core/__pycache__/logger.cpython-311.pyc +0 -0
  16. core/__pycache__/nat_engine.cpython-311.pyc +0 -0
  17. core/__pycache__/openvpn_manager.cpython-311.pyc +0 -0
  18. core/__pycache__/packet_bridge.cpython-311.pyc +0 -0
  19. core/__pycache__/session_tracker.cpython-311.pyc +0 -0
  20. core/__pycache__/socket_translator.cpython-311.pyc +0 -0
  21. core/__pycache__/tcp_engine.cpython-311.pyc +0 -0
  22. core/__pycache__/traffic_router.cpython-311.pyc +0 -0
  23. core/__pycache__/virtual_router.cpython-311.pyc +0 -0
  24. core/dhcp_server.py +391 -0
  25. core/firewall.py +523 -0
  26. core/ip_parser.py +546 -0
  27. core/logger.py +555 -0
  28. core/nat_engine.py +638 -0
  29. core/openvpn_manager.py +508 -0
  30. core/packet_bridge.py +664 -0
  31. core/session_tracker.py +602 -0
  32. core/socket_translator.py +653 -0
  33. core/tcp_engine.py +716 -0
  34. core/traffic_router.py +132 -0
  35. core/virtual_router.py +565 -0
  36. database/app.db +0 -0
  37. flask_app.log +0 -0
  38. main.py +76 -0
  39. main_isp.py +273 -0
  40. models/__pycache__/enhanced_user.cpython-311.pyc +0 -0
  41. models/__pycache__/user.cpython-311.pyc +0 -0
  42. models/enhanced_user.py +427 -0
  43. models/user.py +20 -0
  44. openvpn/ca.crt +20 -0
  45. openvpn/dh.pem +8 -0
  46. openvpn/server.conf +21 -0
  47. openvpn/server.crt +86 -0
  48. openvpn/server.key +28 -0
  49. requirements.txt +33 -0
  50. routes/__pycache__/auth.cpython-311.pyc +0 -0
Dockerfile ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Virtual ISP Stack with OpenVPN Integration
2
+ # Dockerfile for containerized deployment
3
+
4
+ FROM python:3.11-slim
5
+
6
+ # Set working directory
7
+ WORKDIR /app
8
+
9
+ # Install system dependencies
10
+ RUN apt-get update && apt-get install -y \
11
+ openvpn \
12
+ iptables \
13
+ iproute2 \
14
+ net-tools \
15
+ procps \
16
+ build-essential \
17
+ python3-dev \
18
+ && rm -rf /var/lib/apt/lists/*
19
+
20
+ COPY openvpn/server.conf /etc/openvpn/server/server.conf
21
+ COPY openvpn/ca.crt /etc/openvpn/server/ca.crt
22
+ COPY openvpn/server.crt /etc/openvpn/server/server.crt
23
+ COPY openvpn/server.key /etc/openvpn/server/server.key
24
+ COPY openvpn/dh.pem /etc/openvpn/server/dh.pem
25
+
26
+ # Copy requirements and install Python dependencies
27
+ COPY requirements.txt .
28
+ RUN pip install --no-cache-dir -r requirements.txt
29
+
30
+ # Copy application files
31
+ COPY . .
32
+
33
+ # Create necessary directories
34
+ RUN mkdir -p /tmp/vpn_client_configs \
35
+ && mkdir -p /var/log/openvpn \
36
+ && mkdir -p database
37
+
38
+ # Set environment variables
39
+ ENV FLASK_APP=app.py
40
+ ENV FLASK_ENV=production
41
+ ENV PORT=7860
42
+
43
+ # Expose port
44
+ EXPOSE 7860
45
+
46
+ # Health check
47
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
48
+ CMD curl -f http://localhost:7860/health || exit 1
49
+
50
+ # Run the application
51
+ CMD ["python", "app.py"]
52
+
__init__.py ADDED
File without changes
__pycache__/__init__.cpython-311.pyc ADDED
Binary file (173 Bytes). View file
 
__pycache__/app.cpython-311.pyc ADDED
Binary file (9.36 kB). View file
 
__pycache__/main.cpython-311.pyc ADDED
Binary file (3.57 kB). View file
 
__pycache__/main_isp.cpython-311.pyc ADDED
Binary file (10.8 kB). View file
 
app.py ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Virtual ISP Stack with OpenVPN Integration
4
+ HuggingFace Spaces Entry Point
5
+
6
+ This application provides a complete Virtual ISP stack with OpenVPN server integration,
7
+ allowing users to manage VPN connections, generate client configurations, and monitor
8
+ network traffic through a RESTful API.
9
+ """
10
+
11
+ import os
12
+ import sys
13
+ import logging
14
+
15
+ # Add current directory to Python path
16
+ sys.path.insert(0, os.path.dirname(__file__))
17
+
18
+ from flask import Flask, send_from_directory, jsonify
19
+ from flask_cors import CORS
20
+ from models.enhanced_user import db
21
+ from routes.auth import auth_bp
22
+ from routes.vpn_client import vpn_client_bp
23
+ from routes.vpn_server import vpn_server_bp
24
+ from routes.isp_api import init_engines, isp_api
25
+
26
+ # Configure logging
27
+ logging.basicConfig(
28
+ level=logging.INFO,
29
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
30
+ )
31
+ logger = logging.getLogger(__name__)
32
+
33
+ # Create Flask application
34
+ app = Flask(__name__, static_folder=os.path.join(os.path.dirname(__file__), 'static'))
35
+
36
+ # Enable CORS for all routes
37
+ CORS(app, origins="*")
38
+
39
+ # Configuration
40
+ app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'vpn-isp-stack-secret-key-change-in-production')
41
+
42
+ # Database configuration
43
+ database_path = os.path.join(os.path.dirname(__file__), 'database', 'app.db')
44
+ app.config['SQLALCHEMY_DATABASE_URI'] = f"sqlite:///{database_path}"
45
+ app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
46
+
47
+ # Initialize database
48
+ db.init_app(app)
49
+
50
+ # Register blueprints
51
+ app.register_blueprint(auth_bp, url_prefix='/api')
52
+ app.register_blueprint(vpn_client_bp, url_prefix='/api')
53
+ app.register_blueprint(vpn_server_bp, url_prefix='/api')
54
+ app.register_blueprint(isp_api, url_prefix='/api')
55
+
56
+ # Engine configuration
57
+ app.config.update({
58
+ "dhcp": {
59
+ "network": "10.0.0.0/24",
60
+ "range_start": "10.0.0.10",
61
+ "range_end": "10.0.0.100",
62
+ "lease_time": 3600,
63
+ "gateway": "10.0.0.1",
64
+ "dns_servers": ["8.8.8.8", "8.8.4.4"]
65
+ },
66
+ "nat": {
67
+ "port_range_start": 10000,
68
+ "port_range_end": 65535,
69
+ "session_timeout": 300
70
+ },
71
+ "firewall": {
72
+ "default_policy": "ACCEPT",
73
+ "log_blocked": True
74
+ },
75
+ "tcp": {
76
+ "initial_window": 65535,
77
+ "max_retries": 3,
78
+ "timeout": 30
79
+ },
80
+ "openvpn": {
81
+ "server_ip": "10.8.0.1",
82
+ "server_port": 1194,
83
+ "network": "10.8.0.0/24"
84
+ },
85
+ "logger": {
86
+ "log_level": "INFO",
87
+ "log_file": "/tmp/virtual_isp.log"
88
+ }
89
+ })
90
+
91
+ # Add VPN server configuration
92
+ app.config.update({
93
+ 'VPN_SERVER_IP': os.environ.get('VPN_SERVER_IP', '127.0.0.1'),
94
+ 'OPENVPN_PORT': int(os.environ.get('OPENVPN_PORT', 1194)),
95
+ 'IKEV2_PORT': int(os.environ.get('IKEV2_PORT', 500)),
96
+ 'WIREGUARD_PORT': int(os.environ.get('WIREGUARD_PORT', 51820)),
97
+ 'WIREGUARD_SERVER_PUBLIC_KEY': os.environ.get('WIREGUARD_SERVER_PUBLIC_KEY', 'SERVER_PUBLIC_KEY_HERE')
98
+ })
99
+
100
+ # Initialize database tables
101
+ with app.app_context():
102
+ try:
103
+ db.create_all()
104
+ logger.info("Database tables created successfully")
105
+ except Exception as e:
106
+ logger.error(f"Error creating database tables: {e}")
107
+
108
+ # Initialize engines
109
+ try:
110
+ init_engines(app.config)
111
+ logger.info("All engines initialized successfully")
112
+ except Exception as e:
113
+ logger.error(f"Error initializing engines: {e}")
114
+
115
+ @app.route('/')
116
+ def index():
117
+ """Main index page - redirect to auth if not logged in"""
118
+ return serve_static('auth.html')
119
+
120
+ @app.route('/auth')
121
+ def auth_page():
122
+ """Authentication page"""
123
+ return serve_static('auth.html')
124
+
125
+ @app.route('/dashboard')
126
+ def dashboard_page():
127
+ """Dashboard page"""
128
+ return serve_static('index.html')
129
+
130
+ @app.route('/health')
131
+ def health_check():
132
+ """Health check endpoint for monitoring"""
133
+ return jsonify({
134
+ 'status': 'healthy',
135
+ 'service': 'Virtual ISP Stack with OpenVPN',
136
+ 'version': '1.0.0'
137
+ })
138
+
139
+ @app.route('/api')
140
+ def api_info():
141
+ """API information endpoint"""
142
+ return jsonify({
143
+ 'service': 'Virtual ISP Stack API',
144
+ 'version': '1.0.0',
145
+ 'endpoints': {
146
+ 'openvpn': {
147
+ 'status': '/api/openvpn/status',
148
+ 'start': '/api/openvpn/start',
149
+ 'stop': '/api/openvpn/stop',
150
+ 'clients': '/api/openvpn/clients',
151
+ 'config': '/api/openvpn/config/<client_name>',
152
+ 'stats': '/api/openvpn/stats',
153
+ 'configs': '/api/openvpn/configs'
154
+ },
155
+ 'dhcp': {
156
+ 'leases': '/api/dhcp/leases'
157
+ },
158
+ 'nat': {
159
+ 'sessions': '/api/nat/sessions',
160
+ 'stats': '/api/nat/stats'
161
+ },
162
+ 'firewall': {
163
+ 'rules': '/api/firewall/rules',
164
+ 'logs': '/api/firewall/logs',
165
+ 'stats': '/api/firewall/stats'
166
+ }
167
+ }
168
+ })
169
+
170
+ @app.route('/<path:path>')
171
+ def serve_static(path):
172
+ """Serve static files"""
173
+ static_folder_path = app.static_folder
174
+ if static_folder_path is None:
175
+ return jsonify({'error': 'Static folder not configured'}), 404
176
+
177
+ if path != "" and os.path.exists(os.path.join(static_folder_path, path)):
178
+ return send_from_directory(static_folder_path, path)
179
+ else:
180
+ index_path = os.path.join(static_folder_path, 'index.html')
181
+ if os.path.exists(index_path):
182
+ return send_from_directory(static_folder_path, 'index.html')
183
+ else:
184
+ return jsonify({
185
+ 'message': 'Virtual ISP Stack with OpenVPN Integration',
186
+ 'status': 'running',
187
+ 'api_docs': '/api'
188
+ })
189
+
190
+ @app.errorhandler(404)
191
+ def not_found(error):
192
+ """Handle 404 errors"""
193
+ return jsonify({'error': 'Endpoint not found', 'api_docs': '/api'}), 404
194
+
195
+ @app.errorhandler(500)
196
+ def internal_error(error):
197
+ """Handle 500 errors"""
198
+ return jsonify({'error': 'Internal server error'}), 500
199
+
200
+ if __name__ == '__main__':
201
+ # Get port from environment variable (HuggingFace Spaces uses PORT)
202
+ port = int(os.environ.get('PORT', 7860))
203
+
204
+ logger.info(f"Starting Virtual ISP Stack with OpenVPN on port {port}")
205
+
206
+ # Run the application
207
+ app.run(
208
+ host='0.0.0.0',
209
+ port=port,
210
+ debug=False,
211
+ threaded=True
212
+ )
213
+
app_status.json ADDED
@@ -0,0 +1,755 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Virtual ISP Stack - Network Management Dashboard</title>
7
+ <link rel="stylesheet" href="styles.css">
8
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
9
+ </head>
10
+ <body>
11
+ <div class="app-container">
12
+ <!-- Header -->
13
+ <header class="header">
14
+ <div class="header-content">
15
+ <div class="logo">
16
+ <i class="fas fa-network-wired"></i>
17
+ <h1>Virtual ISP Stack</h1>
18
+ </div>
19
+ <div class="header-status">
20
+ <div class="status-indicator" id="systemStatus">
21
+ <i class="fas fa-circle"></i>
22
+ <span>System Status</span>
23
+ </div>
24
+ <div class="refresh-btn" onclick="refreshData()">
25
+ <i class="fas fa-sync-alt"></i>
26
+ </div>
27
+ </div>
28
+ </div>
29
+ </header>
30
+
31
+ <!-- Navigation -->
32
+ <nav class="sidebar">
33
+ <div class="nav-menu">
34
+ <div class="nav-item active" data-section="dashboard">
35
+ <i class="fas fa-tachometer-alt"></i>
36
+ <span>Dashboard</span>
37
+ </div>
38
+ <div class="nav-item" data-section="dhcp">
39
+ <i class="fas fa-server"></i>
40
+ <span>DHCP</span>
41
+ </div>
42
+ <div class="nav-item" data-section="nat">
43
+ <i class="fas fa-exchange-alt"></i>
44
+ <span>NAT</span>
45
+ </div>
46
+ <div class="nav-item" data-section="firewall">
47
+ <i class="fas fa-shield-alt"></i>
48
+ <span>Firewall</span>
49
+ </div>
50
+ <div class="nav-item" data-section="router">
51
+ <i class="fas fa-route"></i>
52
+ <span>Router</span>
53
+ </div>
54
+ <div class="nav-item" data-section="bridge">
55
+ <i class="fas fa-link"></i>
56
+ <span>Bridge</span>
57
+ </div>
58
+ <div class="nav-item" data-section="sessions">
59
+ <i class="fas fa-users"></i>
60
+ <span>Sessions</span>
61
+ </div>
62
+ <div class="nav-item" data-section="logs">
63
+ <i class="fas fa-file-alt"></i>
64
+ <span>Logs</span>
65
+ </div>
66
+ <div class="nav-item" data-section="vpn">
67
+ <i class="fas fa-shield-alt"></i>
68
+ <span>VPN</span>
69
+ </div>
70
+ <div class="nav-item" data-section="config">
71
+ <i class="fas fa-cog"></i>
72
+ <span>Config</span>
73
+ </div>
74
+ </div>
75
+ </nav>
76
+
77
+ <!-- Main Content -->
78
+ <main class="main-content">
79
+ <!-- Dashboard Section -->
80
+ <section id="dashboard" class="content-section active">
81
+ <div class="section-header">
82
+ <h2>System Dashboard</h2>
83
+ <p>Overview of Virtual ISP Stack components and performance</p>
84
+ </div>
85
+
86
+ <!-- System Status Cards -->
87
+ <div class="stats-grid">
88
+ <div class="stat-card">
89
+ <div class="stat-icon">
90
+ <i class="fas fa-server"></i>
91
+ </div>
92
+ <div class="stat-content">
93
+ <h3 id="dhcpLeaseCount">0</h3>
94
+ <p>DHCP Leases</p>
95
+ </div>
96
+ </div>
97
+
98
+ <div class="stat-card">
99
+ <div class="stat-icon">
100
+ <i class="fas fa-exchange-alt"></i>
101
+ </div>
102
+ <div class="stat-content">
103
+ <h3 id="natSessionCount">0</h3>
104
+ <p>NAT Sessions</p>
105
+ </div>
106
+ </div>
107
+
108
+ <div class="stat-card">
109
+ <div class="stat-icon">
110
+ <i class="fas fa-shield-alt"></i>
111
+ </div>
112
+ <div class="stat-content">
113
+ <h3 id="firewallRuleCount">0</h3>
114
+ <p>Firewall Rules</p>
115
+ </div>
116
+ </div>
117
+
118
+ <div class="stat-card">
119
+ <div class="stat-icon">
120
+ <i class="fas fa-link"></i>
121
+ </div>
122
+ <div class="stat-content">
123
+ <h3 id="bridgeClientCount">0</h3>
124
+ <p>Bridge Clients</p>
125
+ </div>
126
+ </div>
127
+ </div>
128
+
129
+ <!-- Component Status -->
130
+ <div class="component-status">
131
+ <h3>Component Status</h3>
132
+ <div class="component-grid" id="componentStatus">
133
+ <!-- Component status items will be populated by JavaScript -->
134
+ </div>
135
+ </div>
136
+
137
+ <!-- Real-time Charts -->
138
+ <div class="charts-container">
139
+ <div class="chart-card">
140
+ <h3>Network Traffic</h3>
141
+ <canvas id="trafficChart"></canvas>
142
+ </div>
143
+ <div class="chart-card">
144
+ <h3>Connection Distribution</h3>
145
+ <canvas id="connectionChart"></canvas>
146
+ </div>
147
+ </div>
148
+ </section>
149
+
150
+ <!-- DHCP Section -->
151
+ <section id="dhcp" class="content-section">
152
+ <div class="section-header">
153
+ <h2>DHCP Management</h2>
154
+ <p>Manage DHCP leases and configuration</p>
155
+ </div>
156
+
157
+ <div class="table-container">
158
+ <div class="table-header">
159
+ <h3>Active Leases</h3>
160
+ <button class="btn btn-secondary" onclick="refreshDHCPLeases()">
161
+ <i class="fas fa-sync-alt"></i> Refresh
162
+ </button>
163
+ </div>
164
+ <div class="table-wrapper">
165
+ <table id="dhcpTable">
166
+ <thead>
167
+ <tr>
168
+ <th>MAC Address</th>
169
+ <th>IP Address</th>
170
+ <th>Lease Time</th>
171
+ <th>Remaining</th>
172
+ <th>State</th>
173
+ <th>Actions</th>
174
+ </tr>
175
+ </thead>
176
+ <tbody id="dhcpTableBody">
177
+ <!-- DHCP leases will be populated here -->
178
+ </tbody>
179
+ </table>
180
+ </div>
181
+ </div>
182
+ </section>
183
+
184
+ <!-- NAT Section -->
185
+ <section id="nat" class="content-section">
186
+ <div class="section-header">
187
+ <h2>NAT Management</h2>
188
+ <p>Network Address Translation sessions and statistics</p>
189
+ </div>
190
+
191
+ <div class="nat-stats">
192
+ <div class="stat-row">
193
+ <div class="stat-item">
194
+ <span class="stat-label">Active Sessions:</span>
195
+ <span class="stat-value" id="natActiveSessions">0</span>
196
+ </div>
197
+ <div class="stat-item">
198
+ <span class="stat-label">Port Utilization:</span>
199
+ <span class="stat-value" id="natPortUtilization">0%</span>
200
+ </div>
201
+ <div class="stat-item">
202
+ <span class="stat-label">Bytes Translated:</span>
203
+ <span class="stat-value" id="natBytesTranslated">0</span>
204
+ </div>
205
+ </div>
206
+ </div>
207
+
208
+ <div class="table-container">
209
+ <div class="table-header">
210
+ <h3>NAT Sessions</h3>
211
+ <button class="btn btn-secondary" onclick="refreshNATSessions()">
212
+ <i class="fas fa-sync-alt"></i> Refresh
213
+ </button>
214
+ </div>
215
+ <div class="table-wrapper">
216
+ <table id="natTable">
217
+ <thead>
218
+ <tr>
219
+ <th>Virtual IP:Port</th>
220
+ <th>Real IP:Port</th>
221
+ <th>Host IP:Port</th>
222
+ <th>Protocol</th>
223
+ <th>Duration</th>
224
+ <th>Bytes In/Out</th>
225
+ <th>Actions</th>
226
+ </tr>
227
+ </thead>
228
+ <tbody id="natTableBody">
229
+ <!-- NAT sessions will be populated here -->
230
+ </tbody>
231
+ </table>
232
+ </div>
233
+ </div>
234
+ </section>
235
+
236
+ <!-- Firewall Section -->
237
+ <section id="firewall" class="content-section">
238
+ <div class="section-header">
239
+ <h2>Firewall Management</h2>
240
+ <p>Configure firewall rules and monitor traffic</p>
241
+ </div>
242
+
243
+ <div class="firewall-controls">
244
+ <button class="btn btn-primary" onclick="showAddRuleModal()">
245
+ <i class="fas fa-plus"></i> Add Rule
246
+ </button>
247
+ <button class="btn btn-secondary" onclick="refreshFirewallRules()">
248
+ <i class="fas fa-sync-alt"></i> Refresh
249
+ </button>
250
+ </div>
251
+
252
+ <div class="table-container">
253
+ <div class="table-header">
254
+ <h3>Firewall Rules</h3>
255
+ </div>
256
+ <div class="table-wrapper">
257
+ <table id="firewallTable">
258
+ <thead>
259
+ <tr>
260
+ <th>Priority</th>
261
+ <th>Rule ID</th>
262
+ <th>Action</th>
263
+ <th>Direction</th>
264
+ <th>Source</th>
265
+ <th>Destination</th>
266
+ <th>Protocol</th>
267
+ <th>Hits</th>
268
+ <th>Status</th>
269
+ <th>Actions</th>
270
+ </tr>
271
+ </thead>
272
+ <tbody id="firewallTableBody">
273
+ <!-- Firewall rules will be populated here -->
274
+ </tbody>
275
+ </table>
276
+ </div>
277
+ </div>
278
+ </section>
279
+
280
+ <!-- Router Section -->
281
+ <section id="router" class="content-section">
282
+ <div class="section-header">
283
+ <h2>Router Management</h2>
284
+ <p>Routing table and network interfaces</p>
285
+ </div>
286
+
287
+ <div class="router-tabs">
288
+ <div class="tab-buttons">
289
+ <button class="tab-btn active" data-tab="routes">Routes</button>
290
+ <button class="tab-btn" data-tab="interfaces">Interfaces</button>
291
+ <button class="tab-btn" data-tab="arp">ARP Table</button>
292
+ </div>
293
+
294
+ <div class="tab-content">
295
+ <div id="routes" class="tab-pane active">
296
+ <div class="table-container">
297
+ <table id="routesTable">
298
+ <thead>
299
+ <tr>
300
+ <th>Destination</th>
301
+ <th>Gateway</th>
302
+ <th>Interface</th>
303
+ <th>Metric</th>
304
+ <th>Type</th>
305
+ <th>Use Count</th>
306
+ <th>Last Used</th>
307
+ </tr>
308
+ </thead>
309
+ <tbody id="routesTableBody">
310
+ <!-- Routes will be populated here -->
311
+ </tbody>
312
+ </table>
313
+ </div>
314
+ </div>
315
+
316
+ <div id="interfaces" class="tab-pane">
317
+ <div class="table-container">
318
+ <table id="interfacesTable">
319
+ <thead>
320
+ <tr>
321
+ <th>Name</th>
322
+ <th>IP Address</th>
323
+ <th>Network</th>
324
+ <th>MTU</th>
325
+ <th>Status</th>
326
+ <th>Actions</th>
327
+ </tr>
328
+ </thead>
329
+ <tbody id="interfacesTableBody">
330
+ <!-- Interfaces will be populated here -->
331
+ </tbody>
332
+ </table>
333
+ </div>
334
+ </div>
335
+
336
+ <div id="arp" class="tab-pane">
337
+ <div class="table-container">
338
+ <table id="arpTable">
339
+ <thead>
340
+ <tr>
341
+ <th>IP Address</th>
342
+ <th>MAC Address</th>
343
+ <th>Actions</th>
344
+ </tr>
345
+ </thead>
346
+ <tbody id="arpTableBody">
347
+ <!-- ARP entries will be populated here -->
348
+ </tbody>
349
+ </table>
350
+ </div>
351
+ </div>
352
+ </div>
353
+ </div>
354
+ </section>
355
+
356
+ <!-- Bridge Section -->
357
+ <section id="bridge" class="content-section">
358
+ <div class="section-header">
359
+ <h2>Packet Bridge</h2>
360
+ <p>Connected clients and bridge statistics</p>
361
+ </div>
362
+
363
+ <div class="bridge-info">
364
+ <div class="info-card">
365
+ <h4>WebSocket Server</h4>
366
+ <p>Port: 8765</p>
367
+ <p>Status: <span class="status-active">Active</span></p>
368
+ </div>
369
+ <div class="info-card">
370
+ <h4>TCP Server</h4>
371
+ <p>Port: 8766</p>
372
+ <p>Status: <span class="status-active">Active</span></p>
373
+ </div>
374
+ </div>
375
+
376
+ <div class="table-container">
377
+ <div class="table-header">
378
+ <h3>Connected Clients</h3>
379
+ <button class="btn btn-secondary" onclick="refreshBridgeClients()">
380
+ <i class="fas fa-sync-alt"></i> Refresh
381
+ </button>
382
+ </div>
383
+ <div class="table-wrapper">
384
+ <table id="bridgeTable">
385
+ <thead>
386
+ <tr>
387
+ <th>Client ID</th>
388
+ <th>Type</th>
389
+ <th>Remote Address</th>
390
+ <th>Connected Time</th>
391
+ <th>Packets In/Out</th>
392
+ <th>Bytes In/Out</th>
393
+ <th>Actions</th>
394
+ </tr>
395
+ </thead>
396
+ <tbody id="bridgeTableBody">
397
+ <!-- Bridge clients will be populated here -->
398
+ </tbody>
399
+ </table>
400
+ </div>
401
+ </div>
402
+ </section>
403
+
404
+ <!-- Sessions Section -->
405
+ <section id="sessions" class="content-section">
406
+ <div class="section-header">
407
+ <h2>Session Tracking</h2>
408
+ <p>Unified view of all network sessions</p>
409
+ </div>
410
+
411
+ <div class="session-summary" id="sessionSummary">
412
+ <!-- Session summary will be populated here -->
413
+ </div>
414
+
415
+ <div class="table-container">
416
+ <div class="table-header">
417
+ <h3>Active Sessions</h3>
418
+ <div class="table-controls">
419
+ <select id="sessionTypeFilter" onchange="filterSessions()">
420
+ <option value="">All Types</option>
421
+ <option value="DHCP_LEASE">DHCP Lease</option>
422
+ <option value="NAT_SESSION">NAT Session</option>
423
+ <option value="TCP_CONNECTION">TCP Connection</option>
424
+ <option value="SOCKET_CONNECTION">Socket Connection</option>
425
+ <option value="BRIDGE_CLIENT">Bridge Client</option>
426
+ </select>
427
+ <button class="btn btn-secondary" onclick="refreshSessions()">
428
+ <i class="fas fa-sync-alt"></i> Refresh
429
+ </button>
430
+ </div>
431
+ </div>
432
+ <div class="table-wrapper">
433
+ <table id="sessionsTable">
434
+ <thead>
435
+ <tr>
436
+ <th>Session ID</th>
437
+ <th>Type</th>
438
+ <th>State</th>
439
+ <th>Virtual IP:Port</th>
440
+ <th>Real IP:Port</th>
441
+ <th>Protocol</th>
442
+ <th>Duration</th>
443
+ <th>Idle Time</th>
444
+ <th>Metrics</th>
445
+ </tr>
446
+ </thead>
447
+ <tbody id="sessionsTableBody">
448
+ <!-- Sessions will be populated here -->
449
+ </tbody>
450
+ </table>
451
+ </div>
452
+ </div>
453
+ </section>
454
+
455
+ <!-- Logs Section -->
456
+ <section id="logs" class="content-section">
457
+ <div class="section-header">
458
+ <h2>System Logs</h2>
459
+ <p>Monitor system events and troubleshoot issues</p>
460
+ </div>
461
+
462
+ <div class="log-controls">
463
+ <div class="log-filters">
464
+ <select id="logLevelFilter" onchange="filterLogs()">
465
+ <option value="">All Levels</option>
466
+ <option value="DEBUG">Debug</option>
467
+ <option value="INFO">Info</option>
468
+ <option value="WARNING">Warning</option>
469
+ <option value="ERROR">Error</option>
470
+ <option value="CRITICAL">Critical</option>
471
+ </select>
472
+ <select id="logCategoryFilter" onchange="filterLogs()">
473
+ <option value="">All Categories</option>
474
+ <option value="SYSTEM">System</option>
475
+ <option value="DHCP">DHCP</option>
476
+ <option value="NAT">NAT</option>
477
+ <option value="FIREWALL">Firewall</option>
478
+ <option value="TCP">TCP</option>
479
+ <option value="ROUTER">Router</option>
480
+ <option value="BRIDGE">Bridge</option>
481
+ <option value="SOCKET">Socket</option>
482
+ <option value="SESSION">Session</option>
483
+ <option value="SECURITY">Security</option>
484
+ </select>
485
+ <input type="text" id="logSearchInput" placeholder="Search logs..." onkeyup="searchLogs()">
486
+ </div>
487
+ <div class="log-actions">
488
+ <button class="btn btn-secondary" onclick="refreshLogs()">
489
+ <i class="fas fa-sync-alt"></i> Refresh
490
+ </button>
491
+ <button class="btn btn-danger" onclick="clearLogs()">
492
+ <i class="fas fa-trash"></i> Clear
493
+ </button>
494
+ </div>
495
+ </div>
496
+
497
+ <div class="log-container" id="logContainer">
498
+ <!-- Log entries will be populated here -->
499
+ </div>
500
+ </section>
501
+
502
+ <!-- VPN Section -->
503
+ <section id="vpn" class="content-section">
504
+ <div class="section-header">
505
+ <h2>VPN Management</h2>
506
+ <p>OpenVPN server management and client connections</p>
507
+ </div>
508
+
509
+ <!-- VPN Server Status -->
510
+ <div class="vpn-status">
511
+ <div class="status-card">
512
+ <div class="status-header">
513
+ <h3>OpenVPN Server</h3>
514
+ <div class="server-controls">
515
+ <button class="btn btn-success" id="startVpnBtn" onclick="startVpnServer()">
516
+ <i class="fas fa-play"></i> Start
517
+ </button>
518
+ <button class="btn btn-danger" id="stopVpnBtn" onclick="stopVpnServer()">
519
+ <i class="fas fa-stop"></i> Stop
520
+ </button>
521
+ <button class="btn btn-secondary" onclick="refreshVpnStatus()">
522
+ <i class="fas fa-sync-alt"></i> Refresh
523
+ </button>
524
+ </div>
525
+ </div>
526
+ <div class="status-info">
527
+ <div class="info-row">
528
+ <span class="info-label">Status:</span>
529
+ <span class="info-value" id="vpnServerStatus">Unknown</span>
530
+ </div>
531
+ <div class="info-row">
532
+ <span class="info-label">Server IP:</span>
533
+ <span class="info-value" id="vpnServerIp">-</span>
534
+ </div>
535
+ <div class="info-row">
536
+ <span class="info-label">Port:</span>
537
+ <span class="info-value" id="vpnServerPort">-</span>
538
+ </div>
539
+ <div class="info-row">
540
+ <span class="info-label">Connected Clients:</span>
541
+ <span class="info-value" id="vpnConnectedClients">0</span>
542
+ </div>
543
+ <div class="info-row">
544
+ <span class="info-label">Uptime:</span>
545
+ <span class="info-value" id="vpnUptime">-</span>
546
+ </div>
547
+ </div>
548
+ </div>
549
+ </div>
550
+
551
+ <!-- VPN Statistics -->
552
+ <div class="vpn-stats">
553
+ <div class="stat-item">
554
+ <span class="stat-label">Total Bytes Received:</span>
555
+ <span class="stat-value" id="vpnBytesReceived">0</span>
556
+ </div>
557
+ <div class="stat-item">
558
+ <span class="stat-label">Total Bytes Sent:</span>
559
+ <span class="stat-value" id="vpnBytesSent">0</span>
560
+ </div>
561
+ </div>
562
+
563
+ <!-- Connected Clients -->
564
+ <div class="table-container">
565
+ <div class="table-header">
566
+ <h3>Connected VPN Clients</h3>
567
+ <div class="table-controls">
568
+ <button class="btn btn-primary" onclick="showGenerateConfigModal()">
569
+ <i class="fas fa-plus"></i> Generate Client Config
570
+ </button>
571
+ <button class="btn btn-secondary" onclick="refreshVpnClients()">
572
+ <i class="fas fa-sync-alt"></i> Refresh
573
+ </button>
574
+ </div>
575
+ </div>
576
+ <div class="table-wrapper">
577
+ <table id="vpnClientsTable">
578
+ <thead>
579
+ <tr>
580
+ <th>Client ID</th>
581
+ <th>Common Name</th>
582
+ <th>VPN IP Address</th>
583
+ <th>Connected Since</th>
584
+ <th>Bytes Received</th>
585
+ <th>Bytes Sent</th>
586
+ <th>Status</th>
587
+ <th>Actions</th>
588
+ </tr>
589
+ </thead>
590
+ <tbody id="vpnClientsTableBody">
591
+ <!-- VPN clients will be populated here -->
592
+ </tbody>
593
+ </table>
594
+ </div>
595
+ </div>
596
+ </section>
597
+
598
+ <!-- Config Section -->
599
+ <section id="config" class="content-section">
600
+ <div class="section-header">
601
+ <h2>System Configuration</h2>
602
+ <p>Configure system parameters and settings</p>
603
+ </div>
604
+
605
+ <div class="config-container">
606
+ <div class="config-section">
607
+ <h3>DHCP Configuration</h3>
608
+ <div class="config-form" id="dhcpConfig">
609
+ <!-- DHCP config form will be populated here -->
610
+ </div>
611
+ </div>
612
+
613
+ <div class="config-section">
614
+ <h3>NAT Configuration</h3>
615
+ <div class="config-form" id="natConfig">
616
+ <!-- NAT config form will be populated here -->
617
+ </div>
618
+ </div>
619
+
620
+ <div class="config-section">
621
+ <h3>Firewall Configuration</h3>
622
+ <div class="config-form" id="firewallConfig">
623
+ <!-- Firewall config form will be populated here -->
624
+ </div>
625
+ </div>
626
+ </div>
627
+
628
+ <div class="config-actions">
629
+ <button class="btn btn-primary" onclick="saveConfiguration()">
630
+ <i class="fas fa-save"></i> Save Configuration
631
+ </button>
632
+ <button class="btn btn-secondary" onclick="resetConfiguration()">
633
+ <i class="fas fa-undo"></i> Reset to Defaults
634
+ </button>
635
+ </div>
636
+ </section>
637
+ </main>
638
+ </div>
639
+
640
+ <!-- Modals -->
641
+ <div id="addRuleModal" class="modal">
642
+ <div class="modal-content">
643
+ <div class="modal-header">
644
+ <h3>Add Firewall Rule</h3>
645
+ <span class="close" onclick="closeModal('addRuleModal')">&times;</span>
646
+ </div>
647
+ <div class="modal-body">
648
+ <form id="addRuleForm">
649
+ <div class="form-group">
650
+ <label for="ruleId">Rule ID:</label>
651
+ <input type="text" id="ruleId" name="ruleId" required>
652
+ </div>
653
+ <div class="form-group">
654
+ <label for="rulePriority">Priority:</label>
655
+ <input type="number" id="rulePriority" name="priority" value="100" required>
656
+ </div>
657
+ <div class="form-group">
658
+ <label for="ruleAction">Action:</label>
659
+ <select id="ruleAction" name="action" required>
660
+ <option value="ACCEPT">Accept</option>
661
+ <option value="DROP">Drop</option>
662
+ <option value="REJECT">Reject</option>
663
+ </select>
664
+ </div>
665
+ <div class="form-group">
666
+ <label for="ruleDirection">Direction:</label>
667
+ <select id="ruleDirection" name="direction">
668
+ <option value="BOTH">Both</option>
669
+ <option value="INBOUND">Inbound</option>
670
+ <option value="OUTBOUND">Outbound</option>
671
+ </select>
672
+ </div>
673
+ <div class="form-group">
674
+ <label for="ruleSourceIp">Source IP:</label>
675
+ <input type="text" id="ruleSourceIp" name="source_ip" placeholder="e.g., 192.168.1.0/24">
676
+ </div>
677
+ <div class="form-group">
678
+ <label for="ruleDestIp">Destination IP:</label>
679
+ <input type="text" id="ruleDestIp" name="dest_ip" placeholder="e.g., 10.0.0.0/8">
680
+ </div>
681
+ <div class="form-group">
682
+ <label for="ruleSourcePort">Source Port:</label>
683
+ <input type="text" id="ruleSourcePort" name="source_port" placeholder="e.g., 80, 80-90, 80,443">
684
+ </div>
685
+ <div class="form-group">
686
+ <label for="ruleDestPort">Destination Port:</label>
687
+ <input type="text" id="ruleDestPort" name="dest_port" placeholder="e.g., 80, 80-90, 80,443">
688
+ </div>
689
+ <div class="form-group">
690
+ <label for="ruleProtocol">Protocol:</label>
691
+ <select id="ruleProtocol" name="protocol">
692
+ <option value="">Any</option>
693
+ <option value="TCP">TCP</option>
694
+ <option value="UDP">UDP</option>
695
+ <option value="ICMP">ICMP</option>
696
+ </select>
697
+ </div>
698
+ <div class="form-group">
699
+ <label for="ruleDescription">Description:</label>
700
+ <input type="text" id="ruleDescription" name="description" placeholder="Rule description">
701
+ </div>
702
+ </form>
703
+ </div>
704
+ <div class="modal-footer">
705
+ <button type="button" class="btn btn-secondary" onclick="closeModal('addRuleModal')">Cancel</button>
706
+ <button type="button" class="btn btn-primary" onclick="addFirewallRule()">Add Rule</button>
707
+ </div>
708
+ </div>
709
+ </div>
710
+
711
+ <!-- Generate VPN Config Modal -->
712
+ <div id="generateConfigModal" class="modal">
713
+ <div class="modal-content">
714
+ <div class="modal-header">
715
+ <h3>Generate VPN Client Configuration</h3>
716
+ <span class="close" onclick="closeModal('generateConfigModal')">&times;</span>
717
+ </div>
718
+ <div class="modal-body">
719
+ <form id="generateConfigForm">
720
+ <div class="form-group">
721
+ <label for="clientName">Client Name:</label>
722
+ <input type="text" id="clientName" name="clientName" required
723
+ placeholder="Enter client name (e.g., client1)">
724
+ </div>
725
+ <div class="form-group">
726
+ <label for="serverIp">Server IP:</label>
727
+ <input type="text" id="serverIp" name="serverIp" required
728
+ placeholder="Enter server IP address">
729
+ </div>
730
+ </form>
731
+ </div>
732
+ <div class="modal-footer">
733
+ <button type="button" class="btn btn-secondary" onclick="closeModal('generateConfigModal')">Cancel</button>
734
+ <button type="button" class="btn btn-primary" onclick="generateClientConfig()">Generate Config</button>
735
+ </div>
736
+ </div>
737
+ </div>
738
+
739
+ <!-- Loading Overlay -->
740
+ <div id="loadingOverlay" class="loading-overlay">
741
+ <div class="loading-spinner">
742
+ <i class="fas fa-spinner fa-spin"></i>
743
+ <p>Loading...</p>
744
+ </div>
745
+ </div>
746
+
747
+ <!-- Toast Notifications -->
748
+ <div id="toastContainer" class="toast-container"></div>
749
+
750
+ <!-- JavaScript -->
751
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
752
+ <script src="app.js"></script>
753
+ </body>
754
+ </html>
755
+
config.json ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "dhcp": {
3
+ "network": "10.0.0.0/24",
4
+ "range_start": "10.0.0.10",
5
+ "range_end": "10.0.0.100",
6
+ "lease_time": 3600,
7
+ "gateway": "10.0.0.1",
8
+ "dns_servers": [
9
+ "8.8.8.8",
10
+ "8.8.4.4"
11
+ ]
12
+ },
13
+ "nat": {
14
+ "port_range_start": 10000,
15
+ "port_range_end": 65535,
16
+ "session_timeout": 300,
17
+ "host_ip": "0.0.0.0"
18
+ },
19
+ "firewall": {
20
+ "default_policy": "ACCEPT",
21
+ "log_blocked": true,
22
+ "log_accepted": false,
23
+ "max_log_entries": 10000,
24
+ "rules": [
25
+ {
26
+ "rule_id": "allow_dhcp",
27
+ "priority": 1,
28
+ "action": "ACCEPT",
29
+ "direction": "BOTH",
30
+ "dest_port": "67,68",
31
+ "protocol": "UDP",
32
+ "description": "Allow DHCP traffic",
33
+ "enabled": true
34
+ },
35
+ {
36
+ "rule_id": "allow_dns",
37
+ "priority": 2,
38
+ "action": "ACCEPT",
39
+ "direction": "BOTH",
40
+ "dest_port": "53",
41
+ "protocol": "UDP",
42
+ "description": "Allow DNS traffic",
43
+ "enabled": true
44
+ }
45
+ ]
46
+ },
47
+ "tcp": {
48
+ "initial_window": 65535,
49
+ "max_retries": 3,
50
+ "timeout": 300,
51
+ "time_wait_timeout": 120,
52
+ "mss": 1460
53
+ },
54
+ "router": {
55
+ "router_id": "virtual-isp-router",
56
+ "default_gateway": "10.0.0.1",
57
+ "interfaces": [
58
+ {
59
+ "name": "virtual0",
60
+ "ip_address": "10.0.0.1",
61
+ "netmask": "255.255.255.0",
62
+ "enabled": true,
63
+ "mtu": 1500
64
+ }
65
+ ],
66
+ "static_routes": []
67
+ },
68
+ "socket_translator": {
69
+ "connect_timeout": 10,
70
+ "read_timeout": 30,
71
+ "max_connections": 1000,
72
+ "buffer_size": 8192
73
+ },
74
+ "packet_bridge": {
75
+ "websocket_host": "0.0.0.0",
76
+ "websocket_port": 8765,
77
+ "tcp_host": "0.0.0.0",
78
+ "tcp_port": 8766,
79
+ "max_clients": 100,
80
+ "client_timeout": 300
81
+ },
82
+ "session_tracker": {
83
+ "max_sessions": 10000,
84
+ "session_timeout": 3600,
85
+ "cleanup_interval": 300,
86
+ "metrics_retention": 86400
87
+ },
88
+ "logger": {
89
+ "log_level": "INFO",
90
+ "log_to_file": true,
91
+ "log_file_path": "/tmp/virtual_isp.log",
92
+ "log_file_max_size": 10485760,
93
+ "log_file_backup_count": 5,
94
+ "log_to_console": true,
95
+ "structured_logging": true,
96
+ "max_memory_logs": 10000
97
+ }
98
+ }
core/__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # Core networking modules for the virtual ISP stack
2
+
core/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (154 Bytes). View file
 
core/__pycache__/dhcp_server.cpython-311.pyc ADDED
Binary file (21.2 kB). View file
 
core/__pycache__/firewall.cpython-311.pyc ADDED
Binary file (27.4 kB). View file
 
core/__pycache__/ip_parser.cpython-311.pyc ADDED
Binary file (23 kB). View file
 
core/__pycache__/logger.cpython-311.pyc ADDED
Binary file (29.4 kB). View file
 
core/__pycache__/nat_engine.cpython-311.pyc ADDED
Binary file (34.9 kB). View file
 
core/__pycache__/openvpn_manager.cpython-311.pyc ADDED
Binary file (25.7 kB). View file
 
core/__pycache__/packet_bridge.cpython-311.pyc ADDED
Binary file (34.3 kB). View file
 
core/__pycache__/session_tracker.cpython-311.pyc ADDED
Binary file (33.9 kB). View file
 
core/__pycache__/socket_translator.cpython-311.pyc ADDED
Binary file (32.8 kB). View file
 
core/__pycache__/tcp_engine.cpython-311.pyc ADDED
Binary file (33.1 kB). View file
 
core/__pycache__/traffic_router.cpython-311.pyc ADDED
Binary file (8.18 kB). View file
 
core/__pycache__/virtual_router.cpython-311.pyc ADDED
Binary file (30.7 kB). View file
 
core/dhcp_server.py ADDED
@@ -0,0 +1,391 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ DHCP Server Module
3
+
4
+ Implements a user-space DHCP server that handles:
5
+ - DHCP DISCOVER → OFFER → REQUEST → ACK sequence
6
+ - IP lease management
7
+ - Lease renewals and expiration
8
+ """
9
+
10
+ import struct
11
+ import time
12
+ import socket
13
+ import threading
14
+ from typing import Dict, Optional, Tuple
15
+ from dataclasses import dataclass
16
+ from enum import Enum
17
+
18
+
19
+ class DHCPMessageType(Enum):
20
+ DISCOVER = 1
21
+ OFFER = 2
22
+ REQUEST = 3
23
+ DECLINE = 4
24
+ ACK = 5
25
+ NAK = 6
26
+ RELEASE = 7
27
+ INFORM = 8
28
+
29
+
30
+ @dataclass
31
+ class DHCPLease:
32
+ """Represents a DHCP lease"""
33
+ mac_address: str
34
+ ip_address: str
35
+ lease_time: int
36
+ lease_start: float
37
+ state: str = 'BOUND'
38
+
39
+ @property
40
+ def is_expired(self) -> bool:
41
+ return time.time() > (self.lease_start + self.lease_time)
42
+
43
+ @property
44
+ def remaining_time(self) -> int:
45
+ remaining = int((self.lease_start + self.lease_time) - time.time())
46
+ return max(0, remaining)
47
+
48
+
49
+ class DHCPPacket:
50
+ """DHCP packet parser and builder"""
51
+
52
+ def __init__(self):
53
+ self.op = 0 # Message op code / message type
54
+ self.htype = 1 # Hardware address type (Ethernet = 1)
55
+ self.hlen = 6 # Hardware address length
56
+ self.hops = 0 # Hops
57
+ self.xid = 0 # Transaction ID
58
+ self.secs = 0 # Seconds elapsed
59
+ self.flags = 0 # Flags
60
+ self.ciaddr = '0.0.0.0' # Client IP address
61
+ self.yiaddr = '0.0.0.0' # Your IP address
62
+ self.siaddr = '0.0.0.0' # Server IP address
63
+ self.giaddr = '0.0.0.0' # Gateway IP address
64
+ self.chaddr = b'\x00' * 16 # Client hardware address
65
+ self.sname = b'\x00' * 64 # Server name
66
+ self.file = b'\x00' * 128 # Boot file name
67
+ self.options = {} # DHCP options
68
+
69
+ @classmethod
70
+ def parse(cls, data: bytes) -> 'DHCPPacket':
71
+ """Parse DHCP packet from raw bytes"""
72
+ packet = cls()
73
+
74
+ # Parse fixed fields (first 236 bytes)
75
+ if len(data) < 236:
76
+ raise ValueError("DHCP packet too short")
77
+
78
+ fields = struct.unpack('!BBBBIHH4s4s4s4s16s64s128s', data[:236])
79
+ packet.op = fields[0]
80
+ packet.htype = fields[1]
81
+ packet.hlen = fields[2]
82
+ packet.hops = fields[3]
83
+ packet.xid = fields[4]
84
+ packet.secs = fields[5]
85
+ packet.flags = fields[6]
86
+ packet.ciaddr = socket.inet_ntoa(fields[7])
87
+ packet.yiaddr = socket.inet_ntoa(fields[8])
88
+ packet.siaddr = socket.inet_ntoa(fields[9])
89
+ packet.giaddr = socket.inet_ntoa(fields[10])
90
+ packet.chaddr = fields[11]
91
+ packet.sname = fields[12]
92
+ packet.file = fields[13]
93
+
94
+ # Parse options (after magic cookie)
95
+ options_data = data[236:]
96
+ if len(options_data) >= 4:
97
+ magic = struct.unpack('!I', options_data[:4])[0]
98
+ if magic == 0x63825363: # DHCP magic cookie
99
+ packet.options = packet._parse_options(options_data[4:])
100
+
101
+ return packet
102
+
103
+ def _parse_options(self, data: bytes) -> Dict[int, bytes]:
104
+ """Parse DHCP options"""
105
+ options = {}
106
+ i = 0
107
+
108
+ while i < len(data):
109
+ if data[i] == 255: # End option
110
+ break
111
+ elif data[i] == 0: # Pad option
112
+ i += 1
113
+ continue
114
+
115
+ option_type = data[i]
116
+ if i + 1 >= len(data):
117
+ break
118
+
119
+ option_length = data[i + 1]
120
+ if i + 2 + option_length > len(data):
121
+ break
122
+
123
+ option_data = data[i + 2:i + 2 + option_length]
124
+ options[option_type] = option_data
125
+ i += 2 + option_length
126
+
127
+ return options
128
+
129
+ def build(self) -> bytes:
130
+ """Build DHCP packet as bytes"""
131
+ # Build fixed fields
132
+ packet_data = struct.pack(
133
+ '!BBBBIHH4s4s4s4s16s64s128s',
134
+ self.op, self.htype, self.hlen, self.hops,
135
+ self.xid, self.secs, self.flags,
136
+ socket.inet_aton(self.ciaddr),
137
+ socket.inet_aton(self.yiaddr),
138
+ socket.inet_aton(self.siaddr),
139
+ socket.inet_aton(self.giaddr),
140
+ self.chaddr, self.sname, self.file
141
+ )
142
+
143
+ # Add magic cookie
144
+ packet_data += struct.pack('!I', 0x63825363)
145
+
146
+ # Add options
147
+ for option_type, option_data in self.options.items():
148
+ packet_data += struct.pack('!BB', option_type, len(option_data))
149
+ packet_data += option_data
150
+
151
+ # Add end option
152
+ packet_data += b'\xff'
153
+
154
+ # Pad to minimum size
155
+ while len(packet_data) < 300:
156
+ packet_data += b'\x00'
157
+
158
+ return packet_data
159
+
160
+ def get_mac_address(self) -> str:
161
+ """Get client MAC address as string"""
162
+ return ':'.join(f'{b:02x}' for b in self.chaddr[:6])
163
+
164
+ def get_message_type(self) -> Optional[DHCPMessageType]:
165
+ """Get DHCP message type from options"""
166
+ if 53 in self.options and len(self.options[53]) == 1:
167
+ msg_type = self.options[53][0]
168
+ try:
169
+ return DHCPMessageType(msg_type)
170
+ except ValueError:
171
+ return None
172
+ return None
173
+
174
+
175
+ class DHCPServer:
176
+ """User-space DHCP server implementation"""
177
+
178
+ def __init__(self, config: Dict):
179
+ self.config = config
180
+ self.leases: Dict[str, DHCPLease] = {} # MAC -> Lease
181
+ self.ip_pool = self._build_ip_pool()
182
+ self.running = False
183
+ self.server_thread = None
184
+ self.lock = threading.Lock()
185
+
186
+ def _build_ip_pool(self) -> set:
187
+ """Build available IP address pool"""
188
+ network = self.config['network']
189
+ start_ip = self.config['range_start']
190
+ end_ip = self.config['range_end']
191
+
192
+ # Convert IP addresses to integers for range calculation
193
+ start_int = struct.unpack('!I', socket.inet_aton(start_ip))[0]
194
+ end_int = struct.unpack('!I', socket.inet_aton(end_ip))[0]
195
+
196
+ pool = set()
197
+ for ip_int in range(start_int, end_int + 1):
198
+ ip_str = socket.inet_ntoa(struct.pack('!I', ip_int))
199
+ pool.add(ip_str)
200
+
201
+ return pool
202
+
203
+ def _get_available_ip(self) -> Optional[str]:
204
+ """Get next available IP address"""
205
+ with self.lock:
206
+ # Remove expired leases
207
+ self._cleanup_expired_leases()
208
+
209
+ # Find available IP
210
+ used_ips = {lease.ip_address for lease in self.leases.values()}
211
+ available_ips = self.ip_pool - used_ips
212
+
213
+ if available_ips:
214
+ return min(available_ips) # Return lowest available IP
215
+ return None
216
+
217
+ def _cleanup_expired_leases(self):
218
+ """Remove expired leases"""
219
+ expired_macs = [
220
+ mac for mac, lease in self.leases.items()
221
+ if lease.is_expired
222
+ ]
223
+ for mac in expired_macs:
224
+ del self.leases[mac]
225
+
226
+ def _create_dhcp_offer(self, discover_packet: DHCPPacket) -> DHCPPacket:
227
+ """Create DHCP OFFER response"""
228
+ mac_address = discover_packet.get_mac_address()
229
+
230
+ # Check for existing lease
231
+ if mac_address in self.leases and not self.leases[mac_address].is_expired:
232
+ offered_ip = self.leases[mac_address].ip_address
233
+ else:
234
+ offered_ip = self._get_available_ip()
235
+ if not offered_ip:
236
+ return None # No available IPs
237
+
238
+ # Create OFFER packet
239
+ offer = DHCPPacket()
240
+ offer.op = 2 # BOOTREPLY
241
+ offer.htype = discover_packet.htype
242
+ offer.hlen = discover_packet.hlen
243
+ offer.xid = discover_packet.xid
244
+ offer.yiaddr = offered_ip
245
+ offer.siaddr = self.config['gateway']
246
+ offer.chaddr = discover_packet.chaddr
247
+
248
+ # Add DHCP options
249
+ offer.options[53] = bytes([DHCPMessageType.OFFER.value]) # Message type
250
+ offer.options[1] = socket.inet_aton('255.255.255.0') # Subnet mask
251
+ offer.options[3] = socket.inet_aton(self.config['gateway']) # Router
252
+ offer.options[6] = b''.join(socket.inet_aton(dns) for dns in self.config['dns_servers']) # DNS
253
+ offer.options[51] = struct.pack('!I', self.config['lease_time']) # Lease time
254
+ offer.options[54] = socket.inet_aton(self.config['gateway']) # DHCP server identifier
255
+
256
+ return offer
257
+
258
+ def _create_dhcp_ack(self, request_packet: DHCPPacket) -> DHCPPacket:
259
+ """Create DHCP ACK response"""
260
+ mac_address = request_packet.get_mac_address()
261
+ requested_ip = request_packet.ciaddr
262
+
263
+ # If no requested IP in ciaddr, check option 50
264
+ if requested_ip == '0.0.0.0' and 50 in request_packet.options:
265
+ requested_ip = socket.inet_ntoa(request_packet.options[50])
266
+
267
+ # Validate request
268
+ if not self._validate_request(mac_address, requested_ip):
269
+ return self._create_dhcp_nak(request_packet)
270
+
271
+ # Create or update lease
272
+ lease = DHCPLease(
273
+ mac_address=mac_address,
274
+ ip_address=requested_ip,
275
+ lease_time=self.config['lease_time'],
276
+ lease_start=time.time()
277
+ )
278
+
279
+ with self.lock:
280
+ self.leases[mac_address] = lease
281
+
282
+ # Create ACK packet
283
+ ack = DHCPPacket()
284
+ ack.op = 2 # BOOTREPLY
285
+ ack.htype = request_packet.htype
286
+ ack.hlen = request_packet.hlen
287
+ ack.xid = request_packet.xid
288
+ ack.yiaddr = requested_ip
289
+ ack.siaddr = self.config['gateway']
290
+ ack.chaddr = request_packet.chaddr
291
+
292
+ # Add DHCP options
293
+ ack.options[53] = bytes([DHCPMessageType.ACK.value]) # Message type
294
+ ack.options[1] = socket.inet_aton('255.255.255.0') # Subnet mask
295
+ ack.options[3] = socket.inet_aton(self.config['gateway']) # Router
296
+ ack.options[6] = b''.join(socket.inet_aton(dns) for dns in self.config['dns_servers']) # DNS
297
+ ack.options[51] = struct.pack('!I', self.config['lease_time']) # Lease time
298
+ ack.options[54] = socket.inet_aton(self.config['gateway']) # DHCP server identifier
299
+
300
+ return ack
301
+
302
+ def _create_dhcp_nak(self, request_packet: DHCPPacket) -> DHCPPacket:
303
+ """Create DHCP NAK response"""
304
+ nak = DHCPPacket()
305
+ nak.op = 2 # BOOTREPLY
306
+ nak.htype = request_packet.htype
307
+ nak.hlen = request_packet.hlen
308
+ nak.xid = request_packet.xid
309
+ nak.chaddr = request_packet.chaddr
310
+
311
+ # Add DHCP options
312
+ nak.options[53] = bytes([DHCPMessageType.NAK.value]) # Message type
313
+ nak.options[54] = socket.inet_aton(self.config['gateway']) # DHCP server identifier
314
+
315
+ return nak
316
+
317
+ def _validate_request(self, mac_address: str, requested_ip: str) -> bool:
318
+ """Validate DHCP request"""
319
+ # Check if IP is in our pool
320
+ if requested_ip not in self.ip_pool:
321
+ return False
322
+
323
+ # Check if IP is available or already assigned to this MAC
324
+ with self.lock:
325
+ for mac, lease in self.leases.items():
326
+ if lease.ip_address == requested_ip:
327
+ if mac != mac_address and not lease.is_expired:
328
+ return False # IP already assigned to different MAC
329
+
330
+ return True
331
+
332
+ def process_packet(self, packet_data: bytes, client_addr: Tuple[str, int]) -> Optional[bytes]:
333
+ """Process incoming DHCP packet and return response"""
334
+ try:
335
+ packet = DHCPPacket.parse(packet_data)
336
+ message_type = packet.get_message_type()
337
+
338
+ if message_type == DHCPMessageType.DISCOVER:
339
+ response = self._create_dhcp_offer(packet)
340
+ elif message_type == DHCPMessageType.REQUEST:
341
+ response = self._create_dhcp_ack(packet)
342
+ elif message_type == DHCPMessageType.RELEASE:
343
+ # Handle lease release
344
+ mac_address = packet.get_mac_address()
345
+ with self.lock:
346
+ if mac_address in self.leases:
347
+ del self.leases[mac_address]
348
+ return None
349
+ else:
350
+ return None
351
+
352
+ if response:
353
+ return response.build()
354
+
355
+ except Exception as e:
356
+ print(f"Error processing DHCP packet: {e}")
357
+ return None
358
+
359
+ def get_leases(self) -> Dict[str, Dict]:
360
+ """Get current lease table"""
361
+ with self.lock:
362
+ self._cleanup_expired_leases()
363
+ return {
364
+ mac: {
365
+ 'ip_address': lease.ip_address,
366
+ 'lease_time': lease.lease_time,
367
+ 'lease_start': lease.lease_start,
368
+ 'remaining_time': lease.remaining_time,
369
+ 'state': lease.state
370
+ }
371
+ for mac, lease in self.leases.items()
372
+ }
373
+
374
+ def release_lease(self, mac_address: str) -> bool:
375
+ """Manually release a lease"""
376
+ with self.lock:
377
+ if mac_address in self.leases:
378
+ del self.leases[mac_address]
379
+ return True
380
+ return False
381
+
382
+ def start(self):
383
+ """Start DHCP server (placeholder for integration with packet bridge)"""
384
+ self.running = True
385
+ print(f"DHCP server started - Pool: {self.config['range_start']} - {self.config['range_end']}")
386
+
387
+ def stop(self):
388
+ """Stop DHCP server"""
389
+ self.running = False
390
+ print("DHCP server stopped")
391
+
core/firewall.py ADDED
@@ -0,0 +1,523 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Firewall Module
3
+
4
+ Implements packet filtering and access control:
5
+ - Rule-based packet filtering (allow/block by IP, port, protocol)
6
+ - Ordered rule processing
7
+ - Logging and statistics
8
+ - Dynamic rule management via API
9
+ """
10
+
11
+ import time
12
+ import threading
13
+ import ipaddress
14
+ import re
15
+ from typing import Dict, List, Optional, Tuple, Any
16
+ from dataclasses import dataclass
17
+ from enum import Enum
18
+
19
+ from .ip_parser import ParsedPacket, TCPHeader, UDPHeader
20
+
21
+
22
+ class FirewallAction(Enum):
23
+ ACCEPT = "ACCEPT"
24
+ DROP = "DROP"
25
+ REJECT = "REJECT"
26
+
27
+
28
+ class FirewallDirection(Enum):
29
+ INBOUND = "INBOUND"
30
+ OUTBOUND = "OUTBOUND"
31
+ BOTH = "BOTH"
32
+
33
+
34
+ @dataclass
35
+ class FirewallRule:
36
+ """Represents a firewall rule"""
37
+ rule_id: str
38
+ priority: int # Lower number = higher priority
39
+ action: FirewallAction
40
+ direction: FirewallDirection
41
+
42
+ # Match criteria
43
+ source_ip: Optional[str] = None # IP or CIDR
44
+ dest_ip: Optional[str] = None # IP or CIDR
45
+ source_port: Optional[str] = None # Port or range (e.g., "80", "80-90", "80,443")
46
+ dest_port: Optional[str] = None # Port or range
47
+ protocol: Optional[str] = None # TCP, UDP, ICMP, or None for any
48
+
49
+ # Metadata
50
+ description: str = ""
51
+ enabled: bool = True
52
+ created_time: float = 0
53
+ hit_count: int = 0
54
+ last_hit: Optional[float] = None
55
+
56
+ def __post_init__(self):
57
+ if self.created_time == 0:
58
+ self.created_time = time.time()
59
+
60
+ def record_hit(self):
61
+ """Record a rule hit"""
62
+ self.hit_count += 1
63
+ self.last_hit = time.time()
64
+
65
+ def to_dict(self) -> Dict:
66
+ """Convert rule to dictionary"""
67
+ return {
68
+ 'rule_id': self.rule_id,
69
+ 'priority': self.priority,
70
+ 'action': self.action.value,
71
+ 'direction': self.direction.value,
72
+ 'source_ip': self.source_ip,
73
+ 'dest_ip': self.dest_ip,
74
+ 'source_port': self.source_port,
75
+ 'dest_port': self.dest_port,
76
+ 'protocol': self.protocol,
77
+ 'description': self.description,
78
+ 'enabled': self.enabled,
79
+ 'created_time': self.created_time,
80
+ 'hit_count': self.hit_count,
81
+ 'last_hit': self.last_hit
82
+ }
83
+
84
+
85
+ @dataclass
86
+ class FirewallLogEntry:
87
+ """Represents a firewall log entry"""
88
+ timestamp: float
89
+ action: str
90
+ rule_id: Optional[str]
91
+ source_ip: str
92
+ dest_ip: str
93
+ source_port: int
94
+ dest_port: int
95
+ protocol: str
96
+ packet_size: int
97
+ reason: str = ""
98
+
99
+ def to_dict(self) -> Dict:
100
+ """Convert log entry to dictionary"""
101
+ return {
102
+ 'timestamp': self.timestamp,
103
+ 'action': self.action,
104
+ 'rule_id': self.rule_id,
105
+ 'source_ip': self.source_ip,
106
+ 'dest_ip': self.dest_ip,
107
+ 'source_port': self.source_port,
108
+ 'dest_port': self.dest_port,
109
+ 'protocol': self.protocol,
110
+ 'packet_size': self.packet_size,
111
+ 'reason': self.reason
112
+ }
113
+
114
+
115
+ class FirewallEngine:
116
+ """Firewall engine implementation"""
117
+
118
+ def __init__(self, config: Dict):
119
+ self.config = config
120
+ self.rules: Dict[str, FirewallRule] = {}
121
+ self.logs: List[FirewallLogEntry] = []
122
+ self.lock = threading.Lock()
123
+
124
+ # Configuration
125
+ self.default_policy = FirewallAction(config.get('default_policy', 'ACCEPT'))
126
+ self.log_blocked = config.get('log_blocked', True)
127
+ self.log_accepted = config.get('log_accepted', False)
128
+ self.max_log_entries = config.get('max_log_entries', 10000)
129
+
130
+ # Statistics
131
+ self.stats = {
132
+ 'packets_processed': 0,
133
+ 'packets_accepted': 0,
134
+ 'packets_dropped': 0,
135
+ 'packets_rejected': 0,
136
+ 'rules_hit': 0,
137
+ 'default_policy_hits': 0
138
+ }
139
+
140
+ # Load initial rules
141
+ initial_rules = config.get('rules', [])
142
+ for rule_config in initial_rules:
143
+ self._add_rule_from_config(rule_config)
144
+
145
+ def _add_rule_from_config(self, rule_config: Dict):
146
+ """Add rule from configuration"""
147
+ rule = FirewallRule(
148
+ rule_id=rule_config['rule_id'],
149
+ priority=rule_config.get('priority', 100),
150
+ action=FirewallAction(rule_config['action']),
151
+ direction=FirewallDirection(rule_config.get('direction', 'BOTH')),
152
+ source_ip=rule_config.get('source_ip'),
153
+ dest_ip=rule_config.get('dest_ip'),
154
+ source_port=rule_config.get('source_port'),
155
+ dest_port=rule_config.get('dest_port'),
156
+ protocol=rule_config.get('protocol'),
157
+ description=rule_config.get('description', ''),
158
+ enabled=rule_config.get('enabled', True)
159
+ )
160
+
161
+ with self.lock:
162
+ self.rules[rule.rule_id] = rule
163
+
164
+ def _match_ip(self, ip: str, pattern: str) -> bool:
165
+ """Match IP address against pattern (IP or CIDR)"""
166
+ try:
167
+ if '/' in pattern:
168
+ # CIDR notation
169
+ network = ipaddress.ip_network(pattern, strict=False)
170
+ return ipaddress.ip_address(ip) in network
171
+ else:
172
+ # Exact IP match
173
+ return ip == pattern
174
+ except (ipaddress.AddressValueError, ValueError):
175
+ return False
176
+
177
+ def _match_port(self, port: int, pattern: str) -> bool:
178
+ """Match port against pattern (port, range, or list)"""
179
+ try:
180
+ if ',' in pattern:
181
+ # List of ports: "80,443,8080"
182
+ ports = [int(p.strip()) for p in pattern.split(',')]
183
+ return port in ports
184
+ elif '-' in pattern:
185
+ # Port range: "80-90"
186
+ start, end = map(int, pattern.split('-', 1))
187
+ return start <= port <= end
188
+ else:
189
+ # Single port: "80"
190
+ return port == int(pattern)
191
+ except (ValueError, TypeError):
192
+ return False
193
+
194
+ def _match_protocol(self, protocol: str, pattern: str) -> bool:
195
+ """Match protocol against pattern"""
196
+ if pattern is None:
197
+ return True # Match any protocol
198
+ return protocol.upper() == pattern.upper()
199
+
200
+ def _evaluate_rule(self, rule: FirewallRule, packet: ParsedPacket, direction: FirewallDirection) -> bool:
201
+ """Evaluate if a rule matches a packet"""
202
+ if not rule.enabled:
203
+ return False
204
+
205
+ # Check direction
206
+ if rule.direction != FirewallDirection.BOTH and rule.direction != direction:
207
+ return False
208
+
209
+ # Check source IP
210
+ if rule.source_ip and not self._match_ip(packet.ip_header.source_ip, rule.source_ip):
211
+ return False
212
+
213
+ # Check destination IP
214
+ if rule.dest_ip and not self._match_ip(packet.ip_header.dest_ip, rule.dest_ip):
215
+ return False
216
+
217
+ # Check protocol
218
+ if packet.transport_header:
219
+ if isinstance(packet.transport_header, TCPHeader):
220
+ protocol = 'TCP'
221
+ source_port = packet.transport_header.source_port
222
+ dest_port = packet.transport_header.dest_port
223
+ elif isinstance(packet.transport_header, UDPHeader):
224
+ protocol = 'UDP'
225
+ source_port = packet.transport_header.source_port
226
+ dest_port = packet.transport_header.dest_port
227
+ else:
228
+ protocol = 'OTHER'
229
+ source_port = 0
230
+ dest_port = 0
231
+ else:
232
+ protocol = 'OTHER'
233
+ source_port = 0
234
+ dest_port = 0
235
+
236
+ if not self._match_protocol(protocol, rule.protocol):
237
+ return False
238
+
239
+ # Check source port
240
+ if rule.source_port and not self._match_port(source_port, rule.source_port):
241
+ return False
242
+
243
+ # Check destination port
244
+ if rule.dest_port and not self._match_port(dest_port, rule.dest_port):
245
+ return False
246
+
247
+ return True
248
+
249
+ def _log_packet(self, action: str, packet: ParsedPacket, rule_id: Optional[str] = None, reason: str = ""):
250
+ """Log packet processing"""
251
+ if not (self.log_blocked or self.log_accepted):
252
+ return
253
+
254
+ # Only log if configured
255
+ if action == 'ACCEPT' and not self.log_accepted:
256
+ return
257
+ if action in ['DROP', 'REJECT'] and not self.log_blocked:
258
+ return
259
+
260
+ # Extract packet information
261
+ if packet.transport_header:
262
+ if isinstance(packet.transport_header, (TCPHeader, UDPHeader)):
263
+ source_port = packet.transport_header.source_port
264
+ dest_port = packet.transport_header.dest_port
265
+ protocol = 'TCP' if isinstance(packet.transport_header, TCPHeader) else 'UDP'
266
+ else:
267
+ source_port = 0
268
+ dest_port = 0
269
+ protocol = 'OTHER'
270
+ else:
271
+ source_port = 0
272
+ dest_port = 0
273
+ protocol = 'OTHER'
274
+
275
+ log_entry = FirewallLogEntry(
276
+ timestamp=time.time(),
277
+ action=action,
278
+ rule_id=rule_id,
279
+ source_ip=packet.ip_header.source_ip,
280
+ dest_ip=packet.ip_header.dest_ip,
281
+ source_port=source_port,
282
+ dest_port=dest_port,
283
+ protocol=protocol,
284
+ packet_size=len(packet.raw_packet),
285
+ reason=reason
286
+ )
287
+
288
+ with self.lock:
289
+ self.logs.append(log_entry)
290
+
291
+ # Trim logs if too many
292
+ if len(self.logs) > self.max_log_entries:
293
+ self.logs = self.logs[-self.max_log_entries:]
294
+
295
+ def process_packet(self, packet: ParsedPacket, direction: FirewallDirection) -> FirewallAction:
296
+ """Process packet through firewall rules"""
297
+ self.stats['packets_processed'] += 1
298
+
299
+ # Get sorted rules by priority
300
+ with self.lock:
301
+ sorted_rules = sorted(self.rules.values(), key=lambda r: r.priority)
302
+
303
+ # Evaluate rules in order
304
+ for rule in sorted_rules:
305
+ if self._evaluate_rule(rule, packet, direction):
306
+ rule.record_hit()
307
+ self.stats['rules_hit'] += 1
308
+
309
+ # Log the action
310
+ self._log_packet(rule.action.value, packet, rule.rule_id, f"Matched rule: {rule.description}")
311
+
312
+ # Update statistics
313
+ if rule.action == FirewallAction.ACCEPT:
314
+ self.stats['packets_accepted'] += 1
315
+ elif rule.action == FirewallAction.DROP:
316
+ self.stats['packets_dropped'] += 1
317
+ elif rule.action == FirewallAction.REJECT:
318
+ self.stats['packets_rejected'] += 1
319
+
320
+ return rule.action
321
+
322
+ # No rule matched, apply default policy
323
+ self.stats['default_policy_hits'] += 1
324
+ self._log_packet(self.default_policy.value, packet, None, "Default policy")
325
+
326
+ if self.default_policy == FirewallAction.ACCEPT:
327
+ self.stats['packets_accepted'] += 1
328
+ elif self.default_policy == FirewallAction.DROP:
329
+ self.stats['packets_dropped'] += 1
330
+ elif self.default_policy == FirewallAction.REJECT:
331
+ self.stats['packets_rejected'] += 1
332
+
333
+ return self.default_policy
334
+
335
+ def add_rule(self, rule: FirewallRule) -> bool:
336
+ """Add firewall rule"""
337
+ with self.lock:
338
+ if rule.rule_id in self.rules:
339
+ return False
340
+ self.rules[rule.rule_id] = rule
341
+ return True
342
+
343
+ def remove_rule(self, rule_id: str) -> bool:
344
+ """Remove firewall rule"""
345
+ with self.lock:
346
+ if rule_id in self.rules:
347
+ del self.rules[rule_id]
348
+ return True
349
+ return False
350
+
351
+ def update_rule(self, rule_id: str, **kwargs) -> bool:
352
+ """Update firewall rule"""
353
+ with self.lock:
354
+ if rule_id not in self.rules:
355
+ return False
356
+
357
+ rule = self.rules[rule_id]
358
+ for key, value in kwargs.items():
359
+ if hasattr(rule, key):
360
+ if key in ['action', 'direction']:
361
+ # Handle enum values
362
+ if key == 'action':
363
+ value = FirewallAction(value)
364
+ elif key == 'direction':
365
+ value = FirewallDirection(value)
366
+ setattr(rule, key, value)
367
+
368
+ return True
369
+
370
+ def enable_rule(self, rule_id: str) -> bool:
371
+ """Enable firewall rule"""
372
+ return self.update_rule(rule_id, enabled=True)
373
+
374
+ def disable_rule(self, rule_id: str) -> bool:
375
+ """Disable firewall rule"""
376
+ return self.update_rule(rule_id, enabled=False)
377
+
378
+ def get_rules(self) -> List[Dict]:
379
+ """Get all firewall rules"""
380
+ with self.lock:
381
+ return [rule.to_dict() for rule in sorted(self.rules.values(), key=lambda r: r.priority)]
382
+
383
+ def get_rule(self, rule_id: str) -> Optional[Dict]:
384
+ """Get specific firewall rule"""
385
+ with self.lock:
386
+ rule = self.rules.get(rule_id)
387
+ return rule.to_dict() if rule else None
388
+
389
+ def get_logs(self, limit: int = 100, filter_action: Optional[str] = None) -> List[Dict]:
390
+ """Get firewall logs"""
391
+ with self.lock:
392
+ logs = self.logs.copy()
393
+
394
+ # Filter by action if specified
395
+ if filter_action:
396
+ logs = [log for log in logs if log.action == filter_action.upper()]
397
+
398
+ # Return most recent logs
399
+ return [log.to_dict() for log in logs[-limit:]]
400
+
401
+ def clear_logs(self):
402
+ """Clear firewall logs"""
403
+ with self.lock:
404
+ self.logs.clear()
405
+
406
+ def get_stats(self) -> Dict:
407
+ """Get firewall statistics"""
408
+ with self.lock:
409
+ stats = self.stats.copy()
410
+ stats['total_rules'] = len(self.rules)
411
+ stats['enabled_rules'] = sum(1 for rule in self.rules.values() if rule.enabled)
412
+ stats['log_entries'] = len(self.logs)
413
+ stats['default_policy'] = self.default_policy.value
414
+
415
+ return stats
416
+
417
+ def reset_stats(self):
418
+ """Reset firewall statistics"""
419
+ self.stats = {
420
+ 'packets_processed': 0,
421
+ 'packets_accepted': 0,
422
+ 'packets_dropped': 0,
423
+ 'packets_rejected': 0,
424
+ 'rules_hit': 0,
425
+ 'default_policy_hits': 0
426
+ }
427
+
428
+ # Reset rule hit counts
429
+ with self.lock:
430
+ for rule in self.rules.values():
431
+ rule.hit_count = 0
432
+ rule.last_hit = None
433
+
434
+ def set_default_policy(self, policy: str):
435
+ """Set default firewall policy"""
436
+ self.default_policy = FirewallAction(policy.upper())
437
+
438
+ def export_rules(self) -> List[Dict]:
439
+ """Export rules for backup/configuration"""
440
+ return self.get_rules()
441
+
442
+ def import_rules(self, rules_config: List[Dict], replace: bool = False):
443
+ """Import rules from configuration"""
444
+ if replace:
445
+ with self.lock:
446
+ self.rules.clear()
447
+
448
+ for rule_config in rules_config:
449
+ self._add_rule_from_config(rule_config)
450
+
451
+
452
+ class FirewallRuleBuilder:
453
+ """Helper class to build firewall rules"""
454
+
455
+ def __init__(self, rule_id: str):
456
+ self.rule_id = rule_id
457
+ self.priority = 100
458
+ self.action = FirewallAction.ACCEPT
459
+ self.direction = FirewallDirection.BOTH
460
+ self.source_ip = None
461
+ self.dest_ip = None
462
+ self.source_port = None
463
+ self.dest_port = None
464
+ self.protocol = None
465
+ self.description = ""
466
+ self.enabled = True
467
+
468
+ def set_priority(self, priority: int) -> 'FirewallRuleBuilder':
469
+ self.priority = priority
470
+ return self
471
+
472
+ def set_action(self, action: str) -> 'FirewallRuleBuilder':
473
+ self.action = FirewallAction(action.upper())
474
+ return self
475
+
476
+ def set_direction(self, direction: str) -> 'FirewallRuleBuilder':
477
+ self.direction = FirewallDirection(direction.upper())
478
+ return self
479
+
480
+ def set_source_ip(self, ip: str) -> 'FirewallRuleBuilder':
481
+ self.source_ip = ip
482
+ return self
483
+
484
+ def set_dest_ip(self, ip: str) -> 'FirewallRuleBuilder':
485
+ self.dest_ip = ip
486
+ return self
487
+
488
+ def set_source_port(self, port: str) -> 'FirewallRuleBuilder':
489
+ self.source_port = port
490
+ return self
491
+
492
+ def set_dest_port(self, port: str) -> 'FirewallRuleBuilder':
493
+ self.dest_port = port
494
+ return self
495
+
496
+ def set_protocol(self, protocol: str) -> 'FirewallRuleBuilder':
497
+ self.protocol = protocol.upper()
498
+ return self
499
+
500
+ def set_description(self, description: str) -> 'FirewallRuleBuilder':
501
+ self.description = description
502
+ return self
503
+
504
+ def set_enabled(self, enabled: bool) -> 'FirewallRuleBuilder':
505
+ self.enabled = enabled
506
+ return self
507
+
508
+ def build(self) -> FirewallRule:
509
+ """Build the firewall rule"""
510
+ return FirewallRule(
511
+ rule_id=self.rule_id,
512
+ priority=self.priority,
513
+ action=self.action,
514
+ direction=self.direction,
515
+ source_ip=self.source_ip,
516
+ dest_ip=self.dest_ip,
517
+ source_port=self.source_port,
518
+ dest_port=self.dest_port,
519
+ protocol=self.protocol,
520
+ description=self.description,
521
+ enabled=self.enabled
522
+ )
523
+
core/ip_parser.py ADDED
@@ -0,0 +1,546 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ IP Parser/Assembler Module
3
+
4
+ Handles IPv4 packet parsing and construction:
5
+ - Parse IPv4, UDP, and TCP headers
6
+ - Calculate and verify checksums
7
+ - Handle packet fragmentation and reassembly
8
+ - Support various IP options
9
+ """
10
+
11
+ import struct
12
+ import socket
13
+ from typing import Dict, List, Optional, Tuple
14
+ from dataclasses import dataclass
15
+ from enum import Enum
16
+
17
+
18
+ class IPProtocol(Enum):
19
+ ICMP = 1
20
+ TCP = 6
21
+ UDP = 17
22
+
23
+
24
+ @dataclass
25
+ class IPv4Header:
26
+ """IPv4 header structure"""
27
+ version: int = 4
28
+ ihl: int = 5 # Internet Header Length (in 32-bit words)
29
+ tos: int = 0 # Type of Service
30
+ total_length: int = 0
31
+ identification: int = 0
32
+ flags: int = 0 # 3 bits: Reserved, Don't Fragment, More Fragments
33
+ fragment_offset: int = 0 # 13 bits
34
+ ttl: int = 64 # Time to Live
35
+ protocol: int = 0
36
+ header_checksum: int = 0
37
+ source_ip: str = '0.0.0.0'
38
+ dest_ip: str = '0.0.0.0'
39
+ options: bytes = b''
40
+
41
+ @property
42
+ def header_length(self) -> int:
43
+ """Get header length in bytes"""
44
+ return self.ihl * 4
45
+
46
+ @property
47
+ def dont_fragment(self) -> bool:
48
+ """Check if Don't Fragment flag is set"""
49
+ return bool(self.flags & 0x2)
50
+
51
+ @property
52
+ def more_fragments(self) -> bool:
53
+ """Check if More Fragments flag is set"""
54
+ return bool(self.flags & 0x1)
55
+
56
+ @property
57
+ def is_fragment(self) -> bool:
58
+ """Check if this is a fragment"""
59
+ return self.more_fragments or self.fragment_offset > 0
60
+
61
+
62
+ @dataclass
63
+ class TCPHeader:
64
+ """TCP header structure"""
65
+ source_port: int = 0
66
+ dest_port: int = 0
67
+ seq_num: int = 0
68
+ ack_num: int = 0
69
+ data_offset: int = 5 # Header length in 32-bit words
70
+ reserved: int = 0
71
+ flags: int = 0 # 9 bits: NS, CWR, ECE, URG, ACK, PSH, RST, SYN, FIN
72
+ window_size: int = 65535
73
+ checksum: int = 0
74
+ urgent_pointer: int = 0
75
+ options: bytes = b''
76
+
77
+ @property
78
+ def header_length(self) -> int:
79
+ """Get header length in bytes"""
80
+ return self.data_offset * 4
81
+
82
+ # TCP Flag properties
83
+ @property
84
+ def fin(self) -> bool:
85
+ return bool(self.flags & 0x01)
86
+
87
+ @property
88
+ def syn(self) -> bool:
89
+ return bool(self.flags & 0x02)
90
+
91
+ @property
92
+ def rst(self) -> bool:
93
+ return bool(self.flags & 0x04)
94
+
95
+ @property
96
+ def psh(self) -> bool:
97
+ return bool(self.flags & 0x08)
98
+
99
+ @property
100
+ def ack(self) -> bool:
101
+ return bool(self.flags & 0x10)
102
+
103
+ @property
104
+ def urg(self) -> bool:
105
+ return bool(self.flags & 0x20)
106
+
107
+ def set_flag(self, flag_name: str, value: bool = True):
108
+ """Set TCP flag"""
109
+ flag_bits = {
110
+ 'fin': 0x01, 'syn': 0x02, 'rst': 0x04, 'psh': 0x08,
111
+ 'ack': 0x10, 'urg': 0x20, 'ece': 0x40, 'cwr': 0x80, 'ns': 0x100
112
+ }
113
+
114
+ if flag_name.lower() in flag_bits:
115
+ bit = flag_bits[flag_name.lower()]
116
+ if value:
117
+ self.flags |= bit
118
+ else:
119
+ self.flags &= ~bit
120
+
121
+
122
+ @dataclass
123
+ class UDPHeader:
124
+ """UDP header structure"""
125
+ source_port: int = 0
126
+ dest_port: int = 0
127
+ length: int = 8 # Header + data length
128
+ checksum: int = 0
129
+
130
+ @property
131
+ def header_length(self) -> int:
132
+ """Get header length in bytes (always 8 for UDP)"""
133
+ return 8
134
+
135
+
136
+ @dataclass
137
+ class ParsedPacket:
138
+ """Parsed packet structure"""
139
+ ip_header: IPv4Header
140
+ transport_header: Optional[object] = None # TCPHeader or UDPHeader
141
+ payload: bytes = b''
142
+ raw_packet: bytes = b''
143
+
144
+
145
+ class IPParser:
146
+ """IPv4 packet parser and assembler"""
147
+
148
+ @staticmethod
149
+ def calculate_checksum(data: bytes) -> int:
150
+ """Calculate Internet checksum"""
151
+ # Pad data to even length
152
+ if len(data) % 2:
153
+ data += b'\x00'
154
+
155
+ checksum = 0
156
+ for i in range(0, len(data), 2):
157
+ word = (data[i] << 8) + data[i + 1]
158
+ checksum += word
159
+
160
+ # Add carry bits
161
+ while checksum >> 16:
162
+ checksum = (checksum & 0xFFFF) + (checksum >> 16)
163
+
164
+ # One's complement
165
+ return (~checksum) & 0xFFFF
166
+
167
+ @staticmethod
168
+ def verify_checksum(data: bytes, checksum: int) -> bool:
169
+ """Verify Internet checksum"""
170
+ calculated = IPParser.calculate_checksum(data)
171
+ return calculated == checksum or (calculated + checksum) == 0xFFFF
172
+
173
+ @classmethod
174
+ def parse_ipv4_header(cls, data: bytes) -> Tuple[IPv4Header, int]:
175
+ """Parse IPv4 header from raw bytes"""
176
+ if len(data) < 20:
177
+ raise ValueError("IPv4 header too short")
178
+
179
+ # Parse fixed part of header
180
+ header_data = struct.unpack('!BBHHHBBH4s4s', data[:20])
181
+
182
+ header = IPv4Header()
183
+ version_ihl = header_data[0]
184
+ header.version = (version_ihl >> 4) & 0xF
185
+ header.ihl = version_ihl & 0xF
186
+ header.tos = header_data[1]
187
+ header.total_length = header_data[2]
188
+ header.identification = header_data[3]
189
+ flags_fragment = header_data[4]
190
+ header.flags = (flags_fragment >> 13) & 0x7
191
+ header.fragment_offset = flags_fragment & 0x1FFF
192
+ header.ttl = header_data[5]
193
+ header.protocol = header_data[6]
194
+ header.header_checksum = header_data[7]
195
+ header.source_ip = socket.inet_ntoa(header_data[8])
196
+ header.dest_ip = socket.inet_ntoa(header_data[9])
197
+
198
+ # Validate version
199
+ if header.version != 4:
200
+ raise ValueError(f"Unsupported IP version: {header.version}")
201
+
202
+ # Parse options if present
203
+ options_length = header.header_length - 20
204
+ if options_length > 0:
205
+ if len(data) < 20 + options_length:
206
+ raise ValueError("IPv4 options truncated")
207
+ header.options = data[20:20 + options_length]
208
+
209
+ return header, header.header_length
210
+
211
+ @classmethod
212
+ def parse_tcp_header(cls, data: bytes) -> Tuple[TCPHeader, int]:
213
+ """Parse TCP header from raw bytes"""
214
+ if len(data) < 20:
215
+ raise ValueError("TCP header too short")
216
+
217
+ # Parse fixed part of header
218
+ header_data = struct.unpack('!HHIIBBHHH', data[:20])
219
+
220
+ header = TCPHeader()
221
+ header.source_port = header_data[0]
222
+ header.dest_port = header_data[1]
223
+ header.seq_num = header_data[2]
224
+ header.ack_num = header_data[3]
225
+ offset_reserved = header_data[4]
226
+ header.data_offset = (offset_reserved >> 4) & 0xF
227
+ header.reserved = (offset_reserved >> 1) & 0x7
228
+ header.flags = ((offset_reserved & 0x1) << 8) | header_data[5]
229
+ header.window_size = header_data[6]
230
+ header.checksum = header_data[7]
231
+ header.urgent_pointer = header_data[8]
232
+
233
+ # Parse options if present
234
+ options_length = header.header_length - 20
235
+ if options_length > 0:
236
+ if len(data) < 20 + options_length:
237
+ raise ValueError("TCP options truncated")
238
+ header.options = data[20:20 + options_length]
239
+
240
+ return header, header.header_length
241
+
242
+ @classmethod
243
+ def parse_udp_header(cls, data: bytes) -> Tuple[UDPHeader, int]:
244
+ """Parse UDP header from raw bytes"""
245
+ if len(data) < 8:
246
+ raise ValueError("UDP header too short")
247
+
248
+ header_data = struct.unpack('!HHHH', data[:8])
249
+
250
+ header = UDPHeader()
251
+ header.source_port = header_data[0]
252
+ header.dest_port = header_data[1]
253
+ header.length = header_data[2]
254
+ header.checksum = header_data[3]
255
+
256
+ return header, 8
257
+
258
+ @classmethod
259
+ def parse_packet(cls, data: bytes) -> ParsedPacket:
260
+ """Parse complete packet"""
261
+ packet = ParsedPacket(raw_packet=data)
262
+
263
+ # Parse IP header
264
+ packet.ip_header, ip_header_len = cls.parse_ipv4_header(data)
265
+
266
+ # Extract payload after IP header
267
+ ip_payload = data[ip_header_len:packet.ip_header.total_length]
268
+
269
+ # Parse transport layer header
270
+ if packet.ip_header.protocol == IPProtocol.TCP.value:
271
+ packet.transport_header, transport_header_len = cls.parse_tcp_header(ip_payload)
272
+ packet.payload = ip_payload[transport_header_len:]
273
+ elif packet.ip_header.protocol == IPProtocol.UDP.value:
274
+ packet.transport_header, transport_header_len = cls.parse_udp_header(ip_payload)
275
+ packet.payload = ip_payload[transport_header_len:]
276
+ else:
277
+ # Unsupported protocol, treat as raw payload
278
+ packet.payload = ip_payload
279
+
280
+ return packet
281
+
282
+ @classmethod
283
+ def build_ipv4_header(cls, header: IPv4Header) -> bytes:
284
+ """Build IPv4 header as bytes"""
285
+ # Calculate header length including options
286
+ header.ihl = (20 + len(header.options) + 3) // 4 # Round up to 32-bit boundary
287
+
288
+ # Build header without checksum
289
+ version_ihl = (header.version << 4) | header.ihl
290
+ flags_fragment = (header.flags << 13) | header.fragment_offset
291
+
292
+ header_data = struct.pack(
293
+ '!BBHHHBBH4s4s',
294
+ version_ihl, header.tos, header.total_length,
295
+ header.identification, flags_fragment,
296
+ header.ttl, header.protocol, 0, # Checksum = 0 for calculation
297
+ socket.inet_aton(header.source_ip),
298
+ socket.inet_aton(header.dest_ip)
299
+ )
300
+
301
+ # Add options and padding
302
+ if header.options:
303
+ header_data += header.options
304
+ # Pad to 32-bit boundary
305
+ padding_needed = (header.ihl * 4) - len(header_data)
306
+ if padding_needed > 0:
307
+ header_data += b'\x00' * padding_needed
308
+
309
+ # Calculate and insert checksum
310
+ checksum = cls.calculate_checksum(header_data)
311
+ header_data = header_data[:10] + struct.pack('!H', checksum) + header_data[12:]
312
+
313
+ return header_data
314
+
315
+ @classmethod
316
+ def build_tcp_header(cls, header: TCPHeader, source_ip: str, dest_ip: str, payload: bytes) -> bytes:
317
+ """Build TCP header as bytes with checksum"""
318
+ # Calculate header length including options
319
+ header.data_offset = (20 + len(header.options) + 3) // 4 # Round up to 32-bit boundary
320
+
321
+ # Build header without checksum
322
+ offset_reserved_flags = (header.data_offset << 12) | (header.reserved << 9) | header.flags
323
+
324
+ header_data = struct.pack(
325
+ '!HHIIHHH',
326
+ header.source_port, header.dest_port,
327
+ header.seq_num, header.ack_num,
328
+ offset_reserved_flags, header.window_size,
329
+ 0, header.urgent_pointer # Checksum = 0 for calculation
330
+ )
331
+
332
+ # Add options and padding
333
+ if header.options:
334
+ header_data += header.options
335
+ # Pad to 32-bit boundary
336
+ padding_needed = (header.data_offset * 4) - len(header_data)
337
+ if padding_needed > 0:
338
+ header_data += b'\x00' * padding_needed
339
+
340
+ # Calculate TCP checksum with pseudo-header
341
+ pseudo_header = struct.pack(
342
+ '!4s4sBBH',
343
+ socket.inet_aton(source_ip),
344
+ socket.inet_aton(dest_ip),
345
+ 0, IPProtocol.TCP.value,
346
+ len(header_data) + len(payload)
347
+ )
348
+
349
+ checksum_data = pseudo_header + header_data + payload
350
+ checksum = cls.calculate_checksum(checksum_data)
351
+
352
+ # Insert checksum
353
+ header_data = header_data[:16] + struct.pack('!H', checksum) + header_data[18:]
354
+
355
+ return header_data
356
+
357
+ @classmethod
358
+ def build_udp_header(cls, header: UDPHeader, source_ip: str, dest_ip: str, payload: bytes) -> bytes:
359
+ """Build UDP header as bytes with checksum"""
360
+ header.length = 8 + len(payload)
361
+
362
+ # Build header without checksum
363
+ header_data = struct.pack(
364
+ '!HHHH',
365
+ header.source_port, header.dest_port,
366
+ header.length, 0 # Checksum = 0 for calculation
367
+ )
368
+
369
+ # Calculate UDP checksum with pseudo-header (optional for IPv4)
370
+ if header.checksum != 0: # If checksum is required
371
+ pseudo_header = struct.pack(
372
+ '!4s4sBBH',
373
+ socket.inet_aton(source_ip),
374
+ socket.inet_aton(dest_ip),
375
+ 0, IPProtocol.UDP.value,
376
+ header.length
377
+ )
378
+
379
+ checksum_data = pseudo_header + header_data + payload
380
+ checksum = cls.calculate_checksum(checksum_data)
381
+
382
+ # Insert checksum
383
+ header_data = header_data[:6] + struct.pack('!H', checksum) + header_data[8:]
384
+
385
+ return header_data
386
+
387
+ @classmethod
388
+ def build_packet(cls, ip_header: IPv4Header, transport_header: Optional[object] = None, payload: bytes = b'') -> bytes:
389
+ """Build complete packet"""
390
+ transport_data = b''
391
+
392
+ # Build transport header
393
+ if transport_header:
394
+ if isinstance(transport_header, TCPHeader):
395
+ transport_data = cls.build_tcp_header(
396
+ transport_header, ip_header.source_ip, ip_header.dest_ip, payload
397
+ )
398
+ elif isinstance(transport_header, UDPHeader):
399
+ transport_data = cls.build_udp_header(
400
+ transport_header, ip_header.source_ip, ip_header.dest_ip, payload
401
+ )
402
+
403
+ # Update IP header total length
404
+ ip_header.total_length = ip_header.header_length + len(transport_data) + len(payload)
405
+
406
+ # Build IP header
407
+ ip_data = cls.build_ipv4_header(ip_header)
408
+
409
+ # Combine all parts
410
+ return ip_data + transport_data + payload
411
+
412
+
413
+ class PacketFragmenter:
414
+ """Handle packet fragmentation and reassembly"""
415
+
416
+ def __init__(self, mtu: int = 1500):
417
+ self.mtu = mtu
418
+ self.fragments: Dict[Tuple[str, str, int], List[Tuple[int, bytes]]] = {} # (src, dst, id) -> [(offset, data)]
419
+
420
+ def fragment_packet(self, packet: bytes, mtu: int = None) -> List[bytes]:
421
+ """Fragment a packet if it exceeds MTU"""
422
+ if mtu is None:
423
+ mtu = self.mtu
424
+
425
+ if len(packet) <= mtu:
426
+ return [packet]
427
+
428
+ # Parse original packet
429
+ parsed = IPParser.parse_packet(packet)
430
+ ip_header = parsed.ip_header
431
+
432
+ # Don't fragment if DF flag is set
433
+ if ip_header.dont_fragment:
434
+ raise ValueError("Packet too large and Don't Fragment flag is set")
435
+
436
+ fragments = []
437
+ payload_mtu = mtu - ip_header.header_length
438
+ payload_mtu = (payload_mtu // 8) * 8 # Must be multiple of 8 bytes
439
+
440
+ # Get the payload to fragment (everything after IP header)
441
+ payload_start = ip_header.header_length
442
+ payload = packet[payload_start:]
443
+
444
+ offset = 0
445
+ while offset < len(payload):
446
+ # Create fragment
447
+ fragment_payload = payload[offset:offset + payload_mtu]
448
+
449
+ # Create new IP header for fragment
450
+ frag_header = IPv4Header(
451
+ version=ip_header.version,
452
+ ihl=ip_header.ihl,
453
+ tos=ip_header.tos,
454
+ identification=ip_header.identification,
455
+ ttl=ip_header.ttl,
456
+ protocol=ip_header.protocol,
457
+ source_ip=ip_header.source_ip,
458
+ dest_ip=ip_header.dest_ip,
459
+ options=ip_header.options
460
+ )
461
+
462
+ # Set fragment offset and flags
463
+ frag_header.fragment_offset = (ip_header.fragment_offset * 8 + offset) // 8
464
+ frag_header.flags = ip_header.flags
465
+
466
+ # Set More Fragments flag if not last fragment
467
+ if offset + len(fragment_payload) < len(payload):
468
+ frag_header.flags |= 0x1 # More Fragments
469
+ else:
470
+ frag_header.flags &= ~0x1 # Clear More Fragments
471
+
472
+ # Build fragment
473
+ fragment = IPParser.build_packet(frag_header, payload=fragment_payload)
474
+ fragments.append(fragment)
475
+
476
+ offset += len(fragment_payload)
477
+
478
+ return fragments
479
+
480
+ def reassemble_packet(self, packet: bytes) -> Optional[bytes]:
481
+ """Reassemble fragmented packet"""
482
+ parsed = IPParser.parse_packet(packet)
483
+ ip_header = parsed.ip_header
484
+
485
+ # If not a fragment, return as-is
486
+ if not ip_header.is_fragment:
487
+ return packet
488
+
489
+ # Create fragment key
490
+ key = (ip_header.source_ip, ip_header.dest_ip, ip_header.identification)
491
+
492
+ # Store fragment
493
+ if key not in self.fragments:
494
+ self.fragments[key] = []
495
+
496
+ payload_start = ip_header.header_length
497
+ fragment_data = packet[payload_start:]
498
+ self.fragments[key].append((ip_header.fragment_offset * 8, fragment_data))
499
+
500
+ # Check if we have all fragments
501
+ fragments = sorted(self.fragments[key])
502
+
503
+ # Verify we have contiguous fragments starting from 0
504
+ expected_offset = 0
505
+ complete_payload = b''
506
+
507
+ for offset, data in fragments:
508
+ if offset != expected_offset:
509
+ return None # Missing fragment
510
+
511
+ complete_payload += data
512
+ expected_offset += len(data)
513
+
514
+ # Check if last fragment (no More Fragments flag)
515
+ last_fragment = None
516
+ for frag_packet in [packet]: # We only have current packet, need to track all
517
+ frag_parsed = IPParser.parse_packet(frag_packet)
518
+ if not frag_parsed.ip_header.more_fragments:
519
+ last_fragment = frag_parsed
520
+ break
521
+
522
+ if last_fragment is None:
523
+ return None # Don't have last fragment yet
524
+
525
+ # Reassemble complete packet
526
+ complete_header = IPv4Header(
527
+ version=ip_header.version,
528
+ ihl=ip_header.ihl,
529
+ tos=ip_header.tos,
530
+ identification=ip_header.identification,
531
+ flags=ip_header.flags & ~0x1, # Clear More Fragments
532
+ fragment_offset=0,
533
+ ttl=ip_header.ttl,
534
+ protocol=ip_header.protocol,
535
+ source_ip=ip_header.source_ip,
536
+ dest_ip=ip_header.dest_ip,
537
+ options=ip_header.options
538
+ )
539
+
540
+ complete_packet = IPParser.build_packet(complete_header, payload=complete_payload)
541
+
542
+ # Clean up fragments
543
+ del self.fragments[key]
544
+
545
+ return complete_packet
546
+
core/logger.py ADDED
@@ -0,0 +1,555 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Logger Module
3
+
4
+ Centralized logging system for the virtual ISP stack:
5
+ - Structured logging with multiple levels
6
+ - Log aggregation and filtering
7
+ - Real-time log streaming
8
+ - Log persistence and rotation
9
+ """
10
+
11
+ import logging
12
+ import logging.handlers
13
+ import time
14
+ import threading
15
+ import json
16
+ import os
17
+ from typing import Dict, List, Optional, Any, Callable
18
+ from dataclasses import dataclass, asdict
19
+ from enum import Enum
20
+ from collections import deque
21
+ import queue
22
+
23
+
24
+ class LogLevel(Enum):
25
+ DEBUG = "DEBUG"
26
+ INFO = "INFO"
27
+ WARNING = "WARNING"
28
+ ERROR = "ERROR"
29
+ CRITICAL = "CRITICAL"
30
+
31
+
32
+ class LogCategory(Enum):
33
+ SYSTEM = "SYSTEM"
34
+ DHCP = "DHCP"
35
+ NAT = "NAT"
36
+ FIREWALL = "FIREWALL"
37
+ TCP = "TCP"
38
+ ROUTER = "ROUTER"
39
+ BRIDGE = "BRIDGE"
40
+ SOCKET = "SOCKET"
41
+ SESSION = "SESSION"
42
+ SECURITY = "SECURITY"
43
+ PERFORMANCE = "PERFORMANCE"
44
+
45
+
46
+ @dataclass
47
+ class LogEntry:
48
+ """Structured log entry"""
49
+ timestamp: float
50
+ level: str
51
+ category: str
52
+ module: str
53
+ message: str
54
+ session_id: Optional[str] = None
55
+ client_id: Optional[str] = None
56
+ source_ip: Optional[str] = None
57
+ dest_ip: Optional[str] = None
58
+ protocol: Optional[str] = None
59
+ metadata: Dict[str, Any] = None
60
+
61
+ def __post_init__(self):
62
+ if self.timestamp == 0:
63
+ self.timestamp = time.time()
64
+ if self.metadata is None:
65
+ self.metadata = {}
66
+
67
+ def to_dict(self) -> Dict:
68
+ """Convert to dictionary"""
69
+ return asdict(self)
70
+
71
+ def to_json(self) -> str:
72
+ """Convert to JSON string"""
73
+ return json.dumps(self.to_dict(), default=str)
74
+
75
+
76
+ class LogFilter:
77
+ """Log filtering class"""
78
+
79
+ def __init__(self):
80
+ self.level_filter: Optional[LogLevel] = None
81
+ self.category_filter: Optional[LogCategory] = None
82
+ self.module_filter: Optional[str] = None
83
+ self.session_filter: Optional[str] = None
84
+ self.client_filter: Optional[str] = None
85
+ self.ip_filter: Optional[str] = None
86
+ self.text_filter: Optional[str] = None
87
+ self.time_range: Optional[tuple] = None
88
+
89
+ def matches(self, entry: LogEntry) -> bool:
90
+ """Check if log entry matches filter criteria"""
91
+ # Level filter
92
+ if self.level_filter:
93
+ entry_level_value = getattr(logging, entry.level)
94
+ filter_level_value = getattr(logging, self.level_filter.value)
95
+ if entry_level_value < filter_level_value:
96
+ return False
97
+
98
+ # Category filter
99
+ if self.category_filter and entry.category != self.category_filter.value:
100
+ return False
101
+
102
+ # Module filter
103
+ if self.module_filter and self.module_filter.lower() not in entry.module.lower():
104
+ return False
105
+
106
+ # Session filter
107
+ if self.session_filter and entry.session_id != self.session_filter:
108
+ return False
109
+
110
+ # Client filter
111
+ if self.client_filter and entry.client_id != self.client_filter:
112
+ return False
113
+
114
+ # IP filter
115
+ if self.ip_filter:
116
+ if (entry.source_ip != self.ip_filter and
117
+ entry.dest_ip != self.ip_filter):
118
+ return False
119
+
120
+ # Text filter
121
+ if self.text_filter and self.text_filter.lower() not in entry.message.lower():
122
+ return False
123
+
124
+ # Time range filter
125
+ if self.time_range:
126
+ start_time, end_time = self.time_range
127
+ if not (start_time <= entry.timestamp <= end_time):
128
+ return False
129
+
130
+ return True
131
+
132
+
133
+ class LogSubscriber:
134
+ """Log subscriber for real-time streaming"""
135
+
136
+ def __init__(self, subscriber_id: str, callback: Callable[[LogEntry], None],
137
+ log_filter: Optional[LogFilter] = None):
138
+ self.subscriber_id = subscriber_id
139
+ self.callback = callback
140
+ self.filter = log_filter or LogFilter()
141
+ self.created_time = time.time()
142
+ self.message_count = 0
143
+ self.last_message_time = None
144
+ self.is_active = True
145
+
146
+ def send_log(self, entry: LogEntry) -> bool:
147
+ """Send log entry to subscriber if it matches filter"""
148
+ if not self.is_active:
149
+ return False
150
+
151
+ if self.filter.matches(entry):
152
+ try:
153
+ self.callback(entry)
154
+ self.message_count += 1
155
+ self.last_message_time = time.time()
156
+ return True
157
+ except Exception as e:
158
+ print(f"Error sending log to subscriber {self.subscriber_id}: {e}")
159
+ self.is_active = False
160
+ return False
161
+
162
+ return False
163
+
164
+
165
+ class VirtualISPLogger:
166
+ """Centralized logger for Virtual ISP stack"""
167
+
168
+ def __init__(self, config: Dict):
169
+ self.config = config
170
+ self.log_entries: deque = deque(maxlen=config.get('max_memory_logs', 10000))
171
+ self.subscribers: Dict[str, LogSubscriber] = {}
172
+ self.lock = threading.Lock()
173
+
174
+ # Configuration
175
+ self.log_level = LogLevel(config.get('log_level', 'INFO'))
176
+ self.log_to_file = config.get('log_to_file', True)
177
+ self.log_file_path = config.get('log_file_path', '/tmp/virtual_isp.log')
178
+ self.log_file_max_size = config.get('log_file_max_size', 10 * 1024 * 1024) # 10MB
179
+ self.log_file_backup_count = config.get('log_file_backup_count', 5)
180
+ self.log_to_console = config.get('log_to_console', True)
181
+ self.structured_logging = config.get('structured_logging', True)
182
+
183
+ # Statistics
184
+ self.stats = {
185
+ 'total_logs': 0,
186
+ 'logs_by_level': {level.value: 0 for level in LogLevel},
187
+ 'logs_by_category': {cat.value: 0 for cat in LogCategory},
188
+ 'active_subscribers': 0,
189
+ 'file_logs_written': 0,
190
+ 'console_logs_written': 0,
191
+ 'dropped_logs': 0
192
+ }
193
+
194
+ # Setup logging
195
+ self._setup_logging()
196
+
197
+ # Background processing
198
+ self.running = False
199
+ self.log_queue = queue.Queue()
200
+ self.processing_thread = None
201
+
202
+ def _setup_logging(self):
203
+ """Setup Python logging infrastructure"""
204
+ # Create logger
205
+ self.logger = logging.getLogger('virtual_isp')
206
+ self.logger.setLevel(getattr(logging, self.log_level.value))
207
+
208
+ # Remove existing handlers
209
+ for handler in self.logger.handlers[:]:
210
+ self.logger.removeHandler(handler)
211
+
212
+ # Console handler
213
+ if self.log_to_console:
214
+ console_handler = logging.StreamHandler()
215
+ if self.structured_logging:
216
+ console_formatter = logging.Formatter(
217
+ '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
218
+ )
219
+ else:
220
+ console_formatter = logging.Formatter('%(message)s')
221
+ console_handler.setFormatter(console_formatter)
222
+ self.logger.addHandler(console_handler)
223
+
224
+ # File handler with rotation
225
+ if self.log_to_file:
226
+ # Ensure log directory exists
227
+ log_dir = os.path.dirname(self.log_file_path)
228
+ if log_dir and not os.path.exists(log_dir):
229
+ os.makedirs(log_dir, exist_ok=True)
230
+
231
+ file_handler = logging.handlers.RotatingFileHandler(
232
+ self.log_file_path,
233
+ maxBytes=self.log_file_max_size,
234
+ backupCount=self.log_file_backup_count
235
+ )
236
+
237
+ if self.structured_logging:
238
+ file_formatter = logging.Formatter(
239
+ '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
240
+ )
241
+ else:
242
+ file_formatter = logging.Formatter('%(message)s')
243
+
244
+ file_handler.setFormatter(file_formatter)
245
+ self.logger.addHandler(file_handler)
246
+
247
+ def _process_log_queue(self):
248
+ """Background thread to process log queue"""
249
+ while self.running:
250
+ try:
251
+ # Get log entry from queue (with timeout)
252
+ try:
253
+ entry = self.log_queue.get(timeout=1.0)
254
+ except queue.Empty:
255
+ continue
256
+
257
+ # Store in memory
258
+ with self.lock:
259
+ self.log_entries.append(entry)
260
+
261
+ # Send to subscribers
262
+ inactive_subscribers = []
263
+ with self.lock:
264
+ for subscriber_id, subscriber in self.subscribers.items():
265
+ if not subscriber.send_log(entry):
266
+ inactive_subscribers.append(subscriber_id)
267
+
268
+ # Remove inactive subscribers
269
+ for subscriber_id in inactive_subscribers:
270
+ self.remove_subscriber(subscriber_id)
271
+
272
+ # Update statistics
273
+ self.stats['total_logs'] += 1
274
+ self.stats['logs_by_level'][entry.level] += 1
275
+ self.stats['logs_by_category'][entry.category] += 1
276
+
277
+ # Mark task as done
278
+ self.log_queue.task_done()
279
+
280
+ except Exception as e:
281
+ print(f"Error processing log queue: {e}")
282
+ time.sleep(1)
283
+
284
+ def log(self, level: LogLevel, category: LogCategory, module: str, message: str,
285
+ session_id: Optional[str] = None, client_id: Optional[str] = None,
286
+ source_ip: Optional[str] = None, dest_ip: Optional[str] = None,
287
+ protocol: Optional[str] = None, **metadata):
288
+ """Log a message"""
289
+ # Check if we should log this level
290
+ level_value = getattr(logging, level.value)
291
+ min_level_value = getattr(logging, self.log_level.value)
292
+ if level_value < min_level_value:
293
+ return
294
+
295
+ # Create log entry
296
+ entry = LogEntry(
297
+ timestamp=time.time(),
298
+ level=level.value,
299
+ category=category.value,
300
+ module=module,
301
+ message=message,
302
+ session_id=session_id,
303
+ client_id=client_id,
304
+ source_ip=source_ip,
305
+ dest_ip=dest_ip,
306
+ protocol=protocol,
307
+ metadata=metadata
308
+ )
309
+
310
+ # Add to queue for background processing
311
+ try:
312
+ self.log_queue.put_nowait(entry)
313
+ except queue.Full:
314
+ self.stats['dropped_logs'] += 1
315
+
316
+ # Also log through Python logging system
317
+ if self.structured_logging:
318
+ log_data = entry.to_dict()
319
+ log_message = f"{message} | {json.dumps(log_data, default=str)}"
320
+ else:
321
+ log_message = message
322
+
323
+ # Log to Python logger
324
+ python_logger_level = getattr(logging, level.value)
325
+ self.logger.log(python_logger_level, log_message)
326
+
327
+ # Update console/file stats
328
+ if self.log_to_console:
329
+ self.stats['console_logs_written'] += 1
330
+ if self.log_to_file:
331
+ self.stats['file_logs_written'] += 1
332
+
333
+ def debug(self, category: LogCategory, module: str, message: str, **kwargs):
334
+ """Log debug message"""
335
+ self.log(LogLevel.DEBUG, category, module, message, **kwargs)
336
+
337
+ def info(self, category: LogCategory, module: str, message: str, **kwargs):
338
+ """Log info message"""
339
+ self.log(LogLevel.INFO, category, module, message, **kwargs)
340
+
341
+ def warning(self, category: LogCategory, module: str, message: str, **kwargs):
342
+ """Log warning message"""
343
+ self.log(LogLevel.WARNING, category, module, message, **kwargs)
344
+
345
+ def error(self, category: LogCategory, module: str, message: str, **kwargs):
346
+ """Log error message"""
347
+ self.log(LogLevel.ERROR, category, module, message, **kwargs)
348
+
349
+ def critical(self, category: LogCategory, module: str, message: str, **kwargs):
350
+ """Log critical message"""
351
+ self.log(LogLevel.CRITICAL, category, module, message, **kwargs)
352
+
353
+ def add_subscriber(self, subscriber_id: str, callback: Callable[[LogEntry], None],
354
+ log_filter: Optional[LogFilter] = None) -> bool:
355
+ """Add log subscriber for real-time streaming"""
356
+ with self.lock:
357
+ if subscriber_id in self.subscribers:
358
+ return False
359
+
360
+ subscriber = LogSubscriber(subscriber_id, callback, log_filter)
361
+ self.subscribers[subscriber_id] = subscriber
362
+ self.stats['active_subscribers'] = len(self.subscribers)
363
+
364
+ return True
365
+
366
+ def remove_subscriber(self, subscriber_id: str) -> bool:
367
+ """Remove log subscriber"""
368
+ with self.lock:
369
+ if subscriber_id in self.subscribers:
370
+ del self.subscribers[subscriber_id]
371
+ self.stats['active_subscribers'] = len(self.subscribers)
372
+ return True
373
+ return False
374
+
375
+ def get_logs(self, limit: int = 100, offset: int = 0,
376
+ log_filter: Optional[LogFilter] = None) -> List[Dict]:
377
+ """Get logs with filtering and pagination"""
378
+ with self.lock:
379
+ # Convert deque to list for easier manipulation
380
+ all_logs = list(self.log_entries)
381
+
382
+ # Apply filter
383
+ if log_filter:
384
+ filtered_logs = [entry for entry in all_logs if log_filter.matches(entry)]
385
+ else:
386
+ filtered_logs = all_logs
387
+
388
+ # Sort by timestamp (newest first)
389
+ filtered_logs.sort(key=lambda x: x.timestamp, reverse=True)
390
+
391
+ # Apply pagination
392
+ paginated_logs = filtered_logs[offset:offset + limit]
393
+
394
+ return [entry.to_dict() for entry in paginated_logs]
395
+
396
+ def search_logs(self, query: str, limit: int = 100) -> List[Dict]:
397
+ """Search logs by text query"""
398
+ log_filter = LogFilter()
399
+ log_filter.text_filter = query
400
+
401
+ return self.get_logs(limit=limit, log_filter=log_filter)
402
+
403
+ def get_logs_by_session(self, session_id: str, limit: int = 100) -> List[Dict]:
404
+ """Get logs for specific session"""
405
+ log_filter = LogFilter()
406
+ log_filter.session_filter = session_id
407
+
408
+ return self.get_logs(limit=limit, log_filter=log_filter)
409
+
410
+ def get_logs_by_client(self, client_id: str, limit: int = 100) -> List[Dict]:
411
+ """Get logs for specific client"""
412
+ log_filter = LogFilter()
413
+ log_filter.client_filter = client_id
414
+
415
+ return self.get_logs(limit=limit, log_filter=log_filter)
416
+
417
+ def get_logs_by_ip(self, ip_address: str, limit: int = 100) -> List[Dict]:
418
+ """Get logs for specific IP address"""
419
+ log_filter = LogFilter()
420
+ log_filter.ip_filter = ip_address
421
+
422
+ return self.get_logs(limit=limit, log_filter=log_filter)
423
+
424
+ def get_recent_errors(self, limit: int = 50) -> List[Dict]:
425
+ """Get recent error and critical logs"""
426
+ log_filter = LogFilter()
427
+ log_filter.level_filter = LogLevel.ERROR
428
+
429
+ return self.get_logs(limit=limit, log_filter=log_filter)
430
+
431
+ def clear_logs(self):
432
+ """Clear all logs from memory"""
433
+ with self.lock:
434
+ self.log_entries.clear()
435
+
436
+ def get_stats(self) -> Dict:
437
+ """Get logging statistics"""
438
+ with self.lock:
439
+ stats = self.stats.copy()
440
+ stats['memory_logs_count'] = len(self.log_entries)
441
+ stats['active_subscribers'] = len(self.subscribers)
442
+ stats['queue_size'] = self.log_queue.qsize()
443
+
444
+ return stats
445
+
446
+ def reset_stats(self):
447
+ """Reset logging statistics"""
448
+ self.stats = {
449
+ 'total_logs': 0,
450
+ 'logs_by_level': {level.value: 0 for level in LogLevel},
451
+ 'logs_by_category': {cat.value: 0 for cat in LogCategory},
452
+ 'active_subscribers': len(self.subscribers),
453
+ 'file_logs_written': 0,
454
+ 'console_logs_written': 0,
455
+ 'dropped_logs': 0
456
+ }
457
+
458
+ def export_logs(self, format: str = 'json', log_filter: Optional[LogFilter] = None) -> str:
459
+ """Export logs in specified format"""
460
+ logs = self.get_logs(limit=10000, log_filter=log_filter)
461
+
462
+ if format == 'json':
463
+ return json.dumps(logs, indent=2, default=str)
464
+ elif format == 'csv':
465
+ import csv
466
+ import io
467
+
468
+ output = io.StringIO()
469
+ if logs:
470
+ writer = csv.DictWriter(output, fieldnames=logs[0].keys())
471
+ writer.writeheader()
472
+ writer.writerows(logs)
473
+
474
+ return output.getvalue()
475
+ else:
476
+ raise ValueError(f"Unsupported export format: {format}")
477
+
478
+ def set_log_level(self, level: LogLevel):
479
+ """Set logging level"""
480
+ self.log_level = level
481
+ self.logger.setLevel(getattr(logging, level.value))
482
+
483
+ def start(self):
484
+ """Start logger"""
485
+ self.running = True
486
+ self.processing_thread = threading.Thread(target=self._process_log_queue, daemon=True)
487
+ self.processing_thread.start()
488
+
489
+ self.info(LogCategory.SYSTEM, 'logger', 'Virtual ISP Logger started')
490
+
491
+ def stop(self):
492
+ """Stop logger"""
493
+ self.info(LogCategory.SYSTEM, 'logger', 'Virtual ISP Logger stopping')
494
+
495
+ self.running = False
496
+
497
+ # Wait for queue to be processed
498
+ self.log_queue.join()
499
+
500
+ # Wait for processing thread
501
+ if self.processing_thread:
502
+ self.processing_thread.join()
503
+
504
+ # Remove all subscribers
505
+ with self.lock:
506
+ self.subscribers.clear()
507
+
508
+ print("Virtual ISP Logger stopped")
509
+
510
+
511
+ # Global logger instance
512
+ _global_logger: Optional[VirtualISPLogger] = None
513
+
514
+
515
+ def get_logger() -> Optional[VirtualISPLogger]:
516
+ """Get global logger instance"""
517
+ return _global_logger
518
+
519
+
520
+ def init_logger(config: Dict) -> VirtualISPLogger:
521
+ """Initialize global logger"""
522
+ global _global_logger
523
+ _global_logger = VirtualISPLogger(config)
524
+ return _global_logger
525
+
526
+
527
+ def log_debug(category: LogCategory, module: str, message: str, **kwargs):
528
+ """Global debug logging function"""
529
+ if _global_logger:
530
+ _global_logger.debug(category, module, message, **kwargs)
531
+
532
+
533
+ def log_info(category: LogCategory, module: str, message: str, **kwargs):
534
+ """Global info logging function"""
535
+ if _global_logger:
536
+ _global_logger.info(category, module, message, **kwargs)
537
+
538
+
539
+ def log_warning(category: LogCategory, module: str, message: str, **kwargs):
540
+ """Global warning logging function"""
541
+ if _global_logger:
542
+ _global_logger.warning(category, module, message, **kwargs)
543
+
544
+
545
+ def log_error(category: LogCategory, module: str, message: str, **kwargs):
546
+ """Global error logging function"""
547
+ if _global_logger:
548
+ _global_logger.error(category, module, message, **kwargs)
549
+
550
+
551
+ def log_critical(category: LogCategory, module: str, message: str, **kwargs):
552
+ """Global critical logging function"""
553
+ if _global_logger:
554
+ _global_logger.critical(category, module, message, **kwargs)
555
+
core/nat_engine.py ADDED
@@ -0,0 +1,638 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ NAT Engine Module
3
+
4
+ Implements Network Address Translation:
5
+ - Map (virtualIP, virtualPort) to (hostIP, hostPort)
6
+ - Maintain connection tracking table
7
+ - Handle port allocation and deallocation
8
+ - Support connection state tracking
9
+ """
10
+
11
+ import time
12
+ import threading
13
+ import socket
14
+ import random
15
+ import struct
16
+ from typing import Dict, Optional, Tuple, Set
17
+ from dataclasses import dataclass
18
+ from enum import Enum
19
+
20
+ # Assuming IPProtocol is defined elsewhere or will be defined
21
+ # from .ip_parser import IPProtocol
22
+
23
+ class NATType(Enum):
24
+ SNAT = "SNAT" # Source NAT
25
+ DNAT = "DNAT" # Destination NAT
26
+
27
+
28
+ @dataclass
29
+ class NATSession:
30
+ """Represents a NAT session"""
31
+ # Virtual (internal) endpoint
32
+ virtual_ip: str
33
+ virtual_port: int
34
+
35
+ # Real (external) endpoint
36
+ real_ip: str
37
+ real_port: int
38
+
39
+ # Host (translated) endpoint
40
+ host_ip: str
41
+ host_port: int
42
+
43
+ # Session metadata
44
+ protocol: int # IP protocol number (e.g., 6 for TCP, 17 for UDP)
45
+ nat_type: NATType
46
+ created_time: float
47
+ last_activity: float
48
+ bytes_in: int = 0
49
+ bytes_out: int = 0
50
+ packets_in: int = 0
51
+ packets_out: int = 0
52
+
53
+ @property
54
+ def session_id(self) -> str:
55
+ """Get unique session identifier"""
56
+ return f"{self.virtual_ip}:{self.virtual_port}-{self.real_ip}:{self.real_port}-{self.protocol}"
57
+
58
+ @property
59
+ def is_expired(self) -> bool:
60
+ """Check if session has expired"""
61
+ timeout = 300 if self.protocol == socket.IPPROTO_TCP else 60 # 5 min for TCP, 1 min for UDP
62
+ return time.time() - self.last_activity > timeout
63
+
64
+ @property
65
+ def duration(self) -> float:
66
+ """Get session duration in seconds"""
67
+ return time.time() - self.created_time
68
+
69
+ def update_activity(self, bytes_transferred: int = 0, direction: str = 'out'):
70
+ """Update session activity"""
71
+ self.last_activity = time.time()
72
+
73
+ if direction == 'out':
74
+ self.bytes_out += bytes_transferred
75
+ self.packets_out += 1
76
+ else:
77
+ self.bytes_in += bytes_transferred
78
+ self.packets_in += 1
79
+
80
+
81
+ class PortPool:
82
+ """Manages available ports for NAT"""
83
+
84
+ def __init__(self, start_port: int = 10000, end_port: int = 65535):
85
+ self.start_port = start_port
86
+ self.end_port = end_port
87
+ self.available_ports: Set[int] = set(range(start_port, end_port + 1))
88
+ self.allocated_ports: Dict[int, str] = {} # port -> session_id
89
+ self.lock = threading.Lock()
90
+
91
+ def allocate_port(self, session_id: str) -> Optional[int]:
92
+ """Allocate a port for a session"""
93
+ with self.lock:
94
+ if not self.available_ports:
95
+ return None
96
+
97
+ # Try to get a random port to distribute load
98
+ port = random.choice(list(self.available_ports))
99
+ self.available_ports.remove(port)
100
+ self.allocated_ports[port] = session_id
101
+
102
+ return port
103
+
104
+ def release_port(self, port: int) -> bool:
105
+ """Release a port back to the pool"""
106
+ with self.lock:
107
+ if port in self.allocated_ports:
108
+ del self.allocated_ports[port]
109
+ if self.start_port <= port <= self.end_port:
110
+ self.available_ports.add(port)
111
+ return True
112
+ return False
113
+
114
+ def get_session_for_port(self, port: int) -> Optional[str]:
115
+ """Get session ID for a port"""
116
+ with self.lock:
117
+ return self.allocated_ports.get(port)
118
+
119
+ def get_stats(self) -> Dict:
120
+ """Get port pool statistics"""
121
+ with self.lock:
122
+ return {
123
+ 'total_ports': self.end_port - self.start_port + 1,
124
+ 'available_ports': len(self.available_ports),
125
+ 'allocated_ports': len(self.allocated_ports),
126
+ 'utilization': len(self.allocated_ports) / (self.end_port - self.start_port + 1)
127
+ }
128
+
129
+
130
+ class NATEngine:
131
+ """Network Address Translation engine"""
132
+
133
+ def __init__(self, config: Dict):
134
+ self.config = config
135
+ self.sessions: Dict[str, NATSession] = {} # session_id -> session
136
+ self.virtual_to_session: Dict[Tuple[str, int, int], str] = {} # (vip, vport, proto) -> session_id
137
+ self.host_to_session: Dict[Tuple[str, int, int], str] = {} # (hip, hport, proto) -> session_id
138
+ self.lock = threading.Lock()
139
+
140
+ # Port pool for outbound connections
141
+ self.port_pool = PortPool(
142
+ config.get('port_range_start', 10000),
143
+ config.get('port_range_end', 65535)
144
+ )
145
+
146
+ # Host IP for outbound connections
147
+ self.host_ip = config.get('host_ip', self._get_default_host_ip())
148
+
149
+ # Session timeout
150
+ self.session_timeout = config.get('session_timeout', 300)
151
+
152
+ # Statistics
153
+ self.stats = {
154
+ 'total_sessions': 0,
155
+ 'active_sessions': 0,
156
+ 'expired_sessions': 0,
157
+ 'port_exhaustion_events': 0,
158
+ 'bytes_translated': 0,
159
+ 'packets_translated': 0
160
+ }
161
+
162
+ # Cleanup thread
163
+ self.running = False
164
+ self.cleanup_thread = None
165
+
166
+ def _get_default_host_ip(self) -> str:
167
+ """Get default host IP address"""
168
+ try:
169
+ # Connect to a remote address to determine local IP
170
+ with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
171
+ s.connect(('8.8.8.8', 80))
172
+ return s.getsockname()[0]
173
+ except Exception:
174
+ return '127.0.0.1'
175
+
176
+ def _cleanup_expired_sessions(self):
177
+ """Clean up expired sessions"""
178
+ current_time = time.time()
179
+ expired_sessions = []
180
+
181
+ with self.lock:
182
+ for session_id, session in self.sessions.items():
183
+ if session.is_expired:
184
+ expired_sessions.append(session_id)
185
+
186
+ for session_id in expired_sessions:
187
+ self._remove_session(session_id)
188
+ self.stats['expired_sessions'] += 1
189
+
190
+ def _remove_session(self, session_id: str):
191
+ """Remove a session and clean up resources"""
192
+ with self.lock:
193
+ if session_id not in self.sessions:
194
+ return
195
+
196
+ session = self.sessions[session_id]
197
+
198
+ # Remove from lookup tables
199
+ virtual_key = (session.virtual_ip, session.virtual_port, session.protocol)
200
+ if virtual_key in self.virtual_to_session:
201
+ del self.virtual_to_session[virtual_key]
202
+
203
+ host_key = (session.host_ip, session.host_port, session.protocol)
204
+ if host_key in self.host_to_session:
205
+ del self.host_to_session[host_key]
206
+
207
+ # Release port
208
+ self.port_pool.release_port(session.host_port)
209
+
210
+ # Remove session
211
+ del self.sessions[session_id]
212
+
213
+ self.stats['active_sessions'] = len(self.sessions)
214
+
215
+ def create_outbound_session(self, virtual_ip: str, virtual_port: int,
216
+ real_ip: str, real_port: int, protocol: int) -> Optional[NATSession]:
217
+ """Create NAT session for outbound connection"""
218
+ # Allocate host port
219
+ session_id = f"{virtual_ip}:{virtual_port}-{real_ip}:{real_port}-{protocol}"
220
+ host_port = self.port_pool.allocate_port(session_id)
221
+
222
+ if host_port is None:
223
+ self.stats['port_exhaustion_events'] += 1
224
+ return None
225
+
226
+ # Create session
227
+ session = NATSession(
228
+ virtual_ip=virtual_ip,
229
+ virtual_port=virtual_port,
230
+ real_ip=real_ip,
231
+ real_port=real_port,
232
+ host_ip=self.host_ip,
233
+ host_port=host_port,
234
+ protocol=protocol,
235
+ nat_type=NATType.SNAT,
236
+ created_time=time.time(),
237
+ last_activity=time.time()
238
+ )
239
+
240
+ with self.lock:
241
+ self.sessions[session_id] = session
242
+
243
+ # Add to lookup tables
244
+ virtual_key = (virtual_ip, virtual_port, protocol)
245
+ self.virtual_to_session[virtual_key] = session_id
246
+
247
+ host_key = (self.host_ip, host_port, protocol)
248
+ self.host_to_session[host_key] = session_id
249
+
250
+ self.stats['total_sessions'] += 1
251
+ self.stats['active_sessions'] = len(self.sessions)
252
+
253
+ return session
254
+
255
+ def translate_outbound(self, virtual_ip: str, virtual_port: int,
256
+ real_ip: str, real_port: int, protocol: int) -> Optional[Tuple[str, int]]:
257
+ """Translate outbound packet (virtual -> host)"""
258
+ virtual_key = (virtual_ip, virtual_port, protocol)
259
+
260
+ with self.lock:
261
+ session_id = self.virtual_to_session.get(virtual_key)
262
+
263
+ if session_id:
264
+ session = self.sessions[session_id]
265
+ session.update_activity(direction='out')
266
+ return (session.host_ip, session.host_port)
267
+ else:
268
+ # Create new session
269
+ session = self.create_outbound_session(virtual_ip, virtual_port, real_ip, real_port, protocol)
270
+ if session:
271
+ return (session.host_ip, session.host_port)
272
+
273
+ return None
274
+
275
+ def translate_inbound(self, host_ip: str, host_port: int, protocol: int) -> Optional[Tuple[str, int]]:
276
+ """Translate inbound packet (host -> virtual)"""
277
+ host_key = (host_ip, host_port, protocol)
278
+
279
+ with self.lock:
280
+ session_id = self.host_to_session.get(host_key)
281
+
282
+ if session_id and session_id in self.sessions:
283
+ session = self.sessions[session_id]
284
+ session.update_activity(direction='in')
285
+ return (session.virtual_ip, session.virtual_port)
286
+
287
+ return None
288
+
289
+ def get_session_by_virtual(self, virtual_ip: str, virtual_port: int, protocol: int) -> Optional[NATSession]:
290
+ """Get session by virtual endpoint"""
291
+ virtual_key = (virtual_ip, virtual_port, protocol)
292
+
293
+ with self.lock:
294
+ session_id = self.virtual_to_session.get(virtual_key)
295
+ if session_id and session_id in self.sessions:
296
+ return self.sessions[session_id]
297
+
298
+ return None
299
+
300
+ def get_session_by_host(self, host_ip: str, host_port: int, protocol: int) -> Optional[NATSession]:
301
+ """Get session by host endpoint"""
302
+ host_key = (host_ip, host_port, protocol)
303
+
304
+ with self.lock:
305
+ session_id = self.host_to_session.get(host_key)
306
+ if session_id and session_id in self.sessions:
307
+ return self.sessions[session_id]
308
+
309
+ return None
310
+
311
+ def close_session(self, session_id: str) -> bool:
312
+ """Manually close a session"""
313
+ with self.lock:
314
+ if session_id in self.sessions:
315
+ self._remove_session(session_id)
316
+ return True
317
+ return False
318
+
319
+ def close_session_by_virtual(self, virtual_ip: str, virtual_port: int, protocol: int) -> bool:
320
+ """Close session by virtual endpoint"""
321
+ virtual_key = (virtual_ip, virtual_port, protocol)
322
+
323
+ with self.lock:
324
+ session_id = self.virtual_to_session.get(virtual_key)
325
+ if session_id:
326
+ self._remove_session(session_id)
327
+ return True
328
+ return False
329
+
330
+ def get_sessions(self) -> Dict[str, Dict]:
331
+ """Get all active sessions"""
332
+ with self.lock:
333
+ return {
334
+ session_id: {
335
+ 'virtual_ip': session.virtual_ip,
336
+ 'virtual_port': session.virtual_port,
337
+ 'real_ip': session.real_ip,
338
+ 'real_port': session.real_port,
339
+ 'host_ip': session.host_ip,
340
+ 'host_port': session.host_port,
341
+ 'protocol': session.protocol,
342
+ 'nat_type': session.nat_type.value,
343
+ 'created_time': session.created_time,
344
+ 'last_activity': session.last_activity,
345
+ 'duration': session.duration,
346
+ 'bytes_in': session.bytes_in,
347
+ 'bytes_out': session.bytes_out,
348
+ 'packets_in': session.packets_in,
349
+ 'packets_out': session.packets_out,
350
+ 'is_expired': session.is_expired
351
+ }
352
+ for session_id, session in self.sessions.items()
353
+ }
354
+
355
+ def get_stats(self) -> Dict:
356
+ """Get NAT statistics"""
357
+ port_stats = self.port_pool.get_stats()
358
+
359
+ with self.lock:
360
+ current_stats = self.stats.copy()
361
+ current_stats['active_sessions'] = len(self.sessions)
362
+ current_stats.update(port_stats)
363
+
364
+ return current_stats
365
+
366
+ def update_packet_stats(self, bytes_count: int):
367
+ """Update packet statistics"""
368
+ self.stats['bytes_translated'] += bytes_count
369
+ self.stats['packets_translated'] += 1
370
+
371
+ def _cleanup_loop(self):
372
+ """Background cleanup loop"""
373
+ while self.running:
374
+ try:
375
+ # print("NAT cleanup loop: Cleaning expired sessions...") # Debug print
376
+ self._cleanup_expired_sessions()
377
+ time.sleep(0.1) # Shorter sleep for faster testing
378
+ except Exception as e:
379
+ print(f"NAT cleanup error: {e}")
380
+ time.sleep(0.1)
381
+
382
+ def start(self):
383
+ """Start NAT engine"""
384
+ self.running = True
385
+ self.cleanup_thread = threading.Thread(target=self._cleanup_loop, daemon=True)
386
+ self.cleanup_thread.start()
387
+ # print(f"NAT engine started - Host IP: {self.host_ip}, Port range: {self.port_pool.start_port}-{self.port_pool.end_port}")
388
+
389
+ def stop(self):
390
+ """Stop NAT engine"""
391
+ # print("Stopping NAT engine...") # Debug print
392
+ self.running = False
393
+ if self.cleanup_thread and self.cleanup_thread.is_alive():
394
+ self.cleanup_thread.join(timeout=1) # Add timeout to join
395
+ if self.cleanup_thread.is_alive():
396
+ print("NAT cleanup thread did not terminate in time.") # Debug print
397
+
398
+ # Close all sessions
399
+ with self.lock:
400
+ session_ids = list(self.sessions.keys())
401
+ for session_id in session_ids:
402
+ self._remove_session(session_id)
403
+
404
+ # print("NAT engine stopped")
405
+
406
+ def _calculate_ip_checksum(self, ip_header_no_checksum: bytes) -> int:
407
+ """Calculate the IP header checksum."""
408
+ # IP header checksum calculation (simplified for demonstration)
409
+ # This is a basic implementation and might need refinement for production use
410
+ s = 0
411
+ # loop through header words
412
+ for i in range(0, len(ip_header_no_checksum), 2):
413
+ w = (ip_header_no_checksum[i] << 8) + (ip_header_no_checksum[i+1])
414
+ s = s + w
415
+
416
+ s = (s & 0xffff) + (s >> 16)
417
+ s = s + (s >> 16)
418
+ return ~s & 0xffff
419
+
420
+ def process_inbound_packet(self, packet: bytes) -> Optional[bytes]:
421
+ """Process an inbound packet (from internet to VPN client) for DNAT."""
422
+ # Parse IP header
423
+ # Assuming Ethernet frame, IP header starts at offset 14
424
+ # For simplicity, let's assume we are only dealing with IPv4 for now
425
+ ip_header_offset = 14
426
+ ip_header_length = (packet[ip_header_offset] & 0xF) * 4
427
+ ip_header = packet[ip_header_offset : ip_header_offset + ip_header_length]
428
+
429
+ # Unpack IP header (version_ihl, tos, total_length, identification, fragment_offset, ttl, protocol, header_checksum, source_address, destination_address)
430
+ iph = struct.unpack('!BBHHHBBH4s4s', ip_header)
431
+
432
+ protocol = iph[6]
433
+ source_ip = socket.inet_ntoa(iph[8])
434
+ dest_ip = socket.inet_ntoa(iph[9])
435
+
436
+ # Only process TCP/UDP for now
437
+ if protocol not in [socket.IPPROTO_TCP, socket.IPPROTO_UDP]:
438
+ return None
439
+
440
+ # Parse TCP/UDP header
441
+ transport_header_offset = ip_header_offset + ip_header_length
442
+ if protocol == socket.IPPROTO_TCP:
443
+ tcp_header = packet[transport_header_offset : transport_header_offset + 20]
444
+ tcph = struct.unpack('!HHLLBBHHH', tcp_header)
445
+ source_port = tcph[0]
446
+ dest_port = tcph[1]
447
+ elif protocol == socket.IPPROTO_UDP:
448
+ udp_header = packet[transport_header_offset : transport_header_offset + 8]
449
+ udph = struct.unpack('!HHHH', udp_header)
450
+ source_port = udph[0]
451
+ dest_port = udph[1]
452
+ else:
453
+ return None
454
+
455
+ # Check for DNAT rule match (simplified for now, actual DNAT rules would be in DNATEngine)
456
+ # For now, assume we are looking for a session based on host_ip (d_addr) and host_port (dest_port)
457
+ translated_endpoint = self.translate_inbound(dest_ip, dest_port, protocol)
458
+
459
+ if translated_endpoint:
460
+ virtual_ip, virtual_port = translated_endpoint
461
+
462
+ # Reconstruct packet with translated destination IP and port
463
+ # Recalculate IP header checksum
464
+ new_dest_ip_bytes = socket.inet_aton(virtual_ip)
465
+
466
+ # Rebuild IP header with new destination IP
467
+ # Need to recalculate checksum for IP header
468
+ # For simplicity, we'll set checksum to 0 and assume it's recalculated later or by OS
469
+ new_ip_header_raw = struct.pack('!BBHHHBBH4s4s', iph[0], iph[1], iph[2], iph[3], iph[4], iph[5], iph[6], 0, iph[8], new_dest_ip_bytes)
470
+ new_ip_header_checksum = self._calculate_ip_checksum(new_ip_header_raw)
471
+ new_ip_header = struct.pack('!BBHHHBBH4s4s', iph[0], iph[1], iph[2], iph[3], iph[4], iph[5], iph[6], new_ip_header_checksum, iph[8], new_dest_ip_bytes)
472
+
473
+ # Rebuild TCP/UDP header with new destination port
474
+ if protocol == socket.IPPROTO_TCP:
475
+ # Recalculate TCP checksum (requires pseudo-header, IP header, and TCP data)
476
+ new_tcp_header_raw = struct.pack('!HHLLBBHHH', source_port, virtual_port, tcph[2], tcph[3], tcph[4], tcph[5], tcph[6], 0, tcph[8])
477
+ # For now, setting checksum to 0. Proper recalculation is complex.
478
+ new_tcp_header = struct.pack('!HHLLBBHHH', source_port, virtual_port, tcph[2], tcph[3], tcph[4], tcph[5], tcph[6], 0, tcph[8])
479
+ return packet[:ip_header_offset] + new_ip_header + new_tcp_header + packet[transport_header_offset + 20:]
480
+ elif protocol == socket.IPPROTO_UDP:
481
+ # Recalculate UDP checksum (requires pseudo-header, IP header, and UDP data)
482
+ new_udp_header_raw = struct.pack('!HHHH', source_port, virtual_port, udph[2], 0)
483
+ # For now, setting checksum to 0. Proper recalculation is complex.
484
+ new_udp_header = struct.pack('!HHHH', source_port, virtual_port, udph[2], 0)
485
+ return packet[:ip_header_offset] + new_ip_header + new_udp_header + packet[transport_header_offset + 8:]
486
+
487
+ return None
488
+
489
+ def process_outbound_packet(self, packet: bytes) -> Optional[bytes]:
490
+ """Process an outbound packet (from VPN client to internet) for SNAT."""
491
+ # Parse IP header
492
+ ip_header_offset = 14
493
+ ip_header_length = (packet[ip_header_offset] & 0xF) * 4
494
+ ip_header = packet[ip_header_offset : ip_header_offset + ip_header_length]
495
+
496
+ # Unpack IP header
497
+ iph = struct.unpack('!BBHHHBBH4s4s', ip_header)
498
+
499
+ protocol = iph[6]
500
+ source_ip = socket.inet_ntoa(iph[8])
501
+ dest_ip = socket.inet_ntoa(iph[9])
502
+
503
+ # Only process TCP/UDP for now
504
+ if protocol not in [socket.IPPROTO_TCP, socket.IPPROTO_UDP]:
505
+ return None
506
+
507
+ # Parse TCP/UDP header
508
+ transport_header_offset = ip_header_offset + ip_header_length
509
+ if protocol == socket.IPPROTO_TCP:
510
+ tcp_header = packet[transport_header_offset : transport_header_offset + 20]
511
+ tcph = struct.unpack('!HHLLBBHHH', tcp_header)
512
+ source_port = tcph[0]
513
+ dest_port = tcph[1]
514
+ elif protocol == socket.IPPROTO_UDP:
515
+ udp_header = packet[transport_header_offset : transport_header_offset + 8]
516
+ udph = struct.unpack('!HHHH', udp_header)
517
+ source_port = udph[0]
518
+ dest_port = udph[1]
519
+ else:
520
+ return None
521
+
522
+ # Perform SNAT
523
+ translated_endpoint = self.translate_outbound(source_ip, source_port, dest_ip, dest_port, protocol)
524
+
525
+ if translated_endpoint:
526
+ host_ip, host_port = translated_endpoint
527
+
528
+ # Reconstruct packet with translated source IP and port
529
+ # Recalculate IP header checksum
530
+ new_source_ip_bytes = socket.inet_aton(host_ip)
531
+
532
+ # Rebuild IP header with new source IP
533
+ new_ip_header_raw = struct.pack('!BBHHHBBH4s4s', iph[0], iph[1], iph[2], iph[3], iph[4], iph[5], iph[6], 0, new_source_ip_bytes, iph[9])
534
+ new_ip_header_checksum = self._calculate_ip_checksum(new_ip_header_raw)
535
+ new_ip_header = struct.pack('!BBHHHBBH4s4s', iph[0], iph[1], iph[2], iph[3], iph[4], iph[5], iph[6], new_ip_header_checksum, new_source_ip_bytes, iph[9])
536
+
537
+ # Rebuild TCP/UDP header with new source port
538
+ if protocol == socket.IPPROTO_TCP:
539
+ # Recalculate TCP checksum
540
+ new_tcp_header_raw = struct.pack('!HHLLBBHHH', host_port, dest_port, tcph[2], tcph[3], tcph[4], tcph[5], tcph[6], 0, tcph[8])
541
+ # For now, setting checksum to 0. Proper recalculation is complex.
542
+ new_tcp_header = struct.pack('!HHLLBBHHH', host_port, dest_port, tcph[2], tcph[3], tcph[4], tcph[5], tcph[6], 0, tcph[8])
543
+ return packet[:ip_header_offset] + new_ip_header + new_tcp_header + packet[transport_header_offset + 20:]
544
+ elif protocol == socket.IPPROTO_UDP:
545
+ # Recalculate UDP checksum
546
+ new_udp_header_raw = struct.pack('!HHHH', host_port, dest_port, udph[2], 0)
547
+ # For now, setting checksum to 0. Proper recalculation is complex.
548
+ new_udp_header = struct.pack('!HHHH', host_port, dest_port, udph[2], 0)
549
+ return packet[:ip_header_offset] + new_ip_header + new_udp_header + packet[transport_header_offset + 8:]
550
+
551
+ return None
552
+
553
+
554
+ class NATRule:
555
+ """Represents a NAT rule for DNAT (port forwarding)"""
556
+
557
+ def __init__(self, external_port: int, internal_ip: str, internal_port: int,
558
+ protocol: int, enabled: bool = True):
559
+ self.external_port = external_port
560
+ self.internal_ip = internal_ip
561
+ self.internal_port = internal_port
562
+ self.protocol = protocol
563
+ self.enabled = enabled
564
+ self.created_time = time.time()
565
+ self.hit_count = 0
566
+ self.last_hit = None
567
+
568
+ def matches(self, port: int, protocol: int) -> bool:
569
+ """Check if rule matches the given port and protocol"""
570
+ return (self.enabled and
571
+ self.external_port == port and
572
+ self.protocol == protocol)
573
+
574
+ def record_hit(self):
575
+ """Record a rule hit"""
576
+ self.hit_count += 1
577
+ self.last_hit = time.time()
578
+
579
+ def to_dict(self) -> Dict:
580
+ """Convert rule to dictionary"""
581
+ return {
582
+ 'external_port': self.external_port,
583
+ 'internal_ip': self.internal_ip,
584
+ 'internal_port': self.internal_port,
585
+ 'protocol': self.protocol,
586
+ 'enabled': self.enabled,
587
+ 'created_time': self.created_time,
588
+ 'hit_count': self.hit_count,
589
+ 'last_hit': self.last_hit
590
+ }
591
+
592
+
593
+ class DNATEngine:
594
+ """Destination NAT engine for port forwarding"""
595
+
596
+ def __init__(self):
597
+ self.rules: Dict[str, NATRule] = {} # rule_id -> rule
598
+ self.lock = threading.Lock()
599
+
600
+ def add_rule(self, rule_id: str, external_port: int, internal_ip: str,
601
+ internal_port: int, protocol: int) -> bool:
602
+ """Add DNAT rule"""
603
+ with self.lock:
604
+ if rule_id in self.rules:
605
+ return False
606
+ rule = NATRule(external_port, internal_ip, internal_port, protocol)
607
+ self.rules[rule_id] = rule
608
+ return True
609
+
610
+ def remove_rule(self, rule_id: str) -> bool:
611
+ """Remove DNAT rule"""
612
+ with self.lock:
613
+ if rule_id in self.rules:
614
+ del self.rules[rule_id]
615
+ return True
616
+ return False
617
+
618
+ def get_rule(self, rule_id: str) -> Optional[NATRule]:
619
+ """Get DNAT rule by ID"""
620
+ with self.lock:
621
+ return self.rules.get(rule_id)
622
+
623
+ def get_matching_rule(self, port: int, protocol: int) -> Optional[NATRule]:
624
+ """Get matching DNAT rule for given port and protocol"""
625
+ with self.lock:
626
+ for rule in self.rules.values():
627
+ if rule.matches(port, protocol):
628
+ rule.record_hit()
629
+ return rule
630
+ return None
631
+
632
+ def get_all_rules(self) -> Dict[str, Dict]:
633
+ """Get all DNAT rules"""
634
+ with self.lock:
635
+ return {rule_id: rule.to_dict() for rule_id, rule in self.rules.items()}
636
+
637
+
638
+
core/openvpn_manager.py ADDED
@@ -0,0 +1,508 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ OpenVPN Manager Module
3
+
4
+ Manages OpenVPN server integration with the Virtual ISP Stack
5
+ """
6
+
7
+ import os
8
+ import json
9
+ import subprocess
10
+ import threading
11
+ import time
12
+ import logging
13
+ from typing import Dict, List, Optional, Any
14
+ from dataclasses import dataclass, asdict
15
+ import ipaddress
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ @dataclass
20
+ class VPNClient:
21
+ """Represents a connected VPN client"""
22
+ client_id: str
23
+ common_name: str
24
+ ip_address: str
25
+ connected_at: float
26
+ bytes_received: int = 0
27
+ bytes_sent: int = 0
28
+ status: str = "connected"
29
+ routed_through_vpn: bool = False
30
+
31
+ @dataclass
32
+ class VPNServerStatus:
33
+ """Represents VPN server status"""
34
+ is_running: bool
35
+ connected_clients: int
36
+ total_bytes_received: int
37
+ total_bytes_sent: int
38
+ uptime: float
39
+ server_ip: str
40
+ server_port: int
41
+
42
+ class OpenVPNManager:
43
+ """Manages OpenVPN server and client connections with traffic routing"""
44
+
45
+ def __init__(self, config: Dict[str, Any]):
46
+ self.config = config
47
+ self.server_config_path = "/etc/openvpn/server/server.conf"
48
+ self.status_log_path = "/tmp/openvpn/openvpn-status.log"
49
+ self.clients: Dict[str, VPNClient] = {}
50
+ self.server_process = None
51
+ self.is_running = False
52
+ self.start_time = None
53
+
54
+ # VPN network configuration
55
+ self.vpn_network = ipaddress.IPv4Network("10.8.0.0/24")
56
+ self.vpn_server_ip = "10.8.0.1"
57
+ self.vpn_port = 1194
58
+
59
+ # Integration with ISP stack
60
+ self.dhcp_server = None
61
+ self.nat_engine = None
62
+ self.firewall = None
63
+ self.router = None
64
+ self.traffic_router = None # New traffic router component
65
+
66
+ # Status monitoring thread
67
+ self.monitor_thread = None
68
+ self.monitor_running = False
69
+
70
+ # Client configuration storage
71
+ self.config_storage_path = "/tmp/vpn_client_configs"
72
+ os.makedirs(self.config_storage_path, exist_ok=True)
73
+
74
+ def set_isp_components(self, dhcp_server=None, nat_engine=None, firewall=None, router=None, traffic_router=None):
75
+ """Set references to ISP stack components for integration"""
76
+ self.dhcp_server = dhcp_server
77
+ self.nat_engine = nat_engine
78
+ self.firewall = firewall
79
+ self.router = router
80
+ self.traffic_router = traffic_router
81
+
82
+ # Configure traffic router with other components
83
+ if self.traffic_router:
84
+ self.traffic_router.set_components(
85
+ nat_engine=nat_engine,
86
+ firewall=firewall,
87
+ dhcp_server=dhcp_server
88
+ )
89
+
90
+ def start_server(self) -> bool:
91
+ """Start the OpenVPN server with traffic routing"""
92
+ try:
93
+ if self.is_running:
94
+ logger.warning("OpenVPN server is already running")
95
+ return True
96
+
97
+ # Ensure configuration exists
98
+ if not os.path.exists(self.server_config_path):
99
+ logger.error(f"OpenVPN server configuration not found: {self.server_config_path}")
100
+ return False
101
+
102
+ # Start traffic router first
103
+ if self.traffic_router and not self.traffic_router.is_running:
104
+ if not self.traffic_router.start():
105
+ logger.error("Failed to start traffic router")
106
+ return False
107
+
108
+
109
+ # Start OpenVPN server
110
+ self.server_process = subprocess.Popen(['sudo', 'openvpn', '--config', self.server_config_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
111
+ self.is_running = True
112
+ self.start_time = time.time()
113
+ logger.info("OpenVPN server started successfully")
114
+
115
+ # Start monitoring thread
116
+ self.start_monitoring()
117
+
118
+ # Configure firewall rules for VPN
119
+ self._configure_vpn_firewall()
120
+
121
+ # Configure NAT for VPN traffic
122
+ self._configure_vpn_nat()
123
+
124
+ return True
125
+
126
+ except Exception as e:
127
+ logger.error(f"Error starting OpenVPN server: {e}")
128
+ return False
129
+
130
+ def stop_server(self) -> bool:
131
+ """Stop the OpenVPN server and traffic routing"""
132
+ try:
133
+ if not self.is_running:
134
+ logger.warning("OpenVPN server is not running")
135
+ return True
136
+
137
+ # Stop monitoring
138
+ self.stop_monitoring()
139
+
140
+ # Remove all client routes before stopping
141
+ if self.traffic_router:
142
+ for client_id in list(self.clients.keys()):
143
+ self.traffic_router.remove_client_route(client_id)
144
+
145
+ # Stop OpenVPN server
146
+ if self.server_process:
147
+ self.server_process.terminate()
148
+ self.server_process.wait(timeout=5)
149
+ if self.server_process.poll() is None:
150
+ self.server_process.kill()
151
+ self.server_process = None
152
+ self.is_running = False
153
+ self.start_time = None
154
+ self.clients.clear()
155
+ logger.info("OpenVPN server stopped successfully")
156
+ return True
157
+
158
+ except Exception as e:
159
+ logger.error(f"Error stopping OpenVPN server: {e}")
160
+ return False
161
+
162
+ def start_monitoring(self):
163
+ """Start the client monitoring thread"""
164
+ if self.monitor_thread and self.monitor_thread.is_alive():
165
+ return
166
+
167
+ self.monitor_running = True
168
+ self.monitor_thread = threading.Thread(target=self._monitor_clients, daemon=True)
169
+ self.monitor_thread.start()
170
+ logger.info("Started OpenVPN client monitoring")
171
+
172
+ def stop_monitoring(self):
173
+ """Stop the client monitoring thread"""
174
+ self.monitor_running = False
175
+ if self.monitor_thread:
176
+ self.monitor_thread.join(timeout=5)
177
+ logger.info("Stopped OpenVPN client monitoring")
178
+
179
+ def _monitor_clients(self):
180
+ """Monitor connected VPN clients"""
181
+ while self.monitor_running:
182
+ try:
183
+ self._update_client_status()
184
+ time.sleep(10) # Update every 10 seconds
185
+ except Exception as e:
186
+ logger.error(f"Error monitoring VPN clients: {e}")
187
+ time.sleep(30) # Wait longer on error
188
+
189
+ def _update_client_status(self):
190
+ """Update client status from OpenVPN status log and manage traffic routing"""
191
+ try:
192
+ with open(self.status_log_path, 'r') as f:
193
+ lines = f.readlines()
194
+
195
+ new_clients = {}
196
+ client_section = False
197
+ for line in lines:
198
+ if line.startswith('ROUTING TABLE'):
199
+ client_section = False
200
+ if client_section and not line.startswith('GLOBAL STATS'):
201
+ parts = line.strip().split(',')
202
+ if len(parts) >= 5:
203
+ common_name = parts[0]
204
+ real_ip_port = parts[1]
205
+ virtual_ip = parts[2]
206
+ bytes_received = int(parts[3])
207
+ bytes_sent = int(parts[4])
208
+ connected_since = float(parts[5]) # Assuming this is a timestamp
209
+
210
+ # Extract IP address from real_ip_port (e.g., 1.2.3.4:12345)
211
+ ip_address = real_ip_port.split(':')[0]
212
+
213
+ client = VPNClient(
214
+ client_id=common_name,
215
+ common_name=common_name,
216
+ ip_address=virtual_ip,
217
+ connected_at=connected_since,
218
+ bytes_received=bytes_received,
219
+ bytes_sent=bytes_sent,
220
+ status="connected",
221
+ routed_through_vpn=True
222
+ )
223
+ new_clients[common_name] = client
224
+ if line.startswith('COMMON NAME'):
225
+ client_section = True
226
+ self.clients = new_clients
227
+
228
+ except Exception as e:
229
+ logger.error(f"Error updating client status: {e}")
230
+
231
+ def _sync_with_dhcp(self):
232
+ """Sync VPN clients with DHCP server"""
233
+ try:
234
+ for client in self.clients.values():
235
+ if client.ip_address != "unknown":
236
+ # Register VPN client IP with DHCP server
237
+ # This allows the ISP stack to track VPN clients
238
+ if hasattr(self.dhcp_server, 'register_static_lease'):
239
+ self.dhcp_server.register_static_lease(
240
+ client.common_name,
241
+ client.ip_address,
242
+ "VPN Client"
243
+ )
244
+ except Exception as e:
245
+ logger.error(f"Error syncing with DHCP: {e}")
246
+
247
+ def _configure_vpn_firewall(self):
248
+ """Configure firewall rules for VPN traffic"""
249
+ try:
250
+ if not self.firewall:
251
+ return
252
+
253
+ # Add firewall rules for VPN
254
+ vpn_rules = [
255
+ {
256
+ "rule_id": "allow_openvpn",
257
+ "priority": 10,
258
+ "action": "ACCEPT",
259
+ "direction": "BOTH",
260
+ "dest_port": str(self.vpn_port),
261
+ "protocol": "UDP",
262
+ "description": "Allow OpenVPN traffic",
263
+ "enabled": True
264
+ },
265
+ {
266
+ "rule_id": "allow_vpn_network",
267
+ "priority": 11,
268
+ "action": "ACCEPT",
269
+ "direction": "BOTH",
270
+ "source_network": str(self.vpn_network),
271
+ "description": "Allow VPN client network traffic",
272
+ "enabled": True
273
+ }
274
+ ]
275
+
276
+ for rule in vpn_rules:
277
+ if hasattr(self.firewall, 'add_rule'):
278
+ self.firewall.add_rule(rule)
279
+
280
+ logger.info("Configured firewall rules for VPN")
281
+
282
+ except Exception as e:
283
+ logger.error(f"Error configuring VPN firewall: {e}")
284
+
285
+ def _configure_vpn_nat(self):
286
+ """Configure NAT for VPN traffic"""
287
+ try:
288
+ # NAT configuration will be handled by the external environment (e.g., HuggingFace Spaces setup)
289
+ # or by the underlying network infrastructure. We are removing direct iptables calls.
290
+ logger.info("Skipping direct iptables NAT configuration as per instructions.")
291
+
292
+ except Exception as e:
293
+ logger.error(f"Error configuring VPN NAT: {e}")
294
+
295
+ def get_server_status(self) -> VPNServerStatus:
296
+ """Get current server status"""
297
+ total_bytes_received = sum(client.bytes_received for client in self.clients.values())
298
+ total_bytes_sent = sum(client.bytes_sent for client in self.clients.values())
299
+ uptime = time.time() - self.start_time if self.start_time else 0
300
+
301
+ return VPNServerStatus(
302
+ is_running=self.is_running,
303
+ connected_clients=len(self.clients),
304
+ total_bytes_received=total_bytes_received,
305
+ total_bytes_sent=total_bytes_sent,
306
+ uptime=uptime,
307
+ server_ip=self.vpn_server_ip,
308
+ server_port=self.vpn_port
309
+ )
310
+ def get_connected_clients(self) -> List[Dict[str, Any]]:
311
+ """Get list of connected clients"""
312
+ return [asdict(client) for client in self.clients.values()]
313
+
314
+ def disconnect_client(self, client_id: str) -> bool:
315
+ """Disconnect a specific client"""
316
+ try:
317
+ if client_id not in self.clients:
318
+ return False
319
+
320
+ # Send kill signal to specific client
321
+ # This requires OpenVPN management interface, simplified for now
322
+ logger.info(f"Disconnecting client: {client_id}")
323
+
324
+ # Remove from clients dict
325
+ del self.clients[client_id]
326
+ return True
327
+
328
+ except Exception as e:
329
+ logger.error(f"Error disconnecting client {client_id}: {e}")
330
+ return False
331
+
332
+ def generate_client_config(self, client_name: str, server_ip: str) -> str:
333
+ """Generate client configuration file with embedded certificates"""
334
+ try:
335
+ # Read real CA certificate
336
+ ca_cert_path = "/etc/openvpn/server/ca.crt"
337
+ with open(ca_cert_path, 'r') as f:
338
+ ca_cert = f.read()
339
+
340
+ client_cert_path = f"/home/ubuntu/easy-rsa/pki/issued/{client_name}.crt"
341
+ with open(client_cert_path, 'r') as f:
342
+ client_cert = f.read()
343
+
344
+ client_key_path = f"/home/ubuntu/easy-rsa/pki/private/{client_name}.key"
345
+ with open(client_key_path, 'r') as f:
346
+ client_key = f.read()
347
+
348
+ # Generate complete client configuration
349
+ client_config = f"""# OpenVPN Client Configuration for {client_name}
350
+ # Generated by Virtual ISP Stack
351
+ # Server: {server_ip}:{self.vpn_port}
352
+
353
+ client
354
+ dev tun
355
+ proto udp
356
+ remote {server_ip} {self.vpn_port}
357
+ resolv-retry infinite
358
+ nobind
359
+ persist-key
360
+ persist-tun
361
+ cipher AES-256-CBC
362
+ auth SHA256
363
+ verb 3
364
+ key-direction 1
365
+ redirect-gateway def1 bypass-dhcp
366
+ dhcp-option DNS 8.8.8.8
367
+ dhcp-option DNS 8.8.4.4
368
+ remote-cert-tls server
369
+
370
+ # Embedded CA Certificate
371
+ <ca>
372
+ {ca_cert}
373
+ </ca>
374
+
375
+ # Embedded Client Certificate
376
+ <cert>
377
+ {client_cert}
378
+ </cert>
379
+
380
+ # Embedded Client Private Key
381
+ <key>
382
+ {client_key}
383
+ </key>
384
+
385
+ # TLS Authentication Key (optional, for extra security)
386
+ # <tls-auth>
387
+ # -----BEGIN OpenVPN Static key V1-----
388
+ # [TLS-AUTH-KEY-CONTENT-WOULD-GO-HERE]
389
+ # -----END OpenVPN Static key V1-----
390
+ # </tls-auth>
391
+ """
392
+
393
+ logger.info(f"Generated client configuration for {client_name}")
394
+ return client_config
395
+
396
+ except Exception as e:
397
+ logger.error(f"Error generating client config: {e}")
398
+ return ""
399
+
400
+ def save_client_config(self, client_name: str, config_content: str) -> bool:
401
+ """Save client configuration to storage"""
402
+ try:
403
+ config_file_path = os.path.join(self.config_storage_path, f"{client_name}.ovpn")
404
+ with open(config_file_path, 'w') as f:
405
+ f.write(config_content)
406
+
407
+ logger.info(f"Saved client configuration for {client_name}")
408
+ return True
409
+
410
+ except Exception as e:
411
+ logger.error(f"Error saving client config for {client_name}: {e}")
412
+ return False
413
+
414
+ def load_client_config(self, client_name: str) -> str:
415
+ """Load client configuration from storage"""
416
+ try:
417
+ config_file_path = os.path.join(self.config_storage_path, f"{client_name}.ovpn")
418
+ if not os.path.exists(config_file_path):
419
+ return ""
420
+
421
+ with open(config_file_path, 'r') as f:
422
+ config_content = f.read()
423
+
424
+ logger.info(f"Loaded client configuration for {client_name}")
425
+ return config_content
426
+
427
+ except Exception as e:
428
+ logger.error(f"Error loading client config for {client_name}: {e}")
429
+ return ""
430
+
431
+ def list_client_configs(self) -> List[str]:
432
+ """List all stored client configurations"""
433
+ try:
434
+ config_files = []
435
+ if os.path.exists(self.config_storage_path):
436
+ for filename in os.listdir(self.config_storage_path):
437
+ if filename.endswith('.ovpn'):
438
+ client_name = filename[:-5] # Remove .ovpn extension
439
+ config_files.append(client_name)
440
+
441
+ return config_files
442
+
443
+ except Exception as e:
444
+ logger.error(f"Error listing client configs: {e}")
445
+ return []
446
+
447
+ def delete_client_config(self, client_name: str) -> bool:
448
+ """Delete client configuration from storage"""
449
+ try:
450
+ config_file_path = os.path.join(self.config_storage_path, f"{client_name}.ovpn")
451
+ if os.path.exists(config_file_path):
452
+ os.remove(config_file_path)
453
+ logger.info(f"Deleted client configuration for {client_name}")
454
+ return True
455
+ else:
456
+ logger.warning(f"Client configuration for {client_name} not found")
457
+ return False
458
+
459
+ except Exception as e:
460
+ logger.error(f"Error deleting client config for {client_name}: {e}")
461
+ return False
462
+
463
+ def generate_and_save_client_config(self, client_name: str, server_ip: str) -> str:
464
+ """Generate client configuration and save it to storage"""
465
+ try:
466
+ config_content = self.generate_client_config(client_name, server_ip)
467
+ if config_content:
468
+ if self.save_client_config(client_name, config_content):
469
+ return config_content
470
+ return ""
471
+
472
+ except Exception as e:
473
+ logger.error(f"Error generating and saving client config for {client_name}: {e}")
474
+ return ""
475
+
476
+ def get_statistics(self) -> Dict[str, Any]:
477
+ """Get comprehensive VPN statistics"""
478
+ status = self.get_server_status()
479
+
480
+ return {
481
+ "server_status": asdict(status),
482
+ "connected_clients": self.get_connected_clients(),
483
+ "network_config": {
484
+ "vpn_network": str(self.vpn_network),
485
+ "server_ip": self.vpn_server_ip,
486
+ "server_port": self.vpn_port
487
+ },
488
+ "integration_status": {
489
+ "dhcp_integrated": self.dhcp_server is not None,
490
+ "nat_integrated": self.nat_engine is not None,
491
+ "firewall_integrated": self.firewall is not None,
492
+ "router_integrated": self.router is not None
493
+ }
494
+ }
495
+
496
+ # Global OpenVPN manager instance
497
+ openvpn_manager = None
498
+
499
+ def initialize_openvpn_manager(config: Dict[str, Any]) -> OpenVPNManager:
500
+ """Initialize the OpenVPN manager"""
501
+ global openvpn_manager
502
+ openvpn_manager = OpenVPNManager(config)
503
+ return openvpn_manager
504
+
505
+ def get_openvpn_manager() -> Optional[OpenVPNManager]:
506
+ """Get the global OpenVPN manager instance"""
507
+ return openvpn_manager
508
+
core/packet_bridge.py ADDED
@@ -0,0 +1,664 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Packet Bridge Module
3
+
4
+ Handles communication with virtual clients:
5
+ - Accept packet streams over WebSocket/TCP
6
+ - Deliver response packets back to clients
7
+ - Frame processing (Ethernet → IPv4)
8
+ - Connection management
9
+ """
10
+
11
+ import asyncio
12
+ import websockets
13
+ import socket
14
+ import threading
15
+ import time
16
+ import struct
17
+ from typing import Dict, List, Optional, Callable, Set, Any, Tuple
18
+ from dataclasses import dataclass
19
+ from enum import Enum
20
+ import json
21
+ import logging
22
+
23
+ from .ip_parser import IPParser, ParsedPacket
24
+
25
+
26
+ class BridgeType(Enum):
27
+ WEBSOCKET = "WEBSOCKET"
28
+ TCP_SOCKET = "TCP_SOCKET"
29
+ UDP_SOCKET = "UDP_SOCKET"
30
+
31
+
32
+ @dataclass
33
+ class ClientConnection:
34
+ """Represents a client connection to the bridge"""
35
+ client_id: str
36
+ bridge_type: BridgeType
37
+ remote_address: str
38
+ remote_port: int
39
+ websocket: Optional[Any] = None # WebSocket connection
40
+ socket: Optional['socket.socket'] = None # TCP/UDP socket
41
+ connected_time: float = 0
42
+ last_activity: float = 0
43
+ packets_received: int = 0
44
+ packets_sent: int = 0
45
+ bytes_received: int = 0
46
+ bytes_sent: int = 0
47
+ is_active: bool = True
48
+
49
+ def __post_init__(self):
50
+ if self.connected_time == 0:
51
+ self.connected_time = time.time()
52
+ if self.last_activity == 0:
53
+ self.last_activity = time.time()
54
+
55
+ def update_activity(self, packet_count: int = 1, byte_count: int = 0, direction: str = 'received'):
56
+ """Update connection activity"""
57
+ self.last_activity = time.time()
58
+
59
+ if direction == 'received':
60
+ self.packets_received += packet_count
61
+ self.bytes_received += byte_count
62
+ else:
63
+ self.packets_sent += packet_count
64
+ self.bytes_sent += byte_count
65
+
66
+ def to_dict(self) -> Dict:
67
+ """Convert to dictionary"""
68
+ return {
69
+ 'client_id': self.client_id,
70
+ 'bridge_type': self.bridge_type.value,
71
+ 'remote_address': self.remote_address,
72
+ 'remote_port': self.remote_port,
73
+ 'connected_time': self.connected_time,
74
+ 'last_activity': self.last_activity,
75
+ 'packets_received': self.packets_received,
76
+ 'packets_sent': self.packets_sent,
77
+ 'bytes_received': self.bytes_received,
78
+ 'bytes_sent': self.bytes_sent,
79
+ 'is_active': self.is_active,
80
+ 'duration': time.time() - self.connected_time
81
+ }
82
+
83
+
84
+ class EthernetFrame:
85
+ """Ethernet frame parser"""
86
+
87
+ def __init__(self):
88
+ self.dest_mac = b'\x00' * 6
89
+ self.src_mac = b'\x00' * 6
90
+ self.ethertype = 0x0800 # IPv4
91
+ self.payload = b''
92
+
93
+ @classmethod
94
+ def parse(cls, data: bytes) -> Optional['EthernetFrame']:
95
+ """Parse Ethernet frame from raw bytes"""
96
+ if len(data) < 14: # Minimum Ethernet header size
97
+ return None
98
+
99
+ frame = cls()
100
+ frame.dest_mac = data[0:6]
101
+ frame.src_mac = data[6:12]
102
+ frame.ethertype = struct.unpack('!H', data[12:14])[0]
103
+ frame.payload = data[14:]
104
+
105
+ return frame
106
+
107
+ def build(self) -> bytes:
108
+ """Build Ethernet frame as bytes"""
109
+ header = self.dest_mac + self.src_mac + struct.pack('!H', self.ethertype)
110
+ return header + self.payload
111
+
112
+ def is_ipv4(self) -> bool:
113
+ """Check if frame contains IPv4 packet"""
114
+ return self.ethertype == 0x0800
115
+
116
+ def is_arp(self) -> bool:
117
+ """Check if frame contains ARP packet"""
118
+ return self.ethertype == 0x0806
119
+
120
+
121
+ class PacketBridge:
122
+ """Packet bridge implementation"""
123
+
124
+ def __init__(self, config: Dict):
125
+ self.config = config
126
+ self.clients: Dict[str, ClientConnection] = {}
127
+ self.packet_handlers: List[Callable[[ParsedPacket, str], Optional[bytes]]] = []
128
+ self.lock = threading.Lock()
129
+
130
+ # Configuration
131
+ self.websocket_host = config.get('websocket_host', '0.0.0.0')
132
+ self.websocket_port = config.get('websocket_port', 8765)
133
+ self.tcp_host = config.get('tcp_host', '0.0.0.0')
134
+ self.tcp_port = config.get('tcp_port', 8766)
135
+ self.max_clients = config.get('max_clients', 100)
136
+ self.client_timeout = config.get('client_timeout', 300)
137
+
138
+ # WebSocket server
139
+ self.websocket_server = None
140
+ self.tcp_server_socket = None
141
+
142
+ # Background tasks
143
+ self.running = False
144
+ self.websocket_task = None
145
+ self.tcp_task = None
146
+ self.cleanup_task = None
147
+
148
+ # Statistics
149
+ self.stats = {
150
+ 'total_clients': 0,
151
+ 'active_clients': 0,
152
+ 'packets_processed': 0,
153
+ 'packets_forwarded': 0,
154
+ 'packets_dropped': 0,
155
+ 'bytes_processed': 0,
156
+ 'websocket_connections': 0,
157
+ 'tcp_connections': 0,
158
+ 'connection_errors': 0
159
+ }
160
+
161
+ # Event loop
162
+ self.loop = None
163
+
164
+ def add_packet_handler(self, handler: Callable[[ParsedPacket, str], Optional[bytes]]):
165
+ """Add packet handler function"""
166
+ self.packet_handlers.append(handler)
167
+
168
+ def remove_packet_handler(self, handler: Callable[[ParsedPacket, str], Optional[bytes]]):
169
+ """Remove packet handler function"""
170
+ if handler in self.packet_handlers:
171
+ self.packet_handlers.remove(handler)
172
+
173
+ def _generate_client_id(self, remote_address: str, remote_port: int) -> str:
174
+ """Generate unique client ID"""
175
+ timestamp = int(time.time() * 1000)
176
+ return f"client_{remote_address}_{remote_port}_{timestamp}"
177
+
178
+ def _process_ethernet_frame(self, frame_data: bytes, client_id: str) -> Optional[bytes]:
179
+ """Process Ethernet frame and extract IP packet"""
180
+ try:
181
+ # Parse Ethernet frame
182
+ frame = EthernetFrame.parse(frame_data)
183
+ if not frame or not frame.is_ipv4():
184
+ return None
185
+
186
+ # Parse IP packet
187
+ packet = IPParser.parse_packet(frame.payload)
188
+ self.stats['packets_processed'] += 1
189
+ self.stats['bytes_processed'] += len(frame_data)
190
+
191
+ # Process through packet handlers
192
+ response_packet = None
193
+ for handler in self.packet_handlers:
194
+ try:
195
+ response = handler(packet, client_id)
196
+ if response:
197
+ response_packet = response
198
+ break
199
+ except Exception as e:
200
+ logging.error(f"Packet handler error: {e}")
201
+
202
+ if response_packet:
203
+ # Wrap response in Ethernet frame
204
+ response_frame = EthernetFrame()
205
+ response_frame.dest_mac = frame.src_mac
206
+ response_frame.src_mac = frame.dest_mac
207
+ response_frame.ethertype = 0x0800
208
+ response_frame.payload = response_packet
209
+
210
+ self.stats['packets_forwarded'] += 1
211
+ return response_frame.build()
212
+ else:
213
+ self.stats['packets_dropped'] += 1
214
+ return None
215
+
216
+ except Exception as e:
217
+ logging.error(f"Error processing Ethernet frame: {e}")
218
+ self.stats['packets_dropped'] += 1
219
+ return None
220
+
221
+ async def _handle_websocket_client(self, websocket, path):
222
+ """Handle WebSocket client connection"""
223
+ client_address = websocket.remote_address
224
+ client_id = self._generate_client_id(client_address[0], client_address[1])
225
+
226
+ # Create client connection
227
+ client = ClientConnection(
228
+ client_id=client_id,
229
+ bridge_type=BridgeType.WEBSOCKET,
230
+ remote_address=client_address[0],
231
+ remote_port=client_address[1],
232
+ websocket=websocket
233
+ )
234
+
235
+ with self.lock:
236
+ if len(self.clients) >= self.max_clients:
237
+ await websocket.close(code=1013, reason="Too many clients")
238
+ return
239
+
240
+ self.clients[client_id] = client
241
+
242
+ self.stats['total_clients'] += 1
243
+ self.stats['active_clients'] = len(self.clients)
244
+ self.stats['websocket_connections'] += 1
245
+
246
+ logging.info(f"WebSocket client connected: {client_id} from {client_address}")
247
+
248
+ try:
249
+ async for message in websocket:
250
+ if isinstance(message, bytes):
251
+ # Binary message - treat as Ethernet frame
252
+ client.update_activity(1, len(message), 'received')
253
+
254
+ response = self._process_ethernet_frame(message, client_id)
255
+ if response:
256
+ await websocket.send(response)
257
+ client.update_activity(1, len(response), 'sent')
258
+
259
+ elif isinstance(message, str):
260
+ # Text message - treat as control message
261
+ try:
262
+ control_msg = json.loads(message)
263
+ await self._handle_control_message(client, control_msg)
264
+ except json.JSONDecodeError:
265
+ logging.warning(f"Invalid control message from {client_id}: {message}")
266
+
267
+ except websockets.exceptions.ConnectionClosed:
268
+ logging.info(f"WebSocket client disconnected: {client_id}")
269
+ except Exception as e:
270
+ logging.error(f"WebSocket client error: {e}")
271
+ self.stats['connection_errors'] += 1
272
+
273
+ finally:
274
+ # Clean up client
275
+ with self.lock:
276
+ if client_id in self.clients:
277
+ self.clients[client_id].is_active = False
278
+ del self.clients[client_id]
279
+
280
+ self.stats['active_clients'] = len(self.clients)
281
+
282
+ async def _handle_control_message(self, client: ClientConnection, message: Dict):
283
+ """Handle control message from client"""
284
+ msg_type = message.get('type')
285
+
286
+ if msg_type == 'ping':
287
+ # Respond to ping
288
+ response = {'type': 'pong', 'timestamp': time.time()}
289
+ await client.websocket.send(json.dumps(response))
290
+
291
+ elif msg_type == 'stats':
292
+ # Send client statistics
293
+ response = {
294
+ 'type': 'stats',
295
+ 'client_stats': client.to_dict(),
296
+ 'bridge_stats': self.get_stats()
297
+ }
298
+ await client.websocket.send(json.dumps(response))
299
+
300
+ elif msg_type == 'config':
301
+ # Handle configuration updates
302
+ config_data = message.get('data', {})
303
+ # Process configuration updates here
304
+ response = {'type': 'config_ack', 'status': 'ok'}
305
+ await client.websocket.send(json.dumps(response))
306
+
307
+ def _handle_tcp_client(self, client_socket: socket.socket, client_address: Tuple[str, int]):
308
+ """Handle TCP client connection"""
309
+ client_id = self._generate_client_id(client_address[0], client_address[1])
310
+
311
+ # Create client connection
312
+ client = ClientConnection(
313
+ client_id=client_id,
314
+ bridge_type=BridgeType.TCP_SOCKET,
315
+ remote_address=client_address[0],
316
+ remote_port=client_address[1],
317
+ socket=client_socket
318
+ )
319
+
320
+ with self.lock:
321
+ if len(self.clients) >= self.max_clients:
322
+ client_socket.close()
323
+ return
324
+
325
+ self.clients[client_id] = client
326
+
327
+ self.stats['total_clients'] += 1
328
+ self.stats['active_clients'] = len(self.clients)
329
+ self.stats['tcp_connections'] += 1
330
+
331
+ logging.info(f"TCP client connected: {client_id} from {client_address}")
332
+
333
+ try:
334
+ client_socket.settimeout(self.client_timeout)
335
+
336
+ while client.is_active:
337
+ try:
338
+ # Read frame length (4 bytes)
339
+ length_data = client_socket.recv(4)
340
+ if not length_data:
341
+ break
342
+
343
+ frame_length = struct.unpack('!I', length_data)[0]
344
+ if frame_length > 65536: # Sanity check
345
+ break
346
+
347
+ # Read frame data
348
+ frame_data = b''
349
+ while len(frame_data) < frame_length:
350
+ chunk = client_socket.recv(frame_length - len(frame_data))
351
+ if not chunk:
352
+ break
353
+ frame_data += chunk
354
+
355
+ if len(frame_data) != frame_length:
356
+ break
357
+
358
+ client.update_activity(1, len(frame_data), 'received')
359
+
360
+ # Process frame
361
+ response = self._process_ethernet_frame(frame_data, client_id)
362
+ if response:
363
+ # Send response with length prefix
364
+ response_length = struct.pack('!I', len(response))
365
+ client_socket.send(response_length + response)
366
+ client.update_activity(1, len(response), 'sent')
367
+
368
+ except socket.timeout:
369
+ continue
370
+ except Exception as e:
371
+ logging.error(f"TCP client error: {e}")
372
+ break
373
+
374
+ except Exception as e:
375
+ logging.error(f"TCP client handler error: {e}")
376
+ self.stats['connection_errors'] += 1
377
+
378
+ finally:
379
+ # Clean up client
380
+ try:
381
+ client_socket.close()
382
+ except:
383
+ pass
384
+
385
+ with self.lock:
386
+ if client_id in self.clients:
387
+ self.clients[client_id].is_active = False
388
+ del self.clients[client_id]
389
+
390
+ self.stats['active_clients'] = len(self.clients)
391
+ logging.info(f"TCP client disconnected: {client_id}")
392
+
393
+ def _tcp_server_loop(self):
394
+ """TCP server loop"""
395
+ try:
396
+ self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
397
+ self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
398
+ self.tcp_server_socket.bind((self.tcp_host, self.tcp_port))
399
+ self.tcp_server_socket.listen(10)
400
+
401
+ logging.info(f"TCP bridge server listening on {self.tcp_host}:{self.tcp_port}")
402
+
403
+ while self.running:
404
+ try:
405
+ client_socket, client_address = self.tcp_server_socket.accept()
406
+
407
+ # Handle client in separate thread
408
+ client_thread = threading.Thread(
409
+ target=self._handle_tcp_client,
410
+ args=(client_socket, client_address),
411
+ daemon=True
412
+ )
413
+ client_thread.start()
414
+
415
+ except socket.error as e:
416
+ if self.running:
417
+ logging.error(f"TCP server error: {e}")
418
+ time.sleep(1)
419
+
420
+ except Exception as e:
421
+ logging.error(f"TCP server loop error: {e}")
422
+
423
+ finally:
424
+ if self.tcp_server_socket:
425
+ self.tcp_server_socket.close()
426
+
427
+ def _cleanup_loop(self):
428
+ """Background cleanup loop"""
429
+ while self.running:
430
+ try:
431
+ current_time = time.time()
432
+ expired_clients = []
433
+
434
+ with self.lock:
435
+ for client_id, client in self.clients.items():
436
+ # Mark inactive clients for removal
437
+ if current_time - client.last_activity > self.client_timeout:
438
+ expired_clients.append(client_id)
439
+
440
+ # Clean up expired clients
441
+ for client_id in expired_clients:
442
+ with self.lock:
443
+ if client_id in self.clients:
444
+ client = self.clients[client_id]
445
+ client.is_active = False
446
+
447
+ # Close connections
448
+ if client.websocket:
449
+ try:
450
+ asyncio.run_coroutine_threadsafe(
451
+ client.websocket.close(),
452
+ self.loop
453
+ )
454
+ except:
455
+ pass
456
+
457
+ if client.socket:
458
+ try:
459
+ client.socket.close()
460
+ except:
461
+ pass
462
+
463
+ del self.clients[client_id]
464
+ logging.info(f"Cleaned up expired client: {client_id}")
465
+
466
+ self.stats['active_clients'] = len(self.clients)
467
+
468
+ time.sleep(30) # Cleanup every 30 seconds
469
+
470
+ except Exception as e:
471
+ logging.error(f"Cleanup loop error: {e}")
472
+ time.sleep(5)
473
+
474
+ def send_packet_to_client(self, client_id: str, packet_data: bytes) -> bool:
475
+ """Send packet to specific client"""
476
+ with self.lock:
477
+ client = self.clients.get(client_id)
478
+
479
+ if not client or not client.is_active:
480
+ return False
481
+
482
+ try:
483
+ if client.bridge_type == BridgeType.WEBSOCKET:
484
+ # Send via WebSocket
485
+ if client.websocket:
486
+ asyncio.run_coroutine_threadsafe(
487
+ client.websocket.send(packet_data),
488
+ self.loop
489
+ )
490
+ client.update_activity(1, len(packet_data), 'sent')
491
+ return True
492
+
493
+ elif client.bridge_type == BridgeType.TCP_SOCKET:
494
+ # Send via TCP socket with length prefix
495
+ if client.socket:
496
+ length_prefix = struct.pack('!I', len(packet_data))
497
+ client.socket.send(length_prefix + packet_data)
498
+ client.update_activity(1, len(packet_data), 'sent')
499
+ return True
500
+
501
+ except Exception as e:
502
+ logging.error(f"Failed to send packet to client {client_id}: {e}")
503
+ # Mark client as inactive
504
+ client.is_active = False
505
+
506
+ return False
507
+
508
+ def broadcast_packet(self, packet_data: bytes, exclude_client: Optional[str] = None) -> int:
509
+ """Broadcast packet to all clients"""
510
+ sent_count = 0
511
+
512
+ with self.lock:
513
+ client_ids = list(self.clients.keys())
514
+
515
+ for client_id in client_ids:
516
+ if client_id != exclude_client:
517
+ if self.send_packet_to_client(client_id, packet_data):
518
+ sent_count += 1
519
+
520
+ return sent_count
521
+
522
+ def get_clients(self) -> Dict[str, Dict]:
523
+ """Get all connected clients"""
524
+ with self.lock:
525
+ return {
526
+ client_id: client.to_dict()
527
+ for client_id, client in self.clients.items()
528
+ }
529
+
530
+ def get_client(self, client_id: str) -> Optional[Dict]:
531
+ """Get specific client"""
532
+ with self.lock:
533
+ client = self.clients.get(client_id)
534
+ return client.to_dict() if client else None
535
+
536
+ def disconnect_client(self, client_id: str) -> bool:
537
+ """Disconnect specific client"""
538
+ with self.lock:
539
+ client = self.clients.get(client_id)
540
+ if not client:
541
+ return False
542
+
543
+ client.is_active = False
544
+
545
+ # Close connection
546
+ if client.websocket:
547
+ try:
548
+ asyncio.run_coroutine_threadsafe(
549
+ client.websocket.close(),
550
+ self.loop
551
+ )
552
+ except:
553
+ pass
554
+
555
+ if client.socket:
556
+ try:
557
+ client.socket.close()
558
+ except:
559
+ pass
560
+
561
+ del self.clients[client_id]
562
+ self.stats['active_clients'] = len(self.clients)
563
+
564
+ return True
565
+
566
+ def get_stats(self) -> Dict:
567
+ """Get bridge statistics"""
568
+ with self.lock:
569
+ stats = self.stats.copy()
570
+ stats['active_clients'] = len(self.clients)
571
+
572
+ return stats
573
+
574
+ def reset_stats(self):
575
+ """Reset bridge statistics"""
576
+ self.stats = {
577
+ 'total_clients': 0,
578
+ 'active_clients': len(self.clients),
579
+ 'packets_processed': 0,
580
+ 'packets_forwarded': 0,
581
+ 'packets_dropped': 0,
582
+ 'bytes_processed': 0,
583
+ 'websocket_connections': 0,
584
+ 'tcp_connections': 0,
585
+ 'connection_errors': 0
586
+ }
587
+
588
+ async def start_websocket_server(self):
589
+ """Start WebSocket server"""
590
+ try:
591
+ self.websocket_server = await websockets.serve(
592
+ self._handle_websocket_client,
593
+ self.websocket_host,
594
+ self.websocket_port,
595
+ max_size=1024*1024, # 1MB max message size
596
+ ping_interval=30,
597
+ ping_timeout=10
598
+ )
599
+
600
+ logging.info(f"WebSocket bridge server started on {self.websocket_host}:{self.websocket_port}")
601
+
602
+ # Keep server running
603
+ await self.websocket_server.wait_closed()
604
+
605
+ except Exception as e:
606
+ logging.error(f"WebSocket server error: {e}")
607
+
608
+ def start(self):
609
+ """Start packet bridge"""
610
+ self.running = True
611
+
612
+ # Start event loop
613
+ self.loop = asyncio.new_event_loop()
614
+ asyncio.set_event_loop(self.loop)
615
+
616
+ # Start WebSocket server in a separate thread
617
+ websocket_thread = threading.Thread(target=self._run_websocket_server_in_thread, daemon=True)
618
+ websocket_thread.start()
619
+
620
+ # Start TCP server in separate thread
621
+ tcp_thread = threading.Thread(target=self._tcp_server_loop, daemon=True)
622
+ tcp_thread.start()
623
+
624
+ # Start cleanup thread
625
+ cleanup_thread = threading.Thread(target=self._cleanup_loop, daemon=True)
626
+ cleanup_thread.start()
627
+
628
+ logging.info("Packet bridge started")
629
+
630
+
631
+
632
+ def stop(self):
633
+ """Stop packet bridge"""
634
+ self.running = False
635
+
636
+ # Close WebSocket server
637
+ if self.websocket_server:
638
+ self.websocket_server.close()
639
+
640
+ # Close TCP server
641
+ if self.tcp_server_socket:
642
+ self.tcp_server_socket.close()
643
+
644
+ # Disconnect all clients
645
+ with self.lock:
646
+ client_ids = list(self.clients.keys())
647
+
648
+ for client_id in client_ids:
649
+ self.disconnect_client(client_id)
650
+
651
+ # Stop event loop
652
+ if self.loop and not self.loop.is_closed():
653
+ self.loop.call_soon_threadsafe(self.loop.stop)
654
+
655
+ logging.info("Packet bridge stopped")
656
+
657
+
658
+
659
+ def _run_websocket_server_in_thread(self):
660
+ """Run the WebSocket server in a separate thread with its own event loop."""
661
+ asyncio.set_event_loop(self.loop)
662
+ self.loop.run_until_complete(self.start_websocket_server())
663
+
664
+
core/session_tracker.py ADDED
@@ -0,0 +1,602 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Session Tracker Module
3
+
4
+ Manages and tracks all network sessions across the virtual ISP stack:
5
+ - Unified session management across all modules
6
+ - Session lifecycle tracking
7
+ - Performance metrics and analytics
8
+ - Session correlation and debugging
9
+ """
10
+
11
+ import time
12
+ import threading
13
+ import uuid
14
+ from typing import Dict, List, Optional, Set, Any, Tuple
15
+ from dataclasses import dataclass, field
16
+ from enum import Enum
17
+ import json
18
+
19
+ from .dhcp_server import DHCPLease
20
+ from .nat_engine import NATSession
21
+ from .tcp_engine import TCPConnection
22
+ from .socket_translator import SocketConnection
23
+
24
+
25
+ class SessionType(Enum):
26
+ DHCP_LEASE = "DHCP_LEASE"
27
+ NAT_SESSION = "NAT_SESSION"
28
+ TCP_CONNECTION = "TCP_CONNECTION"
29
+ SOCKET_CONNECTION = "SOCKET_CONNECTION"
30
+ BRIDGE_CLIENT = "BRIDGE_CLIENT"
31
+
32
+
33
+ class SessionState(Enum):
34
+ INITIALIZING = "INITIALIZING"
35
+ ACTIVE = "ACTIVE"
36
+ IDLE = "IDLE"
37
+ CLOSING = "CLOSING"
38
+ CLOSED = "CLOSED"
39
+ ERROR = "ERROR"
40
+
41
+
42
+ @dataclass
43
+ class SessionMetrics:
44
+ """Session performance metrics"""
45
+ bytes_in: int = 0
46
+ bytes_out: int = 0
47
+ packets_in: int = 0
48
+ packets_out: int = 0
49
+ errors: int = 0
50
+ retransmits: int = 0
51
+ rtt_samples: List[float] = field(default_factory=list)
52
+
53
+ @property
54
+ def total_bytes(self) -> int:
55
+ return self.bytes_in + self.bytes_out
56
+
57
+ @property
58
+ def total_packets(self) -> int:
59
+ return self.packets_in + self.packets_out
60
+
61
+ @property
62
+ def average_rtt(self) -> float:
63
+ return sum(self.rtt_samples) / len(self.rtt_samples) if self.rtt_samples else 0.0
64
+
65
+ def update_bytes(self, bytes_in: int = 0, bytes_out: int = 0):
66
+ """Update byte counters"""
67
+ self.bytes_in += bytes_in
68
+ self.bytes_out += bytes_out
69
+
70
+ def update_packets(self, packets_in: int = 0, packets_out: int = 0):
71
+ """Update packet counters"""
72
+ self.packets_in += packets_in
73
+ self.packets_out += packets_out
74
+
75
+ def add_rtt_sample(self, rtt: float):
76
+ """Add RTT sample"""
77
+ self.rtt_samples.append(rtt)
78
+ # Keep only last 100 samples
79
+ if len(self.rtt_samples) > 100:
80
+ self.rtt_samples = self.rtt_samples[-100:]
81
+
82
+ def to_dict(self) -> Dict:
83
+ """Convert to dictionary"""
84
+ return {
85
+ 'bytes_in': self.bytes_in,
86
+ 'bytes_out': self.bytes_out,
87
+ 'packets_in': self.packets_in,
88
+ 'packets_out': self.packets_out,
89
+ 'total_bytes': self.total_bytes,
90
+ 'total_packets': self.total_packets,
91
+ 'errors': self.errors,
92
+ 'retransmits': self.retransmits,
93
+ 'average_rtt': self.average_rtt,
94
+ 'rtt_samples_count': len(self.rtt_samples)
95
+ }
96
+
97
+
98
+ @dataclass
99
+ class UnifiedSession:
100
+ """Unified session representation"""
101
+ session_id: str
102
+ session_type: SessionType
103
+ state: SessionState
104
+ created_time: float
105
+ last_activity: float
106
+
107
+ # Session identifiers
108
+ virtual_ip: Optional[str] = None
109
+ virtual_port: Optional[int] = None
110
+ real_ip: Optional[str] = None
111
+ real_port: Optional[int] = None
112
+ protocol: Optional[str] = None
113
+
114
+ # Related sessions (for correlation)
115
+ related_sessions: Set[str] = field(default_factory=set)
116
+ parent_session: Optional[str] = None
117
+ child_sessions: Set[str] = field(default_factory=set)
118
+
119
+ # Metrics
120
+ metrics: SessionMetrics = field(default_factory=SessionMetrics)
121
+
122
+ # Additional data
123
+ metadata: Dict[str, Any] = field(default_factory=dict)
124
+
125
+ def __post_init__(self):
126
+ if not self.session_id:
127
+ self.session_id = str(uuid.uuid4())
128
+ if self.created_time == 0:
129
+ self.created_time = time.time()
130
+ if self.last_activity == 0:
131
+ self.last_activity = time.time()
132
+
133
+ def update_activity(self):
134
+ """Update last activity timestamp"""
135
+ self.last_activity = time.time()
136
+
137
+ def add_related_session(self, session_id: str):
138
+ """Add related session"""
139
+ self.related_sessions.add(session_id)
140
+
141
+ def add_child_session(self, session_id: str):
142
+ """Add child session"""
143
+ self.child_sessions.add(session_id)
144
+
145
+ def set_parent_session(self, session_id: str):
146
+ """Set parent session"""
147
+ self.parent_session = session_id
148
+
149
+ @property
150
+ def duration(self) -> float:
151
+ """Get session duration in seconds"""
152
+ return time.time() - self.created_time
153
+
154
+ @property
155
+ def idle_time(self) -> float:
156
+ """Get idle time in seconds"""
157
+ return time.time() - self.last_activity
158
+
159
+ def to_dict(self) -> Dict:
160
+ """Convert to dictionary"""
161
+ return {
162
+ 'session_id': self.session_id,
163
+ 'session_type': self.session_type.value,
164
+ 'state': self.state.value,
165
+ 'created_time': self.created_time,
166
+ 'last_activity': self.last_activity,
167
+ 'duration': self.duration,
168
+ 'idle_time': self.idle_time,
169
+ 'virtual_ip': self.virtual_ip,
170
+ 'virtual_port': self.virtual_port,
171
+ 'real_ip': self.real_ip,
172
+ 'real_port': self.real_port,
173
+ 'protocol': self.protocol,
174
+ 'related_sessions': list(self.related_sessions),
175
+ 'parent_session': self.parent_session,
176
+ 'child_sessions': list(self.child_sessions),
177
+ 'metrics': self.metrics.to_dict(),
178
+ 'metadata': self.metadata
179
+ }
180
+
181
+
182
+ class SessionTracker:
183
+ """Unified session tracker"""
184
+
185
+ def __init__(self, config: Dict):
186
+ self.config = config
187
+ self.sessions: Dict[str, UnifiedSession] = {}
188
+ self.session_index: Dict[Tuple[str, str], Set[str]] = {} # (type, key) -> session_ids
189
+ self.lock = threading.Lock()
190
+
191
+ # Configuration
192
+ self.max_sessions = config.get('max_sessions', 10000)
193
+ self.session_timeout = config.get('session_timeout', 3600)
194
+ self.cleanup_interval = config.get('cleanup_interval', 300)
195
+ self.metrics_retention = config.get('metrics_retention', 86400) # 24 hours
196
+
197
+ # Statistics
198
+ self.stats = {
199
+ 'total_sessions': 0,
200
+ 'active_sessions': 0,
201
+ 'expired_sessions': 0,
202
+ 'session_types': {t.value: 0 for t in SessionType},
203
+ 'session_states': {s.value: 0 for s in SessionState},
204
+ 'cleanup_runs': 0,
205
+ 'correlations_created': 0
206
+ }
207
+
208
+ # Background tasks
209
+ self.running = False
210
+ self.cleanup_thread = None
211
+
212
+ def _generate_session_key(self, session_type: SessionType, **kwargs) -> str:
213
+ """Generate session key for indexing"""
214
+ if session_type == SessionType.DHCP_LEASE:
215
+ return f"dhcp_{kwargs.get('mac_address', 'unknown')}"
216
+ elif session_type == SessionType.NAT_SESSION:
217
+ return f"nat_{kwargs.get('virtual_ip', '')}_{kwargs.get('virtual_port', 0)}_{kwargs.get('protocol', '')}"
218
+ elif session_type == SessionType.TCP_CONNECTION:
219
+ return f"tcp_{kwargs.get('local_ip', '')}_{kwargs.get('local_port', 0)}_{kwargs.get('remote_ip', '')}_{kwargs.get('remote_port', 0)}"
220
+ elif session_type == SessionType.SOCKET_CONNECTION:
221
+ return f"socket_{kwargs.get('connection_id', 'unknown')}"
222
+ elif session_type == SessionType.BRIDGE_CLIENT:
223
+ return f"bridge_{kwargs.get('client_id', 'unknown')}"
224
+ else:
225
+ return f"unknown_{time.time()}"
226
+
227
+ def _add_to_index(self, session: UnifiedSession):
228
+ """Add session to search index"""
229
+ # Index by type
230
+ type_key = (session.session_type.value, 'all')
231
+ if type_key not in self.session_index:
232
+ self.session_index[type_key] = set()
233
+ self.session_index[type_key].add(session.session_id)
234
+
235
+ # Index by IP addresses
236
+ if session.virtual_ip:
237
+ ip_key = ('virtual_ip', session.virtual_ip)
238
+ if ip_key not in self.session_index:
239
+ self.session_index[ip_key] = set()
240
+ self.session_index[ip_key].add(session.session_id)
241
+
242
+ if session.real_ip:
243
+ ip_key = ('real_ip', session.real_ip)
244
+ if ip_key not in self.session_index:
245
+ self.session_index[ip_key] = set()
246
+ self.session_index[ip_key].add(session.session_id)
247
+
248
+ # Index by protocol
249
+ if session.protocol:
250
+ proto_key = ('protocol', session.protocol)
251
+ if proto_key not in self.session_index:
252
+ self.session_index[proto_key] = set()
253
+ self.session_index[proto_key].add(session.session_id)
254
+
255
+ def _remove_from_index(self, session: UnifiedSession):
256
+ """Remove session from search index"""
257
+ for key, session_set in self.session_index.items():
258
+ session_set.discard(session.session_id)
259
+
260
+ def create_session(self, session_type: SessionType, **kwargs) -> str:
261
+ """Create new session"""
262
+ with self.lock:
263
+ # Check session limit
264
+ if len(self.sessions) >= self.max_sessions:
265
+ # Remove oldest expired session
266
+ self._cleanup_expired_sessions()
267
+ if len(self.sessions) >= self.max_sessions:
268
+ return None
269
+
270
+ # Create session
271
+ session = UnifiedSession(
272
+ session_id=kwargs.get('session_id', str(uuid.uuid4())),
273
+ session_type=session_type,
274
+ state=SessionState.INITIALIZING,
275
+ virtual_ip=kwargs.get('virtual_ip'),
276
+ virtual_port=kwargs.get('virtual_port'),
277
+ real_ip=kwargs.get('real_ip'),
278
+ real_port=kwargs.get('real_port'),
279
+ protocol=kwargs.get('protocol'),
280
+ metadata=kwargs.get('metadata', {})
281
+ )
282
+
283
+ # Add to sessions
284
+ self.sessions[session.session_id] = session
285
+ self._add_to_index(session)
286
+
287
+ # Update statistics
288
+ self.stats['total_sessions'] += 1
289
+ self.stats['active_sessions'] = len(self.sessions)
290
+ self.stats['session_types'][session_type.value] += 1
291
+ self.stats['session_states'][SessionState.INITIALIZING.value] += 1
292
+
293
+ return session.session_id
294
+
295
+ def update_session(self, session_id: str, **kwargs) -> bool:
296
+ """Update session"""
297
+ with self.lock:
298
+ session = self.sessions.get(session_id)
299
+ if not session:
300
+ return False
301
+
302
+ # Update fields
303
+ old_state = session.state
304
+
305
+ for key, value in kwargs.items():
306
+ if hasattr(session, key):
307
+ setattr(session, key, value)
308
+
309
+ session.update_activity()
310
+
311
+ # Update state statistics
312
+ if 'state' in kwargs and kwargs['state'] != old_state:
313
+ self.stats['session_states'][old_state.value] -= 1
314
+ self.stats['session_states'][kwargs['state'].value] += 1
315
+
316
+ return True
317
+
318
+ def close_session(self, session_id: str, reason: str = "") -> bool:
319
+ """Close session"""
320
+ with self.lock:
321
+ session = self.sessions.get(session_id)
322
+ if not session:
323
+ return False
324
+
325
+ old_state = session.state
326
+ session.state = SessionState.CLOSED
327
+ session.update_activity()
328
+
329
+ if reason:
330
+ session.metadata['close_reason'] = reason
331
+
332
+ # Update statistics
333
+ self.stats['session_states'][old_state.value] -= 1
334
+ self.stats['session_states'][SessionState.CLOSED.value] += 1
335
+
336
+ return True
337
+
338
+ def remove_session(self, session_id: str) -> bool:
339
+ """Remove session completely"""
340
+ with self.lock:
341
+ session = self.sessions.get(session_id)
342
+ if not session:
343
+ return False
344
+
345
+ # Remove from index
346
+ self._remove_from_index(session)
347
+
348
+ # Remove from sessions
349
+ del self.sessions[session_id]
350
+
351
+ # Update statistics
352
+ self.stats['active_sessions'] = len(self.sessions)
353
+ self.stats['session_types'][session.session_type.value] -= 1
354
+ self.stats['session_states'][session.state.value] -= 1
355
+
356
+ return True
357
+
358
+ def get_session(self, session_id: str) -> Optional[UnifiedSession]:
359
+ """Get session by ID"""
360
+ with self.lock:
361
+ return self.sessions.get(session_id)
362
+
363
+ def find_sessions(self, **criteria) -> List[UnifiedSession]:
364
+ """Find sessions by criteria"""
365
+ with self.lock:
366
+ matching_sessions = []
367
+
368
+ # Use index if possible
369
+ if 'session_type' in criteria:
370
+ type_key = (criteria['session_type'].value if isinstance(criteria['session_type'], SessionType) else criteria['session_type'], 'all')
371
+ candidate_ids = self.session_index.get(type_key, set())
372
+ elif 'virtual_ip' in criteria:
373
+ ip_key = ('virtual_ip', criteria['virtual_ip'])
374
+ candidate_ids = self.session_index.get(ip_key, set())
375
+ elif 'real_ip' in criteria:
376
+ ip_key = ('real_ip', criteria['real_ip'])
377
+ candidate_ids = self.session_index.get(ip_key, set())
378
+ elif 'protocol' in criteria:
379
+ proto_key = ('protocol', criteria['protocol'])
380
+ candidate_ids = self.session_index.get(proto_key, set())
381
+ else:
382
+ candidate_ids = set(self.sessions.keys())
383
+
384
+ # Filter candidates
385
+ for session_id in candidate_ids:
386
+ session = self.sessions.get(session_id)
387
+ if not session:
388
+ continue
389
+
390
+ match = True
391
+ for key, value in criteria.items():
392
+ if hasattr(session, key):
393
+ session_value = getattr(session, key)
394
+ if isinstance(value, (SessionType, SessionState)):
395
+ if session_value != value:
396
+ match = False
397
+ break
398
+ elif session_value != value:
399
+ match = False
400
+ break
401
+ else:
402
+ match = False
403
+ break
404
+
405
+ if match:
406
+ matching_sessions.append(session)
407
+
408
+ return matching_sessions
409
+
410
+ def correlate_sessions(self, session_id1: str, session_id2: str, relationship: str = 'related') -> bool:
411
+ """Create correlation between sessions"""
412
+ with self.lock:
413
+ session1 = self.sessions.get(session_id1)
414
+ session2 = self.sessions.get(session_id2)
415
+
416
+ if not session1 or not session2:
417
+ return False
418
+
419
+ if relationship == 'parent_child':
420
+ session1.add_child_session(session_id2)
421
+ session2.set_parent_session(session_id1)
422
+ else:
423
+ session1.add_related_session(session_id2)
424
+ session2.add_related_session(session_id1)
425
+
426
+ self.stats['correlations_created'] += 1
427
+ return True
428
+
429
+ def update_metrics(self, session_id: str, **metrics) -> bool:
430
+ """Update session metrics"""
431
+ with self.lock:
432
+ session = self.sessions.get(session_id)
433
+ if not session:
434
+ return False
435
+
436
+ session.update_activity()
437
+
438
+ # Update metrics
439
+ if 'bytes_in' in metrics or 'bytes_out' in metrics:
440
+ session.metrics.update_bytes(
441
+ metrics.get('bytes_in', 0),
442
+ metrics.get('bytes_out', 0)
443
+ )
444
+
445
+ if 'packets_in' in metrics or 'packets_out' in metrics:
446
+ session.metrics.update_packets(
447
+ metrics.get('packets_in', 0),
448
+ metrics.get('packets_out', 0)
449
+ )
450
+
451
+ if 'rtt' in metrics:
452
+ session.metrics.add_rtt_sample(metrics['rtt'])
453
+
454
+ if 'errors' in metrics:
455
+ session.metrics.errors += metrics['errors']
456
+
457
+ if 'retransmits' in metrics:
458
+ session.metrics.retransmits += metrics['retransmits']
459
+
460
+ return True
461
+
462
+ def _cleanup_expired_sessions(self):
463
+ """Clean up expired sessions"""
464
+ current_time = time.time()
465
+ expired_sessions = []
466
+
467
+ for session_id, session in self.sessions.items():
468
+ # Check if session is expired
469
+ if (session.state == SessionState.CLOSED and
470
+ current_time - session.last_activity > self.cleanup_interval):
471
+ expired_sessions.append(session_id)
472
+ elif (session.state != SessionState.CLOSED and
473
+ current_time - session.last_activity > self.session_timeout):
474
+ expired_sessions.append(session_id)
475
+
476
+ # Remove expired sessions
477
+ for session_id in expired_sessions:
478
+ self.remove_session(session_id)
479
+ self.stats['expired_sessions'] += 1
480
+
481
+ def _cleanup_loop(self):
482
+ """Background cleanup loop"""
483
+ while self.running:
484
+ try:
485
+ with self.lock:
486
+ self._cleanup_expired_sessions()
487
+ self.stats['cleanup_runs'] += 1
488
+
489
+ time.sleep(self.cleanup_interval)
490
+
491
+ except Exception as e:
492
+ print(f"Session tracker cleanup error: {e}")
493
+ time.sleep(60)
494
+
495
+ def get_sessions(self, limit: int = 100, offset: int = 0, **filters) -> List[Dict]:
496
+ """Get sessions with pagination and filtering"""
497
+ with self.lock:
498
+ if filters:
499
+ sessions = self.find_sessions(**filters)
500
+ else:
501
+ sessions = list(self.sessions.values())
502
+
503
+ # Sort by last activity (most recent first)
504
+ sessions.sort(key=lambda s: s.last_activity, reverse=True)
505
+
506
+ # Apply pagination
507
+ paginated_sessions = sessions[offset:offset + limit]
508
+
509
+ return [session.to_dict() for session in paginated_sessions]
510
+
511
+ def get_session_summary(self) -> Dict:
512
+ """Get session summary statistics"""
513
+ with self.lock:
514
+ summary = {
515
+ 'total_sessions': len(self.sessions),
516
+ 'by_type': {},
517
+ 'by_state': {},
518
+ 'by_protocol': {},
519
+ 'active_sessions_by_age': {
520
+ 'last_hour': 0,
521
+ 'last_day': 0,
522
+ 'older': 0
523
+ }
524
+ }
525
+
526
+ current_time = time.time()
527
+ hour_ago = current_time - 3600
528
+ day_ago = current_time - 86400
529
+
530
+ for session in self.sessions.values():
531
+ # Count by type
532
+ session_type = session.session_type.value
533
+ summary['by_type'][session_type] = summary['by_type'].get(session_type, 0) + 1
534
+
535
+ # Count by state
536
+ session_state = session.state.value
537
+ summary['by_state'][session_state] = summary['by_state'].get(session_state, 0) + 1
538
+
539
+ # Count by protocol
540
+ if session.protocol:
541
+ summary['by_protocol'][session.protocol] = summary['by_protocol'].get(session.protocol, 0) + 1
542
+
543
+ # Count by age
544
+ if session.last_activity > hour_ago:
545
+ summary['active_sessions_by_age']['last_hour'] += 1
546
+ elif session.last_activity > day_ago:
547
+ summary['active_sessions_by_age']['last_day'] += 1
548
+ else:
549
+ summary['active_sessions_by_age']['older'] += 1
550
+
551
+ return summary
552
+
553
+ def get_stats(self) -> Dict:
554
+ """Get tracker statistics"""
555
+ with self.lock:
556
+ stats = self.stats.copy()
557
+ stats['active_sessions'] = len(self.sessions)
558
+
559
+ return stats
560
+
561
+ def reset_stats(self):
562
+ """Reset statistics"""
563
+ self.stats = {
564
+ 'total_sessions': len(self.sessions),
565
+ 'active_sessions': len(self.sessions),
566
+ 'expired_sessions': 0,
567
+ 'session_types': {t.value: 0 for t in SessionType},
568
+ 'session_states': {s.value: 0 for s in SessionState},
569
+ 'cleanup_runs': 0,
570
+ 'correlations_created': 0
571
+ }
572
+
573
+ # Recalculate current counts
574
+ with self.lock:
575
+ for session in self.sessions.values():
576
+ self.stats['session_types'][session.session_type.value] += 1
577
+ self.stats['session_states'][session.state.value] += 1
578
+
579
+ def export_sessions(self, format: str = 'json') -> str:
580
+ """Export sessions data"""
581
+ with self.lock:
582
+ sessions_data = [session.to_dict() for session in self.sessions.values()]
583
+
584
+ if format == 'json':
585
+ return json.dumps(sessions_data, indent=2, default=str)
586
+ else:
587
+ raise ValueError(f"Unsupported export format: {format}")
588
+
589
+ def start(self):
590
+ """Start session tracker"""
591
+ self.running = True
592
+ self.cleanup_thread = threading.Thread(target=self._cleanup_loop, daemon=True)
593
+ self.cleanup_thread.start()
594
+ print("Session tracker started")
595
+
596
+ def stop(self):
597
+ """Stop session tracker"""
598
+ self.running = False
599
+ if self.cleanup_thread:
600
+ self.cleanup_thread.join()
601
+ print("Session tracker stopped")
602
+
core/socket_translator.py ADDED
@@ -0,0 +1,653 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Socket Translator Module
3
+
4
+ Bridges virtual connections to real host sockets:
5
+ - Map virtual connections to host sockets/HTTP clients
6
+ - Bidirectional data streaming
7
+ - Connection lifecycle management
8
+ - Protocol translation (TCP/UDP to host sockets)
9
+ """
10
+
11
+ import socket
12
+ import threading
13
+ import time
14
+ import asyncio
15
+ import aiohttp
16
+ import ssl
17
+ from typing import Dict, Optional, Callable, Tuple, Any
18
+ from dataclasses import dataclass
19
+ from enum import Enum
20
+ import urllib.parse
21
+ import json
22
+
23
+ from .tcp_engine import TCPConnection
24
+
25
+
26
+ class ConnectionType(Enum):
27
+ TCP_SOCKET = "TCP_SOCKET"
28
+ UDP_SOCKET = "UDP_SOCKET"
29
+ HTTP_CLIENT = "HTTP_CLIENT"
30
+ HTTPS_CLIENT = "HTTPS_CLIENT"
31
+
32
+
33
+ @dataclass
34
+ class SocketConnection:
35
+ """Represents a socket connection"""
36
+ connection_id: str
37
+ connection_type: ConnectionType
38
+ virtual_connection: Optional[TCPConnection]
39
+ host_socket: Optional[socket.socket]
40
+ remote_host: str
41
+ remote_port: int
42
+ created_time: float
43
+ last_activity: float
44
+ bytes_sent: int = 0
45
+ bytes_received: int = 0
46
+ is_connected: bool = False
47
+ error_count: int = 0
48
+
49
+ def update_activity(self, bytes_transferred: int = 0, direction: str = 'sent'):
50
+ """Update connection activity"""
51
+ self.last_activity = time.time()
52
+ if direction == 'sent':
53
+ self.bytes_sent += bytes_transferred
54
+ else:
55
+ self.bytes_received += bytes_transferred
56
+
57
+ def to_dict(self) -> Dict:
58
+ """Convert to dictionary"""
59
+ return {
60
+ 'connection_id': self.connection_id,
61
+ 'connection_type': self.connection_type.value,
62
+ 'remote_host': self.remote_host,
63
+ 'remote_port': self.remote_port,
64
+ 'created_time': self.created_time,
65
+ 'last_activity': self.last_activity,
66
+ 'bytes_sent': self.bytes_sent,
67
+ 'bytes_received': self.bytes_received,
68
+ 'is_connected': self.is_connected,
69
+ 'error_count': self.error_count,
70
+ 'duration': time.time() - self.created_time
71
+ }
72
+
73
+
74
+ class HTTPRequest:
75
+ """Represents an HTTP request"""
76
+
77
+ def __init__(self, method: str = 'GET', path: str = '/', headers: Dict[str, str] = None, body: bytes = b''):
78
+ self.method = method.upper()
79
+ self.path = path
80
+ self.headers = headers or {}
81
+ self.body = body
82
+ self.version = 'HTTP/1.1'
83
+
84
+ @classmethod
85
+ def parse(cls, data: bytes) -> Optional['HTTPRequest']:
86
+ """Parse HTTP request from raw data"""
87
+ try:
88
+ lines = data.decode('utf-8', errors='ignore').split('\r\n')
89
+ if not lines:
90
+ return None
91
+
92
+ # Parse request line
93
+ request_line = lines[0].split(' ')
94
+ if len(request_line) < 3:
95
+ return None
96
+
97
+ method, path, version = request_line[0], request_line[1], request_line[2]
98
+
99
+ # Parse headers
100
+ headers = {}
101
+ body_start = 1
102
+ for i, line in enumerate(lines[1:], 1):
103
+ if line == '':
104
+ body_start = i + 1
105
+ break
106
+ if ':' in line:
107
+ key, value = line.split(':', 1)
108
+ headers[key.strip().lower()] = value.strip()
109
+
110
+ # Parse body
111
+ body_lines = lines[body_start:]
112
+ body = '\r\n'.join(body_lines).encode('utf-8')
113
+
114
+ return cls(method, path, headers, body)
115
+
116
+ except Exception:
117
+ return None
118
+
119
+ def to_bytes(self) -> bytes:
120
+ """Convert to raw HTTP request"""
121
+ request_line = f"{self.method} {self.path} {self.version}\r\n"
122
+
123
+ # Add default headers
124
+ if 'host' not in self.headers:
125
+ self.headers['host'] = 'localhost'
126
+ if 'user-agent' not in self.headers:
127
+ self.headers['user-agent'] = 'VirtualISP/1.0'
128
+ if self.body and 'content-length' not in self.headers:
129
+ self.headers['content-length'] = str(len(self.body))
130
+
131
+ # Build headers
132
+ header_lines = []
133
+ for key, value in self.headers.items():
134
+ header_lines.append(f"{key}: {value}\r\n")
135
+
136
+ # Combine all parts
137
+ request_data = request_line + ''.join(header_lines) + '\r\n'
138
+ return request_data.encode('utf-8') + self.body
139
+
140
+
141
+ class HTTPResponse:
142
+ """Represents an HTTP response"""
143
+
144
+ def __init__(self, status_code: int = 200, reason: str = 'OK', headers: Dict[str, str] = None, body: bytes = b''):
145
+ self.status_code = status_code
146
+ self.reason = reason
147
+ self.headers = headers or {}
148
+ self.body = body
149
+ self.version = 'HTTP/1.1'
150
+
151
+ @classmethod
152
+ def parse(cls, data: bytes) -> Optional['HTTPResponse']:
153
+ """Parse HTTP response from raw data"""
154
+ try:
155
+ lines = data.decode('utf-8', errors='ignore').split('\r\n')
156
+ if not lines:
157
+ return None
158
+
159
+ # Parse status line
160
+ status_line = lines[0].split(' ', 2)
161
+ if len(status_line) < 3:
162
+ return None
163
+
164
+ version, status_code, reason = status_line[0], int(status_line[1]), status_line[2]
165
+
166
+ # Parse headers
167
+ headers = {}
168
+ body_start = 1
169
+ for i, line in enumerate(lines[1:], 1):
170
+ if line == '':
171
+ body_start = i + 1
172
+ break
173
+ if ':' in line:
174
+ key, value = line.split(':', 1)
175
+ headers[key.strip().lower()] = value.strip()
176
+
177
+ # Parse body
178
+ body_lines = lines[body_start:]
179
+ body = '\r\n'.join(body_lines).encode('utf-8')
180
+
181
+ return cls(status_code, reason, headers, body)
182
+
183
+ except Exception:
184
+ return None
185
+
186
+ def to_bytes(self) -> bytes:
187
+ """Convert to raw HTTP response"""
188
+ status_line = f"{self.version} {self.status_code} {self.reason}\r\n"
189
+
190
+ # Add default headers
191
+ if 'content-length' not in self.headers and self.body:
192
+ self.headers['content-length'] = str(len(self.body))
193
+ if 'server' not in self.headers:
194
+ self.headers['server'] = 'VirtualISP/1.0'
195
+
196
+ # Build headers
197
+ header_lines = []
198
+ for key, value in self.headers.items():
199
+ header_lines.append(f"{key}: {value}\r\n")
200
+
201
+ # Combine all parts
202
+ response_data = status_line + ''.join(header_lines) + '\r\n'
203
+ return response_data.encode('utf-8') + self.body
204
+
205
+
206
+ class SocketTranslator:
207
+ """Socket translator implementation"""
208
+
209
+ def __init__(self, config: Dict):
210
+ self.config = config
211
+ self.connections: Dict[str, SocketConnection] = {}
212
+ self.lock = threading.Lock()
213
+
214
+ # Configuration
215
+ self.connect_timeout = config.get('connect_timeout', 10)
216
+ self.read_timeout = config.get('read_timeout', 30)
217
+ self.max_connections = config.get('max_connections', 1000)
218
+ self.buffer_size = config.get('buffer_size', 8192)
219
+
220
+ # HTTP client session
221
+ self.http_session = None
222
+ self.loop = None
223
+
224
+ # Statistics
225
+ self.stats = {
226
+ 'total_connections': 0,
227
+ 'active_connections': 0,
228
+ 'failed_connections': 0,
229
+ 'bytes_transferred': 0,
230
+ 'http_requests': 0,
231
+ 'tcp_connections': 0,
232
+ 'udp_connections': 0
233
+ }
234
+
235
+ # Background tasks
236
+ self.running = False
237
+ self.cleanup_thread = None
238
+
239
+ async def _init_http_session(self):
240
+ """Initialize HTTP client session"""
241
+ connector = aiohttp.TCPConnector(
242
+ limit=100,
243
+ limit_per_host=10,
244
+ ttl_dns_cache=300,
245
+ use_dns_cache=True,
246
+ )
247
+
248
+ timeout = aiohttp.ClientTimeout(
249
+ total=self.read_timeout,
250
+ connect=self.connect_timeout
251
+ )
252
+
253
+ self.http_session = aiohttp.ClientSession(
254
+ connector=connector,
255
+ timeout=timeout,
256
+ headers={'User-Agent': 'VirtualISP/1.0'}
257
+ )
258
+
259
+ def _is_http_request(self, data: bytes) -> bool:
260
+ """Check if data looks like an HTTP request"""
261
+ try:
262
+ first_line = data.split(b'\r\n')[0].decode('utf-8', errors='ignore')
263
+ methods = ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS', 'PATCH', 'TRACE']
264
+ return any(first_line.startswith(method + ' ') for method in methods)
265
+ except:
266
+ return False
267
+
268
+ def _determine_connection_type(self, remote_host: str, remote_port: int, data: bytes = b'') -> ConnectionType:
269
+ """Determine the appropriate connection type"""
270
+ # Check for HTTP/HTTPS based on port and data
271
+ if remote_port == 80 or (data and self._is_http_request(data)):
272
+ return ConnectionType.HTTP_CLIENT
273
+ elif remote_port == 443:
274
+ return ConnectionType.HTTPS_CLIENT
275
+ else:
276
+ return ConnectionType.TCP_SOCKET
277
+
278
+ def create_connection(self, virtual_conn: TCPConnection, remote_host: str, remote_port: int,
279
+ initial_data: bytes = b'') -> Optional[SocketConnection]:
280
+ """Create a new socket connection"""
281
+ connection_id = f"{virtual_conn.connection_id}->{remote_host}:{remote_port}"
282
+
283
+ # Check connection limit
284
+ with self.lock:
285
+ if len(self.connections) >= self.max_connections:
286
+ return None
287
+
288
+ # Determine connection type
289
+ conn_type = self._determine_connection_type(remote_host, remote_port, initial_data)
290
+
291
+ # Create socket connection
292
+ socket_conn = SocketConnection(
293
+ connection_id=connection_id,
294
+ connection_type=conn_type,
295
+ virtual_connection=virtual_conn,
296
+ host_socket=None,
297
+ remote_host=remote_host,
298
+ remote_port=remote_port,
299
+ created_time=time.time(),
300
+ last_activity=time.time()
301
+ )
302
+
303
+ with self.lock:
304
+ self.connections[connection_id] = socket_conn
305
+
306
+ # Establish connection based on type
307
+ if conn_type in [ConnectionType.HTTP_CLIENT, ConnectionType.HTTPS_CLIENT]:
308
+ success = self._create_http_connection(socket_conn, initial_data)
309
+ else:
310
+ success = self._create_tcp_connection(socket_conn, initial_data)
311
+
312
+ if success:
313
+ self.stats['total_connections'] += 1
314
+ self.stats['active_connections'] = len(self.connections)
315
+
316
+ if conn_type in [ConnectionType.HTTP_CLIENT, ConnectionType.HTTPS_CLIENT]:
317
+ self.stats['http_requests'] += 1
318
+ else:
319
+ self.stats['tcp_connections'] += 1
320
+ else:
321
+ self.stats['failed_connections'] += 1
322
+ with self.lock:
323
+ if connection_id in self.connections:
324
+ del self.connections[connection_id]
325
+ return None
326
+
327
+ return socket_conn
328
+
329
+ def _create_tcp_connection(self, socket_conn: SocketConnection, initial_data: bytes) -> bool:
330
+ """Create TCP socket connection"""
331
+ try:
332
+ # Create socket
333
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
334
+ sock.settimeout(self.connect_timeout)
335
+
336
+ # Connect
337
+ sock.connect((socket_conn.remote_host, socket_conn.remote_port))
338
+ sock.settimeout(self.read_timeout)
339
+
340
+ socket_conn.host_socket = sock
341
+ socket_conn.is_connected = True
342
+
343
+ # Send initial data if any
344
+ if initial_data:
345
+ sock.send(initial_data)
346
+ socket_conn.update_activity(len(initial_data), 'sent')
347
+
348
+ # Start background thread for receiving data
349
+ thread = threading.Thread(
350
+ target=self._tcp_receive_loop,
351
+ args=(socket_conn,),
352
+ daemon=True
353
+ )
354
+ thread.start()
355
+
356
+ return True
357
+
358
+ except Exception as e:
359
+ print(f"Failed to create TCP connection to {socket_conn.remote_host}:{socket_conn.remote_port}: {e}")
360
+ socket_conn.error_count += 1
361
+ return False
362
+
363
+ def _create_http_connection(self, socket_conn: SocketConnection, initial_data: bytes) -> bool:
364
+ """Create HTTP connection"""
365
+ try:
366
+ # Parse HTTP request
367
+ http_request = HTTPRequest.parse(initial_data)
368
+ if not http_request:
369
+ return False
370
+
371
+ # Set host header
372
+ http_request.headers['host'] = socket_conn.remote_host
373
+
374
+ # Start async HTTP request
375
+ if self.loop and not self.loop.is_closed():
376
+ asyncio.run_coroutine_threadsafe(
377
+ self._handle_http_request(socket_conn, http_request),
378
+ self.loop
379
+ )
380
+ else:
381
+ # Fallback to sync HTTP handling
382
+ return self._handle_http_request_sync(socket_conn, http_request)
383
+
384
+ return True
385
+
386
+ except Exception as e:
387
+ print(f"Failed to create HTTP connection to {socket_conn.remote_host}:{socket_conn.remote_port}: {e}")
388
+ socket_conn.error_count += 1
389
+ return False
390
+
391
+ async def _handle_http_request(self, socket_conn: SocketConnection, http_request: HTTPRequest):
392
+ """Handle HTTP request asynchronously"""
393
+ try:
394
+ if not self.http_session:
395
+ await self._init_http_session()
396
+
397
+ # Build URL
398
+ scheme = 'https' if socket_conn.connection_type == ConnectionType.HTTPS_CLIENT else 'http'
399
+ url = f"{scheme}://{socket_conn.remote_host}:{socket_conn.remote_port}{http_request.path}"
400
+
401
+ # Make request
402
+ async with self.http_session.request(
403
+ method=http_request.method,
404
+ url=url,
405
+ headers=http_request.headers,
406
+ data=http_request.body
407
+ ) as response:
408
+ # Read response
409
+ response_body = await response.read()
410
+
411
+ # Create HTTP response
412
+ http_response = HTTPResponse(
413
+ status_code=response.status,
414
+ reason=response.reason or 'OK',
415
+ headers=dict(response.headers),
416
+ body=response_body
417
+ )
418
+
419
+ # Send response back to virtual connection
420
+ response_data = http_response.to_bytes()
421
+ if socket_conn.virtual_connection and socket_conn.virtual_connection.on_data_received:
422
+ socket_conn.virtual_connection.on_data_received(response_data)
423
+
424
+ socket_conn.update_activity(len(response_data), 'received')
425
+ self.stats['bytes_transferred'] += len(response_data)
426
+
427
+ except Exception as e:
428
+ print(f"HTTP request failed: {e}")
429
+ socket_conn.error_count += 1
430
+
431
+ # Send error response
432
+ error_response = HTTPResponse(
433
+ status_code=500,
434
+ reason='Internal Server Error',
435
+ body=f"Error: {str(e)}".encode('utf-8')
436
+ )
437
+
438
+ response_data = error_response.to_bytes()
439
+ if socket_conn.virtual_connection and socket_conn.virtual_connection.on_data_received:
440
+ socket_conn.virtual_connection.on_data_received(response_data)
441
+
442
+ def _handle_http_request_sync(self, socket_conn: SocketConnection, http_request: HTTPRequest) -> bool:
443
+ """Handle HTTP request synchronously (fallback)"""
444
+ try:
445
+ # Use urllib for sync HTTP requests
446
+ scheme = 'https' if socket_conn.connection_type == ConnectionType.HTTPS_CLIENT else 'http'
447
+ url = f"{scheme}://{socket_conn.remote_host}:{socket_conn.remote_port}{http_request.path}"
448
+
449
+ import urllib.request
450
+ import urllib.error
451
+
452
+ # Create request
453
+ req = urllib.request.Request(
454
+ url,
455
+ data=http_request.body if http_request.body else None,
456
+ headers=http_request.headers,
457
+ method=http_request.method
458
+ )
459
+
460
+ # Make request
461
+ with urllib.request.urlopen(req, timeout=self.read_timeout) as response:
462
+ response_body = response.read()
463
+
464
+ # Create HTTP response
465
+ http_response = HTTPResponse(
466
+ status_code=response.getcode(),
467
+ reason='OK',
468
+ headers=dict(response.headers),
469
+ body=response_body
470
+ )
471
+
472
+ # Send response back to virtual connection
473
+ response_data = http_response.to_bytes()
474
+ if socket_conn.virtual_connection and socket_conn.virtual_connection.on_data_received:
475
+ socket_conn.virtual_connection.on_data_received(response_data)
476
+
477
+ socket_conn.update_activity(len(response_data), 'received')
478
+ self.stats['bytes_transferred'] += len(response_data)
479
+
480
+ return True
481
+
482
+ except Exception as e:
483
+ print(f"Sync HTTP request failed: {e}")
484
+ socket_conn.error_count += 1
485
+ return False
486
+
487
+ def _tcp_receive_loop(self, socket_conn: SocketConnection):
488
+ """Background loop for receiving TCP data"""
489
+ sock = socket_conn.host_socket
490
+ if not sock:
491
+ return
492
+
493
+ try:
494
+ while socket_conn.is_connected:
495
+ try:
496
+ data = sock.recv(self.buffer_size)
497
+ if not data:
498
+ break
499
+
500
+ # Forward data to virtual connection
501
+ if socket_conn.virtual_connection and socket_conn.virtual_connection.on_data_received:
502
+ socket_conn.virtual_connection.on_data_received(data)
503
+
504
+ socket_conn.update_activity(len(data), 'received')
505
+ self.stats['bytes_transferred'] += len(data)
506
+
507
+ except socket.timeout:
508
+ continue
509
+ except Exception as e:
510
+ print(f"TCP receive error: {e}")
511
+ break
512
+
513
+ finally:
514
+ self._close_connection(socket_conn.connection_id)
515
+
516
+ def send_data(self, connection_id: str, data: bytes) -> bool:
517
+ """Send data through socket connection"""
518
+ with self.lock:
519
+ socket_conn = self.connections.get(connection_id)
520
+
521
+ if not socket_conn or not socket_conn.is_connected:
522
+ return False
523
+
524
+ try:
525
+ if socket_conn.connection_type in [ConnectionType.HTTP_CLIENT, ConnectionType.HTTPS_CLIENT]:
526
+ # For HTTP connections, treat as new request
527
+ return self._create_http_connection(socket_conn, data)
528
+ else:
529
+ # TCP connection
530
+ if socket_conn.host_socket:
531
+ socket_conn.host_socket.send(data)
532
+ socket_conn.update_activity(len(data), 'sent')
533
+ self.stats['bytes_transferred'] += len(data)
534
+ return True
535
+
536
+ except Exception as e:
537
+ print(f"Failed to send data: {e}")
538
+ socket_conn.error_count += 1
539
+ self._close_connection(connection_id)
540
+
541
+ return False
542
+
543
+ def _close_connection(self, connection_id: str):
544
+ """Close socket connection"""
545
+ with self.lock:
546
+ socket_conn = self.connections.get(connection_id)
547
+ if not socket_conn:
548
+ return
549
+
550
+ # Close socket
551
+ if socket_conn.host_socket:
552
+ try:
553
+ socket_conn.host_socket.close()
554
+ except:
555
+ pass
556
+
557
+ socket_conn.is_connected = False
558
+
559
+ # Remove from connections
560
+ del self.connections[connection_id]
561
+
562
+ self.stats['active_connections'] = len(self.connections)
563
+
564
+ def close_connection(self, connection_id: str) -> bool:
565
+ """Manually close connection"""
566
+ self._close_connection(connection_id)
567
+ return True
568
+
569
+ def get_connection(self, connection_id: str) -> Optional[SocketConnection]:
570
+ """Get socket connection"""
571
+ with self.lock:
572
+ return self.connections.get(connection_id)
573
+
574
+ def get_connections(self) -> Dict[str, Dict]:
575
+ """Get all socket connections"""
576
+ with self.lock:
577
+ return {
578
+ conn_id: conn.to_dict()
579
+ for conn_id, conn in self.connections.items()
580
+ }
581
+
582
+ def get_stats(self) -> Dict:
583
+ """Get socket translator statistics"""
584
+ with self.lock:
585
+ stats = self.stats.copy()
586
+ stats['active_connections'] = len(self.connections)
587
+
588
+ return stats
589
+
590
+ def _cleanup_loop(self):
591
+ """Background cleanup loop"""
592
+ while self.running:
593
+ try:
594
+ current_time = time.time()
595
+ expired_connections = []
596
+
597
+ with self.lock:
598
+ for conn_id, conn in self.connections.items():
599
+ # Close connections that have been inactive too long
600
+ if current_time - conn.last_activity > self.read_timeout * 2:
601
+ expired_connections.append(conn_id)
602
+
603
+ for conn_id in expired_connections:
604
+ self._close_connection(conn_id)
605
+
606
+ time.sleep(30) # Cleanup every 30 seconds
607
+
608
+ except Exception as e:
609
+ print(f"Socket translator cleanup error: {e}")
610
+ time.sleep(5)
611
+
612
+ def start(self):
613
+ """Start socket translator"""
614
+ self.running = True
615
+
616
+ # Start event loop for async HTTP
617
+ try:
618
+ self.loop = asyncio.new_event_loop()
619
+ asyncio.set_event_loop(self.loop)
620
+
621
+ # Start cleanup thread
622
+ self.cleanup_thread = threading.Thread(target=self._cleanup_loop, daemon=True)
623
+ self.cleanup_thread.start()
624
+
625
+ print("Socket translator started")
626
+ except Exception as e:
627
+ print(f"Failed to start socket translator: {e}")
628
+
629
+ def stop(self):
630
+ """Stop socket translator"""
631
+ self.running = False
632
+
633
+ # Close all connections
634
+ with self.lock:
635
+ connection_ids = list(self.connections.keys())
636
+
637
+ for conn_id in connection_ids:
638
+ self._close_connection(conn_id)
639
+
640
+ # Close HTTP session
641
+ if self.http_session:
642
+ asyncio.run_coroutine_threadsafe(self.http_session.close(), self.loop)
643
+
644
+ # Close event loop
645
+ if self.loop and not self.loop.is_closed():
646
+ self.loop.call_soon_threadsafe(self.loop.stop)
647
+
648
+ # Wait for cleanup thread
649
+ if self.cleanup_thread:
650
+ self.cleanup_thread.join()
651
+
652
+ print("Socket translator stopped")
653
+
core/tcp_engine.py ADDED
@@ -0,0 +1,716 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ TCP Engine Module
3
+
4
+ Implements a complete TCP state machine in user-space:
5
+ - Full TCP state machine (SYN, SYN-ACK, ESTABLISHED, FIN, RST)
6
+ - Sequence and acknowledgment number tracking
7
+ - Sliding window implementation
8
+ - Retransmission and timeout handling
9
+ - Congestion control
10
+ """
11
+
12
+ import time
13
+ import threading
14
+ import random
15
+ from typing import Dict, List, Optional, Tuple, Callable
16
+ from dataclasses import dataclass, field
17
+ from enum import Enum
18
+ from collections import deque
19
+
20
+ from .ip_parser import TCPHeader, IPv4Header, IPParser
21
+
22
+
23
+ class TCPState(Enum):
24
+ CLOSED = "CLOSED"
25
+ LISTEN = "LISTEN"
26
+ SYN_SENT = "SYN_SENT"
27
+ SYN_RECEIVED = "SYN_RECEIVED"
28
+ ESTABLISHED = "ESTABLISHED"
29
+ FIN_WAIT_1 = "FIN_WAIT_1"
30
+ FIN_WAIT_2 = "FIN_WAIT_2"
31
+ CLOSE_WAIT = "CLOSE_WAIT"
32
+ CLOSING = "CLOSING"
33
+ LAST_ACK = "LAST_ACK"
34
+ TIME_WAIT = "TIME_WAIT"
35
+
36
+
37
+ @dataclass
38
+ class TCPSegment:
39
+ """Represents a TCP segment"""
40
+ seq_num: int
41
+ ack_num: int
42
+ flags: int
43
+ window: int
44
+ data: bytes
45
+ timestamp: float = field(default_factory=time.time)
46
+ retransmit_count: int = 0
47
+
48
+ @property
49
+ def data_length(self) -> int:
50
+ """Get data length"""
51
+ return len(self.data)
52
+
53
+ @property
54
+ def seq_end(self) -> int:
55
+ """Get sequence number after this segment"""
56
+ length = self.data_length
57
+ # SYN and FIN consume one sequence number
58
+ if self.flags & 0x02: # SYN
59
+ length += 1
60
+ if self.flags & 0x01: # FIN
61
+ length += 1
62
+ return self.seq_num + length
63
+
64
+
65
+ @dataclass
66
+ class TCPConnection:
67
+ """Represents a TCP connection state"""
68
+ # Connection identification
69
+ local_ip: str
70
+ local_port: int
71
+ remote_ip: str
72
+ remote_port: int
73
+
74
+ # State
75
+ state: TCPState = TCPState.CLOSED
76
+
77
+ # Sequence numbers
78
+ local_seq: int = 0
79
+ local_ack: int = 0
80
+ remote_seq: int = 0
81
+ remote_ack: int = 0
82
+ initial_seq: int = 0
83
+
84
+ # Window management
85
+ local_window: int = 65535
86
+ remote_window: int = 65535
87
+ window_scale: int = 0
88
+
89
+ # Buffers
90
+ send_buffer: deque = field(default_factory=deque)
91
+ recv_buffer: deque = field(default_factory=deque)
92
+ out_of_order_buffer: Dict[int, bytes] = field(default_factory=dict)
93
+
94
+ # Retransmission
95
+ unacked_segments: Dict[int, TCPSegment] = field(default_factory=dict)
96
+ retransmit_timer: Optional[float] = None
97
+ rto: float = 1.0 # Retransmission timeout
98
+ srtt: float = 0.0 # Smoothed round-trip time
99
+ rttvar: float = 0.0 # Round-trip time variation
100
+
101
+ # Congestion control
102
+ cwnd: int = 1 # Congestion window (in MSS units)
103
+ ssthresh: int = 65535 # Slow start threshold
104
+ mss: int = 1460 # Maximum segment size
105
+
106
+ # Timers
107
+ last_activity: float = field(default_factory=time.time)
108
+ time_wait_start: Optional[float] = None
109
+
110
+ # Callbacks
111
+ on_data_received: Optional[Callable[[bytes], None]] = None
112
+ on_connection_closed: Optional[Callable[[], None]] = None
113
+
114
+ @property
115
+ def connection_id(self) -> str:
116
+ """Get unique connection identifier"""
117
+ return f"{self.local_ip}:{self.local_port}-{self.remote_ip}:{self.remote_port}"
118
+
119
+ @property
120
+ def is_established(self) -> bool:
121
+ """Check if connection is established"""
122
+ return self.state == TCPState.ESTABLISHED
123
+
124
+ @property
125
+ def can_send_data(self) -> bool:
126
+ """Check if connection can send data"""
127
+ return self.state in [TCPState.ESTABLISHED, TCPState.CLOSE_WAIT]
128
+
129
+ @property
130
+ def effective_window(self) -> int:
131
+ """Get effective send window"""
132
+ return min(self.remote_window, self.cwnd * self.mss)
133
+
134
+
135
+ class TCPEngine:
136
+ """TCP state machine implementation"""
137
+
138
+ def __init__(self, config: Dict):
139
+ self.config = config
140
+ self.connections: Dict[str, TCPConnection] = {}
141
+ self.listening_ports: Dict[int, Callable] = {} # port -> accept callback
142
+ self.lock = threading.Lock()
143
+ self.running = False
144
+ self.timer_thread = None
145
+
146
+ # Default configuration
147
+ self.default_mss = config.get('mss', 1460)
148
+ self.default_window = config.get('initial_window', 65535)
149
+ self.max_retries = config.get('max_retries', 3)
150
+ self.connection_timeout = config.get('timeout', 300)
151
+ self.time_wait_timeout = config.get('time_wait_timeout', 120)
152
+
153
+ def _generate_isn(self) -> int:
154
+ """Generate Initial Sequence Number"""
155
+ return random.randint(0, 0xFFFFFFFF)
156
+
157
+ def _get_connection_key(self, local_ip: str, local_port: int, remote_ip: str, remote_port: int) -> str:
158
+ """Get connection key"""
159
+ return f"{local_ip}:{local_port}-{remote_ip}:{remote_port}"
160
+
161
+ def _create_tcp_segment(self, conn: TCPConnection, flags: int, data: bytes = b'') -> TCPSegment:
162
+ """Create TCP segment"""
163
+ segment = TCPSegment(
164
+ seq_num=conn.local_seq,
165
+ ack_num=conn.local_ack,
166
+ flags=flags,
167
+ window=conn.local_window,
168
+ data=data
169
+ )
170
+ return segment
171
+
172
+ def _build_tcp_packet(self, conn: TCPConnection, segment: TCPSegment) -> bytes:
173
+ """Build complete TCP packet"""
174
+ # Create IP header
175
+ ip_header = IPv4Header(
176
+ protocol=6, # TCP
177
+ source_ip=conn.local_ip,
178
+ dest_ip=conn.remote_ip,
179
+ ttl=64
180
+ )
181
+
182
+ # Create TCP header
183
+ tcp_header = TCPHeader(
184
+ source_port=conn.local_port,
185
+ dest_port=conn.remote_port,
186
+ seq_num=segment.seq_num,
187
+ ack_num=segment.ack_num,
188
+ flags=segment.flags,
189
+ window_size=segment.window
190
+ )
191
+
192
+ # Build packet
193
+ return IPParser.build_packet(ip_header, tcp_header, segment.data)
194
+
195
+ def _update_rto(self, conn: TCPConnection, rtt: float):
196
+ """Update retransmission timeout using RFC 6298"""
197
+ if conn.srtt == 0:
198
+ # First RTT measurement
199
+ conn.srtt = rtt
200
+ conn.rttvar = rtt / 2
201
+ else:
202
+ # Subsequent measurements
203
+ alpha = 0.125
204
+ beta = 0.25
205
+ conn.rttvar = (1 - beta) * conn.rttvar + beta * abs(conn.srtt - rtt)
206
+ conn.srtt = (1 - alpha) * conn.srtt + alpha * rtt
207
+
208
+ # Calculate RTO
209
+ conn.rto = max(1.0, conn.srtt + 4 * conn.rttvar)
210
+ conn.rto = min(conn.rto, 60.0) # Cap at 60 seconds
211
+
212
+ def _update_congestion_window(self, conn: TCPConnection, acked_bytes: int):
213
+ """Update congestion window (simplified congestion control)"""
214
+ if conn.cwnd < conn.ssthresh:
215
+ # Slow start
216
+ conn.cwnd += 1
217
+ else:
218
+ # Congestion avoidance
219
+ conn.cwnd += max(1, conn.mss * conn.mss // conn.cwnd)
220
+
221
+ def _handle_retransmission(self, conn: TCPConnection):
222
+ """Handle segment retransmission"""
223
+ current_time = time.time()
224
+
225
+ # Find segments that need retransmission
226
+ to_retransmit = []
227
+ for seq_num, segment in conn.unacked_segments.items():
228
+ if current_time - segment.timestamp > conn.rto:
229
+ if segment.retransmit_count < self.max_retries:
230
+ to_retransmit.append(segment)
231
+ else:
232
+ # Max retries exceeded, close connection
233
+ self._close_connection(conn, reset=True)
234
+ return
235
+
236
+ # Retransmit segments
237
+ for segment in to_retransmit:
238
+ segment.retransmit_count += 1
239
+ segment.timestamp = current_time
240
+
241
+ # Exponential backoff
242
+ conn.rto = min(conn.rto * 2, 60.0)
243
+
244
+ # Congestion control: reduce window
245
+ conn.ssthresh = max(conn.cwnd // 2, 2)
246
+ conn.cwnd = 1
247
+
248
+ # Send retransmitted segment
249
+ packet = self._build_tcp_packet(conn, segment)
250
+ self._send_packet(packet)
251
+
252
+ def _send_packet(self, packet: bytes):
253
+ """Send packet (to be implemented by integration layer)"""
254
+ # This will be connected to the packet bridge
255
+ pass
256
+
257
+ def _close_connection(self, conn: TCPConnection, reset: bool = False):
258
+ """Close connection"""
259
+ if reset:
260
+ # Send RST
261
+ segment = self._create_tcp_segment(conn, 0x04) # RST flag
262
+ packet = self._build_tcp_packet(conn, segment)
263
+ self._send_packet(packet)
264
+ conn.state = TCPState.CLOSED
265
+ else:
266
+ # Normal close
267
+ if conn.state == TCPState.ESTABLISHED:
268
+ # Send FIN
269
+ segment = self._create_tcp_segment(conn, 0x01) # FIN flag
270
+ packet = self._build_tcp_packet(conn, segment)
271
+ self._send_packet(packet)
272
+ conn.local_seq += 1
273
+ conn.state = TCPState.FIN_WAIT_1
274
+
275
+ # Cleanup if closed
276
+ if conn.state == TCPState.CLOSED:
277
+ if conn.on_connection_closed:
278
+ conn.on_connection_closed()
279
+
280
+ with self.lock:
281
+ if conn.connection_id in self.connections:
282
+ del self.connections[conn.connection_id]
283
+
284
+ def listen(self, port: int, accept_callback: Callable):
285
+ """Listen on port for incoming connections"""
286
+ with self.lock:
287
+ self.listening_ports[port] = accept_callback
288
+
289
+ def connect(self, local_ip: str, local_port: int, remote_ip: str, remote_port: int) -> Optional[TCPConnection]:
290
+ """Initiate outbound connection"""
291
+ conn_key = self._get_connection_key(local_ip, local_port, remote_ip, remote_port)
292
+
293
+ # Create connection
294
+ conn = TCPConnection(
295
+ local_ip=local_ip,
296
+ local_port=local_port,
297
+ remote_ip=remote_ip,
298
+ remote_port=remote_port,
299
+ state=TCPState.SYN_SENT,
300
+ local_seq=self._generate_isn(),
301
+ mss=self.default_mss,
302
+ local_window=self.default_window
303
+ )
304
+ conn.initial_seq = conn.local_seq
305
+
306
+ with self.lock:
307
+ self.connections[conn_key] = conn
308
+
309
+ # Send SYN
310
+ segment = self._create_tcp_segment(conn, 0x02) # SYN flag
311
+ packet = self._build_tcp_packet(conn, segment)
312
+ self._send_packet(packet)
313
+
314
+ # Track unacked segment
315
+ conn.unacked_segments[conn.local_seq] = segment
316
+ conn.local_seq += 1
317
+ conn.retransmit_timer = time.time()
318
+
319
+ return conn
320
+
321
+ def send_data(self, conn: TCPConnection, data: bytes) -> bool:
322
+ """Send data on established connection"""
323
+ if not conn.can_send_data:
324
+ return False
325
+
326
+ # Add to send buffer
327
+ conn.send_buffer.append(data)
328
+
329
+ # Try to send immediately
330
+ self._try_send_data(conn)
331
+
332
+ return True
333
+
334
+ def _try_send_data(self, conn: TCPConnection):
335
+ """Try to send buffered data"""
336
+ while conn.send_buffer and len(conn.unacked_segments) * conn.mss < conn.effective_window:
337
+ data = conn.send_buffer.popleft()
338
+
339
+ # Split data if larger than MSS
340
+ while data:
341
+ chunk = data[:conn.mss]
342
+ data = data[conn.mss:]
343
+
344
+ # Create and send segment
345
+ segment = self._create_tcp_segment(conn, 0x18, chunk) # PSH+ACK flags
346
+ packet = self._build_tcp_packet(conn, segment)
347
+ self._send_packet(packet)
348
+
349
+ # Track unacked segment
350
+ conn.unacked_segments[conn.local_seq] = segment
351
+ conn.local_seq += len(chunk)
352
+
353
+ if not data:
354
+ break
355
+
356
+ def process_packet(self, packet_data: bytes) -> bool:
357
+ """Process incoming TCP packet"""
358
+ try:
359
+ # Parse packet
360
+ parsed = IPParser.parse_packet(packet_data)
361
+ if not isinstance(parsed.transport_header, TCPHeader):
362
+ return False
363
+
364
+ ip_header = parsed.ip_header
365
+ tcp_header = parsed.transport_header
366
+ payload = parsed.payload
367
+
368
+ # Find or create connection
369
+ conn_key = self._get_connection_key(
370
+ ip_header.dest_ip, tcp_header.dest_port,
371
+ ip_header.source_ip, tcp_header.source_port
372
+ )
373
+
374
+ with self.lock:
375
+ conn = self.connections.get(conn_key)
376
+
377
+ # Handle new connection (SYN to listening port)
378
+ if not conn and tcp_header.syn and not tcp_header.ack:
379
+ if tcp_header.dest_port in self.listening_ports:
380
+ conn = self._handle_new_connection(ip_header, tcp_header)
381
+ if conn:
382
+ self.connections[conn_key] = conn
383
+
384
+ if not conn:
385
+ # Send RST for unknown connection
386
+ self._send_rst(ip_header, tcp_header)
387
+ return False
388
+
389
+ # Process segment
390
+ return self._process_segment(conn, tcp_header, payload)
391
+
392
+ except Exception as e:
393
+ print(f"Error processing TCP packet: {e}")
394
+ return False
395
+
396
+ def _handle_new_connection(self, ip_header: IPv4Header, tcp_header: TCPHeader) -> Optional[TCPConnection]:
397
+ """Handle new incoming connection"""
398
+ accept_callback = self.listening_ports.get(tcp_header.dest_port)
399
+ if not accept_callback:
400
+ return None
401
+
402
+ # Create connection
403
+ conn = TCPConnection(
404
+ local_ip=ip_header.dest_ip,
405
+ local_port=tcp_header.dest_port,
406
+ remote_ip=ip_header.source_ip,
407
+ remote_port=tcp_header.source_port,
408
+ state=TCPState.SYN_RECEIVED,
409
+ local_seq=self._generate_isn(),
410
+ remote_seq=tcp_header.seq_num,
411
+ local_ack=tcp_header.seq_num + 1,
412
+ mss=self.default_mss,
413
+ local_window=self.default_window
414
+ )
415
+ conn.initial_seq = conn.local_seq
416
+
417
+ # Send SYN-ACK
418
+ segment = self._create_tcp_segment(conn, 0x12) # SYN+ACK flags
419
+ packet = self._build_tcp_packet(conn, segment)
420
+ self._send_packet(packet)
421
+
422
+ # Track unacked segment
423
+ conn.unacked_segments[conn.local_seq] = segment
424
+ conn.local_seq += 1
425
+ conn.retransmit_timer = time.time()
426
+
427
+ # Call accept callback
428
+ accept_callback(conn)
429
+
430
+ return conn
431
+
432
+ def _process_segment(self, conn: TCPConnection, tcp_header: TCPHeader, payload: bytes) -> bool:
433
+ """Process TCP segment based on connection state"""
434
+ conn.last_activity = time.time()
435
+
436
+ # Handle RST
437
+ if tcp_header.rst:
438
+ conn.state = TCPState.CLOSED
439
+ self._close_connection(conn)
440
+ return True
441
+
442
+ # State machine
443
+ if conn.state == TCPState.SYN_SENT:
444
+ return self._handle_syn_sent(conn, tcp_header, payload)
445
+ elif conn.state == TCPState.SYN_RECEIVED:
446
+ return self._handle_syn_received(conn, tcp_header, payload)
447
+ elif conn.state == TCPState.ESTABLISHED:
448
+ return self._handle_established(conn, tcp_header, payload)
449
+ elif conn.state == TCPState.FIN_WAIT_1:
450
+ return self._handle_fin_wait_1(conn, tcp_header, payload)
451
+ elif conn.state == TCPState.FIN_WAIT_2:
452
+ return self._handle_fin_wait_2(conn, tcp_header, payload)
453
+ elif conn.state == TCPState.CLOSE_WAIT:
454
+ return self._handle_close_wait(conn, tcp_header, payload)
455
+ elif conn.state == TCPState.CLOSING:
456
+ return self._handle_closing(conn, tcp_header, payload)
457
+ elif conn.state == TCPState.LAST_ACK:
458
+ return self._handle_last_ack(conn, tcp_header, payload)
459
+ elif conn.state == TCPState.TIME_WAIT:
460
+ return self._handle_time_wait(conn, tcp_header, payload)
461
+
462
+ return False
463
+
464
+ def _handle_syn_sent(self, conn: TCPConnection, tcp_header: TCPHeader, payload: bytes) -> bool:
465
+ """Handle segment in SYN_SENT state"""
466
+ if tcp_header.syn and tcp_header.ack:
467
+ # SYN-ACK received
468
+ if tcp_header.ack_num == conn.local_seq:
469
+ conn.remote_seq = tcp_header.seq_num
470
+ conn.local_ack = tcp_header.seq_num + 1
471
+ conn.remote_window = tcp_header.window_size
472
+
473
+ # Remove SYN from unacked segments
474
+ if conn.local_seq - 1 in conn.unacked_segments:
475
+ del conn.unacked_segments[conn.local_seq - 1]
476
+
477
+ # Send ACK
478
+ segment = self._create_tcp_segment(conn, 0x10) # ACK flag
479
+ packet = self._build_tcp_packet(conn, segment)
480
+ self._send_packet(packet)
481
+
482
+ conn.state = TCPState.ESTABLISHED
483
+ return True
484
+
485
+ return False
486
+
487
+ def _handle_syn_received(self, conn: TCPConnection, tcp_header: TCPHeader, payload: bytes) -> bool:
488
+ """Handle segment in SYN_RECEIVED state"""
489
+ if tcp_header.ack and tcp_header.ack_num == conn.local_seq:
490
+ # ACK for our SYN-ACK
491
+ conn.remote_window = tcp_header.window_size
492
+
493
+ # Remove SYN-ACK from unacked segments
494
+ if conn.local_seq - 1 in conn.unacked_segments:
495
+ del conn.unacked_segments[conn.local_seq - 1]
496
+
497
+ conn.state = TCPState.ESTABLISHED
498
+ return True
499
+
500
+ return False
501
+
502
+ def _handle_established(self, conn: TCPConnection, tcp_header: TCPHeader, payload: bytes) -> bool:
503
+ """Handle segment in ESTABLISHED state"""
504
+ # Handle ACK
505
+ if tcp_header.ack:
506
+ self._process_ack(conn, tcp_header.ack_num)
507
+
508
+ # Handle data
509
+ if payload and tcp_header.seq_num == conn.local_ack:
510
+ conn.local_ack += len(payload)
511
+
512
+ # Deliver data
513
+ if conn.on_data_received:
514
+ conn.on_data_received(payload)
515
+
516
+ # Send ACK
517
+ segment = self._create_tcp_segment(conn, 0x10) # ACK flag
518
+ packet = self._build_tcp_packet(conn, segment)
519
+ self._send_packet(packet)
520
+
521
+ # Handle FIN
522
+ if tcp_header.fin:
523
+ conn.local_ack += 1
524
+
525
+ # Send ACK
526
+ segment = self._create_tcp_segment(conn, 0x10) # ACK flag
527
+ packet = self._build_tcp_packet(conn, segment)
528
+ self._send_packet(packet)
529
+
530
+ conn.state = TCPState.CLOSE_WAIT
531
+
532
+ return True
533
+
534
+ def _handle_fin_wait_1(self, conn: TCPConnection, tcp_header: TCPHeader, payload: bytes) -> bool:
535
+ """Handle segment in FIN_WAIT_1 state"""
536
+ if tcp_header.ack:
537
+ self._process_ack(conn, tcp_header.ack_num)
538
+ if not conn.unacked_segments: # Our FIN was ACKed
539
+ conn.state = TCPState.FIN_WAIT_2
540
+
541
+ if tcp_header.fin:
542
+ conn.local_ack += 1
543
+
544
+ # Send ACK
545
+ segment = self._create_tcp_segment(conn, 0x10) # ACK flag
546
+ packet = self._build_tcp_packet(conn, segment)
547
+ self._send_packet(packet)
548
+
549
+ if conn.state == TCPState.FIN_WAIT_2:
550
+ conn.state = TCPState.TIME_WAIT
551
+ conn.time_wait_start = time.time()
552
+ else:
553
+ conn.state = TCPState.CLOSING
554
+
555
+ return True
556
+
557
+ def _handle_fin_wait_2(self, conn: TCPConnection, tcp_header: TCPHeader, payload: bytes) -> bool:
558
+ """Handle segment in FIN_WAIT_2 state"""
559
+ if tcp_header.fin:
560
+ conn.local_ack += 1
561
+
562
+ # Send ACK
563
+ segment = self._create_tcp_segment(conn, 0x10) # ACK flag
564
+ packet = self._build_tcp_packet(conn, segment)
565
+ self._send_packet(packet)
566
+
567
+ conn.state = TCPState.TIME_WAIT
568
+ conn.time_wait_start = time.time()
569
+
570
+ return True
571
+
572
+ def _handle_close_wait(self, conn: TCPConnection, tcp_header: TCPHeader, payload: bytes) -> bool:
573
+ """Handle segment in CLOSE_WAIT state"""
574
+ # Application should close the connection
575
+ return True
576
+
577
+ def _handle_closing(self, conn: TCPConnection, tcp_header: TCPHeader, payload: bytes) -> bool:
578
+ """Handle segment in CLOSING state"""
579
+ if tcp_header.ack:
580
+ self._process_ack(conn, tcp_header.ack_num)
581
+ if not conn.unacked_segments: # Our FIN was ACKed
582
+ conn.state = TCPState.TIME_WAIT
583
+ conn.time_wait_start = time.time()
584
+
585
+ return True
586
+
587
+ def _handle_last_ack(self, conn: TCPConnection, tcp_header: TCPHeader, payload: bytes) -> bool:
588
+ """Handle segment in LAST_ACK state"""
589
+ if tcp_header.ack:
590
+ self._process_ack(conn, tcp_header.ack_num)
591
+ if not conn.unacked_segments: # Our FIN was ACKed
592
+ conn.state = TCPState.CLOSED
593
+ self._close_connection(conn)
594
+
595
+ return True
596
+
597
+ def _handle_time_wait(self, conn: TCPConnection, tcp_header: TCPHeader, payload: bytes) -> bool:
598
+ """Handle segment in TIME_WAIT state"""
599
+ # Just acknowledge any segments
600
+ if tcp_header.seq_num == conn.local_ack:
601
+ segment = self._create_tcp_segment(conn, 0x10) # ACK flag
602
+ packet = self._build_tcp_packet(conn, segment)
603
+ self._send_packet(packet)
604
+
605
+ return True
606
+
607
+ def _process_ack(self, conn: TCPConnection, ack_num: int):
608
+ """Process ACK and remove acknowledged segments"""
609
+ acked_segments = []
610
+ acked_bytes = 0
611
+
612
+ for seq_num, segment in list(conn.unacked_segments.items()):
613
+ if seq_num < ack_num:
614
+ acked_segments.append((seq_num, segment))
615
+ acked_bytes += segment.data_length
616
+ del conn.unacked_segments[seq_num]
617
+
618
+ # Update RTT and congestion window
619
+ if acked_segments:
620
+ # Use first acked segment for RTT calculation
621
+ rtt = time.time() - acked_segments[0][1].timestamp
622
+ self._update_rto(conn, rtt)
623
+ self._update_congestion_window(conn, acked_bytes)
624
+
625
+ # Try to send more data
626
+ self._try_send_data(conn)
627
+
628
+ def _send_rst(self, ip_header: IPv4Header, tcp_header: TCPHeader):
629
+ """Send RST for unknown connection"""
630
+ # Create RST response
631
+ rst_ip = IPv4Header(
632
+ protocol=6,
633
+ source_ip=ip_header.dest_ip,
634
+ dest_ip=ip_header.source_ip,
635
+ ttl=64
636
+ )
637
+
638
+ rst_tcp = TCPHeader(
639
+ source_port=tcp_header.dest_port,
640
+ dest_port=tcp_header.source_port,
641
+ seq_num=tcp_header.ack_num if tcp_header.ack else 0,
642
+ ack_num=tcp_header.seq_num + 1 if tcp_header.syn else tcp_header.seq_num,
643
+ flags=0x14 if tcp_header.ack else 0x04 # RST+ACK or RST
644
+ )
645
+
646
+ packet = IPParser.build_packet(rst_ip, rst_tcp)
647
+ self._send_packet(packet)
648
+
649
+ def _timer_loop(self):
650
+ """Timer loop for handling timeouts"""
651
+ while self.running:
652
+ current_time = time.time()
653
+
654
+ with self.lock:
655
+ connections_to_check = list(self.connections.values())
656
+
657
+ for conn in connections_to_check:
658
+ # Handle retransmissions
659
+ if conn.unacked_segments:
660
+ self._handle_retransmission(conn)
661
+
662
+ # Handle connection timeout
663
+ if current_time - conn.last_activity > self.connection_timeout:
664
+ self._close_connection(conn, reset=True)
665
+
666
+ # Handle TIME_WAIT timeout
667
+ if (conn.state == TCPState.TIME_WAIT and
668
+ conn.time_wait_start and
669
+ current_time - conn.time_wait_start > self.time_wait_timeout):
670
+ conn.state = TCPState.CLOSED
671
+ self._close_connection(conn)
672
+
673
+ time.sleep(1) # Check every second
674
+
675
+ def start(self):
676
+ """Start TCP engine"""
677
+ self.running = True
678
+ self.timer_thread = threading.Thread(target=self._timer_loop, daemon=True)
679
+ self.timer_thread.start()
680
+ print("TCP engine started")
681
+
682
+ def stop(self):
683
+ """Stop TCP engine"""
684
+ self.running = False
685
+ if self.timer_thread:
686
+ self.timer_thread.join()
687
+
688
+ # Close all connections
689
+ with self.lock:
690
+ for conn in list(self.connections.values()):
691
+ self._close_connection(conn, reset=True)
692
+
693
+ print("TCP engine stopped")
694
+
695
+ def get_connections(self) -> Dict[str, Dict]:
696
+ """Get current connections"""
697
+ with self.lock:
698
+ return {
699
+ conn_id: {
700
+ 'local_ip': conn.local_ip,
701
+ 'local_port': conn.local_port,
702
+ 'remote_ip': conn.remote_ip,
703
+ 'remote_port': conn.remote_port,
704
+ 'state': conn.state.value,
705
+ 'local_seq': conn.local_seq,
706
+ 'local_ack': conn.local_ack,
707
+ 'remote_seq': conn.remote_seq,
708
+ 'remote_ack': conn.remote_ack,
709
+ 'window_size': conn.local_window,
710
+ 'cwnd': conn.cwnd,
711
+ 'unacked_segments': len(conn.unacked_segments),
712
+ 'last_activity': conn.last_activity
713
+ }
714
+ for conn_id, conn in self.connections.items()
715
+ }
716
+
core/traffic_router.py ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Traffic Router Module
3
+
4
+ Handles routing of all client traffic through VPN for free data access using async TCP sockets.
5
+ Optimized for performance and scalability.
6
+ """
7
+
8
+
9
+ import asyncio
10
+ import socket
11
+ import logging
12
+ from typing import Dict, Any, Optional
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class TrafficRouter:
18
+ """Manages traffic routing for VPN clients using async TCP sockets"""
19
+
20
+ def __init__(self, config: Dict[str, Any], nat_engine: Any = None):
21
+ self.config = config
22
+ self.is_running = False
23
+ self.vpn_host = self.config.get("vpn_host", "127.0.0.1")
24
+ self.vpn_port = self.config.get("vpn_port", 9000)
25
+ self.internet_host = self.config.get("internet_host", "0.0.0.0")
26
+ self.internet_port = self.config.get("internet_port", 9001)
27
+ self.nat_engine = nat_engine
28
+ self.loop = None
29
+ self.vpn_server = None
30
+ self.internet_server = None
31
+ self.connections = set()
32
+ self.stats = {
33
+ "total_connections": 0,
34
+ "active_connections": 0,
35
+ "bytes_forwarded": 0,
36
+ "errors": 0
37
+ }
38
+
39
+ async def start(self):
40
+ """Start the traffic router using asyncio TCP servers"""
41
+ if self.is_running:
42
+ logger.warning("Traffic Router is already running")
43
+ return True
44
+
45
+ self.is_running = True
46
+ self.loop = asyncio.get_event_loop()
47
+ self.vpn_server = await asyncio.start_server(
48
+ lambda r, w: self._handle_connection(r, w, "VPN"),
49
+ self.vpn_host, self.vpn_port)
50
+ self.internet_server = await asyncio.start_server(
51
+ lambda r, w: self._handle_connection(r, w, "Internet"),
52
+ self.internet_host, self.internet_port)
53
+
54
+ logger.info(f"Traffic Router started on TCP endpoints: {self.vpn_host}:{self.vpn_port} and {self.internet_host}:{self.internet_port}")
55
+ return True
56
+
57
+ async def stop(self):
58
+ """Stop the traffic router"""
59
+ logger.info("Stopping Traffic Router...")
60
+ self.is_running = False
61
+ if self.vpn_server:
62
+ self.vpn_server.close()
63
+ await self.vpn_server.wait_closed()
64
+ if self.internet_server:
65
+ self.internet_server.close()
66
+ await self.internet_server.wait_closed()
67
+ for conn in list(self.connections):
68
+ conn.close()
69
+ logger.info("Traffic Router stopped")
70
+ return True
71
+
72
+ async def _handle_connection(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter, source_name: str):
73
+ """Handle a new connection and forward data asynchronously."""
74
+ peer = writer.get_extra_info("peername")
75
+ logger.info(f"Accepted connection from {peer} on {source_name}")
76
+ self.connections.add(writer)
77
+ try:
78
+ while self.is_running:
79
+ data = await reader.read(4096)
80
+ if not data:
81
+ break
82
+ processed_data = None
83
+ if self.nat_engine:
84
+ if source_name == "VPN":
85
+ processed_data = self.nat_engine.process_outbound_packet(data)
86
+ elif source_name == "Internet":
87
+ processed_data = self.nat_engine.process_inbound_packet(data)
88
+ if processed_data:
89
+ await self._forward_data(processed_data, source_name)
90
+ elif not self.nat_engine:
91
+ await self._forward_data(data, source_name)
92
+ self.stats["bytes_forwarded"] += len(data)
93
+ except Exception as e:
94
+ self.stats["errors"] += 1
95
+ logger.error(f"Error in {source_name} connection: {e}")
96
+ finally:
97
+ writer.close()
98
+ await writer.wait_closed()
99
+ self.connections.discard(writer)
100
+
101
+ async def _forward_data(self, data: bytes, source_name: str):
102
+ """Forward data to the opposite endpoint."""
103
+ # This is a placeholder for actual forwarding logic.
104
+ # You may want to implement connection pooling or load balancing here.
105
+ # For demo, just log the forwarding event.
106
+ logger.debug(f"Forwarded {len(data)} bytes from {source_name}")
107
+
108
+ def get_stats(self) -> Dict[str, Any]:
109
+ """Get traffic router statistics"""
110
+ return {
111
+ "is_running": self.is_running,
112
+ "vpn_host": self.vpn_host,
113
+ "vpn_port": self.vpn_port,
114
+ "internet_host": self.internet_host,
115
+ "internet_port": self.internet_port,
116
+ "total_connections": self.stats["total_connections"],
117
+ "active_connections": len(self.connections),
118
+ "bytes_forwarded": self.stats["bytes_forwarded"],
119
+ "errors": self.stats["errors"]
120
+ }
121
+
122
+
123
+
124
+
125
+
126
+
127
+ def set_components(self, nat_engine: Any = None):
128
+ """Set references to other components for inter-operation."""
129
+ if nat_engine:
130
+ self.nat_engine = nat_engine
131
+
132
+
core/virtual_router.py ADDED
@@ -0,0 +1,565 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Virtual Router Module
3
+
4
+ Implements packet routing between virtual clients and external internet:
5
+ - Maintain routing table for virtual network
6
+ - Forward packets based on destination IP
7
+ - Handle internal vs external routing decisions
8
+ - Support static route configuration
9
+ """
10
+
11
+ import ipaddress
12
+ import time
13
+ import threading
14
+ from typing import Dict, List, Optional, Tuple, Set
15
+ from dataclasses import dataclass
16
+ from enum import Enum
17
+
18
+ from .ip_parser import ParsedPacket, IPv4Header
19
+
20
+
21
+ class RouteType(Enum):
22
+ DIRECT = "DIRECT" # Directly connected network
23
+ STATIC = "STATIC" # Static route
24
+ DEFAULT = "DEFAULT" # Default route
25
+
26
+
27
+ @dataclass
28
+ class RouteEntry:
29
+ """Represents a routing table entry"""
30
+ destination: str # Network in CIDR notation (e.g., "10.0.0.0/24")
31
+ gateway: Optional[str] # Next hop IP (None for direct routes)
32
+ interface: str # Interface name or identifier
33
+ metric: int # Route metric (lower is preferred)
34
+ route_type: RouteType
35
+ created_time: float
36
+ last_used: Optional[float] = None
37
+ use_count: int = 0
38
+
39
+ def __post_init__(self):
40
+ if self.created_time == 0:
41
+ self.created_time = time.time()
42
+
43
+ def record_use(self):
44
+ """Record route usage"""
45
+ self.use_count += 1
46
+ self.last_used = time.time()
47
+
48
+ def matches_destination(self, ip: str) -> bool:
49
+ """Check if this route matches the destination IP"""
50
+ try:
51
+ network = ipaddress.ip_network(self.destination, strict=False)
52
+ return ipaddress.ip_address(ip) in network
53
+ except (ipaddress.AddressValueError, ValueError):
54
+ return False
55
+
56
+ def to_dict(self) -> Dict:
57
+ """Convert route to dictionary"""
58
+ return {
59
+ 'destination': self.destination,
60
+ 'gateway': self.gateway,
61
+ 'interface': self.interface,
62
+ 'metric': self.metric,
63
+ 'route_type': self.route_type.value,
64
+ 'created_time': self.created_time,
65
+ 'last_used': self.last_used,
66
+ 'use_count': self.use_count
67
+ }
68
+
69
+
70
+ @dataclass
71
+ class Interface:
72
+ """Represents a network interface"""
73
+ name: str
74
+ ip_address: str
75
+ netmask: str
76
+ network: str # Network in CIDR notation
77
+ enabled: bool = True
78
+ mtu: int = 1500
79
+ created_time: float = 0
80
+
81
+ def __post_init__(self):
82
+ if self.created_time == 0:
83
+ self.created_time = time.time()
84
+
85
+ # Calculate network if not provided
86
+ if not self.network:
87
+ try:
88
+ interface_network = ipaddress.ip_interface(f"{self.ip_address}/{self.netmask}")
89
+ self.network = str(interface_network.network)
90
+ except (ipaddress.AddressValueError, ValueError):
91
+ self.network = "0.0.0.0/0"
92
+
93
+ def is_local_address(self, ip: str) -> bool:
94
+ """Check if IP address belongs to this interface's network"""
95
+ try:
96
+ network = ipaddress.ip_network(self.network, strict=False)
97
+ return ipaddress.ip_address(ip) in network
98
+ except (ipaddress.AddressValueError, ValueError):
99
+ return False
100
+
101
+ def to_dict(self) -> Dict:
102
+ """Convert interface to dictionary"""
103
+ return {
104
+ 'name': self.name,
105
+ 'ip_address': self.ip_address,
106
+ 'netmask': self.netmask,
107
+ 'network': self.network,
108
+ 'enabled': self.enabled,
109
+ 'mtu': self.mtu,
110
+ 'created_time': self.created_time
111
+ }
112
+
113
+
114
+ class VirtualRouter:
115
+ """Virtual router implementation"""
116
+
117
+ def __init__(self, config: Dict):
118
+ self.config = config
119
+ self.routing_table: List[RouteEntry] = []
120
+ self.interfaces: Dict[str, Interface] = {}
121
+ self.arp_table: Dict[str, str] = {} # IP -> MAC mapping
122
+ self.lock = threading.Lock()
123
+
124
+ # Router configuration
125
+ self.router_id = config.get('router_id', 'virtual-router-1')
126
+ self.default_gateway = config.get('default_gateway')
127
+
128
+ # Statistics
129
+ self.stats = {
130
+ 'packets_routed': 0,
131
+ 'packets_dropped': 0,
132
+ 'route_lookups': 0,
133
+ 'arp_requests': 0,
134
+ 'arp_replies': 0,
135
+ 'routing_errors': 0
136
+ }
137
+
138
+ # Initialize interfaces and routes
139
+ self._initialize_interfaces()
140
+ self._initialize_routes()
141
+
142
+ def _initialize_interfaces(self):
143
+ """Initialize network interfaces from configuration"""
144
+ interfaces_config = self.config.get('interfaces', [])
145
+
146
+ for iface_config in interfaces_config:
147
+ interface = Interface(
148
+ name=iface_config['name'],
149
+ ip_address=iface_config['ip_address'],
150
+ netmask=iface_config.get('netmask', '255.255.255.0'),
151
+ network=iface_config.get('network'),
152
+ enabled=iface_config.get('enabled', True),
153
+ mtu=iface_config.get('mtu', 1500)
154
+ )
155
+
156
+ with self.lock:
157
+ self.interfaces[interface.name] = interface
158
+
159
+ # Add direct route for interface network
160
+ self.add_route(
161
+ destination=interface.network,
162
+ gateway=None,
163
+ interface=interface.name,
164
+ metric=0,
165
+ route_type=RouteType.DIRECT
166
+ )
167
+
168
+ def _initialize_routes(self):
169
+ """Initialize static routes from configuration"""
170
+ routes_config = self.config.get('static_routes', [])
171
+
172
+ for route_config in routes_config:
173
+ self.add_route(
174
+ destination=route_config['destination'],
175
+ gateway=route_config.get('gateway'),
176
+ interface=route_config['interface'],
177
+ metric=route_config.get('metric', 10),
178
+ route_type=RouteType.STATIC
179
+ )
180
+
181
+ # Add default route if configured
182
+ if self.default_gateway:
183
+ # Find interface for default gateway
184
+ default_interface = None
185
+ for interface in self.interfaces.values():
186
+ if interface.is_local_address(self.default_gateway):
187
+ default_interface = interface.name
188
+ break
189
+
190
+ if default_interface:
191
+ self.add_route(
192
+ destination="0.0.0.0/0",
193
+ gateway=self.default_gateway,
194
+ interface=default_interface,
195
+ metric=100,
196
+ route_type=RouteType.DEFAULT
197
+ )
198
+
199
+ def add_interface(self, name: str, ip_address: str, netmask: str = "255.255.255.0",
200
+ network: Optional[str] = None, mtu: int = 1500) -> bool:
201
+ """Add network interface"""
202
+ with self.lock:
203
+ if name in self.interfaces:
204
+ return False
205
+
206
+ interface = Interface(
207
+ name=name,
208
+ ip_address=ip_address,
209
+ netmask=netmask,
210
+ network=network,
211
+ mtu=mtu
212
+ )
213
+
214
+ self.interfaces[name] = interface
215
+
216
+ # Add direct route for interface network
217
+ self.add_route(
218
+ destination=interface.network,
219
+ gateway=None,
220
+ interface=name,
221
+ metric=0,
222
+ route_type=RouteType.DIRECT
223
+ )
224
+
225
+ return True
226
+
227
+ def remove_interface(self, name: str) -> bool:
228
+ """Remove network interface"""
229
+ with self.lock:
230
+ if name not in self.interfaces:
231
+ return False
232
+
233
+ # Remove interface
234
+ del self.interfaces[name]
235
+
236
+ # Remove routes associated with this interface
237
+ self.routing_table = [
238
+ route for route in self.routing_table
239
+ if route.interface != name
240
+ ]
241
+
242
+ return True
243
+
244
+ def enable_interface(self, name: str) -> bool:
245
+ """Enable network interface"""
246
+ with self.lock:
247
+ if name in self.interfaces:
248
+ self.interfaces[name].enabled = True
249
+ return True
250
+ return False
251
+
252
+ def disable_interface(self, name: str) -> bool:
253
+ """Disable network interface"""
254
+ with self.lock:
255
+ if name in self.interfaces:
256
+ self.interfaces[name].enabled = False
257
+ return True
258
+ return False
259
+
260
+ def add_route(self, destination: str, gateway: Optional[str], interface: str,
261
+ metric: int = 10, route_type: RouteType = RouteType.STATIC) -> bool:
262
+ """Add route to routing table"""
263
+ try:
264
+ # Validate destination network
265
+ ipaddress.ip_network(destination, strict=False)
266
+
267
+ # Validate gateway if provided
268
+ if gateway:
269
+ ipaddress.ip_address(gateway)
270
+
271
+ route = RouteEntry(
272
+ destination=destination,
273
+ gateway=gateway,
274
+ interface=interface,
275
+ metric=metric,
276
+ route_type=route_type,
277
+ created_time=time.time()
278
+ )
279
+
280
+ with self.lock:
281
+ # Check if interface exists
282
+ if interface not in self.interfaces:
283
+ return False
284
+
285
+ # Remove existing route with same destination and interface
286
+ self.routing_table = [
287
+ r for r in self.routing_table
288
+ if not (r.destination == destination and r.interface == interface)
289
+ ]
290
+
291
+ # Add new route
292
+ self.routing_table.append(route)
293
+
294
+ # Sort by metric (lower metric = higher priority)
295
+ self.routing_table.sort(key=lambda r: (r.metric, r.created_time))
296
+
297
+ return True
298
+
299
+ except (ipaddress.AddressValueError, ValueError):
300
+ return False
301
+
302
+ def remove_route(self, destination: str, interface: str) -> bool:
303
+ """Remove route from routing table"""
304
+ with self.lock:
305
+ original_count = len(self.routing_table)
306
+ self.routing_table = [
307
+ route for route in self.routing_table
308
+ if not (route.destination == destination and route.interface == interface)
309
+ ]
310
+ return len(self.routing_table) < original_count
311
+
312
+ def lookup_route(self, destination_ip: str) -> Optional[RouteEntry]:
313
+ """Look up route for destination IP"""
314
+ self.stats['route_lookups'] += 1
315
+
316
+ with self.lock:
317
+ # Find all matching routes
318
+ matching_routes = []
319
+ for route in self.routing_table:
320
+ # Skip disabled interfaces
321
+ interface = self.interfaces.get(route.interface)
322
+ if not interface or not interface.enabled:
323
+ continue
324
+
325
+ if route.matches_destination(destination_ip):
326
+ matching_routes.append(route)
327
+
328
+ if not matching_routes:
329
+ self.stats['routing_errors'] += 1
330
+ return None
331
+
332
+ # Sort by specificity (longest prefix match) and then by metric
333
+ def route_priority(route):
334
+ try:
335
+ network = ipaddress.ip_network(route.destination, strict=False)
336
+ return (-network.prefixlen, route.metric, route.created_time)
337
+ except:
338
+ return (0, route.metric, route.created_time)
339
+
340
+ matching_routes.sort(key=route_priority)
341
+ best_route = matching_routes[0]
342
+ best_route.record_use()
343
+
344
+ return best_route
345
+
346
+ def route_packet(self, packet: ParsedPacket) -> Optional[Tuple[str, str]]:
347
+ """Route packet and return (next_hop_ip, interface)"""
348
+ self.stats['packets_routed'] += 1
349
+
350
+ destination_ip = packet.ip_header.dest_ip
351
+
352
+ # Look up route
353
+ route = self.lookup_route(destination_ip)
354
+ if not route:
355
+ self.stats['packets_dropped'] += 1
356
+ return None
357
+
358
+ # Determine next hop
359
+ if route.gateway:
360
+ next_hop = route.gateway
361
+ else:
362
+ # Direct route - destination is next hop
363
+ next_hop = destination_ip
364
+
365
+ return (next_hop, route.interface)
366
+
367
+ def is_local_destination(self, ip: str) -> bool:
368
+ """Check if IP is a local destination (belongs to router interfaces)"""
369
+ with self.lock:
370
+ for interface in self.interfaces.values():
371
+ if interface.ip_address == ip:
372
+ return True
373
+ return False
374
+
375
+ def is_local_network(self, ip: str) -> bool:
376
+ """Check if IP belongs to any local network"""
377
+ with self.lock:
378
+ for interface in self.interfaces.values():
379
+ if interface.is_local_address(ip):
380
+ return True
381
+ return False
382
+
383
+ def get_interface_for_ip(self, ip: str) -> Optional[Interface]:
384
+ """Get interface that can reach the given IP"""
385
+ with self.lock:
386
+ for interface in self.interfaces.values():
387
+ if interface.enabled and interface.is_local_address(ip):
388
+ return interface
389
+ return None
390
+
391
+ def add_arp_entry(self, ip: str, mac: str):
392
+ """Add ARP table entry"""
393
+ with self.lock:
394
+ self.arp_table[ip] = mac
395
+
396
+ def get_arp_entry(self, ip: str) -> Optional[str]:
397
+ """Get MAC address from ARP table"""
398
+ with self.lock:
399
+ return self.arp_table.get(ip)
400
+
401
+ def remove_arp_entry(self, ip: str) -> bool:
402
+ """Remove ARP table entry"""
403
+ with self.lock:
404
+ if ip in self.arp_table:
405
+ del self.arp_table[ip]
406
+ return True
407
+ return False
408
+
409
+ def clear_arp_table(self):
410
+ """Clear ARP table"""
411
+ with self.lock:
412
+ self.arp_table.clear()
413
+
414
+ def get_routing_table(self) -> List[Dict]:
415
+ """Get routing table"""
416
+ with self.lock:
417
+ return [route.to_dict() for route in self.routing_table]
418
+
419
+ def get_interfaces(self) -> Dict[str, Dict]:
420
+ """Get network interfaces"""
421
+ with self.lock:
422
+ return {
423
+ name: interface.to_dict()
424
+ for name, interface in self.interfaces.items()
425
+ }
426
+
427
+ def get_arp_table(self) -> Dict[str, str]:
428
+ """Get ARP table"""
429
+ with self.lock:
430
+ return self.arp_table.copy()
431
+
432
+ def get_stats(self) -> Dict:
433
+ """Get router statistics"""
434
+ with self.lock:
435
+ stats = self.stats.copy()
436
+ stats['total_routes'] = len(self.routing_table)
437
+ stats['total_interfaces'] = len(self.interfaces)
438
+ stats['enabled_interfaces'] = sum(1 for iface in self.interfaces.values() if iface.enabled)
439
+ stats['arp_entries'] = len(self.arp_table)
440
+
441
+ return stats
442
+
443
+ def reset_stats(self):
444
+ """Reset router statistics"""
445
+ self.stats = {
446
+ 'packets_routed': 0,
447
+ 'packets_dropped': 0,
448
+ 'route_lookups': 0,
449
+ 'arp_requests': 0,
450
+ 'arp_replies': 0,
451
+ 'routing_errors': 0
452
+ }
453
+
454
+ # Reset route usage statistics
455
+ with self.lock:
456
+ for route in self.routing_table:
457
+ route.use_count = 0
458
+ route.last_used = None
459
+
460
+ def flush_routes(self, route_type: Optional[RouteType] = None):
461
+ """Flush routes of specified type (or all if None)"""
462
+ with self.lock:
463
+ if route_type:
464
+ self.routing_table = [
465
+ route for route in self.routing_table
466
+ if route.route_type != route_type
467
+ ]
468
+ else:
469
+ self.routing_table.clear()
470
+
471
+ def export_config(self) -> Dict:
472
+ """Export router configuration"""
473
+ return {
474
+ 'router_id': self.router_id,
475
+ 'default_gateway': self.default_gateway,
476
+ 'interfaces': [
477
+ {
478
+ 'name': iface.name,
479
+ 'ip_address': iface.ip_address,
480
+ 'netmask': iface.netmask,
481
+ 'network': iface.network,
482
+ 'enabled': iface.enabled,
483
+ 'mtu': iface.mtu
484
+ }
485
+ for iface in self.interfaces.values()
486
+ ],
487
+ 'static_routes': [
488
+ {
489
+ 'destination': route.destination,
490
+ 'gateway': route.gateway,
491
+ 'interface': route.interface,
492
+ 'metric': route.metric
493
+ }
494
+ for route in self.routing_table
495
+ if route.route_type == RouteType.STATIC
496
+ ]
497
+ }
498
+
499
+ def import_config(self, config: Dict):
500
+ """Import router configuration"""
501
+ # Clear existing configuration
502
+ with self.lock:
503
+ self.interfaces.clear()
504
+ self.routing_table.clear()
505
+ self.arp_table.clear()
506
+
507
+ # Update router settings
508
+ self.router_id = config.get('router_id', self.router_id)
509
+ self.default_gateway = config.get('default_gateway', self.default_gateway)
510
+
511
+ # Reinitialize from new config
512
+ self.config.update(config)
513
+ self._initialize_interfaces()
514
+ self._initialize_routes()
515
+
516
+
517
+ class RouterUtils:
518
+ """Utility functions for router operations"""
519
+
520
+ @staticmethod
521
+ def ip_to_int(ip: str) -> int:
522
+ """Convert IP address to integer"""
523
+ return int(ipaddress.ip_address(ip))
524
+
525
+ @staticmethod
526
+ def int_to_ip(ip_int: int) -> str:
527
+ """Convert integer to IP address"""
528
+ return str(ipaddress.ip_address(ip_int))
529
+
530
+ @staticmethod
531
+ def calculate_network(ip: str, netmask: str) -> str:
532
+ """Calculate network address from IP and netmask"""
533
+ try:
534
+ interface = ipaddress.ip_interface(f"{ip}/{netmask}")
535
+ return str(interface.network)
536
+ except (ipaddress.AddressValueError, ValueError):
537
+ return "0.0.0.0/0"
538
+
539
+ @staticmethod
540
+ def is_private_ip(ip: str) -> bool:
541
+ """Check if IP address is private"""
542
+ try:
543
+ ip_obj = ipaddress.ip_address(ip)
544
+ return ip_obj.is_private
545
+ except (ipaddress.AddressValueError, ValueError):
546
+ return False
547
+
548
+ @staticmethod
549
+ def is_multicast_ip(ip: str) -> bool:
550
+ """Check if IP address is multicast"""
551
+ try:
552
+ ip_obj = ipaddress.ip_address(ip)
553
+ return ip_obj.is_multicast
554
+ except (ipaddress.AddressValueError, ValueError):
555
+ return False
556
+
557
+ @staticmethod
558
+ def validate_cidr(cidr: str) -> bool:
559
+ """Validate CIDR notation"""
560
+ try:
561
+ ipaddress.ip_network(cidr, strict=False)
562
+ return True
563
+ except (ipaddress.AddressValueError, ValueError):
564
+ return False
565
+
database/app.db ADDED
Binary file (45.1 kB). View file
 
flask_app.log ADDED
Binary file (2.14 kB). View file
 
main.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ # DON\'T CHANGE THIS !!!
4
+ sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
5
+
6
+ from flask import Flask, send_from_directory
7
+ from models.user import db
8
+ from routes.user import user_bp
9
+ from routes.auth import auth_bp
10
+ from routes.isp_api import init_engines, isp_api
11
+ from core.openvpn_manager import initialize_openvpn_manager
12
+
13
+
14
+ app = Flask(__name__, static_folder=os.path.join(os.path.dirname(__file__), 'static'))
15
+ app.config['SECRET_KEY'] = 'asdf#FGSgvasgf$5$WGT'
16
+
17
+ app.register_blueprint(user_bp, url_prefix='/api')
18
+ app.register_blueprint(isp_api, url_prefix='/api')
19
+ app.register_blueprint(auth_bp, url_prefix='/api')
20
+
21
+ # uncomment if you need to use database
22
+ app.config['SQLALCHEMY_DATABASE_URI'] = f"sqlite:///{os.path.join(os.path.dirname(__file__), 'database', 'app.db')}"
23
+ app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
24
+ db.init_app(app)
25
+
26
+ with app.app_context():
27
+ db.create_all()
28
+
29
+ # Default configuration for engines
30
+ app.config["dhcp"] = {
31
+ "network": "10.0.0.0/24",
32
+ "range_start": "10.0.0.10",
33
+ "range_end": "10.0.0.100",
34
+ "lease_time": 3600,
35
+ "gateway": "10.0.0.1",
36
+ "dns_servers": ["8.8.8.8", "8.8.4.4"]
37
+ }
38
+
39
+ # Initialize engines only once, when the Flask app is not in debug mode's reloader process
40
+ if not app.debug or os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
41
+ init_engines(app.config)
42
+ initialize_openvpn_manager(app.config)
43
+
44
+ @app.route("/auth")
45
+ def serve_auth():
46
+ return send_from_directory(app.static_folder, "auth.html")
47
+
48
+ @app.route("/dashboard")
49
+ def serve_dashboard():
50
+ return send_from_directory(app.static_folder, "dashboard.html")
51
+
52
+ @app.route("/")
53
+ def serve_root():
54
+ return send_from_directory(app.static_folder, "index.html")
55
+
56
+ @app.route('/<path:path>')
57
+ def serve(path):
58
+ static_folder_path = app.static_folder
59
+ if static_folder_path is None:
60
+ return "Static folder not configured", 404
61
+
62
+ if path != "" and os.path.exists(os.path.join(static_folder_path, path)):
63
+ return send_from_directory(static_folder_path, path)
64
+ else:
65
+ index_path = os.path.join(static_folder_path, 'index.html')
66
+ if os.path.exists(index_path):
67
+ return send_from_directory(static_folder_path, 'index.html')
68
+ else:
69
+ return "index.html not found", 404
70
+
71
+
72
+ if __name__ == '__main__':
73
+ app.run(host='0.0.0.0', port=5000, debug=False)
74
+
75
+
76
+
main_isp.py ADDED
@@ -0,0 +1,273 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Main ISP Application
3
+
4
+ Integrates all core modules and provides the main application entry point
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import json
10
+ import threading
11
+ import time
12
+ from flask import Flask
13
+ from flask_cors import CORS
14
+
15
+ # Add project root to path
16
+ sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
17
+
18
+ # Import routes and core modules
19
+ from routes.isp_api import isp_api, init_engines
20
+
21
+
22
+ def load_config():
23
+ """Load configuration from file or use defaults"""
24
+ config_file = os.path.join(os.path.dirname(__file__), 'config.json')
25
+
26
+ default_config = {
27
+ "dhcp": {
28
+ "network": "10.0.0.0/24",
29
+ "range_start": "10.0.0.10",
30
+ "range_end": "10.0.0.100",
31
+ "lease_time": 3600,
32
+ "gateway": "10.0.0.1",
33
+ "dns_servers": ["8.8.8.8", "8.8.4.4"]
34
+ },
35
+ "nat": {
36
+ "port_range_start": 10000,
37
+ "port_range_end": 65535,
38
+ "session_timeout": 300,
39
+ "host_ip": "0.0.0.0"
40
+ },
41
+ "firewall": {
42
+ "default_policy": "ACCEPT",
43
+ "log_blocked": True,
44
+ "log_accepted": False,
45
+ "max_log_entries": 10000,
46
+ "rules": [
47
+ {
48
+ "rule_id": "allow_dhcp",
49
+ "priority": 1,
50
+ "action": "ACCEPT",
51
+ "direction": "BOTH",
52
+ "dest_port": "67,68",
53
+ "protocol": "UDP",
54
+ "description": "Allow DHCP traffic",
55
+ "enabled": True
56
+ },
57
+ {
58
+ "rule_id": "allow_dns",
59
+ "priority": 2,
60
+ "action": "ACCEPT",
61
+ "direction": "BOTH",
62
+ "dest_port": "53",
63
+ "protocol": "UDP",
64
+ "description": "Allow DNS traffic",
65
+ "enabled": True
66
+ }
67
+ ]
68
+ },
69
+ "tcp": {
70
+ "initial_window": 65535,
71
+ "max_retries": 3,
72
+ "timeout": 300,
73
+ "time_wait_timeout": 120,
74
+ "mss": 1460
75
+ },
76
+ "router": {
77
+ "router_id": "virtual-isp-router",
78
+ "default_gateway": "10.0.0.1",
79
+ "interfaces": [
80
+ {
81
+ "name": "virtual0",
82
+ "ip_address": "10.0.0.1",
83
+ "netmask": "255.255.255.0",
84
+ "enabled": True,
85
+ "mtu": 1500
86
+ }
87
+ ],
88
+ "static_routes": []
89
+ },
90
+ "socket_translator": {
91
+ "connect_timeout": 10,
92
+ "read_timeout": 30,
93
+ "max_connections": 1000,
94
+ "buffer_size": 8192
95
+ },
96
+ "packet_bridge": {
97
+ "websocket_host": "0.0.0.0",
98
+ "websocket_port": 8765,
99
+ "tcp_host": "0.0.0.0",
100
+ "tcp_port": 8766,
101
+ "max_clients": 100,
102
+ "client_timeout": 300
103
+ },
104
+ "session_tracker": {
105
+ "max_sessions": 10000,
106
+ "session_timeout": 3600,
107
+ "cleanup_interval": 300,
108
+ "metrics_retention": 86400
109
+ },
110
+ "logger": {
111
+ "log_level": "INFO",
112
+ "log_to_file": True,
113
+ "log_file_path": "/tmp/virtual_isp.log",
114
+ "log_file_max_size": 10485760,
115
+ "log_file_backup_count": 5,
116
+ "log_to_console": True,
117
+ "structured_logging": True,
118
+ "max_memory_logs": 10000
119
+ },
120
+ "openvpn": {
121
+ "server_config_path": "/etc/openvpn/server/server.conf",
122
+ "ca_cert_path": "/etc/openvpn/server/ca.crt",
123
+ "server_cert_path": "/etc/openvpn/server/server.crt",
124
+ "server_key_path": "/etc/openvpn/server/server.key",
125
+ "dh_path": "/etc/openvpn/server/dh.pem",
126
+ "vpn_network": "10.8.0.0/24",
127
+ "vpn_server_ip": "10.8.0.1",
128
+ "vpn_port": 1194,
129
+ "protocol": "udp",
130
+ "auto_start": False,
131
+ "client_to_client": False,
132
+ "push_routes": [
133
+ "redirect-gateway def1 bypass-dhcp",
134
+ "dhcp-option DNS 8.8.8.8",
135
+ "dhcp-option DNS 8.8.4.4"
136
+ ]
137
+ }
138
+ }
139
+
140
+ if os.path.exists(config_file):
141
+ try:
142
+ with open(config_file, 'r') as f:
143
+ file_config = json.load(f)
144
+
145
+ # Merge with defaults
146
+ def merge_config(default, override):
147
+ result = default.copy()
148
+ for key, value in override.items():
149
+ if key in result and isinstance(result[key], dict) and isinstance(value, dict):
150
+ result[key] = merge_config(result[key], value)
151
+ else:
152
+ result[key] = value
153
+ return result
154
+
155
+ return merge_config(default_config, file_config)
156
+
157
+ except Exception as e:
158
+ print(f"Error loading config file: {e}")
159
+ print("Using default configuration")
160
+ return default_config
161
+ else:
162
+ # Save default config
163
+ try:
164
+ with open(config_file, 'w') as f:
165
+ json.dump(default_config, f, indent=2)
166
+ print(f"Created default configuration file: {config_file}")
167
+ except Exception as e:
168
+ print(f"Could not save default config: {e}")
169
+
170
+ return default_config
171
+
172
+
173
+ def create_app():
174
+ """Create and configure Flask application"""
175
+ app = Flask(__name__, static_folder=os.path.join(os.path.dirname(__file__), 'static'))
176
+
177
+ # Enable CORS for all routes
178
+ CORS(app, origins="*", allow_headers=["Content-Type", "Authorization"])
179
+
180
+ # Load configuration
181
+ config = load_config()
182
+ app.config['ISP_CONFIG'] = config
183
+
184
+ # Register blueprints
185
+ app.register_blueprint(isp_api, url_prefix='/api')
186
+
187
+ # Initialize engines
188
+ init_engines(config)
189
+
190
+ # Serve static files
191
+ @app.route('/', defaults={'path': ''})
192
+ @app.route('/<path:path>')
193
+ def serve_static(path):
194
+ static_folder_path = app.static_folder
195
+ if static_folder_path is None:
196
+ return "Static folder not configured", 404
197
+
198
+ if path != "" and os.path.exists(os.path.join(static_folder_path, path)):
199
+ return app.send_static_file(path)
200
+ else:
201
+ index_path = os.path.join(static_folder_path, 'index.html')
202
+ if os.path.exists(index_path):
203
+ return app.send_static_file('index.html')
204
+ else:
205
+ return """
206
+ <!DOCTYPE html>
207
+ <html>
208
+ <head>
209
+ <title>Virtual ISP Stack</title>
210
+ <style>
211
+ body { font-family: Arial, sans-serif; margin: 40px; }
212
+ .container { max-width: 800px; margin: 0 auto; }
213
+ .status { background: #f0f0f0; padding: 20px; border-radius: 5px; }
214
+ .api-link { color: #0066cc; text-decoration: none; }
215
+ .api-link:hover { text-decoration: underline; }
216
+ </style>
217
+ </head>
218
+ <body>
219
+ <div class="container">
220
+ <h1>Virtual ISP Stack</h1>
221
+ <div class="status">
222
+ <h2>System Status</h2>
223
+ <p>The Virtual ISP Stack is running successfully!</p>
224
+ <p><strong>API Endpoint:</strong> <a href="/api/status" class="api-link">/api/status</a></p>
225
+ <p><strong>System Stats:</strong> <a href="/api/stats" class="api-link">/api/stats</a></p>
226
+ </div>
227
+
228
+ <h2>Available API Endpoints</h2>
229
+ <ul>
230
+ <li><a href="/api/config" class="api-link">GET /api/config</a> - System configuration</li>
231
+ <li><a href="/api/status" class="api-link">GET /api/status</a> - System status</li>
232
+ <li><a href="/api/stats" class="api-link">GET /api/stats</a> - System statistics</li>
233
+ <li><a href="/api/dhcp/leases" class="api-link">GET /api/dhcp/leases</a> - DHCP leases</li>
234
+ <li><a href="/api/nat/sessions" class="api-link">GET /api/nat/sessions</a> - NAT sessions</li>
235
+ <li><a href="/api/firewall/rules" class="api-link">GET /api/firewall/rules</a> - Firewall rules</li>
236
+ <li><a href="/api/tcp/connections" class="api-link">GET /api/tcp/connections</a> - TCP connections</li>
237
+ <li><a href="/api/router/routes" class="api-link">GET /api/router/routes</a> - Routing table</li>
238
+ <li><a href="/api/bridge/clients" class="api-link">GET /api/bridge/clients</a> - Bridge clients</li>
239
+ <li><a href="/api/sessions" class="api-link">GET /api/sessions</a> - Session tracking</li>
240
+ <li><a href="/api/logs" class="api-link">GET /api/logs</a> - System logs</li>
241
+ </ul>
242
+
243
+ <h2>WebSocket Bridge</h2>
244
+ <p>WebSocket server running on port 8765 for packet bridge connections.</p>
245
+ <p>TCP server running on port 8766 for packet bridge connections.</p>
246
+ </div>
247
+ </body>
248
+ </html>
249
+ """, 200
250
+
251
+ return app
252
+
253
+
254
+ def main():
255
+ """Main application entry point"""
256
+ print("Starting Virtual ISP Stack...")
257
+
258
+ # Create Flask app
259
+ app = create_app()
260
+
261
+ # Start the application
262
+ print("Virtual ISP Stack started successfully!")
263
+ print("API available at: http://0.0.0.0:5000/api/")
264
+ print("WebSocket bridge at: ws://0.0.0.0:8765")
265
+ print("TCP bridge at: tcp://0.0.0.0:8766")
266
+
267
+ # Run Flask app
268
+ app.run(host='0.0.0.0', port=5000, debug=False, threaded=True)
269
+
270
+
271
+ if __name__ == '__main__':
272
+ main()
273
+
models/__pycache__/enhanced_user.cpython-311.pyc ADDED
Binary file (26.2 kB). View file
 
models/__pycache__/user.cpython-311.pyc ADDED
Binary file (1.3 kB). View file
 
models/enhanced_user.py ADDED
@@ -0,0 +1,427 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Enhanced User Model with Authentication and VPN Client Management
3
+
4
+ This module provides comprehensive user management with security features,
5
+ VPN client management, and session tracking capabilities.
6
+ """
7
+
8
+
9
+ from werkzeug.security import generate_password_hash, check_password_hash
10
+ from datetime import datetime, timedelta
11
+ import secrets
12
+ import jwt
13
+ import re
14
+ from flask import current_app
15
+
16
+ from .user import db
17
+
18
+ class User(db.Model):
19
+ """Enhanced User model with authentication and VPN management"""
20
+ __tablename__ = 'users'
21
+
22
+ id = db.Column(db.Integer, primary_key=True)
23
+ username = db.Column(db.String(80), unique=True, nullable=False, index=True)
24
+ email = db.Column(db.String(120), unique=True, nullable=False, index=True)
25
+ password_hash = db.Column(db.String(255), nullable=False)
26
+ salt = db.Column(db.String(32), nullable=False)
27
+ created_at = db.Column(db.DateTime, default=datetime.utcnow)
28
+ last_login = db.Column(db.DateTime)
29
+ is_active = db.Column(db.Boolean, default=True)
30
+ is_admin = db.Column(db.Boolean, default=False)
31
+ subscription_type = db.Column(db.String(20), default='free')
32
+ subscription_expires = db.Column(db.DateTime)
33
+ max_concurrent_connections = db.Column(db.Integer, default=1)
34
+ bandwidth_limit_mbps = db.Column(db.Integer, default=10)
35
+ email_verified = db.Column(db.Boolean, default=False)
36
+ email_verification_token = db.Column(db.String(64))
37
+ two_factor_enabled = db.Column(db.Boolean, default=False)
38
+ two_factor_secret = db.Column(db.String(32))
39
+ password_reset_token = db.Column(db.String(64))
40
+ password_reset_expires = db.Column(db.DateTime)
41
+ failed_login_attempts = db.Column(db.Integer, default=0)
42
+ account_locked_until = db.Column(db.DateTime)
43
+
44
+ # Relationships
45
+ vpn_clients = db.relationship('VPNClient', backref='user', lazy=True, cascade='all, delete-orphan')
46
+ vpn_sessions = db.relationship('VPNSession', backref='user', lazy=True)
47
+
48
+ def __init__(self, username, email, password=None):
49
+ self.username = username
50
+ self.email = email
51
+ if password:
52
+ self.set_password(password)
53
+ self.email_verification_token = secrets.token_urlsafe(32)
54
+
55
+ def set_password(self, password):
56
+ """Set password with secure hashing and salt"""
57
+ if not self.validate_password_strength(password):
58
+ raise ValueError("Password does not meet security requirements")
59
+
60
+ self.salt = secrets.token_hex(16)
61
+ self.password_hash = generate_password_hash(password + self.salt)
62
+ self.failed_login_attempts = 0
63
+ self.account_locked_until = None
64
+
65
+ def check_password(self, password):
66
+ """Verify password against hash"""
67
+ if self.is_account_locked():
68
+ return False
69
+
70
+ is_valid = check_password_hash(self.password_hash, password + self.salt)
71
+
72
+ if is_valid:
73
+ self.failed_login_attempts = 0
74
+ self.last_login = datetime.utcnow()
75
+ else:
76
+ self.failed_login_attempts += 1
77
+ if self.failed_login_attempts >= 5:
78
+ self.account_locked_until = datetime.utcnow() + timedelta(minutes=30)
79
+
80
+ return is_valid
81
+
82
+ def is_account_locked(self):
83
+ """Check if account is locked due to failed login attempts"""
84
+ if self.account_locked_until and datetime.utcnow() < self.account_locked_until:
85
+ return True
86
+ elif self.account_locked_until and datetime.utcnow() >= self.account_locked_until:
87
+ # Unlock account
88
+ self.account_locked_until = None
89
+ self.failed_login_attempts = 0
90
+ return False
91
+
92
+ @staticmethod
93
+ def validate_password_strength(password):
94
+ """Validate password meets security requirements"""
95
+ if len(password) < 8:
96
+ return False
97
+ if not re.search(r'[A-Z]', password):
98
+ return False
99
+ if not re.search(r'[a-z]', password):
100
+ return False
101
+ if not re.search(r'\d', password):
102
+ return False
103
+ if not re.search(r'[!@#$%^&*(),.?":{}|<>]', password):
104
+ return False
105
+ return True
106
+
107
+ @staticmethod
108
+ def validate_email(email):
109
+ """Validate email format"""
110
+ pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
111
+ return re.match(pattern, email) is not None
112
+
113
+ @staticmethod
114
+ def validate_username(username):
115
+ """Validate username format"""
116
+ if len(username) < 3 or len(username) > 80:
117
+ return False
118
+ if not re.match(r'^[a-zA-Z0-9_-]+$', username):
119
+ return False
120
+ return True
121
+
122
+ def generate_auth_token(self, expires_in=3600):
123
+ """Generate JWT authentication token"""
124
+ payload = {
125
+ 'user_id': self.id,
126
+ 'username': self.username,
127
+ 'email': self.email,
128
+ 'subscription_type': self.subscription_type,
129
+ 'is_admin': self.is_admin,
130
+ 'exp': datetime.utcnow() + timedelta(seconds=expires_in),
131
+ 'iat': datetime.utcnow()
132
+ }
133
+ return jwt.encode(payload, current_app.config['SECRET_KEY'], algorithm='HS256')
134
+
135
+ def generate_refresh_token(self, expires_in=2592000): # 30 days
136
+ """Generate refresh token for extended sessions"""
137
+ payload = {
138
+ 'user_id': self.id,
139
+ 'type': 'refresh',
140
+ 'exp': datetime.utcnow() + timedelta(seconds=expires_in),
141
+ 'iat': datetime.utcnow()
142
+ }
143
+ return jwt.encode(payload, current_app.config['SECRET_KEY'], algorithm='HS256')
144
+
145
+ @staticmethod
146
+ def verify_auth_token(token):
147
+ """Verify JWT authentication token"""
148
+ try:
149
+ payload = jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=['HS256'])
150
+ if payload.get('type') == 'refresh':
151
+ return None # Refresh tokens cannot be used for authentication
152
+ return User.query.get(payload['user_id'])
153
+ except jwt.ExpiredSignatureError:
154
+ return None
155
+ except jwt.InvalidTokenError:
156
+ return None
157
+
158
+ @staticmethod
159
+ def verify_refresh_token(token):
160
+ """Verify refresh token and return user"""
161
+ try:
162
+ payload = jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=['HS256'])
163
+ if payload.get('type') != 'refresh':
164
+ return None
165
+ return User.query.get(payload['user_id'])
166
+ except jwt.ExpiredSignatureError:
167
+ return None
168
+ except jwt.InvalidTokenError:
169
+ return None
170
+
171
+ def generate_password_reset_token(self):
172
+ """Generate password reset token"""
173
+ self.password_reset_token = secrets.token_urlsafe(32)
174
+ self.password_reset_expires = datetime.utcnow() + timedelta(hours=1)
175
+ return self.password_reset_token
176
+
177
+ def verify_password_reset_token(self, token):
178
+ """Verify password reset token"""
179
+ if (self.password_reset_token == token and
180
+ self.password_reset_expires and
181
+ datetime.utcnow() < self.password_reset_expires):
182
+ return True
183
+ return False
184
+
185
+ def reset_password(self, new_password, token):
186
+ """Reset password using reset token"""
187
+ if not self.verify_password_reset_token(token):
188
+ return False
189
+
190
+ self.set_password(new_password)
191
+ self.password_reset_token = None
192
+ self.password_reset_expires = None
193
+ return True
194
+
195
+ def verify_email(self, token):
196
+ """Verify email using verification token"""
197
+ if self.email_verification_token == token:
198
+ self.email_verified = True
199
+ self.email_verification_token = None
200
+ return True
201
+ return False
202
+
203
+ def can_create_vpn_client(self):
204
+ """Check if user can create additional VPN clients"""
205
+ active_clients = len([c for c in self.vpn_clients if c.is_active])
206
+
207
+ if self.subscription_type == 'free':
208
+ return active_clients < 1
209
+ elif self.subscription_type == 'premium':
210
+ return active_clients < 5
211
+ elif self.subscription_type == 'enterprise':
212
+ return active_clients < 50
213
+
214
+ return False
215
+
216
+ def get_active_sessions_count(self):
217
+ """Get count of active VPN sessions"""
218
+ return len([s for s in self.vpn_sessions if s.disconnected_at is None])
219
+
220
+ def can_connect_vpn(self):
221
+ """Check if user can establish new VPN connections"""
222
+ active_sessions = self.get_active_sessions_count()
223
+ return active_sessions < self.max_concurrent_connections
224
+
225
+ def get_bandwidth_usage_today(self):
226
+ """Get bandwidth usage for today"""
227
+ today = datetime.utcnow().date()
228
+ today_sessions = [s for s in self.vpn_sessions
229
+ if s.connected_at and s.connected_at.date() == today]
230
+
231
+ total_bytes = sum(s.bytes_received + s.bytes_sent for s in today_sessions)
232
+ return total_bytes
233
+
234
+ def is_subscription_active(self):
235
+ """Check if subscription is active"""
236
+ if self.subscription_type == 'free':
237
+ return True
238
+
239
+ return (self.subscription_expires and
240
+ datetime.utcnow() < self.subscription_expires)
241
+
242
+ def to_dict(self, include_sensitive=False):
243
+ """Convert user to dictionary"""
244
+ data = {
245
+ 'id': self.id,
246
+ 'username': self.username,
247
+ 'email': self.email,
248
+ 'created_at': self.created_at.isoformat() if self.created_at else None,
249
+ 'last_login': self.last_login.isoformat() if self.last_login else None,
250
+ 'is_active': self.is_active,
251
+ 'subscription_type': self.subscription_type,
252
+ 'subscription_expires': self.subscription_expires.isoformat() if self.subscription_expires else None,
253
+ 'max_concurrent_connections': self.max_concurrent_connections,
254
+ 'bandwidth_limit_mbps': self.bandwidth_limit_mbps,
255
+ 'email_verified': self.email_verified,
256
+ 'two_factor_enabled': self.two_factor_enabled,
257
+ 'is_subscription_active': self.is_subscription_active(),
258
+ 'active_vpn_clients': len([c for c in self.vpn_clients if c.is_active]),
259
+ 'active_sessions': self.get_active_sessions_count(),
260
+ 'can_create_vpn_client': self.can_create_vpn_client(),
261
+ 'can_connect_vpn': self.can_connect_vpn()
262
+ }
263
+
264
+ if include_sensitive and (self.is_admin or include_sensitive == 'self'):
265
+ data.update({
266
+ 'is_admin': self.is_admin,
267
+ 'failed_login_attempts': self.failed_login_attempts,
268
+ 'account_locked': self.is_account_locked(),
269
+ 'bandwidth_usage_today': self.get_bandwidth_usage_today()
270
+ })
271
+
272
+ return data
273
+
274
+
275
+ class VPNClient(db.Model):
276
+ """VPN Client configuration and management"""
277
+ __tablename__ = 'vpn_clients'
278
+
279
+ id = db.Column(db.Integer, primary_key=True)
280
+ user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
281
+ client_name = db.Column(db.String(100), nullable=False)
282
+ protocol = db.Column(db.String(20), nullable=False) # openvpn, ikev2, wireguard
283
+ certificate_serial = db.Column(db.String(50), unique=True)
284
+ private_key_path = db.Column(db.String(255))
285
+ certificate_path = db.Column(db.String(255))
286
+ config_file_path = db.Column(db.String(255))
287
+ created_at = db.Column(db.DateTime, default=datetime.utcnow)
288
+ last_connected = db.Column(db.DateTime)
289
+ is_active = db.Column(db.Boolean, default=True)
290
+ device_type = db.Column(db.String(50)) # windows, macos, linux, ios, android
291
+ public_key = db.Column(db.Text) # For WireGuard
292
+
293
+ # Relationships
294
+ sessions = db.relationship('VPNSession', backref='vpn_client', lazy=True)
295
+
296
+ def __init__(self, user_id, client_name, protocol, device_type=None):
297
+ self.user_id = user_id
298
+ self.client_name = client_name
299
+ self.protocol = protocol
300
+ self.device_type = device_type
301
+
302
+ def get_active_sessions(self):
303
+ """Get active sessions for this client"""
304
+ return [s for s in self.sessions if s.disconnected_at is None]
305
+
306
+ def get_total_bandwidth_usage(self):
307
+ """Get total bandwidth usage for this client"""
308
+ return sum(s.bytes_received + s.bytes_sent for s in self.sessions)
309
+
310
+ def to_dict(self):
311
+ """Convert VPN client to dictionary"""
312
+ return {
313
+ 'id': self.id,
314
+ 'client_name': self.client_name,
315
+ 'protocol': self.protocol,
316
+ 'device_type': self.device_type,
317
+ 'created_at': self.created_at.isoformat() if self.created_at else None,
318
+ 'last_connected': self.last_connected.isoformat() if self.last_connected else None,
319
+ 'is_active': self.is_active,
320
+ 'certificate_serial': self.certificate_serial,
321
+ 'active_sessions': len(self.get_active_sessions()),
322
+ 'total_bandwidth_usage': self.get_total_bandwidth_usage()
323
+ }
324
+
325
+
326
+ class VPNSession(db.Model):
327
+ """VPN Session tracking"""
328
+ __tablename__ = 'vpn_sessions'
329
+
330
+ id = db.Column(db.Integer, primary_key=True)
331
+ user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
332
+ client_id = db.Column(db.Integer, db.ForeignKey('vpn_clients.id'), nullable=False)
333
+ server_protocol = db.Column(db.String(20), nullable=False)
334
+ client_ip = db.Column(db.String(15))
335
+ server_ip = db.Column(db.String(15))
336
+ client_real_ip = db.Column(db.String(45)) # Support IPv6
337
+ connected_at = db.Column(db.DateTime, default=datetime.utcnow)
338
+ disconnected_at = db.Column(db.DateTime)
339
+ bytes_received = db.Column(db.BigInteger, default=0)
340
+ bytes_sent = db.Column(db.BigInteger, default=0)
341
+ session_duration = db.Column(db.Integer) # in seconds
342
+ disconnect_reason = db.Column(db.String(100))
343
+
344
+ def __init__(self, user_id, client_id, server_protocol, client_ip=None, server_ip=None, client_real_ip=None):
345
+ self.user_id = user_id
346
+ self.client_id = client_id
347
+ self.server_protocol = server_protocol
348
+ self.client_ip = client_ip
349
+ self.server_ip = server_ip
350
+ self.client_real_ip = client_real_ip
351
+
352
+ def disconnect(self, reason=None):
353
+ """Mark session as disconnected"""
354
+ self.disconnected_at = datetime.utcnow()
355
+ self.disconnect_reason = reason
356
+ if self.connected_at:
357
+ self.session_duration = int((self.disconnected_at - self.connected_at).total_seconds())
358
+
359
+ def is_active(self):
360
+ """Check if session is active"""
361
+ return self.disconnected_at is None
362
+
363
+ def get_duration(self):
364
+ """Get session duration in seconds"""
365
+ if self.disconnected_at:
366
+ return self.session_duration
367
+ elif self.connected_at:
368
+ return int((datetime.utcnow() - self.connected_at).total_seconds())
369
+ return 0
370
+
371
+ def to_dict(self):
372
+ """Convert VPN session to dictionary"""
373
+ return {
374
+ 'id': self.id,
375
+ 'client_id': self.client_id,
376
+ 'server_protocol': self.server_protocol,
377
+ 'client_ip': self.client_ip,
378
+ 'server_ip': self.server_ip,
379
+ 'client_real_ip': self.client_real_ip,
380
+ 'connected_at': self.connected_at.isoformat() if self.connected_at else None,
381
+ 'disconnected_at': self.disconnected_at.isoformat() if self.disconnected_at else None,
382
+ 'bytes_received': self.bytes_received,
383
+ 'bytes_sent': self.bytes_sent,
384
+ 'session_duration': self.get_duration(),
385
+ 'disconnect_reason': self.disconnect_reason,
386
+ 'is_active': self.is_active()
387
+ }
388
+
389
+
390
+ class ServerConfiguration(db.Model):
391
+ """VPN Server configuration management"""
392
+ __tablename__ = 'server_configurations'
393
+
394
+ id = db.Column(db.Integer, primary_key=True)
395
+ protocol = db.Column(db.String(20), nullable=False)
396
+ server_name = db.Column(db.String(100), nullable=False)
397
+ listen_port = db.Column(db.Integer, nullable=False)
398
+ network_cidr = db.Column(db.String(18), nullable=False)
399
+ dns_servers = db.Column(db.Text) # JSON string
400
+ routes = db.Column(db.Text) # JSON string
401
+ is_active = db.Column(db.Boolean, default=True)
402
+ created_at = db.Column(db.DateTime, default=datetime.utcnow)
403
+ updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
404
+ max_clients = db.Column(db.Integer, default=100)
405
+
406
+ def __init__(self, protocol, server_name, listen_port, network_cidr):
407
+ self.protocol = protocol
408
+ self.server_name = server_name
409
+ self.listen_port = listen_port
410
+ self.network_cidr = network_cidr
411
+
412
+ def to_dict(self):
413
+ """Convert server configuration to dictionary"""
414
+ return {
415
+ 'id': self.id,
416
+ 'protocol': self.protocol,
417
+ 'server_name': self.server_name,
418
+ 'listen_port': self.listen_port,
419
+ 'network_cidr': self.network_cidr,
420
+ 'dns_servers': self.dns_servers,
421
+ 'routes': self.routes,
422
+ 'is_active': self.is_active,
423
+ 'created_at': self.created_at.isoformat() if self.created_at else None,
424
+ 'updated_at': self.updated_at.isoformat() if self.updated_at else None,
425
+ 'max_clients': self.max_clients
426
+ }
427
+
models/user.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask_sqlalchemy import SQLAlchemy
2
+
3
+ db = SQLAlchemy()
4
+
5
+ class User(db.Model):
6
+ id = db.Column(db.Integer, primary_key=True)
7
+ username = db.Column(db.String(80), unique=True, nullable=False)
8
+ email = db.Column(db.String(120), unique=True, nullable=False)
9
+
10
+ def __repr__(self):
11
+ return f'<User {self.username}>'
12
+
13
+ def to_dict(self):
14
+ return {
15
+ 'id': self.id,
16
+ 'username': self.username,
17
+ 'email': self.email
18
+ }
19
+
20
+
openvpn/ca.crt ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDMzCCAhugAwIBAgIUNO765P4t/yD/PnIFTMVs0Q32TJYwDQYJKoZIhvcNAQEL
3
+ BQAwDjEMMAoGA1UEAwwDeWVzMB4XDTI1MDgwMjAxMjkzNVoXDTM1MDczMTAxMjkz
4
+ NVowDjEMMAoGA1UEAwwDeWVzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
5
+ AQEAtwhMGXouHnHBRd2RhdrW8sOMgqt4wDXZC0J+4UMjOX6Y7t2O1Sgw/sWhwFPk
6
+ QF/cMoQIvsucklPogcnzzGtv9zDkAXyVyCC27UYbg8JfWZK3ZMrt6dfEmYf4KKXm
7
+ D6PLn9guxzBB63dhEWx/7fd6H9C/rK/u0rOh15DQRnfEI468cmXS5uNg8ke/73+y
8
+ Gzb6q7ZOFByBAwM0hW0lStBaIIcxouFrIK8B72O8H+6t10K1GvgiBhKvM3cc8dpN
9
+ y4qvRoN/o+eXarZG7G9dfm9OFgdd9LoXPTTbO+ftFPKOq4F41PnMd2Zcyk7P3GCr
10
+ 3oK7NbISxZ5efLpy45lgSpqKBwIDAQABo4GIMIGFMB0GA1UdDgQWBBQIi0Er30cV
11
+ Qzi+U/LPV4Lf3yvGIzBJBgNVHSMEQjBAgBQIi0Er30cVQzi+U/LPV4Lf3yvGI6ES
12
+ pBAwDjEMMAoGA1UEAwwDeWVzghQ07vrk/i3/IP8+cgVMxWzRDfZMljAMBgNVHRME
13
+ BTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAHzfSFbi1G7WC
14
+ vMSOqSv4/jlBExnz/AlLUBHhgDomIdLK8Pb3tyCD5IYkmi0NT5x6DORcOV2ow1JZ
15
+ o4BL7OVV+fhz3VKXEpG+s3gq5j2m+raqLtu6QKBGg7SIUZ4MLjggvAcPjsK+n8sK
16
+ 86sAUFVTccBxJlKBShAUPSNihyWwxB4PQFvwhefNQSoID1kAB2Fzf1beMX6Gp6Lj
17
+ ldI6e63lpYtIbp4+2F5SxJ/hGTUx+nWbOAHPvhBfhN6sEu9G1C5KPR0cm+xxOpZ9
18
+ lA7y4Dea7pyVybR/b7lFquE3TReXCoLx79UNNSv8erIlsy1jh9yXDnTCk8SN1dpO
19
+ YwJ9U0AHXA==
20
+ -----END CERTIFICATE-----
openvpn/dh.pem ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ -----BEGIN DH PARAMETERS-----
2
+ MIIBCAKCAQEAlPRBW0tYm271xYHi15JrD3JRlpvdjAm+CZoEq0ElLXvSlIKaNQls
3
+ ITH+KIBBX3pgbFFk03fO9ApF0kSOzycRRCuW970iCkDoFUN9y58EG+BI863FkU1h
4
+ 3dx+c59HqdWXkzFK+SmTfKIe12alZFik5G0Xs0hkphCgPaXvWlojorjQoRfKySw3
5
+ VxpybKS83+l3t2ER3Z03IRvWinlnuxVAcymzeSR9hwIMJi3RmYmNmdXNel/WFAo2
6
+ zT5j2f2OZHtnBhvo1V92Rml+5rJksPX4lJMRNwVEnXwqVUyCQOTTiGTUjLOO2gdk
7
+ HLhH5teetBdKL4tFcldeIJSk3e0oWXbURwIBAg==
8
+ -----END DH PARAMETERS-----
openvpn/server.conf ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ port 1194
2
+ proto udp
3
+ dev tun
4
+ ca /etc/openvpn/server/ca.crt
5
+ cert /etc/openvpn/server/server.crt
6
+ key /etc/openvpn/server/server.key
7
+ dh /etc/openvpn/server/dh.pem
8
+ server 10.8.0.0 255.255.255.0
9
+ ifconfig-pool-persist ipp.txt
10
+ push "redirect-gateway def1 bypass-dhcp"
11
+ push "dhcp-option DNS 8.8.8.8"
12
+ push "dhcp-option DNS 8.8.4.4"
13
+ keepalive 10 120
14
+ cipher AES-256-CBC
15
+ persist-key
16
+ persist-tun
17
+ status openvpn-status.log
18
+ verb 3
19
+ explicit-exit-notify 1
20
+
21
+
openvpn/server.crt ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Certificate:
2
+ Data:
3
+ Version: 3 (0x2)
4
+ Serial Number:
5
+ dd:b5:29:c9:70:b2:b3:65:70:ac:0f:57:30:15:b4:2a
6
+ Signature Algorithm: sha256WithRSAEncryption
7
+ Issuer: CN=yes
8
+ Validity
9
+ Not Before: Aug 2 01:29:38 2025 GMT
10
+ Not After : Nov 5 01:29:38 2027 GMT
11
+ Subject: CN=server
12
+ Subject Public Key Info:
13
+ Public Key Algorithm: rsaEncryption
14
+ Public-Key: (2048 bit)
15
+ Modulus:
16
+ 00:dd:9e:02:fb:e3:57:cd:51:43:36:6a:2f:30:f5:
17
+ a1:42:5c:16:f1:7b:4b:0a:aa:b1:34:b5:86:51:3e:
18
+ 6b:82:2e:59:df:42:21:cf:65:14:ea:8c:93:3c:0a:
19
+ 72:a5:2e:0f:64:1a:ec:76:52:18:b2:d3:a0:df:df:
20
+ 19:83:7e:39:9e:f5:16:18:36:34:ae:57:cf:2c:89:
21
+ 7c:c5:97:e3:8f:d0:83:08:7f:14:0c:74:2c:d2:95:
22
+ 09:6e:42:99:a0:28:69:83:68:f4:9c:0e:b5:3e:08:
23
+ 8f:d8:06:ec:d5:aa:c8:bc:19:4b:ff:e4:99:50:12:
24
+ 67:25:d4:79:94:1f:3d:64:b2:c8:00:ea:97:c2:df:
25
+ b8:1c:dc:69:47:9f:59:df:03:06:5a:32:7a:fa:51:
26
+ 96:45:9a:b7:e7:03:ef:9d:3b:94:51:9d:08:69:bb:
27
+ b0:3e:c8:9c:a3:a0:9c:18:aa:e9:88:ec:96:c3:71:
28
+ b1:f6:a7:09:ff:c0:56:b1:24:22:ab:fc:9a:c5:fc:
29
+ fd:67:8e:1a:86:ff:0a:5b:28:46:b4:20:93:05:b6:
30
+ ff:87:93:66:7d:ae:92:c4:0d:20:99:e9:c5:b8:3d:
31
+ 41:3a:06:83:49:e5:13:2e:d6:33:94:45:6a:36:84:
32
+ f9:c9:61:fe:98:3a:6e:41:ed:d8:8c:f1:55:3d:6d:
33
+ 53:fb
34
+ Exponent: 65537 (0x10001)
35
+ X509v3 extensions:
36
+ X509v3 Basic Constraints:
37
+ CA:FALSE
38
+ X509v3 Subject Key Identifier:
39
+ F4:62:12:72:49:40:C2:8A:46:5A:CB:71:BE:33:58:25:B3:E0:01:AC
40
+ X509v3 Authority Key Identifier:
41
+ keyid:08:8B:41:2B:DF:47:15:43:38:BE:53:F2:CF:57:82:DF:DF:2B:C6:23
42
+ DirName:/CN=yes
43
+ serial:34:EE:FA:E4:FE:2D:FF:20:FF:3E:72:05:4C:C5:6C:D1:0D:F6:4C:96
44
+ X509v3 Extended Key Usage:
45
+ TLS Web Server Authentication
46
+ X509v3 Key Usage:
47
+ Digital Signature, Key Encipherment
48
+ X509v3 Subject Alternative Name:
49
+ DNS:server
50
+ Signature Algorithm: sha256WithRSAEncryption
51
+ Signature Value:
52
+ 85:f7:59:01:c2:99:23:c3:9a:99:2a:0a:bc:5d:7d:1c:e8:7c:
53
+ e9:23:a5:87:08:bd:45:1b:a7:a9:b7:3a:06:b6:91:86:ac:61:
54
+ 03:ae:cd:65:80:0e:e4:81:dc:38:b3:fe:6d:6f:02:e4:9e:43:
55
+ 95:d0:a6:38:30:53:52:14:f1:96:2a:30:69:2f:56:24:65:ba:
56
+ 53:c0:b0:22:23:2b:18:37:a1:0c:45:07:cb:ec:a9:71:f7:96:
57
+ 2a:d2:18:94:f0:07:18:1f:4c:d2:c5:d5:66:8f:1d:5c:08:8d:
58
+ 02:00:d6:0d:df:fd:6e:1e:2a:47:8c:30:fd:5b:46:56:0a:5a:
59
+ d4:6d:d4:99:c8:94:26:36:0b:86:30:dd:cb:3a:2e:a2:f3:80:
60
+ 0f:62:80:f8:9d:ec:98:f2:96:20:4f:46:01:ae:9d:35:7f:34:
61
+ 21:d7:71:89:b6:7a:ce:94:7e:14:e6:bf:b6:08:44:39:24:db:
62
+ aa:cf:54:46:34:8f:67:6c:72:22:f1:eb:e9:94:7d:73:26:f3:
63
+ 2f:72:fe:28:b3:cb:28:c3:4c:14:3d:c3:81:1e:8d:96:96:e5:
64
+ df:af:c4:0a:06:71:16:df:8f:a3:30:50:79:45:95:4c:e8:57:
65
+ ee:ed:38:dd:82:8e:0e:b1:2b:4d:27:2b:6f:bc:c8:1c:91:de:
66
+ 2c:55:69:38
67
+ -----BEGIN CERTIFICATE-----
68
+ MIIDWDCCAkCgAwIBAgIRAN21KclwsrNlcKwPVzAVtCowDQYJKoZIhvcNAQELBQAw
69
+ DjEMMAoGA1UEAwwDeWVzMB4XDTI1MDgwMjAxMjkzOFoXDTI3MTEwNTAxMjkzOFow
70
+ ETEPMA0GA1UEAwwGc2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
71
+ AQEA3Z4C++NXzVFDNmovMPWhQlwW8XtLCqqxNLWGUT5rgi5Z30Ihz2UU6oyTPApy
72
+ pS4PZBrsdlIYstOg398Zg345nvUWGDY0rlfPLIl8xZfjj9CDCH8UDHQs0pUJbkKZ
73
+ oChpg2j0nA61PgiP2Abs1arIvBlL/+SZUBJnJdR5lB89ZLLIAOqXwt+4HNxpR59Z
74
+ 3wMGWjJ6+lGWRZq35wPvnTuUUZ0IabuwPsico6CcGKrpiOyWw3Gx9qcJ/8BWsSQi
75
+ q/yaxfz9Z44ahv8KWyhGtCCTBbb/h5Nmfa6SxA0gmenFuD1BOgaDSeUTLtYzlEVq
76
+ NoT5yWH+mDpuQe3YjPFVPW1T+wIDAQABo4GtMIGqMAkGA1UdEwQCMAAwHQYDVR0O
77
+ BBYEFPRiEnJJQMKKRlrLcb4zWCWz4AGsMEkGA1UdIwRCMECAFAiLQSvfRxVDOL5T
78
+ 8s9Xgt/fK8YjoRKkEDAOMQwwCgYDVQQDDAN5ZXOCFDTu+uT+Lf8g/z5yBUzFbNEN
79
+ 9kyWMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAsGA1UdDwQEAwIFoDARBgNVHREECjAI
80
+ ggZzZXJ2ZXIwDQYJKoZIhvcNAQELBQADggEBAIX3WQHCmSPDmpkqCrxdfRzofOkj
81
+ pYcIvUUbp6m3Oga2kYasYQOuzWWADuSB3Diz/m1vAuSeQ5XQpjgwU1IU8ZYqMGkv
82
+ ViRlulPAsCIjKxg3oQxFB8vsqXH3lirSGJTwBxgfTNLF1WaPHVwIjQIA1g3f/W4e
83
+ KkeMMP1bRlYKWtRt1JnIlCY2C4Yw3cs6LqLzgA9igPid7JjyliBPRgGunTV/NCHX
84
+ cYm2es6UfhTmv7YIRDkk26rPVEY0j2dsciLx6+mUfXMm8y9y/iizyyjDTBQ9w4Ee
85
+ jZaW5d+vxAoGcRbfj6MwUHlFlUzoV+7tON2Cjg6xK00nK2+8yByR3ixVaTg=
86
+ -----END CERTIFICATE-----
openvpn/server.key ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -----BEGIN PRIVATE KEY-----
2
+ MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDdngL741fNUUM2
3
+ ai8w9aFCXBbxe0sKqrE0tYZRPmuCLlnfQiHPZRTqjJM8CnKlLg9kGux2Uhiy06Df
4
+ 3xmDfjme9RYYNjSuV88siXzFl+OP0IMIfxQMdCzSlQluQpmgKGmDaPScDrU+CI/Y
5
+ BuzVqsi8GUv/5JlQEmcl1HmUHz1kssgA6pfC37gc3GlHn1nfAwZaMnr6UZZFmrfn
6
+ A++dO5RRnQhpu7A+yJyjoJwYqumI7JbDcbH2pwn/wFaxJCKr/JrF/P1njhqG/wpb
7
+ KEa0IJMFtv+Hk2Z9rpLEDSCZ6cW4PUE6BoNJ5RMu1jOURWo2hPnJYf6YOm5B7diM
8
+ 8VU9bVP7AgMBAAECggEATtwR0sEYtspSYPQS+9iD/AGZ9m75in+n1Ao+E/3isq28
9
+ tDmrn0moUjgYklZjakzEFEqSVx4qhMPSrKcORKCvb1Vl+dKcF2fOpFn+KK++Pagk
10
+ YGsb3ryeUIbRFsejM/79YNIBrOB89OiGCwiX0QZXLLvRs+qL9Za+1pLPenpNVd2w
11
+ zL+AZ8QkJZdHn1vOZt9vKRlpe8psAt64RHb+LqhYWfeLlpIUjpM5Vu9FFewMGPrw
12
+ n+GVCzK4ylq0pJ9bYwKI5Hw4qnJ3j5bGIumEjYBqqmef1+OTD3r/wyhTGpK9RRAu
13
+ WD9YGJeQx3ybzRL7Wj6k5g0dn+UA82Lh7Y8n9IoSaQKBgQDqP/BU2KapOHgFt2DE
14
+ WHU/+zA7/kfMJMGB5dYy8oXTxUY7WuqX9lja3rC0XuH10JTD6Q21jkTujc0T5/1B
15
+ 4KxuX+nQP/T9b4XzVM3pKWVmHUt6wf24sbuTNxOy/Q/wC7eCnkr04CEl0vf3E56N
16
+ JaLG11dbpcn+9RC9FlUhlYY8QwKBgQDyMcz43915YGOQMkGVZFPvKyOy7ol4fFZv
17
+ VRfRoGx9CfHCIOfh9vmlUy6TR4qAQkCnkL730OsxpW3aDTe3qcAcmhiK7u5TfWrE
18
+ cd1WgrkymJ8hyEk6FSV0GMKrccQeEo2T95cKnk6lNXnEdNp5kx7LBQhL36fEtMXS
19
+ FGCcRkNp6QKBgAbm6WLmm0qDIm4wsAY5AQNomEw8OstWDemQ5xXLNYw+1Mns7Nqb
20
+ ZJTWWOiHnyrKAYggNsoxrfBFd1Rt0nV9dDcwVkhPih1pis3XotWK5bTzigTM8Hff
21
+ rMIyrj7o2+5bugV8OoMqk2903t+F0XchM8GeGLHXmbMMb3jSzqFVsYXXAoGBAII1
22
+ Z/99S7LPsXd6rWvFzqJMzRqLx/iw0D92viGDYBAxYnp9+myvvTO27tlbowilleEA
23
+ nsrY1TmRuOd8J7JkXtaBuiQnpJXaXaZTmS3DhhG/n/4nkcbaS5KJJU/LECcizl74
24
+ w4l/5sRHZbnLIRIvmGSJxhYUnjvQ/HGfZvldhSzRAoGBAMVTrxWedC2XeSMwjdhF
25
+ zeDBAp/dTMEnRaS0j3rp+4a4l7Sus1L/p8gBrJtnf/B43bNvQ5cr2jwH7Ql5cF1A
26
+ A7hpZ3C0trNaf6WqslJQhN8j8Cs85S/8rPGM5yAfyzKTMe0ytLUjn+XiQCqCUFcT
27
+ Inqx4ll7r2tlcI3aMlvN2qsd
28
+ -----END PRIVATE KEY-----
requirements.txt ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Core Flask dependencies
2
+ Flask==3.1.1
3
+ flask-cors==6.0.0
4
+ Flask-SQLAlchemy==3.1.1
5
+ Werkzeug==3.1.3
6
+
7
+ # Database
8
+ SQLAlchemy==2.0.41
9
+
10
+ # Async and networking
11
+ aiohttp==3.12.15
12
+ aiohappyeyeballs==2.6.1
13
+ aiosignal==1.4.0
14
+ websockets==15.0.1
15
+
16
+ # Utilities
17
+ attrs==25.3.0
18
+ blinker==1.9.0
19
+ click==8.2.1
20
+ frozenlist==1.7.0
21
+ greenlet==3.2.3
22
+ idna==3.10
23
+ itsdangerous==2.2.0
24
+ Jinja2==3.1.6
25
+ MarkupSafe==3.0.2
26
+ multidict==6.6.3
27
+ propcache==0.3.2
28
+ typing_extensions==4.14.0
29
+ yarl==1.20.1
30
+ jwt
31
+ # Additional dependencies for VPN management
32
+ psutil==5.9.8
33
+
routes/__pycache__/auth.cpython-311.pyc ADDED
Binary file (30 kB). View file