diff --git a/libsofia-sip-ua/tport/sofia-sip/tport_tag.h b/libsofia-sip-ua/tport/sofia-sip/tport_tag.h index 4d19e5da..3ee36d43 100644 --- a/libsofia-sip-ua/tport/sofia-sip/tport_tag.h +++ b/libsofia-sip-ua/tport/sofia-sip/tport_tag.h @@ -148,6 +148,14 @@ TPORT_DLL extern tag_typedef_t tptag_socket_keepalive; TPORT_DLL extern tag_typedef_t tptag_socket_keepalive_ref; #define TPTAG_SOCKET_KEEPALIVE_REF(x) tptag_socket_keepalive_ref, tag_uint_vr(&(x)) +#if defined (__linux__) +TPORT_DLL extern tag_typedef_t tptag_socket_bind_ifc; +#define TPTAG_SOCKET_BIND_IFC(x) tptag_socket_bind_ifc, tag_bool_v((x)) + +TPORT_DLL extern tag_typedef_t tptag_socket_bind_ifc_ref; +#define TPTAG_SOCKET_BIND_IFC_REF(x) tptag_socket_bind_ifc_ref, tag_bool_vr(&(x)) +#endif + TPORT_DLL extern tag_typedef_t tptag_keepalive; #define TPTAG_KEEPALIVE(x) tptag_keepalive, tag_uint_v((x)) diff --git a/libsofia-sip-ua/tport/tport.c b/libsofia-sip-ua/tport/tport.c index 29323315..3d9bb34c 100644 --- a/libsofia-sip-ua/tport/tport.c +++ b/libsofia-sip-ua/tport/tport.c @@ -71,7 +71,7 @@ typedef struct tport_nat_s tport_nat_t; #include #include "tport_internal.h" -#if defined (__linux__) +#if HAVE_GETIFADDRS && defined (__linux__) #include #if HAVE_NET_IF_H #include @@ -520,6 +520,9 @@ tport_t *tport_tcreate(tp_stack_t *stack, tpp->tpp_timeout = UINT_MAX; tpp->tpp_sigcomp_lifetime = UINT_MAX; tpp->tpp_socket_keepalive = 30; + #if defined (__linux__) + tpp->tpp_socket_bind_ifc = 1; + #endif tpp->tpp_keepalive = 0; tpp->tpp_pingpong = 0; tpp->tpp_pong2ping = 0; @@ -803,53 +806,51 @@ int tport_bind_socket(int socket, } } #endif -#if defined(__linux__) - if (tport_bind_socket_iface(socket, su, ai) < 0) { - return -1; - } -#endif + return 0; } -#if defined(__linux__) +#if HAVE_GETIFADDRS && defined (__linux__) int tport_bind_socket_iface(int s, - su_sockaddr_t *su, - su_addrinfo_t *ai) + su_addrinfo_t *ai, + char const **return_culprit) { + su_sockaddr_t *su = (su_sockaddr_t *)ai->ai_addr; struct ifaddrs *addrs, *iap; struct sockaddr_in *sa; struct ifreq ifr; char ipaddr[SU_ADDRSIZE + 2]; - getifaddrs(&addrs); - for (iap = addrs; iap != NULL; iap = iap->ifa_next) { - if (iap->ifa_addr && (iap->ifa_flags & IFF_UP) && iap->ifa_addr->sa_family == su->su_family) { - sa = (struct sockaddr_in *)(iap->ifa_addr); - if(sa->sin_addr.s_addr == su->su_sin.sin_addr.s_addr) { - memset(&ifr, 0, sizeof(struct ifreq)); - strncpy(ifr.ifr_name, (char const *) iap->ifa_name, IFNAMSIZ); - - /* Assign socket to an already active access point (interface) */ - ioctl(s, SIOCSIFNAME, &ifr); - if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(ifr)) < 0) { - SU_DEBUG_3(("socket: %d setsockopt(SO_BINDTODEVICE) error binding to ifc %s: %s\n", - s, ifr.ifr_name, su_strerror(su_errno()))); - freeifaddrs(addrs); - return -1; + if (getifaddrs(&addrs) == 0) { + for (iap = addrs; iap != NULL; iap = iap->ifa_next) { + if (iap->ifa_addr && (iap->ifa_flags & IFF_UP) && iap->ifa_addr->sa_family == su->su_family) { + sa = (struct sockaddr_in *)(iap->ifa_addr); + if(sa->sin_addr.s_addr == su->su_sin.sin_addr.s_addr) { + memset(&ifr, 0, sizeof(struct ifreq)); + strncpy(ifr.ifr_name, (char const *) iap->ifa_name, IFNAMSIZ); + + /* Assign socket to an already active access point (interface) */ + ioctl(s, SIOCSIFNAME, &ifr); + if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(ifr)) < 0) { + SU_DEBUG_3(("socket: %d setsockopt(SO_BINDTODEVICE) error binding to ifc %s: %s\n", + s, ifr.ifr_name, su_strerror(su_errno()))); + freeifaddrs(addrs); + return *return_culprit = "setsockopt", -1; + } + SU_DEBUG_9(("socket: %d, bound %s to ifc: %s\n", s, + su_inet_ntop(su->su_family, SU_ADDR(su), ipaddr, sizeof(ipaddr)), + ifr.ifr_name)); + freeifaddrs(addrs); + return 0; } - SU_DEBUG_9(("socket: %d, bound %s to ifc: %s\n", s, - su_inet_ntop(su->su_family, SU_ADDR(su), ipaddr, sizeof(ipaddr)), - ifr.ifr_name)); - freeifaddrs(addrs); - return 0; } } - } - freeifaddrs(addrs); + freeifaddrs(addrs); - SU_DEBUG_3(("socket: %d: did not find ifc to bind %s\n", - s, su_inet_ntop(su->su_family, SU_ADDR(su), ipaddr, sizeof(ipaddr)))); - /* Technically it's not a "failure" */ + SU_DEBUG_3(("socket: %d: did not find ifc to bind %s\n", + s, su_inet_ntop(su->su_family, SU_ADDR(su), ipaddr, sizeof(ipaddr)))); + /* Technically it's not a "failure" */ + } return 0; } #endif @@ -1276,6 +1277,9 @@ int tport_get_params(tport_t const *self, TPTAG_IDLE(tpp->tpp_idle), TPTAG_TIMEOUT(tpp->tpp_timeout), TPTAG_SOCKET_KEEPALIVE(tpp->tpp_socket_keepalive), +#if defined (__linux__) + TPTAG_SOCKET_BIND_IFC(tpp->tpp_socket_bind_ifc), +#endif TPTAG_KEEPALIVE(tpp->tpp_keepalive), TPTAG_PINGPONG(tpp->tpp_pingpong), TPTAG_PONG2PING(tpp->tpp_pong2ping), @@ -1321,6 +1325,9 @@ int tport_set_params(tport_t *self, usize_t mtu; int connect, sdwn_error, reusable, stun_server, pong2ping; + #if defined (__linux__) + int socket_ifc; + #endif if (self == NULL) return su_seterrno(EINVAL); @@ -1333,6 +1340,7 @@ int tport_set_params(tport_t *self, reusable = self->tp_reusable; stun_server = tpp->tpp_stun_server; pong2ping = tpp->tpp_pong2ping; + socket_ifc = tpp->tpp_socket_bind_ifc; ta_start(ta, tag, value); @@ -1342,6 +1350,9 @@ int tport_set_params(tport_t *self, TPTAG_IDLE_REF(tpp->tpp_idle), TPTAG_TIMEOUT_REF(tpp->tpp_timeout), TPTAG_SOCKET_KEEPALIVE_REF(tpp->tpp_socket_keepalive), +#if defined (__linux__) + TPTAG_SOCKET_BIND_IFC_REF(socket_ifc), +#endif TPTAG_KEEPALIVE_REF(tpp->tpp_keepalive), TPTAG_PINGPONG_REF(tpp->tpp_pingpong), TPTAG_PONG2PING_REF(pong2ping), diff --git a/libsofia-sip-ua/tport/tport_internal.h b/libsofia-sip-ua/tport/tport_internal.h index db5f39cf..05033e47 100644 --- a/libsofia-sip-ua/tport/tport_internal.h +++ b/libsofia-sip-ua/tport/tport_internal.h @@ -123,6 +123,9 @@ typedef struct { unsigned tpp_sdwn_error:1; /**< If true, shutdown is error. */ unsigned tpp_stun_server:1; /**< If true, use stun server */ unsigned tpp_pong2ping:1; /**< If true, respond with pong to ping */ + #if defined (__linux__) + unsigned tpp_socket_bind_ifc:1; /**< If true, force socket bind to the interface */ + #endif unsigned :0; @@ -440,10 +443,10 @@ void tport_base_timer(tport_t *self, su_time_t now); int tport_bind_socket(int socket, su_addrinfo_t *ai, char const **return_culprit); -#if defined(__linux__) +#if HAVE_GETIFADDRS && defined(__linux__) int tport_bind_socket_iface(int s, - su_sockaddr_t *su, - su_addrinfo_t *ai); + su_addrinfo_t *ai, + char const **return_culprit); #endif void tport_close(tport_t *self); int tport_shutdown0(tport_t *self, int how); diff --git a/libsofia-sip-ua/tport/tport_tag.c b/libsofia-sip-ua/tport/tport_tag.c index a430f949..75dd524d 100644 --- a/libsofia-sip-ua/tport/tport_tag.c +++ b/libsofia-sip-ua/tport/tport_tag.c @@ -247,6 +247,21 @@ tag_typedef_t tptag_pingpong = UINTTAG_TYPEDEF(pingpong); */ tag_typedef_t tptag_pong2ping = BOOLTAG_TYPEDEF(pong2ping); +#if defined (__linux__) +/**@def TPTAG_SOCKET_BIND_IFC(x) + * + * Explicit socket binding to network interface (Linux only). + * + * If true, perform a setsockopt() SO_BINDTODEVICE. + * + * This is to get around an issue in certain Linux kernel whereas in a + * multi-homed environment, a socket might bind to the wrong (primary) + * network interface rather than the intended ifc. + * + */ +tag_typedef_t tptag_socket_bind_ifc = BOOLTAG_TYPEDEF(socket_bind_ifc); +#endif + /**@def TPTAG_SIGCOMP_LIFETIME(x) * * Default SigComp lifetime in seconds. diff --git a/libsofia-sip-ua/tport/tport_type_tcp.c b/libsofia-sip-ua/tport/tport_type_tcp.c index db3a76ac..b65d2936 100644 --- a/libsofia-sip-ua/tport/tport_type_tcp.c +++ b/libsofia-sip-ua/tport/tport_type_tcp.c @@ -138,6 +138,9 @@ int tport_stream_init_primary(tport_primary_t *pri, tagi_t const *tags, char const **return_culprit) { +#if defined (__linux__) + unsigned socket_bind_ifc = 0; +#endif pri->pri_primary->tp_socket = socket; /* Set IP TOS if set */ @@ -152,6 +155,19 @@ int tport_stream_init_primary(tport_primary_t *pri, if (tport_bind_socket(socket, ai, return_culprit) == -1) return -1; +#if HAVE_GETIFADDRS && defined(__linux__) + tl_gets(tags, + TPTAG_SOCKET_BIND_IFC(socket_bind_ifc), + TAG_END()); + /* Force socket binding + * + * On Linux 5.x kernel for multi-homed environment + */ + if (socket_bind_ifc == 1 && tport_bind_socket_iface(socket, ai, return_culprit) == -1) { + return -1; + } +#endif + if (listen(socket, pri->pri_params->tpp_qsize) == SOCKET_ERROR) return *return_culprit = "listen", -1; diff --git a/libsofia-sip-ua/tport/tport_type_udp.c b/libsofia-sip-ua/tport/tport_type_udp.c index 5a1715b3..335d42c1 100644 --- a/libsofia-sip-ua/tport/tport_type_udp.c +++ b/libsofia-sip-ua/tport/tport_type_udp.c @@ -132,6 +132,9 @@ int tport_udp_init_primary(tport_primary_t *pri, su_sockaddr_t *su = (su_sockaddr_t *)ai->ai_addr; #endif int const one = 1; (void)one; +#if defined (__linux__) + unsigned socket_bind_ifc = 0; +#endif s = su_socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (s == INVALID_SOCKET) @@ -142,6 +145,19 @@ int tport_udp_init_primary(tport_primary_t *pri, if (tport_bind_socket(s, ai, return_culprit) < 0) return -1; + #if HAVE_GETIFADDRS && defined(__linux__) + tl_gets(tags, + TPTAG_SOCKET_BIND_IFC(socket_bind_ifc), + TAG_END()); + /* Force socket binding + * + * On Linux 5.x kernel for multi-homed environment + */ + if (socket_bind_ifc == 1 && tport_bind_socket_iface(socket, ai, return_culprit) == -1) { + return -1; + } +#endif + tport_set_tos(s, ai, pri->pri_params->tpp_tos); #if HAVE_IP_ADD_MEMBERSHIP