کتابخانه‌ی استاندارد · شبکه

ریالیتی net.reality

لایه‌ی امنیتی ریالیتی (REALITY) در Xray: کارخواهی که خودش را درون دست‌دهی TLS 1.3 ثابت می‌کند، سروری که به بقیه وب‌سایت واقعی را نشان می‌دهد، ابزار کلید و شناسه‌ی کوتاه و پارامترهای پیوند.

واردسازی net.reality

نام آرگومان‌ها شناسه‌ی خود کد است و معادل فارسی ندارد؛ گونه‌ها، کلیدواژه‌ها و نام بسته‌ها با معادل فارسی نوشته می‌شوند.

ریالیتی راهی است برای پنهان کردن یک سرور پراکسی پشت وب‌سایتی که از پیش وجود دارد. کارخواه یک دست‌دهی معمولی TLS 1.3 می‌فرستد که نام سرورش همان وب‌سایت است. درون دست‌دهی، در فیلدی که برای هر کس دیگری بایت تصادفی است، یک پیام کوچک مهرشده می‌گذارد که فقط صاحب کلید سرور می‌تواند بازش کند. اگر پیام باز شود و شناسه‌ی کوتاه شناخته‌شده باشد، سرور به نام خودش جواب می‌دهد و اتصال ترافیک شما را می‌برد. اگر نه (سانسورچی که نشانی را می‌آزماید، مرورگر، پویشگر) سرور کار هوشمندانه‌ای نمی‌کند: بایت‌ها را به وب‌سایت واقعی می‌فرستد و جواب‌ها را برمی‌گرداند، پس کاوشگر گواهی واقعی همان سایت را می‌بیند. گواهی‌ای از خودتان نیست که پیدا شود.

این بسته هر دو نیمه است. اتصال روی نیمه‌ی کارخواه را روی سوکت وصل‌شده اجرا می‌کند و یک tls.Conn برمی‌گرداند. پذیرش نیمه‌ی سرور را روی سوکت پذیرفته‌شده اجرا می‌کند: اولین رکورد را می‌خواند، تصمیم می‌گیرد و یا یک tls.Conn احراز‌شده برمی‌گرداند یا اتصال را به مقصد رله می‌کند. ساخت کلید و شناسه کوتاه جدید رازها را می‌سازند و خواندن پارامترها و نوشتن پارامترها بخش security=reality پیوند vless:// را می‌خوانند و می‌نویسند. پیام آغازین کارخواه را اثر انگشت تی ال اس می‌سازد تا شبیه مرورگر باشد و خود دست‌دهی کد TLS 1.3 بسته‌ی tls است.

روی سیم چه می‌گذرد

گامچه
پیام آغازین کارخواهیک ClientHello شبیه مرورگر با سهم کلید X25519 و وب‌سایت واقعی به عنوان نام سرور. شناسه‌ی نشست ۳۲ بایتی همان پیام مهرشده است.
پیام مهرشده۱۶ بایت: سه بایت نگارش، یک بایت صفر، زمان در ۴ بایت و شناسه‌ی کوتاه ۸ بایتی. با AES-256-GCM مهر می‌شود. کلید از HKDF-SHA-256 روی راز X25519 میان کلید موقت کارخواه و کلید ثابت سرور به دست می‌آید، با ۲۰ بایت اول عدد تصادفی پیام به عنوان نمک و REALITY به عنوان اطلاعات. نانس ۱۲ بایت آخر عدد تصادفی است و کل پیام آغازین (با شناسه‌ی نشست صفر) داده‌ی همراه است.
سرور، احراز نشدهبه مقصد وصل می‌شود، بایت‌های خوانده‌شده را برایش می‌فرستد و در هر دو جهت کپی می‌کند تا هر دو طرف ببندند.
سرور، احراز شدهدست‌دهی TLS 1.3 را با یک گواهی Ed25519 یک‌بارمصرف کامل می‌کند. فیلد امضای گواهی امضا نیست: HMAC-SHA-512 کلید عمومی گواهی است با کلید اشتقاق‌شده‌ی بالا.
بررسی کارخواهبه جای بررسی زنجیره‌ی گواهی، همان HMAC را دوباره حساب می‌کند و امضای CertificateVerify Ed25519 را با کلید گواهی می‌سنجد. فقط سرور واقعی می‌تواند HMAC را بسازد.

