もっと詳しく

挨拶と前書き

みなさんこんにちは、絵とウーバーイーツが楽しいなおこです。

今回はPythonのプログラムをかいている際にrequests使って情報の取得を行おうとしましたがクライアント側はIPv6対応しているけどサーバー側のウェブサーバーはIPv4しか対応していない、けどAAAAレコードにはIPv6が振られているというときにIPv6で接続をしようとしてしまうみたいなガチのレアケースに遭遇しました。

requests自体にipv4やipv6を切り替える機能が無いためOS側から変更しようと思いましたが使っている環境がレンタルサーバーなどのOSのシステム領域をいじれない環境だったため、プログラム側から調整できないかとのgithubやstackoverflowを覗いてみると同じような問題に遭遇している方が数名居ました。

Add possibility to specify IPv4 or IPv6? · Issue #1691 · psf/requests
I noticed that request by default uses IPv6 if a host is capable of it. However, requests seems to lack the ability to force it to use either IPv6 or IPv4. Woul…

Force requests to use IPv4 / IPv6
How to force the requests library to use a specific internet protocol version for a get request? Or can this be achieved better with another method in Python? I…

要はソケットいじればうまく行くよってことらしい。

本題

ソケットをいじるのも良いですが、接続ごとに指定してかんたんにできる物ないかな〜って調べていると先程のissuesの最後にipv4やipv6の選択ができるようにパッチを当てたラッパーがあるらしい↓

python-requests custom address family patch (00824) · Snippets · Snippets
GitLab.com

requests_wrapper.pyをダウンロードしてプログラムを同じディレクトリに設置して使ってやってみたけど、、、https環境のサイトには使えない!httpは行ける

import socket
import requests_wrapper as requests
 
response = requests.get("http://ipv4.google.com/", family=socket.AF_INET)
print (response.status_code)
print (response.text)

じゃあhttpsのipv4でできないの?と思うかもしれませんができます。ラッパーを使わず、数行を追記するだけでipv4の通信にしたりipv6にしたり両方にしたりすることができます。

実際にプログラムを書いていく

ipv4だけの接続

ipv4の場合はこのようになります。

import socket
import requests
# これ↓
import requests.packages.urllib3.util.connection as urllib3_cn
def allowed_gai_family4(): return socket.AF_INET  
urllib3_cn.allowed_gai_family = allowed_gai_family4
# これ↑
 
response = requests.get("https://ipv4.google.com/")
print (response.status_code)
print (response.text)

ipv6だけの接続

今度は逆にipv6へ接続してみましょうsocket.AF_INETに6を追加するだけでipv6だけに制限することができます。

import socket
import requests
# これ↓
import requests.packages.urllib3.util.connection as urllib3_cn
def allowed_gai_family6(): return socket.AF_INET6  #ここ変更
urllib3_cn.allowed_gai_family = allowed_gai_family6  #ここ変更
# これ↑
 
response = requests.get("https://ipv6.google.com/")  #ここ変更
print (response.status_code)
print (response.text)

その他

デフォルトに戻すには?(IPv6とipv4の両方)

import socket
import requests
# これ↓
import requests.packages.urllib3.util.connection as urllib3_cn
def allowed_gai_family(): return socket.AF_UNSPEC  #ここ変更
urllib3_cn.allowed_gai_family = allowed_gai_family  #ここ変更
# これ↑
 
response = requests.get("https://ipv6.google.com/")  
print (response.status_code)
print (response.text)

事前に関数を用意しておけば動的に変更することも可能です。

import socket
import requests

# これ↓
import requests.packages.urllib3.util.connection as urllib3_cn
def allowed_gai_family(): return socket.AF_UNSPEC  
def allowed_gai_family4(): return socket.AF_INET  
def allowed_gai_family6(): return socket.AF_INET6
urllib3_cn.allowed_gai_family = allowed_gai_family
# これ↑
 
# ipv6 or ipv4
urllib3_cn.allowed_gai_family = allowed_gai_family
response = requests.get("https://google.com/")
print (response.status_code)

# ipv4
urllib3_cn.allowed_gai_family = allowed_gai_family4
response = requests.get("https://ipv4.google.com/")
print (response.status_code)

# ipv6
urllib3_cn.allowed_gai_family = allowed_gai_family6
response = requests.get("https://ipv6.google.com/")
print (response.status_code)

最後に

ちなみに、ブログで紹介するプログラムには例としてgoogleのサイトを使用しています。ipv4.google.comやipv6.google.comは適切にAAAAレコードやAレコードが設定されているためプログラム側でいじる必要もないし問題ないですが、個人サイトのAPIで実際にあった怖い話ですね。

The post Python OS側でIPv6制限せずにプログラムで制限を行いIPv4 requestsを行う方法 first appeared on FascodeNetwork Blog.