본문 바로가기
SWE/네트워크

멀티캐스트 프로그래밍 디테일한 설명 | Network interface index

by S나라라2 2024. 4. 10.
반응형

 

멀티캐스트 개념에 대해 간략히 먼저 정리한다.

 

- 브로드캐스트란? 

일대다 전송이다. TV에서 영상 송출하는 방식을 생각하면 이해하기 쉽다.

 

- 멀티캐스트란? 

브로드캐스트의 오버헤드를 줄이기위해 만들어졌다. 일대다 전송이라는 특징은 같지만, 호스트는 멀티캐스트 그룹 한 곳에만 보내면 된다. (해당 데이터그램에 관심이 있는 수신자가 멀티캐스트 그룹에 join해야한다.) 따라서 데이터그램 한 번의 복사로 모든 link에 전달될 수 있다는 큰 이점이 있다.


 

멀티캐스트 프로그래밍의 기본 단계에 대해 살펴본다.

전송부와 수신부가 있다.

*[참고] 연결지향통신(ex.tcp)에서는 server와 client라고 칭하지만, 비연결지향(ex.udp)에서는 sender와 receiver로 부른다.

*[참고] tcp 패킷은 segment, udp 패킷은 datagram이라고 칭한다.

 

- 전송부

1.socket을 생성한다.

2.주소를 설정한다.

3.loopback에 대해 설정한다. 

4.multicast 데이터그램을 전송할 로컬 인터페이스를 설정한다.

5.데이터그램을 전송한다.

6.소켓을 닫는다.

 

- 수신부

1.socket을 생선한다.

2.address 재사용 옵션을 설정한다.

3.로컬 인터페이스 주소에 bind한다.

4.멀티캐스트 그룹에 join한다.

5.데이터그램을 수신한다.

6.소켓을 닫는다.

 


- loopback이란?

loopback interface는 가상의 네트워크 인터페이스이다. 네트워크 프로토콜을 사용하여 같은 호스트 내에서 서로 통신하려고 할 때 사용한다.

loopback을 활성화하면, 로컬 전달 시 IP 계층에서 패킷이 복사된다. (즉 IP계층에서 loopback된다)

*[참고] OSI 7계층에서 IP datagram은 3계층에 속한다. Network or Internetwork layer

 

=> 멀티캐스트에서 간단히 이해하자면, 내가 보낸 데이터그램을 내가 수신하기를 원할 때 loopback을 활성화하면 된다.

 

- Linux C++ loopback 코드 예제

uint_t loop;
setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &loop, sizeof(loop))

// loop=0 : 루프백 비활성화
// loop=1 : 루프백 활성화

 

 

- bind 코드 예제

// ip address and port
std::string addr = "...";
int port = 1024;

// bind
struct sockaddr_in sock_addr;
memset(&sock_addr, 0, sizeof(sock_addr));
sock_addr.sin_family = AF_INET;
sock_addr.sin_port = htons(port);

sock_addr.sin_addr.s_addr = INADDR_ANY;   // (1) bind any IP address
//inet_pton(AF_INET, addr.c_str(), &(sock_addr.sin_addr))  // (2) bind specific IP address


bind(sock, (struct sockaddr *)&sock_addr, sizeof(sock_addr))

 

- bind를 하는 이유?

디바이스는 여러 개의 IP주소를 가질 수 있는데, 소켓이 어떤 IP주소로부터 수신할지 결정한다.

예를 들어 bind할 때 IP Address에 ANY를 넣으면, 현재 디바이스의 모든 IP Address에 들어오는 데이터를 수신하겠다는 의미이다.

 

네트워크 OSI 7계층과 TCP/IP 프로토콜의 매핑을 보면 이해하기 수월하다.

 

데이터는 가장 아래 1번 계층에 먼저 도착한다. 물리적 계층의 케이블을 통해 데이터가 들어오고, NIC에서 먼저 수신한다.

 

- NIC(Network Interface Card) 란?

네트워크 인터페이스 카드. 네트워크를 관장하는 하드웨어이다. PC 뒤쪽에 이더넷 꽂는 구멍을 보면 몇 개의 NIC가 있는지 확인할 수 있다.

 

NIC와 매핑되어 있는 IP address 테이블이 있다. 패킷의 헤더를 열어보며 다음 계층으로 전달된다.

 

등등..

 

 


 

 

- 멀티캐스트 프로그래밍에서 로컬인터페이스 인덱스를 설정하는 경우

(1) IP_MULTICAST_IF

멀티캐스트 전송부에서 특정 네트워크 인터페이스를 지정하여 데이터그램을 전송하고싶을 때

 

(2) IP_ADD_MEMBERSHIP

멀티캐스트 수신부에서 멀티캐스트 그룹에 가입할 때, 특정 네트워크 인터페이스를 지정 사용하여 수신하기 위해

 

 

- 네트워크 인터페이스 인덱스를 얻는 방법

네트워크 인터페이스 이름을 통해 인덱스를 가져올 수 있다.

if_nametoindex()

#include <net/if.h>

unsigned int index = if_nametoindex("eth0");
if (index == 0) {
    // error
}

 

