Skip to content
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
5 changes: 2 additions & 3 deletions packages/ns-dpi/files/dpi-nft
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ chain dpi_actions {
type filter hook prerouting priority filter + 10; policy accept;

{% if log_enabled -%}
ct label netify-blocked counter log prefix "DPI block: " limit rate {{ log_limit }}
ct label netify-blocked counter log prefix "DPI block: " limit rate 1/second
{% endif -%}
ct label netify-blocked counter reject
ct label bulk counter ip dscp set cs1 return
Expand All @@ -37,8 +37,7 @@ def generate_dpi():
e_uci = EUci()
template = Environment(loader=BaseLoader()).from_string(CHAIN)
render = template.render(
log_enabled=e_uci.get('dpi', 'config', 'log_blocked', dtype=bool, default=False),
log_limit=e_uci.get('firewall', 'ns_defaults', 'rule_log_limit', dtype=str, default='1/second')
log_enabled=e_uci.get('dpi', 'config', 'log_blocked', dtype=bool, default=False)
)
# save to nftables directory table-pre, only if the file is changed
file_path = '/usr/share/nftables.d/table-pre/dpi_actions.nft'
Expand Down
89 changes: 21 additions & 68 deletions packages/ns-phonehome/files/phonehome
Original file line number Diff line number Diff line change
@@ -1,61 +1,16 @@
#!/usr/bin/python3

#
# Copyright (C) 2022 Nethesis S.r.l.
# Copyright (C) 2026 Nethesis S.r.l.
# SPDX-License-Identifier: GPL-2.0-only
#

import os
import csv

import json
import uuid
import subprocess
from euci import EUci
from nethsec import inventory

def _run(cmd):
try:
proc = subprocess.run(cmd, shell=True, check=True, capture_output=True, text=True)
return proc.stdout.rstrip().lstrip()
except:
return ''

def _get_cpu_field(field, cpu_info):
for f in cpu_info:
if f['field'].startswith(field):
return f['data']

return ''

cpu_info = json.loads(_run('lscpu -J'))['lscpu']

# map kernel driver to device id
drivers = {}
for line in _run("find /sys | grep '.*/drivers/.*/0000:.*$' | cut -d'/' -f6,7").split('\n'):
try:
(driver,bus) = line.split("/0000:")
drivers[bus] = driver
except:
continue

# lspci -n: 00:1b.0 0403: 8086:293e (rev 03)
# fields: bus class vendor:device revision
pci = {}
if os.path.isdir('/proc/bus/pci'):
for line in _run("lspci -n").split("\n"):
revision = ''
fields = line.split(" ", maxsplit=4)
(vendor, device) = fields[2].split(":")
if len(fields) > 3:
revision = fields[4]
pci[fields[0]] = {"class_id": fields[1].rstrip(":"), "vendor_id": vendor, "device_id": device, "revision": revision.strip(')')}

# lspci -mm: 00:00.0 "Host bridge" "Intel Corporation" "82G33/G31/P35/P31 Express DRAM Controller" -p00 "Red Hat, Inc." "QEMU Virtual Machine"
for fields in csv.reader(_run("lspci -mm").split("\n"), delimiter=' ', quotechar='"'):
pci[fields[0]]['class_name'] = fields[1].strip('"')
pci[fields[0]]['vendor_name'] = fields[2]
pci[fields[0]]['device_name'] = fields[3]
pci[fields[0]]['driver'] = drivers.get(fields[0], '')
from nethsec.inventory import _run

# generate the UUID if not present
u = EUci()
Expand All @@ -65,26 +20,21 @@ if not sid:
u.set('phonehome', 'config', 'uuid', sid)
u.commit('phonehome')

product = _run("cat /sys/devices/virtual/dmi/id/product_name")
if not product:
try:
binfo = json.loads(_run("cat /etc/board.json"))
product = binfo['model']['name']
except:
product = ""

version = ""
with open('/etc/os-release', 'r') as file:
for line in file:
if line.startswith("VERSION_ID="):
version = line.split('=')[1].replace('"','').rstrip()
break
cpu_info = inventory.get_cpu_info()
pci = inventory.get_pci_info()
product = inventory.get_product()
version = inventory.get_version()
mem_info = inventory.get_memory()
mount_points = inventory.get_mount_points()

features = {}
info = {}
for func in dir(inventory):
method = getattr(inventory, func)
if func.startswith("fact_"):
method = getattr(inventory, func)
features[func.removeprefix('fact_')] = method(EUci())
if func.startswith("info_"):
info[func.removeprefix('info_')] = method(EUci())

data = {
"$schema": "https://schema.nethserver.org/facts/2022-12.json",
Expand All @@ -97,20 +47,23 @@ data = {
},
"processors": {
"count": _run("grep processor /proc/cpuinfo | wc -l"),
"model": _get_cpu_field("Model name", cpu_info),
"architecture": _get_cpu_field("Architecture", cpu_info)
"model": cpu_info["model"],
"architecture": cpu_info["architecture"]
},
"product": {
"name": product,
"manufacturer": _run("cat /sys/devices/virtual/dmi/id/sys_vendor")
},
"virtual": _get_cpu_field("Hypervisor vendor", cpu_info) if _get_cpu_field("Hypervisor vendor", cpu_info) else 'physical',
"virtual": inventory.is_virtual(),
"memory": {
"swap": { "used_bytes": _run("free | grep 'Swap': | awk '{print $3}'"), "available_bytes": _run("free | grep 'Swap': | awk '{print $4}'") },
"system": { "used_bytes": _run("free | grep 'Mem': | awk '{print $3}'"), "available_bytes": _run("free | grep 'Mem:' | awk '{print $7}'") }
"swap": { "used_bytes": mem_info.get('SwapUsed', 0) * 1024, "available_bytes": mem_info.get('SwapFree', 0) * 1024 },
"system": { "used_bytes": mem_info.get('MemUsed', 0) * 1024, "available_bytes": mem_info.get('MemAvailable', 0) * 1024 }
},
"pci": list(pci.values()),
"mountpoints": mount_points,
"features": features
}
}

data['facts'].update(info)
print(json.dumps(data))
159 changes: 24 additions & 135 deletions packages/ns-plug/files/inventory
Original file line number Diff line number Diff line change
Expand Up @@ -5,171 +5,60 @@
# SPDX-License-Identifier: GPL-2.0-only
#

import re
import json
import subprocess
import ipaddress
from euci import EUci
from struct import pack
from nethsec import utils, inventory
from socket import inet_ntoa

def _run(cmd):
try:
proc = subprocess.run(cmd, shell=True, check=True, capture_output=True, text=True)
return proc.stdout.rstrip().lstrip()
except:
return ''

def _run_json(cmd):
try:
return json.loads(_run(cmd))
except:
return None

def _get_cpu_field(field, cpu_info):
for f in cpu_info:
if f['field'].startswith(field):
return f['data']

return ''

def _get_role(u, interface):
for zone in utils.get_all_by_type(u, 'firewall', 'zone'):
name = u.get('firewall', zone, 'name')
networks = u.get('firewall', zone, 'network', list=True, default=[])
if interface in networks:
if name == "lan":
return "green"
elif name == "wan":
return "red"
else:
return name

def _get_ip(interface):
info = _run_json(f"ifstatus {interface}")
try:
return info['ipv4-address'][0]['address']
except:
return ''

def _get_mask(interface):
info = _run_json(f"ifstatus {interface}")
try:
m = info['ipv4-address'][0]['mask']
bits = 0xffffffff ^ (1 << 32 - int(m)) - 1
mask = inet_ntoa(pack(">I", bits))
return mask
except:
return ''

def _get_gateway(interface):
info = _run_json(f"ifstatus {interface}")
try:
for r in info['route']:
if r['target'] == '0.0.0.0':
return r['nexthop']
except:
return ''

def _get_public_ip():
# Try ifconfig.co: sometimes the site returns an HTML error page, ignore it
address = _run("curl --fail --max-time 5 --retry 2 https://ifconfig.co")
try:
ipaddress.ip_address(address)
return address
except:
# Fallback to DNS query
address = _run("dig +short myip.opendns.com @resolver1.opendns.com")

try:
ipaddress.ip_address(address)
return address
except:
return ''

release = _run('grep VERSION= /etc/os-release | cut -d= -f 2 | tr -d \'"\'')
cpu_info = _run_json('lscpu -J')['lscpu']
binfo = _run_json("cat /etc/board.json")
board = _run("cat /sys/devices/virtual/dmi/id/board_name")
if not board:
try:
board = binfo['model']['id']
except:
board = ""
product = _run("cat /sys/devices/virtual/dmi/id/product_name")
if not product:
try:
product = binfo['model']['name']
except:
product = ""
dns = _run("uci get dhcp.@dnsmasq[0].server | tr ' ' ','")
if not dns:
dns = _run("grep nameserver /tmp/resolv.conf.d/resolv.conf.auto | awk '{print $2}' | tr '\n' ','")
dns = dns.removesuffix(',')
networks = {}
u = EUci()
devices = utils.get_all_by_type(u, 'network', 'device')
for section in utils.get_all_by_type(u, 'network', 'interface'):
if section == "lan6" or section == "wan6": # skip IPv6 for now
continue
interface = u.get_all('network', section)
device = interface.get("device")
if not device or device == "lo" or device.startswith("ipsec") or device.startswith("tun"):
continue
network = {"type": "ethernet", "name": device, "props": { "role": _get_role(u, section), "ipaddr": _get_ip(section), "netmask": _get_mask(section), "gateway": _get_gateway(section)}}
# get bridge ports, exclude vlans over bridges
is_vlan = bool(re.search(r'\.\d+$', interface['device'])) # check if the device name ends with .<number>
if interface['device'].startswith('br') and not is_vlan:
network["type"] = "bridge"
for d in devices:
if u.get('network', d, 'name') == device:
network['props']["bridge"] = u.get('network', d, 'ports', default=[])
networks[device] = network
from nethsec import inventory
from nethsec.inventory import _run