چیزهایی که گیر می‌اندازند

  • نام سرور باید یکی از نام های سرور باشد. وگرنه سرور شما را مثل هر غریبه‌ای به وب‌سایت می‌فرستد و کارخواه می‌گوید server did not sign with ed25519، چون گواهی وب‌سایت گواهی HMAC Ed25519 نیست. این پیام در همه‌ی حالت‌ها یعنی «احراز نشد»: کلید عمومی اشتباه، شناسه‌ی کوتاه ناشناخته، نام اشتباه.
  • Xray امروزی فقط پیام آغازینی با سهم کلید ترکیبی را می‌پذیرد. پس از تغییر ML-KEM، Xray ClientHello بدون سهم X25519MLKEM768 پیش از X25519 ساده را رد می‌کند. این بسته همیشه یکی می‌فرستد (محدودیت‌ها را ببینید)، پس با چنین سروری از اثر انگشت chrome یا edge استفاده کنید. نمایه‌های firefox و safari سهم ترکیبی نمی‌فرستند و احراز نمی‌شوند.
  • مقصد باید شبیه سرور شما باشد. Xray رکوردهای دست‌دهی را دقیقاً هم‌اندازه‌ی مقصد می‌سازد. مقصد باید سایت TLS 1.3 باشد که همان ALPN را که شما پیشنهاد می‌کنید (معمولاً h2) برمی‌گزیند و X25519MLKEM768 را انتخاب نمی‌کند، چون این بسته تبادل کلید ترکیبی را نمی‌تواند تمام کند (محدودیت‌ها را ببینید).
  • سوکت کارخواه‌های ناموفق را ببندید. کارخواه ردشده تا وقتی که ببندد رله می‌شود؛ برنامه‌ای که سوکتش را باز بگذارد رله را هم باز نگه می‌دارد.
  • شناسه‌ی کوتاه باید تعداد زوجی رقم هگز باشد، حداکثر ۱۶. با صفر به ۸ بایت پر می‌شود. شناسه‌ی کوتاه خالی همان شناسه‌ی تمام‌صفر است و برای کار کردن باید در فهرست سرور باشد.
  • ساعت‌ها. بیشینه اختلاف زمان به میلی‌ثانیه است و ۰ بررسی را خاموش می‌کند، مثل Xray.

کلیدها و شناسه‌های کوتاه

ساخت کلید یک جفت X25519 را به صورت متن base64url ۴۳ نویسه‌ای برمی‌گرداند، همان که Xray برای xray x25519 چاپ می‌کند. کلید خصوصی روی سرور می‌ماند و کلید عمومی در هر کارخواه می‌رود. کلید عمومی از خصوصی کلید عمومی را از روی خصوصی دوباره می‌سازد. شناسه‌ی کوتاه کارخواه‌ها را بدون کلید اضافه از هم جدا می‌کند.

net-reality-guide-keys.salam
// زبان: فارسی
واردسازی شبکه.ریالیتی

تابع ریشه:
    keys := ریالیتی.ساخت کلید()
    سرچاپ "طول کلید خصوصی:"، keys.کلید خصوصی.طول()
    سرچاپ "طول کلید عمومی:"، keys.کلید عمومی.طول()
    سرچاپ "عمومی با خصوصی می‌خواند:"، ریالیتی.کلید عمومی از خصوصی(keys.کلید خصوصی) برابر keys.کلید عمومی

    sid := ریالیتی.شناسه کوتاه جدید(4)
    سرچاپ "رقم‌های شناسه‌ی کوتاه:"، sid.طول()
    سرچاپ "شناسه‌های کوتاه معتبر:"، ریالیتی.شناسه کوتاه معتبر(sid)، ریالیتی.شناسه کوتاه معتبر("abcd1234")، ریالیتی.شناسه کوتاه معتبر("zz")
    سرچاپ "کلیدهای عمومی معتبر:"، ریالیتی.کلید عمومی معتبر(keys.کلید عمومی)، ریالیتی.کلید عمومی معتبر("short")
