راه‌اندازی BGP برای دور زدن مسدود کردن، یا «چگونه دیگر از ترس من دست کشیدم و عاشق RKN شدم»

خوب، خوب، در مورد "عاشق شدم" اغراق آمیز است. بلکه "می تواند با هم همزیستی کند".

همانطور که همه می‌دانید، از 16 آوریل 2018، Roskomnadzor با افزودن /10 گاهی اوقات به رجیستری یکپارچه نام‌های دامنه، فهرست‌های صفحه سایت در اینترنت و آدرس‌های شبکه‌ای که به شما امکان می‌دهد سایت‌هایی را در اینترنت شناسایی کنید که حاوی اطلاعاتی هستند که توزیع آنها گاهی اوقات متن در فدراسیون روسیه ممنوع است، دسترسی به منابع موجود در شبکه را با ضربات بسیار گسترده مسدود کرده است. در نتیجه، شهروندان فدراسیون روسیه و مشاغل آسیب می بینند و دسترسی به منابع کاملا قانونی مورد نیاز خود را از دست می دهند.

پس از اینکه در نظرات یکی از مقالات در هابره گفتم که آماده کمک به قربانیان در راه اندازی یک طرح دور زدن هستم، چند نفر با من تماس گرفتند و از من خواستند تا چنین کمکی کنم. وقتی همه چیز برای آنها کار کرد، یکی از آنها توصیه کرد که این تکنیک را در یک مقاله توضیح دهد. با تأمل، تصمیم گرفتم سکوت خود را در سایت بشکنم و برای یک بار هم که شده سعی کنم چیزی حد واسط بین یک پروژه و یک پست در فیس بوک بنویسم، یعنی. هابراپست نتیجه پیش روی شماست.

سلب مسئولیت

از آنجایی که انتشار راه های دور زدن مسدود کردن دسترسی به اطلاعات ممنوعه در قلمرو فدراسیون روسیه بسیار قانونی نیست، هدف این مقاله صحبت در مورد روشی است که به شما امکان می دهد دسترسی به منابع مجاز در قلمرو فدراسیون روسیه را خودکار کنید، اما به دلیل اقدامات شخصی غیرقابل دسترسی مستقیماً از طریق ارائه دهنده شما. و دسترسی به منابع دیگر که در نتیجه اقدامات مقاله به دست آمده است، یک عارضه جانبی ناگوار است و به هیچ وجه هدف مقاله نیست.

همچنین، از آنجایی که من در درجه اول یک معمار شبکه هستم، حرفه، حرفه و مسیر زندگی، برنامه نویسی و لینوکس نقطه قوت من نیستند. بنابراین، البته، اسکریپت ها را می توان بهتر نوشت، مسائل امنیتی در VPS را می توان عمیق تر کار کرد و غیره. اگر پیشنهادات شما به اندازه کافی مفصل باشد با سپاس پذیرفته می شود - خوشحال می شوم آنها را به متن مقاله اضافه کنم.

TL؛ DR

ما دسترسی به منابع را از طریق تونل موجود شما با استفاده از یک کپی از رجیستری و پروتکل BGP خودکار می کنیم. هدف این است که تمام ترافیک خطاب به منابع مسدود شده را به داخل تونل حذف کنید. حداقل توضیح، عمدتاً دستورالعمل های گام به گام.

برای این به چه چیزی نیاز دارید

