web-dev-qa-db-ja.com

SO_BINDTODEVICELinuxソケットオプションの問題

2枚のネットワークカードを搭載したPCを持っています。 1つ(_eth0_)はLAN /インターネット用で、もう1つは1つのマイクロコントローラーデバイスとのUDP通信用です。マイクロコントローラにはIP(192.168.7.2)とMACアドレスがあります。 2番目のPCネットワークアダプタ(_eth1_)には192.168.7.1があります。

マイクロコントローラーのIPスタックは非常に単純なので、mcがUDPパケットを送信する最も簡単な方法は、それらをブロードキャストすることです。

PC側では、ブロードキャストを受信したいのですが、_eth1_からのみです。そこで、UDPソケットを_eth1_デバイスにバインドしようとします。

問題(以下のソースコード):

  1. setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, device, sizeof(device))にはroot権限が必要ですが、なぜですか? (他のオプションの設定はユーザーとして機能します)

  2. getsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, (void *)buffer, &opt_length)は「プロトコルが利用できません」を示します。 setsockoptコマンドで設定したデバイスを読み戻したいのですが。

  3. 良い情報はどこにありますか? Linuxプログラミング、ネットワークブックをいくつかチェックしましたが、たとえば、インターネットでしか見つけられなかった_SO_BINDTODEVICE_オプションです。

私の長い(汚い)テストプログラムは問題を示しています。 _SO_RCVTIMEO_および_SO_BROADCAST_オプションの設定と取得は、期待どおりに機能します。

ユーザーが終了するときにコードを実行すると、次のようになります。

_could not set SO_BINDTODEVICE (Operation not permitted)"
_

Sudoで実行すると、次のようになります。

_SO_BINDTODEVICE set
./mc-test: could not get SO_BINDTODEVICE (Protocol not available)
_

それで、オプションを設定することはうまくいくようですが、それを読み返すことはできませんか?

_/* SO_BINDTODEVICE test */ 

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/time.h>
#include <errno.h>

#define MC_IP "192.168.7.2"
#define MC_PORT (54321)
#define MY_PORT (54321)
#define MY_DEVICE "eth1"

#define BUFFERSIZE (1000)

/* global variables */
int sock;
struct sockaddr_in MC_addr;
struct sockaddr_in my_addr;
char buffer[BUFFERSIZE];

int main(int argc, char *argv[]) 
{
  unsigned int echolen, clientlen;
  int rc, n;
  char opt_buffer[1000];
  struct protoent *udp_protoent;
  struct timeval receive_timeout;
  int optval;
  socklen_t opt_length;

  /* Create the UDP socket */
  if ((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) 
  {
    printf ("%s: failed to create UDP socket (%s) \n",
        argv[0], strerror(errno));
    exit (EXIT_FAILURE);
  }
  printf ("UDP socket created\n");

  /* set the recvfrom timeout value */
  receive_timeout.tv_sec = 5;
  receive_timeout.tv_usec = 0;
  rc=setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &receive_timeout,
                sizeof(receive_timeout));
  if (rc != 0) 
  {
     printf ("%s: could not set SO_RCVTIMEO (%s)\n",
        argv[0], strerror(errno));
     exit (EXIT_FAILURE);
  }
  printf ("set timeout to\ntime [s]: %d\ntime [ms]: %d\n", receive_timeout.tv_sec, receive_timeout.tv_usec);
  /* verify the recvfrom timeout value */
  rc=getsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &receive_timeout, &opt_length);
  if (rc != 0) 
  {
     printf ("%s: could not get socket options (%s)\n",
        argv[0], strerror(errno));
     exit (EXIT_FAILURE);
  }
  printf ("timeout value\ntime [s]: %d\ntime [ms]: %d\n", receive_timeout.tv_sec, receive_timeout.tv_usec);

  /* allow broadcast messages for the socket */
  int true = 1;
  rc=setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &true, sizeof(true));
  if (rc != 0) 
  {
     printf ("%s: could not set SO_BROADCAST (%s)\n",
        argv[0], strerror(errno));
     exit (EXIT_FAILURE);
  }
  printf ("set SO_BROADCAST\n");
  /* verify SO_BROADCAST setting */
  rc=getsockopt(sock, SOL_SOCKET, SO_BROADCAST, &optval, &opt_length);
  if (optval != 0) 
  {
    printf("SO_BROADCAST is enabled\n");
  }

  /* bind the socket to one network device */
  const char device[] = MY_DEVICE;
  rc=setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, device, sizeof(device));
  if (rc != 0) 
  {
     printf ("%s: could not set SO_BINDTODEVICE (%s)\n",
        argv[0], strerror(errno));
     exit (EXIT_FAILURE);
  }
  printf ("SO_BINDTODEVICE set\n");
  /* verify SO_BINDTODEVICE setting */
  rc = getsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, (void *)buffer, &opt_length);
  if (rc != 0) 
  {
     printf ("%s: could not get SO_BINDTODEVICE (%s)\n",
        argv[0], strerror(errno));
     exit (EXIT_FAILURE);
  }
  if (rc == 0) 
  {
    printf("SO_BINDTODEVICE is: %s\n", buffer);
  }


  /* Construct the server sockaddr_in structure */
  memset(&MC_addr, 0, sizeof(MC_addr));     /* Clear struct */
  MC_addr.sin_family = AF_INET;         /* Internet/IP */
  MC_addr.sin_addr.s_addr = inet_addr(MC_IP);   /* IP address */
  MC_addr.sin_port = htons(MC_PORT);        /* server port */

  /* bind my own Port */
  my_addr.sin_family = AF_INET;
  my_addr.sin_addr.s_addr = INADDR_ANY; /* INADDR_ANY all local addresses */
  my_addr.sin_port = htons(MY_PORT);
  rc = bind (sock, (struct sockaddr *) &my_addr, sizeof(my_addr));
  if (rc < 0) 
  {
     printf ("%s: could not bind port (%s)\n",
        argv[0], strerror(errno));
     exit (EXIT_FAILURE);
  }
  printf ("port bound\n");

  /* identify mc */
  buffer[0] = (char)1;
  buffer[1] = (char)0;
  send_data (buffer, 2);  
  printf ("sent command: %d\n", (char)buffer[0]);

  rc=receive_data(buffer);
  printf ("%d bytes received\n", rc);
  buffer[rc] = (char)0; /* string end symbol */
  printf ("%d - %s\n", (int)(char)buffer[0], &buffer[1]);

  close(sock);
  printf ("socket closed\n");

  exit(0);
}

