Add traffic-control long-tail: mangle/accounting/tc_*
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.)
This commit is contained in:
@@ -0,0 +1,424 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user