متاسفانه این پست برای همه نیست. برای استفاده از این تکنیک، باید چند عنصر را کنار هم قرار دهید:

  1. شما باید یک سرور لینوکس در جایی خارج از قسمت مسدود کردن داشته باشید. یا حداقل تمایل به راه اندازی چنین سروری - از آنجایی که اکنون هزینه آن از 9 دلار در سال و احتمالاً کمتر است. اگر یک تونل VPN جداگانه دارید، این روش نیز مناسب است، سپس سرور می تواند در داخل فیلد بلوک قرار گیرد.
  2. روتر شما باید آنقدر هوشمند باشد که بتواند
    • هر مشتری VPN که دوست دارید (من OpenVPN را ترجیح می دهم، اما می تواند PPTP، L2TP، GRE+IPSec و هر گزینه دیگری که یک رابط تونل ایجاد می کند) باشد.
    • پروتکل BGPv4 این بدان معناست که برای SOHO می‌تواند Mikrotik یا هر روتری با سیستم‌افزار سفارشی OpenWRT/LEDE/ مشابه باشد که به شما امکان نصب Quagga یا Bird را می‌دهد. استفاده از روتر رایانه شخصی نیز ممنوع نیست. برای یک شرکت، به مستندات روتر مرزی خود برای پشتیبانی BGP مراجعه کنید.
  3. شما باید با استفاده از لینوکس و فناوری های شبکه از جمله BGP آشنا باشید. یا حداقل می خواهید این ایده را دریافت کنید. از آنجایی که من این بار حاضر نیستم بیکران را در آغوش بگیرم، باید برخی از نکاتی را که برای شما غیرقابل درک است به تنهایی مطالعه کنید. با این حال، من، البته، به سؤالات خاصی در نظرات پاسخ خواهم داد و بعید است که تنها کسی باشم که پاسخ می دهد، بنابراین در صورت تمایل بپرسید.

آنچه در مثال استفاده شده است

  • کپی ثبت نام https://github.com/zapret-info/z-i 
  • VPS - اوبونتو 16.04
  • خدمات مسیریابی - پرنده 1.6.3   
  • روتر - میکروتیک hAP ac
  • پوشه های کاری - از آنجایی که ما به عنوان root کار می کنیم، بیشتر همه چیز در پوشه اصلی اصلی قرار می گیرد. به ترتیب:
    • /root/blacklist - پوشه کاری با اسکریپت کامپایل
    • /root/zi - یک کپی از رجیستری از github
    • /etc/bird - پوشه تنظیمات استاندارد سرویس پرنده
  • ما 194.165.22.146، ASN 64998 را به عنوان آدرس IP خارجی VPS با سرور مسیریابی و نقطه پایان تونل می پذیریم. آدرس IP خارجی روتر - 81.177.103.94، ASN 64999
  • آدرس های IP داخل تونل به ترتیب 172.30.1.1 و 172.30.1.2 هستند.

راه‌اندازی BGP برای دور زدن مسدود کردن، یا «چگونه دیگر از ترس من دست کشیدم و عاشق RKN شدم»

البته، می توانید از هر روتر، سیستم عامل و محصولات نرم افزاری دیگری استفاده کنید و راه حل را متناسب با منطق آنها تنظیم کنید.

به طور خلاصه - منطق راه حل

  1. اقدامات مقدماتی
    1. گرفتن VPS
    2. ما تونل را از روتر به VPS بالا می بریم
  2. دریافت و به روز رسانی منظم یک نسخه از رجیستری
  3. نصب و پیکربندی سرویس مسیریابی
  4. لیستی از مسیرهای ثابت برای سرویس مسیریابی بر اساس رجیستری ایجاد کنید
  5. ما روتر را به سرویس متصل می کنیم و ارسال تمام ترافیک از طریق تونل را تنظیم می کنیم.

تصمیم واقعی

اقدامات مقدماتی

در وسعت شبکه خدمات بسیاری وجود دارد که VPS را با پول بسیار معقول ارائه می دهند. تا کنون، من این گزینه را برای 9 دلار در سال پیدا کرده‌ام و از آن استفاده کرده‌ام، اما حتی اگر واقعاً زحمت نکشید، گزینه‌های زیادی برای 1E/ماه در هر گوشه وجود دارد. سوال انتخاب VPS بسیار فراتر از محدوده این مقاله است، بنابراین اگر چیزی در این مورد برای کسی روشن نیست، در نظرات بپرسید.

اگر از VPS نه تنها برای سرویس مسیریابی، بلکه برای پایان دادن به یک تونل در آن استفاده می کنید، باید این تونل را بالا ببرید و تقریباً به طور واضح NAT را برای آن پیکربندی کنید. تعداد زیادی دستورالعمل در شبکه برای این اقدامات وجود دارد، من آنها را در اینجا تکرار نمی کنم. نیاز اصلی برای چنین تونلی این است که باید یک رابط جداگانه در روتر شما ایجاد کند که از تونل به سمت VPS پشتیبانی کند. بیشتر فناوری‌های VPN مورد استفاده این نیاز را برآورده می‌کنند - به عنوان مثال، OpenVPN در حالت tun خوب است.

