CentOS 5.1(カーネル 2.6.18-53.el5)にて、IPv6 UDPマルチキャストの受信処理を記述する際、マルチキャストアドレスをbindすると、リンクローカルの場合にエラーとなりました。(Invalid Argument)
UDPマルチキャスト受信では、当初アドレスは0でポート番号のみ指定してbindした後に、setsockoptでIPV6_JOIN_GROUPを指定すればいいと思っていましたし、それで受信はできたのですが、Linuxではなぜか他のアドレス宛でポート番号が一致するとそれもソケットから受信できてしまうという現象が発生しました。
そこで、受信するマルチキャストアドレスをbindしてみたのですが、サイトローカル他のマルチキャストはbindに成功しました。一方、リンクローカルなマルチキャストアドレスはエラーとなるので、Linuxのカーネル内でリンクローカルなマルチキャストアドレスはじいている処理があるのではないかとソースを調べ、対処方法を見つけました。
対処方法
- struct sockaddr_in6のsin6_scope_id属性に0以外の使用するインタフェースのインデックス番号をセットしてからbindする。
- setsockoptでSO_BINDTODEVICEを指定してインタフェース名をソケットに設定してからbindする。
のいずれかが必要です。なお、2.の方法は、一般ユーザではエラー("Operation not permitted")となり、root特権が必要なようです。
linuxカーネル 2.6.25.4
まずはbindに対応する処理のあたりを付けてみると、以下が該当しそうな部分です。
net/ipv6/af_inet6.c
int inet6_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len) { : struct sock *sk = sock->sk; int addr_type = 0; : addr_type = ipv6_addr_type(&addr->sin6_addr); : if (addr_type != IPV6_ADDR_ANY) { : if (addr_type & IPV6_ADDR_LINKLOCAL) { if (addr_len >= sizeof(struct sockaddr_in6) && addr->sin6_scope_id) { sk->sk_bound_dev_if = addr->sin6_scope_id; } /* Binding to link-local address requires an interface */ if (!sk->sk_bound_dev_if) { err = -EINVAL; goto out; } :
bindしようとするアドレスが、リンクローカルアドレスのときは、インタフェース・インデックスが0以外の具体的なインタフェースを指す値でないとEINVALなエラーが発生するようになっています。
sockaddr_in6構造体のsin6_scope_idが0以外の値であるか、struct socket構造体のsk(struct sock構造体)のsk_bound_dev_ifが0以外の値でないとエラーとなります。
struct socketは、include/linux/net.hで定義された構造です。
struct socket { socket_state state; unsigned long flags; const struct proto_ops *ops; struct fasync_struct *fasync_list; struct file *file; struct sock *sk; wait_queue_head_t wait; short type; };
skは、"internal networking protocol agnostic socket representation"とコメント記述があります。
struct sockは、include/net/sock.hで定義された構造です。
struct sock { struct sock_common __sk_common : #define sk_bound_dev_if __sk_common.skc_bound_dev_if :