mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-08-10 04:17:14 +03:00
Add call protocol, Rmux. Update AmneziaWG. Fixes and improvements
This commit is contained in:
267
transport/call/telemost/relay.go
Normal file
267
transport/call/telemost/relay.go
Normal file
@@ -0,0 +1,267 @@
|
||||
package telemost
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/pion/rtp"
|
||||
"github.com/pion/rtp/codecs"
|
||||
"github.com/pion/webrtc/v4"
|
||||
"github.com/sagernet/sing-box/transport/call/common"
|
||||
"github.com/sagernet/sing-box/transport/call/tunnel"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
)
|
||||
|
||||
type SFURelay struct {
|
||||
pubPC *webrtc.PeerConnection
|
||||
subPC *webrtc.PeerConnection
|
||||
pubRemoteSet bool
|
||||
subRemoteSet bool
|
||||
pubPending []webrtc.ICECandidateInit
|
||||
subPending []webrtc.ICECandidateInit
|
||||
mu sync.Mutex
|
||||
logger logger.ContextLogger
|
||||
|
||||
sampleTrack *webrtc.TrackLocalStaticSample
|
||||
tun *tunnel.VP8DataTunnel
|
||||
obf *tunnel.TunnelObfuscator
|
||||
OnConnected func(*tunnel.VP8DataTunnel)
|
||||
OnPubReady func()
|
||||
OnPeerRestart func()
|
||||
OnPubICE func(*webrtc.ICECandidate)
|
||||
OnSubICE func(*webrtc.ICECandidate)
|
||||
|
||||
readBufSize int
|
||||
}
|
||||
|
||||
func (r *SFURelay) SetObfuscator(o *tunnel.TunnelObfuscator) { r.obf = o }
|
||||
|
||||
func NewSFURelay(logger logger.ContextLogger) *SFURelay {
|
||||
return &SFURelay{logger: logger}
|
||||
}
|
||||
|
||||
func (r *SFURelay) Init(iceServers []webrtc.ICEServer) error {
|
||||
config := webrtc.Configuration{ICEServers: iceServers}
|
||||
pubPC, err := NewPeerConnection(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.pubPC = pubPC
|
||||
sampleTrack, _ := webrtc.NewTrackLocalStaticSample(
|
||||
webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8},
|
||||
"video", "tunnel-video",
|
||||
)
|
||||
r.sampleTrack = sampleTrack
|
||||
audioTrack, _ := webrtc.NewTrackLocalStaticRTP(
|
||||
webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeOpus},
|
||||
"audio", "tunnel-audio",
|
||||
)
|
||||
pubPC.AddTransceiverFromTrack(audioTrack, webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendonly})
|
||||
pubPC.AddTransceiverFromTrack(r.sampleTrack, webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendonly})
|
||||
pubPC.OnICECandidate(func(cand *webrtc.ICECandidate) {
|
||||
if cand == nil || r.OnPubICE == nil {
|
||||
return
|
||||
}
|
||||
r.OnPubICE(cand)
|
||||
})
|
||||
pubPC.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
|
||||
r.logger.Debug(fmt.Sprintf("[pub] connection state: %s", state.String()))
|
||||
if state == webrtc.PeerConnectionStateConnected {
|
||||
if r.tun == nil {
|
||||
r.logger.Debug("[relay] starting VP8 publish tunnel on pub PC connected")
|
||||
r.tun = tunnel.NewVP8DataTunnel(r.sampleTrack, r.obf, r.logger)
|
||||
r.tun.Start(0, 0)
|
||||
if r.OnConnected != nil {
|
||||
r.OnConnected(r.tun)
|
||||
}
|
||||
}
|
||||
if r.OnPubReady != nil {
|
||||
r.OnPubReady()
|
||||
}
|
||||
}
|
||||
})
|
||||
subPC, err := NewPeerConnection(config)
|
||||
if err != nil {
|
||||
pubPC.Close()
|
||||
return err
|
||||
}
|
||||
r.subPC = subPC
|
||||
subPC.OnICECandidate(func(cand *webrtc.ICECandidate) {
|
||||
if cand == nil || r.OnSubICE == nil {
|
||||
return
|
||||
}
|
||||
r.OnSubICE(cand)
|
||||
})
|
||||
subPC.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
|
||||
r.logger.Debug(fmt.Sprintf("[sub] connection state: %s", state.String()))
|
||||
})
|
||||
subPC.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) {
|
||||
r.logger.Debug(fmt.Sprintf("[sub] remote track: %s", track.Codec().MimeType))
|
||||
go r.readTrack(track)
|
||||
})
|
||||
r.logger.Debug(fmt.Sprintf("[relay] pub+sub PCs created (%d ICE servers)", len(iceServers)))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *SFURelay) CreatePubOffer() (webrtc.SessionDescription, error) {
|
||||
offer, err := r.pubPC.CreateOffer(nil)
|
||||
if err != nil {
|
||||
return offer, err
|
||||
}
|
||||
if err := r.pubPC.SetLocalDescription(offer); err != nil {
|
||||
return offer, err
|
||||
}
|
||||
offer.SDP = MungeSDPAddVideoContent(offer.SDP)
|
||||
return offer, nil
|
||||
}
|
||||
|
||||
func (r *SFURelay) SetPubAnswer(sdp string) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
err := r.pubPC.SetRemoteDescription(webrtc.SessionDescription{
|
||||
Type: webrtc.SDPTypeAnswer, SDP: sdp,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.pubRemoteSet = true
|
||||
for _, cand := range r.pubPending {
|
||||
r.pubPC.AddICECandidate(cand)
|
||||
}
|
||||
r.pubPending = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *SFURelay) SetSubOffer(sdp string) (webrtc.SessionDescription, error) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
err := r.subPC.SetRemoteDescription(webrtc.SessionDescription{
|
||||
Type: webrtc.SDPTypeOffer, SDP: sdp,
|
||||
})
|
||||
if err != nil {
|
||||
return webrtc.SessionDescription{}, err
|
||||
}
|
||||
r.subRemoteSet = true
|
||||
for _, cand := range r.subPending {
|
||||
r.subPC.AddICECandidate(cand)
|
||||
}
|
||||
r.subPending = nil
|
||||
answer, err := r.subPC.CreateAnswer(nil)
|
||||
if err != nil {
|
||||
return answer, err
|
||||
}
|
||||
r.subPC.SetLocalDescription(answer)
|
||||
return answer, nil
|
||||
}
|
||||
|
||||
func (r *SFURelay) AddPubICECandidate(cand webrtc.ICECandidateInit) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if !r.pubRemoteSet {
|
||||
r.pubPending = append(r.pubPending, cand)
|
||||
return
|
||||
}
|
||||
r.pubPC.AddICECandidate(cand)
|
||||
}
|
||||
|
||||
func (r *SFURelay) AddSubICECandidate(cand webrtc.ICECandidateInit) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if !r.subRemoteSet {
|
||||
r.subPending = append(r.subPending, cand)
|
||||
return
|
||||
}
|
||||
r.subPC.AddICECandidate(cand)
|
||||
}
|
||||
|
||||
func (r *SFURelay) Close() {
|
||||
if r.tun != nil {
|
||||
r.tun.Stop()
|
||||
r.tun = nil
|
||||
}
|
||||
if r.pubPC != nil {
|
||||
r.pubPC.Close()
|
||||
r.pubPC = nil
|
||||
}
|
||||
if r.subPC != nil {
|
||||
r.subPC.Close()
|
||||
r.subPC = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (r *SFURelay) readTrack(track *webrtc.TrackRemote) {
|
||||
if track.Codec().MimeType != webrtc.MimeTypeVP8 {
|
||||
buf := make([]byte, common.UDPBufSize)
|
||||
for {
|
||||
if _, _, err := track.Read(buf); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
var vp8Pkt codecs.VP8Packet
|
||||
var pkt rtp.Packet
|
||||
var frameBuf []byte
|
||||
var lastSeq uint16
|
||||
var haveLastSeq bool
|
||||
frameValid := false
|
||||
var recvCount int
|
||||
bufSz := r.readBufSize
|
||||
if bufSz <= 0 {
|
||||
bufSz = common.RTPBufSize
|
||||
}
|
||||
buf := make([]byte, bufSz)
|
||||
for {
|
||||
n, _, err := track.Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if pkt.Unmarshal(buf[:n]) != nil {
|
||||
continue
|
||||
}
|
||||
if haveLastSeq && pkt.SequenceNumber != lastSeq+1 {
|
||||
frameValid = false
|
||||
frameBuf = frameBuf[:0]
|
||||
}
|
||||
lastSeq = pkt.SequenceNumber
|
||||
haveLastSeq = true
|
||||
vp8Payload, err := vp8Pkt.Unmarshal(pkt.Payload)
|
||||
if err != nil {
|
||||
frameValid = false
|
||||
frameBuf = frameBuf[:0]
|
||||
continue
|
||||
}
|
||||
if vp8Pkt.S == 1 {
|
||||
frameBuf = frameBuf[:0]
|
||||
frameValid = true
|
||||
}
|
||||
if !frameValid {
|
||||
continue
|
||||
}
|
||||
frameBuf = append(frameBuf, vp8Payload...)
|
||||
if !pkt.Marker {
|
||||
continue
|
||||
}
|
||||
recvCount++
|
||||
if recvCount <= 3 || recvCount%200 == 0 {
|
||||
r.logger.Debug(fmt.Sprintf("[video] recv vp8 frame #%d %d bytes", recvCount, len(frameBuf)))
|
||||
}
|
||||
res := r.obf.Decode(frameBuf)
|
||||
frameBuf = frameBuf[:0]
|
||||
frameValid = false
|
||||
if !res.HasFrame || res.SelfEcho {
|
||||
continue
|
||||
}
|
||||
if res.PeerRestart {
|
||||
r.logger.Info(fmt.Sprintf("[video] peer restart detected, new epoch=0x%08x", res.PeerEpoch))
|
||||
if r.OnPeerRestart != nil {
|
||||
r.OnPeerRestart()
|
||||
}
|
||||
}
|
||||
if res.Keepalive || len(res.Payload) == 0 {
|
||||
continue
|
||||
}
|
||||
if r.tun != nil && r.tun.OnData != nil {
|
||||
r.tun.OnData(res.Payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user