Initial version. Effectively proxies SIP, but does not interface with
authorRick van Rein <vanrein@hwdev.(none)>
Wed, 24 Nov 2010 16:42:33 +0000 (16:42 +0000)
committerRick van Rein <vanrein@hwdev.(none)>
Wed, 24 Nov 2010 16:42:33 +0000 (16:42 +0000)
RTPproxy well enough, largely because that software lacks *any* form of
documentation on how to address it from your own software.

Makefile [new file with mode: 0644]
README [new file with mode: 0644]
etc/init.d/sipproxy64 [new file with mode: 0755]
etc/sipproxy64/v4only [new file with mode: 0644]
proxy.c [new file with mode: 0644]
sipproxy64.man [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..b9c494a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,27 @@
+
+OSIPDIR=./osip2/libosip2-3.3.0
+INCDIR=$(OSIPDIR)/include
+LIBOSIP=/usr/lib/libosip2.a
+LIBPARSER=/usr/lib/libosipparser2.a
+LIBTHREAD=/usr/lib/libpthread.a
+# LIBOSIP=./osip2/libosip2-3.3.0/src/osip2/.libs/libosip2.a
+# LIBPARSER=./osip2/libosip2-3.3.0/src/osipparser2/.libs/libosipparser2.a
+# LIBTHREAD=
+CFLAGS=-ggdb3
+LDFLAGS=-pthread
+
+sipproxy64: proxy.o
+       gcc $(LDFLAGS) -o $@ $< $(LIBOSIP) $(LIBPARSER)
+
+proxy.o: proxy.c Makefile
+       gcc -c $(CFLAGS) -I $(INCDIR) -o $@ $<
+
+test: test.c Makefile
+       gcc $(CFLAGS) -I $(INCDIR) -o $@ $< $(LIBOSIP) $(LIBPARSER)
+
+tags: proxy.c
+       ctags --recurse proxy.c osip2/libosip2-3.3.0/src osip2/libosip2-3.3.0/include
+
+clean:
+       rm -f test sipproxy64
+
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..c9a3707
--- /dev/null
+++ b/README
@@ -0,0 +1,109 @@
+==========================================
+README for SIP proxy between IPv4 and IPv6
+==========================================
+
+This software acts as a proxy between SIP phone networks on IPv4 and IPv6.
+It will hardly do a thing but translate addresses and cause RTPproxy to care
+for the media session in a corresponding fashion.
+
+
+Running the proxy
+=================
+
+Information about running SIPproxy64 is contained in the manpage
+sipproxy64.man and this document is about building, installing and
+understanding the source code.
+
+
+Building requirements
+=====================
+
+* POSIX
+* libosip2, libosipparser2
+
+
+Running requirements
+====================
+
+* POSIX with IPv6
+* libosip2, libosipparser2
+* rtpproxy
+
+Make sure that the socket of rtpproxy is accessible for sipproxy64.  If
+rtpproxy runs as a user and group "rtpproxy", it is usually enough to add
+the user running sipproxy64 to that group::
+
+       adduser sipproxy64 -G rtpproxy
+
+
+Mapping addresses
+=================
+
+The conceptual idea of this proxy is that each phone appears to have an
+address in both IPv4 and IPv6 address families.  In situations where no
+IPv6 address is currently present, the proxy will pick up any traffic
+that arrives on the IPv6 address, map addresses contained in SIP messages
+and forward them.  In addition, sessions will be passed through RTPproxy
+and will be translated between address families as well.
+
+Each phone has its own address in both spaces.  So traffic forwarded to
+a particular UDP port on a phone's simulated IPv6 address can simply be
+proxied to the same port on the phone's actual IPv4 address.  The traffic
+can travel in both directions through the proxy; aside from the media
+sessions that are passed through RTPproxy, the proxy does not keep state.
+
+The media traffic all goes through a single IPv4 address and a single
+IPv6 address.  The UDP port on each will be allocated by RTPproxy and
+handed back to the proxy for editing SIP messages before passing them
+on.
+
+To setup an IPv4 phone for registration at an IPv6 registrar, the mapping
+for those phones must be created as an explicit mapping.  Then, if a
+SIP message comes in destined for a particular IPv4 address, this address
+will be mapped to the proper IPv6 address and the SIP message is then passed
+on.
+
+
+Listening for traffic
+=====================
+
+TODO: Which addresses to listen to?
+
+* IPv4 -- any address (or a router address), port 5060
+* IPv6 -- only if part of the address map and locally defined
+
+TODO:  Possibly use the first ipv4/ipv6 pair as proxy address definition;
+       Then, if traffic is forwarded to those, they will always be picked
+       up.  For the other addresses, introduce a local binding for IPv6 only,
+       assuming that the phones will listen on their IPv4 addresses.
+
+
+Rewriting headers
+=================
+
+Headers are changed when they are passed through the proxy.  The following
+is done:
+
+* The header ``Max-forwards`` is lowered by one, and if it reaches zero,
+  the SIP message is discarded.
+
+* The host in ``From``, ``To``, ``Contact`` headers as well as the ``URI``
+  are changed from the address in one address family to the matching address
+  in the other address family.  This will only work inasfar as these are
+  IP addresses.  Names are left untouched.
+
+* The ``User-agent`` header is changed to describe this SIP proxy.
+
+* A ``Via`` header is inserted to ensure that traffic continues to travel
+  through this proxy.  If needed, a ``Via`` header referring to the proxy
+  is removed, and/or a ``Via`` header is used to forward traffic to the next
+  hop in the SIP transaction.
+
+* The ``o=`` and ``m=`` and ``c=`` lines in any SDP attachment are parsed to
+  find any addresses in them; these are then mapped from one address family to
+  the pairing address in the other address family.
+
+* The terms ``IP6`` and ``IP4`` are exchanged in the ``o=`` and ``m=`` and ``c=``
+  lines in SDP attachments.
+
+
diff --git a/etc/init.d/sipproxy64 b/etc/init.d/sipproxy64
new file mode 100755 (executable)
index 0000000..8962b80
--- /dev/null
@@ -0,0 +1,18 @@
+#!/bin/sh
+#
+# SIPproxy64 script for init.d
+#
+# Available commands: start, stop, restart, configtest.
+#
+
+ETCDIR=`dirname "$0"`/../sipproxy64
+
+PHONES=''
+
+for V4ONLY in $( cat "$ETCDIR/v4only" | sed -e 's/[ \t]*//' -e 's/^#.*//' -e '/^$/d' )
+do
+       PHONES="$PHONES $V4ONLY"
+done
+
+echo Phones: $PHONES
+
diff --git a/etc/sipproxy64/v4only b/etc/sipproxy64/v4only
new file mode 100644 (file)
index 0000000..5d7d201
--- /dev/null
@@ -0,0 +1,13 @@
+#
+# In this file, list phones and switches that operate in IPv4-only mode.
+# An IPv6 address will be constructed for them if you don't supply one,
+# or you can give one here if you prefer.
+#
+# Formatting: Lines with <v4addr>=<v6addr>.  No hostnames permitted.
+#
+# The init.d script will bring up a new IPv6 address and start SIPproxy64
+# to service it, thereby simulating IPv6 service for the IPv4-only node.
+#
+
+10.0.0.1=2001:610:66f:6::5060
+
diff --git a/proxy.c b/proxy.c
new file mode 100644 (file)
index 0000000..8b11ec0
--- /dev/null
+++ b/proxy.c
@@ -0,0 +1,1154 @@
+/* $Id:$
+ *
+ * SIPproxy64 -- Proxying SIP traffic between IPv4 and IPv6 networks.
+ *
+ * The program is intended to support IPv4-only phones on IPv6 networks, and vice versa.
+ * It was written for the 0cpm.org project in the belief that it solves problems in going
+ * from an IPv4-based SIP world to a much better IPv6-based SIP world.
+ *
+ * This software is provided as-is under the GNU Public License version 3.  If you need
+ * a more relaxed license, please contact the copyright holder; at the very least, we will
+ * listen to your requests and balance them.  Our general intention is to improve freedom
+ * of users to modify their software and hardware.
+ *
+ * From: Rick van Rein <rick@openfortress.nl>
+ */
+
+
+// TODO: Handle Proxy-Require to detect unknown options
+// TODO: Handle Route as an indication of where to send traffic
+
+
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/select.h>
+#include <sys/stat.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <osip2/osip.h>
+#include <osipparser2/osip_uri.h>
+#include <osipparser2/osip_headers.h>
+#include <osipparser2/sdp_message.h>
+
+
+/* Local structures.
+ */
+struct v4v6map {
+       struct v4v6map *next;
+       struct in_addr  v4addr;
+       struct in6_addr v6addr;
+       int sox;
+       unsigned short flags;
+};
+
+#define MAP_V4BOUND 0x01
+#define MAP_V6BOUND 0x02
+
+
+/* Global variables.
+ */
+char *program;
+
+struct in_addr  v4local, v4uplink;
+struct in6_addr v6local, v6uplink;
+
+char *v4local_str, *v6local_str;
+
+int v4local_sox = -1, v6local_sox = -1;
+
+struct v4v6map *v4v6maplist = NULL;
+unsigned int v4v6maplen = 0;
+
+int seen_u = 0, seen_U = 0;
+
+osip_t *osip;
+
+struct sockaddr_un rtppath;
+
+
+/* Commandline arguments.  See manpage for details.
+ */
+const char *short_opts = "hl:L:u:U:c:";
+struct option long_opts [] = {
+       { "v4local", 1, NULL, 'l' },
+       { "v6local", 1, NULL, 'L' },
+       { "v4uplink",  1, NULL, 'u' },
+       { "v6uplink",  1, NULL, 'U' },
+       { "rtpctl",   1, NULL, 'c' },
+       { "help",     0, NULL, 'h' },
+       { NULL,       0, NULL,  0  },
+};
+
+
+/* Find a mapping from an IPv4 address to an IPv6 address.  Return NULL if not found.
+ */
+struct in6_addr *map_4to6 (const struct in_addr *in) {
+       struct v4v6map *map = v4v6maplist;
+       while (map) {
+               if (memcmp (&map->v4addr, in, sizeof (struct in_addr)) == 0) {
+                       return &map->v6addr;
+               }
+               map = map->next;
+       }
+       return NULL;
+}
+
+
+/* Find a socket based on an IPv4 address.  Return -1 if not found.
+ */
+int map_4to6_sox (const struct in_addr *in) {
+       struct v4v6map *map = v4v6maplist;
+       while (map) {
+               if (memcmp (&map->v4addr, in, sizeof (struct in_addr)) == 0) {
+                       return map->sox;
+               }
+               map = map->next;
+       }
+       return -1;
+}
+
+
+/* Find a mapping from an IPv6 address to an IPv4 address.  Return NULL if not found.
+ */
+struct in_addr *map_6to4 (const struct in6_addr *in) {
+       struct v4v6map *map = v4v6maplist;
+       while (map) {
+               if (memcmp (&map->v6addr, in, sizeof (struct in6_addr)) == 0) {
+                       return &map->v4addr;
+               }
+               map = map->next;
+       }
+       return NULL;
+}
+
+
+/* Find a socket based on an IPv6 address.  Return -1 if not found.
+ */
+int map_6to4_sox (const struct in6_addr *in) {
+       struct v4v6map *map = v4v6maplist;
+       while (map) {
+               if (memcmp (&map->v6addr, in, sizeof (struct in6_addr)) == 0) {
+                       return map->sox;
+               }
+               map = map->next;
+       }
+       return -1;
+}
+
+
+/* Interact with the RTPproxy over its command channel.  Return the response in the buffer,
+ * terminated with a 0.  Return 1 on success or 0 on failure.
+ */
+int rtpctl_io (char buf [201]) {
+       //
+       // Connect to the RTPproxy through a UNIX socket socket
+       int rtpctl = socket (PF_UNIX, SOCK_STREAM, 0);
+       if (rtpctl == -1) {
+               fprintf (stderr, "Failed to access RTPproxy: %s\n", strerror (errno));
+               return 0;
+       } else if (connect (rtpctl, (struct sockaddr *) &rtppath, sizeof (rtppath)) == 1) {
+               fprintf (stderr, "Failed to bind to RTPproxy: %s\n", strerror (errno));
+               close (rtpctl);
+               return 0;
+       }
+       //
+       // Send the command to the RTPproxy socket
+       buf [200] = 0;
+       printf ("RTPproxy > %s", buf);
+       size_t buflen = strlen (buf);
+       errno = 0;
+       if (write (rtpctl, buf, buflen) < buflen) {
+               printf ("Failure writing command to RTPproxy: %s\n", strerror (errno));
+               // At least the '\n' has not been written, so the next write
+               // will complete the line (leading to garbish output); but
+               // at least there is no output from this write alone.
+               buf [0] = 0;
+               close (rtpctl);
+               return 0;
+       }
+       //
+       // Read back the reply from the RTPproxy socket and check for success
+       buflen = read (rtpctl, buf, sizeof (buf)-1);
+       close (rtpctl);
+       if (buflen == -1) {
+               printf ("Failure reading response from RTPproxy: %s\n", strerror (errno));
+               buf [0] = 0;
+               return 0;
+       }
+       buf [buflen] = 0;
+       printf ("RTPproxy < %s\n", buf);
+       return 1;
+}
+
+
+/* Update the RTPproxy over its command channel, sending it information about a connection
+ * to establish.  The address provided may be an IPv4 or IPv6 address, for values AF_INET
+ * and AF_INET6 in addrfam, respectively.  The port is in host by order.  The To: tag is
+ * only known in reply traffic, so it is optional (meaning, it may be set to NULL).
+ * The return value is a nonzero string with the port number opened, or NULL on failure.
+ */
+char *rtpctl_update (int addrfam, void *addr, char *port,
+                               char *call_id, char *from_tag, char *opt_to_tag) {
+       static char buf [200+1];
+       //
+       // Create an Update session command, with Asymmetric flag and optional 6 flag
+       snprintf (buf, 200, "UA%sL%s %s %s %s %s%s%s\n",
+                       (addrfam == AF_INET6)? "EI6": "IE",
+                       (addrfam == AF_INET6)? v6local_str: v4local_str,
+                       call_id,
+                       addr,
+                       port,
+                       from_tag,
+                       opt_to_tag? " "       : "",
+                       opt_to_tag? opt_to_tag: "");
+       //
+       // Actuall pass the command through the RTPproxy
+       if (!rtpctl_io (buf)) {
+               return NULL;
+       }
+       char *space = strchr (buf, ' ');
+       if (space) {
+               *space = 0;
+               return buf;
+       } else {
+               return NULL;
+       }
+}
+
+
+/* Delete a connection from the RTPproxy over its command channel.  The connection is
+ * identified in the same way as when updating the RTPproxy.  The SIP traffic usually
+ * takes care of duplicting that information.
+ */
+void rtpctl_delete (char *call_id, char *from_tag, char *opt_to_tag) {
+       char buf [200+1];
+       //
+       // Create a Delete session command and send an emtpy to_tag if needed
+       snprintf (buf, 200, "D %s %s %s\n",
+                       call_id,
+                       from_tag,
+                       opt_to_tag? opt_to_tag: "");
+       //
+       // Actuall pass the command through the RTPproxy
+       if (!rtpctl_io (buf)) {
+               printf ("RTPproxy deletion of session failed.  Inactivity will solve this.\n");
+       }
+}
+
+
+/* Insert an informational reply or an error, and send it to the sender of a request,
+ * using the provided code and description.
+ */
+void sip_reply (osip_message_t *request, unsigned short code, char *descr, struct v4v6map *recvmap, struct msghdr *target) {
+       osip_message_t *reply = NULL;
+       osip_contact_t *sender = NULL;
+       osip_call_id_t *callid = osip_message_get_call_id (request);
+       osip_from_t *from = osip_message_get_from (request);
+       osip_to_t *to = osip_message_get_to (request);
+       osip_cseq_t *cseq = osip_message_get_cseq (request);
+       char *method = osip_message_get_method (request);
+       int err = (method == NULL) || (callid==NULL) || (from==NULL) || (to==NULL) || (cseq == NULL);
+       err = err || osip_message_init (&reply);
+       err = err || osip_message_get_contact (request, 0, &sender);
+       char *fromstr;
+       char *tostr;
+       char *senderstr;
+       if (!err) {
+               err = err || osip_from_to_str (from, &fromstr);
+               err = err || osip_to_to_str (to, &tostr);
+               err = err || osip_contact_to_str (sender, &senderstr);
+       }
+       if (!err) {
+               char cseqbuf [101];
+               snprintf (cseqbuf, 100, "%s %s", cseq->number, method);
+               osip_message_set_cseq (reply, cseqbuf);
+               osip_message_set_status_code (reply, code);
+               osip_message_set_reason_phrase (reply, descr);
+               osip_message_set_uri (reply, sender->url);
+               osip_message_set_from (reply, fromstr);
+               osip_message_set_to (reply, tostr);
+               osip_message_set_contact (reply, senderstr);
+               osip_message_set_user_agent (reply, "SIPproxy64 from http://devel.0cpm.org/sipproxy64/");
+               osip_message_append_via (reply, "SIP/2.0/UDP [2001:610:66f:6::18]:5060;branch=1234567");
+       }
+       // TODO: More headers? Maybe Via: or Route: ?
+       char *replystr = NULL;
+       size_t replystrlen = 0;
+       err = err || osip_message_to_str (reply, &replystr, &replystrlen);
+       if (!err) {
+               if (target) {
+                       if (recvmap->flags & MAP_V4BOUND) {
+                               //
+                               // Replying over IPv4
+                               char v4ip [INET_ADDRSTRLEN];
+                               inet_ntop (AF_INET,  &((struct sockaddr_in  *) target->msg_name)->sin_addr,  v4ip, sizeof (v4ip));
+                               printf ("\nReplying over IPv4 to %s from socket %d (%d %s)\n", v4ip, v4local_sox, code, descr);
+                               fwrite (replystr, replystrlen, 1, stdout);
+                               ssize_t sent = sendto (recvmap->sox, replystr, replystrlen, MSG_EOR, target->msg_name, sizeof (struct sockaddr_in ));
+                               if (sent < replystrlen) {
+                                       printf ("Sent only %d out of %d bytes\n", sent, replystrlen);
+                               }
+                       } else {
+                               //
+                               // Replying over IPv6
+                               char v6ip [INET6_ADDRSTRLEN];
+                               inet_ntop (AF_INET6, &((struct sockaddr_in6 *) target->msg_name)->sin6_addr, v6ip, sizeof (v6ip));
+                               printf ("\nReplying over IPv6 to %s from socket %d (%d %s)\n", v6ip, v6local_sox, code, descr);
+                               fwrite (replystr, replystrlen, 1, stdout);
+                               ssize_t sent = sendto (v6local_sox, replystr, replystrlen, MSG_EOR, (struct sockaddr *) target->msg_name, sizeof (struct sockaddr_in6));
+                               if (sent < replystrlen) {
+                                       printf ("Sent only %d out of %d bytes\n", sent, replystrlen);
+                               }
+                       }
+               } else {
+                       fprintf (stderr, "Failed to reply in lieu of address to send to\n");
+               }
+       }
+}
+
+/* Prepend a new mapping to the list of IPv4/IPv6 bidirectional mappings.
+ * Search structures are not optimised, because they will not usually cover long lists.
+ * If changes are needed, contact the author -- he may have overlooked your situation.
+ */
+void insert_mapping (struct in_addr *v4addr, struct in6_addr *v6addr) {
+       if (map_4to6 (v4addr)) {
+               char v4ip [INET_ADDRSTRLEN];
+               inet_ntop (AF_INET, v4addr, v4ip, sizeof (v4ip));
+               fprintf (stderr, "%s: Attempt to map IPv4 address %s to multiple IPv6 addresses\n", program, v4ip);
+               exit (1);
+       }
+       if (map_6to4 (v6addr)) {
+               char v6ip [INET6_ADDRSTRLEN];
+               inet_ntop (AF_INET6, v6addr, v6ip, sizeof (v6ip));
+               fprintf (stderr, "%s: Attempt to map IPv6 address %s to multiple IPv4 addresses\n", program, v6ip);
+               exit (1);
+       }
+       struct v4v6map *map = malloc (sizeof (struct v4v6map));
+       if (!map) {
+               fprintf (stderr, "%s: Out of memory\n", program);
+               exit (1);
+       }
+       memcpy (&map->v4addr, v4addr, sizeof (*v4addr));
+       memcpy (&map->v6addr, v6addr, sizeof (*v6addr));
+       map->sox = -1;
+       map->flags = 0;
+       map->next = v4v6maplist;
+       v4v6maplist = map;
+       v4v6maplen++;
+}
+
+
+/* Map an IPv4 address into an IPv6 address or, if it is not recognisable as such,
+ * report failure to do so and leave it up to outer layers to handle it.
+ */
+int map_uri_host_4to6 (osip_uri_t *uri) {
+       //
+       // Try to parse the host part of the URI as an IPv4 address
+       struct in_addr v4ip;
+       if (inet_pton (AF_INET, osip_uri_get_host (uri), &v4ip) != 1) {
+               printf ("map_uri_host_4to6 -> Failed to parse %s as IPv4 address\n", osip_uri_get_host (uri)); //DEBUG
+               return 0;
+       }
+       //
+       // Try to map the IPv4 address to an IPv6 address
+       struct in6_addr *v6ip = map_4to6 (&v4ip);
+       if (!v6ip) {
+               printf ("map_uri_host_4to6 -> Failed to map %s to an IPv6 address\n", osip_uri_get_host (uri)); //DEBUG
+               return 0;
+       }
+       //
+       // Replace the host with the new address
+       // TODO: Why not store the strings alongside the binary addresses in the map?
+       char buf [INET6_ADDRSTRLEN];
+       inet_ntop (AF_INET6, v6ip, buf, sizeof (buf));
+       printf ("map_uri_host_4to6 -> Succeeded to map %s to %s\n", osip_uri_get_host (uri), buf); //DEBUG
+       osip_uri_set_host (uri, strdup (buf));
+       //
+       // Report success
+       return 1;
+}
+
+
+/* Map an IPv6 address into an IPv4 address or, if it is not recognisable as such,
+ * report failure to do so and leave it up to outer layers to handle it.
+ */
+int map_uri_host_6to4 (osip_uri_t *uri) {
+       //
+       // Try to parse the host part of the URI as an IPv6 address
+       struct in6_addr v6ip;
+       if (inet_pton (AF_INET6, osip_uri_get_host (uri), &v6ip) != 1) {
+               printf ("map_uri_host_6to4 -> Failed to parse %s as IPv6 address\n", osip_uri_get_host (uri)); //DEBUG
+               return 0;
+       }
+       //
+       // Try to map the IPv6 address to an IPv4 address
+       struct in_addr *v4ip = map_6to4 (&v6ip);
+       if (!v4ip) {
+               printf ("map_uri_host_6to4 -> Failed to map %s to an IPv4 address\n", osip_uri_get_host (uri)); //DEBUG
+               return 0;
+       }
+       //
+       // Replace the host with the new address
+       // TODO: Why not store the strings alongside the binary addresses in the map?
+       char buf [INET6_ADDRSTRLEN];
+       inet_ntop (AF_INET, v4ip, buf, sizeof (buf));
+       printf ("map_uri_host_6to4 -> Succeeded to map %s to %s\n", osip_uri_get_host (uri), buf); //DEBUG
+       osip_uri_set_host (uri, strdup (buf));
+       //
+       // Report success
+       return 1;
+}
+
+
+/* Handle an incoming SIP message.  Read it from the socket of the given map,
+ * perform sanity checks, rewrite addresses, add a Via header and pass it on.
+ * On the fly, if need be, initiate or terminate RTPproxy.  Also, where
+ * applicable, send notifications back to the originator.
+ * TODO: Parameterise all AF_INET / AF_INET6 differences away, thus avoiding double code.
+ */
+void handle_sip (struct v4v6map *recvmap) {
+       //
+       // Pickup the message and check it is not excessively big
+       char buf [SIP_MESSAGE_MAX_LENGTH + 2];
+       size_t buflen;
+       struct iovec iov = { buf, sizeof (buf) };
+       struct sockaddr_in6 addr6;
+       struct sockaddr_in  addr4;
+       struct msghdr mh = { &addr6, sizeof (addr6), &iov, 1, NULL, 0, 0 };
+       if (recvmap->flags & MAP_V4BOUND) {
+               mh.msg_name = &addr4;
+               mh.msg_namelen = sizeof (addr4);
+       }
+       buflen = recvmsg (recvmap->sox, &mh, MSG_DONTWAIT);
+       char ipstr [INET_ADDRSTRLEN+INET6_ADDRSTRLEN];  // Lacking a "max" macro
+       if (recvmap->flags & MAP_V4BOUND) {
+               inet_ntop (AF_INET,  &addr4.sin_addr,  ipstr, sizeof (ipstr));
+       } else {
+               inet_ntop (AF_INET6, &addr6.sin6_addr, ipstr, sizeof (ipstr));
+       }
+       printf ("\n==================== NEW MESSAGE ===================\n\nReceived over %s from %s to socket %d\n", (recvmap->flags & MAP_V4BOUND)? "IPv4": "IPv6", ipstr, recvmap->sox); //DEBUG
+       fwrite (buf, buflen, 1, stdout);        //DEBUG
+       if (buflen == -1) {
+               fprintf (stderr, "%s: Failure while receiving SIP message: %s\n", program, strerror (errno));
+               return;
+       }
+       if (buflen > SIP_MESSAGE_MAX_LENGTH) {
+               //TODO:Too_early// sip_reply (sip, 513, "Message Too Large", recvmap, &mh);
+               sip_reply (NULL, 513, "Message Too Large", recvmap, &mh);
+               return;
+       }
+       //
+       // Parse the message, and extract vital information from it
+       int disturbed = 0;
+       osip_message_t *sip = NULL;
+       disturbed = disturbed || osip_message_init (&sip);
+       disturbed = disturbed || osip_message_parse (sip, buf, buflen);
+       char *method = NULL;
+       int reason = 0;
+       int resp = 0, code = 0;
+       osip_header_t *maxfwd = NULL;
+       int maxfwdval = 0;
+       osip_uri_t *uri = NULL;
+       osip_from_t *from = NULL;
+       osip_to_t *to = NULL;
+       osip_contact_t *contact = NULL;
+       osip_content_type_t *mimetype = NULL;
+       if (!disturbed) {
+               //
+               // We got it in good order, so respond to INVITE methods with "100 Am on top of it"
+#if 0
+// As per RFC 3621, "A stateless UAS MUST NOT send provisional (1xx) responses."
+               if (MSG_IS_INVITE (sip)) {
+                       sip_reply (sip, 100, (recvmap->flags & MAP_V4BOUND)? "Proxying from IPv4 to IPv6": "Proxying from IPv6 to IPv4", recvmap, &mh);
+               }
+#endif
+               //
+               // Fetch header information
+               resp = MSG_IS_RESPONSE (sip);
+               if (resp) {
+                       reason = osip_message_get_status_code (sip);
+               } else {
+                       method = osip_message_get_method (sip);
+               }
+               disturbed = disturbed || (resp? (reason == 0): (method == NULL));
+               //
+               // Fetch and update Max-Forwards
+               osip_header_t *maxfwdhdr = NULL;
+               osip_message_get_max_forwards (sip, 0, &maxfwdhdr);
+               maxfwdval = maxfwdhdr? atoi (maxfwdhdr->hvalue): 50;
+               if (maxfwdval == 0) {
+                       sip_reply (sip, 483, "Too Many Hops", recvmap, &mh);
+                       return;
+               }
+               if (maxfwdhdr) {
+                       sprintf (maxfwdhdr->hvalue, "%d", maxfwdval-1);
+               }
+       }
+       if (!disturbed) {
+               //
+               // Set the User-Agent header with our own identity
+               //TODO// osip_message_set_user_agent (sip, "SIPproxy64 IPv6/IPv4 proxy from 0cpm.org");
+               //
+               // Fetch the contact addresses that may contain IP addresses
+               if (MSG_IS_INVITE (sip)) {
+                       disturbed = disturbed || osip_message_get_contact (sip, 0, &contact);
+               }
+               from = osip_message_get_from (sip);
+               to   = osip_message_get_to   (sip);
+               uri  = osip_message_get_uri  (sip);
+               mimetype = osip_message_get_content_type (sip);
+               if (resp) {
+                       disturbed = disturbed || (to == NULL) || (from == NULL);
+               } else {
+                       disturbed = disturbed || (to == NULL) || (from == NULL) || (uri == NULL);
+               }
+       }
+       sdp_message_t *sdp = NULL;
+       if ((!disturbed) && mimetype
+                               && !strcmp (mimetype->type,    "application")
+                               && !strcmp (mimetype->subtype, "sdp")) {
+               //
+               // Since there is an SDP attachment, parse and handle it too
+               osip_body_t *body;
+               disturbed = disturbed || osip_message_get_body (sip, 0, &body);
+               disturbed = disturbed || sdp_message_init (&sdp);
+               disturbed = disturbed || sdp_message_parse (sdp, body->body);
+               printf ("Parsed the body\n"); //DEBUG
+               disturbed = disturbed || strcmp (sdp_message_v_version_get (sdp), "0");
+       }
+       int require_resolver = 0;
+       if (!disturbed) {
+               //
+               // Process URI and headers: Contact, From, To.
+               // TODO: URI.host translation based on v4v6map, detecting if a proxy or via is required
+               if (recvmap->flags & MAP_V4BOUND) {
+                       //
+                       // Traffic from IPv4 to IPv6
+                       if (!resp) {
+                               require_resolver = !map_uri_host_4to6 (uri         ) || require_resolver;
+                       }
+                       require_resolver = !map_uri_host_4to6 (from   ->url) || require_resolver;
+                       require_resolver = !map_uri_host_4to6 (to     ->url) || require_resolver;
+                       if (contact) {
+                               require_resolver = !map_uri_host_4to6 (contact->url) || require_resolver;
+                       }
+               } else {
+                       //
+                       // Traffic from IPv6 to IPv4
+                       if (!resp) {
+                               require_resolver = !map_uri_host_6to4 (uri         ) || require_resolver;
+                       }
+                       require_resolver = !map_uri_host_6to4 (from   ->url) || require_resolver;
+                       require_resolver = !map_uri_host_6to4 (to     ->url) || require_resolver;
+                       if (contact) {
+                               require_resolver = !map_uri_host_6to4 (contact->url) || require_resolver;
+                       }
+               }
+       }
+       if (!disturbed) {
+               //
+               // Add routing headers as needed
+               osip_route_t *route_in, *route_fwd;
+               osip_message_get_route (sip, 0, &route_in);
+               osip_message_get_route (sip, 1, &route_fwd);
+               if (route_fwd) {
+                       osip_list_remove (&sip->routes, 1);
+                       printf ("Removing Route: header #1\n");
+               }
+               if (route_in) {
+                       osip_list_remove (&sip->routes, 0);
+                       printf ("Removing Route: header #0\n");
+               }
+               if (resp) {
+                       osip_list_remove (&sip->vias, 0);
+                       printf ("Removing Via: header #0\n");
+               } else {
+                       // TODO: osip_message_append_via with the outgoing host and a real branch
+                       char via [101];
+                       if (recvmap->flags & MAP_V4BOUND) {
+                               snprintf (via, 100, "SIP/2.0/UDP [%s]:5060;branch=1234567", v6local_str);
+                       } else {
+                               snprintf (via, 100, "SIP/2.0/UDP %s:5060;branch=1234567",   v4local_str);
+                       }
+                       osip_message_append_via (sip, via);
+                       char v4route [101];
+                       char v6route [101];
+                       // TODO: Not the proper addresses!
+                       snprintf (v4route, 100, "<sip:%s;lr>",   v4local_str);
+                       //TEST// snprintf (v6route, 100, "<sip:[%s];lr>", v6local_str);
+                       snprintf (v6route, 100, "<sip:[%s];lr>", "2001:610:66f:6::186");
+                       osip_message_set_record_route (sip, (recvmap->flags & MAP_V4BOUND)? v6route: v4route);
+                       osip_message_set_record_route (sip, (recvmap->flags & MAP_V4BOUND)? v4route: v6route);
+               }
+       }
+       char *c_nettp, *c_addrtp, *c_addr, *m_port;
+       sdp_connection_t *cnx;
+       int cnxpos;
+       if ((!disturbed) && sdp) {
+               //
+               // TODO: Proxy SDP address/port pairs through RTPproxy
+               // TODO: Assuming a single c= and a single m= as RTPproxy may be incapable...
+               // pos_media==-1 refers to a c= prefixing the first m=; 0.. are subsequent m= and c=
+               cnxpos = -1;
+               cnx = sdp_message_connection_get (sdp, cnxpos, 0);
+               if (!cnx) {
+                       cnxpos = 0;
+                       cnx = sdp_message_connection_get (sdp, cnxpos, 0);
+               }
+               disturbed = disturbed || !cnx;
+               c_nettp  = cnx->c_nettype;
+               c_addrtp = cnx->c_addrtype;
+               c_addr   = cnx->c_addr;
+               m_port   = sdp_message_m_port_get     (sdp,  0);
+#if 0
+               c_nettp  = sdp_message_c_nettype_get  (sdp, -1, 0);
+               c_addrtp = sdp_message_c_addrtype_get (sdp, -1, 0);
+               c_addr   = sdp_message_c_addr_get     (sdp, -1, 0);
+               m_port   = sdp_message_m_port_get     (sdp,  0);
+               if (! (c_nettp && c_addrtp && c_addr)) {
+                       c_nettp = sdp_message_c_nettype_get  (sdp, 0, 0);
+                       c_nettp = sdp_message_c_addrtype_get (sdp, 0, 0);
+                       c_nettp = sdp_message_c_addr_get     (sdp, 0, 0);
+               }
+#endif
+               if (! (c_nettp && c_addrtp && c_addr)) {
+                       fprintf (stderr, "Missing c= line in SDP content -- ignoring SDP completely\n");
+                       sdp = NULL;
+               }
+               if (!m_port) {
+                       fprintf (stderr, "Missing m= line in SDP content -- ignoring SDP completely\n");
+                       sdp = NULL;
+               }
+       }
+       int c_af_inetX;
+       if ((!disturbed) && sdp) {
+               if (c_nettp && strcmp (c_nettp, "IN")) {
+                       fprintf (stderr, "Strange kind of network %s requested -- not the Internet!\n", c_nettp);
+                       disturbed = 1;
+               }
+               if (recvmap->flags & MAP_V4BOUND) {
+                       //
+                       // Traffic from IPv4 to IPv6
+                       if (m_port && c_addrtp && !strcmp (c_addrtp, "IP4")) {
+                               printf ("Got c= with IP4 address %s and m= with port %s\n", c_addr, m_port);
+                               c_af_inetX = AF_INET;
+                       } else {
+                               disturbed = 1;
+                       }
+               } else {
+                       //
+                       // Traffic from IPv6 to IPv4
+                       char *v6addr = sdp_message_c_addr_get (sdp, 0, 0);
+                       if (m_port && c_addrtp && !strcmp (c_addrtp, "IP6")) {
+                               printf ("Got c= with IP6 address %s and m= with port %s\n", c_addr, m_port);
+                               c_af_inetX = AF_INET6;
+                       } else {
+                               disturbed = 1;
+                       }
+               }
+               //
+               // Collect the information for call identification: id_code, from_tag, [to_tag]
+               osip_uri_param_t *from_tag;
+               osip_uri_param_t *to_tag;
+               osip_from_get_tag (from, &from_tag);
+               osip_to_get_tag (to, &to_tag);
+               osip_call_id_t *call_id_h = osip_message_get_call_id (sip);
+               char *call_id;
+               disturbed = disturbed || osip_call_id_to_str (call_id_h, &call_id);
+               //
+               // Create, update or delete a mapping in RTPproxy
+               if (call_id && from_tag && !disturbed) {
+                       if (!resp) {
+                               //
+                               // Handle initiations
+                               if (strcmp (method, "INVITE") == 0 && sdp) {
+                                       char *rtpport = rtpctl_update (c_af_inetX, c_addr, m_port, call_id, from_tag->gvalue, NULL);
+                                       if (rtpport) {
+                                               printf ("Expecting media on port %s over %s\n", rtpport, (c_af_inetX==AF_INET)? "IPv6": "IPv4");
+                                               sdp_message_m_port_set (sdp, 0, rtpport);
+                                               cnx->c_addrtype [2] ^= '4' ^ '6';
+                                               cnx->c_addr = (c_af_inetX==AF_INET)? v6local_str: v4local_str;
+#if 0
+                                               sdp_message_c_connection_add (sdp, cnxpos,
+                                                       c_nettp,
+                                                       (c_af_inetX==AF_INET)? "IP6": "IP4", 
+                                                       (c_af_inetX==AF_INET)? v6local_str: v4local_str,
+                                                       NULL, NULL);
+#endif
+                                       }
+                                       char ibuf [200+1]; ibuf [0] = 'I'; ibuf [1] = '\n' ; ibuf [2] = 0; rtpctl_io (ibuf); //DEBUG
+                               }
+                               if (strcmp (method, "BYE") == 0) {
+                                       // TODO: Make sure that "BYE" drops RTP connection
+                                       rtpctl_delete (call_id, from_tag->gvalue, to_tag->gvalue);
+                                       char ibuf [200+1]; ibuf [0] = 'I'; ibuf [1] = '\n' ; ibuf [2] = 0; rtpctl_io (ibuf); //DEBUG
+                               }
+                               if (strcmp (method, "CANCEL") == 0) {
+                                       // TODO: Make sure that "CANCEL" drops RTP connection (or...?)
+                                       rtpctl_delete (call_id, from_tag->gvalue, to_tag->gvalue);
+                                       char ibuf [200+1]; ibuf [0] = 'I'; ibuf [1] = '\n' ; ibuf [2] = 0; rtpctl_io (ibuf); //DEBUG
+                               }
+                       } else {
+                               //
+                               // Handle responses
+                               if (reason == 200 && sdp) {             // 200 OK
+                                       char *rtpport = rtpctl_update (c_af_inetX, c_addr, m_port, call_id, from_tag->gvalue, to_tag->gvalue);
+                                       if (rtpport) {
+                                               printf ("Expecting media on port %s over %s\n", rtpport, (c_af_inetX==AF_INET)? "IPv6": "IPv4");
+                                               sdp_message_m_port_set (sdp, 0, rtpport);
+                                               cnx->c_addrtype [2] ^= '4' ^ '6';
+                                               cnx->c_addr = (c_af_inetX==AF_INET)? v6local_str: v4local_str;
+#if 0
+                                               sdp_message_c_connection_add (sdp, 0,
+                                                       c_nettp,
+                                                       (c_af_inetX==AF_INET)? "IP6": "IP4", 
+                                                       (c_af_inetX==AF_INET)? v6local_str: v4local_str,
+                                                       NULL, NULL);
+#endif
+                                       }
+                                       char ibuf [200+1]; ibuf [0] = 'I'; ibuf [1] = '\n' ; ibuf [2] = 0; rtpctl_io (ibuf); //DEBUG
+                               }
+                       }
+               }
+               char *sdp_newstr;
+               disturbed = disturbed || sdp_message_to_str (sdp, &sdp_newstr);
+               if (!disturbed) {
+                       osip_list_init (&(sip)->bodies);
+                       osip_message_set_body (sip, sdp_newstr, strlen (sdp_newstr));
+               }
+       }
+       if (!disturbed) {
+               char *newbuf;
+               size_t newbuflen;
+               osip_message_to_str (sip, &newbuf, &newbuflen);
+               // TODO: Probably need more subtlety in where to forward to (Via, Record-route, ...)
+               if (recvmap->flags & MAP_V4BOUND) {
+                       //
+                       // IPv4 is forwarded as IPv6.  First, from what sender address?
+                       int sender_sox = -1;
+                       if (recvmap->sox != v4local_sox) {
+                               //
+                               // Received on IPv4 phone address; forward from general IPv6 address
+                               sender_sox = v6local_sox;
+                       } else {
+                               //
+                               // Received from IPv4 phone address? Then forward from mapped phone
+                               sender_sox = map_4to6_sox (&addr4.sin_addr);
+                               if (sender_sox == -1) {
+                                       //
+                                       // Not received from IPv4 phone.  Forward from general IPv6
+                                       sender_sox = v6local_sox;
+                               }
+                       }
+                       //
+                       // IPv4 is forwarded as IPv6.  Second, to what target address?
+                       struct in6_addr *target = NULL;
+                       if (recvmap->sox != v4local_sox) {
+                               //
+                               // Received on IPv4 phone address; target at mapped IPv6 address
+                               target = &recvmap->v6addr;
+                       } else {
+                               //
+                               // TODO: If Route: headers exist, follow those (Careful, INVITE)
+                               //
+                               // Second attempt.  Is there a generic uplink on the IPv6 side?
+                               if (seen_U) {//TODO:Proper check?
+                                       target = &v6uplink;
+                               } else {
+                                       sip_reply (sip, 488, "No Generic Uplink Available", recvmap, &mh);
+                                       return;
+                               }
+                       }
+                       char v6ip [INET6_ADDRSTRLEN];
+                       inet_ntop (AF_INET6, target, v6ip, sizeof (v6ip));
+                       printf ("\nForwarding over IPv6 to %s from socket %d\n", v6ip, v6local_sox);
+                       fwrite (newbuf, newbuflen, 1, stdout);  //DEBUG
+                       struct sockaddr_in6 v6dest;
+                       memset (&v6dest, 0, sizeof (v6dest));
+                       v6dest.sin6_family = AF_INET6;
+                       // v6dest.sin6_port = htons (5065);     // TODO: 5060 or receiving port
+                       v6dest.sin6_port = htons (5060);        // TODO: 5060 or receiving port
+                       memcpy (&v6dest.sin6_addr, target, sizeof (v6dest.sin6_addr));
+                       ssize_t sent = sendto (sender_sox, newbuf, newbuflen, MSG_EOR, (struct sockaddr *) &v6dest, sizeof (v6dest));
+                       if (sent < newbuflen) {
+                               printf ("Sent only %d out of %d bytes\n", sent, newbuflen);
+                       }
+               } else {
+                       //
+                       // IPv6 is forwarded as IPv4.  First, from what sender address?
+                       int sender_sox = -1;
+                       if (recvmap->sox != v6local_sox) {
+                               //
+                               // Received on IPv6 phone address; forward from general IPv4 address
+                               sender_sox = v4local_sox;
+                       } else {
+                               //
+                               // Received from IPv6 phone address?  Then forward from mapped phone
+                               sender_sox = map_6to4_sox (&addr6.sin6_addr);
+                               if (sender_sox == -1) {
+                                       //
+                                       // Not received from IPv6 phone.  Forward from general IPv4
+                                       sender_sox = v4local_sox;
+                               }
+                       }
+                       //
+                       // IPv6 is forwarded as IPv4.  Second, to what target address?
+                       struct in_addr *target = NULL;
+                       if (recvmap->sox != v6local_sox) {
+                               //
+                               // Received on IPv6 phone address; target at mapped IPv4 address
+                               target = &recvmap->v4addr;
+                       } else {
+                               //
+                               // TODO: If Route: headers exist, follow those (Careful, INVITE)
+                               //
+                               // Second attempt.  Is there a generic uplink on the IPv4 side?
+                               if (seen_u) {//TODO:Proper check?
+                                       target = &v4uplink;
+                               } else {
+                                       sip_reply (sip, 488, "No Generic Uplink Available", recvmap, &mh);
+                                       return;
+                               }
+                       }
+                       char v4ip [INET_ADDRSTRLEN];
+                       inet_ntop (AF_INET,  target, v4ip, sizeof (v4ip));
+                       inet_ntop (AF_INET, &recvmap->v4addr, v4ip, sizeof (v4ip));
+                       printf ("\nForwarding over IPv4 to %s from socket %d\n", v4ip, v4local_sox);
+                       fwrite (newbuf, newbuflen, 1, stdout);  //DEBUG
+                       struct sockaddr_in v4dest;
+                       memset (&v4dest, 0, sizeof (v4dest));
+                       v4dest.sin_family = AF_INET;
+                       //v4dest.sin_port = htons (5065);               // TODO: 5060 or receiving port
+                       v4dest.sin_port = htons (5060);         // TODO: 5060 or receiving port
+                       memcpy (&v4dest.sin_addr, target, sizeof (v4dest.sin_addr));
+                       ssize_t sent = sendto (sender_sox, newbuf, newbuflen, MSG_EOR, (struct sockaddr *) &v4dest, sizeof (v4dest));
+                       if (sent < newbuflen) {
+                               printf ("Sent only %d out of %d bytes\n", sent, newbuflen);
+                       }
+               }
+       } else {
+               sip_reply (sip, 500, "Server Internal Error", recvmap, &mh);
+       }
+       //
+       // Cleanup after handling the message
+       if (sip) {
+               // TODO:CRASH:TheOneThingIThoughtIUnderstood: osip_message_free (sip);
+       }
+}
+
+
+/* Run the proxy daemon.  Listen to all the sockets that have been opened, and
+ * upon arrival of a SIP message, handle it.
+ */
+void run_proxy (void) {
+       int nfds = 0;
+       struct v4v6map *map = v4v6maplist;
+       while (map) {
+               if (map->sox > nfds) {
+                       nfds = map->sox + 1;
+               }
+               map = map->next;
+       }
+       //
+       // Be a good daemon.  Loop forever.  Build & Handle <--> Wait & Listen.
+       fd_set fdset;
+       FD_ZERO (&fdset);
+       while (1) {
+               //
+               // Build an fd_set of sockets to listen to
+               map = v4v6maplist;
+               while (map) {
+                       if (FD_ISSET (map->sox, &fdset)) {
+                               handle_sip (map);
+                               fflush (stdout);
+                       } else {
+                               FD_SET (map->sox, &fdset);
+                       }
+                       map = map->next;
+               }
+               //
+               // Now listen to the sockets in the fd_set and process output
+               int ready = pselect (nfds, &fdset, NULL, NULL, NULL, NULL);
+       }
+}
+
+
+/* Create sockets and try to listen to the various addresses presented.
+ * Demand that precisely one endpoint of each mapping binds properly.
+ */
+void open_sockets (void) {
+       int sane = 1;
+       int i;
+       struct v4v6map *map = v4v6maplist;
+       for (i=0; i<v4v6maplen; map=map->next, i++) {
+               int v4sox, v6sox;
+               v4sox = socket (PF_INET,  SOCK_DGRAM, 0);
+               v6sox = socket (PF_INET6, SOCK_DGRAM, 0);
+               if ((v4sox == -1) || (v6sox == -1)) {
+                       fprintf (stderr, "%s: Failed to allocate sockets: %s\n", program, strerror (errno));
+                       sane = 0;
+                       continue;
+               }
+               struct sockaddr_in  v4sa;
+               struct sockaddr_in6 v6sa;
+               memset (&v4sa, 0, sizeof (v4sa));
+               memset (&v6sa, 0, sizeof (v6sa));
+               v4sa.sin_family  = AF_INET;
+               v6sa.sin6_family = AF_INET6;
+               memcpy (&v4sa.sin_addr,  &map->v4addr, sizeof (struct in_addr ));
+               memcpy (&v6sa.sin6_addr, &map->v6addr, sizeof (struct in6_addr));
+               v4sa.sin_port  = htons (5060);
+               v6sa.sin6_port = htons (5060);
+               if (bind (v4sox, (struct sockaddr *) &v4sa, sizeof (v4sa)) != 0) {
+                       close (v4sox);
+                       v4sox = -1;
+               }
+               if (bind (v6sox, (struct sockaddr *) &v6sa, sizeof (v6sa)) != 0) {
+                       close (v6sox);
+                       v6sox = -1;
+               }
+               if ((v4sox == -1) && (v6sox == -1)) {
+                       char v4ip [INET_ADDRSTRLEN ];
+                       char v6ip [INET6_ADDRSTRLEN];
+                       inet_ntop (AF_INET,  &map->v4addr, v4ip, sizeof (v4ip));
+                       inet_ntop (AF_INET6, &map->v6addr, v6ip, sizeof (v6ip));
+                       fprintf (stderr, "%s: Both IPv4 address %s and IPv6 address %s in one mapping are remote\n", program, v4ip, v6ip);
+                       sane = 0;
+                       continue;
+               }
+               if ((v4sox != -1) && (v6sox != -1)) {
+                       char v4ip [INET_ADDRSTRLEN ];
+                       char v6ip [INET6_ADDRSTRLEN];
+                       inet_ntop (AF_INET,  &map->v4addr, v4ip, sizeof (v4ip));
+                       inet_ntop (AF_INET6, &map->v6addr, v6ip, sizeof (v6ip));
+                       fprintf (stderr, "%s: Both IPv4 address %s and IPv6 address %s in one mapping are local\n", program, v4ip, v6ip);
+                       sane = 0;
+                       continue;
+               }
+               if (v4sox != -1) {
+                       map->sox = v4sox;
+                       map->flags = MAP_V4BOUND;
+                       // DEBUG
+                       char v4ip [INET_ADDRSTRLEN ];
+                       inet_ntop (AF_INET,  &map->v4addr, v4ip, sizeof (v4ip));
+                       printf ("Bound socket %d to IPv4 address %s\n", v4sox, v4ip); //DEBUG
+                       if (memcmp (&map->v4addr, &v4local, sizeof (v4local)) == 0) {
+                               v4local_sox = v4sox;
+                       }
+               } else {
+                       map->sox = v6sox;
+                       map->flags = MAP_V6BOUND;
+                       // DEBUG
+                       char v6ip [INET6_ADDRSTRLEN ];
+                       inet_ntop (AF_INET6, &map->v6addr, v6ip, sizeof (v6ip));
+                       printf ("Bound socket %d to IPv6 address %s\n", v6sox, v6ip); //DEBUG
+                       if (memcmp (&map->v6addr, &v6local, sizeof (v6local)) == 0) {
+                               v6local_sox = v6sox;
+                       }
+               }
+       }
+       if (!sane) {
+               exit (1);
+       }
+}
+
+
+/* Command line parsing, argument processing, resource setup.
+ * TODO: Parse port numbers after IPs, store as sockaddr instead of plain addresses
+ * TODO: Keep textual addresses in entered form and use them to speed everything up
+ */
+void open_cmdline (int argc, char *argv []) {
+       //
+       // Basic initialisation
+       program = argv [0];
+       memset (&v4uplink, 0, sizeof (v4uplink)); // TODO: Will this do to say no-proxy-available?
+       memset (&v6uplink, 0, sizeof (v6uplink)); // TODO: Will this do to say no-proxy-available?
+       //
+       // Process commandline arguments
+       int sane = 1;
+       char *v4bad = NULL, *v6bad = NULL;
+       int seen_l = 0, seen_L = 0, seen_c = 0, seen_h = 0;
+       char *arg_b = NULL, *arg_B = NULL, *arg_p = NULL, *arg_P = NULL;
+       int opt;
+       while (opt = getopt_long (argc, argv, short_opts, long_opts, NULL), opt != -1) {
+               switch (opt) {
+               case 'l':
+                       seen_l++;
+                       v4local_str = optarg;
+                       if (inet_pton (AF_INET,  optarg, &v4local) != 1) {
+                               v4bad = strdup (v4local_str);
+                       }
+                       break;
+               case 'L':
+                       seen_L++;
+                       v6local_str = optarg;
+                       if (inet_pton (AF_INET6, optarg, &v6local) != 1) {
+                               v6bad = strdup (optarg);
+                       }
+                       break;
+               case 'c':
+                       seen_c++;
+                       if (strncmp (optarg, "unix:", 5) != 0) {
+                               fprintf (stderr, "%s: You currently must specify a unix:/socket/path to -c\n", program);
+                               sane = 0;
+                       } else {
+                               memset (&rtppath, 0, sizeof (rtppath));
+                               rtppath.sun_family = AF_UNIX;
+                               strncpy (rtppath.sun_path, optarg + 5, sizeof (rtppath.sun_path) - 1);
+                       }
+                       break;
+               case 'u':
+                       seen_u++;
+                       if (inet_pton (AF_INET,  optarg, &v4uplink) != 1) {
+                               v4bad = strdup (optarg);
+                       }
+                       break;
+               case 'U':
+                       seen_U++;
+                       if (inet_pton (AF_INET6, optarg, &v6uplink) != 1) {
+                               v6bad = strdup (optarg);
+                       }
+                       break;
+               case 'h':
+                       sane = 0;
+                       seen_h = 1;
+                       break;
+               case '?':
+               default:
+                       fprintf (stderr, "%s: Unrecognised arguments: %s...\n", program, argv [optind]);
+                       sane = 0;
+                       break;
+               }
+       }
+       if (!sane) {
+               fprintf (stderr, "Usage: %s -l <v4local> -L <v6local> -c <rtpctl> [-u <v4uplink>] [-U <v6uplink>] [<v4phone>=<v6phone> ...]\n", program);
+               exit (!seen_h);
+       }
+       //
+       // Sanity check on received input
+       char **phonev = argv + optind;
+       int phonec = argc - optind;
+       if (seen_l != 1) {
+               fprintf (stderr, "%s: You must specify one IPv4 local address of this proxy using -l\n", program);
+               sane = 0;
+       }
+       if (seen_L != 1) {
+               fprintf (stderr, "%s: You must specify one IPv6 local address of this proxy using -L\n", program);
+               sane = 0;
+       }
+       if (seen_c != 1) {
+               fprintf (stderr, "%s: You must specify one control socket to access RTPproxy using -c\n", program);
+               sane = 0;
+       }
+       if (seen_u > 1) {
+               fprintf (stderr, "%s: You may not specify multiple IPv4 uplink addresses using -u\n", program);
+               sane = 0;
+       }
+       if (seen_U > 1) {
+               fprintf (stderr, "%s: You may not specify multiple IPv6 uplink addresses using -U\n", program);
+               sane = 0;
+       }
+       if ((phonec == 0) && ((!seen_u) || (!seen_U))) {
+               fprintf (stderr, "%s: You must specify either both IPv6 and IPv6 proxy addresses using -u and -U,\n\tor you must specify at least one phone address mapping behind the options.\n", program);
+               sane = 0;
+       }
+       if (!sane) {
+               exit (1);
+       }
+       if (v4bad) {
+               fprintf (stderr, "%s: Bad IPv4 address: %s\n", program, v4bad);
+               sane = 0;
+       }
+       if (v6bad) {
+               fprintf (stderr, "%s: Bad IPv6 address: %s\n", program, v6bad);
+               sane = 0;
+       }
+       if (!sane) {
+               exit (1);
+       }
+       //
+       // Build a list of address mappings
+       while (phonec-- > 0) {
+               struct in_addr  v4phone;
+               struct in6_addr v6phone;
+               char *equals = strchr (phonev [phonec], '=');
+               if (!equals) {
+                       fprintf (stderr, "%s: Phone address map should comprise of IPv4=IPv6 arguments, failed on: %s\n", program, phonev [phonec]);
+                       sane = 0;
+                       continue;
+               }
+               *equals = 0;
+               char *v4ip = phonev [phonec];
+               char *v6ip = equals + 1;
+               if (inet_pton (AF_INET, v4ip, &v4phone) != 1) {
+                       fprintf (stderr, "%s: Phone map should start with IPv4 address, failed on: %s\n", program, v4ip);
+                       sane = 0;
+               }
+               if (inet_pton (AF_INET6, v6ip, &v6phone) != 1) {
+                       fprintf (stderr, "%s: Phone map should start with IPv6 address, failed on: %s\n", program, v6ip);
+                       sane = 0;
+               }
+               insert_mapping (&v4phone, &v6phone);
+       }
+       if (seen_u) {
+               // Traffic destined for the IPv4 proxy may be sent to this IPv6 proxy
+               // TODO: Ensure the proxy's IPv6 side is the one that binds
+               insert_mapping (&v4uplink, &v6local);
+       }
+       if (seen_U) {
+               // Traffic destined for the IPv6 proxy may be sent to this IPv4 proxy
+               // TODO: Ensure the proxy's IPv4 side is the one that binds
+               insert_mapping (&v4local, &v6uplink);
+       }
+       if (!sane) {
+               exit (1);
+       }
+}
+
+
+/* Main program.  Parse commandline, setup daemon, start running.
+ */
+int main (int argc, char *argv []) {
+       //
+       // Parse the commandline, setup address mapping, open resources
+       open_cmdline (argc, argv);
+       //
+       // DEBUG
+       struct v4v6map *map = v4v6maplist;
+       while (map) {
+               char v4ip [INET_ADDRSTRLEN ];
+               char v6ip [INET6_ADDRSTRLEN];
+               inet_ntop (AF_INET,  &map->v4addr, v4ip, sizeof (v4ip));
+               inet_ntop (AF_INET6, &map->v6addr, v6ip, sizeof (v6ip));
+               printf ("Created mapping: %s <===> %s\n", v4ip, v6ip); //DEBUG
+               map = map->next;
+       }
+       //
+       // Listen to sockets -- at least the local proxy endpoints and possibly phone addresses
+       open_sockets ();
+       if ((v4local_sox == -1) || (v6local_sox == -1)) {
+               // TODO: If no -u and/or no -U then -l and/or -L do not get bound -- silly!
+               fprintf (stderr, "%s: You should have specified both an IPv4 and IPv6 local address\n", program);
+               exit (1);
+       }
+       //
+       // Run the proxy as a SIP forwarding application
+       osip_init (&osip);
+       run_proxy ();
+       osip_release (osip);
+       //
+       // Done, the nice way.
+       return 0;
+}
diff --git a/sipproxy64.man b/sipproxy64.man
new file mode 100644 (file)
index 0000000..f7df846
--- /dev/null
@@ -0,0 +1,228 @@
+
+SYNOPSIS
+       sipproxy64 -l <v4local> -L <v6local> [-c <rtpctl>] [-u <v4uplink>] [-U <v6uplink>] [<v4phone>=<v6phone>...]
+
+
+DESCRIPTION
+
+The SIPproxy64 is a proxy for SIP and RTP traffic between IPv6 and IPv4.  This proxy does
+nothing more clever, it merely exchanges IPv4 and IPv6 addresses and passes on the traffic
+to other packets.  As a concrete example, it won't do DNS.  If anything needs to be resolved,
+you should direct the traffic to a proxy for further handling.
+
+To handle RTP traffic, SIPproxy64 interacts with RTPproxy.  It is assumed that this has been
+started to listen on IPv4 and IPv6 networks that can handle the traffic passed around with
+the SIP messages.  For instance, the RTPproxy could be started with
+
+       rtpproxy -l <v4local> -6 /<v6local> -u rtpproxy -p <rtpctl>
+
+The addresses used by RTPproxy may be the same as those used by SIPproxy64, but they
+need not be.  Whatever address/port pairs RTPproxy picks will be used in SDP descriptions
+of RTP streams.  The only task of SIPproxy64 concerning SDP is to locate addresses and
+substitute them as RTPproxy dictates.
+
+The actual task of SIPproxy64 is to find IPv6 addresses in messages entering its IPv6
+side, and IPv4 address on messages entering its IPv4 side.  It then exchanges the address
+from one address family with a matching address in the other.  To that end, it holds a
+list of address pairs.  One address pair holds the SIPproxy64's sides.  Furthermore,
+an address pair is constructed for every phone declaration.  Aside from the target URI,
+the SIP headers that may be rewritten are Contact, From and To.  Non-numeric names
+will be left as they are, assuming that DNS has entries for them in both address
+families.
+
+Once rewriting is successful, SIPproxy64 will forward traffic to the next hop.  Before
+doing so, it inserts a Via header with its address on the side where the message will
+leave the SIPproxy64.  To determine the SIP message's destination, the SIPproxy64 TODO
+
+The SIPproxy64 is a polite player.  It will lowers any Max-Forwards header and send
+back a "483 Too many hops" messages if it was received as zero.  It will also supply
+provisionary "100 Crossing over to IPv4" kind of messages to indicate to a phone that
+its request is being catered for.  There may be delays as a result of DNS lookups, so
+this helps to offload SIPproxy64.
+
+
+OPTIONS
+
+-l <v4local>
+--v4local=<v4local>
+       Local listen address for the proxy's IPv4 side.  This address must be specified.
+
+-L <v6side>
+--v6local=<v6side>
+       Local listen address for the proxy's IPv6 side.  This address must be specified.
+       This must be a /64 address if the -u option is to be of any use.
+
+-u <v4uplink>
+--v4uplink=<v4uplink>
+       Optional upstream proxy address for IPv4 traffic.  This address is optional.  Without it,
+       the only proxying option will be to use forwarding addresses from Via headers,
+       which must then be an IPv4 address.  This is normally set on return traffic, so
+       the only reason to specify this option is to be able to proxy new transactions
+       from IPv6 to IPv4.
+
+-U <v6uplink>
+--v6uplink=<v6uplink>
+       Optional upstream proxy address for IPv6 traffic.  This address is optional.  Without it,
+       the only proxying option will be to use forwarding addresses from Via headers,
+       which must then be an IPv6 address.  This is normally set on return traffic, so
+       the only reason to specify this option is to be able to proxy new transactions
+       from IPv4 to IPv6.
+
+-c <rtpctl>
+--rtpctl=<rtpctl>
+       One or more of these options specify the path to an RTPproxy daemon that can
+       proxy the IPv4 and IPv6 networks.  The path is currently limited to UNIX
+       file system sockets only, so specifying multiple options is not useful.
+       If not specified, this setting reverts to the RTPproxy default, being
+       unix:/var/run/rtpproxy.sock
+
+-u <v4phone>
+--v4phone=<v4phone>
+       Any number of addresses of IPv4-only phones can be specified with zero or more of
+       these options.  For each of these, an IPv6 address is constructed by fetching the
+       MAC address for the phone using ARP, and using it to attach 64 bits to the
+       end of the -L address of this proxy.  The address found that way must already
+       be assigned to an interface on the machine that runs this proxy.
+
+-U <v6phone>
+--v6phone=<v6phone>
+       Any number of addresses of IPv6-only phones can be specified with zero or more of
+       these options.  For each of these, an IPv4 address can be picked arbitrarily from
+       the LAN range.  HOWEVER, since IPv4-unaware SIP-phones are not foreseen anytime
+       soon, this option is not implemented at this time.  When we create it, we will
+       probably assign a LAN range in an extra parameter and assign IP numbers
+       consecutively.  The address found that way must then already be assigned to an
+       interface on the machine that runs this proxy.
+
+<v4phone>=<v6phone>
+       TODO:
+       Specify any number of phone IPs on your network.  These will usually be IPv4
+       addresses of such phones.  If such phones are proxied to a publicly routable
+       IPv6 address range, then these phones will be reachable over SIP even if the
+       IPv4 address is only locally reachable.  In this case, SIPproxy64 has an
+       additional function as a sort of "NAT helper".
+
+       It is not currently supported to specify IPv6 addresses, as there does not seem
+       to be a case for those.
+
+EXAMPLES
+       A home router's SIP service may be exported over IPv6.  To be able
+       to actually call local phones, the service must accept incoming
+       calls from unregistered phones, and not demand authentication on
+       the invitation for the call either.
+
+       Assume the home router at 192.168.2.1, and the SIPproxy64 to sit
+       between 192.168.2.12 and 2001:abcd:ef::192.168.2.12.  Also assume
+       an IPv6-side smarter proxy at 2001:fe:dcba::87.  The RTPproxy is
+       silently assumed to run on its default location udp:127.0.0.1:22222
+
+       sipproxy64 -l 192.168.2.21 -L 2001:abcd:ef::192.168.2.12 -u 192.168.2.1 -U 2001:fe:dcba::87
+
+       Another example discloses IPv4-only phones on the IPv6 network, each
+       at its own IP-address.  This scenario is chiefly important for a
+       small setup; larger numbers of phones usually register with a PBX
+       which, if it is IPv4-only, can be disclosed to IPv6 using the approach
+       above.
+
+       Assume the phones to be located at 10.0.0.138 and 10.0.0.139, with
+       intended IPv6 addresses 2001:abcd:ef::10:0:0:138 and
+       2001:abcd:ef::10:0:0:139.  The phones may or may not register through
+       the SIPproxy64; that all depends on the possibilities of the
+       registrar.  The following example assumes no smarter proxies to be
+       configured, but purely to route call traffic.  Also, it assumes to
+       run at 10.0.0.17 annex 2001:abcd:ef:10::0:0:17 and have the RTPproxy
+       at its default location
+
+       sipproxy64 -l 10.0.0.17 -L 2001:abcd:ef::10:0:0:17 10.0.0.138=2001:abcd:ef::10:0:0:138 10.0.0.139=2001:abcd:ef::10:0:0:139
+
+       If the phone needs to call out over IPv6 instead of IPv4, direct the
+       SIP traffic through SIPproxy64.  One way of doing that is to specify
+       the IPv4-side of the proxy as an outgoing proxy.
+
+       If an IPv4-only phone needs to register with an IPv6 proxy, specify
+       that proxy with the -U parameter.  In effect, SIPproxy64 will
+       introduce an additional address mapping from the -l address to the
+       -U address.  Likewise, introducing -u will cause an additional
+       address mapping between the -u address and the -L address.
+
+
+ALGORITHM
+
+       The SIPproxy64 is a stateless SIP proxy.  This means that it is not
+       aware of connections that are live.  RTPproxy does keep state on
+       connections, but it too will timeout if no traffic is passing through
+       it.  In other words, the system will always get back to an empty
+       initial state if left alone.
+
+       To achieve statelessness, SIP sends information in Route: headers
+       that tell a stateless proxy like ours where to forward traffic to.
+       SIPproxy64 makes use of those headers after a call has been setup.
+
+       The general idea of SIPproxy64 is to listen to IPv6 addresses to
+       substitute for their lack on IPv4 phones.  It can do the opposite
+       as well, if the need ever arises.  In general, it maps between the
+       two address families IPv4 and IPv6 by listening on one address and
+       forwarding to the other.  It keeps an internal list of mappings
+       from one to the other.
+
+       If traffic arrives at an IP that is added by SIPproxy64, then it
+       will lookup the real address of the phone and forward there.  In
+       doing so, it uses its generic listen address (from the -l or -L
+       option) as a sender address.  The port numbers are forwarded
+       as-is; the From: To: and Contact: headers in the message are
+       changed.
+
+       If traffic arrives at the general listen addresses (from the -l
+       and -L options), then the first attempt is to look at Route:
+       headers (unless it is an INVITE, in which case they would not be
+       reliable), and follow those.  If this fails, but a generic
+       uplink address has been configured (with the -u or -U option)
+       on the other side, then that is used as the destination address.
+       If this also fails, the request is refused and an error message
+       TODO is sent back to the sender.  Port numbers are kept as they
+       were on the incoming request.
+
+       In the last procedure, the sender address in the forwarded SIP
+       message depends on the sender on the incoming message.  If the
+       message came from a phone whose IP in the other address family
+       is added by SIPproxy64, then that IP is selected as the sender
+       address for the forwarded SIP message; if not, the outgoing
+       address is the generic address.
+
+       Note that an INVITE from the phone, if it is steered through
+       SIPproxy64, will be sent to the generic uplink in the other
+       address family, using the address in that family as added by
+       SIPproxy64.  In other words, an INVITE to the World will be
+       sent as an INVITE in the other address family.
+
+       In all the cases above, if SDP content is found attached to
+       the message, its addresses and ports are rewritten to values
+       that are suggested by RTPproxy.  The proxy will be asked to
+       setup for asynchronous RTP, meaning that it will not wait for
+       traffic in both directions before starting to forward the
+       media to the other side.  Under the assumption that both IPv4
+       and IPv6 addresses can be routed locally, this should give
+       the best possible performance.
+
+
+BUGS
+       The only <phone> options supported are IPv4 phones, because there does not seem
+       to be any use for proxying to IPv6 phones.  If the external site is not a phone
+       but a proxy, then the -U option should be used instead.
+
+       Only one -c option is currently meaningful.  This is the result of only supporting
+       unix:/file/socket paths to an RTPproxy instance.  In complex setups, this option
+       would have to change.  Then again, complex setups may need a more complex proxy?
+
+       It is possible in theory to listen promiscuously to an interface, or
+       to use a mangling firewall, as a way to force traffic for a subnet
+       through the SIPproxy64.  This is not currently supported, which means
+       that all address pairs specified for phones must have exactly one
+       binding; none would not allow traffic to be captured and both would
+       be confusing, if not plain silly.
+
+       The current implementation does not scan port numbers post-fixed to IP addresses,
+       but it really should in order to simplify matters.
+
+SEE ALSO
+       rtpproxy