Blog

sha256: 2b87a252a3d912530dd8c20df6bee7f6cbc4ede0074fdf217e318aab39d9736c

Knot

KNOT DNS

some information related to knot dns / knot-dnsutils. Tested with ‘knotd (Knot DNS), version 3.3.3’ running on OpenBSD 7.5.

Install Knot

pkg_add knot

Build Config

we’re configure this server as “slave” which get’s it’s config from a Primary Nameserver

# /etc/knot/knot.conf 

server:
    rundir: "/var/run/knot"
    user: _knot:_knot
    automatic-acl: on
    listen: [ xx.xx.xx.xx@53, xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx@53 ]

log:
  - target: syslog
    any: info

database:
    storage: "/var/db/knot"

key:
  - id: mykey
    algorithm: hmac-sha256
    secret: xXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXx=

remote:
  - id: primary
    address: [ xx.xx.xx.xx@53, xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx@53 ] # IP Address of Primary Nameserver
    key: mykey

template:
  # default
  - id: default
    storage: "/var/db/knot"
    file: "%s.zone"
    global-module: mod-stats
    semantic-checks: on

zone:

  # Slave Zones
  - domain: your-domain.ch
    master: primary
  - domain: your-other-domain.de
    master: primary
  - domain: your-last-domain.com
    master: primary

enable and start service

rcctl enable knot
rcctl restart knot

zone backup

folder="/tmp/knot"
mkdir $folder
chown -R _knot $folder
knotc zone-backup +backupdir $folder

Build query File

cat ${folder}/zonefiles/stoege.net.zone |awk "{print \$1,\$3}" |grep -E "(NS|DS|A|AAAA|PTR|MX|SOA)$" |\
  sort -u -R > ${folder}/queries.txt 

StressTests (from a Debian Box)

apt install knot-dnsutils
cd /tmp
scp root@45.32.159.233:/tmp/knot/queries.txt .

5k Queries

kxdpgun -i queries.txt 45.32.159.233
using interface ens18, XDP threads 1, UDP, native mode
thread#00: sent 5010, received 5010
total queries:     5010 (1002 pps)
total replies:     5010 (1002 pps) (100%)
average DNS reply size: 63 B
average Ethernet reply rate: 842459 bps (0.84 Mbps)
responded NOERROR:   5010
duration: 5 s

100k Queries

time kxdpgun -t 20 -Q 5000 -i queries.txt -b 20 -p 8853 45.32.159.233
using interface ens18, XDP threads 1, UDP, native mode
thread#00: sent 100020, received 0
total queries:     100020 (5001 pps)
total replies:     0 (0 pps) (0%)
average DNS reply size: 0 B
average Ethernet reply rate: 24 bps (0.00 Mbps)
duration: 20 s

real	0m22.052s
user	0m0.092s
sys	0m0.183s

khost – Simple DNS lookup utility¶

# khost stoege.net
stoege.net. has IPv4 address 159.69.214.12
stoege.net. has IPv6 address 2a01:4f8:c0c:fff7::2
stoege.net. mail is handled by 10 ideo.noflow.ch.
# khost stoege.net -t SOA
stoege.net. start of authority is ns1.noflow.ch. hostmaster.noflow.ch. 2024052701 3600 900 1209600 1800

kdig – Advanced DNS lookup utility¶

# kdig stoege.net A    
;; ->>HEADER<<- opcode: QUERY; status: NOERROR; id: 57426
;; Flags: qr rd ra; QUERY: 1; ANSWER: 1; AUTHORITY: 0; ADDITIONAL: 0

;; QUESTION SECTION:
;; stoege.net.         		IN	A

;; ANSWER SECTION:
stoege.net.         	1800	IN	A	159.69.214.12

;; Received 44 B
;; Time 2024-07-10 19:27:20 CEST
;; From 108.61.10.10@53(UDP) in 1.4 ms

short answer

# kdig +short stoege.net AAAA
2a01:4f8:c0c:fff7::2

output in json

# kdig +json stoege.net AAAA
{
  "dateString": "2024-07-10T19:28:01+0200",
  "dateSeconds": 1720632481,
  "msgLength": 56,
  "ID": 27609,
  "QR": 1,
  "Opcode": 0,
  "AA": 0,
  "TC": 0,
  "RD": 1,
  "RA": 1,
  "AD": 0,
  "CD": 0,
  "RCODE": 0,
  "QDCOUNT": 1,
  "ANCOUNT": 1,
  "NSCOUNT": 0,
  "ARCOUNT": 0,
  "QNAME": "stoege.net.",
  "QTYPE": 28,
  "QTYPEname": "AAAA",
  "QCLASS": 1,
  "QCLASSname": "IN",
  "answerRRs": [
    {
      "NAME": "stoege.net.",
      "TYPE": 28,
      "TYPEname": "AAAA",
      "CLASS": 1,
      "CLASSname": "IN",
      "TTL": 1800,
      "rdataAAAA": "2a01:4f8:c0c:fff7::2",
      "RDLENGTH": 16,
      "RDATAHEX": "2A0104F80C0CFFF70000000000000002"
    }
  ]
}

