리눅스 고급 네트워크 프로그래밍 기법

소켓 옵션 설정 함수

소켓의 동작 방식을 제어하기 위해 setsockopt 함수를 사용합니다:

#include<sys/socket.h>

int setsockopt(int sockfd, int level, int optname, const void* optval, socklen_t* optlen);

// sockfd: 대상 소켓 디스크립터
// level: 제어 계층 수준
// optname, optval, optlen: 기능별로 다른 조합으로 다양한 설정 가능

응용 예제

1. 데이터 송수신 타임아웃 설정

struct timeval tm_out;
tm_out.tv_sec=5;
tm_out.tv_usec=0;

// 수신 타임아웃
setsockopt(server_sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&tm_out, sizeof(tm_out));

// 송신 타임아웃
setsockopt(server_sock, SOL_SOCKET, SO_SNDTIMEO, (char*)&tm_out, sizeof(tm_out));

2. 버퍼 크기 조절

// 수신 버퍼
int buf_size=1024*1024;
setsockopt(server_sock, SOL_SOCKET, SO_RCVBUF, (const char*)&buf_size, sizeof(buf_size));

// 송신 버퍼
setsockopt(server_sock, SOL_SOCKET, SO_SNDBUF, (const char*)&buf_size, sizeof(buf_size));

3. 브로드캐스트 활성화

int enable_broadcast=1;
setsockopt(server_sock, SOL_SOCKET, SO_BROADCAST, (const char*)&enable_broadcast, sizeof(enable_broadcast));

4. 버퍼 복사 비활성화

성능 향상을 위해 소켓 버퍼와 응용 프로그램 버퍼 간의 데이터 복사를 생략할 수 있습니다:

int disable_buf=0;
setsockopt(server_sock, SOL_SOCKET, SO_SNDBUF, (char*)&disable_buf, sizeof(disable_buf));
setsockopt(server_sock, SOL_SOCKET, SO_RCVBUF, (char*)&disable_buf, sizeof(disable_buf));

Select I/O 멀티플렉싱

#include <stdio.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>

#define SRV_PORT 5555
#define MAX_CLIENTS 5
#define MSG_BUF 200

int main(int argc, char **argv)
{
    int srv_sock, cli_sock;
    struct sockaddr_in srv_addr, cli_addr;
    socklen_t addr_len;
    int client_list[MAX_CLIENTS];
    int connected_count;
    int reuse=1;
    char message[MSG_BUF];
    int select_result;
    int i;
    
    // 소켓 생성
    if((srv_sock=socket(AF_INET,SOCK_STREAM,0))<0){
        perror("Socket creation failed");
        return 0;
    }
    
    // 주소 재사용 설정
    if(setsockopt(srv_sock,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(int))==-1){
        perror("setsockopt failed");
        return 0;
    }
    
    // 서버 주소 설정
    srv_addr.sin_family=AF_INET;
    srv_addr.sin_port=htons(SRV_PORT);
    srv_addr.sin_addr.s_addr=INADDR_ANY;
    memset(srv_addr.sin_zero,'\0',sizeof(srv_addr.sin_zero));
    
    // 바인딩
    if(bind(srv_sock,(struct sockaddr*)&srv_addr,sizeof(srv_addr))==-1){
        perror("Bind failed");
        return 0;
    }
    
    // 리슨
    if(listen(srv_sock,5)==-1){
        perror("Listen failed");
        return 0;
    }
    
    printf("Listening on port: %d\n",SRV_PORT);
    
    fd_set active_fds;
    int max_fd;
    struct timeval timeout;
    
    connected_count=0;
    addr_len=sizeof(cli_addr);
    max_fd=srv_sock;
    
    while(1){
        // 파일 디스크립터 집합 초기화
        FD_ZERO(&active_fds);
        FD_SET(srv_sock,&active_fds);
        
        // 타임아웃 설정
        timeout.tv_sec=15;
        timeout.tv_usec=0;
        
        // 클라이언트 소켓 추가
        for(i=0;i<MAX_CLIENTS;i++){
            if(client_list[i]!=0)
                FD_SET(client_list[i],&active_fds);
        }
        
        // select 호출
        select_result=select(max_fd+1,&active_fds,NULL,NULL,&timeout);
        if(select_result<0){
            perror("Select error");
            break;
        }
        else if(select_result==0){
            printf("Timeout occurred\n");
            continue;
        }
        
        // 기존 클라이언트 처리
        for(i=0;i<connected_count;i++){
            if(FD_ISSET(client_list[i],&active_fds)){
                select_result=recv(client_list[i],message,sizeof(message),0);
                
                if(select_result<=0){
                    printf("Client[%d] disconnected\n",i);
                    close(client_list[i]);
                    FD_CLR(client_list[i],&active_fds);
                    client_list[i]=0;
                }
                else{
                    printf("Client[%d] sent: %s\n",i,message);
                    send(client_list[i],message,sizeof(message),0);
                }
            }
        }
        
        // 새로운 연결 처리
        if(FD_ISSET(srv_sock,&active_fds)){
            cli_sock=accept(srv_sock,(struct sockaddr*)&cli_addr,&addr_len);
            if(cli_sock<=0){
                perror("Accept failed");
                continue;
            }
            if(connected_count<MAX_CLIENTS){
                client_list[connected_count++]=cli_sock;
                printf("New connection[%d] from %s: %d\n",connected_count,
                       inet_ntoa(cli_addr.sin_addr),ntohs(cli_addr.sin_port));
                if(cli_sock>max_fd)
                    max_fd=cli_sock;
            }
            else{
                send(cli_sock,"Server overloaded!",sizeof("Server overloaded!"),0);
                close(cli_sock);
                break;
            }
        }
    }
    
    // 모든 클라이언트 종료
    for(i=0;i<MAX_CLIENTS;i++){
        if(client_list[i]!=0)
            close(client_list[i]);
    }
}

