936 lines
18 KiB
C
936 lines
18 KiB
C
/**
|
|
* @file sip/request.c SIP Request
|
|
*
|
|
* Copyright (C) 2010 Creytiv.com
|
|
*/
|
|
#include <re_types.h>
|
|
#include <re_mem.h>
|
|
#include <re_mbuf.h>
|
|
#include <re_sa.h>
|
|
#include <re_list.h>
|
|
#include <re_fmt.h>
|
|
#include <re_dns.h>
|
|
#include <re_uri.h>
|
|
#include <re_sys.h>
|
|
#include <re_udp.h>
|
|
#include <re_msg.h>
|
|
#include <re_sip.h>
|
|
#include "sip.h"
|
|
|
|
|
|
struct sip_request {
|
|
struct le le;
|
|
struct list cachel;
|
|
struct list addrl;
|
|
struct list srvl;
|
|
struct sip_request **reqp;
|
|
struct sip_ctrans *ct;
|
|
struct dns_query *dnsq;
|
|
struct dns_query *dnsq2;
|
|
struct sip *sip;
|
|
char *met;
|
|
char *uri;
|
|
char *host;
|
|
struct mbuf *mb;
|
|
sip_send_h *sendh;
|
|
sip_resp_h *resph;
|
|
void *arg;
|
|
size_t sortkey;
|
|
enum sip_transp tp;
|
|
bool tp_selected;
|
|
bool stateful;
|
|
bool canceled;
|
|
bool provrecv;
|
|
uint16_t port;
|
|
};
|
|
|
|
|
|
static int request_next(struct sip_request *req);
|
|
static bool rr_append_handler(struct dnsrr *rr, void *arg);
|
|
static void srv_handler(int err, const struct dnshdr *hdr, struct list *ansl,
|
|
struct list *authl, struct list *addl, void *arg);
|
|
static int srv_lookup(struct sip_request *req, const char *domain);
|
|
static int addr_lookup(struct sip_request *req, const char *name);
|
|
|
|
|
|
static int str_ldup(char **dst, const char *src, int len)
|
|
{
|
|
struct pl pl;
|
|
|
|
pl.p = src;
|
|
pl.l = len < 0 ? str_len(src) : (size_t)len;
|
|
|
|
return pl_strdup(dst, &pl);
|
|
}
|
|
|
|
|
|
static void destructor(void *arg)
|
|
{
|
|
struct sip_request *req = arg;
|
|
|
|
if (req->reqp && req->stateful) {
|
|
/* user does deref before request has completed */
|
|
*req->reqp = NULL;
|
|
req->reqp = NULL;
|
|
req->sendh = NULL;
|
|
req->resph = NULL;
|
|
sip_request_cancel(mem_ref(req));
|
|
return;
|
|
}
|
|
|
|
list_flush(&req->cachel);
|
|
list_flush(&req->addrl);
|
|
list_flush(&req->srvl);
|
|
list_unlink(&req->le);
|
|
mem_deref(req->dnsq);
|
|
mem_deref(req->dnsq2);
|
|
mem_deref(req->ct);
|
|
mem_deref(req->met);
|
|
mem_deref(req->uri);
|
|
mem_deref(req->host);
|
|
mem_deref(req->mb);
|
|
}
|
|
|
|
|
|
static void terminate(struct sip_request *req, int err,
|
|
const struct sip_msg *msg)
|
|
{
|
|
if (req->reqp) {
|
|
*req->reqp = NULL;
|
|
req->reqp = NULL;
|
|
}
|
|
|
|
list_unlink(&req->le);
|
|
req->sendh = NULL;
|
|
|
|
if (req->resph) {
|
|
req->resph(err, msg, req->arg);
|
|
req->resph = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
static bool close_handler(struct le *le, void *arg)
|
|
{
|
|
struct sip_request *req = le->data;
|
|
(void)arg;
|
|
|
|
req->dnsq = mem_deref(req->dnsq);
|
|
req->dnsq2 = mem_deref(req->dnsq2);
|
|
req->ct = mem_deref(req->ct);
|
|
|
|
terminate(req, ECONNABORTED, NULL);
|
|
mem_deref(req);
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
static void response_handler(int err, const struct sip_msg *msg, void *arg)
|
|
{
|
|
struct sip_request *req = arg;
|
|
|
|
if (msg && msg->scode < 200) {
|
|
if (!req->provrecv) {
|
|
req->provrecv = true;
|
|
if (req->canceled)
|
|
(void)sip_ctrans_cancel(req->ct);
|
|
}
|
|
|
|
if (req->resph)
|
|
req->resph(err, msg, req->arg);
|
|
|
|
return;
|
|
}
|
|
|
|
req->ct = NULL;
|
|
|
|
if (!req->canceled && (err || msg->scode == 503) &&
|
|
(req->addrl.head || req->srvl.head)) {
|
|
|
|
err = request_next(req);
|
|
if (!err)
|
|
return;
|
|
}
|
|
|
|
terminate(req, err, msg);
|
|
mem_deref(req);
|
|
}
|
|
|
|
|
|
static int request(struct sip_request *req, enum sip_transp tp,
|
|
const struct sa *dst)
|
|
{
|
|
struct mbuf *mb = NULL;
|
|
char *branch = NULL;
|
|
int err = ENOMEM;
|
|
struct sa laddr;
|
|
|
|
req->provrecv = false;
|
|
|
|
branch = mem_alloc(24, NULL);
|
|
mb = mbuf_alloc(1024);
|
|
|
|
if (!branch || !mb)
|
|
goto out;
|
|
|
|
(void)re_snprintf(branch, 24, "z9hG4bK%016llx", rand_u64());
|
|
|
|
err = sip_transp_laddr(req->sip, &laddr, tp, dst);
|
|
if (err)
|
|
goto out;
|
|
|
|
err = mbuf_printf(mb, "%s %s SIP/2.0\r\n", req->met, req->uri);
|
|
err |= mbuf_printf(mb, "Via: SIP/2.0/%s %J;branch=%s;rport\r\n",
|
|
sip_transp_name(tp), &laddr, branch);
|
|
err |= req->sendh ? req->sendh(tp, &laddr, dst, mb, req->arg) : 0;
|
|
err |= mbuf_write_mem(mb, mbuf_buf(req->mb), mbuf_get_left(req->mb));
|
|
if (err)
|
|
goto out;
|
|
|
|
mb->pos = 0;
|
|
|
|
if (!req->stateful)
|
|
err = sip_send(req->sip, NULL, tp, dst, mb);
|
|
else
|
|
err = sip_ctrans_request(&req->ct, req->sip, tp, dst, req->met,
|
|
branch, mb, response_handler, req);
|
|
if (err)
|
|
goto out;
|
|
|
|
out:
|
|
mem_deref(branch);
|
|
mem_deref(mb);
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
static int request_next(struct sip_request *req)
|
|
{
|
|
struct dnsrr *rr;
|
|
struct sa dst;
|
|
int err;
|
|
|
|
again:
|
|
rr = list_ledata(req->addrl.head);
|
|
if (!rr) {
|
|
rr = list_ledata(req->srvl.head);
|
|
if (!rr)
|
|
return ENOENT;
|
|
|
|
req->port = rr->rdata.srv.port;
|
|
|
|
dns_rrlist_apply2(&req->cachel, rr->rdata.srv.target,
|
|
DNS_TYPE_A, DNS_TYPE_AAAA, DNS_CLASS_IN,
|
|
true, rr_append_handler, &req->addrl);
|
|
|
|
list_unlink(&rr->le);
|
|
|
|
if (req->addrl.head) {
|
|
dns_rrlist_sort_addr(&req->addrl, req->sortkey);
|
|
mem_deref(rr);
|
|
goto again;
|
|
}
|
|
|
|
err = addr_lookup(req, rr->rdata.srv.target);
|
|
mem_deref(rr);
|
|
|
|
return err;
|
|
}
|
|
|
|
switch (rr->type) {
|
|
|
|
case DNS_TYPE_A:
|
|
sa_set_in(&dst, rr->rdata.a.addr, req->port);
|
|
break;
|
|
|
|
case DNS_TYPE_AAAA:
|
|
sa_set_in6(&dst, rr->rdata.aaaa.addr, req->port);
|
|
break;
|
|
|
|
default:
|
|
return EINVAL;
|
|
}
|
|
|
|
list_unlink(&rr->le);
|
|
mem_deref(rr);
|
|
|
|
err = request(req, req->tp, &dst);
|
|
if (err) {
|
|
if (req->addrl.head || req->srvl.head)
|
|
goto again;
|
|
}
|
|
else if (!req->stateful) {
|
|
req->resph = NULL;
|
|
terminate(req, 0, NULL);
|
|
mem_deref(req);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
static bool transp_next(struct sip *sip, enum sip_transp *tp)
|
|
{
|
|
enum sip_transp i;
|
|
|
|
for (i=(enum sip_transp)(*tp+1); i<SIP_TRANSPC; i++) {
|
|
|
|
if (!sip_transp_supported(sip, i, AF_UNSPEC))
|
|
continue;
|
|
|
|
*tp = i;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
static bool transp_next_srv(struct sip *sip, enum sip_transp *tp)
|
|
{
|
|
enum sip_transp i;
|
|
|
|
for (i=(enum sip_transp)(*tp-1); i>SIP_TRANSP_NONE; i--) {
|
|
|
|
if (!sip_transp_supported(sip, i, AF_UNSPEC))
|
|
continue;
|
|
|
|
*tp = i;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
static bool rr_append_handler(struct dnsrr *rr, void *arg)
|
|
{
|
|
struct list *lst = arg;
|
|
|
|
switch (rr->type) {
|
|
|
|
case DNS_TYPE_A:
|
|
case DNS_TYPE_AAAA:
|
|
case DNS_TYPE_SRV:
|
|
if (rr->le.list)
|
|
break;
|
|
|
|
list_append(lst, &rr->le, mem_ref(rr));
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
static bool rr_cache_handler(struct dnsrr *rr, void *arg)
|
|
{
|
|
struct sip_request *req = arg;
|
|
|
|
switch (rr->type) {
|
|
|
|
case DNS_TYPE_A:
|
|
if (!sip_transp_supported(req->sip, req->tp, AF_INET))
|
|
break;
|
|
|
|
list_unlink(&rr->le_priv);
|
|
list_append(&req->cachel, &rr->le_priv, rr);
|
|
break;
|
|
|
|
#ifdef HAVE_INET6
|
|
case DNS_TYPE_AAAA:
|
|
if (!sip_transp_supported(req->sip, req->tp, AF_INET6))
|
|
break;
|
|
|
|
list_unlink(&rr->le_priv);
|
|
list_append(&req->cachel, &rr->le_priv, rr);
|
|
break;
|
|
#endif
|
|
|
|
case DNS_TYPE_CNAME:
|
|
list_unlink(&rr->le_priv);
|
|
list_append(&req->cachel, &rr->le_priv, rr);
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
static bool rr_naptr_handler(struct dnsrr *rr, void *arg)
|
|
{
|
|
struct sip_request *req = arg;
|
|
enum sip_transp tp;
|
|
|
|
if (rr->type != DNS_TYPE_NAPTR)
|
|
return false;
|
|
|
|
if (!str_casecmp(rr->rdata.naptr.services, "SIP+D2U"))
|
|
tp = SIP_TRANSP_UDP;
|
|
else if (!str_casecmp(rr->rdata.naptr.services, "SIP+D2T"))
|
|
tp = SIP_TRANSP_TCP;
|
|
else if (!str_casecmp(rr->rdata.naptr.services, "SIPS+D2T"))
|
|
tp = SIP_TRANSP_TLS;
|
|
else
|
|
return false;
|
|
|
|
if (!sip_transp_supported(req->sip, tp, AF_UNSPEC))
|
|
return false;
|
|
|
|
req->tp = tp;
|
|
req->tp_selected = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static void naptr_handler(int err, const struct dnshdr *hdr, struct list *ansl,
|
|
struct list *authl, struct list *addl, void *arg)
|
|
{
|
|
struct sip_request *req = arg;
|
|
struct dnsrr *rr;
|
|
(void)hdr;
|
|
(void)authl;
|
|
|
|
dns_rrlist_sort(ansl, DNS_TYPE_NAPTR, req->sortkey);
|
|
|
|
rr = dns_rrlist_apply(ansl, NULL, DNS_TYPE_NAPTR, DNS_CLASS_IN, false,
|
|
rr_naptr_handler, req);
|
|
if (!rr) {
|
|
req->tp = SIP_TRANSPC;
|
|
if (!transp_next_srv(req->sip, &req->tp)) {
|
|
err = EPROTONOSUPPORT;
|
|
goto fail;
|
|
}
|
|
|
|
err = srv_lookup(req, req->host);
|
|
if (err)
|
|
goto fail;
|
|
|
|
return;
|
|
}
|
|
|
|
dns_rrlist_apply(addl, rr->rdata.naptr.replace, DNS_TYPE_SRV,
|
|
DNS_CLASS_IN, true, rr_append_handler, &req->srvl);
|
|
|
|
if (!req->srvl.head) {
|
|
err = dnsc_query(&req->dnsq, req->sip->dnsc,
|
|
rr->rdata.naptr.replace, DNS_TYPE_SRV,
|
|
DNS_CLASS_IN, true, srv_handler, req);
|
|
if (err)
|
|
goto fail;
|
|
|
|
return;
|
|
}
|
|
|
|
dns_rrlist_sort(&req->srvl, DNS_TYPE_SRV, req->sortkey);
|
|
|
|
dns_rrlist_apply(addl, NULL, DNS_QTYPE_ANY, DNS_CLASS_IN, false,
|
|
rr_cache_handler, req);
|
|
|
|
err = request_next(req);
|
|
if (err)
|
|
goto fail;
|
|
|
|
return;
|
|
|
|
fail:
|
|
terminate(req, err, NULL);
|
|
mem_deref(req);
|
|
}
|
|
|
|
|
|
static void srv_handler(int err, const struct dnshdr *hdr, struct list *ansl,
|
|
struct list *authl, struct list *addl, void *arg)
|
|
{
|
|
struct sip_request *req = arg;
|
|
(void)hdr;
|
|
(void)authl;
|
|
|
|
dns_rrlist_apply(ansl, NULL, DNS_TYPE_SRV, DNS_CLASS_IN, false,
|
|
rr_append_handler, &req->srvl);
|
|
|
|
if (!req->srvl.head) {
|
|
if (!req->tp_selected) {
|
|
if (transp_next_srv(req->sip, &req->tp)) {
|
|
|
|
err = srv_lookup(req, req->host);
|
|
if (err)
|
|
goto fail;
|
|
|
|
return;
|
|
}
|
|
|
|
req->tp = SIP_TRANSP_NONE;
|
|
if (!transp_next(req->sip, &req->tp)) {
|
|
err = EPROTONOSUPPORT;
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
req->port = sip_transp_port(req->tp, 0);
|
|
|
|
err = addr_lookup(req, req->host);
|
|
if (err)
|
|
goto fail;
|
|
|
|
return;
|
|
}
|
|
|
|
dns_rrlist_sort(&req->srvl, DNS_TYPE_SRV, req->sortkey);
|
|
|
|
dns_rrlist_apply(addl, NULL, DNS_QTYPE_ANY, DNS_CLASS_IN, false,
|
|
rr_cache_handler, req);
|
|
|
|
err = request_next(req);
|
|
if (err)
|
|
goto fail;
|
|
|
|
return;
|
|
|
|
fail:
|
|
terminate(req, err, NULL);
|
|
mem_deref(req);
|
|
}
|
|
|
|
|
|
static void addr_handler(int err, const struct dnshdr *hdr, struct list *ansl,
|
|
struct list *authl, struct list *addl, void *arg)
|
|
{
|
|
struct sip_request *req = arg;
|
|
(void)hdr;
|
|
(void)authl;
|
|
(void)addl;
|
|
|
|
dns_rrlist_apply2(ansl, NULL, DNS_TYPE_A, DNS_TYPE_AAAA, DNS_CLASS_IN,
|
|
false, rr_append_handler, &req->addrl);
|
|
|
|
/* wait for other (A/AAAA) query to complete */
|
|
if (req->dnsq || req->dnsq2)
|
|
return;
|
|
|
|
if (!req->addrl.head && !req->srvl.head) {
|
|
err = err ? err : EDESTADDRREQ;
|
|
goto fail;
|
|
}
|
|
|
|
dns_rrlist_sort_addr(&req->addrl, req->sortkey);
|
|
|
|
err = request_next(req);
|
|
if (err)
|
|
goto fail;
|
|
|
|
return;
|
|
|
|
fail:
|
|
terminate(req, err, NULL);
|
|
mem_deref(req);
|
|
}
|
|
|
|
|
|
static int srv_lookup(struct sip_request *req, const char *domain)
|
|
{
|
|
char name[256];
|
|
|
|
if (re_snprintf(name, sizeof(name), "%s.%s",
|
|
sip_transp_srvid(req->tp), domain) < 0)
|
|
return ENOMEM;
|
|
|
|
return dnsc_query(&req->dnsq, req->sip->dnsc, name, DNS_TYPE_SRV,
|
|
DNS_CLASS_IN, true, srv_handler, req);
|
|
}
|
|
|
|
|
|
static int addr_lookup(struct sip_request *req, const char *name)
|
|
{
|
|
int err;
|
|
|
|
if (sip_transp_supported(req->sip, req->tp, AF_INET)) {
|
|
|
|
err = dnsc_query(&req->dnsq, req->sip->dnsc, name,
|
|
DNS_TYPE_A, DNS_CLASS_IN, true,
|
|
addr_handler, req);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
#ifdef HAVE_INET6
|
|
if (sip_transp_supported(req->sip, req->tp, AF_INET6)) {
|
|
|
|
err = dnsc_query(&req->dnsq2, req->sip->dnsc, name,
|
|
DNS_TYPE_AAAA, DNS_CLASS_IN, true,
|
|
addr_handler, req);
|
|
if (err)
|
|
return err;
|
|
}
|
|
#endif
|
|
|
|
if (!req->dnsq && !req->dnsq2)
|
|
return EPROTONOSUPPORT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Send a SIP request
|
|
*
|
|
* @param reqp Pointer to allocated SIP request object
|
|
* @param sip SIP Stack
|
|
* @param stateful Stateful client transaction
|
|
* @param met SIP Method string
|
|
* @param metl Length of SIP Method string
|
|
* @param uri Request URI
|
|
* @param uril Length of Request URI string
|
|
* @param route Next hop route URI
|
|
* @param mb Buffer containing SIP request
|
|
* @param sortkey Key for DNS record sorting
|
|
* @param sendh Send handler
|
|
* @param resph Response handler
|
|
* @param arg Handler argument
|
|
*
|
|
* @return 0 if success, otherwise errorcode
|
|
*/
|
|
int sip_request(struct sip_request **reqp, struct sip *sip, bool stateful,
|
|
const char *met, int metl, const char *uri, int uril,
|
|
const struct uri *route, struct mbuf *mb, size_t sortkey,
|
|
sip_send_h *sendh, sip_resp_h *resph, void *arg)
|
|
{
|
|
struct sip_request *req;
|
|
struct sa dst;
|
|
struct pl pl;
|
|
int err;
|
|
|
|
if (!sip || !met || !uri || !route || !mb)
|
|
return EINVAL;
|
|
|
|
if (pl_strcasecmp(&route->scheme, "sip"))
|
|
return ENOSYS;
|
|
|
|
req = mem_zalloc(sizeof(*req), destructor);
|
|
if (!req)
|
|
return ENOMEM;
|
|
|
|
list_append(&sip->reql, &req->le, req);
|
|
|
|
err = str_ldup(&req->met, met, metl);
|
|
if (err)
|
|
goto out;
|
|
|
|
err = str_ldup(&req->uri, uri, uril);
|
|
if (err)
|
|
goto out;
|
|
|
|
if (msg_param_decode(&route->params, "maddr", &pl))
|
|
pl = route->host;
|
|
|
|
err = pl_strdup(&req->host, &pl);
|
|
if (err)
|
|
goto out;
|
|
|
|
req->stateful = stateful;
|
|
req->sortkey = sortkey;
|
|
req->mb = mem_ref(mb);
|
|
req->sip = sip;
|
|
req->sendh = sendh;
|
|
req->resph = resph;
|
|
req->arg = arg;
|
|
|
|
if (!msg_param_decode(&route->params, "transport", &pl)) {
|
|
|
|
if (!pl_strcasecmp(&pl, "udp"))
|
|
req->tp = SIP_TRANSP_UDP;
|
|
else if (!pl_strcasecmp(&pl, "tcp"))
|
|
req->tp = SIP_TRANSP_TCP;
|
|
else if (!pl_strcasecmp(&pl, "tls"))
|
|
req->tp = SIP_TRANSP_TLS;
|
|
else {
|
|
err = EPROTONOSUPPORT;
|
|
goto out;
|
|
}
|
|
|
|
if (!sip_transp_supported(sip, req->tp, AF_UNSPEC)) {
|
|
err = EPROTONOSUPPORT;
|
|
goto out;
|
|
}
|
|
|
|
req->tp_selected = true;
|
|
}
|
|
else {
|
|
req->tp = SIP_TRANSP_NONE;
|
|
if (!transp_next(sip, &req->tp)) {
|
|
err = EPROTONOSUPPORT;
|
|
goto out;
|
|
}
|
|
|
|
req->tp_selected = false;
|
|
}
|
|
|
|
if (!sa_set_str(&dst, req->host,
|
|
sip_transp_port(req->tp, route->port))) {
|
|
|
|
err = request(req, req->tp, &dst);
|
|
if (!req->stateful) {
|
|
mem_deref(req);
|
|
return err;
|
|
}
|
|
}
|
|
else if (route->port) {
|
|
|
|
req->port = sip_transp_port(req->tp, route->port);
|
|
err = addr_lookup(req, req->host);
|
|
}
|
|
else if (req->tp_selected) {
|
|
|
|
err = srv_lookup(req, req->host);
|
|
}
|
|
else {
|
|
err = dnsc_query(&req->dnsq, sip->dnsc, req->host,
|
|
DNS_TYPE_NAPTR, DNS_CLASS_IN, true,
|
|
naptr_handler, req);
|
|
}
|
|
|
|
out:
|
|
if (err)
|
|
mem_deref(req);
|
|
else if (reqp) {
|
|
req->reqp = reqp;
|
|
*reqp = req;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
/**
|
|
* Send a SIP request with formatted arguments
|
|
*
|
|
* @param reqp Pointer to allocated SIP request object
|
|
* @param sip SIP Stack
|
|
* @param stateful Stateful client transaction
|
|
* @param met Null-terminated SIP Method string
|
|
* @param uri Null-terminated Request URI string
|
|
* @param route Next hop route URI (optional)
|
|
* @param auth SIP authentication state
|
|
* @param sendh Send handler
|
|
* @param resph Response handler
|
|
* @param arg Handler argument
|
|
* @param fmt Formatted SIP headers and body
|
|
*
|
|
* @return 0 if success, otherwise errorcode
|
|
*/
|
|
int sip_requestf(struct sip_request **reqp, struct sip *sip, bool stateful,
|
|
const char *met, const char *uri, const struct uri *route,
|
|
struct sip_auth *auth, sip_send_h *sendh, sip_resp_h *resph,
|
|
void *arg, const char *fmt, ...)
|
|
{
|
|
struct uri lroute;
|
|
struct mbuf *mb;
|
|
va_list ap;
|
|
int err;
|
|
|
|
if (!sip || !met || !uri || !fmt)
|
|
return EINVAL;
|
|
|
|
if (!route) {
|
|
struct pl uripl;
|
|
|
|
pl_set_str(&uripl, uri);
|
|
|
|
err = uri_decode(&lroute, &uripl);
|
|
if (err)
|
|
return err;
|
|
|
|
route = &lroute;
|
|
}
|
|
|
|
mb = mbuf_alloc(2048);
|
|
if (!mb)
|
|
return ENOMEM;
|
|
|
|
err = mbuf_write_str(mb, "Max-Forwards: 70\r\n");
|
|
|
|
if (auth)
|
|
err |= sip_auth_encode(mb, auth, met, uri);
|
|
|
|
if (err)
|
|
goto out;
|
|
|
|
va_start(ap, fmt);
|
|
err = mbuf_vprintf(mb, fmt, ap);
|
|
va_end(ap);
|
|
|
|
if (err)
|
|
goto out;
|
|
|
|
mb->pos = 0;
|
|
|
|
err = sip_request(reqp, sip, stateful, met, -1, uri, -1, route, mb,
|
|
(size_t)arg, sendh, resph, arg);
|
|
if (err)
|
|
goto out;
|
|
|
|
out:
|
|
mem_deref(mb);
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
/**
|
|
* Send a SIP dialog request with formatted arguments
|
|
*
|
|
* @param reqp Pointer to allocated SIP request object
|
|
* @param sip SIP Stack
|
|
* @param stateful Stateful client transaction
|
|
* @param met Null-terminated SIP Method string
|
|
* @param dlg SIP Dialog state
|
|
* @param cseq CSeq number
|
|
* @param auth SIP authentication state
|
|
* @param sendh Send handler
|
|
* @param resph Response handler
|
|
* @param arg Handler argument
|
|
* @param fmt Formatted SIP headers and body
|
|
*
|
|
* @return 0 if success, otherwise errorcode
|
|
*/
|
|
int sip_drequestf(struct sip_request **reqp, struct sip *sip, bool stateful,
|
|
const char *met, struct sip_dialog *dlg, uint32_t cseq,
|
|
struct sip_auth *auth, sip_send_h *sendh, sip_resp_h *resph,
|
|
void *arg, const char *fmt, ...)
|
|
{
|
|
struct mbuf *mb;
|
|
va_list ap;
|
|
int err;
|
|
|
|
if (!sip || !met || !dlg || !fmt)
|
|
return EINVAL;
|
|
|
|
mb = mbuf_alloc(2048);
|
|
if (!mb)
|
|
return ENOMEM;
|
|
|
|
err = mbuf_write_str(mb, "Max-Forwards: 70\r\n");
|
|
|
|
if (auth)
|
|
err |= sip_auth_encode(mb, auth, met, sip_dialog_uri(dlg));
|
|
|
|
err |= sip_dialog_encode(mb, dlg, cseq, met);
|
|
|
|
if (sip->software)
|
|
err |= mbuf_printf(mb, "User-Agent: %s\r\n", sip->software);
|
|
|
|
if (err)
|
|
goto out;
|
|
|
|
va_start(ap, fmt);
|
|
err = mbuf_vprintf(mb, fmt, ap);
|
|
va_end(ap);
|
|
|
|
if (err)
|
|
goto out;
|
|
|
|
mb->pos = 0;
|
|
|
|
err = sip_request(reqp, sip, stateful, met, -1, sip_dialog_uri(dlg),
|
|
-1, sip_dialog_route(dlg), mb, sip_dialog_hash(dlg),
|
|
sendh, resph, arg);
|
|
if (err)
|
|
goto out;
|
|
|
|
out:
|
|
mem_deref(mb);
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
/**
|
|
* Cancel a pending SIP Request
|
|
*
|
|
* @param req SIP Request
|
|
*/
|
|
void sip_request_cancel(struct sip_request *req)
|
|
{
|
|
if (!req || req->canceled)
|
|
return;
|
|
|
|
req->canceled = true;
|
|
|
|
if (!req->provrecv)
|
|
return;
|
|
|
|
(void)sip_ctrans_cancel(req->ct);
|
|
}
|
|
|
|
|
|
void sip_request_close(struct sip *sip)
|
|
{
|
|
if (!sip)
|
|
return;
|
|
|
|
list_apply(&sip->reql, true, close_handler, NULL);
|
|
}
|
|
|
|
|
|
/**
|
|
* Check if a SIP request loops
|
|
*
|
|
* @param ls Loop state
|
|
* @param scode Status code from SIP response
|
|
*
|
|
* @return True if loops, otherwise false
|
|
*/
|
|
bool sip_request_loops(struct sip_loopstate *ls, uint16_t scode)
|
|
{
|
|
bool loop = false;
|
|
|
|
if (!ls)
|
|
return false;
|
|
|
|
if (scode < 200) {
|
|
return false;
|
|
}
|
|
else if (scode < 300) {
|
|
ls->failc = 0;
|
|
}
|
|
else if (scode < 400) {
|
|
loop = (++ls->failc >= 16);
|
|
}
|
|
else {
|
|
switch (scode) {
|
|
|
|
default:
|
|
if (ls->last_scode == scode)
|
|
loop = true;
|
|
/*@fallthrough@*/
|
|
case 401:
|
|
case 407:
|
|
case 491:
|
|
if (++ls->failc >= 16)
|
|
loop = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
ls->last_scode = scode;
|
|
|
|
return loop;
|
|
}
|
|
|
|
|
|
/**
|
|
* Reset the loop state
|
|
*
|
|
* @param ls Loop state
|
|
*/
|
|
void sip_loopstate_reset(struct sip_loopstate *ls)
|
|
{
|
|
if (!ls)
|
|
return;
|
|
|
|
ls->last_scode = 0;
|
|
ls->failc = 0;
|
|
}
|