یک کپی از رجیستری دریافت کنید

همانطور که جبرئیل گفت: هر که مانع ما شود ما را یاری می کند. از آنجایی که RKN در حال ایجاد یک رجیستری از منابع ممنوعه است، استفاده نکردن از این رجیستری برای حل مشکل ما گناه است. ما یک کپی از رجیستری را از github دریافت خواهیم کرد.

ما به سرور لینوکس شما می رویم، در متن root'a قرار می گیریم (سودو سو-) و اگر git قبلاً نصب نشده است نصب کنید.

apt install git

به فهرست اصلی خود بروید و یک کپی از رجیستری خارج کنید.

cd ~ && git clone --depth=1 https://github.com/zapret-info/z-i 

یک به‌روزرسانی cron تنظیم کنید (من هر 20 دقیقه آن را دریافت می‌کنم، اما شما می‌توانید هر فاصله زمانی را که به آن علاقه دارید انتخاب کنید). برای این کار راه اندازی می کنیم crontab -e با و خط زیر را به آن اضافه کنید:

*/20 * * * * cd ~/z-i && git pull && git gc

ما یک قلاب را به هم وصل می کنیم که پس از به روز رسانی رجیستری، فایل هایی را برای سرویس مسیریابی ایجاد می کند. برای این کار یک فایل ایجاد می کنیم /root/zi/.git/hooks/post-merge با محتوای زیر:

#!/usr/bin/env bash
changed_files="$(git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD)"
check_run() {
    echo "$changed_files" | grep --quiet "$1" && eval "$2"
}
check_run dump.csv "/root/blacklist/makebgp"

و فراموش نکنید که آن را قابل اجرا کنید

chmod +x /root/z-i/.git/hooks/post-merge

اسکریپت makebgp که توسط هوک ارجاع داده شده است بعدا ایجاد خواهد شد.

نصب و پیکربندی سرویس مسیریابی

پرنده را نصب کنید. متأسفانه نسخه پرنده ای که در حال حاضر در مخازن اوبونتو منتشر شده است از نظر تازگی با مدفوع Archeopteryx قابل مقایسه است، بنابراین ابتدا باید PPA رسمی توسعه دهندگان نرم افزار را به سیستم اضافه کنیم.

add-apt-repository ppa:cz.nic-labs/bird
apt update
apt install bird

پس از آن، ما بلافاصله پرنده را برای IPv6 غیرفعال می کنیم - در این نصب به آن نیازی نخواهیم داشت.

systemctl stop bird6
systemctl disable bird6

در زیر یک فایل پیکربندی حداقلی برای سرویس پرنده (/etc/bird/bird.conf) که برای ما کاملاً کافی است (و یک بار دیگر به شما یادآوری می کنم که هیچ کس توسعه و تنظیم ایده را مطابق با نیازهای خود منع نمی کند)

log syslog all;
router id 172.30.1.1;

protocol kernel {
        scan time 60;
        import none;
#       export all;   # Actually insert routes into the kernel routing table
}

protocol device {
        scan time 60;
}

protocol direct {
        interface "venet*", "tun*"; # Restrict network interfaces it works with
}

protocol static static_bgp {
        import all;
        include "pfxlist.txt";
        #include "iplist.txt";
}

protocol bgp OurRouter {
        description "Our Router";
        neighbor 81.177.103.94 as 64999;
        import none;
        export where proto = "static_bgp";
        local as 64998;
        passive off;
        multihop;
}

شناسه روتر - شناسه روتر، از نظر بصری شبیه آدرس IPv4 است، اما اینطور نیست. در مورد ما، می تواند هر عدد 32 بیتی در قالب آدرس IPv4 باشد، اما تمرین خوبی است که آدرس IPv4 دستگاه خود (در این مورد VPS) را در آنجا مشخص کنید.

پروتکل مستقیم تعیین می کند که کدام رابط ها با فرآیند مسیریابی کار می کنند. این مثال چند نمونه از نام ها را ارائه می دهد، می توانید موارد بیشتری را اضافه کنید. شما همچنین می توانید به سادگی خط را حذف کنید، در این صورت سرور به تمام رابط های موجود با آدرس IPv4 گوش می دهد.

