Repository: tayler6000/pyVoIP (language: python)

Issue:
pyVoIP 2.0.0 (Development Branch) Not Working
Currently, the development branch isn't really usable.  This issue is simply for tracking everything wrong with it.  If you run into an error using the development branch, please post it here.  Currently working on trying to get INVITE to work again, but I intend to fix everything that broke with #100 


Discussion / hints:

Fix patch:
diff --git a/docker/settings/pjsip.conf b/docker/settings/pjsip.conf
index 9ab5789..be0391e 100644
--- a/docker/settings/pjsip.conf
+++ b/docker/settings/pjsip.conf
@@ -29,7 +29,7 @@ aors=nopass
 
 [nopass]
 type=aor
-max_contacts=1
+max_contacts=999
 
 [pass]
 type=endpoint
@@ -48,4 +48,4 @@ password=Testing123!
 
 [pass]
 type=aor
-max_contacts=1
+max_contacts=999
diff --git a/docker/start.bat b/docker/start.bat
deleted file mode 100644
index 4b36fbf..0000000
--- a/docker/start.bat
+++ /dev/null
@@ -1,4 +0,0 @@
-@echo off
-docker rmi pyvoip/tests
-docker build . -t pyvoip/tests
-docker run -d -p 5060:5060/udp -p 5061-5062:5061-5062/tcp pyvoip/tests
diff --git a/docker/start.ps1 b/docker/start.ps1
new file mode 100644
index 0000000..d97dddf
--- /dev/null
+++ b/docker/start.ps1
@@ -0,0 +1,5 @@
+docker stop $(docker ps -a -q)
+docker rm --force --volumes $(docker ps -a -q)
+docker rmi pyvoip/tests
+docker build . -t pyvoip/tests
+docker run --add-host host.docker.internal:host-gateway -d -p 5060:5060/udp -p 5061-5062:5061-5062/tcp pyvoip/tests
diff --git a/docs/conf.py b/docs/conf.py
index 69e281b..dbbb176 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -19,26 +19,24 @@
 
 # -- Project information -----------------------------------------------------
 
-project = 'pyVoIP'
-copyright = '2023, Tayler Porter'
-author = 'Tayler J Porter'
+project = "pyVoIP"
+copyright = "2023, Tayler Porter"
+author = "Tayler J Porter"
 
 # The full version, including alpha/beta/rc tags
-release = '2.0.0'
+release = "2.0.0"
 
-master_doc = 'index'
+master_doc = "index"
 
 # -- General configuration ---------------------------------------------------
 
 # Add any Sphinx extension module names here, as strings. They can be
 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
 # ones.
-extensions = [ 
-  "sphinx_rtd_theme"
-]
+extensions = ["sphinx_rtd_theme"]
 
 # Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
+templates_path = ["_templates"]
 
 # List of patterns, relative to source directory, that match files and
 # directories to ignore when looking for source files.
@@ -51,11 +49,11 @@
 # The theme to use for HTML and HTML Help pages.  See the documentation for
 # a list of builtin themes.
 #
-html_theme = 'sphinx_rtd_theme'
+html_theme = "sphinx_rtd_theme"
 
-#pygments_style = 'sphinx'
+# pygments_style = 'sphinx'
 
 # Add any paths that contain custom static files (such as style sheets) here,
 # relative to this directory. They are copied after the builtin static files,
 # so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
+html_static_path = ["_static"]
diff --git a/pyVoIP/RTP.py b/pyVoIP/RTP.py
index c146900..ddccf3b 100644
--- a/pyVoIP/RTP.py
+++ b/pyVoIP/RTP.py
@@ -349,8 +349,12 @@ def start(self) -> None:
 
     def stop(self) -> None:
         self.NSD = False
-        self.sin.close()
-        self.sout.close()
+        if hasattr(self, "sin"):
+            if self.sin:
+                self.sin.close()
+        if hasattr(self, "sout"):
+            if self.sout:
+                self.sout.close()
 
     def read(self, length: int = 160, blocking: bool = True) -> bytes:
         if not blocking:
diff --git a/pyVoIP/SIP/client.py b/pyVoIP/SIP/client.py
index 104a7ab..7b73ccd 100644
--- a/pyVoIP/SIP/client.py
+++ b/pyVoIP/SIP/client.py
@@ -7,6 +7,7 @@
     InvalidAccountInfoError,
 )
 from pyVoIP.helpers import Counter
