coturn源码解析

2 篇文章 0 订阅
1 篇文章 0 订阅

前言

发现关于coturn源码分析的文章没有,由于只接触其中一部分功能,在此粗略解析一下。

当你想了解coturn源码时,说明你已经具备一点p2p通信知识,以及STUN/TURN相关协议,以及查看config文件,知晓coturn的具体功能。
本人还在coturn上增添一些打印STUN协议解析的日志代码,以便清晰明了的直到客户端接收和发送了什么数据。

概述

coturn 通过libevent/bufferevent进行高并发处理。turnserver是其主程序,它 的main函数入口在src/apps/relay/mainrelay.c中。除此之外,还同时编译出多个工具程序。

bin/turnadmin               bin/turnutils_peer
bin/turnserver              bin/turnutils_rfc5769check
bin/turnutils_natdiscovery  bin/turnutils_stunclient
bin/turnutils_oauth         bin/turnutils_uclient

而且在src/apps/relay/dbdrivers/路径下,支持多个数据库的接口:mongodb/mysql/pgsql/redis/sqlite,里面有个文件dbdriver.c下自动检测系统中有哪个数据库。在turndb/文件夹下,还有对应数据库的SQL初始化命令。

—总之,服务周全,良心产品。

一. turnserver运行过程简介

程序初始运行流程如下。
函数main运行setup_server默认根据4核CPU建立9个服务,见setup_server 函数。

	/* relay threads plus auth threads plus main listener thread */
	/* plus admin thread */
	/* udp address listener thread(s) will start later */
	4个relay(4个CPU) + 3 个auth + 1个main listener + 1 个admin thread

服务相关数据都由结构体turn_params 连接衍生下去,整个运行过程基本都是对turn_params的各个成员初始化,当然还有几个服务,及其对应的event_base。

static struct auth_server authserver[256];
static struct relay_server *general_relay_servers[1+((turnserver_id)-1)];
static struct relay_server *udp_relay_servers[1+((turnserver_id)-1)];
3个
bufferevent_pair_new
udp/dtls/aux
init
init
init
init
init
函数main
函数setup_server
函数setup_listener
函数setup_general_relay_servers --这个才是最重要的STUN协议SERVER
函数setup_socket_per_thread_udp_listener_servers
函数setup_auth_server
函数setup_admin_server
函数run_general_relay_thread
函数setup_relay_server
函数init_turn_server
函数create_dtls_listener_server
函数init_server
函数create_server_socket
函数run_auth_server_thread
结构体turn_params
结构体listener_server
结构体dtls_listener_relay_server_info
event_base
结构体turn_turnserver
函数run_auth_server_thread处理auth_server结构体
init
auth_server_event_base
结构体auth_server
函数run_auth_server_thread
服务 auth_server
函数setup_admin_thread处理admin_server结构体
init
服务 admin_server
结构体admin_server
admin_server_event_base
setup_admin_thread
服务setup_relay_server处理message_to_relay结构体,客户端访问的数据进行处理....
重要函数setup_relay_server
处理in_buf函数relay_receive_message
函数handle_relay_message
函数open_client_connection_session
函数client_input_handler
函数read_client_connection
结构体message_to_relay

read_client_connection才是我们最为关心的客户端访问入口函数!见 1.3 客户端访问入口函数

1.1 函数main

一般main函数步骤都是:初始化参数->载入配置文件->根据配置运行加载,coturn也不例外。src/apps/relay/mainrelay.c中定义的结构体turn_params是整个程序数据结构的根部,main函数主要对其进行初始化。

int main(int argc, char **argv){
	...参数初始化,加载配置..省略...
	setup_server(); //建立服务器监听主函数
	drop_privileges();
	run_listener_server(&(turn_params.listener));
	return 0;
}

1.2 函数void setup_server(void)

建立服务引擎主函数,在src/apps/relay/netengine.c文件中定义,建立的监听服务好多啊!而且运行过程都不一样。

