Files
gluetun/internal/openvpn/extract/extract.go
T
Quentin McGaw 4a78989d9d chore: do not use sentinel errors when unneeded
- main reason being it's a burden to always define sentinel errors at global scope, wrap them with `%w` instead of using a string directly
- only use sentinel errors when it has to be checked using `errors.Is`
- replace all usage of these sentinel errors in `fmt.Errorf` with direct strings that were in the sentinel error
- exclude the sentinel error definition requirement from .golangci.yml
- update unit tests to use ContainersError instead of ErrorIs so it stays as a "not a change detector test" without requiring a sentinel error
2026-05-02 03:29:46 +00:00

162 lines
4.1 KiB
Go

package extract
import (
"errors"
"fmt"
"net/netip"
"strconv"
"strings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
)
func extractDataFromLines(lines []string) (
connection models.Connection, err error,
) {
for i, line := range lines {
hashSymbolIndex := strings.Index(line, "#")
if hashSymbolIndex >= 0 {
line = line[:hashSymbolIndex]
}
ip, port, protocol, err := extractDataFromLine(line)
if err != nil {
return connection, fmt.Errorf("on line %d: %w", i+1, err)
}
connection.UpdateEmptyWith(ip, port, protocol)
if connection.Protocol != "" && connection.IP.IsValid() {
break
}
}
if !connection.IP.IsValid() {
return connection, errors.New("remote line not found")
}
if connection.Protocol == "" {
connection.Protocol = constants.UDP
}
if connection.Port == 0 {
connection.Port = 1194
if strings.HasPrefix(connection.Protocol, "tcp") {
connection.Port = 443
}
}
return connection, nil
}
func extractDataFromLine(line string) (
ip netip.Addr, port uint16, protocol string, err error,
) {
switch {
case strings.HasPrefix(line, "proto "):
protocol, err = extractProto(line)
if err != nil {
return ip, 0, "", fmt.Errorf("extracting protocol from proto line: %w", err)
}
return ip, 0, protocol, nil
case strings.HasPrefix(line, "remote "):
ip, port, protocol, err = extractRemote(line)
if err != nil {
return ip, 0, "", fmt.Errorf("extracting from remote line: %w", err)
}
return ip, port, protocol, nil
case strings.HasPrefix(line, "port "):
port, err = extractPort(line)
if err != nil {
return ip, 0, "", fmt.Errorf("extracting from port line: %w", err)
}
return ip, port, "", nil
}
return ip, 0, "", nil
}
func extractProto(line string) (protocol string, err error) {
fields := strings.Fields(line)
if len(fields) != 2 { //nolint:mnd
return "", fmt.Errorf("proto line has not 2 fields as expected: %s", line)
}
return parseProto(fields[1])
}
func parseProto(field string) (protocol string, err error) {
switch field {
case "tcp", "tcp4", "tcp6", "tcp-client":
// tcp4, tcp6 can be assimilated as tcp since the IP version is
// determined by the remote IP address version.
// tcp-client is a synonym of tcp for OpenVPN 2.5+ acting in client mode.
return constants.TCP, nil
case "udp", "udp4", "udp6":
// udp4, udp6 can be assimilated as udp since the IP version is
// determined by the remote IP address version.
return constants.UDP, nil
default:
return "", fmt.Errorf("network protocol not supported: %s", field)
}
}
func extractRemote(line string) (ip netip.Addr, port uint16,
protocol string, err error,
) {
fields := strings.Fields(line)
n := len(fields)
if n < 2 || n > 4 {
return netip.Addr{}, 0, "", fmt.Errorf("remote line has not 2 fields as expected: %s", line)
}
host := fields[1]
ip, err = netip.ParseAddr(host)
if err != nil {
return netip.Addr{}, 0, "", fmt.Errorf("host is not an IP address: %s", host)
// TODO resolve hostname once there is an option to allow it through
// the firewall before the VPN is up.
}
if n > 2 { //nolint:mnd
portInt, err := strconv.Atoi(fields[2])
if err != nil {
return netip.Addr{}, 0, "", fmt.Errorf("port is not valid: %s", line)
} else if portInt < 1 || portInt > 65535 {
return netip.Addr{}, 0, "", fmt.Errorf("port is not valid: %d must be between 1 and 65535", portInt)
}
port = uint16(portInt)
}
if n > 3 { //nolint:mnd
protocol, err = parseProto(fields[3])
if err != nil {
return netip.Addr{}, 0, "", fmt.Errorf("parsing protocol from remote line: %w", err)
}
}
return ip, port, protocol, nil
}
func extractPort(line string) (port uint16, err error) {
fields := strings.Fields(line)
const expectedFieldsCount = 2
if len(fields) != expectedFieldsCount {
return 0, fmt.Errorf("post line has not 2 fields as expected: %s", line)
}
portInt, err := strconv.Atoi(fields[1])
if err != nil {
return 0, fmt.Errorf("port is not valid: %s", line)
} else if portInt < 1 || portInt > 65535 {
return 0, fmt.Errorf("port is not valid: %d must be between 1 and 65535", portInt)
}
port = uint16(portInt)
return port, nil
}