Skip to content

بروتوكول Hysteria2

Hysteria2 هو بروتوكول وكيل قائم على QUIC يتميز بالتفاوض على عرض النطاق الترددي عبر خوارزمية التحكم في الازدحام Brutal، وتمويه Salamander، والتنكر بصورة HTTP/3. يفوض sing-box تنفيذ البروتوكول إلى sing-quic/hysteria2.

المصدر: protocol/hysteria2/inbound.go، protocol/hysteria2/outbound.go، sing-quic/hysteria2

نظرة عامة على البنية

كل من الوارد والصادر هما أغلفة خفيفة حول مكتبة sing-quic/hysteria2:

go
// الوارد
type Inbound struct {
    inbound.Adapter
    router       adapter.Router
    logger       log.ContextLogger
    listener     *listener.Listener
    tlsConfig    tls.ServerConfig
    service      *hysteria2.Service[int]
    userNameList []string
}

// الصادر
type Outbound struct {
    outbound.Adapter
    logger logger.ContextLogger
    client *hysteria2.Client
}

متطلب TLS

يتطلب Hysteria2 TLS بشكل غير مشروط على كلا الجانبين:

go
if options.TLS == nil || !options.TLS.Enabled {
    return nil, C.ErrTLSRequired
}

تمويه Salamander

Salamander هو النوع الوحيد المدعوم للتمويه. يغلف حزم QUIC بطبقة تمويه لمنع الفحص العميق للحزم من التعرف عليها كـ QUIC:

go
var salamanderPassword string
if options.Obfs != nil {
    if options.Obfs.Password == "" {
        return nil, E.New("missing obfs password")
    }
    switch options.Obfs.Type {
    case hysteria2.ObfsTypeSalamander:
        salamanderPassword = options.Obfs.Password
    default:
        return nil, E.New("unknown obfs type: ", options.Obfs.Type)
    }
}

عند تفعيل Salamander، يجب أن تتطابق كلمة المرور بين العميل والخادم.

التفاوض على عرض النطاق الترددي (Brutal CC)

الميزة الأساسية لـ Hysteria2 هي خوارزمية التحكم في الازدحام Brutal، التي تتطلب من العميل الإعلان عن عرض النطاق الترددي الخاص به. يمكن للخادم أيضاً تعيين حدود عرض النطاق الترددي:

go
service, err := hysteria2.NewService[int](hysteria2.ServiceOptions{
    Context:               ctx,
    Logger:                logger,
    BrutalDebug:           options.BrutalDebug,
    SendBPS:               uint64(options.UpMbps * hysteria.MbpsToBps),
    ReceiveBPS:            uint64(options.DownMbps * hysteria.MbpsToBps),
    SalamanderPassword:    salamanderPassword,
    TLSConfig:             tlsConfig,
    IgnoreClientBandwidth: options.IgnoreClientBandwidth,
    UDPTimeout:            udpTimeout,
    Handler:               inbound,
    MasqueradeHandler:     masqueradeHandler,
})

حقول عرض النطاق الترددي الرئيسية:

  • SendBPS / ReceiveBPS: عرض نطاق الإرسال والاستقبال للخادم بالبت في الثانية، محول من Mbps باستخدام hysteria.MbpsToBps
  • IgnoreClientBandwidth: عند التفعيل، يتجاهل الخادم عرض النطاق المعلن من العميل ويستخدم إعداداته الخاصة
  • BrutalDebug: يفعل تسجيل التصحيح للتحكم في الازدحام

يعلن الصادر بالمثل عن عرض النطاق الترددي:

go
client, err := hysteria2.NewClient(hysteria2.ClientOptions{
    SendBPS:    uint64(options.UpMbps * hysteria.MbpsToBps),
    ReceiveBPS: uint64(options.DownMbps * hysteria.MbpsToBps),
    // ...
})

التنكر (Masquerade)

عند وصول حركة مرور غير Hysteria2 (مثل متصفح ويب)، يمكن للوارد تقديم استجابة تنكر. ثلاثة أنواع من التنكر مدعومة:

خادم ملفات

go
case C.Hysterai2MasqueradeTypeFile:
    masqueradeHandler = http.FileServer(http.Dir(options.Masquerade.FileOptions.Directory))

وكيل عكسي

go
case C.Hysterai2MasqueradeTypeProxy:
    masqueradeURL, _ := url.Parse(options.Masquerade.ProxyOptions.URL)
    masqueradeHandler = &httputil.ReverseProxy{
        Rewrite: func(r *httputil.ProxyRequest) {
            r.SetURL(masqueradeURL)
            if !options.Masquerade.ProxyOptions.RewriteHost {
                r.Out.Host = r.In.Host
            }
        },
    }

نص ثابت

