Setting UserAgent-Header based on endpoint


I am using pjsip and I am looking for a way to set the UserAgent-Header for a certain endpoint to something else. So when I send a REGISTER-Request to that endpoint not the default UA is used but a different one. I have already figured out that there are global headers which get set in pjsip_global_headers.c and then “re-used” later on. So I assume my approach would have to be to check if a more specific UA header is needed (meaning that there is an endpoint which has the parameter set) and then remove the existing header and add the “new/corrected” one. I believe that I need to do that in registration_client_send()? Or in handle_client_registration()? I am not entirely sure where to get the endpoint from though there so that I can check if the endpoint has a custom UA set. Maybe someone can point me in the right direction there?

Outbound registration doesn’t have an endpoint, unless you enable line support and configure an endpoint. You’d also need to add an option on an endpoint to set a specific user agent. The easiest way for that is to look at other changes and see when someone else has added an option and use that as a base. Then you’d probably have to check the interaction between the global headers and the specific header.

Then my config is “wrong”? I currently have an endpoint which is used by an identify, this combination is used to set the codecs and so on for that endpoint/registration when a call is made. I just checked your blog post from a while ago and I assume when I would like to do it correctly I just drop the “identify” section completely, set “line=yes” and specify the endpoint in the registration? Assuming that my provider preserves the line-parameter?

For my custom option that means I would add it as an option for the registration? But in that case when an INVITE for example is sent upon initiating a call the UA will be the default again? So maybe add it for the registration and for the endpoint so that the registration controls the UA for REGISTER requests and the endpoint controls the UA for INVITE and similar requests?

That is how line support works, yes.

Such a thing could be set separately for outbound registrations and endpoints.

Alright, I just enabled the line support, works absolutely great.

When there are separate options for registrations and endpoints I guess I also need an option for aor’s? The qualify-stuff (sending periodic OPTIONS-requests) apparently seems to be independent aswell? This is starting to become a little messy if the parameter needs to be set for basically each different request (REGISTER in registration, INVITE in endpoint, OPTIONS in aor), isn’t it? I would totally understand if it won’t get merged because it’s such a mess with 3 ways to set the user agent for different kind of requests while it doesn’t really make sense to change them to different values…

Stuff is independently done, so generally stuff is set in multiple places - such as the case for outbound proxy.

This one just inspired me to also play around with the line option (thank you @jcolp for your blog entry some years ago) and it works great.

Are there any pitfalls using it? For example, if the ITSP does not support it? Or will it then be just ignored by the ITSP?

It’ll be ignored by the ITSP, and incoming calls will fail to match. Otherwise nada.

Okay, in my setups, incoming calls would still be covered by an identity section, which I still have.

Right. If line support is working, then you can remove that and incoming calls would still work.

1 Like

So I just came up with a patch for the registration which seems to work:

--- a/res/res_pjsip_outbound_registration.c
+++ b/res/res_pjsip_outbound_registration.c
@@ -209,6 +209,9 @@
                                <configOption name="support_outbound">
                                        <synopsis>Enables advertising SIP Outbound support (RFC5626) for outbound REGISTER requests.</synopsis>
+                               <configOption name="user_agent">
+                                       <synopsis>Overrides the User-Agent-Header that should be used for outbound REGISTER requests.</synopsis>
+                               </configOption>
@@ -341,6 +344,8 @@ struct sip_outbound_registration {
                /*! \brief Endpoint to use for related incoming calls */
+               /*! \brief User-Agent to use when sending the REGISTER-request */
+               AST_STRING_FIELD(user_agent);
        /*! \brief Requested expiration time */
        unsigned int expiration;
@@ -431,6 +436,8 @@ struct sip_outbound_registration_client_state {
        char *registration_name;
        /*! \brief Expected time of registration lapse/expiration */
        unsigned int registration_expires;
+       /*! \brief The value for the User-Agent header sent in requests */
+       char *user_agent;

 /*! \brief Outbound registration state information (persists for lifetime that registration should exist) */
@@ -726,6 +733,26 @@ static pj_status_t registration_client_send(struct sip_outbound_registration_cli
        add_security_headers(client_state, tdata);

+        * Replace the User-Agent-header if a different one should be used
+        */
+       if(!ast_strlen_zero(client_state->user_agent)) {
+               static const pj_str_t user_agent_str = { "User-Agent", 10 };
+               pjsip_generic_string_hdr *user_agent_hdr;
+               pj_str_t user_agent_val;
+               user_agent_hdr = pjsip_msg_find_hdr_by_name(tdata->msg, &user_agent_str, NULL);
+               if(user_agent_hdr) {
+                       pj_list_erase(user_agent_hdr);
+               }
+               user_agent_val = pj_str(client_state->user_agent);
+               user_agent_hdr = pjsip_generic_string_hdr_create(tdata->pool, &user_agent_str, &user_agent_val);
+               if (!user_agent_hdr) {
+                       ast_log(LOG_ERROR, "Failed to create User-Agent header\n");
+               } else {
+                       pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *)user_agent_hdr);
+               }
+       }
+       /*
         * Set the transport in case transports were reloaded.
         * When pjproject removes the extraneous error messages produced,
         * we can check status and only set the transport and resend if there was an error
@@ -1523,6 +1550,7 @@ static struct sip_outbound_registration_state *sip_outbound_registration_state_a
        state->client_state->transport_name = ast_strdup(registration->transport);
        state->client_state->registration_name =
+       state->client_state->user_agent = ast_strdup(registration->user_agent);

        ast_statsd_log_string("PJSIP.registrations.count", AST_STATSD_GAUGE, "+1", 1.0);
        ast_statsd_log_string_va("PJSIP.registrations.state.%s", AST_STATSD_GAUGE, "+1", 1.0,
@@ -2759,6 +2787,7 @@ static int load_module(void)
        ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "registration", "security_mechanisms", "", security_mechanisms_handler, NULL, NULL, 0, 0);
        ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "line", "no", OPT_BOOL_T, 1, FLDSET(struct sip_outbound_registration, line));
        ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "endpoint", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct sip_outbound_registration, endpoint));
+       ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "user_agent", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct sip_outbound_registration, user_agent));

         * Register sorcery observers.

Can someone please have a look if this is correct? It seems to work fine, but maybe there is still a mistake/issue with it that I should fix before I implement something similar for endpoint and aor?

An issue with the patch was created at [ASTERISK-30288] Implement option to override User-Agent-Header on a per-registration basis - Digium/Asterisk JIRA