void setup_server(void)
{
	evthread_use_pthreads();
	pthread_mutex_init(&mutex_bps, NULL);

	authserver_number = 1 + (authserver_id)(turn_params.cpus / 2);
	if(authserver_number < MIN_AUTHSERVER_NUMBER) authserver_number = MIN_AUTHSERVER_NUMBER;
#if !defined(TURN_NO_THREAD_BARRIERS)
	/* relay threads plus auth threads plus main listener thread */
	/* plus admin thread */
	/* udp address listener thread(s) will start later */
	barrier_count = turn_params.general_relay_servers_number+authserver_number+1+1;
	//默认4+3+1+1个服务,下面一个一个建立
#endif

	setup_listener();
	allocate_relay_addrs_ports();
	setup_barriers();
	setup_general_relay_servers();

	if(turn_params.net_engine_version == NEV_UDP_SOCKET_PER_THREAD)
		setup_socket_per_thread_udp_listener_servers(); //默认这个UDP监听
	else if(turn_params.net_engine_version == NEV_UDP_SOCKET_PER_ENDPOINT)
		setup_socket_per_endpoint_udp_listener_servers();
	else if(turn_params.net_engine_version == NEV_UDP_SOCKET_PER_SESSION)
		setup_socket_per_session_udp_listener_servers();

	if(turn_params.net_engine_version != NEV_UDP_SOCKET_PER_THREAD) {
		setup_tcp_listener_servers(turn_params.listener.ioa_eng, NULL);
	}

	{
		int tot = 0;
		if(udp_relay_servers[0]) {
			tot = get_real_udp_relay_servers_number();
		}
		if(tot) {
			TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"Total UDP servers: %d\n",(int)tot);
		}
	}

	{
		int tot = get_real_general_relay_servers_number(); //默认4个general_relay_servers
		if(tot) {
			TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"Total General servers: %d\n",(int)tot);
			int i;
			for(i = 0;i<tot;i++) {
				if(!(general_relay_servers[i])) {
					TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,"General server %d is not initialized !\n",(int)i);
				}
			}
		}
	}

	{
		authserver_id sn = 0;
		for(sn = 0; sn < authserver_number;++sn) {
			authserver[sn].id = sn;
			setup_auth_server(&(authserver[sn]));
		}
	}

	setup_admin_server();

	barrier_wait();
}	

1.3 函数read_client_connection

文件src/server/ns_turn_server.c函数read_client_connection,处理访问的数据并输出结果。
结构体ioa_net_data保存着访问者所有的数据。

typedef struct _ioa_net_data {
	ioa_addr			src_addr;
	ioa_network_buffer_handle	nbh;   //客户请求的数据
	int				recv_ttl;
	int				recv_tos;
} ioa_net_data;
static int read_client_connection(turn_turnserver *server,
				  	  	  	  	  ts_ur_super_session *ss, ioa_net_data *in_buffer,
				  	  	  	  	  int can_resume, int count_usage) {
....
int ret = (int)ioa_network_buffer_get_size(in_buffer->nbh); //获取请求数据的大小
....
unsigned char *test_dd = ioa_network_buffer_data(in_buffer->nbh); //请求数据转化为unsigned char *
//解析协议时,注意高低位nswap16/nswap32等等
...
handle_turn_command(server, ss, in_buffer, nbh, &resp_constructed, can_resume); //处理STUN协议主方法
...
int ret = write_client_connection(server, ss, nbh, TTL_IGNORE, TOS_IGNORE); //处理完后回复数据给客户端
....
//当然还支持 旧的STUN协议
handle_old_stun_command(server, ss, in_buffer, nbh, &resp_constructed, old_stun_cookie);
...
//对HTTP访问简单回复:
handle_http_echo(ss->client_socket);//见write_http_echo
}

二. 增加打印解析数据代码

