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

来源:银弹实验室 2021-06-24 16:12:02

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出来的数据。

0x05 client_ustream_read_cb函数

位置:listener_cb–>uh_accept_client–>client_ustream_read_cb


未完待续…