├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── as.go
├── config.centrifugo.test.json
├── eventHandler.go
├── go.mod
├── go.sum
├── how.png
├── main.go
├── main_test.go
└── to.go
/.gitignore:
--------------------------------------------------------------------------------
1 | webrtc-socket-proxy
2 | centrifugo
3 | .vscode
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 |
3 | go:
4 | - master
5 |
6 | before_script:
7 | - wget https://github.com/centrifugal/centrifugo/releases/download/v2.2.2/centrifugo_2.2.2_linux_amd64.tar.gz
8 | - tar xzf centrifugo_2.2.2_linux_amd64.tar.gz
9 | - ./centrifugo --config config.centrifugo.test.json --log_level=debug &
10 |
11 | script:
12 | - go build
13 | - go test
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2019 Chieh Po
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | webrtc-socket-proxy
3 |
4 | Seamless peer-to-peer TCP socket proxy using WebRTC, with centrifugo as the signal server
5 |
6 |
7 |
8 |
9 | 
10 |
11 | 
12 |
13 |
14 | ## Setup
15 |
16 | * Install `webrtc-socket-proxy`
17 |
18 | ```
19 | $ go get -u github.com/poga/webrtc-socket-proxy
20 | ```
21 |
22 | * Setup [centrifugo](https://github.com/centrifugal/centrifugo/releases) with [example config](config.centrifugo.test.json).
23 |
24 | ## Usage
25 |
26 | ```
27 | # the `As` proxy
28 | $ webrtc-socket-proxy -signal= -secret= -as= -upstreamAddr=localhost:8000
29 | # the `To` proxy
30 | $ webrtc-socket-proxy -signal= -secret= -to= -listen=:4444
31 | ```
32 |
33 | You can send data to your `` via connecting to `:4444` now.
34 |
35 | ## Roadmap
36 |
37 | - [ ] TURN server support
38 | - [ ] Multiplex Connections. Currently we only support one connnection per proxy-pair
39 |
40 | ## License
41 |
42 | The MIT License
--------------------------------------------------------------------------------
/as.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "net"
7 |
8 | "github.com/pion/webrtc/v2"
9 | "github.com/rs/zerolog/log"
10 |
11 | centrifuge "github.com/centrifugal/centrifuge-go"
12 | jwt "github.com/dgrijalva/jwt-go"
13 | )
14 |
15 | // As listen to new webRTC connection and proxy it to upstream tcp socket
16 | type As struct {
17 | UpstreamAddr string
18 | SignalServerAddr string
19 | ID string
20 |
21 | peerConn *webrtc.PeerConnection
22 |
23 | UpstreamChan chan []byte
24 | }
25 |
26 | // NewAs returns a new "As" proxy
27 | func NewAs(ID string, secret string, upstreamAddr string, signalServerAddr string) *As {
28 | log.Print("hello")
29 |
30 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
31 | "sub": ID,
32 | })
33 |
34 | // Sign and get the complete encoded token as a string using the secret
35 | tokenString, err := token.SignedString([]byte(secret))
36 |
37 | as := As{ID: ID, UpstreamAddr: upstreamAddr, SignalServerAddr: signalServerAddr}
38 | upChan := make(chan []byte)
39 | as.UpstreamChan = upChan
40 |
41 | signalConfig := centrifuge.DefaultConfig()
42 | log.Print(as.SignalServerAddr)
43 | signalClient := centrifuge.New(as.SignalServerAddr, signalConfig)
44 | signalClient.SetToken(tokenString)
45 | signalClient.Connect()
46 |
47 | webRTCconfig := webrtc.Configuration{
48 | ICEServers: []webrtc.ICEServer{
49 | {
50 | URLs: []string{"stun:stun.l.google.com:19302"},
51 | },
52 | },
53 | }
54 | peerConnection, err := webrtc.NewPeerConnection(webRTCconfig)
55 | if err != nil {
56 | panic(err)
57 | }
58 |
59 | dataChannel, err := peerConnection.CreateDataChannel("data", nil)
60 | if err != nil {
61 | panic(err)
62 | }
63 |
64 | dataChannel.OnOpen(func() {
65 | log.Printf("Data channel '%s'-'%d' open. Random messages will now be sent to any connected DataChannels every 5 seconds", dataChannel.Label(), dataChannel.ID())
66 |
67 | if as.UpstreamAddr == "" {
68 | log.Error().Msgf("cannot connect to upstream \"%s\"", as.UpstreamAddr)
69 | return
70 | }
71 | go func(upChan chan []byte) {
72 | upAddr, err := net.ResolveTCPAddr("tcp", as.UpstreamAddr)
73 | if err != nil {
74 | panic(err)
75 | }
76 | upConn, err := net.DialTCP("tcp", nil, upAddr)
77 | if err != nil {
78 | panic(err)
79 | }
80 | // read from socket
81 | go func() {
82 | for {
83 | data := make([]byte, 1024)
84 | n, err := upConn.Read(data)
85 | if err != nil {
86 | panic(err)
87 | }
88 | log.Printf("sending to down stream %#v", string(data[:n]))
89 | dataChannel.SendText(string(data[:n]))
90 | }
91 | }()
92 |
93 | for {
94 | data := <-upChan
95 |
96 | if _, err := upConn.Write(data); err != nil {
97 | panic(err)
98 | }
99 | }
100 | }(upChan)
101 | })
102 | dataChannel.OnMessage(func(msg webrtc.DataChannelMessage) {
103 | log.Printf("Message from DataChannel '%s': %#v", dataChannel.Label(), string(msg.Data))
104 |
105 | as.UpstreamChan <- msg.Data
106 | })
107 |
108 | // Create an offer to send to the browser
109 | offer, err := peerConnection.CreateOffer(nil)
110 | if err != nil {
111 | panic(err)
112 | }
113 | offerData, err := json.Marshal(offer)
114 | if err != nil {
115 | panic(err)
116 | }
117 | err = signalClient.Publish(fmt.Sprintf("offer-%s", ID), offerData)
118 | if err != nil {
119 | panic(err)
120 | }
121 | err = peerConnection.SetLocalDescription(offer)
122 | if err != nil {
123 | panic(err)
124 | }
125 |
126 | answerChan := make(chan []byte)
127 | sub, err := signalClient.NewSubscription(fmt.Sprintf("answer-%s", ID))
128 | if err != nil {
129 | panic(err)
130 | }
131 | subHandler := &subEventHandler{answerChan}
132 | sub.OnPublish(subHandler)
133 | sub.OnJoin(subHandler)
134 | sub.OnLeave(subHandler)
135 |
136 | err = sub.Subscribe()
137 | if err != nil {
138 | panic(err)
139 | }
140 |
141 | log.Print("waiting answer")
142 |
143 | // wait answer
144 | answerData := <-answerChan
145 | log.Print("got answer")
146 | var answer webrtc.SessionDescription
147 | err = json.Unmarshal(answerData, &answer)
148 | if err != nil {
149 | panic(err)
150 | }
151 |
152 | log.Printf("%v", answer)
153 |
154 | // Apply the answer as the remote description
155 | err = peerConnection.SetRemoteDescription(answer)
156 | if err != nil {
157 | panic(err)
158 | }
159 |
160 | return &as
161 | }
162 |
--------------------------------------------------------------------------------
/config.centrifugo.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "secret": "secret",
3 | "publish": true,
4 | "join_leave": true,
5 | "history_size": 10,
6 | "history_lifetime": 30
7 | }
--------------------------------------------------------------------------------
/eventHandler.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 |
6 | centrifuge "github.com/centrifugal/centrifuge-go"
7 | "github.com/rs/zerolog/log"
8 | )
9 |
10 | type subEventHandler struct {
11 | answerChan chan []byte
12 | }
13 |
14 | func (h *subEventHandler) OnPublish(sub *centrifuge.Subscription, e centrifuge.PublishEvent) {
15 | log.Print(fmt.Sprintf("New publication received from channel %s: %s", sub.Channel(), string(e.Data)))
16 | h.answerChan <- e.Data
17 | }
18 |
19 | func (h *subEventHandler) OnJoin(sub *centrifuge.Subscription, e centrifuge.JoinEvent) {
20 | log.Print(fmt.Sprintf("User %s (client ID %s) joined channel %s", e.User, e.Client, sub.Channel()))
21 | }
22 |
23 | func (h *subEventHandler) OnLeave(sub *centrifuge.Subscription, e centrifuge.LeaveEvent) {
24 | log.Print(fmt.Sprintf("User %s (client ID %s) left channel %s", e.User, e.Client, sub.Channel()))
25 | }
26 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/poga/webrtc-socket-proxy
2 |
3 | go 1.12
4 |
5 | require (
6 | github.com/centrifugal/centrifuge-go v0.2.3
7 | github.com/dgrijalva/jwt-go v3.2.0+incompatible
8 | github.com/gogo/protobuf v1.3.0 // indirect
9 | github.com/gorilla/websocket v1.4.1 // indirect
10 | github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7 // indirect
11 | github.com/pion/webrtc/v2 v2.1.0
12 | github.com/rs/zerolog v1.15.0
13 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a
14 | google.golang.org/grpc v1.23.1
15 | )
16 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
3 | github.com/centrifugal/centrifuge-go v0.2.3 h1:eopSUVHJTl3oU1B4Lq1eTZGkJvchVZxbMq8tBfSb/sM=
4 | github.com/centrifugal/centrifuge-go v0.2.3/go.mod h1:yQliRRkEQKOuekQGqKqtUIzk0KaDCk164JumfRWDXsc=
5 | github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
6 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
7 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
10 | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
11 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
12 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
13 | github.com/gogo/protobuf v1.3.0 h1:G8O7TerXerS4F6sx9OV7/nRfJdnXgHZu/S/7F2SN+UE=
14 | github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
15 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
16 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
17 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
18 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
19 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
20 | github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
21 | github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
22 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
23 | github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7 h1:K//n/AqR5HjG3qxbrBCL4vJPW0MVFSs9CPK1OOJdRME=
24 | github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
25 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
26 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
27 | github.com/lucas-clemente/quic-go v0.7.1-0.20190401152353-907071221cf9/go.mod h1:PpMmPfPKO9nKJ/psF49ESTAGQSdfXxlg1otPbEB2nOw=
28 | github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk=
29 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
30 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
31 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
32 | github.com/pion/datachannel v1.4.5 h1:paz18kYAetpTdK8tlMAtDY+Ayxrv5fndZ5XPZwiZHrU=
33 | github.com/pion/datachannel v1.4.5/go.mod h1:SpMJbuu8v+qbA94m6lWQwSdCf8JKQvgmdSHDNtcbe+w=
34 | github.com/pion/dtls v1.5.0 h1:GtN3DJ0Fv5wC/Y04uOXT1zPeGA4C3HrsP1aAKsIBiU8=
35 | github.com/pion/dtls v1.5.0/go.mod h1:CjlPLfQdsTg3G4AEXjJp8FY5bRweBlxHrgoFrN+fQsk=
36 | github.com/pion/ice v0.5.5 h1:1S16+2EkN1aaH9uC4mp6HUqpbKg8Y69GbTybLGu9GcI=
37 | github.com/pion/ice v0.5.5/go.mod h1:FSTDLP+ian3PtxRjervtyDP2AOqt2c6cvfebZ7dwLnI=
38 | github.com/pion/logging v0.2.1/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
39 | github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
40 | github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
41 | github.com/pion/mdns v0.0.3 h1:DxdOYd0pgwLKiDlIIxfU0qdG5iWh1Xn6CsS9vc6cMAY=
42 | github.com/pion/mdns v0.0.3/go.mod h1:VrN3wefVgtfL8QgpEblPUC46ag1reLIfpqekCnKunLE=
43 | github.com/pion/quic v0.1.1/go.mod h1:zEU51v7ru8Mp4AUBJvj6psrSth5eEFNnVQK5K48oV3k=
44 | github.com/pion/rtcp v1.2.1 h1:S3yG4KpYAiSmBVqKAfgRa5JdwBNj4zK3RLUa8JYdhak=
45 | github.com/pion/rtcp v1.2.1/go.mod h1:a5dj2d6BKIKHl43EnAOIrCczcjESrtPuMgfmL6/K6QM=
46 | github.com/pion/rtp v1.1.3 h1:GTYSTsSLF5vH+UqShGYQEBdoYasWjTTC9UeYglnUO+o=
47 | github.com/pion/rtp v1.1.3/go.mod h1:/l4cvcKd0D3u9JLs2xSVI95YkfXW87a3br3nqmVtSlE=
48 | github.com/pion/sctp v1.6.3/go.mod h1:cCqpLdYvgEUdl715+qbWtgT439CuQrAgy8BZTp0aEfA=
49 | github.com/pion/sctp v1.6.4 h1:edUNxTabSErLWOdeUUAxds8gwx5kGnFam4zL5DWpILk=
50 | github.com/pion/sctp v1.6.4/go.mod h1:cCqpLdYvgEUdl715+qbWtgT439CuQrAgy8BZTp0aEfA=
51 | github.com/pion/sdp/v2 v2.3.0 h1:5EhwPh1xKWYYjjvMuubHoMLy6M0B9U26Hh7q3f7vEGk=
52 | github.com/pion/sdp/v2 v2.3.0/go.mod h1:idSlWxhfWQDtTy9J05cgxpHBu/POwXN2VDRGYxT/EjU=
53 | github.com/pion/srtp v1.2.6 h1:mHQuAMh0P67R7/j1F260u3O+fbRWLyjKLRPZYYvODFM=
54 | github.com/pion/srtp v1.2.6/go.mod h1:rd8imc5htjfs99XiEoOjLMEOcVjME63UHx9Ek9IGst0=
55 | github.com/pion/stun v0.3.1 h1:d09JJzOmOS8ZzIp8NppCMgrxGZpJ4Ix8qirfNYyI3BA=
56 | github.com/pion/stun v0.3.1/go.mod h1:xrCld6XM+6GWDZdvjPlLMsTU21rNxnO6UO8XsAvHr/M=
57 | github.com/pion/transport v0.6.0/go.mod h1:iWZ07doqOosSLMhZ+FXUTq+TamDoXSllxpbGcfkCmbE=
58 | github.com/pion/transport v0.7.0/go.mod h1:iWZ07doqOosSLMhZ+FXUTq+TamDoXSllxpbGcfkCmbE=
59 | github.com/pion/transport v0.8.6 h1:xHQq2mxAjB+UrFs90aUBaXwlmIACfQAZnOiVAX3uqMw=
60 | github.com/pion/transport v0.8.6/go.mod h1:nAmRRnn+ArVtsoNuwktvAD+jrjSD7pA+H3iRmZwdUno=
61 | github.com/pion/turn v1.3.3 h1:jO8bYTgUZ7ls6BCVa5lIQhxu56UFc5afX1A50vfxFio=
62 | github.com/pion/turn v1.3.3/go.mod h1:zGPB7YYB/HTE9MWn0Sbznz8NtyfeVeanZ834cG/MXu0=
63 | github.com/pion/webrtc/v2 v2.1.0 h1:ZaZQI+wmhVtAO11afRg0p+15sxhs9w+CZlSd8Ur+grE=
64 | github.com/pion/webrtc/v2 v2.1.0/go.mod h1:74qrJ4vD600EX5sEDRUxbmr5eu7Ug0GwITuj7c7aZnI=
65 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
66 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
67 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
68 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
69 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
70 | github.com/rs/zerolog v1.15.0 h1:uPRuwkWF4J6fGsJ2R0Gn2jB1EQiav9k3S6CSdygQJXY=
71 | github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
72 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
73 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
74 | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
75 | golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
76 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
77 | golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
78 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a h1:Igim7XhdOpBnWPuYJ70XcNpq8q3BCACtVgNfoJxOV7g=
79 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
80 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
81 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
82 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
83 | golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
84 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU=
85 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
86 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
87 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
88 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
89 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
90 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
91 | golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
92 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
93 | golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI=
94 | golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
95 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
96 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
97 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
98 | golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
99 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
100 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
101 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
102 | google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
103 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
104 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
105 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
106 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
107 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
108 |
--------------------------------------------------------------------------------
/how.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ailabstw/webrtc-socket-proxy/bd691390ce48c264d17db4f8f651393f884747a9/how.png
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "os"
6 |
7 | "github.com/rs/zerolog"
8 | "github.com/rs/zerolog/log"
9 | )
10 |
11 | var (
12 | listenAddr = flag.String("listen", ":4444", "proxy listen address")
13 | upstreamAddr = flag.String("upstream", "", "proxy upstream address")
14 | signalServerAddr = flag.String("signal", "ws://localhost:8000/connection/websocket", "signaling server address")
15 |
16 | secret = flag.String("secret", "", "server secret")
17 |
18 | as = flag.String("as", "", "proxy ID")
19 | to = flag.String("to", "", "proxy target ID")
20 | )
21 |
22 | func main() {
23 | log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout})
24 | flag.Parse()
25 |
26 | if *as != "" {
27 | NewAs(*as, *secret, *upstreamAddr, *signalServerAddr)
28 |
29 | for {
30 | }
31 | } else if *to != "" {
32 | p := NewTo(*to, *secret, *signalServerAddr, *listenAddr)
33 |
34 | log.Print("Proxy listening" + p.Listen + " to target " + p.ID)
35 | p.ListenAndServe()
36 |
37 | return
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/main_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "net"
5 | "os"
6 | "testing"
7 | "time"
8 |
9 | "github.com/rs/zerolog"
10 | "github.com/rs/zerolog/log"
11 | )
12 |
13 | func TestSmoke(t *testing.T) {
14 | log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout})
15 | go newEchoServer(":9064")
16 |
17 | signalServerAddr := "ws://localhost:8000/connection/websocket"
18 |
19 | go NewAs("smoke", "secret", ":9064", signalServerAddr)
20 |
21 | time.Sleep(1 * time.Second)
22 |
23 | to := NewTo("smoke", "secret", signalServerAddr, ":9063")
24 | go to.ListenAndServe()
25 |
26 | time.Sleep(1 * time.Second)
27 |
28 | receiveCount := 0
29 |
30 | go func() {
31 | addr, err := net.ResolveTCPAddr("tcp", ":9063")
32 | if err != nil {
33 | panic(err)
34 | }
35 | conn, err := net.DialTCP("tcp", nil, addr)
36 | if err != nil {
37 | panic(err)
38 | }
39 |
40 | log.Print("connected")
41 |
42 | // read from socket
43 | go func() {
44 | for {
45 | data := make([]byte, 1024)
46 | n, err := conn.Read(data)
47 | if err != nil {
48 | panic(err)
49 | }
50 | resp := string(data[:n])
51 | log.Printf("echo received: %#v", resp)
52 |
53 | if resp == "smoke test\n" {
54 | receiveCount++
55 | } else {
56 | t.Errorf("incorrect echo data %#v", data)
57 | }
58 |
59 | if receiveCount == 3 {
60 | os.Exit(0)
61 | }
62 | }
63 | }()
64 | for i := 0; i < 3; i++ {
65 | if _, err := conn.Write([]byte("smoke test\n")); err != nil {
66 | panic(err)
67 | }
68 | time.Sleep(1 * time.Second)
69 | }
70 | }()
71 |
72 | for {
73 | }
74 | }
75 |
76 | func newEchoServer(port string) {
77 | l, err := net.Listen("tcp", port)
78 | if err != nil {
79 | panic(err)
80 | }
81 | log.Print("Listening to connections on :9064")
82 | defer l.Close()
83 |
84 | for {
85 | conn, err := l.Accept()
86 | if err != nil {
87 | panic(err)
88 | }
89 |
90 | go handleRequest(conn)
91 | }
92 | }
93 |
94 | func handleRequest(conn net.Conn) {
95 | log.Print("Accepted new connection.")
96 | defer conn.Close()
97 | defer log.Print("Closed connection.")
98 |
99 | for {
100 | buf := make([]byte, 1024)
101 | size, err := conn.Read(buf)
102 | if err != nil {
103 | return
104 | }
105 | data := buf[:size]
106 | log.Printf("Read new data from connection %#v", string(data))
107 | conn.Write(data)
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/to.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "net"
7 |
8 | "github.com/rs/zerolog/log"
9 |
10 | centrifuge "github.com/centrifugal/centrifuge-go"
11 | jwt "github.com/dgrijalva/jwt-go"
12 | "github.com/pion/webrtc/v2"
13 | )
14 |
15 | // To listen to tcp socket and proxy it to webrtc connection
16 | type To struct {
17 | Listen string
18 | SignalServerAddr string
19 | ID string
20 |
21 | DataChannel *webrtc.DataChannel
22 |
23 | tcpConn *net.Conn
24 | }
25 |
26 | // NewTo returns a new "To" proxy
27 | func NewTo(ID string, secret string, signalServerAddr string, listen string) *To {
28 | log.Print("new to")
29 | to := To{ID: ID, SignalServerAddr: signalServerAddr, Listen: listen}
30 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
31 | "sub": ID,
32 | })
33 |
34 | // Sign and get the complete encoded token as a string using the secret
35 | tokenString, err := token.SignedString([]byte(secret))
36 |
37 | signalConfig := centrifuge.DefaultConfig()
38 | signalClient := centrifuge.New(to.SignalServerAddr, signalConfig)
39 | signalClient.SetToken(tokenString)
40 | signalClient.Connect()
41 |
42 | config := webrtc.Configuration{
43 | ICEServers: []webrtc.ICEServer{
44 | {
45 | URLs: []string{"stun:stun.l.google.com:19302"},
46 | },
47 | },
48 | }
49 | peerConnection, err := webrtc.NewPeerConnection(config)
50 | if err != nil {
51 | panic(err)
52 | }
53 |
54 | // Set the handler for ICE connection state
55 | // This will notify you when the peer has connected/disconnected
56 | peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
57 | log.Printf("ICE Connection State has changed: %s", connectionState.String())
58 | })
59 |
60 | webRTCConnReady := make(chan struct{})
61 |
62 | // Register data channel creation handling
63 | peerConnection.OnDataChannel(func(d *webrtc.DataChannel) {
64 | log.Printf("New DataChannel %s %d", d.Label(), d.ID())
65 | to.DataChannel = d
66 |
67 | // Register channel opening handling
68 | d.OnOpen(func() {
69 | log.Printf("Data channel '%s'-'%d' open. Random messages will now be sent to any connected DataChannels every 5 seconds", d.Label(), d.ID())
70 | webRTCConnReady <- struct{}{}
71 | })
72 |
73 | // Register text message handling
74 | d.OnMessage(func(msg webrtc.DataChannelMessage) {
75 | log.Printf("Message from DataChannel '%s': %#v", d.Label(), string(msg.Data))
76 |
77 | (*to.tcpConn).Write(msg.Data)
78 | })
79 | })
80 | offerChan := make(chan []byte)
81 | sub, err := signalClient.NewSubscription(fmt.Sprintf("offer-%s", ID))
82 | if err != nil {
83 | panic(err)
84 | }
85 | subHandler := &subEventHandler{offerChan}
86 | sub.OnPublish(subHandler)
87 | sub.OnJoin(subHandler)
88 | sub.OnLeave(subHandler)
89 | err = sub.Subscribe()
90 | if err != nil {
91 | panic(err)
92 | }
93 |
94 | history, err := sub.History()
95 | if err != nil {
96 | panic(err)
97 | }
98 | if len(history) > 0 {
99 | log.Printf("%s", history[0].Data)
100 | offerData := fmt.Sprintf("%s", history[0].Data)
101 | go func() { offerChan <- []byte(offerData[:]) }()
102 | }
103 |
104 | log.Print("waiting offer")
105 | offerData := <-offerChan
106 | log.Print("got offer")
107 | var offer webrtc.SessionDescription
108 | err = json.Unmarshal(offerData, &offer)
109 | if err != nil {
110 | panic(err)
111 | }
112 |
113 | err = peerConnection.SetRemoteDescription(offer)
114 | if err != nil {
115 | panic(err)
116 | }
117 |
118 | // Create answer
119 | answer, err := peerConnection.CreateAnswer(nil)
120 | if err != nil {
121 | panic(err)
122 | }
123 | // signal answer
124 | answerData, err := json.Marshal(answer)
125 | if err != nil {
126 | panic(err)
127 | }
128 | err = signalClient.Publish(fmt.Sprintf("answer-%s", ID), answerData)
129 | if err != nil {
130 | panic(err)
131 | }
132 |
133 | // Sets the LocalDescription, and starts our UDP listeners
134 | err = peerConnection.SetLocalDescription(answer)
135 | if err != nil {
136 | panic(err)
137 | }
138 |
139 | return &to
140 | }
141 |
142 | // ListenAndServe listens on the TCP network address laddr and then handle packets
143 | // on incoming connections.
144 | func (s *To) ListenAndServe() error {
145 | listener, err := net.Listen("tcp", s.Listen)
146 | if err != nil {
147 | return err
148 | }
149 | return s.serve(listener)
150 | }
151 |
152 | func (s *To) serve(ln net.Listener) error {
153 | for {
154 | conn, err := ln.Accept()
155 | if err != nil {
156 | log.Print(err)
157 | continue
158 | }
159 | go s.handleConn(conn)
160 | }
161 | }
162 |
163 | func (s *To) handleConn(conn net.Conn) {
164 | if s.tcpConn != nil {
165 | log.Error().Msg("Only one connection is supported")
166 | conn.Close()
167 | return
168 | }
169 | s.tcpConn = &conn
170 | // write to dst what it reads from src
171 | var pipe = func(src net.Conn) {
172 | defer func() {
173 | conn.Close()
174 | }()
175 |
176 | for {
177 | data := make([]byte, 1024)
178 | n, err := src.Read(data)
179 | if err != nil {
180 | panic(err)
181 | }
182 | log.Printf("proxying %#v", string(data[:n]))
183 | err = s.DataChannel.SendText(string(data[:n]))
184 | if err != nil {
185 | panic(err)
186 | }
187 | }
188 | }
189 |
190 | go pipe(conn)
191 | }
192 |
--------------------------------------------------------------------------------