КДПВ
КДПВ

По данным 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)
	}
}

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

Комментарии (6)


  1. itmind
    08.11.2023 05:09

    Получается, что например, если у меня две сетевых карты, каждая подключена к интернету через своего провайдера и я запущу такой сервер на Go, то все запросы на любой сетевой интерфейс будут идти в этот Go-сервис и ответ этот сервис будет слать через любой из двух сетевых интерфейсов выбрав по определенному алгоритму? Или в чем смысл такого сервера?

    С клиентом более менее понятно, например можно грузить большой файл одновременно и через WiFi и через сотовую сеть.


    1. keylase
      08.11.2023 05:09
      +1

      Всё так, мультиплексирование каналов возможно с обеих точек- и как с клиента, и так и с сервера. Основное требование- поддержка на уровне ядра ОС данного протокола.


    1. igorsd
      08.11.2023 05:09

      Смысл на сервере в том, что сервер должен уметь отправлять клиенту данные в рамках одного соединения на разные адреса клиента. При это у самого сервера совершенно необязательно должно быть несколько интерфейсов.


  1. darkwrat
    08.11.2023 05:09
    +2

    Можно включить MPTCP без пересборки приложения, используя переменные окружения.

    % GODEBUG=multipathtcp=1 go run mptcp.go &
    [1] 70247
    % mptcpize run curl 'http://127.0.0.1:8080/'
    conn.MultipathTCP() = (true, <nil>)
    Исходник сервера
    package main
    
    import (
    	"context"
    	"fmt"
    	"log"
    	"net"
    	"net/http"
    )
    
    type ctxKey uint8
    
    const(
    	ctxHttpConnKey = ctxKey(iota)
    )
    
    func ctxHttpSaveConn(ctx context.Context, conn net.Conn) context.Context {
    	return context.WithValue(ctx, ctxHttpConnKey, conn)
    }
    
    func ctxHttpLoadConn(ctx context.Context) *net.TCPConn {
    	return ctx.Value(ctxHttpConnKey).(*net.TCPConn)
    }
    
    func main() {
    	serv := &http.Server{
    		Addr: ":8080",
    		ConnContext: ctxHttpSaveConn,
    		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    			conn := ctxHttpLoadConn(r.Context())
    			mptcp, err := conn.MultipathTCP()
    			fmt.Fprintf(w, "conn.MultipathTCP() = (%t, %v)", mptcp, err)
    		}),
    	}
    
    	log.Fatal(serv.ListenAndServe())
    }


  1. PrinceKorwin
    08.11.2023 05:09

    Скажите, а почему выбрали Go для такой задачи? Не мешает ли его GC периодическими просадками?


    1. Dreddsa Автор
      08.11.2023 05:09
      +2

      Здравствуйте, потому что:

      • люблю писать на Go

      • статья о добавлении в стандартную библиотеку функционала MPTCP, в бою не пробовал.

      При этом, на мой дилетантский взгляд, GC может помешать только если нужна потребность в обработке данных в режиме реального времени (типа систем реального времени), в остальном GC никому не мешает, а даже помогает.