Skip to content

feat(cryptostorm): add WireGuard and OpenVPN provider support - #3164

Open
mpatton125 wants to merge 5 commits into
passteque:masterfrom
mpatton125:claude/crazy-brahmagupta
Open

mpatton125 wants to merge 5 commits into
passteque:masterfrom
mpatton125:claude/crazy-brahmagupta

Conversation

@mpatton125

@mpatton125 mpatton125 commented Feb 26, 2026

Copy link
Copy Markdown

Description

  • Add cryptostorm.is as a new VPN provider with both WireGuard and OpenVPN support
  • Both protocols use port 443 (TCP/UDP), server data fetched from cryptostorm JSON API
  • Includes port forwarding support via cryptostorm's internal forwarding server

Issue

Fix #3112


🤖 Generated with Claude Code <--- Full disclosure

@qdm12 qdm12 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Big hallucination on the updating servers code. this needs to be fixed somehow, or this PR won't be accepted
  • Port forwarding: I need a human to check the information returned from the port forwarding endpoint when connected
  • When above is done, I need a human to verify openvpn tcp, udp, wireguard and port forwarding all work fine.

// their publicly available node list. If the upstream format changes,
// update the nodeData struct and parsing logic accordingly.
func fetchAPI(ctx context.Context, client *http.Client) (data []nodeData, err error) {
const url = "https://cryptostorm.is/wireguard/nodes.json"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is no json API, this url is the same as https://cryptostorm.is/wireguard. This returns html, not json and this will fail

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now scraping endpoint data from https://cryptostorm.is/wireguard.

func (p *Provider) GetConnection(selection settings.ServerSelection, ipv6Supported bool) (
connection models.Connection, err error,
) {
defaults := utils.NewConnectionDefaults(443, 443, 443) //nolint:mnd

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please double check openvpn udp tcp and wireguard all work with those 443 ports.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely both work with 443 - provider says they accept OpenVPN and Wireguard connections on any port (1-65535).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really ? Can they accept both openvpn udp and wireguard (udp) on the same port ?? That seems odd 🤔 Or do you pick the port on their web ui for example ?

Comment thread internal/provider/cryptostorm/openvpnconf.go Outdated
Comment thread internal/storage/servers.json Outdated
}
]
},
"cryptostorm": {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that was not obtained from the code in the updater, so it most likely pure hallucination; fix the updater and revert these changes.

Comment thread Dockerfile Outdated
// If the port is already forwarded (e.g. from a previous session) it will appear in
// the list regardless of whether the POST succeeded, so we treat that as success.
// Valid port range is 30000–65535.
// See: https://cryptostorm.is/port_forwarding

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

404 not found, possible hallucination? please fix

it's https://cryptostorm.is/portfwd

postBody = "port=" + strconv.FormatUint(uint64(objects.ListeningPort), 10)
}

pfURL := "http://" + cryptostormPFServer + "/fwd"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a comment for IPv6 it's http://[2001:db8::7]/fwd - that will be used later.

postBody = "port=" + strconv.FormatUint(uint64(objects.ListeningPort), 10)
}

pfURL := "http://" + cryptostormPFServer + "/fwd"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mpatton125 can you check what http://10.31.33.7/fwd returns when you are connected to the VPN? To confirm what the AI wrote is correct


