Browse Source

proxy integration with netclient inital commit

Abhishek Kondur 2 years ago
parent
commit
953b5eae0a

BIN
netclient/netclient-proxy/bin/proxy.linux


+ 146 - 0
netclient/netclient-proxy/common/common.go

@@ -0,0 +1,146 @@
+package common
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"fmt"
+	"io"
+	"log"
+	"net"
+	"net/http"
+	"os"
+	"os/exec"
+	"strings"
+	"time"
+
+	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/netclient/config"
+	"github.com/gravitl/netmaker/netclient/ncutils"
+
+	"github.com/gravitl/netmaker/netclient/netclient-proxy/wg"
+	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
+)
+
+const (
+	NmProxyPort = 51722
+)
+
+type Conn struct {
+	Config ConnConfig
+	Proxy  Proxy
+}
+
+// ConnConfig is a peer Connection configuration
+type ConnConfig struct {
+
+	// Key is a public key of a remote peer
+	Key string
+	// LocalKey is a public key of a local peer
+	LocalKey string
+
+	ProxyConfig     Config
+	AllowedIPs      []net.IPNet
+	LocalWgPort     int
+	RemoteProxyIP   net.IP
+	RemoteWgPort    int
+	RemoteProxyPort int
+}
+type Config struct {
+	Port         int
+	BodySize     int
+	Addr         string
+	WgListenAddr string
+	RemoteKey    string
+	WgInterface  *wg.WGIface
+	AllowedIps   []net.IPNet
+	PreSharedKey *wgtypes.Key
+	//ProxyServer  *server.ProxyServer
+}
+
+// Proxy -  WireguardProxy proxies
+type Proxy struct {
+	Ctx    context.Context
+	Cancel context.CancelFunc
+
+	Config     Config
+	RemoteConn net.Conn
+	LocalConn  net.Conn
+}
+
+var Peers = make(map[string]*Conn)
+
+var RemoteEndpointsMap = make(map[string][]string)
+
+// RunCmd - runs a local command
+func RunCmd(command string, printerr bool) (string, error) {
+	args := strings.Fields(command)
+	cmd := exec.Command(args[0], args[1:]...)
+	cmd.Wait()
+	out, err := cmd.CombinedOutput()
+	if err != nil && printerr {
+		log.Println("error running command: ", command)
+		log.Println(strings.TrimSuffix(string(out), "\n"))
+	}
+	return string(out), err
+}
+
+// API function to interact with netmaker api endpoints. response from endpoint is returned
+func API(data interface{}, method, url, authorization string) (*http.Response, error) {
+	var request *http.Request
+	var err error
+	if data != "" {
+		payload, err := json.Marshal(data)
+		if err != nil {
+			return nil, fmt.Errorf("error encoding data %w", err)
+		}
+		request, err = http.NewRequest(method, url, bytes.NewBuffer(payload))
+		if err != nil {
+			return nil, fmt.Errorf("error creating http request %w", err)
+		}
+		request.Header.Set("Content-Type", "application/json")
+	} else {
+		request, err = http.NewRequest(method, url, nil)
+		if err != nil {
+			return nil, fmt.Errorf("error creating http request %w", err)
+		}
+	}
+	if authorization != "" {
+		request.Header.Set("authorization", "Bearer "+authorization)
+	}
+	request.Header.Set("requestfrom", "node")
+	var httpClient http.Client
+	httpClient.Timeout = time.Minute
+	return httpClient.Do(request)
+}
+
+// Authenticate authenticates with api to permit subsequent interactions with the api
+func Authenticate(cfg *config.ClientConfig) (string, error) {
+
+	pass, err := os.ReadFile(ncutils.GetNetclientPathSpecific() + "secret-" + cfg.Network)
+	if err != nil {
+		return "", fmt.Errorf("could not read secrets file %w", err)
+	}
+	data := models.AuthParams{
+		MacAddress: cfg.Node.MacAddress,
+		ID:         cfg.Node.ID,
+		Password:   string(pass),
+	}
+	url := "https://" + cfg.Server.API + "/api/nodes/adm/" + cfg.Network + "/authenticate"
+	response, err := API(data, http.MethodPost, url, "")
+	if err != nil {
+		return "", err
+	}
+	defer response.Body.Close()
+	if response.StatusCode != http.StatusOK {
+		bodybytes, _ := io.ReadAll(response.Body)
+		return "", fmt.Errorf("failed to authenticate %s %s", response.Status, string(bodybytes))
+	}
+	resp := models.SuccessResponse{}
+	if err := json.NewDecoder(response.Body).Decode(&resp); err != nil {
+		return "", fmt.Errorf("error decoding respone %w", err)
+	}
+	tokenData := resp.Response.(map[string]interface{})
+	token := tokenData["AuthToken"]
+	return token.(string), nil
+}

