2009-07-30 40 views
13

私は2枚のネットワークカードを持つPCを持っています。 1つ(eth0)はLAN /インターネット用で、もう1つは1つのマイクロコントローラデバイスとのUDP通信用です。マイクロコントローラには、IP(192.168.7.2)とMACアドレスがあります。 2番目のPCネットワークアダプタ(eth1)は192.168.7.1です。SO_BINDTODEVICEの問題Linuxソケットオプション

マイクロコントローラは非常に単純な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_RCVTIMEOSO_BROADCASTオプションは正常に動作します。持つユーザー出口としてコード実行

ます。sudoで実行

could not set SO_BINDTODEVICE (Operation not permitted)" 

が与える:

だから、
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); 
} 
+1

本当に必要なことはありますか?ソケットを192.168.7.1アドレスにバインドできませんか?わたしにはできる。 – Juliano

+0

@Juliano:特定のインターフェイスへのbind()は、Windows上のブロードキャストパケットに対してのみ機能します。 – Compholio

+0

は192.168.7.255へのバインドを試みましたが、eth0とeth1には異なるネットワークマスクが設定されていますか? – dashesy

答えて

0

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

質問3の場合は、 W. Richard Stevensの "UNIX network programming"ブック。 私はGoogleの書籍のオンライン版のソケットオプションのページを見て - ?SO_BINDTODEVICEオプションは、このオプションは

1

ちょうどIPアドレスをルックアップのLinuxであるかもしれないので... :-(表7.1および7.2に記載されていませんgetfaddrs()で関心のあるインターフェイスのbind()を使ってソケットをそのIPアドレスにバインドします。ソケットでSO_BROADCASTを有効にすると、そのインターフェイスでのみブロードキャストを受信します。

または実際にはgetifaddrs()の部分をスキップして、好きなだけ192.168.7.1に直接bind()することができます。

+0

それは私がやった最初のことでした。そして今、私はそれを確認しました。 PCはMCのブロードキャスト応答を認識しません!私はwiresharkでUDPパケットを見ることができますが、受信ルーチンは何も受信せずにタイムアウトします。 –

+0

192.168.7.255にバインドするとどうなりますか? (それは使用されている放送ですか、それとも255.255.255.255ですか?) – caf

+0

これはうまくいきません。しかし、私は今使っている解決策を投稿します。 私のネットワークの問題を見ていただきありがとうございます。 –

1

私が遭遇した問題は、ブロードキャストを受信して​​いるようです特定のインターフェイスからのtsはLinux、Windows、によって異なって扱われます... http://www.developerweb.net/forum/showthread.php?t=5722

マイクロコントローラのTCP/IPスタックを変更することで問題を解決することを決めました。それはもはやブロードキャストアドレスへの応答を送信するのではなく、着信UDPパケットから宛先IP/MACとしてIP/MACを取ります。次に、私は(PC側で)ソケットをeth1のIPに単にバインドできます。

乾杯、 マイケル

6

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のようなnetdeviceののioctlによって返されたstructいるifreqからifr_ifindexと一致します。したがって、興味のあるもの以外のインタフェースで受信したパケットを無視することができます。

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

+0

'IP_PKTINFO'を使うことは、' recv()/ recvfrom() 'から' recvmsg() 'に変換することを意味します。しかし、特にIP_PKTINFOは、アプリケーションがいくつかの(しかし全てではない)インターフェースを聴きたい場合に、非常に便利です。 しかし、私は 'SO_BINDTODEVICE'が廃止/廃止されていることについては言及しておらず、将来Linuxを削除することによってLinuxがユーザー空間を壊すことはほとんどありません。ですから、一般的な使用事例では、毎週Linux上で 'SO_BINDTODEVICE'を実行します。 – troglobit

-1

setsocketoptには、名前ではなくデバイスインデックスが必要です。さらに、struct ifreqを使用してインデックスを渡す必要があります。

 struct ifreq ifr; 
     memset(&ifr, 0, sizeof(ifr)); 
     snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "eth3"); 

     ioctl(s, SIOCGIFINDEX, &ifr) 
     setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, (void*)&ifr, sizeof(ifr)); 
+3

人によって異なります7ソケット:http://linux.die.net/man/7/ソケット –

1

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

私は完全に別のマシンを使ってマルチキャストパケットを送信し、リスナはインターフェイスeth3ではなく、インターフェイスeth3から動作します。しかし、tcpdumpは両方のインタフェースのパケットを表示します(sudo tcpdump -i eth4 | grep UDP)。私はSO_BINDTODEVICEは実際にはどのように競合の回答を見た後、しばらくの間、このに探してきた

/* 
* 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); 
    } 
} 
14

sender.cとlistener.c:

これらは、アントニーコートニーのサンプルコードに変更されています中古。 Some sourcesは、正しい使用法がstruct ifreqポインタを渡すことを要求しています。このポインタは、デバイス名とインデックスがioctlで取得されます。たとえば:Beej's networking tutorialとして

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

char型のポインタとしてデバイス名を渡すように述べています。たとえば:

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

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

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

5
setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, "eth0", 4); 

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

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

お礼、 Santosh。

0

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

sudo -i 
echo 2 > /proc/sys/net/ipv4/conf/eth1/rp_filter 
echo 2 > /proc/sys/net/ipv4/conf/all/rp_filter 
exit 
-2

私は、/ etc/sudoersファイル(または/etc/sudoers.d内のファイル)に以下を追加することにより、同様の問題を解決しました:

は使用以下、この機能を無効にするには:

myuser myhost=(root) NOPASSWD: /usr/bin/fping 

fpingディレクトリを使用する代わりに、sudo fpingを使用してください。

関連する問題