Skip to content

Commit 24d8c62

Browse files
committed
Add basic vnet test setup
1 parent 5e02561 commit 24d8c62

File tree

6 files changed

+649
-0
lines changed

6 files changed

+649
-0
lines changed

go.mod

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
module github.com/pion/bwe
2+
3+
go 1.25
4+
5+
require (
6+
github.com/pion/bwe-test v0.0.0-20251002002417-3136f8c202a1
7+
github.com/pion/interceptor v0.1.41
8+
github.com/pion/logging v0.2.4
9+
github.com/pion/transport/v3 v3.0.8
10+
github.com/pion/webrtc/v4 v4.1.4
11+
github.com/stretchr/testify v1.11.1
12+
)
13+
14+
require (
15+
github.com/davecgh/go-spew v1.1.1 // indirect
16+
github.com/google/uuid v1.6.0 // indirect
17+
github.com/pion/datachannel v1.5.10 // indirect
18+
github.com/pion/dtls/v3 v3.0.7 // indirect
19+
github.com/pion/ice/v4 v4.0.10 // indirect
20+
github.com/pion/mdns/v2 v2.0.7 // indirect
21+
github.com/pion/randutil v0.1.0 // indirect
22+
github.com/pion/rtcp v1.2.15 // indirect
23+
github.com/pion/rtp v1.8.22 // indirect
24+
github.com/pion/sctp v1.8.39 // indirect
25+
github.com/pion/sdp/v3 v3.0.15 // indirect
26+
github.com/pion/srtp/v3 v3.0.7 // indirect
27+
github.com/pion/stun/v3 v3.0.0 // indirect
28+
github.com/pion/turn/v4 v4.1.1 // indirect
29+
github.com/pmezard/go-difflib v1.0.0 // indirect
30+
github.com/wlynxg/anet v0.0.5 // indirect
31+
golang.org/x/crypto v0.33.0 // indirect
32+
golang.org/x/net v0.35.0 // indirect
33+
golang.org/x/sys v0.30.0 // indirect
34+
gopkg.in/yaml.v3 v3.0.1 // indirect
35+
)
36+
37+
replace github.com/pion/interceptor v0.1.41 => ../interceptor

go.sum

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
4+
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
5+
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
6+
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
7+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
8+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
9+
github.com/pion/bwe-test v0.0.0-20251002002417-3136f8c202a1 h1:2V6zWU9CY7sranrDF9OgSnTVeG57xsIkr7VzcoRuh8w=
10+
github.com/pion/bwe-test v0.0.0-20251002002417-3136f8c202a1/go.mod h1:fVWntMtQq+IWSbwnsiRDQb3msKDMidhfJjS8nBFdQls=
11+
github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o=
12+
github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M=
13+
github.com/pion/dtls/v3 v3.0.7 h1:bItXtTYYhZwkPFk4t1n3Kkf5TDrfj6+4wG+CZR8uI9Q=
14+
github.com/pion/dtls/v3 v3.0.7/go.mod h1:uDlH5VPrgOQIw59irKYkMudSFprY9IEFCqz/eTz16f8=
15+
github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4=
16+
github.com/pion/ice/v4 v4.0.10/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw=
17+
github.com/pion/interceptor v0.1.41 h1:NpvX3HgWIukTf2yTBVjVGFXtpSpWgXjqz7IIpu7NsOw=
18+
github.com/pion/interceptor v0.1.41/go.mod h1:nEt4187unvRXJFyjiw00GKo+kIuXMWQI9K89fsosDLY=
19+
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
20+
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
21+
github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM=
22+
github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA=
23+
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
24+
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
25+
github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo=
26+
github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0=
27+
github.com/pion/rtp v1.8.22 h1:8NCVDDF+uSJmMUkjLJVnIr/HX7gPesyMV1xFt5xozXc=
28+
github.com/pion/rtp v1.8.22/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM=
29+
github.com/pion/sctp v1.8.39 h1:PJma40vRHa3UTO3C4MyeJDQ+KIobVYRZQZ0Nt7SjQnE=
30+
github.com/pion/sctp v1.8.39/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE=
31+
github.com/pion/sdp/v3 v3.0.15 h1:F0I1zds+K/+37ZrzdADmx2Q44OFDOPRLhPnNTaUX9hk=
32+
github.com/pion/sdp/v3 v3.0.15/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E=
33+
github.com/pion/srtp/v3 v3.0.7 h1:QUElw0A/FUg3MP8/KNMZB3i0m8F9XeMnTum86F7S4bs=
34+
github.com/pion/srtp/v3 v3.0.7/go.mod h1:qvnHeqbhT7kDdB+OGB05KA/P067G3mm7XBfLaLiaNF0=
35+
github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw=
36+
github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU=
37+
github.com/pion/transport/v3 v3.0.8 h1:oI3myyYnTKUSTthu/NZZ8eu2I5sHbxbUNNFW62olaYc=
38+
github.com/pion/transport/v3 v3.0.8/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ=
39+
github.com/pion/turn/v4 v4.1.1 h1:9UnY2HB99tpDyz3cVVZguSxcqkJ1DsTSZ+8TGruh4fc=
40+
github.com/pion/turn/v4 v4.1.1/go.mod h1:2123tHk1O++vmjI5VSD0awT50NywDAq5A2NNNU4Jjs8=
41+
github.com/pion/webrtc/v4 v4.1.4 h1:/gK1ACGHXQmtyVVbJFQDxNoODg4eSRiFLB7t9r9pg8M=
42+
github.com/pion/webrtc/v4 v4.1.4/go.mod h1:Oab9npu1iZtQRMic3K3toYq5zFPvToe/QBw7dMI2ok4=
43+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
44+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
45+
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
46+
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
47+
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
48+
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
49+
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
50+
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
51+
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
52+
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
53+
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
54+
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
55+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
56+
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
57+
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
58+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
59+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

