package shorewall import ( "fmt" "strconv" "strings" "git.unkin.net/unkin/tomswall/internal/config" ) // Convert reads a shorewall or shorewall6 config directory and returns a tomswall Config. // Auto-detects IPv6 mode when shorewall6.conf is present. func Convert(dir string) (*config.Config, error) { ipv6 := IsIPv6Dir(dir) return convertDir(dir, ipv6) } func convertDir(dir string, ipv6 bool) (*config.Config, error) { cfg := &config.Config{ Zones: make(map[string]config.Zone), PortGroups: make(map[string]config.PortGroup), } params, _ := ParseParams(dir + "/params") if params == nil { params = make(map[string]string) } if err := convertConf(dir, cfg, params, ipv6); err != nil { confName := "shorewall.conf" if ipv6 { confName = "shorewall6.conf" } return nil, fmt.Errorf("%s: %w", confName, err) } if err := convertZones(dir, cfg, params); err != nil { return nil, fmt.Errorf("zones: %w", err) } // $FW is a shorewall built-in that refers to the firewall zone if _, ok := params["FW"]; !ok { for name, z := range cfg.Zones { if z.Type == config.ZoneFirewall { params["FW"] = name break } } } if len(params) > 0 { cfg.Vars = params } // Shared config files (identical format for shorewall and shorewall6) converters := []struct { name string fn func(string, *config.Config, map[string]string) error }{ {"interfaces", convertInterfaces}, {"hosts", convertHosts}, {"policy", convertPolicy}, {"rules", convertRules}, {"snat", convertSNAT}, {"nat", convertNAT}, {"netmap", convertNetmap}, {"providers", convertProviders}, {"conntrack", convertConntrack}, {"blrules", convertBlrules}, {"tunnels", convertTunnels}, {"rtrules", convertRtrules}, {"stoppedrules", convertStoppedRules}, {"mangle", convertMangle}, {"accounting", convertAccounting}, {"maclist", convertMaclist}, {"routes", convertRoutes}, {"tcdevices", convertTCDevices}, {"tcclasses", convertTCClasses}, {"tcinterfaces", convertTCInterfaces}, {"tcpri", convertTCPri}, {"secmarks", convertSecmarks}, } for _, c := range converters { if err := c.fn(dir, cfg, params); err != nil { return nil, fmt.Errorf("%s: %w", c.name, err) } } // Family-specific converters if ipv6 { if err := convertProxyNDP(dir, cfg, params); err != nil { return nil, fmt.Errorf("proxyndp: %w", err) } } else { if err := convertProxyARP(dir, cfg, params); err != nil { return nil, fmt.Errorf("proxyarp: %w", err) } } return cfg, nil } func subst(s string, params map[string]string) string { if !strings.Contains(s, "$") { return s } return config.SubstituteVars(s, params) } func convertConf(dir string, cfg *config.Config, params map[string]string, ipv6 bool) error { confFile := dir + "/shorewall.conf" if ipv6 { confFile = dir + "/shorewall6.conf" } conf, err := ParseConf(confFile) if err != nil { return err } if conf == nil { return nil } cfg.Settings.TableName = "tomswall" if ipv6 { cfg.Settings.AddressFamily = config.FamilyIP6 } else { cfg.Settings.AddressFamily = config.FamilyIP } if v, ok := conf["LOG_LEVEL"]; ok && v != "" { cfg.Settings.LogLevel = strings.ToLower(v) } else { cfg.Settings.LogLevel = "info" } if v, ok := conf["IP_FORWARDING"]; ok { cfg.Settings.IPForwarding = v == "Yes" || v == "On" || v == "on" || v == "Keep" } if v, ok := conf["IMPLICIT_CONTINUE"]; ok { cfg.Settings.ImplicitContinue = v == "Yes" } return nil } func convertZones(dir string, cfg *config.Config, params map[string]string) error { rows, err := ParseFile(dir + "/zones") if err != nil { return err } for _, row := range rows { name := subst(field(row, 0), params) typeStr := subst(field(row, 1), params) var parents []string if idx := strings.IndexByte(name, ':'); idx > 0 { parentStr := name[idx+1:] name = name[:idx] for _, p := range strings.Split(parentStr, ",") { p = strings.TrimSpace(p) if p != "" { parents = append(parents, p) } } } zone := config.Zone{ Type: convertZoneType(typeStr), } if len(parents) > 0 { zone.Parents = parents } opts := subst(field(row, 2), params) if !isDash(opts) { zone.Options = splitOptions(opts) } inOpts := subst(field(row, 3), params) if !isDash(inOpts) { zone.InOptions = splitOptions(inOpts) } outOpts := subst(field(row, 4), params) if !isDash(outOpts) { zone.OutOptions = splitOptions(outOpts) } cfg.Zones[name] = zone } return nil } func convertZoneType(t string) config.ZoneType { switch strings.ToLower(strings.TrimSpace(t)) { case "firewall", "fw": return config.ZoneFirewall case "ipv4", "ip", "ipv6": return config.ZoneIP case "ipsec", "ipsec4", "ipsec6": return config.ZoneIPSec case "bport", "bport4", "bport6": return config.ZoneBPort case "loopback": return config.ZoneLoopback case "local": return config.ZoneLocal default: return config.ZoneIP } } func splitOptions(s string) []string { var opts []string for _, o := range strings.Split(s, ",") { o = strings.TrimSpace(o) if o != "" { opts = append(opts, o) } } return opts } func convertInterfaces(dir string, cfg *config.Config, params map[string]string) error { rows, err := ParseFile(dir + "/interfaces") if err != nil { return err } for _, row := range rows { zone := subst(field(row, 0), params) iface := subst(field(row, 1), params) intf := config.Interface{ Zone: zone, Interface: iface, } optsStr := subst(field(row, 2), params) if !isDash(optsStr) { intf.Options = parseInterfaceOptions(optsStr) } cfg.Interfaces = append(cfg.Interfaces, intf) } return nil } func parseInterfaceOptions(s string) config.InterfaceOptions { var opts config.InterfaceOptions for _, o := range strings.Split(s, ",") { o = strings.TrimSpace(o) if o == "" { continue } key, val := splitKV(o) switch key { case "dhcp": opts.DHCP = true case "tcpflags": b := true opts.TCPFlags = &b case "nosmurfs": opts.NoSmurfs = true case "routeback": b := true opts.RouteBack = &b case "bridge": opts.Bridge = true case "destonly": opts.DestOnly = true case "optional": opts.Optional = true case "required": opts.Required = true case "physical": opts.Physical = val case "routefilter": if val != "" { n, _ := strconv.Atoi(val) opts.RouteFilter = &n } else { n := 1 opts.RouteFilter = &n } case "logmartians": b := true opts.LogMartians = &b case "arp_filter": b := true opts.ArpFilter = &b case "arp_ignore": if val != "" { n, _ := strconv.Atoi(val) opts.ArpIgnore = &n } case "proxyarp": b := true opts.ProxyArp = &b case "sourceroute": b := true opts.SourceRoute = &b case "upnp": opts.Upnp = true case "wait": if val != "" { n, _ := strconv.Atoi(val) opts.Wait = n } else { opts.Wait = 1 } case "mss": if val != "" { n, _ := strconv.Atoi(val) opts.MSS = n } case "nets": if val != "" { for _, n := range strings.Split(val, ",") { n = strings.TrimSpace(n) if n != "" { opts.Nets = append(opts.Nets, n) } } } case "unmanaged": opts.Unmanaged = true case "accept_ra": if val != "" { n, _ := strconv.Atoi(val) opts.AcceptRA = &n } else { n := 1 opts.AcceptRA = &n } } } return opts } func splitHelperChain(s string) (string, string) { idx := strings.LastIndexByte(s, ':') if idx < 0 { return s, "" } suffix := s[idx+1:] suffixLower := strings.ToLower(suffix) switch suffixLower { case "p": return s[:idx], "prerouting" case "o": return s[:idx], "output" case "po", "op": return s[:idx], "both" } return s, "" } func splitKV(s string) (string, string) { idx := strings.IndexByte(s, '=') if idx < 0 { return strings.ToLower(s), "" } return strings.ToLower(s[:idx]), s[idx+1:] } func convertHosts(dir string, cfg *config.Config, params map[string]string) error { rows, err := ParseFile(dir + "/hosts") if err != nil { return err } for _, row := range rows { zone := subst(field(row, 0), params) hostDef := subst(field(row, 1), params) iface, addrs := splitHostDef(hostDef) host := config.Host{ Zone: zone, Interface: iface, Addresses: addrs, } optsStr := subst(field(row, 2), params) if !isDash(optsStr) { host.Options = parseHostOptions(optsStr) } cfg.Hosts = append(cfg.Hosts, host) } return nil } func splitHostDef(s string) (string, []string) { idx := strings.IndexByte(s, ':') if idx < 0 { return s, nil } iface := s[:idx] addrPart := s[idx+1:] var addrs []string for _, a := range strings.Split(addrPart, ",") { a = strings.TrimSpace(a) if a != "" { addrs = append(addrs, a) } } return iface, addrs } func parseHostOptions(s string) config.HostOptions { var opts config.HostOptions for _, o := range strings.Split(s, ",") { o = strings.TrimSpace(o) switch strings.ToLower(o) { case "broadcast": opts.Broadcast = true case "destonly": opts.DestOnly = true case "ipsec": opts.IPSec = true case "nosmurfs": opts.NoSmurfs = true case "routeback": opts.RouteBack = true case "tcpflags": opts.TCPFlags = true } key, val := splitKV(o) if key == "mss" && val != "" { n, _ := strconv.Atoi(val) opts.MSS = n } } return opts } func convertPolicy(dir string, cfg *config.Config, params map[string]string) error { rows, err := ParseFile(dir + "/policy") if err != nil { return err } for _, row := range rows { source := subst(field(row, 0), params) dest := subst(field(row, 1), params) action := subst(field(row, 2), params) logLevel := subst(field(row, 3), params) pol := config.Policy{ Source: strings.ToLower(source), Dest: strings.ToLower(dest), Action: config.PolicyAction(strings.ToLower(action)), } if !isDash(logLevel) { pol.Log = logLevel } burstLimit := subst(field(row, 4), params) if !isDash(burstLimit) { pol.RateLimit = burstLimit } connLimit := subst(field(row, 5), params) if !isDash(connLimit) { pol.ConnLimit = connLimit } cfg.Policy = append(cfg.Policy, pol) } return nil } func convertRules(dir string, cfg *config.Config, params map[string]string) error { rows, err := ParseFile(dir + "/rules") if err != nil { return err } currentSection := "" for _, row := range rows { if row[0] == "?SECTION" { currentSection = strings.ToLower(field(row, 1)) continue } actionStr := subst(field(row, 0), params) source := subst(field(row, 1), params) dest := subst(field(row, 2), params) proto := subst(field(row, 3), params) action, logLevel := splitActionLog(actionStr) rule := config.Rule{ Action: config.RuleAction(strings.ToLower(action)), Source: source, Dest: dest, } if currentSection != "" && currentSection != "all" { rule.Section = config.RuleSection(currentSection) } if logLevel != "" { rule.Log = logLevel } if !isDash(proto) { rule.Proto = strings.ToLower(proto) } dport := subst(field(row, 4), params) if !isDash(dport) { rule.DPort = parsePortSpec(dport) } sport := subst(field(row, 5), params) if !isDash(sport) { rule.SPort = parsePortSpec(sport) } origdest := subst(field(row, 6), params) if !isDash(origdest) { rule.OrigDest = origdest } rate := subst(field(row, 7), params) if !isDash(rate) { rule.RateLimit = rate } user := subst(field(row, 8), params) if !isDash(user) { rule.User = user } mark := subst(field(row, 9), params) if !isDash(mark) { rule.Mark = mark } connLimit := subst(field(row, 10), params) if !isDash(connLimit) { rule.ConnLimit = connLimit } helper := subst(field(row, 14), params) if !isDash(helper) { rule.Helper = helper } cfg.Rules = append(cfg.Rules, rule) } return nil } func splitActionLog(s string) (action, logLevel string) { idx := strings.IndexByte(s, ':') if idx < 0 { return s, "" } return s[:idx], s[idx+1:] } func parsePortSpec(s string) config.PortSpec { var ports config.PortSpec for _, p := range strings.Split(s, ",") { p = strings.TrimSpace(p) if p != "" { ports = append(ports, p) } } return ports } func convertSNAT(dir string, cfg *config.Config, params map[string]string) error { rows, err := ParseFile(dir + "/snat") if err != nil { return err } for _, row := range rows { actionStr := subst(field(row, 0), params) action, logLevel := splitActionLog(actionStr) snat := config.SNATRule{ Action: config.SNATAction(strings.ToLower(action)), } if logLevel != "" { snat.Log = logLevel } source := subst(field(row, 1), params) if !isDash(source) { snat.Source = source } dest := subst(field(row, 2), params) if !isDash(dest) { snat.Dest = dest } address := subst(field(row, 3), params) if !isDash(address) { snat.Address = address } proto := subst(field(row, 4), params) if !isDash(proto) { snat.Proto = strings.ToLower(proto) } dport := subst(field(row, 5), params) if !isDash(dport) { snat.DPort = parsePortSpec(dport) } sport := subst(field(row, 6), params) if !isDash(sport) { snat.SPort = parsePortSpec(sport) } mark := subst(field(row, 7), params) if !isDash(mark) { snat.Mark = mark } cfg.SNAT = append(cfg.SNAT, snat) } return nil } func convertNAT(dir string, cfg *config.Config, params map[string]string) error { rows, err := ParseFile(dir + "/nat") if err != nil { return err } for _, row := range rows { nat := config.StaticNAT{ External: subst(field(row, 0), params), Interface: subst(field(row, 1), params), Internal: subst(field(row, 2), params), } allIfaces := subst(field(row, 3), params) if strings.EqualFold(allIfaces, "yes") { nat.AllInterfaces = true } local := subst(field(row, 4), params) if strings.EqualFold(local, "yes") { nat.Local = true } cfg.StaticNAT = append(cfg.StaticNAT, nat) } return nil } func convertNetmap(dir string, cfg *config.Config, params map[string]string) error { rows, err := ParseFile(dir + "/netmap") if err != nil { return err } for _, row := range rows { nm := config.Netmap{ Type: config.NetmapType(strings.ToLower(subst(field(row, 0), params))), Net1: subst(field(row, 1), params), Interface: subst(field(row, 2), params), Net2: subst(field(row, 3), params), } net3 := subst(field(row, 4), params) if !isDash(net3) { nm.Net3 = net3 } proto := subst(field(row, 5), params) if !isDash(proto) { nm.Proto = strings.ToLower(proto) } dport := subst(field(row, 6), params) if !isDash(dport) { nm.DPort = parsePortSpec(dport) } sport := subst(field(row, 7), params) if !isDash(sport) { nm.SPort = parsePortSpec(sport) } cfg.Netmap = append(cfg.Netmap, nm) } return nil } func convertProviders(dir string, cfg *config.Config, params map[string]string) error { rows, err := ParseFile(dir + "/providers") if err != nil { return err } for _, row := range rows { name := subst(field(row, 0), params) numberStr := subst(field(row, 1), params) number, _ := strconv.Atoi(numberStr) prov := config.Provider{ Name: name, Number: number, } markStr := subst(field(row, 2), params) if !isDash(markStr) { mark, _ := strconv.ParseInt(markStr, 0, 64) prov.Mark = int(mark) } dup := subst(field(row, 3), params) if !isDash(dup) { prov.Duplicate = dup } iface := subst(field(row, 4), params) if !isDash(iface) { prov.Interface = iface } gw := subst(field(row, 5), params) if !isDash(gw) { prov.Gateway = gw } optsStr := subst(field(row, 6), params) if !isDash(optsStr) { prov.Options = parseProviderOptions(optsStr) } copyStr := subst(field(row, 7), params) if !isDash(copyStr) { for _, c := range strings.Split(copyStr, ",") { c = strings.TrimSpace(c) if c != "" { prov.Copy = append(prov.Copy, c) } } } cfg.Providers = append(cfg.Providers, prov) } return nil } func parseProviderOptions(s string) config.ProviderOptions { var opts config.ProviderOptions for _, o := range strings.Split(s, ",") { o = strings.TrimSpace(o) key, val := splitKV(o) switch key { case "track": opts.Track = true case "balance": if val != "" { n, _ := strconv.Atoi(val) opts.Balance = n } else { opts.Balance = 1 } case "load": if val != "" { f, _ := strconv.ParseFloat(val, 64) opts.Load = f } case "loose": opts.Loose = true case "fallback": if val != "" { n, _ := strconv.Atoi(val) opts.Fallback = n } else { opts.Fallback = 1 } case "primary": opts.Primary = true case "src": opts.Src = val case "mtu": if val != "" { n, _ := strconv.Atoi(val) opts.MTU = n } case "tproxy": opts.TProxy = true case "optional": opts.Optional = true case "persistent": opts.Persistent = true } } return opts } func convertConntrack(dir string, cfg *config.Config, params map[string]string) error { rows, err := ParseFile(dir + "/conntrack") if err != nil { return err } for _, row := range rows { actionStr := subst(field(row, 0), params) ct := config.ConntrackRule{} actionLower := strings.ToLower(actionStr) if strings.HasPrefix(actionLower, "ct:helper:") { ct.Action = config.ConntrackHelper helper := actionStr[10:] helper, chain := splitHelperChain(helper) ct.Helper = helper if chain != "" { ct.Chain = config.ConntrackChain(chain) } } else if strings.HasPrefix(actionLower, "ct:") { ct.Action = config.ConntrackHelper helper := actionStr[3:] helper, chain := splitHelperChain(helper) ct.Helper = helper if chain != "" { ct.Chain = config.ConntrackChain(chain) } } else { ct.Action = config.ConntrackAction(actionLower) } source := subst(field(row, 1), params) if !isDash(source) { ct.Source = source } dest := subst(field(row, 2), params) if !isDash(dest) { ct.Dest = dest } proto := subst(field(row, 3), params) if !isDash(proto) { ct.Proto = strings.ToLower(proto) } dport := subst(field(row, 4), params) if !isDash(dport) { ct.DPort = parsePortSpec(dport) } sport := subst(field(row, 5), params) if !isDash(sport) { ct.SPort = parsePortSpec(sport) } user := subst(field(row, 6), params) if !isDash(user) { ct.User = user } cfg.Conntrack = append(cfg.Conntrack, ct) } return nil } func convertBlrules(dir string, cfg *config.Config, params map[string]string) error { rows, err := ParseFile(dir + "/blrules") if err != nil { return err } for _, row := range rows { actionStr := subst(field(row, 0), params) action, logLevel := splitActionLog(actionStr) rule := config.BlruleRule{ Action: config.BlruleAction(strings.ToLower(action)), Source: subst(field(row, 1), params), Dest: subst(field(row, 2), params), } if logLevel != "" { rule.Log = logLevel } proto := subst(field(row, 3), params) if !isDash(proto) { rule.Proto = strings.ToLower(proto) } dport := subst(field(row, 4), params) if !isDash(dport) { rule.DPort = parsePortSpec(dport) } sport := subst(field(row, 5), params) if !isDash(sport) { rule.SPort = parsePortSpec(sport) } cfg.Blrules = append(cfg.Blrules, rule) } return nil } func convertTunnels(dir string, cfg *config.Config, params map[string]string) error { rows, err := ParseFile(dir + "/tunnels") if err != nil { return err } for _, row := range rows { typeStr := subst(field(row, 0), params) zone := subst(field(row, 1), params) gwStr := subst(field(row, 2), params) var gateways []string for _, gw := range strings.Split(gwStr, ",") { gw = strings.TrimSpace(gw) if gw != "" { gateways = append(gateways, gw) } } tunnel := config.Tunnel{ Type: typeStr, Zone: zone, Gateways: gateways, } gwZones := subst(field(row, 3), params) if !isDash(gwZones) { for _, gz := range strings.Split(gwZones, ",") { gz = strings.TrimSpace(gz) if gz != "" { tunnel.GatewayZones = append(tunnel.GatewayZones, gz) } } } cfg.Tunnels = append(cfg.Tunnels, tunnel) } return nil } func convertRtrules(dir string, cfg *config.Config, params map[string]string) error { rows, err := ParseFile(dir + "/rtrules") if err != nil { return err } for _, row := range rows { source := subst(field(row, 0), params) dest := subst(field(row, 1), params) provider := subst(field(row, 2), params) priorityStr := subst(field(row, 3), params) priority, _ := strconv.Atoi(strings.TrimSuffix(priorityStr, "!")) rule := config.RoutingRule{ Provider: provider, Priority: priority, } if !isDash(source) { rule.Source = source } if !isDash(dest) { rule.Dest = dest } if strings.HasSuffix(priorityStr, "!") { rule.Persistent = true } mark := subst(field(row, 4), params) if !isDash(mark) { rule.Mark = mark } cfg.RoutingRules = append(cfg.RoutingRules, rule) } return nil } func convertStoppedRules(dir string, cfg *config.Config, params map[string]string) error { rows, err := ParseFile(dir + "/stoppedrules") if err != nil { return err } for _, row := range rows { action := subst(field(row, 0), params) rule := config.StoppedRule{ Action: config.StoppedAction(strings.ToLower(action)), } source := subst(field(row, 1), params) if !isDash(source) { rule.Source = source } dest := subst(field(row, 2), params) if !isDash(dest) { rule.Dest = dest } proto := subst(field(row, 3), params) if !isDash(proto) { rule.Proto = strings.ToLower(proto) } dport := subst(field(row, 4), params) if !isDash(dport) { rule.DPort = parsePortSpec(dport) } sport := subst(field(row, 5), params) if !isDash(sport) { rule.SPort = parsePortSpec(sport) } cfg.StoppedRules = append(cfg.StoppedRules, rule) } return nil } func convertMangle(dir string, cfg *config.Config, params map[string]string) error { rows, err := ParseFile(dir + "/mangle") if err != nil { return err } for _, row := range rows { actionStr := subst(field(row, 0), params) rule := config.MangleRule{} actionParts := strings.SplitN(actionStr, "(", 2) base := actionParts[0] var chain string if idx := strings.LastIndexByte(base, ':'); idx > 0 { chain = strings.ToLower(base[idx+1:]) base = base[:idx] } markVal := "" if len(actionParts) > 1 { markVal = strings.TrimSuffix(actionParts[1], ")") } rule.Action = config.MangleAction(strings.ToLower(base)) if chain != "" { rule.Chain = config.MangleChain(chain) } if markVal != "" { rule.MarkValue = markVal } source := subst(field(row, 1), params) if !isDash(source) { rule.Source = source } dest := subst(field(row, 2), params) if !isDash(dest) { rule.Dest = dest } proto := subst(field(row, 3), params) if !isDash(proto) { rule.Proto = strings.ToLower(proto) } dport := subst(field(row, 4), params) if !isDash(dport) { rule.DPort = parsePortSpec(dport) } sport := subst(field(row, 5), params) if !isDash(sport) { rule.SPort = parsePortSpec(sport) } user := subst(field(row, 6), params) if !isDash(user) { rule.User = user } test := subst(field(row, 7), params) if !isDash(test) { rule.Mark = test } length := subst(field(row, 8), params) if !isDash(length) { rule.Length = length } tos := subst(field(row, 9), params) if !isDash(tos) { rule.TOS = tos } helper := subst(field(row, 11), params) if !isDash(helper) { rule.Helper = helper } prob := subst(field(row, 12), params) if !isDash(prob) { f, _ := strconv.ParseFloat(prob, 64) rule.Probability = f } dscp := subst(field(row, 13), params) if !isDash(dscp) { rule.DSCP = dscp } cfg.Mangle = append(cfg.Mangle, rule) } return nil } func convertAccounting(dir string, cfg *config.Config, params map[string]string) error { rows, err := ParseFile(dir + "/accounting") if err != nil { return err } currentSection := "" for _, row := range rows { if row[0] == "?SECTION" { currentSection = strings.ToLower(field(row, 1)) continue } actionStr := subst(field(row, 0), params) rule := config.AccountingRule{ Action: config.AccountingAction(strings.ToLower(actionStr)), } if currentSection != "" { rule.Section = config.AccountingSection(currentSection) } chain := subst(field(row, 1), params) if !isDash(chain) { rule.Chain = chain } source := subst(field(row, 2), params) if !isDash(source) { rule.Source = source } dest := subst(field(row, 3), params) if !isDash(dest) { rule.Dest = dest } proto := subst(field(row, 4), params) if !isDash(proto) { rule.Proto = strings.ToLower(proto) } dport := subst(field(row, 5), params) if !isDash(dport) { rule.DPort = parsePortSpec(dport) } sport := subst(field(row, 6), params) if !isDash(sport) { rule.SPort = parsePortSpec(sport) } mark := subst(field(row, 8), params) if !isDash(mark) { rule.Mark = mark } cfg.Accounting = append(cfg.Accounting, rule) } return nil } func convertMaclist(dir string, cfg *config.Config, params map[string]string) error { rows, err := ParseFile(dir + "/maclist") if err != nil { return err } for _, row := range rows { actionStr := subst(field(row, 0), params) action, logLevel := splitActionLog(actionStr) entry := config.MaclistEntry{ Action: config.MaclistAction(strings.ToLower(action)), Interface: subst(field(row, 1), params), MAC: subst(field(row, 2), params), } if logLevel != "" { entry.Log = logLevel } addrs := subst(field(row, 3), params) if !isDash(addrs) { for _, a := range strings.Split(addrs, ",") { a = strings.TrimSpace(a) if a != "" { entry.Addresses = append(entry.Addresses, a) } } } cfg.Maclist = append(cfg.Maclist, entry) } return nil } func convertProxyARP(dir string, cfg *config.Config, params map[string]string) error { rows, err := ParseFile(dir + "/proxyarp") if err != nil { return err } for _, row := range rows { entry := config.ProxyARP{ Address: subst(field(row, 0), params), Interface: subst(field(row, 1), params), External: subst(field(row, 2), params), } haveRoute := subst(field(row, 3), params) if strings.EqualFold(haveRoute, "yes") { entry.HaveRoute = true } persistent := subst(field(row, 4), params) if strings.EqualFold(persistent, "yes") { entry.Persistent = true } cfg.ProxyARP = append(cfg.ProxyARP, entry) } return nil } func convertRoutes(dir string, cfg *config.Config, params map[string]string) error { rows, err := ParseFile(dir + "/routes") if err != nil { return err } for _, row := range rows { route := config.StaticRoute{ Provider: subst(field(row, 0), params), Dest: subst(field(row, 1), params), } gw := subst(field(row, 2), params) if !isDash(gw) { route.Gateway = gw } dev := subst(field(row, 3), params) if !isDash(dev) { route.Device = dev } cfg.Routes = append(cfg.Routes, route) } return nil } func convertTCDevices(dir string, cfg *config.Config, params map[string]string) error { rows, err := ParseFile(dir + "/tcdevices") if err != nil { return err } for _, row := range rows { dev := config.TCDevice{ Interface: subst(field(row, 0), params), InBandwidth: subst(field(row, 1), params), OutBandwidth: subst(field(row, 2), params), } if isDash(dev.InBandwidth) { dev.InBandwidth = "" } opts := subst(field(row, 3), params) if !isDash(opts) { dev.Options = parseTCDeviceOptions(opts) } cfg.TCDevices = append(cfg.TCDevices, dev) } return nil } func parseTCDeviceOptions(s string) config.TCDeviceOptions { var opts config.TCDeviceOptions for _, o := range strings.Split(s, ",") { o = strings.TrimSpace(o) switch strings.ToLower(o) { case "classify": opts.Classify = true case "htb": opts.HTB = true case "hfsc": opts.HFSC = true default: key, val := splitKV(o) if key == "linklayer" { opts.Linklayer = val } } } return opts } func convertTCClasses(dir string, cfg *config.Config, params map[string]string) error { rows, err := ParseFile(dir + "/tcclasses") if err != nil { return err } for _, row := range rows { cls := config.TCClass{ Interface: subst(field(row, 0), params), Rate: subst(field(row, 2), params), } markStr := subst(field(row, 1), params) if !isDash(markStr) { mark, _ := strconv.Atoi(markStr) cls.Mark = mark } ceil := subst(field(row, 3), params) if !isDash(ceil) { cls.Ceil = ceil } priStr := subst(field(row, 4), params) if !isDash(priStr) { pri, _ := strconv.Atoi(priStr) cls.Priority = pri } opts := subst(field(row, 5), params) if !isDash(opts) { cls.Options = parseTCClassOptions(opts) } cfg.TCClasses = append(cfg.TCClasses, cls) } return nil } func parseTCClassOptions(s string) config.TCClassOptions { var opts config.TCClassOptions for _, o := range strings.Split(s, ",") { switch strings.ToLower(strings.TrimSpace(o)) { case "default": opts.Default = true case "tcp-ack": opts.TCPAck = true case "pfifo": opts.Pfifo = true } } return opts } func convertTCInterfaces(dir string, cfg *config.Config, params map[string]string) error { rows, err := ParseFile(dir + "/tcinterfaces") if err != nil { return err } for _, row := range rows { tci := config.TCInterface{ Interface: subst(field(row, 0), params), } typeStr := subst(field(row, 1), params) if !isDash(typeStr) { tci.Type = strings.ToLower(typeStr) } inBw := subst(field(row, 2), params) if !isDash(inBw) { tci.InBandwidth = inBw } outBw := subst(field(row, 3), params) if !isDash(outBw) { tci.OutBandwidth = outBw } cfg.TCInterfaces = append(cfg.TCInterfaces, tci) } return nil } func convertTCPri(dir string, cfg *config.Config, params map[string]string) error { rows, err := ParseFile(dir + "/tcpri") if err != nil { return err } for _, row := range rows { bandStr := subst(field(row, 0), params) band, _ := strconv.Atoi(bandStr) tp := config.TCPriority{ Band: band, } proto := subst(field(row, 1), params) if !isDash(proto) { tp.Proto = strings.ToLower(proto) } dport := subst(field(row, 2), params) if !isDash(dport) { tp.DPort = parsePortSpec(dport) } sport := subst(field(row, 3), params) if !isDash(sport) { tp.SPort = parsePortSpec(sport) } addr := subst(field(row, 4), params) if !isDash(addr) { tp.Address = addr } iface := subst(field(row, 5), params) if !isDash(iface) { tp.Interface = iface } helper := subst(field(row, 6), params) if !isDash(helper) { tp.Helper = helper } cfg.TCPriorities = append(cfg.TCPriorities, tp) } return nil } func convertSecmarks(dir string, cfg *config.Config, params map[string]string) error { rows, err := ParseFile(dir + "/secmarks") if err != nil { return err } for _, row := range rows { sm := config.SecmarkRule{ Secmark: subst(field(row, 0), params), Chain: subst(field(row, 1), params), } source := subst(field(row, 2), params) if !isDash(source) { sm.Source = source } dest := subst(field(row, 3), params) if !isDash(dest) { sm.Dest = dest } proto := subst(field(row, 4), params) if !isDash(proto) { sm.Proto = strings.ToLower(proto) } dport := subst(field(row, 5), params) if !isDash(dport) { sm.DPort = parsePortSpec(dport) } sport := subst(field(row, 6), params) if !isDash(sport) { sm.SPort = parsePortSpec(sport) } cfg.Secmarks = append(cfg.Secmarks, sm) } return nil } func convertProxyNDP(dir string, cfg *config.Config, params map[string]string) error { rows, err := ParseFile(dir + "/proxyndp") if err != nil { return err } for _, row := range rows { entry := config.ProxyNDP{ Address: subst(field(row, 0), params), Interface: subst(field(row, 1), params), External: subst(field(row, 2), params), } haveRoute := subst(field(row, 3), params) if strings.EqualFold(haveRoute, "yes") { entry.HaveRoute = true } persistent := subst(field(row, 4), params) if strings.EqualFold(persistent, "yes") { entry.Persistent = true } cfg.ProxyNDP = append(cfg.ProxyNDP, entry) } return nil }