ریالیتی 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 چاپ میکند. کلید خصوصی روی سرور میماند و کلید عمومی در هر کارخواه میرود. کلید عمومی از خصوصی کلید عمومی را از روی خصوصی دوباره میسازد. شناسهی کوتاه کارخواهها را بدون کلید اضافه از هم جدا میکند.
// زبان: فارسی
واردسازی شبکه.ریالیتی
تابع ریشه:
keys := ریالیتی.ساخت کلید()
سرچاپ "طول کلید خصوصی:"، keys.کلید خصوصی.طول()
سرچاپ "طول کلید عمومی:"، keys.کلید عمومی.طول()
سرچاپ "عمومی با خصوصی میخواند:"، ریالیتی.کلید عمومی از خصوصی(keys.کلید خصوصی) برابر keys.کلید عمومی
sid := ریالیتی.شناسه کوتاه جدید(4)
سرچاپ "رقمهای شناسهی کوتاه:"، sid.طول()
سرچاپ "شناسههای کوتاه معتبر:"، ریالیتی.شناسه کوتاه معتبر(sid)، ریالیتی.شناسه کوتاه معتبر("abcd1234")، ریالیتی.شناسه کوتاه معتبر("zz")
سرچاپ "کلیدهای عمومی معتبر:"، ریالیتی.کلید عمومی معتبر(keys.کلید عمومی)، ریالیتی.کلید عمومی معتبر("short")
پایانپارامترهای پیوند
پیوند اشتراکی ریالیتی یک پیوند vless:// با security=reality و کلیدهای pbk (کلید عمومی)، sid (شناسهی کوتاه)، spx (مسیر عنکبوتی)، sni، fp (اثر انگشت) و flow است. بستهای که پیوند vless:// را میخواند وی لس است؛ بخش مربوط به ریالیتی اینجاست. متن پرسوجو (بخش پس از ?) را به خواندن پارامترها بدهید تا Settings را پر کند؛ نوشتن پارامترها برمیگرداند. مقدارها همانطور که نوشته شدهاند میمانند و درصدگشایی نمیشوند.
// زبان: فارسی
واردسازی شبکه.ریالیتی
تابع ریشه:
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)
پایانکارخواه، سرور و کاوشگر
نمونه همهچیز را در یک برنامه اجرا میکند. مقصد یک طعمهی ساده است که به یک درخواست جواب میدهد. کارخواه اول کلید و شناسهی کوتاه درست دارد: سرور میپذیرد و دو طرف از راه یک tls.Conn معمولی پیام رد و بدل میکنند. اتصال دوم هرگز TLS صحبت نمیکند و احراز نمیشود؛ به مقصد رله میشود و جواب طعمه را میگیرد. استقرار واقعی همین کد است با مقصد واقعی و دو نیمه در دو برنامه.
// زبان: فارسی
واردسازی شبکه.ریالیتی
واردسازی شبکه.تی سی پی
واردسازی تی ال اس
ناپایا 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)
پایاناستفاده در تونل
پس از موفقیت اتصال روی یا پذیرش یک 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
ساختار جفت کلید| نام | انگلیسی | گونه | توضیح |
|---|---|---|---|
| کلید خصوصی | private_key | رشته = "" | کلید خصوصی. |
| کلید عمومی | public_key | رشته = "" | کلید عمومی. |
پذیرفته شده Accepted
ساختار پذیرفته شدهپذیرش.| نام | انگلیسی | گونه | توضیح |
|---|---|---|---|
| معتبر | ok | منطقی = نادرست | کارخواه احراز شد و conn باز است. |
| پروکسی شده | proxied | منطقی = نادرست | اتصال احراز نشد و تا پایانش به مقصد رله شد. |
| اتصال | conn | تی ال اس.اتصال | اتصال TLS وقتی ok درست است. |
| نام سرور | sni | رشته = "" | نام سروری که کارخواه فرستاد. |
| شناسه کوتاه | short_id | رشته = "" | شناسهی کوتاه کارخواه با ۱۶ رقم هگز. |
| نسخه کارخواه | client_version | رشته = "" | نسخهای که کارخواه در اثبات خود نوشته است به شکل x.y.z؛ کارخواه سلام 26.3.27 میفرستد، یعنی کمینهی پیشفرض ایکسری از ۲۶.۳.۲۷ تا ۲۶.۱۰. |
| خطا | err | رشته = "" | چرا پذیرفته نشد. |
تابعها
ساخت کلید GenerateKey
تابع ساخت کلید(): جفت کلیدکلید عمومی از خصوصی PublicKeyOf
تابع کلید عمومی از خصوصی(private_key: رشته): رشتهشناسه کوتاه جدید NewShortId
تابع شناسه کوتاه جدید(bytes: صحیح): رشتهکلید عمومی معتبر ValidPublicKey
تابع کلید عمومی معتبر(public_key: رشته): منطقیشناسه کوتاه معتبر ValidShortId
تابع شناسه کوتاه معتبر(short_id: رشته): منطقیاتصال روی DialOver
تابع اتصال روی(fd: صحیح۶۴، s: تنظیمات): تی ال اس.اتصالtls.ConnOk نتیجه را بررسی کنید.پذیرش Accept
تابع پذیرش(fd: صحیح۶۴، ss: تنظیمات سرور): پذیرفته شدهخواندن پارامترها ParseParams
تابع خواندن پارامترها(query: رشته): تنظیماتpbk، sid، spx، sni، fp، flow و alpn را از متن پرسوجو میخواند.نوشتن پارامترها FormatParams
تابع نوشتن پارامترها(s: تنظیمات): رشتهsecurity=reality آغاز میشود.