物联网设备常见的web服务器——uhttpd源码分析(二)

2021-07-28 10,100

0x00 前言

uHTTPd 是一个 OpenWrt/LUCI 开发者从头编写的 Web 服务器。 它着力于实现一个稳定高效的服务器,能够满足嵌入式设备的轻量级任务需求,且能够与 OpenWrt 的配置框架 (UCI) 整合。默认情况下它被用于 OpenWrt 的 Web 管理接口 LuCI。当然,uHTTPd 也能提供一个常规 Web 服务器所需要的所有功能。


0x01 简介

物联网设备常见的web服务器——uhttpd源码分析(一):https://www.freebuf.com/articles/network/269696.html

0x02 run_server函数

位置:main–>run_server

static int run_server(void) 
{ 
        uloop_init();//epoll等函数的初始化 
        uh_setup_listeners();//设置监听回调函数 
        uh_plugin_post_init();//插件初始化 
        uloop_run(); //uloop_run轮询处理定时器、进程、描述符事件等。 
        return 0; 
}

0x03 uloop_init函数

主要位置:main–>run_server–>uloop_init–>uloop_init_pollfd

static int uloop_init_pollfd(void) 
{ 
        if (poll_fd >= 0) 
                return 0; 
 
        poll_fd = epoll_create(32); 
        if (poll_fd < 0) 
                return -1; 
 
        fcntl(poll_fd, F_SETFD, fcntl(poll_fd, F_GETFD) | FD_CLOEXEC); 
        return 0; 
}

0x00 epoll_create函数

位置:main–>run_server–>uloop_init–>uloop_init_pollfd–>epoll_create

函数生成一个epoll专用的文件描述符。它其实是在内核申请一空间,用来存放你想关注的socket fd上是否发生以及发生了什么事件。size就是你在这个epoll fd上能关注的最大socket fd数。这里最大数量为32。

参考:

https://blog.csdn.net/yusiguyuan/article/details/15027821

0x01 fcntl函数

位置:main–>run_server–>uloop_init–>uloop_init_pollfd–>fcntl

通过fcntl可以改变已打开的文件性质。fcntl针对描述符提供控制。参数fd是被参数cmd操作的描述符。

参考:

https://baike.baidu.com/item/fcntl/6860021?fr=aladdin

https://blog.csdn.net/qq_37414405/article/details/83690447

0x04 uh_setup_listeners函数

位置:main–>run_server–>uh_setup_listeners

void uh_setup_listeners(void) 
{ 
        struct listener *l; 
        int yes = 1; 
 
        list_for_each_entry(l, &listeners, list) { 
                int sock = l->fd.fd; 
 
                /* TCP keep-alive */ 
                if (conf.tcp_keepalive > 0) { 
#ifdef linux 
                        int tcp_ka_idl, tcp_ka_int, tcp_ka_cnt, tcp_fstopn; 
 
                        tcp_ka_idl = 1; 
                        tcp_ka_cnt = 3; 
                        tcp_ka_int = conf.tcp_keepalive; 
                        tcp_fstopn = 5; 
                        /*检测网线非法断开*/ 
                        setsockopt(sock, SOL_TCP, TCP_KEEPIDLE,  &tcp_ka_idl, sizeof(tcp_ka_idl)); 
                        setsockopt(sock, SOL_TCP, TCP_KEEPINTVL, &tcp_ka_int, sizeof(tcp_ka_int)); 
                        setsockopt(sock, SOL_TCP, TCP_KEEPCNT,   &tcp_ka_cnt, sizeof(tcp_ka_cnt)); 
                        setsockopt(sock, SOL_TCP, TCP_FASTOPEN,  &tcp_fstopn, sizeof(tcp_fstopn)); 
#endif 
 
                        setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes)); 
                } 
 
                l->fd.cb = listener_cb;//accept接受数据函数就在listener_cb中 
                uloop_fd_add(&l->fd, ULOOP_READ); 
        } 
}

最主要关注 l->fd.cb = listener_cb


0x00 setsockopt函数

位置:main–>run_server–>uloop_init–>uloop_init_pollfd–>epoll_create

int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);

sockfd:标识一个套接口的描述字。

level:选项定义的层次;支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6。

optname:需设置的选项。

optval:指针,指向存放选项待设置的新值的缓冲区。

optlen:optval缓冲区长度。

主要是用来检查网络异常后的操作。

参考:

https://www.oschina.net/question/128542_2149349

https://baike.baidu.com/item/setsockopt/10069288?fr=aladdin


0x05 listener_cb函数

位置:main–>run_server–>uh_setup_listeners

当通过epoll有数据过来后,会去调用listener_cb函数,并执行accept函数去对应的数据。

