Add per-device L2/misc long-tail: tunnels/stopped_rules/proxy_arp/proxy_ndp/arp_rules/maclist
Storage + CRUD (migration 0007, model, id-keyed store, REST handlers) + compiler rendering, each owned by a device. proxy_arp/proxy_ndp share the ProxyEntry shape via table-parameterized store helpers.
This commit is contained in:
@@ -0,0 +1,320 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
|
||||
"git.unkin.net/unkin/tomswallapi/internal/model"
|
||||
)
|
||||
|
||||
// ---- Tunnels ---------------------------------------------------------------
|
||||
|
||||
func (s *Store) ListTunnels(ctx context.Context) ([]model.Tunnel, error) {
|
||||
rows, err := s.pool.Query(ctx,
|
||||
`SELECT id, device, type, zone, gateways, gateway_zones, port, comment FROM tunnels ORDER BY id`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []model.Tunnel
|
||||
for rows.Next() {
|
||||
t, err := scanTunnel(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, t)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func (s *Store) GetTunnel(ctx context.Context, id int64) (model.Tunnel, error) {
|
||||
rows, err := s.pool.Query(ctx,
|
||||
`SELECT id, device, type, zone, gateways, gateway_zones, port, comment FROM tunnels WHERE id = $1`, id)
|
||||
if err != nil {
|
||||
return model.Tunnel{}, err
|
||||
}
|
||||
defer rows.Close()
|
||||
if !rows.Next() {
|
||||
return model.Tunnel{}, ErrNotFound
|
||||
}
|
||||
return scanTunnel(rows)
|
||||
}
|
||||
|
||||
func scanTunnel(rows pgx.Rows) (model.Tunnel, error) {
|
||||
var t model.Tunnel
|
||||
var gw, gz []byte
|
||||
if err := rows.Scan(&t.ID, &t.Device, &t.Type, &t.Zone, &gw, &gz, &t.Port, &t.Comment); err != nil {
|
||||
return t, err
|
||||
}
|
||||
if err := unmarshalStrings(gw, &t.Gateways); err != nil {
|
||||
return t, err
|
||||
}
|
||||
return t, unmarshalStrings(gz, &t.GatewayZones)
|
||||
}
|
||||
|
||||
func (s *Store) CreateTunnel(ctx context.Context, t model.Tunnel) (int64, error) {
|
||||
gw, _ := jsonb(t.Gateways)
|
||||
gz, _ := jsonb(t.GatewayZones)
|
||||
var id int64
|
||||
err := pgx.BeginFunc(ctx, s.pool, func(tx pgx.Tx) error {
|
||||
if err := tx.QueryRow(ctx, `
|
||||
INSERT INTO tunnels (device, type, zone, gateways, gateway_zones, port, comment)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id`,
|
||||
t.Device, t.Type, t.Zone, gw, gz, t.Port, t.Comment).Scan(&id); err != nil {
|
||||
return err
|
||||
}
|
||||
return bump(ctx, tx)
|
||||
})
|
||||
return id, err
|
||||
}
|
||||
|
||||
func (s *Store) DeleteTunnel(ctx context.Context, id int64) error {
|
||||
return s.deleteOne(ctx, `DELETE FROM tunnels WHERE id = $1`, id)
|
||||
}
|
||||
|
||||
// ---- Stopped rules ---------------------------------------------------------
|
||||
|
||||
func (s *Store) ListStoppedRules(ctx context.Context) ([]model.StoppedRule, error) {
|
||||
rows, err := s.pool.Query(ctx,
|
||||
`SELECT id, device, action, source, dest, proto, dport, sport, comment FROM stopped_rules ORDER BY id`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []model.StoppedRule
|
||||
for rows.Next() {
|
||||
r, err := scanStopped(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, r)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func (s *Store) GetStoppedRule(ctx context.Context, id int64) (model.StoppedRule, error) {
|
||||
rows, err := s.pool.Query(ctx,
|
||||
`SELECT id, device, action, source, dest, proto, dport, sport, comment FROM stopped_rules WHERE id = $1`, id)
|
||||
if err != nil {
|
||||
return model.StoppedRule{}, err
|
||||
}
|
||||
defer rows.Close()
|
||||
if !rows.Next() {
|
||||
return model.StoppedRule{}, ErrNotFound
|
||||
}
|
||||
return scanStopped(rows)
|
||||
}
|
||||
|
||||
func scanStopped(rows pgx.Rows) (model.StoppedRule, error) {
|
||||
var r model.StoppedRule
|
||||
var dport, sport []byte
|
||||
if err := rows.Scan(&r.ID, &r.Device, &r.Action, &r.Source, &r.Dest, &r.Proto, &dport, &sport, &r.Comment); err != nil {
|
||||
return r, err
|
||||
}
|
||||
if err := unmarshalStrings(dport, &r.DPort); err != nil {
|
||||
return r, err
|
||||
}
|
||||
return r, unmarshalStrings(sport, &r.SPort)
|
||||
}
|
||||
|
||||
func (s *Store) CreateStoppedRule(ctx context.Context, r model.StoppedRule) (int64, error) {
|
||||
dport, _ := jsonb(r.DPort)
|
||||
sport, _ := jsonb(r.SPort)
|
||||
var id int64
|
||||
err := pgx.BeginFunc(ctx, s.pool, func(tx pgx.Tx) error {
|
||||
if err := tx.QueryRow(ctx, `
|
||||
INSERT INTO stopped_rules (device, action, source, dest, proto, dport, sport, comment)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id`,
|
||||
r.Device, r.Action, r.Source, r.Dest, r.Proto, dport, sport, r.Comment).Scan(&id); err != nil {
|
||||
return err
|
||||
}
|
||||
return bump(ctx, tx)
|
||||
})
|
||||
return id, err
|
||||
}
|
||||
|
||||
func (s *Store) DeleteStoppedRule(ctx context.Context, id int64) error {
|
||||
return s.deleteOne(ctx, `DELETE FROM stopped_rules WHERE id = $1`, id)
|
||||
}
|
||||
|
||||
// ---- Proxy ARP / NDP (identical shape, table-parameterized) ----------------
|
||||
|
||||
func (s *Store) listProxy(ctx context.Context, table string) ([]model.ProxyEntry, error) {
|
||||
q := fmt.Sprintf(`SELECT id, device, address, interface, external, haveroute, persistent, comment FROM %s ORDER BY id`, table)
|
||||
rows, err := s.pool.Query(ctx, q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []model.ProxyEntry
|
||||
for rows.Next() {
|
||||
var p model.ProxyEntry
|
||||
if err := rows.Scan(&p.ID, &p.Device, &p.Address, &p.Interface, &p.External, &p.HaveRoute, &p.Persistent, &p.Comment); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, p)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func (s *Store) getProxy(ctx context.Context, table string, id int64) (model.ProxyEntry, error) {
|
||||
q := fmt.Sprintf(`SELECT id, device, address, interface, external, haveroute, persistent, comment FROM %s WHERE id = $1`, table)
|
||||
var p model.ProxyEntry
|
||||
err := s.pool.QueryRow(ctx, q, id).Scan(&p.ID, &p.Device, &p.Address, &p.Interface, &p.External, &p.HaveRoute, &p.Persistent, &p.Comment)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return p, ErrNotFound
|
||||
}
|
||||
return p, err
|
||||
}
|
||||
|
||||
func (s *Store) createProxy(ctx context.Context, table string, p model.ProxyEntry) (int64, error) {
|
||||
q := fmt.Sprintf(`INSERT INTO %s (device, address, interface, external, haveroute, persistent, comment)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id`, table)
|
||||
var id int64
|
||||
err := pgx.BeginFunc(ctx, s.pool, func(tx pgx.Tx) error {
|
||||
if err := tx.QueryRow(ctx, q, p.Device, p.Address, p.Interface, p.External, p.HaveRoute, p.Persistent, p.Comment).Scan(&id); err != nil {
|
||||
return err
|
||||
}
|
||||
return bump(ctx, tx)
|
||||
})
|
||||
return id, err
|
||||
}
|
||||
|
||||
func (s *Store) ListProxyARP(ctx context.Context) ([]model.ProxyEntry, error) {
|
||||
return s.listProxy(ctx, "proxy_arp")
|
||||
}
|
||||
func (s *Store) GetProxyARP(ctx context.Context, id int64) (model.ProxyEntry, error) {
|
||||
return s.getProxy(ctx, "proxy_arp", id)
|
||||
}
|
||||
func (s *Store) CreateProxyARP(ctx context.Context, p model.ProxyEntry) (int64, error) {
|
||||
return s.createProxy(ctx, "proxy_arp", p)
|
||||
}
|
||||
func (s *Store) DeleteProxyARP(ctx context.Context, id int64) error {
|
||||
return s.deleteOne(ctx, `DELETE FROM proxy_arp WHERE id = $1`, id)
|
||||
}
|
||||
|
||||
func (s *Store) ListProxyNDP(ctx context.Context) ([]model.ProxyEntry, error) {
|
||||
return s.listProxy(ctx, "proxy_ndp")
|
||||
}
|
||||
func (s *Store) GetProxyNDP(ctx context.Context, id int64) (model.ProxyEntry, error) {
|
||||
return s.getProxy(ctx, "proxy_ndp", id)
|
||||
}
|
||||
func (s *Store) CreateProxyNDP(ctx context.Context, p model.ProxyEntry) (int64, error) {
|
||||
return s.createProxy(ctx, "proxy_ndp", p)
|
||||
}
|
||||
func (s *Store) DeleteProxyNDP(ctx context.Context, id int64) error {
|
||||
return s.deleteOne(ctx, `DELETE FROM proxy_ndp WHERE id = $1`, id)
|
||||
}
|
||||
|
||||
// ---- ARP rules -------------------------------------------------------------
|
||||
|
||||
func (s *Store) ListArpRules(ctx context.Context) ([]model.ArpRule, error) {
|
||||
rows, err := s.pool.Query(ctx,
|
||||
`SELECT id, device, action, action_address, action_mac, source, dest, opcode, comment FROM arp_rules ORDER BY id`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []model.ArpRule
|
||||
for rows.Next() {
|
||||
var r model.ArpRule
|
||||
if err := rows.Scan(&r.ID, &r.Device, &r.Action, &r.ActionAddress, &r.ActionMAC, &r.Source, &r.Dest, &r.Opcode, &r.Comment); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, r)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func (s *Store) GetArpRule(ctx context.Context, id int64) (model.ArpRule, error) {
|
||||
var r model.ArpRule
|
||||
err := s.pool.QueryRow(ctx,
|
||||
`SELECT id, device, action, action_address, action_mac, source, dest, opcode, comment FROM arp_rules WHERE id = $1`, id,
|
||||
).Scan(&r.ID, &r.Device, &r.Action, &r.ActionAddress, &r.ActionMAC, &r.Source, &r.Dest, &r.Opcode, &r.Comment)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return r, ErrNotFound
|
||||
}
|
||||
return r, err
|
||||
}
|
||||
|
||||
func (s *Store) CreateArpRule(ctx context.Context, r model.ArpRule) (int64, error) {
|
||||
var id int64
|
||||
err := pgx.BeginFunc(ctx, s.pool, func(tx pgx.Tx) error {
|
||||
if err := tx.QueryRow(ctx, `
|
||||
INSERT INTO arp_rules (device, action, action_address, action_mac, source, dest, opcode, comment)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id`,
|
||||
r.Device, r.Action, r.ActionAddress, r.ActionMAC, r.Source, r.Dest, r.Opcode, r.Comment).Scan(&id); err != nil {
|
||||
return err
|
||||
}
|
||||
return bump(ctx, tx)
|
||||
})
|
||||
return id, err
|
||||
}
|
||||
|
||||
func (s *Store) DeleteArpRule(ctx context.Context, id int64) error {
|
||||
return s.deleteOne(ctx, `DELETE FROM arp_rules WHERE id = $1`, id)
|
||||
}
|
||||
|
||||
// ---- Maclist ---------------------------------------------------------------
|
||||
|
||||
func (s *Store) ListMaclist(ctx context.Context) ([]model.MaclistEntry, error) {
|
||||
rows, err := s.pool.Query(ctx,
|
||||
`SELECT id, device, action, interface, mac, addresses, log, comment FROM maclist ORDER BY id`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []model.MaclistEntry
|
||||
for rows.Next() {
|
||||
m, err := scanMaclist(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, m)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func (s *Store) GetMaclist(ctx context.Context, id int64) (model.MaclistEntry, error) {
|
||||
rows, err := s.pool.Query(ctx,
|
||||
`SELECT id, device, action, interface, mac, addresses, log, comment FROM maclist WHERE id = $1`, id)
|
||||
if err != nil {
|
||||
return model.MaclistEntry{}, err
|
||||
}
|
||||
defer rows.Close()
|
||||
if !rows.Next() {
|
||||
return model.MaclistEntry{}, ErrNotFound
|
||||
}
|
||||
return scanMaclist(rows)
|
||||
}
|
||||
|
||||
func scanMaclist(rows pgx.Rows) (model.MaclistEntry, error) {
|
||||
var m model.MaclistEntry
|
||||
var addrs []byte
|
||||
if err := rows.Scan(&m.ID, &m.Device, &m.Action, &m.Interface, &m.MAC, &addrs, &m.Log, &m.Comment); err != nil {
|
||||
return m, err
|
||||
}
|
||||
return m, unmarshalStrings(addrs, &m.Addresses)
|
||||
}
|
||||
|
||||
func (s *Store) CreateMaclist(ctx context.Context, m model.MaclistEntry) (int64, error) {
|
||||
addrs, _ := jsonb(m.Addresses)
|
||||
var id int64
|
||||
err := pgx.BeginFunc(ctx, s.pool, func(tx pgx.Tx) error {
|
||||
if err := tx.QueryRow(ctx, `
|
||||
INSERT INTO maclist (device, action, interface, mac, addresses, log, comment)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id`,
|
||||
m.Device, m.Action, m.Interface, m.MAC, addrs, m.Log, m.Comment).Scan(&id); err != nil {
|
||||
return err
|
||||
}
|
||||
return bump(ctx, tx)
|
||||
})
|
||||
return id, err
|
||||
}
|
||||
|
||||
func (s *Store) DeleteMaclist(ctx context.Context, id int64) error {
|
||||
return s.deleteOne(ctx, `DELETE FROM maclist WHERE id = $1`, id)
|
||||
}
|
||||
Reference in New Issue
Block a user