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

Use UDPMux for Server reflexive candidates #419

Merged
merged 1 commit into from
Feb 20, 2022

Conversation

braginini
Copy link
Contributor

@braginini braginini commented Feb 9, 2022

Description

See reference issue for details #400

Core changes:

  • Modified UDPMux to additionally support Server Reflexive candidates (besides Host candidates).
  • Added UDPMuxSrflx to Agent
  • If UDPMuxSrflx is not provided, the original logic for server reflexive candidates is applied.

The reason why we went with 2 separate UDPMux'ex for Host and Srflx candidates is the conflicts between different candidate types on candidates gathering. Original UDPMux uses uFrag to mux connections which is fine for the case when there is a single candidate type. When there are more different candidate types the packets are getting mixed up.
There is a possible solution to that which is using the STUN transaction ID map. The logic will be more complicated.

Next Steps:
We will introduce a UDPMuxRelay alongside UDPMux (for host candidates) and UDPMuxSrflx (for server reflexive candidates). At this point, there will be 1 UDP port for each candidate type (host, srflx, relay).

We will then consolidate these 3 UDPMux'es into a single one. I believe that once all the candidate type UDPMux'es are ready we'd be able to come up with a better refactor.

Reference issue

#400

EDIT:

This change breaks the UDPMux interface by adding the GetXORMappedAddr function.
See #419 (comment)

agent.go Outdated Show resolved Hide resolved
udp_mux.go Outdated Show resolved Hide resolved
udp_mux.go Outdated Show resolved Hide resolved
udp_mux.go Outdated Show resolved Hide resolved
udp_mux.go Outdated
@@ -16,6 +19,7 @@ type UDPMux interface {
io.Closer
GetConn(ufrag string) (net.PacketConn, error)
RemoveConnByUfrag(ufrag string)
GetXORMappedAddr(serverAddr net.Addr, deadline time.Duration) (*stun.XORMappedAddress, error)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding GetXORMappedAddr breaks the public API.

Does this matter, does anyone implement? If everyone uses our Default I think it is ok.

Copy link
Contributor Author

@braginini braginini Feb 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point and a good question :)
The thing is that if we want to be universal and support all the types of candidates and not only host candidates then this interface should change.
Also, we will be adding GetRelayedAddr function in the next steps to support relay candidates (I will be starting to work on this soon).

One option we could choose not to break the interface is to introduce a new interface UniversalUDPMux which will eventually replace the original UDPMux (which can be deprecated at this point).

I'm hoping that everyone uses the default UDPMux :)

What are your thoughts on that @Sean-Der @davidzhao @boks1971 ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At least I see that it won't break things in pion/webrtc cuz the default one is used
https://github.com/pion/webrtc/blob/master/icemux.go#L22

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this probably should use a different interface. it doesn't feel that the simple UDPMux should be concerned with STUN or XORMappedAddr.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got you,

To keep muxing logic separate I think we could then add a layer (struct) that reads from the UDPConn and handles STUN packets before the muxer. But then, this struct has to always wrap the muxer and should be exposed to the agent user.
I'm happy to jump in a call and discuss this further. We are very much looking forward to improving this part further and making pion/ice more efficient.

Copy link
Contributor Author

@braginini braginini Feb 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed with @davidzhao that we'll create a separate SRFLXUDPMux that would embed a UDPMux instance.

@codecov
Copy link

codecov bot commented Feb 9, 2022

Codecov Report

Merging #419 (dc81fae) into master (aca069d) will decrease coverage by 0.11%.
The diff coverage is 74.05%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #419      +/-   ##
==========================================
- Coverage   79.28%   79.17%   -0.12%     
==========================================
  Files          33       34       +1     
  Lines        3650     3860     +210     
