DNSTap жана BGP менен ILV бөгөттөөсүн айланып өтүңүз

DNSTap жана BGP менен ILV бөгөттөөсүн айланып өтүңүз

Тема абдан курч, мен билем. Мисалы, улуу бар макала, бирок ал жерде бөгөттөөлөр тизмесинин IP бөлүгү гана каралат. Биз ошондой эле домендерди кошобуз.

Соттор жана РКН бардыгын оңду-солду жаап салгандыктан, провайдерлер Revizorro тарабынан чыгарылган айып пулга түшпөө үчүн катуу аракет кылып жатышат, бөгөт коюудан келип чыккан жоготуулар абдан чоң. Ал эми "мыйзамдуу" бөгөттөлгөн сайттардын арасында көптөгөн пайдалуулары бар (саламатсызбы, rutracker)

Мен РКНнын юрисдикциясынан тышкары жашайм, бирок үйдө ата-энем, туугандарым жана досторум калышты. Ошентип, ITден алыс адамдар үчүн блокировканы айланып өтүүнүн оңой жолун ойлоп табуу чечими кабыл алынды, эң жакшысы алардын катышуусу жок.

Бул жазууда мен негизги тармактык нерселерди кадамдар менен сүрөттөбөйм, бирок бул схеманы кантип ишке ашыруунун жалпы принциптерин сүрөттөп берем. Ошентип, тармактын жалпысынан жана айрыкча Linuxта кантип иштээрин билүү зарыл.

Кулпулардын түрлөрү

Биринчиден, бөгөттөлгөн нерселер жөнүндө эс тутумубузду жаңырталы.

RKNден түшүрүлгөн XMLде кулпулардын бир нече түрлөрү бар:

  • IP
  • домен
  • URL

Жөнөкөйлүк үчүн биз аларды экиге кыскартабыз: IP жана домен, жана биз жөн гана доменди URL аркылуу бөгөттөөдөн чыгарабыз (тагыраак айтканда, алар муну биз үчүн жасашкан).

жакшы адамдардан Роскомсвобода керемет түшүндүм API, бул аркылуу биз керектүү нерсени ала алабыз:

Бөгөттөлгөн сайттарга кирүү

Бул үчүн бизге кичинекей чет элдик VPS керек, эң жакшысы чексиз трафик менен - ​​3-5 долларга булардын көбү бар. Пинг өтө чоң эмес болушу үчүн, аны жакынкы чет өлкөлөрдө алып барышыңыз керек, бирок дагы бир жолу, Интернет жана география дайыма эле дал келбей турганын эске алыңыз. Ал эми 5 баксы үчүн SLA жок болгондуктан, каталарга чыдамдуулук үчүн ар кандай провайдерлерден 2+ даана алган жакшы.

Андан кийин, биз кардар роутерден VPSке чейин шифрленген туннелди орнотушубуз керек. Мен Wireguard'ты эң тез жана эң оңой орнотуу катары колдоном. Менде ошондой эле Linux негизиндеги кардар роутерлери бар (APU2 же OpenWRTдеги бир нерсе). Кээ бир Mikrotik / Cisco учурда, сиз OpenVPN жана GRE-over-IPSEC сыяктуу алардагы протоколдорду колдоно аласыз.

Кызыккан трафикти аныктоо жана кайра багыттоо

Сиз, албетте, чет өлкөлөр аркылуу бардык интернет-трафикти өчүрө аласыз. Бирок, балким, жергиликтүү мазмун менен иштөө ылдамдыгы мындан чоң зыян тартат. Мындан тышкары, VPS боюнча өткөрүү жөндөмдүүлүгү талаптары алда канча жогору болот.

Ошондуктан, биз кандайдыр бир жол менен бөгөттөлгөн сайттарга трафикти бөлүштүрүү жана тандап туннелге багыттоо керек болот. Ал жакка "кошумча" трафиктин бир бөлүгү келип калса да, бардыгын туннел аркылуу айдагандан алда канча жакшы.

Трафикти башкаруу үчүн биз BGP протоколун колдонобуз жана VPSден кардарларга керектүү тармактарга каттамдарды жарыялайбыз. Келгиле, BIRD эң функционалдык жана ыңгайлуу BGP демондорунун бири катары алалы.

IP

