C语言典型代码---SSDP设备发现

为何接触到SSDP

起初是为了学习如何扫描局域网设备信息,nmap,arp-scan,netbios,snmp,最近又发现了UPnP,倒霉的名字,还有个小写字母。为何是小写n?

通用即插即用(英语:Universal Plug and Play,简称UPnP),这个n是and……所以是小写。
在这里插入图片描述
PUnP设备允许局域网的发现功能,即通过SSDP方式,通过组播的接口,向局域网呐喊:
在这里插入图片描述
然后局域网内的UPnP设备就会回复一些自己的信息。这样就达成简单的信息采集,随后可以读取和控制UPnP设备。

SSDP介绍

UPnP的发现过程,利用的就是SSDP协议。
SSDP:Simple Sever Discovery Protocol,简单服务发现协议,此协议为网络客户提供一种无需任何配置、管理和维护网络设备服务的机制。此协议采用基于通知和发现路由的多播发现方式实现。协议客户端在保留的多播地址:239.255.255.250:1900(IPV4)发现服务,(IPv6 是:FF0x::C)同时每个设备服务也在此地址上上监听服务发现请求。如果服务监听到的发现请求与此服务相匹配,此服务会使用单播方式响应。

典型报文

一个发现设备的报文如下格式

M-SEARCH * HTTP/1.1
HOST:239.255.255.250:1900
MAN:"ssdp:discover"
ST:ssdp:all\r\nMX:5

HOST:设置为协议保留多播地址和端口,必须是:239.255.255.250:1900(IPv4)或FF0x::C(IPv6)
MAN:设置协议查询的类型,必须是:ssdp:discover
MX:设置设备响应最长等待时间,设备响应在0和这个值之间随机选择响应延迟的值。这样可以为控制点响应平衡网络负载。
ST:设置服务查询的目标,它必须是下面的类型:

名字含义
ssdp:all搜索所有设备和服务
upnp:rootdevice仅搜索网络中的根设备
uuid:device-UUID查询UUID标识的设备
urn:schemas-upnp-org:device:device-Type:version查询device-Type字段指定的设备类型,设备类型和版本由UPNP组织定义。
urn:schemas-upnp-org:service:service-Type:version查询service-Type字段指定的服务类型,服务类型和版本由UPNP组织定义。

响应报文

HTTP/1.1 200 OK
LOCATION: http://192.168.1.1:3725/d40ea1d6-3b90-cadb-b076-fd24c58c3d2c/upnpdev.xml
SERVER: Linux UPnP/1.0 Huawei-ATP-IGD
HILINK_EXT: 0
CACHE-CONTROL: max-age=86500
EXT: 
ST: urn:schemas-upnp-org:device:LANDevice:1
USN: uuid:02bb1755-28e5-8ac9-58be-b48742c75dc7::urn:schemas-upnp-org:device:LANDevice:1
DATE: Wed, 15 Sep 2021 06:24:13 GMT
名字含义
CACHE-CONTROLmax-age指定通知消息存活时间,如果超过此时间间隔,控制点可以认为设备不存在
DATE指定响应生成的时间
EXT向控制点确认MAN头域已经被设备理解
LOCATION包含根设备描述得URL地址
SERVER包含操作系统名,版本,产品名和产品版本信息
ST内容和意义与查询请求的相应字段相同
USN表示不同服务的统一服务名,它提供了一种标识出相同类型服务的能力。

代码实现

发现分为指向发现和全部发现,就像老师喊话一样:

全部发现:

上课!起立!所有学生都站起来
这就相当于ST:ssdp:all,那么所有UPnP终端收到消息,都必须上报。

指向发现:

现在点名:王某文
王某文:到,我实在是冤枉啊,其实哪天晚上我去她房间啥也没干啊……
这就相当于ST:uuid:device-UUID 王某文,那么王某文终端收到消息,就可以说出自己的冤屈。

我利用的是SSDP发现扫描所有设备,所以用的是全部发现。

#define SSDP_MCAST_ADDR ("239.255.255.250")
#define SSDP_PORT (1900)
#define exit_seconds 5

#define MAXSIZE (1024)


//发送ssdp:discover消息
int send_discovery(int udp_sock,struct sockaddr_in client,socklen_t addrlen)
{
	char buf[512] = {0};

	snprintf(buf, sizeof(buf),
			"M-SEARCH * HTTP/1.1\r\n"
			"HOST:%s:%d\r\n"
			"MAN:\"ssdp:discover\"\r\n"
			"MX: 5\r\n"
			"ST:ssdp:all\r\n"
			"\r\n",
			SSDP_MCAST_ADDR, SSDP_PORT
	);
	
	sendto(udp_sock,buf,strlen(buf),0,(struct sockaddr*)&client,addrlen);
	return 0;
}
long GetCurrentSecond()
{
  struct timeval timeofday;

  gettimeofday(&timeofday , NULL);
  return timeofday.tv_sec;
} 