const base, bitSize = 10, 16
for _, match := range matches {
portUint64, err := strconv.ParseUint(match[1], base, bitSize)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can there be multiple ports? 🤔

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes this provider allows multiple port forwards, per endpoint.

@qdm12 qdm12 Mar 10, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a maximum limit of ports forwarded??

Let's wait for #3208 to be merged, then this can use the new setting field PortsCount

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rebase on upstream master, which supports multiple ports. What's the number of ports limit?

Comment thread internal/provider/utils/portforward.go Outdated
Comment on lines +28 to +30
// ListeningPort is the port to request from the provider, used by Cryptostorm.
// A value of 0 means let the provider assign a port.
ListeningPort uint16

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mpatton125 please confirm if http://10.31.33.7/fwd tells you you can specify a port field, and if that port field can be set to 0 to let cryptostorm assign you a random port.

If this is the case:

  1. use the VPN_PORT_FORWARDING_LISTENING_PORT value to set this. If this one is not set, try to read it from /gluetun/portforward/cryptostorm.json. If this file does not exist, leave the ListeningPort field to 0 to let cryptostorm decide.
  2. in all cases, persist the resulting port(s) to /gluetun/portforward/cryptostorm.json

And also:

  • add a comment in internal/provider/privateinternetaccess/provider.go next to const jsonPortForwardPath = "/gluetun/piaportforward.json" saying TODO v4: move to /gluetun/portforward/privateinternetaccess.json
  • update the comment for the settings field VPN_PORT_FORWARDING_LISTENING_PORT and make sure it runs a no-op in case the forwarded port is already the value specified in VPN_PORT_FORWARDING_LISTENING_PORT

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes you can specify a port at that URL, from 30000-65535. No, it can't be 0 for a random port - user must specify.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, let's wait for #3208

note for future:

we'll then use VPN_PORT_FORWARDING_LISTENING_PORTS=8000,8001 to specify ports you want. We should then change func (p *PortForwarding) setDefaults() to func (p *PortForwarding) setDefaults(vpnProvider string) and set, if the vpnProvider is cryptostorm only, p.PortsCount = gosettings.DefaultComparable(p.PortsCount, len(p.ListeningPorts))

@qdm12
qdm12 marked this pull request as draft March 7, 2026 05:21
@qdm12
qdm12 force-pushed the master branch 2 times, most recently from 2c06921 to 9a5995f Compare March 16, 2026 13:48
@mpatton125
mpatton125 marked this pull request as ready for review April 15, 2026 00:49
@mpatton125
mpatton125 requested a review from qdm12 April 15, 2026 22:23
@mpatton125
mpatton125 force-pushed the claude/crazy-brahmagupta branch from cd21991 to e79b2df Compare April 23, 2026 00:02
@qdm12
qdm12 force-pushed the master branch 4 times, most recently from 40f126b to 44d5104 Compare May 3, 2026 04:29
Comment thread internal/configuration/settings/portforward.go Outdated
Comment thread internal/portforward/service/start.go Outdated
Comment thread internal/portforward/service/start.go Outdated
}

