Skip to content
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

Support adding custom VPN Azure service #1739

Closed
wants to merge 4 commits into from

Conversation

domosekai
Copy link
Contributor

A sample implementation of VPN Azure in Go is here.

I will provide more description later.

String resources for other languages will also be added.

Changes proposed in this pull request:

  • Support custom VPN Azure service

@domosekai
Copy link
Contributor Author

domosekai commented Jan 5, 2023

OK. Here is a brief explanation about what's inside the pull request.

Design

A VPN Azure cloud server basically does two things, managing clients (i.e. VPN servers) and relaying VPN traffic. I will call the former control session and the latter data session.
A SoftEther VPN server first registers itself at the Azure control server. The official Azure service integrates with the DDNS function to assign a hostname (e.g. vpn1234) to the VPN server to do both DDNS and VPN Azure (e.g. vpn1234.softether.net and vpn1234.vpnazure.net). DDNS service also provides the address of the Azure control server.
In a privately hosted scenario, DDNS is often not needed as the scale is small. In this case the VPN server needs to specify the VPN Azure server (e.g. control.myazure.net) and the hostname (e.g. vpn1234.myazure.net). The hostname must be specified in FQDN.
The VPN server connects to the Azure control server and establishes a control session. Since we are in a private world, the VPN server needs to authenticate itself (see below). After that, it listens to relay requests from the control session.
Now a VPN client connects to the relay server (e.g. vpn1234.myazure.net). The control server notifies the VPN server about the connection information and the address of the data server. The VPN server starts a new connection to the data server which connects the dot and starts relaying data between the two parties.
In my sample server implementation, the Azure control server and data server is the same for simplicity reason. It does not have to be that way. The VPN server obtains the address of the data server only from the control session and is ready to use a different address.

Authentication

The VPN server is able to authenticate itself to the Azure control server by password or certificate.
Password authentication is done in the same way as SoftEther VPN, except that the hash algorithm is SHA1 instead of SHA0.
Certificate authentication is performed in standard TLS handshake. Note that the client (i.e. VPN server) certificate must contain the hostname (i.e. vpn1234.vpnazure.net) in its CN.
Since the password authentication happens after TLS handshake, it's possible to do 2-factor authentication as long as the Azure server supports. The VPN server just sends whatever it has. If you have configured a password and a certificate, it will send both. However in my sample server implementation only one type is supported.

Encryption

In the official Azure service, the control session is not encrypted. It is now encrypted in TLS at all times.

DNS

Without the help of DDNS, we need to manually setup the DNS environment. It can be done in various ways. The following is just an example.
Let's say we have decided to host the custom Azure service on the suffix .myazure.net. Setup a wildcard DNS record so that any *.myazure.net would get resolved to the same address where the Azure server is.
The Azure server simply looks at the SNI to distinguish between VPN clients (i.e. if target is vpn1234.myazure.net) and servers (i.e. if target is control.myazure.net).

Scalability

If the number of Azure clients (i.e. VPN servers) is large, it may be inefficient to manage the passwords. Instead, one can issue client certificates in batches and add the issuer CA in the Azure server authentication. You can use my sample implementation to do that.

Commands

  • VpnAzureGetCustom and VpnAzureSetCustom
    View and set custom Azure settings, including Azure server address, hostname, authentication and optionally server verification.
  • VpnAzureGetStatus and VpnAzureSetEnable
    View and set VPN Azure status. When you enable VPN Azure, you can select the default service or the custom service, but not both.

Copy link

@kroimon kroimon left a comment

Choose a reason for hiding this comment

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

I successfully tested this PR (rebased onto current master) together with the latest master of vpnazure-go.

Copy link

@kroimon kroimon left a comment

Choose a reason for hiding this comment

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

I had some time playing around with this implementation in the meantime and started playing around with implementing some improvements to the vpnazure-go implementation that I would need, including a JSON API.

One suggestion/proposal/discussion starter:
I think it would make sense to change the initial connection handshake/signature for Azure VPN control and data channels to use a standard HTTP-compatible header.

