Systems/Unix / Linux2012.06.07 10:34

RAW 소켓을 사용한 패킷 전송 및 수신시 기본적으로 알아야 할 사항은 다음과 같다.


1. IP 헤더를 직접 조작할 것이냐

2. 어떤 프로토콜 번호를 사용할 것이냐

3. 어떤 API를 사용하여 패킷을 전송하고 수신할 것이냐


이상의 사항만 숙지하면 RAW 소켓을 사용하여 패킷을 주고 받는 것도 생각보다는 간단하다. 


먼저 전송하는 소스 코드를 보자



구글에서 RAW 소켓을 이미지 검색하면 뜨는 사진. 소켓 공부하다가 이런 사진을 볼 수도 있다니.. 좋은 세상이다... ㅋㅋ



#include <sys/socket.h>

#include <unistd.h>

#include <stdio.h>

#include <stdlib.h>

#include <arpa/inet.h>

#include <netinet/ip.h>


u_int16_t get_checksum(u_int16_t* buf, int nwords)

{

    u_int32_t sum;

    for(sum=0; nwords>0; nwords--) sum += *buf++;

    sum = (sum >> 16) + (sum & 0xffff);

    sum += (sum >> 16);

    return (u_int16_t)(~sum);

}


int main(int argc, const char** argv)

{

    /*

     * create a raw socket

     */

    int sd = socket(PF_INET, SOCK_RAW, 145 /* not used */);

    if ( sd < 0 ) {

        perror("socket() error");

        return -1;

    }


    int turn_on = 1;

    int turn_off = 0;


    /*

     * I will manipulate IP header myself

     */

    if ( setsockopt(sd,IPPROTO_IP,IP_HDRINCL,&turn_on,sizeof(turn_on)) < 0 ) {

        perror("setsockopt() error");

        return -1;

    }


    struct iphdr iphdr;

    struct sockaddr_in din;


    din.sin_family = AF_INET;

    din.sin_port = 0;

    din.sin_addr.s_addr = inet_addr("129.254.198.212");


    iphdr.ihl = 5;

    iphdr.version = 4;

    iphdr.tos = 0;

    iphdr.tot_len = htons( sizeof(struct iphdr) );

    iphdr.id = htons(rand() % 65535);

    iphdr.frag_off = 0;         

    iphdr.ttl = 64;

    iphdr.protocol = 145;

    iphdr.saddr = inet_addr("129.254.173.145");

    iphdr.daddr = inet_addr("129.254.198.212");

    // iphdr.check = get_checksum((u_int16_t*)&iphdr, sizeof(struct iphdr));

    // IP_HDRINCL인 경우에는 checksum 계산 필요 없음. 하위 layer에서 시행됨.


    if ( sendto(sd, &iphdr, sizeof(iphdr), 0,

            (struct sockaddr*)&din, sizeof(din)) < 0 ) {

        perror("sendto() error");

        return -1;

    }


    return 0;

}


이 코드에서 주의하여 볼 부분은 적색의 굵은 폰트로 표시하였다. 우선 (1) 소켓을 열 때 SOCK_RAW를 사용하여 열어야 하고, 프로토콜 번호로는 현재 시스템에서 사용하고 있지 않은 프로토콜 번호를 준다. 이 번호는 /etc/protocols 파일을 살펴보면 알 수 있는데, 현재 145는 사용되고 있지 않은 번호이므로 테스트를 위해 그 번호를 사용했다. 그리고 (2) setsockopt 함수를 사용해서 IP 헤더를 직접 조작하겠다는 설정을 하고 있다. 이렇게 설정한 경우, 전송할 패킷의 IP 헤더는 직접 구성해 주어야 한다.


나머지 코드는 아주 간단하기 짝이 없는데, 한 가지 유의할 부분은 sendto 대신 send를 사용해서 보내면 패킷이 전송되지 않는다는 것. "Destination address required"와 같은 오류 메시지가 출력될 것이다. IP 헤더에 목적지 주소를 적어주었음에도 굳이 sockaddr_in 구조체를 인자로 전달해 주어야 한다는 것은 분명 비효율적이고 이해되지 않는 부분이지만, 리눅스 시스템 콜이 그렇게 생겨 먹었으니 어쩔 수 없는 부분이다. 


받는 쪽 소스 코드는 이보다 더 간단한데 (IP 헤더 말고 다른 데이터를 전송하지 않도록 했기 때문에 더더욱 그러하다) 소켓 bind를 할 필요도 없고, listen 할 필요도 없고, accept 할 필요도 없기 때문이다. 그냥 recv를 하면 된다. 


#include <sys/socket.h>

#include <unistd.h>

#include <stdio.h>

#include <arpa/inet.h>

#include <netinet/ip.h>

#include <strings.h>


int main(int argc, const char** argv)

{

    /*

     * create a raw socket

     */

    int sd = socket(PF_INET, SOCK_RAW, 145);

    if ( sd < 0 ) {

        perror("socket() error");

        return -1;

    }


    char recvPacket[1024] = {0, };

    int pkt_size = 0;

    if ( (pkt_size = recv(sd, recvPacket, 1024, 0)) < 0 ) {

        perror("recvfrom() failed");

        return -1;

    }


    printf("read succeeded");


    printf("pkt_size = %d\n", pkt_size);

    struct iphdr* iphdr = (struct iphdr*)&recvPacket[0];

    printf("iphdr.ihl = %d\n", iphdr->ihl);

    printf("iphdr.version = %d\n", iphdr->version);

    printf("iphdr.tos = %d\n", iphdr->tos);

    printf("iphdr.tot_len = %d\n", htons(iphdr->tot_len));

    printf("iphdr.frag_off = %d\n", iphdr->frag_off);

    printf("iphdr.ttl = %d\n", iphdr->ttl);

    printf("iphdr.protocol = %d\n", iphdr->protocol);


    return 0;

}


패킷을 보낼 때 굳이 sendto를 통해서 보내도록 하고 있음에도, recvfrom을 하면 뭔가 알 수 없는 오류가 생긴다는 것은 이상한 일이다. "Invalid argument"와 같은 오류가 발생한다는 것. recvfrom 대신 recv를 하면 정상적으로 동작한다. 




저작자 표시 비영리 변경 금지
신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

  1. 정보보안

    저 코드를 컴파일해서 사용하면되는건가요? 공부중인데 저 코드를 어떻게 쓰는건가요? 그냥 컴파일 후 바이너리 파일로 만들고 대상 PC에서 실행하면 되는건가요?

    2013.10.11 20:47 신고 [ ADDR : EDIT/ DEL : REPLY ]