5

MPTCP: 一个在 Go 1.21中的被忽略的新特性

 1 year ago
source link: https://colobu.com/2023/07/03/mptcp-a-go-1-21-new-feature/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

MPTCP: 一个在 Go 1.21中的被忽略的新特性

Go 1.21 再有两三个月就发布了,很多同学都已经总结了Go 1.21的新特性了,为新的Go版本的到来造势,但是我还没看到有同学专门介绍Go 1.21为网络库新增加的一个特性,所以我专门新开一篇专门来介绍。

关于MPTCP这个新特性,专门有一个issue (#56539)跟进和讨论。它是一个对TCP的单路径的扩展,由RFC8684规范来定义。

多路径传输控制协议(Multipath TCP,简称MPTCP)是一种在传输层的协议,旨在增强传统的单路径TCP协议,使其能够在多个网络路径上同时传输数据。MPTCP允许同时利用多条路径进行数据传输,提供更高的带宽、更好的负载均衡和更高的可靠性。
传统的TCP协议是为单路径设计的,它通过在端到端之间的单个连接上进行数据传输。而MPTCP通过引入额外的功能,使得一个TCP连接可以同时在多个网络路径上运行。
MPTCP的工作原理如下:

  1. 建立连接:MPTCP的连接建立过程与传统的TCP类似,但在初始握手时,双方会交换能力选项,以确定是否支持MPTCP。
  2. 子流建立:一旦MPTCP连接建立,它可以启动多个子流(subflow),每个子流通过不同的网络路径传输数据。这些子流可以通过不同的IP地址和端口号来标识。
  3. 路径管理:MPTCP使用路径管理机制来选择和管理多个网络路径。它可以根据路径的质量、延迟、带宽等指标进行路径选择,并根据网络条件动态地调整路径的使用。
  4. 数据传输:MPTCP将数据分割成适当大小的数据块,并在不同的子流上发送。接收端会根据数据块的序列号和数据块所属的子流来重新组装数据。

MPTCP的优点包括:

  • 带宽增强:MPTCP可以同时利用多个路径的带宽,从而提供更高的总体带宽。
  • 负载均衡:MPTCP可以根据路径质量和可用带宽动态地调整数据传输,实现负载均衡,提高网络资源利用率。
  • 容错性:由于数据可以通过多个路径传输,MPTCP可以提供更好的容错性。即使某个路径出现故障,数据仍然可以通过其他可用路径进行传输。
  • 移动性支持:MPTCP可以在移动设备切换网络时维持连接,无需重新建立连接,提供更平滑的移动体验。

MPTCP已经成为一项标准化的协议,它被广泛应用于多路径传输场景,例如数据中心内部通信无线网络移动网络等。

比如apple官方文档指出:

iOS 支持 Multipath TCP (MPTCP),并且允许 iPhone 或 iPad 通过蜂窝数据连接建立与目标主机的备份 TCP 连接。
iPhone 和 iPad 在具有有效的蜂窝数据连接的情况下使用 MPTCP 来建立两个连接:

  • 通过 Wi-Fi 的主要 TCP 连接
  • 通过蜂窝数据的备用连接

如果 Wi-Fi 不可用或无响应,iOS 会使用蜂窝数据连接。
https://support.apple.com/zh-cn/HT201373

小红帽的官方帮助文档也对MPTCP进行了专门的介绍

MPTCP在Linux内核中得到广泛支持,并且已经成为Linux内核的一部分。ChatGPT说从Linux内核版本3.6开始,MPTCP就被纳入主线内核,用户可以通过配置和使用MPTCP功能,但是MPTCP社区网站说MPTCP v1是5.6才开始支持。
Linux发行版如Ubuntu、Fedora和Debian等通常默认包含MPTCP支持。自Linux 5.19开始,MPTCP包含以下的特性:

  • 支持socket系统调用中设置IPPROTO_MPTCP协议
  • 如果对方或中间设备不支持 MPTCP,则从 MPTCP 回退到 TCP
  • 使用内核内或用户空间路径管理器进行路径管理
  • 同样使用 TCP 套接字的套接字选项
  • 调试功能,包括 MIB 计数器、诊断支持(使用ss命令)和跟踪点

经过Go社区和MPTCP社区同学的努力,在Go 1.21版本中终于找到和实现一个方便支持MPTCP方式,总结来说,就是下面的四个方法。

对于TCP client,你可以通过下面的方法设置以及检查是否支持MPTCP:

func (*Dialer) SetMultipathTCP(enabled bool)
func (*Dialer) MultipathTCP() bool

对于TCP server,你可以通过下面的方法设置以及检查是否支持MPTCP:

func (*ListenConfig) SetMultipathTCP(enabled bool)
func (*ListenConfig) MultipathTCP() bool

所以对于一个系统,客户端和服务器端都需要进行设置,才能保证MPTCP启作用。

我以一个简单的例子演示如何使用MPTCP。

服务器端代码如下,为Listener启用mptcp,和客户端建立的连接可能支持mptcp,也可能退化成普通的tcp:

package main
import (
"context"
"errors"
"flag"
"fmt"
"net"
addr = flag.String("addr", ":8080", "service address")
func main() {
flag.Parse()
lc := &net.ListenConfig{}
if lc.MultipathTCP() { // 默认mptcp是禁用的
panic("MultipathTCP should be off by default")
lc.SetMultipathTCP(true) // 主动启用mptcp
ln, err := lc.Listen(context.Background(), "tcp", *addr) // 正常tcp监听
if err != nil {
panic(err)
conn, err := ln.Accept()
if err != nil {
panic(err)
go func() {
defer conn.Close()
isMultipathTCP, err := conn.(*net.TCPConn).MultipathTCP() // 检查连接是否支持了mptcp
fmt.Printf("accepted connection from %s with mptcp: %t, err: %v\n", conn.RemoteAddr(), isMultipathTCP, err)
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil {
if errors.Is(err, io.EOF) {
return
panic(err)
if _, err := conn.Write(buf[:n]); err != nil {
panic(err)

客户端代码如下,我们为dialer启用了mptcp,并且检查建立好的连接是否真的支持mptcp,因为客户端或服务器不支持mptcp的话,就退化成普通的tcp了:

package main
import (
"flag"
"fmt"
"net"
"time"
addr = flag.String("addr", "127.0.0.1:8080", "service address")
func main() {
flag.Parse()
d := &net.Dialer{}
if d.MultipathTCP() { // 默认不启用
panic("MultipathTCP should be off by default")
d.SetMultipathTCP(true) // 主动启用
if !d.MultipathTCP() { // 已经设置dial的时候使用mptcp
panic("MultipathTCP is not on after having been forced to on")
c, err := d.Dial("tcp", *addr)
if err != nil {
panic(err)
defer c.Close()
tcp, ok := c.(*net.TCPConn)
if !ok {
panic("struct is not a TCPConn")
mptcp, err := tcp.MultipathTCP() // 建立的连接是否真的支持mptcp
if err != nil {
panic(err)
fmt.Printf("outgoing connection from %s with mptcp: %t\n", *addr, mptcp)
if !mptcp { // 不支持mptcp, panic
panic("outgoing connection is not with MPTCP")
snt := []byte("MPTCP TEST")
if _, err := c.Write(snt); err != nil {
panic(err)
b := make([]byte, len(snt))
if _, err := c.Read(b); err != nil {
panic(err)
fmt.Println(string(b))
time.Sleep(time.Second)

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK