iptablesでREDIRECTする前のポート番号を取得する
iptablesで次のように複数のポートを特定のポートにリダイレクトしているような状況で、リダイレクト前のポート番号をソケットから取得する方法のメモ。
-A PREROUTING -i eth0 -p tcp -m multiport --dports 8000:8080 -j REDIRECT --to-ports 5000
環境
Ubuntu 14.04.4 LTS 64bit版
$ uname -a Linux vm-ubuntu64 3.19.0-25-generic #26~14.04.1-Ubuntu SMP Fri Jul 24 21:16:20 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux $ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 14.04.4 LTS Release: 14.04 Codename: trusty
SO_ORIGINAL_DST
Linuxカーネルには、ドキュメント化されていないソケットオプションとしてSO_ORIGINAL_DSTが存在する。 これを使うと、ソケットからリダイレクト前のポート番号を取得することができる。
74 /* Arguments for setsockopt SOL_IP: */ 75 /* 2.0 firewalling went from 64 through 71 (and +256, +512, etc). */ 76 /* 2.2 firewalling (+ masq) went from 64 through 76 */ 77 /* 2.4 firewalling went 64 through 67. */ 78 #define SO_ORIGINAL_DST 80
ただし、このオプションはTCPまたはSCTPの場合にしか使えず、UDPでは使えない。
279 /* We only do TCP and SCTP at the moment: is there a better way? */ 280 if (sk->sk_protocol != IPPROTO_TCP && sk->sk_protocol != IPPROTO_SCTP) { 281 pr_debug("SO_ORIGINAL_DST: Not a TCP/SCTP socket\n"); 282 return -ENOPROTOOPT; 283 }
Pythonで書いてみる
上を参考に、ソケットからリダイレクト前のポート番号を取得するPythonコードを書くと次のようになる。
def get_original_dport(s): SO_ORIGINAL_DST = 80 buf = s.getsockopt(socket.SOL_IP, SO_ORIGINAL_DST, 16) return struct.unpack('>H', buf[2:4])[0]
getsockopt関数の戻り値はsockaddr_in構造体となるので、structモジュールを使ってポート番号を取り出せばよい。
$ man 7 ip
Address format An IP socket address is defined as a combination of an IP interface address and a 16-bit port number. The basic IP protocol does not supply port numbers, they are implemented by higher level protocols like udp(7) and tcp(7). On raw sockets sin_port is set to the IP protocol. struct sockaddr_in { sa_family_t sin_family; /* address family: AF_INET */ in_port_t sin_port; /* port in network byte order */ struct in_addr sin_addr; /* internet address */ }; /* Internet address. */ struct in_addr { uint32_t s_addr; /* address in network byte order */ };