Большое спасибо @A.B, который восполнил некоторые недостающие части для меня, особенно в отношении семантики netnsid
s. Его PoC очень поучителен. Однако важным недостающим элементом в его PoC является то, как соотнести локальный netnsid
с его глобально уникальным номером инода сетевого пространства имен, потому что только тогда мы можем однозначно соединить правильные соответствующие пары veth
.
Подводя итоги и приведя небольшой пример Python, как собирать информацию программно, не полагаясь на ip netns
и необходимость монтирования :RTNETLINK фактически возвращает netnsid при запросе сетевых интерфейсов. Это атрибут IFLA_LINK_NETNSID
, который появляется в информации о ссылке только при необходимости. Если его там нет, то он не нужен --и мы должны предположить, что индекс равноправного узла относится к интерфейсу локальной сети пространства имен -.
Важным уроком, который следует усвоить, является то, что netnsid
/ IFLA_LINK_NETSID
— это только локально определенное в пространстве имен сети, где вы получили его, когда запрашивали у RTNETLINK информацию о ссылке. netnsid
с тем же значением, полученным в другом сетевом пространстве имен, может идентифицировать другое одноранговое пространство имен, поэтому будьте осторожны и не используйте netnsid
за пределами своего пространства имен. Но какое уникально идентифицируемое сетевое пространство имен(inode
с номером )соответствует какому netnsid
?
Как оказалось, самая последняя версияlsns
по состоянию на март 2018 года вполне способна отображать правильный netnsid
рядом с номером инода сетевого пространства имен! Таким образом, — это способ сопоставления локальных netnsid
с инодами пространства имен, но на самом деле это наоборот! И это скорее оракул (со строчной буквой ell ), чем поиск :RTM _GETNSID требуется идентификатор пространства имен сети либо в виде PID, либо FD (в пространство имен сети ), а затем возвращается netnsid
. См.https://stackoverflow.com/questions/50196902/retrieving-the-netnsid-of-a-network-namespace-in-pythonдля примера того, как запросить оракул сетевого пространства имен Linux.
Следовательно, вам необходимо перечислить доступные сетевые пространства имен (через /proc
и/или /var/run/netns
),затем для данного veth
сетевого интерфейса, присоединенного к сетевому пространству имен, где вы его нашли, запросите netnsid
все сетевые пространства имен, которые вы перечислили в начале (, потому что вы никогда заранее не знаете, какое есть какое ), и, наконец, сопоставьте netnsid
однорангового узла veth
с номером inode пространства имен в соответствии с локальной картой, которую вы создали на шаге 3 после присоединения к пространству имен veth
.
import psutil
import os
import pyroute2
from pyroute2.netlink import rtnl, NLM_F_REQUEST
from pyroute2.netlink.rtnl import nsidmsg
from nsenter import Namespace
# phase I: gather network namespaces from /proc/[0-9]*/ns/net
netns = dict()
for proc in psutil.process_iter():
netnsref= '/proc/{}/ns/net'.format(proc.pid)
netnsid = os.stat(netnsref).st_ino
if netnsid not in netns:
netns[netnsid] = netnsref
# phase II: ask kernel "oracle" about the local IDs for the
# network namespaces we've discovered in phase I, doing this
# from all discovered network namespaces
for id, ref in netns.items():
with Namespace(ref, 'net'):
print('inside net:[{}]...'.format(id))
ipr = pyroute2.IPRoute()
for netnsid, netnsref in netns.items():
with open(netnsref, 'r') as netnsf:
req = nsidmsg.nsidmsg()
req['attrs'] = [('NETNSA_FD', netnsf.fileno())]
resp = ipr.nlm_request(req, rtnl.RTM_GETNSID, NLM_F_REQUEST)
local_nsid = dict(resp[0]['attrs'])['NETNSA_NSID']
if local_nsid != 2**32-1:
print(' net:[{}] <--> nsid {}'.format(netnsid, local_nsid))
Я думаю, это то, что вы ищете
- name: Find timestamp
find:
paths: /tmp
patterns: timestamp
register: my_timestamp
- name: Timestamp not found. End of host.
block:
- debug:
msg: /tmp/timestamp not found. End of host.
- meta: end_host
when: my_timestamp.matched == 0
- name: Find all zone files
find:
paths: "{{ zones_dir }}"
register: my_zones
- name: Find zone files newer than timestamp
set_fact:
my_zones_newer: "{{ my_zones.files|json_query(query)
map('basename')|
list }}"
vars:
query: "[?mtime > to_number('{{ my_timestamp.files.0.mtime }}')].path"
- block:
- name: "Reload {{ my_zones_newer|join(',') }}"
command: /usr/bin/pdns_control reload
- name: Touch timestamp
file:
state: touch
path: /tmp/timestamp
when: my_zones_newer|length > 0
Перезагрузка не будет запущена, если файл зоны будет записан во время этой критической секции. чтобы решить эту проблему, установите mtime
файла /tmp/timestamp
на значение самого нового найденного файла зоны, чтобы избежать потенциальной опасности. Изменить блок
- block:
- name: "Reload {{ my_zones_newer|join(',') }}"
command: /usr/bin/pdns_control reload
- set_fact:
my_mtime_max: "{{ my_zones.files|json_query('[].mtime')|max }}"
- file:
state: touch
modification_time: "{{ '%Y%m%d%H%M.%S'|
strftime(my_mtime_max|
float|
round(precision=0, method='ceil')|
int) }}"
path: /tmp/timestamp
when: my_zones_newer|length > 0
Кажется, что степень детализации модификации _формата времени _является второй. См. time.strftime . В результате переменная my_mtime_max
должна быть округлена в большую сторону method='ceil'
, что оставляет опасность в этой округленной доле секунды.
Надежное решение состоит в том, чтобы сохранить mtime
в файле timestamp
. Например,
- name: Read modification time from /tmp/timestamp (default=0)
set_fact:
my_mtime_max: "{{ lookup('pipe', my_command) }}"
vars:
my_command: sh -c '[ -e /tmp/timestamp ] && cat /tmp/timestamp || echo 0'
- name: Find all zone files
find:
paths: "{{ zones_dir }}"
register: my_zones
- name: Find zone files newer than timestamp
set_fact:
my_zones_newer: "{{ my_zones.files|json_query(query)|
map('basename')|
list }}"
vars:
query: "[?mtime > to_number('{{ my_mtime_max }}')].path"
- block:
- name: "Reload {{ my_zones_newer|join(',') }}"
command: /usr/bin/pdns_control reload
- name: Set mtime of newest zone file to my_mtime_max
set_fact:
my_mtime_max: "{{ my_zones.files|json_query('[].mtime')|max }}"
- name: Store my_mtime_max in /tmp/timestamp
template:
src: timestamp.j2
dest: /tmp/timestamp
when: my_zones_newer|length > 0
shell> cat timestamp.j2
{{ my_mtime_max }}