Raw Socket

From YYpBD's MediaWiki

Jump to: navigation, search
- 제목    :로소켓만들기          
- 이야기꾼:이기천(hetta@nownuri.net)

안녕하세요?
이번에 하게될 이야기는  Raw socket Programing에 대한것입니다.
제가 초보라서 잘 모르는부분이 많습니다.이해하시기 바랍니다.^^
이번 이야기의 재료는 Thamer Al-Herbish 님이 만드신 [rawsock-0.5] 입니다.
이 소스를 한줄 한줄 분석하며 제가 토를 달겠습니다.
먼저, 여러분이 네트워크 프로그래밍에 대한 기본적인 지식은 있다는 가정에서 이야기
를 시작합니다.  만약 여러분이 네트워크 프로그래밍에 대한  지식이 없다면,
이번이야기는 여러분께 부담이 될지도 모르겠습니다.^^
http://kldp.org에가시면 소켓에대한 좋은글이 있습니다.이글을 꼭 읽어보시고
또한 tcp/ip에대한 책중의 헤더부분을 설명한 곳을 읽어보시길 추천합니다.
이번 이야기의 소스는 자료실에 올렸습니다. 여기 자료실에 올려진 자료는 원래의
소스에서 리눅스에서 컴파일 안되는 부분을 뺀것임을 밝혀둡니다.(그냥 #ifndef 이런데
에서 리눅에 필요한거만 간추린것에 불과합니다. 소스 만드신 분한테 말안하구 이런
거해두 괜찮을라나....쿠쿠...)
서론이 길었습니다..
이제 여름 무더위를 말끔하게 씻어줄 시원시원한  raw socket 프로그래밍으로의
여행을 떠나봅시다.~!!!.

먼저 raw socket이 무엇인지 간단히 언급하겠습니다. 먼저 사전적인 의미는
-----------------------------------------------------------------------
[root@super /root]# edic
찾는 단어: raw
[ raw ]
날것의, 덜익은(구운), 가공하지 않은, 원료그대로의, 다루지 않은, 정제하지
않은(중략)
-----------------------------------------------------------------------
이군여.~~~~~ 그니까 뭐..날것의 소켓, 덜익은 소켓,가공하지 않은 소켓, 뭐
이쯤 되겠죠? 이말이 무엇이냐하면 지금까지 여러분이 네트워크 프로그래밍을 하면서
ip헤더와 tcp헤더 udp헤더 icmp 헤더 등등에 쓰여지는것에대해서 별로 신경을 쓰지 않
았습니다. 왜냐하면 복잡한것들을  커널에서 자동으로 붙여주기 때문입니다.
그냥 클라이언트라면 간단히 말해서..

  - socket
  - connect

하고 send 등등을 써서 통신을 했고...
서버라면

  -socket
  -bind
  -listen
  -accept

이런것들을 사용함으로서 우리는 전혀 내부적으로 어떻게 돌아가는지 알필요없이 프로
그래밍을 할수있었습니다.
하지만 raw socket에서는 이러한 헤더들을 우리가 만들어야합니다.
rawsocket은  커널이 하는일을 우리가 하는것입니다. 간단히 개요만 말하자면
ip헤더를 우리가 만들고, tcp헤더를 우리가 만들어서, sendto를 호출함으로서
우리가 원하는 패킷을  만드는것이지요.
예를 들어 유닉스에 있는 traceroute라는 프로그램을 어떻게 길을 찾을까요?그것은
TTL(time to live)값을 처음에 1로 세팅한 패킷을 방출합니다. 그러면 그 패킷은
첫번째 라우터에가서 더이상 나아갈수없으므로, 그 라우터에서는 "이 패킷은 더이상
갈수가 없습니다"이러한 비스므래한 메세지를 패킷을 만든 호스트에 전송합니다.
이것으로 첫번째길을 알수있겠죠...그다음엔 TTL값을 2로 세팅한것을 방출합니다.
이것은 두번째 라우터 에러 메세지를 발생시키고, 이것으로 두번째 길을 알수있겠죠.
이 프로그램 말고도  rawsocket을 이용한 프로그램은 많이 있습니다.

