httpd_sess.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. /*
  2. * SPDX-FileCopyrightText: 2018-2021 Espressif Systems (Shanghai) CO LTD
  3. *
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. #include <stdlib.h>
  7. #include <esp_log.h>
  8. #include <esp_err.h>
  9. #include <fcntl.h>
  10. #include <errno.h>
  11. #include <unistd.h>
  12. #include <esp_http_server.h>
  13. #include "esp_httpd_priv.h"
  14. static const char *TAG = "httpd_sess";
  15. typedef enum {
  16. HTTPD_TASK_NONE = 0,
  17. HTTPD_TASK_INIT, // Init session
  18. HTTPD_TASK_GET_ACTIVE, // Get active session (fd!=-1)
  19. HTTPD_TASK_GET_FREE, // Get free session slot (fd<0)
  20. HTTPD_TASK_FIND_FD, // Find session with specific fd
  21. HTTPD_TASK_SET_DESCRIPTOR, // Set descriptor
  22. HTTPD_TASK_DELETE_INVALID, // Delete invalid session
  23. HTTPD_TASK_FIND_LOWEST_LRU, // Find session with lowest lru
  24. HTTPD_TASK_CLOSE // Close session
  25. } task_t;
  26. typedef struct {
  27. task_t task;
  28. int fd;
  29. fd_set *fdset;
  30. int max_fd;
  31. struct httpd_data *hd;
  32. uint64_t lru_counter;
  33. struct sock_db *session;
  34. } enum_context_t;
  35. void httpd_sess_enum(struct httpd_data *hd, httpd_session_enum_function enum_function, void *context)
  36. {
  37. if ((!hd) || (!hd->hd_sd) || (!hd->config.max_open_sockets)) {
  38. return;
  39. }
  40. struct sock_db *current = hd->hd_sd;
  41. struct sock_db *end = hd->hd_sd + hd->config.max_open_sockets - 1;
  42. while (current <= end) {
  43. if (enum_function && (!enum_function(current, context))) {
  44. break;
  45. }
  46. current++;
  47. }
  48. }
  49. // Check if a FD is valid
  50. static int fd_is_valid(int fd)
  51. {
  52. return fcntl(fd, F_GETFD) != -1 || errno != EBADF;
  53. }
  54. static int enum_function(struct sock_db *session, void *context)
  55. {
  56. if ((!session) || (!context)) {
  57. return 0;
  58. }
  59. enum_context_t *ctx = (enum_context_t *) context;
  60. int found = 0;
  61. switch (ctx->task) {
  62. // Initialize session
  63. case HTTPD_TASK_INIT:
  64. session->fd = -1;
  65. session->ctx = NULL;
  66. break;
  67. // Get active session
  68. case HTTPD_TASK_GET_ACTIVE:
  69. found = (session->fd != -1);
  70. break;
  71. // Get free slot
  72. case HTTPD_TASK_GET_FREE:
  73. found = (session->fd < 0);
  74. break;
  75. // Find fd
  76. case HTTPD_TASK_FIND_FD:
  77. found = (session->fd == ctx->fd);
  78. break;
  79. // Set descriptor
  80. case HTTPD_TASK_SET_DESCRIPTOR:
  81. if (session->fd != -1) {
  82. FD_SET(session->fd, ctx->fdset);
  83. if (session->fd > ctx->max_fd) {
  84. ctx->max_fd = session->fd;
  85. }
  86. }
  87. break;
  88. // Delete invalid session
  89. case HTTPD_TASK_DELETE_INVALID:
  90. if (!fd_is_valid(session->fd)) {
  91. ESP_LOGW(TAG, LOG_FMT("Closing invalid socket %d"), session->fd);
  92. httpd_sess_delete(ctx->hd, session);
  93. }
  94. break;
  95. // Find lowest lru
  96. case HTTPD_TASK_FIND_LOWEST_LRU:
  97. // Found free slot - no need to check other sessions
  98. if (session->fd == -1) {
  99. return 0;
  100. }
  101. // Check/update lowest lru
  102. if (session->lru_counter < ctx->lru_counter) {
  103. ctx->lru_counter = session->lru_counter;
  104. ctx->session = session;
  105. }
  106. break;
  107. case HTTPD_TASK_CLOSE:
  108. if (session->fd != -1) {
  109. ESP_LOGD(TAG, LOG_FMT("cleaning up socket %d"), session->fd);
  110. httpd_sess_delete(ctx->hd, session);
  111. }
  112. break;
  113. default:
  114. return 0;
  115. }
  116. if (found) {
  117. ctx->session = session;
  118. return 0;
  119. }
  120. return 1;
  121. }
  122. static void httpd_sess_close(void *arg)
  123. {
  124. struct sock_db *sock_db = (struct sock_db *) arg;
  125. if (!sock_db) {
  126. return;
  127. }
  128. if (!sock_db->lru_counter && !sock_db->lru_socket) {
  129. ESP_LOGD(TAG, "Skipping session close for %d as it seems to be a race condition", sock_db->fd);
  130. return;
  131. }
  132. sock_db->lru_socket = false;
  133. struct httpd_data *hd = (struct httpd_data *) sock_db->handle;
  134. httpd_sess_delete(hd, sock_db);
  135. }
  136. struct sock_db *httpd_sess_get_free(struct httpd_data *hd)
  137. {
  138. if ((!hd) || (hd->hd_sd_active_count == hd->config.max_open_sockets)) {
  139. return NULL;
  140. }
  141. enum_context_t context = {
  142. .task = HTTPD_TASK_GET_FREE
  143. };
  144. httpd_sess_enum(hd, enum_function, &context);
  145. return context.session;
  146. }
  147. bool httpd_is_sess_available(struct httpd_data *hd)
  148. {
  149. return httpd_sess_get_free(hd) ? true : false;
  150. }
  151. struct sock_db *httpd_sess_get(struct httpd_data *hd, int sockfd)
  152. {
  153. if ((!hd) || (!hd->hd_sd) || (!hd->config.max_open_sockets)) {
  154. return NULL;
  155. }
  156. // Check if called inside a request handler, and the session sockfd in use is same as the parameter
  157. // => Just return the pointer to the sock_db corresponding to the request
  158. if ((hd->hd_req_aux.sd) && (hd->hd_req_aux.sd->fd == sockfd)) {
  159. return hd->hd_req_aux.sd;
  160. }
  161. enum_context_t context = {
  162. .task = HTTPD_TASK_FIND_FD,
  163. .fd = sockfd
  164. };
  165. httpd_sess_enum(hd, enum_function, &context);
  166. return context.session;
  167. }
  168. esp_err_t httpd_sess_new(struct httpd_data *hd, int newfd)
  169. {
  170. ESP_LOGD(TAG, LOG_FMT("fd = %d"), newfd);
  171. if (httpd_sess_get(hd, newfd)) {
  172. ESP_LOGE(TAG, LOG_FMT("session already exists with fd = %d"), newfd);
  173. return ESP_FAIL;
  174. }
  175. struct sock_db *session = httpd_sess_get_free(hd);
  176. if (!session) {
  177. ESP_LOGD(TAG, LOG_FMT("unable to launch session for fd = %d"), newfd);
  178. return ESP_FAIL;
  179. }
  180. // Clear session data
  181. memset(session, 0, sizeof (struct sock_db));
  182. session->fd = newfd;
  183. session->handle = (httpd_handle_t) hd;
  184. session->send_fn = httpd_default_send;
  185. session->recv_fn = httpd_default_recv;
  186. // Call user-defined session opening function
  187. if (hd->config.open_fn) {
  188. esp_err_t ret = hd->config.open_fn(hd, session->fd);
  189. if (ret != ESP_OK) {
  190. httpd_sess_delete(hd, session);
  191. ESP_LOGD(TAG, LOG_FMT("open_fn failed for fd = %d"), newfd);
  192. return ret;
  193. }
  194. }
  195. // increment number of sessions
  196. hd->hd_sd_active_count++;
  197. ESP_LOGD(TAG, LOG_FMT("active sockets: %d"), hd->hd_sd_active_count);
  198. return ESP_OK;
  199. }
  200. void httpd_sess_free_ctx(void **ctx, httpd_free_ctx_fn_t free_fn)
  201. {
  202. if ((!ctx) || (!*ctx)) {
  203. return;
  204. }
  205. if (free_fn) {
  206. free_fn(*ctx);
  207. } else {
  208. free(*ctx);
  209. }
  210. *ctx = NULL;
  211. }
  212. void httpd_sess_clear_ctx(struct sock_db *session)
  213. {
  214. if ((!session) || ((!session->ctx) && (!session->transport_ctx))) {
  215. return;
  216. }
  217. // free user ctx
  218. if (session->ctx) {
  219. httpd_sess_free_ctx(&session->ctx, session->free_ctx);
  220. session->free_ctx = NULL;
  221. }
  222. // Free 'transport' context
  223. if (session->transport_ctx) {
  224. httpd_sess_free_ctx(&session->transport_ctx, session->free_transport_ctx);
  225. session->free_transport_ctx = NULL;
  226. }
  227. }
  228. void *httpd_sess_get_ctx(httpd_handle_t handle, int sockfd)
  229. {
  230. struct sock_db *session = httpd_sess_get(handle, sockfd);
  231. if (!session) {
  232. return NULL;
  233. }
  234. // Check if the function has been called from inside a
  235. // request handler, in which case fetch the context from
  236. // the httpd_req_t structure
  237. struct httpd_data *hd = (struct httpd_data *) handle;
  238. if (hd->hd_req_aux.sd == session) {
  239. return hd->hd_req.sess_ctx;
  240. }
  241. return session->ctx;
  242. }
  243. void httpd_sess_set_ctx(httpd_handle_t handle, int sockfd, void *ctx, httpd_free_ctx_fn_t free_fn)
  244. {
  245. struct sock_db *session = httpd_sess_get(handle, sockfd);
  246. if (!session) {
  247. return;
  248. }
  249. // Check if the function has been called from inside a
  250. // request handler, in which case set the context inside
  251. // the httpd_req_t structure
  252. struct httpd_data *hd = (struct httpd_data *) handle;
  253. if (hd->hd_req_aux.sd == session) {
  254. if (hd->hd_req.sess_ctx != ctx) {
  255. // Don't free previous context if it is in sockdb
  256. // as it will be freed inside httpd_req_cleanup()
  257. if (session->ctx != hd->hd_req.sess_ctx) {
  258. httpd_sess_free_ctx(&hd->hd_req.sess_ctx, hd->hd_req.free_ctx); // Free previous context
  259. }
  260. hd->hd_req.sess_ctx = ctx;
  261. }
  262. hd->hd_req.free_ctx = free_fn;
  263. return;
  264. }
  265. // Else set the context inside the sock_db structure
  266. if (session->ctx != ctx) {
  267. // Free previous context
  268. httpd_sess_free_ctx(&session->ctx, session->free_ctx);
  269. session->ctx = ctx;
  270. }
  271. session->free_ctx = free_fn;
  272. }
  273. void *httpd_sess_get_transport_ctx(httpd_handle_t handle, int sockfd)
  274. {
  275. struct sock_db *session = httpd_sess_get(handle, sockfd);
  276. return session ? session->transport_ctx : NULL;
  277. }
  278. void httpd_sess_set_transport_ctx(httpd_handle_t handle, int sockfd, void *ctx, httpd_free_ctx_fn_t free_fn)
  279. {
  280. struct sock_db *session = httpd_sess_get(handle, sockfd);
  281. if (!session) {
  282. return;
  283. }
  284. if (session->transport_ctx != ctx) {
  285. // Free previous transport context
  286. httpd_sess_free_ctx(&session->transport_ctx, session->free_transport_ctx);
  287. session->transport_ctx = ctx;
  288. }
  289. session->free_transport_ctx = free_fn;
  290. }
  291. void httpd_sess_set_descriptors(struct httpd_data *hd, fd_set *fdset, int *maxfd)
  292. {
  293. enum_context_t context = {
  294. .task = HTTPD_TASK_SET_DESCRIPTOR,
  295. .max_fd = -1,
  296. .fdset = fdset
  297. };
  298. httpd_sess_enum(hd, enum_function, &context);
  299. if (maxfd) {
  300. *maxfd = context.max_fd;
  301. }
  302. }
  303. void httpd_sess_delete_invalid(struct httpd_data *hd)
  304. {
  305. enum_context_t context = {
  306. .task = HTTPD_TASK_DELETE_INVALID,
  307. .hd = hd
  308. };
  309. httpd_sess_enum(hd, enum_function, &context);
  310. }
  311. void httpd_sess_delete(struct httpd_data *hd, struct sock_db *session)
  312. {
  313. if ((!hd) || (!session) || (session->fd < 0)) {
  314. return;
  315. }
  316. ESP_LOGD(TAG, LOG_FMT("fd = %d"), session->fd);
  317. // Call close function if defined
  318. if (hd->config.close_fn) {
  319. hd->config.close_fn(hd, session->fd);
  320. } else {
  321. close(session->fd);
  322. }
  323. // clear all contexts
  324. httpd_sess_clear_ctx(session);
  325. // mark session slot as available
  326. session->fd = -1;
  327. // decrement number of sessions
  328. hd->hd_sd_active_count--;
  329. ESP_LOGD(TAG, LOG_FMT("active sockets: %d"), hd->hd_sd_active_count);
  330. if (!hd->hd_sd_active_count) {
  331. hd->lru_counter = 0;
  332. }
  333. }
  334. void httpd_sess_init(struct httpd_data *hd)
  335. {
  336. enum_context_t context = {
  337. .task = HTTPD_TASK_INIT
  338. };
  339. httpd_sess_enum(hd, enum_function, &context);
  340. }
  341. bool httpd_sess_pending(struct httpd_data *hd, struct sock_db *session)
  342. {
  343. if (!session) {
  344. return false;
  345. }
  346. if (session->pending_fn) {
  347. // test if there's any data to be read (besides read() function, which is handled by select() in the main httpd loop)
  348. // this should check e.g. for the SSL data buffer
  349. if (session->pending_fn(hd, session->fd) > 0) {
  350. return true;
  351. }
  352. }
  353. return (session->pending_len != 0);
  354. }
  355. /* This MUST return ESP_OK on successful execution. If any other
  356. * value is returned, everything related to this socket will be
  357. * cleaned up and the socket will be closed.
  358. */
  359. esp_err_t httpd_sess_process(struct httpd_data *hd, struct sock_db *session)
  360. {
  361. if ((!hd) || (!session)) {
  362. return ESP_FAIL;
  363. }
  364. ESP_LOGD(TAG, LOG_FMT("httpd_req_new"));
  365. if (httpd_req_new(hd, session) != ESP_OK) {
  366. return ESP_FAIL;
  367. }
  368. ESP_LOGD(TAG, LOG_FMT("httpd_req_delete"));
  369. if (httpd_req_delete(hd) != ESP_OK) {
  370. return ESP_FAIL;
  371. }
  372. ESP_LOGD(TAG, LOG_FMT("success"));
  373. session->lru_counter = ++hd->lru_counter;
  374. return ESP_OK;
  375. }
  376. esp_err_t httpd_sess_update_lru_counter(httpd_handle_t handle, int sockfd)
  377. {
  378. if (handle == NULL) {
  379. return ESP_ERR_INVALID_ARG;
  380. }
  381. struct httpd_data *hd = (struct httpd_data *) handle;
  382. enum_context_t context = {
  383. .task = HTTPD_TASK_FIND_FD,
  384. .fd = sockfd
  385. };
  386. httpd_sess_enum(hd, enum_function, &context);
  387. if (context.session) {
  388. context.session->lru_counter = ++hd->lru_counter;
  389. return ESP_OK;
  390. }
  391. return ESP_ERR_NOT_FOUND;
  392. }
  393. esp_err_t httpd_sess_close_lru(struct httpd_data *hd)
  394. {
  395. enum_context_t context = {
  396. .task = HTTPD_TASK_FIND_LOWEST_LRU,
  397. .lru_counter = UINT64_MAX,
  398. .fd = -1
  399. };
  400. httpd_sess_enum(hd, enum_function, &context);
  401. if (!context.session) {
  402. return ESP_OK;
  403. }
  404. ESP_LOGD(TAG, LOG_FMT("Closing session with fd %d"), context.session->fd);
  405. context.session->lru_socket = true;
  406. return httpd_sess_trigger_close_(hd, context.session);
  407. }
  408. esp_err_t httpd_sess_trigger_close_(httpd_handle_t handle, struct sock_db *session)
  409. {
  410. if (!session) {
  411. return ESP_ERR_NOT_FOUND;
  412. }
  413. return httpd_queue_work(handle, httpd_sess_close, session);
  414. }
  415. esp_err_t httpd_sess_trigger_close(httpd_handle_t handle, int sockfd)
  416. {
  417. struct sock_db *session = httpd_sess_get(handle, sockfd);
  418. if (!session) {
  419. return ESP_ERR_NOT_FOUND;
  420. }
  421. return httpd_sess_trigger_close_(handle, session);
  422. }
  423. void httpd_sess_close_all(struct httpd_data *hd)
  424. {
  425. enum_context_t context = {
  426. .task = HTTPD_TASK_CLOSE,
  427. .hd = hd
  428. };
  429. httpd_sess_enum(hd, enum_function, &context);
  430. }