mirror of
https://github.com/NovaOSS/nova-api.git
synced 2024-11-25 21:23:56 +01:00
Fixed trademarks
This commit is contained in:
parent
6aa22e8c55
commit
c0a797599b
|
@ -1,5 +1,5 @@
|
||||||
# ☄️ Nova API Server
|
# ☄️ Nova API Server
|
||||||
Reverse proxy server for OpenAI's API.
|
Reverse proxy server for "Closed"AI's API.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
Assuming you have a new version of Python 3 and pip installed:
|
Assuming you have a new version of Python 3 and pip installed:
|
||||||
|
@ -28,7 +28,7 @@ pip install .
|
||||||
## `.env` configuration
|
## `.env` configuration
|
||||||
|
|
||||||
### `ACTUAL_IPS` (optional)
|
### `ACTUAL_IPS` (optional)
|
||||||
This is a security measure to make sure a proxy, VPN, Tor or any other IP hiding service is used by the host when accessing OpenAI's API.
|
This is a security measure to make sure a proxy, VPN, Tor or any other IP hiding service is used by the host when accessing "Closed"AI's API.
|
||||||
It is a space separated list of IP addresses that are allowed to access the API.
|
It is a space separated list of IP addresses that are allowed to access the API.
|
||||||
You can also just add the *beginning* of an API address, like `12.123.` to allow all IPs starting with `12.123.`.
|
You can also just add the *beginning* of an API address, like `12.123.` to allow all IPs starting with `12.123.`.
|
||||||
|
|
||||||
|
|
42
api/main.py
42
api/main.py
|
@ -1,19 +1,16 @@
|
||||||
import os
|
import os
|
||||||
import httpx
|
|
||||||
import fastapi
|
import fastapi
|
||||||
from keys import Keys
|
|
||||||
|
|
||||||
from starlette.requests import Request
|
|
||||||
from starlette.responses import StreamingResponse
|
from starlette.responses import StreamingResponse
|
||||||
from starlette.background import BackgroundTask
|
from starlette.requests import Request
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
import security
|
import security
|
||||||
|
import transfer
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
target_api_client = httpx.AsyncClient(base_url='https://api.openai.com/')
|
|
||||||
|
|
||||||
app = fastapi.FastAPI()
|
app = fastapi.FastAPI()
|
||||||
|
|
||||||
|
@ -32,9 +29,6 @@ async def startup_event():
|
||||||
security.enable_proxy()
|
security.enable_proxy()
|
||||||
security.ip_protection_check()
|
security.ip_protection_check()
|
||||||
|
|
||||||
# Setup key cache
|
|
||||||
Keys()
|
|
||||||
|
|
||||||
@app.get('/')
|
@app.get('/')
|
||||||
async def root():
|
async def root():
|
||||||
"""Returns the root endpoint."""
|
"""Returns the root endpoint."""
|
||||||
|
@ -46,34 +40,12 @@ async def root():
|
||||||
}
|
}
|
||||||
|
|
||||||
async def _reverse_proxy(request: Request):
|
async def _reverse_proxy(request: Request):
|
||||||
target_url = f'https://api.openai.com/v1/{request.url.path}'
|
headers = {
|
||||||
key = Keys.get(request.body()['model'])
|
name: value
|
||||||
if not key:
|
for name, value in target_response.headers.items()
|
||||||
return fastapi.responses.JSONResponse(
|
if name.lower() not in EXCLUDED_HEADERS
|
||||||
status_code=400,
|
|
||||||
content={
|
|
||||||
'error': 'No API Key for model given, please try again with a valid model.'
|
|
||||||
}
|
}
|
||||||
)
|
|
||||||
request_to_api = target_api_client.build_request(
|
|
||||||
method=request.method,
|
|
||||||
url=target_url,
|
|
||||||
headers={
|
|
||||||
'Authorization': 'Bearer ' + key,
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
content=await request.body(),
|
|
||||||
)
|
|
||||||
|
|
||||||
api_response = await target_api_client.send(request_to_api, stream=True)
|
# ...
|
||||||
|
|
||||||
print(f'[{request.method}] {request.url.path} {api_response.status_code}')
|
|
||||||
Keys(key).unlock()
|
|
||||||
return StreamingResponse(
|
|
||||||
api_response.aiter_raw(),
|
|
||||||
status_code=api_response.status_code,
|
|
||||||
headers=api_response.headers,
|
|
||||||
background=BackgroundTask(api_response.aclose)
|
|
||||||
)
|
|
||||||
|
|
||||||
app.add_route('/{path:path}', _reverse_proxy, ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'])
|
app.add_route('/{path:path}', _reverse_proxy, ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'])
|
||||||
|
|
119
api/proxies.py
Normal file
119
api/proxies.py
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
"""This module contains the Proxy class, which represents a proxy."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import httpx
|
||||||
|
import socket
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
from sockslib import socks
|
||||||
|
from rich import print
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
class Proxy:
|
||||||
|
"""Represents a proxy. The type can be either http, https, socks4 or socks5."""
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
proxy_type: str='http',
|
||||||
|
host: str='127.0.0.1',
|
||||||
|
port: int=8080,
|
||||||
|
username: str=None,
|
||||||
|
password: str=None
|
||||||
|
):
|
||||||
|
self.proxy_type = proxy_type
|
||||||
|
self.ip_address = socket.gethostbyname(host)
|
||||||
|
self.host = host
|
||||||
|
self.port = port
|
||||||
|
self.username = username
|
||||||
|
self.password = password
|
||||||
|
|
||||||
|
@property
|
||||||
|
def auth(self):
|
||||||
|
"""Returns the authentication part of the proxy URL, if the proxy has a username and password."""
|
||||||
|
return f'{self.username}:{self.password}@' if all([self.username, self.password]) else ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def protocol(self):
|
||||||
|
"""Makes sure the hostnames are resolved correctly using the proxy.
|
||||||
|
See https://stackoverflow.com/a/43266186
|
||||||
|
"""
|
||||||
|
return self.proxy_type# + 'h' if self.proxy_type.startswith('socks') else self.proxy_type
|
||||||
|
|
||||||
|
def proxies(self):
|
||||||
|
"""Returns a dictionary of proxies, ready to be used with the requests library or httpx.
|
||||||
|
"""
|
||||||
|
|
||||||
|
url = f'{self.protocol}://{self.auth}{self.host}:{self.port}'
|
||||||
|
|
||||||
|
proxies_dict = {
|
||||||
|
'http://': url.replace('https', 'http') if self.proxy_type == 'https' else url,
|
||||||
|
'https://': url.replace('http', 'https') if self.proxy_type == 'http' else url
|
||||||
|
}
|
||||||
|
|
||||||
|
return proxies_dict
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.proxy_type}://{len(self.auth) * "*"}{self.host}:{self.port}'
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'<Proxy type={self.proxy_type} host={self.host} port={self.port} username={self.username} password={len(self.password) * "*"}>'
|
||||||
|
|
||||||
|
active_proxy = Proxy(
|
||||||
|
proxy_type=os.getenv('PROXY_TYPE', 'http'),
|
||||||
|
host=os.getenv('PROXY_HOST', '127.0.0.1'),
|
||||||
|
port=int(os.getenv('PROXY_PORT', 8080)),
|
||||||
|
username=os.getenv('PROXY_USER'),
|
||||||
|
password=os.getenv('PROXY_PASS')
|
||||||
|
)
|
||||||
|
|
||||||
|
def activate_proxy() -> None:
|
||||||
|
socks.set_default_proxy(
|
||||||
|
proxy_type=socks.PROXY_TYPES[active_proxy.proxy_type.upper()],
|
||||||
|
addr=active_proxy.host,
|
||||||
|
port=active_proxy.port,
|
||||||
|
username=active_proxy.username,
|
||||||
|
password=active_proxy.password
|
||||||
|
)
|
||||||
|
socket.socket = socks.socksocket
|
||||||
|
|
||||||
|
def check_proxy():
|
||||||
|
"""Checks if the proxy is working."""
|
||||||
|
|
||||||
|
resp = httpx.get(
|
||||||
|
url='https://echo.hoppscotch.io/',
|
||||||
|
timeout=20,
|
||||||
|
proxies=active_proxy.proxies()
|
||||||
|
)
|
||||||
|
resp.raise_for_status()
|
||||||
|
|
||||||
|
return resp.ok
|
||||||
|
|
||||||
|
async def check_api():
|
||||||
|
model = 'gpt-3.5-turbo'
|
||||||
|
messages = [
|
||||||
|
{
|
||||||
|
'role': 'user',
|
||||||
|
'content': 'Explain what a wormhole is.'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': 'Bearer ' + os.getenv('CLOSEDAI_KEY')
|
||||||
|
}
|
||||||
|
|
||||||
|
json_data = {
|
||||||
|
'model': model,
|
||||||
|
'messages': messages,
|
||||||
|
'stream': True
|
||||||
|
}
|
||||||
|
|
||||||
|
async with httpx.AsyncClient(timeout=20) as client:
|
||||||
|
async with client.stream("POST", 'https://api.openai.com/v1/chat/completions', headers=headers, json=json_data) as response:
|
||||||
|
response.raise_for_status()
|
||||||
|
async for chunk in response.aiter_text():
|
||||||
|
print(chunk.strip())
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
asyncio.run(check_api())
|
|
@ -1,41 +1,26 @@
|
||||||
import os
|
import os
|
||||||
import socket
|
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
from sockslib import socks
|
|
||||||
from rich import print
|
from rich import print
|
||||||
|
|
||||||
|
import proxies
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
is_proxy_enabled = False
|
is_proxy_enabled = False
|
||||||
|
|
||||||
def enable_proxy():
|
def enable_proxy():
|
||||||
"""Enables the SOCKS5 proxy."""
|
"""Enables the proxy."""
|
||||||
|
|
||||||
global is_proxy_enabled
|
global is_proxy_enabled
|
||||||
|
|
||||||
if all([os.getenv('PROXY_HOST'), os.getenv('PROXY_PORT')]):
|
proxies.activate_proxy()
|
||||||
proxy_type = socks.PROXY_TYPE_HTTP
|
|
||||||
|
|
||||||
if '4' in os.getenv('PROXY_TYPE'):
|
print(f'[green]SUCCESS: Proxy enabled: {proxies.active_proxy}[/green]')
|
||||||
proxy_type = socks.PROXY_TYPE_SOCKS4
|
|
||||||
|
|
||||||
if '5' in os.getenv('PROXY_TYPE'):
|
|
||||||
proxy_type = socks.PROXY_TYPE_SOCKS5
|
|
||||||
|
|
||||||
socks.set_default_proxy(
|
|
||||||
proxy_type=proxy_type,
|
|
||||||
addr=os.getenv('PROXY_HOST'),
|
|
||||||
port=int(os.getenv('PROXY_PORT')),
|
|
||||||
username=os.getenv('PROXY_USER'),
|
|
||||||
password=os.getenv('PROXY_PASS')
|
|
||||||
)
|
|
||||||
socket.socket = socks.socksocket
|
|
||||||
|
|
||||||
is_proxy_enabled = True
|
is_proxy_enabled = True
|
||||||
|
|
||||||
else:
|
|
||||||
print('[yellow]WARNING: PROXY_PORT, PROXY_IP, PROXY_USER, and PROXY_PASS are not set in the .env file or empty. \
|
|
||||||
Consider configuring a SOCKS5 proxy to improve the security.[/yellow]')
|
|
||||||
|
|
||||||
class InsecureIPError(Exception):
|
class InsecureIPError(Exception):
|
||||||
"""Raised when the IP address of the server is not secure."""
|
"""Raised when the IP address of the server is not secure."""
|
||||||
|
|
||||||
|
@ -45,17 +30,27 @@ def ip_protection_check():
|
||||||
actual_ips = os.getenv('ACTUAL_IPS', '').split()
|
actual_ips = os.getenv('ACTUAL_IPS', '').split()
|
||||||
|
|
||||||
if actual_ips:
|
if actual_ips:
|
||||||
detected_ip = httpx.get('https://checkip.amazonaws.com', timeout=5).text.strip()
|
echo_response = httpx.get(
|
||||||
|
url='https://echo.hoppscotch.io/',
|
||||||
|
timeout=15
|
||||||
|
)
|
||||||
|
|
||||||
|
response_data = echo_response.json()
|
||||||
|
response_ip = response_data['headers']['x-forwarded-for']
|
||||||
|
|
||||||
for actual_ip in actual_ips:
|
for actual_ip in actual_ips:
|
||||||
if detected_ip.startswith(actual_ip):
|
if actual_ip in response_data:
|
||||||
raise InsecureIPError(f'IP {detected_ip} is in the values of ACTUAL_IPS of the\
|
raise InsecureIPError(f'IP pattern "{actual_ip}" is in the values of ACTUAL_IPS of the\
|
||||||
.env file. Enable a VPN or proxy to continue.')
|
.env file. Enable a VPN or proxy to continue.')
|
||||||
|
|
||||||
if is_proxy_enabled:
|
if is_proxy_enabled:
|
||||||
print(f'[green]SUCCESS: The IP {detected_ip} was detected, which seems to be a proxy. Great![/green]')
|
print(f'[green]SUCCESS: The IP "{response_ip}" was detected, which seems to be a proxy. Great![/green]')
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print('[yellow]WARNING: ACTUAL_IPS is not set in the .env file or empty.\
|
print('[yellow]WARNING: ACTUAL_IPS is not set in the .env file or empty.\
|
||||||
This means that the real IP of the server could be exposed. If you\'re using something\
|
This means that the real IP of the server could be exposed. If you\'re using something\
|
||||||
like Cloudflare or Repl.it, you can ignore this warning.[/yellow]')
|
like Cloudflare or Repl.it, you can ignore this warning.[/yellow]')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
enable_proxy()
|
||||||
|
ip_protection_check()
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
"""
|
"""
|
||||||
SocksiPy - Python SOCKS module.
|
THE FOLLOWING CODE WAS TAKEN FROM https://raw.githubusercontent.com/m0rtem/CloudFail/master/socks.py
|
||||||
Version 1.5.1
|
|
||||||
|
|
||||||
Code from https://github.com/XX-net/XX-Net/blob/master/code/default/lib/noarch/socks.py
|
SocksiPy - Python SOCKS module.
|
||||||
|
Version 1.5.7
|
||||||
|
|
||||||
Copyright 2006 Dan-Haim. All rights reserved.
|
Copyright 2006 Dan-Haim. All rights reserved.
|
||||||
|
|
||||||
|
@ -54,44 +54,28 @@ Modifications made by Anorov (https://github.com/Anorov)
|
||||||
-Various small bug fixes
|
-Various small bug fixes
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = "1.5.1"
|
__version__ = "1.5.7"
|
||||||
|
|
||||||
import os, sys
|
|
||||||
from base64 import b64encode
|
|
||||||
import socket
|
import socket
|
||||||
import struct
|
import struct
|
||||||
from errno import EOPNOTSUPP, EINVAL, EAGAIN
|
from errno import EOPNOTSUPP, EINVAL, EAGAIN
|
||||||
from io import BytesIO, SEEK_CUR
|
from io import BytesIO
|
||||||
|
from os import SEEK_CUR
|
||||||
|
from base64 import b64encode
|
||||||
try:
|
try:
|
||||||
from collections import Callable
|
|
||||||
except:
|
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
|
except ImportError:
|
||||||
from six import string_types
|
from collections import Callable
|
||||||
|
|
||||||
current_path = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
python_path = os.path.abspath( os.path.join(current_path, os.pardir, os.pardir))
|
|
||||||
if sys.platform == "win32":
|
|
||||||
win32_lib = os.path.abspath( os.path.join(python_path, 'lib', 'win32'))
|
|
||||||
sys.path.append(win32_lib)
|
|
||||||
import win_inet_pton
|
|
||||||
inet_pton = win_inet_pton.inet_pton
|
|
||||||
inet_ntop = win_inet_pton.inet_ntop
|
|
||||||
else:
|
|
||||||
inet_pton = socket.inet_pton
|
|
||||||
inet_ntop = socket.inet_ntop
|
|
||||||
|
|
||||||
from . import utils
|
|
||||||
|
|
||||||
PROXY_TYPE_SOCKS4 = SOCKS4 = 1
|
PROXY_TYPE_SOCKS4 = SOCKS4 = 1
|
||||||
PROXY_TYPE_SOCKS5 = SOCKS5 = 2
|
PROXY_TYPE_SOCKS5 = SOCKS5 = 2
|
||||||
PROXY_TYPE_HTTP = HTTP = 3
|
PROXY_TYPE_HTTP = HTTP = 3
|
||||||
|
|
||||||
PRINTABLE_PROXY_TYPES = {SOCKS4: "SOCKS4", SOCKS5: "SOCKS5", HTTP: "HTTP"}
|
PROXY_TYPES = {"SOCKS4": SOCKS4, "SOCKS5": SOCKS5, "HTTP": HTTP}
|
||||||
|
PRINTABLE_PROXY_TYPES = dict(zip(PROXY_TYPES.values(), PROXY_TYPES.keys()))
|
||||||
|
|
||||||
_orgsocket = _orig_socket = socket.socket
|
_orgsocket = _orig_socket = socket.socket
|
||||||
|
|
||||||
|
|
||||||
class ProxyError(IOError):
|
class ProxyError(IOError):
|
||||||
"""
|
"""
|
||||||
socket_err contains original socket.error exception.
|
socket_err contains original socket.error exception.
|
||||||
|
@ -106,11 +90,6 @@ class ProxyError(IOError):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.msg
|
return self.msg
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
# for %r
|
|
||||||
return repr(self.msg)
|
|
||||||
|
|
||||||
|
|
||||||
class GeneralProxyError(ProxyError): pass
|
class GeneralProxyError(ProxyError): pass
|
||||||
class ProxyConnectionError(ProxyError): pass
|
class ProxyConnectionError(ProxyError): pass
|
||||||
class SOCKS5AuthError(ProxyError): pass
|
class SOCKS5AuthError(ProxyError): pass
|
||||||
|
@ -118,7 +97,6 @@ class SOCKS5Error(ProxyError): pass
|
||||||
class SOCKS4Error(ProxyError): pass
|
class SOCKS4Error(ProxyError): pass
|
||||||
class HTTPError(ProxyError): pass
|
class HTTPError(ProxyError): pass
|
||||||
|
|
||||||
|
|
||||||
SOCKS4_ERRORS = { 0x5B: "Request rejected or failed",
|
SOCKS4_ERRORS = { 0x5B: "Request rejected or failed",
|
||||||
0x5C: "Request rejected because SOCKS server cannot connect to identd on the client",
|
0x5C: "Request rejected because SOCKS server cannot connect to identd on the client",
|
||||||
0x5D: "Request rejected because the client program and identd report different user-ids"
|
0x5D: "Request rejected because the client program and identd report different user-ids"
|
||||||
|
@ -139,7 +117,6 @@ DEFAULT_PORTS = { SOCKS4: 1080,
|
||||||
HTTP: 8080
|
HTTP: 8080
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def set_default_proxy(proxy_type=None, addr=None, port=None, rdns=True, username=None, password=None):
|
def set_default_proxy(proxy_type=None, addr=None, port=None, rdns=True, username=None, password=None):
|
||||||
"""
|
"""
|
||||||
set_default_proxy(proxy_type, addr[, port[, rdns[, username, password]]])
|
set_default_proxy(proxy_type, addr[, port[, rdns[, username, password]]])
|
||||||
|
@ -147,44 +124,20 @@ def set_default_proxy(proxy_type=None, addr=None, port=None, rdns=True, username
|
||||||
Sets a default proxy which all further socksocket objects will use,
|
Sets a default proxy which all further socksocket objects will use,
|
||||||
unless explicitly changed. All parameters are as for socket.set_proxy().
|
unless explicitly changed. All parameters are as for socket.set_proxy().
|
||||||
"""
|
"""
|
||||||
proxy_type = utils.bytes2str_only(proxy_type)
|
|
||||||
addr = utils.to_str(addr)
|
|
||||||
if isinstance(port, bytes):
|
|
||||||
port = int(utils.to_str(port))
|
|
||||||
else:
|
|
||||||
port = int(port)
|
|
||||||
username = utils.to_bytes(username)
|
|
||||||
password = utils.to_bytes(password)
|
|
||||||
|
|
||||||
if isinstance(proxy_type, str):
|
|
||||||
proxy_type = proxy_type.lower()
|
|
||||||
if "http" in proxy_type:
|
|
||||||
proxy_type = PROXY_TYPE_HTTP
|
|
||||||
elif "socks5" in proxy_type:
|
|
||||||
proxy_type = PROXY_TYPE_SOCKS5
|
|
||||||
elif "socks4" in proxy_type:
|
|
||||||
proxy_type = PROXY_TYPE_SOCKS4
|
|
||||||
else:
|
|
||||||
raise ProxyError("unknown proxy type:%s" % proxy_type)
|
|
||||||
|
|
||||||
socksocket.default_proxy = (proxy_type, addr, port, rdns,
|
socksocket.default_proxy = (proxy_type, addr, port, rdns,
|
||||||
username if username else None,
|
username.encode() if username else None,
|
||||||
password if password else None)
|
password.encode() if password else None)
|
||||||
|
|
||||||
|
|
||||||
setdefaultproxy = set_default_proxy
|
setdefaultproxy = set_default_proxy
|
||||||
|
|
||||||
|
|
||||||
def get_default_proxy():
|
def get_default_proxy():
|
||||||
"""
|
"""
|
||||||
Returns the default proxy, set by set_default_proxy.
|
Returns the default proxy, set by set_default_proxy.
|
||||||
"""
|
"""
|
||||||
return socksocket.default_proxy
|
return socksocket.default_proxy
|
||||||
|
|
||||||
|
|
||||||
getdefaultproxy = get_default_proxy
|
getdefaultproxy = get_default_proxy
|
||||||
|
|
||||||
|
|
||||||
def wrap_module(module):
|
def wrap_module(module):
|
||||||
"""
|
"""
|
||||||
Attempts to replace a module's socket library with a SOCKS socket. Must set
|
Attempts to replace a module's socket library with a SOCKS socket. Must set
|
||||||
|
@ -199,27 +152,64 @@ def wrap_module(module):
|
||||||
|
|
||||||
wrapmodule = wrap_module
|
wrapmodule = wrap_module
|
||||||
|
|
||||||
|
|
||||||
def create_connection(dest_pair, proxy_type=None, proxy_addr=None,
|
def create_connection(dest_pair, proxy_type=None, proxy_addr=None,
|
||||||
proxy_port=None, proxy_username=None,
|
proxy_port=None, proxy_rdns=True,
|
||||||
proxy_password=None, timeout=None):
|
proxy_username=None, proxy_password=None,
|
||||||
|
timeout=None, source_address=None,
|
||||||
|
socket_options=None):
|
||||||
"""create_connection(dest_pair, *[, timeout], **proxy_args) -> socket object
|
"""create_connection(dest_pair, *[, timeout], **proxy_args) -> socket object
|
||||||
|
|
||||||
Like socket.create_connection(), but connects to proxy
|
Like socket.create_connection(), but connects to proxy
|
||||||
before returning the socket object.
|
before returning the socket object.
|
||||||
|
|
||||||
dest_pair - 2-tuple of (IP/hostname, port).
|
dest_pair - 2-tuple of (IP/hostname, port).
|
||||||
**proxy_args - Same args passed to socksocket.set_proxy().
|
**proxy_args - Same args passed to socksocket.set_proxy() if present.
|
||||||
timeout - Optional socket timeout value, in seconds.
|
timeout - Optional socket timeout value, in seconds.
|
||||||
|
source_address - tuple (host, port) for the socket to bind to as its source
|
||||||
|
address before connecting (only for compatibility)
|
||||||
"""
|
"""
|
||||||
sock = socksocket()
|
# Remove IPv6 brackets on the remote address and proxy address.
|
||||||
|
remote_host, remote_port = dest_pair
|
||||||
|
if remote_host.startswith('['):
|
||||||
|
remote_host = remote_host.strip('[]')
|
||||||
|
if proxy_addr and proxy_addr.startswith('['):
|
||||||
|
proxy_addr = proxy_addr.strip('[]')
|
||||||
|
|
||||||
|
err = None
|
||||||
|
|
||||||
|
# Allow the SOCKS proxy to be on IPv4 or IPv6 addresses.
|
||||||
|
for r in socket.getaddrinfo(proxy_addr, proxy_port, 0, socket.SOCK_STREAM):
|
||||||
|
family, socket_type, proto, canonname, sa = r
|
||||||
|
sock = None
|
||||||
|
try:
|
||||||
|
sock = socksocket(family, socket_type, proto)
|
||||||
|
|
||||||
|
if socket_options is not None:
|
||||||
|
for opt in socket_options:
|
||||||
|
sock.setsockopt(*opt)
|
||||||
|
|
||||||
if isinstance(timeout, (int, float)):
|
if isinstance(timeout, (int, float)):
|
||||||
sock.settimeout(timeout)
|
sock.settimeout(timeout)
|
||||||
sock.set_proxy(proxy_type, proxy_addr, proxy_port,
|
|
||||||
|
if proxy_type is not None:
|
||||||
|
sock.set_proxy(proxy_type, proxy_addr, proxy_port, proxy_rdns,
|
||||||
proxy_username, proxy_password)
|
proxy_username, proxy_password)
|
||||||
sock.connect(dest_pair)
|
if source_address is not None:
|
||||||
|
sock.bind(source_address)
|
||||||
|
|
||||||
|
sock.connect((remote_host, remote_port))
|
||||||
return sock
|
return sock
|
||||||
|
|
||||||
|
except socket.error as e:
|
||||||
|
err = e
|
||||||
|
if sock is not None:
|
||||||
|
sock.close()
|
||||||
|
sock = None
|
||||||
|
|
||||||
|
if err is not None:
|
||||||
|
raise err
|
||||||
|
|
||||||
|
raise socket.error("gai returned empty list.")
|
||||||
|
|
||||||
class _BaseSocket(socket.socket):
|
class _BaseSocket(socket.socket):
|
||||||
"""Allows Python 2's "delegated" methods such as send() to be overridden
|
"""Allows Python 2's "delegated" methods such as send() to be overridden
|
||||||
|
@ -234,11 +224,8 @@ class _BaseSocket(socket.socket):
|
||||||
|
|
||||||
_savenames = list()
|
_savenames = list()
|
||||||
|
|
||||||
|
|
||||||
def _makemethod(name):
|
def _makemethod(name):
|
||||||
return lambda self, *pos, **kw: self._savedmethods[name](*pos, **kw)
|
return lambda self, *pos, **kw: self._savedmethods[name](*pos, **kw)
|
||||||
|
|
||||||
|
|
||||||
for name in ("sendto", "send", "recvfrom", "recv"):
|
for name in ("sendto", "send", "recvfrom", "recv"):
|
||||||
method = getattr(_BaseSocket, name, None)
|
method = getattr(_BaseSocket, name, None)
|
||||||
|
|
||||||
|
@ -250,7 +237,6 @@ for name in ("sendto", "send", "recvfrom", "recv"):
|
||||||
_BaseSocket._savenames.append(name)
|
_BaseSocket._savenames.append(name)
|
||||||
setattr(_BaseSocket, name, _makemethod(name))
|
setattr(_BaseSocket, name, _makemethod(name))
|
||||||
|
|
||||||
|
|
||||||
class socksocket(_BaseSocket):
|
class socksocket(_BaseSocket):
|
||||||
"""socksocket([family[, type[, proto]]]) -> socket object
|
"""socksocket([family[, type[, proto]]]) -> socket object
|
||||||
|
|
||||||
|
@ -262,27 +248,18 @@ class socksocket(_BaseSocket):
|
||||||
|
|
||||||
default_proxy = None
|
default_proxy = None
|
||||||
|
|
||||||
def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, _sock=None):
|
def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, *args, **kwargs):
|
||||||
if type not in {socket.SOCK_STREAM, socket.SOCK_DGRAM}:
|
if type not in (socket.SOCK_STREAM, socket.SOCK_DGRAM):
|
||||||
msg = "Socket type must be stream or datagram, not {!r}"
|
msg = "Socket type must be stream or datagram, not {!r}"
|
||||||
raise ValueError(msg.format(type))
|
raise ValueError(msg.format(type))
|
||||||
|
|
||||||
|
_BaseSocket.__init__(self, family, type, proto, *args, **kwargs)
|
||||||
self._proxyconn = None # TCP connection to keep UDP relay alive
|
self._proxyconn = None # TCP connection to keep UDP relay alive
|
||||||
self.resolve_dest = True
|
|
||||||
|
|
||||||
if self.default_proxy:
|
if self.default_proxy:
|
||||||
self.proxy = self.default_proxy
|
self.proxy = self.default_proxy
|
||||||
proxy_host = self.proxy[1]
|
|
||||||
if utils.check_ip_valid6(proxy_host):
|
|
||||||
family=socket.AF_INET6
|
|
||||||
elif utils.check_ip_valid4(proxy_host):
|
|
||||||
family=socket.AF_INET
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.proxy = (None, None, None, None, None, None)
|
self.proxy = (None, None, None, None, None, None)
|
||||||
|
|
||||||
_BaseSocket.__init__(self, family, type, proto, _sock)
|
|
||||||
|
|
||||||
self.proxy_sockname = None
|
self.proxy_sockname = None
|
||||||
self.proxy_peername = None
|
self.proxy_peername = None
|
||||||
|
|
||||||
|
@ -317,34 +294,9 @@ class socksocket(_BaseSocket):
|
||||||
password - Password to authenticate with to the server.
|
password - Password to authenticate with to the server.
|
||||||
Only relevant when username is also provided.
|
Only relevant when username is also provided.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
proxy_type = utils.bytes2str_only(proxy_type)
|
|
||||||
addr = utils.to_str(addr)
|
|
||||||
if isinstance(port, bytes):
|
|
||||||
port = int(utils.to_str(port))
|
|
||||||
else:
|
|
||||||
port = int(port)
|
|
||||||
username = utils.to_bytes(username)
|
|
||||||
password = utils.to_bytes(password)
|
|
||||||
|
|
||||||
if isinstance(proxy_type, string_types):
|
|
||||||
proxy_type = proxy_type.lower()
|
|
||||||
if "http" in proxy_type:
|
|
||||||
proxy_type = PROXY_TYPE_HTTP
|
|
||||||
self.resolve_dest = False
|
|
||||||
elif "socks5" in proxy_type:
|
|
||||||
if proxy_type == "socks5h":
|
|
||||||
self.resolve_dest = False
|
|
||||||
rdns = True
|
|
||||||
proxy_type = PROXY_TYPE_SOCKS5
|
|
||||||
elif "socks4" in proxy_type:
|
|
||||||
proxy_type = PROXY_TYPE_SOCKS4
|
|
||||||
else:
|
|
||||||
raise ProxyError("unknown proxy type:%s" % proxy_type)
|
|
||||||
|
|
||||||
self.proxy = (proxy_type, addr, port, rdns,
|
self.proxy = (proxy_type, addr, port, rdns,
|
||||||
username if username else None,
|
username.encode() if username else None,
|
||||||
password if password else None)
|
password.encode() if password else None)
|
||||||
|
|
||||||
setproxy = set_proxy
|
setproxy = set_proxy
|
||||||
|
|
||||||
|
@ -559,26 +511,39 @@ class socksocket(_BaseSocket):
|
||||||
and the resolved address as a tuple object.
|
and the resolved address as a tuple object.
|
||||||
"""
|
"""
|
||||||
host, port = addr
|
host, port = addr
|
||||||
host = utils.to_str(host)
|
|
||||||
proxy_type, _, _, rdns, username, password = self.proxy
|
proxy_type, _, _, rdns, username, password = self.proxy
|
||||||
|
family_to_byte = {socket.AF_INET: b"\x01", socket.AF_INET6: b"\x04"}
|
||||||
|
|
||||||
if utils.check_ip_valid6(host):
|
# If the given destination address is an IP address, we'll
|
||||||
addr_bytes = inet_pton(socket.AF_INET6, host)
|
# use the IP address request even if remote resolving was specified.
|
||||||
file.write(b"\x04" + addr_bytes)
|
# Detect whether the address is IPv4/6 directly.
|
||||||
elif utils.check_ip_valid4(host):
|
for family in (socket.AF_INET, socket.AF_INET6):
|
||||||
addr_bytes = socket.inet_aton(host)
|
try:
|
||||||
file.write(b"\x01" + addr_bytes)
|
addr_bytes = socket.inet_pton(family, host)
|
||||||
else:
|
file.write(family_to_byte[family] + addr_bytes)
|
||||||
|
host = socket.inet_ntop(family, addr_bytes)
|
||||||
|
file.write(struct.pack(">H", port))
|
||||||
|
return host, port
|
||||||
|
except socket.error:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Well it's not an IP number, so it's probably a DNS name.
|
||||||
if rdns:
|
if rdns:
|
||||||
# Resolve remotely
|
# Resolve remotely
|
||||||
host_bytes = host.encode("utf-8")
|
host_bytes = host.encode('idna')
|
||||||
file.write(b"\x03" + chr(len(host_bytes)).encode() + host_bytes)
|
file.write(b"\x03" + chr(len(host_bytes)).encode() + host_bytes)
|
||||||
else:
|
else:
|
||||||
# Resolve locally
|
# Resolve locally
|
||||||
addr_bytes = socket.inet_aton(socket.gethostbyname(host))
|
addresses = socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM, socket.IPPROTO_TCP, socket.AI_ADDRCONFIG)
|
||||||
file.write(b"\x01" + addr_bytes)
|
# We can't really work out what IP is reachable, so just pick the
|
||||||
host = socket.inet_ntoa(addr_bytes)
|
# first.
|
||||||
|
target_addr = addresses[0]
|
||||||
|
family = target_addr[0]
|
||||||
|
host = target_addr[4][0]
|
||||||
|
|
||||||
|
addr_bytes = socket.inet_pton(family, host)
|
||||||
|
file.write(family_to_byte[family] + addr_bytes)
|
||||||
|
host = socket.inet_ntop(family, addr_bytes)
|
||||||
file.write(struct.pack(">H", port))
|
file.write(struct.pack(">H", port))
|
||||||
return host, port
|
return host, port
|
||||||
|
|
||||||
|
@ -590,7 +555,7 @@ class socksocket(_BaseSocket):
|
||||||
length = self._readall(file, 1)
|
length = self._readall(file, 1)
|
||||||
addr = self._readall(file, ord(length))
|
addr = self._readall(file, ord(length))
|
||||||
elif atyp == b"\x04":
|
elif atyp == b"\x04":
|
||||||
addr = inet_ntop(socket.AF_INET6, self._readall(file, 16))
|
addr = socket.inet_ntop(socket.AF_INET6, self._readall(file, 16))
|
||||||
else:
|
else:
|
||||||
raise GeneralProxyError("SOCKS5 proxy server sent invalid data")
|
raise GeneralProxyError("SOCKS5 proxy server sent invalid data")
|
||||||
|
|
||||||
|
@ -602,7 +567,6 @@ class socksocket(_BaseSocket):
|
||||||
Negotiates a connection through a SOCKS4 server.
|
Negotiates a connection through a SOCKS4 server.
|
||||||
"""
|
"""
|
||||||
proxy_type, addr, port, rdns, username, password = self.proxy
|
proxy_type, addr, port, rdns, username, password = self.proxy
|
||||||
dest_addr = utils.to_str(dest_addr)
|
|
||||||
|
|
||||||
writer = self.makefile("wb")
|
writer = self.makefile("wb")
|
||||||
reader = self.makefile("rb", 0) # buffering=0 renamed in Python 3
|
reader = self.makefile("rb", 0) # buffering=0 renamed in Python 3
|
||||||
|
@ -611,7 +575,7 @@ class socksocket(_BaseSocket):
|
||||||
remote_resolve = False
|
remote_resolve = False
|
||||||
try:
|
try:
|
||||||
addr_bytes = socket.inet_aton(dest_addr)
|
addr_bytes = socket.inet_aton(dest_addr)
|
||||||
except socket.error as e:
|
except socket.error:
|
||||||
# It's a DNS name. Check where it should be resolved.
|
# It's a DNS name. Check where it should be resolved.
|
||||||
if rdns:
|
if rdns:
|
||||||
addr_bytes = b"\x00\x00\x00\x01"
|
addr_bytes = b"\x00\x00\x00\x01"
|
||||||
|
@ -632,7 +596,7 @@ class socksocket(_BaseSocket):
|
||||||
# NOTE: This is actually an extension to the SOCKS4 protocol
|
# NOTE: This is actually an extension to the SOCKS4 protocol
|
||||||
# called SOCKS4A and may not be supported in all cases.
|
# called SOCKS4A and may not be supported in all cases.
|
||||||
if remote_resolve:
|
if remote_resolve:
|
||||||
writer.write(dest_addr.encode('utf-8') + b"\x00")
|
writer.write(dest_addr.encode('idna') + b"\x00")
|
||||||
writer.flush()
|
writer.flush()
|
||||||
|
|
||||||
# Get the response from the server
|
# Get the response from the server
|
||||||
|
@ -657,37 +621,30 @@ class socksocket(_BaseSocket):
|
||||||
reader.close()
|
reader.close()
|
||||||
writer.close()
|
writer.close()
|
||||||
|
|
||||||
def _negotiate_HTTP(self, dest_host, dest_port):
|
def _negotiate_HTTP(self, dest_addr, dest_port):
|
||||||
"""
|
"""
|
||||||
Negotiates a connection through an HTTP server.
|
Negotiates a connection through an HTTP server.
|
||||||
NOTE: This currently only supports HTTP CONNECT-style proxies.
|
NOTE: This currently only supports HTTP CONNECT-style proxies.
|
||||||
"""
|
"""
|
||||||
proxy_type, proxy_addr, port, rdns, username, password = self.proxy
|
proxy_type, addr, port, rdns, username, password = self.proxy
|
||||||
|
|
||||||
# If we need to resolve locally, we do this now
|
# If we need to resolve locally, we do this now
|
||||||
dest_host = utils.to_bytes(dest_host)
|
addr = dest_addr if rdns else socket.gethostbyname(dest_addr)
|
||||||
if b":" not in dest_host and not rdns:
|
|
||||||
dest_addr = socket.gethostbyname(dest_host)
|
|
||||||
dest_addr = utils.to_bytes(dest_addr)
|
|
||||||
else:
|
|
||||||
dest_addr = dest_host
|
|
||||||
|
|
||||||
http_headers = [
|
http_headers = [
|
||||||
(b"CONNECT " + utils.to_bytes(dest_addr) + b":"
|
b"CONNECT " + addr.encode('idna') + b":" + str(dest_port).encode() + b" HTTP/1.1",
|
||||||
+ str(dest_port).encode() + b" HTTP/1.1"),
|
b"Host: " + dest_addr.encode('idna')
|
||||||
b"Host: " + dest_addr
|
|
||||||
]
|
]
|
||||||
|
|
||||||
if username and password:
|
if username and password:
|
||||||
http_headers.append(b"Proxy-Authorization: basic "
|
http_headers.append(b"Proxy-Authorization: basic " + b64encode(username + b":" + password))
|
||||||
+ b64encode(username + b":" + password))
|
|
||||||
|
|
||||||
http_headers.append(b"\r\n")
|
http_headers.append(b"\r\n")
|
||||||
|
|
||||||
self.sendall(b"\r\n".join(http_headers))
|
self.sendall(b"\r\n".join(http_headers))
|
||||||
|
|
||||||
# We just need the first line to check if the connection was successful
|
# We just need the first line to check if the connection was successful
|
||||||
fobj = self.makefile("rb")
|
fobj = self.makefile()
|
||||||
status_line = fobj.readline()
|
status_line = fobj.readline()
|
||||||
fobj.close()
|
fobj.close()
|
||||||
|
|
||||||
|
@ -695,11 +652,11 @@ class socksocket(_BaseSocket):
|
||||||
raise GeneralProxyError("Connection closed unexpectedly")
|
raise GeneralProxyError("Connection closed unexpectedly")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
proto, status_code, status_msg = status_line.split(b" ", 2)
|
proto, status_code, status_msg = status_line.split(" ", 2)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise GeneralProxyError("HTTP proxy server sent invalid response")
|
raise GeneralProxyError("HTTP proxy server sent invalid response")
|
||||||
|
|
||||||
if not proto.startswith(b"HTTP/"):
|
if not proto.startswith("HTTP/"):
|
||||||
raise GeneralProxyError("Proxy server does not appear to be an HTTP proxy")
|
raise GeneralProxyError("Proxy server does not appear to be an HTTP proxy")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -716,7 +673,7 @@ class socksocket(_BaseSocket):
|
||||||
raise HTTPError(error)
|
raise HTTPError(error)
|
||||||
|
|
||||||
self.proxy_sockname = (b"0.0.0.0", 0)
|
self.proxy_sockname = (b"0.0.0.0", 0)
|
||||||
self.proxy_peername = dest_addr, dest_port
|
self.proxy_peername = addr, dest_port
|
||||||
|
|
||||||
_proxy_negotiators = {
|
_proxy_negotiators = {
|
||||||
SOCKS4: _negotiate_SOCKS4,
|
SOCKS4: _negotiate_SOCKS4,
|
||||||
|
@ -724,27 +681,26 @@ class socksocket(_BaseSocket):
|
||||||
HTTP: _negotiate_HTTP
|
HTTP: _negotiate_HTTP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def connect(self, dest_pair):
|
def connect(self, dest_pair):
|
||||||
"""
|
"""
|
||||||
Connects to the specified destination through a proxy.
|
Connects to the specified destination through a proxy.
|
||||||
Uses the same API as socket's connect().
|
Uses the same API as socket's connect().
|
||||||
To select the proxy server, use set_proxy().
|
To select the proxy server, use set_proxy().
|
||||||
|
|
||||||
dest_pair
|
dest_pair - 2-tuple of (IP/hostname, port).
|
||||||
"""
|
"""
|
||||||
if len(dest_pair) == 2:
|
if len(dest_pair) != 2 or dest_pair[0].startswith("["):
|
||||||
# IPv4
|
# Probably IPv6, not supported -- raise an error, and hope
|
||||||
|
# Happy Eyeballs (RFC6555) makes sure at least the IPv4
|
||||||
|
# connection works...
|
||||||
|
raise socket.error("PySocks doesn't support IPv6")
|
||||||
|
|
||||||
dest_addr, dest_port = dest_pair
|
dest_addr, dest_port = dest_pair
|
||||||
elif len(dest_pair) == 4:
|
|
||||||
# IPv6
|
|
||||||
dest_addr, dest_port, st_zero, st_stream = dest_pair
|
|
||||||
else:
|
|
||||||
raise GeneralProxyError("Invalid destination-connection (host, port) pair")
|
|
||||||
|
|
||||||
if self.type == socket.SOCK_DGRAM:
|
if self.type == socket.SOCK_DGRAM:
|
||||||
if not self._proxyconn:
|
if not self._proxyconn:
|
||||||
self.bind(("", 0))
|
self.bind(("", 0))
|
||||||
if self.resolve_dest:
|
|
||||||
dest_addr = socket.gethostbyname(dest_addr)
|
dest_addr = socket.gethostbyname(dest_addr)
|
||||||
|
|
||||||
# If the host address is INADDR_ANY or similar, reset the peer
|
# If the host address is INADDR_ANY or similar, reset the peer
|
||||||
|
@ -755,30 +711,33 @@ class socksocket(_BaseSocket):
|
||||||
self.proxy_peername = (dest_addr, dest_port)
|
self.proxy_peername = (dest_addr, dest_port)
|
||||||
return
|
return
|
||||||
|
|
||||||
proxy_type, proxy_host, proxy_port, rdns, username, password = self.proxy
|
proxy_type, proxy_addr, proxy_port, rdns, username, password = self.proxy
|
||||||
proxy_host = utils.to_bytes(proxy_host)
|
|
||||||
|
|
||||||
# Do a minimal input check first
|
# Do a minimal input check first
|
||||||
if not dest_addr or not isinstance(dest_port, int):
|
if (not isinstance(dest_pair, (list, tuple))
|
||||||
|
or len(dest_pair) != 2
|
||||||
|
or not dest_addr
|
||||||
|
or not isinstance(dest_port, int)):
|
||||||
raise GeneralProxyError("Invalid destination-connection (host, port) pair")
|
raise GeneralProxyError("Invalid destination-connection (host, port) pair")
|
||||||
|
|
||||||
|
|
||||||
if proxy_type is None:
|
if proxy_type is None:
|
||||||
# Treat like regular socket object
|
# Treat like regular socket object
|
||||||
|
self.proxy_peername = dest_pair
|
||||||
_BaseSocket.connect(self, (dest_addr, dest_port))
|
_BaseSocket.connect(self, (dest_addr, dest_port))
|
||||||
return
|
return
|
||||||
|
|
||||||
proxy_port = proxy_port or DEFAULT_PORTS.get(proxy_type)
|
proxy_addr = self._proxy_addr()
|
||||||
if not proxy_port:
|
|
||||||
raise GeneralProxyError("Invalid proxy port")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Initial connection to proxy server
|
# Initial connection to proxy server
|
||||||
proxy_ip = socket.gethostbyname(proxy_host)
|
_BaseSocket.connect(self, proxy_addr)
|
||||||
_BaseSocket.connect(self, (proxy_ip, proxy_port))
|
|
||||||
except socket.error as error:
|
except socket.error as error:
|
||||||
# Error while connecting to proxy
|
# Error while connecting to proxy
|
||||||
self.close()
|
self.close()
|
||||||
proxy_server = "{0}:{1}".format(proxy_host, proxy_port)
|
proxy_addr, proxy_port = proxy_addr
|
||||||
|
proxy_server = "{0}:{1}".format(proxy_addr, proxy_port)
|
||||||
printable_type = PRINTABLE_PROXY_TYPES[proxy_type]
|
printable_type = PRINTABLE_PROXY_TYPES[proxy_type]
|
||||||
|
|
||||||
msg = "Error connecting to {0} proxy {1}".format(printable_type,
|
msg = "Error connecting to {0} proxy {1}".format(printable_type,
|
||||||
|
@ -800,8 +759,12 @@ class socksocket(_BaseSocket):
|
||||||
self.close()
|
self.close()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
def _proxy_addr(self):
|
||||||
if __name__ == "__main__":
|
"""
|
||||||
name = "abc"
|
Return proxy address to connect to as tuple object
|
||||||
name2 = name.encode('idna')
|
"""
|
||||||
print(name2)
|
proxy_type, proxy_addr, proxy_port, rdns, username, password = self.proxy
|
||||||
|
proxy_port = proxy_port or DEFAULT_PORTS.get(proxy_type)
|
||||||
|
if not proxy_port:
|
||||||
|
raise GeneralProxyError("Invalid proxy type")
|
||||||
|
return proxy_addr, proxy_port
|
||||||
|
|
|
@ -1,385 +0,0 @@
|
||||||
""""
|
|
||||||
Code from https://github.com/XX-net/XX-Net/blob/master/code/default/lib/noarch/utils.py
|
|
||||||
|
|
||||||
Copyright (c) [2022], [XX-Net]
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
"""
|
|
||||||
|
|
||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import re
|
|
||||||
import os
|
|
||||||
import threading
|
|
||||||
from functools import reduce
|
|
||||||
from six import string_types
|
|
||||||
|
|
||||||
ipv4_pattern = re.compile(br'^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$')
|
|
||||||
|
|
||||||
ipv6_pattern = re.compile(br"""
|
|
||||||
^
|
|
||||||
\s* # Leading whitespace
|
|
||||||
(?!.*::.*::) # Only a single whildcard allowed
|
|
||||||
(?:(?!:)|:(?=:)) # Colon iff it would be part of a wildcard
|
|
||||||
(?: # Repeat 6 times:
|
|
||||||
[0-9a-f]{0,4} # A group of at most four hexadecimal digits
|
|
||||||
(?:(?<=::)|(?<!::):) # Colon unless preceeded by wildcard
|
|
||||||
){6} #
|
|
||||||
(?: # Either
|
|
||||||
[0-9a-f]{0,4} # Another group
|
|
||||||
(?:(?<=::)|(?<!::):) # Colon unless preceeded by wildcard
|
|
||||||
[0-9a-f]{0,4} # Last group
|
|
||||||
(?: (?<=::) # Colon iff preceeded by exacly one colon
|
|
||||||
| (?<!:) #
|
|
||||||
| (?<=:) (?<!::) : #
|
|
||||||
) # OR
|
|
||||||
| # A v4 address with NO leading zeros
|
|
||||||
(?:25[0-4]|2[0-4]\d|1\d\d|[1-9]?\d)
|
|
||||||
(?: \.
|
|
||||||
(?:25[0-4]|2[0-4]\d|1\d\d|[1-9]?\d)
|
|
||||||
){3}
|
|
||||||
)
|
|
||||||
\s* # Trailing whitespace
|
|
||||||
$
|
|
||||||
""", re.VERBOSE | re.IGNORECASE | re.DOTALL)
|
|
||||||
|
|
||||||
|
|
||||||
def check_ip_valid4(ip):
|
|
||||||
"""检查ipv4地址的合法性"""
|
|
||||||
ip = to_bytes(ip)
|
|
||||||
ret = ipv4_pattern.match(ip)
|
|
||||||
if ret is not None:
|
|
||||||
"each item range: [0,255]"
|
|
||||||
for item in ret.groups():
|
|
||||||
if int(item) > 255:
|
|
||||||
return 0
|
|
||||||
return 1
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def check_ip_valid6(ip):
|
|
||||||
"""Copied from http://stackoverflow.com/a/319293/2755602"""
|
|
||||||
ip = to_bytes(ip)
|
|
||||||
|
|
||||||
return ipv6_pattern.match(ip) is not None
|
|
||||||
|
|
||||||
|
|
||||||
def check_ip_valid(ip):
|
|
||||||
ip = to_bytes(ip)
|
|
||||||
if b'.' in ip:
|
|
||||||
return check_ip_valid4(ip)
|
|
||||||
else:
|
|
||||||
return check_ip_valid6(ip)
|
|
||||||
|
|
||||||
|
|
||||||
def get_ip_port(ip_str, port=443):
|
|
||||||
ip_str = to_bytes(ip_str)
|
|
||||||
if b"." in ip_str:
|
|
||||||
# ipv4
|
|
||||||
if b":" in ip_str:
|
|
||||||
# format is ip:port
|
|
||||||
ps = ip_str.split(b":")
|
|
||||||
ip = ps[0]
|
|
||||||
port = ps[1]
|
|
||||||
else:
|
|
||||||
# format is ip
|
|
||||||
ip = ip_str
|
|
||||||
else:
|
|
||||||
# ipv6
|
|
||||||
if b"[" in ip_str:
|
|
||||||
# format: [ab01:12:23:34::1]
|
|
||||||
# format: [ab01:12:23:34::1]:23
|
|
||||||
|
|
||||||
p1 = ip_str.find(b"[")
|
|
||||||
p2 = ip_str.find(b"]")
|
|
||||||
ip = ip_str[p1 + 1:p2]
|
|
||||||
port_str = ip_str[p2 + 1:]
|
|
||||||
if len(port_str) > 0:
|
|
||||||
port = port_str[1:]
|
|
||||||
else:
|
|
||||||
ip = ip_str
|
|
||||||
|
|
||||||
return ip, int(port)
|
|
||||||
|
|
||||||
|
|
||||||
domain_allowed = re.compile("(?!-)[A-Z\d-]{1,63}(?<!-)$")
|
|
||||||
|
|
||||||
|
|
||||||
def check_domain_valid(hostname):
|
|
||||||
if len(hostname) > 255:
|
|
||||||
return False
|
|
||||||
if hostname.endswith("."):
|
|
||||||
hostname = hostname[:-1]
|
|
||||||
|
|
||||||
return all(domain_allowed.match(x) for x in hostname.split("."))
|
|
||||||
|
|
||||||
|
|
||||||
def str2hex(data):
|
|
||||||
data = to_str(data)
|
|
||||||
return ":".join("{:02x}".format(ord(c)) for c in data)
|
|
||||||
|
|
||||||
|
|
||||||
def get_ip_maskc(ip_str):
|
|
||||||
head = ".".join(ip_str.split(".")[:-1])
|
|
||||||
return head + ".0"
|
|
||||||
|
|
||||||
|
|
||||||
def split_ip(strline):
|
|
||||||
"""从每组地址中分离出起始IP以及结束IP"""
|
|
||||||
begin = ""
|
|
||||||
end = ""
|
|
||||||
if "-" in strline:
|
|
||||||
num_regions = strline.split(".")
|
|
||||||
if len(num_regions) == 4:
|
|
||||||
"xxx.xxx.xxx-xxx.xxx-xxx"
|
|
||||||
begin = ''
|
|
||||||
end = ''
|
|
||||||
for region in num_regions:
|
|
||||||
if '-' in region:
|
|
||||||
s, e = region.split('-')
|
|
||||||
begin += '.' + s
|
|
||||||
end += '.' + e
|
|
||||||
else:
|
|
||||||
begin += '.' + region
|
|
||||||
end += '.' + region
|
|
||||||
begin = begin[1:]
|
|
||||||
end = end[1:]
|
|
||||||
|
|
||||||
else:
|
|
||||||
"xxx.xxx.xxx.xxx-xxx.xxx.xxx.xxx"
|
|
||||||
begin, end = strline.split("-")
|
|
||||||
if 1 <= len(end) <= 3:
|
|
||||||
prefix = begin[0:begin.rfind(".")]
|
|
||||||
end = prefix + "." + end
|
|
||||||
|
|
||||||
elif strline.endswith("."):
|
|
||||||
"xxx.xxx.xxx."
|
|
||||||
begin = strline + "0"
|
|
||||||
end = strline + "255"
|
|
||||||
elif "/" in strline:
|
|
||||||
"xxx.xxx.xxx.xxx/xx"
|
|
||||||
(ip, bits) = strline.split("/")
|
|
||||||
if check_ip_valid4(ip) and (0 <= int(bits) <= 32):
|
|
||||||
orgip = ip_string_to_num(ip)
|
|
||||||
end_bits = (1 << (32 - int(bits))) - 1
|
|
||||||
begin_bits = 0xFFFFFFFF ^ end_bits
|
|
||||||
begin = ip_num_to_string(orgip & begin_bits)
|
|
||||||
end = ip_num_to_string(orgip | end_bits)
|
|
||||||
else:
|
|
||||||
"xxx.xxx.xxx.xxx"
|
|
||||||
begin = strline
|
|
||||||
end = strline
|
|
||||||
|
|
||||||
return begin, end
|
|
||||||
|
|
||||||
|
|
||||||
def generate_random_lowercase(n):
|
|
||||||
min_lc = ord(b'a')
|
|
||||||
len_lc = 26
|
|
||||||
ba = bytearray(os.urandom(n))
|
|
||||||
for i, b in enumerate(ba):
|
|
||||||
ba[i] = min_lc + b % len_lc # convert 0..255 to 97..122
|
|
||||||
# sys.stdout.buffer.write(ba)
|
|
||||||
return ba
|
|
||||||
|
|
||||||
|
|
||||||
class SimpleCondition(object):
|
|
||||||
def __init__(self):
|
|
||||||
self.lock = threading.Condition()
|
|
||||||
|
|
||||||
def notify(self):
|
|
||||||
self.lock.acquire()
|
|
||||||
self.lock.notify()
|
|
||||||
self.lock.release()
|
|
||||||
|
|
||||||
def wait(self):
|
|
||||||
self.lock.acquire()
|
|
||||||
self.lock.wait()
|
|
||||||
self.lock.release()
|
|
||||||
|
|
||||||
|
|
||||||
def split_domain(host):
|
|
||||||
host = to_bytes(host)
|
|
||||||
hl = host.split(b".")
|
|
||||||
return hl[0], b".".join(hl[1:])
|
|
||||||
|
|
||||||
|
|
||||||
def ip_string_to_num(s):
|
|
||||||
"""Convert dotted IPv4 address to integer."""
|
|
||||||
return reduce(lambda a, b: a << 8 | b, list(map(int, s.split("."))))
|
|
||||||
|
|
||||||
|
|
||||||
def ip_num_to_string(ip):
|
|
||||||
"""Convert 32-bit integer to dotted IPv4 address."""
|
|
||||||
return ".".join([str(ip >> n & 0xFF) for n in [24, 16, 8, 0]])
|
|
||||||
|
|
||||||
|
|
||||||
private_ipv4_range = [
|
|
||||||
("10.0.0.0", "10.255.255.255"),
|
|
||||||
("127.0.0.0", "127.255.255.255"),
|
|
||||||
("169.254.0.0", "169.254.255.255"),
|
|
||||||
("172.16.0.0", "172.31.255.255"),
|
|
||||||
("192.168.0.0", "192.168.255.255")
|
|
||||||
]
|
|
||||||
|
|
||||||
private_ipv6_range = [
|
|
||||||
("::1", "::1"),
|
|
||||||
("fc00::", "fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
|
|
||||||
]
|
|
||||||
|
|
||||||
private_ipv4_range_bin = []
|
|
||||||
for b, e in private_ipv4_range:
|
|
||||||
bb = ip_string_to_num(b)
|
|
||||||
ee = ip_string_to_num(e)
|
|
||||||
private_ipv4_range_bin.append((bb, ee))
|
|
||||||
|
|
||||||
|
|
||||||
def is_private_ip(ip):
|
|
||||||
ip = to_str(ip)
|
|
||||||
try:
|
|
||||||
if "." in ip:
|
|
||||||
ip_bin = ip_string_to_num(ip)
|
|
||||||
for b, e in private_ipv4_range_bin:
|
|
||||||
if b <= ip_bin <= e:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
if ip == "::1":
|
|
||||||
return True
|
|
||||||
|
|
||||||
fi = ip.find(":")
|
|
||||||
if fi != 4:
|
|
||||||
return False
|
|
||||||
|
|
||||||
be = ip[0:2]
|
|
||||||
if be in ["fc", "fd"]:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
except Exception as e:
|
|
||||||
# print(("is_private_ip(%s), except:%r", ip, e))
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
import string
|
|
||||||
|
|
||||||
printable = set(string.printable)
|
|
||||||
|
|
||||||
|
|
||||||
def get_printable(s):
|
|
||||||
return [x for x in s if x in printable]
|
|
||||||
|
|
||||||
|
|
||||||
def compare_version(version, reference_version):
|
|
||||||
try:
|
|
||||||
p = re.compile(r'([0-9]+)\.([0-9]+)\.([0-9]+)')
|
|
||||||
m1 = p.match(version)
|
|
||||||
m2 = p.match(reference_version)
|
|
||||||
v1 = list(map(int, list(map(m1.group, [1, 2, 3]))))
|
|
||||||
v2 = list(map(int, list(map(m2.group, [1, 2, 3]))))
|
|
||||||
|
|
||||||
if v1 > v2:
|
|
||||||
return 1
|
|
||||||
elif v1 < v2:
|
|
||||||
return -1
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
except Exception as e:
|
|
||||||
print("older_or_equal fail: %s, %s" % (version, reference_version))
|
|
||||||
raise e
|
|
||||||
|
|
||||||
|
|
||||||
def map_with_parameter(function, datas, args):
|
|
||||||
l = []
|
|
||||||
for data in datas:
|
|
||||||
d_out = function(data, args)
|
|
||||||
l.append(d_out)
|
|
||||||
return l
|
|
||||||
|
|
||||||
|
|
||||||
def to_bytes(data, coding='utf-8'):
|
|
||||||
if isinstance(data, bytes):
|
|
||||||
return data
|
|
||||||
if isinstance(data, string_types):
|
|
||||||
return data.encode(coding)
|
|
||||||
if isinstance(data, dict):
|
|
||||||
return dict(map_with_parameter(to_bytes, data.items(), coding))
|
|
||||||
if isinstance(data, tuple):
|
|
||||||
return tuple(map_with_parameter(to_bytes, data, coding))
|
|
||||||
if isinstance(data, list):
|
|
||||||
return list(map_with_parameter(to_bytes, data, coding))
|
|
||||||
if isinstance(data, int):
|
|
||||||
return to_bytes(str(data))
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
return bytes(data)
|
|
||||||
|
|
||||||
|
|
||||||
def to_str(data, coding='utf-8'):
|
|
||||||
if isinstance(data, string_types):
|
|
||||||
return data
|
|
||||||
if isinstance(data, bytes):
|
|
||||||
return data.decode(coding)
|
|
||||||
if isinstance(data, bytearray):
|
|
||||||
return data.decode(coding)
|
|
||||||
if isinstance(data, dict):
|
|
||||||
return dict(map_with_parameter(to_str, data.items(), coding))
|
|
||||||
if isinstance(data, tuple):
|
|
||||||
return tuple(map_with_parameter(to_str, data, coding))
|
|
||||||
if isinstance(data, list):
|
|
||||||
return list(map_with_parameter(to_str, data, coding))
|
|
||||||
if isinstance(data, int):
|
|
||||||
return str(data)
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
return str(data)
|
|
||||||
|
|
||||||
|
|
||||||
def bytes2str_only(data, coding='utf-8'):
|
|
||||||
if isinstance(data, bytes):
|
|
||||||
return data.decode(coding)
|
|
||||||
if isinstance(data, dict):
|
|
||||||
return dict(map_with_parameter(bytes2str_only, data.items(), coding))
|
|
||||||
if isinstance(data, tuple):
|
|
||||||
return tuple(map_with_parameter(bytes2str_only, data, coding))
|
|
||||||
if isinstance(data, list):
|
|
||||||
return list(map_with_parameter(bytes2str_only, data, coding))
|
|
||||||
else:
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
def merge_two_dict(x, y):
|
|
||||||
"""Given two dictionaries, merge them into a new dict as a shallow copy."""
|
|
||||||
z = x.copy()
|
|
||||||
z.update(y)
|
|
||||||
return z
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# print(get_ip_port("1.2.3.4", 443))
|
|
||||||
# print(get_ip_port("1.2.3.4:8443", 443))
|
|
||||||
print((get_ip_port("[face:ab1:11::0]", 443)))
|
|
||||||
print((get_ip_port("ab01::1", 443)))
|
|
||||||
print((get_ip_port("[ab01:55::1]:8444", 443)))
|
|
30
api/transfer.py
Normal file
30
api/transfer.py
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import os
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
from starlette.responses import StreamingResponse
|
||||||
|
from starlette.background import BackgroundTask
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
EXCLUDED_HEADERS = [
|
||||||
|
'content-encoding',
|
||||||
|
'content-length',
|
||||||
|
'transfer-encoding',
|
||||||
|
'connection'
|
||||||
|
]
|
||||||
|
|
||||||
|
async def stream_api_response(request, target_endpoint: str='https://api.openai.com/v1'):
|
||||||
|
async with httpx.AsyncClient(timeout=120) as client:
|
||||||
|
async with client.stream(
|
||||||
|
method=request.method,
|
||||||
|
url=f'{target_endpoint}/{request.url.path}',
|
||||||
|
headers={
|
||||||
|
'Authorization': 'Bearer ' + os.getenv('CLOSEDAI_KEY'),
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
data=await request.body(),
|
||||||
|
) as target_response:
|
||||||
|
target_response.raise_for_status()
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
fastapi
|
fastapi
|
||||||
httpx
|
httpx[socks]
|
||||||
openai
|
openai
|
||||||
python-dotenv
|
python-dotenv
|
||||||
rich
|
rich
|
||||||
starlette
|
starlette
|
||||||
|
win_inet_pton
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
import openai
|
import openai as closedai
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
PORT = 8000
|
PORT = 8000
|
||||||
|
@ -43,12 +43,12 @@ def test_api(model: str=MODEL, messages: List[dict]=None) -> dict:
|
||||||
return response.json()['choices'][0]
|
return response.json()['choices'][0]
|
||||||
|
|
||||||
def test_library():
|
def test_library():
|
||||||
"""Tests if the endpoint is working with the OpenAI library."""
|
"""Tests if the endpoint is working with the "Closed"AI library."""
|
||||||
|
|
||||||
openai.api_base = ENDPOINT
|
closedai.api_base = ENDPOINT
|
||||||
openai.api_key = 'nv-LIBRARY-TEST'
|
closedai.api_key = 'nv-LIBRARY-TEST'
|
||||||
|
|
||||||
completion = openai.ChatCompletion.create(
|
completion = closedai.ChatCompletion.create(
|
||||||
model=MODEL,
|
model=MODEL,
|
||||||
messages=MESSAGES,
|
messages=MESSAGES,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
# Credit: @miss_articulate_python on Discord
|
|
||||||
|
|
||||||
import configparser
|
|
||||||
import os
|
|
||||||
import pathlib
|
|
||||||
import openai
|
|
||||||
|
|
||||||
# creating a config file, so we can store the api key and other settings
|
|
||||||
config_file = pathlib.Path(__file__).parent / 'config.ini'
|
|
||||||
config = configparser.ConfigParser()
|
|
||||||
config.read_dict({
|
|
||||||
'openai': {
|
|
||||||
'api_base': 'http://ENDPOINT',
|
|
||||||
'api_key': '',
|
|
||||||
'reset_ip_every_request': 'false'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if config_file.exists():
|
|
||||||
config.read(config_file)
|
|
||||||
|
|
||||||
with open(config_file, 'w', encoding='utf8') as configfile:
|
|
||||||
config.write(configfile)
|
|
||||||
|
|
||||||
# the normal patch that you apply
|
|
||||||
openai.api_base = config['openai']['api_base']
|
|
||||||
openai.api_key = config['openai']['api_key']
|
|
||||||
|
|
||||||
# many modules lookup these environment variable, so we pre-emptively set them
|
|
||||||
os.environ['OPENAI_API_KEY'] = config['openai']['api_key']
|
|
||||||
os.environ['OPENAI_API_BASE'] = config['openai']['api_base']
|
|
Loading…
Reference in a new issue