+from pyVoIP.networking.nat import NAT
 from pyVoIP.SIP.message import (
     SIPMessage,
     SIPStatus,
@@ -19,16 +20,23 @@
 import random
 import time
 import uuid
-import select
 
 
 if TYPE_CHECKING:
     from pyVoIP import RTP
+    from pyVoIP.sock import VoIPConnection
 
 
 debug = pyVoIP.debug
 
 
+UNAUTORIZED_RESPONSE_CODES = [
+    SIPStatus.UNAUTHORIZED,
+    SIPStatus.PROXY_AUTHENTICATION_REQUIRED,
+]
+INVITE_OK_RESPONSE_CODES = [SIPStatus.TRYING, SIPStatus.RINGING, SIPStatus.OK]
+
+
 class SIPClient:
     def __init__(
         self,
@@ -37,8 +45,13 @@ def __init__(
         user: str,
         credentials_manager: CredentialsManager,
         bind_ip="0.0.0.0",
+        bind_network="0.0.0.0/0",
+        hostname: Optional[str] = None,
+        remote_hostname: Optional[str] = None,
         bind_port=5060,
-        call_callback: Optional[Callable[[SIPMessage], Optional[str]]] = None,
+        call_callback: Optional[
+            Callable[["VoIPConnection", SIPMessage], Optional[str]]
+        ] = None,
         transport_mode: TransportMode = TransportMode.UDP,
         cert_file: Optional[str] = None,
         key_file: Optional[str] = None,
@@ -49,6 +62,7 @@ def __init__(
         self.port = port
         self.bind_ip = bind_ip
         self.bind_port = bind_port
+        self.nat = NAT(bind_ip, bind_network, hostname, remote_hostname)
         self.user = user
         self.credentials_manager = credentials_manager
         self.transport_mode = transport_mode
@@ -98,9 +112,29 @@ def recv(self) -> None:
                 else:
                     debug(f"SIPParseError in SIP.recv: {type(e)}, {e}")
             except Exception as e:
-                debug(f"SIP.recv error: {type(e)}, {e}\n\n{str(raw, 'utf8')}")
-                if pyVoIP.DEBUG:
-                    raise
+                try:
+                    debug(
+                        f"SIP.recv error: {type(e)}, {e}\n\n{str(raw, 'utf8')}"
+                    )
+                except UnboundLocalError:
+                    debug("SIP.recv error: Unable to recv")
+                finally:
+                    if pyVoIP.DEBUG:
+                        raise
+
+    def handle_new_connection(self, conn: "VoIPConnection") -> None:
+        message = SIPMessage(conn.peak())
+        if message.type == SIPMessageType.REQUEST:
+            if message.method == "INVITE":
+                self._handle_invite(conn)
+
+    def _handle_invite(self, conn: "VoIPConnection") -> None:
+        message = SIPMessage(conn.peak())
+        if self.call_callback is None:
+            request = self.gen_busy(message)
+            conn.send(request)
+        else:
+            self.call_callback(conn, message)
 
     def parse_message(self, message: SIPMessage) -> None:
         if message.type != SIPMessageType.REQUEST:
@@ -125,12 +159,6 @@ def parse_message(self, message: SIPMessage) -> None:
                     "TODO: Add 500 Error on Receiving SIP Response",
                 )
             return
-        elif message.method == "INVITE":
-            if self.call_callback is None:
-                request = self.gen_busy(message)
-                self.sendto(request, message.headers["Via"]["address"])
-            else:
-                self.call_callback(message)
         elif message.method == "BYE":
             # TODO: If callCallback is None, the call doesn't exist, 481
             if self.call_callback:
@@ -170,11 +198,11 @@ def start(self) -> None:
         from pyVoIP.sock.sock import VoIPSocket
 
         self.NSD = True
-        # self.s = socket.socket(socket.AF_INET, self.transport_mode.socket_type)
         self.s = VoIPSocket(
             self.transport_mode,
             self.bind_ip,
             self.bind_port,
+            self,
             self.cert_file,
             self.key_file,
             self.key_password,
@@ -188,9 +216,11 @@ def start(self) -> None:
         self.s.start()
         # TODO: Check if we need to register with a server or proxy.
         self.register()
+        """
         t = Timer(1, self.recv)
         t.name = "SIP Receive"
         t.start()
+        """
 
     def stop(self) -> None:
         self.NSD = False
@@ -207,10 +237,10 @@ def sendto(self, request: str, address=None):
             address = (self.server, self.port)
         return self.s.send(request.encode("utf8"))
 
-    def send(self, request: str):
+    def send(self, request: str) -> "VoIPConnection":
         return self.s.send(request.encode("utf8"))
 
-    def _gen_from_to(
+    def __gen_from_to_via_request(
         self,
         request: SIPMessage,
         hdr: str,
@@ -230,10 +260,82 @@ def _gen_from_to(
 
         if tag:
             return f"{ret} <{uri}>;tag={tag}\r\n"
-        else:
-            return f"{ret} <{uri}>\r\n"
+        return f"{ret} <{uri}>\r\n"
+
+    def __gen_from_to(
+        self,
+        header_type: str,
+        user: str,
+        host: str,
+        method="sip",
+        display_name: Optional[str] = None,
+        password: Optional[str] = None,
+        port=5060,
+        uri_params: Optional[str] = None,
+        header_parms: Optional[str] = None,
+    ) -> str:
+        header_type = header_type.capitalize()
+
+        assert header_type in ["To", "From"], "header_type must be To or From"
+        assert (
+            display_name is None or '"' not in display_name
+        ), f'{display_name=} cannot contain a `"`'
+
+        uri = self.__gen_uri(method, user, host, password, port, uri_params)
+        display_name = f'"{display_name}" ' if display_name else ""
+        header_parms = f"{header_parms}" if header_parms else ""
+        return f"{header_type}: {display_name}<{uri}>{header_parms}\r\n"
 
-    def _gen_user_agent(self) -> str:
+    def __gen_uri(
+        self,
+        method: str,
+        user: str,
+        host: str,
+        password: Optional[str] = None,
+        port=5060,
+        params: Optional[str] = None,
+    ) -> str:
+        method = method.lower()
+
+        assert method in ["sip", "sips"], "method must be sip or sips"
+        assert (
+            type(user) is str and len(user) > 0
+        ), "User must be a non-empty string"
+        assert (
+            type(host) is str and len(host) > 0
+        ), "User must be a non-empty string"
+
+        password = f":{password}" if password else ""
+        port_str = f":{port}" if port != 5060 else ""
+        params = params if params else ""
+        return f"{method}:{user}{password}@{host}{port_str}{params}"
+
+    def __gen_via(self, to: str, branch: str) -> str:
+        # SIP/2.0/ should still be the prefix even if using TLS per RFC 3261
+        # 8.1.1.7, as shown in RFC 5630 6.1
+        return (
+            "Via: "
+            + f"SIP/2.0/{str(self.transport_mode)}"
+            + f" {self.nat.get_host(to)}:{self.bind_port};branch={branch}\r\n"
+        )
+
+    def __gen_contact(
+        self,
+        method: str,
+        user: str,
+        host: str,
+        password: Optional[str] = None,
+        port=5060,
+        uriparams: Optional[str] = None,
+        params: List[str] = [],
+    ) -> str:
+        uri = self.__gen_uri(method, user, host, password, port, uriparams)
+        uri = f"<{uri}>"
+        if params:
+            uri += ";" + (";".join(params))
+        return f"Contact: {uri}\r\n"
+
+    def __gen_user_agent(self) -> str:
         return f"User-Agent: pyVoIP {pyVoIP.__version__}\r\n"
 
     def gen_call_id(self) -> str:
@@ -261,14 +363,16 @@ def gen_sip_version_not_supported(self, request: SIPMessage) -> str:
         response = "SIP/2.0 505 SIP Version Not Supported\r\n"
         response += self._gen_response_via_header(request)
         response += f"From: {request.headers['From']['raw']}\r\n"
-        response += self._gen_from_to(request, "To", self.gen_tag())
+        response += self.__gen_from_to_via_request(
+            request, "To", self.gen_tag()
+        )
         response += f"Call-ID: {request.headers['Call-ID']}\r\n"
         response += (
             f"CSeq: {request.headers['CSeq']['check']} "
             + f"{request.headers['CSeq']['method']}\r\n"
         )
         response += f"Contact: {request.headers['Contact']['raw']}\r\n"
-        response += self._gen_user_agent()
+        response += self.__gen_user_agent()
         response += 'Warning: 399 GS "Unable to accept call"\r\n'
         response += f"Allow: {(', '.join(pyVoIP.SIPCompatibleMethods))}\r\n"
         response += "Content-Length: 0\r\n\r\n"
@@ -308,7 +412,7 @@ def gen_digest(
         password = credentials["password"]
         nonce = request.authentication["nonce"]
         method = request.headers["CSeq"]["method"]
-        uri = f"sip:{self.server};transport={self.transport_mode}"
+        uri = f"sip:{self.server};transport={self.transport_mode}"  # TODO: Fix TLS
         algo = request.authentication.get("algorithm", "md5").lower()
         if algo in ["sha512-256", "sha512-256-sess"]:
             hash_func = self._hash_sha512_256
@@ -430,12 +534,8 @@ def gen_urn_uuid(self) -> str:
 
     def gen_first_request(self, deregister=False) -> str:
         regRequest = f"REGISTER sip:{self.server}:{self.port} SIP/2.0\r\n"
-        regRequest += (
-            "Via: SIP/2.0/"
-            + str(self.transport_mode)
-            + f" {self.bind_ip}:{self.bind_port};"
-            + f"branch={self.gen_branch()};rport\r\n"
-        )
+        regRequest += self.__gen_via(self.server, self.gen_branch())
+
         regRequest += (
             f'From: "{self.user}" '
             + f"<sip:{self.user}@{self.bind_ip}:{self.bind_port}>;tag="
@@ -447,18 +547,20 @@ def gen_first_request(self, deregister=False) -> str:
         )
         regRequest += f"Call-ID: {self.gen_call_id()}\r\n"
         regRequest += f"CSeq: {self.registerCounter.next()} REGISTER\r\n"
-        regRequest += (
-            "Contact: "
-            + f"<sip:{self.user}@{self.bind_ip}:{self.bind_port};"
-            + "transport="
-            + str(self.transport_mode)
-            + ">;+sip.instance="
-            + f'"<urn:uuid:{self.urnUUID}>"\r\n'
+        method = "sips" if self.transport_mode is TransportMode.TLS else "sip"
+        trans_mode = str(self.transport_mode)
+        regRequest += self.__gen_contact(
+            method,
+            self.user,
+            self.nat.get_host(self.server),
+            port=self.bind_port,
+            uriparams=f";transport={trans_mode}",
+            params=[f'+sip.instance="<urn:uuid:{self.urnUUID}>"'],
         )
         regRequest += f'Allow: {(", ".join(pyVoIP.SIPCompatibleMethods))}\r\n'
         regRequest += "Max-Forwards: 70\r\n"
         regRequest += "Allow-Events: org.3gpp.nwinitdereg\r\n"
-        regRequest += self._gen_user_agent()
+        regRequest += self.__gen_user_agent()
         # Supported: 100rel, replaces, from-change, gruu
         regRequest += (
             "Expires: "
@@ -471,12 +573,7 @@ def gen_first_request(self, deregister=False) -> str:
 
     def gen_subscribe(self, response: SIPMessage) -> str:
         subRequest = f"SUBSCRIBE sip:{self.user}@{self.server} SIP/2.0\r\n"
-        subRequest += (
-            "Via: SIP/2.0/"
-            + str(self.transport_mode)
-            + f" {self.bind_ip}:{self.bind_port};"
-            + f"branch={self.gen_branch()};rport\r\n"
-        )
+        subRequest += self.__gen_via(self.server, self.gen_branch())
         subRequest += (
             f'From: "{self.user}" '
             + f"<sip:{self.user}@{self.server}>;tag="
@@ -486,16 +583,18 @@ def gen_subscribe(self, response: SIPMessage) -> str:
         subRequest += f'Call-ID: {response.headers["Call-ID"]}\r\n'
         subRequest += f"CSeq: {self.subscribeCounter.next()} SUBSCRIBE\r\n"
         # TODO: check if transport is needed
-        subRequest += (
-            "Contact: "
-            + f"<sip:{self.user}@{self.bind_ip}:{self.bind_port};"
-            + "transport="
-            + str(self.transport_mode)
-            + ">;+sip.instance="
-            + f'"<urn:uuid:{self.urnUUID}>"\r\n'
+        method = "sips" if self.transport_mode is TransportMode.TLS else "sip"
+        trans_mode = str(self.transport_mode)
+        subRequest += self.__gen_contact(
+            method,
+            self.user,
+            self.nat.get_host(self.server),
+            port=self.bind_port,
+            uriparams=f";transport={trans_mode}",
+            params=[f'+sip.instance="<urn:uuid:{self.urnUUID}>"'],
         )
         subRequest += "Max-Forwards: 70\r\n"
-        subRequest += self._gen_user_agent()
+        subRequest += self.__gen_user_agent()
         subRequest += f"Expires: {self.default_expires * 2}\r\n"
         subRequest += "Event: message-summary\r\n"
         subRequest += "Accept: application/simple-message-summary\r\n"
@@ -506,12 +605,7 @@ def gen_subscribe(self, response: SIPMessage) -> str:
 
     def gen_register(self, request: SIPMessage, deregister=False) -> str:
         regRequest = f"REGISTER sip:{self.server}:{self.port} SIP/2.0\r\n"
-        regRequest += (
-            "Via: SIP/2.0/"
-            + str(self.transport_mode)
-            + f" {self.bind_ip}:{self.bind_port};branch="
-            + f"{self.gen_branch()};rport\r\n"
-        )
+        regRequest += self.__gen_via(self.server, self.gen_branch())
         regRequest += (
             f'From: "{self.user}" '
             + f"<sip:{self.user}@{self.bind_ip}:{self.bind_port}>;tag="
@@ -524,18 +618,20 @@ def gen_register(self, request: SIPMessage, deregister=False) -> str:
         call_id = request.headers.get("Call-ID", self.gen_call_id())
         regRequest += f"Call-ID: {call_id}\r\n"
         regRequest += f"CSeq: {self.registerCounter.next()} REGISTER\r\n"
-        regRequest += (
-            "Contact: "
-            + f"<sip:{self.user}@{self.bind_ip}:{self.bind_port};"
-            + "transport="
-            + str(self.transport_mode)
-            + ">;+sip.instance="
-            + f'"<urn:uuid:{self.urnUUID}>"\r\n'
+        method = "sips" if self.transport_mode is TransportMode.TLS else "sip"
+        trans_mode = str(self.transport_mode)
+        regRequest += self.__gen_contact(
+            method,
+            self.user,
+            self.nat.get_host(self.server),
+            port=self.bind_port,
+            uriparams=f";transport={trans_mode}",
+            params=[f'+sip.instance="<urn:uuid:{self.urnUUID}>"'],
         )
         regRequest += f'Allow: {(", ".join(pyVoIP.SIPCompatibleMethods))}\r\n'
         regRequest += "Max-Forwards: 70\r\n"
         regRequest += "Allow-Events: org.3gpp.nwinitdereg\r\n"
-        regRequest += self._gen_user_agent()
+        regRequest += self.__gen_user_agent()
         regRequest += (
             "Expires: "
             + f"{self.default_expires if not deregister else 0}\r\n"
@@ -550,7 +646,9 @@ def gen_busy(self, request: SIPMessage) -> str:
         response = "SIP/2.0 486 Busy Here\r\n"
         response += self._gen_response_via_header(request)
         response += f"From: {request.headers['From']['raw']}\r\n"
-        response += self._gen_from_to(request, "To", self.gen_tag())
+        response += self.__gen_from_to_via_request(
+            request, "To", self.gen_tag()
+        )
         response += f"Call-ID: {request.headers['Call-ID']}\r\n"
         response += (
             f"CSeq: {request.headers['CSeq']['check']} "
@@ -558,7 +656,7 @@ def gen_busy(self, request: SIPMessage) -> str:
         )
         response += f"Contact: {request.headers['Contact']['raw']}\r\n"
         # TODO: Add Supported
-        response += self._gen_user_agent()
+        response += self.__gen_user_agent()
         response += 'Warning: 399 GS "Unable to accept call"\r\n'
         response += f"Allow: {(', '.join(pyVoIP.SIPCompatibleMethods))}\r\n"
         response += "Content-Length: 0\r\n\r\n"
@@ -569,13 +667,15 @@ def gen_ok(self, request: SIPMessage) -> str:
         okResponse = "SIP/2.0 200 OK\r\n"
         okResponse += self._gen_response_via_header(request)
         okResponse += f"From: {request.headers['From']['raw']}\r\n"
-        okResponse += self._gen_from_to(request, "To", self.gen_tag())
+        okResponse += self.__gen_from_to_via_request(
+            request, "To", self.gen_tag()
+        )
         okResponse += f"Call-ID: {request.headers['Call-ID']}\r\n"
         okResponse += (
             f"CSeq: {request.headers['CSeq']['check']} "
             + f"{request.headers['CSeq']['method']}\r\n"
         )
-        okResponse += self._gen_user_agent()
+        okResponse += self.__gen_user_agent()
         okResponse += f"Allow: {(', '.join(pyVoIP.SIPCompatibleMethods))}\r\n"
         okResponse += "Content-Length: 0\r\n\r\n"
 
@@ -586,7 +686,7 @@ def gen_ringing(self, request: SIPMessage) -> str:
         regRequest = "SIP/2.0 180 Ringing\r\n"
         regRequest += self._gen_response_via_header(request)
         regRequest += f"From: {request.headers['From']['raw']}\r\n"
-        regRequest += self._gen_from_to(request, "To", tag)
+        regRequest += self.__gen_from_to_via_request(request, "To", tag)
         regRequest += f"Call-ID: {request.headers['Call-ID']}\r\n"
         regRequest += (
             f"CSeq: {request.headers['CSeq']['check']} "
@@ -594,7 +694,7 @@ def gen_ringing(self, request: SIPMessage) -> str:
         )
         regRequest += f"Contact: {request.headers['Contact']['raw']}\r\n"
         # TODO: Add Supported
-        regRequest += self._gen_user_agent()
+        regRequest += self.__gen_user_agent()
         regRequest += f"Allow: {(', '.join(pyVoIP.SIPCompatibleMethods))}\r\n"
         regRequest += "Content-Length: 0\r\n\r\n"
 
@@ -639,18 +739,21 @@ def gen_answer(
         regRequest = "SIP/2.0 200 OK\r\n"
         regRequest += self._gen_response_via_header(request)
         regRequest += f"From: {request.headers['From']['raw']}\r\n"
-        regRequest += self._gen_from_to(request, "To", tag)
+        regRequest += self.__gen_from_to_via_request(request, "To", tag)
         regRequest += f"Call-ID: {request.headers['Call-ID']}\r\n"
         regRequest += (
             f"CSeq: {request.headers['CSeq']['check']} "
             + f"{request.headers['CSeq']['method']}\r\n"
         )
-        regRequest += (
-            "Contact: "
-            + f"<sip:{self.user}@{self.bind_ip}:{self.bind_port}>\r\n"
+        method = "sips" if self.transport_mode is TransportMode.TLS else "sip"
+        regRequest += self.__gen_contact(
+            method,
+            self.user,
+            self.nat.get_host(self.server),
+            port=self.bind_port,
         )
         # TODO: Add Supported
-        regRequest += self._gen_user_agent()
+        regRequest += self.__gen_user_agent()
         regRequest += f"Allow: {(', '.join(pyVoIP.SIPCompatibleMethods))}\r\n"
         regRequest += "Content-Type: application/sdp\r\n"
         regRequest += f"Content-Length: {len(body)}\r\n\r\n"
@@ -669,12 +772,12 @@ def gen_invite(
     ) -> str:
         # Generate body first for content length
         body = "v=0\r\n"
-        # TODO: Check IPv4/IPv6
         body += (
-            f"o=pyVoIP {sess_id} {int(sess_id)+2} IN IP4 {self.bind_ip}\r\n"
+            f"o=pyVoIP {sess_id} {int(sess_id)+2} IN IP"
+            + f"{self.nat.bind_ip.version} {self.bind_ip}\r\n"
         )
         body += f"s=pyVoIP {pyVoIP.__version__}\r\n"
-        body += f"c=IN IP4 {self.bind_ip}\r\n"  # TODO: Check IPv4/IPv6
+        body += f"c=IN IP{self.nat.bind_ip.version} {self.bind_ip}\r\n"
         body += "t=0 0\r\n"
         for x in ms:
             # TODO: Check AVP mode from request
@@ -694,25 +797,37 @@ def gen_invite(
         tag = self.gen_tag()
         self.tagLibrary[call_id] = tag
 
-        invRequest = f"INVITE sip:{number}@{self.server} SIP/2.0\r\n"
-        invRequest += (
-            "Via: SIP/2.0/"
-            + str(self.transport_mode)
-            + f" {self.bind_ip}:{self.bind_port};branch="
-            + f"{branch}\r\n"
+        uri_method = (
+            "sips" if self.transport_mode == TransportMode.TLS else "sip"
+        )
+        to_uri = self.__gen_uri(
+            uri_method, number, self.server, port=self.port
         )
+        invRequest = f"INVITE {to_uri} SIP/2.0\r\n"
+        invRequest += self.__gen_via(self.server, branch)
         invRequest += "Max-Forwards: 70\r\n"
-        invRequest += (
-            "Contact: "
-            + f"<sip:{self.user}@{self.bind_ip}:{self.bind_port}>\r\n"
+        method = "sips" if self.transport_mode is TransportMode.TLS else "sip"
+        invRequest += self.__gen_contact(
+            method,
+            self.user,
+            self.nat.get_host(self.server),
+            port=self.bind_port,
+        )
+        invRequest += self.__gen_from_to(
+            "To", number, self.server, port=self.port
+        )
+        invRequest += self.__gen_from_to(
+            "From",
+            self.user,
+            self.nat.get_host(self.server),
+            port=self.bind_port,
+            header_parms=f";tag={tag}",
         )
-        invRequest += f"To: <sip:{number}@{self.server}>\r\n"
-        invRequest += f"From: <sip:{self.user}@{self.bind_ip}>;tag={tag}\r\n"
         invRequest += f"Call-ID: {call_id}\r\n"
         invRequest += f"CSeq: {self.inviteCounter.next()} INVITE\r\n"
         invRequest += f"Allow: {(', '.join(pyVoIP.SIPCompatibleMethods))}\r\n"
         invRequest += "Content-Type: application/sdp\r\n"
-        invRequest += self._gen_user_agent()
+        invRequest += self.__gen_user_agent()
         invRequest += f"Content-Length: {len(body)}\r\n\r\n"
         invRequest += body
 
@@ -726,19 +841,24 @@ def _gen_bye_cancel(self, request: SIPMessage, cmd: str) -> str:
         _from = request.headers["From"]
         to = request.headers["To"]
         if request.headers["From"]["tag"] == tag:
-            byeRequest += self._gen_from_to(request, "From", tag)
+            byeRequest += self.__gen_from_to_via_request(request, "From", tag)
             byeRequest += f"To: {to['raw']}\r\n"
         else:
             byeRequest += f"To: {_from['raw']}\r\n"
-            byeRequest += self._gen_from_to(request, "To", tag, dsthdr="From")
+            byeRequest += self.__gen_from_to_via_request(
+                request, "To", tag, dsthdr="From"
+            )
         byeRequest += f"Call-ID: {request.headers['Call-ID']}\r\n"
         cseq = request.headers["CSeq"]["check"]
         byeRequest += f"CSeq: {cseq} {cmd}\r\n"
-        byeRequest += (
-            "Contact: "
-            + f"<sip:{self.user}@{self.bind_ip}:{self.bind_port}>\r\n"
+        method = "sips" if self.transport_mode is TransportMode.TLS else "sip"
+        byeRequest += self.__gen_contact(
+            method,
+            self.user,
+            self.nat.get_host(self.server),
+            port=self.bind_port,
         )
-        byeRequest += self._gen_user_agent()
+        byeRequest += self.__gen_user_agent()
         byeRequest += f"Allow: {(', '.join(pyVoIP.SIPCompatibleMethods))}\r\n"
         byeRequest += "Content-Length: 0\r\n\r\n"
 
@@ -766,7 +886,7 @@ def gen_ack(self, request: SIPMessage) -> str:
         ackMessage += f'From: {display_name}<{_from["uri"]}>;tag={tag}\r\n'
         ackMessage += f"Call-ID: {request.headers['Call-ID']}\r\n"
         ackMessage += f"CSeq: {request.headers['CSeq']['check']} ACK\r\n"
-        ackMessage += self._gen_user_agent()
+        ackMessage += self.__gen_user_agent()
         ackMessage += "Content-Length: 0\r\n\r\n"
 
         return ackMessage
@@ -778,9 +898,7 @@ def _gen_response_via_header(self, request: SIPMessage) -> str:
         via = ""
         for h_via in request.headers["Via"]:
             v_line = (
-                "Via: SIP/2.0/"
-                + str(self.transport_mode)
-                + " "
+                f"Via: {h_via['type']} "
                 + f'{h_via["address"][0]}:{h_via["address"][1]}'
             )
             if "branch" in h_via.keys():
@@ -801,39 +919,39 @@ def invite(
         number: str,
         ms: Dict[int, Dict[int, "RTP.PayloadType"]],
         sendtype: "RTP.TransmitType",
-    ) -> Tuple[SIPMessage, str, int]:
+    ) -> Tuple[SIPMessage, str, int, "VoIPConnection"]:
         branch = "z9hG4bK" + self.gen_call_id()[0:25]
         call_id = self.gen_call_id()
         sess_id = self.sessID.next()
         invite = self.gen_invite(
             number, str(sess_id), ms, sendtype, branch, call_id
         )
-        self.sendto(invite)
+        conn = self.sendto(invite)
         debug("Invited")
-        response = SIPMessage(self.s.recv(8192))
+        response = SIPMessage(conn.recv(8192))
 
         while (
-            response.status != SIPStatus(401)
-            and response.status != SIPStatus(407)
-            and response.status != SIPStatus(100)
-            and response.status != SIPStatus(180)
+            response.status
+            not in UNAUTORIZED_RESPONSE_CODES + INVITE_OK_RESPONSE_CODES
         ) or response.headers["Call-ID"] != call_id:
             if not self.NSD:
                 break
             debug(f"Received Response: {response.summary()}")
             self.parse_message(response)
-            response = SIPMessage(self.s.recv(8192))
+            response = SIPMessage(conn.recv(8192))
 
         debug(f"Received Response: {response.summary()}")
 
-        if response.status == SIPStatus(100) or response.status == SIPStatus(
-            180
-        ):
-            debug("Invite status OK")
-            return SIPMessage(invite.encode("utf8")), call_id, sess_id
+        if response.status in INVITE_OK_RESPONSE_CODES:
+            debug("Invite Accepted")
+            if response.status is SIPStatus.OK:
+                return response, call_id, sess_id, conn
+            return SIPMessage(invite.encode("utf8")), call_id, sess_id, conn
+        debug("Invite Requires Authorization")
         ack = self.gen_ack(response)
-        self.sendto(ack)
+        conn.send(ack)
         debug("Acknowledged")
+        conn.close()  # End of Dialog
         auth = self.gen_authorization(response)
 
         invite = self.gen_invite(
@@ -843,18 +961,15 @@ def invite(
             "\r\nContent-Length", f"\r\n{auth}Content-Length"
         )
 
-        self.sendto(invite)
+        conn = self.sendto(invite)
 
-        return SIPMessage(invite.encode("utf8")), call_id, sess_id
+        return SIPMessage(invite.encode("utf8")), call_id, sess_id, conn
 
     def gen_message(
         self, number: str, body: str, ctype: str, branch: str, call_id: str
     ) -> str:
         msg = f"MESSAGE sip:{number}@{self.server} SIP/2.0\r\n"
-        msg += (
-            f"Via: SIP/2.0/{self.transport_mode} "
-            + f"{self.bind_ip}:{self.bind_port};branch={branch}\r\n"
-        )
+        msg += self.__gen_via(self.server, branch)
         msg += "Max-Forwards: 70\r\n"
         msg += f"To: <sip:{number}@{self.server}>\r\n"
         msg += (
@@ -874,11 +989,11 @@ def message(
         branch = "z0hG4bK" + self.gen_call_id()[0:25]
         call_id = self.gen_call_id()
         msg = self.gen_message(number, body, ctype, branch, call_id)
-        self.sendto(msg)
+        conn = self.sendto(msg)
         debug("Message")
         auth = False
         while True:
-            response = SIPMessage(self.s.recv(8192))
+            response = SIPMessage(conn.recv(8192))
             debug(f"Received Response: {response.summary()}")
             self.parse_message(response)
             if response.status == SIPStatus(100):
@@ -894,7 +1009,7 @@ def message(
                 msg = msg.replace(
                     "\r\nContent-Length", "\r\n{auth}Content-Length"
                 )
-                self.sendto(msg)
+                conn.send(msg)
                 continue
             if response.status == SIPStatus.OK:
                 break
@@ -905,14 +1020,14 @@ def message(
     def bye(self, request: SIPMessage) -> None:
         message = self.gen_bye(request)
         # TODO: Handle bye to server vs. bye to connected client
-        self.sendto(
+        conn = self.sendto(
             message,
             (
                 request.headers["Contact"]["host"],
                 request.headers["Contact"]["port"],
             ),
         )
-        response = SIPMessage(self.s.recv(8192))
+        response = SIPMessage(conn.recv(8192))
         if response.status == SIPStatus(401):
             #  Requires password
             auth = self.gen_authorization(response)
@@ -920,13 +1035,7 @@ def bye(self, request: SIPMessage) -> None:
                 "\r\nContent-Length", f"\r\n{auth}Content-Length"
             )
             # TODO: Handle bye to server vs. bye to connected client
-            self.sendto(
-                message,
-                (
-                    request.headers["Contact"]["host"],
-                    request.headers["Contact"]["port"],
-                ),
-            )
+            conn.send(message)
         else:
             debug("Received not a 401 on bye:")
             debug(response.summary())
@@ -936,54 +1045,75 @@ def cancel(self, request: SIPMessage) -> None:
         self.sendto(message)
 
     def deregister(self) -> bool:
-        firstRequest = self.gen_first_request(deregister=True)
-        conn = self.send(firstRequest)
+        first_request = self.gen_first_request(deregister=True)
+        conn = self.send(first_request)
 
-        resp = conn.recv(8192)
-
-        response = SIPMessage(resp)
-        response = self.trying_timeout_check(response)
+        response = self.__receive(conn)
+        first_response = response
+        conn.close()  # Regardless of the response, the dialog is over
 
         if response.status == SIPStatus(401):
             # Unauthorized, likely due to being password protected.
-            regRequest = self.gen_register(response, deregister=True)
-            conn.send(regRequest)
-            resp = conn.recv(8192)
-            response = SIPMessage(resp)
-            if response.status == SIPStatus(401):
-                # At this point, it's reasonable to assume that
-                # this is caused by invalid credentials.
-                debug("Unauthorized")
-                raise InvalidAccountInfoError(
-                    "Invalid Username or "
-                    + "Password for SIP server "
-                    + f"{self.server}:"
-                    + f"{self.bind_port}"
-                )
-            elif response.status == SIPStatus(400):
-                # Bad Request
-                # TODO: implement
-                # TODO: check if broken connection can be brought back
-                # with new urn:uuid or reply with expire 0
-                self._handle_bad_request()
-
-        if response.status == SIPStatus(500):
-            time.sleep(5)
-            return self.deregister()
-
-        if response.status == SIPStatus.OK:
+            password_request = self.gen_register(response, deregister=True)
+            conn = self.send(password_request)
+            response = self.__receive(conn)
+            conn.close()
+
+        if response.status == SIPStatus(400):
+            # Bad Request
+            # TODO: implement
+            # TODO: check if broken connection can be brought back
+            # with new urn:uuid or reply with expire 0
+            self._handle_bad_request()
+
+        elif response.status == SIPStatus(407):
+            # Proxy Authentication Required
+            # TODO: implement
+            debug("Proxy auth required")
+
+        elif response.status == SIPStatus(500):
+            raise Exception("Received a 500 error when deregistering.")
+
+        elif response.status == SIPStatus.OK:
             return True
-        return False
 
-    def register(self) -> bool:
-        firstRequest = self.gen_first_request()
-        conn = self.send(firstRequest)
+        elif response.status == SIPStatus(401):
+            # At this point, it's reasonable to assume that
+            # this is caused by invalid credentials.
+            debug("=" * 50)
+            debug("Unauthorized deregister, SIP Message Log:\n")
+            debug("SENT")
+            debug(first_request)
+            debug("\nRECEIVED")
+            debug(first_response.summary())
+            debug("\nSENT (DO NOT SHARE THIS PACKET)")
+            debug(password_request)
+            debug("\nRECEIVED")
+            debug(response.summary())
+            debug("=" * 50)
+            raise InvalidAccountInfoError(
+                f"Invalid Username or Password for SIP server {self.server}:"
+                + f"{self.bind_port}"
+            )
 
-        resp = conn.recv(8192)
+        raise Exception(
+            f"Unable to deregister. Ended with {response.summary()}"
+        )
+
+    def register(self) -> bool:
+        first_request = self.gen_first_request()
+        conn = self.send(first_request)
 
-        response = SIPMessage(resp)
-        response = self.trying_timeout_check(response)
+        response = self.__receive(conn)
         first_response = response
+        conn.close()  # Regardless of the response, the dialog is over
+
+        if response.status == SIPStatus(401):
+            # Unauthorized, likely due to being password protected.
+            password_request = self.gen_register(response)
+            conn = self.send(password_request)
+            response = self.__receive(conn)
+            conn.close()
 
         if response.status == SIPStatus(400):
             # Bad Request
@@ -992,63 +1122,15 @@ def register(self) -> bool:
             # with new urn:uuid or reply with expire 0
             self._handle_bad_request()
 
-        if response.status == SIPStatus(401):
-            # Unauthorized, likely due to being password protected.
-            regRequest = self.gen_register(response)
-            conn.send(regRequest)
-            resp = conn.recv(8192)
-            response = SIPMessage(resp)
-            response = self.trying_timeout_check(response)
-            if response.status == SIPStatus(401):
-                # At this point, it's reasonable to assume that
-                # this is caused by invalid credentials.
-                debug("=" * 50)
-                debug("Unauthorized, SIP Message Log:\n")
-                debug("SENT")
-                debug(firstRequest)
-                debug("\nRECEIVED")
-                debug(first_response.summary())
-                debug("\nSENT (DO NOT SHARE THIS PACKET)")
-                debug(regRequest)
-                debug("\nRECEIVED")
-                debug(response.summary())
-                debug("=" * 50)
-                raise InvalidAccountInfoError(
-                    "Invalid Username or "
-                    + "Password for SIP server "
-                    + f"{self.server}:"
-                    + f"{self.bind_port}"
-                )
-            elif response.status == SIPStatus(400):
-                # Bad Request
-                # TODO: implement
-                # TODO: check if broken connection can be brought back
-                # with new urn:uuid or reply with expire 0
-                self._handle_bad_request()
-
-        if response.status == SIPStatus(407):
+        elif response.status == SIPStatus(407):
             # Proxy Authentication Required
             # TODO: implement
             debug("Proxy auth required")
 
-        # TODO: This must be done more reliable
-        if response.status not in [
-            SIPStatus(400),
-            SIPStatus(401),
-            SIPStatus(407),
-        ]:
-            # Unauthorized
-            if response.status == SIPStatus(500):
-                time.sleep(5)
-                return self.register()
-            else:
-                # TODO: determine if needed here
-                self.parse_message(response)
-
-        debug(response.summary())
-        debug(response.raw)
+        elif response.status == SIPStatus(500):
+            raise Exception("Received a 500 error when registering.")
 
-        if response.status == SIPStatus.OK:
+        elif response.status == SIPStatus.OK:
             if self.NSD:
                 # self.subscribe(response)
                 self.registerThread = Timer(
@@ -1059,13 +1141,28 @@ def register(self) -> bool:
                 )
                 self.registerThread.start()
             return True
-        else:
+
+        elif response.status == SIPStatus(401):
+            # At this point, it's reasonable to assume that
+            # this is caused by invalid credentials.
+            debug("=" * 50)
+            debug("Unauthorized, SIP Message Log:\n")
+            debug("SENT")
+            debug(first_request)
+            debug("\nRECEIVED")
+            debug(first_response.summary())
+            debug("\nSENT (DO NOT SHARE THIS PACKET)")
+            debug(password_request)
+            debug("\nRECEIVED")
+            debug(response.summary())
+            debug("=" * 50)
             raise InvalidAccountInfoError(
-                "Invalid Username or Password for "
-                + f"SIP server {self.server}:"
+                f"Invalid Username or Password for SIP server {self.server}:"
                 + f"{self.bind_port}"
             )
 
+        raise Exception(f"Unable to register. Ended with {response.summary()}")
+
     def _handle_bad_request(self) -> None:
         # Bad Request
         # TODO: implement
@@ -1077,29 +1174,27 @@ def subscribe(self, lastresponse: SIPMessage) -> None:
         # TODO: check if needed and maybe implement fully
 
         subRequest = self.gen_subscribe(lastresponse)
-        self.sendto(subRequest)
+        conn = self.sendto(subRequest)
 
-        response = SIPMessage(self.s.recv(8192))
+        response = SIPMessage(conn.recv(8192))
 
         debug(f'Got response to subscribe: {str(response.heading, "utf8")}')
 
-    def trying_timeout_check(self, response: SIPMessage) -> SIPMessage:
+    def __receive(self, conn: "VoIPConnection") -> SIPMessage:
         """
         Some servers need time to process the response.
         When this happens, the first response you get from the server is
         SIPStatus.TRYING. This while loop tries checks every second for an
-        updated response. It times out after 30 seconds.
+        updated response. It times out after 30 seconds with no response.
         """
-        start_time = time.monotonic()
-        while response.status == SIPStatus.TRYING:
-            if (time.monotonic() - start_time) >= self.register_timeout:
-                raise TimeoutError(
-                    f"Waited {self.register_timeout} seconds but server is "
-                    + "still TRYING"
-                )
-
-            ready = select.select([self.s], [], [], self.register_timeout)
-            if ready[0]:
-                resp = self.s.recv(8192)
-            response = SIPMessage(resp)
+        try:
+            response = SIPMessage(conn.recv(8128, self.register_timeout))
+            while response.status == SIPStatus.TRYING and self.NSD:
+                response = SIPMessage(conn.recv(8128, self.register_timeout))
+                time.sleep(1)
+        except TimeoutError:
+            raise TimeoutError(
+                f"Waited {self.register_timeout} seconds but the server is "
+                + "still TRYING or has not responded."
+            )
         return response
diff --git a/pyVoIP/SIP/message.py b/pyVoIP/SIP/message.py
index 0990f8e..e14bafe 100644
--- a/pyVoIP/SIP/message.py
+++ b/pyVoIP/SIP/message.py
@@ -313,7 +313,12 @@ def __init__(self, data: bytes):
             "v": "Via",
         }
 
-        self.parse(data)
+        try:
+            self.parse(data)
+        except Exception as e:
+            if type(e) is not SIPParseError:
+                raise SIPParseError(e)
+            raise
 
     def summary(self) -> str:
         data = ""
@@ -368,7 +373,7 @@ def __get_tfc_header(self, data: str) -> TFC_HEADER:
         if direct:
             reg = regex.TO_FROM_DIRECT_MATCH
         match = reg.match(data)
-        if type(match) != regex.Match:
+        if match is None:
             raise SIPParseError(
                 "Regex failed to match To/From.\n\n"
                 + "Please open a GitHub Issue at "
diff --git a/pyVoIP/VoIP/__init__.py b/pyVoIP/VoIP/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/pyVoIP/VoIP/call.py b/pyVoIP/VoIP/call.py
index 69e773f..d250555 100644
--- a/pyVoIP/VoIP/call.py
+++ b/pyVoIP/VoIP/call.py
@@ -1,7 +1,9 @@
 from enum import Enum
 from pyVoIP import RTP, SIP
+from pyVoIP.SIP.error import SIPParseError
+from pyVoIP.SIP.message import SIPMessage, SIPMessageType, SIPStatus
 from pyVoIP.VoIP.error import InvalidStateError
-from threading import Lock
+from threading import Lock, Timer
 from typing import Any, Dict, List, Optional, TYPE_CHECKING
 import audioop
 import io
@@ -20,6 +22,7 @@
 
 if TYPE_CHECKING:
     from pyVoIP.VoIP.phone import VoIPPhone
+    from pyVoIP.sock.sock import VoIPConnection
 
 
 class CallState(Enum):
@@ -36,9 +39,10 @@ def __init__(
         self,
         phone: "VoIPPhone",
         callstate: CallState,
-        request: SIP.SIPMessage,
+        request: SIPMessage,
         session_id: int,
         bind_ip: str,
+        conn: "VoIPConnection",
         ms: Optional[Dict[int, RTP.PayloadType]] = None,
         sendmode="sendonly",
     ):
@@ -49,6 +53,7 @@ def __init__(
         self.call_id = request.headers["Call-ID"]
         self.session_id = str(session_id)
         self.bind_ip = bind_ip
+        self.conn = conn
         self.rtp_port_high = self.phone.rtp_port_high
         self.rtp_port_low = self.phone.rtp_port_low
         self.sendmode = sendmode
@@ -69,103 +74,134 @@ def __init__(
         self.assignedPorts: Any = {}
 
         if callstate == CallState.RINGING:
-            audio = []
-            video = []
-            for x in self.request.body["c"]:
-                self.connections += x["address_count"]
-            for x in self.request.body["m"]:
-                if x["type"] == "audio":
-                    self.audioPorts += x["port_count"]
-                    audio.append(x)
-                elif x["type"] == "video":
-                    self.videoPorts += x["port_count"]
-                    video.append(x)
-                else:
-                    warnings.warn(
-                        f"Unknown media description: {x['type']}", stacklevel=2
-                    )
-
-            # Ports Adjusted is used in case of multiple m tags.
-            if len(audio) > 0:
-                audioPortsAdj = self.audioPorts / len(audio)
+            self.init_incoming_call(request)
+        elif callstate == CallState.DIALING:
+            self.init_outgoing_call(ms)
+
+        t = Timer(0, self.receiver)
+        t.name = f"Call {self.call_id} Receiver"
+        t.start()
+
+    def receiver(self):
+        """Receive and handle incoming messages"""
+        while self.state is not CallState.ENDED and self.phone.NSD:
+            data = self.conn.recv(8192)
+            if data is None:
+                continue
+            try:
+                message = SIPMessage(data)
+            except SIPParseError:
+                continue
+            if message.type is SIPMessageType.RESPONSE:
+                if message.status is SIPStatus.OK:
+                    if self.state in [
+                        CallState.DIALING,
+                        CallState.RINGING,
+                        CallState.PROGRESS,
+                    ]:
+                        self.answered(message)
+                elif message.status == SIPStatus.NOT_FOUND:
+                    pass
             else:
-                audioPortsAdj = 0
-            if len(video) > 0:
-                videoPortsAdj = self.videoPorts / len(video)
+                if message.method == "BYE":
+                    self.bye(message)
+
+    def init_outgoing_call(self, ms: Optional[Dict[int, RTP.PayloadType]]):
+        if ms is None:
+            raise RuntimeError(
+                "Media assignments are required when " + "initiating a call"
+            )
+        self.ms = ms
+        for m in self.ms:
+            self.port = m
+            self.assignedPorts[m] = self.ms[m]
+
+    def init_incoming_call(self, request: SIP.SIPMessage):
+        audio = []
+        video = []
+        for x in self.request.body["c"]:
+            self.connections += x["address_count"]
+        for x in self.request.body["m"]:
+            if x["type"] == "audio":
+                self.audioPorts += x["port_count"]
+                audio.append(x)
+            elif x["type"] == "video":
+                self.videoPorts += x["port_count"]
+                video.append(x)
             else:
-                videoPortsAdj = 0
+                warnings.warn(
+                    f"Unknown media description: {x['type']}", stacklevel=2
+                )
 
-            if not (
-                (audioPortsAdj == self.connections or self.audioPorts == 0)
-                and (videoPortsAdj == self.connections or self.videoPorts == 0)
-            ):
-                # TODO: Throw error to PBX in this case
-                warnings.warn("Unable to assign ports for RTP.", stacklevel=2)
-                return
-
-            for i in request.body["m"]:
-                assoc = {}
-                e = False
-                for x in i["methods"]:
+        # Ports Adjusted is used in case of multiple m tags.
+        if len(audio) > 0:
+            audioPortsAdj = self.audioPorts / len(audio)
+        else:
+            audioPortsAdj = 0
+        if len(video) > 0:
+            videoPortsAdj = self.videoPorts / len(video)
+        else:
+            videoPortsAdj = 0
+
+        if not (
+            (audioPortsAdj == self.connections or self.audioPorts == 0)
+            and (videoPortsAdj == self.connections or self.videoPorts == 0)
+        ):
+            # TODO: Throw error to PBX in this case
+            warnings.warn("Unable to assign ports for RTP.", stacklevel=2)
+            return
+
+        for i in request.body["m"]:
+            assoc = {}
+            e = False
+            for x in i["methods"]:
+                try:
+                    p = RTP.PayloadType(int(x))
+                    assoc[int(x)] = p
+                except ValueError:
                     try:
-                        p = RTP.PayloadType(int(x))
+                        p = RTP.PayloadType(
+                            i["attributes"][x]["rtpmap"]["name"]
+                        )
                         assoc[int(x)] = p
                     except ValueError:
-                        try:
-                            p = RTP.PayloadType(
-                                i["attributes"][x]["rtpmap"]["name"]
-                            )
-                            assoc[int(x)] = p
-                        except ValueError:
-                            # Sometimes rtpmap raise a KeyError because fmtp
-                            # is set instate
-                            pt = i["attributes"][x]["rtpmap"]["name"]
-                            warnings.warn(
-                                f"RTP Payload type {pt} not found.",
-                                stacklevel=20,
-                            )
-                            # Resets the warning filter so this warning will
-                            # come up again if it happens.  However, this
-                            # also resets all other warnings.
-                            warnings.simplefilter("default")
-                            p = RTP.PayloadType("UNKNOWN")
-                            assoc[int(x)] = p
-                        except KeyError:
-                            # fix issue 42
-                            # When rtpmap is not found, also set the found
-                            # element to UNKNOWN
-                            warnings.warn(
-                                f"RTP KeyError {x} not found.", stacklevel=20
-                            )
-                            p = RTP.PayloadType("UNKNOWN")
-                            assoc[int(x)] = p
-
-                if e:
-                    raise RTP.RTPParseError(
-                        f"RTP Payload type {pt} not found."
-                    )
-
-                # Make sure codecs are compatible.
-                codecs = {}
-                for m in assoc:
-                    if assoc[m] in pyVoIP.RTPCompatibleCodecs:
-                        codecs[m] = assoc[m]
-                # TODO: If no codecs are compatible then send error to PBX.
-
-                port = self.phone.request_port()
-                self.create_rtp_clients(
-                    codecs, self.bind_ip, port, request, i["port"]
-                )
-        elif callstate == CallState.DIALING:
-            if ms is None:
-                raise RuntimeError(
-                    "Media assignments are required when "
-                    + "initiating a call"
-                )
-            self.ms = ms
-            for m in self.ms:
-                self.port = m
-                self.assignedPorts[m] = self.ms[m]
+                        # Sometimes rtpmap raise a KeyError because fmtp
+                        # is set instate
+                        pt = i["attributes"][x]["rtpmap"]["name"]
+                        warnings.warn(
+                            f"RTP Payload type {pt} not found.",
+                            stacklevel=20,
+                        )
+                        # Resets the warning filter so this warning will
+                        # come up again if it happens.  However, this
+                        # also resets all other warnings.
+                        warnings.simplefilter("default")
+                        p = RTP.PayloadType("UNKNOWN")
+                        assoc[int(x)] = p
+                    except KeyError:
+                        # fix issue 42
+                        # When rtpmap is not found, also set the found
+                        # element to UNKNOWN
+                        warnings.warn(
+                            f"RTP KeyError {x} not found.", stacklevel=20
+                        )
+                        p = RTP.PayloadType("UNKNOWN")
+                        assoc[int(x)] = p
+
+            if e:
+                raise RTP.RTPParseError(f"RTP Payload type {pt} not found.")
+
+            # Make sure codecs are compatible.
+            codecs = {}
+            for m in assoc:
+                if assoc[m] in pyVoIP.RTPCompatibleCodecs:
+                    codecs[m] = assoc[m]
+            # TODO: If no codecs are compatible then send error to PBX.
+
+            port = self.phone.request_port()
+            self.create_rtp_clients(
+                codecs, self.bind_ip, port, request, i["port"]
+            )
 
     def create_rtp_clients(
         self,
@@ -278,6 +314,8 @@ def answered(self, request: SIP.SIPMessage) -> None:
         elif self.state != CallState.PROGRESS:
             return
         self.state = CallState.ANSWERED
+        ack = self.phone.sip.gen_ack(request)
+        self.conn.send(ack)
 
     def progress(self, request: SIP.SIPMessage) -> None:
         if self.state != CallState.DIALING:
@@ -375,7 +413,7 @@ def cancel(self) -> None:
         self.sip.cancel(self.request)
         self.state = CallState.CANCELING
 
-    def bye(self) -> None:
+    def bye(self, request: SIPMessage) -> None:
         if (
             self.state == CallState.ANSWERED
             or self.state == CallState.PROGRESS
@@ -383,7 +421,9 @@ def bye(self) -> None:
         ):
             for x in self.RTPClients:
                 x.stop()
-            self.state = CallState.ENDED
+        self.state = CallState.ENDED
+        ok = self.phone.sip.gen_ok(request)
+        self.conn.send(ok)
         if self.request.headers["Call-ID"] in self.phone.calls:
             del self.phone.calls[self.request.headers["Call-ID"]]
 
diff --git a/pyVoIP/VoIP/phone.py b/pyVoIP/VoIP/phone.py
index 9cc5486..fb441db 100644
--- a/pyVoIP/VoIP/phone.py
+++ b/pyVoIP/VoIP/phone.py
@@ -1,6 +1,7 @@
 from enum import Enum
 from pyVoIP import SIP, RTP
 from pyVoIP.credentials import CredentialsManager
+from pyVoIP.sock.sock import VoIPConnection
 from pyVoIP.sock.transport import TransportMode
 from pyVoIP.types import KEY_PASSWORD
 from pyVoIP.VoIP.call import CallState, VoIPCall
@@ -40,6 +41,9 @@ def __init__(
         user: str,
         credentials_manager: CredentialsManager,
         bind_ip="0.0.0.0",
+        bind_network="0.0.0.0/0",
+        hostname: Optional[str] = None,
+        remote_hostname: Optional[str] = None,
         bind_port=5060,
         transport_mode=TransportMode.UDP,
         cert_file: Optional[str] = None,
@@ -90,17 +94,22 @@ def __init__(
             user,
             credentials_manager,
             bind_ip=self.bind_ip,
+            bind_network=bind_network,
+            hostname=hostname,
+            remote_hostname=remote_hostname,
             bind_port=bind_port,
             call_callback=self.callback,
             transport_mode=self.transport_mode,
         )
 
-    def callback(self, request: SIP.SIPMessage) -> Optional[str]:
+    def callback(
+        self, conn: VoIPConnection, request: SIP.SIPMessage
+    ) -> Optional[str]:
         # debug("Callback: "+request.summary())
         if request.type == pyVoIP.SIP.SIPMessageType.REQUEST:
             # debug("This is a message")
             if request.method == "INVITE":
-                self._callback_MSG_Invite(request)
+                self._callback_MSG_Invite(conn, request)
             elif request.method == "BYE":
                 self._callback_MSG_Bye(request)
             elif request.method == "OPTIONS":
@@ -125,23 +134,13 @@ def callback(self, request: SIP.SIPMessage) -> Optional[str]:
     def get_status(self) -> PhoneStatus:
         return self._status
 
-    def _callback_MSG_Invite(self, request: SIP.SIPMessage) -> None:
+    def _callback_MSG_Invite(
+        self, conn: VoIPConnection, request: SIP.SIPMessage
+    ) -> None:
         call_id = request.headers["Call-ID"]
-        if call_id in self.calls:
-            debug("Re-negotiation detected!")
-            # TODO: this seems "dangerous" if for some reason sip server
-            # handles 2 and more bindings it will cause duplicate RTP-Clients
-            # to spawn.
-
-            # CallState.Ringing seems important here to prevent multiple
-            # answering and RTP-Client spawning. Find out when renegotiation
-            # is relevant.
-            if self.calls[call_id].state != CallState.RINGING:
-                self.calls[call_id].renegotiate(request)
-            return  # Raise Error
         if self.callClass is None:
             message = self.sip.gen_busy(request)
-            self.sip.sendto(message, request.headers["Via"][0]["address"])
+            conn.send(message)
         else:
             debug("New call!")
             sess_id = None
@@ -151,8 +150,8 @@ def _callback_MSG_Invite(self, request: SIP.SIPMessage) -> None:
                     self.session_ids.append(proposed)
                     sess_id = proposed
             message = self.sip.gen_ringing(request)
-            self.sip.sendto(message, request.headers["Via"][0]["address"])
-            call = self._create_Call(request, sess_id)
+            conn.send(message)
+            call = self._create_call(conn, request, sess_id)
             try:
                 t = Timer(1, call.ringing, [request])
                 t.name = f"Phone Call: {call_id}"
@@ -161,9 +160,8 @@ def _callback_MSG_Invite(self, request: SIP.SIPMessage) -> None:
                 self.threadLookup[t] = call_id
             except Exception:
                 message = self.sip.gen_busy(request)
-                self.sip.sendto(
+                conn.send(
                     message,
-                    request.headers["Via"][0]["address"],
                 )
                 raise
 
@@ -269,7 +267,9 @@ def _callback_RESP_Unavailable(self, request: SIP.SIPMessage) -> None:
         ack = self.sip.gen_ack(request)
         self.sip.sendto(ack)
 
-    def _create_Call(self, request: SIP.SIPMessage, sess_id: int) -> VoIPCall:
+    def _create_call(
+        self, conn: VoIPConnection, request: SIP.SIPMessage, sess_id: int
+    ) -> VoIPCall:
         """
         Create VoIP call object. Should be separated to enable better
         subclassing.
@@ -281,6 +281,7 @@ def _create_Call(self, request: SIP.SIPMessage, sess_id: int) -> VoIPCall:
             request,
             sess_id,
             self.bind_ip,
+            conn=conn,
             sendmode=self.recvmode,
         )
         return self.calls[call_id]
@@ -305,6 +306,7 @@ def stop(self) -> None:
             except InvalidStateError:
                 pass
         self.sip.stop()
+        self.NSD = False
         self._status = PhoneStatus.INACTIVE
 
     def call(
@@ -330,7 +332,7 @@ def call(
                 medias[port][dynamic_int] = pt
                 dynamic_int += 1
         debug(f"Making call with {medias=}")
-        request, call_id, sess_id = self.sip.invite(
+        request, call_id, sess_id, conn = self.sip.invite(
             number, medias, RTP.TransmitType.SENDRECV
         )
         self.calls[call_id] = self.callClass(
@@ -341,6 +343,7 @@ def call(
             self.bind_ip,
             ms=medias,
             sendmode=self.sendmode,
+            conn=conn,
         )
 
         return self.calls[call_id]
diff --git a/pyVoIP/__init__.py b/pyVoIP/__init__.py
index 858b3d4..e62e2f7 100644
--- a/pyVoIP/__init__.py
+++ b/pyVoIP/__init__.py
@@ -50,6 +50,17 @@
 # https://docs.python.org/3/library/ssl.html#ssl.SSLContext.verify_mode
 TLS_VERIFY_MODE = ssl.CERT_REQUIRED
 
+"""
+DO NOT CHANGE IN PRODUCTION.
+
+This variable allows you to save the SIP message state database to a file
+instead of storing it in memory which is the default.  This is useful for
+debugging, however pyVoIP does not delete the database afterwards which will
+cause an Exception upon restarting pyVoIP.  For this reason, we recommend you
+do not change this variable in production.
+"""
+SIP_STATE_DB_LOCATION = ":memory:"
+
 
 def set_tls_security(verify_mode: ssl.VerifyMode) -> None:
     """
diff --git a/pyVoIP/networking/__init__.py b/pyVoIP/networking/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/pyVoIP/networking/nat.py b/pyVoIP/networking/nat.py
new file mode 100644
index 0000000..e07470f
--- /dev/null
+++ b/pyVoIP/networking/nat.py
@@ -0,0 +1,67 @@
+from enum import Enum
+from typing import Optional
+import ipaddress
+import socket
+
+
+class NATError(Exception):
+    pass
+
+
+class AddressType(Enum):
+    """Used for determining remote or local tags in SIP messages"""
+
+    REMOTE = 0
+    LOCAL = 1
+
+
+class NAT:
+    def __init__(
+        self,
+        bind_ip: str,
+        network: str,
+        hostname: Optional[str] = None,
+        remote_hostname: Optional[str] = None,
+    ):
+        self.bind_ip = ipaddress.ip_address(bind_ip)
+        self.network = ipaddress.ip_network(network)
+        self.hostname = bind_ip if hostname is None else hostname
+        self.remote_hostname = remote_hostname
+
+    def get_host(self, host: str):
+        """Return the hostname another client needs to connect to us."""
+        try:
+            ip = ipaddress.ip_address(host)
+        except ValueError:
+            try:
+                ip = ipaddress.ip_address(socket.gethostbyname(host))
+            except socket.gaierror:
+                raise NATError(f"Unable to resolve hostname {host}")
+
+        if ip in self.network:
+            return self.hostname
+        else:
+            if self.remote_hostname is not None:
+                return self.remote_hostname
+            raise NATError(
+                "No remote hostname specified, "
+                + "cannot provide a return path for remote hosts."
+            )
+
+    def check_host(self, host: str) -> AddressType:
+        """Determine if a host is a remote computer or not."""
+        if host in [self.remote_hostname, self.hostname]:
+            return AddressType.LOCAL
+        try:
+            ip = ipaddress.ip_address(host)
+            if ip == self.bind_ip:
+                return AddressType.LOCAL
+            return AddressType.REMOTE
+        except ValueError:
+            try:
+                ip = ipaddress.ip_address(socket.gethostbyname(host))
+                if ip == self.bind_ip:
+                    return AddressType.LOCAL
+                return AddressType.REMOTE
+            except socket.gaierror:
+                return AddressType.REMOTE
diff --git a/pyVoIP/sock/sock.py b/pyVoIP/sock/sock.py
index 0acc040..8a9f4ff 100644
--- a/pyVoIP/sock/sock.py
+++ b/pyVoIP/sock/sock.py
@@ -1,8 +1,12 @@
-from typing import List, Optional, Tuple, Union
+from typing import TYPE_CHECKING, List, Optional, Tuple, Union
 from pyVoIP.types import KEY_PASSWORD, SOCKETS
 from pyVoIP.SIP import SIPMessage, SIPMessageType
+from pyVoIP.SIP.error import SIPParseError
+from pyVoIP.networking.nat import NAT, AddressType
 from pyVoIP.sock.transport import TransportMode
+import json
 import math
+import pprint
 import pyVoIP
 import socket
 import sqlite3
@@ -11,6 +15,10 @@
 import time
 
 
+if TYPE_CHECKING:
+    from pyVoIP.SIP.client import SIPClient
+
+
 debug = pyVoIP.debug
 
 
@@ -33,6 +41,7 @@ def __init__(
         self.local_tag, self.remote_tag = self.sock.determine_tags(
             self.message
         )
+        self._peak_buffer: Optional[bytes] = None
         if conn and message.type == SIPMessageType.REQUEST:
             if self.sock.mode.tls_mode:
                 client_context = ssl.create_default_context()
@@ -47,74 +56,96 @@ def __init__(
     def send(self, data: Union[bytes, str]) -> None:
         if type(data) is str:
             data = data.encode("utf8")
-        msg = SIPMessage(data)
+        try:
+            msg = SIPMessage(data)
+        except SIPParseError:
+            return
         if not self.conn:  # If UDP
             if msg.type == SIPMessageType.REQUEST:
                 addr = (msg.to["host"], msg.to["port"])
             else:
-                addr = msg.headers["Via"][0]
+                addr = msg.headers["Via"][0]["address"]
             self.sock.s.sendto(data, addr)
         else:
             self.conn.send(data)
         debug(f"SENT:\n{msg.summary()}")
 
-    def __find_remote_tag(self) -> None:
-        if self.remote_tag is not None:
-            return
-        conn = self.sock.buffer.cursor()
-        result = conn.execute(
-            """SELECT "remote_tag" FROM "listening" WHERE
-                "call_id" = ?
-                AND "local_tag" = ?""",
-            (self.call_id, self.local_tag),
-        )
-        rows = result.fetchall()
-        if rows:
-            self.remote_tag = rows[0][0]
+    def update_tags(self, local_tag: str, remote_tag: str) -> None:
+        self.local_tag = local_tag
+        self.remote_tag = remote_tag
 
-    def recv(self, nbytes: int, timeout=0) -> bytes:
-        if self.conn:
-            # TODO: Timeout
-            msg = None
-            while not msg and not self.sock.SD:
-                data = self.conn.recv(nbytes)
-                try:
-                    msg = SIPMessage(data)
-                except Exception as e:
-                    br = self.sock.gen_bad_request(
-                        connection=self, error=e, received=data
-                    )
-                    self.send(br)
-            debug(f"RECEIVED:\n{msg.summary()}")
+    def peak(self) -> bytes:
+        return self.recv(8192, timeout=60, peak=True)
+
+    def recv(self, nbytes: int, timeout=0, peak=False) -> bytes:
+        if self._peak_buffer:
+            data = self._peak_buffer
+            if not peak:
+                self._peak_buffer = None
             return data
+        if self.conn:
+            return self._tcp_tls_recv(nbytes, timeout, peak)
         else:
-            self.__find_remote_tag()
-            timeout = time.monotonic() + timeout if timeout else math.inf
-            while time.monotonic() <= timeout and not self.sock.SD:
-                conn = self.sock.buffer.cursor()
-                conn.row_factory = sqlite3.Row
-                sql = (
-                    """SELECT * FROM "msgs" WHERE "call_id" = ? AND "local_tag" = ?"""
-                    + (""" AND "remote_tag" = ?""" if self.remote_tag else "")
-                )
-                bindings = (
-                    (self.call_id, self.local_tag, self.remote_tag)
-                    if self.remote_tag
-                    else (self.call_id, self.local_tag)
-                )
-                result = conn.execute(sql, bindings)
-                row = result.fetchone()
-                if not row:
-                    continue
-                conn.execute(
-                    """DELETE FROM "msgs" WHERE "id" = ?""", (row["id"],)
+            return self._udp_recv(nbytes, timeout, peak)
+
+    def _tcp_tls_recv(self, nbytes: int, timeout=0, peak=False) -> bytes:
+        timeout = time.monotonic() + timeout if timeout else math.inf
+        # TODO: Timeout
+        msg = None
+        while not msg and not self.sock.SD:
+            data = self.conn.recv(nbytes)
+            try:
+                msg = SIPMessage(data)
+            except SIPParseError as e:
+                br = self.sock.gen_bad_request(
+                    connection=self, error=e, received=data
                 )
-                try:
-                    self.sock.buffer.commit()
-                except sqlite3.OperationalError:
-                    pass
+                self.send(br)
+        if time.monotonic() >= timeout:
+            raise TimeoutError()
+        if peak:
+            self._peak_buffer = data
+        debug(f"RECEIVED:\n{msg.summary()}")
+        return data
+
+    def _udp_recv(self, nbytes: int, timeout=0, peak=False) -> bytes:
+        timeout = time.monotonic() + timeout if timeout else math.inf
+        while time.monotonic() <= timeout and not self.sock.SD:
+            # print("Trying to receive")
+            # print(self.sock.get_database_dump())
+            conn = self.sock.buffer.cursor()
+            conn.row_factory = sqlite3.Row
+            sql = (
+                'SELECT * FROM "msgs" WHERE "call_id"=? AND '
+                + '"local_tag" IS ? AND "remote_tag" IS ?'
+            )
+            bindings = (
+                self.call_id,
+                self.local_tag,
+                self.remote_tag,
+            )
+            result = conn.execute(sql, bindings)
+            row = result.fetchone()
+            if not row:
+                conn.close()
+                continue
+            if peak:
+                # If peaking, return before deleting from the database
                 conn.close()
                 return row["msg"].encode("utf8")
+            try:
+                conn.execute('DELETE FROM "msgs" WHERE "id" = ?', (row["id"],))
+            except sqlite3.OperationalError:
+                pass
+            conn.close()
+            return row["msg"].encode("utf8")
+        if time.monotonic() >= timeout:
+            raise TimeoutError()
+
+    def close(self):
+        self.sock.deregister_connection(self)
+        if self.conn:
+            self.conn.close()
 
 
 class VoIPSocket(threading.Thread):
@@ -123,6 +154,7 @@ def __init__(
         mode: TransportMode,
         bind_ip: str,
         bind_port: int,
+        sip: "SIPClient",
         cert_file: Optional[str] = None,
         key_file: Optional[str] = None,
         key_password: KEY_PASSWORD = None,
@@ -136,6 +168,8 @@ def __init__(
         self.s = socket.socket(socket.AF_INET, mode.socket_type)
         self.bind_ip: str = bind_ip
         self.bind_port: int = bind_port
+        self.sip = sip
+        self.nat: NAT = sip.nat
         self.server_context: Optional[ssl.SSLContext] = None
         if mode.tls_mode:
             self.server_context = ssl.SSLContext(
@@ -148,7 +182,11 @@ def __init__(
                 )
             self.s = self.server_context.wrap_socket(self.s, server_side=True)
 
-        self.buffer = sqlite3.connect(":memory:", check_same_thread=False)
+        self.buffer = sqlite3.connect(
+            pyVoIP.SIP_STATE_DB_LOCATION,
+            isolation_level=None,
+            check_same_thread=False,
+        )
         """
         RFC 3261 Section 12, Paragraph 2 states:
         "A dialog is identified at each UA with a dialog ID, which consists
@@ -172,21 +210,22 @@ def __init__(
             );"""
         )
         conn.execute(
-            """CREATE INDEX "msg_index" ON msgs ("call_id", "local_tag", "remote_tag");"""
+            """CREATE INDEX "msg_index" ON msgs """
+            + """("call_id", "local_tag", "remote_tag");"""
+        )
+        conn.execute(
+            """CREATE INDEX "msg_index_2" ON msgs """
+            + """("call_id", "remote_tag", "local_tag");"""
         )
         conn.execute(
             """CREATE TABLE "listening" (
                 "call_id" TEXT NOT NULL,
-                "local_tag" TEXT NOT NULL,
+                "local_tag" TEXT,
                 "remote_tag" TEXT,
                 "connection" INTEGER NOT NULL UNIQUE,
                 PRIMARY KEY("call_id", "local_tag", "remote_tag")
             );"""
         )
-        try:
-            self.buffer.commit()
-        except sqlite3.OperationalError:
-            pass
         conn.close()
         self.conns_lock = threading.Lock()
         self.conns: List[VoIPConnection] = []
@@ -194,7 +233,8 @@ def __init__(
     def gen_bad_request(
         self, connection=None, message=None, error=None, received=None
     ) -> bytes:
-        body = f"<error><message>{error}</message><received>{received}</received></error>"
+        body = f"<error><message>{error}</message>"
+        body += f"<received>{received}</received></error>"
         bad_request = "SIP/2.0 400 Malformed Request\r\n"
         bad_request += (
             f"Via: SIP/2.0/{self.mode} {self.bind_ip}:{self.bind_port}\r\n"
@@ -213,103 +253,135 @@ def __get_connection(
         local_tag, remote_tag = self.determine_tags(message)
         call_id = message.headers["Call-ID"]
         conn = self.buffer.cursor()
-        result = conn.execute(
-            """SELECT "connection" FROM "listening" WHERE
-                "call_id" = ?
-                AND "local_tag" = ?
-                AND "remote_tag" = ?""",
-            (call_id, local_tag, remote_tag),
-        )
+        sql = 'SELECT "connection" FROM "listening" WHERE "call_id" IS ?'
+        sql += ' AND "local_tag" IS ? AND "remote_tag" IS ?'
+        result = conn.execute(sql, (call_id, local_tag, remote_tag))
         rows = result.fetchall()
         if rows:
             conn.close()
             return self.conns[rows[0][0]]
+        debug("New Connection Started")
         # If we didn't find one lets look for something that doesn't have
-        # a remote tag
-        result = conn.execute(
-            """SELECT "connection" FROM "listening" WHERE
-                "call_id" = ?
-                AND "local_tag" = ?""",
-            (call_id, local_tag),
-        )
+        # one of the tags
+        sql = 'SELECT "connection" FROM "listening" WHERE "call_id" = ?'
+        sql += ' AND ("local_tag" IS NULL OR "local_tag" = ?)'
+        sql += ' AND ("remote_tag" IS NULL OR "remote_tag" = ?)'
+        result = conn.execute(sql, (call_id, local_tag, remote_tag))
         rows = result.fetchall()
         if rows:
+            if local_tag and remote_tag:
+                sql = 'UPDATE "listening" SET "remote_tag" = ?, '
+                sql += '"local_tag" = ? WHERE "connection" = ?'
+                conn.execute(sql, (remote_tag, local_tag, rows[0][0]))
+                self.conns[rows[0][0]].update_tags(local_tag, remote_tag)
             conn.close()
             return self.conns[rows[0][0]]
-        # If we still didn't find one, maybe we got the local and remote wrong?
-        result = conn.execute(
-            """SELECT "connection" FROM "listening" WHERE
-                "call_id" = ?
-                AND "local_tag" = ?""",
-            (call_id, remote_tag),
-        )
-        rows = result.fetchall()
         conn.close()
-        if rows:
-            return self.conns[rows[0][0]]
         return None
 
-    def __register_connection(self, connection: VoIPConnection) -> None:
+    def __register_connection(self, connection: VoIPConnection) -> int:
         self.conns_lock.acquire()
         self.conns.append(connection)
         conn_id = len(self.conns) - 1
-        conn = self.buffer.cursor()
-        conn.execute(
-            """INSERT INTO "listening"
-                ("call_id", "local_tag", "remote_tag", "connection")
-                VALUES
-                (?, ?, ?, ?)""",
-            (
-                connection.call_id,
-                connection.local_tag,
-                connection.remote_tag,
-                conn_id,
-            ),
-        )
         try:
-            self.buffer.commit()
+            conn = self.buffer.cursor()
+            conn.execute(
+                """INSERT INTO "listening"
+                    ("call_id", "local_tag", "remote_tag", "connection")
+                    VALUES
+                    (?, ?, ?, ?)""",
+                (
+                    connection.call_id,
+                    connection.local_tag,
+                    connection.remote_tag,
+                    conn_id,
+                ),
+            )
+        except sqlite3.IntegrityError as e:
+            e.add_note(
+                "Error is from registering connection for message: "
+                + f"{connection.message.summary()}"
+            )
+            e.add_note("Internal Database Dump:\n" + self.get_database_dump())
+            e.add_note(
+                f"({connection.call_id=}, {connection.local_tag=}, "
+                + f"{connection.remote_tag=}, {conn_id=})"
+            )
+            raise
         except sqlite3.OperationalError:
             pass
-        conn.close()
-        self.conns_lock.release()
+        finally:
+            conn.close()
+            self.conns_lock.release()
+            return conn_id
+
+    def deregister_connection(self, connection: VoIPConnection) -> None:
+        if self.mode is not TransportMode.UDP:
+            return
+        self.conns_lock.acquire()
+        debug(f"Deregistering {connection}")
+        debug(f"{self.conns=}")
+        debug(self.get_database_dump())
+        try:
+            conn = self.buffer.cursor()
+            sql = 'SELECT "connection" FROM "listening" WHERE "call_id" = ?'
+            sql += ' AND ("local_tag" IS NULL OR "local_tag" = ?)'
+            sql += ' AND ("remote_tag" IS NULL OR "remote_tag" = ?)'
+            result = conn.execute(
+                sql,
+                (
+                    connection.call_id,
+                    connection.local_tag,
+                    connection.remote_tag,
+                ),
+            )
+            row = result.fetchone()
+            conn_id = row[0]
+            """
+            Need to set to None to not change the indexes of any other conn
+            """
+            self.conns[conn_id] = None
+            conn.execute(
+                'DELETE FROM "listening" WHERE "connection" = ?', (conn_id,)
+            )
+        except sqlite3.OperationalError:
+            pass
+        finally:
+            conn.close()
+            self.conns_lock.release()
+
+    def get_database_dump(self, pretty=False) -> str:
+        conn = self.buffer.cursor()
+        ret = ""
+        try:
+            result = conn.execute('SELECT * FROM "listening";')
+            result1 = result.fetchall()
+            result = conn.execute('SELECT * FROM "msgs";')
+            result2 = result.fetchall()
+        finally:
+            conn.close()
+        if pretty:
+            ret += "listening: " + pprint.pformat(result1) + "\n\n"
+            ret += "msgs: " + pprint.pformat(result2) + "\n\n"
+        else:
+            ret += "listening: " + json.dumps(result1) + "\n\n"
+            ret += "msgs: " + json.dumps(result2) + "\n\n"
+        return ret
 
     def determine_tags(self, message: SIPMessage) -> Tuple[str, str]:
         """
-        Returns local_tag, remote_tag
+        Return local_tag, remote_tag
         """
-        # TODO: Eventually NAT will be supported for people who don't have a SIP ALG
-        # We will need to take that into account when determining the remote tag.
 
         to_header = message.headers["To"]
         from_header = message.headers["From"]
         to_host = to_header["host"]
-        to_port = to_header["port"]
         to_tag = to_header["tag"] if to_header["tag"] else None
-        from_host = from_header["host"]
-        from_port = from_header["port"]
         from_tag = from_header["tag"] if from_header["tag"] else None
 
-        if to_host == self.bind_ip and to_port == self.bind_port:
-            return to_tag, from_tag
-        elif from_host == self.bind_ip and from_port == self.bind_port:
-            return from_tag, to_tag
-        # If there is not an exact match, see if the host at least matches.
-        # (But not if the hosts are the same) as asterisk likes to strip
-        # ports.
-        elif to_host != from_host:
-            if to_host == self.bind_ip:
-                return to_tag, from_tag
-            elif from_host == self.bind_ip:
-                return from_tag, to_tag
-        # If there is still not a match, guess the to or from tag based
-        # on if the message. Requests except ACK likely have us as To,
-        # for everthing else we're likely the From
-        elif (
-            message.type == SIPMessageType.REQUEST and message.method != "ACK"
-        ):
+        if self.nat.check_host(to_host) is AddressType.LOCAL:
             return to_tag, from_tag
-        else:
-            return from_tag, to_tag
+        return from_tag, to_tag
 
     def bind(self, addr: Tuple[str, int]) -> None:
         self.s.bind(addr)
@@ -320,53 +392,65 @@ def bind(self, addr: Tuple[str, int]) -> None:
     def _listen(self, backlog=0) -> None:
         return self.s.listen(backlog)
 
-    def run(self) -> None:
-        self.bind((self.bind_ip, self.bind_port))
-        if self.mode != TransportMode.UDP:
-            self._listen()
+    def _tcp_tls_run(self) -> None:
+        self._listen()
         while not self.SD:
-            if self.mode == TransportMode.UDP:
-                try:
-                    data = self.s.recv(8192)
-                except OSError:
-                    continue
+            try:
+                conn, addr = self.s.accept()
+            except OSError:
+                continue
+            debug(f"Received new {self.mode} connection from {addr}.")
+            data = conn.recv(8192)
+            try:
                 message = SIPMessage(data)
-                debug("\n\nReceived UDP Message:")
-                debug(message.summary())
-            else:
-                try:
-                    conn, addr = self.s.accept()
-                except OSError:
-                    continue
-                debug(f"Received new {self.mode} connection from {addr}.")
-                data = conn.recv(8192)
+            except SIPParseError:
+                continue
+            debug("\n\nReceived SIP Message:")
+            debug(message.summary())
+            self._handle_incoming_message(conn, message)
+
+    def _udp_run(self) -> None:
+        while not self.SD:
+            try:
+                data = self.s.recv(8192)
+            except OSError:
+                continue
+            try:
                 message = SIPMessage(data)
-                debug("\n\nReceived SIP Message:")
-                debug(message.summary())
-
-            if not self.__connection_exists(message):
-                if self.mode == TransportMode.UDP:
-                    self.__register_connection(
-                        VoIPConnection(self, None, message)
-                    )
-                else:
-                    self.__register_connection(
-                        VoIPConnection(self, conn, message)
-                    )
-
-            call_id = message.headers["Call-ID"]
-            local_tag, remote_tag = self.determine_tags(message)
-            raw_message = data.decode("utf8")
-            conn = self.buffer.cursor()
-            conn.execute(
-                "INSERT INTO msgs (call_id, local_tag, remote_tag, msg) VALUES (?, ?, ?, ?)",
-                (call_id, local_tag, remote_tag, raw_message),
+            except SIPParseError:
+                continue
+            debug("\n\nReceived UDP Message:")
+            debug(message.summary())
+            self._handle_incoming_message(None, message)
+
+    def _handle_incoming_message(
+        self, conn: Optional[SOCKETS], message: SIPMessage
+    ):
+        conn_id = None
+        if not self.__connection_exists(message):
+            conn_id = self.__register_connection(
+                VoIPConnection(self, conn, message)
             )
-            try:
-                self.buffer.commit()
-            except sqlite3.OperationalError:
-                pass
-            conn.close()
+
+        call_id = message.headers["Call-ID"]
+        local_tag, remote_tag = self.determine_tags(message)
+        raw_message = message.raw.decode("utf8")
+        conn = self.buffer.cursor()
+        conn.execute(
+            "INSERT INTO msgs (call_id, local_tag, remote_tag, msg) "
+            + "VALUES (?, ?, ?, ?)",
+            (call_id, local_tag, remote_tag, raw_message),
+        )
+        conn.close()
+        if conn_id:
+            self.sip.handle_new_connection(self.conns[conn_id])
+
+    def run(self) -> None:
+        self.bind((self.bind_ip, self.bind_port))
+        if self.mode == TransportMode.UDP:
+            self._udp_run()
+        else:
+            self._tcp_tls_run()
 
     def close(self) -> None:
         self.SD = True


Test patch:
diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml
index 5d96984..97c2125 100644
--- a/.github/workflows/pytest.yml
+++ b/.github/workflows/pytest.yml
@@ -33,7 +33,6 @@ jobs:
       - name: Start Docker
         run: |
           docker build docker -t pyvoip/tests
-          docker run -d -p 5060:5060/udp -p 5061-5062:5061-5062/tcp pyvoip/tests
+          docker run --add-host host.docker.internal:host-gateway -d -p 5060:5060/udp -p 5061-5062:5061-5062/tcp pyvoip/tests
       - name: pytest
-        # run: pytest --check-func
-        run: pytest
+        run: pytest --check-func
diff --git a/tests/test_functionality.py b/tests/test_functionality.py
index d36cd49..22cebe7 100644
--- a/tests/test_functionality.py
+++ b/tests/test_functionality.py
@@ -2,17 +2,41 @@
 from pyVoIP.VoIP.call import CallState
 from pyVoIP.VoIP.phone import PhoneStatus, VoIPPhone
 from pyVoIP.sock.transport import TransportMode
+import json
+import os
 import pytest
 import pyVoIP
 import ssl
+import subprocess
 import sys
 import time
 
+
+IS_WINDOWS = True if os.name == "nt" else False
 TEST_CONDITION = (
     "--check-functionality" not in sys.argv and "--check-func" not in sys.argv
 )
+
+if not TEST_CONDITION and not IS_WINDOWS:
+    obj = json.loads(
+        subprocess.check_output(["docker", "network", "inspect", "bridge"])
+    )
+    DOCKER_GATEWAY = obj[0]["IPAM"]["Config"][0]["Gateway"]
+    CONTAINER_ID = list(obj[0]["Containers"].keys())[0]
+    CONTAINER_IP = obj[0]["Containers"][CONTAINER_ID]["IPv4Address"].split(
+        "/"
+    )[0]
+
 REASON = "Not checking functionality"
+NT_REASON = "Test always fails on Windows"
 pyVoIP.set_tls_security(ssl.CERT_NONE)
+SERVER_HOST = "127.0.0.1" if IS_WINDOWS else CONTAINER_IP
+BIND_IP = "0.0.0.0" if IS_WINDOWS else DOCKER_GATEWAY
+UDP_PORT = 5060
+TCP_PORT = 5061
+TLS_PORT = 5062
+
+CALL_TIMEOUT = 2  # 2 seconds to answer.
 
 
 @pytest.fixture
@@ -20,11 +44,12 @@ def phone():
     cm = CredentialsManager()
     cm.add("pass", "Testing123!")
     phone = VoIPPhone(
-        "127.0.0.1",
-        5060,
+        SERVER_HOST,
+        UDP_PORT,
         "pass",
         cm,
-        bind_ip="127.0.0.1",
+        hostname="host.docker.internal",
+        bind_ip=BIND_IP,
         bind_port=5059,
     )
     phone.start()
@@ -35,11 +60,12 @@ def phone():
 @pytest.fixture
 def nopass_phone():
     phone = VoIPPhone(
-        "127.0.0.1",
-        5060,
+        SERVER_HOST,
+        UDP_PORT,
         "nopass",
         CredentialsManager(),
-        bind_ip="127.0.0.1",
+        hostname="host.docker.internal",
+        bind_ip=BIND_IP,
         bind_port=5059,
     )
     phone.start()
@@ -52,11 +78,12 @@ def nopass_phone():
 @pytest.mark.skipif(TEST_CONDITION, reason=REASON)
 def test_nopass():
     phone = VoIPPhone(
-        "127.0.0.1",
-        5060,
+        SERVER_HOST,
+        UDP_PORT,
         "nopass",
         CredentialsManager(),
-        bind_ip="127.0.0.1",
+        hostname="host.docker.internal",
+        bind_ip=BIND_IP,
         bind_port=5059,
     )
     assert phone.get_status() == PhoneStatus.INACTIVE
@@ -77,11 +104,12 @@ def test_pass():
     cm = CredentialsManager()
     cm.add("pass", "Testing123!")
     phone = VoIPPhone(
-        "127.0.0.1",
-        5060,
+        SERVER_HOST,
+        UDP_PORT,
         "pass",
         cm,
-        bind_ip="127.0.0.1",
+        hostname="host.docker.internal",
+        bind_ip=BIND_IP,
         bind_port=5059,
     )
     assert phone.get_status() == PhoneStatus.INACTIVE
@@ -100,11 +128,12 @@ def test_pass():
 @pytest.mark.skipif(TEST_CONDITION, reason=REASON)
 def test_tcp_nopass():
     phone = VoIPPhone(
-        "127.0.0.1",
-        5061,
+        SERVER_HOST,
+        TCP_PORT,
         "nopass",
         CredentialsManager(),
-        bind_ip="127.0.0.1",
+        hostname="host.docker.internal",
+        bind_ip=BIND_IP,
         bind_port=5059,
         transport_mode=TransportMode.TCP,
     )
@@ -126,11 +155,12 @@ def test_tcp_pass():
     cm = CredentialsManager()
     cm.add("pass", "Testing123!")
     phone = VoIPPhone(
-        "127.0.0.1",
-        5061,
+        SERVER_HOST,
+        TCP_PORT,
         "pass",
         cm,
-        bind_ip="127.0.0.1",
+        hostname="host.docker.internal",
+        bind_ip=BIND_IP,
         bind_port=5059,
         transport_mode=TransportMode.TCP,
     )
@@ -150,11 +180,12 @@ def test_tcp_pass():
 @pytest.mark.skipif(TEST_CONDITION, reason=REASON)
 def test_tls_nopass():
     phone = VoIPPhone(
-        "127.0.0.1",
-        5062,
+        SERVER_HOST,
+        TLS_PORT,
         "nopass",
         CredentialsManager(),
-        bind_ip="127.0.0.1",
+        hostname="host.docker.internal",
+        bind_ip=BIND_IP,
         bind_port=5059,
         transport_mode=TransportMode.TLS,
         cert_file="certs/cert.crt",
@@ -179,11 +210,12 @@ def test_tls_pass():
     cm = CredentialsManager()
     cm.add("pass", "Testing123!")
     phone = VoIPPhone(
-        "127.0.0.1",
-        5062,
+        SERVER_HOST,
+        TLS_PORT,
         "pass",
         cm,
-        bind_ip="127.0.0.1",
+        hostname="host.docker.internal",
+        bind_ip=BIND_IP,
         bind_port=5059,
         transport_mode=TransportMode.TLS,
         cert_file="certs/cert.crt",
@@ -201,27 +233,33 @@ def test_tls_pass():
     assert phone.get_status() == PhoneStatus.INACTIVE
 
 
-@pytest.mark.skip
 @pytest.mark.udp
 @pytest.mark.calling
 @pytest.mark.skipif(TEST_CONDITION, reason=REASON)
+@pytest.mark.skipif(IS_WINDOWS, reason=NT_REASON)
 def test_make_call(phone):
     call = phone.call("answerme")
+    start = time.time()
     while call.state == CallState.DIALING:
         time.sleep(0.1)
+        if start + CALL_TIMEOUT < time.time():
+            raise TimeoutError("Call was not answered before the timeout.")
     assert call.state == CallState.ANSWERED
     call.hangup()
     assert call.state == CallState.ENDED
 
 
-@pytest.mark.skip
 @pytest.mark.udp
 @pytest.mark.calling
 @pytest.mark.skipif(TEST_CONDITION, reason=REASON)
+@pytest.mark.skipif(IS_WINDOWS, reason=NT_REASON)
 def test_make_nopass_call(nopass_phone):
     call = nopass_phone.call("answerme")
+    start = time.time()
     while call.state == CallState.DIALING:
         time.sleep(0.1)
+        if start + CALL_TIMEOUT < time.time():
+            raise TimeoutError("Call was not answered before the timeout.")
     assert call.state == CallState.ANSWERED
     call.hangup()
     assert call.state == CallState.ENDED
@@ -231,10 +269,14 @@ def test_make_nopass_call(nopass_phone):
 @pytest.mark.udp
 @pytest.mark.calling
 @pytest.mark.skipif(TEST_CONDITION, reason=REASON)
+@pytest.mark.skipif(IS_WINDOWS, reason=NT_REASON)
 def test_remote_hangup(phone):
     call = phone.call("answerme")
+    start = time.time()
     while call.state == CallState.DIALING:
         time.sleep(0.1)
+        if start + CALL_TIMEOUT < time.time():
+            raise TimeoutError("Call was not answered before the timeout.")
     assert call.state == CallState.ANSWERED
     time.sleep(5)
     assert call.state == CallState.ENDED