2009-07-30 38 views
13

Tôi có PC với hai thẻ mạng. Một (eth0) là dành cho mạng LAN/Internet và một cho giao tiếp UDP với một thiết bị vi điều khiển. Bộ vi điều khiển có địa chỉ IP (192.168.7.2) và địa chỉ MAC. Bộ điều hợp mạng máy tính thứ hai (eth1) có 192.168.7.1.Các sự cố với tùy chọn ổ cắm SO_BINDTODEVICE Linux

Bộ vi điều khiển có ngăn xếp IP rất đơn giản, vì vậy cách dễ nhất để mc gửi gói UDP là phát chúng.

Ở phía PC, tôi muốn nhận các chương trình phát sóng - nhưng chỉ từ eth1. Vì vậy, tôi cố gắng kết nối ổ cắm UDP với thiết bị eth1.

Những vấn đề (mã nguồn dưới đây):

  1. setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, device, sizeof(device)) đòi hỏi quyền root, tại sao? (thiết lập các tùy chọn khác hoạt động với tư cách người dùng)

  2. getsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, (void *)buffer, &opt_length) cung cấp "Giao thức không khả dụng". Tôi muốn đọc lại thiết bị tôi đã đặt qua lệnh setsockopt.

  3. Tôi có thể tìm thông tin tốt ở đâu? Tôi đã kiểm tra một số sách mạng, lập trình Linux, nhưng ví dụ như tùy chọn SO_BINDTODEVICE tôi chỉ tìm thấy trên internet.

Chương trình thử nghiệm dài (bẩn) của tôi hiển thị các sự cố. Đặt và nhận lại các tùy chọn SO_RCVTIMEOSO_BROADCAST hoạt động như mong đợi.

Chạy mã như dùng thoát khỏi với:

could not set SO_BINDTODEVICE (Operation not permitted)" 

Chạy với sudo cho:

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

Vì vậy, thiết lập các tùy chọn dường như làm việc nhưng đọc nó trở lại là không thể?

