2016-03-18 34 views
19

this Matasano crypto challenge私は、人工的に減速した文字列比較関数を持つサーバーに対してタイミング攻撃を行うことを試みています。 「あなたの選択したWebフレームワーク」を使用すると言われていましたが、Webフレームワークをインストールする気がなかったので、モジュールにHTTPServer classを組み込みました。urllib.requestからHTTPServerへの多数の同時要求を行うときの不思議な例外

私はうまくいきましたが、それは非常に遅かったので、multiprocessing.dummyに組み込まれた(あまりにも文書化された)スレッドプールを使用してスピードアップしようとしました。はるかに高速でしたが、私は奇妙なことに気付きました。私が8つ以下の要求を同時に行うとうまくいきます。それ以上のものがあれば、それはしばらくの間働き、一見無作為な時に私にエラーを与えます。エラーは一貫性がなく、常に同じではないようですが、通常はConnection refused, invalid argument,OSError: [Errno 22] Invalid argument,urllib.error.URLError: <urlopen error [Errno 22] Invalid argument>,BrokenPipeError: [Errno 32] Broken pipe、またはurllib.error.URLError: <urlopen error [Errno 61] Connection refused>です。

サーバーで処理できる接続の数に制限はありますか。私はスレッド自体の数は問題ではないと思います。なぜなら、Webサーバーを実行せずにスローダウンした文字列の比較を行い、同時に500個のスレッドを呼び出す単純な関数を書いたからです。 100スレッド以上を使用しているクローラを作成しているので(同じWebサイトに同時にリクエストを出している)、うまく機能しているため、多くのスレッドから要求を出すだけで問題になるとは思わない。おそらく、HTTPServerは大量のトラフィックを受け取った本番Webサイトを確実にホストするためのものではないようですが、クラッシュさせるのは簡単なことですが驚いています。

このような不思議なバグを診断するときに通常行っているように、問題に無関係に見えるコードから徐々に削除することを試みましたが、この場合はあまり役に立ちませんでした。一見無関係なコードを削除していたように見えましたが、サーバーが処理できる接続の数は徐々に増加しましたが、クラッシュの原因は明確ではありませんでした。

私は一度に行うことができるリクエストの数を増やす方法を知っていますか、それともなぜこれが起こっているのですか?

私のコードは複雑ですが、私は、問題を示し、この簡単なプログラムを思い付いた:何らかの理由で

#!/usr/bin/env python3 

import os 
import random 

from http.server import BaseHTTPRequestHandler, HTTPServer 
from multiprocessing.dummy import Pool as ThreadPool 
from socketserver import ForkingMixIn, ThreadingMixIn 
from threading import Thread 
from time import sleep 
from urllib.error import HTTPError 
from urllib.request import urlopen 


class FancyHTTPServer(ThreadingMixIn, HTTPServer): 
    pass 


class MyRequestHandler(BaseHTTPRequestHandler): 
    def do_GET(self): 
     sleep(random.uniform(0, 2)) 
     self.send_response(200) 
     self.end_headers() 
     self.wfile.write(b"foo") 

    def log_request(self, code=None, size=None): 
     pass 

def request_is_ok(number): 
    try: 
     urlopen("http://localhost:31415/test" + str(number)) 
    except HTTPError: 
     return False 
    else: 
     return True 


server = FancyHTTPServer(("localhost", 31415), MyRequestHandler) 
try: 
    Thread(target=server.serve_forever).start() 
    with ThreadPool(200) as pool: 
     for i in range(10): 
      numbers = [random.randint(0, 99999) for j in range(20000)] 
      for j, result in enumerate(pool.imap(request_is_ok, numbers)): 
       if j % 20 == 0: 
        print(i, j) 
finally: 
    server.shutdown() 
    server.server_close() 
    print("done testing server") 

それは100以上のスレッドかそこらを持っていない限り、上記のプログラムが正常に動作しますが、私のチャレンジの実際のコードは8つのスレッドしか扱えません。私は9でそれを実行すると、私は通常接続エラーを取得し、10と、私は常に接続エラーを取得します。 multiprocessing.dummy.poolの代わりにconcurrent.futures.ThreadPoolExecutor,concurrent.futures.ProcessPoolExecutormultiprocessing.poolを使ってみましたが、どれも役に立たないようでした。私は平文HTTPServerオブジェクト(ThreadingMixInなし)を使ってみましたが、非常にゆっくりと動作させただけで問題を解決できませんでした。私はForkingMixInを使ってみましたが、それはそれを修正しませんでした。

