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 */
           };

関連リンク