6b600f8c8d
Storage + CRUD (migration 0008, model, id-keyed store, REST handlers) + compiler rendering for mangle, accounting, and tc_devices/tc_classes/tc_filters/ tc_interfaces/tc_priorities, each owned by a device. (Nested tc option structs deferred.)
425 lines
14 KiB
Go
425 lines
14 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
|
|
"git.unkin.net/unkin/tomswallapi/internal/model"
|
|
)
|
|
|
|
// ---- Mangle ----------------------------------------------------------------
|
|
|
|
func (s *Store) ListMangle(ctx context.Context) ([]model.MangleRule, error) {
|
|
rows, err := s.pool.Query(ctx, `
|
|
SELECT id, device, action, chain, mark_value, source, dest, proto, dport, sport,
|
|
"user", mark, length, tos, helper, probability, comment
|
|
FROM mangle ORDER BY id`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var out []model.MangleRule
|
|
for rows.Next() {
|
|
m, err := scanMangle(rows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, m)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
func (s *Store) GetMangle(ctx context.Context, id int64) (model.MangleRule, error) {
|
|
rows, err := s.pool.Query(ctx, `
|
|
SELECT id, device, action, chain, mark_value, source, dest, proto, dport, sport,
|
|
"user", mark, length, tos, helper, probability, comment
|
|
FROM mangle WHERE id = $1`, id)
|
|
if err != nil {
|
|
return model.MangleRule{}, err
|
|
}
|
|
defer rows.Close()
|
|
if !rows.Next() {
|
|
return model.MangleRule{}, ErrNotFound
|
|
}
|
|
return scanMangle(rows)
|
|
}
|
|
|
|
func scanMangle(rows pgx.Rows) (model.MangleRule, error) {
|
|
var m model.MangleRule
|
|
var dport, sport []byte
|
|
if err := rows.Scan(&m.ID, &m.Device, &m.Action, &m.Chain, &m.MarkValue, &m.Source, &m.Dest, &m.Proto,
|
|
&dport, &sport, &m.User, &m.Mark, &m.Length, &m.TOS, &m.Helper, &m.Probability, &m.Comment); err != nil {
|
|
return m, err
|
|
}
|
|
if err := unmarshalStrings(dport, &m.DPort); err != nil {
|
|
return m, err
|
|
}
|
|
return m, unmarshalStrings(sport, &m.SPort)
|
|
}
|
|
|
|
func (s *Store) CreateMangle(ctx context.Context, m model.MangleRule) (int64, error) {
|
|
dport, _ := jsonb(m.DPort)
|
|
sport, _ := jsonb(m.SPort)
|
|
var id int64
|
|
err := pgx.BeginFunc(ctx, s.pool, func(tx pgx.Tx) error {
|
|
if err := tx.QueryRow(ctx, `
|
|
INSERT INTO mangle (device, action, chain, mark_value, source, dest, proto, dport, sport,
|
|
"user", mark, length, tos, helper, probability, comment)
|
|
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16) RETURNING id`,
|
|
m.Device, m.Action, m.Chain, m.MarkValue, m.Source, m.Dest, m.Proto, dport, sport,
|
|
m.User, m.Mark, m.Length, m.TOS, m.Helper, m.Probability, m.Comment).Scan(&id); err != nil {
|
|
return err
|
|
}
|
|
return bump(ctx, tx)
|
|
})
|
|
return id, err
|
|
}
|
|
|
|
func (s *Store) DeleteMangle(ctx context.Context, id int64) error {
|
|
return s.deleteOne(ctx, `DELETE FROM mangle WHERE id = $1`, id)
|
|
}
|
|
|
|
// ---- Accounting ------------------------------------------------------------
|
|
|
|
func (s *Store) ListAccounting(ctx context.Context) ([]model.AccountingRule, error) {
|
|
rows, err := s.pool.Query(ctx,
|
|
`SELECT id, device, action, section, chain, source, dest, proto, dport, sport, mark, comment FROM accounting ORDER BY id`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var out []model.AccountingRule
|
|
for rows.Next() {
|
|
a, err := scanAccounting(rows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, a)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
func (s *Store) GetAccounting(ctx context.Context, id int64) (model.AccountingRule, error) {
|
|
rows, err := s.pool.Query(ctx,
|
|
`SELECT id, device, action, section, chain, source, dest, proto, dport, sport, mark, comment FROM accounting WHERE id = $1`, id)
|
|
if err != nil {
|
|
return model.AccountingRule{}, err
|
|
}
|
|
defer rows.Close()
|
|
if !rows.Next() {
|
|
return model.AccountingRule{}, ErrNotFound
|
|
}
|
|
return scanAccounting(rows)
|
|
}
|
|
|
|
func scanAccounting(rows pgx.Rows) (model.AccountingRule, error) {
|
|
var a model.AccountingRule
|
|
var dport, sport []byte
|
|
if err := rows.Scan(&a.ID, &a.Device, &a.Action, &a.Section, &a.Chain, &a.Source, &a.Dest, &a.Proto, &dport, &sport, &a.Mark, &a.Comment); err != nil {
|
|
return a, err
|
|
}
|
|
if err := unmarshalStrings(dport, &a.DPort); err != nil {
|
|
return a, err
|
|
}
|
|
return a, unmarshalStrings(sport, &a.SPort)
|
|
}
|
|
|
|
func (s *Store) CreateAccounting(ctx context.Context, a model.AccountingRule) (int64, error) {
|
|
dport, _ := jsonb(a.DPort)
|
|
sport, _ := jsonb(a.SPort)
|
|
var id int64
|
|
err := pgx.BeginFunc(ctx, s.pool, func(tx pgx.Tx) error {
|
|
if err := tx.QueryRow(ctx, `
|
|
INSERT INTO accounting (device, action, section, chain, source, dest, proto, dport, sport, mark, comment)
|
|
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING id`,
|
|
a.Device, a.Action, a.Section, a.Chain, a.Source, a.Dest, a.Proto, dport, sport, a.Mark, a.Comment).Scan(&id); err != nil {
|
|
return err
|
|
}
|
|
return bump(ctx, tx)
|
|
})
|
|
return id, err
|
|
}
|
|
|
|
func (s *Store) DeleteAccounting(ctx context.Context, id int64) error {
|
|
return s.deleteOne(ctx, `DELETE FROM accounting WHERE id = $1`, id)
|
|
}
|
|
|
|
// ---- TC devices ------------------------------------------------------------
|
|
|
|
func (s *Store) ListTCDevices(ctx context.Context) ([]model.TCDevice, error) {
|
|
rows, err := s.pool.Query(ctx,
|
|
`SELECT id, device, interface, in_bandwidth, out_bandwidth, comment FROM tc_devices ORDER BY id`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var out []model.TCDevice
|
|
for rows.Next() {
|
|
var t model.TCDevice
|
|
if err := rows.Scan(&t.ID, &t.Device, &t.Interface, &t.InBandwidth, &t.OutBandwidth, &t.Comment); err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, t)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
func (s *Store) GetTCDevice(ctx context.Context, id int64) (model.TCDevice, error) {
|
|
var t model.TCDevice
|
|
err := s.pool.QueryRow(ctx,
|
|
`SELECT id, device, interface, in_bandwidth, out_bandwidth, comment FROM tc_devices WHERE id = $1`, id,
|
|
).Scan(&t.ID, &t.Device, &t.Interface, &t.InBandwidth, &t.OutBandwidth, &t.Comment)
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return t, ErrNotFound
|
|
}
|
|
return t, err
|
|
}
|
|
|
|
func (s *Store) CreateTCDevice(ctx context.Context, t model.TCDevice) (int64, error) {
|
|
var id int64
|
|
err := pgx.BeginFunc(ctx, s.pool, func(tx pgx.Tx) error {
|
|
if err := tx.QueryRow(ctx, `
|
|
INSERT INTO tc_devices (device, interface, in_bandwidth, out_bandwidth, comment)
|
|
VALUES ($1,$2,$3,$4,$5) RETURNING id`,
|
|
t.Device, t.Interface, t.InBandwidth, t.OutBandwidth, t.Comment).Scan(&id); err != nil {
|
|
return err
|
|
}
|
|
return bump(ctx, tx)
|
|
})
|
|
return id, err
|
|
}
|
|
|
|
func (s *Store) DeleteTCDevice(ctx context.Context, id int64) error {
|
|
return s.deleteOne(ctx, `DELETE FROM tc_devices WHERE id = $1`, id)
|
|
}
|
|
|
|
// ---- TC classes ------------------------------------------------------------
|
|
|
|
func (s *Store) ListTCClasses(ctx context.Context) ([]model.TCClass, error) {
|
|
rows, err := s.pool.Query(ctx,
|
|
`SELECT id, device, interface, mark, rate, ceil, priority, comment FROM tc_classes ORDER BY id`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var out []model.TCClass
|
|
for rows.Next() {
|
|
var t model.TCClass
|
|
if err := rows.Scan(&t.ID, &t.Device, &t.Interface, &t.Mark, &t.Rate, &t.Ceil, &t.Priority, &t.Comment); err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, t)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
func (s *Store) GetTCClass(ctx context.Context, id int64) (model.TCClass, error) {
|
|
var t model.TCClass
|
|
err := s.pool.QueryRow(ctx,
|
|
`SELECT id, device, interface, mark, rate, ceil, priority, comment FROM tc_classes WHERE id = $1`, id,
|
|
).Scan(&t.ID, &t.Device, &t.Interface, &t.Mark, &t.Rate, &t.Ceil, &t.Priority, &t.Comment)
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return t, ErrNotFound
|
|
}
|
|
return t, err
|
|
}
|
|
|
|
func (s *Store) CreateTCClass(ctx context.Context, t model.TCClass) (int64, error) {
|
|
var id int64
|
|
err := pgx.BeginFunc(ctx, s.pool, func(tx pgx.Tx) error {
|
|
if err := tx.QueryRow(ctx, `
|
|
INSERT INTO tc_classes (device, interface, mark, rate, ceil, priority, comment)
|
|
VALUES ($1,$2,$3,$4,$5,$6,$7) RETURNING id`,
|
|
t.Device, t.Interface, t.Mark, t.Rate, t.Ceil, t.Priority, t.Comment).Scan(&id); err != nil {
|
|
return err
|
|
}
|
|
return bump(ctx, tx)
|
|
})
|
|
return id, err
|
|
}
|
|
|
|
func (s *Store) DeleteTCClass(ctx context.Context, id int64) error {
|
|
return s.deleteOne(ctx, `DELETE FROM tc_classes WHERE id = $1`, id)
|
|
}
|
|
|
|
// ---- TC filters ------------------------------------------------------------
|
|
|
|
func (s *Store) ListTCFilters(ctx context.Context) ([]model.TCFilter, error) {
|
|
rows, err := s.pool.Query(ctx,
|
|
`SELECT id, device, class, source, dest, proto, dport, sport, tos, length, priority, comment FROM tc_filters ORDER BY id`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var out []model.TCFilter
|
|
for rows.Next() {
|
|
f, err := scanTCFilter(rows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, f)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
func (s *Store) GetTCFilter(ctx context.Context, id int64) (model.TCFilter, error) {
|
|
rows, err := s.pool.Query(ctx,
|
|
`SELECT id, device, class, source, dest, proto, dport, sport, tos, length, priority, comment FROM tc_filters WHERE id = $1`, id)
|
|
if err != nil {
|
|
return model.TCFilter{}, err
|
|
}
|
|
defer rows.Close()
|
|
if !rows.Next() {
|
|
return model.TCFilter{}, ErrNotFound
|
|
}
|
|
return scanTCFilter(rows)
|
|
}
|
|
|
|
func scanTCFilter(rows pgx.Rows) (model.TCFilter, error) {
|
|
var f model.TCFilter
|
|
var dport, sport []byte
|
|
if err := rows.Scan(&f.ID, &f.Device, &f.Class, &f.Source, &f.Dest, &f.Proto, &dport, &sport, &f.TOS, &f.Length, &f.Priority, &f.Comment); err != nil {
|
|
return f, err
|
|
}
|
|
if err := unmarshalStrings(dport, &f.DPort); err != nil {
|
|
return f, err
|
|
}
|
|
return f, unmarshalStrings(sport, &f.SPort)
|
|
}
|
|
|
|
func (s *Store) CreateTCFilter(ctx context.Context, f model.TCFilter) (int64, error) {
|
|
dport, _ := jsonb(f.DPort)
|
|
sport, _ := jsonb(f.SPort)
|
|
var id int64
|
|
err := pgx.BeginFunc(ctx, s.pool, func(tx pgx.Tx) error {
|
|
if err := tx.QueryRow(ctx, `
|
|
INSERT INTO tc_filters (device, class, source, dest, proto, dport, sport, tos, length, priority, comment)
|
|
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING id`,
|
|
f.Device, f.Class, f.Source, f.Dest, f.Proto, dport, sport, f.TOS, f.Length, f.Priority, f.Comment).Scan(&id); err != nil {
|
|
return err
|
|
}
|
|
return bump(ctx, tx)
|
|
})
|
|
return id, err
|
|
}
|
|
|
|
func (s *Store) DeleteTCFilter(ctx context.Context, id int64) error {
|
|
return s.deleteOne(ctx, `DELETE FROM tc_filters WHERE id = $1`, id)
|
|
}
|
|
|
|
// ---- TC interfaces ---------------------------------------------------------
|
|
|
|
func (s *Store) ListTCInterfaces(ctx context.Context) ([]model.TCInterface, error) {
|
|
rows, err := s.pool.Query(ctx,
|
|
`SELECT id, device, interface, type, in_bandwidth, out_bandwidth, comment FROM tc_interfaces ORDER BY id`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var out []model.TCInterface
|
|
for rows.Next() {
|
|
var t model.TCInterface
|
|
if err := rows.Scan(&t.ID, &t.Device, &t.Interface, &t.Type, &t.InBandwidth, &t.OutBandwidth, &t.Comment); err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, t)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
func (s *Store) GetTCInterface(ctx context.Context, id int64) (model.TCInterface, error) {
|
|
var t model.TCInterface
|
|
err := s.pool.QueryRow(ctx,
|
|
`SELECT id, device, interface, type, in_bandwidth, out_bandwidth, comment FROM tc_interfaces WHERE id = $1`, id,
|
|
).Scan(&t.ID, &t.Device, &t.Interface, &t.Type, &t.InBandwidth, &t.OutBandwidth, &t.Comment)
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return t, ErrNotFound
|
|
}
|
|
return t, err
|
|
}
|
|
|
|
func (s *Store) CreateTCInterface(ctx context.Context, t model.TCInterface) (int64, error) {
|
|
var id int64
|
|
err := pgx.BeginFunc(ctx, s.pool, func(tx pgx.Tx) error {
|
|
if err := tx.QueryRow(ctx, `
|
|
INSERT INTO tc_interfaces (device, interface, type, in_bandwidth, out_bandwidth, comment)
|
|
VALUES ($1,$2,$3,$4,$5,$6) RETURNING id`,
|
|
t.Device, t.Interface, t.Type, t.InBandwidth, t.OutBandwidth, t.Comment).Scan(&id); err != nil {
|
|
return err
|
|
}
|
|
return bump(ctx, tx)
|
|
})
|
|
return id, err
|
|
}
|
|
|
|
func (s *Store) DeleteTCInterface(ctx context.Context, id int64) error {
|
|
return s.deleteOne(ctx, `DELETE FROM tc_interfaces WHERE id = $1`, id)
|
|
}
|
|
|
|
// ---- TC priorities ---------------------------------------------------------
|
|
|
|
func (s *Store) ListTCPriorities(ctx context.Context) ([]model.TCPriority, error) {
|
|
rows, err := s.pool.Query(ctx,
|
|
`SELECT id, device, band, proto, dport, sport, address, interface, helper, comment FROM tc_priorities ORDER BY id`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var out []model.TCPriority
|
|
for rows.Next() {
|
|
p, err := scanTCPriority(rows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, p)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
func (s *Store) GetTCPriority(ctx context.Context, id int64) (model.TCPriority, error) {
|
|
rows, err := s.pool.Query(ctx,
|
|
`SELECT id, device, band, proto, dport, sport, address, interface, helper, comment FROM tc_priorities WHERE id = $1`, id)
|
|
if err != nil {
|
|
return model.TCPriority{}, err
|
|
}
|
|
defer rows.Close()
|
|
if !rows.Next() {
|
|
return model.TCPriority{}, ErrNotFound
|
|
}
|
|
return scanTCPriority(rows)
|
|
}
|
|
|
|
func scanTCPriority(rows pgx.Rows) (model.TCPriority, error) {
|
|
var p model.TCPriority
|
|
var dport, sport []byte
|
|
if err := rows.Scan(&p.ID, &p.Device, &p.Band, &p.Proto, &dport, &sport, &p.Address, &p.Interface, &p.Helper, &p.Comment); err != nil {
|
|
return p, err
|
|
}
|
|
if err := unmarshalStrings(dport, &p.DPort); err != nil {
|
|
return p, err
|
|
}
|
|
return p, unmarshalStrings(sport, &p.SPort)
|
|
}
|
|
|
|
func (s *Store) CreateTCPriority(ctx context.Context, p model.TCPriority) (int64, error) {
|
|
dport, _ := jsonb(p.DPort)
|
|
sport, _ := jsonb(p.SPort)
|
|
var id int64
|
|
err := pgx.BeginFunc(ctx, s.pool, func(tx pgx.Tx) error {
|
|
if err := tx.QueryRow(ctx, `
|
|
INSERT INTO tc_priorities (device, band, proto, dport, sport, address, interface, helper, comment)
|
|
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9) RETURNING id`,
|
|
p.Device, p.Band, p.Proto, dport, sport, p.Address, p.Interface, p.Helper, p.Comment).Scan(&id); err != nil {
|
|
return err
|
|
}
|
|
return bump(ctx, tx)
|
|
})
|
|
return id, err
|
|
}
|
|
|
|
func (s *Store) DeleteTCPriority(ctx context.Context, id int64) error {
|
|
return s.deleteOne(ctx, `DELETE FROM tc_priorities WHERE id = $1`, id)
|
|
}
|