diff -u -r asterisk-1.8.0/channels/chan_sip.c asterisk-1.8.0-path/channels/chan_sip.c --- asterisk-1.8.0/channels/chan_sip.c 2010-10-15 22:12:04.000000000 +0200 +++ asterisk-1.8.0-path/channels/chan_sip.c 2010-10-27 21:54:37.000000000 +0200 @@ -1255,6 +1255,8 @@ static void free_old_route(struct sip_route *route); static void list_route(struct sip_route *route); static void build_route(struct sip_pvt *p, struct sip_request *req, int backwards); +static void build_path(struct sip_pvt *p, struct sip_peer *peer, struct sip_request *req, char *pathbuf); +static void copy_route(struct sip_route **d, struct sip_route *s); static enum check_auth_result register_verify(struct sip_pvt *p, struct ast_sockaddr *addr, struct sip_request *req, const char *uri); static struct sip_pvt *get_sip_pvt_byid_locked(const char *callid, const char *totag, const char *fromtag); @@ -1494,6 +1496,7 @@ static int add_rpid(struct sip_request *req, struct sip_pvt *p); static int add_vidupdate(struct sip_request *req); static void add_route(struct sip_request *req, struct sip_route *route); +static void make_route_list(struct sip_route *route, char *r, int rem); static int copy_header(struct sip_request *req, const struct sip_request *orig, const char *field); static int copy_all_header(struct sip_request *req, const struct sip_request *orig, const char *field); static int copy_via_headers(struct sip_pvt *p, struct sip_request *req, const struct sip_request *orig, const char *field); @@ -4318,6 +4321,11 @@ peer->chanvars = NULL; } + if (peer->path) { + free_old_route(peer->path); + peer->path = NULL; + } + register_peer_exten(peer, FALSE); ast_free_ha(peer->ha); ast_free_ha(peer->directmediaha); @@ -4347,6 +4355,7 @@ /*! \brief Update peer data in database (if used) */ static void update_peer(struct sip_peer *p, int expire) { +/* klaus: TODO: store/read Path to/from realtime */ int rtcachefriends = ast_test_flag(&p->flags[1], SIP_PAGE2_RTCACHEFRIENDS); if (sip_cfg.peer_rtupdate && (p->is_realtime || rtcachefriends)) { @@ -4809,6 +4818,8 @@ return 0; } +static int __set_address_from_contact(const char *fullcontact, struct ast_sockaddr *addr, int tcp); + /*! \brief Create address structure from peer reference. * This function copies data from peer to the dialog, so we don't have to look up the peer * again from memory or database during the life time of the dialog. @@ -4836,6 +4847,11 @@ ast_copy_flags(&dialog->flags[0], &peer->flags[0], SIP_FLAGS_TO_COPY); ast_copy_flags(&dialog->flags[1], &peer->flags[1], SIP_PAGE2_FLAGS_TO_COPY); ast_copy_flags(&dialog->flags[2], &peer->flags[2], SIP_PAGE3_FLAGS_TO_COPY); + copy_route(&dialog->route, peer->path); + if (dialog->route) { + /* Parse SIP URI of first route-set hop and use it as target address */ + __set_address_from_contact(dialog->route->hop, &dialog->sa, dialog->socket.type == SIP_TRANSPORT_TLS ? 1 : 0); + } dialog->capability = peer->capability; dialog->prefs = peer->prefs; if (ast_test_flag(&dialog->flags[1], SIP_PAGE2_T38SUPPORT)) { @@ -9237,12 +9253,21 @@ /*! \brief Add route header into request per learned route */ static void add_route(struct sip_request *req, struct sip_route *route) { - char r[SIPBUFSIZE*2], *p; - int n, rem = sizeof(r); + char r[SIPBUFSIZE*2]; if (!route) return; + make_route_list(route, r, sizeof(r)); + add_header(req, "Route", r); +} + +/*! \brief Make the comma separated list of route headers from the route list */ +static void make_route_list(struct sip_route *route, char *r, int rem) +{ + char *p; + int n; + p = r; for (;route ; route = route->next) { n = strlen(route->hop); @@ -9259,7 +9284,6 @@ rem -= (n+2); } *p = '\0'; - add_header(req, "Route", r); } /*! \brief Set destination from SIP URI @@ -12705,6 +12729,7 @@ ast_update_realtime(tablename, "name", peer->name, "fullcontact", "", "ipaddr", "", "port", "", "regseconds", "0", "regserver", "", "useragent", "", "lastms", "", SENTINEL); } else { ast_db_del("SIP/Registry", peer->name); + ast_db_del("SIP/RegistryPath", peer->name); ast_db_del("SIP/PeerMethods", peer->name); } } @@ -12786,6 +12811,7 @@ static void reg_source_db(struct sip_peer *peer) { char data[256]; + char path[SIPBUFSIZE*2]; struct ast_sockaddr sa; int expire; char full_addr[128]; @@ -12843,6 +12869,11 @@ unref_peer(peer, "remove registration ref"), ref_peer(peer, "add registration ref")); register_peer_exten(peer, TRUE); + if (ast_db_get("SIP/RegistryPath", peer->name, path, sizeof(path))) { + return; + } + build_path(0, peer, 0, path); + } /*! \brief Save contact header for 200 OK on INVITE */ @@ -13103,14 +13134,19 @@ } } pvt->expiry = expire; + build_path(pvt, peer, req, 0); snprintf(data, sizeof(data), "%s:%d:%s:%s", ast_sockaddr_stringify(&peer->addr), expire, peer->username, peer->fullcontact); /* Saving TCP connections is useless, we won't be able to reconnect XXX WHY???? XXX \todo Fix this immediately. */ - if (!peer->rt_fromcontact && (peer->socket.type & SIP_TRANSPORT_UDP)) + if (!peer->rt_fromcontact && (peer->socket.type & SIP_TRANSPORT_UDP)) { + char path[SIPBUFSIZE*2]; ast_db_put("SIP/Registry", peer->name, data); + make_route_list(peer->path, path, sizeof(path)); + ast_db_put("SIP/RegistryPath", peer->name, path); + } manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "ChannelType: SIP\r\nPeer: SIP/%s\r\nPeerStatus: Registered\r\nAddress: %s\r\n", peer->name, ast_sockaddr_stringify(&peer->addr)); /* Is this a new IP address for us? */ @@ -13146,10 +13182,10 @@ static void list_route(struct sip_route *route) { if (!route) { - ast_verbose("list_route: no route\n"); + ast_verbose("list_route: no route/path\n"); } else { for (;route; route = route->next) - ast_verbose("list_route: hop: <%s>\n", route->hop); + ast_verbose("list_route: route/path hop: <%s>\n", route->hop); } } @@ -13261,6 +13297,107 @@ } } +/*! \brief copy route-set */ +static void copy_route(struct sip_route **d, struct sip_route *s) +{ + struct sip_route *thishop, *head, *tail; + /* Build a tailq, then assign it to **d when done. + */ + head = NULL; + tail = head; + while (s) { + int len = strlen(s->hop) + 1; + if ((thishop = ast_malloc(sizeof(*thishop) + len))) { + /* ast_calloc is not needed because all fields are initialized in this block */ + ast_copy_string(thishop->hop, s->hop, len); + ast_debug(2, "copy_route: copied hop: <%s>\n", thishop->hop); + thishop->next = NULL; + /* Link in at the end */ + if (tail) { + tail->next = thishop; + } else { + head = thishop; + } + tail = thishop; + } + s = s->next; + } + *d = head; +} +/*! \brief Build route list from Path header + * RFC 3327 requires that the Path header contains SIP URIs with lr paramter. + * Thus, we do not care about strict routers + */ +static void build_path(struct sip_pvt *p, struct sip_peer *peer, struct sip_request *req, char *pathbuf) +{ + struct sip_route *thishop, *head, *tail; + int start = 0; + int len; + const char *rr; + + if (peer->path) { + free_old_route(peer->path); + peer->path = NULL; + } + + if (!ast_test_flag(&peer->flags[0], SIP_USEPATH)) { + ast_debug(2, "build_path: do not use Path headers\n"); + return; + } + ast_debug(2, "build_path: try to build pre-loaded route-set by parsing Path headers\n"); + + /* Build a tailq, then assign it to peer->path when done. + */ + head = NULL; + tail = head; + /* 1st we pass through all the hops in any Path headers */ + for (;;) { + /* Either loop over the request's Path headers or parse the buffer */ + if (req) { + rr = __get_header(req, "Path", &start); + if (*rr == '\0') { + break; + } + } else if (pathbuf) { + if (start == 0) { + rr = pathbuf; + start++; + } else { + break; + } + } else { + break; + } + for (; (rr = strchr(rr, '<')) ; rr += len) { /* Each route entry */ + ++rr; + len = strcspn(rr, ">") + 1; + /* Make a struct route */ + if ((thishop = ast_malloc(sizeof(*thishop) + len))) { + /* ast_calloc is not needed because all fields are initialized in this block */ + ast_copy_string(thishop->hop, rr, len); + ast_debug(2, "build_path: Path hop: <%s>\n", thishop->hop); + /* Link in */ + thishop->next = NULL; + /* Link in at the end */ + if (tail) { + tail->next = thishop; + } else { + head = thishop; + } + tail = thishop; + } + } + } + + /* Store as new route */ + peer->path = head; + + /* For debugging dump what we ended up with */ + if (p && sip_debug_test_pvt(p)) { + list_route(peer->path); + } +} + /*! \brief builds the sip_pvt's randdata field which is used for the nonce * challenge. When forceupdate is not set, the nonce is only updated if * the current one is stale. In this case, a stalenonce is one which @@ -13788,12 +13925,14 @@ res = 0; break; case PARSE_REGISTER_QUERY: + ast_log(LOG_DEBUG, "Register query received\n"); ast_string_field_set(p, fullcontact, peer->fullcontact); transmit_response_with_date(p, "200 OK", req); peer->lastmsgssent = -1; res = 0; break; case PARSE_REGISTER_UPDATE: + ast_log(LOG_DEBUG, "Updateing contact info\n"); ast_string_field_set(p, fullcontact, peer->fullcontact); update_peer(peer, p->expiry); /* Say OK and ask subsystem to retransmit msg counter */ @@ -16334,6 +16473,20 @@ ast_cli(fd, " Ign SDP ver : %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[1], SIP_PAGE2_IGNORESDPVERSION))); ast_cli(fd, " Trust RPID : %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[0], SIP_TRUSTRPID))); ast_cli(fd, " Send RPID : %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[0], SIP_SENDRPID))); + ast_cli(fd, " Use Path : %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[0], SIP_USEPATH))); + ast_cli(fd, " Path : "); + if (!peer->path) { + ast_cli(fd, "N/A\n"); + } else { + struct sip_route *r=peer->path; + int first = 1; + while (r) { + ast_cli(fd, "%s<%s>", first ? "" : ", ", r->hop); + first = 0; + r=r->next; + } + ast_cli(fd, "\n"); + } ast_cli(fd, " Subscriptions: %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[1], SIP_PAGE2_ALLOWSUBSCRIBE))); ast_cli(fd, " Overlap dial : %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[1], SIP_PAGE2_ALLOWOVERLAP))); if (peer->outboundproxy) @@ -24767,6 +24932,7 @@ ast_copy_flags(&p->flags[0], &peer->flags[0], SIP_FLAGS_TO_COPY); ast_copy_flags(&p->flags[1], &peer->flags[1], SIP_PAGE2_FLAGS_TO_COPY); ast_copy_flags(&p->flags[2], &peer->flags[2], SIP_PAGE3_FLAGS_TO_COPY); + copy_route(&p->route, peer->path); /* Send OPTIONs to peer's fullcontact */ if (!ast_strlen_zero(peer->fullcontact)) @@ -25193,6 +25359,9 @@ if (!strcasecmp(v->name, "trustrpid")) { ast_set_flag(&mask[0], SIP_TRUSTRPID); ast_set2_flag(&flags[0], ast_true(v->value), SIP_TRUSTRPID); + } else if (!strcasecmp(v->name, "usepath")) { + ast_set_flag(&mask[0], SIP_USEPATH); + ast_set2_flag(&flags[0], ast_true(v->value), SIP_USEPATH); } else if (!strcasecmp(v->name, "sendrpid")) { ast_set_flag(&mask[0], SIP_SENDRPID); if (!strcasecmp(v->value, "pai")) { diff -u -r asterisk-1.8.0/channels/sip/include/sip.h asterisk-1.8.0-path/channels/sip/include/sip.h --- asterisk-1.8.0/channels/sip/include/sip.h 2010-09-15 21:22:15.000000000 +0200 +++ asterisk-1.8.0-path/channels/sip/include/sip.h 2010-10-26 17:56:59.000000000 +0200 @@ -278,6 +278,7 @@ #define SIP_PROG_INBAND_NO (1 << 25) #define SIP_PROG_INBAND_YES (2 << 25) +#define SIP_USEPATH (1 << 27) /*!< P: Trust and use incoming Path headers? */ #define SIP_SENDRPID (3 << 29) /*!< DP: Remote Party-ID Support */ #define SIP_SENDRPID_NO (0 << 29) #define SIP_SENDRPID_PAI (1 << 29) /*!< Use "P-Asserted-Identity" for rpid */ @@ -1232,6 +1233,7 @@ int timer_t1; /*!< The maximum T1 value for the peer */ int timer_b; /*!< The maximum timer B (transaction timeouts) */ int fromdomainport; /*!< The From: domain port */ + struct sip_route *path; /*!< Head of linked list of out-of-dialog outgoing routing steps (fm Path headers) */ /*XXX Seems like we suddenly have two flags with the same content. Why? To be continued... */ enum sip_peer_type type; /*!< Distinguish between "user" and "peer" types. This is used solely for CLI and manager commands */ diff -u -r asterisk-1.8.0/configs/sip.conf.sample asterisk-1.8.0-path/configs/sip.conf.sample --- asterisk-1.8.0/configs/sip.conf.sample 2010-09-04 00:21:50.000000000 +0200 +++ asterisk-1.8.0-path/configs/sip.conf.sample 2010-10-27 21:44:12.000000000 +0200 @@ -388,6 +388,13 @@ ;outboundproxy=[2001:db8::1]:5062 ; IPv6 address literal with explicit port ; ; (could also be tcp,udp) - defining transports on the proxy line only ; ; applies for the global proxy, otherwise use the transport= option +;usepath=yes ; This activates parsing and handling of Path header as defined in RFC 3327. This enables + ; Asterisk to route outgoing out-of-dialog requests via a set of proxies by using a pre-loaded + ; route-set defined by the Path headers in the REGISTER request. NOTE: As this influences routing + ; of SIP requests make sure to not trust Path headers provided by the user's SIP client (the proxy + ; in front of Asterisk should remove existing user provided Path headers). NOTE: when a peer has a + ; path and outboundproxy set, the path will be added to Router header but routing to next hop is + ; done using the outboundproxy. NOTE: Path support does not work with realtime yet. ;matchexternaddrlocally = yes ; Only substitute the externaddr or externhost setting if it matches ; your localnet setting. Unless you have some sort of strange network ; setup you will not need to enable this.