--- /dev/null
+/* $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;
+}
--- /dev/null
+
+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