Asterisk源码解读(一:基础介绍与chan_sip代码分析)

基础知识

1.什么是asterisk通道?

Asterisk通道是指通过asterisk建立起来的一路通话。这类通话都包含一个incoming连接和一个outbound连接。每个电话都是通过一种通道驱动程序建立起来的,比如SIP,ZAP,IAX2等等。每一类的通道驱动,都拥有自己私有的通道数据结构,这些私有的结构从属于一个通用的Asterisk通道数据结构中,具体定义在channel.h和channel.c中。

所谓通道,便是电话与asterisk建立连接的所需要的链路。asterisk帮助我们使一台电话与另一台电话通话。那么其前置条件便是,asterisk必须与两台电话建立通道连接。发起呼叫端与asterisk建立的连接称之为incoming连接,也就是Incomging通道,而被呼叫端与asterisk建立的连接被称之为outbound,也就是outbound通道。

因此通道便是通话的前提,也可以说每台通话都是由通道建立起来,而谁来建立和控制通道呢,那就引出了通道驱动的概念。我们都知道,通话的方式有多种多样,比如我们用微信打电话,用固定电话打电话,等等,这些电话所使用的协议是不一样的,因此所建立的通道也是截然不同的,因此驱动的也有很多种。但同时这些驱动都有其相同的特征,知识内部实现不同而已,因此asterisk利用面向对象的方法实现了通道驱动的基类,其被声明和定义在channel.h与channel.c中。

2.呼叫场景(call scrnario)

下面为当一个呼叫电话抵达asterisk的情景

  1. 呼叫的抵达被某一类通道驱动的接口感知,比如SIP socket;
  2. 通道驱动为其创建一个PBX通道并且为这个通道开启一个PBX线程;
  3. 执行拨号方案;
  4. 在这个时候至少发生下面其中之一:
    a.Asterisk应答这个呼叫并且播放媒体数据或者读取媒体数据,比如IVR业务

b.拨号方案促使asterisk通过dial应用(dial application)创建一个对外呼叫,比如呼叫固定电话或移动电话时将连接落地网关。(具体见app_dial.c)

所谓SIP是基于TCP或UDP的协议,其本质还是两个socket直接的报文交互,因此asterisk会有专门的线程在监听端口数据包,并对数据包进行解析。

3.桥接通道(Bridge channels)

下面为当asterisk往外拨号时的情景:

  1. Dial创建一个outbound PBX通道并且要求对应的通道驱动创建一个呼叫
  2. 当电话被接通,asterisk桥接媒体流,使得第一个通道呼叫者能够与第二路通道的被呼叫者进行通话
  3. 有些情况下,呼入和呼出的通道使用相同的技术方案和兼容的编解码技术,这时候就会使用本地桥接。这种本地桥接方案中,通道驱动在内部直接传送到来的语音数据到对外的流中,而不通过PBX发送语音帧。
  4. 如果是SIP协议,将会有一个"external native bridge",asterisk重定向端点,使得语音直接在呼叫者和被呼叫者之间传送。信号被保存下来以方便提供正确的CDR记录。

4.植入通道(Masquerading channels)

有些情形下,一个通道可能植入到另一个通道中。这种情景在呼叫转移中比较常见,新的通道植入并且接管桥接的工作,旧的通道就成了僵通道而被挂起。

chan_sip.c

1.基础介绍

Chan_sip.c是SIP协议(RFC3261)的实现代码,它没有实现对S/MIME, TCP and TLS的支持,对应的配置文件是sip.conf,代码所在的分组是:通道驱动类(channel_drivers)。

SIP通道处理各种类型的Sip sessions和dialogs(注意:并不是所有的dialogs都是“电话呼叫”),主要包括:

    • Incoming calls that will be sent to the PBX core
    • Outgoing calls, generated by the PBX
    • SIP subscriptions and notifications of states and voicemail messages
    • SIP registrations, both inbound and outbound
    • SIP peer management (peerpoke, OPTIONS)
    • SIP text messages

在SIP通道中,通常会有一列活跃的SIP dialogs,CLI下的命令sip show channels可以显示出大部分dialogs,除了订阅类的(它们可以用命令sip show subscriptions显示出来)。

CLI命令sip show channels的示例: 

debian120*CLI> sip show channels 

Peer             User/ANR    Call ID      Seq (Tx/Rx)  Form  Hold     Last Message    

211.150.115.116  0132364499  51e8b037316  00102/00000  alaw  No       Init: INVITE               

202.108.12.94    0000123456  76ad6e55-e0  00101/00001  alaw  No       Rx: ACK                    

211.150.115.116  0216252766  29df5b95633  00102/00000  alaw  No       Init: INVITE               

202.108.12.94    0000123456  76ad6e55-2c   00101/00001  alaw  No       Rx: ACK                    

211.150.115.116  0137587006  720c5ecb32e  00102/00000  alaw  No        Tx: ACK                    

202.108.12.94    0000123456  76ad6e55-bf  00101/00001  alaw  No       Rx: ACK                    

211.150.115.116  0138797950  6d96c21a580  00102/00000  alaw  No        Tx: ACK                    

