< back

the byzantine ridiculousness of remote work from mainland china (w.r.t openvpn)

i've spent quite a lot of time during the last few years living in mainland china. the 跳墙 meta has evolved quite a lot over the last decade or so. from getting away with simply tunneling http traffic thru ssh back in the day, to using the shadowsocks protocol and now to those like vmess and trojan (i expect the cat and mouse game of these newer protocols to continue, where a dev gets v& and new ones come in to fill the spot).

in 2023 openvpn is completely unusable over home internet lines in china due to DPI capabilities of the GFW. connections to a fresh openvpn server will last from maybe a few minutes to a day before the banhammer comes down. this posed a major problem for me, since i was working for a client where some resources were restricted behind an ACL and could only be reached using a connection to their openvpn server.

since i was already using v2ray (with vmess protocol) to jump over the firewall, i decided to just try the OOTB functionality of openvpn to route it through a socks proxy using shadowsocks over v2ray. this did eventually work with some caveats:

  1. i had to setup my v2ray server to route all connections, it didn't seem to work with rule based routing
  2. connection was unstable

i looked around for a bit to see if anyone else had needed to do a similar thing and luckily this legend had done so (albeit with the usecase of accessing netflix and other streaming sites from the mainland); furthermore he had a whole repo dedicated to it.

the gist of the repo is that it's a docker container that runs an openvpn client and a dante server. you run this container on the host with your v2ray server. you would config v2ray to pass traffic you want going through openvpn, to go to a local socks port (dante in the container). dante then passes this thru to openvpn (in the container).

here crude network diagram of whats going on:

network diagrom showing how to route openvpn via a v2ray and socks proxy

and here's the server side v2ray conf i'm using:

{
  "log": {
    "loglevel": "info",
      "access": "/var/log/v2ray/access.log",
      "error": "/var/log/v2ray/error.log"
  },
    "inbounds": [
    {
      "port": XXXX,
      "listen":"127.0.0.1",
      "protocol": "vmess",
      "settings": {
        "clients": [
        {
          "id": "YYYY",
          "alterId": 64
        }
        ]
      },
      "streamSettings": {
        "network": "ws",
        "wsSettings": {
          "path": "/ray"
        }
      }
    }
    ],
    "outbounds": [
    {
      "protocol": "freedom",
      "settings": {
      }
    },
    {
      "protocol": "socks",
      "settings": {
        "servers": [{
          "port": ZZZZ,
          "address": "127.0.0.1"
        }]
      },
      "streamSettings": {
        "tcpSettings": {
          "header": {
            "type": "none"
          }
        },
        "network": "tcp",
        "security": "none"
      },
      "tag": "work"
    }
    ],
    "routing": {
      "domainStrategy": "IPOnDemand",
      "rules": [
      {
        "type": "field",
        "domain": [
          "domain:AAAA"
        ],
        "outboundTag": "work"
      }
      ]
    }
}

where:

XXXX: is the port my v2ray server listens on, and which my v2ray client connects to

YYYY: is a guid that is the same on v2ray client and server

ZZZZ: is the port on which the dante socks server (from docker conatiner) is listening

AAAA: is the wildcard rule for domains which are part of my clients intranet

and this is the corresponding (minimal and redacted) v2ray config on client side:

{
  "outbounds": [
    {
      "protocol": "vmess",
      "streamSettings": {
        "wsSettings": {
          "path": "/ray",
        },
        "tlsSettings": {
          "allowInsecure": false
        },
        "security": "tls",
        "network": "ws"
      },
      "tag": "proxy",
      "settings": {
        "vnext": [
          {
            "address": "XXXX",
            "users": [
              {
                "id": "YYYY",
                "alterId": 0,
                "level": 0,
                "security": "auto"
              }
            ],
            "port": 443
          }
        ]
      }
    }
  ]
}

where:

XXXX is domain name of my v2ray server

YYYY is same guid as on server

obv you will have to setup a domain name which would be the XXXX in my example config - so i'd recommend doing this thru smth like cloudflare, so that if the GFW randomly blocks ur connections, you can just reset and cloudflare will proxy your server via a different IP. you can easily do this reverse proxy thing thru nginx, or even easier nowadays caddy (cos it has automatic TLS certs). this will be left as an exercise for the reader. this page has everything you'd need for setting up v2ray w ur own domain name.