Any Comments ?

sha256: 4034db839fb307e487b0188f378a9bc142ededf7de783788811c270f126f03f5

Debian

Patch OpenSSH Only

apt install --only-upgrade  openssh-client openssh-server openssh-sftp-server

Time Zone

timedatectl set-timezone Europe/Zurich

-> set symlink: /etc/localtime -> ../usr/share/zoneinfo/Europe/Zurich

Fix Sudo Stuff

use ‘sudo -i’ and keep SSH_AUTH_SOCK if set

apt update
apt install sudo
usermod -aG sudo stoege
echo "Defaults env_keep+=SSH_AUTH_SOCK" > /etc/sudoers.d/ssh_auth_sock
echo "%sudo ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/group_sudo_nopass

Any Comments ?

sha256: 7e5018c28bea4357e2f6703ec2876e92798e020801a61e46d6e3698151bc5a38

Sound Selector MacOS

Sound Selector for MacOS

on MacOS, you can switch the input and output source on “system setting/sound”. i’d like todo this on the cli.

SwitchAudio

there is a litte tool called switchaudio. it can list, and also set the input/output device. let’s build a small wrapper around.

brew install switchaudio-osx

Usage

List Sound Devices

stoege@mac ~ % sound.sh

1: Externe Kopfhörer
2: Externes Mikrofon
3: Jabra Link 400
4: Mac mini-Lautsprecher
5: SRS-XB33
6: USB Audio Device

Set Sound Device

Mariadb

Install MariaDB on OpenBSD

Wanna install and Operate MariaDB on OpenBSD? Here a few hints …

Install Package

pkg_add mariadb-server mariadb-client
root@puffy /tmp# pkg_add mariadb-server                                                                                                                                    
quirks-7.14 signed on 2024-06-15T18:27:56Z
mariadb-server-10.9.8p0v1:lzo2-2.10p2: ok
mariadb-server-10.9.8p0v1:snappy-1.1.10p1: ok
mariadb-server-10.9.8p0v1:mariadb-client-10.9.8v1: ok
mariadb-server-10.9.8p0v1:p5-FreezeThaw-0.5001p0: ok
mariadb-server-10.9.8p0v1:p5-MLDBM-2.05p0: ok
mariadb-server-10.9.8p0v1:p5-Net-Daemon-0.49: ok
mariadb-server-10.9.8p0v1:p5-PlRPC-0.2020p0: ok
mariadb-server-10.9.8p0v1:p5-Math-Base-Convert-0.11p0: ok
mariadb-server-10.9.8p0v1:p5-Clone-0.46: ok
mariadb-server-10.9.8p0v1:p5-Module-Runtime-0.016p0: ok
mariadb-server-10.9.8p0v1:p5-Params-Util-1.102: ok
mariadb-server-10.9.8p0v1:p5-SQL-Statement-1.414: ok
mariadb-server-10.9.8p0v1:p5-DBI-1.643p0: ok
mariadb-server-10.9.8p0v1:p5-DBD-MariaDB-1.23: ok
mariadb-server-10.9.8p0v1:libxml-2.12.7: ok
mariadb-server-10.9.8p0v1: ok
Running tags: ok
The following new rcscripts were installed: /etc/rc.d/mysqld
See rcctl(8) for details.
New and changed readme(s):
	/usr/local/share/doc/pkg-readmes/mariadb-server

add MariaDB Tests

https://mariadb.com/kb/en/mariadb-test-overview/

SSHChat

SSH Chat

how to run your own SSH Chat Server

Setup

# add go
pkg_add go

# add user 'sshchat'
adduser

# switch user
su - sshchat
ftp https://github.com/shazow/ssh-chat/archive/v1.10.tar.gz
tar zxf v1.10.tar.gz
cd ssh-chat-1.10/
make build

# back to root
exit
cp /home/sshchat/ssh-chat-1.10/ssh-chat /usr/local/bin/

sshchat - ipfile

manage whiteliste ip in dedicated file

# create folder
mkdir -p /etc/pf.d

# sample file
echo "127.0.0.1" > /etc/pf.d/sshchat

