1.原理
众所周知,当调用connect()时要经历三次握手的过程SYN-SYN&ACK-ACK,而这种SYN扫描方式又叫半开放式扫描,即客户端只发送SYN帧,端口开放回应SYN&ACK帧,端口关闭回应RST帧,缺点就是不可靠,可能会丢包。
2.实现方式及遇到的问题
前面架构篇有讲过,每个扫描线程(tcpSynScanPort)会建立一个线程池,对每个要扫描的端口都创建一个线程tcpSynScanEach(线程配置成detach属性)。tcpSynScanEach负责发送SYN帧到各端口,另有一个线程tcpSynScanRecv负责处理所有收到的数据包。发送的数据包要自己组装,协议可以使用原始套接字,协议选择TCP。
(1)参数传递
也是主线程里malloc一块,从线程sendto之后就free,只不过不能只像connect一样,传递目的ip和端口,因为攒包的时候要填写本机ip与端口,所以自己定义一个数据结构。
(2)字节序
网络字节序与主机字节序的转换。
(3)线程安全
同上一篇
(4)校验和
在填写首部字段时免不了要写校验和字段,需要小心校验的范围,这里做一下总结。
IP校验和只校验20字节的IP报头;
ICMP校验和覆盖整个报文(ICMP报头+ICMP数据);
UDP和TCP校验和不仅覆盖整个报文,而且还有12字节的IP伪首部,包括源IP地址(4字节)、目的IP地址(4字节)、协议(2字节,第一字节补0)和TCP/UDP包长(2字节)。另外UDP、TCP数据报的长度可以为奇数字节,所以在计算校验和时需要在最后增加填充字节0(注意,填充字节只是为了计算校验和,可以不被传送)。
(5)对丢包的处理
只发SYN和接收ACK或RST帧其实是在ip层进行的,所以丢包的可能性很大。在这种扫描模式下,无论对方端口是打开还是关闭,都会有应答,所以我用一个全局数组记录每个端口的状态,0表示没有应答,1表示ack,2表示收到rst
(6)弱智错误
对于a<x<b的判断,要写if( (a<x) && (x<b) )好嘛,不能直接a<x<b好嘛。。
(7)各线程间的同步
main函数调用端口扫描主线程,main函数pthread_join等待扫描过程结束。
扫描主线程调用各个端口扫描线程,各端口扫描线程发送之后就自动结束了,扫描主线程不用等待。
扫描主线程调用接收线程,通过判断全局变量synCnt来确定是否扫描结束,归零后由扫描主线程kill掉接收线程,用pthread_cancel函数。
(8)tcp帧头
struct tcphdr
{
u_int16_t th_sport; /* source port */
u_int16_t th_dport; /* destination port */
tcp_seq th_seq; /* sequence number */
tcp_seq th_ack; /* acknowledgement number */
# if __BYTE_ORDER == __LITTLE_ENDIAN
u_int8_t th_x2:4; /* (unused) */
u_int8_t th_off:4; /* data offset */
# endif
# if __BYTE_ORDER == __BIG_ENDI