پایان
خروجیطول کلید خصوصی: 43 طول کلید عمومی: 43 عمومی با خصوصی می‌خواند: true رقم‌های شناسه‌ی کوتاه: 8 شناسه‌های کوتاه معتبر: true true false کلیدهای عمومی معتبر: true false

پیوند اشتراکی ریالیتی یک پیوند vless:// با security=reality و کلیدهای pbk (کلید عمومی)، sid (شناسه‌ی کوتاه)، spx (مسیر عنکبوتی)، sni، fp (اثر انگشت) و flow است. بسته‌ای که پیوند vless:// را می‌خواند وی لس است؛ بخش مربوط به ریالیتی اینجاست. متن پرس‌وجو (بخش پس از ?) را به خواندن پارامترها بدهید تا Settings را پر کند؛ نوشتن پارامترها برمی‌گرداند. مقدارها همان‌طور که نوشته شده‌اند می‌مانند و درصدگشایی نمی‌شوند.

net-reality-guide-link.salam
// زبان: فارسی
واردسازی شبکه.ریالیتی

تابع ریشه:
    query := "security=reality&pbk=IH8tyHx_SyHSCyaQgNTOvHYefAehmhpbMgkwFeAPnXY&sid=abcd1234&spx=%2F&sni=www.example.com&fp=chrome&flow=xtls-rprx-vision"
    s := ریالیتی.خواندن پارامترها(query)
    سرچاپ s.نام سرور، s.کلید عمومی
    سرچاپ s.شناسه کوتاه، s.اثر انگشت، s.جریان
    سرچاپ ریالیتی.نوشتن پارامترها(s)
پایان
خروجیwww.example.com IH8tyHx_SyHSCyaQgNTOvHYefAehmhpbMgkwFeAPnXY abcd1234 chrome xtls-rprx-vision security=reality&pbk=IH8tyHx_SyHSCyaQgNTOvHYefAehmhpbMgkwFeAPnXY&sid=abcd1234&spx=%2F&sni=www.example.com&fp=chrome&flow=xtls-rprx-vision

کارخواه، سرور و کاوشگر

نمونه همه‌چیز را در یک برنامه اجرا می‌کند. مقصد یک طعمه‌ی ساده است که به یک درخواست جواب می‌دهد. کارخواه اول کلید و شناسه‌ی کوتاه درست دارد: سرور می‌پذیرد و دو طرف از راه یک tls.Conn معمولی پیام رد و بدل می‌کنند. اتصال دوم هرگز TLS صحبت نمی‌کند و احراز نمی‌شود؛ به مقصد رله می‌شود و جواب طعمه را می‌گیرد. استقرار واقعی همین کد است با مقصد واقعی و دو نیمه در دو برنامه.

net-reality-guide-loopback.salam
// زبان: فارسی
واردسازی شبکه.ریالیتی
واردسازی شبکه.تی سی پی
واردسازی تی ال اس

ناپایا ss := ریالیتی.تنظیمات سرور {}

تابع decoy(arg: تهی*):
    srv := تی سی پی.سرور { توصیفگر = arg برگردان صحیح۶۴ }
    c := تی سی پی.پذیرش(srv)
    اگر تی سی پی.اتصال معتبر(c):
        تی سی پی.خواندن(c، 4096)
        تی سی پی.نوشتن(c، "decoy site says hello")
        تی سی پی.بستن(c)
    پایان
پایان

تابع serve(arg: تهی*):
    srv := تی سی پی.سرور { توصیفگر = arg برگردان صحیح۶۴ }
    c := تی سی پی.پذیرش(srv)
    اگر وارونه تی سی پی.اتصال معتبر(c):
        برگشت
    پایان
    r := ریالیتی.پذیرش(c.توصیفگر، ss)
    اگر r.معتبر:
        ناپایا t := r.اتصال
        d := تی ال اس.خواندن(t، 4096)
        تی ال اس.نوشتن(t، "hello " + d + " from short id " + r.شناسه کوتاه)
        تی ال اس.بستن اتصال(t)
    پایان
پایان

