Files
1Panel/agent/utils/firewall/forwarding/sysctl.go
T
ssongliu e119239d3d refactor: manage firewall rules through runtime state and file backups (#13998)
Replace database rule synchronization with native rule operations and file-based backup, import, and recovery. Update firewall management views, descriptions, whitelist handling, and translations.

Include terminal reconnect fixes and xpack build tags for Darwin build targets. Remove temporary regression test files before committing.
2026-10-08 18:19:30 +08:00

162 lines
4.4 KiB
Go

package forwarding
import (
"errors"
"fmt"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"github.com/1Panel-dev/1Panel/agent/constant"
)
func ensureFamilyForwardingSysctls(system forwardingSystem, family string) error {
withIPv6 := family == FamilyIPv6
if withIPv6 {
interfaces, err := IPv6RAInterfaces(system.ReadFile)
if err != nil {
return fmt.Errorf("check IPv6 Router Advertisement: %w", err)
}
if len(interfaces) > 0 {
return fmt.Errorf("IPv6 forwarding blocked: interfaces %s may depend on RA/SLAAC with accept_ra=1; persist accept_ra=2 on interfaces that require RA before retrying", strings.Join(interfaces, ", "))
}
}
paths := []string{"/proc/sys/net/ipv4/ip_forward"}
if withIPv6 {
paths = []string{"/proc/sys/net/ipv6/conf/all/forwarding"}
}
for _, path := range paths {
if err := system.WriteFile(path, []byte("1"), constant.FilePerm); err != nil {
return fmt.Errorf("failed to enable IP forwarding at %s: %w", path, err)
}
}
data, err := system.ReadFile("/etc/sysctl.conf")
if err != nil && !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("failed to read /etc/sysctl.conf: %w", err)
}
content := enableFamilyForwardingSysctls(string(data), family)
if err := system.WriteFile("/etc/sysctl.conf", []byte(content), constant.FilePerm); err != nil {
return fmt.Errorf("failed to persist IP forwarding: %w", err)
}
return nil
}
func IPv6RAInterfaces(readFile func(string) ([]byte, error)) ([]string, error) {
addresses, err := readFile("/proc/net/if_inet6")
if err != nil {
return nil, err
}
const permanentAddress = 0x80
globalAddresses := make(map[string]bool)
raInterfaces := make(map[string]bool)
for _, line := range strings.Split(string(addresses), "\n") {
fields := strings.Fields(line)
if len(fields) == 0 {
continue
}
if len(fields) != 6 {
return nil, fmt.Errorf("invalid IPv6 address entry: %q", line)
}
name := fields[5]
if name == "lo" {
continue
}
flags, err := strconv.ParseUint(fields[4], 16, 32)
if err != nil {
return nil, err
}
if _, exists := globalAddresses[name]; !exists {
globalAddresses[name] = false
}
if fields[3] == "00" {
globalAddresses[name] = true
if flags&permanentAddress == 0 {
raInterfaces[name] = true
}
}
}
routes, err := readFile("/proc/net/ipv6_route")
if err != nil {
return nil, err
}
const raRouteFlags = 0x00010000 | 0x00800000
for _, line := range strings.Split(string(routes), "\n") {
fields := strings.Fields(line)
if len(fields) == 0 {
continue
}
if len(fields) != 10 {
return nil, fmt.Errorf("invalid IPv6 route entry: %q", line)
}
flags, err := strconv.ParseUint(fields[8], 16, 32)
if err != nil {
return nil, err
}
if flags&raRouteFlags != 0 {
raInterfaces[fields[9]] = true
}
}
for name, hasGlobalAddress := range globalAddresses {
if !hasGlobalAddress {
raInterfaces[name] = true
}
}
var interfaces []string
for name := range raInterfaces {
if name == "lo" {
continue
}
if name == "." || name == ".." || filepath.Base(name) != name {
return nil, fmt.Errorf("invalid IPv6 interface %q", name)
}
value, err := readFile("/proc/sys/net/ipv6/conf/" + name + "/accept_ra")
if err != nil {
return nil, err
}
switch strings.TrimSpace(string(value)) {
case "1":
interfaces = append(interfaces, name)
case "0", "2":
default:
return nil, fmt.Errorf("invalid accept_ra value for %s", name)
}
}
sort.Strings(interfaces)
return interfaces, nil
}
func enableFamilyForwardingSysctls(content, family string) string {
lines := strings.Split(strings.TrimRight(content, "\n"), "\n")
wanted := map[string]string{"net.ipv4.ip_forward": "net.ipv4.ip_forward = 1"}
if family == FamilyIPv6 {
wanted = map[string]string{"net.ipv6.conf.all.forwarding": "net.ipv6.conf.all.forwarding = 1"}
}
found := make(map[string]bool, len(wanted))
for index, line := range lines {
trimmed := strings.TrimSpace(line)
if strings.HasPrefix(trimmed, "#") {
continue
}
parts := strings.SplitN(trimmed, "=", 2)
if len(parts) != 2 {
continue
}
key := strings.TrimSpace(parts[0])
if replacement, ok := wanted[key]; ok {
lines[index] = replacement
found[key] = true
}
}
for _, key := range []string{"net.ipv4.ip_forward", "net.ipv6.conf.all.forwarding"} {
if replacement, ok := wanted[key]; ok && !found[key] {
lines = append(lines, replacement)
}
}
if len(lines) > 0 && lines[0] == "" {
lines = lines[1:]
}
return strings.Join(lines, "\n") + "\n"
}