IP тарабынан бөгөттөө менен, баары түшүнүктүү: биз VPS менен бардык бөгөттөлгөн IP'дерди жарыялайбыз. Маселе, API кайтарган тизмеде болжол менен 600 миң подсет бар жана алардын басымдуу көпчүлүгү /32 хост. Бул маршруттардын саны алсыз кардар роутерлерин чаташтырышы мүмкүн.

Ошондуктан, тизмени иштеп чыгууда, анда 24 же андан көп хост бар болсо, тармакка / 2 чейин жыйынтыктоо чечими кабыл алынды. Ошентип, каттамдардын саны ~100 миңге чейин кыскарды. Бул үчүн сценарий төмөнкүдөй болот.

Домендер

Бул татаалыраак жана бир нече жолдору бар. Мисалы, сиз ар бир кардар роутерге тунук Squid орнотуп, ал жерде HTTP интервенциясын жасап, биринчи учурда суралган URL дарегин, экинчисинде SNI доменин алуу үчүн TLS кол алышуусуна көз салсаңыз болот.

Бирок жаңы TLS1.3 + eSNI ар кандай түрлөрүнөн улам, HTTPS анализи күн сайын азыраак реалдуу болуп баратат. Ооба, кардар тарабында инфраструктура татаалдашып баратат - сиз жок дегенде OpenWRT колдонушуңуз керек болот.

Ошондуктан, мен DNS сурамдарына жоопторду кармап туруу жолун чечтим. Бул жерде да каалаган DNS-TLS/HTTPS башыңызга кыймылдай баштайт, бирок биз (азыр) кардардагы бул бөлүктү көзөмөлдөй алабыз - же аны өчүрүп коюңуз же DoT/DoH үчүн өз сервериңизди колдонуңуз.

DNSти кантип тосуу керек?

Бул жерде да бир нече ыкмалар болушу мүмкүн.

  • PCAP же NFLOG аркылуу DNS трафикти кармоо
    Бул эки жолду кармап калуу утилитада ишке ашырылат сидмат. Бирок ал көптөн бери колдоого алынган эмес жана функционалдуулугу абдан примитивдүү, андыктан ага жабдык жазуу керек.
  • DNS серверинин журналдарын талдоо
    Тилекке каршы, мага белгилүү болгон рекурсорлор жоопторду киргизе алышпайт, бирок сурамдарды гана жаза алышат. Негизи, бул логикалуу, анткени суроо-талаптардан айырмаланып, жооптор татаал түзүлүшкө ээ жана аларды текст түрүндө жазуу кыйын.
  • DNSTap
    Бактыга жараша, алардын көбү буга чейин бул максат үчүн DNSTap колдойт.

DNSTap деген эмне?

DNSTap жана BGP менен ILV бөгөттөөсүн айланып өтүңүз

Бул DNS серверинен структураланган DNS сурамдарынын жана жоопторунун коллекторуна өткөрүү үчүн Протокол буферлерине жана кадр агымдарына негизделген кардар-сервер протоколу. Негизи, DNS сервери суроо-жооп метаберилиштерин (билдирүүнүн түрү, кардар/сервердин IP ж.б.) плюс толук DNS билдирүүлөрүн (экилик) формада, алар менен тармак аркылуу иштейт.

DNSTap парадигмасында DNS сервери кардар, коллектор сервердин ролун аткарарын түшүнүү маанилүү. Башкача айтканда, DNS сервер коллектор менен туташат, тескерисинче эмес.

Бүгүнкү күндө DNSTap бардык популярдуу DNS серверлеринде колдоого алынат. Бирок, мисалы, BIND көптөгөн дистрибуцияларда (Ubuntu LTS сыяктуу) көбүнчө кандайдыр бир себептерден улам анын колдоосуз курулат. Келгиле, кайра чогултуу менен убара болбойлу, бирок жеңилирээк жана тезирээк рекурсорду алалы - Unbound.

DNSTap кантип кармаса болот?

бар кээ бир сан DNSTap окуяларынын агымы менен иштөө үчүн CLI утилиталары, бирок алар биздин көйгөйдү чечүү үчүн ылайыктуу эмес. Ошондуктан, мен керектүү нерселердин баарын жасай турган өзүмдүн велосипедимди ойлоп табууну чечтим: dnstap-bgp

