Pytanie Ustawianie źródłowego adresu IP dla gniazda UDP


Mam gniazdo UDP, które jest powiązane z INADDR_ANY, aby słuchać pakietów na wszystkich IP moich serwerów. Wysyłam odpowiedzi przez to samo gniazdo.

Teraz serwer wybiera automatycznie, który IP jest używany jako źródłowy adres IP, gdy pakiety są wysyłane, ale chciałbym móc ustawić wychodzące źródło IP samodzielnie.

Czy jest jakiś sposób, aby to zrobić bez konieczności tworzenia osobnego gniazda dla każdego adresu IP?


26
2018-06-17 13:53


pochodzenie




Odpowiedzi:


Nikołaj, używając oddzielnego gniazda i powiązania (2) dla każdego adresu lub bałaganu z tablicami routingu, często nie jest wykonalną opcją, np. z dynamicznymi adresami. Pojedynczy IP_ADDRANYserwer UDP powinien być w stanie odpowiadać na ten sam dynamicznie przydzielony adres IP, na który jest odbierany pakiet.

Na szczęście istnieje inny sposób. W zależności od wsparcia twojego systemu możesz skorzystać z IP_PKTINFO opcje gniazda do ustawiania lub odbierania pomocniczych danych dotyczących wiadomości. Dane pomocnicze (przez cmsg(3)) jest jednak w wielu miejscach online comp.os.linux.development.system miał pełną próbkę kodu specyficzną dla IP_PKTINFO.

Używany kod w łączu IP_PKTINFO (lub IP_RECVDSTADDR w zależności od platformy), aby uzyskać docelowy adres komunikatu UDP od pomocniczego cmsg(3) dane. Parafrazowane tutaj:

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

Gene, twoje pytanie pytanie, jak ustawić adres źródłowy na pakiety wychodzące. Z IP_PKTINFO możliwe jest ustawienie ipi_spec_dst dziedzinie struct in_pktinfo w dodatkowych danych przekazanych do sendmsg(2). Zobacz post przytoczony powyżej, cmsg(3), i sendmsg(2) wytyczne dotyczące tworzenia i manipulowania danymi pomocniczymi w struct msghdr. Przykład (bez gwarancji tutaj) może być:

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

Zauważ, że jest inaczej w IPv6: użyj struct in6_pktinfo::ipi6_addr zarówno w przypadkach recvmsg, jak i sendmsg.

Zauważ też, że Windows nie obsługuje odpowiednika dla ipi_spec_dst w strukturze in_pktinfo, więc nie możesz użyć tej metody do ustawienia adresu źródłowego na wychodzącym pakiecie winsock2.

(przywołane strony man - uzyskanie limitu 1 hiperłącza)

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

26
2017-10-14 00:44





Myślałem, że rozwinę Jeremy'ego, jak to zrobić dla IPv6. Jeremy pozostawia wiele szczegółów, a niektóre dokumenty (takie jak strona man dla Linuxa dla ipv6) są po prostu błędne. Najpierw na niektórych dystrybucjach musisz zdefiniować _GNU_SOURCE, w przeciwnym razie niektóre z rzeczy IPv6 nie są zdefiniowane:

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

Następnie skonfiguruj gniazdo w dość standardowy sposób, który nasłuchuje na wszystkich pakietach IP (tj. Zarówno IPv4, jak i IPv6) na określonym porcie UDP:

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

Zwróć uwagę, że powyższy kod ustawia opcje IP i IPv6 dla gniazda IPv6. Okazuje się, że jeśli pakiet dotrze na adres IPv4, otrzymasz cmsg IP_PKTINFO (np. IPv4), mimo że jest to gniazdo IPv6, a jeśli nie włączysz ich, nie będą wysyłane. Zauważ również, że ustawiono opcję IPV6_RECPKTINFO (o której nie wspomniano w człowiek 7 ipv6), a nie IPV6_PKTINFO (co jest opisane błędnie w człowiek 7 ipv6). Teraz otrzymasz pakiet 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);

Następnym krokiem jest wyodrębnienie interfejsu i adresu, z którego otrzymano pakiet UDP na serwerze 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;
  }
}

Wreszcie możemy wysłać odpowiedź z powrotem, używając tego samego miejsca docelowego.

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

Zauważ, jak, jeśli pakiet wszedł za pośrednictwem IPv4, musimy umieścić opcję IPv4 w cmsg, mimo że jest to gniazdo AF_INET6. Przynajmniej to musisz zrobić dla Linuksa.

Jest to zaskakująca ilość pracy, ale AFAICT to minimum, które musisz zrobić, aby stworzyć solidny serwer UDP, który działa we wszystkich możliwych środowiskach Linux. Większość z nich nie jest wymagana dla protokołu TCP, ponieważ obsługuje przezroczystość wieloobiektową.


18
2017-09-13 02:50





Ty też bind(2) do każdego adresu interfejsu i zarządzać wieloma gniazdami, lub niech jądro wykona niejawne przypisanie źródła źródłowego INADDR_ANY. Nie ma innego wyjścia.

Moje pytanie brzmi - dlaczego tego potrzebujesz? Czy normalne routing IP nie działa dla ciebie?


3
2018-06-17 15:14



Dzięki temu routing IP działa dobrze, a pakiety docierają do miejsca docelowego, ale niestety wszyscy klienci łączą się z ich określonym adresem IP serwera, a protokół wymaga, aby otrzymali odpowiedź z tego konkretnego adresu IP. Teraz wszyscy klienci otrzymują odpowiedź z tego samego adresu IP. - Gene Vincent
Podejrzewam, że to byłaby tablica routingu - czy masz jedną domyślną trasę / bramę? Pomocne może być dodanie tras właściwych dla adresów klientów. - Nikolai Fetissov
Tak, dodanie trasy hosta pomaga, ale wolałbym to zrobić w moim programie. - Gene Vincent


Ostatnio napotkałem ten sam problem.

To, co robię, aby rozwiązać ten problem, to

  1. pobierz nazwę interfejsu z odebranego pakietu
  2. przywiąż gniazdo do określonego interfejsu
  3. odłącz gniazdo

Przykład:

  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
2017-08-18 08:16



Znalazłem link o źródłowym wyborze adresu IP z centrum wiedzy IBM: https://www.ibm.com/support/knowledgecenter/SSLTBW_2.1.0/com.ibm.zos.v2r1.halz002/tcpip_source_ip_addr_selection.htm - Chih-Ying Lin