欢迎您访问科普小知识本站旨在为大家提供日常生活中常见的科普小知识,以及科普文章!
您现在的位置是:首页  > 科普文章

ADSL接入及共享技术的实现(二)

科普小知识2022-09-05 13:56:55
...

encaplength、encapdata 针对 rfc1483bridged、rfc1483routed、atmarp 等协议的规定进行相应初始化及数据填充,供底层驱动在封装、解封装时直接作为依据。

pcookie 是 muxbind 底层设备驱动后返回的设备索引号,以便系统卸载网络服务子层时可解除该绑定。

ifp 是网络服务子层 attach至协议栈时返回的子网接口索引号,以便系统卸载该 service 时可解除绑定。

6.2.3 service 协议数据收发流程

service 正确绑定至协议栈后,就可以接收sar end驱动层传入的该协议类型数据报文并对其处理,根据处理的结果选择转发或丢弃;对于协议栈下发的报文,也可以方便地进行相应协议封装,再交给 end驱动发送。其收发流程框图如图 6-1 所示。

图6-1 service 收发流程示意图

接收流程(虚线箭头所示):

1)sar 模块收到报文,产生中断,进入接收中断处理例程

2)调用end_rcv_rtn_call将报文返回给协议栈(reserved 中存放协议类型)

3)mux层询问该报文的协议类型

4)sar驱动层将 reserved 中的类型返回给mux层

5)mux层根据协议类型将报文提交给相应的 service.

6)service 解封装后,调用 do_protocol_with_type将报文提交给协议栈

发送流程(实线箭头所示):

1)协议栈通过mux层发送报文

2)mux 层将报文传给预先绑定的 service

3)service 对数据进行封装后,return false,将数据返回 mux 层

4)mux 层将数据发送到 sar 模块相应的 pvc 接口

5)sar 模块相应的 pvc 接口发送该报文。Www.11665.COM

6.3 rfc1483协议报文处理

由于 rfc1483数据报文的封装格式比较简单,我们将该类型报文的封装、解封装例程放在 end 驱动中实现,以提高系统对该类型报文的处理效率。

6.3.1 rfc1483 报文封装格式

rfc1483 标准描述了atm网络上承载无连接网络互连业务(即路由和桥接的协议数据单元)的两种不同方法。第一种方法允许在一条 atm 虚电路上复用多个协议,这是需要在所传送的 pdu 前加上 ieee802.2 逻辑链路控制信头,以此来表示所传递的 pdu 的协议,这种方法称为 llc 封装; 第二种方法是一个高层协议由一条atm虚电路来承载,这种方法成为基于 vc 的复用。

6.3.1.1 llc 封装

在 llc 封装中,路由协议通过在 pdu 前加一个ieee802.2 llc 信头来进行标识, ieee802.2 llc 信头后接 ieee802.1a 子网络附属点(snap)信头。在 llc 类型 1 的操作中,llc 信头包括 3 个字节,如下:

dsap

ssap

ctrl

在用于路由协议的llc 封装中,llc信头编码有两种格式:一种为 0xfe-fe-03, 用于说明后接的是 iso 路由协议的 pdu;另一种为 0xaa-aa-03,用于说明后接的是非 iso 路由协议。控制字段编码为 0x03,用于说明后接的是无编号信息的命令 pdu。

对于 iso 路由协议pdu,其aal5 cpcs-pdu 净荷字段的格式为

llc 0xfe-fe-03

iso pdu (up to 2^16 - 4 octets)

对于非 iso 路由pdu (如 ip 协议),其封装格式为

llc

0xaa-aa-03

oui

00-00-00

ethernet type(2字节)

非iso pdu (up to 2^16 - 4 octets)

(其中,如果是 ip 报文的话,ethernet type 值为 0x0800)

对于桥接协议报文,在 llc 封装的 snap 信头中应标识出桥接媒体的类型。与非 iso 路由协议的封装一样,llc信头编码值 0xaa-aa-03 表示存在 snap 信头, snap 信头中的oui值是 802.1组织编码 0x00-80-c2,桥接媒体的实际类型由2字节的 pid 来标识。此外, pid还标识是否在封装的桥接 pdu 中保留源帧校验序列(fcs)。以最常见的以太网/802.3为例,用于封装 802.3 pdu的aal5 cpcs-pdu净荷帧格式如下

llc 0xfe-fe-03

oui 0x00-80-c2

pid 0x00-01 or 0x00-07

pad 0x00-00

mac 目的地址

mac 源地址

mac 帧其它内容

帧校验(如果 pid是 00-01)

6.3.1.1 基于vc 的复用

在基于 vc 的复用中,不同的协议报文可以通过不同的 vc 通道传递,因此在 aal5的cpcp-pdu 净荷上就不再包含明确标识所承载协议的信息了,使得处理开销最小。

路由协议的 pdu 在 aal5 cpcs-pdu 的净荷中直接承载。

