《深入理解LINUX网络技术内幕》小记1
1.函數指針是一種很方便的方式,使用函數指針的優點是可以根據不同準則以及該對象所扮演的角色進行初始化。函數指針在網絡代碼中 廣為使用,舉例說明:
a.當入口數據封包或出口數據封包由路由器子系統處理時,會對 緩沖區數據結構中的兩個函數做初始化。
b.當數據封包已準備好在網絡硬件上傳輸時,就會交給net_device數據結構的hard_start_xmit函數指針。該函數由該設備所關聯的設備驅動程序進行初始化。
c.當L3協議想傳輸數據封包時,會調用一組函數指針中的一個。
2.互斥機制
2.1 回轉鎖 ?
2.2 讀-寫回轉鎖
2.3 讀取更新
3.數據結構:struct sk_buff:一個封包存儲在這里,所有網絡分層都會使用這個結構來儲存其報頭、有關用戶的信息(有效載荷),以及用來協調其工作的其他內部信息
??? ? ?struct net_device ?在Linux內核中每種網絡設備都用這個數據結構表示,包括軟硬件的配置信息。
4.分配內存:alloc_skb()和dev_alloc_skb,
建立一個緩沖區會涉及到兩次內存分配(一個是分配緩沖區,而另一個是分配sk_buff結構)
5.釋放內存kfree_skb和dev_kfree_skb
6.skb_push會把一個數據塊添加到緩沖區的開端,而skb_put會把一個數據塊添加到緩沖區的尾端。
7.列表管理函數:
7.1 skb_queue_head_init 用一個元素為空的隊列對sk_buff_head
7.2 skb_queue_head, skb_queue_tail 把一個 緩沖區分別添加到隊列的頭和尾。
7.3 skb_dequeue,skb_dequque_tail 把一個元素分別從隊列的頭和尾去掉。
7.4 skb_quque_purge 把隊列變為空隊列
7.5 skb_quque_walk依次循環運行隊列里的每個元素
8.net_device結構
net_device數據結構存儲著特定網絡設備的所有信息,每個設備都有一個這種結構無論是真實設備還是虛擬設備。
三個標識符:int ifindex ? ? ? ? ? ? ? ? ? int iflink ? ? ? ? ? ? ? ? ? unsigned short dev_id ??
?
9.根目錄(通常是/usr/src/linux) ? ?分為:Documention,include(linux ? ?if.h, if_packet.h, if_ether.h, skbuff.h, netdevice.h slab.h, if_arp.h), kernel, mm, drivers(net ? ?3c59x.c, tulip.c, sys9000.c), net (core ? ? ? ? ? ? skbuff.c)
第三章 ?用戶空間與內核的接口
本章主要了解給定目錄如何添加到/proc及其所在位置、處理給定ioctl命令的內核處理函數(kernel handler), 以及目前用戶空間網絡配置最佳接口Nerlink提供了那些函數?
9.1 procfs(/proc文件系統) ? 這是一個虛擬文件系統,通常是掛在/proc,允許內核以文件的形式向用戶輸出內部信息。
? ? ? ?sysctl(/proc/sys目錄)此接口允許用戶空間讀取或修改內核變量的值,不能用此接口對每個內核變量進行操作:內核應明確指出哪些變量從此接口是可見的。
? ? ? ?sysfs(/sys文件系統)procfs和sysctl已經被濫用了多年,這就導致引入了一種新的文件系統:sysfs。sysfs以非常干凈而有組織的方式輸出很多信息。
ioctl系統調用:ioctl(輸入/輸出控制)系統調用操作的對象是一個文件,通常是用于實現特殊設備所需但標準文件系統沒有提供的操作。
netlink套接字(socket):這是網絡應用程序與內核通信時最新的首選機制。IPROUTE2包中大多數命令都是用此接口。
大多數網絡功能在其初始化時都會在/proc中注冊一個或多個文件,不是在引導時就是在模塊加載時。當一位用戶讀取該文件時,會引起內核間接運行一組內核函數,以返回某種輸出內容。/proc中的目錄可以使用proc_mkdir創建。/proc/net中的文件可以使用定義在include/linux/proc_fs.h中的proc_net_fops_create和proc_net_remove予以注冊和除名。
以下是ARP協議在/proc/net中注冊其arp文件:
static struct file_opeations arp_seq_fops = {
.owner ? ? ? ? ? ?= ? ? THIS_MODULE,
.open ? ? ? ? ? ? ?= ? ? arp_seq_open,
.read ? ? ? ? ? ? ? = ? ? seq_read,
.llseek ? ? ? ? ? ? = ? ? seq_lseek,
.release ? ? ? ? ?= ? ? seq_release_private,
};
?
static int __init arp_proc_init(void)?
{
if (!proc_net_fops_create("arp", S_IRUGO, &arp_seq_fops))
return -ENOMEN;
return 0;
}
open所初始化的例程會做另一次重要的初始化:注冊一個函數指針數組,包括procfs用于遍歷要傳回給用戶數據的所有例程:一個例程啟動傾卸(dump),另一個推進到一個項目,而另一個再傾卸一個項目。這些例程內部負責保存必要的環境信息,這些信息會就是傾卸點以及從正確位置重新繼續傾卸所必需的。
static struct seq_operations arp_seq_ops = {
.start ? ? ? ? ?= ? ?clip_seq_start,
.next ? ? ? ? ?= ? ?neigh_seq_next,
.stop ? ? ? ? ?= ? ?neigh_seq_stop,
.show ? ? ? ?= ? ?clip_seq_show,
};
static int arp_seq_open(struct inode *inode, struct file *file)?
{
....
rc = seq_open(file, &arp_seq_ops);
...
}
10.sysctl:目錄/proc/sys
用戶在/proc/sys下看到的一個文件,實際上是一個內核變量。內核可以定義:
要將其放在/proc/sys的何處。 ? 命名 ? ? ? 訪問權限。
導致運行期間創建目錄或文件的事件實例如下:
當一個內核模塊實現一項新功能,或者有一個協議被加載或卸載(unload)時;
當一個新的網絡設備被注冊或除名時。
/proc/sys中的文件和目錄都是以ctl_table結構定義的。ctl_table結構的注冊和除名是通過在kernel/sysctl.c中定義的register_sysctl_table和unregister_sysctl_table函數完成
以下是ctl_table的關鍵字段:
const char *procname ? 在/proc/sys中所用的文件名
int maclen ? ? 輸出的內核變量的尺寸大小
mode_t mode ? ? 分派給/proc/sys中關聯的文件或目錄的訪問權限
ctl_table *child ? ?用于建立目錄與文件之間的父子關系
proc_handler ?當你在/proc/sys中讀取或寫入一個文件時,完成讀取或寫入操作的函數。所有與文件相關聯的ctl_instances都必須由proc_handler初始化。內核會給目錄分派一個默認值。
strategy ? 當/proc/sys中的文件用sysctl系統調用訪問時,此函數就會被調用
extra1 ? ? extra2 ? 通常用于定義變量的最小值和最大值。
11.proc_dostring ? 讀/寫一個字符串
proc_dointvec ? ?讀/寫一個包含一個或多個整數的數組
proc_dointvec_minmac ? ?類似proc_dointvec,但是,要確定輸入數據在min/max范圍內。
proc_dointvec_jiffies ? 讀/寫一個整數數組。
proc_dointvec_ms_jiffies ? 讀/寫一個整數數組。
proc_doulongvec_minmax ? 類似proc_dointvec_minmac,但其值為長整數而非整數
proc_doulongvec_ms_ ? 讀/寫一個長整數數組。此內核變量以jiffies為單位表示。
jiffies_minmax ??
初始化strategy 的函數
sysctl_string ? ?讀/寫一個字符串
sysctl_intvec ? 讀/寫一個整數數組,而且確定其值符合min/max范圍
sysctl_jiffies ? ?讀/寫一個以jiffies表示的值,然后將其轉成秒數
?sysctl_ms_jiffies ?讀/寫一個以jiffies表示的值,然后將其轉成毫秒數
{
.ctl_name ? ? ? = ? ?NET_IPV4_CONF_FORWARDING,
.procname ? ? = ? ? "forwarding",
.data ? ? ? ? ? ? ? = ? ? &ipv4_devconf.forwarding,
.maxlen ? ? ? ? = ? ? ?sizeof(int),
.mode ? ? ? ? ? ?= ? ? ?0644,
.proc_handler = ? &devint_sysctl_forward,
}
{?
.ctl_name ? ? ? = ? CTL_NET,
.procname ? ? = ? ?"net",
.mode ? ? ? ? ? ? = ? 0555,
.child ? ? ? ? ? ? ?= ? net_table,
}
在/proc/sys中注冊文件
指向一個ctl_table ?實體的指針
一個標識,指出新元素應該放在位于相同目錄中ctl_table實體列表的何處:頭(1)或尾(0).
static ctl_table scsi_table[] = {
.ctl_name ? ? ? ? = ? ? DEV_SCSI_LOGGING_LEVEL,
.procname ? ? ? = ? ? "logging_level",
.data ? ? ? ? ? ? ? ? = ? ? &scsi_logging_level,
.maxlen ? ? ? ? ? = ? ? sizeof(scsi_logging_level),
.mode ? ? ? ? ? ? ?= ? ? ?0644,
.proc_handler = ?&proc_dointvec ?},
};
static ctl_table scsi_dir_tablep[] = {
{ .ctl_name ? ? = DEV_SCSI,
?.procname ? ?= "scsi",
.mode ? ? ? ? ? ? = 0555,
.child ? ? ? ? ? ? ? = scsi_table },
};
?
static ?ctl_table scsi_root_table[] ? = ?{
{.ctl_name ? ? ? ? ? ? ? ?= ? CTL_DEV,
?.procname ? ? ? ? ? ? ? = ? "dev",
?.mode ? ? ? ? ? ? ? ? ? ? ? = ?0555,
.child ? ? ? ? ? ? ? ? ? ? ? ? = ? scsi_dir_table },
{}
}; int __init scsi_init_sysctl(void)?
{
scsi_table_header ? ?= register_sysctl_table(scsi_root_table, 1);
}
min_delay ?文件被分派了proc_handler和strategy 兩個例程。因為內核變量ip_rt_min_delay以jiffies表示,但是用戶的輸入和輸出都是以秒來表示,因此這兩個例程可以完成把秒轉換為jiffies.
ip_local_port_range文件是一個有趣的案例。
當系統管理輸入像ifconfig eth0 mtu 1250這樣的命令,用以改變接口eth0的MTU時,ifconfig會打開一個套接字,用從系統管理員那里接收的信息初始化一個本地數據結構,然后以ioctl調用傳送給內核。SIOCSIFMTU是命令標識符。
struct ifreq data;
fd = socket(PF_INET, SOCK_DGRAM, 0);
err ?= ?ioctl(fd, SIOCSIFMTU, &data);
SIOCGIFADDR 和SIOCSIFADDR這兩個命令可以為接口新增或刪除IP地址
netlink可作為內核內部以及多個用戶空間進城之間的消息傳輸系統。
通過netlink套接字,你可以使用標準套接字API打開或關閉套接字、使用套接字傳輸數據或者接受套接字數據。
int socket(int domain, int type, ?int protocol)
NETLINK_ROUTE協議用于大多數網絡功能,如路由和鄰居協議,而NETLINK_FIREWALL用于防火墻(Netfilter).
netlink的功能之一是傳送單播和多播消息:目的地終端點地址可以是一個PID、一個多播群組ID或 兩者的結合。內核定義netlink多播群組的目的是傳出特定種類事件的 通知消息,而用戶程序如果對這類通知消息感興趣,可以向這些群組注冊。
netlink相對其他用戶-內核接口(如ioctl)的優點之一就是內核可以啟動傳輸,而不只是僅限于響應用戶空間請求而返回信息。
配置改變串行化
每次應用配置改變時,內核中內負責處理此事的例程都會取得一個信號量(rtnl_sem),以確保對儲存網絡配置內容的數據結構的訪問具有互斥性。無論該配置的改變時通過 ioctl還是netlink而施加,都是如此。
第二部分 ? 系統初始化
PCI(Peripheral Component Interconnect,外設部件互聯)設備上。 ?
NIC(network interoce card,網絡接口卡)有關的任務都必須先完成,網絡才能開啟并運行。首先,關鍵內核組件必須予以初始化。其次,設備驅動程序必須對其所負責的所有設備予以初始化和注冊,然后分配一些資源(IRQ、I/O端口等)讓內核能使用,以便與驅動程序通信。
第四章 ?通知鏈
內核的很多子系統之間具有很強的相互依賴性,因此,其中一個子系統偵測到的或者產生的事件,其他子系統可能都有興趣。為了實現這種交互需求,LINUX使用了所謂的通知鏈(notification chain)
通知鏈如何聲明以及網絡代碼定義了哪些鏈(chain).
內核 子系統如何向 通知鏈注冊
內核 子系統如何在鏈上產生通知信息
注意:通知鏈只在內核子系統之間使用。內核和用戶空間之間的通知信息則依賴其他機制。
通知鏈就是一份簡單的函數列表,當給定時間發生時予以執行。每個函數都讓另一個子系統知道,調用此函數的子系統內所發生的的一個事件或者子系統所偵測到的一個事件。
就每條通知鏈而言,都有被動端(被通知者)和主動端(通知鏈),也就是所謂的發布-訂閱(publish-and-subscribe)模型:
被通知者(notified)就是要求接收某事件的子系統,而且會提供回調函數予以調用。
通知者(notifier)就是感受到一個事件并調用回呼函數的 子系統。
所執行的函數是由被通知的子系統所選取,絕不是鏈條的擁有者(產生通知信息的子系統)決定該執行什么函數。擁有者只是定義這份列表而已; 任何內核子系統都可以對該鏈條注冊一個回調函數以接收通知信息。
通知鏈的使用使源碼更易于編寫和維護。
每位子系統維護者應該知道:
自己對來自其他子系統的哪種事件感興趣。
自己知道的事件有哪幾種,并且其他子系統可能感興趣的事件又是哪種
通知鏈列表元素的類型是notifier_block,其定義如下:
struct notifier_block
{
int ?(*notifier_call)(struct notifier_block *self, unsigned long, void *);
struct notifier_block *next;
int priority;
};
notifier_call是要執行的函數,next用于鏈接列表的元素,而priority代表的是該函數的優先級。
notifier_block實例的常見名稱有xxx_chain、xxx_notifier_chain以及xxx_notifier_list.
鏈注冊
當一個內核組件對給定通知鏈的事件感興趣時,可以用通用函數notifier_chain_register予以注冊。內核也提供一組內含notifier_chain_register的包裹函數
注冊 ? ?int notifier_chain_register(struct notifier_block **list, struct notifier_block *n)
除名 ? ?int notifier_chain_unregister(struct notifier_block **n1, struct notifier_block *n)
通知 ? ?int notifier_call_chain(struct notifier_block **n, unsigned long val void *v)
因為調用notifier_chain_register函數就是把回調函數插入到所有列表中,因此必須把列表指定為輸入參數。然而,此函數很少被直接調用,通常是改用通用的包裹函數
int notifier_chain_register(struct notifier_block **list, struct notifier_block *n)
{
write_lock(¬ifier_lock);
while(*list)
{
if (n->priority > (*list)->priority)
break;
list = &((*list)->next);
}
n->next = *list;
*list = n;
write_unlock(¬ifier_lock);
return 0;
}
鏈上的通知事件
int notifier_call_chain(struct notifier_block **n, unsigned long val, void *v)?
{
int ret = NOTIFY_DONE;
struct notifier_block *nb = *n;
while (nb)?
{
ret = nb->notifier_call(nb, val, v);
if (ret & NOTIFY_STOP_MASK)?
{
return ret;
}
nb = nb->next;
}
return ret;
}
NOTIFY_OK ? 通知信息被正確地處理了
NOTIFY_DONE ? 對通知信息不感興趣
NOTIFY_BAD ? ? 有些事情出錯
NOTIFY_STOP ? 函數被正確調用,然而,此事件不需要進一步調用其他回調函數
NOTIFY_STOP_MASK ? 此標識由notifier_call_chain檢查,以了解是否停止調用回調函數,或者繼續調用下去。NOTIFY_BAD和NOTIFY_STOP在其定義中都包括了此標識。
notifier_call_chain捕獲并返回由最后一個調用的回調函數所接收的返回值。
inetaddr_chain
發送有關本地接口上的IPv4地址的插入、刪除以及變更的通知信息。
netdev_chain
發送有關網絡設備注冊狀態的通知信息。
某些NIC設備驅動程序可以用reboot_notifier_list鏈注冊;當系統重新引導時,此鏈會發出警告。
int register_netdevice_notifier(struct notifier_block *nb)
{
return notifier_chain_register(&netdev_chain, nb);
}
static struct notifier_block fib_inetaddr_notifier = {
.notifier_call ?= fib_inetaddr_event,
};
static struct notifier_block fib_netdev_notifier = {
.notifier_call ?= fib_netdev_event,
};
void __ init ip_fib_init(void)
{
..........
register_netdevice_notifier(&fib_netdev_notifier);
register_inetaddr_notifier(&fib_inetaddr_notifier);
}
改變本地IP地址配置以及改變本地設備的注冊狀態,都會影響路由表。
notifier_chain_register + 包裹函數 ? ??
notifier_chain_unregister ?+ ?包裹函數
notifier_call_chain ? ? ? ? ??
struct notifier_block ?定義通知信息的處理函數,包括要調用的回調函數
與通知鏈相關的文件
/usr/src/linux ? ? net(core(dev.c),ipv4(devinet.c)) ? ? kernel(sys.c) ? ? ? ? ?include---linux----notifier.h
網絡設備初始化
USB、PCI CardBus、IEEE1394(Apple稱其為FireWire)以及其他。
一段內核網絡代碼的初始化。
NIC的初始化。
NIC如何使用中斷,以及IRQ處理函數如何分配和釋放。
用戶如何給以模塊方式加載的設備驅動程序提供配置參數。
設備初始化及配置期間,用戶空間與內核之間的交互。
parse_early_param();-->parse_args(...)
parse_args(...)
.....
init_IRQ(); ? init_timers(); ? ? ? ? ? ? softirq_init(); ? ? ?rest_init();---->kernel_thread(init, ...)----><init>
do_basic_setup(); ? ? ? ? ?free_init_mem(); ? ? ? ? ? ? ? ? ? ?run_init_process(....)
user_modehelper_init() ? ? ? ? ? ? ?driver_init() ? ? ? ? ? ? ?sock_init() ? ? ? ? ? ? ? ? ? ? ? ? ? do_initcalls();
引導期間選項 ? ? ? ? ? ? ? ? ? ? ? ? ? 中斷和定時器(硬中斷和軟中斷分別由init_IRQ和softirq_init做初始化。
初始化函數(內核子系統及內建的設備驅動程序由do_initcalls初始化。free_init_mem會釋放一塊被無用程序所持有的內存)
run_init_process確定在系統上運行的第一個進程,也就是所有其他進程的父進程,一直運行直到系統做完工作。
設備注冊和初始化
一個網絡設備可用,就必須被內核認可,并且關聯正確的驅動程序。
硬件初始化 ? ? ?由設備驅動程序和通用總程序合作完成。
軟件初始化 ? ? ?在設備能夠被使用之前,依賴于所開啟和配置的網絡協議為何而定。
?功能初始化 ? ?Linux內核有很多網絡選項。
net_device數據結構包含一組函數指針,內核可用它們與設備驅動程序及特殊內核功能交互。這些函數的初始化部分取決于設備類型(例如Ethernet),部分取決于廠家和型號。
IRQ線 ? 在"設備與內核之間的交互",NIC必須被分派一個IRQ,然后在必要時用它要求內核的注意。
I/O端口和內存注冊
驅動程序將其設備的一個內存區域(例如,其配置寄存器)映射到系統內存,使得驅動程序的讀/寫操作可以通過系統內存地址直接進行,這樣可以簡化代碼。I/O端口和內存分別使用request_region和release_region注冊及釋放。
設備與內核之間的交互
輪詢(polling) ? ?由內核端驅動。
中斷 ? 由設備端驅動,當設備需要內核注意時,會向內核發送出一個硬件信號(產生中斷事件)
硬件中斷 ??
每個中斷事件都會運行一個函數,被稱為中斷處理例程(interrupt handler),而中斷處理例程必須按照設備的所需進行裁剪,因此由設備驅動程序安裝。一般而言,當設備驅動程序注冊一個NIC時,會請求并分派一個IRQ。然后,用兩個依賴體系結構的函數為給定的IRQ注冊或刪除(如果驅動程序被卸載了)處理例程。這兩個函數定義在kernel/irq/manage.c中,并由arch/XXX/kernel/irq.c中的體系結構描述函數改寫,其中XXX是體系結構描述目錄:
int request_irq(unsigned int irq, void (*handler)(int, void *, struct pt_regs*), unsigned long irqflags, const char *devname, void *dev_id)
此函數會注冊一個處理例程。首先確保所請求的中斷是一個有效的中斷,而且還沒分配給另一個設備,除非這兩個設備能夠共享IRQ
void free_irq(unsigned_int irq, void *dev_id)
給定的設備由dev_id標識,此函數會刪除處理例程,而且如果沒有其他設備注冊在該IRQ線,就關閉該IRQ。
中斷類型,通過中斷,NIC能夠告知其驅動程序幾種不同的事情,其中包括:
接收一幀 ? ? 傳輸失敗 ? ? ? DMA傳輸已成功完成
設備有足夠內存處理新傳輸
當出口隊列沒有足夠空間保存一個最大尺寸的幀時,NIC設備驅動程序會停止出口隊列而關閉傳輸。當內存可用時,該隊列又會再次開啟。
當內存可用時,該隊列又會再次開啟。
static int
el3_start_xmit(struct sk_buff *skb, struct net_device *dev)?
{
................
netif_stop_queue (dev);
................
if (inw(ioaddr ?+ ?TX_FREE) > 1536)
netif_start_queue(dev);
else
outw(SetTxThreshold + 1536, ioaddr + EL3_CMD);
.....................
}
驅動程序可以用netif_stop_queue停止設備隊列,因此能禁止內核提交后續的傳輸請求。然后,驅動程序會檢查該設備的內存是否有足夠的空間容納一個1536個字節的包。如果有,驅動程序就會啟動隊列,允許內核再次提交傳輸請求;否則,就會指示設備(通過用一個outw調用寫一個配置寄存器),當條件滿足時產生一個中斷。中斷處理例程將使用netif_start_queue重啟設備隊列,使內核能夠重新繼續傳輸。
IP層的主要任務和所用策略
說明IP層接受函數如何處理入口封包,以及IP選項如何處理
轉發和本地傳遞
說明入口IP封包如何傳遞至本地的L4協議處理函數,或者當目的IP地址不屬于本地主機,而該主機有開啟轉發功能時,該如何給以轉發。
說明L4協議如何銜接IP層來請求傳輸
處理分段:說明分段和重組是怎么處理的
L4協議和Raw IP的處理
說明L4協議如何為入口流量注冊處理函數
因特網控制消息協議
說明ICMP協議的所有細節
?
版本(version)
報頭長度?
服務類型(type of service,TOS)
總長度:封包長度
識別(identity)
IP報頭的TOS字段的新舊意義
1.原始版本 ? 2.區分服務 ? 3.顯式擁塞通知
DF(不分段) ?MF(還有其他分段)
片段偏移量(fragment offset)
存活時間(Time To Live,TTL)
要識別一個片段所屬的IP封包,內核會考慮下列參數:
1.發送方和目的地IP地址;
2.IP封包ID
3.L4協議
路徑MTU發現:用于發現封包傳輸至制定目的地地址而不用被fen分段的最大尺寸。
更多細節建議讀RFC 791
通過相同目的地的路徑可能bu不只一條,這是為了冗余或負載平衡
檢驗和字段是一種冗余字段,網絡協議用它發現傳輸錯誤。有些檢驗和不僅能jian檢測錯誤,而且還可以自動修復特定類型的錯誤。
封包調整 ? ?
由ip_init完成的主要任務
1.用dev_add_pack函數為IP封包注冊處理程序,此函數程序為函數,名為ip_rcv
2.初始化路由器子系統,包括與協議無關的緩存
3.初始化用于管理IP端點的基礎架構
封包接收,封包轉發(路由決策前)
封包轉發(路由決策后)
封包傳輸
NF_HOOK(PROTOCOL, HOOK_POSITION_IN_THE_STACK, SKB_BUFFER, IN_DEVICE, OUT_DEVICE, do_something_finish);
NF_HOOK輸出值可以是下列值之一:
當do_something_finish稍后被執行時,就是其輸出值;
如果因為某個過濾器的緣故使得SKB_BUFFER被丟棄,就返回-EPERM
如果沒有足夠的內存執行過濾運算,就返回-ENOMEM
ip_route_input決定輸入封包的命運
ip_route_output_flow傳輸封包前使用
dst_pmtu給定一個路由表緩存項目,就可返回相關的PMTU
ip_route_xxx函數會查詢路由表,并根據一組字段做出其決策:
1.目的IP地址;
2.源IP地址
3.服務類型(TOS);
4.進行接收的設備(接收時才有)
5.可進行傳輸的設備列表;
ip_rcv
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt);
netif_receive_skb函數會把指向L3協議的指針(skb->nh)設在L2報頭尾端。因此,IP層函數可以安全地將它轉換成iphdr結構
if(skb->pkt_type == PACKET_OTHERHOST) ?goto drop;
if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {
IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);
goto out;
}
if (!pskb_may_pull(skb, sizeof(struct iphdr)))
goto inhdr_error;
iph = skb->nh.iph;
if(pskb_trim_rcsum(skb, len)) {
IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);
goto drop;
}
?
?
總結
以上是生活随笔為你收集整理的《深入理解LINUX网络技术内幕》小记1的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《网站安全攻防秘笈》摘录
- 下一篇: 微信小程序之坦克大战学习