리눅스 네트워크 프로그래밍 - 네트워크 인터페이스 IPv6 주소 설정하기

반응형

본 글에서는 리눅스 시스템의 각 네트워크 인터페이스에 IPv6 주소를 할당(추가)하거나 제거하는 프로그램을 작성하는 방법에 대해 설명한다. 

 

본 글에서 다루는 IPv6 주소 할당 관련 기능은 다음과 같으며, 각 기능 별 예제 프로그램은 C 언어로 작성되었다.

 

  • 네트워크 인터페이스에 IPv6 주소 할당(추가)하기
  • MAC 주소로부터 EUI-64 형식 Interface ID 생성하기 
  • 링크-로컬 IPv6 주소 할당하기 
  • 글로벌 IPv6 주소 할당하기 (Stateless Address Autoconfiguration)
  • 네트워크 인터페이스에 할당된 IPv6 주소 제거하기

 

네트워크 인터페이스에 할당된 IPv6 주소(들)을 확인하는 방법은 아래 글에 설명되어 있다.

2020/02/08 - [프로그래밍/리눅스 프로그래밍] - 리눅스 네트워크 프로그래밍 - 네트워크 인터페이스 IPv6 주소 확인하기

 

 

 

개요

리눅스 시스템 상에서, 각 네트워크 인터페이스는 하나 이상의 IPv6 주소를 가질 수 있다. 본 글에서는 네트워크 인터페이스에 IPv6 주소를 (추가) 할당하거나, 할당된 주소를 제거하는 방법을 소개한다.

 

본 글의 예제 프로그램에서는, 추가(할당)하고자 하는 IPv6 주소를 직접 생성한다. 이를 위해 MAC 주소로부터 Modified EUI-64 형식 Interface ID 를 생성하고, 해당 Interface ID 를 이용하여 IPv6 주소를 생성하는 방법(Stateless Address Autoconfiguration)도 함께 소개한다.

 

할당할 IPv6 주소를 생성한 후, 원하는 네트워크 인터페이스에 대한 ioctl() 시스템 콜을 호출함으로써 IPv6 주소를 할당할 수 있다. 또한 ioctl() 시스템 콜을 통해 네트워크 인터페이스에 할당된 IPv6 주소들 중 특정 IPv6 주소를 제거할 수 있다. 

 

 

네트워크 인터페이스에 IPv6 주소 할당(추가)하기

원하는 IPv6 주소를 네트워크 인터페이스에 할당(추가)할 수 있다.

다음은 특정 IPv6 주소를 네트워크인터페이스에 할당(추가)하는 예제 함수이다. 

#include <linux/in6.h>
#include <net/if.h>
#include <sys/ioctl.h> 
#include <sys/socket.h>
#include <sys/types.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>


/// IPv6 주소 길이
#define IPv6_ALEN 16


/**
 * IPv6 주소 할당 관련 구조체 
 *  - 할당할 IPv6 주소 정보를 채운 후 ioctl()을 통해 전달한다.
 *  - from linux/ipv6.h
 */
struct in6_ifreq 
{
  struct in6_addr addr;
  uint32_t prefix_len;
  int ifindex;
};


/**
 * 네트워크인터페이스에 IPv6 주소를 할당(추가)한다.
 * 
 * @param[in] ifname      네트워크 인터페이스 이름 (예: eth0)
 * @param[in] ip_addr     할당할 IPv6 주소
 * @param[in] prefix_len  할당할 IPv6 주소의 프리픽스 길이(비트 수. ~64)
 * 
 * @retval 0: 성공
 * @retval -1: 실패
 */ 
int SetSpecifiedIPv6Address(const char *ifname, const uint8_t *ip_addr, const uint32_t prefix_len)
{
  struct ifreq ifr;
  struct in6_ifreq ifr6;
  int sockfd, ret;

  /*
   * IPv6 소켓을 연다.
   */ 
  sockfd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_IP);
  if(sockfd < 0) {
    perror("Fail to socket()");
    return -1;
  }

  /*
   * IPv6 주소를 할당하고자 하는 네트워크인터페이스의 인터페이스번호(운영체제에서 사용되는)를 확인한다.
   */
  snprintf(ifr.ifr_name, IFNAMSIZ, "%s", ifname);
  ret = ioctl(sockfd, SIOGIFINDEX, &ifr);
  if(ret < 0) {
    perror("Fail to ioctl(SIOGIFINDEX)");
    close(sockfd);
    return -1;
  }

  /*
   * IPv6 주소를 할당한다.
   */ 
  memcpy(&(ifr6.addr), ip_addr, IPv6_ALEN);
  ifr6.ifindex = ifr.ifr_ifindex;
  ifr6.prefix_len = prefix_len;
  ret = ioctl(sockfd, SIOCSIFADDR, &ifr6);
  if(ret < 0) {
    perror("Fail to ioctl(SIOCSIFADDR)\n");
    close(sockfd);
    return -1;
  }

 /*
  * 소켓을 닫는다.
  */
  close(sockfd);
  return 0;
}

 

 

