分类: 系统运维
2008-03-24 20:40:38
附录A:一个实现示例
下面是本文档中描述的的一个实现例子。
因为很多可能处理这些代码的人很熟悉BerkeleyUnix内核及其编码风格(被亲切地称为
“内核规范格式”),这些代码正是遵循该风格。使用了Berkeley“子程序”(subroutines,
实际上就是宏和/或内联汇编的扩展)来转换网络字节序(networkbyteorder)以及拷贝
/比较字节串。这些例行程序在A.5中为那些不熟悉它们的读者作简要介绍。
这些代码在25页列出的所有机器上运行通过。作者希望没有字节序或对齐方面的问
题(但是其中嵌入了假设的对BerkeleyUnix有效的对齐方式,这种对齐方式对于其他IP
实现可能不正确。见sl_compress_tcp和sl_decompress_tcp中提到的关于对齐的注释)。
曾试图尝试使这些代码更高效,不幸的是可能会使其中的一部分不可理解。作者为
此感到抱歉(老实说,我的C语言风格被认为是晦涩的,并且借口就是“效率”)。
该实例代码以及完整的BerkeleyUnix实现(机器可读的)通过匿名ftp从
ftp.ee.lbl.gov(128.3.254.68)上得到,即文件cslip.tar.Z。这是一个压缩的Unix
tar文件,必须以二进制模式进行ftp。附录中所有代码均包含下面的版权通告:/*
*Copyright(c)1989RegentsoftheUniversityofCalifornia.
*Allrightsreserved.
*
*Redistributionanduseinsourceandbinaryformsare
*permittedprovidedthattheabovecopyrightnoticeandthis
*paragraphareduplicatedinallsuchformsandthatany
*documentation,advertisingmaterials,andothermaterials
*relatedtosuchdistributionanduseacknowledgethatthe
*softwarewasdevelopedbytheUniversityofCalifornia,
*Berkeley.ThenameoftheUniversitymaynotbeusedto
*endorseorpromoteproductsderivedfromthissoftware
*withoutspecificpriorwrittenpermission.
*THISSOFTWAREISPROVIDED``ASIS''ANDWITHOUTANYEXPRESS
*ORIMPLIEDWARRANTIES,INCLUDING,WITHOUTLIMITATION,THE
*IMPLIEDWARRANTIESOFMERCHANTIBILITYANDFITNESSFORA
*PARTICULARPURPOSE.
*/
Jacobson[Page27]
1144Compressing/IPHeadersFebruary1990
A.1DefinitionsandStateData
#defineMAX_STATES16/*mustbe>2and<255*/
#defineMAX_HDR128/*max+IPhdrlength(byprotocoldef)*/
/*packettypes*/
#defineTYPE_IP0x40
#defineTYPE_UNCOMPRESSED_0x70
#defineTYPE_COMPRESSED_0x80
#defineTYPE_ERROR0x00/*thisisnotatypethateverappearson
*thewire.Thereceiveframerusesitto
*tellthedecompressortherewasapacket
*transmissionerror.*/
/*Bitsinfirstoctetofcompressedpacket*/
#defineNEW_C0x40/*flagbitsforwhatchangedinapacket*/
#defineNEW_I0x20
#define_PUSH_BIT0x10
#defineNEW_S0x08
#defineNEW_A0x04
#defineNEW_W0x02
#defineNEW_U0x01
/*reserved,special-casevaluesofabove*/
#defineSPECIAL_I(NEW_S|NEW_W|NEW_U)/*echoedinteractivetraffic*/
#defineSPECIAL_D(NEW_S|NEW_A|NEW_W|NEW_U)/*unidirectionaldata*/
#defineSPECIALS_MASK(NEW_S|NEW_A|NEW_W|NEW_U)
/*"state"dataforeachactivetcpconversationonthewire.Thisis
*basicallyacopyoftheentireIP/headerfromthelastpackettogether
*withasmallidentifierthetransmit&receiveendsofthelineuseto
*locatesavedheader.*/
structcstate{
structcstate*cs_next;/*nextmostrecentlyusedcstate(xmitonly)*/
u_shortcs_hlen;/*sizeofhdr(receiveonly)*/
u_charcs_id;/*connection#associatedwiththisstate*/
u_charcs_filler;
union{
charhdr[MAX_HDR];
structipcsu_ip;/*ip/tcphdrfrommostrecentpacket*/
}slcs_u;
};
#definecs_ipslcs_u.csu_ip
Jacobson[Page28]
1144Compressing/IPHeadersFebruary1990
#definecs_hdrslcs_u.csu_hdr
/*
*allthestatedataforoneserialline(weneedoneoftheseperline).
*/
structslcompress{
structcstate*last_cs;/*mostrecentlyusedtstate*/
u_charlast_recv;/*lastrcvdconn.id*/
u_charlast_xmit;/*lastsentconn.id*/
u_shortflags;
structcstatetstate[MAX_STATES];/*xmitconnectionstates*/
structcstaterstate[MAX_STATES];/*receiveconnectionstates*/
};
/*flagvalues*/
#defineSLF_TOSS1/*tossingrcvdframesbecauseofinputerr*/
/*
*Thefollowingmacrosareusedtoencodeanddecodenumbers.Theyall
*assumethat`cp'pointstoabufferwherethenextbyteencoded(decoded)
*istobestored(retrieved).Sincethedecoderoutinesdoarithmetic,
*theyhavetoconvertfromandtonetworkbyteorder.
*/
/*
*ENCODEencodesanumberthatisknowntobenon-zero.ENCODEZchecksfor
*zero(zerohastobeencodedinthelong,3byteform).
*/
#defineENCODE(n){\
if((u_short)(n)>=256){\
*cp++=0;\
cp[1]=(n);\
cp[0]=(n)>>8;\
cp+=2;\
}else{\
*cp++=(n);\
}\
}
#defineENCODEZ(n){\
if((u_short)(n)>=256||(u_short)(n)==0){\
*cp++=0;\
cp[1]=(n);\
cp[0]=(n)>>8;\
cp+=2;\
}else{\
*cp++=(n);\
}\
}
/*
*DECODELtakesthe(compressed)changeatbytecpandaddsittothe
Jacobson[Page29]
1144Compressing/IPHeadersFebruary1990
*currentvalueofpacketfield'f'(whichmustbea4-byte(long)integer
*innetworkbyteorder).DECODESdoesthesamefora2-byte(short)field.
*DECODEUtakesthechangeatcpandstuffsitintothe(short)fieldf.
*'cp'isupdatedtopointtothenextfieldinthecompressedheader.
*/
#defineDECODEL(f){\
if(*cp==0){\
(f)=htonl(ntohl(f)+((cp[1]<<8)|cp[2]));\
cp+=3;\
}else{\
(f)=htonl(ntohl(f)+(u_long)*cp++);\
}\
}
#defineDECODES(f){\
if(*cp==0){\
(f)=htons(ntohs(f)+((cp[1]<<8)|cp[2]));\
cp+=3;\
}else{\
(f)=htons(ntohs(f)+(u_long)*cp++);\
}\
}
#defineDECODEU(f){\
if(*cp==0){\
(f)=htons((cp[1]<<8)|cp[2]);\
cp+=3;\
}else{\
(f)=htons((u_long)*cp++);\
}\
}
Jacobson[Page30]
1144Compressing/IPHeadersFebruary1990
A.2压缩
该子程序看起来令人生畏,其实并非如此。代码分为大小大致相等的四块:第一块
一个最近最少使用的活动连接循环链表(注46),第二块计算sequence/ack/window/urg
变化量并确定压缩数据包的大致结构(bulk),第三部分处理特殊情形的编码,第四块进行
数据包ID和connectionnumber的编码并且用压缩后的头部代替初始数据包的头部。
该子程序的参数为一个指向待压缩数据包的指针,一个指向串行链路压缩状态结构的指
针,一个允许/禁止对connection进行压缩的标志(即C位)。
压缩是“取代式”(inplace)的,所以每产生一个压缩数据包,输入数据包的开始地址
和长度(m中的off和len域)以反映出初始数据包头部已被移除并被经过压缩的头部所取
代。不管产生压缩数据包还是未压缩数据包,压缩状态结构都要更新。该子程序返回帧传输
器(transitframer)传送的数据包类型(TYPE_IP,TYPE_UNCOMPRESSED_或者
TYPE_COMPRESSED_)。
由于头部各种域中有16位和32位,所以输入IP数据包必须做好对齐(例如,在SPARC
上,IP头部在32位边界对齐)。如果不是这样的话,必须对下面的代码进行彻底修改(先把
输入头部按字节拷贝到某个地方然后再转变可能代价会小一些)。
注意输出数据包可以可按任意方式对齐(也就是说,它可以很容易的从奇字节边界开始)。
注46:注意对连接表的两个最常见的操作是终止于第一个入口的查找(最近使用连接的一
个新的数据包)以及把表的最后一个入口移到表的头部(新连接来的第一个数据包)。一个
循环表可以有效的处理这两个操作。
Jacobson[Page31]
1144Compressing/IPHeadersFebruary1990
u_char
sl_compress_tcp(m,comp,compress_cid)
structmbuf*m;
structslcompress*comp;
intcompress_cid;
{
registerstructcstate*cs=comp->last_cs->cs_next;
registerstructip*ip=mtod(m,structip*);
registeru_inthlen=ip->ip_hl;
registerstructtcphdr*oth;/*lastheader*/
registerstructtcphdr*th;/*currentheader*/
registeru_intdeltaS,deltaA;/*generalpurposetemporaries*/
registeru_intchanges=0;/*changemask*/
u_charnew_seq[16];/*changesfromlasttocurrent*/
registeru_char*cp=new_seq;
/*BailifthisisanIPfragmentorifthepacketisn't
*`compressible'(i.e.,ACKisn'tsetorsomeothercontrolbitis
*set).(Weassumethatthecallerhasalreadymadesurethepacket
*isIPproto).*/
if((ip->ip_off&htons(0x3fff))||m->m_len<40)
return(TYPE_IP);
th=(structtcphdr*)&((int*)ip)[hlen];
if((th->th_flags&(TH_SYN|TH_FIN|TH_RST|TH_ACK))!=TH_ACK)
return(TYPE_IP);
/*Packetiscompressible--we'regoingtosendeithera
*COMPRESSED_orUNCOMPRESSED_packet.Eitherwayweneedto
*locate(orcreate)theconnectionstate.Specialcasethemost
*recentlyusedconnectionsinceit'smostlikelytobeusedagain&
*wedon'thavetodoanyreorderingifit'sused.*/
if(ip->ip_src.s_addr!=cs->cs_ip.ip_src.s_addr||
ip->ip_dst.s_addr!=cs->cs_ip.ip_dst.s_addr||
*(int*)th!=((int*)&cs->cs_ip)[cs->cs_ip.ip_hl]){
/*Wasn'tthefirst--searchforit.
*Statesarekeptinacircularlylinkedlistwithlast_cs
*pointingtotheendofthelist.Thelistiskeptinlru
*orderbymovingastatetotheheadofthelistwhenever
*itisreferenced.Sincethelistisshortand,
*empirically,theconnectionwewantisalmostalwaysnear
*thefront,welocatestatesvialinearsearch.Ifwe
*don'tfindastateforthedatagram,theoldeststateis
*(re-)used.*/
registerstructcstate*lcs;
registerstructcstate*lastcs=comp->last_cs;
do{
lcs=cs;
cs=cs->cs_next;
if(ip->ip_src.s_addr==cs->cs_ip.ip_src.s_addr
&&ip->ip_dst.s_addr==cs->cs_ip.ip_dst.s_addr
&&*(int*)th==((int*)&cs->cs_ip)[cs->cs_ip.ip_hl])
gotofound;
Jacobson[Page32]
1144Compressing/IPHeadersFebruary1990
}while(cs!=lastcs);
/*Didn'tfindit--re-useoldestcstate.Sendan
*uncompressedpacketthattellstheothersidewhat
*connectionnumberwe'reusingforthisconversation.Note
*thatsincethestatelistiscircular,theoldeststate
*pointstothenewestandweonlyneedtosetlast_csto
*updatethelrulinkage.*/
comp->last_cs=lcs;
hlen+=th->th_off;
hlen<<=2;
gotouncompressed;
found:
/*Foundit--movetothefrontontheconnectionlist.*/
if(lastcs==cs)
comp->last_cs=lcs;
else{
lcs->cs_next=cs->cs_next;
cs->cs_next=lastcs->cs_next;
lastcs->cs_next=cs;
}
}
/*
*Makesurethatonlywhatweexpecttochangechanged.Thefirst
*lineofthe`if'checkstheIPprotocolversion,headerlength&
*typeofservice.The2ndlinechecksthe"Don'tfragment"bit.
*The3rdlinechecksthetime-to-liveandprotocol(theprotocol
*checkisunnecessarybutcostless).The4thlinechecksthe
*headerlength.The5thlinechecksIPoptions,ifany.The6th
*linechecksoptions,ifany.Ifanyofthesethingsare
*differentbetweentheprevious¤tdatagram,wesendthe
*currentdatagram`uncompressed'.
*/
oth=(structtcphdr*)&((int*)&cs->cs_ip)[hlen];
deltaS=hlen;
hlen+=th->th_off;
hlen<<=2;
if(((u_short*)ip)[0]!=((u_short*)&cs->cs_ip)[0]||
((u_short*)ip)[3]!=((u_short*)&cs->cs_ip)[3]||
((u_short*)ip)[4]!=((u_short*)&cs->cs_ip)[4]||
th->th_off!=oth->th_off||
(deltaS>5&&BCMP(ip+1,&cs->cs_ip+1,(deltaS-5)<<2))||
(th->th_off>5&&BCMP(th+1,oth+1,(th->th_off-5)<<2)))
gotouncompressed;
/*
*Figureoutwhichofthechangingfieldschanged.Thereceiver
Jacobson[Page33]
1144Compressing/IPHeadersFebruary1990
*expectschangesintheorder:urgent,window,ack,seq.
*/
if(th->th_flags&TH_URG){
deltaS=ntohs(th->th_urp);
ENCODEZ(deltaS);
changes|=NEW_U;
}elseif(th->th_urp!=oth->th_urp)
/*
*argh!URGnotsetbuturpchanged--asensible
*implementationshouldneverdothisbut793doesn't
*prohibitthechangesowehavetodealwithit.
*/
gotouncompressed;
if(deltaS=(u_short)(ntohs(th->th_win)-ntohs(oth->th_win))){
ENCODE(deltaS);
changes|=NEW_W;
}
if(deltaA=ntohl(th->th_ack)-ntohl(oth->th_ack)){
if(deltaA>0xffff)
gotouncompressed;
ENCODE(deltaA);
changes|=NEW_A;
}
if(deltaS=ntohl(th->th_seq)-ntohl(oth->th_seq)){
if(deltaS>0xffff)
gotouncompressed;
ENCODE(deltaS);
changes|=NEW_S;
}
/*
*Lookforthespecial-caseencodings.
*/
switch(changes){
case0:
/*
*Nothingchanged.Ifthispacketcontainsdataandthelast
*onedidn't,thisisprobablyadatapacketfollowingan
*ack(normalonaninteractiveconnection)andwesendit
*compressed.Otherwiseit'sprobablyaretransmit,
*retransmittedackorwindowprobe.Sendituncompressed
*incasetheothersidemissedthecompressedversion.
*/
if(ip->ip_len!=cs->cs_ip.ip_len&&
ntohs(cs->cs_ip.ip_len)==hlen)
break;
/*(fallthrough)*/
caseSPECIAL_I:
Jacobson[Page34]
1144Compressing/IPHeadersFebruary1990
caseSPECIAL_D:
/*
*Actualchangesmatchoneofourspecialcaseencodings--
*sendpacketuncompressed.
*/
gotouncompressed;
caseNEW_S|NEW_A:
if(deltaS==deltaA&&
deltaS==ntohs(cs->cs_ip.ip_len)-hlen){
/*specialcaseforechoedterminaltraffic*/
changes=SPECIAL_I;
cp=new_seq;
}
break;
caseNEW_S:
if(deltaS==ntohs(cs->cs_ip.ip_len)-hlen){
/*specialcasefordataxfer*/
changes=SPECIAL_D;
cp=new_seq;
}
break;
}
deltaS=ntohs(ip->ip_id)-ntohs(cs->cs_ip.ip_id);
if(deltaS!=1){
ENCODEZ(deltaS);
changes|=NEW_I;
}
if(th->th_flags&TH_PUSH)
changes|=_PUSH_BIT;
/*
*Grabthecksumbeforeweoverwriteitbelow.Thenupdateour
*statewiththispacket'sheader.
*/
deltaA=ntohs(th->th_sum);
BCOPY(ip,&cs->cs_ip,hlen);
/*
*Wewanttousetheoriginalpacketasourcompressedpacket.(cp-
*new_seq)isthenumberofbytesweneedforcompressedsequence
*numbers.Inadditionweneedonebyteforthechangemask,one
*fortheconnectionidandtwoforthetcpchecksum.So,(cp-
*new_seq)+4bytesofheaderareneeded.hlenishowmanybytes
*oftheoriginalpackettotosssosubtractthetwotogetthenew
*packetsize.
*/
deltaS=cp-new_seq;
cp=(u_char*)ip;
if(compress_cid==0||comp->last_xmit!=cs->cs_id){
comp->last_xmit=cs->cs_id;
Jacobson[Page35]
1144Compressing/IPHeadersFebruary1990
hlen-=deltaS+4;
cp+=hlen;
*cp++=changes|NEW_C;
*cp++=cs->cs_id;
}else{
hlen-=deltaS+3;
cp+=hlen;
*cp++=changes;
}
m->m_len-=hlen;
m->m_off+=hlen;
*cp++=deltaA>>8;
*cp++=deltaA;
BCOPY(new_seq,cp,deltaS);
return(TYPE_COMPRESSED_);
uncompressed:
/*
*Updateconnectionstatecs&senduncompressedpacket
*('uncompressed'meansaregularip/tcppacketbutwiththe
*'conversationid'wehopetouseonfuturecompressedpacketsin
*theprotocolfield).
*/
BCOPY(ip,&cs->cs_ip,hlen);
ip->ip_p=cs->cs_id;
comp->last_xmit=cs->cs_id;
return(TYPE_UNCOMPRESSED_);
}
Jacobson[Page36]
1144Compressing/IPHeadersFebruary1990
A.3解压缩
该子程序对一个收到的数据包进行解压。调用参数是一个指向待解压数据包的指针,数
据包的长度和类型,以及一个指向输入串行线路的压缩状态结构的指针。如果正确则返回指
向结果数据包的指针,如果输入数据包中有错误,返回0,如果数据包类型为COMPRESSED_
或UNCOMPRESSED_,压缩状态将被更新。
新的数据包“取代式”地构建得到。这意味着在bufp前必须由128字节的空间以重新构
建IP和头部。重新构建后的数据包将在32位边界处对齐。
u_char*
sl_uncompress_tcp(bufp,len,type,comp)
u_char*bufp;
intlen;
u_inttype;
structslcompress*comp;
{
registeru_char*cp;
registeru_inthlen,changes;
registerstructtcphdr*th;
registerstructcstate*cs;
registerstructip*ip;
switch(type){
caseTYPE_ERROR:
default:
gotobad;
caseTYPE_IP:
return(bufp);
caseTYPE_UNCOMPRESSED_:
/*
*Locatethesavedstateforthisconnection.Ifthestate
*indexislegal,clearthe'discard'flag.
*/
ip=(structip*)bufp;
if(ip->ip_p>=MAX_STATES)
gotobad;
cs=&comp->rstate[comp->last_recv=ip->ip_p];
comp->flags&=~SLF_TOSS;
/*
*RestoretheIPprotocolfieldthensaveacopyofthis
*packetheader.(Thechecksumiszeroedinthecopysowe
*don'thavetozeroiteachtimeweprocessacompressed
Jacobson[Page37]
1144Compressing/IPHeadersFebruary1990
*packet.
*/
ip->ip_p=IPPROTO_;
hlen=ip->ip_hl;
hlen+=((structtcphdr*)&((int*)ip)[hlen])->th_off;
hlen<<=2;
BCOPY(ip,&cs->cs_ip,hlen);
cs->cs_ip.ip_sum=0;
cs->cs_hlen=hlen;
return(bufp);
caseTYPE_COMPRESSED_:
break;
}
/*We'vegotacompressedpacket.*/
cp=bufp;
changes=*cp++;
if(changes&NEW_C){
/*
*Makesurethestateindexisinrange,thengrabthe
*state.Ifwehaveagoodstateindex,clearthe'discard'
*flag.
*/
if(*cp>=MAX_STATES)
gotobad;
comp->flags&=~SLF_TOSS;
comp->last_recv=*cp++;
}else{
/*
*Thispackethasanimplicitstateindex.Ifwe'vehada
*lineerrorsincethelasttimewegotanexplicitstate
*index,wehavetotossthepacket.
*/
if(comp->flags&SLF_TOSS)
return((u_char*)0);
}
/*
*FindthestatethenfillinthechecksumandPUSHbit.
*/
cs=&comp->rstate[comp->last_recv];
hlen=cs->cs_ip.ip_hl<<2;
th=(structtcphdr*)&((u_char*)&cs->cs_ip)[hlen];
th->th_sum=htons((*cp<<8)|cp[1]);
cp+=2;
if(changes&_PUSH_BIT)
th->th_flags|=TH_PUSH;
else
th->th_flags&=~TH_PUSH;
/*
Jacobson[Page38]
1144Compressing/IPHeadersFebruary1990
*Fixupthestate'sack,seq,urgandwinfieldsbasedonthe
*changemask.
*/
switch(changes&SPECIALS_MASK){
caseSPECIAL_I:
{
registeru_inti=ntohs(cs->cs_ip.ip_len)-cs->cs_hlen;
th->th_ack=htonl(ntohl(th->th_ack)+i);
th->th_seq=htonl(ntohl(th->th_seq)+i);
}
break;
caseSPECIAL_D:
th->th_seq=htonl(ntohl(th->th_seq)+ntohs(cs->cs_ip.ip_len)
-cs->cs_hlen);
break;
default:
if(changes&NEW_U){
th->th_flags|=TH_URG;
DECODEU(th->th_urp)
}else
th->th_flags&=~TH_URG;
if(changes&NEW_W)
DECODES(th->th_win)
if(changes&NEW_A)
DECODEL(th->th_ack)
if(changes&NEW_S)
DECODEL(th->th_seq)
break;
}
/*UpdatetheIPID*/
if(changes&NEW_I)
DECODES(cs->cs_ip.ip_id)
else
cs->cs_ip.ip_id=htons(ntohs(cs->cs_ip.ip_id)+1);
/*
*Atthispoint,cppointstothefirstbyteofdatainthepacket.
*Ifwe'renotalignedona4-byteboundary,copythedatadownso
*theIP&headerswillbealigned.Thenbackupcpbythe
*/IPheaderlengthtomakeroomforthereconstructedheader(we
*assumethepacketwewerehandedhasenoughspacetoprepend128
*bytesofheader).Adjustthelenthtoaccountforthenewheader
*&fillintheIPtotallength.
*/
len-=(cp-bufp);
if(len<0)
/*
*wemusthavedroppedsomecharacters(crcshoulddetect
*thisbuttheoldslipframingwon't)
Jacobson[Page39]
1144Compressing/IPHeadersFebruary1990
*/
gotobad;
if((int)cp&3){
if(len>0)
OVBCOPY(cp,(int)cp&~3,len);
cp=(u_char*)((int)cp&~3);
}
cp-=cs->cs_hlen;
len+=cs->cs_hlen;
cs->cs_ip.ip_len=htons(len);
BCOPY(&cs->cs_ip,cp,cs->cs_hlen);
/*recomputetheipheaderchecksum*/
{
registeru_short*bp=(u_short*)cp;
for(changes=0;hlen>0;hlen-=2)
changes+=*bp++;
changes=(changes&0xffff)+(changes>>16);
changes=(changes&0xffff)+(changes>>16);
((structip*)cp)->ip_sum=~changes;
}
return(cp);
bad:
comp->flags|=SLF_TOSS;
return((u_char*)0);
}
Jacobson[Page40]
1144Compressing/IPHeadersFebruary1990
A.4初始化
本子程序对某条串行链路的传输方和接收方的状态结构都进行初始化。它在每一条
链路建立时被调用。
void
sl_compress_init(comp)
structslcompress*comp;
{
registeru_inti;
registerstructcstate*tstate=comp->tstate;
/*
*Cleanoutanyjunkleftfromthelasttimelinewasused.
*/
bzero((char*)comp,sizeof(*comp));
/*
*Linkthetransmitstatesintoacircularlist.
*/
for(i=MAX_STATES-1;i>0;--i){
tstate[i].cs_id=i;
tstate[i].cs_next=&tstate[i-1];
}
tstate[0].cs_next=&tstate[MAX_STATES-1];
tstate[0].cs_id=0;
comp->last_cs=&tstate[0];
/*
*Makesurewedon'taccidentallydoCIDcompression
*(assumesMAX_STATES<255).
*/
comp->last_recv=255;
comp->last_xmit=255;
}
A.5BerkeleyUnix的依赖关系
注意:如果在你想把该例子代码运行于不是4BSD(BerkleyUnix)的系统上时才有用。
这里的代码使用了规范的BerkeleyUnix头文件(位于/usr/include/netinet)来定义IP
和头部。结构标志(structuretags)遵循的,即使你没有访问4BSD系统,
也应该是能看懂(注47)。
注48.如果不能看懂,这些头文件(和所有的Berkeley网络代码)可以通过匿名ftp从主
机ucbarpa.berkeley.edu获取,文件分别为pub/4.3/tcp.tar以及pub/4.3/inet.tar.
Jacobson[Page41]
1144Compressing/IPHeadersFebruary1990
宏BCOPY(src,dst,amt)调用来从src拷贝amt个字节到dst。在BSD中,它被转化为
bcopy调用。如果你不幸运行于System-VUnix环境下,它将被转化为memcpy调用。宏
OVBCOPY(src,dst,amt)用来在当src和dst覆盖(overlap)时的拷贝(也就是说,在做
4-字节对齐的拷贝时)。在BSD内核中,它转化为一个ovbcopy调用。因为AT&T修补了
memcpy的定义,可能应该转化为一个System-V下的copy循环。
宏BCMP(src,dst,amt)调用来比较src和dst的amt个字节是否相等。在BSD中,它被
转化为bcmp调用。在System-V中,它被转化为memcmp调用,你也可以自己写一个子程序
来完成这些比较。子程序在src和dst所有的字节都相等时返回0,否则返回非0值。
子程序ntohl(dat)把(4个字节)的long数据从网络字节序(networkbyteorder)
转化为主机字节序(hostbyteorder)。在一个合理的cpu中,这可能是“什么都不做的”
宏定义:
#definentohl(dat)(dat)
在一个Vax或者IBMPC(或者任何Intel字节序的系统)中,你将必须定义一个宏或者子
程序来重新安排这些字节。
子程序ntohs(dat)与ntohl相似,但是转化的是(2个字节的)short而不是long。子程
序htonl(dat)和htons(dat)完成long和short的相反的转换(主机字节序到网络字节序)。
结构mbuf在调用sl_compress_tcp时使用,因为该子程序在如果输入数据包是压缩数
据包时需要修改startaddress和length。在BSD中,mbuf是内核的缓冲结构(buffer
managementstructure)。如果是其它的系统,下面的定义应该足够了:
structmbuf{
u_char*m_off;/*pointertostartofdata*/
intm_len;/*lengthofdata*/
};
#definemtod(m,t)((t)(m->m_off))
Jacobson[Page42]
1144Compressing/IPHeadersFebruary1990
附录B与过去错误的兼容性
在与现代的串行链路(参考文献[9])联合使用时,头部压缩自动进行对用户
来说是不可见的。不幸的是,很多站点还有使用参考文献[12]中定义的SLIP的用户,该
没有顾及到不同的类型来区分头部经过压缩的数据包和IP数据包,也没有顾及到版本
号,也没有顾及到用来自动协商头部压缩的选项。
作者已经使用下面的技巧来允许头部经过压缩的SLIP与现存的服务器和客户端实现互
操作。注意这是用来与过去错误兼容的手段,思维正确(rightthinking)的人应该把它视
为具有攻击性的。在这里提供出来只是为了减小在运行SLIP时用户等待vendors释放
的痛苦。
B.1没有“type”字节照样存活
选用A.1中奇怪的数据包类型号是为了允许在不想或者不可能增加一个显式type
字节时链路上发送数据包的类型。注意IP数据包的第一个字节的头4位总是包含“4”(即
IP版本号)。同时压缩数据包头部第一个字节的最高有效位(themostsignificant
bit)总是被忽略。使用A.1中的数据包类型,则type可以使用下面的代码编码到输出数据
包的那几个最高有效位中:
p->dat[0]|=sl_compress_tcp(p,comp);
在接收方的解码为
if(p->dat[0]&0x80)
type=TYPE_COMPRESSED_;
elseif(p->dat[0]>=0x70){
type=TYPE_UNCOMPRESSED_;
p->dat[0]&=~0x30;
}else
type=TYPE_IP;
status=sl_uncompress_tcp(p,type,comp);
B.2向后兼容SLIP服务器
参考文献[12]中描述的SLIP不包括能用来自动协商头部压缩的机制。允许这种SLIP
的用户使用头部压缩是件好事,但是,如果这两种类型的SLIP用户使用同一个服务器,手
动配置每一个连接的两端使之能够使用本压缩将是一件很烦人的事情。下面的过程用来避免
手动配置。
因为有两种类型的拨号客户端(dial-inclients)即使用压缩的和不使用压缩的,而同
一个服务器要为这两种类型的客户端服务。很明显服务器将要重新配置每一个新的客户端会
话,但客户端就是更改也很少改变配置。如果必须手动配置,它应该在不经常改动的一方进
行——即客户端。这就暗示着服务器应该以某种方式从客户端得知是否使用头部压缩。假设
由于对称性(也就是说,如果使用则应该在两个方向上都使用压缩)服务器可以通过来自客
户端的压缩数据包来暗示着它可以向客户端发送压缩数据包。这就导致了下面的算法:
每一条链路用两位来控制头部压缩:allowed和on。如果设置了on位,发送的是压缩数
据包,否则不发送压缩数据包。如果设置了allowed位,可以接收压缩数据包,如果收到的
UNCOMPRESSED_数据包没有设置on位,则设置on位(注49)。如果收到压缩数据包并且
没有设置allowed位,数据包将被忽略。
客户端配置为两位都设置(如果设置了on则总是设置allowed),服务器开始每一个会话
时设置allowed位,清除on位。客户端来的第一个压缩数据包(一定是一个
UNCOMPRESSED_数据包)将为服务器打开压缩功能。
注49:因为参考文献[12]中的帧不包括错误检测,必须注意千万别把服务器的压缩开关设
置为false。在压缩被使能之前,UNCOMPRESSED_数据包应该检查连续性(例如,IP检验
和的正确性)。COMPRESSED_数据包的到达不应该用来使能压缩。
Jacobson[Page44]
1144Compressing/IPHeadersFebruary1990
C更主动的压缩
如3.2.2中指出的那样,压缩头部中存在很容易检测到的特征,表明可以进行进一步
的压缩。这有价值么?
压缩后的数据包的头部仅有7个比特(注50)。帧必须至少为一个比特(已表明“type”),
更可能的情况是2到3个字节。在大多数有趣的场合,将至少有一个字节的数据。最后,端
到端的检查——checksum——必须未加修改地加以传递(注51)。
帧,数据和checksum将保留即使头部完全地压缩,所以数据包的平均大小最多从4个字
节降到三个字节加一个比特——大约把延迟性能提高25%(注52)。这个提高可能看起来很
大,在一条2400bps的链路上,它意味着击键回显的响应时间为25毫秒而不是29毫秒。
在当前人类进化阶段,这个差别根本无法感觉出来(detectable)。
但是,作者坦率地承认把该压缩方案曲解为一种非常特殊情形数据获取问题:我们有漂
浮与200KV之上的工具和控制包,通过遥测系统与地面通信。由于很多原因(多路复用通信,
流水线操作,错误恢复,精心检测过的实现的可用性等等),跟这个包使用/IP通信是很方
便的。但是,因为遥感链路的基本用处就是获取数据,设计成为上传信道<下传信道容量的
0.5%。为了满足应用延迟性要求,数据包为100个字节,并且,因为每隔一个数据包确
认一次,相关的上传信道的确认(ack)带宽为a/200,其中a为确认数据包的总长度。如果
使用本文档的方案,最小的ack为4个字节,意味着上传链路的带宽未下传链路带宽的2%。
注50:已经对几百万个固定流量负载的数据包进行测试(也就是说,统计了以年之内从我家
到工作地之间的流量)表明80%的数据包使用两种特殊情形编码之一,这样,唯一的头部就是
changemask。
注51.如果某人试图向你出售压缩检验和的方案,一定不要买。(Justsay“no”)。某
些可怜的傻瓜仍然要继续伤心的经历来展示端到端的讨论是真理。更坏的是,因为这个傻瓜
正在捣乱你的端到端的错误检查,你可能为这个教训付出学费,他们一点也不聪明。得到两
个字节时间的延迟但丢失内心的平静有什么好处?
注52.再次注意在讨论时我们必须关心交互延迟时间:批量数据传输的性能主要由发送数
据的时间决定,包含成百上千个字节数据的数据报文中三个字节和四个字节头部的差异实际
上没有差别。
Jacobson[Page45]
1144Compressing/IPHeadersFebruary1990
这是不可能的,所以我们使用注15中描述的方案:如果帧中第一个比特位为1,意味着压缩
数据包头部与上一次的相同。否则接下去的两个比特位将给出3.2中描述的类型之一。因为
链路有很多转发错误(forwarderror),流量仅做一次跳跃(hop),checksum已经被
相同头部的数据包类型压缩出去(惭愧!)(注53),所以这些数据包的头部总长度就是一个
比特。在几个月的操作中,超个99%的40个字节的/IP头部都压缩降为一个比特(注54)。
D安全方面的考虑
本文档不讨论安全方面的问题。
E作者地址
Address:VanJacobson
RealTimeSystemsGroup
MailStop46A
LawrenceBerkeleyLaboratory
Berkeley,CA94720
Phone:Useemail(authorignoreshisphone)
EMail:van@helios.ee.lbl.gov
注53:检验和在解压器方已经重新产生,当然,“丢弃”(“toss”)逻辑被设计成更加主动以
防止错误的传播。
注54:我们已经听到建议,认为出于实时的需要要求放弃/IP而赞成使用具有更小头部
的轻权(light-weight'protocol)。很难想象头部平均只有一个比特的。