diff --git a/pjsip-apps/src/pjsua/pjsua_app.c b/pjsip-apps/src/pjsua/pjsua_app.c index fd60ebb765..de2178d3cc 100644 --- a/pjsip-apps/src/pjsua/pjsua_app.c +++ b/pjsip-apps/src/pjsua/pjsua_app.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2008-2026 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program is free software; you can redistribute it and/or modify @@ -2190,6 +2190,13 @@ static pj_status_t app_init(void) app_config_init_video(&app_config.acc_cfg[i]); app_config.acc_cfg[i].txt_red_level = app_config.txt_red_level; + if (app_config.instance_id.slen && + !app_config.acc_cfg[i].rfc5626_instance_id.slen) + { + app_config.acc_cfg[i].rfc5626_instance_id = app_config.instance_id; + } + if (app_config.outb_disable) + app_config.acc_cfg[i].use_rfc5626 = PJ_FALSE; status = pjsua_acc_add(&app_config.acc_cfg[i], PJ_TRUE, NULL); if (status != PJ_SUCCESS) diff --git a/pjsip-apps/src/pjsua/pjsua_app_cli.c b/pjsip-apps/src/pjsua/pjsua_app_cli.c index 9d7fbaddc8..c952cffe2c 100644 --- a/pjsip-apps/src/pjsua/pjsua_app_cli.c +++ b/pjsip-apps/src/pjsua/pjsua_app_cli.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2008-2026 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program is free software; you can redistribute it and/or modify @@ -843,6 +843,9 @@ static pj_status_t cmd_add_account(pj_cli_cmd_val *cval) acc_cfg.rtp_cfg = app_config.rtp_cfg; acc_cfg.txt_red_level = app_config.txt_red_level; + acc_cfg.rfc5626_instance_id = app_config.instance_id; + if (app_config.outb_disable) + acc_cfg.use_rfc5626 = PJ_FALSE; app_config_init_video(&acc_cfg); status = pjsua_acc_add(&acc_cfg, PJ_TRUE, NULL); diff --git a/pjsip-apps/src/pjsua/pjsua_app_common.h b/pjsip-apps/src/pjsua/pjsua_app_common.h index f0b8b2682b..145dd952af 100644 --- a/pjsip-apps/src/pjsua/pjsua_app_common.h +++ b/pjsip-apps/src/pjsua/pjsua_app_common.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2008-2026 Teluu Inc. (http://www.teluu.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -89,6 +89,8 @@ typedef struct pjsua_app_config pj_bool_t no_udp; pj_bool_t use_tls; pj_bool_t keep_call_on_tsx_fail; + pj_str_t instance_id; + pj_bool_t outb_disable; pjsua_transport_config udp_cfg; pjsua_transport_config rtp_cfg; pj_bool_t enable_rtcp_mux; diff --git a/pjsip-apps/src/pjsua/pjsua_app_config.c b/pjsip-apps/src/pjsua/pjsua_app_config.c index b30153f70a..917f155cbd 100644 --- a/pjsip-apps/src/pjsua/pjsua_app_config.c +++ b/pjsip-apps/src/pjsua/pjsua_app_config.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2008-2026 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program is free software; you can redistribute it and/or modify @@ -97,6 +97,9 @@ static void usage(void) PJSIP_SESS_TIMER_DEF_SE); puts (" --timer-min-se=N Session timers minimum expiration period, in secs (def:90)"); puts (" --outb-rid=string Set SIP outbound reg-id (default:1)"); + puts (" --instance-id=URN Set the SIP instance ID of all accounts, e.g."); + puts (" \"\""); + puts (" --outb-disable Disable SIP outbound (RFC 5626) for all accounts"); puts (" --auto-update-nat=N Enable/disable SIP traversal behind symmetric NAT (def: 1)"); puts (" 0: disabled, 1:enabled with exception, 2: fully enabled"); puts (" --disable-stun Disable STUN for this account"); @@ -409,6 +412,7 @@ static pj_status_t parse_args(int argc, char *argv[], OPT_100REL, OPT_USE_IMS, OPT_REALM, OPT_USERNAME, OPT_PASSWORD, OPT_AKA_OP, OPT_AKA_AMF, OPT_REG_RETRY_INTERVAL, OPT_REG_USE_PROXY, OPT_MWI, OPT_NAMESERVER, OPT_STUN_SRV, OPT_UPNP, OPT_OUTB_RID, + OPT_INSTANCE_ID, OPT_OUTB_DISABLE, OPT_ADD_BUDDY, OPT_OFFER_X_MS_MSG, OPT_NO_PRESENCE, OPT_AUTO_ANSWER, OPT_AUTO_PLAY, OPT_AUTO_PLAY_HANGUP, OPT_AUTO_LOOP, OPT_AUTO_CONF, OPT_CLOCK_RATE, OPT_SND_CLOCK_RATE, OPT_STEREO, @@ -596,6 +600,8 @@ static pj_status_t parse_args(int argc, char *argv[], { "timer-se", 1, 0, OPT_TIMER_SE}, { "timer-min-se", 1, 0, OPT_TIMER_MIN_SE}, { "outb-rid", 1, 0, OPT_OUTB_RID}, + { "instance-id", 1, 0, OPT_INSTANCE_ID}, + { "outb-disable", 0, 0, OPT_OUTB_DISABLE}, { "video", 0, 0, OPT_VIDEO}, { "text", 0, 0, OPT_TEXT}, { "text-red", 1, 0, OPT_TEXT_RED}, @@ -911,6 +917,14 @@ static pj_status_t parse_args(int argc, char *argv[], cur_acc->rfc5626_reg_id = pj_str(pj_optarg); break; + case OPT_INSTANCE_ID: + cfg->instance_id = pj_str(pj_optarg); + break; + + case OPT_OUTB_DISABLE: + cfg->outb_disable = PJ_TRUE; + break; + case OPT_USE_IMS: /* Activate IMS settings */ cur_acc->auth_pref.initial_auth = PJ_TRUE; break; @@ -2399,6 +2413,13 @@ int write_settings(pjsua_app_config *config, char *buf, pj_size_t max) if (config->cfg.server_failover) cfg_add(&cfg, max, "--server-failover\n"); + if (config->instance_id.slen) { + cfg_addf(&cfg, max, "--instance-id %.*s\n", + (int)config->instance_id.slen, config->instance_id.ptr); + } + if (config->outb_disable) + cfg_add(&cfg, max, "--outb-disable\n"); + /* Outbound proxy */ for (i=0; icfg.outbound_proxy_cnt; ++i) { cfg_addf(&cfg, max, "--outbound %.*s\n", diff --git a/pjsip-apps/src/pjsua/pjsua_app_legacy.c b/pjsip-apps/src/pjsua/pjsua_app_legacy.c index ed67c95e4e..ab2a9efe30 100644 --- a/pjsip-apps/src/pjsua/pjsua_app_legacy.c +++ b/pjsip-apps/src/pjsua/pjsua_app_legacy.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2008-2026 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program is free software; you can redistribute it and/or modify @@ -1051,6 +1051,9 @@ static void ui_add_account(pjsua_transport_config *rtp_cfg) acc_cfg.rtp_cfg = *rtp_cfg; acc_cfg.txt_red_level = app_config.txt_red_level; + acc_cfg.rfc5626_instance_id = app_config.instance_id; + if (app_config.outb_disable) + acc_cfg.use_rfc5626 = PJ_FALSE; app_config_init_video(&acc_cfg); status = pjsua_acc_add(&acc_cfg, PJ_TRUE, NULL); diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h index 4629389d36..728a797f72 100644 --- a/pjsip/include/pjsua-lib/pjsua.h +++ b/pjsip/include/pjsua-lib/pjsua.h @@ -4841,6 +4841,13 @@ typedef struct pjsua_acc_config * value will look like "" * without the doublequote. * + * An instance ID set here is sent in every REGISTER, also without SIP + * outbound. Registrars such as Kamailio and Flexisip then treat contacts + * with the same instance ID as one device and replace the older binding, + * so the ID must be unique to the device and persistent (RFC 5626 + * section 4.1). The generated one is sent only with outbound: it is the + * same for all instances on one host, and on iOS ("localhost"). + * * Default: empty */ pj_str_t rfc5626_instance_id; diff --git a/pjsip/include/pjsua2/account.hpp b/pjsip/include/pjsua2/account.hpp index ac5862e090..1f44a239b7 100644 --- a/pjsip/include/pjsua2/account.hpp +++ b/pjsip/include/pjsua2/account.hpp @@ -838,6 +838,13 @@ struct AccountNatConfig : public PersistentObject * value will look like "" * without the double-quotes. * + * An instance ID set here is sent in every REGISTER, also without SIP + * outbound. Registrars such as Kamailio and Flexisip then treat contacts + * with the same instance ID as one device and replace the older binding, + * so the ID must be unique to the device and persistent (RFC 5626 + * section 4.1). The generated one is sent only with outbound: it is the + * same for all instances on one host, and on iOS ("localhost"). + * * Default: empty */ string sipOutboundInstanceId; diff --git a/pjsip/src/pjsua-lib/pjsua_acc.c b/pjsip/src/pjsua-lib/pjsua_acc.c index 6aed518db2..05c996c420 100644 --- a/pjsip/src/pjsua-lib/pjsua_acc.c +++ b/pjsip/src/pjsua-lib/pjsua_acc.c @@ -261,6 +261,10 @@ static void init_outbound_setting(pjsua_acc *acc) (int)acc_cfg->rfc5626_instance_id.slen, acc_cfg->rfc5626_instance_id.ptr); acc->rfc5626_instprm.slen = len; + PJ_LOG(4,(THIS_FILE, "Acc %d: instance ID %.*s is sent in every " + "REGISTER", acc->index, + (int)acc_cfg->rfc5626_instance_id.slen, + acc_cfg->rfc5626_instance_id.ptr)); } if (acc_cfg->rfc5626_reg_id.slen == 0) { @@ -279,7 +283,8 @@ static void init_outbound_setting(pjsua_acc *acc) acc->rfc5626_regprm.slen = len; } - acc->rfc5626_status = OUTBOUND_WANTED; + if (acc_cfg->use_rfc5626) + acc->rfc5626_status = OUTBOUND_WANTED; } /* Forget a 439 recorded against a first hop we may no longer be talking to, @@ -532,7 +537,7 @@ static pj_status_t initialize_acc(unsigned acc_id) /* If SIP outbound is enabled, generate instance and reg ID if they are * not specified */ - if (acc_cfg->use_rfc5626) { + if (acc_cfg->use_rfc5626 || acc_cfg->rfc5626_instance_id.slen) { init_outbound_setting(acc); } @@ -2163,6 +2168,7 @@ static pj_bool_t update_regc_contact(pjsua_acc *acc, pjsua_acc_config *acc_cfg = &acc->cfg; const pj_str_t *contact = new_contact ? new_contact : &acc->contact; pj_bool_t need_outbound = PJ_FALSE; + pj_bool_t need_instance; pj_str_t prev_reg_contact = acc->reg_contact; unsigned prev_status = acc->rfc5626_status; const pj_str_t tcp_param = pj_str(";transport=tcp"); @@ -2192,6 +2198,10 @@ static pj_bool_t update_regc_contact(pjsua_acc *acc, need_outbound = PJ_TRUE; done: + /* A configured instance ID is sent without reg-id too, which RFC 5626 + * section 6 allows. The generated one only goes with outbound. + */ + need_instance = need_outbound || acc_cfg->rfc5626_instance_id.slen; { pj_ssize_t len; pj_str_t reg_contact; @@ -2200,8 +2210,8 @@ static pj_bool_t update_regc_contact(pjsua_acc *acc, acc->cfg.contact_params.slen + acc->cfg.reg_contact_params.slen + acc->cfg.reg_contact_uri_params.slen + - (need_outbound? - (acc->rfc5626_instprm.slen + acc->rfc5626_regprm.slen): 0) + + (need_outbound? acc->rfc5626_regprm.slen: 0) + + (need_instance? acc->rfc5626_instprm.slen: 0) + 5; /* allowance */ reg_contact.ptr = (char*) pj_pool_alloc(acc->pool, len); @@ -2272,14 +2282,13 @@ static pj_bool_t update_regc_contact(pjsua_acc *acc, if (need_outbound) { acc->rfc5626_status = OUTBOUND_WANTED; - /* Need to use outbound, append the contact with - * +sip.instance and reg-id parameters. - */ + /* Need to use outbound, append the reg-id parameter */ pj_strcat(®_contact, &acc->rfc5626_regprm); - pj_strcat(®_contact, &acc->rfc5626_instprm); } else { acc->rfc5626_status = OUTBOUND_NA; } + if (need_instance) + pj_strcat(®_contact, &acc->rfc5626_instprm); /* Contact params */ pj_strcat(®_contact, &acc->cfg.reg_contact_params); diff --git a/tests/pjsua/scripts-sipp/uas-register-instance-nooutbound.py b/tests/pjsua/scripts-sipp/uas-register-instance-nooutbound.py new file mode 100644 index 0000000000..198a037a19 --- /dev/null +++ b/tests/pjsua/scripts-sipp/uas-register-instance-nooutbound.py @@ -0,0 +1,14 @@ +# +# An instance ID with SIP outbound disabled, over TCP: the REGISTER carries +# +sip.instance without reg-id and does not offer outbound. SIPp checks the +# REGISTER, see the XML scenario. +# +PJSUA = ['--null-audio --outb-disable ' + '--instance-id=""'] + +# Add the account only once SIPp listens: a start-up REGISTER would be +# refused, and an "rr" racing it could be lost to PJSIP_EBUSY. +PJSUA_EXPECTS = [[0, "", "+a sip:pjsua@127.0.0.1 " + "sip:127.0.0.1:$SIPP_PORT;transport=tcp * pjsua pjsua"]] + +SIPP_TRANSPORT = "t1" diff --git a/tests/pjsua/scripts-sipp/uas-register-instance-nooutbound.xml b/tests/pjsua/scripts-sipp/uas-register-instance-nooutbound.xml new file mode 100644 index 0000000000..7887d76dc2 --- /dev/null +++ b/tests/pjsua/scripts-sipp/uas-register-instance-nooutbound.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/pjsua/scripts-sipp/uas-register-instance-norequire.py b/tests/pjsua/scripts-sipp/uas-register-instance-norequire.py new file mode 100644 index 0000000000..71c8d44e5d --- /dev/null +++ b/tests/pjsua/scripts-sipp/uas-register-instance-norequire.py @@ -0,0 +1,13 @@ +# +# As uas-register-outbound-norequire, with an instance ID configured: the +# refresh keeps +sip.instance. SIPp checks the REGISTERs, see the XML scenario. +# +PJSUA = ['--null-audio ' + '--instance-id=""'] + +# Add the account only once SIPp listens: a start-up REGISTER would be +# refused, and an "rr" racing it could be lost to PJSIP_EBUSY. +PJSUA_EXPECTS = [[0, "", "+a sip:pjsua@127.0.0.1 " + "sip:127.0.0.1:$SIPP_PORT;transport=tcp * pjsua pjsua"]] + +SIPP_TRANSPORT = "t1" diff --git a/tests/pjsua/scripts-sipp/uas-register-instance-norequire.xml b/tests/pjsua/scripts-sipp/uas-register-instance-norequire.xml new file mode 100644 index 0000000000..958b53ece6 --- /dev/null +++ b/tests/pjsua/scripts-sipp/uas-register-instance-norequire.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/pjsua/scripts-sipp/uas-register-instance-udp.py b/tests/pjsua/scripts-sipp/uas-register-instance-udp.py new file mode 100644 index 0000000000..fa6cb0d6d8 --- /dev/null +++ b/tests/pjsua/scripts-sipp/uas-register-instance-udp.py @@ -0,0 +1,10 @@ +# +# pjsua never offers SIP outbound over UDP, so +sip.instance appears here +# only because an instance ID is configured. SIPp checks the REGISTER, see +# the XML scenario. +# +PJSUA = ['--null-audio --id=sip:pjsua@127.0.0.1 --registrar=$SIPP_URI ' + '--realm=* --username=pjsua --password=pjsua ' + '--instance-id=""'] + +PJSUA_EXPECTS = [] diff --git a/tests/pjsua/scripts-sipp/uas-register-instance-udp.xml b/tests/pjsua/scripts-sipp/uas-register-instance-udp.xml new file mode 100644 index 0000000000..a2562ce232 --- /dev/null +++ b/tests/pjsua/scripts-sipp/uas-register-instance-udp.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +