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

阅读量144197

发布时间 : 2021-08-04 12:00:10

 

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.anquanke.com/post/id/248666

安全客 - 有思想的安全新媒体

分享到:微信
+10赞
收藏
银弹实验室
分享到:微信

发表评论

内容需知
  • 投稿须知
  • 转载须知
  • 官网QQ群8:819797106
  • 官网QQ群3:830462644(已满)
  • 官网QQ群2:814450983(已满)
  • 官网QQ群1:702511263(已满)
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 360网络攻防实验室 安全客 All Rights Reserved 京ICP备08010314号-66