static void listener_cb(struct uloop_fd *fd, unsigned int events) 
{ 
        struct listener *l = container_of(fd, struct listener, fd);//获取struct listener结构体的起始地址 
 
        while (1) { 
                if (!uh_accept_client(fd->fd, l->tls))//accept接受数据,并设置对应的回调函数 
                        break; 
        } 
        /*如果连接数大于或等于最大连接数则执行删除对应的fd操作,最大连接数默认为100*/ 
        if (conf.max_connections && n_clients >= conf.max_connections) 
                uh_block_listener(l);//删除全局变量对应的fd,主要操作就是将对应的全局变量的fd设置为NULL,和设置事件的值 
}

0x00 uh_accept_client函数

位置:listener_cb–>uh_accept_client

/*accept接受数据,并设置对应的回调函数*/ 
bool uh_accept_client(int fd, bool tls) 
{ 
        static struct client *next_client; 
        struct client *cl; 
        unsigned int sl; 
        int sfd; 
        static int client_id = 0; 
        struct sockaddr_in6 addr; 
 
        if (!next_client) 
                next_client = calloc(1, sizeof(*next_client));//申请内存 
 
        cl = next_client; 
 
        sl = sizeof(addr); 
        sfd = accept(fd, (struct sockaddr *) &addr, &sl); 
        if (sfd < 0) 
                return false; 
 
        set_addr(&cl->peer_addr, &addr);//addr拷贝到cl->peer_addr中 
        sl = sizeof(addr); 
        getsockname(sfd, (struct sockaddr *) &addr, &sl);//服务器端可以通过它得到相关客户端地址 
        set_addr(&cl->srv_addr, &addr); 
 
        cl->us = &cl->sfd.stream; 
        if (tls) { 
                uh_tls_client_attach(cl); 
        } else { 
                cl->us->notify_read = client_ustream_read_cb;//读操作 
                cl->us->notify_write = client_ustream_write_cb;//写操作 
                cl->us->notify_state = client_notify_state;//通知状态 
        } 
 
        cl->us->string_data = true; 
        ustream_fd_init(&cl->sfd, sfd);//将sfd赋值给cl中的fd,以便后续使用 
 
        uh_poll_connection(cl);//主要用来设置超时定时器 
        list_add_tail(&cl->list, &clients); 
 
        next_client = NULL; 
        n_clients++;//作用可以看listener_cb函数,主要作用是总共连接数 
        cl->id = client_id++; 
        cl->tls = tls; 
 
        return true; 
}


0x01 calloc函数

位置:listener_cb–>uh_accept_client–>calloc

void *calloc(size_t nitems, size_t size)

分配所需的内存空间,并返回一个指向它的指针。malloc 和 calloc 之间的不同点是,malloc 不会设置内存为零,而 calloc 会设置分配的内存为零。

参考:

https://www.runoob.com/cprogramming/c-function-calloc.html


0x02 accept函数

位置:listener_cb–>uh_accept_client–>accept

int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);

如果函数执行正确的话,将会返回新的套接字描述符,用于指向与当前通信的客户端发送或接收数据。

参考:

http://blog.chinaunix.net/uid-28595538-id-4919587.html


0x03 getsockname函数

位置:listener_cb–>uh_accept_client–>getsockname

int getsockname(int s, struct sockaddr *name, socklen_t *namelen);

服务器端可以通过它得到相关客户端地址。

参考:

https://blog.csdn.net/qinrenzhi/article/details/94043263

https://www.yiibai.com/unix_system_calls/getsockname.html


0x04 ustream_fd_init函数

位置:listener_cb–>uh_accept_client–>ustream_fd_init

void ustream_fd_init(struct ustream_fd *sf, int fd) 
{ 
        struct ustream *s = &sf->stream; 
 
        ustream_init_defaults(s); 
 
        sf->fd.fd = fd; 
        sf->fd.cb = ustream_uloop_cb; 
        s->set_read_blocked = ustream_fd_set_read_blocked; 
        s->write = ustream_fd_write; 
        s->free = ustream_fd_free; 
        s->poll = ustream_fd_poll; //读写操作 
        ustream_fd_set_uloop(s, false); 
}

通过ustream_fd_poll函数会调用uh_accept_client中的回调函数client_ustream_read_cb,真正读取客户端数据的是ustream_fd_poll函数,该函数使用read读取,而client_ustream_read_cb仅仅是操作read出来的数据。


0x06 client_ustream_read_cb函数

位置:listener_cb–>uh_accept_client–>client_ustream_read_cb


未完待续…


本文作者:知微攻防实验室

本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/163391.html

Tags:
评论  (0)
快来写下你的想法吧!

知微攻防实验室

文章数:22 积分: 70

专注物联网安全研究,于细微处下功夫,构筑物联网安全防御体系

安全问答社区

安全问答社区

脉搏官方公众号

脉搏公众号