본 글에서는 리눅스 시스템의 각 네트워크 인터페이스에 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;
}
'프로그래밍 > 리눅스 프로그래밍' 카테고리의 다른 글
리눅스 네트워크 프로그래밍 - CAN(Controller Area Network) 통신 (2) | 2020.04.27 |
---|---|
리눅스 네트워크 프로그래밍 - UDP6(UDP over IPv6) 송수신 (0) | 2020.03.04 |
리눅스 네트워크 프로그래밍 - 네트워크 인터페이스 비활성화하기 (0) | 2020.02.16 |
리눅스 네트워크 프로그래밍 - 네트워크 인터페이스 활성화하기 (0) | 2020.02.15 |
리눅스 네트워크 프로그래밍 - 네트워크 인터페이스 MAC 주소 확인하기 (0) | 2020.02.15 |
리눅스 네트워크 프로그래밍 - 네트워크 인터페이스 MAC 주소 설정하기 (0) | 2020.02.15 |
리눅스 네트워크 프로그래밍 - inet_ntop() : IPv6 주소를 문자열로 변환 (0) | 2020.02.12 |
리눅스 네트워크 프로그래밍 - 네트워크 인터페이스 IPv6 주소 확인하기 (0) | 2020.02.08 |