mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-08-07 06:15:15 +03:00
195 lines
5.9 KiB
Go
195 lines
5.9 KiB
Go
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)))
|
|
}
|
|
}
|