202.108.12.94    0000123456  76ad6e55-a5  00101/00001  alaw  No       Rx: ACK                    

211.150.115.116  0578708822  617679d2699  00102/00000  alaw  No        Tx: ACK                    

202.108.12.94    0000123456  76ad6e55-20  00101/00001  alaw  No       Rx: ACK                    

211.150.115.116  0512534057  6049a06e77d  00102/00000  alaw  No         Tx: ACK                    

202.108.12.94    0000123456  76ad6e55-b7  00101/00001  alaw  No       Rx: ACK                    

211.150.115.116  0132684273  4224f333507  00102/00000  alaw  No        Tx: ACK                    

202.108.12.94    0000123456  76ad6e55-95  00101/00001  alaw  No       Rx: ACK  

代码剖析

chan_sip模块注册了load_module()函数作为asterisk在加载本模块时的入口函数。 本例中以最新版的pjsip代码为参考

AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP Channel Driver",
		.support_level = AST_MODULE_SUPPORT_CORE,
		.load = load_module,
		.unload = unload_module,
		.load_pri = AST_MODPRI_CHANNEL_DRIVER,
	       );

load_module()函数读取配置文件sip.conf,并且注册一个通道驱动类型,即sip,具体见sip_tech中的结构内容。

static int load_module(void)
{
	struct sip_peer *bogus_peer;

	ast_verbose("SIP channel loading...\n");

	if (STASIS_MESSAGE_TYPE_INIT(session_timeout_type)) {
		unload_module();
		return AST_MODULE_LOAD_FAILURE;
	}

	if (!(sip_tech.capabilities = ast_format_cap_alloc(0))) {
		unload_module();
		return AST_MODULE_LOAD_FAILURE;
	}

	if (ast_sip_api_provider_register(&chan_sip_api_provider)) {
		unload_module();
		return AST_MODULE_LOAD_FAILURE;
	}

	/* the fact that ao2_containers can't resize automatically is a major worry! */
	/* if the number of objects gets above MAX_XXX_BUCKETS, things will slow down */
	peers = ao2_t_container_alloc(HASH_PEER_SIZE, peer_hash_cb, peer_cmp_cb, "allocate peers");
	peers_by_ip = ao2_t_container_alloc(HASH_PEER_SIZE, peer_iphash_cb, peer_ipcmp_cb, "allocate peers_by_ip");
	dialogs = ao2_t_container_alloc(HASH_DIALOG_SIZE, dialog_hash_cb, dialog_cmp_cb, "allocate dialogs");
	dialogs_needdestroy = ao2_t_container_alloc(1, NULL, NULL, "allocate dialogs_needdestroy");
	dialogs_rtpcheck = ao2_t_container_alloc(HASH_DIALOG_SIZE, dialog_hash_cb, dialog_cmp_cb, "allocate dialogs for rtpchecks");
	threadt = ao2_t_container_alloc(HASH_DIALOG_SIZE, threadt_hash_cb, threadt_cmp_cb, "allocate threadt table");
	if (!peers || !peers_by_ip || !dialogs || !dialogs_needdestroy || !dialogs_rtpcheck
		|| !threadt) {
		ast_log(LOG_ERROR, "Unable to create primary SIP container(s)\n");
		unload_module();
		return AST_MODULE_LOAD_FAILURE;
	}

	if (!(sip_cfg.caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) {
		unload_module();
		return AST_MODULE_LOAD_FAILURE;
	}
	ast_format_cap_append_by_type(sip_tech.capabilities, AST_MEDIA_TYPE_AUDIO);

	registry_list = ao2_t_container_alloc(HASH_REGISTRY_SIZE, registry_hash_cb, registry_cmp_cb, "allocate registry_list");
	subscription_mwi_list = ao2_t_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX,
		AO2_CONTAINER_ALLOC_OPT_INSERT_BEGIN, NULL, NULL, "allocate subscription_mwi_list");

	if (!(sched = ast_sched_context_create())) {
		ast_log(LOG_ERROR, "Unable to create scheduler context\n");
		unload_module();
		return AST_MODULE_LOAD_FAILURE;
	}

	if (!(io = io_context_create())) {
		ast_log(LOG_ERROR, "Unable to create I/O context\n");
		unload_module();
		return AST_MODULE_LOAD_FAILURE;
	}

	sip_reloadreason = CHANNEL_MODULE_LOAD;

	can_parse_xml = sip_is_xml_parsable();
	if (reload_config(sip_reloadreason)) {	/* Load the configuration from sip.conf */
		unload_module();
		return AST_MODULE_LOAD_DECLINE;
	}

	/* Initialize bogus peer. Can be done first after reload_config() */
	if (!(bogus_peer = temp_peer("(bogus_peer)"))) {
		ast_log(LOG_ERROR, "Unable to create bogus_peer for authentication\n");
		unload_module();
		return AST_MODULE_LOAD_FAILURE;
	}
	/* Make sure the auth will always fail. */
	ast_string_field_set(bogus_peer, md5secret, BOGUS_PEER_MD5SECRET);
	ast_clear_flag(&bogus_peer->flags[0], SIP_INSECURE);
	ao2_t_global_obj_replace_unref(g_bogus_peer, bogus_peer, "Set the initial bogus peer.");
	ao2_t_ref(bogus_peer, -1, "Module load is done with the bogus peer.");

	/* Prepare the version that does not require DTMF BEGIN frames.
	 * We need to use tricks such as memcpy and casts because the variable
	 * has const fields.
	 */
	memcpy(&sip_tech_info, &sip_tech, sizeof(sip_tech));
	memset((void *) &sip_tech_info.send_digit_begin, 0, sizeof(sip_tech_info.send_digit_begin));

	if (ast_msg_tech_register(&sip_msg_tech)) {
		unload_module();
		return AST_MODULE_LOAD_FAILURE;
	}

	/* Make sure we can register our sip channel type */
	if (ast_channel_register(&sip_tech)) {
		ast_log(LOG_ERROR, "Unable to register channel type 'SIP'\n");
		unload_module();
		return AST_MODULE_LOAD_FAILURE;
	}

#ifdef TEST_FRAMEWORK
	AST_TEST_REGISTER(test_sip_peers_get);
	AST_TEST_REGISTER(test_sip_mwi_subscribe_parse);
	AST_TEST_REGISTER(test_tcp_message_fragmentation);
	AST_TEST_REGISTER(get_in_brackets_const_test);
#endif

	/* Register AstData providers */
	ast_data_register_multiple(sip_data_providers, ARRAY_LEN(sip_data_providers));

	/* Register all CLI functions for SIP */
	ast_cli_register_multiple(cli_sip, ARRAY_LEN(cli_sip));

	/* Tell the RTP engine about our RTP glue */
	ast_rtp_glue_register(&sip_rtp_glue);

	/* Register dialplan applications */
	ast_register_application_xml(app_dtmfmode, sip_dtmfmode);
	ast_register_application_xml(app_sipaddheader, sip_addheader);
	ast_register_application_xml(app_sipremoveheader, sip_removeheader);
#ifdef TEST_FRAMEWORK
	ast_register_application_xml(app_sipsendcustominfo, sip_sendcustominfo);
#endif

	/* Register dialplan functions */
	ast_custom_function_register(&sip_header_function);
	ast_custom_function_register(&sippeer_function);
	ast_custom_function_register(&checksipdomain_function);

	/* Register manager commands */
	ast_manager_register_xml("SIPpeers", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, manager_sip_show_peers);
	ast_manager_register_xml("SIPshowpeer", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, manager_sip_show_peer);
	ast_manager_register_xml("SIPqualifypeer", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, manager_sip_qualify_peer);
	ast_manager_register_xml("SIPshowregistry", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, manager_show_registry);
	ast_manager_register_xml("SIPnotify", EVENT_FLAG_SYSTEM, manager_sipnotify);
	ast_manager_register_xml("SIPpeerstatus", EVENT_FLAG_SYSTEM, manager_sip_peer_status);
	sip_poke_all_peers();
	sip_keepalive_all_peers();
	sip_send_all_registers();
	sip_send_all_mwi_subscriptions();
	initialize_escs();

	if (sip_epa_register(&cc_epa_static_data)) {
		unload_module();
		return AST_MODULE_LOAD_DECLINE;
	}

	if (sip_reqresp_parser_init() == -1) {
		ast_log(LOG_ERROR, "Unable to initialize the SIP request and response parser\n");
		unload_module();
		return AST_MODULE_LOAD_DECLINE;
	}

	if (can_parse_xml) {
		/* SIP CC agents require the ability to parse XML PIDF bodies
		 * in incoming PUBLISH requests
		 */
		if (ast_cc_agent_register(&sip_cc_agent_callbacks)) {
			unload_module();
			return AST_MODULE_LOAD_DECLINE;
		}
	}
	if (ast_cc_monitor_register(&sip_cc_monitor_callbacks)) {
		unload_module();
		return AST_MODULE_LOAD_DECLINE;
	}
	if (!(sip_monitor_instances = ao2_container_alloc(37, sip_monitor_instance_hash_fn, sip_monitor_instance_cmp_fn))) {
		unload_module();
		return AST_MODULE_LOAD_DECLINE;
	}

	/* And start the monitor for the first time */
	restart_monitor();

	ast_realtime_require_field(ast_check_realtime("sipregs") ? "sipregs" : "sippeers",
		"name", RQ_CHAR, 10,
		"ipaddr", RQ_CHAR, INET6_ADDRSTRLEN - 1,
		"port", RQ_UINTEGER2, 5,
		"regseconds", RQ_INTEGER4, 11,
		"defaultuser", RQ_CHAR, 10,
		"fullcontact", RQ_CHAR, 35,
		"regserver", RQ_CHAR, 20,
		"useragent", RQ_CHAR, 20,
		"lastms", RQ_INTEGER4, 11,
		SENTINEL);


	sip_register_tests();
	network_change_stasis_subscribe();

	if (sip_cfg.websocket_enabled) {
		ast_websocket_add_protocol("sip", sip_websocket_callback);
	}

	return AST_MODULE_LOAD_SUCCESS;
}