MAC 주소로부터 Modified EUI-64 형식 Interface ID 생성하기 

6바이트 길이의 MAC 주소로부터 8바이트 길이 Modified EUI-64 형식 Interface ID 를 생성할 수 있다. 이 Interface ID 는 링크-로컬 IPv6 주소를 할당하거나 Stateless Address Autoconfiguration 방식(RFC 4862)에 따라 IPv6 주소를 할당할 때 사용된다.

다음은 네트워크 인터페이스의 MAC 주소로부터 Modified EUI-64 형식 Interface ID 를 생성하는 예제 함수이다.

#include <stdint.h>
#include <string.h>

/**
 * MAC 주소로부터 Modified EUI-64 형식의 Interface ID 를 생성한다.
 * 
 * @param[in] mac_addr      MAC 주소 (6바이트 길이)
 * @param[out] interface_id 생성된 Interface ID 가 저장될 버퍼 (8바이트 길이)
 */ 
void DeriveModifiedEUI64InterfaceIdFromMacAddress(const uint8_t *mac_addr, uint8_t *interface_id)
{
  memcpy(interface_id, mac_addr, 3);
  interface_id[3] = 0xFF;
  interface_id[4] = 0xFE;
  memcpy(interface_id + 5, mac_addr + 3, 3);  
  
  /* MAC address의 U/L bit을 역전시킨다.(U/L bit의 의미가 반대임)
   * 	MAC address의 U/L bit의 의미 - 0: universal, 1: local
   * 	Modified EUI-64 주소의 U/L bit의 의미 - 0: local, 1: universal */
  if(interface_id[0] & (1<<1)) {
    interface_id[0] &= ~(1<<1);
  } else {
    interface_id[0] |= (1<<1);
  }
}

 

 

링크-로컬 IPv6 주소 할당하기 

위에서 생성된 Modified EUI-64 형식 Interface ID 를 이용하여 링크-로컬 IPv6 주소를 생성할 수 있다.

다음은 Modified EUI-64 형식 Interface ID 를 이용하여 생성한 링크-로컬 IPv6 주소를 네트워크 인터페이스에 할당(추가)하는 예제 함수이다.

#include <stdint.h>
#include <string.h>

/// EUI64 Interface ID 길이
#define EUI64_INTERFACE_ID_LEN 8
/// 링크-로컬 주소의 프리픽스 길이
#define LINK_LOCAL_ADDRESS_PREFIX_LEN 8
/// IPv6 주소 길이
#define IPv6_ALEN 16

/**
 * 네트워크 인터페이스에 링크-로컬 IPv6 주소를 설정한다.
 * 
 * @param[in] ifname    네트워크 인터페이스 이름(예: eth0)
 * @param[in] mac_addr  네트워크 인터페이스의 MAC 주소 (6바이트 길이)
 * 
 * @return SetSpecifiedIPv6Address() 결과값
 */  
int SetLinkLocalIPv6Address(const char *ifname, const uint8_t *mac_addr)
{
  uint8_t interface_id[EUI64_INTERFACE_ID_LEN];
  uint8_t ll_prefix[LINK_LOCAL_ADDRESS_PREFIX_LEN] = { 0xfe,0x80,0x00,0x00,0x00,0x00,0x00,0x00 };
  uint8_t ip_addr[IPv6_ALEN];  
  
  /*
   * MAC 주소로부터 Modified EUI-64 Interface ID 를 생성한다.
   */ 
  MakeModifiedEUI64InterfaceIdFromMacAddress(mac_addr, interface_id); 
  
  /* 
   * 프리픽스와 Interface ID 를 결합하여 링크-로컬 주소를 생성한다.
   */
  memcpy(ip_addr, ll_prefix, LINK_LOCAL_ADDRESS_PREFIX_LEN);
  memcpy(ip_addr + LINK_LOCAL_ADDRESS_PREFIX_LEN, interface_id, EUI64_INTERFACE_ID_LEN);  
  
  /*
   * 생성된 링크-로컬 주소를 네트워크 인터페이스에 할당한다.
   */
  return SetSpecifiedIPv6Address(ifname, ip_addr, LINK_LOCAL_ADDRESS_PREFIX_LEN * 8);
}

 

 

글로벌 IPv6 주소 할당하기 (Stateless Address Autoconfiguration)

라우터 등으로부터 수신한 네트워크 프리픽스 정보를 기반으로 글로벌 IPv6 주소를 생성하여, 네트워크 인터페이스에 할당할 수 있다.
다음은 제공되는 프리픽스 정보를 이용하여 IPv6 주소 생성 후, 이를 네트워크 인터페이스에 할당(추가)하는 예제 프로그램이다.

#include <stdint.h>
#include <string.h>

