mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-08-07 14:25:17 +03:00
Add call protocol, Rmux. Update AmneziaWG. Fixes and improvements
This commit is contained in:
194
transport/call/vk/p2p.go
Normal file
194
transport/call/vk/p2p.go
Normal file
@@ -0,0 +1,194 @@
|
||||
package vk
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/pion/webrtc/v4"
|
||||
)
|
||||
|
||||
type P2PHandler struct {
|
||||
bridge *Bridge
|
||||
remotePeerId *int64
|
||||
pendingOffer *webrtc.SessionDescription
|
||||
pendingCandidates []webrtc.ICECandidateInit
|
||||
connected bool
|
||||
}
|
||||
|
||||
func NewP2PHandler(bridge *Bridge) *P2PHandler {
|
||||
return &P2PHandler{bridge: bridge}
|
||||
}
|
||||
|
||||
func (p *P2PHandler) Init() {
|
||||
relay := p.bridge.relay
|
||||
if err := relay.Init(p.bridge.iceServers); err != nil {
|
||||
p.bridge.logger.Error(fmt.Sprintf("[p2p] Init relay failed: %v", err))
|
||||
return
|
||||
}
|
||||
p.setupCallbacks()
|
||||
offer, err := relay.CreateOffer()
|
||||
if err != nil {
|
||||
p.bridge.logger.Error(fmt.Sprintf("[p2p] Create offer failed: %v", err))
|
||||
return
|
||||
}
|
||||
p.pendingOffer = &offer
|
||||
p.bridge.logger.Debug(fmt.Sprintf("[p2p] Offer ready, SDP length: %d", len(offer.SDP)))
|
||||
}
|
||||
|
||||
func (p *P2PHandler) Reset() {
|
||||
p.bridge.logger.Debug("[p2p] Resetting Pion PC...")
|
||||
p.connected = false
|
||||
p.bridge.relay.Close()
|
||||
p.bridge.relay = p.bridge.newRelay()
|
||||
relay := p.bridge.relay
|
||||
if err := relay.Init(p.bridge.iceServers); err != nil {
|
||||
p.bridge.logger.Warn(fmt.Sprintf("[p2p] Reset init failed: %v", err))
|
||||
return
|
||||
}
|
||||
p.setupCallbacks()
|
||||
offer, err := relay.CreateOffer()
|
||||
if err != nil {
|
||||
p.bridge.logger.Warn(fmt.Sprintf("[p2p] Reset create-offer failed: %v", err))
|
||||
return
|
||||
}
|
||||
p.pendingOffer = &offer
|
||||
p.pendingCandidates = nil
|
||||
p.bridge.logger.Debug(fmt.Sprintf("[p2p] New offer ready after reset, SDP length: %d", len(offer.SDP)))
|
||||
}
|
||||
|
||||
func (p *P2PHandler) OnRegisteredPeer(participantId int64) {
|
||||
oldPeer := p.remotePeerId
|
||||
p.remotePeerId = &participantId
|
||||
p.bridge.logger.Debug(fmt.Sprintf("[p2p] Peer registered: %d (connected=%v)", participantId, p.connected))
|
||||
if oldPeer != nil && (p.pendingOffer == nil) {
|
||||
if *oldPeer != participantId {
|
||||
p.bridge.logger.Debug(fmt.Sprintf("[p2p] New peer %d replacing old peer %d, resetting", participantId, *oldPeer))
|
||||
} else {
|
||||
p.bridge.logger.Debug(fmt.Sprintf("[p2p] Same peer %d re-registered, no pending offer, resetting", participantId))
|
||||
}
|
||||
p.Reset()
|
||||
}
|
||||
p.sendOfferToPeer(participantId)
|
||||
}
|
||||
|
||||
func (p *P2PHandler) OnTransmittedData(data map[string]interface{}) {
|
||||
if cand, ok := data["candidate"]; ok {
|
||||
p.bridge.logger.Debug("[p2p] Remote ICE candidate")
|
||||
candJSON, _ := json.Marshal(cand)
|
||||
var candInit webrtc.ICECandidateInit
|
||||
json.Unmarshal(candJSON, &candInit)
|
||||
p.bridge.relay.AddICECandidate(candInit)
|
||||
}
|
||||
if sdp, ok := data["sdp"].(map[string]interface{}); ok {
|
||||
sdpType, _ := sdp["type"].(string)
|
||||
sdpStr, _ := sdp["sdp"].(string)
|
||||
p.bridge.logger.Debug(fmt.Sprintf("[p2p] Remote SDP: %s", sdpType))
|
||||
if sdpType == "answer" {
|
||||
p.bridge.relay.SetRemoteDescription(webrtc.SDPTypeAnswer, sdpStr)
|
||||
} else if sdpType == "offer" {
|
||||
p.bridge.relay.SetRemoteDescription(webrtc.SDPTypeOffer, sdpStr)
|
||||
answer, err := p.bridge.relay.CreateAnswer()
|
||||
if err == nil && p.remotePeerId != nil {
|
||||
p.bridge.vkSend("transmit-data", map[string]interface{}{
|
||||
"participantId": *p.remotePeerId,
|
||||
"data": map[string]interface{}{"sdp": map[string]interface{}{
|
||||
"type": answer.Type.String(), "sdp": answer.SDP,
|
||||
}},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *P2PHandler) OnPionICECandidate(data json.RawMessage) {
|
||||
if p.remotePeerId != nil {
|
||||
var cand interface{}
|
||||
json.Unmarshal(data, &cand)
|
||||
p.bridge.vkSend("transmit-data", map[string]interface{}{
|
||||
"participantId": *p.remotePeerId,
|
||||
"data": map[string]interface{}{"candidate": cand},
|
||||
})
|
||||
} else {
|
||||
var candInit webrtc.ICECandidateInit
|
||||
json.Unmarshal(data, &candInit)
|
||||
p.pendingCandidates = append(p.pendingCandidates, candInit)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *P2PHandler) OnConnectionState(state string) {
|
||||
switch state {
|
||||
case "connected":
|
||||
p.connected = true
|
||||
p.bridge.logger.Info("TUNNEL CONNECTED")
|
||||
case "disconnected":
|
||||
p.connected = false
|
||||
p.bridge.logger.Debug("[p2p] Connection disconnected, kicking peer")
|
||||
p.kickRemotePeer()
|
||||
case "failed":
|
||||
p.connected = false
|
||||
p.bridge.logger.Debug("[p2p] Connection failed, removing stale peer")
|
||||
p.kickRemotePeer()
|
||||
case "closed":
|
||||
p.connected = false
|
||||
p.bridge.logger.Debug("[p2p] Connection closed, kicking peer")
|
||||
p.kickRemotePeer()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *P2PHandler) setupCallbacks() {
|
||||
p.bridge.relay.OnICECandidate(func(cand *webrtc.ICECandidate) {
|
||||
if cand == nil {
|
||||
return
|
||||
}
|
||||
p.bridge.logger.Debug(fmt.Sprintf("[p2p] ICE candidate: type=%s proto=%s", cand.Typ.String(), cand.Protocol.String()))
|
||||
candJSON := cand.ToJSON()
|
||||
raw, _ := json.Marshal(candJSON)
|
||||
p.OnPionICECandidate(raw)
|
||||
})
|
||||
p.bridge.relay.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
|
||||
p.OnConnectionState(state.String())
|
||||
})
|
||||
}
|
||||
|
||||
func (p *P2PHandler) kickRemotePeer() {
|
||||
if p.remotePeerId != nil {
|
||||
p.bridge.vkSend("remove-participant", map[string]interface{}{
|
||||
"participantId": *p.remotePeerId,
|
||||
"ban": false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (p *P2PHandler) sendOfferToPeer(participantId int64) {
|
||||
offer := p.pendingOffer
|
||||
candidates := p.pendingCandidates
|
||||
p.pendingOffer = nil
|
||||
p.pendingCandidates = nil
|
||||
if offer != nil {
|
||||
p.bridge.logger.Debug(fmt.Sprintf("[p2p] Sending offer to peer %d", participantId))
|
||||
sdpStr, _ := json.Marshal(offer.SDP)
|
||||
p.bridge.mu.Lock()
|
||||
p.bridge.vkSeq++
|
||||
seq := p.bridge.vkSeq
|
||||
raw := fmt.Sprintf(`{"command":"transmit-data","sequence":%d,"participantId":%d,"data":{"sdp":{"type":%q,"sdp":%s}}}`,
|
||||
seq, participantId, offer.Type.String(), sdpStr)
|
||||
if p.bridge.vkWs != nil {
|
||||
p.bridge.vkWs.WriteMessage(websocket.TextMessage, []byte(raw))
|
||||
}
|
||||
p.bridge.mu.Unlock()
|
||||
p.bridge.logger.Debug("[vk-ws] -> transmit-data (offer)")
|
||||
}
|
||||
for _, cand := range candidates {
|
||||
candJSON, _ := json.Marshal(cand)
|
||||
var c interface{}
|
||||
json.Unmarshal(candJSON, &c)
|
||||
p.bridge.vkSend("transmit-data", map[string]interface{}{
|
||||
"participantId": participantId,
|
||||
"data": map[string]interface{}{"candidate": c},
|
||||
})
|
||||
}
|
||||
if len(candidates) > 0 {
|
||||
p.bridge.logger.Debug(fmt.Sprintf("[p2p] Flushed %d ICE candidates", len(candidates)))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user