Иш алгоритми:

  • Ишке киргизилгенде, ал текст файлынан домендердин тизмесин жүктөйт, аларды инвертирлейт (habr.com -> com.habr), үзүлгөн сызыктарды, дубликаттарды жана субдомендерди кошпойт (мисалы, тизмеде habr.com жана www.habr.com болсо, ал биринчиси гана жүктөлөт) жана бул тизме аркылуу тез издөө үчүн префикс дарагын курат
  • DNSTap серверинин милдетин аткарып, ал DNS серверинен туташууну күтөт. Негизи, ал UNIX жана TCP розеткаларын да колдойт, бирок мен билген DNS серверлери UNIX розеткаларын гана колдоно алышат
  • Кирүүчү DNSTap пакеттери алгач Protobuf түзүмүнө сериядан ажыратылат, андан кийин Protobuf талааларынын биринде жайгашкан бинардык DNS билдирүүсү DNS RR жазууларынын деңгээлине талданат.
  • Суралган хосттун (же анын негизги доменинин) жүктөлгөн тизмеде бар-жогу текшерилет, эгерде жок болсо, жооп көңүл бурулбайт.
  • Жооптон бир гана A/AAAA/CNAME RR тандалып алынат жана алардан тиешелүү IPv4/IPv6 даректери чыгарылат
  • IP даректер конфигурациялануучу TTL менен кэштелет жана бардык конфигурацияланган BGP теңдештерине жарнамаланат
  • Буга чейин кэштелген IPди көрсөткөн жооп келгенде, анын TTL жаңыланат
  • TTL мөөнөтү аяктагандан кийин, жазуу кэштен жана BGP жарыяларынан алынып салынат

Кошумча функциялар:

  • SIGHUP домендердин тизмесин кайра окуу
  • Кэшти башка инстанциялар менен синхрондоштуруу dnstap-bgp HTTP/JSON аркылуу
  • Кайра күйгүзгөндөн кийин анын мазмунун калыбына келтирүү үчүн дисктеги кэштин көчүрмөсүн жасаңыз (BoltDB маалымат базасында).
  • Башка тармактын аталыш мейкиндигине өтүүнү колдоо (эмне үчүн бул төмөндө сүрөттөлөт)
  • IPv6 колдоосу

чектөөлөр:

  • IDN домендери азырынча колдоого алынбайт
  • Бир нече BGP жөндөөлөрү

чогулттум RPM жана DEB жеңил орнотуу үчүн пакеттер. Systemd менен салыштырмалуу акыркы ОСтун бардыгында иштеши керек. аларда эч кандай көз карандылык жок.

схемасы

Ошентип, келгиле, бардык компоненттерди чогултуп баштайлы. Натыйжада, биз бул тармак топологиясына окшош нерсени алышыбыз керек:
DNSTap жана BGP менен ILV бөгөттөөсүн айланып өтүңүз

Иштин логикасы, менимче, диаграммадан ачык көрүнүп турат:

  • Кардардын сервери DNS катары конфигурацияланган жана DNS сурамдары да VPN аркылуу өтүшү керек. Бул провайдер бөгөттөө үчүн DNS тосмосун колдоно албашы үчүн зарыл.
  • Сайтты ачып жатканда кардар "xxx.org'дун IP'лери кандай" сыяктуу DNS суроосун жөнөтөт.
  • чектелбеген xxx.org сайтын чечет (же аны кэштен алат) жана кардарга "xxx.org мындай жана мындай IP бар" деп жооп жөнөтөт, аны DNSTap аркылуу параллелдүү түрдө кайталайт.
  • dnstap-bgp бул даректерди жарыялайт БИРД домен бөгөттөлгөн тизмеде болсо, BGP аркылуу
  • БИРД менен бул IPлерге маршрутту жарнамалайт next-hop self кардар роутер
  • Бул IP даректерине кардардан кийинки пакеттер туннель аркылуу өтөт

Серверде бөгөттөлгөн сайттарга маршруттар үчүн мен BIRD ичиндеги өзүнчө таблицаны колдоном жана ал ОС менен эч кандай кесилишкен эмес.

Бул схеманын кемчилиги бар: кардардан биринчи SYN пакети, кыязы, ата мекендик провайдер аркылуу кетүүгө үлгүрөт. маршрут дароо жарыяланбайт. Ал эми бул жерде провайдер бөгөттөөнү кантип жасаганына жараша опциялар болушу мүмкүн. Эгерде ал жөн гана трафикти түшүрсө, анда эч кандай көйгөй жок. Ал эми аны кандайдыр бир DPIге багыттаса, анда (теориялык жактан) атайын эффекттер мүмкүн.