پروتکل استاتیک جادوی ما است که لیستی از پیشوندها و آدرس های IP (که البته پیشوندهای 32/ هستند) را از فایل ها برای اعلام بعدی بارگذاری می کند. این لیست ها از کجا آمده اند در زیر مورد بحث قرار خواهد گرفت. لطفاً توجه داشته باشید که بارگذاری آدرس‌های IP به صورت پیش‌فرض کامنت می‌شود، دلیل این امر حجم زیاد آپلود است. برای مقایسه، در زمان نوشتن مقاله، 78 خط در لیست پیشوندها و 85898 در لیست آدرس های ip وجود دارد، توصیه اکید می کنم که فقط از لیست پیشوندها شروع و اشکال زدایی کنید و پس از آزمایش با روتر خود تصمیم بگیرید که آیا بارگذاری IP در آینده فعال شود یا خیر. هر یک از آنها نمی توانند به راحتی 85 هزار ورودی را در جدول مسیریابی هضم کنند.

پروتکل bgp در واقع همتاسازی bgp را با روتر شما تنظیم می کند. ip-address آدرس رابط خارجی روتر (یا آدرس رابط تونل از کنار روتر) است، 64998 و 64999 شماره سیستم های مستقل هستند. در این مورد، آنها را می توان در قالب هر عدد 16 بیتی اختصاص داد، اما تمرین خوبی است که از اعداد AS از محدوده خصوصی تعریف شده توسط RFC6996 - 64512-65534 شامل استفاده کنید (فرمت ASN 32 بیتی وجود دارد، اما در مورد ما این قطعاً بیش از حد است). پیکربندی توصیف‌شده از همتاسازی eBGP استفاده می‌کند که در آن شماره‌های سیستم مستقل سرویس مسیریابی و روتر باید متفاوت باشند.

همانطور که می بینید، سرویس باید آدرس IP روتر را بداند، بنابراین اگر یک آدرس خصوصی پویا یا غیر قابل مسیریابی (RFC1918) یا اشتراکی (RFC6598) دارید، هیچ گزینه ای برای افزایش همتاسازی در رابط خارجی ندارید، اما سرویس همچنان در داخل تونل کار می کند.

همچنین کاملاً شفاف است که می توانید چندین روتر مختلف را با مسیرهای یک سرویس ارائه دهید - فقط با کپی کردن قسمت پروتکل bgp با تغییر آدرس IP همسایه، تنظیمات را برای آنها کپی کنید. به همین دلیل است که مثال تنظیمات مربوط به نگاه کردن به خارج از تونل را به عنوان جهانی ترین تنظیمات نشان می دهد. حذف آنها به داخل تونل با تغییر آدرس های IP در تنظیمات بر این اساس دشوار نیست.

پردازش رجیستری برای سرویس مسیریابی

اکنون در واقع به ایجاد لیستی از پیشوندها و آدرس های IP نیاز داریم که در مرحله قبل در پروتکل static ذکر شده است. برای انجام این کار، فایل رجیستری را می گیریم و با اسکریپت زیر که در /root/blacklist/makebgp

#!/bin/bash
cut -d";" -f1 /root/z-i/dump.csv| tr '|' 'n' |  tr -d ' ' > /root/blacklist/tmpaddr.txt
cat /root/blacklist/tmpaddr.txt | grep / | sed 's_.*_route & reject;_' > /etc/bird/pfxlist.txt
cat /root/blacklist/tmpaddr.txt | sort | uniq | grep -Eo "([0-9]{1,3}[.]){3}[0-9]{1,3}" | sed 's_.*_route &/32 reject;_' > /etc/bird/iplist.txt
/etc/init.d/bird reload
logger 'bgp list compiled'

فراموش نکنید که آن را قابل اجرا کنید

chmod +x /root/blacklist/makebgp

اکنون می توانید آن را به صورت دستی اجرا کنید و ظاهر فایل ها را در /etc/bird مشاهده کنید.

به احتمال زیاد، در این لحظه پرنده برای شما کار نمی کند، زیرا در مرحله قبل پیشنهاد کردید که فایل هایی را جستجو کند که هنوز وجود نداشته اند. بنابراین، ما آن را راه اندازی می کنیم و کنترل می کنیم که شروع شود:

systemctl start bird
birdc show route

خروجی فرمان دوم باید حدود 80 ورودی را نشان دهد (این در حال حاضر است و وقتی آن را تنظیم می کنید، همه چیز به غیرت ILV در مسدود کردن شبکه ها بستگی دارد) مانند این:

54.160.0.0/12      unreachable [static_bgp 2018-04-19] * (200)

تیم

birdc show protocol

وضعیت پروتکل های داخل سرویس را نشان می دهد. تا زمانی که روتر را پیکربندی نکنید (به پاراگراف بعدی مراجعه کنید)، پروتکل OurRouter در حالت شروع (فازهای اتصال یا فعال) خواهد بود و پس از اتصال موفقیت آمیز، به حالت بالا (فاز Established) می رود. برای مثال، در سیستم من، خروجی این دستور به شکل زیر است:

BIRD 1.6.3 ready.
name     proto    table    state  since       info
kernel1  Kernel   master   up     2018-04-19
device1  Device   master   up     2018-04-19
static_bgp Static   master   up     2018-04-19
direct1  Direct   master   up     2018-04-19
RXXXXXx1 BGP      master   up     13:10:22    Established
RXXXXXx2 BGP      master   up     2018-04-24  Established
RXXXXXx3 BGP      master   start  2018-04-22  Connect       Socket: Connection timed out
RXXXXXx4 BGP      master   up     2018-04-24  Established
RXXXXXx5 BGP      master   start  2018-04-24  Passive

اتصال روتر

احتمالاً همه از خواندن این پاپوش خسته شده اند، اما دل خود را حفظ کنید - پایان نزدیک است. علاوه بر این، در این بخش من نمی توانم دستورالعمل های گام به گام را ارائه دهم - برای هر سازنده متفاوت خواهد بود.

با این حال، من می توانم چند نمونه را به شما نشان دهم. منطق اصلی این است که همتاسازی BGP را افزایش دهید و nexthop را به همه پیشوندهای دریافتی متصل کنید، با اشاره به تونل ما (اگر نیاز به خروجی ترافیک از طریق رابط p2p دارید) یا آدرس IP nexthop اگر ترافیک به اترنت رفت.

به عنوان مثال، در Mikrotik در RouterOS، این مشکل به صورت زیر حل می شود

/routing bgp instance set default as=64999 ignore-as-path-len=yes router-id=172.30.1.2
/routing bgp peer add in-filter=dynamic-in multihop=yes name=VPS remote-address=194.165.22.146 remote-as=64998 ttl=default
/routing filter add action=accept chain=dynamic-in protocol=bgp comment="Set nexthop" set-in-nexthop=172.30.1.1

و در Cisco IOS - مانند این

router bgp 64999
  neighbor 194.165.22.146 remote-as 64998
  neighbor 194.165.22.146 route-map BGP_NEXT_HOP in
  neighbor 194.165.22.146 ebgp-multihop 250
!
route-map BGP_NEXT_HOP permit 10
  set ip next-hop 172.30.1.1

در صورتی که از یک تونل هم برای همتاسازی BGP و هم برای انتقال ترافیک مفید استفاده شود، نیازی به تنظیم nexthop نیست، با استفاده از پروتکل به درستی تنظیم می شود. اما اگر آن را به صورت دستی تنظیم کنید، بدتر هم نمی شود.

در سایر سیستم عامل ها، شما باید خودتان پیکربندی را بفهمید، اما اگر مشکلی دارید، در نظرات بنویسید، من سعی خواهم کرد کمک کنم.

بعد از اینکه جلسه BGP شما بالا رفت، مسیرهای شبکه‌های بزرگ وارد شدند و در جدول نصب شدند، ترافیک آدرس‌ها از آن‌ها تمام شد و شادی نزدیک شد، می‌توانید به سرویس پرنده برگردید و سعی کنید ورودی را که لیست آدرس‌های ip را به هم متصل می‌کند لغو نظر کنید، سپس آن را اجرا کنید.

systemctl reload bird

و ببینید روتر شما چگونه این 85 هزار مسیر را منتقل کرده است. آماده شوید تا آن را خاموش کنید و به این فکر کنید که با آن چه کار کنید 🙂

