This commit is contained in:
Costin Manolache
2021-04-03 08:32:40 -07:00
parent fd5474ea74
commit 0e572dbe9b
24 changed files with 445 additions and 268 deletions

View File

@@ -54,6 +54,10 @@ push/docker.ugate: docker push/ugate
push/ugate:
docker push ${IMAGE}:latest
build/android:
cd android && gomobile bind -a -ldflags '-s -w' -target android -o android/ugate.aar .
# Using Intellij plugin: missing manifest features
# Build with buildpack: 30 sec to deploy
# Build with docker: 26 sec

View File

@@ -1,186 +0,0 @@
package android
import (
"encoding/base64"
"log"
"os"
"time"
"github.com/costinm/ugate"
"github.com/costinm/ugate/pkg/local"
"github.com/costinm/ugate/pkg/msgs"
"github.com/costinm/ugate/pkg/udp"
"github.com/costinm/ugate/pkg/ugatesvc"
"github.com/costinm/ugate/pkg/auth"
)
/*
gomobile bindings
- called 2x, once with lang=java and once with lang=go
- Signed integer and floating point types.
- String and boolean types.
- Byte slice types. Note that byte slices are passed by reference,
and support mutation.
- Any function type all of whose parameters and results have
supported types. Functions must return either no results,
one result, or two results where the type of the second is
the built-in 'error' type.
- Any interface type, all of whose exported methods have
supported function types.
- Any struct type, all of whose exported methods have
supported function types and all of whose exported fields
have supported types.
*/
// Adapter from func to interface
type HandlerCallbackFunc func(cmdS string, data []byte)
type MessageHandler interface {
Handle(topic string, data []byte)
}
var (
ld *local.LLDiscovery
gw *ugatesvc.UGate
udpGate = udp.NewUDPGate(nil, nil)
)
// Called to inject a message into Go impl
func Send(cmdS string, data []byte) {
switch cmdS {
case "r":
// refresh networks
log.Println("UDS: refresh network (r)")
go func() {
time.Sleep(2 * time.Second)
ld.RefreshNetworks()
}()
// TODO: P - properties, json
// CON - STOP/START - set connected WIFI
//
}
}
// Android and device version of DMesh.
func InitDmesh(callbackFunc MessageHandler) {
log.Print("Starting native process pwd=", os.Getenv("PWD"), os.Environ())
// SYSTEMSERVERCLASSPATH=/system/framework/services.jar:/system/framework/ethernet-service.jar:/system/framework/wifi-service.jar:/system/framework/com.android.location.provider.jar
// PATH=/sbin:/system/sbin:/system/bin:/system/xbin:/odm/bin:/vendor/bin:/vendor/xbin
// STORAGE=/storage/emulated/0/Android/data/com.github.costinm.dmwifi/files
// ANDROID_DATA=/data
// ANDROID_SOCKET_zygote_secondary=12
// ASEC_MOUNTPOINT=/mnt/asec
// EXTERNAL_STORAGE=/sdcard
// ANDROID_BOOTLOGO=1
// ANDROID_ASSETS=/system/app
// BASE=/data/user/0/com.github.costinm.dmwifi/files
// ANDROID_STORAGE=/storage
// ANDROID_ROOT=/system
// DOWNLOAD_CACHE=/data/cache
// BOOTCLASSPATH=/system/framework/core-oj.jar:/system/framework/core-libart.jar:/system/framework/conscrypt.jar:/system/framework/okhttp.jar:/system/framework/bouncycastle.jar:/system/framework/apache-xml.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/telephony-common.jar:/system/framework/voip-common.jar:/system/framework/ims-common.jar:/system/framework/android.hidl.base-V1.0-java.jar:/system/framework/android.hidl.manager-V1.0-java.jar:/system/framework/framework-oahl-backward-compatibility.jar:/system/framework/android.test.base.jar:/system/framework/com.google.vr.platform.jar]
cfgf := os.Getenv("BASE")
if cfgf == "" {
cfgf = os.Getenv("HOME")
if cfgf == "" {
cfgf = os.Getenv("TEMPDIR")
}
if cfgf == "" {
cfgf = os.Getenv("TMP")
}
if cfgf == "" {
cfgf = "/tmp"
}
}
cfgf += "/"
// File-based config
config := ugatesvc.NewConf(cfgf)
//meshH := "v.webinf.info:5222" // ugatesvc.Conf(config, "MESH", "v.webinf.info:5222")
// Init or load certificates/keys
authz := auth.NewAuth(config, os.Getenv("HOSTNAME"), "v.webinf.info")
msgs.DefaultMux.Auth = authz
gcfg := &ugate.GateCfg{
BasePort: 15000,
}
GW := ugatesvc.NewGate(nil, authz, gcfg, nil)
// HTTPGate - common structures
//GW := mesh.New(authz, nil)
// SSH transport + reverse streams.
//sshg := sshgate.NewSSHGate(GW, authz)
//GW.SSHGate = sshg
//sshg.InitServer()
//sshg.ListenSSH(":5222")
//
//// Connect to a mesh node
//if meshH != "" {
// GW.Vpn = meshH
// go sshgate.MaintainVPNConnection(GW)
//}
// Local discovery interface - multicast, local network IPs
ld := local.NewLocal(GW, authz)
go ld.PeriodicThread()
local.ListenUDP(ld)
GW.Mux.HandleFunc("/dmesh/ll/if", ld.HttpGetLLIf)
//h2s, err := h2.NewTransport(authz)
//if err != nil {
// log.Fatal(err)
//}
// DNS capture, interpret the names, etc
// Off until DNS moved to smaller package.
//dnss, _ := dns.NewDmDns(5223)
//GW.DNS = dnss
//net.DefaultResolver.PreferGo = true
//net.DefaultResolver.Dial = dns.DNSDialer(5223)
//udpNat := udp.NewUDPGate(GW)
//udpNat.DNS = dnss
//hgw := httpproxy.NewHTTPGate(GW, h2s)
//hgw.HttpProxyCapture("localhost:5204")
// Start a basic UI on the debug port
// u, _ := ui.NewUI(GW, h2s, hgw, ld)
//udpNat.InitMux(h2s.LocalMux)
//// Periodic registrations.
//m.Registry.RefreshNetworksPeriodic()
log.Printf("Loading with VIP6: %v ID64: %s\n",
authz.VIP6,
base64.RawURLEncoding.EncodeToString(authz.VIP6[8:]))
//dnss.Serve()
//err = http.ListenAndServe("localhost:5227", u)
//if err != nil {
// log.Println(err)
//}
// TODO: mux adapter, so we can exchange messages.
}

