22.6. Связывание с адресами интерфейсов

22.6. Связывание с адресами интерфейсов

Одно из типичных применений функции get_ifi_info связано с приложениями UDP, которым нужно выполнять мониторинг всех интерфейсов на узле, чтобы знать, когда и на какой интерфейс приходит дейтаграмма. Это позволяет получающей программе узнавать адрес получателя дейтаграммы UDP, так как именно по этому адресу определяется сокет, на который доставляется дейтаграмма, даже если узел не поддерживает параметр сокета IP_RECVDSTADDR.

ПРИМЕЧАНИЕ

Вспомните наше обсуждение в конце раздела 22.2. Если узел использует более распространенную модель системы с гибкой привязкой (см. раздел 8.8), IP-адрес получателя может отличаться от IP-адреса принимающего интерфейса. В этом случае мы можем определить только адрес получателя дейтаграммы, который не обязательно должен быть адресом, присвоенным принимающему интерфейсу. Чтобы определить принимающий интерфейс, требуется параметр сокета IP_RECVIF или IPV6_PKTINFO.

В листинге 22.13 показана первая часть примера применения этой технологии к эхо-серверу UDP, который связывается со всеми адресами направленной передачи, широковещательной передачи и, наконец, с универсальными адресами.

Листинг 22.13. Первая часть сервера UDP, который с помощью функции bind связывается со всеми адресами

//advio/udpserv03.c

 1 #include "unpifi.h"

 2 void mydg_echo(int, SA*, socklen_t, SA*);

 3 int

 4 main(int argc, char **argv)

 5 {

 6  int sockfd;

 7  const int on = 1;

 8  pid_t pid;

 9  struct ifi_info *ifi, *ifihead;

10  struct sockaddr_in *sa, cliaddr, wildaddr;

11  for (ifihead = ifi = Get_ifi_info(AF_INET, 1);

12   ifi != NULL; ifi = ifi->ifi_next) {

13   /* связываем направленный адрес */

14   sockfd = Socket(AF_INET, SOCK_DGRAM, 0);

15   Setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

16   sa = (struct sockaddr_in*)ifi->ifi_addr;

17   sa->sin_family = AF_INET;

18   sa->sin_port = htons(SERV_PORT);

19   Bind(sockfd, (SA*)sa, sizeof(*sa));

20   printf("bound %s ", Sock_ntop((SA*)sa, sizeof(*sa)));

21   if ((pid = Fork()) == 0) { /* дочерний процесс */

22    mydg_echo(sockfd, (SA*)&cliaddr, sizeof(cliaddr), (SA*)sa);

23    exit(0); /* не выполняется */

24   }

Вызов функции get_ifi_info для получения информации об интерфейсе

11-12 Функция get_ifi_info получает все адреса IPv4, включая дополнительные (псевдонимы), для всех интерфейсов. Затем программа перебирает все структуры ifi_info.

Создание сокета UDP и связывание адреса направленной передачи

13-20 Создается сокет UDP, и с ним связывается адрес направленной передачи. Мы также устанавливаем параметр сокета SO_REUSEADDR, поскольку мы связываем один и тот же порт (параметр SERV_PORT) для всех IP-адресов.

ПРИМЕЧАНИЕ

Не все реализации требуют, чтобы был установлен этот параметр сокета. Например, Беркли-реализации не требуют этого параметра и позволяют с помощью функции bind связать уже связанный порт, если новый связываемый IP-адрес не является универсальным адресом и отличается от всех IP-адресов, уже связанных с портом. Однако Solaris 2.5 для успешного связывания с одним и тем же портом второго адреса направленной передачи требует установки этого параметра.

Порождение дочернего процесса для данного адреса

21-24 Вызывается функция fork, порождающая дочерний процесс. В этом дочернем процессе вызывается функция mydg_echo, которая ждет прибытия любой дейтаграммы на сокет и отсылает ее обратно отправителю.

В листинге 22.14 показана следующая часть функции main, которая обрабатывает широковещательные адреса.

Листинг 22.14. Вторая часть сервера UDP, который с помощью функции bind связывается со всеми адресами

//advio/udpserv03.c

25   if (ifi->ifi_flags & IFF_BROADCAST) {

26    /* пытаемся связать широковещательный адрес */

27    sockfd = Socket(AF_INET, SOCK_DGRAM, 0);

28    Setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

29    sa = (struct sockaddr_in*)ifi->ifi_brdaddr;

30    sa->sin_family = AF_INET;

31    sa->sin_port = htons(SERV_PORT);

32    if (bind(sockfd, (SA*)sa, sizeof(*sa)) < 0) {

33     if (errno == EADDRINUSE) {

34      printf("EADDRINUSE: %s ",

35       Sock_ntop((SA*)sa, sizeof(*sa)));

36      Close(sockfd);

37      continue;

38     } else

39      err_sys("bind error for %s",

40       Sock_ntop((SA*)sa, sizeof(*sa)));

41    }

42    printf("bound %s ", Sock_ntop((SA*)sa, sizeof(*sa)));

43    if ((pid = Fork()) == 0) { /* дочерний процесс */

44     mydg_echo(sockfd, (SA*)&cliaddr, sizeof(cliaddr),

45      (SA*)sa);

46     exit(0); /* не выполняется */

47    }

48   }

49  }

Связывание с широковещательными адресами

25-42 Если интерфейс поддерживает широковещательную передачу, создается сокет UDP и с ним связывается широковещательный адрес. На этот раз мы позволим функции bind завершиться с ошибкой EADDRINUSE, поскольку если у интерфейса имеется несколько дополнительных адресов (псевдонимов) в одной подсети, то каждый из различных адресов направленной передачи будет иметь один и тот же широковещательный адрес. Подобный пример приведен после листинга 17.3. В этом сценарии мы предполагаем, что успешно выполнится только первая функция bind.

Порождение дочернего процесса

43-47 Порождается дочерний процесс, и он вызывает функцию mydg_echo.

Заключительная часть функции main показана в листинге 22.15. В этом коде при помощи функции bind происходит связывание с универсальным адресом для обработки любого адреса получателя, отличного от адресов направленной и широковещательной передачи, которые уже связаны. На этот сокет будут приходить только дейтаграммы, предназначенные для ограниченного широковещательного адреса (255.255.255.255).

Листинг 22.15. Заключительная часть сервера UDP, связывающегося со всеми адресами

//advio/udpserv03.c

50  /* связываем универсальный адрес */

51  sockfd = Socket(AF_INET, SOCK_DGRAM, 0);

52  Setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

53  bzero(&wildaddr, sizeof(wildaddr));

54  wildaddr.sin_family = AF_INET;

55  wildaddr.sin_addr.s_addr = htonl(INADDR_ANY);

56  wildaddr.sin_port = htons(SERV_PORT);

57  Bind(sockfd, (SA*)&wildaddr, sizeof(wildaddr));

58  printf("bound %s ", Sock_ntop((SA*)&wildaddr, sizeof(wildaddr)));

59  if ((pid = Fork()) == 0) { /* дочерний процесс */

60   mydg_echo(sockfd, (SA*)&cliaddr, sizeof(cliaddr), (SA*)sa);

61   exit(0); /* не выполняется */

62  }

63  exit(0);

64 }

Создание сокета и связывание с универсальным адресом

50-62 Создается сокет UDP, устанавливается параметр сокета SO_REUSEADDR и происходит связывание с универсальным IP-адресом. Порождается дочерний процесс, вызывающий функцию mydg_echo.

Завершение работы функции main

63 Функция main завершается, и сервер продолжает выполнять работу, как и все порожденные дочерние процессы.

Функция mydg_echo, которая выполняется всеми дочерними процессами, показана в листинге 22.16.

Листинг 22.16. Функция mydg_echo

//advio/udpserv03.c

65 void

66 mydg_echo(int sockfd, SA *pcliaddr, socklen_t clilen, SA *myaddr)

67 {

68  int n;

69  char mesg[MAXLINE];

70  socklen_t len;

71  for (;;) {

72   len = clilen;

73   n = Recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len);

74   printf("child %d, datagram from %s", getpid(),

75   Sock_ntop(pcliaddr, len));

76   printf(", to %s ", Sock_ntop(myaddr, clilen));

77   Sendto(sockfd, mesg, n, 0, pcliaddr, len);

78  }

79 }

Новый аргумент

65-66 Четвертым аргументом этой функции является IP-адрес, связанный с сокетом. Этот сокет должен получать только дейтаграммы, предназначенные для данного IP-адреса. Если IP-адрес является универсальным, сокет должен получать только те дейтаграммы, которые не подходят ни для какого другого сокета, связанного с тем же портом.

Чтение дейтаграммы и отражение ответа

71-78 Дейтаграмма читается с помощью функции recvfrom и отправляется клиенту обратно с помощью функции sendto. Эта функция также выводит IP-адрес клиента и IP-адрес, который был связан с сокетом.

Запустим эту программу на нашем узле solaris после установки псевдонима для интерфейса hme0 Ethernet. Адрес псевдонима: узел 200 в сети 10.0.0/24.

solaris % udpserv03

bound 127.0.0.1:9877     интерфейс закольцовки

bound 10.0.0.200:9877    направленный адрес интерфейса hme0:1

bound 10.0.0.255:9877    широковещательный адрес интерфейса hme0:1

bound 192.168.1.20:9877  направленный адрес интерфейса hme0

bound 192.168.1.255:9877 широковещательный адрес интерфейса hme0

bound 0.0.0.0.9877       универсальный адрес

При помощи утилиты netstat мы можем проверить, что все сокеты связаны с указанными IP-адресами и портом:

solaris % netstat -na | grep 9877

127.0.0.1.9877       Idle

10.0.0.200.9877      Idle

    *.9877           Idle

192.129.100.100.9877 Idle

    *.9877           Idle

    *.9877           Idle

Следует отметить, что для простоты мы создаем по одному дочернему процессу на сокет, хотя возможны другие варианты. Например, чтобы ограничить число процессов, программа может управлять всеми дескрипторами сама, используя функцию select и не вызывая функцию fork. Проблема в данном случае будет заключаться в усложнении кода. Хотя использовать функцию select для всех дескрипторов несложно, нам придется осуществить некоторое сопоставление каждого дескриптора связанному с ним IP-адресу (вероятно, с помощью массива структур), чтобы иметь возможность вывести IP-адрес получателя после того, как на определенном сокете получена дейтаграмма. Часто бывает проще использовать отдельный процесс или поток для каждой операции или дескриптора вместо мультиплексирования множества различных операций или дескрипторов одним процессом.

Данный текст является ознакомительным фрагментом.