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) ----------