Fix OpenVPN start: CIDR→netmask, dhcp-option DNS, ccd dir, dh none ECDHE
Four bugs prevented OpenVPN from actually listening on its UDP port: 1. server.conf line 'server 10.8.0.0/24' — OpenVPN 2.5 rejects CIDR. Fix: cidrToServerDirective() converts CIDR to 'NETWORK NETMASK' (e.g. '10.8.0.0 255.255.255.0') using net.ParseCIDR. 2. push_dns '1.1.1.1 8.8.8.8' was emitted as a single push directive instead of multiple 'push dhcp-option DNS x' lines. Fix: split space-separated IPs into individual push directives; pass through if already 'dhcp-option ...' form. 3. client-config-dir referenced a ccd/ directory that was never created. Fix: CreateInstance now MkdirAll(ccd); WriteServerConf also defensively MkdirAll(extraDir) before writing the directive. 4. dh.pem generated with 1024-bit DH params → OpenSSL 3.0 refuses with 'dh key too small'. Fix: use 'dh none' in server.conf so OpenVPN 2.4+ uses ECDHE key exchange — no DH params needed at all. Removed the slow openssl dhparam generation from EnsureCA. Verified: instance starts, OpenVPN parses config successfully, now fails only at TUNSETIFF (expected: requires root/CAP_NET_ADMIN).
This commit is contained in:
@@ -64,12 +64,8 @@ func (m *Manager) EnsureCA() error {
|
||||
-days 3650 -subj "/CN=OpenVPN-Manager-CA" 2>/dev/null`); err != nil {
|
||||
return fmt.Errorf("generate CA: %w", err)
|
||||
}
|
||||
// DH 参数(轻量: 1024,生产可改为 2048/4096)
|
||||
if _, err := os.Stat(filepath.Join(m.pkiDir, "dh.pem")); os.IsNotExist(err) {
|
||||
if err := runShell(`openssl dhparam -out "` + m.pkiDir + `/dh.pem" 1024 2>/dev/null`); err != nil {
|
||||
return fmt.Errorf("generate DH: %w", err)
|
||||
}
|
||||
}
|
||||
// DH 参数:用 "dh none" 走 ECDHE,不再需要 dh.pem 文件。
|
||||
// (保留目录以兼容旧配置)
|
||||
// TLS-Auth key
|
||||
if _, err := os.Stat(filepath.Join(m.pkiDir, "ta.key")); os.IsNotExist(err) {
|
||||
if err := runShell(`openvpn --genkey secret "` + m.pkiDir + `/ta.key"`); err != nil {
|
||||
@@ -93,10 +89,12 @@ func (m *Manager) WriteServerConf(in *model.Instance, extraDir string) error {
|
||||
conf.WriteString("ca " + filepath.Join(m.pkiDir, "ca.crt") + "\n")
|
||||
conf.WriteString("cert " + filepath.Join(m.PKIPath(in.Name), "issued", "server.crt") + "\n")
|
||||
conf.WriteString("key " + filepath.Join(m.PKIPath(in.Name), "private", "server.key") + "\n")
|
||||
conf.WriteString("dh " + filepath.Join(m.pkiDir, "dh.pem") + "\n")
|
||||
// 用 "dh none" 让 OpenVPN 走 ECDHE 密钥交换(OpenVPN 2.4+),
|
||||
// 不需要 DH 参数文件,既安全又避免 openssl dhparam 2048 的长等待。
|
||||
conf.WriteString("dh none\n")
|
||||
conf.WriteString("tls-auth " + filepath.Join(m.pkiDir, "ta.key") + " 0\n")
|
||||
conf.WriteString("topology subnet\n")
|
||||
conf.WriteString("server " + in.Subnet + "\n")
|
||||
conf.WriteString("server " + cidrToServerDirective(in.Subnet) + "\n")
|
||||
conf.WriteString("ifconfig-pool-persist " + filepath.Join(dir, "ipp.txt") + "\n")
|
||||
conf.WriteString("keepalive 10 120\n")
|
||||
conf.WriteString("persist-key\npersist-tun\n")
|
||||
@@ -112,7 +110,14 @@ func (m *Manager) WriteServerConf(in *model.Instance, extraDir string) error {
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
conf.WriteString("push \"" + line + "\"\n")
|
||||
// 支持 "1.1.1.1 8.8.8.8" 或 "dhcp-option DNS 1.1.1.1" 两种写法
|
||||
if strings.HasPrefix(line, "dhcp-option") {
|
||||
conf.WriteString("push \"" + line + "\"\n")
|
||||
} else {
|
||||
for _, ip := range strings.Fields(line) {
|
||||
conf.WriteString("push \"dhcp-option DNS " + ip + "\"\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if in.PushRoutes != "" {
|
||||
@@ -125,6 +130,7 @@ func (m *Manager) WriteServerConf(in *model.Instance, extraDir string) error {
|
||||
}
|
||||
}
|
||||
if extraDir != "" {
|
||||
_ = os.MkdirAll(extraDir, 0o755)
|
||||
conf.WriteString("client-config-dir " + extraDir + "\n")
|
||||
}
|
||||
if in.AccessMode == model.AccessWhitelist {
|
||||
@@ -562,6 +568,21 @@ func atoi64(s string) int64 {
|
||||
return n
|
||||
}
|
||||
|
||||
// cidrToServerDirective 把 "10.8.0.0/24" 转成 OpenVPN server 指令需要的
|
||||
// "10.8.0.0 255.255.255.0" 格式(网络地址 + 点分掩码)。
|
||||
// 如果输入不含 "/"(已经是 "ip mask" 形式),原样返回。
|
||||
func cidrToServerDirective(subnet string) string {
|
||||
subnet = strings.TrimSpace(subnet)
|
||||
if !strings.Contains(subnet, "/") {
|
||||
return subnet
|
||||
}
|
||||
_, ipnet, err := net.ParseCIDR(subnet)
|
||||
if err != nil {
|
||||
return subnet // 让 OpenVPN 报错,总比生成错误格式好
|
||||
}
|
||||
return ipnet.IP.String() + " " + net.IP(ipnet.Mask).String()
|
||||
}
|
||||
|
||||
func pemTrim(b []byte) string {
|
||||
return strings.TrimSpace(string(b))
|
||||
}
|
||||
Reference in New Issue
Block a user