Using HAproxy to terminate SNI and port forwarding


I have different servers in a couple of western countries as personal VPNs for myself and family and friends. Because of staggering internet censorship in Iran, using a domestic VPS as the relay is almost inevitable to bypass GFW. So, to deceive DPI, I use a decoy website with some arbitrary activity to minimize the risk of exposing the true nature of domestic VPS. All in all, it works to a great degree but the problem is, that I can only use it for one external VPS by addressing it in “default_backend” of haproxy. That is, if the SNI doesn’t match with the websites of the server it goes to default, and from there, it relays to the foreign server. Now, I want to implement a system, in which the subdomain of an fqdn can pinpoint the exact server we want to connect to; i.e. US.mydomain.tld connects to US vps and GER.mydomain.tld connects to the server in Germany. This is my config file of haproxy but it doesn’t work.

frontend HTTPS
    bind    myIP:443
    mode    tcp
    option  tcplog
    option  forwardfor

    tcp-request     inspect-delay 5s
    tcp-request     content accept if       { req_ssl_hello_type 1 }

    use_backend nginx       if { ssl_fc_sni -i myDomain.tld }
    use_backend nginx       if { ssl_fc_sni -i www.myDomain.tld }
    use_backend GERrelay    if { ssl_fc_sni -i ger.myDomain.tld }
    use_backend USrelay     if { ssl_fc_sni -i us.myDomain.tld }

    default_backend nginx

frontend GERterminatedSSL
    mode    http
    bind    myIP:445      ssl     crt     /etc/haproxy/certs/fullchain.pem
    default_backend GERbackend

backend GERbackend
    mode    http
    server  6tunnel

frontend USterminatedSSL
    mode    http
    bind    myIP:446      ssl     crt     /etc/haproxy/certs/fullchain.pem
    default_backend USbackend

backend USbackend
    mode    http
    server  6tunnel
backend nginx
    mode    tcp
    option  ssl-hello-chk
    option  forwardfor
    server  nginx   send-proxy-v2   check

backend GERrelay
    mode    tcp
    server  local
backend USrelay
    mode    tcp
    server  local

Whatever approach I choose doesn’t work or better said, I can’t make it work. Can someone show the correct way of rectifying this method?

P.S. I originally posted this in stack overflow but there was no answer up until posting this

Based on your description and configuration, I have absolutely no idea what you are trying to do.

What is listening on exactly? What certificate is /etc/haproxy/certs/fullchain.pem ?

No one can help you with this description.

What is the result that you expect?
What is the result that you actually get?

I want it to work, but it doesn’t is not a helpful description.

Thanks for the reply. You are absolutely right. The frustration and lack of understanding/knowledge got the best of me and I didn’t draw a clear picture of the problem and desired outcomes.

What is the result that you expect?

I wanted to re-route the incoming connections to the external/foreign servers based on the SNI.

The domestic server is just a facade to minimize the risk of getting identified and blocked by DPI and Iran’s GFW. The attempt is to redirect specific connections to external servers based in the US and EU to circumvent the censorship.

To achieve this, the haproxy should decide which connections are gonna redirected to outside servers. My mistake was redirecting it based on SNI which is not the part of domestic webserver and belongs to the external VPS and webserver. That is, the domestic server wouldn’t recognize the SNI and would drop the connection.

In the end, I did resolve this method by:
1- Including the foreign SNIs in the frontend and creating respective backend for each one of them.
2- Getting rid of the secondary frontend and SNI termination altogether.
3- Changing all the modes to tcp.

With these steps, the system works fine but I am trying to implement a new method in which, the header and SNI are the same, unlike the current approach.
For this, I must come up with a frontend that redirects the incoming connections based on the header, (e.g. US.myDomesticDomain.tld gets re-routed to US based vps and GER.myDomesticDomain.tld gets re-routed to Germany’s vps) and haproxy must set the SNI so that the connection gets accepted by the foreign/external server.

I don’t know if this is feasible or not, but any suggestion or guidance is much appreciated.

Well it is likely your previous issue was caused by sending unencrypted cleartext HTTP from the domestic to the foreign VPS, because you actually decrypted SSL without reencrypting it.

But again, I don’t know what is and how it is configured. I also don’t know how your foreign VPS is configured.

But again, I don’t know what is and how it is configured

It is just a simple relay. It receives the packet and sends it as it is to the foreign server.
I was experimenting with different types of relaying data packets and settled on haproxy’s own embedded function. The other methods work too but for now, haproxy is sufficient for handling this task.

server	ip	myExternalDomain:443 check   resolvers   mynameserver

I also don’t know how your foreign VPS is configured.

It is a simple proxying task. If the SNI and other parameters match with Xray and V2ray servers, the data is gonna handed to them, if not, the default backend which is nginx, gonna process it. It works on both sides and I haven’t encountered a problem so far.

Well it is likely your previous issue was caused by sending unencrypted cleartext HTTP from the domestic to the foreign VPS, because you actually decrypted SSL without reencrypting it.

Most likely this is the problem. My first approach was wrong and I should have worked with header instead of SNI.
Now, I’m working on parsing the received data by:

  1. Haproxy listens to port 443 on tcp mode.
  2. If the received SNI is the same as the foreign server’s header(e.g. US.myDomesticDomain.tld) it’s gonna send it to a backend that unencrypts data in http mode and extracts the header.
  3. Based on that header, the server should set the external server’s SNI and send it to the external server in tcp mode.

I don’t know if it’s gonna work or not, so, what is your assessment to this approach?

I think you should use a forwarding proxy like tinyproxy or squid.

Doing this with haproxy is painful, as haproxy is not a real forwarding proxy.

I haven’t even heard of these programs. If I can solve it with haproxy it would be wonderful but if it gets frustrating or wearisome, then I try your recommendation.
Thanks again for your time