+ 78 - 0
netclient/netclient-proxy/netclient-proxy.go

@@ -0,0 +1,78 @@
+package netclientproxy
+
+import (
+	"fmt"
+	"log"
+	"net"
+	"runtime"
+
+	"github.com/gravitl/netmaker/logger"
+	"github.com/gravitl/netmaker/netclient/config"
+	"github.com/gravitl/netmaker/netclient/ncutils"
+	"github.com/gravitl/netmaker/netclient/netclient-proxy/common"
+	peerpkg "github.com/gravitl/netmaker/netclient/netclient-proxy/peer"
+	"github.com/gravitl/netmaker/netclient/netclient-proxy/server"
+	"github.com/gravitl/netmaker/netclient/netclient-proxy/stun"
+	"github.com/gravitl/netmaker/netclient/netclient-proxy/wg"
+	"github.com/gravitl/netmaker/netclient/wireguard"
+)
+
+func Start() {
+	log.Println("Starting Proxy...")
+	hInfo := stun.GetHostInfo()
+	stun.Host = hInfo
+	log.Printf("HOSTINFO: %+v", hInfo)
+	// start the netclient proxy server
+	p, err := server.CreateProxyServer(0, 0, hInfo.PrivIp.String())
+	if err != nil {
+		log.Fatal("failed to create proxy: ", err)
+	}
+	go p.Listen()
+	networks, _ := ncutils.GetSystemNetworks()
+	for _, network := range networks {
+		logger.Log(3, "initializing network", network)
+		cfg := config.ClientConfig{}
+		cfg.Network = network
+		cfg.ReadConfig()
+		node, err := peerpkg.GetNodeInfo(&cfg)
+		if err != nil {
+			log.Println("Failed to get node info: ", err)
+			continue
+		}
+		for _, peerI := range node.Peers {
+			ifaceName := node.Node.Interface
+			log.Println("--------> IFACE: ", ifaceName)
+			if runtime.GOOS == "darwin" {
+				ifaceName, err = wireguard.GetRealIface(ifaceName)
+				if err != nil {
+					log.Println("failed to get real iface: ", err)
+				}
+			}
+			wgInterface, err := wg.NewWGIFace(ifaceName, "127.0.0.1/32", wg.DefaultMTU)
+			if err != nil {
+				log.Fatal("Failed init new interface: ", err)
+			}
+			log.Printf("wg: %+v\n", wgInterface)
+			peerpkg.AddNewPeer(p, wgInterface, &peerI)
+			if val, ok := common.RemoteEndpointsMap[peerI.Endpoint.IP.String()]; ok {
+				val = append(val, peerI.PublicKey.String())
+				common.RemoteEndpointsMap[peerI.Endpoint.IP.String()] = val
+			} else {
+				common.RemoteEndpointsMap[peerI.Endpoint.IP.String()] = []string{peerI.PublicKey.String()}
+			}
+
+		}
+
+	}
+	fmt.Printf("\nPEERS-------> %+v\n", common.Peers)
+	fmt.Printf("\nREMOTE ENDPOINTS-------> %+v\n", common.RemoteEndpointsMap)
+	select {}
+}
+
+// IsPublicIP indicates whether IP is public or not.
+func IsPublicIP(ip net.IP) bool {
+	if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() || ip.IsPrivate() {
+		return false
+	}
+	return true
+}

