fix: 修复代理问题
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
import enum
|
||||
import ipaddress
|
||||
import socket
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
from .errors import ReplyError
|
||||
from .._helpers import is_ipv4_address
|
||||
|
||||
RSV = NULL = 0x00
|
||||
SOCKS_VER = 0x04
|
||||
|
||||
|
||||
class Command(enum.IntEnum):
|
||||
CONNECT = 0x01
|
||||
BIND = 0x02
|
||||
|
||||
|
||||
class ReplyCode(enum.IntEnum):
|
||||
REQUEST_GRANTED = 0x5A
|
||||
REQUEST_REJECTED_OR_FAILED = 0x5B
|
||||
CONNECTION_FAILED = 0x5C
|
||||
AUTHENTICATION_FAILED = 0x5D
|
||||
|
||||
|
||||
ReplyMessages = {
|
||||
ReplyCode.REQUEST_GRANTED: 'Request granted',
|
||||
ReplyCode.REQUEST_REJECTED_OR_FAILED: 'Request rejected or failed',
|
||||
ReplyCode.CONNECTION_FAILED: (
|
||||
'Request rejected because SOCKS server cannot connect to identd on the client'
|
||||
),
|
||||
ReplyCode.AUTHENTICATION_FAILED: (
|
||||
'Request rejected because the client program and identd report different user-ids'
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class ConnectRequest:
|
||||
host: str # hostname or IPv4 address
|
||||
port: int
|
||||
user_id: Optional[str]
|
||||
|
||||
def dumps(self):
|
||||
port_bytes = self.port.to_bytes(2, 'big')
|
||||
include_hostname = False
|
||||
|
||||
if is_ipv4_address(self.host):
|
||||
host_bytes = ipaddress.IPv4Address(self.host).packed
|
||||
else:
|
||||
include_hostname = True
|
||||
host_bytes = bytes([NULL, NULL, NULL, 1])
|
||||
|
||||
data = bytearray([SOCKS_VER, Command.CONNECT])
|
||||
data += port_bytes
|
||||
data += host_bytes
|
||||
|
||||
if self.user_id:
|
||||
data += self.user_id.encode('ascii')
|
||||
|
||||
data.append(NULL)
|
||||
|
||||
if include_hostname:
|
||||
data += self.host.encode('idna')
|
||||
data.append(NULL)
|
||||
|
||||
return bytes(data)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ConnectReply:
|
||||
SIZE = 8
|
||||
|
||||
rsv: int
|
||||
reply: ReplyCode
|
||||
host: str # should be ignored when using Command.CONNECT
|
||||
port: int # should be ignored when using Command.CONNECT
|
||||
|
||||
@classmethod
|
||||
def loads(cls, data: bytes) -> 'ConnectReply':
|
||||
if len(data) != cls.SIZE:
|
||||
raise ReplyError('Malformed connect reply')
|
||||
|
||||
rsv = data[0]
|
||||
if rsv != RSV: # pragma: no cover
|
||||
raise ReplyError(f'Unexpected reply version: {data[0]:#02X}')
|
||||
|
||||
try:
|
||||
reply = ReplyCode(data[1])
|
||||
except ValueError:
|
||||
raise ReplyError(f'Invalid reply code: {data[1]:#02X}')
|
||||
|
||||
if reply != ReplyCode.REQUEST_GRANTED: # pragma: no cover
|
||||
msg = ReplyMessages.get(reply, 'Unknown error')
|
||||
raise ReplyError(msg, error_code=reply)
|
||||
|
||||
try:
|
||||
port = int.from_bytes(data[2:4], byteorder="big")
|
||||
except ValueError:
|
||||
raise ReplyError('Invalid port data')
|
||||
|
||||
try:
|
||||
host = socket.inet_ntop(socket.AF_INET, data[4:8])
|
||||
except ValueError:
|
||||
raise ReplyError('Invalid port data')
|
||||
|
||||
return cls(rsv=rsv, reply=reply, host=host, port=port)
|
||||
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
class Connection:
|
||||
def send(self, request: ConnectRequest) -> bytes:
|
||||
return request.dumps()
|
||||
|
||||
def receive(self, data: bytes) -> ConnectReply:
|
||||
return ConnectReply.loads(data)
|
||||
Reference in New Issue
Block a user