d9d192757b
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.
321 lines
10 KiB
Go
321 lines
10 KiB
Go
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)
|
|
}
|