mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2026-02-26 16:57:08 +00:00
146 lines
3.5 KiB
Go
146 lines
3.5 KiB
Go
package sudoku
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"strings"
|
|
|
|
"github.com/metacubex/mihomo/transport/sudoku/multiplex"
|
|
)
|
|
|
|
const (
|
|
MultiplexMagicByte byte = multiplex.MagicByte
|
|
MultiplexVersion byte = multiplex.Version
|
|
)
|
|
|
|
// StartMultiplexClient writes the multiplex preface and upgrades an already-handshaked Sudoku tunnel into a multiplex session.
|
|
func StartMultiplexClient(conn net.Conn) (*MultiplexClient, error) {
|
|
if conn == nil {
|
|
return nil, fmt.Errorf("nil conn")
|
|
}
|
|
|
|
if err := multiplex.WritePreface(conn); err != nil {
|
|
return nil, fmt.Errorf("write multiplex preface failed: %w", err)
|
|
}
|
|
|
|
sess, err := multiplex.NewClientSession(conn)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("start multiplex session failed: %w", err)
|
|
}
|
|
|
|
return &MultiplexClient{sess: sess}, nil
|
|
}
|
|
|
|
type MultiplexClient struct {
|
|
sess *multiplex.Session
|
|
}
|
|
|
|
// Dial opens a new logical stream, writes the target address, and returns the stream as net.Conn.
|
|
func (c *MultiplexClient) Dial(ctx context.Context, targetAddress string) (net.Conn, error) {
|
|
if c == nil || c.sess == nil || c.sess.IsClosed() {
|
|
return nil, fmt.Errorf("multiplex session is closed")
|
|
}
|
|
if strings.TrimSpace(targetAddress) == "" {
|
|
return nil, fmt.Errorf("target address cannot be empty")
|
|
}
|
|
|
|
addrBuf, err := EncodeAddress(targetAddress)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("encode target address failed: %w", err)
|
|
}
|
|
|
|
if ctx != nil && ctx.Err() != nil {
|
|
return nil, ctx.Err()
|
|
}
|
|
|
|
stream, err := c.sess.OpenStream(addrBuf)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return stream, nil
|
|
}
|
|
|
|
func (c *MultiplexClient) Close() error {
|
|
if c == nil || c.sess == nil {
|
|
return nil
|
|
}
|
|
return c.sess.Close()
|
|
}
|
|
|
|
func (c *MultiplexClient) IsClosed() bool {
|
|
if c == nil || c.sess == nil {
|
|
return true
|
|
}
|
|
return c.sess.IsClosed()
|
|
}
|
|
|
|
// AcceptMultiplexServer upgrades a server-side, already-handshaked Sudoku connection into a multiplex session.
|
|
//
|
|
// The caller must have already consumed the multiplex magic byte (MultiplexMagicByte). This function consumes the
|
|
// multiplex version byte and starts the session.
|
|
func AcceptMultiplexServer(conn net.Conn) (*MultiplexServer, error) {
|
|
if conn == nil {
|
|
return nil, fmt.Errorf("nil conn")
|
|
}
|
|
v, err := multiplex.ReadVersion(conn)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := multiplex.ValidateVersion(v); err != nil {
|
|
return nil, err
|
|
}
|
|
sess, err := multiplex.NewServerSession(conn)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &MultiplexServer{sess: sess}, nil
|
|
}
|
|
|
|
// MultiplexServer wraps a multiplex session created from a handshaked Sudoku tunnel connection.
|
|
type MultiplexServer struct {
|
|
sess *multiplex.Session
|
|
}
|
|
|
|
func (s *MultiplexServer) AcceptStream() (net.Conn, error) {
|
|
if s == nil || s.sess == nil {
|
|
return nil, fmt.Errorf("nil session")
|
|
}
|
|
c, _, err := s.sess.AcceptStream()
|
|
return c, err
|
|
}
|
|
|
|
// AcceptTCP accepts a multiplex stream and returns the target address declared in the open frame.
|
|
func (s *MultiplexServer) AcceptTCP() (net.Conn, string, error) {
|
|
if s == nil || s.sess == nil {
|
|
return nil, "", fmt.Errorf("nil session")
|
|
}
|
|
stream, payload, err := s.sess.AcceptStream()
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
|
|
target, err := DecodeAddress(bytes.NewReader(payload))
|
|
if err != nil {
|
|
_ = stream.Close()
|
|
return nil, "", err
|
|
}
|
|
|
|
return stream, target, nil
|
|
}
|
|
|
|
func (s *MultiplexServer) Close() error {
|
|
if s == nil || s.sess == nil {
|
|
return nil
|
|
}
|
|
return s.sess.Close()
|
|
}
|
|
|
|
func (s *MultiplexServer) IsClosed() bool {
|
|
if s == nil || s.sess == nil {
|
|
return true
|
|
}
|
|
return s.sess.IsClosed()
|
|
}
|