2011/07/27(Wed)IPv6におけるインタフェース指定パケット送信
2011/07/27 20:41
RFCにもちらっと説明があるけど、あまり詳細が分からなかったのでメモ。
はじめに
1つのサーバ上で複数のネットワークとreachableなマルチホーム環境、またはリンクローカルな通信などの用途で、インタフェースを指定したパケットの送信をやりたいということがある。IPv6は、一つのインタフェースに複数のIPv6アドレスを付与できるため、単純に出力先インタフェースの指定*1だけでなく、送信元IPアドレスも指定する必要がある。
ここでは、RFC3542 Advanced Sockets Application Program Interface (API) for IPv6で定義されているオプションを使用する。
ソケットオプションで直接setsockopt()を用いて設定する方法と、インタフェース情報を補助的データとして添えてsendmsg()で送信する方法がある。
IPv6拡張ヘッダへのアクセス
RFC3542の4. Access to IPv6 and Extension Headersに説明されているが、IPv6ソケットでは、今回使用するインタフェース情報の指定以外にも、以下のような情報をカーネルに指示することが出来る。1. The send/receive interface and source/destination address,そして、それらの情報の渡し方は以下の2つの方法がある。
2. The hop limit,
3. Next hop address,
4. The traffic class,
5. Routing header,
6. Hop-by-Hop options header, and
7. Destination options header.
Two different mechanisms exist for sending this optional information:それぞれ日本語では、付随(sticky)オプション、補助的(ancillary)データと呼べばいいかな。
1. Using setsockopt to specify the option content for a socket.
These are known "sticky" options since they affect all transmitted
packets on the socket until either a new setsockopt is done or the
options are overridden using ancillary data.
2. Using ancillary data to specify the option content for a single
datagram. This only applies to datagram and raw sockets; not to
TCP sockets.
IPV6_PKTINFO
そして、今回インタフェース情報の指定のために使うIPV6_PKTINFOというオプションデータが、付随(sticky)オプションや補助的(ancillary)データとして指定されていた時の評価順序が6.7. Summary of Outgoing Interface Selectionに示されている。For a given outgoing packet on a given socket, the outgoing interfaceちょっと説明を加えると、
is determined in the following order:
1. if an interface is specified in an IPV6_PKTINFO ancillary data
item, the interface is used.
2. otherwise, if an interface is specified in an IPV6_PKTINFO sticky
option, the interface is used.
3. otherwise, if the destination address is a multicast address and
the IPV6_MULTICAST_IF socket option is specified for the socket,
the interface is used.
4. otherwise, if an IPV6_NEXTHOP ancillary data item is specified,
the interface to the next hop is used.
5. otherwise, if an IPV6_NEXTHOP sticky option is specified, the
interface to the next hop is used.
6. otherwise, the outgoing interface should be determined in an
implementation dependent manner.
- IPV6_PKTINFO補助的(ancillary)データ
CMSG_DATA(cmsg)にin6_pktinfo構造体を指定。 - IPV6_PKTINFO付随(sticky)オプション
setsockoptの第4引数にin6_pktinfo構造体を指定。 - IPV6_MULTICAST_IFオプション(マルチキャスト限定)
- IPV6_NEXTHOP補助的データ
- IPV6_NEXTHOP付随オプション
- 実装依存
IPV6_PKTINFO 補助的(ancillary)データ
例えばこんな感じ。/** * @brief メッセージを送信する * @param[in] dstaddr 送信先アドレス * @param[in] dstport 送信先ポート * @param[in] msg 送信するメッセージ * @param[in] msglen メッセージ長 * @param[in] ifindex 送信するインタフェースインデックス */ static int send_with_ancillary_pktinfo(const struct in6_addr *dstaddr, int dstport, const void *msg, int msglen, int ifindex){ int soc, err; char ifname[IFNAMSIZ]; struct msghdr msghdr; struct iovec iov; struct cmsghdr *cmsg; int cmsglen; struct in6_pktinfo *pinfo; struct sockaddr_in6 dst; struct in6_addr srcaddr; char dstaddrstr[INET6_ADDRSTRLEN], srcaddrstr[INET6_ADDRSTRLEN]; // ソケットを開く if((soc = socket(AF_INET6, SOCK_DGRAM, 0)) < 0){ perror("socket"); return -1; } // 送信元アドレスをifindexから決定する if(if_get_global_ipv6addr(if_indextoname(ifindex, ifname), &srcaddr) != 0){ perror("if_get_global_ipv6addr"); goto fail; } printf("Sending IPv6 packet by using %s.\n", __FUNCTION__); printf("Source : %s\n", inet_ntop(AF_INET6, &srcaddr, srcaddrstr, sizeof(srcaddrstr))); printf("Destination: %s\n", inet_ntop(AF_INET6, dstaddr, dstaddrstr, sizeof(dstaddrstr))); printf("Interface : %s (%d)\n", if_indextoname(ifindex, ifname), ifindex); // 制御メッセージ cmsglen = CMSG_SPACE(sizeof(struct in6_pktinfo)); cmsg = (struct cmsghdr *)malloc(cmsglen); if(cmsg == NULL){ perror("malloc"); goto fail; } // 宛先 memset(&dst, 0, sizeof(struct sockaddr_in6)); dst.sin6_family = AF_INET6; memcpy(&dst.sin6_addr, dstaddr, sizeof(struct in6_addr)); dst.sin6_port = htons(dstport); dst.sin6_scope_id = ifindex; // アドレスがリンクローカルスコープの場合のみ必要? // メッセージヘッダ memset(&msghdr, 0, sizeof(struct msghdr)); msghdr.msg_control = cmsg; msghdr.msg_controllen = cmsglen; msghdr.msg_iov = &iov; iov.iov_base = (void *)msg; iov.iov_len = msglen; msghdr.msg_iovlen = 1; msghdr.msg_name = &dst; msghdr.msg_namelen = sizeof(dst); // ヘッダ memset(cmsg, 0, cmsglen); cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); cmsg->cmsg_level = IPPROTO_IPV6; cmsg->cmsg_type = IPV6_PKTINFO; pinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg); // in6_pktinfo構造体へのデータセット memcpy(&pinfo->ipi6_addr, &srcaddr, sizeof(struct in6_addr)); pinfo->ipi6_ifindex = ifindex; if((err = sendmsg(soc, &msghdr, 0)) < 0){ perror("sendmsg"); goto out; } goto out; fail: err = -1; out: free(cmsg); close(soc); return err; }
IPV6_PKTINFO 付随(sticky)オプション
一方、setsockoptを使った場合はこんな感じ。/** * @brief メッセージを送信する * @param[in] dstaddr 送信先アドレス * @param[in] dstport 送信先ポート * @param[in] msg 送信するメッセージ * @param[in] msglen メッセージ長 * @param[in] ifindex 送信するインタフェースインデックス */ static int send_with_sticky_pktinfo(const struct in6_addr *dstaddr, int dstport, const void *msg, int msglen, int ifindex){ int soc, err; char ifname[IFNAMSIZ]; struct in6_pktinfo pinfo; struct sockaddr_in6 dst; struct in6_addr srcaddr; char dstaddrstr[INET6_ADDRSTRLEN], srcaddrstr[INET6_ADDRSTRLEN]; // ソケットを開く if((soc = socket(AF_INET6, SOCK_DGRAM, 0)) < 0){ perror("socket"); return -1; } // 送信元アドレスをifindexから決定する if(if_get_global_ipv6addr(if_indextoname(ifindex, ifname), &srcaddr) != 0){ perror("if_get_global_ipv6addr"); goto fail; } printf("Sending IPv6 packet by using %s.\n", __FUNCTION__); printf("Source : %s\n", inet_ntop(AF_INET6, &srcaddr, srcaddrstr, sizeof(srcaddrstr))); printf("Destination: %s\n", inet_ntop(AF_INET6, dstaddr, dstaddrstr, sizeof(dstaddrstr))); printf("Interface : %s (%d)\n", if_indextoname(ifindex, ifname), ifindex); // 宛先 memset(&dst, 0, sizeof(struct sockaddr_in6)); dst.sin6_family = AF_INET6; memcpy(&dst.sin6_addr, dstaddr, sizeof(struct in6_addr)); dst.sin6_port = htons(dstport); dst.sin6_scope_id = ifindex; // アドレスがリンクローカルスコープの場合のみ必要? // in6_pktinfo構造体へのデータセット memcpy(&pinfo.ipi6_addr, &srcaddr, sizeof(struct in6_addr)); pinfo.ipi6_ifindex = ifindex; // IPV6_PKTINFOを設定 if(setsockopt(soc, IPPROTO_IPV6, IPV6_PKTINFO, &pinfo, sizeof(struct in6_pktinfo)) == -1){ perror("setsockopt"); goto fail; } if((err = sendto(soc, msg, msglen, 0, (struct sockaddr *)&dst, sizeof(dst))) < 0){ perror("sendto"); goto out; } goto out; fail: err = -1; out: close(soc); return err; }
IPV6_2292PKTINFO
どうやら、RFC3542策定前のIPV6_PKTINFOとしてRFC2292 Advanced Sockets API for IPv6 (Obsolete)で策定されていたものも残っているらしい。これは、補助的(ancillary)データと付随(sticky)オプションを組合わせたような方式で、PKTINFOはCMSG_DATAに格納し、setsockoptで1を設定するだけ。
多分互換性のために残っているだけなので、あまり使う機会はなさそう。
/** * @brief メッセージを送信する * @param[in] dstaddr 送信先アドレス * @param[in] dstport 送信先ポート * @param[in] msg 送信するメッセージ * @param[in] msglen メッセージ長 * @param[in] ifindex 送信するインタフェースインデックス */ static int send_with_2292pktinfo(const struct in6_addr *dstaddr, int dstport, const void *msg, int msglen, int ifindex){ int soc, err; char ifname[IFNAMSIZ]; struct msghdr msghdr; struct iovec iov; struct cmsghdr *cmsg; int cmsglen; struct in6_pktinfo *pinfo; const int on = 1; struct sockaddr_in6 dst; struct in6_addr srcaddr; char dstaddrstr[INET6_ADDRSTRLEN], srcaddrstr[INET6_ADDRSTRLEN]; // ソケットを開く if((soc = socket(AF_INET6, SOCK_DGRAM, 0)) < 0){ perror("socket"); return -1; } // 送信元アドレスをifindexから決定する if(if_get_global_ipv6addr(if_indextoname(ifindex, ifname), &srcaddr) != 0){ perror("if_get_global_ipv6addr"); goto fail; } printf("Sending IPv6 packet by using %s.\n", __FUNCTION__); printf("Source : %s\n", inet_ntop(AF_INET6, &srcaddr, srcaddrstr, sizeof(srcaddrstr))); printf("Destination: %s\n", inet_ntop(AF_INET6, dstaddr, dstaddrstr, sizeof(dstaddrstr))); printf("Interface : %s (%d)\n", if_indextoname(ifindex, ifname), ifindex); // 制御メッセージ cmsglen = CMSG_SPACE(sizeof(struct in6_pktinfo)); cmsg = (struct cmsghdr *)malloc(cmsglen); if(cmsg == NULL){ perror("malloc"); goto fail; } // 宛先 memset(&dst, 0, sizeof(struct sockaddr_in6)); dst.sin6_family = AF_INET6; memcpy(&dst.sin6_addr, dstaddr, sizeof(struct in6_addr)); dst.sin6_port = htons(dstport); dst.sin6_scope_id = ifindex; // アドレスがリンクローカルスコープの場合のみ必要? // メッセージヘッダ memset(&msghdr, 0, sizeof(struct msghdr)); msghdr.msg_control = cmsg; msghdr.msg_controllen = cmsglen; msghdr.msg_iov = &iov; iov.iov_base = (void *)msg; iov.iov_len = msglen; msghdr.msg_iovlen = 1; msghdr.msg_name = &dst; msghdr.msg_namelen = sizeof(dst); // ヘッダ memset(cmsg, 0, cmsglen); cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); cmsg->cmsg_level = IPPROTO_IPV6; cmsg->cmsg_type = IPV6_2292PKTINFO; pinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg); // in6_pktinfo構造体へのデータセット memcpy(&pinfo->ipi6_addr, &srcaddr, sizeof(struct in6_addr)); pinfo->ipi6_ifindex = ifindex; // IPV6_2292PKTINFOを設定 if(setsockopt(soc, IPPROTO_IPV6, IPV6_2292PKTINFO, &on, sizeof(int)) == -1){ perror("setsockopt"); goto fail; } if((err = sendmsg(soc, &msghdr, 0)) < 0){ perror("sendmsg"); goto out; } goto out; fail: err = -1; out: free(cmsg); close(soc); return err; }
サンプル
というわけで、この3種類の方法でそれぞれパケットを出力できるサンプルpktinfo.cを作ってみました。main関数の宛先アドレスとインタフェース名を適当に変えてやると、そのホストのUDP:1234に適当なパケットを投げてくれるはず。
$ gcc -Wall -o pktinfo -D _GNU_SOURCE pktinfo.c $ ./pktinfo Sending IPv6 packet by using send_with_ancillary_pktinfo. Source : 2001:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx Destination: 2001:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx Interface : eth0 (2) ---------- Sending IPv6 packet by using send_with_sticky_pktinfo. Source : 2001:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx Destination: 2001:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx Interface : eth0 (2) ---------- Sending IPv6 packet by using send_with_2292pktinfo. Source : 2001:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx Destination: 2001:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx Interface : eth0 (2) ----------