为了清晰明了的知道接收和发送的UDP数据,都是什么内容,为此添加打印日志代码,更好的学习STUN/TURN协议

	stun_attr_ref sar = stun_attr_get_first_str(ioa_network_buffer_data(nbh), ioa_network_buffer_get_size(nbh));

	if (!sar)
	{
		printf("stun_attr_get_first_str: NULL\n");
		return 0;
	}

	while (sar)
	{
		int sarlen = stun_attr_get_len(sar);

		u16bits stun_attr_type = stun_attr_get_type(sar);
		printf("ATTR[0x%04X][%s] LENGTH[%d] ", stun_attr_type, get_attr_type_string(stun_attr_type), sarlen);
		if (stun_attr_type == 0)
			break;
		if(sarlen >4096) break;

		if (sarlen > 0)
		{
			u08bits *o = (u08bits *)turn_malloc(sarlen + 1);
			ns_bcopy(stun_attr_get_value(sar), o, sarlen);
			o[sarlen] = 0;

			switch (stun_attr_type)
			{
			case STUN_ATTRIBUTE_XOR_MAPPED_ADDRESS:	
			case STUN_ATTRIBUTE_XOR_PEER_ADDRESS:
			case STUN_ATTRIBUTE_XOR_RELAYED_ADDRESS:
			case STUN_ATTRIBUTE_MAPPED_ADDRESS:
			case STUN_ATTRIBUTE_ALTERNATE_SERVER:
			case OLD_STUN_ATTRIBUTE_RESPONSE_ADDRESS:
			case OLD_STUN_ATTRIBUTE_SOURCE_ADDRESS:
			case OLD_STUN_ATTRIBUTE_CHANGED_ADDRESS:
			case OLD_STUN_ATTRIBUTE_REFLECTED_FROM:
			case STUN_ATTRIBUTE_RESPONSE_ORIGIN:
			case STUN_ATTRIBUTE_OTHER_ADDRESS:
			case STUN_ATTRIBUTE_USER_INFO_MY_REALY_ADDR:
			case STUN_ATTRIBUTE_RES_USER_INFO_REAL_ADDR:
			case STUN_ATTRIBUTE_RES_USER_INFO_MAPPED_ADDR:
			case STUN_ATTRIBUTE_RES_USER_INFO_RELAYED_ADDR:

				addr_set_any(peer_addr);
				stun_attr_get_addr_str(ioa_network_buffer_data(nbh),
									   ioa_network_buffer_get_size(nbh),
									   sar, peer_addr,
									   NULL);

				if(ss && stun_attr_type == STUN_ATTRIBUTE_USER_INFO_MY_REALY_ADDR && recv0_send1 == 0 && user) {
					if(user->real_addr->addr == NULL){
						user->real_addr->addr = turn_malloc(sizeof(ioa_addr));
					}
					user->real_addr->addr->s4.sin_port = ((const u16bits*)stun_attr_get_value(sar))[1];
					user->real_addr->addr->s4.sin_addr.s_addr=((const u32bits*)stun_attr_get_value(sar))[1];
					user->real_addr->addr->ss.sa_family = AF_INET;
					addr_cpy(peer_addr,user->real_addr->addr);
				}

				addr_debug_print(1, peer_addr, "");
			
				break;
			case STUN_ATTRIBUTE_CHANNEL_NUMBER:
				chn = stun_attr_get_channel_number(sar);
				printf("channel  = [%d] ", chn);
				break;

			case STUN_ATTRIBUTE_LIFETIME:
				printf("lifetime [%d]", nswap32(*((const u32bits*)o)));
				break;
			case STUN_ATTRIBUTE_EVEN_PORT:
				printf("port [%d]", stun_attr_get_even_port(sar));
				break;
			case STUN_ATTRIBUTE_RESERVATION_TOKEN:
				printf("token [%ld]", stun_attr_get_reservation_token_value(sar));
				break;			
			
			case STUN_ATTRIBUTE_FINGERPRINT:
			case STUN_ATTRIBUTE_SOFTWARE:
			case STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY:

				
				printf("Hex:[0x");
				for(ti=0;ti<sarlen;ti++){
					printf("%02X",(u08bits )o[ti]);
				}
				printf("]");
				break;

			case STUN_ATTRIBUTE_REQUESTED_TRANSPORT:
				printf("TRANSPORT [%d]",get_transport_value((const u08bits *) o));
				break;
			case STUN_ATTRIBUTE_RES_USERID_INFO:
				parse_res_user_info( o, sarlen);
				break;
			case STUN_ATTRIBUTE_ASK_USERID_INFO:
				parse_res_user_info( o, sarlen);
				break;		
			
			case STUN_ATTRIBUTE_ERROR_CODE:
				err_code = (int)(o[2] * 100 + o[3]);
				printf("error code [%d]  ", err_code);
				printf("string [%s]",o+4);
				break;
			case STUN_ATTRIBUTE_DATA:
			default:
				printf("Hex:[0x");
				for(ti=0;ti<sarlen;ti++){
					printf("%02X",(u08bits )o[ti]);
				}
				//printf("]\nstring:	[%s]",o);
			}
			turn_free(o,sarlen+1);
		}
		printf("\n");
		sar = stun_attr_get_next_str(ioa_network_buffer_data(nbh),
									 ioa_network_buffer_get_size(nbh), sar);
		}
	}

增加后,打印代码如下:

======================================================UDP SEND >>>>>>>>>>>>>>>
len[68] 010300302112a442ce6672a1043f958f3c97b929001600080001c22871c3cfd80020000800019904c0a801c8000d000400000e10802200044e6f6e6580280004da135792


SOCKET TYPE :
MESSAGE INFO :
RESPONSE OK [0x03][STUN_METHOD_ALLOCATE]
ATTR[0x0016][STUN_ATTRIBUTE_XOR_RELAYED_ADDRESS] LENGTH[8] IPv4. addr is : 113.195.207.216:49704
ATTR[0x0020][STUN_ATTRIBUTE_XOR_MAPPED_ADDRESS] LENGTH[8] IPv4. addr is : 192.168.1.200:39172
ATTR[0x000D][STUN_ATTRIBUTE_LIFETIME] LENGTH[4] lifetime [3600]
ATTR[0x8022][STUN_ATTRIBUTE_SOFTWARE] LENGTH[4] Hex:[0x4E6F6E65]
ATTR[0x8028][STUN_ATTRIBUTE_FINGERPRINT] LENGTH[4] Hex:[0xDA135792]

IPv4. =====udp_send===: 192.168.1.200:39172

欢迎留言共同讨论:)

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 11
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值