Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added simple HTTP API in order to obtain deeplinks #141

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.idea/*
*.bak
**/__pycache__
**/*.py.save*
25 changes: 12 additions & 13 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
FROM alpine:3.10

RUN adduser tgproxy -u 10000 -D

RUN apk add --no-cache python3 py3-cryptography ca-certificates libcap

RUN chown -R tgproxy:tgproxy /home/tgproxy
RUN setcap cap_net_bind_service=+ep /usr/bin/python3.7

USER tgproxy

WORKDIR /home/tgproxy/
CMD ["python3", "mtprotoproxy.py"]
FROM ubuntu:18.04
ENV SNI=www.google.com
ENV PROXY_PORT=3256
ENV API_PORT=3257
ENV SECRET=08ca404ff6d62e9de1e15132a71a3ba0
ENV API_TOKEN=37c8f71dc98045a3250aad930982b860
ENV PYTHONPATH="${PYTHONPATH}:/opt/mtproxy"
RUN apt-get -y update && apt-get -y install python3 python3-cryptography
RUN mkdir -p /opt/mtproxy
COPY . /opt/mtproxy/
WORKDIR /opt/mtproxy
ENTRYPOINT ./mtprotoproxy.py faketls ${SNI} ${PROXY_PORT} ${API_PORT} ${SECRET} ${API_TOKEN}
11 changes: 11 additions & 0 deletions Dockerfile@cyberspacelabs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM ubuntu:18.04
ENV SNI=www.google.com
ENV PROXY_PORT=3256
ENV API_PORT=3257
ENV SECRET=08ca404ff6d62e9de1e15132a71a3ba0
ENV PYTHONPATH="${PYTHONPATH}:/opt/mtproxy"
RUN apt-get -y update && apt-get -y install python3 python3-cryptography
RUN mkdir -p /opt/mtproxy
COPY . /opt/mtproxy/
WORKDIR /opt/mtproxy
ENTRYPOINT ./mtprotoproxy.py faketls ${SNI} ${PROXY_PORT} ${API_PORT} ${SECRET}
18 changes: 9 additions & 9 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
The MIT License

Copyright (c) 2018, Alexander Bersenev

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
The MIT License
Copyright (c) 2018, Alexander Bersenev
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
58 changes: 29 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
# Async MTProto Proxy #

Fast and simple to setup mtproto proxy written on Python.

## Starting Up ##

1. `git clone -b stable https://github.com/alexbers/mtprotoproxy.git; cd mtprotoproxy`
2. *(optional, recommended)* edit *config.py*, set **PORT**, **USERS** and **AD_TAG**
3. `docker-compose up -d` (or just `python3 mtprotoproxy.py` if you don't like Docker)
4. *(optional, get a link to share the proxy)* `docker-compose logs`

![Demo](https://alexbers.com/mtprotoproxy/install_demo_v2.gif)

## Channel Advertising ##

To advertise a channel get a tag from **@MTProxybot** and put it to *config.py*.

## Performance ##

The proxy performance should be enough to comfortably serve about 4 000 simultaneous users on
the VDS instance with 1 CPU core and 1024MB RAM.

## Advanced Usage ##

The proxy can be launched:
- with a custom config: `python3 mtprotoproxy.py [configfile]`
- several times, clients will be automaticaly balanced between instances
- with uvloop module to get an extra speed boost
- with runtime statistics exported for [Prometheus](https://prometheus.io/): using [prometheus](https://github.com/alexbers/mtprotoproxy/tree/prometheus) branch
# Async MTProto Proxy #
Fast and simple to setup mtproto proxy written on Python.
## Starting Up ##
1. `git clone -b stable https://github.com/alexbers/mtprotoproxy.git; cd mtprotoproxy`
2. *(optional, recommended)* edit *config.py*, set **PORT**, **USERS** and **AD_TAG**
3. `docker-compose up -d` (or just `python3 mtprotoproxy.py` if you don't like Docker)
4. *(optional, get a link to share the proxy)* `docker-compose logs`
![Demo](https://alexbers.com/mtprotoproxy/install_demo_v2.gif)
## Channel Advertising ##
To advertise a channel get a tag from **@MTProxybot** and put it to *config.py*.
## Performance ##
The proxy performance should be enough to comfortably serve about 4 000 simultaneous users on
the VDS instance with 1 CPU core and 1024MB RAM.
## Advanced Usage ##
The proxy can be launched:
- with a custom config: `python3 mtprotoproxy.py [configfile]`
- several times, clients will be automaticaly balanced between instances
- with uvloop module to get an extra speed boost
- with runtime statistics exported for [Prometheus](https://prometheus.io/): using [prometheus](https://github.com/alexbers/mtprotoproxy/tree/prometheus) branch
63 changes: 63 additions & 0 deletions api/APIServer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from threading import Thread
import json
import urlparse

CONFIG = {}

class ServerThread(Thread):
def __init__(self, name):
Thread.__init__(self)
self.name = name

def run(self):
global CONFIG
server_class = ThreadingHTTPServer
httpd = server_class(('0.0.0.0', CONFIG['API_PORT']), APIHandler)
httpd.serve_forever()

class APIHandler(BaseHTTPRequestHandler):
def do_HEAD(self):
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()

def do_GET(self):
paths = ['/']
if self.path in paths:
if CONFIG['API_TOKEN']:
if "?" in self.path:
params = dict(urlparse.parse_qsl(self.path.split("?")[1], True))
if params['token'] and CONFIG['API_TOKEN'] == params['token']:
self.respond({'status': 200, 'content': self.dump_config()})
else:
self.respond({'status': 403, 'content': 'Invalid token'})
else:
self.respond({'status': 403, 'content': 'Token authentication required'})
else:
self.respond({'status': 200, 'content' : self.dump_config()})
else:
self.respond({'status': 404, 'content': 'Not found'})

def dump_config(self):
global CONFIG
data = {
'deeplinks': CONFIG['ACTIVATORS']
}
return json.dumps(data, indent=2, ensure_ascii=False)

def handle_http(self, status_code, content):
self.send_response(status_code)
self.send_header('Content-Type', 'application/json')
self.end_headers()
return bytes(content, 'utf-8')

def respond(self, opts):
response = self.handle_http(opts['status'], opts['content'])
self.wfile.write(response)


def start_api(config):
global CONFIG
CONFIG = config
ServerThread('API Server').start()
Empty file added api/__init__.py
Empty file.
44 changes: 22 additions & 22 deletions config.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
PORT = 3256

# name -> secret (32 hex chars)
USERS = {
"tg": "00000000000000000000000000000000",
"tg2": "0123456789abcdef0123456789abcdef"
}

# Makes the proxy harder to detect
# Can be incompatible with very old clients
SECURE_ONLY = True

# Makes the proxy even more hard to detect
# Compatible only with the recent clients
# TLS_ONLY = True

# The domain for TLS, bad clients are proxied there
# Use random existing domain, proxy checks it on start
# TLS_DOMAIN = "www.google.com"

# Tag for advertising, obtainable from @MTProxybot
# AD_TAG = "3c09c680b76ee91a4c25ad51f742267d"
PORT = 3256
# name -> secret (32 hex chars)
USERS = {
"tg": "00000000000000000000000000000000",
"tg2": "0123456789abcdef0123456789abcdef"
}
# Makes the proxy harder to detect
# Can be incompatible with very old clients
SECURE_ONLY = True
# Makes the proxy even more hard to detect
# Compatible only with the recent clients
# TLS_ONLY = True
# The domain for TLS, bad clients are proxied there
# Use random existing domain, proxy checks it on start
# TLS_DOMAIN = "www.google.com"
# Tag for advertising, obtainable from @MTProxybot
# AD_TAG = "3c09c680b76ee91a4c25ad51f742267d"
20 changes: 10 additions & 10 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
version: '2.0'
services:
mtprotoproxy:
build: .
restart: unless-stopped
network_mode: "host"
volumes:
- ./config.py:/home/tgproxy/config.py
- ./mtprotoproxy.py:/home/tgproxy/mtprotoproxy.py
# mem_limit: 1024m
version: '2.0'
services:
mtprotoproxy:
build: .
restart: unless-stopped
network_mode: "host"
volumes:
- ./config.py:/home/tgproxy/config.py
- ./mtprotoproxy.py:/home/tgproxy/mtprotoproxy.py
# mem_limit: 1024m
63 changes: 56 additions & 7 deletions mtprotoproxy.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/python3

import asyncio
import socket
Expand All @@ -19,6 +19,7 @@
import os
import stat
import traceback
import string


TG_DATACENTER_PORT = 443
Expand Down Expand Up @@ -76,6 +77,8 @@
MIN_MSG_LEN = 12
MAX_MSG_LEN = 2 ** 24

PORT_DEFAULT = 3256
PORT_API_DEFAULT = 3257

my_ip_info = {"ipv4": None, "ipv6": None}
used_handshakes = collections.OrderedDict()
Expand All @@ -89,6 +92,12 @@

config = {}

def generate_secret():
random_str = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(16))
sha = hashlib.sha256()
sha.update(random_str.encode('utf-8'))
digest = sha.hexdigest()
return digest[:32]

def init_config():
global config
Expand All @@ -98,6 +107,27 @@ def init_config():
elif len(sys.argv) == 2:
# launch with own config
conf_dict = runpy.run_path(sys.argv[1])
elif len(sys.argv) > 6 and sys.argv[1] == 'faketls':
conf_dict = {}
conf_dict["TLS_ONLY"] = True
conf_dict["TLS_DOMAIN"] = sys.argv[2]
conf_dict["USERS"] = {"tg": sys.argv[5]}
conf_dict["PORT"] = int(sys.argv[3])
conf_dict["API_PORT"] = int(sys.argv[4])
conf_dict["API_TOKEN"] = sys.argv[6]

elif len(sys.argv) > 2 and sys.argv[1] == 'faketls+gensec':
conf_dict = {}
conf_dict["TLS_ONLY"] = True
conf_dict["TLS_DOMAIN"] = sys.argv[2]
conf_dict["USERS"] = {"tg": generate_secret()}
conf_dict["PORT"] = PORT_DEFAULT
conf_dict["API_PORT"] = PORT_API_DEFAULT
if len(sys.argv) > 3:
conf_dict["PORT"] = int(sys.argv[3])
if len(sys.argv) > 4:
conf_dict["API_PORT"] = int(sys.argv[4])

else:
# undocumented way of launching
conf_dict = {}
Expand All @@ -113,7 +143,7 @@ def init_config():

conf_dict = {k: v for k, v in conf_dict.items() if k.isupper()}

conf_dict.setdefault("PORT", 3256)
conf_dict.setdefault("PORT", PORT_DEFAULT)
conf_dict.setdefault("USERS", {"tg": "00000000000000000000000000000000"})
conf_dict["AD_TAG"] = bytes.fromhex(conf_dict.get("AD_TAG", ""))

Expand Down Expand Up @@ -1790,12 +1820,21 @@ def get_ip_from_url(url):
disable_middle_proxy = True


API_CONFIG = {}

def print_tg_info():
global my_ip_info

global API_CONFIG

API_CONFIG['ACTIVATORS'] = []
API_CONFIG['API_PORT'] = -1
if 'API_PORT' in config:
API_CONFIG['API_PORT'] = config['API_PORT']
if 'API_TOKEN' in config:
API_CONFIG['API_TOKEN'] = config['API_TOKEN']
print_default_warning = False

if config.PORT == 3256:
if config.PORT == PORT_DEFAULT:
print("The default port 3256 is used, this is not recommended", flush=True)
if config.TLS_ONLY:
print("Since you have TLS only mode enabled the best port is 443", flush=True)
Expand All @@ -1811,19 +1850,25 @@ def print_tg_info():
if not config.SECURE_ONLY:
params = {"server": ip, "port": config.PORT, "secret": secret}
params_encodeded = urllib.parse.urlencode(params, safe=':')
print("{}: tg://proxy?{}".format(user, params_encodeded), flush=True)
link = 'tg://proxy?{}'.format(params_encodeded)
API_CONFIG['ACTIVATORS'].append(link)
print("{}: {}".format(user, link), flush=True)

params = {"server": ip, "port": config.PORT, "secret": "dd" + secret}
params_encodeded = urllib.parse.urlencode(params, safe=':')
print("{}: tg://proxy?{}".format(user, params_encodeded), flush=True)
link = 'tg://proxy?{}'.format(params_encodeded)
API_CONFIG['ACTIVATORS'].append(link)
print("{}: {}".format(user, link), flush=True)

tls_secret = "ee" + secret + config.TLS_DOMAIN.encode().hex()
# the base64 links is buggy on ios
# tls_secret = bytes.fromhex("ee" + secret) + config.TLS_DOMAIN.encode()
# tls_secret_base64 = base64.urlsafe_b64encode(tls_secret)
params = {"server": ip, "port": config.PORT, "secret": tls_secret}
params_encodeded = urllib.parse.urlencode(params, safe=':')
print("{}: tg://proxy?{} (new)".format(user, params_encodeded), flush=True)
link = 'tg://proxy?{}'.format(params_encodeded)
API_CONFIG['ACTIVATORS'].append(link)
print("{}: {} (new)".format(user, link), flush=True)

if secret in ["00000000000000000000000000000000", "0123456789abcdef0123456789abcdef"]:
msg = "The default secret {} is used, this is not recommended".format(secret)
Expand All @@ -1840,6 +1885,10 @@ def print_tg_info():

if print_default_warning:
print_err("Warning: one or more default settings detected")
if API_CONFIG['API_PORT'] > 0:
print('starting introspection API')
from api import APIServer
APIServer.start_api(API_CONFIG)


def setup_files_limit():
Expand Down
Loading