uci = EUci()
release = inventory.get_version()
cpu_info = inventory.get_cpu_info()
product = inventory.get_product()
dns = inventory.info_dns_servers(uci)
networks = inventory.get_networks(uci)
mem_info = inventory.get_memory()
mount_points = inventory.get_mount_points()

features = {}
for func in dir(inventory):
if func.startswith("fact_"):
method = getattr(inventory, func)
features[func.removeprefix('fact_')] = method(EUci())
features[func.removeprefix('fact_')] = method(uci)
# Prevent duplicated info
del features['network']['configuration']

data = {
"arp_macs": _run('cat /proc/net/arp | grep -v IP | wc -l'),
"dmi": { "product": { "name": product, "uuid": _run("cat /sys/class/dmi/id/product_uuid") }, "bios": { "version": _run("cat /sys/devices/virtual/dmi/id/bios_version"), "vendor": _run("cat /sys/devices/virtual/dmi/id/bios_vendor")}, "board": { "product": board, "manufacturer": _run("cat /sys/devices/virtual/dmi/id/sys_vendor") }},
"virtual": _get_cpu_field("Hypervisor vendor", cpu_info) if _get_cpu_field("Hypervisor vendor", cpu_info) else 'physical',
"dmi": { "product": { "name": product, "uuid": _run("cat /sys/class/dmi/id/product_uuid") }, "bios": { "version": _run("cat /sys/devices/virtual/dmi/id/bios_version"), "vendor": _run("cat /sys/devices/virtual/dmi/id/bios_vendor")}, "board": { "product": product, "manufacturer": _run("cat /sys/devices/virtual/dmi/id/sys_vendor") }},
"virtual": inventory.is_virtual(),
"kernel": _run('uname'),
"kernelrelease": _run('uname -r'),
"networking": { "fqdn": _run('uname -n')},
"os": { "type": "nethsecurity", "name": "NethSec", "release": { "full": release, "major": 7 }, "family": "OpenWRT" },
"processors": { "count": _run("grep processor /proc/cpuinfo | wc -l"), "models": [ _get_cpu_field("Model name", cpu_info) ], "isa": _get_cpu_field("Architecture", cpu_info)},
"processors": { "count": _run("grep processor /proc/cpuinfo | wc -l"), "models": [ cpu_info["model"] ], "isa": cpu_info["architecture"]},
"timezone": _run('uci get system.@system[0].zonename'),
"system_uptime": { "seconds": _run("cat /proc/uptime | awk -F. '{print $1}'") },
"esmithdb": {
"networks": list(networks.values()),
"configuration" : [
{ "name": "sysconfig", "type": "configuration", "props": {"Version": release} },
{ "name": "dns", "type": "configuration", "props": {"NameServers": dns} },
{ "name": "dns", "type": "configuration", "props": {"NameServers": ','.join(dns)} },
{ "name" : "SystemName", "type" : _run("uname -n | cut -d'.' -f1") },
{ "name" : "DomainName", "type" : _run("uname -n | cut -d'.' -f2-") }
]
},
"memory": {
"swap": { "used_bytes": 0, "available_bytes": 0, "total_bytes": 0 },
"swap": { "used_bytes": mem_info.get('SwapUsed', 0), "available_bytes": mem_info.get('SwapFree', 0), "total_bytes": mem_info.get('SwapTotal', 0) },
"system": {
"used_bytes": int(_run("free | grep 'Mem': | awk '{print $3}'"))*1024,
"available_bytes": int(_run("free | grep 'Mem:' | awk '{print $7}'"))*1024,
"total_bytes": int(_run("free | grep 'Mem': | awk '{print $7}'"))*1024
}
},
"mountpoints": {
"/": {
"used_bytes": int(_run("df -P | sort | uniq | grep '/$' | awk '{print $3}'"))*1024,
"available_bytes": int(_run("df -P | sort | uniq | grep '/$' | awk '{print $4}'"))*1024,
"size_bytes": int(_run("df -P | sort | uniq | grep '/$' | awk '{print $2}'"))*1024
"used_bytes": mem_info.get('MemUsed', 0),
"available_bytes": mem_info.get('MemAvailable', 0),
"total_bytes": mem_info.get('MemTotal', 0)
}
},
"mountpoints": mount_points,
"rpms": { "nethserver-firewall-base-ui": _run("opkg status ns-ui | grep Version | awk '{print $2}'") },
"public_ip": _get_public_ip(),
"public_ip": inventory.info_default_ipv4(uci),
"features": features
}

Expand Down
2 changes: 1 addition & 1 deletion packages/python3-nethsec/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk

PKG_NAME:=python3-nethsec
# renovate: datasource=github-tags depName=NethServer/python3-nethsec
PKG_VERSION:=1.5.1
PKG_VERSION:=1.6.0
PKG_RELEASE:=1

PKG_MAINTAINER:=Giacomo Sanchietti <giacomo.sanchietti@nethesis.it>
Expand Down
Loading