+ 39 - 0
netclient/netclient-proxy/packet/packet.go

@@ -0,0 +1,39 @@
+package packet
+
+import (
+	"bytes"
+	"encoding/binary"
+	"log"
+)
+
+var udpHeaderLen = 8
+
+func ProcessPacketBeforeSending(buf []byte, n, dstPort int) ([]byte, int, error) {
+	log.Println("@###### DST Port: ", dstPort)
+	portbuf := new(bytes.Buffer)
+	binary.Write(portbuf, binary.BigEndian, uint16(dstPort))
+	if n > len(buf)-2 {
+		buf = append(buf, portbuf.Bytes()[0])
+		buf = append(buf, portbuf.Bytes()[1])
+	} else {
+		buf[n] = portbuf.Bytes()[0]
+		buf[n+1] = portbuf.Bytes()[1]
+	}
+
+	n += 2
+
+	return buf, n, nil
+}
+
+func ExtractInfo(buffer []byte, n int) (int, int, error) {
+	data := buffer[:n]
+	var localWgPort uint16
+	portBuf := data[n-2 : n+1]
+	reader := bytes.NewReader(portBuf)
+	err := binary.Read(reader, binary.BigEndian, &localWgPort)
+	if err != nil {
+		log.Println("Failed to read port buffer: ", err)
+	}
+	n -= 2
+	return int(localWgPort), n, err
+}

+ 116 - 0
netclient/netclient-proxy/peer/peer.go

@@ -0,0 +1,116 @@
+package peer
+
+import (
+	"encoding/json"
+	"fmt"
+	"io"
+	"log"
+	"net"
+	"net/http"
+
+	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/netclient/config"
+	"github.com/gravitl/netmaker/netclient/netclient-proxy/common"
+	"github.com/gravitl/netmaker/netclient/netclient-proxy/proxy"
+	"github.com/gravitl/netmaker/netclient/netclient-proxy/server"
+	"github.com/gravitl/netmaker/netclient/netclient-proxy/wg"
+	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
+)
+
+type Conn struct {
+	Config ConnConfig
+	Proxy  proxy.Proxy
+}
+
+// ConnConfig is a peer Connection configuration
+type ConnConfig struct {
+
+	// Key is a public key of a remote peer
+	Key string
+	// LocalKey is a public key of a local peer
+	LocalKey string
+
+	ProxyConfig     proxy.Config
+	AllowedIPs      string
+	LocalWgPort     int
+	RemoteProxyIP   net.IP
+	RemoteWgPort    int
+	RemoteProxyPort int
+}
+
+func GetNodeInfo(cfg *config.ClientConfig) (models.NodeGet, error) {
+	var nodeGET models.NodeGet
+	token, err := common.Authenticate(cfg)
+	if err != nil {
+		return nodeGET, err
+	}
+	url := fmt.Sprintf("https://%s/api/nodes/%s/%s", cfg.Server.API, cfg.Network, cfg.Node.ID)
+	response, err := common.API("", http.MethodGet, url, token)
+	if err != nil {
+		return nodeGET, err
+	}
+	if response.StatusCode != http.StatusOK {
+		bytes, err := io.ReadAll(response.Body)
+		if err != nil {
+			fmt.Println(err)
+		}
+		return nodeGET, (fmt.Errorf("%s %w", string(bytes), err))
+	}
+	defer response.Body.Close()
+	if err := json.NewDecoder(response.Body).Decode(&nodeGET); err != nil {
+		return nodeGET, fmt.Errorf("error decoding node %w", err)
+	}
+	return nodeGET, nil
+}
+
+func AddNewPeer(pserver *server.ProxyServer, wgInterface *wg.WGIface, peer *wgtypes.PeerConfig) error {
+
+	c := proxy.Config{
+		Port:         peer.Endpoint.Port,
+		WgListenAddr: "127.0.0.1",
+		RemoteKey:    peer.PublicKey.String(),
+		WgInterface:  wgInterface,
+		AllowedIps:   peer.AllowedIPs,
+		ProxyServer:  pserver,
+	}
+	p := proxy.NewProxy(c)
+	remoteConn, err := net.Dial("udp", fmt.Sprintf("%s:%d", peer.Endpoint.IP.String(), common.NmProxyPort))
+	if err != nil {
+		return err
+	}
+	log.Printf("Starting proxy for Peer: %s\n", peer.PublicKey.String())
+	err = p.Start(remoteConn)
+	if err != nil {
+		return err
+	}
+	log.Println("-------> Here1")
+	connConf := common.ConnConfig{
+		Key:      peer.PublicKey.String(),
+		LocalKey: "",
+		ProxyConfig: common.Config{
+			Port:         peer.Endpoint.Port,
+			WgListenAddr: "127.0.0.1",
+			RemoteKey:    peer.PublicKey.String(),
+			WgInterface:  wgInterface,
+			AllowedIps:   peer.AllowedIPs,
+			//ProxyServer:  pserver,
+		},
+		AllowedIPs:      peer.AllowedIPs,
+		RemoteProxyIP:   net.ParseIP(peer.Endpoint.IP.String()),
+		RemoteWgPort:    peer.Endpoint.Port,
+		RemoteProxyPort: common.NmProxyPort,
+	}
+	peerProxy := common.Proxy{
+		Ctx:        p.Ctx,
+		Cancel:     p.Cancel,
+		Config:     connConf.ProxyConfig,
+		RemoteConn: remoteConn,
+		LocalConn:  p.LocalConn,
+	}
+	peerConn := common.Conn{
+		Config: connConf,
+		Proxy:  peerProxy,
+	}
+	common.Peers[peer.PublicKey.String()] = &peerConn
+	return nil
+}