/* 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

bạn có chắc chắn cần tất cả những điều đó không? Bạn không thể chỉ cần bind() socket vào địa chỉ 192.168.7.1? Nó làm việc cho tôi. – Juliano

+0

@Juliano: bind() ing vào một giao diện cụ thể chỉ hoạt động trên các gói phát sóng trên Windows. – Compholio

+0

đã thử ràng buộc với 192.168.7.255 và đảm bảo eth0 và eth1 có mặt nạ mạng khác nhau? – dashesy

Trả lời

0

Câu trả lời cho câu hỏi 2 có vẻ là getockopt không được hỗ trợ cho tùy chọn SO_BINDTODEVICE. Trong nguồn kernel Linux (2.6.27), tùy chọn chỉ được xử lý trong hàm sock_setsockopt của linux-2.6.27.25-0.1/net/core/sock.c

Đối với câu hỏi 3 có vẻ như, rất nhiều người khuyên bạn nên "Lập trình mạng UNIX" cuốn sách của W. Richard Stevens. Tôi đã xem qua các trang tùy chọn ổ cắm của phiên bản sách trực tuyến trên google - tùy chọn SO_BINDTODEVICE không được liệt kê trong bảng 7.1 và 7.2 :-( ... có thể vì tùy chọn này chỉ là Linux?

1

Chỉ cần tra cứu địa chỉ IP của giao diện mà bạn quan tâm với getifaddrs() và kết nối ổ cắm của bạn với địa chỉ IP đó bằng bind() .Nếu bạn bật SO_BROADCAST trên ổ cắm, bạn sẽ chỉ nhận được các chương trình phát sóng được nhận trên giao diện đó. thực sự bạn có thể bỏ qua phần getifaddrs() và chỉ liên kết trực tiếp() với 192.168.7.1 nếu bạn thích.

+0

Đó là điều đầu tiên tôi làm. Và tôi đã xác minh nó ngay bây giờ. Máy tính KHÔNG nhận ra câu trả lời phát sóng của MC! Tôi có thể thấy gói UDP với Wireshark nhưng thời gian nhận thường xuyên mà không nhận được bất cứ thứ gì. –

+0

Điều gì sẽ xảy ra nếu bạn liên kết với 192.168.7.255? (Có phải đó là chương trình phát sóng đang được sử dụng hoặc là 255.255.255.255 không?) – caf

+0

Điều đó cũng không hoạt động. Nhưng tôi sẽ đăng giải pháp mà tôi đang sử dụng ngay bây giờ. Cảm ơn bạn đã xem xét sự cố mạng của tôi. –

1

Vấn đề mà tôi gặp phải có vẻ là nhận được broadcas ts từ một giao diện cụ thể được xử lý khác nhau bởi Linux, Windows, ... http://www.developerweb.net/forum/showthread.php?t=5722

Bây giờ tôi quyết định giải quyết vấn đề (tài liệu nhỏ và tính di động kém) bằng cách thay đổi ngăn xếp TCP/IP của vi điều khiển. Nó sẽ không còn gửi câu trả lời cho địa chỉ quảng bá mà thay vào đó lấy IP/MAC từ gói UDP đến làm IP/MAC đích. Sau đó, tôi có thể (ở phía máy tính) chỉ cần ràng buộc các ổ cắm vào IP của eth1.

Chúc mừng, Michael

6

OK, tôi đã xem xét thêm một chút. SO_BINDTODEVICE được coi là "gần lỗi thời" vào năm 1999 và chỉ là root do một số "tác động bảo mật không xác định" (tôi không thể tìm ra chính xác điều gì).

Tuy nhiên, bạn sẽ có thể nhận được hành vi bạn muốn bằng cách liên kết với INADDR_ANY và đặt IP_PKTINFO socketopt. Điều này sẽ truyền một thông báo bổ sung trên socket chứa cấu trúc pktinfo mô tả gói tin gửi đến. Cấu trúc này bao gồm các chỉ số của giao diện mà các gói tin đến ở trên:

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

Các ipi_ifindex phù hợp với ifr_ifindex từ ifreq struct được trả về bởi các IOCTLs netdevice như SIOCGIFCONF. Vì vậy, bạn sẽ có thể sử dụng để bỏ qua các gói nhận được trên các giao diện khác với giao diện bạn quan tâm.

Doco for IP_PKTINFO nằm trong ip (7) và cho giao diện ioctls trong netdevice (7).

+0

Sử dụng 'IP_PKTINFO' có nghĩa là chuyển đổi từ' recv()/recvfrom() 'thành' recvmsg() ', không thân thiện với người dùng. Nhưng chắc chắn, 'IP_PKTINFO' có thể rất tiện dụng, đặc biệt nếu bạn muốn ứng dụng của bạn lắng nghe một vài giao diện (nhưng không phải tất cả). Tuy nhiên, tôi không thấy tham chiếu đến 'SO_BINDTODEVICE' bị lỗi thời/không được chấp nhận và hầu như không có khả năng Linux sẽ phá vỡ không gian người dùng bằng cách xóa nó trong tương lai. Vì vậy, đối với trường hợp sử dụng phổ biến, tôi sẽ đi với 'SO_BINDTODEVICE' trên Linux mỗi ngày trong tuần. – troglobit

-1

setsocketopt cần chỉ mục thiết bị chứ không phải tên. Ngoài ra, bạn nên sử dụng struct ifreq để chuyển chỉ mục:

 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

Không theo man 7 socket: http://linux.die.net/man/7/socket –

1

Tôi có thể xác nhận rằng gửi multicast đến giao diện cụ thể cũng hoạt động như thế này. Xem các mã mẫu bên dưới. Tuy nhiên tôi không thể có được chương trình listener.c làm việc nếu giao diện được thiết lập bởi SO_BINDTODEVICE đến eth4 giao diện phụ của tôi.

Tôi đã sử dụng máy hoàn toàn khác để gửi các gói đa hướng và trình lắng nghe hoạt động từ giao diện eth3, không phải từ giao diện eth4. Tuy nhiên, tcpdump hiển thị các gói trong cả hai giao diện (sudo tcpdump -i eth4 | grep UDP).

Đây là những sửa đổi mẫu mã Antony Courtney:

sender.c và 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); 
    } 
} 
14

Tôi đã được nhìn vào điều này trong một thời gian sau khi nhìn thấy câu trả lời mâu thuẫn với nhau như thế nào thực sự là SO_BINDTODEVICE đã sử dụng. Some sources tuyên bố rằng việc sử dụng chính xác là để vượt qua trong một con trỏ struct ifreq, trong đó có tên thiết bị và chỉ số thu được thông qua một ioctl. Ví dụ:

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

Gọi tên Beej's networking tutorial để chuyển tên thiết bị làm con trỏ char. Ví dụ:

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

Tôi đã thử cả hai phương pháp này và cả hai đều làm những gì được yêu cầu, nhưng tôi muốn lưu ý rằng chỉ mục thiết bị thu được trong phương pháp đầu tiên là không cần thiết. Nếu bạn nhìn vào mã hạt nhân trong net/core/sock.c, sock_bindtodevice chỉ cần sao chép chuỗi tên thiết bị, hãy gọi dev_get_by_name_rcu để nhận thiết bị và liên kết với nó.

Lý do cách tiếp cận đầu tiên hoạt động là tên thiết bị là phần tử đầu tiên trong cấu trúc ifreq, xem http://linux.die.net/man/7/netdevice.

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

Dòng mã trên chỉ đủ để nhận thư từ eth0 interface. Tôi đã thử nghiệm điều này trên Linux.

LƯU Ý: Nó sẽ không hoạt động nếu có giao diện cầu kiểm soát giao diện thực tế.

Trân trọng, Santosh.

0

Nếu bạn không thể nhận gói multicast trên giao diện phụ, nó cũng có thể là lọc đường dẫn ngược lại đang chặn chúng. Điều này lọc ra các gói tin nhận được nếu những gói tin đó sẽ không đi ra trên giao diện mà họ đang đến trong ngày.

Để tắt tính năng này, sử dụng như sau:

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

Tôi đã giải quyết một vấn đề tương tự bằng cách thêm dòng sau vào/etc/sudoers (hoặc trong một tập tin trong /etc/sudoers.d):

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

Sau đó thay vì sử dụng thư mục fping, hãy sử dụng sudo fping.

Các vấn đề liên quan