File size: 6,578 Bytes
6a5b8d8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
"""

PPTP Server Implementation

Handles PPTP tunneling and packet forwarding

"""

import asyncio
import socket
import struct
from typing import Dict, Optional, Tuple
from dataclasses import dataclass
import os
import hmac
import hashlib
from .ip_parser import IPv4Header, IPParser
from .logger import Logger, LogCategory

@dataclass
class PPTPSession:
    call_id: int
    peer_call_id: int
    client_ip: str
    assigned_ip: str
    created_at: float
    last_seen: float
    bytes_in: int = 0
    bytes_out: int = 0

class PPTPServer:
    """PPTP server implementation"""
    
    def __init__(self, logger: Logger, ip_pool_start: str = "10.20.0.2"):
        self.logger = logger
        self.sessions: Dict[int, PPTPSession] = {}  # call_id -> session
        self.next_call_id = 1
        self.next_ip = ip_pool_start
        self._running = False
        self._transport = None
        
    async def start(self, host: str = "0.0.0.0", port: int = 1723):
        """Start PPTP server"""
        loop = asyncio.get_running_loop()
        self._transport, _ = await loop.create_server(
            lambda: PPTPProtocol(self),
            host, port
        )
        self._running = True
        self.logger.info(LogCategory.SYSTEM, "pptp_server", f"PPTP server started on {host}:{port}")

    async def stop(self):
        """Stop PPTP server"""
        if self._transport:
            self._transport.close()
        self._running = False
        self.logger.info(LogCategory.SYSTEM, "pptp_server", "PPTP server stopped")

    def allocate_ip(self) -> str:
        """Allocate next available IP from pool"""
        allocated = self.next_ip
        # Increment last octet, handling rollover
        last_octet = int(self.next_ip.split('.')[-1])
        if last_octet >= 254:
            raise ValueError("IP pool exhausted")
        self.next_ip = f"10.20.0.{last_octet + 1}"
        return allocated

    async def handle_packet(self, data: bytes, addr: Tuple[str, int]):
        """Handle incoming PPTP packet"""
        try:
            if len(data) < 8:
                return
                
            # Parse PPTP header
            message_type = struct.unpack('!H', data[2:4])[0]
            
            if message_type == 1:  # Control Message
                await self._handle_control(data, addr)
            else:  # Data Message (GRE)
                await self._handle_gre(data, addr)
                
        except Exception as e:
            self.logger.error(LogCategory.SYSTEM, "pptp_server", f"Error handling packet: {e}")

    async def _handle_control(self, data: bytes, addr: Tuple[str, int]):
        """Handle PPTP control message"""
        control_type = struct.unpack('!H', data[8:10])[0]
        
        if control_type == 1:  # Start-Control-Connection-Request
            # Send Start-Control-Connection-Reply
            reply = self._build_start_control_reply()
            await self._send_control(reply, addr)
            
        elif control_type == 7:  # Outgoing-Call-Request
            call_id = struct.unpack('!H', data[12:14])[0]
            
            # Create new session
            session = PPTPSession(
                call_id=call_id,
                peer_call_id=self.next_call_id,
                client_ip=addr[0],
                assigned_ip=self.allocate_ip(),
                created_at=asyncio.get_running_loop().time(),
                last_seen=asyncio.get_running_loop().time()
            )
            self.sessions[call_id] = session
            self.next_call_id += 1
            
            # Send Outgoing-Call-Reply
            reply = self._build_outgoing_call_reply(session)
            await self._send_control(reply, addr)

    async def _handle_gre(self, data: bytes, addr: Tuple[str, int]):
        """Handle GRE encapsulated data"""
        try:
            if len(data) < 12:  # GRE v1 header size
                return
                
            # Parse GRE header
            flags = struct.unpack('!H', data[0:2])[0]
            protocol = struct.unpack('!H', data[2:4])[0]
            payload_len = struct.unpack('!H', data[4:6])[0]
            call_id = struct.unpack('!H', data[6:8])[0]
            
            if call_id not in self.sessions:
                return
                
            session = self.sessions[call_id]
            session.last_seen = asyncio.get_running_loop().time()
            
            # Handle PPP payload
            if protocol == 0x880B:  # PPP
                await self._handle_ppp(data[8:], session)
                
        except Exception as e:
            self.logger.error(LogCategory.SYSTEM, "pptp_server", f"Error handling GRE packet: {e}")

    async def _handle_ppp(self, data: bytes, session: PPTPSession):
        """Handle PPP payload"""
        try:
            if len(data) < 4:
                return
                
            protocol = struct.unpack('!H', data[2:4])[0]
            
            if protocol == 0x0021:  # IP
                ip_packet = data[4:]
                await self._handle_ip_packet(ip_packet, session)
                
        except Exception as e:
            self.logger.error(LogCategory.SYSTEM, "pptp_server", f"Error handling PPP packet: {e}")

    async def _handle_ip_packet(self, data: bytes, session: PPTPSession):
        """Handle IP packet inside PPP frame"""
        try:
            # Parse IP header
            ip_header = IPParser.parse_ipv4_header(data)
            
            # Update statistics
            session.bytes_in += len(data)
            
            # Forward packet to destination
            if ip_header.protocol == socket.IPPROTO_TCP:
                await self._forward_tcp(data, session)
            elif ip_header.protocol == socket.IPPROTO_UDP:
                await self._forward_udp(data, session)
                
        except Exception as e:
            self.logger.error(LogCategory.SYSTEM, "pptp_server", f"Error handling IP packet: {e}")

    async def _forward_tcp(self, data: bytes, session: PPTPSession):
        """Forward TCP packet"""
        # This will be handled by the TCP forwarding engine
        # Just a placeholder for now
        pass

    async def _forward_udp(self, data: bytes, session: PPTPSession):
        """Forward UDP packet"""
        # This will be handled by the UDP forwarding engine
        # Just a placeholder for now
        pass