Ошондой эле кардарлар DNS TTL кереметтерин сыйлабашы мүмкүн, бул кардар Unbound сурагандын ордуна өзүнүн чириген кэшиндеги эски жазууларды колдонууга алып келиши мүмкүн.

Иш жүзүндө, биринчиси да, экинчиси да мен үчүн көйгөй жараткан жок, бирок сиздин пробегиңиз ар кандай болушу мүмкүн.

Server Tuning

Жылдыруу үчүн, мен жаздым Ansible үчүн ролу. Ал Linux негизинде серверлерди да, кардарларды да конфигурациялай алат (дебге негизделген бөлүштүрүү үчүн иштелип чыккан). Бардык орнотуулар абдан ачык жана орнотулган inventory.yml. Бул рол менин чоң оюн китебимден кесилген, андыктан анда каталар болушу мүмкүн - суроо-жулуп кош келиңиз 🙂

Келгиле, негизги компоненттерди карап көрөлү.

BGP

Бир хостто эки BGP демондорун иштетүүдө негизги көйгөй бар: BIRD жергиликтүү хост (же кандайдыр бир локалдык интерфейс) менен BGP пирингди орноткусу келбейт. Дегеле сөздөн. Гуглинг жана почта тизмелерин окуу жардам берген жок, алар бул долбоор боюнча деп ырасташат. Балким, бир жолу бардыр, бирок мен аны таба алган жокмун.

Башка BGP демонун сынап көрүңүз, бирок мага BIRD жагат жана аны мен бардык жерде колдоном, мен объекттерди чыгаргым келбейт.

Ошондуктан, мен dnstap-bgpди veth интерфейси аркылуу тамырга туташтырылган тармактын аттар мейкиндигине жашырдым: бул түтүк сыяктуу, анын учтары ар кандай аттар мейкиндигинде чыгып турат. Бул учулардын ар биринде биз хосттун чегинен чыкпаган жеке p2p IP даректерин илип коёбуз, ошондуктан алар каалаган нерсе болушу мүмкүн. Бул процесстерге кирүү үчүн колдонулган механизм баары сүйгөн Docker жана башка контейнерлер.

Бул үчүн жазылган скрипт жана жогоруда сүрөттөлгөн функция dnstap-bgpге кошулган. Ушундан улам, ал root катары иштетилиши керек же setcap буйругу аркылуу CAP_SYS_ADMIN бинардыкына берилиши керек.

Ат мейкиндигин түзүү үчүн мисал скрипт

#!/bin/bash

NS="dtap"

IP="/sbin/ip"
IPNS="$IP netns exec $NS $IP"

IF_R="veth-$NS-r"
IF_NS="veth-$NS-ns"

IP_R="192.168.149.1"
IP_NS="192.168.149.2"

/bin/systemctl stop dnstap-bgp || true

$IP netns del $NS > /dev/null 2>&1
$IP netns add $NS

$IP link add $IF_R type veth peer name $IF_NS
$IP link set $IF_NS netns $NS

$IP addr add $IP_R remote $IP_NS dev $IF_R
$IP link set $IF_R up

$IPNS addr add $IP_NS remote $IP_R dev $IF_NS
$IPNS link set $IF_NS up

/bin/systemctl start dnstap-bgp

dnstap-bgp.conf

namespace = "dtap"
domains = "/var/cache/rkn_domains.txt"
ttl = "168h"

[dnstap]
listen = "/tmp/dnstap.sock"
perm = "0666"

[bgp]
as = 65000
routerid = "192.168.149.2"

peers = [
    "192.168.149.1",
]

bird.conf

router id 192.168.1.1;

table rkn;

# Clients
protocol bgp bgp_client1 {
    table rkn;
    local as 65000;
    neighbor 192.168.1.2 as 65000;
    direct;
    bfd on;
    next hop self;
    graceful restart;
    graceful restart time 60;
    export all;
    import none;
}

# DNSTap-BGP
protocol bgp bgp_dnstap {
    table rkn;
    local as 65000;
    neighbor 192.168.149.2 as 65000;
    direct;
    passive on;
    rr client;
    import all;
    export none;
}