桥接协议的 pdu 和llc封装格式描述相同,但仅包含 pid 字段后的内容。其 aal5 cpcs-pdu 净荷字段格式如下所示:

pad 0x00-00

mac 目的地址

mac 源地址

mac 帧其它内容

帧校验(由vc 连接特性决定)

6.3.2 rfc1483 报文的封装处理

上层协议在调用 muxsend 之前,会先调用 endaddressform 函数进行二层数据封装,我们在这个函数里实现ip 报文到ethernet类型报文的数据封装。sarendsend函数里会调用 atmsrv_encap 进行 ethernet 报文到 aal5 相应协议类型的封装。

6.3.3 rfc1483 报文解封装处理

在中断接收处理函数里,会调用 atmsrv_decap 解除atm协议封装,同时将二层协议类型存于 mblkpkthdr.reserved 中,由 endpacketdataget通知 mux。

7增加 nat 支持

目前已经有很多for vxworks 的第三方 nat 协议产品, 而且 windriver 公司自己也推出了tornado 开发平台下的 nat 组件, 但价位实在太高。 由于该协议本身实现的难度不大,而且有 linux 或 bsd 的相关模块可供参考和移植,所以我们决定自己实现这个协议。难点主要在于nat收发处理函数的绑定, nat 端口地址转换hash表的建立及查找算法的实现。

7.1  nat 模块主处理流程

主要包括lan口和wan口的ip地址转换。如图 7-1 所示。

图7-1 nat 主处理流程示意图

7.2 nat 模块初始化

7.2.1 nat 数据结构初始化

1) nat_session结构,记录每一连接的连接信息:

struct nat_session

{

uchar ip_proto; //协议类型

struct in_addr src_addr, dst_addr, new_addr; //源地址,目标地址,转换后地址

/* port numbers are in host uchar order: */

union

{

/* for tcp _and_ udp: */

struct { uint16 src_port, dst_port, new_port; } tcp; //端口

/* for icmp: */

struct { uint16 src_ident, new_ident; } icmp; //identification

} u;

enum nat_session_state state; //连接状态

unsigned long timestamp; //时间戳

nat_alg *alg; //应用层网关函数指针

int alg_use; //标志,是否需要应用层网关

nat_session *parent; //ftp中指向控制连接session的指针

nat_session *next_hash; //hash表下一表项

};

2)nat_interface接口结构

struct nat_interface

{

nat_interface *next; //指向下一个接口结构

struct in_addr ipaddress; //接口地址(转换地址)

/* pointer to table of session structures, and hash table: */

nat_session *sessions; //指向session结构表

nat_session **hashtable; //指向hash结构表

int next_session;

/* pointer to table of fragment structures */

nat_fragment *fragments; //指向fragment结构表

int frag_queued; //记录保存有多少个分片队列

/* stats - counts of sessions */ //各种计数器

uint32 tcp_out, udp_out;

uint32 icmp_q_out, icmp_err_out, icmp_err_in;

uint32 discard_in, discard_out, refused_in, del_early;

};

7.2.2 nat 收发处理函数的绑定

仔细分析 vxworks 协议栈代码后,觉得使用 _ipfilterhook 绑定 nat 接收报文处理函数的方式比较可行,该钩子函数在协议栈接收到数据报文时被调用,函数输入输出格式如下:

bool natipfilterhookrtn

(

struct ifnet rcvifnet, /* 数据接收的接口*/

struct mbuf **m, /* 数据报文地址 */

struct ip **ip, /* ip 头部地址 */

int hlen /* ip 头的长度 */

)

返回值: 为 false时表示报文处理正常,协议栈可继续转发或处理

为true 时指示系统丢弃该报文

由于无法找到协议栈输出报文的钩子,我们打算把输出报文 nat 转换放在 _ipfilterhook 的 lan 口钩子中处理,即在 lan口报文进入协议栈之前就更改源ip和端口地址。但这样做的缺点是:此时系统还没有检索过该报文的路由目的接口,需要人为增加查找路由表算法,当发现是发往指定 wan 口时再行转换,处理的效率较低。

为充分利用协议栈的报文路由处理功能,决定采用更改协议栈代码的方式,在 ip_output 函数适当位置处人为增加一个钩子 _func_natoutput,重新编译 vxworks 协议栈库函数。 这样,nat 模块初始化时即可将nat输出报文处理函数绑定至该钩子。函数的输入输出关系格式如下:

int natoutput

(

struct mbuf** m0, /* 数据报文地址 */

struct ip **ip, /* ip 头部地址 */

struct in_ifaddr* ia /* 报文目的接口 */

)

返回值: ok 或 error (error 时将丢弃并释放该报文)

7.3 nat模块主要算法

7.3.1 nat 端口地址转换hash算法

