全部博文(403)
分类:
2006-09-15 16:55:18
一、开发目的:
以尽量快的速度,发现internet上存在ftp服务的主机地址。
二、实现方法:
不同于以往的调用connect,而是自己实现了TCP和IP协议的一些内容,向目标主机发送TCP SYN连接信号的IP包,一边监听发往本机IP包,从中找出那些对本机发出的SYN连接信号的响应,包括SYN和RST,分别为接收连接和拒绝连接;还要找出ICMP不可到达包,表示目标地址不可达。对于地址范围内的IP地址,如果对SYN信号没有响应,应该再发送几次,因为IP包是可能在网络中丢失的。
三、数据组织
模块内部保存了用一棵红黑树保存了那些已经有响应的IP地址和端口号,下一轮中就无需再向其发送SYN连接信号,而且可以保证同一个IP地址和端口不会被重复返回。扫描完全国地址(1个端口)这棵红黑树可能要占用100M左右的内存空间。
四、模块接口使用说明
#ifndef _TCP_SCAN_H_
#define _TCP_SCAN_H_
#include
#define TS_ACCEPTED 1
#define TS_REFUSED 2
#define TS_UNREACHABLE 3
struct addrseg
{
int as_family;
void *as_address;
int as_bits;
};
typedef int (*scan_info_t)(const struct sockaddr *, socklen_t, int, void *);
#ifdef __cplusplus
extern "C"
{
#endif
int tcp_scan(const struct addrseg *addrscope, const unsigned short *ports,
unsigned int ifindex, const char *ifname, int resetuid,
scan_info_t info, void *arg);
#ifdef __cplusplus
}
#endif
#endif
以上是tcp_scan.h文件。可以看到,这个模块只有一个函数tcp_scan。现在解释一下函数的接口。
int tcp_scan(const struct addrseg *addrscope, const unsigned short *ports,
unsigned int ifindex, const char *ifname, int resetuid,
scan_info_t info, void *arg);
const struct addrseg *addrscope:
IP地址范围,包括多个地址段,每个地址段是一个struct addrseg结构。struct addrseg的定义如下:
struct addrseg
{
int as_family;
void *as_address;
int as_bits;
};
成员as_family表示地址类型,可以是AF_INET或AF_INET6,分别表示IPV4地址和IPV6地址。
成员as_address指向一个IPV4地址(struct in_addr)或IPV6地址(struct in6_addr)。
成员as_bits表示地址的有效位数,例如扫描61.*.*.*,则有效位数为8。
tcp_scan的第一个参数addrscope实际上就是struct addrseg型的数组,数组中的最后一addrseg的as_family域必须为AF_UNSPEC,表示数组的结束。
const unsigned short *ports:
要扫描的端口集合,也就是unsigned short型的数组,数组最后个一元素必须为0。
unsigned int ifindex:
由于我不想自己去访问路由表(偷懒了^_^),所以要求用户指定一个网卡作为通信设备。在单网卡的机器上关系不大,但多网卡时就只能使用一块网卡。
ifindex表示网络设备号,如果你不清楚则这个参数置为0就可以了。
const char *ifname:
网络设备名。这个参数仅当ifindex为0时有效,表示用于通信的网络设备名。如果你的机器只有一块网卡,其设备名通常是”eth0”。
int resetuid:
tcp_scan运行时需要root权限,resetuid表示是否在无需root权限时把进程的有效权限置为运行者的权限。如果你不知道我说什么,请置为1。
scan_info_t info:
scan_info_t的定义如是下:
typedef int (*scan_info_t)(const struct sockaddr *, socklen_t, int, void *);
这是一个回调函数,当tcp_scan发现一个IP地址的一个端口号可连接时,通过这个回调函数返回。如果你不知道什么是回调函数,你就需要好好补一补C语言了。我比较喜欢用回调,所以以后我写的模块里可能经常看到。
scan_info_t的第一个参数const struct sockaddr *一个有响应的IP地址和端口号,封装在一个struct sockaddr结构里;第二个参数socklen_t,前面那个结构的长度,应该很好理解;第三个参数为状态,可能为TS_ACCEPTED,TS_REFUSED和TS_UNREACHABLE,在3个宏在tcp_scan.h里已定义,分别表示地址接受连接,拒绝连接和地址不可达;最后个一参数void *,回调函数的惯例,用户自定义。
void *arg:
原封不动作为回调函数info的最后一个参数。
tcp_scan返回负值表示失败,失败原因查看errno;非负值表示成功。另外,回调函数info返回负值的话可让tcp_scan立即以失败返回。
五、例示程序
int info(const struct sockaddr *sockaddr, socklen_t addrlen,
int state, void *arg)
{
struct sockaddr_in *sin = (struct sockaddr_in *)sockaddr;
printf("%s:%d ", inet_ntoa(sin->sin_addr), ntohs(sin->sin_port));
switch (state)
{
case TS_ACCEPTED:
printf("Connection accepted\n");
break;
case TS_REFUSED:
printf("Connection refused\n");
break;
case TS_UNREACHABLE:
printf("Destination unreachable\n");
break;
}
return 0;
}
int main(void)
{
struct addrseg addrseg[10];
unsigned short port[2] = { 21, 0 };
addrseg[0].as_family = AF_INET;
addrseg[0].as_address = malloc(sizeof (struct in_addr));
inet_pton(AF_INET, "162.105.0.0", addrseg[0].as_address);
addrseg[0].as_bits = 16;
addrseg[1].as_family = AF_INET;
addrseg[1].as_address = malloc(sizeof (struct in_addr));
inet_pton(AF_INET, "166.111.0.0", addrseg[1].as_address);
addrseg[1].as_bits = 16;
addrseg[2].as_family = AF_UNSPEC;
tcp_scan(addrseg, port, 0, "eth0", 1, info, NULL);
}
以上是扫描北大清华所有IP地址的ftp端口的示使,由于是示例,没有注意代码风格,见谅。
六、测试结果
扫描了61.*.*.*,202.*.*.*,210.*.*.*,212.*.*.*,218.*.*.*,162.105.*.*和166.111.*.*。一次扫描用时约70分钟,由于目前把扫描次数定为3,所以tcp_scan正常返回大约需70*3分钟。测试环境为P4 1.4G,256M内存。以上IP地址范围大约有只有一半是国内IP地址。
七、说明
目前本模块还有少量功能未完成,IPV6地址扫描还差一个函数,因此目前还不能扫描IPV6地址。此外还必须加入MSL,也就是说扫描完成后还必须再等一段时间,可能还会收到响应。根椐RFC的建议MSL为2分钟,BSD系统一般定为30秒。那么,那怕只扫描一个IP地址也必须等待2*MSL=1分钟-4分钟。但这对我们的关系不大,对于大范围的扫描1-4分钟是完全可以忽略的。MSL功能也很快可以加上。此外,虽然本模块提供了多端口扫描功能,但目前tcp_scan的空间复杂性是随端口数线性增长的(可以进行优代),所以最好不要在大IP地址范围内(如全国)扫描多个端口,除非你的内存吃得消;代替方法是调用多次tcp_scan,每次扫描一个端口。
本模块中设置了一些隐藏接口(为了好玩),如果你熟读源代码还可以发现更灵活的使用tcp_scan。
本模块中红-黑树的实现取自Linux内核源代码,此外还有一个in_cksum函数,取自unp的示例代码。除此之外不存在其实非自编代码。
注意:进行tcp_scan必须有root权限!