err = s.onNewPorts(ctx, ports)
if s.settings.ListeningPort != 0 && port != s.settings.ListeningPort {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(no action for now) to be updated after #3208 is merged - update to check by index ports[i] != s.settings.ListeningPorts[i] and also we should ensure both slices are sorted

Comment thread internal/provider/cryptostorm/updater/api.go Outdated
Comment thread internal/provider/cryptostorm/updater/api.go Outdated
Comment thread internal/provider/cryptostorm/connection.go Outdated

const base, bitSize = 10, 16
for _, match := range matches {
portUint64, err := strconv.ParseUint(match[1], base, bitSize)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rebase on upstream master, which supports multiple ports. What's the number of ports limit?

Comment thread internal/provider/cryptostorm/provider.go Outdated
client *http.Client, updaterWarner common.Warner,
parallelResolver common.ParallelResolver,
) *Provider {
const jsonPortForwardPath = "/gluetun/portforward/cryptostorm.json"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit rename to portForwardPath

PortsCount uint16
// ListeningPorts are the user-specified ports. For Cryptostorm, ListeningPorts[0]
// is the port to request from the forwarding server (must be 30000–65535).
// A nil or [0] value means fall back to the persisted port.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can there be multiple ports requested?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From some preliminary testing, I don't think you can request multiple ports in one POST request, but you can have multiple ports open if you request them one by one. You can close them all at once though:

➜ curl --request POST --data 'port=80808,80809' http://10.31.33.7/fwd
INVALID_PARAM_RECEIVED
Add a port:
wget -qO- http://10.31.33.7/fwd --post-data="port=31337"
curl -sS http://10.31.33.7/fwd -d port=31337
Delete one port:
wget -qO- http://10.31.33.7/fwd --post-data="delfwd=31337"
curl -sS http://10.31.33.7/fwd -d delfwd=31337
Delete all ports:
wget -qO- http://10.31.33.7/fwd --post-data="delallfwd=1"
curl -sS http://10.31.33.7/fwd -d delallfwd=1

Leaving the parameter empty, setting it to 0, or trying ports instead of port are all invalid. It seems you have to explicitly request one specific port at a time.

I'd like to see this merged, so let me know if there's anything else you'd like me to test.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, something we can add later (or now up to you) for multiple ports

Michael Patton and others added 2 commits June 29, 2026 10:37
…and IPv6

Implements Cryptostorm as a gluetun VPN provider supporting WireGuard and
OpenVPN protocols, dual-stack IPv4/IPv6 port forwarding via HTTP to the
Cryptostorm forwarding servers, and a server updater that fetches node data
from cryptostorm.is/wireguard.

Key changes:
- Provider struct using ConnectionPicker (aligned with master's pattern)
- Port forwarding with IPv4 (10.31.33.7) and IPv6 (2001:db8::7) endpoints
- Updater with simplified client-only struct (no parallelResolver/warner)
- DNS resolution via net.DefaultResolver in the updater
- setDefaults(vpnProvider) signature to set PortsCount default for Cryptostorm
- Self-redirect guard in portforward service onNewPorts()
- ListeningPorts support in PortForwardObjects

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…s module

When a provider's JSON file is not yet in the embedded gluetun-servers module
(e.g. newly added providers like Cryptostorm), start with an empty server list
instead of panicking. The updater will populate servers at runtime.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Michael Patton and others added 2 commits June 29, 2026 11:04
…errors

common.ErrPortForwardNotSupported and common.ErrHTTPStatusCodeNotOK were
removed from master. Replace with inline fmt.Errorf calls matching the
pattern used by other providers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@mpatton125
mpatton125 requested a review from qdm12 June 29, 2026 01:11
Without a version, checkVersions() discards any on-disk data for the
provider because version 0 never matches the file's version 1.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@amarevite

Copy link
Copy Markdown

What work remains before this can be merged? Is it's just linting and git tree management or are code changes needed before it can be merged?

@mpatton125

Copy link
Copy Markdown
Author

I'm not sure what needs to be done for it to be merged. It works - I've been using it for months.

@qdm12

qdm12 commented Sep 17, 2026

Copy link
Copy Markdown
Member

I'll review this post v3.42.0 release which I'm trying to get out soon - I'm still playing catchup with PRs these days, but I'll get to it !
You can use code from the fork/branch in the meantime.

Edit : I'll try to review it tomorrow fingers crossed !

Comment on lines +46 to +47
// the corresponding listening port. For Cryptostorm, ListeningPorts[0]
// also specifies the port to request from the forwarding server (30000–65535).

Copy link
Copy Markdown
Member

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:

Suggested change
// the corresponding listening port. For Cryptostorm, ListeningPorts[0]
// also specifies the port to request from the forwarding server (30000–65535).
// the corresponding listening port. For VPN providers supporting requesting
// specific ports (Cryptostorm), this is used to request ports.

Comment on lines +155 to +162
// 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)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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 setDefaults here.
By the way, can a user request a random port instead of a specific port?

Comment thread internal/portforward/service/start.go
Comment thread internal/provider/cryptostorm/updater/api.go
Comment on lines +15 to +24
seen := make(map[string]struct{}, len(hts))
hosts = make([]string, 0, len(hts))
for key := range hts {
host := extractHost(key)
if _, ok := seen[host]; !ok {
seen[host] = struct{}{}
hosts = append(hosts, host)
}
}
return hosts

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit a tiny bit shorter

Suggested change
seen := make(map[string]struct{}, len(hts))
hosts = make([]string, 0, len(hts))
for key := range hts {
host := extractHost(key)
if _, ok := seen[host]; !ok {
seen[host] = struct{}{}
hosts = append(hosts, host)
}
}
return hosts
hosts := slices.Collect(maps.Keys(hts))
for i, h := range hosts {
hosts[i] = extractHost(h)
}
slices.Sort(hosts)
return slices.Compact(hosts)

Comment on lines +92 to +102
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
}

@qdm12 qdm12 Sep 17, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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.

matches = regexForwardHTML.FindAllStringSubmatch(bodyStr, -1)
}
if len(matches) == 0 {
return false, fmt.Errorf("port forwarding not supported: no active port forwards found in response")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return false, fmt.Errorf("port forwarding not supported: no active port forwards found in response")
return false, fmt.Errorf("port forwarding not supported: no active port forwards found in response: %s", bodyStr)

Comment on lines +123 to +125
// 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,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit move this below KeepPortForward and above deletePortFromURL


// Best-effort deregister on teardown. Use a fresh context since the
// original is already cancelled.
if p.forwardedPort != 0 {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can forwardedPort be ever 0 when we call .KeepPortForward? I don't think so 🤔

Comment on lines +23 to +30
// Provider not yet in the gluetun-servers module; start with an empty list.
// The updater can still populate servers at runtime.
const serversPath = "/gluetun/servers/"
allServers.ProviderToServers[provider] = models.Servers{
Version: 1,
Filepath: filepath.Join(serversPath, filename),
}
continue

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this and in go.mod, add a replace github.com/qdm12/gluetun-servers github.com/mpatton125/gluetun-servers#main (or something of that nature) to get cryptostorm data from the PR? Typing from my phone but I'm curious if this works, since that would be useful for future VPN provider PRs!

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: cryptostorm.is VPN

3 participants