webrtc
                                
                                 webrtc copied to clipboard
                                
                                    webrtc copied to clipboard
                            
                            
                            
                        webrtc client is not sending stun message to keepalive then ice got disconnect
Your environment.
- Version: github.com/pion/webrtc/v3 v3.1.11
- Browser: NOT RELATED
- Other Information - ion-sfu c8cea05fa60612ae819594afc9655573d8958a5d
What did you do?
- follow here broadcasting-ion-sfu
package main import ( "bytes" "encoding/json" "flag" "fmt" "io" "log" "net/url" "github.com/google/uuid" "github.com/gorilla/websocket" "github.com/pion/mediadevices" "github.com/pion/mediadevices/pkg/codec/vpx" "github.com/pion/mediadevices/pkg/frame" "github.com/pion/mediadevices/pkg/prop" "github.com/pion/webrtc/v3" "github.com/sourcegraph/jsonrpc2" // Note: If you don't have a camera or microphone or your adapters are not supported, // you can always swap your adapters with our dummy adapters below. // _ "github.com/pion/mediadevices/pkg/driver/videotest" // _ "github.com/pion/mediadevices/pkg/driver/audiotest" _ "github.com/pion/mediadevices/pkg/driver/camera" // This is required to register camera adapter _ "github.com/pion/mediadevices/pkg/driver/microphone" // This is required to register microphone adapter ) type Candidate struct { Target int `json:"target"` Candidate *webrtc.ICECandidate `json:candidate` } type ResponseCandidate struct { Target int `json:"target"` Candidate *webrtc.ICECandidateInit `json:candidate` } // SendOffer object to send to the sfu over Websockets type SendOffer struct { SID string `json:sid` Offer *webrtc.SessionDescription `json:offer` } // SendAnswer object to send to the sfu over Websockets type SendAnswer struct { SID string `json:sid` Answer *webrtc.SessionDescription `json:answer` } // TrickleResponse received from the sfu server type TrickleResponse struct { Params ResponseCandidate `json:params` Method string `json:method` } // Response received from the sfu over Websockets type Response struct { Params *webrtc.SessionDescription `json:params` Result *webrtc.SessionDescription `json:result` Method string `json:method` Id uint64 `json:id` } var peerConnection *webrtc.PeerConnection var connectionID uint64 var remoteDescription *webrtc.SessionDescription var addr string func main() { flag.StringVar(&addr, "a", "${HOST}:7000", "address to use") flag.Parse() u := url.URL{Scheme: "ws", Host: addr, Path: "/ws"} log.Printf("connecting to %s", u.String()) c, _, err := websocket.DefaultDialer.Dial(u.String(), nil) if err != nil { log.Fatal("dial:", err) } defer c.Close() config := webrtc.Configuration{ ICEServers: []webrtc.ICEServer{ { URLs: []string{"stun:stun.l.google.com:19302"}, }, /*{ URLs: []string{"turn:TURN_IP:3478"}, Username: "username", Credential: "password", },*/ }, SDPSemantics: webrtc.SDPSemanticsUnifiedPlanWithFallback, } // Create a new RTCPeerConnection mediaEngine := webrtc.MediaEngine{} vpxParams, err := vpx.NewVP8Params() if err != nil { panic(err) } vpxParams.BitRate = 500_000 // 500kbps codecSelector := mediadevices.NewCodecSelector( mediadevices.WithVideoEncoders(&vpxParams), ) codecSelector.Populate(&mediaEngine) api := webrtc.NewAPI(webrtc.WithMediaEngine(&mediaEngine)) peerConnection, err = api.NewPeerConnection(config) if err != nil { panic(err) } // Read incoming Websocket messages done := make(chan struct{}) go readMessage(c, done) fmt.Println(mediadevices.EnumerateDevices()) s, err := mediadevices.GetUserMedia(mediadevices.MediaStreamConstraints{ Video: func(c *mediadevices.MediaTrackConstraints) { c.FrameFormat = prop.FrameFormat(frame.FormatYUY2) c.Width = prop.Int(640) c.Height = prop.Int(480) }, Codec: codecSelector, }) if err != nil { panic(err) } for _, track := range s.GetTracks() { track.OnEnded(func(err error) { fmt.Printf("Track (ID: %s) ended with error: %v\n", track.ID(), err) }) _, err = peerConnection.AddTransceiverFromTrack(track, webrtc.RtpTransceiverInit{ Direction: webrtc.RTPTransceiverDirectionSendonly, }, ) if err != nil { panic(err) } } // Creating WebRTC offer offer, err := peerConnection.CreateOffer(nil) // Set the remote SessionDescription err = peerConnection.SetLocalDescription(offer) if err != nil { panic(err) } // Handling OnICECandidate event peerConnection.OnICECandidate(func(candidate *webrtc.ICECandidate) { if candidate != nil { candidateJSON, err := json.Marshal(&Candidate{ Candidate: candidate, Target: 0, }) params := (*json.RawMessage)(&candidateJSON) if err != nil { log.Fatal(err) } message := &jsonrpc2.Request{ Method: "trickle", Params: params, } reqBodyBytes := new(bytes.Buffer) json.NewEncoder(reqBodyBytes).Encode(message) messageBytes := reqBodyBytes.Bytes() c.WriteMessage(websocket.TextMessage, messageBytes) } }) peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) { fmt.Printf("Connection State has changed to %s \n", connectionState.String()) }) offerJSON, err := json.Marshal(&SendOffer{ Offer: peerConnection.LocalDescription(), SID: "test room", }) params := (*json.RawMessage)(&offerJSON) connectionUUID := uuid.New() connectionID = uint64(connectionUUID.ID()) offerMessage := &jsonrpc2.Request{ Method: "join", Params: params, ID: jsonrpc2.ID{ IsString: false, Str: "", Num: connectionID, }, } reqBodyBytes := new(bytes.Buffer) json.NewEncoder(reqBodyBytes).Encode(offerMessage) messageBytes := reqBodyBytes.Bytes() c.WriteMessage(websocket.TextMessage, messageBytes) <-done } func readMessage(connection *websocket.Conn, done chan struct{}) { defer close(done) for { _, message, err := connection.ReadMessage() if err != nil || err == io.EOF { log.Fatal("Error reading: ", err) break } fmt.Printf("recv: %s", message) var response Response json.Unmarshal(message, &response) if response.Id == connectionID { result := *response.Result remoteDescription = response.Result if err := peerConnection.SetRemoteDescription(result); err != nil { log.Fatal(err) } } else if response.Id != 0 && response.Method == "offer" { peerConnection.SetRemoteDescription(*response.Params) answer, err := peerConnection.CreateAnswer(nil) if err != nil { log.Fatal(err) } peerConnection.SetLocalDescription(answer) connectionUUID := uuid.New() connectionID = uint64(connectionUUID.ID()) offerJSON, err := json.Marshal(&SendAnswer{ Answer: peerConnection.LocalDescription(), SID: "test room", }) params := (*json.RawMessage)(&offerJSON) answerMessage := &jsonrpc2.Request{ Method: "answer", Params: params, ID: jsonrpc2.ID{ IsString: false, Str: "", Num: connectionID, }, } reqBodyBytes := new(bytes.Buffer) json.NewEncoder(reqBodyBytes).Encode(answerMessage) messageBytes := reqBodyBytes.Bytes() connection.WriteMessage(websocket.TextMessage, messageBytes) } else if response.Method == "trickle" { var trickleResponse TrickleResponse if err := json.Unmarshal(message, &trickleResponse); err != nil { log.Fatal(err) } err := peerConnection.AddICECandidate(*trickleResponse.Params.Candidate) if err != nil { log.Fatal(err) } } } }
- go mod init WebRTCCamera
- go mod tidy- module WebRTCCamera go 1.17 require ( github.com/google/uuid v1.3.0 github.com/gorilla/websocket v1.4.2 github.com/pion/logging v0.2.2 github.com/pion/mediadevices v0.3.1 github.com/pion/webrtc/v3 v3.1.11 github.com/sourcegraph/jsonrpc2 v0.1.0 ) require ( github.com/blackjack/webcam v0.0.0-20200313125108-10ed912a8539 // indirect github.com/gen2brain/malgo v0.10.35 // indirect github.com/pion/datachannel v1.5.2 // indirect github.com/pion/dtls/v2 v2.0.10 // indirect github.com/pion/ice/v2 v2.1.14 // indirect github.com/pion/interceptor v0.1.2 // indirect github.com/pion/mdns v0.0.5 // indirect github.com/pion/randutil v0.1.0 // indirect github.com/pion/rtcp v1.2.9 // indirect github.com/pion/rtp v1.7.4 // indirect github.com/pion/sctp v1.8.0 // indirect github.com/pion/sdp/v3 v3.0.4 // indirect github.com/pion/srtp/v2 v2.0.5 // indirect github.com/pion/stun v0.3.5 // indirect github.com/pion/transport v0.12.3 // indirect github.com/pion/turn/v2 v2.0.5 // indirect github.com/pion/udp v0.1.1 // indirect golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d // indirect golang.org/x/net v0.0.0-20211020060615-d418f374d309 // indirect golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect )
- build and run
- got disconnected around 60 seconds later
What did you expect?
- do not disconnect, keep connected
What happened?
- ICEconnection got disconnected
- It is not sending STUNmessage any more, with help of wireshark
- Supposed to send STUNmessage here https://github.com/pion/ice/blob/715f2083100814b24cf1ec213d71178d64d65777/agent.go#L694
- BUT, selectedPair.Local.LastSent()is not long ago enough, should be longer than 2 seconds
- Why LastSentis keep being not long ago, because it is keep updating at here https://github.com/pion/ice/blob/715f2083100814b24cf1ec213d71178d64d65777/candidate_base.go#L306
- here siblings https://github.com/pion/ice/blob/715f2083100814b24cf1ec213d71178d64d65777/candidatepair.go#L89 and https://github.com/pion/ice/blob/715f2083100814b24cf1ec213d71178d64d65777/candidatepair.go#L94 they both write message to remote
- the problem is CandidatePair.Writeis doing too much,candidateBase.lastSentis keep updating totime.Now()then https://github.com/pion/ice/blob/715f2083100814b24cf1ec213d71178d64d65777/agent.go#L701 has no chance to sendSTUNheartbeat message to keep alive, then got disconnected after timeout
- Is there anything I did wrong or how to fix it?
func (p *CandidatePair) Write(b []byte) (int, error) {
	fmt.Printf("CandidatePair Write to %s for %x\n", p.Remote.String(), b)
	return p.Local.writeTo(b, p.Remote)
}
func (a *Agent) sendSTUN(msg *stun.Message, local, remote Candidate) {
	a.log.Tracef("send STUN message: %s from %s to %s", string(msg.Raw), local.String(), remote.String())
	_, err := local.writeTo(msg.Raw, remote)
	if err != nil {
		a.log.Tracef("failed to send STUN message: %s", err)
	}
}
just can't get it, they are both writing by the same Candidate, holding the same candidateBase.lastSent, CandidatePair is running Write constantly, how can Agent to sendSTUN
Hi @http600
If you remove the lastReceived/LastSent checks https://github.com/pion/ice/blob/master/agent.go#L701-L702 does it reconnect?
Do you always get disconnected? What causes the disconnection?
I believe things are correct today. If you are sending RTP/RTCP/DTLS traffic then ICE doesn't need to send keepAlives. We consider any inbound traffic to update timing information here
Hi @http600
If you remove the lastReceived/LastSent checks https://github.com/pion/ice/blob/master/agent.go#L701-L702 does it reconnect?
Do you always get disconnected? What causes the disconnection?
I believe things are correct today. If you are sending RTP/RTCP/DTLS traffic then ICE doesn't need to send keepAlives. We consider any inbound traffic to update timing information here
Hi @Sean-Der appreciate it very much, We consider any inbound traffic to update timing information, that's great, RTP is sending constantly, I can see it in wireshark, also I can play it in on web page as mentioned in broadcasting-ion-sfu, so the function of forward is working fine, I will check why constant RTP sending is not keeping alive in the environment, thanks.
client has received DTLSv1.2 with Content Type: Alert (21)
Hi @http600
If you remove the lastReceived/LastSent checks https://github.com/pion/ice/blob/master/agent.go#L701-L702 does it reconnect?
still got disconnected even sending/receiving stun smoothly
Do you always get disconnected? What causes the disconnection?
I'm not sure currently, but the last packet was DTLSV1.2 alert message
I believe things are correct today. If you are sending RTP/RTCP/DTLS traffic then ICE doesn't need to send keepAlives. We consider any inbound traffic to update timing information here
I'm sure it is working, just can not figure it out what is the cause of this problem
Is there any solution yet? Facing same problem here, connection got disconnected in 2 minutes after sending big file data in data channels.
@Sean-Der
@dafapro / @http600 - did either of you ever figure this out? I'm experiencing a similar issue, connecting a libjuice remote from Pion.