go
case C.Hysterai2MasqueradeTypeString:
    masqueradeHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if options.Masquerade.StringOptions.StatusCode != 0 {
            w.WriteHeader(options.Masquerade.StringOptions.StatusCode)
        }
        w.Write([]byte(options.Masquerade.StringOptions.Content))
    })

القفز بين المنافذ (Port Hopping)

يدعم الصادر القفز بين المنافذ -- الاتصال بمنافذ متعددة للخادم لتجنب الخنق لكل منفذ:

go
client, err := hysteria2.NewClient(hysteria2.ClientOptions{
    ServerAddress: options.ServerOptions.Build(),
    ServerPorts:   options.ServerPorts,         // قائمة نطاقات المنافذ
    HopInterval:   time.Duration(options.HopInterval),  // كم مرة يتم تبديل المنافذ
    // ...
})

نموذج المستمع

على عكس البروتوكولات القائمة على TCP، يستمع Hysteria2 على UDP (QUIC). يبدأ الوارد بالاستماع لحزم UDP وتمريرها إلى خدمة QUIC:

go
func (h *Inbound) Start(stage adapter.StartStage) error {
    if stage != adapter.StartStateStart {
        return nil
    }
    h.tlsConfig.Start()
    packetConn, err := h.listener.ListenUDP()
    if err != nil {
        return err
    }
    return h.service.Start(packetConn)
}

إدارة المستخدمين

يتم تحديد المستخدمين بفهرس عددي صحيح، مع قائمة أسماء موازية للتسجيل:

go
userList := make([]int, 0, len(options.Users))
userNameList := make([]string, 0, len(options.Users))
userPasswordList := make([]string, 0, len(options.Users))
for index, user := range options.Users {
    userList = append(userList, index)
    userNameList = append(userNameList, user.Name)
    userPasswordList = append(userPasswordList, user.Password)
}
service.UpdateUsers(userList, userPasswordList)

تستخدم المصادقة فهرس المستخدم المخزن في السياق:

go
userID, _ := auth.UserFromContext[int](ctx)
if userName := h.userNameList[userID]; userName != "" {
    metadata.User = userName
}

معالجة الاتصال

تتبع اتصالات TCP وUDP نمط sing-box القياسي:

go
func (h *Inbound) NewConnectionEx(ctx, conn, source, destination, onClose) {
    // تعيين حقول البيانات الوصفية
    h.router.RouteConnectionEx(ctx, conn, metadata, onClose)
}

func (h *Inbound) NewPacketConnectionEx(ctx, conn, source, destination, onClose) {
    // تعيين حقول البيانات الوصفية
    h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
}

اتصال الصادر

go
func (h *Outbound) DialContext(ctx, network, destination) (net.Conn, error) {
    switch N.NetworkName(network) {
    case N.NetworkTCP:
        return h.client.DialConn(ctx, destination)
    case N.NetworkUDP:
        conn, err := h.ListenPacket(ctx, destination)
        return bufio.NewBindPacketConn(conn, destination), nil
    }
}

func (h *Outbound) ListenPacket(ctx, destination) (net.PacketConn, error) {
    return h.client.ListenPacket(ctx)
}

تحديث الواجهة

ينفذ الصادر adapter.InterfaceUpdateListener للتعامل مع تغييرات الشبكة عن طريق إغلاق اتصال QUIC:

go
func (h *Outbound) InterfaceUpdated() {
    h.client.CloseWithError(E.New("network changed"))
}

أمثلة على التكوين

الوارد

json
{
  "type": "hysteria2",
  "tag": "hy2-in",
  "listen": "::",
  "listen_port": 443,
  "up_mbps": 100,
  "down_mbps": 100,
  "obfs": {
    "type": "salamander",
    "password": "obfs-password"
  },
  "users": [
    { "name": "user1", "password": "user-password" }
  ],
  "tls": {
    "enabled": true,
    "certificate_path": "/path/to/cert.pem",
    "key_path": "/path/to/key.pem"
  },
  "masquerade": {
    "type": "proxy",
    "proxy": {
      "url": "https://www.example.com",
      "rewrite_host": true
    }
  }
}

الصادر

json
{
  "type": "hysteria2",
  "tag": "hy2-out",
  "server": "example.com",
  "server_port": 443,
  "up_mbps": 50,
  "down_mbps": 100,
  "password": "user-password",
  "obfs": {
    "type": "salamander",
    "password": "obfs-password"
  },
  "tls": {
    "enabled": true,
    "server_name": "example.com"
  }
}

مع القفز بين المنافذ

json
{
  "type": "hysteria2",
  "tag": "hy2-hop",
  "server": "example.com",
  "server_ports": "443,8443-8500",
  "hop_interval": "30s",
  "password": "user-password",
  "tls": {
    "enabled": true,
    "server_name": "example.com"
  }
}