# Static routes list
protocol static static_rkn {
    table rkn;
    include "rkn_routes.list";
    import all;
    export none;
}

rkn_routes.list

route 3.226.79.85/32 via "ens3";
route 18.236.189.0/24 via "ens3";
route 3.224.21.0/24 via "ens3";
...

DNS

Демейки боюнча, Ubuntuдо Unbound бинардыкы AppArmor профили тарабынан кысылган, бул ага DNSTap розеткаларынын бардык түрлөрүнө туташууга тыюу салат. Бул профилди өчүрө аласыз же өчүрө аласыз:

# cd /etc/apparmor.d/disable && ln -s ../usr.sbin.unbound .
# apparmor_parser -R /etc/apparmor.d/usr.sbin.unbound

Бул, балким, оюн китебине кошулушу керек. Албетте, профилди оңдоп, керектүү укуктарды берүү идеалдуу, бирок мен өтө жалкоо болдум.

unbound.conf

server:
    chroot: ""
    port: 53
    interface: 0.0.0.0
    root-hints: "/var/lib/unbound/named.root"
    auto-trust-anchor-file: "/var/lib/unbound/root.key"
    access-control: 192.168.0.0/16 allow

remote-control:
    control-enable: yes
    control-use-cert: no

dnstap:
    dnstap-enable: yes
    dnstap-socket-path: "/tmp/dnstap.sock"
    dnstap-send-identity: no
    dnstap-send-version: no

    dnstap-log-client-response-messages: yes

Тизмелерди жүктөө жана иштетүү

IP даректердин тизмесин жүктөө жана иштетүү үчүн скрипт
Ал тизмени жүктөйт, префикске чейин жыйынтыктайт pfx. The кошпогула и корутунду чыгарба сиз IP жана тармактарды өткөрүп жиберүүнү же жыйынтыктабоону айта аласыз. мага керек болчу. менин VPSтин ички тармагы бөгөттөөлөр тизмесинде болгон 🙂

Кызык жери, RosKomSvoboda API демейки Python колдонуучу агенти менен өтүнүчтөрдү бөгөттөйт. Сценарий-бала аны түшүндү окшойт. Ошондуктан, биз аны Ognelis деп өзгөртөбүз.

Азырынча ал IPv4 менен гана иштейт. IPv6 үлүшү аз, бирок аны оңдоо оңой болот. Эгер сиз дагы bird6 колдонууга туура келбесе.

rkn.py

#!/usr/bin/python3

import json, urllib.request, ipaddress as ipa

url = 'https://api.reserve-rbl.ru/api/v2/ips/json'
pfx = '24'

dont_summarize = {
    # ipa.IPv4Network('1.1.1.0/24'),
}

dont_add = {
    # ipa.IPv4Address('1.1.1.1'),
}

req = urllib.request.Request(
    url,
    data=None, 
    headers={
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.47 Safari/537.36'
    }
)

f = urllib.request.urlopen(req)
ips = json.loads(f.read().decode('utf-8'))

prefix32 = ipa.IPv4Address('255.255.255.255')

r = {}
for i in ips:
    ip = ipa.ip_network(i)
    if not isinstance(ip, ipa.IPv4Network):
        continue

    addr = ip.network_address

    if addr in dont_add:
        continue

    m = ip.netmask
    if m != prefix32:
        r[m] = [addr, 1]
        continue

    sn = ipa.IPv4Network(str(addr) + '/' + pfx, strict=False)

    if sn in dont_summarize:
        tgt = addr
    else:
        tgt = sn

    if not sn in r:
        r[tgt] = [addr, 1]
    else:
        r[tgt][1] += 1

o = []
for n, v in r.items():
    if v[1] == 1:
        o.append(str(v[0]) + '/32')
    else:
        o.append(n)

for k in o:
    print(k)

Жаңыртуу үчүн скрипт
Мен аны күнүнө бир жолу таажыга чуркайм, балким, аны ар бир 4 саат сайын тартып тургандыр. бул, менин оюмча, RKN провайдерлерден талап кылган жаңылануу мезгили. Мындан тышкары, алар тезирээк келиши мүмкүн болгон башка супер-шашылыш бөгөттөөлөр бар.