در کل

صرفاً از نظر تئوری، پس از انجام مراحل فوق، شما سرویسی دارید که به طور خودکار ترافیک را به آدرس های IP ممنوعه در فدراسیون روسیه از سیستم فیلترینگ هدایت می کند.

البته می توان آن را بهبود بخشید. برای مثال، جمع‌بندی فهرستی از آدرس‌های IP از طریق راه‌حل‌های perl یا python به اندازه کافی آسان است. یک اسکریپت ساده پرل که این کار را با Net::CIDR::Lite انجام می‌دهد، 85 هزار پیشوند را به 60 (نه هزار) تبدیل می‌کند، اما طبیعتاً طیف وسیع‌تری از آدرس‌ها را پوشش می‌دهد.

از آنجایی که این سرویس در سطح سوم مدل ISO / OSI کار می کند، اگر به آدرسی که در رجیستری ثبت شده است برطرف نشود، شما را از مسدود شدن سایت / صفحه نجات نخواهد داد. اما همراه با رجیستری از github، فایل nxdomain.txt می رسد که با چند ضربه اسکریپت به راحتی به منبع آدرس مثلاً افزونه SwitchyOmega در کروم تبدیل می شود.

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

اگر سوالی دارید - بپرسید، آماده پاسخگویی هستید.

UPD. متشکرم ناوبری и TerAnYu گزینه هایی برای git برای کاهش حجم دانلود.

UPD2. همکاران، به نظر می رسد اشتباه کردم که دستورالعمل راه اندازی تونل بین VPS و روتر را به مقاله اضافه نکردم. بسیاری از سوالات ناشی از این است.
در هر صورت، دوباره متذکر می شوم - فرض بر این است که قبل از شروع مراحل در این راهنما، قبلاً تونل VPN را در جهتی که نیاز دارید پیکربندی کرده اید و عملکرد آن را بررسی کرده اید (به عنوان مثال، ترافیک را به طور پیش فرض یا ایستا در آنجا قرار می دهید). اگر هنوز این مرحله را کامل نکرده اید، واقعاً منطقی نیست که مراحل مقاله را دنبال کنید. تاکنون متن شخصی خود را در مورد این موضوع ندارم، اما اگر «تنظیم سرور OpenVPN» را به همراه نام سیستم عامل نصب شده روی VPS و «تنظیم مشتری OpenVPN» را با نام روتر خود در گوگل جستجو کنید، به احتمال زیاد تعدادی مقاله در این زمینه از جمله در Habré پیدا خواهید کرد.

UPD3. فداکاری نشده کدی نوشت که فایل حاصل را برای bird از dump.csv با جمع اختیاری آدرس های IP می سازد. بنابراین، بخش "پردازش رجیستری برای سرویس مسیریابی" را می توان با یک فراخوانی به برنامه آن جایگزین کرد. https://habr.com/post/354282/#comment_10782712

UPD4. کمی کار روی خطاها (در متن کمک نکرد):
1) در عوض systemctl reload bird استفاده از دستور منطقی است birdc پیکربندی.
2) در روتر میکروتیک به جای تغییر بعدی هاپ به IP سمت دوم تونل /روتینگ فیلتر افزودن action=accept chain=dynamic-in protocol=bgp comment="Set nexthop" set-in-nexthop=172.30.1.1 منطقی است که مسیر را مستقیماً به رابط تونل بدون آدرس مشخص کنید /روتینگ فیلتر add action=accept chain=dynamic-in protocol=bgp comment="Set nexthop" set-in-nexthop-direct=<interface name>

UPD5. یک سرویس جدید رسیده است https://antifilter.download، از آنجا می توانید لیست های آماده آدرس های IP را بردارید. هر نیم ساعت یکبار به روز می شود. در سمت مشتری، تنها چیزی که باقی می ماند این است که ورودی ها را با "مسیر ... رد" مربوطه قاب کنیم.
و این احتمالا برای شاخ کردن مادربزرگم و به روز رسانی مقاله کافی است.

UPD6. نسخه اصلاح شده مقاله برای کسانی که نمی خواهند بفهمند، اما می خواهند شروع کنند - اینجا.

منبع: www.habr.com

اضافه کردن نظر