Load_module最后调用restart_monitor()来启动sip监听。restart_monitor另外还有两处会被调用,在sip_request_call()和sip_reload()函数体内。

restart_monitor调用pthread接口启动一个独立的监听线程,线程id记录在monitor_thread,线程入口函数是do_monitor()

static int restart_monitor(void)
{
	/* If we're supposed to be stopped -- stay stopped */
	if (monitor_thread == AST_PTHREADT_STOP)
		return 0;
	ast_mutex_lock(&monlock);
	if (monitor_thread == pthread_self()) {
		ast_mutex_unlock(&monlock);
		ast_log(LOG_WARNING, "Cannot kill myself\n");
		return -1;
	}
	if (monitor_thread != AST_PTHREADT_NULL && monitor_thread != AST_PTHREADT_STOP) {
		/* Wake up the thread */
		pthread_kill(monitor_thread, SIGURG);
	} else {
		/* Start a new monitor */
		if (ast_pthread_create_background(&monitor_thread, NULL, do_monitor, NULL) < 0) {
			ast_mutex_unlock(&monlock);
			ast_log(LOG_ERROR, "Unable to start monitor thread.\n");
			return -1;
		}
	}
	ast_mutex_unlock(&monlock);
	return 0;
}

do_monitor()给SIP UDP socket添加事件处理器,sipsock_read负责读取socket收到的数据
同时:
do_monitor ()函数然后进入一个for()循环中,这个循环不断检测是否需要reload sip模块,并且遍历sip session列表检查是否有需要kill的session。它是怎么遍历的呢?原来是chan_sip 维护了一个sip_pvt结构的列表,头指针保存在全局变量iflist中,通过sip_pvt的next域进行遍历。每个sip_pvt结构记录了一个session的全部信息。

