package extract import ( "errors" "fmt" "net/netip" "strconv" "strings" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/models" ) var ( errRemoteLineNotFound = errors.New("remote line not found") ) func extractDataFromLines(lines []string) ( connection models.Connection, err error) { for i, line := range lines { 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, errRemoteLineNotFound } if connection.Protocol == "" { connection.Protocol = constants.UDP } if connection.Port == 0 { connection.Port = 1194 if connection.Protocol == constants.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 } return ip, 0, "", nil } var ( errProtoLineFieldsCount = errors.New("proto line has not 2 fields as expected") errProtocolNotSupported = errors.New("network protocol not supported") ) func extractProto(line string) (protocol string, err error) { fields := strings.Fields(line) if len(fields) != 2 { //nolint:gomnd return "", fmt.Errorf("%w: %s", errProtoLineFieldsCount, line) } switch fields[1] { case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6": default: return "", fmt.Errorf("%w: %s", errProtocolNotSupported, fields[1]) } return fields[1], nil } var ( errRemoteLineFieldsCount = errors.New("remote line has not 2 fields as expected") errHostNotIP = errors.New("host is not an IP address") errPortNotValid = errors.New("port is not valid") ) 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("%w: %s", errRemoteLineFieldsCount, line) } host := fields[1] ip, err = netip.ParseAddr(host) if err != nil { return netip.Addr{}, 0, "", fmt.Errorf("%w: %s", errHostNotIP, host) // TODO resolve hostname once there is an option to allow it through // the firewall before the VPN is up. } if n > 2 { //nolint:gomnd portInt, err := strconv.Atoi(fields[2]) if err != nil { return netip.Addr{}, 0, "", fmt.Errorf("%w: %s", errPortNotValid, line) } else if portInt < 1 || portInt > 65535 { return netip.Addr{}, 0, "", fmt.Errorf("%w: %d must be between 1 and 65535", errPortNotValid, portInt) } port = uint16(portInt) } if n > 3 { //nolint:gomnd switch fields[3] { case "tcp", "udp": protocol = fields[3] default: return netip.Addr{}, 0, "", fmt.Errorf("%w: %s", errProtocolNotSupported, fields[3]) } } return ip, port, protocol, nil }