그러나 위와 같이 고정 "eth0"으로 설정하는 것은 유연성이 떨어지므로 좋은 방법이 아니다.

대신 시스템에서 인터페이스 이름을 동적으로 읽어와서 사용하는 것이 더 바람직하다.

 

- 특정 IP Address의 네트워크 인터페이스 인덱스를 구하는 예제 코드

1. getifaddrs() // 모든 네트워크 정보를 읽어온다.

2. 읽어온 네트워크의 address와 찾으려는 Ip address의 매칭되면 

3. if_nametoindex()  // 인터페이스 이름을 통해서 인덱스를 얻는다.

#include <arpa/inet.h>
#include <cerrno>
#include <ifaddrs.h>
#include <iostream>
#include <net/if.h>  // if_nameindex
#include <string>
#include <string.h>
#include <sysexits.h>
#include <sys/socket.h>
#include <sys/types.h>

inline int getEthInterface(std::string local_addr) {
  std::string eth_interface_name;

  struct ifaddrs* ptr_ifaddrs = nullptr;

  auto result = getifaddrs(&ptr_ifaddrs);
  if (result != 0) {
    std::cout << "`getifaddrs()` failed: " << strerror(errno) << std::endl;

    return EX_OSERR;
  }

  for (struct ifaddrs* ptr_entry = ptr_ifaddrs; ptr_entry != nullptr; ptr_entry = ptr_entry->ifa_next) {
    std::string ipaddress_human_readable_form;
    std::string netmask_human_readable_form;

    std::string interface_name = std::string(ptr_entry->ifa_name);
    sa_family_t address_family = ptr_entry->ifa_addr->sa_family;
    if (address_family == AF_INET) {
      // IPv4

      // Be aware that the `ifa_addr`, `ifa_netmask` and `ifa_data` fields might contain nullptr.
      // Dereferencing nullptr causes "Undefined behavior" problems.
      // So it is need to check these fields before dereferencing.
      if (ptr_entry->ifa_addr != nullptr) {
        char buffer[INET_ADDRSTRLEN] = {
            0,
        };
        inet_ntop(address_family, &((struct sockaddr_in*)(ptr_entry->ifa_addr))->sin_addr, buffer, INET_ADDRSTRLEN);

        ipaddress_human_readable_form = std::string(buffer);
      }

      if (ptr_entry->ifa_netmask != nullptr) {
        char buffer[INET_ADDRSTRLEN] = {
            0,
        };
        inet_ntop(address_family, &((struct sockaddr_in*)(ptr_entry->ifa_netmask))->sin_addr, buffer, INET_ADDRSTRLEN);

        netmask_human_readable_form = std::string(buffer);
      }

      std::cout << interface_name << ": IP address = " << ipaddress_human_readable_form << ", netmask = " << netmask_human_readable_form << std::endl;
      if (ipaddress_human_readable_form == local_addr) {
        // FOUND!!
        eth_interface_name = interface_name;
        break;
      }
    } else if (address_family == AF_INET6) {
      // IPv6
      uint32_t scope_id = 0;
      if (ptr_entry->ifa_addr != nullptr) {
        char buffer[INET6_ADDRSTRLEN] = {
            0,
        };
        inet_ntop(address_family, &((struct sockaddr_in6*)(ptr_entry->ifa_addr))->sin6_addr, buffer, INET6_ADDRSTRLEN);

        ipaddress_human_readable_form = std::string(buffer);
        scope_id = ((struct sockaddr_in6*)(ptr_entry->ifa_addr))->sin6_scope_id;
      }

      if (ptr_entry->ifa_netmask != nullptr) {
        char buffer[INET6_ADDRSTRLEN] = {
            0,
        };
        inet_ntop(address_family, &((struct sockaddr_in6*)(ptr_entry->ifa_netmask))->sin6_addr, buffer, INET6_ADDRSTRLEN);

        netmask_human_readable_form = std::string(buffer);
      }

      std::cout << interface_name << ": IP address = " << ipaddress_human_readable_form << ", netmask = " << netmask_human_readable_form
                << ", Scope-ID = " << scope_id << std::endl;
      if (ipaddress_human_readable_form == local_addr) {
        // FOUND!!
        eth_interface_name = interface_name;
        break;
      }
    }
  }
  if (!eth_interface_name.empty()) {
    return if_nametoindex(eth_interface_name.c_str());
  }
  return 0;
}

int main(void) {
  struct ipv6_mreq group;
  group.ipv6mr_interface = getEthInterface("fde1:53ba:...");
  std::cout << "interface index:" << std::to_string(group.ipv6mr_interface) << std::endl;
  return 1;
}

 

아래 출처에서 코드를 가져와 method call구조로 변경했다.

출처: https://dev.to/fmtweisszwerg/cc-how-to-get-all-interface-addresses-on-the-local-device-3pki

 


 

참고:

https://www.ibm.com/docs/en/i/7.4?topic=designs-examples-using-multicasting-af-inet

https://witestlab.poly.edu/blog/tcp-ip-protocol-stack/

반응형