`
memorymyann
  • 浏览: 266330 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

26.原始套接字

阅读更多

一个小程序:

//发送方

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <netdb.h>
#include <string.h>
#include <time.h>
#include <sys/select.h>

struct mydata {
        char data[12];
};

int main(int argc, char ** argv) {
        struct mydata senddata;
        strcpy(senddata.data, "hello world");
        int sockfd;
        struct sockaddr_in addr;
        socklen_t addr_len = sizeof(addr);

        bzero(&addr, sizeof(struct sockaddr_in));
        addr.sin_family = AF_INET;
        inet_pton(AF_INET, argv[1], &addr.sin_addr);

        sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
        char sendbuf[] = "hello world";
        int sendbuf_len = sendto(sockfd, (void *)&senddata, sizeof(struct mydata), 0, (struct sockaddr *)&addr, addr_len);
        printf("send len = %d\n", sendbuf_len);
        close(sockfd);
}

 

//接受方

#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <netdb.h>
#include <string.h>
#include <time.h>
#include <sys/select.h>

void proc_v4(char *);
struct mydata {
        char data[12];
};

int main(int argc, char ** argv) {
        int sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
        char readbuf[100];
        int ipv = atoi(argv[1]);
        int len = read(sockfd, readbuf, ipv);
        printf("len = %d\n", len);
        proc_v4(readbuf);
        close(sockfd);
}

void proc_v4(char * package) {
        struct mydata * content;
        struct ip * ip = (struct ip *)package;
        struct in_addr from= ip->ip_src;
        printf("from %s: ", inet_ntoa(from));
        content = (struct mydata *)(package + sizeof(struct ip));
        printf("%s\n", content->data);
}

 

原本网上有一些用原始套接字的例子,但我找了很久只有2个版本,一个是用原始套接字发送ICMP包,另一个则是发送TCP包(带有攻击性质)。发送ICMP包,指定的服务类型为ECHO回显服务,而这是由内核提供的,也就是说我们必须要自己来写接受方,只要发送出去即可。TCP例子则是不停的向同一台机器发送请求连接的SYN包。缺点是:本来学习的主题是原始套接口,而这2个例子还要带上对TCP和ICMP的学习。(笔者太笨了,写了很久也没有让ICMP回显,可能是ICMP头部写的有问题,包被主机忽略了)。

 

我这里依然采用的是ICMP协议,但我没有自己写ICMP包头,只是简单把要传递的信息放入了ICMP包中,内核依然会将其送入接受的套接口中。

 

1.原始套接口创建

sockfd = socket(AF_INET, SOCK_RAW, 86);

第一个参数很熟悉了,表示IPV4,SOCK_RAW表示原始套接字,最后的86是我乱写。第3个参数可选值很多IPPROTO_ICMP或者IPPROTO_TCP。如果是IPPROTO_ICMP表示通过这个原始套接字发送的IP包中带的数据将会是ICMP包,依次理解IPPROTO_TCP则表示带为TCP包。而86则是内核没有支持的协议或者说内核不认识的协议。这些将会影响接受方内核对IP数据包的处理。

 

2.原始套接口输出

端口对原始套接口是没有意义的,一般使用sendto或者sendmsg发送,当然可以调用connect的原始套接字,则可以直接使用write。超过MTU的会被分片。

 

3.原始套接口的输入

回忆一下TCP和UDP,他们都是通过端口号和地址来找到接受进程。而原始套接口则不同。它遵循以下几个规则:

 a.如果发送方创建时候使用的socket(AF_INET,SOCK_RAW, IPPROTO_TCP),也就是从这个套接口出来的为TCP包,或者UDP包不会传递给任何原始套接口,他们将直接递交给TCP或者UDP协议处理。所以要想抓TCP或者UDP包,用原始套接口无法实现。

b.如果发送的是ICMP数据,除了回射,时间戳,地址掩码请求完全有内核处理,所有接受到的都将传递给某个原始套接口。(我理解是只要你创建了原始套接字,则这个套接字就会收到这个包的一个拷贝)。

c.如果是IGMP数据,所有IGMP分组将会传递某个原始套接口。

d.不能识别的协议字段,例如我上面的86,都将传递某个原始套接口。

e.被分片的包会重组再按照abcd处理。

f.ip包会进行版本,头部校验和,头部长度以及目的地址检查。检查不合格者会丢弃。

 

4.套接口选项IP_HDRINCL

上面程序比较简单,我没有打开这个选项,如果打开这个选项,则你写入的数据会从IP包头部开始,也就是说你需要写IP头部。

 

一个打开了IP_HDRINCL的原始套接字样例,接受方仍然可以采用原来的程序,这个程序修改了发送者的IP

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <netdb.h>
#include <string.h>
#include <time.h>
#include <sys/select.h>

 

//计算IP头部校验和的算法

unsigned short csum(unsigned short * buf, int nwords) {

   unsigned long sum;

   for(sum = 0; nwords > 0; nwords--)

       sum += *buf++;

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

   sum += (sum >> 16);

   return (unsigned short)(~sum);

}

 

struct mydata {

   char data[12];

};

 

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

  char buf[400];

  struct ip * ip_head = (struct ip *)buf;

  struct mydata * senddata = (struct mydata *)(buf + sizeof(struct ip));

  int one = 1;

  const int * val = &one;

 

  memset(buf, 0, 400);

 

  int sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);

 

  bzero(&addr, sizeof(struct sockaddr_in));

  addr.sin_family = AF_INET;

  inet_pton(AF_INET, argv[1], &addr.sin_addr);

 

  //init ip head

  ip_head->ip_hl = 5; //头部长度

  ip_head->ip_v = 4;//ip版本号

  ip_head->ip_tos = 16;

  ip_head->ip_p = IPPROTO_ICMP; //服务类型

  inet_pton(AF_INET, argv[1], &ip_head->ip_dst); //指定目的IP地址

  inet_pton(AF_INET, "192.168.1.23", &ip_head->ip_src); //指定源IP地址,这里可以随便指定,不一定要是本机IP地址,当然如果不是本机地址,对方获得的就是错误地址,原始套接口没有确认包返回,所以不会有影响,但如果期望对方返回信息的话,就要填自己的IP地址。

 

  strcpy(senddata->data, "hello world");

  setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, val, sizeof(one));

 

  ip_head->ip_sum = csum((unsigned short *) buf, sizeof(struct ip) + sizeof(struct mydata));//计算校验和

  sendto(sockfd, buf, ip_head->ip_len, 0, (struct sockaddr *)&addr, sizeof(addr));

  close(sockfd);

  exit(0);

}

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics