mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 04:22:33 +08:00
feat(dns): 优化系统工具箱DNS设置以适配现代Linux系统 (#1202)
* Initial plan * feat(dns): 优化系统工具箱DNS设置以适配现代Linux系统 - 创建 pkg/dns 包实现多种DNS管理方式 - 支持 NetworkManager (RHEL 9.x/10.x) - 支持 netplan (Debian 12+/Ubuntu 22+) - 回退到直接修改 /etc/resolv.conf - 更新前端显示当前DNS管理方式 - 添加单元测试 Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com> * fix: 修复代码审查发现的问题 - 提取 shell 参数转义逻辑到独立函数 - 修正 netplan 配置文件选择的注释说明 - 使用常量替代硬编码的缩进值 - 添加前端空数组安全检查 Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com> * fix: 优化 * fix: 优化 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com> Co-authored-by: 耗子 <haozi@loli.email>
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"github.com/acepanel/panel/internal/app"
|
||||
"github.com/acepanel/panel/internal/http/request"
|
||||
"github.com/acepanel/panel/pkg/dns"
|
||||
"github.com/acepanel/panel/pkg/io"
|
||||
"github.com/acepanel/panel/pkg/ntp"
|
||||
"github.com/acepanel/panel/pkg/shell"
|
||||
@@ -32,19 +33,16 @@ func NewToolboxSystemService(t *gotext.Locale) *ToolboxSystemService {
|
||||
|
||||
// GetDNS 获取 DNS 信息
|
||||
func (s *ToolboxSystemService) GetDNS(w http.ResponseWriter, r *http.Request) {
|
||||
raw, err := io.Read("/etc/resolv.conf")
|
||||
dnsServers, manager, err := dns.GetDNS()
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
match := regexp.MustCompile(`nameserver\s+(\S+)`).FindAllStringSubmatch(raw, -1)
|
||||
dns := make([]string, 0)
|
||||
for _, m := range match {
|
||||
dns = append(dns, m[1])
|
||||
}
|
||||
|
||||
Success(w, dns)
|
||||
Success(w, chix.M{
|
||||
"dns": dnsServers,
|
||||
"manager": manager.String(),
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateDNS 设置 DNS 信息
|
||||
@@ -55,11 +53,7 @@ func (s *ToolboxSystemService) UpdateDNS(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
var dns string
|
||||
dns += "nameserver " + req.DNS1 + "\n"
|
||||
dns += "nameserver " + req.DNS2 + "\n"
|
||||
|
||||
if err := io.Write("/etc/resolv.conf", dns, 0644); err != nil {
|
||||
if err := dns.SetDNS(req.DNS1, req.DNS2); err != nil {
|
||||
Error(w, http.StatusInternalServerError, s.t.Get("failed to update DNS: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
484
pkg/dns/dns.go
Normal file
484
pkg/dns/dns.go
Normal file
@@ -0,0 +1,484 @@
|
||||
// Package dns 提供 DNS 配置管理功能
|
||||
package dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"go.yaml.in/yaml/v4"
|
||||
|
||||
"github.com/acepanel/panel/pkg/io"
|
||||
"github.com/acepanel/panel/pkg/shell"
|
||||
"github.com/acepanel/panel/pkg/systemctl"
|
||||
)
|
||||
|
||||
// Manager 定义了 DNS 管理的类型
|
||||
type Manager int
|
||||
|
||||
const (
|
||||
ManagerUnknown Manager = iota
|
||||
ManagerNetworkManager
|
||||
ManagerNetplan
|
||||
ManagerResolvConf
|
||||
)
|
||||
|
||||
// String 返回 Manager 的字符串表示
|
||||
func (m Manager) String() string {
|
||||
switch m {
|
||||
case ManagerNetworkManager:
|
||||
return "NetworkManager"
|
||||
case ManagerNetplan:
|
||||
return "netplan"
|
||||
case ManagerResolvConf:
|
||||
return "resolv.conf"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// DetectManager 检测当前系统使用的 DNS 管理方式
|
||||
func DetectManager() Manager {
|
||||
if isNetworkManagerActive() {
|
||||
return ManagerNetworkManager
|
||||
}
|
||||
if isNetplanAvailable() {
|
||||
return ManagerNetplan
|
||||
}
|
||||
return ManagerResolvConf
|
||||
}
|
||||
|
||||
// GetDNS 获取当前 DNS 配置
|
||||
func GetDNS() ([]string, Manager, error) {
|
||||
manager := DetectManager()
|
||||
dns, err := getDNSFromResolvConf()
|
||||
return dns, manager, err
|
||||
}
|
||||
|
||||
// SetDNS 设置 DNS 服务器
|
||||
func SetDNS(dns1, dns2 string) error {
|
||||
manager := DetectManager()
|
||||
|
||||
switch manager {
|
||||
case ManagerNetworkManager:
|
||||
return setDNSWithNetworkManager(dns1, dns2)
|
||||
case ManagerNetplan:
|
||||
return setDNSWithNetplan(dns1, dns2)
|
||||
default:
|
||||
return setDNSWithResolvConf(dns1, dns2)
|
||||
}
|
||||
}
|
||||
|
||||
// isNetworkManagerActive 检查 NetworkManager 是否正在运行
|
||||
func isNetworkManagerActive() bool {
|
||||
active, _ := systemctl.Status("NetworkManager")
|
||||
return active
|
||||
}
|
||||
|
||||
// isNetplanAvailable 检查 netplan 是否可用
|
||||
func isNetplanAvailable() bool {
|
||||
if _, err := shell.Execf("command -v netplan"); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
configFiles := []string{
|
||||
"/etc/netplan/*.yaml",
|
||||
"/etc/netplan/*.yml",
|
||||
}
|
||||
for _, pattern := range configFiles {
|
||||
files, _ := filepath.Glob(pattern)
|
||||
if len(files) > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// getDNSFromResolvConf 从 /etc/resolv.conf 获取 DNS
|
||||
func getDNSFromResolvConf() ([]string, error) {
|
||||
raw, err := io.Read("/etc/resolv.conf")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
match := regexp.MustCompile(`nameserver\s+(\S+)`).FindAllStringSubmatch(raw, -1)
|
||||
dns := make([]string, 0)
|
||||
for _, m := range match {
|
||||
dns = append(dns, m[1])
|
||||
}
|
||||
|
||||
return dns, nil
|
||||
}
|
||||
|
||||
// setDNSWithNetworkManager 使用 NetworkManager 设置 DNS
|
||||
func setDNSWithNetworkManager(dns1, dns2 string) error {
|
||||
// 获取所有活动的连接
|
||||
connections, err := getActiveNMConnections()
|
||||
if err != nil || len(connections) == 0 {
|
||||
// 回退到直接修改 resolv.conf
|
||||
return setDNSWithResolvConf(dns1, dns2)
|
||||
}
|
||||
|
||||
// 构建 DNS 服务器列表
|
||||
dnsServers := dns1
|
||||
if dns2 != "" {
|
||||
dnsServers = dns1 + "," + dns2
|
||||
}
|
||||
|
||||
var lastErr error
|
||||
successCount := 0
|
||||
|
||||
// 为所有活动的连接设置 DNS
|
||||
for _, conn := range connections {
|
||||
connName := conn.name
|
||||
// 使用 nmcli 设置 DNS
|
||||
if _, err = shell.Execf("nmcli connection modify %s ipv4.dns %s", connName, dnsServers); err != nil {
|
||||
lastErr = fmt.Errorf("failed to set DNS for connection %s: %w", connName, err)
|
||||
continue
|
||||
}
|
||||
// 设置 DNS 优先级,确保自定义 DNS 优先
|
||||
_, _ = shell.Execf("nmcli connection modify %s ipv4.dns-priority -1", connName)
|
||||
// 忽略 DHCP 提供的 DNS
|
||||
_, _ = shell.Execf("nmcli connection modify %s ipv4.ignore-auto-dns yes", connName)
|
||||
// 重新激活连接以应用更改
|
||||
if _, err = shell.Execf("nmcli connection up %s", connName); err != nil {
|
||||
lastErr = fmt.Errorf("failed to reactivate connection %s: %w", connName, err)
|
||||
continue
|
||||
}
|
||||
successCount++
|
||||
}
|
||||
|
||||
// 只要有一个连接成功设置就算成功
|
||||
if successCount == 0 && lastErr != nil {
|
||||
return lastErr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// nmConnection NetworkManager 连接信息
|
||||
type nmConnection struct {
|
||||
name string // 连接名称(带引号处理空格)
|
||||
device string // 设备名
|
||||
}
|
||||
|
||||
// getActiveNMConnections 获取所有活动的 NetworkManager 连接
|
||||
func getActiveNMConnections() ([]nmConnection, error) {
|
||||
output, err := shell.Execf("nmcli -t -f NAME,DEVICE connection show --active")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var connections []nmConnection
|
||||
lines := strings.Split(output, "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
// 格式: NAME:DEVICE
|
||||
parts := strings.SplitN(line, ":", 2)
|
||||
if len(parts) >= 2 && parts[1] != "" && isValidNetworkInterface(parts[1]) {
|
||||
// 返回带引号的连接名称,以处理包含空格的名称
|
||||
quotedName := "'" + strings.ReplaceAll(parts[0], "'", "'\"'\"'") + "'"
|
||||
connections = append(connections, nmConnection{
|
||||
name: quotedName,
|
||||
device: parts[1],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(connections) == 0 {
|
||||
return nil, fmt.Errorf("no active NetworkManager connections found")
|
||||
}
|
||||
|
||||
return connections, nil
|
||||
}
|
||||
|
||||
// setDNSWithNetplan 使用 netplan 设置 DNS
|
||||
func setDNSWithNetplan(dns1, dns2 string) error {
|
||||
// 查找 netplan 配置文件
|
||||
configPath, err := findNetplanConfig()
|
||||
if err != nil {
|
||||
// 回退到直接修改 resolv.conf
|
||||
return setDNSWithResolvConf(dns1, dns2)
|
||||
}
|
||||
|
||||
// 读取现有配置
|
||||
content, err := io.Read(configPath)
|
||||
if err != nil {
|
||||
return setDNSWithResolvConf(dns1, dns2)
|
||||
}
|
||||
// 更新 DNS 配置
|
||||
newContent, err := updateNetplanDNS(content, dns1, dns2)
|
||||
if err != nil {
|
||||
return setDNSWithResolvConf(dns1, dns2)
|
||||
}
|
||||
// 写入配置文件
|
||||
if err = io.Write(configPath, newContent, 0600); err != nil {
|
||||
return fmt.Errorf("failed to write netplan config: %w", err)
|
||||
}
|
||||
// 应用 netplan 配置
|
||||
if _, err = shell.Execf("netplan apply"); err != nil {
|
||||
return fmt.Errorf("failed to apply netplan config: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// findNetplanConfig 查找 netplan 配置文件
|
||||
func findNetplanConfig() (string, error) {
|
||||
patterns := []string{
|
||||
"/etc/netplan/*.yaml",
|
||||
"/etc/netplan/*.yml",
|
||||
}
|
||||
|
||||
for _, pattern := range patterns {
|
||||
files, err := filepath.Glob(pattern)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if len(files) > 0 {
|
||||
// netplan 按文件名字母顺序处理配置文件
|
||||
// 返回最后一个文件,因为它的配置会覆盖之前的配置
|
||||
return files[len(files)-1], nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("failed to find netplan config file")
|
||||
}
|
||||
|
||||
// netplanConfig netplan 配置结构
|
||||
type netplanConfig struct {
|
||||
Network netplanNetwork `yaml:"network"`
|
||||
}
|
||||
|
||||
// netplanNetwork 网络配置
|
||||
type netplanNetwork struct {
|
||||
Version int `yaml:"version,omitempty"`
|
||||
Renderer string `yaml:"renderer,omitempty"`
|
||||
Ethernets map[string]*netplanInterface `yaml:"ethernets,omitempty"`
|
||||
Wifis map[string]*netplanInterface `yaml:"wifis,omitempty"`
|
||||
Bonds map[string]*netplanInterface `yaml:"bonds,omitempty"`
|
||||
Bridges map[string]*netplanInterface `yaml:"bridges,omitempty"`
|
||||
Vlans map[string]*netplanInterface `yaml:"vlans,omitempty"`
|
||||
}
|
||||
|
||||
// netplanInterface 网络接口配置
|
||||
type netplanInterface struct {
|
||||
DHCP4 any `yaml:"dhcp4,omitempty"`
|
||||
DHCP6 any `yaml:"dhcp6,omitempty"`
|
||||
Addresses []string `yaml:"addresses,omitempty"`
|
||||
Gateway4 string `yaml:"gateway4,omitempty"`
|
||||
Gateway6 string `yaml:"gateway6,omitempty"`
|
||||
Routes []netplanRoute `yaml:"routes,omitempty"`
|
||||
Nameservers *netplanNameservers `yaml:"nameservers,omitempty"`
|
||||
MTU int `yaml:"mtu,omitempty"`
|
||||
MACAddress string `yaml:"macaddress,omitempty"`
|
||||
Optional any `yaml:"optional,omitempty"`
|
||||
Match *netplanMatch `yaml:"match,omitempty"`
|
||||
SetName string `yaml:"set-name,omitempty"`
|
||||
Interfaces []string `yaml:"interfaces,omitempty"`
|
||||
Parameters map[string]any `yaml:"parameters,omitempty"`
|
||||
ID int `yaml:"id,omitempty"`
|
||||
Link string `yaml:"link,omitempty"`
|
||||
Extra map[string]yaml.Node `yaml:",inline"` // 保留未知字段
|
||||
}
|
||||
|
||||
// netplanRoute 路由配置
|
||||
type netplanRoute struct {
|
||||
To string `yaml:"to,omitempty"`
|
||||
Via string `yaml:"via,omitempty"`
|
||||
Metric int `yaml:"metric,omitempty"`
|
||||
}
|
||||
|
||||
// netplanNameservers DNS 配置
|
||||
type netplanNameservers struct {
|
||||
Addresses []string `yaml:"addresses,omitempty"`
|
||||
Search []string `yaml:"search,omitempty"`
|
||||
}
|
||||
|
||||
// netplanMatch 网卡匹配规则
|
||||
type netplanMatch struct {
|
||||
MACAddress string `yaml:"macaddress,omitempty"`
|
||||
Driver string `yaml:"driver,omitempty"`
|
||||
}
|
||||
|
||||
// updateNetplanDNS 更新 netplan 配置中的 DNS
|
||||
func updateNetplanDNS(content, dns1, dns2 string) (string, error) {
|
||||
var config netplanConfig
|
||||
if err := yaml.Unmarshal([]byte(content), &config); err != nil {
|
||||
return "", fmt.Errorf("failed to parse netplan config: %w", err)
|
||||
}
|
||||
|
||||
// 构建新的 DNS 地址列表
|
||||
dnsAddresses := []string{dns1}
|
||||
if dns2 != "" {
|
||||
dnsAddresses = append(dnsAddresses, dns2)
|
||||
}
|
||||
|
||||
// 更新所有网络接口的 DNS 配置
|
||||
updated := false
|
||||
|
||||
// 更新 ethernets
|
||||
for _, iface := range config.Network.Ethernets {
|
||||
if iface != nil {
|
||||
if iface.Nameservers == nil {
|
||||
iface.Nameservers = &netplanNameservers{}
|
||||
}
|
||||
iface.Nameservers.Addresses = dnsAddresses
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
|
||||
// 更新 wifis
|
||||
for _, iface := range config.Network.Wifis {
|
||||
if iface != nil {
|
||||
if iface.Nameservers == nil {
|
||||
iface.Nameservers = &netplanNameservers{}
|
||||
}
|
||||
iface.Nameservers.Addresses = dnsAddresses
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
|
||||
// 更新 bonds
|
||||
for _, iface := range config.Network.Bonds {
|
||||
if iface != nil {
|
||||
if iface.Nameservers == nil {
|
||||
iface.Nameservers = &netplanNameservers{}
|
||||
}
|
||||
iface.Nameservers.Addresses = dnsAddresses
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
|
||||
// 更新 bridges
|
||||
for _, iface := range config.Network.Bridges {
|
||||
if iface != nil {
|
||||
if iface.Nameservers == nil {
|
||||
iface.Nameservers = &netplanNameservers{}
|
||||
}
|
||||
iface.Nameservers.Addresses = dnsAddresses
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
|
||||
// 更新 vlans
|
||||
for _, iface := range config.Network.Vlans {
|
||||
if iface != nil {
|
||||
if iface.Nameservers == nil {
|
||||
iface.Nameservers = &netplanNameservers{}
|
||||
}
|
||||
iface.Nameservers.Addresses = dnsAddresses
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
|
||||
// 如果配置中没有任何接口,尝试检测当前活动的网络接口并添加配置
|
||||
if !updated {
|
||||
activeIface := detectActiveInterface()
|
||||
if activeIface == "" {
|
||||
return "", fmt.Errorf("no network interface found in config and failed to detect active interface")
|
||||
}
|
||||
|
||||
// 创建 ethernets 配置
|
||||
if config.Network.Ethernets == nil {
|
||||
config.Network.Ethernets = make(map[string]*netplanInterface)
|
||||
}
|
||||
|
||||
// 为检测到的接口添加配置
|
||||
config.Network.Ethernets[activeIface] = &netplanInterface{
|
||||
Nameservers: &netplanNameservers{
|
||||
Addresses: dnsAddresses,
|
||||
},
|
||||
}
|
||||
|
||||
// 设置默认版本
|
||||
if config.Network.Version == 0 {
|
||||
config.Network.Version = 2
|
||||
}
|
||||
}
|
||||
|
||||
// 序列化为 YAML
|
||||
output, err := yaml.Marshal(&config)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to marshal netplan config: %w", err)
|
||||
}
|
||||
|
||||
return string(output), nil
|
||||
}
|
||||
|
||||
// detectActiveInterface 检测当前活动的网络接口名称
|
||||
// 返回第一个非 lo/docker/veth/br- 的活动接口
|
||||
func detectActiveInterface() string {
|
||||
// 尝试获取默认路由的网络接口
|
||||
output, err := shell.Execf("ip route show default 2>/dev/null | awk '/default/ {print $5}' | head -n1")
|
||||
if err == nil {
|
||||
iface := strings.TrimSpace(output)
|
||||
if iface != "" && isValidNetworkInterface(iface) {
|
||||
return iface
|
||||
}
|
||||
}
|
||||
|
||||
// 回退:获取所有 UP 状态的接口
|
||||
output, err = shell.Execf("ip -o link show up 2>/dev/null | awk -F': ' '{print $2}'")
|
||||
if err == nil {
|
||||
lines := strings.Split(strings.TrimSpace(output), "\n")
|
||||
for _, line := range lines {
|
||||
iface := strings.TrimSpace(line)
|
||||
if isValidNetworkInterface(iface) {
|
||||
return iface
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// isValidNetworkInterface 检查接口名是否为有效的物理/外部网络接口
|
||||
func isValidNetworkInterface(name string) bool {
|
||||
if name == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
// 排除虚拟接口和回环接口
|
||||
excludePrefixes := []string{
|
||||
"lo", // 回环
|
||||
"docker", // Docker
|
||||
"veth", // Docker/容器虚拟网卡
|
||||
"br-", // Docker 桥接
|
||||
"virbr", // libvirt 虚拟桥接
|
||||
"vnet", // 虚拟网络
|
||||
"tun", // VPN 隧道
|
||||
"tap", // TAP 设备
|
||||
"flannel", // Kubernetes flannel
|
||||
"cni", // Kubernetes CNI
|
||||
"cali", // Calico
|
||||
}
|
||||
|
||||
for _, prefix := range excludePrefixes {
|
||||
if strings.HasPrefix(name, prefix) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// setDNSWithResolvConf 直接修改 /etc/resolv.conf 设置 DNS
|
||||
func setDNSWithResolvConf(dns1, dns2 string) error {
|
||||
var dns string
|
||||
dns += "nameserver " + dns1 + "\n"
|
||||
if dns2 != "" {
|
||||
dns += "nameserver " + dns2 + "\n"
|
||||
}
|
||||
|
||||
if err := io.Write("/etc/resolv.conf", dns, 0644); err != nil {
|
||||
return fmt.Errorf("failed to write /etc/resolv.conf: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
325
pkg/dns/dns_test.go
Normal file
325
pkg/dns/dns_test.go
Normal file
@@ -0,0 +1,325 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type DNSTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func TestDNSTestSuite(t *testing.T) {
|
||||
suite.Run(t, &DNSTestSuite{})
|
||||
}
|
||||
|
||||
func (s *DNSTestSuite) SetupTest() {
|
||||
if _, err := os.Stat("testdata"); os.IsNotExist(err) {
|
||||
s.NoError(os.MkdirAll("testdata", 0755))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DNSTestSuite) TearDownTest() {
|
||||
s.NoError(os.RemoveAll("testdata"))
|
||||
}
|
||||
|
||||
func (s *DNSTestSuite) TestManagerString() {
|
||||
s.Equal("NetworkManager", ManagerNetworkManager.String())
|
||||
s.Equal("netplan", ManagerNetplan.String())
|
||||
s.Equal("resolv.conf", ManagerResolvConf.String())
|
||||
s.Equal("unknown", ManagerUnknown.String())
|
||||
}
|
||||
|
||||
func (s *DNSTestSuite) TestDetectManager() {
|
||||
// DetectManager 会返回一个有效的 Manager 类型
|
||||
manager := DetectManager()
|
||||
s.True(manager >= ManagerUnknown && manager <= ManagerResolvConf)
|
||||
}
|
||||
|
||||
func (s *DNSTestSuite) TestUpdateNetplanDNS() {
|
||||
// 测试基本的 netplan 配置更新
|
||||
content := `network:
|
||||
version: 2
|
||||
ethernets:
|
||||
eth0:
|
||||
dhcp4: true`
|
||||
|
||||
result, err := updateNetplanDNS(content, "8.8.8.8", "8.8.4.4")
|
||||
s.NoError(err)
|
||||
s.Contains(result, "nameservers:")
|
||||
s.Contains(result, "8.8.8.8")
|
||||
s.Contains(result, "8.8.4.4")
|
||||
}
|
||||
|
||||
func (s *DNSTestSuite) TestUpdateNetplanDNSWithExisting() {
|
||||
// 测试替换现有的 DNS 配置
|
||||
content := `network:
|
||||
version: 2
|
||||
ethernets:
|
||||
eth0:
|
||||
dhcp4: true
|
||||
nameservers:
|
||||
addresses: [1.1.1.1, 1.0.0.1]`
|
||||
|
||||
result, err := updateNetplanDNS(content, "8.8.8.8", "8.8.4.4")
|
||||
s.NoError(err)
|
||||
s.Contains(result, "8.8.8.8")
|
||||
s.Contains(result, "8.8.4.4")
|
||||
// 旧的 DNS 应该被移除
|
||||
s.NotContains(result, "1.1.1.1")
|
||||
s.NotContains(result, "1.0.0.1")
|
||||
}
|
||||
|
||||
// TestUpdateNetplanDNSWithWifi 测试 wifi 网络接口配置
|
||||
func (s *DNSTestSuite) TestUpdateNetplanDNSWithWifi() {
|
||||
content := `network:
|
||||
version: 2
|
||||
wifis:
|
||||
wlan0:
|
||||
dhcp4: true
|
||||
access-points:
|
||||
"my-wifi":
|
||||
password: "secret"`
|
||||
|
||||
result, err := updateNetplanDNS(content, "8.8.8.8", "")
|
||||
s.NoError(err)
|
||||
s.Contains(result, "nameservers:")
|
||||
s.Contains(result, "8.8.8.8")
|
||||
}
|
||||
|
||||
// TestUpdateNetplanDNSWithMultipleInterfaces 测试多接口配置
|
||||
func (s *DNSTestSuite) TestUpdateNetplanDNSWithMultipleInterfaces() {
|
||||
content := `network:
|
||||
version: 2
|
||||
ethernets:
|
||||
eth0:
|
||||
dhcp4: true
|
||||
eth1:
|
||||
addresses:
|
||||
- 192.168.1.100/24
|
||||
gateway4: 192.168.1.1`
|
||||
|
||||
result, err := updateNetplanDNS(content, "8.8.8.8", "114.114.114.114")
|
||||
s.NoError(err)
|
||||
s.Contains(result, "nameservers:")
|
||||
s.Contains(result, "8.8.8.8")
|
||||
s.Contains(result, "114.114.114.114")
|
||||
}
|
||||
|
||||
// TestUpdateNetplanDNSWithRoutes 测试带路由配置的接口
|
||||
func (s *DNSTestSuite) TestUpdateNetplanDNSWithRoutes() {
|
||||
content := `network:
|
||||
version: 2
|
||||
ethernets:
|
||||
eth0:
|
||||
dhcp4: false
|
||||
addresses:
|
||||
- 10.0.0.10/24
|
||||
routes:
|
||||
- to: default
|
||||
via: 10.0.0.1`
|
||||
|
||||
result, err := updateNetplanDNS(content, "223.5.5.5", "223.6.6.6")
|
||||
s.NoError(err)
|
||||
s.Contains(result, "nameservers:")
|
||||
s.Contains(result, "223.5.5.5")
|
||||
s.Contains(result, "223.6.6.6")
|
||||
// 路由配置应该被保留
|
||||
s.Contains(result, "routes:")
|
||||
}
|
||||
|
||||
// TestUpdateNetplanDNSWithBond 测试 bond 网络配置
|
||||
func (s *DNSTestSuite) TestUpdateNetplanDNSWithBond() {
|
||||
content := `network:
|
||||
version: 2
|
||||
ethernets:
|
||||
eth0: {}
|
||||
eth1: {}
|
||||
bonds:
|
||||
bond0:
|
||||
interfaces:
|
||||
- eth0
|
||||
- eth1
|
||||
addresses:
|
||||
- 10.0.0.100/24
|
||||
parameters:
|
||||
mode: active-backup
|
||||
primary: eth0`
|
||||
|
||||
result, err := updateNetplanDNS(content, "8.8.8.8", "8.8.4.4")
|
||||
s.NoError(err)
|
||||
s.Contains(result, "nameservers:")
|
||||
s.Contains(result, "8.8.8.8")
|
||||
// bond 配置应该被保留
|
||||
s.Contains(result, "bonds:")
|
||||
s.Contains(result, "bond0:")
|
||||
}
|
||||
|
||||
// TestUpdateNetplanDNSWithRenderer 测试带 renderer 的配置
|
||||
func (s *DNSTestSuite) TestUpdateNetplanDNSWithRenderer() {
|
||||
content := `network:
|
||||
version: 2
|
||||
renderer: networkd
|
||||
ethernets:
|
||||
ens3:
|
||||
dhcp4: true`
|
||||
|
||||
result, err := updateNetplanDNS(content, "8.8.8.8", "")
|
||||
s.NoError(err)
|
||||
s.Contains(result, "renderer: networkd")
|
||||
s.Contains(result, "nameservers:")
|
||||
}
|
||||
|
||||
// TestUpdateNetplanDNSPreserveSearch 测试保留 search 域
|
||||
func (s *DNSTestSuite) TestUpdateNetplanDNSPreserveSearch() {
|
||||
content := `network:
|
||||
version: 2
|
||||
ethernets:
|
||||
eth0:
|
||||
dhcp4: true
|
||||
nameservers:
|
||||
addresses: [1.1.1.1]
|
||||
search: [example.com, local]`
|
||||
|
||||
result, err := updateNetplanDNS(content, "8.8.8.8", "8.8.4.4")
|
||||
s.NoError(err)
|
||||
s.Contains(result, "8.8.8.8")
|
||||
s.Contains(result, "8.8.4.4")
|
||||
// 旧 DNS 应该被替换
|
||||
s.NotContains(result, "1.1.1.1")
|
||||
// search 域应该被保留
|
||||
s.Contains(result, "search:")
|
||||
s.Contains(result, "example.com")
|
||||
}
|
||||
|
||||
// TestUpdateNetplanDNSEmptyConfig 测试空配置
|
||||
// 注意:当配置为空时,会尝试检测活动网络接口并自动添加配置
|
||||
func (s *DNSTestSuite) TestUpdateNetplanDNSEmptyConfig() {
|
||||
content := `network:
|
||||
version: 2`
|
||||
|
||||
result, err := updateNetplanDNS(content, "8.8.8.8", "")
|
||||
// 如果系统有活动网络接口,应该成功
|
||||
// 如果没有(如在 CI 环境),应该返回错误
|
||||
if err == nil {
|
||||
s.Contains(result, "nameservers:")
|
||||
s.Contains(result, "8.8.8.8")
|
||||
}
|
||||
}
|
||||
|
||||
// TestUpdateNetplanDNSInvalidYAML 测试无效的 YAML
|
||||
func (s *DNSTestSuite) TestUpdateNetplanDNSInvalidYAML() {
|
||||
content := `network:
|
||||
version: 2
|
||||
ethernets:
|
||||
eth0:
|
||||
invalid yaml here`
|
||||
|
||||
_, err := updateNetplanDNS(content, "8.8.8.8", "")
|
||||
s.Error(err)
|
||||
}
|
||||
|
||||
// TestUpdateNetplanDNSWithMatch 测试带 match 规则的配置
|
||||
func (s *DNSTestSuite) TestUpdateNetplanDNSWithMatch() {
|
||||
content := `network:
|
||||
version: 2
|
||||
ethernets:
|
||||
id0:
|
||||
match:
|
||||
macaddress: "00:11:22:33:44:55"
|
||||
set-name: eth0
|
||||
dhcp4: true`
|
||||
|
||||
result, err := updateNetplanDNS(content, "8.8.8.8", "")
|
||||
s.NoError(err)
|
||||
s.Contains(result, "nameservers:")
|
||||
s.Contains(result, "match:")
|
||||
s.Contains(result, "macaddress")
|
||||
}
|
||||
|
||||
// TestUpdateNetplanDNSWithVlan 测试 VLAN 配置
|
||||
func (s *DNSTestSuite) TestUpdateNetplanDNSWithVlan() {
|
||||
content := `network:
|
||||
version: 2
|
||||
ethernets:
|
||||
eth0:
|
||||
dhcp4: false
|
||||
vlans:
|
||||
vlan100:
|
||||
id: 100
|
||||
link: eth0
|
||||
addresses:
|
||||
- 192.168.100.1/24`
|
||||
|
||||
result, err := updateNetplanDNS(content, "8.8.8.8", "8.8.4.4")
|
||||
s.NoError(err)
|
||||
s.Contains(result, "nameservers:")
|
||||
s.Contains(result, "vlans:")
|
||||
s.Contains(result, "vlan100:")
|
||||
}
|
||||
|
||||
func (s *DNSTestSuite) TestFindNetplanConfig() {
|
||||
// findNetplanConfig 应该能正常执行不崩溃
|
||||
// 在实际系统上可能会找到配置文件也可能找不到
|
||||
configPath, err := findNetplanConfig()
|
||||
if err == nil {
|
||||
// 如果找到了配置文件,验证文件确实存在
|
||||
s.FileExists(configPath)
|
||||
}
|
||||
// 无论是否找到配置文件,函数都应该正常返回
|
||||
}
|
||||
|
||||
func (s *DNSTestSuite) TestSetDNSWithResolvConf() {
|
||||
// 这个测试需要 root 权限才能写入 /etc/resolv.conf
|
||||
// 在非特权环境中跳过
|
||||
s.T().Skip("需要 root 权限")
|
||||
}
|
||||
|
||||
func (s *DNSTestSuite) TestGetDNS() {
|
||||
// GetDNS 应该能返回当前的 DNS 配置
|
||||
dns, manager, err := GetDNS()
|
||||
// 即使出错也不应该 panic
|
||||
if err != nil {
|
||||
s.T().Logf("获取 DNS 出错(可能没有权限): %v", err)
|
||||
return
|
||||
}
|
||||
s.NotNil(dns)
|
||||
s.True(manager >= ManagerUnknown && manager <= ManagerResolvConf)
|
||||
}
|
||||
|
||||
// TestIsValidNetworkInterface 测试网络接口名称验证
|
||||
func (s *DNSTestSuite) TestIsValidNetworkInterface() {
|
||||
// 有效的接口名
|
||||
s.True(isValidNetworkInterface("eth0"))
|
||||
s.True(isValidNetworkInterface("ens3"))
|
||||
s.True(isValidNetworkInterface("enp0s3"))
|
||||
s.True(isValidNetworkInterface("wlan0"))
|
||||
s.True(isValidNetworkInterface("bond0"))
|
||||
s.True(isValidNetworkInterface("br0"))
|
||||
|
||||
// 无效的接口名(虚拟接口)
|
||||
s.False(isValidNetworkInterface("lo"))
|
||||
s.False(isValidNetworkInterface("docker0"))
|
||||
s.False(isValidNetworkInterface("veth12345"))
|
||||
s.False(isValidNetworkInterface("br-abc123"))
|
||||
s.False(isValidNetworkInterface("virbr0"))
|
||||
s.False(isValidNetworkInterface("tun0"))
|
||||
s.False(isValidNetworkInterface("tap0"))
|
||||
s.False(isValidNetworkInterface("flannel.1"))
|
||||
s.False(isValidNetworkInterface("cni0"))
|
||||
s.False(isValidNetworkInterface("cali12345"))
|
||||
s.False(isValidNetworkInterface(""))
|
||||
}
|
||||
|
||||
// TestDetectActiveInterface 测试活动接口检测
|
||||
func (s *DNSTestSuite) TestDetectActiveInterface() {
|
||||
// 这个测试依赖系统环境,只验证函数不会 panic
|
||||
iface := detectActiveInterface()
|
||||
// 在有网络的环境下应该能检测到接口
|
||||
// 在 CI 环境可能返回空字符串
|
||||
if iface != "" {
|
||||
s.True(isValidNetworkInterface(iface))
|
||||
}
|
||||
}
|
||||
@@ -5020,6 +5020,10 @@ msgstr "Private Key"
|
||||
msgid "DNS modifications will revert to default after system restart."
|
||||
msgstr "DNS modifications will revert to default after system restart."
|
||||
|
||||
#: src/views/toolbox/SystemView.vue:93
|
||||
msgid "Current DNS manager: %{ manager }"
|
||||
msgstr "Current DNS manager: %{ manager }"
|
||||
|
||||
#: src/views/toolbox/SystemView.vue:92
|
||||
msgid "Enter primary DNS server"
|
||||
msgstr ""
|
||||
|
||||
@@ -5163,6 +5163,10 @@ msgstr ""
|
||||
msgid "DNS modifications will revert to default after system restart."
|
||||
msgstr ""
|
||||
|
||||
#: src/views/toolbox/SystemView.vue:93
|
||||
msgid "Current DNS manager: %{ manager }"
|
||||
msgstr ""
|
||||
|
||||
#: src/views/toolbox/SystemView.vue:92
|
||||
msgid "Enter primary DNS server"
|
||||
msgstr ""
|
||||
|
||||
@@ -4874,6 +4874,10 @@ msgstr "下载私钥"
|
||||
msgid "DNS modifications will revert to default after system restart."
|
||||
msgstr "DNS 修改将在系统重启后恢复为默认设置。"
|
||||
|
||||
#: src/views/toolbox/SystemView.vue:93
|
||||
msgid "Current DNS manager: %{ manager }"
|
||||
msgstr "当前 DNS 管理方式:%{ manager }"
|
||||
|
||||
#: src/views/toolbox/SystemView.vue:92
|
||||
msgid "Enter primary DNS server"
|
||||
msgstr "输入主 DNS 服务器"
|
||||
|
||||
@@ -4856,6 +4856,10 @@ msgstr ""
|
||||
msgid "DNS modifications will revert to default after system restart."
|
||||
msgstr "DNS 修改將在系統重新啟動後恢復為預設設置。"
|
||||
|
||||
#: src/views/toolbox/SystemView.vue:93
|
||||
msgid "Current DNS manager: %{ manager }"
|
||||
msgstr "目前 DNS 管理方式:%{ manager }"
|
||||
|
||||
#: src/views/toolbox/SystemView.vue:92
|
||||
msgid "Enter primary DNS server"
|
||||
msgstr ""
|
||||
|
||||
@@ -22,9 +22,12 @@ const timezone = ref('')
|
||||
const timezones = ref<any[]>([])
|
||||
const time = ref(DateTime.now().toMillis())
|
||||
|
||||
const dnsManager = ref('')
|
||||
|
||||
useRequest(system.dns()).onSuccess(({ data }) => {
|
||||
dns1.value = data[0]
|
||||
dns2.value = data[1]
|
||||
dns1.value = data.dns?.[0] ?? ''
|
||||
dns2.value = data.dns?.[1] ?? ''
|
||||
dnsManager.value = data.manager
|
||||
})
|
||||
useRequest(system.swap()).onSuccess(({ data }) => {
|
||||
swap.value = data.size
|
||||
@@ -84,7 +87,14 @@ const handleSyncTime = () => {
|
||||
<n-tabs v-model:value="currentTab" type="line" placement="left" animated>
|
||||
<n-tab-pane name="dns" tab="DNS">
|
||||
<n-flex vertical>
|
||||
<n-alert type="warning">
|
||||
<n-alert type="info">
|
||||
{{
|
||||
$gettext('Current DNS manager: %{ manager }', {
|
||||
manager: dnsManager
|
||||
})
|
||||
}}
|
||||
</n-alert>
|
||||
<n-alert v-if="dnsManager === 'resolv.conf'" type="warning">
|
||||
{{ $gettext('DNS modifications will revert to default after system restart.') }}
|
||||
</n-alert>
|
||||
<n-form>
|
||||
|
||||
Reference in New Issue
Block a user