Skip to content

Commit

Permalink
Introducing data library
Browse files Browse the repository at this point in the history
  • Loading branch information
signal-09 committed Sep 11, 2024
1 parent 384782c commit 3fbc739
Show file tree
Hide file tree
Showing 9 changed files with 482 additions and 580 deletions.
74 changes: 42 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,45 @@ This is a macOS network wrapper to imitate GNU/Linux [iproute2](https://wiki.lin

Command `ip`:

| objects | implemented | supported | note |
| :---------: | :---------: | :-------: | ---- |
| address | :white_check_mark: | :white_check_mark: |
| addrlabel | :x: | :grey_question: | IPv6 protocol address label |
| maddress | :x: | :grey_question: |
| route | :white_check_mark: | :white_check_mark: |
| rule | :x: | :grey_question: | (e.g. [source based routing with FreeBSD...](https://mmacleod.ca/2011/06/source-based-routing-with-freebsd-using-multiple-routing-table/))
| neighbor | :white_check_mark: | :white_check_mark: | using [ARP](https://en.wikipedia.org/wiki/Address_Resolution_Protocol) for IPv4 and [NDP](https://en.wikipedia.org/wiki/Neighbor_Discovery_Protocol) for IPv6
| ntable | :x: | :grey_question: |
| ntbl | :x: | :grey_question: |
| link | :white_check_mark: | :white_check_mark: |
| l2tp | :x: | :grey_question: |
| fou | :x: | :grey_question: | IP-IP tunnel over UDP?
| ila | :x: | :grey_question: | IPv6 Identifier Locator Addressing
| macsec | :x: | :x: |
| tunnel | :x: | :white_check_mark: | [IP-IP](https://kovyrin.net/2006/03/17/how-to-create-ip-ip-tunnel-between-freebsd-and-linux/) only
| tuntap | :x: | :grey_question: | [Tunnelblick](https://github.com/Tunnelblick/Tunnelblick/tree/master/third_party) third party [tuntaposx](https://tuntaposx.sourceforge.net)?
| token | :x: | :grey_question: | May be related to [non-numeric IPv6 mask](https://forums.freebsd.org/threads/how-to-apply-non-numeric-mask-to-ipv6-address.69829/)?
| tcpmetrics | :x: | :grey_question: |
| monitor | :x: | :x: |
| xfrm | :x: | :x: |
| mroute | :x: | :grey_question: | See [Max OS: no multicast route for 127.0.0.1](https://issues.redhat.com/browse/JGRP-1808)
| mrule | :x: | :grey_question: |
| netns | :x: | :x: |
| netconf | :x: | :white_check_mark: |
| vrf | :x: | :grey_question: | [Virtual Routing and Forwarding](https://en.wikipedia.org/wiki/Virtual_routing_and_forwarding)
| sr | :x: | :grey_question: | IPv6 Segment Routing management
| nexthop | :x: | :grey_question: |
| mptcp | :x: | :x: | Multipath TCP
| ioam | :x: | :grey_question: | IPv6 In-situ OAM (IOAM)
| help | :white_check_mark: | :white_check_mark: |
| stats | :x: | :grey_question: |
| objects | implemented | supported | note |
| :-----: | :---------: | :-------: | ---- |
| address | :white_check_mark: | :white_check_mark: |
| addrlabel | :x: | :grey_question: | IPv6 protocol address label
| maddress | :x: | :grey_question: |
| route | :white_check_mark: | :white_check_mark: |
| rule | :x: | :grey_question: | (e.g. [source based routing with FreeBSD...](https://mmacleod.ca/2011/06/source-based-routing-with-freebsd-using-multiple-routing-table/))
| neighbor | :white_check_mark: | :white_check_mark: | using [ARP](https://en.wikipedia.org/wiki/Address_Resolution_Protocol) for IPv4 and [NDP](https://en.wikipedia.org/wiki/Neighbor_Discovery_Protocol) for IPv6
| ntable | :x: | :grey_question: |
| ntbl | :x: | :grey_question: |
| link | :white_check_mark: | :white_check_mark: |
| l2tp | :x: | :grey_question: |
| fou | :x: | :grey_question: | IP-IP tunnel over UDP?
| ila | :x: | :grey_question: | IPv6 Identifier Locator Addressing
| macsec | :x: | :x: |
| tunnel | :x: | :white_check_mark: | [IP-IP](https://kovyrin.net/2006/03/17/how-to-create-ip-ip-tunnel-between-freebsd-and-linux/) only
| tuntap | :x: | :grey_question: | [Tunnelblick](https://github.com/Tunnelblick/Tunnelblick/tree/master/third_party) third party [tuntaposx](https://tuntaposx.sourceforge.net)?
| token | :x: | :grey_question: | May be related to [non-numeric IPv6 mask](https://forums.freebsd.org/threads/how-to-apply-non-numeric-mask-to-ipv6-address.69829/)?
| tcpmetrics | :x: | :grey_question: |
| monitor | :x: | :x: |
| xfrm | :x: | :x: |
| mroute | :x: | :grey_question: | See [Max OS: no multicast route for 127.0.0.1](https://issues.redhat.com/browse/JGRP-1808)
| mrule | :x: | :grey_question: |
| netns | :x: | :x: |
| netconf | :x: | :white_check_mark: |
| vrf | :x: | :grey_question: | [Virtual Routing and Forwarding](https://en.wikipedia.org/wiki/Virtual_routing_and_forwarding)
| sr | :x: | :grey_question: | IPv6 Segment Routing management
| nexthop | :x: | :grey_question: |
| mptcp | :x: | :x: | Multipath TCP
| ioam | :x: | :grey_question: | IPv6 In-situ OAM (IOAM)
| help | :white_check_mark: | :white_check_mark: |
| stats | :x: | :grey_question: |

Command `bridge`:

| objects | implemented | supported | note |
| :-----: | :---------: | :-------: | ---- |
| link | :white_check_mark: | :white_check_mark: | `show` only
| fdb | :white_check_mark: | :white_check_mark: | `show` only

Examples:

Expand All @@ -54,6 +61,9 @@ Examples:
* `ip neigh [ list | show ]`
* `ip neigh flush`

* `bridge link [ list | show ]`
* `bridge fdb `

## Installation

### Homebrew
Expand Down
256 changes: 256 additions & 0 deletions iproute4mac/data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
def isnumber(value):
return isinstance(value, int | float | complex)


def empty(value):
return value is None or (not isnumber(value) and not value)


def _get_item(data, key):
value = None
if key in data:
return True, data[key]
else:
for key, value in data.items():
if isinstance(value, dict):
found, res = _get_item(value, key)
if found:
return True, res
return False, None


def get_item(data, key, default=None):
"""
Recurse search for `key` in `data` dict
Input:
`data` dictionary to search in
`key` key to find
"""
found, res = _get_item(data, key)
return res if found else default


def find_item(data, key, value=None, recurse=True, strict=False):
"""
Search for `key` in `data` dict
Input:
`data` dictionary to search in
`key` key to find
`value` (optional) specific value to find
`strict` (optional) return bool(value) (e.g. True/False instead of "", [], {})
"""
if key in data:
if value is not None:
return data[key] == value
if strict:
return isnumber(data[key]) or bool(data[key])
return data[key] is not None
if recurse:
for k, v in data.items():
if isinstance(v, dict):
if find_item(v, key, value=value, recurse=recurse, strict=strict):
return True
return False


def dict_format(data, string, *fields, default=None):
if not data or not fields or empty(data.get(fields[0], default)):
return ""
return string.format(*[data.get(field, default) for field in fields])


def delete_keys(data, *args):
for entry in data:
for arg in args:
entry.pop(arg, None)


def list_filter(data):
"""
Recursively remove empty values
"""

res = []
for value in data:
if isinstance(value, dict):
value = dict_filter(value)
elif isinstance(value, list):
value = list_filter(value)
if not empty(value):
res.append(value)
return res


def dict_filter(data):
"""
Recursively remove empty values
"""

res = {}
for key, value in data.items():
if isinstance(value, dict):
value = dict_filter(value)
elif isinstance(value, list):
value = list_filter(value)
if not empty(value):
res[key] = value
return res


class _Item:
"""
Dictionary of data
"""

__slots__ = ("_data",)
_OPTIONAL_FIELDS = {
# key: value
}

def __init__(self):
raise NotImplementedError

def __str__(self):
return self.str()

def __contains__(self, item):
return item in self._data

def __getitem__(self, key):
return self._data.get(key)

def __setitem__(self, key, value):
self._data[key] = value

def __delitem__(self, key):
if key in self._data:
del self._data[key]

def get(self, key, default=None, recurse=False):
return get_item(self._data, key, default) if recurse else self._data.get(key, default)

def pop(self, key, default=None):
if default is None:
return self._data.pop(key)
return self._data.pop(key, default)

def present(self, key, value=None, recurse=False, strict=False):
return find_item(self._data, key, value=value, recurse=recurse, strict=strict)

def data(self, details=True):
"""
Raw data, without empty values and, eventually, optional fields
"""
res = {}
for key, value in self._data.items():
if value is None:
continue
if not details and key in self._OPTIONAL_FIELDS:
skip = self._OPTIONAL_FIELDS[key]
if skip is None or skip == value:
continue
res[key] = value
return res

def _cast(self, value):
if value.isdigit():
return int(value)
if value.replace(".", "", 1).isdigit():
return float(value)
if value == "<none>":
return None
if value.lower() in ["true", "yes", "enabled"]:
return True
if value.lower() in ["false", "no", "disabled"]:
return False
return value

def _value(self, key, value):
if isinstance(value, dict):
return self._dict(value)
if isinstance(value, list):
return self._list(value)
if isinstance(value, str):
return self._cast(value)
return value

def _list(self, data):
res = []
for value in data:
if isinstance(value, dict):
value = self._dict(value)
elif isinstance(value, list):
value = self._list(value)
elif value is not None:
value = self._cast(value)
res.append(value)
return res

def _dict(self, data):
res = {}
for key, value in data.items():
value = self._value(key, value)
if not empty(value):
res[key] = value
return res

def dict(self, details=True):
return self._dict(self.data(details=details))

def str(self, details=True):
raise NotImplementedError


class _Items:
"""
List of _Item
"""

__slots__ = ("_data",)

def __init__(self):
raise NotImplementedError

def __iter__(self):
for item in self._data:
yield item

def __str__(self):
return "\n".join(map(str, self._data))

def __len__(self):
return len(self._data)

def __getitem__(self, index):
return self._data[index]

def pop(self, index=-1):
return self._data.pop(index)

def append(self, item):
if not isinstance(item, _Item):
raise ValueError("item is not of {_Item}")
if hasattr(self, "_data"):
self._data.append(item)
else:
self._data = [item]

def set(self, data):
# all(instance(...)) also accept empty list ([]) as valid
if not isinstance(data, list) or not all(isinstance(item, _Item) for item in data):
raise ValueError("data is not list() of {_Item}")
self._data = data

def dict(self, details=None):
return [item.dict(details=details) for item in self._data]

# alias self.list() as self.dict()
list = dict

def str(self, details=None):
return "\n".join([item.str(details=details) for item in self._data])

def lookup(self, key, value):
return next((item for item in self._data if item.present(key, value)), None)
17 changes: 15 additions & 2 deletions iproute4mac/debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,27 @@ def usage():


def debug_address(argv=[]):
utils.stdout('Testing "ifconfig":... ')

old_options = utils.options_override({"show_details": True, "json": True, "pretty": True})
interfaces = ifconfig.Ifconfig()
utils.options_restore(old_options)
ip_ifconfig = str(interfaces)
os_ifconfig = ifconfig.run()

if diff := list(unified_diff(ip_ifconfig.splitlines(), os_ifconfig.splitlines(), n=10000)):
print("\n".join(list(diff)))
if diff := list(
unified_diff(
ip_ifconfig.splitlines(),
os_ifconfig.splitlines(),
fromfile="iproute4mac",
tofile="ifconfig",
n=50,
)
):
utils.stdout(end="\n")
utils.stdout("\n".join(list(diff)), end="\n")
else:
utils.stdout("OK", end="\n")


def debug_neigh(argv=[]):
Expand Down
Loading

0 comments on commit 3fbc739

Please sign in to comment.