第8章  IPv6内部

8.1 IPv6/IPsec的实现

供稿:井上 良信. 翻译:intron@intron.ac.

  本节解释IPv6和IPsec相关的实现内部。 这些功能衍生于 KAME 工程

8.1.1 IPv6

8.1.1.1 一致性

  IPv6 相关的函数与最新的 IPv6 规格一致或努力保持一致。 为了以后的引用,我们将一些相关文档列在下面 (注释: 这不是一个完整的清单 ── 那将太难于维护……)。

  详细信息请参考文档中的特定章节、 RFC、手册页或源代码中的注释。

  一致性测试由TAHI工程的KAME STABLE kit进行。 结果可在 http://www.tahi.org/report/KAME/查看。 过去我们也用快照参加新罕布什尔大学互操作性实验室(Univ. of New Hampshire IOL) (http://www.iol.unh.edu/)的测试。

  • RFC1639: 大地址记录上的FTP操作 (FOOBAR)

    • RFC2428比RFC1639更受欢迎。FTP客户端将 先尝试RFC2428,如果失败了才尝试RFC1639。

  • RFC1886: 支持IPv6的DNS扩展

  • RFC1933: IPv6主机和路由器的过渡机制

    • IPv4兼容地址不被支持。

    • 自动管道(在这个RFC的4.3节中描述)不支持。

    • gif(4) 接口以一种通用的方法实现 IPv[46]-over-IPv[46] 管理,并且覆盖了规格书中描述的“可配置管道”。 详细情况请参见这篇文档中的23.5.1.5节。

  • RFC1981: IPv6的路径最大传输单元的发现

  • RFC2080: IPv6的RIPng

    • usr.sbin/route6d支持此功能。

  • RFC2292: IPv6的高级套接字应用程序接口(API)

    • 对于已被支持的库函数/内核API,参见 sys/netinet6/ADVAPI

  • RFC2362: 协议无关的组播稀疏模式(Protocol Independent Multicast-Sparse Mode, PIM-SM)

    • RFC2362定义PIM-SM的包格式 draft-ietf-pim-ipv6-01.txt据此写成。

  • RFC2373: IPv6寻址结构

    • 支持结点要求的地址,与作用域要求一致。

  • RFC2374: 一种IPv6可聚合全局单播地址格式

    • 支持接口标识的64位长度。

  • RFC2375: IPv6组播地址分派

    • 用户程序使用的众所周知的地址就被在此RFC中分派。

  • RFC2428: IPv6和NAT的FTP扩展

    • RFC2428比RFC1639更受欢迎。FTP客户端会先尝试RFC2428, 如果失败了再尝试RFC1639。

  • RFC2460: IPv6规格

  • RFC2461: IPv6的邻居发现

    • 详细情况请参见本文档23.5.1.2节。

  • RFC2462: IPv6无状态地址自动配置

    • 详细情况请参见本文档23.5.1.4节。

  • RFC2463: IPv6的ICMPv6规格

    • 详细情况请参见本文档23.5.1.9节。

  • RFC2464: 以太网上IPv6包的传输

  • RFC2465: IPv6的MIB:正文惯例和通用群

    • 必要的统计由内核收集。实际的 IPv6 MIB支持以一个补丁包提供给ucd-snmp。

  • RFC2466: IPv6的MIB:ICMPv6群

    • 必要的统计由内核收集。实际的 IPv6 MIB支持以一个补丁包提供给ucd-snmp。

  • RFC2467: 在FDDI网络上IPv6包的传输

  • RFC2497: 在ARCnet网络上IPv6包的传输

  • RFC2553: IPv6的基本套接字接口扩展

    • IPv4映射地址(3.7)和IPv6通配绑定套接字(3.8)的特殊行为都被支持 详细情况请参见本文档的23.5.1.12节。

  • RFC2675: IPv6特大报文

    • 详细情况请参见本文档的23.5.1.7节。

  • RFC2710: IPv6的组播监听者发现

  • RFC2711: IPv6路由器警报选项

  • draft-ietf-ipngwg-router-renum-08: IPv6 的路由器重编号

  • draft-ietf-ipngwg-icmp-namelookups-02: 通过ICMP的IPv6名字查找

  • draft-ietf-ipngwg-icmp-name-lookups-03: 通过ICMP的IPv6名字查找

  • draft-ietf-pim-ipv6-01.txt: IPv6的PIM

  • draft-itojun-ipv6-tcp-to-anycast-00: 关闭向着IPv6任意传播类型(译者注:指单播、组播之类)地址的TCP连接

  • draft-yamamoto-wideipv6-comm-model-00

    • 详细情况请参见本文档的23.5.1.6节。

  • draft-ietf-ipngwg-scopedaddr-format-00.txt : IPv6作用域地址格式的一种扩展

