本章主要介绍Linux支持IP的数据结构和基本活动,如入口IP包如何传递至IP接收函数,校验和如何验证,以及IP选项如何处理。

主要的IPv4数据结构:

struct iphdr{

};//ip报头

struct ip_options{

};//此结构代表必须被传输或转发的封包选项,那些选项存储在此结构中,而不是struct iphdr结构中

struct ipcm_cookie{

};//此结构包含了传输封包所需的各种信息

struct ipq{

};//IP封包的片段集,参见第二十二章IP片段hash表的组织一节

struct inet_peer{

};//内核会为最近连接过的每个远程主机都保留一个这一结构的实例

struct ipstats_mib{

};//SNMP(简单网络管理协议)采用一种名为MIB(Management Information Base ,管理信息库)的对象来收集系统的相关统计数据。此结构会保存关于IP层的统计资料。

struct in_device{

};//此结构存储了一个网络设备所有与IPv4相关的配置内容。net_device结构中的ip_ptr指针指向该结构

struct in_ifaddr{

};//当在接口山配置一个IPv4地址时,内核会建立一个in_faddr结构

struct ipv4_devconf{

};//该结构用于调整网络设备的行为。每个设备都有一个实例。其字段通过/proc/sys/net/ipv4/conf输出

struct ipv4_config{

};//不同于ipv4_devconf存储每个设备的配置,该结构存储每个主机的配置

struct cork{

};//该结构用于存储套接字选项CORK选项。第二十一章会看到其字段如何应用

sk_buff和net_device结构里与校验和有关的字段:

net_device->features字段表明设备的能力,其中和控制检验和计算的一些标志如下:

NETIF_F_NO_SUM:此设备很可靠,不需要使用任何L4校验和。回绕设备就开启了此c功能。

NETIF_F_IP_CSUM:此设备可以在硬件中计算L4检验和,但是只针对使用IPv4的TCP,UDP。

NETIF_F_HW_CSUM:此设备可以为任何协议在硬件中计算L4校验和。

skb_buff中主要有两个字段跟校验和有关:skb->csum和skb->ip_summed。

当一个封包被接收时,skb->csum可能包含其L4校验和,skb->ip_summed字段则会记录L4校验和的状态,这些状态代表设备驱动程序要告诉L4层的事,状态有下列这些值:

CHECKSUM_NONE:csum中的校验和无效,需要L4层自己来计算。为社么csum中的校验和无效,因为①设备不提供硬件校验和计算。②校验和必须重新计算并验证。如第十八章“对L4校验和所做的修改”一节提到的情况。

CHECKSUM_HW:NIC以L4报头和有效载荷计算了校验和,然后把校验和拷贝到skb->csum字段。软件(L4接收函数)需要把伪报头的校验和添加到skb->csum,并验证最后所得的校验和。

CHECKSUM_UNNECESSARY:NIC已经计算了L4报头以及伪报头的校验和(伪报头的校验和可以由设备驱动程序在软件中计算),所以,软件(L4接收函数)无需再计算L4的校验和。

当一个封包被传输时,skb->csum不再是校验和本是,而是指向NIC要把它计算的校验和即将存放的地方,也就是说,封包传输期间,只有当校验和是在硬件中计算时,才会用到此字段。skb->ip_summed依然表示L4校验和状态:

CHECKSUM_NONE:协议已经处理了校验和,设备不需要做任何事。

CHECKSUM_HW:协议只把伪报头的校验和存储在报头中,设备应该添加L4报头和有效载荷的校验和。

封包的一般性处理:

协议初始化:

ipv4协议由ip_init函数初始化,该函数完成以下主要任务:

为ip封包注册处理函数ip_rcv。(参见第十三章)

初始化路由子系统,包括与协议无关的缓存。(参见第三十二章)

初始化用于管理ip端点的基础架构(参见二十三章“长效ip端点信息”一节)

开机期间,ip_init会由inet_init调用。inet_init会处理所有与ipv4有关的子系统的初始化。

和Netfilter交互:

基本上,防火墙在网络堆栈程序中的某些地方都有钩子函数。当符合某些条件时,封包就会通过那些钩子函数。

与路由子系统的交互:

ip层在好几个地方必须和路由表交互。本章只简单说明ip层用于查询路由表的三个函数:

ip_route_input:决定封包是被本地传递,转发或丢弃。

ip_route_output_flow:传输封包前使用,此函数会返回下个跳点以及要使用的出口设备。

dst_pmtu:给定一个路由表缓存项目,返回相关的PMTU。

上述函数会把路由表的查询结构存储在skb->dst中。

处理输入ip封包:

下面从ip_rcv函数开始分析内核网络协议栈内ip封包的路径。ip_rcv函数原型如下:

int ip_rcv(struct sk_buff *skb,struct net_device *dev,struct packet_type *pt);

在第十章和第十三章已经知道,NIC驱动如何设定L3协议标识符skb->protocol和封包类型skb->pkt_type。当L2的目的地址和接收接口的地址不同时,skb->pkt_type = PACKET_OTHERHOST,通常这些包会被抛弃去,若接口处于混杂模式,会把这些包传给相关的嗅探器。但是,到了L3,ip_rcv只会将其丢弃:

if(skb->pkt_type == PACKET_OTHERHOST)

goto drop;

skb_share_check会检查封包的引用计数是否大于1,若处理程序看见引用计数大于1,就会自己建立一份缓冲区副本,使其可以修改封包。

if((skb = skb_share_check(skb,GFP_ATOMIC)) == NULL){

IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);

goto out;

}

pskb_may_pull的工作是确保skb->data所指区域包含的数据至少和ip头一样大。如果条件符合,则无事可做,否则缺漏的部分就会从skb_shinfo(skb)->frags[]里的数据片段(如果有的话)拷贝过来 ,之后再次初始话iph(struct iphdr)。

if(!pskb_may_pull(skb,sizeof(struct iphdr)))

goto inhdr_error;

iphdr = skb->nh.iph;

接着对ip报头做一些健康检查。

if(iph->ihl < 5 || iph->version !=4)

goto inhdr_error;

现在,重复先前做过的检查,但是这次是完整的ip头(包括选项)。

if(!pskb_may_pull(skb,iph->ihl*4))

goto inhdr_error;

iph = skb->nh.iph;

接着计算校验和。

if(ip_fast_csum((u8 *)iph,iph->ihl) != 0)

goto inhdr_error;

然后继续检查,确保接收的封包长度大于或等于ip报头中记录的长度(因为L2层可能为了满足最小传输尺寸而做了填充,所以封包长度可能大于ip头中记录的长度)。同时确保封包的此少和ip报头一样大。

{

_ _u32 len = ntohs(iph->tot_len);

if(skb->len < len || len < iph->ihl<<2))//<<2是因为报头的大小以4字节为单位,所以需要先乘以4

goto inhdr_error;

//pskb_trim_rcsum用于检查L2是否填充了封包使其达到特定的最小长度,如果有,将其剪裁成正确的大小

if(pskb_trim_rcsum(skb,len)){

IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);

goto drop;

}

}

最后来到函数的尾端,调用Netfilter子系统。

return NF_HOOL(PF_INET,NF_IP_PRE_ROUTING,    skb , dev ,NULL,ip_rcv_finish);

Netfilter子系统(确切的说,是PF_INET,NF_IP_PRE_ROUTING位置)不决定丢弃封包,则后续会执行ip_rcv_finish函数。

ip_rcv_finish函数(static inline  int ip_rcv_finish(struct sk_buff *skb) ):

ip_rcv主要做一些基本的健康检查,ip_rcv_finish会处理主要工作:

决定封包传给本地还是转发,若转发,还要找到出口设备和下个跳点。

分析和处理一些ip选项,并非所有选项都在这处理。

skb->nh字段是在netif_receive_skb里初始化的,当时,还不知道L3协议,所以用nh.raw初始化,现在,可以取得指向IP报头的指针了。

