<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-Hans-CN">
	<id>http://www.anwsome.com//index.php?action=history&amp;feed=atom&amp;title=%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AATCP%2FIP%E6%A0%882-IPv4%E5%92%8CICMPv4</id>
	<title>编写一个TCP/IP栈2-IPv4和ICMPv4 - 版本历史</title>
	<link rel="self" type="application/atom+xml" href="http://www.anwsome.com//index.php?action=history&amp;feed=atom&amp;title=%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AATCP%2FIP%E6%A0%882-IPv4%E5%92%8CICMPv4"/>
	<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AATCP/IP%E6%A0%882-IPv4%E5%92%8CICMPv4&amp;action=history"/>
	<updated>2026-04-15T01:54:49Z</updated>
	<subtitle>本wiki上该页面的版本历史</subtitle>
	<generator>MediaWiki 1.45.1</generator>
	<entry>
		<id>http://www.anwsome.com//index.php?title=%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AATCP/IP%E6%A0%882-IPv4%E5%92%8CICMPv4&amp;diff=44&amp;oldid=prev</id>
		<title>Xlong：​创建页面，内容为“此次在我们的用户空间TCP/IP堆栈中,我们将实现一个最小可行的IP层,&#039;&#039;并使用ICMP的回声请求(&#039;&#039;也称为pings)进行测试。  我们将查看IPv4和ICMPv4的格式,并介绍如何检查其完整性。一些功能,例如IP分片,作为练习。  对于我们的网络栈, 选择IPv4 优先于 IPv6,因为它仍然是互联网的默认网络协议。然而,未来我们的网络栈可以通过IPv6进行扩展。  = 互联网协议版本4 =…”</title>
		<link rel="alternate" type="text/html" href="http://www.anwsome.com//index.php?title=%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AATCP/IP%E6%A0%882-IPv4%E5%92%8CICMPv4&amp;diff=44&amp;oldid=prev"/>
		<updated>2025-12-24T10:27:45Z</updated>

		<summary type="html">&lt;p&gt;创建页面，内容为“此次在我们的用户空间TCP/IP堆栈中,我们将实现一个最小可行的IP层,&amp;#039;&amp;#039;并使用ICMP的回声请求(&amp;#039;&amp;#039;也称为pings)进行测试。  我们将查看IPv4和ICMPv4的格式,并介绍如何检查其完整性。一些功能,例如IP分片,作为练习。  对于我们的网络栈, 选择IPv4 优先于 IPv6,因为它仍然是互联网的默认网络协议。然而,未来我们的网络栈可以通过IPv6进行扩展。  = 互联网协议版本4 =…”&lt;/p&gt;