Төмөнкүлөрдү аткарат:

  • Биринчи скрипти иштетет жана каттамдардын тизмесин жаңылайт (rkn_routes.list) BIRD үчүн
  • BIRD кайра жүктөө
  • dnstap-bgp үчүн домендердин тизмесин жаңыртат жана тазалайт
  • dnstap-bgp кайра жүктөө

rkn_update.sh

#!/bin/bash

ROUTES="/etc/bird/rkn_routes.list"
DOMAINS="/var/cache/rkn_domains.txt"

# Get & summarize routes
/opt/rkn.py | sed 's/(.*)/route 1 via "ens3";/' > $ROUTES.new

if [ $? -ne 0 ]; then
    rm -f $ROUTES.new
    echo "Unable to download RKN routes"
    exit 1
fi

if [ -e $ROUTES ]; then
    mv $ROUTES $ROUTES.old
fi

mv $ROUTES.new $ROUTES

/bin/systemctl try-reload-or-restart bird

# Get domains
curl -s https://api.reserve-rbl.ru/api/v2/domains/json -o - | jq -r '.[]' | sed 's/^*.//' | sort | uniq > $DOMAINS.new

if [ $? -ne 0 ]; then
    rm -f $DOMAINS.new
    echo "Unable to download RKN domains"
    exit 1
fi

if [ -e $DOMAINS ]; then
    mv $DOMAINS $DOMAINS.old
fi

mv $DOMAINS.new $DOMAINS

/bin/systemctl try-reload-or-restart dnstap-bgp

Алар көп ойлонбостон жазылган, ошондуктан, эгер сиз жакшыртса боло турган нерсени көрсөңүз - ага барыңыз.

Кардар орнотуу

Бул жерде мен Linux роутерлери үчүн мисалдарды берем, бирок Mikrotik / Cisco учурда бул дагы оңой болушу керек.

Биринчиден, биз BIRD орноттук:

bird.conf

router id 192.168.1.2;
table rkn;

protocol device {
    scan time 10;
};

# Servers
protocol bgp bgp_server1 {
    table rkn;
    local as 65000;
    neighbor 192.168.1.1 as 65000;
    direct;
    bfd on;
    next hop self;
    graceful restart;
    graceful restart time 60;
    rr client;
    export none;
    import all;
}

protocol kernel {
    table rkn;
    kernel table 222;
    scan time 10;
    export all;
    import none;
}

Ошентип, биз BGPден алынган маршруттарды 222 ядро ​​​​багыттоо таблицасы менен синхронизациялайбыз.

Андан кийин, демейки пластинканы кароодон мурун ядродон бул пластинаны карап чыгууну сурануу жетиштүү:

# ip rule add from all pref 256 lookup 222
# ip rule
0:  from all lookup local
256:    from all lookup 222
32766:  from all lookup main
32767:  from all lookup default

Баары, сервердин туннелинин IP дарегин DNS катары таратуу үчүн роутерде DHCPди конфигурациялоо калды жана схема даяр.

кемчиликтер

Домендердин тизмесин түзүү жана иштетүү үчүн учурдагы алгоритм менен, башка нерселер менен катар, youtube.com жана анын CDN'лери.

Жана бул бардык видеолор VPN аркылуу өтүшүнө алып келет, ал бүт каналды жабышы мүмкүн. Мүмкүн, азырынча RKNди блоктогон популярдуу домендердин тизмесин түзүү керектир, ичегилер ичке. Жана талдоодо аларды өткөрүп жибериңиз.

жыйынтыктоо

Сүрөттөлгөн ыкма провайдерлер ишке ашырган дээрлик бардык бөгөттөөлөрдү айланып өтүүгө мүмкүндүк берет.

Негизи, dnstap-bgp домендик аталыштын негизинде трафикти башкаруунун кандайдыр бир деңгээли талап кылынган башка максаттар үчүн колдонсо болот. Биздин убакта миңдеген сайттар бир эле IP дарекке илинип калышы мүмкүн экенин эстен чыгарбоо керек (мисалы, кээ бир Cloudflare артына), андыктан бул ыкманын тактыгы өтө төмөн.

Бирок кулпуларды айланып өтүү муктаждыктары үчүн бул жетиштүү.

Кошумчалар, түзөтүүлөр, тартуу сурамдары - кош келиңиздер!

Source: www.habr.com

Комментарий кошуу