Skip to content

ndb e1178 link rename #1180

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions pyroute2/iproute/linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@
NeighbourFieldFilter,
NeighbourIPRouteFilter,
)
from pyroute2.requests.probe import ProbeFieldFilter
from pyroute2.requests.route import RouteFieldFilter, RouteIPRouteFilter
from pyroute2.requests.rule import RuleFieldFilter, RuleIPRouteFilter

Expand Down Expand Up @@ -425,15 +426,22 @@ def probe(self, command, **kwarg):
'''
msg = probe_msg()
#
request = (
RequestProcessor(context=kwarg, prime=kwarg)
.apply_filter(ProbeFieldFilter())
.finalize()
)
#
msg['family'] = AF_INET
msg['proto'] = 1
msg['port'] = 0
msg['port'] = kwarg.get('port', 0)
msg['dst_len'] = 32
#
msg['attrs'].append(['PROBE_KIND', kwarg.get('kind', 'ping')])
msg['attrs'].append(['PROBE_DST', kwarg.get('dst', '0.0.0.0')])
msg['attrs'].append(['PROBE_NUM', kwarg.get('num', 1)])
msg['attrs'].append(['PROBE_TIMEOUT', kwarg.get('timeout', 1)])
for key, value in request.items():
nla = probe_msg.name2nla(key)
if msg.valid_nla(nla) and value is not None:
msg['attrs'].append([nla, value])

# iterate the results immediately, don't defer the probe run
return tuple(self.nlm_request(msg, msg_type=RTM_NEWPROBE, msg_flags=1))

Expand Down
45 changes: 39 additions & 6 deletions pyroute2/ndb/objects/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,7 @@ class Interface(RTNL_Object):
'alt_ifname_list': lambda x: list(json.loads(x or '[]'))
}
field_filter = LinkFieldFilter
old_ifname = None

@classmethod
def _count(cls, view):
Expand Down Expand Up @@ -847,17 +848,25 @@ def add_altname(self, ifname):
new_list = set(self['alt_ifname_list'])
new_list.add(ifname)
self['alt_ifname_list'] = list(new_list)
return self

@check_auth('obj:modify')
def del_altname(self, ifname):
new_list = set(self['alt_ifname_list'])
new_list.remove(ifname)
self['alt_ifname_list'] = list(new_list)
return self

@check_auth('obj:modify')
def __setitem__(self, key, value):
if key == 'peer':
dict.__setitem__(self, key, value)
elif key == 'ifname':
if value in self['alt_ifname_list']:
self.del_altname(value)
if key in self and self.old_ifname is None:
self.old_ifname = self[key]
super(Interface, self).__setitem__(key, value)
elif key == 'target' and self.state == 'invalid':
dict.__setitem__(self, key, value)
elif key == 'net_ns_fd' and self.state == 'invalid':
Expand Down Expand Up @@ -977,9 +986,19 @@ def make_req(self, prime):
return req

@check_auth('obj:modify')
def apply_altnames(self, alt_ifname_setup):
alt_ifname_remove = set(self['alt_ifname_list']) - alt_ifname_setup
alt_ifname_add = alt_ifname_setup - set(self['alt_ifname_list'])
def apply_altnames(
self, alt_ifname_setup, alt_ifname_current, old_ifname=None
):
if 'alt_ifname_list' in self.changed:
self.changed.remove('alt_ifname_list')
if alt_ifname_current is None:
# load the current state
self.load_from_system()
self.load_sql(set_state=False)
alt_ifname_current = set(self['alt_ifname_list'])

alt_ifname_remove = alt_ifname_current - alt_ifname_setup
alt_ifname_add = alt_ifname_setup - alt_ifname_current
for ifname in alt_ifname_remove:
self.sources[self['target']].api(
'link', 'property_del', index=self['index'], altname=ifname
Expand All @@ -988,8 +1007,11 @@ def apply_altnames(self, alt_ifname_setup):
self.sources[self['target']].api(
'link', 'property_add', index=self['index'], altname=ifname
)
# reload alt ifnames from the system to check the state
self.load_from_system()
self.load_sql(set_state=False)
if old_ifname is not None and old_ifname in self['alt_ifname_list']:
alt_ifname_setup.add(old_ifname)
if set(self['alt_ifname_list']) != alt_ifname_setup:
raise Exception('could not setup alt ifnames')

Expand All @@ -1002,9 +1024,14 @@ def apply(self, rollback=False, req_filter=None, mode='apply'):
setns = self.state.get() == 'setns'
remove = self.state.get() == 'remove'
alt_ifname_setup = set(self['alt_ifname_list'])
if 'alt_ifname_list' in self.changed:
self.changed.remove('alt_ifname_list')
old_ifname = self.old_ifname if 'ifname' in self.changed else None
try:
if 'index' in self and (
self.old_ifname or 'alt_ifname_list' in self.changed
):
self.apply_altnames(alt_ifname_setup, None)
if 'alt_ifname_list' in self.changed:
self.changed.remove('alt_ifname_list')
super(Interface, self).apply(rollback, req_filter, mode)
if setns:
self.load_value('target', self['net_ns_fd'])
Expand All @@ -1013,7 +1040,9 @@ def apply(self, rollback=False, req_filter=None, mode='apply'):
if spec:
self.state.set('system')
if not remove:
self.apply_altnames(alt_ifname_setup)
self.apply_altnames(
alt_ifname_setup, set(self['alt_ifname_list']), old_ifname
)

except NetlinkError as e:
if (
Expand Down Expand Up @@ -1059,6 +1088,8 @@ def req_filter(req):
self.apply(rollback, req_filter, mode)
else:
raise
finally:
self.old_ifname = None
if ('net_ns_fd' in self.get('peer', {})) and (
self['peer']['net_ns_fd'] in self.view.ndb.sources
):
Expand Down Expand Up @@ -1101,13 +1132,15 @@ def hook_apply(self, method, **spec):
self.ndb._event_queue.put(update)

def load_from_system(self):
self.load_event.clear()
(
self.ndb._event_queue.put(
self.sources[self['target']].api(
self.api, 'get', index=self['index']
)
)
)
self.load_event.wait(1)

def load_sql(self, *argv, **kwarg):
spec = super(Interface, self).load_sql(*argv, **kwarg)
Expand Down
125 changes: 93 additions & 32 deletions pyroute2/netlink/rtnl/probe_msg.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import errno
import json
import shutil
import socket
import ssl
import subprocess

from pyroute2.netlink import nlmsg
Expand All @@ -24,51 +27,109 @@ class probe_msg(nlmsg):
('PROBE_KIND', 'asciiz'),
('PROBE_STDOUT', 'asciiz'),
('PROBE_STDERR', 'asciiz'),
('PROBE_SRC', 'target'),
('PROBE_DST', 'target'),
('PROBE_SRC', 'asciiz'),
('PROBE_DST', 'asciiz'),
('PROBE_NUM', 'uint8'),
('PROBE_TIMEOUT', 'uint8'),
('PROBE_HOSTNAME', 'asciiz'),
('PROBE_SSL_VERIFY', 'uint8'),
('PROBE_SSL_VERSION', 'asciiz'),
('PROBE_SSL_CERT_JSON', 'asciiz'),
('PROBE_SSL_CERT_DER', 'cdata'),
)


def proxy_newprobe(msg, nl):
def probe_ping(msg, nl):
num = msg.get('num')
timeout = msg.get('timeout')
dst = msg.get('dst')
kind = msg.get('kind')
args = [shutil.which(kind), '-c', f'{num}', '-W', f'{timeout}', f'{dst}']
if args[0] is None:
raise NetlinkError(errno.ENOENT, 'probe not found')

if kind.endswith('ping'):
args = [
shutil.which(kind),
'-c',
f'{num}',
'-W',
f'{timeout}',
f'{dst}',
]
if args[0] is None:
raise NetlinkError(errno.ENOENT, 'probe not found')

process = subprocess.Popen(
args, stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
process = subprocess.Popen(
args, stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
try:
out, err = process.communicate(timeout=timeout)
if out:
msg['attrs'].append(['PROBE_STDOUT', out])
if err:
msg['attrs'].append(['PROBE_STDERR', err])
except subprocess.TimeoutExpired:
process.terminate()
raise NetlinkError(errno.ETIMEDOUT, 'timeout expired')
finally:
process.stdout.close()
process.stderr.close()
return_code = process.wait()
if return_code != 0:
raise NetlinkError(errno.EHOSTUNREACH, 'probe failed')


def probe_tcp(msg, nl, close=True):
timeout = msg.get('timeout')
dst = msg.get('dst')
port = msg.get('port')
connection = None
try:
connection = socket.create_connection((dst, port), timeout=timeout)
except ConnectionRefusedError:
raise NetlinkError(errno.ECONNREFUSED, 'connection refused')
except TimeoutError:
raise NetlinkError(errno.ETIMEDOUT, 'timeout expired')
except Exception:
raise NetlinkError(errno.ECOMM, 'probe failed')
finally:
if close and connection is not None:
connection.close()
return connection


def probe_ssl(msg, nl):
hostname = msg.get('hostname') or msg.get('dst')
context = ssl.create_default_context()
context.verify_mode = msg.get('ssl_verify', ssl.CERT_REQUIRED)
with probe_tcp(msg, nl, close=False) as connection:
try:
out, err = process.communicate(timeout=timeout)
if out:
msg['attrs'].append(['PROBE_STDOUT', out])
if err:
msg['attrs'].append(['PROBE_STDERR', err])
except subprocess.TimeoutExpired:
process.terminate()
raise NetlinkError(errno.ETIMEDOUT, 'timeout expired')
finally:
process.stdout.close()
process.stderr.close()
return_code = process.wait()
if return_code != 0:
raise NetlinkError(errno.EHOSTUNREACH, 'probe failed')
with context.wrap_socket(
connection, server_hostname=hostname
) as ssl_wrap:
version = ssl_wrap.version()
peer_cert_json = ssl_wrap.getpeercert(binary_form=False)
peer_cert_der = ssl_wrap.getpeercert(binary_form=True)
if peer_cert_json is not None:
msg['attrs'].append(
['PROBE_SSL_CERT_JSON', json.dumps(peer_cert_json)]
)
if peer_cert_der is not None:
msg['attrs'].append(['PROBE_SSL_CERT_DER', peer_cert_der])
if version is not None:
msg['attrs'].append(['PROBE_SSL_VERSION', version])
except ssl.SSLError as e:
code = errno.EPROTO
if e.reason == 'UNSUPPORTED_PROTOCOL':
code = errno.EPROTONOSUPPORT
elif e.reason == 'CERTIFICATE_VERIFY_FAILED':
code = errno.EACCES
raise NetlinkError(code, e.strerror)
except Exception:
raise NetlinkError(errno.ECOMM, 'probe failed')


def proxy_newprobe(msg, nl):
kind = msg.get('kind')

if kind.endswith('ping'):
probe_ping(msg, nl)
elif kind == 'tcp':
probe_tcp(msg, nl)
elif kind == 'ssl':
probe_ssl(msg, nl)
else:
raise NetlinkError(errno.ENOTSUP, 'probe type not supported')

msg.reset()
msg.encode()
return {'verdict': 'return', 'data': msg.data}
13 changes: 13 additions & 0 deletions pyroute2/requests/probe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from .common import NLAKeyTransform


class ProbeFieldFilter(NLAKeyTransform):
_nla_prefix = 'PROBE_'

def finalize(self, context):
if 'kind' not in context:
context['kind'] = 'ping'
if 'num' not in context:
context['num'] = 1
if 'timeout' not in context:
context['timeout'] = 1