struct net_device *dev = skb->dev;

struct iphdr *iph = skb->nh.iph;

skb->dst可能包含封包通往其目的的路由信息,如果没有,询问路由子系统。

if(skb->dst == NULL){

if(ip_route_input(skb,iph->daddr,iph->saddr,iph->tos,dev))

goto drop;

}

接着,更新Traffic Control(Qos层)所用的统计数据。

#ifdef CONFIG_NET_CLS_ROUTE

...

#endif

当ip报头的长度大于20字节时,有一些IP选项要处理。

if(iph->ipl > 5){

struct ip_options *opt;

if(skb_cow(skb,skb_headroom(skb))){//skb_cow,如果缓冲区和别人共享,就会做出缓冲区副本

IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);

goto drop;

}

iph = skb->nh.iph;

//解析报头中的IP选项,将分析结果放在中skb->cb所指的私有数据域字段的ip_option结构内。

if(ip_options_compile(NULL,skb))

goto inhdr_error;

}

在封包是来源地路由的情况下,内核必须检查该设备的配置是否允许使用该选项。一般情况下,默认是允许的。若设备配置不允许来源地路由选项,则该封包就被丢弃(但不会产生ICMP消息)。当设备允许IP源路由时,调用ip_options_rcv_srr函数设置skb->dst,决定使用哪个设备把该封包转发至来源地路由列表中的下一个跳点。

if(opt->srr){

....

if(ip_options_rcv_srr(skb))

goto drop;

}

ip_rcv_finish函数最后会调用dst_input,完成封包的处理。

IP选项处理:

并非一个封包的所有ip选项都必须在其所有片段中重复,下面是选项相关的主要API:

ip_options_compile:分析IP报头中的一群选项,然后对一个ip_options结构的实例初始化。

ip_options_build:对IP报头中选项部分做初始化,传输本地封包时会用到该函数。

ip_options_fragment:第一个片段时唯一继承了原有封包所有选项的片段,其他片段则不会,但是会以空选项填充,使所有片段尺寸一样,这样可以简化分片流程。

ip_forward_options:转发一个封包时,有些选项必须被处理。

ip_options_get:此函数会接收一群选项,用ip_options_compile 解析,然后把结果存储在其分配的ip_options结构中。

ip_options_echo:指定入口IP封包及其IP选项后,此函数就可以建立用于回复传送者的IP选项。

ip_options_compile函数:

原型:int ip_options_compile(struct ip_options *opt ,struct sk_buff *skb)

当skb不为NULL时(本例中opt为NULL),表示正在处理入口封包。当skb为NULL时(本例中,opt不为NULL),表示正在处理本地传输的封包。

在传输一个本地封包时,opt不为NULL,opt->data包含一个指向IP报头的指针。处理入口封包时,opt为NULL,报头包含在skb中,ip_options结构存储在skb->cb。ip_options_compile函数会根据IP报头位于何处而对本地变量做初始化。