تابع ریشه:
    keys := ریالیتی.ساخت کلید()
    dest := تی سی پی.پیوند(0)
    dth := نخ‌ساز(decoy، dest.توصیفگر برگردان تهی*)
    ss.کلید خصوصی = keys.کلید خصوصی
    ss.مقصد = "127.0.0.1:" + تی سی پی.درگاه(dest)
    ss.نام های سرور = "www.example.com"
    ss.شناسه های کوتاه = "abcd1234"

    srv := تی سی پی.پیوند(0)
    sth := نخ‌ساز(serve، srv.توصیفگر برگردان تهی*)
    c := تی سی پی.شماره گیری("127.0.0.1"، تی سی پی.درگاه(srv))
    ناپایا s := ریالیتی.تنظیمات {}
    s.نام سرور = "www.example.com"
    s.کلید عمومی = keys.کلید عمومی
    s.شناسه کوتاه = "abcd1234"
    ناپایا t := ریالیتی.اتصال روی(c.توصیفگر، s)
    سرچاپ "کارخواه وصل شد:"، تی ال اس.اتصال معتبر(t)
    تی ال اس.نوشتن(t، "reality")
    سرچاپ تی ال اس.خواندن(t، 4096)
    تی ال اس.بستن اتصال(t)
    نخ‌پیوند(sth)
    تی سی پی.بستن(تی سی پی.شماره گیری("127.0.0.1"، تی سی پی.درگاه(dest)))
    نخ‌پیوند(dth)

    dest2 := تی سی پی.پیوند(0)
    dth2 := نخ‌ساز(decoy، dest2.توصیفگر برگردان تهی*)
    ss.مقصد = "127.0.0.1:" + تی سی پی.درگاه(dest2)
    srv2 := تی سی پی.پیوند(0)
    sth2 := نخ‌ساز(serve، srv2.توصیفگر برگردان تهی*)
    p := تی سی پی.شماره گیری("127.0.0.1"، تی سی پی.درگاه(srv2))
    تی سی پی.نوشتن(p، "GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n")
    سرچاپ "کاوشگر این را می‌گیرد:"، تی سی پی.خواندن(p، 4096)
    تی سی پی.بستن(p)
    نخ‌پیوند(sth2)
    نخ‌پیوند(dth2)
    تی سی پی.بستن سرور(srv)
    تی سی پی.بستن سرور(srv2)
    تی سی پی.بستن سرور(dest)
    تی سی پی.بستن سرور(dest2)
پایان
خروجیکارخواه وصل شد: true hello reality from short id abcd123400000000 کاوشگر این را می‌گیرد: decoy site says hello

استفاده در تونل

پس از موفقیت اتصال روی یا پذیرش یک tls.Conn دارید، همان چیزی که انتقال شبکه برای اتصال‌های TLS نگه می‌دارد. برای استفاده از ریالیتی به عنوان لایه‌ی امنیتی یک خروجی وی‌لس، سوکت TCP را مثل همیشه وصل کنید و به جای tls.WrapClient این را صدا بزنید: reality.DialOver(fd, settings)؛ روی سرور به جای tls.WrapServer بنویسید reality.Accept(fd, server_settings). tunnel.WrapTls نتیجه را به جریان تبدیل می‌کند. اتصال سپس سرآیند وی‌لس و بار را می‌برد.

چه چیزی با Xray واقعی آزموده شد

Xray 26.11.9 (لینوکس amd64، از انتشار رسمی، با وارسی چکیده) روی رابط loopback با یک سرور TLS 1.3 محلی به عنوان مقصد اجرا شد:

  • این کارخواه در برابر سرور ریالیتی Xray واقعی (ورودی وی‌لس): دست‌دهی کامل می‌شود و یک درخواست وی‌لس از راه آن به یک سرویس پژواک TCP می‌رسد و برمی‌گردد.
  • کارخواه Xray واقعی (خروجی وی‌لس با ریالیتی، اثر انگشت chrome) در برابر این سرور: درخواست پذیرفته می‌شود، سرآیند وی‌لس می‌رسد و داده رفت و برگشت دارد.
  • openssl s_client ساده در برابر این سرور گواهی مقصد را می‌بیند.

آزمون‌های مخزن این بسته را در برابر خودش به کار می‌برند، چون Xray آنجا در دسترس نیست.