变量t表示现在的时间,sip->lastrtptx表示上次发送rtp包的时间,如果两者之差大于keep alive间隔,则说明需要发送keep alive包了。

static void *do_monitor(void *data)
{
	int res;
	time_t t;
	int reloading;

	/* Add an I/O event to our SIP UDP socket */
	if (sipsock > -1) {
		sipsock_read_id = ast_io_add(io, sipsock, sipsock_read, AST_IO_IN, NULL);
	}

	/* From here on out, we die whenever asked */
	for(;;) {
		/* Check for a reload request */
		ast_mutex_lock(&sip_reload_lock);
		reloading = sip_reloading;
		sip_reloading = FALSE;
		ast_mutex_unlock(&sip_reload_lock);
		if (reloading) {
			ast_verb(1, "Reloading SIP\n");
			sip_do_reload(sip_reloadreason);

			/* Change the I/O fd of our UDP socket */
			if (sipsock > -1) {
				if (sipsock_read_id) {
					sipsock_read_id = ast_io_change(io, sipsock_read_id, sipsock, NULL, 0, NULL);
				} else {
					sipsock_read_id = ast_io_add(io, sipsock, sipsock_read, AST_IO_IN, NULL);
				}
			} else if (sipsock_read_id) {
				ast_io_remove(io, sipsock_read_id);
				sipsock_read_id = NULL;
			}
		}

		/* Check for dialogs needing to be killed */
		t = time(NULL);

		/*
		 * Check dialogs with rtp and rtptimeout.
		 * All dialogs which have rtp are in dialogs_rtpcheck.
		 */
		ao2_t_callback(dialogs_rtpcheck, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE,
			dialog_checkrtp_cb, &t,
			"callback to check rtptimeout and hangup calls if necessary");
		/*
		 * Check dialogs marked to be destroyed.
		 * All dialogs with needdestroy set are in dialogs_needdestroy.
		 */
		ao2_t_callback(dialogs_needdestroy, OBJ_NODATA | OBJ_MULTIPLE, dialog_needdestroy,
			NULL, "callback to check dialogs which need to be destroyed");

		/* XXX TODO The scheduler usage in this module does not have sufficient
		 * synchronization being done between running the scheduler and places
		 * scheduling tasks.  As it is written, any scheduled item may not run
		 * any sooner than about  1 second, regardless of whether a sooner time
		 * was asked for. */

		pthread_testcancel();
		/* Wait for sched or io */
		res = ast_sched_wait(sched);
		if ((res < 0) || (res > 1000)) {
			res = 1000;
		}
		res = ast_io_wait(io, res);
		if (res > 20) {
			ast_debug(1, "chan_sip: ast_io_wait ran %d all at once\n", res);
		}
		ast_mutex_lock(&monlock);
		res = ast_sched_runq(sched);
		if (res >= 20) {
			ast_debug(1, "chan_sip: ast_sched_runq ran %d all at once\n", res);
		}
		ast_mutex_unlock(&monlock);
	}

	/* Never reached */
	return NULL;
}

相关重要的数据结构

sip_pvt: PVT structures are used for each SIP dialog, ie. a call, a registration, a subscribe
sip_pvt这个结构维护了一个sip session的重要数据信息,关键字段如下:
struct sip_pvt* next
Next dialog in chain。指向链上的下一个sip_pvt结构

