-
-
Notifications
You must be signed in to change notification settings - Fork 631
feat(cryptostorm): add WireGuard and OpenVPN provider support #3164
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
218d5b1
33410b3
c8d715d
72533a8
2da1180
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,7 +43,8 @@ type PortForwarding struct { | |
| // forwarded ports. The redirection is disabled if it is the slice [0], | ||
| // which is its default as well. If set and not [0], its length must match | ||
| // the PortsCount value, such that each forwarded port is redirected to | ||
| // the corresponding listening port. | ||
| // the corresponding listening port. For Cryptostorm, ListeningPorts[0] | ||
| // also specifies the port to request from the forwarding server (30000–65535). | ||
| ListeningPorts []uint16 `json:"listening_port"` | ||
| // PortsCount is the number of ports to forward. It is optional for ProtonVPN | ||
| // and be between 1 and 5. For other providers, it must be set to 1 if port | ||
|
|
@@ -66,6 +67,7 @@ func (p PortForwarding) Validate(vpnProvider string) (err error) { | |
| providerSelected = *p.Provider | ||
| } | ||
| validProviders := []string{ | ||
| providers.Cryptostorm, | ||
| providers.Perfectprivacy, | ||
| providers.PrivateInternetAccess, | ||
| providers.Privatevpn, | ||
|
|
@@ -143,14 +145,21 @@ func (p *PortForwarding) OverrideWith(other PortForwarding) { | |
| p.Password = gosettings.OverrideWithComparable(p.Password, other.Password) | ||
| } | ||
|
|
||
| func (p *PortForwarding) setDefaults() { | ||
| func (p *PortForwarding) setDefaults(vpnProvider string) { | ||
| p.Enabled = gosettings.DefaultPointer(p.Enabled, false) | ||
| p.Provider = gosettings.DefaultPointer(p.Provider, "") | ||
| p.Filepath = gosettings.DefaultPointer(p.Filepath, "/tmp/gluetun/forwarded_port") | ||
| p.UpCommand = gosettings.DefaultPointer(p.UpCommand, "") | ||
| p.DownCommand = gosettings.DefaultPointer(p.DownCommand, "") | ||
| p.ListeningPorts = gosettings.DefaultSlice(p.ListeningPorts, []uint16{0}) // disabled | ||
| p.PortsCount = gosettings.DefaultComparable(p.PortsCount, 1) | ||
| // For Cryptostorm, default PortsCount to the number of listening ports | ||
| // specified, since each listening port maps directly to a requested port. | ||
| defaultPortsCount := uint16(1) | ||
| if vpnProvider == providers.Cryptostorm && | ||
| len(p.ListeningPorts) > 0 && p.ListeningPorts[0] != 0 { | ||
| defaultPortsCount = uint16(len(p.ListeningPorts)) | ||
| } | ||
| p.PortsCount = gosettings.DefaultComparable(p.PortsCount, defaultPortsCount) | ||
|
Comment on lines
+155
to
+162
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since the Validate method already takes the vpn provider string as argument, let's just validate that the user sets the ports count to the same number of listening ports, and revert the changes to |
||
| } | ||
|
|
||
| func (p PortForwarding) String() string { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package cryptostorm | ||
|
|
||
| import ( | ||
| "github.com/qdm12/gluetun/internal/configuration/settings" | ||
| "github.com/qdm12/gluetun/internal/models" | ||
| "github.com/qdm12/gluetun/internal/provider/utils" | ||
| ) | ||
|
|
||
| func (p *Provider) GetConnection(selection settings.ServerSelection, ipv6Supported bool) ( | ||
| connection models.Connection, err error, | ||
| ) { | ||
| defaults := utils.NewConnectionDefaults(443, 443, 443) //nolint:mnd | ||
| return utils.GetConnection(p.Name(), | ||
| p.storage, selection, defaults, ipv6Supported, p.connPicker) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package cryptostorm | ||
|
|
||
| import ( | ||
| "github.com/qdm12/gluetun/internal/configuration/settings" | ||
| "github.com/qdm12/gluetun/internal/constants/openvpn" | ||
| "github.com/qdm12/gluetun/internal/models" | ||
| "github.com/qdm12/gluetun/internal/provider/utils" | ||
| ) | ||
|
|
||
| func (p *Provider) OpenVPNConfig(connection models.Connection, | ||
| settings settings.OpenVPN, ipv6Supported bool, | ||
| ) (lines []string) { | ||
| providerSettings := utils.OpenVPNProviderSettings{ | ||
| RemoteCertTLS: true, | ||
| AuthUserPass: true, | ||
| Ciphers: []string{openvpn.AES256gcm}, | ||
| VerifyX509Type: "name", | ||
| TLSCrypt: "4875d729589689955012a2ee77f180ecb815c4a336c719c11241a058dafaae00806bbc21d5f1abad085341a3fca4b4f93949151c2979b4ee4390e8d9443acb0061d537f1e9157e45f542c3648f56330505f3eaff97ef82ee063b9d88bb9d5aa0060428455b51a2a4fd929d9af4b94adcb0a4acaa14ff62a9b0f4f9f0b3f01e71fc98a6c60e8584f4deb3de793a5a7bc27014c9369f9724bc810ef0d191b3020478eead725b3ae6aaef2e1030a197e417421f159ed54eb2629afcfb337cf9a0025bf1d5c0d820fffb219d0b4214043d2df27ed367b522945a5dadc748e2ca379e3971789dbdf609b3d9bfe866361b28e3c90589baa925157ad833093a5a7bede5", //nolint:lll | ||
| CAs: []string{"MIICCzCCAW2gAwIBAgIUMRTTJ6nuPjmSxaRfbw5f+dZ9d/gwCgYIKoZIzj0EAwQwGTEXMBUGA1UEAwwOY3J5cHRvc3Rvcm0gQ0EwHhcNMTgwOTE3MjAwODU4WhcNMzgwOTE3MjAwODU4WjAZMRcwFQYDVQQDDA5jcnlwdG9zdG9ybSBDQTCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAEARKu20PBrr226TP6mQQGtzCqQqBKfGaA05Ml5nrGSV6wzBQDQga4/cPepGrE/tpzRX72KSfZD6nJfQLYen7kdc3PAEvWFBhCovq7e4L6xJ5qV5aMf89QjNhJ/xn//dlxE8Z6UfIx63dJX9q3EHNxateU84lDkbCrqckkckcZF4C1a9Ooo1AwTjAdBgNVHQ4EFgQUdaVDaoi48Yf2RugXqJ4yJ4Z4utgwHwYDVR0jBBgwFoAUdaVDaoi48Yf2RugXqJ4yJ4Z4utgwDAYDVR0TBAUwAwEB/zAKBggqhkjOPQQDBAOBiwAwgYcCQVcCw/8OVpNqltDYczqHmX4sMRsZTY0iIzl1rYY/0/ZPIvzjlMFnouHwb8asJZRMBNECq7u9PCbG3jdu6lYtcCm+AkIB3IYYKuXLKW7ucdttNODBqH2Rail+9oBWTV2ZFKVVwELlKadHx9UvAcpAaV1alkN80CgI2tad2/qVdpSIQpfVvTI="}, //nolint:lll | ||
| } | ||
| return utils.OpenVPNConfig(providerSettings, connection, settings, ipv6Supported) | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,264 @@ | ||||||||||||||||||||||
| package cryptostorm | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import ( | ||||||||||||||||||||||
| "context" | ||||||||||||||||||||||
| "encoding/json" | ||||||||||||||||||||||
| "fmt" | ||||||||||||||||||||||
| "io" | ||||||||||||||||||||||
| "io/fs" | ||||||||||||||||||||||
| "net/http" | ||||||||||||||||||||||
| "os" | ||||||||||||||||||||||
| "path/filepath" | ||||||||||||||||||||||
| "regexp" | ||||||||||||||||||||||
| "strconv" | ||||||||||||||||||||||
| "strings" | ||||||||||||||||||||||
| "time" | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| "github.com/qdm12/gluetun/internal/provider/utils" | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // portRangePattern matches the valid Cryptostorm port range 30000-65535. | ||||||||||||||||||||||
| const portRangePattern = `([3-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // ipPattern matches either an IPv4 address or a bracketed IPv6 address. | ||||||||||||||||||||||
| const ipPattern = `(?:\d+\.\d+\.\d+\.\d+|\[[0-9a-fA-F:]+\])` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // regexForwardPlainText matches plain text responses (e.g. from curl): | ||||||||||||||||||||||
| // | ||||||||||||||||||||||
| // 37.120.234.253:55555 -> 10.10.123.139:55555 | ||||||||||||||||||||||
| // [2400:a842:c46e:1::4a]:55555 -> [fd00:10:10::e0e0]:55555 | ||||||||||||||||||||||
| var regexForwardPlainText = regexp.MustCompile( | ||||||||||||||||||||||
| ipPattern + `:` + portRangePattern + `\s*->\s*` + ipPattern + `:\d+`) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // regexForwardHTML matches the HTML response from the port forwarding page. | ||||||||||||||||||||||
| // Each forwarded port has a hidden delete input: | ||||||||||||||||||||||
| // | ||||||||||||||||||||||
| // <input type="hidden" name="delfwd" value="30000"> | ||||||||||||||||||||||
| var regexForwardHTML = regexp.MustCompile( | ||||||||||||||||||||||
| `name="delfwd"\s+value="` + portRangePattern + `"`) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // portForwardURLs lists all port forwarding endpoints. | ||||||||||||||||||||||
| // IPv4 and IPv6 must both be registered for dual-stack support. | ||||||||||||||||||||||
| var portForwardURLs = []string{ | ||||||||||||||||||||||
| "http://10.31.33.7/fwd", | ||||||||||||||||||||||
| "http://[2001:db8::7]/fwd", | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
Comment on lines
+42
to
+45
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit just so it's "more" immutable - and then adapt callers to use
Suggested change
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // portForwardData is the data persisted to the port forward JSON file. | ||||||||||||||||||||||
| type portForwardData struct { | ||||||||||||||||||||||
| Ports []uint16 `json:"ports"` | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // PortForward registers a forwarded port with the Cryptostorm port forwarding server | ||||||||||||||||||||||
| // and returns the active forwarded ports. The server returns plain text listing | ||||||||||||||||||||||
| // current forwardings. We POST the desired port and parse the response. | ||||||||||||||||||||||
| // Valid port range is 30000-65535. | ||||||||||||||||||||||
| // See: https://cryptostorm.is/portfwd | ||||||||||||||||||||||
| func (p *Provider) PortForward(ctx context.Context, objects utils.PortForwardObjects) ( | ||||||||||||||||||||||
| internalToExternalPorts map[uint16]uint16, err error, | ||||||||||||||||||||||
| ) { | ||||||||||||||||||||||
| const timeout = 10 * time.Second | ||||||||||||||||||||||
| ctx, cancel := context.WithTimeout(ctx, timeout) | ||||||||||||||||||||||
| defer cancel() | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Determine the port to request: | ||||||||||||||||||||||
| // 1. Use VPN_PORT_FORWARDING_LISTENING_PORTS[0] if set (non-zero). | ||||||||||||||||||||||
| // 2. Otherwise try to read a previously persisted port. | ||||||||||||||||||||||
|
Comment on lines
+65
to
+66
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't really see the point of persisting the port then, the user likely would re-use the same |
||||||||||||||||||||||
| // 3. Otherwise return an error (Cryptostorm does not auto-assign ports). | ||||||||||||||||||||||
| var listeningPort uint16 | ||||||||||||||||||||||
| if len(objects.ListeningPorts) > 0 && objects.ListeningPorts[0] != 0 { | ||||||||||||||||||||||
| listeningPort = objects.ListeningPorts[0] | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| if listeningPort == 0 { | ||||||||||||||||||||||
| data, err := readPortForwardData(p.portForwardPath) | ||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||
| return nil, fmt.Errorf("reading persisted port forward data: %w", err) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| if len(data.Ports) > 0 { | ||||||||||||||||||||||
| listeningPort = data.Ports[0] | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if listeningPort == 0 { | ||||||||||||||||||||||
| return nil, fmt.Errorf("port forwarding not supported: set VPN_PORT_FORWARDING_LISTENING_PORTS to a value between 30000 and 65535") | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this seems wrong? Anyway likely remove if we only use |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| postBody := "port=" + strconv.FormatUint(uint64(listeningPort), 10) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Register the port with both IPv4 and IPv6 endpoints. | ||||||||||||||||||||||
| // The IPv6 endpoint may not be reachable if the tunnel has no IPv6, | ||||||||||||||||||||||
| // so we log a warning rather than failing hard. | ||||||||||||||||||||||
|
Comment on lines
+89
to
+90
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you could inject the IPv6 support status to the |
||||||||||||||||||||||
| requestedFound := false | ||||||||||||||||||||||
| for _, url := range portForwardURLs { | ||||||||||||||||||||||
| found, err := p.registerPort(ctx, objects.Client, url, postBody, listeningPort, objects) | ||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||
| if url == portForwardURLs[0] { | ||||||||||||||||||||||
| // IPv4 failure is fatal. | ||||||||||||||||||||||
| return nil, err | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| // IPv6 failure is a warning only. | ||||||||||||||||||||||
| objects.Logger.Warn("IPv6 port forward registration failed (" + url + "): " + err.Error()) | ||||||||||||||||||||||
| continue | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
Comment on lines
+92
to
+102
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit do it outside a for loop, this is a bit confusing that index 0 is ipv4 and index 1 is ipv6. Just have two code blocks following each-other, and ideally enable the ipv6 block in an if block if ipv6 is in fact supported. |
||||||||||||||||||||||
| if found { | ||||||||||||||||||||||
| requestedFound = true | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if !requestedFound { | ||||||||||||||||||||||
| return nil, fmt.Errorf("port forwarding not supported: requested port %d not found in server response", listeningPort) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| p.forwardedPort = listeningPort | ||||||||||||||||||||||
| internalToExternalPorts = map[uint16]uint16{listeningPort: listeningPort} | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Persist so the next restart can reuse this port without re-requesting. | ||||||||||||||||||||||
| if err := writePortForwardData(p.portForwardPath, portForwardData{Ports: []uint16{listeningPort}}); err != nil { | ||||||||||||||||||||||
| return nil, fmt.Errorf("persisting port forward data: %w", err) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return internalToExternalPorts, nil | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // registerPort POSTs the port to a single forwarding endpoint and returns | ||||||||||||||||||||||
| // whether the requested port was confirmed in the response. | ||||||||||||||||||||||
| func (p *Provider) registerPort(ctx context.Context, client *http.Client, | ||||||||||||||||||||||
|
Comment on lines
+123
to
+125
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit move this below KeepPortForward and above deletePortFromURL |
||||||||||||||||||||||
| url, postBody string, listeningPort uint16, objects utils.PortForwardObjects, | ||||||||||||||||||||||
| ) (requestedFound bool, err error) { | ||||||||||||||||||||||
| request, err := http.NewRequestWithContext(ctx, http.MethodPost, url, | ||||||||||||||||||||||
| strings.NewReader(postBody)) | ||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||
| return false, fmt.Errorf("creating HTTP request: %w", err) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| request.Header.Set("Content-Type", "application/x-www-form-urlencoded") | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| response, err := client.Do(request) | ||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||
| return false, fmt.Errorf("sending HTTP request: %w", err) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| defer response.Body.Close() | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if response.StatusCode != http.StatusOK { | ||||||||||||||||||||||
| return false, fmt.Errorf("HTTP status code not OK: %d %s", | ||||||||||||||||||||||
| response.StatusCode, response.Status) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| body, err := io.ReadAll(response.Body) | ||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||
| return false, fmt.Errorf("reading response body: %w", err) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Parse forwarded ports from the response. The server returns HTML to | ||||||||||||||||||||||
| // Go's HTTP client but plain text to curl, so we try both formats. | ||||||||||||||||||||||
| bodyStr := string(body) | ||||||||||||||||||||||
| matches := regexForwardPlainText.FindAllStringSubmatch(bodyStr, -1) | ||||||||||||||||||||||
| if len(matches) == 0 { | ||||||||||||||||||||||
| matches = regexForwardHTML.FindAllStringSubmatch(bodyStr, -1) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| if len(matches) == 0 { | ||||||||||||||||||||||
| return false, fmt.Errorf("port forwarding not supported: no active port forwards found in response") | ||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // The server response lists all currently active forwards for this session, | ||||||||||||||||||||||
| // which may include stale ports from prior runs. Delete any that are not | ||||||||||||||||||||||
| // the one we requested, then verify ours is present. | ||||||||||||||||||||||
| const base, bitSize = 10, 16 | ||||||||||||||||||||||
| for _, match := range matches { | ||||||||||||||||||||||
| portUint64, err := strconv.ParseUint(match[1], base, bitSize) | ||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||
| return false, fmt.Errorf("parsing port number %q: %w", match[1], err) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| port := uint16(portUint64) | ||||||||||||||||||||||
| if port == listeningPort { | ||||||||||||||||||||||
| requestedFound = true | ||||||||||||||||||||||
| continue | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| // Best-effort delete; log but don't fail if it errors. | ||||||||||||||||||||||
| if err := p.deletePortFromURL(ctx, client, url, port); err != nil { | ||||||||||||||||||||||
| objects.Logger.Warn("deleting stale port forward " + | ||||||||||||||||||||||
| strconv.FormatUint(uint64(port), 10) + " from " + url + ": " + err.Error()) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return requestedFound, nil | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| func (p *Provider) KeepPortForward(ctx context.Context, | ||||||||||||||||||||||
| objects utils.PortForwardObjects, | ||||||||||||||||||||||
| ) (err error) { | ||||||||||||||||||||||
| // Cryptostorm port assignments persist for the session; no keepalive needed. | ||||||||||||||||||||||
| <-ctx.Done() | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Best-effort deregister on teardown. Use a fresh context since the | ||||||||||||||||||||||
| // original is already cancelled. | ||||||||||||||||||||||
| if p.forwardedPort != 0 { | ||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can |
||||||||||||||||||||||
| const deleteTimeout = 5 * time.Second | ||||||||||||||||||||||
| deleteCtx, cancel := context.WithTimeout(context.Background(), deleteTimeout) | ||||||||||||||||||||||
| defer cancel() | ||||||||||||||||||||||
| for _, url := range portForwardURLs { | ||||||||||||||||||||||
| if err := p.deletePortFromURL(deleteCtx, objects.Client, url, p.forwardedPort); err != nil { | ||||||||||||||||||||||
| objects.Logger.Warn("deregistering port forward on teardown from " + url + ": " + err.Error()) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| p.forwardedPort = 0 | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return ctx.Err() | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| func (p *Provider) deletePortFromURL(ctx context.Context, client *http.Client, url string, port uint16) error { | ||||||||||||||||||||||
| body := strings.NewReader("delfwd=" + strconv.FormatUint(uint64(port), 10)) | ||||||||||||||||||||||
| request, err := http.NewRequestWithContext(ctx, http.MethodPost, url, body) | ||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||
| return fmt.Errorf("creating delete request: %w", err) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| request.Header.Set("Content-Type", "application/x-www-form-urlencoded") | ||||||||||||||||||||||
| response, err := client.Do(request) | ||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||
| return fmt.Errorf("sending delete request: %w", err) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| _ = response.Body.Close() | ||||||||||||||||||||||
| if response.StatusCode != http.StatusOK { | ||||||||||||||||||||||
| return fmt.Errorf("HTTP status code not OK: %d %s", | ||||||||||||||||||||||
| response.StatusCode, response.Status) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| return nil | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| func readPortForwardData(path string) (data portForwardData, err error) { | ||||||||||||||||||||||
| file, err := os.Open(path) | ||||||||||||||||||||||
| if os.IsNotExist(err) { | ||||||||||||||||||||||
| return data, nil | ||||||||||||||||||||||
| } else if err != nil { | ||||||||||||||||||||||
| return data, err | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| decoder := json.NewDecoder(file) | ||||||||||||||||||||||
| if err := decoder.Decode(&data); err != nil { | ||||||||||||||||||||||
| _ = file.Close() | ||||||||||||||||||||||
| return data, err | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return data, file.Close() | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| func writePortForwardData(path string, data portForwardData) (err error) { | ||||||||||||||||||||||
| const dirPermission = fs.FileMode(0o755) | ||||||||||||||||||||||
| if err := os.MkdirAll(filepath.Dir(path), dirPermission); err != nil { | ||||||||||||||||||||||
| return err | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const permission = fs.FileMode(0o644) | ||||||||||||||||||||||
| file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, permission) | ||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||
| return err | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| encoder := json.NewEncoder(file) | ||||||||||||||||||||||
| if err := encoder.Encode(data); err != nil { | ||||||||||||||||||||||
| _ = file.Close() | ||||||||||||||||||||||
| return err | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return file.Close() | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since cryptostorm appears to support multiple ports, I think it would be nice to support it here. That way that comment can be changed to: