diff --git a/browser/assets/tun.wasm b/browser/assets/tun.wasm index eb1b4bfc0..82a84e0cc 100755 Binary files a/browser/assets/tun.wasm and b/browser/assets/tun.wasm differ diff --git a/browser/conn/bind_webrtc.go b/browser/conn/bind_webrtc.go index 939b9b646..420d74e33 100644 --- a/browser/conn/bind_webrtc.go +++ b/browser/conn/bind_webrtc.go @@ -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 { diff --git a/browser/wasm/client_js.go b/browser/wasm/client_js.go index 0a1fbe6ea..091a398bd 100644 --- a/browser/wasm/client_js.go +++ b/browser/wasm/client_js.go @@ -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{} {