먼저 소스를 다운받아서 한번 설치해볼까여......?
다 아시겠지만...
tar -xvf raw_lnx.tar
하면 하나의 디렉토리가 생성되고 그안에 화일이 풀리죠?
그 디렉토리에 들어가서...그냥 make 만 하십시요...
이제 컴파일이 되었습니다. 실행화일이 3개 나오지요? tcp, udp, icmp
이중에 tcp 를 분석해봅시다. 이거 하나만 하면 나머지는 거의 똑같습니다.
프로그램 사용법은
./tcp 7 192.168.0.1 7 192.168.0.2 이렇게했다면 192.168.0.1  7번 포트에서
192.168.0.2 7번 포트로 가는 하나의 패킷이 만들어진것입니다.
그패킷은 tcpdump 출력 결과로 알수가 있겠죠?
tcpdump -i eth0                        file://eth0 는 여러분의 네트워크 장치입니다.  

tcp 를 만들려면 필요한 화일은 Makefile 에보면..
tcp.c in_cksum.c tcp_gen.c ip_gen.c trans_check.c 이죠?

앞에 ! 표가 있는곳이 소스임을 뜻합니다.
분석의 순서는 in_cksum.c  trans_check.c ip_gen.c tcp_gen.c 마지막으로
tcp.c를 분석하겠습니다. 각개격파~~ 입니다. 하나하나 분석해보죠...
-------------------------in_cksum.c -------------------------------------
이 체크섬루틴은 ip, tcp 둘다 쓰는것입니다.
이것이 하는역활은 패킷이 과연 온전하게 아무 이상없이 상대방에게 전달되었는지를
밝히는것입니다.패킷이 잘려지거나 변조되면 이 값이 틀려져서, 상대방은
이 체크섬을 보고 패킷이 온전하지 않다는걸 알고, 이러한 패킷을 거부해버립니다.
매우 중요한 값입니다.

!#include <stdlib.h>
!#include <stdio.h>
!#include <sys/types.h>
이러한 헤더화일이 있어야겠죠...

!unsigned short in_cksum(unsigned short *addr,int len)
!{
!        register int sum = 0;
register로 선언하면 속도가 빨라집니다.

!        u_short answer = 0;
anster는 리턴되는값이지요? 일단 0으로 초기화를 합니다.

!        register u_short *w = addr;
w라는 포인터 변수를 인수로 받은 addr로 초기화 하고있습니다.
addr자제가 바뀌면 곤란하겠죠? 그래서 이런과정이 필요한것입니다.

!        register int nleft = len;
nleft라는 변수는 인수로받은 길이로 초기화 되고있습니다.nleft는 말그대로 이제
읽을것이 얼마나 남았느지를 표시하는것입니다.

!        while (nleft > 1)  {
!                sum += *w++;
!                nleft -= 2;
!        }
만약 nleft가 남아있다면 sum에 *w가 가리키는 값을 계속 더하는 것입니다.

!        if (nleft == 1) {
!                *(u_char *)(&answer) = *(u_char *)w ;
!                sum += answer;
!        }
nleft가 1과 같다면,즉 읽어야할것이 짝수가 아니면, 그 값또한 더해주어야합니다.

!        sum = (sum >> 16) + (sum & 0xffff);    
더한것중 상위 16비트와 하위 16비트를 더하는것입니다.

!        sum += (sum >> 16);                    
만약 carry비트(즉 자리올림수)가 있으면 그값도 더해주어야합니다.

!        answer = ~sum;                        
~ 에 의해서 비트수가 반전되는것입니다. 즉,11110000이었다면 00001111이됩니다.

!        return(answer);
answer 을 리턴합니다.

!}
-----------------------------in_cksum.c end ------------------------------
다시한번 체크섬루틴을 정리해보겠습니다.
    1.먼저 입력받은것을 short형으로 해서 전부더한다.
      (만약에 nleft가 1이남으면.. 그것두 sum에 더한다 )
    2.그다음 상위 16비트와 하위 16비트를 더한다
      (자리올림수가 있다면 그것도 더한다.)
    3.그다음 ~을 이용하여서 반전시킨다.~~!!!!

이것으로서 in_chsum.c를 모두 알아보았습니다.



다음은 trans_check.c를 알아봅시다.
trans_check()함수는 체크섬을 계산하기위한 가상헤더(psuedo header) 를
만들기위한것입니다.
--------------------------- trans_check.c ---------------------------------
!#include <stdlib.h>
!#include <stdio.h>
!#include <sys/types.h>
!#include <sys/socket.h>
!#include <netinet/in.h>
!#include <arpa/inet.h>
!#include <netinet/in_systm.h>
!#include <netinet/ip.h>
!#include <netinet/udp.h>
!#include <netinet/tcp.h>
!#include <errno.h>
!#include <string.h>
!#include "checksum.h"
이러한 헤더화일이 필요하겠죠?