struct sip_pvt {
	struct sip_pvt *next;                   /*!< Next dialog in chain */
	enum invitestates invitestate;          /*!< Track state of SIP_INVITEs */
	struct ast_callid *logger_callid;		/*!< Identifier for call used in log messages */
	int method;                             /*!< SIP method that opened this dialog */
	AST_DECLARE_STRING_FIELDS(
		AST_STRING_FIELD(callid);       /*!< Global CallID */
		AST_STRING_FIELD(initviabranch); /*!< The branch ID from the topmost Via header in the initial request */
		AST_STRING_FIELD(initviasentby); /*!< The sent-by from the topmost Via header in the initial request */
		AST_STRING_FIELD(accountcode);  /*!< Account code */
		AST_STRING_FIELD(realm);        /*!< Authorization realm */
		AST_STRING_FIELD(nonce);        /*!< Authorization nonce */
		AST_STRING_FIELD(opaque);       /*!< Opaque nonsense */
		AST_STRING_FIELD(qop);          /*!< Quality of Protection, since SIP wasn't complicated enough yet. */
		AST_STRING_FIELD(domain);       /*!< Authorization domain */
		AST_STRING_FIELD(from);         /*!< The From: header */
		AST_STRING_FIELD(useragent);    /*!< User agent in SIP request */
		AST_STRING_FIELD(exten);        /*!< Extension where to start */
		AST_STRING_FIELD(context);      /*!< Context for this call */
		AST_STRING_FIELD(messagecontext); /*!< Default context for outofcall messages. */
		AST_STRING_FIELD(subscribecontext); /*!< Subscribecontext */
		AST_STRING_FIELD(subscribeuri); /*!< Subscribecontext */
		AST_STRING_FIELD(fromdomain);   /*!< Domain to show in the from field */
		AST_STRING_FIELD(fromuser);     /*!< User to show in the user field */
		AST_STRING_FIELD(fromname);     /*!< Name to show in the user field */
		AST_STRING_FIELD(tohost);       /*!< Host we should put in the "to" field */
		AST_STRING_FIELD(todnid);       /*!< DNID of this call (overrides host) */
		AST_STRING_FIELD(language);     /*!< Default language for this call */
		AST_STRING_FIELD(mohinterpret); /*!< MOH class to use when put on hold */
		AST_STRING_FIELD(mohsuggest);   /*!< MOH class to suggest when putting a peer on hold */
		AST_STRING_FIELD(rdnis);        /*!< Referring DNIS */
		AST_STRING_FIELD(redircause);   /*!< Referring cause */
		AST_STRING_FIELD(theirtag);     /*!< Their tag */
		AST_STRING_FIELD(theirprovtag); /*!< Provisional their tag, used when evaluating responses to invites */
		AST_STRING_FIELD(tag);          /*!< Our tag for this session */
		AST_STRING_FIELD(username);     /*!< [user] name */
		AST_STRING_FIELD(peername);     /*!< [peer] name, not set if [user] */
		AST_STRING_FIELD(authname);     /*!< Who we use for authentication */
		AST_STRING_FIELD(uri);          /*!< Original requested URI */
		AST_STRING_FIELD(okcontacturi); /*!< URI from the 200 OK on INVITE */
		AST_STRING_FIELD(peersecret);   /*!< Password */
		AST_STRING_FIELD(peermd5secret);
		AST_STRING_FIELD(cid_num);      /*!< Caller*ID number */
		AST_STRING_FIELD(cid_name);     /*!< Caller*ID name */
		AST_STRING_FIELD(cid_tag);      /*!< Caller*ID tag */
		AST_STRING_FIELD(mwi_from);     /*!< Name to place in the From header in outgoing NOTIFY requests */
		AST_STRING_FIELD(fullcontact);  /*!< The Contact: that the UA registers with us */
		                                /* we only store the part in <brackets> in this field. */
		AST_STRING_FIELD(our_contact);  /*!< Our contact header */
		AST_STRING_FIELD(url);          /*!< URL to be sent with next message to peer */
		AST_STRING_FIELD(parkinglot);   /*!< Parkinglot */
		AST_STRING_FIELD(engine);       /*!< RTP engine to use */
		AST_STRING_FIELD(dialstring);   /*!< The dialstring used to call this SIP endpoint */
		AST_STRING_FIELD(last_presence_subtype);   /*!< The last presence subtype sent for a subscription. */
		AST_STRING_FIELD(last_presence_message);   /*!< The last presence message for a subscription */
		AST_STRING_FIELD(msg_body);     /*!< Text for a MESSAGE body */
		AST_STRING_FIELD(tel_phone_context);       /*!< The phone-context portion of a TEL URI */
	);
	char via[128];                          /*!< Via: header */
	int maxforwards;                        /*!< SIP Loop prevention */
	struct sip_socket socket;               /*!< The socket used for this dialog */
	uint32_t ocseq;                         /*!< Current outgoing seqno */
	uint32_t icseq;                         /*!< Current incoming seqno */
	uint32_t init_icseq;                    /*!< Initial incoming seqno from first request */
	ast_group_t callgroup;                  /*!< Call group */
	ast_group_t pickupgroup;                /*!< Pickup group */
	struct ast_namedgroups *named_callgroups;   /*!< Named call group */
	struct ast_namedgroups *named_pickupgroups; /*!< Named pickup group */
	uint32_t lastinvite;                    /*!< Last seqno of invite */
	struct ast_flags flags[3];              /*!< SIP_ flags */