/* send data to the MC *****************************************************/
/* buffer points to the bytes to send */
/* buf_length is the number of bytes to send */
/* returns allways 0 */
int send_data( char *buffer, int buf_length )
{
  int rc;

  rc = sendto (sock, buffer, buf_length, 0,
                 (struct sockaddr *) &MC_addr,
                 sizeof(MC_addr));
  if (rc < 0) 
  {
    printf ("could not send data\n");
    close (sock);
    exit (EXIT_FAILURE);
  }
  return(0);
}

/* receive data from the MC *****************************************************/
/* buffer points to the memory for the received data */
/* max BUFFERSIZE bytes can be received */
/* returns number of bytes received */
int receive_data(char *buffer)
{
  int rc, MC_addr_length;

  MC_addr_length = sizeof(MC_addr);
  rc = recvfrom (sock, buffer, BUFFERSIZE, 0,
                 (struct sockaddr *) &MC_addr,
                 &MC_addr_length);
  if (rc < 0) 
  {
    printf ("could not receive data\n");
    close (sock);
    exit (EXIT_FAILURE);
  }
  return(rc);
}
_
17
Michael

SO_BINDTODEVICEが実際にどのように使用されているかについて矛盾する答えを見た後、私はしばらくこれを調べてきました。 一部のソース 正しい使用法は、ioctlを介して取得されたデバイス名とインデックスを持つstruct ifreqポインターを渡すことであると主張しています。例えば:

struct ifreq ifr;
memset(&ifr, 0, sizeof(struct ifreq));
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "eth0");
ioctl(fd, SIOCGIFINDEX, &ifr);
setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE,  (void*)&ifr, sizeof(struct ifreq));

一方、 Beejのネットワークチュートリアル は、デバイス名をcharポインタとして渡すように言っています。例えば:

char *devname = "eth0";
setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, devname, strlen(devname));

私はこれらの方法の両方を試しましたが、どちらも必要なことを実行しますが、最初の方法で取得したデバイスインデックスは不要であることに注意してください。 net/core/sock.c のカーネルコードを見ると、sock_bindtodeviceはデバイス名の文字列をコピーし、dev_get_by_name_rcuを呼び出してデバイスを取得し、それにバインドします。

最初のアプローチが機能する理由は、デバイス名がifreq構造体の最初の要素であるためです。 http://linux.die.net/man/7/netdevice を参照してください。

17
austinmarton

OK、もう少し調べてみました。 SO_BINDTODEVICEは、1999年に「ほぼ廃止された」と見なされ、不特定の「セキュリティへの影響」のためにルートのみです(正確に何を見つけることができませんでした)。