Raw 소켓 프로그래밍

Raw 소켓은 네트워크 계층에서 직접 패킷을 조작할 수 있는 저수준 인터페이스입니다. 다음 기능이 가능합니다:

  • 네트워크 카드를 프로미스큐어스 모드로 설정하여 모든 패킷 스니핑
  • 사용자 정의 IP, ICMP, TCP, UDP 패킷 생성 및 전송
  • 새로운 프로토콜 검증

보안 관련 애플리케이션(루트킷, IP 스푸핑, DoS 공격, 패킷 스니핑)에도 활용됩니다.

Raw 소켓 생성

int raw_socket=socket(AF_INET, SOCK_RAW, htons(ETH_P_IP));
// IP 계층의 모든 데이터그램 수신 가능

// 프로토콜 코드 및 의미
/*
IPPROTO_ICMP - ICMP 프로토콜
ETH_P_IP - IP 프로토콜  
IPPROTO_TCP - TCP 프로토콜
IPPROTO_UDP - UDP 프로토콜
IPPROTO_IPV6 - IPv6 프로토콜
IPPROTO_EGP - EGP 프로토콜
*/

데이터 전송

Raw 소켓에서 데이터를 전송하기 전에 헤더 포함 옵션을 설정해야 합니다:

int header_include=1;
setsockopt(socket_fd, IPPROTO_IP, IP_HDRINCL, &header_include, sizeof(header_include));

예제: 패킷 캡처 및 분석

#include <stdio.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/types.h>
#include <linux/if_ether.h>
#include <linux/in.h>

#define PACKET_BUFFER 2048

int main(int argc, char **argv)
{
    int raw_sock;
    char buffer[PACKET_BUFFER];
    char *eth_header;
    char *ip_header;
    char *ptr_header;
    
    // Raw 소켓 생성
    if((raw_sock=socket(PF_PACKET,SOCK_RAW,htons(ETH_P_IP)))<0){
        printf("Raw socket creation failed!\n");
        exit(0);
    }
    
    long packet_counter =0;
    
    while(1){
        int bytes_received = recvfrom(raw_sock,buffer,2048,0,NULL,NULL);
        
        if(bytes_received<42){
            printf("Incomplete header received!\n");
            exit(0);
        }
        
        eth_header=(char*)buffer;
        ptr_header=eth_header;
        int mask=0XFF;
        packet_counter++;
        
        printf("---------------Packet Analysis[%ld]---------------\n",packet_counter);
        printf("MAC:");
        int i=6;
        for(;i<=11;i++)
            printf("%.2X:",ptr_header[i]&mask);
        printf("------->");
        for(i=0;i<=5;i++)
            printf("%.2X:",ptr_header[i]&mask);
        printf("\n");
        
        ip_header=eth_header+14;
        ptr_header=ip_header+12;
        
        printf("IP:");
        for(i=0;i<=3;i++){
            printf("%d",ptr_header[i]&mask);
            if(i!=3)
                printf(".");
        }
        printf("------->");
        for(i=4;i<=7;i++){
            printf("%d",ptr_header[i]&mask);
            if(i!=7)
                printf(".");
        }
        printf("\n");
        
        int protocol_type=(ip_header+9)[0];
        ptr_header=ip_header+20;
        
        printf("Protocol:");
        switch(protocol_type){
        case IPPROTO_ICMP:
            printf("ICMP\n");
            break;
        case IPPROTO_IGMP:
            printf("IGMP\n");
            break;
        case IPPROTO_IPIP:
            printf("IP");
            break;
        case IPPROTO_TCP:
            printf("TCP|Source Port: %u |",(ptr_header[0]<<8)&0XFF00|ptr_header[1]&0XFF);
            printf("Dest Port: %u\n",(ptr_header[2]<<8)&0XFF00|ptr_header[3]&0XFF);
            break;
        case IPPROTO_UDP:
            printf("UDP|Source Port: %u |",(ptr_header[0]<<8)&0XFF00|ptr_header[1]&0XFF);
            printf("Dest Port: %u\n",(ptr_header[2]<<8)&0XFF00|ptr_header[3]&0XFF);
            break;
        case IPPROTO_RAW:
            printf("RAW\n");
            break;
        default:
            printf("Unknown\n");    
        }
        printf("-----------------End--------------------");
    }
    
    return 0;
}

