On Mon, Apr 15, 2019 at 04:33:00PM +0800, xiao ruizhu wrote: [...] > When conntracks change during a dialog, SDP messages may be sent from > different conntracks to establish expects with identical tuples. In this > case expects conflict may be detected for the 2nd SDP message and end up > with a process failure. > > The fixing here is to check both RTP and RTCP expect existence before > creation. When there is an existing expect with the same tuples for a > different conntrack, reuse it. > > Here are two scenarios for the case. > > 1) > SERVER CPE > > | INVITE SDP | > 5060 |<----------------------|5060 > | 100 Trying | > 5060 |---------------------->|5060 > | 183 SDP | > 5060 |---------------------->|5060 ===> Conntrack 1 > | PRACK | > 50601 |<----------------------|5060 > | 200 OK (PRACK) | > 50601 |---------------------->|5060 > | 200 OK (INVITE) | > 5060 |---------------------->|5060 > | ACK | > 50601 |<----------------------|5060 > | | > |<--- RTP stream ------>| > | | > | INVITE SDP (t38) | > 50601 |---------------------->|5060 ===> Conntrack 2 > > With a certain configuration in the CPE, SIP messages "183 with SDP" and > "re-INVITE with SDP t38" will go through the sip helper to create > expects for RTP and RTCP. > > It is okay to create RTP and RTCP expects for "183", whose master > connection source port is 5060, and destination port is 5060. > > In the "183" message, port in Contact header changes to 50601 (from the > original 5060). So the following requests e.g. PRACK and ACK are sent to > port 50601. It is a different conntrack (let call Conntrack 2) from the > original INVITE (let call Conntrack 1) due to the port difference. > > In this example, after the call is established, there is RTP stream but no > RTCP stream for Conntrack 1, so the RTP expect created upon "183" is > cleared, and RTCP expect created for Conntrack 1 retains. > > When "re-INVITE with SDP t38" arrives to create RTP&RTCP expects, current > ALG implementation will call nf_ct_expect_related() for RTP and RTCP. The > expects tuples are identical to those for Conntrack 1. RTP expect for > Conntrack 2 succeeds in creation as the one for Conntrack 1 has been > removed. RTCP expect for Conntrack 2 fails in creation because it has > idential tuples and 'conflict' with the one retained for Conntrack 1. And > then result in a failure in processing of the re-INVITE. > > 2) > > SERVER A CPE > > | REGISTER | > 5060 |<------------------| 5060 ==> CT1 > | 200 | > 5060 |------------------>| 5060 > | | > | INVITE SDP(1) | > 5060 |<------------------| 5060 > | 300(multi choice) | > 5060 |------------------>| 5060 SERVER B > | ACK | > 5060 |<------------------| 5060 > | INVITE SDP(2) | > 5060 |-------------------->| 5060 ==> CT2 > | 100 | > 5060 |<--------------------| 5060 > | 200(contact changes)| > 5060 |<--------------------| 5060 > | ACK | > 5060 |-------------------->| 50601 ==> CT3 > | | > |<--- RTP stream ---->| > | | > | BYE | > 5060 |<--------------------| 50601 > | 200 | > 5060 |-------------------->| 50601 > | INVITE SDP(3) | > 5060 |<------------------| 5060 ==> CT1 > > CPE sends an INVITE request(1) to Server A, and creates a RTP&RTCP expect > pair for this Conntrack 1 (CT1). Server A responds 300 to redirect to > Server B. The RTP&RTCP expect pairs created on CT1 are removed upon 300 > response. > > CPE sends the INVITE request(2) to Server B, and creates an expect pair > for the new conntrack (due to destination address difference), let call > CT2. Server B changes the port to 50601 in 200 OK response, and the > following requests ACK and BYE from CPE are sent to 50601. The call is > established. There is RTP stream and no RTCP stream. So RTP expect is > removed and RTCP expect for CT2 retains. > > As BYE request is sent from port 50601, it is another conntrack, let call > CT3, different from CT2 due to the port difference. So the BYE request will > not remove the RTCP expect for CT2. > > Then another outgoing call is made, with the same RTP port being used (not > definitely but possibly). CPE firstly sends the INVITE request(3) to Server > A, and tries to create a RTP&RTCP expect pairs for this CT1. In current ALG > implementation, the RTCP expect for CT1 fails in creation because it > 'conflicts' with the residual one for CT2. As a result the INVITE request > fails to send. > > Signed-off-by: xiao ruizhu <katrina.xiaorz@xxxxxxxxx> > --- > Changes in v3: > - take Pablo's advice about the comments, nf_conntrack_expect_lock and > nf_ct_sip_expect_exists() > - change the policy to reuse the exising expect(s) instead of removal then > recreation, to avoid CPU cycle waste > Changes in v2: > - add a comment on release_conflicting_expect functionality > - move local variable errp to the beginning of the block > v1: > - original patch > --- > net/netfilter/nf_conntrack_sip.c | 45 +++++++++++++++++++++++++++++++++++----- > 1 file changed, 40 insertions(+), 5 deletions(-) > > diff --git a/net/netfilter/nf_conntrack_sip.c b/net/netfilter/nf_conntrack_sip.c > index f067c6b..0e17c14 100644 > --- a/net/netfilter/nf_conntrack_sip.c > +++ b/net/netfilter/nf_conntrack_sip.c > @@ -799,6 +799,31 @@ static int ct_sip_parse_sdp_addr(const struct nf_conn *ct, const char *dptr, > return 1; > } > > +static bool nf_ct_sip_expect_exists(const struct nf_conntrack_expect *expect, > + const struct nf_conn *ct, > + enum sip_expectation_classes class) > +{ > + return (expect && expect->master != ct && > + nfct_help(expect->master)->helper == nfct_help(ct)->helper && > + expect->class == class); > +} > + > +/* Look for an expect with identical tuple but for a different master. */ > +static bool nf_ct_sip_expect_found(const struct nf_conntrack_expect *expect, > + const struct nf_conn *ct) > +{ > + struct nf_conntrack_expect *exp; > + struct net *net = nf_ct_net(ct); > + bool found = 0; > + > + spin_lock_bh(&nf_conntrack_expect_lock); > + exp = __nf_ct_expect_find(net, nf_ct_zone(ct), &expect->tuple); __nf_ct_expect_find() may return NULL. > + found = nf_ct_sip_expect_exists(exp, ct, expect->class); > + spin_unlock_bh(&nf_conntrack_expect_lock); > + > + return found; > +} > + > static int refresh_signalling_expectation(struct nf_conn *ct, > union nf_inet_addr *addr, > u8 proto, __be16 port, > @@ -929,9 +954,7 @@ static int set_expected_rtp_rtcp(struct sk_buff *skb, unsigned int protoff, > do { > exp = __nf_ct_expect_find(net, nf_ct_zone(ct), &tuple); > > - if (!exp || exp->master == ct || > - nfct_help(exp->master)->helper != nfct_help(ct)->helper || > - exp->class != class) > + if (!nf_ct_sip_expect_exists(exp, ct, class)) > break; > #ifdef CONFIG_NF_NAT_NEEDED > if (!direct_rtp && > @@ -983,11 +1006,23 @@ static int set_expected_rtp_rtcp(struct sk_buff *skb, unsigned int protoff, > /* -EALREADY handling works around end-points that send > * SDP messages with identical port but different media type, > * we pretend expectation was set up. > + * It also works in the case that SDP messages are sent with > + * identical expect tuples but for different master conntracks. > */ > - int errp = nf_ct_expect_related(rtp_exp); > + int errp; > + > + if (nf_ct_sip_expect_found(rtp_exp, ct)) > + errp = -EALREADY; > + else > + errp = nf_ct_expect_related(rtp_exp); I wonder if we can handle this from __nf_ct_expect_check() itself. We could just check if master mismatches, then return -EALREADY from there? Similar to 876c27314ce51, but catch the master mismatches case. > if (errp == 0 || errp == -EALREADY) { > - int errcp = nf_ct_expect_related(rtcp_exp); > + int errcp; > + > + if (nf_ct_sip_expect_found(rtcp_exp, ct)) > + errcp = -EALREADY; > + else > + errcp = nf_ct_expect_related(rtcp_exp); > > if (errcp == 0 || errcp == -EALREADY) > ret = NF_ACCEPT; > -- > 1.9.1 >