simulation/log_format_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package simulation
2+
3+
import (
4+
"fmt"
5+
"log/slog"
6+
"time"
7+
8+
"github.com/pion/interceptor"
9+
"github.com/pion/rtcp"
10+
"github.com/pion/rtp"
11+
)
12+
13+
type packetLogger struct {
14+
vantagePoint string
15+
direction string
16+
}
17+
18+
func (l *packetLogger) LogRTPPacket(header *rtp.Header, payload []byte, attributes interceptor.Attributes) {
19+
ts := time.Now()
20+
slog.Info("rtp", "vantage-point", l.vantagePoint, "direction", l.direction, "ts", ts, "pt", header.PayloadType, "ssrc", header.SSRC, "sequence-number", header.SequenceNumber, "rtp-timestamp", header.Timestamp, "marker", header.Marker, "payload-size", len(payload))
21+
}
22+
23+
func (l *packetLogger) LogRTCPPackets(pkts []rtcp.Packet, attributes interceptor.Attributes) {
24+
for _, pkt := range pkts {
25+
slog.Info("rtcp", "vantage-point", l.vantagePoint, "direction", l.direction, "type", fmt.Sprintf("%T", pkt))
26+
}
27+
}

simulation/peer_test.go

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
package simulation
2+
3+
import (
4+
"github.com/pion/interceptor"
5+
"github.com/pion/interceptor/pkg/packetdump"
6+
"github.com/pion/interceptor/pkg/rfc8888"
7+
"github.com/pion/interceptor/pkg/rtpfb"
8+
"github.com/pion/interceptor/pkg/twcc"
9+
"github.com/pion/logging"
10+
"github.com/pion/transport/v3/vnet"
11+
"github.com/pion/webrtc/v4"
12+
)
13+
14+
type option func(*peer) error
15+
16+
func setVNet(vnet *vnet.Net, publicIPs []string) option {
17+
return func(p *peer) error {
18+
p.settingEngine.SetNet(vnet)
19+
p.settingEngine.SetNAT1To1IPs(publicIPs, webrtc.ICECandidateTypeHost)
20+
return nil
21+
}
22+
}
23+
24+
func onRemoteTrack(handler func(*webrtc.TrackRemote)) option {
25+
return func(p *peer) error {
26+
p.onRemoteTrack = handler
27+
return nil
28+
}
29+
}
30+
31+
func onConnected(handler func()) option {
32+
return func(p *peer) error {
33+
p.onConnected = handler
34+
return nil
35+
}
36+
}
37+
38+
func registerDefaultCodecs() option {
39+
return func(p *peer) error {
40+
return p.mediaEngine.RegisterDefaultCodecs()
41+
}
42+
}
43+
44+
func registerPacketLogger(vantagePoint string) option {
45+
return func(p *peer) error {
46+
ipl := &packetLogger{vantagePoint: vantagePoint, direction: "in"}
47+
rd, err := packetdump.NewReceiverInterceptor(packetdump.PacketLog(ipl))
48+
if err != nil {
49+
return err
50+
}
51+
opl := &packetLogger{vantagePoint: vantagePoint, direction: "out"}
52+
sd, err := packetdump.NewSenderInterceptor(packetdump.PacketLog(opl))
53+
if err != nil {
54+
return err
55+
}
56+
p.interceptorRegistry.Add(rd)
57+
p.interceptorRegistry.Add(sd)
58+
return nil
59+
}
60+
}
61+
62+
func registerRTPFB() option {
63+
return func(p *peer) error {
64+
rtpfb, err := rtpfb.NewInterceptor()
65+
if err != nil {
66+
return err
67+
}
68+
p.interceptorRegistry.Add(rtpfb)
69+
return nil
70+
}
71+
}
72+
73+
func registerTWCC() option {
74+
return func(p *peer) error {
75+
twcc, err := twcc.NewSenderInterceptor()
76+
if err != nil {
77+
return err
78+
}
79+
p.interceptorRegistry.Add(twcc)
80+
return nil
81+
}
82+
}
83+
84+
func registerTWCCHeaderExtension() option {
85+
return func(p *peer) error {
86+
twccHdrExt, err := twcc.NewHeaderExtensionInterceptor()
87+
if err != nil {
88+
return err
89+
}
90+
p.interceptorRegistry.Add(twccHdrExt)
91+
return nil
92+
}
93+
}
94+
95+
func registerCCFB() option {
96+
return func(p *peer) error {
97+
ccfb, err := rfc8888.NewSenderInterceptor()
98+
if err != nil {
99+
return err
100+
}
101+
p.interceptorRegistry.Add(ccfb)
102+
return nil
103+
}
104+
}
105+
106+
type peer struct {
107+
logger logging.LeveledLogger
108+
pc *webrtc.PeerConnection
109+
110+
settingEngine *webrtc.SettingEngine
111+
mediaEngine *webrtc.MediaEngine
112+
interceptorRegistry *interceptor.Registry
113+
114+
onRemoteTrack func(*webrtc.TrackRemote)
115+
onConnected func()
116+
}
117+
118+
func newPeer(opts ...option) (*peer, error) {
119+
p := &peer{
120+
logger: logging.NewDefaultLoggerFactory().NewLogger("bwe_test_peer"),
121+
pc: nil,
122+
settingEngine: &webrtc.SettingEngine{},
123+
mediaEngine: &webrtc.MediaEngine{},
124+
interceptorRegistry: &interceptor.Registry{},
125+
onRemoteTrack: nil,
126+
onConnected: nil,
127+
}
128+
for _, opt := range opts {
129+
if err := opt(p); err != nil {
130+
return nil, err
131+
}
132+
}
133+
pc, err := webrtc.NewAPI(
134+
webrtc.WithMediaEngine(p.mediaEngine),
135+
webrtc.WithSettingEngine(*p.settingEngine),
136+
webrtc.WithInterceptorRegistry(p.interceptorRegistry),
137+
).NewPeerConnection(webrtc.Configuration{
138+
ICEServers: []webrtc.ICEServer{
139+
{
140+
URLs: []string{"stun:stun.l.google.com:19302"},
141+
},
142+
},
143+
})
144+
if err != nil {
145+
return nil, err
146+
}
147+
148+
pc.OnNegotiationNeeded(p.onNegotiationNeeded)
149+
pc.OnSignalingStateChange(p.onSignalingStateChange)
150+
pc.OnICECandidate(p.onICECandidate)
151+
pc.OnICEGatheringStateChange(p.onICEGatheringStateChange)
152+
pc.OnICEConnectionStateChange(p.onICEConnectionStateChange)
153+
pc.OnConnectionStateChange(p.onConnectionStateChange)
154+
pc.OnDataChannel(p.onDataChannel)
155+
pc.OnTrack(p.onTrack)
156+
157+
p.pc = pc
158+
return p, nil
159+
}
160+
161+
// Callbacks
162+
163+
func (p *peer) onNegotiationNeeded() {
164+
p.logger.Infof("negotiation needed")
165+
}
166+
167+
func (p *peer) onSignalingStateChange(s webrtc.SignalingState) {
168+
p.logger.Infof("new signaling state: %v", s)
169+
}
170+
171+
func (p *peer) onICECandidate(c *webrtc.ICECandidate) {
172+
p.logger.Infof("got new ICE candidate: %v", c)
173+
}
174+
175+
func (p *peer) onICEGatheringStateChange(s webrtc.ICEGatheringState) {
176+
p.logger.Infof("new ICE gathering state: %v", s)
177+
}
178+
179+
func (p *peer) onICEConnectionStateChange(s webrtc.ICEConnectionState) {
180+
p.logger.Infof("new ICE connection state: %v", s)
181+
}
182+
183+
func (p *peer) onConnectionStateChange(s webrtc.PeerConnectionState) {
184+
p.logger.Infof("new connection state: %v", s)
185+
if s == webrtc.PeerConnectionStateConnected && p.onConnected != nil {
186+
p.onConnected()
187+
}
188+
}
189+
190+
func (p *peer) onDataChannel(dc *webrtc.DataChannel) {
191+
p.logger.Infof("got new data channel: id=%v, label=%v", dc.ID(), dc.Label())
192+
}
193+
194+
func (p *peer) onTrack(track *webrtc.TrackRemote, _ *webrtc.RTPReceiver) {
195+
if p.onRemoteTrack != nil {
196+
p.onRemoteTrack(track)
197+
}
198+
}
199+
200+
// Signaling helpers
201+
202+
func (p *peer) createOffer() (*webrtc.SessionDescription, error) {
203+
offer, err := p.pc.CreateOffer(nil)
204+
if err != nil {
205+
return nil, err
206+
}
207+
gc := webrtc.GatheringCompletePromise(p.pc)
208+
if err = p.pc.SetLocalDescription(offer); err != nil {
209+
return nil, err
210+
}
211+
<-gc
212+
return p.pc.LocalDescription(), nil
213+
}
214+
215+
func (p *peer) createAnswer() (*webrtc.SessionDescription, error) {
216+
answer, err := p.pc.CreateAnswer(nil)
217+
if err != nil {
218+
return nil, err
219+
}
220+
gc := webrtc.GatheringCompletePromise(p.pc)
221+
if err = p.pc.SetLocalDescription(answer); err != nil {
222+
return nil, err
223+
}
224+
<-gc
225+
return p.pc.LocalDescription(), nil
226+
}
227+
228+
func (p *peer) setRemoteDescription(description *webrtc.SessionDescription) error {
229+
return p.pc.SetRemoteDescription(*description)
230+
}
231+
232+
// Track management
233+
234+
func (p *peer) addLocalTrack() (*webrtc.TrackLocalStaticSample, error) {
235+
track, err := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{
236+
MimeType: webrtc.MimeTypeH264,
237+
ClockRate: 0,
238+
Channels: 0,
239+
SDPFmtpLine: "",
240+
RTCPFeedback: []webrtc.RTCPFeedback{},
241+
}, "video", "pion")
242+
if err != nil {
243+
return nil, err
244+
}
245+
s, err := p.pc.AddTrack(track)
246+
if err != nil {
247+
return nil, err
248+
}
249+
go p.readRTCP(s)
250+
return track, err
251+
}
252+
253+
func (p *peer) addRemoteTrack() error {
254+
_, err := p.pc.AddTransceiverFromKind(webrtc.RTPCodecTypeVideo)
255+
return err
256+
}
257+
258+
func (p *peer) readRTCP(r *webrtc.RTPSender) {
259+
for {
260+
_, _, err := r.ReadRTCP()
261+
if err != nil {
262+
return
263+
}
264+
}
265+
}

0 commit comments

Comments
 (0)