브로드캐스트 통신

ARP(Address Resolution Protocol)와 NTP(Network Time Protocol)는 브로드캐스트 통신의 대표적 예입니다.

ARP는 LAN 내에서 IP 주소에서 MAC 주소로의 매핑을 찾기 위한 프로토콜입니다. 호스트 A가 호스트 B와 통신하려 할 때 B의 IP만 알고 있다면, 전체 네트워크에 ARP 요청을 보내 해당 IP의 MAC 주소를 알아냅니다.

NTP는 네트워크 시간 동기화 프로토콜입니다. 브로드캐스트 지원 LAN에서 NTP 서버는 일정한 시간 간격으로 전체 네트워크에 시간 정보를 전송하고, 클라이언트는 이를 받아 시계를 동기화합니다.

브로드캐스트 주소 이해

  • 네트워크 브로드캐스트: 서브넷 분할이 없는 네트워크 전체 (거의 사용되지 않음)
  • 제한된 브로드캐스트: 255.255.255.255 (라우터가 전달하지 않음)
  • 서브넷 브로드캐스트: 특정 서브넷 내 (예: 192.168.1.255)
  • 모든 서브넷 브로드캐스트: 모든 서브넷 (예: 192.168.255.255)

브로드캐스트 구현 절차

  1. UDP 소켓 생성
  2. SO_BROADCAST 속성 설정
  3. 브로드캐스트 주소(INADDR_BROADCAST) 및 포트 지정
  4. 데이터 송수신 수행

브로드캐스트 서버 예제

#include <sys/types.h>
#include <stdio.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <errno.h>

#define BUFFER_SIZE 200
#define BCAST_PORT 5050

int main(int argc, char **argv)
{
    int server_sock;
    struct sockaddr_in srv_addr, cli_addr;
    int broadcast_enable=1;

    if((server_sock=socket(AF_INET,SOCK_DGRAM,0))<0){
        perror("socket creation failed");
        return 0;
    }
    
    if(setsockopt(server_sock,SOL_SOCKET,SO_BROADCAST,&broadcast_enable,sizeof(broadcast_enable))<0){
        perror("setsockopt failed");
        return 0;
    }
    
    srv_addr.sin_family=AF_INET;
    srv_addr.sin_port=htons(INADDR_ANY);
    srv_addr.sin_addr.s_addr=htonl(INADDR_BROADCAST);
    
    if(bind(server_sock,(struct sockaddr*)&srv_addr,sizeof(struct sockaddr))<0){
        perror("bind failed");
        return 0;
    }
    
    cli_addr.sin_family=AF_INET;
    cli_addr.sin_port=htons(BCAST_PORT);
    cli_addr.sin_addr.s_addr=htonl(INADDR_BROADCAST);
    
    while(1){
        char msg[BUFFER_SIZE];
        printf("Enter message:");
        scanf("%s",msg);
        if(sendto(server_sock,msg,strlen(msg),0,(struct sockaddr*)&cli_addr,sizeof(cli_addr))<0){
            perror("sendto failed");
            return 0;
        }
        else
            printf("Message sent: %s\n",msg);
    }
    
    return 0;
}

브로드캐스트 클라이언트 예제

#include <sys/types.h>
#include <stdio.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <errno.h>

int main(int argc, char **argv)
{
    int client_sock;
    struct sockaddr_in srv_addr, cli_addr;
    
    client_sock=socket(AF_INET,SOCK_DGRAM,0);
    
    srv_addr.sin_family=AF_INET;
    srv_addr.sin_port=htons(5050);
    srv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    
    int reuse_opt=1;
    if(setsockopt(client_sock,SOL_SOCKET,SO_REUSEADDR,&reuse_opt,sizeof(reuse_opt))<0){
        perror("setsockopt failed");
        return 0;
    }
    
    if(bind(client_sock,(struct sockaddr*)&srv_addr,sizeof(struct sockaddr))!=0){
        perror("bind failed");
        return 0;
    }
    
    char buffer[200];
    
    while(1){
        memset(buffer,0,200);
        int size=0;
        size=recvfrom(client_sock,buffer,200,0,(struct sockaddr*)&srv_addr,sizeof(srv_addr));
        buffer[size]='\0';
        printf("From IP:%s Message:%s\n",inet_ntoa(cli_addr.sin_addr),buffer);
        
        if(strcmp(buffer,"quit")==0){
            printf("Terminating...\n");
            close(client_sock);
            return 0;
        }
    }
    
    return 0;
}

