|
網(wǎng)絡(luò)技術(shù)是從1990年代中期發(fā)展起來(lái)的新技術(shù),它把互聯(lián)網(wǎng)上分散的資源融為有機(jī)整體,實(shí)現(xiàn)資源的全面共享和有機(jī)協(xié)作,使人們能夠透明地使用資源的整體能力并按需獲取信息。資源包括高性能計(jì)算機(jī)、存儲(chǔ)資源、數(shù)據(jù)資源、信息資源、知識(shí)資源、專家資源、大型數(shù)據(jù)庫(kù)、網(wǎng)絡(luò)、傳感器等。 當(dāng)前的互聯(lián)網(wǎng)只限于信息共享,網(wǎng)絡(luò)則被認(rèn)為是互聯(lián)網(wǎng)發(fā)展的第三階段。 前一陣子要寫(xiě)一個(gè)簡(jiǎn)單的arp協(xié)議的分析程序,在翻閱了一些資料以后,決定使用libpcap庫(kù)來(lái)實(shí)現(xiàn),但是后來(lái)涉及到寫(xiě)鏈路層數(shù)據(jù)的緣故(另外一個(gè)程序,這個(gè)程序就是發(fā)送一個(gè)假冒的arp request,在本文沒(méi)有實(shí)現(xiàn),今后有空再整理吧),所以放棄了libpcap。由于本人使用的是solaris環(huán)境,所以無(wú)法使用bpf,但是sun公司仍然為開(kāi)發(fā)者提供了一個(gè)與設(shè)備底層無(wú)關(guān)的接口DLPI,DLPI的全稱是Data Link Provider Interface,通過(guò)DLPI開(kāi)發(fā)者可以訪問(wèn)數(shù)據(jù)鏈路層的數(shù)據(jù)包,在早期的sunos系統(tǒng)中基本上采用的是NIT設(shè)備,但是現(xiàn)在solaris系統(tǒng)都使用了DLPI.關(guān)于DLPI的具體介紹大家可以訪問(wèn)網(wǎng)站www.opengroup.org/pubs/catalog/c811.htm,我這里就不多說(shuō)了。 在搜索了許多資料之后發(fā)現(xiàn)目前關(guān)于DLPI的編程資料不多,沒(méi)有具體的過(guò)程,后來(lái)翻閱了Neal Nuckolls寫(xiě)的一篇文章How to Use the STREAMS Data Link Provider Interface (DLPI),根據(jù)例子做了修改(主要是提供了協(xié)議分析的部分),現(xiàn)在把編寫(xiě)一個(gè)DLPI過(guò)程共享一下,希望能對(duì)大家有所幫助。建議大家可以先看看Neal Nuckolls的文章,其中有部分涉及到流編程的,可以參考http://docs.sun.com/app/docs/doc/816-4855的streams programming guide(不過(guò)這不是必須的)。 使用DLPI來(lái)訪問(wèn)數(shù)據(jù)鏈路層有幾個(gè)步驟: 1、打開(kāi)網(wǎng)絡(luò)設(shè)備 2、將一個(gè)流 attach到一個(gè)特定的設(shè)備上,這里就是我們剛才打開(kāi)的設(shè)備 3、將設(shè)備設(shè)置為混雜模式(可選) 4、把數(shù)據(jù)鏈路層sap綁定到流 5、調(diào)用ioctl,設(shè)置raw模式 6、配置其他模塊(可選) 7、刷新緩存 8、接收數(shù)據(jù)進(jìn)入分析階段 第一步,我們首先打開(kāi)一個(gè)網(wǎng)絡(luò)設(shè)備,在本例中我們打開(kāi)的是/dev/bge設(shè)備,這是本機(jī)的網(wǎng)絡(luò)接口,注意不是/dev/bge0,通過(guò)open調(diào)用打開(kāi),并且返回一個(gè)描述符 fd=open(device, 2) 第二步,attach一個(gè)流到設(shè)備上,這是通過(guò)發(fā)送DL_ATTACH_REQ原語(yǔ)來(lái)完成的 dlattachreq(fd, ppa) int fd; u_long ppa; { dl_attach_req_t attach_req; struct strbuf ctl; int flags; attach_req.dl_primitive = DL_ATTACH_REQ; attach_req.dl_ppa = ppa; ctl.maxlen = 0; ctl.len = sizeof (attach_req); ctl.buf = (char *) &attach_req; flags = 0; if (putmsg(fd, &ctl, (struct strbuf*) NULL, flags) < 0) syserr("dlattachreq: putmsg"); } dl_attach_req_t是一個(gè)定義在dlpi.h中的結(jié)構(gòu)體,我們通過(guò)填寫(xiě)結(jié)構(gòu)體來(lái)發(fā)布原語(yǔ),putmsg將消息發(fā)送到一個(gè)流,以上這個(gè)函數(shù)是DLPI中發(fā)布原語(yǔ)的主要格式 發(fā)布了DL_ATTACH_REQ原語(yǔ)之后,還要確認(rèn)是否成功, dlokack(fd, bufp) int fd; char *bufp; { union DL_primitives *dlp; struct strbuf ctl; int flags; ctl.maxlen = MAXDLBUF; ctl.len = 0; ctl.buf = bufp; strgetmsg(fd, &ctl, (struct strbuf*)NULL, &flags, "dlokack"); dlp = (union DL_primitives *) ctl.buf; expecting(DL_OK_ACK, dlp); if (ctl.len < sizeof (dl_ok_ack_t)) err("dlokack: response ctl.len too short: %d", ctl.len); if (flags != RS_HIPRI) err("dlokack: DL_OK_ACK was not M_PCPROTO"); if (ctl.len < sizeof (dl_ok_ack_t)) err("dlokack: short response ctl.len: %d", ctl.len); } 第三步,將設(shè)備設(shè)置為混雜模式下工作(可選) dlpromisconreq(fd, DL_PROMISC_PHYS); 這一個(gè)步驟也是通過(guò)發(fā)布DLPI原語(yǔ)來(lái)實(shí)現(xiàn)的,具體代碼后面給出 第四步,綁定流 dlbindreq(fd, sap, 0, DL_CLDLS, 0, 0); dlbindack(fd, buf); 第五步,設(shè)置raw模式 strioctl(fd, DLIOCRAW, -1, 0, NULL) 第六步,配置其他模塊(在詳細(xì)代碼中給出) 第七步,刷新數(shù)據(jù),這是通過(guò)ioctl調(diào)用實(shí)現(xiàn)的 ioctl(fd, I_FLUSH, FLUSHR) 第八步,這是我們最關(guān)心的步驟,實(shí)際上,前面的這些步驟我們都可以忽略,大致明白有這么個(gè)過(guò)程就可以了,到時(shí)候?qū)懘a的時(shí)候照搬這個(gè)框架就可以。使用DLPI編程并不難,關(guān)鍵在于大家要了解它的框架,沒(méi)必要非得自己去寫(xiě)一個(gè)框架來(lái),本文就是利用了Michael R. Widner的代碼,今后如果要增加功能只需要往這個(gè)框架里填就可以了。 協(xié)議分析的過(guò)程是在函數(shù)filter完成的,函數(shù)申明如下 void filter(register char *cp,register u_int pktlen); 該函數(shù)接收兩個(gè)參數(shù),cp是直接從設(shè)備緩存里拷貝過(guò)來(lái)的待分析數(shù)據(jù),是鏈路層的封裝數(shù)據(jù),pktlen是數(shù)據(jù)的長(zhǎng)度。在本文中由于操作環(huán)境是以太網(wǎng),因此接收的數(shù)據(jù)鏈路層數(shù)據(jù)是以太網(wǎng)封裝格式,如不清楚以太網(wǎng)封裝的可以參考《TCP/IP詳解 卷一:協(xié)議》,以太網(wǎng)封裝三種標(biāo)準(zhǔn)的協(xié)議類型:IP協(xié)議、ARP協(xié)議和RARP協(xié)議。14字節(jié)的以太網(wǎng)首部包括了6字節(jié)的目的地址,6字節(jié)的源地址和2字節(jié)的類型字段,IP的類型值為0x0800,ARP的類型值為0x0806,RARP的類型值為0x8035。通過(guò)檢查類型字段來(lái)區(qū)別接收到的數(shù)據(jù)是屬于哪一種協(xié)議,函數(shù)實(shí)現(xiàn)代碼如下 void filter(cp, pktlen) register char *cp; register u_int pktlen; { register struct ip *ip; register struct tcphdr *tcph; register struct ether_header *eth; char *head=cp; static long line_count=0;//計(jì)數(shù)器,用來(lái)記錄接收的數(shù)據(jù)次數(shù) u_short EtherType=ntohs(((struct ether_header *)cp)->ether_type); //如果EtherType小于0x600說(shuō)明這是一個(gè)符合802.3標(biāo)準(zhǔn)的數(shù)據(jù)格式,應(yīng)當(dāng)對(duì)數(shù)據(jù)作出調(diào)整 if(EtherType < 0x600) { EtherType = *(u_short *)(cp + SZETH + 6); cp+=8; pktlen-=8; } eth=(struct ether_header*)cp; fprintf(LOG,"%-5d",++line_count); if(EtherType == ETHERTYPE_IP) //檢查協(xié)議類型是否IP協(xié)議 { ip=(struct ip *)(cp+SZETH);//調(diào)整指針的位置,SZETH是以太網(wǎng)首部長(zhǎng)度 Mac_info(e->ether_shost);//Mac_info函數(shù)打印出物理地址 fprintf(LOG,"("); Ip_info(&ip->ip_src);//Ip_info函數(shù)打印出IP地址 fprintf(LOG,")"); fprintf(LOG,"--->"); Mac_info(e->ether_dhost); fprintf(LOG,"("); Ip_info(&ip->ip_dst); fprintf(LOG,")"); fprintf(LOG,"\n"); } else if(EtherType == ARP_PROTO)//如果協(xié)議類型是ARP { cp+=SZETH; struct ether_arp *arp=(struct ether_arp *)cp; switch(ntohs(arp->ea_hdr.ar_op))//檢查arp的操作 { case ARPOP_REQUEST: //如果是arp請(qǐng)求 fprintf(LOG,"arp request:who has "); arp_ip_info(arp->arp_tpa); //打印arp報(bào)文信息中的地址 fprintf(LOG," tells "); arp_ip_info(arp->arp_spa); fprintf(LOG,"\n"); break; case ARPOP_REPLY: //arp應(yīng)答 fprintf(LOG,"arp reply: "); arp_ip_info(arp->arp_spa); fprintf(LOG," is at "); Mac_info((struct ether_addr*)&arp->arp_sha); fprintf(LOG,"\n"); break; } //可以在這里添加代碼打印出arp數(shù)據(jù)報(bào)的具體內(nèi)容 } } 程序的具體實(shí)現(xiàn)代碼如下: /* 程序sniffer.c的代碼清單 */ #include <sys/stream.h> #include <sys/dlpi.h> #include <sys/bufmod.h> #include <stdio.h> #include <ctype.h> #include <string.h> #include <sys/time.h> #include <sys/file.h> #include <sys/stropts.h> #include <sys/signal.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <net/if.h> #include <net/if_arp.h> #include <netinet/in.h> #include <netinet/if_ether.h> #include <netinet/in_systm.h> #include <netinet/ip.h> #include <netinet/udp.h> #include <netinet/ip_var.h> #include <netinet/udp_var.h> #include <netinet/in_systm.h> #include <netinet/tcp.h> #include <netinet/ip_icmp.h> #include <netdb.h> #include <arpa/inet.h> #define MAXDLBUF 32768 #define MAXWAIT 15 #define MAXDLADDR 1024 #define BITSPERBYTE 8
#define bcopy(s1, s2, len) memcpy(s2, s1, len) #define index(s, c) strchr(s, c) #define rindex(s, c) strrchr(s, c) #define bcmp(s1, s2, len) (memcmp(s1, s2, len)!=0) #define ERR stderr char *device, *ProgName, *LogName; FILE *LOG; int debug=0; long databuf[MAXDLBUF]; int sap=0; #define NIT_DEV "/dev/bge" #define CHUNKSIZE 4096 int if_fd = -1; int Packet[CHUNKSIZE+32]; int promisc = 1; int bufmod = 0; int filter_flags=0;
int maxbuflen=128; void Pexit(err,msg) int err; char *msg; { perror(msg); exit(err); } void Zexit(err,msg) int err; char *msg; { fprintf(ERR,msg); exit(err); } #define ARP_PROTO (0x0806) #define IP ((struct ip *)Packet) #define IP_OFFSET (0x1FFF) #define SZETH (sizeof(struct ether_header)) #define ARPLEN (sizeof(struct ether_arp)) #define MACLEN (6) #define IPALEN (4) #define IPLEN (ntohs(ip->ip_len)) #define IPHLEN (ip->ip_hl) #define INET_ADDRSTRLEN 16 #define MAXBUFLEN (8192) time_t LastTIME = 0; char *Ptm(t) register time_t *t; { register char *p = ctime(t); p[strlen(p)-6]=0; return(p); } char *NOWtm() { time_t tm; time(&tm); return( Ptm(&tm) ); } void print_data(uchar_t *buf,int size) { int i=0; char *p=buf; for(;i<size;i++){ if(i%16 == 0) fprintf(LOG,"\n"); if(i%2 == 0) fprintf(LOG," "); fprintf(LOG,"%02x",*p++&0x00ff); } fprintf(LOG,"\n"); } //打印物理地址 void Mac_info(struct ether_addr*mac) { fprintf(LOG,"%02x:%02x:%02x:%02x:%02x:%02x", mac->ether_addr_octet[0], mac->ether_addr_octet[1], mac->ether_addr_octet[2], mac->ether_addr_octet[3], mac->ether_addr_octet[4], mac->ether_addr_octet[5]); } //打印ip地址char buf[MAXDLBUF]; void Ip_info(struct in_addr *ip) { char str[INET_ADDRSTRLEN]; inet_ntop(AF_INET,ip,str,sizeof(str)); if(*str) fprintf(LOG,"%s",str); } //打印ip地址的另外一個(gè)版本 void arp_ip_info(uchar_t pa[]) { fprintf(LOG,"%d.%d.%d.%d",pa[0],pa[1],pa[2],pa[3]); }
void death() { register struct CREC *CLe; fprintf(LOG,"\nLog ended at => %s\n",NOWtm()); fflush(LOG); if(LOG != stdout) fclose(LOG); exit(1); } err(fmt, a1, a2, a3, a4) char *fmt; char *a1, *a2, *a3, *a4; { (void) fprintf(stderr, fmt, a1, a2, a3, a4); (void) fprintf(stderr, "\n"); (void) exit(1); } void sigalrm() { (void) err("sigalrm: TIMEOUT"); } strgetmsg(fd, ctlp, datap, flagsp, caller) int fd; struct strbuf *ctlp, *datap; int *flagsp; char *caller; { int rc; static char errmsg[80]; (void) signal(SIGALRM, sigalrm); if (alarm(MAXWAIT) < 0) { (void) sprintf(errmsg, "%s: alarm", caller); syserr(errmsg); }
*flagsp = 0; if ((rc = getmsg(fd, ctlp, datap, flagsp)) < 0) { (void) sprintf(errmsg, "%s: getmsg", caller); syserr(errmsg); }
if (alarm(0) < 0) { (void) sprintf(errmsg, "%s: alarm", caller); syserr(errmsg); }
if ((rc & (MORECTL | MOREDATA)) == (MORECTL | MOREDATA)) err("%s: MORECTL|MOREDATA", caller); if (rc & MORECTL) err("%s: MORECTL", caller); if (rc & MOREDATA) err("%s: MOREDATA", caller);
[page_break]if (ctlp->len < sizeof (long)) err("getmsg: control portion length < sizeof (long): %d", ctlp->len); } expecting(prim, dlp) int prim; union DL_primitives *dlp; { if (dlp->dl_primitive != (u_long)prim) { err("unexpected dlprim error\n"); exit(1); } } strioctl(fd, cmd, timout, len, dp) int fd; int cmd; int timout; int len; char *dp; { struct strioctl sioc; int rc; sioc.ic_cmd = cmd; sioc.ic_timout = timout; sioc.ic_len = len; sioc.ic_dp = dp; rc = ioctl(fd, I_STR, &sioc); if (rc < 0) return (rc); else return (sioc.ic_len); } dlattachreq(fd, ppa) int fd; u_long ppa; { dl_attach_req_t attach_req; struct strbuf ctl; int flags; attach_req.dl_primitive = DL_ATTACH_REQ; attach_req.dl_ppa = ppa; ctl.maxlen = 0; ctl.len = sizeof (attach_req); ctl.buf = (char *) &attach_req; flags = 0; if (putmsg(fd, &ctl, (struct strbuf*) NULL, flags) < 0) syserr("dlattachreq: putmsg"); } dlokack(fd, bufp) int fd; char *bufp; { union DL_primitives *dlp; struct strbuf ctl; int flags; ctl.maxlen = MAXDLBUF; ctl.len = 0; ctl.buf = bufp; strgetmsg(fd, &ctl, (struct strbuf*)NULL, &flags, "dlokack"); dlp = (union DL_primitives *) ctl.buf; expecting(DL_OK_ACK, dlp); if (ctl.len < sizeof (dl_ok_ack_t)) err("dlokack: response ctl.len too short: %d", ctl.len); if (flags != RS_HIPRI) err("dlokack: DL_OK_ACK was not M_PCPROTO"); if (ctl.len < sizeof (dl_ok_ack_t)) err("dlokack: short response ctl.len: %d", ctl.len); } dlbindreq(fd, sap, max_conind, service_mode, conn_mgmt, xidtest) int fd; u_long sap; u_long max_conind; u_long service_mode; u_long conn_mgmt; u_long xidtest; { dl_bind_req_t bind_req; struct strbuf ctl; int flags;
bind_req.dl_primitive = DL_BIND_REQ; bind_req.dl_sap = sap; bind_req.dl_max_conind = max_conind; bind_req.dl_service_mode = service_mode; bind_req.dl_conn_mgmt = conn_mgmt; bind_req.dl_xidtest_flg = xidtest; ctl.maxlen = 0; ctl.len = sizeof (bind_req); ctl.buf = (char *) &bind_req; flags = 0; if (putmsg(fd, &ctl, (struct strbuf*) NULL, flags) < 0) syserr("dlbindreq: putmsg"); } dlbindack(fd, bufp) int fd; char *bufp; { union DL_primitives *dlp; struct strbuf ctl; int flags; ctl.maxlen = MAXDLBUF; ctl.len = 0; ctl.buf = bufp; strgetmsg(fd, &ctl, (struct strbuf*)NULL, &flags, "dlbindack"); dlp = (union DL_primitives *) ctl.buf; expecting(DL_BIND_ACK, dlp); if (flags != RS_HIPRI) err("dlbindack: DL_OK_ACK was not M_PCPROTO"); if (ctl.len < sizeof (dl_bind_ack_t)) err("dlbindack: short response ctl.len: %d", ctl.len); } dlpromisconreq(fd, level) int fd; u_long level; { dl_promiscon_req_t promiscon_req; struct strbuf ctl; int flags; promiscon_req.dl_primitive = DL_PROMISCON_REQ; promiscon_req.dl_level = level; ctl.maxlen = 0; ctl.len = sizeof (promiscon_req); ctl.buf = (char *) &promiscon_req; flags = 0; if (putmsg(fd, &ctl, (struct strbuf*) NULL, flags) < 0) syserr("dlpromiscon: putmsg"); } syserr(s) char *s; { (void) perror(s); exit(1); } void filter(cp, pktlen) register char *cp; register u_int pktlen; { register struct ip *ip; register struct tcphdr *tcph; register struct ether_header *eth; char *head=cp; static long line_count=0;
u_short EtherType=ntohs(((struct ether_header *)cp)->ether_type); if(EtherType < 0x600) { EtherType = *(u_short *)(cp + SZETH + 6); cp+=8; pktlen-=8; } eth=(struct ether_header*)cp; fprintf(LOG,"%-5d",++line_count); if(EtherType == ETHERTYPE_IP) { ip=(struct ip *)(cp+SZETH); Mac_info(e->ether_shost); fprintf(LOG,"("); Ip_info(&ip->ip_src); fprintf(LOG,")"); fprintf(LOG,"--->"); Mac_info(e->ether_dhost); fprintf(LOG,"("); Ip_info(&ip->ip_dst); fprintf(LOG,")"); fprintf(LOG,"\n"); } else if(EtherType == ARP_PROTO) { cp+=SZETH; struct ether_arp *arp=(struct ether_arp *)cp; switch(ntohs(arp->ea_hdr.ar_op)) { case ARPOP_REQUEST: fprintf(LOG,"arp request:who has "); arp_ip_info(arp->arp_tpa); fprintf(LOG," tells "); arp_ip_info(arp->arp_spa); fprintf(LOG,"\n"); break; case ARPOP_REPLY: fprintf(LOG,"arp reply: "); arp_ip_info(arp->arp_spa); fprintf(LOG," is at "); Mac_info((struct ether_addr*)&arp->arp_sha); fprintf(LOG,"\n"); break; } //打印出arp數(shù)據(jù)報(bào)的內(nèi)容 } } do_it() { long buf[MAXDLBUF]; char *device; int ppa; int fd; struct strbuf data; int flags; int i; int c; int offset; int len; struct timeval t; u_int chunksize = 16 * 1024; struct sb_hdr *bp; char *p, *limp; int mrwtmp; device = "/dev/bge"; ppa = 0; sap= 0x0806; if ((fd = open(device, 2)) < 0) syserr(device);
dlattachreq(fd, ppa); dlokack(fd, buf); if (promisc) { dlpromisconreq(fd, DL_PROMISC_PHYS); dlokack(fd, buf); }
dlbindreq(fd, sap, 0, DL_CLDLS, 0, 0); dlbindack(fd, buf);
if (strioctl(fd, DLIOCRAW, -1, 0, NULL) < 0) syserr("DLIOCRAW"); if (bufmod) { if (ioctl(fd, I_PUSH, "bufmod") < 0) syserr("push bufmod"); t.tv_sec = 0; t.tv_usec = 500000; if (strioctl(fd, SBIOCSTIME, -1, sizeof (struct timeval), &t) < 0) syserr("SBIOCSTIME"); if (strioctl(fd, SBIOCSCHUNK, -1, sizeof (u_int), &chunksize) < 0) syserr("SBIOCSCHUNK"); } if (ioctl(fd, I_FLUSH, FLUSHR) < 0) syserr("I_FLUSH");
if(1){ data.buf = (char *) databuf; data.maxlen = MAXDLBUF; data.len = 0; while (((mrwtmp=getmsg(fd, NULL, &data, &flags))==0) || (mrwtmp==MOREDATA) || (mrwtmp=MORECTL)) { p = data.buf; limp = p + data.len; filter(data.buf, data.len); data.len = 0; } printf("finished getmsg() = %i\n",mrwtmp); } } int main(argc, argv) int argc; char **argv; { char cbuf[BUFSIZ]; struct ifconf ifc; int s, ac=1, backg=0;
ProgName=argv[0]; device=NIT_DEV; while((ac<argc) && (argv[ac][0] == '-')) { register char ch = argv[ac++][1]; switch(toupper(ch)) { case 'I': device=argv[ac++]; break; case 'O': if(!(LOG=fopen((LogName=argv[ac++]),"a"))) Zexit(1,"Output file cant be opened\n"); break; case 's': sap=atoi(argv[ac++]); break; default : fprintf(ERR, "Usage: %s [-s] [-i interface] [-o file]\n", ProgName); fprintf(ERR," -d int set new data limit (128 default)\n"); fprintf(ERR," -o <file> output to <file>\n"); exit(1); } }
fprintf(ERR,"Using logical device %s [%s]\n",device,NIT_DEV); fprintf(ERR,"Output to %s.%s%s",(LOG)?LogName:"stdout", (debug)?" (debug)":"",(backg)?" Backgrounding ":"\n"); if(!LOG) LOG=stdout; signal(SIGINT, death); signal(SIGTERM,death); signal(SIGKILL,death); signal(SIGQUIT,death); if(backg && debug) { fprintf(ERR,"[Cannot bg with debug on]\n"); backg=0; } fprintf(LOG,"\nLog started at => %s [pid %d]\n",NOWtm(),getpid()); fflush(LOG); do_it(); } 編譯運(yùn)行: #gcc -lsocket -lsnl -o sniffer sniffer.c #./sniffer 同時(shí)在另一個(gè)終端上運(yùn)行ping 192.168.1.10 Using logical device /dev/bge [/dev/bge] Output to stdout. Log started at => Tue Jul 12 18:13:44 [pid 948] 1 arp request:who has 192.168.1.22 tells 192.168.1.10 2 arp request:who has 192.168.1.22 tells 192.168.1.10 3 arp request:who has 192.168.1.22 tells 192.168.1.10 4 arp request:who has 192.168.1.22 tells 192.168.1.10 5 arp request:who has 192.168.1.22 tells 192.168.1.10
網(wǎng)絡(luò)的神奇作用吸引著越來(lái)越多的用戶加入其中,正因如此,網(wǎng)絡(luò)的承受能力也面臨著越來(lái)越嚴(yán)峻的考驗(yàn)―從硬件上、軟件上、所用標(biāo)準(zhǔn)上......,各項(xiàng)技術(shù)都需要適時(shí)應(yīng)勢(shì),對(duì)應(yīng)發(fā)展,這正是網(wǎng)絡(luò)迅速走向進(jìn)步的催化劑。
|