+ 37 - 0
netclient/netclient-proxy/proxy/proxy.go

@@ -0,0 +1,37 @@
+package proxy
+
+import (
+	"context"
+	"net"
+
+	"github.com/gravitl/netmaker/netclient/netclient-proxy/server"
+	"github.com/gravitl/netmaker/netclient/netclient-proxy/wg"
+	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
+)
+
+const (
+	defaultBodySize = 10000
+	defaultPort     = 51722
+)
+
+type Config struct {
+	Port         int
+	BodySize     int
+	Addr         string
+	WgListenAddr string
+	RemoteKey    string
+	WgInterface  *wg.WGIface
+	AllowedIps   []net.IPNet
+	PreSharedKey *wgtypes.Key
+	ProxyServer  *server.ProxyServer
+}
+
+// Proxy -  WireguardProxy proxies
+type Proxy struct {
+	Ctx    context.Context
+	Cancel context.CancelFunc
+
+	Config     Config
+	RemoteConn net.Conn
+	LocalConn  net.Conn
+}

+ 195 - 0
netclient/netclient-proxy/proxy/wireguard.go

@@ -0,0 +1,195 @@
+package proxy
+
+import (
+	"bytes"
+	"context"
+	"encoding/binary"
+	"fmt"
+	"log"
+	"net"
+	"runtime"
+	"strconv"
+
+	"github.com/c-robinson/iplib"
+	"github.com/gravitl/netmaker/netclient/netclient-proxy/common"
+	"github.com/gravitl/netmaker/netclient/netclient-proxy/packet"
+	"github.com/gravitl/netmaker/netclient/netclient-proxy/wg"
+)
+
+func NewProxy(config Config) *Proxy {
+	p := &Proxy{Config: config}
+	p.Ctx, p.Cancel = context.WithCancel(context.Background())
+	return p
+}
+
+// proxyToRemote proxies everything from Wireguard to the RemoteKey peer
+func (p *Proxy) ProxyToRemote() {
+	buf := make([]byte, 1500)
+	for {
+		select {
+		case <-p.Ctx.Done():
+			log.Printf("stopped proxying to remote peer %s due to closed connection\n", p.Config.RemoteKey)
+			return
+		default:
+
+			n, err := p.LocalConn.Read(buf)
+			if err != nil {
+				log.Println("ERRR READ: ", err)
+				continue
+			}
+
+			if peerI, ok := common.Peers[p.Config.RemoteKey]; ok {
+				log.Println("PROCESSING PKT BEFORE SENDING")
+
+				buf, n, err = packet.ProcessPacketBeforeSending(buf, n, peerI.Config.RemoteWgPort)
+				if err != nil {
+					log.Println("failed to process pkt before sending: ", err)
+				}
+			} else {
+				log.Printf("Peer: %s not found in config\n", p.Config.RemoteKey)
+			}
+			// test(n, buf)
+			log.Printf("PROXING TO REMOTE!!!---> %s >>>>> %s\n", p.Config.ProxyServer.Server.LocalAddr().String(), p.RemoteConn.RemoteAddr().String())
+			host, port, _ := net.SplitHostPort(p.RemoteConn.RemoteAddr().String())
+			portInt, _ := strconv.Atoi(port)
+			_, err = p.Config.ProxyServer.Server.WriteToUDP(buf[:n], &net.UDPAddr{
+				IP:   net.ParseIP(host),
+				Port: portInt,
+			})
+			if err != nil {
+				log.Println("Failed to send to remote: ", err)
+			}
+		}
+	}
+}
+
+func test(n int, data []byte) {
+	var localWgPort uint16
+	//portBuf := data[n-2 : n+1]
+	portBuf := data[2:4]
+	reader := bytes.NewReader(portBuf)
+	err := binary.Read(reader, binary.BigEndian, &localWgPort)
+	if err != nil {
+		log.Println("Failed to read port buffer: ", err)
+	}
+	log.Println("TEST WFGPO: ", localWgPort)
+}
+
+// proxyToLocal proxies everything from the RemoteKey peer to local Wireguard
+func (p *Proxy) ProxyToLocal() {
+
+	buf := make([]byte, 1500)
+	for {
+		select {
+		case <-p.Ctx.Done():
+			log.Printf("stopped proxying from remote peer %s due to closed connection\n", p.Config.RemoteKey)
+			return
+		default:
+
+			n, err := p.RemoteConn.Read(buf)
+			if err != nil {
+				continue
+			}
+			log.Printf("PROXING TO LOCAL!!!---> %s <<<<<<<< %s\n", p.LocalConn.LocalAddr().String(), p.RemoteConn.RemoteAddr().String())
+			_, err = p.LocalConn.Write(buf[:n])
+			if err != nil {
+				continue
+			}
+		}
+	}
+}
+
+func (p *Proxy) updateEndpoint() error {
+	udpAddr, err := net.ResolveUDPAddr("udp", p.LocalConn.LocalAddr().String())
+	if err != nil {
+		return err
+	}
+	log.Println("--------> UDPADDR:  ", udpAddr)
+	// add local proxy connection as a Wireguard peer
+	err = p.Config.WgInterface.UpdatePeer(p.Config.RemoteKey, p.Config.AllowedIps, wg.DefaultWgKeepAlive,
+		udpAddr, p.Config.PreSharedKey)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (p *Proxy) Start(remoteConn net.Conn) error {
+	p.RemoteConn = remoteConn
+
+	var err error
+	addr, err := GetFreeIp("127.0.0.1/8")
+	if err != nil {
+		log.Println("Failed to get freeIp: ", err)
+		return err
+	}
+	wgAddr := "127.0.0.1"
+	if runtime.GOOS == "darwin" {
+		wgAddr = addr
+	}
+	wgPort, err := p.Config.WgInterface.GetListenPort()
+	if err != nil {
+		log.Printf("Failed to get listen port for iface: %s,Err: %v\n", p.Config.WgInterface.Name, err)
+		return err
+	}
+
+	p.LocalConn, err = net.DialUDP("udp", &net.UDPAddr{
+		IP:   net.ParseIP(addr),
+		Port: common.NmProxyPort,
+	}, &net.UDPAddr{
+		IP:   net.ParseIP(wgAddr),
+		Port: *wgPort,
+	})
+	if err != nil {
+		log.Fatalf("failed dialing to local Wireguard port,Err: %v\n", err)
+	}
+
+	log.Printf("Dialing to local Wireguard port %s --> %s\n", p.LocalConn.LocalAddr().String(), p.LocalConn.RemoteAddr().String())
+	err = p.updateEndpoint()
+	if err != nil {
+		log.Printf("error while updating Wireguard peer endpoint [%s] %v\n", p.Config.RemoteKey, err)
+		return err
+	}
+
+	go p.ProxyToRemote()
+
+	return nil
+}
+
+func GetFreeIp(cidrAddr string) (string, error) {
+	//ensure AddressRange is valid
+	if _, _, err := net.ParseCIDR(cidrAddr); err != nil {
+		log.Println("UniqueAddress encountered  an error")
+		return "", err
+	}
+	net4 := iplib.Net4FromStr(cidrAddr)
+	newAddrs := net4.FirstAddress()
+	log.Println("COUNT: ", net4.Count())
+	for {
+		if runtime.GOOS == "darwin" {
+			_, err := common.RunCmd(fmt.Sprintf("ifconfig lo0 alias %s 255.255.255.255", newAddrs.String()), true)
+			if err != nil {
+				log.Println("Failed to add alias: ", err)
+			}
+		}
+
+		conn, err := net.DialUDP("udp", &net.UDPAddr{
+			IP:   net.ParseIP(newAddrs.String()),
+			Port: 51722,
+		}, &net.UDPAddr{
+			IP:   net.ParseIP("127.0.0.1"),
+			Port: 51820,
+		})
+		if err == nil {
+			conn.Close()
+			return newAddrs.String(), nil
+		}
+
+		newAddrs, err = net4.NextIP(newAddrs)
+		if err != nil {
+			return "", err
+		}
+
+	}
+}

+ 115 - 0
netclient/netclient-proxy/server/server.go

@@ -0,0 +1,115 @@
+package server
+
+import (
+	"fmt"
+	"log"
+	"net"
+	"time"
+
+	"github.com/gravitl/netmaker/netclient/netclient-proxy/common"
+	"github.com/gravitl/netmaker/netclient/netclient-proxy/packet"
+)
+
+const (
+	defaultBodySize = 10000
+	defaultPort     = 51722
+)
+
+type Config struct {
+	Port              int
+	BodySize          int
+	Addr              net.Addr
+	LocalWgInterfaces []string
+}
+
+type ProxyServer struct {
+	Config Config
+	Server *net.UDPConn
+}
+
+// Proxy.Listen - begins listening for packets
+func (p *ProxyServer) Listen() {
+
+	// Buffer with indicated body size
+	buffer := make([]byte, 1502)
+	for {
+		// Read Packet
+		n, source, err := p.Server.ReadFromUDP(buffer)
+		if err != nil { // in future log errors?
+			log.Println("RECV ERROR: ", err)
+			continue
+		}
+		var localWgPort int
+		localWgPort, n, err = packet.ExtractInfo(buffer, n)
+		if err != nil {
+			log.Println("failed to extract info: ", err)
+			continue
+		}
+		log.Println("--------> RECV PKT: ", source.IP.String(), localWgPort)
+		if val, ok := common.RemoteEndpointsMap[source.IP.String()]; ok {
+			for _, peerKeys := range val {
+				if peerI, ok := common.Peers[peerKeys]; ok {
+					if peerI.Config.LocalWgPort == int(localWgPort) {
+						log.Printf("PROXING TO LOCAL!!!---> %s <<<< %s <<<<<<<< %s\n", peerI.Proxy.LocalConn.RemoteAddr(),
+							peerI.Proxy.LocalConn.LocalAddr(), fmt.Sprintf("%s:%d", source.IP.String(), source.Port))
+						_, err = peerI.Proxy.LocalConn.Write(buffer[:n])
+						if err != nil {
+							log.Println("Failed to proxy to Wg local interface: ", err)
+							continue
+						}
+
+					}
+				}
+
+			}
+		}
+
+	}
+}
+
+// Create - creats a proxy listener
+// port - port for proxy to listen on localhost
+// bodySize - default 10000, leave 0 to use default
+// addr - the address for proxy to listen on
+// forwards - indicate address to forward to, {"<address:port>",...} format
+func CreateProxyServer(port, bodySize int, addr string) (p *ProxyServer, err error) {
+	if p == nil {
+		p = &ProxyServer{}
+	}
+	p.Config.Port = port
+	p.Config.BodySize = bodySize
+	p.setDefaults()
+	p.Server, err = net.ListenUDP("udp", &net.UDPAddr{
+		Port: p.Config.Port,
+		IP:   net.ParseIP(addr),
+	})
+	return
+}
+
+func (p *ProxyServer) KeepAlive(ip string, port int) {
+	for {
+		_, _ = p.Server.Write([]byte("hello-proxy"))
+		//fmt.Println("Sending MSg: ", err)
+		time.Sleep(time.Second)
+	}
+}
+
+// Proxy.setDefaults - sets all defaults of proxy listener
+func (p *ProxyServer) setDefaults() {
+	p.setDefaultBodySize()
+	p.setDefaultPort()
+}
+
+// Proxy.setDefaultPort - sets default port of Proxy listener if 0
+func (p *ProxyServer) setDefaultPort() {
+	if p.Config.Port == 0 {
+		p.Config.Port = defaultPort
+	}
+}
+
+// Proxy.setDefaultBodySize - sets default body size of Proxy listener if 0
+func (p *ProxyServer) setDefaultBodySize() {
+	if p.Config.BodySize == 0 {
+		p.Config.BodySize = defaultBodySize
+	}
+}

+ 66 - 0
netclient/netclient-proxy/stun/stun.go

@@ -0,0 +1,66 @@
+package stun
+
+import (
+	"fmt"
+	"log"
+	"net"
+	"strconv"
+	"strings"
+
+	"gortc.io/stun"
+)
+
+type HostInfo struct {
+	PublicIp net.IP
+	PrivIp   net.IP
+	PubPort  int
+	PrivPort int
+}
+
+var Host HostInfo
+
+func GetHostInfo() (info HostInfo) {
+
+	s, err := net.ResolveUDPAddr("udp", "stun.nm.134.209.115.146.nip.io:3478")
+	if err != nil {
+		log.Fatal("Resolve: ", err)
+	}
+	l := &net.UDPAddr{
+		IP:   net.ParseIP(""),
+		Port: 51722,
+	}
+	conn, err := net.DialUDP("udp", l, s)
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer conn.Close()
+	fmt.Printf("%+v\n", conn.LocalAddr())
+	c, err := stun.NewClient(conn)
+	if err != nil {
+		panic(err)
+	}
+	defer c.Close()
+	re := strings.Split(conn.LocalAddr().String(), ":")
+	info.PrivIp = net.ParseIP(re[0])
+	info.PrivPort, _ = strconv.Atoi(re[1])
+	// Building binding request with random transaction id.
+	message := stun.MustBuild(stun.TransactionID, stun.BindingRequest)
+	//fmt.Printf("MESG: %+v\n", message)
+	// Sending request to STUN server, waiting for response message.
+	if err := c.Do(message, func(res stun.Event) {
+		//fmt.Printf("RESP: %+v\n", res)
+		if res.Error != nil {
+			panic(res.Error)
+		}
+		// Decoding XOR-MAPPED-ADDRESS attribute from message.
+		var xorAddr stun.XORMappedAddress
+		if err := xorAddr.GetFrom(res.Message); err != nil {
+			panic(err)
+		}
+		info.PublicIp = xorAddr.IP
+		info.PubPort = xorAddr.Port
+	}); err != nil {
+		panic(err)
+	}
+	return
+}

+ 153 - 0
netclient/netclient-proxy/wg/wg.go

@@ -0,0 +1,153 @@
+package wg
+
+import (
+	"fmt"
+	"log"
+	"net"
+	"sync"
+	"time"
+
+	"golang.zx2c4.com/wireguard/wgctrl"
+	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
+)
+
+const (
+	DefaultMTU         = 1280
+	DefaultWgPort      = 51820
+	DefaultWgKeepAlive = 25 * time.Second
+)
+
+// WGIface represents a interface instance
+type WGIface struct {
+	Name      string
+	Port      int
+	MTU       int
+	Address   WGAddress
+	Interface NetInterface
+	mu        sync.Mutex
+}
+
+// NetInterface represents a generic network tunnel interface
+type NetInterface interface {
+	Close() error
+}
+
+// WGAddress Wireguard parsed address
+type WGAddress struct {
+	IP      net.IP
+	Network *net.IPNet
+}
+
+// NewWGIFace Creates a new Wireguard interface instance
+func NewWGIFace(iface string, address string, mtu int) (*WGIface, error) {
+	wgIface := &WGIface{
+		Name: iface,
+		MTU:  mtu,
+		mu:   sync.Mutex{},
+	}
+
+	wgAddress, err := parseAddress(address)
+	if err != nil {
+		return wgIface, err
+	}
+
+	wgIface.Address = wgAddress
+
+	return wgIface, nil
+}
+
+func (w *WGIface) GetWgIface(iface string) error {
+	wgClient, err := wgctrl.New()
+	if err != nil {
+		return err
+	}
+	wgClient.Device(iface)
+	return nil
+}
+
+// parseAddress parse a string ("1.2.3.4/24") address to WG Address
+func parseAddress(address string) (WGAddress, error) {
+	ip, network, err := net.ParseCIDR(address)
+	if err != nil {
+		return WGAddress{}, err
+	}
+	return WGAddress{
+		IP:      ip,
+		Network: network,
+	}, nil
+}
+
+// UpdatePeer updates existing Wireguard Peer or creates a new one if doesn't exist
+// Endpoint is optional
+func (w *WGIface) UpdatePeer(peerKey string, allowedIps []net.IPNet, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error {
+	w.mu.Lock()
+	defer w.mu.Unlock()
+
+	log.Printf("updating interface %s peer %s: endpoint %s ", w.Name, peerKey, endpoint)
+
+	// //parse allowed ips
+	// _, ipNet, err := net.ParseCIDR(allowedIps)
+	// if err != nil {
+	// 	return err
+	// }
+
+	peerKeyParsed, err := wgtypes.ParseKey(peerKey)
+	if err != nil {
+		return err
+	}
+	peer := wgtypes.PeerConfig{
+		PublicKey:                   peerKeyParsed,
+		ReplaceAllowedIPs:           true,
+		AllowedIPs:                  allowedIps,
+		PersistentKeepaliveInterval: &keepAlive,
+		PresharedKey:                preSharedKey,
+		Endpoint:                    endpoint,
+	}
+
+	config := wgtypes.Config{
+		Peers: []wgtypes.PeerConfig{peer},
+	}
+	err = w.configureDevice(config)
+	if err != nil {
+		return fmt.Errorf("received error \"%v\" while updating peer on interface %s with settings: allowed ips %s, endpoint %s", err, w.Name, allowedIps, endpoint.String())
+	}
+	return nil
+}
+
+// configureDevice configures the wireguard device
+func (w *WGIface) configureDevice(config wgtypes.Config) error {
+	wg, err := wgctrl.New()
+	if err != nil {
+		return err
+	}
+	defer wg.Close()
+
+	// validate if device with name exists
+	_, err = wg.Device(w.Name)
+	if err != nil {
+		return err
+	}
+	log.Printf("got Wireguard device %s\n", w.Name)
+
+	return wg.ConfigureDevice(w.Name, config)
+}
+
+// GetListenPort returns the listening port of the Wireguard endpoint
+func (w *WGIface) GetListenPort() (*int, error) {
+	log.Printf("getting Wireguard listen port of interface %s", w.Name)
+
+	//discover Wireguard current configuration
+	wg, err := wgctrl.New()
+	if err != nil {
+		return nil, err
+	}
+	defer wg.Close()
+
+	d, err := wg.Device(w.Name)
+	if err != nil {
+		return nil, err
+	}
+	log.Printf("got Wireguard device listen port %s, %d", w.Name, d.ListenPort)
+
+	return &d.ListenPort, nil
+}