Skip to content

Commit

Permalink
Implement more granular control over port number allocation
Browse files Browse the repository at this point in the history
which can be achieved by passing a custom port allocator,
a callable function taking number of try as an argument and
returning next port candidate to try.

Provide a reference implementation of the RTP port allocator,
which takes range of ports and allocates even numbers in this
range.
  • Loading branch information
sobomax committed Jul 19, 2024
1 parent e1e9d0c commit 524a12b
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 15 deletions.
26 changes: 24 additions & 2 deletions sippy/Network_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@
from typing import Optional, Tuple, List, Union
from queue import Queue
from abc import ABC, abstractmethod
from random import random
from random import random, randint

from sippy.Core.Exceptions import dump_exception
from sippy.Time.MonoTime import MonoTime
from sippy.Time.Timeout import Timeout
from sippy.SipConf import MyPort

class Remote_address():
transport: str
Expand All @@ -47,7 +48,7 @@ def __str__(self):
return f'{self.transport}:{self.address[0]}:{self.address[1]}'

class Network_server_opts():
laddress: Optional[Tuple[str, int]] = None
laddress: Optional[Tuple[str, Union[int, callable, MyPort]]] = None
data_callback: Optional[callable] = None
direct_dispatch: bool = False
ploss_out_rate: float = 0.0
Expand Down Expand Up @@ -124,3 +125,24 @@ def shutdown(self):
self.sendqueue.put(None)
self.join()
self.uopts.data_callback = None

class PortAllocationError(Exception): pass

class RTP_port_allocator():
min_port: int
max_port: int

def __init__(self, min_port:int = 1024, max_port:int = 65535):
if min_port % 2 != 0:
min_port += 1
assert min_port <= max_port, f'min_port={min_port} > max_port={max_port}'
self.min_port = min_port
self.max_port = max_port

def __call__(self, ntry: int) -> int:
rlen = self.max_port - self.min_port
if ntry > (rlen // 2):
raise PortAllocationError(f'No free ports available after {ntry} tries')
port = self.min_port + (randint(0, rlen // 2) * 2)
assert port <= self.max_port, f'port={port} > self.max_port={self.max_port}'
return port
38 changes: 25 additions & 13 deletions sippy/Udp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class BrokenPipeError(Exception):
pass

from errno import ECONNRESET, ENOTCONN, ESHUTDOWN, EWOULDBLOCK, ENOBUFS, EAGAIN, \
EINTR, EBADF
EINTR, EBADF, EADDRINUSE
from datetime import datetime
from time import sleep, time
from threading import Thread
Expand All @@ -41,7 +41,8 @@ class BrokenPipeError(Exception):

from sippy.Core.EventDispatcher import ED2
from sippy.Core.Exceptions import dump_exception
from sippy.Network_server import Network_server_opts, Network_server, Remote_address
from sippy.Network_server import Network_server_opts, Network_server, Remote_address, \
RTP_port_allocator
from sippy.Time.Timeout import Timeout
from sippy.Time.MonoTime import MonoTime
from sippy.SipConf import MyPort
Expand Down Expand Up @@ -191,7 +192,19 @@ def __init__(self, global_config, uopts):
# TypeError: 'MyPort' object cannot be interpreted as an integer
# might be something inside socket.bind?
address = (address[0], int(address[1]))
self.skt.bind(address)
if not callable(address[1]):
self.skt.bind(address)
else:
ntry = -1
for ntry in iter(lambda: ntry + 1, -1):
try_address = (address[0], address[1](ntry))
try: self.skt.bind(try_address)
except OSError as ex:
if ex.errno != EADDRINUSE:
raise
else:
self.uopts.laddress = try_address
break
if self.uopts.laddress[1] == 0:
self.uopts.laddress = self.skt.getsockname()
self.asenders = []
Expand Down Expand Up @@ -278,28 +291,27 @@ def pong_received(self, data, ra, udp_server, rtime):
ED2.breakLoop()

def run(self):
palloc = RTP_port_allocator()
local_host = '127.0.0.1'
local_host6 = '[::1]'
remote_host = local_host
remote_host6 = local_host6
self.ping_laddr = (local_host, 12345)
self.pong_laddr = (local_host, 54321)
self.ping_laddr6 = (local_host6, 12345)
self.pong_laddr = (local_host, palloc)
self.ping_laddr6 = (local_host6, 0)
self.pong_laddr6 = (local_host6, 54321)
self.ping_raddr = (remote_host, 12345)
self.pong_raddr = (remote_host, 54321)
self.ping_raddr6 = (remote_host6, 12345)
self.pong_raddr6 = (remote_host6, 54321)
uopts_ping = Udp_server_opts(self.ping_laddr, self.ping_received)
uopts_ping6 = Udp_server_opts(self.ping_laddr6, self.ping_received)
uopts_pong = Udp_server_opts(self.pong_laddr, self.pong_received)
uopts_pong6 = Udp_server_opts(self.pong_laddr6, self.pong_received)
udp_server_ping = Udp_server({}, uopts_ping)
udp_server_pong = Udp_server({}, uopts_pong)
udp_server_pong.send_to(self.ping_data, self.ping_laddr)
self.ping_raddr = udp_server_ping.getSIPaddr()[0]
self.pong_raddr = udp_server_pong.getSIPaddr()[0]
udp_server_pong.send_to(self.ping_data, self.ping_raddr)
udp_server_ping6 = Udp_server({}, uopts_ping6)
udp_server_pong6 = Udp_server({}, uopts_pong6)
udp_server_pong6.send_to(self.ping_data6, self.ping_laddr6)
self.ping_raddr6 = udp_server_ping6.getSIPaddr()[0]
self.pong_raddr6 = udp_server_pong6.getSIPaddr()[0]
udp_server_pong6.send_to(self.ping_data6, self.ping_raddr6)
ED2.loop()
udp_server_ping.shutdown()
udp_server_pong.shutdown()
Expand Down

0 comments on commit 524a12b

Please sign in to comment.