# set permission
chmod 600 /etc/pf.d/sshchat

pf.conf

update pf.conf appropriate

PyProject 1

Sample PyProject from: https://github.com/volfpeter/motorhead/tree/main

PyProject

poetry init
poetry add motor pydantic
poetry add mkdocs-material mkdocstrings[python] mypy ruff poethepoet pytest pytest-asyncio pytest-random-order --group dev
[project]
name = "motorhead"
description = "Async MongoDB with vanilla Pydantic v2+ - made easy."
readme = "README.md"
license = { text = "MIT" }
authors = [
    { name = "Peter Volf", email = "do.volfp@gmail.com" },
]
requires-python = ">=3.10"
dependencies = ["pydantic", "motor"]
classifiers = [
    "Intended Audience :: Information Technology",
    "Operating System :: OS Independent",
    "Programming Language :: Python :: 3",
    "Development Status :: 4 - Beta",
    "Topic :: Internet",
    "Topic :: Software Development :: Libraries",
    "Topic :: Software Development",
    "Typing :: Typed",
    "Environment :: Web Environment",
    "Framework :: FastAPI",
    "Intended Audience :: Developers",
    "License :: OSI Approved :: MIT License",
    "Topic :: Internet :: WWW/HTTP",
]

[project.urls]
homepage = "https://github.com/volfpeter/motorhead"
documentation = "https://volfpeter.github.io/motorhead"
tracker = "https://github.com/volfpeter/motorhead/issues"

[tool.poetry]
name = "motorhead"
version = "0.2403.0"
description = "Async MongoDB with vanilla Pydantic v2+ - made easy."
authors = ["Peter Volf <do.volfp@gmail.com>"]
readme = "README.md"
packages = [{include = "motorhead"}]

[tool.poetry.dependencies]
python = "^3.10"
motor = "^3.1.0"
pydantic = "^2.1.0"

[tool.poetry.group.dev.dependencies]
mkdocs-material = "^9.5.19"
mkdocstrings = {extras = ["python"], version = "^0.25.0"}
mypy = "^1.10.0"
ruff = "^0.4.2"
poethepoet = "^0.26.0"
pytest = "^8.2.0"
pytest-asyncio = "^0.23.6"
pytest-docker = "^3.1.1"
pytest-random-order = "^1.1.1"

[tool.mypy]
strict = true
show_error_codes = true
exclude = ["tree_app"]

[[tool.mypy.overrides]]
module = ["motor.*"]
ignore_missing_imports = true

[tool.ruff]
line-length = 108
lint.exclude = [
    ".git",
    ".mypy_cache",
    ".pytest_cache",
    ".ruff_cache",
    ".venv",
    "dist",
    "docs",
]
lint.select = [
    "E",  # pycodestyle errors
    "W",  # pycodestyle warnings
    "F",  # pyflakes
    "I",  # isort
    "S",  # flake8-bandit - we must ignore these rules in tests
    "C",  # flake8-comprehensions
    "B",  # flake8-bugbear
]

[tool.ruff.lint.per-file-ignores]
"tests/**/*" = ["S101"]  # S101: use of assert detected

[tool.pytest.ini_options]
addopts = "--random-order"

[tool.poe.tasks]
serve-docs = "mkdocs serve"
check-format = "ruff format --check ."
lint = "ruff check ."
mypy = "mypy ."
format = "ruff format ."
lint-fix = "ruff . --fix"
test = "python -m pytest tests --random-order"

static-checks.sequence = ["lint", "check-format", "mypy"]
static-checks.ignore_fail = "return_non_zero"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

Any Comments ?

sha256: 889b2e95d218175c3503451a55e2c17af3760750a9b2d6e52559b7f336bee043

Borgbackup

Prerequisite

  • you need a remote Borg Server (Unix/Linux Machine with Borg installed)
  • valid User and Key for SCP Transfer
  • SSH Key -> /backup/id_ed25519

Create Local Folder

test -d /backup || (mkdir /backup; chmod 700 /backup)

Borg Backup Script

cat << 'EOF2' > /backup/borg.sh
#!/usr/bin/env bash

# BorgBackup Script, v1.0, 2024-04-09, by @stoege

# Remote server details
REMOTE_USER="borguser"
REMOTE_HOST="your.remote.borg.server"
REMOTE_REPO="mysamplerepo"

# Local directory to backup
LOCAL_DIR="/"

# List of directories to exclude
EXCLUDE_DIRS=(
    "*/.cache/"
    "/tmp"
    "/restore"
)

# Set PassPhrase for the Backup Encryption (run: pwgen 32 1)
export BORG_PASSPHRASE="your-super-strong-password"
export BORG_RSH='ssh -i /backup/id_ed25519'