&lt;p&gt;&lt;b&gt;新页面&lt;/b&gt;&lt;/p&gt;&lt;div&gt;此次在我们的用户空间TCP/IP堆栈中,我们将实现一个最小可行的IP层,&amp;#039;&amp;#039;并使用ICMP的回声请求(&amp;#039;&amp;#039;也称为pings)进行测试。&lt;br /&gt;
&lt;br /&gt;
我们将查看IPv4和ICMPv4的格式,并介绍如何检查其完整性。一些功能,例如IP分片,作为练习。&lt;br /&gt;
&lt;br /&gt;
对于我们的网络栈, 选择IPv4 优先于 IPv6,因为它仍然是互联网的默认网络协议。然而,未来我们的网络栈可以通过IPv6进行扩展。&lt;br /&gt;
&lt;br /&gt;
= 互联网协议版本4 =&lt;br /&gt;
在实现以太网帧之后,下一层(L3)负责将数据传输到目标。&amp;#039;&amp;#039;即互联网协议&amp;#039;&amp;#039;(IP) 是为TCP和UDP等传输协议提供基础而发明的。它是无连接的,这意味着与TCP不同,所有数据报在网络堆栈中都是相互独立处理的。这也意味着IP数据报可能会丢失或者过期&amp;lt;sup&amp;gt;。&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
此外, IP 不保证成功交付。这是协议设计者有意识地选择的,因为IP旨在为同样无法保证交付的协议提供基础。UDP就是这样一个协议。&lt;br /&gt;
&lt;br /&gt;
如果需要通信方之间的可靠性,则在IP之上使用TCP等协议。在这种情况下,更高级别的协议负责检测缺失的数据, 并确保所有数据均已送达。&lt;br /&gt;
&lt;br /&gt;
== 头部格式 ==&lt;br /&gt;
IPv4 头通常长度为 20 个 八位。头部可以包含尾随选项,但在我们的实现中会省略这些选项。字段的含义相对简单,可以描述为一个C结构体:&lt;br /&gt;
&lt;br /&gt;
4位&amp;lt;code&amp;gt;version&amp;lt;/code&amp;gt;字段表示互联网头的格式。在我们的情况下,IPv4 的值为 4。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
struct iphdr {&lt;br /&gt;
    uint8_t version : 4;&lt;br /&gt;
    uint8_t ihl : 4;&lt;br /&gt;
    uint8_t tos;&lt;br /&gt;
    uint16_t len;&lt;br /&gt;
    uint16_t id;&lt;br /&gt;
    uint16_t flags : 3;&lt;br /&gt;
    uint16_t frag_offset : 13;&lt;br /&gt;
    uint8_t ttl;&lt;br /&gt;
    uint8_t proto;&lt;br /&gt;
    uint16_t csum;&lt;br /&gt;
    uint32_t saddr;&lt;br /&gt;
    uint32_t daddr;&lt;br /&gt;
} __attribute__((packed));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;#039;&amp;#039;I&amp;#039;&amp;#039;互联网头长字段&amp;lt;code&amp;gt;ihl&amp;lt;/code&amp;gt;长度同样为4位,&amp;#039;&amp;#039;words&amp;#039;&amp;#039;表示IP头中32位字数。由于字段大小为 4 位,因此最大值只能为 15。因此,IP头的最大长度为60个八位。&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;服务字段的类型&amp;#039;&amp;#039;&amp;lt;code&amp;gt;tos&amp;lt;/code&amp;gt;源自第一个IP规范。在后续规格中,它已被划分为更小的字段,但为了简单起见,我们将按照原始规范中定义的字段进行处理。该字段传达了用于IP数据报的服务质量。&lt;br /&gt;
&lt;br /&gt;
全长场&amp;lt;code&amp;gt;len&amp;lt;/code&amp;gt;传达整个IP数据报的长度。由于是16位字段,因此最大长度为65535字节。大型IP数据报存在碎片化现象,在这些数据报中,它们被分割成更小的数据报,以满足不同通信接口的最大传输单元(MTU)。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt;字段用于索引数据报,最终用于重新组装碎片化的IP数据报。字段的值只是一个由发送方递增的计数器。接收方通过该字段知道如何对传入的分片数据排序。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;flags&amp;lt;/code&amp;gt;字段定义了数据报的各种控制标志。具体而言,发送方可以指定数据报是否允许分片,是最后分片数据还是更多分片数据进入。&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;分片偏移字段&amp;#039;&amp;#039;&amp;lt;code&amp;gt;frag_offset&amp;lt;/code&amp;gt;在数据报中表示片段的位置。当然,第一个数据报将该索引设置为0。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;ttl&amp;lt;/code&amp;gt;一个常用的属性,用来计算数据报的生命周期。通常由原始发送方设置为64,每个接收方都会逐一声明此计数器。当数据报降至零时,数据报将被丢弃, 并可能回复ICMP消息以表示错误。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;proto&amp;lt;/code&amp;gt;字段为数据图提供了在其有效载荷中携带其他协议的固有能力。该字段通常包含16(UDP)或6(TCP)等值,仅用于将实际数据类型传达给接收方。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;csum&amp;lt;/code&amp;gt;用于验证IP头的完整性。其算法相对简单,将在本教程中进一步说明。&lt;br /&gt;
&lt;br /&gt;
最后,&amp;lt;code&amp;gt;saddr&amp;lt;/code&amp;gt;&amp;lt;code&amp;gt;和daddr&amp;lt;/code&amp;gt;字段分别表示数据报的源地址和目标地址。尽管这些字段长度为32位,因此提供了约45亿个地址,但地址范围将在不久的将来耗尽。IPv6协议将这一长度延长至128位,因此未来能够支持更大范围的互联网协议的地址范围。&lt;br /&gt;
&lt;br /&gt;
== 校验和 ==&lt;br /&gt;
使用校验和字段来检查IP数据报的完整性。&lt;br /&gt;
&lt;br /&gt;
算法的实际代码如下:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
uint16_t checksum(void *addr, int count)&lt;br /&gt;
{&lt;br /&gt;
    /* Compute Internet Checksum for &amp;quot;count&amp;quot; bytes&lt;br /&gt;
     *         beginning at location &amp;quot;addr&amp;quot;.&lt;br /&gt;
     * Taken from https://tools.ietf.org/html/rfc1071&lt;br /&gt;
     */&lt;br /&gt;
&lt;br /&gt;
    register uint32_t sum = 0;&lt;br /&gt;
    uint16_t * ptr = addr;&lt;br /&gt;
&lt;br /&gt;
    while( count &amp;gt; 1 )  {&lt;br /&gt;
        /*  This is the inner loop */&lt;br /&gt;
        sum += * ptr++;&lt;br /&gt;
        count -= 2;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /*  Add left-over byte, if any */&lt;br /&gt;
    if( count &amp;gt; 0 )&lt;br /&gt;
        sum += * (uint8_t *) ptr;&lt;br /&gt;
&lt;br /&gt;
    /*  Fold 32-bit sum to 16 bits */&lt;br /&gt;
    while (sum&amp;gt;&amp;gt;16)&lt;br /&gt;
        sum = (sum &amp;amp; 0xffff) + (sum &amp;gt;&amp;gt; 16);&lt;br /&gt;
&lt;br /&gt;
    return ~sum;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;以IP头为例&amp;lt;code&amp;gt;45 00 00 54 41 e0 40 00 40 01 00 00 0a 00 00 04 0a 00 00 05&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
# 将字段加在一起,使两者的互补之数产生&amp;lt;code&amp;gt;01 1b 3e&amp;lt;/code&amp;gt;。&lt;br /&gt;
# 然后,为了将其转换为其互补:&amp;lt;code&amp;gt;1b 3e&amp;lt;/code&amp;gt;+&amp;lt;code&amp;gt;01&amp;lt;/code&amp;gt;=&amp;lt;code&amp;gt;1b 3f&amp;lt;/code&amp;gt;。&lt;br /&gt;
# 最后,取一个互补的值,从而得出了校对值&amp;lt;code&amp;gt;e4c0&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
IP头&amp;lt;code&amp;gt;45 00 00 54 41 e0 40 00 40 01 e4 c0 0a 00 00 04 0a 00 00 05&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
可以通过再次应用算法来验证校验,如果结果为0,数据很可能就好了。&lt;br /&gt;
&lt;br /&gt;
= 互联网控制消息协议版本（ICMP协议） =&lt;br /&gt;
由于互联网协议缺乏可靠性机制,因此需要通过某种方式向各方通报可能的错误情况。因此,&amp;#039;&amp;#039;互联网控制消息协议&amp;#039;&amp;#039;(ICMP) 被用于网络中的诊断措施。一个例子是网关无法访问的情况——识别此功能的网络堆栈会将ICMP“Gateway Unreachable”消息发送回源。&lt;br /&gt;
&lt;br /&gt;
== 头部格式 ==&lt;br /&gt;
ICMP 头位于相应 IP 数据包的有效载荷中。ICMPv4 头标的结构如下:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
struct icmp_v4 {&lt;br /&gt;
    uint8_t type;&lt;br /&gt;
    uint8_t code;&lt;br /&gt;
    uint16_t csum;&lt;br /&gt;
    uint8_t data[];&lt;br /&gt;
} __attribute__((packed));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;在这里,&amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt;字段发送消息的类型。该字段预留了42个值,仅使用约8个常用值。在这里的实现中,使用了类型0(回声回复)、3(目的地不可访问)和8型(回声请求)。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;code&amp;lt;/code&amp;gt;字段进一步描述了消息的含义。例如,当类型为3(目的地不可访问)时,代码字段意味着原因。常见错误是数据包无法路由到网络时:源主机很可能接收到带有第3类代码和代码0(Net Unreachable)的ICMP消息。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;csum&amp;lt;/code&amp;gt;字段与 IPv4 头字段相同,并且可以使用相同的算法来计算它。然而,在ICMPv4中,&amp;#039;&amp;#039;校验和是端到端的&amp;#039;&amp;#039;,这意味着在计算校验和时也包含有效载荷。&lt;br /&gt;
&lt;br /&gt;
== 消息及其处理 ==&lt;br /&gt;
实际的ICMP有效载荷包括查询/信息消息和错误消息。首先,我们将查看 Echo 请求/回复消息,通常称为网络中的“ping”:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
struct icmp_v4_echo {&lt;br /&gt;
    uint16_t id;&lt;br /&gt;
    uint16_t seq;&lt;br /&gt;
    uint8_t data[];&lt;br /&gt;
} __attribute__((packed));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;消息格式很紧凑。字段&amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt;由发送主机设置,以确定回声回复的处理过程。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;字段seq&amp;lt;/code&amp;gt;是响应的序列号,它只是一个从零开始的数值,每当出现新的响应请求时,它就会逐一递增。用于检测在传输过程中是否出现响应消息消失或被重新排序。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt;字段是可选的,但通常包含像响应的时间戳这样的信息。然后可用于估算主机之间的往返时间。&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;Destination Unreachable&amp;#039;&amp;#039;最常见的ICMPv4错误消息“目的地无法访问”具有以下格式:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
struct icmp_v4_dst_unreachable {&lt;br /&gt;
    uint8_t unused;&lt;br /&gt;
    uint8_t len;&lt;br /&gt;
    uint16_t var;&lt;br /&gt;
    uint8_t data[];&lt;br /&gt;
} __attribute__((packed));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;第一个octet 未使用。然后&amp;lt;code&amp;gt;len&amp;lt;/code&amp;gt;字段表示原始数据报的长度,在 IPv4 的 4 个octet 中。2-octet 字段的值&amp;lt;code&amp;gt;var&amp;lt;/code&amp;gt;取决于ICMP代码。&lt;br /&gt;
&lt;br /&gt;
最后,尽可能将导致“目的地无法到达状态”的原始IP数据包放入&amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt;&amp;lt;code&amp;gt;字段&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
= 测试实现 =&lt;br /&gt;
从 shell 中,我们可以验证我们的用户空间网络堆栈是否响应 ICMP 的回波请求:&lt;br /&gt;
&lt;br /&gt;
= 关键代码实现 =&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void icmpv4_incoming(struct sk_buff *skb) &lt;br /&gt;
{&lt;br /&gt;
    struct iphdr *iphdr = ip_hdr(skb);&lt;br /&gt;
    struct icmp_v4 *icmp = (struct icmp_v4 *) iphdr-&amp;gt;data;&lt;br /&gt;
&lt;br /&gt;
    //TODO: Check csum&lt;br /&gt;
&lt;br /&gt;
    switch (icmp-&amp;gt;type) {&lt;br /&gt;
    case ICMP_V4_ECHO:&lt;br /&gt;
        icmpv4_reply(skb);&lt;br /&gt;
        return;&lt;br /&gt;
    case ICMP_V4_DST_UNREACHABLE:&lt;br /&gt;
        print_err(&amp;quot;ICMPv4 received &amp;#039;dst unreachable&amp;#039; code %d, &amp;quot;&lt;br /&gt;
                  &amp;quot;check your routes and firewall rules\n&amp;quot;, icmp-&amp;gt;code);&lt;br /&gt;
        goto drop_pkt;&lt;br /&gt;
    default:&lt;br /&gt;
        print_err(&amp;quot;ICMPv4 did not match supported types\n&amp;quot;);&lt;br /&gt;
        goto drop_pkt;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
drop_pkt:&lt;br /&gt;
    free_skb(skb);&lt;br /&gt;
    return;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void icmpv4_reply(struct sk_buff *skb)&lt;br /&gt;
{&lt;br /&gt;
    struct iphdr *iphdr = ip_hdr(skb);&lt;br /&gt;
    struct icmp_v4 *icmp;&lt;br /&gt;
    struct sock sk;&lt;br /&gt;
    memset(&amp;amp;sk, 0, sizeof(struct sock));&lt;br /&gt;
    &lt;br /&gt;
    uint16_t icmp_len = iphdr-&amp;gt;len - (iphdr-&amp;gt;ihl * 4);&lt;br /&gt;
&lt;br /&gt;
    skb_reserve(skb, ETH_HDR_LEN + IP_HDR_LEN + icmp_len);&lt;br /&gt;
    skb_push(skb, icmp_len);&lt;br /&gt;
    &lt;br /&gt;
    icmp = (struct icmp_v4 *)skb-&amp;gt;data;&lt;br /&gt;
        &lt;br /&gt;
    icmp-&amp;gt;type = ICMP_V4_REPLY;&lt;br /&gt;
    icmp-&amp;gt;csum = 0;&lt;br /&gt;
    icmp-&amp;gt;csum = checksum(icmp, icmp_len, 0);&lt;br /&gt;
&lt;br /&gt;
    skb-&amp;gt;protocol = ICMPV4;&lt;br /&gt;
    sk.daddr = iphdr-&amp;gt;saddr;&lt;br /&gt;
&lt;br /&gt;
    ip_output(&amp;amp;sk, skb);&lt;br /&gt;
    free_skb(skb);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;IP报文的接收&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
int ip_rcv(struct sk_buff *skb)&lt;br /&gt;
{&lt;br /&gt;
    struct iphdr *ih = ip_hdr(skb);&lt;br /&gt;
    uint16_t csum = -1;&lt;br /&gt;
&lt;br /&gt;
    if (ih-&amp;gt;version != IPV4) {&lt;br /&gt;
        print_err(&amp;quot;Datagram version was not IPv4\n&amp;quot;);&lt;br /&gt;
        goto drop_pkt;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (ih-&amp;gt;ihl &amp;lt; 5) {&lt;br /&gt;
        print_err(&amp;quot;IPv4 header length must be at least 5\n&amp;quot;);&lt;br /&gt;
        goto drop_pkt;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (ih-&amp;gt;ttl == 0) {&lt;br /&gt;
        //TODO: Send ICMP error&lt;br /&gt;
        print_err(&amp;quot;Time to live of datagram reached 0\n&amp;quot;);&lt;br /&gt;
        goto drop_pkt;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    csum = checksum(ih, ih-&amp;gt;ihl * 4, 0);&lt;br /&gt;
&lt;br /&gt;
    if (csum != 0) {&lt;br /&gt;
        // Invalid checksum, drop packet handling&lt;br /&gt;
        goto drop_pkt;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // TODO: Check fragmentation, possibly reassemble&lt;br /&gt;
&lt;br /&gt;
    ip_init_pkt(ih);&lt;br /&gt;
&lt;br /&gt;
    ip_dbg(&amp;quot;in&amp;quot;, ih);&lt;br /&gt;
&lt;br /&gt;
    switch (ih-&amp;gt;proto) {&lt;br /&gt;
    case ICMPV4:&lt;br /&gt;
        icmpv4_incoming(skb);&lt;br /&gt;
        return 0;&lt;br /&gt;
    case IP_TCP:&lt;br /&gt;
        tcp_in(skb);&lt;br /&gt;
        return 0;&lt;br /&gt;
    default:&lt;br /&gt;
        print_err(&amp;quot;Unknown IP header proto\n&amp;quot;);&lt;br /&gt;
        goto drop_pkt;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
drop_pkt:&lt;br /&gt;
    free_skb(skb);&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;IP报文发送：&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void ip_send_check(struct iphdr *ihdr)&lt;br /&gt;
{&lt;br /&gt;
    uint32_t csum = checksum(ihdr, ihdr-&amp;gt;ihl * 4, 0);&lt;br /&gt;
    ihdr-&amp;gt;csum = csum;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int ip_output(struct sock *sk, struct sk_buff *skb)&lt;br /&gt;
{&lt;br /&gt;
    struct rtentry *rt;&lt;br /&gt;
    struct iphdr *ihdr = ip_hdr(skb);&lt;br /&gt;
&lt;br /&gt;
    rt = route_lookup(sk-&amp;gt;daddr);&lt;br /&gt;
&lt;br /&gt;
    if (!rt) {&lt;br /&gt;
        // TODO: dest_unreachable&lt;br /&gt;
        print_err(&amp;quot;IP output route lookup fail\n&amp;quot;);&lt;br /&gt;
        return -1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    skb-&amp;gt;dev = rt-&amp;gt;dev;&lt;br /&gt;
    skb-&amp;gt;rt = rt;&lt;br /&gt;
&lt;br /&gt;
    skb_push(skb, IP_HDR_LEN);&lt;br /&gt;
&lt;br /&gt;
    ihdr-&amp;gt;version = IPV4;&lt;br /&gt;
    ihdr-&amp;gt;ihl = 0x05;&lt;br /&gt;
    ihdr-&amp;gt;tos = 0;&lt;br /&gt;
    ihdr-&amp;gt;len = skb-&amp;gt;len;&lt;br /&gt;
    ihdr-&amp;gt;id = ihdr-&amp;gt;id;&lt;br /&gt;
    ihdr-&amp;gt;frag_offset = 0x4000;&lt;br /&gt;
    ihdr-&amp;gt;ttl = 64;&lt;br /&gt;
    ihdr-&amp;gt;proto = skb-&amp;gt;protocol;&lt;br /&gt;
    ihdr-&amp;gt;saddr = skb-&amp;gt;dev-&amp;gt;addr;&lt;br /&gt;
    ihdr-&amp;gt;daddr = sk-&amp;gt;daddr;&lt;br /&gt;
    ihdr-&amp;gt;csum = 0;&lt;br /&gt;
&lt;br /&gt;
    ip_dbg(&amp;quot;out&amp;quot;, ihdr);&lt;br /&gt;
&lt;br /&gt;
    ihdr-&amp;gt;len = htons(ihdr-&amp;gt;len);&lt;br /&gt;
    ihdr-&amp;gt;id = htons(ihdr-&amp;gt;id);&lt;br /&gt;
    ihdr-&amp;gt;daddr = htonl(ihdr-&amp;gt;daddr);&lt;br /&gt;
    ihdr-&amp;gt;saddr = htonl(ihdr-&amp;gt;saddr);&lt;br /&gt;
    ihdr-&amp;gt;csum = htons(ihdr-&amp;gt;csum);&lt;br /&gt;
    ihdr-&amp;gt;frag_offset = htons(ihdr-&amp;gt;frag_offset);&lt;br /&gt;
&lt;br /&gt;
    ip_send_check(ihdr);&lt;br /&gt;
&lt;br /&gt;
    return dst_neigh_output(skb);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;这篇文章的[https://github.com/saminiir/level-ip 源代码]可在 GitHub 上找到。&lt;/div&gt;</summary>
		<author><name>Xlong</name></author>
	</entry>
</feed>