ただし、INADDR_ANYにバインドし、IP_PKTINFO socketoptを設定することで、必要な動作を得ることができるはずです。これにより、着信パケットを説明するpktinfo構造を含む追加のメッセージがソケットに渡されます。この構造には、パケットが着信したインターフェイスのインデックスが含まれます。

struct in_pktinfo {
    unsigned int   ipi_ifindex;  /* Interface index */
    struct in_addr ipi_spec_dst; /* Local address */
    struct in_addr ipi_addr;     /* Header Destination address */
};

Ipi_ifindexは、SIOCGIFCONFなどのnetdeviceioctlによって返される構造体ifreqのifr_ifindexと一致します。したがって、これを使用して、関心のあるインターフェイス以外のインターフェイスで受信したパケットを無視できるはずです。

IP_PKTINFOのDocoはip(7)にあり、インターフェイスioctlsのDocoはnetdevice(7)にあります。

9
caf
setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, "eth0", 4);

上記のコード行は、eth0 interfaceからのみメッセージを受信するのに十分です。 Linuxでこれをテストしました。

注:実際のインターフェイスを制御するブリッジインターフェイスがある場合は機能しません。

よろしく、サントッシュ。

8
Santosh

Linux 3.8より前は、このソケットオプションを設定できましたが、getsockopt()では取得できませんでした。 Linux 3.8以降、読み取り可能です。 optlen引数には、デバイス名を受け取るために使用できるバッファーサイズが含まれている必要があり、IFNAMSZバイトにすることをお勧めします。実際のデバイス名の長さは、optlen引数で報告されます。

5
Dražen G.

私が遭遇した問題は、特定のインターフェースからのブロードキャストの受信がLinux、Windows、... http://www.developerweb.net/forum/showthread.phpによって異なる方法で処理されることであるようです。 ?t = 5722

マイクロコントローラーのTCP/IPスタックを変更することで、問題(ドキュメントが少なく、移植性が悪い)を解決することにしました。ブロードキャストアドレスに応答を送信しなくなりますが、代わりに、着信UDPパケットからIP/MACを宛先IP/MACとして取得します。次に、(PC側で)ソケットをeth1のIPにバインドするだけです。

乾杯、マイケル

2
Michael

特定のインターフェースへのマルチキャストの送信もこのように機能することを確認できます。以下のサンプルコードを参照してください。ただし、インターフェイスがSO_BINDTODEVICEによってセカンダリインターフェイスeth4に設定されている場合、listener.cプログラムを機能させることができません。

マルチキャストパケットの送信にまったく異なるマシンを使用しましたが、リスナーはインターフェイスeth4からではなく、インターフェイスeth3から動作します。ただし、tcpdumpは両方のインターフェースのパケットを表示します(Sudo tcpdump -i eth4 | grepUDP)。

これらは、AntonyCourtneyのサンプルコードに対する変更です。

sender.cおよびlistener.c:

/*
 * sender.c -- multicasts "hello, world!" to a multicast group once a second
 *
 * Antony Courtney, 25/11/94
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <net/if.h>  

#define HELLO_PORT 12345
#define HELLO_GROUP "225.0.0.37"

main(int argc, char *argv[])
{
     struct sockaddr_in addr;
     int fd, cnt;
     struct ip_mreq mreq;
     char *message="Hello, World!";
    char com[1000];

     /* create what looks like an ordinary UDP socket */
     if ((fd=socket(AF_INET,SOCK_DGRAM,0)) < 0) {
      perror("socket");
      exit(1);
     }

     /* set up destination address */
     memset(&addr,0,sizeof(addr));
     addr.sin_family=AF_INET;
     addr.sin_addr.s_addr=inet_addr(HELLO_GROUP);
     addr.sin_port=htons(HELLO_PORT);



     u_char ttl=7;
     setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl));
     struct ifreq ifr;
       memset(&ifr, 0, sizeof(struct ifreq));
       snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "eth4");
       ioctl(fd, SIOCGIFINDEX, &ifr);

 printf("[[%d]]\n", ifr.ifr_ifindex );
       setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE,  (void*)&ifr, sizeof(struct ifreq));


     inet_ntop(AF_INET, &(addr), com, INET_ADDRSTRLEN);
     printf("addr=%s\n", com );


     /* now just sendto() our destination! */
     while (1) {
      if (sendto(fd,message,strlen(message),0,(struct sockaddr *) &addr,
             sizeof(addr)) < 0) {
           perror("sendto");
           exit(1);
      }
      sleep(1);
     }
}


