리눅스 네트워크 프로그래밍 - UDP6(UDP over IPv6) 송수신

반응형

 

리눅스 네트워크 프로그래밍 - 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# 

 

파트너스 활동을 통해 일정액의 수수료를 제공받을 수 있음

 

댓글

Designed by JB FACTORY