Skip to content

Commit dae3343

Browse files
committed
Add ice-proxy example
1 parent f5d98ce commit dae3343

File tree

5 files changed

+362
-1
lines changed

5 files changed

+362
-1
lines changed

examples/ice-proxy/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# ice-proxy
2+
ice-proxy demonstrates Pion WebRTC's proxy abilities.
3+
4+
## Instructions
5+
6+
### Download ice-proxy
7+
This example requires you to clone the repo since it is serving static HTML.
8+
9+
```
10+
git clone https://github.com/pion/webrtc.git
11+
cd webrtc/examples/ice-proxy
12+
```
13+
14+
### Run ice-proxy
15+
Execute `go run *.go`
16+
17+
### Open the Web UI
18+
Open [http://localhost:8080](http://localhost:8080). This will automatically start a PeerConnection. This page will now prints stats about the PeerConnection.
19+
20+
Congrats, you have used Pion WebRTC! Now start building something cool

examples/ice-proxy/index.html

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<html>
2+
<!--
3+
SPDX-FileCopyrightText: 2025 The Pion community <https://pion.ly>
4+
SPDX-License-Identifier: MIT
5+
-->
6+
<head>
7+
<title>ice-proxy</title>
8+
</head>
9+
10+
<body>
11+
<h1>ICE Proxy</h1>
12+
13+
<h3> ICE Connection States </h3>
14+
<div id="iceConnectionStates"></div> <br />
15+
16+
<h3> Inbound DataChannel Messages </h3>
17+
<div id="inboundDataChannelMessages"></div>
18+
</body>
19+
20+
<script>
21+
let pc = new RTCPeerConnection({iceServers: [{
22+
urls: 'turn:127.0.0.1:17342?transport=tcp',
23+
username: 'turn_username',
24+
credential: 'turn_password',
25+
}]})
26+
let dc = pc.createDataChannel('data')
27+
28+
dc.onmessage = event => {
29+
let el = document.createElement('p')
30+
el.appendChild(document.createTextNode(event.data))
31+
32+
document.getElementById('inboundDataChannelMessages').appendChild(el);
33+
}
34+
35+
pc.oniceconnectionstatechange = () => {
36+
let el = document.createElement('p')
37+
el.appendChild(document.createTextNode(pc.iceConnectionState))
38+
39+
document.getElementById('iceConnectionStates').appendChild(el);
40+
}
41+
42+
pc.createOffer()
43+
.then(offer => {
44+
pc.setLocalDescription(offer)
45+
46+
return fetch(`/doSignaling`, {
47+
method: 'post',
48+
headers: {
49+
'Accept': 'application/json, text/plain, */*',
50+
'Content-Type': 'application/json'
51+
},
52+
body: JSON.stringify(offer)
53+
})
54+
})
55+
.then(res => res.json())
56+
.then(res => {
57+
pc.setRemoteDescription(res)
58+
})
59+
.catch(alert)
60+
</script>
61+
</html>

examples/ice-proxy/main.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// SPDX-FileCopyrightText: 2025 The Pion community <https://pion.ly>
2+
// SPDX-License-Identifier: MIT
3+
4+
//go:build !js
5+
// +build !js
6+
7+
// ice-proxy demonstrates Pion WebRTC's proxy abilities.
8+
package main
9+
10+
import (
11+
"encoding/json"
12+
"errors"
13+
"fmt"
14+
"io"
15+
"log"
16+
"net/http"
17+
"time"
18+
19+
"github.com/pion/webrtc/v4"
20+
)
21+
22+
var api *webrtc.API //nolint
23+
24+
//nolint:cyclop
25+
26+
func doSignaling(res http.ResponseWriter, req *http.Request) { //nolint:cyclop
27+
peerConnection, err := api.NewPeerConnection(webrtc.Configuration{
28+
ICEServers: []webrtc.ICEServer{
29+
{
30+
URLs: []string{"turn:127.0.0.1:17342?transport=tcp"},
31+
Username: "turn_username",
32+
Credential: "turn_password",
33+
},
34+
},
35+
})
36+
if err != nil {
37+
panic(err)
38+
}
39+
40+
// Set the handler for ICE connection state
41+
// This will notify you when the peer has connected/disconnected
42+
peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
43+
log.Printf("ICE Connection State has changed: %s\n", connectionState.String())
44+
})
45+
46+
// Send the current time via a DataChannel to the remote peer every 3 seconds
47+
peerConnection.OnDataChannel(func(d *webrtc.DataChannel) {
48+
d.OnOpen(func() {
49+
for range time.Tick(time.Second * 3) {
50+
if err = d.SendText(time.Now().String()); err != nil {
51+
if errors.Is(err, io.ErrClosedPipe) {
52+
return
53+
}
54+
panic(err)
55+
}
56+
}
57+
})
58+
})
59+
60+
var offer webrtc.SessionDescription
61+
if err = json.NewDecoder(req.Body).Decode(&offer); err != nil {
62+
panic(err)
63+
}
64+
65+
if err = peerConnection.SetRemoteDescription(offer); err != nil {
66+
panic(err)
67+
}
68+
69+
// Create channel that is blocked until ICE Gathering is complete
70+
gatherComplete := webrtc.GatheringCompletePromise(peerConnection)
71+
72+
answer, err := peerConnection.CreateAnswer(nil)
73+
if err != nil {
74+
panic(err)
75+
} else if err = peerConnection.SetLocalDescription(answer); err != nil {
76+
panic(err)
77+
}
78+
79+
// Block until ICE Gathering is complete, disabling trickle ICE
80+
// we do this because we only can exchange one signaling message
81+
// in a production application you should exchange ICE Candidates via OnICECandidate
82+
<-gatherComplete
83+
84+
response, err := json.Marshal(*peerConnection.LocalDescription())
85+
if err != nil {
86+
panic(err)
87+
}
88+
89+
res.Header().Set("Content-Type", "application/json")
90+
if _, err := res.Write(response); err != nil {
91+
panic(err)
92+
}
93+
}
94+
95+
//nolint:cyclop
96+
func main() {
97+
// Setup TURN
98+
turnServer := newTURNServer()
99+
defer func() {
100+
if err := turnServer.Close(); err != nil {
101+
log.Printf("close turn server: %v", err)
102+
}
103+
}()
104+
105+
// Setup proxy
106+
proxyURL, proxyListener := newHTTPProxy()
107+
defer func() {
108+
if err := proxyListener.Close(); err != nil {
109+
log.Printf("close proxy listener: %v", err)
110+
}
111+
}()
112+
113+
// Setup proxy dialer
114+
proxyDialer := newProxyDialer(proxyURL)
115+
116+
// Set proxy dialer, works only for TURN + TCP
117+
var settingEngine webrtc.SettingEngine
118+
settingEngine.SetICEProxyDialer(proxyDialer)
119+
120+
api = webrtc.NewAPI(webrtc.WithSettingEngine(settingEngine))
121+
122+
http.Handle("/", http.FileServer(http.Dir(".")))
123+
http.HandleFunc("/doSignaling", doSignaling)
124+
125+
fmt.Println("Open http://localhost:8080 to access this demo")
126+
// nolint: gosec
127+
panic(http.ListenAndServe(":8080", nil))
128+
}

examples/ice-proxy/misc.go

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// SPDX-FileCopyrightText: 2025 The Pion community <https://pion.ly>
2+
// SPDX-License-Identifier: MIT
3+
4+
package main
5+
6+
import (
7+
"bufio"
8+
"fmt"
9+
"io"
10+
"log"
11+
"net"
12+
"net/http"
13+
"net/url"
14+
15+
"github.com/pion/turn/v4"
16+
"golang.org/x/net/proxy"
17+
)
18+
19+
var _ proxy.Dialer = &proxyDialer{}
20+
21+
type proxyDialer struct {
22+
proxyAddr string
23+
}
24+
25+
func newProxyDialer(u *url.URL) proxy.Dialer {
26+
if u.Scheme != "http" {
27+
panic("unsupported proxy scheme")
28+
}
29+
30+
return &proxyDialer{
31+
proxyAddr: u.Host,
32+
}
33+
}
34+
35+
func (d *proxyDialer) Dial(network, addr string) (net.Conn, error) {
36+
if network != "tcp" && network != "tcp4" && network != "tcp6" {
37+
panic("unsupported proxy network type")
38+
}
39+
40+
conn, err := net.Dial(network, d.proxyAddr)
41+
if err != nil {
42+
panic(err)
43+
}
44+
45+
// Create a CONNECT request to the proxy with target address.
46+
req := &http.Request{
47+
Method: http.MethodConnect,
48+
URL: &url.URL{Host: addr},
49+
Header: http.Header{
50+
"Proxy-Connection": []string{"Keep-Alive"},
51+
},
52+
}
53+
54+
err = req.Write(conn)
55+
if err != nil {
56+
panic(err)
57+
}
58+
59+
resp, err := http.ReadResponse(bufio.NewReader(conn), req)
60+
if err != nil {
61+
panic(err)
62+
}
63+
defer func() {
64+
if err := resp.Body.Close(); err != nil {
65+
log.Printf("close response body: %v", err)
66+
}
67+
}()
68+
69+
if resp.StatusCode != http.StatusOK {
70+
panic("unexpected proxy status code: " + resp.Status)
71+
}
72+
73+
return conn, nil
74+
}
75+
76+
func newHTTPProxy() (*url.URL, net.Listener) {
77+
listener, err := net.Listen("tcp", "127.0.0.1:0")
78+
if err != nil {
79+
panic(err)
80+
}
81+
82+
go func() {
83+
for {
84+
conn, err := listener.Accept()
85+
if err != nil {
86+
return
87+
}
88+
go proxyHandleConn(conn)
89+
}
90+
}()
91+
92+
return &url.URL{
93+
Scheme: "http",
94+
Host: fmt.Sprintf("127.0.0.1:%d", listener.Addr().(*net.TCPAddr).Port), // nolint:forcetypeassert
95+
}, listener
96+
}
97+
98+
func proxyHandleConn(clientConn net.Conn) {
99+
// Read the request from the client
100+
req, err := http.ReadRequest(bufio.NewReader(clientConn))
101+
if err != nil {
102+
panic(err)
103+
}
104+
105+
if req.Method != http.MethodConnect {
106+
panic("unexpected request method: " + req.Method)
107+
}
108+
109+
// Establish a connection to the target server
110+
targetConn, err := net.Dial("tcp", req.URL.Host)
111+
if err != nil {
112+
panic(err)
113+
}
114+
115+
// Answer to the client with a 200 OK response
116+
if _, err := clientConn.Write([]byte("HTTP/1.1 200 OK\r\n\r\n")); err != nil {
117+
panic(err)
118+
}
119+
120+
// Copy data between client and target
121+
go io.Copy(clientConn, targetConn) // nolint: errcheck
122+
go io.Copy(targetConn, clientConn) // nolint: errcheck
123+
}
124+
125+
func newTURNServer() *turn.Server {
126+
tcpListener, err := net.Listen("tcp4", "127.0.0.1:17342")
127+
if err != nil {
128+
panic(err)
129+
}
130+
131+
server, err := turn.NewServer(turn.ServerConfig{
132+
AuthHandler: func(username, realm string, addr net.Addr) ([]byte, bool) {
133+
log.Printf("Request to TURN from %q", addr.String())
134+
135+
return turn.GenerateAuthKey("turn_username", realm, "turn_password"), true
136+
},
137+
ListenerConfigs: []turn.ListenerConfig{
138+
{
139+
Listener: tcpListener,
140+
RelayAddressGenerator: &turn.RelayAddressGeneratorStatic{
141+
RelayAddress: net.ParseIP("127.0.0.1"),
142+
Address: "127.0.0.1",
143+
},
144+
},
145+
},
146+
})
147+
if err != nil {
148+
panic(err)
149+
}
150+
151+
return server
152+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ require (
1616
github.com/pion/srtp/v3 v3.0.5
1717
github.com/pion/stun/v3 v3.0.0
1818
github.com/pion/transport/v3 v3.0.7
19+
github.com/pion/turn/v4 v4.0.0
1920
github.com/sclevine/agouti v3.0.0+incompatible
2021
github.com/stretchr/testify v1.10.0
2122
golang.org/x/net v0.35.0
@@ -27,7 +28,6 @@ require (
2728
github.com/onsi/ginkgo v1.16.5 // indirect
2829
github.com/onsi/gomega v1.17.0 // indirect
2930
github.com/pion/mdns/v2 v2.0.7 // indirect
30-
github.com/pion/turn/v4 v4.0.0 // indirect
3131
github.com/pmezard/go-difflib v1.0.0 // indirect
3232
github.com/wlynxg/anet v0.0.5 // indirect
3333
golang.org/x/crypto v0.33.0 // indirect

0 commit comments

Comments
 (0)