listener.c :

/*
 * listener.c -- joins a multicast group and echoes all data it receives from
 *      the group to its stdout...
 *
 * Antony Courtney, 25/11/94
 * Modified by: Frédéric Bastien (25/03/04)
 * to compile without warning and work correctly
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <net/if.h>  

#define HELLO_PORT 12345
#define HELLO_GROUP "225.0.0.37"
#define MSGBUFSIZE 256

main(int argc, char *argv[])
{
     struct sockaddr_in addr;
     int fd, nbytes,addrlen;
     struct ip_mreq mreq;
     char msgbuf[MSGBUFSIZE];

     u_int yes=1;            /*** MODIFICATION TO ORIGINAL */

     /* create what looks like an ordinary UDP socket */
     if ((fd=socket(AF_INET,SOCK_DGRAM,0)) < 0) {
      perror("socket");
      exit(1);
     }
     struct ifreq ifr;
     memset(&ifr, 0, sizeof(struct ifreq));
     snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "eth4");
     ioctl(fd, SIOCGIFINDEX, &ifr);

     printf("[[%d]]\n", ifr.ifr_ifindex );

     if(  setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE,  (void*)&ifr, sizeof(struct ifreq)) < 0 )
       {
     perror("SO_BINDTODEVICE");
     exit(1);
       }

/**** MODIFICATION TO ORIGINAL */
    /* allow multiple sockets to use the same PORT number */
    if (setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(yes)) < 0) {
       perror("Reusing ADDR failed");
       exit(1);
       }
/*** END OF MODIFICATION TO ORIGINAL */


     /* set up destination address */
     memset(&addr,0,sizeof(addr));
     addr.sin_family=AF_INET;
     addr.sin_addr.s_addr=htonl(INADDR_ANY); /* N.B.: differs from sender */
     addr.sin_port=htons(HELLO_PORT);


     /* bind to receive address */
     if (bind(fd,(struct sockaddr *) &addr,sizeof(addr)) < 0) {
      perror("bind");
      exit(1);
     }


      /*
      ifr.ifr_flags = IFF_UP | IFF_ALLMULTI | IFF_MULTICAST;
      ioctl(fd, SIOCSIFFLAGS, &ifr );
      */

      /* use setsockopt() to request that the kernel join a multicast group */
     mreq.imr_multiaddr.s_addr=inet_addr(HELLO_GROUP);
     mreq.imr_interface.s_addr=htonl(INADDR_ANY);
     if (setsockopt(fd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq)) < 0) {
      perror("setsockopt");
      exit(1);
     }

     /* now just enter a read-print loop */
     while (1) {
      addrlen=sizeof(addr);
      if ((nbytes=recvfrom(fd,msgbuf,MSGBUFSIZE,0,
                   (struct sockaddr *) &addr,&addrlen)) < 0) {
           perror("recvfrom");
           exit(1);
      }
      msgbuf[nbytes]='\0';
      puts(msgbuf);
     }
}
2
Tomi

Getifaddrs()を使用して、関心のあるインターフェイスのIPアドレスを検索し、bind()を使用してソケットをそのIPアドレスにバインドするだけです。ソケットでSO_BROADCASTを有効にすると、そのインターフェイスでのみブロードキャストが受信されます。

または、実際には、getifaddrs()の部分をスキップして、必要に応じて192.168.7.1に直接bind()することもできます。

2
caf

セカンダリインターフェイスでマルチキャストパケットを受信できない場合は、それらをブロックしているのはリバースパスフィルタリングである可能性があります。これにより、受信したパケットが着信しているインターフェイスで送信されない場合、それらのパケットが除外されます。

この機能を無効にするには、次を使用します。

Sudo -i
echo 2 > /proc/sys/net/ipv4/conf/eth1/rp_filter
echo 2 > /proc/sys/net/ipv4/conf/all/rp_filter
exit
1
qbert220

質問2の答えは、getsockoptがSO_BINDTODEVICEオプションではサポートされていないということのようです。 Linuxカーネルソース(2.6.27)では、オプションはlinux-2.6.27.25-0.1/net/core /sock.cのsock_setsockopt関数でのみ処理されます。

質問3については、多くの人がW.リチャードスティーブンスの「UNIXネットワークプログラミング」の本を勧めているようです。私はグーグルブックオンラインバージョンのソケットオプションページを調べました-SO_BINDTODEVICEオプションは表7.1と7.2にリストされていません:-(...おそらくこのオプションはLinuxのみであるためですか?

1
Michael