mirror of
https://github.com/netbirdio/netbird.git
synced 2026-07-17 20:19:55 +00:00
feature: successful WebRTC connection in wireguard bind
This commit is contained in:
Binary file not shown.
@@ -1,13 +1,20 @@
|
||||
package conn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/pion/webrtc/v3"
|
||||
signal "github.com/wiretrustee/wiretrustee/signal/client"
|
||||
"github.com/wiretrustee/wiretrustee/signal/proto"
|
||||
"golang.zx2c4.com/wireguard/conn"
|
||||
"log"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (*WebRTCBind) makeReceive(dcConn *DataChannelConn) conn.ReceiveFunc {
|
||||
func (*WebRTCBind) makeReceive(dcConn net.Conn) conn.ReceiveFunc {
|
||||
return func(buff []byte) (int, conn.Endpoint, error) {
|
||||
n, err := dcConn.Read(buff)
|
||||
if err != nil {
|
||||
@@ -20,33 +27,350 @@ func (*WebRTCBind) makeReceive(dcConn *DataChannelConn) conn.ReceiveFunc {
|
||||
|
||||
// WebRTCBind is an implementation of Wireguard Bind interface backed by WebRTC data channel
|
||||
type WebRTCBind struct {
|
||||
id string
|
||||
pc *webrtc.PeerConnection
|
||||
conn *DataChannelConn
|
||||
mu sync.Mutex
|
||||
id string
|
||||
pc *webrtc.PeerConnection
|
||||
conn net.Conn
|
||||
incoming chan *webrtc.DataChannel
|
||||
mu sync.Mutex
|
||||
signal signal.Client
|
||||
key string
|
||||
remoteKey string
|
||||
closeCond *Cond
|
||||
closeErr error
|
||||
}
|
||||
|
||||
func NewWebRTCBind(id string, pc *webrtc.PeerConnection) conn.Bind {
|
||||
func NewWebRTCBind(id string, signal signal.Client, pubKey string, remotePubKey string) conn.Bind {
|
||||
|
||||
return &WebRTCBind{
|
||||
id: id,
|
||||
pc: pc,
|
||||
conn: nil,
|
||||
mu: sync.Mutex{},
|
||||
id: id,
|
||||
pc: nil,
|
||||
conn: nil,
|
||||
signal: signal,
|
||||
mu: sync.Mutex{},
|
||||
key: pubKey,
|
||||
remoteKey: remotePubKey,
|
||||
closeCond: NewCond(),
|
||||
incoming: make(chan *webrtc.DataChannel, 1),
|
||||
}
|
||||
}
|
||||
|
||||
// acceptDC accepts a datachannel over opened WebRTC connection and wraps it into net.Conn
|
||||
// blocks until channel was successfully opened
|
||||
func (bind *WebRTCBind) acceptDC() (stream net.Conn, err error) {
|
||||
for dc := range bind.incoming {
|
||||
stream, err := WrapDataChannel(dc)
|
||||
if err != nil {
|
||||
dc.Close()
|
||||
return nil, err
|
||||
}
|
||||
log.Printf("accepted datachannel connection %s", dc.Label())
|
||||
|
||||
return stream, nil
|
||||
}
|
||||
return nil, context.Canceled
|
||||
}
|
||||
|
||||
// openDC creates datachannel over opened WebRTC connection and wraps it into net.Conn
|
||||
// blocks until channel was successfully opened
|
||||
func (bind *WebRTCBind) openDC() (stream net.Conn, err error) {
|
||||
dc, err := bind.pc.CreateDataChannel(bind.id, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open RTCDataChannel: %w", err)
|
||||
}
|
||||
|
||||
stream, err = WrapDataChannel(dc)
|
||||
if err != nil {
|
||||
dc.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Printf("opened datachannel connection %s", dc.Label())
|
||||
return stream, err
|
||||
}
|
||||
|
||||
func newPeerConnection() (*webrtc.PeerConnection, error) {
|
||||
config := webrtc.Configuration{
|
||||
ICEServers: []webrtc.ICEServer{
|
||||
{
|
||||
URLs: []string{"stun:stun.l.google.com:19302"},
|
||||
},
|
||||
},
|
||||
}
|
||||
pc, err := webrtc.NewPeerConnection(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pc, nil
|
||||
}
|
||||
|
||||
func (bind *WebRTCBind) Open(port uint16) (fns []conn.ReceiveFunc, actualPort uint16, err error) {
|
||||
|
||||
log.Printf("opening WebRTCBind connection")
|
||||
connected := NewCond()
|
||||
bind.pc, err = newPeerConnection()
|
||||
if err != nil {
|
||||
bind.pc.Close()
|
||||
return nil, 0, err
|
||||
}
|
||||
bind.pc.OnICEConnectionStateChange(func(state webrtc.ICEConnectionState) {
|
||||
switch state {
|
||||
case webrtc.ICEConnectionStateConnected:
|
||||
connected.Signal()
|
||||
case webrtc.ICEConnectionStateClosed:
|
||||
log.Printf("WebRTC connection closed")
|
||||
//TODO
|
||||
}
|
||||
})
|
||||
|
||||
bind.pc.OnDataChannel(func(dc *webrtc.DataChannel) {
|
||||
bind.incoming <- dc
|
||||
})
|
||||
|
||||
controlling := bind.key < bind.remoteKey
|
||||
// decision who is creating an offer
|
||||
if controlling {
|
||||
_, err = bind.pc.CreateDataChannel(bind.id, nil)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
offer, err := bind.pc.CreateOffer(nil)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
if err := bind.pc.SetLocalDescription(offer); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// Create channel that is blocked until ICE Gathering is complete
|
||||
gatherComplete := webrtc.GatheringCompletePromise(bind.pc)
|
||||
select {
|
||||
case <-gatherComplete:
|
||||
case <-bind.closeCond.C:
|
||||
return nil, 0, fmt.Errorf("closed while waiting for WebRTC candidates")
|
||||
}
|
||||
|
||||
log.Printf("candidates gathered")
|
||||
|
||||
err = bind.signal.Send(&proto.Message{
|
||||
Key: bind.key,
|
||||
RemoteKey: bind.remoteKey,
|
||||
Body: &proto.Body{
|
||||
Type: proto.Body_OFFER,
|
||||
Payload: Encode(bind.pc.LocalDescription()),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
log.Printf("sent an offer to a remote peer")
|
||||
|
||||
//answerCh := make(chan webrtc.SessionDescription, 1)
|
||||
|
||||
go bind.signal.Receive(func(msg *proto.Message) error {
|
||||
log.Printf("received a message from %v -> %v", msg.RemoteKey, msg.Body.Payload)
|
||||
if msg.GetBody().Type == proto.Body_ANSWER {
|
||||
log.Printf("received answer %s", msg.GetBody().GetPayload())
|
||||
err := setRemoteDescription(bind.pc, msg.GetBody().GetPayload())
|
||||
if err != nil {
|
||||
log.Printf("%v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
} else {
|
||||
gatherComplete := webrtc.GatheringCompletePromise(bind.pc)
|
||||
|
||||
go bind.signal.Receive(func(msg *proto.Message) error {
|
||||
log.Printf("received a message from %v -> %v", msg.RemoteKey, msg.Body.Payload)
|
||||
if msg.GetBody().Type == proto.Body_OFFER {
|
||||
log.Printf("received offer %s", msg.GetBody().GetPayload())
|
||||
|
||||
err = setRemoteDescription(bind.pc, msg.GetBody().GetPayload())
|
||||
if err != nil {
|
||||
log.Printf("%v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
sdp, err := bind.pc.CreateAnswer(nil)
|
||||
if err != nil {
|
||||
log.Printf("%v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := bind.pc.SetLocalDescription(sdp); err != nil {
|
||||
log.Printf("%v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
select {
|
||||
case <-gatherComplete:
|
||||
case <-bind.closeCond.C:
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Printf("candidates gathered")
|
||||
|
||||
err = bind.signal.Send(&proto.Message{
|
||||
Key: bind.key,
|
||||
RemoteKey: bind.remoteKey,
|
||||
Body: &proto.Body{
|
||||
Type: proto.Body_ANSWER,
|
||||
Payload: Encode(bind.pc.LocalDescription()),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("sent an answer to a remote peer")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
select {
|
||||
case <-time.After(30 * time.Second):
|
||||
return nil, 0, fmt.Errorf("failed to connect in time: %w", err)
|
||||
case <-connected.C:
|
||||
}
|
||||
log.Printf("WebRTC connection has opened successfully")
|
||||
|
||||
//once WebRTC has been established we can now create a datachannel and resume
|
||||
var dcConn net.Conn
|
||||
if controlling {
|
||||
dcConn, err = bind.openDC()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
} else {
|
||||
dcConn, err = bind.acceptDC()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
}
|
||||
bind.conn = dcConn
|
||||
fns = append(fns, bind.makeReceive(bind.conn))
|
||||
return fns, 38676, nil
|
||||
|
||||
}
|
||||
|
||||
/*func (bind *WebRTCBind) Open(port uint16) (fns []conn.ReceiveFunc, actualPort uint16, err error) {
|
||||
|
||||
log.Printf("OPEN 1------")
|
||||
|
||||
controlling := bind.key > bind.remoteKey
|
||||
|
||||
bind.mu.Lock()
|
||||
defer bind.mu.Unlock()
|
||||
|
||||
//todo whole webrtc logic from the beginning
|
||||
//todo create peer connection, offer/answer, wait until connected
|
||||
|
||||
dc, err := bind.pc.CreateDataChannel(bind.id, nil)
|
||||
config := webrtc.Configuration{
|
||||
ICEServers: []webrtc.ICEServer{
|
||||
{
|
||||
URLs: []string{"stun:stun.l.google.com:19302"},
|
||||
},
|
||||
},
|
||||
}
|
||||
pc, err := webrtc.NewPeerConnection(config)
|
||||
if err != nil {
|
||||
return nil, 0, nil
|
||||
return nil, 0, err
|
||||
}
|
||||
bind.pc = pc
|
||||
|
||||
log.Printf("OPEN 2------")
|
||||
|
||||
log.Printf("OPEN 3------")
|
||||
|
||||
var sdp webrtc.SessionDescription
|
||||
var dc *webrtc.DataChannel
|
||||
if controlling {
|
||||
// Create offer
|
||||
sdp, err = pc.CreateOffer(nil)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if err := pc.SetLocalDescription(sdp); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
dc, err = pc.CreateDataChannel(bind.id, nil)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
} else {
|
||||
dcConn, err := WrapDataChannel(dc)
|
||||
if err != nil {
|
||||
dc.Close()
|
||||
return nil, 0, err
|
||||
}
|
||||
}
|
||||
|
||||
go bind.signal.Receive(func(msg *proto.Message) error {
|
||||
log.Printf("received a message from %v -> %v", msg.RemoteKey, msg.Body.Payload)
|
||||
switch msg.GetBody().Type {
|
||||
case proto.Body_OFFER:
|
||||
|
||||
log.Printf("received offer %s", msg.GetBody().GetPayload())
|
||||
|
||||
err = setRemoteDescription(pc, msg.GetBody().GetPayload())
|
||||
if err != nil {
|
||||
log.Printf("%v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
sdp, err := pc.CreateAnswer(nil)
|
||||
if err != nil {
|
||||
log.Printf("%v", err)
|
||||
return err
|
||||
}
|
||||
if err := pc.SetLocalDescription(sdp); err != nil {
|
||||
log.Printf("%v", err)
|
||||
return err
|
||||
}
|
||||
break
|
||||
case proto.Body_ANSWER:
|
||||
|
||||
log.Printf("received answer %s", msg.GetBody().GetPayload())
|
||||
|
||||
err = setRemoteDescription(pc, msg.GetBody().GetPayload())
|
||||
if err != nil {
|
||||
log.Printf("%v", err)
|
||||
return err
|
||||
}
|
||||
break
|
||||
case proto.Body_CANDIDATE:
|
||||
log.Printf("received candidate %s", msg.GetBody().GetPayload())
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
// Add handlers for setting up the connection.
|
||||
pc.OnICEConnectionStateChange(func(state webrtc.ICEConnectionState) {
|
||||
fmt.Println(fmt.Sprint(state))
|
||||
})
|
||||
pc.OnICECandidate(func(candidate *webrtc.ICECandidate) {
|
||||
if candidate != nil {
|
||||
if controlling {
|
||||
bind.signal.Send(&proto.Message{
|
||||
Key: bind.key,
|
||||
RemoteKey: bind.remoteKey,
|
||||
Body: &proto.Body{
|
||||
Type: proto.Body_OFFER,
|
||||
Payload: Encode(pc.LocalDescription()),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
log.Printf("OPEN 4------")
|
||||
|
||||
// blocks until channel is open
|
||||
dcConn, err := WrapDataChannel(dc)
|
||||
if err != nil {
|
||||
dc.Close()
|
||||
@@ -57,7 +381,42 @@ func (bind *WebRTCBind) Open(port uint16) (fns []conn.ReceiveFunc, actualPort ui
|
||||
|
||||
fns = append(fns, bind.makeReceive(bind.conn))
|
||||
|
||||
log.Printf("OPEN 5------")
|
||||
|
||||
return fns, 38676, nil
|
||||
}*/
|
||||
|
||||
func setRemoteDescription(pc *webrtc.PeerConnection, payload string) error {
|
||||
descr, err := Decode(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = pc.SetRemoteDescription(*descr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("parsed SDP %s", descr.SDP)
|
||||
|
||||
return nil
|
||||
}
|
||||
func Decode(in string) (*webrtc.SessionDescription, error) {
|
||||
descr := &webrtc.SessionDescription{}
|
||||
err := json.Unmarshal([]byte(in), descr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return descr, nil
|
||||
}
|
||||
|
||||
func Encode(obj interface{}) string {
|
||||
b, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func (*WebRTCBind) Close() error {
|
||||
|
||||
@@ -6,19 +6,23 @@ import (
|
||||
"fmt"
|
||||
"github.com/wiretrustee/wiretrustee/browser/conn"
|
||||
"github.com/wiretrustee/wiretrustee/signal/client"
|
||||
"github.com/wiretrustee/wiretrustee/signal/proto"
|
||||
"golang.zx2c4.com/wireguard/device"
|
||||
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
/* "context"
|
||||
"github.com/wiretrustee/wiretrustee/signal/client"
|
||||
"github.com/wiretrustee/wiretrustee/signal/proto"*/
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"log"
|
||||
"net"
|
||||
"syscall/js"
|
||||
/* "time"*/)
|
||||
"time"
|
||||
)
|
||||
|
||||
func handleError(err error) {
|
||||
fmt.Printf("Unexpected error. Check console.")
|
||||
panic(err)
|
||||
}
|
||||
|
||||
func getElementByID(id string) js.Value {
|
||||
return js.Global().Get("document").Call("getElementById", id)
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -31,23 +35,17 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("connected to signal")
|
||||
|
||||
go func() {
|
||||
signalClient.Receive(func(msg *proto.Message) error {
|
||||
log.Printf("received a message from %v -> %v", msg.RemoteKey, msg.Body.Payload)
|
||||
return nil
|
||||
})
|
||||
}()
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
log.Printf("connected to signal")
|
||||
|
||||
tun, _, err := netstack.CreateNetTUN(
|
||||
[]net.IP{net.ParseIP("10.100.0.2")},
|
||||
[]net.IP{net.ParseIP("8.8.8.8")},
|
||||
1420)
|
||||
|
||||
dev := device.NewDevice(tun, &conn.WebRTCBind{}, device.NewLogger(device.LogLevelVerbose, ""))
|
||||
b := conn.NewWebRTCBind("chann-1", signalClient, key.PublicKey().String(), remoteKey.String())
|
||||
dev := device.NewDevice(tun, b, device.NewLogger(device.LogLevelVerbose, ""))
|
||||
|
||||
err = dev.IpcSet(fmt.Sprintf("private_key=%s\npublic_key=%s\npersistent_keepalive_interval=5\nendpoint=65.108.52.126:50000\nallowed_ip=0.0.0.0/0",
|
||||
hex.EncodeToString(key[:]),
|
||||
@@ -65,42 +63,7 @@ func main() {
|
||||
|
||||
log.Printf("device started")
|
||||
|
||||
/*stunURL, err := ice.ParseURL("stun:stun.wiretrustee.com:5555")
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
agent, err := ice.NewAgent(&ice.AgentConfig{
|
||||
Urls: []*ice.URL{stunURL},
|
||||
CandidateTypes: []ice.CandidateType{ice.CandidateTypeHost, ice.CandidateTypeServerReflexive, ice.CandidateTypeRelay},
|
||||
})
|
||||
|
||||
fmt.Println(agent)
|
||||
|
||||
err = agent.OnCandidate(func(candidate ice.Candidate) {
|
||||
fmt.Printf("gathered candidate %s", cancel)
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("started gathering candidates")*/
|
||||
|
||||
select {}
|
||||
|
||||
/*log.Printf("sending msg to signal")
|
||||
|
||||
err = signalClient.Send(&proto.Message{
|
||||
Key: key.PublicKey().String(),
|
||||
RemoteKey: remoteKey.String(),
|
||||
Body: &proto.Body{
|
||||
Type: 0,
|
||||
Payload: "hello",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}*/
|
||||
}
|
||||
|
||||
js.Global().Set("generateWireguardKey", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
|
||||
Reference in New Issue
Block a user