深入理解Linux网络技术内幕学习笔记第十九章:因特网协议第四版(IPv4):Linux的原理和功能
本章主要介绍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的原理和功能相关推荐
- 深入立即Linux网络技术内幕学习笔记第十六章:桥接:Linux实现
网桥设备抽象: 对Linux而言,网桥是虚拟设备,要想传输或接收数据,需要将真实设备绑定到虚拟网桥上. 上图中,有几点需要注意: LAN1和LAN2通过网桥连接在一起,子网都是一样的. 网桥连接到路由 ...
- 深入理解linux网络技术内幕读书笔记(十)--帧的接收
Table of Contents 1 概述 1.1 帧接收的中断处理 2 设备的开启与关闭 3 队列 4 通知内核帧已接收:NAPI和netif_rx 4.1 NAPI简介 4.1.1 NAPI优点 ...
- 深入理解Linux网路技术内幕学习笔记第四章:通知链
第四章:通知链 内核很多子系统之间具有很强的依赖性,其中一个子系统侦测到的或者产生的事件,其他子系统可能都感兴趣,为了实现这种需求,Linux使用了通知链.通知链只在内核子系统之间使用. 通知链就是一 ...
- C++ Primer 学习笔记 第十九章 特殊工具与技术
某些应用程序对内存分配有特殊需求,如使用new将对象放置在特定的内存空间中,为实现它,应用程序需要重载new和delete. new实际执行步骤: 1.new表达式调用operator new(或op ...
- MAC OS X 技术内幕 学习笔记之四 MAC OS系统的启动引导
MAC OS X 技术内幕 学习笔记之四 MAC OS系统的启动引导 MAC OS的启动非常快,同样的运行硬件环境,运行MAC系统感觉比运行windows系统要快不少.在使用笔记本时,同样的电池容量, ...
- Spring Security技术栈学习笔记(十四)使用Spring Social集成QQ登录验证方式
上一篇文章<Spring Security技术栈开发企业级认证与授权(十三)Spring Social集成第三方登录验证开发流程介绍>主要是介绍了OAuth2协议的基本内容以及Spring ...
- python数据挖掘学习笔记】十九.鸢尾花数据集可视化、线性回归、决策树花样分析
#2018-04-05 16:57:26 April Thursday the 14 week, the 095 day SZ SSMR python数据挖掘学习笔记]十九.鸢尾花数据集可视化.线性回 ...
- [go学习笔记.第十六章.TCP编程] 3.项目-海量用户即时通讯系统-redis介入,用户登录,注册
1.实现功能-完成用户登录 在redis手动添加测试用户,并画出示意图以及说明注意事项(后续通过程序注册用户) 如:输入用户名和密码,如果在redis中存在并正确,则登录,否则退出系统,并给出相应提示 ...
- Python学习笔记(十九)面向对象 - 继承
Python学习笔记(十九)面向对象 - 继承 一.继承的概念 # 继承:子类继承父类的所有方法和属性# 1. 子类 class A(object):def __init__(self):self.n ...
最新文章
- Binary Tree Nodes(单表多实例查询)
- Yarn的默认端口(转载)
- 依据imu姿态角计算z轴倾角_1. 姿态的表示方法
- floatmap 二维数组_Golang学习笔记(四):array、slice、map
- 导出oracle11g的空表,轻松解决oracle11g 空表不能 exp 导出 的问题。
- C++ 文字常量与常变量
- ASP.NET加密解密
- [转载]Magento 店铺多语言设置
- 运动会管理系统的需求调研会纪要
- Windows7 品牌机OEM原版光盘镜像下载大全
- 小波分解与小波包分解的区别
- 基于RabbitMQ实现的订单超时功能-记录备查
- 【算法-LeetCode】121. 买卖股票的最佳时机(动态规划;贪心)
- html/css面试题(3)
- xdebug(32) : warning C4229: 使用了记时错误 : 忽略数据上的修饰符
- PeckShield:2019年度区块链安全复盘总结暨区块链十大安全事件
- 素问·阴阳应象大论原文
- EasyExcel 设置边框样式(线条类型和线条颜色)
- tftpd 安装和配置
- 100种思维模型之大脑实验思维模型-65