8.1.1.2 近邻发现

  近邻发现是相当稳定的。当前,地址解析(Address Resolution)、 重复地址侦测(Duplicated Address Detection), 以及近邻不可到达侦测(Neighbor Unreachability Detection)都得到了支持。 在不久的将来,我们将在内核中加入代理近邻公告(Proxy Neighbor Advertisement)支持,并加入自发近邻公告(Unsolicited Neighbor Advertisement)传输命令管理工具。

  如果DAD(重复地址侦测)失败,地址将被标记为“重复”, 会有消息产生至syslog中(并且通常会在控制台上有显示)。 “重复”标记可用ifconfig(8)检查。 检查出DAD失败并从失败中恢复是管理员的职责。 这样的行为应该在不久的将来得到改进。

  一些网络驱动器将组播包回环至其自身, 即使已被命令不要这样(尤其在混杂模式)。 在这样DAD可能失败的情况中,DAD引擎可以看见进入的NS包 (实际是从结点自身)并且考虑为表明重复的标志。 你可能需要在查看位于sys/netinet6/nd6_nbr.c:nd6_dad_timer()中 被标记为“heuristics”(试探性的)的#if条件作为这种问题的解释。 (注意在“heuristics”节中的代码片段不与规格一致)。

  近邻发现(Neighbor Discovery)规格书(RFC2461)没有谈及 在如下情形中的近邻缓存处理:

  1. 在没有近邻缓存条目时 结点自发的 RS/NS/NA/重定向 包, 不含数据链路层地址

  2. 介质上的近邻缓存处理,不含数据链路层地址 (我们一个近邻缓存条目给IsRouter位)

  对于第一种情形,我们在IETF ipngwg信件列表讨论的基础上实际解决方法。 更多详细情况请参见源代码中的注释和从(IPng 7155,日期1999年2月6日) 开始的信件线索。

  IPv6的链路上决定规则(RFC2461)与BSD网络代码的假定有很大不同。 在这时,缺省路同器清单为空时,没有链路上决定规则得到支持 (RFC2461,第5.2节,第2段最后一句 ── 注意规格书这一节中有几处错用 词语“host”(主机)和“node”(结点))。

  为避免可能的DoS攻击和无限循环,现在只有ND包中的10个选项被接受。 所以,如果你有20个前缀选项附在RA中,只有前10个前缀会被辨认。 如果这使你遇到麻烦,请在FREEBSD-CURRENT信件列表询问, 和/或修改sys/netinet6/nd6.c中的nd6_maxndop。 如果有大的需求,我们可以为这个变量提供sysctl开关。

8.1.1.3 作用域索引

  IPv6使用有作用域的地址。所以对IPv6地址指定作用域索引是非常重要的 (对于链路-本地地址,为接口索引;对于站点-本地地址,为站点索引)。 没有作用域索引,有作用域的IPv6地址对于内核是意义不明确的。 内核将无法确定一个包的向外接口。

  普通用户级应用程序应当使用高级应用程序接口(API) (RFC2292)来指定作用域索引,或接口索引。与此目的相同的结构体 sockaddr_in6的成员sin6_scope_id被定义在 RFC2553。然而,sin6_scope_id的语义相当含糊。 如果你要照顾到你的应用程序的移植性, 我们建议你使用高级应用程序接口,而不是sin6_scope_id。

  在内核中,一个链路-本地有作用域地址的接口索引被嵌入到 IPv6地址中的第2个16位字(第3、4字节)。 例如,你可以看见

   fe80:1::200:f8ff:fe01:6317
   

  位于路由表和接口地址结构体(struct in6_ifaddr)之中。上面的地址是一个链路-本地单播地址, 属于接口标识为1的网络接口。 被嵌入的索引允许我们有效的鉴别在多个接口上的链路-本地地址, 仅凭一点小的代码改动。

  路由守护程序和配置程序,像 route6d(8)ifconfig(8),将需要使用“被嵌入的”作用域索引。 这些程序使用路由套接字和ioctl(像 SIOCGIFADDR_IN6),内核应用程序接口将返回填入了第2个16位字的IPv6地址。 应用程序接口用来使用内核内部结构体。 总之,使用这些应用程序接口的程序应被准备好应付各种内核的差异。

  当你在命令行指定有作用域地址时,绝不要 写成嵌入形式(例如ff02:1::1或fe80:2::fedc)。这恐怕不行。 请一直使用标准形式,像ff02::1或 fe80::fedc,带上指定接口的命令行选项(像 ping6 -I ne0 ff02::1)。通常, 如果一条命令里没有带上指定外发接口的命令行选项, 那条命令就没有准确接受有作用域地址。 这似乎有悖于IPv6支持“牙科医生办公室”况状的承诺。 我们认为规格书需要对此的改进。

  用户级工具中有一些支持扩展数字IPv6语法, 就像记述于文档 draft-ietf-ipngwg-scopedaddr-format-00.txt中的。 你能指定向外的链路,通过使用像“fe80::1%ne0”的外发接口名。 用这种方法你将没有多少麻烦就能指定链路-本地有作用域地址。

  为了在你的程序中使用这个扩展,你需要使用 getaddrinfo(3),并使用getnameinfo(3)与NI_WITHSCOPEID。 这些实现目前假定一个链路和一个接口的1对1的关系, 这比规格书说的更强。

8.1.1.4 即插即用

  无状态地址自动配置大部分被实现在内核中。 近邻发现函数被实现在内核中,并与内核成为一个整体。 主机的路由器公告(Router Advertisement, RA)输入被实现在内核中。 末端主机的路由器请求(Router Solicitation, RS)输出、 路由器的RS输入和路由器的RA输出被实现在用户级。

8.1.1.4.1 链路-本地和特殊地址的指定

  IPv6链路-本地地址生成自IEEE802地址 (以太网MAC地址)。当接口启用时(IFF_UP),每个接口被自动指定一个IPv6 链路-本地地址。并且链路-本地地址的直接路由被加入到路由表中。

  这里是netstat命令的输出:

Internet6:
Destination                   Gateway                   Flags      Netif Expire
fe80:1::%ed0/64               link#1                    UC          ed0
fe80:2::%ep0/64               link#2                    UC          ep0

  只要有可能,没有IEEE802地址的接口(伪接口, 像隧道接口,或PPP接口)会从其它接口借用IEEE802地址, 例如以太网地址。如果没有IEEE802硬件相连, 一个最近得到的伪随机值,MD5(hostname), 会被用来作为链路-本地地址的来源。如果这对你不适用, 你就需要手工配置链路-本地地址。

  如果一个接口不能处理IPv6(例如缺少组播支持), 链路-本地地址就不会被指定到那个接口。详情参考第2节。

  每个接口将请求得到的组播地址和链路-本地全结点组播地址 (例如分别是fe80::1:ff01:6317和ff02::1,在接口相连的链路上)。 除了一个链路-本地地址,回环地址(::1)也会被指定给回环接口。 同时,::1/128和ff01::/32被自动的加入到路由表, 回环接口连接结点-本地组播群ff01::1。

8.1.1.4.2 主机上的无状态地址自动配置

  在IPv6规格中,结点被分为两类: 路由器主机。 路由器转发包至其它结点,主机不转发包。 net.inet6.ip6.forwarding定义这个结点是路由器还是主机 (如果是1则为路由器,如果是0则为主机)。

  当一台主机监听到来自路由器的路由器公告时, 主机可以自行无状态地址自动配置。 这种行为由net.inet6.ip6.accept_rtadv控制 (如果设为1主机就自动配置自己)。通过自动配置, 接收接口的网络地址前缀 (通常为全局地址前缀)被添加。缺省路由也被配置。 路由器周期性的产生路由器公告包。 为了要求一个相连路由器产生RA包, 主机可以发送路由器请求。 使用rtsol命令在任何时刻产生路由器请求包。 守护程序rtsold(8)也是可用的。 rtsold(8)在任何必要的时候产生路由器请求, 这在移动使用时(笔记本/膝上型计算机)工作的很好。 如果希望忽略路由器公告,使用sysctl设置 net.inet6.ip6.accept_rtadv为0。

  使用守护程序rtadvd(8)产生路由器公告。

  注意,IPv6规格假定如下几条成立, 不一致的情形不被规定:

  • 只有主机会监听路由器公告

  • 主机只有一个网络接口(除了回环)

  所以,在路由器或多接口主机上使能net.inet6.ip6.accept_rtadv是不明智的。 一个被错误配置的结点可能行为古怪 (不一致的配置为那些要做某些实验的人们而被允许)。

  总结sysctl开关:

   accept_rtadv    转发        结点的角色
    ---     ---     ---
    0       0       主机 (待手工配置)
    0       1       路由器
    1       0       被自动配置的主机
                    (规格假定主机只有一个接口,
                    有多个接口的自动配置的主机
                    在作用域外)
    1       1       非法,或实验性的
                    (规格中的“作用域外”)

  RFC2462有针对输入路由器公告前缀信息选项的合法性规则, 位于 5.5.3 (e)。这是为了保护主机免受恶意的 (或被错误配置的)以很短的前缀周期公告的路由器的侵害。 有一个来自Jim Bound发给ipngwg信件列表 (在该文档中查找“(ipng 6712)”)的更新, 这些合法性规则在Jim的更新中被实现。

  参看本文档23.5.1.2 对DAD(重复地址侦测)和自动配置的关系的描述。

8.1.1.5 通用隧道接口

  GIF(通用接口, Generic InterFace)是用于可配置隧道的虚接口。 详细情况被描述在gif(4)。目前有

  • v6 包裹于 v6

  • v6 包裹于 v4

  • v4 包裹于 v6

  • v4 包裹于 v4

  可用。使用gifconfig(8)指派物理(外部)源和目的地址给GIF接口。 source and destination address to gif interfaces. Configuration that 对内部和外部IP头部(v4包裹于v4,或v6包裹于v6)使用相同地址族的配置是危险的。 这很容易将接口和路由表配置为无限级隧道。 请警惕

  GIF可被配置为与ECN匹配。参看23.5.4.5中隧道的ECN匹配问题, 以及gif(4)中的配置方法。

  如果你要用GIF接口配置一个IPv4-in-IPv6隧道, 请仔细阅读gif(4)。你将需要自动删除指派给GIF接口的链路-本地IPv6地址。

8.1.1.6 源地址选择

  当前源选择规则是面向作用域的(有一些例外──见下文)。 对于一个给定的目的,一个源IPv6地址被按如下规则选择:

  1. 如果源地址显式的由用户指定 (例如,通过高级API),则使用被指定地址。

  2. 如果有一个地址被指派给与目的地址有着相同作用域的外发接口 (常常通过查找路由表决定),则使用这个被指派的地址。

    这是最典型的情形。

  3. 如果没有地址满足上述条件, 选择一个指派给发送结点上的一个接口的全局地址。

  4. 如果没有地址满足上述条件, 并目的地址有着站点-本地作用域, 选择一个指派给发送结点上的一个接口的站点-本地地址。

  5. 如果没有地址满足上述条件, 选择与路由表目的对应入口相关联的地址。

  例如,对于ff01::1则::1被选择, 对于fe80:1::2a0:24ff:feab:839b则fe80:1::200:f8ff:fe01:6317选择(注意, 被嵌入的地址索引 ── 描述于23.5.1.3 ── 帮助我们选择正确的源地址。那些被嵌入的索引将不会在线 )。如果外发接口对作用域有多个地址, 地址将会被按最长匹配被选择(规则3)。 假想3ffe:501:808:1:200:f8ff:fe01:6317和3ffe:2001:9:124:200:f8ff:fe01:6317 被给予外发接口。3ffe:501:808:1:200:f8ff:fe01:6317 被选择作为目的3ffe:501:800::1的源。

  注意,上述规则并未在IPv6规格书中载明。 而是被考虑为“由实现决定的”条目。有些我们不使用上述规则的情况。 一个例子是已连接的TCP会话, 我们使用保存在传输控制块(TCB)中的地址作为源。 另一个例子是近邻公告(Neighbor Advertisement, NA)的源地址。 在规格书(RFC2461 7.2.2)里NA的源应该是对应的NS目标的目标地址。 在这种情形中,我们依照规格书而不是上述最长匹配规则。

  对于新连接(此时规则1不适用),如果有其它选择, 不合适的地址(而是首选 lifetime = 0 的地址)将不被选择为源地址。 如果没有其它选择,不合适的地址将被作为最后的选择。 如果不合适的地址有多个选择, 上述的作用域规则将会被用来从那些不合适的地址中做出选择。 如果你要按照某些理由禁用不合适的地址, 配置net.inet6.ip6.use_deprecated为0。 与不合适的地址相关的话题被描述在 RFC2462 5.5.4 (注意: 对于如何使用“不合适的”地址有一些进行中的争论)。

8.1.1.7 Jumbo Payload

  The Jumbo Payload hop-by-hop option is implemented and can be used to send IPv6 packets with payloads longer than 65,535 octets. But currently no physical interface whose MTU is more than 65,535 is supported, so such payloads can be seen only on the loopback interface (i.e. lo0).

  If you want to try jumbo payloads, you first have to reconfigure the kernel so that the MTU of the loopback interface is more than 65,535 bytes; add the following to the kernel configuration file:

   options "LARGE_LOMTU" #To test jumbo payload

  and recompile the new kernel.

  Then you can test jumbo payloads by the ping6(8) command with -b and -s options. The -b option must be specified to enlarge the size of the socket buffer and the -s option specifies the length of the packet, which should be more than 65,535. For example, type as follows:

