Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

webrtc client is not sending stun message to keepalive then ice got disconnect #2061

Open
http600 opened this issue Dec 24, 2021 · 8 comments

Comments

@http600
Copy link

http600 commented Dec 24, 2021

Your environment.

  • Version: github.com/pion/webrtc/v3 v3.1.11
  • Browser: NOT RELATED
  • Other Information - ion-sfu c8cea05fa60612ae819594afc9655573d8958a5d

What did you do?

  1. 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)
    		    }
    	    }
        }
    }
  2. go mod init WebRTCCamera
  3. 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
    )
    
    
  4. build and run
  5. got disconnected around 60 seconds later

What did you expect?

  1. do not disconnect, keep connected

What happened?

  1. ICE connection got disconnected
  2. It is not sending STUN message any more, with help of wireshark
  3. Supposed to send STUN message here https://github.com/pion/ice/blob/715f2083100814b24cf1ec213d71178d64d65777/agent.go#L694
  4. BUT, selectedPair.Local.LastSent() is not long ago enough, should be longer than 2 seconds
  5. Why LastSent is keep being not long ago, because it is keep updating at here https://github.com/pion/ice/blob/715f2083100814b24cf1ec213d71178d64d65777/candidate_base.go#L306
  6. 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
  7. the problem is CandidatePair.Write is doing too much, candidateBase.lastSent is keep updating to time.Now() then https://github.com/pion/ice/blob/715f2083100814b24cf1ec213d71178d64d65777/agent.go#L701 has no chance to send STUN heartbeat message to keep alive, then got disconnected after timeout
  8. Is there anything I did wrong or how to fix it?
@http600
Copy link
Author

http600 commented Dec 25, 2021

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

@Sean-Der
Copy link
Member

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

@http600
Copy link
Author

http600 commented Dec 26, 2021

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.

@http600
Copy link
Author

http600 commented Dec 26, 2021

client has received DTLSv1.2 with Content Type: Alert (21)

@http600
Copy link
Author

http600 commented Dec 26, 2021

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

@Sean-Der Sean-Der added this to the 3.2.0 milestone Jan 23, 2022
@Sean-Der Sean-Der removed this from the 3.2.0 milestone May 22, 2022
@dafapro
Copy link

dafapro commented Jun 23, 2022

Is there any solution yet? Facing same problem here, connection got disconnected in 2 minutes after sending big file data in data channels.

@dafapro
Copy link

dafapro commented Jun 23, 2022

@Sean-Der

@adriancable
Copy link
Contributor

@dafapro / @http600 - did either of you ever figure this out? I'm experiencing a similar issue, connecting a libjuice remote from Pion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

4 participants