Merge "sip_to_pjsip: Migrate IPv4/IPv6 (Dual Stack) configurations."

changes/70/3770/4
Joshua Colp 9 years ago committed by Gerrit Code Review
commit cc26efece3

@ -1,10 +1,11 @@
#!/usr/bin/python
import optparse
import socket
import urlparse # Python 2.7 required for Literal IPv6 Addresses
import astdicts
import astconfigparser
import socket
import re
PREFIX = 'pjsip_'
@ -213,37 +214,42 @@ def from_progressinband(key, val, section, pjsip, nmapped):
set_value('inband_progress', val, section, pjsip, nmapped)
def build_host(config, host, section, port_key):
def build_host(config, host, section='general', port_key=None):
"""
Returns a string composed of a host:port. This assumes that the host
may have a port as part of the initial value. The port_key is only used
if the host does not already have a port set on it.
Throws a LookupError if the key does not exist
may have a port as part of the initial value. The port_key overrides
a port in host, see parameter 'bindport' in chan_sip.
"""
port = None
try:
socket.inet_pton(socket.AF_INET6, host)
if not host.startswith('['):
# SIP URI will need brackets.
host = '[' + host + ']'
else:
# If brackets are present, there may be a port as well
port = re.match('\[.*\]:(\d+)', host)
except socket.error:
# No biggie. It's just not an IPv6 address
port = re.match('.*:(\d+)', host)
pass
result = host
# Literal IPv6 (like [::]), IPv4, or hostname
# does not work for IPv6 without brackets; case catched above
url = urlparse.urlparse('sip://' + host)
if not port:
if port_key:
try:
port = config.get(section, port_key)[0]
result += ':' + port
host = url.hostname # no port, but perhaps no brackets
try:
socket.inet_pton(socket.AF_INET6, host)
if not host.startswith('['):
# SIP URI will need brackets.
host = '[' + host + ']'
except socket.error:
pass
return host + ':' + port
except LookupError:
pass
return result
# Returns host:port, in brackets if required
# TODO Does not compress IPv6, for example 0:0:0:0:0:0:0:0 should get [::]
return url.netloc
def from_host(key, val, section, pjsip, nmapped):
@ -506,17 +512,59 @@ peer_map = [
]
def set_transport_common(section, pjsip, nmapped):
def split_hostport(addr):
"""
Given an address in the form 'host:port' separate the host and port
components.
Returns a two-tuple of strings, (host, port). If no port is present in the
string, then the port section of the tuple is None.
"""
try:
socket.inet_pton(socket.AF_INET6, addr)
if not addr.startswith('['):
return (addr, None)
except socket.error:
pass
# Literal IPv6 (like [::]), IPv4, or hostname
# does not work for IPv6 without brackets; case catched above
url = urlparse.urlparse('sip://' + addr)
# TODO Does not compress IPv6, for example 0:0:0:0:0:0:0:0 should get [::]
return (url.hostname, url.port)
def set_transport_common(section, sip, pjsip, protocol, nmapped):
"""
sip.conf has several global settings that in pjsip.conf apply to individual
transports. This function adds these global settings to each individual
transport.
The settings included are:
externaddr (or externip)
externhost
externtcpport for TCP
externtlsport for TLS
localnet
tos_sip
cos_sip
"""
try:
extern_addr = sip.multi_get('general', ['externaddr', 'externip',
'externhost'])[0]
host, port = split_hostport(extern_addr)
try:
port = sip.get('general', 'extern' + protocol + 'port')[0]
except LookupError:
pass
set_value('external_media_address', host, section, pjsip,
nmapped, 'transport')
set_value('external_signaling_address', host, section, pjsip,
nmapped, 'transport')
if port:
set_value('external_signaling_port', port, section, pjsip,
nmapped, 'transport')
except LookupError:
pass
try:
merge_value('localnet', sip.get('general', 'localnet')[0], 'general',
@ -538,34 +586,69 @@ def set_transport_common(section, pjsip, nmapped):
pass
def split_hostport(addr):
def get_bind(sip, pjsip, protocol):
"""
Given an address in the form 'addr:port' separate the addr and port
components.
Returns a two-tuple of strings, (addr, port). If no port is present in the
string, then the port section of the tuple is None.
Given the protocol (udp, tcp, or tls), return
- the bind address, like [::] or 0.0.0.0
- name of the section to be created
"""
section = 'transport-' + protocol
# UDP cannot be disabled in chan_sip
if protocol != 'udp':
try:
enabled = sip.get('general', protocol + 'enable')[0]
except LookupError:
# No value means disabled by default. Don't create this transport
return (None, section)
if enabled != 'yes':
return (None, section)
try:
socket.inet_pton(socket.AF_INET6, addr)
if not addr.startswith('['):
return (addr, None)
else:
# If brackets are present, there may be a port as well
match = re.match('\[(.*\)]:(\d+)', addr)
if match:
return (match.group(1), match.group(2))
else:
return (addr, None)
except socket.error:
bind = pjsip.get(section, 'bind')[0]
# The first run created an transport already but this
# server was not configured for IPv4/IPv6 Dual Stack
return (None, section)
except LookupError:
pass
# IPv4 address or hostname
host, sep, port = addr.rpartition(':')
if not sep and not port:
return (host, None)
try:
bind = pjsip.get(section + '6', 'bind')[0]
# The first run created an IPv6 transport, because
# the server was configured with :: as bindaddr.
# Now, re-use its port and create the IPv4 transport
host, port = split_hostport(bind)
bind = '0.0.0.0'
if port:
bind += ':' + str(port)
except LookupError:
# This is the first run, no transport in pjsip exists.
try:
bind = sip.get('general', protocol + 'bindaddr')[0]
except LookupError:
if protocol == 'udp':
try:
bind = sip.get('general', 'bindaddr')[0]
except LookupError:
bind = '0.0.0.0'
else:
try:
bind = pjsip.get('transport-udp6', 'bind')[0]
except LookupError:
bind = pjsip.get('transport-udp', 'bind')[0]
# Only TCP reuses host:port of UDP, others reuse just host
if protocol == 'tls':
bind, port = split_hostport(bind)
host, port = split_hostport(bind)
if host == '::':
section += '6'
if protocol == 'udp':
host = build_host(sip, bind, 'general', 'bindport')
else:
return (host, port)
host = build_host(sip, bind)
return (host, section)
def create_udp(sip, pjsip, nmapped):
@ -575,34 +658,13 @@ def create_udp(sip, pjsip, nmapped):
bindaddr (or udpbindaddr)
bindport
externaddr (or externip)
externhost
"""
protocol = 'udp'
bind, section = get_bind(sip, pjsip, protocol)
try:
bind = sip.multi_get('general', ['udpbindaddr', 'bindaddr'])[0]
except LookupError:
bind = ''
bind = build_host(sip, bind, 'general', 'bindport')
try:
extern_addr = sip.multi_get('general', ['externaddr', 'externip',
'externhost'])[0]
host, port = split_hostport(extern_addr)
set_value('external_media_address', host, 'transport-udp', pjsip,
nmapped, 'transport')
set_value('external_signaling_address', host, 'transport-udp', pjsip,
nmapped, 'transport')
if port:
set_value('external_signaling_port', port, 'transport-udp', pjsip,
nmapped, 'transport')
except LookupError:
pass
set_value('protocol', 'udp', 'transport-udp', pjsip, nmapped, 'transport')
set_value('bind', bind, 'transport-udp', pjsip, nmapped, 'transport')
set_transport_common('transport-udp', pjsip, nmapped)
set_value('protocol', protocol, section, pjsip, nmapped, 'transport')
set_value('bind', bind, section, pjsip, nmapped, 'transport')
set_transport_common(section, sip, pjsip, protocol, nmapped)
def create_tcp(sip, pjsip, nmapped):
@ -611,131 +673,61 @@ def create_tcp(sip, pjsip, nmapped):
on the following settings from sip.conf:
tcpenable
tcpbindaddr
externtcpport
tcpbindaddr (or bindaddr)
"""
try:
enabled = sip.get('general', 'tcpenable')[0]
except:
# No value means disabled by default. No need for a tranport
return
if enabled == 'no':
protocol = 'tcp'
bind, section = get_bind(sip, pjsip, protocol)
if not bind:
return
try:
bind = sip.get('general', 'tcpbindaddr')[0]
bind = build_host(sip, bind, 'general', 'bindport')
except LookupError:
# No tcpbindaddr means to default to the udpbindaddr
bind = pjsip.get('transport-udp', 'bind')[0]
try:
extern_addr = sip.multi_get('general', ['externaddr', 'externip',
'externhost'])[0]
host, port = split_hostport(extern_addr)
try:
tcpport = sip.get('general', 'externtcpport')[0]
except:
tcpport = port
set_value('external_media_address', host, 'transport-tcp', pjsip,
nmapped, 'transport')
set_value('external_signaling_address', host, 'transport-tcp', pjsip,
nmapped, 'transport')
if tcpport:
set_value('external_signaling_port', tcpport, 'transport-tcp',
pjsip, nmapped, 'transport')
except LookupError:
pass
set_value('protocol', 'tcp', 'transport-tcp', pjsip, nmapped, 'transport')
set_value('bind', bind, 'transport-tcp', pjsip, nmapped, 'transport')
set_transport_common('transport-tcp', pjsip, nmapped)
def set_tls_bindaddr(val, pjsip, nmapped):
"""
Creates the TCP bind address. This has two possible methods of
working:
Use the 'tlsbindaddr' option from sip.conf directly if it has both
an address and port. If no port is present, use 5061
If there is no 'tlsbindaddr' option present in sip.conf, use the
previously-established UDP bind address and port 5061
"""
try:
bind = sip.get('general', 'tlsbindaddr')[0]
explicit = True
except LookupError:
# No tlsbindaddr means to default to the bindaddr but with standard TLS
# port
bind = pjsip.get('transport-udp', 'bind')[0]
explicit = False
matchv4 = re.match('\d+\.\d+\.\d+\.\d+:\d+', bind)
matchv6 = re.match('\[.*\]:d+', bind)
if matchv4 or matchv6:
if explicit:
# They provided a port. We'll just use it.
set_value('bind', bind, 'transport-tls', pjsip, nmapped,
'transport')
return
else:
# Need to strip the port from the UDP address
index = bind.rfind(':')
bind = bind[:index]
# Reaching this point means either there was no port provided or we
# stripped the port off. We need to add on the default 5061 port
bind += ':5061'
set_value('protocol', protocol, section, pjsip, nmapped, 'transport')
set_value('bind', bind, section, pjsip, nmapped, 'transport')
set_transport_common(section, sip, pjsip, protocol, nmapped)
set_value('bind', bind, 'transport-tls', pjsip, nmapped, 'transport')
def set_tls_cert_file(val, pjsip, nmapped):
def set_tls_cert_file(val, pjsip, section, nmapped):
"""Sets cert_file based on sip.conf tlscertfile"""
set_value('cert_file', val, 'transport-tls', pjsip, nmapped,
set_value('cert_file', val, section, pjsip, nmapped,
'transport')
def set_tls_private_key(val, pjsip, nmapped):
def set_tls_private_key(val, pjsip, section, nmapped):
"""Sets privkey_file based on sip.conf tlsprivatekey or sslprivatekey"""
set_value('priv_key_file', val, 'transport-tls', pjsip, nmapped,
set_value('priv_key_file', val, section, pjsip, nmapped,
'transport')
def set_tls_cipher(val, pjsip, nmapped):
def set_tls_cipher(val, pjsip, section, nmapped):
"""Sets cipher based on sip.conf tlscipher or sslcipher"""
set_value('cipher', val, 'transport-tls', pjsip, nmapped, 'transport')
set_value('cipher', val, section, pjsip, nmapped, 'transport')
def set_tls_cafile(val, pjsip, nmapped):
def set_tls_cafile(val, pjsip, section, nmapped):
"""Sets ca_list_file based on sip.conf tlscafile"""
set_value('ca_list_file', val, 'transport-tls', pjsip, nmapped,
set_value('ca_list_file', val, section, pjsip, nmapped,
'transport')
def set_tls_capath(val, pjsip, nmapped):
def set_tls_capath(val, pjsip, section, nmapped):
"""Sets ca_list_path based on sip.conf tlscapath"""
set_value('ca_list_path', val, 'transport-tls', pjsip, nmapped,
set_value('ca_list_path', val, section, pjsip, nmapped,
'transport')
def set_tls_verifyclient(val, pjsip, nmapped):
def set_tls_verifyclient(val, pjsip, section, nmapped):
"""Sets verify_client based on sip.conf tlsverifyclient"""
set_value('verify_client', val, 'transport-tls', pjsip, nmapped,
set_value('verify_client', val, section, pjsip, nmapped,
'transport')
def set_tls_verifyserver(val, pjsip, nmapped):
def set_tls_verifyserver(val, pjsip, section, nmapped):
"""Sets verify_server based on sip.conf tlsdontverifyserver"""
if val == 'no':
set_value('verify_server', 'yes', 'transport-tls', pjsip, nmapped,
set_value('verify_server', 'yes', section, pjsip, nmapped,
'transport')
else:
set_value('verify_server', 'no', 'transport-tls', pjsip, nmapped,
set_value('verify_server', 'no', section, pjsip, nmapped,
'transport')
@ -745,7 +737,7 @@ def create_tls(sip, pjsip, nmapped):
settings from sip.conf:
tlsenable (or sslenable)
tlsbindaddr (or sslbindaddr)
tlsbindaddr (or sslbindaddr or bindaddr)
tlsprivatekey (or sslprivatekey)
tlscipher (or sslcipher)
tlscafile
@ -755,9 +747,16 @@ def create_tls(sip, pjsip, nmapped):
tlsdontverifyserver
tlsclientmethod (or sslclientmethod)
"""
protocol = 'tls'
bind, section = get_bind(sip, pjsip, protocol)
if not bind:
return
set_value('protocol', protocol, section, pjsip, nmapped, 'transport')
set_value('bind', bind, section, pjsip, nmapped, 'transport')
set_transport_common(section, sip, pjsip, protocol, nmapped)
tls_map = [
(['tlsbindaddr', 'sslbindaddr'], set_tls_bindaddr),
(['tlscertfile', 'sslcert', 'tlscert'], set_tls_cert_file),
(['tlsprivatekey', 'sslprivatekey'], set_tls_private_key),
(['tlscipher', 'sslcipher'], set_tls_cipher),
@ -767,26 +766,21 @@ def create_tls(sip, pjsip, nmapped):
(['tlsdontverifyserver'], set_tls_verifyserver)
]
try:
enabled = sip.multi_get('general', ['tlsenable', 'sslenable'])[0]
except LookupError:
# Not enabled. Don't create a transport
return
if enabled == 'no':
return
set_value('protocol', 'tls', 'transport-tls', pjsip, nmapped, 'transport')
for i in tls_map:
try:
i[1](sip.multi_get('general', i[0])[0], pjsip, nmapped)
i[1](sip.multi_get('general', i[0])[0], pjsip, section, nmapped)
except LookupError:
pass
try:
method = sip.multi_get('general', ['tlsclientmethod', 'sslclientmethod'])[0]
print 'In chan_sip, you specified the TLS version. With chan_sip, this was just for outbound client connections. In chan_pjsip, this value is for client and server. Instead, consider not to specify \'tlsclientmethod\' for chan_sip and \'method = sslv23\' for chan_pjsip.'
method = sip.multi_get('general', ['tlsclientmethod',
'sslclientmethod'])[0]
if section != 'transport-' + protocol + '6': # print only once
print 'In chan_sip, you specified the TLS version. With chan_sip,' \
' this was just for outbound client connections. In' \
' chan_pjsip, this value is for client and server. Instead,' \
' consider not to specify \'tlsclientmethod\' for chan_sip' \
' and \'method = sslv23\' for chan_pjsip.'
except LookupError:
"""
OpenSSL emerged during the 90s. SSLv2 and SSLv3 were the only
@ -799,26 +793,7 @@ def create_tls(sip, pjsip, nmapped):
'sslv23' as default when unspecified, which gives TLSv1.0 and v1.2.
"""
method = 'sslv23'
set_value('method', method, 'transport-tls', pjsip, nmapped, 'transport')
set_transport_common('transport-tls', pjsip, nmapped)
try:
extern_addr = sip.multi_get('general', ['externaddr', 'externip',
'externhost'])[0]
host, port = split_hostport(extern_addr)
try:
tlsport = sip.get('general', 'externtlsport')[0]
except:
tlsport = port
set_value('external_media_address', host, 'transport-tls', pjsip,
nmapped, 'transport')
set_value('external_signaling_address', host, 'transport-tls', pjsip,
nmapped, 'transport')
if tlsport:
set_value('external_signaling_port', tlsport, 'transport-tls',
pjsip, nmapped, 'transport')
except LookupError:
pass
set_value('method', method, section, pjsip, nmapped, 'transport')
def map_transports(sip, pjsip, nmapped):
@ -828,23 +803,29 @@ def map_transports(sip, pjsip, nmapped):
configuration sections in pjsip.conf.
sip.conf only allows a single UDP transport, TCP transport,
and TLS transport. As such, the mapping into PJSIP can be made
consistent by defining three sections:
and TLS transport for each IP version. As such, the mapping
into PJSIP can be made consistent by defining six sections:
transport-udp6
transport-udp
transport-tcp6
transport-tcp
transport-tls6
transport-tls
To accommodate the default behaviors in sip.conf, we'll need to
create the UDP transport first, followed by the TCP and TLS transports.
create the UDP transports first, followed by the TCP and TLS transports.
"""
# First create a UDP transport. Even if no bind parameters were provided
# in sip.conf, chan_sip would always bind to UDP 0.0.0.0:5060
create_udp(sip, pjsip, nmapped)
create_udp(sip, pjsip, nmapped)
# TCP settings may be dependent on UDP settings, so do it second.
create_tcp(sip, pjsip, nmapped)
create_tcp(sip, pjsip, nmapped)
create_tls(sip, pjsip, nmapped)
create_tls(sip, pjsip, nmapped)
@ -1219,6 +1200,8 @@ if __name__ == "__main__":
sip_filename, pjsip_filename = cli_options()
# configuration parser for sip.conf
sip = astconfigparser.MultiOrderedConfigParser()
print 'Please, report any issue at:'
print ' https://issues.asterisk.org/'
print 'Reading', sip_filename
sip.read(sip_filename)
print 'Converting to PJSIP...'

Loading…
Cancel
Save