開始する
したがって、Python 3.4をインストールします。 スクリプトを書いています。これにはメモ帳を使用しました。 sslソケットの場合、sslモジュールが必要です。 実際には、ソケットソケット用です。
import ssl import socket
クライアントをリッスンするソケットを作成します。 SSLサーバーの場合、そのサーバー用の自己署名証明書を作成する必要があります。この証明書はクライアントに提供されます。 証明書を作成するために、opensslユーティリティを使用しました。 ここからindy.fulgan.com/SSLからユーティリティをダウンロードしました 。 証明書を作成するには、ユーティリティの設定が必要です。例はweb.mit.edu/crypto/openssl.cnfにあります。 コンピューター上のフォルダーに構成を配置し、そのパスを設定します(コマンドラインのすべてのアクション)。
set OPENSSL_CONF=__\openssl.cnf
秘密鍵を生成します
openssl genrsa -des3 -out server.key 1024
途中で、キーとパスワードの確認のためにパスワードを入力するように求められます。 証明書リクエストを作成する
openssl req -new -key server.key -out server.csr
リクエストを生成するとき、キーパスワードを入力し、会社、都市、国などに関する情報を入力する必要があります。 記入します。 パスワードなしでキーを使用できるようにするには、キーをコピーして仮釈放します
copy server.key server.key.org openssl rsa -in server.key.org -out server.key
最後に、自己署名証明書を作成します
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
便宜上、Pythonスクリプトの横に証明書とキーを配置します。 クライアントをリッスンするソケットを作成し、アプリケーションが進むポート(以降、Pythonのコード)をリッスンするように設定します
sock = ssl.wrap_socket(socket.socket(), 'server.key', 'server.crt', True) sock.bind( ('localhost', 43433) ) sock.listen(10)
クライアントから着信接続とリクエストを受け取ります
conn, addr = sock.accept() data = conn.recv(1024)
次に、受信したデータを目的のサーバーに送信する必要があります。 このためにソケットとヘルメットのデータを作成します
serv = ssl.wrap_socket(socket.socket()) serv.connect( ('server_url', 443) ) serv.send(data)
リクエストが送信されたので、今度はレスポンスを取得してクライアントに渡す必要があります
data = serv.recv(1024) conn.send(data)
さて、すべてのプロキシの準備ができて実行され、リクエストを投げる-それは動作しません! 理由を調べるには、ログを追加します。
ロギング
ロギングモジュールを接続し、ロギングコンフィグレーションを構成し、興味深い場所にロギングを追加します
import logging logging.basicConfig(filename = "proxy.log", level = logging.DEBUG, format = "%(asctime)s - %(message)s") logging.info(" "); conn, addr = sock.accept() logging.info(" ") data = conn.recv(1024) logging.info(data) logging.info(" ") serv.send(data) logging.info(" ") data = serv.recv(1024) logging.info(data) logging.info(" ") client.send(resp)
すべてのデータを読む
クライアントはブロック単位でデータを送信することがわかりました。 完全なリクエストを読んでいません。 その後、サーバーもブロックで応答することがわかりました。 コードを改善して、要求と応答をブロック単位で読み取ります。 これを行うには、要求全体を追加するバッファーを作成し、ソケットを0.1秒のタイムアウトに設定します。これは、着信接続からのデータを待機し、サイクルでデータを読み取ってバッファーに入れます。 データがない場合、例外を取得してループを終了します
logging.info(" ") data = conn.recv(1024) req = b'' conn.settimeout(0.1) while data: req += data try: data = conn.recv(1024) except socket.error: break logging.info(req)
サーバーからデータを読み取る場合も同じです
logging.info(" ") resp = b'' serv.settimeout(1) data = serv.recv(1024) while data: resp += data try: data = serv.recv(1024) except socket.error: break logging.info(resp)
サーバーとクライアントに送信するデータを変更します
logging.info(" ") serv.send(req) logging.info(" ") client.send(resp)
始めます。 これで動作するようになりましたが、サーバーへのすべてのリクエストでスクリプトを実行する必要があり、あまり便利ではありません。
複数リクエスト処理
スクリプトを改善し、リクエストを処理した後、再びソケットをリッスンします
while True: logging.info(" "); conn, addr = sock.accept() logging.info(" ") data = conn.recv(1024) req = b'' conn.settimeout(0.1) while data: req += data try: data = conn.recv(1024) except socket.error: break logging.info(req) logging.info(" ") serv.send(req) logging.info(" ") resp = b'' serv.settimeout(1) data = serv.recv(1024) while data: resp += data try: data = serv.recv(1024) except socket.error: break logging.info(resp) logging.info(" ") client.send(resp)
これは機能しますが、問題があります-プログラムが通常の方法で終了できない無限ループがあります。 終了するには、キーボード割り込みCtrl + Cを使用してリクエストを送信します。その後、KeyboardInterruptを除き、プログラムは終了します。
サービス停止
多かれ少なかれ通常の出力を提供するために、STOPをソケットに転送することにしました。これが終了の制御コマンドになります。 このようなコマンドのハンドラーを作成します。 これを行うには、クライアントソケットからの読み取りコードを変更する必要があります。 最初の4バイトを受け取り、それらがSTOPの場合、サイクルを中断します。
logging.info(" ") data = conn.recv(4) if data == b'STOP': break
プロキシを停止する関数を作成します。 その中で、ソケット(ssl)を作成し、プロキシにSTOPを送信します
def stop(): logging.info(""); me = ssl.wrap_socket(socket.socket()) me.connect( ('localhost', 43433) ) me.send(b'STOP') me.close()
STOPコマンドを開始するには、コマンドラインパラメーターを使用します。 停止行をコマンドラインに渡した場合、stop()関数を呼び出します(ログ形式を設定した後、このコードと停止関数を最初に配置します)。
if len(sys.argv) > 1: if sys.argv[1] == "stop": stop();
これで、同じスクリプトでプロキシを停止できます。 サーバーの起動コードを停止した後に実行されないように、メイン関数をrun関数でラップすると、
def run(): # - def stop(): # if len(sys.argv) > 1: if sys.argv[1] == "stop": stop(); else: print(" ", sys.argv[1]) else: run()
同時に、間違ったコマンドでケースを処理しました。
悪魔
問題がありました。プロキシアプリケーションを起動するとコマンドラインがハングします。一見するとフリーズしたようです。 この問題を解決するために、デーモンを作成します。 なぜなら Windowsがある場合、ウィンドウなしでプロセスを開始することでデーモンが実行されます。このコードはクロスプラットフォームではありません。 それではdaemonize()関数を書きましょう
import subprocess def daemonize(): logging.info(" "); subprocess.Popen("py proxy.py", creationflags=0x08000000, close_fds=True)
ここでcreationflags = 0x08000000、プロセスのフラグCREATE_NO_WINDOWを設定します。 コマンドラインでstartを渡した場合、デーモンモードでサービスを開始します。
if len(sys.argv) > 1: if sys.argv[1] == "stop": stop(); elif sys.argv[1] == "start": daemonize(); else: print(" ", sys.argv[1]) else: run()
これで、デーモンモードでサービスを開始して停止できます。
マルチタスク
もう1つ小さなタッチとして、複数のクライアントを処理する機能を追加します。これを行うには、クライアント作業コードを別の関数に配置します
def client_run(client, data): req = b'' logging.info(" ") client.settimeout(0.1) while data: req += data try: data = client.recv(1024) except socket.error: break logging.info(req) serv = ssl.wrap_socket(socket.socket()) serv.connect( ('server_name', 443) ) logging.info(" ") serv.send(req) logging.info(" ") resp = b'' serv.settimeout(1) data = serv.recv(1024) while data: resp += data try: data = serv.recv(1024) except socket.error: break logging.info(resp) logging.info(" ") client.send(resp)
そしてメイン関数では、別個のスレッドでclient_runを実行します。 socket.listen(10)をインストールし、同時に最大10個のスレッドを作成できます
def run(): logging.info(" "); sock = ssl.wrap_socket(socket.socket(), 'server.key', 'server.crt', True) sock.bind( ('localhost', 43433) ) sock.listen(10) while True: logging.info(" "); conn, addr = sock.accept() data = conn.recv(4) if data == b'STOP': break logging.info(" ") t = threading.Thread(target = client_run, args = ( conn, data ) ) t.run() logging.info("")
これで、プロキシサービスの準備ができました。
PS:後で、同僚が私の仕事にstunnelを使用できると私に言ったので、誰かが興味があるなら、私はそれを置いて、スクリプトをここに置くことにしました。 stunnelの構成は次のとおりです。
[client-in] sslVersion = SSLv3 accept = 127.0.0.1:43433 connect = 127.0.0.1:8080 [server-out] sslVersion = TLSv1 client = yes accept = 127.0.0.1:8080 connect = server_name:443
私はまた、 サーバーの設定が正しくなく、SNI検証に合格しなかったため、バージョン4.36でのみ機能しました。 そのような検証はありません。
githubのソースgithub.com/sesk/py_proxy