Pythonおよびurllib2でソースIP /インターフェースを設定するにはどうすればよいですか?
残念ながら、使用中の標準ライブラリモジュールのスタック(urllib2、httplib、socket)は、この目的のためにやや不適切に設計されています-操作の重要なポイントで、_HTTPConnection.connect
_(httplib内)は_socket.create_connection
_に委任します。これにより、ソケットインスタンスsock
の作成と_sock.connect
_呼び出しの間に「フック」がまったくなくなり、_sock.bind
_の直前に_sock.connect
_を挿入する必要があります。ソースIPを設定します(このような気密で過度にカプセル化された方法で抽象化を設計しないことで広く伝道しています-今週木曜日にOSCONで「Zenand the Art ofAbstractionMaintenance」というタイトルで話します-しかし、ここでの問題は、このように設計された抽象化のスタックをどのように処理するかです、ため息をつきます)。
このような問題に直面した場合、2つのあまり良くない解決策しかありません。元の設計者が対応しなかった「フック」を配置する必要がある誤った設計のコードをコピー、貼り付け、編集します。または、そのコードを「モンキーパッチ」します。どちらも良いことではありませんが、どちらも機能するので、少なくとも(オープンソースで動的な言語を使用して)そのようなオプションがあることに感謝しましょう。この場合、私はモンキーパッチを適用すると思います(これは悪いですが、コピーアンドペーストのコーディングはさらに悪いです)-次のようなコードフラグメント:
_import socket
true_socket = socket.socket
def bound_socket(*a, **k):
sock = true_socket(*a, **k)
sock.bind((sourceIP, 0))
return sock
socket.socket = bound_socket
_
正確なニーズに応じて(すべてのソケットを同じソースIPにバインドする必要がありますか、それとも...?)、通常の_urllib2
_を使用する前にこれを実行するか、(もちろんより複雑な方法で)実行することができます。必要に応じて、特定の方法でバインドする必要がある発信ソケットだけが必要です(その後、_socket.socket = true_socket
_を復元するたびに、将来のソケットがまだ作成されないようにします)。 2番目の選択肢は、適切にオーケストレーションするために独自の複雑さを追加するため、すべてを説明する前に、そのような複雑さが必要かどうかを明確にするのを待っています。
AKXの良い答えは、「コピー/貼り付け/編集」の代替案の変形であるため、それについてあまり拡張する必要はありません。ただし、connect
メソッドで_socket.create_connection
_を正確に再現しないことに注意してください。ソース ここ (ページの最後にあります)そして、コピー/貼り付け/編集されたバージョンで具体化したい_create_connection
_関数の他の機能を決定します。ルート。
これはうまくいくようです。
import urllib2, httplib, socket
class BindableHTTPConnection(httplib.HTTPConnection):
def connect(self):
"""Connect to the Host and port specified in __init__."""
self.sock = socket.socket()
self.sock.bind((self.source_ip, 0))
if isinstance(self.timeout, float):
self.sock.settimeout(self.timeout)
self.sock.connect((self.Host,self.port))
def BindableHTTPConnectionFactory(source_ip):
def _get(Host, port=None, strict=None, timeout=0):
bhc=BindableHTTPConnection(Host, port=port, strict=strict, timeout=timeout)
bhc.source_ip=source_ip
return bhc
return _get
class BindableHTTPHandler(urllib2.HTTPHandler):
def http_open(self, req):
return self.do_open(BindableHTTPConnectionFactory('127.0.0.1'), req)
opener = urllib2.build_opener(BindableHTTPHandler)
opener.open("http://google.com/").read() # Will fail, 127.0.0.1 can't reach google.com.
ただし、そこで「127.0.0.1」をパラメーター化する方法を理解する必要があります。
HTTPConnectionのsource_address引数 (Python 2.7)で導入)を利用するさらなる改良点は次のとおりです。
import functools
import httplib
import urllib2
class BoundHTTPHandler(urllib2.HTTPHandler):
def __init__(self, source_address=None, debuglevel=0):
urllib2.HTTPHandler.__init__(self, debuglevel)
self.http_class = functools.partial(httplib.HTTPConnection,
source_address=source_address)
def http_open(self, req):
return self.do_open(self.http_class, req)
これにより、source_address対応のカスタム rllib2.HTTPHandler 実装が得られます。これを新しい rllib2.OpenerDirector に追加し、次のコードを使用してデフォルトのオープナーとしてインストールできます(将来的には rlopen() 呼び出し)。
handler = BoundHTTPHandler(source_address=("192.168.1.10", 0))
opener = urllib2.build_opener(handler)
urllib2.install_opener(opener)
モンキーパッチの少し良いバージョンでフォローアップすると思いました。一部のソケットで異なるポートオプションを設定できるようにする必要がある場合、またはソケットをサブクラス化するSSLのようなものを使用している場合は、次のコードの方が少しうまく機能します。
_ip_address = None
def bind_outgoing_sockets_to_ip(ip_address):
"""This binds all python sockets to the passed in ip address"""
global _ip_address
_ip_address = ip_address
import socket
from socket import socket as s
class bound_socket(s):
def connect(self, *args, **kwargs):
if self.family == socket.AF_INET:
if self.getsockname()[0] == "0.0.0.0" and _ip_address:
self.bind((_ip_address, 0))
s.connect(self, *args, **kwargs)
socket.socket = bound_socket
別のIPアドレスにバインドする必要がある同じプロセスでWebサーバーのようなものを実行する必要がある場合にのみ、接続時にソケットをバインドする必要があります。
利用可能な最高レベルでモンキーパッチを適用する必要があるという理由で、httplib.HTTPSConnection.__init__()
のsource_address
キーワード引数(urllib2
、AFAICTによって公開されていない)を利用して、httplib
の代わりにsocket
にパッチを適用するAlexの回答の代替案を次に示します。テストされ、作業中Python 2.7.2。
import httplib
HTTPSConnection_real = httplib.HTTPSConnection
class HTTPSConnection_monkey(HTTPSConnection_real):
def __init__(*a, **kw):
HTTPSConnection_real.__init__(*a, source_address=(SOURCE_IP, 0), **kw)
httplib.HTTPSConnection = HTTPSConnection_monkey
Python 2.7 httplib.HTTPConnectionにはsource_addressが追加されており、バインドするIPポートペアを提供できます。
参照: http://docs.python.org/2/library/httplib.html#httplib.HTTPConnection