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:
@@ -45,6 +45,13 @@ type Input struct {
|
||||
ProxyNDP []model.ProxyEntry
|
||||
ArpRules []model.ArpRule
|
||||
Maclist []model.MaclistEntry
|
||||
Mangle []model.MangleRule
|
||||
Accounting []model.AccountingRule
|
||||
TCDevices []model.TCDevice
|
||||
TCClasses []model.TCClass
|
||||
TCFilters []model.TCFilter
|
||||
TCInterfaces []model.TCInterface
|
||||
TCPriorities []model.TCPriority
|
||||
}
|
||||
|
||||
// RenderedConfig is the per-device output served to the agent.
|
||||
@@ -72,6 +79,13 @@ type RenderedConfig struct {
|
||||
ProxyNDP []RenderedProxy `yaml:"proxy_ndp,omitempty" json:"proxy_ndp,omitempty"`
|
||||
ArpRules []RenderedArpRule `yaml:"arp_rules,omitempty" json:"arp_rules,omitempty"`
|
||||
Maclist []RenderedMaclist `yaml:"maclist,omitempty" json:"maclist,omitempty"`
|
||||
Mangle []RenderedMangle `yaml:"mangle,omitempty" json:"mangle,omitempty"`
|
||||
Accounting []RenderedAccounting `yaml:"accounting,omitempty" json:"accounting,omitempty"`
|
||||
TCDevices []RenderedTCDevice `yaml:"tc_devices,omitempty" json:"tc_devices,omitempty"`
|
||||
TCClasses []RenderedTCClass `yaml:"tc_classes,omitempty" json:"tc_classes,omitempty"`
|
||||
TCFilters []RenderedTCFilter `yaml:"tc_filters,omitempty" json:"tc_filters,omitempty"`
|
||||
TCInterfaces []RenderedTCInterface `yaml:"tc_interfaces,omitempty" json:"tc_interfaces,omitempty"`
|
||||
TCPriorities []RenderedTCPriority `yaml:"tc_priorities,omitempty" json:"tc_priorities,omitempty"`
|
||||
}
|
||||
|
||||
// RenderedSNAT is a resolved SNAT/masquerade rule: source addresses masqueraded
|
||||
@@ -220,6 +234,7 @@ func Render(in Input) (*RenderedConfig, error) {
|
||||
// Per-device long-tail sections owned by this device.
|
||||
renderPerDevice(in, out)
|
||||
renderPerDeviceL2(in, out)
|
||||
renderTraffic(in, out)
|
||||
|
||||
return out, nil
|
||||
}
|
||||
@@ -480,5 +495,26 @@ func Compile(ctx context.Context, s *store.Store, device string) (*RenderedConfi
|
||||
if in.Maclist, err = s.ListMaclist(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if in.Mangle, err = s.ListMangle(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if in.Accounting, err = s.ListAccounting(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if in.TCDevices, err = s.ListTCDevices(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if in.TCClasses, err = s.ListTCClasses(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if in.TCFilters, err = s.ListTCFilters(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if in.TCInterfaces, err = s.ListTCInterfaces(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if in.TCPriorities, err = s.ListTCPriorities(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return Render(in)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
package compiler
|
||||
|
||||
// Traffic-control tier rendered into a device's config.
|
||||
|
||||
type RenderedMangle struct {
|
||||
Action string `yaml:"action" json:"action"`
|
||||
Chain string `yaml:"chain,omitempty" json:"chain,omitempty"`
|
||||
MarkValue string `yaml:"mark_value,omitempty" json:"mark_value,omitempty"`
|
||||
Source string `yaml:"source,omitempty" json:"source,omitempty"`
|
||||
Dest string `yaml:"dest,omitempty" json:"dest,omitempty"`
|
||||
Proto string `yaml:"proto,omitempty" json:"proto,omitempty"`
|
||||
DPort []string `yaml:"dport,omitempty" json:"dport,omitempty"`
|
||||
SPort []string `yaml:"sport,omitempty" json:"sport,omitempty"`
|
||||
User string `yaml:"user,omitempty" json:"user,omitempty"`
|
||||
Mark string `yaml:"mark,omitempty" json:"mark,omitempty"`
|
||||
Length string `yaml:"length,omitempty" json:"length,omitempty"`
|
||||
TOS string `yaml:"tos,omitempty" json:"tos,omitempty"`
|
||||
Helper string `yaml:"helper,omitempty" json:"helper,omitempty"`
|
||||
Probability *float64 `yaml:"probability,omitempty" json:"probability,omitempty"`
|
||||
Comment string `yaml:"comment,omitempty" json:"comment,omitempty"`
|
||||
}
|
||||
|
||||
type RenderedAccounting struct {
|
||||
Action string `yaml:"action" json:"action"`
|
||||
Section string `yaml:"section,omitempty" json:"section,omitempty"`
|
||||
Chain string `yaml:"chain,omitempty" json:"chain,omitempty"`
|
||||
Source string `yaml:"source,omitempty" json:"source,omitempty"`
|
||||
Dest string `yaml:"dest,omitempty" json:"dest,omitempty"`
|
||||
Proto string `yaml:"proto,omitempty" json:"proto,omitempty"`
|
||||
DPort []string `yaml:"dport,omitempty" json:"dport,omitempty"`
|
||||
SPort []string `yaml:"sport,omitempty" json:"sport,omitempty"`
|
||||
Mark string `yaml:"mark,omitempty" json:"mark,omitempty"`
|
||||
Comment string `yaml:"comment,omitempty" json:"comment,omitempty"`
|
||||
}
|
||||
|
||||
type RenderedTCDevice struct {
|
||||
Interface string `yaml:"interface" json:"interface"`
|
||||
InBandwidth string `yaml:"in_bandwidth,omitempty" json:"in_bandwidth,omitempty"`
|
||||
OutBandwidth string `yaml:"out_bandwidth,omitempty" json:"out_bandwidth,omitempty"`
|
||||
Comment string `yaml:"comment,omitempty" json:"comment,omitempty"`
|
||||
}
|
||||
|
||||
type RenderedTCClass struct {
|
||||
Interface string `yaml:"interface" json:"interface"`
|
||||
Mark int `yaml:"mark,omitempty" json:"mark,omitempty"`
|
||||
Rate string `yaml:"rate,omitempty" json:"rate,omitempty"`
|
||||
Ceil string `yaml:"ceil,omitempty" json:"ceil,omitempty"`
|
||||
Priority int `yaml:"priority,omitempty" json:"priority,omitempty"`
|
||||
Comment string `yaml:"comment,omitempty" json:"comment,omitempty"`
|
||||
}
|
||||
|
||||
type RenderedTCFilter struct {
|
||||
Class string `yaml:"class" json:"class"`
|
||||
Source string `yaml:"source,omitempty" json:"source,omitempty"`
|
||||
Dest string `yaml:"dest,omitempty" json:"dest,omitempty"`
|
||||
Proto string `yaml:"proto,omitempty" json:"proto,omitempty"`
|
||||
DPort []string `yaml:"dport,omitempty" json:"dport,omitempty"`
|
||||
SPort []string `yaml:"sport,omitempty" json:"sport,omitempty"`
|
||||
TOS string `yaml:"tos,omitempty" json:"tos,omitempty"`
|
||||
Length int `yaml:"length,omitempty" json:"length,omitempty"`
|
||||
Priority int `yaml:"priority,omitempty" json:"priority,omitempty"`
|
||||
Comment string `yaml:"comment,omitempty" json:"comment,omitempty"`
|
||||
}
|
||||
|
||||
type RenderedTCInterface struct {
|
||||
Interface string `yaml:"interface" json:"interface"`
|
||||
Type string `yaml:"type,omitempty" json:"type,omitempty"`
|
||||
InBandwidth string `yaml:"in_bandwidth,omitempty" json:"in_bandwidth,omitempty"`
|
||||
OutBandwidth string `yaml:"out_bandwidth,omitempty" json:"out_bandwidth,omitempty"`
|
||||
Comment string `yaml:"comment,omitempty" json:"comment,omitempty"`
|
||||
}
|
||||
|
||||
type RenderedTCPriority struct {
|
||||
Band int `yaml:"band" json:"band"`
|
||||
Proto string `yaml:"proto,omitempty" json:"proto,omitempty"`
|
||||
DPort []string `yaml:"dport,omitempty" json:"dport,omitempty"`
|
||||
SPort []string `yaml:"sport,omitempty" json:"sport,omitempty"`
|
||||
Address string `yaml:"address,omitempty" json:"address,omitempty"`
|
||||
Interface string `yaml:"interface,omitempty" json:"interface,omitempty"`
|
||||
Helper string `yaml:"helper,omitempty" json:"helper,omitempty"`
|
||||
Comment string `yaml:"comment,omitempty" json:"comment,omitempty"`
|
||||
}
|
||||
|
||||
func renderTraffic(in Input, out *RenderedConfig) {
|
||||
dev := in.Device.Name
|
||||
for _, m := range in.Mangle {
|
||||
if m.Device != dev {
|
||||
continue
|
||||
}
|
||||
out.Mangle = append(out.Mangle, RenderedMangle{
|
||||
Action: m.Action, Chain: m.Chain, MarkValue: m.MarkValue, Source: m.Source, Dest: m.Dest,
|
||||
Proto: m.Proto, DPort: m.DPort, SPort: m.SPort, User: m.User, Mark: m.Mark,
|
||||
Length: m.Length, TOS: m.TOS, Helper: m.Helper, Probability: m.Probability, Comment: m.Comment,
|
||||
})
|
||||
}
|
||||
for _, a := range in.Accounting {
|
||||
if a.Device != dev {
|
||||
continue
|
||||
}
|
||||
out.Accounting = append(out.Accounting, RenderedAccounting{
|
||||
Action: a.Action, Section: a.Section, Chain: a.Chain, Source: a.Source, Dest: a.Dest,
|
||||
Proto: a.Proto, DPort: a.DPort, SPort: a.SPort, Mark: a.Mark, Comment: a.Comment,
|
||||
})
|
||||
}
|
||||
for _, t := range in.TCDevices {
|
||||
if t.Device != dev {
|
||||
continue
|
||||
}
|
||||
out.TCDevices = append(out.TCDevices, RenderedTCDevice{
|
||||
Interface: t.Interface, InBandwidth: t.InBandwidth, OutBandwidth: t.OutBandwidth, Comment: t.Comment,
|
||||
})
|
||||
}
|
||||
for _, t := range in.TCClasses {
|
||||
if t.Device != dev {
|
||||
continue
|
||||
}
|
||||
out.TCClasses = append(out.TCClasses, RenderedTCClass{
|
||||
Interface: t.Interface, Mark: t.Mark, Rate: t.Rate, Ceil: t.Ceil, Priority: t.Priority, Comment: t.Comment,
|
||||
})
|
||||
}
|
||||
for _, t := range in.TCFilters {
|
||||
if t.Device != dev {
|
||||
continue
|
||||
}
|
||||
out.TCFilters = append(out.TCFilters, RenderedTCFilter{
|
||||
Class: t.Class, Source: t.Source, Dest: t.Dest, Proto: t.Proto, DPort: t.DPort, SPort: t.SPort,
|
||||
TOS: t.TOS, Length: t.Length, Priority: t.Priority, Comment: t.Comment,
|
||||
})
|
||||
}
|
||||
for _, t := range in.TCInterfaces {
|
||||
if t.Device != dev {
|
||||
continue
|
||||
}
|
||||
out.TCInterfaces = append(out.TCInterfaces, RenderedTCInterface{
|
||||
Interface: t.Interface, Type: t.Type, InBandwidth: t.InBandwidth, OutBandwidth: t.OutBandwidth, Comment: t.Comment,
|
||||
})
|
||||
}
|
||||
for _, t := range in.TCPriorities {
|
||||
if t.Device != dev {
|
||||
continue
|
||||
}
|
||||
out.TCPriorities = append(out.TCPriorities, RenderedTCPriority{
|
||||
Band: t.Band, Proto: t.Proto, DPort: t.DPort, SPort: t.SPort,
|
||||
Address: t.Address, Interface: t.Interface, Helper: t.Helper, Comment: t.Comment,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
-- Traffic-control tier: mangle, accounting, and tc_* (device/class/filter/
|
||||
-- interface/priority). Each is owned by a device. (Nested tc option structs on
|
||||
-- tc_device/tc_class are deferred.)
|
||||
|
||||
CREATE TABLE mangle (
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
device TEXT NOT NULL REFERENCES devices(name) ON DELETE CASCADE,
|
||||
action TEXT NOT NULL,
|
||||
chain TEXT NOT NULL DEFAULT '',
|
||||
mark_value TEXT NOT NULL DEFAULT '',
|
||||
source TEXT NOT NULL DEFAULT '',
|
||||
dest TEXT NOT NULL DEFAULT '',
|
||||
proto TEXT NOT NULL DEFAULT '',
|
||||
dport JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
sport JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
"user" TEXT NOT NULL DEFAULT '',
|
||||
mark TEXT NOT NULL DEFAULT '',
|
||||
length TEXT NOT NULL DEFAULT '',
|
||||
tos TEXT NOT NULL DEFAULT '',
|
||||
helper TEXT NOT NULL DEFAULT '',
|
||||
probability REAL,
|
||||
comment TEXT NOT NULL DEFAULT ''
|
||||
);
|
||||
|
||||
CREATE TABLE accounting (
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
device TEXT NOT NULL REFERENCES devices(name) ON DELETE CASCADE,
|
||||
action TEXT NOT NULL,
|
||||
section TEXT NOT NULL DEFAULT '',
|
||||
chain TEXT NOT NULL DEFAULT '',
|
||||
source TEXT NOT NULL DEFAULT '',
|
||||
dest TEXT NOT NULL DEFAULT '',
|
||||
proto TEXT NOT NULL DEFAULT '',
|
||||
dport JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
sport JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
mark TEXT NOT NULL DEFAULT '',
|
||||
comment TEXT NOT NULL DEFAULT ''
|
||||
);
|
||||
|
||||
CREATE TABLE tc_devices (
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
device TEXT NOT NULL REFERENCES devices(name) ON DELETE CASCADE,
|
||||
interface TEXT NOT NULL,
|
||||
in_bandwidth TEXT NOT NULL DEFAULT '',
|
||||
out_bandwidth TEXT NOT NULL DEFAULT '',
|
||||
comment TEXT NOT NULL DEFAULT ''
|
||||
);
|
||||
|
||||
CREATE TABLE tc_classes (
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
device TEXT NOT NULL REFERENCES devices(name) ON DELETE CASCADE,
|
||||
interface TEXT NOT NULL,
|
||||
mark INT NOT NULL DEFAULT 0,
|
||||
rate TEXT NOT NULL DEFAULT '',
|
||||
ceil TEXT NOT NULL DEFAULT '',
|
||||
priority INT NOT NULL DEFAULT 0,
|
||||
comment TEXT NOT NULL DEFAULT ''
|
||||
);
|
||||
|
||||
CREATE TABLE tc_filters (
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
device TEXT NOT NULL REFERENCES devices(name) ON DELETE CASCADE,
|
||||
class TEXT NOT NULL,
|
||||
source TEXT NOT NULL DEFAULT '',
|
||||
dest TEXT NOT NULL DEFAULT '',
|
||||
proto TEXT NOT NULL DEFAULT '',
|
||||
dport JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
sport JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
tos TEXT NOT NULL DEFAULT '',
|
||||
length INT NOT NULL DEFAULT 0,
|
||||
priority INT NOT NULL DEFAULT 0,
|
||||
comment TEXT NOT NULL DEFAULT ''
|
||||
);
|
||||
|
||||
CREATE TABLE tc_interfaces (
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
device TEXT NOT NULL REFERENCES devices(name) ON DELETE CASCADE,
|
||||
interface TEXT NOT NULL,
|
||||
type TEXT NOT NULL DEFAULT '',
|
||||
in_bandwidth TEXT NOT NULL DEFAULT '',
|
||||
out_bandwidth TEXT NOT NULL DEFAULT '',
|
||||
comment TEXT NOT NULL DEFAULT ''
|
||||
);
|
||||
|
||||
CREATE TABLE tc_priorities (
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
device TEXT NOT NULL REFERENCES devices(name) ON DELETE CASCADE,
|
||||
band INT NOT NULL,
|
||||
proto TEXT NOT NULL DEFAULT '',
|
||||
dport JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
sport JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
address TEXT NOT NULL DEFAULT '',
|
||||
interface TEXT NOT NULL DEFAULT '',
|
||||
helper TEXT NOT NULL DEFAULT '',
|
||||
comment TEXT NOT NULL DEFAULT ''
|
||||
);
|
||||
@@ -0,0 +1,96 @@
|
||||
package model
|
||||
|
||||
// Traffic-control tier: mangle, accounting, and tc_* sections (per-device).
|
||||
|
||||
type MangleRule struct {
|
||||
ID int64 `json:"id"`
|
||||
Device string `json:"device"`
|
||||
Action string `json:"action"`
|
||||
Chain string `json:"chain,omitempty"`
|
||||
MarkValue string `json:"mark_value,omitempty"`
|
||||
Source string `json:"source,omitempty"`
|
||||
Dest string `json:"dest,omitempty"`
|
||||
Proto string `json:"proto,omitempty"`
|
||||
DPort []string `json:"dport,omitempty"`
|
||||
SPort []string `json:"sport,omitempty"`
|
||||
User string `json:"user,omitempty"`
|
||||
Mark string `json:"mark,omitempty"`
|
||||
Length string `json:"length,omitempty"`
|
||||
TOS string `json:"tos,omitempty"`
|
||||
Helper string `json:"helper,omitempty"`
|
||||
Probability *float64 `json:"probability,omitempty"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
}
|
||||
|
||||
type AccountingRule struct {
|
||||
ID int64 `json:"id"`
|
||||
Device string `json:"device"`
|
||||
Action string `json:"action"`
|
||||
Section string `json:"section,omitempty"`
|
||||
Chain string `json:"chain,omitempty"`
|
||||
Source string `json:"source,omitempty"`
|
||||
Dest string `json:"dest,omitempty"`
|
||||
Proto string `json:"proto,omitempty"`
|
||||
DPort []string `json:"dport,omitempty"`
|
||||
SPort []string `json:"sport,omitempty"`
|
||||
Mark string `json:"mark,omitempty"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
}
|
||||
|
||||
type TCDevice struct {
|
||||
ID int64 `json:"id"`
|
||||
Device string `json:"device"`
|
||||
Interface string `json:"interface"`
|
||||
InBandwidth string `json:"in_bandwidth,omitempty"`
|
||||
OutBandwidth string `json:"out_bandwidth,omitempty"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
}
|
||||
|
||||
type TCClass struct {
|
||||
ID int64 `json:"id"`
|
||||
Device string `json:"device"`
|
||||
Interface string `json:"interface"`
|
||||
Mark int `json:"mark,omitempty"`
|
||||
Rate string `json:"rate,omitempty"`
|
||||
Ceil string `json:"ceil,omitempty"`
|
||||
Priority int `json:"priority,omitempty"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
}
|
||||
|
||||
type TCFilter struct {
|
||||
ID int64 `json:"id"`
|
||||
Device string `json:"device"`
|
||||
Class string `json:"class"`
|
||||
Source string `json:"source,omitempty"`
|
||||
Dest string `json:"dest,omitempty"`
|
||||
Proto string `json:"proto,omitempty"`
|
||||
DPort []string `json:"dport,omitempty"`
|
||||
SPort []string `json:"sport,omitempty"`
|
||||
TOS string `json:"tos,omitempty"`
|
||||
Length int `json:"length,omitempty"`
|
||||
Priority int `json:"priority,omitempty"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
}
|
||||
|
||||
type TCInterface struct {
|
||||
ID int64 `json:"id"`
|
||||
Device string `json:"device"`
|
||||
Interface string `json:"interface"`
|
||||
Type string `json:"type,omitempty"`
|
||||
InBandwidth string `json:"in_bandwidth,omitempty"`
|
||||
OutBandwidth string `json:"out_bandwidth,omitempty"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
}
|
||||
|
||||
type TCPriority struct {
|
||||
ID int64 `json:"id"`
|
||||
Device string `json:"device"`
|
||||
Band int `json:"band"`
|
||||
Proto string `json:"proto,omitempty"`
|
||||
DPort []string `json:"dport,omitempty"`
|
||||
SPort []string `json:"sport,omitempty"`
|
||||
Address string `json:"address,omitempty"`
|
||||
Interface string `json:"interface,omitempty"`
|
||||
Helper string `json:"helper,omitempty"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
}
|
||||
@@ -63,6 +63,7 @@ func (s *Server) mountResources(r chi.Router) {
|
||||
s.mountLongtail(r)
|
||||
s.mountPerDevice(r)
|
||||
s.mountPerDeviceL2(r)
|
||||
s.mountTraffic(r)
|
||||
}
|
||||
|
||||
// respondOne writes a single resource, mapping ErrNotFound to 404.
|
||||
|
||||
@@ -0,0 +1,265 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"git.unkin.net/unkin/tomswallapi/internal/model"
|
||||
)
|
||||
|
||||
// mountTraffic wires the traffic-control tier: mangle, accounting, tc_*.
|
||||
func (s *Server) mountTraffic(r chi.Router) {
|
||||
idCRUD(r, "/mangle", s.listMangle, s.createMangle, s.getMangle, s.deleteMangle)
|
||||
idCRUD(r, "/accounting", s.listAccounting, s.createAccounting, s.getAccounting, s.deleteAccounting)
|
||||
idCRUD(r, "/tc-devices", s.listTCDevices, s.createTCDevice, s.getTCDevice, s.deleteTCDevice)
|
||||
idCRUD(r, "/tc-classes", s.listTCClasses, s.createTCClass, s.getTCClass, s.deleteTCClass)
|
||||
idCRUD(r, "/tc-filters", s.listTCFilters, s.createTCFilter, s.getTCFilter, s.deleteTCFilter)
|
||||
idCRUD(r, "/tc-interfaces", s.listTCInterfaces, s.createTCInterface, s.getTCInterface, s.deleteTCInterface)
|
||||
idCRUD(r, "/tc-priorities", s.listTCPriorities, s.createTCPriority, s.getTCPriority, s.deleteTCPriority)
|
||||
}
|
||||
|
||||
func (s *Server) listMangle(w http.ResponseWriter, r *http.Request) {
|
||||
list, err := s.store.ListMangle(r.Context())
|
||||
respondList(w, list, err)
|
||||
}
|
||||
func (s *Server) createMangle(w http.ResponseWriter, r *http.Request) {
|
||||
var v model.MangleRule
|
||||
if !decode(w, r, &v) {
|
||||
return
|
||||
}
|
||||
if v.Device == "" || v.Action == "" {
|
||||
writeError(w, http.StatusBadRequest, "device and action are required")
|
||||
return
|
||||
}
|
||||
id, err := s.store.CreateMangle(r.Context(), v)
|
||||
if err == nil {
|
||||
v.ID = id
|
||||
}
|
||||
respondCreated(w, v, err)
|
||||
}
|
||||
func (s *Server) getMangle(w http.ResponseWriter, r *http.Request) {
|
||||
id, ok := idParam(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
v, err := s.store.GetMangle(r.Context(), id)
|
||||
respondOne(w, v, err)
|
||||
}
|
||||
func (s *Server) deleteMangle(w http.ResponseWriter, r *http.Request) {
|
||||
id, ok := idParam(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
respondDelete(w, s.store.DeleteMangle(r.Context(), id))
|
||||
}
|
||||
|
||||
func (s *Server) listAccounting(w http.ResponseWriter, r *http.Request) {
|
||||
list, err := s.store.ListAccounting(r.Context())
|
||||
respondList(w, list, err)
|
||||
}
|
||||
func (s *Server) createAccounting(w http.ResponseWriter, r *http.Request) {
|
||||
var v model.AccountingRule
|
||||
if !decode(w, r, &v) {
|
||||
return
|
||||
}
|
||||
if v.Device == "" || v.Action == "" {
|
||||
writeError(w, http.StatusBadRequest, "device and action are required")
|
||||
return
|
||||
}
|
||||
id, err := s.store.CreateAccounting(r.Context(), v)
|
||||
if err == nil {
|
||||
v.ID = id
|
||||
}
|
||||
respondCreated(w, v, err)
|
||||
}
|
||||
func (s *Server) getAccounting(w http.ResponseWriter, r *http.Request) {
|
||||
id, ok := idParam(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
v, err := s.store.GetAccounting(r.Context(), id)
|
||||
respondOne(w, v, err)
|
||||
}
|
||||
func (s *Server) deleteAccounting(w http.ResponseWriter, r *http.Request) {
|
||||
id, ok := idParam(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
respondDelete(w, s.store.DeleteAccounting(r.Context(), id))
|
||||
}
|
||||
|
||||
func (s *Server) listTCDevices(w http.ResponseWriter, r *http.Request) {
|
||||
list, err := s.store.ListTCDevices(r.Context())
|
||||
respondList(w, list, err)
|
||||
}
|
||||
func (s *Server) createTCDevice(w http.ResponseWriter, r *http.Request) {
|
||||
var v model.TCDevice
|
||||
if !decode(w, r, &v) {
|
||||
return
|
||||
}
|
||||
if v.Device == "" || v.Interface == "" {
|
||||
writeError(w, http.StatusBadRequest, "device and interface are required")
|
||||
return
|
||||
}
|
||||
id, err := s.store.CreateTCDevice(r.Context(), v)
|
||||
if err == nil {
|
||||
v.ID = id
|
||||
}
|
||||
respondCreated(w, v, err)
|
||||
}
|
||||
func (s *Server) getTCDevice(w http.ResponseWriter, r *http.Request) {
|
||||
id, ok := idParam(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
v, err := s.store.GetTCDevice(r.Context(), id)
|
||||
respondOne(w, v, err)
|
||||
}
|
||||
func (s *Server) deleteTCDevice(w http.ResponseWriter, r *http.Request) {
|
||||
id, ok := idParam(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
respondDelete(w, s.store.DeleteTCDevice(r.Context(), id))
|
||||
}
|
||||
|
||||
func (s *Server) listTCClasses(w http.ResponseWriter, r *http.Request) {
|
||||
list, err := s.store.ListTCClasses(r.Context())
|
||||
respondList(w, list, err)
|
||||
}
|
||||
func (s *Server) createTCClass(w http.ResponseWriter, r *http.Request) {
|
||||
var v model.TCClass
|
||||
if !decode(w, r, &v) {
|
||||
return
|
||||
}
|
||||
if v.Device == "" || v.Interface == "" {
|
||||
writeError(w, http.StatusBadRequest, "device and interface are required")
|
||||
return
|
||||
}
|
||||
id, err := s.store.CreateTCClass(r.Context(), v)
|
||||
if err == nil {
|
||||
v.ID = id
|
||||
}
|
||||
respondCreated(w, v, err)
|
||||
}
|
||||
func (s *Server) getTCClass(w http.ResponseWriter, r *http.Request) {
|
||||
id, ok := idParam(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
v, err := s.store.GetTCClass(r.Context(), id)
|
||||
respondOne(w, v, err)
|
||||
}
|
||||
func (s *Server) deleteTCClass(w http.ResponseWriter, r *http.Request) {
|
||||
id, ok := idParam(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
respondDelete(w, s.store.DeleteTCClass(r.Context(), id))
|
||||
}
|
||||
|
||||
func (s *Server) listTCFilters(w http.ResponseWriter, r *http.Request) {
|
||||
list, err := s.store.ListTCFilters(r.Context())
|
||||
respondList(w, list, err)
|
||||
}
|
||||
func (s *Server) createTCFilter(w http.ResponseWriter, r *http.Request) {
|
||||
var v model.TCFilter
|
||||
if !decode(w, r, &v) {
|
||||
return
|
||||
}
|
||||
if v.Device == "" || v.Class == "" {
|
||||
writeError(w, http.StatusBadRequest, "device and class are required")
|
||||
return
|
||||
}
|
||||
id, err := s.store.CreateTCFilter(r.Context(), v)
|
||||
if err == nil {
|
||||
v.ID = id
|
||||
}
|
||||
respondCreated(w, v, err)
|
||||
}
|
||||
func (s *Server) getTCFilter(w http.ResponseWriter, r *http.Request) {
|
||||
id, ok := idParam(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
v, err := s.store.GetTCFilter(r.Context(), id)
|
||||
respondOne(w, v, err)
|
||||
}
|
||||
func (s *Server) deleteTCFilter(w http.ResponseWriter, r *http.Request) {
|
||||
id, ok := idParam(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
respondDelete(w, s.store.DeleteTCFilter(r.Context(), id))
|
||||
}
|
||||
|
||||
func (s *Server) listTCInterfaces(w http.ResponseWriter, r *http.Request) {
|
||||
list, err := s.store.ListTCInterfaces(r.Context())
|
||||
respondList(w, list, err)
|
||||
}
|
||||
func (s *Server) createTCInterface(w http.ResponseWriter, r *http.Request) {
|
||||
var v model.TCInterface
|
||||
if !decode(w, r, &v) {
|
||||
return
|
||||
}
|
||||
if v.Device == "" || v.Interface == "" {
|
||||
writeError(w, http.StatusBadRequest, "device and interface are required")
|
||||
return
|
||||
}
|
||||
id, err := s.store.CreateTCInterface(r.Context(), v)
|
||||
if err == nil {
|
||||
v.ID = id
|
||||
}
|
||||
respondCreated(w, v, err)
|
||||
}
|
||||
func (s *Server) getTCInterface(w http.ResponseWriter, r *http.Request) {
|
||||
id, ok := idParam(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
v, err := s.store.GetTCInterface(r.Context(), id)
|
||||
respondOne(w, v, err)
|
||||
}
|
||||
func (s *Server) deleteTCInterface(w http.ResponseWriter, r *http.Request) {
|
||||
id, ok := idParam(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
respondDelete(w, s.store.DeleteTCInterface(r.Context(), id))
|
||||
}
|
||||
|
||||
func (s *Server) listTCPriorities(w http.ResponseWriter, r *http.Request) {
|
||||
list, err := s.store.ListTCPriorities(r.Context())
|
||||
respondList(w, list, err)
|
||||
}
|
||||
func (s *Server) createTCPriority(w http.ResponseWriter, r *http.Request) {
|
||||
var v model.TCPriority
|
||||
if !decode(w, r, &v) {
|
||||
return
|
||||
}
|
||||
if v.Device == "" || v.Band == 0 {
|
||||
writeError(w, http.StatusBadRequest, "device and band are required")
|
||||
return
|
||||
}
|
||||
id, err := s.store.CreateTCPriority(r.Context(), v)
|
||||
if err == nil {
|
||||
v.ID = id
|
||||
}
|
||||
respondCreated(w, v, err)
|
||||
}
|
||||
func (s *Server) getTCPriority(w http.ResponseWriter, r *http.Request) {
|
||||
id, ok := idParam(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
v, err := s.store.GetTCPriority(r.Context(), id)
|
||||
respondOne(w, v, err)
|
||||
}
|
||||
func (s *Server) deleteTCPriority(w http.ResponseWriter, r *http.Request) {
|
||||
id, ok := idParam(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
respondDelete(w, s.store.DeleteTCPriority(r.Context(), id))
|
||||
}
|
||||
@@ -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