2010-06-17 4 views
22

내 서버에있는 모든 IP에서 패킷을 수신 대기하도록 INADDR_ANY에 바인딩 된 UDP 소켓이 있습니다. 나는 같은 소켓을 통해 응답을 보내고있다.UDP 소켓의 소스 IP 설정

지금 서버는 패킷을 보낼 때 원본 IP로 사용되는 IP를 자동으로 선택하지만 나가는 원본 IP를 직접 설정할 수 있기를 원합니다.

각 IP에 대해 별도의 소켓을 만들지 않고도 그렇게 할 수 있습니까?

답변

22

니콜라이 (Nikolai)는 각 주소에 대해 별도의 소켓 및 bind (2)를 사용하거나 라우팅 테이블을 망가 뜨리는 것이 가능하지 않은 경우가 많습니다. 동적 주소. 단일 IP_ADDRANY - 바운드 UDP 서버는 패킷이 수신되는 동일한 동적으로 할당 된 IP 주소로 응답하는 것처럼 보일 수 있어야합니다.

운 좋게도 다른 방법이 있습니다. 시스템의 지원에 따라 IP_PKTINFO 소켓 옵션을 사용하여 메시지에 대한 보조 데이터를 설정하거나 수신 할 수 있습니다. 보조 데이터 (cmsg(3) 경유)는 comp.os.linux.development.systemIP_PKTINFO에 관련된 전체 코드 샘플을 가지고 있지만 온라인으로 많은 곳에서 다루어집니다.

cmsg(3) 데이터에서 UDP 메시지의 대상 주소를 얻기 위해 링크의 코드는 IP_PKTINFO (또는 플랫폼에 따라 IP_RECVDSTADDR)을 사용합니다. 여기에 바꿔 치기 :

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)으로 전달되는 보조 데이터에 struct in_pktinfoipi_spec_dst 필드를 설정할 수 있습니다. struct msghdr에서 보조 데이터를 만들고 조작하는 방법에 대한 지침은 위에 언급 된 게시물 cmsg(3)sendmsg(2)을 참조하십시오. 예 (여기 보장 할) 수 있습니다 :이 IPv6를에서 다른

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

참고하십시오 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 
3

각 인터페이스 주소에 bind(2)을 입력하고 여러 소켓을 관리하거나 커널에 INADDR_ANY이라는 암시 적 소스 IP 할당을 수행하게하십시오. 다른 방법은 없습니다.

제 질문은 - 왜 이것을 필요로합니까? 정상적인 IP 라우팅이 작동하지 않습니까?

+0

감사합니다. IP 라우팅이 작동합니다. 벌금과 패킷이 목적지로 도착하지만 유감스럽게도 클라이언트는 모두 특정 서버 IP에 연결하고 프로토콜은이 특정 IP에서 응답을 얻도록 요구합니다. 현재 모든 고객은 동일한 IP에서 응답을 얻습니다. –

+0

내 의심이 라우팅 테이블이 될 것입니다 - 단일 기본 경로/게이트웨이가 있습니까? 클라이언트 주소에 특정한 경로를 추가하는 것이 도움이 될 수 있습니다. –

+0

예, 호스트 경로를 추가하면 도움이되지만 내 프로그램에서 호스트 경로를 사용하는 것이 좋습니다. –

17

난 제레미의 IPv6에 대해이 작업을 수행하는 방법에 대한 확장 거라고 생각했다. Jeremy는 많은 세부 사항을 생략하고 일부 문서 (Linux의 ipv6 용 man 페이지와 같은)는 단순한 잘못입니다.우선 몇 가지 배포판에 당신이 그렇지 않으면 IPv6의 물건 중 일부는 정의되지 않은, _GNU_SOURCE를 정의해야합니다 : 다음, 즉, 모든 IP 패킷 (를 수신 상당히 표준적인 방법으로 소켓을 설정

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

모두 IPv4 및 IPv6) 특정 호스트의 UDP 포트에 대한 IP 및 IPv6 옵션을 설정합니다. 위의 코드는 IPv6 소켓에 대한 IP 및 IPv6 옵션을 모두 설정합니다. IPv4 주소에 패킷이 도착하면 IPv6 소켓 임에도 불구하고 IP_PKTINFO (즉 IPv4) cmsg가 표시되고 사용하지 않으면 전송되지 않습니다. 또한 IPV6_RECPKTINFO 옵션이 설정되어 있는지 확인하십시오 (man 7 ipv6에 언급되지 않음). IPV6_PKTINFO는 (man 7 ipv6에 잘못 설명되어 있습니다.) 이제 UDP 패킷을 수신 :

다음 단계는 인터페이스를 추출하고 UDP 패킷이는 cmsg에서의 수신 처리하는 것입니다
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); 

:

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를 통해 들어온 경우 우리가는 cmsg의 그것이 AF_INET6 소켓 경우에도로는 IPv4 옵션을 넣어 가지고 어떻게 알 수 있습니다. 적어도 리눅스에서해야 할 일입니다.

놀라 울 정도의 작업이지만 실제로 생각할 수있는 모든 Linux 환경에서 작동하는 견고한 UDP 서버를 만들기 위해서는 최소한의 노력이 필요합니다. 그것의 대부분은 멀티 호밍을 투명하게 처리하기 때문에 TCP에는 필요하지 않습니다.

0

최근에 같은 문제가 발생했습니다.

나는이 문제가

  1. 바인드 소켓 특정 인터페이스에 수신 된 패킷에서 인터페이스 이름을 얻을 수 있습니다 해결하기 위해 무엇을
  2. 때어 소켓

예 :

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

IBM 지식 센터에서 소스 IP 선택에 대한 링크를 찾았습니다 : [https://www.ibm.com/support/knowledgecenter/SSLTBW_2.1.0/com.ibm.zos.v2r1.halz002/tcpip_source_ip_addr_selection.htm] https://www.ibm.com/support/knowledgecenter/SSLTBW_2.1.0/com.ibm.zos.v2r1.halz002/tcpip_source_ip_addr_selection.htm) –

관련 문제