mirror of
https://github.com/qdm12/gluetun.git
synced 2026-05-06 20:10:11 +02:00
4a78989d9d
- 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
162 lines
4.1 KiB
Go
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
|
|
}
|