!struct psuedohdr  {
!  struct in_addr source_address;
!  struct in_addr dest_address;
!  unsigned char place_holder;
!  unsigned char protocol;
!  unsigned short length;
!} psuedohdr;
체크섬의 계산을 위해서 가상헤더를 만드는것입니다.
psuedo header 는 아래와 같은 모양입니다.위에서 선언한것과 똑같지요?
-------------------------------------------------------------------------
|                                                                       |
|                       32-bit source IP address                        |
|                                                                       |
-------------------------------------------------------------------------
|                                                                       |
|                       32-bit destination IP address                   |
|                                                                       |
-------------------------------------------------------------------------
|              |                  |                                     |
|    zero      |   8-bit protocol |          16-bit UDP length          |  
|              |                  |                                     |
-------------------------------------------------------------------------

!unsigned short trans_check(unsigned char proto,
!              char *packet,
!              int length,
!              struct in_addr source_address,
!              struct in_addr dest_address)
여기서 char *packet은 tcp헤더 를 받고있다는것을 tcp.c에서 알수있습니다.
  
!  {
!  char *psuedo_packet;
가상헤더인 psuedo_packet을 선언합니다.

!  unsigned short answer;
answer 값은 리턴될 값이지요.

!  psuedohdr.protocol = proto;
!  psuedohdr.length = htons(length);
!  psuedohdr.place_holder = 0;
!
!  psuedohdr.source_address = source_address;
!  psuedohdr.dest_address = dest_address;
pauedohdr는 위에서 구조체를 선언할때 함께 선언한 변수입니다. 이값을 인수로
받은 수에 의해서 채웁니다.


!  if((psuedo_packet = malloc(sizeof(psuedohdr) + length)) == NULL)  {
!    perror("malloc");
!    exit(1);
!  }
포인터 변수인 psuedo_packet을 위한 공간을 마련합니다. psuedohdr와.
인수로 받은 tcp의 길이를 채워야되기때문입니다.

!  memcpy(psuedo_packet,&psuedohdr,sizeof(psuedohdr));
psuedo_packet에 아까 만든 가상해더 psuedohdr을 복사합니다.

!  memcpy((psuedo_packet + sizeof(psuedohdr)),
!    packet,length);
인수로 받은packet을 psuedo_packet에 복사합니다.

!  
!  answer = (unsigned short)in_cksum((unsigned short *)psuedo_packet,
!                   (length + sizeof(psuedohdr)));
체크섬계산을 위한 psuedo_packet이 완성되었습니다. 이제 in_cksum()만 호출
하면 됩니다.

!  free(psuedo_packet);
할당된 메모리를 헤제시켜줍니다.

!  return answer;
answer를 리턴합니다.

!}
-------------------------------trans_check.c-------------------------------
이것으로 trans_check()함수도 알아보았습니다. 요약하자면 psudo_packet 포인터
변수를 선언하여서 거기에 psuedohdr과 인수로받은 tcp헤더를 넣고, in_cksum()을
호출하고 그 값을 리턴합니다.


자. 이제 ip_gen.c 화일을 알아봅시다.
이함수는 ip 헤더를 채워주는 함수입니다.

iphdr구조체의 구조를 알아보겠습니다.
/usr/include/linux/ip.h를 보면 알수있습니다.
struct iphdr {
  unsigned char     ihl:4,
            version:4;
  unsigned char     tos;
  unsigned short    tot_len;
  unsigned short    id;
  unsigned short    frag_off;
  unsigned char     ttl;
  unsigned char     protocol;
  unsigned short    check;
  unsigned long     saddr;
  unsigned long     daddr;
  /*The options start here. */
};

ip헤더를 그림으로 알아보면.....


0                8 9              15 16                                  31
---------------------------------------------------------------------------
|        | header |                 |                                     |
| version| length |  type of service|    total length                     |
---------------------------------------------------------------------------
|                                   |       |                             |
|     identification                | flags |      fragment offset        |
---------------------------------------------------------------------------
|                 |                 |                                     |
| time to live    |    protocol     |          header checksum            |
---------------------------------------------------------------------------
|                                                                         |
|                            source Ip address                            |
---------------------------------------------------------------------------  
|                                                                         |
|                            destination IP address                       |
---------------------------------------------------------------------------              
ip 헤더를 만든다는것은 단순히... 여기있는 이 공란들을 채우는데 불과합니다.~~
ip헤더는 옵션이 없다고할때 20byte입니다.
이제 하나하나 알아봅시다.
------------------------------ ip_gen.c ------------------------------------
!#include <stdlib.h>
!#include <stdio.h>
!#include <sys/types.h>
!#include <sys/socket.h>
!#include <netinet/in.h>
!#include <arpa/inet.h>
!#include <netinet/in_systm.h>
!#include <netinet/ip.h>
!#include <unistd.h>
!#include <string.h>
!#include "checksum.h"
이런 헤더화일들이 필요하겠죠?