/// EUI64 Interface ID 길이
#define EUI64_INTERFACE_ID_LEN 8
/// IPv6 주소 길이
#define IPv6_ALEN 16


/**
 * 제공되는 프리픽스 정보를 기반으로 한 IPv6 를 네트워크 인터페이스에 할당한다.
 * 
 * @param[in] ifname      네트워크 인터페이스 이름 (예: eth0)
 * @param[in] prefix      프리픽스 정보가 담긴 배열 (최대 8바이트 길이)
 * @param[in] prefix_len  프리픽스 길이 (비트 수. ~64)
 * @param[in] mac_addr    네트워크 인터페이스의 MAC 주소 (6바이트 길이)
 * 
 * @return SetSpecifiedIPv6Address 결과 값
 */ 
int SetIPv6AddressWithPrefixInformation(const char *ifname, const uint8_t *prefix, const uint32_t prefix_len, const uint8_t *mac_addr)
{
  uint32_t bytes = prefix_len / 8;
  uint32_t remained_bit = prefix_len % 8;
  uint8_t interface_id[EUI64_INTERFACE_ID_LEN];
  uint8_t ip_addr[IPv6_ALEN];

  memset(ip_addr, 0, sizeof(ip_addr));

  /*
   * MAC 주소로부터 Modified EUI-64 Interface ID 를 생성한다.
   */ 
  MakeModifiedEUI64InterfaceIdFromMacAddress(mac_addr, interface_id); 
  
  /*
   * 프리픽스 정보와 Interface ID 를 결합하여 IPv6 주소를 생성한다.
   */
  memcpy(ip_addr, prefix, (remained_bit) ? bytes+1 : bytes);
  memcpy(ip_addr + (IPv6_ALEN - EUI64_INTERFACE_ID_LEN), interface_id, EUI64_INTERFACE_ID_LEN);
  
  /*
   * 생성된 주소를 네트워크 인터페이스에 할당한다.
   */
  return SetSpecifiedIPv6Address(ifname, ip_addr, prefix_len);
}

 

 

네트워크 인터페이스에 할당된 IPv6 주소 제거하기

네트워크 인터페이스에 할당된 IPv6 주소(들) 중 원하는 주소를 제거할 수 있다. 

다음은 네트워크 인터페이스에 할당된 IPv6 중 특정 하나의 IPv6 주소를 제거하는 예제 프로그램이다.

#include <linux/in6.h>
#include <net/if.h>
#include <sys/ioctl.h> 
#include <sys/socket.h>
#include <sys/types.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>


/// IPv6 주소 길이
#define IPv6_ALEN 16


/**
 * IPv6 주소 삭제 관련 구조체 
 *  - 삭제할 IPv6 주소 정보를 채운 후 ioctl()을 통해 전달한다.
 *  - from linux/ipv6.h
 */
struct in6_ifreq 
{
  struct in6_addr addr;
  uint32_t prefix_len;
  int ifindex;
};


/**
 * 네트워크 인터페이스에 할당된 IPv6 를 삭제한다.
 * 
 * @param[in] ifname      네트워크 인터페이스 이름 (예: eth0)
 * @param[in] ip_addr     삭제할 IPv6 주소
 * @param[in] prefix_len  삭제할 IPv6 주소의 프리픽스 길이(비트 수. ~64)
 * 
 * @retval 0: 성공
 * @retval -1: 실패
 */ 
int DeleteIPv6Address(const char *ifname, const uint8_t *ip_addr, const uint32_t prefix_len)
{
  struct ifreq ifr;
  struct in6_ifreq ifr6;
  int sockfd, ret;

  /*
   * IPv6 소켓을 연다.
   */ 
  sockfd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_IP);
  if(sockfd < 0) {
    perror("Fail to socket()");
    return -1;
  }

  /*
   * IPv6 주소를 할당하고자 하는 네트워크인터페이스의 인터페이스번호(운영체제에서 사용되는)를 확인한다.
   */
  snprintf(ifr.ifr_name, IFNAMSIZ, "%s", ifname);
  ret = ioctl(sockfd, SIOGIFINDEX, &ifr);
  if(ret < 0) {
    perror("Fail to ioctl(SIOGIFINDEX)");
    close(sockfd);
    return -1;
  }

  /*
   * IPv6 주소를 삭제한다.
   */ 
  memcpy(&(ifr6.addr), ip_addr, IPv6_ALEN);
  ifr6.ifindex = ifr.ifr_ifindex;
  ifr6.prefix_len = prefix_len;
  ret = ioctl(sockfd, SIOCDIFADDR, &ifr6);
  if(ret < 0) {
    perror("Fail to ioctl(SIOCDIFADDR)\n");
    close(sockfd);
    return -1;
  }

 /*
  * 소켓을 닫는다.
  */
  close(sockfd);
  return 0;
}

 

 

댓글

Designed by JB FACTORY