Re: [asterisk-dev] PJSIP and RTP address selection
Hi Joshua, Thanks for giving this some thought, I really do appreciate. I'll aim towards implementing both the first options, users can then pick for themselves. I think both the options I've proposed could work, but one may have benefits over the other that I can't envison currently. I did actually think of another "trick" over the weekend that could make RTP seem to come back from the right location, so for reference for other people (this won't work unless asterisk is set up to assume the remote side is behind NAT and waits for RTP from source first). We bind STUN to a single IP in the case here and then DNAT all other IPs on INPUT to that IP, so say STUN is bound to 1.1.1.1 (basic mode only, one can probably adjust to make work for full mode too but would be much more complex since you'd need to inject a related conntrack entry in some way), then a DNAT like this enables you to utilize basic STUN against any IP bound on the host: -A PREROUTING -i bond0 -p udp -m udp --dport 3478 -j DNAT --to-destination 1.1.1.1 This creates a conntrack tuple similar to (conntrack -L format, split over multiple lines): udp 17 19 src=original_source dst=original_dest sport=38277 dport=3478 src=1.1.1.1 dst=original_source sport=3478 dport=38277 mark=0 use=1 Given that entry netfilter re-adjusts the source IP on egress from the host again back to the original incoming destination. The same should work even if stun is bound to ANY, you just have to DNAT to the IP that will be selected by the kernel on egress. I think the same will hold for RTP, in which case one could perform the same kind of mapping, out of hand: -A PREROUTING -i bond0 -p udp -m udp --dport $rtpstart:$((rtpend+1)) -j DNAT --to-destination 1.1.1.1 Assuming that 1.1.1.1 is the default source off the box the same should apply. It is assumed rtpstart and rtpend is environment variables containing the values from rtp.conf. I have not tested this, but I've used this "trick" for things like multi-homed OpenVPN and other udp based protocols that you really want to bind to ANY but screws up due to this. It's a "nasty" solution in my opinion but it should (might) work. This solution may retain the benefits of the current configuration that you were concerned about. Also, since IPv6 and IPv4 retains independent entries in the connection tracking table this should still allow jumping between IPv4 and IPv6 for RTP transport. In short this might be the "silver bullet" that you're looking for. If like me your udp ports is firewalled to only allow RTP to be received from authenticated sources this provides some additional protection against rtp bleed type scenarios. The downside is that if you suddenly jump between transports then the firewalling might cause problems. Anyway, perhaps this is useable to someone, perhaps not. Kind Regards, Jaco On 2018/09/16 21:18, Joshua Colp wrote: > On Thu, Sep 13, 2018, at 7:16 PM, Joshua Colp wrote: >> On Thu, Sep 13, 2018, at 7:00 PM, Matt Fredrickson wrote: >> >> >> I have two potential fixes (and two that aren't practical options I don't think but might be with knowledge I don't have) both with advantages and disadvantages: > I gave some further thought over this weekend to any other alternative > approaches which would have less of an impact but sadly came up empty. I > think the list you provided is indeed the available options. > -- _ -- Bandwidth and Colocation Provided by http://www.api-digital.com -- Astricon is coming up October 9-11! Signup is available at: https://www.asterisk.org/community/astricon-user-conference asterisk-dev mailing list To UNSUBSCRIBE or update options visit: http://lists.digium.com/mailman/listinfo/asterisk-dev
Re: [asterisk-dev] PJSIP and RTP address selection
On Thu, Sep 13, 2018, at 7:16 PM, Joshua Colp wrote: > On Thu, Sep 13, 2018, at 7:00 PM, Matt Fredrickson wrote: > > > > > > I have two potential fixes (and two that aren't practical options I > > > don't think but might be with knowledge I don't have) both with > > > advantages and disadvantages: I gave some further thought over this weekend to any other alternative approaches which would have less of an impact but sadly came up empty. I think the list you provided is indeed the available options. -- Joshua Colp Digium - A Sangoma Company | Senior Software Developer 445 Jan Davis Drive NW - Huntsville, AL 35806 - US Check us out at: www.digium.com & www.asterisk.org -- _ -- Bandwidth and Colocation Provided by http://www.api-digital.com -- Astricon is coming up October 9-11! Signup is available at: https://www.asterisk.org/community/astricon-user-conference asterisk-dev mailing list To UNSUBSCRIBE or update options visit: http://lists.digium.com/mailman/listinfo/asterisk-dev
Re: [asterisk-dev] PJSIP and RTP address selection
On Thu, Sep 13, 2018, at 7:00 PM, Matt Fredrickson wrote: > > I have two potential fixes (and two that aren't practical options I > > don't think but might be with knowledge I don't have) both with > > advantages and disadvantages: > > > > 1. Bind the socket against the advertised address. > > That seems interesting, although I'm not sure what that means in a > multi-homed world with multiple address/media streams (IPv4 + IPv6). > Also, I wonder how this works with ICE/STUN/TURN across many > interfaces and address families. Multi-home is hard to get right for > all scenarios. I can't help but wonder if instead of binding to the > wildcard address we should be explicitly binding to each > interface/address and making our own source address selection rather > than letting the kernel decide. Sometimes the kernel will decide in a > way that surprises you and I think that's what you're hitting. Indeed, the other problematic area of binding to the advertised address is that due to asynchronous DNS resolution what you end up going out on may not be what you thought - so the RTP instance and SDP has to be updated or else you could get IPv4 SDP but the traffic going out over IPv6, which is technically acceptable but things sometimes don't like it. In a pure environment where you know with greater certainty ahead of time it's easier to choose early in the process and use one. > > > 2. Upon receiving the first rtp, "narrow" the socket listening address > > to the received "to" address. > > That also doesn't seem unreasonable, but I'd rather hear what Josh > thinks since he spent lots of time with his head in this code. The problem is getting this information. You'd need to read in the full IP packet from the socket, parse the IP header itself, and look at that information. It should be possible but it's not something that has been done in Asterisk, and I'm not sure if it alters the underlying permissions required if running as a user. > > > (3.) Have the RTP sent to my primary address to begin with, not the > > socket address as for PJSIP transport. > > (4.) Update the rtp engine to be able to have multiple socket pairs and > > switch between them as the remote side does. > > That seems "most right", and matches my idea solution from above. But > then again, I'm curious how it would affect our ICE/STUN/TURN stack. Indeed - that is the most right. A question arises though - which one do you use for sending early media if you haven't received any media yet? > > > The first has various disadvantages as I understand from Joshua. Most > > of them over my head. The advantage is that the source address would be > > (more) deterministic upon sending RTP. This can be done by passing the > > transport address to rtp instance, presumably similar to what chan_sip > > does. This would in some cases break things like signaling on ipv4 and > > rtp on ipv6 if pjsip transport is not bound to ANY. This was as I > > understood one of Joshua's bigger concerns. > > Yeah Indeed, and we (both Matt and I as well as others) actually use this every day for meetings. Our video conference server (using Asterisk of course) has both IPv4 and IPv6 ICE candidates. Matt ends up using IPv4, I use IPv6. > > > The second option has the advantage that unless the address to which the > > remote side sends changes things should just work. This can be > > implemented by creating a new socket, binding it to the more specific > > address and then using dup2() to replace the old socket file descriptor, > > before closing the newly creating file descriptor. It can be returned > > to "ANY" in a similar manner if required. RTCP ports will need to be > > re-bound as well. > > > > This should probably be a configurable option either way, and one could > > add a transport option "bind_rtp_to_transport_address", and/or a > > "narrow_rtp_address" (the latter would make no sense if the former is > > active, unless the bind address is an ANY of sorts). These can be > > implemented in conjunction or separately. > > I'd hate having to add another options for this behavior. It seems > like there should be a path forward that gets most of the right cases > most of the time without it being an optional behavior. I don't think it's possible to please every scenario without an option, short of the major rework of having multiple sockets which I'd only be comfortable with in master. > > > The third option basically involves binding the socket to ANY and > > pretending to send data to the known addresses for the peer and using > > those addresses in the SDP (if we've seen SDP for the conversation > > already, those addresses, otherwise for the remote address of the SIP > > communication - this would break a number of things potentially, thus > > likely not a serious option. For example, if we're sending an INVITE to > > a web-socket transport, then potentially the web-socket connection has > > been proxied and the
Re: [asterisk-dev] PJSIP and RTP address selection
On Tue, Sep 11, 2018 at 1:51 PM, Jaco Kroon wrote: > Hi, > > I've got a scenario where (when using PJSIP, using chan_sip does what I > expect) PJSIP will advertise one address in the SDP during a > conversation but then start transmitting from another. In my case PJSIP > is advertising 197.96.209.1 in the SDP, but 197.96.209.251 is being used > to send. > > I can manipulate that by altering the IPv4 routing table to influence > address selection. > > This is due to when using PJSIP the RTP socket is bound against ANY > ([::] specifically so that both IPv4 and IPv6 will function). chan_sip > on the other hand has the RTP port bound to the same address as the > transport. After discussion with Joshua on IRC it became clear that the > PJSIP behaviour may be preferred in many cases, and that things are > plainly more complicated than one would hope. Ugh. This sounds like it's in the belly of the address selection code of PJSIP and squarely in Josh's territory. > I have two potential fixes (and two that aren't practical options I > don't think but might be with knowledge I don't have) both with > advantages and disadvantages: > > 1. Bind the socket against the advertised address. That seems interesting, although I'm not sure what that means in a multi-homed world with multiple address/media streams (IPv4 + IPv6). Also, I wonder how this works with ICE/STUN/TURN across many interfaces and address families. Multi-home is hard to get right for all scenarios. I can't help but wonder if instead of binding to the wildcard address we should be explicitly binding to each interface/address and making our own source address selection rather than letting the kernel decide. Sometimes the kernel will decide in a way that surprises you and I think that's what you're hitting. > 2. Upon receiving the first rtp, "narrow" the socket listening address > to the received "to" address. That also doesn't seem unreasonable, but I'd rather hear what Josh thinks since he spent lots of time with his head in this code. > (3.) Have the RTP sent to my primary address to begin with, not the > socket address as for PJSIP transport. > (4.) Update the rtp engine to be able to have multiple socket pairs and > switch between them as the remote side does. That seems "most right", and matches my idea solution from above. But then again, I'm curious how it would affect our ICE/STUN/TURN stack. > The first has various disadvantages as I understand from Joshua. Most > of them over my head. The advantage is that the source address would be > (more) deterministic upon sending RTP. This can be done by passing the > transport address to rtp instance, presumably similar to what chan_sip > does. This would in some cases break things like signaling on ipv4 and > rtp on ipv6 if pjsip transport is not bound to ANY. This was as I > understood one of Joshua's bigger concerns. Yeah > The second option has the advantage that unless the address to which the > remote side sends changes things should just work. This can be > implemented by creating a new socket, binding it to the more specific > address and then using dup2() to replace the old socket file descriptor, > before closing the newly creating file descriptor. It can be returned > to "ANY" in a similar manner if required. RTCP ports will need to be > re-bound as well. > > This should probably be a configurable option either way, and one could > add a transport option "bind_rtp_to_transport_address", and/or a > "narrow_rtp_address" (the latter would make no sense if the former is > active, unless the bind address is an ANY of sorts). These can be > implemented in conjunction or separately. I'd hate having to add another options for this behavior. It seems like there should be a path forward that gets most of the right cases most of the time without it being an optional behavior. > The third option basically involves binding the socket to ANY and > pretending to send data to the known addresses for the peer and using > those addresses in the SDP (if we've seen SDP for the conversation > already, those addresses, otherwise for the remote address of the SIP > communication - this would break a number of things potentially, thus > likely not a serious option. For example, if we're sending an INVITE to > a web-socket transport, then potentially the web-socket connection has > been proxied and the remote address of the web socket connection isn't > actually where the remote side is, for example, if proxying via > httpd/apache to localhost:8088 then asterisk sees 127.0.0.1 as the > "rermote". > > I'm tending towards option 2. This would perhaps also have a side > effect of minimizing attack surface for things like RTP bleed. It might be the lowest friction way forward (without rewriting the RTP/ICE/STUN/TURN layers). > I suspect this has not come to light before since most setups is likely > to only have a single IPv4 and single IPv6 global address, or in the > case of
[asterisk-dev] PJSIP and RTP address selection
Hi, I've got a scenario where (when using PJSIP, using chan_sip does what I expect) PJSIP will advertise one address in the SDP during a conversation but then start transmitting from another. In my case PJSIP is advertising 197.96.209.1 in the SDP, but 197.96.209.251 is being used to send. I can manipulate that by altering the IPv4 routing table to influence address selection. This is due to when using PJSIP the RTP socket is bound against ANY ([::] specifically so that both IPv4 and IPv6 will function). chan_sip on the other hand has the RTP port bound to the same address as the transport. After discussion with Joshua on IRC it became clear that the PJSIP behaviour may be preferred in many cases, and that things are plainly more complicated than one would hope. I have two potential fixes (and two that aren't practical options I don't think but might be with knowledge I don't have) both with advantages and disadvantages: 1. Bind the socket against the advertised address. 2. Upon receiving the first rtp, "narrow" the socket listening address to the received "to" address. (3.) Have the RTP sent to my primary address to begin with, not the socket address as for PJSIP transport. (4.) Update the rtp engine to be able to have multiple socket pairs and switch between them as the remote side does. The first has various disadvantages as I understand from Joshua. Most of them over my head. The advantage is that the source address would be (more) deterministic upon sending RTP. This can be done by passing the transport address to rtp instance, presumably similar to what chan_sip does. This would in some cases break things like signaling on ipv4 and rtp on ipv6 if pjsip transport is not bound to ANY. This was as I understood one of Joshua's bigger concerns. The second option has the advantage that unless the address to which the remote side sends changes things should just work. This can be implemented by creating a new socket, binding it to the more specific address and then using dup2() to replace the old socket file descriptor, before closing the newly creating file descriptor. It can be returned to "ANY" in a similar manner if required. RTCP ports will need to be re-bound as well. This should probably be a configurable option either way, and one could add a transport option "bind_rtp_to_transport_address", and/or a "narrow_rtp_address" (the latter would make no sense if the former is active, unless the bind address is an ANY of sorts). These can be implemented in conjunction or separately. The third option basically involves binding the socket to ANY and pretending to send data to the known addresses for the peer and using those addresses in the SDP (if we've seen SDP for the conversation already, those addresses, otherwise for the remote address of the SIP communication - this would break a number of things potentially, thus likely not a serious option. For example, if we're sending an INVITE to a web-socket transport, then potentially the web-socket connection has been proxied and the remote address of the web socket connection isn't actually where the remote side is, for example, if proxying via httpd/apache to localhost:8088 then asterisk sees 127.0.0.1 as the "rermote". I'm tending towards option 2. This would perhaps also have a side effect of minimizing attack surface for things like RTP bleed. I suspect this has not come to light before since most setups is likely to only have a single IPv4 and single IPv6 global address, or in the case of multi-homing would have one on each interface with the kernel RPF filter getting rid of traffic from a source other than where it would route back to, basically forcing an IP match based on route-based address selection. Joshua suggested that before coding on this is started all use-cases should be explored and documented, which I think is a good idea. I'd be happy to drive that process, I'd however need to understand where this should be documented. So in this respect this email servers as a request for pointers. DISCLAIMER: As I've realized I'm no SIP expert and anything beyond what's available in chan_sip currently is for me a massive learning curse. A challenge I'm quite enjoying. For further explanation, my setup is explained below. This perhaps just gives more background information to the problem I'm experiencing, and may or may not be useful to other people reading this. My setup is a bit convoluted (but no more so than required for my needs). I do run multiple asterisk instances on a single host. For each instance I assign a unique IP to the host (one IPv4 and one IPv6 where the IPv6 is of the form pre:fix::i.p.v.4 (And I have a /64 prefix delegated for this purpose). Currently IPv6 is NOT advertised in DNS until such time as I can get everything else working. On the HOST I thus have the following addresses assigned for the host: inet 197.96.209.251/24 brd 197.96.209.255 scope global bond0 inet6