mirror of
https://github.com/fosrl/newt.git
synced 2025-05-13 05:30:39 +01:00
Userspace connection + proxy working
This commit is contained in:
parent
c572ae0e36
commit
236f5acfc7
11 changed files with 381 additions and 0 deletions
0
.gitignore
vendored
Normal file
0
.gitignore
vendored
Normal file
0
Dockerfile
Normal file
0
Dockerfile
Normal file
8
Makefile
Normal file
8
Makefile
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Description: Makefile for building the project
|
||||
|
||||
BINARY_NAME=newt
|
||||
|
||||
ll: build
|
||||
|
||||
build:
|
||||
go build -o bin/$(BINARY_NAME) -v
|
17
go.mod
Normal file
17
go.mod
Normal file
|
@ -0,0 +1,17 @@
|
|||
module newt
|
||||
|
||||
go 1.23.1
|
||||
|
||||
toolchain go1.23.2
|
||||
|
||||
require golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
||||
|
||||
require (
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
golang.org/x/crypto v0.28.0 // indirect
|
||||
golang.org/x/net v0.30.0 // indirect
|
||||
golang.org/x/sys v0.26.0 // indirect
|
||||
golang.org/x/time v0.7.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 // indirect
|
||||
)
|
16
go.sum
Normal file
16
go.sum
Normal file
|
@ -0,0 +1,16 @@
|
|||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
||||
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
|
||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY=
|
323
main.go
Normal file
323
main.go
Normal file
|
@ -0,0 +1,323 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.zx2c4.com/wireguard/conn"
|
||||
"golang.zx2c4.com/wireguard/device"
|
||||
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||
)
|
||||
|
||||
type ProxyTarget struct {
|
||||
Protocol string
|
||||
Listen string
|
||||
Targets []string
|
||||
}
|
||||
|
||||
type ProxyManager struct {
|
||||
targets []ProxyTarget
|
||||
tnet *netstack.Net
|
||||
}
|
||||
|
||||
func NewProxyManager(tnet *netstack.Net) *ProxyManager {
|
||||
return &ProxyManager{
|
||||
tnet: tnet,
|
||||
}
|
||||
}
|
||||
|
||||
func (pm *ProxyManager) AddTarget(protocol, listen string, targets []string) {
|
||||
pm.targets = append(pm.targets, ProxyTarget{
|
||||
Protocol: protocol,
|
||||
Listen: listen,
|
||||
Targets: targets,
|
||||
})
|
||||
}
|
||||
|
||||
func (pm *ProxyManager) Start() error {
|
||||
for _, target := range pm.targets {
|
||||
switch strings.ToLower(target.Protocol) {
|
||||
case "tcp":
|
||||
go pm.serveTCP(target)
|
||||
case "udp":
|
||||
go pm.serveUDP(target)
|
||||
default:
|
||||
return fmt.Errorf("unsupported protocol: %s", target.Protocol)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pm *ProxyManager) serveTCP(target ProxyTarget) {
|
||||
listener, err := pm.tnet.ListenTCP(&net.TCPAddr{
|
||||
IP: net.ParseIP(target.Listen),
|
||||
Port: 0,
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("Failed to start TCP listener for %s: %v", target.Listen, err)
|
||||
return
|
||||
}
|
||||
defer listener.Close()
|
||||
|
||||
log.Printf("TCP proxy listening on %s", listener.Addr())
|
||||
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
log.Printf("Failed to accept TCP connection: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
go pm.handleTCPConnection(conn, target.Targets)
|
||||
}
|
||||
}
|
||||
|
||||
func (pm *ProxyManager) handleTCPConnection(clientConn net.Conn, targets []string) {
|
||||
defer clientConn.Close()
|
||||
|
||||
// Round-robin through targets
|
||||
targetIndex := 0
|
||||
target := targets[targetIndex]
|
||||
targetIndex = (targetIndex + 1) % len(targets)
|
||||
|
||||
serverConn, err := net.Dial("tcp", target)
|
||||
if err != nil {
|
||||
log.Printf("Failed to connect to target %s: %v", target, err)
|
||||
return
|
||||
}
|
||||
defer serverConn.Close()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
|
||||
// Client -> Server
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
io.Copy(serverConn, clientConn)
|
||||
}()
|
||||
|
||||
// Server -> Client
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
io.Copy(clientConn, serverConn)
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func (pm *ProxyManager) serveUDP(target ProxyTarget) {
|
||||
addr := &net.UDPAddr{
|
||||
IP: net.ParseIP(target.Listen),
|
||||
Port: 0,
|
||||
}
|
||||
|
||||
conn, err := pm.tnet.ListenUDP(addr)
|
||||
if err != nil {
|
||||
log.Printf("Failed to start UDP listener for %s: %v", target.Listen, err)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
log.Printf("UDP proxy listening on %s", conn.LocalAddr())
|
||||
|
||||
buffer := make([]byte, 65535)
|
||||
targetIndex := 0
|
||||
|
||||
for {
|
||||
// Read from the UDP connection
|
||||
n, remoteAddr, err := conn.ReadFrom(buffer)
|
||||
if err != nil {
|
||||
log.Printf("Failed to read UDP packet: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
t := target.Targets[targetIndex]
|
||||
targetIndex = (targetIndex + 1) % len(target.Targets)
|
||||
|
||||
targetAddr, err := net.ResolveUDPAddr("udp", t)
|
||||
if err != nil {
|
||||
log.Printf("Failed to resolve target address %s: %v", target, err)
|
||||
continue
|
||||
}
|
||||
|
||||
go func(data []byte, remote net.Addr) {
|
||||
targetConn, err := net.DialUDP("udp", nil, targetAddr)
|
||||
if err != nil {
|
||||
log.Printf("Failed to connect to target %s: %v", target, err)
|
||||
return
|
||||
}
|
||||
defer targetConn.Close()
|
||||
|
||||
_, err = targetConn.Write(data)
|
||||
if err != nil {
|
||||
log.Printf("Failed to write to target: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
response := make([]byte, 65535)
|
||||
n, err := targetConn.Read(response)
|
||||
if err != nil {
|
||||
log.Printf("Failed to read response from target: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = conn.WriteTo(response[:n], remote)
|
||||
if err != nil {
|
||||
log.Printf("Failed to write response to client: %v", err)
|
||||
}
|
||||
}(buffer[:n], remoteAddr)
|
||||
}
|
||||
}
|
||||
|
||||
func fixKey(key string) string {
|
||||
// Remove any whitespace
|
||||
key = strings.TrimSpace(key)
|
||||
|
||||
// Decode from base64
|
||||
decoded, err := base64.StdEncoding.DecodeString(key)
|
||||
if err != nil {
|
||||
log.Fatal("Error decoding base64:", err)
|
||||
}
|
||||
|
||||
// Convert to hex
|
||||
return hex.EncodeToString(decoded)
|
||||
}
|
||||
|
||||
func ping(tnet *netstack.Net, dst string) {
|
||||
socket, err := tnet.Dial("ping4", dst)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
requestPing := icmp.Echo{
|
||||
Seq: rand.Intn(1 << 16),
|
||||
Data: []byte("gopher burrow"),
|
||||
}
|
||||
icmpBytes, _ := (&icmp.Message{Type: ipv4.ICMPTypeEcho, Code: 0, Body: &requestPing}).Marshal(nil)
|
||||
socket.SetReadDeadline(time.Now().Add(time.Second * 10))
|
||||
start := time.Now()
|
||||
_, err = socket.Write(icmpBytes)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
n, err := socket.Read(icmpBytes[:])
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
replyPacket, err := icmp.ParseMessage(1, icmpBytes[:n])
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
replyPing, ok := replyPacket.Body.(*icmp.Echo)
|
||||
if !ok {
|
||||
log.Panicf("invalid reply type: %v", replyPacket)
|
||||
}
|
||||
if !bytes.Equal(replyPing.Data, requestPing.Data) || replyPing.Seq != requestPing.Seq {
|
||||
log.Panicf("invalid ping reply: %v", replyPing)
|
||||
}
|
||||
log.Printf("Ping latency: %v", time.Since(start))
|
||||
}
|
||||
|
||||
func main() {
|
||||
var (
|
||||
tunnelIP string
|
||||
privateKey string
|
||||
publicKey string
|
||||
endpoint string
|
||||
tcpTargets string
|
||||
udpTargets string
|
||||
listenIP string
|
||||
serverIP string
|
||||
dns string
|
||||
)
|
||||
|
||||
flag.StringVar(&tunnelIP, "tunnel-ip", "", "Tunnel IP address")
|
||||
flag.StringVar(&privateKey, "private-key", "", "WireGuard private key")
|
||||
flag.StringVar(&publicKey, "public-key", "", "WireGuard public key")
|
||||
flag.StringVar(&endpoint, "endpoint", "", "WireGuard endpoint (host:port)")
|
||||
flag.StringVar(&tcpTargets, "tcp-targets", "", "Comma-separated list of TCP targets (host:port)")
|
||||
flag.StringVar(&udpTargets, "udp-targets", "", "Comma-separated list of UDP targets (host:port)")
|
||||
flag.StringVar(&listenIP, "listen-ip", "", "IP to listen for incoming connections")
|
||||
flag.StringVar(&serverIP, "server-ip", "", "IP to filter and ping on the server side. Inside tunnel...")
|
||||
flag.StringVar(&dns, "dns", "8.8.8.8", "DNS server to use")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
// Create TUN device and network stack
|
||||
tun, tnet, err := netstack.CreateNetTUN(
|
||||
[]netip.Addr{netip.MustParseAddr(tunnelIP)},
|
||||
[]netip.Addr{netip.MustParseAddr(dns)},
|
||||
1420)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
// Create WireGuard device
|
||||
dev := device.NewDevice(tun, conn.NewDefaultBind(), device.NewLogger(device.LogLevelVerbose, ""))
|
||||
|
||||
// Configure WireGuard
|
||||
config := fmt.Sprintf(`private_key=%s
|
||||
public_key=%s
|
||||
allowed_ip=%s/32
|
||||
endpoint=%s
|
||||
persistent_keepalive_interval=5
|
||||
`, fixKey(privateKey), fixKey(publicKey), serverIP, endpoint)
|
||||
|
||||
err = dev.IpcSet(config)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
// Bring up the device
|
||||
err = dev.Up()
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
// Ping to bring the tunnel up on the server side quickly
|
||||
ping(tnet, serverIP)
|
||||
|
||||
// Create proxy manager
|
||||
pm := NewProxyManager(tnet)
|
||||
|
||||
// Add TCP targets
|
||||
if tcpTargets != "" {
|
||||
targets := strings.Split(tcpTargets, ",")
|
||||
pm.AddTarget("tcp", listenIP, targets)
|
||||
}
|
||||
|
||||
// Add UDP targets
|
||||
if udpTargets != "" {
|
||||
targets := strings.Split(udpTargets, ",")
|
||||
pm.AddTarget("udp", listenIP, targets)
|
||||
}
|
||||
|
||||
// Start proxies
|
||||
err = pm.Start()
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
// Wait for interrupt signal
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-sigCh
|
||||
|
||||
// Cleanup
|
||||
dev.Close()
|
||||
}
|
BIN
newt
Executable file
BIN
newt
Executable file
Binary file not shown.
1
test/cleanup.sh
Normal file
1
test/cleanup.sh
Normal file
|
@ -0,0 +1 @@
|
|||
ip link del dev wg0
|
1
test/key
Normal file
1
test/key
Normal file
|
@ -0,0 +1 @@
|
|||
eN6oRymkBFTCLOwlpEgB9zkCJpl0zb6NL5TRogXzNlk=
|
9
test/newt_client.sh
Normal file
9
test/newt_client.sh
Normal file
|
@ -0,0 +1,9 @@
|
|||
./newt \
|
||||
--tunnel-ip=192.168.4.28 \
|
||||
"--private-key=kAexrEV1OHlMYQU3BZatZxNfKGAbzo+ATspAdtOcRks=" \
|
||||
"--public-key=Kn4eD0kvcTwjO//zqH/CtNVkMNdMiUkbqFxysEym2D8=" \
|
||||
--endpoint=192.168.1.16:51820 \
|
||||
--tcp-targets=127.0.0.1:8080 \
|
||||
--udp-targets=127.0.0.1:53 \
|
||||
--listen-ip=192.168.4.28 \
|
||||
--server-ip=192.168.4.1
|
6
test/wg_server.sh
Normal file
6
test/wg_server.sh
Normal file
|
@ -0,0 +1,6 @@
|
|||
ip link add dev wg0 type wireguard
|
||||
ip addr add 192.168.4.1/24 dev wg0
|
||||
ip link set up dev wg0
|
||||
wg set wg0 private-key ./key
|
||||
wg set wg0 listen-port 51820
|
||||
wg set wg0 peer 3QfirSdDVihYCAz66t6DTAtFtsh+9WVVu7ItlL750hI= allowed-ips 192.168.4.28
|
Loading…
Reference in a new issue