一、 路由套接字
1.概述
在路由器接口中支持三种类型的操作
1). 进程能通过写路由套接口向内核发消息。
2). 进程能在路由套接口上从内核读消息,这是核心通知进程已收到一个ICMP重定向消息并进行了处理的方式。
3). 进程可以用sysctl函数得到路由表或列出所有已配置的接口。
2.数据链路套接口地址结构
在路由套接口上返回的一些消息中包含数据链路套接口地址结构,他在
struct sockaddr_dl { uint8_t sdl_len; sa_family_t sdl_family; /* AF_LINK */ uint16_t sdl_index; /* system assigned index, if > 0 */ uint8_t sdl_type; /* IFT_ETHER, etc. from*/ uint8_t sdl_nlen; /* name length, starting in sdl_data[0] */ uint8_t sdl_alen; /* link-layer address length */ uint8_t sdl_slen; /* link-layer selector length */ char sdl_data[12]; /* minimum work area, can be larger; contains i/f name and link-layer address */ };
3.sysctl操作
我们对于路由套接口的主要兴趣点在于使用sysctl函数检查路由表和接口清单,使用该函数检查路由表清单不需要超级用户权限。
#include#include int sysctl(int *name, u_int namelen, void *oldp, size_t *oldlenp, void *newp, size_t newlen); //返回:若成功为0,若出错-1
这个函数使用类似SNMP(简单网络管理协议)MIB(管理信息库)的名字
参数name是指定名字的一个整数数组,namelen是数组中的元素数目。数组的第一个元素指明请求被发往内核的哪个子系统,第二个参数指明这个子系统的某个部分,依次类推。要取一个值,oldp需指向一个缓冲区,以让内核存放该值。oldlenp是一个值-结果参数:调用函数时oldlenp指向的值是缓冲区的大小,返回的值是内核在缓冲区中返回的数据量,如果缓冲区不够大,就返回ENOMEM错误。作为一个特例,oldp可以是一个空指针而oldlenp是一个非空指针,内核确定这个调用本应返回的数据量,并通过oldlenp返回这个值。要设置一个新值,newp需指向一个大小为newlen的缓冲区,如果没有指定新值,newp应为一个空指针,newlen应为0
我们感兴趣的是网络子系统,通过把名字数组的第一个元素设为CTL_NET来指定,第二个元素可以是:
- AF_INET: 获取或者设置影响网际协议的变量。下一级为使用某个IPPROTO_XXX常值指定的具体协议。
- AF_LINK: 获取或设置链路层信息,例如:PPP接口的数目。
- AF_ROUTE: 返回路由表或接口清单的信息。
- AF_UNSPEC: 获取或设置一些套接口层变量,例如套接口发送或接收缓冲区的最大大小
现在提供一个sysctl的简单例子,这个例子使用网际协议检查UDP校验和是否打开:
#include "unproute.h" #include#include #include /* for UDPCTL_xxx constants */ int main(int argc, char **argv) { int mib[4], val; size_t len; mib[0] = CTL_NET; mib[1] = AF_INET; mib[2] = IPPROTO_UDP; mib[3] = UDPCTL_CHECKSUM; len = sizeof(val); Sysctl(mib, 4, &val, &len, NULL, 0); printf("udp checksum flag: %d\n", val); exit(0) ; }
下图给出sysctl的CTL_NET/AF_ROUTE/NET_RT_IFLIST命令返回的信息
4.接口名字和索引函数
下面四个函数用于需要描述一个解耦的场合,这里存在一个概念,即每个接口都有一个唯一的名字和一个唯一的正值索引(0从不用做索引)
#includeif.h> unsigned int if_nametoindex(const char * ifname); // 返回:成功时为正的接口索引,出错时为0 char * if_indextoname(unsigned int ifindex, char * ifname); // 返回: 成功时为指向接口名的指针,出错时为NULL struct if_nameindex * if_nameindex(void); //返回: 成功时为非空指针,出错时为NULL void if_freenameindex(struct if_nameindex * ptr);
if_nametoindex返回名为ifname的接口的索引,if_indextoname对给定的ifindex返回一个指向其接口名的指针,ifname参数指向一个大小为IFNAMSIZ头文件中定义的缓冲区,调用者必须分配这个缓冲区以保存结果,成功时这个指针也是函数的返回值,if_nameindex返回一个指向if_nameindex结构的数组的指针
二、密钥管理套接字
1.支持的唯一套接字是原始套接字
2.IPSec基于安全关联(security association,SA)为分组提供安全服务,其描述了源地址和目的地址(加上可选的传输协议和端口)、机制(认证)以及密钥素材的组合。单个分组交通流上都可以应用不止一个SA(例如一个用于认真,一个用于加密)。存放在一个系统中的所有SA构成了安全观念数据库(SADB).
3.IPSec还需要一个安全策略数据库(security policy database,SPDB).SPDB描述分组流通的需求,例如主机A和主机B之间的分组流通必须使用IPsec AH认证。
4.支持3种类型操作
- 向内核和打开的密钥管理套接字发送消息。可以从密钥管理守护进程请求密钥
- 可以从密钥管理套接字读入,可以请求某个密钥管理守护进程为依照策略需受保护的一个新的TCP会话安装一个SA.
- 发送一个dump消息。
5.创建静态关联
- 安全参数索引结合目的地址和所用协议唯一标识一个SA.
- 通过填好所有消息,发送SADB_ADD消息来创建静态关联。
- SADB_ADD消息必须的扩展有3个:SA,地址和密钥
6.动态维护安全关联
向密钥管理套接字发送请求,密钥管理套接字创建SA,并和远端协商安全参数,返回需要的SA.
详见UNP
三、广播
1.广播地址
如果用{netid, subnetid, hostid}( {网络ID,子网ID,主机ID})表示IPv4地址。那么有四种类型的广播地址,我们用-1表示所有比特位均为1的字段:
1). 子网广播地址:{netid, subnetid, -1}。这类地址编排指定子网上的所有接口。
2). 全部子网广播地址:{netid, -1, -1}。这类广播地址编排指定网络上的所有子网。
3). 网络广播地址:{netid, -1}。这类地址用于不进行子网划分的网络。
4). 受限广播地址:{-1, -1, -1}。路由器从不转发目的地址为255.255.255.255的IP数据
2.单播和广播的比较
单播IP数据报仅有通过目的IP地址指定的单个主机接受,子网上的其他主机均不受影响;
子网上所有未参加相应广播应用的所有机也必须沿协议栈一路向上完整的处理收取UDP广播数据报,直至该数据包经历UDP层时被丢弃为止
3.竞争状态
多个进程访问共享数据,但正确结构依赖于进程的执行顺序,这种情况我们称之为竞争状态(race condition)。竞争状态通常是线程编程中始终要注意的一个重要问题,因为在线程中有非常之多的数据需要共享,例如所有的全程变量。
在进行信号处理时,通常会出现各种类型的竞争状态。这是因为在我们的程序执行过程中,内核随时都会递交信号。
4.使用广播的dg_cli函数
#include "unp.h" static void recvfrom_alarm(int); void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen) { int n; const int on = 1; char sendline[MAXLINE], recvline[MAXLINE + 1]; socklen_t len; struct sockaddr *preply_addr; preply_addr = Malloc(servlen); Setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)); Signal(SIGALRM, recvfrom_alarm); while (Fgets(sendline, MAXLINE, fp) != NULL) { Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen); alarm(5); for ( ; ; ) { len = servlen; n = recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &len); if (n < 0) { if (errno == EINTR) break; /* waited long enough for replies */ else err_sys("recvfrom error"); } else { recvline[n] = 0; /* null terminate */ printf("from %s: %s", Sock_ntop_host(preply_addr, len), recvline); } } } free(preply_addr); } static void recvfrom_alarm(int signo) { return; /* just interrupt the recvfrom() */ }
四、多播
1.多播地址
1).IPv4多播地址和IPv6多播地址、IPv6多播地址
IPv4中的D类地址(从224.0.0.0到239.255.255.255)是多播地址。D类地址的低28位构成了多播组ID(group ID),而整个32位地址则称为组地址(group address)。
下面是多播地址映射到以太网地址的方法:
下面的IPv6的地址格式:
2).多播地址的范围
IPv6多播地址有一个4位的显示范围字段,该字段决定了多播数据报能够游走的范围。IPv6分组也有一个跳限字段,它限制分组被路由器转发的次数。下面是几个已经分配了的范围字段值:
- 1:节点局部即局部于节点(node-local)
- 2:链路局部即局部于链路(link-local)
- 3:网点局部即局部于网点(site-local)
- 8:组织局部即局部于组织(orgainization-local)
- 14:全球(global)
其他值或者还没有分配或者已经保留。节点局部数据报禁止从接口输出,链路局部数据报不能被路由器转发,网点和组织的定义由该网点或组织的多播路由器管理员决定。
2.多播套接字选项
下面列出部分关于多播的套接字选项
3.mcast_jion和相关函数
下面的这些函数可以隐藏因为IPv4和IPv6版本有过多的差别而造成代码凌乱不堪:
#include "unp.h" int mcast_join(int sockfd, const struct sockaddr * sa, socklen_t salen, const char * ifname, u_int ifindex); //返回: 成功时为0,出错时为-1 int mcast_leave(int sockfd, const struct sockaddr * sa, socklen_t salen); //返回: 成功时为0,出错时为-1 int mcast_set_if(int sockfd, const char * ifname, u_int ifindex); //返回: 成功时为0,出错时为-1 int mcast_set_loop(int sockfd, int flag); //返回: 成功时为0,出错时为-1 int mcast_set_ttl(int sockfd, int ttl); //返回: 成功时为0,出错时为-1 int mcast_get_if(int sockfd); //返回: 成功时为非负接口索引,出错时为-1 int mcast_get_loop(int sockfd); //返回: 成功时为当前回馈标志,出错时为-1 int mcast_get_ttl(int sockfd); //返回: 成功时为当前TTL或跳限,出错时为-1
- mcast_join函数加入一个多播组,这个组的IP地址在由addr指向的套接口地址结构中,它的长度由salen指定。
- mcast_leave离开一个多播组,这个组的IP地址在由addr指向的套接口地址结构中。
- mcast_set_if给外出多播数据报设置缺省的接口索引。如果ifname非空,则指定接口名字,否则ifindex大于0,则指定了接口索引。
- mcast_set_loop设置回馈选项为1或0
- mcast_set_ttl设置IPv4的TTL或IPv6的跳限。
4.单播、广播、组播的区别小结(源自http://www.cnblogs.com/Ewin/archive/2008/11/25/1340752.html)
1.单播:
主机之间一对一的通讯模式,网络中的交换机和路由器对数据只进行转发不进行复制。如果10个客户机需要相同的数据,则服务器需要逐一传送,重复10次相同的工作。但由于其能够针对每个客户的及时响应,所以现在的网页浏览全部都是采用单播模式,具体的说就是IP单播协议。网络中的路由器和交换机根据其目标地址选择传输路径,将IP单播数据传送到其指定的目的地。
单播的优点:
1)服务器及时响应客户机的请求
2)服务器针对每个客户不通的请求发送不通的数据,容易实现个性化服务。
单播的缺点:
1)服务器针对每个客户机发送数据流,服务器流量=客户机数量×客户机流量;在客户数量大、每个客户机流量大的流媒体应用中服务器不堪重负。
2)现有的网络带宽是金字塔结构,城际省际主干带宽仅仅相当于其所有用户带宽之和的5%。如果全部使用单播协议,将造成网络主干不堪重负。现在的P2P应用就已经使主干经常阻塞。而将主干扩展20倍几乎是不可能。
2.广播:
主机之间一对所有的通讯模式,网络对其中每一台主机发出的信号都进行无条件复制并转发,所有主机都可以接收到所有信息(不管你是否需要),由于其不用路径选择,所以其网络成本可以很低廉。有线电视网就是典型的广播型网络,我们的电视机实际上是接受到所有频道的信号,但只将一个频道的信号还原成画面。在数据网络中也允许广播的存在,但其被限制在二层交换机的局域网范围内,禁止广播数据穿过路由器,防止广播数据影响大面积的主机。
广播的优点:
1)网络设备简单,维护简单,布网成本低廉
2)由于服务器不用向每个客户机单独发送数据,所以服务器流量负载极低。
广播的缺点:
1)无法针对每个客户的要求和时间及时提供个性化服务。
2)网络允许服务器提供数据的带宽有限,客户端的最大带宽=服务总带宽。例如有线电视的客户端的线路支持100个频道(如果采用数字压缩技术,理论上可以提供500个频道),即使服务商有更大的财力配置更多的发送设备、改成光纤主干,也无法超过此极限。也就是说无法向众多客户提供更多样化、更加个性化的服务。
3)广播禁止允许在Internet宽带网上传输。
3.组播:
主机之间一对一组的通讯模式,也就是加入了同一个组的主机可以接受到此组内的所有数据,网络中的交换机和路由器只向有需求者复制并转发其所需数据。主机可以向路由器请求加入或退出某个组,网络中的路由器和交换机有选择的复制并传输数据,即只将组内数据传输给那些加入组的主机。这样既能一次将数据传输给多个有需要(加入组)的主机,又能保证不影响其他不需要(未加入组)的主机的其他通讯。
组播的优点:
1)需要相同数据流的客户端加入相同的组共享一条数据流,节省了服务器的负载。具备广播所具备的优点。
2)由于组播协议是根据接受者的需要对数据流进行复制转发,所以服务端的服务总带宽不受客户接入端带宽的限制。IP协议允许有2亿6千多万个组播,所以其提供的服务可以非常丰富。
3)此协议和单播协议一样允许在Internet宽带网上传输。
组播的缺点:
1)与单播协议相比没有纠错机制,发生丢包错包后难以弥补,但可以通过一定的容错机制和QOS加以弥补。
2)现行网络虽然都支持组播的传输,但在客户认证、QOS等方面还需要完善,这些缺点在理论上都有成熟的解决方案,只是需要逐步推广应用到现存网络当中