# Function to perform full backup
perform_full_backup() {

    # Construct exclude options
    exclude_opts=""
    for dir in "${EXCLUDE_DIRS[@]}"; do
        exclude_opts+="--exclude $dir "
    done

    # Run BorgBackup command
    borg create \
        --verbose \
        --stats \
        --progress \
        --compression lz4 \
        $REMOTE_USER@$REMOTE_HOST:$REMOTE_REPO::'{hostname}-{now:%Y-%m-%d_%H:%M:%S}' \
        $LOCAL_DIR \
        $exclude_opts \
    || borg init -e repokey-blake2 $REMOTE_USER@$REMOTE_HOST:$REMOTE_REPO
}

# Function to perform restore
perform_restore() {

    # Create Restore
    test -d /restore || mkdir /restore

    # Change Dir
    cd /restore
 
    # Run BorgBackup command to restore specific directory
    borg extract \
        --verbose \
        --progress \
        $REMOTE_USER@$REMOTE_HOST:$REMOTE_REPO::$1 \
        $2
}

# Function to list all backups
list_backups() {

    # Run BorgBackup command to list all archives
    borg list $REMOTE_USER@$REMOTE_HOST:$REMOTE_REPO

}


# Function to rotate backups
rotate_backups() {
    # Run BorgBackup command to prune archives based on retention policy
    borg prune \
        --verbose \
        --list \
        --glob-archives "${hostname}*" \
        --keep-hourly=2 \
        --keep-daily=2 \
        --keep-weekly=2 \
        --keep-monthly=2 \
        $REMOTE_USER@$REMOTE_HOST:$REMOTE_REPO
}

# Install on Host
install_myself() {

  # Folder
  f="/backup"

  # Create Directory, copy File
  test -d ${f} || (mkdir ${f}; chmod 700 ${f})
  cp $0 ${f}/
  
  # Inform User
  cat << EOF

# to install in Crontab:
crontab -e
5 6,12,18 * * * cd ${f}; $0 backup >> /var/log/borgbackup.log 2>&1

EOF

}

# Help
show_help() {
  echo "$0 [ backup | list | rotate | install | restore BACKUPNAME /etc ]"
  exit 1
}


# Main script
echo "Starting BorgBackup..."

# Check if BorgBackup is installed
if ! command -v borg &> /dev/null; then
    echo "Error: BorgBackup is not installed. Please install BorgBackup."
    exit 1
fi

# Check if parameter is provided
if [ $# -eq 0 ]; then
  show_help
fi

# Perform action based on parameter
case "$1" in
    "backup")
        echo "Performing full backup..."
        perform_full_backup
        rotate_backups
        ;;
    "restore")
        if [ $# -lt 3 ]; then
            echo "Error: Please specify a Backup Set and directory to restore."
            echo "$0 BACKUP_SET /FOLDER/TO/RESTORE"
            exit 1
        fi
        echo "Performing restore for directory '$3' on set '$2'"
        perform_restore $2 $3
        ;;
    "list")
        echo "Listing all backups..."
        list_backups
        ;;
    "rotate")
        echo "Rotating backups..."
        rotate_backups
        ;;
    "install")
        echo "Install Scripts"
        install_myself
        ;;
    *)
        show_help
        ;;
esac

# Check backup status
if [ $? -eq 0 ]; then
    echo "Action completed successfully."
else
    echo "Action failed."
fi

# Finally done
exit 0

EOF2
chmod 700 /backup/borg.sh

Execute It

Create Backup

/backup/borg.sh backup

List Backup

/backup/borg.sh list

Restore Folder

/backup/borg.sh restore hostname-date /etc"

Any Comments ?

sha256: 3adc039f17d2b87ef48b8e9d200c53675b430603c048d4879aacb2dabb3ce37f

Python MTR

Setup Project

Poetry

poetry init
poetry add twisted
poetry add twisted-mtr

Build Python Script

cat << 'EOF' > main.py
#!/usr/bin/env python3

'''
    An example usage for twisted-mtr which initiates multiple async traceroutes
    to multiple IPv4 and IPv6 target IP addresses at the same time. You will
    need to set your source IP addresses correctly and have a working dual
    IPv4/IPv6 networking stack to run this example.

'''

import sys
import signal
import logging
import ipaddress
from twisted.internet import reactor
from twisted_mtr import logger, errors, mtr, utils


log = logger.get_logger('trace', level=logging.DEBUG)


