web-dev-qa-db-ja.com

UDPソケットのソースIPの設定

サーバーが持っているすべてのIPでパケットをリッスンするためにINADDR_ANYにバインドされているUDPソケットがあります。同じソケットを介して返信を送信しています。

現在、サーバーはパケットが送信されるときにソースIPとして使用されるIPを自動的に選択しますが、発信ソースIPを自分で設定できるようにしたいと考えています。

IPごとに個別のソケットを作成する必要なしにそれを行う方法はありますか?

27
Gene Vincent

Nikolai、アドレスごとに個別のソケットとbind(2)を使用するか、ルーティングテーブルをいじることは、多くの場合、実行可能なオプションではありません。動的アドレス。単一のIP_ADDRANY- bound UDPサーバーは、パケットが受信された同じ動的に割り当てられたIPアドレスで応答するように見える必要があります。

幸いにも、別の方法があります。システムのサポートに応じて、IP_PKTINFOソケットオプションを使用して、メッセージに関する補助データを設定または受信できます。 comp.os.linux.development.system には完全なコードサンプルがありましたが、補助データ(cmsg(3)経由)はオンラインの多くの場所でカバーされていますIP_PKTINFOに固有です。

リンク内のコードは、IP_PKTINFO(またはプラットフォームによってはIP_RECVDSTADDR)を使用して、補助[cmsg(3)]データからUDPメッセージの宛先アドレスを取得します。ここで言い換えると:

struct msghdr msg;
struct cmsghdr *cmsg;
struct in_addr addr;
// after recvmsg(sd, &msg, flags);
for(cmsg = CMSG_FIRSTHDR(&msg);
    cmsg != NULL;
    cmsg = CMSG_NXTHDR(&msg, cmsg)) {
  if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) {
    addr = ((struct in_pktinfo*)CMSG_DATA(cmsg))->ipi_addr;
    printf("message received on address %s\n", inet_ntoa(addr));
  }
}

ジーン、あなたの質問は発信パケットにソースアドレスを設定する方法を尋ねました。 IP_PKTINFOを使用すると、sendmsg(2)に渡される補助データでipi_spec_dststruct in_pktinfoフィールドを設定できます。 struct msghdrで補助データを作成および操作する方法のガイドラインについては、上記の投稿、cmsg(3)、およびsendmsg(2)を参照してください。例(ここでは保証なし)は次のとおりです。

struct msghdr msg;
struct cmsghdr *cmsg;
struct in_pktinfo *pktinfo;
// after initializing msghdr & control data to CMSG_SPACE(sizeof(struct in_pktinfo))
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = IPPROTO_IP;
cmsg->cmsg_type = IP_PKTINFO;
cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
pktinfo = (struct in_pktinfo*) CMSG_DATA(cmsg);
pktinfo->ipi_ifindex = src_interface_index;
pktinfo->ipi_spec_dst = src_addr;
// bytes_sent = sendmsg(sd, &msg, flags);

これはIPv6では異なります。recvmsgとsendmsgの両方のケースでstruct in6_pktinfo::ipi6_addrを使用してください。

また、Windowsはin_pktinfo構造体のipi_spec_dstに相当するものをサポートしていないため、このメソッドを使用して送信winsock2パケットに送信元アドレスを設定することはできません。

(参照されるマニュアルページ-1つのハイパーリンク制限を回避する)

http:// linux.die.net/man/2/sendmsg
http:// linux.die.net/man/3/cmsg
26
Jeremy Fishman

IPv6でこれを行う方法についてJeremyの説明を広げたいと思いました。 Jeremyは多くの詳細を省いており、一部のドキュメント(Linuxのipv6のmanページなど)は明らかに間違っています。最初に一部のディストリビューションでは_GNU_SOURCEを定義する必要があります。そうでない場合、IPv6の一部が定義されていません。

#define _GNU_SOURCE
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>

次に、特定のUDPポートですべてのIPパケット(つまり、IPv4とIPv6の両方)を待機するかなり標準的な方法でソケットを設定します。

const int on=1, off=0;
int result;
struct sockaddr_in6 sin6;
int soc;

soc = socket(AF_INET6, SOCK_DGRAM, 0);
setsockopt(soc, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
setsockopt(soc, IPPROTO_IP, IP_PKTINFO, &on, sizeof(on));
setsockopt(soc, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on));
setsockopt(soc, IPPROTO_IPV6, IPV6_V6ONLY, &off, sizeof(off));
memset(&sin6, '\0', sizeof(sin6));
sin6.sin6_family = htons(AF_INET6);
sin6.sin6_port = htons(MY_UDP_PORT);
result = bind(soc, (struct sockaddr*)&sin6, sizeof(sin6));