멀티캐스트 통신

멀티캐스트는 브로드캐스트의 변형으로, 송신자와 다수의 수신자 간의 점대다점 연결을 제공합니다.

IP 주소 체계에서 D 클래스 주소(224.0.0.0 ~ 239.255.255.255)가 멀티캐스트 주소로 사용됩니다. 이 중 224.0.0.0 ~ 224.0.0.255는 특수 목적용으로 예약되어 있어 사용하지 않는 것이 좋습니다.

소켓 속성 설정

멀티캐스트 관련 파라미터는 5개이며, setsockopt를 통해 설정합니다:

// 멀티캐스트 그룹 참여
int setsockopt(client_sock,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mcast_addr,sizeof(mcast_addr))

// 멀티캐스트 그룹 탈퇴
int setsockopt(client_sock,IPPROTO_IP,IP_DROP_MEMBERSHIP,&mcast_addr,sizeof(mcast_addr))

// 중요한 파라미터 mcast_addr 구조체:
struct ip_mreq{
    struct in_addr imr_multiaddr;   // 멀티캐스트 주소
    struct in_addr imr_interface;   // IPv4 인터페이스 주소
}

구현 절차

  1. 서버: 멀티캐스트 주소 설정 및 그룹 생성
  2. 클라이언트: 멀티캐스트 주소 지정 후 참여
  3. 프로그램 종료 시 그룹 탈퇴

멀티캐스트 서버 예제

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char **argv)
{
    int server_socket;
    struct sockaddr_in address;
    
    // UDP 소켓 생성
    server_socket=socket(AF_INET,SOCK_DGRAM,0);
    if(server_socket<0){
        perror("socket creation failed");
        return 0;
    }
    
    // 멀티캐스트 주소 초기화
    memset(&address,0,sizeof(address));
    address.sin_family=AF_INET;
    address.sin_port=htons(5555);
    address.sin_addr.s_addr=inet_addr("224.0.1.100");
    
    // 메시지 전송
    while(1){
        char msg[200];
        printf("Input message:");
        scanf("%s",msg);
        if(sendto(server_socket,msg,sizeof(msg),0,(struct sockaddr*)&address,sizeof(address))<0){
            perror("sendto failed");
            return 0;
        }
    }
    
    return 0;
}

멀티캐스트 클라이언트 예제

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char **argv)
{
    struct ip_mreq mreq;
    int addr_len;
    int client_socket;
    struct sockaddr_in server_address;
    
    // 주소 초기화
    memset(&server_address,0,sizeof(server_address));
    server_address.sin_family=AF_INET;
    server_address.sin_port=htons(5555);
    server_address.sin_addr.s_addr=htonl(INADDR_ANY);
    
    if((client_socket=socket(AF_INET,SOCK_DGRAM,0))<0){
        perror("client socket creation failed");
        return 0;
    }
    
    // 소켓 바인딩
    if(bind(client_socket,(struct sockaddr*)&server_address,sizeof(server_address))<0){
        printf("bind failed");
        return 0;
    }
    
    int reuse_option=1;
    if(setsockopt(client_socket,SOL_SOCKET,SO_REUSEADDR,&reuse_option,sizeof(reuse_option))<0){
        printf("setsockopt1 failed");
        return 0;
    }
    
    // 멀티캐스트 참여
    mreq.imr_multiaddr.s_addr=inet_addr("224.0.1.100");
    mreq.imr_interface.s_addr=htonl(INADDR_ANY);
    
    if(setsockopt(client_socket,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq))<0){
        perror("setsockopt2 failed");
        return 0;
    }
    
    while(1){
        char msg_buffer[200];
        addr_len=sizeof(server_address);
        if(recvfrom(client_socket,msg_buffer,200,0,(struct sockaddr*)&server_address,(socklen_t *)addr_len)<0){
            perror("recvfrom failed");
        }
        printf("Server message: %s\n",msg_buffer);
        
        if(strcmp(msg_buffer,"quit")==0){
            if(setsockopt(client_socket,IPPROTO_IP,IP_DROP_MEMBERSHIP,&mreq,sizeof(mreq))<0){
                perror("setsockopt3 failed");
            }
            close(client_socket);
            return 0;
        }
    }
    
    return 0;
}

태그: linux network-programming sockets select poll

8월 7일 10:47에 게시됨