	/* boolean flags that don't belong in flags */
	unsigned short do_history:1;          /*!< Set if we want to record history */
	unsigned short alreadygone:1;         /*!< the peer has sent a message indicating termination of the dialog */
	unsigned short needdestroy:1;         /*!< this dialog needs to be destroyed by the monitor thread */
	unsigned short final_destruction_scheduled:1; /*!< final dialog destruction is scheduled. Keep dialog
	                                               *   around until then to handle retransmits. */
	unsigned short outgoing_call:1;       /*!< this is an outgoing call */
	unsigned short answered_elsewhere:1;  /*!< This call is cancelled due to answer on another channel */
	unsigned short novideo:1;             /*!< Didn't get video in invite, don't offer */
	unsigned short notext:1;              /*!< Text not supported  (?) */
	unsigned short session_modify:1;      /*!< Session modification request true/false  */
	unsigned short route_persistent:1;    /*!< Is this the "real" route? */
	unsigned short autoframing:1;         /*!< Whether to use our local configuration for frame sizes (off)
	                                       *   or respect the other endpoint's request for frame sizes (on)
	                                       *   for incoming calls
	                                       */
	unsigned short req_secure_signaling:1;/*!< Whether we are required to have secure signaling or not */
	unsigned short natdetected:1;         /*!< Whether we detected a NAT when processing the Via */
	int timer_t1;                     /*!< SIP timer T1, ms rtt */
	int timer_b;                      /*!< SIP timer B, ms */
	unsigned int sipoptions;          /*!< Supported SIP options on the other end */
	unsigned int reqsipoptions;       /*!< Required SIP options on the other end */
	struct ast_format_cap *caps;             /*!< Special capability (codec) */
	struct ast_format_cap *jointcaps;        /*!< Supported capability at both ends (codecs) */
	struct ast_format_cap *peercaps;         /*!< Supported peer capability */
	struct ast_format_cap *redircaps;        /*!< Redirect codecs */
	struct ast_format_cap *prefcaps;         /*!< Preferred codec (outbound only) */
	int noncodeccapability;	          /*!< DTMF RFC2833 telephony-event */
	int jointnoncodeccapability;      /*!< Joint Non codec capability */
#ifdef YEASTAR_SWJ_CODEC
	int annexb;//G.729
	int annexa;//G.723.1
	int mode;//iLBC
#endif
	int maxcallbitrate;               /*!< Maximum Call Bitrate for Video Calls */
	int t38_maxdatagram;              /*!< T.38 FaxMaxDatagram override */
	int request_queue_sched_id;       /*!< Scheduler ID of any scheduled action to process queued requests */
	int provisional_keepalive_sched_id;   /*!< Scheduler ID for provisional responses that need to be sent out to avoid cancellation */
	const char *last_provisional;         /*!< The last successfully transmitted provisonal response message */
	int authtries;                        /*!< Times we've tried to authenticate */
	struct sip_proxy *outboundproxy;      /*!< Outbound proxy for this dialog. Use ref_proxy to set this instead of setting it directly*/
	struct t38properties t38;             /*!< T38 settings */
	struct ast_sockaddr udptlredirip;     /*!< Where our T.38 UDPTL should be going if not to us */
	struct ast_udptl *udptl;              /*!< T.38 UDPTL session */
	char zone[MAX_TONEZONE_COUNTRY];      /*!< Default tone zone for channels created by this dialog */
	int callingpres;                      /*!< Calling presentation */
	int expiry;                         /*!< How long we take to expire */
	int sessionversion;                 /*!< SDP Session Version */
	int sessionid;                      /*!< SDP Session ID */
	long branch;                        /*!< The branch identifier of this session */
	long invite_branch;                 /*!< The branch used when we sent the initial INVITE */
	int64_t sessionversion_remote;      /*!< Remote UA's SDP Session Version */
	unsigned int portinuri:1;           /*!< Non zero if a port has been specified, will also disable srv lookups */
	struct ast_sockaddr sa;              /*!< Our peer */
	struct ast_sockaddr redirip;         /*!< Where our RTP should be going if not to us */
	struct ast_sockaddr vredirip;        /*!< Where our Video RTP should be going if not to us */
	struct ast_sockaddr tredirip;        /*!< Where our Text RTP should be going if not to us */
	time_t lastrtprx;                   /*!< Last RTP received */
	time_t lastrtptx;                   /*!< Last RTP sent */
	int rtptimeout;                     /*!< RTP timeout time */
	int rtpholdtimeout;                 /*!< RTP timeout time on hold*/
	int rtpkeepalive;                   /*!< RTP send packets for keepalive */
	struct ast_acl_list *directmediaacl; /*!< Which IPs are allowed to interchange direct media with this peer - copied from sip_peer */
	struct ast_sockaddr recv;            /*!< Received as */
	struct ast_sockaddr ourip;           /*!< Our IP (as seen from the outside) */
	enum transfermodes allowtransfer;   /*!< REFER: restriction scheme */
	struct ast_channel *owner;          /*!< Who owns us (if we have an owner) */
	struct sip_route route;             /*!< List of routing steps (fm Record-Route) */
	struct sip_notify *notify;          /*!< Custom notify type */
	struct sip_auth_container *peerauth;/*!< Realm authentication credentials */
	int noncecount;                     /*!< Nonce-count */
	unsigned int stalenonce:1;          /*!< Marks the current nonce as responded too */
	unsigned int ongoing_reinvite:1;    /*!< There is a reinvite in progress that might need to be cleaned up */
	char lastmsg[256];                  /*!< Last Message sent/received */
	int amaflags;                       /*!< AMA Flags */
	uint32_t pendinginvite; /*!< Any pending INVITE or state NOTIFY (in subscribe pvt's) ? (seqno of this) */
	uint32_t glareinvite;      /*!< A invite received while a pending invite is already present is stored here.  Its seqno is the
	                           value. Since this glare invite's seqno is not the same as the pending invite's, it must be
	                           held in order to properly process acknowledgements for our 491 response. */
	struct sip_request initreq;         /*!< Latest request that opened a new transaction
	                                         within this dialog.
	                                         NOT the request that opened the dialog */