محدودیت‌ها

  • از حافظه‌ی کد ریالیتی نوشته شده و فقط با Xray 26.11.9 سنجیده شده. چینش پیام مهرشده، اشتقاق کلید و قاعده‌ی گواهی روی loopback با آن نسخه می‌خواند. Xray‌های قدیمی‌تر و sing-box امتحان نشدند. بررسی نگارش و زمان همان‌طور است که آن نسخه انجام می‌دهد، نه از روی یک مشخصه.
  • سهم کلید ترکیبی یک جانگهدار است. سهم X25519MLKEM768 یک کلید کپسوله‌سازی ML-KEM خوش‌ساخت ولی تصادفی دارد که کلیدی پشتش نیست. مقصدی که این گروه را برگزیند تبادل را با آن کامل می‌کند و این بسته نمی‌تواند تمامش کند، چون ML-KEM پیاده نشده. مقصدی را بگیرید که X25519MLKEM768 را معامله نمی‌کند، یا بپذیرید که با آن سرور کار نمی‌کند.
  • جریان xtls-rprx-vision پیاده نشده. flow فقط در Settings و پارامترهای پیوند حمل می‌شود. جریان را خالی بگذارید.
  • مسیر عنکبوتی spx ذخیره و برگردانده می‌شود و به کار نمی‌رود. کارخواه پس از بررسی ناموفق مقصد را مرور نمی‌کند.
  • امضای اختیاری ML-DSA-65 گواهی در Xray بررسی نمی‌شود.
  • حفاظت از بازپخش ندارد. ClientHello ضبط‌شده دوباره احراز می‌شود. برای محدود کردنش بیشینه اختلاف زمان را تنظیم کنید.
  • HelloRetryRequest ندارد. پیام آغازین در لحظه‌ی ساخت ثابت می‌شود، پس سروری که گروه دیگری بخواهد دست‌دهی را شکست می‌دهد.
  • زمان‌ثابت نیست. کد Ed25519 در برابر بردارهای RFC 8032 درست است ولی در برابر حمله‌های زمانی سخت نشده. سرور برای هر اتصال با کلید تازه امضا می‌کند.
  • سرور کارخواه احراز‌نشده را فقط با خواندن اول (تا ۱۶۳۸۴ بایت) رله می‌کند؛ ClientHello که در چند رکورد تقسیم شده باشد رله می‌شود ولی احراز نمی‌شود.

گونه‌ها

تنظیمات Settings

ساختار تنظیمات
آنچه کارخواه برای رسیدن به سرور ریالیتی لازم دارد.
نامانگلیسیگونهتوضیح
نام سرورserver_nameرشته = ""نام سرور در ClientHello. باید یکی از نام های سرور سرور و نامی باشد که مقصد به آن جواب می‌دهد.
کلید عمومیpublic_keyرشته = ""کلید عمومی سرور، ۴۳ نویسه‌ی base64url.
شناسه کوتاهshort_idرشته = ""حداکثر ۱۶ رقم هگز، تعداد زوج. خالی یعنی همه صفر.
مسیر عنکبوتیspider_xرشته = ""مقدار spx یک پیوند. ذخیره و برگردانده می‌شود، به کار نمی‌رود.
اثر انگشتfingerprintرشته = "chrome"نام نمایه‌ی اثر انگشت تی ال اس. خالی یعنی chrome.
پروتکل برنامهalpnرشته = ""جایگزین ALPN، جداشده با ویرگول. خالی فهرست نمایه را نگه می‌دارد.
جریانflowرشته = ""جریان یک پیوند. فقط حمل می‌شود؛ xtls-rprx-vision پیاده نشده.
جابجایی ساعتclock_offsetصحیح = 0ثانیه‌هایی که به زمان داخل ClientHello افزوده می‌شود. برای آزمون بررسی زمان کارساز.

تنظیمات سرور ServerSettings

