4693b7093c
- Add secmarks (id-keyed) and vars (key-keyed) resources: migration 0009, model, store CRUD, REST handlers. - Compiler rendering for the global-compiled tail: blrules, conntrack, secmarks render on enforcing devices; vars render on every device. This closes the rendering gap left by batch 1 (blrules/conntrack were stored but not rendered).
542 lines
19 KiB
Go
542 lines
19 KiB
Go
// Package compiler projects the fleet-global model through a device's binding
|
|
// table into a rendered, interface-agnostic config the tomswall agent applies.
|
|
//
|
|
// Rules are compiled to address-matched (saddr/daddr) forward rules with no
|
|
// iif/oif, which is what makes them correct under FRR/ECMP: any device on any
|
|
// path permits the 5-tuple and each device's own conntrack handles the return.
|
|
// Firewalls always enforce; routers enforce only when their fabric opts in.
|
|
package compiler
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
|
|
"git.unkin.net/unkin/tomswallapi/internal/model"
|
|
"git.unkin.net/unkin/tomswallapi/internal/store"
|
|
)
|
|
|
|
// Input is the fully-resolved model needed to render one device. Keeping Render
|
|
// pure (no store access) makes it unit-testable without a database.
|
|
type Input struct {
|
|
Generation int64
|
|
Settings model.Settings
|
|
Device model.Device
|
|
Fabric *model.Fabric
|
|
Zones map[string]model.Zone
|
|
Groups map[string]model.AddressGroup
|
|
PortGroups map[string]model.PortGroup
|
|
Rules []model.Rule
|
|
Policies []model.Policy
|
|
Bindings []model.Binding
|
|
SNAT []model.SNATRule
|
|
Netmap []model.NetmapRule
|
|
NAT []model.NATRule
|
|
Hosts []model.Host
|
|
Providers []model.Provider
|
|
Routes []model.Route
|
|
RoutingRules []model.RoutingRule
|
|
Tunnels []model.Tunnel
|
|
StoppedRules []model.StoppedRule
|
|
ProxyARP []model.ProxyEntry
|
|
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
|
|
Blrules []model.BlruleRule
|
|
Conntrack []model.ConntrackRule
|
|
Secmarks []model.SecmarkRule
|
|
Vars []model.Var
|
|
}
|
|
|
|
// RenderedConfig is the per-device output served to the agent.
|
|
type RenderedConfig struct {
|
|
Generation int64 `yaml:"generation" json:"generation"`
|
|
Device string `yaml:"device" json:"device"`
|
|
Class model.DeviceClass `yaml:"class" json:"class"`
|
|
Enforcing bool `yaml:"enforcing" json:"enforcing"`
|
|
Settings RenderedSettings `yaml:"settings" json:"settings"`
|
|
Resolver []string `yaml:"resolver,omitempty" json:"resolver,omitempty"`
|
|
Bindings map[string][]string `yaml:"bindings,omitempty" json:"bindings,omitempty"` // zone -> interfaces
|
|
Sets []RenderedSet `yaml:"sets,omitempty" json:"sets,omitempty"`
|
|
Rules []RenderedRule `yaml:"rules,omitempty" json:"rules,omitempty"`
|
|
Policies []model.Policy `yaml:"policies,omitempty" json:"policies,omitempty"`
|
|
SNAT []RenderedSNAT `yaml:"snat,omitempty" json:"snat,omitempty"`
|
|
Netmap []RenderedNetmap `yaml:"netmap,omitempty" json:"netmap,omitempty"`
|
|
NAT []RenderedNAT `yaml:"nat,omitempty" json:"nat,omitempty"`
|
|
Hosts []RenderedHost `yaml:"hosts,omitempty" json:"hosts,omitempty"`
|
|
Providers []RenderedProvider `yaml:"providers,omitempty" json:"providers,omitempty"`
|
|
Routes []RenderedRoute `yaml:"routes,omitempty" json:"routes,omitempty"`
|
|
RoutingRules []RenderedRoutingRule `yaml:"routing_rules,omitempty" json:"routing_rules,omitempty"`
|
|
Tunnels []RenderedTunnel `yaml:"tunnels,omitempty" json:"tunnels,omitempty"`
|
|
StoppedRules []RenderedStoppedRule `yaml:"stopped_rules,omitempty" json:"stopped_rules,omitempty"`
|
|
ProxyARP []RenderedProxy `yaml:"proxy_arp,omitempty" json:"proxy_arp,omitempty"`
|
|
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"`
|
|
Blrules []RenderedBlrule `yaml:"blrules,omitempty" json:"blrules,omitempty"`
|
|
Conntrack []RenderedConntrack `yaml:"conntrack,omitempty" json:"conntrack,omitempty"`
|
|
Secmarks []RenderedSecmark `yaml:"secmarks,omitempty" json:"secmarks,omitempty"`
|
|
Vars map[string]string `yaml:"vars,omitempty" json:"vars,omitempty"`
|
|
}
|
|
|
|
// RenderedSNAT is a resolved SNAT/masquerade rule: source addresses masqueraded
|
|
// (or SNATed to Address) as they leave via the resolved egress interfaces.
|
|
type RenderedSNAT struct {
|
|
Action string `yaml:"action" json:"action"`
|
|
Source []string `yaml:"source,omitempty" json:"source,omitempty"` // source CIDRs
|
|
Egress []string `yaml:"egress" json:"egress"` // egress interface names
|
|
Address string `yaml:"address,omitempty" json:"address,omitempty"`
|
|
Probability *float64 `yaml:"probability,omitempty" json:"probability,omitempty"`
|
|
Comment string `yaml:"comment,omitempty" json:"comment,omitempty"`
|
|
}
|
|
|
|
// RenderedNetmap is a resolved network-to-network mapping on one interface.
|
|
type RenderedNetmap struct {
|
|
Type string `yaml:"type" json:"type"`
|
|
FromNet string `yaml:"from_net" json:"from_net"`
|
|
ToNet string `yaml:"to_net" json:"to_net"`
|
|
Interface string `yaml:"interface,omitempty" json:"interface,omitempty"`
|
|
Comment string `yaml:"comment,omitempty" json:"comment,omitempty"`
|
|
}
|
|
|
|
// RenderedNAT is a resolved one-to-one static NAT on one interface.
|
|
type RenderedNAT struct {
|
|
External string `yaml:"external" json:"external"`
|
|
Internal string `yaml:"internal" json:"internal"`
|
|
Interface string `yaml:"interface,omitempty" json:"interface,omitempty"`
|
|
Comment string `yaml:"comment,omitempty" json:"comment,omitempty"`
|
|
}
|
|
|
|
// RenderedSettings is the effective settings after per-device overrides.
|
|
type RenderedSettings struct {
|
|
AddressFamily string `yaml:"address_family" json:"address_family"`
|
|
LogLevel string `yaml:"log_level" json:"log_level"`
|
|
IPForwarding bool `yaml:"ip_forwarding" json:"ip_forwarding"`
|
|
TableName string `yaml:"table_name" json:"table_name"`
|
|
}
|
|
|
|
// RenderedSet is an nftables named set the agent must materialize. Members carry
|
|
// the concrete elements when the API knows them (static, or asn once expanded);
|
|
// dns and unexpanded asn sets carry their source so the agent/expander can
|
|
// populate them out-of-band without a rule reload.
|
|
type RenderedSet struct {
|
|
Name string `yaml:"name" json:"name"`
|
|
Kind model.AddressGroupType `yaml:"kind" json:"kind"`
|
|
Members []string `yaml:"members,omitempty" json:"members,omitempty"` // static CIDRs / expanded prefixes
|
|
FQDNs []string `yaml:"fqdns,omitempty" json:"fqdns,omitempty"` // dns: names to resolve on-device
|
|
ASNs []string `yaml:"asns,omitempty" json:"asns,omitempty"` // asn: source ASNs
|
|
Refresh string `yaml:"refresh,omitempty" json:"refresh,omitempty"`
|
|
}
|
|
|
|
// RenderedMatch is one OR'd element of a rule direction: the zone's subnets
|
|
// AND, optionally, a named set to intersect with.
|
|
type RenderedMatch struct {
|
|
Zone string `yaml:"zone" json:"zone"`
|
|
Subnets []string `yaml:"subnets,omitempty" json:"subnets,omitempty"`
|
|
Set string `yaml:"set,omitempty" json:"set,omitempty"`
|
|
}
|
|
|
|
// RenderedRule is an interface-agnostic forward rule.
|
|
type RenderedRule struct {
|
|
Action string `yaml:"action" json:"action"`
|
|
Source []RenderedMatch `yaml:"source" json:"source"`
|
|
Dest []RenderedMatch `yaml:"dest" json:"dest"`
|
|
Proto string `yaml:"proto,omitempty" json:"proto,omitempty"`
|
|
Ports []string `yaml:"ports,omitempty" json:"ports,omitempty"`
|
|
Log string `yaml:"log,omitempty" json:"log,omitempty"`
|
|
Comment string `yaml:"comment,omitempty" json:"comment,omitempty"`
|
|
}
|
|
|
|
// Marshal serializes the rendered config to YAML.
|
|
func (c *RenderedConfig) Marshal() ([]byte, error) { return yaml.Marshal(c) }
|
|
|
|
// enforces reports whether the device applies rules: firewalls always do; routers
|
|
// only when their fabric opts into defense-in-depth.
|
|
func enforces(dev model.Device, fabric *model.Fabric) bool {
|
|
if dev.Class == model.ClassFirewall {
|
|
return true
|
|
}
|
|
return dev.Class == model.ClassRouter && fabric != nil && fabric.EnforceOnRouters
|
|
}
|
|
|
|
// setNameFor resolves a rule's selector reference (as written after + or &) to a
|
|
// concrete nft set name. A reference may be a group's bare name or its computed
|
|
// set name (e.g. an asn group "cloudflare" whose set is "asn_cloudflare").
|
|
func setNameFor(groups map[string]model.AddressGroup, ref string) (model.AddressGroup, bool) {
|
|
if g, ok := groups[ref]; ok {
|
|
return g, true
|
|
}
|
|
for _, g := range groups {
|
|
if g.SetName() == ref {
|
|
return g, true
|
|
}
|
|
}
|
|
return model.AddressGroup{}, false
|
|
}
|
|
|
|
// Render projects the model into a device config. It is pure and deterministic.
|
|
func Render(in Input) (*RenderedConfig, error) {
|
|
out := &RenderedConfig{
|
|
Generation: in.Generation,
|
|
Device: in.Device.Name,
|
|
Class: in.Device.Class,
|
|
Enforcing: enforces(in.Device, in.Fabric),
|
|
Settings: renderSettings(in),
|
|
Resolver: effectiveResolver(in),
|
|
Bindings: map[string][]string{},
|
|
}
|
|
for _, b := range in.Bindings {
|
|
out.Bindings[b.Zone] = b.Interfaces
|
|
}
|
|
|
|
usedSets := map[string]model.AddressGroup{}
|
|
|
|
// Every enforcing device carries every applicable rule: the interface-agnostic
|
|
// address-matched form is correct under ECMP precisely because it does not
|
|
// depend on which device is on the path (over-approximation is safe). Reported
|
|
// FIBs are stored for observability/validation, not to limit rules.
|
|
if out.Enforcing {
|
|
for _, rule := range in.Rules {
|
|
rr, err := renderRule(in, rule, usedSets)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("rule %d: %w", rule.ID, err)
|
|
}
|
|
out.Rules = append(out.Rules, rr)
|
|
}
|
|
out.Policies = in.Policies
|
|
}
|
|
|
|
// Emit a set definition for every address group any rule referenced.
|
|
names := make([]string, 0, len(usedSets))
|
|
for n := range usedSets {
|
|
names = append(names, n)
|
|
}
|
|
sort.Strings(names)
|
|
for _, n := range names {
|
|
out.Sets = append(out.Sets, renderSet(usedSets[n]))
|
|
}
|
|
|
|
// NAT tier: resolve the global NAT intents against this device's bindings.
|
|
// These are binding-scoped, independent of the forward-rule enforce flag.
|
|
out.SNAT = renderSNATRules(in, out.Bindings)
|
|
out.Netmap = renderNetmapRules(in, out.Bindings)
|
|
out.NAT = renderNATRules(in)
|
|
|
|
// Per-device long-tail sections owned by this device.
|
|
renderPerDevice(in, out)
|
|
renderPerDeviceL2(in, out)
|
|
renderTraffic(in, out)
|
|
renderGlobal2(in, out)
|
|
|
|
return out, nil
|
|
}
|
|
|
|
// renderSNATRules resolves masquerade/SNAT intents that egress via this device.
|
|
// A SNAT lands here only if the device binds the egress zone (and, when the
|
|
// source is a zone, that zone too) — which auto-scopes masquerade to edges.
|
|
func renderSNATRules(in Input, bindings map[string][]string) []RenderedSNAT {
|
|
var out []RenderedSNAT
|
|
for _, s := range in.SNAT {
|
|
egress := bindings[s.Egress]
|
|
if len(egress) == 0 {
|
|
continue // device is not an egress for this SNAT
|
|
}
|
|
var source []string
|
|
if z, ok := in.Zones[s.Source]; ok {
|
|
if _, bound := bindings[s.Source]; !bound {
|
|
continue // device does not attach the source zone
|
|
}
|
|
source = z.Subnets
|
|
} else {
|
|
source = []string{s.Source} // literal CIDR
|
|
}
|
|
out = append(out, RenderedSNAT{
|
|
Action: s.Action, Source: source, Egress: egress,
|
|
Address: s.Address, Probability: s.Probability, Comment: s.Comment,
|
|
})
|
|
}
|
|
return out
|
|
}
|
|
|
|
// renderNetmapRules resolves netmaps anchored (device:zone or device:interface)
|
|
// at this device.
|
|
func renderNetmapRules(in Input, bindings map[string][]string) []RenderedNetmap {
|
|
var out []RenderedNetmap
|
|
for _, n := range in.Netmap {
|
|
dev, sel, ok := strings.Cut(n.Anchor, ":")
|
|
if !ok || dev != in.Device.Name {
|
|
continue
|
|
}
|
|
iface := sel
|
|
if ifaces, bound := bindings[sel]; bound && len(ifaces) > 0 {
|
|
iface = ifaces[0] // anchor named a zone: use its bound interface
|
|
}
|
|
out = append(out, RenderedNetmap{
|
|
Type: n.Type, FromNet: n.FromNet, ToNet: n.ToNet, Interface: iface, Comment: n.Comment,
|
|
})
|
|
}
|
|
return out
|
|
}
|
|
|
|
// renderNATRules resolves 1:1 static NATs bound to this device.
|
|
func renderNATRules(in Input) []RenderedNAT {
|
|
var out []RenderedNAT
|
|
for _, n := range in.NAT {
|
|
if n.Device != in.Device.Name {
|
|
continue
|
|
}
|
|
out = append(out, RenderedNAT{
|
|
External: n.External, Internal: n.Internal, Interface: n.Interface, Comment: n.Comment,
|
|
})
|
|
}
|
|
return out
|
|
}
|
|
|
|
func renderSettings(in Input) RenderedSettings {
|
|
s := RenderedSettings{
|
|
AddressFamily: in.Settings.AddressFamily,
|
|
LogLevel: in.Settings.LogLevel,
|
|
IPForwarding: in.Settings.IPForwarding,
|
|
TableName: in.Settings.TableName,
|
|
}
|
|
// Per-device string overrides.
|
|
if v, ok := in.Device.Settings["address_family"]; ok {
|
|
s.AddressFamily = v
|
|
}
|
|
if v, ok := in.Device.Settings["log_level"]; ok {
|
|
s.LogLevel = v
|
|
}
|
|
if v, ok := in.Device.Settings["table_name"]; ok {
|
|
s.TableName = v
|
|
}
|
|
return s
|
|
}
|
|
|
|
func effectiveResolver(in Input) []string {
|
|
if len(in.Device.Resolver) > 0 {
|
|
return in.Device.Resolver
|
|
}
|
|
return in.Settings.DefaultResolver
|
|
}
|
|
|
|
func renderRule(in Input, rule model.Rule, usedSets map[string]model.AddressGroup) (RenderedRule, error) {
|
|
src, err := renderMatches(in, rule.Source, usedSets)
|
|
if err != nil {
|
|
return RenderedRule{}, fmt.Errorf("source: %w", err)
|
|
}
|
|
dst, err := renderMatches(in, rule.Dest, usedSets)
|
|
if err != nil {
|
|
return RenderedRule{}, fmt.Errorf("dest: %w", err)
|
|
}
|
|
proto, ports := resolvePorts(in, rule)
|
|
return RenderedRule{
|
|
Action: rule.Action,
|
|
Source: src,
|
|
Dest: dst,
|
|
Proto: proto,
|
|
Ports: ports,
|
|
Log: rule.Log,
|
|
Comment: rule.Comment,
|
|
}, nil
|
|
}
|
|
|
|
func renderMatches(in Input, list []string, usedSets map[string]model.AddressGroup) ([]RenderedMatch, error) {
|
|
elems, err := model.ParseElements(list)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out := make([]RenderedMatch, 0, len(elems))
|
|
for _, e := range elems {
|
|
m := RenderedMatch{Zone: e.Zone}
|
|
if z, ok := in.Zones[e.Zone]; ok {
|
|
m.Subnets = z.Subnets
|
|
}
|
|
if e.Selector != model.SelNone {
|
|
g, ok := setNameFor(in.Groups, e.Ref)
|
|
if !ok {
|
|
return nil, fmt.Errorf("unknown address group %q", e.Ref)
|
|
}
|
|
m.Set = g.SetName()
|
|
usedSets[g.SetName()] = g
|
|
}
|
|
out = append(out, m)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func resolvePorts(in Input, rule model.Rule) (proto string, ports []string) {
|
|
if rule.PortGroup != "" {
|
|
if pg, ok := in.PortGroups[rule.PortGroup]; ok {
|
|
return pg.Proto, pg.Ports
|
|
}
|
|
}
|
|
return rule.Proto, rule.Ports
|
|
}
|
|
|
|
func renderSet(g model.AddressGroup) RenderedSet {
|
|
rs := RenderedSet{Name: g.SetName(), Kind: g.Type, Refresh: g.Refresh}
|
|
switch g.Type {
|
|
case model.GroupStatic:
|
|
rs.Members = g.Members
|
|
case model.GroupDNS:
|
|
rs.FQDNs = g.Members
|
|
case model.GroupASN:
|
|
rs.ASNs = g.Members // source ASNs
|
|
rs.Members = g.Resolved // concrete prefixes from the central expander (may be empty until first expansion)
|
|
}
|
|
return rs
|
|
}
|
|
|
|
// Compile fetches the model for a device from the store and renders its config.
|
|
func Compile(ctx context.Context, s *store.Store, device string) (*RenderedConfig, error) {
|
|
dev, err := s.GetDevice(ctx, device)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
gen, err := s.Generation(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
settings, err := s.GetSettings(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
in := Input{Generation: gen, Settings: settings, Device: dev}
|
|
|
|
if dev.Fabric != "" {
|
|
f, err := s.GetFabric(ctx, dev.Fabric)
|
|
if err == nil {
|
|
in.Fabric = &f
|
|
} else if err != store.ErrNotFound {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
zones, err := s.ListZones(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
in.Zones = make(map[string]model.Zone, len(zones))
|
|
for _, z := range zones {
|
|
in.Zones[z.Name] = z
|
|
}
|
|
|
|
groups, err := s.ListAddressGroups(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
in.Groups = make(map[string]model.AddressGroup, len(groups))
|
|
for _, g := range groups {
|
|
in.Groups[g.Name] = g
|
|
}
|
|
|
|
pgs, err := s.ListPortGroups(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
in.PortGroups = make(map[string]model.PortGroup, len(pgs))
|
|
for _, p := range pgs {
|
|
in.PortGroups[p.Name] = p
|
|
}
|
|
|
|
if in.Rules, err = s.ListRules(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
if in.Policies, err = s.ListPolicies(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
if in.Bindings, err = s.ListBindings(ctx, device); err != nil {
|
|
return nil, err
|
|
}
|
|
if in.SNAT, err = s.ListSNAT(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
if in.Netmap, err = s.ListNetmap(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
if in.NAT, err = s.ListNAT(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
if in.Hosts, err = s.ListHosts(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
if in.Providers, err = s.ListProviders(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
if in.Routes, err = s.ListRoutes(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
if in.RoutingRules, err = s.ListRoutingRules(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
if in.Tunnels, err = s.ListTunnels(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
if in.StoppedRules, err = s.ListStoppedRules(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
if in.ProxyARP, err = s.ListProxyARP(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
if in.ProxyNDP, err = s.ListProxyNDP(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
if in.ArpRules, err = s.ListArpRules(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
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
|
|
}
|
|
if in.Blrules, err = s.ListBlrules(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
if in.Conntrack, err = s.ListConntrack(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
if in.Secmarks, err = s.ListSecmarks(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
if in.Vars, err = s.ListVars(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
return Render(in)
|
|
}
|