% ping6 -b 70000 -s 68000 ::1

  The IPv6 specification requires that the Jumbo Payload option must not be used in a packet that carries a fragment header. If this condition is broken, an ICMPv6 Parameter Problem message must be sent to the sender. specification is followed, but you cannot usually see an ICMPv6 error caused by this requirement.

  When an IPv6 packet is received, the frame length is checked and compared to the length specified in the payload length field of the IPv6 header or in the value of the Jumbo Payload option, if any. If the former is shorter than the latter, the packet is discarded and statistics are incremented. You can see the statistics as output of netstat(8) command with `-s -p ip6' option:

% netstat -s -p ip6
      ip6:
        (snip)
        1 with data size < data length

  So, kernel does not send an ICMPv6 error unless the erroneous packet is an actual Jumbo Payload, that is, its packet size is more than 65,535 bytes. As described above, currently no physical interface with such a huge MTU is supported, so it rarely returns an ICMPv6 error.

  TCP/UDP over jumbogram is not supported at this moment. This is because we have no medium (other than loopback) to test this. Contact us if you need this.

  IPsec does not work on jumbograms. This is due to some specification twists in supporting AH with jumbograms (AH header size influences payload length, and this makes it real hard to authenticate inbound packet with jumbo payload option as well as AH).

  There are fundamental issues in *BSD support for jumbograms. We would like to address those, but we need more time to finalize these. To name a few:

  • mbuf pkthdr.len field is typed as "int" in 4.4BSD, so it will not hold jumbogram with len > 2G on 32bit architecture CPUs. If we would like to support jumbogram properly, the field must be expanded to hold 4G + IPv6 header + link-layer header. Therefore, it must be expanded to at least int64_t (u_int32_t is NOT enough).

  • We mistakingly use "int" to hold packet length in many places. We need to convert them into larger integral type. It needs a great care, as we may experience overflow during packet length computation.

  • We mistakingly check for ip6_plen field of IPv6 header for packet payload length in various places. We should be checking mbuf pkthdr.len instead. ip6_input() will perform sanity check on jumbo payload option on input, and we can safely use mbuf pkthdr.len afterwards.

  • TCP code needs a careful update in bunch of places, of course.

8.1.1.8 Loop prevention in header processing

  IPv6 specification allows arbitrary number of extension headers to be placed onto packets. If we implement IPv6 packet processing code in the way BSD IPv4 code is implemented, kernel stack may overflow due to long function call chain. sys/netinet6 code is carefully designed to avoid kernel stack overflow. Because of this, sys/netinet6 code defines its own protocol switch structure, as "struct ip6protosw" (see netinet6/ip6protosw.h). There is no such update to IPv4 part (sys/netinet) for compatibility, but small change is added to its pr_input() prototype. So "struct ipprotosw" is also defined. Because of this, if you receive IPsec-over-IPv4 packet with massive number of IPsec headers, kernel stack may blow up. IPsec-over-IPv6 is okay. (Off-course, for those all IPsec headers to be processed, each such IPsec header must pass each IPsec check. So an anonymous attacker will not be able to do such an attack.)

8.1.1.9 ICMPv6

  After RFC2463 was published, IETF ipngwg has decided to disallow ICMPv6 error packet against ICMPv6 redirect, to prevent ICMPv6 storm on a network medium. This is already implemented into the kernel.

8.1.1.10 Applications

  For userland programming, we support IPv6 socket API as specified in RFC2553, RFC2292 and upcoming Internet drafts.

  TCP/UDP over IPv6 is available and quite stable. You can enjoy telnet(1), ftp(1), rlogin(1), rsh(1), ssh(1), etc. These applications are protocol independent. That is, they automatically chooses IPv4 or IPv6 according to DNS.

8.1.1.11 Kernel Internals

  While ip_forward() calls ip_output(), ip6_forward() directly calls if_output() since routers must not divide IPv6 packets into fragments.

  ICMPv6 should contain the original packet as long as possible up to 1280. UDP6/IP6 port unreach, for instance, should contain all extension headers and the *unchanged* UDP6 and IP6 headers. So, all IP6 functions except TCP never convert network byte order into host byte order, to save the original packet.

  tcp_input(), udp6_input() and icmp6_input() can not assume that IP6 header is preceding the transport headers due to extension headers. So, in6_cksum() was implemented to handle packets whose IP6 header and transport header is not continuous. TCP/IP6 nor UDP6/IP6 header structures do not exist for checksum calculation.

  To process IP6 header, extension headers and transport headers easily, network drivers are now required to store packets in one internal mbuf or one or more external mbufs. A typical old driver prepares two internal mbufs for 96 - 204 bytes data, however, now such packet data is stored in one external mbuf.

  netstat -s -p ip6 tells you whether or not your driver conforms such requirement. In the following example, "cce0" violates the requirement. (For more information, refer to Section 2.)

Mbuf statistics:
                317 one mbuf
                two or more mbuf::
                        lo0 = 8
            cce0 = 10
                3282 one ext mbuf
                0 two or more ext mbuf
   

  Each input function calls IP6_EXTHDR_CHECK in the beginning to check if the region between IP6 and its header is continuous. IP6_EXTHDR_CHECK calls m_pullup() only if the mbuf has M_LOOP flag, that is, the packet comes from the loopback interface. m_pullup() is never called for packets coming from physical network interfaces.

  Both IP and IP6 reassemble functions never call m_pullup().

8.1.1.12 IPv4 mapped address and IPv6 wildcard socket

  RFC2553 describes IPv4 mapped address (3.7) and special behavior of IPv6 wildcard bind socket (3.8). The spec allows you to:

  • Accept IPv4 connections by AF_INET6 wildcard bind socket.

  • Transmit IPv4 packet over AF_INET6 socket by using special form of the address like ::ffff:10.1.1.1.

  but the spec itself is very complicated and does not specify how the socket layer should behave. Here we call the former one "listening side" and the latter one "initiating side", for reference purposes.

  You can perform wildcard bind on both of the address families, on the same port.

  The following table show the behavior of FreeBSD 4.x.

listening side          initiating side
                (AF_INET6 wildcard      (connection to ::ffff:10.1.1.1)
                socket gets IPv4 conn.)
                ---                     ---
FreeBSD 4.x     configurable            supported
                default: enabled
   

  The following sections will give you more details, and how you can configure the behavior.

  Comments on listening side:

  It looks that RFC2553 talks too little on wildcard bind issue, especially on the port space issue, failure mode and relationship between AF_INET/INET6 wildcard bind. There can be several separate interpretation for this RFC which conform to it but behaves differently. So, to implement portable application you should assume nothing about the behavior in the kernel. Using getaddrinfo(3) is the safest way. Port number space and wildcard bind issues were discussed in detail on ipv6imp mailing list, in mid March 1999 and it looks that there is no concrete consensus (means, up to implementers). You may want to check the mailing list archives.

  If a server application would like to accept IPv4 and IPv6 connections, there will be two alternatives.

  One is using AF_INET and AF_INET6 socket (you will need two sockets). Use getaddrinfo(3) with AI_PASSIVE into ai_flags, and socket(2) and bind(2) to all the addresses returned. By opening multiple sockets, you can accept connections onto the socket with proper address family. IPv4 connections will be accepted by AF_INET socket, and IPv6 connections will be accepted by AF_INET6 socket.

  Another way is using one AF_INET6 wildcard bind socket. Use getaddrinfo(3) with AI_PASSIVE into ai_flags and with AF_INET6 into ai_family, and set the 1st argument hostname to NULL. And socket(2) and bind(2) to the address returned. (should be IPv6 unspecified addr). You can accept either of IPv4 and IPv6 packet via this one socket.

  To support only IPv6 traffic on AF_INET6 wildcard binded socket portably, always check the peer address when a connection is made toward AF_INET6 listening socket. If the address is IPv4 mapped address, you may want to reject the connection. You can check the condition by using IN6_IS_ADDR_V4MAPPED() macro.

  To resolve this issue more easily, there is system dependent setsockopt(2) option, IPV6_BINDV6ONLY, used like below.

   int on;

    setsockopt(s, IPPROTO_IPV6, IPV6_BINDV6ONLY,
           (char *)&on, sizeof (on)) < 0));
   

  When this call succeed, then this socket only receive IPv6 packets.

  Comments on initiating side:

  Advise to application implementers: to implement a portable IPv6 application (which works on multiple IPv6 kernels), we believe that the following is the key to the success:

  • NEVER hardcode AF_INET nor AF_INET6.

  • Use getaddrinfo(3) and getnameinfo(3) throughout the system. Never use gethostby*(), getaddrby*(), inet_*() or getipnodeby*(). (To update existing applications to be IPv6 aware easily, sometime getipnodeby*() will be useful. But if possible, try to rewrite the code to use getaddrinfo(3) and getnameinfo(3).)

  • If you would like to connect to destination, use getaddrinfo(3) and try all the destination returned, like telnet(1) does.

  • Some of the IPv6 stack is shipped with buggy getaddrinfo(3). Ship a minimal working version with your application and use that as last resort.

  If you would like to use AF_INET6 socket for both IPv4 and IPv6 outgoing connection, you will need to use getipnodebyname(3). When you would like to update your existing application to be IPv6 aware with minimal effort, this approach might be chosen. But please note that it is a temporal solution, because getipnodebyname(3) itself is not recommended as it does not handle scoped IPv6 addresses at all. For IPv6 name resolution, getaddrinfo(3) is the preferred API. So you should rewrite your application to use getaddrinfo(3), when you get the time to do it.

  When writing applications that make outgoing connections, story goes much simpler if you treat AF_INET and AF_INET6 as totally separate address family. {set,get}sockopt issue goes simpler, DNS issue will be made simpler. We do not recommend you to rely upon IPv4 mapped address.

8.1.1.12.1 unified tcp and inpcb code

  FreeBSD 4.x uses shared tcp code between IPv4 and IPv6 (from sys/netinet/tcp*) and separate udp4/6 code. It uses unified inpcb structure.

  The platform can be configured to support IPv4 mapped address. Kernel configuration is summarized as follows:

  • By default, AF_INET6 socket will grab IPv4 connections in certain condition, and can initiate connection to IPv4 destination embedded in IPv4 mapped IPv6 address.

  • You can disable it on entire system with sysctl like below.

    sysctl net.inet6.ip6.mapped_addr=0

8.1.1.12.1.1 listening side

  Each socket can be configured to support special AF_INET6 wildcard bind (enabled by default). You can disable it on each socket basis with setsockopt(2) like below.

   int on;

    setsockopt(s, IPPROTO_IPV6, IPV6_BINDV6ONLY,
           (char *)&on, sizeof (on)) < 0));
   

  Wildcard AF_INET6 socket grabs IPv4 connection if and only if the following conditions are satisfied:

  • there is no AF_INET socket that matches the IPv4 connection

  • the AF_INET6 socket is configured to accept IPv4 traffic, i.e. getsockopt(IPV6_BINDV6ONLY) returns 0.

  There is no problem with open/close ordering.

8.1.1.12.1.2 initiating side

  FreeBSD 4.x supports outgoing connection to IPv4 mapped address (::ffff:10.1.1.1), if the node is configured to support IPv4 mapped address.

8.1.1.13 sockaddr_storage

  When RFC2553 was about to be finalized, there was discussion on how struct sockaddr_storage members are named. One proposal is to prepend "__" to the members (like "__ss_len") as they should not be touched. The other proposal was not to prepend it (like "ss_len") as we need to touch those members directly. There was no clear consensus on it.

  As a result, RFC2553 defines struct sockaddr_storage as follows:

   struct sockaddr_storage {
        u_char  __ss_len;   /* address length */
        u_char  __ss_family;    /* address family */
        /* and bunch of padding */
    };
   

  On the contrary, XNET draft defines as follows:

   struct sockaddr_storage {
        u_char  ss_len;     /* address length */
        u_char  ss_family;  /* address family */
        /* and bunch of padding */
    };
   

  In December 1999, it was agreed that RFC2553bis should pick the latter (XNET) definition.

  Current implementation conforms to XNET definition, based on RFC2553bis discussion.

  If you look at multiple IPv6 implementations, you will be able to see both definitions. As an userland programmer, the most portable way of dealing with it is to:

  1. ensure ss_family and/or ss_len are available on the platform, by using GNU autoconf,

  2. have -Dss_family=__ss_family to unify all occurrences (including header file) into __ss_family, or

  3. never touch __ss_family. cast to sockaddr * and use sa_family like:

       struct sockaddr_storage ss;
        family = ((struct sockaddr *)&ss)->sa_family
           
    

8.1.2 网络驱动程序

  现在如下两项被要求由标准驱动程序支持:

  1. mbuf聚集。在这个稳定发行版中, 我们为所有操作系统将MINCLSIZE改为MHLEN+1, 以便使所有驱动程序按照我们期望的那样工作。

  2. 组播。如果ifmcstat(8)没有对一个接口产生组播群, 此接口就需要被打补丁。

  如果驱动程序中的任何一个不支持这些要求, 那么驱动程序不能用于IPv6和/或IPsec通信。 如果你发觉你的使用IPv6/IPsec的网卡有问题,那么, 请报告给FreeBSD 问题报告邮件列表

  (注意:过去我们要求所有PCMCIA驱动程序有一个对in6_ifattach()的调用。 我们现在不再有那样的要求。)

8.1.3 翻译器

  我们将IPv4/IPv6翻译器分为4类:

  • 翻译器 A ── 被用于过度的早期阶段,使得从IPv6岛中的IPv6主机建立一个到 IPv4海中的IPv4主机成为可能。

  • 翻译器 B ── 被用于过度的早期阶段,使得从IPv4海中的IPv4主机建立一个到 IPv6岛中的IPv6主机成为可能。

  • 翻译器 C ── 被用于过度的晚期阶段,使得从IPv4岛中的IPv4主机建立一个到 IPv6海中的IPv6主机成为可能。

  • 翻译器 D ── 被用于过度的晚期阶段,使得从IPv6海中的IPv6主机建立一个到 IPv4岛中的IPv4主机成为可能。

  A类的TCP延时翻译器已被支持。 这被称为“FAITH”。我们也提供A类的IP头部翻译器。 (后者还没有被放入FreeBSD 4.x。)

8.1.3.1 FAITH TCP 延时翻译器

  FAITH系统使用TCP延时守护程序,被称为faithd(8), 由内核支持。FAITH将保留一个IPv6地址前缀, 并且把向该前缀的TCP连接中转到IPv4目的。

  例如,如果被保留的IPv6前缀是 3ffe:0501:0200:ffff::,TCP连接的IPv6目的是 3ffe:0501:0200:ffff::163.221.202.12, 连接将被中转到IPv4目的163.221.202.12。

   目的 IPv4 结点 (163.221.202.12)
      ^
      | IPv4 tcp toward 163.221.202.12
    FAITH-中转 双堆栈结点
      ^
      | IPv6 TCP toward 3ffe:0501:0200:ffff::163.221.202.12
    源 IPv6 结点
   

  faithd(8)必须在FAITH-中转双栈结点上被调用。

  更多详细信息,参考 src/usr.sbin/faithd/README

8.1.4 IPsec

  IPsec主要按三个部分组织。

  1. 策略管理

  2. 钥匙管理

  3. AH和ESP处理

8.1.4.1 策略管理

  内核实现了实验性的策略管理代码。 有两种方法管理安全策略。 一种是使用setsockopt(2)配置每个套接字的策略。这种情况下, 相关策略配置被描述在ipsec_set_policy(3)。 另一种是通过setkey(8)使用PF_KEY接口配置以内核包过滤器为基础的策略。

  策略条目不按其索引重排序, 所以在你添加时的条目顺序是非常重要的。

8.1.4.2 钥匙管理

  在工具包(sys/netkey)中实现的钥匙管理代码是一个自造的PFKEY第2版的实现。 这符合RFC2367。

  自造的IKE管理程序“racoon”包含在工具包(kame/kame/racoon)。 一般说来,你需要将racoon运行为守护程序, 然后建立一条策略请求钥匙(就像 ping -P 'out ipsec esp/transport//use')。 内核将会按需要与racoon守护程序联系以交换钥匙。

8.1.4.3 AH 和 ESP 处理

  IPsec模块作为标准IPv4/IPv6处理的“钩子”被实现。 当发送一个包时,ip{,6}_output()通过检查是否可以找到一个匹配用的SPD (Security Policy Database),来检查是否需要ESP/AH处理。 如果需要ESP/AH,{esp,ah}{4,6}_output()将被调用,mbuf将被随之更新。 当收到一个包时,{esp,ah}4_input()将被按协议号调用,即 (*inetsw[proto])()。 {esp,ah}4_input()将解密/检查包的真实性, 并剥去菊花链头部,ESP/AH的填充。 在包受理时剥去ESP/AH部分是安全的, header on packet reception, since we 因为我们从不受理接收到的“原样的”包。

  通过使用ESP/AH,TCP4/6有效数据段大小将受 ESP/AH插入的附加菊花链头部的影响。 我们的代码考虑了这样的情况。

  基本的加密函数可在目录"sys/crypto"中找到。 ESP/AH 转换被列在 {esp,ah}_core.c,带有包裹函数。 使用你希望添加一些算法,就把包裹函数添加至 {esp,ah}_core.c,并添加你的加密算法代码至 sys/crypto。

  这个发行版部分实现了隧道模式, 有如下限制:

  • IPsec隧道不与GIF通用隧道接口组合。 这需要特别注意,因为我们可能会造成在 ip_output() 和 tunnelifp->if_output() 之间的无限循环。 对于是否将他们联合起来更好的观点一直在变化。

  • MTU 和 “不切分”位(Don't Fragment)(IPv4) 还需要进一步考察, 不过一般说来工作情况良好。

  • AH 隧道的认证模式必须被复议。 我们需要改善策略管理引擎, 最终要做的。

8.1.4.4 遵守 RFC 和 ID

  内核中的 IPsec 代码遵守 (或努力去遵守) 如下标准:

  “旧 IPsec”规格,载于 rfc182[5-9].txt

  “新 IPsec”规格,载于 rfc240[1-6].txtrfc241[01].txtrfc2451.txtdraft-mcdonald-simple-ipsec-api-01.txt (草案已过期,但是你可以取自 ftp://ftp.kame.net/pub/internet-drafts/)。 (注意:IKE 规格,rfc241[7-9].txt 在用户级实现,如“racoon”IKE 守护程序)

  当然支持的算法是:

  • 旧 IPsec AH

    • 空加密检查和 (无文档, 仅为排错)

    • 加锁的 MD5 带128位加密检查和 (rfc1828.txt)

    • 加锁的 SHA1 带128位加密检查和 (无文档)

    • HMAC MD5 带128位加密检查和 (rfc2085.txt)

    • HMAC SHA1 带128位加密检查和 (无文档)

  • 旧 IPsec ESP

    • 无加密 (无文档,相同于 rfc2410.txt)

    • DES-CBC 模式 (rfc1829.txt)

  • 新 IPsec AH

    • 空加密检查和 (无文档, 仅为排错)

    • 加锁的 MD5 带96位加密检查和 (无文档)

    • 加锁的 SHA1 带96位加密检查和 (无文档)

    • HMAC MD5 带96位加密检查和 (rfc2403.txt)

    • HMAC SHA1 带96位加密检查和 (rfc2404.txt)

  • 新 IPsec ESP

    • 无加密 (rfc2410.txt)

    • DES-CBC 带衍生的 IV (draft-ietf-ipsec-ciph-des-derived-01.txt, 草案已过期)

    • DES-CBC 带显式的 IV (rfc2405.txt)

    • 3DES-CBC 带显式的 IV (rfc2451.txt)

    • BLOWFISH CBC (rfc2451.txt)

    • CAST128 CBC (rfc2451.txt)

    • RC5 CBC (rfc2451.txt)

    • 上面每种情形可以与下列组合:

      • ESP HMAC-MD5 认证 (96位)

      • ESP HMAC-SHA1 认证 (96位)

  如下算法被支持:

  • 旧 IPsec AH

    • HMAC MD5 带128位加密检查和 + 64位 防重复 (rfc2085.txt)

    • 加锁的 SHA1 带160位加密检查和 + 32位填充 (rfc1852.txt)

  IPsec (在内核中) 和 IKE (用户级的“racoon”) 已被在几种互操作测试情形中测试,与许多其它实现互操作良好。 并且,当前的 IPsec 实现对于载于RFC中的加密算法有很大的覆盖面 (我们只覆盖了无智能属性的算法)。

8.1.4.5 IPsec 隧道兼容 ECN

  与 ECN 兼容良好的 IPsec 隧道的支持被描述在 draft-ipsec-ecn-00.txt

  普通的 IPsec 隧道被描述在 RFC2401 。加封装时, IPv4 TOS 域 (或 IPv6 交换类域) 将被从内部 IP 头部复制到 外部 IP 头部。 去封装时,外部 IP 头部会被简单的抛弃。 去封装的规则与 ECN 不兼容, 这是因为外部 IP TOS/交换类域中的 ECN 位会被丢失。

  为了使 IPsec 隧道与 ECN 配合良好, 我们应该修改加封装和去封装的步骤。 这被描述在 http://www.aciri.org/floyd/papers/draft-ipsec-ecn-00.txt, 第 3 章。

  IPsec 隧道的实现可以给我们三种选择,这些选择通过设置 net.inet.ipsec.ecn (或 net.inet6.ipsec6.ecn) 为一些特定的值来指定:

  • RFC2401: 未考虑 ECN (sysctl 项的值为 -1)

  • ECN 被禁用 (sysctl 项的值为 0)

  • ECN 被允许 (sysctl 项的值为 1)

  注意,以上选项在每个结点都可配置, 而不是按每安全关联 (Security Association, SA) 的方式。 (draft-ipsec-ecn-00 要求按每安全关联进行配置, 但是对于我来说那显得太多了)

  各选项总结如下 (详见源代码):

                加封装                          去封装
                ---                             ---
RFC2401         把所有 TOS 位                   抛弃外部的 TOS 位
                从内部复制到外部                (原样的使用内部 TOS 位)

ECN 被禁用      除 ECN (掩码 0xfc) 外将         抛弃外部的 TOS 位
                TOS 位从内部复制到外部。        (原样的使用内部 TOS 位)
                设置 ECN 位为 0 。

ECN 被允许      除 ECN CE (掩码 0xfe) 外将      使用内部 TOS 位,有一些改变。
                TOS 位从内部复制到外部。        如果外部 ECN CE 位是1,
                设置 ECN CE 位为 0 。           则在内部使能 ECN CE 位。

   

  通用配置方法如下:

  • 如果两个 IPsec 隧道端点能兼容 ECN 你最好将两个端点配置为 “ECN 被允许” (sysctl 项的值为 1)。

  • 如果另一端对 TOS 位的控制很严格,使用“RFC2401” (sysctl 项的值为 -1)。

  • 在其它情形中,使用“ECN 被禁用” (sysctl 项的值为 0)。

  缺省行为是“ECN 被禁用” (sysctl 项的值为 0)。

  更多信息请参考:

   http://www.aciri.org/floyd/papers/draft-ipsec-ecn-00.txt, RFC2481 (显式拥塞通知), src/sys/netinet6/{ah,esp}_input.c

  (感谢长·健二朗 的详细分析 )

8.1.4.6 互操作性

  KAME 的代码已经在一些平台上测试了 IPsec/IKE 的互操作性。 注意,互操作性测试的两边都已修改了它们的实现, 所以如下清单仅供参考。

  Altiga, Ashley-laurent (vpcom.com), Data Fellows (F-Secure), Ericsson ACC, FreeS/WAN, 日立, IBM AIX®, IIJ, Intel, Microsoft® Windows NT®, NIST (linux IPsec + plutoplus), Netscreen, OpenBSD, RedCreek, Routerware, SSH, Secure Computing, Soliton, 东芝, VPNet, Yamaha(在日本还用日文假名写作“ヤマハ”,对应汉字为繁体的“山叶”, 为其创始人山叶寅楠的姓氏,但日本人并不习惯于用汉字“山叶”指称该公司) RT100i

本文档和其它文档可从这里下载:ftp://ftp.FreeBSD.org/pub/FreeBSD/doc/.

如果对于FreeBSD有问题,请先阅读文档,如不能解决再联系<questions@FreeBSD.org>.
关于本文档的问题请发信联系 <doc@FreeBSD.org>.