ساختار تنظیمات سرور
آنچه سرور لازم دارد.
نامانگلیسیگونهتوضیح
کلید خصوصیprivate_keyرشته = ""کلید خصوصی سرور، ۴۳ نویسه‌ی base64url.
مقصدdestرشته = ""وب‌سایتی که به بقیه نشان داده می‌شود، به شکل host:port.
نام های سرورserver_namesرشته = ""نام‌هایی که کارخواه می‌تواند به عنوان نام سرور بفرستد، جداشده با ویرگول.
شناسه های کوتاهshort_idsرشته = ""شناسه‌های کوتاه پذیرفته‌شده، جداشده با ویرگول.
بیشینه اختلاف زمانmax_time_diffصحیح = 60000بیشترین اختلاف مجاز ساعت کارخواه و سرور به میلی‌ثانیه. پیش‌فرض ۶۰۰۰۰ است و ۰ بررسی را خاموش می‌کند.
مهلت مقصدdest_timeout_msصحیح = 5000زمان مجاز برای وصل شدن به مقصد.

جفت کلید KeyPair

ساختار جفت کلید
یک جفت کلید X25519 به شکل متنی که Xray به کار می‌برد.
نامانگلیسیگونهتوضیح
کلید خصوصیprivate_keyرشته = ""کلید خصوصی.
کلید عمومیpublic_keyرشته = ""کلید عمومی.

پذیرفته شده Accepted

ساختار پذیرفته شده
نتیجه‌ی پذیرش.
نامانگلیسیگونهتوضیح
معتبرokمنطقی = نادرستکارخواه احراز شد و conn باز است.
پروکسی شدهproxiedمنطقی = نادرستاتصال احراز نشد و تا پایانش به مقصد رله شد.
اتصالconnتی ال اس.اتصالاتصال TLS وقتی ok درست است.
نام سرورsniرشته = ""نام سروری که کارخواه فرستاد.
شناسه کوتاهshort_idرشته = ""شناسه‌ی کوتاه کارخواه با ۱۶ رقم هگز.
نسخه کارخواهclient_versionرشته = ""نسخه‌ای که کارخواه در اثبات خود نوشته است به شکل x.y.z؛ کارخواه سلام 26.3.27 می‌فرستد، یعنی کمینه‌ی پیش‌فرض ایکس‌ری از ۲۶.۳.۲۷ تا ۲۶.۱۰.
خطاerrرشته = ""چرا پذیرفته نشد.

تابع‌ها

ساخت کلید GenerateKey

تابع ساخت کلید(): جفت کلید
جفت کلید X25519 تازه می‌سازد.

کلید عمومی از خصوصی PublicKeyOf

تابع کلید عمومی از خصوصی(private_key: رشته): رشته
کلید عمومی یک کلید خصوصی، یا متن خالی وقتی کلید خصوصی معتبر نباشد.

شناسه کوتاه جدید NewShortId

تابع شناسه کوتاه جدید(bytes: صحیح): رشته
شناسه‌ی کوتاه تصادفی با تعداد بایت داده‌شده، ۱ تا ۸، به صورت هگز می‌سازد.

کلید عمومی معتبر ValidPublicKey

تابع کلید عمومی معتبر(public_key: رشته): منطقی
وقتی درست است که متن کلید base64url ۴۳ نویسه‌ای باشد.

شناسه کوتاه معتبر ValidShortId

تابع شناسه کوتاه معتبر(short_id: رشته): منطقی
وقتی درست است که متن تعداد زوجی رقم هگز، حداکثر ۱۶، باشد.

اتصال روی DialOver

تابع اتصال روی(fd: صحیح۶۴، s: تنظیمات): تی ال اس.اتصال
نیمه‌ی کارخواه را روی سوکت وصل‌شده اجرا می‌کند. tls.ConnOk نتیجه را بررسی کنید.

پذیرش Accept

تابع پذیرش(fd: صحیح۶۴، ss: تنظیمات سرور): پذیرفته شده
نیمه‌ی سرور را روی سوکت پذیرفته‌شده اجرا می‌کند. هنگام رله کردن کارخواه ردشده مسدود می‌ماند.

خواندن پارامترها ParseParams

تابع خواندن پارامترها(query: رشته): تنظیمات
pbk، sid، spx، sni، fp، flow و alpn را از متن پرس‌وجو می‌خواند.

نوشتن پارامترها FormatParams

تابع نوشتن پارامترها(s: تنظیمات): رشته
بخش ریالیتی پرس‌وجوی پیوند را می‌نویسد، با security=reality آغاز می‌شود.