	int initid;                         /*!< Auto-congest ID if appropriate (scheduler) */
	int waitid;                         /*!< Wait ID for scheduler after 491 or other delays */
	int reinviteid;                     /*!< Reinvite in case of provisional, but no final response */
	int autokillid;                     /*!< Auto-kill ID (scheduler) */
	int t38id;                          /*!< T.38 Response ID */
	struct sip_refer *refer;            /*!< REFER: SIP transfer data structure */
	enum subscriptiontype subscribed;   /*!< SUBSCRIBE: Is this dialog a subscription?  */
	int stateid;                        /*!< SUBSCRIBE: ID for devicestate subscriptions */
	int laststate;                      /*!< SUBSCRIBE: Last known extension state */
	struct ao2_container *last_device_state_info; /*!< SUBSCRIBE: last known extended extension state (take care of refs)*/
	struct timeval last_ringing_channel_time; /*!< SUBSCRIBE: channel timestamp of the channel which caused the last early-state notification */
	int last_presence_state;            /*!< SUBSCRIBE: Last known presence state */
	uint32_t dialogver;                 /*!< SUBSCRIBE: Version for subscription dialog-info */

	struct ast_dsp *dsp;                /*!< Inband DTMF or Fax CNG tone Detection dsp */

	struct sip_peer *relatedpeer;       /*!< If this dialog is related to a peer, which one
	                                         Used in peerpoke, mwi subscriptions */
	struct sip_registry *registry;      /*!< If this is a REGISTER dialog, to which registry */
	struct ast_rtp_instance *rtp;       /*!< RTP Session */
	struct ast_rtp_instance *vrtp;      /*!< Video RTP session */
	struct ast_rtp_instance *trtp;      /*!< Text RTP session */
	struct sip_pkt *packets;            /*!< Packets scheduled for re-transmission */
	struct sip_history_head *history;   /*!< History of this SIP dialog */
	size_t history_entries;             /*!< Number of entires in the history */
	struct ast_variable *chanvars;      /*!< Channel variables to set for inbound call */
	AST_LIST_HEAD_NOLOCK(, sip_msg_hdr) msg_headers; /*!< Additional MESSAGE headers to send. */
	AST_LIST_HEAD_NOLOCK(request_queue, sip_request) request_queue; /*!< Requests that arrived but could not be processed immediately */
	struct sip_invite_param *options;   /*!< Options for INVITE */
	struct sip_st_dlg *stimer;          /*!< SIP Session-Timers */
	struct ast_sdp_srtp *srtp;              /*!< Structure to hold Secure RTP session data for audio */
	struct ast_sdp_srtp *vsrtp;             /*!< Structure to hold Secure RTP session data for video */
	struct ast_sdp_srtp *tsrtp;             /*!< Structure to hold Secure RTP session data for text */

	int red;                            /*!< T.140 RTP Redundancy */
	int hangupcause;                    /*!< Storage of hangupcause copied from our owner before we disconnect from the AST channel (only used at hangup) */