if __name__ == '__main__':

    log.info(f'Starting up...')

    # Find mtr-packet in your path
    mtr_binary_name = 'mtr-packet'
    mtr_binary_path = utils.find_binary(mtr_binary_name)

    # Replace with your local IPv4 address
    # Note that if you set this to an IP address not available on your system
    # your traceroutes will simply all time out
    local_ipv4 = ipaddress.IPv4Address('10.1.2.3')

    # Replace with your local IPv6 address
    # Note that if you set this to an IP address not available on your system
    # your traceroutes will simply all time out
    local_ipv6 = ipaddress.IPv6Address('2404:1:2:3:4:5:6:7')

    # Create the TraceRoute Twisted process object
    app_mtr = mtr.TraceRoute(
        mtr_binary_path=mtr_binary_path,
        local_ipv4=local_ipv4,
        local_ipv6=local_ipv6
    )

    # Bind to the Twisted tractor with the mtr-packet binary
    reactor.spawnProcess(app_mtr, mtr_binary_name, [mtr_binary_path], {})

    # Sets to track the traceroutes that have been dispatched and completed
    requested = set()
    completed = set()

    # Success callback
    def _test_traceroute_callback(timestamp, target_ip, protocol, port, hops):
        log.info(f'Completed traceroute started at {timestamp} to: '
                 f'{target_ip} ({protocol}:{port})')
        completed.add(str(target_ip))
        for (hop_num, hop_ip, microseconds) in hops:
            log.info(f' - {hop_num} {hop_ip} {microseconds}')
        if requested == completed:
            log.info('All traces complete, stopping reactor')
            reactor.stop()

    # Error callback
    def _test_trace_error(counter, joined_request, error, extra):
        log.error(f'Error running traceroute: {error}')
        reactor.stop()

    # Queue up our traceroutes
    target_ip = utils.parse_ip('8.1.1.1')  # No route after a few hops to test
    requested.add(str(target_ip))
    app_mtr.trace(_test_traceroute_callback, _test_trace_error, target_ip)

    target_ip = utils.parse_ip('8.8.8.8')
    requested.add(str(target_ip))
    app_mtr.trace(_test_traceroute_callback, _test_trace_error, target_ip, protocol='udp', port=53)

    target_ip = utils.parse_ip('1.1.1.1')
    requested.add(str(target_ip))
    app_mtr.trace(_test_traceroute_callback, _test_trace_error, target_ip, protocol='tcp', port=53, ttl=3)

    target_ip = utils.parse_ip('2404:6800:4015:802::200e')
    requested.add(str(target_ip))
    app_mtr.trace(_test_traceroute_callback, _test_trace_error, target_ip)

    target_ip = utils.parse_ip('2606:4700::6810:7b60')
    requested.add(str(target_ip))
    app_mtr.trace(_test_traceroute_callback, _test_trace_error, target_ip)

    # Polite hook for control+c to abort the traceroutes before they complete
    def signal_handler(sig, frame):
        sys.stdout.write('\n')  # put the ^C on its own line
        log.info(f'Caught keyboard interrupt, shutting down...')
        reactor.stop()

    signal.signal(signal.SIGINT, signal_handler)

    # Start the Twisted reactor event loop
    log.info(f'Starting event loop...')
    reactor.run()

    # If we reach here the reactor has been stopped, all done
    log.info(f'Goodbye')
EOF

don’t forget to set your local_ipv4 and local_ipv6 address correctly

Python Twisted

WebServer with Python Twisted

cat << 'EOF' > main.py
from twisted.web import server, resource
from twisted.internet import reactor, endpoints

class Counter(resource.Resource):
    isLeaf = True
    numberRequests = 0

    def render_GET(self, request):

        client_ip = request.getClientAddress().host

        r=request.uri.decode('utf-8')
        if not r =="/favicon.ico":
          self.numberRequests += 1

        request.setHeader(b"content-type", b"text/plain")
        content = u"I am request #{} from {}\n".format(self.numberRequests, client_ip)
        return content.encode("ascii")

endpoints.serverFromString(reactor, "tcp:8080").listen(server.Site(Counter()))
reactor.run()
EOF

Run

poetry init
poetry add twisted
poetry run python main.py

Browse

Open your Browser: http://ip-of-your-host:8080

XZ

MacOS

even MacOS seems not hardly affected, better safe than sorry !

# get Version
brew info xz

# Cleanup Cache
brew cleanup -v -s --prune=all

# Downgrade
brew reinstall xz

# Update
brew update

# Upgrade
brew upgrade

# reboot
reboot

# confirm, 5.4.6 should be fine
xz -V

Any Comments ?

sha256: d2d6b0518ee60fc80381a2fb44dee61d06c02a7d4182045ff25d59f4894d1a10