Pull to refresh

Multipath TCP в Go

Level of difficultyEasy
Reading time4 min
Views5.5K
КДПВ
КДПВ

По данным mptcp.io на 1 ноября 2023 года в глобальной сети функционируют около 350 тысяч ресурсов с поддержкой Multipath TCP (далее - MPTCP).

График с сайта
График с сайта

Ранее уже был проведен некоторый анализ внедрения стандарта.

И, если коротенечко, то MPTCP позволяет использовать несколько каналов связи. Например на смартфоне использовать одновременно WiFi и сотовую сеть.

Предназначен для решения задачи улучшения производительности (утилизации каналов) и надежности передачи данных в сети.

Другим инструментом решения этой задачи можно считать QUIC (пример реализации).

Отличие между MPTCP и QUIC заключается в том, что MPTCP работает поверх протокола TCP и позволяет использовать несколько сетевых путей для передачи данных, в то время как QUIC работает поверх протокола UDP и обеспечивает улучшенную производительность и надежность передачи данных через множество оптимизаций (как TCP, только с хуками). Как-то писал о механизмах надежной передачи данных.

Преимущества MPTCP включают возможность использования нескольких сетевых путей для улучшения производительности и надежности передачи данных.

Преимущества QUIC включают улучшенную производительность и надежность передачи данных за счет использования протокола UDP и оптимизаций, таких как мультиплексирование и быстрое установление соединения.

Некоторое сравнение с численными показателями имеется.

История развития стандарта MPTCP стартует с 2009 года. IETF свободно предоставляет полный комплект документов к ознакомлению. И есть варианты блекджеком и формулами.

Для меня еще интересны ресурсы: multipath-tcp.org и еще их код из репозитория.

Стандарт давно подвезли в iOS, ядро Linux, в документацию Red Hat и т.д.

И вот в Go, начиная с версии 1.21, MPTCP доступен в стандартной библиотеке. Методы

  • клиент

func (*Dialer) SetMultipathTCP(enabled bool)
func (*Dialer) MultipathTCP() bool
  • сервер

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

По умолчанию не используется. И может быть использован, только если ядро поддерживает.

On Linux, the net package can now use Multipath TCP when the kernel supports it. It is not used by default. To use Multipath TCP when available on a client, call the Dialer.SetMultipathTCP method before calling the Dialer.Dial or Dialer.DialContext methods. To use Multipath TCP when available on a server, call the ListenConfig.SetMultipathTCP method before calling the ListenConfig.Listen method. Specify the network as "tcp" or "tcp4" or "tcp6" as usual. If Multipath TCP is not supported by the kernel or the remote host, the connection will silently fall back to TCP. To test whether a particular connection is using Multipath TCP, use the TCPConn.MultipathTCP method.

In a future Go release we may enable Multipath TCP by default on systems that support it.

Но, в общем и целом, его использование может выглядеть так:

  • сервер

package main

import (
	"context"
	"errors"
	"flag"
	"fmt"
	"io"
	"log/slog"
	"net"
	"os"
)

func main() {
	log := slog.New(slog.NewTextHandler(
      os.Stdout, 
      &slog.HandlerOptions{AddSource: true},
    ))
	log.Info("starting server")

    // проверка поддержки mptcp
    lc := &net.ListenConfig{}
	if lc.MultipathTCP() {
		log.Error("multipathTCP should be off by default")
		os.Exit(1)
	}
	log.Info("mptcp supported")

	// прямо включить поддержку mptcp
	lc.SetMultipathTCP(true)
	ln, err := lc.Listen(context.Background(), "tcp", ":8080")
	if err != nil {
		log.Error(err.Error())
		os.Exit(1)
	}
	log.Info("listening on port 8080")

	for {
		conn, err := ln.Accept()
		if err != nil {
			log.Error("conn", "error", err)
			continue
		}

		go func() {
			defer conn.Close()
			// проверка поддержки mptcp
			isMultipathTCP, err := conn.(*net.TCPConn).MultipathTCP()
			fmt.Printf("accepted connection from %s with mptcp: %t, err: %v\n", conn.RemoteAddr(), isMultipathTCP, err)
			for {
				buf := make([]byte, 1024)
				n, err := conn.Read(buf)
				if err != nil {
					if errors.Is(err, io.EOF) {
						return
					}
					log.Error("read", "error", err)
					os.Exit(1)
				}
				if _, err := conn.Write(buf[:n]); err != nil {
					log.Error("write", "error", err)
					os.Exit(1)
				}
			}
		}()
	}
}
  • клиент

package main

import (
	"fmt"
	"log/slog"
	"net"
	"os"
	"time"
)

func main() {
	log := slog.New(slog.NewTextHandler(
      os.Stdout, 
      &slog.HandlerOptions{AddSource: true},
    ))
	log.Info("starting client")

	// проверка поддержки MPTCP
	d := &net.Dialer{}
	if d.MultipathTCP() {
		log.Error("multipathTCP should be off by default")
		os.Exit(1)
	}

	// включение MPTCP
	d.SetMultipathTCP(true)
	if !d.MultipathTCP() {
		log.Error("multipathTCP is not on after having been forced to o")
		os.Exit(1)
	}
	log.Info("mpTCP enabled")

	c, err := d.Dial("tcp", "127.0.0.1:8080")
	if err != nil {
		log.Error("multipathTCP should be off by default")
		os.Exit(1)
	}
	defer c.Close()

	tcp, ok := c.(*net.TCPConn)
	if !ok {
		log.Error("struct is not a TCPConn")
		os.Exit(1)
	}

	// Действительно ли установленное соединение поддерживает mptcp
	mptcp, err := tcp.MultipathTCP()
	if err != nil {
		log.Error("multipathTCP should be off by default")
		os.Exit(1)
	}
	log.Info("outgoing connection from 127.0.0.1:8080 with mptcp")

	// mptcp нет поддержки
	if !mptcp {
		log.Error("outgoing connection is not with MPTCP")
		os.Exit(1)
	}

	for {
		snt := []byte("MPTCP TEST")
		if _, err := c.Write(snt); err != nil {
			log.Error("write", "error", err)
			os.Exit(1)
		}
		b := make([]byte, len(snt))
		if _, err := c.Read(b); err != nil {
			log.Error("read", "error", err)
			os.Exit(1)
		}
		fmt.Println(string(b))
		time.Sleep(time.Second)
	}
}

За основу взят код из статьи.

Tags:
Hubs:
Total votes 11: ↑9 and ↓2+7
Comments13

Articles