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("
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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

GoNetwork 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

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.