View File

@@ -3,13 +3,15 @@ module github.com/costinm/ugate/cmd/ugate
go 1.16
replace github.com/costinm/ugate => ../../
replace github.com/costinm/ugate/webrtc => ../../webrtc
replace github.com/costinm/ugate/dns => ../../dns
replace github.com/costinm/ugate/webpush => ../../webpush
require (
github.com/costinm/ugate v0.0.0-20210221155556-10edd21fadbf
golang.org/x/net v0.0.0-20201224014010-6772e930b67b
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68
github.com/costinm/ugate v0.0.0-20210328173325-afc113d007e8
github.com/costinm/ugate/dns v0.0.0-00010101000000-000000000000
github.com/costinm/ugate/webpush v0.0.0-20210329161419-fd5474ea74fe
)

26
cmd/ugate/go.sum Normal file
View File

@@ -0,0 +1,26 @@
github.com/miekg/dns v1.1.40 h1:pyyPFfGMnciYUk/mXpKkVmeMQjfXqt3FAJ2hy7tPiLA=
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -33,14 +33,10 @@
"hosts": {
"localiperf": {
"addr": "localhost:15102"
},
"h.webinf.info": {
"addr": "h.webinf.info:15007",
"id": "B5B6KYYUBVKCX4PWPWSWAIHW2X2D3Q4HZPJYWZ6UECL2PAODHTFA"
},
"c1.webinf.info": {
"addr": "c1.webinf.info:15007"
}
},
"remoteAccept": null
"remoteAccept": {
"h.webinf.info": "B5B6KYYUBVKCX4PWPWSWAIHW2X2D3Q4HZPJYWZ6UECL2PAODHTFA",
"c1.webinf.info": ""
}
}

View File

@@ -44,19 +44,19 @@ hosts:
addr: localhost:15102
# Live test server
h.webinf.info:
addr: h.webinf.info:15007
id: B5B6KYYUBVKCX4PWPWSWAIHW2X2D3Q4HZPJYWZ6UECL2PAODHTFA
c1.webinf.info:
addr: c1.webinf.info:15007
# h.webinf.info:
# addr: h.webinf.info:15007
# id: B5B6KYYUBVKCX4PWPWSWAIHW2X2D3Q4HZPJYWZ6UECL2PAODHTFA
#
# c1.webinf.info:
# addr: c1.webinf.info:15007
# Remote accept request (reverse forward, "-R")
# Will also include relays, stun, etc.
# TODO: could also be part of the hosts: definition, include a list
# of ports. This way we don't dup the public key.
remoteAccept:
#h.webinf.info: "B5B6KYYUBVKCX4PWPWSWAIHW2X2D3Q4HZPJYWZ6UECL2PAODHTFA"
h.webinf.info: "B5B6KYYUBVKCX4PWPWSWAIHW2X2D3Q4HZPJYWZ6UECL2PAODHTFA"
#c1.webinf.info: ""
c1.webinf.info: ""

View File