	struct sip_subscription_mwi *mwi;   /*!< If this is a subscription MWI dialog, to which subscription */
	/*! The SIP methods supported by this peer. We get this information from the Allow header of the first
	 * message we receive from an endpoint during a dialog.
	 */
	unsigned int allowed_methods;
	/*! Some peers are not trustworthy with their Allow headers, and so we need to override their wicked
	 * ways through configuration. This is a copy of the peer's disallowed_methods, so that we can apply them
	 * to the sip_pvt at various stages of dialog establishment
	 */
	unsigned int disallowed_methods;
	/*! When receiving an SDP offer, it is important to take note of what media types were offered.
	 * By doing this, even if we don't want to answer a particular media stream with something meaningful, we can
	 * still put an m= line in our answer with the port set to 0.
	 *
	 * The reason for the length being 4 (OFFERED_MEDIA_COUNT) is that in this branch of Asterisk, the only media types supported are
	 * image, audio, text, and video. Therefore we need to keep track of which types of media were offered.
	 * Note that secure RTP defines new types of SDP media.
	 *
	 * If we wanted to be 100% correct, we would keep a list of all media streams offered. That way we could respond
	 * even to unknown media types, and we could respond to multiple streams of the same type. Such large-scale changes
	 * are not a good idea for released branches, though, so we're compromising by just making sure that for the common cases:
	 * audio and video, audio and T.38, and audio and text, we give the appropriate response to both media streams.
	 *
	 * The large-scale changes would be a good idea for implementing during an SDP rewrite.
	 */
	AST_LIST_HEAD_NOLOCK(, offered_media) offered_media;
	struct ast_cc_config_params *cc_params;
	struct sip_epa_entry *epa_entry;
	int fromdomainport;                 /*!< Domain port to show in from field */

	struct ast_rtp_dtls_cfg dtls_cfg;
};

struct ast_channel* owner

Who owns us (if we have an owner)。指向了拥有这个结构的通道的指针

struct sip_pkt* packets

Packets scheduled for re-transmission。维护待重传的sip packet

int pendinginvite

Any pending invite ? (seqno of this)。如果有等待的邀请包,则在这里记下这个包序号

struct ast_rtp* rtp

RTP Session,指向RTP Session的指针

int rtptimeout

RTP timeout time, RTP的超时时间

struct sockaddr_in sa

Our peer,对方的地址信息

char tag[11]

Our tag for this session,比如:tag=965531f1-52721549

重要的报文解析函数sipsock_read

所有到来的sip包都在这里开始处理,在处理sip包期间,sipsock_read需要对sip的拥有者channel上锁,sipsock_read成功则返回0,失败则返回1。它解析sip包并且找到所在的dialog,或者创建新的dialog。并且把解析好的包交给handle_request()处理。

sipsock_read第一步接收socket数据,存到结构sip_request的data域中。

然后解析SIP包,获取sip request method,如INVITE, BYE等

随后找到对应的sip_pvt结构,或者创建新的sip_pvt结构,结构指针返回到变量p中。

在进一步操作之前,需要对p->owner上锁,这个操作会最多尝试100次直至成功。

如果上锁操作失败,将会返回503 sip消息。

更深一步的解析处理操作交给handle_request()函数处理,完了之后就是释放channel的锁。

static int sipsock_read(int *id, int fd, short events, void *ignore)
{
	struct sip_request req;
	struct ast_sockaddr addr;
	int res;
	static char readbuf[65535];

	memset(&req, 0, sizeof(req));
	res = ast_recvfrom(fd, readbuf, sizeof(readbuf) - 1, 0, &addr);
	if (res < 0) {
#if !defined(__FreeBSD__)
		if (errno == EAGAIN)
			ast_log(LOG_NOTICE, "SIP: Received packet with bad UDP checksum\n");
		else
#endif
		if (errno != ECONNREFUSED)
			ast_log(LOG_WARNING, "Recv error: %s\n", strerror(errno));
		return 1;
	}

	readbuf[res] = '\0';

	if (!(req.data = ast_str_create(SIP_MIN_PACKET))) {
		return 1;
	}

	if (ast_str_set(&req.data, 0, "%s", readbuf) == AST_DYNSTR_BUILD_FAILED) {
		return -1;
	}

	req.socket.fd = sipsock;
	set_socket_transport(&req.socket, AST_TRANSPORT_UDP);
	req.socket.tcptls_session	= NULL;
	req.socket.port = htons(ast_sockaddr_port(&bindaddr));

	handle_request_do(&req, &addr);
	deinit_req(&req);

	return 1;
}

函数handle_request()视数据包的类型而处理,如果是对外出包的回应,则交给handle_response()处理,如果是一个请求包,则视请求类型(INVITE, OPTIONS, REFER, BYE, CANCEL etc)交给不同的函数处理。如果是一个INVITE包,则交给handle_request_invite()处理,在那里将会创建一个新的channel,这个通道随后会执行一个单独的通道线程。这就是一个来电呼叫。如果这个呼叫被应答,则一个桥接通道或者PBX本身会回调sip_answer()函数。而真正的媒体数据,音频或者视频,则会在RTP子系统中处理,具体见rtp.c

Outbound calls

Outbound calls are set up by the PBX through the sip_request_call() function. After that, they are activated by sip_call().

Hanging up

The PBX issues a hangup on both incoming and outgoing calls through the sip_hangup() function

现在回顾一下注册SIP通道驱动时,我们注册了一系列通道驱动的回调函数,这些有什么用呢?比如当需要发出一个outbound call时,则会调用sip_request_call()。而当需要hangup时,则调用sip_hangup()。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

举世无双勇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值