1. Formål
Ping(at)Work er en webapplikasjon for å definere maler for nettverks-PING og kjøre dem manuelt eller etter plan. Resultatene lagres med status (OK, advarsel, kritisk, feil) beregnet fra terskler for latens og pakketap, og statusendringer kan varsles på e-post eller webhook.
Løsningen leveres som en offline-pakke for Rocky Linux 9 (x86_64) og kjører likt på fysisk server, VMware og Proxmox VE.
2. Arkitektur
Nettleser ──HTTPS:443──▶ Caddy (pingforge-caddy)
(HTTP:80 → 301 HTTPS) │ reverse_proxy, TLS, HSTS
▼
gunicorn + Flask (pingforge-web) 127.0.0.1:8000
│ maler, brukere, kø, visning
▼
SQLite /var/lib/pingforge/pingforge.db (WAL)
▲
│ henter kø, planlegger, lagrer resultater
Worker (pingforge-worker) ──▶ /usr/bin/ping (CAP_NET_RAW)
│
└──▶ SMTP / webhook ved statusendring
| Komponent | Teknologi | Ansvar |
|---|---|---|
|
Caddy 2.11.7 (statisk Go-binær) |
TLS-terminering, HTTP→HTTPS, sikkerhetshoder, komprimering, proxy til gunicorn |
|
Python 3.9, Flask 3.1, gunicorn 23 (2 prosesser × 4 tråder) |
Brukergrensesnitt, innlogging, CRUD for maler, legger kjøringer i kø, viser resultater, dokumenter |
|
Python 3.9, én prosess med tråder |
Utfører køen, planlegger maler, kjører |
Database |
SQLite 3 i WAL-modus |
Maler, mål, kjøringer, resultater, brukere, innloggingsfeil, nøkkel/verdi (worker-puls) |
Skillet mellom web og worker er bevisst: web-prosessen starter aldri underprosesser og trenger ingen
ekstra rettigheter. Bare worker har CAP_NET_RAW.
3. Kjøreflyt
-
Brukeren trykker Kjør eller Lagre og kjør. Web lager en rad i
runsmedstate = queuedog sender brukeren til kjøringssiden. -
Worker leser køen hvert 2. sekund og starter én tråd per kjøring.
stateblirrunning. -
For hvert mål og hver IP-versjon bygges en argumentliste (se Kommandobygging), og
pingkjøres parallelt med inntilmax_parallelsamtidige prosesser. -
Utdata tolkes (sendt, mottatt, tap, min/snitt/maks/mdev). Hvert resultat får status etter tersklene. Kjøringens status er det verste resultatet.
-
Resultatene lagres i én transaksjon. Malen får
last_statusoglast_run_at. -
Hvis status har endret seg siden forrige kjøring, sendes varsel.
-
Kjøringssiden spør
/api/runs/<id>hvert 1,5 sekund og laster seg på nytt når kjøringen er ferdig.
Planlagte maler: hvert 15. sekund legger worker i kø de aktiverte malene der
last_run_at + schedule_minutes er passert og som ikke allerede har en kjøring i kø eller i gang.
3.1. Kommandobygging
/usr/bin/ping -<4|6> -n -c <count> -i <interval> -s <size> -W <timeout> -w <deadline>
[-t <ttl>] [-M do] [-Q <dscp<<2>] [-I <interface>] -- <host>
-
deadline = ceil(count × interval + timeout) + 1sekunder. Prosessen avbrytes etterdeadline + 10sekunder. -
-Qfår hele TOS-byten, dvs. DSCP-verdien skiftet to bit (EF 46 → 184). -
Kommandoen kjøres med
subprocess.run(liste), uten shell, medLC_ALL=Cog minimalPATH.
3.2. Statusregler
| Status | Regel (første som slår til) |
|---|---|
Feil |
|
Kritisk |
0 svar, eller tap ≥ kritisk tapsterskel, eller snittlatens ≥ kritisk latensterskel |
Advarsel |
tap ≥ advarsel tapsterskel, eller snittlatens ≥ advarsel latensterskel |
OK |
ellers |
Varsel sendes når status endres. Den aller første kjøringen varsler bare hvis den ikke er OK.
4. Validering
| Felt | Regel |
|---|---|
Mål |
IPv4/IPv6 ( |
Antall |
1–1000 |
Intervall |
0,2–60 s (komma og punktum godtas) |
Størrelse |
0–65507 byte |
Timeout |
1–60 s |
TTL |
1–255 eller tom |
Grensesnitt |
|
DSCP |
0, 8, 10, 18, 26, 34, 40, 46, 48 |
Plan |
0, 5, 15, 60 eller egendefinert 1–10080 min |
Terskler |
Tall ≥ 0 (tap ≤ 100). Advarsel ≤ kritisk |
Varsle |
E-postadresser |
5. Datamodell
users(id, username UNIQUE, pw_hash, created_at)
templates(id, name, description, ip_version, count, interval, size, timeout, ttl,
interface, dscp, df, schedule_minutes, warn_latency, crit_latency,
warn_loss, crit_loss, notify, enabled, last_status, last_run_at,
created_at, updated_at)
targets(id, template_id → templates ON DELETE CASCADE, host, label, position)
runs(id, template_id → templates ON DELETE SET NULL, template_name, trigger,
requested_by, state, status, queued_at, started_at, finished_at, message)
results(id, run_id → runs ON DELETE CASCADE, host, label, ip_version, command,
sent, received, loss, rtt_min, rtt_avg, rtt_max, rtt_mdev, status,
output, exit_code)
login_failures(key, ts)
kv(key PRIMARY KEY, value) -- worker_heartbeat
Tidsstempler lagres som ISO 8601 i UTC og vises i serverens lokale tidssone. PRAGMA user_version
holder skjemaversjonen (1).
6. HTTP-grensesnitt
| Metode | Sti | Funksjon |
|---|---|---|
GET/POST |
|
Innlogging |
POST |
|
Utlogging |
GET |
|
Oversikt over maler |
GET |
|
Ny / rediger mal, med siste kjøring |
POST |
|
Lagre ( |
POST |
|
Kjør, dupliser, slett |
GET |
|
Historikk og detaljer |
GET |
|
Resultater som CSV |
GET |
|
JSON |
GET |
|
Innstillinger og systemstatus |
POST |
|
Bytt passord, importer maler |
GET |
|
Eksporter maler |
GET |
|
Bytt språk (cookie |
GET |
|
Dokumenter (offentlig) |
GET |
|
Helsesjekk |
Alle POST krever CSRF-token. Alle sider unntatt innlogging, dokumenter og helsesjekk krever innlogging.
6.1. Eksportformat for maler
{
"format": "pingforge-templates",
"version": 1,
"exported": "2026-10-07T17:43:00+00:00",
"templates": [
{
"name": "Kjernerutere – latens", "description": "", "ip_version": "4",
"count": 10, "interval": 0.2, "size": 56, "timeout": 1, "ttl": 64,
"interface": "", "dscp": 46, "df": true, "schedule_minutes": 5,
"warn_latency": 30.0, "crit_latency": 80.0, "warn_loss": 1.0, "crit_loss": 5.0,
"notify": "noc@example.com", "enabled": true,
"targets": [ { "host": "10.10.0.1", "label": "core-osl-01" } ]
}
]
}
6.2. Webhook
POST med Content-Type: application/json:
{ "text": "[PingForge] CRITICAL: WAN-linker ...", "template": "WAN-linker", "template_id": 3,
"run_id": 42, "status": "critical", "previous_status": "ok",
"results": [ { "host": "192.0.2.10", "label": "wan-b", "ip_version": "4", "status": "critical",
"loss": 100.0, "rtt_min": null, "rtt_avg": null, "rtt_max": null, "rtt_mdev": null } ] }
Feltet text gjør at innkommende webhooks i Slack, Mattermost og Rocket.Chat viser meldingen
direkte.
7. Brukergrensesnitt
-
Server-rendret HTML (Jinja2) som fungerer uten JavaScript.
static/js/app.jslegger til dynamiske mål-rader, levende kommando-forhåndsvisning, kopiering, søk, temabytte og oppdatering av kjøringssiden. -
Språk: alle tekster ligger i
i18n.pysom par (engelsk, norsk). Valget lagres i cookienlang.default_languagestyrer standarden. Brukermanual og lisensside vises på valgt språk. -
Mørk/lys modus: CSS-variabler under
[data-theme="dark"]og[data-theme="light"]. Valget lagres i cookienthemeslik at siden rendres i riktig tema uten blink. -
Skrifter: IBM Plex Sans og Mono, levert lokalt (woff2). Ingen eksterne ressurser lastes.
-
Tilgjengelighet: semantiske knapper og skjemaelementer, synlig fokus, «hopp til innhold», status som tekst (ikke bare farge), kontrast ≥ 4,5:1, mobiltilpasset fra 360 px.
8. Sikkerhet
| Tiltak | Gjennomføring |
|---|---|
Transport |
Caddy med TLS 1.2/1.3, HSTS, HTTP→HTTPS. Selvsignert RSA 3072-sertifikat ved installasjon |
Innlogging |
Werkzeug scrypt-hash. Sperre etter 5 feil per bruker / 20 per IP i 15 min. Sesjon 12 t |
Sesjon |
Signert cookie (itsdangerous) med |
CSRF |
Token per sesjon på alle POST |
Hoder |
CSP |
Kommandoer |
Ingen shell, validerte argumenter, |
Prosesser |
Egne systembrukere. systemd-herding: |
Nettverk |
Bare Caddy lytter eksternt. Caddys admin-API er slått av |
9. Filer og rettigheter
| Sti | Eier / modus | Innhold |
|---|---|---|
|
root, 0644/0755 |
Python-pakken |
|
root |
Python-miljø (fra wheels) |
|
root, 0755 |
Caddy |
|
root |
Dokumentasjon (vises i appen) |
|
root:pingforge 0640 |
Konfigurasjon og hemmeligheter |
|
root 0644 |
Proxy-oppsett |
|
root:pingforge-caddy 0750 |
Sertifikat (0644) og nøkkel (0640) |
|
pingforge 0750 |
Database |
|
pingforge-caddy 0750 |
Caddys arbeidsdata |
|
root 0644 |
Tjenester |
|
root 0755 |
Administrasjon |
10. Kapasitet
-
Én kjøring bruker høyst
max_parallel(16)ping-prosesser. Flere maler kan kjøre samtidig. -
Varighet per mål ≈
count × intervalsekunder (5 × 1 s = 5 s). 256 mål med 16 parallelle tar ca. 16 × varigheten for ett mål. -
Databasevekst ≈ 1–3 kB per resultat (inkludert rå utdata). 50 mål hvert 5. minutt i 90 dager gir ca. 1,3 mill. resultater, i størrelsesorden 2–4 GB. Senk
retention_daysved store volumer.
11. Bygging og pakking
build.sh setter sammen pakken fra kildekoden og vendor/:
-
vendor/wheels/– wheels for CPython 3.9 manylinux x86_64 (låst irequirements.txt) og pip. -
vendor/caddy/– Caddy-binær og lisens (SHA-512 kontrollert ved nedlasting). -
vendor/fonts/– OFL-lisensene for IBM Plex.
Resultatet er dist/pingforge-<versjon>-el9-x86_64.tar.gz med SHA256SUMS inni og en
.sha256-fil ved siden av. scripts/gen_notices.py lager Tredjepartsmerknader og
scripts/gen_inventory.py lager third_party_inventory.json fra de samme filene; build.sh kjører begge.
pingforge-1.0.0-el9-x86_64/
├── install.sh uninstall.sh VERSION LICENSE README.md requirements.txt SHA256SUMS
├── third_party_inventory.json
├── app/pingforge/ Python-pakken (app, worker, cli, maler, static)
├── wheels/ 13 wheel-filer
├── caddy/ caddy, LICENSE
├── config/ Caddyfile, pingforge.conf.example
├── systemd/ pingforge-web/-worker/-caddy.service
└── Dokumenter/ all dokumentasjon
12. Kildekode
| Fil | Innhold |
|---|---|
|
Flask-applikasjon: ruter, innlogging, CSRF, hoder, dokumentvisning |
|
Kø, planlegging, utførelse, opprydding |
|
Bygging av |
|
Validering av skjema og import, CIDR-utvidelse |
|
Skjema og databasefunksjoner |
|
E-post og webhook |
|
Engelske og norske tekster |
|
|
|
Lesing av |
|
Jinja2-maler |
|
CSS, JavaScript, fonter, ikon |