//client发现主线程
void *client_discovery(void *threadid)
{  
	struct sockaddr_in myaddr;  
	struct sockaddr_in serveraddr;  
	struct timeval timeout = {3,0};
	
	int ssdp_sock;  
	socklen_t addrlen;
	int ret;  
	fd_set r_fds;
	fd_set w_fds;
	char buf[MAXSIZE] = {0};
	int count = 0;
	int stop = 0;
	long time_exit = GetCurrentSecond()+exit_seconds;


	addrlen = sizeof(myaddr);
	bzero(&myaddr, sizeof(myaddr));  
	myaddr.sin_family = AF_INET;  
	myaddr.sin_addr.s_addr = inet_addr("0.0.0.0"); //htonl(INADDR_ANY)  
	myaddr.sin_port = htons(56889);  

	ssdp_sock=socket(AF_INET,SOCK_DGRAM,0);  
	if( ssdp_sock < 0) 
	{
		zlog_info(zlogc_alm,"创建socket失败");
		ssdp_sock = 0;
		goto ssdp_exit;
	}  

	bzero(&serveraddr, sizeof(serveraddr));  
	serveraddr.sin_family = AF_INET;  
	serveraddr.sin_addr.s_addr = inet_addr("239.255.255.250");  
	serveraddr.sin_port = htons(1900);  

	ret=bind(ssdp_sock, (struct sockaddr *)&myaddr, sizeof(myaddr));  
	if( ret < 0 )   
	{
		zlog_info(zlogc_alm,"bindsocket失败");
		goto ssdp_exit;
	}  

	while((time_exit>GetCurrentSecond())&&(!stop)) 
	{
		FD_ZERO(&r_fds);//读描述符集合
		FD_ZERO(&w_fds);//写描述符集合
		FD_SET(ssdp_sock,&r_fds);
		FD_SET(ssdp_sock,&w_fds);

		//轮询读写时间
		ret = select(ssdp_sock+1,&r_fds,&w_fds,NULL,&timeout);
		
		switch (ret) 
		{
			case -1: 
			{
				printf("Select Error\n");
				//abort();
				stop = 1;
				break;
			}
			case 0:
			{
				break;
			}
			default:
			{
				memset(buf,0,sizeof(buf));
				if(FD_ISSET(ssdp_sock,&r_fds) > 0) 
				{
					//printf("获取设备端IP和端口由于发送USERNAME\n");
					int num = recvfrom(ssdp_sock,buf,MAXSIZE,0,(struct sockaddr *)&myaddr,&addrlen);
					if(0 < num) 
					{
						buf[num] = '\0';
						if(NULL != strstr(buf,"HTTP/1.1 200 OK") && NULL != strstr(buf,"LOCATION: http://"))
						{
							printf("Get ssdp message frome[%s] %s\n",inet_ntoa(myaddr.sin_addr),buf); 

						} 
					} 
				} 
				else if(FD_ISSET(ssdp_sock,&w_fds) > 0) 
				{
					if(count == 0)
					{
						//printf("发送ssdp:discover消息\n");
						send_discovery(ssdp_sock,serveraddr,addrlen);
						count=1;
					}
					
				}
				break;
			}
		}

	}  
ssdp_exit:
	if(ssdp_sock)
	{
		close(ssdp_sock); 
	}
	pthread_exit(NULL);
}  

int ssdp_scan_process(void)
{
	pthread_t 	nlk_process;
	pthread_attr_t attr_nlk;
    
	pthread_attr_init(&attr_nlk);
	pthread_attr_setdetachstate(&attr_nlk, PTHREAD_CREATE_DETACHED);
	pthread_create(&nlk_process, &attr_nlk, client_discovery, NULL);
	return nbl_ok;

}

代码简单来说,就是为组播地址注册了一个udp的socket,并且绑定了两个描述符,然后利用select,轮训去检查两个描述符,通俗一点就是,像个对讲机一样,有人上报消息吗?没人,我就下发扫描所有的报文,然后再等待别人说话。
我们可以通过修改send_discovery函数,修改ST部分,就可以指定扫描某些UUID的设备。就像我们可以通过华为或者米家的APP自动发现局域网内的他们自己生产设备,这些基于类似的协议实现的,可能不是SSDP,而是其他的P。在这里插入图片描述
其实这段代码也是从CSDN的资料库中搜罗到的,其中就有一个坑
在这里插入图片描述
缺少一对双引号,最后也是通过wireshark抓包发现了这个问题。
花积分下代码还送个坑。
在这里插入图片描述
最后提前祝大家中秋节快乐。
在这里插入图片描述

  • 8
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 14
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

胖哥王老师

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

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

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

打赏作者

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

抵扣说明:

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

余额充值