Right now, the handshakes are ACTL for the control channel and AZURE_CONNECT_SIGNATURE! for the data channel before switching to Pack data.
Changing both to send an HTTP request like POST /vpnrelay/control HTTP/1.1 and POST /vpnrelay/data HTTP/1.1 instead would align the connection establishment with the other protocols used by SoftEther.
For example, a normal VPN client session is started with POST /vpnsvc/connect.cgi HTTP/1.1, the built-in JSON-RPC is called by POST /api/* HTTP/1.1 and the Web Admin Console by POST /admin/* HTTP/1.1. Even Microsoft's SSTP-VPN uses SSTP_DUPLEX_POST /sra_{BA195980-CD49-458b-9E23-C84EE0ADCD75}/ HTTP/1.1 for connection establishment.

Aligning the connection establishment to all use HTTP-style requests would facilitate distinguishing the protocols not only by TLS SNI, but also by using a standard HTTP parser like Go's net/http package.

Right now, this PR is still in a stage where changes like this can be made and there is no urgent need to keep compatibility between the 'official' VPN Azure service and a custom relay server. In fact, it is already different in a few places, including using SHA1 for password hashes instead of SHA0.

}
Unlock(ac->Lock);

SendAll(s, AZURE_PROTOCOL_CONTROL_SIGNATURE, StrLen(AZURE_PROTOCOL_CONTROL_SIGNATURE), false);
SendAll(s, AZURE_PROTOCOL_CONTROL_SIGNATURE, StrLen(AZURE_PROTOCOL_CONTROL_SIGNATURE), use_encryption);
Copy link

Choose a reason for hiding this comment

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

Pseudo-code:

Suggested change
SendAll(s, AZURE_PROTOCOL_CONTROL_SIGNATURE, StrLen(AZURE_PROTOCOL_CONTROL_SIGNATURE), use_encryption);
if (use_custom_azure)
{
HTTP_HEADER *h;
h = NewHttpHeader("POST", "/vpnrelay/control", "HTTP/1.1");
AddHttpValue(h, NewHttpValue("Connection", "Keep-Alive"));
PostHttp(s, h, NULL, 0);
FreeHttpHeader(h);
}
else
{
SendAll(s, AZURE_PROTOCOL_CONTROL_SIGNATURE, StrLen(AZURE_PROTOCOL_CONTROL_SIGNATURE), false);
}


if (IsEmptyStr(ac->DDnsStatusCopy.AzureCertHash) || StrCmpi(server_cert_hash_str, ac->DDnsStatusCopy.AzureCertHash) == 0
|| StrCmpi(server_cert_hash_str, ac->DDnsStatus.AzureCertHash) == 0)
if (Cmp(relay_cert_hash, server_cert_hash, SHA1_SIZE) == 0)
{
if (SendAll(ns, AZURE_PROTOCOL_DATA_SIANGTURE, 24, true))
Copy link

Choose a reason for hiding this comment

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

Pseudo-code:

Suggested change
if (SendAll(ns, AZURE_PROTOCOL_DATA_SIANGTURE, 24, true))
bool ok = false;
if (param->UseCustom)
{
HTTP_HEADER *h;
h = NewHttpHeader("POST", "/vpnrelay/data", "HTTP/1.1");
AddHttpValue(h, NewHttpValue("Connection", "Keep-Alive"));
ok = PostHttp(s, h, NULL, 0);
FreeHttpHeader(h);
}
else
{
ok = SendAll(ns, AZURE_PROTOCOL_DATA_SIANGTURE, 24, true)
}
if (ok)

@domosekai
Copy link
Contributor Author

I don't think the alignment is helpful. An azure server is not a SoftEther server.

@kroimon
Copy link

kroimon commented Apr 28, 2023

This would be helpful in the case you don't have full control over DNS and need to use a single DNS name for control, data and VPN connections.
In this case, the VPN Azure server needs to be able to differentiate the incoming connections by reading the handshake before deciding how to process it.

@domosekai
Copy link
Contributor Author

Well you are actually redesigning the azure system.
You are free to do that but I was just trying to replicate what azure is doing while keeping breaking changes as few as possible.

@kroimon
Copy link

kroimon commented Apr 28, 2023

Can you explain how this would be a breaking change?
As far as I can tell, custom VPN Azure servers are a new feature, right?

This would just change the connection signature from ACTL and AZURE_CONNECT_SIGNATURE! to POST /vpnrelay/control HTTP/1.1 and POST /vpnrelay/data HTTP/1.1.

The idea is that this would make it easier to parse the type of an incoming connection on the VPN Azure server side.

Right now, the decision on how to handle an incoming connection on the VPN Azure server relies on SNI.
Implementing connection signature parsing similar to SoftEther's ServerDownloadSignature makes it possible to run a VPN Azure server without any DNS records set-up. Of course, this can be done with the current signatures as well, but aligning them to all use an HTTP-style header makes that easier.

@domosekai
Copy link
Contributor Author

No, signatures and SNI are irrelevant.
SNI is used to distinguish client and server connections. This can't be changed because we can't change existing clients (SSTP and SoftEther).
The signatures you mentioned are only used in server connections, in which the SNI is always the same.

@domosekai
Copy link
Contributor Author

It's also important to note that VPN azure by nature must be able to handle an unspecified number of servers. There is no way to do that without manipulating DNS.
If you can't modify DNS and still want to use it for a single server, that would be an edge case. But I won't make a hack for it.

@kroimon
Copy link

kroimon commented Apr 28, 2023

No, signatures and SNI are irrelevant.
SNI is used to distinguish client and server connections.

So they are both not irrelevant, because they can be used to distinguish client and server connections.

This can't be changed because we can't change existing clients (SSTP and SoftEther).

The custom VPN Azure functionality is currently not in any existing client, so there is nothing that would need to be changed.
I only suggest changes to functionality that is being added with this PR here.
The only impact this would currently have is that vpnazure-go would need to have a few lines changed in handleServer.

The signatures you mentioned are only used in server connections, in which the SNI is always the same.

This is currently true, but does not need to be, because control and relay server can be different.
Also, the custom VPN Azure server can receive connections from both SoftEther servers and SoftEther clients and need to differentiate between the two as you wrote yourself.

It's also important to note that VPN azure by nature must be able to handle an unspecified number of servers. There is no way to do that without manipulating DNS.
If you can't modify DNS and still want to use it for a single server, that would be an edge case. But I won't make a hack for it.

Multiple SoftEther servers can be handled without SNI and DNS when listening on more than one port.

I really don't want to fight here, I just think that you don't understand why I am suggesting this.
As I said, this change would make the protocol easier to parse and align it with the rest of the handshakes while not introducing any breaking changes. If you think this would break anything, please explain what exactly it would break so I can understand.

@domosekai domosekai closed this May 11, 2024
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.

None yet

2 participants