上記のコードは、IPv6ソケットのIPオプションとIPv6オプションの両方を設定していることに注意してください。パケットがIPv4アドレスに到着すると、IPv6ソケットであってもIP_PKTINFO(つまりIPv4)cmsgを取得します。有効にしないと、送信されません。また、IPV6_RECPKTINFOオプションが設定されていることにも注意してください(これはman 7 ipv6では言及されていません)。IPV6_PKTINFO(man 7 ipv6で誤って記述されています)ではありません。次に、udpパケットを受信します。

int bytes_received;
struct sockaddr_in6 from;
struct iovec iovec[1];
struct msghdr msg;
char msg_control[1024];
char udp_packet[1500];

iovec[0].iov_base = udp_packet;
iovec[0].iov_len = sizeof(udp_packet);
msg.msg_name = &from;
msg.msg_namelen = sizeof(from);
msg.msg_iov = iovec;
msg.msg_iovlen = sizeof(iovec) / sizeof(*iovec);
msg.msg_control = msg_control;
msg.msg_controllen = sizeof(msg_control);
msg.msg_flags = 0;
bytes_received = recvmsg(soc, &msg, 0);

次のステップは、インターフェースを抽出し、UDPパケットがcmsgから受信されたアドレスを指定することです。

struct in_pktinfo in_pktinfo;
struct in6_pktinfo in6_pktinfo;
int have_in_pktinfo = 0;
int have_in6_pktinfo = 0;
struct cmsghdr* cmsg;

for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != 0; cmsg = CMSG_NXTHDR(&msg, cmsg))
{
  if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO)
  {
    in_pktinfo = *(struct in_pktinfo*)CMSG_DATA(cmsg);
    have_in_pktinfo = 1;
  }
  if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO)
  {
    in6_pktinfo = *(struct in6_pktinfo*)CMSG_DATA(cmsg);
    have_in6_pktinfo = 1;
  }
}

最後に、同じ宛先を使用して応答を送り返します。

int cmsg_space;

iovec[0].iov_base = udp_response;
iovec[0].iov_len = udp_response_length;
msg.msg_name = &from;
msg.msg_namelen = sizeof(from);
msg.msg_iov = iovec;
msg.msg_iovlen = sizeof(iovec) / sizeof(*iovec);
msg.msg_control = msg_control;
msg.msg_controllen = sizeof(msg_control);
msg.msg_flags = 0;
cmsg_space = 0;
cmsg = CMSG_FIRSTHDR(&msg);
if (have_in6_pktinfo)
{
  cmsg->cmsg_level = IPPROTO_IPV6;
  cmsg->cmsg_type = IPV6_PKTINFO;
  cmsg->cmsg_len = CMSG_LEN(sizeof(in6_pktinfo));
  *(struct in6_pktinfo*)CMSG_DATA(cmsg) = in6_pktinfo;
  cmsg_space += CMSG_SPACE(sizeof(in6_pktinfo));
}
if (have_in_pktinfo)
{
  cmsg->cmsg_level = IPPROTO_IP;
  cmsg->cmsg_type = IP_PKTINFO;
  cmsg->cmsg_len = CMSG_LEN(sizeof(in_pktinfo));
  *(struct in_pktinfo*)CMSG_DATA(cmsg) = in_pktinfo;
  cmsg_space += CMSG_SPACE(sizeof(in_pktinfo));
}
msg.msg_controllen = cmsg_space;
ret = sendmsg(soc, &msg, 0);

パケットがIPv4経由で着信した場合、AF_INET6ソケットであっても、IPv4オプションをcmsgに追加する必要があることに再度注意してください。少なくとも、それはLinuxに対してしなければならないことです。

これは驚くべき作業量ですが、考えられるすべてのLinux環境で機能する堅牢なUDPサーバーを作成するために最低限必要な作業はAFAICTです。マルチホームを透過的に処理するため、TCPにはほとんど必要ありません。

18
Russell Stuart

bind(2) を各インターフェイスアドレスに割り当てて複数のソケットを管理するか、カーネルにINADDR_ANYを使用して暗黙的なソースIP割り当てを実行させます。他に方法はありません。

私の質問は-なぜこれが必要なのでしょうか?通常のIPルーティングが機能していませんか?

3

最近同じ問題に遭遇しました。

この問題を解決するために私がすることは

  1. 受信したパケットからインターフェース名を取得する
  2. ソケットを特定のインターフェースにバインド
  3. アンバインドソケット

例:

  struct ifreq ifr;
  ...
  recvmsg(fd, &msg...)
  ...      
  if (msg.msg_controllen >= sizeof(struct cmsghdr))
    for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
      if (cmptr->cmsg_level == SOL_IP && cmptr->cmsg_type == IP_PKTINFO)
      {
        iface_index = ((struct in_pktinfo *)CMSG_DATA(cmptr))->ipi_ifindex;
      }
  if_indextoname(iface_index , ifr.ifr_name);
  mret=setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr));

  sendmsg(...);

  memset(&ifr, 0, sizeof(ifr));
  snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "");
  mret=setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr));
0
Chih-Ying Lin