私はこれについて何をすべきですか?私は、2013年後半のMacBook ProでOS X 10.11.3を実行しているPython 3.5.1を実行しています。

EDIT:私はForkingMixInと、そしてThreadingMixInで、シンプルHTTPServerとして、代わりにスレッドのプロセスでサーバーを実行しているなど、いくつかのより多くの事を、試してみました。誰も助けなかった。

編集:この問題は私が思ったよりも見知らぬです。私はサーバーで1つのスクリプトを作成しようとしました。スレッドをたくさん作成してリクエストを作成し、端末のさまざまなタブでそれらを実行しました。サーバーのプロセスは正常に実行されましたが、要求を作成したプロセスがクラッシュしました。例外は、ConnectionResetError: [Errno 54] Connection reset by peer,urllib.error.URLError: <urlopen error [Errno 54] Connection reset by peer>,OSError: [Errno 41] Protocol wrong type for socket,urllib.error.URLError: <urlopen error [Errno 41] Protocol wrong type for socket>,urllib.error.URLError: <urlopen error [Errno 22] Invalid argument>の組み合わせであった。

上記のようなダミーサーバーで試してみましたが、同時リクエスト数を5以下に制限してもうまくいきましたが、6つのリクエストでクライアントプロセスがクラッシュしました。サーバーからいくつかのエラーがありましたが、それは継続しました。要求を行うためにスレッドまたはプロセスを使用していたかどうかにかかわらず、クライアントはクラッシュしました。私はその後、サーバに遅い機能を入れようとしましたが、同時に60件のリクエストを処理できましたが、70でクラッシュしました。これは、サーバに問題があるという証拠と矛盾するようです。

EDIT:私はrequests代わりのurllib.requestを用いて説明し、同様の問題に遭遇したことのほとんどを試してみました。

EDIT:私は現在、OS X 10.11.4を実行しており、同じ問題が発生しています。

+0

あなたがあなたの未使用のクライアント接続を閉じている確保していますか? –

+0

@Cory Shay、私は 'x = urlopen(何でも)'と 'x.close()'をやってみましたが、それは役に立たなかったようです。 –

+0

私が述べた理由が必ずしもこの問題が起こっている理由ではないことを認めなければならない。潜在的に他人がいる可能性があります。しかし、これを調べるのに役立つかもしれない質問がいくつかあります。「ulimit -r $((32 * 1024))を発行すればどうなるでしょうか?」と "netstat -anp | grep SERVERPROCESSNAME'の出力は何ですか?" –

答えて

8

デフォルトのlisten()のバックログ値が使用されています。これは、おそらく多くのエラーの原因です。これは、接続が確立されている同時クライアントの数ではなく、接続が確立される前に待機キューで待機しているクライアントの数です。サーバークラスを次のように変更してください。

class FancyHTTPServer(ThreadingMixIn, HTTPServer): 
    def server_activate(self): 
     self.socket.listen(128) 

128です。さらに増やしたい場合は、socket.SOMAXCONNまたはOS somaxconnをチェックしたいかもしれません。依然として重い負荷の下でランダムなエラーが発生する場合は、ulimitの設定を確認し、必要に応じて増やす必要があります。

私はあなたの例でそれを行い、1000スレッド以上のスレッドが正常に実行されているので、問題が解決するはずです。


更新

それは改善したが、それはまだ200台の同時クライアントでクラッシュだ場合、私はあなたの主な問題は、バックログのサイズだったかなり確信しています。問題は同時クライアントの数ではなく、同時接続要求の数であることに注意してください。それが何を意味するのかについての簡単な説明です.TCPの内部構造に深く関わることはありません。この例では

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
s.bind((HOST, PORT)) 
s.listen(BACKLOG) 
while running: 
    conn, addr = s.accept() 
    do_something(conn, addr) 

