Python3 get{sock|peer}name under FreeBSD inetd with tcp46 Quiz

Summary

  • FreeBSDのinetd経由でpython3スクリプトを動かす。
  • スクリプト内でstdinのfile descriptor (0)を使ってsocketを開く。
  • getsocknameやgetpeernameでlocal/remoteのIPアドレスを取得しようとすると
  • inetd.confでtcp46を指定していると返されるIPアドレスを解釈できない。
  • tcpやtcp4なら問題なく解釈できる。
  • python3ではなくgolangで同等のことをやると期待通りに動作する。

環境

各種バージョン。

$ freebsd-version -ku
12.1-RELEASE-p1
12.1-RELEASE-p1

$ uname -srm
FreeBSD 12.1-RELEASE-p1 amd64

$ python3 --version
Python 3.6.9

inetd 設定。/etc/rc.confでinetd_enable=”YES”とするとかservice inetd onestartなどを使うとかは適宜。

/etc/services
foo 8888/tcp
inetd.conf
foo stream tcp46 nowait root /path/to/foo.py foo.py

fdからsocketを得る方法としてsocket.fromfd()とsocket.socket()の2つあるようなので両方試す。他にあるかもしれない。

foo.py
#!/usr/local/bin/python3

import socket
import sys

if __name__ == '__main__':

  print('# socket.fromfd()')
  s = socket.fromfd(sys.stdin.fileno(), socket.AF_INET, socket.SOCK_STREAM)
  pname = s.getpeername()
  print('  pname:', pname)
  sname = s.getsockname()
  print('  sname:', sname)

  print('# socket.socket()')
  s = socket.socket(fileno=sys.stdin.fileno())
  pname = s.getpeername()
  print('  pname:', pname)
  sname = s.getsockname()
  print('  sname:', sname)

172.30.0.1でinetdとスクリプトを待ち受けさせ、192.168.0.195からアクセスする。 (両方のIPアドレスはプライベートなものに書き換えました。)

inetd.confでprotocol指定をtcpやtcp4にすると期待通りのIPアドレスを取得できるが、tcp46にするとうまく理解できないものが返される。 (恥ずかしながらIPv6の環境がないのでtcp6は試せない。)

tcpやtcp4のときは期待どおり
# socket.fromfd()
  pname: ('192.168.0.195', 54267)
  sname: ('172.30.0.1', 8888)
# socket.socket()
  pname: ('192.168.0.195', 54267)
  sname: ('172.30.0.1', 8888)
tcp46のときは駄目で繰り返すと一部変化する
$ nc 172.30.0.1 8888
# socket.fromfd()
  pname: ('::%54', 53956, 0, 54)
  sname: ('::%4294967295', 8888, 0, 4294967295)
# socket.socket()
  pname: ('::29bf:3c00:800:0', 53956, 0, 0)
  sname: ('::80a:1a11:800:0%4294960592', 8888, 0, 4294960592)

$ nc 172.30.0.1 8888
# socket.fromfd()
  pname: ('::%61', 53999, 0, 61)
  sname: ('::%4294967295', 8888, 0, 4294967295)
# socket.socket()
  pname: ('::29bf:3c00:800:0', 53999, 0, 0)
  sname: ('::80a:1a11:800:0%4294960592', 8888, 0, 4294960592)

IPv4/IPv6両受けなので、IPv6形式のアドレスが帰ってくるのは当然だし、4個組の値になっているのも マニュアル にある通り。

  • AF_INETのときは(host, port)
  • AF_INET6のときは(host, port, flowinfo, scopeid)

でも、IPv6形式のアドレスだと思って読んでもうまく理解できないのである。 (どなたかご教示賜ればありがたいです)

同様のことをやるgolangプログラムを書いてみるとtcp46でも期待通りに動くので、 私がポカをやっているのでなければこれはひょっとしてPython側の問題ではないかと思っております。

備考

2020/Jan/09 ごろ書いた。