!#define IPVERSION 4  
현재 ip버전은 4입니다.

!#define DEFAULT_TTL 60  /* Just hard code the ttl in the ip header.*/
ttl값을 64로 합니다.

!void ip_gen(char *packet,unsigned char protocol,struct in_addr saddr,
!       struct in_addr daddr,unsigned short length)
!{
!  struct iphdr *iphdr;
채워나갈 iphdr 포인터 구조체를 선언합니다.

!  iphdr = (struct iphdr *)packet;
iphdr포인터를 인수로 받은 packet과 같은 곳을 가리키게합니다.

!  memset((char *)iphdr,'\0',sizeof(struct iphdr));
iphdr포인터 변수를 0으로 초기화합니다.

!  iphdr->ihl = 5;
ip header length 를 5로 합니다.

!  iphdr->version = IPVERSION;
버전은 위에서 define한대로 4로 합니다.

!  iphdr->tot_len =length;   /* MUST */
total length는 인수로 받은수로 합니다. htons를 써서는안됩니다. BSD 에서 유래된
시스템에서는 이 totoal length부분을 host order로 해야 된다는군여....


!  iphdr->id = htons(getpid());
identification 을 넣습니다. 이값은 각각의 패킷을 구분할때 쓰는것입니다.만약 패킷
이 잘려 졌다면 이값으로 같은 패킷을 구분합니다. 보통 커널에서는 이값을 각각의
패킷마다 1씩 증가시킵니다.

!  iphdr->ttl = DEFAULT_TTL;
time to live 값을 60으로 입력합니다.

!  iphdr->protocol = protocol;
protocol을 인수로 받은 값으로 넣습니다.

!  iphdr->saddr = saddr.s_addr;
!  iphdr->daddr = daddr.s_addr;
발신지와 목적지 주소를 넣습니다.

!  iphdr->check = (unsigned short)in_cksum((unsigned short *)iphdr,
!                     sizeof(struct iphdr));
체크섬을 계산해서 넣습니다.

!  return;
!}

------------------------------ip_gen.c end -------------------------------


자 이제 tcp_gen.c을 알아봅시다.
이 구조체의 구조는
/usr/include/linux/tcp.c에서 선언하고있습니다.
struct tcphdr {
  unsigned short    source;
  unsigned short    dest;
  unsigned long     seq;
  unsigned long     ack_seq;
  unsigned short    res1:4,
            doff:4,
            fin:1,
            syn:1,
            rst:1,
            psh:1,
            ack:1,
            urg:1,
            res2:2;
  unsigned short    window;
  unsigned short    check;
  unsigned short    urg_ptr;
};

tcp헤더를 그림으로 알아보면....
1                                15   16                              31
-------------------------------------------------------------------------
|                                   |                                   |
|     source port number            |      destination port number      |
-------------------------------------------------------------------------
|                                                                       |
|                    sequence number                                    |
-------------------------------------------------------------------------
|                                                                       |
|                         acknowledgment number                         |
-------------------------------------------------------------------------
| header |              |           |                                   |
|  length|  reserved    |  FLAGES   |     window size                   |
-------------------------------------------------------------------------
|                                   |                                   |
|       TCP checksum                |          urgent pointer           |
-------------------------------------------------------------------------
FLAGES 는 좌로부터 urg ack psh rst sun fin 입니다.

이제부터 하는일은 단순히 이것들을 채우는것입니다.

------------------------------tcp_gen.c ----------------------------------
!#include <stdlib.h>
!#include <stdio.h>
!#include <sys/types.h>
!#include <sys/socket.h>
!#include <netinet/in.h>
!#include <arpa/inet.h>
!#include <netinet/in_systm.h>
!#include <linux/tcp.h>
!#include <unistd.h>
!#include <string.h>
이런 헤더화일들이 필요하겠죠?

!#define TH_OFFSET 5         /* TCP header offset */
!#define TCP_WINDOW_SIZE 512 /* Just have it hardcoded. */
tcp hedaer offset 을 5로 , window_size를 512로 정의하고있군요.

