반응형
리눅스 네트워크 프로그래밍 - UDP6(UDP over IPv6) 송수신
본 글에서는 리눅스 시스템 상에서 IPv6 프로토콜을 통해 UDP 데이터그램(이하 UDP6)을 송수신하는 프로그램의 작성 방법을 소개한다.
UDP6(UDP over IPv6) 데이터그램 송신 프로그램
C 언어로 작성된 프로그램 상에서 UDP6 데이터그램을 송신하는 절차는 다음과 같다.
- 소켓을 연다 - socket() 시스템콜 호출.
- 소켓을 송신용 네트워크 인터페이스에 연결(bind)한다 - setsockopt() 시스템콜 호출.
- 목적지 정보(IPv6 주소, UDP 포트번호)를 설정한다.
- UDP 패킷을 전송한다 - sendto() 시스템콜 호출.
다음은 UDP6 데이터그램을 송신하는 예제 프로그램의 소스 코드이다.
#include <arpa/inet.h>
#include <netinet/in.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
/// IPv6 주소 길이 (바이트수)
#define IPv6_ALEN 16
/// IPv6 주소 문자열 최대 길이
#define IPv6_ADDR_STR_MAX_LEN ((IPv6_ALEN * 2) + 7)
/**
* @brief UDP6 데이터그램을 송신한다.
*
* @param[in] ifname 송신할 네트워크인터페이스 이름(예: eth0)
* @param[in] dst_ip 목적지 IPv6 주소 (16바이트 길이)
* @param[in] port 목적지 포트번호
*
* @retval 0: 성공
* @retval -1: 실패
*/
static int SendUdp6(const char *ifname, const uint8_t *dst_ip, const uint16_t port)
{
char dst_ip_str[IPv6_ADDR_STR_MAX_LEN + 1];
printf("Send UDP6 datagram to %s:%u via %s\n",
inet_ntop(AF_INET6, dst_ip, dst_ip_str, sizeof(dst_ip_str)), port, ifname);
/*
* 소켓을 연다.
*/
int sock = socket(AF_INET6, SOCK_DGRAM, 0);
if (sock < 0) {
perror("Fail to socket() ");
return;
}
printf("Success to socket()\n");
/*
* 소켓을 송신 네트워크 인터페이스에 연결한다.
* - 네트워크 인터페이스의 식별번호를 확인한다.
* - 해당 식별번호에 소켓을 연결한다.
*/
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
int ret = ioctl(sock, SIOGIFINDEX, &ifr);
if (ret < 0) {
perror("Fail to ioctl() ");
close(sock);
return;
}
printf("Success to ioctl() - ifindex = %d\n", ifr.ifr_ifindex);
if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)) < 0) {
perror("Fail to setsockopt() ");
close(sock);
return;
}
printf("Success to setsockopt()\n");
/*
* 목적지 정보를 설정한다.
*/
struct sockaddr_in6 remote;
memset(&remote, 0, sizeof(struct sockaddr_in6));
remote.sin6_family = AF_INET6;
remote.sin6_port = htons(port);
memcpy(remote.sin6_addr.s6_addr, dst_ip, IPv6_ALEN);
/*
* UDP6 데이터그램을 주기적으로 송신한다.
*/
uint8_t txbuf[1000];
while(1)
{
ret = sendto(sock, txbuf, sizeof(txbuf), 0, (struct sockaddr *)&remote, sizeof(remote));
if (ret < 0) {
printf("Fail to send %zu-bytes UDP6 datagram - %m\n", sizeof(txbuf));
} else {
printf("Success to send %zu-bytes UDP6 datagram\n", sizeof(txbuf));
}
sleep(1);
}
return 0;
}
/**
* @brief 프로그램 사용법을 화면에 출력한다.
*
* @param[in] app_filename 어플리케이션 실행파일명
*/
static void usage(const char *app_filename)
{
printf("Usage: %s <ifname> <dst_ip> <dst_port>\n", app_filename);
}
/**
* @brief 테스트 프로그램 메인 함수
*
* @param[in] argc 프로그램 실행 파라미터 개수
* @param[in] argv 프로그램 실행 파라미터(들)
*
* @retval 0: 성공
* @retval -1: 실패
*/
int main(int argc, char *argv[])
{
uint8_t dst_ip[IPv6_ALEN];
uint16_t port;
/*
* 파라미터가 충분히 입력되지 않았으면 종료한다.
*/
if (argc < 4) {
usage(argv[0]);
return -1;
}
/*
* 입력 파라미터를 저장한다.
*/
inet_pton(AF_INET6, argv[2], dst_ip);
port = (uint16_t)strtoul(argv[3], NULL, 10);
/*
* UDP6 데이터그램을 송신한다.
*/
return SendUdp6(argv[1], dst_ip, port);
}
위 코드를 다음과 같이 컴파일한 후 실행하면 UDP6 데이터그램이 송신되는 것을 확인할 수 있다.
root@dc59ab7f0f54:/workspace#
root@dc59ab7f0f54:/workspace# gcc -o udp6_tx udp6_tx.c
root@dc59ab7f0f54:/workspace#
root@dc59ab7f0f54:/workspace# ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:ac:11:00:02
inet addr:172.17.0.2 Bcast:172.17.255.255 Mask:255.255.0.0
inet6 addr: fe80::42:acff:fe11:2/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:13 errors:0 dropped:0 overruns:0 frame:0
TX packets:14 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:1046 (1.0 KB) TX bytes:1124 (1.1 KB)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:4 errors:0 dropped:0 overruns:0 frame:0
TX packets:4 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:4384 (4.3 KB) TX bytes:4384 (4.3 KB)
root@dc59ab7f0f54:/workspace#
root@dc59ab7f0f54:/workspace# ./udp6_tx eth0 fe80::42:acff:fe11:3 1234
Send UDP6 datagram to fe80::42:acff:fe11:3:1234 via eth0
Success to socket()
Success to ioctl() - ifindex = 10
Success to setsockopt()
Success to send 1000-bytes UDP6 datagram
Success to send 1000-bytes UDP6 datagram
Success to send 1000-bytes UDP6 datagram
^C
root@dc59ab7f0f54:/workspace#
UDP6(UDP over IPv6) 데이터그램 수신 프로그램
C 언어로 작성된 프로그램 상에서 UDP6 데이터그램을 수신하는 절차는 다음과 같다.
- 소켓을 연다 - socket() 시스템콜 호출.
- 소켓을 주소 및 포트번호에 바인드한다 - bind() 시스템콜 호출.
- UDP 패킷을 수신한다 - recvfrom() 시스템콜 호출.
다음은 UDP6 데이터그램을 송신하는 예제 프로그램의 소스 코드이다.
#include <arpa/inet.h>
#include <netinet/in.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
/// IPv6 주소 길이 (바이트수)
#define IPv6_ALEN 16
/// IPv6 주소 문자열 최대 길이
#define IPv6_ADDR_STR_MAX_LEN ((IPv6_ALEN * 2) + 7)
/**
* @brief UDP6 데이터그램을 수신한다.
*
* @param[in] port 수신 포트번호
*
* @retval 0: 성공
* @retval -1: 실패
*/
static int ReceiveUdp6(const uint16_t port)
{
printf("Receive UDP6 datagram via port %u\n", port);
/*
* 소켓을 연다.
*/
int sock = socket(AF_INET6, SOCK_DGRAM, 0);
if (sock < 0) {
perror("Fail to socket() ");
return;
}
printf("Success to socket()\n");
/*
* 소켓을 바인드한다
*/
struct sockaddr_in6 addr;
socklen_t addrlen = sizeof(struct sockaddr_in6);
memset(&addr, 0, addrlen);
addr.sin6_family = AF_INET6;
addr.sin6_addr = in6addr_any;
addr.sin6_port = htons(port);
int ret = bind(sock, (struct sockaddr *)&addr, addrlen);
if (ret < 0) {
perror("Fail to bind() ");
close(sock);
return;
}
printf("Success to bind()\n");
/*
* UDP6 데이터그램을 수신한다.
*/
uint8_t rxbuf[1000];
char src_ip_str[IPv6_ADDR_STR_MAX_LEN + 1];
struct sockaddr_in6 peer;
memset(&peer, 0, sizeof(peer));
while(1)
{
ret = recvfrom(sock, rxbuf, sizeof(rxbuf), 0, (struct sockaddr *)&peer, &addrlen);
if (ret < 0) {
printf("Fail to receive UDP6 datagram - %m\n");
continue;
}
printf("%d-bytes UDP6 datagram from %s/%u\n",
ret,
inet_ntop(AF_INET6, peer.sin6_addr.s6_addr, src_ip_str, sizeof(src_ip_str)),
ntohs(peer.sin6_port));
}
return 0;
}
/**
* @brief 프로그램 사용법을 화면에 출력한다.
*
* @param[in] app_filename 어플리케이션 실행파일명
*/
static void usage(const char *app_filename)
{
printf("Usage: %s <rx_port>\n", app_filename);
}
/**
* @brief 테스트 프로그램 메인 함수
*
* @param[in] argc 프로그램 실행 파라미터 개수
* @param[in] argv 프로그램 실행 파라미터(들)
*
* @retval 0: 성공
* @retval -1: 실패
*/
int main(int argc, char *argv[])
{
uint16_t port;
/*
* 파라미터가 충분히 입력되지 않았으면 종료한다.
*/
if (argc < 2) {
usage(argv[0]);
return -1;
}
/*
* 입력 파라미터를 저장한다.
*/
port = (uint16_t)strtoul(argv[1], NULL, 10);
/*
* UDP6 데이터그램을 송신한다.
*/
return ReceiveUdp6(port);
}
위 코드를 다음과 같이 컴파일한 후 실행하면 UDP6 데이터그램 수신 시, 정상 수신되는 것을 확인할 수 있다.
root@dc59ab7f0f54:/workspace#
root@dc59ab7f0f54:/workspace# gcc -o udp6_rx udp6_rx.c
root@dc59ab7f0f54:/workspace#
root@dc59ab7f0f54:/workspace#
root@dc59ab7f0f54:/workspace# ./udp6_rx 1234
Receive UDP6 datagram via port 1234
Success to socket()
Success to bind()
1000-bytes UDP6 datagram from fe80::42:acff:fe11:2/37639
1000-bytes UDP6 datagram from fe80::42:acff:fe11:2/37639
1000-bytes UDP6 datagram from fe80::42:acff:fe11:2/37639
1000-bytes UDP6 datagram from fe80::42:acff:fe11:2/37639
^C
root@dc59ab7f0f54:/workspace#
파트너스 활동을 통해 일정액의 수수료를 제공받을 수 있음
'프로그래밍 > 리눅스 프로그래밍' 카테고리의 다른 글
GPSd를 이용한 GNSS 프로그래밍 (0) | 2020.12.24 |
---|---|
리눅스 프로그래밍 - 프로세스 종료 신호(SIGINT, SIGTERM) 후킹하기 (0) | 2020.08.21 |
리눅스 타이머 프로그래밍 (0) | 2020.07.31 |
리눅스 네트워크 프로그래밍 - CAN(Controller Area Network) 통신 (2) | 2020.04.27 |
리눅스 네트워크 프로그래밍 - 네트워크 인터페이스 비활성화하기 (0) | 2020.02.16 |
리눅스 네트워크 프로그래밍 - 네트워크 인터페이스 활성화하기 (0) | 2020.02.15 |
리눅스 네트워크 프로그래밍 - 네트워크 인터페이스 MAC 주소 확인하기 (0) | 2020.02.15 |
리눅스 네트워크 프로그래밍 - 네트워크 인터페이스 MAC 주소 설정하기 (0) | 2020.02.15 |