Python

複数接続同時処理するサーバを立てる

More than 1 year has passed since last update.

pythonでTCPサーバを立てるのに SocketServer.TCPServer を使った例をよく見かけます。

SocketServer.TCPServerを使った例
import SocketServer

HOST, PORT = "", 12345

class SampleHandler(SocketServer.BaseRequestHandler):
    def handle(self):
        client = self.request
        address = self.client_address[0]
        client.send("May I ask your name? ")
        name = client.recv(80).strip()
        self.request.send("Welcome %s from %s\n" % (name, address))

if __name__ == "__main__":
    server = SocketServer.TCPServer((HOST, PORT), SampleHandler)
    server.serve_forever()

ですが、これですと1時点に1接続しか処理できず、処理中に新たな接続要求が来た場合には前の処理が終了するまで待たされてしまいます。
この SocketServer.TCPServerSocketServer.ThreadingTCPServer に変えるだけで複数接続を同時処理できるようになります。

SocketServer.TCPServerをSocketServer.ThreadingTCPServerに変更(メイン部分のみ)
if __name__ == "__main__":
    server = SocketServer.ThreadingTCPServer((HOST, PORT), SampleHandler)
    server.serve_forever()

ただし、使用ソケット数が増えてしまうので、iptablesで接続数上限を設定したり、sysctlでTIME_WAIT時間を短くするなどの対処も行うといいでしょう。上限や時間はサービスに合わせて調整してください。

iptables設定で一つのIPアドレスからは15接続に制限
-A INPUT -p tcp -m tcp --dport 12345 --tcp-flags FIN,SYN,RST,ACK SYN -m connlimit --connlimit-above 15 --connlimit-mask 32 --connlimit-saddr -j REJECT --reject-with tcp-reset
/etc/sysctl.confでTIME_WAIT時間を15秒に短縮(未設定時は60秒)
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15

また、スクリプトを再起動するとアドレス使用中エラーになるなら、次のようにスクリプト側でアドレス再利用設定をしておくといいでしょう。

import SocketServer
import socket

HOST, PORT = "", 12345

class SampleHandler(SocketServer.BaseRequestHandler, object):
    def handle(self):
        client = self.request
        address = self.client_address[0]
        client.send("May I ask your name? ")
        name = client.recv(80).strip()
        self.request.send("Welcome %s from %s\n" % (name, address))

class SampleServer(SocketServer.ThreadingTCPServer, object):
    def server_bind(self):
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.socket.bind(self.server_address)

if __name__ == "__main__":
    server = SampleServer((HOST, PORT), SampleHandler)
    server.serve_forever()

ThreadingTCPServerを使ってCTF問題サーバを2つほど運用してみました。

SECCON CTF 2014 winter online qualifications
Choose the number : https://github.com/shiracamus/seccon2014/blob/master/number/number.py
Let's disassemble : https://github.com/shiracamus/seccon2014/blob/master/disassemble/disassemble.py