!void tcp_gen(char *packet,unsigned short sport,
!        unsigned short dport,unsigned long seq,
!        unsigned long ack)
!{
!  struct tcphdr *tcp;
tcp 포인터 구조체를 선언하고있습니다.  

!  tcp = (struct tcphdr *)packet;
tcp포인터 구조체 변수를 인수로 받은 packet으로 초기화합니다.
!  memset((char *)tcp,'\0',sizeof(struct tcphdr));
tcp를 0으로 채웁니다.

!  tcp->source = htons(sport);
!  tcp->dest = htons(dport);
발신지와 목적지 주소를 넣습니다.네트워크 순서로 넣어야겠죠?

!  tcp->seq = htonl(seq);
!  tcp->ack_seq = htonl(ack);
계산한 seq 와 ack를 넣습니다.

!  tcp->res1 = 0;
!  tcp->doff = TH_OFFSET;
reserved에 0을 넣고 tcp header length 에 5를 넣습니다.

!  tcp->window = htons(TCP_WINDOW_SIZE);
윈도우 사이즈를 선언한 60으로 넣습니다.
!  tcp->fin = 1;
fin flag를 세팅합니다.

!  return;
!}

----------------------------- tcp_gen.c ----------------------------------


---------------------------------------tcp.c------------------------------
/* Generate a TCP packet
* with a FIN flag and pass it through a raw socket.
*
* Thamer Al-Herbish shadows@whitefang.com
*/

이프로그램은 fin flag를 세팅해서 raw socket에 보내는거군요..
! #include <stdlib.h>
! #include <stdio.h>
! #include <sys/types.h>
! #include <sys/socket.h>
! #include <netinet/in.h>
! #include <arpa/inet.h>
! #include <netinet/in_systm.h>
! #include <linux/ip.h>
! #include <linux/tcp.h>
! #include <string.h>
! #include <unistd.h>
! #include <time.h>
! #include <rawsock_utils.h>
이러한 헤더 화일들이 필요하겠죠?  