==========================================
+ Hits         2894     3056     +162     
- Misses        590      625      +35     
- Partials      166      179      +13     
Flag Coverage Δ
go 79.17% <74.05%> (-0.12%) ⬇️
wasm 25.18% <0.00%> (-1.45%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
agent_config.go 82.14% <ø> (ø)
gather.go 66.80% <69.09%> (+0.13%) ⬆️
udp_mux_universal.go 75.16% <75.16%> (ø)
agent.go 81.65% <100.00%> (+0.44%) ⬆️
selection.go 82.07% <0.00%> (+1.88%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update aca069d...dc81fae. Read the comment docs.

Copy link
Contributor

@boks1971 boks1971 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm! This is very nice!

@davidzhao would be the best person to catch any gotchas here. I am not fully familiar with this module.

gather.go Outdated

conn, err := a.udpMuxSrflx.GetConn(a.localUfrag)
if err != nil {
a.log.Warnf("could not find local connection in UDPMux %s %s: %v\n", network, url, err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe log it as UDPMuxSrflx to avoid any confusion.

Copy link
Member

@davidzhao davidzhao left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel a bit unease with GetXORMappedAddr in the UDPMux interface. It feels very specific to Srflx and it would complicate UDPMux for those that don't require this functionality. (Since most are running Pion in the server environment, they don't have to worry about srflx).

gather.go Outdated
return
}

conn, err := a.udpMuxSrflx.GetConn(a.localUfrag)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wouldn't this mean all of the srflx candidates would be using the same conn? If so when any one of the STUN servers fails, it'll end up closing the shared connection in closeConnAndLog

this might require a different key that's specific to each ufrag + url.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remember if a candidate is ultimately not chosen, it'll also get closed by the ICE agent. In that case even the chosen srflx candidate will be closed too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's right, good point.

udp_mux.go Outdated
@@ -16,6 +19,7 @@ type UDPMux interface {
io.Closer
GetConn(ufrag string) (net.PacketConn, error)
RemoveConnByUfrag(ufrag string)
GetXORMappedAddr(serverAddr net.Addr, deadline time.Duration) (*stun.XORMappedAddress, error)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this probably should use a different interface. it doesn't feel that the simple UDPMux should be concerned with STUN or XORMappedAddr.

@braginini
Copy link
Contributor Author

braginini commented Feb 16, 2022

@davidzhao @Sean-Der
I updated this PR as per suggestions of @davidzhao

I kept UDPMux and UDPMuxDefault as is (original ICE version, well with a minor change, see below) and added UniversalUDPMux that embeds UDPMuxDefault.
UniversalUDPMux is supposed to work with any type of candidate but so far it works only with Server Reflexive.
I will be adding relay and host candidates support in the following PRs.

The core idea of UniversalUDPMuxDefault is to just handle packets coming from the STUN server discovering a XorMappedAddress.
All other packets are just forwarded to the UDPMuxDefault that does the actual muxing.
(UDPMuxDefault is embedded in the UniversalUDPMuxDefault).
All this is done by overriding ReadFrom of the original UDPConn

I used STUN URL to add some uniqueness to the muxedconn and to support multiple STUN servers.

Important
I had to modify the original UDPMuxDefault.RemoveConnByUfrag to avoid code duplication.
The change is pretty simple and shouldn't affect anything. Instead of removing conn by exact ufrag match I remove it by prefix.
This allows me to remove all the connections created by UniversalUDPMuxDefault (with the STUN URL suffix).

P.S.
I tried to put as many comments as possible but let me know if you need further explanations.
What do you think guys?

The original UDPMux only works for the HOST candidates.
UniversalUDPMux adds support for SRFLX candidates
and will later support Relay candidates.
UniversalUDPMux embeds UDPMuxDefault and
handles STUN server packets to discover XORMappedAddr
forwarding the remaining packets for muxing to UDPMuxDefault.
@braginini
Copy link
Contributor Author

Hey @Sean-Der @davidzhao

I reset our fork branch to a clean state and commit the whole work once again.
The reason being Github actions that I completely missed - there were many failing (e.g. lint).
I enabled actions in our fork, fixed all the issues and added more tests. All is green now :)

I'm very sorry for not doing it from the very beginning!
Looking forward for you to checking my work.

Cheers,
Misha

@braginini braginini reopened this Feb 18, 2022
Copy link
Member

@davidzhao davidzhao left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really great work @braginini! It's awesome to have your contributions here.

@davidzhao davidzhao merged commit 2d70ec8 into pion:master Feb 20, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

5 participants