@@ -1,14 +1,18 @@
package main
import (
"fmt"
"log"
"net"
_ "net/http/pprof"
"github.com/costinm/ugate/pkg/local"
msgs "github.com/costinm/ugate/webpush"
ug "github.com/costinm/ugate/pkg/ugatesvc"
"github.com/costinm/ugate"
"github.com/costinm/ugate/dns"
"github.com/costinm/ugate/pkg/udp"
"github.com/costinm/ugate/pkg/ugatesvc"
)
type startFunc func(ug *ugatesvc.UGate)
var initHooks []func(gate *ugatesvc.UGate) startFunc
// Minimal TCP over H2 Gateway, defaulting to Istio ports and capture behavior.
// There is no envoy - all traffic is upgraded to optional mTLS + H2 and sent to
@@ -21,34 +25,54 @@ import (
// - option to use mTLS - if the network is secure ( ipsec or equivalent ) no encryption
// - detect TLS and pass it through
// - inbound: extract metadata
// - DNS and DNS capture (if root)
// - control plane using webpush messaging
// - webRTC and H2 for mesh communication
// - convert from H2/H1, based on local port config.
//
// Extras:
// - SOCKS and PROXY
//
// This does not include lwIP, which is now only used with AndroidVPN in
// JNI mode.
func main() {
// Load configs from the current dir and var/lib/dmesh, or env variables
// Writes to current dir.
config := ug.NewConf("./", "./var/lib/dmesh")
config := ugatesvc.NewConf("./", "./var/lib/dmesh")
//
// Start a Gate. Basic H2 and H2R services enabled.
ug := ug.NewGate(&net.Dialer{}, nil, nil, config)
ug := ugatesvc.NewGate(&net.Dialer{}, nil, nil, config)
// Initialize the messaging.
msgs.DefaultMux.Auth = ug.Auth
// Discover local nodes using multicast UDP
localgw := local.NewLocal(ug, ug.Auth)
local.ListenUDP(localgw)
ug.Mux.HandleFunc("/dmesh/ll/if", localgw.HttpGetLLIf)
sf := []startFunc{}
if initHooks != nil {
for _, h := range initHooks {
s := h(ug)
if s != nil {
sf = append(sf, s)
}
}
}
// Init DNS capture and server
dnss, _ := dns.NewDmDns(5223)
//GW. = dnss
net.DefaultResolver.PreferGo = true
net.DefaultResolver.Dial = dns.DNSDialer(5223)
// Init Iptables capture (off by default - android doesn't like it)
// UDP Gate is used with TProxy and lwIP.
udpNat := udp.NewUDPGate(dnss, dnss)
udpNat.InitMux(ug.Mux)
hproxy := ugatesvc.NewHTTPProxy(ug)
hproxy.HttpProxyCapture(fmt.Sprintf("127.0.0.1:%d", ug.Config.BasePort+ugate.PORT_HTTP_PROXY))
// Init WebRTC port
go dnss.Serve()
for _, h := range sf {
go h(ug)
}
log.Println("Started: ", ug.Auth.ID)
select {}

11
cmd/ugate/ugate_dns.go Normal file
View File

@@ -0,0 +1,11 @@
package main
import (
"github.com/costinm/ugate/pkg/ugatesvc"
)
func init() {
initHooks = append(initHooks, func(ug *ugatesvc.UGate) startFunc{
return nil
})
}

View File

@@ -0,0 +1,18 @@
package main
import (
"fmt"
"github.com/costinm/ugate"
"github.com/costinm/ugate/pkg/iptables"
"github.com/costinm/ugate/pkg/ugatesvc"
)
func init() {
initHooks = append(initHooks, func(ug *ugatesvc.UGate) startFunc {
// Init Iptables capture (off by default - android doesn't like it)
iptables.IptablesCapture(ug, fmt.Sprintf("0.0.0.0:%d", ug.Config.BasePort+ugate.PORT_IPTABLES), false)
iptables.IptablesCapture(ug, fmt.Sprintf("0.0.0.0:%d", ug.Config.BasePort+ugate.PORT_IPTABLES_IN), true)
return nil
})
}

17
cmd/ugate/ugate_local.go Normal file
View File

@@ -0,0 +1,17 @@
package main
import (
"github.com/costinm/ugate/pkg/local"
"github.com/costinm/ugate/pkg/ugatesvc"
)
func init() {
initHooks = append(initHooks, func(ug *ugatesvc.UGate) startFunc {
// Discover local nodes using multicast UDP
localgw := local.NewLocal(ug, ug.Auth)
local.ListenUDP(localgw)
go localgw.PeriodicThread()
ug.Mux.HandleFunc("/dmesh/ll/if", localgw.HttpGetLLIf)
return nil
})
}

View File

@@ -0,0 +1,13 @@
package main
import (
"github.com/costinm/ugate/pkg/ugatesvc"
"github.com/costinm/ugate/webpush"
)
func init() {
initHooks = append(initHooks, func(gate *ugatesvc.UGate) startFunc {
webpush.InitMux(webpush.DefaultMux, gate.Mux, gate.Auth)
return nil
})
}

View File

@@ -7,6 +7,8 @@ import (
"github.com/costinm/ugate/pkg/ugatesvc"
)
var initHooks []func(gate *ugatesvc.UGate)
// Minimal uGate - not using any optional package.
// Used to determine the 'base' size and cost of various options.
//
@@ -26,6 +28,12 @@ func main() {
// Start a Gate. Basic H2 and H2R services enabled.
ug := ugatesvc.NewGate(&net.Dialer{}, nil, cfg, config)
if initHooks != nil {
for _, h := range initHooks {
h(ug)
}
}
// direct TCP connect to local iperf3 and fortio (or HTTP on default port)
ug.Add(&ugate.Listener{
Address: ":12011",

View File

@@ -2,15 +2,74 @@ package iptables
import (
"errors"
"log"
"net"
"os"
"syscall"
"time"
"unsafe"
"github.com/costinm/ugate"
"github.com/costinm/ugate/pkg/ugatesvc"
)
//
func IptablesCapture(ug *ugatesvc.UGate, addr string, in bool) error {
// For http proxy we need a dedicated plain HTTP port
nl, err := net.Listen("tcp", addr)
if err != nil {
log.Println("Failed to listen", err)
return err
}
for {
remoteConn, err := nl.Accept()
ugate.VarzAccepted.Add(1)
if ne, ok := err.(net.Error); ok {
ugate.VarzAcceptErr.Add(1)
if ne.Temporary() {
time.Sleep(100 * time.Millisecond)
continue
}
}
if err != nil {
log.Println("Accept error, closing iptables listener ", err)
return err
}
go handleAcceptedConn(ug, remoteConn, in)
}
return nil
}
// Mirroring handleAcceptedConn in UGate
func handleAcceptedConn(ug *ugatesvc.UGate, acceptedCon net.Conn, in bool) {
bconn := ugate.GetConn(acceptedCon)
ug.TrackStreamIN(bconn.Meta())
defer ug.OnAcceptDone(bconn)
str := bconn.Meta()
//case ugate.ProtoIPTablesIn:
// // iptables is replacing the conn - process before creating the buffer
// DestAddr is also set as a sideeffect
str.Dest, str.ReadErr = SniffIptables(str)
if str.ReadErr != nil {
return
}
cfg := ug.FindCfgIptablesIn(bconn)
if cfg.ForwardTo != "" {
str.Dest = cfg.ForwardTo
}
str.Listener = cfg
str.Type = cfg.Protocol
if !in {
str.Egress = true
}
str.ReadErr = ug.HandleStream(str)
}
// Status:
// - TCP capture with redirect works
@@ -27,7 +86,7 @@ import (
// https://github.com/ryanchapman/go-any-proxy/blob/master/any_proxy.go,
// and other examples.
// Based on REDIRECT.
func SniffIptables(str *ugate.Stream, proto string) (string, error) {
func SniffIptables(str *ugate.Stream) (string, error) {
if _, ok := str.Out.(*net.TCPConn); !ok {
return "", errors.New("invalid connection for iptbles")
}

View File

@@ -46,6 +46,7 @@ const (
extensionServerName uint16 = 0
)
// Expecting SNI
func SniffSNI(acc *ugate.RawConn) error {
acc.Sniff()
@@ -72,6 +73,7 @@ func SniffSNI(acc *ugate.RawConn) error {
rlen := int(buf[3])<<8 | int(buf[4])
if rlen > 4096 {
log.Println("RLen ", rlen)
return sniErr
}
@@ -94,6 +96,7 @@ func SniffSNI(acc *ugate.RawConn) error {
chLen := end - 5
if chLen < 38 {
log.Println("chLen ", chLen)
return sniErr
}
@@ -104,6 +107,7 @@ func SniffSNI(acc *ugate.RawConn) error {
sessionIdLen := int(clientHello[38])
if sessionIdLen > 32 || chLen < 39+sessionIdLen {
log.Println("sLen ", sessionIdLen)
return sniErr
}
m.sessionId = clientHello[39 : 39+sessionIdLen]

View File

@@ -136,7 +136,6 @@ type UdpNat struct {
type UDPGateConfig struct {
DNS ugate.UDPHandler
HostResolver ugate.HostResolver
}
type UDPGate struct {

View File

@@ -20,7 +20,7 @@ import (
// Called at the end of the connection handling. After this point
// nothing should use or refer to the connection, both proxy directions
// should already be closed for write or fully closed.
func (ug *UGate) onAcceptDone(rc ugate.MetaConn) {
func (ug *UGate) OnAcceptDone(rc ugate.MetaConn) {
str := rc.Meta()
ug.m.Lock()
delete(ug.ActiveTcp, str.StreamId)
@@ -85,7 +85,7 @@ func (ug *UGate) onAcceptDone(rc ugate.MetaConn) {
// Deferred to connection close, only for the raw accepted connection.
func (ug *UGate) onAcceptDoneAndRecycle(rc *ugate.RawConn) {
ug.onAcceptDone(rc)
ug.OnAcceptDone(rc)
ugate.BufferedConPool.Put(rc)
}
@@ -121,8 +121,8 @@ func (ug *UGate) onAcceptDoneAndRecycle(rc *ugate.RawConn) {
func (ug *UGate) HandleTUN(conn net.Conn, target *net.TCPAddr) error {
bconn := ugate.GetConn(conn)
bconn.Meta().Egress = true
ug.trackStreamIN(bconn.Meta())
defer ug.onAcceptDone(bconn)
ug.TrackStreamIN(bconn.Meta())
defer ug.OnAcceptDone(bconn)
ra := conn.RemoteAddr()
//la := conn.LocalAddr()
@@ -139,17 +139,18 @@ func (ug *UGate) HandleTUN(conn net.Conn, target *net.TCPAddr) error {
}
bconn.Stream.Egress = true
log.Println("TUN TCP ", bconn.Meta())
// TODO: config ? Could be shared with iptables port
return ug.handleStream(bconn.Meta())
return ug.HandleStream(bconn.Meta())
}
// Handle a virtual (multiplexed) stream, received over
// another connection.
func (ug *UGate) HandleVirtualIN(bconn ugate.MetaConn) error {
ug.trackStreamIN(bconn.Meta())
defer ug.onAcceptDone(bconn)
ug.TrackStreamIN(bconn.Meta())
defer ug.OnAcceptDone(bconn)
return ug.handleStream(bconn.Meta())
return ug.HandleStream(bconn.Meta())
}
// At this point the stream has the metadata:
@@ -158,7 +159,7 @@ func (ug *UGate) HandleVirtualIN(bconn ugate.MetaConn) error {
// - Headers
// - TLS context
// - Dest and Listener are set.
func (ug *UGate) handleStream(str *ugate.Stream) error {
func (ug *UGate) HandleStream(str *ugate.Stream) error {
if str.Listener == nil {
str.Listener = ug.DefaultListener
}
@@ -166,7 +167,7 @@ func (ug *UGate) handleStream(str *ugate.Stream) error {
if cfg.Protocol == ugate.ProtoHTTPS {
str.PostDial(str, nil)
return ug.h2Handler.Handle(str)
return ug.H2Handler.Handle(str)
}
// Config has an in-process handler - not forwarding (or the handler may
@@ -182,7 +183,7 @@ func (ug *UGate) handleStream(str *ugate.Stream) error {
return ug.dialOut(str)
}
func (ug *UGate) trackStreamIN(s *ugate.Stream) {
func (ug *UGate) TrackStreamIN(s *ugate.Stream) {
ug.m.Lock()
ug.ActiveTcp[s.StreamId] = s
ug.m.Unlock()
@@ -211,8 +212,8 @@ func (ug *UGate) handleAcceptedConn(l *ugate.Listener, acceptedCon net.Conn) {
// Get a buffered stream - this is used for sniffing.
// Most common case is TLS, we want the SNI.
bconn := ugate.GetConn(acceptedCon)
ug.trackStreamIN(bconn.Meta())
defer ug.onAcceptDone(bconn)
ug.TrackStreamIN(bconn.Meta())
defer ug.OnAcceptDone(bconn)
str := bconn.Meta()
// Special protocols, muxed on a single port - will extract real
@@ -222,25 +223,24 @@ func (ug *UGate) handleAcceptedConn(l *ugate.Listener, acceptedCon net.Conn) {
switch cfg.Protocol {
// TODO: costin: does not compile on android gomobile, missing syscall.
// remove dep, reverse it.
//case ugate.ProtoIPTablesIn:
// // iptables is replacing the conn - process before creating the buffer
// str.Dest, str.ReadErr = iptables.SniffIptables(str, cfg.Protocol)
// cfg = ug.findCfgIptablesIn(bconn)
//case ugate.ProtoIPTables:
// str.Dest, str.ReadErr = iptables.SniffIptables(str, cfg.Protocol)
// str.Egress = true
case ugate.ProtoSocks:
str.Egress = true
str.ReadErr = socks.ReadSocksHeader(bconn)
case ugate.ProtoHTTP:
str.ReadErr = ug.h2Handler.handleHTTPListener(l, bconn)
str.ReadErr = ug.H2Handler.handleHTTPListener(l, bconn)
return
case ugate.ProtoHTTPS:
tlsTerm = true
// Used to present the right cert
str.ReadErr = sni.SniffSNI(bconn)
if str.ReadErr != nil {
log.Println("XXX Failed to snif SNI", str.ReadErr)
}
case ugate.ProtoTLS:
str.ReadErr = sni.SniffSNI(bconn)
if str.ReadErr != nil {
log.Println("XXX Failed to snif SNI in TLS", str.ReadErr, l.Address, l.Protocol, bconn.Meta())
}
if str.Dest == "" {
// No destination - terminate here
// TODO: also if dest hostname == local name or VIP or ID
@@ -280,7 +280,7 @@ func (ug *UGate) handleAcceptedConn(l *ugate.Listener, acceptedCon net.Conn) {
tlsOrOrigStr = tc.Meta()
}
str.ReadErr = ug.handleStream(tlsOrOrigStr)
str.ReadErr = ug.HandleStream(tlsOrOrigStr)
}
// Auto-detect protocol on the wire, so routing info can be

View File

@@ -135,7 +135,7 @@ func (t *H2Transport) GetClientConn(req *http.Request, addr string) (*http2.Clie
return nil, err
}
cc, err := t.ug.h2Handler.h2t.NewClientConn(tc)
cc, err := t.ug.H2Handler.h2t.NewClientConn(tc)
return cc, err
}

View File

@@ -67,6 +67,7 @@ func NewH2Transport(ug *UGate) (*H2Transport, error) {
}
// UpdateReverseAccept updates the upstream accept connections, based on config.
// Should be called when the config changes
func (t *H2Transport) UpdateReverseAccept() {
for addr, key := range t.ug.Config.H2R {
t.maintainRemoteAccept(addr, key)
@@ -147,6 +148,12 @@ func (l *H2Transport) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
// HTTP/1.1
if r.Method == "CONNECT" {
// WS or HTTP Proxy
}
//if r.ProtoMajor > 1 {
if len(parts) > 2 {
if parts[1] == "h2r" {
@@ -180,11 +187,6 @@ func (l *H2Transport) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}
// HTTP/1.1
if r.Method == "CONNECT" {
// WS or HTTP Proxy
}
if strings.HasPrefix(r.RequestURI, "/ws") {
} else {

View File

@@ -0,0 +1,152 @@
package ugatesvc
import (
"io"
"log"
"net"
"net/http"
"strings"
"github.com/costinm/ugate"
)
// Used for HTTP_PROXY=localhost:port, to intercept outbound traffic using http proxy protocol.
// CONNECT too.
// Experimental, not the main capture mode - TUN and SOCKS should be used if possible.
// HTTPGate handles HTTP requests
type HTTPGate struct {
//Auth *auth.Auth
gw *UGate
}
func NewHTTPProxy(gw *UGate) *HTTPGate {
return &HTTPGate{
gw: gw,
}
}
// Start listening on the addr, as a HTTP_PROXY
// Handles CONNECT and PROXY requests using the gateway
// for streams.
func (gw *HTTPGate) HttpProxyCapture(addr string) error {
// For http proxy we need a dedicated plain HTTP port
nl, err := net.Listen("tcp", addr)
if err != nil {
log.Println("Failed to listen", err)
return err
}
go http.Serve(nl, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == "CONNECT" {
gw.handleConnect(w, r)
return
}
// This is a real HTTP proxy
if r.URL.IsAbs() {
log.Println("HTTPPRX", r.Method, r.Host, r.RemoteAddr, r.URL)
gw.captureHttpProxyAbsURL(w, r)
return
}
}))
return nil
}
// Http proxy to a configured HTTP host. Hostname to HTTP address explicitly
// configured. Also hostnmae to file serving.
func (gw *HTTPGate) proxy(w http.ResponseWriter, r *http.Request) bool {
// TODO: if host is XXXX.m.SUFFIX -> forward to node.
host, found := gw.gw.Config.Hosts[r.Host]
if !found {
return false
}
if len(host.Addr) > 0 {
log.Println("FWDHTTP: ", r.Method, r.Host, r.RemoteAddr, r.URL)
gw.gw.H2Handler.ForwardHTTP(w, r, host.Addr)
}
return true
}
// WIP: HTTP proxy with absolute address, to a QUIC server (or sidecar)`
func (gw *HTTPGate) captureHttpProxyAbsURL(w http.ResponseWriter, r *http.Request) {
// HTTP proxy mode - uses the QUIC client to connect to the node
// TODO: redirect via VPN, only root VPN can do plaintext requests
// parse r.URL, follow the same steps as TCP - if mesh use Client/mtls, if VPN set forward to VPN, else use H2 client
// r.Host is populated from the absolute URL.
// Typical headers (curl):
// User-Agent, Acept, Proxy-Connection:Keep-Alive
if gw.proxy(w, r) {
return
}
ht := &http.Transport{
DialContext: gw.gw.DialContext,
}
hc := &http.Client{Transport: ht}
// TODO: use VPN to Dial !!!
//
resp, err := hc.Transport.RoundTrip(r)
if err != nil {
log.Println("XXX ", err)
http.Error(w, err.Error(), 500)
return
}
origBody := resp.Body
defer origBody.Close()
CopyResponseHeaders(w.Header(), resp.Header)
w.WriteHeader(resp.StatusCode)
io.Copy(w, resp.Body)
log.Println("PHTTP: ", r.URL)
}
// WIP: If method is CONNECT - operate in TCP proxy mode. This can be used to proxy
// a TCP UdpNat to a mesh node, from localhost or from a net node.
// Only used to capture local traffic - should be bound to localhost only, like socks.
// It speaks HTTP/1.1, no QUIC
func (gw *HTTPGate) handleConnect(w http.ResponseWriter, r *http.Request) {
hij, ok := w.(http.Hijacker)
if !ok {
w.WriteHeader(503)
w.Write([]byte("Error - no hijack support"))
return
}
host := r.URL.Host
if !strings.Contains(host, ":") {
host = host + ":443"
}
// TODO: second param may contain unprocessed data.
proxyClient, _, e := hij.Hijack()
if e != nil {
w.WriteHeader(503)
w.Write([]byte("Error - no hijack support"))
return
}
//ra := proxyClient.RemoteAddr().(*net.TCPAddr)
str := ugate.GetConn(proxyClient)
defer gw.gw.OnAcceptDone(str)
gw.gw.TrackStreamIN(str.Meta())
str.Stream.Dest = host
str.Stream.Egress = true
str.PostDialHandler = func(conn net.Conn, err error) {
if err != nil {
w.WriteHeader(503)
w.Write([]byte("Dial error" + err.Error()))
return
}
proxyClient.Write([]byte("HTTP/1.0 200 OK\r\n\r\n"))
}
// TODO: add sniffing on the outbound
gw.gw.dialOut(str.Meta())
}

View File

@@ -13,7 +13,7 @@ import (
"github.com/costinm/ugate/pkg/pipe"
)
// After a Stream ( TCP+meta or HTTP ) is accepted, we need to route it based on
// After a Stream ( TCP+meta or HTTP ) is accepted/captured, we need to route it based on
// the config.
//
// Use cases:
@@ -26,6 +26,8 @@ import (
var NotFound = errors.New("not found")
// Primary function for egress streams, after metadata has been parsed.
//
// Dial the target and proxy to it.
// - if Dest is a mesh node, use BTS
// - else use TCP proxy.
@@ -163,7 +165,7 @@ func (ug *UGate) CreateConn(ctx context.Context, dmn *ugate.DMNode) (error) {
return err
}
cc, err := ug.h2Handler.h2t.NewClientConn(lconn)
cc, err := ug.H2Handler.h2t.NewClientConn(lconn)
if err != nil {
nc.Close()
return err

View File

@@ -14,7 +14,7 @@ import (
"github.com/costinm/ugate"
"github.com/costinm/ugate/pkg/auth"
"github.com/costinm/ugate/pkg/msgs"
msgs "github.com/costinm/ugate/webpush"
)
@@ -45,7 +45,7 @@ type UGate struct {
// template, used for TLS connections and the host ID
TLSConfig *tls.Config
h2Handler *H2Transport
H2Handler *H2Transport
// Direct Nodes by interface address (which is derived from public key). This includes only
// directly connected notes - either Wifi on same segment, or VPNs and
@@ -85,11 +85,24 @@ func NewGate(d ugate.ContextDialer, a *auth.Auth, cfg *ugate.GateCfg, cs ugate.C
}
}
}
if cfg.H2R == nil {
cfg.H2R = map[string]string{}
}
if cfg.Hosts == nil {
cfg.Hosts = map[string]*ugate.DMNode{}
}
if cfg.Listeners == nil {
cfg.Listeners = map[string]*ugate.Listener{}
}
if cs != nil {
Get(cs, "ugate", cfg)
}
if d == nil {
d = &net.Dialer{}
}
if a == nil {
a = auth.NewAuth(cs, cfg.Name, cfg.Domain)
}
@@ -137,9 +150,9 @@ func NewGate(d ugate.ContextDialer, a *auth.Auth, cfg *ugate.GateCfg, cs ugate.C
//SessionTicketsDisabled: true,
}
ug.h2Handler, _ = NewH2Transport(ug)
ug.H2Handler, _ = NewH2Transport(ug)
go ug.h2Handler.UpdateReverseAccept()
go ug.H2Handler.UpdateReverseAccept()
ug.Mux.Handle("/debug/", http.DefaultServeMux)
ug.Mux.HandleFunc("/dmesh/tcpa", ug.HttpTCP)
@@ -159,6 +172,8 @@ func NewGate(d ugate.ContextDialer, a *auth.Auth, cfg *ugate.GateCfg, cs ugate.C
ug.Add(t)
}
return ug
}
@@ -285,10 +300,7 @@ func (ug *UGate) DefaultPorts(base int) error {
// Egress: iptables and SOCKS5
// Not on localhost - redirect changes the port, keeps IP
ug.Add(&ugate.Listener{
Address: fmt.Sprintf("0.0.0.0:%d", base+ugate.PORT_IPTABLES),
Protocol: ugate.ProtoIPTables,
})
ug.Add(&ugate.Listener{
Address: fmt.Sprintf("127.0.0.1:%d", base+ugate.PORT_SOCKS),
Protocol: ugate.ProtoSocks,
@@ -296,6 +308,10 @@ func (ug *UGate) DefaultPorts(base int) error {
// TODO: add HTTP CONNECT for egress.
// Ingress: iptables ( capture all incoming )
ug.Add(&ugate.Listener{
Address: fmt.Sprintf("0.0.0.0:%d", base+ugate.PORT_IPTABLES),
Protocol: ugate.ProtoIPTables,
})
ug.Add(&ugate.Listener{
Address: fmt.Sprintf("0.0.0.0:%d", base+ugate.PORT_IPTABLES_IN),
Protocol: ugate.ProtoIPTablesIn,
@@ -312,7 +328,7 @@ func (ug *UGate) DefaultPorts(base int) error {
ug.Add(&ugate.Listener{
Address: fmt.Sprintf("0.0.0.0:%d", base+ugate.PORT_HTTPS),
Protocol: ugate.ProtoHTTPS,
Handler: ug.h2Handler,
Handler: ug.H2Handler,
})
go func() {
err := http.ListenAndServe(fmt.Sprintf(":%d", base), ug.Mux)
@@ -324,7 +340,9 @@ func (ug *UGate) DefaultPorts(base int) error {
return nil
}
func (ug *UGate) findCfgIptablesIn(bconn ugate.MetaConn) *ugate.Listener {
// Based on the port in the Dest, find the Listener config.
// Used when the dest IP:port is extracted from the metadata
func (ug *UGate) FindCfgIptablesIn(bconn ugate.MetaConn) *ugate.Listener {
m := bconn.Meta()
_, p, _ := net.SplitHostPort(m.Dest)
l := ug.Config.Listeners["-:"+p]

View File

@@ -197,7 +197,7 @@ func TestSrv(t *testing.T) {
// This is a H2 request that is forwarded to a stream.
p := pipe.New()
r, _ := http.NewRequest("POST", "https://127.0.0.1:6107/dm/"+"127.0.0.1:6112", p)
res, err := ag.h2Handler.RoundTrip(r)
res, err := ag.H2Handler.RoundTrip(r)
if err != nil {
t.Fatal(err)
}
@@ -214,13 +214,13 @@ func TestSrv(t *testing.T) {
ag.Config.H2R = map[string]string{
"127.0.0.1:6107": "",
}
ag.h2Handler.UpdateReverseAccept()
ag.H2Handler.UpdateReverseAccept()
// Connecting to Bob's gateway (from c). Request should go to Alice.
//
p := pipe.New()
r, _ := http.NewRequest("POST",
"https://127.0.0.1:6107/dm/"+ag.Auth.ID, p)
res, err := cg.h2Handler.RoundTrip(r)
res, err := cg.H2Handler.RoundTrip(r)
if err != nil {
t.Fatal(err)
@@ -235,8 +235,8 @@ func TestSrv(t *testing.T) {
ag.Config.H2R = map[string]string{
}
ag.h2Handler.UpdateReverseAccept()
if len(ag.h2Handler.reverse) > 0 {
ag.H2Handler.UpdateReverseAccept()
if len(ag.H2Handler.reverse) > 0 {
t.Error("Failed to disconnect")
}
})

View File

@@ -76,7 +76,12 @@ const (
PORT_IPTABLES = 1
PORT_IPTABLES_IN = 6
PORT_SOCKS = 9
// SNI and HTTP could share the same port - would also
// reduce missconfig risks
PORT_HTTP_PROXY = 2
PORT_SNI = 3
// H2, HTTPS, H2R
PORT_HTTPS = 7
)

View File

@@ -70,8 +70,11 @@ func NewMux() *Mux {
var DefaultMux = NewMux()
func InitMux(mux *Mux, auth *auth.Auth) {
func InitMux(mux *Mux, hmux *http.ServeMux, auth *auth.Auth) {
mux.Auth = auth
hmux.HandleFunc("/push/", DefaultMux.HTTPHandlerWebpush)
hmux.HandleFunc("/subscribe", SubscribeHandler)
hmux.HandleFunc("/s/", HTTPHandlerSend)
}
// Send a message to the default mux. Will serialize the event and save it for debugging.