! int main(int argc,char *argv[])
! {
!   unsigned char packet[
!   sizeof(struct iphdr) +
!   sizeof(struct tcphdr)];
packet이라는  배열은  우리가 최종적으로 보내개될 패킷입니다.이제부터 하는일이
이 패킷이라는 배열을 채우는일이지요...  크기는 40byte겠죠? ( ip 헤더,
tcp 헤더가 각각 20byte 이므로....)    

!   struct sockaddr_in mysocket;
!   unsigned short sport, dport;
!   struct in_addr saddr, daddr;
!   struct tcphdr *tcp;
!   unsigned long seq, ack;
!   int sockd, on = 1;
이러한 구조체 변수등을 설정해주고......

!   if(argc < 5)  {
!       fprintf(stderr,"usage: %s source_port source_address dest_port\
! dest_address\n", argv[0]);
!     exit(1);
!   }
아까 예를 들었듯 4개의 인수보다 적게 인수가 들어오면 중단시키는 루틴이죠?

!   sport = (unsigned short)atoi(argv[1]);
첫번째 인수를 sport 즉 소스측 포트에 설정하공있죠?

!   saddr.s_addr = inet_addr(argv[2]);
두번째 인수를 inet_addr 이 함수를 거쳐서 발신지  주소에 입력하고있군요.
inet_addr는 우리가 쓰는 192.168.0.1이런것들(dotted decimal) 인터냇에
보낼수있게 변환시킴니다.

!   dport = (unsigned short)atoi(argv[3]);
아까와 똑같이 3번째 인수를 목적지측 포트에 설정하고있죠? atoi는 문자를 숫자로
변환시키는 함수입니다.

!   daddr.s_addr = inet_addr(argv[4]);
두번째 인수를 inet_addr 이 함수를 거쳐서 목적지 주소에 입력하고있군요. inet_addr
는우리가 쓰는 192.168.0.2이런것들(dotted decimal) 인터냇에 보낼수있게
변환시킴니다.


!   if((sockd = socket(AF_INET,SOCK_RAW,IPPROTO_RAW)) < 0)  {
!     perror("socket");
!     exit(1);
!   }
소켓 지사자(descriptor)를 만들고있죠?
       int socket(int domain, int type, int protocol);
raw를 만들때는 type을 SOCK_RAW로 해주어야만 합니다.

!  
!   if(setsockopt(sockd,IPPROTO_IP,IP_HDRINCL,(char *)&on,sizeof(on)) < 0)  {
!     perror("setsockopt");
!     exit(1);
!   }
여기서 소켓 옵션으로 IP_HDRINCL을 주면 우리가 헤더들을 만들수 있게되는것입니다.
       int setsockopt(int s, int level, int optname,  const  void
       *optval, int optlen);
IP_HDRINCL의 값으로 &on을 넣고있죠. *optval이기 때문에 그냥 on을 넣으면
안됩니다. &on으로 해야겠죠^^




!   srand(getpid());
이제 sequence number를 만들기위해서 프로세스 아이디를 씨앗으로 합니다.

!  
!   seq = rand()%time(NULL);
time()이라는 함수는 1970.1.1에서부터 지금까지 흘러운 초를 리턴하는 함수이죠?
발생된 난수를 time()함수의 리턴값으로 나누어 그 나머지가 sequence number(이하
seq)가 되는것입니다.

!   ack = rand()%time(NULL);
acknowledge(이하 ack) number 또한 seq와 똑같은 방법으로 만듭니다.

!   ip_gen(packet,IPPROTO_TCP,saddr,daddr,sizeof(packet));
자! 그다음에 아까 packet배열과 tcp프로토콜, 발신지주소,목적지주소,packet배열의
크기를 인수로해서 ip 헤더를 만드는 함수를 호출합니다.
이함수가 하는일은 ip헤더를 채우는겁니다.(20byte)

!  
!   tcp = (struct tcphdr *)(packet + sizeof(struct iphdr));
!
이제 앞에서 정의한(struct tcphdr *tcp)  tcp구조체 포인터를 iphdr만큼
이동시킵니다. 왜 이동시킬까요? 이제 tcp헤더를 만들어서 써넣어야하기 때문이지요.

!   tcp_gen((char *)tcp,sport,dport,seq,ack);
이제 tcp구조체 포인터, 발신지 포트번호, 목적지 포트번호,seq,ack를 인수로해서
tcp헤더를 만드는 함수를 호출하는겁입니다.

!  
!   tcp->check = trans_check(IPPROTO_TCP,(char *)tcp,
!                sizeof(struct tcphdr),
!                saddr,
!                daddr);
tcp chucksum을 만들어야겠죠? ip 나 tcp나 checksum 루틴은 똑같습니다.


!  
!   memset(&mysocket,'\0',sizeof(mysocket));
!  
!   mysocket.sin_family = AF_INET;
!   mysocket.sin_port = htons(dport);
!   mysocket.sin_addr = daddr;
이제 아까 정의한 ( struct sockaddr_in mysocket ) mysocket을 만드는거지요..
이것은 지금까지 해본 프로그래밍과 똑같이 하면됩니다. memset은 mysocket을
전부 0으로 채우는것입니다. 그리고 sin_family는 AF_INET로 하고 sin_port는
목적지 포트로 넣어좁니다.htons는  host to network short 호스트에서 네트워크로
나갈때의 바이트 순서가 틀리기때문에 맞도록 전화해주는거지요.그담에 in_addr를
목적지 주소로 세팅합니다.

!  
!   if(sendto(sockd,&packet,sizeof(packet),0x0,(struct sockaddr *)&mysocket,
!         sizeof(mysocket)) != sizeof(packet))  {
!     perror("sendto");
!     exit(1);
!   }
자!!! 드디어 다 끝났습니다. 생각보다는 간단하지요? 그냥 ip헤더 만들고tcp헤더
만들어서 packet배열에 쑤셔넣은 담에 sendto해버리면 끝납니다.

!  
!   exit(0);
! }
-------------------------tcp.c end --------------------------------------

화일들이 많아서 복잡해보이기는 하지만 알고보면 처음에 언급했듯이
ip헤더 만들고 tcp헤더 만들어서 sendto 하면 된다는걸 아셨을겁니다~~^^
여기서 채운값들은 하나의 "예"에 불과합니다.
나머지 값들은 여러분이  tcp/ip책에 보시면 알수있습니다.
그리고 tcp를 분석하셨으니 udp 와 ipicmp는 여러분이 한번 도전해보십시요~~
tcp만 알면 나머지 두개는 다 똑같습니다
제가 초보라서 확실히 잘 모르는 부분이 많습니다.
잘못된 부분있으면 혼자만 알고있지 마시고, 제게도 꼭 알려주세여...^^
좀 아쉬움이 남긴하지만...
이것으로 raw socket 프로그래밍 강의를 마치겠습니다 ^^
좋은 프로그램 많이 짜보시기 바랍니다.
맞춤검색