Маркирането на трафик от един cgroup е лесната част. Три отделни решения трябва да се подредят, преди едно приложение да излезе през физическия интерфейс, докато всичко останало остава в тунела: кой socket принадлежи на изключението, кое търсене в маршрутизацията задейства маркерът и кой адрес на източника носи пакетът, когато излиза. Повечето неуспешни опити разбират първото, наполовина разбират второто и никога не поглеждат третото.

Три решения, не едно

Класификацията отговаря кой е освободен. Маршрутизацията отговаря накъде вместо това отива маркираният пакет. Изборът на източник отговаря какво ще види далечната страна и накъде трябва да се върне отговорът.

Те се провалят независимо. Пакет може да бъде класифициран, пренасочен към физическия интерфейс и все още да носи частен адрес, който никой upstream рутер не може да върне, което създава таймаут, изглеждащ точно като блокировка от firewall. Нищо в стъпката на класификация не ви предупреждава за това. Нищо и в стъпката на маршрутизация.

Редът също е важен. Класификацията се случва при генерирането на пакета, маршрутизацията — след това, а транслацията — в точката на изход.

Ако не можете да назовете кой от трите етапа е счупен, вие не дебъгвате, а пренареждате правила и се надявате.

Съпоставете cgroup, който ще съществува и утре

nftables може да провери членството на socket в cgroup v2, което е наистина полезно, защото следва процеса, а не дестинацията. Уловката е идентификаторът. Приложение за работен плот, стартирано от меню, често живее във временен scope, чието име се променя при следващото стартиране.

nft list ruleset
systemctl status session.slice 2>/dev/null
cat /proc/self/cgroup

Копирането на име на scope от една сесия и твърдото му закодиране създава правило, което работи днес и тихо спира да съвпада утре. Вместо това поставете приложението в умишлено стабилен контекст на изпълнение, например отделен scope или unit, който контролирате, и съпоставяйте с него. Тогава правилото оцелява след рестартирания и обновявания на пакети. То също става нещо, за което колега може да разсъждава без вашата история на сесиите.

Бъдете също точни кой механизъм използвате. Класификацията net_cls в cgroup v1 и съпоставянето на socket в cgroup v2 са различни неща и поле с подобно име в документацията не ги прави взаимозаменяеми.

Верига от тип route е това, което задейства второто търсене

За локално генериран трафик типът на веригата решава дали нещо се случва след поставянето на маркера. Верига output от тип filter може да маркира пакет и ядрото продължава да използва маршрута, който вече е избрало. Верига от тип route е механизмът, който иска ново търсене.

table inet bypass {
  chain mark {
    type route hook output priority mangle; policy accept;
    socket cgroupv2 level 2 "app.slice" meta mark set 0x1 comment "app bypass"
  }
}
ip rule add fwmark 0x1 lookup 200
ip route add default via 203.0.113.1 dev wlan0 table 200

След това маркерът се нуждае от правило за политика, което го изпраща към таблица, и тази таблица трябва да е пълна: маршрут по подразбиране, всякакви локални дестинации, които приложението все още изисква, и upstream шлюз, който действително отговаря.

Избирайте стойността на маркера съзнателно. WireGuard използва свой собствен fwmark за външния транспортен трафик и случайното попадане на същия номер може да попречи на собственото route изключение на тунела по начин, който е болезнен за диагностициране.

Адресът на източника не следва маршрута

Socket, който вече е избрал адрес на източника за пътя през тунела, запазва този адрес след пренасочването. Нищо в промяната на маршрута не го пренаписва и затова пакетът напуска физическия интерфейс с частен източник, който upstream мрежата не може да върне.

Видимият симптом е приложение, което изглежда изпраща данни и никога не получава отговор, и лог на firewall, който не показва нищо, защото пакетът така и не се е върнал.

Тясната поправка е правило за транслация на източника, ограничено до маркера на това приложение и неговия реален изходен интерфейс, интегрирано със съществуващия NAT, вместо да го заменя:

table inet bypass {
  chain postrouting {
    type nat hook postrouting priority srcnat; policy accept;
    meta mark 0x1 oifname "wlan0" masquerade
  }
}

Транслирайте изключението и нищо друго. Общо правило masquerade поправя симптома и унищожава способността ви да кажете кой трафик по кой път е минал. Съпоставяйте по маркера, реалния изходен интерфейс и, ако можете, по собствения адрес на приложението. Широките правила се пишат лесно и са почти невъзможни за одит по-късно.

Какво все още се изплъзва от изключението

Разрешаването на имена може да идва от отделен resolver процес в друг cgroup, така че класифицирането на socket на браузъра не класифицира неговите заявки. Ако заявките на resolver останат в тунела, докато връзката излиза извън него, отговорите split-horizon могат да не съвпаднат с пътя. Това несъответствие създава откази, които изглеждат като DNS бъгове и не са.

Дъщерните процеси наследяват cgroup в повечето конфигурации, но помощник, който се премества в свой собствен scope, не го прави. Проверявайте, вместо да предполагате, особено за всичко, което се изпълнява отново.

Накрая решете какво трябва да се случи, когато тунелът е спрян. Изключение, което тихо се превръща в пълен обход, е различна позиция за сигурност от това, което се проваля затворено, и само едно от двете е това, което сте имали предвид.

Подобни статии