napt 转换表查找算法可分为按端口查找和按地址查找两种。在实际的 adsl接入环境中,局端很少会为一个连接分配多个 ip,因此我们采用按端口查找的算法来简单实现。

1) session 结构数组的初始化

nat初始化时根据系统支持的最大转换数目建立一个按端口分布的 nat_session结构数组(nat_session 节点的定义见7.2.1), 同时建立一个指向nat_session 的空 hash 表。hash节点结构如下:

typedef struct nat_hash_bucket_

{

struct nat_hash_bucket_ *next; //指向下一个节点

struct nat_hash_bucket_ *prev; //指向前一个节点

unsigned nat_session *psession; //指向nat链表中相应的节点

}nat_hash_bucket;

2) 新建 session

get_free_session 在session静态表(结构数组)中获得一个free session,并将其从hash表中unlink出来。查找的依据为时间戳, 顺序遍历session表,直到找到第一个超时的session。若未找到,则覆盖当前指针指向的session。

填充该 session 结构,返回该session 所对应的端口号。根据 tcp/udp 报文的源 ip 和源端口号计算出 hash 头,增加到 hash 头所对应的链表后。其中,hash 头的算法如下:

local uint16 ipnat_hash_fn(ipaddrtype addr1,

ipaddrtype addr2,

uint16 port1,

uint16 port2)

{

uint16 bucket;

bucket = addr1 >> 16;

bucket ^= addr1 & 0xffff;

bucket ^= port1;

bucket ^= addr2 >> 16;

bucket ^= addr2 & 0xffff;

bucket ^= port2;

bucket = bucket % ipnat_hashlen;

return(bucket);

}

转换后的 tcp/ip 报文源ip变为 wan口ip,源端口变为session 索引值,这样做的优点是:外部返回的报文可以通过tcp/ip报文的目的端口号直接定位到 session 数组中,查找的速度最快。

3) 查找 session

通过 tcp/udp 报文的源 ip 和源端口号计算出 hash 头,然后遍历该头所对应的链表,直到找到相匹配的hash 节点。

4) 删除 session

收到 tcp/udp 连接关闭请求后,根据目的端口号找到 session 数组,释放该 session, 并将其从 hash 表中 unlink 出来。

7.3.2 tcp/udp 协议端口地址转换

setup_tcpudp_outgoing 为从内部网络出去的tcp/udp连接建立一个session,记录下连接的源地址、端口,目标地址、端口,转换后的地址、端口,协议类型以及连接建立时间等信息。

translate_outgoing_tcpudp 核心函数,根据 session 的对应记录,转换数据包的ip地址、端口,同时重新调整ip校验和及tcp/udp校验和,其中,校验和调整主要用到了adjust_chksum函数。算法如下:

void adjust_chksum(ulong *chksum, ushort oldw, ushort neww)

{

*chksum -= oldw & 0xffff;

if ((*lchksum)

{

(*chksum) --;

(*chksum) &= 0xffff;

}

(*chksum) += neww & 0xffff;

if ((*chksum) & 0x10000)

{

(*chksum) ++;

(*chksum) &= 0xffff;

}

}

8 设计总结

本系统的优点是,仅需采用一块 cpu 即可同时实现 adsl接入和路由两项功能,硬件资源利用率高,而且,两种功能在同一套系统平台中可以有机结合,避免因中间插入多余转换接口导致包处理效率降低。

文中所述的仅实现了一个最简单的 adsl 共享接入方式,并不能完全满足实际使用中的种种需求,还需要补充其它如协议报文处理、icmp报文处理、应用层网关处理等功能。 此外,在实际测试中,感觉 nat 地址转换hash 表的算法还不够理想:在新建一个节点时需要重新遍历整个 nat_session 数组,这将导致在连接数较多的情况下再新建一条连接非常困难,系统效率急剧降低。

在这次开发设计前期,由于对操作系统的架构不够熟悉,在设备驱动编写方面没有经验,导致编写的驱动层次感很差,不利于维护和后期扩展。走了很多弯路之后,总结出经验:开发前期的调研工作非常必要,在着手写代码之前应该多花些时间了解开发平台、熟悉整个系统的架构、模块在系统之间的地位及模块间的接口关系。磨刀不误砍柴工,总体规划充分之后才能编写出效率高、易维护、易扩展的模块代码出来。

参考文档

itu-t recommendation i.363.5 aal specification: type 5 aal, march 1993

ietf rfc1483. multiprotocol encapsulation over atm adaptation layer5, july.1993

ietf rfc1577. classical ip and arp over atm, january 1994

ietf rfc2364

ietf rfc2516

ietf rfc791 ip

ietf rfc768 udp

ietf rfc793 tcp

ietf rfc792 icmp

ietf rfc826 arp

ietf rfc1631 nat

vxworks network services standard。

vxworks mux接口规范。(vxworks network protocol user’s guide)。

宽带综合业务数字网与atm局域网/李津生等编著 北京:清华大学出版社,1997