深入理解Linux网络技术内幕学习笔记第十九章:因特网协议第四版(IPv4):Linux的原理和功能相关推荐

  1. 深入立即Linux网络技术内幕学习笔记第十六章:桥接:Linux实现

    网桥设备抽象: 对Linux而言,网桥是虚拟设备,要想传输或接收数据,需要将真实设备绑定到虚拟网桥上. 上图中,有几点需要注意: LAN1和LAN2通过网桥连接在一起,子网都是一样的. 网桥连接到路由 ...

  2. 深入理解linux网络技术内幕读书笔记(十)--帧的接收

    Table of Contents 1 概述 1.1 帧接收的中断处理 2 设备的开启与关闭 3 队列 4 通知内核帧已接收:NAPI和netif_rx 4.1 NAPI简介 4.1.1 NAPI优点 ...

  3. 深入理解Linux网路技术内幕学习笔记第四章:通知链

    第四章:通知链 内核很多子系统之间具有很强的依赖性,其中一个子系统侦测到的或者产生的事件,其他子系统可能都感兴趣,为了实现这种需求,Linux使用了通知链.通知链只在内核子系统之间使用. 通知链就是一 ...

  4. C++ Primer 学习笔记 第十九章 特殊工具与技术

    某些应用程序对内存分配有特殊需求,如使用new将对象放置在特定的内存空间中,为实现它,应用程序需要重载new和delete. new实际执行步骤: 1.new表达式调用operator new(或op ...

  5. MAC OS X 技术内幕 学习笔记之四 MAC OS系统的启动引导

    MAC OS X 技术内幕 学习笔记之四 MAC OS系统的启动引导 MAC OS的启动非常快,同样的运行硬件环境,运行MAC系统感觉比运行windows系统要快不少.在使用笔记本时,同样的电池容量, ...

  6. Spring Security技术栈学习笔记(十四)使用Spring Social集成QQ登录验证方式

    上一篇文章<Spring Security技术栈开发企业级认证与授权(十三)Spring Social集成第三方登录验证开发流程介绍>主要是介绍了OAuth2协议的基本内容以及Spring ...

  7. python数据挖掘学习笔记】十九.鸢尾花数据集可视化、线性回归、决策树花样分析

    #2018-04-05 16:57:26 April Thursday the 14 week, the 095 day SZ SSMR python数据挖掘学习笔记]十九.鸢尾花数据集可视化.线性回 ...

  8. [go学习笔记.第十六章.TCP编程] 3.项目-海量用户即时通讯系统-redis介入,用户登录,注册

    1.实现功能-完成用户登录 在redis手动添加测试用户,并画出示意图以及说明注意事项(后续通过程序注册用户) 如:输入用户名和密码,如果在redis中存在并正确,则登录,否则退出系统,并给出相应提示 ...

  9. Python学习笔记(十九)面向对象 - 继承

    Python学习笔记(十九)面向对象 - 继承 一.继承的概念 # 继承:子类继承父类的所有方法和属性# 1. 子类 class A(object):def __init__(self):self.n ...

最新文章

  1. Binary Tree Nodes(单表多实例查询)
  2. Yarn的默认端口(转载)
  3. 依据imu姿态角计算z轴倾角_1. 姿态的表示方法
  4. floatmap 二维数组_Golang学习笔记(四):array、slice、map
  5. 导出oracle11g的空表,轻松解决oracle11g 空表不能 exp 导出 的问题。
  6. C++ 文字常量与常变量
  7. ASP.NET加密解密
  8. [转载]Magento 店铺多语言设置
  9. 运动会管理系统的需求调研会纪要
  10. Windows7 品牌机OEM原版光盘镜像下载大全
  11. 小波分解与小波包分解的区别
  12. 基于RabbitMQ实现的订单超时功能-记录备查
  13. 【算法-LeetCode】121. 买卖股票的最佳时机(动态规划;贪心)
  14. html/css面试题(3)
  15. xdebug(32) : warning C4229: 使用了记时错误 : 忽略数据上的修饰符
  16. PeckShield:2019年度区块链安全复盘总结暨区块链十大安全事件
  17. 素问·阴阳应象大论原文
  18. EasyExcel 设置边框样式(线条类型和线条颜色)
  19. tftpd 安装和配置
  20. 100种思维模型之大脑实验思维模型-65

热门文章

  1. 输出所有的最长公共子序列
  2. 彻底删除Win7任务栏通知区域已卸载程序图标
  3. 【金融】新成立基金建仓时点、行业分布与市场行情关系探究
  4. 移动端开发,苹果手机样式乱,iphon7不支持white-space属性,控制显示几行
  5. TPS 是一种糟糕的评价标准
  6. dockers 安装 awvs
  7. dockers移盘挂载
  8. 多台仪器同步测量软件,ATECLOUD智能云测试平台
  9. android电视识别多区u盘,【当贝市场】电视无法识别U盘APK文件终极方法
  10. 亚马逊云服务器防火墙,亚马逊云科技中国区上线Web应用程序防火墙Amazon WAF