inetd の IPv6 対応


FreeBSD は 4.0-RELEASE から標準で IPv6 が使えるようになっており、 inetd も IPv6 に対応しています。 inetd の IPv6 対応は、TCP の場合、以下のような仕様になってます。

  • ``tcp'' (または tcp4) と定義した場合は IPv4 only のソケットを扱う
  • ``tcp6'' と定義した場合は IPv6 only のソケットを扱う
  • ``tcp46'' と定義した場合は IPv6/IPv4 兼用のソケットを扱う

一般に AF_INET6 のソケットを生成し、そのアドレスを in6addr_any (ANY アドレス) として bind(2), listen(2) した場合、 指定したポート番号に対するコネクション確立要求は、IPv6 のみではなく、 IPv4 のものも受け付けることが出来ます。 inetd.conf、にて ``tcp46'' と定義した場合の動作がこれにあたります。

クライアント側アプリケーションが IPv4 にて接続してきた場合は、 prefix が ::ffff: となった IPv4 mapped IPv6 address として、 アプリケーションに通知されます(accept(2) や getpeername(2) にて取得できるアドレス)。

inetd.conf にて ``tcp'' と定義した場合は、AF_INET のソケットを生成しますので、 IPv4 での接続要求しか受け付けることが出来ません。

では、どのようにして inetd は ``tcp6'' と ``tcp46'' の動作を切り分けているのか、 確認してみます。

inetd のソースは /usr/src/usr.sbin/inetd/inetd.c です。 getconfigent() が inetd.conf のエントリを読み込む処理で、 以下の辺りがポイントになります。


   1534 struct servtab *
   1535 getconfigent()
   1536 {

       ... (snip) ...

   1709     sep->se_nomapped = 0;
   1710     while (isdigit(sep->se_proto[strlen(sep->se_proto) - 1])) {
   1711 #ifdef INET6
   1712         if (sep->se_proto[strlen(sep->se_proto) - 1] == '6') {
   1713             if (no_v6bind != 0) {
   1714                 syslog(LOG_NOTICE, "IPv6 bind is ignored for %s",
   1715                        sep->se_service);
   1716                 freeconfig(sep);
   1717                 goto more;
   1718             }
   1719             sep->se_proto[strlen(sep->se_proto) - 1] = '\0';
   1720             v6bind = 1;
   1721             continue;
   1722         }
   1723 #endif
   1724         if (sep->se_proto[strlen(sep->se_proto) - 1] == '4') {
   1725             sep->se_proto[strlen(sep->se_proto) - 1] = '\0';
   1726             v4bind = 1;
   1727             continue;
   1728         }
   1729         /* illegal version num */
   1730         syslog(LOG_ERR, "bad IP version for %s", sep->se_proto);
   1731         freeconfig(sep);
   1732         goto more;
   1733     }
   1734     if (strcmp(sep->se_proto, "unix") == 0) {
   1735             sep->se_family = AF_UNIX;
   1736     } else
   1737 #ifdef INET6
   1738     if (v6bind != 0) {
   1739         sep->se_family = AF_INET6;
   1740         if (v4bind == 0 || no_v4bind != 0)
   1741             sep->se_nomapped = 1;
   1742     } else
   1743 #endif

inetd.conf のプロトコル名文字列が sp->se_proto に格納されており、 その末尾の文字が数字の場合に、末尾の文字を消しながら処理をループしています。 ソースを読めばわかる通り、``tcp4'' と定義されていた場合は v4bind が 1 となり、``tcp6'' と定義されいた場合は v6bind が 1 となります。 ``tcp46'' の場合は v4bind, v6bind ともに 1 が設定されます。

この状態でループを抜け、1738 行目の if 文に進みます。 定義が ``tcp6'' となっていた場合は、v6bind == 1, v4bind == 0 なので、 1741 行目の処理が実行され、sep->se_nomapped に 1 が格納されます。

sep->se_nomapped は IPv4 mapped IPv6 address は扱わない、 というフラグになります。この機能を実現するのが、IPV6_V6ONLY というソケットオプション (IPv6 レベルのオプション) です。

inetd は setup() にて inetd.conf のエントリに応じたソケットを生成した後、 sep->se_nomapped の値に応じて IPV6_V6ONLY オプションに関する setsockopt(2) を実行します。


   1187 void
   1188 setup(sep)
   1189     struct servtab *sep;
   1190 {

       ... (snip) ...

   1219     if (sep->se_family == AF_INET6) {
   1220         int flag = sep->se_nomapped ? 1 : 0;
   1221         if (setsockopt(sep->se_fd, IPPROTO_IPV6, IPV6_V6ONLY,
   1222                    (char *)&flag, sizeof (flag)) < 0)
   1223             syslog(LOG_ERR, "setsockopt (IPV6_V6ONLY): %m");
   1224     }

上記の通り sep->se_nomapped が 1 であれば、 IPV6_V6ONLY オプションをを有効にします。

IPV6_V6ONLY オプションは Draft-ietf-ipngwg-rfc2553bis-03.txt にて提唱されている、新しいオプションです。 以下のように記述されています。


  5.3 IPV6_V6ONLY option for AF_INET6 Sockets
  
  This socket option restricts AF_INET6 sockets to IPv6 communications
  only.  As stated in section <3.7 Compatibility with IPv4 Nodes>,
  AF_INET6 sockets may be used for both IPv4 and IPv6 communications. Some
  applications may want to restrict their use of an AF_INET6 socket to
  IPv6 communications only.  For these applications the IPV6_V6ONLY socket
  option is defined.  When this option is turned on, the socket can be
  used to send and receive IPv6 packets only.  This is an IPPROTO_IPV6
  level option.  This option takes an int value.  This is a boolean
  option.  By default this option is turned off.
  
         Here is an example of setting this option:
  
             int on = 1;
  
             if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY,
                            (char *)&on, sizeof(on)) == -1)
                 perror("setsockopt IPV6_V6ONLY");
             else
                 printf("IPV6_V6ONLY set0);
  
  Note - This option has no effect on the use of IPv4 Mapped addresses
  which enter a node as a valid IPv6 addresses for IPv6 communications as
  defined by Stateless IP/ICMP Translation Algorithm (SIIT) [8].

ん? よく見ると、サンプルソースの最後の printf() が間違ってる。これじゃあ、コンパイル通らんでしょう(笑)。

冒頭でも述べた通り、通常 AF_INET6 のソケットには、 IPv4 のパケットも通知されます。しかし、IPV6_V6ONLY オプションを有効にすれば、 IPv6 のパケットのみしか通知されなくなります。

FreeBSD の inetd は、このオプションの動作を利用して、 ``tcp46'' と ``tcp6'' を使い分けているわけです。

FreeBSD では 4.4-RELEASE から、この IPV6_V6ONLY オプションをサポートしています。 新しい標準化機能を貪欲に取り込んでいますね。


TetsuoSTREAMS > FreeBSD > inetd の IPv6 対応