Backend Development 12 min read

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.

Architecture Digest
Architecture Digest
Architecture Digest
Design and Prototype of a Distributed VPN Using Go and KCP

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("\nhub Server is not specified") } if *local == "" { flag.Usage() log.Fatal("\nlocal 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)\n", 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.

distributed systemsGonetwork programmingVPNKCP
Architecture Digest
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.