ipv6 端口号_【翻译】 IPv6 与 Rust

f0d6322c31d29c342d9f611f3eab85f1.png
原文标题: IPv6 and Rust
原文链接: https:// blog.apnic.net/2020/06/ 02/ipv6-and-rust/
公众号: Rust碎碎念

Rust是一门旨在将内存安全和高性能相结合的语言,同时它也提供了能够在高效、友好的环境中编写正确代码的工具。它是Mozilla内部在2000年代末的一个研究项目,到2015年趋于稳定并可以用于生产环境。

在NLnet Labs, 我们在准备启动两个与路由安全框架相关的两个项目时选择了Rust,这两个项目是PKI: the certification authority Kril,以及依赖软件,Routinator。 最初只是被保证内存安全这一特点所吸引,但是后来我们开始喜欢上它出色的原生构建工具和丰富的类型系统,该类型系统能够在编译期预防许多常见错误。

但是,使用Rust及其生态系统来写支持IPV6的网络应用程序难度有多大呢?

尽管Rust的标准库非常小,它凭借十分易用的依赖管理,除了最基础的功能外,都将其转移到外部库,但是它确实包含构建网络应用程序所必须的完整的原语集。这些原语是按照Berkeley套接字库建模的:如果您熟悉C语言中的网络编程,您就会很快熟悉Rust的原语。

当然,除此之外,Rust的标准库使用了该语言的类型系统。没有简单地使用无类型的文件描述符,每种socket都有它自己的类型:TcpStream和UdpStream分别用于使用TCP和UDP协议的socket,TcpListener用于socket监听即将到来的TCP连接。与Berkeley sockets非常相似,这些类型具有连接到远程地址或绑定到本地地址的connect和bind方法。

对于这些地址,socket类型同时接受IPv4和IPv6地址协议族。类型系统再次发挥了作用。

Rust强大的枚举类型——本质上是一种原生的、被语言良好支持构建的标记联合类型——允许在协议族之间进行选择。IP地址的类型,即IpAddr,是一种包含Ipv4Addr或者Ipv6Addr的枚举。 SocketAddr类型在这种IP地址类型之上添加了一个端口号。

对一个给定的IPv6地址可以选择手动来创建,但是这样有些枯燥:

use std::net::{IpAddr, Ipv6Addr};

let addr = IpAddr::V6(Ipv6Addr::new(
    0x2001, 0xdb8, 0, 0, 0, 0, 0,1
));

幸运的是,这种事情几乎不再是必须的。相反,地址通常是从用户输入读取,需要从字符串形式进行转换。使用FromStr trait进行这种操作就变得很容易。这个FromStr trait相当于Rust中的一个接口,可以被所有的address类型来实现:

use std::net::IpAddr;
use std::str::FromStr;

let addr = IpAddr::from_str("2001:db8::1").unwrap();

注意这里IpAddr的使用,没有显式地指明地址协议族。大多数情况下,网络应用程序不应该关心一个用户提供的地址是IPv4还是IPv6。这个接口提供了这种透明度。

Socket地址也可以由字符串来创建。FromStr的实现要求主机和端口之间使用冒号分隔开,IPv6地址应该用方括号来包含。尽管如此,还是有一个单独的trait可以创建socket地址,即ToSocketAddrs。这个trait不仅能对字符串表示的地址进行转换,而且还能处理使用pair来表示的地址和端口号:

use std::net::{IpAddr, ToSocketAddrs};

let addrs = "[2001:db8::1]:443".to_socket_addrs().unwrap();
let addrs = ("2001:db8::1", 443).to_socket_addrs().unwrap();

更好的是,ToSocketAddrs还能通过本地的解析器(resolver)来查找主机名。因为这样的查找可以得到不止一个地址,所以ToSocketAddrs实际上返回的是一个socket地址的迭代器。这是否意味着当调用,比如说TcpStream::connect的时候,不得不去遍历所有的地址?不,这个方法已经帮你做了这一步。并且它甚至还能接受任何实现了ToSocketAddrs的事物来作为它的参数。所以,要连接到APNIC(亚太互联网络信息中心)的web服务器的443端口,你只需要做下面这些:

use std::net::TcpStream;
let sock = TcpStream::connect("apnic.net:443").unwrap();

在内部,该实现从底层C库调用getaddrinfo,而没有指定地址族。因此,如果IPv4和IPv6都可用,它将对IPv4和IPv6地址产生一个序列并且会连接到第一个可以工作的地址。至于选择的是IPv4还是IPv6则取决于底层C库的实现以及本地的网络。

对于大多数应用程序,这样都可以完美工作。但是,仅使用标准库,无法对IPv6 socket进行主机解析。幸运地是,net2 crate为网络原语增加了更多选项。 尽管设置socket选项通常是必要的,但是它还提供了socket构建器-TcpBuilder和UdpBuilder,通过它们可以设置地址协议族。 使用net2, 通过IPv6连接到APNIC就相当简单了:

use net2::TcpBuilder;

let builder = TcpBuilder::new_v6().unwrap();
let sock = builder.connect("apnic.net:443").unwrap();

尽管解析器仍然会产生IPv4和IPv6地址,但是使用仅IPv6的socket连接前者会失败,从而将这些地址进行过滤。

对于TcpListenerUdpSocket绑定本地地址,过程也是类似。bind方法接收实现了ToSocketAddrs的类型作为参数,尝试为每一个产生的地址进行bind,直到它操作成功:

use std::net::TcpListener;

let sock = TcpListener::bind("[::1]:8080").unwrap();

同样的,在这里,提供的是IPv4地址还是IPv6地址并不重要,标准库会负责辨别使用哪个地址协议族。

如果一个socket已经连接,查询它的本地和远端地址也是可以的。每一个socket类型都有一个local_addr方法返回socket被绑定的本地地址。TcpStreamUdpStream还可以通过peer_addr返回远端地址。这两个方法都返回一个IpAddr的值,即使是通过net2指定地址协议族创建的socket也是如此。尽管这个值可以被直接显示,比如需要打日志,但是如果需要指定的IPv6处理,它需要进行匹配(match):

use std::net::{IpAddr, TcpStream};

let sock = TcpStream::connect("apnic.net:443").unwrap();
let addr = sock.peer_addr().unwrap();
println!(“Peer address: {}”);
if let IpAddr::V6(addr) = addr {
   // addr is now an Ipv6Addr ...
}

重申一次,模式是相同的:如果你正在开发一款对地址协议和地址解析没有特殊要求的网络应用程序,你不需要关心地址协议族和地址之间的差异以及主机名解析-标准库会做出正确的选择。

一个现代开发平台应该如何对待IPv6: 假定情况被悄悄地考虑了。这或许是Rust及其生态系统最令人震惊的一个例子:许多经过仔细思考,平衡的设计决策。在用Rust开发了两年可用于生产的软件之后,我们对自己的决定非常满意。

原文作者Martin Hoffmann是NLnet Labs的实验室,他专注于软件和标准路由安全性。

61de0508676113a8f0b2e1534212b725.gif
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值