Design and Prototype of a Distributed VPN Using Go and KCP
This article describes the motivation, requirements, solution research, and detailed design of a lightweight distributed VPN built with Go, employing KCP as the transport protocol, and includes prototype source code for both hub and peer components.
Introduction: The author, a budget‑conscious user, seeks to interconnect domestic cloud hosts and foreign VPS across different regions with minimal latency and high throughput.
Requirements: Connect servers located in various provinces or countries while keeping delay low and bandwidth high.
Solution research: Evaluated three approaches – traditional VPN (high overhead and does not improve poor network conditions), kcptun (based on KCP but suffers from cumbersome per‑port mapping), and other alternatives such as proxies.
KCP description: KCP is a fast, reliable protocol that can reduce average latency by 30‑40% at the cost of 10‑20% extra bandwidth, requiring the user to provide the underlying UDP transport and timing callbacks.
Design: The system is implemented in Go, using KCP as the transport layer. It consists of two main components – a hub server and peer clients. The hub server maintains a list of peers, handles heart‑beat validation, updates peer information, and forwards packets. Each peer client registers with the hub, creates a TUN device, reads packets from the TUN interface, resolves destination IPs via the hub’s peer map, and sends data over UDP/KCP.
Prototype code – Hub server (fastvpn_hub.go):
package main
import (
"log"
"net"
"os"
"syscall"
"time"
// "github.com/kavu/go_reuseport"
kcp "github.com/xtaci/kcp-go"
)
func main() {
var err error
var conn *net.UDPConn
if len(os.Args) < 2 {
os.Exit(-1)
}
raddr := os.Args[1]
localAddr, _ := net.ResolveUDPAddr("udp", ":8887")
serverAddr, _ := net.ResolveUDPAddr("udp", raddr)
conn, err = net.DialUDP("udp", localAddr, serverAddr)
if err != nil {
log.Println("dial udp err")
log.Println(err)
}
_, err = conn.Write([]byte("hello"))
if err != nil {
log.Println("udp write error")
log.Println(err)
}
buf := make([]byte, 1024)
n, _, err := conn.ReadFromUDP(buf)
log.Println("udp read data: ", string(buf[:n]))
conn.Close()
// heartbeat(localAddr, serverAddr) // omitted for brevity
klisten, err := kcp.Listen(":8887")
if err != nil {
log.Println("listen error")
log.Println(err)
}
log.Println("accept start")
for {
conn, err := klisten.Accept()
if err != nil {
log.Println("accept error")
log.Println(err)
continue
}
go func() {
for {
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil {
log.Println("read error.")
log.Println(err)
}
log.Println("receive data: ", string(buf[:n]))
_, err = conn.Write([]byte("hello"))
if err != nil {
log.Println("kcp write error")
log.Println(err)
}
log.Println("remote addr: ", conn.RemoteAddr())
time.Sleep(3 * time.Second)
}
}()
}
}Prototype code – Peer client (peerclient.go):
package main
import (
"encoding/json"
"flag"
"fmt"
"log"
"net"
"os"
"os/exec"
"strings"
// "fastvpn/common"
"github.com/songgao/water"
"golang.org/x/net/ipv4"
)
const (
BUFFERSIZE = 1500
MTU = "1300"
)
var (
hubServer = flag.String("hub", "", "server addr like 192.168.11.100:8796")
local = flag.String("local", "", "local ip like 172.16.97.101")
listen = flag.String("listen", ":6222", "udp for bind")
port = flag.String("port", "9999", "local port like 9999")
peerMap = make(map[string]string)
)
func checkFatalErr(err error, msg string) {
if err != nil {
log.Println(msg)
log.Fatal(err)
}
}
func runIP(args ...string) {
cmd := exec.Command("/sbin/ip", args...)
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
if err := cmd.Run(); err != nil {
log.Fatal("Error running /sbin/ip:", err)
}
}
func main() {
flag.Parse()
if *hubServer == "" {
flag.Usage()
log.Fatal("
hub Server is not specified")
}
if *local == "" {
flag.Usage()
log.Fatal("
local ip is not specified")
}
hubAddr, err := net.ResolveUDPAddr("udp", *hubServer)
checkFatalErr(err, "Unable to resolve server UDP socket")
listenAddr, err := net.ResolveUDPAddr("udp", *listen)
checkFatalErr(err, "Unable to resolve local UDP socket")
config := water.Config{DeviceType: water.TUN}
iface, err := water.New(config)
checkFatalErr(err, "Unable to allocate TUN interface: ")
log.Println("Interface allocated: ", iface.Name())
runIP("link", "set", "dev", iface.Name(), "mtu", MTU)
runIP("addr", "add", *local, "dev", iface.Name())
runIP("link", "set", "dev", iface.Name(), "up")
conn, err := net.ListenUDP("udp", listenAddr)
checkFatalErr(err, "Unable to connect server")
defer conn.Close()
privateIP := strings.Split(*local, "/")[0]
conn.WriteToUDP([]byte(privateIP), hubAddr)
go func() {
buf := make([]byte, BUFFERSIZE)
for {
n, addr, err := conn.ReadFromUDP(buf)
if addr.String() == hubAddr.String() {
log.Println("receive data from server:")
if err := json.Unmarshal(buf[:n], &peerMap); err != nil {
log.Println("peermap unmarshal error")
log.Println(err)
}
} else {
log.Println("receive data from peer:")
}
if err != nil || n == 0 {
fmt.Println("Error: ", err)
continue
}
log.Println(string(buf[:n]))
iface.Write(buf[:n])
}
}()
packet := make([]byte, BUFFERSIZE)
for {
plen, err := iface.Read(packet)
if err != nil {
break
}
header, _ := ipv4.ParseHeader(packet[:plen])
dstIP := header.Dst.String()
realDest, ok := peerMap[dstIP]
if ok {
realDestAddr, err := net.ResolveUDPAddr("udp", realDest)
if err != nil {
log.Println("resolve real dest ip error")
log.Println(err)
continue
}
fmt.Printf("Sending to remote: %+v (%v)
", header, err)
conn.WriteTo(packet[:plen], realDestAddr)
}
}
}Conclusion: The prototype demonstrates basic data forwarding between hub and peers using KCP over UDP, but lacks NAT traversal, encryption, and full validation; further enhancements are required.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Architecture Digest
Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