、ソケットは現在、特定のポートで接続を受け入れて、クライアントが接続されるまでs.accept()コールがブロックされます。多くのクライアントを同時に接続しようとすることができます。また、アプリケーションによっては、s.accept()に電話できず、クライアントが接続しようとしている速度でクライアント接続をディスパッチすることができない場合があります。保留中のクライアントはキューに入れられ、そのキューの最大サイズはBACKLOG値によって決定されます。キューがいっぱいになると、クライアントはConnection Refusedエラーで失敗します。

ThreadingMixInクラスは、do_something(conn, addr)コールを別のスレッドで実行するため、スレッドが役に立たないため、サーバーはメインループとs.accept()呼び出しに戻ることができます。

さらにバックログを増やすことはできますが、キューが大きくなりすぎてサーバーがs.accept()コールを実行する前に一部のクライアントがタイムアウトする可能性があるため、それが役に立たないところがあります。

上記のとおり、問題は同時接続の数ではなく、同時接続の試行回数です。実際のアプリケーションでは128で十分ですが、一度にすべての200個のスレッドに接続してキューをフラッディングしようとしているため、テストでエラーが発生しています。

Too many open filesエラーが発生しない限り、ulimitを心配する必要はありませんが、128を超えるバックログを増やしたい場合は、socket.SOMAXCONNに関する調査を行ってください。これは良いスタートです:https://utcc.utoronto.ca/~cks/space/blog/python/AvoidSOMAXCONN

+1

私はそれをして、150スレッドでも動作します!それは200でクラッシュしますが、150は私の目的には十分かもしれません。もしそうでなければ、少なくとも私はそれについて何らかのアイディアを持っているかもしれません。私はこの「聴く」ことが何であるのか、あるいはsomaxconnやulimitが何であるのか分からないので、私はそれをすべて研究し、異なる数字を試してみたいと思います。恩恵は、あなたの答えは非常に有用だった。ありがとうございました。 –

+1

@EliasZamaria最新の回答を確認してください。私はあなたが少し失われているので、より詳細な説明を提供しました。 –

+0

ありがとうございました。このTCPのものは、私が通常扱っているものよりも低レベルです。私はそれについて多くは知らないのです。私は時間があり、ここに投稿するときにもう少し私は自分自身に対処することができない以上の問題に遭遇すると、それをもう少し再生します。 –

-1

ノーマルは、コアと同じ数のスレッドしか使用しないため、8スレッドの要件(仮想コアを含む)を使用することです。スレッディングモデルは作業を開始するのが最も簡単ですが、実際にはそれを行うためのゴミの方法です。複数の接続を処理するより良い方法は、非同期のアプローチを使用することです。それはより困難です。

スレッドを使用して、プログラムを終了した後にプロセスが開いたままであるかどうかを調べることができます。これは、スレッドが閉じていないことを意味し、明らかに問題を引き起こします。

これを試してみてください

...あなたのスレッドが正常に終了していることを確認します
class FancyHTTPServer(ThreadingMixIn, HTTPServer): 
    daemon_threads = True 

。それはスレッドプールで自動的に発生する可能性がありますが、とにかく試してみる価値があります。

+1

最初に、I/OにバインドされていないタスクがCPUバインドの場合は、コアと同じ数のスレッドを使用します。次に、Pythonスレッドは、GILのために一度に1つのスレッドでのみ実行されます。 –

+1

訂正:Pythonスレッドは、一度に1つのコアでのみ実行されます。 –

1

NodeJsでコードを正常に実行したため、問題がIOブロッキングに関連していると思います。私はまた、サーバーとクライアントの両方が個別に作業するのに問題があることに気付きました。

しかし、いくつかの変更を要求の数を増加させることが可能である:

http.server.HTTPServer.request_queue_size = 500

    • 同時接続数を定義します。

      別のプロセスでサーバーを実行します。

      server = multiprocessing.Process(target = RunHTTPServer) server.start()

    • の再利用を許可する要求を実行するために、クライアント側で

    • 要求を処理するために、サーバー側でスレッドプールを使用して、接続プールを使用しますスキーマを設定することで、すべてのこれらの変更では、「キープアライブ」ヘッダー

    を使用して、クライアント側の接続は、私はすべての問題なしで500個のスレッドでコードを実行するために管理しました。

    import random 
    from time import sleep, clock 
    from http.server import BaseHTTPRequestHandler, HTTPServer 
    from multiprocessing import Process 
    from multiprocessing.pool import ThreadPool 
    from socketserver import ThreadingMixIn 
    from concurrent.futures import ThreadPoolExecutor 
    from urllib3 import HTTPConnectionPool 
    from urllib.error import HTTPError 
    
    
    class HTTPServerThreaded(HTTPServer): 
        request_queue_size = 500 
        allow_reuse_address = True 
    
        def serve_forever(self): 
         executor = ThreadPoolExecutor(max_workers=self.request_queue_size) 
    
         while True: 
          try: 
           request, client_address = self.get_request() 
           executor.submit(ThreadingMixIn.process_request_thread, self, request, client_address) 
          except OSError: 
           break 
    
         self.server_close() 
    
    
    class MyRequestHandler(BaseHTTPRequestHandler): 
        default_request_version = 'HTTP/1.1' 
    
        def do_GET(self): 
         sleep(random.uniform(0, 1)/100.0) 
    
         data = b"abcdef" 
         self.send_response(200) 
         self.send_header("Content-type", 'text/html') 
         self.send_header("Content-length", len(data)) 
         self.end_headers() 
         self.wfile.write(data) 
    
        def log_request(self, code=None, size=None): 
         pass 
    
    
    def RunHTTPServer(): 
        server = HTTPServerThreaded(('127.0.0.1', 5674), MyRequestHandler) 
        server.serve_forever() 
    
    
    client_headers = { 
        'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; Win64; x64)', 
        'Content-Type': 'text/plain', 
        'Connection': 'keep-alive' 
    } 
    
    client_pool = None 
    
    def request_is_ok(number): 
        response = client_pool.request('GET', "/test" + str(number), headers=client_headers) 
        return response.status == 200 and response.data == b"abcdef" 
    
    
    if __name__ == '__main__': 
    
        # start the server in another process 
        server = Process(target=RunHTTPServer) 
        server.start() 
    
        # start a connection pool for the clients 
        client_pool = HTTPConnectionPool('127.0.0.1', 5674) 
    
        # execute the requests 
        with ThreadPool(500) as thread_pool: 
         start = clock() 
    
         for i in range(5): 
          numbers = [random.randint(0, 99999) for j in range(20000)] 
          for j, result in enumerate(thread_pool.imap(request_is_ok, numbers)): 
           if j % 1000 == 0: 
            print(i, j, result) 
    
         end = clock() 
         print("execution time: %s" % (end-start,)) 
    

    アップデート1:

    request_queue_sizeはちょうどあなたに実行することができない要求を格納するためのより多くのスペースを与え増やすあなたはそれを試してみることにしたいのであれば、ここでの完全なコードです彼らは後で実行することができます。 キューが長いほど、応答時間の分散が大きくなります。これは、ここでの目標とは反対のことです。 ThreadingMixInは、あらゆるリクエストに対してスレッドを作成して破棄するため、高価ですので、理想的ではありません。待機キューを減らすためのより良い選択は、再利用可能なスレッドのプールを使用して要求を処理することです。

    別のプロセスでサーバを実行している理由は、実行時間を短縮するために別のCPUを利用することです。 HTTPConnectionPoolを使用してクライアント側の場合

    は、接続を分析しながら、私もまた、urlopenといくつかの奇妙な振る舞いを持っていたので、私は要求の一定の流れを維持するために見つけた唯一の方法でした。

  • +0

    私は 'request_queue_size'を試しました。これはPedroが提案した' self.socket.listen'と同等で、私の問題を修正したようです。 –

    +0

    'http.server.HTTPServer.allow_reuse_address = True'が何をすべきか分かりません。これはデフォルト値の1のようです。https://hg.python.org/cpython/file/3.5/Lib/http/server.py#l134 –

    +0

    を参照してください。私の質問に編集したように、私は実行しようとしましたスレッドではなくプロセス内のサーバーであり、それは役に立たなかった。 –