From 76a09141eba69937bb940a7c45b9efd4cb68c62c Mon Sep 17 00:00:00 2001 From: CN_SZTL Date: Sun, 5 Jul 2020 20:42:06 +0800 Subject: [PATCH] luci-app-nginx-pingos: add package --- package/lienol/luci-app-nginx-pingos/Makefile | 98 + .../luasrc/controller/pingos.lua | 17 + .../luasrc/model/cbi/pingos.lua | 65 + .../luasrc/view/pingos/status.htm | 52 + .../modules/nginx-client-module/COPYRIGHT | 24 + .../modules/nginx-client-module/config | 19 + .../modules/nginx-client-module/ngx_client.c | 1387 +++++++++ .../modules/nginx-client-module/ngx_client.h | 165 + .../nginx-client-module/ngx_http_client.c | 1889 ++++++++++++ .../nginx-client-module/ngx_http_client.h | 323 ++ .../modules/nginx-client-module/t/config | 13 + .../modules/nginx-client-module/t/nginx.conf | 57 + .../t/ngx_client_stat_module.c | 148 + .../t/ngx_client_test_module.c | 188 ++ .../t/ngx_http_client_test_module.c | 203 ++ .../nginx-client-module/t/tcpserver.go | 41 + .../modules/nginx-multiport-module/COPYRIGHT | 24 + .../modules/nginx-multiport-module/README.md | 181 ++ .../modules/nginx-multiport-module/config | 31 + .../doc/ngx-http-broadcast-module.md | 77 + .../doc/ngx-stream-zone-module.md | 92 + .../ngx_event_multiport_module.c | 1018 +++++++ .../ngx_http_broadcast_module.c | 263 ++ .../ngx_http_inner_proxy_module.c | 254 ++ .../nginx-multiport-module/ngx_multiport.h | 53 + .../ngx_multiport_misc.c | 165 + .../ngx_process_slot_module.c | 167 + .../ngx_stream_zone_module.c | 551 ++++ .../ngx_stream_zone_module.h | 39 + .../modules/nginx-multiport-module/t/config | 12 + .../nginx-multiport-module/t/nginx.conf | 71 + .../t/ngx_multiport_test_module.c | 168 + .../t/ngx_stream_zone_test_module.c | 134 + .../nginx-multiport-module/t/ngx_test_macro.h | 41 + .../modules/nginx-rtmp-module/AUTHORS | 9 + .../modules/nginx-rtmp-module/LICENSE | 22 + .../modules/nginx-rtmp-module/config | 169 + .../dash/ngx_rtmp_dash_module.c | 1532 ++++++++++ .../nginx-rtmp-module/dash/ngx_rtmp_mp4.c | 1167 +++++++ .../nginx-rtmp-module/dash/ngx_rtmp_mp4.h | 52 + .../hls/ngx_rtmp_hls_module.c | 2713 +++++++++++++++++ .../nginx-rtmp-module/hls/ngx_rtmp_mpegts.c | 558 ++++ .../nginx-rtmp-module/hls/ngx_rtmp_mpegts.h | 58 + .../http/ngx_http_flv_live_module.c | 733 +++++ .../http/ngx_http_set_header.c | 282 ++ .../http/ngx_http_set_header.h | 11 + .../mpegts/ngx_hls_http_module.c | 1303 ++++++++ .../mpegts/ngx_hls_live_module.c | 1094 +++++++ .../mpegts/ngx_hls_live_module.h | 95 + .../mpegts/ngx_mpegts_gop_module.c | 731 +++++ .../mpegts/ngx_mpegts_gop_module.h | 19 + .../mpegts/ngx_mpegts_http_module.c | 611 ++++ .../mpegts/ngx_mpegts_live_module.c | 1891 ++++++++++++ .../mpegts/ngx_mpegts_live_module.h | 29 + .../modules/nginx-rtmp-module/ngx_live.c | 449 +++ .../modules/nginx-rtmp-module/ngx_live.h | 42 + .../nginx-rtmp-module/ngx_live_record.c | 1307 ++++++++ .../nginx-rtmp-module/ngx_live_record.h | 59 + .../nginx-rtmp-module/ngx_live_relay.c | 418 +++ .../nginx-rtmp-module/ngx_live_relay.h | 99 + .../ngx_live_relay_httpflv.c | 637 ++++ .../nginx-rtmp-module/ngx_live_relay_inner.c | 399 +++ .../nginx-rtmp-module/ngx_live_relay_rtmp.c | 860 ++++++ .../nginx-rtmp-module/ngx_live_relay_simple.c | 460 +++ .../nginx-rtmp-module/ngx_live_relay_static.c | 656 ++++ .../modules/nginx-rtmp-module/ngx_netcall.c | 205 ++ .../modules/nginx-rtmp-module/ngx_netcall.h | 47 + .../modules/nginx-rtmp-module/ngx_rtmp.c | 1343 ++++++++ .../modules/nginx-rtmp-module/ngx_rtmp.h | 1021 +++++++ .../ngx_rtmp_access_module.c | 482 +++ .../modules/nginx-rtmp-module/ngx_rtmp_amf.c | 664 ++++ .../modules/nginx-rtmp-module/ngx_rtmp_amf.h | 71 + .../nginx-rtmp-module/ngx_rtmp_bandwidth.c | 26 + .../nginx-rtmp-module/ngx_rtmp_bandwidth.h | 31 + .../nginx-rtmp-module/ngx_rtmp_bitop.c | 63 + .../nginx-rtmp-module/ngx_rtmp_bitop.h | 46 + .../nginx-rtmp-module/ngx_rtmp_cmd_module.c | 1096 +++++++ .../nginx-rtmp-module/ngx_rtmp_cmd_module.h | 166 + .../nginx-rtmp-module/ngx_rtmp_codec_module.c | 1376 +++++++++ .../nginx-rtmp-module/ngx_rtmp_codec_module.h | 85 + .../ngx_rtmp_control_module.c | 922 ++++++ .../nginx-rtmp-module/ngx_rtmp_core_module.c | 1203 ++++++++ .../nginx-rtmp-module/ngx_rtmp_dynamic.c | 999 ++++++ .../nginx-rtmp-module/ngx_rtmp_dynamic.h | 36 + .../modules/nginx-rtmp-module/ngx_rtmp_eval.c | 298 ++ .../modules/nginx-rtmp-module/ngx_rtmp_eval.h | 44 + .../nginx-rtmp-module/ngx_rtmp_exec_module.c | 1551 ++++++++++ .../nginx-rtmp-module/ngx_rtmp_gop_module.c | 734 +++++ .../nginx-rtmp-module/ngx_rtmp_handler.c | 948 ++++++ .../nginx-rtmp-module/ngx_rtmp_handshake.c | 641 ++++ .../modules/nginx-rtmp-module/ngx_rtmp_init.c | 767 +++++ .../nginx-rtmp-module/ngx_rtmp_limit_module.c | 205 ++ .../nginx-rtmp-module/ngx_rtmp_live_module.c | 1108 +++++++ .../nginx-rtmp-module/ngx_rtmp_live_module.h | 66 + .../nginx-rtmp-module/ngx_rtmp_log_module.c | 1350 ++++++++ .../ngx_rtmp_monitor_module.c | 458 +++ .../ngx_rtmp_monitor_module.h | 20 + .../ngx_rtmp_notify_module.c | 1964 ++++++++++++ .../ngx_rtmp_notify_module.h | 19 + .../ngx_rtmp_proxy_protocol.c | 197 ++ .../ngx_rtmp_proxy_protocol.h | 19 + .../nginx-rtmp-module/ngx_rtmp_receive.c | 464 +++ .../ngx_rtmp_record_module.c | 1308 ++++++++ .../ngx_rtmp_record_module.h | 96 + .../modules/nginx-rtmp-module/ngx_rtmp_send.c | 699 +++++ .../ngx_rtmp_shared_module.c | 402 +++ .../nginx-rtmp-module/ngx_rtmp_stat_module.c | 754 +++++ .../nginx-rtmp-module/ngx_rtmp_streams.h | 19 + .../ngx_rtmp_sys_stat_module.c | 170 ++ .../nginx-rtmp-module/ngx_rtmp_variables.c | 1578 ++++++++++ .../nginx-rtmp-module/ngx_rtmp_variables.h | 85 + .../nginx-rtmp-module/ngx_rtmp_version.h | 15 + .../modules/nginx-toolkit-module/COPYRIGHT | 24 + .../modules/nginx-toolkit-module/README.md | 37 + .../modules/nginx-toolkit-module/config | 47 + .../doc/ngx-dynamic-conf-module.md | 343 +++ .../doc/ngx-dynamic-resolver-module.md | 157 + .../doc/ngx-event-resolver-module.md | 126 + .../doc/ngx-event-timer-module.md | 112 + .../nginx-toolkit-module/doc/ngx-map.md | 112 + .../nginx-toolkit-module/doc/ngx-poold.md | 19 + .../nginx-toolkit-module/doc/ngx-rbuf.md | 40 + .../doc/ngx-toolkit-misc.md | 89 + .../nginx-toolkit-module/ngx_dynamic_conf.c | 963 ++++++ .../nginx-toolkit-module/ngx_dynamic_conf.h | 84 + .../ngx_dynamic_resolver.c | 700 +++++ .../ngx_dynamic_resolver.h | 75 + .../nginx-toolkit-module/ngx_event_resolver.c | 281 ++ .../nginx-toolkit-module/ngx_event_resolver.h | 43 + .../ngx_event_timer_module.c | 271 ++ .../ngx_event_timer_module.h | 42 + .../nginx-toolkit-module/ngx_http_dynamic.c | 1677 ++++++++++ .../nginx-toolkit-module/ngx_http_dynamic.h | 31 + .../ngx_http_trace_module.c | 527 ++++ .../modules/nginx-toolkit-module/ngx_map.c | 327 ++ .../modules/nginx-toolkit-module/ngx_map.h | 131 + .../modules/nginx-toolkit-module/ngx_poold.c | 190 ++ .../modules/nginx-toolkit-module/ngx_poold.h | 56 + .../modules/nginx-toolkit-module/ngx_rbuf.c | 292 ++ .../modules/nginx-toolkit-module/ngx_rbuf.h | 59 + .../modules/nginx-toolkit-module/ngx_timerd.c | 269 ++ .../modules/nginx-toolkit-module/ngx_timerd.h | 68 + .../nginx-toolkit-module/ngx_toolkit_misc.c | 383 +++ .../nginx-toolkit-module/ngx_toolkit_misc.h | 126 + .../modules/nginx-toolkit-module/t/config | 32 + .../nginx-toolkit-module/t/dns_install.sh | 7 + .../nginx-toolkit-module/t/md5testfile | 24 + .../modules/nginx-toolkit-module/t/named.conf | 25 + .../modules/nginx-toolkit-module/t/nginx.conf | 94 + .../nginx-toolkit-module/t/nginx_dynamic.conf | 74 + .../t/ngx_dynamic_conf_test_module.c | 200 ++ .../t/ngx_dynamic_resolver_test_module.c | 193 ++ .../t/ngx_event_resolver_test_module.c | 150 + .../t/ngx_event_timer_test_module.c | 151 + .../t/ngx_http_dynamic_test_module.c | 300 ++ .../t/ngx_map_test_module.c | 251 ++ .../t/ngx_poold_test_module.c | 90 + .../t/ngx_rbuf_test_module.c | 116 + .../nginx-toolkit-module/t/ngx_test_macro.h | 41 + .../t/ngx_timerd_test_module.c | 198 ++ .../t/ngx_toolkit_misc_test_module.c | 285 ++ .../nginx-toolkit-module/t/test.com.zone | 14 + .../nginx-toolkit-module/t/test1.com.zone | 14 + .../modules/nginx-ts-module/LICENSE | 22 + .../modules/nginx-ts-module/README.rst | 256 ++ .../modules/nginx-ts-module/config | 18 + .../nginx-ts-module/src/ngx_http_ts_module.c | 263 ++ .../src/ngx_stream_ts_module.c | 243 ++ .../modules/nginx-ts-module/src/ngx_ts_aac.c | 69 + .../modules/nginx-ts-module/src/ngx_ts_aac.h | 30 + .../modules/nginx-ts-module/src/ngx_ts_avc.c | 298 ++ .../modules/nginx-ts-module/src/ngx_ts_avc.h | 57 + .../modules/nginx-ts-module/src/ngx_ts_dash.c | 1426 +++++++++ .../modules/nginx-ts-module/src/ngx_ts_dash.h | 134 + .../nginx-ts-module/src/ngx_ts_dash_mp4.c | 1486 +++++++++ .../modules/nginx-ts-module/src/ngx_ts_hls.c | 1002 ++++++ .../modules/nginx-ts-module/src/ngx_ts_hls.h | 75 + .../nginx-ts-module/src/ngx_ts_stream.c | 1496 +++++++++ .../nginx-ts-module/src/ngx_ts_stream.h | 113 + .../patches/101-feature_test_fix.patch | 116 + .../patches/102-sizeof_test_fix.patch | 27 + .../patches/103-sys_nerr.patch | 12 + .../patches/104-endianness_fix.patch | 21 + .../patches/200-config.patch | 18 + .../patches/201-ignore-invalid-options.patch | 12 + .../luci-app-nginx-pingos/po/zh-cn/pingos.po | 41 + .../root/etc/config/pingos | 10 + .../root/etc/init.d/pingos | 65 + .../root/etc/pingos.template | 207 ++ .../etc/uci-defaults/luci-app-nginx-pingos | 11 + .../root/resource/conf-template/nginx.conf | 205 ++ .../root/resource/crossdomain.xml | 4 + .../root/resource/stat.xsl | 355 +++ 193 files changed, 70408 insertions(+) create mode 100644 package/lienol/luci-app-nginx-pingos/Makefile create mode 100644 package/lienol/luci-app-nginx-pingos/luasrc/controller/pingos.lua create mode 100644 package/lienol/luci-app-nginx-pingos/luasrc/model/cbi/pingos.lua create mode 100644 package/lienol/luci-app-nginx-pingos/luasrc/view/pingos/status.htm create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/COPYRIGHT create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/config create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/ngx_client.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/ngx_client.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/ngx_http_client.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/ngx_http_client.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/t/config create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/t/nginx.conf create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/t/ngx_client_stat_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/t/ngx_client_test_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/t/ngx_http_client_test_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/t/tcpserver.go create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/COPYRIGHT create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/README.md create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/config create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/doc/ngx-http-broadcast-module.md create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/doc/ngx-stream-zone-module.md create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/ngx_event_multiport_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/ngx_http_broadcast_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/ngx_http_inner_proxy_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/ngx_multiport.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/ngx_multiport_misc.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/ngx_process_slot_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/ngx_stream_zone_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/ngx_stream_zone_module.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/t/config create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/t/nginx.conf create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/t/ngx_multiport_test_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/t/ngx_stream_zone_test_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/t/ngx_test_macro.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/AUTHORS create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/LICENSE create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/config create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/dash/ngx_rtmp_dash_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/dash/ngx_rtmp_mp4.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/dash/ngx_rtmp_mp4.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/hls/ngx_rtmp_hls_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/hls/ngx_rtmp_mpegts.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/hls/ngx_rtmp_mpegts.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/http/ngx_http_flv_live_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/http/ngx_http_set_header.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/http/ngx_http_set_header.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/mpegts/ngx_hls_http_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/mpegts/ngx_hls_live_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/mpegts/ngx_hls_live_module.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/mpegts/ngx_mpegts_gop_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/mpegts/ngx_mpegts_gop_module.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/mpegts/ngx_mpegts_http_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/mpegts/ngx_mpegts_live_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/mpegts/ngx_mpegts_live_module.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_record.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_record.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_relay.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_relay.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_relay_httpflv.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_relay_inner.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_relay_rtmp.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_relay_simple.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_relay_static.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_netcall.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_netcall.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_access_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_amf.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_amf.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_bandwidth.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_bandwidth.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_bitop.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_bitop.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_cmd_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_cmd_module.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_codec_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_codec_module.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_control_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_core_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_dynamic.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_dynamic.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_eval.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_eval.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_exec_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_gop_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_handler.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_handshake.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_init.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_limit_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_live_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_live_module.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_log_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_monitor_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_monitor_module.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_notify_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_notify_module.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_proxy_protocol.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_proxy_protocol.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_receive.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_record_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_record_module.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_send.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_shared_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_stat_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_streams.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_sys_stat_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_variables.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_variables.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_version.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/COPYRIGHT create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/README.md create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/config create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/doc/ngx-dynamic-conf-module.md create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/doc/ngx-dynamic-resolver-module.md create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/doc/ngx-event-resolver-module.md create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/doc/ngx-event-timer-module.md create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/doc/ngx-map.md create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/doc/ngx-poold.md create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/doc/ngx-rbuf.md create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/doc/ngx-toolkit-misc.md create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_dynamic_conf.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_dynamic_conf.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_dynamic_resolver.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_dynamic_resolver.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_event_resolver.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_event_resolver.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_event_timer_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_event_timer_module.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_http_dynamic.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_http_dynamic.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_http_trace_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_map.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_map.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_poold.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_poold.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_rbuf.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_rbuf.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_timerd.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_timerd.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_toolkit_misc.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_toolkit_misc.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/config create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/dns_install.sh create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/md5testfile create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/named.conf create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/nginx.conf create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/nginx_dynamic.conf create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_dynamic_conf_test_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_dynamic_resolver_test_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_event_resolver_test_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_event_timer_test_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_http_dynamic_test_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_map_test_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_poold_test_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_rbuf_test_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_test_macro.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_timerd_test_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_toolkit_misc_test_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/test.com.zone create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/test1.com.zone create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/LICENSE create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/README.rst create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/config create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_http_ts_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_stream_ts_module.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_aac.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_aac.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_avc.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_avc.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_dash.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_dash.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_dash_mp4.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_hls.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_hls.h create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_stream.c create mode 100644 package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_stream.h create mode 100644 package/lienol/luci-app-nginx-pingos/patches/101-feature_test_fix.patch create mode 100644 package/lienol/luci-app-nginx-pingos/patches/102-sizeof_test_fix.patch create mode 100644 package/lienol/luci-app-nginx-pingos/patches/103-sys_nerr.patch create mode 100644 package/lienol/luci-app-nginx-pingos/patches/104-endianness_fix.patch create mode 100644 package/lienol/luci-app-nginx-pingos/patches/200-config.patch create mode 100644 package/lienol/luci-app-nginx-pingos/patches/201-ignore-invalid-options.patch create mode 100644 package/lienol/luci-app-nginx-pingos/po/zh-cn/pingos.po create mode 100644 package/lienol/luci-app-nginx-pingos/root/etc/config/pingos create mode 100644 package/lienol/luci-app-nginx-pingos/root/etc/init.d/pingos create mode 100644 package/lienol/luci-app-nginx-pingos/root/etc/pingos.template create mode 100644 package/lienol/luci-app-nginx-pingos/root/etc/uci-defaults/luci-app-nginx-pingos create mode 100644 package/lienol/luci-app-nginx-pingos/root/resource/conf-template/nginx.conf create mode 100644 package/lienol/luci-app-nginx-pingos/root/resource/crossdomain.xml create mode 100644 package/lienol/luci-app-nginx-pingos/root/resource/stat.xsl diff --git a/package/lienol/luci-app-nginx-pingos/Makefile b/package/lienol/luci-app-nginx-pingos/Makefile new file mode 100644 index 0000000000..61afbb9631 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/Makefile @@ -0,0 +1,98 @@ +# Copyright (C) 2020 Lienol + +include $(TOPDIR)/rules.mk + +PKG_NAME:=luci-app-nginx-pingos +PKG_VERSION:=1.19.0 +PKG_RELEASE:=3 + +PKG_SOURCE:=nginx-$(PKG_VERSION).tar.gz +PKG_SOURCE_URL:=https://nginx.org/download/ +PKG_HASH:=44a616171fcd7d7ad7c6af3e6f3ad0879b54db5a5d21be874cd458b5691e36c8 + +PKG_DIR:=$(BUILD_DIR)/$(PKG_NAME) +PKG_BUILD_DIR:=$(PKG_DIR)/nginx-$(PKG_VERSION) + +PKG_FIXUP:=autoreconf +PKG_BUILD_PARALLEL:=1 +PKG_INSTALL:=1 + +include $(INCLUDE_DIR)/package.mk + +define Package/$(PKG_NAME) + CATEGORY:=LuCI + SUBMENU:=3. Applications + TITLE:=PingOS server + PKGARCH:=all + URL:=https://pingos.io/ + DEPENDS:=+libpcre +libopenssl +zlib +libpthread +endef + +define Package/$(PKG_NAME)/conffiles +endef + +define Build/Prepare + rm -r $(PKG_BUILD_DIR) + mkdir -p $(PKG_BUILD_DIR) + tar -zxvf $(DL_DIR)/$(PKG_SOURCE) -C $(PKG_DIR) + $(CP) -pR ./modules $(PKG_BUILD_DIR)/modules + $(call Build/Prepare/Default,) +endef + +ADDITIONAL_MODULES:= --with-http_ssl_module \ + --add-module=./modules/nginx-rtmp-module \ + --add-module=./modules/nginx-client-module \ + --add-module=./modules/nginx-multiport-module \ + --add-module=./modules/nginx-toolkit-module + +TARGET_CFLAGS += -fvisibility=hidden -ffunction-sections -fdata-sections -DNGX_LUA_NO_BY_LUA_BLOCK +TARGET_LDFLAGS += -Wl,--gc-sections + +CONFIGURE_ARGS += \ + --crossbuild=Linux::$(ARCH) \ + --prefix=/usr \ + --conf-path=/usr/share/pingos/conf/nginx.conf \ + $(ADDITIONAL_MODULES) \ + --error-log-path=/var/etc/pingos/error.log \ + --pid-path=/var/etc/pingos/pingos.pid \ + --lock-path=/var/etc/pingos/pingos.lock \ + --http-log-path=/var/etc/pingos/access.log \ + --http-client-body-temp-path=/var/etc/pingos/lib/body \ + --http-proxy-temp-path=/var/etc/pingos/lib/proxy \ + --with-cc="$(TARGET_CC)" \ + --with-cc-opt="$(TARGET_CPPFLAGS) $(TARGET_CFLAGS)" \ + --with-ld-opt="$(TARGET_LDFLAGS)" \ + --without-http_upstream_zone_module + +define Package/$(PKG_NAME)/install + $(INSTALL_DIR) $(1)/etc + $(INSTALL_CONF) ./root/etc/pingos.template $(1)/etc/pingos.template + + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_CONF) ./root/etc/config/pingos $(1)/etc/config/pingos + + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_BIN) ./root/etc/init.d/pingos $(1)/etc/init.d/pingos + + $(INSTALL_DIR) $(1)/etc/uci-defaults + $(INSTALL_CONF) ./root/etc/uci-defaults/* $(1)/etc/uci-defaults + + $(INSTALL_DIR) $(1)/usr/share/pingos + cp -pR $(PKG_INSTALL_DIR)/usr/share/pingos/conf $(1)/usr/share/pingos + $(INSTALL_DATA) ./root/resource/conf-template/nginx.conf $(1)/usr/share/pingos/conf/nginx.conf + + cp -pR $(PKG_INSTALL_DIR)/usr/html $(1)/usr/share/pingos/html + $(INSTALL_DATA) ./root/resource/crossdomain.xml $(1)/usr/share/pingos/html/crossdomain.xml + $(INSTALL_DATA) ./root/resource/stat.xsl $(1)/usr/share/pingos/html/stat.xsl + + $(INSTALL_DIR) $(1)/usr/lib/lua/luci + cp -pR ./luasrc/* $(1)/usr/lib/lua/luci/ + + $(INSTALL_DIR) $(1)/usr/lib/lua/luci/i18n + po2lmo ./po/zh-cn/pingos.po $(1)/usr/lib/lua/luci/i18n/pingos.zh-cn.lmo + + $(INSTALL_DIR) $(1)/usr/sbin + $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/sbin/nginx $(1)/usr/sbin/pingos +endef + +$(eval $(call BuildPackage,$(PKG_NAME))) diff --git a/package/lienol/luci-app-nginx-pingos/luasrc/controller/pingos.lua b/package/lienol/luci-app-nginx-pingos/luasrc/controller/pingos.lua new file mode 100644 index 0000000000..7c7bc99724 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/luasrc/controller/pingos.lua @@ -0,0 +1,17 @@ +-- Copyright 2020 Lienol +module("luci.controller.pingos", package.seeall) + +function index() + if not nixio.fs.access("/etc/config/pingos") then return end + + entry({"admin", "nas"}, firstchild(), "NAS", 44).dependent = false + entry({"admin", "nas", "pingos"}, cbi("pingos"), _("PingOS"), 3).dependent = true + entry({"admin", "nas", "pingos", "status"}, call("act_status")).leaf = true +end + +function act_status() + local e = {} + e.status = luci.sys.call("ps -w | grep pingos | grep nginx | grep -v grep > /dev/null") == 0 + luci.http.prepare_content("application/json") + luci.http.write_json(e) +end diff --git a/package/lienol/luci-app-nginx-pingos/luasrc/model/cbi/pingos.lua b/package/lienol/luci-app-nginx-pingos/luasrc/model/cbi/pingos.lua new file mode 100644 index 0000000000..ef8cd4cc04 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/luasrc/model/cbi/pingos.lua @@ -0,0 +1,65 @@ +m = Map("pingos", translate("PingOS")) +m:append(Template("pingos/status")) + +s = m:section(TypedSection, "global") +s.anonymous = true +s.addremove = false + +s:tab("global", translate("Global Settings")) +s:tab("template", translate("Edit Template")) + +nginx = s:taboption("template", Value, "_nginx", translatef("Edit the template that is used for generating the %s configuration.", "nginx"), + translatef("This is the content of the file '%s'", "/etc/pingos.template") .. "
" .. + translatef("Values enclosed by pipe symbols ('|') should not be changed. They get their values from the '%s' tab.", translate("Global Settings"))) +nginx.template = "cbi/tvalue" +nginx.rows = 30 + +function nginx.cfgvalue(self, section) + return nixio.fs.readfile("/etc/pingos.template") +end + +function nginx.write(self, section, value) + value = value:gsub("\r\n?", "\n") + nixio.fs.writefile("/etc/pingos.template", value) +end + +o = s:taboption("global", Flag, "enable", translate("Enable")) +o.rmempty = false + +o = s:taboption("global", Value, "http_port", "HTTP(S)" ..translate("Port")) +o.datatype = "port" +o.default = 8082 +o.rmempty = false + +o = s:taboption("global", Flag, "https", translate("HTTPS")) +o.rmempty = false + +o = s:taboption("global", FileUpload, "certificate", translate("certificate")) +o:depends("https", 1) + +o = s:taboption("global", FileUpload, "key", translate("key")) +o:depends("https", 1) + +o = s:taboption("global", Value, "rtmp_port", "RTMP" ..translate("Port")) +o.datatype = "port" +o.default = 1935 +o.rmempty = false + +o = s:taboption("global", Flag, "hls", translate("HLS")) +o.rmempty = false + +o = s:taboption("global", Flag, "hls2", translate("HLS2")) +o.rmempty = false + +o = s:taboption("global", Flag, "ts_record", "TS " .. translate("Record")) +o.rmempty = false + +o = s:taboption("global", Flag, "flv_record", "FLV " .. translate("Record")) +o.rmempty = false + +o = s:taboption("global", Value, "record_path", translate("Record") .. translate("Path")) +o.default = "/tmp/record" +o:depends("ts_record", 1) +o:depends("flv_record", 1) + +return m diff --git a/package/lienol/luci-app-nginx-pingos/luasrc/view/pingos/status.htm b/package/lienol/luci-app-nginx-pingos/luasrc/view/pingos/status.htm new file mode 100644 index 0000000000..d2b1a343b5 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/luasrc/view/pingos/status.htm @@ -0,0 +1,52 @@ +
+ <%:Running Status%> +
+
+ +
<%:Collecting data...%>
+
+
+ +
+ +
    +
  • rtmp rtmp://ip/live/<%:Stream name%>
  • +
  • http(s)-flv http(s)://ip/flv/<%:Stream name%>
  • +
  • hls http(s)://ip/hls/<%:Stream name%>.m3u8
  • +
  • hls+ http(s)://ip/hls2/<%:Stream name%>.m3u8
  • +
  • http(s)-ts http(s)://ip/ts/<%:Stream name%>
  • +
+
+
+
+
+ + \ No newline at end of file diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/COPYRIGHT b/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/COPYRIGHT new file mode 100644 index 0000000000..837a6682d6 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/COPYRIGHT @@ -0,0 +1,24 @@ +Copyright (C) 2016-2020, by Jie Wu "AlexWoo" . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/config b/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/config new file mode 100644 index 0000000000..36352b8029 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/config @@ -0,0 +1,19 @@ +ngx_addon_name=ngx_client_module + + +CORE_MODULES="$CORE_MODULES \ + ngx_client_module \ + ngx_http_client_module \ + " + +NGX_ADDON_SRCS="$NGX_ADDON_SRCS \ + $ngx_addon_dir/ngx_client.c \ + $ngx_addon_dir/ngx_http_client.c \ + " + +NGX_ADDON_DEPS="$NGX_ADDON_DEPS \ + $ngx_addon_dir/ngx_client.h \ + $ngx_addon_dir/ngx_http_client.h \ + " + +CFLAGS="$CFLAGS -I $ngx_addon_dir" diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/ngx_client.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/ngx_client.c new file mode 100644 index 0000000000..e18d4a0273 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/ngx_client.c @@ -0,0 +1,1387 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + * fixed by Ping cczjp89@gmail.com + */ + + +#include +#include "ngx_client.h" +#include "ngx_event_resolver.h" +#include "ngx_dynamic_resolver.h" +#include "ngx_poold.h" +#include "ngx_map.h" +#include "ngx_timerd.h" + + +#define NGX_CLIENT_DISCARD_BUFFER_SIZE 4096 + + +static void *ngx_client_module_create_conf(ngx_cycle_t *cycle); +static char *ngx_client_module_init_conf(ngx_cycle_t *cycle, void *conf); + + +typedef struct ngx_client_pool_s ngx_client_pool_t; + + +struct ngx_client_pool_s { + ngx_map_node_t node; + ngx_queue_t cs_queue; /* client session queue */ + ngx_uint_t qsize; /* client pool size */ + + u_char addr[NGX_SOCKADDR_STRLEN]; + ngx_str_t paddr; + + ngx_client_pool_t *next; /* free pool node */ +}; + + +typedef struct { + ngx_map_t client_pools; /* key is ip:port */ + /* max keepalive client session */ + ngx_uint_t max_idle_client; + ngx_msec_t keepalive; + ngx_msec_t connect_timeout; + ngx_msec_t send_timeout; + size_t postpone_output; + ngx_flag_t dynamic_resolver; + ngx_flag_t tcp_nodelay; + ngx_flag_t tcp_nopush; + + ngx_uint_t idle_connction; /* connection num in pools */ + ngx_uint_t nalloc; + ngx_uint_t nfree; + ngx_client_pool_t *free; /* recycle free pool node */ +} ngx_client_conf_t; + + +static ngx_command_t ngx_client_commands[] = { + + { ngx_string("max_idle_client"), + NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + 0, + offsetof(ngx_client_conf_t, max_idle_client), + NULL }, + + { ngx_string("keepalive"), + NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + 0, + offsetof(ngx_client_conf_t, keepalive), + NULL }, + + { ngx_string("connect_timeout"), + NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + 0, + offsetof(ngx_client_conf_t, connect_timeout), + NULL }, + + { ngx_string("send_timeout"), + NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + 0, + offsetof(ngx_client_conf_t, send_timeout), + NULL }, + + { ngx_string("postpone_output"), + NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + 0, + offsetof(ngx_client_conf_t, postpone_output), + NULL }, + + { ngx_string("dynamic_resolver"), + NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + 0, + offsetof(ngx_client_conf_t, dynamic_resolver), + NULL }, + + { ngx_string("tcp_nodelay"), + NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + 0, + offsetof(ngx_client_conf_t, tcp_nodelay), + NULL }, + + { ngx_string("tcp_nopush"), + NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + 0, + offsetof(ngx_client_conf_t, tcp_nopush), + NULL }, + + ngx_null_command +}; + + +static ngx_core_module_t ngx_client_module_ctx = { + ngx_string("client"), + ngx_client_module_create_conf, + ngx_client_module_init_conf +}; + + +ngx_module_t ngx_client_module = { + NGX_MODULE_V1, + &ngx_client_module_ctx, /* module context */ + ngx_client_commands, /* module directives */ + NGX_CORE_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void * +ngx_client_module_create_conf(ngx_cycle_t *cycle) +{ + ngx_client_conf_t *ccf; + + ccf = ngx_pcalloc(cycle->pool, sizeof(ngx_client_conf_t)); + if (ccf == NULL) { + return NULL; + } + + ngx_map_init(&ccf->client_pools, ngx_map_hash_str, ngx_cmp_str); + + ccf->max_idle_client = NGX_CONF_UNSET_UINT; + ccf->keepalive = NGX_CONF_UNSET_MSEC; + ccf->connect_timeout = NGX_CONF_UNSET_MSEC; + ccf->send_timeout = NGX_CONF_UNSET_MSEC; + ccf->postpone_output = NGX_CONF_UNSET_SIZE; + ccf->dynamic_resolver = NGX_CONF_UNSET; + ccf->tcp_nodelay = NGX_CONF_UNSET; + ccf->tcp_nodelay = NGX_CONF_UNSET; + + return ccf; +} + + +static char * +ngx_client_module_init_conf(ngx_cycle_t *cycle, void *conf) +{ + ngx_client_conf_t *ccf = conf; + + ngx_conf_init_uint_value(ccf->max_idle_client, 1024); + ngx_conf_init_msec_value(ccf->keepalive, 60000); + + ngx_conf_init_msec_value(ccf->connect_timeout, 3000); + ngx_conf_init_msec_value(ccf->send_timeout, 10000); + ngx_conf_init_size_value(ccf->postpone_output, 1460); + ngx_conf_init_value(ccf->dynamic_resolver, 1); + ngx_conf_init_value(ccf->tcp_nodelay, 1); + ngx_conf_init_value(ccf->tcp_nopush, 0); + + return NGX_CONF_OK; +} + + +/* client pool */ +static ngx_client_pool_t * +ngx_client_get_client_pool() +{ + ngx_client_conf_t *ccf; + ngx_client_pool_t *pool; + + ccf = (ngx_client_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_client_module); + + pool = ccf->free; + if (pool == NULL) { + pool = ngx_pcalloc(ngx_cycle->pool, sizeof(ngx_client_pool_t)); + if (pool == NULL) { + return NULL; + } + + ++ccf->nalloc; + } else { + ccf->free = pool->next; + ngx_memzero(pool, sizeof(ngx_client_pool_t)); + + --ccf->nfree; + } + + ngx_queue_init(&pool->cs_queue); + + return pool; +} + + +static void +ngx_client_put_client_pool(ngx_client_pool_t *p) +{ + ngx_client_conf_t *ccf; + + ccf = (ngx_client_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_client_module); + + p->next = ccf->free; + ccf->free = p; + + ++ccf->nfree; +} + + +static ngx_connection_t * +ngx_client_get_connection(struct sockaddr *sockaddr, socklen_t socklen) +{ + ngx_client_conf_t *ccf; + ngx_client_pool_t *pool; + ngx_map_node_t *node; + ngx_queue_t *cq; + ngx_str_t paddr; + u_char addr[NGX_SOCKADDR_STRLEN]; + ngx_connection_t *c; + + ccf = (ngx_client_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_client_module); + +#if (NGX_HAVE_UNIX_DOMAIN) + if (sockaddr->sa_family == AF_UNIX) { // Unix will not reuse + return NULL; + } +#endif + + // get client connection pool for sockaddr + paddr.data = addr; + paddr.len = NGX_SOCKADDR_STRLEN; + paddr.len = ngx_sock_ntop(sockaddr, socklen, paddr.data, paddr.len, 1); + + node = ngx_map_find(&ccf->client_pools, (intptr_t) &paddr); + + if (node == NULL) { // connection pool for addr is empty + return NULL; + } + + // get a idle connection from client connection pool + pool = (ngx_client_pool_t *) node; + cq = ngx_queue_head(&pool->cs_queue); + ngx_queue_remove(cq); + c = (ngx_connection_t *) ((char *) cq - offsetof(ngx_connection_t, queue)); + --ccf->idle_connction; + --pool->qsize; + + // recycle empty pool + if (ngx_queue_empty(&pool->cs_queue)) { + ngx_map_delete(&ccf->client_pools, (intptr_t) &paddr); + ngx_client_put_client_pool(pool); + } + + // init connection + if (c->read->timer_set) { + NGX_DEL_TIMER(c->read, c->number); + } + + if (c->read->posted) { + ngx_delete_posted_event(c->read); + } + + return c; +} + + +static void +ngx_client_keepalive_handler(ngx_event_t *rev) +{ + ngx_client_conf_t *ccf; + ngx_client_pool_t *pool; + ngx_connection_t *c; + ngx_int_t n; + ngx_buf_t b; + u_char buffer[NGX_CLIENT_DISCARD_BUFFER_SIZE]; + + c = rev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_CORE, c->log, 0, "client keepalive handler"); + + if (rev->timedout || c->close) { + goto close; + } + + // read and discard data + b.start = buffer; + b.end = buffer + NGX_CLIENT_DISCARD_BUFFER_SIZE; + + for (;;) { + b.pos = b.last = b.start; + + n = c->recv(c, b.last, b.end - b.last); + + if (n == 0 || n == NGX_ERROR) { + ngx_log_error(NGX_LOG_INFO, c->log, ngx_errno, + "server close while client keepalive"); + goto close; + } + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "server recv data while client keepalive %d", n); + + goto close; + } + +close: + // remove connection from pool + ccf = (ngx_client_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_client_module); + + pool = c->data; + + ngx_queue_remove(&c->queue); + --ccf->idle_connction; + --pool->qsize; + + // recycle empty pool + if (ngx_queue_empty(&pool->cs_queue)) { + ngx_map_delete(&ccf->client_pools, (intptr_t) &pool->paddr); + ngx_client_put_client_pool(pool); + } + + if (c->read->timer_set) { + NGX_DEL_TIMER(c->read, c->number); + } + + if (c->write->timer_set) { + NGX_DEL_TIMER(c->write, c->number); + } + + ngx_close_connection(c); +} + + +static void +ngx_client_reusable_connection(ngx_client_session_t *s) +{ + ngx_client_conf_t *ccf; + ngx_client_pool_t *pool; + ngx_map_node_t *node; + ngx_connection_t *c; + ngx_str_t paddr; + u_char addr[NGX_SOCKADDR_STRLEN]; + + ccf = (ngx_client_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_client_module); + + c = s->connection; + +#if (NGX_HAVE_UNIX_DOMAIN) + if (s->peer.sockaddr->sa_family == AF_UNIX) { // Unix will not reuse + return; + } +#endif + + if (ccf->idle_connction > ccf->keepalive) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "too many connections in pool"); + return; + } + + ngx_log_error(NGX_LOG_INFO, &s->log, 0, "client, put connection in pool"); + + c->pool = NULL; + + // get client connection pool for c->sockaddr + paddr.data = addr; + paddr.len = NGX_SOCKADDR_STRLEN; + paddr.len = ngx_sock_ntop(s->peer.sockaddr, s->peer.socklen, + paddr.data, paddr.len, 1); + + node = ngx_map_find(&ccf->client_pools, (intptr_t) &paddr); + pool = (ngx_client_pool_t *) node; + + if (pool == NULL) { // connection pool for addr is empty + pool = ngx_client_get_client_pool(); + if (pool == NULL) { + return; + } + + ngx_memcpy(pool->addr, addr, NGX_SOCKADDR_STRLEN); + pool->paddr.data = pool->addr; + pool->paddr.len = paddr.len; + + pool->node.raw_key = (intptr_t) &pool->paddr; + ngx_map_insert(&ccf->client_pools, &pool->node, 0); + } + + // put connection in connection pool + ngx_queue_insert_head(&pool->cs_queue, &c->queue); + c->data = pool; + ++ccf->idle_connction; + ++pool->qsize; + + c->log = ngx_cycle->log; + c->read->log = ngx_cycle->log; + + // set timer for keepalive time + c->read->handler = ngx_client_keepalive_handler; + NGX_ADD_TIMER(c->read, ccf->keepalive, offsetof(ngx_connection_t, number)); + + if (c->write->timer_set) { + NGX_DEL_TIMER(c->write, c->number); + } + + if (c->write->posted) { + ngx_delete_posted_event(c->write); + } + + if (c->write->active && (ngx_event_flags & NGX_USE_LEVEL_EVENT)) { + if (ngx_del_event(c->write, NGX_WRITE_EVENT, 0) != NGX_OK) { + if (c->read->timer_set) { + NGX_DEL_TIMER(c->read, c->number); + } + + ngx_close_connection(c); + return; + } + } +} + + +/* client */ + +static u_char * +ngx_client_log_error(ngx_log_t *log, u_char *buf, size_t len) +{ + u_char *p; + ngx_client_session_t *s; + + p = buf; + + if (log->action) { + p = ngx_snprintf(buf, len, " while %s", log->action); + len -= p - buf; + buf = p; + } + + s = log->data; + if (s == NULL) { + return p; + } + + if (s->connection) { + p = ngx_snprintf(buf, len, ", server ip: %V", + &s->connection->addr_text); + len -= p - buf; + buf = p; + } + + p = ngx_snprintf(buf, len, ", server: %V, csession: %p", &s->server, s); + len -= p - buf; + buf = p; + + return p; +} + + +static ngx_int_t +ngx_client_get_peer(ngx_peer_connection_t *pc, void *data) +{ + ngx_connection_t *c; + + c = ngx_client_get_connection(pc->sockaddr, pc->socklen); + if (c == NULL) { // cannot find reusable keepalive connection + return NGX_OK; + } + + c->idle = 0; + c->sent = 0; + c->log = pc->log; + c->read->log = pc->log; + c->write->log = pc->log; + + pc->connection = c; + pc->cached = 1; + + return NGX_DONE; +} + + +static void +ngx_client_free_peer(ngx_peer_connection_t *pc, void *data, + ngx_uint_t state) +{ +} + + +static ngx_int_t +ngx_client_test_connect(ngx_connection_t *c) +{ + int err; + socklen_t len; + +#if (NGX_HAVE_KQUEUE) + + if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { + if (c->write->pending_eof || c->read->pending_eof) { + if (c->write->pending_eof) { + err = c->write->kq_errno; + + } else { + err = c->read->kq_errno; + } + + (void) ngx_connection_error(c, err, + "kevent() reported that connect() failed"); + return NGX_ERROR; + } + + } else +#endif + { + err = 0; + len = sizeof(int); + + /* + * BSDs and Linux return 0 and set a pending error in err + * Solaris returns -1 and sets errno + */ + + if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, (void *) &err, &len) + == -1) + { + err = ngx_socket_errno; + } + + if (err) { + (void) ngx_connection_error(c, err, "connect() failed"); + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static void +ngx_client_connected(ngx_client_session_t *s) +{ + ngx_event_t *wev; + + if (ngx_client_test_connect(s->connection) != NGX_OK) { + ngx_client_close(s); + return; + } + + s->log.action = NULL; + + ngx_log_debug0(NGX_LOG_DEBUG_CORE, &s->log, 0, + "nginx client connected"); + + wev = s->connection->write; + + if (wev->timedout) { /* rev or wev timedout */ + ngx_log_error(NGX_LOG_ERR, &s->log, NGX_ETIMEDOUT, + "server timed out"); + s->connection->timedout = 1; + + ngx_client_close(s); + + return; + } + + if (wev->timer_set) { + NGX_DEL_TIMER(wev, s->connection->number); + } + + s->connected = 1; + s->peer.tries = 0; + + if (s->client_connected) { + s->client_connected(s); + } +} + + +static void +ngx_client_write_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + ngx_client_session_t *s; + ngx_int_t n; + + c = ev->data; + s = c->data; + + if (c->destroyed) { + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_CORE, &s->log, 0, + "nginx client write handler"); + + if (!s->connected) { + ngx_client_connected(s); + + return; + } + + if (s->client_send) { + s->client_send(s); + + return; + } + + /* write data buffered in s->out */ + if (s->out == NULL) { + return; + } + + n = ngx_client_write(s, NULL); + if (n == NGX_ERROR) { + ngx_client_close(s); + } +} + + +static void +ngx_client_read_discarded(ngx_client_session_t *s) +{ + ngx_int_t n; + ngx_buf_t b; + u_char buffer[NGX_CLIENT_DISCARD_BUFFER_SIZE]; + + b.start = buffer; + b.end = buffer + NGX_CLIENT_DISCARD_BUFFER_SIZE; + + for (;;) { + b.pos = b.last = b.start; + + n = ngx_client_read(s, &b); + + if (n == NGX_ERROR || n == 0) { + ngx_log_error(NGX_LOG_ERR, &s->log, ngx_errno, + "nginx client read discard error"); + ngx_client_close(s); + + return; + } + + if (n == NGX_AGAIN) { + return; + } + } +} + + +static void +ngx_client_read_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + ngx_client_session_t *s; + + c = ev->data; + s = c->data; + + if (c->destroyed) { + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_CORE, &s->log, 0, + "nginx client read handler"); + + if (!s->connected) { + ngx_client_connected(s); + + return; + } + + if (s->client_recv) { + s->client_recv(s); + } else { + /* read and drop */ + ngx_client_read_discarded(s); + } +} + + +static void +ngx_client_connect_server(void *data, struct sockaddr *sa, socklen_t socklen) +{ + ngx_client_session_t *s; + ngx_connection_t *c; + ngx_int_t rc; + int tcp_nodelay; + + s = data; + + if (sa == NULL) { + ngx_log_error(NGX_LOG_ERR, &s->log, 0, + "nginx client resolver peer %v failed", &s->server); + goto failed; + } + + ngx_inet_set_port(sa, s->port); + + s->peer.sockaddr = ngx_pcalloc(s->pool, sizeof(socklen)); + ngx_memcpy(s->peer.sockaddr, sa, socklen); + s->peer.socklen = socklen; + s->peer.name = &s->server; + + s->log.action = "connecting to server"; + + rc = ngx_event_connect_peer(&s->peer); + if (rc == NGX_ERROR) { + ngx_log_error(NGX_LOG_ERR, &s->log, ngx_errno, + "nginx client connect peer failed"); + goto failed; + } else if (rc == NGX_DECLINED) { + ngx_log_error(NGX_LOG_ERR, &s->log, ngx_errno, + "nginx client connect peer failed"); + goto failed; + } + + if (rc == NGX_DONE) { + ngx_log_error(NGX_LOG_INFO, &s->log, 0, "client, reuse connection"); + } + + // NGX_AGAIN: send syn, wait for syn,ack + // NGX_OK: connect to server + // NGX_DONE: reuse keepalive connection + + s->connection = s->peer.connection; + c = s->connection; + c->pool = s->pool; + c->pool->log = s->peer.log; + + // set tcp_nodelay + if (c->type == SOCK_STREAM && s->tcp_nodelay + && c->tcp_nodelay == NGX_TCP_NODELAY_UNSET) + { + ngx_log_debug0(NGX_LOG_DEBUG_CORE, c->log, 0, "tcp_nodelay"); + + tcp_nodelay = 1; + + if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, + (const void *) &tcp_nodelay, sizeof(int)) == -1) + { + ngx_connection_error(c, ngx_socket_errno, + "setsockopt(TCP_NODELAY) failed"); + goto failed; + } + + c->tcp_nodelay = NGX_TCP_NODELAY_SET; + } + + // set tcp_nopush + if (!s->tcp_nopush) { + c->tcp_nopush = NGX_TCP_NOPUSH_DISABLED; + } + + c->addr_text.data = ngx_pcalloc(s->pool, NGX_SOCKADDR_STRLEN); + if (c->addr_text.data == NULL) { + goto failed; + } + c->addr_text.len = ngx_sock_ntop(sa, socklen, c->addr_text.data, + NGX_SOCKADDR_STRLEN, 1); + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, &s->log, 0, + "nginx client connect server, rc: %i", rc); + + c->log->connection = c->number; + + c->data = s; + + c->write->handler = ngx_client_write_handler; + c->read->handler = ngx_client_read_handler; + + if (rc == NGX_AGAIN) { + NGX_ADD_TIMER(c->write, s->connect_timeout, + offsetof(ngx_connection_t, number)); + return; + } + + /* NGX_OK */ + + ngx_client_connected(s); + + return; + +failed: + ngx_client_close(s); +} + + +static void +ngx_client_resolver_server(void *data, ngx_resolver_addr_t *addrs, + ngx_uint_t naddrs) +{ + ngx_client_session_t *s; + ngx_uint_t n; + + s = data; + + if (naddrs == 0) { + ngx_log_error(NGX_LOG_ERR, &s->log, ngx_errno, + "nginx client resolver failed"); + ngx_client_close(s); + return; + } + + n = ngx_random() % naddrs; + + ngx_client_connect_server(data, addrs[n].sockaddr, addrs[n].socklen); +} + + +static void +ngx_client_close_connection(ngx_client_session_t *s) +{ + ngx_connection_t *c; + + c = s->connection; + + if (c == NULL || c->destroyed) { + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_CORE, &s->log, 0, + "nginx client close connection"); + + s->connected = 0; + s->connection = NULL; + c->destroyed = 1; + + if (c->write->timer_set) { + NGX_DEL_TIMER(c->write, c->number); + } + + if (c->read->timer_set) { + NGX_DEL_TIMER(c->read, c->number); + } + + ngx_close_connection(c); +} + + +ngx_client_session_t * +ngx_client_create(ngx_str_t *peer, ngx_str_t *local, ngx_flag_t udp, + ngx_log_t *log) +{ + ngx_client_session_t *s; + ngx_client_conf_t *ccf; + ngx_pool_t *pool; + ngx_int_t rc, n; + u_char *p, *last; + size_t plen; + + ccf = (ngx_client_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_client_module); + + if (peer == NULL || peer->len == 0) { + ngx_log_error(NGX_LOG_ERR, log, 0, "client init, peer is NULL"); + return NULL; + } + + pool = NGX_CREATE_POOL(4096, ngx_cycle->log); + if (pool == NULL) { + return NULL; + } + + s = ngx_pcalloc(pool, sizeof(ngx_client_session_t)); + if (s == NULL) { + goto clear; + } + s->pool = pool; + + /* set log */ + // ci->log.connection not set, should set when connection established + s->log = ngx_cycle->new_log; + s->log.handler = ngx_client_log_error; + s->log.data = s; + s->log.action = "create client"; + + s->log.log_level = NGX_LOG_INFO; + + /* parse peer */ + last = peer->data + peer->len; + +#if (NGX_HAVE_INET6) + if (peer->len && peer->data[0] == '[') { + + p = ngx_strlchr(peer->data, last, ']'); + + if (p == NULL || p == last - 1) { + ngx_log_error(NGX_LOG_ERR, log, 0, + "client init, parse peer %V error", peer); + goto clear; + } + + ++p; + } else +#endif + { + p = ngx_strlchr(peer->data, last, ':'); + if (p == NULL) { + p = last; + } + } + + s->server.len = p - peer->data; + s->server.data = ngx_pcalloc(s->pool, s->server.len); + if (s->server.data == NULL) { + goto clear; + } + ngx_memcpy(s->server.data, peer->data, peer->len); + + if (p != last) { /* has port */ + if (*p != ':') { + ngx_log_error(NGX_LOG_ERR, log, 0, + "client init, parse peer %V error", peer); + goto clear; + } + + ++p; + plen = last - p; + + n = ngx_atoi(p, plen); + if (n < 1 || n > 65535) { + ngx_log_error(NGX_LOG_ERR, log, 0, + "client init, parse peer %V error", peer); + goto clear; + } + s->port = n; + } + + /* parse local */ + if (local && local->len) { + s->peer.local = ngx_pcalloc(s->pool, sizeof(ngx_addr_t)); + if (s->peer.local == NULL) { + goto clear; + } + + rc = ngx_parse_addr_port(s->pool, s->peer.local, peer->data, peer->len); + if (rc != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, log, 0, "invalid local address \"%V\"", + local); + goto clear; + } + + s->peer.local->name.data = ngx_pcalloc(s->pool, sizeof(local->len)); + if (s->peer.local->name.data == NULL) { + goto clear; + } + ngx_memcpy(s->peer.local->name.data, local->data, local->len); + s->peer.local->name.len = local->len; + } + + /* set default */ + s->connect_timeout = ccf->connect_timeout; + s->send_timeout = ccf->send_timeout; + s->postpone_output = ccf->postpone_output; + s->dynamic_resolver = ccf->dynamic_resolver; + s->tcp_nodelay = ccf->tcp_nodelay; + s->tcp_nopush = ccf->tcp_nopush; + + /* set peer */ + s->peer.log = &s->log; + s->peer.get = ngx_client_get_peer; + s->peer.free = ngx_client_free_peer; + s->peer.type = udp ? SOCK_DGRAM : SOCK_STREAM; + s->peer.log_error = NGX_ERROR_INFO; + + return s; + +clear: + NGX_DESTROY_POOL(pool); + + return NULL; +} + + +void +ngx_client_connect(ngx_client_session_t *s) +{ + s->log.action = "resolving"; + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, &s->log, 0, "nginx client connect %V", + &s->server); + + /* start connect */ + if (s->dynamic_resolver) { + ngx_dynamic_resolver_start_resolver(&s->server, + ngx_client_connect_server, s); + } else { + ngx_event_resolver_start_resolver(&s->server, + ngx_client_resolver_server, s); + } +} + + +ngx_int_t +ngx_client_write(ngx_client_session_t *s, ngx_chain_t *out) +{ + off_t size; + ngx_uint_t last, flush, sync; + ngx_chain_t *cl, *ln, **ll, *chain; + ngx_connection_t *c; + ngx_event_t *wev; + + c = s->peer.connection; + wev = c->write; + + if (c->error) { + return NGX_ERROR; + } + + size = 0; + flush = 0; + sync = 0; + last = 0; + ll = &s->out; + + /* find the size, the flush point and the last link of the saved chain */ + + for (cl = s->out; cl; cl = cl->next) { + ll = &cl->next; + + ngx_log_debug7(NGX_LOG_DEBUG_EVENT, &s->log, 0, + "nginx client write, write old buf t:%d f:%d %p, " + "pos %p, size: %z file: %O, size: %O", + cl->buf->temporary, cl->buf->in_file, + cl->buf->start, cl->buf->pos, + cl->buf->last - cl->buf->pos, + cl->buf->file_pos, + cl->buf->file_last - cl->buf->file_pos); + +#if 1 + if (ngx_buf_size(cl->buf) == 0 && !ngx_buf_special(cl->buf)) { + ngx_log_error(NGX_LOG_ALERT, &s->log, 0, + "nginx client write, zero size buf in writer " + "t:%d r:%d f:%d %p %p-%p %p %O-%O", + cl->buf->temporary, + cl->buf->recycled, + cl->buf->in_file, + cl->buf->start, + cl->buf->pos, + cl->buf->last, + cl->buf->file, + cl->buf->file_pos, + cl->buf->file_last); + + ngx_debug_point(); + return NGX_ERROR; + } +#endif + + size += ngx_buf_size(cl->buf); + + if (cl->buf->flush || cl->buf->recycled) { + flush = 1; + } + + if (cl->buf->sync) { + sync = 1; + } + + if (cl->buf->last_buf) { + last = 1; + } + } + + /* add the new chain to the existent one */ + + for (ln = out; ln; ln = ln->next) { + cl = ngx_alloc_chain_link(s->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + cl->buf = ln->buf; + *ll = cl; + ll = &cl->next; + + ngx_log_debug7(NGX_LOG_DEBUG_EVENT, &s->log, 0, + "nginx client write, write new buf t:%d f:%d %p, " + "pos %p, size: %z file: %O, size: %O", + cl->buf->temporary, cl->buf->in_file, + cl->buf->start, cl->buf->pos, + cl->buf->last - cl->buf->pos, + cl->buf->file_pos, + cl->buf->file_last - cl->buf->file_pos); + +#if 1 + if (ngx_buf_size(cl->buf) == 0 && !ngx_buf_special(cl->buf)) { + ngx_log_error(NGX_LOG_ALERT, &s->log, 0, + "nginx client write, zero size buf in writer " + "t:%d r:%d f:%d %p %p-%p %p %O-%O", + cl->buf->temporary, + cl->buf->recycled, + cl->buf->in_file, + cl->buf->start, + cl->buf->pos, + cl->buf->last, + cl->buf->file, + cl->buf->file_pos, + cl->buf->file_last); + + ngx_debug_point(); + return NGX_ERROR; + } +#endif + + size += ngx_buf_size(cl->buf); + + if (cl->buf->flush || cl->buf->recycled) { + flush = 1; + } + + if (cl->buf->sync) { + sync = 1; + } + + if (cl->buf->last_buf) { + last = 1; + } + } + + *ll = NULL; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, &s->log, 0, "nginx client write, " + "http write filter: l:%ui f:%ui s:%O", last, flush, size); + + /* + * avoid the output if there are no last buf, no flush point, + * there are the incoming bufs and the size of all bufs + * is smaller than "postpone_output" directive + */ + + if (!last && !flush && out && size < (off_t) s->postpone_output) { + return NGX_OK; + } + + if (size == 0 && !(last && c->need_last_buf)) { + if (last || flush || sync) { + for (cl = s->out; cl; /* void */) { + ln = cl; + cl = cl->next; + ngx_free_chain(s->pool, ln); + } + + s->out = NULL; + + return NGX_OK; + } + + ngx_log_error(NGX_LOG_ALERT, &s->log, 0, + "nginx client write, the output chain is empty"); + + ngx_debug_point(); + + return NGX_ERROR; + } + + chain = c->send_chain(c, s->out, 0); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, &s->log, 0, + "nginx client write %p", chain); + + if (chain == NGX_CHAIN_ERROR) { + c->error = 1; + ngx_log_error(NGX_LOG_ERR, &s->log, ngx_errno, "write error"); + return NGX_ERROR; + } + + for (cl = s->out; cl && cl != chain; /* void */) { + ln = cl; + cl = cl->next; + ngx_free_chain(s->pool, ln); + } + + s->out = chain; + + if (chain) { + NGX_ADD_TIMER(c->write, s->send_timeout, + offsetof(ngx_connection_t, number)); + if (ngx_handle_write_event(c->write, 0) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_AGAIN; + } + + if (wev->active) { /* if NGX_OK, del write notification */ + if (ngx_del_event(wev, NGX_WRITE_EVENT, 0) != NGX_OK) { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +ngx_int_t +ngx_client_read(ngx_client_session_t *s, ngx_buf_t *b) +{ + ngx_connection_t *c; + ngx_int_t n; + + if (s == NULL || b == NULL) { + return NGX_ERROR; + } + + if (b->last == b->end) { + return NGX_DECLINED; + } + + c = s->peer.connection; + + n = c->recv(c, b->last, b->end - b->last); + + if (n == 0) { + ngx_log_error(NGX_LOG_ERR, &s->log, ngx_errno, "server closed"); + return 0; + } + + if (n == NGX_ERROR) { + ngx_log_error(NGX_LOG_ERR, &s->log, ngx_errno, "read error"); + return NGX_ERROR; + } + + if (n == NGX_AGAIN) { + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_AGAIN; + } + + b->last += n; + s->recv += n; + + return n; +} + + +void +ngx_client_set_keepalive(ngx_client_session_t *s) +{ + ngx_pool_t *pool; + + if (s->closed) { + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_CORE, &s->log, 0, + "nginx client set keepalive"); + + ngx_client_reusable_connection(s); + + pool = s->pool; + NGX_DESTROY_POOL(pool); +} + + +static void +ngx_client_close_handler(ngx_event_t *ev) +{ + ngx_client_session_t *s; + ngx_pool_t *pool; + ngx_client_closed_pt closed; + + s = ev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_CORE, &s->log, 0, "nginx client close"); + + if (s->client_closed) { + closed = s->client_closed; + s->client_closed = NULL; + + closed(s); + } + + ngx_client_close_connection(s); + + pool = s->pool; + NGX_DESTROY_POOL(pool); /* s alloc from pool */ +} + + +void +ngx_client_close(ngx_client_session_t *s) +{ + ngx_event_t *e; + + if (s->closed) { + return; + } + + s->log.action = "close"; + + s->closed = 1; + + e = &s->close; + e->data = s; + e->handler = ngx_client_close_handler; + e->log = &s->log; + + ngx_post_event(e, &ngx_posted_events); +} + + +ngx_chain_t * +ngx_client_state(ngx_http_request_t *r, unsigned detail) +{ + ngx_client_conf_t *ccf; + ngx_chain_t *cl; + ngx_buf_t *b; + ngx_map_node_t *node; + ngx_client_pool_t *pool; + size_t len, len1; + ngx_uint_t n; + + ccf = (ngx_client_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_client_module); + + len = sizeof("##########ngx client connection pool##########\n") - 1 + + sizeof("ngx_client_pool nalloc node: \n") - 1 + NGX_OFF_T_LEN + + sizeof("ngx_client_pool nfree node: \n") - 1 + NGX_OFF_T_LEN + + sizeof("ngx_client_pool idle connection: \n") - 1 + NGX_OFF_T_LEN; + + len1 = 0; + + /* node for create pool */ + if (detail) { + n = ccf->nalloc - ccf->nfree; + /* " addr:port: qsize\n" */ + len1 = 4 + NGX_SOCKADDR_STRLEN + 2 + NGX_OFF_T_LEN + 1; + len += len1 * n; + } + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NULL; + } + cl->next = NULL; + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NULL; + } + cl->buf = b; + + b->last = ngx_snprintf(b->last, len, + "##########ngx client connection pool##########\n" + "ngx_client_pool nalloc node: %ui\n" + "ngx_client_pool nfree node: %ui\n" + "ngx_client_pool idle connection: %ui\n", + ccf->nalloc, ccf->nfree, ccf->idle_connction); + + if (detail) { + for (node = ngx_map_begin(&ccf->client_pools); node; + node = ngx_map_next(node)) + { + /* m is first element of ngx_poold_node_t */ + pool = (ngx_client_pool_t *) node; + b->last = ngx_snprintf(b->last, len1, " %V: %ui\n", + &pool->paddr, pool->qsize); + } + } + + return cl; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/ngx_client.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/ngx_client.h new file mode 100644 index 0000000000..7b67b7b81e --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/ngx_client.h @@ -0,0 +1,165 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#ifndef _NGX_CLIENT_H_INCLUDED_ +#define _NGX_CLIENT_H_INCLUDED_ + + +#include +#include +#include +#include + + +typedef struct ngx_client_session_s ngx_client_session_t; + +typedef void (* ngx_client_connect_pt)(ngx_client_session_t *s); +typedef void (* ngx_client_recv_pt)(ngx_client_session_t *s); +typedef void (* ngx_client_send_pt)(ngx_client_session_t *s); +typedef void (* ngx_client_closed_pt)(ngx_client_session_t *s); + + +struct ngx_client_session_s { + ngx_peer_connection_t peer; + ngx_str_t server; /* server original address */ + in_port_t port; /* server port */ + + ngx_connection_t *connection; + + ngx_pool_t *pool; + ngx_log_t log; + + void *data; /* save ctx for callback */ + + ngx_chain_t *out; /* save data unsend */ + + /* configured part */ + + /* timer for connecting to server */ + ngx_msec_t connect_timeout; + + /* timer for sending buffer full */ + ngx_msec_t send_timeout; + + /* + * data will be postponed until nginx has at least + * postpone_output bytes of data to send + */ + size_t postpone_output; + + /* use dynamic resolver mechanism for resolving domain */ + unsigned dynamic_resolver:1; + + unsigned tcp_nodelay:1; /* TCP_NODELAY */ + unsigned tcp_nopush:1; /* TCP_CORK */ + + /* runtime part */ + + size_t recv; /* client recv bytes */ + + unsigned connected:1; /* client connected to server */ + unsigned closed:1; /* client has been closed */ + + ngx_event_t close; /* for async close */ + + /* callback */ + + ngx_client_connect_pt client_connected; /* connect successd */ + ngx_client_recv_pt client_recv; /* recv msg from peer */ + ngx_client_send_pt client_send; /* send msg to peer */ + ngx_client_closed_pt client_closed; /* finalize connection */ +}; + + +/* + * create a client session + * + * return value: + * return client session for successd, return NULL for failed + * paras: + * peer: server address and port, address could be domain or ip + * local: set if need to bind local address, or set NULL + * udp: set 1, use udp, set 0, use tcp + * log: for logging error when create client session failed + */ +ngx_client_session_t *ngx_client_create(ngx_str_t *peer, ngx_str_t *local, + ngx_flag_t udp, ngx_log_t *log); + + +/* + * connect to client server, should use client session created by + * ngx_client_create. before connect to server, user can set paras in + * configured part. + * + * return value: + * void + * paras: + * s: client session created by ngx_client_create + */ +void ngx_client_connect(ngx_client_session_t *s); + + +/* + * send data to server + * + * return value: + * NGX_ERROR: write error, client session will be closed + * NGX_AGAIN: data not sent completely, it will save in client session out + * NGX_OK: data sent completely + * paras: + * s: client session + * out: data for sending + */ +ngx_int_t ngx_client_write(ngx_client_session_t *s, ngx_chain_t *out); + + +/* + * read data from server + * + * return value: + * NGX_ERROR: read error, client session will be closed + * NGX_DECLINED: buf for receiving data is full + * NGX_AGAIN: no data for reading + * 0: server closed + * >0: bytes read into buffer + * paras: + * s: client session + * b: buffer for receiving data + */ +ngx_int_t ngx_client_read(ngx_client_session_t *s, ngx_buf_t *b); + + +/* + * keepalive client connection, and destroy session + * if use client connect the same ip:port, + * new client session will reuse the connection + * + * return value: + * void + * paras: + * s: client session + */ +void ngx_client_set_keepalive(ngx_client_session_t *s); + + +/* + * close client session + * + * return value: + * void + * paras: + * s: client session + */ +void ngx_client_close(ngx_client_session_t *s); + + +/* + * paras: + * r: http request to query status of client + */ +ngx_chain_t *ngx_client_state(ngx_http_request_t *r, unsigned detail); + + +#endif diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/ngx_http_client.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/ngx_http_client.c new file mode 100644 index 0000000000..3a94803c53 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/ngx_http_client.c @@ -0,0 +1,1889 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#include "ngx_http_client.h" +#include "ngx_rbuf.h" +#include "ngx_poold.h" +#include "ngx_map.h" +#include "ngx_timerd.h" + + +static void *ngx_http_client_module_create_conf(ngx_cycle_t *cycle); +static char *ngx_http_client_module_init_conf(ngx_cycle_t *cycle, void *conf); + +/* headers in */ +static ngx_int_t ngx_http_client_process_header_line(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset); +static ngx_int_t ngx_http_client_process_content_length(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset); +static ngx_int_t ngx_http_client_process_connection(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset); +static ngx_int_t + ngx_http_client_process_transfer_encoding(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset); + +/* headers out */ +static void ngx_http_client_host(ngx_http_request_t *r, ngx_str_t *value); +static void ngx_http_client_user_agent(ngx_http_request_t *r, + ngx_str_t *value); +static void ngx_http_client_connection(ngx_http_request_t *r, ngx_str_t *value); +static void ngx_http_client_accept(ngx_http_request_t *r, ngx_str_t *value); +static void ngx_http_client_date(ngx_http_request_t *r, ngx_str_t *value); + + +/* for http response */ +typedef struct { + ngx_list_t headers; + + ngx_uint_t http_version; + ngx_uint_t status_n; + ngx_str_t status_line; + + ngx_table_elt_t *status; + ngx_table_elt_t *date; + ngx_table_elt_t *server; + ngx_table_elt_t *connection; + + ngx_table_elt_t *expires; + ngx_table_elt_t *etag; + ngx_table_elt_t *x_accel_expires; + ngx_table_elt_t *x_accel_redirect; + ngx_table_elt_t *x_accel_limit_rate; + + ngx_table_elt_t *content_type; + ngx_table_elt_t *content_length; + + ngx_table_elt_t *last_modified; + ngx_table_elt_t *location; + ngx_table_elt_t *accept_ranges; + ngx_table_elt_t *www_authenticate; + ngx_table_elt_t *transfer_encoding; + +#if (NGX_HTTP_GZIP) + ngx_table_elt_t *content_encoding; +#endif + + off_t content_length_n; + + unsigned connection_type:2; + unsigned chunked:1; +} ngx_http_client_headers_in_t; + + +typedef struct { + ngx_map_node_t node; + ngx_str_t key; + ngx_str_t value; +} ngx_http_client_header_out_t; + + +typedef struct { + ngx_array_t headers; /* ngx_http_client_header_out_t */ + ngx_map_t hash; /* find header by header */ +} ngx_http_client_headers_out_t; + + +typedef struct { + ngx_client_session_t *session; + void *request; + + /* Request */ + ngx_request_url_t url; + + /* Response */ + ngx_http_status_t status; + ngx_http_chunked_t chunked; + ngx_int_t length; + + /* bufs */ + ngx_chain_t *in; + ngx_buf_t *buffer; /* status line buf */ + + /* config */ + ngx_msec_t header_timeout; + size_t header_buffer_size; + + /* runtime */ + off_t rbytes; /* read bytes */ + off_t wbytes; /* write bytes */ + + ngx_http_client_headers_in_t headers_in; + ngx_http_client_headers_out_t headers_out; + + ngx_http_client_handler_pt read_handler; + ngx_http_client_handler_pt write_handler; +} ngx_http_client_ctx_t; + + +static ngx_str_t ngx_http_client_method[] = { + ngx_string("GET"), + ngx_string("HEAD"), + ngx_string("POST"), + ngx_string("PUT"), + ngx_string("DELETE"), + ngx_string("MKCOL"), + ngx_string("COPY"), + ngx_string("MOVE"), + ngx_string("OPTIONS"), + ngx_string("PROPFIND"), + ngx_string("PROPPATCH"), + ngx_string("LOCK"), + ngx_string("UNLOCK"), + ngx_string("PATCH"), + ngx_string("TRACE") +}; + + +static ngx_str_t ngx_http_client_version[] = { + ngx_string("HTTP/0.9"), /* not support, will not use */ + ngx_string("HTTP/1.0"), + ngx_string("HTTP/1.1"), + ngx_string("HTTP/2.0") +}; + + +#define NGX_HTTP_CLIENT_CONNECTION_CLOSE 1 +#define NGX_HTTP_CLIENT_CONNECTION_KEEP_ALIVE 2 +#define NGX_HTTP_CLIENT_CONNECTION_UPGRADE 3 + + +typedef void (*ngx_http_client_fill_header_pt)(ngx_http_request_t *r, + ngx_str_t *value); + +typedef struct { + ngx_str_t name; + ngx_http_client_fill_header_pt handler; +} ngx_http_client_fill_header_t; + + +typedef struct { + ngx_hash_t headers_in_hash; + + /* wait for response header timeout */ + ngx_msec_t header_timeout; + size_t header_buffer_size; + size_t body_buffer_size; +} ngx_http_client_conf_t; + + +ngx_http_header_t ngx_http_client_headers_in[] = { + + { ngx_string("Status"), offsetof(ngx_http_client_headers_in_t, status), + ngx_http_client_process_header_line }, + + { ngx_string("Date"), offsetof(ngx_http_client_headers_in_t, date), + ngx_http_client_process_header_line }, + + { ngx_string("Server"), offsetof(ngx_http_client_headers_in_t, server), + ngx_http_client_process_header_line }, + + { ngx_string("Connection"), + offsetof(ngx_http_client_headers_in_t, connection), + ngx_http_client_process_connection }, + + { ngx_string("Expires"), offsetof(ngx_http_client_headers_in_t, expires), + ngx_http_client_process_header_line }, + + { ngx_string("ETag"), offsetof(ngx_http_client_headers_in_t, etag), + ngx_http_client_process_header_line }, + + { ngx_string("X-Accel-Expires"), + offsetof(ngx_http_client_headers_in_t, x_accel_expires), + ngx_http_client_process_header_line }, + + { ngx_string("X-Accel-Redirect"), + offsetof(ngx_http_client_headers_in_t, x_accel_redirect), + ngx_http_client_process_header_line }, + + { ngx_string("X-Accel-Limit-Rate"), + offsetof(ngx_http_client_headers_in_t, x_accel_limit_rate), + ngx_http_client_process_header_line }, + + { ngx_string("Content-Type"), + offsetof(ngx_http_client_headers_in_t, content_type), + ngx_http_client_process_header_line }, + + { ngx_string("Content-Length"), + offsetof(ngx_http_client_headers_in_t, content_length), + ngx_http_client_process_content_length }, + + { ngx_string("Last-Modified"), + offsetof(ngx_http_client_headers_in_t, last_modified), + ngx_http_client_process_header_line }, + + { ngx_string("Location"), offsetof(ngx_http_client_headers_in_t, location), + ngx_http_client_process_header_line }, + + { ngx_string("Accept-Ranges"), + offsetof(ngx_http_client_headers_in_t, accept_ranges), + ngx_http_client_process_header_line }, + + { ngx_string("WWW-Authenticate"), + offsetof(ngx_http_client_headers_in_t, www_authenticate), + ngx_http_client_process_header_line }, + + { ngx_string("Transfer-Encoding"), + offsetof(ngx_http_client_headers_in_t, transfer_encoding), + ngx_http_client_process_transfer_encoding }, + +#if (NGX_HTTP_GZIP) + { ngx_string("Content-Encoding"), + offsetof(ngx_http_client_headers_in_t, content_encoding), + ngx_http_client_process_header_line }, +#endif + + { ngx_null_string, 0, NULL } +}; + + +ngx_http_client_fill_header_t ngx_http_client_default_header[] = { + { ngx_string("Host"), ngx_http_client_host }, + { ngx_string("User-Agent"), ngx_http_client_user_agent }, + { ngx_string("Connection"), ngx_http_client_connection }, + { ngx_string("Accept"), ngx_http_client_accept }, + { ngx_string("Date"), ngx_http_client_date }, + { ngx_null_string, NULL } +}; + + +static ngx_command_t ngx_http_client_commands[] = { + + { ngx_string("header_timeout"), + NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + 0, + offsetof(ngx_http_client_conf_t, header_timeout), + NULL }, + + { ngx_string("header_buffer_size"), + NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + 0, + offsetof(ngx_http_client_conf_t, header_buffer_size), + NULL }, + + { ngx_string("body_buffer_size"), + NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + 0, + offsetof(ngx_http_client_conf_t, body_buffer_size), + NULL }, + + ngx_null_command +}; + + +static ngx_core_module_t ngx_http_client_module_ctx = { + ngx_string("http_client"), + ngx_http_client_module_create_conf, + ngx_http_client_module_init_conf +}; + + +ngx_module_t ngx_http_client_module = { + NGX_MODULE_V1, + &ngx_http_client_module_ctx, /* module context */ + ngx_http_client_commands, /* module directives */ + NGX_CORE_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void * +ngx_http_client_module_create_conf(ngx_cycle_t *cycle) +{ + ngx_http_client_conf_t *hccf; + + hccf = ngx_pcalloc(cycle->pool, sizeof(ngx_http_client_conf_t)); + if (hccf == NULL) { + return NULL; + } + + hccf->header_timeout = NGX_CONF_UNSET_MSEC; + hccf->header_buffer_size = NGX_CONF_UNSET_SIZE; + hccf->body_buffer_size = NGX_CONF_UNSET_SIZE; + + return hccf; +} + + +static char * +ngx_http_client_module_init_conf(ngx_cycle_t *cycle, void *conf) +{ + ngx_http_client_conf_t *hccf = conf; + + ngx_array_t headers_in; + ngx_hash_key_t *hk; + ngx_hash_init_t hash; + ngx_http_header_t *header; + + /* upstream_headers_in_hash */ + + if (ngx_array_init(&headers_in, cycle->pool, 32, sizeof(ngx_hash_key_t)) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + for (header = ngx_http_client_headers_in; header->name.len; header++) { + hk = ngx_array_push(&headers_in); + if (hk == NULL) { + return NGX_CONF_ERROR; + } + + hk->key = header->name; + hk->key_hash = ngx_hash_key_lc(header->name.data, header->name.len); + hk->value = header; + } + + hash.hash = &hccf->headers_in_hash; + hash.key = ngx_hash_key_lc; + hash.max_size = 512; + hash.bucket_size = ngx_align(64, ngx_cacheline_size); + hash.name = "upstream_headers_in_hash"; + hash.pool = cycle->pool; + hash.temp_pool = NULL; + + if (ngx_hash_init(&hash, headers_in.elts, headers_in.nelts) != NGX_OK) { + return NGX_CONF_ERROR; + } + + ngx_conf_init_msec_value(hccf->header_timeout, 10000); + ngx_conf_init_size_value(hccf->header_buffer_size, ngx_pagesize); + ngx_conf_init_size_value(hccf->body_buffer_size, ngx_pagesize); + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_http_client_process_header_line(ngx_http_request_t *r, ngx_table_elt_t *h, + ngx_uint_t offset) +{ + ngx_table_elt_t **ph; + ngx_http_client_ctx_t *ctx; + + ctx = r->ctx[0]; + + ph = (ngx_table_elt_t **) ((char *) &ctx->headers_in + offset); + + if (*ph == NULL) { + *ph = h; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_client_process_content_length(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset) +{ + ngx_http_client_ctx_t *ctx; + + ctx = r->ctx[0]; + + if (ctx->headers_in.content_length != NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "duplicate Content-Length header, %V: %V", &h->key, &h->value); + return NGX_ERROR; + } + + ctx->headers_in.content_length = h; + ctx->headers_in.content_length_n = ngx_atoof(h->value.data, h->value.len); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_client_process_connection(ngx_http_request_t *r, ngx_table_elt_t *h, + ngx_uint_t offset) +{ + ngx_http_client_ctx_t *ctx; + + ctx = r->ctx[0]; + + ctx->headers_in.connection = h; + + if (ngx_strcasestrn(h->value.data, "close", 5)) { + ctx->headers_in.connection_type = NGX_HTTP_CLIENT_CONNECTION_CLOSE; + } else if (ngx_strcasestrn(h->value.data, "keep-alive", 10)) { + ctx->headers_in.connection_type = NGX_HTTP_CLIENT_CONNECTION_KEEP_ALIVE; + } else if (ngx_strcasestrn(h->value.data, "upgrade", 7)) { + ctx->headers_in.connection_type = NGX_HTTP_CLIENT_CONNECTION_UPGRADE; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_client_process_transfer_encoding(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset) +{ + ngx_http_client_ctx_t *ctx; + + ctx = r->ctx[0]; + + ctx->headers_in.transfer_encoding = h; + + if (ngx_strlcasestrn(h->value.data, h->value.data + h->value.len, + (u_char *) "chunked", 7 - 1) + != NULL) + { + ctx->headers_in.chunked = 1; + } + + return NGX_OK; +} + + +static void +ngx_http_client_host(ngx_http_request_t *r, ngx_str_t *value) +{ + ngx_http_client_ctx_t *ctx; + + ctx = r->ctx[0]; + + value->data = ctx->url.host.data; + value->len = ctx->url.host.len; +} + + +static void +ngx_http_client_user_agent(ngx_http_request_t *r, ngx_str_t *value) +{ + value->data = (u_char *) NGINX_VER; + value->len = sizeof(NGINX_VER) - 1; +} + + +static void +ngx_http_client_connection(ngx_http_request_t *r, ngx_str_t *value) +{ + if (r->http_version < NGX_HTTP_CLIENT_VERSION_11) { + value->data = (u_char *) "close"; + value->len = sizeof("close") - 1; + } else { + value->len = 0; + } +} + + +static void +ngx_http_client_accept(ngx_http_request_t *r, ngx_str_t *value) +{ + value->data = (u_char *) "*/*"; + value->len = sizeof("*/*") - 1; +} + + +static void +ngx_http_client_date(ngx_http_request_t *r, ngx_str_t *value) +{ + value->data = ngx_cached_http_time.data; + value->len = ngx_cached_http_time.len; +} + + +static void +ngx_http_client_free_request(ngx_http_request_t *hcr) +{ + ngx_http_client_ctx_t *ctx; + ngx_client_session_t *s; + ngx_pool_t *pool; + ngx_http_cleanup_t *cln; + + if (hcr->pool == NULL) { + return; + } + + ctx = hcr->ctx[0]; + s = ctx->session; + + if (ctx->request) { + cln = hcr->cleanup; + hcr->cleanup = NULL; + + while (cln) { + if (cln->handler) { + cln->handler(cln->data); + } + + cln = cln->next; + } + } + + if (ctx->in) { + ngx_put_chainbufs(ctx->in); + ctx->in = NULL; + } + + if (s) { + s->client_recv = NULL; + s->client_send = NULL; + s->client_closed = NULL; + s->out = NULL; + } + + pool = hcr->pool; + hcr->pool = NULL; + + NGX_DESTROY_POOL(pool); +} + + +static void +ngx_http_client_close_handler(ngx_client_session_t *s) +{ + ngx_http_request_t *r; + + r = s->data; + + ngx_http_client_free_request(r); +} + + +static void +ngx_http_client_discarded_body(ngx_http_request_t *r) +{ + ngx_http_client_ctx_t *ctx; + ngx_chain_t *cl; + ngx_int_t rc; + + ctx = r->ctx[0]; + + rc = ngx_http_client_read_body(r, &cl); + + if (rc == 0 || rc == NGX_ERROR) { // http client close + ngx_http_client_finalize_request(r, 1); + return; + } + + // if detach, all http response receive, set keepalive + if (rc == NGX_DONE) { + ngx_http_client_finalize_request(r, 0); + return; + } + + // NGX_AGAIN + + if (ctx->in) { // make rbuf recycle immediately + ngx_put_chainbufs(ctx->in); + ctx->in = NULL; + } +} + + +static void +ngx_http_client_read_handler(ngx_client_session_t *s) +{ + ngx_http_request_t *r; + ngx_http_client_ctx_t *ctx; + + r = s->data; + ctx = r->ctx[0]; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http client, read handler"); + + if (ctx->request && ctx->read_handler) { + ctx->read_handler(ctx->request, r); + } else { + ngx_http_client_discarded_body(r); + } +} + + +static void +ngx_http_client_process_header(ngx_client_session_t *s) +{ + ngx_http_request_t *r; + ngx_http_client_ctx_t *ctx; + ngx_buf_t *b; + ngx_int_t n, rc; + ngx_table_elt_t *h; + ngx_http_header_t *hh; + ngx_http_client_conf_t *hccf; + ngx_event_t *rev; + + hccf = (ngx_http_client_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_http_client_module); + + r = s->data; + ctx = r->ctx[0]; + rev = r->connection->read; + + b = ctx->buffer; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http client, process header"); + + for (;;) { + + rc = ngx_http_parse_header_line(r, b, 1); + + if (rc == NGX_OK) { + + /* a header line has been parsed successfully */ + + h = ngx_list_push(&ctx->headers_in.headers); + if (h == NULL) { + goto error; + } + + h->hash = r->header_hash; + + h->key.len = r->header_name_end - r->header_name_start; + h->value.len = r->header_end - r->header_start; + + h->key.data = ngx_pnalloc(r->pool, + h->key.len + 1 + h->value.len + 1 + h->key.len); + if (h->key.data == NULL) { + goto error; + } + + h->value.data = h->key.data + h->key.len + 1; + h->lowcase_key = h->key.data + h->key.len + 1 + h->value.len + 1; + + ngx_memcpy(h->key.data, r->header_name_start, h->key.len); + h->key.data[h->key.len] = '\0'; + ngx_memcpy(h->value.data, r->header_start, h->value.len); + h->value.data[h->value.len] = '\0'; + + if (h->key.len == r->lowcase_index) { + ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len); + + } else { + ngx_strlow(h->lowcase_key, h->key.data, h->key.len); + } + + hh = ngx_hash_find(&hccf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { + goto error; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http server header: \"%V: %V\"", &h->key, &h->value); + + continue; + } + + if (rc == NGX_HTTP_PARSE_HEADER_DONE) { + + /* a whole header has been parsed successfully */ + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http server header done"); + + /* + * if no "Server" and "Date" in header line, + * then add the special empty headers + */ + + if (ctx->headers_in.server == NULL) { + h = ngx_list_push(&ctx->headers_in.headers); + if (h == NULL) { + goto error; + } + + h->hash = ngx_hash(ngx_hash(ngx_hash(ngx_hash( + ngx_hash('s', 'e'), 'r'), 'v'), 'e'), 'r'); + + ngx_str_set(&h->key, "Server"); + ngx_str_null(&h->value); + h->lowcase_key = (u_char *) "server"; + } + + if (ctx->headers_in.date == NULL) { + h = ngx_list_push(&ctx->headers_in.headers); + if (h == NULL) { + goto error; + } + + h->hash = ngx_hash(ngx_hash(ngx_hash('d', 'a'), 't'), 'e'); + + ngx_str_set(&h->key, "Date"); + ngx_str_null(&h->value); + h->lowcase_key = (u_char *) "date"; + } + + /* clear content length if response is chunked */ + + if (ctx->headers_in.chunked) { + ctx->headers_in.content_length_n = -1; + } + + ctx->length = ctx->headers_in.content_length_n; + + break; + } + + if (rc == NGX_AGAIN) { + n = ngx_client_read(s, b); + + if (n == NGX_ERROR || n == 0) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, ngx_errno, + "http client, process header read error"); + ngx_http_client_finalize_request(r, 1); + return; + } + + if (n == NGX_AGAIN) { + if (!rev->timer_set) { + NGX_ADD_TIMER(rev, ctx->header_timeout, + offsetof(ngx_connection_t, number)); + } + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_client_finalize_request(r, 1); + return; + } + + return; + } + + /* NGX_OK */ + ctx->rbytes += n; + + continue; + } + + /* there was error while a header line parsing */ + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "http client, http server sent invalid header"); + + goto error; + } + + s->client_recv = ngx_http_client_read_handler; + + if (rev->timer_set) { + NGX_DEL_TIMER(rev, r->connection->number); + } + + return ngx_http_client_read_handler(s); + +error: + ngx_http_client_finalize_request(r, 1); +} + + +static void +ngx_http_client_process_status_line(ngx_client_session_t *s) +{ + ngx_http_request_t *r; + ngx_http_client_ctx_t *ctx; + ngx_buf_t *b; + ngx_int_t n, rc; + ngx_event_t *rev; + + r = s->data; + ctx = r->ctx[0]; + rev = r->connection->read; + + b = ctx->buffer; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http client, process status line"); + + for (;;) { + rc = ngx_http_parse_status_line(r, b, &ctx->status); + + if (rc == NGX_AGAIN) { + n = ngx_client_read(s, b); + + if (n == NGX_ERROR || n == 0) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, ngx_errno, + "http client, process status line read error"); + ngx_http_client_finalize_request(r, 1); + return; + } + + if (n == NGX_AGAIN) { + if (!rev->timer_set) { + NGX_ADD_TIMER(rev, ctx->header_timeout, + offsetof(ngx_connection_t, number)); + } + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_client_finalize_request(r, 1); + return; + } + + return; + } + + /* NGX_OK */ + ctx->rbytes += n; + + continue; + } + + if (rc == NGX_ERROR) { + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "http client, recv no valid HTTP/1.0 header"); + + r->http_version = NGX_HTTP_VERSION_9; + } + + /* NGX_OK */ + break; + } + + ctx->headers_in.http_version = ctx->status.http_version; + ctx->headers_in.status_n = ctx->status.code; + + ctx->headers_in.status_line.len = ctx->status.end - ctx->status.start; + ctx->headers_in.status_line.data = ngx_pcalloc(r->connection->pool, + ctx->headers_in.status_line.len); + if (ctx->headers_in.status_line.data == NULL) { + ngx_http_client_finalize_request(r, 1); + return; + } + ngx_memcpy(ctx->headers_in.status_line.data, ctx->status.start, + ctx->headers_in.status_line.len); + + s->client_recv = ngx_http_client_process_header; + return ngx_http_client_process_header(s); +} + + +static void +ngx_http_client_wait_response_handler(ngx_client_session_t *s) +{ + ngx_http_request_t *r; + ngx_http_client_ctx_t *ctx; + ngx_buf_t *b; + ngx_connection_t *c; + size_t size; + ngx_int_t n; + ngx_event_t *rev; + + r = s->data; + c = r->connection; + ctx = r->ctx[0]; + size = ctx->header_buffer_size; + rev = s->connection->read; + + b = ctx->buffer; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http client, process response handler"); + + if (b == NULL) { + b = ngx_create_temp_buf(c->pool, size); + if (b == NULL) { + ngx_http_client_finalize_request(r, 1); + return; + } + + ctx->buffer = b; + } else if (b->start == NULL) { + + b->start = ngx_pcalloc(c->pool, size); + if (b->start == NULL) { + ngx_http_client_finalize_request(r, 1); + return; + } + + b->last = b->pos = b->start; + b->end = b->last + size; + } + + n = ngx_client_read(s, b); + /* + * if NGX_ERROR or no bytes read + * if ngx_client_read return NGX_ERROR, s will reconnect + * if 0, ngx_http_client_wait_response_handler + * will called next read event triggered + */ + if (n == NGX_ERROR || n == 0) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, ngx_errno, + "http client, process response handler read error, rc :%i", n); + ngx_http_client_finalize_request(r, 1); + return; + } + + if (n == NGX_AGAIN) { + if (!rev->timer_set) { + NGX_ADD_TIMER(rev, ctx->header_timeout, + offsetof(ngx_connection_t, number)); + } + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_client_finalize_request(r, 1); + return; + } + + return; + } + + ctx->rbytes += n; + + s->client_recv = ngx_http_client_process_status_line; + return ngx_http_client_process_status_line(s); +} + + +static ngx_int_t +ngx_http_client_set_url(ngx_http_request_t *r, ngx_str_t *url, ngx_log_t *log) +{ + ngx_http_client_ctx_t *ctx; + ngx_client_session_t *cs; + ngx_int_t rc; + + ctx = r->ctx[0]; + + if (ctx->session) { + ngx_log_error(NGX_LOG_INFO, log, 0, "http client, url has been set"); + return NGX_OK; + } + + r->request_line.data = ngx_pcalloc(r->pool, url->len); + if (r->request_line.data == NULL) { + return NGX_ERROR; + } + ngx_memcpy(r->request_line.data, url->data, url->len); + r->request_line.len = url->len; + + rc = ngx_parse_request_url(&ctx->url, &r->request_line); + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + /* create session */ + cs = ngx_client_create(&ctx->url.host, NULL, 0, log); + if (cs == NULL) { + return NGX_ERROR; + } + + cs->port = ngx_request_port(&ctx->url.scheme, &ctx->url.port); + + ctx->session = cs; + cs->data = r; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_client_add_header(ngx_http_request_t *r, ngx_str_t *header, + ngx_str_t *value) +{ + ngx_http_client_ctx_t *ctx; + ngx_http_client_header_out_t *h; + ngx_map_node_t *node; + + ctx = r->ctx[0]; + + node = ngx_map_find(&ctx->headers_out.hash, (intptr_t) header); + if (node) { // header exist + h = (ngx_http_client_header_out_t *) node; + } else { // header not exist + h = ngx_array_push(&ctx->headers_out.headers); + if (h == NULL) { + return NGX_ERROR; + } + ngx_memzero(h, sizeof(ngx_http_client_header_out_t)); + + h->key.data = ngx_pcalloc(r->pool, header->len); + if (h->key.data == NULL) { + return NGX_ERROR; + } + ngx_memcpy(h->key.data, header->data, header->len); + h->key.len = header->len; + + h->node.raw_key = (intptr_t) &h->key; + ngx_map_insert(&ctx->headers_out.hash, &h->node, 1); + } + + if (value->len == 0) { // delete header + h->value.len = 0; + return NGX_OK; + } + + // add or modify header + h->value.data = ngx_pcalloc(r->pool, value->len); + if (h->value.data == NULL) { + return NGX_ERROR; + } + ngx_memcpy(h->value.data, value->data, value->len); + h->value.len = value->len; + + return NGX_OK; +} + + +static ngx_buf_t * +ngx_http_client_create_request_buf(ngx_client_session_t *s) +{ + ngx_http_request_t *r; + ngx_http_client_ctx_t *ctx; + ngx_buf_t *b; + size_t len; + ngx_http_client_header_out_t *h; + ngx_uint_t i; + + r = s->data; + ctx = r->ctx[0]; + + /* Request Line */ + /* method */ + len = ngx_http_client_method[r->method].len + 1; /* "GET " */ + + /* path + args */ + ++len; /* "/" */ + if (ctx->url.path.len) { + /* "path" */ + len += ctx->url.path.len; + } + + if (ctx->url.args.len) { + /* "?args" */ + ++len; + len += ctx->url.args.len; + } + ++len; /* " " */ + + /* version */ + len += sizeof("HTTP/1.x") - 1 + sizeof(CRLF) - 1; + + /* Request Headers */ + /* User set headers */ + h = ctx->headers_out.headers.elts; + for (i = 0; i < ctx->headers_out.headers.nelts; ++i, ++h) { + if (h->value.len == 0) { + continue; + } + + len += h->key.len + sizeof(": ") - 1 + h->value.len + sizeof(CRLF) - 1; + } + + /* Request Headers end */ + len += sizeof(CRLF) - 1; + + /* start fill http request */ + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NULL; + } + + /* method */ + b->last = ngx_cpymem(b->last, ngx_http_client_method[r->method].data, + ngx_http_client_method[r->method].len); + *b->last++ = ' '; + + /* path + args */ + *b->last++ = '/'; + if (ctx->url.path.len) { + b->last = ngx_cpymem(b->last, ctx->url.path.data, ctx->url.path.len); + } + + if (ctx->url.args.len) { + *b->last++ = '?'; + b->last = ngx_cpymem(b->last, ctx->url.args.data, ctx->url.args.len); + } + *b->last++ = ' '; + + /* version */ + b->last = ngx_cpymem(b->last, + ngx_http_client_version[r->http_version].data, + ngx_http_client_version[r->http_version].len); + *b->last++ = CR; *b->last++ = LF; + + /* Request Headers */ + h = ctx->headers_out.headers.elts; + for (i = 0; i < ctx->headers_out.headers.nelts; ++i, ++h) { + if (h->value.len == 0) { + continue; + } + + b->last = ngx_cpymem(b->last, h->key.data, h->key.len); + *b->last++ = ':'; *b->last++ = ' '; + b->last = ngx_cpymem(b->last, h->value.data, h->value.len); + *b->last++ = CR; *b->last++ = LF; + } + + /* Request Headers end */ + *b->last++ = CR; *b->last++ = LF; + + return b; +} + + +static void +ngx_http_client_send_header(ngx_client_session_t *s) +{ + ngx_http_request_t *r; + ngx_http_client_ctx_t *ctx; + ngx_buf_t *b; + ngx_chain_t out; + ngx_event_t *rev; + + r = s->data; + ctx = r->ctx[0]; + + r->connection = s->connection; + rev = r->connection->read; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http client, send header"); + + r->connection = s->peer.connection; + + b = ngx_http_client_create_request_buf(s); + if (b == NULL) { + goto destroy; + } + b->flush = 1; + + out.buf = b; + out.next = NULL; + + /* send http request header */ + ngx_client_write(s, &out); + + /* user defined, for send body function callback */ + if (ctx->request && ctx->write_handler) { + ctx->write_handler(ctx->request, r); + } + + NGX_ADD_TIMER(rev, ctx->header_timeout, offsetof(ngx_connection_t, number)); + + return; + +destroy: + ngx_http_client_finalize_request(r, 1); +} + + +static ngx_int_t +ngx_http_client_body_length(ngx_http_request_t *r, ngx_chain_t *cl) +{ + ngx_http_client_ctx_t *ctx; + ngx_buf_t *buf; + ngx_chain_t **ll; + ngx_int_t len; + + ctx = r->ctx[0]; + + for (ll = &ctx->in; *ll; ll = &(*ll)->next); + + while (cl) { + *ll = cl; + cl = cl->next; + (*ll)->next = NULL; + + if (ctx->length != -1) { + buf = (*ll)->buf; + + len = ngx_min(buf->last - buf->pos, ctx->length); + ctx->length -= len; + + if (ctx->length == 0) { + if (cl || buf->last - buf->pos > len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "http client, read unexpected data"); + ngx_put_chainbufs(cl); + } + return NGX_DONE; + } + } + + ll = &(*ll)->next; + } + + return NGX_AGAIN; +} + + +static ngx_int_t +ngx_http_client_body_chunked(ngx_http_request_t *r, ngx_chain_t *cl) +{ + ngx_http_client_ctx_t *ctx; + ngx_http_client_conf_t *hccf; + ngx_buf_t *buf, *b; + ngx_chain_t **ll, *ln; + ngx_int_t rc; + size_t len; + + ctx = r->ctx[0]; + hccf = (ngx_http_client_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_http_client_module); + + for (ll = &ctx->in; *ll; ll = &(*ll)->next); + + while (1) { + + b = cl->buf; + rc = ngx_http_parse_chunked(r, b, &ctx->chunked); + + ngx_log_debug7(NGX_LOG_DEBUG_CORE, r->connection->log, 0, + "http client, parse chunked %p %p-%p %p, rc: %d, %O %O", + b->start, b->pos, b->last, b->end, + rc, ctx->chunked.size, ctx->chunked.length); + + if (rc == NGX_OK) { + + /* a chunk has been parsed successfully */ + + while (1) { + if (*ll == NULL) { + *ll = ngx_get_chainbuf(hccf->body_buffer_size, 1); + if (*ll == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "http client, get chainbuf failed"); + return NGX_ERROR; + } + } + + buf = (*ll)->buf; + + if (b->last - b->pos >= ctx->chunked.size) { + len = ngx_min(buf->end - buf->last, ctx->chunked.size); + } else { + len = ngx_min(buf->end - buf->last, b->last - b->pos); + } + + buf->last = ngx_cpymem(buf->last, b->pos, len); + b->pos += len; + ctx->chunked.size -= len; + + if (buf->last == buf->end) { + ll = &(*ll)->next; + } + + if (b->pos == b->last) { // current cl read over + ln = cl; + cl = cl->next; + ngx_put_chainbuf(ln); + + if (cl == NULL) { + return NGX_AGAIN; + } + + b = cl->buf; + } + + if (ctx->chunked.size == 0) { // current chunk read over + break; + } + } + + ngx_log_debug7(NGX_LOG_DEBUG_CORE, r->connection->log, 0, + "http client, parse done %p %p-%p %p, rc: %d, %O %O", + b->start, b->pos, b->last, b->end, + rc, ctx->chunked.size, ctx->chunked.length); + + continue; + } + + if (rc == NGX_AGAIN) { + ln = cl; + cl = cl->next; + ngx_put_chainbuf(ln); + + if (cl == NULL) { + return NGX_AGAIN; + } + + continue; + } + + if (rc == NGX_DONE) { + if (b->pos != b->last || cl->next) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "http client, read unexpected chunked data"); + } + ngx_put_chainbufs(cl); + + return NGX_DONE; + } + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "http client, invalid chunked response"); + + return NGX_ERROR; + } +} + + +/* create and set http request */ + +ngx_http_request_t * +ngx_http_client_create(ngx_log_t *log, ngx_uint_t method, ngx_str_t *url, + ngx_keyval_t *headers, ngx_http_client_handler_pt send_body, void *request) +{ + ngx_pool_t *pool; + ngx_http_request_t *r; + ngx_http_client_ctx_t *ctx; + ngx_http_client_conf_t *hccf; + ngx_http_client_fill_header_t *h; + ngx_str_t value; + + hccf = (ngx_http_client_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_http_client_module); + + if (url == NULL) { + ngx_log_error(NGX_LOG_ERR, log, 0, + "url is NULL when create http client"); + return NULL; + } + + pool = NGX_CREATE_POOL(4096, ngx_cycle->log); + if (pool == NULL) { + ngx_log_error(NGX_LOG_ERR, log, 0, + "client create, create pool failed"); + return NULL; + } + + r = ngx_pcalloc(pool, sizeof(ngx_http_request_t)); + if (r == NULL) { + ngx_log_error(NGX_LOG_ERR, log, 0, + "client create, create http request failed"); + goto destroy; + } + r->pool = pool; + r->main = r; + + /* create http client ctx */ + r->ctx = ngx_pcalloc(pool, sizeof(void *) * 1); + if (r->ctx == NULL) { + ngx_log_error(NGX_LOG_ERR, log, 0, + "client create, create http request ctxs failed"); + goto destroy; + } + + ctx = ngx_pcalloc(pool, sizeof(ngx_http_client_ctx_t)); + if (ctx == NULL) { + ngx_log_error(NGX_LOG_ERR, log, 0, + "client create, create http request ctx failed"); + goto destroy; + } + r->ctx[0] = ctx; + + /* set paras for http client */ + r->method = method; + + /* default version HTTP/1.1 */ + r->http_version = NGX_HTTP_CLIENT_VERSION_11; + + /* for send body */ + ctx->request = request; + ctx->write_handler = send_body; + + ctx->header_timeout = hccf->header_timeout; + ctx->header_buffer_size = hccf->header_buffer_size; + + if (ngx_http_client_set_url(r, url, log) == NGX_ERROR) { + ngx_log_error(NGX_LOG_ERR, log, 0, + "client create, set url failed"); + goto destroy; + } + + /* headers_out */ + if (ngx_array_init(&ctx->headers_out.headers, pool, 64, + sizeof(ngx_http_client_header_out_t)) != NGX_OK) + { + ngx_log_error(NGX_LOG_ERR, log, 0, + "client create, init headers out failed"); + goto destroy; + } + + ngx_map_init(&ctx->headers_out.hash, ngx_map_hash_str, ngx_cmp_str); + + h = ngx_http_client_default_header; + while(h->name.len) { + h->handler(r, &value); + if (ngx_http_client_add_header(r, &h->name, &value) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, log, 0, + "client create, set default header %V: %V failed", + &h->name, &value); + goto destroy; + } + ++h; + } + + while (headers && headers->key.len) { + if (ngx_http_client_add_header(r, &headers->key, &headers->value) + != NGX_OK) + { + ngx_log_error(NGX_LOG_ERR, log, 0, + "client create, set user header %V: %V failed", + &h->name, &value); + goto destroy; + } + ++headers; + } + + return r; + +destroy: + NGX_DESTROY_POOL(pool); + + return NULL; +} + + +ngx_http_cleanup_t * +ngx_http_client_cleanup_add(ngx_http_request_t *r, size_t size) +{ + ngx_http_cleanup_t *cln; + + r = r->main; + + cln = ngx_palloc(r->pool, sizeof(ngx_http_cleanup_t)); + if (cln == NULL) { + return NULL; + } + + if (size) { + cln->data = ngx_palloc(r->pool, size); + if (cln->data == NULL) { + return NULL; + } + + } else { + cln->data = NULL; + } + + cln->handler = NULL; + cln->next = r->cleanup; + + r->cleanup = cln; + + return cln; +} + + +void +ngx_http_client_set_read_handler(ngx_http_request_t *r, + ngx_http_client_handler_pt read_handler) +{ + ngx_http_client_ctx_t *ctx; + + ctx = r->ctx[0]; + + ctx->read_handler = read_handler; +} + + +ngx_int_t +ngx_http_client_set_headers(ngx_http_request_t *r, ngx_keyval_t *headers) +{ + while (headers && headers->key.len) { + if (ngx_http_client_add_header(r, &headers->key, &headers->value) + != NGX_OK) + { + return NGX_ERROR; + } + ++headers; + } + + return NGX_OK; +} + + +void +ngx_http_client_set_write_handler(ngx_http_request_t *r, + ngx_http_client_handler_pt write_handler) +{ + ngx_http_client_ctx_t *ctx; + + ctx = r->ctx[0]; + + ctx->write_handler = write_handler; +} + + +void +ngx_http_client_set_version(ngx_http_request_t *r, ngx_uint_t version) +{ + r->http_version = version; +} + + +void +ngx_http_client_setopt(ngx_http_request_t *r, unsigned opt, ngx_uint_t value) +{ + ngx_http_client_ctx_t *ctx; + ngx_client_session_t *s; + + ctx = r->ctx[0]; + s = ctx->session; + + switch (opt) { + case NGX_HTTP_CLIENT_OPT_CONNECT_TIMEOUT: + s->connect_timeout = value; + break; + case NGX_HTTP_CLIENT_OPT_SEND_TIMEOUT: + s->send_timeout = value; + break; + case NGX_HTTP_CLIENT_OPT_POSTPONE_OUTPUT: + s->postpone_output = value; + break; + case NGX_HTTP_CLIENT_OPT_DYNAMIC_RESOLVER: + s->dynamic_resolver = value > 0; + break; + case NGX_HTTP_CLIENT_OPT_TCP_NODELAY: + s->tcp_nodelay = value > 0; + break; + case NGX_HTTP_CLIENT_OPT_TCP_NOPUSH: + s->tcp_nopush = value > 0; + break; + case NGX_HTTP_CLIENT_OPT_HEADER_TIMEOUT: + ctx->header_timeout = value; + break; + default: + ngx_log_error(NGX_LOG_ERR, &s->log, 0, + "try to set unsupported opt %d", opt); + break; + } +} + +/* send http request */ + +ngx_int_t +ngx_http_client_send(ngx_http_request_t *r) +{ + ngx_client_session_t *s; + ngx_http_client_ctx_t *ctx; + + ctx = r->ctx[0]; + s = ctx->session; + + /* init */ + s->client_connected = ngx_http_client_send_header; + s->client_recv = ngx_http_client_wait_response_handler; + s->client_closed = ngx_http_client_close_handler; + + /* + * init ctx->headers_in, headers_in use c->pool, + * reconnect will destroy and reinit ctx->headsers_in + */ + if (ngx_list_init(&ctx->headers_in.headers, r->pool, 20, + sizeof(ngx_table_elt_t)) + != NGX_OK) + { + return NGX_ERROR; + } + ctx->headers_in.content_length_n = -1; + + ngx_client_connect(s); + + return NGX_OK; +} + + +ngx_http_request_t * +ngx_http_client_get(ngx_log_t *log, ngx_str_t *url, ngx_keyval_t *headers, + void *request) +{ + ngx_http_request_t *r; + + r = ngx_http_client_create(log, NGX_HTTP_CLIENT_GET, url, headers, + NULL, request); + if (r == NULL) { + return NULL; + } + + if (ngx_http_client_send(r) == NGX_ERROR) { + return NULL; + } + + return r; +} + + +ngx_http_request_t * +ngx_http_client_head(ngx_log_t *log, ngx_str_t *url, ngx_keyval_t *headers, + void *request) +{ + ngx_http_request_t *r; + + r = ngx_http_client_create(log, NGX_HTTP_CLIENT_HEAD, url, headers, + NULL, request); + if (r == NULL) { + return NULL; + } + + if (ngx_http_client_send(r) == NGX_ERROR) { + return NULL; + } + + return r; +} + + +ngx_http_request_t * +ngx_http_client_post(ngx_log_t *log, ngx_str_t *url, ngx_keyval_t *headers, + ngx_http_client_handler_pt send_body, void *request) +{ + ngx_http_request_t *r; + + r = ngx_http_client_create(log, NGX_HTTP_CLIENT_POST, url, headers, + send_body, request); + if (r == NULL) { + return NULL; + } + + if (ngx_http_client_send(r) == NGX_ERROR) { + return NULL; + } + + return r; +} + + +/* get response */ + +ngx_uint_t +ngx_http_client_http_version(ngx_http_request_t *r) +{ + ngx_http_client_ctx_t *ctx; + + ctx = r->ctx[0]; + + return ctx->headers_in.http_version; +} + + +ngx_uint_t +ngx_http_client_status_code(ngx_http_request_t *r) +{ + ngx_http_client_ctx_t *ctx; + + ctx = r->ctx[0]; + + return ctx->headers_in.status_n; +} + + +ngx_str_t * +ngx_http_client_header_in(ngx_http_request_t *r, ngx_str_t *key) +{ + ngx_http_client_ctx_t *ctx; + ngx_table_elt_t *h; + ngx_list_part_t *part; + ngx_uint_t i; + + ctx = r->ctx[0]; + + part = &ctx->headers_in.headers.part; + h = part->elts; + + for (i = 0; /* void */; ++i) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + h = part->elts; + i = 0; + } + + if (h[i].hash == 0) { + continue; + } + + if (h[i].key.len == key->len + && ngx_strncasecmp(h[i].key.data, key->data, key->len) == 0) + { + return &h[i].value; + } + } + + return NULL; +} + + +ngx_int_t +ngx_http_client_read_body(ngx_http_request_t *r, ngx_chain_t **in) +{ + ngx_client_session_t *s; + ngx_http_client_ctx_t *ctx; + ngx_http_client_conf_t *hccf; + ngx_buf_t *buf; + ngx_int_t n, rc; + ngx_event_t *rev; + ngx_chain_t *cl, **ll, *ln; + + ctx = r->ctx[0]; + s = ctx->session; + rev = r->connection->read; + hccf = (ngx_http_client_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_http_client_module); + + // recycle bufs + while (ctx->in) { + cl = ctx->in; + ctx->in = cl->next; + if (cl->buf->pos != cl->buf->last) { + break; + } + + ngx_put_chainbuf(cl); + } + + cl = NULL; + ll = &cl; + + // part of body will read with header + if (ctx->buffer->last != ctx->buffer->pos) { + ln = ngx_get_chainbuf(hccf->body_buffer_size, 0); + if (ln == NULL) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "http client, alloc chainbuf without buffer failed"); + return NGX_ERROR; + } + buf = ln->buf; + buf->pos = ctx->buffer->pos; + buf->last = ctx->buffer->last; + ctx->buffer->pos = ctx->buffer->last; + + *ll = ln; + ll = &(*ll)->next; + } + + // start read + while (1) { + ln = ngx_get_chainbuf(hccf->body_buffer_size, 1); + if (ln == NULL) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "http client, alloc chainbuf with buffer failed"); + return NGX_ERROR; + } + buf = ln->buf; + + n = ngx_client_read(s, buf); + + if (n == 0) { + ngx_put_chainbuf(ln); + ngx_put_chainbufs(cl); + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "http client, server close"); + return 0; + } + + if (n == NGX_ERROR) { + ngx_put_chainbuf(ln); + ngx_put_chainbufs(cl); + + ngx_log_error(NGX_LOG_ERR, r->connection->log, ngx_errno, + "http client, server error close"); + return NGX_ERROR; + } + + if (n == NGX_AGAIN) { // all data in socket has been read + ngx_put_chainbuf(ln); + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, ngx_errno, + "http client, handle read event error"); + return NGX_ERROR; + } + + break; + } + + *ll = ln; + ll = &(*ll)->next; + ctx->rbytes += n; + } + + if (ctx->headers_in.chunked) { + rc = ngx_http_client_body_chunked(r, cl); + } else { + rc = ngx_http_client_body_length(r, cl); + } + + if (rc == NGX_ERROR) { // parse chunked error + return NGX_ERROR; + } + + *in = ctx->in; + if (rc == NGX_DONE) { // all body has been read + return NGX_DONE; + } + + return NGX_AGAIN; +} + + +off_t +ngx_http_client_rbytes(ngx_http_request_t *r) +{ + ngx_http_client_ctx_t *ctx; + + ctx = r->ctx[0]; + + return ctx->rbytes; +} + + +off_t +ngx_http_client_wbytes(ngx_http_request_t *r) +{ + ngx_http_client_ctx_t *ctx; + + ctx = r->ctx[0]; + + return ctx->wbytes; +} + + +/* end request */ + +void +ngx_http_client_detach(ngx_http_request_t *r) +{ + ngx_http_client_ctx_t *ctx; + + if (r == NULL) { + return; + } + + ctx = r->ctx[0]; + + ctx->request = NULL; + + if (r->connection) { + ngx_post_event(r->connection->read, &ngx_posted_events); + } +} + + +void +ngx_http_client_finalize_request(ngx_http_request_t *r, ngx_flag_t closed) +{ + ngx_http_client_ctx_t *ctx; + ngx_client_session_t *s; + + ctx = r->ctx[0]; + s = ctx->session; + + ngx_http_client_free_request(r); + + if (closed) { + ngx_client_close(s); + } else { + ngx_client_set_keepalive(s); + } +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/ngx_http_client.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/ngx_http_client.h new file mode 100644 index 0000000000..d06a620773 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/ngx_http_client.h @@ -0,0 +1,323 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#ifndef _NGX_HTTP_CLIENT_H_INCLUDE_ +#define _NGX_HTTP_CLIENT_H_INCLUDE_ + +#include +#include +#include +#include "ngx_client.h" +#include "ngx_toolkit_misc.h" + + +// http client method +#define NGX_HTTP_CLIENT_GET 0 +#define NGX_HTTP_CLIENT_HEAD 1 +#define NGX_HTTP_CLIENT_POST 2 +#define NGX_HTTP_CLIENT_PUT 3 +#define NGX_HTTP_CLIENT_DELETE 4 +#define NGX_HTTP_CLIENT_MKCOL 5 +#define NGX_HTTP_CLIENT_COPY 6 +#define NGX_HTTP_CLIENT_MOVE 7 +#define NGX_HTTP_CLIENT_OPTIONS 8 +#define NGX_HTTP_CLIENT_PROPFIND 9 +#define NGX_HTTP_CLIENT_PROPPATCH 10 +#define NGX_HTTP_CLIENT_LOCK 11 +#define NGX_HTTP_CLIENT_UNLOCK 12 +#define NGX_HTTP_CLIENT_PATCH 13 +#define NGX_HTTP_CLIENT_TRACE 14 + +// http client version +#define NGX_HTTP_CLIENT_VERSION_9 0 +#define NGX_HTTP_CLIENT_VERSION_10 1 +#define NGX_HTTP_CLIENT_VERSION_11 2 +#define NGX_HTTP_CLIENT_VERSION_20 3 + +// http client opt +#define NGX_HTTP_CLIENT_OPT_CONNECT_TIMEOUT 0 +#define NGX_HTTP_CLIENT_OPT_SEND_TIMEOUT 1 +#define NGX_HTTP_CLIENT_OPT_POSTPONE_OUTPUT 2 +#define NGX_HTTP_CLIENT_OPT_DYNAMIC_RESOLVER 3 +#define NGX_HTTP_CLIENT_OPT_TCP_NODELAY 4 +#define NGX_HTTP_CLIENT_OPT_TCP_NOPUSH 5 +#define NGX_HTTP_CLIENT_OPT_HEADER_TIMEOUT 6 + + +typedef void (* ngx_http_client_handler_pt)(void *r, ngx_http_request_t *hcr); + + +/* create and set http request */ + +/* + * create a http request for sending to server + * + * return value: + * return http request for successd, return NULL for failed + * + * paras: + * log: error in create will use this log + * method: http client method + * url: full request url like "http://test.com/index.html?hello=world" + * headers: http request header for sending + * send_body: callback for sending body + * request: who send http request + */ +ngx_http_request_t *ngx_http_client_create(ngx_log_t *log, + ngx_uint_t method, ngx_str_t *url, ngx_keyval_t *headers, + ngx_http_client_handler_pt send_body, void *request); + +/* + * add cleanup as ngx_http_cleanup_add + */ +ngx_http_cleanup_t *ngx_http_client_cleanup_add(ngx_http_request_t *r, + size_t size); + +/* + * set read handler for http client, should set before send request, + * otherwise body from server will discard + * + * return value: + * void + * + * paras: + * r: http client request + * read_handler: handler for setting + */ +void ngx_http_client_set_read_handler(ngx_http_request_t *r, + ngx_http_client_handler_pt read_handler); + +/* + * set http headers + * + * return value: + * NGX_OK for successd, NGX_ERROR for failed + * + * paras: + * r: http client request + * headers: headers set into r + * if value is not null, will set or modify the header + * if value is null string, will delete the header + */ +ngx_int_t ngx_http_client_set_headers(ngx_http_request_t *r, + ngx_keyval_t *headers); + +/* + * set write handler for http client, if set, + * will use this handler for sending body, + * + * return value: + * void + * + * paras: + * r: http client request + * write_handler: handler for setting + */ +void ngx_http_client_set_write_handler(ngx_http_request_t *r, + ngx_http_client_handler_pt write_handler); + +/* + * set write handler for http client, if set, + * will use this handler for sending body, + * + * return value: + * void + * + * paras: + * r: http client request + * write_handler: handler for setting + */ +void ngx_http_client_set_version(ngx_http_request_t *r, ngx_uint_t version); + +/* + * set http client option + * + * return value: + * void + * + * paras: + * r: http client request + * opt: http client opt + * NGX_HTTP_CLIENT_OPT_CONNECT_TIMEOUT: + * connect server timer + * NGX_HTTP_CLIENT_OPT_SEND_TIMEOUT: + * send data timer when buffer full + * NGX_HTTP_CLIENT_OPT_POSTPONE_OUTPUT: + * size threshold to send + * NGX_HTTP_CLIENT_OPT_DYNAMIC_RESOLVER: + * whether use dynamic resolver to resolv domain + * NGX_HTTP_CLIENT_OPT_TCP_NODELAY: + * whether set TCP_NODELAY + * NGX_HTTP_CLIENT_OPT_TCP_NOPUSH: + * whether set TCP_CORK + * NGX_HTTP_CLIENT_OPT_HEADER_TIMEOUT: + * timer for waiting response header from server + * value: http client opt value want to set + */ +void ngx_http_client_setopt(ngx_http_request_t *r, unsigned opt, + ngx_uint_t value); + +/* send http request */ + +/* + * send http request + * + * return value: + * NGX_OK for successd, NGX_ERROR for failed + * + * paras: + * r: http request for seding, create by ngx_http_client_create + */ +ngx_int_t ngx_http_client_send(ngx_http_request_t *r); + +/* + * create and send http GET request to server + * + * return value: + * return http request for successd, return NULL for failed + * + * paras: + * log: error in create will use this log + * url: full request url like "http://test.com/index.html?hello=world" + * headers: http request header for sending + * request: who send http request + */ +ngx_http_request_t *ngx_http_client_get(ngx_log_t *log, ngx_str_t *url, + ngx_keyval_t *headers, void *request); + +/* + * create and send http HEAD request to server + * + * return value: + * return http request for successd, return NULL for failed + * + * paras: + * log: error in create will use this log + * url: full request url like "http://test.com/index.html?hello=world" + * headers: http request header for sending + * request: who send http request + */ +ngx_http_request_t *ngx_http_client_head(ngx_log_t *log, ngx_str_t *url, + ngx_keyval_t *headers, void *request); + +/* + * create and send http POST request to server + * + * return value: + * return http request for successd, return NULL for failed + * + * paras: + * log: error in create will use this log + * url: full request url like "http://test.com/index.html?hello=world" + * headers: http request header for sending + * send_body: callback for sending body + * request: who send http request + */ +ngx_http_request_t *ngx_http_client_post(ngx_log_t *log, ngx_str_t *url, + ngx_keyval_t *headers, ngx_http_client_handler_pt send_body, void *request); + + +/* get response */ + +/* + * get http response version + * + * return value: + * http response version + * + * paras: + * r: http client request + */ +ngx_uint_t ngx_http_client_http_version(ngx_http_request_t *r); + +/* + * get http response status code + * + * return value: + * http response status code like 200, 500 + * + * paras: + * r: http client request + */ +ngx_uint_t ngx_http_client_status_code(ngx_http_request_t *r); + +/* + * get http response header's value + * + * return value: + * http response header's value + * + * paras: + * r: http client request + * key: http header like "Host", "Content-Type" + */ +ngx_str_t *ngx_http_client_header_in(ngx_http_request_t *r, ngx_str_t *key); + +/* + * read http response body + * + * return value: + * NGX_AGAIN: read part of data + * 0: tcp connection disconnect, need finalize request with 1 + * NGX_ERROR: tcp connection error disconnect, need finalize request with 1 + * NGX_DONE: response body has been read, could finalize request with 0 + * + * paras: + * r: http client request + * in: where read data put + */ +ngx_int_t ngx_http_client_read_body(ngx_http_request_t *r, ngx_chain_t **in); + +/* + * get receive bytes + * + * return value: + * bytes receive from server + * + * paras: + * r: http client request + */ +off_t ngx_http_client_rbytes(ngx_http_request_t *r); + +/* + * get send bytes + * + * return value: + * bytes send to server + * + * paras: + * r: http client request + */ +off_t ngx_http_client_wbytes(ngx_http_request_t *r); + + +/* end request */ + +/* + * detach http client request with it's creator, + * all read and write handler will not be triggered + * + * return value: + * bytes send to server + * + * paras: + * r: http client request + */ +void ngx_http_client_detach(ngx_http_request_t *r); + +/* + * finalize http client request + * + * return value: + * void + * + * paras: + * r: http client request + * closed: set to 1, will close connection to server + * set to 0, will keep connection to server alive + */ +void ngx_http_client_finalize_request(ngx_http_request_t *r, ngx_flag_t closed); + +#endif diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/t/config b/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/t/config new file mode 100644 index 0000000000..0ac5274dd2 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/t/config @@ -0,0 +1,13 @@ +ngx_addon_name=ngx_client_test_module + +HTTP_MODULES="$HTTP_MODULES \ + ngx_client_test_module \ + ngx_http_client_test_module \ + ngx_client_stat_module \ + " + +NGX_ADDON_SRCS="$NGX_ADDON_SRCS + $ngx_addon_dir/ngx_client_test_module.c \ + $ngx_addon_dir/ngx_http_client_test_module.c \ + $ngx_addon_dir/ngx_client_stat_module.c \ + " diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/t/nginx.conf b/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/t/nginx.conf new file mode 100644 index 0000000000..081f0321de --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/t/nginx.conf @@ -0,0 +1,57 @@ + +user root; +worker_processes 4; + +#error_log logs/error.log; +#error_log logs/error.log notice; +error_log logs/error.log info; + +#pid logs/nginx.pid; + +events { + worker_connections 1024; + resolver 192.168.84.254; + dynamic_refresh_interval 5m; +} + + +http { + include mime.types; + default_type application/octet-stream; + + #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + # '$status $body_bytes_sent "$http_referer" ' + # '"$http_user_agent" "$http_x_forwarded_for"'; + + #access_log logs/access.log main; + + sendfile on; + #tcp_nopush on; + + #keepalive_timeout 0; + keepalive_timeout 65; + + #gzip on; + + server { + listen 80; + server_name localhost; + + location / { + root html; + index index.html index.htm; + } + + location /client_test { + client_test; + } + + location /http_client_test { + http_client_test; + } + + location /client_stat { + client_stat; + } + } +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/t/ngx_client_stat_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/t/ngx_client_stat_module.c new file mode 100644 index 0000000000..bdfe2d1475 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/t/ngx_client_stat_module.c @@ -0,0 +1,148 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#include +#include +#include +#include +#include "ngx_client.h" +#include "ngx_rbuf.h" +#include "ngx_poold.h" +#include "ngx_timerd.h" +#include "ngx_event_timer_module.h" +#include "ngx_event_resolver.h" +#include "ngx_dynamic_resolver.h" + + +static char *ngx_client_stat(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); + + +static ngx_command_t ngx_client_stat_commands[] = { + + { ngx_string("client_stat"), + NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS, + ngx_client_stat, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_client_stat_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_client_stat_module = { + NGX_MODULE_V1, + &ngx_client_stat_module_ctx, /* module context */ + ngx_client_stat_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_int_t +ngx_client_stat_handler(ngx_http_request_t *r) +{ + ngx_chain_t **ll, *out; + ngx_buf_t *b; + size_t len; + + r->headers_out.status = NGX_HTTP_OK; + ngx_http_send_header(r); + + ll = &out; + + len = sizeof("--------------------------------------------------\n") - 1 + + sizeof("ngx_worker: ngx_process_slot: pid: \n") - 1 + + 3 * NGX_OFF_T_LEN; + + *ll = ngx_alloc_chain_link(r->pool); + if (*ll == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + (*ll)->next = NULL; + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + (*ll)->buf = b; + + b->last = ngx_snprintf(b->last, len, + "--------------------------------------------------\n" + "ngx_worker: %i ngx_process_slot: %i pid: %i\n", + ngx_worker, ngx_process_slot, ngx_pid); + + if (*ll) { + ll = &(*ll)->next; + } + *ll = ngx_rbuf_state(r, 1); + + if (*ll) { + ll = &(*ll)->next; + } + *ll = ngx_event_timer_state(r); + + if (*ll) { + ll = &(*ll)->next; + } + *ll = ngx_event_resolver_state(r); + + if (*ll) { + ll = &(*ll)->next; + } + *ll = ngx_dynamic_resolver_state(r); + + if (*ll) { + ll = &(*ll)->next; + } + *ll = ngx_poold_state(r, 1); + + if (*ll) { + ll = &(*ll)->next; + } + *ll = ngx_timerd_state(r, 1); + + if (*ll) { + ll = &(*ll)->next; + } + *ll = ngx_client_state(r, 1); + + (*ll)->buf->last_buf = 1; + + return ngx_http_output_filter(r, out); +} + +static char * +ngx_client_stat(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_core_loc_conf_t *clcf; + + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + clcf->handler = ngx_client_stat_handler; + + return NGX_CONF_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/t/ngx_client_test_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/t/ngx_client_test_module.c new file mode 100644 index 0000000000..db6c586e36 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/t/ngx_client_test_module.c @@ -0,0 +1,188 @@ +#include +#include +#include +#include "ngx_client.h" + + +static char *ngx_client_test(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); + + +static ngx_command_t ngx_client_test_commands[] = { + + { ngx_string("client_test"), + NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS, + ngx_client_test, + 0, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_client_test_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_client_test_module = { + NGX_MODULE_V1, + &ngx_client_test_module_ctx, /* module context */ + ngx_client_test_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void +ngx_client_test_connected(ngx_client_session_t *s) +{ + ngx_buf_t *b; + size_t len; + ngx_chain_t out; + ngx_http_request_t *r; + ngx_event_t *wev; + + ngx_log_error(NGX_LOG_ERR, &s->log, 0, "client connected"); + + r = s->data; + wev = s->peer.connection->write; + + len = sizeof("nginx client test\n") - 1; + b = ngx_create_temp_buf(s->pool, len); + + if (b == NULL) { + ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + b->last = ngx_copy(b->last, "nginx client test\n", len); + b->last_buf = 1; + + out.buf = b; + out.next = NULL; + + ngx_client_write(s, &out); + + ngx_handle_write_event(wev, 0); +} + +static void +ngx_client_test_recv(ngx_client_session_t *s) +{ + ngx_buf_t *b; + ngx_int_t n; + ngx_connection_t *c; + ngx_str_t recv; + ngx_http_request_t *r; + + c = s->peer.connection; + r = s->data; + + b = ngx_create_temp_buf(s->pool, 4096); + if (b == NULL) { + ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + n = c->recv(c, b->pos, b->end - b->last); + if (n == NGX_AGAIN) { + ngx_log_error(NGX_LOG_ERR, &s->log, 0, "client recv NGX_AGAIN"); + return; + } + + if (n == NGX_ERROR || n == 0) { + ngx_log_error(NGX_LOG_ERR, &s->log, 0, "client recv NGX_ERROR"); + ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + ngx_client_close(s); + return; + } + + b->last += n; + + recv.data = b->pos; + recv.len = b->last - b->pos; + + ngx_log_error(NGX_LOG_ERR, &s->log, 0, "client recv %d: %V, %z", + n, &recv, recv.len); + + ngx_http_finalize_request(r, NGX_HTTP_FORBIDDEN); + ngx_client_set_keepalive(s); + return; +} + +static void +ngx_client_test_send(ngx_client_session_t *s) +{ + ngx_log_error(NGX_LOG_ERR, &s->log, 0, "client send"); +} + +static void +ngx_client_test_closed(ngx_client_session_t *s) +{ + ngx_log_error(NGX_LOG_ERR, &s->log, 0, "client closed"); +} + +static ngx_int_t +ngx_client_test_handler(ngx_http_request_t *r) +{ + ngx_client_session_t *s; + ngx_str_t echo; + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "client test handler"); + + if (ngx_http_arg(r, (u_char *) "echo", sizeof("echo") - 1, &echo) + != NGX_OK) + { + return NGX_HTTP_BAD_REQUEST; + } + + s = ngx_client_create(&echo, NULL, 0, r->connection->log); + if (s == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + //ci->dynamic_resolver = 0; + //ci->recvbuf = 4096; + + s->client_connected = ngx_client_test_connected; + s->client_recv = ngx_client_test_recv; + s->client_send = ngx_client_test_send; + s->client_closed = ngx_client_test_closed; + s->data = r; + + ngx_client_connect(s); + + ++r->count; + + return NGX_DONE; +} + + +static char * +ngx_client_test(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_core_loc_conf_t *clcf; + + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + clcf->handler = ngx_client_test_handler; + + return NGX_CONF_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/t/ngx_http_client_test_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/t/ngx_http_client_test_module.c new file mode 100644 index 0000000000..aacef52d6a --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/t/ngx_http_client_test_module.c @@ -0,0 +1,203 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#include "ngx_http_client.h" +#include "ngx_rbuf.h" + + +static char *ngx_http_client_test(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + + +static ngx_command_t ngx_http_client_test_commands[] = { + + { ngx_string("http_client_test"), + NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS, + ngx_http_client_test, + 0, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_http_client_test_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_http_client_test_module = { + NGX_MODULE_V1, + &ngx_http_client_test_module_ctx, /* module context */ + ngx_http_client_test_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void +ngx_http_client_test_recv_body(void *request, ngx_http_request_t *hcr) +{ + ngx_http_request_t *r; + ngx_chain_t *cl = NULL; + ngx_chain_t **ll; + ngx_int_t rc; + + r = request; + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "http client test recv body"); + + rc = ngx_http_client_read_body(hcr, &cl); + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "http client test recv body, rc %i %i, %O", + rc, ngx_errno, ngx_http_client_rbytes(hcr)); + + if (rc == 0) { + goto done; + } + + if (rc == NGX_ERROR) { + goto done; + } + + if (rc == NGX_DONE) { + for (ll = &cl; (*ll)->next; ll = &(*ll)->next); + + (*ll)->buf->last_buf = 1; + } + + ngx_http_output_filter(r, cl); + + ngx_http_run_posted_requests(r->connection); + + if (rc == NGX_AGAIN) { + return; + } + + if (rc == NGX_DONE) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "all body has been read"); + ngx_http_client_finalize_request(hcr, 0); + } + +done: + ngx_http_finalize_request(r, NGX_OK); +} + +static void +ngx_http_client_test_recv(void *request, ngx_http_request_t *hcr) +{ + ngx_http_request_t *r; + static ngx_str_t content_type = ngx_string("Content-Type"); + static ngx_str_t connection = ngx_string("Connection"); + static ngx_str_t unknown = ngx_string("Unknown"); + ngx_str_t *ct, *con; + + r = request; + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "http client test recv, connection: %p", hcr->connection); + + r->headers_out.status = 200; + + ngx_http_client_set_read_handler(hcr, ngx_http_client_test_recv_body); + + ngx_http_send_header(r); + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "status_code: %ui http_version: %ui", + ngx_http_client_status_code(hcr), + ngx_http_client_http_version(hcr)); + + ct = ngx_http_client_header_in(hcr, &content_type); + con = ngx_http_client_header_in(hcr, &connection); + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "Content-Type: %V", ct); + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "Connection: %V", con); + + if (ngx_http_client_header_in(hcr, &unknown) == NULL) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "no header Unknown"); + } + + ngx_http_client_test_recv_body(request, hcr); +} + + +static ngx_int_t +ngx_http_client_test_handler(ngx_http_request_t *r) +{ + ngx_http_request_t *hcr; + static ngx_str_t request_url = ngx_string("http://101.200.241.232/"); + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "http client test handler"); + + // Default header Host, User-Agent, Connection(below HTTP/1.1), Accept, Date + hcr = ngx_http_client_create(r->connection->log, NGX_HTTP_CLIENT_GET, + &request_url, NULL, NULL, r); + + // add Connection, delete Date, Modify Host, add new header + ngx_str_t value; + + value.data = (u_char *) "World"; + value.len = sizeof("World") - 1; + + ngx_keyval_t headers[] = { + { ngx_string("Host"), ngx_string("www.test.com") }, + { ngx_string("Connection"), ngx_string("upgrade") }, + { ngx_string("Date"), ngx_null_string }, + { ngx_string("Hello"), value }, + { ngx_null_string, ngx_null_string } // must end with null str + }; + ngx_http_client_set_headers(hcr, headers); + + ngx_http_client_set_read_handler(hcr, ngx_http_client_test_recv); + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "http client test before send"); + + ngx_http_client_send(hcr); + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "http client test after send"); + +// ngx_http_client_detach(hcr); +// return NGX_HTTP_FORBIDDEN; + + ++r->count; + + return NGX_DONE; +} + + +static char * +ngx_http_client_test(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_core_loc_conf_t *clcf; + + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + clcf->handler = ngx_http_client_test_handler; + + return NGX_CONF_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/t/tcpserver.go b/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/t/tcpserver.go new file mode 100644 index 0000000000..2ed5d2d9ee --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-client-module/t/tcpserver.go @@ -0,0 +1,41 @@ +package main + +import ( + "fmt" + "net" +) + +func handleConnection(c net.Conn) { + b := make([]byte, 4096) + + for { + n, err := c.Read(b) + if err != nil { + fmt.Print("Read Error ", err) + c.Close() + return + } + + fmt.Print("recv ", n, " data:", string(b)) + + c.Write(b[0:n]) + } +} + +func main() { + ln, err := net.Listen("tcp", ":10000") + if err != nil { + fmt.Print("Listen Error ", err) + return + } + + for { + conn, err := ln.Accept() + if err != nil { + fmt.Print("Accept Error ", err) + continue + } + + go handleConnection(conn) + } +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/COPYRIGHT b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/COPYRIGHT new file mode 100644 index 0000000000..837a6682d6 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/COPYRIGHT @@ -0,0 +1,24 @@ +Copyright (C) 2016-2020, by Jie Wu "AlexWoo" . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/README.md b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/README.md new file mode 100644 index 0000000000..6e16a909ec --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/README.md @@ -0,0 +1,181 @@ +# Module nginx-multiport-module +--- +## Instructions + +Every worker process can bind own port, user can visit specific worker process by using the port. + +- [ngx-stream-zone-module](doc/ngx-stream-zone-module.md) + + Record stream's owner worker process slot + +- [ngx-http-broadcast-module](doc/ngx-http-broadcast-module.md) + + Broadcast HTTP request to all worker processes when receive HTTP request + +## Directives + +### multi\_listen + + Syntax : multi_listen multiport relationport; + Default : None; + Context : events + +multiport can configured as below: + + address:port + port + unix:path + +when configured with IPv4 or IPv6 port, worker process listen port plus with worker process's slot. For Example, we start four workers, add configured multiport with 9000. worker 0 will listen 9000, worker 1 will listen 9001, worker 2 will listen 9002, worker 3 will listen 9003 + +when configured with unix path, worker will listen path plus with suffix of worker process's slot. For Example, we start four workers, add configured multiport with unix:/tmp/http. worker 0 will listen /tmp/http.0, worker 1 will listen /tmp/http.1, worker 2 will listen /tmp/http.2, worker 3 will listen /tmp/http.3 + + +relationport must configured same as listen directives in http server, rtmp server, stream server or other server + +### inner\_proxy + + Syntax : inner_proxy multiport uri; + Default : None; + Context : http, server, location + +- multiport: configured in multi_listen +- uri: uri for inner_proxy, configured as below + + location /multiport_test/ { + inner_proxy unix:/tmp/http.sock.80 /inner_proxy; + multiport_test; + } + + location /inner_proxy/ { + rewrite ^/inner_proxy/(.*):/(.*) /$2 break; + proxy_pass http://$1:; + } + + As example above, if send subrequest to process whose workerid is 0, the uri will change to /inner_proxy/unix:/tmp/http.sock.80.0:/multiport_test/xxx + + proxy_pass will send current request to process 0 as inner proxy request. + +## API + +- ngx\_multiport\_get\_port + + ngx_int_t ngx_event_multiport_get_port(ngx_pool_t *pool, ngx_str_t *port, ngx_str_t *multiport, ngx_int_t pslot); + + - para: + + pool: pool for port memory alloc + port: process real listen port while process\_slot is pslot + multiport: port configure for processes, format as below: + + port only: port + IPv4: host:port host must be ipaddr of IPv4 or * + IPv6: [host]:port host must be ipaddr of IPv6 + Unix: unix:/path + + pslot: process\_slot, process\_slot of other worker process can get through ngx\_process\_slot\_get\_slot + + - return value: + + NGX\_OK for successd, NGX\_ERROR for failed + +- ngx\_multiport\_get\_slot + + ngx_int_t ngx_multiport_get_slot(ngx_uint_t wpid); + + - para: + + wpid: worker process id, 0 to ccf->worker_processes - 1 + + - return value: + + ngx_process_slot for successd, NGX_ERROR for failed + +- ngx\_http\_inner\_proxy\_request + + ngx_int_t ngx_http_inner_proxy_request(ngx_http_request_t *r, ngx_int_t pslot); + + send a inner proxy request to specific process, must use with directives inner\_proxy + + - paras: + + - r: http request for send inner request to sibling worker + - pslot: sibling worker ngx_process_slot + + - return values: + + - NGX_OK: for successd + - NGX_ERROR: for failed + - NGX_DECLINED: for not configured or send inner proxy to self + +## Build + +cd to NGINX source directory & run this: + + ./configure --add-module=/path/to/nginx-multiport-module/ + make && make install + +## Example + +See t/ngx\_http\_process\_slot\_test\_module.c as reference + +**Build**: + + ./configure --with-debug --with-ipv6 --add-module=/path/to/nginx-multiport-module/t/ --add-module=/path/to/nginx-multiport-module/ --add-module=/path/to/echo-nginx-module/ + + make && make install + +**Configure**: + + worker_processes 4; + + events { + ... + + multi_listen 9000 80; + multi_listen unix:/tmp/http.sock.80 80; + } + + http { + ... + + server { + ... + + location /multiport_test/ { + inner_proxy unix:/tmp/http.sock.80 /inner_proxy; + multiport_test; + } + + location /inner_proxy/ { + rewrite ^/inner_proxy/(.*):/(.*) /$2 break; + proxy_pass http://$1:; + } + } + } + +**Test for API**: + + $ curl http://192.168.84.254/multiport_test/123 + TEST cases 19, 19 pass + +If request send to worker1 to worker3, the request will proxy to worker 0. will get log as below: + + 2017/10/14 20:45:44 [error] 20065#0: *6 multiport test handler, client: 192.168.84.1, server: localhost, request: "GET /multiport_test/123 HTTP/1.1", host: "192.168.84.254:9003" + 2017/10/14 20:45:44 [error] 20065#0: *6 inner proxy return 0, client: 192.168.84.1, server: localhost, request: "GET /multiport_test/123 HTTP/1.1", host: "192.168.84.254:9003" + 2017/10/14 20:45:44 [error] 20062#0: *8 multiport test handler, client: unix:, server: localhost, request: "GET //multiport_test/123 HTTP/1.0", host: "localhost" + +**Test for multiport**: + + curl -v http://127.0.0.1/ + curl -v http://127.0.0.1:9000/ + curl -v http://127.0.0.1:9001/ + curl -v http://127.0.0.1:9002/ + curl -v http://127.0.0.1:9003/ + + curl -v --unix-socket /tmp/http.sock.80.0 http:/ + curl -v --unix-socket /tmp/http.sock.80.1 http:/ + curl -v --unix-socket /tmp/http.sock.80.2 http:/ + curl -v --unix-socket /tmp/http.sock.80.3 http:/ + +Tests will get the same result, for port 9000 will always send to worker process 0, 9001 to worker process 1 and so on diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/config b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/config new file mode 100644 index 0000000000..36313b330e --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/config @@ -0,0 +1,31 @@ +ngx_addon_name=ngx_multiport_module + +EVENT_MODULES="$EVENT_MODULES \ + ngx_event_multiport_module \ + " + +CORE_MODULES="$CORE_MODULES \ + ngx_process_slot_module \ + ngx_stream_zone_module \ + " + +HTTP_FILTER_MODULES="$HTTP_FILTER_MODULES \ + ngx_http_broadcast_module \ + ngx_http_inner_proxy_module \ + " + +NGX_ADDON_SRCS="$NGX_ADDON_SRCS \ + $ngx_addon_dir/ngx_multiport_misc.c \ + $ngx_addon_dir/ngx_event_multiport_module.c \ + $ngx_addon_dir/ngx_process_slot_module.c \ + $ngx_addon_dir/ngx_stream_zone_module.c \ + $ngx_addon_dir/ngx_http_broadcast_module.c \ + $ngx_addon_dir/ngx_http_inner_proxy_module.c \ + " + +NGX_ADDON_DEPS="$NGX_ADDON_DEPS \ + $ngx_addon_dir/ngx_multiport.h \ + $ngx_addon_dir/ngx_stream_zone_module.h \ + " + +CFLAGS="$CFLAGS -I $ngx_addon_dir" diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/doc/ngx-http-broadcast-module.md b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/doc/ngx-http-broadcast-module.md new file mode 100644 index 0000000000..1c86a5bc95 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/doc/ngx-http-broadcast-module.md @@ -0,0 +1,77 @@ +# ngx-http-broadcast-module +--- +## Instructions + +Broadcast HTTP request to all worker processes when receive HTTP request + +## Directives + +### broadcast + + Syntax : broadcast multiport uri; + Default : None; + Context : location + +- multiport is multi_listen port configured in event +- uri is http proxy_pass uri configured as below + + + location /auth_proxy/ { + rewrite ^/auth_proxy/(.*) /auth break; + proxy_pass http://$1:; + } + + +## Build + +cd to NGINX source directory & run this: + + ./configure --add-module=/path/to/nginx-multiport-module/ + make && make install + +## Example + +**Build**: + + ./configure --with-debug --with-ipv6 --add-module=/path/to/nginx-multiport-module/t/ --add-module=/path/to/nginx-multiport-module/ --add-module=/path/to/echo-nginx-module/ + make && make install + +**Configure**: + + events { + ... + multi_listen unix:/tmp/http.sock.80 80; + } + + + http { + ... + + server { + listen 80; + server_name localhost; + + ... + + location / { + broadcast unix:/tmp/http.sock.80 /auth_proxy; + } + + location /auth_proxy/ { + rewrite ^/auth_proxy/(.*) /auth break; + proxy_pass http://$1:; + } + + location /auth { + # return 403; + echo "auth"; + echo $scheme://$host$uri?$args; + } + } + } + +**Test**: + + curl -v 'http://192.168.84.254/aa?a=b&c=d' + +curl will get all response content if worker not return non 200 response \ No newline at end of file diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/doc/ngx-stream-zone-module.md b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/doc/ngx-stream-zone-module.md new file mode 100644 index 0000000000..9864558bc2 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/doc/ngx-stream-zone-module.md @@ -0,0 +1,92 @@ +# ngx-stream-zone-module +--- +## Instructions + +Record stream's owner worker process slot + +## Directives + +### stream\_zone + + Syntax : stream_zone buckets=$nbuckets streams=$nstreams; + Default : None; + Context : main + +nbuckets is hash buckect number, nstreams is max streams system can store + +nbuckets is recommended use a prime number + +## API + +**header file** + +For using this API, You should include the header file as below: + + #include "ngx_stream_zone_module.h" + +**ngx\_stream\_zone\_insert\_stream** + + ngx_int_t ngx_stream_zone_insert_stream(ngx_str_t *name); + +- para: + + name: stream name + +- return value: + + process\_slot for owner of stream, NGX\_ERROR for error + +**ngx\_stream\_zone\_delete\_stream** + + void ngx_stream_zone_delete_stream(ngx_str_t *name); + +- para: + + name: stream name + +**ngx\_stream\_zone\_state** + + ngx_chain_t *ngx_stream_zone_state(ngx_http_request_t *r, ngx_flag_t detail); + +- para: + + - r: http request to query status of rbuf + - detail: print stream detail in log + +- return value: + + chain of stream zone state for returning to http client + +## Build + +cd to NGINX source directory & run this: + + ./configure --add-module=/path/to/nginx-multiport-module/ + make && make install + +## Example + +See t/ngx\_stream\_zone\_test\_module.c as reference + +**Build**: + + ./configure --with-debug --with-ipv6 --add-module=/path/to/nginx-multiport-module/t/ --add-module=/path/to/nginx-multiport-module/ + make && make install + +**Configure**: + + stream_zone buckets=10007 streams=10000; + +**Test**: + + curl -XPOST -v "http://127.0.0.1:9001/stream_zone_test/ab?stream=test" + curl -XPOST -v "http://127.0.0.1:9002/stream_zone_test/ab?stream=test1" + curl -XPOST -v "http://127.0.0.1:9003/stream_zone_test/ab?stream=test2" + + curl -XPOST -v "http://127.0.0.1:9003/stream_zone_test/ab?stream=test" + + curl -XDELETE -v "http://127.0.0.1:9000/stream_zone_test/ab?stream=test3" + curl -XDELETE -v "http://127.0.0.1:9002/stream_zone_test/ab?stream=test1" + curl -XDELETE -v "http://127.0.0.1:9001/stream_zone_test/ab?stream=test2" + + curl -XGET -v "http://127.0.0.1:9001/stream_zone_test/ab" \ No newline at end of file diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/ngx_event_multiport_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/ngx_event_multiport_module.c new file mode 100644 index 0000000000..0c0e10afde --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/ngx_event_multiport_module.c @@ -0,0 +1,1018 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#include +#include +#include +#include "ngx_multiport.h" + + +static ngx_int_t ngx_event_multiport_process_init(ngx_cycle_t *cycle); +static void ngx_event_multiport_process_exit(ngx_cycle_t *cycle); + +static void *ngx_event_multiport_create_conf(ngx_cycle_t *cycle); +static char *ngx_event_multiport_init_conf(ngx_cycle_t *cycle, void *conf); + +static char *ngx_event_multiport_listen(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + + +//multiport_listen listenparas relationport +typedef struct { + ngx_str_t multiport; + ngx_str_t relatedport; + ngx_listening_t listening; +} ngx_event_multiport_listen_t; + +typedef struct { + ngx_array_t *ports; /* array of ngx_event_multiport_listen_t */ +} ngx_event_multiport_conf_t; + + +static ngx_str_t event_multiport_name = ngx_string("event_multiport"); + + +static ngx_command_t ngx_event_multiport_commands[] = { + + { ngx_string("multi_listen"), + NGX_EVENT_CONF|NGX_CONF_TAKE2, + ngx_event_multiport_listen, + 0, + 0, + NULL }, + + ngx_null_command +}; + + +ngx_event_module_t ngx_event_multiport_module_ctx = { + &event_multiport_name, + ngx_event_multiport_create_conf, /* create configuration */ + ngx_event_multiport_init_conf, /* init configuration */ + + { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL } +}; + + +/* this module use ngx_cycle->log */ +ngx_module_t ngx_event_multiport_module = { + NGX_MODULE_V1, + &ngx_event_multiport_module_ctx, /* module context */ + ngx_event_multiport_commands, /* module directives */ + NGX_EVENT_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + ngx_event_multiport_process_init, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + ngx_event_multiport_process_exit, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void * +ngx_event_multiport_create_conf(ngx_cycle_t *cycle) +{ + ngx_event_multiport_conf_t *conf; + + conf = ngx_pcalloc(cycle->pool, sizeof(ngx_event_multiport_conf_t)); + if (conf == NULL) { + return NULL; + } + + return conf; +} + +static char * +ngx_event_multiport_init_conf(ngx_cycle_t *cycle, void *conf) +{ + return NGX_CONF_OK; +} + +static char * +ngx_event_multiport_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_event_multiport_conf_t *emcf = conf; + + ngx_str_t *value; + ngx_event_multiport_listen_t *mls; + ngx_url_t u; + + value = cf->args->elts; + + ngx_memzero(&u, sizeof(ngx_url_t)); + + /* check listen base port */ + u.url = value[1]; + u.listen = 1; + u.default_port = 0; + + if (ngx_parse_url(cf->temp_pool, &u) != NGX_OK) { + if (u.err) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "%s in \"%V\" of the \"multi_listen" + " baseport\" directive", + u.err, &u.url); + } + + return NGX_CONF_ERROR; + } + + ngx_memzero(&u, sizeof(ngx_url_t)); + + /* check relation port */ + u.url = value[2]; + u.listen = 1; + u.default_port = 0; + + if (ngx_parse_url(cf->temp_pool, &u) != NGX_OK) { + if (u.err) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "%s in \"%V\" of the \"multi_listen" + " relationport\" directive", + u.err, &u.url); + } + + return NGX_CONF_ERROR; + } + + if (emcf->ports == NULL) { + emcf->ports = ngx_array_create(cf->pool, 4, + sizeof(ngx_event_multiport_listen_t)); + if (emcf->ports == NULL) { + return NGX_CONF_ERROR; + } + } + + mls = ngx_array_push(emcf->ports); + if (mls == NULL) { + return NGX_CONF_ERROR; + } + mls->multiport = value[1]; + mls->relatedport = value[2]; + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_event_multiport_init_listening(ngx_cycle_t *cycle, ngx_listening_t *ls, + void *sockaddr, socklen_t socklen, ngx_listening_t *cls) +{ + size_t len; + struct sockaddr *sa; + u_char text[NGX_SOCKADDR_STRLEN]; + + ngx_memzero(ls, sizeof(ngx_listening_t)); + + sa = ngx_palloc(cycle->pool, socklen); + if (sa == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(sa, sockaddr, socklen); + + ls->sockaddr = sa; + ls->socklen = socklen; + + len = ngx_sock_ntop(sa, socklen, text, NGX_SOCKADDR_STRLEN, 1); + ls->addr_text.len = len; + + switch (ls->sockaddr->sa_family) { +#if (NGX_HAVE_INET6) + case AF_INET6: + ls->addr_text_max_len = NGX_INET6_ADDRSTRLEN; + break; +#endif +#if (NGX_HAVE_UNIX_DOMAIN) + case AF_UNIX: + ls->addr_text_max_len = NGX_UNIX_ADDRSTRLEN; + len++; + break; +#endif + case AF_INET: + ls->addr_text_max_len = NGX_INET_ADDRSTRLEN; + break; + default: + ls->addr_text_max_len = NGX_SOCKADDR_STRLEN; + break; + } + + ls->addr_text.data = ngx_pnalloc(cycle->pool, len); + if (ls->addr_text.data == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(ls->addr_text.data, text, len); + +#if (NGX_HAVE_UNIX_DOMAIN) + if (ls->sockaddr->sa_family == AF_UNIX) { + u_char *name = ls->addr_text.data + sizeof("unix:") - 1; + ngx_delete_file(name); + } +#endif + + ls->fd = (ngx_socket_t) -1; + ls->type = SOCK_STREAM; + + ls->backlog = cls->backlog; + ls->rcvbuf = cls->rcvbuf; + ls->sndbuf = cls->sndbuf; + +#if (NGX_HAVE_KEEPALIVE_TUNABLE) + ls->keepidle = cls->keepidle; + ls->keepintvl = cls->keepintvl; + ls->keepcnt = cls->keepcnt; +#endif + + ls->addr_ntop = cls->addr_ntop; + ls->handler = cls->handler; + ls->servers = cls->servers; + ls->worker = ngx_worker; + + ls->log= cls->log; + ls->logp = cls->logp; + + ls->pool_size = cls->pool_size; + ls->post_accept_buffer_size = cls->post_accept_buffer_size; + ls->post_accept_timeout = cls->post_accept_timeout; + +#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY) + ls->ipv6only = cls->ipv6only; +#endif + + ls->keepalive = cls->keepalive; + +#if (NGX_HAVE_DEFERRED_ACCEPT) + ls->deferred_accept = cls->deferred_accept; + ls->delete_deferred = cls->delete_deferred; + ls->add_deferred = cls->add_deferred; +#endif + +#ifdef SO_ACCEPTFILTER + ls->accept_filter = cls->accept_filter; +#endif + +#if (NGX_HAVE_SETFIB) + ls->setfib = cls->setfib; +#endif + +#if (NGX_HAVE_TCP_FASTOPEN) + ls->fastopen = cls->fastopen; +#endif + + return NGX_OK; +} + +static ngx_int_t +ngx_event_multiport_open_listening_sock(ngx_cycle_t *cycle, ngx_listening_t *ls) +{ + int reuseaddr; + ngx_uint_t tries, failed; + ngx_err_t err; + ngx_log_t *log; + ngx_socket_t s; + + reuseaddr = 1; +#if (NGX_SUPPRESS_WARN) + failed = 0; +#endif + + log = cycle->log; + + /* TODO: configurable try number */ + + for (tries = 5; tries; tries--) { + failed = 0; + + if (ls->fd != (ngx_socket_t) -1) { + return NGX_OK; + } + + s = ngx_socket(ls->sockaddr->sa_family, ls->type, 0); + + if (s == (ngx_socket_t) -1) { + ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno, + "multiport, " ngx_socket_n " %V failed", + &ls->addr_text); + return NGX_ERROR; + } + + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, + (const void *) &reuseaddr, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno, + "multiport, setsockopt(SO_REUSEADDR) %V failed", + &ls->addr_text); + + if (ngx_close_socket(s) == -1) { + ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno, + "multiport, " ngx_close_socket_n " %V failed", + &ls->addr_text); + } + + return NGX_ERROR; + } + +#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY) + + if (ls->sockaddr->sa_family == AF_INET6) { + int ipv6only; + + ipv6only = ls->ipv6only; + + if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, + (const void *) &ipv6only, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno, + "multiport, setsockopt(IPV6_V6ONLY) %V failed," + " ignored", &ls->addr_text); + } + } +#endif + /* TODO: close on exit */ + + if (!(ngx_event_flags & NGX_USE_IOCP_EVENT)) { + if (ngx_nonblocking(s) == -1) { + ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno, + "multiport, " ngx_nonblocking_n " %V failed", + &ls->addr_text); + + if (ngx_close_socket(s) == -1) { + ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno, + "multiport, " ngx_close_socket_n " %V failed", + &ls->addr_text); + } + + return NGX_ERROR; + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_CORE, log, 0, + "multiport, bind() %V #%d ", &ls->addr_text, s); + + if (bind(s, ls->sockaddr, ls->socklen) == -1) { + ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno, + "multiport, bind() to %V failed", &ls->addr_text); + + if (ngx_close_socket(s) == -1) { + ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno, + "multiport, " ngx_close_socket_n " %V failed", + &ls->addr_text); + } + + return NGX_ERROR; + } + +#if (NGX_HAVE_UNIX_DOMAIN) + + if (ls->sockaddr->sa_family == AF_UNIX) { + mode_t mode; + u_char *name; + + name = ls->addr_text.data + sizeof("unix:") - 1; + mode = (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH); + + if (chmod((char *) name, mode) == -1) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno, + "multiport, chmod() \"%s\" failed", name); + } + } +#endif + + if (ls->type != SOCK_STREAM) { + ls->fd = s; + continue; + } + + if (listen(s, ls->backlog) == -1) { + err = ngx_socket_errno; + + /* + * on OpenVZ after suspend/resume EADDRINUSE + * may be returned by listen() instead of bind(), see + * https://bugzilla.openvz.org/show_bug.cgi?id=2470 + */ + + if (ngx_close_socket(s) == -1) { + ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno, + "multiport, " ngx_close_socket_n " %V failed", + &ls->addr_text); + } + + if (err != NGX_EADDRINUSE) { + ngx_log_error(NGX_LOG_EMERG, log, err, + "multiport, listen() to %V, backlog %d failed", + &ls->addr_text, ls->backlog); + return NGX_ERROR; + } + + failed = 1; + } + + ls->listen = 1; + ls->fd = s; + + if (!failed) { + break; + } + + /* TODO: delay configurable */ + + ngx_log_error(NGX_LOG_NOTICE, log, 0, + "multiport, try again to bind() after 500ms"); + + ngx_msleep(500); + } + + if (failed) { + ngx_log_error(NGX_LOG_EMERG, log, 0, + "multiport, still could not bind()"); + return NGX_ERROR; + } + + return NGX_OK; +} + +static ngx_int_t +ngx_event_multiport_configure_listening_socket(ngx_cycle_t *cycle, + ngx_listening_t *ls) +{ + int value; + +#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER) + struct accept_filter_arg af; +#endif + + ls->log = *ls->logp; + + if (ls->rcvbuf != -1) { + if (setsockopt(ls->fd, SOL_SOCKET, SO_RCVBUF, + (const void *) &ls->rcvbuf, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "multiport, setsockopt(SO_RCVBUF, %d) %V failed," + " ignored", ls->rcvbuf, &ls->addr_text); + } + } + + if (ls->sndbuf != -1) { + if (setsockopt(ls->fd, SOL_SOCKET, SO_SNDBUF, + (const void *) &ls->sndbuf, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "multiport, setsockopt(SO_SNDBUF, %d) %V failed," + " ignored", ls->sndbuf, &ls->addr_text); + } + } + + if (ls->keepalive) { + value = (ls->keepalive == 1) ? 1 : 0; + + if (setsockopt(ls->fd, SOL_SOCKET, SO_KEEPALIVE, + (const void *) &value, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "multiport, setsockopt(SO_KEEPALIVE, %d) %V failed," + " ignored", value, &ls->addr_text); + } + } + +#if (NGX_HAVE_KEEPALIVE_TUNABLE) + + if (ls->keepidle) { + value = ls->keepidle; + +#if (NGX_KEEPALIVE_FACTOR) + value *= NGX_KEEPALIVE_FACTOR; +#endif + + if (setsockopt(ls->fd, IPPROTO_TCP, TCP_KEEPIDLE, + (const void *) &value, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "multiport, setsockopt(TCP_KEEPIDLE, %d) %V failed," + " ignored", value, &ls->addr_text); + } + } + + if (ls->keepintvl) { + value = ls->keepintvl; + +#if (NGX_KEEPALIVE_FACTOR) + value *= NGX_KEEPALIVE_FACTOR; +#endif + + if (setsockopt(ls->fd, IPPROTO_TCP, TCP_KEEPINTVL, + (const void *) &value, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "multiport, setsockopt(TCP_KEEPINTVL, %d) %V failed," + " ignored", value, &ls->addr_text); + } + } + + if (ls->keepcnt) { + if (setsockopt(ls->fd, IPPROTO_TCP, TCP_KEEPCNT, + (const void *) &ls->keepcnt, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "multiport, setsockopt(TCP_KEEPCNT, %d) %V failed," + " ignored", ls->keepcnt, &ls->addr_text); + } + } + +#endif + +#if (NGX_HAVE_SETFIB) + if (ls->setfib != -1) { + if (setsockopt(ls->fd, SOL_SOCKET, SO_SETFIB, + (const void *) &ls->setfib, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "multiport, setsockopt(SO_SETFIB, %d) %V failed," + " ignored", ls->setfib, &ls->addr_text); + } + } +#endif + +#if (NGX_HAVE_TCP_FASTOPEN) + if (ls->fastopen != -1) { + if (setsockopt(ls->fd, IPPROTO_TCP, TCP_FASTOPEN, + (const void *) &ls->fastopen, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "multiport, setsockopt(TCP_FASTOPEN, %d) %V failed," + " ignored", ls->fastopen, &ls->addr_text); + } + } +#endif + + if (ls->listen) { + + /* change backlog via listen() */ + + if (listen(ls->fd, ls->backlog) == -1) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "multiport, listen() to %V, backlog %d failed", + &ls->addr_text, ls->backlog); + return NGX_ERROR; + } + } + + /* + * setting deferred mode should be last operation on socket, + * because code may prematurely continue cycle on failure + */ + +#if (NGX_HAVE_DEFERRED_ACCEPT) + +#ifdef SO_ACCEPTFILTER + + if (ls->delete_deferred) { + if (setsockopt(ls->fd, SOL_SOCKET, SO_ACCEPTFILTER, NULL, 0) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "multiport, setsockopt(SO_ACCEPTFILTER, NULL) " + "for %V failed, ignored", &ls->addr_text); + + if (ls->accept_filter) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, + "multiport, could not change the accept filter " + "to \"%s\" for %V, ignored", + ls->accept_filter, &ls->addr_text); + } + + continue; + } + + ls->deferred_accept = 0; + } + + if (ls->add_deferred) { + ngx_memzero(&af, sizeof(struct accept_filter_arg)); + (void) ngx_cpystrn((u_char *) af.af_name, + (u_char *) ls->accept_filter, 16); + + if (setsockopt(ls->fd, SOL_SOCKET, SO_ACCEPTFILTER, + &af, sizeof(struct accept_filter_arg)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "multiport, setsockopt(SO_ACCEPTFILTER, \"%s\") " + "for %V failed, ignored", + ls->accept_filter, &ls->addr_text); + continue; + } + + ls->deferred_accept = 1; + } + +#endif + +#ifdef TCP_DEFER_ACCEPT + + if (ls->add_deferred || ls->delete_deferred) { + + if (ls->add_deferred) { + /* + * There is no way to find out how long a connection was + * in queue (and a connection may bypass deferred queue at all + * if syncookies were used), hence we use 1 second timeout + * here. + */ + value = 1; + + } else { + value = 0; + } + + if (setsockopt(ls->fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, + &value, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "multiport, setsockopt(TCP_DEFER_ACCEPT, %d)" + " for %V failed, ignored", value, &ls->addr_text); + + return NGX_ERROR; + } + } + + if (ls->add_deferred) { + ls->deferred_accept = 1; + } + +#endif + +#endif /* NGX_HAVE_DEFERRED_ACCEPT */ + +#if (NGX_HAVE_IP_RECVDSTADDR) + + if (ls->wildcard + && ls->type == SOCK_DGRAM + && ls->sockaddr->sa_family == AF_INET) + { + value = 1; + + if (setsockopt(ls->fd, IPPROTO_IP, IP_RECVDSTADDR, + (const void *) &value, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "multiport, setsockopt(IP_RECVDSTADDR) " + "for %V failed, ignored", &ls->addr_text); + } + } + +#elif (NGX_HAVE_IP_PKTINFO) + + if (ls->wildcard + && ls->type == SOCK_DGRAM + && ls->sockaddr->sa_family == AF_INET) + { + value = 1; + + if (setsockopt(ls->fd, IPPROTO_IP, IP_PKTINFO, + (const void *) &value, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "multiport, setsockopt(IP_PKTINFO) " + "for %V failed, ignored", &ls->addr_text); + } + } + +#endif + +#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO) + + if (ls->wildcard + && ls->type == SOCK_DGRAM + && ls->sockaddr->sa_family == AF_INET6) + { + value = 1; + + if (setsockopt(ls->fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, + (const void *) &value, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "multiport, setsockopt(IPV6_RECVPKTINFO) " + "for %V failed, ignored", &ls->addr_text); + } + } + +#endif + + return NGX_OK; +} + +static void +ngx_event_multiport_close_listening_sock(ngx_cycle_t *cycle, + ngx_listening_t *ls) +{ + ngx_connection_t *c; + + c = ls->connection; + + if (c) { + if (c->read->active) { + if (ngx_event_flags & NGX_USE_EPOLL_EVENT) { + + /* + * it seems that Linux-2.6.x OpenVZ sends events + * for closed shared listening sockets unless + * the events was explicitly deleted + */ + + ngx_del_event(c->read, NGX_READ_EVENT, 0); + + } else { + ngx_del_event(c->read, NGX_READ_EVENT, NGX_CLOSE_EVENT); + } + } + + ngx_free_connection(c); + + c->fd = (ngx_socket_t) -1; + } + + ngx_log_debug2(NGX_LOG_DEBUG_CORE, cycle->log, 0, + "multiport, close listening %V #%d", &ls->addr_text, ls->fd); + + if (ngx_close_socket(ls->fd) == -1) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno, + "multiport, ", ngx_close_socket_n " %V failed", + &ls->addr_text); + } + +#if (NGX_HAVE_UNIX_DOMAIN) + + if (ls->sockaddr->sa_family == AF_UNIX + && ngx_process == NGX_PROCESS_WORKER) + { + u_char *name = ls->addr_text.data + sizeof("unix:") - 1; + + if (ngx_delete_file(name) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno, + "multiport, " ngx_delete_file_n " %s failed", name); + } + } + +#endif + + ls->fd = (ngx_socket_t) -1; +} + +static ngx_listening_t * +ngx_event_multiport_find_relation_port(ngx_cycle_t *cycle, ngx_str_t *str) +{ + ngx_url_t u; + ngx_listening_t *ls; + ngx_uint_t i; + + ngx_memzero(&u, sizeof(ngx_url_t)); + + u.url = *str; + u.listen = 1; + u.default_port = 0; + + if (ngx_parse_url(cycle->pool, &u) != NGX_OK) { + if (u.err) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, + "multiport, relation port err: %V", str); + } + + return NULL; + } + + ls = cycle->listening.elts; + + for (i = 0; i < cycle->listening.nelts; ++i) { + + if (ls[i].socklen == u.socklen + && ngx_memcmp(ls[i].sockaddr, &u.sockaddr, u.socklen) == 0) + { + return &ls[i]; + } + } + + ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, + "multiport, can not find relation port: %V", str); + return NULL; +} + +static ngx_int_t +ngx_event_multiport_set_port(ngx_cycle_t *cycle, + ngx_event_multiport_listen_t *mls, ngx_listening_t *ls) +{ + ngx_str_t port; + ngx_int_t rc; + ngx_url_t u; + + ngx_memzero(&port, sizeof(ngx_str_t)); + + rc = ngx_multiport_get_port(cycle->pool, &port, &mls->multiport, + ngx_process_slot); + if (rc == NGX_ERROR) { + ngx_log_error(NGX_LOG_ERR, cycle->log, 0, + "multiport, get multiport error"); + return NGX_ERROR; + } + + ngx_memzero(&u, sizeof(ngx_url_t)); + u.url = port; + u.listen = 1; + u.default_port = 0; + + if (ngx_parse_url(cycle->pool, &u) != NGX_OK) { + if (u.err) { + ngx_log_error(NGX_LOG_ERR, cycle->log, 0, + "multiport, %s in \"%V\" when set port", u.err, &u.url); + } + + return NGX_ERROR; + } + + /* init multiport listening */ + if (ngx_event_multiport_init_listening(cycle, &mls->listening, + (struct sockaddr *) &u.sockaddr, u.socklen, ls) != NGX_OK) + { + ngx_log_error(NGX_LOG_ERR, cycle->log, 0, + "multiport, init listening error: %V", &mls->multiport); + return NGX_ERROR; + } + + /* open multiport listening */ + if (ngx_event_multiport_open_listening_sock(cycle, &mls->listening) + != NGX_OK) + { + ngx_log_error(NGX_LOG_ERR, cycle->log, 0, + "multiport, open listening error: %V", &mls->multiport); + return NGX_ERROR; + } + + /* config multiport listening */ + if (ngx_event_multiport_configure_listening_socket(cycle, + &mls->listening) != NGX_OK) + { + return NGX_ERROR; + } + + return NGX_OK; +} + +static ngx_int_t +ngx_event_multiport_enable_accept_event(ngx_cycle_t *cycle, ngx_listening_t *ls) +{ + ngx_connection_t *c; + ngx_event_t *rev; + + c = ngx_get_connection(ls->fd, ngx_cycle->log); + + if (c == NULL) { + return NGX_ERROR; + } + + c->type = ls->type; + c->log = &ls->log; + + c->listening = ls; + ls->connection = c; + + rev = c->read; + + rev->log = c->log; + rev->accept = 1; + +#if (NGX_HAVE_DEFERRED_ACCEPT) + rev->deferred_accept = ls->deferred_accept; +#endif + +#if (NGX_WIN32) + + if (ngx_event_flags & NGX_USE_IOCP_EVENT) { + ngx_iocp_conf_t *iocpcf; + + rev->handler = ngx_event_acceptex; + + if (ngx_add_event(rev, 0, NGX_IOCP_ACCEPT) == NGX_ERROR) { + return NGX_ERROR; + } + + ls->log.handler = ngx_acceptex_log_error; + + iocpcf = ngx_event_get_conf(cycle->conf_ctx, ngx_iocp_module); + if (ngx_event_post_acceptex(ls, iocpcf->post_acceptex) + == NGX_ERROR) + { + return NGX_ERROR; + } + + } else { + rev->handler = ngx_event_accept; + + if (ngx_use_accept_mutex) { + return NGX_OK; + } + + if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) { + return NGX_ERROR; + } + } + +#else + + rev->handler = (c->type == SOCK_STREAM)? ngx_event_accept + : ngx_event_recvmsg; + + if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) { + return NGX_ERROR; + } + +#endif + + return NGX_OK; +} + +static ngx_int_t +ngx_event_multiport_process_init(ngx_cycle_t *cycle) +{ + ngx_event_multiport_conf_t *emcf; + ngx_event_multiport_listen_t *mls; + ngx_listening_t *rls; + ngx_uint_t i; + + if (ngx_process != NGX_PROCESS_WORKER) { + return NGX_OK; + } + + emcf = ngx_event_get_conf(cycle->conf_ctx, ngx_event_multiport_module); + if (emcf->ports == NULL) { + return NGX_OK; + } + + mls = emcf->ports->elts; + for (i = 0; i < emcf->ports->nelts; ++i) { + /* find relation listening in cycle->listening */ + rls = ngx_event_multiport_find_relation_port(cycle, + &mls[i].relatedport); + if (rls == NULL) { + return NGX_ERROR; + } + + /* set process port with relation port */ + if (ngx_event_multiport_set_port(cycle, &mls[i], rls) != NGX_OK) { + return NGX_ERROR; + } + + /* enable accept */ + if (ngx_event_multiport_enable_accept_event(cycle, &mls[i].listening) + != NGX_OK) + { + ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno, + "multiport, enable accept failed"); + return NGX_ERROR; + } + } + + return NGX_OK; +} + +static void +ngx_event_multiport_process_exit(ngx_cycle_t *cycle) +{ + ngx_event_multiport_conf_t *emcf; + ngx_event_multiport_listen_t *mls; + ngx_uint_t i; + + if (ngx_process != NGX_PROCESS_WORKER) { + return; + } + + emcf = ngx_event_get_conf(cycle->conf_ctx, ngx_event_multiport_module); + if (emcf->ports == NULL) { + return; + } + + mls = emcf->ports->elts; + + for (i = 0; i < emcf->ports->nelts; ++i) { + ngx_event_multiport_close_listening_sock(cycle, &mls[i].listening); + } +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/ngx_http_broadcast_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/ngx_http_broadcast_module.c new file mode 100644 index 0000000000..5bce05f1f4 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/ngx_http_broadcast_module.c @@ -0,0 +1,263 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#include +#include +#include +#include "ngx_multiport.h" + + +typedef struct { + ngx_str_t multiport; + ngx_str_t uri; +} ngx_http_broadcast_conf_t; + +typedef struct { + ngx_int_t workerid; + ngx_http_request_t *sr; +} ngx_http_broadcast_ctx_t; + + +static ngx_int_t ngx_http_broadcast_filter_init(ngx_conf_t *cf); + +static void *ngx_http_broadcast_create_conf(ngx_conf_t *cf); +static char *ngx_http_broadcast_merge_conf(ngx_conf_t *cf, + void *parent, void *child); +static char *ngx_http_broadcast(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + + +static ngx_command_t ngx_http_broadcast_commands[] = { + + { ngx_string("broadcast"), + NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2, + ngx_http_broadcast, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_http_broadcast_module_ctx = { + NULL, /* preconfiguration */ + ngx_http_broadcast_filter_init, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + ngx_http_broadcast_create_conf, /* create location configuration */ + ngx_http_broadcast_merge_conf /* merge location configuration */ +}; + + +ngx_module_t ngx_http_broadcast_module = { + NGX_MODULE_V1, + &ngx_http_broadcast_module_ctx, /* module context */ + ngx_http_broadcast_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_http_output_header_filter_pt ngx_http_next_header_filter; +static ngx_http_output_body_filter_pt ngx_http_next_body_filter; + + +static void * +ngx_http_broadcast_create_conf(ngx_conf_t *cf) +{ + ngx_http_broadcast_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_broadcast_conf_t)); + if (conf == NULL) { + return NULL; + } + + return conf; +} + +static char * +ngx_http_broadcast_merge_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_http_broadcast_conf_t *prev = parent; + ngx_http_broadcast_conf_t *conf = child; + + ngx_conf_merge_str_value(conf->multiport, prev->multiport, ""); + ngx_conf_merge_str_value(conf->uri, prev->uri, ""); + + return NGX_CONF_OK; +} + +static char * +ngx_http_broadcast(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_broadcast_conf_t *hbcf; + ngx_str_t *value; + + hbcf = conf; + + if (hbcf->multiport.data != NULL) { + return "is duplicate"; + } + + value = cf->args->elts; + + hbcf->multiport = value[1]; + hbcf->uri = value[2]; + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_http_broadcast_header_filter(ngx_http_request_t *r) +{ + ngx_http_broadcast_conf_t *hbcf; + + hbcf = ngx_http_get_module_loc_conf(r, ngx_http_broadcast_module); + + if (hbcf == NULL || hbcf->multiport.len == 0) { /* not configured */ + goto next; + } + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "broadcast header filter, r:%p r->main:%p, %ui, %O", + r, r->main, r->headers_out.status, r->headers_out.content_length_n); + + r->headers_out.status = NGX_HTTP_OK; + ngx_http_clear_content_length(r); + ngx_http_clear_accept_ranges(r); + +next: + return ngx_http_next_header_filter(r); +} + + +static ngx_int_t +ngx_http_broadcast_send_subrequest(ngx_http_request_t *r, ngx_int_t pslot) +{ + ngx_http_broadcast_conf_t *hbcf; + ngx_str_t uri; + ngx_str_t port; + ngx_http_request_t *sr; + ngx_int_t rc; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "broadcast send subrequest to %i", pslot); + + hbcf = ngx_http_get_module_loc_conf(r, ngx_http_broadcast_module); + + if (ngx_multiport_get_port(r->pool, &port, &hbcf->multiport, pslot) + == NGX_ERROR) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "broadcast get port error, %V %i", &hbcf->multiport, pslot); + return NGX_ERROR; + } + + uri.len = hbcf->uri.len + 1 + port.len; + uri.data = ngx_pcalloc(r->pool, uri.len); + ngx_snprintf(uri.data, uri.len, "%V/%V", &hbcf->uri, &port); + + rc = ngx_http_subrequest(r, &uri, &r->args, &sr, NULL, 0); + sr->method = r->method; + sr->method_name = r->method_name; + + return rc; +} + +static ngx_int_t +ngx_http_broadcast_body_filter(ngx_http_request_t *r, ngx_chain_t *in) +{ + ngx_http_broadcast_conf_t *hbcf; + ngx_http_broadcast_ctx_t *ctx; + ngx_core_conf_t *ccf; + ngx_int_t rc; + ngx_buf_t *b; + ngx_chain_t cl; + + hbcf = ngx_http_get_module_loc_conf(r->main, ngx_http_broadcast_module); + + if (hbcf == NULL || hbcf->multiport.len == 0) { /* not configured */ + return ngx_http_next_body_filter(r, in); + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "broadcast body filter, r:%p r->main:%p", r, r->main); + + if (r != r->main) { /* send subrequest */ + if (r->headers_out.status != NGX_HTTP_OK) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "broadcast subrequest send non 200 response: %i", + r->headers_out.status); + return NGX_OK; + } + + return ngx_http_next_body_filter(r, in); + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_broadcast_module); + + if (ctx == NULL) { + ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_broadcast_ctx_t)); + if (ctx == NULL) { + return NGX_ERROR; + } + + ngx_http_set_ctx(r, ctx, ngx_http_broadcast_module); + } + + /* send to all process */ + + ccf = (ngx_core_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_core_module); + + while (ctx->workerid < ccf->worker_processes) { + + rc = ngx_http_broadcast_send_subrequest(r, + ngx_multiport_get_slot(ctx->workerid)); + ++ctx->workerid; + + return rc; + } + + b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t)); + + if (b == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + b->last_buf = 1; + + cl.buf = b; + cl.next = NULL; + + return ngx_http_next_body_filter(r, &cl); +} + + +static ngx_int_t +ngx_http_broadcast_filter_init(ngx_conf_t *cf) +{ + ngx_http_next_header_filter = ngx_http_top_header_filter; + ngx_http_top_header_filter = ngx_http_broadcast_header_filter; + + ngx_http_next_body_filter = ngx_http_top_body_filter; + ngx_http_top_body_filter = ngx_http_broadcast_body_filter; + + return NGX_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/ngx_http_inner_proxy_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/ngx_http_inner_proxy_module.c new file mode 100644 index 0000000000..2991b0e218 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/ngx_http_inner_proxy_module.c @@ -0,0 +1,254 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#include +#include +#include +#include "ngx_multiport.h" + + +typedef struct { + ngx_str_t multiport; + ngx_str_t uri; +} ngx_http_inner_proxy_conf_t; + +typedef struct { + ngx_str_t port; + ngx_flag_t last; +} ngx_http_inner_proxy_ctx_t; + + +static ngx_int_t ngx_http_inner_proxy_filter_init(ngx_conf_t *cf); + +static void *ngx_http_inner_proxy_create_conf(ngx_conf_t *cf); +static char *ngx_http_inner_proxy_merge_conf(ngx_conf_t *cf, + void *parent, void *child); +static char *ngx_http_inner_proxy(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + + +static ngx_command_t ngx_http_inner_proxy_commands[] = { + + { ngx_string("inner_proxy"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2, + ngx_http_inner_proxy, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_http_inner_proxy_module_ctx = { + NULL, /* preconfiguration */ + ngx_http_inner_proxy_filter_init, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + ngx_http_inner_proxy_create_conf, /* create location configuration */ + ngx_http_inner_proxy_merge_conf /* merge location configuration */ +}; + + +ngx_module_t ngx_http_inner_proxy_module = { + NGX_MODULE_V1, + &ngx_http_inner_proxy_module_ctx, /* module context */ + ngx_http_inner_proxy_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_http_output_header_filter_pt ngx_http_next_header_filter; +static ngx_http_output_body_filter_pt ngx_http_next_body_filter; + + +static void * +ngx_http_inner_proxy_create_conf(ngx_conf_t *cf) +{ + ngx_http_inner_proxy_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_inner_proxy_conf_t)); + if (conf == NULL) { + return NULL; + } + + return conf; +} + +static char * +ngx_http_inner_proxy_merge_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_http_inner_proxy_conf_t *prev = parent; + ngx_http_inner_proxy_conf_t *conf = child; + + ngx_conf_merge_str_value(conf->multiport, prev->multiport, ""); + ngx_conf_merge_str_value(conf->uri, prev->uri, ""); + + return NGX_CONF_OK; +} + +static char * +ngx_http_inner_proxy(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_inner_proxy_conf_t *hipcf; + ngx_str_t *value; + + hipcf = conf; + + if (hipcf->multiport.data != NULL) { + return "is duplicate"; + } + + value = cf->args->elts; + + hipcf->multiport = value[1]; + hipcf->uri = value[2]; + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_http_inner_proxy_header_filter(ngx_http_request_t *r) +{ + ngx_http_inner_proxy_ctx_t *ctx; + + ctx = ngx_http_get_module_ctx(r->main, ngx_http_inner_proxy_module); + + if (ctx == NULL) { /* not configured */ + return ngx_http_next_header_filter(r); + } + + if (r == r->main) { + return NGX_OK; + } + + r->main->headers_out = r->headers_out; + + return ngx_http_next_header_filter(r->main); +} + + +static ngx_int_t +ngx_http_inner_proxy_body_filter(ngx_http_request_t *r, ngx_chain_t *in) +{ + ngx_http_inner_proxy_ctx_t *ctx; + ngx_chain_t *cl, l; + ngx_buf_t *b; + + ctx = ngx_http_get_module_ctx(r->main, ngx_http_inner_proxy_module); + + if (ctx == NULL) { /* not configured */ + return ngx_http_next_body_filter(r, in); + } + + if (r == r->main) { + if (ctx->last == 0) { + return NGX_OK; + } + + b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t)); + + if (b == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + b->last_buf = 1; + + l.buf = b; + l.next = NULL; + + return ngx_http_next_body_filter(r, &l); + } + + for (cl = in; cl; cl = cl->next) { + if (cl->buf->last_in_chain) { + ctx->last = 1; + } + } + + return ngx_http_next_body_filter(r, in); +} + + +static ngx_int_t +ngx_http_inner_proxy_filter_init(ngx_conf_t *cf) +{ + ngx_http_next_header_filter = ngx_http_top_header_filter; + ngx_http_top_header_filter = ngx_http_inner_proxy_header_filter; + + ngx_http_next_body_filter = ngx_http_top_body_filter; + ngx_http_top_body_filter = ngx_http_inner_proxy_body_filter; + + return NGX_OK; +} + + +ngx_int_t +ngx_http_inner_proxy_request(ngx_http_request_t *r, ngx_int_t pslot) +{ + ngx_http_inner_proxy_conf_t *hipcf; + ngx_http_inner_proxy_ctx_t *ctx; + ngx_http_request_t *sr; + ngx_str_t uri; + ngx_int_t rc; + + hipcf = ngx_http_get_module_loc_conf(r, ngx_http_inner_proxy_module); + + if (hipcf == NULL || hipcf->multiport.len == 0) { /* not configured */ + return NGX_DECLINED; + } + + if (pslot == ngx_process_slot) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "inner proxy send request to self: %i", ngx_process_slot); + return NGX_DECLINED; + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_inner_proxy_module); + if (ctx) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "inner proxy has been called in this request"); + return NGX_ERROR; + } + + ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_inner_proxy_ctx_t)); + if (ctx == NULL) { + return NGX_ERROR; + } + ngx_http_set_ctx(r, ctx, ngx_http_inner_proxy_module); + + if (ngx_multiport_get_port(r->pool, &ctx->port, &hipcf->multiport, pslot) + == NGX_ERROR) + { + return NGX_ERROR; + } + + uri.len = hipcf->uri.len + 1 + ctx->port.len + 2 + r->uri.len; + uri.data = ngx_pcalloc(r->pool, uri.len); + ngx_snprintf(uri.data, uri.len, "%V/%V:/%V", + &hipcf->uri, &ctx->port, &r->uri); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "inner proxy send request to %V", &ctx->port); + rc = ngx_http_subrequest(r, &uri, &r->args, &sr, NULL, 0); + sr->method = r->method; + sr->method_name = r->method_name; + + return rc; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/ngx_multiport.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/ngx_multiport.h new file mode 100644 index 0000000000..d3fc86bec0 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/ngx_multiport.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#ifndef _NGX_MULTIPORT_H_INCLUDED_ +#define _NGX_MULTIPORT_H_INCLUDED_ + + +#include +#include +#include + + +/* + * return value: + * NGX_OK for success, NGX_ERROR for failed + * paras: + * pool: pool for port memory alloc + * port: process real listen port while process_slot is pslot + * multiport: port configure for processes, format as below: + * port only: port + * IPv4: host:port host must be ipaddr of IPv4 or * + * IPv6: [host]:port host must be ipaddr of IPv6 + * Unix: unix:/path + * pslot: process_slot + */ +ngx_int_t ngx_multiport_get_port(ngx_pool_t *pool, ngx_str_t *port, + ngx_str_t *multiport, ngx_int_t pslot); + + +/* + * return value: + * ngx_process_slot for successd, NGX_ERROR for failed + * paras: + * wpid: worker process id, 0 to ccf->worker_processes - 1 + */ +ngx_int_t ngx_multiport_get_slot(ngx_uint_t wpid); + + +/* + * return value: + * NGX_OK : for successd + * NGX_ERROR : for failed + * NGX_DECLINED: for not configured or send inner proxy to self + * paras: + * r : http request for send inner request to sibling worker + * pslot: sibling worker ngx_process_slot + */ +ngx_int_t ngx_http_inner_proxy_request(ngx_http_request_t *r, ngx_int_t pslot); + + +#endif diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/ngx_multiport_misc.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/ngx_multiport_misc.c new file mode 100644 index 0000000000..98ec0eb5c4 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/ngx_multiport_misc.c @@ -0,0 +1,165 @@ +#include +#include + + +static ngx_int_t +ngx_multiport_get_port_unix(ngx_pool_t *pool, ngx_str_t *port, + ngx_str_t *multiport, ngx_int_t pslot) +{ +#if (NGX_HAVE_UNIX_DOMAIN) + u_char *p; + size_t len; + + len = multiport->len + 5; /* unix:/path -> unix:/path.127\0 */ + port->data = ngx_pcalloc(pool, len); + if (port->data == NULL) { + return NGX_ERROR; + } + + p = ngx_snprintf(port->data, len, "%V.%i", multiport, pslot); + *p = 0; + port->len = p - port->data; + + return NGX_OK; +#else + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, + "the unix domain sockets not support"); + return NGX_ERROR; +#endif +} + +static ngx_int_t +ngx_multiport_get_port_inet6(ngx_pool_t *pool, ngx_str_t *port, + ngx_str_t *multiport, ngx_int_t pslot) +{ +#if (NGX_HAVE_INET6) + u_char *p, *last; + ngx_str_t addr; + size_t len; + ngx_int_t n; + + last = multiport->data + multiport->len; + p = ngx_strlchr(multiport->data, last, ']'); + + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "invalid INET6 host"); + return NGX_ERROR; + } + + ++p; + if (p == last || *p != ':') { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "no INET6 port"); + return NGX_ERROR; + } + + ++p; + addr.data = multiport->data; + addr.len = p - multiport->data; + + len = last - p; + n = ngx_atoi(p, len); + + /* 65408 + 127 = 65535, pslot in [0, 127] */ + if (n < 1 || n + pslot > 65408) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "invalid INET6 port"); + return NGX_ERROR; + } + n += pslot; + + len = multiport->len + 3; /* [::]:1 -> [::]:128\0 */ + port->data = ngx_pcalloc(pool, len); + if (port->data == NULL) { + return NGX_ERROR; + } + + p = port->data; + p = ngx_snprintf(p, len, "%V%i", &addr, n); + port->len = p - port->data; + + return NGX_OK; +#else + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, + "the INET6 sockets not support"); + return NGX_ERROR; +#endif +} + +static ngx_int_t +ngx_multiport_get_port_inet(ngx_pool_t *pool, ngx_str_t *port, + ngx_str_t *multiport, ngx_int_t pslot) +{ + u_char *p, *last; + ngx_str_t addr; + size_t len; + ngx_int_t n; + + last = multiport->data + multiport->len; + p = ngx_strlchr(multiport->data, last, ':'); + + if (p == NULL) { /* port */ + p = multiport->data; + addr.len = 0; + } else { /* host:port */ + ++p; + if (p == last) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "no port"); + return NGX_ERROR; + } + + addr.data = multiport->data; + addr.len = p - multiport->data; + } + + len = last - p; + n = ngx_atoi(p, len); + + /* 65408 + 127 = 65535, pslot in [0, 127] */ + if (n < 1 || n + pslot > 65408) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "invalid port"); + return NGX_ERROR; + } + n += pslot; + + len = multiport->len + 3; /* 127.0.0.1:1 -> 127.0.0.1:128\0 */ + port->data = ngx_pcalloc(pool, len); + if (port->data == NULL) { + return NGX_ERROR; + } + + p = port->data; + if (addr.len == 0) { + p = ngx_snprintf(p, len, "%i", n); + } else { + p = ngx_snprintf(p, len, "%V%i", &addr, n); + } + port->len = p - port->data; + + return NGX_OK; +} + +ngx_int_t +ngx_multiport_get_port(ngx_pool_t *pool, ngx_str_t *port, + ngx_str_t *multiport, ngx_int_t pslot) +{ + u_char *p; + size_t len; + + p = multiport->data; + len = multiport->len; + + if (pslot < 0 || pslot > 127) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "invalid pslot: %i", + pslot); + return NGX_ERROR; + } + + if (len >= 5 && ngx_strncasecmp(p, (u_char *) "unix:", 5) == 0) { + return ngx_multiport_get_port_unix(pool, port, multiport, pslot); + } + + if (len && p[0] == '[') { + return ngx_multiport_get_port_inet6(pool, port, multiport, pslot); + } + + return ngx_multiport_get_port_inet(pool, port, multiport, pslot); +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/ngx_process_slot_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/ngx_process_slot_module.c new file mode 100644 index 0000000000..201281b50c --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/ngx_process_slot_module.c @@ -0,0 +1,167 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#include +#include + + +static ngx_int_t ngx_process_slot_module_init(ngx_cycle_t *cycle); +static ngx_int_t ngx_process_slot_process_init(ngx_cycle_t *cycle); +static void ngx_process_slot_process_exit(ngx_cycle_t *cycle); + +static void *ngx_process_slot_module_create_conf(ngx_cycle_t *cycle); +static char *ngx_process_slot_module_init_conf(ngx_cycle_t *cycle, void *conf); + + +#define MAX_PROCESSES 128 + +typedef struct { + ngx_atomic_int_t process_slot[MAX_PROCESSES]; +} ngx_process_slot_ctx_t; + +typedef struct { + ngx_process_slot_ctx_t *ctx; +} ngx_process_slot_conf_t; + + +static ngx_command_t ngx_process_slot_commands[] = { + + ngx_null_command +}; + + +static ngx_core_module_t ngx_process_slot_module_ctx = { + ngx_string("process_slot"), + ngx_process_slot_module_create_conf, + ngx_process_slot_module_init_conf +}; + + +ngx_module_t ngx_process_slot_module = { + NGX_MODULE_V1, + &ngx_process_slot_module_ctx, /* module context */ + ngx_process_slot_commands, /* module directives */ + NGX_CORE_MODULE, /* module type */ + NULL, /* init master */ + ngx_process_slot_module_init, /* init module */ + ngx_process_slot_process_init, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + ngx_process_slot_process_exit, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void * +ngx_process_slot_module_create_conf(ngx_cycle_t *cycle) +{ + ngx_process_slot_conf_t *pscf; + + pscf = ngx_palloc(cycle->pool, sizeof(ngx_process_slot_conf_t)); + if (pscf == NULL) { + return NULL; + } + + return pscf; +} + +static char * +ngx_process_slot_module_init_conf(ngx_cycle_t *cycle, void *conf) +{ + return NGX_CONF_OK; +} + +static ngx_int_t +ngx_process_slot_module_init(ngx_cycle_t *cycle) +{ + ngx_process_slot_conf_t *pscf; + ngx_shm_t shm; + ngx_uint_t i; + + pscf = (ngx_process_slot_conf_t *) ngx_get_conf(cycle->conf_ctx, + ngx_process_slot_module); + + shm.size = sizeof(ngx_process_slot_ctx_t); + shm.name.len = sizeof("process_slot_zone") - 1; + shm.name.data = (u_char *) "process_slot_zone"; + shm.log = cycle->log; + + if (ngx_shm_alloc(&shm) != NGX_OK) { + return NGX_ERROR; + } + + pscf->ctx = (ngx_process_slot_ctx_t *) shm.addr; + + for (i = 0; i < MAX_PROCESSES; ++i) { + pscf->ctx->process_slot[i] = -1; + } + + return NGX_OK; +} + +static ngx_int_t +ngx_process_slot_process_init(ngx_cycle_t *cycle) +{ + ngx_process_slot_conf_t *pscf; + ngx_process_slot_ctx_t *ctx; + + if (ngx_process != NGX_PROCESS_WORKER) { + return NGX_OK; + } + + pscf = (ngx_process_slot_conf_t *) ngx_get_conf(cycle->conf_ctx, + ngx_process_slot_module); + ctx = pscf->ctx; + + for (;;) { + if (ngx_atomic_cmp_set((ngx_atomic_t *) &ctx->process_slot[ngx_worker], + (ngx_atomic_uint_t)ctx->process_slot[ngx_worker], ngx_process_slot)) + { + break; + } + } + + return NGX_OK; +} + +static void +ngx_process_slot_process_exit(ngx_cycle_t *cycle) +{ + ngx_process_slot_conf_t *pscf; + ngx_process_slot_ctx_t *ctx; + + if (ngx_process != NGX_PROCESS_WORKER) { + return; + } + + pscf = (ngx_process_slot_conf_t *) ngx_get_conf(cycle->conf_ctx, + ngx_process_slot_module); + ctx = pscf->ctx; + + ngx_atomic_cmp_set((ngx_atomic_t *) &ctx->process_slot[ngx_worker], + (ngx_atomic_uint_t)ngx_process_slot, -1); +} + +ngx_int_t +ngx_multiport_get_slot(ngx_uint_t wpid) +{ + ngx_process_slot_conf_t *pscf; + ngx_process_slot_ctx_t *ctx; + ngx_core_conf_t *ccf; + + ccf = (ngx_core_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_core_module); + + if (wpid >= (ngx_uint_t)ccf->worker_processes) { + return -1; + } + + pscf = (ngx_process_slot_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_process_slot_module); + ctx = pscf->ctx; + + return ctx->process_slot[wpid]; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/ngx_stream_zone_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/ngx_stream_zone_module.c new file mode 100644 index 0000000000..94c2df679a --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/ngx_stream_zone_module.c @@ -0,0 +1,551 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#include +#include +#include + +typedef struct ngx_stream_zone_hash_s ngx_stream_zone_hash_t; +typedef struct ngx_stream_zone_node_s ngx_stream_zone_node_t; +typedef struct ngx_stream_zone_conf_s ngx_stream_zone_conf_t; + +static ngx_int_t +ngx_stream_zone_init_process(ngx_cycle_t *cycle); +static void +ngx_stream_zone_exit_process(ngx_cycle_t *cycle); +static void * +ngx_stream_zone_create_conf(ngx_cycle_t *cf); +static char * +ngx_stream_zone_init_conf(ngx_cycle_t *cycle, void *conf); +static char * +ngx_stream_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static char * +ngx_stream_zone_shm_init(ngx_shm_t *shm, ngx_stream_zone_conf_t *szcf, + ngx_cycle_t *cycle); + + +#define NAME_LEN 1024 + +static ngx_str_t stream_zone_key = ngx_string("stream_zone"); + +struct ngx_stream_zone_node_s { + u_char name[NAME_LEN]; + ngx_int_t slot; /* process slot */ + ngx_int_t idx; + ngx_int_t next; /* idx of stream node */ +}; + +struct ngx_stream_zone_hash_s { + ngx_shmtx_t mutex; + ngx_shmtx_sh_t lock; + ngx_int_t node; /* idx of stream node */ +}; + +struct ngx_stream_zone_conf_s { + ngx_int_t nbuckets; + ngx_int_t nstreams; + + ngx_pool_t *pool; + + ngx_shmtx_t *mutex; + ngx_shmtx_sh_t *lock; + ngx_stream_zone_hash_t *hash; /* hash in shm */ + ngx_stream_zone_node_t *stream_node;/* node in shm */ + ngx_int_t *free_node; /* free node chain */ + ngx_int_t *alloc; /* node number in use*/ +}; + + +static ngx_command_t ngx_stream_zone_commands[] = { + + { ngx_string("stream_zone"), + NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE2, + ngx_stream_zone, + 0, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_core_module_t ngx_stream_zone_module_ctx = { + ngx_string("rtmp_stream_zone"), + ngx_stream_zone_create_conf, /* create conf */ + ngx_stream_zone_init_conf /* init conf */ +}; + + +ngx_module_t ngx_stream_zone_module = { + NGX_MODULE_V1, + &ngx_stream_zone_module_ctx, /* module context */ + ngx_stream_zone_commands, /* module directives */ + NGX_CORE_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + ngx_stream_zone_init_process, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + ngx_stream_zone_exit_process, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_stream_zone_node_t * +ngx_stream_zone_get_node(ngx_str_t *name, ngx_int_t pslot) +{ + ngx_stream_zone_conf_t *szcf; + ngx_stream_zone_node_t *node; + + szcf = (ngx_stream_zone_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_stream_zone_module); + + ngx_shmtx_lock(szcf->mutex); + + if (*szcf->free_node == -1) { + ngx_shmtx_unlock(szcf->mutex); + return NULL; + } + + node = &szcf->stream_node[*szcf->free_node]; + *szcf->free_node = node->next; + + *ngx_copy(node->name, name->data, ngx_min(NAME_LEN - 1, name->len)) = '\0'; + node->slot = pslot; + node->next = -1; + + ++*szcf->alloc; + + ngx_shmtx_unlock(szcf->mutex); + + return node; +} + +static void +ngx_stream_zone_put_node(ngx_int_t idx) +{ + ngx_stream_zone_conf_t *szcf; + ngx_stream_zone_node_t *node; + + szcf = (ngx_stream_zone_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_stream_zone_module); + + ngx_shmtx_lock(szcf->mutex); + + node = &szcf->stream_node[idx]; + + node->next = *szcf->free_node; + *szcf->free_node = idx; + + --*szcf->alloc; + + ngx_shmtx_unlock(szcf->mutex); +} + +static void * +ngx_stream_zone_create_conf(ngx_cycle_t *cf) +{ + ngx_stream_zone_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_zone_conf_t)); + if (conf == NULL) { + return NULL; + } + + conf->nbuckets = NGX_CONF_UNSET; + conf->nstreams = NGX_CONF_UNSET; + conf->pool = ngx_create_pool(4096, cf->log); + + return conf; +} + +static char * +ngx_stream_zone_init_conf(ngx_cycle_t *cycle, void *conf) +{ + size_t len; + ngx_shm_t shm; + ngx_stream_zone_conf_t *szcf = conf; + + ngx_conf_init_value(szcf->nbuckets, 512); + ngx_conf_init_value(szcf->nstreams, 40960); + + /* create shm zone */ + len = sizeof(ngx_shmtx_t) + sizeof(ngx_shmtx_sh_t) + + sizeof(ngx_stream_zone_hash_t) * szcf->nbuckets + + sizeof(ngx_stream_zone_node_t) * szcf->nstreams + + sizeof(ngx_int_t) + sizeof(ngx_int_t); + + shm.size = len; + shm.name = stream_zone_key; + shm.log = cycle->log; + + if (ngx_shm_alloc(&shm) != NGX_OK) { + return NGX_CONF_ERROR; + } + + return ngx_stream_zone_shm_init(&shm, szcf, cycle); +} + +static void +ngx_stream_zone_clear(ngx_cycle_t *cycle) +{ + ngx_stream_zone_conf_t *szcf; + volatile ngx_int_t idx, cur, next; + + szcf = (ngx_stream_zone_conf_t *) ngx_get_conf(cycle->conf_ctx, + ngx_stream_zone_module); + + if (szcf->nbuckets <= 0 || szcf->nstreams <= 0) { + return; + } + + for (idx = 0; idx < szcf->nbuckets; ++idx) { + + ngx_shmtx_lock(&szcf->hash[idx].mutex); + cur = -1; + + while (1) { + if (cur == -1) { + next = szcf->hash[idx].node; + } else { + next = szcf->stream_node[cur].next; + } + + if (next == -1) { + break; + } + + if (szcf->stream_node[next].slot == ngx_process_slot) { + if (cur == -1) { + szcf->hash[idx].node = szcf->stream_node[next].next; + } else { + szcf->stream_node[cur].next = szcf->stream_node[next].next; + } + + ngx_stream_zone_put_node(next); + continue; + } + + cur = next; + } + ngx_shmtx_unlock(&szcf->hash[idx].mutex); + } +} + +static ngx_int_t +ngx_stream_zone_init_process(ngx_cycle_t *cycle) +{ + ngx_stream_zone_clear(cycle); + + return NGX_OK; +} + +static void +ngx_stream_zone_exit_process(ngx_cycle_t *cycle) +{ + ngx_stream_zone_clear(cycle); +} + +static char * +ngx_stream_zone_shm_init(ngx_shm_t *shm, ngx_stream_zone_conf_t *szcf, + ngx_cycle_t *cycle) +{ + u_char *p; + ngx_int_t i, next; + + p = shm->addr; + + szcf->mutex = (ngx_shmtx_t *) p; + p += sizeof(ngx_shmtx_t); + + szcf->lock = (ngx_shmtx_sh_t *) p; + p += sizeof(ngx_shmtx_sh_t); + + szcf->hash = (ngx_stream_zone_hash_t *) p; + p += sizeof(ngx_stream_zone_hash_t) * szcf->nbuckets; + + szcf->stream_node = (ngx_stream_zone_node_t *) p; + p += sizeof(ngx_stream_zone_node_t) * szcf->nstreams; + + szcf->free_node = (ngx_int_t *) p; + p += sizeof(ngx_int_t); + + szcf->alloc = (ngx_int_t *) p; + + /* init shm zone */ +#if (NGX_HAVE_ATOMIC_OPS) + + p = NULL; + +#else + p = ngx_pnalloc(szcf->pool, cycle->lock_file.len + + stream_zone_key.len); + if (p == NULL) { + return NGX_CONF_ERROR; + } + *ngx_sprintf(p, "%V%V", &cycle->lock_file, &stream_zone_key) = 0; + +#endif + + if (ngx_shmtx_create(szcf->mutex, szcf->lock, p) != NGX_OK) { + return NGX_CONF_ERROR; + } + + for (i = 0; i < szcf->nbuckets; ++i) { +#if (NGX_HAVE_ATOMIC_OPS) + + p = NULL; + +#else + p = ngx_pnalloc(szcf->pool, cycle->lock_file.len + stream_zone_key.len + + NGX_INT32_LEN); + if (p == NULL) { + return NGX_CONF_ERROR; + } + *ngx_sprintf(p, "%V%V%d", &cycle->lock_file, + &stream_zone_key, i) = 0; + +#endif + + if (ngx_shmtx_create(&szcf->hash[i].mutex, &szcf->hash[i].lock, p) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + szcf->hash[i].node = -1; + } + + next = -1; + i = szcf->nstreams; + + do { + --i; + + szcf->stream_node[i].slot = -1; + szcf->stream_node[i].idx = i; + szcf->stream_node[i].next = next; + next = i; + } while (i); + + *szcf->free_node = i; + *szcf->alloc = 0; + + return NGX_CONF_OK; +} + +static char * +ngx_stream_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_uint_t i; + ngx_str_t *value; + ngx_stream_zone_conf_t *szcf = conf; + + value = cf->args->elts; + + for (i = 1; i < cf->args->nelts; ++i) { + + if (ngx_strncmp(value[i].data, "buckets=", 8) == 0) { + szcf->nbuckets = ngx_atoi(value[i].data + 8, value[i].len - 8); + + if (szcf->nbuckets <= 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid buckets \"%V\"", &value[i]); + return NGX_CONF_ERROR; + } + + continue; + } + + if (ngx_strncmp(value[i].data, "streams=", 8) == 0) { + szcf->nstreams = ngx_atoi(value[i].data + 8, value[i].len - 8); + + if (szcf->nstreams <= 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid streams \"%V\"", &value[i]); + return NGX_CONF_ERROR; + } + + continue; + } + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\"", &value[i]); + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + +ngx_int_t +ngx_stream_zone_insert_stream(ngx_str_t *name) +{ + ngx_stream_zone_conf_t *szcf; + volatile ngx_uint_t idx; + volatile ngx_int_t i, pslot; + ngx_stream_zone_node_t *node; + + szcf = (ngx_stream_zone_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_stream_zone_module); + + if (szcf->nbuckets <= 0 || szcf->nstreams <= 0) { + return NGX_ERROR; + } + + if (name->len >= NAME_LEN) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, + "stream name(%V) too long", name); + return NGX_ERROR; + } + + idx = ngx_hash_key(name->data, name->len) % szcf->nbuckets; + + ngx_shmtx_lock(&szcf->hash[idx].mutex); + i = szcf->hash[idx].node; + pslot = -1; + while (i != -1) { + if (ngx_strlen(szcf->stream_node[i].name) == name->len + && ngx_memcmp(szcf->stream_node[i].name, name->data, name->len) + == 0) + { + pslot = szcf->stream_node[i].slot; + break; + } + + i = szcf->stream_node[i].next; + } + + if (i == -1) { /* stream not in hash */ + node = ngx_stream_zone_get_node(name, ngx_process_slot); + if (node == NULL) { + ngx_shmtx_unlock(&szcf->hash[idx].mutex); + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, + "stream zone get node failed"); + return NGX_ERROR; + } + node->slot = ngx_process_slot; + + node->next = szcf->hash[idx].node; + szcf->hash[idx].node = node->idx; + + pslot = ngx_process_slot; + } + ngx_shmtx_unlock(&szcf->hash[idx].mutex); + + return pslot; +} + +void +ngx_stream_zone_delete_stream(ngx_str_t *name) +{ + ngx_stream_zone_conf_t *szcf; + volatile ngx_uint_t idx; + volatile ngx_int_t cur, next; + + szcf = (ngx_stream_zone_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_stream_zone_module); + + if (szcf->nbuckets <= 0 || szcf->nstreams <= 0) { + return; + } + + idx = ngx_hash_key(name->data, name->len) % szcf->nbuckets; + + ngx_shmtx_lock(&szcf->hash[idx].mutex); + cur = -1; + next = szcf->hash[idx].node; + while (next != -1) { + if (ngx_strlen(szcf->stream_node[next].name) == name->len + && ngx_memcmp(szcf->stream_node[next].name, name->data, name->len) + == 0) + { + if (szcf->stream_node[next].slot != ngx_process_slot) { + break; + } + + if (cur == -1) { /* link header */ + szcf->hash[idx].node = szcf->stream_node[next].next; + } else { + szcf->stream_node[cur].next = szcf->stream_node[next].next; + } + ngx_stream_zone_put_node(next); + break; + } + + cur = next; + next = szcf->stream_node[next].next; + } + ngx_shmtx_unlock(&szcf->hash[idx].mutex); +} + +ngx_chain_t * +ngx_stream_zone_state(ngx_http_request_t *r, ngx_flag_t detail) +{ + ngx_stream_zone_conf_t *szcf; + ngx_chain_t *cl; + ngx_buf_t *b; + size_t len; + volatile ngx_int_t idx, next; + + szcf = (ngx_stream_zone_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_stream_zone_module); + + if (szcf->nbuckets <= 0 || szcf->nstreams <= 0) { + return NULL; + } + + len = sizeof("##########stream zone state##########\n") - 1 + + sizeof("ngx_stream_zone buckets: \n") - 1 + NGX_OFF_T_LEN + + sizeof("ngx_stream_zone streams: \n") - 1 + NGX_OFF_T_LEN + + sizeof("ngx_stream_zone alloc: \n") - 1 + NGX_OFF_T_LEN; + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NULL; + } + cl->next = NULL; + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NULL; + } + cl->buf = b; + + b->last = ngx_snprintf(b->last, len, + "##########stream zone state##########\n" + "ngx_stream_zone buckets: %i\nngx_stream_zone streams: %i\n" + "ngx_stream_zone alloc: %i\n", + szcf->nbuckets, szcf->nstreams, *szcf->alloc); + + if (detail) { + for (idx = 0; idx < szcf->nbuckets; ++idx) { + ngx_shmtx_lock(&szcf->hash[idx].mutex); + + next = szcf->hash[idx].node; + if (next == -1) { + ngx_shmtx_unlock(&szcf->hash[idx].mutex); + continue; + } + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "slot: %i", idx); + + while (next != -1) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "\t\tname:%s, slot:%i, idx:%i, next:%i", + szcf->stream_node[next].name, + szcf->stream_node[next].slot, + szcf->stream_node[next].idx, + szcf->stream_node[next].next); + + next = szcf->stream_node[next].next; + } + + ngx_shmtx_unlock(&szcf->hash[idx].mutex); + } + } + + return cl; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/ngx_stream_zone_module.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/ngx_stream_zone_module.h new file mode 100644 index 0000000000..ba2d082d08 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/ngx_stream_zone_module.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#ifndef _NGX_STREAM_ZONE_MODULE_H_INCLUDED_ +#define _NGX_STREAM_ZONE_MODULE_H_INCLUDED_ + + +#include +#include +#include + + +/* + * return value: + * process_slot for owner of stream, NGX_ERROR for error + * name: + * stream name + */ +ngx_int_t ngx_stream_zone_insert_stream(ngx_str_t *name); + +/* + * name: + * stream name + */ +void ngx_stream_zone_delete_stream(ngx_str_t *name); + +/* + * return value: + * chain of stream zone state for returning to http client + * paras: + * r: http request to query status of rbuf + * detail: print stream detail in log + */ +ngx_chain_t *ngx_stream_zone_state(ngx_http_request_t *r, ngx_flag_t detail); + + +#endif diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/t/config b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/t/config new file mode 100644 index 0000000000..134ede979f --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/t/config @@ -0,0 +1,12 @@ +ngx_addon_name=ngx_multiport_test_module + +HTTP_MODULES="$HTTP_MODULES \ + ngx_stream_zone_test_module \ + ngx_multiport_test_module \ + " + +NGX_ADDON_SRCS="$NGX_ADDON_SRCS + $ngx_addon_dir/ngx_stream_zone_test_module.c \ + $ngx_addon_dir/ngx_multiport_test_module.c \ + " + diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/t/nginx.conf b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/t/nginx.conf new file mode 100644 index 0000000000..3745d421d2 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/t/nginx.conf @@ -0,0 +1,71 @@ + +user root; +worker_processes 4; + +#error_log logs/error.log; +#error_log logs/error.log notice; +error_log logs/error.log info; + +#pid logs/nginx.pid; + +stream_zone buckets=10007 streams=10000; + +events { + worker_connections 1024; + multi_listen 9000 80; + multi_listen unix:/tmp/http.sock.80 80; +} + + +http { + include mime.types; + default_type application/octet-stream; + + #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + # '$status $body_bytes_sent "$http_referer" ' + # '"$http_user_agent" "$http_x_forwarded_for"'; + + #access_log logs/access.log main; + + sendfile on; + #tcp_nopush on; + + #keepalive_timeout 0; + keepalive_timeout 65; + + #gzip on; + + server { + listen 80; + server_name localhost; + + location /stream_zone_test/ { + stream_zone_test; + } + + location /multiport_test/ { + inner_proxy unix:/tmp/http.sock.80 /inner_proxy; + multiport_test; + } + + location /inner_proxy/ { + rewrite ^/inner_proxy/(.*):/(.*) /$2 break; + proxy_pass http://$1:; + } + + location / { + broadcast unix:/tmp/http.sock.80 /auth_proxy; + } + + location /auth_proxy/ { + rewrite ^/auth_proxy/(.*) /auth break; + proxy_pass http://$1:; + } + + location /auth { + # return 403; + echo "auth"; + echo $scheme://$host$uri?$args; + } + } +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/t/ngx_multiport_test_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/t/ngx_multiport_test_module.c new file mode 100644 index 0000000000..b9c3025d1b --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/t/ngx_multiport_test_module.c @@ -0,0 +1,168 @@ +#include +#include +#include +#include "ngx_multiport.h" +#include "ngx_test_macro.h" + + +static char *ngx_multiport_test(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); + + +static ngx_command_t ngx_multiport_test_commands[] = { + + { ngx_string("multiport_test"), + NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS, + ngx_multiport_test, + 0, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_multiport_test_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_multiport_test_module = { + NGX_MODULE_V1, + &ngx_multiport_test_module_ctx, /* module context */ + ngx_multiport_test_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +ngx_int_t +ngx_multiport_test_get_port(ngx_http_request_t *r, char *multiport, + ngx_int_t pslot, char *expect) +{ + ngx_str_t port; + ngx_int_t rc; + ngx_str_t mp; + + mp.data = (u_char *) multiport; + mp.len = ngx_strlen(multiport); + + ngx_memzero(&port, sizeof(ngx_str_t)); + rc = ngx_multiport_get_port(r->pool, &port, &mp, pslot); + if (port.len) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "port: %V, %s, %d", + &port, port.data, port.len); + } + if (expect == NULL && rc == NGX_ERROR) { + return 1; + } else { + return ngx_test_str(&port, expect); + } + + return 0; +} + +static ngx_int_t +ngx_multiport_test_handler(ngx_http_request_t *r) +{ + ngx_buf_t *b; + ngx_chain_t cl; + size_t len; + ngx_int_t rc; + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "multiport test handler"); + + rc = ngx_http_inner_proxy_request(r, ngx_multiport_get_slot(0)); + if (rc != NGX_DECLINED) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "inner proxy return %i", rc); + return rc; + } + + NGX_TEST_INIT + + /* test ngx_multiport_get_port */ + /* normal format */ + NGX_TEST_ISOK(ngx_multiport_test_get_port(r, "10", 127, "137")); + NGX_TEST_ISOK(ngx_multiport_test_get_port(r, "127.0.0.1:55635", 4, + "127.0.0.1:55639")); + NGX_TEST_ISOK(ngx_multiport_test_get_port(r, "[::127.0.0.1]:1024", 0, + "[::127.0.0.1]:1024")); + NGX_TEST_ISOK(ngx_multiport_test_get_port(r, "unix:/tmp.test", 7, + "unix:/tmp.test.7")); + + /* inet6 format error */ + NGX_TEST_ISOK(ngx_multiport_test_get_port(r, "[::127.0.0.1:1024", 0, NULL)); + NGX_TEST_ISOK(ngx_multiport_test_get_port(r, "[::127.0.0.1]:abcd", + 0, NULL)); + NGX_TEST_ISOK(ngx_multiport_test_get_port(r, "[::127.0.0.1]:65409", + 0, NULL)); + NGX_TEST_ISOK(ngx_multiport_test_get_port(r, "[::127.0.0.1]:", 0, NULL)); + NGX_TEST_ISOK(ngx_multiport_test_get_port(r, "[::127.0.0.1]", 0, NULL)); + + /* inet format error */ + NGX_TEST_ISOK(ngx_multiport_test_get_port(r, "127.0.0.1:", 4, NULL)); + NGX_TEST_ISOK(ngx_multiport_test_get_port(r, "abcd", 4, NULL)); + NGX_TEST_ISOK(ngx_multiport_test_get_port(r, "65410", 4, NULL)); + + /* pslot error */ + NGX_TEST_ISOK(ngx_multiport_test_get_port(r, "65410", -1, NULL)); + NGX_TEST_ISOK(ngx_multiport_test_get_port(r, "65410", 128, NULL)); + + /* test ngx_multiport_get_slot */ + NGX_TEST_ISOK(ngx_multiport_get_slot(4) == -1); + NGX_TEST_ISOK(ngx_multiport_get_slot(0) == 0); + NGX_TEST_ISOK(ngx_multiport_get_slot(1) == 1); + NGX_TEST_ISOK(ngx_multiport_get_slot(2) == 2); + NGX_TEST_ISOK(ngx_multiport_get_slot(3) == 3); + + r->headers_out.status = NGX_HTTP_OK; + + ngx_http_send_header(r); + + len = sizeof("pslot: %i TEST cases 4294967296, 4294967296 pass\n") - 1; + b = ngx_create_temp_buf(r->pool, len); + + if (b == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + b->last = ngx_snprintf(b->last, len, "pslot: %i TEST cases %d, %d pass\n", + ngx_process_slot, count, pass); + b->last_buf = 1; + b->last_in_chain = 1; + + cl.buf = b; + cl.next = NULL; + + return ngx_http_output_filter(r, &cl); +} + + +static char * +ngx_multiport_test(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_core_loc_conf_t *clcf; + + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + clcf->handler = ngx_multiport_test_handler; + + return NGX_CONF_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/t/ngx_stream_zone_test_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/t/ngx_stream_zone_test_module.c new file mode 100644 index 0000000000..8adab4ff3b --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/t/ngx_stream_zone_test_module.c @@ -0,0 +1,134 @@ +#include +#include +#include +#include "ngx_stream_zone_module.h" + + +static char *ngx_stream_zone_test(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + +static ngx_command_t ngx_stream_zone_test_commands[] = { + + { ngx_string("stream_zone_test"), + NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS, + ngx_stream_zone_test, + 0, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_stream_zone_test_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_stream_zone_test_module = { + NGX_MODULE_V1, + &ngx_stream_zone_test_module_ctx, /* module context */ + ngx_stream_zone_test_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_int_t +ngx_stream_zone_test_handler(ngx_http_request_t *r) +{ + ngx_chain_t cl, *out; + ngx_buf_t *b; + size_t len; + ngx_str_t stream; + ngx_int_t rc; + + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "stream zone test handler"); + + rc = -1; + if (r->method == NGX_HTTP_GET) { + out = ngx_stream_zone_state(r, 1); + out->buf->last_buf = 1; + out->buf->last_in_chain = 1; + + r->headers_out.status = NGX_HTTP_OK; + ngx_http_send_header(r); + + return ngx_http_output_filter(r, out); + } else if (r->method == NGX_HTTP_DELETE) { + + if (ngx_http_arg(r, (u_char *) "stream", 6, &stream) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "stream zone test, no stream in http args"); + return NGX_HTTP_BAD_REQUEST; + } + + len = sizeof("delete stream=\n") - 1 + stream.len; + + ngx_stream_zone_delete_stream(&stream); + } else if (r->method == NGX_HTTP_POST) { + + if (ngx_http_arg(r, (u_char *) "stream", 6, &stream) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "stream zone test, no stream in http args"); + return NGX_HTTP_BAD_REQUEST; + } + + len = sizeof("stream= in process:4294967296\n") - 1 + stream.len; + + rc = ngx_stream_zone_insert_stream(&stream); + } else { + return NGX_HTTP_BAD_REQUEST; + } + + r->headers_out.status = NGX_HTTP_OK; + ngx_http_send_header(r); + + b = ngx_create_temp_buf(r->pool, len); + + if (b == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + if (r->method == NGX_HTTP_DELETE) { + b->last = ngx_snprintf(b->last, len, "delete stream=%V\n", &stream); + } else { + b->last = ngx_snprintf(b->last, len, + "stream=%V in process:%i\n", &stream, rc); + } + b->last_buf = 1; + b->last_in_chain = 1; + + cl.buf = b; + cl.next = NULL; + + return ngx_http_output_filter(r, &cl); +} + +static char * +ngx_stream_zone_test(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_core_loc_conf_t *clcf; + + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + clcf->handler = ngx_stream_zone_test_handler; + + return NGX_CONF_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/t/ngx_test_macro.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/t/ngx_test_macro.h new file mode 100644 index 0000000000..6da2f68cae --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-multiport-module/t/ngx_test_macro.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#ifndef _NGX_TEST_MACRO_H_INCLUDE_ +#define _NGX_TEST_MACRO_H_INCLUDE_ + + +#include +#include + + +static ngx_int_t count = 0; +static ngx_int_t pass = 0; + +#define NGX_TEST_INIT count = 0, pass = 0; + +#define NGX_TEST_ISOK(testcase) \ +{ \ + ngx_int_t __ret = testcase; \ + ++count; \ + if (__ret) ++pass; \ + ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, 0, " TEST "#testcase"%s",\ + (__ret ? " ...OK" : " ...ERROR")); \ +} + +#define NGX_TEST_INT(di, si) \ + (di == si) + +static ngx_inline ngx_int_t +ngx_test_str(ngx_str_t *nstr, char *cstr) +{ + size_t len; + + len = ngx_strlen(cstr); + + return (nstr->len == len && ngx_memcmp(nstr->data, cstr, len) == 0); +} + +#endif diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/AUTHORS b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/AUTHORS new file mode 100644 index 0000000000..db7b2e4ee9 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/AUTHORS @@ -0,0 +1,9 @@ +Project author: + + Roman Arutyunyan + Moscow, Russia, Pingo + + Contacts: + arut@qip.ru + arutyunyan.roman@gmail.com + cczjp89@gmail.com \ No newline at end of file diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/LICENSE b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/LICENSE new file mode 100644 index 0000000000..15cb1e0ee9 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2012-2014, Roman Arutyunyan +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/config b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/config new file mode 100644 index 0000000000..4bf0264dc8 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/config @@ -0,0 +1,169 @@ +ngx_addon_name="ngx_rtmp_module" + +RTMP_CORE_MODULES=" \ + ngx_live_module \ + ngx_rtmp_module \ + ngx_rtmp_dynamic_module \ + ngx_rtmp_dynamic_core_module \ + ngx_rtmp_core_module \ + ngx_rtmp_cmd_module \ + ngx_rtmp_codec_module \ + ngx_rtmp_access_module \ + ngx_rtmp_live_module \ + ngx_live_record_module \ + ngx_live_relay_module \ + ngx_live_relay_rtmp_module \ + ngx_live_relay_static_module \ + ngx_live_relay_simple_module \ + ngx_rtmp_exec_module \ + ngx_rtmp_notify_module \ + ngx_live_relay_inner_module \ + ngx_rtmp_log_module \ + ngx_rtmp_limit_module \ + ngx_rtmp_hls_module \ + ngx_rtmp_dash_module \ + ngx_rtmp_shared_module \ + ngx_rtmp_record_module \ + ngx_rtmp_gop_module \ + ngx_rtmp_monitor_module \ + ngx_mpegts_live_module \ + ngx_mpegts_gop_module \ + ngx_hls_live_module \ + " + + +RTMP_HTTP_MODULES=" \ + ngx_rtmp_stat_module \ + ngx_rtmp_sys_stat_module \ + ngx_rtmp_control_module \ + ngx_http_flv_live_module \ + ngx_hls_http_module \ + ngx_mpegts_http_module \ + " + + +RTMP_DEPS=" \ + $ngx_addon_dir/ngx_netcall.h \ + $ngx_addon_dir/ngx_rtmp_amf.h \ + $ngx_addon_dir/ngx_rtmp_bandwidth.h \ + $ngx_addon_dir/ngx_rtmp_cmd_module.h \ + $ngx_addon_dir/ngx_rtmp_codec_module.h \ + $ngx_addon_dir/ngx_rtmp_eval.h \ + $ngx_addon_dir/ngx_rtmp.h \ + $ngx_addon_dir/ngx_rtmp_version.h \ + $ngx_addon_dir/ngx_rtmp_live_module.h \ + $ngx_addon_dir/ngx_rtmp_notify_module.h \ + $ngx_addon_dir/ngx_rtmp_streams.h \ + $ngx_addon_dir/ngx_rtmp_bitop.h \ + $ngx_addon_dir/ngx_rtmp_proxy_protocol.h \ + $ngx_addon_dir/ngx_rtmp_monitor_module.h \ + $ngx_addon_dir/hls/ngx_rtmp_mpegts.h \ + $ngx_addon_dir/dash/ngx_rtmp_mp4.h \ + $ngx_addon_dir/http/ngx_http_set_header.h \ + $ngx_addon_dir/ngx_live.h \ + $ngx_addon_dir/ngx_live_relay.h \ + $ngx_addon_dir/ngx_live_record.h \ + $ngx_addon_dir/ngx_rtmp_dynamic.h \ + $ngx_addon_dir/ngx_rtmp_variables.h \ + $ngx_addon_dir/ngx_rtmp_record_module.h \ + $ngx_addon_dir/mpegts/ngx_mpegts_live_module.h \ + $ngx_addon_dir/mpegts/ngx_hls_live_module.h \ + $ngx_addon_dir/mpegts/ngx_mpegts_gop_module.h \ + " + + +RTMP_CORE_SRCS=" \ + $ngx_addon_dir/ngx_netcall.c \ + $ngx_addon_dir/ngx_rtmp.c \ + $ngx_addon_dir/ngx_rtmp_init.c \ + $ngx_addon_dir/ngx_rtmp_handshake.c \ + $ngx_addon_dir/ngx_rtmp_handler.c \ + $ngx_addon_dir/ngx_rtmp_amf.c \ + $ngx_addon_dir/ngx_rtmp_send.c \ + $ngx_addon_dir/ngx_rtmp_eval.c \ + $ngx_addon_dir/ngx_rtmp_receive.c \ + $ngx_addon_dir/ngx_rtmp_core_module.c \ + $ngx_addon_dir/ngx_rtmp_cmd_module.c \ + $ngx_addon_dir/ngx_rtmp_codec_module.c \ + $ngx_addon_dir/ngx_rtmp_access_module.c \ + $ngx_addon_dir/ngx_rtmp_live_module.c \ + $ngx_addon_dir/ngx_rtmp_bandwidth.c \ + $ngx_addon_dir/ngx_rtmp_exec_module.c \ + $ngx_addon_dir/ngx_rtmp_notify_module.c \ + $ngx_addon_dir/ngx_rtmp_log_module.c \ + $ngx_addon_dir/ngx_rtmp_limit_module.c \ + $ngx_addon_dir/ngx_rtmp_bitop.c \ + $ngx_addon_dir/ngx_rtmp_proxy_protocol.c \ + $ngx_addon_dir/hls/ngx_rtmp_hls_module.c \ + $ngx_addon_dir/dash/ngx_rtmp_dash_module.c \ + $ngx_addon_dir/hls/ngx_rtmp_mpegts.c \ + $ngx_addon_dir/dash/ngx_rtmp_mp4.c \ + $ngx_addon_dir/ngx_live.c \ + $ngx_addon_dir/ngx_live_relay.c \ + $ngx_addon_dir/ngx_live_relay_httpflv.c \ + $ngx_addon_dir/ngx_live_relay_rtmp.c \ + $ngx_addon_dir/ngx_live_relay_inner.c \ + $ngx_addon_dir/ngx_live_relay_simple.c \ + $ngx_addon_dir/ngx_live_relay_static.c \ + $ngx_addon_dir/ngx_live_record.c \ + $ngx_addon_dir/ngx_rtmp_shared_module.c \ + $ngx_addon_dir/ngx_rtmp_gop_module.c \ + $ngx_addon_dir/ngx_rtmp_monitor_module.c \ + $ngx_addon_dir/ngx_rtmp_dynamic.c \ + $ngx_addon_dir/ngx_rtmp_variables.c \ + $ngx_addon_dir/ngx_rtmp_record_module.c \ + $ngx_addon_dir/mpegts/ngx_mpegts_live_module.c \ + $ngx_addon_dir/mpegts/ngx_hls_live_module.c \ + $ngx_addon_dir/mpegts/ngx_mpegts_gop_module.c \ + " + + +RTMP_HTTP_SRCS=" \ + $ngx_addon_dir/ngx_rtmp_sys_stat_module.c \ + $ngx_addon_dir/ngx_rtmp_stat_module.c \ + $ngx_addon_dir/ngx_rtmp_control_module.c \ + $ngx_addon_dir/http/ngx_http_flv_live_module.c \ + $ngx_addon_dir/http/ngx_http_set_header.c \ + $ngx_addon_dir/mpegts/ngx_hls_http_module.c \ + $ngx_addon_dir/mpegts/ngx_mpegts_http_module.c \ + " + +if [ -f auto/module ] ; then + ngx_module_incs=$ngx_addon_dir + ngx_module_deps=$RTMP_DEPS + + if [ $ngx_module_link = DYNAMIC ] ; then + ngx_module_name="$RTMP_CORE_MODULES $RTMP_HTTP_MODULES" + ngx_module_srcs="$RTMP_CORE_SRCS $RTMP_HTTP_SRCS" + + . auto/module + + else + ngx_module_type=CORE + ngx_module_name=$RTMP_CORE_MODULES + ngx_module_srcs=$RTMP_CORE_SRCS + + . auto/module + + + ngx_module_type=HTTP + ngx_module_name=$RTMP_HTTP_MODULES + ngx_module_incs="$ngx_addon_dir $ngx_addon_dir/http $ngx_addon_dir/hls $ngx_addon_dir/mpegts" + ngx_module_deps= + ngx_module_srcs=$RTMP_HTTP_SRCS + + . auto/module + fi + +else + CORE_MODULES="$CORE_MODULES $RTMP_CORE_MODULES" + HTTP_MODULES="$HTTP_MODULES $RTMP_HTTP_MODULES" + + NGX_ADDON_DEPS="$NGX_ADDON_DEPS $RTMP_DEPS" + NGX_ADDON_SRCS="$NGX_ADDON_SRCS $RTMP_CORE_SRCS $RTMP_HTTP_SRCS" + + CFLAGS="$CFLAGS -I$ngx_addon_dir -I$ngx_addon_dir/http -I$ngx_addon_dir/hls -I$ngx_addon_dir/mpegts" +fi + +USE_OPENSSL=YES + diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/dash/ngx_rtmp_dash_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/dash/ngx_rtmp_dash_module.c new file mode 100644 index 0000000000..b4946066a8 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/dash/ngx_rtmp_dash_module.c @@ -0,0 +1,1532 @@ + + +#include +#include +#include +#include +#include "ngx_rtmp_live_module.h" +#include "ngx_rtmp_mp4.h" + + +static ngx_rtmp_publish_pt next_publish; +static ngx_rtmp_close_stream_pt next_close_stream; +static ngx_rtmp_stream_begin_pt next_stream_begin; +static ngx_rtmp_stream_eof_pt next_stream_eof; + + +static ngx_int_t ngx_rtmp_dash_postconfiguration(ngx_conf_t *cf); +static void * ngx_rtmp_dash_create_app_conf(ngx_conf_t *cf); +static char * ngx_rtmp_dash_merge_app_conf(ngx_conf_t *cf, + void *parent, void *child); +static ngx_int_t ngx_rtmp_dash_write_init_segments(ngx_rtmp_session_t *s); + + +#define NGX_RTMP_DASH_BUFSIZE (1024*1024) +#define NGX_RTMP_DASH_MAX_MDAT (10*1024*1024) +#define NGX_RTMP_DASH_MAX_SAMPLES 1024 +#define NGX_RTMP_DASH_DIR_ACCESS 0744 + + +typedef struct { + uint32_t timestamp; + uint32_t duration; +} ngx_rtmp_dash_frag_t; + + +typedef struct { + ngx_uint_t id; + ngx_uint_t opened; + ngx_uint_t mdat_size; + ngx_uint_t sample_count; + ngx_uint_t sample_mask; + ngx_fd_t fd; + char type; + uint32_t earliest_pres_time; + uint32_t latest_pres_time; + ngx_rtmp_mp4_sample_t samples[NGX_RTMP_DASH_MAX_SAMPLES]; +} ngx_rtmp_dash_track_t; + + +typedef struct { + ngx_str_t playlist; + ngx_str_t playlist_bak; + ngx_str_t name; + ngx_str_t stream; + time_t start_time; + + ngx_uint_t nfrags; + ngx_uint_t frag; + ngx_rtmp_dash_frag_t *frags; /* circular 2 * winfrags + 1 */ + + unsigned opened:1; + unsigned has_video:1; + unsigned has_audio:1; + + ngx_file_t video_file; + ngx_file_t audio_file; + + ngx_uint_t id; + + ngx_rtmp_dash_track_t audio; + ngx_rtmp_dash_track_t video; +} ngx_rtmp_dash_ctx_t; + + +typedef struct { + ngx_str_t path; + ngx_msec_t playlen; +} ngx_rtmp_dash_cleanup_t; + + +typedef struct { + ngx_flag_t dash; + ngx_msec_t fraglen; + ngx_msec_t playlen; + ngx_flag_t nested; + ngx_str_t path; + ngx_uint_t winfrags; + ngx_flag_t cleanup; + ngx_path_t *slot; +} ngx_rtmp_dash_app_conf_t; + + +static ngx_command_t ngx_rtmp_dash_commands[] = { + + { ngx_string("dash"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_dash_app_conf_t, dash), + NULL }, + + { ngx_string("dash_fragment"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_dash_app_conf_t, fraglen), + NULL }, + + { ngx_string("dash_path"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_dash_app_conf_t, path), + NULL }, + + { ngx_string("dash_playlist_length"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_dash_app_conf_t, playlen), + NULL }, + + { ngx_string("dash_cleanup"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_dash_app_conf_t, cleanup), + NULL }, + + { ngx_string("dash_nested"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_dash_app_conf_t, nested), + NULL }, + + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_rtmp_dash_module_ctx = { + NULL, /* preconfiguration */ + ngx_rtmp_dash_postconfiguration, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + ngx_rtmp_dash_create_app_conf, /* create location configuration */ + ngx_rtmp_dash_merge_app_conf, /* merge location configuration */ +}; + + +ngx_module_t ngx_rtmp_dash_module = { + NGX_MODULE_V1, + &ngx_rtmp_dash_module_ctx, /* module context */ + ngx_rtmp_dash_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_rtmp_dash_frag_t * +ngx_rtmp_dash_get_frag(ngx_rtmp_session_t *s, ngx_int_t n) +{ + ngx_rtmp_dash_ctx_t *ctx; + ngx_rtmp_dash_app_conf_t *dacf; + + dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module); + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module); + + return &ctx->frags[(ctx->frag + n) % (dacf->winfrags * 2 + 1)]; +} + + +static void +ngx_rtmp_dash_next_frag(ngx_rtmp_session_t *s) +{ + ngx_rtmp_dash_ctx_t *ctx; + ngx_rtmp_dash_app_conf_t *dacf; + + dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module); + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module); + + if (ctx->nfrags == dacf->winfrags) { + ctx->frag++; + } else { + ctx->nfrags++; + } +} + + +static ngx_int_t +ngx_rtmp_dash_rename_file(u_char *src, u_char *dst) +{ + /* rename file with overwrite */ + +#if (NGX_WIN32) + return MoveFileEx((LPCTSTR) src, (LPCTSTR) dst, MOVEFILE_REPLACE_EXISTING); +#else + return ngx_rename_file(src, dst); +#endif +} + + +static ngx_int_t +ngx_rtmp_dash_write_playlist(ngx_rtmp_session_t *s) +{ + char *sep; + u_char *p, *last; + ssize_t n; + ngx_fd_t fd; + struct tm tm; + ngx_str_t noname, *name; + ngx_uint_t i; + ngx_rtmp_dash_ctx_t *ctx; + ngx_rtmp_codec_ctx_t *codec_ctx; + ngx_rtmp_dash_frag_t *f; + ngx_rtmp_dash_app_conf_t *dacf; + + static u_char buffer[NGX_RTMP_DASH_BUFSIZE]; + static u_char start_time[sizeof("1970-09-28T12:00:00Z")]; + static u_char pub_time[sizeof("1970-09-28T12:00:00Z")]; + + dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module); + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module); + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + if (dacf == NULL || ctx == NULL || codec_ctx == NULL) { + return NGX_ERROR; + } + + if (ctx->id == 0) { + ngx_rtmp_dash_write_init_segments(s); + } + + fd = ngx_open_file(ctx->playlist_bak.data, NGX_FILE_WRONLY, + NGX_FILE_TRUNCATE, NGX_FILE_DEFAULT_ACCESS); + + if (fd == NGX_INVALID_FILE) { + ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno, + "dash: open failed: '%V'", &ctx->playlist_bak); + return NGX_ERROR; + } + +#define NGX_RTMP_DASH_MANIFEST_HEADER \ + "\n" \ + "\n" \ + " \n" + + +#define NGX_RTMP_DASH_MANIFEST_VIDEO \ + " \n" \ + " \n" \ + " \n" \ + " \n" + + +#define NGX_RTMP_DASH_MANIFEST_VIDEO_FOOTER \ + " \n" \ + " \n" \ + " \n" \ + " \n" + + +#define NGX_RTMP_DASH_MANIFEST_TIME \ + " \n" + + +#define NGX_RTMP_DASH_MANIFEST_AUDIO \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" + + +#define NGX_RTMP_DASH_MANIFEST_AUDIO_FOOTER \ + " \n" \ + " \n" \ + " \n" \ + " \n" + + +#define NGX_RTMP_DASH_MANIFEST_FOOTER \ + " \n" \ + "\n" + + ngx_libc_gmtime(ctx->start_time, &tm); + + ngx_sprintf(start_time, "%4d-%02d-%02dT%02d:%02d:%02dZ%Z", + tm.tm_year + 1900, tm.tm_mon + 1, + tm.tm_mday, tm.tm_hour, + tm.tm_min, tm.tm_sec); + + ngx_libc_gmtime(ngx_time(), &tm); + + ngx_sprintf(pub_time, "%4d-%02d-%02dT%02d:%02d:%02dZ%Z", + tm.tm_year + 1900, tm.tm_mon + 1, + tm.tm_mday, tm.tm_hour, + tm.tm_min, tm.tm_sec); + + ngx_memset(buffer, 0, sizeof(buffer)); + last = buffer + sizeof(buffer); + + p = ngx_slprintf(buffer, last, NGX_RTMP_DASH_MANIFEST_HEADER, + start_time, + pub_time, + (ngx_uint_t) (dacf->fraglen / 1000), + (ngx_uint_t) (dacf->fraglen / 1000), + (ngx_uint_t) (dacf->fraglen / 250 + 1)); + + /* + * timeShiftBufferDepth formula: + * 2 * minBufferTime + max_fragment_length + 1 + */ + + n = ngx_write_fd(fd, buffer, p - buffer); + + ngx_str_null(&noname); + + name = (dacf->nested ? &noname : &ctx->name); + sep = (dacf->nested ? "" : "-"); + + if (ctx->has_video) { + p = ngx_slprintf(buffer, last, NGX_RTMP_DASH_MANIFEST_VIDEO, + codec_ctx->width, + codec_ctx->height, + (ngx_uint_t) codec_ctx->frame_rate, + &ctx->name, + codec_ctx->avc_profile, + codec_ctx->avc_compat, + codec_ctx->avc_level, + codec_ctx->width, + codec_ctx->height, + (ngx_uint_t) codec_ctx->frame_rate, + (ngx_uint_t) (codec_ctx->video_data_rate * 1000), + name, sep, + name, sep); + + for (i = 0; i < ctx->nfrags; i++) { + f = ngx_rtmp_dash_get_frag(s, i); + p = ngx_slprintf(p, last, NGX_RTMP_DASH_MANIFEST_TIME, + f->timestamp, f->duration); + } + + p = ngx_slprintf(p, last, NGX_RTMP_DASH_MANIFEST_VIDEO_FOOTER); + + n = ngx_write_fd(fd, buffer, p - buffer); + } + + if (ctx->has_audio) { + p = ngx_slprintf(buffer, last, NGX_RTMP_DASH_MANIFEST_AUDIO, + &ctx->name, + codec_ctx->audio_codec_id == NGX_RTMP_AUDIO_AAC ? + (codec_ctx->aac_sbr ? "40.5" : "40.2") : "6b", + codec_ctx->sample_rate, + (ngx_uint_t) (codec_ctx->audio_data_rate * 1000), + name, sep, + name, sep); + + for (i = 0; i < ctx->nfrags; i++) { + f = ngx_rtmp_dash_get_frag(s, i); + p = ngx_slprintf(p, last, NGX_RTMP_DASH_MANIFEST_TIME, + f->timestamp, f->duration); + } + + p = ngx_slprintf(p, last, NGX_RTMP_DASH_MANIFEST_AUDIO_FOOTER); + + n = ngx_write_fd(fd, buffer, p - buffer); + } + + p = ngx_slprintf(buffer, last, NGX_RTMP_DASH_MANIFEST_FOOTER); + n = ngx_write_fd(fd, buffer, p - buffer); + + if (n < 0) { + ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno, + "dash: write failed: '%V'", &ctx->playlist_bak); + ngx_close_file(fd); + return NGX_ERROR; + } + + ngx_close_file(fd); + + if (ngx_rtmp_dash_rename_file(ctx->playlist_bak.data, ctx->playlist.data) + == NGX_FILE_ERROR) + { + ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno, + "dash: rename failed: '%V'->'%V'", + &ctx->playlist_bak, &ctx->playlist); + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_dash_write_init_segments(ngx_rtmp_session_t *s) +{ + ngx_fd_t fd; + ngx_int_t rc; + ngx_buf_t b; + ngx_rtmp_dash_ctx_t *ctx; + ngx_rtmp_codec_ctx_t *codec_ctx; + + static u_char buffer[NGX_RTMP_DASH_BUFSIZE]; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module); + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + if (ctx == NULL || codec_ctx == NULL) { + return NGX_ERROR; + } + + /* init video */ + + *ngx_sprintf(ctx->stream.data + ctx->stream.len, "init.m4v") = 0; + + fd = ngx_open_file(ctx->stream.data, NGX_FILE_RDWR, NGX_FILE_TRUNCATE, + NGX_FILE_DEFAULT_ACCESS); + + if (fd == NGX_INVALID_FILE) { + ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno, + "dash: error creating video init file"); + return NGX_ERROR; + } + + b.start = buffer; + b.end = b.start + sizeof(buffer); + b.pos = b.last = b.start; + + ngx_rtmp_mp4_write_ftyp(&b); + ngx_rtmp_mp4_write_moov(s, &b, NGX_RTMP_MP4_VIDEO_TRACK); + + rc = ngx_write_fd(fd, b.start, (size_t) (b.last - b.start)); + if (rc == NGX_ERROR) { + ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno, + "dash: writing video init failed"); + } + + ngx_close_file(fd); + + /* init audio */ + + *ngx_sprintf(ctx->stream.data + ctx->stream.len, "init.m4a") = 0; + + fd = ngx_open_file(ctx->stream.data, NGX_FILE_RDWR, NGX_FILE_TRUNCATE, + NGX_FILE_DEFAULT_ACCESS); + + if (fd == NGX_INVALID_FILE) { + ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno, + "dash: error creating dash audio init file"); + return NGX_ERROR; + } + + b.pos = b.last = b.start; + + ngx_rtmp_mp4_write_ftyp(&b); + ngx_rtmp_mp4_write_moov(s, &b, NGX_RTMP_MP4_AUDIO_TRACK); + + rc = ngx_write_fd(fd, b.start, (size_t) (b.last - b.start)); + if (rc == NGX_ERROR) { + ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno, + "dash: writing audio init failed"); + } + + ngx_close_file(fd); + + return NGX_OK; +} + + +static void +ngx_rtmp_dash_close_fragment(ngx_rtmp_session_t *s, ngx_rtmp_dash_track_t *t) +{ + u_char *pos, *pos1; + size_t left; + ssize_t n; + ngx_fd_t fd; + ngx_buf_t b; + ngx_rtmp_dash_ctx_t *ctx; + ngx_rtmp_dash_frag_t *f; + + static u_char buffer[NGX_RTMP_DASH_BUFSIZE]; + + if (!t->opened) { + return; + } + + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->log, 0, + "dash: close fragment id=%ui, type=%c, pts=%uD", + t->id, t->type, t->earliest_pres_time); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module); + + b.start = buffer; + b.end = buffer + sizeof(buffer); + b.pos = b.last = b.start; + + ngx_rtmp_mp4_write_styp(&b); + + pos = b.last; + b.last += 44; /* leave room for sidx */ + + ngx_rtmp_mp4_write_moof(&b, t->earliest_pres_time, t->sample_count, + t->samples, t->sample_mask, t->id); + pos1 = b.last; + b.last = pos; + + ngx_rtmp_mp4_write_sidx(&b, t->mdat_size + 8 + (pos1 - (pos + 44)), + t->earliest_pres_time, t->latest_pres_time); + b.last = pos1; + ngx_rtmp_mp4_write_mdat(&b, t->mdat_size + 8); + + /* move the data down to make room for the headers */ + + f = ngx_rtmp_dash_get_frag(s, ctx->nfrags); + + *ngx_sprintf(ctx->stream.data + ctx->stream.len, "%uD.m4%c", + f->timestamp, t->type) = 0; + + fd = ngx_open_file(ctx->stream.data, NGX_FILE_RDWR, + NGX_FILE_TRUNCATE, NGX_FILE_DEFAULT_ACCESS); + + if (fd == NGX_INVALID_FILE) { + ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno, + "dash: error creating dash temp video file"); + goto done; + } + + if (ngx_write_fd(fd, b.pos, (size_t) (b.last - b.pos)) == NGX_ERROR) { + goto done; + } + + left = (size_t) t->mdat_size; + +#if (NGX_WIN32) + if (SetFilePointer(t->fd, 0, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "dash: SetFilePointer error"); + goto done; + } +#else + if (lseek(t->fd, 0, SEEK_SET) == -1) { + ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno, + "dash: lseek error"); + goto done; + } +#endif + + while (left > 0) { + + n = ngx_read_fd(t->fd, buffer, ngx_min(sizeof(buffer), left)); + if (n == NGX_ERROR) { + break; + } + + n = ngx_write_fd(fd, buffer, (size_t) n); + if (n == NGX_ERROR) { + break; + } + + left -= n; + } + +done: + + if (fd != NGX_INVALID_FILE) { + ngx_close_file(fd); + } + + ngx_close_file(t->fd); + + t->fd = NGX_INVALID_FILE; + t->opened = 0; +} + + +static ngx_int_t +ngx_rtmp_dash_close_fragments(ngx_rtmp_session_t *s) +{ + ngx_rtmp_dash_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module); + if (ctx == NULL || !ctx->opened) { + return NGX_OK; + } + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0, + "dash: close fragments"); + + ngx_rtmp_dash_close_fragment(s, &ctx->video); + ngx_rtmp_dash_close_fragment(s, &ctx->audio); + + ngx_rtmp_dash_next_frag(s); + + ngx_rtmp_dash_write_playlist(s); + + ctx->id++; + ctx->opened = 0; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_dash_open_fragment(ngx_rtmp_session_t *s, ngx_rtmp_dash_track_t *t, + ngx_uint_t id, char type) +{ + ngx_rtmp_dash_ctx_t *ctx; + + if (t->opened) { + return NGX_OK; + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0, + "dash: open fragment id=%ui, type='%c'", id, type); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module); + + *ngx_sprintf(ctx->stream.data + ctx->stream.len, "raw.m4%c", type) = 0; + + t->fd = ngx_open_file(ctx->stream.data, NGX_FILE_RDWR, + NGX_FILE_TRUNCATE, NGX_FILE_DEFAULT_ACCESS); + + if (t->fd == NGX_INVALID_FILE) { + ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno, + "dash: error creating fragment file"); + return NGX_ERROR; + } + + t->id = id; + t->type = type; + t->sample_count = 0; + t->earliest_pres_time = 0; + t->latest_pres_time = 0; + t->mdat_size = 0; + t->opened = 1; + + if (type == 'v') { + t->sample_mask = NGX_RTMP_MP4_SAMPLE_SIZE| + NGX_RTMP_MP4_SAMPLE_DURATION| + NGX_RTMP_MP4_SAMPLE_DELAY| + NGX_RTMP_MP4_SAMPLE_KEY; + } else { + t->sample_mask = NGX_RTMP_MP4_SAMPLE_SIZE| + NGX_RTMP_MP4_SAMPLE_DURATION; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_dash_open_fragments(ngx_rtmp_session_t *s) +{ + ngx_rtmp_dash_ctx_t *ctx; + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0, + "dash: open fragments"); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module); + + if (ctx->opened) { + return NGX_OK; + } + + ngx_rtmp_dash_open_fragment(s, &ctx->video, ctx->id, 'v'); + + ngx_rtmp_dash_open_fragment(s, &ctx->audio, ctx->id, 'a'); + + ctx->opened = 1; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_dash_ensure_directory(ngx_rtmp_session_t *s) +{ + size_t len; + ngx_file_info_t fi; + ngx_rtmp_dash_ctx_t *ctx; + ngx_rtmp_dash_app_conf_t *dacf; + + static u_char path[NGX_MAX_PATH + 1]; + + dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module); + + *ngx_snprintf(path, sizeof(path) - 1, "%V", &dacf->path) = 0; + + if (ngx_file_info(path, &fi) == NGX_FILE_ERROR) { + + if (ngx_errno != NGX_ENOENT) { + ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno, + "dash: " ngx_file_info_n " failed on '%V'", + &dacf->path); + return NGX_ERROR; + } + + /* ENOENT */ + + if (ngx_create_dir(path, NGX_RTMP_DASH_DIR_ACCESS) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno, + "dash: " ngx_create_dir_n " failed on '%V'", + &dacf->path); + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "dash: directory '%V' created", &dacf->path); + + } else { + + if (!ngx_is_dir(&fi)) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "dash: '%V' exists and is not a directory", + &dacf->path); + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "dash: directory '%V' exists", &dacf->path); + } + + if (!dacf->nested) { + return NGX_OK; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module); + + len = dacf->path.len; + if (dacf->path.data[len - 1] == '/') { + len--; + } + + *ngx_snprintf(path, sizeof(path) - 1, "%*s/%V", len, dacf->path.data, + &ctx->name) = 0; + + if (ngx_file_info(path, &fi) != NGX_FILE_ERROR) { + + if (ngx_is_dir(&fi)) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "dash: directory '%s' exists", path); + return NGX_OK; + } + + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "dash: '%s' exists and is not a directory", path); + + return NGX_ERROR; + } + + if (ngx_errno != NGX_ENOENT) { + ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno, + "dash: " ngx_file_info_n " failed on '%s'", path); + return NGX_ERROR; + } + + /* NGX_ENOENT */ + + if (ngx_create_dir(path, NGX_RTMP_DASH_DIR_ACCESS) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno, + "dash: " ngx_create_dir_n " failed on '%s'", path); + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "dash: directory '%s' created", path); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_dash_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) +{ + u_char *p; + size_t len; + ngx_rtmp_dash_ctx_t *ctx; + ngx_rtmp_dash_frag_t *f; + ngx_rtmp_dash_app_conf_t *dacf; + + dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module); + if (dacf == NULL || !dacf->dash || dacf->path.len == 0) { + goto next; + } + + if (s->interprocess) { + goto next; + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0, + "dash: publish: name='%s' type='%s'", v->name, v->type); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module); + + if (ctx == NULL) { + ctx = ngx_pcalloc(s->pool, sizeof(ngx_rtmp_dash_ctx_t)); + if (ctx == NULL) { + goto next; + } + ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_dash_module); + + } else { + if (ctx->opened) { + goto next; + } + + f = ctx->frags; + ngx_memzero(ctx, sizeof(ngx_rtmp_dash_ctx_t)); + ctx->frags = f; + } + + if (ctx->frags == NULL) { + ctx->frags = ngx_pcalloc(s->pool, + sizeof(ngx_rtmp_dash_frag_t) * + (dacf->winfrags * 2 + 1)); + if (ctx->frags == NULL) { + return NGX_ERROR; + } + } + + ctx->id = 0; + + if (ngx_strstr(v->name, "..")) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "dash: bad stream name: '%s'", v->name); + return NGX_ERROR; + } + + ctx->name.len = ngx_strlen(v->name); + ctx->name.data = ngx_palloc(s->pool, ctx->name.len + 1); + + if (ctx->name.data == NULL) { + return NGX_ERROR; + } + + *ngx_cpymem(ctx->name.data, v->name, ctx->name.len) = 0; + + len = dacf->path.len + 1 + ctx->name.len + sizeof(".mpd"); + if (dacf->nested) { + len += sizeof("/index") - 1; + } + + ctx->playlist.data = ngx_palloc(s->pool, len); + p = ngx_cpymem(ctx->playlist.data, dacf->path.data, dacf->path.len); + + if (p[-1] != '/') { + *p++ = '/'; + } + + p = ngx_cpymem(p, ctx->name.data, ctx->name.len); + + /* + * ctx->stream holds initial part of stream file path + * however the space for the whole stream path + * is allocated + */ + + ctx->stream.len = p - ctx->playlist.data + 1; + ctx->stream.data = ngx_palloc(s->pool, + ctx->stream.len + NGX_INT32_LEN + + sizeof(".m4x")); + + ngx_memcpy(ctx->stream.data, ctx->playlist.data, ctx->stream.len - 1); + ctx->stream.data[ctx->stream.len - 1] = (dacf->nested ? '/' : '-'); + + if (dacf->nested) { + p = ngx_cpymem(p, "/index.mpd", sizeof("/index.mpd") - 1); + } else { + p = ngx_cpymem(p, ".mpd", sizeof(".mpd") - 1); + } + + ctx->playlist.len = p - ctx->playlist.data; + + *p = 0; + + /* playlist bak (new playlist) path */ + + ctx->playlist_bak.data = ngx_palloc(s->pool, + ctx->playlist.len + sizeof(".bak")); + p = ngx_cpymem(ctx->playlist_bak.data, ctx->playlist.data, + ctx->playlist.len); + p = ngx_cpymem(p, ".bak", sizeof(".bak") - 1); + + ctx->playlist_bak.len = p - ctx->playlist_bak.data; + + *p = 0; + + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->log, 0, + "dash: playlist='%V' playlist_bak='%V' stream_pattern='%V'", + &ctx->playlist, &ctx->playlist_bak, &ctx->stream); + + ctx->start_time = ngx_time(); + + if (ngx_rtmp_dash_ensure_directory(s) != NGX_OK) { + return NGX_ERROR; + } + +next: + return next_publish(s, v); +} + + +static ngx_int_t +ngx_rtmp_dash_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v) +{ + ngx_rtmp_dash_ctx_t *ctx; + ngx_rtmp_dash_app_conf_t *dacf; + + dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module); + + if (dacf == NULL || !dacf->dash || ctx == NULL) { + goto next; + } + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0, + "dash: delete stream"); + + ngx_rtmp_dash_close_fragments(s); + +next: + return next_close_stream(s, v); +} + + +static void +ngx_rtmp_dash_update_fragments(ngx_rtmp_session_t *s, ngx_int_t boundary, + uint32_t timestamp) +{ + int32_t d; + ngx_int_t hit; + ngx_rtmp_dash_ctx_t *ctx; + ngx_rtmp_dash_frag_t *f; + ngx_rtmp_dash_app_conf_t *dacf; + + dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module); + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module); + f = ngx_rtmp_dash_get_frag(s, ctx->nfrags); + + d = (int32_t) (timestamp - f->timestamp); + + if (d >= 0) { + + f->duration = timestamp - f->timestamp; + hit = (f->duration >= dacf->fraglen); + + /* keep fragment lengths within 2x factor for dash.js */ + if (f->duration >= dacf->fraglen * 2) { + boundary = 1; + } + + } else { + + /* sometimes clients generate slightly unordered frames */ + + hit = (-d > 1000); + } + + if (ctx->has_video && !hit) { + boundary = 0; + } + + if (!ctx->has_video && ctx->has_audio) { + boundary = hit; + } + + if (ctx->audio.mdat_size >= NGX_RTMP_DASH_MAX_MDAT) { + boundary = 1; + } + + if (ctx->video.mdat_size >= NGX_RTMP_DASH_MAX_MDAT) { + boundary = 1; + } + + if (!ctx->opened) { + boundary = 1; + } + + if (boundary) { + ngx_rtmp_dash_close_fragments(s); + ngx_rtmp_dash_open_fragments(s); + + f = ngx_rtmp_dash_get_frag(s, ctx->nfrags); + f->timestamp = timestamp; + } +} + + +static ngx_int_t +ngx_rtmp_dash_append(ngx_rtmp_session_t *s, ngx_chain_t *in, + ngx_rtmp_dash_track_t *t, ngx_int_t key, uint32_t timestamp, uint32_t delay) +{ + u_char *p; + size_t size, bsize; + ngx_rtmp_mp4_sample_t *smpl; + + static u_char buffer[NGX_RTMP_DASH_BUFSIZE]; + + p = buffer; + size = 0; + + for (; in && size < sizeof(buffer); in = in->next) { + + bsize = (size_t) (in->buf->last - in->buf->pos); + if (size + bsize > sizeof(buffer)) { + bsize = (size_t) (sizeof(buffer) - size); + } + + p = ngx_cpymem(p, in->buf->pos, bsize); + size += bsize; + } + + ngx_rtmp_dash_update_fragments(s, key, timestamp); + + if (t->sample_count == 0) { + t->earliest_pres_time = timestamp; + } + + t->latest_pres_time = timestamp; + + if (t->sample_count < NGX_RTMP_DASH_MAX_SAMPLES) { + + if (ngx_write_fd(t->fd, buffer, size) == NGX_ERROR) { + ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno, + "dash: " ngx_write_fd_n " failed"); + return NGX_ERROR; + } + + smpl = &t->samples[t->sample_count]; + + smpl->delay = delay; + smpl->size = (uint32_t) size; + smpl->duration = 0; + smpl->timestamp = timestamp; + smpl->key = (key ? 1 : 0); + + if (t->sample_count > 0) { + smpl = &t->samples[t->sample_count - 1]; + smpl->duration = timestamp - smpl->timestamp; + } + + t->sample_count++; + t->mdat_size += (ngx_uint_t) size; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_dash_audio(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + u_char htype; + ngx_rtmp_dash_ctx_t *ctx; + ngx_rtmp_codec_ctx_t *codec_ctx; + ngx_rtmp_dash_app_conf_t *dacf; + + dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module); + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module); + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + if (dacf == NULL || !dacf->dash || ctx == NULL || + codec_ctx == NULL || h->mlen < 2) + { + return NGX_OK; + } + + /* Only AAC is supported */ + + if (codec_ctx->audio_codec_id != NGX_RTMP_AUDIO_AAC || + codec_ctx->aac_header == NULL) + { + return NGX_OK; + } + + if (in->buf->last - in->buf->pos < 2) { + return NGX_ERROR; + } + + /* skip AAC config */ + + htype = in->buf->pos[1]; + if (htype != 1) { + return NGX_OK; + } + + ctx->has_audio = 1; + + /* skip RTMP & AAC headers */ + + in->buf->pos += 2; + + return ngx_rtmp_dash_append(s, in, &ctx->audio, 0, h->timestamp, 0); +} + + +static ngx_int_t +ngx_rtmp_dash_video(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + u_char *p; + uint8_t ftype, htype; + uint32_t delay; + ngx_rtmp_dash_ctx_t *ctx; + ngx_rtmp_codec_ctx_t *codec_ctx; + ngx_rtmp_dash_app_conf_t *dacf; + + dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module); + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module); + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + if (dacf == NULL || !dacf->dash || ctx == NULL || codec_ctx == NULL || + codec_ctx->avc_header == NULL || h->mlen < 5) + { + return NGX_OK; + } + + /* Only H264 is supported */ + + if (codec_ctx->video_codec_id != NGX_RTMP_VIDEO_H264) { + return NGX_OK; + } + + if (in->buf->last - in->buf->pos < 5) { + return NGX_ERROR; + } + + ftype = (in->buf->pos[0] & 0xf0) >> 4; + + /* skip AVC config */ + + htype = in->buf->pos[1]; + if (htype != 1) { + return NGX_OK; + } + + p = (u_char *) &delay; + + p[0] = in->buf->pos[4]; + p[1] = in->buf->pos[3]; + p[2] = in->buf->pos[2]; + p[3] = 0; + + ctx->has_video = 1; + + /* skip RTMP & H264 headers */ + + in->buf->pos += 5; + + return ngx_rtmp_dash_append(s, in, &ctx->video, ftype == 1, h->timestamp, + delay); +} + + +static ngx_int_t +ngx_rtmp_dash_stream_begin(ngx_rtmp_session_t *s, ngx_rtmp_stream_begin_t *v) +{ + return next_stream_begin(s, v); +} + + +static ngx_int_t +ngx_rtmp_dash_stream_eof(ngx_rtmp_session_t *s, ngx_rtmp_stream_eof_t *v) +{ + ngx_rtmp_dash_close_fragments(s); + + return next_stream_eof(s, v); +} + + +static ngx_int_t +ngx_rtmp_dash_cleanup_dir(ngx_str_t *ppath, ngx_msec_t playlen) +{ + time_t mtime, max_age; + u_char *p; + u_char path[NGX_MAX_PATH + 1], mpd_path[NGX_MAX_PATH + 1]; + ngx_dir_t dir; + ngx_err_t err; + ngx_str_t name, spath, mpd; + ngx_int_t nentries, nerased; + ngx_file_info_t fi; + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0, + "dash: cleanup path='%V' playlen=%M", ppath, playlen); + + if (ngx_open_dir(ppath, &dir) != NGX_OK) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, ngx_errno, + "dash: cleanup open dir failed '%V'", ppath); + return NGX_ERROR; + } + + nentries = 0; + nerased = 0; + + for ( ;; ) { + ngx_set_errno(0); + + if (ngx_read_dir(&dir) == NGX_ERROR) { + err = ngx_errno; + + if (ngx_close_dir(&dir) == NGX_ERROR) { + ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, ngx_errno, + "dash: cleanup " ngx_close_dir_n " \"%V\" failed", + ppath); + } + + if (err == NGX_ENOMOREFILES) { + return nentries - nerased; + } + + ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, err, + "dash: cleanup " ngx_read_dir_n + " '%V' failed", ppath); + return NGX_ERROR; + } + + name.data = ngx_de_name(&dir); + if (name.data[0] == '.') { + continue; + } + + name.len = ngx_de_namelen(&dir); + + p = ngx_snprintf(path, sizeof(path) - 1, "%V/%V", ppath, &name); + *p = 0; + + spath.data = path; + spath.len = p - path; + + nentries++; + + if (!dir.valid_info && ngx_de_info(path, &dir) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, ngx_errno, + "dash: cleanup " ngx_de_info_n " \"%V\" failed", + &spath); + + continue; + } + + if (ngx_de_is_dir(&dir)) { + + if (ngx_rtmp_dash_cleanup_dir(&spath, playlen) == 0) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0, + "dash: cleanup dir '%V'", &name); + + /* + * null-termination gets spoiled in win32 + * version of ngx_open_dir + */ + + *p = 0; + + if (ngx_delete_dir(path) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno, + "dash: cleanup " ngx_delete_dir_n + " failed on '%V'", &spath); + } else { + nerased++; + } + } + + continue; + } + + if (!ngx_de_is_file(&dir)) { + continue; + } + + if (name.len >= 8 && name.data[name.len - 8] == 'i' && + name.data[name.len - 7] == 'n' && + name.data[name.len - 6] == 'i' && + name.data[name.len - 5] == 't' && + name.data[name.len - 4] == '.' && + name.data[name.len - 3] == 'm' && + name.data[name.len - 2] == '4') + { + if (name.len == 8) { + ngx_str_set(&mpd, "index"); + } else { + mpd.data = name.data; + mpd.len = name.len - 9; + } + + p = ngx_snprintf(mpd_path, sizeof(mpd_path) - 1, "%V/%V.mpd", + ppath, &mpd); + *p = 0; + + if (ngx_file_info(mpd_path, &fi) != NGX_FILE_ERROR) { + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0, + "dash: cleanup '%V' delayed, mpd exists '%s'", + &name, mpd_path); + continue; + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0, + "dash: cleanup '%V' allowed, mpd missing '%s'", + &name, mpd_path); + + max_age = 0; + + } else if (name.len >= 4 && name.data[name.len - 4] == '.' && + name.data[name.len - 3] == 'm' && + name.data[name.len - 2] == '4' && + name.data[name.len - 1] == 'v') + { + max_age = playlen / 500; + + } else if (name.len >= 4 && name.data[name.len - 4] == '.' && + name.data[name.len - 3] == 'm' && + name.data[name.len - 2] == '4' && + name.data[name.len - 1] == 'a') + { + max_age = playlen / 500; + + } else if (name.len >= 4 && name.data[name.len - 4] == '.' && + name.data[name.len - 3] == 'm' && + name.data[name.len - 2] == 'p' && + name.data[name.len - 1] == 'd') + { + max_age = playlen / 500; + + } else if (name.len >= 4 && name.data[name.len - 4] == '.' && + name.data[name.len - 3] == 'r' && + name.data[name.len - 2] == 'a' && + name.data[name.len - 1] == 'w') + { + max_age = playlen / 1000; + + } else { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0, + "dash: cleanup skip unknown file type '%V'", &name); + continue; + } + + mtime = ngx_de_mtime(&dir); + if (mtime + max_age > ngx_cached_time->sec) { + continue; + } + + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0, + "dash: cleanup '%V' mtime=%T age=%T", + &name, mtime, ngx_cached_time->sec - mtime); + + if (ngx_delete_file(path) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno, + "dash: cleanup " ngx_delete_file_n " failed on '%V'", + &spath); + continue; + } + + nerased++; + } +} + + +#if (nginx_version >= 1011005) +static ngx_msec_t +#else +static time_t +#endif +ngx_rtmp_dash_cleanup(void *data) +{ + ngx_rtmp_dash_cleanup_t *cleanup = data; + + ngx_rtmp_dash_cleanup_dir(&cleanup->path, cleanup->playlen); + +#if (nginx_version >= 1011005) + return cleanup->playlen * 2; +#else + return cleanup->playlen / 500; +#endif +} + + +static void * +ngx_rtmp_dash_create_app_conf(ngx_conf_t *cf) +{ + ngx_rtmp_dash_app_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_dash_app_conf_t)); + if (conf == NULL) { + return NULL; + } + + conf->dash = NGX_CONF_UNSET; + conf->fraglen = NGX_CONF_UNSET_MSEC; + conf->playlen = NGX_CONF_UNSET_MSEC; + conf->cleanup = NGX_CONF_UNSET; + conf->nested = NGX_CONF_UNSET; + + return conf; +} + + +static char * +ngx_rtmp_dash_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_rtmp_dash_app_conf_t *prev = parent; + ngx_rtmp_dash_app_conf_t *conf = child; + ngx_rtmp_dash_cleanup_t *cleanup; + + ngx_conf_merge_value(conf->dash, prev->dash, 0); + ngx_conf_merge_msec_value(conf->fraglen, prev->fraglen, 5000); + ngx_conf_merge_msec_value(conf->playlen, prev->playlen, 30000); + ngx_conf_merge_value(conf->cleanup, prev->cleanup, 1); + ngx_conf_merge_value(conf->nested, prev->nested, 0); + + if (conf->fraglen) { + conf->winfrags = conf->playlen / conf->fraglen; + } + + /* schedule cleanup */ + + if (conf->dash && conf->path.len && conf->cleanup) { + if (conf->path.data[conf->path.len - 1] == '/') { + conf->path.len--; + } + + cleanup = ngx_pcalloc(cf->pool, sizeof(*cleanup)); + if (cleanup == NULL) { + return NGX_CONF_ERROR; + } + + cleanup->path = conf->path; + cleanup->playlen = conf->playlen; + + conf->slot = ngx_pcalloc(cf->pool, sizeof(*conf->slot)); + if (conf->slot == NULL) { + return NGX_CONF_ERROR; + } + + conf->slot->manager = ngx_rtmp_dash_cleanup; + conf->slot->name = conf->path; + conf->slot->data = cleanup; + conf->slot->conf_file = cf->conf_file->file.name.data; + conf->slot->line = cf->conf_file->line; + + if (ngx_add_path(cf, &conf->slot) != NGX_OK) { + return NGX_CONF_ERROR; + } + } + + ngx_conf_merge_str_value(conf->path, prev->path, ""); + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_rtmp_dash_postconfiguration(ngx_conf_t *cf) +{ + ngx_rtmp_handler_pt *h; + ngx_rtmp_core_main_conf_t *cmcf; + + cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); + + h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_VIDEO]); + *h = ngx_rtmp_dash_video; + + h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AUDIO]); + *h = ngx_rtmp_dash_audio; + + next_publish = ngx_rtmp_publish; + ngx_rtmp_publish = ngx_rtmp_dash_publish; + + next_close_stream = ngx_rtmp_close_stream; + ngx_rtmp_close_stream = ngx_rtmp_dash_close_stream; + + next_stream_begin = ngx_rtmp_stream_begin; + ngx_rtmp_stream_begin = ngx_rtmp_dash_stream_begin; + + next_stream_eof = ngx_rtmp_stream_eof; + ngx_rtmp_stream_eof = ngx_rtmp_dash_stream_eof; + + return NGX_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/dash/ngx_rtmp_mp4.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/dash/ngx_rtmp_mp4.c new file mode 100644 index 0000000000..98efe14dcf --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/dash/ngx_rtmp_mp4.c @@ -0,0 +1,1167 @@ + + +#include +#include +#include "ngx_rtmp_mp4.h" +#include + + +static ngx_int_t +ngx_rtmp_mp4_field_32(ngx_buf_t *b, uint32_t n) +{ + u_char bytes[4]; + + bytes[0] = ((uint32_t) n >> 24) & 0xFF; + bytes[1] = ((uint32_t) n >> 16) & 0xFF; + bytes[2] = ((uint32_t) n >> 8) & 0xFF; + bytes[3] = (uint32_t) n & 0xFF; + + if (b->last + sizeof(bytes) > b->end) { + return NGX_ERROR; + } + + b->last = ngx_cpymem(b->last, bytes, sizeof(bytes)); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_field_24(ngx_buf_t *b, uint32_t n) +{ + u_char bytes[3]; + + bytes[0] = ((uint32_t) n >> 16) & 0xFF; + bytes[1] = ((uint32_t) n >> 8) & 0xFF; + bytes[2] = (uint32_t) n & 0xFF; + + if (b->last + sizeof(bytes) > b->end) { + return NGX_ERROR; + } + + b->last = ngx_cpymem(b->last, bytes, sizeof(bytes)); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_field_16(ngx_buf_t *b, uint16_t n) +{ + u_char bytes[2]; + + bytes[0] = ((uint32_t) n >> 8) & 0xFF; + bytes[1] = (uint32_t) n & 0xFF; + + if (b->last + sizeof(bytes) > b->end) { + return NGX_ERROR; + } + + b->last = ngx_cpymem(b->last, bytes, sizeof(bytes)); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_field_8(ngx_buf_t *b, uint8_t n) +{ + u_char bytes[1]; + + bytes[0] = n & 0xFF; + + if (b->last + sizeof(bytes) > b->end) { + return NGX_ERROR; + } + + b->last = ngx_cpymem(b->last, bytes, sizeof(bytes)); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_put_descr(ngx_buf_t *b, int tag, size_t size) +{ + ngx_rtmp_mp4_field_8(b, (uint8_t) tag); + ngx_rtmp_mp4_field_8(b, size & 0x7F); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_data(ngx_buf_t *b, void *data, size_t n) +{ + if (b->last + n > b->end) { + return NGX_ERROR; + } + + b->last = ngx_cpymem(b->last, (u_char *) data, n); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_box(ngx_buf_t *b, const char box[4]) +{ + if (b->last + 4 > b->end) { + return NGX_ERROR; + } + + b->last = ngx_cpymem(b->last, (u_char *) box, 4); + + return NGX_OK; +} + + +static u_char * +ngx_rtmp_mp4_start_box(ngx_buf_t *b, const char box[4]) +{ + u_char *p; + + p = b->last; + + if (ngx_rtmp_mp4_field_32(b, 0) != NGX_OK) { + return NULL; + } + + if (ngx_rtmp_mp4_box(b, box) != NGX_OK) { + return NULL; + } + + return p; +} + + +static ngx_int_t +ngx_rtmp_mp4_update_box_size(ngx_buf_t *b, u_char *p) +{ + u_char *curpos; + + if (p == NULL) { + return NGX_ERROR; + } + + curpos = b->last; + + b->last = p; + + ngx_rtmp_mp4_field_32(b, (uint32_t) (curpos - p)); + + b->last = curpos; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_matrix(ngx_buf_t *buf, uint32_t a, uint32_t b, uint32_t c, + uint32_t d, uint32_t tx, uint32_t ty) +{ + +/* + * transformation matrix + * |a b u| + * |c d v| + * |tx ty w| + */ + + ngx_rtmp_mp4_field_32(buf, a << 16); /* 16.16 format */ + ngx_rtmp_mp4_field_32(buf, b << 16); /* 16.16 format */ + ngx_rtmp_mp4_field_32(buf, 0); /* u in 2.30 format */ + ngx_rtmp_mp4_field_32(buf, c << 16); /* 16.16 format */ + ngx_rtmp_mp4_field_32(buf, d << 16); /* 16.16 format */ + ngx_rtmp_mp4_field_32(buf, 0); /* v in 2.30 format */ + ngx_rtmp_mp4_field_32(buf, tx << 16); /* 16.16 format */ + ngx_rtmp_mp4_field_32(buf, ty << 16); /* 16.16 format */ + ngx_rtmp_mp4_field_32(buf, 1 << 30); /* w in 2.30 format */ + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_mp4_write_ftyp(ngx_buf_t *b) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "ftyp"); + + /* major brand */ + ngx_rtmp_mp4_box(b, "iso6"); + + /* minor version */ + ngx_rtmp_mp4_field_32(b, 1); + + /* compatible brands */ + ngx_rtmp_mp4_box(b, "isom"); + ngx_rtmp_mp4_box(b, "iso6"); + ngx_rtmp_mp4_box(b, "dash"); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_mp4_write_styp(ngx_buf_t *b) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "styp"); + + /* major brand */ + ngx_rtmp_mp4_box(b, "iso6"); + + /* minor version */ + ngx_rtmp_mp4_field_32(b, 1); + + /* compatible brands */ + ngx_rtmp_mp4_box(b, "isom"); + ngx_rtmp_mp4_box(b, "iso6"); + ngx_rtmp_mp4_box(b, "dash"); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_mvhd(ngx_buf_t *b) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "mvhd"); + + /* version */ + ngx_rtmp_mp4_field_32(b, 0); + + /* creation time */ + ngx_rtmp_mp4_field_32(b, 0); + + /* modification time */ + ngx_rtmp_mp4_field_32(b, 0); + + /* timescale */ + ngx_rtmp_mp4_field_32(b, 1000); + + /* duration */ + ngx_rtmp_mp4_field_32(b, 0); + + /* reserved */ + ngx_rtmp_mp4_field_32(b, 0x00010000); + ngx_rtmp_mp4_field_16(b, 0x0100); + ngx_rtmp_mp4_field_16(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + + ngx_rtmp_mp4_write_matrix(b, 1, 0, 0, 1, 0, 0); + + /* reserved */ + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + + /* next track id */ + ngx_rtmp_mp4_field_32(b, 1); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_tkhd(ngx_rtmp_session_t *s, ngx_buf_t *b, + ngx_rtmp_mp4_track_type_t ttype) +{ + u_char *pos; + ngx_rtmp_codec_ctx_t *codec_ctx; + + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + pos = ngx_rtmp_mp4_start_box(b, "tkhd"); + + /* version */ + ngx_rtmp_mp4_field_8(b, 0); + + /* flags: TrackEnabled */ + ngx_rtmp_mp4_field_24(b, 0x0000000f); + + /* creation time */ + ngx_rtmp_mp4_field_32(b, 0); + + /* modification time */ + ngx_rtmp_mp4_field_32(b, 0); + + /* track id */ + ngx_rtmp_mp4_field_32(b, 1); + + /* reserved */ + ngx_rtmp_mp4_field_32(b, 0); + + /* duration */ + ngx_rtmp_mp4_field_32(b, 0); + + /* reserved */ + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + + /* reserved */ + ngx_rtmp_mp4_field_16(b, ttype == NGX_RTMP_MP4_VIDEO_TRACK ? 0 : 0x0100); + + /* reserved */ + ngx_rtmp_mp4_field_16(b, 0); + + ngx_rtmp_mp4_write_matrix(b, 1, 0, 0, 1, 0, 0); + + if (ttype == NGX_RTMP_MP4_VIDEO_TRACK) { + ngx_rtmp_mp4_field_32(b, (uint32_t) codec_ctx->width << 16); + ngx_rtmp_mp4_field_32(b, (uint32_t) codec_ctx->height << 16); + } else { + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + } + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_mdhd(ngx_buf_t *b) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "mdhd"); + + /* version */ + ngx_rtmp_mp4_field_32(b, 0); + + /* creation time */ + ngx_rtmp_mp4_field_32(b, 0); + + /* modification time */ + ngx_rtmp_mp4_field_32(b, 0); + + /* time scale*/ + ngx_rtmp_mp4_field_32(b, 1000); + + /* duration */ + ngx_rtmp_mp4_field_32(b, 0); + + /* lanuguage */ + ngx_rtmp_mp4_field_16(b, 0x15C7); + + /* reserved */ + ngx_rtmp_mp4_field_16(b, 0); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_hdlr(ngx_buf_t *b, ngx_rtmp_mp4_track_type_t ttype) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "hdlr"); + + /* version and flags */ + ngx_rtmp_mp4_field_32(b, 0); + + /* pre defined */ + ngx_rtmp_mp4_field_32(b, 0); + + if (ttype == NGX_RTMP_MP4_VIDEO_TRACK) { + ngx_rtmp_mp4_box(b, "vide"); + } else { + ngx_rtmp_mp4_box(b, "soun"); + } + + /* reserved */ + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + + if (ttype == NGX_RTMP_MP4_VIDEO_TRACK) { + /* video handler string, NULL-terminated */ + ngx_rtmp_mp4_data(b, "VideoHandler", sizeof("VideoHandler")); + } else { + /* sound handler string, NULL-terminated */ + ngx_rtmp_mp4_data(b, "SoundHandler", sizeof("SoundHandler")); + } + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_vmhd(ngx_buf_t *b) +{ + /* size is always 20, apparently */ + ngx_rtmp_mp4_field_32(b, 20); + + ngx_rtmp_mp4_box(b, "vmhd"); + + /* version and flags */ + ngx_rtmp_mp4_field_32(b, 0x01); + + /* reserved (graphics mode=copy) */ + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_smhd(ngx_buf_t *b) +{ + /* size is always 16, apparently */ + ngx_rtmp_mp4_field_32(b, 16); + + ngx_rtmp_mp4_box(b, "smhd"); + + /* version and flags */ + ngx_rtmp_mp4_field_32(b, 0); + + /* reserved (balance normally=0) */ + ngx_rtmp_mp4_field_16(b, 0); + ngx_rtmp_mp4_field_16(b, 0); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_dref(ngx_buf_t *b) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "dref"); + + /* version and flags */ + ngx_rtmp_mp4_field_32(b, 0); + + /* entry count */ + ngx_rtmp_mp4_field_32(b, 1); + + /* url size */ + ngx_rtmp_mp4_field_32(b, 0xc); + + ngx_rtmp_mp4_box(b, "url "); + + /* version and flags */ + ngx_rtmp_mp4_field_32(b, 0x00000001); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_dinf(ngx_buf_t *b) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "dinf"); + + ngx_rtmp_mp4_write_dref(b); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_avcc(ngx_rtmp_session_t *s, ngx_buf_t *b) +{ + u_char *pos, *p; + ngx_chain_t *in; + ngx_rtmp_codec_ctx_t *codec_ctx; + + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + if (codec_ctx == NULL) { + return NGX_ERROR; + } + + in = codec_ctx->avc_header->chain; + if (in == NULL) { + return NGX_ERROR; + } + + pos = ngx_rtmp_mp4_start_box(b, "avcC"); + + /* assume config fits one chunk (highly probable) */ + + /* + * Skip: + * - flv fmt + * - H264 CONF/PICT (0x00) + * - 0 + * - 0 + * - 0 + */ + + p = in->buf->pos + 5; + + if (p < in->buf->last) { + ngx_rtmp_mp4_data(b, p, (size_t) (in->buf->last - p)); + } else { + ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno, + "dash: invalid avcc received"); + } + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_video(ngx_rtmp_session_t *s, ngx_buf_t *b) +{ + u_char *pos; + ngx_rtmp_codec_ctx_t *codec_ctx; + + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + pos = ngx_rtmp_mp4_start_box(b, "avc1"); + + /* reserved */ + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_16(b, 0); + + /* data reference index */ + ngx_rtmp_mp4_field_16(b, 1); + + /* codec stream version & revision */ + ngx_rtmp_mp4_field_16(b, 0); + ngx_rtmp_mp4_field_16(b, 0); + + /* reserved */ + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + + /* width & height */ + ngx_rtmp_mp4_field_16(b, (uint16_t) codec_ctx->width); + ngx_rtmp_mp4_field_16(b, (uint16_t) codec_ctx->height); + + /* horizontal & vertical resolutions 72 dpi */ + ngx_rtmp_mp4_field_32(b, 0x00480000); + ngx_rtmp_mp4_field_32(b, 0x00480000); + + /* data size */ + ngx_rtmp_mp4_field_32(b, 0); + + /* frame count */ + ngx_rtmp_mp4_field_16(b, 1); + + /* compressor name */ + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + + /* reserved */ + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_16(b, 0x18); + ngx_rtmp_mp4_field_16(b, 0xffff); + + ngx_rtmp_mp4_write_avcc(s, b); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_esds(ngx_rtmp_session_t *s, ngx_buf_t *b) +{ + size_t dsi_len; + u_char *pos, *dsi; + ngx_buf_t *db; + ngx_rtmp_codec_ctx_t *codec_ctx; + + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + if (codec_ctx == NULL || codec_ctx->aac_header == NULL) { + return NGX_ERROR; + } + + db = codec_ctx->aac_header->chain->buf; + if (db == NULL) { + return NGX_ERROR; + } + + dsi = db->pos + 2; + if (dsi > db->last) { + return NGX_ERROR; + } + + dsi_len = db->last - dsi; + + pos = ngx_rtmp_mp4_start_box(b, "esds"); + + /* version */ + ngx_rtmp_mp4_field_32(b, 0); + + + /* ES Descriptor */ + + ngx_rtmp_mp4_put_descr(b, 0x03, 23 + dsi_len); + + /* ES_ID */ + ngx_rtmp_mp4_field_16(b, 1); + + /* flags */ + ngx_rtmp_mp4_field_8(b, 0); + + + /* DecoderConfig Descriptor */ + + ngx_rtmp_mp4_put_descr(b, 0x04, 15 + dsi_len); + + /* objectTypeIndication: Audio ISO/IEC 14496-3 (AAC) */ + ngx_rtmp_mp4_field_8(b, 0x40); + + /* streamType: AudioStream */ + ngx_rtmp_mp4_field_8(b, 0x15); + + /* bufferSizeDB */ + ngx_rtmp_mp4_field_24(b, 0); + + /* maxBitrate */ + ngx_rtmp_mp4_field_32(b, 0x0001F151); + + /* avgBitrate */ + ngx_rtmp_mp4_field_32(b, 0x0001F14D); + + + /* DecoderSpecificInfo Descriptor */ + + ngx_rtmp_mp4_put_descr(b, 0x05, dsi_len); + ngx_rtmp_mp4_data(b, dsi, dsi_len); + + + /* SL Descriptor */ + + ngx_rtmp_mp4_put_descr(b, 0x06, 1); + ngx_rtmp_mp4_field_8(b, 0x02); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_audio(ngx_rtmp_session_t *s, ngx_buf_t *b) +{ + u_char *pos; + ngx_rtmp_codec_ctx_t *codec_ctx; + + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + pos = ngx_rtmp_mp4_start_box(b, "mp4a"); + + /* reserved */ + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_16(b, 0); + + /* data reference index */ + ngx_rtmp_mp4_field_16(b, 1); + + /* reserved */ + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + + /* channel count */ + ngx_rtmp_mp4_field_16(b, (uint16_t) codec_ctx->audio_channels); + + /* sample size */ + ngx_rtmp_mp4_field_16(b, (uint16_t) (codec_ctx->sample_size * 8)); + + /* reserved */ + ngx_rtmp_mp4_field_32(b, 0); + + /* time scale */ + ngx_rtmp_mp4_field_16(b, 1000); + + /* sample rate */ + ngx_rtmp_mp4_field_16(b, (uint16_t) codec_ctx->sample_rate); + + ngx_rtmp_mp4_write_esds(s, b); +#if 0 + /* tag size*/ + ngx_rtmp_mp4_field_32(b, 8); + + /* null tag */ + ngx_rtmp_mp4_field_32(b, 0); +#endif + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_stsd(ngx_rtmp_session_t *s, ngx_buf_t *b, + ngx_rtmp_mp4_track_type_t ttype) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "stsd"); + + /* version & flags */ + ngx_rtmp_mp4_field_32(b, 0); + + /* entry count */ + ngx_rtmp_mp4_field_32(b, 1); + + if (ttype == NGX_RTMP_MP4_VIDEO_TRACK) { + ngx_rtmp_mp4_write_video(s, b); + } else { + ngx_rtmp_mp4_write_audio(s, b); + } + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_stts(ngx_buf_t *b) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "stts"); + + ngx_rtmp_mp4_field_32(b, 0); /* version */ + ngx_rtmp_mp4_field_32(b, 0); /* entry count */ + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_stsc(ngx_buf_t *b) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "stsc"); + + ngx_rtmp_mp4_field_32(b, 0); /* version */ + ngx_rtmp_mp4_field_32(b, 0); /* entry count */ + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_stsz(ngx_buf_t *b) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "stsz"); + + ngx_rtmp_mp4_field_32(b, 0); /* version */ + ngx_rtmp_mp4_field_32(b, 0); /* entry count */ + ngx_rtmp_mp4_field_32(b, 0); /* moar zeros */ + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_stco(ngx_buf_t *b) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "stco"); + + ngx_rtmp_mp4_field_32(b, 0); /* version */ + ngx_rtmp_mp4_field_32(b, 0); /* entry count */ + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_stbl(ngx_rtmp_session_t *s, ngx_buf_t *b, + ngx_rtmp_mp4_track_type_t ttype) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "stbl"); + + ngx_rtmp_mp4_write_stsd(s, b, ttype); + ngx_rtmp_mp4_write_stts(b); + ngx_rtmp_mp4_write_stsc(b); + ngx_rtmp_mp4_write_stsz(b); + ngx_rtmp_mp4_write_stco(b); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_minf(ngx_rtmp_session_t *s, ngx_buf_t *b, + ngx_rtmp_mp4_track_type_t ttype) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "minf"); + + if (ttype == NGX_RTMP_MP4_VIDEO_TRACK) { + ngx_rtmp_mp4_write_vmhd(b); + } else { + ngx_rtmp_mp4_write_smhd(b); + } + + ngx_rtmp_mp4_write_dinf(b); + ngx_rtmp_mp4_write_stbl(s, b, ttype); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_mdia(ngx_rtmp_session_t *s, ngx_buf_t *b, + ngx_rtmp_mp4_track_type_t ttype) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "mdia"); + + ngx_rtmp_mp4_write_mdhd(b); + ngx_rtmp_mp4_write_hdlr(b, ttype); + ngx_rtmp_mp4_write_minf(s, b, ttype); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + +static ngx_int_t +ngx_rtmp_mp4_write_trak(ngx_rtmp_session_t *s, ngx_buf_t *b, + ngx_rtmp_mp4_track_type_t ttype) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "trak"); + + ngx_rtmp_mp4_write_tkhd(s, b, ttype); + ngx_rtmp_mp4_write_mdia(s, b, ttype); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_mvex(ngx_buf_t *b) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "mvex"); + + ngx_rtmp_mp4_field_32(b, 0x20); + + ngx_rtmp_mp4_box(b, "trex"); + + /* version & flags */ + ngx_rtmp_mp4_field_32(b, 0); + + /* track id */ + ngx_rtmp_mp4_field_32(b, 1); + + /* default sample description index */ + ngx_rtmp_mp4_field_32(b, 1); + + /* default sample duration */ + ngx_rtmp_mp4_field_32(b, 0); + + /* default sample size, 1024 for AAC */ + ngx_rtmp_mp4_field_32(b, 0); + + /* default sample flags, key on */ + ngx_rtmp_mp4_field_32(b, 0); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_mp4_write_moov(ngx_rtmp_session_t *s, ngx_buf_t *b, + ngx_rtmp_mp4_track_type_t ttype) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "moov"); + + ngx_rtmp_mp4_write_mvhd(b); + ngx_rtmp_mp4_write_mvex(b); + ngx_rtmp_mp4_write_trak(s, b, ttype); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_tfhd(ngx_buf_t *b) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "tfhd"); + + /* version & flags */ + ngx_rtmp_mp4_field_32(b, 0x00020000); + + /* track id */ + ngx_rtmp_mp4_field_32(b, 1); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_tfdt(ngx_buf_t *b, uint32_t earliest_pres_time) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "tfdt"); + + /* version == 1 aka 64 bit integer */ + ngx_rtmp_mp4_field_32(b, 0x00000000); + ngx_rtmp_mp4_field_32(b, earliest_pres_time); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_trun(ngx_buf_t *b, uint32_t sample_count, + ngx_rtmp_mp4_sample_t *samples, ngx_uint_t sample_mask, u_char *moof_pos) +{ + u_char *pos; + uint32_t i, offset, nitems, flags; + + pos = ngx_rtmp_mp4_start_box(b, "trun"); + + nitems = 0; + + /* data offset present */ + flags = 0x01; + + if (sample_mask & NGX_RTMP_MP4_SAMPLE_DURATION) { + nitems++; + flags |= 0x000100; + } + + if (sample_mask & NGX_RTMP_MP4_SAMPLE_SIZE) { + nitems++; + flags |= 0x000200; + } + + if (sample_mask & NGX_RTMP_MP4_SAMPLE_KEY) { + nitems++; + flags |= 0x000400; + } + + if (sample_mask & NGX_RTMP_MP4_SAMPLE_DELAY) { + nitems++; + flags |= 0x000800; + } + + offset = (pos - moof_pos) + 20 + (sample_count * nitems * 4) + 8; + + ngx_rtmp_mp4_field_32(b, flags); + ngx_rtmp_mp4_field_32(b, sample_count); + ngx_rtmp_mp4_field_32(b, offset); + + for (i = 0; i < sample_count; i++, samples++) { + + if (sample_mask & NGX_RTMP_MP4_SAMPLE_DURATION) { + ngx_rtmp_mp4_field_32(b, samples->duration); + } + + if (sample_mask & NGX_RTMP_MP4_SAMPLE_SIZE) { + ngx_rtmp_mp4_field_32(b, samples->size); + } + + if (sample_mask & NGX_RTMP_MP4_SAMPLE_KEY) { + ngx_rtmp_mp4_field_32(b, samples->key ? 0x00000000 : 0x00010000); + } + + if (sample_mask & NGX_RTMP_MP4_SAMPLE_DELAY) { + ngx_rtmp_mp4_field_32(b, samples->delay); + } + } + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_traf(ngx_buf_t *b, uint32_t earliest_pres_time, + uint32_t sample_count, ngx_rtmp_mp4_sample_t *samples, + ngx_uint_t sample_mask, u_char *moof_pos) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "traf"); + + ngx_rtmp_mp4_write_tfhd(b); + ngx_rtmp_mp4_write_tfdt(b, earliest_pres_time); + ngx_rtmp_mp4_write_trun(b, sample_count, samples, sample_mask, moof_pos); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_mfhd(ngx_buf_t *b, uint32_t index) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "mfhd"); + + /* don't know what this is */ + ngx_rtmp_mp4_field_32(b, 0); + + /* fragment index. */ + ngx_rtmp_mp4_field_32(b, index); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_mp4_write_sidx(ngx_buf_t *b, ngx_uint_t reference_size, + uint32_t earliest_pres_time, uint32_t latest_pres_time) +{ + u_char *pos; + uint32_t duration; + + duration = latest_pres_time - earliest_pres_time; + + pos = ngx_rtmp_mp4_start_box(b, "sidx"); + + /* version */ + ngx_rtmp_mp4_field_32(b, 0); + + /* reference id */ + ngx_rtmp_mp4_field_32(b, 1); + + /* timescale */ + ngx_rtmp_mp4_field_32(b, 1000); + + /* earliest presentation time */ + ngx_rtmp_mp4_field_32(b, earliest_pres_time); + + /* first offset */ + ngx_rtmp_mp4_field_32(b, duration); /*TODO*/ + + /* reserved */ + ngx_rtmp_mp4_field_16(b, 0); + + /* reference count = 1 */ + ngx_rtmp_mp4_field_16(b, 1); + + /* 1st bit is reference type, the rest is reference size */ + ngx_rtmp_mp4_field_32(b, reference_size); + + /* subsegment duration */ + ngx_rtmp_mp4_field_32(b, duration); + + /* first bit is startsWithSAP (=1), next 3 bits are SAP type (=001) */ + ngx_rtmp_mp4_field_8(b, 0x90); + + /* SAP delta time */ + ngx_rtmp_mp4_field_24(b, 0); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_mp4_write_moof(ngx_buf_t *b, uint32_t earliest_pres_time, + uint32_t sample_count, ngx_rtmp_mp4_sample_t *samples, + ngx_uint_t sample_mask, uint32_t index) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "moof"); + + ngx_rtmp_mp4_write_mfhd(b, index); + ngx_rtmp_mp4_write_traf(b, earliest_pres_time, sample_count, samples, + sample_mask, pos); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +ngx_uint_t +ngx_rtmp_mp4_write_mdat(ngx_buf_t *b, ngx_uint_t size) +{ + ngx_rtmp_mp4_field_32(b, size); + + ngx_rtmp_mp4_box(b, "mdat"); + + return NGX_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/dash/ngx_rtmp_mp4.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/dash/ngx_rtmp_mp4.h new file mode 100644 index 0000000000..697b6c87c7 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/dash/ngx_rtmp_mp4.h @@ -0,0 +1,52 @@ + + +#ifndef _NGX_RTMP_MP4_H_INCLUDED_ +#define _NGX_RTMP_MP4_H_INCLUDED_ + + +#include +#include +#include + + +#define NGX_RTMP_MP4_SAMPLE_SIZE 0x01 +#define NGX_RTMP_MP4_SAMPLE_DURATION 0x02 +#define NGX_RTMP_MP4_SAMPLE_DELAY 0x04 +#define NGX_RTMP_MP4_SAMPLE_KEY 0x08 + + +typedef struct { + uint32_t size; + uint32_t duration; + uint32_t delay; + uint32_t timestamp; + unsigned key:1; +} ngx_rtmp_mp4_sample_t; + + +typedef enum { + NGX_RTMP_MP4_FILETYPE_INIT, + NGX_RTMP_MP4_FILETYPE_SEG +} ngx_rtmp_mp4_file_type_t; + + +typedef enum { + NGX_RTMP_MP4_VIDEO_TRACK, + NGX_RTMP_MP4_AUDIO_TRACK +} ngx_rtmp_mp4_track_type_t; + + +ngx_int_t ngx_rtmp_mp4_write_ftyp(ngx_buf_t *b); +ngx_int_t ngx_rtmp_mp4_write_styp(ngx_buf_t *b); +ngx_int_t ngx_rtmp_mp4_write_moov(ngx_rtmp_session_t *s, ngx_buf_t *b, + ngx_rtmp_mp4_track_type_t ttype); +ngx_int_t ngx_rtmp_mp4_write_moof(ngx_buf_t *b, uint32_t earliest_pres_time, + uint32_t sample_count, ngx_rtmp_mp4_sample_t *samples, + ngx_uint_t sample_mask, uint32_t index); +ngx_int_t ngx_rtmp_mp4_write_sidx(ngx_buf_t *b, + ngx_uint_t reference_size, uint32_t earliest_pres_time, + uint32_t latest_pres_time); +ngx_uint_t ngx_rtmp_mp4_write_mdat(ngx_buf_t *b, ngx_uint_t size); + + +#endif /* _NGX_RTMP_MP4_H_INCLUDED_ */ diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/hls/ngx_rtmp_hls_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/hls/ngx_rtmp_hls_module.c new file mode 100644 index 0000000000..35f1c6e03b --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/hls/ngx_rtmp_hls_module.c @@ -0,0 +1,2713 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include +#include +#include +#include "ngx_rtmp_mpegts.h" + + +static ngx_rtmp_publish_pt next_publish; +static ngx_rtmp_close_stream_pt next_close_stream; +static ngx_rtmp_stream_begin_pt next_stream_begin; +static ngx_rtmp_stream_eof_pt next_stream_eof; + + +static char * ngx_rtmp_hls_variant(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static ngx_int_t ngx_rtmp_hls_postconfiguration(ngx_conf_t *cf); +static void * ngx_rtmp_hls_create_app_conf(ngx_conf_t *cf); +static char * ngx_rtmp_hls_merge_app_conf(ngx_conf_t *cf, + void *parent, void *child); +static ngx_int_t ngx_rtmp_hls_flush_audio(ngx_rtmp_session_t *s); +static ngx_int_t ngx_rtmp_hls_ensure_directory(ngx_rtmp_session_t *s, + ngx_str_t *path); + + +#define NGX_RTMP_HLS_BUFSIZE (1024*1024) +#define NGX_RTMP_HLS_DIR_ACCESS 0744 + + +typedef struct { + uint64_t id; + uint64_t key_id; + double duration; + unsigned active:1; + unsigned discont:1; /* before */ +} ngx_rtmp_hls_frag_t; + + +typedef struct { + ngx_str_t suffix; + ngx_array_t args; +} ngx_rtmp_hls_variant_t; + + +typedef struct { + unsigned opened:1; + + ngx_rtmp_mpegts_file_t file; + + ngx_str_t playlist; + ngx_str_t playlist_bak; + ngx_str_t var_playlist; + ngx_str_t var_playlist_bak; + ngx_str_t stream; + ngx_str_t keyfile; + ngx_str_t name; + u_char key[16]; + + uint64_t frag; + uint64_t frag_ts; + uint64_t key_id; + ngx_uint_t nfrags; + ngx_rtmp_hls_frag_t *frags; /* circular 2 * winfrags + 1 */ + + ngx_uint_t audio_cc; + ngx_uint_t video_cc; + ngx_uint_t key_frags; + + uint64_t aframe_base; + uint64_t aframe_num; + + ngx_buf_t *aframe; + uint64_t aframe_pts; + + ngx_rtmp_hls_variant_t *var; +} ngx_rtmp_hls_ctx_t; + + +typedef struct { + ngx_str_t path; + ngx_msec_t playlen; + ngx_uint_t frags_per_key; +} ngx_rtmp_hls_cleanup_t; + + +typedef struct { + ngx_flag_t hls; + ngx_msec_t fraglen; + ngx_msec_t max_fraglen; + ngx_msec_t muxdelay; + ngx_msec_t sync; + ngx_msec_t playlen; + ngx_uint_t winfrags; + ngx_flag_t continuous; + ngx_flag_t nested; + ngx_str_t path; + ngx_uint_t naming; + ngx_uint_t slicing; + ngx_uint_t type; + ngx_path_t *slot; + ngx_msec_t max_audio_delay; + size_t audio_buffer_size; + ngx_flag_t cleanup; + ngx_array_t *variant; + ngx_str_t base_url; + ngx_int_t granularity; + ngx_flag_t keys; + ngx_str_t key_path; + ngx_str_t key_url; + ngx_uint_t frags_per_key; +} ngx_rtmp_hls_app_conf_t; + + +#define NGX_RTMP_HLS_NAMING_SEQUENTIAL 1 +#define NGX_RTMP_HLS_NAMING_TIMESTAMP 2 +#define NGX_RTMP_HLS_NAMING_SYSTEM 3 + + +#define NGX_RTMP_HLS_SLICING_PLAIN 1 +#define NGX_RTMP_HLS_SLICING_ALIGNED 2 + + +#define NGX_RTMP_HLS_TYPE_LIVE 1 +#define NGX_RTMP_HLS_TYPE_EVENT 2 + + +static ngx_conf_enum_t ngx_rtmp_hls_naming_slots[] = { + { ngx_string("sequential"), NGX_RTMP_HLS_NAMING_SEQUENTIAL }, + { ngx_string("timestamp"), NGX_RTMP_HLS_NAMING_TIMESTAMP }, + { ngx_string("system"), NGX_RTMP_HLS_NAMING_SYSTEM }, + { ngx_null_string, 0 } +}; + + +static ngx_conf_enum_t ngx_rtmp_hls_slicing_slots[] = { + { ngx_string("plain"), NGX_RTMP_HLS_SLICING_PLAIN }, + { ngx_string("aligned"), NGX_RTMP_HLS_SLICING_ALIGNED }, + { ngx_null_string, 0 } +}; + + +static ngx_conf_enum_t ngx_rtmp_hls_type_slots[] = { + { ngx_string("live"), NGX_RTMP_HLS_TYPE_LIVE }, + { ngx_string("event"), NGX_RTMP_HLS_TYPE_EVENT }, + { ngx_null_string, 0 } +}; + + +static ngx_command_t ngx_rtmp_hls_commands[] = { + + { ngx_string("hls"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, hls), + NULL }, + + { ngx_string("hls_fragment"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, fraglen), + NULL }, + + { ngx_string("hls_max_fragment"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, max_fraglen), + NULL }, + + { ngx_string("hls_path"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, path), + NULL }, + + { ngx_string("hls_playlist_length"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, playlen), + NULL }, + + { ngx_string("hls_muxdelay"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, muxdelay), + NULL }, + + { ngx_string("hls_sync"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, sync), + NULL }, + + { ngx_string("hls_continuous"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, continuous), + NULL }, + + { ngx_string("hls_nested"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, nested), + NULL }, + + { ngx_string("hls_fragment_naming"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_enum_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, naming), + &ngx_rtmp_hls_naming_slots }, + + { ngx_string("hls_fragment_slicing"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_enum_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, slicing), + &ngx_rtmp_hls_slicing_slots }, + + { ngx_string("hls_type"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_enum_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, type), + &ngx_rtmp_hls_type_slots }, + + { ngx_string("hls_max_audio_delay"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, max_audio_delay), + NULL }, + + { ngx_string("hls_audio_buffer_size"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, audio_buffer_size), + NULL }, + + { ngx_string("hls_cleanup"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, cleanup), + NULL }, + + { ngx_string("hls_variant"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_hls_variant, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("hls_base_url"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, base_url), + NULL }, + + { ngx_string("hls_fragment_naming_granularity"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, granularity), + NULL }, + + { ngx_string("hls_keys"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, keys), + NULL }, + + { ngx_string("hls_key_path"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, key_path), + NULL }, + + { ngx_string("hls_key_url"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, key_url), + NULL }, + + { ngx_string("hls_fragments_per_key"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, frags_per_key), + NULL }, + + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_rtmp_hls_module_ctx = { + NULL, /* preconfiguration */ + ngx_rtmp_hls_postconfiguration, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + ngx_rtmp_hls_create_app_conf, /* create location configuration */ + ngx_rtmp_hls_merge_app_conf, /* merge location configuration */ +}; + + +ngx_module_t ngx_rtmp_hls_module = { + NGX_MODULE_V1, + &ngx_rtmp_hls_module_ctx, /* module context */ + ngx_rtmp_hls_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_rtmp_hls_frag_t * +ngx_rtmp_hls_get_frag(ngx_rtmp_session_t *s, ngx_int_t n) +{ + ngx_rtmp_hls_ctx_t *ctx; + ngx_rtmp_hls_app_conf_t *hacf; + + hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); + + return &ctx->frags[(ctx->frag + n) % (hacf->winfrags * 2 + 1)]; +} + + +static void +ngx_rtmp_hls_next_frag(ngx_rtmp_session_t *s) +{ + ngx_rtmp_hls_ctx_t *ctx; + ngx_rtmp_hls_app_conf_t *hacf; + + hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); + + if (ctx->nfrags == hacf->winfrags) { + ctx->frag++; + } else { + ctx->nfrags++; + } +} + + +static ngx_int_t +ngx_rtmp_hls_rename_file(u_char *src, u_char *dst) +{ + /* rename file with overwrite */ + +#if (NGX_WIN32) + return MoveFileEx((LPCTSTR) src, (LPCTSTR) dst, MOVEFILE_REPLACE_EXISTING); +#else + return ngx_rename_file(src, dst); +#endif +} + + +static ngx_int_t +ngx_rtmp_hls_write_variant_playlist(ngx_rtmp_session_t *s) +{ + static u_char buffer[1024]; + + u_char *p, *last; + ssize_t rc; + ngx_fd_t fd; + ngx_str_t *arg; + ngx_uint_t n, k; + ngx_rtmp_hls_ctx_t *ctx; + ngx_rtmp_hls_variant_t *var; + ngx_rtmp_hls_app_conf_t *hacf; + + hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); + + fd = ngx_open_file(ctx->var_playlist_bak.data, NGX_FILE_WRONLY, + NGX_FILE_TRUNCATE, NGX_FILE_DEFAULT_ACCESS); + + if (fd == NGX_INVALID_FILE) { + ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno, + "hls: " ngx_open_file_n " failed: '%V'", + &ctx->var_playlist_bak); + + return NGX_ERROR; + } + +#define NGX_RTMP_HLS_VAR_HEADER "#EXTM3U\n#EXT-X-VERSION:3\n" + + rc = ngx_write_fd(fd, NGX_RTMP_HLS_VAR_HEADER, + sizeof(NGX_RTMP_HLS_VAR_HEADER) - 1); + if (rc < 0) { + ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno, + "hls: " ngx_write_fd_n " failed: '%V'", + &ctx->var_playlist_bak); + ngx_close_file(fd); + return NGX_ERROR; + } + + var = hacf->variant->elts; + for (n = 0; n < hacf->variant->nelts; n++, var++) + { + p = buffer; + last = buffer + sizeof(buffer); + + p = ngx_slprintf(p, last, "#EXT-X-STREAM-INF:PROGRAM-ID=1"); + + arg = var->args.elts; + for (k = 0; k < var->args.nelts; k++, arg++) { + p = ngx_slprintf(p, last, ",%V", arg); + } + + if (p < last) { + *p++ = '\n'; + } + + p = ngx_slprintf(p, last, "%V%*s%V", + &hacf->base_url, + ctx->name.len - ctx->var->suffix.len, ctx->name.data, + &var->suffix); + if (hacf->nested) { + p = ngx_slprintf(p, last, "%s", "/index"); + } + + p = ngx_slprintf(p, last, "%s", ".m3u8\n"); + + rc = ngx_write_fd(fd, buffer, p - buffer); + if (rc < 0) { + ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno, + "hls: " ngx_write_fd_n " failed '%V'", + &ctx->var_playlist_bak); + ngx_close_file(fd); + return NGX_ERROR; + } + } + + ngx_close_file(fd); + + if (ngx_rtmp_hls_rename_file(ctx->var_playlist_bak.data, + ctx->var_playlist.data) + == NGX_FILE_ERROR) + { + ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno, + "hls: rename failed: '%V'->'%V'", + &ctx->var_playlist_bak, &ctx->var_playlist); + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_hls_write_playlist(ngx_rtmp_session_t *s) +{ + static u_char buffer[1024]; + ngx_fd_t fd; + u_char *p, *end; + ngx_rtmp_hls_ctx_t *ctx; + ssize_t n; + ngx_rtmp_hls_app_conf_t *hacf; + ngx_rtmp_hls_frag_t *f; + ngx_uint_t i, max_frag; + ngx_str_t name_part, key_name_part; + uint64_t prev_key_id; + const char *sep, *key_sep; + + + hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); + + fd = ngx_open_file(ctx->playlist_bak.data, NGX_FILE_WRONLY, + NGX_FILE_TRUNCATE, NGX_FILE_DEFAULT_ACCESS); + + if (fd == NGX_INVALID_FILE) { + ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno, + "hls: " ngx_open_file_n " failed: '%V'", + &ctx->playlist_bak); + return NGX_ERROR; + } + + max_frag = hacf->fraglen / 1000; + + for (i = 0; i < ctx->nfrags; i++) { + f = ngx_rtmp_hls_get_frag(s, i); + if (f->duration > max_frag) { + max_frag = (ngx_uint_t) (f->duration + .5); + } + } + + p = buffer; + end = p + sizeof(buffer); + + p = ngx_slprintf(p, end, + "#EXTM3U\n" + "#EXT-X-VERSION:3\n" + "#EXT-X-MEDIA-SEQUENCE:%uL\n" + "#EXT-X-TARGETDURATION:%ui\n", + ctx->frag, max_frag); + + if (hacf->type == NGX_RTMP_HLS_TYPE_EVENT) { + p = ngx_slprintf(p, end, "#EXT-X-PLAYLIST-TYPE: EVENT\n"); + } + + n = ngx_write_fd(fd, buffer, p - buffer); + if (n < 0) { + ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno, + "hls: " ngx_write_fd_n " failed: '%V'", + &ctx->playlist_bak); + ngx_close_file(fd); + return NGX_ERROR; + } + + sep = hacf->nested ? (hacf->base_url.len ? "/" : "") : "-"; + key_sep = hacf->nested ? (hacf->key_url.len ? "/" : "") : "-"; + + name_part.len = 0; + if (!hacf->nested || hacf->base_url.len) { + name_part = ctx->name; + } + + key_name_part.len = 0; + if (!hacf->nested || hacf->key_url.len) { + key_name_part = ctx->name; + } + + prev_key_id = 0; + + for (i = 0; i < ctx->nfrags; i++) { + f = ngx_rtmp_hls_get_frag(s, i); + + p = buffer; + end = p + sizeof(buffer); + + if (f->discont) { + p = ngx_slprintf(p, end, "#EXT-X-DISCONTINUITY\n"); + } + + if (hacf->keys && (i == 0 || f->key_id != prev_key_id)) { + p = ngx_slprintf(p, end, "#EXT-X-KEY:METHOD=AES-128," + "URI=\"%V%V%s%uL.key\",IV=0x%032XL\n", + &hacf->key_url, &key_name_part, + key_sep, f->key_id, f->key_id); + } + + prev_key_id = f->key_id; + + p = ngx_slprintf(p, end, + "#EXTINF:%.3f,\n" + "%V%V%s%uL.ts\n", + f->duration, &hacf->base_url, &name_part, sep, f->id); + + ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->log, 0, + "hls: fragment frag=%uL, n=%ui/%ui, duration=%.3f, " + "discont=%i", + ctx->frag, i + 1, ctx->nfrags, f->duration, f->discont); + + n = ngx_write_fd(fd, buffer, p - buffer); + if (n < 0) { + ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno, + "hls: " ngx_write_fd_n " failed '%V'", + &ctx->playlist_bak); + ngx_close_file(fd); + return NGX_ERROR; + } + } + + ngx_close_file(fd); + + if (ngx_rtmp_hls_rename_file(ctx->playlist_bak.data, ctx->playlist.data) + == NGX_FILE_ERROR) + { + ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno, + "hls: rename failed: '%V'->'%V'", + &ctx->playlist_bak, &ctx->playlist); + return NGX_ERROR; + } + + if (ctx->var) { + return ngx_rtmp_hls_write_variant_playlist(s); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_hls_copy(ngx_rtmp_session_t *s, void *dst, u_char **src, size_t n, + ngx_chain_t **in) +{ + u_char *last; + size_t pn; + + if (*in == NULL) { + return NGX_ERROR; + } + + for ( ;; ) { + last = (*in)->buf->last; + + if ((size_t)(last - *src) >= n) { + if (dst) { + ngx_memcpy(dst, *src, n); + } + + *src += n; + + while (*in && *src == (*in)->buf->last) { + *in = (*in)->next; + if (*in) { + *src = (*in)->buf->pos; + } + } + + return NGX_OK; + } + + pn = last - *src; + + if (dst) { + ngx_memcpy(dst, *src, pn); + dst = (u_char *)dst + pn; + } + + n -= pn; + *in = (*in)->next; + + if (*in == NULL) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "hls: failed to read %uz byte(s)", n); + return NGX_ERROR; + } + + *src = (*in)->buf->pos; + } +} + + +static ngx_int_t +ngx_rtmp_hls_append_hevc_aud(ngx_rtmp_session_t *s, ngx_buf_t *out) +{ + static u_char aud_nal[] = { 0x00, 0x00, 0x00, 0x01, 0x46, 0x01, 0x50 }; + + if (out->last + sizeof(aud_nal) > out->end) { + return NGX_ERROR; + } + + out->last = ngx_cpymem(out->last, aud_nal, sizeof(aud_nal)); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_hls_append_hevc_vps_sps_pps(ngx_rtmp_session_t *s, ngx_buf_t *out) +{ + ngx_rtmp_codec_ctx_t *codec_ctx; + u_char *p; + ngx_chain_t *in; + ngx_rtmp_hls_ctx_t *ctx; + ngx_uint_t i, j, rnal_unit_len, nal_unit_len, + num_arrays, nal_unit_type, + rnum_nalus, num_nalus; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); + + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + if (ctx == NULL || codec_ctx == NULL) { + return NGX_ERROR; + } + + in = codec_ctx->avc_header->chain; + if (in == NULL) { + return NGX_ERROR; + } + + p = in->buf->pos; + /* 6 bytes + * FrameType 4 bits + * CodecID 4 bits + * AVCPacketType 1 byte + * CompositionTime 3 bytes + * HEVCDecoderConfigurationRecord + * configurationVersion 1 byte + */ + + /* 20 bytes + * HEVCDecoderConfigurationRecord + * general_profile_space 2 bits + * general_tier_flag 1 bit + * general_profile_idc 5 bits + * general_profile_compatibility_flags 4 bytes + * general_constraint_indicator_flags 6 bytes + * general_level_idc 1 byte + * min_spatial_segmentation_idc 4 bits reserved + 12 bits + * parallelismType 6 bits reserved + 2 bits + * chroma_format_idc 6 bits reserved + 2 bits + * bit_depth_luma_minus8 5 bits reserved + 3 bits + * bit_depth_chroma_minus8 5 bits reserved + 3 bits + * avgFrameRate 2 bytes + */ + + /* 1 bytes + * HEVCDecoderConfigurationRecord + * constantFrameRate 2 bits + * numTemporalLayers 3 bits + * temporalIdNested 1 bit + * lengthSizeMinusOne 2 bits + */ + + if (ngx_rtmp_hls_copy(s, NULL, &p, 27, &in) != NGX_OK) { + return NGX_ERROR; + } + + /* 1 byte + * HEVCDecoderConfigurationRecord + * numOfArrays 1 byte + */ + num_arrays = 0; + if (ngx_rtmp_hls_copy(s, &num_arrays, &p, 1, &in) != NGX_OK) { + return NGX_ERROR; + } + + for (i = 0; i < num_arrays; ++i) { + /* + * array_completeness 1 bit + * reserved 1 bit + * NAL_unit_type 6 bits + * numNalus 2 bytes + */ + if (ngx_rtmp_hls_copy(s, &nal_unit_type, &p, 1, &in) != NGX_OK) { // 32 33 34 39 + return NGX_ERROR; + } + nal_unit_type &= 0x3f; + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "rtmp-hls: append_vps_sps_pps| type %d", nal_unit_type); + if (ngx_rtmp_hls_copy(s, &rnum_nalus, &p, 2, &in) != NGX_OK) { + return NGX_ERROR; + } + num_nalus = 0; + ngx_rtmp_rmemcpy(&num_nalus, &rnum_nalus, 2); + for (j = 0; j < num_nalus; ++j) { + /* + * nalUnitLength 2 bytes + */ + if (ngx_rtmp_hls_copy(s, &rnal_unit_len, &p, 2, &in) != NGX_OK) { + return NGX_ERROR; + } + nal_unit_len = 0; + ngx_rtmp_rmemcpy(&nal_unit_len, &rnal_unit_len, 2); + if (out->end - out->last < 4) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "hls: too small buffer for hevc header NAL size"); + return NGX_ERROR; + } + + *out->last++ = 0; + *out->last++ = 0; + *out->last++ = 0; + *out->last++ = 1; + + if (out->end - out->last < (ngx_int_t)nal_unit_len) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "hls: too small buffer for hevc header NAL"); + return NGX_ERROR; + } + + if (ngx_rtmp_hls_copy(s, out->last, &p, nal_unit_len, &in) + != NGX_OK) + { + return NGX_ERROR; + } + + out->last += nal_unit_len; + } + } + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_hls_append_aud(ngx_rtmp_session_t *s, ngx_buf_t *out) +{ + static u_char aud_nal[] = { 0x00, 0x00, 0x00, 0x01, 0x09, 0xf0 }; + + if (out->last + sizeof(aud_nal) > out->end) { + return NGX_ERROR; + } + + out->last = ngx_cpymem(out->last, aud_nal, sizeof(aud_nal)); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_hls_append_sps_pps(ngx_rtmp_session_t *s, ngx_buf_t *out) +{ + ngx_rtmp_codec_ctx_t *codec_ctx; + u_char *p; + ngx_chain_t *in; + ngx_rtmp_hls_ctx_t *ctx; + int8_t nnals; + uint16_t len, rlen; + ngx_int_t n; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); + + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + if (ctx == NULL || codec_ctx == NULL) { + return NGX_ERROR; + } + + in = codec_ctx->avc_header->chain; + if (in == NULL) { + return NGX_ERROR; + } + + p = in->buf->pos; + + /* + * Skip bytes: + * - flv fmt + * - H264 CONF/PICT (0x00) + * - 0 + * - 0 + * - 0 + * - version + * - profile + * - compatibility + * - level + * - nal bytes + */ + + if (ngx_rtmp_hls_copy(s, NULL, &p, 10, &in) != NGX_OK) { + return NGX_ERROR; + } + + /* number of SPS NALs */ + if (ngx_rtmp_hls_copy(s, &nnals, &p, 1, &in) != NGX_OK) { + return NGX_ERROR; + } + + nnals &= 0x1f; /* 5lsb */ + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "hls: SPS number: %uz", nnals); + + /* SPS */ + for (n = 0; ; ++n) { + for (; nnals; --nnals) { + + /* NAL length */ + if (ngx_rtmp_hls_copy(s, &rlen, &p, 2, &in) != NGX_OK) { + return NGX_ERROR; + } + + ngx_rtmp_rmemcpy(&len, &rlen, 2); + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "hls: header NAL length: %uz", (size_t) len); + + /* AnnexB prefix */ + if (out->end - out->last < 4) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "hls: too small buffer for header NAL size"); + return NGX_ERROR; + } + + *out->last++ = 0; + *out->last++ = 0; + *out->last++ = 0; + *out->last++ = 1; + + /* NAL body */ + if (out->end - out->last < len) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "hls: too small buffer for header NAL"); + return NGX_ERROR; + } + + if (ngx_rtmp_hls_copy(s, out->last, &p, len, &in) != NGX_OK) { + return NGX_ERROR; + } + + out->last += len; + } + + if (n == 1) { + break; + } + + /* number of PPS NALs */ + if (ngx_rtmp_hls_copy(s, &nnals, &p, 1, &in) != NGX_OK) { + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "hls: PPS number: %uz", nnals); + } + + return NGX_OK; +} + + +static uint64_t +ngx_rtmp_hls_get_fragment_id(ngx_rtmp_session_t *s, uint64_t ts) +{ + ngx_rtmp_hls_ctx_t *ctx; + ngx_rtmp_hls_app_conf_t *hacf; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); + + hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); + + switch (hacf->naming) { + + case NGX_RTMP_HLS_NAMING_TIMESTAMP: + return ts; + + case NGX_RTMP_HLS_NAMING_SYSTEM: + return (uint64_t) ngx_cached_time->sec * 1000 + ngx_cached_time->msec; + + default: /* NGX_RTMP_HLS_NAMING_SEQUENTIAL */ + return ctx->frag + ctx->nfrags; + } +} + + +static ngx_int_t +ngx_rtmp_hls_close_fragment(ngx_rtmp_session_t *s) +{ + ngx_rtmp_hls_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); + if (ctx == NULL || !ctx->opened) { + return NGX_OK; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "hls: close fragment n=%uL", ctx->frag); + + ngx_rtmp_mpegts_close_file(&ctx->file); + + ctx->opened = 0; + + ngx_rtmp_hls_next_frag(s); + + ngx_rtmp_hls_write_playlist(s); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_hls_open_fragment(ngx_rtmp_session_t *s, uint64_t ts, + ngx_int_t discont) +{ + uint64_t id; + ngx_fd_t fd; + ngx_uint_t g; + ngx_rtmp_hls_ctx_t *ctx; + ngx_rtmp_hls_frag_t *f; + ngx_rtmp_hls_app_conf_t *hacf; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); + + if (ctx->opened) { + return NGX_OK; + } + + hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); + + if (ngx_rtmp_hls_ensure_directory(s, &hacf->path) != NGX_OK) { + return NGX_ERROR; + } + + if (hacf->keys && + ngx_rtmp_hls_ensure_directory(s, &hacf->key_path) != NGX_OK) + { + return NGX_ERROR; + } + + id = ngx_rtmp_hls_get_fragment_id(s, ts); + + if (hacf->granularity) { + g = (ngx_uint_t) hacf->granularity; + id = (uint64_t) (id / g) * g; + } + + ngx_sprintf(ctx->stream.data + ctx->stream.len, "%uL.ts%Z", id); + + if (hacf->keys) { + if (ctx->key_frags == 0) { + + ctx->key_frags = hacf->frags_per_key - 1; + ctx->key_id = id; + + if (RAND_bytes(ctx->key, 16) < 0) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "hls: failed to create key"); + return NGX_ERROR; + } + + ngx_sprintf(ctx->keyfile.data + ctx->keyfile.len, "%uL.key%Z", id); + + fd = ngx_open_file(ctx->keyfile.data, NGX_FILE_WRONLY, + NGX_FILE_TRUNCATE, NGX_FILE_DEFAULT_ACCESS); + + if (fd == NGX_INVALID_FILE) { + ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno, + "hls: failed to open key file '%s'", + ctx->keyfile.data); + return NGX_ERROR; + } + + if (ngx_write_fd(fd, ctx->key, 16) != 16) { + ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno, + "hls: failed to write key file '%s'", + ctx->keyfile.data); + ngx_close_file(fd); + return NGX_ERROR; + } + + ngx_close_file(fd); + + } else { + if (hacf->frags_per_key) { + ctx->key_frags--; + } + + if (ngx_set_file_time(ctx->keyfile.data, 0, ngx_cached_time->sec) + != NGX_OK) + { + ngx_log_error(NGX_LOG_ALERT, s->log, ngx_errno, + ngx_set_file_time_n " '%s' failed", + ctx->keyfile.data); + } + } + } + + ngx_log_debug6(NGX_LOG_DEBUG_RTMP, s->log, 0, + "hls: open fragment file='%s', keyfile='%s', " + "frag=%uL, n=%ui, time=%uL, discont=%i", + ctx->stream.data, + ctx->keyfile.data ? ctx->keyfile.data : (u_char *) "", + ctx->frag, ctx->nfrags, ts, discont); + + if (hacf->keys && + ngx_rtmp_mpegts_init_encryption(&ctx->file, ctx->key, 16, ctx->key_id) + != NGX_OK) + { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "hls: failed to initialize hls encryption"); + return NGX_ERROR; + } + + ctx->file.acodec = s->acodec; + ctx->file.vcodec = s->vcodec; + if (ngx_rtmp_mpegts_open_file(&ctx->file, ctx->stream.data, + s->log) + != NGX_OK) + { + return NGX_ERROR; + } + + ctx->opened = 1; + + f = ngx_rtmp_hls_get_frag(s, ctx->nfrags); + + ngx_memzero(f, sizeof(*f)); + + f->active = 1; + f->discont = discont; + f->id = id; + f->key_id = ctx->key_id; + + ctx->frag_ts = ts; + + /* start fragment with audio to make iPhone happy */ + + ngx_rtmp_hls_flush_audio(s); + + return NGX_OK; +} + + +static void +ngx_rtmp_hls_restore_stream(ngx_rtmp_session_t *s) +{ + ngx_rtmp_hls_ctx_t *ctx; + ngx_file_t file; + ssize_t ret; + off_t offset; + u_char *p, *last, *end, *next, *pa, *pp, c; + ngx_rtmp_hls_frag_t *f; + double duration; + ngx_int_t discont; + uint64_t mag, key_id, base; + static u_char buffer[4096]; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); + + ngx_memzero(&file, sizeof(file)); + + file.log = s->log; + + ngx_str_set(&file.name, "m3u8"); + + file.fd = ngx_open_file(ctx->playlist.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, + 0); + if (file.fd == NGX_INVALID_FILE) { + return; + } + + offset = 0; + ctx->nfrags = 0; + f = NULL; + duration = 0; + discont = 0; + key_id = 0; + + for ( ;; ) { + + ret = ngx_read_file(&file, buffer, sizeof(buffer), offset); + if (ret <= 0) { + goto done; + } + + p = buffer; + end = buffer + ret; + + for ( ;; ) { + last = ngx_strlchr(p, end, '\n'); + + if (last == NULL) { + if (p == buffer) { + goto done; + } + break; + } + + next = last + 1; + offset += (next - p); + + if (p != last && last[-1] == '\r') { + last--; + } + + +#define NGX_RTMP_MSEQ "#EXT-X-MEDIA-SEQUENCE:" +#define NGX_RTMP_MSEQ_LEN (sizeof(NGX_RTMP_MSEQ) - 1) + + + if (ngx_memcmp(p, NGX_RTMP_MSEQ, NGX_RTMP_MSEQ_LEN) == 0) { + + ctx->frag = (uint64_t) strtod((const char *) + &p[NGX_RTMP_MSEQ_LEN], NULL); + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "hls: restore sequence frag=%uL", ctx->frag); + } + + +#define NGX_RTMP_XKEY "#EXT-X-KEY:" +#define NGX_RTMP_XKEY_LEN (sizeof(NGX_RTMP_XKEY) - 1) + + if (ngx_memcmp(p, NGX_RTMP_XKEY, NGX_RTMP_XKEY_LEN) == 0) { + + /* recover key id from initialization vector */ + + key_id = 0; + base = 1; + pp = last - 1; + + for ( ;; ) { + if (pp < p) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "hls: failed to read key id"); + break; + } + + c = *pp; + if (c == 'x') { + break; + } + + if (c >= '0' && c <= '9') { + c -= '0'; + goto next; + } + + c |= 0x20; + + if (c >= 'a' && c <= 'f') { + c -= 'a' - 10; + goto next; + } + + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "hls: bad character in key id"); + break; + + next: + + key_id += base * c; + base *= 0x10; + pp--; + } + } + + +#define NGX_RTMP_EXTINF "#EXTINF:" +#define NGX_RTMP_EXTINF_LEN (sizeof(NGX_RTMP_EXTINF) - 1) + + + if (ngx_memcmp(p, NGX_RTMP_EXTINF, NGX_RTMP_EXTINF_LEN) == 0) { + + duration = strtod((const char *) &p[NGX_RTMP_EXTINF_LEN], NULL); + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "hls: restore durarion=%.3f", duration); + } + + +#define NGX_RTMP_DISCONT "#EXT-X-DISCONTINUITY" +#define NGX_RTMP_DISCONT_LEN (sizeof(NGX_RTMP_DISCONT) - 1) + + + if (ngx_memcmp(p, NGX_RTMP_DISCONT, NGX_RTMP_DISCONT_LEN) == 0) { + + discont = 1; + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0, + "hls: discontinuity"); + } + + /* find '.ts\r' */ + + if (p + 4 <= last && + last[-3] == '.' && last[-2] == 't' && last[-1] == 's') + { + f = ngx_rtmp_hls_get_frag(s, ctx->nfrags); + + ngx_memzero(f, sizeof(*f)); + + f->duration = duration; + f->discont = discont; + f->active = 1; + f->id = 0; + + discont = 0; + + mag = 1; + for (pa = last - 4; pa >= p; pa--) { + if (*pa < '0' || *pa > '9') { + break; + } + f->id += (*pa - '0') * mag; + mag *= 10; + } + + f->key_id = key_id; + + ngx_rtmp_hls_next_frag(s); + + ngx_log_debug6(NGX_LOG_DEBUG_RTMP, s->log, 0, + "hls: restore fragment '%*s' id=%uL, " + "duration=%.3f, frag=%uL, nfrags=%ui", + (size_t) (last - p), p, f->id, f->duration, + ctx->frag, ctx->nfrags); + } + + p = next; + } + } + +done: + ngx_close_file(file.fd); +} + + +static ngx_int_t +ngx_rtmp_hls_ensure_directory(ngx_rtmp_session_t *s, ngx_str_t *path) +{ + size_t len; + ngx_file_info_t fi; + ngx_rtmp_hls_ctx_t *ctx; + ngx_rtmp_hls_app_conf_t *hacf; + + static u_char zpath[NGX_MAX_PATH + 1]; + + hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); + + if (path->len + 1 > sizeof(zpath)) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, "hls: too long path"); + return NGX_ERROR; + } + + ngx_snprintf(zpath, sizeof(zpath), "%V%Z", path); + + if (ngx_file_info(zpath, &fi) == NGX_FILE_ERROR) { + + if (ngx_errno != NGX_ENOENT) { + ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno, + "hls: " ngx_file_info_n " failed on '%V'", path); + return NGX_ERROR; + } + + /* ENOENT */ + + if (ngx_create_dir(zpath, NGX_RTMP_HLS_DIR_ACCESS) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno, + "hls: " ngx_create_dir_n " failed on '%V'", path); + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "hls: directory '%V' created", path); + + } else { + + if (!ngx_is_dir(&fi)) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "hls: '%V' exists and is not a directory", path); + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "hls: directory '%V' exists", path); + } + + if (!hacf->nested) { + return NGX_OK; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); + + len = path->len; + if (path->data[len - 1] == '/') { + len--; + } + + if (len + 1 + ctx->name.len + 1 > sizeof(zpath)) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, "hls: too long path"); + return NGX_ERROR; + } + + ngx_snprintf(zpath, sizeof(zpath) - 1, "%*s/%V%Z", len, path->data, + &ctx->name); + + if (ngx_file_info(zpath, &fi) != NGX_FILE_ERROR) { + + if (ngx_is_dir(&fi)) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "hls: directory '%s' exists", zpath); + return NGX_OK; + } + + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "hls: '%s' exists and is not a directory", zpath); + + return NGX_ERROR; + } + + if (ngx_errno != NGX_ENOENT) { + ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno, + "hls: " ngx_file_info_n " failed on '%s'", zpath); + return NGX_ERROR; + } + + /* NGX_ENOENT */ + + if (ngx_create_dir(zpath, NGX_RTMP_HLS_DIR_ACCESS) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno, + "hls: " ngx_create_dir_n " failed on '%s'", zpath); + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "hls: directory '%s' created", zpath); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_hls_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) +{ + ngx_rtmp_hls_app_conf_t *hacf; + ngx_rtmp_hls_ctx_t *ctx; + u_char *p, *pp; + ngx_rtmp_hls_frag_t *f; + ngx_buf_t *b; + size_t len; + ngx_rtmp_hls_variant_t *var; + ngx_uint_t n; + + hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); + if (hacf == NULL || !hacf->hls || hacf->path.len == 0) { + goto next; + } + + if (s->interprocess) { + goto next; + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0, + "hls: publish: name='%s' type='%s'", + v->name, v->type); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); + + if (ctx == NULL) { + + ctx = ngx_pcalloc(s->pool, sizeof(ngx_rtmp_hls_ctx_t)); + ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_hls_module); + + } else { + + f = ctx->frags; + b = ctx->aframe; + + ngx_memzero(ctx, sizeof(ngx_rtmp_hls_ctx_t)); + + ctx->frags = f; + ctx->aframe = b; + + if (b) { + b->pos = b->last = b->start; + } + } + + if (ctx->frags == NULL) { + ctx->frags = ngx_pcalloc(s->pool, + sizeof(ngx_rtmp_hls_frag_t) * + (hacf->winfrags * 2 + 1)); + if (ctx->frags == NULL) { + return NGX_ERROR; + } + } + + if (ngx_strstr(v->name, "..")) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "hls: bad stream name: '%s'", v->name); + return NGX_ERROR; + } + + ctx->name.len = ngx_strlen(v->name); + ctx->name.data = ngx_palloc(s->pool, ctx->name.len + 1); + + if (ctx->name.data == NULL) { + return NGX_ERROR; + } + + *ngx_cpymem(ctx->name.data, v->name, ctx->name.len) = 0; + + len = hacf->path.len + 1 + ctx->name.len + sizeof(".m3u8"); + if (hacf->nested) { + len += sizeof("/index") - 1; + } + + ctx->playlist.data = ngx_palloc(s->pool, len); + p = ngx_cpymem(ctx->playlist.data, hacf->path.data, hacf->path.len); + + if (p[-1] != '/') { + *p++ = '/'; + } + + p = ngx_cpymem(p, ctx->name.data, ctx->name.len); + + /* + * ctx->stream holds initial part of stream file path + * however the space for the whole stream path + * is allocated + */ + + ctx->stream.len = p - ctx->playlist.data + 1; + ctx->stream.data = ngx_palloc(s->pool, + ctx->stream.len + NGX_INT64_LEN + + sizeof(".ts")); + + ngx_memcpy(ctx->stream.data, ctx->playlist.data, ctx->stream.len - 1); + ctx->stream.data[ctx->stream.len - 1] = (hacf->nested ? '/' : '-'); + + /* varint playlist path */ + + if (hacf->variant) { + var = hacf->variant->elts; + for (n = 0; n < hacf->variant->nelts; n++, var++) { + if (ctx->name.len > var->suffix.len && + ngx_memcmp(var->suffix.data, + ctx->name.data + ctx->name.len - var->suffix.len, + var->suffix.len) + == 0) + { + ctx->var = var; + + len = (size_t) (p - ctx->playlist.data); + + ctx->var_playlist.len = len - var->suffix.len + sizeof(".m3u8") + - 1; + ctx->var_playlist.data = ngx_palloc(s->pool, + ctx->var_playlist.len + 1); + + pp = ngx_cpymem(ctx->var_playlist.data, ctx->playlist.data, + len - var->suffix.len); + pp = ngx_cpymem(pp, ".m3u8", sizeof(".m3u8") - 1); + *pp = 0; + + ctx->var_playlist_bak.len = ctx->var_playlist.len + + sizeof(".bak") - 1; + ctx->var_playlist_bak.data = ngx_palloc(s->pool, + ctx->var_playlist_bak.len + 1); + + pp = ngx_cpymem(ctx->var_playlist_bak.data, + ctx->var_playlist.data, + ctx->var_playlist.len); + pp = ngx_cpymem(pp, ".bak", sizeof(".bak") - 1); + *pp = 0; + + break; + } + } + } + + + /* playlist path */ + + if (hacf->nested) { + p = ngx_cpymem(p, "/index.m3u8", sizeof("/index.m3u8") - 1); + } else { + p = ngx_cpymem(p, ".m3u8", sizeof(".m3u8") - 1); + } + + ctx->playlist.len = p - ctx->playlist.data; + + *p = 0; + + /* playlist bak (new playlist) path */ + + ctx->playlist_bak.data = ngx_palloc(s->pool, + ctx->playlist.len + sizeof(".bak")); + p = ngx_cpymem(ctx->playlist_bak.data, ctx->playlist.data, + ctx->playlist.len); + p = ngx_cpymem(p, ".bak", sizeof(".bak") - 1); + + ctx->playlist_bak.len = p - ctx->playlist_bak.data; + + *p = 0; + + /* key path */ + + if (hacf->keys) { + len = hacf->key_path.len + 1 + ctx->name.len + 1 + NGX_INT64_LEN + + sizeof(".key"); + + ctx->keyfile.data = ngx_palloc(s->pool, len); + if (ctx->keyfile.data == NULL) { + return NGX_ERROR; + } + + p = ngx_cpymem(ctx->keyfile.data, hacf->key_path.data, + hacf->key_path.len); + + if (p[-1] != '/') { + *p++ = '/'; + } + + p = ngx_cpymem(p, ctx->name.data, ctx->name.len); + *p++ = (hacf->nested ? '/' : '-'); + + ctx->keyfile.len = p - ctx->keyfile.data; + } + + ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->log, 0, + "hls: playlist='%V' playlist_bak='%V' " + "stream_pattern='%V' keyfile_pattern='%V'", + &ctx->playlist, &ctx->playlist_bak, + &ctx->stream, &ctx->keyfile); + + if (hacf->continuous) { + ngx_rtmp_hls_restore_stream(s); + } + +next: + return next_publish(s, v); +} + + +static ngx_int_t +ngx_rtmp_hls_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v) +{ + ngx_rtmp_hls_app_conf_t *hacf; + ngx_rtmp_hls_ctx_t *ctx; + + hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); + + if (hacf == NULL || !hacf->hls || ctx == NULL) { + goto next; + } + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0, + "hls: close stream"); + + ngx_rtmp_hls_close_fragment(s); + +next: + return next_close_stream(s, v); +} + + +static ngx_int_t +ngx_rtmp_hls_parse_aac_header(ngx_rtmp_session_t *s, ngx_uint_t *objtype, + ngx_uint_t *srindex, ngx_uint_t *chconf) +{ + ngx_rtmp_codec_ctx_t *codec_ctx; + ngx_chain_t *cl; + u_char *p, b0, b1; + + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + cl = codec_ctx->aac_header->chain; + + p = cl->buf->pos; + + if (ngx_rtmp_hls_copy(s, NULL, &p, 2, &cl) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_rtmp_hls_copy(s, &b0, &p, 1, &cl) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_rtmp_hls_copy(s, &b1, &p, 1, &cl) != NGX_OK) { + return NGX_ERROR; + } + + *objtype = b0 >> 3; + if (*objtype == 0 || *objtype == 0x1f) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "hls: unsupported adts object type:%ui", *objtype); + return NGX_ERROR; + } + + if (*objtype > 4) { + + /* + * Mark all extended profiles as LC + * to make Android as happy as possible. + */ + + *objtype = 2; + } + + *srindex = ((b0 << 1) & 0x0f) | ((b1 & 0x80) >> 7); + if (*srindex == 0x0f) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "hls: unsupported adts sample rate:%ui", *srindex); + return NGX_ERROR; + } + + *chconf = (b1 >> 3) & 0x0f; + + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->log, 0, + "hls: aac object_type:%ui, sample_rate_index:%ui, " + "channel_config:%ui", *objtype, *srindex, *chconf); + + return NGX_OK; +} + + +static void +ngx_rtmp_hls_update_fragment(ngx_rtmp_session_t *s, uint64_t ts, + ngx_int_t boundary, ngx_uint_t flush_rate) +{ + ngx_rtmp_hls_ctx_t *ctx; + ngx_rtmp_hls_app_conf_t *hacf; + ngx_rtmp_hls_frag_t *f; + ngx_msec_t ts_frag_len; + ngx_int_t same_frag, force,discont; + ngx_buf_t *b; + int64_t d; + + hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); + f = NULL; + force = 0; + discont = 1; + + if (ctx->opened) { + f = ngx_rtmp_hls_get_frag(s, ctx->nfrags); + d = (int64_t) (ts - ctx->frag_ts); + + if (d > (int64_t) hacf->max_fraglen * 90 || d < -90000) { + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "hls: force fragment split: %.3f sec, ", d / 90000.); + force = 1; + + } else { + f->duration = (ts - ctx->frag_ts) / 90000.; + discont = 0; + } + } + + switch (hacf->slicing) { + case NGX_RTMP_HLS_SLICING_PLAIN: + if (f && f->duration < hacf->fraglen / 1000.) { + boundary = 0; + } + break; + + case NGX_RTMP_HLS_SLICING_ALIGNED: + + ts_frag_len = hacf->fraglen * 90; + same_frag = ctx->frag_ts / ts_frag_len == ts / ts_frag_len; + + if (f && same_frag) { + boundary = 0; + } + + if (f == NULL && (ctx->frag_ts == 0 || same_frag)) { + ctx->frag_ts = ts; + boundary = 0; + } + + break; + } + + if (boundary || force) { + ngx_rtmp_hls_close_fragment(s); + ngx_rtmp_hls_open_fragment(s, ts, discont); + } + + b = ctx->aframe; + if (ctx->opened && b && b->last > b->pos && + ctx->aframe_pts + (uint64_t) hacf->max_audio_delay * 90 / flush_rate + < ts) + { + ngx_rtmp_hls_flush_audio(s); + } +} + + +static ngx_int_t +ngx_rtmp_hls_flush_audio(ngx_rtmp_session_t *s) +{ + ngx_rtmp_hls_ctx_t *ctx; + ngx_rtmp_mpegts_frame_t frame; + ngx_int_t rc; + ngx_buf_t *b; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); + + if (ctx == NULL || !ctx->opened) { + return NGX_OK; + } + + b = ctx->aframe; + + if (b == NULL || b->pos == b->last) { + return NGX_OK; + } + + ngx_memzero(&frame, sizeof(frame)); + + frame.dts = ctx->aframe_pts; + frame.pts = frame.dts; + frame.cc = ctx->audio_cc; + frame.pid = 0x101; + frame.sid = 0xc0; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "hls: flush audio pts=%uL", frame.pts); + + rc = ngx_rtmp_mpegts_write_frame(&ctx->file, &frame, b); + + if (rc != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "hls: audio flush failed"); + } + + ctx->audio_cc = frame.cc; + b->pos = b->last = b->start; + + return rc; +} + + +static ngx_int_t +ngx_rtmp_hls_audio(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_rtmp_hls_app_conf_t *hacf; + ngx_rtmp_hls_ctx_t *ctx; + ngx_rtmp_codec_ctx_t *codec_ctx; + uint64_t pts, est_pts; + int64_t dpts; + size_t bsize; + ngx_buf_t *b; + u_char *p = NULL; + ngx_uint_t objtype, srindex, chconf, size = 0; + ngx_flag_t mp3_tag = 0; + + hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); + + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + if (hacf == NULL || !hacf->hls || ctx == NULL || + codec_ctx == NULL || h->mlen < 2) + { + return NGX_OK; + } + + if ((codec_ctx->audio_codec_id != NGX_RTMP_AUDIO_AAC || + codec_ctx->aac_header == NULL || ngx_rtmp_is_codec_header(in)) && + codec_ctx->audio_codec_id != NGX_RTMP_AUDIO_MP3) + { + return NGX_OK; + } + + if (s->pause) { + return NGX_OK; + } + + b = ctx->aframe; + + if (b == NULL) { + + b = ngx_pcalloc(s->pool, sizeof(ngx_buf_t)); + if (b == NULL) { + return NGX_ERROR; + } + + ctx->aframe = b; + + b->start = ngx_palloc(s->pool, hacf->audio_buffer_size); + if (b->start == NULL) { + return NGX_ERROR; + } + + b->end = b->start + hacf->audio_buffer_size; + b->pos = b->last = b->start; + } + + if (codec_ctx->audio_codec_id == NGX_RTMP_AUDIO_AAC) { + size = h->mlen - 2 + 7; + } else if (codec_ctx->audio_codec_id == NGX_RTMP_AUDIO_MP3) { + size = h->mlen - 1; + } + + pts = (uint64_t) h->timestamp * 90; + + if (b->start + size > b->end) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "hls: too big audio frame"); + return NGX_OK; + } + + /* + * start new fragment here if + * there's no video at all, otherwise + * do it in video handler + */ + + ngx_rtmp_hls_update_fragment(s, pts, codec_ctx->avc_header == NULL, 2); + + if (b->last + size > b->end) { + ngx_rtmp_hls_flush_audio(s); + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "hls: audio pts=%uL", pts); + + if (codec_ctx->audio_codec_id == NGX_RTMP_AUDIO_AAC) { + if (b->last + 7 > b->end) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0, + "hls: not enough buffer for audio header"); + return NGX_OK; + } + + p = b->last; + b->last += 5; + + /* copy payload */ + + for (; in && b->last < b->end; in = in->next) { + + bsize = in->buf->last - in->buf->pos; + if (b->last + bsize > b->end) { + bsize = b->end - b->last; + } + + b->last = ngx_cpymem(b->last, in->buf->pos, bsize); + } + + /* make up ADTS header */ + + if (ngx_rtmp_hls_parse_aac_header(s, &objtype, &srindex, &chconf) + != NGX_OK) + { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "hls: aac header error"); + return NGX_OK; + } + + /* we have 5 free bytes + 2 bytes of RTMP frame header */ + + p[0] = 0xff; + p[1] = 0xf1; + p[2] = (u_char) (((objtype - 1) << 6) | (srindex << 2) | + ((chconf & 0x04) >> 2)); + p[3] = (u_char) (((chconf & 0x03) << 6) | ((size >> 11) & 0x03)); + p[4] = (u_char) (size >> 3); + p[5] = (u_char) ((size << 5) | 0x1f); + p[6] = 0xfc; + } else if (codec_ctx->audio_codec_id == NGX_RTMP_AUDIO_MP3) { + if (b->last > b->end) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0, + "hls: not enough buffer for audio header"); + return NGX_OK; + } + + p = b->last; + + /* copy payload */ + + for (; in && b->last < b->end; in = in->next) { + + bsize = in->buf->last - in->buf->pos; + if (b->last + bsize > b->end) { + bsize = b->end - b->last; + } + + if (0 == mp3_tag) { + b->last = ngx_cpymem(b->last, in->buf->pos+1, bsize-1); + mp3_tag = 1; + } else { + b->last = ngx_cpymem(b->last, in->buf->pos, bsize); + } + } + } + + if (p != b->start) { + ctx->aframe_num++; + return NGX_OK; + } + + ctx->aframe_pts = pts; + + if (!hacf->sync || codec_ctx->sample_rate == 0) { + return NGX_OK; + } + + /* align audio frames */ + + /* TODO: We assume here AAC frame size is 1024 + * Need to handle AAC frames with frame size of 960 */ + + est_pts = ctx->aframe_base + ctx->aframe_num * 90000 * 1024 / + codec_ctx->sample_rate; + dpts = (int64_t) (est_pts - pts); + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0, + "hls: audio sync dpts=%L (%.5fs)", + dpts, dpts / 90000.); + + if (dpts <= (int64_t) hacf->sync * 90 && + dpts >= (int64_t) hacf->sync * -90) + { + ctx->aframe_num++; + ctx->aframe_pts = est_pts; + return NGX_OK; + } + + ctx->aframe_base = pts; + ctx->aframe_num = 1; + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0, + "hls: audio sync gap dpts=%L (%.5fs)", + dpts, dpts / 90000.); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_hls_video(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_rtmp_hls_app_conf_t *hacf; + ngx_rtmp_hls_ctx_t *ctx; + ngx_rtmp_codec_ctx_t *codec_ctx; + u_char *p; + uint8_t fmt, ftype, htype, nal_type, src_nal_type; + uint32_t len, rlen; + ngx_buf_t out, *b; + uint32_t cts; + ngx_rtmp_mpegts_frame_t frame; + ngx_uint_t nal_bytes; + ngx_int_t aud_sent, sps_pps_sent, boundary; + static u_char buffer[NGX_RTMP_HLS_BUFSIZE]; + ngx_rtmp_core_app_conf_t *cacf; + + cacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_core_module); + + hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); + + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + if (hacf == NULL || !hacf->hls || ctx == NULL || codec_ctx == NULL || + codec_ctx->avc_header == NULL || h->mlen < 1) + { + return NGX_OK; + } + + /* Only H264 is supported */ + if (codec_ctx->video_codec_id != NGX_RTMP_VIDEO_H264 && + codec_ctx->video_codec_id != cacf->hevc_codec) + { + return NGX_OK; + } + + if (s->pause) { + return NGX_OK; + } + + p = in->buf->pos; + if (ngx_rtmp_hls_copy(s, &fmt, &p, 1, &in) != NGX_OK) { + return NGX_ERROR; + } + + /* 1: keyframe (IDR) + * 2: inter frame + * 3: disposable inter frame */ + + ftype = (fmt & 0xf0) >> 4; + + /* H264 HDR/PICT */ + + if (ngx_rtmp_hls_copy(s, &htype, &p, 1, &in) != NGX_OK) { + return NGX_ERROR; + } + + /* proceed only with PICT */ + + if (htype != 1) { + return NGX_OK; + } + + /* 3 bytes: decoder delay */ + + if (ngx_rtmp_hls_copy(s, &cts, &p, 3, &in) != NGX_OK) { + return NGX_ERROR; + } + + cts = ((cts & 0x00FF0000) >> 16) | ((cts & 0x000000FF) << 16) | + (cts & 0x0000FF00); + + ngx_memzero(&out, sizeof(out)); + + out.start = buffer; + out.end = buffer + sizeof(buffer); + out.pos = out.start; + out.last = out.pos; + + nal_bytes = codec_ctx->avc_nal_bytes; + aud_sent = 0; + sps_pps_sent = 0; + + while (in) { + if (ngx_rtmp_hls_copy(s, &rlen, &p, nal_bytes, &in) != NGX_OK) { + return NGX_OK; + } + + len = 0; + ngx_rtmp_rmemcpy(&len, &rlen, nal_bytes); + + if (len == 0) { + continue; + } + + if (ngx_rtmp_hls_copy(s, &src_nal_type, &p, 1, &in) != NGX_OK) { + return NGX_OK; + } + + if (codec_ctx->video_codec_id == NGX_RTMP_VIDEO_H264) { + nal_type = src_nal_type & 0x1f; + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0, + "hls: h264 NAL type=%ui, len=%uD", + (ngx_uint_t) nal_type, len); + + if (nal_type >= 7 && nal_type <= 9) { + if (ngx_rtmp_hls_copy(s, NULL, &p, len - 1, &in) != NGX_OK) { + return NGX_ERROR; + } + continue; + } + + if (!aud_sent) { + switch (nal_type) { + case 1: + case 5: + case 6: + if (ngx_rtmp_hls_append_aud(s, &out) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "hls: error appending AUD NAL"); + } + aud_sent = 1; + break; + + case 9: + aud_sent = 1; + break; + } + } + + switch (nal_type) { + case 1: + sps_pps_sent = 0; + break; + case 5: + if (sps_pps_sent) { + break; + } + if (ngx_rtmp_hls_append_sps_pps(s, &out) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "hls: error appenging SPS/PPS NALs"); + } + sps_pps_sent = 1; + break; + } + } else { + if (src_nal_type == 0) { + if (ngx_rtmp_hls_copy(s, out.last, &p, 3, &in) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "rtmp-hls: video| buffer error"); + return NGX_ERROR; + } + if (out.last[0] == 0x00 && out.last[1] == 0x00 && + out.last[2] == 0x01) + { + ngx_rtmp_hls_copy(s, &src_nal_type, &p, 1, &in); + len -= 4; + } else { + p -= 3; + } + } + + nal_type = (src_nal_type >> 1) & 0x3f; + + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "hls: h265 NAL type=%ui, len=%uD", + (ngx_uint_t) nal_type, len); + + if (nal_type == 32 // VPS + || nal_type == 33 // SPS + || nal_type == 34 // PPS + || nal_type == 35 // AUD + ) + { + if (ngx_rtmp_hls_copy(s, NULL, &p, len - 1, &in) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "rtmp-hls: video| jump nal, failed copy "); + return NGX_ERROR; + } + continue; + } + + if (!aud_sent) { + if (ngx_rtmp_hls_append_hevc_aud(s, &out) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "hls: error appending AUD NAL"); + } + aud_sent = 1; + } + + if (!IS_IRAP(nal_type)) { + sps_pps_sent = 0; + } else if (!sps_pps_sent) { + if (ngx_rtmp_hls_append_hevc_vps_sps_pps(s, &out) != NGX_OK) + { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "hls: error appenging VPS/SPS/PPS NALs"); + } + sps_pps_sent = 1; + } + } + + /* AnnexB prefix */ + + if (out.end - out.last < 5) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "hls: not enough buffer for AnnexB prefix"); + return NGX_OK; + } + + /* first AnnexB prefix is long (4 bytes) */ + + if (out.last == out.pos) { + *out.last++ = 0; + } + + *out.last++ = 0; + *out.last++ = 0; + *out.last++ = 1; + *out.last++ = src_nal_type; + + /* NAL body */ + + if (out.end - out.last < (ngx_int_t) len) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "hls: not enough buffer for NAL"); + return NGX_OK; + } + + if (ngx_rtmp_hls_copy(s, out.last, &p, len - 1, &in) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "rtmp-hls: video| copy nal, failed copy, len %d, in %d", + len - 1, in->buf->last - in->buf->pos); + return NGX_ERROR; + } + + out.last += (len - 1); + } + + ngx_memzero(&frame, sizeof(frame)); + + frame.cc = ctx->video_cc; + frame.dts = (uint64_t) h->timestamp * 90; + frame.pts = frame.dts + cts * 90; + frame.pid = 0x100; + frame.sid = 0xe0; + frame.key = (ftype == 1); + + /* + * start new fragment if + * - we have video key frame AND + * - we have audio buffered or have no audio at all or stream is closed + */ + + b = ctx->aframe; + boundary = frame.key && (codec_ctx->aac_header == NULL || !ctx->opened || + (b && b->last > b->pos)); + + ngx_rtmp_hls_update_fragment(s, frame.dts, boundary, 1); + + if (!ctx->opened) { + return NGX_OK; + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0, + "hls: video pts=%uL, dts=%uL", frame.pts, frame.dts); + + if (ngx_rtmp_mpegts_write_frame(&ctx->file, &frame, &out) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "hls: video frame failed"); + } + + ctx->video_cc = frame.cc; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_hls_stream_begin(ngx_rtmp_session_t *s, ngx_rtmp_stream_begin_t *v) +{ + return next_stream_begin(s, v); +} + + +static ngx_int_t +ngx_rtmp_hls_stream_eof(ngx_rtmp_session_t *s, ngx_rtmp_stream_eof_t *v) +{ + ngx_rtmp_hls_flush_audio(s); + + ngx_rtmp_hls_close_fragment(s); + + return next_stream_eof(s, v); +} + + +static ngx_int_t +ngx_rtmp_hls_cleanup_dir(ngx_str_t *ppath, ngx_msec_t playlen) +{ + ngx_dir_t dir; + time_t mtime, max_age; + ngx_err_t err; + ngx_str_t name, spath; + u_char *p; + ngx_int_t nentries, nerased; + u_char path[NGX_MAX_PATH + 1]; + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0, + "hls: cleanup path='%V' playlen=%M", + ppath, playlen); + + if (ngx_open_dir(ppath, &dir) != NGX_OK) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, ngx_errno, + "hls: cleanup open dir failed '%V'", ppath); + return NGX_ERROR; + } + + nentries = 0; + nerased = 0; + + for ( ;; ) { + ngx_set_errno(0); + + if (ngx_read_dir(&dir) == NGX_ERROR) { + err = ngx_errno; + + if (ngx_close_dir(&dir) == NGX_ERROR) { + ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, ngx_errno, + "hls: cleanup " ngx_close_dir_n " \"%V\" failed", + ppath); + } + + if (err == NGX_ENOMOREFILES) { + return nentries - nerased; + } + + ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, err, + "hls: cleanup " ngx_read_dir_n + " '%V' failed", ppath); + return NGX_ERROR; + } + + name.data = ngx_de_name(&dir); + if (name.data[0] == '.') { + continue; + } + + name.len = ngx_de_namelen(&dir); + + p = ngx_snprintf(path, sizeof(path) - 1, "%V/%V", ppath, &name); + *p = 0; + + spath.data = path; + spath.len = p - path; + + nentries++; + + if (!dir.valid_info && ngx_de_info(path, &dir) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, ngx_errno, + "hls: cleanup " ngx_de_info_n " \"%V\" failed", + &spath); + + continue; + } + + if (ngx_de_is_dir(&dir)) { + + if (ngx_rtmp_hls_cleanup_dir(&spath, playlen) == 0) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0, + "hls: cleanup dir '%V'", &name); + + /* + * null-termination gets spoiled in win32 + * version of ngx_open_dir + */ + + *p = 0; + + if (ngx_delete_dir(path) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno, + "hls: cleanup " ngx_delete_dir_n + " failed on '%V'", &spath); + } else { + nerased++; + } + } + + continue; + } + + if (!ngx_de_is_file(&dir)) { + continue; + } + + if (name.len >= 3 && name.data[name.len - 3] == '.' && + name.data[name.len - 2] == 't' && + name.data[name.len - 1] == 's') + { + max_age = playlen / 500; + + } else if (name.len >= 5 && name.data[name.len - 5] == '.' && + name.data[name.len - 4] == 'm' && + name.data[name.len - 3] == '3' && + name.data[name.len - 2] == 'u' && + name.data[name.len - 1] == '8') + { + max_age = playlen / 1000; + + } else if (name.len >= 4 && name.data[name.len - 4] == '.' && + name.data[name.len - 3] == 'k' && + name.data[name.len - 2] == 'e' && + name.data[name.len - 1] == 'y') + { + max_age = playlen / 500; + + } else { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0, + "hls: cleanup skip unknown file type '%V'", &name); + continue; + } + + mtime = ngx_de_mtime(&dir); + if (mtime + max_age > ngx_cached_time->sec) { + continue; + } + + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0, + "hls: cleanup '%V' mtime=%T age=%T", + &name, mtime, ngx_cached_time->sec - mtime); + + if (ngx_delete_file(path) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno, + "hls: cleanup " ngx_delete_file_n " failed on '%V'", + &spath); + continue; + } + + nerased++; + } +} + + +#if (nginx_version >= 1011005) +static ngx_msec_t +#else +static time_t +#endif +ngx_rtmp_hls_cleanup(void *data) +{ + ngx_rtmp_hls_cleanup_t *cleanup = data; + + ngx_rtmp_hls_cleanup_dir(&cleanup->path, cleanup->playlen); + +#if (nginx_version >= 1011005) + return cleanup->playlen * 2; +#else + return cleanup->playlen / 500; +#endif +} + + +static char * +ngx_rtmp_hls_variant(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_rtmp_hls_app_conf_t *hacf = conf; + + ngx_str_t *value, *arg; + ngx_uint_t n; + ngx_rtmp_hls_variant_t *var; + + value = cf->args->elts; + + if (hacf->variant == NULL) { + hacf->variant = ngx_array_create(cf->pool, 1, + sizeof(ngx_rtmp_hls_variant_t)); + if (hacf->variant == NULL) { + return NGX_CONF_ERROR; + } + } + + var = ngx_array_push(hacf->variant); + if (var == NULL) { + return NGX_CONF_ERROR; + } + + ngx_memzero(var, sizeof(ngx_rtmp_hls_variant_t)); + + var->suffix = value[1]; + + if (cf->args->nelts == 2) { + return NGX_CONF_OK; + } + + if (ngx_array_init(&var->args, cf->pool, cf->args->nelts - 2, + sizeof(ngx_str_t)) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + arg = ngx_array_push_n(&var->args, cf->args->nelts - 2); + if (arg == NULL) { + return NGX_CONF_ERROR; + } + + for (n = 2; n < cf->args->nelts; n++) { + *arg++ = value[n]; + } + + return NGX_CONF_OK; +} + + +static void * +ngx_rtmp_hls_create_app_conf(ngx_conf_t *cf) +{ + ngx_rtmp_hls_app_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_hls_app_conf_t)); + if (conf == NULL) { + return NULL; + } + + conf->hls = NGX_CONF_UNSET; + conf->fraglen = NGX_CONF_UNSET_MSEC; + conf->max_fraglen = NGX_CONF_UNSET_MSEC; + conf->muxdelay = NGX_CONF_UNSET_MSEC; + conf->sync = NGX_CONF_UNSET_MSEC; + conf->playlen = NGX_CONF_UNSET_MSEC; + conf->continuous = NGX_CONF_UNSET; + conf->nested = NGX_CONF_UNSET; + conf->naming = NGX_CONF_UNSET_UINT; + conf->slicing = NGX_CONF_UNSET_UINT; + conf->type = NGX_CONF_UNSET_UINT; + conf->max_audio_delay = NGX_CONF_UNSET_MSEC; + conf->audio_buffer_size = NGX_CONF_UNSET_SIZE; + conf->cleanup = NGX_CONF_UNSET; + conf->granularity = NGX_CONF_UNSET; + conf->keys = NGX_CONF_UNSET; + conf->frags_per_key = NGX_CONF_UNSET_UINT; + + return conf; +} + + +static char * +ngx_rtmp_hls_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_rtmp_hls_app_conf_t *prev = parent; + ngx_rtmp_hls_app_conf_t *conf = child; + ngx_rtmp_hls_cleanup_t *cleanup; + + ngx_conf_merge_value(conf->hls, prev->hls, 0); + ngx_conf_merge_msec_value(conf->fraglen, prev->fraglen, 5000); + ngx_conf_merge_msec_value(conf->max_fraglen, prev->max_fraglen, + conf->fraglen * 10); + ngx_conf_merge_msec_value(conf->muxdelay, prev->muxdelay, 700); + ngx_conf_merge_msec_value(conf->sync, prev->sync, 2); + ngx_conf_merge_msec_value(conf->playlen, prev->playlen, 30000); + ngx_conf_merge_value(conf->continuous, prev->continuous, 1); + ngx_conf_merge_value(conf->nested, prev->nested, 0); + ngx_conf_merge_uint_value(conf->naming, prev->naming, + NGX_RTMP_HLS_NAMING_SEQUENTIAL); + ngx_conf_merge_uint_value(conf->slicing, prev->slicing, + NGX_RTMP_HLS_SLICING_PLAIN); + ngx_conf_merge_uint_value(conf->type, prev->type, + NGX_RTMP_HLS_TYPE_LIVE); + ngx_conf_merge_msec_value(conf->max_audio_delay, prev->max_audio_delay, + 300); + ngx_conf_merge_size_value(conf->audio_buffer_size, prev->audio_buffer_size, + NGX_RTMP_HLS_BUFSIZE); + ngx_conf_merge_value(conf->cleanup, prev->cleanup, 1); + ngx_conf_merge_str_value(conf->base_url, prev->base_url, ""); + ngx_conf_merge_value(conf->granularity, prev->granularity, 0); + ngx_conf_merge_value(conf->keys, prev->keys, 0); + ngx_conf_merge_str_value(conf->key_path, prev->key_path, ""); + ngx_conf_merge_str_value(conf->key_url, prev->key_url, ""); + ngx_conf_merge_uint_value(conf->frags_per_key, prev->frags_per_key, 0); + + if (conf->fraglen) { + conf->winfrags = conf->playlen / conf->fraglen; + } + + /* schedule cleanup */ + + if (conf->hls && conf->path.len && conf->cleanup && + conf->type != NGX_RTMP_HLS_TYPE_EVENT) + { + if (conf->path.data[conf->path.len - 1] == '/') { + conf->path.len--; + } + + cleanup = ngx_pcalloc(cf->pool, sizeof(*cleanup)); + if (cleanup == NULL) { + return NGX_CONF_ERROR; + } + + cleanup->path = conf->path; + cleanup->playlen = conf->playlen; + + conf->slot = ngx_pcalloc(cf->pool, sizeof(*conf->slot)); + if (conf->slot == NULL) { + return NGX_CONF_ERROR; + } + + conf->slot->manager = ngx_rtmp_hls_cleanup; + conf->slot->name = conf->path; + conf->slot->data = cleanup; + conf->slot->conf_file = cf->conf_file->file.name.data; + conf->slot->line = cf->conf_file->line; + + if (ngx_add_path(cf, &conf->slot) != NGX_OK) { + return NGX_CONF_ERROR; + } + } + + ngx_conf_merge_str_value(conf->path, prev->path, ""); + + if (conf->keys && conf->cleanup && conf->key_path.len && + ngx_strcmp(conf->key_path.data, conf->path.data) != 0 && + conf->type != NGX_RTMP_HLS_TYPE_EVENT) + { + if (conf->key_path.data[conf->key_path.len - 1] == '/') { + conf->key_path.len--; + } + + cleanup = ngx_pcalloc(cf->pool, sizeof(*cleanup)); + if (cleanup == NULL) { + return NGX_CONF_ERROR; + } + + cleanup->path = conf->key_path; + cleanup->playlen = conf->playlen; + + conf->slot = ngx_pcalloc(cf->pool, sizeof(*conf->slot)); + if (conf->slot == NULL) { + return NGX_CONF_ERROR; + } + + conf->slot->manager = ngx_rtmp_hls_cleanup; + conf->slot->name = conf->key_path; + conf->slot->data = cleanup; + conf->slot->conf_file = cf->conf_file->file.name.data; + conf->slot->line = cf->conf_file->line; + + if (ngx_add_path(cf, &conf->slot) != NGX_OK) { + return NGX_CONF_ERROR; + } + } + + ngx_conf_merge_str_value(conf->key_path, prev->key_path, ""); + + if (conf->key_path.len == 0) { + conf->key_path = conf->path; + } + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_rtmp_hls_postconfiguration(ngx_conf_t *cf) +{ + ngx_rtmp_core_main_conf_t *cmcf; + ngx_rtmp_handler_pt *h; + + cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); + + h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_VIDEO]); + *h = ngx_rtmp_hls_video; + + h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AUDIO]); + *h = ngx_rtmp_hls_audio; + + next_publish = ngx_rtmp_publish; + ngx_rtmp_publish = ngx_rtmp_hls_publish; + + next_close_stream = ngx_rtmp_close_stream; + ngx_rtmp_close_stream = ngx_rtmp_hls_close_stream; + + next_stream_begin = ngx_rtmp_stream_begin; + ngx_rtmp_stream_begin = ngx_rtmp_hls_stream_begin; + + next_stream_eof = ngx_rtmp_stream_eof; + ngx_rtmp_stream_eof = ngx_rtmp_hls_stream_eof; + + return NGX_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/hls/ngx_rtmp_mpegts.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/hls/ngx_rtmp_mpegts.c new file mode 100644 index 0000000000..110dfcac86 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/hls/ngx_rtmp_mpegts.c @@ -0,0 +1,558 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp.h" +#include "ngx_rtmp_mpegts.h" + + +u_char ngx_rtmp_mpegts_pat[] = { + + /* TS */ + 0x47, 0x40, 0x00, 0x10, 0x00, + /* PSI */ + 0x00, 0xb0, 0x0d, 0x00, 0x01, 0xc1, 0x00, 0x00, + /* PAT */ + 0x00, 0x01, 0xf0, 0x01, + /* CRC */ + 0x2e, 0x70, 0x19, 0x05, + /* stuffing 167 bytes */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + + +static u_char ngx_mpegts_ts_header[] = { + /* TS */ + 0x47, 0x50, 0x01, 0x10, 0x00 +}; + +static u_char ngx_mpegts_pmt_header[] = { + /* PSI */ + 0x02, 0xb0, 0x17, 0x00, 0x01, 0xc1, 0x00, 0x00, + /* PMT */ + 0xe1, 0x00, + 0xf0, 0x00 +}; + + +enum { + NGX_RTMP_MPEGTS_PID_H264 = 0, + NGX_RTMP_MPEGTS_PID_H265, + NGX_RTMP_MPEGTS_PID_AAC, + NGX_RTMP_MPEGTS_PID_MP3 +}; + +static u_char ngx_mpegts_pid[4][5] = { + {0x1b, 0xe1, 0x00, 0xf0, 0x00}, /* h264 */ + {0x24, 0xe1, 0x00, 0xf0, 0x00}, /* h265 */ + {0x0f, 0xe1, 0x01, 0xf0, 0x00}, /* aac */ + {0x03, 0xe1, 0x01, 0xf0, 0x00} /* mp3 */ +}; + +/* 700 ms PCR delay */ +#define NGX_RTMP_HLS_DELAY 63000 + +static uint32_t crc32table[256] = { + 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, + 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, + 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7, + 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, + 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, + 0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef, + 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, + 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, + 0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, + 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, + 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, + 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, + 0x0808d07d, 0x0cc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, + 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, + 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, + 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, + 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050, + 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, + 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, + 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, + 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1, + 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, + 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, + 0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 0xf5ee4bb9, + 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, + 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, + 0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, + 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, + 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, + 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, + 0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, + 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, + 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, + 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, + 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676, + 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, + 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, + 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, + 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 +}; + + +uint32_t +ngx_rtmp_mpegts_crc32(u_char *crc_buf, const u_char *data, int len) +{ + int i; + uint32_t crc = 0xFFFFFFFF; + + for(i = 0; i < len; i++) { + crc = (crc << 8) ^ crc32table[((crc >> 24) ^ *data++) & 0xFF]; + } + + crc_buf[0] = (crc & 0xff000000) >> 24; + crc_buf[1] = (crc & 0x00ff0000) >> 16; + crc_buf[2] = (crc & 0x0000ff00) >> 8; + crc_buf[3] = crc & 0x000000ff; + + return crc; +} + + +ngx_int_t +ngx_rtmp_mpegts_gen_pmt(ngx_int_t vcodec, ngx_int_t acodec, + ngx_log_t *log, u_char *pmt) +{ + u_char *p, crc_buf[4], *pmt_pos; + ngx_int_t vpid, apid; + u_char section_length; + + vpid = -1; + apid = -1; + + if (vcodec == acodec && vcodec == 0) { + return NGX_ERROR; + } + + p = pmt; + p = ngx_cpymem(p, ngx_mpegts_ts_header, sizeof(ngx_mpegts_ts_header)); + pmt_pos = p; + + p = ngx_cpymem(p, ngx_mpegts_pmt_header, sizeof(ngx_mpegts_pmt_header)); + + if (vcodec == 0) { + // ignore + } else if (vcodec == NGX_RTMP_VIDEO_H264) { + vpid = NGX_RTMP_MPEGTS_PID_H264; + } else if (vcodec == NGX_RTMP_HEVC_CODEC_ID) { + vpid = NGX_RTMP_MPEGTS_PID_H265; + } else if (log) { + ngx_log_error(NGX_LOG_ERR, log, 0, + "rtmp: gen_pmt| unknown video codec (%d)", vcodec); + } + + switch (acodec) { + case 0: //ignore + break; + + case NGX_RTMP_AUDIO_MP3: + apid = NGX_RTMP_MPEGTS_PID_MP3; + break; + + case NGX_RTMP_AUDIO_AAC: + apid = NGX_RTMP_MPEGTS_PID_AAC; + break; + + default: + if (log) { + ngx_log_error(NGX_LOG_ERR, log, 0, + "rtmp: gen_pmt| unknown video codec (%d)", vcodec); + } + } + + section_length = 13; + if (vpid != -1) { + p = ngx_cpymem(p, ngx_mpegts_pid[vpid], 5); + section_length += 5; + } + + if (apid != -1) { + p = ngx_cpymem(p, ngx_mpegts_pid[apid], 5); + section_length += 5; + } + + pmt_pos[2] = section_length; + + ngx_rtmp_mpegts_crc32(crc_buf, pmt_pos, p - pmt_pos); + p = ngx_cpymem(p, crc_buf, 4); + + ngx_memset(p, 0xff, 188 - (p - pmt)); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mpegts_write_file(ngx_rtmp_mpegts_file_t *file, u_char *in, + size_t in_size) +{ + u_char *out; + size_t out_size, n; + ssize_t rc; + + static u_char buf[1024]; + + if (!file->encrypt) { + ngx_log_debug1(NGX_LOG_DEBUG_CORE, file->log, 0, + "mpegts: write %uz bytes", in_size); + + if (file->whandle == NULL) { + rc = ngx_write_fd(file->fd, in, in_size); + if (rc < 0) { + return NGX_ERROR; + } + + file->file_size += rc; + } else { + rc = file->whandle(file, in, in_size); + if (rc < 0) { + return NGX_ERROR; + } + } + + return NGX_OK; + } + + /* encrypt */ + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, file->log, 0, + "mpegts: write %uz encrypted bytes", in_size); + + out = buf; + out_size = sizeof(buf); + + if (file->size > 0 && file->size + in_size >= 16) { + ngx_memcpy(file->buf + file->size, in, 16 - file->size); + + in += 16 - file->size; + in_size -= 16 - file->size; + + AES_cbc_encrypt(file->buf, out, 16, &file->key, file->iv, AES_ENCRYPT); + + out += 16; + out_size -= 16; + + file->size = 0; + } + + for ( ;; ) { + n = in_size & ~0x0f; + + if (n > 0) { + if (n > out_size) { + n = out_size; + } + + AES_cbc_encrypt(in, out, n, &file->key, file->iv, AES_ENCRYPT); + + in += n; + in_size -= n; + + } else if (out == buf) { + break; + } + + rc = ngx_write_fd(file->fd, buf, out - buf + n); + if (rc < 0) { + return NGX_ERROR; + } + + out = buf; + out_size = sizeof(buf); + } + + if (in_size) { + ngx_memcpy(file->buf + file->size, in, in_size); + file->size += in_size; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_mpegts_write_header(ngx_rtmp_mpegts_file_t *file) +{ + ngx_int_t ret; + u_char pmt[188]; + + ret = ngx_rtmp_mpegts_write_file(file, ngx_rtmp_mpegts_pat, + sizeof(ngx_rtmp_mpegts_pat)); + if (ret != NGX_OK) { + return ret; + } + + if (ngx_rtmp_mpegts_gen_pmt(file->vcodec, + file->acodec, file->log, pmt) != NGX_OK) + { + return NGX_ERROR; + } + + return ngx_rtmp_mpegts_write_file(file, pmt, sizeof(pmt)); +} + + +static u_char * +ngx_rtmp_mpegts_write_pcr(u_char *p, uint64_t pcr) +{ + *p++ = (u_char) (pcr >> 25); + *p++ = (u_char) (pcr >> 17); + *p++ = (u_char) (pcr >> 9); + *p++ = (u_char) (pcr >> 1); + *p++ = (u_char) (pcr << 7 | 0x7e); + *p++ = 0; + + return p; +} + + +static u_char * +ngx_rtmp_mpegts_write_pts(u_char *p, ngx_uint_t fb, uint64_t pts) +{ + ngx_uint_t val; + + val = fb << 4 | (((pts >> 30) & 0x07) << 1) | 1; + *p++ = (u_char) val; + + val = (((pts >> 15) & 0x7fff) << 1) | 1; + *p++ = (u_char) (val >> 8); + *p++ = (u_char) val; + + val = (((pts) & 0x7fff) << 1) | 1; + *p++ = (u_char) (val >> 8); + *p++ = (u_char) val; + + return p; +} + + +ngx_int_t +ngx_rtmp_mpegts_write_frame(ngx_rtmp_mpegts_file_t *file, + ngx_rtmp_mpegts_frame_t *f, ngx_buf_t *b) +{ + ngx_uint_t pes_size, header_size, body_size, in_size, stuff_size, flags; + u_char packet[188], *p, *base; + ngx_int_t first, rc; + + ngx_log_debug6(NGX_LOG_DEBUG_CORE, file->log, 0, + "mpegts: pid=%ui, sid=%ui, pts=%uL, " + "dts=%uL, key=%ui, size=%ui", + f->pid, f->sid, f->pts, f->dts, + (ngx_uint_t) f->key, (size_t) (b->last - b->pos)); + + first = 1; + + while (b->pos < b->last) { + p = packet; + + f->cc++; + + *p++ = 0x47; + *p++ = (u_char) (f->pid >> 8); + + if (first) { + p[-1] |= 0x40; + } + + *p++ = (u_char) f->pid; + *p++ = 0x10 | (f->cc & 0x0f); /* payload */ + + if (first) { + + if (f->key) { + packet[3] |= 0x20; /* adaptation */ + + *p++ = 7; /* size */ + *p++ = 0x50; /* random access + PCR */ + + p = ngx_rtmp_mpegts_write_pcr(p, f->dts - NGX_RTMP_HLS_DELAY); + } + + /* PES header */ + + *p++ = 0x00; + *p++ = 0x00; + *p++ = 0x01; + *p++ = (u_char) f->sid; + + header_size = 5; + flags = 0x80; /* PTS */ + + if (f->dts != f->pts) { + header_size += 5; + flags |= 0x40; /* DTS */ + } + + pes_size = (b->last - b->pos) + header_size + 3; + if (pes_size > 0xffff) { + pes_size = 0; + } + + *p++ = (u_char) (pes_size >> 8); + *p++ = (u_char) pes_size; + *p++ = 0x80; /* H222 */ + *p++ = (u_char) flags; + *p++ = (u_char) header_size; + + p = ngx_rtmp_mpegts_write_pts(p, flags >> 6, f->pts + + NGX_RTMP_HLS_DELAY); + + if (f->dts != f->pts) { + p = ngx_rtmp_mpegts_write_pts(p, 1, f->dts + + NGX_RTMP_HLS_DELAY); + } + + first = 0; + } + + body_size = (ngx_uint_t) (packet + sizeof(packet) - p); + in_size = (ngx_uint_t) (b->last - b->pos); + + if (body_size <= in_size) { + ngx_memcpy(p, b->pos, body_size); + b->pos += body_size; + + } else { + stuff_size = (body_size - in_size); + + if (packet[3] & 0x20) { + + /* has adaptation */ + + base = &packet[5] + packet[4]; + p = ngx_movemem(base + stuff_size, base, p - base); + ngx_memset(base, 0xff, stuff_size); + packet[4] += (u_char) stuff_size; + + } else { + + /* no adaptation */ + + packet[3] |= 0x20; + p = ngx_movemem(&packet[4] + stuff_size, &packet[4], + p - &packet[4]); + + packet[4] = (u_char) (stuff_size - 1); + if (stuff_size >= 2) { + packet[5] = 0; + ngx_memset(&packet[6], 0xff, stuff_size - 2); + } + } + + ngx_memcpy(p, b->pos, in_size); + b->pos = b->last; + } + + rc = ngx_rtmp_mpegts_write_file(file, packet, sizeof(packet)); + if (rc != NGX_OK) { + return rc; + } + } + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_mpegts_init_encryption(ngx_rtmp_mpegts_file_t *file, + u_char *key, size_t key_len, uint64_t iv) +{ + if (AES_set_encrypt_key(key, key_len * 8, &file->key)) { + return NGX_ERROR; + } + + ngx_memzero(file->iv, 8); + + file->iv[8] = (u_char) (iv >> 56); + file->iv[9] = (u_char) (iv >> 48); + file->iv[10] = (u_char) (iv >> 40); + file->iv[11] = (u_char) (iv >> 32); + file->iv[12] = (u_char) (iv >> 24); + file->iv[13] = (u_char) (iv >> 16); + file->iv[14] = (u_char) (iv >> 8); + file->iv[15] = (u_char) (iv); + + file->encrypt = 1; + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_mpegts_open_file(ngx_rtmp_mpegts_file_t *file, u_char *path, + ngx_log_t *log) +{ + file->log = log; + + file->fd = ngx_open_file(path, NGX_FILE_WRONLY, NGX_FILE_TRUNCATE, + NGX_FILE_DEFAULT_ACCESS); + + if (file->fd == NGX_INVALID_FILE) { + ngx_log_error(NGX_LOG_ERR, log, ngx_errno, + "hls: error creating fragment file"); + return NGX_ERROR; + } + + file->size = 0; + + if (ngx_rtmp_mpegts_write_header(file) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, log, ngx_errno, + "hls: error writing fragment header"); + ngx_close_file(file->fd); + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_mpegts_close_file(ngx_rtmp_mpegts_file_t *file) +{ + u_char buf[16]; + ssize_t rc; + + if (file->encrypt) { + ngx_memset(file->buf + file->size, 16 - file->size, 16 - file->size); + + AES_cbc_encrypt(file->buf, buf, 16, &file->key, file->iv, AES_ENCRYPT); + + rc = ngx_write_fd(file->fd, buf, 16); + if (rc < 0) { + return NGX_ERROR; + } + } + + ngx_close_file(file->fd); + + return NGX_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/hls/ngx_rtmp_mpegts.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/hls/ngx_rtmp_mpegts.h new file mode 100644 index 0000000000..a4a16a619f --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/hls/ngx_rtmp_mpegts.h @@ -0,0 +1,58 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#ifndef _NGX_RTMP_MPEGTS_H_INCLUDED_ +#define _NGX_RTMP_MPEGTS_H_INCLUDED_ + + +#include +#include +#include + + +typedef struct ngx_rtmp_mpegts_file_s ngx_rtmp_mpegts_file_t; + + +typedef ssize_t (*ngx_rtmp_mpegts_write_pt) (ngx_rtmp_mpegts_file_t *file, + u_char *in, size_t in_size); + + +struct ngx_rtmp_mpegts_file_s { + ngx_fd_t fd; + ngx_log_t *log; + off_t file_size; + unsigned encrypt:1; + unsigned size:4; + u_char buf[16]; + u_char iv[16]; + AES_KEY key; + ngx_int_t acodec; + ngx_int_t vcodec; + ngx_buf_t wbuf; + ngx_rtmp_mpegts_write_pt whandle; +}; + + +typedef struct { + uint64_t pts; + uint64_t dts; + ngx_uint_t pid; + ngx_uint_t sid; + ngx_uint_t cc; + unsigned key:1; +} ngx_rtmp_mpegts_frame_t; + + +ngx_int_t ngx_rtmp_mpegts_init_encryption(ngx_rtmp_mpegts_file_t *file, + u_char *key, size_t key_len, uint64_t iv); +ngx_int_t ngx_rtmp_mpegts_open_file(ngx_rtmp_mpegts_file_t *file, u_char *path, + ngx_log_t *log); +ngx_int_t ngx_rtmp_mpegts_close_file(ngx_rtmp_mpegts_file_t *file); +ngx_int_t ngx_rtmp_mpegts_write_header(ngx_rtmp_mpegts_file_t *file); +ngx_int_t ngx_rtmp_mpegts_write_frame(ngx_rtmp_mpegts_file_t *file, + ngx_rtmp_mpegts_frame_t *f, ngx_buf_t *b); + +#endif /* _NGX_RTMP_MPEGTS_H_INCLUDED_ */ diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/http/ngx_http_flv_live_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/http/ngx_http_flv_live_module.c new file mode 100644 index 0000000000..aa31ef2474 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/http/ngx_http_flv_live_module.c @@ -0,0 +1,733 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#include +#include +#include +#include +#include "ngx_rtmp.h" +#include "ngx_rtmp_cmd_module.h" +#include "ngx_rbuf.h" +#include "ngx_http_set_header.h" +#include "ngx_rtmp_monitor_module.h" + + +static char *ngx_http_flv_live(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); + +static void *ngx_http_flv_live_create_loc_conf(ngx_conf_t *cf); +static char *ngx_http_flv_live_merge_loc_conf(ngx_conf_t *cf, void *parent, + void *child); + +static u_char ngx_flv_live_audio_header[] = "FLV\x1\x1\0\0\0\x9\0\0\0\0"; +static u_char ngx_flv_live_video_header[] = "FLV\x1\x4\0\0\0\x9\0\0\0\0"; +static u_char ngx_flv_live_av_header[] = "FLV\x1\x5\0\0\0\x9\0\0\0\0"; + +static ngx_keyval_t ngx_http_flv_live_headers[] = { + { ngx_string("Cache-Control"), ngx_string("no-cache") }, + { ngx_string("Content-Type"), ngx_string("video/x-flv") }, + { ngx_null_string, ngx_null_string } +}; + +#define NGX_FLV_TAG_SIZE 11 +#define NGX_FLV_PTS_SIZE 4 + +typedef struct { + ngx_rtmp_session_t *session; +} ngx_http_flv_live_ctx_t; + +typedef struct { + ngx_str_t app; + ngx_str_t flashver; + ngx_str_t swf_url; + ngx_str_t tc_url; + ngx_str_t page_url; + ngx_uint_t audio; + ngx_uint_t video; + + ngx_rtmp_addr_conf_t *addr_conf; +} ngx_http_flv_live_loc_conf_t; + + +static ngx_command_t ngx_http_flv_live_commands[] = { + + { ngx_string("flv_live"), + NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123, + ngx_http_flv_live, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_http_flv_live_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + ngx_http_flv_live_create_loc_conf, /* create location configuration */ + ngx_http_flv_live_merge_loc_conf /* merge location configuration */ +}; + + +ngx_module_t ngx_http_flv_live_module = { + NGX_MODULE_V1, + &ngx_http_flv_live_module_ctx, /* module context */ + ngx_http_flv_live_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_int_t +ngx_http_flv_live_send_header(ngx_http_request_t *r) +{ + ngx_int_t rc; + ngx_keyval_t *h; + ngx_buf_t *b; + ngx_chain_t out; + ngx_http_flv_live_loc_conf_t *hflcf; + + if (r->header_sent) { + return NGX_OK; + } + + hflcf = ngx_http_get_module_loc_conf(r, ngx_http_flv_live_module); + + r->headers_out.status = NGX_HTTP_OK; + r->keepalive = 0; /* set Connection to closed */ + + h = ngx_http_flv_live_headers; + while (h->key.len) { + rc = ngx_http_set_header_out(r, &h->key, &h->value); + if (rc != NGX_OK) { + return rc; + } + ++h; + } + + rc = ngx_http_send_header(r); + if (rc == NGX_ERROR || rc > NGX_OK) { + return rc; + } + + b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t)); + if (b == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + switch (hflcf->audio | (hflcf->video << 1)) { + case 1: // audio only + b->start = b->pos = ngx_flv_live_audio_header; + b->end = b->last = ngx_flv_live_audio_header + + sizeof(ngx_flv_live_audio_header) - 1; + break; + + case 2: // video only + b->start = b->pos = ngx_flv_live_video_header; + b->end = b->last = ngx_flv_live_video_header + + sizeof(ngx_flv_live_video_header) - 1; + break; + + case 3: // audio and video + b->start = b->pos = ngx_flv_live_av_header; + b->end = b->last = ngx_flv_live_av_header + + sizeof(ngx_flv_live_av_header) - 1; + break; + + default: + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "flv-live: send_header| av header config error."); + + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + b->memory = 1; + + out.buf = b; + out.next = NULL; + + return ngx_http_output_filter(r, &out); +} + +static ngx_chain_t * +ngx_http_flv_live_prepare_out_chain(ngx_rtmp_session_t *s) +{ + ngx_rtmp_frame_t *frame; + ngx_chain_t *head, **ll, *cl; + u_char *p; + size_t datasize, prev_tag_size; + + frame = NULL; + head = NULL; + datasize = 0; + + while (s->out_pos != s->out_last) { + frame = s->out[s->out_pos]; + if (frame->hdr.type != NGX_RTMP_MSG_VIDEO + && frame->hdr.type != NGX_RTMP_MSG_AUDIO + && frame->hdr.type != NGX_RTMP_MSG_AMF_META + && frame->hdr.type != NGX_RTMP_MSG_AMF3_META) + { + ngx_rtmp_shared_free_frame(frame); + ++s->out_pos; + s->out_pos %= s->out_queue; + frame = NULL; + + continue; + } + break; + } + + /* no frame to send */ + if (frame == NULL) { + return NULL; + } + + for (ll = &head; *ll; ll = &(*ll)->next); + + for (cl = frame->chain; cl; cl = cl->next) { + datasize += (cl->buf->last - cl->buf->pos); + } + prev_tag_size = datasize + NGX_FLV_TAG_SIZE; + + /* flv tag header */ + *ll = ngx_get_chainbuf(NGX_FLV_TAG_SIZE, 1); + if (*ll == NULL) { + goto falied; + } + p = (*ll)->buf->pos; + + /* TagType 1 byte */ + *p++ = frame->hdr.type; + + /* DataSize 3 bytes */ + *p++ = ((u_char *) &datasize)[2]; + *p++ = ((u_char *) &datasize)[1]; + *p++ = ((u_char *) &datasize)[0]; + + /* Timestamp 4 bytes */ + *p++ = ((u_char *) &frame->hdr.timestamp)[2]; + *p++ = ((u_char *) &frame->hdr.timestamp)[1]; + *p++ = ((u_char *) &frame->hdr.timestamp)[0]; + *p++ = ((u_char *) &frame->hdr.timestamp)[3]; + + /* StreamID 4 bytes, always set to 0 */ + *p++ = 0; + *p++ = 0; + *p++ = 0; + + (*ll)->buf->last = p; + ll = &(*ll)->next; + + /* flv payload */ + for (cl = frame->chain; cl; cl = cl->next) { + (*ll) = ngx_get_chainbuf(0, 0); + if (*ll == NULL) { + goto falied; + } + (*ll)->buf->pos = cl->buf->pos; + (*ll)->buf->last = cl->buf->last; + ll = &(*ll)->next; + } + + /* flv previous tag size */ + *ll = ngx_get_chainbuf(NGX_FLV_PTS_SIZE, 1); + if (*ll == NULL) { + goto falied; + } + p = (*ll)->buf->pos; + + *p++ = ((u_char *) &prev_tag_size)[3]; + *p++ = ((u_char *) &prev_tag_size)[2]; + *p++ = ((u_char *) &prev_tag_size)[1]; + *p++ = ((u_char *) &prev_tag_size)[0]; + + (*ll)->buf->last = p; + (*ll)->buf->flush = 1; + + ngx_rtmp_monitor_frame(s, &frame->hdr, NULL, frame->av_header, 0); + + return head; + +falied: + ngx_put_chainbufs(head); + + ngx_rtmp_finalize_session(s); + return NULL; +} + +static void +ngx_http_flv_live_write_handler(ngx_http_request_t *r) +{ + ngx_http_flv_live_ctx_t *ctx; + ngx_rtmp_session_t *s; + ngx_event_t *wev; + size_t present, sent; + ngx_int_t rc; + ngx_chain_t *cl; + + wev = r->connection->write; + + if (r->connection->destroyed) { + return; + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_flv_live_module); + s = ctx->session; + + if (wev->timedout) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, NGX_ETIMEDOUT, + "http flv live, client timed out"); + r->connection->timedout = 1; + s->finalize_reason = NGX_LIVE_FLV_SEND_TIMEOUT; + if (r->header_sent) { + ngx_http_finalize_request(r, NGX_HTTP_REQUEST_TIME_OUT); + } else { + r->error_page = 1; + ngx_http_finalize_request(r, NGX_HTTP_SERVICE_UNAVAILABLE); + } + + return; + } + + if (wev->timer_set) { + ngx_del_timer(wev); + } + + if (ngx_rtmp_core_main_conf->fast_reload && (ngx_exiting || ngx_terminate)) { + r->error_page = 1; + ngx_http_finalize_request(r, NGX_HTTP_SERVICE_UNAVAILABLE); + return; + } + + if (ngx_rtmp_prepare_merge_frame(s) == NGX_ERROR) { + ngx_http_finalize_request(r, NGX_ERROR); + return; + } + + if (s->out_chain) { + rc = ngx_http_flv_live_send_header(r); + if (rc == NGX_ERROR || rc > NGX_OK) { + s->finalize_reason = NGX_LIVE_FLV_SEND_ERR; + ngx_http_finalize_request(r, rc); + return; + } + } + + while (s->out_chain) { + present = r->connection->sent; + + if (r->connection->buffered) { + rc = ngx_http_output_filter(r, NULL); + } else { + rc = ngx_http_output_filter(r, s->out_chain); + } + + sent = r->connection->sent - present; + + ngx_rtmp_update_bandwidth(&ngx_rtmp_bw_out, sent); + + if (rc == NGX_AGAIN) { + ngx_add_timer(wev, s->timeout); + if (ngx_handle_write_event(wev, 0) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, ngx_errno, + "http flv live, handle write event failed"); + ngx_http_finalize_request(r, NGX_ERROR); + } + return; + } + + if (rc == NGX_ERROR) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, ngx_errno, + "http flv live, send error"); + s->finalize_reason = NGX_LIVE_FLV_SEND_ERR; + ngx_http_finalize_request(r, NGX_ERROR); + return; + } + + /* NGX_OK */ + for (cl = s->out_chain; cl;) { + s->out_chain = cl->next; + ngx_free_chain(s->pool, cl); + cl = s->out_chain; + } + + if (ngx_rtmp_prepare_merge_frame(s) == NGX_ERROR) { + ngx_http_finalize_request(r, NGX_ERROR); + return; + } + } + + if (wev->active) { + ngx_del_event(wev, NGX_WRITE_EVENT, 0); + } +} + + +static void +ngx_http_flv_live_parse_url(ngx_http_request_t *r, ngx_str_t *app, + ngx_str_t *name) +{ + u_char *p, *end, *pos; + + p = r->uri.data + 1; /* skip '/' */ + end = r->uri.data + r->uri.len; + app->data = p; + + pos = ngx_strnstr(p, ".flv", end - p); + if (pos) { + end = pos; + } + + p = (u_char *) ngx_strnstr(p, "/", end - p); + while (p) { + name->data = p; + p = (u_char *) ngx_strnstr(p + 1, "/", end - p); + } + + if (name->data == NULL) { + return; + } + + app->len = name->data - app->data; + + ++name->data; + name->len = end - name->data; +} + +static ngx_int_t +ngx_http_flv_live_parse(ngx_http_request_t *r, ngx_rtmp_session_t *s, + ngx_rtmp_play_t *v) +{ + ngx_http_flv_live_loc_conf_t *hflcf; + ngx_str_t app, stream; + size_t tcurl_len; + u_char *p; + + hflcf = ngx_http_get_module_loc_conf(r, ngx_http_flv_live_module); + + ngx_memzero(&app, sizeof(ngx_str_t)); + ngx_memzero(&stream, sizeof(ngx_str_t)); + + ngx_http_flv_live_parse_url(r, &app, &stream); + + if (app.len == 0 || stream.len == 0 || stream.len > NGX_RTMP_MAX_NAME) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "http flv live, url error: %V", &r->uri); + return NGX_HTTP_BAD_REQUEST; + } + + if (hflcf->app.len) { + app = hflcf->app; + } + + if (ngx_http_arg(r, (u_char *) "flashver", 8, &s->flashver) != NGX_OK) { + s->flashver = hflcf->flashver; + } + + ngx_http_arg(r, (u_char *) "app", 3, &app); + + s->app = app; + + /* tc_url */ +#if (NGX_HTTP_SSL) + if (r->connection->ssl) { + tcurl_len = sizeof("https://") - 1; + } else +#endif + { + tcurl_len = sizeof("http://") - 1; + } + tcurl_len += r->headers_in.server.len + 1 + app.len; + + s->tc_url.len = tcurl_len; + s->tc_url.data = ngx_pcalloc(r->pool, tcurl_len); + if (s->tc_url.data == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + p = s->tc_url.data; + +#if (NGX_HTTP_SSL) + if (r->connection->ssl) { + p = ngx_cpymem(p, "https://", sizeof("https://") - 1); + } else +#endif + { + p = ngx_cpymem(p, "http://", sizeof("http://") - 1); + } + + p = ngx_cpymem(p, r->headers_in.server.data, r->headers_in.server.len); + *p++ = '/'; + p = ngx_cpymem(p, app.data, app.len); + + /* page_url */ + if (r->headers_in.referer) { + s->page_url = r->headers_in.referer->value; + } else { + s->page_url = hflcf->page_url; + } + + s->acodecs = 0x0DF7; + s->vcodecs = 0xFC; + + ngx_memcpy(v->name, stream.data, stream.len); + + if (r->args.len) { + ngx_memcpy(v->args, r->args.data, + ngx_min(r->args.len, NGX_RTMP_MAX_ARGS)); + } + + ngx_rtmp_cmd_middleware_init(s); + + return NGX_OK; +} + +static void +ngx_http_flv_live_cleanup(void *data) +{ + ngx_http_request_t *r; + ngx_http_flv_live_ctx_t *ctx; + + r = data; + + ctx = ngx_http_get_module_ctx(r, ngx_http_flv_live_module); + + if (ctx == NULL) { + return; + } + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "http flv live, cleanup"); + + if (ctx->session) { + ctx->session->request = NULL; + + if (ctx->session->finalize_reason == 0) { + ctx->session->finalize_reason = r->connection->read->error? + NGX_LIVE_FLV_RECV_ERR: + NGX_LIVE_NORMAL_CLOSE; + } + + ngx_rtmp_finalize_fake_session(ctx->session); + } +} + +static ngx_int_t +ngx_http_flv_live_handler(ngx_http_request_t *r) +{ + ngx_http_flv_live_loc_conf_t *hflcf; + ngx_http_flv_live_ctx_t *ctx; + ngx_rtmp_session_t *s; + ngx_rtmp_play_t v; + ngx_int_t rc; + ngx_uint_t n; + ngx_rtmp_core_srv_conf_t *cscf; + ngx_rtmp_core_app_conf_t **cacfp; + ngx_http_cleanup_t *cln; + ngx_rtmp_core_main_conf_t *cmcf; + + rc = ngx_http_discard_request_body(r); + + if (rc != NGX_OK) { + return rc; + } + + ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_flv_live_ctx_t)); + if (ctx == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + ngx_http_set_ctx(r, ctx, ngx_http_flv_live_module); + + /* cleanup handler */ + cln = ngx_http_cleanup_add(r, 0); + if (cln == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + cln->handler = ngx_http_flv_live_cleanup; + cln->data = r; + + hflcf = ngx_http_get_module_loc_conf(r, ngx_http_flv_live_module); + + /* create fake session */ + s = ngx_rtmp_create_session(hflcf->addr_conf); + if (s == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + s->connection = r->connection; + s->number = r->connection->number; + s->remote_addr_text.data = ngx_pcalloc(s->pool, r->connection->addr_text.len); + s->remote_addr_text.len = r->connection->addr_text.len; + ngx_memcpy(s->remote_addr_text.data, + r->connection->addr_text.data, r->connection->addr_text.len); + ngx_rtmp_set_combined_log(s, r->connection->log->data, + r->connection->log->handler); + s->log->connection = r->connection->number; + ctx->session = s; + + /* get host, app, stream name */ + ngx_memzero(&v, sizeof(ngx_rtmp_play_t)); + rc = ngx_http_flv_live_parse(r, s, &v); + if (rc != NGX_OK) { + return rc; + } + + if (ngx_rtmp_set_virtual_server(s, &s->domain)) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + s->live_type = NGX_HTTP_FLV_LIVE; + s->live_server = ngx_live_create_server(&s->serverid); + s->request = r; + + v.silent = 1; + + cacfp = cscf->applications.elts; + for (n = 0; n < cscf->applications.nelts; ++n, ++cacfp) { + if ((*cacfp)->name.len == s->app.len && + ngx_strncmp((*cacfp)->name.data, s->app.data, s->app.len) == 0) + { + /* found app! */ + s->app_conf = (*cacfp)->app_conf; + break; + } + } + + if (s->app_conf == NULL) { + + if (cscf->default_app == NULL || cscf->default_app->app_conf == NULL) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "http flv live, application not found '%V'", &s->app); + return NGX_HTTP_NOT_FOUND; + } + + s->app_conf = cscf->default_app->app_conf; + } + + s->prepare_handler = ngx_http_flv_live_prepare_out_chain; + + s->stage = NGX_LIVE_PLAY; + s->ptime = ngx_current_msec; + + cmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_core_module); + s->variables = ngx_pcalloc(s->pool, cmcf->variables.nelts + * sizeof(ngx_http_variable_value_t)); + if (s->variables == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + if (ngx_rtmp_play_filter(s, &v) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + ngx_add_timer(r->connection->write, s->timeout); + + r->read_event_handler = ngx_http_test_reading; + r->write_event_handler = ngx_http_flv_live_write_handler; + + ++r->count; + + return NGX_DONE; +} + + +static void * +ngx_http_flv_live_create_loc_conf(ngx_conf_t *cf) +{ + ngx_http_flv_live_loc_conf_t *hflcf; + + hflcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_flv_live_loc_conf_t)); + if (hflcf == NULL) { + return NULL; + } + + return hflcf; +} + +static char * +ngx_http_flv_live_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_http_flv_live_loc_conf_t *prev = parent; + ngx_http_flv_live_loc_conf_t *conf = child; + + ngx_conf_merge_str_value(conf->app, prev->app, ""); + ngx_conf_merge_str_value(conf->flashver, prev->flashver, ""); + ngx_conf_merge_str_value(conf->swf_url, prev->swf_url, ""); + ngx_conf_merge_str_value(conf->tc_url, prev->tc_url, ""); + ngx_conf_merge_str_value(conf->page_url, prev->page_url, ""); + + return NGX_CONF_OK; +} + +static char * +ngx_http_flv_live(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_core_loc_conf_t *clcf; + ngx_http_flv_live_loc_conf_t *hflcf; + ngx_str_t *value, v; + ngx_uint_t i; + ngx_uint_t audio, video; + + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + clcf->handler = ngx_http_flv_live_handler; + + hflcf = conf; + + value = cf->args->elts; + + hflcf->addr_conf = ngx_rtmp_find_related_addr_conf(cf->cycle, &value[1]); + if (hflcf->addr_conf == NULL) { + return NGX_CONF_ERROR; + } + + audio = NGX_CONF_UNSET_UINT; + video = NGX_CONF_UNSET_UINT; + + for (i = 2; i < cf->args->nelts; ++i) { + if (ngx_strncmp(value[i].data, "app=", 4) == 0) { + v.data = value[i].data + 4; + v.len = value[i].len - 4; + hflcf->app = v; + } else if (ngx_strncmp(value[i].data, "audio=", 6) == 0) { + v.data = value[i].data + 6; + v.len = value[i].len - 6; + audio = ngx_atoi(v.data, v.len); + } else if (ngx_strncmp(value[i].data, "video=", 6) == 0) { + v.data = value[i].data + 6; + v.len = value[i].len - 6; + video = ngx_atoi(v.data, v.len); + } else { + return NGX_CONF_ERROR; + } + } + + if (audio == NGX_CONF_UNSET_UINT) { + audio = 1; + } + + if (video == NGX_CONF_UNSET_UINT) { + video = 1; + } + + hflcf->audio = audio; + hflcf->video = video; + + return NGX_CONF_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/http/ngx_http_set_header.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/http/ngx_http_set_header.c new file mode 100644 index 0000000000..ffef7aa593 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/http/ngx_http_set_header.c @@ -0,0 +1,282 @@ +#include +#include +#include + +typedef struct ngx_http_header_val_s ngx_http_header_val_t; + +typedef ngx_int_t (*ngx_http_set_header_pt)(ngx_http_request_t *r, + ngx_http_header_val_t *hv, ngx_str_t *value); + +struct ngx_http_header_val_s { + ngx_http_complex_value_t value; + ngx_uint_t hash; + ngx_str_t key; + ngx_http_set_header_pt handler; + ngx_uint_t offset; +}; + +typedef struct { + ngx_str_t name; + ngx_uint_t offset; + ngx_http_set_header_pt handler; + +} ngx_http_set_header_t; + +//TODO need fill all header set in future +/* for header has no quick link in ngx_http_headers_out_t */ +static ngx_int_t ngx_http_set_header_out_other(ngx_http_request_t *r, + ngx_http_header_val_t *hv, ngx_str_t *value); +/* for header has quick link like ngx_table_elt_t* in ngx_http_headers_out_t */ +static ngx_int_t ngx_http_set_header_out_builtin(ngx_http_request_t *r, + ngx_http_header_val_t *hv, ngx_str_t *value); +/* for header has quick link like ngx_array_t in ngx_http_headers_out_t */ +static ngx_int_t ngx_http_set_header_out_builtin_multi(ngx_http_request_t *r, + ngx_http_header_val_t *hv, ngx_str_t *value); +/* for header has quick link like ngx_table_elt_t* and + * other attribute in ngx_http_headers_out_t defined below */ +static ngx_int_t ngx_http_set_header_out_content_type(ngx_http_request_t *r, + ngx_http_header_val_t *hv, ngx_str_t *value); + +static ngx_http_set_header_t ngx_http_set_header_out_handlers[] = { + + { ngx_string("Server"), + offsetof(ngx_http_headers_out_t, server), + ngx_http_set_header_out_builtin }, + + { ngx_string("Date"), + offsetof(ngx_http_headers_out_t, date), + ngx_http_set_header_out_builtin }, + + { ngx_string("Content-Type"), + offsetof(ngx_http_headers_out_t, content_type), + ngx_http_set_header_out_content_type }, + + { ngx_string("Cache-Control"), + offsetof(ngx_http_headers_out_t, cache_control), + ngx_http_set_header_out_builtin_multi }, + + { ngx_null_string, 0, ngx_http_set_header_out_other } +}; + +static ngx_int_t +ngx_http_set_header_out_helper(ngx_http_request_t *r, + ngx_http_header_val_t *hv, ngx_str_t *value, unsigned no_create) +{ + ngx_table_elt_t *h; + ngx_list_part_t *part; + ngx_uint_t i; + + part = &r->headers_out.headers.part; + h = part->elts; + + for (i = 0; /* void */; ++i) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + h = part->elts; + i = 0; + } + + if (h[i].hash != 0 + && h[i].key.len == hv->key.len + && ngx_strncasecmp(hv->key.data, h[i].key.data, h[i].key.len) == 0) + /* header has been set */ + { + h[i].value = *value; + if (value->len == 0) { /* if value is empty, remove header */ + h[i].hash = 0; + } else { + h[i].hash = hv->hash; + } + + return NGX_OK; + } + } + + if (no_create && value->len == 0) { /* set header to empty but header cannot found */ + return NGX_OK; + } + + /* header has not been set, create it */ + + h = ngx_list_push(&r->headers_out.headers); + + if (h == NULL) { + return NGX_ERROR; + } + + h->hash = hv->hash; + h->key = hv->key; + h->value = *value; + + h->lowcase_key = ngx_pnalloc(r->pool, h->key.len); + if (h->lowcase_key == NULL) { + return NGX_ERROR; + } + + ngx_strlow(h->lowcase_key, h->key.data, h->key.len); + + return NGX_OK; +} + +static ngx_int_t +ngx_http_set_header_out_other(ngx_http_request_t * r, + ngx_http_header_val_t * hv,ngx_str_t * value) +{ + return ngx_http_set_header_out_helper(r, hv, value, 0); +} + + +static ngx_int_t +ngx_http_set_header_out_builtin(ngx_http_request_t *r, + ngx_http_header_val_t *hv, ngx_str_t *value) +{ + ngx_table_elt_t *h, **old; + + if (hv->offset) { + old = (ngx_table_elt_t **) ((char *) &r->headers_out + hv->offset); + } else { + old = NULL; + } + + if (old == NULL || *old == NULL) { + /* user should use ngx_http_set_header_out_other but use this func to set header */ + //TODO + return ngx_http_set_header_out_helper(r, hv, value, 0); + } + + h = *old; + + h->value = *value; + if (value->len == 0) { /* if value is empty, remove header */ + + h->hash = 0; + return NGX_OK; + } + + h->hash = hv->hash; + h->key = hv->key; + + return NGX_OK; +} + +static ngx_int_t +ngx_http_set_header_out_builtin_multi(ngx_http_request_t *r, + ngx_http_header_val_t *hv, ngx_str_t *value) +{ + ngx_array_t *pa; + ngx_table_elt_t *ho, **ph; + ngx_uint_t i; + + pa = (ngx_array_t *) ((char *) &r->headers_out + hv->offset); + + if (pa->elts == NULL) { + if (ngx_array_init(pa, r->pool, 2, sizeof(ngx_table_elt_t *)) + != NGX_OK) + { + return NGX_ERROR; + } + } + + if (pa->nelts > 0) { + ph = pa->elts; + for (i = 1; i < pa->nelts; i++) { /* clear old value */ + ph[i]->hash = 0; + ph[i]->value.len = 0; + } + + ph[0]->value = *value; + + if (value->len == 0) { + ph[0]->hash = 0; + } else { + ph[0]->hash = hv->hash; + } + + return NGX_OK; + } + + /* header does not set */ + ph = ngx_array_push(pa); + if (ph == NULL) { + return NGX_ERROR; + } + + ho = ngx_list_push(&r->headers_out.headers); + if (ho == NULL) { + return NGX_ERROR; + } + + ho->value = *value; + ho->hash = hv->hash; + ho->key = hv->key; + *ph = ho; + + return NGX_OK; +} + +static ngx_int_t +ngx_http_set_header_out_content_type(ngx_http_request_t *r, + ngx_http_header_val_t *hv, ngx_str_t *value) +{ + ngx_uint_t i; + + r->headers_out.content_type_len = value->len; + + for (i = 0; i < value->len; i++) { + if (value->data[i] == ';') { + r->headers_out.content_type_len = i; + break; + } + } + + r->headers_out.content_type = *value; + r->headers_out.content_type_hash = hv->hash; + r->headers_out.content_type_lowcase = NULL; + + value->len = 0; + + return ngx_http_set_header_out_helper(r, hv, value, 1); +} + +ngx_int_t +ngx_http_set_header_out(ngx_http_request_t *r, ngx_str_t *key, ngx_str_t *value) +{ + ngx_http_header_val_t hv; + ngx_http_set_header_t *handlers = ngx_http_set_header_out_handlers; + ngx_uint_t i; + ngx_str_t v; + + hv.hash = ngx_hash_key_lc(key->data, key->len); + hv.key = *key; + + hv.offset = 0; + hv.handler = NULL; + + for (i = 0; handlers[i].name.len; ++i) { + if (hv.key.len != handlers[i].name.len + || ngx_strncasecmp(hv.key.data, handlers[i].name.data, + handlers[i].name.len) != 0) { + continue; + } + + /* match handler */ + hv.offset = handlers[i].offset; + hv.handler = handlers[i].handler; + + break; + } + + if (handlers[i].name.len == 0 && handlers[i].handler) { /* if not matched, use ngx_http_set_header as default*/ + hv.offset = handlers[i].offset; + hv.handler = handlers[i].handler; + } + + v = *value; + + return hv.handler(r, &hv, &v); +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/http/ngx_http_set_header.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/http/ngx_http_set_header.h new file mode 100644 index 0000000000..eca5f3e4fb --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/http/ngx_http_set_header.h @@ -0,0 +1,11 @@ +#ifndef _NGX_RTMP_HTTP_HEADER_OUT_H_INCLUDED_ +#define _NGX_RTMP_HTTP_HEADER_OUT_H_INCLUDED_ + +#include +#include +#include + +ngx_int_t ngx_http_set_header_out(ngx_http_request_t *r, + ngx_str_t *key, ngx_str_t *value); + +#endif diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/mpegts/ngx_hls_http_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/mpegts/ngx_hls_http_module.c new file mode 100644 index 0000000000..599a942785 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/mpegts/ngx_hls_http_module.c @@ -0,0 +1,1303 @@ + +/* + * Copyright (C) Pingo (cczjp89@gmail.com) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "ngx_http_set_header.h" +#include "ngx_mpegts_live_module.h" +#include "ngx_hls_live_module.h" +#include "ngx_rbuf.h" +#include "ngx_rtmp_dynamic.h" + +#ifndef NGX_HTTP_GONE +#define NGX_HTTP_GONE 410 +#endif + +static ngx_keyval_t ngx_302_headers[] = { + { ngx_string("Location"), ngx_null_string }, + { ngx_null_string, ngx_null_string } +}; + +static ngx_keyval_t ngx_m3u8_headers[] = { + { ngx_string("Cache-Control"), ngx_string("no-cache") }, + { ngx_string("Content-Type"), ngx_string("application/vnd.apple.mpegurl") }, + { ngx_null_string, ngx_null_string } +}; + +static ngx_keyval_t ngx_ts_headers[] = { + { ngx_string("Cache-Control"), ngx_string("no-cache") }, + { ngx_string("Content-Type"), ngx_string("video/mp2t") }, + { ngx_null_string, ngx_null_string } +}; + + +typedef struct { + ngx_str_t app; + ngx_str_t name; + ngx_str_t stream; + ngx_str_t serverid; + ngx_str_t sid; + ngx_rtmp_session_t *session; + ngx_msec_t timeout; + ngx_uint_t content_pos; + ngx_chain_t *m3u8; + ngx_uint_t out_pos; + ngx_uint_t out_last; + ngx_chain_t *out_chain; + ngx_hls_live_frag_t *frag; +} ngx_hls_http_ctx_t; + + +typedef struct { + ngx_str_t app; + ngx_str_t flashver; + ngx_str_t swf_url; + ngx_str_t tc_url; + ngx_str_t page_url; + ngx_msec_t timeout; + ngx_rtmp_addr_conf_t *addr_conf; +} ngx_hls_http_loc_conf_t; + +static u_char NGX_HLS_LIVE_ARG_SESSION[] = "session"; + +static ngx_int_t NGX_HLS_LIVE_ARG_SESSION_LENGTH = 7; + +static void * ngx_hls_http_create_loc_conf(ngx_conf_t *cf); +static char * ngx_hls_http_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); +static char * ngx_http_hls(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +//static char * ngx_hls_http_variant(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static ngx_int_t ngx_hls_http_postconfiguration(ngx_conf_t *cf); + +static ngx_command_t ngx_hls_http_commands[] = { + + { ngx_string("hls2_live"), + NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, + ngx_http_hls, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + + ngx_null_command +}; + +static ngx_http_module_t ngx_hls_http_module_ctx = { + NULL, /* preconfiguration */ + ngx_hls_http_postconfiguration, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + ngx_hls_http_create_loc_conf, /* create location configuration */ + ngx_hls_http_merge_loc_conf /* merge location configuration */ +}; + +ngx_module_t ngx_hls_http_module = { + NGX_MODULE_V1, + &ngx_hls_http_module_ctx, /* module context */ + ngx_hls_http_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + +static void * +ngx_hls_http_create_loc_conf(ngx_conf_t *cf) +{ + ngx_hls_http_loc_conf_t *hlcf; + + hlcf = ngx_pcalloc(cf->pool, sizeof(ngx_hls_http_loc_conf_t)); + if (hlcf == NULL) { + return NULL; + } + + return hlcf; +} + +static char * +ngx_hls_http_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_hls_http_loc_conf_t *prev = parent; + ngx_hls_http_loc_conf_t *conf = child; + + ngx_conf_merge_str_value(conf->app, prev->app, ""); + ngx_conf_merge_str_value(conf->flashver, prev->flashver, ""); + ngx_conf_merge_str_value(conf->swf_url, prev->swf_url, ""); + ngx_conf_merge_str_value(conf->tc_url, prev->tc_url, ""); + ngx_conf_merge_str_value(conf->page_url, prev->page_url, ""); + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_hls_http_ctx_init(ngx_http_request_t *r, ngx_rtmp_addr_conf_t *addr_conf) +{ + u_char *p, *e; + ngx_buf_t *buf; + ngx_rtmp_core_srv_conf_t *cscf; + ngx_hls_http_loc_conf_t *hlcf; + ngx_hls_http_ctx_t *ctx; + ngx_str_t *app, *name, *stream, *domain, *serverid; + ngx_int_t rc; + + hlcf = ngx_http_get_module_loc_conf(r, ngx_hls_http_module); + ctx = ngx_http_get_module_ctx(r, ngx_hls_http_module); + + app = &ctx->app; + name = &ctx->name; + stream = &ctx->stream; + serverid = &ctx->serverid; + domain = &r->headers_in.server; + + p = r->uri.data; + e = r->uri.data + r->uri.len; + + p++; + app->data = p; + p = ngx_strlchr(p, e, '/'); + if (p == NULL) { + app->data = NULL; + app->len = 0; + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "hls-http: ctx_init| invalid uri, lack of app"); + + return NGX_ERROR; + } + app->len = p - app->data; + + if (hlcf->app.len > 0 && hlcf->app.data) { + *app = hlcf->app; + } + + p++; + name->data = p; + if (ngx_strncmp(&e[-5], ".m3u8", 5) == 0) { + p = ngx_strlchr(p, e, '.'); + } else if (ngx_strncmp(&e[-3], ".ts", 3) == 0) { + p = ngx_strlchr(p, e, '-'); + } else { + p = NULL; + } + + if (p == NULL) { + name->data = NULL; + name->len = 0; + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "hls-http: ctx_init| invalid uri, lack of name"); + + return NGX_ERROR; + } + name->len = p - name->data; + + cscf = addr_conf->default_server-> + ctx->srv_conf[ngx_rtmp_core_module.ctx_index]; + + rc = ngx_rtmp_find_virtual_server(addr_conf->virtual_names, domain, &cscf); + if (rc != NGX_OK && rc != NGX_DECLINED) { + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "hls-http: ctx_init| server(%V) not found.", domain); + + return NGX_ERROR; + } + + if (cscf && cscf->serverid.len) { + serverid->data = ngx_pcalloc(r->connection->pool, cscf->serverid.len); + if (serverid->data == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "hls-http: ctx_init| pcalloc failed."); + return NGX_ERROR; + } + serverid->len = cscf->serverid.len; + ngx_memcpy(serverid->data, cscf->serverid.data, cscf->serverid.len); + } else { + *serverid = *domain; + } + + buf = ngx_create_temp_buf(r->connection->pool, + serverid->len + 1 + app->len + name->len + 1); + buf->last = ngx_slprintf(buf->start, buf->end, + "%V/%V/%V", serverid, app, name); + + stream->data = buf->pos; + stream->len = buf->last - buf->pos; + + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "hls-http: ctx_init| hls stream (%V).", stream); + + return NGX_OK; +} + + +static ngx_int_t +ngx_hls_http_send_header(ngx_http_request_t *r, ngx_uint_t status, ngx_keyval_t *h) +{ + ngx_int_t rc; + + r->headers_out.status = status; +// r->keepalive = 0; /* set Connection to closed */ + + //set eTag + if (ngx_http_set_etag(r) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + while (h && h->key.len) { + rc = ngx_http_set_header_out(r, &h->key, &h->value); + if (rc != NGX_OK) { + return rc; + } + ++h; + } + + return ngx_http_send_header(r); +} + +static ngx_int_t +ngx_hls_http_master_m3u8_handler(ngx_http_request_t *r, + ngx_rtmp_addr_conf_t *addr_conf) +{ + + ngx_int_t rc; + ngx_str_t m3u8_url; + ngx_str_t host; + u_char sstr[NGX_RTMP_MAX_SESSION] = {0}; + static ngx_uint_t sindex = 0; + ngx_str_t location = ngx_string(""); + ngx_str_t uri; + ngx_str_t uri_tail; + ngx_buf_t *m3u8; + ngx_chain_t out; + ngx_str_t scheme = ngx_string(""); + ngx_str_t http = ngx_string("http"); + ngx_str_t https = ngx_string("https"); + u_char *p; + + host = r->headers_in.host->value; + + rc = ngx_http_discard_request_body(r); + if (rc != NGX_OK) { + return rc; + } + + *ngx_snprintf(sstr, sizeof(sstr) - 1, "%uDt-%uDi-%dp-%uDc", + time(NULL), sindex++, ngx_process_slot, r->connection->number) = 0; + + ngx_http_arg(r, (u_char*)"location", 8, &location); + if (location.len == 0) { + uri = r->uri; + } else { + uri_tail.data = + ngx_strlchr(r->uri.data + 1, r->uri.data + r->uri.len - 1, '/'); + if (uri_tail.data == NULL) { + uri_tail = r->uri; + } else { + uri_tail.len = r->uri.data+r->uri.len - uri_tail.data; + } + + uri.len = location.len + uri_tail.len; + uri.data = ngx_pcalloc(r->pool, uri.len); + if (uri.data == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "hls-http: master_m3u8_handler| pcalloc uri buffer failed"); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + ngx_snprintf(uri.data, uri.len, "%V%V", &location, &uri_tail); + } + + ngx_http_arg(r, (u_char*)"scheme", 6, &scheme); + if (scheme.len == 0) { +#if (NGX_HTTP_SSL) + if (r->connection->ssl) { + scheme = https; + } else +#endif + { + scheme = http; + } + } + + m3u8_url.len = scheme.len + ngx_strlen("://") + + host.len + + uri.len + + NGX_HLS_LIVE_ARG_SESSION_LENGTH + 2 + + ngx_strlen(sstr); + + if (r->args.len > 0) { + m3u8_url.len += 1 + r->args.len; + } + + m3u8_url.data = ngx_pcalloc(r->connection->pool, m3u8_url.len); + + p = ngx_snprintf(m3u8_url.data, m3u8_url.len, "%V://%V%V?%s=%s", + &scheme, &host, &uri, NGX_HLS_LIVE_ARG_SESSION, sstr); + + if (r->args.len > 0) { + ngx_slprintf(p, m3u8_url.data + m3u8_url.len, "&%V", &r->args); + } + + m3u8 = ngx_create_temp_buf(r->connection->pool, 64 * 1024); + m3u8->memory = 1; + m3u8->flush = 1; + out.buf = m3u8; + out.next = NULL; + + m3u8->last = ngx_snprintf(m3u8->pos, m3u8->end - m3u8->start, + "#EXTM3U\n" + "#EXT-X-STREAM-INF:BANDWIDTH=1,AVERAGE-BANDWIDTH=1\n" + //"#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1\n" + "%V\n", &m3u8_url); + + r->headers_out.content_length_n = m3u8->last - m3u8->pos; + + rc = ngx_hls_http_send_header(r, NGX_HTTP_OK, ngx_m3u8_headers); + if (rc != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "hls-http: master_m3u8_handler| " + "send header failed, redirect url: %V, rc=%d", &m3u8_url, rc); + } else { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "hls-http: master_m3u8_handler| redirect url %V", &m3u8_url); + } + + return ngx_http_output_filter(r, &out); +} + +static ngx_int_t +ngx_hls_http_redirect_handler(ngx_http_request_t *r, + ngx_rtmp_addr_conf_t *addr_conf) +{ + ngx_int_t rc; + ngx_str_t m3u8_url; + ngx_str_t host; + u_char sstr[NGX_RTMP_MAX_SESSION] = {0}; + static ngx_uint_t sindex = 0; + ngx_str_t location = ngx_string(""); + ngx_str_t uri; + ngx_str_t uri_tail; + ngx_str_t scheme = ngx_string(""); + ngx_str_t http = ngx_string("http"); + ngx_str_t https = ngx_string("https"); + u_char *p; + + host = r->headers_in.host->value; + + rc = ngx_http_discard_request_body(r); + if (rc != NGX_OK) { + return rc; + } + + *ngx_snprintf(sstr, sizeof(sstr) - 1, "%uDt-%uDi-%dp-%uDc", + time(NULL), sindex++, ngx_process_slot, r->connection->number) = 0; + + ngx_http_arg(r, (u_char*)"location", 8, &location); + if (location.len == 0) { + uri = r->uri; + } else { + uri_tail.data = + ngx_strlchr(r->uri.data + 1, r->uri.data + r->uri.len - 1, '/'); + if (uri_tail.data == NULL) { + uri_tail = r->uri; + } else { + uri_tail.len = r->uri.data+r->uri.len - uri_tail.data; + } + + uri.len = location.len + uri_tail.len; + uri.data = ngx_pcalloc(r->pool, uri.len); + if (uri.data == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "hls-http: redirect_handler| pcalloc uri buffer failed"); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + ngx_snprintf(uri.data, uri.len, "%V%V", &location, &uri_tail); + } + + ngx_http_arg(r, (u_char*)"scheme", 6, &scheme); + if (scheme.len == 0) { +#if (NGX_HTTP_SSL) + if (r->connection->ssl) { + scheme = https; + } else +#endif + { + scheme = http; + } + } + + m3u8_url.len = scheme.len + ngx_strlen("://") + + host.len + + uri.len + + NGX_HLS_LIVE_ARG_SESSION_LENGTH + 2 + + ngx_strlen(sstr); + + if (r->args.len > 0) { + m3u8_url.len += 1 + r->args.len; + } + + m3u8_url.data = ngx_pcalloc(r->connection->pool, m3u8_url.len); + + p = ngx_snprintf(m3u8_url.data, m3u8_url.len, "%V://%V%V?%s=%s", + &scheme, &host, &uri, NGX_HLS_LIVE_ARG_SESSION, sstr); + + if (r->args.len > 0) { + ngx_slprintf(p, m3u8_url.data + m3u8_url.len, "&%V", &r->args); + } + + ngx_http_set_header_out(r, &ngx_302_headers[0].key, &m3u8_url); + + r->headers_out.content_length_n = 0; + r->header_only = 1; + + rc = ngx_hls_http_send_header(r, NGX_HTTP_MOVED_TEMPORARILY, NULL); + if (rc != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "hls-http: redirect_handler| " + "send header failed, redirect url: %V, rc=%d", &m3u8_url, rc); + } else { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "hls-http: redirect_handler| redirect url %V", &m3u8_url); + } + + return rc; +} + + +static ngx_hls_http_ctx_t * +ngx_hls_http_create_ctx(ngx_http_request_t *r, ngx_rtmp_addr_conf_t *addr_conf) +{ + ngx_hls_http_ctx_t *ctx; + ngx_rtmp_core_srv_conf_t *cscf; + u_char *p; + + cscf = addr_conf->default_server->ctx-> + srv_conf[ngx_rtmp_core_module.ctx_index]; + + ctx = ngx_http_get_module_ctx(r, ngx_hls_http_module); + if (ctx == NULL) { + p = ngx_pcalloc(r->connection->pool, sizeof(ngx_hls_http_ctx_t)); + + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "hls-http: create_ctx| alloc hls live ctx failed"); + return NULL; + } + + ctx = (ngx_hls_http_ctx_t *)p; + + ngx_http_set_ctx(r, ctx, ngx_hls_http_module); + } + + ctx->timeout = cscf->timeout; + + ngx_hls_http_ctx_init(r, addr_conf); + + if (ctx->app.len == 0 || ctx->name.len == 0) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "hls-http: create_ctx| parse app or name failed, uri %V", &r->uri); + return NULL; + } + + ngx_http_arg(r, NGX_HLS_LIVE_ARG_SESSION, NGX_HLS_LIVE_ARG_SESSION_LENGTH, + &ctx->sid); + + return ctx; +} + + +static void +ngx_hls_http_cleanup(void *data) +{ + ngx_http_request_t *r; + ngx_hls_http_ctx_t *ctx; + ngx_chain_t *cl; + + r = data; + ctx = ngx_http_get_module_ctx(r, ngx_hls_http_module); + + if (!ctx) { + return; + } + + if (ctx->out_chain == NULL) { + cl = ctx->out_chain; + while (cl) { + ctx->out_chain = cl->next; + ngx_put_chainbuf(cl); + cl = ctx->out_chain; + } + } + + ctx->content_pos = 0; + + if (ctx->session) { + ctx->session->request = NULL; + ctx->session->connection = NULL; + } + + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "hls-http: cleanup_handler| http cleanup"); + + if (ctx->frag) { + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "hls-http: cleanup_handler| free frag[%p]", ctx->frag); + ngx_hls_live_free_frag(ctx->frag); + ctx->frag = NULL; + } +} + + +static ngx_int_t +ngx_hls_http_parse(ngx_http_request_t *r, ngx_rtmp_session_t *s, + ngx_rtmp_play_t *v) +{ + ngx_hls_http_loc_conf_t *hlcf; + size_t tcurl_len; + u_char *p; + ngx_hls_http_ctx_t *ctx; + + ctx = ngx_http_get_module_ctx(r, ngx_hls_http_module); + + hlcf = ngx_http_get_module_loc_conf(r, ngx_hls_http_module); + +#define NGX_HLS_HTTP_SET_VAL(_val) \ + s->_val.data = ngx_pcalloc(s->pool, ctx->_val.len); \ + s->_val.len = ctx->_val.len; \ + ngx_memcpy(s->_val.data, ctx->_val.data, ctx->_val.len) + + NGX_HLS_HTTP_SET_VAL(app); + NGX_HLS_HTTP_SET_VAL(stream); + +#undef NGX_HLS_HTTP_SET_VAL + + if (ngx_http_arg(r, (u_char *) "flashver", 8, &s->flashver) != NGX_OK) { + s->flashver = hlcf->flashver; + } + + /* tc_url */ +#if (NGX_HTTP_SSL) + if (r->connection->ssl) { + tcurl_len = sizeof("https://") - 1; + } else +#endif + { + tcurl_len = sizeof("http://") - 1; + } + tcurl_len += r->headers_in.server.len + 1 + s->app.len; + + s->tc_url.len = tcurl_len; + s->tc_url.data = ngx_pcalloc(r->pool, tcurl_len); + if (s->tc_url.data == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + p = s->tc_url.data; + +#if (NGX_HTTP_SSL) + if (r->connection->ssl) { + p = ngx_cpymem(p, "https://", sizeof("https://") - 1); + } else +#endif + { + p = ngx_cpymem(p, "http://", sizeof("http://") - 1); + } + + p = ngx_cpymem(p, r->headers_in.server.data, r->headers_in.server.len); + *p++ = '/'; + p = ngx_cpymem(p, s->app.data, s->app.len); + + /* page_url */ + if (r->headers_in.referer) { + s->page_url = r->headers_in.referer->value; + } else { + s->page_url = hlcf->page_url; + } + + s->acodecs = 0x0DF7; + s->vcodecs = 0xFC; + + ngx_memcpy(v->name, ctx->name.data, ctx->name.len); + + if (r->args.len) { + ngx_memcpy(v->args, r->args.data, + ngx_min(r->args.len, NGX_RTMP_MAX_ARGS)); + } + + ngx_memcpy(v->session, ctx->sid.data, ctx->sid.len); + + ngx_rtmp_cmd_middleware_init(s); + + return NGX_OK; +} + + +static ngx_rtmp_session_t* +ngx_hls_http_create_session(ngx_http_request_t *r) +{ + ngx_hls_http_loc_conf_t *hlcf; + ngx_rtmp_session_t *s; + ngx_rtmp_play_t v; + ngx_int_t rc; + ngx_uint_t n; + ngx_rtmp_core_srv_conf_t *cscf; + ngx_rtmp_core_app_conf_t **cacfp; + ngx_rtmp_core_main_conf_t *cmcf; + + hlcf = ngx_http_get_module_loc_conf(r, ngx_hls_http_module); + + /* create fake session */ + s = ngx_rtmp_create_session(hlcf->addr_conf); + if (s == NULL) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "hls-http: create_session| create session failed"); + return NULL; + } + + /* get host, app, stream name */ + ngx_memzero(&v, sizeof(ngx_rtmp_play_t)); + + rc = ngx_hls_http_parse(r, s, &v); + if (rc != NGX_OK) { + return NULL; + } + + if (ngx_rtmp_set_virtual_server(s, &s->domain)) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "hls-http: create_session| set virtual server failed, %V", + &s->domain); + return NULL; + } + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + s->log->connection = r->connection->number; + s->number = r->connection->number; + s->live_type = NGX_HLS_LIVE; + s->live_server = ngx_live_create_server(&s->serverid); + s->remote_addr_text.data = ngx_pcalloc(s->pool, r->connection->addr_text.len); + s->remote_addr_text.len = r->connection->addr_text.len; + ngx_memcpy(s->remote_addr_text.data, + r->connection->addr_text.data, r->connection->addr_text.len); + + v.silent = 1; + + cacfp = cscf->applications.elts; + for (n = 0; n < cscf->applications.nelts; ++n, ++cacfp) { + if ((*cacfp)->name.len == s->app.len && + ngx_strncmp((*cacfp)->name.data, s->app.data, s->app.len) == 0) + { + /* found app! */ + s->app_conf = (*cacfp)->app_conf; + break; + } + } + + if (s->app_conf == NULL) { + + if (cscf->default_app == NULL || cscf->default_app->app_conf == NULL) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "hls-http: create_session| application not found '%V'", &s->app); + + ngx_rtmp_finalize_fake_session(s); + + return NULL; + } + + s->app_conf = cscf->default_app->app_conf; + } + + s->stage = NGX_LIVE_PLAY; + s->ptime = ngx_current_msec; +// s->connection = r->connection; + + cmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_core_module); + s->variables = ngx_pcalloc(s->pool, cmcf->variables.nelts + * sizeof(ngx_http_variable_value_t)); + if (s->variables == NULL) { + return NULL; + } + + if (ngx_rtmp_play_filter(s, &v) != NGX_OK) { + return NULL; + } + + ngx_add_timer(r->connection->write, s->timeout); + + return s; +} + + +static ngx_int_t +ngx_hls_http_m3u8_handler(ngx_http_request_t *r, ngx_rtmp_addr_conf_t *addr_conf) +{ + ngx_hls_http_ctx_t *ctx; + ngx_int_t rc; + ngx_rtmp_session_t *s; + ngx_chain_t *out; + ngx_buf_t *buf; + + ctx = ngx_hls_http_create_ctx(r, addr_conf); + if (ctx == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "hls-http: m3u8_handler| create ctx failed"); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + s = ngx_hls_live_fetch_session(&ctx->serverid, &ctx->stream, &ctx->sid); + if (s == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "hls-http: m3u8_handler| hls session %V not found", &ctx->sid); + + s = ngx_hls_http_create_session(r); + if (s == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "hls-http: m3u8_handler| create hls session %V error", + &ctx->sid); + + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + s->sockaddr = ngx_pcalloc(s->pool, sizeof(struct sockaddr)); + ngx_memcpy(s->sockaddr, r->connection->sockaddr, sizeof(struct sockaddr)); + } + + s->request = r; + + ctx->session = s; + + if (!ctx->m3u8) { + ctx->m3u8 = ngx_pcalloc(r->connection->pool, sizeof(ngx_chain_t)); + ctx->m3u8->buf = ngx_create_temp_buf(r->connection->pool, 1024*512); + } + + out = ctx->m3u8; + buf = out->buf; + buf->last = buf->pos = buf->start; + buf->memory = 1; + buf->flush = 1; +// buf->last_in_chain = 1; +// buf->last_buf = 1; + + rc = ngx_hls_live_write_playlist(s, buf, &r->headers_out.last_modified_time); + if (rc != NGX_OK) { + goto again; + } + + r->headers_out.content_length_n = buf->last - buf->pos; + + s->out_bytes += r->headers_out.content_length_n; + + if (!r->header_sent) { + rc = ngx_hls_http_send_header(r, NGX_HTTP_OK, ngx_m3u8_headers); + if (rc != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "hls-http: m3u8_handler| send http header failed"); + return rc; + } + } + + rc = ngx_http_output_filter(r, out); + if (rc != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "hls-http: m3u8_handler| send http content failed"); + return rc; + } + + return rc; + +again: + r->count++; + + return NGX_DONE; +} + + +static u_char* +ngx_hls_http_strrchr(ngx_str_t *str, u_char c) +{ + u_char *s, *e; + + s = str->data; + e = str->data + str->len; + + e--; + while(e != s) { + if (*e == c) { + break; + } + e--; + } + + if (e == s) { + return NULL; + } + + return e; +} + + +static ngx_int_t +ngx_hls_http_parse_frag(ngx_http_request_t *r, ngx_str_t *name) +{ + u_char *s, *e; + + e = ngx_hls_http_strrchr(&r->uri, '?'); + if (e == NULL) { + e = r->uri.data + r->uri.len; + } + + s = ngx_hls_http_strrchr(&r->uri, '/'); + if (s == NULL) { + s = r->uri.data; + } else { + s++; + } + + name->data = s; + name->len = e - s; + + return NGX_OK; +} + +static ngx_chain_t * +ngx_hls_http_prepare_out_chain(ngx_http_request_t *r, ngx_int_t nframes) +{ + ngx_hls_http_ctx_t *ctx; + ngx_hls_live_frag_t *frag; + ngx_rtmp_session_t *s; + ngx_chain_t *out, *cl, **ll; + ngx_mpegts_frame_t *frame; + ngx_int_t i = 0; + + ctx = ngx_http_get_module_ctx(r, ngx_hls_http_module); + s = ctx->session; + frag = ctx->frag; + + out = NULL; + + ll = &out; + while (i < nframes && ctx->content_pos != frag->content_last) { + frame = frag->content[ctx->content_pos]; + + for (cl = frame->chain; cl; cl = cl->next) { + *ll = ngx_get_chainbuf(0, 0); + (*ll)->buf->pos = cl->buf->pos; + (*ll)->buf->last = cl->buf->last; + (*ll)->buf->flush = 1; + + ll = &(*ll)->next; + } + + *ll = NULL; + ctx->content_pos = ngx_hls_live_next(s, ctx->content_pos); + i++; + } + + return out; +} + +static void +ngx_hls_http_write_handler(ngx_http_request_t *r) +{ + ngx_hls_http_ctx_t *ctx; + ngx_rtmp_session_t *s; + ngx_event_t *wev; + size_t present, sent; + ngx_int_t rc; + ngx_chain_t *cl; + + wev = r->connection->write; //wev->handler = ngx_http_request_handler; + + if (r->connection->destroyed) { + return; + } + + ctx = ngx_http_get_module_ctx(r, ngx_hls_http_module); + s = ctx->session; + + if (wev->timedout) { + ngx_log_error(NGX_LOG_INFO, s->log, NGX_ETIMEDOUT, + "hls_http: write_handler| client timed out"); + r->connection->timedout = 1; + if (r->header_sent) { + ngx_http_finalize_request(r, NGX_HTTP_CLIENT_CLOSED_REQUEST); + } else { + r->error_page = 1; + ngx_http_finalize_request(r, NGX_HTTP_SERVICE_UNAVAILABLE); + } + + return; + } + + if (wev->timer_set) { + ngx_del_timer(wev); + } + + if (ctx->out_chain == NULL) { + ctx->out_chain = ngx_hls_http_prepare_out_chain(r, 4); + } + + rc = NGX_OK; + + while (ctx->out_chain) { + present = r->connection->sent; + + if (r->connection->buffered) { + rc = ngx_http_output_filter(r, NULL); + } else { + rc = ngx_http_output_filter(r, ctx->out_chain); + } + + sent = r->connection->sent - present; + + ngx_rtmp_update_bandwidth(&ngx_rtmp_bw_out, sent); + + if (rc == NGX_AGAIN) { + ngx_add_timer(wev, s->timeout); + if (ngx_handle_write_event(wev, 0) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno, + "hls_http: write_handler| handle write event failed"); + ngx_http_finalize_request(r, NGX_ERROR); + } + return; + } + + if (rc == NGX_ERROR) { + ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno, + "hls_http: write_handler| send error"); + ngx_http_finalize_request(r, NGX_ERROR); + + return; + } + + /* NGX_OK */ + cl = ctx->out_chain; + while (cl) { + ctx->out_chain = cl->next; + ngx_put_chainbuf(cl); + cl = ctx->out_chain; + } + + if (ctx->frag->content_pos == ctx->frag->content_last) { + ctx->out_chain = NULL; + break; + } + + ctx->out_chain = ngx_hls_http_prepare_out_chain(r, 4); + } + + if (wev->active) { + ngx_del_event(wev, NGX_WRITE_EVENT, 0); + } + + ngx_http_finalize_request(r, NGX_HTTP_OK); +} + +static ngx_int_t +ngx_hls_http_ts_handler(ngx_http_request_t *r, ngx_rtmp_addr_conf_t *addr_conf) +{ + ngx_hls_http_ctx_t *ctx; + ngx_rtmp_session_t *s; + ngx_hls_live_frag_t *frag; + ngx_int_t rc; + ngx_str_t name; + + ctx = ngx_hls_http_create_ctx(r, addr_conf); + if (ctx == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "hls-http: ts_handler| create ctx failed"); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + s = ngx_hls_live_fetch_session(&ctx->serverid, &ctx->stream, &ctx->sid); + if (s == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "hls-http: ts_handler| hls session %V not found", &ctx->sid); + return NGX_DECLINED; + } + ctx->session = s; + s->request = r; + +// ngx_rtmp_set_combined_log(s, r->connection->log->data, +// r->connection->log->handler); + + rc = ngx_hls_http_parse_frag(r, &name); + if (rc != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "hls-http: ts_handler| parse frag args failed %V", &r->uri); + return NGX_HTTP_NOT_ALLOWED; + } + + frag = ngx_hls_live_find_frag(s, &name); + if (frag == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "hls-http: ts_handler| ts not found, %V", &r->uri); + return NGX_HTTP_NOT_FOUND; + } + + ctx->frag = frag; + + r->headers_out.content_length_n = frag->length; + r->headers_out.last_modified_time = frag->last_modified_time; + s->out_bytes += r->headers_out.content_length_n; + + rc = ngx_hls_http_send_header(r, NGX_HTTP_OK, ngx_ts_headers); + if (rc != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "hls-http: ts_handler| send http header failed, %V", &r->uri); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + ngx_rtmp_shared_acquire_frag(frag); + + if (1) { + r->write_event_handler = ngx_hls_http_write_handler; + + r->count++; + + ngx_hls_http_write_handler(r); + + return NGX_DONE; + } else { + ctx->out_chain = ngx_hls_live_prepare_frag(s, frag); + + ngx_rtmp_update_bandwidth(&ngx_rtmp_bw_out, frag->length); + + return ngx_http_output_filter(r, ctx->out_chain); + } +} + + +static ngx_int_t +ngx_hls_http_handler(ngx_http_request_t *r) +{ + ngx_hls_http_loc_conf_t *hlcf; + ngx_int_t rc; + ngx_http_cleanup_t *cln; + ngx_str_t sstr; + ngx_hls_http_ctx_t *ctx; + + hlcf = ngx_http_get_module_loc_conf(r, ngx_hls_http_module); + + rc = ngx_http_discard_request_body(r); + + if (rc != NGX_OK) { + return rc; + } + + if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "hls-http: live_handler| donnot support the method"); + return NGX_HTTP_NOT_ALLOWED; + } + + if (ngx_rtmp_core_main_conf->fast_reload && (ngx_exiting || ngx_terminate)) { + return NGX_DECLINED; + } + + if (r->uri.len < 4) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "hls-http: live_handler| donnot support the file type"); + return NGX_DECLINED; + } + + ctx = ngx_http_get_module_ctx(r, ngx_hls_http_module); + if (!ctx) { + cln = ngx_http_cleanup_add(r, 0); + if (cln == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + cln->handler = ngx_hls_http_cleanup; + cln->data = r; + r->read_event_handler = ngx_http_test_reading; + } + + if(!ngx_strncmp(r->uri.data + r->uri.len - 5, ".m3u8", 5)) { + + rc = ngx_http_arg(r, NGX_HLS_LIVE_ARG_SESSION, + NGX_HLS_LIVE_ARG_SESSION_LENGTH, &sstr); + + if (rc != NGX_OK || sstr.len == 0) { + if (1) { + return ngx_hls_http_master_m3u8_handler(r, hlcf->addr_conf); + } else { + return ngx_hls_http_redirect_handler(r, hlcf->addr_conf); + } + } else { + return ngx_hls_http_m3u8_handler(r, hlcf->addr_conf); + } + + } else if (!ngx_strncmp(r->uri.data + r->uri.len - 3, ".ts", 3)) { + return ngx_hls_http_ts_handler(r, hlcf->addr_conf); + } + + return NGX_DECLINED; +} + +static ngx_int_t +ngx_hls_http_m3u8(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, ngx_chain_t *in) +{ + ngx_http_request_t *r; + ngx_int_t rc; + ngx_hls_http_ctx_t *ctx; + ngx_buf_t *buf; + ngx_chain_t *out; + + r = s->request; + if (!r) { + return NGX_ERROR; + } + + ctx = ngx_http_get_module_ctx(r, ngx_hls_http_module); + + if (!ctx->m3u8) { + ctx->m3u8 = ngx_pcalloc(r->connection->pool, sizeof(ngx_chain_t)); + ctx->m3u8->buf = ngx_create_temp_buf(r->connection->pool, 1024*512); + } + + out = ctx->m3u8; + buf = out->buf; + buf->last = buf->pos = buf->start; + buf->memory = 1; + buf->flush = 1; +// buf->last_in_chain = 1; +// buf->last_buf = 1; + + ngx_hls_live_write_playlist(s, buf, &r->headers_out.last_modified_time); + + r->headers_out.content_length_n = buf->last - buf->pos; + + s->out_bytes += r->headers_out.content_length_n; + + rc = ngx_hls_http_send_header(r, NGX_HTTP_OK, ngx_m3u8_headers); + if (rc != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "hls-http: m3u8| send http header failed"); + return rc; + } + + rc = ngx_http_output_filter(r, out); + if (rc != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "hls-http: m3u8| send http content failed"); + return rc; + } + + ngx_http_finalize_request(r, NGX_HTTP_OK); + + return NGX_OK; +} + +static ngx_int_t +ngx_hls_http_close(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, ngx_chain_t *in) +{ + ngx_http_request_t *r; + ngx_hls_http_ctx_t *ctx; + + r = s->request; + if (!r) { + return NGX_OK; + } + + ctx = ngx_http_get_module_ctx(r, ngx_hls_http_module); + if (!ctx) { + return NGX_OK; + } + + s->request = NULL; + s->connection = NULL; + ctx->session = NULL; + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "hls-http: close| finalize http request"); + + ngx_http_finalize_request(r, NGX_HTTP_GONE); + + return NGX_OK; +} + + +static char * +ngx_http_hls(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_core_loc_conf_t *clcf; + ngx_hls_http_loc_conf_t *hlcf; + ngx_str_t *value; + ngx_uint_t n; + + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + clcf->handler = ngx_hls_http_handler; + + hlcf = conf; + + value = cf->args->elts; + + hlcf->addr_conf = ngx_rtmp_find_related_addr_conf(cf->cycle, &value[1]); + if (hlcf->addr_conf == NULL) { + return NGX_CONF_ERROR; + } + + for (n = 2; n < cf->args->nelts; ++n) { +#define PARSE_CONF_ARGS(arg) \ + { \ + size_t len = sizeof(#arg"=") - 1; \ + if (ngx_memcmp(value[n].data, #arg"=", len) == 0) { \ + hlcf->arg.data = value[n].data + len; \ + hlcf->arg.len = value[n].len - len; \ + continue; \ + } \ + } + + PARSE_CONF_ARGS(app); + PARSE_CONF_ARGS(flashver); + PARSE_CONF_ARGS(swf_url); + PARSE_CONF_ARGS(tc_url); + PARSE_CONF_ARGS(page_url); + +#undef PARSE_CONF_ARGS + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"%V\" para not support", &value[n]); + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + +static ngx_int_t +ngx_hls_http_postconfiguration(ngx_conf_t *cf) +{ + ngx_rtmp_core_main_conf_t *cmcf; + ngx_rtmp_handler_pt *h; + + cmcf = ngx_rtmp_core_main_conf; + + if (!cmcf) { + return NGX_OK; + } + + h = ngx_array_push(&cmcf->events[NGX_MPEGTS_MSG_M3U8]); + *h = ngx_hls_http_m3u8; + + h = ngx_array_push(&cmcf->events[NGX_MPEGTS_MSG_CLOSE]); + *h = ngx_hls_http_close; + + return NGX_OK; +} + diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/mpegts/ngx_hls_live_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/mpegts/ngx_hls_live_module.c new file mode 100644 index 0000000000..efab067928 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/mpegts/ngx_hls_live_module.c @@ -0,0 +1,1094 @@ + +/* + * Copyright (C) Pingo (cczjp89@gmail.com) + */ + + +#include +#include +#include +#include +#include +#include "ngx_rbuf.h" +#include "ngx_hls_live_module.h" +#include "ngx_mpegts_live_module.h" +#include "ngx_rtmp_live_module.h" +#include "ngx_mpegts_gop_module.h" + +static ngx_rtmp_play_pt next_play; +static ngx_rtmp_close_stream_pt next_close_stream; + +static ngx_int_t ngx_hls_live_postconfiguration(ngx_conf_t *cf); +static void * ngx_hls_live_create_main_conf(ngx_conf_t *cf); +static void * ngx_hls_live_create_app_conf(ngx_conf_t *cf); +static char * ngx_hls_live_merge_app_conf(ngx_conf_t *cf, + void *parent, void *child); +static ngx_int_t +ngx_hls_live_write_frame(ngx_rtmp_session_t *s, ngx_mpegts_frame_t *frame); +static void +ngx_hls_live_update_playlist(ngx_rtmp_session_t *s); +static ngx_int_t +ngx_hls_live_update(ngx_rtmp_session_t *s, ngx_rtmp_codec_ctx_t *codec_ctx); + +static ngx_mpegts_video_pt next_mpegts_video; +static ngx_mpegts_audio_pt next_mpegts_audio; + +typedef struct { + ngx_flag_t hls; + ngx_msec_t fraglen; + ngx_msec_t max_fraglen; + ngx_msec_t playlen; + ngx_uint_t winfrags; + ngx_uint_t minfrags; + ngx_uint_t slicing; + ngx_uint_t type; + ngx_path_t *slot; + size_t audio_buffer_size; + ngx_flag_t cleanup; + ngx_array_t *variant; + ngx_str_t base_url; + ngx_pool_t *pool; + ngx_msec_t timeout; +} ngx_hls_live_app_conf_t; + +typedef struct { + ngx_hls_live_frag_t *free_frag; + ngx_pool_t *pool; +} ngx_hls_live_main_conf_t; + +ngx_hls_live_main_conf_t *ngx_hls_live_main_conf = NULL; + +#define NGX_RTMP_HLS_NAMING_SEQUENTIAL 1 +#define NGX_RTMP_HLS_NAMING_TIMESTAMP 2 +#define NGX_RTMP_HLS_NAMING_SYSTEM 3 + + +#define NGX_RTMP_HLS_SLICING_PLAIN 1 +#define NGX_RTMP_HLS_SLICING_ALIGNED 2 + + +#define NGX_RTMP_HLS_TYPE_LIVE 1 +#define NGX_RTMP_HLS_TYPE_EVENT 2 + + +static ngx_conf_enum_t ngx_hls_live_slicing_slots[] = { + { ngx_string("plain"), NGX_RTMP_HLS_SLICING_PLAIN }, + { ngx_string("aligned"), NGX_RTMP_HLS_SLICING_ALIGNED }, + { ngx_null_string, 0 } +}; + +/* +static ngx_conf_enum_t ngx_hls_live_type_slots[] = { + { ngx_string("live"), NGX_RTMP_HLS_TYPE_LIVE }, + { ngx_string("event"), NGX_RTMP_HLS_TYPE_EVENT }, + { ngx_null_string, 0 } +}; +*/ + +static ngx_command_t ngx_hls_live_commands[] = { + + { ngx_string("hls2memory"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_hls_live_app_conf_t, hls), + NULL }, + + { ngx_string("hls2"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_hls_live_app_conf_t, hls), + NULL }, + + { ngx_string("hls2_fragment"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_hls_live_app_conf_t, fraglen), + NULL }, + + { ngx_string("hls2_max_fragment"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_hls_live_app_conf_t, max_fraglen), + NULL }, + + { ngx_string("hls2_playlist_length"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_hls_live_app_conf_t, playlen), + NULL }, + + { ngx_string("hls2_minfrags"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_hls_live_app_conf_t, minfrags), + NULL }, + + { ngx_string("hls2_fragment_slicing"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_enum_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_hls_live_app_conf_t, slicing), + &ngx_hls_live_slicing_slots }, + + { ngx_string("hls2_audio_buffer_size"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_hls_live_app_conf_t, audio_buffer_size), + NULL }, + + { ngx_string("hls2_cleanup"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_hls_live_app_conf_t, cleanup), + NULL }, + + { ngx_string("hls2_base_url"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_hls_live_app_conf_t, base_url), + NULL }, + + { ngx_string("hls2_timeout"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_hls_live_app_conf_t, timeout), + NULL }, + + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_hls_live_module_ctx = { + NULL, /* preconfiguration */ + ngx_hls_live_postconfiguration, /* postconfiguration */ + + ngx_hls_live_create_main_conf, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + ngx_hls_live_create_app_conf, /* create location configuration */ + ngx_hls_live_merge_app_conf, /* merge location configuration */ +}; + + +ngx_module_t ngx_hls_live_module = { + NGX_MODULE_V1, + &ngx_hls_live_module_ctx, /* module context */ + ngx_hls_live_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_hls_live_frag_t * +ngx_hls_live_get_frag(ngx_rtmp_session_t *s, ngx_int_t n) +{ + ngx_hls_live_ctx_t *ctx; + ngx_hls_live_app_conf_t *hacf; + + hacf = ngx_rtmp_get_module_app_conf(s, ngx_hls_live_module); + ctx = ngx_rtmp_get_module_ctx(s, ngx_hls_live_module); + + return ctx->frags[(ctx->nfrag + n) % (hacf->winfrags * 2 + 1)]; +} + + +static void +ngx_hls_live_next_frag(ngx_rtmp_session_t *s) +{ + ngx_hls_live_ctx_t *ctx; + ngx_hls_live_app_conf_t *hacf; + + hacf = ngx_rtmp_get_module_app_conf(s, ngx_hls_live_module); + ctx = ngx_rtmp_get_module_ctx(s, ngx_hls_live_module); + + if (ctx->nfrags == hacf->winfrags) { + ctx->nfrag++; + } else { + ctx->nfrags++; + } +} + + +ngx_int_t +ngx_hls_live_write_playlist(ngx_rtmp_session_t *s, ngx_buf_t *out, + time_t *last_modified_time) +{ + ngx_hls_live_ctx_t *ctx; + ngx_str_t m3u8; + ngx_hls_live_app_conf_t *hacf; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_hls_live_module); + if (ctx == NULL) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, "hls-live: playlist| ctx is null"); + + return NGX_ERROR; + } + + ctx->last_time = time(NULL); + + hacf = ngx_rtmp_get_module_app_conf(s, ngx_hls_live_module); + + if (ctx->nfrags < hacf->minfrags || ctx->playing == 0) { + return NGX_AGAIN; + } + + ngx_hls_live_update_playlist(s); + + *last_modified_time = ctx->playlist_modified_time; + + out->last = ngx_cpymem(out->pos, ctx->playlist->pos, + ctx->playlist->last - ctx->playlist->pos); + + m3u8.data = out->pos; + m3u8.len = out->last - out->pos; + + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, "hls-live: playlist| %V, %D", + &m3u8, ctx->last_time); + + return NGX_OK; +} + + +ngx_hls_live_frag_t* +ngx_hls_live_find_frag(ngx_rtmp_session_t *s, ngx_str_t *name) +{ + ngx_hls_live_ctx_t *ctx; + u_char *p0, *p1, *e; + ngx_uint_t frag_id; + ngx_hls_live_frag_t *frag; + ngx_hls_live_app_conf_t *hacf; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_hls_live_module); + + p0 = name->data; + e = p0 + name->len; + + for (; *e != '.' && e != p0; e--); + + if (e == p0) { + return NULL; + } + + p1 = e; + + for (; *e != '-' && e != p0; e--); + + if (e == p0) { + return NULL; + } + + p0 = e + 1; + + frag_id = ngx_atoi(p0, p1 - p0); + + hacf = ngx_rtmp_get_module_app_conf(s, ngx_hls_live_module); + + if (frag_id > ctx->nfrag + ctx->nfrags || + ctx->nfrag + ctx->nfrags - frag_id > hacf->winfrags * 2 + 1) + { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "hls-live: find_frag| invalid frag id[%d], curr id [%d]", + frag_id, ctx->nfrag + ctx->nfrags); + return NULL; + } + + frag = ctx->frags[frag_id % (hacf->winfrags * 2 + 1)]; + + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "hls-live: find_frag| find frag %p [%d] [frag %d] length %ui", + frag, frag_id, frag->id, frag->length); + + return frag; +} + +void +ngx_rtmp_shared_acquire_frag(ngx_hls_live_frag_t *frag) +{ + frag->ref++; +} + +ngx_chain_t* +ngx_hls_live_prepare_frag(ngx_rtmp_session_t *s, ngx_hls_live_frag_t *frag) +{ + ngx_chain_t *out, *cl, **ll, *last_chain; + ngx_mpegts_frame_t *frame; + + ll = &out; + if (frag->content_pos == frag->content_last) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, "hls-live: prepare_frag| " + "pos %D == last %D", + frag->content_pos, frag->content_last); + return NULL; + } + + last_chain = NULL; + + while (frag->content_pos != frag->content_last) { + frame = frag->content[frag->content_pos]; + + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, "hls-live: prepare_frag| " + "pos %D, last %D, frame chain %p", + frag->content_pos, frag->content_last, + frame); + for (cl = frame->chain; cl; cl = cl->next) { + *ll = ngx_get_chainbuf(0, 0); + (*ll)->buf->pos = cl->buf->pos; + (*ll)->buf->last = cl->buf->last; + (*ll)->buf->flush = 1; + + last_chain = *ll; + + ll = &(*ll)->next; + } + + *ll = NULL; + frag->content_pos = ngx_hls_live_next(s, frag->content_pos); + } + + if (last_chain) { + last_chain->buf->last_buf = 1; + last_chain->buf->last_in_chain = 1; + } + frag->content_pos = 0; + + return out; +} + + +ngx_rtmp_session_t* +ngx_hls_live_fetch_session(ngx_str_t *server, + ngx_str_t *stream, ngx_str_t *session) +{ + ngx_live_stream_t *live_stream; + ngx_hls_live_ctx_t *ctx; + + live_stream = ngx_live_fetch_stream(server, stream); + if (live_stream) { + for (ctx = live_stream->hls_ctx; ctx; ctx = ctx->next) { + if (session->len == ctx->sid.len && + !ngx_strncmp(ctx->sid.data, session->data, session->len)) + { + ngx_log_error(NGX_LOG_DEBUG, ctx->session->log, 0, + "hls-live: fetch_session| session[%p]" + "server %V, stream %V, session %V", + ctx->session, server, stream, session); + return ctx->session; + } + } + } + + return NULL; +} + +static uint64_t +ngx_hls_live_get_fragment_id(ngx_rtmp_session_t *s, uint64_t ts) +{ + ngx_hls_live_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_hls_live_module); + + return ctx->nfrag + ctx->nfrags; +} + +static void +ngx_hls_live_update_playlist(ngx_rtmp_session_t *s) +{ + u_char *p, *end; + ngx_hls_live_ctx_t *ctx; + ngx_hls_live_app_conf_t *hacf; + ngx_hls_live_frag_t *frag; + ngx_uint_t i, max_frag; + ngx_str_t name_part; + ngx_str_t m3u8; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_hls_live_module); + hacf = ngx_rtmp_get_module_app_conf(s, ngx_hls_live_module); + + time(&ctx->playlist_modified_time); + + max_frag = hacf->fraglen / 1000; + + frag = NULL; + + for (i = 0; i < ctx->nfrags; i++) { + frag = ngx_hls_live_get_frag(s, i); + if (frag && frag->duration > max_frag) { + max_frag = (ngx_uint_t) (frag->duration + .5); + } + } + + if (!ctx->playlist) { + ctx->playlist = ngx_create_temp_buf(s->pool, 1024*512); + } + + p = ctx->playlist->pos; + end = ctx->playlist->end; + + p = ngx_slprintf(p, end, + "#EXTM3U\n" + "#EXT-X-VERSION:3\n" + "#EXT-X-MEDIA-SEQUENCE:%uL\n" + "#EXT-X-TARGETDURATION:%ui\n", + ctx->nfrag, max_frag); + +// if (hacf->type == NGX_RTMP_HLS_TYPE_EVENT) { +// p = ngx_slprintf(p, end, "#EXT-X-PLAYLIST-TYPE: EVENT\n"); +// } + + name_part = s->name; + + for (i = 0; i < ctx->nfrags; i++) { + frag = ngx_hls_live_get_frag(s, i); + + if (frag->discont) { + p = ngx_slprintf(p, end, "#EXT-X-DISCONTINUITY\n"); + } + + p = ngx_slprintf(p, end, + "#EXTINF:%.3f,\n" + "%V%V-%uL.ts?session=%V&slot=%d\n", + frag->duration, &hacf->base_url, + &name_part, frag->id, &ctx->sid, ngx_process_slot); + + ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->log, 0, + "hls: fragment nfrag=%uL, n=%ui/%ui, duration=%.3f, " + "discont=%i", + ctx->nfrag, i + 1, ctx->nfrags, frag->duration, frag->discont); + } +/* + for (; frag && i < hacf->winfrags; i++) { + p = ngx_slprintf(p, end, + "#EXTINF:%.3f,\n" + "%V%V-%uL.ts?session=%V&slot=%d\n", + hacf->fraglen/1000.0, &hacf->base_url, + &name_part, i, &ctx->sid, ngx_process_slot); + } +*/ + ctx->playlist->last = p; + m3u8.data = ctx->playlist->pos; + m3u8.len = ctx->playlist->last - ctx->playlist->pos; + + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, "hls-live: playlist| %V", &m3u8); + +} + +static ngx_int_t +ngx_hls_live_close_fragment(ngx_rtmp_session_t *s) +{ + ngx_hls_live_ctx_t *ctx; + ngx_hls_live_app_conf_t *hacf; + + hacf = ngx_rtmp_get_module_app_conf(s, ngx_hls_live_module); + ctx = ngx_rtmp_get_module_ctx(s, ngx_hls_live_module); + + if (ctx == NULL || !ctx->opened) { + return NGX_OK; + } + + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "hls: close fragment id=%uL", ctx->nfrag); + + ctx->opened = 0; + + ngx_hls_live_next_frag(s); + + if (ctx->nfrags >= hacf->minfrags && !ctx->playing) + { + ctx->playing = 1; + ngx_rtmp_fire_event(s, NGX_MPEGTS_MSG_M3U8, NULL, NULL); + } + + return NGX_OK; +} + + +void +ngx_hls_live_free_frag(ngx_hls_live_frag_t *frag) +{ + ngx_mpegts_frame_t *frame; + ngx_uint_t i; + + frag->ref--; + + if (frag->ref > 0) { + return; + } + + ngx_log_error(NGX_LOG_DEBUG, ngx_cycle->log, 0, + "hls-live: free_frag| frag[%p] ref %ui", frag, frag->ref); + + for (i = 0; i < frag->content_last; ++i) { + frame = frag->content[i]; + if (frame) { + ngx_rtmp_shared_free_mpegts_frame(frame); + } + } + + frag->next = ngx_hls_live_main_conf->free_frag; + ngx_hls_live_main_conf->free_frag = frag; +} + + +static ngx_hls_live_frag_t* +ngx_hls_live_create_frag(ngx_rtmp_session_t *s) { + ngx_hls_live_frag_t *frag; + + if (ngx_hls_live_main_conf->free_frag) { + frag = ngx_hls_live_main_conf->free_frag; + ngx_hls_live_main_conf->free_frag = + ngx_hls_live_main_conf->free_frag->next; + + ngx_memzero(frag, sizeof(ngx_hls_live_frag_t) + + sizeof(ngx_mpegts_frame_t*) * s->out_queue); + } else { + frag = ngx_pcalloc(ngx_hls_live_main_conf->pool, + sizeof(ngx_hls_live_frag_t) + + sizeof(ngx_mpegts_frame_t*) * s->out_queue); + } + + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "hls-live: create_frag| create frag[%p]", frag); + + return frag; +} + + +static ngx_int_t +ngx_hls_live_open_fragment(ngx_rtmp_session_t *s, uint64_t ts, + ngx_int_t discont) +{ + uint64_t id; + ngx_hls_live_ctx_t *ctx; + ngx_hls_live_frag_t **ffrag, *frag; + ngx_hls_live_app_conf_t *hacf; + ngx_mpegts_frame_t *frame; + ngx_chain_t patpmt; + + hacf = ngx_rtmp_get_module_app_conf(s, ngx_hls_live_module); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_hls_live_module); + + if (ctx->opened) { + return NGX_OK; + } + + id = ngx_hls_live_get_fragment_id(s, ts); + + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "hls: open_fragment| create frag[%uL] timestamp %uL", id, ts); + + ffrag = &(ctx->frags[id % (hacf->winfrags * 2 + 1)]); + if (*ffrag) { + ngx_hls_live_free_frag(*ffrag); + } + *ffrag = ngx_hls_live_create_frag(s); + + frag = *ffrag; + + ngx_memzero(frag, sizeof(*frag)); + + frag->ref = 1; + frag->active = 1; + frag->discont = discont; + frag->id = id; + + ctx->opened = 1; + ctx->frag_ts = ts; + + ngx_memzero(&patpmt, sizeof(patpmt)); + patpmt.buf = ngx_create_temp_buf(s->pool, 376); + patpmt.buf->last = ngx_cpymem(patpmt.buf->pos, + ngx_rtmp_mpegts_pat, 188); + + ngx_rtmp_mpegts_gen_pmt(s->vcodec, s->acodec, s->log, patpmt.buf->last); + patpmt.buf->last += 188; + + frame = ngx_rtmp_shared_alloc_mpegts_frame(&patpmt, 1); + + ngx_hls_live_write_frame(s, frame); + + ngx_rtmp_shared_free_mpegts_frame(frame); + + frag->length = 376; + + return NGX_OK; +} + + +static void +ngx_hls_live_timeout(ngx_event_t *ev) +{ + ngx_rtmp_session_t *s; + ngx_hls_live_ctx_t *ctx; + + s = ev->data; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_hls_live_module); + + if (time(NULL) - ctx->last_time > ctx->timeout/1000) { + ngx_log_error(NGX_LOG_ALERT, s->log, 0, "hls-live: timeout| " + "hls live timeout, %D - %D > %D.", time(NULL), ctx->last_time, + ctx->timeout/1000); + + ngx_rtmp_finalize_fake_session(s); + return; + } + + ngx_add_timer(ev, (ctx->timeout + 3000)/2); +} + + +static ngx_int_t +ngx_hls_live_join(ngx_rtmp_session_t *s, u_char *name, unsigned publisher) +{ + ngx_hls_live_ctx_t *ctx; + ngx_live_stream_t *st; + ngx_rtmp_live_app_conf_t *lacf; + ngx_hls_live_app_conf_t *hacf; + + hacf = ngx_rtmp_get_module_app_conf(s, ngx_hls_live_module); + + lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module); + if (lacf == NULL) { + return NGX_ERROR; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_hls_live_module); + if (ctx && ctx->stream) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0, + "mpegts-live: join| already joined"); + return NGX_ERROR; + } + + if (ctx == NULL) { + ctx = ngx_pcalloc(s->pool, sizeof(ngx_hls_live_ctx_t)); + ngx_rtmp_set_ctx(s, ctx, ngx_hls_live_module); + + ctx->frags = ngx_pcalloc(s->pool, + sizeof(ngx_hls_live_frag_t *) * (hacf->winfrags * 2 + 1)); + if (ctx->frags == NULL) { + return NGX_ERROR; + } + } + + ctx->session = s; + + ngx_log_error(NGX_LOG_INFO, s->log, 0, + "mpegts-live: join| join '%s'", name); + + st = s->live_stream; + + if (!(publisher || st->publish_ctx || lacf->idle_streams)) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "mpegts-live: join| stream not found"); + + s->status = 404; + + ngx_rtmp_finalize_session(s); + + return NGX_ERROR; + } + + ctx->stream = st; + ctx->next = st->hls_ctx; + + st->hls_ctx = ctx; + + ctx->ev.data = s; + ctx->ev.handler = ngx_hls_live_timeout; + ctx->ev.log = s->log; + ctx->timeout = hacf->timeout; + + ngx_add_timer(&ctx->ev, (ctx->timeout + 3000)/2); + + return NGX_OK; +} + +static ngx_int_t +ngx_hls_live_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) +{ + ngx_hls_live_app_conf_t *hacf; + ngx_hls_live_ctx_t *ctx; +// ngx_rtmp_codec_ctx_t *codec_ctx; + + hacf = ngx_rtmp_get_module_app_conf(s, ngx_hls_live_module); + if (hacf == NULL || !hacf->hls || s->live_type != NGX_HLS_LIVE) { + goto next; + } + + if (s->interprocess || s->live_type != NGX_HLS_LIVE) { + goto next; + } + + if (ngx_hls_live_join(s, v->name, 0) == NGX_ERROR) { + return NGX_ERROR; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_hls_live_module); + + ctx->sid.len = ngx_strlen(v->session); + ctx->sid.data = ngx_pcalloc(s->pool, ctx->sid.len); + ngx_memcpy(ctx->sid.data, v->session, ctx->sid.len); +/* + if (s->live_stream->publish_ctx) { + + codec_ctx = ngx_rtmp_get_module_ctx(s->live_stream->publish_ctx->session, + ngx_rtmp_codec_module); + + ngx_mpegts_gop_link(s->live_stream->publish_ctx->session, + s, hacf->playlen, hacf->playlen); + ngx_hls_live_update(s, codec_ctx); + } +*/ +next: + return next_play(s, v); +} + + +static ngx_int_t +ngx_hls_live_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v) +{ + ngx_hls_live_app_conf_t *hacf; + ngx_hls_live_ctx_t *ctx, **cctx; + ngx_uint_t i; + ngx_hls_live_frag_t *frag; + + hacf = ngx_rtmp_get_module_app_conf(s, ngx_hls_live_module); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_hls_live_module); + + if (hacf == NULL || !hacf->hls || ctx == NULL) { + goto next; + } + + if (ctx->ev.timer_set) { + ngx_del_timer(&ctx->ev); + } + + if (ctx->stream == NULL) { + goto next; + } + + ngx_rtmp_fire_event(s, NGX_MPEGTS_MSG_CLOSE, NULL, NULL); + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0, + "hls: close stream"); + + ngx_hls_live_close_fragment(s); + + for (i = 0; i < 2 * hacf->winfrags + 1; i++) { + frag = ctx->frags[i % (hacf->winfrags * 2 + 1)]; + if (frag) { + ngx_hls_live_free_frag(frag); + } + } + + for (cctx = &ctx->stream->hls_ctx; *cctx; cctx = &(*cctx)->next) { + if (*cctx == ctx) { + *cctx = ctx->next; + break; + } + } + + if (ctx->stream->hls_ctx) { + ctx->stream = NULL; + goto next; + } + +next: + return next_close_stream(s, v); +} + + +static void +ngx_hls_live_update_fragment(ngx_rtmp_session_t *s, uint64_t ts, + ngx_int_t boundary) +{ + ngx_hls_live_ctx_t *ctx; + ngx_hls_live_app_conf_t *hacf; + ngx_hls_live_frag_t *frag; + ngx_msec_t ts_frag_len; + ngx_int_t same_frag, force,discont; + int64_t d; + + hacf = ngx_rtmp_get_module_app_conf(s, ngx_hls_live_module); + ctx = ngx_rtmp_get_module_ctx(s, ngx_hls_live_module); + frag = NULL; + force = 0; + discont = 1; + + if (ctx->opened) { + frag = ngx_hls_live_get_frag(s, ctx->nfrags); + d = (int64_t) (ts - ctx->frag_ts); + + if (d > (int64_t) hacf->max_fraglen * 90 || d < -90000) { + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "hls: force fragment split: %.3f sec, ", d / 90000.); + force = 1; +// if (!boundary) { + discont = 0; +// } + } else { + frag->duration = (ts - ctx->frag_ts) / 90000.; + discont = 0; + } + } + + switch (hacf->slicing) { + case NGX_RTMP_HLS_SLICING_PLAIN: + if (frag && frag->duration < hacf->fraglen / 1000.) { + boundary = 0; + } + break; + + case NGX_RTMP_HLS_SLICING_ALIGNED: + + ts_frag_len = hacf->fraglen * 90; + same_frag = ctx->frag_ts / ts_frag_len == ts / ts_frag_len; + + if (frag && same_frag) { + boundary = 0; + } + + if (frag == NULL && (ctx->frag_ts == 0 || same_frag)) { + ctx->frag_ts = ts; + boundary = 0; + } + + break; + } + + if (boundary || force) { + ngx_hls_live_close_fragment(s); + ngx_hls_live_open_fragment(s, ts, discont); + } +} + + +static ngx_int_t +ngx_hls_live_write_frame(ngx_rtmp_session_t *s, ngx_mpegts_frame_t *frame) +{ + ngx_hls_live_frag_t *frag; + ngx_hls_live_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_hls_live_module); + + frag = ngx_hls_live_get_frag(s, ctx->nfrags); + + frag->length += frame->length; + + frag->content[frag->content_last] = frame; + frag->content_last = ngx_hls_live_next(s, frag->content_last); + time(&frag->last_modified_time); + + ngx_rtmp_shared_acquire_frame(frame); + + return NGX_OK; +} + + +static ngx_int_t +ngx_hls_live_update(ngx_rtmp_session_t *s, ngx_rtmp_codec_ctx_t *codec_ctx) +{ + ngx_hls_live_ctx_t *ctx; + ngx_mpegts_frame_t *frame; + ngx_int_t boundary; + ngx_buf_t *b; + + b = NULL; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_hls_live_module); + + while (s->out_pos != s->out_last) { + + frame = s->mpegts_out[s->out_pos]; +#if 1 + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "hls-live: update| " + "frame[%p] pos[%O] last[%O] pts[%uL] type [%d], key %d, opened %d", + frame, s->out_pos, s->out_last,frame->pts, + frame->type, frame->key, ctx->opened); +#endif + boundary = 0; + + if (frame->type == NGX_MPEGTS_MSG_AUDIO) { + boundary = codec_ctx->avc_header == NULL; + } else if (frame->type == NGX_MPEGTS_MSG_VIDEO) { + b = ctx->aframe; + boundary = frame->key && + (codec_ctx->aac_header == NULL || !ctx->opened || + (b && b->last > b->pos)); + } else { + return NGX_ERROR; + } + + s->acodec = codec_ctx->audio_codec_id; + s->vcodec = codec_ctx->video_codec_id; + + ngx_hls_live_update_fragment(s, frame->pts, boundary); + + if (!ctx->opened) { + break; + } + + ngx_hls_live_write_frame(s, frame); + + ngx_rtmp_shared_free_mpegts_frame(frame); + + ++s->out_pos; + s->out_pos %= s->out_queue; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_hls_live_av(ngx_rtmp_session_t *s, ngx_mpegts_frame_t *frame) +{ + ngx_live_stream_t *live_stream; + ngx_hls_live_ctx_t *ctx; + ngx_rtmp_session_t *ss; + ngx_rtmp_codec_ctx_t *codec_ctx; + ngx_hls_live_app_conf_t *hacf; + + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + hacf = ngx_rtmp_get_module_app_conf(s, ngx_hls_live_module); + + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "hls-live: av| pts[%uL] type [%d] key[%d]", + frame->dts/90, frame->type, frame->key); + + live_stream = s->live_stream; + for (ctx = live_stream->hls_ctx; ctx; ctx = ctx->next) { + ss = ctx->session; + + switch (ngx_mpegts_gop_link(s, ss, hacf->playlen, hacf->playlen)) { + case NGX_DECLINED: + continue; + case NGX_ERROR: + ngx_rtmp_finalize_fake_session(ss); + continue; + default: + break; + } + + ngx_hls_live_update(ss, codec_ctx); + } + + if (frame->type == NGX_MPEGTS_MSG_VIDEO) { + return next_mpegts_video(s, frame); + } else if (frame->type == NGX_MPEGTS_MSG_AUDIO) { + return next_mpegts_audio(s, frame); + } + + return NGX_ERROR; +} + +static void * +ngx_hls_live_create_main_conf(ngx_conf_t *cf) +{ + ngx_hls_live_main_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_hls_live_main_conf_t)); + if (conf == NULL) { + return NULL; + } + + conf->pool = ngx_create_pool(1024, ngx_cycle->log); + ngx_hls_live_main_conf = conf; + + return conf; +} + +static void * +ngx_hls_live_create_app_conf(ngx_conf_t *cf) +{ + ngx_hls_live_app_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_hls_live_app_conf_t)); + if (conf == NULL) { + return NULL; + } + + conf->pool = ngx_create_pool(1024, ngx_cycle->log); + + conf->hls = NGX_CONF_UNSET; + conf->fraglen = NGX_CONF_UNSET_MSEC; + conf->max_fraglen = NGX_CONF_UNSET_MSEC; + conf->playlen = NGX_CONF_UNSET_MSEC; + conf->slicing = NGX_CONF_UNSET_UINT; + conf->type = NGX_CONF_UNSET_UINT; + conf->audio_buffer_size = NGX_CONF_UNSET_SIZE; + conf->cleanup = NGX_CONF_UNSET; + conf->timeout = NGX_CONF_UNSET_MSEC; + conf->minfrags = NGX_CONF_UNSET_UINT; + + return conf; +} + + +static char * +ngx_hls_live_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_hls_live_app_conf_t *prev = parent; + ngx_hls_live_app_conf_t *conf = child; + + ngx_conf_merge_value(conf->hls, prev->hls, 0); + ngx_conf_merge_msec_value(conf->fraglen, prev->fraglen, 5000); + ngx_conf_merge_msec_value(conf->max_fraglen, prev->max_fraglen, + conf->fraglen * 10); + ngx_conf_merge_msec_value(conf->playlen, prev->playlen, conf->fraglen * 3); + ngx_conf_merge_uint_value(conf->slicing, prev->slicing, + NGX_RTMP_HLS_SLICING_PLAIN); + ngx_conf_merge_value(conf->cleanup, prev->cleanup, 1); + ngx_conf_merge_str_value(conf->base_url, prev->base_url, ""); + ngx_conf_merge_uint_value(conf->minfrags, prev->minfrags, 2); + + conf->timeout = conf->playlen * 3; + + if (conf->fraglen) { + conf->winfrags = conf->playlen / conf->fraglen; + } + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_hls_live_postconfiguration(ngx_conf_t *cf) +{ + next_play = ngx_rtmp_play; + ngx_rtmp_play = ngx_hls_live_play; + + next_close_stream = ngx_rtmp_close_stream; + ngx_rtmp_close_stream = ngx_hls_live_close_stream; + + next_mpegts_video = ngx_mpegts_video; + ngx_mpegts_video = ngx_hls_live_av; + + next_mpegts_audio = ngx_mpegts_audio; + ngx_mpegts_audio = ngx_hls_live_av; + + return NGX_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/mpegts/ngx_hls_live_module.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/mpegts/ngx_hls_live_module.h new file mode 100644 index 0000000000..b019c886a4 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/mpegts/ngx_hls_live_module.h @@ -0,0 +1,95 @@ + +/* + * Copyright (C) Pingo (cczjp89@gmail.com) + */ + +#ifndef _NGX_HLS_LIVE_MODULE_H_INCLUDE_ +#define _NGX_HLS_LIVE_MODULE_H_INCLUDE_ + +#define ngx_hls_live_next(s, pos) ((pos + 1) % s->out_queue) +#define ngx_hls_live_prev(s, pos) (pos == 0 ? s->out_queue - 1 : pos - 1) + +typedef struct ngx_hls_live_frag_s ngx_hls_live_frag_t; +typedef struct ngx_hls_live_play_s ngx_hls_live_play_t; + +struct ngx_hls_live_play_s { + ngx_str_t name; + /* connection parameters */ + ngx_rtmp_addr_conf_t *addr_conf; + ngx_str_t app; + ngx_str_t stream; + ngx_str_t args; + ngx_str_t flashver; + ngx_str_t swf_url; + ngx_str_t tc_url; + uint32_t acodecs; + uint32_t vcodecs; + ngx_str_t page_url; + ngx_str_t domain; + ngx_str_t serverid; + ngx_log_t *log; +}; + +struct ngx_hls_live_frag_s { + ngx_uint_t ref; + ngx_hls_live_frag_t *next; + time_t last_modified_time; + uint64_t id; + uint64_t key_id; + double duration; + unsigned active:1; + unsigned discont:1; /* before */ + ngx_uint_t length; + ngx_chain_t *out; + ngx_uint_t content_last; + ngx_uint_t content_pos; + ngx_mpegts_frame_t *content[0]; +}; + +struct ngx_hls_live_ctx_s { + unsigned opened:1; + unsigned playing:1; + + ngx_buf_t *patpmt; + ngx_rtmp_session_t *session; + + ngx_str_t sid; + ngx_live_stream_t *stream; + ngx_str_t name; + + uint64_t nfrag; + uint64_t frag_ts; + uint64_t key_id; + ngx_uint_t nfrags; + ngx_hls_live_frag_t **frags; /* circular 2 * winfrags + 1 */ + ngx_hls_live_frag_t *frag; + + ngx_uint_t audio_cc; + ngx_uint_t video_cc; + ngx_uint_t key_frags; + + uint64_t aframe_base; + uint64_t aframe_num; + + ngx_buf_t *aframe; + uint64_t aframe_pts; + ngx_event_t ev; + ngx_msec_t timeout; + ngx_msec_t last_time; + time_t playlist_modified_time; + ngx_buf_t *playlist; + ngx_hls_live_ctx_t *next; +}; + +ngx_int_t ngx_hls_live_write_playlist(ngx_rtmp_session_t *s, ngx_buf_t *out, + time_t *last_modified_time); +ngx_hls_live_frag_t* ngx_hls_live_find_frag(ngx_rtmp_session_t *s, + ngx_str_t *name); +ngx_chain_t* ngx_hls_live_prepare_frag(ngx_rtmp_session_t *s, + ngx_hls_live_frag_t *frag); +void ngx_hls_live_free_frag(ngx_hls_live_frag_t *frag); +ngx_rtmp_session_t* ngx_hls_live_fetch_session(ngx_str_t *server, + ngx_str_t *stream, ngx_str_t *session); +void ngx_rtmp_shared_acquire_frag(ngx_hls_live_frag_t *frag); + +#endif diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/mpegts/ngx_mpegts_gop_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/mpegts/ngx_mpegts_gop_module.c new file mode 100644 index 0000000000..0827da9dee --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/mpegts/ngx_mpegts_gop_module.c @@ -0,0 +1,731 @@ + +/* + * Copyright (C) Pingo (cczjp89@gmail.com) + */ + +#include +#include +#include "ngx_rtmp.h" +#include "ngx_rtmp_codec_module.h" +#include "ngx_rtmp_live_module.h" +#include "ngx_mpegts_live_module.h" + + +static ngx_rtmp_close_stream_pt next_close_stream; + +static ngx_mpegts_video_pt next_mpegts_video; +static ngx_mpegts_audio_pt next_mpegts_audio; + +static void *ngx_mpegts_gop_create_app_conf(ngx_conf_t *cf); +static char *ngx_mpegts_gop_merge_app_conf(ngx_conf_t *cf, void *parent, + void *child); + +static ngx_int_t ngx_mpegts_gop_postconfiguration(ngx_conf_t *cf); + +#define ngx_mpegts_gop_next(s, pos) ((pos + 1) % s->out_queue) +#define ngx_mpegts_gop_prev(s, pos) (pos == 0 ? s->out_queue - 1 : pos - 1) + +typedef struct { + /* publisher: head of cache + * player: cache send position of publisher's out + */ + size_t gop_pos; + /* tail of cache */ + size_t gop_last; + /* 0 for not send, 1 for sending, 2 for sent */ + ngx_flag_t send_gop; + + ngx_mpegts_frame_t *keyframe; + + ngx_uint_t meta_version; + + uint64_t first_timestamp; + uint64_t current_timestamp; + + ngx_uint_t base_type; + + /* only for publisher, must at last of ngx_mpegts_gop_ctx_t */ + ngx_mpegts_frame_t *cache[]; +} ngx_mpegts_gop_ctx_t; + +typedef struct { + ngx_msec_t cache_time; + ngx_msec_t roll_back; + ngx_msec_t one_off_send; + ngx_flag_t low_latency; + ngx_flag_t send_all; + ngx_msec_t fix_timestamp; + ngx_flag_t zero_start; +} ngx_mpegts_gop_app_conf_t; + + +static ngx_command_t ngx_mpegts_gop_commands[] = { + + { ngx_string("mpegts_cache_time"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_mpegts_gop_app_conf_t, cache_time), + NULL }, + + { ngx_string("mpegts_roll_back"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_mpegts_gop_app_conf_t, roll_back), + NULL }, + + { ngx_string("mpegts_one_off_send"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_mpegts_gop_app_conf_t, one_off_send), + NULL }, + + { ngx_string("mpegts_low_latency"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_mpegts_gop_app_conf_t, low_latency), + NULL }, + + { ngx_string("mpegts_send_all"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_mpegts_gop_app_conf_t, send_all), + NULL }, + + { ngx_string("mpegts_fix_timestamp"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_mpegts_gop_app_conf_t, fix_timestamp), + NULL }, + + { ngx_string("mpegts_zero_start"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_mpegts_gop_app_conf_t, zero_start), + NULL }, + + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_mpegts_gop_module_ctx = { + NULL, /* preconfiguration */ + ngx_mpegts_gop_postconfiguration, /* postconfiguration */ + NULL, /* create main configuration */ + NULL, /* init main configuration */ + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + ngx_mpegts_gop_create_app_conf, /* create app configuration */ + ngx_mpegts_gop_merge_app_conf /* merge app configuration */ +}; + + +ngx_module_t ngx_mpegts_gop_module = { + NGX_MODULE_V1, + &ngx_mpegts_gop_module_ctx, /* module context */ + ngx_mpegts_gop_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void * +ngx_mpegts_gop_create_app_conf(ngx_conf_t *cf) +{ + ngx_mpegts_gop_app_conf_t *gacf; + + gacf = ngx_pcalloc(cf->pool, sizeof(ngx_mpegts_gop_app_conf_t)); + if (gacf == NULL) { + return NULL; + } + + gacf->cache_time = NGX_CONF_UNSET_MSEC; + gacf->roll_back = NGX_CONF_UNSET_MSEC; + gacf->one_off_send = NGX_CONF_UNSET_MSEC; + gacf->low_latency = NGX_CONF_UNSET; + gacf->send_all = NGX_CONF_UNSET; + gacf->fix_timestamp = NGX_CONF_UNSET_MSEC; + gacf->zero_start = NGX_CONF_UNSET; + + return gacf; +} + +static char * +ngx_mpegts_gop_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_mpegts_gop_app_conf_t *prev = parent; + ngx_mpegts_gop_app_conf_t *conf = child; + + ngx_conf_merge_msec_value(conf->cache_time, prev->cache_time, 0); + ngx_conf_merge_msec_value(conf->roll_back, prev->roll_back, conf->cache_time); + ngx_conf_merge_msec_value(conf->one_off_send, prev->one_off_send, 3000); + ngx_conf_merge_value(conf->low_latency, prev->low_latency, 0); + ngx_conf_merge_value(conf->send_all, prev->send_all, 0); + ngx_conf_merge_msec_value(conf->fix_timestamp, prev->fix_timestamp, 10000); + ngx_conf_merge_value(conf->zero_start, prev->zero_start, 0); + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_mpegts_gop_link_frame(ngx_rtmp_session_t *s, ngx_mpegts_frame_t *frame) +{ + ngx_uint_t nmsg; + + if (frame == NULL) { + return NGX_OK; + } + + nmsg = (s->out_last - s->out_pos) % s->out_queue + 1; + + if (nmsg >= s->out_queue) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "link frame nmsg(%ui) >= out_queue(%O)", nmsg, s->out_queue); + return NGX_AGAIN; + } + +#if 0 + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "link frame pos[%O] last[%O], pts[%ud]", + s->out_pos, s->out_last, frame->pts); +#endif + + s->mpegts_out[s->out_last] = frame; + s->out_last = ngx_mpegts_gop_next(s, s->out_last); + + ngx_rtmp_shared_acquire_mpegts_frame(frame); + + return NGX_OK; +} + + +static void +ngx_mpegts_gop_reset_gop(ngx_rtmp_session_t *s, ngx_mpegts_gop_ctx_t *ctx, + ngx_mpegts_frame_t *frame) +{ + ngx_mpegts_gop_app_conf_t *gacf; + ngx_mpegts_frame_t *f, *next_keyframe; + size_t pos; + ngx_uint_t nmsg; + ngx_msec_t cache_time; + + pos = ctx->gop_pos; + + f = ctx->cache[pos]; + if (f == NULL) { + return; + } + + gacf = ngx_rtmp_get_module_app_conf(s, ngx_mpegts_gop_module); + + cache_time = gacf->cache_time > gacf->roll_back ? + gacf->cache_time : gacf->roll_back; + + /* only audio in cache */ + if (ctx->keyframe == NULL) { + if (frame->pts - ctx->cache[ctx->gop_pos]->pts > cache_time * 90) { + ngx_rtmp_shared_free_mpegts_frame(f); + ctx->cache[ctx->gop_pos] = NULL; + ctx->gop_pos = ngx_mpegts_gop_next(s, ctx->gop_pos); + } + + return; + } + + /* only video of video + audio */ + next_keyframe = ctx->keyframe->next; + + /* only one gop in cache */ + if (next_keyframe == NULL) { + return; + } + + nmsg = (ctx->gop_last - ctx->gop_pos) % s->out_queue + 2; + if (nmsg >= s->out_queue) { + goto reset; + } + + if (frame->type == NGX_MPEGTS_MSG_AUDIO) { + return; + } + + if (frame->type == NGX_MPEGTS_MSG_VIDEO && frame->pts + - next_keyframe->pts < cache_time * 90) + { + return; + } + +reset: + for (pos = ctx->gop_pos; ctx->cache[pos] != next_keyframe; + pos = ngx_mpegts_gop_next(s, pos)) + { + f = ctx->cache[pos]; + + ngx_rtmp_shared_free_mpegts_frame(f); + + ctx->cache[pos] = NULL; + } + + ctx->keyframe = next_keyframe; + ctx->gop_pos = pos; +} + +static void +ngx_mpegts_gop_print_cache(ngx_rtmp_session_t *s, ngx_mpegts_gop_ctx_t *ctx) +{ +#if (NGX_DEBUG) + ngx_mpegts_frame_t *frame; + u_char content[10240], *p; + size_t pos; + + ngx_memzero(content, sizeof(content)); + + p = content; + for (pos = ctx->gop_pos; pos != ctx->gop_last; + pos = ngx_mpegts_gop_next(s, pos)) + { + frame = ctx->cache[pos]; + switch (frame->type) { + case NGX_MPEGTS_MSG_AUDIO: + *p++ = 'A'; + break; + case NGX_MPEGTS_MSG_VIDEO: + *p++ = 'V'; + break; + default: + *p++ = 'O'; + break; + } + + if (frame->key) { + *p++ = 'I'; + } + + *p++ = ' '; + } + + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "[%z %z] %s", ctx->gop_pos, ctx->gop_last, content); +#endif +} + +ngx_int_t +ngx_mpegts_gop_cache(ngx_rtmp_session_t *s, ngx_mpegts_frame_t *frame) +{ + ngx_mpegts_gop_app_conf_t *gacf; + ngx_mpegts_gop_ctx_t *ctx; + ngx_mpegts_frame_t **keyframe; + ngx_uint_t nmsg; + + gacf = ngx_rtmp_get_module_app_conf(s, ngx_mpegts_gop_module); + if (gacf->cache_time == 0) { + return NGX_OK; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_gop_module); + if (ctx == NULL) { + ctx = ngx_pcalloc(s->pool, sizeof(ngx_mpegts_gop_ctx_t) + + s->out_queue * sizeof(ngx_mpegts_frame_t *)); + if (ctx == NULL) { + return NGX_ERROR; + } + ngx_rtmp_set_ctx(s, ctx, ngx_mpegts_gop_module); + } + + nmsg = (ctx->gop_last - ctx->gop_pos) % s->out_queue + 1; + if (nmsg >= s->out_queue) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "cache frame nmsg(%ui) >= out_queue(%z)", nmsg, s->out_queue); + return NGX_AGAIN; + } +#if 0 + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "cache frame: %ud[%d], %ud, %ud", + frame->type, frame->key, frame->pts, frame->length); +#endif + /* first video frame is not intra_frame */ + if (ctx->keyframe == NULL && frame->type == NGX_MPEGTS_MSG_VIDEO + && !frame->key) + { + return NGX_OK; + } + + /* video intra_frame */ + if (frame->key) { + for (keyframe = &ctx->keyframe; *keyframe; + keyframe = &((*keyframe)->next)); + *keyframe = frame; + } + + frame->pos = ctx->gop_last; + ctx->current_timestamp = frame->pts; + ctx->cache[ctx->gop_last] = frame; + ctx->gop_last = ngx_mpegts_gop_next(s, ctx->gop_last); + + ngx_rtmp_shared_acquire_mpegts_frame(frame); + + ngx_mpegts_gop_reset_gop(s, ctx, frame); + + ngx_mpegts_gop_print_cache(s, ctx); + + return NGX_OK; +} + + +static ngx_int_t +ngx_mpegts_gop_send_gop(ngx_rtmp_session_t *s, ngx_rtmp_session_t *ss) +{ + ngx_mpegts_gop_app_conf_t *gacf; + ngx_mpegts_gop_ctx_t *sctx, *ssctx; + ngx_mpegts_frame_t *frame, *keyframe; + size_t pos; + + gacf = ngx_rtmp_get_module_app_conf(s, ngx_mpegts_gop_module); + + sctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_gop_module); + ssctx = ngx_rtmp_get_module_ctx(ss, ngx_mpegts_gop_module); + + /* already send gop */ + if (ssctx->send_gop == 2) { + return NGX_OK; + } + + //***************************************************************** + // + // TODO: send pat pmt + // + //***************************************************************** + + /* link frame in s to ss */ + if (ssctx->send_gop == 0) { + ssctx->gop_pos = sctx->gop_pos; + if (sctx->cache[ssctx->gop_pos] == NULL) { + return NGX_AGAIN; + } + + ssctx->send_gop = 1; + ssctx->first_timestamp = sctx->cache[ssctx->gop_pos]->pts; + ssctx->base_type = sctx->cache[ssctx->gop_pos]->type; + } else { + if (sctx->cache[ssctx->gop_pos] == NULL) { + ssctx->gop_pos = sctx->gop_pos; + } + } + + frame = NULL; + + keyframe = sctx->keyframe; + while (ss->roll_back && keyframe && + ((sctx->current_timestamp - keyframe->pts) > ss->roll_back * 90)) + { + ngx_log_error(NGX_LOG_INFO, s->log, 0, + "rtmp-gop: send_gop| curr %D - k %D, %D", + sctx->current_timestamp, keyframe->pts, ss->roll_back * 90); + frame = keyframe; + keyframe = keyframe->next; + } + + if (frame == NULL) { + pos = ssctx->gop_pos; + frame = sctx->cache[pos]; + } else { + pos = frame->pos; + } + + while (frame) { + + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "send gop link %D, type %d, curr %D", + frame->pts/90, frame->type, sctx->current_timestamp/90); + if (ngx_mpegts_gop_link_frame(ss, frame) == NGX_AGAIN) { + break; + } + + if (!gacf->send_all && frame->type == ssctx->base_type && + frame->pts - ssctx->first_timestamp >= gacf->one_off_send * 90) + { + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "gone %D, type %d, first %D, curr %D, send %D", + frame->pts/90, frame->type, ssctx->first_timestamp/90, + sctx->current_timestamp/90, gacf->one_off_send); + + ssctx->send_gop = 2; + pos = ngx_mpegts_gop_next(s, pos); + break; + } + + pos = ngx_mpegts_gop_next(s, pos); + frame = sctx->cache[pos]; + } + + if (frame == NULL) { /* send all frame in cache */ + ssctx->send_gop = 2; + } + + ssctx->gop_pos = pos; + ngx_rtmp_send_message(ss, NULL, 0); + + return NGX_AGAIN; +} + +ngx_int_t +ngx_mpegts_gop_send(ngx_rtmp_session_t *s, ngx_rtmp_session_t *ss) +{ + ngx_mpegts_gop_app_conf_t *gacf; + ngx_mpegts_gop_ctx_t *sctx, *ssctx; + ngx_mpegts_frame_t *frame; + + gacf = ngx_rtmp_get_module_app_conf(s, ngx_mpegts_gop_module); + if (gacf->cache_time == 0) { + return NGX_DECLINED; + } + + sctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_gop_module); + if (sctx == NULL) { /* publisher doesn't publish av frame */ + return NGX_DECLINED; + } + + ssctx = ngx_rtmp_get_module_ctx(ss, ngx_mpegts_gop_module); + if (ssctx == NULL) { + ssctx = ngx_pcalloc(ss->pool, sizeof(ngx_mpegts_gop_ctx_t)); + if (ssctx == NULL) { + return NGX_ERROR; + } + ngx_rtmp_set_ctx(ss, ssctx, ngx_mpegts_gop_module); + } + + if (ngx_mpegts_gop_send_gop(s, ss) == NGX_AGAIN) { + return NGX_OK; + } + + if (sctx->cache[ssctx->gop_pos] == NULL) { + ngx_log_error(NGX_LOG_ERR, ss->log, 0, + "mpegts-gop: gop_send| current gop pos is NULL, " + "skip to new postion [pos %d last %d] %d", + sctx->gop_pos, sctx->gop_last, ssctx->gop_pos); + + ssctx->gop_pos = sctx->gop_pos; + } + + frame = sctx->cache[ssctx->gop_pos]; + if (ngx_mpegts_gop_link_frame(ss, frame) == NGX_AGAIN) { + return NGX_AGAIN; + } + + ssctx->gop_pos = ngx_mpegts_gop_next(s, ssctx->gop_pos); + ngx_rtmp_send_message(ss, NULL, 0); + + return NGX_OK; +} + +static ngx_int_t +ngx_mpegts_gop_offset_frames(ngx_rtmp_session_t *s, ngx_rtmp_session_t *ss, + ngx_msec_t time_offset, ngx_msec_t duration) +{ + ngx_mpegts_gop_ctx_t *sctx, *ssctx; + ngx_mpegts_frame_t *frame, *keyframe; + size_t pos; + + sctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_gop_module); + ssctx = ngx_rtmp_get_module_ctx(ss, ngx_mpegts_gop_module); + + /* already send gop */ + if (ssctx->send_gop == 2) { + return NGX_OK; + } + + //***************************************************************** + // + // TODO: send pat pmt + // + //***************************************************************** + + /* link frame in s to ss */ + if (ssctx->send_gop == 0) { + ssctx->gop_pos = sctx->gop_pos; + if (sctx->cache[ssctx->gop_pos] == NULL) { + return NGX_AGAIN; + } + + ssctx->send_gop = 1; + ssctx->first_timestamp = sctx->cache[ssctx->gop_pos]->pts; + ssctx->base_type = sctx->cache[ssctx->gop_pos]->type; + } else { + if (sctx->cache[ssctx->gop_pos] == NULL) { + ssctx->gop_pos = sctx->gop_pos; + } + } + + frame = NULL; + + keyframe = sctx->keyframe; + while (time_offset && keyframe && + ((sctx->current_timestamp - keyframe->pts) > time_offset * 90)) + { + ngx_log_error(NGX_LOG_DEBUG, ss->log, 0, + "mpegts-gop: offset_frames| curr %D - k %D = %D, %D", + sctx->current_timestamp, keyframe->pts, + (sctx->current_timestamp - keyframe->pts) / 90, time_offset); + frame = keyframe; + keyframe = keyframe->next; + ssctx->first_timestamp = frame->pts; + ssctx->base_type = frame->type; + } + + if (frame == NULL) { + pos = ssctx->gop_pos; + frame = sctx->cache[pos]; + } else { + pos = frame->pos; + } + + while (frame) { + + if (ngx_mpegts_gop_link_frame(ss, frame) == NGX_AGAIN) { + break; + } + + if (frame->type == ssctx->base_type && + frame->pts - ssctx->first_timestamp >= duration * 90) + { + ngx_log_error(NGX_LOG_INFO, ss->log, 0, "gone %D, first %D, curr %D", + frame->pts, ssctx->first_timestamp, sctx->current_timestamp); + ssctx->send_gop = 2; + pos = ngx_mpegts_gop_next(s, pos); + break; + } + + pos = ngx_mpegts_gop_next(s, pos); + frame = sctx->cache[pos]; + } + + if (frame == NULL) { /* send all frame in cache */ + ssctx->send_gop = 2; + } + + ssctx->gop_pos = pos; + + return NGX_AGAIN; +} + +ngx_int_t +ngx_mpegts_gop_link(ngx_rtmp_session_t *s, ngx_rtmp_session_t *ss, + ngx_msec_t time_offset, ngx_msec_t duration) +{ + ngx_mpegts_gop_app_conf_t *gacf; + ngx_mpegts_gop_ctx_t *sctx, *ssctx; + ngx_mpegts_frame_t *frame; + + gacf = ngx_rtmp_get_module_app_conf(s, ngx_mpegts_gop_module); + if (gacf->cache_time == 0) { + return NGX_DECLINED; + } + + sctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_gop_module); + if (sctx == NULL) { /* publisher doesn't publish av frame */ + return NGX_DECLINED; + } + + ssctx = ngx_rtmp_get_module_ctx(ss, ngx_mpegts_gop_module); + if (ssctx == NULL) { + ssctx = ngx_pcalloc(ss->pool, sizeof(ngx_mpegts_gop_ctx_t)); + if (ssctx == NULL) { + return NGX_ERROR; + } + ngx_rtmp_set_ctx(ss, ssctx, ngx_mpegts_gop_module); + } + + if (ngx_mpegts_gop_offset_frames(s, ss, time_offset, duration) == NGX_AGAIN) + { + return NGX_OK; + } + + /* new frame is video key frame */ + if (sctx->cache[ssctx->gop_pos] == NULL) { + ngx_log_error(NGX_LOG_ERR, ss->log, 0, + "mpegts-gop: link| current gop pos is NULL, " + "skip to new postion [pos %d last %d] %d", + sctx->gop_pos, sctx->gop_last, ssctx->gop_pos); + + ssctx->gop_pos = sctx->gop_pos; + } + + frame = sctx->cache[ssctx->gop_pos]; + if (ngx_mpegts_gop_link_frame(ss, frame) == NGX_AGAIN) { + return NGX_AGAIN; + } + + ssctx->gop_pos = ngx_mpegts_gop_next(s, ssctx->gop_pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_mpegts_gop_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v) +{ + ngx_mpegts_gop_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_gop_module); + if (ctx == NULL) { + goto next; + } + + if (!s->published) { + goto next; + } + + /* free cache in publisher */ + while (ctx->gop_pos != ctx->gop_last) { + ngx_rtmp_shared_free_mpegts_frame(ctx->cache[ctx->gop_pos]); + ctx->gop_pos = ngx_mpegts_gop_next(s, ctx->gop_pos); + } + +next: + return next_close_stream(s, v); +} + +static ngx_int_t +ngx_mpegts_gop_av(ngx_rtmp_session_t *s, ngx_mpegts_frame_t *frame) +{ + if (frame->type == NGX_MPEGTS_MSG_VIDEO) { + ngx_mpegts_gop_cache(s, frame); + + return next_mpegts_video(s, frame); + } else if (frame->type == NGX_MPEGTS_MSG_AUDIO) { + ngx_mpegts_gop_cache(s, frame); + + return next_mpegts_audio(s, frame); + } + + return NGX_ERROR; +} + +static ngx_int_t +ngx_mpegts_gop_postconfiguration(ngx_conf_t *cf) +{ + next_close_stream = ngx_rtmp_close_stream; + ngx_rtmp_close_stream = ngx_mpegts_gop_close_stream; + + next_mpegts_video = ngx_mpegts_video; + ngx_mpegts_video = ngx_mpegts_gop_av; + + next_mpegts_audio = ngx_mpegts_audio; + ngx_mpegts_audio = ngx_mpegts_gop_av; + + return NGX_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/mpegts/ngx_mpegts_gop_module.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/mpegts/ngx_mpegts_gop_module.h new file mode 100644 index 0000000000..3b1ca53074 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/mpegts/ngx_mpegts_gop_module.h @@ -0,0 +1,19 @@ + +/* + * Copyright (C) Pingo (cczjp89@gmail.com) + */ + +#ifndef _NGX_MPEGTS_GOP_MODULE_H_INCLUDE_ +#define _NGX_MPEGTS_GOP_MODULE_H_INCLUDE_ + +#include "ngx_rtmp.h" + +ngx_int_t +ngx_mpegts_gop_cache(ngx_rtmp_session_t *s, ngx_mpegts_frame_t *frame); +ngx_int_t +ngx_mpegts_gop_link(ngx_rtmp_session_t *s, ngx_rtmp_session_t *ss, + ngx_msec_t time_offset, ngx_msec_t duration); +ngx_int_t +ngx_mpegts_gop_send(ngx_rtmp_session_t *s, ngx_rtmp_session_t *ss); + +#endif diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/mpegts/ngx_mpegts_http_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/mpegts/ngx_mpegts_http_module.c new file mode 100644 index 0000000000..ce2ac3cb49 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/mpegts/ngx_mpegts_http_module.c @@ -0,0 +1,611 @@ + +/* + * Copyright (C) Pingo (cczjp89@gmail.com) + */ + + +#include +#include +#include +#include +#include "ngx_rtmp.h" +#include "ngx_rtmp_cmd_module.h" +#include "ngx_rbuf.h" +#include "ngx_http_set_header.h" +#include "ngx_rtmp_monitor_module.h" +#include "ngx_mpegts_gop_module.h" +#include "ngx_mpegts_live_module.h" + + +static char *ngx_mpegts_http(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); + +static void *ngx_mpegts_http_create_loc_conf(ngx_conf_t *cf); +static char *ngx_mpegts_http_merge_loc_conf(ngx_conf_t *cf, void *parent, + void *child); + +static ngx_keyval_t ngx_mpegts_http_headers[] = { + { ngx_string("Cache-Control"), ngx_string("no-cache") }, +// { ngx_string("Content-Type"), ngx_string("video/x-ts") }, + { ngx_null_string, ngx_null_string } +}; + +typedef struct { + ngx_rtmp_session_t *session; +} ngx_mpegts_http_ctx_t; + +typedef struct { + ngx_str_t app; + ngx_str_t flashver; + ngx_str_t swf_url; + ngx_str_t tc_url; + ngx_str_t page_url; + + ngx_rtmp_addr_conf_t *addr_conf; +} ngx_mpegts_http_loc_conf_t; + + +static ngx_command_t ngx_mpegts_http_commands[] = { + + { ngx_string("ts_live"), + NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12, + ngx_mpegts_http, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_mpegts_http_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + ngx_mpegts_http_create_loc_conf, /* create location configuration */ + ngx_mpegts_http_merge_loc_conf /* merge location configuration */ +}; + + +ngx_module_t ngx_mpegts_http_module = { + NGX_MODULE_V1, + &ngx_mpegts_http_module_ctx, /* module context */ + ngx_mpegts_http_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_int_t +ngx_mpegts_http_send_header(ngx_http_request_t *r) +{ + ngx_int_t rc; + ngx_keyval_t *h; + ngx_chain_t out; + ngx_mpegts_http_ctx_t *ctx; + ngx_rtmp_session_t *s, *ps; + + if (r->header_sent) { + return NGX_OK; + } + + ctx = ngx_http_get_module_ctx(r, ngx_mpegts_http_module); + s = ctx->session; + ps = s->live_stream->publish_ctx->session; + + r->headers_out.status = NGX_HTTP_OK; + r->keepalive = 0; /* set Connection to closed */ + + h = ngx_mpegts_http_headers; + while (h->key.len) { + rc = ngx_http_set_header_out(r, &h->key, &h->value); + if (rc != NGX_OK) { + return rc; + } + ++h; + } + + rc = ngx_http_send_header(r); + if (rc == NGX_ERROR || rc > NGX_OK) { + return rc; + } + + ngx_memzero(&out, sizeof(out)); + out.buf = ngx_create_temp_buf(s->pool, 376); + out.buf->last = ngx_cpymem(out.buf->pos, + ngx_rtmp_mpegts_pat, 188); + + ngx_rtmp_mpegts_gen_pmt(ps->vcodec, ps->acodec, s->log, out.buf->last); + out.buf->last += 188; + + out.buf->flush = 1; + out.buf->memory = 1; + + return ngx_http_output_filter(r, &out); +} + +static void +ngx_mpegts_http_write_handler(ngx_http_request_t *r) +{ + ngx_mpegts_http_ctx_t *ctx; + ngx_rtmp_session_t *s; + ngx_event_t *wev; + size_t present, sent; + ngx_int_t rc; + ngx_chain_t *cl; + + wev = r->connection->write; + + if (r->connection->destroyed) { + return; + } + + ctx = ngx_http_get_module_ctx(r, ngx_mpegts_http_module); + s = ctx->session; + + if (wev->timedout) { + ngx_log_error(NGX_LOG_INFO, s->log, NGX_ETIMEDOUT, + "mpegts-http: write_handler| client timed out"); + r->connection->timedout = 1; + s->finalize_reason = NGX_LIVE_FLV_SEND_TIMEOUT; + if (r->header_sent) { + ngx_http_finalize_request(r, NGX_HTTP_REQUEST_TIME_OUT); + } else { + r->error_page = 1; + ngx_http_finalize_request(r, NGX_HTTP_SERVICE_UNAVAILABLE); + } + + return; + } + + if (wev->timer_set) { + ngx_del_timer(wev); + } + + if (ngx_rtmp_core_main_conf->fast_reload && (ngx_exiting || ngx_terminate)) { + r->error_page = 1; + ngx_http_finalize_request(r, NGX_HTTP_SERVICE_UNAVAILABLE); + return; + } + + if (ngx_rtmp_prepare_merge_frame(s) == NGX_ERROR) { + ngx_http_finalize_request(r, NGX_ERROR); + return; + } + + if (s->out_chain) { + rc = ngx_mpegts_http_send_header(r); + if (rc == NGX_ERROR || rc > NGX_OK) { + s->finalize_reason = NGX_LIVE_FLV_SEND_ERR; + ngx_http_finalize_request(r, rc); + return; + } + } + + while (s->out_chain) { + present = r->connection->sent; + + if (r->connection->buffered) { + rc = ngx_http_output_filter(r, NULL); + } else { + rc = ngx_http_output_filter(r, s->out_chain); + } + + sent = r->connection->sent - present; + + ngx_rtmp_update_bandwidth(&ngx_rtmp_bw_out, sent); + + if (rc == NGX_AGAIN) { + ngx_add_timer(wev, s->timeout); + if (ngx_handle_write_event(wev, 0) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, ngx_errno, + "mpegts-http: write_handler| write event failed"); + ngx_http_finalize_request(r, NGX_ERROR); + } + return; + } + + if (rc == NGX_ERROR) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, ngx_errno, + "mpegts-http: write_handler| send error"); + s->finalize_reason = NGX_LIVE_FLV_SEND_ERR; + ngx_http_finalize_request(r, NGX_ERROR); + return; + } + + /* NGX_OK */ + for (cl = s->out_chain; cl;) { + s->out_chain = cl->next; + ngx_free_chain(s->pool, cl); + cl = s->out_chain; + } + + if (ngx_rtmp_prepare_merge_frame(s) == NGX_ERROR) { + ngx_http_finalize_request(r, NGX_ERROR); + return; + } + } + + if (wev->active) { + ngx_del_event(wev, NGX_WRITE_EVENT, 0); + } +} + + +static void +ngx_mpegts_http_parse_url(ngx_http_request_t *r, ngx_str_t *app, + ngx_str_t *name) +{ + u_char *p, *end, *pos; + + p = r->uri.data + 1; /* skip '/' */ + end = r->uri.data + r->uri.len; + app->data = p; + + pos = (u_char *) ngx_strnstr(p, ".ts", end - p); + if (pos) { + end = pos; + } + + p = (u_char *) ngx_strnstr(p, "/", end - p); + while (p) { + name->data = p; + p = (u_char *) ngx_strnstr(p + 1, "/", end - p); + } + + if (name->data == NULL) { + return; + } + + app->len = name->data - app->data; + + ++name->data; + name->len = end - name->data; +} + +static ngx_int_t +ngx_mpegts_http_parse(ngx_http_request_t *r, ngx_rtmp_session_t *s, + ngx_rtmp_play_t *v) +{ + ngx_mpegts_http_loc_conf_t *hflcf; + ngx_str_t app, stream; + size_t tcurl_len; + u_char *p; + + hflcf = ngx_http_get_module_loc_conf(r, ngx_mpegts_http_module); + + ngx_memzero(&app, sizeof(ngx_str_t)); + ngx_memzero(&stream, sizeof(ngx_str_t)); + + ngx_mpegts_http_parse_url(r, &app, &stream); + + if (app.len == 0 || stream.len == 0 || stream.len > NGX_RTMP_MAX_NAME) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "mpegts-http: http_parse| url error: %V", &r->uri); + return NGX_HTTP_BAD_REQUEST; + } + + if (hflcf->app.len) { + app = hflcf->app; + } + + ngx_http_arg(r, (u_char *) "app", 3, &app); + + s->app = app; + + if (ngx_http_arg(r, (u_char *) "flashver", 8, &s->flashver) != NGX_OK) { + s->flashver = hflcf->flashver; + } + + /* tc_url */ +#if (NGX_HTTP_SSL) + if (r->connection->ssl) { + tcurl_len = sizeof("https://") - 1; + } else +#endif + { + tcurl_len = sizeof("http://") - 1; + } + tcurl_len += r->headers_in.server.len + 1 + app.len; + + s->tc_url.len = tcurl_len; + s->tc_url.data = ngx_pcalloc(r->pool, tcurl_len); + if (s->tc_url.data == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + p = s->tc_url.data; + +#if (NGX_HTTP_SSL) + if (r->connection->ssl) { + p = ngx_cpymem(p, "https://", sizeof("https://") - 1); + } else +#endif + { + p = ngx_cpymem(p, "http://", sizeof("http://") - 1); + } + + p = ngx_cpymem(p, r->headers_in.server.data, r->headers_in.server.len); + *p++ = '/'; + p = ngx_cpymem(p, app.data, app.len); + + /* page_url */ + if (r->headers_in.referer) { + s->page_url = r->headers_in.referer->value; + } else { + s->page_url = hflcf->page_url; + } + + s->acodecs = 0x0DF7; + s->vcodecs = 0xFC; + + ngx_memcpy(v->name, stream.data, stream.len); + + if (r->args.len) { + ngx_memcpy(v->args, r->args.data, + ngx_min(r->args.len, NGX_RTMP_MAX_ARGS)); + } + + ngx_rtmp_cmd_middleware_init(s); + + return NGX_OK; +} + +static void +ngx_mpegts_http_cleanup(void *data) +{ + ngx_http_request_t *r; + ngx_mpegts_http_ctx_t *ctx; + + r = data; + + ctx = ngx_http_get_module_ctx(r, ngx_mpegts_http_module); + + if (ctx == NULL) { + return; + } + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "mpegts-http: cleanup| cleanup"); + + if (ctx->session) { + ctx->session->request = NULL; + + if (ctx->session->finalize_reason == 0) { + ctx->session->finalize_reason = r->connection->read->error? + NGX_LIVE_FLV_RECV_ERR : NGX_LIVE_NORMAL_CLOSE; + } + + ngx_rtmp_finalize_fake_session(ctx->session); + } +} + +static ngx_chain_t * +ngx_mpegts_http_prepare_out_chain(ngx_rtmp_session_t *s) +{ + ngx_mpegts_frame_t *frame; + ngx_chain_t *head, **ll, *cl; + + frame = NULL; + head = NULL; + + if (s->out_pos != s->out_last) { + frame = s->mpegts_out[s->out_pos]; + } + + /* no frame to send */ + if (frame == NULL) { + return NULL; + } + + ll = &head; + + for (cl = frame->chain; cl; cl = cl->next) { + (*ll) = ngx_get_chainbuf(0, 0); + if (*ll == NULL) { + goto falied; + } + (*ll)->buf->pos = cl->buf->pos; + (*ll)->buf->last = cl->buf->last; + if (!(*ll)->next) { + (*ll)->buf->flush = 1; + } + + ll = &(*ll)->next; + } + + return head; + +falied: + ngx_put_chainbufs(head); + + ngx_rtmp_finalize_session(s); + return NULL; +} + +static ngx_int_t +ngx_mpegts_http_handler(ngx_http_request_t *r) +{ + ngx_mpegts_http_loc_conf_t *hflcf; + ngx_mpegts_http_ctx_t *ctx; + ngx_rtmp_session_t *s; + ngx_rtmp_play_t v; + ngx_int_t rc; + ngx_uint_t n; + ngx_rtmp_core_srv_conf_t *cscf; + ngx_rtmp_core_app_conf_t **cacfp; + ngx_http_cleanup_t *cln; + ngx_rtmp_core_main_conf_t *cmcf; + + ctx = ngx_pcalloc(r->pool, sizeof(ngx_mpegts_http_ctx_t)); + if (ctx == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + ngx_http_set_ctx(r, ctx, ngx_mpegts_http_module); + + /* cleanup handler */ + cln = ngx_http_cleanup_add(r, 0); + if (cln == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + cln->handler = ngx_mpegts_http_cleanup; + cln->data = r; + + hflcf = ngx_http_get_module_loc_conf(r, ngx_mpegts_http_module); + + /* create fake session */ + s = ngx_rtmp_create_session(hflcf->addr_conf); + if (s == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + ctx->session = s; + s->connection = r->connection; + ngx_rtmp_set_combined_log(s, r->connection->log->data, + r->connection->log->handler); + s->log->connection = r->connection->number; + s->number = r->connection->number; + s->remote_addr_text.data = ngx_pcalloc(s->pool, r->connection->addr_text.len); + s->remote_addr_text.len = r->connection->addr_text.len; + ngx_memcpy(s->remote_addr_text.data, + r->connection->addr_text.data, r->connection->addr_text.len); + + /* get host, app, stream name */ + ngx_memzero(&v, sizeof(ngx_rtmp_play_t)); + rc = ngx_mpegts_http_parse(r, s, &v); + if (rc != NGX_OK) { + return rc; + } + + if (ngx_rtmp_set_virtual_server(s, &s->domain)) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + s->live_type = NGX_MPEGTS_LIVE; + s->live_server = ngx_live_create_server(&s->serverid); + s->request = r; + + v.silent = 1; + + cacfp = cscf->applications.elts; + for (n = 0; n < cscf->applications.nelts; ++n, ++cacfp) { + if ((*cacfp)->name.len == s->app.len && + ngx_strncmp((*cacfp)->name.data, s->app.data, s->app.len) == 0) + { + /* found app! */ + s->app_conf = (*cacfp)->app_conf; + break; + } + } + + if (s->app_conf == NULL) { + + if (cscf->default_app == NULL || cscf->default_app->app_conf == NULL) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "mpegts-http: http_handler| " + "application not found '%V'", &s->app); + return NGX_HTTP_NOT_FOUND; + } + + s->app_conf = cscf->default_app->app_conf; + } + + s->prepare_handler = ngx_mpegts_http_prepare_out_chain; + + s->stage = NGX_LIVE_PLAY; + s->ptime = ngx_current_msec; + + cmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_core_module); + s->variables = ngx_pcalloc(s->pool, cmcf->variables.nelts + * sizeof(ngx_http_variable_value_t)); + if (s->variables == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + if (ngx_rtmp_play_filter(s, &v) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + ngx_add_timer(r->connection->write, s->timeout); + + r->read_event_handler = ngx_http_test_reading; + r->write_event_handler = ngx_mpegts_http_write_handler; + + ++r->count; + + return NGX_DONE; +} + + +static void * +ngx_mpegts_http_create_loc_conf(ngx_conf_t *cf) +{ + ngx_mpegts_http_loc_conf_t *hflcf; + + hflcf = ngx_pcalloc(cf->pool, sizeof(ngx_mpegts_http_loc_conf_t)); + if (hflcf == NULL) { + return NULL; + } + + return hflcf; +} + +static char * +ngx_mpegts_http_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_mpegts_http_loc_conf_t *prev = parent; + ngx_mpegts_http_loc_conf_t *conf = child; + + ngx_conf_merge_str_value(conf->app, prev->app, ""); + ngx_conf_merge_str_value(conf->flashver, prev->flashver, ""); + ngx_conf_merge_str_value(conf->swf_url, prev->swf_url, ""); + ngx_conf_merge_str_value(conf->tc_url, prev->tc_url, ""); + ngx_conf_merge_str_value(conf->page_url, prev->page_url, ""); + + return NGX_CONF_OK; +} + +static char * +ngx_mpegts_http(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_core_loc_conf_t *clcf; + ngx_mpegts_http_loc_conf_t *hflcf; + ngx_str_t *value, v; + ngx_uint_t i; + + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + clcf->handler = ngx_mpegts_http_handler; + + hflcf = conf; + + value = cf->args->elts; + + hflcf->addr_conf = ngx_rtmp_find_related_addr_conf(cf->cycle, &value[1]); + if (hflcf->addr_conf == NULL) { + return NGX_CONF_ERROR; + } + + for (i = 2; i < cf->args->nelts; ++i) { + if (ngx_strncmp(value[i].data, "app=", 4) == 0) { + v.data = value[i].data + 4; + v.len = value[i].len - 4; + hflcf->app = v; + } else { + return NGX_CONF_ERROR; + } + } + + return NGX_CONF_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/mpegts/ngx_mpegts_live_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/mpegts/ngx_mpegts_live_module.c new file mode 100644 index 0000000000..1bac711e59 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/mpegts/ngx_mpegts_live_module.c @@ -0,0 +1,1891 @@ + +/* + * Copyright (C) Pingo (cczjp89@gmail.com) + */ + +#include +#include +#include +#include "ngx_rtmp.h" +#include "ngx_rtmp_cmd_module.h" +#include "ngx_rtmp_live_module.h" +#include "ngx_rbuf.h" +#include "ngx_mpegts_live_module.h" +#include "ngx_mpegts_gop_module.h" +#include "ngx_rtmp_codec_module.h" + +ngx_mpegts_video_pt ngx_mpegts_video; +ngx_mpegts_audio_pt ngx_mpegts_audio; + +static ngx_rtmp_publish_pt next_publish; +static ngx_rtmp_play_pt next_play; +static ngx_rtmp_close_stream_pt next_close_stream; + +#define NGX_MPEGTS_BUF_SIZE 1316 +#define NGX_RTMP_MPEG_BUFSIZE 1024*1024 + +#define TS_AUDIO_TYPE_AAC 0 +#define TS_AUDIO_TYPE_MP3 1 + +#define TS_VIDEO_TYPE_H264 0 +#define TS_VIDEO_TYPE_H265 1 + +typedef struct ngx_mpegts_live_app_conf_s { + ngx_pool_t *pool; + size_t audio_buffer_size; + ngx_msec_t sync; + ngx_msec_t audio_delay; + size_t out_queue; + ngx_mpegts_live_ctx_t *players; + u_char packet_buffer[NGX_RTMP_MPEG_BUFSIZE]; +} ngx_mpegts_live_app_conf_t; + +typedef struct ngx_mpegts_live_avc_codec_s { + ngx_rtmp_frame_t *avc_header; + ngx_uint_t video_codec_id; + ngx_uint_t avc_nal_bytes; +} ngx_mpegts_live_avc_codec_t; + +typedef struct ngx_mpegts_live_aac_codec_s { + ngx_rtmp_frame_t *aac_header; + uint64_t sample_rate; +} ngx_mpegts_live_aac_codec_t; + +struct ngx_mpegts_live_ctx_s { + + ngx_mpegts_live_ctx_t *next; + ngx_rtmp_session_t *session; + ngx_live_stream_t *stream; + + /* mpegts-module config */ + size_t audio_buffer_size; + ngx_msec_t sync; + ngx_msec_t audio_delay; + size_t out_queue; + + /* pat pmt frame*/ + ngx_mpegts_frame_t *patpmt; + + /* video packet */ + ngx_mpegts_live_avc_codec_t *avc_codec; + ngx_uint_t video_cc; + + /* audio packet */ + ngx_mpegts_live_aac_codec_t *aac_codec; + ngx_uint_t audio_cc; + uint64_t aframe_pts; + ngx_uint_t aframe_num; + ngx_msec_t aframe_base; + ngx_buf_t *aframe; +}; + +/* 700 ms PCR delay */ +#define NGX_RTMP_MEGPTS_DELAY 63000 + +static void * +ngx_mpegts_live_create_app_conf(ngx_conf_t *cf); +static char * +ngx_mpegts_live_merge_app_conf(ngx_conf_t *cf, void *parent, void *child); +static ngx_int_t +ngx_mpegts_live_postconfiguration(ngx_conf_t *cf); + +static ngx_command_t ngx_mpegts_live_commands[] = { + + { ngx_string("mpegts_audio_buffer_size"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_mpegts_live_app_conf_t, audio_buffer_size), + NULL }, + + { ngx_string("mpegts_sync"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_mpegts_live_app_conf_t, sync), + NULL }, + + { ngx_string("mpegts_audio_delay"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_mpegts_live_app_conf_t, audio_delay), + NULL }, + + { ngx_string("mpegts_out_queue"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_mpegts_live_app_conf_t, out_queue), + NULL }, + + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_mpegts_live_ctx = { + NULL, /* preconfiguration */ + ngx_mpegts_live_postconfiguration, /* postconfiguration */ + NULL, /* create main configuration */ + NULL, /* init main configuration */ + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + ngx_mpegts_live_create_app_conf, /* create app configuration */ + ngx_mpegts_live_merge_app_conf /* merge app configuration */ +}; + + +ngx_module_t ngx_mpegts_live_module = { + NGX_MODULE_V1, + &ngx_mpegts_live_ctx, /* module context */ + ngx_mpegts_live_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void * +ngx_mpegts_live_create_app_conf(ngx_conf_t *cf) +{ + ngx_mpegts_live_app_conf_t *macf; + + macf = ngx_pcalloc(cf->pool, sizeof(ngx_mpegts_live_app_conf_t)); + if (!macf) { + return NULL; + } + + macf->audio_buffer_size = NGX_CONF_UNSET; + macf->sync = NGX_CONF_UNSET_MSEC; + macf->audio_delay = NGX_CONF_UNSET_MSEC; + macf->out_queue = NGX_CONF_UNSET; + + return macf; +} + + +static char * +ngx_mpegts_live_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_mpegts_live_app_conf_t *prev = parent; + ngx_mpegts_live_app_conf_t *conf = child; + + ngx_conf_merge_size_value(conf->audio_buffer_size, prev->audio_buffer_size, + NGX_RTMP_MPEG_BUFSIZE); + ngx_conf_merge_msec_value(conf->sync, prev->sync, 2); + ngx_conf_merge_msec_value(conf->audio_delay, prev->audio_delay, 300); + ngx_conf_merge_size_value(conf->out_queue, prev->out_queue, 4096); + conf->pool = ngx_create_pool(4096, &cf->cycle->new_log); + if (!conf->pool) { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + +static u_char * +ngx_mpegts_live_write_pcr(u_char *p, uint64_t pcr) +{ + *p++ = (u_char) (pcr >> 25); + *p++ = (u_char) (pcr >> 17); + *p++ = (u_char) (pcr >> 9); + *p++ = (u_char) (pcr >> 1); + *p++ = (u_char) (pcr << 7 | 0x7e); + *p++ = 0; + + return p; +} + +static u_char * +ngx_mpegts_live_write_pts(u_char *p, ngx_uint_t fb, uint64_t pts) +{ + ngx_uint_t val; + + val = fb << 4 | (((pts >> 30) & 0x07) << 1) | 1; + *p++ = (u_char) val; + + val = (((pts >> 15) & 0x7fff) << 1) | 1; + *p++ = (u_char) (val >> 8); + *p++ = (u_char) val; + + val = (((pts) & 0x7fff) << 1) | 1; + *p++ = (u_char) (val >> 8); + *p++ = (u_char) val; + + return p; +} + +ngx_int_t +ngx_mpegts_live_shared_append_chain(ngx_mpegts_frame_t *f, ngx_buf_t *b, + ngx_flag_t mandatory) +{ + ngx_uint_t pes_size, header_size, body_size, in_size, stuff_size, flags; + u_char *packet, *p, *base; + ngx_int_t first; + ngx_chain_t *cl, **ll; + uint64_t pcr; + + for (ll = &f->chain; (*ll) && (*ll)->next; ll = &(*ll)->next); + cl = *ll; + + if ((b == NULL || b->pos == b->last) && mandatory) { + *ll = ngx_get_chainbuf(NGX_MPEGTS_BUF_SIZE, 1); + (*ll)->buf->flush = 1; + return NGX_OK; + } + + first = 1; + + while (b->pos < b->last) { + if ((*ll) && (*ll)->buf->end - (*ll)->buf->last < 188) { + ll = &(*ll)->next; + cl = *ll; + } + + if (*ll == NULL) { + *ll = ngx_get_chainbuf(NGX_MPEGTS_BUF_SIZE, 1); + cl = *ll; + cl->buf->flush = 1; + } + + packet = cl->buf->last; + p = packet; + + f->cc++; + + *p++ = 0x47; + *p++ = (u_char) (f->pid >> 8); + + if (first) { + p[-1] |= 0x40; + } + + *p++ = (u_char) f->pid; + *p++ = 0x10 | (f->cc & 0x0f); /* payload */ + + if (first) { + + if (f->key) { + packet[3] |= 0x20; /* adaptation */ + + *p++ = 7; /* size */ + *p++ = 0x50; /* random access + PCR */ + if (f->dts < NGX_RTMP_MEGPTS_DELAY) { + pcr = 0; + } else { + pcr = f->dts; + } + p = ngx_mpegts_live_write_pcr(p, pcr); + } + + /* PES header */ + + *p++ = 0x00; + *p++ = 0x00; + *p++ = 0x01; + *p++ = (u_char) f->sid; + + header_size = 5; + flags = 0x80; /* PTS */ + + if (f->dts != f->pts) { + header_size += 5; + flags |= 0x40; /* DTS */ + } + + pes_size = (b->last - b->pos) + header_size + 3; + if (pes_size > 0xffff) { + pes_size = 0; + } + + *p++ = (u_char) (pes_size >> 8); + *p++ = (u_char) pes_size; + *p++ = 0x80; /* H222 */ + *p++ = (u_char) flags; + *p++ = (u_char) header_size; + + p = ngx_mpegts_live_write_pts(p, flags >> 6, f->pts + + NGX_RTMP_MEGPTS_DELAY); + + if (f->dts != f->pts) { + p = ngx_mpegts_live_write_pts(p, 1, f->dts + + NGX_RTMP_MEGPTS_DELAY); + } + + first = 0; + } + + body_size = (ngx_uint_t) (packet + 188 - p); + in_size = (ngx_uint_t) (b->last - b->pos); + + if (body_size <= in_size) { + ngx_memcpy(p, b->pos, body_size); + b->pos += body_size; + + } else { + stuff_size = (body_size - in_size); + + if (packet[3] & 0x20) { + + /* has adaptation */ + + base = &packet[5] + packet[4]; + p = ngx_movemem(base + stuff_size, base, p - base); + ngx_memset(base, 0xff, stuff_size); + packet[4] += (u_char) stuff_size; + + } else { + + /* no adaptation */ + + packet[3] |= 0x20; + p = ngx_movemem(&packet[4] + stuff_size, &packet[4], + p - &packet[4]); + + packet[4] = (u_char) (stuff_size - 1); + if (stuff_size >= 2) { + packet[5] = 0; + ngx_memset(&packet[6], 0xff, stuff_size - 2); + } + } + + ngx_memcpy(p, b->pos, in_size); + b->pos = b->last; + } + + cl->buf->last += 188; + f->length += 188; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_mpegts_live_copy(ngx_rtmp_session_t *s, void *dst, u_char **src, size_t n, + ngx_chain_t **in) +{ + u_char *last; + size_t pn; + + if (*in == NULL) { + return NGX_ERROR; + } + + for ( ;; ) { + last = (*in)->buf->last; + + if ((size_t)(last - *src) >= n) { + if (dst) { + ngx_memcpy(dst, *src, n); + } + + *src += n; + + while (*in && *src == (*in)->buf->last) { + *in = (*in)->next; + if (*in) { + *src = (*in)->buf->pos; + } + } + + return NGX_OK; + } + + pn = last - *src; + + if (dst) { + ngx_memcpy(dst, *src, pn); + dst = (u_char *)dst + pn; + } + + n -= pn; + *in = (*in)->next; + + if (*in == NULL) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "rtmp-mpegts: mpegts_copy| failed to read %uz byte(s)", n); + return NGX_ERROR; + } + + *src = (*in)->buf->pos; + } +} + + +static ngx_int_t +ngx_mpegts_live_append_aud(ngx_rtmp_session_t *s, ngx_buf_t *out) +{ + static u_char aud_nal[] = { 0x00, 0x00, 0x00, 0x01, 0x09, 0xf0 }; + + if (out->last + sizeof(aud_nal) > out->end) { + return NGX_ERROR; + } + + out->last = ngx_cpymem(out->last, aud_nal, sizeof(aud_nal)); + + return NGX_OK; +} + + +static ngx_int_t +ngx_mpegts_live_append_sps_pps(ngx_rtmp_session_t *s, ngx_buf_t *out) +{ + u_char *p; + ngx_chain_t *in; + ngx_mpegts_live_ctx_t *ctx; + int8_t nnals; + uint16_t len, rlen; + ngx_int_t n; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_live_module); + + if (ctx == NULL || ctx->avc_codec == NULL) { + return NGX_ERROR; + } + + in = ctx->avc_codec->avc_header->chain; + if (in == NULL) { + return NGX_ERROR; + } + + p = in->buf->pos; + + /* + * Skip bytes: + * - flv fmt + * - H264 CONF/PICT (0x00) + * - 0 + * - 0 + * - 0 + * - version + * - profile + * - compatibility + * - level + * - nal bytes + */ + + if (ngx_mpegts_live_copy(s, NULL, &p, 10, &in) != NGX_OK) { + return NGX_ERROR; + } + + /* number of SPS NALs */ + if (ngx_mpegts_live_copy(s, &nnals, &p, 1, &in) != NGX_OK) { + return NGX_ERROR; + } + + nnals &= 0x1f; /* 5lsb */ + + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "rtmp-mpegts: append_sps_pps| SPS number: %uz", nnals); + + /* SPS */ + for (n = 0; ; ++n) { + for (; nnals; --nnals) { + + /* NAL length */ + if (ngx_mpegts_live_copy(s, &rlen, &p, 2, &in) != NGX_OK) { + return NGX_ERROR; + } + + ngx_rtmp_rmemcpy(&len, &rlen, 2); + + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "rtmp-mpegts: append_sps_pps| header NAL length: %uz", (size_t) len); + + /* AnnexB prefix */ + if (out->end - out->last < 4) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "rtmp-mpegts: append_sps_pps| too small buffer for header NAL size"); + return NGX_ERROR; + } + + *out->last++ = 0; + *out->last++ = 0; + *out->last++ = 0; + *out->last++ = 1; + + /* NAL body */ + if (out->end - out->last < len) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "rtmp-mpegts: append_sps_pps| too small buffer for header NAL"); + return NGX_ERROR; + } + + if (ngx_mpegts_live_copy(s, out->last, &p, len, &in) != NGX_OK) { + return NGX_ERROR; + } + + out->last += len; + } + + if (n == 1) { + break; + } + + /* number of PPS NALs */ + if (ngx_mpegts_live_copy(s, &nnals, &p, 1, &in) != NGX_OK) { + return NGX_ERROR; + } + + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "rtmp-mpegts: append_sps_pps| PPS number: %uz", nnals); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_mpegts_live_init_aac_codec(ngx_rtmp_session_t *s) +{ + ngx_mpegts_live_ctx_t *ctx; + ngx_rtmp_codec_ctx_t *codec_ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_live_module); + codec_ctx = ngx_rtmp_get_module_ctx(s->live_stream->publish_ctx->session, ngx_rtmp_codec_module); + + if (ctx->aac_codec) { + return NGX_OK; + } + + if (codec_ctx == NULL || codec_ctx->aac_header == NULL) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "rtmp-mpegts: aac_codec| codec ctx %p, aac_header is null", codec_ctx); + return NGX_AGAIN; + } + + ctx->aac_codec = ngx_pcalloc(s->pool, sizeof(ngx_mpegts_live_aac_codec_t)); + if (ctx->aac_codec == NULL) { + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "rtmp-mpegts: aac_codec| alloc mpegts aac_codec failed"); + return NGX_ERROR; + } + + ctx->aac_codec->aac_header = ngx_rtmp_shared_alloc_frame(1024, + codec_ctx->aac_header->chain, 0); + + ctx->aac_codec->sample_rate = codec_ctx->sample_rate; + + return NGX_OK; +} + + +static ngx_int_t +ngx_mpegts_live_init_avc_codec(ngx_rtmp_session_t *s) +{ + ngx_mpegts_live_ctx_t *ctx; + ngx_rtmp_codec_ctx_t *codec_ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_live_module); + codec_ctx = ngx_rtmp_get_module_ctx(s->live_stream->publish_ctx->session, ngx_rtmp_codec_module); + + if (ctx->avc_codec) { + return NGX_OK; + } + + if (codec_ctx == NULL || codec_ctx->avc_header == NULL) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "rtmp-mpegts: avc_codec| codec ctx %p, avc_header is null", codec_ctx); + return NGX_AGAIN; + } + + ctx->avc_codec = ngx_pcalloc(s->pool, sizeof(ngx_mpegts_live_avc_codec_t)); + if (ctx->avc_codec == NULL) { + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "rtmp-mpegts: avc_codec| alloc mpegts avc_codec failed"); + return NGX_ERROR; + } + + ctx->avc_codec->video_codec_id = codec_ctx->video_codec_id; + ctx->avc_codec->avc_nal_bytes = codec_ctx->avc_nal_bytes; + ctx->avc_codec->avc_header = ngx_rtmp_shared_alloc_frame(1024, + codec_ctx->avc_header->chain, 0); + + return NGX_OK; +} + +/* +static void +ngx_mpegts_live_append_out_chain(ngx_chain_t **header, ngx_mpegts_frame_t *frame) +{ + ngx_chain_t **ll; + ngx_chain_t *tail; + + ngx_rtmp_shared_acquire_frame(frame); + tail = frame->chain; + + for (ll = header; *ll; ll = &((*ll)->next)) { + (*ll)->buf->flush = 1; + } + + *ll = tail; + + for (; *ll; ll = &((*ll)->next)) { + (*ll)->buf->flush = 1; + if (!(*ll)->next) { + (*ll)->buf->last_in_chain = 1; + } + } +} +*/ + +static ngx_int_t +ngx_mpegts_live_flush_audio(ngx_rtmp_session_t *s) +{ + ngx_mpegts_live_ctx_t *ctx; + ngx_mpegts_frame_t *frame; + ngx_int_t rc; + ngx_buf_t *b; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_live_module); + + if (ctx == NULL) { + return NGX_OK; + } + + b = ctx->aframe; + + if (b == NULL || b->pos == b->last) { + return NGX_OK; + } + + frame = ngx_rtmp_shared_alloc_mpegts_frame(NULL, 0); + + frame->dts = ctx->aframe_pts; + frame->pts = ctx->aframe_pts; + frame->cc = ctx->audio_cc; + frame->pid = 0x101; + frame->sid = 0xc0; + frame->type = NGX_MPEGTS_MSG_AUDIO; + + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "rtmp-mpegts: flush_audio| pts=%uL", frame->pts); + + rc = ngx_mpegts_live_shared_append_chain(frame, b, 1); + + if (rc != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "rtmp-mpegts: flush_audio| flush failed"); + } else { + ctx->audio_cc = frame->cc; + ngx_mpegts_live_audio_filter(s, frame); + } + + ngx_rtmp_shared_free_mpegts_frame(frame); + + b->pos = b->last = b->start; + + return rc; +} + + +static ngx_int_t +ngx_mpegts_live_append_hevc_vps_sps_pps(ngx_rtmp_session_t *s, ngx_buf_t *out) +{ + ngx_mpegts_live_ctx_t *ctx; + u_char *p; + ngx_chain_t *in; + ngx_uint_t rnal_unit_len, nal_unit_len, i, j, + num_arrays, nal_unit_type, rnum_nalus, + num_nalus; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_live_module); + + if (ctx == NULL || ctx->avc_codec == NULL) { + return NGX_ERROR; + } + + in = ctx->avc_codec->avc_header->chain; + if (in == NULL) { + return NGX_ERROR; + } + + p = in->buf->pos; + /* 6 bytes + * FrameType 4 bits + * CodecID 4 bits + * AVCPacketType 1 byte + * CompositionTime 3 bytes + * HEVCDecoderConfigurationRecord + * configurationVersion 1 byte + */ + + /* 20 bytes + * HEVCDecoderConfigurationRecord + * general_profile_space 2 bits + * general_tier_flag 1 bit + * general_profile_idc 5 bits + * general_profile_compatibility_flags 4 bytes + * general_constraint_indicator_flags 6 bytes + * general_level_idc 1 byte + * min_spatial_segmentation_idc 4 bits reserved + 12 bits + * parallelismType 6 bits reserved + 2 bits + * chroma_format_idc 6 bits reserved + 2 bits + * bit_depth_luma_minus8 5 bits reserved + 3 bits + * bit_depth_chroma_minus8 5 bits reserved + 3 bits + * avgFrameRate 2 bytes + */ + + /* 1 bytes + * HEVCDecoderConfigurationRecord + * constantFrameRate 2 bits + * numTemporalLayers 3 bits + * temporalIdNested 1 bit + * lengthSizeMinusOne 2 bits + */ + + if (ngx_mpegts_live_copy(s, NULL, &p, 27, &in) != NGX_OK) { + return NGX_ERROR; + } + + /* 1 byte + * HEVCDecoderConfigurationRecord + * numOfArrays 1 byte + */ + num_arrays = 0; + if (ngx_mpegts_live_copy(s, &num_arrays, &p, 1, &in) != NGX_OK) { + return NGX_ERROR; + } + + for (i = 0; i < num_arrays; ++i) { + /* + * array_completeness 1 bit + * reserved 1 bit + * NAL_unit_type 6 bits + * numNalus 2 bytes + */ + if (ngx_mpegts_live_copy(s, &nal_unit_type, &p, 1, &in) != NGX_OK) { + return NGX_ERROR; + } + nal_unit_type &= 0x3f; + + if (ngx_mpegts_live_copy(s, &rnum_nalus, &p, 2, &in) != NGX_OK) { + return NGX_ERROR; + } + num_nalus = 0; + ngx_rtmp_rmemcpy(&num_nalus, &rnum_nalus, 2); + + for (j = 0; j < num_nalus; ++j) { + /* + * nalUnitLength 2 bytes + */ + if (ngx_mpegts_live_copy(s, &rnal_unit_len, &p, 2, &in) != NGX_OK) { + return NGX_ERROR; + } + nal_unit_len = 0; + ngx_rtmp_rmemcpy(&nal_unit_len, &rnal_unit_len, 2); + if (out->end - out->last < 4) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "hls: too small buffer for header NAL size"); + return NGX_ERROR; + } + + *out->last++ = 0; + *out->last++ = 0; + *out->last++ = 0; + *out->last++ = 1; + + if (out->end - out->last < (ngx_int_t)nal_unit_len) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "hls: too small buffer for header NAL"); + return NGX_ERROR; + } + + if (ngx_mpegts_live_copy(s, out->last, &p, nal_unit_len, &in) != NGX_OK) { + return NGX_ERROR; + } + + out->last += nal_unit_len; + } + } + return NGX_OK; +} + + +/* set h265 aud first, now is null*/ +static ngx_int_t +ngx_mpegts_live_append_hevc_aud(ngx_rtmp_session_t *s, ngx_buf_t *out) +{ + static u_char aud_nal[] = { 0x00, 0x00, 0x00, 0x01, 0x46, 0x01, 0x50 }; + + if (out->last + sizeof(aud_nal) > out->end) { + return NGX_ERROR; + } + + out->last = ngx_cpymem(out->last, aud_nal, sizeof(aud_nal)); + + return NGX_OK; +} + + +static ngx_int_t +ngx_mpegts_live_h265_handler(ngx_rtmp_session_t *s, ngx_rtmp_frame_t *f) +{ + ngx_mpegts_live_ctx_t *ctx; + ngx_mpegts_live_app_conf_t *macf; + u_char *p; + uint8_t fmt, ftype, htype, nal_type, src_nal_type; + uint32_t len, rlen; + ngx_buf_t out; + uint32_t cts; + ngx_mpegts_frame_t *frame; + ngx_uint_t nal_bytes; + ngx_int_t aud_sent, sps_pps_sent; + u_char *buffer; + ngx_rtmp_header_t *h; + ngx_chain_t *in; + ngx_int_t rc; + ngx_rtmp_core_app_conf_t *cacf; + + cacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_core_module); + + h = &f->hdr; + in = f->chain; + + macf = ngx_rtmp_get_module_app_conf(s, ngx_mpegts_live_module); + buffer = macf->packet_buffer; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_live_module); + if (ctx == NULL || h->mlen < 1) { + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "rtmp-mpegts: h265_handler| " + "resource error, mpegts_ctx=%p, h->mlen=%d", + ctx, h->mlen); + return NGX_OK; + } + + if (ctx->avc_codec == NULL) { + rc = ngx_mpegts_live_init_avc_codec(s); + if (rc == NGX_ERROR) { + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "rtmp-mpegts: h265_handler| init avc_codec failed"); + return NGX_OK; + } else if (rc == NGX_AGAIN) { + return NGX_OK; + } + } + + /* H265 is supported */ + if (ctx->avc_codec->video_codec_id != cacf->hevc_codec) + { + return NGX_OK; + } + + p = in->buf->pos; + if (ngx_mpegts_live_copy(s, &fmt, &p, 1, &in) != NGX_OK) { + return NGX_ERROR; + } + + /* 1: keyframe (IDR) + * 2: inter frame + * 3: disposable inter frame */ + + ftype = (fmt & 0xf0) >> 4; // 0x17/0x27/... + + /* H264 HDR/PICT */ + + if (ngx_mpegts_live_copy(s, &htype, &p, 1, &in) != NGX_OK) { + return NGX_ERROR; + } + + /* proceed only with PICT */ + + if (htype != 1) { /*0:AVC sequence header,1:AVC NALU 2:AVC end of sequence*/ + return NGX_OK; + } + + /* 3 bytes: decoder delay */ + + if (ngx_mpegts_live_copy(s, &cts, &p, 3, &in) != NGX_OK) { + return NGX_ERROR; + } + /* convert big end to little end */ + cts = ((cts & 0x00FF0000) >> 16) | ((cts & 0x000000FF) << 16) | + (cts & 0x0000FF00); + + ngx_memzero(&out, sizeof(out)); + + out.start = buffer; + out.end = buffer + NGX_RTMP_MPEG_BUFSIZE; + out.pos = out.start; + out.last = out.pos; + + nal_bytes = ctx->avc_codec->avc_nal_bytes; + aud_sent = 0; + sps_pps_sent = 0; + ngx_int_t vps_copy = 0; + ngx_int_t sps_copy = 0; + ngx_int_t pps_copy = 0; + + while (in) { + if (ngx_mpegts_live_copy(s, &rlen, &p, nal_bytes, &in) != NGX_OK) { + return NGX_OK; + } + + len = 0; + ngx_rtmp_rmemcpy(&len, &rlen, nal_bytes); + + if (len == 0) { + continue; + } + + if (ngx_mpegts_live_copy(s, &src_nal_type, &p, 1, &in) != NGX_OK) { + return NGX_OK; + } + + nal_type = (src_nal_type & 0x7e) >> 1; + + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "rtmp-mpegts: h265_handler| h265 NAL type=%ui, len=%uD", + (ngx_uint_t) nal_type, len); + + /* h264 format of rtmp_flv contains NAL header Prefix "00 00 00 01" */ + if (0 == nal_type) { + u_char nal_header[4] = {0}; + if (ngx_mpegts_live_copy(s, nal_header, &p, 3, &in) != NGX_OK) { + return NGX_OK; + } + + if (0 != ngx_strcmp(nal_header, "\0\0\1")) { + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "mpegts module: h265 hander| " + "is not h265 NAL header [00 00 00 01]"); + p -= 3; + goto NAL_TRAIL_N; + return NGX_OK; + } + + if (ngx_mpegts_live_copy(s, &src_nal_type, &p, 1, &in) != NGX_OK) { + return NGX_OK; + } + + nal_type = (src_nal_type & 0x7e) >> 1; + if (0 == nal_type) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "mpegts module: h265 hander| h265 NAL type reparse error"); + return NGX_OK; + } + +#define HEVC_NAL_AUD_LENGTH 0 + if (out.end - out.last < (ngx_int_t) (len + HEVC_NAL_AUD_LENGTH)) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "mpegts module: h265 hander| not enough buffer for NAL"); + return NGX_OK; + } +#if 1 + if (ngx_mpegts_live_append_hevc_aud(s, &out) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "mpegts module: h265 hander| error appending AUD NAL"); + } +#endif + /* back to 00 00 01 nal_type*/ + p = p - 4; + if (ngx_mpegts_live_copy(s, out.last, &p, len - 1, &in) != NGX_OK) { + return NGX_ERROR; + } + + out.last += (len - 1); + break; + } + +NAL_TRAIL_N: + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0, + "mpegts module: h265 hander| h265 NAL type=%ui, len=%uD", + (ngx_uint_t) nal_type, len); + + /* + * NAL_VPS 32 + * NAL_SPS 33 + * NAL_PPS 34 + * NAL_AUD 35 + * NAL_SEI_PREFIX 39 + * NAL_SEI_SUFFIX 40 + */ + if ((nal_type >= 32 && nal_type <= 35) + || nal_type == 39 || nal_type == 40) + { + if (out.end - out.last < (5 + len -1)) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "hls: not enough buffer for AnnexB prefix"); + return NGX_OK; + } + if (32 == nal_type) { + ++vps_copy; + if(!aud_sent){ + if (ngx_mpegts_live_append_hevc_aud(s, &out) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "hls: error appending AUD NAL"); + } + aud_sent = 1; + } + } else if (33 == nal_type) { + ++sps_copy; + } else if (34 == nal_type) { + ++pps_copy; + } else if (35 == nal_type) { + aud_sent = 1; + } + + *out.last++ = 0; + *out.last++ = 0; + *out.last++ = 0; + *out.last++ = 1; + *out.last++ = src_nal_type; + if (ngx_mpegts_live_copy(s, out.last, &p, len - 1, &in) != NGX_OK) { + return NGX_ERROR; + } + out.last += (len - 1); + continue; + } + + if (vps_copy > 0 && sps_copy > 0 && pps_copy > 0) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "mpegts vps_copy %i, sps_copy %i, pps_copy %i\n", + vps_copy, sps_copy, pps_copy); + sps_pps_sent = 1; + } + + if (!aud_sent) { + if (35 == nal_type) { + aud_sent = 1; + } else if (!sps_pps_sent) { + if (ngx_mpegts_live_append_hevc_vps_sps_pps(s, &out) != NGX_OK) + { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "hls: error appending AUD NAL"); + } + aud_sent = 1; + } + } + + if (IS_IRAP(nal_type)) { + if (!sps_pps_sent) { + if (ngx_mpegts_live_append_hevc_vps_sps_pps(s, &out) != NGX_OK) + { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "hls: error appenging VPS/SPS/PPS NALs"); + } + sps_pps_sent = 1; + } + } + + /* AnnexB prefix */ + if (out.end - out.last < 5) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "hls: not enough buffer for AnnexB prefix"); + return NGX_OK; + } + + /* first AnnexB prefix is long (4 bytes) */ + + if (out.last == out.pos) { + *out.last++ = 0; + } + + *out.last++ = 0; + *out.last++ = 0; + *out.last++ = 1; + *out.last++ = src_nal_type; + + /* NAL body */ + if (out.end - out.last < (ngx_int_t) len) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "hls: not enough buffer for NAL"); + return NGX_OK; + } + + if (ngx_mpegts_live_copy(s, out.last, &p, len - 1, &in) != NGX_OK) { + return NGX_ERROR; + } + + out.last += (len - 1); + } + + frame = ngx_rtmp_shared_alloc_mpegts_frame(NULL, 0); + if (frame == NULL) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "rtmp-mpegts: h265_handler| " + "memory error, alloc mpegts frame failed"); + return NGX_ERROR; + } + + /* continuity counter */ + frame->cc = ctx->video_cc; + frame->dts = (uint64_t) h->timestamp * 90; + /* pts = dts + composition time */ + frame->pts = frame->dts + cts * 90; + /* program id */ + frame->pid = 0x100; + /* stream id, video range from 0xe0 to 0xef */ + frame->sid = 0xe0; + frame->key = (ftype == 1); + frame->type = NGX_MPEGTS_MSG_VIDEO; + + /* + * start new fragment if + * - we have video key frame AND + * - we have audio buffered or have no audio at all or stream is closed + */ + if (ctx->aframe && ctx->aframe->last > ctx->aframe->pos && + ctx->aframe_pts + (uint64_t) ctx->audio_delay * 90 < frame->dts) + { + ngx_mpegts_live_flush_audio(s); + } + + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "rtmp-mpegts: h265_handler| video pts=%uL, dts=%uL", + frame->pts, frame->dts); + + if (ngx_mpegts_live_shared_append_chain(frame, &out, 1) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "rtmp-mpegts: h264_handler| video frame failed"); + } else { + ctx->video_cc = frame->cc; + ngx_mpegts_live_video_filter(s, frame); + } + + ngx_rtmp_shared_free_mpegts_frame(frame); + + return NGX_OK; +} + + +static ngx_int_t +ngx_mpegts_live_h264_handler(ngx_rtmp_session_t *s, ngx_rtmp_frame_t *f) +{ + ngx_rtmp_header_t *h; + ngx_chain_t *in; + ngx_mpegts_live_ctx_t *ctx; + u_char *p; + uint8_t fmt, ftype, htype, nal_type, src_nal_type; + uint32_t len, rlen; + ngx_buf_t out; + uint32_t cts; + ngx_mpegts_frame_t *frame; + ngx_uint_t nal_bytes; + ngx_int_t aud_sent, sps_pps_sent; + u_char *buffer; + ngx_mpegts_live_app_conf_t *macf; + ngx_int_t rc; + + h = &f->hdr; + in = f->chain; + + macf = ngx_rtmp_get_module_app_conf(s, ngx_mpegts_live_module); + buffer = macf->packet_buffer; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_live_module); + if (ctx == NULL || h->mlen < 1) { + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "mpegts-mux: h264_handler| " + "resource error, mpegts_ctx=%p, h->mlen=%d", + ctx, h->mlen); + return NGX_OK; + } + + if (ctx->avc_codec == NULL) { + rc = ngx_mpegts_live_init_avc_codec(s); + if (rc == NGX_ERROR) { + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "mpegts-mux: h264_handler| init avc_codec failed"); + return NGX_OK; + } else if (rc == NGX_AGAIN) { + return NGX_OK; + } + } + + /* H264 is supported */ + if (ctx->avc_codec->video_codec_id != NGX_RTMP_VIDEO_H264) { + return NGX_OK; + } + + p = in->buf->pos; + if (ngx_mpegts_live_copy(s, &fmt, &p, 1, &in) != NGX_OK) { + return NGX_ERROR; + } + + /* 1: keyframe (IDR) + * 2: inter frame + * 3: disposable inter frame */ + + ftype = (fmt & 0xf0) >> 4; + + /* H264 HDR/PICT */ + + if (ngx_mpegts_live_copy(s, &htype, &p, 1, &in) != NGX_OK) { + return NGX_ERROR; + } + + /* proceed only with PICT */ + + if (htype != 1) { + return NGX_OK; + } + + /* 3 bytes: decoder delay */ + + if (ngx_mpegts_live_copy(s, &cts, &p, 3, &in) != NGX_OK) { + return NGX_ERROR; + } + + cts = ((cts & 0x00FF0000) >> 16) | ((cts & 0x000000FF) << 16) | + (cts & 0x0000FF00); + + ngx_memzero(&out, sizeof(out)); + + out.start = buffer; + out.end = buffer + NGX_RTMP_MPEG_BUFSIZE; + out.pos = out.start; + out.last = out.pos; + + nal_bytes = ctx->avc_codec->avc_nal_bytes; + aud_sent = 0; + sps_pps_sent = 0; + + while (in) { + if (ngx_mpegts_live_copy(s, &rlen, &p, nal_bytes, &in) != NGX_OK) { + return NGX_OK; + } + + len = 0; + ngx_rtmp_rmemcpy(&len, &rlen, nal_bytes); + + if (len == 0) { + continue; + } + + if (ngx_mpegts_live_copy(s, &src_nal_type, &p, 1, &in) != NGX_OK) { + return NGX_OK; + } + + nal_type = src_nal_type & 0x1f; + + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "mpegts-mux: h264_handler| h264 NAL type=%ui, len=%uD", + (ngx_uint_t) nal_type, len); + + if (nal_type >= 7 && nal_type <= 9) { + if (ngx_mpegts_live_copy(s, NULL, &p, len - 1, &in) != NGX_OK) { + return NGX_ERROR; + } + continue; + } + + if (!aud_sent) { + switch (nal_type) { + case 1: + case 5: + case 6: + if (ngx_mpegts_live_append_aud(s, &out) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "mpegts-mux: h264_handler| error appending AUD NAL"); + } + aud_sent = 1; + break; + + case 9: + aud_sent = 1; + break; + } + } + + switch (nal_type) { + case 1: + sps_pps_sent = 0; + break; + case 5: + if (sps_pps_sent) { + break; + } + if (ngx_mpegts_live_append_sps_pps(s, &out) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "mpegts-mux: h264_handler| error appenging SPS/PPS NALs"); + } + sps_pps_sent = 1; + break; + } + + /* AnnexB prefix */ + + if (out.end - out.last < 5) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "mpegts-mux: h264_handler| not enough buffer for AnnexB prefix"); + return NGX_OK; + } + + /* first AnnexB prefix is long (4 bytes) */ + + if (out.last == out.pos) { + *out.last++ = 0; + } + + *out.last++ = 0; + *out.last++ = 0; + *out.last++ = 1; + *out.last++ = src_nal_type; + + /* NAL body */ + + if (out.end - out.last < (ngx_int_t) len) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "mpegts-mux: h264_handler| not enough buffer for NAL"); + return NGX_OK; + } + + if (ngx_mpegts_live_copy(s, out.last, &p, len - 1, &in) != NGX_OK) { + return NGX_ERROR; + } + + out.last += (len - 1); + } + + frame = ngx_rtmp_shared_alloc_mpegts_frame(NULL, 0); + if (frame == NULL) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "mpegts-mux: h264_handler| " + "memory error, alloc mpegts frame failed"); + return NGX_ERROR; + } + + frame->cc = ctx->video_cc; + frame->dts = (uint64_t) h->timestamp * 90; + frame->pts = frame->dts + (cts * 90); + frame->pid = 0x100; + frame->sid = 0xe0; + frame->key = (ftype == 1); + frame->type = NGX_MPEGTS_MSG_VIDEO; + + if (ctx->aframe && ctx->aframe->last > ctx->aframe->pos && + ctx->aframe_pts + (uint64_t) ctx->audio_delay * 90 < frame->dts) + { + ngx_mpegts_live_flush_audio(s); + } + + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "mpegts-mux: h264_handler| video pts=%uL, dts=%uL", + frame->pts/90, frame->dts/90); + + if (ngx_mpegts_live_shared_append_chain(frame, &out, 1) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "mpegts-mux: h264_handler| video frame failed"); + } else { + ctx->video_cc = frame->cc; + ngx_mpegts_live_video_filter(s, frame); + } + + ngx_rtmp_shared_free_mpegts_frame(frame); + + return NGX_OK; +} + + +static ngx_int_t +ngx_mpegts_live_parse_aac_header(ngx_rtmp_session_t *s, ngx_uint_t *objtype, + ngx_uint_t *srindex, ngx_uint_t *chconf) +{ + ngx_mpegts_live_ctx_t *ctx; + ngx_chain_t *cl; + u_char *p, b0, b1; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_live_module); + + cl = ctx->aac_codec->aac_header->chain; + + p = cl->buf->pos; + + if (ngx_mpegts_live_copy(s, NULL, &p, 2, &cl) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_mpegts_live_copy(s, &b0, &p, 1, &cl) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_mpegts_live_copy(s, &b1, &p, 1, &cl) != NGX_OK) { + return NGX_ERROR; + } + + *objtype = b0 >> 3; + if (*objtype == 0 || *objtype == 0x1f) { + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "rtmp-mpegts: parse_aac_header| " + "unsupported adts object type:%ui", *objtype); + return NGX_ERROR; + } + + if (*objtype > 4) { + + /* + * Mark all extended profiles as LC + * to make Android as happy as possible. + */ + + *objtype = 2; + } + + *srindex = ((b0 << 1) & 0x0f) | ((b1 & 0x80) >> 7); + if (*srindex == 0x0f) { + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "rtmp-mpegts: parse_aac_header| " + "unsupported adts sample rate:%ui", *srindex); + return NGX_ERROR; + } + + *chconf = (b1 >> 3) & 0x0f; + + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "rtmp-mpegts: parse_aac_header| " + "aac object_type:%ui, sample_rate_index:%ui, " + "channel_config:%ui", *objtype, *srindex, *chconf); + + return NGX_OK; +} + + +static ngx_int_t +ngx_mpegts_live_aac_handler(ngx_rtmp_session_t *s, ngx_rtmp_frame_t *f) +{ + ngx_rtmp_header_t *h; + ngx_chain_t *in; + ngx_mpegts_live_ctx_t *ctx; + uint64_t pts, est_pts; + int64_t dpts; + size_t bsize; + ngx_buf_t *b; + u_char *p; + ngx_uint_t objtype, srindex, chconf, size; + ngx_int_t rc; + + h = &f->hdr; + in = f->chain; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_live_module); + + if (ctx == NULL || h->mlen < 2) { + return NGX_OK; + } + + if (ctx->aac_codec == NULL) { + rc = ngx_mpegts_live_init_aac_codec(s); + if (rc == NGX_ERROR) { + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "rtmp-mpegts: aac_handler| init aac_codec failed"); + return NGX_OK; + } else if(rc == NGX_AGAIN) { + return NGX_OK; + } + } + + if (ngx_rtmp_is_codec_header(in)) { + return NGX_OK; + } + + b = ctx->aframe; + + if (b == NULL) { + + b = ngx_pcalloc(s->pool, sizeof(ngx_buf_t)); + if (b == NULL) { + return NGX_ERROR; + } + + ctx->aframe = b; + + b->start = ngx_palloc(s->pool, ctx->audio_buffer_size); + if (b->start == NULL) { + return NGX_ERROR; + } + + b->end = b->start + ctx->audio_buffer_size; + b->pos = b->last = b->start; + } + + size = h->mlen - 2 + 7; + pts = (uint64_t) h->timestamp * 90; + + if (b->start + size > b->end) { + return NGX_OK; + } + + if (b->last > b->pos && + ctx->aframe_pts + (uint64_t) ctx->audio_delay * 90 / 2 < pts) + { + ngx_mpegts_live_flush_audio(s); + } + + if (b->last + size > b->end) { + ngx_mpegts_live_flush_audio(s); + } + + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "rtmp-mpegts: aac_handler| audio pts=%uL", pts); + + if (b->last + 7 > b->end) { + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "rtmp-mpegts: aac_handler| not enough buffer for audio header"); + return NGX_OK; + } + + p = b->last; + b->last += 5; + + /* copy payload */ + + for (; in && b->last < b->end; in = in->next) { + + bsize = in->buf->last - in->buf->pos; + if (b->last + bsize > b->end) { + bsize = b->end - b->last; + } + + b->last = ngx_cpymem(b->last, in->buf->pos, bsize); + } + + /* make up ADTS header */ + + if (ngx_mpegts_live_parse_aac_header(s, &objtype, &srindex, &chconf) + != NGX_OK) + { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "rtmp-mpegts: aac_handler| aac header error"); + return NGX_OK; + } + + /* we have 5 free bytes + 2 bytes of RTMP frame header */ + + p[0] = 0xff; + p[1] = 0xf1; + p[2] = (u_char) (((objtype - 1) << 6) | (srindex << 2) | + ((chconf & 0x04) >> 2)); + p[3] = (u_char) (((chconf & 0x03) << 6) | ((size >> 11) & 0x03)); + p[4] = (u_char) (size >> 3); + p[5] = (u_char) ((size << 5) | 0x1f); + p[6] = 0xfc; + + if (p != b->start) { + ctx->aframe_num++; + return NGX_OK; + } + + ctx->aframe_pts = pts; + + if (!ctx->sync || ctx->aac_codec->sample_rate == 0) { + return NGX_OK; + } + + /* align audio frames */ + + /* TODO: We assume here AAC frame size is 1024 + * Need to handle AAC frames with frame size of 960 */ + + est_pts = ctx->aframe_base + ctx->aframe_num * 90000 * 1024 / + ctx->aac_codec->sample_rate; + dpts = (int64_t) (est_pts - pts); + + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "rtmp-mpegts: aac_handler| audio sync dpts=%L (%.5fs)", + dpts, dpts / 90000.); + + if (dpts <= (int64_t) ctx->sync * 90 && + dpts >= (int64_t) ctx->sync * -90) + { + ctx->aframe_num++; + ctx->aframe_pts = est_pts; + return NGX_OK; + } + + ctx->aframe_base = pts; + ctx->aframe_num = 1; + + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "rtmp-mpegts: aac_handler| audio sync gap dpts=%L (%.5fs)", + dpts, dpts / 90000.); + + return NGX_OK; +} + +static ngx_int_t +ngx_mpegts_live_av(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_mpegts_live_ctx_t *ctx; + ngx_rtmp_frame_t frame; + ngx_rtmp_codec_ctx_t *codec_ctx; + ngx_rtmp_core_app_conf_t *cacf; + + cacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_core_module); + + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_live_module); + if (ctx == NULL || codec_ctx == NULL || codec_ctx->avc_header == NULL) { + return NGX_OK; + } + + /* Only H264 and H265 is supported */ + if (codec_ctx->video_codec_id != NGX_RTMP_VIDEO_H264 && + codec_ctx->video_codec_id != cacf->hevc_codec) + { + return NGX_OK; + } + + if (s->pause) { + return NGX_OK; + } + + ngx_memzero(&frame, sizeof(frame)); + + frame.hdr = *h; + frame.chain = in; + + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "mpegts-mux: av| pts[%uL] type [%d] key[%d]", + frame.hdr.timestamp, frame.hdr.type, frame.keyframe); + + switch (frame.hdr.type) { + case NGX_RTMP_MSG_AUDIO: + // only aac, for now + ngx_mpegts_live_aac_handler(s, &frame); + break; + + case NGX_RTMP_MSG_VIDEO: + /* h264 h265 */ + if (codec_ctx->video_codec_id == NGX_RTMP_VIDEO_H264) { + ngx_mpegts_live_h264_handler(s, &frame); + } else if (codec_ctx->video_codec_id == cacf->hevc_codec) { + ngx_mpegts_live_h265_handler(s, &frame); + } + break; + + default: + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "rtmp-mpegts: av| unknown frame-type=%d", frame.hdr.type); + break; + } + + return NGX_OK; +} + +ngx_int_t +ngx_mpegts_live_video_filter(ngx_rtmp_session_t *s, ngx_mpegts_frame_t *frame) +{ + return ngx_mpegts_video(s, frame); +} + +ngx_int_t +ngx_mpegts_live_audio_filter(ngx_rtmp_session_t *s, ngx_mpegts_frame_t *frame) +{ + return ngx_mpegts_audio(s, frame); +} + +static ngx_int_t +ngx_mpegts_live_avframe(ngx_rtmp_session_t *s, ngx_mpegts_frame_t *frame) +{ + ngx_mpegts_live_ctx_t *cctx; + ngx_rtmp_session_t *ss; + + for (cctx = s->live_stream->mpegts_ctx; cctx; cctx = cctx->next) { + ss = cctx->session; + ngx_mpegts_gop_send(s, ss); + + if (!s->connection->write->active) { + ngx_post_event(s->connection->write, &ngx_posted_events); + } + } + + return NGX_OK; +} + +static ngx_int_t +ngx_mpegts_live_ctx_init(ngx_rtmp_session_t *s) +{ + ngx_mpegts_live_app_conf_t *macf; + ngx_mpegts_live_ctx_t *ctx; + + macf = ngx_rtmp_get_module_app_conf(s, ngx_mpegts_live_module); + if (macf == NULL) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "rtmp-mpegts: ctx_init| get app conf failed"); + return NGX_ERROR; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_live_module); + if (ctx == NULL) { + ctx = ngx_pcalloc(s->pool, sizeof(ngx_mpegts_live_ctx_t)); + if (ctx == NULL) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "rtmp-mpegts: ctx_init| pcalloc ctx failed"); + return NGX_ERROR; + } + + ngx_rtmp_set_ctx(s, ctx, ngx_mpegts_live_module); + ctx->session = s; + } + + ctx->sync = macf->sync; + ctx->audio_buffer_size = macf->audio_buffer_size; + ctx->audio_delay = macf->audio_delay; + ctx->out_queue = macf->out_queue; + + return NGX_OK; +} + +static ngx_int_t +ngx_mpegts_live_join(ngx_rtmp_session_t *s, u_char *name, unsigned publisher) +{ + ngx_mpegts_live_ctx_t *ctx; + ngx_live_stream_t *st; + ngx_rtmp_live_app_conf_t *lacf; + + lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module); + if (lacf == NULL) { + return NGX_ERROR; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_live_module); + if (ctx && ctx->stream) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0, + "mpegts-live: join| already joined"); + return NGX_ERROR; + } + + if (ctx == NULL) { + ctx = ngx_pcalloc(s->pool, sizeof(ngx_mpegts_live_ctx_t)); + if (ctx == NULL) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "mpegts-live: join| pcalloc ctx failed"); + return NGX_ERROR; + } + + ngx_rtmp_set_ctx(s, ctx, ngx_mpegts_live_module); + } + + ngx_memzero(ctx, sizeof(*ctx)); + + ctx->session = s; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "mpegts-live: join| join '%s'", name); + + st = s->live_stream; + + if (!(publisher || st->publish_ctx || lacf->idle_streams)) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "mpegts-live: join| stream not found"); + + s->status = 404; + + return NGX_ERROR; + } + + ctx->stream = st; + ctx->next = st->mpegts_ctx; + + st->mpegts_ctx = ctx; + + return NGX_OK; +} + +static ngx_int_t +ngx_mpegts_live_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) +{ + ngx_mpegts_live_ctx_init(s); + + return next_publish(s, v); +} + +static ngx_int_t +ngx_mpegts_live_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) +{ + if (s->live_type != NGX_MPEGTS_LIVE) { + goto next; + } + + if (ngx_mpegts_live_join(s, v->name, 0) == NGX_ERROR) { + return NGX_ERROR; + } + +next: + return next_play(s, v); +} + +static ngx_int_t +ngx_mpegts_live_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v) +{ + ngx_mpegts_live_ctx_t *ctx, **cctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_live_module); + if (ctx == NULL) { + goto next; + } + + if (ctx->stream == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0, + "mpegts-live: close_stream| not joined"); + goto next; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "mpegts-live: close_stream| leave '%s'", ctx->stream->name); + + for (cctx = &ctx->stream->mpegts_ctx; *cctx; cctx = &(*cctx)->next) { + if (*cctx == ctx) { + *cctx = ctx->next; + break; + } + } + + if (ctx->stream->mpegts_ctx) { + ctx->stream = NULL; + goto next; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "mpegts-live: close_stream| delete empty stream '%s'", + ctx->stream->name); + + ctx->stream = NULL; + +next: + return next_close_stream(s, v); +} + +static ngx_int_t +ngx_mpegts_live_postconfiguration(ngx_conf_t *cf) +{ + ngx_rtmp_core_main_conf_t *cmcf; + ngx_rtmp_handler_pt *h; + + cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); + + /* register raw event handlers */ + + h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AUDIO]); + *h = ngx_mpegts_live_av; + + h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_VIDEO]); + *h = ngx_mpegts_live_av; + + next_publish = ngx_rtmp_publish; + ngx_rtmp_publish = ngx_mpegts_live_publish; + + next_play = ngx_rtmp_play; + ngx_rtmp_play = ngx_mpegts_live_play; + + next_close_stream = ngx_rtmp_close_stream; + ngx_rtmp_close_stream = ngx_mpegts_live_close_stream; + + ngx_mpegts_video = ngx_mpegts_live_avframe; + ngx_mpegts_audio = ngx_mpegts_live_avframe; + + return NGX_OK; +} + diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/mpegts/ngx_mpegts_live_module.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/mpegts/ngx_mpegts_live_module.h new file mode 100644 index 0000000000..11c27d480c --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/mpegts/ngx_mpegts_live_module.h @@ -0,0 +1,29 @@ + +/* + * Copyright (C) Pingo (cczjp89@gmail.com) + */ + +#ifndef _NGX_RTMP_MPEGTS_MODULE_H +#define _NGX_RTMP_MPEGTS_MODULE_H + +#include "ngx_rtmp.h" + +typedef ngx_int_t (*ngx_mpegts_video_pt)(ngx_rtmp_session_t *s, + ngx_mpegts_frame_t *frame); +typedef ngx_int_t (*ngx_mpegts_audio_pt)(ngx_rtmp_session_t *s, + ngx_mpegts_frame_t *frame); + +extern ngx_mpegts_video_pt ngx_mpegts_video; +extern ngx_mpegts_audio_pt ngx_mpegts_audio; + +ngx_int_t +ngx_rtmp_mpegts_gen_pmt(ngx_int_t vcodec, ngx_int_t acodec, + ngx_log_t *log, u_char *pmt); +ngx_int_t +ngx_mpegts_live_video_filter(ngx_rtmp_session_t *s, ngx_mpegts_frame_t *frame); +ngx_int_t +ngx_mpegts_live_audio_filter(ngx_rtmp_session_t *s, ngx_mpegts_frame_t *frame); + +extern u_char ngx_rtmp_mpegts_pat[]; + +#endif diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live.c new file mode 100644 index 0000000000..713f14a92b --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live.c @@ -0,0 +1,449 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#include "ngx_live.h" + + +static void *ngx_live_create_conf(ngx_cycle_t *cf); +static char *ngx_live_init_conf(ngx_cycle_t *cycle, void *conf); + + +static ngx_command_t ngx_live_commands[] = { + + { ngx_string("stream_buckets"), + NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + 0, + offsetof(ngx_live_conf_t, stream_buckets), + NULL }, + + { ngx_string("server_buckets"), + NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + 0, + offsetof(ngx_live_conf_t, server_buckets), + NULL }, + + ngx_null_command +}; + + +static ngx_core_module_t ngx_live_module_ctx = { + ngx_string("live"), + ngx_live_create_conf, /* create conf */ + ngx_live_init_conf /* init conf */ +}; + + +ngx_module_t ngx_live_module = { + NGX_MODULE_V1, + &ngx_live_module_ctx, /* module context */ + ngx_live_commands, /* module directives */ + NGX_CORE_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void * +ngx_live_create_conf(ngx_cycle_t *cycle) +{ + ngx_live_conf_t *lcf; + + lcf = ngx_pcalloc(cycle->pool, sizeof(ngx_live_conf_t)); + if (lcf == NULL) { + return NULL; + } + + lcf->stream_buckets = NGX_CONF_UNSET_SIZE; + lcf->server_buckets = NGX_CONF_UNSET_SIZE; + + return lcf; +} + +static char * +ngx_live_init_conf(ngx_cycle_t *cycle, void *conf) +{ + ngx_live_conf_t *lcf = conf; + + lcf->pool = ngx_create_pool(4096, cycle->log); + if (lcf->pool == NULL) { + return NGX_CONF_ERROR; + } + + ngx_conf_init_size_value(lcf->stream_buckets, 10007); + ngx_conf_init_size_value(lcf->server_buckets, 1031); + + lcf->servers = ngx_pcalloc(lcf->pool, + sizeof(ngx_live_server_t *) * lcf->server_buckets); + if (lcf->servers == NULL) { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + +static ngx_live_server_t ** +ngx_live_find_server(ngx_str_t *serverid) +{ + ngx_live_conf_t *lcf; + ngx_live_server_t **psrv; + + lcf = (ngx_live_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_live_module); + + psrv = &lcf->servers[ngx_hash_key(serverid->data, serverid->len) + % lcf->server_buckets]; + for (; *psrv; psrv = &(*psrv)->next) { + if (ngx_strlen((*psrv)->serverid) == serverid->len && + ngx_memcmp((*psrv)->serverid, serverid->data, serverid->len) == 0) + { + break; + } + } + + return psrv; +} + +static ngx_live_server_t * +ngx_live_get_server(ngx_str_t *serverid) +{ + ngx_live_conf_t *lcf; + ngx_live_server_t *srv; + + if (serverid->len > NGX_LIVE_SERVERID_LEN - 1) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, + "serverid too long: %ui", serverid->len); + return NULL; + } + + lcf = (ngx_live_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_live_module); + + srv = lcf->free_server; + if (srv == NULL) { + srv = ngx_pcalloc(lcf->pool, sizeof(ngx_live_server_t)); + if (srv == NULL) { + return NULL; + } + + srv->streams = ngx_pcalloc(lcf->pool, + sizeof(ngx_live_stream_t *) * lcf->stream_buckets); + if (srv->streams == NULL) { + return NULL; + } + + ++lcf->alloc_server_count; + } else { + lcf->free_server = srv->next; + --lcf->free_server_count; + } + + *ngx_cpymem(srv->serverid, serverid->data, serverid->len) = 0; + srv->deleted = 0; + srv->n_stream = 0; + + return srv; +} + +static void +ngx_live_put_server(ngx_live_server_t *server) +{ + ngx_live_conf_t *lcf; + + lcf = (ngx_live_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_live_module); + + server->next = lcf->free_server; + lcf->free_server = server; + ++lcf->free_server_count; +} + +static ngx_live_stream_t ** +ngx_live_find_stream(ngx_live_server_t *server, ngx_str_t *stream) +{ + ngx_live_conf_t *lcf; + ngx_live_stream_t **pst; + + lcf = (ngx_live_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_live_module); + + pst = &server->streams[ngx_hash_key(stream->data, stream->len) + % lcf->stream_buckets]; + for (; *pst; pst = &(*pst)->next) { + if (ngx_strlen((*pst)->name) == stream->len && + ngx_memcmp((*pst)->name, stream->data, stream->len) == 0) + { + break; + } + } + + return pst; +} + +static ngx_live_stream_t * +ngx_live_get_stream(ngx_str_t *stream) +{ + ngx_live_conf_t *lcf; + ngx_live_stream_t *st; + + if (stream->len > NGX_LIVE_STREAM_LEN - 1) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, + "stream too long: %ui", stream->len); + return NULL; + } + + lcf = (ngx_live_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_live_module); + + st = lcf->free_stream; + if (st == NULL) { + st = ngx_pcalloc(lcf->pool, sizeof(ngx_live_stream_t)); + ++lcf->alloc_stream_count; + } else { + lcf->free_stream = st->next; + --lcf->free_stream_count; + ngx_memzero(st, sizeof(ngx_live_stream_t)); + } + + *ngx_cpymem(st->name, stream->data, stream->len) = 0; + st->pslot = -1; + st->epoch = ngx_current_msec; + ngx_map_init(&st->pubctx, ngx_map_hash_int, ngx_cmp_int); + + return st; +} + +static void +ngx_live_put_stream(ngx_live_stream_t *st) +{ + ngx_live_conf_t *lcf; + + lcf = (ngx_live_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_live_module); + + st->next = lcf->free_stream; + lcf->free_stream = st; + ++lcf->free_stream_count; +} + +ngx_live_server_t * +ngx_live_create_server(ngx_str_t *serverid) +{ + ngx_live_server_t **psrv; + + psrv = ngx_live_find_server(serverid); + if (*psrv) { + (*psrv)->deleted = 0; + return *psrv; + } + + *psrv = ngx_live_get_server(serverid); + + return *psrv; +} + +ngx_live_server_t * +ngx_live_fetch_server(ngx_str_t *serverid) +{ + ngx_live_server_t **psrv; + + psrv = ngx_live_find_server(serverid); + + return *psrv; +} + +void +ngx_live_delete_server(ngx_str_t *serverid) +{ + ngx_live_server_t **psrv, *srv; + + psrv = ngx_live_find_server(serverid); + if (*psrv == NULL) { + return; + } + + if ((*psrv)->n_stream != 0) { + (*psrv)->deleted = 1; + } + + if ((*psrv)->n_stream == 0) { + srv = *psrv; + *psrv = srv->next; + ngx_live_put_server(srv); + } +} + +ngx_live_stream_t * +ngx_live_create_stream(ngx_str_t *serverid, ngx_str_t *stream) +{ + ngx_live_server_t **psrv; + ngx_live_stream_t **pst; + + psrv = ngx_live_find_server(serverid); + if (*psrv == NULL) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, + "server %V does not exist when create stream", serverid); + return NULL; + } + + pst = ngx_live_find_stream(*psrv, stream); + + if (*pst) { + return *pst; + } + + *pst = ngx_live_get_stream(stream); + ++(*psrv)->n_stream; + + return *pst; +} + +ngx_live_stream_t * +ngx_live_fetch_stream(ngx_str_t *serverid, ngx_str_t *stream) +{ + ngx_live_server_t **psrv; + ngx_live_stream_t **pst; + + psrv = ngx_live_find_server(serverid); + if (*psrv == NULL) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, + "server %V does not exist when fetch stream", serverid); + return NULL; + } + + pst = ngx_live_find_stream(*psrv, stream); + + return *pst; +} + +void +ngx_live_delete_stream(ngx_str_t *serverid, ngx_str_t *stream) +{ + ngx_live_server_t **psrv; + ngx_live_stream_t **pst, *st; + + psrv = ngx_live_find_server(serverid); + if (*psrv == NULL) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, + "server %V does not exist when delete stream", serverid); + return; + } + + pst = ngx_live_find_stream(*psrv, stream); + + if (*pst == NULL) { + return; + } + + st = *pst; + + *pst = st->next; + ngx_live_put_stream(st); + --(*psrv)->n_stream; + + if ((*psrv)->deleted && (*psrv)->n_stream == 0) { + ngx_live_delete_server(serverid); + } +} + +void +ngx_live_create_ctx(ngx_rtmp_session_t *s, unsigned publishing) +{ + ngx_rtmp_core_ctx_t *ctx, **pctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_core_module); + if (ctx == NULL) { + ctx = ngx_pcalloc(s->pool, sizeof(ngx_rtmp_core_ctx_t)); + if (ctx == NULL) { + return; + } + + ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_core_module); + } + + ctx->publishing = publishing; + ctx->session = s; + if (publishing) { + pctx = &s->live_stream->publish_ctx; + } else { + pctx = &s->live_stream->play_ctx; + } + + ctx->next = (*pctx); + *pctx = ctx; +} + +void +ngx_live_delete_ctx(ngx_rtmp_session_t *s) +{ + ngx_rtmp_core_ctx_t *ctx, **pctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_core_module); + if (ctx == NULL) { + return; + } + + if (ctx->publishing) { + pctx = &s->live_stream->publish_ctx; + } else { + pctx = &s->live_stream->play_ctx; + } + + for (/* void */; *pctx; pctx = &(*pctx)->next) { + if (*pctx == ctx) { + *pctx = ctx->next; + return; + } + } +} + + +ngx_chain_t * +ngx_live_state(ngx_http_request_t *r) +{ + ngx_live_conf_t *lcf; + ngx_chain_t *cl; + ngx_buf_t *b; + size_t len; + + lcf = (ngx_live_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_live_module); + + + len = sizeof("##########ngx live state##########\n") - 1 + + sizeof("ngx_live nalloc server: \n") - 1 + NGX_OFF_T_LEN + + sizeof("ngx_live nfree server: \n") - 1 + NGX_OFF_T_LEN + + sizeof("ngx_live nalloc stream: \n") - 1 + NGX_OFF_T_LEN + + sizeof("ngx_live nfree stream: \n") - 1 + NGX_OFF_T_LEN; + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NULL; + } + cl->next = NULL; + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NULL; + } + cl->buf = b; + + b->last = ngx_snprintf(b->last, len, + "##########ngx live state##########\n" + "ngx_live nalloc server: %ui\nngx_live nfree server: %ui\n" + "ngx_live nalloc stream: %ui\nngx_live nfree stream: %ui\n", + lcf->alloc_server_count, lcf->free_server_count, + lcf->alloc_stream_count, lcf->free_stream_count); + + return cl; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live.h new file mode 100644 index 0000000000..8fad1693c7 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#ifndef _NGX_LIVE_H_INCLUDED_ +#define _NGX_LIVE_H_INCLUDED_ + + +#include "ngx_rtmp.h" + + +typedef struct { + size_t stream_buckets; + size_t server_buckets; + + ngx_live_server_t **servers; + + ngx_live_server_t *free_server; + ngx_live_stream_t *free_stream; + + ngx_uint_t alloc_server_count; + ngx_uint_t free_server_count; + + ngx_uint_t alloc_stream_count; + ngx_uint_t free_stream_count; + + ngx_pool_t *pool; +} ngx_live_conf_t; + + +extern ngx_module_t ngx_live_module; + + +/* + * paras: + * r: http request to query status of rbuf + */ +ngx_chain_t *ngx_live_state(ngx_http_request_t *r); + + +#endif diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_record.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_record.c new file mode 100644 index 0000000000..3ff27e2020 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_record.c @@ -0,0 +1,1307 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#include +#include +#include "ngx_rtmp.h" +#include "ngx_rtmp_cmd_module.h" +#include "ngx_rtmp_codec_module.h" +#include "ngx_live_record.h" + + +ngx_live_record_start_pt ngx_live_record_start; +ngx_live_record_update_pt ngx_live_record_update; +ngx_live_record_done_pt ngx_live_record_done; + +static ngx_live_record_start_pt next_record_start; +static ngx_live_record_update_pt next_record_update; +static ngx_live_record_done_pt next_record_done; + +static ngx_rtmp_publish_pt next_publish; +static ngx_rtmp_close_stream_pt next_close_stream; + + +static ngx_int_t ngx_live_record_postconfiguration(ngx_conf_t *cf); +static void * ngx_live_record_create_app_conf(ngx_conf_t *cf); +static char * ngx_live_record_merge_app_conf(ngx_conf_t *cf, + void *parent, void *child); + + +#define NGX_LIVE_RECORD_BUFSIZE (10*1024*1024) + + +typedef struct { + ngx_flag_t record; + ngx_str_t path; + ngx_msec_t interval; + + ngx_msec_t min_fraglen; + ngx_msec_t max_fraglen; + + size_t buffer; +} ngx_live_record_app_conf_t; + + +static ngx_command_t ngx_live_record_commands[] = { + + { ngx_string("live_record"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_live_record_app_conf_t, record), + NULL }, + + { ngx_string("live_record_path"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_live_record_app_conf_t, path), + NULL }, + + { ngx_string("live_record_interval"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_live_record_app_conf_t, interval), + NULL }, + + { ngx_string("live_record_min_fragment"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_live_record_app_conf_t, min_fraglen), + NULL }, + + { ngx_string("live_record_max_fragment"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_live_record_app_conf_t, max_fraglen), + NULL }, + + { ngx_string("live_record_buffer"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_live_record_app_conf_t, buffer), + NULL }, + + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_live_record_module_ctx = { + NULL, /* preconfiguration */ + ngx_live_record_postconfiguration, /* postconfiguration */ + NULL, /* create main configuration */ + NULL, /* init main configuration */ + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + ngx_live_record_create_app_conf, /* create app configuration */ + ngx_live_record_merge_app_conf /* merge app configuration */ +}; + + +ngx_module_t ngx_live_record_module = { + NGX_MODULE_V1, + &ngx_live_record_module_ctx, /* module context */ + ngx_live_record_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void * +ngx_live_record_create_app_conf(ngx_conf_t *cf) +{ + ngx_live_record_app_conf_t *racf; + + racf = ngx_pcalloc(cf->pool, sizeof(ngx_live_record_app_conf_t)); + + if (racf == NULL) { + return NULL; + } + + racf->record = NGX_CONF_UNSET; + racf->interval = NGX_CONF_UNSET_MSEC; + racf->min_fraglen = NGX_CONF_UNSET_MSEC; + racf->max_fraglen = NGX_CONF_UNSET_MSEC; + racf->buffer = NGX_CONF_UNSET_SIZE; + + return racf; +} + + +static char * +ngx_live_record_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_err_t err; + ngx_live_record_app_conf_t *prev; + ngx_live_record_app_conf_t *conf; + u_char path[NGX_MAX_PATH + 1]; + + prev = parent; + conf = child; + + ngx_conf_merge_value(conf->record, prev->record, 0); + ngx_conf_merge_str_value(conf->path, prev->path, "record"); + ngx_conf_merge_msec_value(conf->interval, prev->interval, 10 * 60 * 1000); + ngx_conf_merge_msec_value(conf->min_fraglen, prev->min_fraglen, 8 * 1000); + ngx_conf_merge_msec_value(conf->max_fraglen, prev->max_fraglen, 12 * 1000); + ngx_conf_merge_msec_value(conf->buffer, prev->buffer, 1024 * 1024); + + if (conf->path.data[conf->path.len - 1] == '/') { + --conf->path.len; + } + + if (ngx_get_full_name(cf->pool, &cf->cycle->prefix, &conf->path) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + *ngx_snprintf(path, sizeof(path) - 1, "%V/", &conf->path) = 0; + err = ngx_create_full_path(path, 0755); + if (err) { + ngx_conf_log_error(NGX_LOG_CRIT, cf, err, + ngx_create_dir_n " \"%s\" failed", path); + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + +static ssize_t +ngx_live_record_flush(ngx_rtmp_mpegts_file_t *file) +{ + ssize_t rc; + + rc = ngx_write_fd(file->fd, file->wbuf.pos, + file->wbuf.last - file->wbuf.pos); + if (rc < 0) { + ngx_log_error(NGX_LOG_ERR, file->log, ngx_errno, + "flush record buf error"); + return rc; + } + + file->file_size += rc; + file->wbuf.last = file->wbuf.pos; + + return rc; +} + + +static ssize_t +ngx_live_record_write_buf(ngx_rtmp_mpegts_file_t *file, u_char *in, + size_t in_size) +{ + u_char *p, *end; + size_t len; + ssize_t rc, n; + + end = in + in_size; + n = 0; + + for (p = in; p != end; /* void */ ) { + len = ngx_min(file->wbuf.end - file->wbuf.last, end - p); + file->wbuf.last = ngx_cpymem(file->wbuf.last, p, len); + + p += len; + n += len; + + if (file->wbuf.last == file->wbuf.end) { + rc = ngx_live_record_flush(file); + if (rc < 0) { + return rc; + } + } + } + + return n; +} + + +static ngx_int_t +ngx_live_record_open_file(ngx_rtmp_session_t *s) +{ + ngx_live_record_app_conf_t *lracf; + ngx_live_record_ctx_t *ctx; + ngx_err_t err; + off_t file_size; + size_t len; + struct tm tm; + u_char *p; + ngx_rtmp_codec_ctx_t *codec_ctx; + + lracf = ngx_rtmp_get_module_app_conf(s, ngx_live_record_module); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_live_record_module); + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + len = lracf->path.len + sizeof("/") - 1 + s->serverid.len + sizeof("/") - 1 + + s->app.len + sizeof("/") - 1 + s->name.len + sizeof("/") - 1 + + sizeof("YYYYMMDD/") - 1 + s->name.len + + NGX_OFF_T_LEN + sizeof("_.ts") - 1; + + if (ctx->file.name.len == 0) { // first create in current session + ctx->file.name.data = ngx_pcalloc(s->pool, len + 1); + if (ctx->file.name.data == NULL) { + ngx_log_error(NGX_LOG_CRIT, s->log, 0, + "record: alloc for ts name failed"); + return NGX_ERROR; + } + } + + // fill file name + ngx_libc_localtime(ctx->last_time, &tm); + + p = ngx_snprintf(ctx->file.name.data, len, + "%V/%V/%V/%V/%04d%02d%02d/%V_%d.ts", + &lracf->path, &s->serverid, &s->app, &s->name, + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + &s->name, ctx->last_time); + *p = 0; + ctx->file.name.len = p - ctx->file.name.data; + + // create dir + err = ngx_create_full_path(ctx->file.name.data, 0755); + if (err) { + ngx_log_error(NGX_LOG_ERR, s->log, err, + ngx_create_dir_n " \"%V\" failed", &ctx->index.name); + return NGX_ERROR; + } + + // open file + ctx->file.fd = ngx_open_file(ctx->file.name.data, NGX_FILE_RDWR, + NGX_FILE_CREATE_OR_OPEN, NGX_FILE_DEFAULT_ACCESS); + if (ctx->file.fd == NGX_INVALID_FILE) { + ngx_log_error(NGX_LOG_CRIT, s->log, ngx_errno, + "record: failed to open file '%V'", &ctx->file.name); + + return NGX_ERROR; + } + + file_size = lseek(ctx->file.fd, 0, SEEK_END); + if (file_size == (off_t) -1) { + ngx_log_error(NGX_LOG_CRIT, s->log, ngx_errno, + "record: %V seek failed", &ctx->file.name); + + return NGX_ERROR; + } + + if (ctx->ts.wbuf.start == NULL) { + ctx->ts.wbuf.start = ngx_pcalloc(s->pool, lracf->buffer); + if (ctx->ts.wbuf.start == NULL) { + ngx_log_error(NGX_LOG_CRIT, s->log, 0, + "record: alloc write buffer error"); + return NGX_ERROR; + } + + ctx->ts.wbuf.pos = ctx->ts.wbuf.last = ctx->ts.wbuf.start; + ctx->ts.wbuf.end = ctx->ts.wbuf.start + lracf->buffer; + + ctx->ts.whandle = ngx_live_record_write_buf; + } + ctx->ts.fd = ctx->file.fd; + ctx->ts.log = s->log; + ctx->ts.file_size = file_size; + ctx->ts.vcodec = codec_ctx->video_codec_id; + ctx->ts.acodec = codec_ctx->audio_codec_id; + + if (file_size == 0) { // empty file + if (ngx_rtmp_mpegts_write_header(&ctx->ts) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno, + "record: error writing fragment header"); + + return NGX_ERROR; + } + ngx_live_record_flush(&ctx->ts); + } + + ctx->startsize = ctx->ts.file_size; + ctx->endsize = ctx->ts.file_size; + + return NGX_OK; +} + + +static void +ngx_live_record_write_index(ngx_rtmp_session_t *s, ngx_live_record_ctx_t *ctx, + ngx_msec_t curr_time) +{ + u_char *p, buf[1024]; + + ngx_live_record_flush(&ctx->ts); + + ctx->endsize = ctx->ts.file_size - 1; + + p = ngx_snprintf(buf, sizeof(buf) - 1, + "%V-%D.ts?startsize=%O&endsize=%O&starttime=%M&endtime=%M\n", + &s->name, ctx->last_time, ctx->startsize, ctx->endsize, + ctx->starttime, ctx->endtime); + *p = 0; + + if (ngx_write_fd(ctx->index.fd, buf, p - buf) < 0) { + ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno, + "record, write %V failed: %s", &ctx->index.name, buf); + } + + ctx->startsize = ctx->ts.file_size; + ctx->starttime = curr_time; +} + + +static ngx_int_t +ngx_live_record_open_index(ngx_rtmp_session_t *s) +{ + ngx_live_record_app_conf_t *lracf; + ngx_live_record_ctx_t *ctx; + ngx_err_t err; + size_t len; + struct tm tm; + u_char *p; + + lracf = ngx_rtmp_get_module_app_conf(s, ngx_live_record_module); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_live_record_module); + + len = lracf->path.len + sizeof("/") - 1 + s->serverid.len + sizeof("/") - 1 + + s->app.len + sizeof("/") - 1 + s->name.len + sizeof("/") - 1 + + sizeof("index/YYYYMMDD/") - 1 + s->name.len + + NGX_OFF_T_LEN + sizeof("-.m3u8") - 1; + + if (ctx->index.name.len == 0) { // first create in current session + ctx->index.name.data = ngx_pcalloc(s->pool, len + 1); + if (ctx->index.name.data == NULL) { + ngx_log_error(NGX_LOG_CRIT, s->log, 0, + "record: alloc for index name failed"); + return NGX_ERROR; + } + } + + // fill index and file name + ngx_libc_localtime(ctx->last_time, &tm); + + p = ngx_snprintf(ctx->index.name.data, len, + "%V/%V/%V/%V/index/%04d%02d%02d/%V-%D.m3u8", + &lracf->path, &s->serverid, &s->app, &s->name, + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + &s->name, ctx->last_time); + *p = 0; + ctx->index.name.len = p - ctx->index.name.data; + + // create dir + err = ngx_create_full_path(ctx->index.name.data, 0755); + if (err) { + ngx_log_error(NGX_LOG_ERR, s->log, err, + ngx_create_dir_n " \"%V\" failed", &ctx->index.name); + return NGX_ERROR; + } + + // open index + ctx->index.fd = ngx_open_file(ctx->index.name.data, NGX_FILE_RDWR, + NGX_FILE_CREATE_OR_OPEN, NGX_FILE_DEFAULT_ACCESS); + if (ctx->index.fd == NGX_INVALID_FILE) { + ngx_log_error(NGX_LOG_CRIT, s->log, ngx_errno, + "record: failed to open index '%V'", &ctx->index.name); + + return NGX_OK; + } + + if (lseek(ctx->index.fd, 0, SEEK_END) == (off_t) -1) { + ngx_log_error(NGX_LOG_CRIT, s->log, ngx_errno, + "record, %V seek failed", &ctx->index.name); + + return NGX_OK; + } + + return ngx_live_record_open_file(s); +} + + +static void +ngx_live_record_close_index(ngx_rtmp_session_t *s, ngx_live_record_ctx_t *ctx) +{ + if (ctx->index.fd == -1 || ctx->file.fd == -1) { + return; + } + + ngx_live_record_write_index(s, ctx, 0); + + ngx_close_file(ctx->file.fd); + ctx->file.fd = -1; + + ngx_close_file(ctx->index.fd); + ctx->index.fd = -1; +} + + +static void +ngx_live_record_reopen_index(ngx_rtmp_session_t *s, ngx_live_record_ctx_t *ctx, + ngx_msec_t curr_time, time_t last_time) +{ + // close old index and file + ngx_live_record_close_index(s, ctx); + + ngx_live_record_update(s); + + ctx->last_time = last_time; + + ctx->begintime = curr_time; + ctx->starttime = curr_time; + ctx->endtime = curr_time; + + // open new index and file + if (ngx_live_record_open_index(s) == NGX_ERROR) { + ctx->last_time = 0; + + if (ctx->index.fd != -1) { + ngx_close_file(ctx->index.fd); + } + + if (ctx->file.fd != -1) { + ngx_close_file(ctx->file.fd); + } + + return; + } +} + + +static ngx_int_t +ngx_live_record_copy(ngx_rtmp_session_t *s, void *dst, u_char **src, size_t n, + ngx_chain_t **in) +{ + u_char *last; + size_t pn; + + if (*in == NULL) { + return NGX_ERROR; + } + + for ( ;; ) { + last = (*in)->buf->last; + + if ((size_t)(last - *src) >= n) { + if (dst) { + ngx_memcpy(dst, *src, n); + } + + *src += n; + + while (*in && *src == (*in)->buf->last) { + *in = (*in)->next; + if (*in) { + *src = (*in)->buf->pos; + } + } + + return NGX_OK; + } + + pn = last - *src; + + if (dst) { + ngx_memcpy(dst, *src, pn); + dst = (u_char *)dst + pn; + } + + n -= pn; + *in = (*in)->next; + + if (*in == NULL) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "hls: failed to read %uz byte(s)", n); + return NGX_ERROR; + } + + *src = (*in)->buf->pos; + } +} + + +static ngx_int_t +ngx_live_record_parse_aac_header(ngx_rtmp_session_t *s, ngx_uint_t *objtype, + ngx_uint_t *srindex, ngx_uint_t *chconf) +{ + ngx_rtmp_codec_ctx_t *codec_ctx; + ngx_chain_t *cl; + u_char *p, b0, b1; + + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + cl = codec_ctx->aac_header->chain; + + p = cl->buf->pos; + + if (ngx_live_record_copy(s, NULL, &p, 2, &cl) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_live_record_copy(s, &b0, &p, 1, &cl) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_live_record_copy(s, &b1, &p, 1, &cl) != NGX_OK) { + return NGX_ERROR; + } + + *objtype = b0 >> 3; + if (*objtype == 0 || *objtype == 0x1f) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "record: unsupported adts object type:%ui", *objtype); + return NGX_ERROR; + } + + if (*objtype > 4) { + + /* + * Mark all extended profiles as LC + * to make Android as happy as possible. + */ + + *objtype = 2; + } + + *srindex = ((b0 << 1) & 0x0f) | ((b1 & 0x80) >> 7); + if (*srindex == 0x0f) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "record: unsupported adts sample rate:%ui", *srindex); + return NGX_ERROR; + } + + *chconf = (b1 >> 3) & 0x0f; + + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->log, 0, + "record: aac object_type:%ui, sample_rate_index:%ui, " + "channel_config:%ui", *objtype, *srindex, *chconf); + + return NGX_OK; +} + + +static ngx_int_t +ngx_live_record_append_aud(ngx_rtmp_session_t *s, ngx_buf_t *out) +{ + static u_char aud_nal[] = { 0x00, 0x00, 0x00, 0x01, 0x09, 0xf0 }; + + if (out->last + sizeof(aud_nal) > out->end) { + return NGX_ERROR; + } + + out->last = ngx_cpymem(out->last, aud_nal, sizeof(aud_nal)); + + return NGX_OK; +} + + +static ngx_int_t +ngx_live_record_append_sps_pps(ngx_rtmp_session_t *s, ngx_buf_t *out) +{ + ngx_rtmp_codec_ctx_t *codec_ctx; + u_char *p; + ngx_chain_t *in; + int8_t nnals; + uint16_t len, rlen; + ngx_int_t n; + + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + if (codec_ctx == NULL || codec_ctx->avc_header == NULL) { + return NGX_OK; + } + + in = codec_ctx->avc_header->chain; + if (in == NULL) { + return NGX_ERROR; + } + + p = in->buf->pos; + + /* + * Skip bytes: + * - flv fmt + * - H264 CONF/PICT (0x00) + * - 0 + * - 0 + * - 0 + * - version + * - profile + * - compatibility + * - level + * - nal bytes + */ + + if (ngx_live_record_copy(s, NULL, &p, 10, &in) != NGX_OK) { + return NGX_ERROR; + } + + /* number of SPS NALs */ + if (ngx_live_record_copy(s, &nnals, &p, 1, &in) != NGX_OK) { + return NGX_ERROR; + } + + nnals &= 0x1f; /* 5lsb */ + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "record: SPS number: %uz", nnals); + + /* SPS */ + for (n = 0; ; ++n) { + for (; nnals; --nnals) { + + /* NAL length */ + if (ngx_live_record_copy(s, &rlen, &p, 2, &in) != NGX_OK) { + return NGX_ERROR; + } + + ngx_rtmp_rmemcpy(&len, &rlen, 2); + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "record: header NAL length: %uz", (size_t) len); + + /* AnnexB prefix */ + if (out->end - out->last < 4) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "record: too small buffer for header NAL size"); + return NGX_ERROR; + } + + *out->last++ = 0; + *out->last++ = 0; + *out->last++ = 0; + *out->last++ = 1; + + /* NAL body */ + if (out->end - out->last < len) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "record: too small buffer for header NAL"); + return NGX_ERROR; + } + + if (ngx_live_record_copy(s, out->last, &p, len, &in) != NGX_OK) { + return NGX_ERROR; + } + + out->last += len; + } + + if (n == 1) { + break; + } + + /* number of PPS NALs */ + if (ngx_live_record_copy(s, &nnals, &p, 1, &in) != NGX_OK) { + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "record: PPS number: %uz", nnals); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_live_record_aac(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_live_record_ctx_t *ctx; + ngx_rtmp_codec_ctx_t *codec_ctx; + uint64_t pts; + ngx_rtmp_mpegts_frame_t frame; + ngx_buf_t out; + u_char *p; + ngx_uint_t objtype, srindex, chconf, size; + static u_char buffer[NGX_LIVE_RECORD_BUFSIZE]; + ngx_live_record_app_conf_t *lracf; + ngx_msec_t curr_time; + time_t last_time; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_live_record_module); + + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + if (codec_ctx == NULL || codec_ctx->aac_header == NULL) { + return NGX_OK; + } + + if (h->mlen < 2) { + return NGX_OK; + } + + if (ctx->open == 2) { + if (codec_ctx->avc_header == NULL) { // pure audio + ctx->open = 1; + } else { + return NGX_OK; + } + } + + lracf = ngx_rtmp_get_module_app_conf(s, ngx_live_record_module); + + if (ctx->last_time == 0) { + ctx->publish_epoch = ngx_current_msec; + ctx->last_time = ngx_time() - ngx_time() % (lracf->interval / 1000); + ctx->basetime = ctx->publish_epoch - h->timestamp; + + ctx->begintime = ngx_current_msec; + ctx->starttime = ngx_current_msec; + ctx->endtime = ngx_current_msec; + + // open new index and file + if (ngx_live_record_open_index(s) == NGX_ERROR) { + ctx->last_time = 0; + + if (ctx->index.fd != -1) { + ngx_close_file(ctx->index.fd); + } + + if (ctx->file.fd != -1) { + ngx_close_file(ctx->file.fd); + } + + return NGX_OK; + } + } + + /* + * FLV Audio data config + * SoundFormat 4bits, SoundRate 2bits, SoundSize 1bit, SoundType 1bit + * AACPacketType 1byte + * + * mpegts ADTS 7 bytes + */ + size = h->mlen - 2 + 7; + pts = (uint64_t) h->timestamp * 90; + + p = in->buf->pos; + + /* skip FLV Audio data config */ + if (ngx_live_record_copy(s, NULL, &p, 2, &in) != NGX_OK) { + return NGX_ERROR; + } + + ngx_memzero(&out, sizeof(out)); + + out.start = buffer; + out.end = buffer + sizeof(buffer); + out.pos = out.start; + out.last = out.pos; + + /* make up ADTS */ + if (ngx_live_record_parse_aac_header(s, &objtype, &srindex, &chconf) + != NGX_OK) + { + ngx_log_error(NGX_LOG_ERR, s->log, 0, "record: aac header error"); + return NGX_OK; + } + + *out.last++ = 0xff; + *out.last++ = 0xf1; + *out.last++ = (u_char) (((objtype - 1) << 6) | (srindex << 2) | + ((chconf & 0x04) >> 2)); + *out.last++ = (u_char) (((chconf & 0x03) << 6) | ((size >> 11) & 0x03)); + *out.last++ = (u_char) (size >> 3); + *out.last++ = (u_char) ((size << 5) | 0x1f); + *out.last++ = 0xfc; + + /* copy payload */ + while (in) { + if (in->buf->last - p) { + out.last = ngx_cpymem(out.last, p, in->buf->last - p); + } + + in = in->next; + if (in) { + p = in->buf->pos; + } + } + + // reopen index and ts file + curr_time = ctx->basetime + h->timestamp; + if (codec_ctx->avc_header == NULL) { // no video + last_time = curr_time / 1000 - (curr_time / 1000) + % (lracf->interval / 1000); + if (curr_time > ctx->starttime + lracf->min_fraglen) { + if (last_time > ctx->last_time) { + ngx_live_record_reopen_index(s, ctx, curr_time, last_time); + } else { + ngx_live_record_write_index(s, ctx, curr_time); + } + } + } + + /* write frame */ + ngx_memzero(&frame, sizeof(frame)); + + frame.cc = ctx->audio_cc; + frame.dts = pts; + frame.pts = frame.dts; + frame.pid = 0x101; + frame.sid = 0xc0; + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0, + "record: audio pts=%uL, dts=%uL", frame.pts, frame.dts); + + if (ngx_rtmp_mpegts_write_frame(&ctx->ts, &frame, &out) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, "record: audio frame failed"); + } + + ctx->endtime = curr_time; + ctx->audio_cc = frame.cc; + + return NGX_OK; +} + + +static ngx_int_t +ngx_live_record_avc(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_live_record_ctx_t *ctx; + ngx_rtmp_codec_ctx_t *codec_ctx; + u_char *p; + uint8_t fmt, ftype, nal_type, src_nal_type; + uint32_t len, rlen; + ngx_buf_t out; + uint32_t cts; + ngx_rtmp_mpegts_frame_t frame; + ngx_uint_t nal_bytes; + ngx_int_t aud_sent, sps_pps_sent; + static u_char buffer[NGX_LIVE_RECORD_BUFSIZE]; + ngx_live_record_app_conf_t *lracf; + ngx_msec_t curr_time; + time_t last_time; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_live_record_module); + + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + p = in->buf->pos; + if (ngx_live_record_copy(s, &fmt, &p, 1, &in) != NGX_OK) { + return NGX_ERROR; + } + + /* + * frame type: + * 1: keyframe (for AVC, a seekable frame) + * 2: inter frame (for AVC, a non- seekable frame) + * 3: disposable inter frame (H.263 only) + * 4: generated keyframe (reserved for server use only) + * 5: video info/command frame + */ + ftype = (fmt & 0xf0) >> 4; + + if (ctx->open == 2) { // wait for key frame + if (ftype == 1) { + ctx->open = 1; + } else { + return NGX_OK; + } + } + + lracf = ngx_rtmp_get_module_app_conf(s, ngx_live_record_module); + + if (ctx->last_time == 0) { + ctx->publish_epoch = ngx_current_msec; + ctx->last_time = ngx_time() - ngx_time() % (lracf->interval / 1000); + ctx->basetime = ctx->publish_epoch - h->timestamp; + + ctx->begintime = ngx_current_msec; + ctx->starttime = ngx_current_msec; + ctx->endtime = ngx_current_msec; + + // open new index and file + if (ngx_live_record_open_index(s) == NGX_ERROR) { + ctx->last_time = 0; + + if (ctx->index.fd != -1) { + ngx_close_file(ctx->index.fd); + } + + if (ctx->file.fd != -1) { + ngx_close_file(ctx->file.fd); + } + + return NGX_OK; + } + } + + if (ngx_live_record_copy(s, NULL, &p, 1, &in) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_live_record_copy(s, &cts, &p, 3, &in) != NGX_OK) { + return NGX_ERROR; + } + + /* CompositionTime */ + cts = ((cts & 0x00FF0000) >> 16) | ((cts & 0x000000FF) << 16) | + (cts & 0x0000FF00); + + /* Data */ + ngx_memzero(&out, sizeof(out)); + + out.start = buffer; + out.end = buffer + sizeof(buffer); + out.pos = out.start; + out.last = out.pos; + + nal_bytes = codec_ctx->avc_nal_bytes; + aud_sent = 0; + sps_pps_sent = 0; + + while (in) { + if (ngx_live_record_copy(s, &rlen, &p, nal_bytes, &in) != NGX_OK) { + return NGX_OK; + } + + len = 0; + ngx_rtmp_rmemcpy(&len, &rlen, nal_bytes); + + if (len == 0) { + continue; + } + + if (ngx_live_record_copy(s, &src_nal_type, &p, 1, &in) != NGX_OK) { + return NGX_OK; + } + + nal_type = src_nal_type & 0x1f; + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0, + "record: h264 NAL type=%ui, len=%uD", + (ngx_uint_t) nal_type, len); + + if (nal_type >= 7 && nal_type <= 9) { + if (ngx_live_record_copy(s, NULL, &p, len - 1, &in) != NGX_OK) { + return NGX_ERROR; + } + continue; + } + + if (!aud_sent) { + switch (nal_type) { + case 1: + case 5: + case 6: + if (ngx_live_record_append_aud(s, &out) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "record: error appending AUD NAL"); + } + aud_sent = 1; + break; + + case 9: + aud_sent = 1; + break; + } + } + + switch (nal_type) { + case 1: + sps_pps_sent = 0; + break; + case 5: + if (sps_pps_sent) { + break; + } + if (ngx_live_record_append_sps_pps(s, &out) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "record: error appenging SPS/PPS NALs"); + } + sps_pps_sent = 1; + break; + } + + /* AnnexB prefix */ + + if (out.end - out.last < 5) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "record: not enough buffer for AnnexB prefix"); + return NGX_OK; + } + + /* first AnnexB prefix is long (4 bytes) */ + + if (out.last == out.pos) { + *out.last++ = 0; + } + + *out.last++ = 0; + *out.last++ = 0; + *out.last++ = 1; + *out.last++ = src_nal_type; + + /* NAL body */ + + if (out.end - out.last < (ngx_int_t) len) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "record: not enough buffer for NAL"); + return NGX_OK; + } + + if (ngx_live_record_copy(s, out.last, &p, len - 1, &in) != NGX_OK) { + return NGX_ERROR; + } + + out.last += (len - 1); + } + + // reopen index and ts file + curr_time = ctx->basetime + h->timestamp; + last_time = curr_time / 1000 - (curr_time / 1000) + % (lracf->interval / 1000); + if (ftype == 1) { // key frame + if (curr_time > ctx->starttime + lracf->min_fraglen) { + if (last_time > ctx->last_time) { + ngx_live_record_reopen_index(s, ctx, curr_time, last_time); + } else { + ngx_live_record_write_index(s, ctx, curr_time); + } + } + } else if (curr_time > ctx->starttime + lracf->max_fraglen) { // force slice + if (last_time > ctx->last_time) { + ngx_log_error(NGX_LOG_INFO, s->log, 0, "record: force slice, " + "curr_time:%M, starttime:%M, max_fraglen:%M", + curr_time, ctx->starttime, lracf->max_fraglen); + + ngx_live_record_reopen_index(s, ctx, curr_time, last_time); + } else { + ngx_live_record_write_index(s, ctx, curr_time); + } + } + + /* write frame */ + ngx_memzero(&frame, sizeof(frame)); + + frame.cc = ctx->video_cc; + frame.dts = (uint64_t) h->timestamp * 90; + frame.pts = (h->timestamp + cts) * 90; + frame.pid = 0x100; + frame.sid = 0xe0; + frame.key = (ftype == 1); + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0, + "record: video pts=%uL, dts=%uL", frame.pts, frame.dts); + + if (ngx_rtmp_mpegts_write_frame(&ctx->ts, &frame, &out) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, "record: video frame failed"); + } + + ctx->endtime = curr_time; + ctx->video_cc = frame.cc; + + return NGX_OK; +} + + +static ngx_int_t +ngx_live_record_av(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_live_record_ctx_t *ctx; + ngx_rtmp_codec_ctx_t *codec_ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_live_record_module); + if (ctx == NULL || !ctx->open) { + return NGX_OK; + } + + if (ngx_rtmp_is_codec_header(in)) { + return NGX_OK; + } + + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + if (h->type == NGX_RTMP_MSG_AUDIO) { + switch (codec_ctx->audio_codec_id) { + case NGX_RTMP_AUDIO_AAC: + return ngx_live_record_aac(s, h, in); + } + } else { + switch (codec_ctx->video_codec_id) { + case NGX_RTMP_VIDEO_H264: + return ngx_live_record_avc(s, h, in); + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_live_record_start_handle(ngx_rtmp_session_t *s) +{ + ngx_live_record_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_live_record_module); + + return next_publish(s, &ctx->pubv); +} + + +static ngx_int_t +ngx_live_record_update_handle(ngx_rtmp_session_t *s) +{ + return NGX_OK; +} + + +static ngx_int_t +ngx_live_record_done_handle(ngx_rtmp_session_t *s) +{ + return NGX_OK; +} + + +const char * +ngx_live_record_open(ngx_rtmp_session_t *s) +{ + ngx_live_record_ctx_t *ctx; + + if (s->interprocess) { + return "interprocess"; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_live_record_module); + + if (ctx->open) { + return NGX_CONF_OK; + } + + ngx_log_error(NGX_LOG_INFO, s->log, 0, "record: open %V:", &s->stream); + + ctx->open = 2; + + return NGX_CONF_OK; +} + + +const char * +ngx_live_record_close(ngx_rtmp_session_t *s) +{ + ngx_live_record_ctx_t *ctx; + + if (s->interprocess) { + return "interprocess"; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_live_record_module); + + if (ctx->open == 0) { + return NGX_CONF_OK; + } + + ngx_log_error(NGX_LOG_INFO, s->log, 0, "record: close %V:", &s->stream); + + ngx_live_record_done(s); + + ngx_live_record_close_index(s, ctx); + + ctx->open = 0; + ctx->last_time = 0; + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_live_record_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) +{ + ngx_live_record_app_conf_t *lracf; + ngx_live_record_ctx_t *ctx; + + if (s->interprocess) { + return next_publish(s, v); + } + + ctx = ngx_pcalloc(s->pool, sizeof(ngx_live_record_ctx_t)); + if (ctx == NULL) { + return NGX_ERROR; + } + ngx_rtmp_set_ctx(s, ctx, ngx_live_record_module); + + lracf = ngx_rtmp_get_module_app_conf(s, ngx_live_record_module); + + if (lracf->record) { + ctx->open = 1; + } + ctx->pubv = *v; + ctx->index.fd = -1; + ctx->file.fd = -1; + + return ngx_live_record_start(s); +} + + +static ngx_int_t +ngx_live_record_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v) +{ + ngx_live_record_ctx_t *ctx; + + if (s->interprocess) { + goto next; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_live_record_module); + if (ctx == NULL) { + goto next; + } + + if (ctx->open == 0) { + goto next; + } + + ngx_live_record_done(s); + + ngx_live_record_close_index(s, ctx); + ctx->open = 0; + +next: + return next_close_stream(s, v); +} + + +static ngx_int_t +ngx_live_record_postconfiguration(ngx_conf_t *cf) +{ + ngx_rtmp_core_main_conf_t *cmcf; + ngx_rtmp_handler_pt *h; + + cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); + + h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AUDIO]); + *h = ngx_live_record_av; + + h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_VIDEO]); + *h = ngx_live_record_av; + + next_record_start = ngx_live_record_start; + ngx_live_record_start = ngx_live_record_start_handle; + + next_record_update = ngx_live_record_update; + ngx_live_record_update = ngx_live_record_update_handle; + + next_record_done = ngx_live_record_done; + ngx_live_record_done = ngx_live_record_done_handle; + + next_publish = ngx_rtmp_publish; + ngx_rtmp_publish = ngx_live_record_publish; + + next_close_stream = ngx_rtmp_close_stream; + ngx_rtmp_close_stream = ngx_live_record_close_stream; + + return NGX_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_record.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_record.h new file mode 100644 index 0000000000..0d008c2097 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_record.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#ifndef _NGX_LIVE_RECORD_H_INCLUDED_ +#define _NGX_LIVE_RECORD_H_INCLUDED_ + + +#include +#include +#include "ngx_rtmp.h" +#include "hls/ngx_rtmp_mpegts.h" + + +typedef struct { + unsigned open; /* 0 close, 1 open, 2 wait for key */ + + time_t last_time; + + ngx_file_t index; + + ngx_rtmp_mpegts_file_t ts; + ngx_file_t file; + + ngx_rtmp_publish_t pubv; + + ngx_uint_t audio_cc; + ngx_uint_t video_cc; + + ngx_msec_t begintime; + ngx_msec_t starttime; + ngx_msec_t endtime; + off_t startsize; + off_t endsize; + + ngx_msec_t publish_epoch; + ngx_msec_t basetime; +} ngx_live_record_ctx_t; + + +typedef ngx_int_t (*ngx_live_record_start_pt)(ngx_rtmp_session_t *s); +typedef ngx_int_t (*ngx_live_record_update_pt)(ngx_rtmp_session_t *s); +typedef ngx_int_t (*ngx_live_record_done_pt)(ngx_rtmp_session_t *s); + + +extern ngx_live_record_start_pt ngx_live_record_start; +extern ngx_live_record_update_pt ngx_live_record_update; +extern ngx_live_record_done_pt ngx_live_record_done; + + +extern ngx_module_t ngx_live_record_module; + + +const char *ngx_live_record_open(ngx_rtmp_session_t *s); +const char *ngx_live_record_close(ngx_rtmp_session_t *s); + + +#endif diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_relay.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_relay.c new file mode 100644 index 0000000000..148c9ffda9 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_relay.c @@ -0,0 +1,418 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#include "ngx_live_relay.h" +#include "ngx_rtmp_cmd_module.h" +#include "ngx_toolkit_misc.h" + + +static ngx_rtmp_publish_pt next_publish; +static ngx_rtmp_play_pt next_play; +static ngx_rtmp_close_stream_pt next_close_stream; + + +static ngx_int_t ngx_live_relay_postconfiguration(ngx_conf_t *cf); +static void *ngx_live_relay_create_app_conf(ngx_conf_t *cf); +static char *ngx_live_relay_merge_app_conf(ngx_conf_t *cf, void *parent, + void *child); + + +ngx_live_push_pt ngx_live_push; +ngx_live_pull_pt ngx_live_pull; +ngx_live_push_close_pt ngx_live_push_close; +ngx_live_pull_close_pt ngx_live_pull_close; + + +typedef ngx_int_t (* relay_create_pt)(ngx_rtmp_session_t *rs, + ngx_live_relay_t *relay, + ngx_live_relay_url_t *url); + + +static relay_create_pt create_relay[] = { + ngx_live_relay_create_httpflv, + ngx_live_relay_create_rtmp +}; + + +static const char *relay_protocol[] = { + "httpflv", + "rtmp" +}; + + +static const char *relay_type[] = { + "push", + "pull" +}; + + +static ngx_command_t ngx_live_relay_commands[] = { + + { ngx_string("failed_reconnect"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_live_relay_app_conf_t, failed_reconnect), + NULL }, + + { ngx_string("relay_reconnect"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_live_relay_app_conf_t, relay_reconnect), + NULL }, + + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_live_relay_module_ctx = { + NULL, /* preconfiguration */ + ngx_live_relay_postconfiguration, /* postconfiguration */ + NULL, /* create main configuration */ + NULL, /* init main configuration */ + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + ngx_live_relay_create_app_conf, /* create app configuration */ + ngx_live_relay_merge_app_conf /* merge app configuration */ +}; + + +ngx_module_t ngx_live_relay_module = { + NGX_MODULE_V1, + &ngx_live_relay_module_ctx, /* module context */ + ngx_live_relay_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void * +ngx_live_relay_create_app_conf(ngx_conf_t *cf) +{ + ngx_live_relay_app_conf_t *racf; + + racf = ngx_pcalloc(cf->pool, sizeof(ngx_live_relay_app_conf_t)); + if (racf == NULL) { + return NULL; + } + + racf->failed_reconnect = NGX_CONF_UNSET_MSEC; + racf->relay_reconnect = NGX_CONF_UNSET_MSEC; + + return racf; +} + + +static char * +ngx_live_relay_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_live_relay_app_conf_t *prev = parent; + ngx_live_relay_app_conf_t *conf = child; + + ngx_conf_merge_msec_value(conf->failed_reconnect, prev->failed_reconnect, + 50); + ngx_conf_merge_msec_value(conf->relay_reconnect, prev->relay_reconnect, + 3000); + + return NGX_CONF_OK; +} + + +ngx_int_t +ngx_live_relay_create(ngx_rtmp_session_t *rs, ngx_live_relay_t *relay) +{ + ngx_live_relay_ctx_t *ctx; + ngx_live_relay_url_t *url; + relay_create_pt create; + + ctx = ngx_rtmp_get_module_ctx(rs, ngx_live_relay_module); + if (ctx->idx >= relay->urls.nelts) { + ctx->idx = 0; + } + ctx->failed_delay = 0; + + url = relay->urls.elts; + url += ctx->idx; + + if (url->relay_type >= NGX_LIVE_RELAY_MAXTYPE) { + ngx_log_error(NGX_LOG_ERR, rs->log, 0, "unsupported relay type %ui", + url->relay_type); + return NGX_ERROR; + } + + create = create_relay[url->relay_type]; + + ngx_log_error(NGX_LOG_INFO, rs->log, 0, + "create %s relay %s to %V:%d, domain='%V' app='%V' name='%V' " + "pargs='%V' referer='%V' user_agent='%V'", + relay_protocol[url->relay_type], relay_type[rs->publishing], + &url->url.host, url->port, &relay->domain, &relay->app, + &relay->name, &relay->pargs, &relay->referer, &relay->user_agent); + + ++ctx->idx; + + return create(rs, relay, url); +} + + +ngx_int_t +ngx_live_relay_play_local(ngx_rtmp_session_t *rs) +{ + ngx_rtmp_play_t v; + + ngx_memzero(&v, sizeof(ngx_rtmp_play_t)); + v.silent = 1; + *(ngx_cpymem(v.name, rs->name.data, ngx_min(sizeof(v.name) - 1, + rs->name.len))) = 0; + if (rs->pargs.len) { + *(ngx_cpymem(v.args, rs->pargs.data, ngx_min(sizeof(v.args) - 1, + rs->pargs.len))) = 0; + } + + return ngx_rtmp_play_filter(rs, &v); +} + + +ngx_int_t +ngx_live_relay_publish_local(ngx_rtmp_session_t *rs) +{ + ngx_rtmp_publish_t v; + + ngx_memzero(&v, sizeof(ngx_rtmp_publish_t)); + v.silent = 1; + *(ngx_cpymem(v.name, rs->name.data, ngx_min(sizeof(v.name) - 1, + rs->name.len))) = 0; + if (rs->pargs.len) { + *(ngx_cpymem(v.args, rs->pargs.data, ngx_min(sizeof(v.args) - 1, + rs->pargs.len))) = 0; + } + + return ngx_rtmp_publish_filter(rs, &v); +} + + +static ngx_int_t +ngx_live_relay_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) +{ + ngx_live_relay_ctx_t *ctx; + + // second publish will not trigger push + if (s->live_stream->publish_ctx->next != NULL) { + goto next; + } + + if (ngx_live_push(s) != NGX_OK) { + return NGX_ERROR; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module); + if (ctx == NULL) { + goto next; + } + + if (ctx->reconnect.timer_set) { + ngx_del_timer(&ctx->reconnect); + } + + if (ctx->reconnect.posted) { + ngx_delete_posted_event(&ctx->reconnect); + } + + ctx->successd = 1; + ctx->failed_reconnect = 0; + + ctx->idx = 0; + +next: + return next_publish(s, v); +} + + +static ngx_int_t +ngx_live_relay_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) +{ + ngx_live_relay_ctx_t *ctx; + + if (ngx_live_pull(s) != NGX_OK) { + return NGX_ERROR; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module); + if (ctx == NULL) { + goto next; + } + + if (ctx->reconnect.timer_set) { + ngx_del_timer(&ctx->reconnect); + } + + if (ctx->reconnect.posted) { + ngx_delete_posted_event(&ctx->reconnect); + } + + ctx->successd = 1; + ctx->failed_reconnect = 0; + + ctx->idx = 0; + +next: + + return next_play(s, v); +} + + +static ngx_int_t +ngx_live_relay_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v) +{ + ngx_rtmp_core_ctx_t *ctx; + ngx_live_relay_ctx_t *rctx; + + if (s->live_stream == NULL) { + goto next; + } + + if (s->publishing) { + /* + * normal publisher close or relay puller close + * need to trigger ngx_live_pull_close + */ + ngx_live_pull_close(s); + + // all publishers close, close push (play + relay) + if (s->live_stream->publish_ctx == NULL) { + for (ctx = s->live_stream->play_ctx; ctx; ctx = ctx->next) { + if (ctx->session->relay) { + ctx->session->finalize_reason = NGX_LIVE_RELAY_CLOSE; + ngx_rtmp_finalize_session(ctx->session); + } + } + } + } else { + rctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module); + if (rctx) { + // push close will trigger relay push reconnect in relay modules + ngx_live_push_close(s); + } + + // all players close, close pull (publish + relay) + if (s->live_stream->play_ctx == NULL) { + for (ctx = s->live_stream->publish_ctx; ctx; ctx = ctx->next) { + if (ctx->session->relay) { + ctx->session->finalize_reason = NGX_LIVE_RELAY_CLOSE; + ngx_rtmp_finalize_session(ctx->session); + } + } + } + } + +next: + return next_close_stream(s, v); +} + + +static ngx_int_t +ngx_live_relay_push(ngx_rtmp_session_t *s) +{ + return NGX_OK; +} + + +static ngx_int_t +ngx_live_relay_pull(ngx_rtmp_session_t *s) +{ + return NGX_OK; +} + + +static ngx_int_t +ngx_live_relay_push_close(ngx_rtmp_session_t *s) +{ + ngx_live_relay_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module); + + if (ctx && ctx->reconnect.timer_set) { + ngx_del_timer(&ctx->reconnect); + } + + if (ctx && ctx->reconnect.posted) { + ngx_delete_posted_event(&ctx->reconnect); + } + + return NGX_OK; +} + + +/* + * pull reconnect need to trigger ngx_live_pull chain + */ +static ngx_int_t +ngx_live_relay_pull_close(ngx_rtmp_session_t *s) +{ + ngx_live_relay_ctx_t *ctx; + ngx_flag_t has_player; + ngx_rtmp_core_ctx_t *cctx;; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module); + + has_player = 0; + + if (s->static_pull == 0) { + for (cctx = s->live_stream->play_ctx; cctx; cctx = cctx->next) { + if (cctx->session->relay == 0) { // has pure player, not relay push + has_player = 1; + break; + } + } + } + + if (has_player || s->static_pull) { // has player in stream + // ctx is NULL, s is a normal publisher + // ctx is not NULL, s is a puller, if giveup flag set + // no need to create pull reconnect + if (ctx == NULL || !ctx->giveup) { + ngx_live_pull(s); + } + } + + if (ctx && ctx->reconnect.timer_set) { + ngx_del_timer(&ctx->reconnect); + } + + if (ctx && ctx->reconnect.posted) { + ngx_delete_posted_event(&ctx->reconnect); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_live_relay_postconfiguration(ngx_conf_t *cf) +{ + next_publish = ngx_rtmp_publish; + ngx_rtmp_publish = ngx_live_relay_publish; + + next_play = ngx_rtmp_play; + ngx_rtmp_play = ngx_live_relay_play; + + next_close_stream = ngx_rtmp_close_stream; + ngx_rtmp_close_stream = ngx_live_relay_close_stream; + + ngx_live_pull = ngx_live_relay_pull; + ngx_live_push = ngx_live_relay_push; + ngx_live_pull_close = ngx_live_relay_pull_close; + ngx_live_push_close = ngx_live_relay_push_close; + + return NGX_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_relay.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_relay.h new file mode 100644 index 0000000000..b52405b555 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_relay.h @@ -0,0 +1,99 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#ifndef _NGX_LIVE_RELAY_H_INCLUDE_ +#define _NGX_LIVE_RELAY_H_INCLUDE_ + + +#include +#include "ngx_rtmp.h" + + +#define NGX_LIVE_RELAY_HTTPFLV 0 +#define NGX_LIVE_RELAY_RTMP 1 +#define NGX_LIVE_RELAY_MAXTYPE 2 + + +typedef struct { + // reconnect + ngx_event_t reconnect; + ngx_flag_t successd; // connect successd + /* + * connect failed for first time, reconnect immediately + * then reconnect after failed_reconect + * then reconnect after 2 * failed_reconect + * then reconnect after 2 * 2 * failed_reconect + * ... + * when failed_reconnect is bigger than relay_reconnect + * then use relay_reconnect as failed_reconect + * + * connect failed not include connect timeout + */ + ngx_msec_t failed_reconnect; // reconnect timeout + ngx_flag_t failed_delay; + ngx_flag_t giveup; // no need to reconnect + + // base para + ngx_str_t domain; + ngx_str_t app; + ngx_str_t args; // rtmp app args + ngx_str_t name; + ngx_str_t pargs; + + // other para + ngx_str_t referer; // rtmp page_url + ngx_str_t user_agent; // rtmp flashver + ngx_str_t swf_url; // rtmp swf_url + uint32_t acodecs; + uint32_t vcodecs; + + void *tag; + ngx_uint_t idx; +} ngx_live_relay_ctx_t; + + +typedef struct { + ngx_request_url_t url; + in_port_t port; + ngx_uint_t relay_type; +} ngx_live_relay_url_t; + + +typedef struct { + ngx_msec_t failed_reconnect; + ngx_msec_t relay_reconnect; +} ngx_live_relay_app_conf_t; + + +extern ngx_module_t ngx_live_relay_module; + + +ngx_int_t ngx_live_relay_create_httpflv(ngx_rtmp_session_t *rs, + ngx_live_relay_t *relay, ngx_live_relay_url_t *url); + +ngx_int_t ngx_live_relay_create_rtmp(ngx_rtmp_session_t *rs, + ngx_live_relay_t *relay, ngx_live_relay_url_t *url); + +ngx_int_t ngx_live_relay_create(ngx_rtmp_session_t *rs, + ngx_live_relay_t *relay); + +ngx_int_t ngx_live_relay_play_local(ngx_rtmp_session_t *rs); + +ngx_int_t ngx_live_relay_publish_local(ngx_rtmp_session_t *rs); + + +typedef ngx_int_t (*ngx_live_pull_pt)(ngx_rtmp_session_t *s); +typedef ngx_int_t (*ngx_live_push_pt)(ngx_rtmp_session_t *s); +typedef ngx_int_t (*ngx_live_pull_close_pt)(ngx_rtmp_session_t *s); +typedef ngx_int_t (*ngx_live_push_close_pt)(ngx_rtmp_session_t *s); + + +extern ngx_live_push_pt ngx_live_push; +extern ngx_live_pull_pt ngx_live_pull; +extern ngx_live_push_close_pt ngx_live_push_close; +extern ngx_live_pull_close_pt ngx_live_pull_close; + + +#endif diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_relay_httpflv.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_relay_httpflv.c new file mode 100644 index 0000000000..142e023ba0 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_relay_httpflv.c @@ -0,0 +1,637 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#include "ngx_live_relay.h" +#include "ngx_http_client.h" +#include "ngx_toolkit_misc.h" +#include "ngx_dynamic_resolver.h" +#include "ngx_rbuf.h" + + +typedef struct { + ngx_uint_t status; + char *code; + char *level; + char *desc; +} ngx_http_status_code_t; + + +static ngx_http_status_code_t ngx_http_relay_status_code[] = { + { 400, "NetStream.Play.BadName", "error", "Bad Request" }, + { 403, "NetStream.Play.Forbidden", "error", "Forbidden" }, + { 404, "NetStream.Play.StreamNotFound", "error", "No such stream" }, + { 503, "NetStream.Play.ServiceUnavailable", "error", "Service Unavailable"}, + { 0, "NetStream.Play.StreamError", "error", "Stream Error" } +}; + + +static ngx_int_t +ngx_live_relay_httpflv_parse(ngx_rtmp_session_t *s, ngx_buf_t *b) +{ + u_char ch, *p, *pc; + ngx_rtmp_stream_t *st; + ngx_rtmp_header_t *h; + ngx_chain_t **ll; + size_t len; + ngx_rtmp_core_srv_conf_t *cscf; + ngx_int_t rc = NGX_AGAIN; + enum { + flv_header_F = 0, + flv_header_FL, + flv_header_FLV, + flv_header_Version, + flv_header_Flags, + flv_header_DataOffset0, + flv_header_DataOffset1, + flv_header_DataOffset2, + flv_header_DataOffset3, + flv_tagsize0, + flv_tagsize1, + flv_tagsize2, + flv_tagsize3, + flv_tagtype, + flv_datasize0, + flv_datasize1, + flv_datasize2, + flv_timestamp0, + flv_timestamp1, + flv_timestamp2, + flv_timestamp_extended, + flv_streamid0, + flv_streamid1, + flv_streamid2, + flv_data + } state; + + state = s->flv_state; + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + for (p = b->pos; p < b->last; ++p) { + ch = *p; + + switch (state) { + + case flv_header_F: + switch (ch) { + case 'F': + state = flv_header_FL; + break; + default: + rc = NGX_ERROR; + goto done; + } + break; + + case flv_header_FL: + switch (ch) { + case 'L': + state = flv_header_FLV; + break; + default: + rc = NGX_ERROR; + goto done; + } + break; + + case flv_header_FLV: + switch (ch) { + case 'V': + state = flv_header_Version; + break; + default: + rc = NGX_ERROR; + goto done; + } + break; + + case flv_header_Version: + s->flv_version = ch; + if (s->flv_version != 1) { + rc = NGX_ERROR; + goto done; + } + state = flv_header_Flags; + break; + + case flv_header_Flags: + s->flv_flags = ch; + state = flv_header_DataOffset0; + break; + + case flv_header_DataOffset0: + pc = (u_char *) &s->flv_data_offset; + pc[3] = ch; + state = flv_header_DataOffset1; + break; + + case flv_header_DataOffset1: + pc = (u_char *) &s->flv_data_offset; + pc[2] = ch; + state = flv_header_DataOffset2; + break; + + case flv_header_DataOffset2: + pc = (u_char *) &s->flv_data_offset; + pc[1] = ch; + state = flv_header_DataOffset3; + break; + + case flv_header_DataOffset3: + pc = (u_char *) &s->flv_data_offset; + pc[0] = ch; + state = flv_tagsize0; + break; + + case flv_tagsize0: + s->flv_tagsize = 0; + pc = (u_char *) &s->flv_tagsize; + pc[3] = ch; + state = flv_tagsize1; + break; + + case flv_tagsize1: + pc = (u_char *) &s->flv_tagsize; + pc[2] = ch; + state = flv_tagsize2; + break; + + case flv_tagsize2: + pc = (u_char *) &s->flv_tagsize; + pc[1] = ch; + state = flv_tagsize3; + break; + + case flv_tagsize3: + pc = (u_char *) &s->flv_tagsize; + pc[0] = ch; + + st = &s->in_streams[0]; + h = &st->hdr; + + if (h->mlen == 0 && s->flv_first_pts == 0) { + s->flv_first_pts = 1; + if (s->flv_tagsize != 0) { + rc = NGX_ERROR; + goto done; + } + } else { + if (h->mlen + 11 != s->flv_tagsize) { + rc = NGX_ERROR; + goto done; + } + } + state = flv_tagtype; + + break; + + case flv_tagtype: + if (ch != NGX_RTMP_MSG_AMF_META && ch != NGX_RTMP_MSG_AUDIO + && ch != NGX_RTMP_MSG_VIDEO) + { + rc = NGX_ERROR; + goto done; + } + + st = &s->in_streams[0]; + h = &st->hdr; + h->type = ch; + state = flv_datasize0; + + break; + + case flv_datasize0: + st = &s->in_streams[0]; + h = &st->hdr; + h->mlen = 0; + pc = (u_char *) &h->mlen; + + pc[2] = ch; + state = flv_datasize1; + + break; + + case flv_datasize1: + st = &s->in_streams[0]; + h = &st->hdr; + pc = (u_char *) &h->mlen; + + pc[1] = ch; + state = flv_datasize2; + + break; + + case flv_datasize2: + st = &s->in_streams[0]; + h = &st->hdr; + pc = (u_char *) &h->mlen; + + pc[0] = ch; + state = flv_timestamp0; + st->len = h->mlen; + + break; + + case flv_timestamp0: + st = &s->in_streams[0]; + h = &st->hdr; + pc = (u_char *) &h->timestamp; + + pc[2] = ch; + state = flv_timestamp1; + + break; + + case flv_timestamp1: + st = &s->in_streams[0]; + h = &st->hdr; + pc = (u_char *) &h->timestamp; + + pc[1] = ch; + state = flv_timestamp2; + + break; + + case flv_timestamp2: + st = &s->in_streams[0]; + h = &st->hdr; + pc = (u_char *) &h->timestamp; + + pc[0] = ch; + state = flv_timestamp_extended; + + break; + + case flv_timestamp_extended: + st = &s->in_streams[0]; + h = &st->hdr; + pc = (u_char *) &h->timestamp; + + pc[3] = ch; + state = flv_streamid0; + + break; + + case flv_streamid0: + st = &s->in_streams[0]; + h = &st->hdr; + h->msid = 0; + pc = (u_char *) &h->msid; + + pc[2] = ch; + state = flv_streamid1; + + break; + + case flv_streamid1: + st = &s->in_streams[0]; + h = &st->hdr; + pc = (u_char *) &h->msid; + + pc[1] = ch; + state = flv_streamid2; + + break; + + case flv_streamid2: + st = &s->in_streams[0]; + h = &st->hdr; + pc = (u_char *) &h->msid; + + pc[0] = ch; + state = flv_data; + + break; + + case flv_data: + st = &s->in_streams[0]; + + for (ll = &st->in; (*ll) && (*ll)->buf->last == (*ll)->buf->end; + ll = &(*ll)->next); + + for (;;) { + if (*ll == NULL) { + *ll = ngx_get_chainbuf(cscf->chunk_size, 1); + } + + len = ngx_min(st->len, b->last - p); + if ((*ll)->buf->end - (*ll)->buf->last >= (long) len) { + (*ll)->buf->last = ngx_cpymem((*ll)->buf->last, p, len); + p += len; + st->len -= len; + + break; + } + + len = (*ll)->buf->end - (*ll)->buf->last; + (*ll)->buf->last = ngx_cpymem((*ll)->buf->last, p, len); + p += len; + st->len -= len; + + ll = &(*ll)->next; + } + + if (st->len != 0) { + rc = NGX_AGAIN; + goto done; + } + + state = flv_tagsize0; + rc = NGX_OK; + goto done; + } + } + +done: + b->pos = p; + s->flv_state = state; + + return rc; +} + + +static void +ngx_live_relay_httpflv_recv_body(void *request, ngx_http_request_t *hcr) +{ + ngx_int_t n; + ngx_rtmp_session_t *s; + ngx_chain_t *cl, *l, *in; + ngx_rtmp_header_t *h; + ngx_rtmp_stream_t *st = NULL; + + s = request; + + n = ngx_http_client_read_body(hcr, &cl); + + if (n == 0 || n == NGX_ERROR) { + s->finalize_reason = n == 0? NGX_LIVE_NORMAL_CLOSE: + NGX_LIVE_FLV_RECV_ERR; + ngx_log_error(NGX_LOG_INFO, s->log, ngx_errno, + "http relay, recv body error"); + ngx_rtmp_finalize_session(s); + return; + } + + l = cl; + for (;;) { + if (l && l->buf->pos == l->buf->last) { + l = l->next; + } + + if (l == NULL) { + return; + } + + n = ngx_live_relay_httpflv_parse(s, l->buf); + + if (n == NGX_ERROR) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "http relay, parse flv frame failed in state %d", + s->flv_state); + ngx_http_client_finalize_request(hcr, 1); + + return; + } + + if (n == NGX_AGAIN) { + continue; + } + + /* NGX_OK */ + st = &s->in_streams[0]; + h = &st->hdr; + in = st->in; + + if (ngx_rtmp_receive_message(s, h, in) != NGX_OK) { + ngx_rtmp_finalize_session(s); + return; + } + + ngx_put_chainbufs(st->in); + st->in = NULL; + } +} + + +static void +ngx_live_relay_httpflv_cleanup(void *data) +{ + ngx_rtmp_session_t *s; + + s = data; + + ngx_log_error(NGX_LOG_INFO, s->log, 0, "http flv client, cleanup"); + + if (s) { + if (s->close.posted) { + ngx_delete_posted_event(&s->close); + } + + if (s->finalize_reason == 0) { + s->finalize_reason = NGX_LIVE_FLV_RECV_ERR; + } + + ngx_rtmp_finalize_fake_session(s); + } +} + + +static void +ngx_live_relay_httpflv_error(ngx_rtmp_session_t *s, ngx_uint_t status) +{ + ngx_live_stream_t *st; + ngx_rtmp_core_ctx_t *cctx; + char *code, *level, *desc; + size_t i; + + for (i = 0; ngx_http_relay_status_code[i].status; ++i) { + + if (status != ngx_http_relay_status_code[i].status) { + continue; + } + + break; + } + + code = ngx_http_relay_status_code[i].code; + level = ngx_http_relay_status_code[i].level; + desc = ngx_http_relay_status_code[i].desc; + + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "http relay transit, %d: level='%s' code='%s' description='%s'", + status, level, code, desc); + + st = ngx_live_create_stream(&s->serverid, &s->stream); + cctx = st->play_ctx; + + for (; cctx; cctx = cctx->next) { + cctx->session->status = status; + ngx_rtmp_send_status(cctx->session, code, level, desc); + + if (ngx_strcmp(level, "error") == 0 && !cctx->session->static_pull) { + cctx->session->finalize_reason = NGX_LIVE_RELAY_TRANSIT; + ngx_rtmp_finalize_session(cctx->session); + } + } +} + + +static void +ngx_live_relay_httpflv_recv(void *request, ngx_http_request_t *hcr) +{ + ngx_rtmp_session_t *s; + ngx_uint_t status_code; + + s = request; + status_code = ngx_http_client_status_code(hcr); + + s->stage = NGX_LIVE_PLAY; + s->ptime = ngx_current_msec; + + s->connection = hcr->connection; + ngx_rtmp_set_combined_log(s, hcr->connection->log->data, + hcr->connection->log->handler); + s->log->connection = s->connection->number; + + if (ngx_rtmp_core_main_conf->fast_reload && (ngx_exiting || ngx_terminate)) { + ngx_live_relay_httpflv_error(s, NGX_LIVE_PROCESS_EXIT); + s->finalize_reason = NGX_LIVE_PROCESS_EXIT; + ngx_http_client_finalize_request(hcr, 1); + return; + } + + if (status_code != NGX_HTTP_OK) { + ngx_live_relay_httpflv_error(s, status_code); + s->finalize_reason = NGX_LIVE_FLV_RECV_ERR; + ngx_http_client_finalize_request(hcr, 1); + return; + } + + ngx_live_relay_publish_local(s); + + ngx_http_client_set_read_handler(hcr, ngx_live_relay_httpflv_recv_body); + ngx_live_relay_httpflv_recv_body(request, hcr); +} + + +static ngx_int_t +ngx_live_relay_httpflv_send_request(ngx_rtmp_session_t *s, + ngx_live_relay_url_t *url) +{ + ngx_http_request_t *hcr; + ngx_str_t request_url; + size_t len; + ngx_live_relay_ctx_t *ctx; + ngx_http_cleanup_t *cln; + u_char *p; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module); + + // request url + // http:// + url + ':' + port + '/' + app + '/' + name + ['?' + pargs] + len = 7 + url->url.host.len + 1 + sizeof("65535") - 1 + + 1 + ctx->app.len + 1 + ctx->name.len; + if (ctx->pargs.len) { + len = len + 1 + ctx->pargs.len; + } + + request_url.data = ngx_pcalloc(s->pool, len); + if (request_url.data == NULL) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "alloc mem for request url failed"); + return NGX_ERROR; + } + + if (ctx->pargs.len) { + p = ngx_snprintf(request_url.data, len, "http://%V:%d/%V/%V?%V", + &url->url.host, url->port, &ctx->app, &ctx->name, &ctx->pargs); + } else { + p = ngx_snprintf(request_url.data, len, "http://%V:%d/%V/%V", + &url->url.host, url->port, &ctx->app, &ctx->name); + } + request_url.len = p - request_url.data; + + // request headers + ngx_keyval_t headers[] = { + { ngx_string("Host"), ctx->domain }, + { ngx_string("Referer"), ctx->referer }, + { ngx_string("User-Agent"), ctx->user_agent }, + { ngx_null_string, ngx_null_string } + }; + + s->stage = NGX_LIVE_CONNECT; + s->connect_time = ngx_current_msec; + + hcr = ngx_http_client_get(s->log, &request_url, headers, s); + ngx_http_client_set_read_handler(hcr, ngx_live_relay_httpflv_recv); + + cln = ngx_http_client_cleanup_add(hcr, 0); + if (cln == NULL) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, "http client add cleanup failed"); + ngx_live_relay_httpflv_error(s, NGX_HTTP_INTERNAL_SERVER_ERROR); + ngx_http_client_finalize_request(hcr, 1); + return NGX_ERROR; + } + cln->handler = ngx_live_relay_httpflv_cleanup; + cln->data = s; + + s->request = hcr; + s->live_type = NGX_HTTP_FLV_LIVE; + + return NGX_OK; +} + + +ngx_int_t +ngx_live_relay_create_httpflv(ngx_rtmp_session_t *s, ngx_live_relay_t *relay, + ngx_live_relay_url_t *url) +{ + ngx_live_relay_ctx_t *rctx; + // must use ngx_sockaddr_t, because sizeof(struct sockaddr) + // is not long enouph, content will be covered by other var + ngx_sockaddr_t nsa; + struct sockaddr *sa; + socklen_t len; + + rctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module); + if (rctx == NULL) { + return NGX_ERROR; + } + +#define NGX_LIVE_RELAY_CTX(para) \ + if (ngx_copy_str(s->pool, &rctx->para, &relay->para) != NGX_OK) { \ + goto destroy; \ + } + + NGX_LIVE_RELAY_CTX(domain); + NGX_LIVE_RELAY_CTX(app); + NGX_LIVE_RELAY_CTX(name); + NGX_LIVE_RELAY_CTX(pargs); + NGX_LIVE_RELAY_CTX(referer); + NGX_LIVE_RELAY_CTX(user_agent); +#undef NGX_LIVE_RELAY_CTX + + rctx->tag = relay->tag; + + // get address, host in url must be resolv sync + sa = (struct sockaddr *) &nsa; + len = ngx_dynamic_resolver_gethostbyname(&url->url.host, sa); + if (len == 0) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "relay httpflv: gethostbyname failed %V", &url->url.host); + goto destroy; + } + + // send http request + if (ngx_live_relay_httpflv_send_request(s, url) != NGX_OK) { + goto destroy; + } + + return NGX_OK; + +destroy: + ngx_rtmp_finalize_session(s); + + return NGX_ERROR; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_relay_inner.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_relay_inner.c new file mode 100644 index 0000000000..235b3f1d74 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_relay_inner.c @@ -0,0 +1,399 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#include +#include +#include "ngx_live_relay.h" +#include "ngx_stream_zone_module.h" +#include "ngx_multiport.h" + + +static ngx_live_push_pt next_push; +static ngx_live_pull_pt next_pull; +static ngx_live_push_close_pt next_push_close; +static ngx_live_pull_close_pt next_pull_close; + + +static void *ngx_live_relay_inner_create_app_conf(ngx_conf_t *cf); +static char *ngx_live_relay_inner_merge_app_conf(ngx_conf_t *cf, + void *parent, void *child); +static ngx_int_t ngx_live_relay_inner_postconfiguration(ngx_conf_t *cf); + + +typedef struct { + ngx_flag_t inner_pull; + ngx_str_t inner_pull_port; +} ngx_live_relay_inner_app_conf_t; + + +static ngx_command_t ngx_live_relay_inner_commands[] = { + + { ngx_string("rtmp_auto_pull"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_live_relay_inner_app_conf_t, inner_pull), + NULL }, + + { ngx_string("rtmp_auto_pull_port"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_live_relay_inner_app_conf_t, inner_pull_port), + NULL }, + + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_live_relay_inner_module_ctx = { + NULL, /* preconfiguration */ + ngx_live_relay_inner_postconfiguration, /* postconfiguration */ + NULL, /* create main configuration */ + NULL, /* init main configuration */ + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + ngx_live_relay_inner_create_app_conf, /* create app configuration */ + ngx_live_relay_inner_merge_app_conf /* merge app configuration */ +}; + + +ngx_module_t ngx_live_relay_inner_module = { + NGX_MODULE_V1, + &ngx_live_relay_inner_module_ctx, /* module context */ + ngx_live_relay_inner_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void * +ngx_live_relay_inner_create_app_conf(ngx_conf_t *cf) +{ + ngx_live_relay_inner_app_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_live_relay_inner_app_conf_t)); + if (conf == NULL) { + return NULL; + } + + conf->inner_pull = NGX_CONF_UNSET; + + return conf; +} + +static char * +ngx_live_relay_inner_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_live_relay_inner_app_conf_t *prev = parent; + ngx_live_relay_inner_app_conf_t *conf = child; + + ngx_conf_merge_value(conf->inner_pull, prev->inner_pull, 1); + ngx_conf_merge_str_value(conf->inner_pull_port, prev->inner_pull_port, + "unix:/tmp/inner.sock"); + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_live_relay_inner_create_relay(ngx_rtmp_session_t *rs, + ngx_live_relay_t *relay, ngx_int_t pslot) +{ + ngx_live_relay_inner_app_conf_t *riacf; + ngx_live_relay_url_t *url; + ngx_str_t port; + + riacf = ngx_rtmp_get_module_app_conf(rs, ngx_live_relay_inner_module); + + ngx_memzero(relay, sizeof(ngx_live_relay_t)); + if (ngx_array_init(&relay->urls, rs->pool, 1, sizeof(ngx_live_relay_url_t)) + != NGX_OK) + { + ngx_log_error(NGX_LOG_ERR, rs->log, 0, + "inner relay, relay init error: %V", &riacf->inner_pull_port); + return NGX_ERROR; + } + + url = ngx_array_push(&relay->urls); + if (url == NULL) { + ngx_log_error(NGX_LOG_ERR, rs->log, 0, + "inner relay, get url failed: %V", &riacf->inner_pull_port); + return NGX_ERROR; + } + relay->tag = &ngx_live_relay_inner_module; + + ngx_memzero(url, sizeof(ngx_live_relay_url_t)); + ngx_memzero(&port, sizeof(ngx_str_t)); + + if (ngx_multiport_get_port(rs->pool, &port, + &riacf->inner_pull_port, pslot) == NGX_ERROR) + { + ngx_log_error(NGX_LOG_ERR, rs->log, 0, + "inner relay, get mulitport error: %V", + &riacf->inner_pull_port); + return NGX_ERROR; + } + + url->url.host = port; + url->url.host_with_port = port; + url->relay_type = NGX_LIVE_RELAY_RTMP; + + return NGX_OK; +} + + +static void +ngx_live_relay_inner_handler(ngx_event_t *ev) +{ + ngx_rtmp_session_t *s; + ngx_live_relay_app_conf_t *lracf; + ngx_live_relay_ctx_t *ctx; + ngx_live_relay_t relay; + + s = ev->data; + ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module); + + if (!ctx->failed_delay && ev->timedout) { // connect timeout + ngx_log_error(NGX_LOG_ERR, s->log, NGX_ETIMEDOUT, + "inner relay, relay timeout"); + s->finalize_reason = NGX_LIVE_RELAY_TIMEOUT; + ngx_rtmp_finalize_session(s); + + return; + } + + // relay pull, no player or relay push no publisher + if ((s->publishing && s->live_stream->play_ctx == NULL) + || (!s->publishing && s->live_stream->publish_ctx == NULL)) + { + return; + } + + lracf = ngx_rtmp_get_module_app_conf(s, ngx_live_relay_module); + + ngx_add_timer(&ctx->reconnect, lracf->relay_reconnect); + + if (ngx_live_relay_inner_create_relay(s, &relay, s->live_stream->pslot) + != NGX_OK) + { + return; + } + + ngx_live_relay_create(s, &relay); +} + + +// if stream's need to continue pull or push chain, otherwise return NGX_OK +static ngx_int_t +ngx_live_relay_inner_relay(ngx_rtmp_session_t *s, unsigned publishing) +{ + ngx_rtmp_session_t *rs; + ngx_live_relay_ctx_t *ctx, *pctx; + ngx_live_relay_app_conf_t *lracf; + ngx_int_t pslot; + + pslot = ngx_stream_zone_insert_stream(&s->stream); + if (pslot == NGX_ERROR) { // stream zone not configured or configured error + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "inner relay, insert stream %V failed", &s->stream); + return NGX_DECLINED; + } + + ngx_log_error(NGX_LOG_INFO, s->log, 0, + "inner relay, stream %V not in current process, " + "pslot:%i ngx_process_slot:%i", + &s->stream, pslot, ngx_process_slot); + + s->live_stream->pslot = pslot; + if (pslot == ngx_process_slot) { // current process become stream owner + return NGX_DECLINED; + } + + rs = ngx_rtmp_create_relay_session(s, &ngx_live_relay_inner_module); + if (rs == NULL) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "inner relay, create relay session failed"); + return NGX_DECLINED; + } + rs->publishing = publishing; + rs->live_stream = s->live_stream; + ngx_live_create_ctx(rs, publishing); + + ctx = ngx_rtmp_get_module_ctx(rs, ngx_live_relay_module); + ctx->reconnect.log = rs->log; + ctx->reconnect.data = rs; + ctx->reconnect.handler = ngx_live_relay_inner_handler; + + lracf = ngx_rtmp_get_module_app_conf(rs, ngx_live_relay_module); + + // play trigger pull or publish trigger push + if (s->publishing != rs->publishing) { + ngx_post_event(&ctx->reconnect, &ngx_posted_events); + return NGX_OK; + } + + // normal publisher close, need to trigger pull + if (s->publishing && !s->relay) { + ngx_post_event(&ctx->reconnect, &ngx_posted_events); + return NGX_OK; + } + + // reconnect + pctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module); + if (pctx->successd) { // prev relay susccessd + ngx_post_event(&ctx->reconnect, &ngx_posted_events); + return NGX_OK; + } + + if (!pctx->reconnect.timer_set) { // prev relay timeout + ctx->failed_reconnect = ngx_min(pctx->failed_reconnect * 2, + lracf->relay_reconnect); + ngx_post_event(&ctx->reconnect, &ngx_posted_events); + return NGX_OK; + } + + if (pctx->failed_reconnect) { + ctx->failed_reconnect = ngx_min(pctx->failed_reconnect * 2, + lracf->relay_reconnect); + } else { + ctx->failed_reconnect = lracf->failed_reconnect; + } + + ctx->failed_delay = 1; + ngx_add_timer(&ctx->reconnect, ctx->failed_reconnect); + + return NGX_OK; +} + + +static ngx_int_t +ngx_live_relay_inner_push(ngx_rtmp_session_t *s) +{ + ngx_live_relay_inner_app_conf_t *riacf; + + riacf = ngx_rtmp_get_module_app_conf(s, ngx_live_relay_inner_module); + + if (!riacf->inner_pull) { + goto next; + } + + if (s->relay) { + goto next; + } + + if (ngx_live_relay_inner_relay(s, 0) == NGX_OK) { + return NGX_OK; + } + +next: + return next_push(s); +} + + +static ngx_int_t +ngx_live_relay_inner_pull(ngx_rtmp_session_t *s) +{ + ngx_live_relay_inner_app_conf_t *riacf; + + riacf = ngx_rtmp_get_module_app_conf(s, ngx_live_relay_inner_module); + + if (!riacf->inner_pull) { + goto next; + } + + if (!s->publishing && s->relay) { // relay push + goto next; + } + + if (s->live_stream->pslot != -1) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "inner relay, stream %V already in current process", + &s->stream); + if (s->live_stream->pslot == ngx_process_slot) { + // stream leader is in current process, continue pull or push chain + goto next; + } + + return NGX_OK; + } + + if (ngx_live_relay_inner_relay(s, 1) == NGX_OK) { + return NGX_OK; + } + +next: + return next_pull(s); +} + + +static ngx_int_t +ngx_live_relay_inner_push_close(ngx_rtmp_session_t *s) +{ + ngx_live_relay_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module); + if (ctx->tag != &ngx_live_relay_inner_module) { + goto next; + } + + // relay push and has publisher in stream + if (!ctx->giveup && s->live_stream->publish_ctx) { + ngx_live_relay_inner_relay(s, 0); + } + +next: + return next_push_close(s); +} + + +static ngx_int_t +ngx_live_relay_inner_pull_close(ngx_rtmp_session_t *s) +{ + ngx_live_relay_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module); + if (ctx == NULL) { + goto next; + } + + if (ctx->tag != &ngx_live_relay_inner_module) { + goto next; + } + + // inner relay close + s->live_stream->pslot = -1; + +next: + return next_pull_close(s); +} + + +static ngx_int_t +ngx_live_relay_inner_postconfiguration(ngx_conf_t *cf) +{ + /* chain handlers */ + + next_push = ngx_live_push; + ngx_live_push = ngx_live_relay_inner_push; + + next_pull = ngx_live_pull; + ngx_live_pull = ngx_live_relay_inner_pull; + + next_push_close = ngx_live_push_close; + ngx_live_push_close = ngx_live_relay_inner_push_close; + + next_pull_close = ngx_live_pull_close; + ngx_live_pull_close = ngx_live_relay_inner_pull_close; + + return NGX_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_relay_rtmp.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_relay_rtmp.c new file mode 100644 index 0000000000..f510991cf5 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_relay_rtmp.c @@ -0,0 +1,860 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#include "ngx_live_relay.h" +#include "ngx_poold.h" +#include "ngx_toolkit_misc.h" +#include "ngx_dynamic_resolver.h" + + +static ngx_int_t ngx_live_relay_rtmp_postconfiguration(ngx_conf_t *cf); +static void *ngx_live_relay_rtmp_create_app_conf(ngx_conf_t *cf); +static char *ngx_live_relay_rtmp_merge_app_conf(ngx_conf_t *cf, void *parent, + void *child); + + +typedef struct { + ngx_msec_t buflen; +} ngx_live_relay_rtmp_app_conf_t; + + +typedef struct { + char *code; + ngx_uint_t status; + ngx_flag_t finalize; +} ngx_rtmp_status_code_t; + + +static ngx_rtmp_status_code_t ngx_rtmp_relay_status_error_code[] = { + { "NetStream.Publish.BadName", 400, 1 }, + { "NetStream.Stream.Forbidden", 403, 1 }, + { "NetStream.Play.StreamNotFound", 404, 1 }, + { "NetStream.Relay.ServerError", 500, 1 }, + { NULL, 0, 0 } +}; + + +#define NGX_RTMP_RELAY_CONNECT_TRANS 1 +#define NGX_RTMP_RELAY_CREATE_STREAM_TRANS 2 + + +#define NGX_RTMP_RELAY_CSID_AMF_INI 3 +#define NGX_RTMP_RELAY_CSID_AMF 5 +#define NGX_RTMP_RELAY_MSID 1 + + +static ngx_command_t ngx_live_relay_rtmp_commands[] = { + + { ngx_string("relay_buffer"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_live_relay_rtmp_app_conf_t, buflen), + NULL }, + + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_live_relay_rtmp_module_ctx = { + NULL, /* preconfiguration */ + ngx_live_relay_rtmp_postconfiguration, /* postconfiguration */ + NULL, /* create main configuration */ + NULL, /* init main configuration */ + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + ngx_live_relay_rtmp_create_app_conf, /* create app configuration */ + ngx_live_relay_rtmp_merge_app_conf /* merge app configuration */ +}; + + +ngx_module_t ngx_live_relay_rtmp_module = { + NGX_MODULE_V1, + &ngx_live_relay_rtmp_module_ctx, /* module context */ + ngx_live_relay_rtmp_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void * +ngx_live_relay_rtmp_create_app_conf(ngx_conf_t *cf) +{ + ngx_live_relay_rtmp_app_conf_t *racf; + + racf = ngx_pcalloc(cf->pool, sizeof(ngx_live_relay_rtmp_app_conf_t)); + if (racf == NULL) { + return NULL; + } + + racf->buflen = NGX_CONF_UNSET_MSEC; + + return racf; +} + + +static char * +ngx_live_relay_rtmp_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_live_relay_rtmp_app_conf_t *prev = parent; + ngx_live_relay_rtmp_app_conf_t *conf = child; + + ngx_conf_merge_msec_value(conf->buflen, prev->buflen, 5000); + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_live_relay_rtmp_send_connect(ngx_rtmp_session_t *s) +{ + ngx_str_t app, tcurl; + double acodecs = 3575, vcodecs = 252; + static double trans = NGX_RTMP_RELAY_CONNECT_TRANS; + + static ngx_rtmp_amf_elt_t out_cmd[] = { + + { NGX_RTMP_AMF_STRING, + ngx_string("app"), + NULL, 0 }, /* <-- fill */ + + { NGX_RTMP_AMF_STRING, + ngx_string("tcUrl"), + NULL, 0 }, /* <-- fill */ + + { NGX_RTMP_AMF_STRING, + ngx_string("pageUrl"), + NULL, 0 }, /* <-- fill */ + + { NGX_RTMP_AMF_STRING, + ngx_string("swfUrl"), + NULL, 0 }, /* <-- fill */ + + { NGX_RTMP_AMF_STRING, + ngx_string("flashVer"), + NULL, 0 }, /* <-- fill */ + + { NGX_RTMP_AMF_NUMBER, + ngx_string("audioCodecs"), + NULL, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("videoCodecs"), + NULL, 0 } + }; + + static ngx_rtmp_amf_elt_t out_elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + "connect", 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &trans, 0 }, + + { NGX_RTMP_AMF_OBJECT, + ngx_null_string, + out_cmd, sizeof(out_cmd) } + }; + + ngx_rtmp_core_app_conf_t *cacf; + ngx_rtmp_core_srv_conf_t *cscf; + ngx_live_relay_ctx_t *ctx; + ngx_rtmp_header_t h; + + + cacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_core_module); + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module); + if (cacf == NULL || ctx == NULL) { + return NGX_ERROR; + } + + /* app */ + if (ctx->args.len) { + app.len = ctx->app.len + 1 + ctx->args.len; + app.data = ngx_pcalloc(s->pool, app.len); + if (app.data == NULL) { + return NGX_ERROR; + } + + ngx_snprintf(app.data, app.len, "%V?%V", &ctx->app, &ctx->args); + } else { + app = ctx->app; + } + + out_cmd[0].data = app.data; + out_cmd[0].len = app.len; + + /* tcUrl */ + tcurl.len = sizeof("rtmp://") - 1 + ctx->domain.len + 1 + ctx->app.len; + tcurl.data = ngx_palloc(s->pool, tcurl.len); + if (tcurl.data == NULL) { + return NGX_ERROR; + } + ngx_snprintf(tcurl.data, tcurl.len, "rtmp://%V/%V", + &ctx->domain, &ctx->app); + + out_cmd[1].data = tcurl.data; + out_cmd[1].len = tcurl.len; + + /* pageUrl */ + out_cmd[2].data = ctx->referer.data; + out_cmd[2].len = ctx->referer.len; + + /* swfUrl */ + out_cmd[3].data = ctx->swf_url.data; + out_cmd[3].len = ctx->swf_url.len; + + /* flashVer */ + out_cmd[4].data = ctx->user_agent.data; + out_cmd[4].len = ctx->user_agent.len; + + if (ctx->acodecs != 0) { + acodecs = (double) ctx->acodecs; + } + out_cmd[5].data = &acodecs; + + if (ctx->vcodecs != 0) { + vcodecs = (double) ctx->vcodecs; + } + out_cmd[6].data = &vcodecs; + + ngx_memzero(&h, sizeof(h)); + h.csid = NGX_RTMP_RELAY_CSID_AMF_INI; + h.type = NGX_RTMP_MSG_AMF_CMD; + + s->status = NGX_LIVE_CONNECT; + s->connect_time = ngx_current_msec; + + return ngx_rtmp_send_chunk_size(s, cscf->chunk_size) != NGX_OK + || ngx_rtmp_send_ack_size(s, cscf->ack_window) != NGX_OK + || ngx_rtmp_send_amf(s, &h, out_elts, + sizeof(out_elts) / sizeof(out_elts[0])) != NGX_OK + ? NGX_ERROR + : NGX_OK; +} + + +static ngx_int_t +ngx_live_relay_rtmp_send_create_stream(ngx_rtmp_session_t *s) +{ + static double trans = NGX_RTMP_RELAY_CREATE_STREAM_TRANS; + + static ngx_rtmp_amf_elt_t out_elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + "createStream", 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &trans, 0 }, + + { NGX_RTMP_AMF_NULL, + ngx_null_string, + NULL, 0 } + }; + + ngx_rtmp_header_t h; + + + ngx_memzero(&h, sizeof(h)); + h.csid = NGX_RTMP_RELAY_CSID_AMF_INI; + h.type = NGX_RTMP_MSG_AMF_CMD; + + s->stage = NGX_LIVE_CREATE_STREAM; + s->create_stream_time = ngx_current_msec; + + return ngx_rtmp_send_amf(s, &h, out_elts, + sizeof(out_elts) / sizeof(out_elts[0])); +} + + +static ngx_int_t +ngx_live_relay_rtmp_send_publish(ngx_rtmp_session_t *s) +{ + ngx_str_t name; + static double trans; + + static ngx_rtmp_amf_elt_t out_elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + "publish", 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &trans, 0 }, + + { NGX_RTMP_AMF_NULL, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + NULL, 0 }, /* <- to fill */ + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + "live", 0 } + }; + + ngx_rtmp_header_t h; + ngx_live_relay_ctx_t *ctx; + + + ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module); + if (ctx == NULL) { + return NGX_ERROR; + } + + if (ctx->pargs.len) { + name.len = ctx->name.len + 1 + ctx->pargs.len; + name.data = ngx_pcalloc(s->pool, name.len); + if (name.data == NULL) { + return NGX_ERROR; + } + + ngx_snprintf(name.data, name.len, "%V?%V", &ctx->name, &ctx->pargs); + } else { + name = ctx->name; + } + + out_elts[3].data = name.data; + out_elts[3].len = name.len; + + ngx_memzero(&h, sizeof(h)); + h.csid = NGX_RTMP_RELAY_CSID_AMF; + h.msid = NGX_RTMP_RELAY_MSID; + h.type = NGX_RTMP_MSG_AMF_CMD; + + s->stage = NGX_LIVE_PUBLISH; + s->ptime = ngx_current_msec; + + return ngx_rtmp_send_amf(s, &h, out_elts, + sizeof(out_elts) / sizeof(out_elts[0])); +} + + +static ngx_int_t +ngx_live_relay_rtmp_send_play(ngx_rtmp_session_t *s) +{ + ngx_str_t name; + static double trans; + static double start, duration; + + static ngx_rtmp_amf_elt_t out_elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + "play", 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &trans, 0 }, + + { NGX_RTMP_AMF_NULL, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + NULL, 0 }, /* <- fill */ + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &start, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &duration, 0 }, + }; + + ngx_rtmp_header_t h; + ngx_live_relay_ctx_t *ctx; + ngx_live_relay_rtmp_app_conf_t *racf; + + + racf = ngx_rtmp_get_module_app_conf(s, ngx_live_relay_rtmp_module); + ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module); + if (racf == NULL) { + return NGX_ERROR; + } + + if (ctx->pargs.len) { + name.len = ctx->name.len + 1 + ctx->pargs.len; + name.data = ngx_pcalloc(s->pool, name.len); + if (name.data == NULL) { + return NGX_ERROR; + } + + ngx_snprintf(name.data, name.len, "%V?%V", &ctx->name, &ctx->pargs); + } else { + name = ctx->name; + } + + out_elts[3].data = name.data; + out_elts[3].len = name.len; + + start = -1000; + duration = -1000; + + ngx_memzero(&h, sizeof(h)); + h.csid = NGX_RTMP_RELAY_CSID_AMF; + h.msid = NGX_RTMP_RELAY_MSID; + h.type = NGX_RTMP_MSG_AMF_CMD; + + s->stage = NGX_LIVE_PLAY; + s->ptime = ngx_current_msec; + + return ngx_rtmp_send_amf(s, &h, out_elts, + sizeof(out_elts) / sizeof(out_elts[0])) != NGX_OK + || ngx_rtmp_send_set_buflen(s, NGX_RTMP_RELAY_MSID, + racf->buflen) != NGX_OK + ? NGX_ERROR + : NGX_OK; +} + + +static ngx_int_t +ngx_live_relay_rtmp_status_error(ngx_rtmp_session_t *s, char *type, char *code, + char *level, char *desc) +{ + ngx_rtmp_core_ctx_t *cctx; + size_t i; + ngx_flag_t status = 0; + + if (ngx_strcmp(type, "onStatus") == 0) { + status = 1; + } + + for (i = 0; ngx_rtmp_relay_status_error_code[i].code; ++i) { + + if (ngx_strcmp(ngx_rtmp_relay_status_error_code[i].code, code) + != 0) + { + continue; + } + + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "relay transit, %s: level='%s' code='%s' description='%s'", + type, level, code, desc); + + if (s->publishing) { + cctx = s->live_stream->play_ctx; + } else { + cctx = s->live_stream->publish_ctx; + } + + for (; cctx; cctx = cctx->next) { + cctx->session->status = ngx_rtmp_relay_status_error_code[i].status; + status ? ngx_rtmp_send_status(cctx->session, code, level, desc) + : ngx_rtmp_send_error(cctx->session, code, level, desc); + + if (ngx_rtmp_relay_status_error_code[i].finalize + && !cctx->session->static_pull) + { + cctx->session->finalize_reason = NGX_LIVE_RELAY_TRANSIT; + ngx_rtmp_finalize_session(cctx->session); + } + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_live_relay_rtmp_on_result(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_live_relay_ctx_t *ctx; + static struct { + double trans; + u_char level[32]; + u_char code[128]; + u_char desc[1024]; + } v; + + static ngx_rtmp_amf_elt_t in_inf[] = { + + { NGX_RTMP_AMF_STRING, + ngx_string("level"), + &v.level, sizeof(v.level) }, + + { NGX_RTMP_AMF_STRING, + ngx_string("code"), + &v.code, sizeof(v.code) }, + + { NGX_RTMP_AMF_STRING, + ngx_string("description"), + &v.desc, sizeof(v.desc) }, + }; + + static ngx_rtmp_amf_elt_t in_elts[] = { + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &v.trans, 0 }, + + { NGX_RTMP_AMF_NULL, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_OBJECT, + ngx_null_string, + in_inf, sizeof(in_inf) }, + }; + + + ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module); + if (ctx == NULL) { + return NGX_OK; + } + + ngx_memzero(&v, sizeof(v)); + if (ngx_rtmp_receive_amf(s, in, in_elts, + sizeof(in_elts) / sizeof(in_elts[0]))) + { + return NGX_ERROR; + } + + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->log, 0, + "relay: _result: level='%s' code='%s' description='%s'", + v.level, v.code, v.desc); + + switch ((ngx_int_t)v.trans) { + case NGX_RTMP_RELAY_CONNECT_TRANS: + return ngx_live_relay_rtmp_send_create_stream(s); + + case NGX_RTMP_RELAY_CREATE_STREAM_TRANS: + if (s->publishing == 0) { + if (ngx_live_relay_rtmp_send_publish(s) != NGX_OK) { + return NGX_ERROR; + } + return ngx_live_relay_play_local(s); + + } else { + if (ngx_live_relay_rtmp_send_play(s) != NGX_OK) { + return NGX_ERROR; + } + return ngx_live_relay_publish_local(s); + } + + default: + return NGX_OK; + } +} + + +static ngx_int_t +ngx_live_relay_rtmp_on_error(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_live_relay_ctx_t *ctx; + static struct { + double trans; + u_char level[32]; + u_char code[128]; + u_char desc[1024]; + } v; + + static ngx_rtmp_amf_elt_t in_inf[] = { + + { NGX_RTMP_AMF_STRING, + ngx_string("level"), + &v.level, sizeof(v.level) }, + + { NGX_RTMP_AMF_STRING, + ngx_string("code"), + &v.code, sizeof(v.code) }, + + { NGX_RTMP_AMF_STRING, + ngx_string("description"), + &v.desc, sizeof(v.desc) }, + }; + + static ngx_rtmp_amf_elt_t in_elts[] = { + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &v.trans, 0 }, + + { NGX_RTMP_AMF_NULL, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_OBJECT, + ngx_null_string, + in_inf, sizeof(in_inf) }, + }; + + + ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module); + if (ctx == NULL) { + return NGX_OK; + } + + ngx_memzero(&v, sizeof(v)); + if (ngx_rtmp_receive_amf(s, in, in_elts, + sizeof(in_elts) / sizeof(in_elts[0]))) + { + return NGX_ERROR; + } + + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->log, 0, + "relay: _error: level='%s' code='%s' description='%s'", + v.level, v.code, v.desc); + + ngx_live_relay_rtmp_status_error(s, "_error", (char *) v.code, + (char *) v.level, (char *) v.desc); + + return NGX_OK; +} + + +static ngx_int_t +ngx_live_relay_rtmp_on_status(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_live_relay_ctx_t *ctx; + static struct { + double trans; + u_char level[32]; + u_char code[128]; + u_char desc[1024]; + } v; + + static ngx_rtmp_amf_elt_t in_inf[] = { + + { NGX_RTMP_AMF_STRING, + ngx_string("level"), + &v.level, sizeof(v.level) }, + + { NGX_RTMP_AMF_STRING, + ngx_string("code"), + &v.code, sizeof(v.code) }, + + { NGX_RTMP_AMF_STRING, + ngx_string("description"), + &v.desc, sizeof(v.desc) }, + }; + + static ngx_rtmp_amf_elt_t in_elts[] = { + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &v.trans, 0 }, + + { NGX_RTMP_AMF_NULL, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_OBJECT, + ngx_null_string, + in_inf, sizeof(in_inf) }, + }; + + static ngx_rtmp_amf_elt_t in_elts_meta[] = { + + { NGX_RTMP_AMF_OBJECT, + ngx_null_string, + in_inf, sizeof(in_inf) }, + }; + + + ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module); + if (ctx == NULL) { + return NGX_OK; + } + + ngx_memzero(&v, sizeof(v)); + if (h->type == NGX_RTMP_MSG_AMF_META) { + ngx_rtmp_receive_amf(s, in, in_elts_meta, + sizeof(in_elts_meta) / sizeof(in_elts_meta[0])); + } else { + ngx_rtmp_receive_amf(s, in, in_elts, + sizeof(in_elts) / sizeof(in_elts[0])); + } + + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->log, 0, + "relay: onStatus: level='%s' code='%s' description='%s'", + v.level, v.code, v.desc); + + ngx_live_relay_rtmp_status_error(s, "onStatus", (char *) v.code, + (char *) v.level, (char *) v.desc); + + return NGX_OK; +} + + +static ngx_int_t +ngx_live_relay_rtmp_handshake_done(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_live_relay_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module); + if (ctx == NULL) { + return NGX_OK; + } + + return ngx_live_relay_rtmp_send_connect(s); +} + + +static ngx_int_t +ngx_live_relay_rtmp_get_peer(ngx_peer_connection_t *pc, void *data) +{ + return NGX_OK; +} + + +static void +ngx_live_relay_rtmp_free_peer(ngx_peer_connection_t *pc, void *data, + ngx_uint_t state) +{ +} + + +ngx_int_t +ngx_live_relay_create_rtmp(ngx_rtmp_session_t *s, ngx_live_relay_t *relay, + ngx_live_relay_url_t *url) +{ + ngx_live_relay_ctx_t *rctx; + ngx_pool_t *pool; + ngx_peer_connection_t *pc; + ngx_connection_t *c; + ngx_int_t rc; + ngx_str_t name; + u_char text[NGX_SOCKADDRLEN]; + // must use ngx_sockaddr_t, because sizeof(struct sockaddr) + // is not long enouph, content will be covered by other var + ngx_sockaddr_t nsa; + struct sockaddr *sa; + socklen_t len; + + rctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module); + if (rctx == NULL) { + return NGX_ERROR; + } + + pool = ngx_create_pool(4096, ngx_cycle->log); + if (pool == NULL) { + goto destroy; + } + +#define NGX_LIVE_RELAY_CTX(para) \ + if (ngx_copy_str(s->pool, &rctx->para, &relay->para) != NGX_OK) { \ + goto destroy; \ + } + + NGX_LIVE_RELAY_CTX(domain); + NGX_LIVE_RELAY_CTX(app); + NGX_LIVE_RELAY_CTX(name); + NGX_LIVE_RELAY_CTX(pargs); + NGX_LIVE_RELAY_CTX(referer); + NGX_LIVE_RELAY_CTX(user_agent); +#undef NGX_LIVE_RELAY_CTX + + rctx->tag = relay->tag; + + // connect server + pc = ngx_pcalloc(s->pool, sizeof(ngx_peer_connection_t)); + if (pc == NULL) { + goto destroy; + } + pc->log = s->log; + + // get address + sa = (struct sockaddr *) &nsa; + len = ngx_dynamic_resolver_gethostbyname(&url->url.host, sa); + if (len == 0) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "relay rtmp: gethostbyname failed %V", &url->url.host); + goto destroy; + } + + if (url->port != 0) { + ngx_inet_set_port(sa, url->port); + } + + ngx_memzero(text, sizeof(text)); + name.len = ngx_sock_ntop(sa, len, text, NGX_SOCKADDRLEN, 1); + name.data = text; + + /* copy log to keep shared log unchanged */ + pc->get = ngx_live_relay_rtmp_get_peer; + pc->free = ngx_live_relay_rtmp_free_peer; + pc->name = &name; + pc->socklen = len; + pc->sockaddr = sa; + + rc = ngx_event_connect_peer(pc); + if (rc != NGX_OK && rc != NGX_AGAIN ) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, "relay: connection failed"); + goto destroy; + } + c = pc->connection; + if (ngx_copy_str(pool, &c->addr_text, &url->url.host_with_port) != NGX_OK) { + goto destroy; + } + ngx_rtmp_init_session(s, c); + c->pool = pool; + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_active, 1); +#endif + + ngx_rtmp_client_handshake(s, 1); + + return NGX_OK; + +destroy: + if (pool) { + ngx_destroy_pool(pool); + } + + ngx_rtmp_finalize_session(s); + + return NGX_ERROR; +} + + +static ngx_int_t +ngx_live_relay_rtmp_postconfiguration(ngx_conf_t *cf) +{ + ngx_rtmp_core_main_conf_t *cmcf; + ngx_rtmp_handler_pt *h; + ngx_rtmp_amf_handler_t *ch; + + cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); + + h = ngx_array_push(&cmcf->events[NGX_RTMP_HANDSHAKE_DONE]); + *h = ngx_live_relay_rtmp_handshake_done; + + ch = ngx_array_push(&cmcf->amf); + ngx_str_set(&ch->name, "_result"); + ch->handler = ngx_live_relay_rtmp_on_result; + + ch = ngx_array_push(&cmcf->amf); + ngx_str_set(&ch->name, "_error"); + ch->handler = ngx_live_relay_rtmp_on_error; + + ch = ngx_array_push(&cmcf->amf); + ngx_str_set(&ch->name, "onStatus"); + ch->handler = ngx_live_relay_rtmp_on_status; + + return NGX_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_relay_simple.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_relay_simple.c new file mode 100644 index 0000000000..43c0e0f475 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_relay_simple.c @@ -0,0 +1,460 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#include +#include +#include "ngx_live_relay.h" +#include "ngx_rtmp_dynamic.h" +#include "ngx_dynamic_resolver.h" + + +static ngx_live_push_pt next_push; +static ngx_live_pull_pt next_pull; +static ngx_live_push_close_pt next_push_close; + + +static void *ngx_live_relay_simple_create_app_conf(ngx_conf_t *cf); +static char *ngx_live_relay_simple_merge_app_conf(ngx_conf_t *cf, + void *parent, void *child); +static ngx_int_t ngx_live_relay_simple_postconfiguration(ngx_conf_t *cf); + +static char *ngx_live_relay_push(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char *ngx_live_relay_pull(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + + +typedef struct { + ngx_live_relay_t pull; + ngx_array_t pushes; /* ngx_live_relay_t */ +} ngx_live_relay_simple_app_conf_t; + + +typedef struct { + ngx_live_relay_t *relay; +} ngx_live_relay_simple_ctx_t; + + +static ngx_command_t ngx_live_relay_simple_commands[] = { + + { ngx_string("push"), + NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_live_relay_push, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("pull"), + NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_live_relay_pull, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_live_relay_simple_module_ctx = { + NULL, /* preconfiguration */ + ngx_live_relay_simple_postconfiguration,/* postconfiguration */ + NULL, /* create main configuration */ + NULL, /* init main configuration */ + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + ngx_live_relay_simple_create_app_conf, /* create app configuration */ + ngx_live_relay_simple_merge_app_conf /* merge app configuration */ +}; + + +ngx_module_t ngx_live_relay_simple_module = { + NGX_MODULE_V1, + &ngx_live_relay_simple_module_ctx, /* module context */ + ngx_live_relay_simple_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void * +ngx_live_relay_simple_create_app_conf(ngx_conf_t *cf) +{ + ngx_live_relay_simple_app_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_live_relay_simple_app_conf_t)); + if (conf == NULL) { + return NULL; + } + + if (ngx_array_init(&conf->pushes, cf->pool, 8, sizeof(ngx_live_relay_t)) + != NGX_OK) + { + return NULL; + } + + return conf; +} + + +static char * +ngx_live_relay_simple_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) +{ + return NGX_CONF_OK; +} + + +static void +ngx_live_relay_simple_handler(ngx_event_t *ev) +{ + ngx_rtmp_session_t *s; + ngx_live_relay_app_conf_t *lracf; + ngx_live_relay_ctx_t *ctx; + ngx_live_relay_simple_ctx_t *sctx; + ngx_live_relay_t *relay; + + s = ev->data; + ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module); + + if (!ctx->failed_delay && ev->timedout) { // connect timeout + ngx_log_error(NGX_LOG_ERR, s->log, NGX_ETIMEDOUT, + "simple relay, relay timeout"); + s->finalize_reason = NGX_LIVE_RELAY_TIMEOUT; + ngx_rtmp_finalize_session(s); + + return; + } + + // relay pull, no player or relay push no publisher + if ((s->publishing && s->live_stream->play_ctx == NULL) + || (!s->publishing && s->live_stream->publish_ctx == NULL)) + { + return; + } + + lracf = ngx_rtmp_get_module_app_conf(s, ngx_live_relay_module); + + ngx_add_timer(&ctx->reconnect, lracf->relay_reconnect); + + sctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_simple_module); + relay = sctx->relay; + + ngx_live_relay_create(s, relay); +} + + +static ngx_int_t +ngx_live_relay_simple_relay(ngx_rtmp_session_t *s, ngx_live_relay_t *relay, + unsigned publishing) +{ + ngx_rtmp_session_t *rs; + ngx_live_relay_ctx_t *ctx, *pctx; + ngx_live_relay_app_conf_t *lracf; + ngx_live_relay_simple_ctx_t *sctx; + + rs = ngx_rtmp_create_relay_session(s, &ngx_live_relay_simple_module); + if (rs == NULL) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "relay simple, create relay session failed"); + return NGX_DECLINED; + } + rs->publishing = publishing; + rs->live_stream = s->live_stream; + ngx_live_create_ctx(rs, publishing); + + sctx = ngx_pcalloc(rs->pool, sizeof(ngx_live_relay_simple_ctx_t)); + if (sctx == NULL) { + ngx_log_error(NGX_LOG_ERR, rs->log, 0, + "relay simple, create simple relay ctx failed"); + ngx_rtmp_finalize_session(rs); + + return NGX_OK; + } + ngx_rtmp_set_ctx(rs, sctx, ngx_live_relay_simple_module); + sctx->relay = relay; + + ctx = ngx_rtmp_get_module_ctx(rs, ngx_live_relay_module); + ctx->reconnect.log = rs->log; + ctx->reconnect.data = rs; + ctx->reconnect.handler = ngx_live_relay_simple_handler; + + if (s->publishing != rs->publishing) { + ngx_post_event(&ctx->reconnect, &ngx_posted_events); + return NGX_OK; + } + + // normal publisher close, need to trigger pull + if (s->publishing && !s->relay) { + ngx_post_event(&ctx->reconnect, &ngx_posted_events); + return NGX_OK; + } + + // reconnect + pctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module); + if (pctx->successd) { // prev relay successd + ngx_post_event(&ctx->reconnect, &ngx_posted_events); + return NGX_OK; + } + + ctx->idx = pctx->idx; + ctx->failed_reconnect = pctx->failed_reconnect; + + if (ctx->idx < relay->urls.nelts) { // retry backup url immediately + ngx_post_event(&ctx->reconnect, &ngx_posted_events); + return NGX_OK; + } + + lracf = ngx_rtmp_get_module_app_conf(rs, ngx_live_relay_module); + + if (!pctx->reconnect.timer_set) { // prev relay timeout + ctx->failed_reconnect = ngx_min(pctx->failed_reconnect * 2, + lracf->relay_reconnect); + ngx_post_event(&ctx->reconnect, &ngx_posted_events); + return NGX_OK; + } + + if (pctx->failed_reconnect) { + ctx->failed_reconnect = ngx_min(pctx->failed_reconnect * 2, + lracf->relay_reconnect); + } else { + ctx->failed_reconnect = lracf->failed_reconnect; + } + + ctx->failed_delay = 1; + ngx_add_timer(&ctx->reconnect, ctx->failed_reconnect); + + return NGX_OK; +} + + +static ngx_int_t +ngx_live_relay_simple_push(ngx_rtmp_session_t *s) +{ + ngx_live_relay_simple_app_conf_t *rsacf; + ngx_live_relay_t *relay; + ngx_uint_t i; + + rsacf = ngx_rtmp_get_module_app_conf(s, ngx_live_relay_simple_module); + + if (rsacf->pushes.nelts == 0) { // not configured + goto next; + } + + if (s->relay && s->static_pull == 0) { + goto next; + } + + relay = rsacf->pushes.elts; + for (i = 0; i < rsacf->pushes.nelts; ++i, ++relay) { + ngx_live_relay_simple_relay(s, relay, 0); + } + +next: + return next_push(s); +} + + +static ngx_int_t +ngx_live_relay_simple_pull(ngx_rtmp_session_t *s) +{ + ngx_live_relay_simple_app_conf_t *rsacf; + ngx_live_relay_t *relay; + + rsacf = ngx_rtmp_get_module_app_conf(s, ngx_live_relay_simple_module); + + if (rsacf->pull.urls.nelts == 0) { // not configured + goto next; + } + + if (!s->publishing && s->relay) { // relay push + goto next; + } + + if (s->live_stream->publish_ctx) { // already has publisher + goto next; + } + + relay = &rsacf->pull; + + if (ngx_live_relay_simple_relay(s, relay, 1) == NGX_OK) { + return NGX_OK; + } + +next: + return next_pull(s); +} + + +static ngx_int_t +ngx_live_relay_simple_push_close(ngx_rtmp_session_t *s) +{ + ngx_live_relay_ctx_t *ctx; + ngx_live_relay_simple_ctx_t *sctx; + ngx_live_relay_t *relay; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module); + if (ctx->tag != &ngx_live_relay_simple_module) { + goto next; + } + + // relay push and has publisher in stream + if (!ctx->giveup && s->live_stream->publish_ctx) { + sctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_simple_module); + relay = sctx->relay; + + ngx_live_relay_simple_relay(s, relay, 0); + } + +next: + return next_push_close(s); +} + + +static char * +ngx_live_relay_push_pull(ngx_conf_t *cf, ngx_live_relay_t *relay) +{ + ngx_live_relay_url_t *url; + ngx_str_t *value, n, v; + ngx_uint_t i; + u_char *p; + + relay->tag = &ngx_live_relay_simple_module; + + if (ngx_array_init(&relay->urls, cf->pool, 8, sizeof(ngx_live_relay_url_t)) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + value = cf->args->elts; + ++value; + + for (i = 1; i < cf->args->nelts; ++i, ++value) { + if (ngx_strncasecmp(value->data, (u_char *) "rtmp://", 7) == 0 + || ngx_strncasecmp(value->data, (u_char *) "http://", 7) == 0) + { + url = ngx_array_push(&relay->urls); + if (url == NULL) { + return NGX_CONF_ERROR; + } + ngx_memzero(url, sizeof(ngx_live_relay_url_t)); + + if (value->data[0] == 'h') { + url->relay_type = NGX_LIVE_RELAY_HTTPFLV; + } else { + url->relay_type = NGX_LIVE_RELAY_RTMP; + } + + if (ngx_parse_request_url(&url->url, value) != NGX_OK) { + return NGX_CONF_ERROR; + } + + url->port = ngx_request_port(&url->url.scheme, &url->url.port); + if (url->port == 0) { + return "invalid port"; + } + + ngx_dynamic_resolver_add_domain(&url->url.host, cf->cycle); + + continue; + } + + p = ngx_strlchr(value->data, value->data + value->len, '='); + + if (p == NULL) { + return "unsupported parameter format"; + } else { + n.data = value->data; + n.len = p - value->data; + + v.data = p + 1; + v.len = value->data + value->len - v.data; + } + +#define NGX_LIVE_RELAY_STR_PAR(name, var) \ + if (n.len == sizeof(name) - 1 \ + && ngx_strncasecmp(n.data, (u_char *) name, n.len) == 0) \ + { \ + relay->var = v; \ + continue; \ + } + + NGX_LIVE_RELAY_STR_PAR("domain", domain); + NGX_LIVE_RELAY_STR_PAR("app", app); + NGX_LIVE_RELAY_STR_PAR("name", name); + NGX_LIVE_RELAY_STR_PAR("pargs", pargs); + NGX_LIVE_RELAY_STR_PAR("referer", referer); + NGX_LIVE_RELAY_STR_PAR("user_agent", user_agent); + +#undef NGX_LIVE_RELAY_STR_PAR + + return "unsupported parameter"; + } + + return NGX_CONF_OK; +} + + +static char * +ngx_live_relay_push(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_live_relay_simple_app_conf_t *rsacf; + ngx_live_relay_t *relay; + char *rc; + + rsacf = conf; + + relay = ngx_array_push(&rsacf->pushes); + if (relay == NULL) { + return NGX_CONF_ERROR; + } + ngx_memzero(relay, sizeof(ngx_live_relay_t)); + + rc = ngx_live_relay_push_pull(cf, relay); + if (rc != NGX_CONF_OK) { + return rc; + } + + return NGX_CONF_OK; +} + + +static char * +ngx_live_relay_pull(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_live_relay_simple_app_conf_t *rsacf; + + rsacf = conf; + + if (rsacf->pull.urls.nelts) { + return "is duplicate"; + } + + return ngx_live_relay_push_pull(cf, &rsacf->pull); +} + + +static ngx_int_t +ngx_live_relay_simple_postconfiguration(ngx_conf_t *cf) +{ + /* chain handlers */ + + next_push = ngx_live_push; + ngx_live_push = ngx_live_relay_simple_push; + + next_pull = ngx_live_pull; + ngx_live_pull = ngx_live_relay_simple_pull; + + next_push_close = ngx_live_push_close; + ngx_live_push_close = ngx_live_relay_simple_push_close; + + return NGX_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_relay_static.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_relay_static.c new file mode 100644 index 0000000000..6b63504a5f --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_live_relay_static.c @@ -0,0 +1,656 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#include +#include +#include "ngx_rtmp.h" +#include "ngx_live_relay.h" +#include "ngx_map.h" +#include "ngx_dynamic_conf.h" +#include "ngx_rtmp_dynamic.h" +#include "ngx_dynamic_resolver.h" + +static ngx_live_pull_pt next_pull; + + +static void *ngx_live_relay_static_create_main_conf(ngx_conf_t *cf); +static char *ngx_live_relay_static_init_main_conf(ngx_conf_t *cf, void *conf); + +static void *ngx_live_relay_static_create_main_dconf(ngx_conf_t *cf); +static char *ngx_live_relay_static_init_main_dconf(ngx_conf_t *cf, void *conf); + +static char *ngx_live_relay_pull(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + +static ngx_int_t ngx_live_relay_static_postconfiguration(ngx_conf_t *cf); + + +typedef struct ngx_live_relay_static_relay_s ngx_live_relay_static_relay_t; + +struct ngx_live_relay_static_relay_s { + ngx_map_node_t node; + ngx_live_relay_t *relay; + ngx_rtmp_session_t *session; + ngx_live_relay_static_relay_t *next; +}; + + +typedef struct { + /* ngx_live_relay_static_relay_t */ + ngx_map_t pulls[2]; + /* 0 and 1 for index of conf */ + unsigned used; + ngx_str_t pull_port; + + ngx_live_relay_static_relay_t *free; + unsigned nalloc; + unsigned nfree; +} ngx_live_relay_static_main_conf_t; + + +typedef struct { + ngx_array_t pulls; /* ngx_live_relay_t */ +} ngx_live_relay_static_main_dconf_t; + + +typedef struct { + ngx_live_relay_static_relay_t *relay; +} ngx_live_relay_static_ctx_t; + + +static ngx_command_t ngx_live_relay_static_dcommands[] = { + + { ngx_string("static_pull"), + NGX_RTMP_MAIN_CONF|NGX_CONF_1MORE, + ngx_live_relay_pull, + NGX_RTMP_MAIN_CONF_OFFSET, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_rtmp_dynamic_module_t ngx_live_relay_static_module_dctx = { + ngx_live_relay_static_create_main_dconf,/* create main configuration */ + ngx_live_relay_static_init_main_dconf, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create app configuration */ + NULL /* merge app configuration */ +}; + + +static ngx_command_t ngx_live_relay_static_commands[] = { + + { ngx_string("static_pull_port"), + NGX_RTMP_MAIN_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_RTMP_MAIN_CONF_OFFSET, + offsetof(ngx_live_relay_static_main_conf_t, pull_port), + NULL }, + + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_live_relay_static_module_ctx = { + NULL, /* preconfiguration */ + ngx_live_relay_static_postconfiguration,/* postconfiguration */ + ngx_live_relay_static_create_main_conf, /* create main configuration */ + ngx_live_relay_static_init_main_conf, /* init main configuration */ + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + NULL, /* create app configuration */ + NULL /* merge app configuration */ +}; + + +ngx_module_t ngx_live_relay_static_module = { + NGX_MODULE_V1, + &ngx_live_relay_static_module_ctx, /* module context */ + ngx_live_relay_static_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + (uintptr_t) &ngx_live_relay_static_module_dctx, /* module dynamic context */ + (uintptr_t) ngx_live_relay_static_dcommands, /* module dynamic directives */ + NGX_MODULE_V1_DYNAMIC_PADDING +}; + + +static void +ngx_live_relay_static_handler(ngx_event_t *ev) +{ + ngx_rtmp_session_t *s; + ngx_live_relay_app_conf_t *lracf; + ngx_live_relay_ctx_t *ctx; + ngx_live_relay_static_ctx_t *sctx; + ngx_live_relay_t *relay; + + s = ev->data; + ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module); + + if (!ctx->failed_delay && ev->timedout) { // connect timeout + ngx_log_error(NGX_LOG_ERR, s->log, NGX_ETIMEDOUT, + "static relay, relay timeout"); + s->finalize_reason = NGX_LIVE_RELAY_TIMEOUT; + ngx_rtmp_finalize_session(s); + + return; + } + + lracf = ngx_rtmp_get_module_app_conf(s, ngx_live_relay_module); + + ngx_add_timer(&ctx->reconnect, lracf->relay_reconnect); + + sctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_static_module); + relay = sctx->relay->relay; + + ngx_live_relay_create(s, relay); +} + + +static ngx_int_t +ngx_live_relay_static_relay(ngx_rtmp_session_t *s, + ngx_live_relay_static_relay_t *r) +{ + ngx_rtmp_session_t *rs; + ngx_live_relay_ctx_t *ctx, *pctx; + ngx_live_relay_app_conf_t *lracf; + ngx_live_relay_static_main_conf_t *rsmcf; + ngx_live_relay_static_ctx_t *sctx; + ngx_live_relay_t *relay; + ngx_rtmp_addr_conf_t *addr_conf; + + relay = r->relay; + rsmcf = ngx_rtmp_cycle_get_module_main_conf(ngx_cycle, + ngx_live_relay_static_module); + addr_conf = ngx_rtmp_find_related_addr_conf((ngx_cycle_t *) ngx_cycle, + &rsmcf->pull_port); + if (addr_conf == NULL) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, + "relay static, find related add_conf for %V failed", + &rsmcf->pull_port); + return NGX_DECLINED; + } + + rs = ngx_rtmp_create_static_session(relay, addr_conf, + &ngx_live_relay_static_module); + if (rs == NULL) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, + "relay static, create relay session %V failed", &relay->stream); + return NGX_DECLINED; + } + r->session = rs; + + rs->publishing = 1; + rs->live_stream = ngx_live_create_stream(&rs->domain, &rs->stream); + ngx_live_create_ctx(rs, 1); + + sctx = ngx_pcalloc(rs->pool, sizeof(ngx_live_relay_static_ctx_t)); + if (sctx == NULL) { + ngx_log_error(NGX_LOG_ERR, rs->log, 0, + "relay static, create static relay ctx failed"); + ngx_rtmp_finalize_session(rs); + + return NGX_OK; + } + ngx_rtmp_set_ctx(rs, sctx, ngx_live_relay_static_module); + sctx->relay = r; + + ctx = ngx_rtmp_get_module_ctx(rs, ngx_live_relay_module); + ctx->reconnect.log = rs->log; + ctx->reconnect.data = rs; + ctx->reconnect.handler = ngx_live_relay_static_handler; + + if (s == NULL) { + ngx_post_event(&ctx->reconnect, &ngx_posted_events); + return NGX_OK; + } + + // reconnect + pctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module); + if (pctx->successd) { // prev relay successd + ngx_post_event(&ctx->reconnect, &ngx_posted_events); + return NGX_OK; + } + + ctx->idx = pctx->idx; + ctx->failed_reconnect = pctx->failed_reconnect; + + if (ctx->idx < relay->urls.nelts) { // retry backup url immediately + ngx_post_event(&ctx->reconnect, &ngx_posted_events); + return NGX_OK; + } + + lracf = ngx_rtmp_get_module_app_conf(rs, ngx_live_relay_module); + + if (!pctx->reconnect.timer_set) { // prev relay timeout + ctx->failed_reconnect = ngx_min(pctx->failed_reconnect * 2, + lracf->relay_reconnect); + ngx_post_event(&ctx->reconnect, &ngx_posted_events); + return NGX_OK; + } + + if (pctx->failed_reconnect) { + ctx->failed_reconnect = ngx_min(pctx->failed_reconnect * 2, + lracf->relay_reconnect); + } else { + ctx->failed_reconnect = lracf->failed_reconnect; + } + + ctx->failed_delay = 1; + ngx_add_timer(&ctx->reconnect, ctx->failed_reconnect); + + return NGX_OK; +} + + +// only be called when reconnect +static ngx_int_t +ngx_live_relay_static_pull(ngx_rtmp_session_t *s) +{ + ngx_live_relay_static_ctx_t *ctx; + ngx_live_relay_ctx_t *rctx; + + rctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module); + if (rctx == NULL || rctx->tag != &ngx_live_relay_static_module) { + goto next; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_static_module); + + if (ngx_live_relay_static_relay(s, ctx->relay) == NGX_OK) { + return NGX_OK; + } + +next: + return next_pull(s); +} + + +static ngx_live_relay_static_relay_t * +ngx_live_relay_get_static_relay(ngx_live_relay_static_main_conf_t *rsmcf) +{ + ngx_live_relay_static_relay_t *r; + + r = rsmcf->free; + if (r) { + rsmcf->free = r->next; + r->session = NULL; + r->next = NULL; + + --rsmcf->nfree; + } else { + r = ngx_pcalloc(ngx_cycle->pool, sizeof(ngx_live_relay_static_relay_t)); + if (r == NULL) { + return NULL; + } + + ++rsmcf->nalloc; + } + + return r; +} + + +static void +ngx_live_relay_put_static_relay(ngx_live_relay_static_main_conf_t *rsmcf, + ngx_live_relay_static_relay_t *r) +{ + r->next = rsmcf->free; + rsmcf->free = r; + ++rsmcf->nfree; +} + + +static void * +ngx_live_relay_static_create_main_conf(ngx_conf_t *cf) +{ + ngx_live_relay_static_main_conf_t *rsmcf; + + rsmcf = ngx_pcalloc(cf->pool, sizeof(ngx_live_relay_static_main_conf_t)); + if (rsmcf == NULL) { + return NULL; + } + + rsmcf->used = 1; + + ngx_map_init(&rsmcf->pulls[0], ngx_map_hash_str, ngx_cmp_str); + ngx_map_init(&rsmcf->pulls[1], ngx_map_hash_str, ngx_cmp_str); + + return rsmcf; +} + + +static char * +ngx_live_relay_static_init_main_conf(ngx_conf_t *cf, void *conf) +{ + ngx_live_relay_static_main_conf_t *rsmcf; + + rsmcf = conf; + + if (rsmcf->pull_port.len == 0) { + rsmcf->pull_port.data = (u_char *) "1935"; + rsmcf->pull_port.len = sizeof("1935") - 1; + } + + return NGX_CONF_OK; +} + + +static void * +ngx_live_relay_static_create_main_dconf(ngx_conf_t *cf) +{ + ngx_live_relay_static_main_dconf_t *rsmdcf; + + rsmdcf = ngx_pcalloc(cf->pool, sizeof(ngx_live_relay_static_main_dconf_t)); + if (rsmdcf == NULL) { + return NULL; + } + + if (ngx_array_init(&rsmdcf->pulls, cf->pool, 64, sizeof(ngx_live_relay_t)) + != NGX_OK) + { + return NULL; + } + + return rsmdcf; +} + + +// merge all static pull into ngx_live_relay_static_main_conf_t; +static char * +ngx_live_relay_static_init_main_dconf(ngx_conf_t *cf, void *conf) +{ + ngx_live_relay_static_main_conf_t *rsmcf; + ngx_live_relay_static_main_dconf_t *rsmdcf; + ngx_core_conf_t *ccf; + ngx_live_relay_t *relay; + ngx_live_relay_static_relay_t *srelay, *old, *sl, *sln, **sll; + ngx_live_relay_static_ctx_t *ctx; + ngx_live_relay_ctx_t *rctx; + ngx_map_node_t *node; + unsigned used; + char *rc; + ngx_uint_t i, hash; + + rsmdcf = conf; + rsmcf = ngx_rtmp_cycle_get_module_main_conf(ngx_cycle, + ngx_live_relay_static_module); + ccf = (ngx_core_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_core_module); + + used = rsmcf->used? 0: 1; + sl = NULL; + sll = &sl; + + relay = rsmdcf->pulls.elts; + for (i = 0; i < rsmdcf->pulls.nelts; ++i, ++relay) { + // should static pull in current process? + if (ngx_process == NGX_PROCESS_WORKER) { + hash = ngx_hash_key_lc(relay->stream.data, relay->stream.len); + if (hash % ccf->worker_processes != ngx_worker) { + continue; + } + } + + // check static pull duplicate + node = ngx_map_find(&rsmcf->pulls[used], (intptr_t) &relay->stream); + if (node) { + rc = "duplicate static pull"; + goto error; + } + + srelay = ngx_live_relay_get_static_relay(rsmcf); + if (srelay == NULL) { + rc = "get static relay failed"; + goto error; + } + srelay->relay = relay; + srelay->node.raw_key = (intptr_t) &relay->stream; + ngx_map_insert(&rsmcf->pulls[used], &srelay->node, 0); + + // check static pull is exist + node = ngx_map_find(&rsmcf->pulls[rsmcf->used], + (intptr_t) &relay->stream); + if (node) { + old = (ngx_live_relay_static_relay_t *) node; + srelay->session = old->session; + + // link swap static pull + *sll = old; + sll = &(*sll)->next; + } + } + + // delete swap static pull from old + while (sl) { + sln = sl; + sl = sl->next; + ngx_map_delete(&rsmcf->pulls[rsmcf->used], + (intptr_t) &sln->relay->stream); + ngx_live_relay_put_static_relay(rsmcf, sln); + } + + // stop deleted static pull + node = ngx_map_begin(&rsmcf->pulls[rsmcf->used]); + while (node) { + srelay = (ngx_live_relay_static_relay_t *) node; + node = ngx_map_next(node); + ngx_live_relay_put_static_relay(rsmcf, srelay); + + rctx = ngx_rtmp_get_module_ctx(srelay->session, ngx_live_relay_module); + rctx->giveup = 1; + srelay->session->finalize_reason = NGX_LIVE_NORMAL_CLOSE; + ngx_rtmp_finalize_session(srelay->session); + + ngx_map_delete(&rsmcf->pulls[rsmcf->used], + (intptr_t) &srelay->relay->stream); + } + + // new static relay + node = ngx_map_begin(&rsmcf->pulls[used]); + for (; node; node = ngx_map_next(node)) { + srelay = (ngx_live_relay_static_relay_t *) node; + if (srelay->session == NULL) { + ngx_live_relay_static_relay(NULL, srelay); + } else { + ctx = ngx_rtmp_get_module_ctx(srelay->session, + ngx_live_relay_static_module); + ctx->relay = srelay; + } + } + + rsmcf->used = used; + + return NGX_CONF_OK; + +error: + // recycle static relay resource + node = ngx_map_begin(&rsmcf->pulls[used]); + while (node) { + srelay = (ngx_live_relay_static_relay_t *) node; + node = ngx_map_next(node); + ngx_live_relay_put_static_relay(rsmcf, srelay); + + ngx_map_delete(&rsmcf->pulls[used], + (intptr_t) &srelay->relay->stream); + } + + return rc; +} + + +static char * +ngx_live_relay_pull(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_live_relay_static_main_dconf_t *rsmdcf; + ngx_live_relay_t *relay; + ngx_live_relay_url_t *url; + ngx_str_t *value, n, v; + ngx_uint_t i; + u_char *p; + + rsmdcf = conf; + + relay = ngx_array_push(&rsmdcf->pulls); + if (relay == NULL) { + return NGX_CONF_ERROR; + } + ngx_memzero(relay, sizeof(ngx_live_relay_t)); + + relay->tag = &ngx_live_relay_static_module; + + if (ngx_array_init(&relay->urls, cf->pool, 8, sizeof(ngx_live_relay_url_t)) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + value = cf->args->elts; + ++value; + + for (i = 1; i < cf->args->nelts; ++i, ++value) { + if (ngx_strncasecmp(value->data, (u_char *) "rtmp://", 7) == 0 + || ngx_strncasecmp(value->data, (u_char *) "http://", 7) == 0) + { + url = ngx_array_push(&relay->urls); + if (url == NULL) { + return NGX_CONF_ERROR; + } + ngx_memzero(url, sizeof(ngx_live_relay_url_t)); + + if (value->data[0] == 'h') { + url->relay_type = NGX_LIVE_RELAY_HTTPFLV; + } else { + url->relay_type = NGX_LIVE_RELAY_RTMP; + } + + if (ngx_parse_request_url(&url->url, value) != NGX_OK) { + return NGX_CONF_ERROR; + } + + url->port = ngx_request_port(&url->url.scheme, &url->url.port); + if (url->port == 0) { + return "invalid port"; + } + + ngx_dynamic_resolver_add_domain(&url->url.host, cf->cycle); + + continue; + } + + p = ngx_strlchr(value->data, value->data + value->len, '='); + + if (p == NULL) { + return "unsupported parameter format"; + } else { + n.data = value->data; + n.len = p - value->data; + + v.data = p + 1; + v.len = value->data + value->len - v.data; + } + +#define NGX_LIVE_RELAY_STR_PAR(name, var) \ + if (n.len == sizeof(name) - 1 \ + && ngx_strncasecmp(n.data, (u_char *) name, n.len) == 0) \ + { \ + relay->var = v; \ + continue; \ + } + + NGX_LIVE_RELAY_STR_PAR("domain", domain); + NGX_LIVE_RELAY_STR_PAR("app", app); + NGX_LIVE_RELAY_STR_PAR("name", name); + NGX_LIVE_RELAY_STR_PAR("pargs", pargs); + NGX_LIVE_RELAY_STR_PAR("referer", referer); + NGX_LIVE_RELAY_STR_PAR("user_agent", user_agent); + +#undef NGX_LIVE_RELAY_STR_PAR + + return "unsupported parameter"; + } + + if (relay->domain.len == 0) { + return "domain not configured"; + } + + if (relay->app.len == 0) { + return "app not configured"; + } + + if (relay->name.len == 0) { + return "name not configured"; + } + + // domain/app/name + relay->stream.len = relay->domain.len + 1 + relay->app.len + 1 + + relay->name.len; + relay->stream.data = ngx_pcalloc(cf->pool, relay->stream.len); + if (relay->stream.data == NULL) { + return NGX_CONF_ERROR; + } + ngx_snprintf(relay->stream.data, relay->stream.len, "%V/%V/%V", + &relay->domain, &relay->app, &relay->name); + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_live_relay_static_postconfiguration(ngx_conf_t *cf) +{ + next_pull = ngx_live_pull; + ngx_live_pull = ngx_live_relay_static_pull; + + return NGX_OK; +} + + +ngx_chain_t * +ngx_live_relay_static_state(ngx_http_request_t *r) +{ + ngx_live_relay_static_main_conf_t *rsmcf; + ngx_chain_t *cl; + ngx_buf_t *b; + size_t len; + + rsmcf = ngx_rtmp_cycle_get_module_main_conf(ngx_cycle, + ngx_live_relay_static_module); + + len = sizeof("##########rtmp live relay static##########\n") - 1 + + sizeof("relay_static alloc frame: \n") - 1 + NGX_OFF_T_LEN + + sizeof("relay_static free frame: \n") - 1 + NGX_OFF_T_LEN; + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NULL; + } + cl->next = NULL; + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NULL; + } + cl->buf = b; + + b->last = ngx_snprintf(b->last, len, + "##########rtmp live relay static##########\n" + "relay_static alloc frame: %ui\n" + "relay_static free frame: %ui\n", + rsmcf->nalloc, rsmcf->nfree); + + return cl; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_netcall.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_netcall.c new file mode 100644 index 0000000000..277bd91e18 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_netcall.c @@ -0,0 +1,205 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#include "ngx_netcall.h" +#include "ngx_poold.h" + + +// cleanup only be called when connect failed(exclusive timeout) +static void +ngx_netcall_cleanup(void *data) +{ + ngx_netcall_ctx_t *nctx; + + nctx = data; + + if (nctx->ev.timer_set) { + ngx_del_timer(&nctx->ev); + } + + if (nctx->ev.posted) { + ngx_delete_posted_event(&nctx->ev); + } + + if (nctx->hcr) { + ngx_http_client_detach(nctx->hcr); + nctx->handler(nctx, NGX_ERROR); + nctx->hcr = NULL; + } +} + +// netcall timeout +static void +ngx_netcall_timeout(ngx_event_t *ev) +{ + ngx_netcall_ctx_t *nctx; + + nctx = ev->data; + + if (nctx->ev.timer_set) { + ngx_del_timer(&nctx->ev); + } + + if (nctx->ev.posted) { + ngx_delete_posted_event(&nctx->ev); + } + + if (nctx->hcr) { + ngx_http_client_detach(nctx->hcr); + nctx->handler(nctx, NGX_ERROR); + nctx->hcr = NULL; + } +} + +static void +ngx_netcall_handler(void *data, ngx_http_request_t *hcr) +{ + ngx_netcall_ctx_t *nctx; + ngx_int_t code; + + nctx = data; + + if (nctx->ev.timer_set) { + ngx_del_timer(&nctx->ev); + } + + if (nctx->ev.posted) { + ngx_delete_posted_event(&nctx->ev); + } + + code = ngx_http_client_status_code(hcr); + + if (nctx->hcr) { + ngx_http_client_detach(nctx->hcr); + nctx->handler(nctx, code); + nctx->hcr = NULL; + } +} + +static void +ngx_netcall_destroy_handler(ngx_event_t *ev) +{ + ngx_netcall_ctx_t *nctx; + + nctx = ev->data; + + NGX_DESTROY_POOL(nctx->pool); +} + +ngx_netcall_ctx_t * +ngx_netcall_create_ctx(ngx_uint_t type, ngx_str_t *groupid, ngx_uint_t stage, + ngx_msec_t timeout, ngx_msec_t update, ngx_uint_t idx) +{ + ngx_netcall_ctx_t *ctx; + ngx_pool_t *pool; + + pool = NGX_CREATE_POOL(4096, ngx_cycle->log); + if (pool == NULL) { + return NULL; + } + + ctx = ngx_pcalloc(pool, sizeof(ngx_netcall_ctx_t)); + if (ctx == NULL) { + NGX_DESTROY_POOL(pool); + return NULL; + } + + ctx->url.data = ngx_pcalloc(pool, NGX_NETCALL_MAX_URL_LEN); + if (ctx->url.data == NULL) { + NGX_DESTROY_POOL(pool); + return NULL; + } + + ctx->pool = pool; + + ctx->idx = idx; + ctx->type = type; + + ctx->groupid.len = groupid->len; + ctx->groupid.data = ngx_pcalloc(pool, ctx->groupid.len); + if (ctx->groupid.data == NULL) { + NGX_DESTROY_POOL(pool); + return NULL; + } + ngx_memcpy(ctx->groupid.data, groupid->data, groupid->len); + + ctx->ev.log = ngx_cycle->log; + ctx->ev.data = ctx; + + ctx->stage = stage; + ctx->timeout = timeout; + ctx->update = update; + + return ctx; +} + +void +ngx_netcall_create(ngx_netcall_ctx_t *nctx, ngx_log_t *log) +{ + ngx_http_request_t *hcr; + ngx_http_cleanup_t *cln; + + hcr = ngx_http_client_get(log, &nctx->url, NULL, nctx); + if (hcr == NULL) { + return; + } + + ngx_http_client_set_read_handler(hcr, ngx_netcall_handler); + + cln = ngx_http_client_cleanup_add(hcr, 0); + if (cln == NULL) { + ngx_log_error(NGX_LOG_ERR, log, 0, + "netcall create add cleanup failed"); + return; + } + cln->handler = ngx_netcall_cleanup; + cln->data = nctx; + + // detach old http client request + if (nctx->hcr) { + ngx_http_client_detach(nctx->hcr); + } + + nctx->hcr = hcr; + + nctx->ev.log = log; + nctx->ev.handler = ngx_netcall_timeout; + ngx_add_timer(&nctx->ev, nctx->timeout); +} + +void +ngx_netcall_destroy(ngx_netcall_ctx_t *nctx) +{ + if (nctx->ev.timer_set) { + ngx_del_timer(&nctx->ev); + } + + if (nctx->ev.posted) { + ngx_delete_posted_event(&nctx->ev); + } + + if (nctx->hcr) { // use detach will keep client connection alive + ngx_http_client_detach(nctx->hcr); + nctx->hcr = NULL; + } + + // destroy may called in nctx->handler + // destroy pool may cause memory error + // so we destroy nctx pool asynchronous + nctx->ev.handler = ngx_netcall_destroy_handler; + // reset ev log, use rtmp session log may be destroy + nctx->ev.log = ngx_cycle->log; + ngx_post_event(&nctx->ev, &ngx_posted_events); +} + +ngx_str_t * +ngx_netcall_header(ngx_netcall_ctx_t *nctx, ngx_str_t *key) +{ + ngx_http_request_t *hcr; + + hcr = nctx->hcr; + + return ngx_http_client_header_in(hcr, key); +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_netcall.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_netcall.h new file mode 100644 index 0000000000..3d55d41350 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_netcall.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#ifndef _NGX_NETCALL_H_INCLUDE_ +#define _NGX_NETCALL_H_INCLUDE_ + + +#include +#include "ngx_http_client.h" + + +#define NGX_NETCALL_MAX_URL_LEN 2048 + +typedef struct ngx_netcall_ctx_s ngx_netcall_ctx_t; + +typedef void (* ngx_netcall_handler_pt)(ngx_netcall_ctx_t *ctx, ngx_int_t code); + +struct ngx_netcall_ctx_s { + ngx_pool_t *pool; + + ngx_str_t url; + + ngx_uint_t idx; + ngx_uint_t type; + ngx_str_t groupid; + ngx_uint_t stage; + ngx_msec_t timeout; + ngx_msec_t update; + + ngx_event_t ev; + ngx_http_request_t *hcr; + ngx_netcall_handler_pt handler; + void *data; +}; + + +ngx_netcall_ctx_t *ngx_netcall_create_ctx(ngx_uint_t type, ngx_str_t *groupid, + ngx_uint_t stage, ngx_msec_t timeout, ngx_msec_t update, ngx_uint_t idx); + +void ngx_netcall_create(ngx_netcall_ctx_t *nctx, ngx_log_t *log); +void ngx_netcall_destroy(ngx_netcall_ctx_t *nctx); +ngx_str_t *ngx_netcall_header(ngx_netcall_ctx_t *nctx, ngx_str_t *key); + + +#endif diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp.c new file mode 100644 index 0000000000..2151b30a78 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp.c @@ -0,0 +1,1343 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include +#include +#include "ngx_rtmp.h" +#include "ngx_rtmp_variables.h" + + +static char *ngx_rtmp_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static ngx_int_t ngx_rtmp_optimize_servers(ngx_conf_t *cf, + ngx_rtmp_core_main_conf_t *cmcf, ngx_array_t *ports); +static ngx_int_t ngx_rtmp_add_addrs(ngx_conf_t *cf, ngx_rtmp_port_t *mport, + ngx_rtmp_conf_addr_t *addr); +#if (NGX_HAVE_INET6) +static ngx_int_t ngx_rtmp_add_addrs6(ngx_conf_t *cf, ngx_rtmp_port_t *mport, + ngx_rtmp_conf_addr_t *addr); +#endif +static ngx_int_t ngx_rtmp_cmp_conf_addrs(const void *one, const void *two); +static ngx_int_t ngx_rtmp_init_events(ngx_conf_t *cf, + ngx_rtmp_core_main_conf_t *cmcf); +static ngx_int_t ngx_rtmp_init_event_handlers(ngx_conf_t *cf, + ngx_rtmp_core_main_conf_t *cmcf); +static char * ngx_rtmp_merge_applications(ngx_conf_t *cf, + ngx_array_t *applications, void **app_conf, ngx_rtmp_module_t *module, + ngx_uint_t ctx_index); +static ngx_int_t ngx_rtmp_init_process(ngx_cycle_t *cycle); + + +#if (nginx_version >= 1007011) +ngx_queue_t ngx_rtmp_init_queue; +#elif (nginx_version >= 1007005) +ngx_thread_volatile ngx_queue_t ngx_rtmp_init_queue; +#else +ngx_thread_volatile ngx_event_t *ngx_rtmp_init_queue; +#endif + +ngx_int_t NGX_RTMP_HEVC_CODEC_ID = NGX_RTMP_VIDEO_H265; + +ngx_uint_t ngx_rtmp_max_module; + + +static ngx_command_t ngx_rtmp_commands[] = { + + { ngx_string("rtmp"), + NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, + ngx_rtmp_block, + 0, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_core_module_t ngx_rtmp_module_ctx = { + ngx_string("rtmp"), + NULL, + NULL +}; + + +ngx_module_t ngx_rtmp_module = { + NGX_MODULE_V1, + &ngx_rtmp_module_ctx, /* module context */ + ngx_rtmp_commands, /* module directives */ + NGX_CORE_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + ngx_rtmp_init_process, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static char * +ngx_rtmp_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + char *rv; + ngx_uint_t m, mi, s; + ngx_conf_t pcf; + ngx_array_t ports; + ngx_module_t **modules; + ngx_rtmp_module_t *module; + ngx_rtmp_conf_ctx_t *ctx; + ngx_rtmp_core_srv_conf_t *cscf, **cscfp; + ngx_rtmp_core_main_conf_t *cmcf; + + ctx = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_conf_ctx_t)); + if (ctx == NULL) { + return NGX_CONF_ERROR; + } + + *(ngx_rtmp_conf_ctx_t **) conf = ctx; + + /* count the number of the rtmp modules and set up their indices */ + +#if (nginx_version >= 1009011) + + ngx_rtmp_max_module = ngx_count_modules(cf->cycle, NGX_RTMP_MODULE); + +#else + + ngx_rtmp_max_module = 0; + for (m = 0; ngx_modules[m]; m++) { + if (ngx_modules[m]->type != NGX_RTMP_MODULE) { + continue; + } + + ngx_modules[m]->ctx_index = ngx_rtmp_max_module++; + } + +#endif + + + /* the rtmp main_conf context, it is the same in the all rtmp contexts */ + + ctx->main_conf = ngx_pcalloc(cf->pool, + sizeof(void *) * ngx_rtmp_max_module); + if (ctx->main_conf == NULL) { + return NGX_CONF_ERROR; + } + + + /* + * the rtmp null srv_conf context, it is used to merge + * the server{}s' srv_conf's + */ + + ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module); + if (ctx->srv_conf == NULL) { + return NGX_CONF_ERROR; + } + + + /* + * the rtmp null app_conf context, it is used to merge + * the server{}s' app_conf's + */ + + ctx->app_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module); + if (ctx->app_conf == NULL) { + return NGX_CONF_ERROR; + } + + + /* + * create the main_conf's, the null srv_conf's, and the null app_conf's + * of the all rtmp modules + */ + +#if (nginx_version >= 1009011) + modules = cf->cycle->modules; +#else + modules = ngx_modules; +#endif + + for (m = 0; modules[m]; m++) { + if (modules[m]->type != NGX_RTMP_MODULE) { + continue; + } + + module = modules[m]->ctx; + mi = modules[m]->ctx_index; + + if (module->create_main_conf) { + ctx->main_conf[mi] = module->create_main_conf(cf); + if (ctx->main_conf[mi] == NULL) { + return NGX_CONF_ERROR; + } + } + + if (module->create_srv_conf) { + ctx->srv_conf[mi] = module->create_srv_conf(cf); + if (ctx->srv_conf[mi] == NULL) { + return NGX_CONF_ERROR; + } + } + + if (module->create_app_conf) { + ctx->app_conf[mi] = module->create_app_conf(cf); + if (ctx->app_conf[mi] == NULL) { + return NGX_CONF_ERROR; + } + } + } + + pcf = *cf; + cf->ctx = ctx; + + for (m = 0; modules[m]; m++) { + if (modules[m]->type != NGX_RTMP_MODULE) { + continue; + } + + module = modules[m]->ctx; + + if (module->preconfiguration) { + if (module->preconfiguration(cf) != NGX_OK) { + return NGX_CONF_ERROR; + } + } + } + + /* parse inside the rtmp{} block */ + + cf->module_type = NGX_RTMP_MODULE; + cf->cmd_type = NGX_RTMP_MAIN_CONF; + rv = ngx_conf_parse(cf, NULL); + + if (rv != NGX_CONF_OK) { + *cf = pcf; + return rv; + } + + + /* init rtmp{} main_conf's, merge the server{}s' srv_conf's */ + + cmcf = ctx->main_conf[ngx_rtmp_core_module.ctx_index]; + cscfp = cmcf->servers.elts; + + for (m = 0; modules[m]; m++) { + if (modules[m]->type != NGX_RTMP_MODULE) { + continue; + } + + module = modules[m]->ctx; + mi = modules[m]->ctx_index; + + /* init rtmp{} main_conf's */ + + cf->ctx = ctx; + + if (module->init_main_conf) { + rv = module->init_main_conf(cf, ctx->main_conf[mi]); + if (rv != NGX_CONF_OK) { + *cf = pcf; + return rv; + } + } + + for (s = 0; s < cmcf->servers.nelts; s++) { + + /* merge the server{}s' srv_conf's */ + + cf->ctx = cscfp[s]->ctx; + + if (module->merge_srv_conf) { + rv = module->merge_srv_conf(cf, + ctx->srv_conf[mi], + cscfp[s]->ctx->srv_conf[mi]); + if (rv != NGX_CONF_OK) { + *cf = pcf; + return rv; + } + } + + if (module->merge_app_conf) { + + /* merge the server{}'s app_conf */ + + /*ctx->app_conf = cscfp[s]->ctx->loc_conf;*/ + + rv = module->merge_app_conf(cf, + ctx->app_conf[mi], + cscfp[s]->ctx->app_conf[mi]); + if (rv != NGX_CONF_OK) { + *cf = pcf; + return rv; + } + + /* merge the applications{}' app_conf's */ + + cscf = cscfp[s]->ctx->srv_conf[ngx_rtmp_core_module.ctx_index]; + + rv = ngx_rtmp_merge_applications(cf, &cscf->applications, + cscfp[s]->ctx->app_conf, + module, mi); + if (rv != NGX_CONF_OK) { + *cf = pcf; + return rv; + } + } + + } + } + + + if (ngx_rtmp_init_events(cf, cmcf) != NGX_OK) { + return NGX_CONF_ERROR; + } + + for (m = 0; modules[m]; m++) { + if (modules[m]->type != NGX_RTMP_MODULE) { + continue; + } + + module = modules[m]->ctx; + + if (module->postconfiguration) { + if (module->postconfiguration(cf) != NGX_OK) { + return NGX_CONF_ERROR; + } + } + } + + *cf = pcf; + + if (ngx_rtmp_init_event_handlers(cf, cmcf) != NGX_OK) { + return NGX_CONF_ERROR; + } + + if (ngx_array_init(&ports, cf->temp_pool, 4, sizeof(ngx_rtmp_conf_port_t)) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + if (ngx_rtmp_optimize_servers(cf, cmcf, cmcf->ports) != NGX_OK) { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + +static char * +ngx_rtmp_merge_applications(ngx_conf_t *cf, ngx_array_t *applications, + void **app_conf, ngx_rtmp_module_t *module, ngx_uint_t ctx_index) +{ + char *rv; + ngx_rtmp_conf_ctx_t *ctx, saved; + ngx_rtmp_core_app_conf_t **cacfp; + ngx_uint_t n; + ngx_rtmp_core_app_conf_t *cacf; + + if (applications == NULL) { + return NGX_CONF_OK; + } + + ctx = (ngx_rtmp_conf_ctx_t *) cf->ctx; + saved = *ctx; + + cacfp = applications->elts; + for (n = 0; n < applications->nelts; ++n, ++cacfp) { + + ctx->app_conf = (*cacfp)->app_conf; + + rv = module->merge_app_conf(cf, app_conf[ctx_index], + (*cacfp)->app_conf[ctx_index]); + if (rv != NGX_CONF_OK) { + return rv; + } + + cacf = (*cacfp)->app_conf[ngx_rtmp_core_module.ctx_index]; + rv = ngx_rtmp_merge_applications(cf, &cacf->applications, + (*cacfp)->app_conf, + module, ctx_index); + if (rv != NGX_CONF_OK) { + return rv; + } + } + + *ctx = saved; + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_rtmp_init_events(ngx_conf_t *cf, ngx_rtmp_core_main_conf_t *cmcf) +{ + size_t n; + + for(n = 0; n < NGX_RTMP_MAX_EVENT; ++n) { + if (ngx_array_init(&cmcf->events[n], cf->pool, 1, + sizeof(ngx_rtmp_handler_pt)) != NGX_OK) + { + return NGX_ERROR; + } + } + + if (ngx_array_init(&cmcf->amf, cf->pool, 1, + sizeof(ngx_rtmp_amf_handler_t)) != NGX_OK) + { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_init_event_handlers(ngx_conf_t *cf, ngx_rtmp_core_main_conf_t *cmcf) +{ + ngx_hash_init_t calls_hash; + ngx_rtmp_handler_pt *eh; + ngx_rtmp_amf_handler_t *h; + ngx_hash_key_t *ha; + size_t n, m; + + static size_t pm_events[] = { + NGX_RTMP_MSG_CHUNK_SIZE, + NGX_RTMP_MSG_ABORT, + NGX_RTMP_MSG_ACK, + NGX_RTMP_MSG_ACK_SIZE, + NGX_RTMP_MSG_BANDWIDTH + }; + + static size_t amf_events[] = { + NGX_RTMP_MSG_AMF_CMD, + NGX_RTMP_MSG_AMF_META, + NGX_RTMP_MSG_AMF_SHARED, + NGX_RTMP_MSG_AMF3_CMD, + NGX_RTMP_MSG_AMF3_META, + NGX_RTMP_MSG_AMF3_SHARED + }; + + /* init standard protocol events */ + for(n = 0; n < sizeof(pm_events) / sizeof(pm_events[0]); ++n) { + eh = ngx_array_push(&cmcf->events[pm_events[n]]); + *eh = ngx_rtmp_protocol_message_handler; + } + + /* init amf events */ + for(n = 0; n < sizeof(amf_events) / sizeof(amf_events[0]); ++n) { + eh = ngx_array_push(&cmcf->events[amf_events[n]]); + *eh = ngx_rtmp_amf_message_handler; + } + + /* init user protocol events */ + eh = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_USER]); + *eh = ngx_rtmp_user_message_handler; + + /* aggregate to audio/video map */ + eh = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AGGREGATE]); + *eh = ngx_rtmp_aggregate_message_handler; + + /* init amf callbacks */ + ngx_array_init(&cmcf->amf_arrays, cf->pool, 1, sizeof(ngx_hash_key_t)); + + h = cmcf->amf.elts; + for(n = 0; n < cmcf->amf.nelts; ++n, ++h) { + ha = cmcf->amf_arrays.elts; + for(m = 0; m < cmcf->amf_arrays.nelts; ++m, ++ha) { + if (h->name.len == ha->key.len + && !ngx_strncmp(h->name.data, ha->key.data, ha->key.len)) + { + break; + } + } + if (m == cmcf->amf_arrays.nelts) { + ha = ngx_array_push(&cmcf->amf_arrays); + ha->key = h->name; + ha->key_hash = ngx_hash_key_lc(ha->key.data, ha->key.len); + ha->value = ngx_array_create(cf->pool, 1, + sizeof(ngx_rtmp_handler_pt)); + if (ha->value == NULL) { + return NGX_ERROR; + } + } + + eh = ngx_array_push((ngx_array_t*)ha->value); + *eh = h->handler; + } + + calls_hash.hash = &cmcf->amf_hash; + calls_hash.key = ngx_hash_key_lc; + calls_hash.max_size = 512; + calls_hash.bucket_size = ngx_cacheline_size; + calls_hash.name = "amf_hash"; + calls_hash.pool = cf->pool; + calls_hash.temp_pool = NULL; + + if (ngx_hash_init(&calls_hash, cmcf->amf_arrays.elts, cmcf->amf_arrays.nelts) + != NGX_OK) + { + return NGX_ERROR; + } + + return NGX_OK; +} + + +/* add the server core module configuration to the address:port */ + +static ngx_int_t +ngx_rtmp_add_server(ngx_conf_t *cf, ngx_rtmp_core_srv_conf_t *cscf, + ngx_rtmp_conf_addr_t *addr) +{ + ngx_uint_t i; + ngx_rtmp_core_srv_conf_t **server; + + if (addr->servers.elts == NULL) { + if (ngx_array_init(&addr->servers, cf->temp_pool, 4, + sizeof(ngx_rtmp_core_srv_conf_t *)) + != NGX_OK) + { + return NGX_ERROR; + } + + } else { + server = addr->servers.elts; + for (i = 0; i < addr->servers.nelts; i++) { + if (server[i] == cscf) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "a duplicate listen %s", addr->opt.addr); + return NGX_ERROR; + } + } + } + + server = ngx_array_push(&addr->servers); + if (server == NULL) { + return NGX_ERROR; + } + + *server = cscf; + + return NGX_OK; +} + + +/* + * add the server address, the server names and the server core module + * configurations to the port list + */ + +static ngx_int_t +ngx_rtmp_add_address(ngx_conf_t *cf, ngx_rtmp_core_srv_conf_t *cscf, + ngx_rtmp_conf_port_t *port, ngx_rtmp_listen_opt_t *lsopt) +{ + ngx_rtmp_conf_addr_t *addr; + + if (port->addrs.elts == NULL) { + if (ngx_array_init(&port->addrs, cf->temp_pool, 4, + sizeof(ngx_rtmp_conf_addr_t)) + != NGX_OK) + { + return NGX_ERROR; + } + } + + addr = ngx_array_push(&port->addrs); + if (addr == NULL) { + return NGX_ERROR; + } + + addr->opt = *lsopt; + addr->hash.buckets = NULL; + addr->hash.size = 0; + addr->wc_head = NULL; + addr->wc_tail = NULL; +#if (NGX_PCRE) + addr->nregex = 0; + addr->regex = NULL; +#endif + addr->default_server = cscf; + addr->servers.elts = NULL; + + return ngx_rtmp_add_server(cf, cscf, addr); +} + + +static ngx_int_t +ngx_rtmp_add_addresses(ngx_conf_t *cf, ngx_rtmp_core_srv_conf_t *cscf, + ngx_rtmp_conf_port_t *port, ngx_rtmp_listen_opt_t *lsopt) +{ + ngx_uint_t i, default_server, proxy_protocol; + ngx_rtmp_conf_addr_t *addr; + + /* + * we cannot compare whole sockaddr struct's as kernel + * may fill some fields in inherited sockaddr struct's + */ + + addr = port->addrs.elts; + + for (i = 0; i < port->addrs.nelts; i++) { + + if (ngx_cmp_sockaddr(lsopt->sockaddr, lsopt->socklen, + addr[i].opt.sockaddr, + addr[i].opt.socklen, 0) + != NGX_OK) + { + continue; + } + + /* the address is already in the address list */ + + if (ngx_rtmp_add_server(cf, cscf, &addr[i]) != NGX_OK) { + return NGX_ERROR; + } + + /* preserve default_server bit during listen options overwriting */ + default_server = addr[i].opt.default_server; + + proxy_protocol = lsopt->proxy_protocol || addr[i].opt.proxy_protocol; + + if (lsopt->set) { + + if (addr[i].opt.set) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "duplicate listen options for %s", addr[i].opt.addr); + return NGX_ERROR; + } + + addr[i].opt = *lsopt; + } + + /* check the duplicate "default" server for this address:port */ + + if (lsopt->default_server) { + + if (default_server) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "a duplicate default server for %s", addr[i].opt.addr); + return NGX_ERROR; + } + + default_server = 1; + addr[i].default_server = cscf; + } + + addr[i].opt.default_server = default_server; + addr[i].opt.proxy_protocol = proxy_protocol; + + return NGX_OK; + } + + /* add the address to the addresses list that bound to this port */ + + return ngx_rtmp_add_address(cf, cscf, port, lsopt); +} + + +ngx_int_t +ngx_rtmp_add_listen(ngx_conf_t *cf, ngx_rtmp_core_srv_conf_t *cscf, + ngx_rtmp_listen_opt_t *lsopt) +{ + in_port_t p; + ngx_uint_t i; + struct sockaddr *sa; + ngx_rtmp_conf_port_t *port; + ngx_rtmp_core_main_conf_t *cmcf; + + cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); + + if (cmcf->ports == NULL) { + cmcf->ports = ngx_array_create(cf->temp_pool, 2, + sizeof(ngx_rtmp_conf_port_t)); + if (cmcf->ports == NULL) { + return NGX_ERROR; + } + } + + sa = lsopt->sockaddr; + p = ngx_inet_get_port(sa); + + port = cmcf->ports->elts; + for (i = 0; i < cmcf->ports->nelts; i++) { + + if (p != port[i].port || sa->sa_family != port[i].family) { + continue; + } + + /* a port is already in the port list */ + + return ngx_rtmp_add_addresses(cf, cscf, &port[i], lsopt); + } + + /* add a port to the port list */ + + port = ngx_array_push(cmcf->ports); + if (port == NULL) { + return NGX_ERROR; + } + + port->family = sa->sa_family; + port->port = p; + port->addrs.elts = NULL; + + return ngx_rtmp_add_address(cf, cscf, port, lsopt); +} + + +static int ngx_libc_cdecl +ngx_rtmp_cmp_dns_wildcards(const void *one, const void *two) +{ + ngx_hash_key_t *first, *second; + + first = (ngx_hash_key_t *) one; + second = (ngx_hash_key_t *) two; + + return ngx_dns_strcmp(first->key.data, second->key.data); +} + + +static ngx_int_t +ngx_rtmp_server_names(ngx_conf_t *cf, ngx_rtmp_core_main_conf_t *cmcf, + ngx_rtmp_conf_addr_t *addr) +{ + ngx_int_t rc; + ngx_uint_t n, s; + ngx_hash_init_t hash; + ngx_hash_keys_arrays_t ha; + ngx_rtmp_server_name_t *name; + ngx_rtmp_core_srv_conf_t **cscfp; +#if (NGX_PCRE) + ngx_uint_t regex, i; + + regex = 0; +#endif + + ngx_memzero(&ha, sizeof(ngx_hash_keys_arrays_t)); + + ha.temp_pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log); + if (ha.temp_pool == NULL) { + return NGX_ERROR; + } + + ha.pool = cf->pool; + + if (ngx_hash_keys_array_init(&ha, NGX_HASH_LARGE) != NGX_OK) { + goto failed; + } + + cscfp = addr->servers.elts; + + for (s = 0; s < addr->servers.nelts; s++) { + + name = cscfp[s]->server_names.elts; + + for (n = 0; n < cscfp[s]->server_names.nelts; n++) { + +#if (NGX_PCRE) + if (name[n].regex) { + regex++; + continue; + } +#endif + + rc = ngx_hash_add_key(&ha, &name[n].name, name[n].server, + NGX_HASH_WILDCARD_KEY); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_DECLINED) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "invalid server name or wildcard \"%V\" on %s", + &name[n].name, addr->opt.addr); + return NGX_ERROR; + } + + if (rc == NGX_BUSY) { + ngx_log_error(NGX_LOG_WARN, cf->log, 0, + "conflicting server name \"%V\" on %s, ignored", + &name[n].name, addr->opt.addr); + } + } + } + + hash.key = ngx_hash_key_lc; + hash.max_size = cmcf->server_names_hash_max_size; + hash.bucket_size = cmcf->server_names_hash_bucket_size; + hash.name = "rtmp_server_names_hash"; + hash.pool = cf->pool; + + if (ha.keys.nelts) { + hash.hash = &addr->hash; + hash.temp_pool = NULL; + + if (ngx_hash_init(&hash, ha.keys.elts, ha.keys.nelts) != NGX_OK) { + goto failed; + } + } + + if (ha.dns_wc_head.nelts) { + + ngx_qsort(ha.dns_wc_head.elts, (size_t) ha.dns_wc_head.nelts, + sizeof(ngx_hash_key_t), ngx_rtmp_cmp_dns_wildcards); + + hash.hash = NULL; + hash.temp_pool = ha.temp_pool; + + if (ngx_hash_wildcard_init(&hash, ha.dns_wc_head.elts, + ha.dns_wc_head.nelts) + != NGX_OK) + { + goto failed; + } + + addr->wc_head = (ngx_hash_wildcard_t *) hash.hash; + } + + if (ha.dns_wc_tail.nelts) { + + ngx_qsort(ha.dns_wc_tail.elts, (size_t) ha.dns_wc_tail.nelts, + sizeof(ngx_hash_key_t), ngx_rtmp_cmp_dns_wildcards); + + hash.hash = NULL; + hash.temp_pool = ha.temp_pool; + + if (ngx_hash_wildcard_init(&hash, ha.dns_wc_tail.elts, + ha.dns_wc_tail.nelts) + != NGX_OK) + { + goto failed; + } + + addr->wc_tail = (ngx_hash_wildcard_t *) hash.hash; + } + + ngx_destroy_pool(ha.temp_pool); + +#if (NGX_PCRE) + + if (regex == 0) { + return NGX_OK; + } + + addr->nregex = regex; + addr->regex = ngx_palloc(cf->pool, regex * sizeof(ngx_rtmp_server_name_t)); + if (addr->regex == NULL) { + return NGX_ERROR; + } + + i = 0; + + for (s = 0; s < addr->servers.nelts; s++) { + + name = cscfp[s]->server_names.elts; + + for (n = 0; n < cscfp[s]->server_names.nelts; n++) { + if (name[n].regex) { + addr->regex[i++] = name[n]; + } + } + } + +#endif + + return NGX_OK; + +failed: + + ngx_destroy_pool(ha.temp_pool); + + return NGX_ERROR; +} + + +static ngx_listening_t * +ngx_rtmp_add_listening(ngx_conf_t *cf, ngx_rtmp_conf_addr_t *addr) +{ + ngx_listening_t *ls; + + ls = ngx_create_listening(cf, addr->opt.sockaddr, + addr->opt.socklen); + if (ls == NULL) { + return NULL; + } + + ls->addr_ntop = 1; + ls->handler = ngx_rtmp_init_connection; + ls->pool_size = 4096; + + /* TODO: error_log directive */ + ls->logp = &cf->cycle->new_log; + ls->log.data = &ls->addr_text; + ls->log.handler = ngx_accept_log_error; + + ls->keepalive = addr->opt.so_keepalive; +#if (NGX_HAVE_KEEPALIVE_TUNABLE) + ls->keepidle = addr->opt.tcp_keepidle; + ls->keepintvl = addr->opt.tcp_keepintvl; + ls->keepcnt = addr->opt.tcp_keepcnt; +#endif + +#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY) + ls->ipv6only = addr->opt.ipv6only; +#endif + +#if (NGX_HAVE_REUSEPORT) + ls->reuseport = addr->opt.reuseport; +#endif + + return ls; +} + + +static ngx_int_t +ngx_rtmp_init_listening(ngx_conf_t *cf, ngx_rtmp_conf_port_t *port) +{ + ngx_uint_t i, last, bind_wildcard; + ngx_listening_t *ls; + ngx_rtmp_port_t *mport; + ngx_rtmp_conf_addr_t *addr; + + addr = port->addrs.elts; + last = port->addrs.nelts; + + /* + * If there is a binding to an "*:port" then we need to bind() to + * the "*:port" only and ignore other implicit bindings. The bindings + * have been already sorted: explicit bindings are on the start, then + * implicit bindings go, and wildcard binding is in the end. + */ + + if (addr[last - 1].opt.wildcard) { + addr[last - 1].opt.bind = 1; + bind_wildcard = 1; + + } else { + bind_wildcard = 0; + } + + i = 0; + + while (i < last) { + + if (bind_wildcard && !addr[i].opt.bind) { + i++; + continue; + } + + ls = ngx_rtmp_add_listening(cf, &addr[i]); + if (ls == NULL) { + return NGX_ERROR; + } + + mport = ngx_palloc(cf->pool, sizeof(ngx_rtmp_port_t)); + if (mport == NULL) { + return NGX_ERROR; + } + + ls->servers = mport; + + if (i == last - 1) { + mport->naddrs = last; + + } else { + mport->naddrs = 1; + i = 0; + } + + switch (ls->sockaddr->sa_family) { +#if (NGX_HAVE_INET6) + case AF_INET6: + if (ngx_rtmp_add_addrs6(cf, mport, addr) != NGX_OK) { + return NGX_ERROR; + } + break; +#endif + default: /* AF_INET */ + if (ngx_rtmp_add_addrs(cf, mport, addr) != NGX_OK) { + return NGX_ERROR; + } + break; + } + +#if nginx_version < 1015002 + if (ngx_clone_listening(cf, ls) != NGX_OK) { +#else + if (ngx_clone_listening(cf->cycle, ls) != NGX_OK) { +#endif + return NGX_ERROR; + } + + addr++; + last--; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_optimize_servers(ngx_conf_t *cf, ngx_rtmp_core_main_conf_t *cmcf, + ngx_array_t *ports) +{ + ngx_uint_t i, p; + ngx_rtmp_conf_port_t *port; + ngx_rtmp_conf_addr_t *addr; + + if (ports == NULL) { + return NGX_OK; + } + + port = ports->elts; + for (p = 0; p < ports->nelts; p++) { + + ngx_sort(port[p].addrs.elts, (size_t) port[p].addrs.nelts, + sizeof(ngx_rtmp_conf_addr_t), ngx_rtmp_cmp_conf_addrs); + + /* + * check whether all name-based servers have the same + * configuration as a default server for given address:port + */ + + addr = port[p].addrs.elts; + for (i = 0; i < port[p].addrs.nelts; ++i) { + + if (addr[i].servers.nelts > 1 +#if (NGX_PCRE) + || addr[i].default_server->captures +#endif + ) + { + if (ngx_rtmp_server_names(cf, cmcf, &addr[i]) != NGX_OK) { + return NGX_ERROR; + } + } + + if (ngx_rtmp_init_listening(cf, &port[p]) != NGX_OK) { + return NGX_ERROR; + } + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_add_addrs(ngx_conf_t *cf, ngx_rtmp_port_t *mport, + ngx_rtmp_conf_addr_t *addr) +{ + u_char *p; + size_t len; + ngx_uint_t i; + ngx_rtmp_in_addr_t *addrs; + struct sockaddr_in *sin; + u_char buf[NGX_SOCKADDR_STRLEN]; + ngx_rtmp_virtual_names_t *vn; + + mport->addrs = ngx_pcalloc(cf->pool, + mport->naddrs * sizeof(ngx_rtmp_in_addr_t)); + if (mport->addrs == NULL) { + return NGX_ERROR; + } + + addrs = mport->addrs; + + for (i = 0; i < mport->naddrs; i++) { + + sin = (struct sockaddr_in *) addr[i].opt.sockaddr; + addrs[i].addr = sin->sin_addr.s_addr; + addrs[i].conf.default_server = addr[i].default_server; + + len = ngx_sock_ntop(addr[i].opt.sockaddr, +#if (nginx_version >= 1005003) + addr[i].opt.socklen, +#endif + buf, NGX_SOCKADDR_STRLEN, 1); + + p = ngx_pnalloc(cf->pool, len); + if (p == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(p, buf, len); + + addrs[i].conf.addr_text.len = len; + addrs[i].conf.addr_text.data = p; + addrs[i].conf.proxy_protocol = addr->opt.proxy_protocol; + + if (addr[i].hash.buckets == NULL + && (addr[i].wc_head == NULL + || addr[i].wc_head->hash.buckets == NULL) + && (addr[i].wc_tail == NULL + || addr[i].wc_tail->hash.buckets == NULL) +#if (NGX_PCRE) + && addr[i].nregex == 0 +#endif + ) + { + continue; + } + + vn = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_virtual_names_t)); + if (vn == NULL) { + return NGX_ERROR; + } + + addrs[i].conf.virtual_names = vn; + + vn->names.hash = addr[i].hash; + vn->names.wc_head = addr[i].wc_head; + vn->names.wc_tail = addr[i].wc_tail; +#if (NGX_PCRE) + vn->nregex = addr[i].nregex; + vn->regex = addr[i].regex; +#endif + } + + return NGX_OK; +} + + +#if (NGX_HAVE_INET6) + +static ngx_int_t +ngx_rtmp_add_addrs6(ngx_conf_t *cf, ngx_rtmp_port_t *mport, + ngx_rtmp_conf_addr_t *addr) +{ + u_char *p; + size_t len; + ngx_uint_t i; + ngx_rtmp_in6_addr_t *addrs6; + struct sockaddr_in6 *sin6; + u_char buf[NGX_SOCKADDR_STRLEN]; + ngx_rtmp_virtual_names_t *vn; + + mport->addrs = ngx_pcalloc(cf->pool, + mport->naddrs * sizeof(ngx_rtmp_in6_addr_t)); + if (mport->addrs == NULL) { + return NGX_ERROR; + } + + addrs6 = mport->addrs; + + for (i = 0; i < mport->naddrs; i++) { + + sin6 = (struct sockaddr_in6 *) addr[i].opt.sockaddr; + addrs6[i].addr6 = sin6->sin6_addr; + addrs6[i].conf.default_server = addr[i].default_server; + + len = ngx_sock_ntop(addr[i].opt.sockaddr, +#if (nginx_version >= 1005003) + addr[i].opt.socklen, +#endif + buf, NGX_SOCKADDR_STRLEN, 1); + + p = ngx_pnalloc(cf->pool, len); + if (p == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(p, buf, len); + + addrs6[i].conf.addr_text.len = len; + addrs6[i].conf.addr_text.data = p; + addrs6[i].conf.proxy_protocol = addr->opt.proxy_protocol; + + if (addr[i].hash.buckets == NULL + && (addr[i].wc_head == NULL + || addr[i].wc_head->hash.buckets == NULL) + && (addr[i].wc_tail == NULL + || addr[i].wc_tail->hash.buckets == NULL) +#if (NGX_PCRE) + && addr[i].nregex == 0 +#endif + ) + { + continue; + } + + vn = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_virtual_names_t)); + if (vn == NULL) { + return NGX_ERROR; + } + + addrs6[i].conf.virtual_names = vn; + + vn->names.hash = addr[i].hash; + vn->names.wc_head = addr[i].wc_head; + vn->names.wc_tail = addr[i].wc_tail; +#if (NGX_PCRE) + vn->nregex = addr[i].nregex; + vn->regex = addr[i].regex; +#endif + } + + return NGX_OK; +} + +#endif + + +static ngx_int_t +ngx_rtmp_cmp_conf_addrs(const void *one, const void *two) +{ + ngx_rtmp_conf_addr_t *first, *second; + + first = (ngx_rtmp_conf_addr_t *) one; + second = (ngx_rtmp_conf_addr_t *) two; + + if (first->opt.wildcard) { + /* a wildcard must be the last resort, shift it to the end */ + return 1; + } + + if (first->opt.bind && !second->opt.bind) { + /* shift explicit bind()ed addresses to the start */ + return -1; + } + + if (!first->opt.bind && second->opt.bind) { + /* shift explicit bind()ed addresses to the start */ + return 1; + } + + /* do not sort by default */ + + return 0; +} + + +ngx_int_t +ngx_rtmp_fire_event(ngx_rtmp_session_t *s, ngx_uint_t evt, + ngx_rtmp_header_t *h, ngx_chain_t *in) +{ + ngx_rtmp_core_main_conf_t *cmcf; + ngx_array_t *ch; + ngx_rtmp_handler_pt *hh; + size_t n; + + cmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_core_module); + + ch = &cmcf->events[evt]; + hh = ch->elts; + for(n = 0; n < ch->nelts; ++n, ++hh) { + if (*hh && (*hh)(s, h, in) != NGX_OK) { + return NGX_ERROR; + } + } + return NGX_OK; +} + + +void * +ngx_rtmp_rmemcpy(void *dst, const void* src, size_t n) +{ + u_char *d, *s; + + d = dst; + s = (u_char*)src + n - 1; + + while(s >= (u_char*)src) { + *d++ = *s--; + } + + return dst; +} + + +ngx_int_t +ngx_rtmp_find_virtual_server(ngx_rtmp_virtual_names_t *virtual_names, + ngx_str_t *host, ngx_rtmp_core_srv_conf_t **cscfp) +{ + ngx_rtmp_core_srv_conf_t *cscf; + + if (virtual_names == NULL) { + return NGX_DECLINED; + } + + cscf = ngx_hash_find_combined(&virtual_names->names, + ngx_hash_key(host->data, host->len), + host->data, host->len); + + if (cscf) { + *cscfp = cscf; + return NGX_OK; + } + +#if (NGX_PCRE) + + if (host->len && virtual_names->nregex) { + ngx_int_t n; + ngx_uint_t i; + ngx_rtmp_server_name_t *sn; + + sn = virtual_names->regex; + + for (i = 0; i < virtual_names->nregex; ++i) { + + n = ngx_regex_exec(sn[i].regex->regex, host, NULL, 0); + + if (n == NGX_REGEX_NO_MATCHED) { + continue; + } + + if (n >= 0) { + *cscfp = sn[i].server; + return NGX_OK; + } + + return NGX_ERROR; + } + } + +#endif + + return NGX_DECLINED; +} + +ngx_int_t +ngx_rtmp_set_virtual_server(ngx_rtmp_session_t *s, ngx_str_t *host) +{ + ngx_int_t rc; + ngx_rtmp_addr_conf_t *addr_conf; + ngx_rtmp_core_srv_conf_t *cscf; + +#if (NGX_SUPPRESS_WARN) + cscf = NULL; +#endif + + addr_conf = s->addr_conf; + + rc = ngx_rtmp_find_virtual_server(addr_conf->virtual_names, host, &cscf); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_DECLINED) { + return NGX_OK; + } + + s->srv_conf = cscf->ctx->srv_conf; + + return NGX_OK; +} + +static ngx_int_t +ngx_rtmp_init_process(ngx_cycle_t *cycle) +{ +#if (nginx_version >= 1007005) + ngx_queue_init(&ngx_rtmp_init_queue); +#endif + return NGX_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp.h new file mode 100644 index 0000000000..5df632e7f2 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp.h @@ -0,0 +1,1021 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#ifndef _NGX_RTMP_H_INCLUDED_ +#define _NGX_RTMP_H_INCLUDED_ + + +#include +#include +#include +#include +#include +#include + +#include "ngx_rtmp_amf.h" +#include "ngx_rtmp_bandwidth.h" +#include "ngx_http_client.h" +#include "ngx_netcall.h" +#include "ngx_map.h" + + +#if (NGX_WIN32) +typedef __int8 int8_t; +typedef unsigned __int8 uint8_t; +#endif + + +#if (NGX_PCRE) + +typedef struct { + ngx_uint_t capture; + ngx_int_t index; +} ngx_rtmp_regex_variable_t; + +typedef struct { + ngx_regex_t *regex; + ngx_uint_t ncaptures; + ngx_rtmp_regex_variable_t *variables; + ngx_uint_t nvariables; + ngx_str_t name; +} ngx_rtmp_regex_t; + +typedef struct { + ngx_rtmp_regex_t *regex; + void *value; +} ngx_rtmp_map_regex_t; + +#endif + + +typedef struct { + void **main_conf; + void **srv_conf; + void **app_conf; +} ngx_rtmp_conf_ctx_t; + + +typedef struct { + void *addrs; + ngx_uint_t naddrs; +} ngx_rtmp_port_t; + + +typedef struct { + int family; + in_port_t port; + ngx_array_t addrs; /* array of ngx_rtmp_conf_addr_t */ +} ngx_rtmp_conf_port_t; + + +#define NGX_RTMP_VERSION 3 + +#define NGX_LOG_DEBUG_RTMP NGX_LOG_DEBUG_CORE + +#define NGX_RTMP_DEFAULT_CHUNK_SIZE 128 + + +/* RTMP message types */ +#define NGX_RTMP_MSG_CHUNK_SIZE 1 +#define NGX_RTMP_MSG_ABORT 2 +#define NGX_RTMP_MSG_ACK 3 +#define NGX_RTMP_MSG_USER 4 +#define NGX_RTMP_MSG_ACK_SIZE 5 +#define NGX_RTMP_MSG_BANDWIDTH 6 +#define NGX_RTMP_MSG_EDGE 7 +#define NGX_RTMP_MSG_AUDIO 8 +#define NGX_RTMP_MSG_VIDEO 9 +#define NGX_RTMP_MSG_AMF3_META 15 +#define NGX_RTMP_MSG_AMF3_SHARED 16 +#define NGX_RTMP_MSG_AMF3_CMD 17 +#define NGX_RTMP_MSG_AMF_META 18 +#define NGX_RTMP_MSG_AMF_SHARED 19 +#define NGX_RTMP_MSG_AMF_CMD 20 +#define NGX_RTMP_MSG_AGGREGATE 22 +#define NGX_RTMP_MSG_MAX 22 + +#define NGX_RTMP_CONNECT NGX_RTMP_MSG_MAX + 1 +#define NGX_RTMP_DISCONNECT NGX_RTMP_MSG_MAX + 2 +#define NGX_RTMP_HANDSHAKE_DONE NGX_RTMP_MSG_MAX + 3 +#define NGX_MPEGTS_MSG_AUDIO NGX_RTMP_MSG_MAX + 4 +#define NGX_MPEGTS_MSG_VIDEO NGX_RTMP_MSG_MAX + 5 +#define NGX_MPEGTS_MSG_PATPMT NGX_RTMP_MSG_MAX + 6 +#define NGX_MPEGTS_MSG_M3U8 NGX_RTMP_MSG_MAX + 7 +#define NGX_MPEGTS_MSG_CLOSE NGX_RTMP_MSG_MAX + 8 +#define NGX_RTMP_MAX_EVENT NGX_RTMP_MSG_MAX + 9 + + +/* RMTP control message types */ +#define NGX_RTMP_USER_STREAM_BEGIN 0 +#define NGX_RTMP_USER_STREAM_EOF 1 +#define NGX_RTMP_USER_STREAM_DRY 2 +#define NGX_RTMP_USER_SET_BUFLEN 3 +#define NGX_RTMP_USER_RECORDED 4 +#define NGX_RTMP_USER_PING_REQUEST 6 +#define NGX_RTMP_USER_PING_RESPONSE 7 +#define NGX_RTMP_USER_UNKNOWN 8 +#define NGX_RTMP_USER_BUFFER_END 31 + + +/* Chunk header: + * max 3 basic header + * + max 11 message header + * + max 4 extended header (timestamp) */ +#define NGX_RTMP_MAX_CHUNK_HEADER 18 + +#define IS_IRAP(_nal_type_) (_nal_type_ >= 16 && _nal_type_ <= 23) + +/* Audio codecs */ +enum { + /* Uncompressed codec id is actually 0, + * but we use another value for consistency */ + NGX_RTMP_AUDIO_UNCOMPRESSED = 16, + NGX_RTMP_AUDIO_ADPCM = 1, + NGX_RTMP_AUDIO_MP3 = 2, + NGX_RTMP_AUDIO_LINEAR_LE = 3, + NGX_RTMP_AUDIO_NELLY16 = 4, + NGX_RTMP_AUDIO_NELLY8 = 5, + NGX_RTMP_AUDIO_NELLY = 6, + NGX_RTMP_AUDIO_G711A = 7, + NGX_RTMP_AUDIO_G711U = 8, + NGX_RTMP_AUDIO_AAC = 10, + NGX_RTMP_AUDIO_SPEEX = 11, + NGX_RTMP_AUDIO_MP3_8 = 14, + NGX_RTMP_AUDIO_DEVSPEC = 15, +}; + + +/* Video codecs */ +enum { + NGX_RTMP_VIDEO_JPEG = 1, + NGX_RTMP_VIDEO_SORENSON_H263 = 2, + NGX_RTMP_VIDEO_SCREEN = 3, + NGX_RTMP_VIDEO_ON2_VP6 = 4, + NGX_RTMP_VIDEO_ON2_VP6_ALPHA = 5, + NGX_RTMP_VIDEO_SCREEN2 = 6, + NGX_RTMP_VIDEO_H264 = 7, + NGX_RTMP_VIDEO_H265 = 12 +}; + +extern ngx_int_t NGX_RTMP_HEVC_CODEC_ID; + +typedef struct { + uint32_t csid; /* chunk stream id */ + uint32_t timestamp; /* timestamp (delta) */ + uint32_t mlen; /* message length */ + uint8_t type; /* message type id */ + uint32_t msid; /* message stream id */ +} ngx_rtmp_header_t; + + +typedef struct { + ngx_rtmp_header_t hdr; + uint32_t dtime; + uint32_t len; /* current fragment length */ + uint8_t ext; + ngx_chain_t *in; +} ngx_rtmp_stream_t; + + +typedef struct ngx_rtmp_frame_s ngx_rtmp_frame_t; + +struct ngx_rtmp_frame_s { + ngx_rtmp_header_t hdr; + ngx_flag_t av_header; + ngx_flag_t keyframe; + ngx_flag_t mandatory; + ngx_uint_t ref; + + ngx_rtmp_frame_t *next; + ngx_chain_t *chain; +}; + +typedef struct ngx_mpegts_frame_s ngx_mpegts_frame_t; + +struct ngx_mpegts_frame_s { + uint64_t pts; + uint64_t dts; + ngx_uint_t pid; + ngx_uint_t sid; + ngx_uint_t cc; + unsigned key:1; + ngx_uint_t ref; + + ngx_uint_t type; + ngx_uint_t length; + size_t pos; + + ngx_mpegts_frame_t *next; + ngx_chain_t *chain; +}; + +/* disable zero-sized array warning by msvc */ + +#if (NGX_WIN32) +#pragma warning(push) +#pragma warning(disable:4200) +#endif + +#define NGX_RTMP_LIVE 0 +#define NGX_HTTP_FLV_LIVE 1 +#define NGX_HLS_LIVE 2 +#define NGX_MPEGTS_LIVE 3 + +typedef struct ngx_rtmp_session_s ngx_rtmp_session_t; + +#define NGX_RTMP_MAX_MERGE_FRAME 64 + +typedef ngx_chain_t * (* ngx_rtmp_prepared_pt)(ngx_rtmp_session_t *s); + +typedef struct ngx_live_stream_s ngx_live_stream_t; +typedef struct ngx_live_server_s ngx_live_server_t; +typedef struct ngx_rtmp_addr_conf_s ngx_rtmp_addr_conf_t; + +#define NGX_LIVE_INIT 0 +#define NGX_LIVE_HANDSHAKE_DONE 1 +#define NGX_LIVE_CONNECT 2 +#define NGX_LIVE_CREATE_STREAM 3 +#define NGX_LIVE_PUBLISH 4 +#define NGX_LIVE_PLAY 5 +#define NGX_LIVE_AV 6 +#define NGX_LIVE_CLOSE 7 + +#define NGX_LIVE_INTERNAL_ERR 0 +#define NGX_LIVE_NORMAL_CLOSE 1 +#define NGX_LIVE_RTMP_SEND_ERR 2 +#define NGX_LIVE_RTMP_SEND_TIMEOUT 3 +#define NGX_LIVE_FLV_SEND_ERR 4 +#define NGX_LIVE_FLV_SEND_TIMEOUT 5 +#define NGX_LIVE_RTMP_RECV_ERR 6 +#define NGX_LIVE_FLV_RECV_ERR 7 +#define NGX_LIVE_RELAY_TRANSIT 8 +#define NGX_LIVE_RELAY_TIMEOUT 9 +#define NGX_LIVE_CONTROL_DROP 10 +#define NGX_LIVE_DROP_IDLE 11 +#define NGX_LIVE_OCLP_NOTIFY_ERR 12 +#define NGX_LIVE_OCLP_RELAY_ERR 13 +#define NGX_LIVE_OCLP_PARA_ERR 14 +#define NGX_LIVE_RELAY_CLOSE 15 +#define NGX_LIVE_PROCESS_EXIT 16 + +struct ngx_rtmp_session_s { + ngx_atomic_uint_t number; + struct sockaddr *sockaddr; + ngx_msec_t roll_back; + + unsigned int destroyed:1; + + ngx_flag_t pause; + + ngx_int_t acodec; + ngx_int_t vcodec; + uint32_t signature; /* "RTMP" */ /* <-- FIXME wtf */ + + ngx_event_t close; + + ngx_pool_t *pool; + ngx_log_t *log; + + ngx_rtmp_addr_conf_t *addr_conf; + + void **ctx; + void **main_conf; + void **srv_conf; + void **app_conf; + + ngx_live_server_t *live_server; + ngx_live_stream_t *live_stream; + + ngx_str_t remote_addr_text; + ngx_str_t *addr_text; + int connected; + + ngx_http_variable_value_t *variables; + +#if (NGX_PCRE) + ngx_uint_t ncaptures; + int *captures; + u_char *captures_data; +#endif + +#if (nginx_version >= 1007005) + ngx_queue_t posted_dry_events; +#else + ngx_event_t *posted_dry_events; +#endif + + /* client buffer time in msec */ + uint32_t buflen; + uint32_t ack_size; + + ngx_str_t groupid; + + /* app/name */ + ngx_str_t stream; + + /* stream name in publish or play*/ + ngx_str_t name; + ngx_str_t pargs; /* play or publish args */ + + /* connection parameters */ + ngx_str_t app; + ngx_str_t args; + ngx_str_t flashver; + ngx_str_t swf_url; + ngx_str_t tc_url; + uint32_t acodecs; + uint32_t vcodecs; + ngx_str_t page_url; + + /* middleware */ + ngx_str_t scheme; + ngx_str_t domain; + ngx_str_t serverid; + + /* handshake data */ + ngx_buf_t *hs_buf; + u_char *hs_digest; + unsigned hs_old:1; + ngx_uint_t hs_stage; + + /* connection timestamps */ + ngx_msec_t epoch; + ngx_msec_t peer_epoch; + ngx_msec_t base_time; + uint32_t current_time; + + /* ping */ + ngx_event_t ping_evt; + unsigned ping_active:1; + unsigned ping_reset:1; + + /* auto-pushed? */ + unsigned interprocess:1; + unsigned static_pull:1; + unsigned relay:1; + unsigned played:1; + unsigned published:1; + unsigned closed:1; + unsigned publishing:1; + + /* live type: 0- RTMP 1- http-flv 2- hls */ + unsigned live_type:2; + ngx_uint_t status; + ngx_http_request_t *request; + + unsigned flv_state; + + ngx_uint_t flv_version; + ngx_uint_t flv_flags; + unsigned flv_data_offset; + unsigned flv_tagsize; + unsigned flv_first_pts; + + /* input stream 0 (reserved by RTMP spec) + * is used as free chain link */ + + ngx_rtmp_stream_t *in_streams; + uint32_t in_csid; + ngx_uint_t in_chunk_size; + ngx_pool_t *in_pool; + uint32_t in_bytes; + uint32_t in_last_ack; + + ngx_pool_t *in_old_pool; + ngx_int_t in_chunk_size_changing; + + ngx_connection_t *connection; + + /* merge frame and send */ + ngx_mpegts_frame_t *prepare_mpegts_frame[NGX_RTMP_MAX_MERGE_FRAME]; + ngx_rtmp_frame_t *prepare_frame[NGX_RTMP_MAX_MERGE_FRAME]; + ngx_chain_t *merge[NGX_RTMP_MAX_MERGE_FRAME]; + ngx_uint_t nframe; + ngx_rtmp_prepared_pt prepare_handler; + + /* for trace and statistics */ + ngx_int_t notify_status; + ngx_uint_t finalize_reason; + ngx_uint_t stage; + ngx_msec_t init_time; + ngx_msec_t handshake_done_time; + ngx_msec_t connect_time; + ngx_msec_t create_stream_time; + ngx_msec_t ptime; /* publish or play time */ + ngx_msec_t first_data; /* audio video or metadata*/ + ngx_msec_t first_metadata; + ngx_msec_t first_audio; + ngx_msec_t first_video; + ngx_msec_t close_stream_time; + + /* circular buffer of RTMP message pointers */ + ngx_msec_t timeout; + uint32_t out_bytes; + size_t out_pos, out_last; + ngx_chain_t *out_chain; + unsigned out_buffer:1; + size_t out_queue; + size_t out_cork; + ngx_mpegts_frame_t **mpegts_out; + ngx_rtmp_frame_t *out[0]; +}; + +/* live stream manage */ +#define NGX_LIVE_SERVERID_LEN 512 +#define NGX_LIVE_STREAM_LEN 512 + +typedef struct ngx_rtmp_core_ctx_s ngx_rtmp_core_ctx_t; +typedef struct ngx_rtmp_live_ctx_s ngx_rtmp_live_ctx_t; +typedef struct ngx_mpegts_live_ctx_s ngx_mpegts_live_ctx_t; +typedef struct ngx_hls_live_ctx_s ngx_hls_live_ctx_t; + +struct ngx_rtmp_core_ctx_s { + ngx_rtmp_core_ctx_t *next; + ngx_rtmp_session_t *session; + + unsigned publishing:1; +}; + + +#define NGX_RTMP_MAX_OCLP 8 +#define NGX_RTMP_MAX_PUSH 8 + + +struct ngx_live_stream_s { + u_char name[NGX_LIVE_STREAM_LEN]; + + ngx_int_t pslot; + + ngx_rtmp_core_ctx_t *publish_ctx; + ngx_rtmp_core_ctx_t *play_ctx; + + /* notify */ + ngx_netcall_ctx_t *stream_nctx; + + ngx_live_stream_t *next; + + /* for live */ + ngx_map_t pubctx; + ngx_rtmp_live_ctx_t *ctx; + ngx_mpegts_live_ctx_t *mpegts_ctx; + ngx_hls_live_ctx_t *hls_ctx; + ngx_rtmp_bandwidth_t bw_in; + ngx_rtmp_bandwidth_t bw_in_audio; + ngx_rtmp_bandwidth_t bw_in_video; + ngx_rtmp_bandwidth_t bw_out; + ngx_msec_t epoch; + unsigned active:1; + unsigned publishing:1; + unsigned notify_meta:1; +}; + +struct ngx_live_server_s { + u_char serverid[NGX_LIVE_SERVERID_LEN]; + ngx_uint_t n_stream; + ngx_flag_t deleted; + + ngx_live_server_t *next; + + ngx_live_stream_t **streams; +}; + + +ngx_live_server_t *ngx_live_create_server(ngx_str_t *serverid); +ngx_live_server_t *ngx_live_fetch_server(ngx_str_t *serverid); +void ngx_live_delete_server(ngx_str_t *serverid); + +ngx_live_stream_t *ngx_live_create_stream(ngx_str_t *serverid, + ngx_str_t *stream); +ngx_live_stream_t *ngx_live_fetch_stream(ngx_str_t *serverid, + ngx_str_t *stream); +void ngx_live_delete_stream(ngx_str_t *serverid, ngx_str_t *stream); + +void ngx_live_create_ctx(ngx_rtmp_session_t *s, unsigned publishing); +void ngx_live_delete_ctx(ngx_rtmp_session_t *s); + +void ngx_live_print(); + +#if (NGX_WIN32) +#pragma warning(pop) +#endif + + +/* handler result code: + * NGX_ERROR - error + * NGX_OK - success, may continue + * NGX_DONE - success, input parsed, reply sent; need no + * more calls on this event */ +typedef ngx_int_t (*ngx_rtmp_handler_pt)(ngx_rtmp_session_t *s, + ngx_rtmp_header_t *h, ngx_chain_t *in); + + +typedef struct { + ngx_str_t name; + ngx_rtmp_handler_pt handler; +} ngx_rtmp_amf_handler_t; + + +typedef struct { + ngx_flag_t fast_reload; + + ngx_array_t servers; /* ngx_rtmp_core_srv_conf_t */ + + ngx_array_t events[NGX_RTMP_MAX_EVENT]; + + ngx_hash_t amf_hash; + ngx_array_t amf_arrays; + ngx_array_t amf; + + ngx_uint_t server_names_hash_max_size; + ngx_uint_t server_names_hash_bucket_size; + + ngx_hash_t variables_hash; + + ngx_array_t variables; /* ngx_http_variable_t */ + ngx_array_t prefix_variables; /* ngx_http_variable_t */ + ngx_uint_t ncaptures; + + ngx_uint_t variables_hash_max_size; + ngx_uint_t variables_hash_bucket_size; + + ngx_hash_keys_arrays_t *variables_keys; + + ngx_array_t *ports; /* ngx_rtmp_conf_port_t */ +} ngx_rtmp_core_main_conf_t; + + +/* global main conf for stats */ +extern ngx_rtmp_core_main_conf_t *ngx_rtmp_core_main_conf; + + +typedef struct { + ngx_array_t applications; /* ngx_rtmp_core_app_conf_t */ + ngx_str_t name; + ngx_uint_t merge_frame; + ngx_flag_t tcp_nodelay; + void **app_conf; + ngx_uint_t hevc_codec; +} ngx_rtmp_core_app_conf_t; + + +typedef struct ngx_rtmp_core_srv_conf_s { + ngx_array_t applications; /* ngx_rtmp_core_app_conf_t */ + + ngx_rtmp_core_app_conf_t *default_app; + + ngx_msec_t timeout; + ngx_msec_t ping; + ngx_msec_t ping_timeout; + ngx_flag_t so_keepalive; + ngx_int_t max_streams; + + ngx_uint_t ack_window; + + ngx_int_t chunk_size; + ngx_pool_t *pool; + ngx_chain_t *free; + ngx_chain_t *free_hs; + size_t max_message; + ngx_flag_t play_time_fix; + ngx_flag_t publish_time_fix; + ngx_flag_t busy; + size_t out_queue; + size_t out_cork; + ngx_msec_t buflen; + + ngx_rtmp_conf_ctx_t *ctx; + + unsigned listen:1; +#if (NGX_PCRE) + unsigned captures:1; +#endif + + ngx_str_t server_name; + ngx_str_t serverid; + + /* array of the ngx_rtmp_server_name_t, "server_name" directive */ + ngx_array_t server_names; +} ngx_rtmp_core_srv_conf_t; + + +typedef struct { +#if (NGX_PCRE) + ngx_rtmp_regex_t *regex; +#endif + ngx_rtmp_core_srv_conf_t *server; /* virtual name server conf */ + ngx_str_t name; +} ngx_rtmp_server_name_t; + + +typedef struct { + ngx_hash_combined_t names; + + ngx_uint_t nregex; + ngx_rtmp_server_name_t *regex; +} ngx_rtmp_virtual_names_t; + + +struct ngx_rtmp_addr_conf_s { + ngx_rtmp_core_srv_conf_t *default_server; + ngx_rtmp_virtual_names_t *virtual_names; + + ngx_str_t addr_text; + unsigned proxy_protocol:1; +}; + +typedef struct { + in_addr_t addr; + ngx_rtmp_addr_conf_t conf; +} ngx_rtmp_in_addr_t; + + +#if (NGX_HAVE_INET6) + +typedef struct { + struct in6_addr addr6; + ngx_rtmp_addr_conf_t conf; +} ngx_rtmp_in6_addr_t; + +#endif + + +typedef struct { + struct sockaddr *sockaddr; + socklen_t socklen; + + unsigned set:1; + unsigned default_server:1; + unsigned bind:1; + unsigned wildcard:1; +#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY) + unsigned ipv6only:2; +#endif + unsigned reuseport:1; + unsigned so_keepalive:2; + unsigned proxy_protocol:1; +#if (NGX_HAVE_KEEPALIVE_TUNABLE) + int tcp_keepidle; + int tcp_keepintvl; + int tcp_keepcnt; +#endif + + u_char addr[NGX_SOCKADDR_STRLEN + 1]; +} ngx_rtmp_listen_opt_t; + + +typedef struct { + ngx_rtmp_listen_opt_t opt; + + ngx_hash_t hash; + ngx_hash_wildcard_t *wc_head; + ngx_hash_wildcard_t *wc_tail; + +#if (NGX_PCRE) + ngx_uint_t nregex; + ngx_rtmp_server_name_t *regex; +#endif + + /* the default server configuration for this address:port */ + ngx_rtmp_core_srv_conf_t *default_server; + ngx_array_t servers; /* array of ngx_http_core_srv_conf_t */ +} ngx_rtmp_conf_addr_t; + + +/* nginx dynamic conf */ +typedef struct { + ngx_str_t serverid; +} ngx_rtmp_core_srv_dconf_t; + + +typedef struct { + ngx_int_t (*preconfiguration)(ngx_conf_t *cf); + ngx_int_t (*postconfiguration)(ngx_conf_t *cf); + + void *(*create_main_conf)(ngx_conf_t *cf); + char *(*init_main_conf)(ngx_conf_t *cf, void *conf); + + void *(*create_srv_conf)(ngx_conf_t *cf); + char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, + void *conf); + + void *(*create_app_conf)(ngx_conf_t *cf); + char *(*merge_app_conf)(ngx_conf_t *cf, void *prev, + void *conf); +} ngx_rtmp_module_t; + +#define NGX_RTMP_MODULE 0x504D5452 /* "RTMP" */ + +#define NGX_RTMP_MAIN_CONF 0x02000000 +#define NGX_RTMP_SRV_CONF 0x04000000 +#define NGX_RTMP_APP_CONF 0x08000000 +#define NGX_RTMP_REC_CONF 0x10000000 + + +#define NGX_RTMP_MAIN_CONF_OFFSET offsetof(ngx_rtmp_conf_ctx_t, main_conf) +#define NGX_RTMP_SRV_CONF_OFFSET offsetof(ngx_rtmp_conf_ctx_t, srv_conf) +#define NGX_RTMP_APP_CONF_OFFSET offsetof(ngx_rtmp_conf_ctx_t, app_conf) + + +#define ngx_rtmp_get_module_ctx(s, module) (s)->ctx[module.ctx_index] +#define ngx_rtmp_set_ctx(s, c, module) s->ctx[module.ctx_index] = c; +#define ngx_rtmp_delete_ctx(s, module) s->ctx[module.ctx_index] = NULL; + + +#define ngx_rtmp_get_module_main_conf(s, module) \ + (s)->main_conf[module.ctx_index] +#define ngx_rtmp_get_module_srv_conf(s, module) (s)->srv_conf[module.ctx_index] +#define ngx_rtmp_get_module_app_conf(s, module) ((s)->app_conf ? \ + (s)->app_conf[module.ctx_index] : NULL) + +#define ngx_rtmp_conf_get_module_main_conf(cf, module) \ + ((ngx_rtmp_conf_ctx_t *) cf->ctx)->main_conf[module.ctx_index] +#define ngx_rtmp_conf_get_module_srv_conf(cf, module) \ + ((ngx_rtmp_conf_ctx_t *) cf->ctx)->srv_conf[module.ctx_index] +#define ngx_rtmp_conf_get_module_app_conf(cf, module) \ + ((ngx_rtmp_conf_ctx_t *) cf->ctx)->app_conf[module.ctx_index] + +#define ngx_rtmp_cycle_get_module_main_conf(cycle, module) \ + (cycle->conf_ctx[ngx_rtmp_module.index] ? \ + ((ngx_rtmp_conf_ctx_t *) cycle->conf_ctx[ngx_rtmp_module.index]) \ + ->main_conf[module.ctx_index]: \ + NULL) + +/* for virtual server */ +#if (NGX_PCRE) +ngx_rtmp_regex_t *ngx_rtmp_regex_compile(ngx_conf_t *cf, + ngx_regex_compile_t *rc); +ngx_int_t ngx_rtmp_regex_exec(ngx_rtmp_session_t *s, ngx_rtmp_regex_t *re, + ngx_str_t *str); +#endif +ngx_int_t ngx_rtmp_add_listen(ngx_conf_t *cf, ngx_rtmp_core_srv_conf_t *cscf, + ngx_rtmp_listen_opt_t *lsopt); +ngx_int_t ngx_rtmp_set_virtual_server(ngx_rtmp_session_t *s, ngx_str_t *host); + + +#ifdef NGX_DEBUG +char* ngx_rtmp_message_type(uint8_t type); +char* ngx_rtmp_user_message_type(uint16_t evt); +#endif + + +typedef struct { + ngx_array_t urls; // ngx_live_relay_url_t + + ngx_str_t domain; + ngx_str_t app; + ngx_str_t name; + ngx_str_t pargs; + ngx_str_t referer; // rtmp page_url + ngx_str_t user_agent; // rtmp flashver + + ngx_str_t stream; + + void *tag; // module create relay +} ngx_live_relay_t; + + +void ngx_rtmp_init_connection(ngx_connection_t *c); +ngx_rtmp_session_t *ngx_rtmp_create_session(ngx_rtmp_addr_conf_t *addr_conf); +ngx_rtmp_session_t *ngx_rtmp_create_relay_session(ngx_rtmp_session_t *s, + void *tag); +ngx_rtmp_session_t *ngx_rtmp_create_static_session(ngx_live_relay_t *relay, + ngx_rtmp_addr_conf_t *addr_conf, void *tag); + +void ngx_rtmp_set_combined_log(ngx_rtmp_session_t *s, void *d, + ngx_log_handler_pt h); +void ngx_rtmp_init_session(ngx_rtmp_session_t *s, ngx_connection_t *c); +void ngx_rtmp_finalize_session(ngx_rtmp_session_t *s); +void ngx_rtmp_finalize_fake_session(ngx_rtmp_session_t *s); +void ngx_rtmp_handshake(ngx_rtmp_session_t *s); +void ngx_rtmp_client_handshake(ngx_rtmp_session_t *s, unsigned async); +void ngx_rtmp_free_handshake_buffers(ngx_rtmp_session_t *s); +void ngx_rtmp_cycle(ngx_rtmp_session_t *s); +void ngx_rtmp_reset_ping(ngx_rtmp_session_t *s); +ngx_int_t ngx_rtmp_fire_event(ngx_rtmp_session_t *s, ngx_uint_t evt, + ngx_rtmp_header_t *h, ngx_chain_t *in); + +ngx_int_t ngx_rtmp_set_chunk_size(ngx_rtmp_session_t *s, ngx_uint_t size); + + +/* Bit reverse: we need big-endians in many places */ +void * ngx_rtmp_rmemcpy(void *dst, const void* src, size_t n); + +#define ngx_rtmp_rcpymem(dst, src, n) \ + (((u_char*)ngx_rtmp_rmemcpy(dst, src, n)) + (n)) + + +static ngx_inline uint16_t +ngx_rtmp_r16(uint16_t n) +{ + return (n << 8) | (n >> 8); +} + + +static ngx_inline uint32_t +ngx_rtmp_r32(uint32_t n) +{ + return (n << 24) | ((n << 8) & 0xff0000) | ((n >> 8) & 0xff00) | (n >> 24); +} + + +static ngx_inline uint64_t +ngx_rtmp_r64(uint64_t n) +{ + return (uint64_t) ngx_rtmp_r32((uint32_t) n) << 32 | + ngx_rtmp_r32((uint32_t) (n >> 32)); +} + + +ngx_int_t +ngx_rtmp_find_virtual_server(ngx_rtmp_virtual_names_t *virtual_names, + ngx_str_t *host, ngx_rtmp_core_srv_conf_t **cscfp); + +/* Receiving messages */ +ngx_int_t ngx_rtmp_receive_message(ngx_rtmp_session_t *s, + ngx_rtmp_header_t *h, ngx_chain_t *in); +ngx_int_t ngx_rtmp_protocol_message_handler(ngx_rtmp_session_t *s, + ngx_rtmp_header_t *h, ngx_chain_t *in); +ngx_int_t ngx_rtmp_user_message_handler(ngx_rtmp_session_t *s, + ngx_rtmp_header_t *h, ngx_chain_t *in); +ngx_int_t ngx_rtmp_aggregate_message_handler(ngx_rtmp_session_t *s, + ngx_rtmp_header_t *h, ngx_chain_t *in); +ngx_int_t ngx_rtmp_amf_message_handler(ngx_rtmp_session_t *s, + ngx_rtmp_header_t *h, ngx_chain_t *in); +ngx_int_t ngx_rtmp_amf_shared_object_handler(ngx_rtmp_session_t *s, + ngx_rtmp_header_t *h, ngx_chain_t *in); + + +/* Shared output buffers */ + +ngx_int_t ngx_rtmp_prepare_merge_frame(ngx_rtmp_session_t *s); +void ngx_rtmp_free_merge_frame(ngx_rtmp_session_t *s); + +void ngx_rtmp_shared_append_chain(ngx_rtmp_frame_t *frame, size_t size, + ngx_chain_t *cl, ngx_flag_t mandatory); +ngx_rtmp_frame_t *ngx_rtmp_shared_alloc_frame(size_t size, ngx_chain_t *cl, + ngx_flag_t mandatory); +void ngx_rtmp_shared_free_frame(ngx_rtmp_frame_t *frame); + +#define ngx_rtmp_shared_acquire_frame(frame) ++frame->ref; + +ngx_chain_t *ngx_rtmp_shared_state(ngx_http_request_t *r); + +void ngx_mpegts_shared_append_chain(ngx_mpegts_frame_t *frame, ngx_chain_t *cl, + ngx_flag_t mandatory); +ngx_mpegts_frame_t *ngx_rtmp_shared_alloc_mpegts_frame(ngx_chain_t *cl, + ngx_flag_t mandatory); +void ngx_rtmp_shared_free_mpegts_frame(ngx_mpegts_frame_t *frame); + +#define ngx_rtmp_shared_acquire_mpegts_frame(frame) ++frame->ref; + +/* Sending messages */ +ngx_int_t ngx_rtmp_send_message(ngx_rtmp_session_t *s, ngx_rtmp_frame_t *out, + ngx_uint_t priority); + +/* GOP */ +ngx_int_t ngx_rtmp_gop_cache(ngx_rtmp_session_t *s, ngx_rtmp_frame_t *frame); +ngx_int_t ngx_rtmp_gop_send(ngx_rtmp_session_t *s, ngx_rtmp_session_t *ss); + +/* RTMP Relation server */ +ngx_rtmp_addr_conf_t *ngx_rtmp_find_related_addr_conf(ngx_cycle_t *cycle, + ngx_str_t *addr); + +/* core */ +ngx_int_t ngx_rtmp_arg(ngx_rtmp_session_t *s, u_char *name, size_t len, + ngx_str_t *value); + +/* Note on priorities: + * the bigger value the lower the priority. + * priority=0 is the highest */ + + +#define NGX_RTMP_LIMIT_SOFT 0 +#define NGX_RTMP_LIMIT_HARD 1 +#define NGX_RTMP_LIMIT_DYNAMIC 2 + +/* Protocol control messages */ +ngx_rtmp_frame_t *ngx_rtmp_create_chunk_size(ngx_rtmp_session_t *s, + uint32_t chunk_size); +ngx_rtmp_frame_t *ngx_rtmp_create_abort(ngx_rtmp_session_t *s, + uint32_t csid); +ngx_rtmp_frame_t *ngx_rtmp_create_ack(ngx_rtmp_session_t *s, + uint32_t seq); +ngx_rtmp_frame_t *ngx_rtmp_create_ack_size(ngx_rtmp_session_t *s, + uint32_t ack_size); +ngx_rtmp_frame_t *ngx_rtmp_create_bandwidth(ngx_rtmp_session_t *s, + uint32_t ack_size, uint8_t limit_type); + +ngx_int_t ngx_rtmp_send_chunk_size(ngx_rtmp_session_t *s, + uint32_t chunk_size); +ngx_int_t ngx_rtmp_send_abort(ngx_rtmp_session_t *s, + uint32_t csid); +ngx_int_t ngx_rtmp_send_ack(ngx_rtmp_session_t *s, + uint32_t seq); +ngx_int_t ngx_rtmp_send_ack_size(ngx_rtmp_session_t *s, + uint32_t ack_size); +ngx_int_t ngx_rtmp_send_bandwidth(ngx_rtmp_session_t *s, + uint32_t ack_size, uint8_t limit_type); + +/* User control messages */ +ngx_rtmp_frame_t *ngx_rtmp_create_stream_begin(ngx_rtmp_session_t *s, + uint32_t msid); +ngx_rtmp_frame_t *ngx_rtmp_create_stream_eof(ngx_rtmp_session_t *s, + uint32_t msid); +ngx_rtmp_frame_t *ngx_rtmp_create_stream_dry(ngx_rtmp_session_t *s, + uint32_t msid); +ngx_rtmp_frame_t *ngx_rtmp_create_set_buflen(ngx_rtmp_session_t *s, + uint32_t msid, uint32_t buflen_msec); +ngx_rtmp_frame_t *ngx_rtmp_create_recorded(ngx_rtmp_session_t *s, + uint32_t msid); +ngx_rtmp_frame_t *ngx_rtmp_create_ping_request(ngx_rtmp_session_t *s, + uint32_t timestamp); +ngx_rtmp_frame_t *ngx_rtmp_create_ping_response(ngx_rtmp_session_t *s, + uint32_t timestamp); + +ngx_int_t ngx_rtmp_send_stream_begin(ngx_rtmp_session_t *s, + uint32_t msid); +ngx_int_t ngx_rtmp_send_stream_eof(ngx_rtmp_session_t *s, + uint32_t msid); +ngx_int_t ngx_rtmp_send_stream_dry(ngx_rtmp_session_t *s, + uint32_t msid); +ngx_int_t ngx_rtmp_send_set_buflen(ngx_rtmp_session_t *s, + uint32_t msid, uint32_t buflen_msec); +ngx_int_t ngx_rtmp_send_recorded(ngx_rtmp_session_t *s, + uint32_t msid); +ngx_int_t ngx_rtmp_send_ping_request(ngx_rtmp_session_t *s, + uint32_t timestamp); +ngx_int_t ngx_rtmp_send_ping_response(ngx_rtmp_session_t *s, + uint32_t timestamp); + +/* AMF sender/receiver */ +ngx_int_t ngx_rtmp_append_amf(ngx_rtmp_session_t *s, + ngx_chain_t **first, ngx_chain_t **last, + ngx_rtmp_amf_elt_t *elts, size_t nelts); +ngx_int_t ngx_rtmp_receive_amf(ngx_rtmp_session_t *s, ngx_chain_t *in, + ngx_rtmp_amf_elt_t *elts, size_t nelts); + +ngx_rtmp_frame_t *ngx_rtmp_create_amf(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_rtmp_amf_elt_t *elts, size_t nelts); +ngx_int_t ngx_rtmp_send_amf(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_rtmp_amf_elt_t *elts, size_t nelts); + +/* AMF status sender */ +ngx_rtmp_frame_t *ngx_rtmp_create_error(ngx_rtmp_session_t *s, char *code, + char* level, char *desc); +ngx_rtmp_frame_t *ngx_rtmp_create_status(ngx_rtmp_session_t *s, char *code, + char* level, char *desc); +ngx_rtmp_frame_t *ngx_rtmp_create_play_status(ngx_rtmp_session_t *s, char *code, + char* level, ngx_uint_t duration, ngx_uint_t bytes); +ngx_rtmp_frame_t *ngx_rtmp_create_sample_access(ngx_rtmp_session_t *s); + +ngx_int_t ngx_rtmp_send_error(ngx_rtmp_session_t *s, char *code, + char* level, char *desc); +ngx_int_t ngx_rtmp_send_status(ngx_rtmp_session_t *s, char *code, + char* level, char *desc); +ngx_int_t ngx_rtmp_send_play_status(ngx_rtmp_session_t *s, char *code, + char* level, ngx_uint_t duration, ngx_uint_t bytes); +ngx_int_t ngx_rtmp_send_sample_access(ngx_rtmp_session_t *s); +void ngx_rtmp_mpegts_mux(ngx_rtmp_session_t *s); + + +/* Frame types */ +#define NGX_RTMP_VIDEO_KEY_FRAME 1 +#define NGX_RTMP_VIDEO_INTER_FRAME 2 +#define NGX_RTMP_VIDEO_DISPOSABLE_FRAME 3 + + +static ngx_inline ngx_int_t +ngx_rtmp_get_video_frame_type(ngx_chain_t *in) +{ + return (in->buf->pos[0] & 0xf0) >> 4; +} + + +static ngx_inline ngx_int_t +ngx_rtmp_is_codec_header(ngx_chain_t *in) +{ + return in->buf->pos + 1 < in->buf->last && in->buf->pos[1] == 0; +} + + +extern ngx_rtmp_bandwidth_t ngx_rtmp_bw_out; +extern ngx_rtmp_bandwidth_t ngx_rtmp_bw_in; + + +extern ngx_uint_t ngx_rtmp_naccepted; +#if (nginx_version >= 1007011) +extern ngx_queue_t ngx_rtmp_init_queue; +#elif (nginx_version >= 1007005) +extern ngx_thread_volatile ngx_queue_t ngx_rtmp_init_queue; +#else +extern ngx_thread_volatile ngx_event_t *ngx_rtmp_init_queue; +#endif + +extern ngx_uint_t ngx_rtmp_max_module; +extern ngx_module_t ngx_rtmp_module; +extern ngx_module_t ngx_rtmp_core_module; + +#endif /* _NGX_RTMP_H_INCLUDED_ */ diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_access_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_access_module.c new file mode 100644 index 0000000000..55b5d0350e --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_access_module.c @@ -0,0 +1,482 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp.h" +#include "ngx_rtmp_cmd_module.h" + + +static ngx_rtmp_publish_pt next_publish; +static ngx_rtmp_play_pt next_play; + + +#define NGX_RTMP_ACCESS_PUBLISH 0x01 +#define NGX_RTMP_ACCESS_PLAY 0x02 + + +static char * ngx_rtmp_access_rule(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static ngx_int_t ngx_rtmp_access_postconfiguration(ngx_conf_t *cf); +static void * ngx_rtmp_access_create_app_conf(ngx_conf_t *cf); +static char * ngx_rtmp_access_merge_app_conf(ngx_conf_t *cf, + void *parent, void *child); + + +typedef struct { + in_addr_t mask; + in_addr_t addr; + ngx_uint_t deny; + ngx_uint_t flags; +} ngx_rtmp_access_rule_t; + + +#if (NGX_HAVE_INET6) + +typedef struct { + struct in6_addr addr; + struct in6_addr mask; + ngx_uint_t deny; + ngx_uint_t flags; +} ngx_rtmp_access_rule6_t; + +#endif + + +typedef struct { + ngx_array_t rules; /* array of ngx_rtmp_access_rule_t */ +#if (NGX_HAVE_INET6) + ngx_array_t rules6; /* array of ngx_rtmp_access_rule6_t */ +#endif +} ngx_rtmp_access_app_conf_t; + + +static ngx_command_t ngx_rtmp_access_commands[] = { + + { ngx_string("allow"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE12, + ngx_rtmp_access_rule, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("deny"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE12, + ngx_rtmp_access_rule, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_rtmp_access_module_ctx = { + NULL, /* preconfiguration */ + ngx_rtmp_access_postconfiguration, /* postconfiguration */ + NULL, /* create main configuration */ + NULL, /* init main configuration */ + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + ngx_rtmp_access_create_app_conf, /* create app configuration */ + ngx_rtmp_access_merge_app_conf, /* merge app configuration */ +}; + + +ngx_module_t ngx_rtmp_access_module = { + NGX_MODULE_V1, + &ngx_rtmp_access_module_ctx, /* module context */ + ngx_rtmp_access_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void * +ngx_rtmp_access_create_app_conf(ngx_conf_t *cf) +{ + ngx_rtmp_access_app_conf_t *aacf; + + aacf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_access_app_conf_t)); + if (aacf == NULL) { + return NULL; + } + + if (ngx_array_init(&aacf->rules, cf->pool, 1, + sizeof(ngx_rtmp_access_rule_t)) + != NGX_OK) + { + return NULL; + } + +#if (NGX_HAVE_INET6) + if (ngx_array_init(&aacf->rules6, cf->pool, 1, + sizeof(ngx_rtmp_access_rule6_t)) + != NGX_OK) + { + return NULL; + } +#endif + + return aacf; +} + + +static ngx_int_t +ngx_rtmp_access_merge_rules(ngx_array_t *prev, ngx_array_t *rules) +{ + void *p; + + if (prev->nelts == 0) { + return NGX_OK; + } + + if (rules->nelts == 0) { + *rules = *prev; + return NGX_OK; + } + + p = ngx_array_push_n(rules, prev->nelts); + if (p == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(p, prev->elts, prev->size * prev->nelts); + + return NGX_OK; +} + + +static char * +ngx_rtmp_access_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_rtmp_access_app_conf_t *prev = parent; + ngx_rtmp_access_app_conf_t *conf = child; + + if (ngx_rtmp_access_merge_rules(&prev->rules, &conf->rules) != NGX_OK) { + return NGX_CONF_ERROR; + } + +#if (NGX_HAVE_INET6) + if (ngx_rtmp_access_merge_rules(&prev->rules6, &conf->rules6) != NGX_OK) { + return NGX_CONF_ERROR; + } +#endif + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_rtmp_access_found(ngx_rtmp_session_t *s, ngx_uint_t deny) +{ + if (deny) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "access forbidden by rule"); + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_access_inet(ngx_rtmp_session_t *s, in_addr_t addr, ngx_uint_t flag) +{ + ngx_uint_t i; + ngx_rtmp_access_rule_t *rule; + ngx_rtmp_access_app_conf_t *ascf; + + ascf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_access_module); + + rule = ascf->rules.elts; + for (i = 0; i < ascf->rules.nelts; i++) { + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, s->log, 0, + "access: %08XD %08XD %08XD", + addr, rule[i].mask, rule[i].addr); + + if ((addr & rule[i].mask) == rule[i].addr && (flag & rule[i].flags)) { + return ngx_rtmp_access_found(s, rule[i].deny); + } + } + + return NGX_OK; +} + + +#if (NGX_HAVE_INET6) + +static ngx_int_t +ngx_rtmp_access_inet6(ngx_rtmp_session_t *s, u_char *p, ngx_uint_t flag) +{ + ngx_uint_t n; + ngx_uint_t i; + ngx_rtmp_access_rule6_t *rule6; + ngx_rtmp_access_app_conf_t *ascf; + + ascf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_access_module); + + rule6 = ascf->rules6.elts; + for (i = 0; i < ascf->rules6.nelts; i++) { + +#if (NGX_DEBUG) + { + size_t cl, ml, al; + u_char ct[NGX_INET6_ADDRSTRLEN]; + u_char mt[NGX_INET6_ADDRSTRLEN]; + u_char at[NGX_INET6_ADDRSTRLEN]; + + cl = ngx_inet6_ntop(p, ct, NGX_INET6_ADDRSTRLEN); + ml = ngx_inet6_ntop(rule6[i].mask.s6_addr, mt, NGX_INET6_ADDRSTRLEN); + al = ngx_inet6_ntop(rule6[i].addr.s6_addr, at, NGX_INET6_ADDRSTRLEN); + + ngx_log_debug6(NGX_LOG_DEBUG_HTTP, s->log, 0, + "access: %*s %*s %*s", cl, ct, ml, mt, al, at); + } +#endif + + for (n = 0; n < 16; n++) { + if ((p[n] & rule6[i].mask.s6_addr[n]) != rule6[i].addr.s6_addr[n]) { + goto next; + } + } + + if (flag & rule6[i].flags) { + return ngx_rtmp_access_found(s, rule6[i].deny); + } + + next: + continue; + } + + return NGX_OK; +} + +#endif + + +static ngx_int_t +ngx_rtmp_access(ngx_rtmp_session_t *s, ngx_uint_t flag) +{ + struct sockaddr_in *sin; + ngx_rtmp_access_app_conf_t *ascf; +#if (NGX_HAVE_INET6) + u_char *p; + in_addr_t addr; + struct sockaddr_in6 *sin6; +#endif + + ascf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_access_module); + if (ascf == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, s->log, 0, + "access: NULL app conf"); + return NGX_ERROR; + } + + /* relay etc */ + if (s->sockaddr == NULL) { + return NGX_OK; + } + + switch (s->sockaddr->sa_family) { + + case AF_INET: + sin = (struct sockaddr_in *) s->sockaddr; + return ngx_rtmp_access_inet(s, sin->sin_addr.s_addr, flag); + +#if (NGX_HAVE_INET6) + + case AF_INET6: + sin6 = (struct sockaddr_in6 *) s->sockaddr; + p = sin6->sin6_addr.s6_addr; + + if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { + addr = p[12] << 24; + addr += p[13] << 16; + addr += p[14] << 8; + addr += p[15]; + return ngx_rtmp_access_inet(s, htonl(addr), flag); + } + + return ngx_rtmp_access_inet6(s, p, flag); + +#endif + } + + return NGX_OK; +} + + +static char * +ngx_rtmp_access_rule(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_rtmp_access_app_conf_t *ascf = conf; + + ngx_int_t rc; + ngx_uint_t all; + ngx_str_t *value; + ngx_cidr_t cidr; + ngx_rtmp_access_rule_t *rule; +#if (NGX_HAVE_INET6) + ngx_rtmp_access_rule6_t *rule6; +#endif + size_t n; + ngx_uint_t flags; + + ngx_memzero(&cidr, sizeof(ngx_cidr_t)); + + value = cf->args->elts; + + n = 1; + flags = 0; + + if (cf->args->nelts == 2) { + + flags = NGX_RTMP_ACCESS_PUBLISH | NGX_RTMP_ACCESS_PLAY; + + } else { + + for(; n < cf->args->nelts - 1; ++n) { + + if (value[n].len == sizeof("publish") - 1 && + ngx_strcmp(value[1].data, "publish") == 0) + { + flags |= NGX_RTMP_ACCESS_PUBLISH; + continue; + + } + + if (value[n].len == sizeof("play") - 1 && + ngx_strcmp(value[1].data, "play") == 0) + { + flags |= NGX_RTMP_ACCESS_PLAY; + continue; + + } + + ngx_log_error(NGX_LOG_ERR, cf->log, 0, + "unexpected access specified: '%V'", &value[n]); + return NGX_CONF_ERROR; + } + } + + all = (value[n].len == 3 && ngx_strcmp(value[n].data, "all") == 0); + + if (!all) { + + rc = ngx_ptocidr(&value[n], &cidr); + + if (rc == NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\"", &value[1]); + return NGX_CONF_ERROR; + } + + if (rc == NGX_DONE) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "low address bits of %V are meaningless", + &value[1]); + } + } + + switch (cidr.family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + case 0: /* all */ + + rule6 = ngx_array_push(&ascf->rules6); + if (rule6 == NULL) { + return NGX_CONF_ERROR; + } + + rule6->mask = cidr.u.in6.mask; + rule6->addr = cidr.u.in6.addr; + rule6->deny = (value[0].data[0] == 'd') ? 1 : 0; + rule6->flags = flags; + + if (!all) { + break; + } + + rule = ngx_array_push(&ascf->rules); + if (rule == NULL) { + return NGX_CONF_ERROR; + } + + rule->mask = cidr.u.in.mask; + rule->addr = cidr.u.in.addr; + rule->deny = (value[0].data[0] == 'd') ? 1 : 0; + rule->flags = flags; + + break; + /* "all" passes through */ +#endif + + default: /* AF_INET */ + + rule = ngx_array_push(&ascf->rules); + if (rule == NULL) { + return NGX_CONF_ERROR; + } + + rule->mask = cidr.u.in.mask; + rule->addr = cidr.u.in.addr; + rule->deny = (value[0].data[0] == 'd') ? 1 : 0; + rule->flags = flags; + } + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_rtmp_access_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) +{ + if (s->interprocess) { + goto next; + } + + if (ngx_rtmp_access(s, NGX_RTMP_ACCESS_PUBLISH) != NGX_OK) { + return NGX_ERROR; + } + +next: + return next_publish(s, v); +} + + +static ngx_int_t +ngx_rtmp_access_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) +{ + if (ngx_rtmp_access(s, NGX_RTMP_ACCESS_PLAY) != NGX_OK) { + return NGX_ERROR; + } + + return next_play(s, v); +} + + +static ngx_int_t +ngx_rtmp_access_postconfiguration(ngx_conf_t *cf) +{ + /* chain handlers */ + next_publish = ngx_rtmp_publish; + ngx_rtmp_publish = ngx_rtmp_access_publish; + + next_play = ngx_rtmp_play; + ngx_rtmp_play = ngx_rtmp_access_play; + + return NGX_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_amf.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_amf.c new file mode 100644 index 0000000000..77521ecea9 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_amf.c @@ -0,0 +1,664 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp_amf.h" +#include "ngx_rtmp.h" +#include + + +static ngx_inline void* +ngx_rtmp_amf_reverse_copy(void *dst, void* src, size_t len) +{ + size_t k; + + if (dst == NULL || src == NULL) { + return NULL; + } + + for(k = 0; k < len; ++k) { + ((u_char*)dst)[k] = ((u_char*)src)[len - 1 - k]; + } + + return dst; +} + +#define NGX_RTMP_AMF_DEBUG_SIZE 16 + +#ifdef NGX_DEBUG +static void +ngx_rtmp_amf_debug(const char* op, ngx_log_t *log, u_char *p, size_t n) +{ + u_char hstr[3 * NGX_RTMP_AMF_DEBUG_SIZE + 1]; + u_char str[NGX_RTMP_AMF_DEBUG_SIZE + 1]; + u_char *hp, *sp; + static u_char hex[] = "0123456789ABCDEF"; + size_t i; + + hp = hstr; + sp = str; + + for(i = 0; i < n && i < NGX_RTMP_AMF_DEBUG_SIZE; ++i) { + *hp++ = ' '; + if (p) { + *hp++ = hex[(*p & 0xf0) >> 4]; + *hp++ = hex[*p & 0x0f]; + *sp++ = (*p >= 0x20 && *p <= 0x7e) ? + *p : (u_char)'?'; + ++p; + } else { + *hp++ = 'X'; + *hp++ = 'X'; + *sp++ = '?'; + } + } + *hp = *sp = '\0'; + + ngx_log_debug4(NGX_LOG_DEBUG_RTMP, log, 0, + "AMF %s (%d)%s '%s'", op, n, hstr, str); +} +#endif + +static ngx_int_t +ngx_rtmp_amf_get(ngx_rtmp_amf_ctx_t *ctx, void *p, size_t n) +{ + size_t size; + ngx_chain_t *l; + size_t offset; + u_char *pos, *last; +#ifdef NGX_DEBUG + void *op = p; + size_t on = n; +#endif + + if (!n) + return NGX_OK; + + for(l = ctx->link, offset = ctx->offset; l; l = l->next, offset = 0) { + + pos = l->buf->pos + offset; + last = l->buf->last; + + if (last >= pos + n) { + if (p) { + p = ngx_cpymem(p, pos, n); + } + ctx->offset = offset + n; + ctx->link = l; + +#ifdef NGX_DEBUG + ngx_rtmp_amf_debug("read", ctx->log, (u_char*)op, on); +#endif + + return NGX_OK; + } + + size = last - pos; + + if (p) { + p = ngx_cpymem(p, pos, size); + } + + n -= size; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ctx->log, 0, + "AMF read eof (%d)", n); + + return NGX_DONE; +} + + +static ngx_int_t +ngx_rtmp_amf_put(ngx_rtmp_amf_ctx_t *ctx, void *p, size_t n) +{ + ngx_buf_t *b; + size_t size; + ngx_chain_t *l, *ln; + +#ifdef NGX_DEBUG + ngx_rtmp_amf_debug("write", ctx->log, (u_char*)p, n); +#endif + + l = ctx->link; + + if (ctx->link && ctx->first == NULL) { + ctx->first = ctx->link; + } + + while(n) { + b = l ? l->buf : NULL; + + if (b == NULL || b->last == b->end) { + + ln = ctx->alloc(ctx->arg); + if (ln == NULL) { + return NGX_ERROR; + } + + if (ctx->first == NULL) { + ctx->first = ln; + } + + if (l) { + l->next = ln; + } + + l = ln; + ctx->link = l; + b = l->buf; + } + + size = b->end - b->last; + + if (size >= n) { + b->last = ngx_cpymem(b->last, p, n); + return NGX_OK; + } + + b->last = ngx_cpymem(b->last, p, size); + p = (u_char*)p + size; + n -= size; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_amf_read_object(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_amf_elt_t *elts, + size_t nelts) +{ + uint8_t type; + uint16_t len; + size_t n, namelen, maxlen; + ngx_int_t rc; + u_char buf[2]; + + maxlen = 0; + for(n = 0; n < nelts; ++n) { + namelen = elts[n].name.len; + if (namelen > maxlen) + maxlen = namelen; + } + + for( ;; ) { + +#if !(NGX_WIN32) + char name[maxlen]; +#else + char name[1024]; + if (maxlen > sizeof(name)) { + return NGX_ERROR; + } +#endif + /* read key */ + switch (ngx_rtmp_amf_get(ctx, buf, 2)) { + case NGX_DONE: + /* Envivio sends unfinalized arrays */ + return NGX_OK; + case NGX_OK: + break; + default: + return NGX_ERROR; + } + + ngx_rtmp_amf_reverse_copy(&len, buf, 2); + + if (!len) + break; + + if (len <= maxlen) { + rc = ngx_rtmp_amf_get(ctx, name, len); + + } else { + rc = ngx_rtmp_amf_get(ctx, name, maxlen); + if (rc != NGX_OK) + return NGX_ERROR; + rc = ngx_rtmp_amf_get(ctx, 0, len - maxlen); + } + + if (rc != NGX_OK) + return NGX_ERROR; + + /* TODO: if we require array to be sorted on name + * then we could be able to use binary search */ + for(n = 0; n < nelts + && (len != elts[n].name.len + || ngx_strncmp(name, elts[n].name.data, len)); + ++n); + + if (ngx_rtmp_amf_read(ctx, n < nelts ? &elts[n] : NULL, 1) != NGX_OK) + return NGX_ERROR; + } + + if (ngx_rtmp_amf_get(ctx, &type, 1) != NGX_OK + || type != NGX_RTMP_AMF_END) + { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_amf_read_array(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_amf_elt_t *elts, + size_t nelts) +{ + uint32_t len; + size_t n; + u_char buf[4]; + + /* read length */ + if (ngx_rtmp_amf_get(ctx, buf, 4) != NGX_OK) + return NGX_ERROR; + + ngx_rtmp_amf_reverse_copy(&len, buf, 4); + + for (n = 0; n < len; ++n) { + if (ngx_rtmp_amf_read(ctx, n < nelts ? &elts[n] : NULL, 1) != NGX_OK) + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_amf_read_variant(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_amf_elt_t *elts, + size_t nelts) +{ + uint8_t type; + ngx_int_t rc; + size_t n; + ngx_rtmp_amf_elt_t elt; + + rc = ngx_rtmp_amf_get(ctx, &type, 1); + if (rc != NGX_OK) { + return rc; + } + + ngx_memzero(&elt, sizeof(elt)); + for (n = 0; n < nelts; ++n, ++elts) { + if (type == elts->type) { + elt.data = elts->data; + elt.len = elts->len; + } + } + + elt.type = type | NGX_RTMP_AMF_TYPELESS; + + return ngx_rtmp_amf_read(ctx, &elt, 1); +} + + +static ngx_int_t +ngx_rtmp_amf_is_compatible_type(uint8_t t1, uint8_t t2) +{ + return t1 == t2 + || (t1 == NGX_RTMP_AMF_OBJECT && t2 == NGX_RTMP_AMF_MIXED_ARRAY) + || (t2 == NGX_RTMP_AMF_OBJECT && t1 == NGX_RTMP_AMF_MIXED_ARRAY); +} + + +ngx_int_t +ngx_rtmp_amf_read(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_amf_elt_t *elts, + size_t nelts) +{ + void *data; + ngx_int_t type; + uint8_t type8; + size_t n; + uint16_t len; + ngx_int_t rc; + u_char buf[8]; + uint32_t max_index; + + for(n = 0; n < nelts; ++n) { + + if (elts && elts->type & NGX_RTMP_AMF_TYPELESS) { + type = elts->type & ~NGX_RTMP_AMF_TYPELESS; + data = elts->data; + + } else { + switch (ngx_rtmp_amf_get(ctx, &type8, 1)) { + case NGX_DONE: + if (elts->type & NGX_RTMP_AMF_OPTIONAL) { + return NGX_OK; + } else { + return NGX_ERROR; + } + break; + case NGX_ERROR: + return NGX_ERROR; + } + type = type8; + data = (elts && + ngx_rtmp_amf_is_compatible_type( + (uint8_t) (elts->type & 0xff), (uint8_t) type)) + ? elts->data + : NULL; + + if (elts && (elts->type & NGX_RTMP_AMF_CONTEXT)) { + if (data) { + *(ngx_rtmp_amf_ctx_t *) data = *ctx; + } + data = NULL; + } + } + + switch (type) { + case NGX_RTMP_AMF_NUMBER: + if (ngx_rtmp_amf_get(ctx, buf, 8) != NGX_OK) { + return NGX_ERROR; + } + ngx_rtmp_amf_reverse_copy(data, buf, 8); + break; + + case NGX_RTMP_AMF_BOOLEAN: + if (ngx_rtmp_amf_get(ctx, data, 1) != NGX_OK) { + return NGX_ERROR; + } + break; + + case NGX_RTMP_AMF_STRING: + if (ngx_rtmp_amf_get(ctx, buf, 2) != NGX_OK) { + return NGX_ERROR; + } + ngx_rtmp_amf_reverse_copy(&len, buf, 2); + + if (data == NULL) { + rc = ngx_rtmp_amf_get(ctx, data, len); + + } else if (elts->len <= len) { + rc = ngx_rtmp_amf_get(ctx, data, elts->len - 1); + if (rc != NGX_OK) + return NGX_ERROR; + ((char*)data)[elts->len - 1] = 0; + rc = ngx_rtmp_amf_get(ctx, NULL, len - elts->len + 1); + + } else { + rc = ngx_rtmp_amf_get(ctx, data, len); + ((char*)data)[len] = 0; + } + + if (rc != NGX_OK) { + return NGX_ERROR; + } + + break; + + case NGX_RTMP_AMF_NULL: + case NGX_RTMP_AMF_ARRAY_NULL: + break; + + case NGX_RTMP_AMF_MIXED_ARRAY: + if (ngx_rtmp_amf_get(ctx, &max_index, 4) != NGX_OK) { + return NGX_ERROR; + } + if (ngx_rtmp_amf_read_object(ctx, data, + data && elts ? elts->len / sizeof(ngx_rtmp_amf_elt_t) : 0 + ) != NGX_OK) + { + return NGX_ERROR; + } + break; + + case NGX_RTMP_AMF_OBJECT: + if (ngx_rtmp_amf_read_object(ctx, data, + data && elts ? elts->len / sizeof(ngx_rtmp_amf_elt_t) : 0 + ) != NGX_OK) + { + return NGX_ERROR; + } + break; + + case NGX_RTMP_AMF_ARRAY: + if (ngx_rtmp_amf_read_array(ctx, data, + data && elts ? elts->len / sizeof(ngx_rtmp_amf_elt_t) : 0 + ) != NGX_OK) + { + return NGX_ERROR; + } + break; + + case NGX_RTMP_AMF_VARIANT_: + if (ngx_rtmp_amf_read_variant(ctx, data, + data && elts ? elts->len / sizeof(ngx_rtmp_amf_elt_t) : 0 + ) != NGX_OK) + { + return NGX_ERROR; + } + break; + + case NGX_RTMP_AMF_INT8: + if (ngx_rtmp_amf_get(ctx, data, 1) != NGX_OK) { + return NGX_ERROR; + } + break; + + case NGX_RTMP_AMF_INT16: + if (ngx_rtmp_amf_get(ctx, buf, 2) != NGX_OK) { + return NGX_ERROR; + } + ngx_rtmp_amf_reverse_copy(data, buf, 2); + break; + + case NGX_RTMP_AMF_INT32: + if (ngx_rtmp_amf_get(ctx, buf, 4) != NGX_OK) { + return NGX_ERROR; + } + ngx_rtmp_amf_reverse_copy(data, buf, 4); + break; + + case NGX_RTMP_AMF_END: + return NGX_OK; + + default: + return NGX_ERROR; + } + + if (elts) { + ++elts; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_amf_write_object(ngx_rtmp_amf_ctx_t *ctx, + ngx_rtmp_amf_elt_t *elts, size_t nelts) +{ + uint16_t len; + size_t n; + u_char buf[2]; + + for(n = 0; n < nelts; ++n) { + + len = (uint16_t) elts[n].name.len; + + if (ngx_rtmp_amf_put(ctx, + ngx_rtmp_amf_reverse_copy(buf, + &len, 2), 2) != NGX_OK) + { + return NGX_ERROR; + } + + if (ngx_rtmp_amf_put(ctx, elts[n].name.data, len) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_rtmp_amf_write(ctx, &elts[n], 1) != NGX_OK) { + return NGX_ERROR; + } + } + + if (ngx_rtmp_amf_put(ctx, "\0\0", 2) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_amf_write_array(ngx_rtmp_amf_ctx_t *ctx, + ngx_rtmp_amf_elt_t *elts, size_t nelts) +{ + uint32_t len; + size_t n; + u_char buf[4]; + + len = nelts; + if (ngx_rtmp_amf_put(ctx, + ngx_rtmp_amf_reverse_copy(buf, + &len, 4), 4) != NGX_OK) + { + return NGX_ERROR; + } + + for(n = 0; n < nelts; ++n) { + if (ngx_rtmp_amf_write(ctx, &elts[n], 1) != NGX_OK) { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_amf_write(ngx_rtmp_amf_ctx_t *ctx, + ngx_rtmp_amf_elt_t *elts, size_t nelts) +{ + size_t n; + ngx_int_t type; + uint8_t type8; + void *data; + uint16_t len; + uint32_t max_index; + u_char buf[8]; + + for(n = 0; n < nelts; ++n) { + + type = elts[n].type; + data = elts[n].data; + len = (uint16_t) elts[n].len; + + if (type & NGX_RTMP_AMF_TYPELESS) { + type &= ~NGX_RTMP_AMF_TYPELESS; + } else { + type8 = (uint8_t)type; + if (ngx_rtmp_amf_put(ctx, &type8, 1) != NGX_OK) + return NGX_ERROR; + } + + switch(type) { + case NGX_RTMP_AMF_NUMBER: + if (ngx_rtmp_amf_put(ctx, + ngx_rtmp_amf_reverse_copy(buf, + data, 8), 8) != NGX_OK) + { + return NGX_ERROR; + } + break; + + case NGX_RTMP_AMF_BOOLEAN: + if (ngx_rtmp_amf_put(ctx, data, 1) != NGX_OK) { + return NGX_ERROR; + } + break; + + case NGX_RTMP_AMF_STRING: + if (len == 0 && data) { + len = (uint16_t) ngx_strlen((u_char*) data); + } + + if (ngx_rtmp_amf_put(ctx, + ngx_rtmp_amf_reverse_copy(buf, + &len, 2), 2) != NGX_OK) + { + return NGX_ERROR; + } + + if (ngx_rtmp_amf_put(ctx, data, len) != NGX_OK) { + return NGX_ERROR; + } + break; + + case NGX_RTMP_AMF_NULL: + case NGX_RTMP_AMF_ARRAY_NULL: + break; + + case NGX_RTMP_AMF_MIXED_ARRAY: + max_index = 0; + if (ngx_rtmp_amf_put(ctx, &max_index, 4) != NGX_OK) { + return NGX_ERROR; + } + + type8 = NGX_RTMP_AMF_END; + if (ngx_rtmp_amf_write_object(ctx, data, + elts[n].len / sizeof(ngx_rtmp_amf_elt_t)) != NGX_OK + || ngx_rtmp_amf_put(ctx, &type8, 1) != NGX_OK) + { + return NGX_ERROR; + } + break; + + case NGX_RTMP_AMF_OBJECT: + type8 = NGX_RTMP_AMF_END; + if (ngx_rtmp_amf_write_object(ctx, data, + elts[n].len / sizeof(ngx_rtmp_amf_elt_t)) != NGX_OK + || ngx_rtmp_amf_put(ctx, &type8, 1) != NGX_OK) + { + return NGX_ERROR; + } + break; + + case NGX_RTMP_AMF_ARRAY: + if (ngx_rtmp_amf_write_array(ctx, data, + elts[n].len / sizeof(ngx_rtmp_amf_elt_t)) != NGX_OK) + { + return NGX_ERROR; + } + break; + + case NGX_RTMP_AMF_INT8: + if (ngx_rtmp_amf_put(ctx, data, 1) != NGX_OK) { + return NGX_ERROR; + } + break; + + case NGX_RTMP_AMF_INT16: + if (ngx_rtmp_amf_put(ctx, + ngx_rtmp_amf_reverse_copy(buf, + data, 2), 2) != NGX_OK) + { + return NGX_ERROR; + } + break; + + case NGX_RTMP_AMF_INT32: + if (ngx_rtmp_amf_put(ctx, + ngx_rtmp_amf_reverse_copy(buf, + data, 4), 4) != NGX_OK) + { + return NGX_ERROR; + } + break; + + default: + return NGX_ERROR; + } + } + + return NGX_OK; +} + diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_amf.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_amf.h new file mode 100644 index 0000000000..8f70a129dc --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_amf.h @@ -0,0 +1,71 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#ifndef _NGX_RTMP_AMF_H_INCLUDED_ +#define _NGX_RTMP_AMF_H_INCLUDED_ + + +#include +#include + + +/* basic types */ +#define NGX_RTMP_AMF_NUMBER 0x00 +#define NGX_RTMP_AMF_BOOLEAN 0x01 +#define NGX_RTMP_AMF_STRING 0x02 +#define NGX_RTMP_AMF_OBJECT 0x03 +#define NGX_RTMP_AMF_NULL 0x05 +#define NGX_RTMP_AMF_ARRAY_NULL 0x06 +#define NGX_RTMP_AMF_MIXED_ARRAY 0x08 +#define NGX_RTMP_AMF_END 0x09 +#define NGX_RTMP_AMF_ARRAY 0x0a + +/* extended types */ +#define NGX_RTMP_AMF_INT8 0x0100 +#define NGX_RTMP_AMF_INT16 0x0101 +#define NGX_RTMP_AMF_INT32 0x0102 +#define NGX_RTMP_AMF_VARIANT_ 0x0103 + +/* r/w flags */ +#define NGX_RTMP_AMF_OPTIONAL 0x1000 +#define NGX_RTMP_AMF_TYPELESS 0x2000 +#define NGX_RTMP_AMF_CONTEXT 0x4000 + +#define NGX_RTMP_AMF_VARIANT (NGX_RTMP_AMF_VARIANT_\ + |NGX_RTMP_AMF_TYPELESS) + + +typedef struct { + ngx_int_t type; + ngx_str_t name; + void *data; + size_t len; +} ngx_rtmp_amf_elt_t; + + +typedef ngx_chain_t * (*ngx_rtmp_amf_alloc_pt)(void *arg); + + +typedef struct { + ngx_chain_t *link, *first; + size_t offset; + ngx_rtmp_amf_alloc_pt alloc; + void *arg; + ngx_log_t *log; +} ngx_rtmp_amf_ctx_t; + + +/* reading AMF */ +ngx_int_t ngx_rtmp_amf_read(ngx_rtmp_amf_ctx_t *ctx, + ngx_rtmp_amf_elt_t *elts, size_t nelts); + +/* writing AMF */ +ngx_int_t ngx_rtmp_amf_write(ngx_rtmp_amf_ctx_t *ctx, + ngx_rtmp_amf_elt_t *elts, size_t nelts); + + +#endif /* _NGX_RTMP_AMF_H_INCLUDED_ */ + diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_bandwidth.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_bandwidth.c new file mode 100644 index 0000000000..82f9f0d47c --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_bandwidth.c @@ -0,0 +1,26 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp_bandwidth.h" + + +void +ngx_rtmp_update_bandwidth(ngx_rtmp_bandwidth_t *bw, uint32_t bytes) +{ + if (ngx_cached_time->sec > bw->intl_end) { + bw->bandwidth = ngx_cached_time->sec > + bw->intl_end + NGX_RTMP_BANDWIDTH_INTERVAL + ? 0 + : bw->intl_bytes / NGX_RTMP_BANDWIDTH_INTERVAL; + bw->intl_bytes = 0; + bw->intl_end = ngx_cached_time->sec + NGX_RTMP_BANDWIDTH_INTERVAL; + } + + bw->bytes += bytes; + bw->intl_bytes += bytes; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_bandwidth.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_bandwidth.h new file mode 100644 index 0000000000..b4984825bf --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_bandwidth.h @@ -0,0 +1,31 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#ifndef _NGX_RTMP_BANDWIDTH_H_INCLUDED_ +#define _NGX_RTMP_BANDWIDTH_H_INCLUDED_ + + +#include +#include + + +/* Bandwidth update interval in seconds */ +#define NGX_RTMP_BANDWIDTH_INTERVAL 10 + + +typedef struct { + uint64_t bytes; + uint64_t bandwidth; /* bytes/sec */ + + time_t intl_end; + uint64_t intl_bytes; +} ngx_rtmp_bandwidth_t; + + +void ngx_rtmp_update_bandwidth(ngx_rtmp_bandwidth_t *bw, uint32_t bytes); + + +#endif /* _NGX_RTMP_BANDWIDTH_H_INCLUDED_ */ diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_bitop.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_bitop.c new file mode 100644 index 0000000000..855d425fd2 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_bitop.c @@ -0,0 +1,63 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp_bitop.h" + + +void +ngx_rtmp_bit_init_reader(ngx_rtmp_bit_reader_t *br, u_char *pos, u_char *last) +{ + ngx_memzero(br, sizeof(ngx_rtmp_bit_reader_t)); + + br->pos = pos; + br->last = last; +} + + +uint64_t +ngx_rtmp_bit_read(ngx_rtmp_bit_reader_t *br, ngx_uint_t n) +{ + uint64_t v; + ngx_uint_t d; + + v = 0; + + while (n) { + + if (br->pos >= br->last) { + br->err = 1; + return 0; + } + + d = (br->offs + n > 8 ? (ngx_uint_t) (8 - br->offs) : n); + + v <<= d; + v += (*br->pos >> (8 - br->offs - d)) & ((u_char) 0xff >> (8 - d)); + + br->offs += d; + n -= d; + + if (br->offs == 8) { + br->pos++; + br->offs = 0; + } + } + + return v; +} + + +uint64_t +ngx_rtmp_bit_read_golomb(ngx_rtmp_bit_reader_t *br) +{ + ngx_uint_t n; + + for (n = 0; ngx_rtmp_bit_read(br, 1) == 0 && !br->err; n++); + + return ((uint64_t) 1 << n) + ngx_rtmp_bit_read(br, n) - 1; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_bitop.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_bitop.h new file mode 100644 index 0000000000..c954a35f39 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_bitop.h @@ -0,0 +1,46 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#ifndef _NGX_RTMP_BITOP_H_INCLUDED_ +#define _NGX_RTMP_BITOP_H_INCLUDED_ + + +#include +#include + + +typedef struct { + u_char *pos; + u_char *last; + ngx_uint_t offs; + ngx_uint_t err; +} ngx_rtmp_bit_reader_t; + + +void ngx_rtmp_bit_init_reader(ngx_rtmp_bit_reader_t *br, u_char *pos, + u_char *last); +uint64_t ngx_rtmp_bit_read(ngx_rtmp_bit_reader_t *br, ngx_uint_t n); +uint64_t ngx_rtmp_bit_read_golomb(ngx_rtmp_bit_reader_t *br); + + +#define ngx_rtmp_bit_read_err(br) ((br)->err) + +#define ngx_rtmp_bit_read_eof(br) ((br)->pos == (br)->last) + +#define ngx_rtmp_bit_read_8(br) \ + ((uint8_t) ngx_rtmp_bit_read(br, 8)) + +#define ngx_rtmp_bit_read_16(br) \ + ((uint16_t) ngx_rtmp_bit_read(br, 16)) + +#define ngx_rtmp_bit_read_32(br) \ + ((uint32_t) ngx_rtmp_bit_read(br, 32)) + +#define ngx_rtmp_bit_read_64(br) \ + ((uint64_t) ngx_rtmp_read(br, 64)) + + +#endif /* _NGX_RTMP_BITOP_H_INCLUDED_ */ diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_cmd_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_cmd_module.c new file mode 100644 index 0000000000..b29d002c17 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_cmd_module.c @@ -0,0 +1,1096 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp_cmd_module.h" +#include "ngx_rtmp_notify_module.h" +#include "ngx_rtmp_streams.h" +#include "ngx_stream_zone_module.h" +#include "ngx_dynamic_conf.h" +#include "ngx_toolkit_misc.h" +#include "ngx_rtmp_dynamic.h" + + +#define NGX_RTMP_FMS_VERSION "FMS/3,0,1,123" +#define NGX_RTMP_CAPABILITIES 31 + + +static ngx_int_t ngx_rtmp_cmd_connect(ngx_rtmp_session_t *s, + ngx_rtmp_connect_t *v); +static ngx_int_t ngx_rtmp_cmd_disconnect(ngx_rtmp_session_t *s); +static ngx_int_t ngx_rtmp_cmd_create_stream(ngx_rtmp_session_t *s, + ngx_rtmp_create_stream_t *v); +static ngx_int_t ngx_rtmp_cmd_close_stream(ngx_rtmp_session_t *s, + ngx_rtmp_close_stream_t *v); +static ngx_int_t ngx_rtmp_cmd_delete_stream(ngx_rtmp_session_t *s, + ngx_rtmp_delete_stream_t *v); +static ngx_int_t ngx_rtmp_cmd_publish(ngx_rtmp_session_t *s, + ngx_rtmp_publish_t *v); +static ngx_int_t ngx_rtmp_cmd_play(ngx_rtmp_session_t *s, + ngx_rtmp_play_t *v); +static ngx_int_t ngx_rtmp_cmd_seek(ngx_rtmp_session_t *s, + ngx_rtmp_seek_t *v); +static ngx_int_t ngx_rtmp_cmd_pause(ngx_rtmp_session_t *s, + ngx_rtmp_pause_t *v); + + +static ngx_int_t ngx_rtmp_cmd_stream_begin(ngx_rtmp_session_t *s, + ngx_rtmp_stream_begin_t *v); +static ngx_int_t ngx_rtmp_cmd_stream_eof(ngx_rtmp_session_t *s, + ngx_rtmp_stream_eof_t *v); +static ngx_int_t ngx_rtmp_cmd_stream_dry(ngx_rtmp_session_t *s, + ngx_rtmp_stream_dry_t *v); +static ngx_int_t ngx_rtmp_cmd_recorded(ngx_rtmp_session_t *s, + ngx_rtmp_recorded_t *v); +static ngx_int_t ngx_rtmp_cmd_set_buflen(ngx_rtmp_session_t *s, + ngx_rtmp_set_buflen_t *v); + + +ngx_rtmp_connect_pt ngx_rtmp_connect; +ngx_rtmp_disconnect_pt ngx_rtmp_disconnect; +ngx_rtmp_create_stream_pt ngx_rtmp_create_stream; +ngx_rtmp_close_stream_pt ngx_rtmp_close_stream; +ngx_rtmp_delete_stream_pt ngx_rtmp_delete_stream; +ngx_rtmp_publish_pt ngx_rtmp_publish; +ngx_rtmp_play_pt ngx_rtmp_play; +ngx_rtmp_seek_pt ngx_rtmp_seek; +ngx_rtmp_pause_pt ngx_rtmp_pause; + + +ngx_rtmp_stream_begin_pt ngx_rtmp_stream_begin; +ngx_rtmp_stream_eof_pt ngx_rtmp_stream_eof; +ngx_rtmp_stream_dry_pt ngx_rtmp_stream_dry; +ngx_rtmp_recorded_pt ngx_rtmp_recorded; +ngx_rtmp_set_buflen_pt ngx_rtmp_set_buflen; + + +static ngx_int_t ngx_rtmp_cmd_postconfiguration(ngx_conf_t *cf); + + +static ngx_rtmp_module_t ngx_rtmp_cmd_module_ctx = { + NULL, /* preconfiguration */ + ngx_rtmp_cmd_postconfiguration, /* postconfiguration */ + NULL, /* create main configuration */ + NULL, /* init main configuration */ + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + NULL, /* create app configuration */ + NULL /* merge app configuration */ +}; + + +ngx_module_t ngx_rtmp_cmd_module = { + NGX_MODULE_V1, + &ngx_rtmp_cmd_module_ctx, /* module context */ + NULL, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +void +ngx_rtmp_cmd_fill_args(u_char name[NGX_RTMP_MAX_NAME], + u_char args[NGX_RTMP_MAX_ARGS]) +{ + u_char *p; + + p = (u_char *)ngx_strchr(name, '?'); + if (p == NULL) { + return; + } + + *p++ = 0; + ngx_cpystrn(args, p, NGX_RTMP_MAX_ARGS); +} + +void +ngx_rtmp_cmd_reset_vhost(ngx_rtmp_session_t *s, ngx_rtmp_connect_t *v) +{ + ngx_request_url_t ru; + ngx_str_t tcurl; + char *p; + + tcurl.data = v->tc_url; + tcurl.len = ngx_strlen(v->tc_url); + + if (ngx_parse_request_url(&ru, &tcurl) == NGX_ERROR) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "tcurl error, %s", v->tc_url); + return; + } + + if (ngx_inet_addr(ru.host.data, ru.host.len) == INADDR_NONE) { + /* domain is not ip */ + return; + } + + p = ngx_strstr(v->app, "/"); + if (p == NULL) { /* app only has one level */ + return; + } + + /* use first level of app as domain, the rest as app */ + *ngx_snprintf(v->tc_url, NGX_RTMP_MAX_URL, "rtmp://%s", v->app) = 0; + *ngx_snprintf(v->app, NGX_RTMP_MAX_NAME, "%s", p + 1) = 0; +} + +void +ngx_rtmp_cmd_middleware_init(ngx_rtmp_session_t *s) +{ + ngx_rtmp_core_srv_dconf_t *rcsdf; + ngx_rtmp_core_srv_conf_t *rcsf; + ngx_request_url_t rurl; + + if (ngx_parse_request_url(&rurl, &s->tc_url) == NGX_ERROR) { + return; + } + + s->scheme = rurl.scheme; + s->domain = rurl.host; + + rcsf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + rcsdf = ngx_rtmp_get_module_srv_dconf(s, &ngx_rtmp_core_module); + if (rcsdf && rcsdf->serverid.len) { + s->serverid.data = ngx_pcalloc(s->pool, + rcsdf->serverid.len); + if (s->serverid.data == NULL) { + return; + } + s->serverid.len = rcsdf->serverid.len; + ngx_memcpy(s->serverid.data, rcsdf->serverid.data, s->serverid.len); + } else if (rcsf && rcsf->serverid.len) { + s->serverid.data = ngx_pcalloc(s->pool, + rcsf->serverid.len); + if (s->serverid.data == NULL) { + return; + } + s->serverid.len = rcsf->serverid.len; + ngx_memcpy(s->serverid.data, rcsf->serverid.data, s->serverid.len); + }else { + s->serverid = s->domain; + } +} + +void +ngx_rtmp_cmd_stream_init(ngx_rtmp_session_t *s, u_char *name, u_char *args, + unsigned publishing) +{ + u_char *p; + + if (s->name.len == 0) { + s->name.len = ngx_strlen(name); + s->name.data = ngx_palloc(s->pool, s->name.len); + ngx_memcpy(s->name.data, name, s->name.len); + } + + if (s->pargs.len == 0) { + s->pargs.len = ngx_strlen(args); + s->pargs.data = ngx_palloc(s->pool, s->pargs.len); + ngx_memcpy(s->pargs.data, args, s->pargs.len); + } + + if (s->stream.len == 0) { + /* serverid/app/name */ + s->stream.len = s->serverid.len + 1 + s->app.len + 1 + s->name.len; + s->stream.data = ngx_palloc(s->pool, s->stream.len); + p = s->stream.data; + + p = ngx_copy(p, s->serverid.data, s->serverid.len); + *p++ = '/'; + p = ngx_copy(p, s->app.data, s->app.len); + *p++ = '/'; + p = ngx_copy(p, s->name.data, s->name.len); + } + + s->live_stream = ngx_live_create_stream(&s->serverid, &s->stream); + + ngx_live_create_ctx(s, publishing); + s->publishing = publishing; + + ngx_rtmp_notify_stream_start(s); +} + +ngx_int_t +ngx_rtmp_publish_filter(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) +{ + if (s->published) { + ngx_log_error(NGX_LOG_INFO, s->log, 0, "session has been published"); + return NGX_OK; + } + + ngx_log_error(NGX_LOG_INFO, s->log, 0, "rtmp publish filter"); + + s->published = 1; + + if (!s->relay) { /* relay pull */ + ngx_rtmp_cmd_stream_init(s, v->name, v->args, 1); + } + + return ngx_rtmp_publish(s, v); +} + +ngx_int_t +ngx_rtmp_play_filter(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) +{ + if (s->played) { + ngx_log_error(NGX_LOG_INFO, s->log, 0, "session has been played"); + return NGX_OK; + } + + ngx_log_error(NGX_LOG_INFO, s->log, 0, "rtmp play filter"); + + s->played = 1; + + if (!s->relay) { /* relay push */ + ngx_rtmp_cmd_stream_init(s, v->name, v->args, 0); + } + + return ngx_rtmp_play(s, v); +} + +ngx_int_t +ngx_rtmp_close_stream_filter(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v) +{ + if (s->closed) { + ngx_log_error(NGX_LOG_INFO, s->log, 0, "session has been closed"); + return NGX_OK; + } + + ngx_log_error(NGX_LOG_INFO, s->log, 0, "rtmp close stream filter"); + + s->closed = 1; + + ngx_live_delete_ctx(s); + if (s->live_stream && s->live_stream->play_ctx == NULL + && s->live_stream->publish_ctx == NULL) + { + if (s->live_stream->pslot == ngx_process_slot) { + ngx_stream_zone_delete_stream(&s->stream); + } + ngx_rtmp_notify_stream_done(s); + ngx_live_delete_stream(&s->serverid, &s->stream); + } + + return ngx_rtmp_close_stream(s, v); +} + +static ngx_int_t +ngx_rtmp_cmd_connect_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + size_t len; + ngx_rtmp_core_srv_conf_t *cscf; + ngx_rtmp_core_app_conf_t **cacfp; + ngx_uint_t n; + u_char *p; + + static ngx_rtmp_connect_t v; + + static ngx_rtmp_amf_elt_t in_cmd[] = { + + { NGX_RTMP_AMF_STRING, + ngx_string("app"), + v.app, sizeof(v.app) }, + + { NGX_RTMP_AMF_STRING, + ngx_string("flashVer"), + v.flashver, sizeof(v.flashver) }, + + { NGX_RTMP_AMF_STRING, + ngx_string("swfUrl"), + v.swf_url, sizeof(v.swf_url) }, + + { NGX_RTMP_AMF_STRING, + ngx_string("tcUrl"), + v.tc_url, sizeof(v.tc_url) }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("audioCodecs"), + &v.acodecs, sizeof(v.acodecs) }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("videoCodecs"), + &v.vcodecs, sizeof(v.vcodecs) }, + + { NGX_RTMP_AMF_STRING, + ngx_string("pageUrl"), + v.page_url, sizeof(v.page_url) }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("objectEncoding"), + &v.object_encoding, 0}, + }; + + static ngx_rtmp_amf_elt_t in_elts[] = { + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &v.trans, 0 }, + + { NGX_RTMP_AMF_OBJECT, + ngx_null_string, + in_cmd, sizeof(in_cmd) }, + }; + + ngx_memzero(&v, sizeof(v)); + if (ngx_rtmp_receive_amf(s, in, in_elts, + sizeof(in_elts) / sizeof(in_elts[0]))) + { + return NGX_ERROR; + } + + len = ngx_strlen(v.app); + if (len > 10 && !ngx_memcmp(v.app + len - 10, "/_definst_", 10)) { + v.app[len - 10] = 0; + } else if (len && v.app[len - 1] == '/') { + v.app[len - 1] = 0; + } + + ngx_rtmp_cmd_fill_args(v.app, v.args); + ngx_rtmp_cmd_reset_vhost(s, &v); + +#define NGX_RTMP_SET_STRPAR(name) \ + s->name.len = ngx_strlen(v.name); \ + s->name.data = ngx_palloc(s->pool, s->name.len); \ + ngx_memcpy(s->name.data, v.name, s->name.len) + + NGX_RTMP_SET_STRPAR(app); + NGX_RTMP_SET_STRPAR(args); + NGX_RTMP_SET_STRPAR(flashver); + NGX_RTMP_SET_STRPAR(swf_url); + NGX_RTMP_SET_STRPAR(tc_url); + NGX_RTMP_SET_STRPAR(page_url); + +#undef NGX_RTMP_SET_STRPAR + + p = ngx_strlchr(s->app.data, s->app.data + s->app.len, '?'); + if (p) { + s->app.len = (p - s->app.data); + } + + s->acodecs = (uint32_t) v.acodecs; + s->vcodecs = (uint32_t) v.vcodecs; + + ngx_rtmp_cmd_middleware_init(s); + + if (ngx_rtmp_set_virtual_server(s, &s->domain)) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "rtmp connect, set vhost for \"%V\" failed", &s->domain); + return NGX_ERROR; + } + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + /* find application & set app_conf */ + cacfp = cscf->applications.elts; + for(n = 0; n < cscf->applications.nelts; ++n, ++cacfp) { + if ((*cacfp)->name.len == s->app.len && + ngx_strncmp((*cacfp)->name.data, s->app.data, s->app.len) == 0) + { + /* found app! */ + s->app_conf = (*cacfp)->app_conf; + break; + } + } + + if (s->app_conf == NULL) { + + if (cscf->default_app == NULL || cscf->default_app->app_conf == NULL) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "connect: application not found: '%V'", &s->app); + return NGX_ERROR; + } + + s->app_conf = cscf->default_app->app_conf; + } + + s->live_server = ngx_live_create_server(&s->serverid); + + s->stage = NGX_LIVE_CONNECT; + s->connect_time = ngx_current_msec; + + return ngx_rtmp_connect(s, &v); +} + + +static ngx_int_t +ngx_rtmp_cmd_connect(ngx_rtmp_session_t *s, ngx_rtmp_connect_t *v) +{ + ngx_rtmp_core_srv_conf_t *cscf; + ngx_rtmp_core_app_conf_t *cacf; + ngx_rtmp_header_t h; + int tcp_nodelay; + + static double trans; + static double capabilities = NGX_RTMP_CAPABILITIES; + static double object_encoding = 0; + + static ngx_rtmp_amf_elt_t out_obj[] = { + + { NGX_RTMP_AMF_STRING, + ngx_string("fmsVer"), + NGX_RTMP_FMS_VERSION, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("capabilities"), + &capabilities, 0 }, + }; + + static ngx_rtmp_amf_elt_t out_inf[] = { + + { NGX_RTMP_AMF_STRING, + ngx_string("level"), + "status", 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_string("code"), + "NetConnection.Connect.Success", 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_string("description"), + "Connection succeeded.", 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("objectEncoding"), + &object_encoding, 0 } + }; + + static ngx_rtmp_amf_elt_t out_elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + "_result", 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &trans, 0 }, + + { NGX_RTMP_AMF_OBJECT, + ngx_null_string, + out_obj, sizeof(out_obj) }, + + { NGX_RTMP_AMF_OBJECT, + ngx_null_string, + out_inf, sizeof(out_inf) }, + }; + + if (s->connected) { + ngx_log_error(NGX_LOG_INFO, s->log, 0, + "connect: duplicate connection"); + return NGX_ERROR; + } + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + /* set tcp_nodelay */ + cacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_core_module); + if (cacf->tcp_nodelay && + s->connection->tcp_nodelay == NGX_TCP_NODELAY_UNSET) + { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0, + "tcp_nodelay"); + + tcp_nodelay = 1; + + if (setsockopt(s->connection->fd, IPPROTO_TCP, TCP_NODELAY, + (const void *) &tcp_nodelay, sizeof(int)) == -1) + { + ngx_connection_error(s->connection, ngx_socket_errno, + "setsockopt(TCP_NODELAY) failed"); + return NGX_ERROR; + } + + s->connection->tcp_nodelay = NGX_TCP_NODELAY_SET; + } + + trans = v->trans; + + /* fill session parameters */ + s->connected = 1; + + ngx_memzero(&h, sizeof(h)); + h.csid = NGX_RTMP_CSID_AMF_INI; + h.type = NGX_RTMP_MSG_AMF_CMD; + + object_encoding = v->object_encoding; + + return ngx_rtmp_send_ack_size(s, cscf->ack_window) != NGX_OK || + ngx_rtmp_send_bandwidth(s, cscf->ack_window, + NGX_RTMP_LIMIT_DYNAMIC) != NGX_OK || + ngx_rtmp_send_chunk_size(s, cscf->chunk_size) != NGX_OK || + ngx_rtmp_send_amf(s, &h, out_elts, + sizeof(out_elts) / sizeof(out_elts[0])) + != NGX_OK ? NGX_ERROR : NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_cmd_create_stream_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + static ngx_rtmp_create_stream_t v; + + static ngx_rtmp_amf_elt_t in_elts[] = { + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &v.trans, sizeof(v.trans) }, + }; + + if (ngx_rtmp_receive_amf(s, in, in_elts, + sizeof(in_elts) / sizeof(in_elts[0]))) + { + return NGX_ERROR; + } + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0, "createStream"); + + s->stage = NGX_LIVE_CREATE_STREAM; + s->create_stream_time = ngx_current_msec; + + return ngx_rtmp_create_stream(s, &v); +} + + +static ngx_int_t +ngx_rtmp_cmd_create_stream(ngx_rtmp_session_t *s, ngx_rtmp_create_stream_t *v) +{ + /* support one message stream per connection */ + static double stream; + static double trans; + ngx_rtmp_header_t h; + + static ngx_rtmp_amf_elt_t out_elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + "_result", 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &trans, 0 }, + + { NGX_RTMP_AMF_NULL, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &stream, sizeof(stream) }, + }; + + trans = v->trans; + stream = NGX_RTMP_MSID; + + ngx_memzero(&h, sizeof(h)); + + h.csid = NGX_RTMP_CSID_AMF_INI; + h.type = NGX_RTMP_MSG_AMF_CMD; + + return ngx_rtmp_send_amf(s, &h, out_elts, + sizeof(out_elts) / sizeof(out_elts[0])) == NGX_OK ? + NGX_DONE : NGX_ERROR; +} + + +static ngx_int_t +ngx_rtmp_cmd_close_stream_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + static ngx_rtmp_close_stream_t v; + + static ngx_rtmp_amf_elt_t in_elts[] = { + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &v.stream, 0 }, + }; + + if (ngx_rtmp_receive_amf(s, in, in_elts, + sizeof(in_elts) / sizeof(in_elts[0]))) + { + return NGX_ERROR; + } + + s->finalize_reason = NGX_LIVE_NORMAL_CLOSE; + + return ngx_rtmp_close_stream_filter(s, &v); +} + + +static ngx_int_t +ngx_rtmp_cmd_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v) +{ + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_cmd_delete_stream_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + static ngx_rtmp_delete_stream_t v; + + static ngx_rtmp_amf_elt_t in_elts[] = { + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_NULL, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &v.stream, 0 }, + }; + + if (ngx_rtmp_receive_amf(s, in, in_elts, + sizeof(in_elts) / sizeof(in_elts[0]))) + { + return NGX_ERROR; + } + + s->finalize_reason = NGX_LIVE_NORMAL_CLOSE; + + return ngx_rtmp_delete_stream(s, &v); +} + + +static ngx_int_t +ngx_rtmp_cmd_delete_stream(ngx_rtmp_session_t *s, ngx_rtmp_delete_stream_t *v) +{ + ngx_rtmp_close_stream_t cv; + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0, "deleteStream"); + + cv.stream = 0; + + return ngx_rtmp_close_stream_filter(s, &cv); +} + + +static ngx_int_t +ngx_rtmp_cmd_publish_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + static ngx_rtmp_publish_t v; + + static ngx_rtmp_amf_elt_t in_elts[] = { + + /* transaction is always 0 */ + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_NULL, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + &v.name, sizeof(v.name) }, + + { NGX_RTMP_AMF_OPTIONAL | NGX_RTMP_AMF_STRING, + ngx_null_string, + &v.type, sizeof(v.type) }, + }; + + ngx_memzero(&v, sizeof(v)); + + if (ngx_rtmp_receive_amf(s, in, in_elts, + sizeof(in_elts) / sizeof(in_elts[0]))) + { + return NGX_ERROR; + } + + ngx_rtmp_cmd_fill_args(v.name, v.args); + + ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->log, 0, + "publish: name='%s' args='%s' type=%s silent=%d", + v.name, v.args, v.type, v.silent); + + s->stage = NGX_LIVE_PUBLISH; + s->ptime = ngx_current_msec; + + return ngx_rtmp_publish_filter(s, &v); +} + + +static ngx_int_t +ngx_rtmp_cmd_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) +{ + ngx_log_error(NGX_LOG_INFO, s->log, 0, + "publish: name='%V' pargs='%V' app='%V' args='%V' flashver='%V' " + "swf_url='%V' tc_url='%V' page_url='%V' acodecs=%uD vcodecs=%uD " + "scheme='%V' domain='%V' serverid='%V' stream='%V' groupid='%V'", + &s->name, &s->pargs, &s->app, &s->args, &s->flashver, + &s->swf_url, &s->tc_url, &s->page_url, s->acodecs, s->vcodecs, + &s->scheme, &s->domain, &s->serverid, &s->stream, &s->groupid); + + return NGX_OK; +} + +static ngx_int_t +ngx_rtmp_cmd_play_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + static ngx_rtmp_play_t v; + + static ngx_rtmp_amf_elt_t in_elts[] = { + + /* transaction is always 0 */ + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_NULL, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + &v.name, sizeof(v.name) }, + + { NGX_RTMP_AMF_OPTIONAL | NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &v.start, 0 }, + + { NGX_RTMP_AMF_OPTIONAL | NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &v.duration, 0 }, + + { NGX_RTMP_AMF_OPTIONAL | NGX_RTMP_AMF_BOOLEAN, + ngx_null_string, + &v.reset, 0 } + }; + + ngx_memzero(&v, sizeof(v)); + + if (ngx_rtmp_receive_amf(s, in, in_elts, + sizeof(in_elts) / sizeof(in_elts[0]))) + { + return NGX_ERROR; + } + + ngx_rtmp_cmd_fill_args(v.name, v.args); + + ngx_log_debug6(NGX_LOG_DEBUG_RTMP, s->log, 0, + "play: name='%s' args='%s' start=%i duration=%i " + "reset=%i silent=%i", + v.name, v.args, (ngx_int_t) v.start, + (ngx_int_t) v.duration, (ngx_int_t) v.reset, + (ngx_int_t) v.silent); + + s->stage = NGX_LIVE_PLAY; + s->ptime = ngx_current_msec; + + return ngx_rtmp_play_filter(s, &v); +} + + +static ngx_int_t +ngx_rtmp_cmd_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) +{ + ngx_log_error(NGX_LOG_INFO, s->log, 0, + "play: name='%V' pargs='%V' app='%V' args='%V' flashver='%V' " + "swf_url='%V' tc_url='%V' page_url='%V' acodecs=%uD vcodecs=%uD " + "scheme='%V' domain='%V' serverid='%V' stream='%V' groupid='%V'", + &s->name, &s->pargs, &s->app, &s->args, &s->flashver, + &s->swf_url, &s->tc_url, &s->page_url, s->acodecs, s->vcodecs, + &s->scheme, &s->domain, &s->serverid, &s->stream, &s->groupid); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_cmd_play2_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + static ngx_rtmp_play_t v; + static ngx_rtmp_close_stream_t vc; + + static ngx_rtmp_amf_elt_t in_obj[] = { + + { NGX_RTMP_AMF_NUMBER, + ngx_string("start"), + &v.start, 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_string("streamName"), + &v.name, sizeof(v.name) }, + }; + + static ngx_rtmp_amf_elt_t in_elts[] = { + + /* transaction is always 0 */ + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_NULL, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_OBJECT, + ngx_null_string, + &in_obj, sizeof(in_obj) } + }; + + ngx_memzero(&v, sizeof(v)); + + if (ngx_rtmp_receive_amf(s, in, in_elts, + sizeof(in_elts) / sizeof(in_elts[0]))) + { + return NGX_ERROR; + } + + ngx_rtmp_cmd_fill_args(v.name, v.args); + + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->log, 0, + "play2: name='%s' args='%s' start=%i", + v.name, v.args, (ngx_int_t) v.start); + + /* continue from current timestamp */ + + if (v.start < 0) { + v.start = s->current_time; + } + + ngx_memzero(&vc, sizeof(vc)); + + /* close_stream should be synchronous */ + ngx_rtmp_close_stream_filter(s, &vc); + + return ngx_rtmp_play_filter(s, &v); +} + + +static ngx_int_t +ngx_rtmp_cmd_pause_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + static ngx_rtmp_pause_t v; + + static ngx_rtmp_amf_elt_t in_elts[] = { + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_NULL, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_BOOLEAN, + ngx_null_string, + &v.pause, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &v.position, 0 }, + }; + + ngx_memzero(&v, sizeof(v)); + + if (ngx_rtmp_receive_amf(s, in, in_elts, + sizeof(in_elts) / sizeof(in_elts[0]))) + { + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0, + "pause: pause=%i position=%i", + (ngx_int_t) v.pause, (ngx_int_t) v.position); + + return ngx_rtmp_pause(s, &v); +} + + +static ngx_int_t +ngx_rtmp_cmd_pause(ngx_rtmp_session_t *s, ngx_rtmp_pause_t *v) +{ + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_cmd_disconnect_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + s->stage = NGX_LIVE_CLOSE; + s->close_stream_time = ngx_current_msec; + + ngx_log_error(NGX_LOG_INFO, s->log, 0, "disconnect"); + + return ngx_rtmp_disconnect(s); +} + + +static ngx_int_t +ngx_rtmp_cmd_disconnect(ngx_rtmp_session_t *s) +{ + return ngx_rtmp_delete_stream(s, NULL); +} + + +static ngx_int_t +ngx_rtmp_cmd_seek_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + static ngx_rtmp_seek_t v; + + static ngx_rtmp_amf_elt_t in_elts[] = { + + /* transaction is always 0 */ + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_NULL, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &v.offset, sizeof(v.offset) }, + }; + + ngx_memzero(&v, sizeof(v)); + + if (ngx_rtmp_receive_amf(s, in, in_elts, + sizeof(in_elts) / sizeof(in_elts[0]))) + { + return NGX_ERROR; + } + + ngx_log_error(NGX_LOG_INFO, s->log, 0, + "seek: offset=%i", (ngx_int_t) v.offset); + + return ngx_rtmp_seek(s, &v); +} + + +static ngx_int_t +ngx_rtmp_cmd_seek(ngx_rtmp_session_t *s, ngx_rtmp_seek_t *v) +{ + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_cmd_stream_begin(ngx_rtmp_session_t *s, ngx_rtmp_stream_begin_t *v) +{ + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_cmd_stream_eof(ngx_rtmp_session_t *s, ngx_rtmp_stream_eof_t *v) +{ + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_cmd_stream_dry(ngx_rtmp_session_t *s, ngx_rtmp_stream_dry_t *v) +{ + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_cmd_recorded(ngx_rtmp_session_t *s, + ngx_rtmp_recorded_t *v) +{ + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_cmd_set_buflen(ngx_rtmp_session_t *s, ngx_rtmp_set_buflen_t *v) +{ + return NGX_OK; +} + + +static ngx_rtmp_amf_handler_t ngx_rtmp_cmd_map[] = { + { ngx_string("connect"), ngx_rtmp_cmd_connect_init }, + { ngx_string("createStream"), ngx_rtmp_cmd_create_stream_init }, + { ngx_string("closeStream"), ngx_rtmp_cmd_close_stream_init }, + { ngx_string("deleteStream"), ngx_rtmp_cmd_delete_stream_init }, + { ngx_string("publish"), ngx_rtmp_cmd_publish_init }, + { ngx_string("play"), ngx_rtmp_cmd_play_init }, + { ngx_string("play2"), ngx_rtmp_cmd_play2_init }, + { ngx_string("seek"), ngx_rtmp_cmd_seek_init }, + { ngx_string("pause"), ngx_rtmp_cmd_pause_init }, + { ngx_string("pauseraw"), ngx_rtmp_cmd_pause_init }, +}; + + +static ngx_int_t +ngx_rtmp_cmd_postconfiguration(ngx_conf_t *cf) +{ + ngx_rtmp_core_main_conf_t *cmcf; + ngx_rtmp_handler_pt *h; + ngx_rtmp_amf_handler_t *ch, *bh; + size_t n, ncalls; + + cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); + + /* redirect disconnects to deleteStream + * to free client modules from registering + * disconnect callback */ + + h = ngx_array_push(&cmcf->events[NGX_RTMP_DISCONNECT]); + if (h == NULL) { + return NGX_ERROR; + } + + *h = ngx_rtmp_cmd_disconnect_init; + + /* register AMF callbacks */ + + ncalls = sizeof(ngx_rtmp_cmd_map) / sizeof(ngx_rtmp_cmd_map[0]); + + ch = ngx_array_push_n(&cmcf->amf, ncalls); + if (ch == NULL) { + return NGX_ERROR; + } + + bh = ngx_rtmp_cmd_map; + + for(n = 0; n < ncalls; ++n, ++ch, ++bh) { + *ch = *bh; + } + + ngx_rtmp_connect = ngx_rtmp_cmd_connect; + ngx_rtmp_disconnect = ngx_rtmp_cmd_disconnect; + ngx_rtmp_create_stream = ngx_rtmp_cmd_create_stream; + ngx_rtmp_close_stream = ngx_rtmp_cmd_close_stream; + ngx_rtmp_delete_stream = ngx_rtmp_cmd_delete_stream; + ngx_rtmp_publish = ngx_rtmp_cmd_publish; + ngx_rtmp_play = ngx_rtmp_cmd_play; + ngx_rtmp_seek = ngx_rtmp_cmd_seek; + ngx_rtmp_pause = ngx_rtmp_cmd_pause; + + ngx_rtmp_stream_begin = ngx_rtmp_cmd_stream_begin; + ngx_rtmp_stream_eof = ngx_rtmp_cmd_stream_eof; + ngx_rtmp_stream_dry = ngx_rtmp_cmd_stream_dry; + ngx_rtmp_recorded = ngx_rtmp_cmd_recorded; + ngx_rtmp_set_buflen = ngx_rtmp_cmd_set_buflen; + + return NGX_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_cmd_module.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_cmd_module.h new file mode 100644 index 0000000000..5bc3c03caf --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_cmd_module.h @@ -0,0 +1,166 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#ifndef _NGX_RTMP_CMD_H_INCLUDED_ +#define _NGX_RTMP_CMD_H_INCLUDED_ + + +#include +#include +#include +#include "ngx_rtmp.h" + + +#define NGX_RTMP_MAX_NAME 256 +#define NGX_RTMP_MAX_FLASHVER 256 +#define NGX_RTMP_MAX_URL 256 +#define NGX_RTMP_MAX_SESSION 256 +#define NGX_RTMP_MAX_ARGS NGX_RTMP_MAX_NAME + + +/* Basic RTMP call support */ + +typedef struct { + double trans; + u_char app[NGX_RTMP_MAX_NAME]; + u_char args[NGX_RTMP_MAX_ARGS]; + u_char flashver[NGX_RTMP_MAX_FLASHVER]; + u_char swf_url[NGX_RTMP_MAX_URL]; + u_char tc_url[NGX_RTMP_MAX_URL]; + double acodecs; + double vcodecs; + u_char page_url[NGX_RTMP_MAX_URL]; + double object_encoding; +} ngx_rtmp_connect_t; + + +typedef struct { + double trans; + double stream; +} ngx_rtmp_create_stream_t; + + +typedef struct { + double stream; +} ngx_rtmp_delete_stream_t; + + +typedef struct { + double stream; +} ngx_rtmp_close_stream_t; + + +typedef struct { + u_char name[NGX_RTMP_MAX_NAME]; + u_char args[NGX_RTMP_MAX_ARGS]; + u_char type[16]; + int silent; +} ngx_rtmp_publish_t; + + +typedef struct { + u_char name[NGX_RTMP_MAX_NAME]; + u_char args[NGX_RTMP_MAX_ARGS]; + u_char session[NGX_RTMP_MAX_SESSION]; + double start; + double duration; + int reset; + int silent; +} ngx_rtmp_play_t; + + +typedef struct { + double offset; +} ngx_rtmp_seek_t; + + +typedef struct { + uint8_t pause; + double position; +} ngx_rtmp_pause_t; + + +typedef struct { + uint32_t msid; +} ngx_rtmp_msid_t; + + +typedef ngx_rtmp_msid_t ngx_rtmp_stream_begin_t; +typedef ngx_rtmp_msid_t ngx_rtmp_stream_eof_t; +typedef ngx_rtmp_msid_t ngx_rtmp_stream_dry_t; +typedef ngx_rtmp_msid_t ngx_rtmp_recorded_t; + + +typedef struct { + uint32_t msid; + uint32_t buflen; +} ngx_rtmp_set_buflen_t; + + +void ngx_rtmp_cmd_fill_args(u_char name[NGX_RTMP_MAX_NAME], + u_char args[NGX_RTMP_MAX_ARGS]); +void ngx_rtmp_cmd_middleware_init(ngx_rtmp_session_t *s); +void ngx_rtmp_cmd_stream_init(ngx_rtmp_session_t *s, u_char *name, u_char *args, + unsigned publishing); + +ngx_int_t ngx_rtmp_publish_filter(ngx_rtmp_session_t *s, + ngx_rtmp_publish_t *v); +ngx_int_t ngx_rtmp_play_filter(ngx_rtmp_session_t *s, + ngx_rtmp_play_t *v); +ngx_int_t ngx_rtmp_close_stream_filter(ngx_rtmp_session_t *s, + ngx_rtmp_close_stream_t *v); + +ngx_int_t ngx_rtmp_push_filter(ngx_rtmp_session_t *s); +ngx_int_t ngx_rtmp_pull_filter(ngx_rtmp_session_t *s); + +typedef ngx_int_t (*ngx_rtmp_connect_pt)(ngx_rtmp_session_t *s, + ngx_rtmp_connect_t *v); +typedef ngx_int_t (*ngx_rtmp_disconnect_pt)(ngx_rtmp_session_t *s); +typedef ngx_int_t (*ngx_rtmp_create_stream_pt)(ngx_rtmp_session_t *s, + ngx_rtmp_create_stream_t *v); +typedef ngx_int_t (*ngx_rtmp_close_stream_pt)(ngx_rtmp_session_t *s, + ngx_rtmp_close_stream_t *v); +typedef ngx_int_t (*ngx_rtmp_delete_stream_pt)(ngx_rtmp_session_t *s, + ngx_rtmp_delete_stream_t *v); +typedef ngx_int_t (*ngx_rtmp_publish_pt)(ngx_rtmp_session_t *s, + ngx_rtmp_publish_t *v); +typedef ngx_int_t (*ngx_rtmp_play_pt)(ngx_rtmp_session_t *s, + ngx_rtmp_play_t *v); +typedef ngx_int_t (*ngx_rtmp_seek_pt)(ngx_rtmp_session_t *s, + ngx_rtmp_seek_t *v); +typedef ngx_int_t (*ngx_rtmp_pause_pt)(ngx_rtmp_session_t *s, + ngx_rtmp_pause_t *v); + +typedef ngx_int_t (*ngx_rtmp_stream_begin_pt)(ngx_rtmp_session_t *s, + ngx_rtmp_stream_begin_t *v); +typedef ngx_int_t (*ngx_rtmp_stream_eof_pt)(ngx_rtmp_session_t *s, + ngx_rtmp_stream_eof_t *v); +typedef ngx_int_t (*ngx_rtmp_stream_dry_pt)(ngx_rtmp_session_t *s, + ngx_rtmp_stream_dry_t *v); +typedef ngx_int_t (*ngx_rtmp_recorded_pt)(ngx_rtmp_session_t *s, + ngx_rtmp_recorded_t *v); +typedef ngx_int_t (*ngx_rtmp_set_buflen_pt)(ngx_rtmp_session_t *s, + ngx_rtmp_set_buflen_t *v); + + +extern ngx_rtmp_connect_pt ngx_rtmp_connect; +extern ngx_rtmp_disconnect_pt ngx_rtmp_disconnect; +extern ngx_rtmp_create_stream_pt ngx_rtmp_create_stream; +extern ngx_rtmp_close_stream_pt ngx_rtmp_close_stream; +extern ngx_rtmp_delete_stream_pt ngx_rtmp_delete_stream; +extern ngx_rtmp_publish_pt ngx_rtmp_publish; +extern ngx_rtmp_play_pt ngx_rtmp_play; +extern ngx_rtmp_seek_pt ngx_rtmp_seek; +extern ngx_rtmp_pause_pt ngx_rtmp_pause; + +extern ngx_rtmp_stream_begin_pt ngx_rtmp_stream_begin; +extern ngx_rtmp_stream_eof_pt ngx_rtmp_stream_eof; +extern ngx_rtmp_stream_dry_pt ngx_rtmp_stream_dry; +extern ngx_rtmp_set_buflen_pt ngx_rtmp_set_buflen; +extern ngx_rtmp_recorded_pt ngx_rtmp_recorded; + + +#endif /*_NGX_RTMP_CMD_H_INCLUDED_ */ diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_codec_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_codec_module.c new file mode 100644 index 0000000000..9fe27b60e8 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_codec_module.c @@ -0,0 +1,1376 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp_codec_module.h" +#include "ngx_rtmp_live_module.h" +#include "ngx_rtmp_cmd_module.h" +#include "ngx_rtmp_bitop.h" +#include "ngx_rbuf.h" + + +#define NGX_RTMP_CODEC_META_OFF 0 +#define NGX_RTMP_CODEC_META_ON 1 +#define NGX_RTMP_CODEC_META_COPY 2 + + +static void * ngx_rtmp_codec_create_app_conf(ngx_conf_t *cf); +static char * ngx_rtmp_codec_merge_app_conf(ngx_conf_t *cf, + void *parent, void *child); +static ngx_int_t ngx_rtmp_codec_postconfiguration(ngx_conf_t *cf); +static ngx_int_t ngx_rtmp_codec_reconstruct_meta(ngx_rtmp_session_t *s); +static ngx_int_t ngx_rtmp_codec_copy_meta(ngx_rtmp_session_t *s, + ngx_rtmp_header_t *h, ngx_chain_t *in); +static ngx_int_t ngx_rtmp_codec_prepare_meta(ngx_rtmp_session_t *s, + uint32_t timestamp); +static void ngx_rtmp_codec_parse_aac_header(ngx_rtmp_session_t *s, + ngx_chain_t *in); +static void ngx_rtmp_codec_parse_avc_header(ngx_rtmp_session_t *s, + ngx_chain_t *in); +static void ngx_rtmp_codec_parse_hevc_header(ngx_rtmp_session_t *s, + ngx_chain_t *in); +#if (NGX_DEBUG) +static void ngx_rtmp_codec_dump_header(ngx_rtmp_session_t *s, const char *type, + ngx_chain_t *in); +#endif + + +typedef struct { + ngx_uint_t meta; +} ngx_rtmp_codec_app_conf_t; + + +static ngx_conf_enum_t ngx_rtmp_codec_meta_slots[] = { + { ngx_string("off"), NGX_RTMP_CODEC_META_OFF }, + { ngx_string("on"), NGX_RTMP_CODEC_META_ON }, + { ngx_string("copy"), NGX_RTMP_CODEC_META_COPY }, + { ngx_null_string, 0 } +}; + + +static ngx_command_t ngx_rtmp_codec_commands[] = { + + { ngx_string("meta"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_enum_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_codec_app_conf_t, meta), + &ngx_rtmp_codec_meta_slots }, + + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_rtmp_codec_module_ctx = { + NULL, /* preconfiguration */ + ngx_rtmp_codec_postconfiguration, /* postconfiguration */ + NULL, /* create main configuration */ + NULL, /* init main configuration */ + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + ngx_rtmp_codec_create_app_conf, /* create app configuration */ + ngx_rtmp_codec_merge_app_conf /* merge app configuration */ +}; + + +ngx_module_t ngx_rtmp_codec_module = { + NGX_MODULE_V1, + &ngx_rtmp_codec_module_ctx, /* module context */ + ngx_rtmp_codec_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static const char * +audio_codecs[] = { + "", + "ADPCM", + "MP3", + "LinearLE", + "Nellymoser16", + "Nellymoser8", + "Nellymoser", + "G711A", + "G711U", + "", + "AAC", + "Speex", + "", + "", + "MP3-8K", + "DeviceSpecific", + "Uncompressed" +}; + + +static const char * +video_codecs[] = { + "", + "Jpeg", + "Sorenson-H263", + "ScreenVideo", + "On2-VP6", + "On2-VP6-Alpha", + "ScreenVideo2", + "H264", +}; + + +u_char * +ngx_rtmp_get_audio_codec_name(ngx_uint_t id) +{ + return (u_char *)(id < sizeof(audio_codecs) / sizeof(audio_codecs[0]) + ? audio_codecs[id] + : ""); +} + + +u_char * +ngx_rtmp_get_video_codec_name(ngx_uint_t id) +{ + return (u_char *)(id < sizeof(video_codecs) / sizeof(video_codecs[0]) + ? video_codecs[id] + : ""); +} + + +static ngx_uint_t +ngx_rtmp_codec_get_next_version() +{ + ngx_uint_t v; + static ngx_uint_t version; + + do { + v = ++version; + } while (v == 0); + + return v; +} + + +static ngx_int_t +ngx_rtmp_codec_disconnect(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_rtmp_codec_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + if (ctx == NULL) { + return NGX_OK; + } + + if (ctx->avc_header) { + ngx_rtmp_shared_free_frame(ctx->avc_header); + ctx->avc_header = NULL; + } + + if (ctx->aac_header) { + ngx_rtmp_shared_free_frame(ctx->aac_header); + ctx->aac_header = NULL; + } + + if (ctx->meta) { + ngx_rtmp_shared_free_frame(ctx->meta); + ctx->meta = NULL; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_codec_av(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_rtmp_core_srv_conf_t *cscf; + ngx_rtmp_core_app_conf_t *cacf; + ngx_rtmp_codec_ctx_t *ctx; + ngx_rtmp_frame_t **header; + uint8_t fmt; + u_char frametype; + static ngx_uint_t sample_rates[] = + { 5512, 11025, 22050, 44100 }; + + if (h->type != NGX_RTMP_MSG_AUDIO && h->type != NGX_RTMP_MSG_VIDEO) { + return NGX_OK; + } + + if (h->type == NGX_RTMP_MSG_VIDEO) { + frametype = in->buf->pos[0] & 0xf0; + if (frametype != 0x10 && frametype != 0x20) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "codec: receive unkwnon frametype %02xD", frametype); + return NGX_OK; + } + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + if (ctx == NULL) { + ctx = ngx_pcalloc(s->pool, sizeof(ngx_rtmp_codec_ctx_t)); + ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_codec_module); + } + + /* save codec */ + if (in->buf->last - in->buf->pos < 1) { + return NGX_OK; + } + + fmt = in->buf->pos[0]; + if (h->type == NGX_RTMP_MSG_AUDIO) { + ctx->audio_codec_id = (fmt & 0xf0) >> 4; + ctx->audio_channels = (fmt & 0x01) + 1; + ctx->sample_size = (fmt & 0x02) ? 2 : 1; + + if (ctx->sample_rate == 0) { + ctx->sample_rate = sample_rates[(fmt & 0x0c) >> 2]; + } + s->acodec = ctx->audio_codec_id; + } else { + ctx->video_codec_id = (fmt & 0x0f); + s->vcodec = ctx->video_codec_id; + } + + /* save AVC/AAC header */ + if (in->buf->last - in->buf->pos < 3) { + return NGX_OK; + } + + /* no conf */ + if (!ngx_rtmp_is_codec_header(in)) { + return NGX_OK; + } + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + cacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_core_module); + header = NULL; + + if (h->type == NGX_RTMP_MSG_AUDIO) { + if (ctx->audio_codec_id == NGX_RTMP_AUDIO_AAC) { + header = &ctx->aac_header; + ngx_rtmp_codec_parse_aac_header(s, in); + } + } else { + if (ctx->video_codec_id == NGX_RTMP_VIDEO_H264) { + header = &ctx->avc_header; + ngx_rtmp_codec_parse_avc_header(s, in); + } else if (ctx->video_codec_id == cacf->hevc_codec) { + header = &ctx->avc_header; + ngx_rtmp_codec_parse_hevc_header(s, in); + } + } + + if (header == NULL) { + return NGX_OK; + } + + if (*header) { + ngx_rtmp_shared_free_frame(*header); + } + + *header = ngx_rtmp_shared_alloc_frame(cscf->chunk_size, in, 0); + + return NGX_OK; +} + + +static void +ngx_rtmp_codec_parse_aac_header(ngx_rtmp_session_t *s, ngx_chain_t *in) +{ + ngx_uint_t idx; + ngx_rtmp_codec_ctx_t *ctx; + ngx_rtmp_bit_reader_t br; + + static ngx_uint_t aac_sample_rates[] = + { 96000, 88200, 64000, 48000, + 44100, 32000, 24000, 22050, + 16000, 12000, 11025, 8000, + 7350, 0, 0, 0 }; + +#if (NGX_DEBUG) + ngx_rtmp_codec_dump_header(s, "aac", in); +#endif + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + ngx_rtmp_bit_init_reader(&br, in->buf->pos, in->buf->last); + + ngx_rtmp_bit_read(&br, 16); + + ctx->aac_profile = (ngx_uint_t) ngx_rtmp_bit_read(&br, 5); + if (ctx->aac_profile == 31) { + ctx->aac_profile = (ngx_uint_t) ngx_rtmp_bit_read(&br, 6) + 32; + } + + idx = (ngx_uint_t) ngx_rtmp_bit_read(&br, 4); + if (idx == 15) { + ctx->sample_rate = (ngx_uint_t) ngx_rtmp_bit_read(&br, 24); + } else { + ctx->sample_rate = aac_sample_rates[idx]; + } + + ctx->aac_chan_conf = (ngx_uint_t) ngx_rtmp_bit_read(&br, 4); + + if (ctx->aac_profile == 5 || ctx->aac_profile == 29) { + + if (ctx->aac_profile == 29) { + ctx->aac_ps = 1; + } + + ctx->aac_sbr = 1; + + idx = (ngx_uint_t) ngx_rtmp_bit_read(&br, 4); + if (idx == 15) { + ctx->sample_rate = (ngx_uint_t) ngx_rtmp_bit_read(&br, 24); + } else { + ctx->sample_rate = aac_sample_rates[idx]; + } + + ctx->aac_profile = (ngx_uint_t) ngx_rtmp_bit_read(&br, 5); + if (ctx->aac_profile == 31) { + ctx->aac_profile = (ngx_uint_t) ngx_rtmp_bit_read(&br, 6) + 32; + } + } + + /* MPEG-4 Audio Specific Config + + 5 bits: object type + if (object type == 31) + 6 bits + 32: object type + 4 bits: frequency index + if (frequency index == 15) + 24 bits: frequency + 4 bits: channel configuration + + if (object_type == 5) + 4 bits: frequency index + if (frequency index == 15) + 24 bits: frequency + 5 bits: object type + if (object type == 31) + 6 bits + 32: object type + + var bits: AOT Specific Config + */ + + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->log, 0, + "codec: aac header profile=%ui, " + "sample_rate=%ui, chan_conf=%ui", + ctx->aac_profile, ctx->sample_rate, ctx->aac_chan_conf); +} + + +/* + * ITU-T H.265 7.3.1 General NAL unit syntax + */ +static ngx_int_t +ngx_rtmp_codec_parse_hevc_nal_to_rbsp(ngx_rtmp_session_t *s, u_char *p, + ngx_rtmp_bit_reader_t *br, ngx_uint_t nal_unit_type, + ngx_uint_t nal_unit_len) +{ + ngx_uint_t i, count, rbsp_bytes; + + /* + * nal_unit + * nal_unit_header() + * NumBytesInRbsp = 0 + * for (i = 2; i < NumBytesInNalUnit; i++) + * if (i + 2 < NumBytesInNalUnit && next_bits(24) == 0x000003) { + * rbsp_byte[NumBytesInRbsp++] + * rbsp_byte[NumBytesInRbsp++] + * i += 2 + * emulation_prevention_three_byte // equal to 0x03 + * } else + * rbsp_byte[NumBytesInRbsp++] + * + * nal_unit_header + * forbidden_zero_bit 1 bit + * nal_unit_type 6 bits + * nuh_layer_id 6 bits + * nuh_temporal_id_plus1 3 bits + * + * ITU-T H.265 7.4.2.1 + * emulation_prevention_three_byte is a byte equal to 0x03. + * When an emulation_prevention_three_byte is present in the NAL unit, + * it shall be discarded by the decoding process + * Within the NAL unit, the following three-byte sequences shall not + * occur at any byte-aligned position: + * 0x000000 + * 0x000001 + * 0x000002 + * Within the NAL unit, any four-byte sequence that starts with + * 0x000003 other than the following sequences shall not occur at + * any byte-aligned position: + * 0x00000300 + * 0x00000301 + * 0x00000302 + * 0x00000303 + */ + + ngx_rtmp_bit_read(br, 1); + if (ngx_rtmp_bit_read(br, 6) != nal_unit_type) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "nal_unit_type not expect %ui", nal_unit_type); + return NGX_ERROR; + } + ngx_rtmp_bit_read(br, 6); + ngx_rtmp_bit_read(br, 3); + + count = 0; + rbsp_bytes = 0; + for (i = 0; i < nal_unit_len; ++i) { + if (count == 2) { /* already 0x0000 */ + if (br->pos[i] < 0x03) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "three bytes sequence error"); + return NGX_ERROR; + } + + if (br->pos[i] == 0x03 && br->pos[i + 1] > 0x03) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "four bytes sequence error"); + return NGX_ERROR; + } + + if (br->pos[i] == 0x03) { + count = 0; + continue; + } + } + + *p++ = br->pos[i]; + ++rbsp_bytes; + if (br->pos[i] == 0x00) { + ++count; + } else { + count = 0; + } + } + + return rbsp_bytes; +} + +/* + * ITU-T H.265 7.3.3 Profile, tier and level syntax + */ +static void +ngx_rtmp_codec_parse_hevc_ptl(ngx_rtmp_session_t *s, ngx_rtmp_bit_reader_t *br, + ngx_flag_t profilePresentFlag, ngx_uint_t maxNumSubLayersMinus1) +{ + ngx_uint_t i, slppf[8], sllpf[8]; + + if (profilePresentFlag) { + /* + * profile_tier_level + * general_profile_space 2 bits + * general_tier_flag 1 bit + * general_profile_idc 5 bits + * for (j = 0; j < 32; j++) + * general_profile_compatibility_flag[j] 1 bit + * general_progressive_source_flag 1 bit + * general_interlaced_source_flag 1 bit + * general_non_packed_constraint_flag 1 bit + * general_frame_only_constraint_flag 1 bit + * + * general_max_12bit_constraint_flag 1 bit + * general_max_10bit_constraint_flag 1 bit + * general_max_8bit_constraint_flag 1 bit + * general_max_422chroma_constraint_flag 1 bit + * general_max_420chroma_constraint_flag 1 bit + * general_max_monochrome_constraint_flag 1 bit + * general_intra_constraint_flag 1 bit + * general_one_picture_only_constraint_flag 1 bit + * general_lower_bit_rate_constraint_flag 1 bit + * general_reserved_zero_34bits 34 bits + * + * general_inbld_flag 1 bit + */ + ngx_rtmp_bit_read(br, 88); + } + + /* + * profile_tier_level + * general_level_idc 8 bits + */ + ngx_rtmp_bit_read(br, 8); + + /* + * profile_tier_level + * for(i = 0; i < maxNumSubLayersMinus1; i++) { + * sub_layer_profile_present_flag[i] 1 bit + * sub_layer_level_present_flag[i] 1 bit + * } + * + * if (maxNumSubLayersMinus1 > 0) + * for(i = maxNumSubLayersMinus1; i < 8; i++) + * reserved_zero_2bits[i] 2 bits + */ + for (i = 0; i < maxNumSubLayersMinus1; ++i) { + slppf[i] = ngx_rtmp_bit_read(br, 1); + sllpf[i] = ngx_rtmp_bit_read(br, 1); + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "%d sub_layer_profile_present_flag:%d, " + "sub_layer_level_present_flag:%d", i, slppf[i], sllpf[i]); + } + + if (maxNumSubLayersMinus1 > 0) { + for (i = maxNumSubLayersMinus1; i < 8; ++i) { + ngx_uint_t t = ngx_rtmp_bit_read(br, 2); + ngx_log_error(NGX_LOG_ERR, s->log, 0, "zero bit %d", t); + } + } + + /* + * profile_tier_level + * for (i = 0; i < maxNumSubLayersMinus1; i++) { + * if (sub_layer_profile_present_flag[i] { + * 44 bits + * } + * if (sub_layer_level_present_flag[i]) { + * sub_layer_level_idc[i] 8 bits + * } + * } + */ + for (i = 0; i < maxNumSubLayersMinus1; ++i) { + if (slppf[i]) { + ngx_rtmp_bit_read(br, 88); + } + + if (sllpf[i]) { + ngx_rtmp_bit_read(br, 8); + } + } +} + +/* + * ITU-T H.265 7.3.2.2 Sequence parameter set RBSP syntax + */ +static void +ngx_rtmp_codec_parse_hevc_sps(ngx_rtmp_session_t *s, ngx_rtmp_codec_ctx_t *ctx, + ngx_rtmp_bit_reader_t *pbr, ngx_uint_t nal_unit_len) +{ + ngx_uint_t mslm, psi, cfi, width, height, + subwidthC, subheightC, + cwlo, cwro, cwto, cwbo; + ngx_rtmp_bit_reader_t br; + u_char buf[4096]; + ngx_int_t rbsp_bytes; + + ngx_rtmp_bit_init_reader(&br, pbr->pos, pbr->pos + nal_unit_len); + rbsp_bytes = ngx_rtmp_codec_parse_hevc_nal_to_rbsp(s, buf, &br, NAL_SPS, + nal_unit_len); + if (rbsp_bytes == NGX_ERROR) { + return; + } + + ngx_rtmp_bit_init_reader(&br, buf, buf + rbsp_bytes); + + /* + * seq_parameter_set_rbsp + * sps_video_parameter_set_id 4 bits + * sps_max_sub_layers_minus1 3 bits + * sps_temporal_id_nesting_flag 1 bit + */ + ngx_rtmp_bit_read(&br, 4); + mslm = ngx_rtmp_bit_read(&br, 3); + ngx_rtmp_bit_read(&br, 1); + + /* + * seq_parameter_set_rbsp + * profile_tier_level(1, sps_max_sub_layers_minus1) + */ + ngx_rtmp_codec_parse_hevc_ptl(s, &br, 1, mslm); + + /* calc resolution */ + /* + * seq_parameter_set_rbsp + * sps_seq_parameter_set_id v + * chroma_format_idc v + * if (chroma_format_idc == 3) + * separate_colour_plane_flag 1 bit + * pic_width_in_luma_samples v + * pic_height_in_luma_samples v + * conformance_window_flag 1 bit + * if (conformance_window_flag) { + * conf_win_left_offset v + * conf_win_right_offset v + * conf_win_top_offset v + * conf_win_bottom_offset v + * } + */ + psi = ngx_rtmp_bit_read_golomb(&br); + if (psi > 16 || br.err) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "read sps_seq_parameter_set_id error: %ui", psi); + return; + } + + cfi = ngx_rtmp_bit_read_golomb(&br); + if (cfi > 3 || br.err) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "read chroma_format_idc error: %ui", cfi); + return; + } + + if (cfi == 3) { + ngx_rtmp_bit_read(&br, 1); + } + + width = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br); + if (br.err) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, "read width error"); + return; + } + + height = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br); + if (br.err) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, "read height error"); + return; + } + + if (ngx_rtmp_bit_read(&br, 1)) { + cwlo = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br); + cwro = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br); + cwto = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br); + cwbo = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br); + + /* + * ITU-T H.265 Table 6-1 + */ + if (cfi == 1) { /* 4:2:0 */ + subwidthC = 2; + subheightC = 2; + } else if (cfi == 2) { /* 4:2:2 */ + subwidthC = 2; + subheightC = 1; + } else { /* Monochrome or 4:4:4 */ + subwidthC = 1; + subheightC = 1; + } + + /* + * ITU-T H.265 7.4.3.2.1 + * + * horizontal picture coordinates from + * SubWidthC * conf_win_left_offset to + * pic_width_in_luma_samples - (SubWidthC * conf_win_right_offset + 1) + * vertical picture coordinates from + * SubHeightC * conf_win_top_offset to + * pic_height_in_luma_samples - + * (SubHeightC * conf_win_bottom_offset + 1) + */ + ctx->width = width - (subwidthC * cwro + 1) - (subwidthC * cwlo); + ctx->height = height - (subheightC * cwbo + 1) - (subheightC * cwto); + } else { + ctx->width = width; + ctx->height = height; + } + + return; +} + +static void +ngx_rtmp_codec_parse_hevc_header(ngx_rtmp_session_t *s, ngx_chain_t *in) +{ + ngx_uint_t i, j, num_arrays, nal_unit_type, num_nalus, + nal_unit_len; + ngx_rtmp_codec_ctx_t *ctx; + ngx_rtmp_bit_reader_t br; + +#if (NGX_DEBUG) + ngx_rtmp_codec_dump_header(s, "hevc", in); +#endif + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + ngx_rtmp_bit_init_reader(&br, in->buf->pos, in->buf->last); + + /* + * FrameType 4 bits + * CodecID 4 bits + * AVCPacketType 1 byte + * CompositionTime 3 bytes + * HEVCDecoderConfigurationRecord + * configurationVersion 1 byte + */ + ngx_rtmp_bit_read(&br, 48); + + /* + * HEVCDecoderConfigurationRecord + * general_profile_space 2 bits + * general_tier_flag 1 bit + * general_profile_idc 5 bits + * general_profile_compatibility_flags 4 bytes + * general_constraint_indicator_flags 6 bytes + * general_level_idc 1 byte + * min_spatial_segmentation_idc 4 bits reserved + 12 bits + * parallelismType 6 bits reserved + 2 bits + * chroma_format_idc 6 bits reserved + 2 bits + * bit_depth_luma_minus8 5 bits reserved + 3 bits + * bit_depth_chroma_minus8 5 bits reserved + 3 bits + * avgFrameRate 2 bytes + */ + ngx_rtmp_bit_read(&br, 160); + + /* + * HEVCDecoderConfigurationRecord + * constantFrameRate 2 bits + * numTemporalLayers 3 bits + * temporalIdNested 1 bit + * lengthSizeMinusOne 2 bits + */ + ctx->avc_nal_bytes = (ngx_uint_t) ((ngx_rtmp_bit_read_8(&br) & 0x03) + 1); + + /* + * HEVCDecoderConfigurationRecord + * numOfArrays 1 byte + */ + num_arrays = (ngx_uint_t) ngx_rtmp_bit_read_8(&br); + + for (i = 0; i < num_arrays; ++i) { + /* + * array_completeness 1 bit + * reserved 1 bit + * NAL_unit_type 6 bits + * numNalus 2 bytes + */ + nal_unit_type = (ngx_uint_t) (ngx_rtmp_bit_read_8(&br) & 0x3f); + num_nalus = (ngx_uint_t) ngx_rtmp_bit_read_16(&br); + + for (j = 0; j < num_nalus; ++j) { + /* + * nalUnitLength 2 bytes + */ + nal_unit_len = (ngx_uint_t) ngx_rtmp_bit_read_16(&br); + + switch (nal_unit_type) { + case NAL_SPS: + ngx_rtmp_codec_parse_hevc_sps(s, ctx, &br, nal_unit_len); + ngx_rtmp_bit_read(&br, nal_unit_len * 8); + break; + default: + ngx_rtmp_bit_read(&br, nal_unit_len * 8); + break; + } + } + } + + ngx_log_debug7(NGX_LOG_DEBUG_RTMP, s->log, 0, + "codec: hevc header " + "profile=%ui, compat=%ui, level=%ui, " + "nal_bytes=%ui, ref_frames=%ui, width=%ui, height=%ui", + ctx->avc_profile, ctx->avc_compat, ctx->avc_level, + ctx->avc_nal_bytes, ctx->avc_ref_frames, + ctx->width, ctx->height); +} + + +static void +ngx_rtmp_codec_parse_avc_header(ngx_rtmp_session_t *s, ngx_chain_t *in) +{ + ngx_uint_t profile_idc, width, height, crop_left, crop_right, + crop_top, crop_bottom, frame_mbs_only, n, cf_idc, + num_ref_frames; + ngx_rtmp_codec_ctx_t *ctx; + ngx_rtmp_bit_reader_t br; + +#if (NGX_DEBUG) + ngx_rtmp_codec_dump_header(s, "avc", in); +#endif + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + ngx_rtmp_bit_init_reader(&br, in->buf->pos, in->buf->last); + + ngx_rtmp_bit_read(&br, 48); + + ctx->avc_profile = (ngx_uint_t) ngx_rtmp_bit_read_8(&br); + ctx->avc_compat = (ngx_uint_t) ngx_rtmp_bit_read_8(&br); + ctx->avc_level = (ngx_uint_t) ngx_rtmp_bit_read_8(&br); + + /* nal bytes */ + ctx->avc_nal_bytes = (ngx_uint_t) ((ngx_rtmp_bit_read_8(&br) & 0x03) + 1); + + /* nnals */ + if ((ngx_rtmp_bit_read_8(&br) & 0x1f) == 0) { + return; + } + + /* nal size */ + ngx_rtmp_bit_read(&br, 16); + + /* nal type */ + if (ngx_rtmp_bit_read_8(&br) != 0x67) { + return; + } + + /* SPS */ + + /* profile idc */ + profile_idc = (ngx_uint_t) ngx_rtmp_bit_read(&br, 8); + + /* flags */ + ngx_rtmp_bit_read(&br, 8); + + /* level idc */ + ngx_rtmp_bit_read(&br, 8); + + /* SPS id */ + ngx_rtmp_bit_read_golomb(&br); + + if (profile_idc == 100 || profile_idc == 110 || + profile_idc == 122 || profile_idc == 244 || profile_idc == 44 || + profile_idc == 83 || profile_idc == 86 || profile_idc == 118) + { + /* chroma format idc */ + cf_idc = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br); + + if (cf_idc == 3) { + + /* separate color plane */ + ngx_rtmp_bit_read(&br, 1); + } + + /* bit depth luma - 8 */ + ngx_rtmp_bit_read_golomb(&br); + + /* bit depth chroma - 8 */ + ngx_rtmp_bit_read_golomb(&br); + + /* qpprime y zero transform bypass */ + ngx_rtmp_bit_read(&br, 1); + + /* seq scaling matrix present */ + if (ngx_rtmp_bit_read(&br, 1)) { + + for (n = 0; n < (cf_idc != 3 ? 8u : 12u); n++) { + + /* seq scaling list present */ + if (ngx_rtmp_bit_read(&br, 1)) { + + /* TODO: scaling_list() + if (n < 6) { + } else { + } + */ + } + } + } + } + + /* log2 max frame num */ + ngx_rtmp_bit_read_golomb(&br); + + /* pic order cnt type */ + switch (ngx_rtmp_bit_read_golomb(&br)) { + case 0: + + /* max pic order cnt */ + ngx_rtmp_bit_read_golomb(&br); + break; + + case 1: + + /* delta pic order alwys zero */ + ngx_rtmp_bit_read(&br, 1); + + /* offset for non-ref pic */ + ngx_rtmp_bit_read_golomb(&br); + + /* offset for top to bottom field */ + ngx_rtmp_bit_read_golomb(&br); + + /* num ref frames in pic order */ + num_ref_frames = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br); + + for (n = 0; n < num_ref_frames; n++) { + + /* offset for ref frame */ + ngx_rtmp_bit_read_golomb(&br); + } + } + + /* num ref frames */ + ctx->avc_ref_frames = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br); + + /* gaps in frame num allowed */ + ngx_rtmp_bit_read(&br, 1); + + /* pic width in mbs - 1 */ + width = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br); + + /* pic height in map units - 1 */ + height = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br); + + /* frame mbs only flag */ + frame_mbs_only = (ngx_uint_t) ngx_rtmp_bit_read(&br, 1); + + if (!frame_mbs_only) { + + /* mbs adaprive frame field */ + ngx_rtmp_bit_read(&br, 1); + } + + /* direct 8x8 inference flag */ + ngx_rtmp_bit_read(&br, 1); + + /* frame cropping */ + if (ngx_rtmp_bit_read(&br, 1)) { + + crop_left = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br); + crop_right = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br); + crop_top = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br); + crop_bottom = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br); + + } else { + + crop_left = 0; + crop_right = 0; + crop_top = 0; + crop_bottom = 0; + } + + ctx->width = (width + 1) * 16 - (crop_left + crop_right) * 2; + ctx->height = (2 - frame_mbs_only) * (height + 1) * 16 - + (crop_top + crop_bottom) * 2; + + ngx_log_debug7(NGX_LOG_DEBUG_RTMP, s->log, 0, + "codec: avc header " + "profile=%ui, compat=%ui, level=%ui, " + "nal_bytes=%ui, ref_frames=%ui, width=%ui, height=%ui", + ctx->avc_profile, ctx->avc_compat, ctx->avc_level, + ctx->avc_nal_bytes, ctx->avc_ref_frames, + ctx->width, ctx->height); +} + + +#if (NGX_DEBUG) +static void +ngx_rtmp_codec_dump_header(ngx_rtmp_session_t *s, const char *type, + ngx_chain_t *in) +{ + u_char buf[256], *p, *pp; + u_char hex[] = "0123456789abcdef"; + + for (pp = buf, p = in->buf->pos; + p < in->buf->last && pp < buf + sizeof(buf) - 1; + ++p) + { + *pp++ = hex[*p >> 4]; + *pp++ = hex[*p & 0x0f]; + } + + *pp = 0; + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0, + "codec: %s header %s", type, buf); +} +#endif + + +static ngx_int_t +ngx_rtmp_codec_reconstruct_meta(ngx_rtmp_session_t *s) +{ + ngx_rtmp_codec_ctx_t *ctx; + ngx_rtmp_core_srv_conf_t *cscf; + ngx_int_t rc; + + static struct { + double width; + double height; + double duration; + double frame_rate; + double video_data_rate; + double video_codec_id; + double audio_data_rate; + double audio_codec_id; + u_char profile[32]; + u_char level[32]; + } v; + + static ngx_rtmp_amf_elt_t out_inf[] = { + + { NGX_RTMP_AMF_STRING, + ngx_string("Server"), + "PingOS (https://pingos.io)", 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("width"), + &v.width, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("height"), + &v.height, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("displayWidth"), + &v.width, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("displayHeight"), + &v.height, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("duration"), + &v.duration, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("framerate"), + &v.frame_rate, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("fps"), + &v.frame_rate, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("videodatarate"), + &v.video_data_rate, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("videocodecid"), + &v.video_codec_id, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("audiodatarate"), + &v.audio_data_rate, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("audiocodecid"), + &v.audio_codec_id, 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_string("profile"), + &v.profile, sizeof(v.profile) }, + + { NGX_RTMP_AMF_STRING, + ngx_string("level"), + &v.level, sizeof(v.level) }, + }; + + static ngx_rtmp_amf_elt_t out_elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + "onMetaData", 0 }, + + { NGX_RTMP_AMF_OBJECT, + ngx_null_string, + out_inf, sizeof(out_inf) }, + }; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + if (ctx == NULL) { + return NGX_OK; + } + + if (ctx->meta) { + ngx_rtmp_shared_free_frame(ctx->meta); + } + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + ctx->meta = ngx_rtmp_shared_alloc_frame(cscf->chunk_size, NULL, 1); + + v.width = ctx->width; + v.height = ctx->height; + v.duration = ctx->duration; + v.frame_rate = ctx->frame_rate; + v.video_data_rate = ctx->video_data_rate; + v.video_codec_id = ctx->video_codec_id; + v.audio_data_rate = ctx->audio_data_rate; + v.audio_codec_id = ctx->audio_codec_id; + ngx_memcpy(v.profile, ctx->profile, sizeof(ctx->profile)); + ngx_memcpy(v.level, ctx->level, sizeof(ctx->level)); + + rc = ngx_rtmp_append_amf(s, &ctx->meta->chain, &ctx->meta->chain, out_elts, + sizeof(out_elts) / sizeof(out_elts[0])); + if (rc != NGX_OK || ctx->meta == NULL) { + return NGX_ERROR; + } + + return ngx_rtmp_codec_prepare_meta(s, 0); +} + + +static ngx_int_t +ngx_rtmp_codec_copy_meta(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_rtmp_codec_ctx_t *ctx; + ngx_rtmp_core_srv_conf_t *cscf; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + if (ctx->meta) { + ngx_rtmp_shared_free_frame(ctx->meta); + } + + ctx->meta = ngx_rtmp_shared_alloc_frame(cscf->chunk_size, in, 0); + + if (ctx->meta == NULL) { + return NGX_ERROR; + } + + return ngx_rtmp_codec_prepare_meta(s, h->timestamp); +} + + +static ngx_int_t +ngx_rtmp_codec_prepare_meta(ngx_rtmp_session_t *s, uint32_t timestamp) +{ + ngx_rtmp_codec_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + ctx->meta->hdr.csid = NGX_RTMP_CSID_AMF; + ctx->meta->hdr.msid = NGX_RTMP_MSID; + ctx->meta->hdr.type = NGX_RTMP_MSG_AMF_META; + ctx->meta->hdr.timestamp = timestamp; + + ctx->meta_version = ngx_rtmp_codec_get_next_version(); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_codec_meta_data(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_rtmp_codec_app_conf_t *cacf; + ngx_rtmp_codec_ctx_t *ctx; + ngx_uint_t skip; + + static struct { + double width; + double height; + double duration; + double frame_rate; + double video_data_rate; + double video_codec_id_n; + u_char video_codec_id_s[32]; + double audio_data_rate; + double audio_codec_id_n; + u_char audio_codec_id_s[32]; + u_char profile[32]; + u_char level[32]; + } v; + + static ngx_rtmp_amf_elt_t in_video_codec_id[] = { + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &v.video_codec_id_n, 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + &v.video_codec_id_s, sizeof(v.video_codec_id_s) }, + }; + + static ngx_rtmp_amf_elt_t in_audio_codec_id[] = { + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &v.audio_codec_id_n, 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + &v.audio_codec_id_s, sizeof(v.audio_codec_id_s) }, + }; + + static ngx_rtmp_amf_elt_t in_inf[] = { + + { NGX_RTMP_AMF_NUMBER, + ngx_string("width"), + &v.width, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("height"), + &v.height, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("duration"), + &v.duration, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("framerate"), + &v.frame_rate, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("fps"), + &v.frame_rate, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("videodatarate"), + &v.video_data_rate, 0 }, + + { NGX_RTMP_AMF_VARIANT, + ngx_string("videocodecid"), + in_video_codec_id, sizeof(in_video_codec_id) }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("audiodatarate"), + &v.audio_data_rate, 0 }, + + { NGX_RTMP_AMF_VARIANT, + ngx_string("audiocodecid"), + in_audio_codec_id, sizeof(in_audio_codec_id) }, + + { NGX_RTMP_AMF_STRING, + ngx_string("profile"), + &v.profile, sizeof(v.profile) }, + + { NGX_RTMP_AMF_STRING, + ngx_string("level"), + &v.level, sizeof(v.level) }, + }; + + static ngx_rtmp_amf_elt_t in_elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_OBJECT, + ngx_null_string, + in_inf, sizeof(in_inf) }, + }; + + cacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_codec_module); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + if (ctx == NULL) { + ctx = ngx_pcalloc(s->pool, sizeof(ngx_rtmp_codec_ctx_t)); + ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_codec_module); + } + + ngx_memzero(&v, sizeof(v)); + + /* use -1 as a sign of unchanged data; + * 0 is a valid value for uncompressed audio */ + v.audio_codec_id_n = -1; + + /* FFmpeg sends a string in front of actal metadata; ignore it */ + skip = !(in->buf->last > in->buf->pos + && *in->buf->pos == NGX_RTMP_AMF_STRING); + if (ngx_rtmp_receive_amf(s, in, in_elts + skip, + sizeof(in_elts) / sizeof(in_elts[0]) - skip)) + { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "codec: error parsing data frame"); + return NGX_OK; + } + + ctx->width = (ngx_uint_t) v.width; + ctx->height = (ngx_uint_t) v.height; + ctx->duration = (ngx_uint_t) v.duration; + ctx->frame_rate = v.frame_rate; + ctx->video_data_rate = (ngx_uint_t) v.video_data_rate; + ctx->video_codec_id = (ngx_uint_t) v.video_codec_id_n; + ctx->audio_data_rate = (ngx_uint_t) v.audio_data_rate; + ctx->audio_codec_id = (v.audio_codec_id_n == -1 + ? 0 : v.audio_codec_id_n == 0 + ? NGX_RTMP_AUDIO_UNCOMPRESSED : (ngx_uint_t) v.audio_codec_id_n); + ngx_memcpy(ctx->profile, v.profile, sizeof(v.profile)); + ngx_memcpy(ctx->level, v.level, sizeof(v.level)); + + ngx_log_debug8(NGX_LOG_DEBUG_RTMP, s->log, 0, + "codec: data frame: " + "width=%ui height=%ui duration=%ui frame_rate=%f " + "video=%s (%ui) audio=%s (%ui)", + ctx->width, ctx->height, ctx->duration, ctx->frame_rate, + ngx_rtmp_get_video_codec_name(ctx->video_codec_id), + ctx->video_codec_id, + ngx_rtmp_get_audio_codec_name(ctx->audio_codec_id), + ctx->audio_codec_id); + + switch (cacf->meta) { + case NGX_RTMP_CODEC_META_ON: + return ngx_rtmp_codec_reconstruct_meta(s); + case NGX_RTMP_CODEC_META_COPY: + return ngx_rtmp_codec_copy_meta(s, h, in); + } + + /* NGX_RTMP_CODEC_META_OFF */ + + return NGX_OK; +} + + +static void * +ngx_rtmp_codec_create_app_conf(ngx_conf_t *cf) +{ + ngx_rtmp_codec_app_conf_t *cacf; + + cacf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_codec_app_conf_t)); + if (cacf == NULL) { + return NULL; + } + + cacf->meta = NGX_CONF_UNSET_UINT; + + return cacf; +} + + +static char * +ngx_rtmp_codec_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_rtmp_codec_app_conf_t *prev = parent; + ngx_rtmp_codec_app_conf_t *conf = child; + + ngx_conf_merge_uint_value(conf->meta, prev->meta, NGX_RTMP_CODEC_META_ON); + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_rtmp_codec_postconfiguration(ngx_conf_t *cf) +{ + ngx_rtmp_core_main_conf_t *cmcf; + ngx_rtmp_handler_pt *h; + ngx_rtmp_amf_handler_t *ch; + + cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); + + h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AUDIO]); + *h = ngx_rtmp_codec_av; + + h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_VIDEO]); + *h = ngx_rtmp_codec_av; + + h = ngx_array_push(&cmcf->events[NGX_RTMP_DISCONNECT]); + *h = ngx_rtmp_codec_disconnect; + + /* register metadata handler */ + ch = ngx_array_push(&cmcf->amf); + if (ch == NULL) { + return NGX_ERROR; + } + ngx_str_set(&ch->name, "@setDataFrame"); + ch->handler = ngx_rtmp_codec_meta_data; + + ch = ngx_array_push(&cmcf->amf); + if (ch == NULL) { + return NGX_ERROR; + } + ngx_str_set(&ch->name, "onMetaData"); + ch->handler = ngx_rtmp_codec_meta_data; + + + return NGX_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_codec_module.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_codec_module.h new file mode 100644 index 0000000000..961d9cd91b --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_codec_module.h @@ -0,0 +1,85 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#ifndef _NGX_RTMP_CODEC_H_INCLUDED_ +#define _NGX_RTMP_CODEC_H_INCLUDED_ + + +#include +#include +#include "ngx_rtmp.h" + + +/* hevc nal type */ +enum { + NAL_TRAIL_N = 0, + NAL_TRAIL_R = 1, + NAL_TSA_N = 2, + NAL_TSA_R = 3, + NAL_STSA_N = 4, + NAL_STSA_R = 5, + NAL_RADL_N = 6, + NAL_RADL_R = 7, + NAL_RASL_N = 8, + NAL_RASL_R = 9, + NAL_BLA_W_LP = 16, + NAL_BLA_W_RADL = 17, + NAL_BLA_N_LP = 18, + NAL_IDR_W_RADL = 19, + NAL_IDR_N_LP = 20, + NAL_CRA_NUT = 21, + NAL_VPS = 32, + NAL_SPS = 33, + NAL_PPS = 34, + NAL_AUD = 35, + NAL_EOS_NUT = 36, + NAL_EOB_NUT = 37, + NAL_FD_NUT = 38, + NAL_SEI_PREFIX = 39, + NAL_SEI_SUFFIX = 40, +}; + + +u_char * ngx_rtmp_get_audio_codec_name(ngx_uint_t id); +u_char * ngx_rtmp_get_video_codec_name(ngx_uint_t id); + + +typedef struct { + ngx_uint_t width; + ngx_uint_t height; + ngx_uint_t duration; + double frame_rate; + ngx_uint_t video_data_rate; + ngx_uint_t video_codec_id; + ngx_uint_t audio_data_rate; + ngx_uint_t audio_codec_id; + ngx_uint_t aac_profile; + ngx_uint_t aac_chan_conf; + ngx_uint_t aac_sbr; + ngx_uint_t aac_ps; + ngx_uint_t avc_profile; /* avc or hevc */ + ngx_uint_t avc_compat; /* avc or hevc */ + ngx_uint_t avc_level; /* avc or hevc */ + ngx_uint_t avc_nal_bytes; /* avc or hevc */ + ngx_uint_t avc_ref_frames; /* avc or hevc */ + ngx_uint_t sample_rate; /* 5512, 11025, 22050, 44100 */ + ngx_uint_t sample_size; /* 1=8bit, 2=16bit */ + ngx_uint_t audio_channels; /* 1, 2 */ + u_char profile[32]; + u_char level[32]; + + ngx_rtmp_frame_t *avc_header; /* avc or hevc */ + ngx_rtmp_frame_t *aac_header; + + ngx_rtmp_frame_t *meta; + ngx_uint_t meta_version; +} ngx_rtmp_codec_ctx_t; + + +extern ngx_module_t ngx_rtmp_codec_module; + + +#endif /* _NGX_RTMP_LIVE_H_INCLUDED_ */ diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_control_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_control_module.c new file mode 100644 index 0000000000..4966ec5742 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_control_module.c @@ -0,0 +1,922 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include +#include "ngx_rtmp.h" +#include "ngx_live.h" +#include "ngx_rtmp_live_module.h" +#include "ngx_live_record.h" +#include "ngx_rtmp_record_module.h" +#include "ngx_rtmp_dynamic.h" + + +static char *ngx_rtmp_control(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static void *ngx_rtmp_control_create_loc_conf(ngx_conf_t *cf); +static char *ngx_rtmp_control_merge_loc_conf(ngx_conf_t *cf, + void *parent, void *child); + + +typedef const char * (*ngx_rtmp_control_handler_t)(ngx_http_request_t *r, + ngx_rtmp_session_t *); + + +#define NGX_RTMP_CONTROL_ALL 0xff +#define NGX_RTMP_CONTROL_RECORD 0x01 +#define NGX_RTMP_CONTROL_DROP 0x02 +#define NGX_RTMP_CONTROL_REDIRECT 0x04 +#define NGX_RTMP_CONTROL_PAUSE 0x08 +#define NGX_RTMP_CONTROL_RESUME 0x10 + + +enum { + NGX_RTMP_CONTROL_FILTER_CLIENT = 0, + NGX_RTMP_CONTROL_FILTER_PUBLISHER, + NGX_RTMP_CONTROL_FILTER_SUBSCRIBER +}; + + +typedef struct { + ngx_uint_t count; + ngx_uint_t filter; + ngx_str_t method; + ngx_str_t path; + ngx_array_t sessions; /* ngx_rtmp_session_t * */ +} ngx_rtmp_control_ctx_t; + + +typedef struct { + ngx_uint_t control; +} ngx_rtmp_control_loc_conf_t; + + +static ngx_conf_bitmask_t ngx_rtmp_control_masks[] = { + { ngx_string("all"), NGX_RTMP_CONTROL_ALL }, + { ngx_string("record"), NGX_RTMP_CONTROL_RECORD }, + { ngx_string("drop"), NGX_RTMP_CONTROL_DROP }, + { ngx_string("redirect"), NGX_RTMP_CONTROL_REDIRECT }, + { ngx_string("pause"), NGX_RTMP_CONTROL_PAUSE }, + { ngx_string("resume"), NGX_RTMP_CONTROL_RESUME }, + { ngx_null_string, 0 } +}; + + +static ngx_command_t ngx_rtmp_control_commands[] = { + + { ngx_string("rtmp_control"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, + ngx_rtmp_control, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_rtmp_control_loc_conf_t, control), + ngx_rtmp_control_masks }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_rtmp_control_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + ngx_rtmp_control_create_loc_conf, /* create location configuration */ + ngx_rtmp_control_merge_loc_conf, /* merge location configuration */ +}; + + +ngx_module_t ngx_rtmp_control_module = { + NGX_MODULE_V1, + &ngx_rtmp_control_module_ctx, /* module context */ + ngx_rtmp_control_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static const char * +ngx_rtmp_control_record_handler(ngx_http_request_t *r, ngx_rtmp_session_t *s) +{ + ngx_rtmp_control_ctx_t *ctx; + ngx_uint_t rn; + ngx_str_t rec = ngx_null_string; + ngx_rtmp_core_app_conf_t *cacf; + ngx_rtmp_record_app_conf_t *racf; + + ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module); + cacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_core_module); + racf = cacf->app_conf[ngx_rtmp_record_module.ctx_index]; + + if (ngx_http_arg(r, (u_char *) "rec", sizeof("rec") - 1, &rec) != NGX_OK) { + rec.len = 0; + } + + rn = ngx_rtmp_record_find(racf, &rec); + if (rn == NGX_CONF_UNSET_UINT) { + return "Recorder not found"; + } + + if (ctx->method.len == sizeof("start") - 1 && + ngx_strncmp(ctx->method.data, "start", ctx->method.len) == 0) + { + ngx_rtmp_record_open(s, rn, &ctx->path); + + return ngx_live_record_open(s); + + } else if (ctx->method.len == sizeof("stop") - 1 && + ngx_strncmp(ctx->method.data, "stop", ctx->method.len) == 0) + { + ngx_rtmp_record_close(s, rn, &ctx->path); + + return ngx_live_record_close(s); + + } else { + return "Undefined method"; + } +} + + +static const char * +ngx_rtmp_control_drop_handler(ngx_http_request_t *r, ngx_rtmp_session_t *s) +{ + ngx_rtmp_control_ctx_t *ctx; + + ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module); + + s->finalize_reason = NGX_LIVE_CONTROL_DROP; + ngx_rtmp_finalize_session(s); + + ++ctx->count; + + return NGX_CONF_OK; +} + + +static const char * +ngx_rtmp_control_redirect_handler(ngx_http_request_t *r, ngx_rtmp_session_t *s) +{ + ngx_str_t name; + ngx_rtmp_play_t vplay; + ngx_rtmp_publish_t vpublish; + ngx_rtmp_live_ctx_t *lctx; + ngx_rtmp_control_ctx_t *ctx; + ngx_rtmp_close_stream_t vc; + + if (ngx_http_arg(r, (u_char *) "newname", sizeof("newname") - 1, &name) + != NGX_OK) + { + return "newname not specified"; + } + + if (name.len >= NGX_RTMP_MAX_NAME) { + name.len = NGX_RTMP_MAX_NAME - 1; + } + + ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module); + ctx->count++; + + ngx_memzero(&vc, sizeof(ngx_rtmp_close_stream_t)); + + /* close_stream should be synchronous */ + ngx_rtmp_close_stream_filter(s, &vc); + + lctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module); + + if (lctx && lctx->publishing) { + /* publish */ + + ngx_memzero(&vpublish, sizeof(ngx_rtmp_publish_t)); + + ngx_memcpy(vpublish.name, name.data, name.len); + + ngx_rtmp_cmd_fill_args(vpublish.name, vpublish.args); + + if (ngx_rtmp_publish_filter(s, &vpublish) != NGX_OK) { + return "publish failed"; + } + + } else { + /* play */ + + ngx_memzero(&vplay, sizeof(ngx_rtmp_play_t)); + + ngx_memcpy(vplay.name, name.data, name.len); + + ngx_rtmp_cmd_fill_args(vplay.name, vplay.args); + + if (ngx_rtmp_play_filter(s, &vplay) != NGX_OK) { + return "play failed"; + } + } + + return NGX_CONF_OK; +} + +static const char * +ngx_rtmp_control_pause_handler(ngx_http_request_t *r, ngx_rtmp_session_t *s) +{ + ngx_rtmp_control_ctx_t *ctx; + + ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module); + + s->pause = 1; + + ++ctx->count; + + return NGX_CONF_OK; +} + +static const char * +ngx_rtmp_control_resume_handler(ngx_http_request_t *r, ngx_rtmp_session_t *s) +{ + ngx_rtmp_control_ctx_t *ctx; + + ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module); + + if (s->pause == 1) { + s->pause = 2; + } + + ++ctx->count; + + return NGX_CONF_OK; +} + +static const char * +ngx_rtmp_control_walk_session(ngx_http_request_t *r, ngx_rtmp_core_ctx_t *cctx) +{ + ngx_str_t addr, *paddr, clientid; + ngx_rtmp_session_t *s, **ss; + ngx_rtmp_control_ctx_t *ctx; + + s = cctx->session; + + if (s == NULL) { + return NGX_CONF_OK; + } + + if (ngx_http_arg(r, (u_char *) "addr", sizeof("addr") - 1, &addr) + == NGX_OK) + { + paddr = s->addr_text; + if (paddr->len != addr.len || + ngx_strncmp(paddr->data, addr.data, addr.len)) + { + return NGX_CONF_OK; + } + } + + if (ngx_http_arg(r, (u_char *) "clientid", sizeof("clientid") - 1, + &clientid) + == NGX_OK) + { + if (s->number != + (ngx_uint_t) ngx_atoi(clientid.data, clientid.len)) + { + return NGX_CONF_OK; + } + } + + ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module); + + switch (ctx->filter) { + case NGX_RTMP_CONTROL_FILTER_PUBLISHER: + if (!cctx->publishing) { + return NGX_CONF_OK; + } + break; + + case NGX_RTMP_CONTROL_FILTER_SUBSCRIBER: + if (cctx->publishing) { + return NGX_CONF_OK; + } + break; + + case NGX_RTMP_CONTROL_FILTER_CLIENT: + break; + } + + ss = ngx_array_push(&ctx->sessions); + if (ss == NULL) { + return "allocation error"; + } + + *ss = s; + + return NGX_CONF_OK; +} + + +static const char * +ngx_rtmp_control_walk_stream(ngx_http_request_t *r, ngx_live_stream_t *st) +{ + const char *s; + ngx_rtmp_core_ctx_t *ctx; + + for (ctx = st->play_ctx; ctx; ctx = ctx->next) { + s = ngx_rtmp_control_walk_session(r, ctx); + if (s != NGX_CONF_OK) { + return s; + } + } + + for (ctx = st->publish_ctx; ctx; ctx = ctx->next) { + s = ngx_rtmp_control_walk_session(r, ctx); + if (s != NGX_CONF_OK) { + return s; + } + } + + return NGX_CONF_OK; +} + + +static const char * +ngx_rtmp_control_walk_server(ngx_http_request_t *r, ngx_live_server_t *srv) +{ + ngx_live_conf_t *lcf; + ngx_live_stream_t *st; + ngx_str_t serverid, app, name, stream; + size_t n; + const char *s; + u_char *p; + + lcf = (ngx_live_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_live_module); + + if (ngx_http_arg(r, (u_char *) "app", sizeof("app") - 1, &app) != NGX_OK) { + app.len = 0; + } + + if (app.len == 0) { + for (n = 0; n < lcf->stream_buckets; ++n) { + for (st = srv->streams[n]; st; st = st->next) { + s = ngx_rtmp_control_walk_stream(r, st); + if (s != NGX_CONF_OK) { + return s; + } + } + } + + return NGX_CONF_OK; + } + + if (ngx_http_arg(r, (u_char *) "name", sizeof("name") - 1, &name) + != NGX_OK) + { + name.len = 0; + } + + serverid.data = srv->serverid; + serverid.len = ngx_strlen(srv->serverid); + + /* serverid/app/name */ + stream.len = serverid.len + 1 + app.len + 1 + name.len; + stream.data = ngx_pcalloc(r->pool, stream.len); + p = stream.data; + p = ngx_copy(p, serverid.data, serverid.len); + *p++ = '/'; + p = ngx_copy(p, app.data, app.len); + *p++ = '/'; + p = ngx_copy(p, name.data, name.len); + + if (name.len == 0) { + for (n = 0; n < lcf->stream_buckets; ++n) { + for (st = srv->streams[n]; st; st = st->next) { + if (ngx_memcmp(stream.data, st->name, stream.len) == 0) { + s = ngx_rtmp_control_walk_stream(r, st); + if (s != NGX_CONF_OK) { + return s; + } + } + } + } + } else { + st = ngx_live_fetch_stream(&serverid, &stream); + if (st) { + s = ngx_rtmp_control_walk_stream(r, st); + if (s != NGX_CONF_OK) { + return s; + } + } + } + + return NGX_CONF_OK; +} + + +static const char * +ngx_rtmp_control_walk(ngx_http_request_t *r, ngx_rtmp_control_handler_t h) +{ + ngx_live_server_t *server; + ngx_str_t srv; + ngx_str_t serverid = ngx_null_string; + ngx_uint_t n; + const char *msg; + ngx_rtmp_session_t **s; + ngx_rtmp_control_ctx_t *ctx; + + if (ngx_http_arg(r, (u_char *) "srv", sizeof("srv") - 1, &srv) != NGX_OK) { + return "Server not set"; + } + + ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module); + + serverid = srv; + + if (!serverid.len) { + ngx_rmtp_get_serverid_by_domain(&serverid, &srv); + } + + server = ngx_live_fetch_server(&serverid); + if (server == NULL) { + return NGX_CONF_OK; + } + + msg = ngx_rtmp_control_walk_server(r, server); + if (msg != NGX_CONF_OK) { + return msg; + } + + s = ctx->sessions.elts; + for (n = 0; n < ctx->sessions.nelts; n++) { + msg = h(r, s[n]); + if (msg != NGX_CONF_OK) { + return msg; + } + } + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_rtmp_control_record(ngx_http_request_t *r, ngx_str_t *method) +{ + ngx_buf_t *b; + const char *msg; + ngx_chain_t cl; + ngx_rtmp_control_ctx_t *ctx; + size_t len; + + ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module); + ctx->filter = NGX_RTMP_CONTROL_FILTER_PUBLISHER; + + msg = ngx_rtmp_control_walk(r, ngx_rtmp_control_record_handler); + if (msg != NGX_CONF_OK) { + goto error; + } + + /* output record path */ + + len = sizeof("ok\n") - 1; + + r->headers_out.status = NGX_HTTP_OK; + r->headers_out.content_length_n = len; + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + goto error; + } + + ngx_memzero(&cl, sizeof(cl)); + cl.buf = b; + + b->last = ngx_cpymem(b->pos, "ok\n", len); + b->last_buf = 1; + + ngx_http_send_header(r); + + return ngx_http_output_filter(r, &cl); + +error: + return NGX_HTTP_INTERNAL_SERVER_ERROR; +} + + +static ngx_int_t +ngx_rtmp_control_drop(ngx_http_request_t *r, ngx_str_t *method) +{ + size_t len; + u_char *p; + ngx_buf_t *b; + ngx_chain_t cl; + const char *msg; + ngx_rtmp_control_ctx_t *ctx; + + ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module); + + if (ctx->method.len == sizeof("publisher") - 1 && + ngx_memcmp(ctx->method.data, "publisher", ctx->method.len) == 0) + { + ctx->filter = NGX_RTMP_CONTROL_FILTER_PUBLISHER; + + } else if (ctx->method.len == sizeof("subscriber") - 1 && + ngx_memcmp(ctx->method.data, "subscriber", ctx->method.len) + == 0) + { + ctx->filter = NGX_RTMP_CONTROL_FILTER_SUBSCRIBER; + + } else if (method->len == sizeof("client") - 1 && + ngx_memcmp(ctx->method.data, "client", ctx->method.len) == 0) + { + ctx->filter = NGX_RTMP_CONTROL_FILTER_CLIENT; + + } else { + msg = "Undefined filter"; + goto error; + } + + msg = ngx_rtmp_control_walk(r, ngx_rtmp_control_drop_handler); + if (msg != NGX_CONF_OK) { + goto error; + } + + /* output count */ + + len = NGX_INT_T_LEN; + + p = ngx_palloc(r->connection->pool, len); + if (p == NULL) { + return NGX_ERROR; + } + + len = (size_t) (ngx_snprintf(p, len, "%ui", ctx->count) - p); + + r->headers_out.status = NGX_HTTP_OK; + r->headers_out.content_length_n = len; + + b = ngx_calloc_buf(r->pool); + if (b == NULL) { + goto error; + } + + b->start = b->pos = p; + b->end = b->last = p + len; + b->temporary = 1; + b->last_buf = 1; + + ngx_memzero(&cl, sizeof(cl)); + cl.buf = b; + + ngx_http_send_header(r); + + return ngx_http_output_filter(r, &cl); + +error: + return NGX_HTTP_INTERNAL_SERVER_ERROR; +} + + +static ngx_int_t +ngx_rtmp_control_redirect(ngx_http_request_t *r, ngx_str_t *method) +{ + size_t len; + u_char *p; + ngx_buf_t *b; + ngx_chain_t cl; + const char *msg; + ngx_rtmp_control_ctx_t *ctx; + + ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module); + + if (ctx->method.len == sizeof("publisher") - 1 && + ngx_memcmp(ctx->method.data, "publisher", ctx->method.len) == 0) + { + ctx->filter = NGX_RTMP_CONTROL_FILTER_PUBLISHER; + + } else if (ctx->method.len == sizeof("subscriber") - 1 && + ngx_memcmp(ctx->method.data, "subscriber", ctx->method.len) + == 0) + { + ctx->filter = NGX_RTMP_CONTROL_FILTER_SUBSCRIBER; + + } else if (ctx->method.len == sizeof("client") - 1 && + ngx_memcmp(ctx->method.data, "client", ctx->method.len) == 0) + { + ctx->filter = NGX_RTMP_CONTROL_FILTER_CLIENT; + + } else { + msg = "Undefined filter"; + goto error; + } + + msg = ngx_rtmp_control_walk(r, ngx_rtmp_control_redirect_handler); + if (msg != NGX_CONF_OK) { + goto error; + } + + /* output count */ + + len = NGX_INT_T_LEN; + + p = ngx_pcalloc(r->connection->pool, len); + if (p == NULL) { + msg = "ngx_palloc failed"; + goto error; + } + + len = (size_t) (ngx_snprintf(p, len, "%ui", ctx->count) - p); + + r->headers_out.status = NGX_HTTP_OK; + r->headers_out.content_length_n = len; + + b = ngx_calloc_buf(r->pool); + if (b == NULL) { + msg = "calloc buf failed"; + goto error; + } + + b->start = b->pos = p; + b->end = b->last = p + len; + b->temporary = 1; + b->last_buf = 1; + + ngx_memzero(&cl, sizeof(cl)); + cl.buf = b; + + ngx_http_send_header(r); + + return ngx_http_output_filter(r, &cl); + +error: + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "rtmp-control: redirect| %s", msg); + return NGX_HTTP_INTERNAL_SERVER_ERROR; +} + + +static ngx_int_t +ngx_rtmp_control_pause(ngx_http_request_t *r, ngx_str_t *method) +{ + size_t len; + u_char *p; + ngx_buf_t *b; + ngx_chain_t cl; + const char *msg; + ngx_rtmp_control_ctx_t *ctx; + + ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module); + + if (ctx->method.len == sizeof("publisher") - 1 && + ngx_memcmp(ctx->method.data, "publisher", ctx->method.len) == 0) + { + ctx->filter = NGX_RTMP_CONTROL_FILTER_PUBLISHER; + + } else if (ctx->method.len == sizeof("subscriber") - 1 && + ngx_memcmp(ctx->method.data, "subscriber", ctx->method.len) + == 0) + { + ctx->filter = NGX_RTMP_CONTROL_FILTER_SUBSCRIBER; + + } else if (method->len == sizeof("client") - 1 && + ngx_memcmp(ctx->method.data, "client", ctx->method.len) == 0) + { + ctx->filter = NGX_RTMP_CONTROL_FILTER_CLIENT; + + } else { + msg = "Undefined filter"; + goto error; + } + + msg = ngx_rtmp_control_walk(r, ngx_rtmp_control_pause_handler); + if (msg != NGX_CONF_OK) { + goto error; + } + + /* output count */ + + len = NGX_INT_T_LEN; + + p = ngx_palloc(r->connection->pool, len); + if (p == NULL) { + return NGX_ERROR; + } + + len = (size_t) (ngx_snprintf(p, len, "%ui", ctx->count) - p); + + r->headers_out.status = NGX_HTTP_OK; + r->headers_out.content_length_n = len; + + b = ngx_calloc_buf(r->pool); + if (b == NULL) { + goto error; + } + + b->start = b->pos = p; + b->end = b->last = p + len; + b->temporary = 1; + b->last_buf = 1; + + ngx_memzero(&cl, sizeof(cl)); + cl.buf = b; + + ngx_http_send_header(r); + + return ngx_http_output_filter(r, &cl); + +error: + return NGX_HTTP_INTERNAL_SERVER_ERROR; +} + + +static ngx_int_t +ngx_rtmp_control_resume(ngx_http_request_t *r, ngx_str_t *method) +{ + size_t len; + u_char *p; + ngx_buf_t *b; + ngx_chain_t cl; + const char *msg; + ngx_rtmp_control_ctx_t *ctx; + + ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module); + + if (ctx->method.len == sizeof("publisher") - 1 && + ngx_memcmp(ctx->method.data, "publisher", ctx->method.len) == 0) + { + ctx->filter = NGX_RTMP_CONTROL_FILTER_PUBLISHER; + + } else if (ctx->method.len == sizeof("subscriber") - 1 && + ngx_memcmp(ctx->method.data, "subscriber", ctx->method.len) + == 0) + { + ctx->filter = NGX_RTMP_CONTROL_FILTER_SUBSCRIBER; + + } else if (method->len == sizeof("client") - 1 && + ngx_memcmp(ctx->method.data, "client", ctx->method.len) == 0) + { + ctx->filter = NGX_RTMP_CONTROL_FILTER_CLIENT; + + } else { + msg = "Undefined filter"; + goto error; + } + + msg = ngx_rtmp_control_walk(r, ngx_rtmp_control_resume_handler); + if (msg != NGX_CONF_OK) { + goto error; + } + + /* output count */ + + len = NGX_INT_T_LEN; + + p = ngx_palloc(r->connection->pool, len); + if (p == NULL) { + return NGX_ERROR; + } + + len = (size_t) (ngx_snprintf(p, len, "%ui", ctx->count) - p); + + r->headers_out.status = NGX_HTTP_OK; + r->headers_out.content_length_n = len; + + b = ngx_calloc_buf(r->pool); + if (b == NULL) { + goto error; + } + + b->start = b->pos = p; + b->end = b->last = p + len; + b->temporary = 1; + b->last_buf = 1; + + ngx_memzero(&cl, sizeof(cl)); + cl.buf = b; + + ngx_http_send_header(r); + + return ngx_http_output_filter(r, &cl); + +error: + return NGX_HTTP_INTERNAL_SERVER_ERROR; +} + + +static ngx_int_t +ngx_rtmp_control_handler(ngx_http_request_t *r) +{ + u_char *p; + ngx_str_t section, method; + ngx_uint_t n; + ngx_rtmp_control_ctx_t *ctx; + ngx_rtmp_control_loc_conf_t *llcf; + + llcf = ngx_http_get_module_loc_conf(r, ngx_rtmp_control_module); + if (llcf->control == 0) { + return NGX_DECLINED; + } + + /* uri format: .../section/method?args */ + + ngx_str_null(§ion); + ngx_str_null(&method); + + for (n = r->uri.len; n; --n) { + p = &r->uri.data[n - 1]; + + if (*p != '/') { + continue; + } + + if (method.data) { + section.data = p + 1; + section.len = method.data - section.data - 1; + break; + } + + method.data = p + 1; + method.len = r->uri.data + r->uri.len - method.data; + } + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "rtmp_control: section='%V' method='%V'", + §ion, &method); + + ctx = ngx_pcalloc(r->pool, sizeof(ngx_rtmp_control_ctx_t)); + if (ctx == NULL) { + return NGX_ERROR; + } + + ngx_http_set_ctx(r, ctx, ngx_rtmp_control_module); + + if (ngx_array_init(&ctx->sessions, r->pool, 1, sizeof(void *)) != NGX_OK) { + return NGX_ERROR; + } + + ctx->method = method; + +#define NGX_RTMP_CONTROL_SECTION(flag, secname) \ + if (llcf->control & NGX_RTMP_CONTROL_##flag && \ + section.len == sizeof(#secname) - 1 && \ + ngx_strncmp(section.data, #secname, sizeof(#secname) - 1) == 0) \ + { \ + return ngx_rtmp_control_##secname(r, &method); \ + } + + NGX_RTMP_CONTROL_SECTION(RECORD, record); + NGX_RTMP_CONTROL_SECTION(DROP, drop); + NGX_RTMP_CONTROL_SECTION(REDIRECT, redirect); + NGX_RTMP_CONTROL_SECTION(PAUSE, pause); + NGX_RTMP_CONTROL_SECTION(RESUME, resume); + +#undef NGX_RTMP_CONTROL_SECTION + + return NGX_DECLINED; +} + + +static void * +ngx_rtmp_control_create_loc_conf(ngx_conf_t *cf) +{ + ngx_rtmp_control_loc_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_control_loc_conf_t)); + if (conf == NULL) { + return NULL; + } + + conf->control = 0; + + return conf; +} + + +static char * +ngx_rtmp_control_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_rtmp_control_loc_conf_t *prev = parent; + ngx_rtmp_control_loc_conf_t *conf = child; + + ngx_conf_merge_bitmask_value(conf->control, prev->control, 0); + + return NGX_CONF_OK; +} + + +static char * +ngx_rtmp_control(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_core_loc_conf_t *clcf; + + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + clcf->handler = ngx_rtmp_control_handler; + + return ngx_conf_set_bitmask_slot(cf, cmd, conf); +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_core_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_core_module.c new file mode 100644 index 0000000000..3396adf0c4 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_core_module.c @@ -0,0 +1,1203 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include +#include +#include "ngx_rtmp.h" +#include "ngx_dynamic_conf.h" +#include "ngx_rtmp_dynamic.h" +#include "ngx_rtmp_variables.h" + + +static ngx_int_t ngx_rtmp_core_preconfiguration(ngx_conf_t *cf); + +static void *ngx_rtmp_core_create_main_conf(ngx_conf_t *cf); +static char *ngx_rtmp_core_init_main_conf(ngx_conf_t *cf, void *conf); +static void *ngx_rtmp_core_create_srv_conf(ngx_conf_t *cf); +static char *ngx_rtmp_core_merge_srv_conf(ngx_conf_t *cf, void *parent, + void *child); +static void *ngx_rtmp_core_create_app_conf(ngx_conf_t *cf); +static char *ngx_rtmp_core_merge_app_conf(ngx_conf_t *cf, void *parent, + void *child); +static char *ngx_rtmp_core_server(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char *ngx_rtmp_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char *ngx_rtmp_core_server_name(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char *ngx_rtmp_core_application(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + +static void *ngx_rtmp_core_create_srv_dconf(ngx_conf_t *cf); +static char *ngx_rtmp_core_init_srv_dconf(ngx_conf_t *cf, void *conf); + +static char *ngx_rtmp_merge_frame(ngx_conf_t *cf, void *post, void *data); + +static ngx_conf_post_handler_pt ngx_rtmp_merge_frame_p = + ngx_rtmp_merge_frame; + +ngx_rtmp_core_main_conf_t *ngx_rtmp_core_main_conf; + + +static ngx_conf_deprecated_t ngx_conf_deprecated_so_keepalive = { + ngx_conf_deprecated, "so_keepalive", + "so_keepalive\" parameter of the \"listen" +}; + + +static ngx_command_t ngx_rtmp_core_commands[] = { + + { ngx_string("server_names_hash_max_size"), + NGX_RTMP_MAIN_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_RTMP_MAIN_CONF_OFFSET, + offsetof(ngx_rtmp_core_main_conf_t, server_names_hash_max_size), + NULL }, + + { ngx_string("server_names_hash_bucket_size"), + NGX_RTMP_MAIN_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_RTMP_MAIN_CONF_OFFSET, + offsetof(ngx_rtmp_core_main_conf_t, server_names_hash_bucket_size), + NULL }, + + { ngx_string("fast_reload"), + NGX_RTMP_MAIN_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_MAIN_CONF_OFFSET, + offsetof(ngx_rtmp_core_main_conf_t, fast_reload), + NULL }, + + { ngx_string("server"), + NGX_RTMP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, + ngx_rtmp_core_server, + 0, + 0, + NULL }, + + { ngx_string("listen"), + NGX_RTMP_SRV_CONF|NGX_CONF_TAKE12, + ngx_rtmp_core_listen, + NGX_RTMP_SRV_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("server_name"), + NGX_RTMP_SRV_CONF|NGX_CONF_1MORE, + ngx_rtmp_core_server_name, + NGX_RTMP_SRV_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("application"), + NGX_RTMP_SRV_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE1, + ngx_rtmp_core_application, + NGX_RTMP_SRV_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("so_keepalive"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_RTMP_SRV_CONF_OFFSET, + offsetof(ngx_rtmp_core_srv_conf_t, so_keepalive), + &ngx_conf_deprecated_so_keepalive }, + + { ngx_string("timeout"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_SRV_CONF_OFFSET, + offsetof(ngx_rtmp_core_srv_conf_t, timeout), + NULL }, + + { ngx_string("ping"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_SRV_CONF_OFFSET, + offsetof(ngx_rtmp_core_srv_conf_t, ping), + NULL }, + + { ngx_string("ping_timeout"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_SRV_CONF_OFFSET, + offsetof(ngx_rtmp_core_srv_conf_t, ping_timeout), + NULL }, + + { ngx_string("max_streams"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_RTMP_SRV_CONF_OFFSET, + offsetof(ngx_rtmp_core_srv_conf_t, max_streams), + NULL }, + + { ngx_string("ack_window"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_RTMP_SRV_CONF_OFFSET, + offsetof(ngx_rtmp_core_srv_conf_t, ack_window), + NULL }, + + { ngx_string("chunk_size"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_RTMP_SRV_CONF_OFFSET, + offsetof(ngx_rtmp_core_srv_conf_t, chunk_size), + NULL }, + + { ngx_string("max_message"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_RTMP_SRV_CONF_OFFSET, + offsetof(ngx_rtmp_core_srv_conf_t, max_message), + NULL }, + + { ngx_string("out_queue"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_RTMP_SRV_CONF_OFFSET, + offsetof(ngx_rtmp_core_srv_conf_t, out_queue), + NULL }, + + { ngx_string("merge_frame"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_core_app_conf_t, merge_frame), + &ngx_rtmp_merge_frame_p }, + + { ngx_string("tcp_nodelay"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_core_app_conf_t, tcp_nodelay), + NULL }, + + { ngx_string("out_cork"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_RTMP_SRV_CONF_OFFSET, + offsetof(ngx_rtmp_core_srv_conf_t, out_cork), + NULL }, + + { ngx_string("busy"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_SRV_CONF_OFFSET, + offsetof(ngx_rtmp_core_srv_conf_t, busy), + NULL }, + + /* time fixes are needed for flash clients */ + { ngx_string("play_time_fix"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_SRV_CONF_OFFSET, + offsetof(ngx_rtmp_core_srv_conf_t, play_time_fix), + NULL }, + + { ngx_string("publish_time_fix"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_SRV_CONF_OFFSET, + offsetof(ngx_rtmp_core_srv_conf_t, publish_time_fix), + NULL }, + + { ngx_string("buflen"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_SRV_CONF_OFFSET, + offsetof(ngx_rtmp_core_srv_conf_t, buflen), + NULL }, + + { ngx_string("serverid"), + NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_RTMP_SRV_CONF_OFFSET, + offsetof(ngx_rtmp_core_srv_conf_t, serverid), + NULL }, + + { ngx_string("hevc_codecid"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_core_app_conf_t, hevc_codec), + NULL }, + + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_rtmp_core_module_ctx = { + ngx_rtmp_core_preconfiguration, /* preconfiguration */ + NULL, /* postconfiguration */ + ngx_rtmp_core_create_main_conf, /* create main configuration */ + ngx_rtmp_core_init_main_conf, /* init main configuration */ + ngx_rtmp_core_create_srv_conf, /* create server configuration */ + ngx_rtmp_core_merge_srv_conf, /* merge server configuration */ + ngx_rtmp_core_create_app_conf, /* create app configuration */ + ngx_rtmp_core_merge_app_conf /* merge app configuration */ +}; + +/* nginx rtmp dynamic */ +static ngx_command_t ngx_rtmp_core_dcommands[] = { + + { ngx_string("serverid"), + NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_RTMP_SRV_CONF_OFFSET, + offsetof(ngx_rtmp_core_srv_dconf_t, serverid), + NULL }, + + ngx_null_command +}; + +static ngx_rtmp_dynamic_module_t ngx_rtmp_core_module_dctx = { + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + ngx_rtmp_core_create_srv_dconf, /* create server configuration */ + ngx_rtmp_core_init_srv_dconf, /* merge server configuration */ + + NULL, /* create app configuration */ + NULL /* merge app configuration */ +}; + + +ngx_module_t ngx_rtmp_core_module = { + NGX_MODULE_V1, + &ngx_rtmp_core_module_ctx, /* module context */ + ngx_rtmp_core_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + (uintptr_t) &ngx_rtmp_core_module_dctx, /* module dynamic context */ + (uintptr_t) ngx_rtmp_core_dcommands, /* module dynamic directives */ + NGX_MODULE_V1_DYNAMIC_PADDING +}; + + +static void * +ngx_rtmp_core_create_main_conf(ngx_conf_t *cf) +{ + ngx_rtmp_core_main_conf_t *cmcf; + + cmcf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_core_main_conf_t)); + if (cmcf == NULL) { + return NULL; + } + + ngx_rtmp_core_main_conf = cmcf; + + if (ngx_array_init(&cmcf->servers, cf->pool, 4, + sizeof(ngx_rtmp_core_srv_conf_t *)) + != NGX_OK) + { + return NULL; + } + + cmcf->server_names_hash_max_size = NGX_CONF_UNSET_UINT; + cmcf->server_names_hash_bucket_size = NGX_CONF_UNSET_UINT; + cmcf->fast_reload = NGX_CONF_UNSET; + cmcf->variables_hash_max_size = 1024; + cmcf->variables_hash_bucket_size = 64; + + return cmcf; +} + + +static char * +ngx_rtmp_core_init_main_conf(ngx_conf_t *cf, void *conf) +{ + ngx_rtmp_core_main_conf_t *cmcf; + + cmcf = conf; + + ngx_conf_init_uint_value(cmcf->server_names_hash_max_size, 512); + ngx_conf_init_uint_value(cmcf->server_names_hash_bucket_size, + ngx_cacheline_size); + ngx_conf_init_value(cmcf->fast_reload, 0); + + return NGX_CONF_OK; +} + + +static void * +ngx_rtmp_core_create_srv_conf(ngx_conf_t *cf) +{ + ngx_rtmp_core_srv_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_core_srv_conf_t)); + if (conf == NULL) { + return NULL; + } + + if (ngx_array_init(&conf->applications, cf->pool, 4, + sizeof(ngx_rtmp_core_app_conf_t *)) + != NGX_OK) + { + return NULL; + } + + if (ngx_array_init(&conf->server_names, cf->temp_pool, 4, + sizeof(ngx_rtmp_server_name_t)) + != NGX_OK) + { + return NULL; + } + + conf->timeout = NGX_CONF_UNSET_MSEC; + conf->ping = NGX_CONF_UNSET_MSEC; + conf->ping_timeout = NGX_CONF_UNSET_MSEC; + conf->so_keepalive = NGX_CONF_UNSET; + conf->max_streams = NGX_CONF_UNSET; + conf->chunk_size = NGX_CONF_UNSET; + conf->ack_window = NGX_CONF_UNSET_UINT; + conf->max_message = NGX_CONF_UNSET_SIZE; + conf->out_queue = NGX_CONF_UNSET_SIZE; + conf->out_cork = NGX_CONF_UNSET_SIZE; + conf->play_time_fix = NGX_CONF_UNSET; + conf->publish_time_fix = NGX_CONF_UNSET; + conf->buflen = NGX_CONF_UNSET_MSEC; + conf->busy = NGX_CONF_UNSET; + + return conf; +} + + +static char * +ngx_rtmp_core_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_rtmp_core_srv_conf_t *prev = parent; + ngx_rtmp_core_srv_conf_t *conf = child; + + ngx_str_t name; + ngx_rtmp_server_name_t *sn; + + ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 60000); + ngx_conf_merge_msec_value(conf->ping, prev->ping, 60000); + ngx_conf_merge_msec_value(conf->ping_timeout, prev->ping_timeout, 30000); + + ngx_conf_merge_value(conf->so_keepalive, prev->so_keepalive, 0); + ngx_conf_merge_value(conf->max_streams, prev->max_streams, 32); + ngx_conf_merge_value(conf->chunk_size, prev->chunk_size, 4096); + ngx_conf_merge_uint_value(conf->ack_window, prev->ack_window, 5000000); + ngx_conf_merge_size_value(conf->max_message, prev->max_message, + 1 * 1024 * 1024); + ngx_conf_merge_size_value(conf->out_queue, prev->out_queue, 2048); + ngx_conf_merge_size_value(conf->out_cork, prev->out_cork, + conf->out_queue / 8); + ngx_conf_merge_value(conf->play_time_fix, prev->play_time_fix, 1); + ngx_conf_merge_value(conf->publish_time_fix, prev->publish_time_fix, 1); + ngx_conf_merge_msec_value(conf->buflen, prev->buflen, 1000); + ngx_conf_merge_value(conf->busy, prev->busy, 0); + ngx_conf_merge_str_value(conf->serverid, prev->serverid, "default"); + + if (prev->pool == NULL) { + prev->pool = ngx_create_pool(4096, &cf->cycle->new_log); + if (prev->pool == NULL) { + return NGX_CONF_ERROR; + } + } + + conf->pool = prev->pool; + + if (conf->server_names.nelts == 0) { + sn = ngx_array_push(&conf->server_names); +#if (NGX_PCRE) + sn->regex = NULL; +#endif + sn->server = conf; + ngx_str_set(&sn->name, ""); + } + + sn = conf->server_names.elts; + name = sn[0].name; + +#if (NGX_PCRE) + if (sn->regex) { + name.len++; + name.data--; + } else +#endif + if (name.data[0] == '.') { + name.len--; + name.data++; + } + + conf->server_name.len = name.len; + conf->server_name.data = ngx_pstrdup(cf->pool, &name); + if (conf->server_name.data == NULL) { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + +static void * +ngx_rtmp_core_create_app_conf(ngx_conf_t *cf) +{ + ngx_rtmp_core_app_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_core_app_conf_t)); + if (conf == NULL) { + return NULL; + } + + if (ngx_array_init(&conf->applications, cf->pool, 1, + sizeof(ngx_rtmp_core_app_conf_t *)) + != NGX_OK) + { + return NULL; + } + + conf->hevc_codec = NGX_CONF_UNSET_UINT; + conf->merge_frame = NGX_CONF_UNSET_UINT; + conf->tcp_nodelay = NGX_CONF_UNSET; + + return conf; +} + + +static char * +ngx_rtmp_core_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_rtmp_core_app_conf_t *prev = parent; + ngx_rtmp_core_app_conf_t *conf = child; + + ngx_conf_merge_uint_value(conf->hevc_codec, prev->hevc_codec, 12); + ngx_conf_merge_uint_value(conf->merge_frame, prev->merge_frame, 32); + ngx_conf_merge_value(conf->tcp_nodelay, prev->tcp_nodelay, 1); + + NGX_RTMP_HEVC_CODEC_ID = conf->hevc_codec; + + return NGX_CONF_OK; +} + +/* nginx rtmp dynamic */ +static void * +ngx_rtmp_core_create_srv_dconf(ngx_conf_t *cf) +{ + ngx_rtmp_core_srv_dconf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_core_srv_dconf_t)); + if (conf == NULL) { + return NULL; + } + + return conf; +} + +static char * +ngx_rtmp_core_init_srv_dconf(ngx_conf_t *cf, void *conf) +{ + return NGX_CONF_OK; +} + + +static char * +ngx_rtmp_merge_frame(ngx_conf_t *cf, void *post, void *data) +{ + ngx_uint_t *mfp; + + mfp = data; + + if (*mfp > NGX_RTMP_MAX_MERGE_FRAME) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the merge_frame must be no larger than %ui", + NGX_RTMP_MAX_MERGE_FRAME); + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + +static char * +ngx_rtmp_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + char *rv; + void *mconf; + ngx_uint_t m; + ngx_conf_t pcf; + ngx_module_t **modules; + ngx_rtmp_module_t *module; + struct sockaddr_in *sin; + ngx_rtmp_conf_ctx_t *ctx, *rtmp_ctx; + ngx_rtmp_listen_opt_t lsopt; + ngx_rtmp_core_srv_conf_t *cscf, **cscfp; + ngx_rtmp_core_main_conf_t *cmcf; + + ctx = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_conf_ctx_t)); + if (ctx == NULL) { + return NGX_CONF_ERROR; + } + + rtmp_ctx = cf->ctx; + ctx->main_conf = rtmp_ctx->main_conf; + + /* the server{}'s srv_conf */ + + ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module); + if (ctx->srv_conf == NULL) { + return NGX_CONF_ERROR; + } + + ctx->app_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module); + if (ctx->app_conf == NULL) { + return NGX_CONF_ERROR; + } + +#if (nginx_version >= 1009011) + modules = cf->cycle->modules; +#else + modules = ngx_modules; +#endif + + for (m = 0; modules[m]; m++) { + if (modules[m]->type != NGX_RTMP_MODULE) { + continue; + } + + module = modules[m]->ctx; + + if (module->create_srv_conf) { + mconf = module->create_srv_conf(cf); + if (mconf == NULL) { + return NGX_CONF_ERROR; + } + + ctx->srv_conf[modules[m]->ctx_index] = mconf; + } + + if (module->create_app_conf) { + mconf = module->create_app_conf(cf); + if (mconf == NULL) { + return NGX_CONF_ERROR; + } + + ctx->app_conf[modules[m]->ctx_index] = mconf; + } + } + + /* the server configuration context */ + + cscf = ctx->srv_conf[ngx_rtmp_core_module.ctx_index]; + cscf->ctx = ctx; + + cmcf = ctx->main_conf[ngx_rtmp_core_module.ctx_index]; + + cscfp = ngx_array_push(&cmcf->servers); + if (cscfp == NULL) { + return NGX_CONF_ERROR; + } + + *cscfp = cscf; + + /* parse inside server{} */ + + pcf = *cf; + cf->ctx = ctx; + cf->cmd_type = NGX_RTMP_SRV_CONF; + + rv = ngx_conf_parse(cf, NULL); + + *cf = pcf; + + if (rv == NGX_CONF_OK && !cscf->listen) { + /* if listen not configured, set 1935 as default */ + ngx_memzero(&lsopt, sizeof(ngx_rtmp_listen_opt_t)); + + lsopt.sockaddr = ngx_pcalloc(cf->pool, NGX_SOCKADDR_STRLEN); + if (lsopt.sockaddr == NULL) { + return NGX_CONF_ERROR; + } + + sin = (struct sockaddr_in *) lsopt.sockaddr; + + sin->sin_family = AF_INET; + sin->sin_port = htons(1935); + sin->sin_addr.s_addr = INADDR_ANY; + + lsopt.socklen = sizeof(struct sockaddr_in); + lsopt.wildcard = 1; + + (void) ngx_sock_ntop(lsopt.sockaddr, lsopt.socklen, lsopt.addr, + NGX_SOCKADDR_STRLEN, 1); + + if (ngx_rtmp_add_listen(cf, cscf, &lsopt) != NGX_OK) { + return NGX_CONF_ERROR; + } + } + + return rv; +} + + +static char * +ngx_rtmp_core_application(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + char *rv; + ngx_int_t i; + ngx_str_t *value; + ngx_conf_t save; + ngx_module_t **modules; + ngx_rtmp_module_t *module; + ngx_rtmp_conf_ctx_t *ctx, *pctx; + ngx_rtmp_core_srv_conf_t *cscf; + ngx_rtmp_core_app_conf_t *cacf, **cacfp; + + ctx = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_conf_ctx_t)); + if (ctx == NULL) { + return NGX_CONF_ERROR; + } + + pctx = cf->ctx; + ctx->main_conf = pctx->main_conf; + ctx->srv_conf = pctx->srv_conf; + + ctx->app_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module); + if (ctx->app_conf == NULL) { + return NGX_CONF_ERROR; + } + +#if (nginx_version >= 1009011) + modules = cf->cycle->modules; +#else + modules = ngx_modules; +#endif + + for (i = 0; modules[i]; i++) { + if (modules[i]->type != NGX_RTMP_MODULE) { + continue; + } + + module = modules[i]->ctx; + + if (module->create_app_conf) { + ctx->app_conf[modules[i]->ctx_index] = module->create_app_conf(cf); + if (ctx->app_conf[modules[i]->ctx_index] == NULL) { + return NGX_CONF_ERROR; + } + } + } + + cacf = ctx->app_conf[ngx_rtmp_core_module.ctx_index]; + cacf->app_conf = ctx->app_conf; + + value = cf->args->elts; + + cacf->name = value[1]; + cscf = pctx->srv_conf[ngx_rtmp_core_module.ctx_index]; + + if (cacf->name.len == 1 && cacf->name.data[0] == '*') { /* default_app */ + cscf->default_app = cacf; + } + + cacfp = ngx_array_push(&cscf->applications); + if (cacfp == NULL) { + return NGX_CONF_ERROR; + } + + *cacfp = cacf; + + save = *cf; + cf->ctx = ctx; + cf->cmd_type = NGX_RTMP_APP_CONF; + + rv = ngx_conf_parse(cf, NULL); + + *cf= save; + + return rv; +} + + +static char * +ngx_rtmp_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_rtmp_core_srv_conf_t *cscf = conf; + + ngx_str_t *value; + ngx_url_t u; + ngx_uint_t i; + ngx_rtmp_listen_opt_t lsopt; + + cscf->listen = 1; + + value = cf->args->elts; + + ngx_memzero(&u, sizeof(ngx_url_t)); + + u.url = value[1]; + u.listen = 1; + u.default_port = 1935; + + if (ngx_parse_url(cf->pool, &u) != NGX_OK) { + if (u.err) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "%s in \"%V\" of the \"listen\" directive", + u.err, &u.url); + } + + return NGX_CONF_ERROR; + } + + ngx_memzero(&lsopt, sizeof(ngx_rtmp_listen_opt_t)); + + lsopt.sockaddr = ngx_pcalloc(cf->pool, NGX_SOCKADDR_STRLEN); + if (lsopt.sockaddr == NULL) { + return NGX_CONF_ERROR; + } + ngx_memcpy(lsopt.sockaddr, (u_char *) &u.sockaddr, u.socklen); + + lsopt.socklen = u.socklen; + lsopt.wildcard = u.wildcard; + + (void) ngx_sock_ntop(lsopt.sockaddr, lsopt.socklen, lsopt.addr, + NGX_SOCKADDR_STRLEN, 1); + + for (i = 2; i < cf->args->nelts; i++) { + + if (ngx_strcmp(value[i].data, "default_server") == 0 + || ngx_strcmp(value[i].data, "default") == 0) + { + lsopt.default_server = 1; + continue; + } + + if (ngx_strcmp(value[i].data, "bind") == 0) { + lsopt.bind = 1; + continue; + } + + if (ngx_strncmp(value[i].data, "ipv6only=o", 10) == 0) { +#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY) + struct sockaddr *sa; + + sa = lsopt.sockaddr; + + if (sa->sa_family == AF_INET6) { + + if (ngx_strcmp(&value[i].data[10], "n") == 0) { + lsopt.ipv6only = 1; + + } else if (ngx_strcmp(&value[i].data[10], "ff") == 0) { + lsopt.ipv6only = 0; + + } else { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid ipv6only flags \"%s\"", + &value[i].data[9]); + return NGX_CONF_ERROR; + } + + lsopt.bind = 1; + + } else { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "ipv6only is not supported " + "on addr \"%s\", ignored", lsopt.addr); + } + + continue; +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "bind ipv6only is not supported " + "on this platform"); + return NGX_CONF_ERROR; +#endif + } + + if (ngx_strcmp(value[i].data, "reuseport") == 0) { +#if (NGX_HAVE_REUSEPORT) + lsopt.reuseport = 1; + lsopt.set = 1; + lsopt.bind = 1; +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "reuseport is not supported " + "on this platform, ignored"); +#endif + continue; + } + + if (ngx_strncmp(value[i].data, "so_keepalive=", 13) == 0) { + + if (ngx_strcmp(&value[i].data[13], "on") == 0) { + lsopt.so_keepalive = 1; + + } else if (ngx_strcmp(&value[i].data[13], "off") == 0) { + lsopt.so_keepalive = 2; + + } else { + +#if (NGX_HAVE_KEEPALIVE_TUNABLE) + u_char *p, *end; + ngx_str_t s; + + end = value[i].data + value[i].len; + s.data = value[i].data + 13; + + p = ngx_strlchr(s.data, end, ':'); + if (p == NULL) { + p = end; + } + + if (p > s.data) { + s.len = p - s.data; + + lsopt.tcp_keepidle = ngx_parse_time(&s, 1); + if (lsopt.tcp_keepidle == (time_t) NGX_ERROR) { + goto invalid_so_keepalive; + } + } + + s.data = (p < end) ? (p + 1) : end; + + p = ngx_strlchr(s.data, end, ':'); + if (p == NULL) { + p = end; + } + + if (p > s.data) { + s.len = p - s.data; + + lsopt.tcp_keepintvl = ngx_parse_time(&s, 1); + if (lsopt.tcp_keepintvl == (time_t) NGX_ERROR) { + goto invalid_so_keepalive; + } + } + + s.data = (p < end) ? (p + 1) : end; + + if (s.data < end) { + s.len = end - s.data; + + lsopt.tcp_keepcnt = ngx_atoi(s.data, s.len); + if (lsopt.tcp_keepcnt == NGX_ERROR) { + goto invalid_so_keepalive; + } + } + + if (lsopt.tcp_keepidle == 0 && lsopt.tcp_keepintvl == 0 + && lsopt.tcp_keepcnt == 0) + { + goto invalid_so_keepalive; + } + + lsopt.so_keepalive = 1; + +#else + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the \"so_keepalive\" parameter accepts " + "only \"on\" or \"off\" on this platform"); + return NGX_CONF_ERROR; + +#endif + } + + lsopt.bind = 1; + + continue; + +#if (NGX_HAVE_KEEPALIVE_TUNABLE) + invalid_so_keepalive: + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid so_keepalive value: \"%s\"", + &value[i].data[13]); + return NGX_CONF_ERROR; +#endif + } + + if (ngx_strcmp(value[i].data, "proxy_protocol") == 0) { + lsopt.proxy_protocol = 1; + continue; + } + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the invalid \"%V\" parameter", &value[i]); + return NGX_CONF_ERROR; + } + + if (ngx_rtmp_add_listen(cf, cscf, &lsopt) == NGX_OK) { + return NGX_CONF_OK; + } + + return NGX_CONF_ERROR; +} + +static char * +ngx_rtmp_core_server_name(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_rtmp_core_srv_conf_t *cscf = conf; + + u_char ch; + ngx_str_t *value; + ngx_uint_t i; + ngx_rtmp_server_name_t *sn; + + value = cf->args->elts; + + for (i = 1; i < cf->args->nelts; i++) { + + ch = value[i].data[0]; + + if ((ch == '*' && (value[i].len < 3 || value[i].data[1] != '.')) + || (ch == '.' && value[i].len < 2)) + { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "server name \"%V\" is invalid", &value[i]); + return NGX_CONF_ERROR; + } + + if (ngx_strchr(value[i].data, '/')) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "server name \"%V\" has suspicious symbols", + &value[i]); + } + + sn = ngx_array_push(&cscf->server_names); + if (sn == NULL) { + return NGX_CONF_ERROR; + } + +#if (NGX_PCRE) + sn->regex = NULL; +#endif + sn->server = cscf; + + if (ngx_strcasecmp(value[i].data, (u_char *) "$hostname") == 0) { + sn->name = cf->cycle->hostname; + + } else { + sn->name = value[i]; + } + + if (value[i].data[0] != '~') { + ngx_strlow(sn->name.data, sn->name.data, sn->name.len); + continue; + } + +#if (NGX_PCRE) + { + u_char *p; + ngx_regex_compile_t rc; + u_char errstr[NGX_MAX_CONF_ERRSTR]; + + if (value[i].len == 1) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "empty regex in server name \"%V\"", &value[i]); + return NGX_CONF_ERROR; + } + + value[i].len--; + value[i].data++; + + ngx_memzero(&rc, sizeof(ngx_regex_compile_t)); + + rc.pattern = value[i]; + rc.err.len = NGX_MAX_CONF_ERRSTR; + rc.err.data = errstr; + + for (p = value[i].data; p < value[i].data + value[i].len; p++) { + if (*p >= 'A' && *p <= 'Z') { + rc.options = NGX_REGEX_CASELESS; + break; + } + } + + sn->regex = ngx_rtmp_regex_compile(cf, &rc); + if (sn->regex == NULL) { + return NGX_CONF_ERROR; + } + + sn->name = value[i]; + cscf->captures = (rc.captures > 0); + } +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "using regex \"%V\" " + "requires PCRE library", &value[i]); + + return NGX_CONF_ERROR; +#endif + } + + return NGX_CONF_OK; +} + +static ngx_rtmp_addr_conf_t * +ngx_rtmp_get_addr_conf(ngx_listening_t *ls, struct sockaddr *sa, socklen_t len) +{ + ngx_uint_t i; + ngx_rtmp_addr_conf_t *addr_conf; + ngx_rtmp_port_t *port; + struct sockaddr_in *sin; + ngx_rtmp_in_addr_t *addr; +#if (NGX_HAVE_INET6) + struct sockaddr_in6 *sin6; + ngx_rtmp_in6_addr_t *addr6; +#endif + + port = ls->servers; + addr_conf = NULL; + + if (port->naddrs > 1) { + switch (sa->sa_family) { +#if (NGX_HAVE_INET6) + case AF_INET6: + sin6 = (struct sockaddr_in6 *) sa; + + addr6 = port->addrs; + + /* the last address is "*" */ + + for (i = 0; i < port->naddrs - 1; ++i) { + if (ngx_memcmp(&addr6[i].addr6, &sin6->sin6_addr, 16) == 0) { + break; + } + } + + addr_conf = &addr6[i].conf; + + break; +#endif + default: + sin = (struct sockaddr_in *) sa; + + addr = port->addrs; + + /* the last address is "*" */ + + for (i = 0; i < port->naddrs - 1; ++i) { + if (addr[i].addr == sin->sin_addr.s_addr) { + break; + } + } + + addr_conf = &addr[i].conf; + + break; + } + } else { + + switch (sa->sa_family) { +#if (NGX_HAVE_INET6) + case AF_INET6: + addr6 = port->addrs; + addr_conf = &addr6[0].conf; + break; +#endif + + default: + addr = port->addrs; + addr_conf = &addr[0].conf; + break; + } + } + + return addr_conf; +} + +ngx_rtmp_addr_conf_t * +ngx_rtmp_find_related_addr_conf(ngx_cycle_t *cycle, ngx_str_t *addr) +{ + ngx_url_t u; + ngx_listening_t *ls; + ngx_uint_t i; + + ngx_memzero(&u, sizeof(ngx_url_t)); + + u.url = *addr; + u.listen = 1; + u.default_port = 0; + + if (ngx_parse_url(cycle->pool, &u) != NGX_OK) { + if (u.err) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, + "Relation port err: %V", addr); + } + + return NULL; + } + + ls = cycle->listening.elts; + + for (i = 0; i < cycle->listening.nelts; ++i, ++ls) { + + if (ls->handler == ngx_rtmp_init_connection + && ls->socklen == u.socklen + && ngx_memcmp(ls->sockaddr, (u_char *) &u.sockaddr, + u.socklen) == 0) + { + return ngx_rtmp_get_addr_conf(ls, &u.sockaddr.sockaddr, u.socklen); + } + } + + ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, + "Can not find relation port: %V", addr); + + return NULL; +} + + +ngx_int_t +ngx_rtmp_arg(ngx_rtmp_session_t *s, u_char *name, size_t len, ngx_str_t *value) +{ + u_char *p, *last; + + if (s->pargs.len == 0) { + return NGX_DECLINED; + } + + p = s->pargs.data; + last = p + s->pargs.len; + + for ( /* void */ ; p < last; p++) { + + /* we need '=' after name, so drop one char from last */ + + p = ngx_strlcasestrn(p, last - 1, name, len - 1); + + if (p == NULL) { + return NGX_DECLINED; + } + + if ((p == s->pargs.data || *(p - 1) == '&') && *(p + len) == '=') { + + value->data = p + len + 1; + + p = ngx_strlchr(p, last, '&'); + + if (p == NULL) { + p = s->pargs.data + s->pargs.len; + } + + value->len = p - value->data; + + return NGX_OK; + } + } + + return NGX_DECLINED; +} + + +static ngx_int_t +ngx_rtmp_core_preconfiguration(ngx_conf_t *cf) +{ + return ngx_rtmp_variables_add_core_vars(cf); +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_dynamic.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_dynamic.c new file mode 100644 index 0000000000..fd92cad97a --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_dynamic.c @@ -0,0 +1,999 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#include +#include +#include "ngx_rtmp.h" +#include "ngx_rtmp_dynamic.h" + + +static void *ngx_rtmp_dynamic_create_conf(ngx_conf_t *cf); +static void *ngx_rtmp_dynamic_core_create_main_conf(ngx_conf_t *cf); +static char *ngx_rtmp_dynamic_core_init_main_conf(ngx_conf_t *cf, void *conf); +static void *ngx_rtmp_dynamic_core_create_srv_conf(ngx_conf_t *cf); +static char *ngx_rtmp_dynamic_core_init_srv_conf(ngx_conf_t *cf, void *conf); +static void *ngx_rtmp_dynamic_core_create_app_conf(ngx_conf_t *cf); +static char *ngx_rtmp_dynamic_core_init_app_conf(ngx_conf_t *cf, void *conf); + +static char *ngx_rtmp_dynamic_block(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char *ngx_rtmp_dynamic_core_server(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char *ngx_rtmp_dynamic_core_server_name(ngx_conf_t *cf, + ngx_command_t *cmd, void *conf); +static char *ngx_rtmp_dynamic_core_application(ngx_conf_t *cf, + ngx_command_t *cmd, void *conf); + + +typedef struct { + ngx_str_t name; + ngx_map_node_t node; + + void **app_conf; +} ngx_rtmp_dynamic_core_app_conf_t; + +typedef struct { + /* array of the ngx_rtmp_server_name_t, "server_name" directive */ + ngx_array_t server_names; + + ngx_rtmp_dynamic_core_app_conf_t *default_app; + ngx_map_t app_conf; + + void **srv_conf; +} ngx_rtmp_dynamic_core_srv_conf_t; + +typedef struct { +#if (NGX_PCRE) + ngx_dynamic_regex_t *regex; +#endif + ngx_rtmp_dynamic_core_srv_conf_t *server; /* virtual name server conf */ + ngx_str_t name; +} ngx_rtmp_dynamic_server_name_t; + + +typedef struct { + /* ngx_rtmp_dynamic_srv_conf_t */ + ngx_array_t servers; + + ngx_uint_t server_names_hash_max_size; + ngx_uint_t server_names_hash_bucket_size; + + /* the default server configuration for this address:port */ + ngx_rtmp_dynamic_core_srv_conf_t *default_server; + + ngx_hash_combined_t names; + +#if (NGX_PCRE) + ngx_uint_t nregex; + ngx_rtmp_dynamic_server_name_t *regex; +#endif +} ngx_rtmp_dynamic_core_main_conf_t; + + +typedef struct { + void **main_conf; +} ngx_rtmp_dynamic_conf_t; + + +static ngx_core_module_t ngx_rtmp_dynamic_module_ctx = { + ngx_string("rtmp_dynamic"), + NULL, + NULL +}; + +static ngx_command_t ngx_rtmp_dynamic_dcommands[] = { + + { ngx_string("rtmp"), + NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, + ngx_rtmp_dynamic_block, + 0, + 0, + NULL }, + + ngx_null_command +}; + +static ngx_dynamic_core_module_t ngx_rtmp_dynamic_module_dctx = { + ngx_string("rtmp_dynamic"), + ngx_rtmp_dynamic_create_conf, + NULL +}; + +ngx_module_t ngx_rtmp_dynamic_module = { + NGX_MODULE_V1, + &ngx_rtmp_dynamic_module_ctx, /* module context */ + NULL, /* module directives */ + NGX_CORE_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + (uintptr_t) &ngx_rtmp_dynamic_module_dctx, /* module dynamic context */ + (uintptr_t) ngx_rtmp_dynamic_dcommands, /* module dynamic directives */ + NGX_MODULE_V1_DYNAMIC_PADDING +}; + + +static ngx_rtmp_module_t ngx_rtmp_dynamic_core_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create application configuration */ + NULL /* merge application configuration */ +}; + + +static ngx_command_t ngx_rtmp_dynamic_core_dcommands[] = { + + { ngx_string("server_names_hash_max_size"), + NGX_RTMP_MAIN_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + 0, + offsetof(ngx_rtmp_dynamic_core_main_conf_t, server_names_hash_max_size), + NULL }, + + { ngx_string("server_names_hash_bucket_size"), + NGX_RTMP_MAIN_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + 0, + offsetof(ngx_rtmp_dynamic_core_main_conf_t, + server_names_hash_bucket_size), + NULL }, + + { ngx_string("server"), + NGX_RTMP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, + ngx_rtmp_dynamic_core_server, + 0, + 0, + NULL }, + + { ngx_string("server_name"), + NGX_RTMP_SRV_CONF|NGX_CONF_1MORE, + ngx_rtmp_dynamic_core_server_name, + 0, + 0, + NULL }, + + { ngx_string("application"), + NGX_RTMP_SRV_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE12, + ngx_rtmp_dynamic_core_application, + 0, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_rtmp_dynamic_module_t ngx_rtmp_dynamic_core_module_dctx = { + ngx_rtmp_dynamic_core_create_main_conf, /* create main configuration */ + ngx_rtmp_dynamic_core_init_main_conf, /* init main configuration */ + + ngx_rtmp_dynamic_core_create_srv_conf, /* create srv configuration */ + ngx_rtmp_dynamic_core_init_srv_conf, /* init srv configuration */ + + ngx_rtmp_dynamic_core_create_app_conf, /* create app configuration */ + ngx_rtmp_dynamic_core_init_app_conf /* init app configuration */ +}; + + +ngx_module_t ngx_rtmp_dynamic_core_module = { + NGX_MODULE_V1, + &ngx_rtmp_dynamic_core_module_ctx, /* module context */ + NULL, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + (uintptr_t) &ngx_rtmp_dynamic_core_module_dctx, /* module dynamic context */ + (uintptr_t) ngx_rtmp_dynamic_core_dcommands, /* module dynamic directives */ + NGX_MODULE_V1_DYNAMIC_PADDING +}; + + +static void * +ngx_rtmp_dynamic_create_conf(ngx_conf_t *cf) +{ + ngx_rtmp_dynamic_conf_t *rdccf; + + rdccf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_dynamic_conf_t)); + if (rdccf == NULL) { + return NULL; + } + + return rdccf; +} + +static void * +ngx_rtmp_dynamic_core_create_main_conf(ngx_conf_t *cf) +{ + ngx_rtmp_dynamic_core_main_conf_t *rdcmcf; + + rdcmcf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_dynamic_core_main_conf_t)); + if (rdcmcf == NULL) { + return NULL; + } + + if (ngx_array_init(&rdcmcf->servers, cf->pool, 4, + sizeof(ngx_rtmp_dynamic_core_srv_conf_t *)) != NGX_OK) + { + return NULL; + } + + rdcmcf->server_names_hash_max_size = NGX_CONF_UNSET_UINT; + rdcmcf->server_names_hash_bucket_size = NGX_CONF_UNSET_UINT; + + return rdcmcf; +} + +static char * +ngx_rtmp_dynamic_core_init_main_conf(ngx_conf_t *cf, void *conf) +{ + ngx_rtmp_dynamic_core_main_conf_t *rdcmcf; + + rdcmcf = conf; + + ngx_conf_init_uint_value(rdcmcf->server_names_hash_max_size, 512); + ngx_conf_init_uint_value(rdcmcf->server_names_hash_bucket_size, + ngx_cacheline_size); + + return NGX_CONF_OK; +} + +static void * +ngx_rtmp_dynamic_core_create_srv_conf(ngx_conf_t *cf) +{ + ngx_rtmp_dynamic_core_srv_conf_t *rdcscf; + + rdcscf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_dynamic_core_srv_conf_t)); + if (rdcscf == NULL) { + return NULL; + } + + if (ngx_array_init(&rdcscf->server_names, cf->pool, 4, + sizeof(ngx_rtmp_dynamic_server_name_t)) != NGX_OK) + { + return NULL; + } + + return rdcscf; +} + +static char * +ngx_rtmp_dynamic_core_init_srv_conf(ngx_conf_t *cf, void *conf) +{ + return NGX_CONF_OK; +} + +static void * +ngx_rtmp_dynamic_core_create_app_conf(ngx_conf_t *cf) +{ + ngx_rtmp_dynamic_core_app_conf_t *rdcacf; + + rdcacf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_dynamic_core_app_conf_t)); + if (rdcacf == NULL) { + return NULL; + } + + return rdcacf; +} + +static char * +ngx_rtmp_dynamic_core_init_app_conf(ngx_conf_t *cf, void *conf) +{ + return NGX_CONF_OK; +} + +static ngx_int_t +ngx_rtmp_dynamic_core_find_application(ngx_rtmp_session_t *s, + ngx_rtmp_dynamic_core_srv_conf_t *rdcscf, + ngx_rtmp_dynamic_core_app_conf_t **rdcacf) +{ + ngx_map_node_t *node; + + node = ngx_map_find(&rdcscf->app_conf, (intptr_t) &s->app); + if (node == NULL) { + *rdcacf = rdcscf->default_app; + } else { + *rdcacf = (ngx_rtmp_dynamic_core_app_conf_t *) + ((char *) node - offsetof(ngx_rtmp_dynamic_core_app_conf_t, node)); + } + + return NGX_OK; +} + +static ngx_int_t +ngx_rtmp_dynamic_core_init_virtual_servers(ngx_conf_t *cf, + ngx_rtmp_dynamic_conf_t *hdcf) +{ + ngx_rtmp_dynamic_core_main_conf_t *rdcmcf; + ngx_rtmp_dynamic_core_srv_conf_t **rdcscfp; + ngx_hash_init_t hash; + ngx_hash_keys_arrays_t ha; + ngx_rtmp_dynamic_server_name_t *name; + ngx_uint_t s, n; + ngx_int_t rc; +#if (NGX_PCRE) + ngx_uint_t regex, i; + + regex = 0; +#endif + + rdcmcf = hdcf->main_conf[ngx_rtmp_dynamic_core_module.ctx_index]; + + ngx_memzero(&ha, sizeof(ngx_hash_keys_arrays_t)); + + ha.temp_pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log); + if (ha.temp_pool == NULL) { + return NGX_ERROR; + } + + ha.pool = cf->pool; + + if (ngx_hash_keys_array_init(&ha, NGX_HASH_LARGE) != NGX_OK) { + goto failed; + } + + rdcscfp = rdcmcf->servers.elts; + rdcmcf->default_server = rdcscfp[0]; + + for (s = 0; s < rdcmcf->servers.nelts; ++s) { + + name = rdcscfp[s]->server_names.elts; + + for (n = 0; n < rdcscfp[s]->server_names.nelts; ++n) { +#if (NGX_PCRE) + if (name[n].regex) { + ++regex; + continue; + } +#endif + + rc = ngx_hash_add_key(&ha, &name[n].name, name[n].server, + NGX_HASH_WILDCARD_KEY); + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_DECLINED) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "invalid server name or wildcard \"%V\"", + &name[n].name); + return NGX_ERROR; + } + + if (rc == NGX_BUSY) { + ngx_log_error(NGX_LOG_WARN, cf->log, 0, + "conflicting server name \"%V\", ignored", + &name[n].name); + } + } + } + + hash.key = ngx_hash_key_lc; + hash.max_size = rdcmcf->server_names_hash_max_size; + hash.bucket_size = rdcmcf->server_names_hash_bucket_size; + hash.name = "rtmp_dynamic_server_names_hash"; + hash.pool = cf->pool; + + if (ha.keys.nelts) { + hash.hash = &rdcmcf->names.hash; + hash.temp_pool = NULL; + + if (ngx_hash_init(&hash, ha.keys.elts, ha.keys.nelts) != NGX_OK) { + goto failed; + } + } + + if (ha.dns_wc_head.nelts) { + + ngx_qsort(ha.dns_wc_head.elts, (size_t) ha.dns_wc_head.nelts, + sizeof(ngx_hash_key_t), ngx_dynamic_cmp_dns_wildcards); + + hash.hash = NULL; + hash.temp_pool = ha.temp_pool; + + if (ngx_hash_wildcard_init(&hash, ha.dns_wc_head.elts, + ha.dns_wc_head.nelts) != NGX_OK) + { + goto failed; + } + + rdcmcf->names.wc_head = (ngx_hash_wildcard_t *) hash.hash; + } + + if (ha.dns_wc_tail.nelts) { + + ngx_qsort(ha.dns_wc_tail.elts, (size_t) ha.dns_wc_tail.nelts, + sizeof(ngx_hash_key_t), ngx_dynamic_cmp_dns_wildcards); + + hash.hash = NULL; + hash.temp_pool = ha.temp_pool; + + if (ngx_hash_wildcard_init(&hash, ha.dns_wc_tail.elts, + ha.dns_wc_tail.nelts) != NGX_OK) + { + goto failed; + } + + rdcmcf->names.wc_tail = (ngx_hash_wildcard_t *) hash.hash; + + } + + ngx_destroy_pool(ha.temp_pool); + +#if (NGX_PCRE) + if (regex == 0) { + return NGX_OK; + } + + rdcmcf->nregex = regex; + rdcmcf->regex = ngx_pcalloc(cf->pool, + regex * sizeof(ngx_rtmp_dynamic_server_name_t)); + if (rdcmcf->regex == NULL) { + return NGX_ERROR; + } + + i = 0; + + for (s = 0; s < rdcmcf->servers.nelts; ++s) { + + name = rdcscfp[s]->server_names.elts; + + for (n = 0; n < rdcscfp[s]->server_names.nelts; ++n) { + if (name[n].regex) { + rdcmcf->regex[i++] = name[n]; + } + } + } +#endif + + return NGX_OK; + +failed: + ngx_destroy_pool(ha.temp_pool); + + return NGX_ERROR; +} + +static ngx_int_t +ngx_rtmp_dynamic_core_find_virtual_server(ngx_str_t *server, + ngx_rtmp_dynamic_core_main_conf_t *rdcmcf, + ngx_rtmp_dynamic_core_srv_conf_t **rdcscfp) +{ + ngx_rtmp_dynamic_core_srv_conf_t *rdcscf; + + rdcscf = ngx_hash_find_combined(&rdcmcf->names, + ngx_hash_key(server->data, server->len), + server->data, server->len); + + if (rdcscf) { + *rdcscfp = rdcscf; + return NGX_OK; + } + +#if (NGX_PCRE) + + if (server->len && rdcmcf->nregex) { + ngx_int_t n; + ngx_uint_t i; + ngx_rtmp_dynamic_server_name_t *sn; + + sn = rdcmcf->regex; + + for (i = 0; i < rdcmcf->nregex; ++i) { + + n = ngx_regex_exec(sn[i].regex->regex, server, NULL, 0); + + if (n == NGX_REGEX_NO_MATCHED) { + continue; + } + + if (n >= 0) { + *rdcscfp = sn[i].server; + return NGX_OK; + } + + return NGX_ERROR; + } + } +#endif + + return NGX_DECLINED; +} + +static char * +ngx_rtmp_dynamic_core_application(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ + ngx_rtmp_dynamic_module_t *module; + ngx_rtmp_dynamic_core_app_conf_t *rdcacf; + ngx_rtmp_dynamic_core_srv_conf_t *rdcscf; + ngx_conf_t pcf; + void *mconf, **app_conf; + ngx_uint_t i, ci; + ngx_str_t *value; + char *rv = NULL; + + rdcscf = conf; + + /* app_conf is temp cause hdclcf does not created now */ + app_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module); + if (app_conf == NULL) { + return NGX_CONF_ERROR; + } + + for (i = 0; cf->cycle->modules[i]; ++i) { + if (cf->cycle->modules[i]->type != NGX_RTMP_MODULE) { + continue; + } + + module = (ngx_rtmp_dynamic_module_t *) + cf->cycle->modules[i]->spare_hook0; + + if (module == NULL) { + continue; + } + + if (module->create_app_conf) { + mconf = module->create_app_conf(cf); + if (mconf == NULL) { + return NGX_CONF_ERROR; + } + + app_conf[cf->cycle->modules[i]->ctx_index] = mconf; + } + } + + rdcacf = app_conf[ngx_rtmp_dynamic_core_module.ctx_index]; + rdcacf->app_conf = app_conf; + + value = cf->args->elts; + + rdcacf->name = value[1]; + if (rdcacf->name.len == 1 && rdcacf->name.data[0] == '*') { /* default */ + if (rdcscf->default_app) { + return "is duplicate"; + } + + rdcscf->default_app = rdcacf; + } else { + rdcacf->node.raw_key = (intptr_t) &rdcacf->name; + ngx_map_insert(&rdcscf->app_conf, &rdcacf->node, 0); + } + + pcf = *cf; + cf->ctx = rdcacf->app_conf; + cf->cmd_type = NGX_RTMP_APP_CONF; + + if (ngx_dynamic_conf_parse(cf, 0) != NGX_OK) { + goto failed; + } + + for (i = 0; cf->cycle->modules[i]; ++i) { + if (cf->cycle->modules[i]->type != NGX_RTMP_MODULE) { + continue; + } + + module = (ngx_rtmp_dynamic_module_t *) + cf->cycle->modules[i]->spare_hook0; + ci = cf->cycle->modules[i]->ctx_index; + if (module == NULL) { + continue; + } + + if (module->init_app_conf) { + rv = module->init_app_conf(cf, rdcacf->app_conf[ci]); + if (rv != NGX_CONF_OK) { + goto failed; + } + } + } + + *cf = pcf; + + return NGX_CONF_OK; + +failed: + *cf = pcf; + + if (rv) { + return rv; + } + + return NGX_CONF_ERROR; +} + +static char * +ngx_rtmp_dynamic_core_server_name(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ + ngx_rtmp_dynamic_core_srv_conf_t *rdcscf; + u_char ch; + ngx_str_t *value; + ngx_uint_t i; + ngx_rtmp_dynamic_server_name_t *sn; + + rdcscf = conf; + + value = cf->args->elts; + + for (i = 1; i < cf->args->nelts; i++) { + + ch = value[i].data[0]; + + if ((ch == '*' && (value[i].len < 3 || value[i].data[1] != '.')) + || (ch == '.' && value[i].len < 2)) + { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "server name \"%V\" is invalid", &value[i]); + return NGX_CONF_ERROR; + } + + if (ngx_strchr(value[i].data, '/')) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "server name \"%V\" has suspicious symbols", + &value[i]); + } + + sn = ngx_array_push(&rdcscf->server_names); + if (sn == NULL) { + return NGX_CONF_ERROR; + } + +#if (NGX_PCRE) + sn->regex = NULL; +#endif + sn->server = rdcscf; + + if (ngx_strcasecmp(value[i].data, (u_char *) "$hostname") == 0) { + sn->name = cf->cycle->hostname; + + } else { + sn->name = value[i]; + } + + if (value[i].data[0] != '~') { + ngx_strlow(sn->name.data, sn->name.data, sn->name.len); + continue; + } + +#if (NGX_PCRE) + { + u_char *p; + ngx_regex_compile_t rc; + u_char errstr[NGX_MAX_CONF_ERRSTR]; + + if (value[i].len == 1) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "empty regex in server name \"%V\"", &value[i]); + return NGX_CONF_ERROR; + } + + value[i].len--; + value[i].data++; + + ngx_memzero(&rc, sizeof(ngx_regex_compile_t)); + + rc.pattern = value[i]; + rc.err.len = NGX_MAX_CONF_ERRSTR; + rc.err.data = errstr; + + for (p = value[i].data; p < value[i].data + value[i].len; p++) { + if (*p >= 'A' && *p <= 'Z') { + rc.options = NGX_REGEX_CASELESS; + break; + } + } + + sn->regex = ngx_dynamic_regex_compile(cf, &rc); + if (sn->regex == NULL) { + return NGX_CONF_ERROR; + } + + sn->name = value[i]; + } +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "using regex \"%V\" " + "requires PCRE library", &value[i]); + + return NGX_CONF_ERROR; +#endif + } + + return NGX_CONF_OK; +} + +static char * +ngx_rtmp_dynamic_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_rtmp_dynamic_module_t *module; + ngx_rtmp_dynamic_core_main_conf_t *rdcmcf; + ngx_rtmp_dynamic_core_srv_conf_t *rdcscf, **rdcscfp; + ngx_conf_t pcf; + void *mconf, **srv_conf; + ngx_uint_t i, ci; + char *rv = NULL; + + rdcmcf = conf; + + /* srv_conf is temp cause hdcscf does not created now */ + srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module); + if (srv_conf == NULL) { + return NGX_CONF_ERROR; + } + + for (i = 0; cf->cycle->modules[i]; ++i) { + if (cf->cycle->modules[i]->type != NGX_RTMP_MODULE) { + continue; + } + + module = (ngx_rtmp_dynamic_module_t *) + cf->cycle->modules[i]->spare_hook0; + if (module == NULL) { + continue; + } + + if (module->create_srv_conf) { + mconf = module->create_srv_conf(cf); + if (mconf == NULL) { + return NGX_CONF_ERROR; + } + + srv_conf[cf->cycle->modules[i]->ctx_index] = mconf; + } + } + + rdcscf = srv_conf[ngx_rtmp_dynamic_core_module.ctx_index]; + rdcscf->srv_conf = srv_conf; + ngx_map_init(&rdcscf->app_conf, ngx_map_hash_str, ngx_cmp_str); + + /* save hdcscf into hdcmcf */ + rdcscfp = ngx_array_push(&rdcmcf->servers); + if (rdcscfp == NULL) { + return NGX_CONF_ERROR; + } + *rdcscfp = rdcscf; + + pcf = *cf; + cf->ctx = rdcscf->srv_conf; + cf->cmd_type = NGX_RTMP_SRV_CONF; + + if (ngx_dynamic_conf_parse(cf, 0) != NGX_OK) { + goto failed; + } + + for (i = 0; cf->cycle->modules[i]; ++i) { + if (cf->cycle->modules[i]->type != NGX_RTMP_MODULE) { + continue; + } + + module = (ngx_rtmp_dynamic_module_t *) + cf->cycle->modules[i]->spare_hook0; + ci = cf->cycle->modules[i]->ctx_index; + if (module == NULL) { + continue; + } + + if (module->init_srv_conf) { + rv = module->init_srv_conf(cf, rdcscf->srv_conf[ci]); + if (rv != NGX_CONF_OK) { + goto failed; + } + } + } + + *cf = pcf; + + return NGX_CONF_OK; + +failed: + *cf = pcf; + + if (rv) { + return rv; + } + + return NGX_CONF_ERROR; +} + +static char * +ngx_rtmp_dynamic_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_rtmp_dynamic_conf_t *rdccf; + ngx_rtmp_dynamic_module_t *module; + ngx_conf_t pcf; + ngx_uint_t i, ci; + char *rv = NULL; + + rdccf = conf; + + /* create main_conf ctx */ + rdccf->main_conf = ngx_pcalloc(cf->pool, sizeof(void *) + * ngx_rtmp_max_module); + if (rdccf->main_conf == NULL) { + return NGX_CONF_ERROR; + } + + /* create http dynamic conf for all http module */ + for (i = 0; cf->cycle->modules[i]; ++i) { + if (cf->cycle->modules[i]->type != NGX_RTMP_MODULE) { + continue; + } + + module = (ngx_rtmp_dynamic_module_t *) + cf->cycle->modules[i]->spare_hook0; + ci = cf->cycle->modules[i]->ctx_index; + + if (module == NULL) { + continue; + } + + if (module->create_main_conf) { + rdccf->main_conf[ci] = module->create_main_conf(cf); + if (rdccf->main_conf[ci] == NULL) { + return NGX_CONF_ERROR; + } + } + } + + /* save conf for recovery */ + pcf = *cf; + + cf->ctx = rdccf->main_conf; + cf->module_type = NGX_RTMP_MODULE; + cf->cmd_type = NGX_RTMP_MAIN_CONF; + + if (ngx_dynamic_conf_parse(cf, 0) != NGX_OK) { + goto failed; + } + + for (i = 0; cf->cycle->modules[i]; ++i) { + if (cf->cycle->modules[i]->type != NGX_RTMP_MODULE) { + continue; + } + + module = (ngx_rtmp_dynamic_module_t *) + cf->cycle->modules[i]->spare_hook0; + ci = cf->cycle->modules[i]->ctx_index; + + if (module == NULL) { + continue; + } + + if (module->init_main_conf) { + rv = module->init_main_conf(cf, rdccf->main_conf[ci]); + if (rv != NGX_CONF_OK) { + goto failed; + } + } + } + + if (ngx_rtmp_dynamic_core_init_virtual_servers(cf, rdccf) != NGX_OK) { + goto failed; + } + + *cf = pcf; + + return NGX_CONF_OK; + +failed: + *cf = pcf; + + if (rv) { + return rv; + } + + return NGX_CONF_ERROR; +} + + +/* interface from here */ + +void * +ngx_rtmp_get_module_main_dconf(ngx_rtmp_session_t *s, ngx_module_t *m) +{ + ngx_rtmp_dynamic_conf_t *rdcf; + + rdcf = ngx_get_dconf(&ngx_rtmp_dynamic_module); + if (rdcf == NULL || rdcf->main_conf == NULL) { + return NULL; + } + + return rdcf->main_conf[m->ctx_index]; +} + +void * +ngx_rtmp_get_module_srv_dconf(ngx_rtmp_session_t *s, ngx_module_t *m) +{ + ngx_rtmp_dynamic_core_main_conf_t *rdcmcf; + ngx_rtmp_dynamic_core_srv_conf_t *rdcscf; + ngx_int_t rc; + + rdcmcf = ngx_rtmp_get_module_main_dconf(s, &ngx_rtmp_dynamic_core_module); + if (rdcmcf == NULL) { + return NULL; + } + + rc = ngx_rtmp_dynamic_core_find_virtual_server(&s->domain, rdcmcf, &rdcscf); + switch (rc) { + case NGX_ERROR: + return NULL; + case NGX_DECLINED: /* virtual server not found */ + return rdcmcf->default_server->srv_conf[m->ctx_index]; + default: + return rdcscf->srv_conf[m->ctx_index]; + } +} + +void * +ngx_rtmp_get_module_app_dconf(ngx_rtmp_session_t *s, ngx_module_t *m) +{ + ngx_rtmp_dynamic_core_srv_conf_t *rdcscf; + ngx_rtmp_dynamic_core_app_conf_t *rdcacf; + ngx_int_t rc; + + rdcscf = ngx_rtmp_get_module_srv_dconf(s, &ngx_rtmp_dynamic_core_module); + if (rdcscf == NULL) { + return NULL; + } + + rdcacf = NULL; + rc = ngx_rtmp_dynamic_core_find_application(s, rdcscf, &rdcacf); + if (rc == NGX_ERROR) { + return NULL; + } + + if (rdcacf) { + return rdcacf->app_conf[m->ctx_index]; + } + + return NULL; +} + +void +ngx_rmtp_get_serverid_by_domain(ngx_str_t *serverid, ngx_str_t *domain) +{ + ngx_rtmp_dynamic_conf_t *rdcf; + ngx_rtmp_dynamic_core_main_conf_t *rdcmcf; + ngx_rtmp_dynamic_core_srv_conf_t *rdcscf; + ngx_rtmp_core_srv_dconf_t *rcsdcf; + + rdcf = ngx_get_dconf(&ngx_rtmp_dynamic_module); + if (rdcf == NULL || rdcf->main_conf) { + goto notfound; + } + + rdcmcf = rdcf->main_conf[ngx_rtmp_dynamic_core_module.ctx_index]; + if (rdcmcf == NULL) { + goto notfound; + } + + ngx_rtmp_dynamic_core_find_virtual_server(domain, rdcmcf, &rdcscf); + if (rdcscf && rdcscf->srv_conf) { + rcsdcf = rdcscf->srv_conf[ngx_rtmp_core_module.ctx_index]; + if (rcsdcf && rcsdcf->serverid.len) { + *serverid = rcsdcf->serverid; + return; + } + } + +notfound: + *serverid = *domain; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_dynamic.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_dynamic.h new file mode 100644 index 0000000000..f74c724388 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_dynamic.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#ifndef _NGX_RTMP_DYNAMIC_H_INCLUDED_ +#define _NGX_RTMP_DYNAMIC_H_INCLUDED_ + + +#include +#include +#include "ngx_map.h" +#include "ngx_dynamic_conf.h" +#include "ngx_rtmp.h" + + +typedef struct { + void *(*create_main_conf)(ngx_conf_t *cf); + char *(*init_main_conf)(ngx_conf_t *cf, void *conf); + + void *(*create_srv_conf)(ngx_conf_t *cf); + char *(*init_srv_conf)(ngx_conf_t *cf, void *conf); + + void *(*create_app_conf)(ngx_conf_t *cf); + char *(*init_app_conf)(ngx_conf_t *cf, void *conf); +} ngx_rtmp_dynamic_module_t; + + +void *ngx_rtmp_get_module_main_dconf(ngx_rtmp_session_t *s, ngx_module_t *m); +void *ngx_rtmp_get_module_srv_dconf(ngx_rtmp_session_t *s, ngx_module_t *m); +void *ngx_rtmp_get_module_app_dconf(ngx_rtmp_session_t *s, ngx_module_t *m); + +void ngx_rmtp_get_serverid_by_domain(ngx_str_t *serverid, ngx_str_t *domain); + + +#endif diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_eval.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_eval.c new file mode 100644 index 0000000000..d79d647298 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_eval.c @@ -0,0 +1,298 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp_eval.h" + + +#define NGX_RTMP_EVAL_BUFLEN 16 + + +static void +ngx_rtmp_eval_session_str(void *ctx, ngx_rtmp_eval_t *e, ngx_str_t *ret) +{ + *ret = *(ngx_str_t *) ((u_char *) ctx + e->offset); +} + + +static void +ngx_rtmp_eval_connection_str(void *ctx, ngx_rtmp_eval_t *e, ngx_str_t *ret) +{ + ngx_rtmp_session_t *s = ctx; + + *ret = *(ngx_str_t *) ((u_char *) s->connection + e->offset); +} + + +ngx_rtmp_eval_t ngx_rtmp_eval_session[] = { + + { ngx_string("app"), + ngx_rtmp_eval_session_str, + offsetof(ngx_rtmp_session_t, app) }, + + { ngx_string("flashver"), + ngx_rtmp_eval_session_str, + offsetof(ngx_rtmp_session_t, flashver) }, + + { ngx_string("swfurl"), + ngx_rtmp_eval_session_str, + offsetof(ngx_rtmp_session_t, swf_url) }, + + { ngx_string("tcurl"), + ngx_rtmp_eval_session_str, + offsetof(ngx_rtmp_session_t, tc_url) }, + + { ngx_string("pageurl"), + ngx_rtmp_eval_session_str, + offsetof(ngx_rtmp_session_t, page_url) }, + + { ngx_string("addr"), + ngx_rtmp_eval_connection_str, + offsetof(ngx_connection_t, addr_text) }, + + ngx_rtmp_null_eval +}; + + +static void +ngx_rtmp_eval_append(ngx_buf_t *b, void *data, size_t len, ngx_log_t *log) +{ + size_t buf_len; + + if (b->last + len > b->end) { + buf_len = 2 * (b->last - b->pos) + len; + + b->start = ngx_alloc(buf_len, log); + if (b->start == NULL) { + return; + } + + b->last = ngx_cpymem(b->start, b->pos, b->last - b->pos); + b->pos = b->start; + b->end = b->start + buf_len; + } + + b->last = ngx_cpymem(b->last, data, len); +} + + +static void +ngx_rtmp_eval_append_var(void *ctx, ngx_buf_t *b, ngx_rtmp_eval_t **e, + ngx_str_t *name, ngx_log_t *log) +{ + ngx_uint_t k; + ngx_str_t v; + ngx_rtmp_eval_t *ee; + + for (; *e; ++e) { + for (k = 0, ee = *e; ee->handler; ++k, ++ee) { + if (ee->name.len == name->len && + ngx_memcmp(ee->name.data, name->data, name->len) == 0) + { + ee->handler(ctx, ee, &v); + ngx_rtmp_eval_append(b, v.data, v.len, log); + } + } + } +} + + +ngx_int_t +ngx_rtmp_eval(void *ctx, ngx_str_t *in, ngx_rtmp_eval_t **e, ngx_str_t *out, + ngx_log_t *log) +{ + u_char c, *p; + ngx_str_t name; + ngx_buf_t b; + ngx_uint_t n; + + enum { + NORMAL, + ESCAPE, + NAME, + SNAME + } state = NORMAL; + + b.pos = b.last = b.start = ngx_alloc(NGX_RTMP_EVAL_BUFLEN, log); + if (b.pos == NULL) { + return NGX_ERROR; + } + + b.end = b.pos + NGX_RTMP_EVAL_BUFLEN; + name.data = NULL; + + for (n = 0; n < in->len; ++n) { + p = &in->data[n]; + c = *p; + + switch (state) { + case SNAME: + if (c != '}') { + continue; + } + + name.len = p - name.data; + ngx_rtmp_eval_append_var(ctx, &b, e, &name, log); + + state = NORMAL; + + continue; + + case NAME: + if (c == '{' && name.data == p) { + ++name.data; + state = SNAME; + continue; + } + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { + continue; + } + + name.len = p - name.data; + ngx_rtmp_eval_append_var(ctx, &b, e, &name, log); + switch (c) { + case '$': + name.data = p + 1; + state = NAME; + continue; + case '\\': + state = ESCAPE; + continue; + } + + ngx_rtmp_eval_append(&b, &c, 1, log); + state = NORMAL; + break; + + case NORMAL: + switch (c) { + case '$': + name.data = p + 1; + state = NAME; + continue; + case '\\': + state = ESCAPE; + continue; + } + ngx_rtmp_eval_append(&b, &c, 1, log); + state = NORMAL; + break; + + case ESCAPE: + ngx_rtmp_eval_append(&b, &c, 1, log); + state = NORMAL; + break; + + } + } + + if (state == NAME) { + p = &in->data[n]; + name.len = p - name.data; + ngx_rtmp_eval_append_var(ctx, &b, e, &name, log); + } + + c = 0; + ngx_rtmp_eval_append(&b, &c, 1, log); + + out->data = b.pos; + out->len = b.last - b.pos - 1; + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_eval_streams(ngx_str_t *in) +{ +#if !(NGX_WIN32) + ngx_int_t mode, create, v, close_src; + ngx_fd_t dst, src; + u_char *path; + + path = in->data; + + while (*path >= '0' && *path <= '9') { + path++; + } + + switch ((char) *path) { + + case '>': + + v = (path == in->data ? 1 : ngx_atoi(in->data, path - in->data)); + if (v == NGX_ERROR) { + return NGX_ERROR; + } + + dst = (ngx_fd_t) v; + mode = NGX_FILE_WRONLY; + create = NGX_FILE_TRUNCATE; + path++; + + if (*path == (u_char) '>') { + mode = NGX_FILE_APPEND; + create = NGX_FILE_CREATE_OR_OPEN; + path++; + } + + break; + + case '<': + + v = (path == in->data ? 0 : ngx_atoi(in->data, path - in->data)); + if (v == NGX_ERROR) { + return NGX_ERROR; + } + + dst = (ngx_fd_t) v; + mode = NGX_FILE_RDONLY; + create = NGX_FILE_OPEN; + path++; + + break; + + default: + + return NGX_DONE; + } + + if (*path == (u_char) '&') { + + path++; + v = ngx_atoi(path, in->data + in->len - path); + if (v == NGX_ERROR) { + return NGX_ERROR; + } + src = (ngx_fd_t) v; + close_src = 0; + + } else { + + src = ngx_open_file(path, mode, create, NGX_FILE_DEFAULT_ACCESS); + if (src == NGX_INVALID_FILE) { + return NGX_ERROR; + } + close_src = 1; + + } + + if (src == dst) { + return NGX_OK; + } + + dup2(src, dst); + + if (close_src) { + ngx_close_file(src); + } + return NGX_OK; + +#else + return NGX_DONE; +#endif +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_eval.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_eval.h new file mode 100644 index 0000000000..b05d16b111 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_eval.h @@ -0,0 +1,44 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#ifndef _NGX_RTMP_EVAL_H_INCLUDED_ +#define _NGX_RTMP_EVAL_H_INCLUDED_ + + +#include +#include +#include "ngx_rtmp.h" + + +typedef struct ngx_rtmp_eval_s ngx_rtmp_eval_t; + + +typedef void (* ngx_rtmp_eval_pt)(void *ctx, ngx_rtmp_eval_t *e, + ngx_str_t *ret); + + +struct ngx_rtmp_eval_s { + ngx_str_t name; + ngx_rtmp_eval_pt handler; + ngx_uint_t offset; +}; + + +#define ngx_rtmp_null_eval { ngx_null_string, NULL, 0 } + + +/* standard session eval variables */ +extern ngx_rtmp_eval_t ngx_rtmp_eval_session[]; + + +ngx_int_t ngx_rtmp_eval(void *ctx, ngx_str_t *in, ngx_rtmp_eval_t **e, + ngx_str_t *out, ngx_log_t *log); + + +ngx_int_t ngx_rtmp_eval_streams(ngx_str_t *in); + + +#endif /* _NGX_RTMP_EVAL_H_INCLUDED_ */ diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_exec_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_exec_module.c new file mode 100644 index 0000000000..2d6c2fed3f --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_exec_module.c @@ -0,0 +1,1551 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp_cmd_module.h" +#include "ngx_rtmp_eval.h" +#include "ngx_poold.h" +#include + +#ifdef NGX_LINUX +#include +#endif + + +#if !(NGX_WIN32) +static ngx_rtmp_publish_pt next_publish; +static ngx_rtmp_play_pt next_play; +static ngx_rtmp_close_stream_pt next_close_stream; +#endif + + +static ngx_int_t ngx_rtmp_exec_init_process(ngx_cycle_t *cycle); +static ngx_int_t ngx_rtmp_exec_postconfiguration(ngx_conf_t *cf); +static void * ngx_rtmp_exec_create_main_conf(ngx_conf_t *cf); +static char * ngx_rtmp_exec_init_main_conf(ngx_conf_t *cf, void *conf); +static void * ngx_rtmp_exec_create_app_conf(ngx_conf_t *cf); +static char * ngx_rtmp_exec_merge_app_conf(ngx_conf_t *cf, + void *parent, void *child); +/*static char * ngx_rtmp_exec_block(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf);*/ +static char * ngx_rtmp_exec_conf(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char *ngx_rtmp_exec_kill_signal(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + + +#define NGX_RTMP_EXEC_RESPAWN 0x01 +#define NGX_RTMP_EXEC_KILL 0x02 + + +#define NGX_RTMP_EXEC_PUBLISHING 0x01 +#define NGX_RTMP_EXEC_PLAYING 0x02 + + +enum { + NGX_RTMP_EXEC_PUSH, + NGX_RTMP_EXEC_PULL, + + NGX_RTMP_EXEC_PUBLISH, + NGX_RTMP_EXEC_PUBLISH_DONE, + NGX_RTMP_EXEC_PLAY, + NGX_RTMP_EXEC_PLAY_DONE, + NGX_RTMP_EXEC_RECORD_DONE, + + NGX_RTMP_EXEC_MAX, + + NGX_RTMP_EXEC_STATIC +}; + + +typedef struct { + ngx_str_t id; + ngx_uint_t type; + ngx_str_t cmd; + ngx_array_t args; /* ngx_str_t */ + ngx_array_t names; +} ngx_rtmp_exec_conf_t; + + +typedef struct { + ngx_rtmp_exec_conf_t *conf; + ngx_log_t *log; + ngx_rtmp_eval_t **eval; + void *eval_ctx; + unsigned active:1; + unsigned managed:1; + ngx_pid_t pid; + ngx_pid_t *save_pid; + int pipefd; + ngx_connection_t dummy_conn; /*needed by ngx_xxx_event*/ + ngx_event_t read_evt, write_evt; + ngx_event_t respawn_evt; + ngx_msec_t respawn_timeout; + ngx_int_t kill_signal; +} ngx_rtmp_exec_t; + + +typedef struct { + ngx_array_t static_conf; /* ngx_rtmp_exec_conf_t */ + ngx_array_t static_exec; /* ngx_rtmp_exec_t */ + ngx_msec_t respawn_timeout; + ngx_int_t kill_signal; + ngx_log_t *log; +} ngx_rtmp_exec_main_conf_t; + + +typedef struct ngx_rtmp_exec_pull_ctx_s ngx_rtmp_exec_pull_ctx_t; + +struct ngx_rtmp_exec_pull_ctx_s { + ngx_pool_t *pool; + ngx_uint_t counter; + ngx_str_t name; + ngx_str_t app; + ngx_array_t pull_exec; /* ngx_rtmp_exec_t */ + ngx_rtmp_exec_pull_ctx_t *next; +}; + + +typedef struct { + ngx_int_t active; + ngx_array_t conf[NGX_RTMP_EXEC_MAX]; + /* ngx_rtmp_exec_conf_t */ + ngx_flag_t respawn; + ngx_flag_t options; + ngx_uint_t nbuckets; + ngx_rtmp_exec_pull_ctx_t **pull; +} ngx_rtmp_exec_app_conf_t; + + +typedef struct { + ngx_uint_t flags; + ngx_str_t path; /* /tmp/rec/myfile-123.flv */ + ngx_str_t filename; /* myfile-123.flv */ + ngx_str_t basename; /* myfile-123 */ + ngx_str_t dirname; /* /tmp/rec */ + ngx_str_t recorder; + u_char name[NGX_RTMP_MAX_NAME]; + u_char args[NGX_RTMP_MAX_ARGS]; + u_char starttime[NGX_RTMP_MAX_NAME]; + ngx_array_t push_exec; /* ngx_rtmp_exec_t */ + ngx_rtmp_exec_pull_ctx_t *pull; +} ngx_rtmp_exec_ctx_t; + + +#if !(NGX_WIN32) +static void ngx_rtmp_exec_respawn(ngx_event_t *ev); +static ngx_int_t ngx_rtmp_exec_kill(ngx_rtmp_exec_t *e, ngx_int_t kill_signal); +static ngx_int_t ngx_rtmp_exec_run(ngx_rtmp_exec_t *e); +#endif + + +static ngx_command_t ngx_rtmp_exec_commands[] = { +/* + { ngx_string("exec_block"), + NGX_RTMP_APP_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS|NGX_CONF_TAKE1, + ngx_rtmp_exec_block, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, +*/ + { ngx_string("exec"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_exec_conf, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_exec_app_conf_t, conf) + + NGX_RTMP_EXEC_PUSH * sizeof(ngx_array_t), + NULL }, + + { ngx_string("exec_push"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_exec_conf, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_exec_app_conf_t, conf) + + NGX_RTMP_EXEC_PUSH * sizeof(ngx_array_t), + NULL }, + + { ngx_string("exec_pull"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_exec_conf, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_exec_app_conf_t, conf) + + NGX_RTMP_EXEC_PULL * sizeof(ngx_array_t), + NULL }, + + { ngx_string("exec_publish"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_exec_conf, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_exec_app_conf_t, conf) + + NGX_RTMP_EXEC_PUBLISH * sizeof(ngx_array_t), + NULL }, + + { ngx_string("exec_publish_done"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_exec_conf, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_exec_app_conf_t, conf) + + NGX_RTMP_EXEC_PUBLISH_DONE * sizeof(ngx_array_t), + NULL }, + + { ngx_string("exec_play"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_exec_conf, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_exec_app_conf_t, conf) + + NGX_RTMP_EXEC_PLAY * sizeof(ngx_array_t), + NULL }, + + { ngx_string("exec_play_done"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_exec_conf, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_exec_app_conf_t, conf) + + NGX_RTMP_EXEC_PLAY_DONE * sizeof(ngx_array_t), + NULL }, + + { ngx_string("exec_record_done"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_RTMP_REC_CONF| + NGX_CONF_1MORE, + ngx_rtmp_exec_conf, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_exec_app_conf_t, conf) + + NGX_RTMP_EXEC_RECORD_DONE * sizeof(ngx_array_t), + NULL }, + + { ngx_string("exec_static"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_exec_conf, + NGX_RTMP_MAIN_CONF_OFFSET, + offsetof(ngx_rtmp_exec_main_conf_t, static_conf), + NULL }, + + { ngx_string("respawn"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_exec_app_conf_t, respawn), + NULL }, + + { ngx_string("respawn_timeout"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_MAIN_CONF_OFFSET, + offsetof(ngx_rtmp_exec_main_conf_t, respawn_timeout), + NULL }, + + { ngx_string("exec_kill_signal"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_rtmp_exec_kill_signal, + NGX_RTMP_MAIN_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("exec_options"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_exec_app_conf_t, options), + NULL }, + + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_rtmp_exec_module_ctx = { + NULL, /* preconfiguration */ + ngx_rtmp_exec_postconfiguration, /* postconfiguration */ + ngx_rtmp_exec_create_main_conf, /* create main configuration */ + ngx_rtmp_exec_init_main_conf, /* init main configuration */ + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + ngx_rtmp_exec_create_app_conf, /* create app configuration */ + ngx_rtmp_exec_merge_app_conf /* merge app configuration */ +}; + + +ngx_module_t ngx_rtmp_exec_module = { + NGX_MODULE_V1, + &ngx_rtmp_exec_module_ctx, /* module context */ + ngx_rtmp_exec_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + ngx_rtmp_exec_init_process, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void +ngx_rtmp_exec_eval_ctx_cstr(void *sctx, ngx_rtmp_eval_t *e, ngx_str_t *ret) +{ + ngx_rtmp_session_t *s = sctx; + + ngx_rtmp_exec_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_exec_module); + if (ctx == NULL) { + ret->len = 0; + return; + } + + ret->data = (u_char *) ctx + e->offset; + ret->len = ngx_strlen(ret->data); +} + + +static void +ngx_rtmp_exec_eval_ctx_str(void *sctx, ngx_rtmp_eval_t *e, ngx_str_t *ret) +{ + ngx_rtmp_session_t *s = sctx; + + ngx_rtmp_exec_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_exec_module); + if (ctx == NULL) { + ret->len = 0; + return; + } + + *ret = * (ngx_str_t *) ((u_char *) ctx + e->offset); +} + + +static void +ngx_rtmp_exec_eval_pctx_str(void *ctx, ngx_rtmp_eval_t *e, ngx_str_t *ret) +{ + *ret = *(ngx_str_t *) ((u_char *) ctx + e->offset); +} + + +static ngx_rtmp_eval_t ngx_rtmp_exec_push_specific_eval[] = { + + { ngx_string("name"), + ngx_rtmp_exec_eval_ctx_cstr, + offsetof(ngx_rtmp_exec_ctx_t, name) }, + + { ngx_string("args"), + ngx_rtmp_exec_eval_ctx_cstr, + offsetof(ngx_rtmp_exec_ctx_t, args) }, + + ngx_rtmp_null_eval +}; + + +static ngx_rtmp_eval_t * ngx_rtmp_exec_push_eval[] = { + ngx_rtmp_eval_session, + ngx_rtmp_exec_push_specific_eval, + NULL +}; + + +static ngx_rtmp_eval_t ngx_rtmp_exec_pull_specific_eval[] = { + + { ngx_string("name"), + ngx_rtmp_exec_eval_pctx_str, + offsetof(ngx_rtmp_exec_pull_ctx_t, name) }, + + { ngx_string("app"), + ngx_rtmp_exec_eval_pctx_str, + offsetof(ngx_rtmp_exec_pull_ctx_t, app) }, + + ngx_rtmp_null_eval +}; + + +static ngx_rtmp_eval_t * ngx_rtmp_exec_pull_eval[] = { + ngx_rtmp_exec_pull_specific_eval, + NULL +}; + + +static ngx_rtmp_eval_t ngx_rtmp_exec_event_specific_eval[] = { + + { ngx_string("name"), + ngx_rtmp_exec_eval_ctx_cstr, + offsetof(ngx_rtmp_exec_ctx_t, name) }, + + { ngx_string("args"), + ngx_rtmp_exec_eval_ctx_cstr, + offsetof(ngx_rtmp_exec_ctx_t, args) }, + + { ngx_string("path"), + ngx_rtmp_exec_eval_ctx_str, + offsetof(ngx_rtmp_exec_ctx_t, path) }, + + { ngx_string("filename"), + ngx_rtmp_exec_eval_ctx_str, + offsetof(ngx_rtmp_exec_ctx_t, filename) }, + + { ngx_string("basename"), + ngx_rtmp_exec_eval_ctx_str, + offsetof(ngx_rtmp_exec_ctx_t, basename) }, + + { ngx_string("dirname"), + ngx_rtmp_exec_eval_ctx_str, + offsetof(ngx_rtmp_exec_ctx_t, dirname) }, + + { ngx_string("recorder"), + ngx_rtmp_exec_eval_ctx_str, + offsetof(ngx_rtmp_exec_ctx_t, recorder) }, + + { ngx_string("starttime"), + ngx_rtmp_exec_eval_ctx_cstr, + offsetof(ngx_rtmp_exec_ctx_t, starttime) }, + + ngx_rtmp_null_eval +}; + + +static ngx_rtmp_eval_t * ngx_rtmp_exec_event_eval[] = { + ngx_rtmp_eval_session, + ngx_rtmp_exec_event_specific_eval, + NULL +}; + + +static void * +ngx_rtmp_exec_create_main_conf(ngx_conf_t *cf) +{ + ngx_rtmp_exec_main_conf_t *emcf; + + emcf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_exec_main_conf_t)); + if (emcf == NULL) { + return NULL; + } + + emcf->respawn_timeout = NGX_CONF_UNSET_MSEC; + emcf->kill_signal = NGX_CONF_UNSET; + + if (ngx_array_init(&emcf->static_conf, cf->pool, 1, + sizeof(ngx_rtmp_exec_conf_t)) != NGX_OK) + { + return NULL; + } + + return emcf; +} + + +static char * +ngx_rtmp_exec_init_main_conf(ngx_conf_t *cf, void *conf) +{ + ngx_rtmp_exec_main_conf_t *emcf = conf; + ngx_rtmp_exec_conf_t *ec; + ngx_rtmp_exec_t *e; + ngx_uint_t n; + + if (emcf->respawn_timeout == NGX_CONF_UNSET_MSEC) { + emcf->respawn_timeout = 5000; + } + +#if !(NGX_WIN32) + if (emcf->kill_signal == NGX_CONF_UNSET) { + emcf->kill_signal = SIGKILL; + } +#endif + + if (ngx_array_init(&emcf->static_exec, cf->pool, + emcf->static_conf.nelts, + sizeof(ngx_rtmp_exec_t)) != NGX_OK) + { + return NGX_CONF_ERROR; + } + + e = ngx_array_push_n(&emcf->static_exec, emcf->static_conf.nelts); + if (e == NULL) { + return NGX_CONF_ERROR; + } + + emcf->log = &cf->cycle->new_log; + + ec = emcf->static_conf.elts; + + for (n = 0; n < emcf->static_conf.nelts; n++, e++, ec++) { + ngx_memzero(e, sizeof(*e)); + e->conf = ec; + e->managed = 1; + e->log = emcf->log; + e->respawn_timeout = emcf->respawn_timeout; + e->kill_signal = emcf->kill_signal; + } + + return NGX_CONF_OK; +} + + +static void * +ngx_rtmp_exec_create_app_conf(ngx_conf_t *cf) +{ + ngx_rtmp_exec_app_conf_t *eacf; + + eacf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_exec_app_conf_t)); + if (eacf == NULL) { + return NULL; + } + + eacf->respawn = NGX_CONF_UNSET; + eacf->options = NGX_CONF_UNSET; + eacf->nbuckets = NGX_CONF_UNSET_UINT; + + return eacf; +} + + +static ngx_int_t +ngx_rtmp_exec_merge_confs(ngx_array_t *conf, ngx_array_t *prev) +{ + size_t n; + ngx_rtmp_exec_conf_t *ec, *pec; + + if (prev->nelts == 0) { + return NGX_OK; + } + + if (conf->nelts == 0) { + *conf = *prev; + return NGX_OK; + } + + ec = ngx_array_push_n(conf, prev->nelts); + if (ec == NULL) { + return NGX_ERROR; + } + + pec = prev->elts; + for (n = 0; n < prev->nelts; n++, ec++, pec++) { + *ec = *pec; + } + + return NGX_OK; +} + + +static char * +ngx_rtmp_exec_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_rtmp_exec_app_conf_t *prev = parent; + ngx_rtmp_exec_app_conf_t *conf = child; + + ngx_uint_t n; + + ngx_conf_merge_value(conf->respawn, prev->respawn, 1); + ngx_conf_merge_uint_value(conf->nbuckets, prev->nbuckets, 1024); + + for (n = 0; n < NGX_RTMP_EXEC_MAX; n++) { + if (ngx_rtmp_exec_merge_confs(&conf->conf[n], &prev->conf[n]) != NGX_OK) + { + return NGX_CONF_ERROR; + } + + if (conf->conf[n].nelts) { + conf->active = 1; + prev->active = 1; + } + } + + if (conf->conf[NGX_RTMP_EXEC_PULL].nelts > 0) { + conf->pull = ngx_pcalloc(cf->pool, sizeof(void *) * conf->nbuckets); + if (conf->pull == NULL) { + return NGX_CONF_ERROR; + } + } + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_rtmp_exec_init_process(ngx_cycle_t *cycle) +{ +#if !(NGX_WIN32) + ngx_rtmp_core_main_conf_t *cmcf = ngx_rtmp_core_main_conf; + ngx_rtmp_core_srv_conf_t **cscf; + ngx_rtmp_conf_ctx_t *cctx; + ngx_rtmp_exec_main_conf_t *emcf; + ngx_rtmp_exec_t *e; + ngx_uint_t n; + + if (cmcf == NULL || cmcf->servers.nelts == 0) { + return NGX_OK; + } + + /* execs are always started by the first worker */ + if (ngx_process_slot) { + return NGX_OK; + } + + cscf = cmcf->servers.elts; + cctx = (*cscf)->ctx; + emcf = cctx->main_conf[ngx_rtmp_exec_module.ctx_index]; + + /* FreeBSD note: + * When worker is restarted, child process (ffmpeg) will + * not be terminated if it's connected to another + * (still alive) worker. That leads to starting + * another instance of exec_static process. + * Need to kill previously started processes. + * + * On Linux "prctl" syscall is used to kill child + * when nginx worker is terminated. + */ + + e = emcf->static_exec.elts; + for (n = 0; n < emcf->static_exec.nelts; ++n, ++e) { + e->respawn_evt.data = e; + e->respawn_evt.log = e->log; + e->respawn_evt.handler = ngx_rtmp_exec_respawn; + ngx_post_event((&e->respawn_evt), &ngx_rtmp_init_queue); + } +#endif + + return NGX_OK; +} + + +#if !(NGX_WIN32) +static void +ngx_rtmp_exec_respawn(ngx_event_t *ev) +{ + ngx_rtmp_exec_run((ngx_rtmp_exec_t *) ev->data); +} + + +static void +ngx_rtmp_exec_child_dead(ngx_event_t *ev) +{ + ngx_connection_t *dummy_conn = ev->data; + ngx_rtmp_exec_t *e; + + e = dummy_conn->data; + + ngx_log_error(NGX_LOG_INFO, e->log, 0, + "exec: child %ui exited; %s", (ngx_int_t) e->pid, + e->respawn_timeout == NGX_CONF_UNSET_MSEC ? "respawning" : + "ignoring"); + + ngx_rtmp_exec_kill(e, 0); + + if (e->respawn_timeout == NGX_CONF_UNSET_MSEC) { + return; + } + + if (e->respawn_timeout == 0) { + ngx_rtmp_exec_run(e); + return; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, e->log, 0, + "exec: shedule respawn %Mmsec", e->respawn_timeout); + + e->respawn_evt.data = e; + e->respawn_evt.log = e->log; + e->respawn_evt.handler = ngx_rtmp_exec_respawn; + + ngx_add_timer(&e->respawn_evt, e->respawn_timeout); +} + + +static ngx_int_t +ngx_rtmp_exec_kill(ngx_rtmp_exec_t *e, ngx_int_t kill_signal) +{ + if (e->respawn_evt.timer_set) { + ngx_del_timer(&e->respawn_evt); + } + + if (e->read_evt.active) { + ngx_del_event(&e->read_evt, NGX_READ_EVENT, 0); + } + + if (e->active == 0) { + return NGX_OK; + } + + ngx_log_error(NGX_LOG_INFO, e->log, 0, + "exec: terminating child %ui", (ngx_int_t) e->pid); + + e->active = 0; + close(e->pipefd); + if (e->save_pid) { + *e->save_pid = NGX_INVALID_PID; + } + + if (kill_signal == 0) { + return NGX_OK; + } + + if (kill(e->pid, kill_signal) == -1) { + ngx_log_error(NGX_LOG_INFO, e->log, ngx_errno, + "exec: kill failed pid=%i", (ngx_int_t) e->pid); + } else { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, e->log, 0, + "exec: killed pid=%i", (ngx_int_t) e->pid); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_exec_run(ngx_rtmp_exec_t *e) +{ + int fd, ret, maxfd, pipefd[2]; + char **args, **arg_out; + ngx_pid_t pid; + ngx_str_t *arg_in, a; + ngx_uint_t n; + ngx_rtmp_exec_conf_t *ec; + + ec = e->conf; + + ngx_log_error(NGX_LOG_INFO, e->log, 0, + "exec: starting %s child '%V'", + e->managed ? "managed" : "unmanaged", &ec->cmd); + + pipefd[0] = -1; + pipefd[1] = -1; + + if (e->managed) { + + if (e->active) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, e->log, 0, + "exec: already active '%V'", &ec->cmd); + return NGX_OK; + } + + if (pipe(pipefd) == -1) { + ngx_log_error(NGX_LOG_INFO, e->log, ngx_errno, + "exec: pipe failed"); + return NGX_ERROR; + } + + /* make pipe write end survive through exec */ + + ret = fcntl(pipefd[1], F_GETFD); + + if (ret != -1) { + ret &= ~FD_CLOEXEC; + ret = fcntl(pipefd[1], F_SETFD, ret); + } + + if (ret == -1) { + + close(pipefd[0]); + close(pipefd[1]); + + ngx_log_error(NGX_LOG_INFO, e->log, ngx_errno, + "exec: fcntl failed"); + + return NGX_ERROR; + } + } + + pid = fork(); + + switch (pid) { + + case -1: + + /* failure */ + + if (pipefd[0] != -1) { + close(pipefd[0]); + } + + if (pipefd[1] != -1) { + close(pipefd[1]); + } + + ngx_log_error(NGX_LOG_INFO, e->log, ngx_errno, + "exec: fork failed"); + + return NGX_ERROR; + + case 0: + + /* child */ + +#if (NGX_LINUX) + if (e->managed) { + prctl(PR_SET_PDEATHSIG, e->kill_signal, 0, 0, 0); + } +#endif + + /* close all descriptors but pipe write end */ + + maxfd = sysconf(_SC_OPEN_MAX); + for (fd = 0; fd < maxfd; ++fd) { + if (fd == pipefd[1]) { + continue; + } + + close(fd); + } + + fd = open("/dev/null", O_RDWR); + + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + + args = ngx_alloc((ec->args.nelts + 2) * sizeof(char *), e->log); + if (args == NULL) { + exit(1); + } + + arg_in = ec->args.elts; + arg_out = args; + *arg_out++ = (char *) ec->cmd.data; + + for (n = 0; n < ec->args.nelts; n++, ++arg_in) { + + if (e->eval == NULL) { + a = *arg_in; + } else { + ngx_rtmp_eval(e->eval_ctx, arg_in, e->eval, &a, e->log); + } + + if (ngx_rtmp_eval_streams(&a) != NGX_DONE) { + continue; + } + + *arg_out++ = (char *) a.data; + } + + *arg_out = NULL; + +#if (NGX_DEBUG) + { + char **p; + + for (p = args; *p; p++) { + ngx_write_fd(STDERR_FILENO, "'", 1); + ngx_write_fd(STDERR_FILENO, *p, strlen(*p)); + ngx_write_fd(STDERR_FILENO, "' ", 2); + } + + ngx_write_fd(STDERR_FILENO, "\n", 1); + } +#endif + + if (execvp((char *) ec->cmd.data, args) == -1) { + char *msg; + + msg = strerror(errno); + + ngx_write_fd(STDERR_FILENO, "execvp error: ", 14); + ngx_write_fd(STDERR_FILENO, msg, strlen(msg)); + ngx_write_fd(STDERR_FILENO, "\n", 1); + + exit(1); + } + + break; + + default: + + /* parent */ + + if (pipefd[1] != -1) { + close(pipefd[1]); + } + + if (pipefd[0] != -1) { + + e->active = 1; + e->pid = pid; + e->pipefd = pipefd[0]; + + if (e->save_pid) { + *e->save_pid = pid; + } + + e->dummy_conn.fd = e->pipefd; + e->dummy_conn.data = e; + e->dummy_conn.read = &e->read_evt; + e->dummy_conn.write = &e->write_evt; + e->read_evt.data = &e->dummy_conn; + e->write_evt.data = &e->dummy_conn; + + e->read_evt.log = e->log; + e->read_evt.handler = ngx_rtmp_exec_child_dead; + + if (ngx_add_event(&e->read_evt, NGX_READ_EVENT, 0) != NGX_OK) { + ngx_log_error(NGX_LOG_INFO, e->log, ngx_errno, + "exec: failed to add child control event"); + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, e->log, 0, + "exec: child '%V' started pid=%i", + &ec->cmd, (ngx_int_t) pid); + break; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_exec_init_ctx(ngx_rtmp_session_t *s, u_char name[NGX_RTMP_MAX_NAME], + u_char args[NGX_RTMP_MAX_ARGS], ngx_uint_t flags) +{ + ngx_uint_t n; + ngx_array_t *push_conf; + ngx_rtmp_exec_t *e; + ngx_rtmp_exec_ctx_t *ctx; + ngx_rtmp_exec_conf_t *ec; + ngx_rtmp_exec_app_conf_t *eacf; + ngx_rtmp_exec_main_conf_t *emcf; + time_t t; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_exec_module); + + if (ctx != NULL) { + goto done; + } + + ctx = ngx_pcalloc(s->pool, sizeof(ngx_rtmp_exec_ctx_t)); + + if (ctx == NULL) { + return NGX_ERROR; + } + + ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_exec_module); + + eacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_exec_module); + + emcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_exec_module); + + push_conf = &eacf->conf[NGX_RTMP_EXEC_PUSH]; + + if (push_conf->nelts > 0) { + + if (ngx_array_init(&ctx->push_exec, s->pool, + push_conf->nelts, + sizeof(ngx_rtmp_exec_t)) != NGX_OK) + { + return NGX_ERROR; + } + + e = ngx_array_push_n(&ctx->push_exec, push_conf->nelts); + + if (e == NULL) { + return NGX_ERROR; + } + + ec = push_conf->elts; + + for (n = 0; n < push_conf->nelts; n++, e++, ec++) { + ngx_memzero(e, sizeof(*e)); + e->conf = ec; + e->managed = 1; + e->log = s->log; + e->eval = ngx_rtmp_exec_push_eval; + e->eval_ctx = s; + e->kill_signal = emcf->kill_signal; + e->respawn_timeout = (eacf->respawn ? emcf->respawn_timeout : + NGX_CONF_UNSET_MSEC); + } + } + +done: + + ngx_memcpy(ctx->name, name, NGX_RTMP_MAX_NAME); + ngx_memcpy(ctx->args, args, NGX_RTMP_MAX_ARGS); + + t = time( 0 ); + strftime((char *)(ctx->starttime), sizeof(ctx->starttime), + "%Y%m%d%H%M%S", localtime(&t)); + + ctx->flags |= flags; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_exec_init_pull_ctx(ngx_rtmp_session_t *s, + u_char name[NGX_RTMP_MAX_NAME]) +{ + size_t len; + ngx_uint_t n; + ngx_pool_t *pool; + ngx_array_t *pull_conf; + ngx_rtmp_exec_t *e; + ngx_rtmp_exec_ctx_t *ctx; + ngx_rtmp_exec_conf_t *ec; + ngx_rtmp_exec_pull_ctx_t *pctx, **ppctx; + ngx_rtmp_exec_app_conf_t *eacf; + ngx_rtmp_exec_main_conf_t *emcf; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_exec_module); + if (ctx->pull != NULL) { + return NGX_OK; + } + + eacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_exec_module); + + pull_conf = &eacf->conf[NGX_RTMP_EXEC_PULL]; + + if (pull_conf->nelts == 0) { + return NGX_OK; + } + + emcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_exec_module); + + len = ngx_strlen(name); + + ppctx = &eacf->pull[ngx_hash_key(name, len) % eacf->nbuckets]; + + for (; *ppctx; ppctx = &(*ppctx)->next) { + pctx = *ppctx; + + if (pctx->name.len == len && + ngx_strncmp(name, pctx->name.data, len) == 0) + { + goto done; + } + } + + pool = NGX_CREATE_POOL(4096, emcf->log); + if (pool == NULL) { + return NGX_ERROR; + } + + pctx = ngx_pcalloc(pool, sizeof(ngx_rtmp_exec_pull_ctx_t)); + if (pctx == NULL) { + goto error; + } + + pctx->pool = pool; + pctx->name.len = len; + pctx->name.data = ngx_palloc(pool, len); + + if (pctx->name.data == NULL) { + goto error; + } + + ngx_memcpy(pctx->name.data, name, len); + + pctx->app.len = s->app.len; + pctx->app.data = ngx_palloc(pool, s->app.len); + + if (pctx->app.data == NULL) { + goto error; + } + + ngx_memcpy(pctx->app.data, s->app.data, s->app.len); + + if (ngx_array_init(&pctx->pull_exec, pool, pull_conf->nelts, + sizeof(ngx_rtmp_exec_t)) != NGX_OK) + { + goto error; + } + + e = ngx_array_push_n(&pctx->pull_exec, pull_conf->nelts); + if (e == NULL) { + goto error; + } + + ec = pull_conf->elts; + for (n = 0; n < pull_conf->nelts; n++, e++, ec++) { + ngx_memzero(e, sizeof(*e)); + e->conf = ec; + e->managed = 1; + e->log = emcf->log; + e->eval = ngx_rtmp_exec_pull_eval; + e->eval_ctx = pctx; + e->kill_signal = emcf->kill_signal; + e->respawn_timeout = (eacf->respawn ? emcf->respawn_timeout : + NGX_CONF_UNSET_MSEC); + } + + *ppctx = pctx; + +done: + + ctx->pull = pctx; + ctx->pull->counter++; + + return NGX_OK; + +error: + + NGX_DESTROY_POOL(pool); + + return NGX_ERROR; +} + + +static ngx_int_t +ngx_rtmp_exec_filter(ngx_rtmp_session_t *s, ngx_rtmp_exec_conf_t *ec) +{ + size_t len; + ngx_str_t *v; + ngx_uint_t n; + ngx_rtmp_exec_ctx_t *ctx; + + if (ec->names.nelts == 0) { + return NGX_OK; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_exec_module); + + len = ngx_strlen(ctx->name); + + v = ec->names.elts; + for (n = 0; n < ec->names.nelts; n++, s++) { + if (v->len == len && ngx_strncmp(v->data, ctx->name, len) == 0) { + return NGX_OK; + } + } + + return NGX_DECLINED; +} + + +static void +ngx_rtmp_exec_unmanaged(ngx_rtmp_session_t *s, ngx_array_t *e, const char *op) +{ + ngx_uint_t n; + ngx_rtmp_exec_t en; + ngx_rtmp_exec_conf_t *ec; + + if (e->nelts == 0) { + return; + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0, + "exec: %s %uz unmanaged command(s)", op, e->nelts); + + ec = e->elts; + for (n = 0; n < e->nelts; n++, ec++) { + if (ngx_rtmp_exec_filter(s, ec) != NGX_OK) { + continue; + } + + ngx_memzero(&en, sizeof(ngx_rtmp_exec_t)); + + en.conf = ec; + en.eval = ngx_rtmp_exec_event_eval; + en.eval_ctx = s; + en.log = s->log; + + ngx_rtmp_exec_run(&en); + } +} + + +static void +ngx_rtmp_exec_managed(ngx_rtmp_session_t *s, ngx_array_t *e, const char *op) +{ + ngx_uint_t n; + ngx_rtmp_exec_t *en; + + if (e->nelts == 0) { + return; + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0, + "exec: %s %uz managed command(s)", op, e->nelts); + + en = e->elts; + for (n = 0; n < e->nelts; n++, en++) { + if (ngx_rtmp_exec_filter(s, en->conf) == NGX_OK) { + ngx_rtmp_exec_run(en); + } + } +} + + +static ngx_int_t +ngx_rtmp_exec_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) +{ + ngx_rtmp_exec_ctx_t *ctx; + ngx_rtmp_exec_app_conf_t *eacf; + + eacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_exec_module); + + if (eacf == NULL || !eacf->active) { + goto next; + } + + if (s->interprocess) { + goto next; + } + + if (ngx_rtmp_exec_init_ctx(s, v->name, v->args, NGX_RTMP_EXEC_PUBLISHING) + != NGX_OK) + { + goto next; + } + + ngx_rtmp_exec_unmanaged(s, &eacf->conf[NGX_RTMP_EXEC_PUBLISH], "publish"); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_exec_module); + + ngx_rtmp_exec_managed(s, &ctx->push_exec, "push"); + +next: + return next_publish(s, v); +} + + +static ngx_int_t +ngx_rtmp_exec_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) +{ + ngx_rtmp_exec_ctx_t *ctx; + ngx_rtmp_exec_pull_ctx_t *pctx; + ngx_rtmp_exec_app_conf_t *eacf; + + eacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_exec_module); + + if (eacf == NULL || !eacf->active) { + goto next; + } + + if (ngx_rtmp_exec_init_ctx(s, v->name, v->args, NGX_RTMP_EXEC_PLAYING) + != NGX_OK) + { + goto next; + } + + ngx_rtmp_exec_unmanaged(s, &eacf->conf[NGX_RTMP_EXEC_PLAY], "play"); + + if (ngx_rtmp_exec_init_pull_ctx(s, v->name) != NGX_OK) { + goto next; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_exec_module); + pctx = ctx->pull; + + if (pctx && pctx->counter == 1) { + ngx_rtmp_exec_managed(s, &pctx->pull_exec, "pull"); + } + +next: + return next_play(s, v); +} + + +static ngx_int_t +ngx_rtmp_exec_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v) +{ + size_t n; + ngx_rtmp_exec_t *e; + ngx_rtmp_exec_ctx_t *ctx; + ngx_rtmp_exec_pull_ctx_t *pctx, **ppctx; + ngx_rtmp_exec_app_conf_t *eacf; + + eacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_exec_module); + if (eacf == NULL) { + goto next; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_exec_module); + if (ctx == NULL) { + goto next; + } + + if (ctx->flags & NGX_RTMP_EXEC_PUBLISHING) { + ngx_rtmp_exec_unmanaged(s, &eacf->conf[NGX_RTMP_EXEC_PUBLISH_DONE], + "publish_done"); + } + + if (ctx->flags & NGX_RTMP_EXEC_PLAYING) { + ngx_rtmp_exec_unmanaged(s, &eacf->conf[NGX_RTMP_EXEC_PLAY_DONE], + "play_done"); + } + + ctx->flags = 0; + + if (ctx->push_exec.nelts > 0) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "exec: delete %uz push command(s)", + ctx->push_exec.nelts); + + e = ctx->push_exec.elts; + for (n = 0; n < ctx->push_exec.nelts; n++, e++) { + ngx_rtmp_exec_kill(e, e->kill_signal); + } + } + + pctx = ctx->pull; + + if (pctx && --pctx->counter == 0) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "exec: delete %uz pull command(s)", + pctx->pull_exec.nelts); + + e = pctx->pull_exec.elts; + for (n = 0; n < pctx->pull_exec.nelts; n++, e++) { + ngx_rtmp_exec_kill(e, e->kill_signal); + } + + ppctx = &eacf->pull[ngx_hash_key(pctx->name.data, pctx->name.len) % + eacf->nbuckets]; + + for (; *ppctx; ppctx = &(*ppctx)->next) { + if (pctx == *ppctx) { + *ppctx = pctx->next; + break; + } + } + + NGX_DESTROY_POOL(pctx->pool); + } + + ctx->pull = NULL; + +next: + return next_close_stream(s, v); +} +#endif /* NGX_WIN32 */ + + +static char * +ngx_rtmp_exec_conf(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + char *p = conf; + + size_t n, nargs; + ngx_str_t *s, *value, v; + ngx_array_t *confs; + ngx_rtmp_exec_conf_t *ec; + ngx_rtmp_exec_app_conf_t *eacf; + + confs = (ngx_array_t *) (p + cmd->offset); + + eacf = ngx_rtmp_conf_get_module_app_conf(cf, ngx_rtmp_exec_module); + + if (confs->nalloc == 0 && ngx_array_init(confs, cf->pool, 1, + sizeof(ngx_rtmp_exec_conf_t)) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + value = cf->args->elts; + + ec = ngx_array_push(confs); + if (ec == NULL) { + return NGX_CONF_ERROR; + } + + ngx_memzero(ec, sizeof(ngx_rtmp_exec_conf_t)); + + /* type is undefined for explicit execs */ + + ec->type = NGX_CONF_UNSET_UINT; + ec->cmd = value[1]; + + if (ngx_array_init(&ec->names, cf->pool, 1, sizeof(ngx_str_t)) != NGX_OK) { + return NGX_CONF_ERROR; + } + + if (cf->args->nelts == 2) { + return NGX_CONF_OK; + } + + nargs = cf->args->nelts - 2; + if (ngx_array_init(&ec->args, cf->pool, nargs, sizeof(ngx_str_t)) != NGX_OK) + { + return NGX_CONF_ERROR; + } + + for (n = 2; n < cf->args->nelts; n++) { + + v = value[n]; + + if (eacf->options == 1) { + + if (v.len >= 5 && ngx_strncmp(v.data, "name=", 5) == 0) { + + s = ngx_array_push(&ec->names); + if (s == NULL) { + return NGX_CONF_ERROR; + } + + v.data += 5; + v.len -= 5; + + *s = v; + + continue; + } + } + + s = ngx_array_push(&ec->args); + if (s == NULL) { + return NGX_CONF_ERROR; + } + + *s = v; + } + + return NGX_CONF_OK; +} + +/* +static char * +ngx_rtmp_exec_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + char *rv; + ngx_str_t *value; + ngx_conf_t save; + ngx_array_t *confs; + ngx_rtmp_conf_ctx_t *ctx, *pctx; + ngx_rtmp_exec_conf_t *ec, *eec; + ngx_rtmp_exec_app_conf_t *eacf; + ngx_rtmp_exec_main_conf_t *emcf; + + value = cf->args->elts; + + eacf = ngx_rtmp_conf_get_module_app_conf(cf, ngx_rtmp_exec_module); + + emcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_exec_module); + + ctx = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_conf_ctx_t)); + if (ctx == NULL) { + return NGX_CONF_ERROR; + } + + pctx = cf->ctx; + + ctx->main_conf = pctx->main_conf; + ctx->srv_conf = pctx->srv_conf; + + ctx->app_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module); + if (ctx->app_conf == NULL) { + return NGX_CONF_ERROR; + } + + ec = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_exec_conf_t)); + if (ec == NULL) { + return NGX_CONF_ERROR; + } + + ec->id = value[1]; + ec->type = NGX_CONF_UNSET_UINT; + + ctx->app_conf[ngx_rtmp_exec_module.ctx_index] = ec; + + save = *cf; + + cf->ctx = ctx; + cf->cmd_type = NGX_RTMP_EXEC_CONF; + + rv = ngx_conf_parse(cf, NULL); + *cf= save; + + switch (ec->type) { + + case NGX_RTMP_EXEC_STATIC: + confs = &emcf->static_conf; + break; + + case NGX_CONF_UNSET_UINT: + return "unspecified exec type"; + + default: + confs = &eacf->conf[ec->type]; + } + + if (confs->nalloc == 0 && ngx_array_init(confs, cf->pool, 1, + sizeof(ngx_rtmp_exec_conf_t)) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + eec = ngx_array_push(confs); + if (eec == NULL) { + return NGX_CONF_ERROR; + } + + *eec = *ec; + + return rv; +} +*/ + +static char * +ngx_rtmp_exec_kill_signal(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_rtmp_exec_main_conf_t *emcf = conf; + + ngx_str_t *value; + + value = cf->args->elts; + value++; + + emcf->kill_signal = ngx_atoi(value->data, value->len); + if (emcf->kill_signal != NGX_ERROR) { + return NGX_CONF_OK; + } + +#define NGX_RMTP_EXEC_SIGNAL(name) \ + if (value->len == sizeof(#name) - 1 && \ + ngx_strncasecmp(value->data, (u_char *) #name, value->len) == 0) \ + { \ + emcf->kill_signal = SIG##name; \ + return NGX_CONF_OK; \ + } + + /* POSIX.1-1990 signals */ + +#if !(NGX_WIN32) + NGX_RMTP_EXEC_SIGNAL(HUP); + NGX_RMTP_EXEC_SIGNAL(INT); + NGX_RMTP_EXEC_SIGNAL(QUIT); + NGX_RMTP_EXEC_SIGNAL(ILL); + NGX_RMTP_EXEC_SIGNAL(ABRT); + NGX_RMTP_EXEC_SIGNAL(FPE); + NGX_RMTP_EXEC_SIGNAL(KILL); + NGX_RMTP_EXEC_SIGNAL(SEGV); + NGX_RMTP_EXEC_SIGNAL(PIPE); + NGX_RMTP_EXEC_SIGNAL(ALRM); + NGX_RMTP_EXEC_SIGNAL(TERM); + NGX_RMTP_EXEC_SIGNAL(USR1); + NGX_RMTP_EXEC_SIGNAL(USR2); + NGX_RMTP_EXEC_SIGNAL(CHLD); + NGX_RMTP_EXEC_SIGNAL(CONT); + NGX_RMTP_EXEC_SIGNAL(STOP); + NGX_RMTP_EXEC_SIGNAL(TSTP); + NGX_RMTP_EXEC_SIGNAL(TTIN); + NGX_RMTP_EXEC_SIGNAL(TTOU); +#endif + +#undef NGX_RMTP_EXEC_SIGNAL + + return "unknown signal"; +} + + +static ngx_int_t +ngx_rtmp_exec_postconfiguration(ngx_conf_t *cf) +{ +#if !(NGX_WIN32) + + next_publish = ngx_rtmp_publish; + ngx_rtmp_publish = ngx_rtmp_exec_publish; + + next_play = ngx_rtmp_play; + ngx_rtmp_play = ngx_rtmp_exec_play; + + next_close_stream = ngx_rtmp_close_stream; + ngx_rtmp_close_stream = ngx_rtmp_exec_close_stream; + +#endif /* NGX_WIN32 */ + + return NGX_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_gop_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_gop_module.c new file mode 100644 index 0000000000..771f292c77 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_gop_module.c @@ -0,0 +1,734 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#include +#include +#include "ngx_rtmp.h" +#include "ngx_rtmp_codec_module.h" +#include "ngx_rtmp_live_module.h" + + +static ngx_rtmp_close_stream_pt next_close_stream; + + +static void *ngx_rtmp_gop_create_app_conf(ngx_conf_t *cf); +static char *ngx_rtmp_gop_merge_app_conf(ngx_conf_t *cf, void *parent, + void *child); + +static ngx_int_t ngx_rtmp_gop_postconfiguration(ngx_conf_t *cf); + +#define ngx_rtmp_gop_next(s, pos) ((pos + 1) % s->out_queue) +#define ngx_rtmp_gop_prev(s, pos) (pos == 0 ? s->out_queue - 1 : pos - 1) + +typedef struct { + /* publisher: head of cache + * player: cache send position of publisher's out + */ + size_t gop_pos; + /* tail of cache */ + size_t gop_last; + /* 0 for not send, 1 for sending, 2 for sent */ + ngx_flag_t send_gop; + + ngx_rtmp_frame_t *keyframe; + + ngx_rtmp_frame_t *aac_header; + ngx_rtmp_frame_t *avc_header; + + ngx_rtmp_frame_t *latest_aac_header; + ngx_rtmp_frame_t *latest_avc_header; + + ngx_uint_t meta_version; + + uint32_t first_timestamp; + + /* only for publisher, must at last of ngx_rtmp_gop_ctx_t */ + ngx_rtmp_frame_t *cache[]; +} ngx_rtmp_gop_ctx_t; + +typedef struct { + ngx_msec_t cache_time; + ngx_flag_t low_latency; + ngx_flag_t send_all; + ngx_msec_t fix_timestamp; + ngx_flag_t zero_start; +} ngx_rtmp_gop_app_conf_t; + + +static ngx_command_t ngx_rtmp_gop_commands[] = { + + { ngx_string("cache_time"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_gop_app_conf_t, cache_time), + NULL }, + + { ngx_string("low_latency"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_gop_app_conf_t, low_latency), + NULL }, + + { ngx_string("send_all"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_gop_app_conf_t, send_all), + NULL }, + + { ngx_string("fix_timestamp"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_gop_app_conf_t, fix_timestamp), + NULL }, + + { ngx_string("zero_start"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_gop_app_conf_t, zero_start), + NULL }, + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_rtmp_gop_module_ctx = { + NULL, /* preconfiguration */ + ngx_rtmp_gop_postconfiguration, /* postconfiguration */ + NULL, /* create main configuration */ + NULL, /* init main configuration */ + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + ngx_rtmp_gop_create_app_conf, /* create app configuration */ + ngx_rtmp_gop_merge_app_conf /* merge app configuration */ +}; + + +ngx_module_t ngx_rtmp_gop_module = { + NGX_MODULE_V1, + &ngx_rtmp_gop_module_ctx, /* module context */ + ngx_rtmp_gop_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void * +ngx_rtmp_gop_create_app_conf(ngx_conf_t *cf) +{ + ngx_rtmp_gop_app_conf_t *gacf; + + gacf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_gop_app_conf_t)); + if (gacf == NULL) { + return NULL; + } + + gacf->cache_time = NGX_CONF_UNSET_MSEC; + gacf->low_latency = NGX_CONF_UNSET; + gacf->send_all = NGX_CONF_UNSET; + gacf->fix_timestamp = NGX_CONF_UNSET_MSEC; + gacf->zero_start = NGX_CONF_UNSET; + + return gacf; +} + +static char * +ngx_rtmp_gop_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_rtmp_gop_app_conf_t *prev = parent; + ngx_rtmp_gop_app_conf_t *conf = child; + + ngx_conf_merge_msec_value(conf->cache_time, prev->cache_time, 0); + ngx_conf_merge_value(conf->low_latency, prev->low_latency, 0); + ngx_conf_merge_value(conf->send_all, prev->send_all, 0); + ngx_conf_merge_msec_value(conf->fix_timestamp, prev->fix_timestamp, 10000); + ngx_conf_merge_value(conf->zero_start, prev->zero_start, 0); + + return NGX_CONF_OK; +} + +static ngx_int_t +ngx_rtmp_gop_link_frame(ngx_rtmp_session_t *s, ngx_rtmp_frame_t *frame) +{ + ngx_uint_t nmsg; + ngx_rtmp_live_chunk_stream_t *cs; + ngx_uint_t csidx; + ngx_rtmp_live_ctx_t *lctx; + ngx_rtmp_gop_ctx_t *ctx; + uint32_t delta; + ngx_rtmp_gop_app_conf_t *gacf; + + if (frame == NULL) { + return NGX_OK; + } + + gacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_gop_module); + if (gacf->fix_timestamp) { + lctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module); + csidx = !(frame->hdr.type == NGX_RTMP_MSG_VIDEO); + + cs = &lctx->cs[csidx]; + + delta = frame->hdr.timestamp > cs->last_timestamp ? + frame->hdr.timestamp - cs->last_timestamp : + cs->last_timestamp - frame->hdr.timestamp; + + if (delta > gacf->fix_timestamp) { + delta = 0; + } + + if (!gacf->zero_start && cs->timestamp == 0) { + cs->timestamp = frame->hdr.timestamp; + } else if (frame->hdr.timestamp > cs->last_timestamp) { + cs->timestamp += delta; + } else if (cs->timestamp >= delta) { + cs->timestamp -= delta; + } + + cs->last_timestamp = frame->hdr.timestamp; + + ngx_log_error(NGX_LOG_DEBUG, s->log, 0, + "gop: link_frame| type %d, delta %d," + " timestamp %uD, fixed timestamp %uD", + frame->hdr.type, delta, frame->hdr.timestamp, cs->timestamp); + + frame->hdr.timestamp = cs->timestamp; + if (frame->hdr.type == NGX_RTMP_MSG_AMF_META) { + frame->hdr.timestamp = 0; + } + } + + nmsg = (s->out_last - s->out_pos) % s->out_queue + 1; + + if (nmsg >= s->out_queue) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "link frame nmsg(%ui) >= out_queue(%O)", nmsg, s->out_queue); + return NGX_AGAIN; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_gop_module); + + if (frame->hdr.type == NGX_RTMP_MSG_AUDIO && frame->av_header) { + ctx->latest_aac_header = frame; + } + + if (frame->hdr.type == NGX_RTMP_MSG_VIDEO && frame->av_header) { + ctx->latest_avc_header = frame; + } + + s->out[s->out_last] = frame; + s->out_last = ngx_rtmp_gop_next(s, s->out_last); + + ngx_rtmp_shared_acquire_frame(frame); + + return NGX_OK; +} + +static void +ngx_rtmp_gop_set_avframe_tag(ngx_rtmp_frame_t *frame) +{ + ngx_chain_t *cl; + + if (frame->hdr.type != NGX_RTMP_MSG_AUDIO && + frame->hdr.type != NGX_RTMP_MSG_VIDEO) + { + return; + } + + cl = frame->chain; + + frame->av_header = ngx_rtmp_is_codec_header(cl); + frame->keyframe = (frame->hdr.type == NGX_RTMP_MSG_VIDEO) + ? (ngx_rtmp_get_video_frame_type(cl) == NGX_RTMP_VIDEO_KEY_FRAME) + : 0; + + if (frame->av_header) { + frame->mandatory = 1; + } +} + +static void +ngx_rtmp_gop_reset_avheader(ngx_rtmp_gop_ctx_t *ctx, + ngx_rtmp_frame_t *frame) +{ + if (frame->hdr.type == NGX_RTMP_MSG_AUDIO) { + if (ctx->aac_header) { + ngx_rtmp_shared_free_frame(ctx->aac_header); + } + ctx->aac_header = frame; + } else { + if (ctx->avc_header) { + ngx_rtmp_shared_free_frame(ctx->avc_header); + } + ctx->avc_header = frame; + } +} + +static void +ngx_rtmp_gop_reset_gop(ngx_rtmp_session_t *s, ngx_rtmp_gop_ctx_t *ctx, + ngx_rtmp_frame_t *frame) +{ + ngx_rtmp_gop_app_conf_t *gacf; + ngx_rtmp_frame_t *f, *next_keyframe; + size_t pos; + ngx_uint_t nmsg; + + /* reset av_header at the front of cache */ + for (pos = ctx->gop_pos; pos != ctx->gop_last; + pos = ngx_rtmp_gop_next(s, pos)) + { + if (ctx->cache[pos]->av_header) { + ngx_rtmp_gop_reset_avheader(ctx, ctx->cache[pos]); + ctx->gop_pos = ngx_rtmp_gop_next(s, ctx->gop_pos); + continue; + } + + break; + } + + f = ctx->cache[pos]; + if (f == NULL) { + return; + } + + gacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_gop_module); + + /* only audio in cache */ + if (ctx->keyframe == NULL) { + if (frame->hdr.timestamp - ctx->cache[ctx->gop_pos]->hdr.timestamp + > gacf->cache_time) + { + ngx_rtmp_shared_free_frame(f); + ctx->cache[ctx->gop_pos] = NULL; + ctx->gop_pos = ngx_rtmp_gop_next(s, ctx->gop_pos); + } + + return; + } + + /* only video of video + audio */ + next_keyframe = ctx->keyframe->next; + + /* only one gop in cache */ + if (next_keyframe == NULL) { + return; + } + + nmsg = (ctx->gop_last - ctx->gop_pos) % s->out_queue + 2; + if (nmsg >= s->out_queue) { + goto reset; + } + + if (frame->hdr.type == NGX_RTMP_MSG_AUDIO) { + return; + } + + if (frame->hdr.type == NGX_RTMP_MSG_VIDEO && frame->hdr.timestamp + - next_keyframe->hdr.timestamp < gacf->cache_time) + { + return; + } + +reset: + for (pos = ctx->gop_pos; ctx->cache[pos] != next_keyframe; + pos = ngx_rtmp_gop_next(s, pos)) + { + f = ctx->cache[pos]; + + if (f->av_header) { + ngx_rtmp_gop_reset_avheader(ctx, f); + } else { + ngx_rtmp_shared_free_frame(f); + } + + ctx->cache[pos] = NULL; + } + + ctx->keyframe = next_keyframe; + ctx->gop_pos = pos; +} + +static void +ngx_rtmp_gop_print_cache(ngx_rtmp_session_t *s, ngx_rtmp_gop_ctx_t *ctx) +{ +#if (NGX_DEBUG) + ngx_rtmp_frame_t *frame; + u_char content[10240], *p; + size_t pos; + + ngx_memzero(content, sizeof(content)); + + p = content; + for (pos = ctx->gop_pos; pos != ctx->gop_last; + pos = ngx_rtmp_gop_next(s, pos)) + { + frame = ctx->cache[pos]; + switch (frame->hdr.type) { + case NGX_RTMP_MSG_AUDIO: + *p++ = 'A'; + break; + case NGX_RTMP_MSG_VIDEO: + *p++ = 'V'; + break; + default: + *p++ = 'O'; + break; + } + + if (frame->keyframe) { + *p++ = 'I'; + } + + if (frame->av_header) { + *p++ = 'H'; + } + + *p++ = ' '; + } + + ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->log, 0, + "[%z %z] [%p %p] %s", ctx->gop_pos, ctx->gop_last, ctx->aac_header, + ctx->avc_header, content); +#endif +} + +ngx_int_t +ngx_rtmp_gop_cache(ngx_rtmp_session_t *s, ngx_rtmp_frame_t *frame) +{ + ngx_rtmp_gop_app_conf_t *gacf; + ngx_rtmp_gop_ctx_t *ctx; + ngx_rtmp_frame_t **keyframe; + ngx_uint_t nmsg; + + gacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_gop_module); + if (gacf->cache_time == 0) { + return NGX_OK; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_gop_module); + if (ctx == NULL) { + ctx = ngx_pcalloc(s->pool, sizeof(ngx_rtmp_gop_ctx_t) + + s->out_queue * sizeof(ngx_rtmp_frame_t *)); + if (ctx == NULL) { + return NGX_ERROR; + } + ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_gop_module); + } + + nmsg = (ctx->gop_last - ctx->gop_pos) % s->out_queue + 1; + if (nmsg >= s->out_queue) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "cache frame nmsg(%ui) >= out_queue(%z)", nmsg, s->out_queue); + return NGX_AGAIN; + } + + ngx_rtmp_gop_set_avframe_tag(frame); + + ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->log, 0, + "cache frame: %ud[%d %d], %ud, %ud", + frame->hdr.type, frame->keyframe, frame->av_header, + frame->hdr.timestamp, frame->hdr.mlen); + + if (frame->hdr.type == NGX_RTMP_MSG_AUDIO && frame->av_header) { + ctx->latest_aac_header = frame; + } + + if (frame->hdr.type == NGX_RTMP_MSG_VIDEO && frame->av_header) { + ctx->latest_avc_header = frame; + } + + /* first video frame is not intra_frame or video header */ + if (ctx->keyframe == NULL && frame->hdr.type == NGX_RTMP_MSG_VIDEO + && !frame->keyframe && !frame->av_header) + { + return NGX_OK; + } + + /* video intra_frame */ + if (frame->keyframe && !frame->av_header) { + for (keyframe = &ctx->keyframe; *keyframe; + keyframe = &((*keyframe)->next)); + *keyframe = frame; + } + + ctx->cache[ctx->gop_last] = frame; + ctx->gop_last = ngx_rtmp_gop_next(s, ctx->gop_last); + + ngx_rtmp_shared_acquire_frame(frame); + + ngx_rtmp_gop_reset_gop(s, ctx, frame); + + ngx_rtmp_gop_print_cache(s, ctx); + + return NGX_OK; +} + +static ngx_int_t +ngx_rtmp_gop_send_meta(ngx_rtmp_session_t *s, ngx_rtmp_session_t *ss) +{ + ngx_rtmp_gop_ctx_t *ssctx; + ngx_rtmp_codec_ctx_t *cctx; + + ssctx = ngx_rtmp_get_module_ctx(ss, ngx_rtmp_gop_module); + cctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + /* meta */ + if (ssctx->meta_version != cctx->meta_version + && ngx_rtmp_gop_link_frame(ss, cctx->meta) == NGX_AGAIN) + { + return NGX_AGAIN; + } + ssctx->meta_version = cctx->meta_version; + + return NGX_OK; +} + +static ngx_int_t +ngx_rtmp_gop_send_gop(ngx_rtmp_session_t *s, ngx_rtmp_session_t *ss) +{ + ngx_rtmp_gop_app_conf_t *gacf; + ngx_rtmp_gop_ctx_t *sctx, *ssctx; + ngx_rtmp_frame_t *frame; + size_t pos; + + gacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_gop_module); + + sctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_gop_module); + ssctx = ngx_rtmp_get_module_ctx(ss, ngx_rtmp_gop_module); + + /* already send gop */ + if (ssctx->send_gop == 3) { + return NGX_OK; + } + + if (ssctx->send_gop == 0) { + if (ngx_rtmp_gop_send_meta(s, ss) == NGX_AGAIN) { + return NGX_AGAIN; + } + + ngx_rtmp_send_message(ss, NULL, 0); + + ssctx->send_gop = 1; + + return NGX_AGAIN; + } + + /* link frame in s to ss */ + if (ssctx->send_gop == 1) { + ssctx->gop_pos = sctx->gop_pos; + if (sctx->cache[ssctx->gop_pos] == NULL) { + return NGX_AGAIN; + } + + if (sctx->aac_header) { + if (ngx_rtmp_gop_link_frame(ss, sctx->aac_header) == NGX_AGAIN) { + return NGX_AGAIN; + } + } + + if (sctx->avc_header) { + if (ngx_rtmp_gop_link_frame(ss, sctx->avc_header) == NGX_AGAIN) { + return NGX_AGAIN; + } + } + + ssctx->send_gop = 2; + ssctx->first_timestamp = sctx->cache[ssctx->gop_pos]->hdr.timestamp; + } else { + if (sctx->cache[ssctx->gop_pos] == NULL) { + ssctx->gop_pos = sctx->gop_pos; + } + } + + pos = ssctx->gop_pos; + frame = sctx->cache[pos]; + while (frame) { + if (!gacf->send_all && + frame->hdr.timestamp - ssctx->first_timestamp >= gacf->cache_time) + { + ssctx->send_gop = 3; + break; + } + + if (ngx_rtmp_gop_link_frame(ss, frame) == NGX_AGAIN) { + break; + } + + pos = ngx_rtmp_gop_next(s, pos); + frame = sctx->cache[pos]; + } + + if (frame == NULL) { /* send all frame in cache */ + ssctx->send_gop = 3; + } + + ssctx->gop_pos = pos; + ngx_rtmp_send_message(ss, NULL, 0); + + return NGX_AGAIN; +} + +ngx_int_t +ngx_rtmp_gop_send(ngx_rtmp_session_t *s, ngx_rtmp_session_t *ss) +{ + ngx_rtmp_gop_app_conf_t *gacf; + ngx_rtmp_gop_ctx_t *sctx, *ssctx; + ngx_rtmp_frame_t *frame; + size_t pos; + + gacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_gop_module); + if (gacf->cache_time == 0) { + return NGX_DECLINED; + } + + sctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_gop_module); + if (sctx == NULL) { /* publisher doesn't publish av frame */ + return NGX_DECLINED; + } + + ssctx = ngx_rtmp_get_module_ctx(ss, ngx_rtmp_gop_module); + if (ssctx == NULL) { + ssctx = ngx_pcalloc(ss->pool, sizeof(ngx_rtmp_gop_ctx_t)); + if (ssctx == NULL) { + return NGX_ERROR; + } + ngx_rtmp_set_ctx(ss, ssctx, ngx_rtmp_gop_module); + } + + if (ngx_rtmp_gop_send_gop(s, ss) == NGX_AGAIN) { + return NGX_OK; + } + + /* send frame by frame */ + if (ngx_rtmp_gop_send_meta(s, ss) == NGX_AGAIN) { + return NGX_AGAIN; + } + + pos = ngx_rtmp_gop_prev(s, sctx->gop_last); + /* new frame is video key frame */ + if (sctx->cache[pos]->keyframe && !sctx->cache[pos]->av_header) { + if (gacf->low_latency && pos != ssctx->gop_pos) { + ssctx->gop_pos = pos; + + ss->out_pos = ss->out_last; + + ngx_log_error(NGX_LOG_INFO, ss->log, 0, + "gop, low latency, chase to new keyframe"); + + if (sctx->latest_aac_header + && sctx->latest_aac_header != ssctx->latest_aac_header) + { + if (ngx_rtmp_gop_link_frame(ss, sctx->latest_aac_header) + == NGX_AGAIN) + { + return NGX_AGAIN; + } + } + + if (sctx->latest_avc_header + && sctx->latest_avc_header != ssctx->latest_avc_header) + { + if (ngx_rtmp_gop_link_frame(ss, sctx->latest_avc_header) + == NGX_AGAIN) + { + return NGX_AGAIN; + } + } + } + } else { + if (sctx->cache[ssctx->gop_pos] == NULL) { + ngx_log_error(NGX_LOG_ERR, ss->log, 0, + "gop, current gop pos is NULL, " + "skip to new postion [pos %d last %d] %d", + sctx->gop_pos, sctx->gop_last, ssctx->gop_pos); + + if (sctx->aac_header + && sctx->aac_header != ssctx->latest_aac_header) + { + if (ngx_rtmp_gop_link_frame(ss, sctx->aac_header) + == NGX_AGAIN) + { + return NGX_AGAIN; + } + } + + if (sctx->avc_header + && sctx->avc_header != ssctx->latest_avc_header) + { + if (ngx_rtmp_gop_link_frame(ss, sctx->avc_header) + == NGX_AGAIN) + { + return NGX_AGAIN; + } + } + + ssctx->gop_pos = sctx->gop_pos; + } + } + + frame = sctx->cache[ssctx->gop_pos]; + if (ngx_rtmp_gop_link_frame(ss, frame) == NGX_AGAIN) { + return NGX_AGAIN; + } + + ssctx->gop_pos = ngx_rtmp_gop_next(s, ssctx->gop_pos); + ngx_rtmp_send_message(ss, NULL, 0); + + return NGX_OK; +} + +static ngx_int_t +ngx_rtmp_gop_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v) +{ + ngx_rtmp_gop_ctx_t *ctx; + ngx_rtmp_live_ctx_t *lctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_gop_module); + if (ctx == NULL) { + goto next; + } + + lctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module); + + if (!lctx->publishing) { + goto next; + } + + if (ctx->avc_header) { + ngx_rtmp_shared_free_frame(ctx->avc_header); + } + + if (ctx->aac_header) { + ngx_rtmp_shared_free_frame(ctx->aac_header); + } + + /* free cache in publisher */ + while (ctx->gop_pos != ctx->gop_last) { + ngx_rtmp_shared_free_frame(ctx->cache[ctx->gop_pos]); + ctx->gop_pos = ngx_rtmp_gop_next(s, ctx->gop_pos); + } + +next: + return next_close_stream(s, v); +} + +static ngx_int_t +ngx_rtmp_gop_postconfiguration(ngx_conf_t *cf) +{ + next_close_stream = ngx_rtmp_close_stream; + ngx_rtmp_close_stream = ngx_rtmp_gop_close_stream; + + return NGX_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_handler.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_handler.c new file mode 100644 index 0000000000..7503653262 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_handler.c @@ -0,0 +1,948 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp.h" +#include "ngx_rtmp_amf.h" +#include "ngx_rbuf.h" +#include "ngx_poold.h" +#include "ngx_rtmp_monitor_module.h" + + +static void ngx_rtmp_recv(ngx_event_t *rev); +static void ngx_rtmp_send(ngx_event_t *rev); +static void ngx_rtmp_ping(ngx_event_t *rev); +static ngx_int_t ngx_rtmp_finalize_set_chunk_size(ngx_rtmp_session_t *s); + + +ngx_uint_t ngx_rtmp_naccepted; + + +ngx_rtmp_bandwidth_t ngx_rtmp_bw_out; +ngx_rtmp_bandwidth_t ngx_rtmp_bw_in; + + +#ifdef NGX_DEBUG +char* +ngx_rtmp_message_type(uint8_t type) +{ + static char* types[] = { + "?", + "chunk_size", + "abort", + "ack", + "user", + "ack_size", + "bandwidth", + "edge", + "audio", + "video", + "?", + "?", + "?", + "?", + "?", + "amf3_meta", + "amf3_shared", + "amf3_cmd", + "amf_meta", + "amf_shared", + "amf_cmd", + "?", + "aggregate" + }; + + return type < sizeof(types) / sizeof(types[0]) + ? types[type] + : "?"; +} + + +char* +ngx_rtmp_user_message_type(uint16_t evt) +{ + static char* evts[] = { + "stream_begin", + "stream_eof", + "stream dry", + "set_buflen", + "recorded", + "", + "ping_request", + "ping_response", + }; + + return evt < sizeof(evts) / sizeof(evts[0]) + ? evts[evt] + : "?"; +} +#endif + + +void +ngx_rtmp_cycle(ngx_rtmp_session_t *s) +{ + ngx_connection_t *c; + + c = s->connection; + c->read->handler = ngx_rtmp_recv; + c->write->handler = ngx_rtmp_send; + + s->ping_evt.data = c; + s->ping_evt.log = s->log; + s->ping_evt.handler = ngx_rtmp_ping; + ngx_rtmp_reset_ping(s); + + ngx_rtmp_recv(c->read); +} + + +static ngx_chain_t * +ngx_rtmp_alloc_in_buf(ngx_rtmp_session_t *s) +{ + ngx_chain_t *cl; + ngx_buf_t *b; + size_t size; + + if ((cl = ngx_alloc_chain_link(s->in_pool)) == NULL + || (cl->buf = ngx_calloc_buf(s->in_pool)) == NULL) + { + return NULL; + } + + cl->next = NULL; + b = cl->buf; + size = s->in_chunk_size + NGX_RTMP_MAX_CHUNK_HEADER; + + b->start = b->last = b->pos = ngx_palloc(s->in_pool, size); + if (b->start == NULL) { + return NULL; + } + b->end = b->start + size; + + return cl; +} + + +void +ngx_rtmp_reset_ping(ngx_rtmp_session_t *s) +{ + ngx_rtmp_core_srv_conf_t *cscf; + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + if (cscf->ping == 0) { + return; + } + + s->ping_active = 0; + s->ping_reset = 0; + ngx_add_timer(&s->ping_evt, cscf->ping); + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "ping: wait %Mms", cscf->ping); +} + + +static void +ngx_rtmp_ping(ngx_event_t *pev) +{ + ngx_connection_t *c; + ngx_rtmp_session_t *s; + ngx_rtmp_core_srv_conf_t *cscf; + + c = pev->data; + s = c->data; + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + /* i/o event has happened; no need to ping */ + if (s->ping_reset) { + ngx_rtmp_reset_ping(s); + return; + } + + if (s->ping_active) { + ngx_log_error(NGX_LOG_INFO, s->log, 0, "ping: unresponded"); + ngx_rtmp_finalize_session(s); + return; + } + + if (cscf->busy) { + ngx_log_error(NGX_LOG_INFO, s->log, 0, "ping: not busy between pings"); + ngx_rtmp_finalize_session(s); + return; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "ping: schedule %Mms", cscf->ping_timeout); + + if (ngx_rtmp_send_ping_request(s, (uint32_t)ngx_current_msec) != NGX_OK) { + ngx_rtmp_finalize_session(s); + return; + } + + s->ping_active = 1; + ngx_add_timer(pev, cscf->ping_timeout); +} + + +static void +ngx_rtmp_recv(ngx_event_t *rev) +{ + ngx_int_t n; + ngx_connection_t *c; + ngx_rtmp_session_t *s; + ngx_rtmp_core_srv_conf_t *cscf; + ngx_rtmp_header_t *h; + ngx_rtmp_stream_t *st, *st0; + ngx_chain_t *in, *head; + ngx_buf_t *b; + u_char *p, *pp, *old_pos; + size_t size, fsize, old_size; + uint8_t fmt, ext; + uint32_t csid, timestamp; + + c = rev->data; + s = c->data; + b = NULL; + old_pos = NULL; + old_size = 0; + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + if (c->destroyed) { + return; + } + + if (ngx_rtmp_core_main_conf->fast_reload && (ngx_exiting || ngx_terminate)) { + s->finalize_reason = NGX_LIVE_PROCESS_EXIT; + ngx_rtmp_finalize_session(s); + return; + } + + for( ;; ) { + + st = &s->in_streams[s->in_csid]; + + /* allocate new buffer */ + if (st->in == NULL) { + st->in = ngx_rtmp_alloc_in_buf(s); + if (st->in == NULL) { + ngx_log_error(NGX_LOG_INFO, s->log, 0, "in buf alloc failed"); + ngx_rtmp_finalize_session(s); + return; + } + } + + h = &st->hdr; + in = st->in; + b = in->buf; + + if (old_size) { + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "reusing formerly read data: %d", old_size); + + b->pos = b->start; + b->last = ngx_movemem(b->pos, old_pos, old_size); + + if (s->in_chunk_size_changing) { + ngx_rtmp_finalize_set_chunk_size(s); + } + + } else { + + if (old_pos) { + b->pos = b->last = b->start; + } + + n = c->recv(c, b->last, b->end - b->last); + + if (n == NGX_ERROR || n == 0) { + s->finalize_reason = n == 0? NGX_LIVE_NORMAL_CLOSE: + NGX_LIVE_RTMP_RECV_ERR; + ngx_rtmp_finalize_session(s); + return; + } + + if (n == NGX_AGAIN) { + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + ngx_rtmp_finalize_session(s); + } + return; + } + + s->ping_reset = 1; + ngx_rtmp_update_bandwidth(&ngx_rtmp_bw_in, n); + b->last += n; + s->in_bytes += n; + + if (s->in_bytes >= 0xf0000000) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0, + "resetting byte counter"); + s->in_bytes = 0; + s->in_last_ack = 0; + } + + if (s->ack_size && s->in_bytes - s->in_last_ack >= s->ack_size) { + + s->in_last_ack = s->in_bytes; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "sending RTMP ACK(%uD)", s->in_bytes); + + if (ngx_rtmp_send_ack(s, s->in_bytes)) { + ngx_rtmp_finalize_session(s); + return; + } + } + } + + old_pos = NULL; + old_size = 0; + + /* parse headers */ + if (b->pos == b->start) { + p = b->pos; + + /* chunk basic header */ + fmt = (*p >> 6) & 0x03; + csid = *p++ & 0x3f; + + if (csid == 0) { + if (b->last - p < 1) + continue; + csid = 64; + csid += *(uint8_t*)p++; + + } else if (csid == 1) { + if (b->last - p < 2) + continue; + csid = 64; + csid += *(uint8_t*)p++; + csid += (uint32_t)256 * (*(uint8_t*)p++); + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0, + "RTMP bheader fmt=%d csid=%D", + (int)fmt, csid); + + if (csid >= (uint32_t)cscf->max_streams) { + ngx_log_error(NGX_LOG_INFO, s->log, 0, + "RTMP in chunk stream too big: %D >= %D", + csid, cscf->max_streams); + ngx_rtmp_finalize_session(s); + return; + } + + /* link orphan */ + if (s->in_csid == 0) { + + /* unlink from stream #0 */ + st->in = st->in->next; + + /* link to new stream */ + s->in_csid = csid; + st = &s->in_streams[csid]; + if (st->in == NULL) { + in->next = in; + } else { + in->next = st->in->next; + st->in->next = in; + } + st->in = in; + h = &st->hdr; + h->csid = csid; + } + + ext = st->ext; + timestamp = st->dtime; + if (fmt <= 2 ) { + if (b->last - p < 3) + continue; + /* timestamp: + * big-endian 3b -> little-endian 4b */ + pp = (u_char*)×tamp; + pp[2] = *p++; + pp[1] = *p++; + pp[0] = *p++; + pp[3] = 0; + + ext = (timestamp == 0x00ffffff); + + if (fmt <= 1) { + if (b->last - p < 4) + continue; + /* size: + * big-endian 3b -> little-endian 4b + * type: + * 1b -> 1b*/ + pp = (u_char*)&h->mlen; + pp[2] = *p++; + pp[1] = *p++; + pp[0] = *p++; + pp[3] = 0; + h->type = *(uint8_t*)p++; + + if (fmt == 0) { + if (b->last - p < 4) + continue; + /* stream: + * little-endian 4b -> little-endian 4b */ + pp = (u_char*)&h->msid; + pp[0] = *p++; + pp[1] = *p++; + pp[2] = *p++; + pp[3] = *p++; + } + } + } + + /* extended header */ + if (ext) { + if (b->last - p < 4) + continue; + pp = (u_char*)×tamp; + pp[3] = *p++; + pp[2] = *p++; + pp[1] = *p++; + pp[0] = *p++; + } + + if (st->len == 0) { + /* Messages with type=3 should + * never have ext timestamp field + * according to standard. + * However that's not always the case + * in real life */ + st->ext = (ext && cscf->publish_time_fix); + if (fmt) { + st->dtime = timestamp; + } else { + h->timestamp = timestamp; + st->dtime = 0; + } + } + + ngx_log_debug8(NGX_LOG_DEBUG_RTMP, s->log, 0, + "RTMP mheader fmt=%d %s (%d) " + "time=%uD+%uD mlen=%D len=%D msid=%D", + (int)fmt, ngx_rtmp_message_type(h->type), (int)h->type, + h->timestamp, st->dtime, h->mlen, st->len, h->msid); + + /* header done */ + b->pos = p; + + if (h->mlen > cscf->max_message) { + ngx_log_error(NGX_LOG_INFO, s->log, 0, + "too big message: %uz", cscf->max_message); + ngx_rtmp_finalize_session(s); + return; + } + } + + size = b->last - b->pos; + fsize = h->mlen - st->len; + + if (size < ngx_min(fsize, s->in_chunk_size)) + continue; + + /* buffer is ready */ + + if (fsize > s->in_chunk_size) { + /* collect fragmented chunks */ + st->len += s->in_chunk_size; + b->last = b->pos + s->in_chunk_size; + old_pos = b->last; + old_size = size - s->in_chunk_size; + + } else { + /* handle! */ + head = st->in->next; + st->in->next = NULL; + b->last = b->pos + fsize; + old_pos = b->last; + old_size = size - fsize; + st->len = 0; + h->timestamp += st->dtime; + + if (ngx_rtmp_receive_message(s, h, head) != NGX_OK) { + ngx_rtmp_finalize_session(s); + return; + } + + if (s->in_chunk_size_changing) { + /* copy old data to a new buffer */ + if (!old_size) { + ngx_rtmp_finalize_set_chunk_size(s); + } + + } else { + /* add used bufs to stream #0 */ + st0 = &s->in_streams[0]; + st->in->next = st0->in; + st0->in = head; + st->in = NULL; + } + } + + s->in_csid = 0; + } +} + + +static ngx_flag_t +ngx_rtmp_relative_timestamp(ngx_rtmp_session_t *s, ngx_rtmp_header_t *lh) +{ + return 0; +} + +static ngx_chain_t * +ngx_rtmp_prepare_out_chain(ngx_rtmp_session_t *s) +{ + ngx_rtmp_core_srv_conf_t *cscf; + ngx_rtmp_frame_t *frame; + ngx_chain_t *head, *l, **ll; + uint32_t mlen, timestamp, ext_timestamp; + uint8_t fmt, hsize, thsize; + static uint8_t hdrsize[] = { 12, 8, 4, 1 }; + ngx_flag_t relative; + ngx_rtmp_header_t lh; + u_char th[7], *p, *pp; + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + frame = s->out[s->out_pos]; + head = NULL; + + if (frame->hdr.csid >= (uint32_t)cscf->max_streams) { + ngx_log_error(NGX_LOG_INFO, s->log, 0, + "RTMP out chunk stream too big: %D >= %D", + frame->hdr.csid, cscf->max_streams); + goto failed; + } + + mlen = 0; + fmt = 0; + relative = ngx_rtmp_relative_timestamp(s, &lh); + + for (l = frame->chain; l; l = l->next) { + mlen += ngx_buf_size(l->buf); + } + + if (relative && lh.csid && frame->hdr.msid == lh.msid) { + ++fmt; + if (frame->hdr.type == lh.type && mlen && mlen == lh.mlen) { + ++fmt; + if (frame->hdr.timestamp == lh.timestamp) { + ++fmt; + } + } + timestamp = frame->hdr.timestamp - lh.timestamp; + } else { + timestamp = frame->hdr.timestamp; + } + + hsize = hdrsize[fmt]; + + ngx_log_debug7(NGX_LOG_DEBUG_RTMP, s->log, 0, + "RTMP prep %s (%d) fmt=%d csid=%uD timestamp=%uD mlen=%uD msid=%uD", + ngx_rtmp_message_type(frame->hdr.type), (int)frame->hdr.type, + (int)fmt, frame->hdr.csid, timestamp, mlen, frame->hdr.msid); + + ext_timestamp = 0; + if (timestamp >= 0x00ffffff) { + ext_timestamp = timestamp; + timestamp = 0x00ffffff; + hsize += 4; + } + + if (frame->hdr.csid >= 64) { + ++hsize; + if (frame->hdr.csid >= 320) { + ++hsize; + } + } + + /* fill initial header */ + head = ngx_get_chainbuf(NGX_RTMP_MAX_CHUNK_HEADER, 1); + if (head == NULL) { + goto failed; + } + p = head->buf->pos; + + /* basic header */ + *p = (fmt << 6); + if (frame->hdr.csid >= 2 && frame->hdr.csid <= 63) { + *p++ |= (((uint8_t)frame->hdr.csid) & 0x3f); + } else if (frame->hdr.csid >= 64 && frame->hdr.csid < 320) { + ++p; + *p++ = (uint8_t)(frame->hdr.csid - 64); + } else { + *p++ |= 1; + *p++ = (uint8_t)(frame->hdr.csid - 64); + *p++ = (uint8_t)((frame->hdr.csid - 64) >> 8); + } + + /* create fmt3 header for successive fragments */ + thsize = p - head->buf->pos; + ngx_memcpy(th, head->buf->pos, thsize); + th[0] |= 0xc0; + + /* message header */ + if (fmt <= 2) { + pp = (u_char *)×tamp; + *p++ = pp[2]; + *p++ = pp[1]; + *p++ = pp[0]; + if (fmt <= 1) { + pp = (u_char *)&mlen; + *p++ = pp[2]; + *p++ = pp[1]; + *p++ = pp[0]; + *p++ = frame->hdr.type; + if (fmt == 0) { + pp = (u_char *)&frame->hdr.msid; + *p++ = pp[0]; + *p++ = pp[1]; + *p++ = pp[2]; + *p++ = pp[3]; + } + } + } + + /* extended timestamp */ + if (ext_timestamp) { + pp = (u_char *)&ext_timestamp; + *p++ = pp[3]; + *p++ = pp[2]; + *p++ = pp[1]; + *p++ = pp[0]; + + /* This CONTRADICTS the standard + * but that's the way flash client + * wants data to be encoded; + * ffmpeg complains */ + if (cscf->play_time_fix) { + ngx_memcpy(&th[thsize], p - 4, 4); + thsize += 4; + } + } + head->buf->last = p; + + /* append headers to successive fragments */ + ll = &head->next; + l = frame->chain; + while (l && l->buf->pos == l->buf->last) { + l = l->next; + } + if (l == NULL) { + return head; + } + + *ll = ngx_get_chainbuf(0, 0); + (*ll)->buf->pos = l->buf->pos; + (*ll)->buf->last = l->buf->last; + + for (l = l->next; l; l = l->next) { + /* chunk header */ + ll = &(*ll)->next; + *ll = ngx_get_chainbuf(NGX_RTMP_MAX_CHUNK_HEADER, 1); + (*ll)->buf->last = ngx_cpymem((*ll)->buf->last, th, thsize); + + /* payload */ + ll = &(*ll)->next; + *ll = ngx_get_chainbuf(0, 0); + (*ll)->buf->pos = l->buf->pos; + (*ll)->buf->last = l->buf->last; + } + + ngx_rtmp_monitor_frame(s, &frame->hdr, NULL, frame->av_header, 0); + + return head; + +failed: + ngx_put_chainbufs(head); + + ngx_rtmp_finalize_session(s); + return NULL; +} + +static void +ngx_rtmp_send(ngx_event_t *wev) +{ + ngx_connection_t *c; + ngx_rtmp_session_t *s; + ngx_int_t n; + ngx_chain_t *chain, *cl; + off_t sent; + + c = wev->data; + s = c->data; + + if (c->destroyed) { + return; + } + + if (wev->timedout) { + ngx_log_error(NGX_LOG_INFO, s->log, NGX_ETIMEDOUT, "client timed out"); + c->timedout = 1; + s->finalize_reason = NGX_LIVE_RTMP_SEND_TIMEOUT; + ngx_rtmp_finalize_session(s); + return; + } + + if (wev->timer_set) { + ngx_del_timer(wev); + } + + if (s->prepare_handler == NULL) { + s->prepare_handler = ngx_rtmp_prepare_out_chain; + } + + if (ngx_rtmp_core_main_conf->fast_reload && (ngx_exiting || ngx_terminate)) { + s->finalize_reason = NGX_LIVE_PROCESS_EXIT; + ngx_rtmp_finalize_session(s); + return; + } + + if (ngx_rtmp_prepare_merge_frame(s) == NGX_ERROR) { + ngx_rtmp_finalize_session(s); + return; + } + + while (s->out_chain) { + sent = c->sent; + + chain = c->send_chain(c, s->out_chain, 0); + + n = c->sent - sent; + + if (chain == NGX_CHAIN_ERROR) { /* NGX_ERROR */ + c->error = 1; + s->finalize_reason = NGX_LIVE_RTMP_SEND_ERR; + ngx_rtmp_finalize_session(s); + return; + } + + for (cl = s->out_chain; cl != chain;) { + s->out_chain = cl->next; + ngx_free_chain(s->pool, cl); + cl = s->out_chain; + } + + if (chain) { /* NGX_AGAIN */ + ngx_add_timer(c->write, s->timeout); + if (ngx_handle_write_event(c->write, 0) != NGX_OK) { + ngx_rtmp_finalize_session(s); + } + return; + } + + s->out_bytes += n; + s->ping_reset = 1; + ngx_rtmp_update_bandwidth(&ngx_rtmp_bw_out, n); + + if (ngx_rtmp_prepare_merge_frame(s) == NGX_ERROR) { + ngx_rtmp_finalize_session(s); + return; + } + } + + if (wev->active) { + ngx_del_event(wev, NGX_WRITE_EVENT, 0); + } + + ngx_event_process_posted((ngx_cycle_t *) ngx_cycle, &s->posted_dry_events); +} + + +ngx_int_t +ngx_rtmp_send_message(ngx_rtmp_session_t *s, ngx_rtmp_frame_t *out, + ngx_uint_t priority) +{ + ngx_uint_t nmsg; + + if (out == NULL) { + goto send; + } + + nmsg = (s->out_last - s->out_pos) % s->out_queue + 1; + + if (priority > 3) { + priority = 3; + } + + /* drop packet? + * Note we always leave 1 slot free */ + if (nmsg + priority * s->out_queue / 4 >= s->out_queue) { + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0, + "RTMP drop message bufs=%ui, priority=%ui", + nmsg, priority); + return NGX_AGAIN; + } + + s->out[s->out_last++] = out; + s->out_last %= s->out_queue; + + ngx_rtmp_shared_acquire_frame(out); + + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->log, 0, + "RTMP send nmsg=%ui, priority=%ui #%ui", + nmsg, priority, s->out_last); + + if (priority && s->out_buffer && nmsg < s->out_cork) { + return NGX_OK; + } + +send: + if (!s->connection->write->active) { + ngx_post_event(s->connection->write, &ngx_posted_events); + } + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_receive_message(ngx_rtmp_session_t *s, + ngx_rtmp_header_t *h, ngx_chain_t *in) +{ + ngx_rtmp_core_main_conf_t *cmcf; + ngx_array_t *evhs; + size_t n; + ngx_rtmp_handler_pt *evh; + + cmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_core_module); + +#ifdef NGX_DEBUG + { + int nbufs; + ngx_chain_t *ch; + + for(nbufs = 1, ch = in; + ch->next; + ch = ch->next, ++nbufs); + + ngx_log_debug7(NGX_LOG_DEBUG_RTMP, s->log, 0, + "RTMP recv %s (%d) csid=%D timestamp=%D " + "mlen=%D msid=%D nbufs=%d", + ngx_rtmp_message_type(h->type), (int)h->type, + h->csid, h->timestamp, h->mlen, h->msid, nbufs); + } +#endif + + if (h->type > NGX_RTMP_MSG_MAX) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "unexpected RTMP message type: %d", (int)h->type); + return NGX_OK; + } + + evhs = &cmcf->events[h->type]; + evh = evhs->elts; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, "nhandlers: %d", evhs->nelts); + + for(n = 0; n < evhs->nelts; ++n, ++evh) { + if (!evh) { + continue; + } + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, "calling handler %d", n); + + switch ((*evh)(s, h, in)) { + case NGX_ERROR: + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "handler %d failed", n); + return NGX_ERROR; + case NGX_DONE: + return NGX_OK; + } + } + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_set_chunk_size(ngx_rtmp_session_t *s, ngx_uint_t size) +{ + ngx_rtmp_core_srv_conf_t *cscf; + ngx_chain_t *li, *fli, *lo, *flo; + ngx_buf_t *bi, *bo; + ngx_int_t n; + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + s->in_old_pool = s->in_pool; + s->in_chunk_size = size; + s->in_pool = NGX_CREATE_POOL(4096, ngx_cycle->log); + + /* copy existing chunk data */ + if (s->in_old_pool) { + s->in_chunk_size_changing = 1; + s->in_streams[0].in = NULL; + + for(n = 1; n < cscf->max_streams; ++n) { + /* stream buffer is circular + * for all streams except for the current one + * (which caused this chunk size change); + * we can simply ignore it */ + li = s->in_streams[n].in; + if (li == NULL || li->next == NULL) { + s->in_streams[n].in = NULL; + continue; + } + /* move from last to the first */ + li = li->next; + fli = li; + lo = ngx_rtmp_alloc_in_buf(s); + if (lo == NULL) { + return NGX_ERROR; + } + flo = lo; + for ( ;; ) { + bi = li->buf; + bo = lo->buf; + + if (bo->end - bo->last >= bi->last - bi->pos) { + bo->last = ngx_cpymem(bo->last, bi->pos, + bi->last - bi->pos); + li = li->next; + if (li == fli) { + lo->next = flo; + s->in_streams[n].in = lo; + break; + } + continue; + } + + bi->pos += (ngx_cpymem(bo->last, bi->pos, + bo->end - bo->last) - bo->last); + lo->next = ngx_rtmp_alloc_in_buf(s); + lo = lo->next; + if (lo == NULL) { + return NGX_ERROR; + } + } + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_finalize_set_chunk_size(ngx_rtmp_session_t *s) +{ + if (s->in_chunk_size_changing && s->in_old_pool) { + NGX_DESTROY_POOL(s->in_old_pool); + s->in_old_pool = NULL; + s->in_chunk_size_changing = 0; + } + return NGX_OK; +} + + diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_handshake.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_handshake.c new file mode 100644 index 0000000000..352aa29f32 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_handshake.c @@ -0,0 +1,641 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp.h" + +#include +#include + + +static void ngx_rtmp_handshake_send(ngx_event_t *wev); +static void ngx_rtmp_handshake_recv(ngx_event_t *rev); +static void ngx_rtmp_handshake_done(ngx_rtmp_session_t *s); + + +/* RTMP handshake : + * + * =peer1= =peer2= + * challenge ----> (.....[digest1]......) ----> 1537 bytes + * response <---- (...........[digest2]) <---- 1536 bytes + * + * + * - both packets contain random bytes except for digests + * - digest1 position is calculated on random packet bytes + * - digest2 is always at the end of the packet + * + * digest1: HMAC_SHA256(packet, peer1_partial_key) + * digest2: HMAC_SHA256(packet, HMAC_SHA256(digest1, peer2_full_key)) + */ + + +/* Handshake keys */ +static u_char +ngx_rtmp_server_key[] = { + 'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ', + 'F', 'l', 'a', 's', 'h', ' ', 'M', 'e', 'd', 'i', 'a', ' ', + 'S', 'e', 'r', 'v', 'e', 'r', ' ', + '0', '0', '1', + + 0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1, + 0x02, 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, + 0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE +}; + + +static u_char +ngx_rtmp_client_key[] = { + 'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ', + 'F', 'l', 'a', 's', 'h', ' ', 'P', 'l', 'a', 'y', 'e', 'r', ' ', + '0', '0', '1', + + 0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1, + 0x02, 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, + 0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE +}; + + +static const u_char +ngx_rtmp_server_version[4] = { + 0x0D, 0x0E, 0x0A, 0x0D +}; + + +static const u_char +ngx_rtmp_client_version[4] = { + 0x0C, 0x00, 0x0D, 0x0E +}; + + +#define NGX_RTMP_HANDSHAKE_KEYLEN SHA256_DIGEST_LENGTH +#define NGX_RTMP_HANDSHAKE_BUFSIZE 3073 +#define NGX_RTMP_HANDSHAKE_CHALLENGE_SIZE 1537 + + +#define NGX_RTMP_HANDSHAKE_SERVER_RECV_CHALLENGE 1 +#define NGX_RTMP_HANDSHAKE_SERVER_SEND_CHALLRESP 2 +#define NGX_RTMP_HANDSHAKE_SERVER_RECV_RESPONSE 3 +#define NGX_RTMP_HANDSHAKE_SERVER_DONE 4 + + +#define NGX_RTMP_HANDSHAKE_CLIENT_SEND_CHALLENGE 6 +#define NGX_RTMP_HANDSHAKE_CLIENT_RECV_CHALLENGE 7 +#define NGX_RTMP_HANDSHAKE_CLIENT_RECV_RESPONSE 8 +#define NGX_RTMP_HANDSHAKE_CLIENT_SEND_RESPONSE 9 +#define NGX_RTMP_HANDSHAKE_CLIENT_DONE 10 + + +static ngx_str_t ngx_rtmp_server_full_key + = { sizeof(ngx_rtmp_server_key), ngx_rtmp_server_key }; +static ngx_str_t ngx_rtmp_server_partial_key + = { 36, ngx_rtmp_server_key }; + +static ngx_str_t ngx_rtmp_client_full_key + = { sizeof(ngx_rtmp_client_key), ngx_rtmp_client_key }; +static ngx_str_t ngx_rtmp_client_partial_key + = { 30, ngx_rtmp_client_key }; + + +static ngx_int_t +ngx_rtmp_make_digest(ngx_str_t *key, ngx_buf_t *src, + u_char *skip, u_char *dst, ngx_log_t *log) +{ + static HMAC_CTX *hmac; + unsigned int len; + + if (hmac == NULL) { +#if OPENSSL_VERSION_NUMBER < 0x10100000L + static HMAC_CTX shmac; + hmac = &shmac; + HMAC_CTX_init(hmac); +#else + hmac = HMAC_CTX_new(); + if (hmac == NULL) { + return NGX_ERROR; + } +#endif + } + + HMAC_Init_ex(hmac, key->data, key->len, EVP_sha256(), NULL); + + if (skip && src->pos <= skip && skip <= src->last) { + if (skip != src->pos) { + HMAC_Update(hmac, src->pos, skip - src->pos); + } + if (src->last != skip + NGX_RTMP_HANDSHAKE_KEYLEN) { + HMAC_Update(hmac, skip + NGX_RTMP_HANDSHAKE_KEYLEN, + src->last - skip - NGX_RTMP_HANDSHAKE_KEYLEN); + } + } else { + HMAC_Update(hmac, src->pos, src->last - src->pos); + } + + HMAC_Final(hmac, dst, &len); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_find_digest(ngx_buf_t *b, ngx_str_t *key, size_t base, ngx_log_t *log) +{ + size_t n, offs; + u_char digest[NGX_RTMP_HANDSHAKE_KEYLEN]; + u_char *p; + + offs = 0; + for (n = 0; n < 4; ++n) { + offs += b->pos[base + n]; + } + offs = (offs % 728) + base + 4; + p = b->pos + offs; + + if (ngx_rtmp_make_digest(key, b, p, digest, log) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_memcmp(digest, p, NGX_RTMP_HANDSHAKE_KEYLEN) == 0) { + return offs; + } + + return NGX_ERROR; +} + + +static ngx_int_t +ngx_rtmp_write_digest(ngx_buf_t *b, ngx_str_t *key, size_t base, + ngx_log_t *log) +{ + size_t n, offs; + u_char *p; + + offs = 0; + for (n = 8; n < 12; ++n) { + offs += b->pos[base + n]; + } + offs = (offs % 728) + base + 12; + p = b->pos + offs; + + if (ngx_rtmp_make_digest(key, b, p, p, log) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static void +ngx_rtmp_fill_random_buffer(ngx_buf_t *b, u_char *end) +{ + for (; b->last != end; ++b->last) { + *b->last = (u_char) rand(); + } +} + + +static ngx_buf_t * +ngx_rtmp_alloc_handshake_buffer(ngx_rtmp_session_t *s) +{ + ngx_rtmp_core_srv_conf_t *cscf; + ngx_chain_t *cl; + ngx_buf_t *b; + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0, + "handshake: allocating buffer"); + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + if (cscf->free_hs) { + cl = cscf->free_hs; + b = cl->buf; + cscf->free_hs = cl->next; + ngx_free_chain(cscf->pool, cl); + + } else { + b = ngx_pcalloc(cscf->pool, sizeof(ngx_buf_t)); + if (b == NULL) { + return NULL; + } + b->memory = 1; + b->start = ngx_pcalloc(cscf->pool, NGX_RTMP_HANDSHAKE_BUFSIZE); + if (b->start == NULL) { + return NULL; + } + b->end = b->start + NGX_RTMP_HANDSHAKE_BUFSIZE; + } + + b->pos = b->last = b->start; + + return b; +} + + +void +ngx_rtmp_free_handshake_buffers(ngx_rtmp_session_t *s) +{ + ngx_rtmp_core_srv_conf_t *cscf; + ngx_chain_t *cl; + + if (s->hs_buf == NULL) { + return; + } + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + cl = ngx_alloc_chain_link(cscf->pool); + if (cl == NULL) { + return; + } + cl->buf = s->hs_buf; + cl->next = cscf->free_hs; + cscf->free_hs = cl; + s->hs_buf = NULL; +} + + +static ngx_int_t +ngx_rtmp_handshake_create_challenge(ngx_rtmp_session_t *s, + const u_char version[4], ngx_str_t *key) +{ + ngx_buf_t *b; + u_char *end; + + b = s->hs_buf; + b->last = b->pos = b->start; + end = b->start + NGX_RTMP_HANDSHAKE_CHALLENGE_SIZE; + *b->last++ = '\x03'; + b->last = ngx_rtmp_rcpymem(b->last, &s->epoch, 4); + b->last = ngx_cpymem(b->last, version, 4); + ngx_rtmp_fill_random_buffer(b, end); + ++b->pos; + if (ngx_rtmp_write_digest(b, key, 0, s->log) != NGX_OK) { + return NGX_ERROR; + } + --b->pos; + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_handshake_parse_challenge(ngx_rtmp_session_t *s, + ngx_str_t *peer_key, ngx_str_t *key) +{ + ngx_buf_t *b; + u_char *p; + ngx_int_t offs; + + b = s->hs_buf; + if (*b->pos != '\x03') { + ngx_log_error(NGX_LOG_INFO, s->log, 0, + "handshake: unexpected RTMP version: %i", + (ngx_int_t)*b->pos); + return NGX_ERROR; + } + ++b->pos; + s->peer_epoch = 0; + ngx_rtmp_rmemcpy(&s->peer_epoch, b->pos, 4); + + p = b->pos + 4; + ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->log, 0, + "handshake: peer version=%i.%i.%i.%i epoch=%uD", + (ngx_int_t)p[3], (ngx_int_t)p[2], + (ngx_int_t)p[1], (ngx_int_t)p[0], + (uint32_t)s->peer_epoch); + if (*(uint32_t *)p == 0) { + s->hs_old = 1; + return NGX_OK; + } + + offs = ngx_rtmp_find_digest(b, peer_key, 772, s->log); + if (offs == NGX_ERROR) { + offs = ngx_rtmp_find_digest(b, peer_key, 8, s->log); + } + if (offs == NGX_ERROR) { + ngx_log_error(NGX_LOG_INFO, s->log, 0, "handshake: digest not found"); + s->hs_old = 1; + return NGX_OK; + } + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "handshake: digest found at pos=%i", offs); + b->pos += offs; + b->last = b->pos + NGX_RTMP_HANDSHAKE_KEYLEN; + s->hs_digest = ngx_palloc(s->pool, NGX_RTMP_HANDSHAKE_KEYLEN); + if (ngx_rtmp_make_digest(key, b, NULL, s->hs_digest, s->log) + != NGX_OK) + { + return NGX_ERROR; + } + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_handshake_create_response(ngx_rtmp_session_t *s) +{ + ngx_buf_t *b; + u_char *p, *pos; + ngx_str_t key; + + b = s->hs_buf; + pos = b->pos; + b->pos = b->start + NGX_RTMP_HANDSHAKE_CHALLENGE_SIZE; + ngx_rtmp_fill_random_buffer(b, b->end); + if (s->hs_digest) { + p = b->last - NGX_RTMP_HANDSHAKE_KEYLEN; + key.data = s->hs_digest; + key.len = NGX_RTMP_HANDSHAKE_KEYLEN; + if (ngx_rtmp_make_digest(&key, b, p, p, s->log) != NGX_OK) { + return NGX_ERROR; + } + } + b->pos = pos; + + return NGX_OK; +} + + +static void +ngx_rtmp_handshake_done(ngx_rtmp_session_t *s) +{ + ngx_rtmp_free_handshake_buffers(s); + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0, + "handshake: done"); + + if (ngx_rtmp_fire_event(s, NGX_RTMP_HANDSHAKE_DONE, + NULL, NULL) != NGX_OK) + { + ngx_rtmp_finalize_session(s); + return; + } + + s->stage = NGX_LIVE_HANDSHAKE_DONE; + s->handshake_done_time = ngx_current_msec; + + ngx_rtmp_cycle(s); +} + + +static void +ngx_rtmp_handshake_recv(ngx_event_t *rev) +{ + ssize_t n; + ngx_connection_t *c; + ngx_rtmp_session_t *s; + ngx_buf_t *b; + + c = rev->data; + s = c->data; + + if (c->destroyed) { + return; + } + + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, s->log, NGX_ETIMEDOUT, + "handshake: recv: client timed out"); + c->timedout = 1; + ngx_rtmp_finalize_session(s); + return; + } + + if (rev->timer_set) { + ngx_del_timer(rev); + } + + b = s->hs_buf; + + while (b->last != b->end) { + n = c->recv(c, b->last, b->end - b->last); + + if (n == NGX_ERROR || n == 0) { + ngx_rtmp_finalize_session(s); + return; + } + + if (n == NGX_AGAIN) { + ngx_add_timer(rev, s->timeout); + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "handshake: recv: handle read event error"); + ngx_rtmp_finalize_session(s); + } + return; + } + + b->last += n; + } + + if (rev->active) { + ngx_del_event(rev, NGX_READ_EVENT, 0); + } + + ++s->hs_stage; + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "handshake: stage %ui", s->hs_stage); + + switch (s->hs_stage) { + case NGX_RTMP_HANDSHAKE_SERVER_SEND_CHALLRESP: + if (ngx_rtmp_handshake_parse_challenge(s, + &ngx_rtmp_client_partial_key, + &ngx_rtmp_server_full_key) != NGX_OK) + { + ngx_log_error(NGX_LOG_INFO, s->log, 0, + "handshake: error parsing challenge"); + ngx_rtmp_finalize_session(s); + return; + } + + // create version and challege + if (ngx_rtmp_handshake_create_challenge(s, ngx_rtmp_server_version, + &ngx_rtmp_server_partial_key) != NGX_OK) + { + ngx_log_error(NGX_LOG_INFO, s->log, 0, + "handshake: error creating challenge"); + ngx_rtmp_finalize_session(s); + return; + } + + // create response + if (s->hs_old) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0, + "handshake: old-style response"); + s->hs_buf->last = s->hs_buf->end; + } else if (ngx_rtmp_handshake_create_response(s) != NGX_OK) { + ngx_log_error(NGX_LOG_INFO, s->log, 0, + "handshake: response error"); + ngx_rtmp_finalize_session(s); + return; + } + + ngx_rtmp_handshake_send(c->write); + break; + + case NGX_RTMP_HANDSHAKE_SERVER_DONE: + ngx_rtmp_handshake_done(s); + break; + + case NGX_RTMP_HANDSHAKE_CLIENT_RECV_RESPONSE: + if (ngx_rtmp_handshake_parse_challenge(s, + &ngx_rtmp_server_partial_key, + &ngx_rtmp_client_full_key) != NGX_OK) + { + ngx_log_error(NGX_LOG_INFO, s->log, 0, + "handshake: error parsing challenge"); + ngx_rtmp_finalize_session(s); + return; + } + s->hs_buf->pos = s->hs_buf->last = s->hs_buf->start + + NGX_RTMP_HANDSHAKE_CHALLENGE_SIZE; + ngx_rtmp_handshake_recv(c->read); + break; + + case NGX_RTMP_HANDSHAKE_CLIENT_SEND_RESPONSE: + if (ngx_rtmp_handshake_create_response(s) != NGX_OK) { + ngx_log_error(NGX_LOG_INFO, s->log, 0, + "handshake: response error"); + ngx_rtmp_finalize_session(s); + return; + } + ngx_rtmp_handshake_send(c->write); + break; + } +} + + +static void +ngx_rtmp_handshake_send(ngx_event_t *wev) +{ + ngx_int_t n; + ngx_connection_t *c; + ngx_rtmp_session_t *s; + ngx_buf_t *b; + + c = wev->data; + s = c->data; + + if (c->destroyed) { + return; + } + + if (wev->timedout) { + ngx_log_error(NGX_LOG_INFO, s->log, NGX_ETIMEDOUT, + "handshake: send: client timed out"); + c->timedout = 1; + ngx_rtmp_finalize_session(s); + return; + } + + if (wev->timer_set) { + ngx_del_timer(wev); + } + + b = s->hs_buf; + + while(b->pos != b->last) { + n = c->send(c, b->pos, b->last - b->pos); + + if (n == NGX_ERROR) { + ngx_rtmp_finalize_session(s); + return; + } + + if (n == NGX_AGAIN || n == 0) { + ngx_add_timer(c->write, s->timeout); + if (ngx_handle_write_event(c->write, 0) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "handshake: recv: handle write event error"); + ngx_rtmp_finalize_session(s); + } + return; + } + + b->pos += n; + } + + if (wev->active) { + ngx_del_event(wev, NGX_WRITE_EVENT, 0); + } + + ++s->hs_stage; + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "handshake: stage %ui", s->hs_stage); + + switch (s->hs_stage) { + + case NGX_RTMP_HANDSHAKE_SERVER_RECV_RESPONSE: + s->hs_buf->last = s->hs_buf->start + + NGX_RTMP_HANDSHAKE_CHALLENGE_SIZE; + ngx_rtmp_handshake_recv(c->read); + break; + + case NGX_RTMP_HANDSHAKE_CLIENT_RECV_CHALLENGE: + s->hs_buf->pos = s->hs_buf->last = s->hs_buf->end + - NGX_RTMP_HANDSHAKE_CHALLENGE_SIZE; + ngx_rtmp_handshake_recv(c->read); + break; + + case NGX_RTMP_HANDSHAKE_CLIENT_DONE: + ngx_rtmp_handshake_done(s); + break; + } +} + + +void +ngx_rtmp_handshake(ngx_rtmp_session_t *s) +{ + ngx_connection_t *c; + + c = s->connection; + c->read->handler = ngx_rtmp_handshake_recv; + c->write->handler = ngx_rtmp_handshake_send; + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0, + "handshake: start server handshake"); + + s->hs_buf = ngx_rtmp_alloc_handshake_buffer(s); + s->hs_buf->pos = s->hs_buf->last = s->hs_buf->end + - NGX_RTMP_HANDSHAKE_CHALLENGE_SIZE; + s->hs_stage = NGX_RTMP_HANDSHAKE_SERVER_RECV_CHALLENGE; + + ngx_rtmp_handshake_recv(c->read); +} + + +void +ngx_rtmp_client_handshake(ngx_rtmp_session_t *s, unsigned async) +{ + ngx_connection_t *c; + + c = s->connection; + c->read->handler = ngx_rtmp_handshake_recv; + c->write->handler = ngx_rtmp_handshake_send; + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0, + "handshake: start client handshake"); + + s->hs_buf = ngx_rtmp_alloc_handshake_buffer(s); + s->hs_stage = NGX_RTMP_HANDSHAKE_CLIENT_SEND_CHALLENGE; + + if (ngx_rtmp_handshake_create_challenge(s, + ngx_rtmp_client_version, + &ngx_rtmp_client_partial_key) != NGX_OK) + { + ngx_rtmp_finalize_session(s); + return; + } + + if (async) { + ngx_add_timer(c->write, s->timeout); + if (ngx_handle_write_event(c->write, 0) != NGX_OK) { + ngx_rtmp_finalize_session(s); + } + return; + } + + ngx_rtmp_handshake_send(c->write); +} + diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_init.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_init.c new file mode 100644 index 0000000000..3cbc62868f --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_init.c @@ -0,0 +1,767 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp.h" +#include "ngx_rtmp_cmd_module.h" +#include "ngx_live_relay.h" +#include "ngx_rtmp_proxy_protocol.h" +#include "ngx_http_client.h" +#include "ngx_rbuf.h" +#include "ngx_poold.h" + + +static void ngx_rtmp_close_connection(ngx_connection_t *c); +static u_char * ngx_rtmp_log_error(ngx_log_t *log, u_char *buf, size_t len); + + +typedef struct { + ngx_str_t *client; + ngx_rtmp_session_t *session; + void *data; // combined log + ngx_log_handler_pt handler; // combined log handler +} ngx_rtmp_error_log_ctx_t; + + +char *ngx_live_stage[] = { + "init", + "handshake_done", + "connect", + "create_stream", + "publish", + "play", + "audio_video", + "close_stream", +}; + + +char *ngx_live_err[] = { + "internal_err", + "normal_close", + "rtmp_send_err", + "rtmp_send_timeout", + "flv_send_err", + "flv_send_timeout", + "rtmp_recv_err", + "flv_recv_err", + "relay_transit", + "relay_timeout", + "control_drop", + "drop_idle", + "notify_notify_err", + "notify_relay_err", + "notify_para_err", + "relay_close", + "process_exit", +}; + + +void +ngx_rtmp_init_connection(ngx_connection_t *c) +{ + ngx_uint_t i; + ngx_rtmp_port_t *port; + struct sockaddr *sa; + struct sockaddr_in *sin; + ngx_rtmp_in_addr_t *addr; + ngx_rtmp_session_t *s; + ngx_rtmp_addr_conf_t *addr_conf; + ngx_int_t unix_socket; +#if (NGX_HAVE_INET6) + struct sockaddr_in6 *sin6; + ngx_rtmp_in6_addr_t *addr6; +#endif + + ++ngx_rtmp_naccepted; + + /* find the server configuration for the address:port */ + + /* AF_INET only */ + + port = c->listening->servers; + unix_socket = 0; + + if (port->naddrs > 1) { + + /* + * There are several addresses on this port and one of them + * is the "*:port" wildcard so getsockname() is needed to determine + * the server address. + * + * AcceptEx() already gave this address. + */ + + if (ngx_connection_local_sockaddr(c, NULL, 0) != NGX_OK) { + ngx_rtmp_close_connection(c); + return; + } + + sa = c->local_sockaddr; + + switch (sa->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + sin6 = (struct sockaddr_in6 *) sa; + + addr6 = port->addrs; + + /* the last address is "*" */ + + for (i = 0; i < port->naddrs - 1; i++) { + if (ngx_memcmp(&addr6[i].addr6, &sin6->sin6_addr, 16) == 0) { + break; + } + } + + addr_conf = &addr6[i].conf; + + break; +#endif + + case AF_UNIX: + unix_socket = 1; + + sin = (struct sockaddr_in *) sa; + + addr = port->addrs; + + /* the last address is "*" */ + + for (i = 0; i < port->naddrs - 1; i++) { + if (addr[i].addr == sin->sin_addr.s_addr) { + break; + } + } + + addr_conf = &addr[i].conf; + + break; + + default: /* AF_INET */ + sin = (struct sockaddr_in *) sa; + + addr = port->addrs; + + /* the last address is "*" */ + + for (i = 0; i < port->naddrs - 1; i++) { + if (addr[i].addr == sin->sin_addr.s_addr) { + break; + } + } + + addr_conf = &addr[i].conf; + + break; + } + + } else { + switch (c->local_sockaddr->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + addr6 = port->addrs; + addr_conf = &addr6[0].conf; + break; +#endif + + case AF_UNIX: + unix_socket = 1; + addr = port->addrs; + addr_conf = &addr[0].conf; + break; + + default: /* AF_INET */ + addr = port->addrs; + addr_conf = &addr[0].conf; + break; + } + } + + ngx_log_error(NGX_LOG_INFO, c->log, 0, "*%ui client connected '%V'", + c->number, &c->addr_text); + + s = ngx_rtmp_create_session(addr_conf); + if (s == NULL) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "create rtmp session failed"); + return; + } + s->log->connection = c->number; + s->number = c->number; + s->remote_addr_text.data = ngx_pcalloc(s->pool, c->addr_text.len); + s->remote_addr_text.len = c->addr_text.len; + ngx_memcpy(s->remote_addr_text.data, c->addr_text.data, c->addr_text.len); + s->sockaddr = ngx_pcalloc(s->pool, sizeof(struct sockaddr)); + ngx_memcpy(s->sockaddr, c->sockaddr, sizeof(struct sockaddr)); + + ngx_rtmp_init_session(s, c); + + /* only auto-pushed connections are + * done through unix socket */ + + s->interprocess = unix_socket; + + if (addr_conf->proxy_protocol) { + ngx_rtmp_proxy_protocol(s); + + } else { + ngx_rtmp_handshake(s); + } +} + + +static u_char * +ngx_rtmp_log_error(ngx_log_t *log, u_char *buf, size_t len) +{ + u_char *p; + ngx_rtmp_session_t *s; + ngx_rtmp_error_log_ctx_t *ctx; + + p = buf; + + ctx = log->data; + + if (ctx->client) { + p = ngx_snprintf(buf, len, ", client: %V", ctx->client); + len -= p - buf; + buf = p; + } + + s = ctx->session; + + if (s == NULL) { + return p; + } + + p = ngx_snprintf(buf, len, ", server: %V, session: %p", s->addr_text, s); + len -= p - buf; + buf = p; + + if (s->stream.len) { + p = ngx_snprintf(buf, len, ", stream: %V", &s->stream); + len -= p - buf; + buf = p; + } + + if (ctx->data) { // combined log + log->data = ctx->data; + p = ctx->handler(log, p, len); + log->data = ctx; + } + + return p; +} + + +static void +ngx_rtmp_close_connection(ngx_connection_t *c) +{ + ngx_pool_t *pool; + + if (c == NULL) { + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, c->log, 0, "close connection"); + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_active, -1); +#endif + + pool = c->pool; + ngx_close_connection(c); + ngx_destroy_pool(pool); +} + + +static void +ngx_rtmp_close_session(ngx_rtmp_session_t *s) +{ + if (s->ping_evt.timer_set) { + ngx_del_timer(&s->ping_evt); + } + + if (s->in_old_pool) { + NGX_DESTROY_POOL(s->in_old_pool); + s->in_old_pool = NULL; + } + + if (s->in_pool) { + NGX_DESTROY_POOL(s->in_pool); + s->in_pool = NULL; + } + + if (s->live_type == NGX_HTTP_FLV_LIVE) { + ngx_put_chainbufs(s->in_streams[0].in); + } + + ngx_rtmp_free_handshake_buffers(s); + + ngx_rtmp_free_merge_frame(s); + + if (s->live_type == NGX_MPEGTS_LIVE) { + while (s->out_pos != s->out_last) { + ngx_rtmp_shared_free_mpegts_frame(s->mpegts_out[s->out_pos++]); + s->out_pos %= s->out_queue; + } + } else { + while (s->out_pos != s->out_last) { + ngx_rtmp_shared_free_frame(s->out[s->out_pos++]); + s->out_pos %= s->out_queue; + } + } + + if (s->pool) { + NGX_DESTROY_POOL(s->pool); + } +} + + +static void +ngx_rtmp_close_session_handler(ngx_event_t *e) +{ + ngx_rtmp_session_t *s; + ngx_connection_t *c; + + s = e->data; + c = s->connection; + if (c) { + c->destroyed = 1; + } + + ngx_log_error(NGX_LOG_INFO, s->log, 0, "async close session"); + + ngx_rtmp_fire_event(s, NGX_RTMP_DISCONNECT, NULL, NULL); + + // close connection before session, connection log use session log + // may cause memory error + ngx_rtmp_close_connection(c); + + ngx_rtmp_close_session(s); +} + + +static void +ngx_rtmp_async_finalize_http_client(ngx_event_t *ev) +{ + ngx_rtmp_session_t *s; + ngx_http_request_t *hcr; + + s = ev->data; + hcr = s->request; + + ngx_log_error(NGX_LOG_INFO, s->log, 0, "asyn finalize http client"); + + if (hcr) { + ngx_http_client_finalize_request(hcr, 1); + } +} + + +static void +ngx_rtmp_async_finalize_http_request(ngx_event_t *ev) +{ + ngx_rtmp_session_t *s; + ngx_http_request_t *r; + + s = ev->data; + r = s->request; + + ngx_log_error(NGX_LOG_INFO, s->log, 0, "asyn finalize http request"); + + if (r->header_sent) { + ngx_http_finalize_request(r, NGX_HTTP_CLIENT_CLOSED_REQUEST); + ngx_http_run_posted_requests(r->connection); + } else { + r->error_page = 1; + + if (s->status) { + ngx_http_finalize_request(r, s->status); + } else { + ngx_http_finalize_request(r, NGX_HTTP_SERVICE_UNAVAILABLE); + } + } +} + + +void +ngx_rtmp_finalize_session(ngx_rtmp_session_t *s) +{ + ngx_event_t *e; + ngx_connection_t *c; + + ngx_log_error(NGX_LOG_INFO, s->log, 0, "finalize session"); + + if (s->live_type == NGX_HLS_LIVE) { + ngx_rtmp_finalize_fake_session(s); + return; + } + + c = s->connection; + if (c && c->destroyed) { + return; + } + + if (s->live_type != NGX_RTMP_LIVE) { + e = &s->close; + e->data = s; + if (s->relay) { + e->handler = ngx_rtmp_async_finalize_http_client; + } else { + e->handler = ngx_rtmp_async_finalize_http_request; + } + e->log = s->log; + + ngx_post_event(e, &ngx_posted_events); + + return; + } + + e = &s->close; + e->data = s; + e->handler = ngx_rtmp_close_session_handler; + e->log = s->log; + + ngx_post_event(e, &ngx_posted_events); +} + + +// session combined with http request or http client request +void +ngx_rtmp_finalize_fake_session(ngx_rtmp_session_t *s) +{ + ngx_log_error(NGX_LOG_INFO, s->log, 0, "finalize fake session"); + + ngx_rtmp_fire_event(s, NGX_RTMP_DISCONNECT, NULL, NULL); + + ngx_rtmp_close_session(s); +} + + +void +ngx_rtmp_set_combined_log(ngx_rtmp_session_t *s, void *d, ngx_log_handler_pt h) +{ + ngx_rtmp_error_log_ctx_t *ctx; + + ctx = s->log->data; + ctx->data = d; + ctx->handler = h; +} + + +// Only rtmp need to init session +void +ngx_rtmp_init_session(ngx_rtmp_session_t *s, ngx_connection_t *c) +{ + ngx_rtmp_error_log_ctx_t *ctx; + ngx_rtmp_core_main_conf_t *cmcf; + + cmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_core_module); + + c->data = s; + s->connection = c; + + c->log = s->log; + c->write->log = s->log; + c->read->log = s->log; + + ctx = s->log->data; + ctx->client = &c->addr_text; + + s->log->connection = c->number; + + c->log_error = NGX_ERROR_INFO; + + ngx_rtmp_set_chunk_size(s, NGX_RTMP_DEFAULT_CHUNK_SIZE); + + if (s->variables == NULL) { + s->variables = ngx_pcalloc(s->pool, cmcf->variables.nelts + * sizeof(ngx_http_variable_value_t)); + } + + if (s->variables == NULL) { + ngx_rtmp_finalize_session(s); + return; + } + + return; +} + + +ngx_rtmp_session_t * +ngx_rtmp_create_relay_session(ngx_rtmp_session_t *s, void *tag) +{ + ngx_rtmp_session_t *rs; + ngx_live_relay_ctx_t *rctx; + ngx_rtmp_core_main_conf_t *cmcf; + + rs = ngx_rtmp_create_session(s->addr_conf); + if (rs == NULL) { + return NULL; + } + + rs->relay = 1; + rs->main_conf = s->main_conf; + rs->srv_conf = s->srv_conf; + rs->app_conf = s->app_conf; + + // clone para from origin session +#define NGX_RTMP_RELAY_SESSION_COPY_PARA(para) \ + if (ngx_copy_str(rs->pool, &rs->para, &s->para) != NGX_OK) { \ + goto destroy; \ + } + + NGX_RTMP_RELAY_SESSION_COPY_PARA(stream); + + NGX_RTMP_RELAY_SESSION_COPY_PARA(name); + NGX_RTMP_RELAY_SESSION_COPY_PARA(pargs); + + NGX_RTMP_RELAY_SESSION_COPY_PARA(app); + NGX_RTMP_RELAY_SESSION_COPY_PARA(args); + NGX_RTMP_RELAY_SESSION_COPY_PARA(flashver); + NGX_RTMP_RELAY_SESSION_COPY_PARA(swf_url); + NGX_RTMP_RELAY_SESSION_COPY_PARA(tc_url); + NGX_RTMP_RELAY_SESSION_COPY_PARA(page_url); + + NGX_RTMP_RELAY_SESSION_COPY_PARA(serverid); +#undef NGX_RTMP_RELAY_SESSION_COPY_PARA + + rs->acodecs = s->acodecs; + rs->vcodecs = s->vcodecs; + + ngx_rtmp_cmd_middleware_init(rs); + + // create relay ctx + rctx = ngx_pcalloc(rs->pool, sizeof(ngx_live_relay_ctx_t)); + if (rctx == NULL) { + goto destroy; + } + rctx->domain = rs->domain; + rctx->app = rs->app; + rctx->args = rs->args; + rctx->name = rs->name; + rctx->pargs = rs->pargs; + + rctx->referer = rs->page_url; + rctx->user_agent = rs->flashver; + rctx->swf_url = rs->swf_url; + rctx->acodecs = rs->acodecs; + rctx->vcodecs = rs->vcodecs; + + rctx->tag = tag; + + ngx_rtmp_set_ctx(rs, rctx, ngx_live_relay_module); + + cmcf = ngx_rtmp_get_module_main_conf(rs, ngx_rtmp_core_module); + if (rs->variables == NULL) { + rs->variables = ngx_pcalloc(rs->pool, cmcf->variables.nelts + * sizeof(ngx_http_variable_value_t)); + } + + return rs; + +destroy: + ngx_rtmp_close_session(rs); + + return NULL; +} + + +ngx_rtmp_session_t * +ngx_rtmp_create_static_session(ngx_live_relay_t *relay, + ngx_rtmp_addr_conf_t *addr_conf, void *tag) +{ + ngx_rtmp_session_t *rs; + ngx_live_relay_ctx_t *rctx; + ngx_uint_t n; + ngx_rtmp_core_srv_conf_t *cscf; + ngx_rtmp_core_app_conf_t **cacfp; + + rs = ngx_rtmp_create_session(addr_conf); + if (rs == NULL) { + return NULL; + } + + rs->static_pull = 1; + rs->relay = 1; + + // clone para from origin session +#define NGX_RTMP_RELAY_SESSION_COPY_PARA(to, from) \ + if (ngx_copy_str(rs->pool, &rs->to, &relay->from) != NGX_OK) { \ + goto destroy; \ + } + + NGX_RTMP_RELAY_SESSION_COPY_PARA(app, app); + NGX_RTMP_RELAY_SESSION_COPY_PARA(name, name); + NGX_RTMP_RELAY_SESSION_COPY_PARA(pargs, pargs); + NGX_RTMP_RELAY_SESSION_COPY_PARA(page_url, referer); + NGX_RTMP_RELAY_SESSION_COPY_PARA(flashver, user_agent); + + NGX_RTMP_RELAY_SESSION_COPY_PARA(stream, stream); +#undef NGX_RTMP_RELAY_SESSION_COPY_PARA + + rs->tc_url.len = sizeof("rtmp://") - 1 + relay->domain.len + + sizeof("/") - 1 + relay->app.len; + rs->tc_url.data = ngx_pcalloc(rs->pool, rs->tc_url.len); + if (rs->tc_url.data == NULL) { + goto destroy; + } + ngx_snprintf(rs->tc_url.data, rs->tc_url.len, "rtmp://%V/%V", + &relay->domain, &relay->app); + + ngx_rtmp_cmd_middleware_init(rs); + + if (ngx_rtmp_set_virtual_server(rs, &rs->domain)) { + goto destroy; + } + cscf = ngx_rtmp_get_module_srv_conf(rs, ngx_rtmp_core_module); + + rs->live_server = ngx_live_create_server(&rs->serverid); + + cacfp = cscf->applications.elts; + for (n = 0; n < cscf->applications.nelts; ++n, ++cacfp) { + if ((*cacfp)->name.len == rs->app.len && + ngx_strncmp((*cacfp)->name.data, rs->app.data, rs->app.len) == 0) + { + /* found app! */ + rs->app_conf = (*cacfp)->app_conf; + break; + } + } + + if (rs->app_conf == NULL) { + + if (cscf->default_app == NULL || cscf->default_app->app_conf == NULL) { + ngx_log_error(NGX_LOG_ERR, rs->log, 0, + "static session: application not found: '%V'", &rs->app); + goto destroy; + } + + rs->app_conf = cscf->default_app->app_conf; + } + + // create relay ctx + rctx = ngx_pcalloc(rs->pool, sizeof(ngx_live_relay_ctx_t)); + if (rctx == NULL) { + goto destroy; + } + rctx->domain = rs->domain; + rctx->app = rs->app; + rctx->args = rs->args; + rctx->name = rs->name; + rctx->pargs = rs->pargs; + + rctx->referer = rs->page_url; + rctx->user_agent = rs->flashver; + rctx->swf_url = rs->swf_url; + rctx->acodecs = rs->acodecs; + rctx->vcodecs = rs->vcodecs; + + rctx->tag = tag; + + ngx_rtmp_set_ctx(rs, rctx, ngx_live_relay_module); + + return rs; + +destroy: + ngx_rtmp_close_session(rs); + + return NULL; +} + + +// if return NULL, memory must be error +ngx_rtmp_session_t * +ngx_rtmp_create_session(ngx_rtmp_addr_conf_t *addr_conf) +{ + ngx_rtmp_session_t *s; + ngx_rtmp_core_srv_conf_t *cscf; + ngx_pool_t *pool; + ngx_rtmp_error_log_ctx_t *ctx; + + pool = NGX_CREATE_POOL(4096, ngx_cycle->log); + if (pool == NULL) { + return NULL; + } + + s = ngx_pcalloc(pool, sizeof(ngx_rtmp_session_t) + + sizeof(ngx_rtmp_frame_t *) * ((ngx_rtmp_core_srv_conf_t *) + addr_conf->default_server->ctx-> srv_conf[ngx_rtmp_core_module + .ctx_index])->out_queue); + if (s == NULL) { + goto destroy; + } + s->pool = pool; + + s->addr_conf = addr_conf; + + s->main_conf = addr_conf->default_server->ctx->main_conf; + s->srv_conf = addr_conf->default_server->ctx->srv_conf; + + s->addr_text = ngx_pcalloc(s->pool, sizeof(ngx_str_t)); + s->addr_text->data = ngx_pcalloc(s->pool, addr_conf->addr_text.len); + s->addr_text->len = addr_conf->addr_text.len; + ngx_memcpy(s->addr_text->data, + addr_conf->addr_text.data, addr_conf->addr_text.len); + + s->ctx = ngx_pcalloc(pool, sizeof(void *) * ngx_rtmp_max_module); + if (s->ctx == NULL) { + goto destroy; + } + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + s->out_queue = cscf->out_queue; + s->out_cork = cscf->out_cork; + s->in_streams = ngx_pcalloc(pool, sizeof(ngx_rtmp_stream_t) + * cscf->max_streams); + if (s->in_streams == NULL) { + goto destroy; + } + +#if (nginx_version >= 1007005) + ngx_queue_init(&s->posted_dry_events); +#endif + + s->epoch = ngx_current_msec; + s->timeout = cscf->timeout; + s->buflen = cscf->buflen; + + // init log + ctx = ngx_pcalloc(pool, sizeof(ngx_rtmp_error_log_ctx_t)); + if (ctx == NULL) { + goto destroy; + } + + ctx->session = s; + + s->log = ngx_pcalloc(pool, sizeof(ngx_log_t)); + if (s->pool == NULL) { + goto destroy; + } + *s->log = ngx_cycle->new_log; + + s->log->handler = ngx_rtmp_log_error; + s->log->data = ctx; + + s->stage = NGX_LIVE_INIT; + s->init_time = ngx_current_msec; + + s->mpegts_out = ngx_pcalloc(s->pool, sizeof(ngx_mpegts_frame_t *) * + ((ngx_rtmp_core_srv_conf_t *) addr_conf->default_server->ctx-> + srv_conf[ngx_rtmp_core_module.ctx_index])->out_queue); + + return s; + +destroy: + if (pool) { + NGX_DESTROY_POOL(pool); + } + + return NULL; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_limit_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_limit_module.c new file mode 100644 index 0000000000..dae814375d --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_limit_module.c @@ -0,0 +1,205 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp.h" + + +typedef struct { + ngx_int_t max_conn; + ngx_shm_zone_t *shm_zone; +} ngx_rtmp_limit_main_conf_t; + + +static ngx_str_t shm_name = ngx_string("rtmp_limit"); + + +static ngx_int_t ngx_rtmp_limit_postconfiguration(ngx_conf_t *cf); +static void *ngx_rtmp_limit_create_main_conf(ngx_conf_t *cf); + + +static ngx_command_t ngx_rtmp_limit_commands[] = { + + { ngx_string("max_connections"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_RTMP_MAIN_CONF_OFFSET, + offsetof(ngx_rtmp_limit_main_conf_t, max_conn), + NULL }, + + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_rtmp_limit_module_ctx = { + NULL, /* preconfiguration */ + ngx_rtmp_limit_postconfiguration, /* postconfiguration */ + ngx_rtmp_limit_create_main_conf, /* create main configuration */ + NULL, /* init main configuration */ + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + NULL, /* create app configuration */ + NULL /* merge app configuration */ +}; + + +ngx_module_t ngx_rtmp_limit_module = { + NGX_MODULE_V1, + &ngx_rtmp_limit_module_ctx, /* module context */ + ngx_rtmp_limit_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void * +ngx_rtmp_limit_create_main_conf(ngx_conf_t *cf) +{ + ngx_rtmp_limit_main_conf_t *lmcf; + + lmcf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_limit_main_conf_t)); + if (lmcf == NULL) { + return NULL; + } + + lmcf->max_conn = NGX_CONF_UNSET; + + return lmcf; +} + + +static ngx_int_t +ngx_rtmp_limit_connect(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_rtmp_limit_main_conf_t *lmcf; + ngx_slab_pool_t *shpool; + ngx_shm_zone_t *shm_zone; + uint32_t *nconn, n; + ngx_int_t rc; + + lmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_limit_module); + if (lmcf->max_conn == NGX_CONF_UNSET) { + return NGX_OK; + } + + shm_zone = lmcf->shm_zone; + shpool = (ngx_slab_pool_t *) shm_zone->shm.addr; + nconn = shm_zone->data; + + ngx_shmtx_lock(&shpool->mutex); + n = ++*nconn; + ngx_shmtx_unlock(&shpool->mutex); + + rc = n > (ngx_uint_t) lmcf->max_conn ? NGX_ERROR : NGX_OK; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "limit: inc conection counter: %uD", n); + + if (rc != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "limit: too many connections: %uD > %i", + n, lmcf->max_conn); + } + + return rc; +} + + +static ngx_int_t +ngx_rtmp_limit_disconnect(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_rtmp_limit_main_conf_t *lmcf; + ngx_slab_pool_t *shpool; + ngx_shm_zone_t *shm_zone; + uint32_t *nconn, n; + + lmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_limit_module); + if (lmcf->max_conn == NGX_CONF_UNSET) { + return NGX_OK; + } + + shm_zone = lmcf->shm_zone; + shpool = (ngx_slab_pool_t *) shm_zone->shm.addr; + nconn = shm_zone->data; + + ngx_shmtx_lock(&shpool->mutex); + n = --*nconn; + ngx_shmtx_unlock(&shpool->mutex); + + (void) n; + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "limit: dec conection counter: %uD", n); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_limit_shm_init(ngx_shm_zone_t *shm_zone, void *data) +{ + ngx_slab_pool_t *shpool; + uint32_t *nconn; + + if (data) { + shm_zone->data = data; + return NGX_OK; + } + + shpool = (ngx_slab_pool_t *) shm_zone->shm.addr; + + nconn = ngx_slab_alloc(shpool, 4); + if (nconn == NULL) { + return NGX_ERROR; + } + + *nconn = 0; + + shm_zone->data = nconn; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_limit_postconfiguration(ngx_conf_t *cf) +{ + ngx_rtmp_core_main_conf_t *cmcf; + ngx_rtmp_limit_main_conf_t *lmcf; + ngx_rtmp_handler_pt *h; + + cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); + + h = ngx_array_push(&cmcf->events[NGX_RTMP_CONNECT]); + *h = ngx_rtmp_limit_connect; + + h = ngx_array_push(&cmcf->events[NGX_RTMP_DISCONNECT]); + *h = ngx_rtmp_limit_disconnect; + + lmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_limit_module); + if (lmcf->max_conn == NGX_CONF_UNSET) { + return NGX_OK; + } + + lmcf->shm_zone = ngx_shared_memory_add(cf, &shm_name, ngx_pagesize * 2, + &ngx_rtmp_limit_module); + if (lmcf->shm_zone == NULL) { + return NGX_ERROR; + } + + lmcf->shm_zone->init = ngx_rtmp_limit_shm_init; + + return NGX_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_live_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_live_module.c new file mode 100644 index 0000000000..502bc8e2d9 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_live_module.c @@ -0,0 +1,1108 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp_live_module.h" +#include "ngx_rtmp_cmd_module.h" +#include "ngx_rtmp_codec_module.h" + + +static ngx_rtmp_publish_pt next_publish; +static ngx_rtmp_play_pt next_play; +static ngx_rtmp_close_stream_pt next_close_stream; +static ngx_rtmp_pause_pt next_pause; +static ngx_rtmp_stream_begin_pt next_stream_begin; +static ngx_rtmp_stream_eof_pt next_stream_eof; + + +static ngx_int_t ngx_rtmp_live_postconfiguration(ngx_conf_t *cf); +static void * ngx_rtmp_live_create_app_conf(ngx_conf_t *cf); +static char * ngx_rtmp_live_merge_app_conf(ngx_conf_t *cf, + void *parent, void *child); +static char *ngx_rtmp_live_set_msec_slot(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static void ngx_rtmp_live_start(ngx_rtmp_session_t *s); +static void ngx_rtmp_live_stop(ngx_rtmp_session_t *s); + + +static ngx_command_t ngx_rtmp_live_commands[] = { + + { ngx_string("live"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_live_app_conf_t, live), + NULL }, + + { ngx_string("buffer"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_live_app_conf_t, buflen), + NULL }, + + { ngx_string("sync"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_rtmp_live_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_live_app_conf_t, sync), + NULL }, + + { ngx_string("interleave"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_live_app_conf_t, interleave), + NULL }, + + { ngx_string("wait_key"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_live_app_conf_t, wait_key), + NULL }, + + { ngx_string("wait_video"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_live_app_conf_t, wait_video), + NULL }, + + { ngx_string("publish_notify"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_live_app_conf_t, publish_notify), + NULL }, + + { ngx_string("play_restart"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_live_app_conf_t, play_restart), + NULL }, + + { ngx_string("idle_streams"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_live_app_conf_t, idle_streams), + NULL }, + + { ngx_string("drop_idle_publisher"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_rtmp_live_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_live_app_conf_t, idle_timeout), + NULL }, + + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_rtmp_live_module_ctx = { + NULL, /* preconfiguration */ + ngx_rtmp_live_postconfiguration, /* postconfiguration */ + NULL, /* create main configuration */ + NULL, /* init main configuration */ + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + ngx_rtmp_live_create_app_conf, /* create app configuration */ + ngx_rtmp_live_merge_app_conf /* merge app configuration */ +}; + + +ngx_module_t ngx_rtmp_live_module = { + NGX_MODULE_V1, + &ngx_rtmp_live_module_ctx, /* module context */ + ngx_rtmp_live_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void * +ngx_rtmp_live_create_app_conf(ngx_conf_t *cf) +{ + ngx_rtmp_live_app_conf_t *lacf; + + lacf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_live_app_conf_t)); + if (lacf == NULL) { + return NULL; + } + + lacf->live = NGX_CONF_UNSET; + lacf->buflen = NGX_CONF_UNSET_MSEC; + lacf->sync = NGX_CONF_UNSET_MSEC; + lacf->idle_timeout = NGX_CONF_UNSET_MSEC; + lacf->interleave = NGX_CONF_UNSET; + lacf->wait_key = NGX_CONF_UNSET; + lacf->wait_video = NGX_CONF_UNSET; + lacf->publish_notify = NGX_CONF_UNSET; + lacf->play_restart = NGX_CONF_UNSET; + lacf->idle_streams = NGX_CONF_UNSET; + + return lacf; +} + + +static char * +ngx_rtmp_live_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_rtmp_live_app_conf_t *prev = parent; + ngx_rtmp_live_app_conf_t *conf = child; + + ngx_conf_merge_value(conf->live, prev->live, 0); + ngx_conf_merge_msec_value(conf->buflen, prev->buflen, 0); + ngx_conf_merge_msec_value(conf->sync, prev->sync, 300); + ngx_conf_merge_msec_value(conf->idle_timeout, prev->idle_timeout, 0); + ngx_conf_merge_value(conf->interleave, prev->interleave, 0); + ngx_conf_merge_value(conf->wait_key, prev->wait_key, 1); + ngx_conf_merge_value(conf->wait_video, prev->wait_video, 0); + ngx_conf_merge_value(conf->publish_notify, prev->publish_notify, 0); + ngx_conf_merge_value(conf->play_restart, prev->play_restart, 0); + ngx_conf_merge_value(conf->idle_streams, prev->idle_streams, 1); + + return NGX_CONF_OK; +} + + +static char * +ngx_rtmp_live_set_msec_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + char *p = conf; + ngx_str_t *value; + ngx_msec_t *msp; + + msp = (ngx_msec_t *) (p + cmd->offset); + + value = cf->args->elts; + + if (value[1].len == sizeof("off") - 1 && + ngx_strncasecmp(value[1].data, (u_char *) "off", value[1].len) == 0) + { + *msp = 0; + return NGX_CONF_OK; + } + + return ngx_conf_set_msec_slot(cf, cmd, conf); +} + + +static void +ngx_rtmp_live_idle(ngx_event_t *pev) +{ + ngx_connection_t *c; + ngx_rtmp_session_t *s; + + c = pev->data; + s = c->data; + + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "live: drop idle publisher"); + + s->finalize_reason = NGX_LIVE_DROP_IDLE; + ngx_rtmp_finalize_session(s); +} + + +static void +ngx_rtmp_live_set_status(ngx_rtmp_session_t *s, ngx_rtmp_frame_t *control, + ngx_rtmp_frame_t **status, size_t nstatus, + unsigned active) +{ + ngx_rtmp_live_app_conf_t *lacf; + ngx_rtmp_live_ctx_t *ctx, *pctx; + ngx_rtmp_frame_t **frame; + ngx_event_t *e; + size_t n; + + lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module); + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "live: set active=%ui", active); + + if (ctx->active == active) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "live: unchanged active=%ui", active); + return; + } + + ctx->active = active; + + if (ctx->publishing) { + + /* publisher */ + + if (lacf->idle_timeout) { + e = &ctx->idle_evt; + + if (active && !ctx->idle_evt.timer_set) { + e->data = s->connection; + e->log = s->log; + e->handler = ngx_rtmp_live_idle; + + ngx_add_timer(e, lacf->idle_timeout); + + } else if (!active && ctx->idle_evt.timer_set) { + ngx_del_timer(e); + } + } + + ctx->stream->active = active; + + for (pctx = ctx->stream->ctx; pctx; pctx = pctx->next) { + if (pctx->publishing == 0) { + ngx_rtmp_live_set_status(pctx->session, control, status, + nstatus, active); + } + } + + return; + } + + /* subscriber */ + + if (control && ngx_rtmp_send_message(s, control, 0) != NGX_OK) { + ngx_rtmp_finalize_session(s); + return; + } + + if (!ctx->silent) { + frame = status; + + for (n = 0; n < nstatus; ++n, ++frame) { + if (*frame && ngx_rtmp_send_message(s, *frame, 0) != NGX_OK) { + ngx_rtmp_finalize_session(s); + return; + } + } + } + + ctx->cs[0].active = 0; + ctx->cs[0].dropped = 0; + + ctx->cs[1].active = 0; + ctx->cs[1].dropped = 0; +} + + +static void +ngx_rtmp_live_start(ngx_rtmp_session_t *s) +{ + ngx_rtmp_live_app_conf_t *lacf; + ngx_rtmp_frame_t *control; + ngx_rtmp_frame_t *status[3]; + size_t n, nstatus; + + if (s->live_type == NGX_HLS_LIVE) { + return; + } + + lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module); + + control = ngx_rtmp_create_stream_begin(s, NGX_RTMP_MSID); + + nstatus = 0; + + if (lacf->play_restart) { + status[nstatus++] = ngx_rtmp_create_status(s, "NetStream.Play.Start", + "status", "Start live"); + status[nstatus++] = ngx_rtmp_create_sample_access(s); + } + + if (lacf->publish_notify) { + status[nstatus++] = ngx_rtmp_create_status(s, + "NetStream.Play.PublishNotify", + "status", "Start publishing"); + } + + ngx_rtmp_live_set_status(s, control, status, nstatus, 1); + + if (control) { + ngx_rtmp_shared_free_frame(control); + } + + for (n = 0; n < nstatus; ++n) { + ngx_rtmp_shared_free_frame(status[n]); + } +} + + +static void +ngx_rtmp_live_stop(ngx_rtmp_session_t *s) +{ + ngx_rtmp_live_app_conf_t *lacf; + ngx_rtmp_frame_t *control; + ngx_rtmp_frame_t *status[3]; + size_t n, nstatus; + + lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module); + + control = ngx_rtmp_create_stream_eof(s, NGX_RTMP_MSID); + + nstatus = 0; + + if (lacf->play_restart) { + status[nstatus++] = ngx_rtmp_create_status(s, "NetStream.Play.Stop", + "status", "Stop live"); + } + + if (lacf->publish_notify) { + status[nstatus++] = ngx_rtmp_create_status(s, + "NetStream.Play.UnpublishNotify", + "status", "Stop publishing"); + } + + ngx_rtmp_live_set_status(s, control, status, nstatus, 0); + + if (control) { + ngx_rtmp_shared_free_frame(control); + } + + for (n = 0; n < nstatus; ++n) { + ngx_rtmp_shared_free_frame(status[n]); + } +} + + +static ngx_int_t +ngx_rtmp_live_stream_begin(ngx_rtmp_session_t *s, ngx_rtmp_stream_begin_t *v) +{ + ngx_rtmp_live_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module); + + if (ctx == NULL || ctx->stream == NULL || !ctx->publishing) { + goto next; + } + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0, + "live: stream_begin"); + + ngx_rtmp_live_start(s); + +next: + return next_stream_begin(s, v); +} + + +static ngx_int_t +ngx_rtmp_live_stream_eof(ngx_rtmp_session_t *s, ngx_rtmp_stream_eof_t *v) +{ + ngx_rtmp_live_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module); + + if (ctx == NULL || ctx->stream == NULL || !ctx->publishing) { + goto next; + } + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0, + "live: stream_eof"); + + ngx_rtmp_live_stop(s); + +next: + return next_stream_eof(s, v); +} + + +static void +ngx_rtmp_live_join(ngx_rtmp_session_t *s, u_char *name, unsigned publisher) +{ + ngx_rtmp_live_ctx_t *ctx; + ngx_live_stream_t *st; + ngx_rtmp_live_app_conf_t *lacf; + ngx_str_t pubpri; + + lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module); + if (lacf == NULL) { + return; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module); + if (ctx && ctx->stream) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0, + "live: already joined"); + return; + } + + if (ctx == NULL) { + ctx = ngx_palloc(s->pool, sizeof(ngx_rtmp_live_ctx_t)); + ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_live_module); + } + + ngx_memzero(ctx, sizeof(*ctx)); + + ctx->session = s; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "live: join '%s'", name); + + st = s->live_stream; + + if (!(publisher || st->publish_ctx || lacf->idle_streams)) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "live: stream not found"); + + s->status = 404; + ngx_rtmp_send_status(s, "NetStream.Play.StreamNotFound", "error", + "No such stream"); + + ngx_rtmp_finalize_session(s); + + return; + } + + if (publisher) { + // set publish priority + if (s->relay) { // relay pull + ctx->pubpri = -1; + } else { + if (ngx_rtmp_arg(s, (u_char *) "pubpri", 6, &pubpri) == NGX_OK) { + ctx->pubpri = ngx_atoi(pubpri.data, pubpri.len); + if (ctx->pubpri == NGX_ERROR) { // invalid publish priority + ctx->pubpri = 0; + } + } + } + + // duplicate publish priority + if (ngx_map_find(&st->pubctx, (intptr_t) ctx->pubpri)) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "live: already publishing"); + + ngx_rtmp_send_status(s, "NetStream.Publish.BadName", "error", + "Already publishing"); + + return; + } + + ctx->node.raw_key = (intptr_t) ctx->pubpri; + ngx_map_insert(&st->pubctx, &ctx->node, 0); + + st->publishing = 1; + } + + ctx->stream = st; + ctx->publishing = publisher; + ctx->next = st->ctx; + + st->ctx = ctx; + + if (lacf->buflen) { + s->out_buffer = 1; + } + + ctx->cs[0].csid = NGX_RTMP_CSID_VIDEO; + ctx->cs[1].csid = NGX_RTMP_CSID_AUDIO; + + if (!ctx->publishing && ctx->stream->active) { + ngx_rtmp_live_start(s); + } +} + + +static ngx_int_t +ngx_rtmp_live_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v) +{ + ngx_rtmp_session_t *ss; + ngx_rtmp_live_ctx_t *ctx, **cctx, *pctx; + ngx_rtmp_live_app_conf_t *lacf; + + lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module); + if (lacf == NULL) { + goto next; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module); + if (ctx == NULL) { + goto next; + } + + if (ctx->stream == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0, + "live: not joined"); + goto next; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "live: leave '%s'", ctx->stream->name); + + if (s->publishing) { + ngx_map_delete(&s->live_stream->pubctx, (intptr_t) ctx->pubpri); + } + + if (ctx->stream->publishing && ctx->publishing) { + ctx->stream->publishing = 0; + } + + for (cctx = &ctx->stream->ctx; *cctx; cctx = &(*cctx)->next) { + if (*cctx == ctx) { + *cctx = ctx->next; + break; + } + } + + if (ctx->publishing || ctx->stream->active) { + ngx_rtmp_live_stop(s); + } + + if (ctx->publishing) { + ngx_rtmp_send_status(s, "NetStream.Unpublish.Success", + "status", "Stop publishing"); + if (!lacf->idle_streams) { + for (pctx = ctx->stream->ctx; pctx; pctx = pctx->next) { + if (pctx->publishing == 0) { + ss = pctx->session; + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, ss->log, 0, + "live: no publisher"); + ngx_rtmp_finalize_session(ss); + } + } + } + } + + if (ctx->stream->ctx) { + ctx->stream = NULL; + goto next; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "live: delete empty stream '%s'", + ctx->stream->name); + + ctx->stream = NULL; + + if (!ctx->silent && !ctx->publishing && !lacf->play_restart) { + ngx_rtmp_send_status(s, "NetStream.Play.Stop", "status", "Stop live"); + } + +next: + return next_close_stream(s, v); +} + + +static ngx_int_t +ngx_rtmp_live_pause(ngx_rtmp_session_t *s, ngx_rtmp_pause_t *v) +{ + ngx_rtmp_live_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module); + + if (ctx == NULL || ctx->stream == NULL) { + goto next; + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0, + "live: pause=%i timestamp=%f", + (ngx_int_t) v->pause, v->position); + + if (v->pause) { + if (ngx_rtmp_send_status(s, "NetStream.Pause.Notify", "status", + "Paused live") + != NGX_OK) + { + return NGX_ERROR; + } + + ctx->paused = 1; + + ngx_rtmp_live_stop(s); + + } else { + if (ngx_rtmp_send_status(s, "NetStream.Unpause.Notify", "status", + "Unpaused live") + != NGX_OK) + { + return NGX_ERROR; + } + + ctx->paused = 0; + + ngx_rtmp_live_start(s); + } + +next: + return next_pause(s, v); +} + +static ngx_int_t +ngx_rtmp_live_av(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_rtmp_live_ctx_t *ctx, *pctx; + ngx_rtmp_codec_ctx_t *codec_ctx; + ngx_rtmp_frame_t *header, *coheader, *meta, *avframe, *dummy; + ngx_rtmp_core_srv_conf_t *cscf; + ngx_rtmp_live_app_conf_t *lacf; + ngx_rtmp_session_t *ss; + ngx_rtmp_header_t ch, lh, clh; + ngx_int_t rc, mandatory, dummy_audio; + ngx_uint_t prio; + ngx_uint_t peers; + ngx_uint_t meta_version; + ngx_uint_t csidx; + uint32_t delta; + ngx_rtmp_live_chunk_stream_t *cs; + u_char frametype; + +#define MSG_TYPE (h->type == NGX_RTMP_MSG_VIDEO ? "video" : "audio") + + lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module); + if (lacf == NULL) { + return NGX_ERROR; + } + + if (!lacf->live || in == NULL || in->buf == NULL || s->pause == 1) { + return NGX_OK; + } + + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + if (s->pause == 2) { + if ((codec_ctx && codec_ctx->video_codec_id == 0) || + (h->type == NGX_RTMP_MSG_VIDEO && + ngx_rtmp_get_video_frame_type(in) == NGX_RTMP_VIDEO_KEY_FRAME)) + { + s->pause = 0; + } else { + return NGX_OK; + } + } + + if (h->type == NGX_RTMP_MSG_VIDEO) { + frametype = in->buf->pos[0] & 0xf0; + if (frametype != 0x10 && frametype != 0x20) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "codec: receive unkwnon frametype %02xD", frametype); + return NGX_OK; + } + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module); + if (ctx == NULL || ctx->stream == NULL) { + return NGX_OK; + } + + if (ctx->publishing == 0) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "live: %s from non-publisher", MSG_TYPE); + return NGX_OK; + } + + if (!ctx->stream->active) { + ngx_rtmp_live_start(s); + } + + if (ctx->idle_evt.timer_set) { + ngx_add_timer(&ctx->idle_evt, lacf->idle_timeout); + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0, + "live: %s packet timestamp=%uD", + MSG_TYPE, h->timestamp); + + s->current_time = h->timestamp; + + peers = 0; + header = NULL; + coheader = NULL; + meta = NULL; + dummy = NULL; + meta_version = 0; + mandatory = 0; + + prio = (h->type == NGX_RTMP_MSG_VIDEO ? + ngx_rtmp_get_video_frame_type(in) : 0); + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + csidx = !(lacf->interleave || h->type == NGX_RTMP_MSG_VIDEO); + + cs = &ctx->cs[csidx]; + + ngx_memzero(&ch, sizeof(ch)); + + ch.timestamp = h->timestamp; + ch.msid = NGX_RTMP_MSID; + ch.csid = cs->csid; + ch.type = h->type; + ch.mlen = h->mlen; + + lh = ch; + + if (cs->active) { + lh.timestamp = cs->timestamp; + } + + clh = lh; + clh.type = (h->type == NGX_RTMP_MSG_AUDIO ? NGX_RTMP_MSG_VIDEO : + NGX_RTMP_MSG_AUDIO); + + cs->active = 1; + cs->timestamp = ch.timestamp; + + delta = ch.timestamp - lh.timestamp; +/* + if (delta >> 31) { + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0, + "live: clipping non-monotonical timestamp %uD->%uD", + lh.timestamp, ch.timestamp); + + delta = 0; + + ch.timestamp = lh.timestamp; + } +*/ + avframe = ngx_rtmp_shared_alloc_frame(cscf->chunk_size, in, 0); + avframe->hdr = ch; + + if (codec_ctx) { + + if (h->type == NGX_RTMP_MSG_AUDIO) { + header = codec_ctx->aac_header; + + if (lacf->interleave) { + coheader = codec_ctx->avc_header; + } + + if (codec_ctx->audio_codec_id == NGX_RTMP_AUDIO_AAC && + ngx_rtmp_is_codec_header(in)) + { + prio = 0; + mandatory = 1; + } + + } else { + header = codec_ctx->avc_header; + + if (lacf->interleave) { + coheader = codec_ctx->aac_header; + } + + if (codec_ctx->video_codec_id == NGX_RTMP_VIDEO_H264 && + ngx_rtmp_is_codec_header(in)) + { + prio = 0; + mandatory = 1; + } + } + + if (codec_ctx->meta) { + meta = codec_ctx->meta; + meta_version = codec_ctx->meta_version; + } + } + + if (ngx_rtmp_gop_cache(s, avframe) == NGX_ERROR) { + return NGX_ERROR; + } + + /* broadcast to all subscribers */ + + for (pctx = ctx->stream->ctx; pctx; pctx = pctx->next) { + // not the highest priority + if (ngx_map_rbegin(&ctx->stream->pubctx) != &ctx->node) { + break; + } + + // do not send to publish session + if (pctx->publishing || pctx->paused || s->pause) { + continue; + } + + ss = pctx->session; + cs = &pctx->cs[csidx]; + + if (ss->live_type == NGX_MPEGTS_LIVE || ss->live_type == NGX_HLS_LIVE) { + continue; + } + + /* send gop cache is set */ + switch (ngx_rtmp_gop_send(s, ss)) { + case NGX_DECLINED: + break; + case NGX_ERROR: + ngx_rtmp_finalize_session(ss); + continue; + default: + peers++; + continue; + } + + /* send metadata */ + + if (meta && meta_version != pctx->meta_version) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, ss->log, 0, + "live: meta"); + + if (ngx_rtmp_send_message(ss, meta, 0) == NGX_OK) { + pctx->meta_version = meta_version; + } + } + + /* sync stream */ + + if (cs->active && (lacf->sync && cs->dropped > lacf->sync)) { + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ss->log, 0, + "live: sync %s dropped=%uD", MSG_TYPE, cs->dropped); + + cs->active = 0; + cs->dropped = 0; + } + + /* absolute packet */ + + if (!cs->active) { + + if (mandatory) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, ss->log, 0, + "live: skipping header"); + continue; + } + + if (lacf->wait_video && h->type == NGX_RTMP_MSG_AUDIO && + !pctx->cs[0].active) + { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, ss->log, 0, + "live: waiting for video"); + continue; + } + + if (lacf->wait_key && prio != NGX_RTMP_VIDEO_KEY_FRAME && + (lacf->interleave || h->type == NGX_RTMP_MSG_VIDEO)) + { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, ss->log, 0, + "live: skip non-key"); + continue; + } + + dummy_audio = 0; + if (lacf->wait_video && h->type == NGX_RTMP_MSG_VIDEO && + !pctx->cs[1].active) + { + dummy_audio = 1; + if (dummy == NULL) { + dummy = ngx_rtmp_shared_alloc_frame(cscf->chunk_size, + NULL, 1); + dummy->hdr = clh; + } + } + + if (header || coheader) { + + /* send absolute codec header */ + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ss->log, 0, + "live: abs %s header timestamp=%uD", + MSG_TYPE, lh.timestamp); + + if (header) { + header->hdr = lh; + rc = ngx_rtmp_send_message(ss, header, 0); + if (rc != NGX_OK) { + continue; + } + } + + if (coheader) { + coheader->hdr = clh; + rc = ngx_rtmp_send_message(ss, coheader, 0); + if (rc != NGX_OK) { + continue; + } + + } else if (dummy_audio) { + ngx_rtmp_send_message(ss, dummy, 0); + } + + cs->timestamp = lh.timestamp; + cs->active = 1; + ss->current_time = cs->timestamp; + + } + } + + /* send av packet */ + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ss->log, 0, + "live: rel %s packet delta=%uD", + MSG_TYPE, delta); + + if (ngx_rtmp_send_message(ss, avframe, prio) != NGX_OK) { + ++pctx->ndropped; + + cs->dropped += delta; + + if (mandatory) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, ss->log, 0, + "live: mandatory packet failed"); + ngx_rtmp_finalize_session(ss); + } + + continue; + } + + cs->timestamp += delta; + ++peers; + ss->current_time = cs->timestamp; + } + + if (avframe) { + ngx_rtmp_shared_free_frame(avframe); + } + + if (dummy) { + ngx_rtmp_shared_free_frame(dummy); + } + + ngx_rtmp_update_bandwidth(&ctx->stream->bw_in, h->mlen); + ngx_rtmp_update_bandwidth(&ctx->stream->bw_out, h->mlen * peers); + + ngx_rtmp_update_bandwidth(h->type == NGX_RTMP_MSG_AUDIO ? + &ctx->stream->bw_in_audio : + &ctx->stream->bw_in_video, + h->mlen); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_live_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) +{ + ngx_rtmp_live_app_conf_t *lacf; + ngx_rtmp_live_ctx_t *ctx; + + lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module); + + if (lacf == NULL || !lacf->live) { + goto next; + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0, + "live: publish: name='%s' type='%s'", + v->name, v->type); + + /* join stream as publisher */ + + ngx_rtmp_live_join(s, v->name, 1); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module); + if (ctx == NULL || !ctx->publishing) { + goto next; + } + + ctx->silent = v->silent; + + if (!ctx->silent) { + ngx_rtmp_send_status(s, "NetStream.Publish.Start", + "status", "Start publishing"); + } + +next: + return next_publish(s, v); +} + + +static ngx_int_t +ngx_rtmp_live_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) +{ + ngx_rtmp_live_app_conf_t *lacf; + ngx_rtmp_live_ctx_t *ctx; + ngx_rtmp_session_t *ps; + + lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module); + + if (lacf == NULL || !lacf->live || + s->live_type == NGX_HLS_LIVE || s->live_type == NGX_MPEGTS_LIVE) + { + goto next; + } + + ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->log, 0, + "live: play: name='%s' start=%uD duration=%uD reset=%d", + v->name, (uint32_t) v->start, + (uint32_t) v->duration, (uint32_t) v->reset); + + /* join stream as subscriber */ + + ngx_rtmp_live_join(s, v->name, 0); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module); + if (ctx == NULL) { + goto next; + } + + ctx->silent = v->silent; + + if (!ctx->silent && !lacf->play_restart) { + ngx_rtmp_send_status(s, "NetStream.Play.Start", + "status", "Start live"); + ngx_rtmp_send_sample_access(s); + } + + if (s->live_stream->publish_ctx && s->live_stream->publish_ctx->session) { + ps = s->live_stream->publish_ctx->session; + ngx_rtmp_gop_send(ps, s); + } + +next: + return next_play(s, v); +} + + +static ngx_int_t +ngx_rtmp_live_postconfiguration(ngx_conf_t *cf) +{ + ngx_rtmp_core_main_conf_t *cmcf; + ngx_rtmp_handler_pt *h; + + cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); + + /* register raw event handlers */ + + h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AUDIO]); + *h = ngx_rtmp_live_av; + + h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_VIDEO]); + *h = ngx_rtmp_live_av; + + /* chain handlers */ + + next_publish = ngx_rtmp_publish; + ngx_rtmp_publish = ngx_rtmp_live_publish; + + next_play = ngx_rtmp_play; + ngx_rtmp_play = ngx_rtmp_live_play; + + next_close_stream = ngx_rtmp_close_stream; + ngx_rtmp_close_stream = ngx_rtmp_live_close_stream; + + next_pause = ngx_rtmp_pause; + ngx_rtmp_pause = ngx_rtmp_live_pause; + + next_stream_begin = ngx_rtmp_stream_begin; + ngx_rtmp_stream_begin = ngx_rtmp_live_stream_begin; + + next_stream_eof = ngx_rtmp_stream_eof; + ngx_rtmp_stream_eof = ngx_rtmp_live_stream_eof; + + return NGX_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_live_module.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_live_module.h new file mode 100644 index 0000000000..cd9896ae5a --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_live_module.h @@ -0,0 +1,66 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#ifndef _NGX_RTMP_LIVE_H_INCLUDED_ +#define _NGX_RTMP_LIVE_H_INCLUDED_ + + +#include +#include +#include "ngx_rtmp.h" +#include "ngx_rtmp_cmd_module.h" +#include "ngx_rtmp_bandwidth.h" +#include "ngx_rtmp_streams.h" +#include "ngx_map.h" + + +typedef struct { + unsigned active:1; + uint32_t timestamp; + uint32_t last_timestamp; + uint32_t csid; + uint32_t dropped; +} ngx_rtmp_live_chunk_stream_t; + + +struct ngx_rtmp_live_ctx_s { + ngx_map_node_t node; + ngx_int_t pubpri; + ngx_rtmp_session_t *session; + ngx_live_stream_t *stream; + ngx_rtmp_live_ctx_t *next; + ngx_uint_t ndropped; + ngx_rtmp_live_chunk_stream_t cs[2]; + ngx_uint_t meta_version; + ngx_event_t idle_evt; + unsigned active:1; + unsigned publishing:1; + unsigned silent:1; + unsigned paused:1; +}; + + +typedef struct { + ngx_flag_t live; + ngx_flag_t meta; + ngx_msec_t sync; + ngx_msec_t idle_timeout; + ngx_flag_t atc; + ngx_flag_t interleave; + ngx_flag_t wait_key; + ngx_flag_t wait_video; + ngx_flag_t publish_notify; + ngx_flag_t play_restart; + ngx_flag_t idle_streams; + ngx_flag_t fix_timestamp; + ngx_msec_t buflen; +} ngx_rtmp_live_app_conf_t; + + +extern ngx_module_t ngx_rtmp_live_module; + + +#endif /* _NGX_RTMP_LIVE_H_INCLUDED_ */ diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_log_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_log_module.c new file mode 100644 index 0000000000..80c978fc39 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_log_module.c @@ -0,0 +1,1350 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp_cmd_module.h" +#include "ngx_rtmp_codec_module.h" + + +static ngx_rtmp_publish_pt next_publish; +static ngx_rtmp_play_pt next_play; + + +static ngx_int_t ngx_rtmp_log_postconfiguration(ngx_conf_t *cf); +static void *ngx_rtmp_log_create_main_conf(ngx_conf_t *cf); +static void * ngx_rtmp_log_create_app_conf(ngx_conf_t *cf); +static char * ngx_rtmp_log_merge_app_conf(ngx_conf_t *cf, + void *parent, void *child); +static char * ngx_rtmp_log_set_log(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char * ngx_rtmp_log_set_format(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char * ngx_rtmp_log_compile_format(ngx_conf_t *cf, ngx_array_t *ops, + ngx_array_t *args, ngx_uint_t s); + + +typedef struct ngx_rtmp_log_op_s ngx_rtmp_log_op_t; + +#define MAX_ACCESS_LOG_LINE_LEN 4096 + +typedef size_t (*ngx_rtmp_log_op_getlen_pt)(ngx_rtmp_session_t *s, + ngx_rtmp_log_op_t *op); +typedef u_char * (*ngx_rtmp_log_op_getdata_pt)(ngx_rtmp_session_t *s, + u_char *buf, ngx_rtmp_log_op_t *log); + + +struct ngx_rtmp_log_op_s { + ngx_rtmp_log_op_getlen_pt getlen; + ngx_rtmp_log_op_getdata_pt getdata; + ngx_str_t value; + ngx_uint_t offset; +}; + + +typedef struct { + ngx_str_t name; + ngx_rtmp_log_op_getlen_pt getlen; + ngx_rtmp_log_op_getdata_pt getdata; + ngx_uint_t offset; +} ngx_rtmp_log_var_t; + + +typedef struct { + ngx_str_t name; + ngx_array_t *ops; /* ngx_rtmp_log_op_t */ +} ngx_rtmp_log_fmt_t; + + +typedef struct { + ngx_open_file_t *file; + time_t disk_full_time; + time_t error_log_time; + ngx_msec_t trunc_timer; + ngx_rtmp_log_fmt_t *format; +} ngx_rtmp_log_t; + + +typedef struct { + ngx_array_t *logs; /* ngx_rtmp_log_t */ + ngx_uint_t off; +} ngx_rtmp_log_app_conf_t; + + +typedef struct { + ngx_array_t formats; /* ngx_rtmp_log_fmt_t */ + ngx_uint_t combined_used; +} ngx_rtmp_log_main_conf_t; + + +typedef struct { + ngx_rtmp_session_t *session; + ngx_event_t event; + ngx_rtmp_log_t *log; +} ngx_rtmp_log_timer_ctx_t; + +typedef struct { + unsigned play:1; + unsigned publish:1; + ngx_uint_t last_bytes; + u_char name[NGX_RTMP_MAX_NAME]; + u_char stream[NGX_RTMP_MAX_NAME]; + u_char args[NGX_RTMP_MAX_ARGS]; + ngx_array_t timers; /* ngx_rtmp_log_timer_ctx_t */ +} ngx_rtmp_log_ctx_t; + + +static ngx_str_t ngx_rtmp_access_log = ngx_string(NGX_HTTP_LOG_PATH); + + +static ngx_command_t ngx_rtmp_log_commands[] = { + + { ngx_string("access_log"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE123, + ngx_rtmp_log_set_log, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("log_format"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_2MORE, + ngx_rtmp_log_set_format, + NGX_RTMP_MAIN_CONF_OFFSET, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_rtmp_log_module_ctx = { + NULL, /* preconfiguration */ + ngx_rtmp_log_postconfiguration, /* postconfiguration */ + ngx_rtmp_log_create_main_conf, /* create main configuration */ + NULL, /* init main configuration */ + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + ngx_rtmp_log_create_app_conf, /* create app configuration */ + ngx_rtmp_log_merge_app_conf /* merge app configuration */ +}; + + +ngx_module_t ngx_rtmp_log_module = { + NGX_MODULE_V1, + &ngx_rtmp_log_module_ctx, /* module context */ + ngx_rtmp_log_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_str_t ngx_rtmp_combined_fmt = + ngx_string("$remote_addr [$time_local] $command " + "\"$app\" \"$name\" \"$args\" - " + "$bytes_received $bytes_sent " + "\"$pageurl\" \"$flashver\" ($session_readable_time)"); + + +static size_t +ngx_rtmp_log_var_default_getlen(ngx_rtmp_session_t *s, ngx_rtmp_log_op_t *op) +{ + return op->value.len; +} + + +static u_char * +ngx_rtmp_log_var_default_getdata(ngx_rtmp_session_t *s, u_char *buf, + ngx_rtmp_log_op_t *op) +{ + return ngx_cpymem(buf, op->value.data, op->value.len); +} + +static size_t +ngx_rtmp_log_var_parg_getlen(ngx_rtmp_session_t *s, ngx_rtmp_log_op_t *op) +{ + ngx_str_t *name = &(op->value); + u_char *arg; + size_t len; + ngx_str_t value; + + len = name->len - (sizeof("parg_") - 1); + arg = name->data + sizeof("parg_") - 1; + + if (ngx_rtmp_arg(s, arg, len, &value) != NGX_OK) { + return op->value.len; + } + + return value.len; +} + + +static u_char * +ngx_rtmp_log_var_parg_getdata(ngx_rtmp_session_t *s, u_char *buf, + ngx_rtmp_log_op_t *op) +{ + ngx_str_t *name = &(op->value); + u_char *arg; + size_t len; + ngx_str_t value; + + len = name->len - (sizeof("parg_") - 1); + arg = name->data + sizeof("parg_") - 1; + + if (ngx_rtmp_arg(s, arg, len, &value) != NGX_OK) { + return ngx_cpymem(buf, op->value.data, op->value.len); + } + + return ngx_cpymem(buf, value.data, value.len); +} + +static size_t +ngx_rtmp_log_var_connection_getlen(ngx_rtmp_session_t *s, ngx_rtmp_log_op_t *op) +{ + return NGX_INT_T_LEN; +} + +static u_char * +ngx_rtmp_log_var_connection_getdata(ngx_rtmp_session_t *s, u_char *buf, + ngx_rtmp_log_op_t *op) +{ + return ngx_sprintf(buf, "%ui", (ngx_uint_t) s->number); +} + + +static size_t +ngx_rtmp_log_var_remote_addr_getlen(ngx_rtmp_session_t *s, + ngx_rtmp_log_op_t *op) +{ + return s->remote_addr_text.len; +} + + +static u_char * +ngx_rtmp_log_var_remote_addr_getdata(ngx_rtmp_session_t *s, u_char *buf, + ngx_rtmp_log_op_t *op) +{ + return ngx_cpymem(buf, s->remote_addr_text.data, + s->remote_addr_text.len); +} + + +static size_t +ngx_rtmp_log_var_msec_getlen(ngx_rtmp_session_t *s, + ngx_rtmp_log_op_t *op) +{ + return NGX_TIME_T_LEN + 4; +} + + +static u_char * +ngx_rtmp_log_var_msec_getdata(ngx_rtmp_session_t *s, u_char *buf, + ngx_rtmp_log_op_t *op) +{ + ngx_time_t *tp; + + tp = ngx_timeofday(); + + return ngx_sprintf(buf, "%T.%03M", tp->sec, tp->msec); +} + +static size_t +ngx_rtmp_log_var_sec_getlen(ngx_rtmp_session_t *s, + ngx_rtmp_log_op_t *op) +{ + return NGX_TIME_T_LEN + 4; +} + + +static u_char * +ngx_rtmp_log_var_sec_getdata(ngx_rtmp_session_t *s, u_char *buf, + ngx_rtmp_log_op_t *op) +{ + ngx_time_t *tp; + + tp = ngx_timeofday(); + + return ngx_sprintf(buf, "%T", tp->sec); +} + + +static size_t +ngx_rtmp_log_var_session_string_getlen(ngx_rtmp_session_t *s, + ngx_rtmp_log_op_t *op) +{ + return ((ngx_str_t *) ((u_char *) s + op->offset))->len; +} + + +static u_char * +ngx_rtmp_log_var_session_string_getdata(ngx_rtmp_session_t *s, u_char *buf, + ngx_rtmp_log_op_t *op) +{ + ngx_str_t *str; + + str = (ngx_str_t *) ((u_char *) s + op->offset); + + return ngx_cpymem(buf, str->data, str->len); +} + + +static size_t +ngx_rtmp_log_var_command_getlen(ngx_rtmp_session_t *s, + ngx_rtmp_log_op_t *op) +{ + return sizeof("PLAY+PUBLISH") - 1; +} + + +static u_char * +ngx_rtmp_log_var_command_getdata(ngx_rtmp_session_t *s, u_char *buf, + ngx_rtmp_log_op_t *op) +{ + ngx_rtmp_log_ctx_t *ctx; + ngx_str_t *cmd; + ngx_uint_t n; + + static ngx_str_t commands[] = { + ngx_string("NONE"), + ngx_string("PLAY"), + ngx_string("PUBLISH"), + ngx_string("PLAY+PUBLISH") + }; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_log_module); + + n = ctx ? (ctx->play + ctx->publish * 2) : 0; + + cmd = &commands[n]; + + return ngx_cpymem(buf, cmd->data, cmd->len); +} + + +static size_t +ngx_rtmp_log_var_context_cstring_getlen(ngx_rtmp_session_t *s, + ngx_rtmp_log_op_t *op) +{ + return ngx_max(NGX_RTMP_MAX_NAME, NGX_RTMP_MAX_ARGS); +} + + +static u_char * +ngx_rtmp_log_var_context_cstring_getdata(ngx_rtmp_session_t *s, u_char *buf, + ngx_rtmp_log_op_t *op) +{ + ngx_rtmp_log_ctx_t *ctx; + u_char *p; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_log_module); + if (ctx == NULL) { + return buf; + } + + p = (u_char *) ctx + op->offset; + while (*p) { + *buf++ = *p++; + } + + return buf; +} + + +static size_t +ngx_rtmp_log_var_session_uint32_getlen(ngx_rtmp_session_t *s, + ngx_rtmp_log_op_t *op) +{ + return NGX_INT32_LEN; +} + + +static u_char * +ngx_rtmp_log_var_session_uint32_getdata(ngx_rtmp_session_t *s, u_char *buf, + ngx_rtmp_log_op_t *op) +{ + uint32_t *v; + + v = (uint32_t *) ((uint8_t *) s + op->offset); + + return ngx_sprintf(buf, "%uD", *v); +} + +static u_char * +ngx_rtmp_log_var_session_video_flag(ngx_rtmp_session_t *s, u_char *buf, + ngx_rtmp_log_op_t *op) +{ + ngx_rtmp_codec_ctx_t *ctx; + ngx_int_t flag; + + flag = 0; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + if (ctx) { + flag = (ctx->video_codec_id > 0); + } + + return ngx_sprintf(buf, "%d", flag); +} + +static u_char * +ngx_rtmp_log_var_session_audio_flag(ngx_rtmp_session_t *s, u_char *buf, + ngx_rtmp_log_op_t *op) +{ + ngx_rtmp_codec_ctx_t *ctx; + ngx_int_t flag; + + flag = 0; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + if (ctx) { + flag = (ctx->audio_codec_id > 0); + } + + return ngx_sprintf(buf, "%d", flag); +} + +static u_char * +ngx_rtmp_log_var_session_bitrate_getdata(ngx_rtmp_session_t *s, u_char *buf, + ngx_rtmp_log_op_t *op) +{ + ngx_rtmp_log_ctx_t *ctx; + uint32_t bitrate; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_log_module); + if (!ctx) { + return buf; + } + + bitrate = 0; + if (s->publishing) { + bitrate = 8 * (s->in_bytes - ctx->last_bytes); + ctx->last_bytes = s->in_bytes; + } else { + bitrate = 8 * (s->out_bytes - ctx->last_bytes); + ctx->last_bytes = s->out_bytes; + } + + return ngx_sprintf(buf, "%D", bitrate); +} + +static size_t +ngx_rtmp_log_var_time_local_getlen(ngx_rtmp_session_t *s, + ngx_rtmp_log_op_t *op) +{ + return ngx_cached_http_log_time.len; +} + + +static u_char * +ngx_rtmp_log_var_time_local_getdata(ngx_rtmp_session_t *s, u_char *buf, + ngx_rtmp_log_op_t *op) +{ + return ngx_cpymem(buf, ngx_cached_http_log_time.data, + ngx_cached_http_log_time.len); +} + + +static size_t +ngx_rtmp_log_var_session_time_getlen(ngx_rtmp_session_t *s, + ngx_rtmp_log_op_t *op) +{ + return NGX_INT64_LEN; +} + + +static u_char * +ngx_rtmp_log_var_session_time_getdata(ngx_rtmp_session_t *s, u_char *buf, + ngx_rtmp_log_op_t *op) +{ + return ngx_sprintf(buf, "%L", + (int64_t) (ngx_current_msec - s->epoch) / 1000); +} + + +static size_t +ngx_rtmp_log_var_session_readable_time_getlen(ngx_rtmp_session_t *s, + ngx_rtmp_log_op_t *op) +{ + return NGX_INT_T_LEN + sizeof("d 23h 59m 59s") - 1; +} + + +static u_char * +ngx_rtmp_log_var_session_readable_time_getdata(ngx_rtmp_session_t *s, + u_char *buf, ngx_rtmp_log_op_t *op) +{ + int64_t v; + ngx_uint_t days, hours, minutes, seconds; + + v = (ngx_current_msec - s->epoch) / 1000; + + days = (ngx_uint_t) (v / (60 * 60 * 24)); + hours = (ngx_uint_t) (v / (60 * 60) % 24); + minutes = (ngx_uint_t) (v / 60 % 60); + seconds = (ngx_uint_t) (v % 60); + + if (days) { + buf = ngx_sprintf(buf, "%uid ", days); + } + + if (days || hours) { + buf = ngx_sprintf(buf, "%uih ", hours); + } + + if (days || hours || minutes) { + buf = ngx_sprintf(buf, "%uim ", minutes); + } + + buf = ngx_sprintf(buf, "%uis", seconds); + + return buf; +} + + +static ngx_rtmp_log_var_t ngx_rtmp_log_vars[] = { + { ngx_string("connection"), + ngx_rtmp_log_var_connection_getlen, + ngx_rtmp_log_var_connection_getdata, + 0 }, + + { ngx_string("remote_addr"), + ngx_rtmp_log_var_remote_addr_getlen, + ngx_rtmp_log_var_remote_addr_getdata, + 0 }, + + { ngx_string("app"), + ngx_rtmp_log_var_session_string_getlen, + ngx_rtmp_log_var_session_string_getdata, + offsetof(ngx_rtmp_session_t, app) }, + + { ngx_string("flashver"), + ngx_rtmp_log_var_session_string_getlen, + ngx_rtmp_log_var_session_string_getdata, + offsetof(ngx_rtmp_session_t, flashver) }, + + { ngx_string("swfurl"), + ngx_rtmp_log_var_session_string_getlen, + ngx_rtmp_log_var_session_string_getdata, + offsetof(ngx_rtmp_session_t, swf_url) }, + + { ngx_string("tcurl"), + ngx_rtmp_log_var_session_string_getlen, + ngx_rtmp_log_var_session_string_getdata, + offsetof(ngx_rtmp_session_t, tc_url) }, + + { ngx_string("domain"), + ngx_rtmp_log_var_session_string_getlen, + ngx_rtmp_log_var_session_string_getdata, + offsetof(ngx_rtmp_session_t, domain) }, + + { ngx_string("parg_"), + ngx_rtmp_log_var_parg_getlen, + ngx_rtmp_log_var_parg_getdata, + 0 }, + + { ngx_string("pageurl"), + ngx_rtmp_log_var_session_string_getlen, + ngx_rtmp_log_var_session_string_getdata, + offsetof(ngx_rtmp_session_t, page_url) }, + + { ngx_string("command"), + ngx_rtmp_log_var_command_getlen, + ngx_rtmp_log_var_command_getdata, + 0 }, + + { ngx_string("name"), + ngx_rtmp_log_var_context_cstring_getlen, + ngx_rtmp_log_var_context_cstring_getdata, + offsetof(ngx_rtmp_log_ctx_t, name) }, + + { ngx_string("stream"), + ngx_rtmp_log_var_context_cstring_getlen, + ngx_rtmp_log_var_context_cstring_getdata, + offsetof(ngx_rtmp_log_ctx_t, stream) }, + + { ngx_string("args"), + ngx_rtmp_log_var_context_cstring_getlen, + ngx_rtmp_log_var_context_cstring_getdata, + offsetof(ngx_rtmp_log_ctx_t, args) }, + + { ngx_string("bytes_sent"), + ngx_rtmp_log_var_session_uint32_getlen, + ngx_rtmp_log_var_session_uint32_getdata, + offsetof(ngx_rtmp_session_t, out_bytes) }, + + { ngx_string("bytes_received"), + ngx_rtmp_log_var_session_uint32_getlen, + ngx_rtmp_log_var_session_uint32_getdata, + offsetof(ngx_rtmp_session_t, in_bytes) }, + + { ngx_string("time_local"), + ngx_rtmp_log_var_time_local_getlen, + ngx_rtmp_log_var_time_local_getdata, + 0 }, + + { ngx_string("msec"), + ngx_rtmp_log_var_msec_getlen, + ngx_rtmp_log_var_msec_getdata, + 0 }, + + { ngx_string("ntp"), + ngx_rtmp_log_var_sec_getlen, + ngx_rtmp_log_var_sec_getdata, + 0 }, + + { ngx_string("session_time"), + ngx_rtmp_log_var_session_time_getlen, + ngx_rtmp_log_var_session_time_getdata, + 0 }, + + { ngx_string("session_readable_time"), + ngx_rtmp_log_var_session_readable_time_getlen, + ngx_rtmp_log_var_session_readable_time_getdata, + 0 }, + + { ngx_string("bitrate"), + ngx_rtmp_log_var_session_uint32_getlen, + ngx_rtmp_log_var_session_bitrate_getdata, + 0 }, + + { ngx_string("be_video"), + ngx_rtmp_log_var_session_uint32_getlen, + ngx_rtmp_log_var_session_video_flag, + 0 }, + + { ngx_string("be_audio"), + ngx_rtmp_log_var_session_uint32_getlen, + ngx_rtmp_log_var_session_audio_flag, + 0 }, + + { ngx_null_string, NULL, NULL, 0 } +}; + + +static void * +ngx_rtmp_log_create_main_conf(ngx_conf_t *cf) +{ + ngx_rtmp_log_main_conf_t *lmcf; + ngx_rtmp_log_fmt_t *fmt; + + lmcf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_log_main_conf_t)); + if (lmcf == NULL) { + return NULL; + } + + if (ngx_array_init(&lmcf->formats, cf->pool, 4, sizeof(ngx_rtmp_log_fmt_t)) + != NGX_OK) + { + return NULL; + } + + fmt = ngx_array_push(&lmcf->formats); + if (fmt == NULL) { + return NULL; + } + + ngx_str_set(&fmt->name, "combined"); + + fmt->ops = ngx_array_create(cf->pool, 16, sizeof(ngx_rtmp_log_op_t)); + if (fmt->ops == NULL) { + return NULL; + } + + return lmcf; + +} + + +static void * +ngx_rtmp_log_create_app_conf(ngx_conf_t *cf) +{ + ngx_rtmp_log_app_conf_t *lacf; + + lacf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_log_app_conf_t)); + if (lacf == NULL) { + return NULL; + } + + return lacf; +} + + +static char * +ngx_rtmp_log_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_rtmp_log_app_conf_t *prev = parent; + ngx_rtmp_log_app_conf_t *conf = child; + ngx_rtmp_log_main_conf_t *lmcf; + ngx_rtmp_log_fmt_t *fmt; + ngx_rtmp_log_t *log; + + if (conf->logs || conf->off) { + return NGX_OK; + } + + conf->logs = prev->logs; + conf->off = prev->off; + + if (conf->logs || conf->off) { + return NGX_OK; + } + + conf->logs = ngx_array_create(cf->pool, 2, sizeof(ngx_rtmp_log_t)); + if (conf->logs == NULL) { + return NGX_CONF_ERROR; + } + + log = ngx_array_push(conf->logs); + if (log == NULL) { + return NGX_CONF_ERROR; + } + + log->file = ngx_conf_open_file(cf->cycle, &ngx_rtmp_access_log); + if (log->file == NULL) { + return NGX_CONF_ERROR; + } + + log->disk_full_time = 0; + log->error_log_time = 0; + log->trunc_timer = 0; + + lmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_log_module); + fmt = lmcf->formats.elts; + + log->format = &fmt[0]; + lmcf->combined_used = 1; + + return NGX_CONF_OK; +} + + +/* + * access_log off; + * access_log file; + * access_log file format_name; + * access_log file trunc=1m; + * access_log file format_name trunc=1m; + */ +static char * +ngx_rtmp_log_set_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_rtmp_log_app_conf_t *lacf = conf; + + ngx_rtmp_log_main_conf_t *lmcf; + ngx_rtmp_log_fmt_t *fmt; + ngx_rtmp_log_t *log; + ngx_str_t *value, name, timer; + ngx_uint_t n; + ngx_flag_t format_configured; + + name.len = 0; + format_configured = 0; + + value = cf->args->elts; + + if (ngx_strcmp(value[1].data, "off") == 0) { + lacf->off = 1; + return NGX_CONF_OK; + } + + if (lacf->logs == NULL) { + lacf->logs = ngx_array_create(cf->pool, 2, sizeof(ngx_rtmp_log_t)); + if (lacf->logs == NULL) { + return NGX_CONF_ERROR; + } + } + + log = ngx_array_push(lacf->logs); + if (log == NULL) { + return NGX_CONF_ERROR; + } + + ngx_memzero(log, sizeof(*log)); + + lmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_log_module); + + log->file = ngx_conf_open_file(cf->cycle, &value[1]); + if (log->file == NULL) { + return NGX_CONF_ERROR; + } + + for (n = 2; n < cf->args->nelts; ++n) { + /* sizeof("trunc=") - 1 = 6 */ + if (ngx_memcmp("trunc=", value[n].data, 6) == 0) { + timer.data = value[n].data + 6; + timer.len = value[n].len - 6; + + log->trunc_timer = ngx_parse_time(&timer, 0); + if (log->trunc_timer == (ngx_msec_t) NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "unknown trunc timer format \"%V\"", &timer); + return NGX_CONF_ERROR; + } + } else { + if (format_configured) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "format name has been configured"); + return NGX_CONF_ERROR; + } + + format_configured = 1; + name = value[n]; + } + } + + if (name.len == 0) { + ngx_str_set(&name, "combined"); + lmcf->combined_used = 1; + + } else { + if (ngx_strcmp(name.data, "combined") == 0) { + lmcf->combined_used = 1; + } + } + + fmt = lmcf->formats.elts; + for (n = 0; n < lmcf->formats.nelts; ++n, ++fmt) { + if (fmt->name.len == name.len && + ngx_strncasecmp(fmt->name.data, name.data, name.len) == 0) + { + log->format = fmt; + break; + } + } + + if (log->format == NULL) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "unknown log format \"%V\"", + &name); + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + +static char * +ngx_rtmp_log_set_format(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_rtmp_log_main_conf_t *lmcf = conf; + ngx_rtmp_log_fmt_t *fmt; + ngx_str_t *value; + ngx_uint_t i; + + value = cf->args->elts; + + if (cf->cmd_type != NGX_RTMP_MAIN_CONF) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "\"log_format\" directive can only be used on " + "\"rtmp\" level"); + } + + fmt = lmcf->formats.elts; + for (i = 0; i < lmcf->formats.nelts; i++) { + if (fmt[i].name.len == value[1].len && + ngx_strcmp(fmt[i].name.data, value[1].data) == 0) + { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "duplicate \"log_format\" name \"%V\"", + &value[1]); + return NGX_CONF_ERROR; + } + } + + fmt = ngx_array_push(&lmcf->formats); + if (fmt == NULL) { + return NGX_CONF_ERROR; + } + + fmt->name = value[1]; + + fmt->ops = ngx_array_create(cf->pool, 16, sizeof(ngx_rtmp_log_op_t)); + if (fmt->ops == NULL) { + return NGX_CONF_ERROR; + } + + return ngx_rtmp_log_compile_format(cf, fmt->ops, cf->args, 2); +} + + +static char * +ngx_rtmp_log_compile_format(ngx_conf_t *cf, ngx_array_t *ops, ngx_array_t *args, + ngx_uint_t s) +{ + size_t i, len; + u_char *data, *d, c; + ngx_uint_t bracket; + ngx_str_t *value, var, pre_var; + ngx_rtmp_log_op_t *op; + ngx_rtmp_log_var_t *v; + u_char *p; + + value = args->elts; + + for (; s < args->nelts; ++s) { + i = 0; + + len = value[s].len; + d = value[s].data; + + while (i < len) { + + op = ngx_array_push(ops); + if (op == NULL) { + return NGX_CONF_ERROR; + } + + ngx_memzero(op, sizeof(*op)); + + data = &d[i]; + + if (d[i] == '$') { + if (++i == len) { + goto invalid; + } + + if (d[i] == '{') { + bracket = 1; + if (++i == len) { + goto invalid; + } + } else { + bracket = 0; + } + + var.data = &d[i]; + + for (var.len = 0; i < len; ++i, ++var.len) { + c = d[i]; + + if (c == '}' && bracket) { + ++i; + bracket = 0; + break; + } + + if ((c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || + (c == '_')) + { + continue; + } + + break; + } + + if (bracket) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "missing closing bracket in \"%V\"", + &var); + return NGX_CONF_ERROR; + } + + if (var.len == 0) { + goto invalid; + } + + for (v = ngx_rtmp_log_vars; v->name.len; ++v) { + if (v->name.len == var.len && + ngx_strncmp(v->name.data, var.data, var.len) == 0) + { + op->getlen = v->getlen; + op->getdata = v->getdata; + op->offset = v->offset; + break; + } + } + + if (v->name.len == 0) { + p = ngx_strnstr(var.data, "_", var.len); + if (p == NULL) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "unknown variable \"%V\"", &var); + return NGX_CONF_ERROR; + } + + pre_var.data = var.data; + pre_var.len = p - var.data + 1; + for (v = ngx_rtmp_log_vars; v->name.len; ++v) { + if (v->name.len == pre_var.len && + !ngx_strncmp(v->name.data, pre_var.data, pre_var.len)) + { + op->getlen = v->getlen; + op->getdata = v->getdata; + op->offset = v->offset; + op->value = var; + break; + } + } + } + + if (v->name.len == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "unknown variable \"%V\"", &var); + return NGX_CONF_ERROR; + } + + continue; + } + + ++i; + + while (i < len && d[i] != '$') { + ++i; + } + + op->getlen = ngx_rtmp_log_var_default_getlen; + op->getdata = ngx_rtmp_log_var_default_getdata; + + op->value.len = &d[i] - data; + + op->value.data = ngx_pnalloc(cf->pool, op->value.len); + if (op->value.data == NULL) { + return NGX_CONF_ERROR; + } + + ngx_memcpy(op->value.data, data, op->value.len); + } + } + + return NGX_CONF_OK; + +invalid: + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%s\"", data); + + return NGX_CONF_ERROR; +} + + +static ngx_rtmp_log_ctx_t * +ngx_rtmp_log_set_names(ngx_rtmp_session_t *s, u_char *name, u_char *args) +{ + ngx_rtmp_log_ctx_t *ctx; + ngx_rtmp_log_app_conf_t *lacf; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_log_module); + if (ctx == NULL) { + ctx = ngx_pcalloc(s->pool, sizeof(ngx_rtmp_log_ctx_t)); + if (ctx == NULL) { + return NULL; + } + + lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_log_module); + + if (lacf->logs) { + if (ngx_array_init(&ctx->timers, s->pool, + lacf->logs->nelts, sizeof(ngx_rtmp_log_timer_ctx_t)) != NGX_OK) + { + return NULL; + } + } + + ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_log_module); + } + + ngx_memcpy(ctx->stream, s->stream.data, s->stream.len); + ngx_memcpy(ctx->name, name, NGX_RTMP_MAX_NAME); + ngx_memcpy(ctx->args, args, NGX_RTMP_MAX_ARGS); + + return ctx; +} + + +static void +ngx_rtmp_log_write(ngx_rtmp_session_t *s, ngx_rtmp_log_t *log, u_char *buf, + size_t len) +{ + u_char *name; + time_t now; + ssize_t n; + int err; + + err = 0; + name = log->file->name.data; + n = ngx_write_fd(log->file->fd, buf, len); + + if (n == (ssize_t) len) { + return; + } + + now = ngx_time(); + + if (n == -1) { + err = ngx_errno; + + if (err == NGX_ENOSPC) { + log->disk_full_time = now; + } + + if (now - log->error_log_time > 59) { + ngx_log_error(NGX_LOG_ALERT, s->log, err, + ngx_write_fd_n " to \"%s\" failed", name); + log->error_log_time = now; + } + } + + if (now - log->error_log_time > 59) { + ngx_log_error(NGX_LOG_ALERT, s->log, err, + ngx_write_fd_n " to \"%s\" was incomplete: %z of %uz", + name, n, len); + log->error_log_time = now; + } +} + +static void +ngx_rtmp_log_pre_write(ngx_rtmp_session_t *s, ngx_rtmp_log_t *log) +{ + ngx_rtmp_log_op_t *op; + u_char line[MAX_ACCESS_LOG_LINE_LEN], *p; + size_t len; + ngx_uint_t n; + + if (ngx_time() == log->disk_full_time) { + /* FreeBSD full disk protection; + * nginx http logger does the same */ + return; + } + + len = 0; + op = log->format->ops->elts; + for (n = 0; n < log->format->ops->nelts; ++n, ++op) { + len += op->getlen(s, op); + } + len += NGX_LINEFEED_SIZE; + + if (len > MAX_ACCESS_LOG_LINE_LEN) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "Access line len %z greater than %d", + len, MAX_ACCESS_LOG_LINE_LEN); + ngx_rtmp_finalize_session(s); + + return; + } + + p = line; + op = log->format->ops->elts; + for (n = 0; n < log->format->ops->nelts; ++n, ++op) { + p = op->getdata(s, p, op); + } + + ngx_linefeed(p); + + ngx_rtmp_log_write(s, log, line, p - line); +} + +static void +ngx_rtmp_log_trunc_timer(ngx_event_t *ev) +{ + ngx_rtmp_log_timer_ctx_t *ltctx; + ngx_rtmp_session_t *s; + ngx_rtmp_log_t *log; + ngx_msec_t t; + + ltctx = ev->data; + + s = ltctx->session; + log = ltctx->log; + + ngx_rtmp_log_pre_write(s, log); + + t = log->trunc_timer - ngx_current_msec % log->trunc_timer; + ngx_add_timer(ev, t); +} + +static void +ngx_rtmp_log_add_trunc_timer(ngx_rtmp_session_t *s, ngx_rtmp_log_ctx_t *ctx, + ngx_rtmp_log_t *log) +{ + ngx_rtmp_log_timer_ctx_t *ltctx; + ngx_event_t *e; + ngx_msec_t t; + + ltctx = ngx_array_push(&ctx->timers); + ngx_memzero(ltctx, sizeof(ngx_rtmp_log_timer_ctx_t)); + ltctx->session = s; + ltctx->log = log; + e = <ctx->event; + + e->data = ltctx; + e->log = s->log; + e->handler = ngx_rtmp_log_trunc_timer; + + t = log->trunc_timer - ngx_current_msec % log->trunc_timer; + ngx_add_timer(e, t); +} + +static ngx_int_t +ngx_rtmp_log_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) +{ + ngx_rtmp_log_app_conf_t *lacf; + ngx_rtmp_log_t *log; + ngx_rtmp_log_ctx_t *ctx; + ngx_uint_t i; + + if (s->interprocess || s->relay) { + goto next; + } + + ctx = ngx_rtmp_log_set_names(s, v->name, v->args); + if (ctx == NULL) { + goto next; + } + + if (ctx->publish) { /* avoid multi push */ + goto next; + } + + ctx->publish = 1; + + lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_log_module); + + if (lacf->logs == NULL) { + goto next; + } + + log = lacf->logs->elts; + for (i = 0; i < lacf->logs->nelts; ++i, ++log) { + if (log->trunc_timer != 0) { + ngx_rtmp_log_add_trunc_timer(s, ctx, log); + } + } + +next: + return next_publish(s, v); +} + + +static ngx_int_t +ngx_rtmp_log_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) +{ + ngx_rtmp_log_app_conf_t *lacf; + ngx_rtmp_log_t *log; + ngx_rtmp_log_ctx_t *ctx; + ngx_uint_t i; + + if (s->interprocess || s->relay) { + goto next; + } + + ctx = ngx_rtmp_log_set_names(s, v->name, v->args); + if (ctx == NULL) { + goto next; + } + + if (ctx->play) { /* avoid mulit pull */ + goto next; + } + + ctx->play = 1; + + lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_log_module); + + if (lacf->logs == NULL) { + goto next; + } + + log = lacf->logs->elts; + for (i = 0; i < lacf->logs->nelts; ++i, ++log) { + if (log->trunc_timer != 0) { + ngx_rtmp_log_add_trunc_timer(s, ctx, log); + } + } + +next: + return next_play(s, v); +} + + +static ngx_int_t +ngx_rtmp_log_disconnect(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_rtmp_log_app_conf_t *lacf; + ngx_rtmp_log_ctx_t *ctx; + ngx_rtmp_log_timer_ctx_t *ltctx; + ngx_rtmp_log_t *log; + ngx_uint_t i; + + if (s->interprocess || s->relay) { + return NGX_OK; + } + + lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_log_module); + if (lacf == NULL || lacf->off || lacf->logs == NULL) { + return NGX_OK; + } + + log = lacf->logs->elts; + for (i = 0; i < lacf->logs->nelts; ++i, ++log) { + ngx_rtmp_log_pre_write(s, log); + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_log_module); + + if (ctx == NULL) { + return NGX_OK; + } + + ltctx = ctx->timers.elts; + for (i = 0; i < ctx->timers.nelts; ++i, ++ltctx) { + ngx_del_timer(<ctx->event); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_log_postconfiguration(ngx_conf_t *cf) +{ + ngx_rtmp_core_main_conf_t *cmcf; + ngx_rtmp_handler_pt *h; + ngx_rtmp_log_main_conf_t *lmcf; + ngx_array_t a; + ngx_rtmp_log_fmt_t *fmt; + ngx_str_t *value; + + lmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_log_module); + if (lmcf->combined_used) { + if (ngx_array_init(&a, cf->pool, 1, sizeof(ngx_str_t)) != NGX_OK) { + return NGX_ERROR; + } + + value = ngx_array_push(&a); + if (value == NULL) { + return NGX_ERROR; + } + + *value = ngx_rtmp_combined_fmt; + fmt = lmcf->formats.elts; + + if (ngx_rtmp_log_compile_format(cf, fmt->ops, &a, 0) + != NGX_CONF_OK) + { + return NGX_ERROR; + } + } + + cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); + + h = ngx_array_push(&cmcf->events[NGX_RTMP_DISCONNECT]); + *h = ngx_rtmp_log_disconnect; + + next_publish = ngx_rtmp_publish; + ngx_rtmp_publish = ngx_rtmp_log_publish; + + next_play = ngx_rtmp_play; + ngx_rtmp_play = ngx_rtmp_log_play; + + return NGX_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_monitor_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_monitor_module.c new file mode 100644 index 0000000000..0a8d29f576 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_monitor_module.c @@ -0,0 +1,458 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#include "ngx_rtmp_monitor_module.h" +#include "ngx_rtmp_cmd_module.h" +#include "ngx_rtmp_codec_module.h" + + +static ngx_rtmp_close_stream_pt next_close_stream; + + +static void *ngx_rtmp_monitor_create_app_conf(ngx_conf_t *cf); +static char *ngx_rtmp_monitor_merge_app_conf(ngx_conf_t *cf, + void *parent, void *child); +static ngx_int_t ngx_rtmp_monitor_postconfiguration(ngx_conf_t *cf); + +static char *ngx_rtmp_monitor_dump(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char *ngx_rtmp_monitor_buffered_log(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + + +#define NGX_RTMP_MONITOR_BUFFER_SIZE 61 + + +typedef struct { + ngx_str_t dump_path; + ngx_flag_t monitor; + ngx_log_t *buffered_log; +} ngx_rtmp_monitor_app_conf_t; + + +typedef struct { + ngx_event_t consume; + ngx_msec_t buffered; + ngx_uint_t nbuffered; + + double frame_rate; + double nframes; + + double buffers[NGX_RTMP_MONITOR_BUFFER_SIZE]; + ngx_uint_t fps[NGX_RTMP_MONITOR_BUFFER_SIZE]; + ngx_uint_t curr; + + ngx_flag_t dump; + + unsigned publishing:1; +} ngx_rtmp_monitor_ctx_t; + + +static ngx_command_t ngx_rtmp_monitor_commands[] = { + + { ngx_string("dump"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_rtmp_monitor_dump, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("buffered_log"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_monitor_buffered_log, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_rtmp_monitor_module_ctx = { + NULL, /* preconfiguration */ + ngx_rtmp_monitor_postconfiguration, /* postconfiguration */ + NULL, /* create main configuration */ + NULL, /* init main configuration */ + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + ngx_rtmp_monitor_create_app_conf, /* create app configuration */ + ngx_rtmp_monitor_merge_app_conf /* merge app configuration */ +}; + + +ngx_module_t ngx_rtmp_monitor_module = { + NGX_MODULE_V1, + &ngx_rtmp_monitor_module_ctx, /* module context */ + ngx_rtmp_monitor_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void +ngx_rtmp_monitor_dump_frame(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ +} + +static void +ngx_rtmp_monitor_consume(ngx_event_t *ev) +{ + ngx_rtmp_session_t *s, *ps; + ngx_rtmp_monitor_ctx_t *ctx; + ngx_rtmp_codec_ctx_t *cctx; + ngx_rtmp_monitor_app_conf_t *macf; + u_char peer[NGX_SOCKADDR_STRLEN]; + u_char local[NGX_SOCKADDR_STRLEN]; + struct sockaddr paddr, laddr; + socklen_t plen, llen; + + s = ev->data; + + macf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_monitor_module); + + if (macf->monitor == 0) { + return; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_monitor_module); + if (ctx == NULL) { + return; + } + + if (ctx->publishing) { + ps = s; + } else { + if (s->live_stream->publish_ctx) { + ps = s->live_stream->publish_ctx->session; + } else { + goto next; + } + } + + cctx = ngx_rtmp_get_module_ctx(ps, ngx_rtmp_codec_module); + if (cctx == NULL) { + goto next; + } + + if (cctx->frame_rate != 0) { + ctx->frame_rate = cctx->frame_rate; + } + +next: + if (ctx->frame_rate == 0) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "monitor, frame rate error, stream: %V, frame rate: %.2f", + &s->stream, ctx->frame_rate); + return; + } + + ctx->nframes -= ctx->frame_rate; + + if (ctx->nframes <= 0) { + ctx->nframes = 0; + if (ctx->buffered == 0) { + ++ctx->nbuffered; + } + ++ctx->buffered; + + ngx_memzero(local, sizeof(local)); + ngx_memzero(peer, sizeof(peer)); + plen = sizeof(paddr); + llen = sizeof(laddr); + + if (s->connection) { + getpeername(s->connection->fd, &paddr, &plen); + getsockname(s->connection->fd, &laddr, &llen); + + ngx_sock_ntop(&paddr, plen, peer, NGX_SOCKADDR_STRLEN, 1); + ngx_sock_ntop(&laddr, llen, local, NGX_SOCKADDR_STRLEN, 1); + + ngx_log_error(NGX_LOG_ERR, macf->buffered_log, 0, + "%p %s, peer: %s, local: %s, " + "stream: %V, buffered: %ui, time: %uis", + s, ctx->publishing ? "publisher" : "player", peer, local, + &s->stream, ctx->nbuffered, ctx->buffered); + + } + } else { + ctx->buffered = 0; + } + + ctx->buffers[ctx->curr] = ctx->nframes; + + ctx->curr = (ctx->curr + 1) % NGX_RTMP_MONITOR_BUFFER_SIZE; + + ctx->fps[ctx->curr] = 0; + + ngx_add_timer(&ctx->consume, 1000); +} + +static ngx_int_t +ngx_rtmp_monitor_av(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_rtmp_monitor_frame(s, h, in, ngx_rtmp_is_codec_header(in), 1); + +// if (h->type == NGX_RTMP_MSG_VIDEO) { +// ngx_log_error(NGX_LOG_INFO, s->log, 0, +// "!!!!!!video(%d): %02xD %02xD %02xD %02xD %02xD", +// h->mlen, in->buf->pos[0], in->buf->pos[1], in->buf->pos[2], +// in->buf->pos[3], in->buf->pos[4], in->buf->pos[5]); +// } +// +// if (h->type == NGX_RTMP_MSG_AUDIO) { +// ngx_log_error(NGX_LOG_INFO, s->log, 0, +// "!!!!!!audio(%d): %02xD %02xD %02xD %02xD %02xD", +// h->mlen, in->buf->pos[0], in->buf->pos[1], in->buf->pos[2], +// in->buf->pos[3], in->buf->pos[4], in->buf->pos[5]); +// } + + return NGX_OK; +} + +static ngx_int_t +ngx_rtmp_monitor_meta_data(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + if (s->first_metadata == 0) { + s->stage = NGX_LIVE_AV; + s->first_metadata = ngx_current_msec; + s->first_data = s->first_data == 0? ngx_current_msec: s->first_data; + } + + ngx_log_error(NGX_LOG_INFO, s->log, 0, "receive metadata"); + + return NGX_OK; +} + +static ngx_int_t +ngx_rtmp_monitor_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v) +{ + ngx_rtmp_monitor_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_monitor_module); + + if (ctx == NULL) { + goto next; + } + + if (ctx->consume.timer_set) { + ngx_del_timer(&ctx->consume); + } + + if (ctx->consume.posted) { + ngx_delete_posted_event(&ctx->consume); + } + +next: + return next_close_stream(s, v); +} + +void +ngx_rtmp_monitor_frame(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in, ngx_flag_t is_header, ngx_flag_t publishing) +{ + ngx_rtmp_monitor_app_conf_t *macf; + ngx_rtmp_monitor_ctx_t *ctx; + + if (s->first_metadata == 0 && (h->type == NGX_RTMP_MSG_AMF_META + || h->type == NGX_RTMP_MSG_AMF3_META)) + { + s->stage = NGX_LIVE_AV; + s->first_metadata = ngx_current_msec; + s->first_data = s->first_data == 0? ngx_current_msec: s->first_data; + } + + if (s->first_audio == 0 && h->type == NGX_RTMP_MSG_AUDIO) { + s->stage = NGX_LIVE_AV; + s->first_audio = ngx_current_msec; + s->first_data = s->first_data == 0? ngx_current_msec: s->first_data; + } + + if (s->first_video == 0 && h->type == NGX_RTMP_MSG_VIDEO) { + s->stage = NGX_LIVE_AV; + s->first_video = ngx_current_msec; + s->first_data = s->first_data == 0? ngx_current_msec: s->first_data; + } + + if (h->type == NGX_RTMP_MSG_AUDIO && is_header) { + if (s->publishing) { + ngx_log_error(NGX_LOG_INFO, s->log, 0, "receive audio header"); + } else { + ngx_log_error(NGX_LOG_INFO, s->log, 0, "send audio header"); + } + } + + if (h->type == NGX_RTMP_MSG_VIDEO && is_header) { + if (s->publishing) { + ngx_log_error(NGX_LOG_INFO, s->log, 0, "receive video header"); + } else { + ngx_log_error(NGX_LOG_INFO, s->log, 0, "send video header"); + } + } + + if (h->type != NGX_RTMP_MSG_VIDEO) { + return; + } + + macf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_monitor_module); + + if (macf->monitor == 0) { + return; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_monitor_module); + + if (ctx == NULL) { + ctx = ngx_pcalloc(s->pool, sizeof(ngx_rtmp_monitor_ctx_t)); + if (ctx == NULL) { + return; + } + ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_monitor_module); + + ctx->dump = macf->dump_path.len > 0; + ctx->publishing = publishing; + + ctx->consume.data = s; + ctx->consume.log = s->log; + ctx->consume.handler = ngx_rtmp_monitor_consume; + ngx_add_timer(&ctx->consume, 1000); + } + + if (publishing && ctx->dump) { + ngx_rtmp_monitor_dump_frame(s, h, in); + } + + if (is_header) { + return; + } + + ++ctx->nframes; + ++ctx->fps[ctx->curr]; +} + + +static void * +ngx_rtmp_monitor_create_app_conf(ngx_conf_t *cf) +{ + ngx_rtmp_monitor_app_conf_t *macf; + + macf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_monitor_app_conf_t)); + if (macf == NULL) { + return NULL; + } + + macf->monitor = NGX_CONF_UNSET; + + return macf; +} + +static char * +ngx_rtmp_monitor_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_rtmp_monitor_app_conf_t *prev = parent; + ngx_rtmp_monitor_app_conf_t *conf = child; + + if (conf->dump_path.data == NULL) { + conf->dump_path = prev->dump_path; + } + + ngx_conf_merge_value(conf->monitor, prev->monitor, 0); + + if (conf->buffered_log == NULL) { + if (prev->buffered_log) { + conf->buffered_log = prev->buffered_log; + } else { + conf->buffered_log = &cf->cycle->new_log; + } + } + + return NGX_CONF_OK; +} + +static char * +ngx_rtmp_monitor_dump(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_rtmp_monitor_app_conf_t *macf; + ngx_str_t *value; + + macf = conf; + + if (macf->dump_path.len) { + return "is duplicate"; + } + + value = cf->args->elts; + + macf->dump_path = value[1]; + + if (macf->dump_path.len > 0 && + macf->dump_path.data[macf->dump_path.len - 1] == '/') + { + --macf->dump_path.len; + } + + if (ngx_conf_full_name(cf->cycle, &macf->dump_path, 0) != NGX_OK) { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + +static char * +ngx_rtmp_monitor_buffered_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_rtmp_monitor_app_conf_t *macf; + + macf = conf; + + macf->monitor = 1; + + return ngx_log_set_log(cf, &macf->buffered_log); +} + +static ngx_int_t +ngx_rtmp_monitor_postconfiguration(ngx_conf_t *cf) +{ + ngx_rtmp_core_main_conf_t *cmcf; + ngx_rtmp_handler_pt *h; + ngx_rtmp_amf_handler_t *ch; + + cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); + + h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AUDIO]); + *h = ngx_rtmp_monitor_av; + + h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_VIDEO]); + *h = ngx_rtmp_monitor_av; + + /* register metadata handler */ + ch = ngx_array_push(&cmcf->amf); + if (ch == NULL) { + return NGX_ERROR; + } + ngx_str_set(&ch->name, "@setDataFrame"); + ch->handler = ngx_rtmp_monitor_meta_data; + + ch = ngx_array_push(&cmcf->amf); + if (ch == NULL) { + return NGX_ERROR; + } + ngx_str_set(&ch->name, "onMetaData"); + ch->handler = ngx_rtmp_monitor_meta_data; + + next_close_stream = ngx_rtmp_close_stream; + ngx_rtmp_close_stream = ngx_rtmp_monitor_close_stream; + + return NGX_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_monitor_module.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_monitor_module.h new file mode 100644 index 0000000000..1ba70cd6c3 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_monitor_module.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#ifndef _NGX_RTMP_MONITOR_H_INCLUDED_ +#define _NGX_RTMP_MONITOR_H_INCLUDED_ + + +#include +#include +#include +#include "ngx_rtmp.h" + + +void ngx_rtmp_monitor_frame(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in, ngx_flag_t is_header, ngx_flag_t publishing); + + +#endif diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_notify_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_notify_module.c new file mode 100644 index 0000000000..14df5ab4a7 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_notify_module.c @@ -0,0 +1,1964 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + * + * Open Capability Live Platform + */ + + +#include +#include +#include "ngx_rtmp.h" +#include "ngx_rtmp_cmd_module.h" +#include "ngx_live_record.h" +#include "ngx_live_relay.h" +#include "ngx_dynamic_resolver.h" +#include "ngx_toolkit_misc.h" +#include "ngx_netcall.h" +#include "ngx_rtmp_variables.h" + + +static ngx_live_record_start_pt next_record_start; +static ngx_live_record_update_pt next_record_update; +static ngx_live_record_done_pt next_record_done; + + +static ngx_rtmp_publish_pt next_publish; +static ngx_rtmp_play_pt next_play; +static ngx_rtmp_close_stream_pt next_close_stream; + + +static ngx_live_push_pt next_push; +static ngx_live_pull_pt next_pull; +static ngx_live_push_close_pt next_push_close; +static ngx_live_pull_close_pt next_pull_close; + + +static ngx_int_t ngx_rtmp_notify_init_process(ngx_cycle_t *cycle); + +static ngx_int_t ngx_rtmp_notify_postconfiguration(ngx_conf_t *cf); +static void *ngx_rtmp_notify_create_main_conf(ngx_conf_t *cf); +static char *ngx_rtmp_notify_init_main_conf(ngx_conf_t *cf, void *conf); +static void *ngx_rtmp_notify_create_srv_conf(ngx_conf_t *cf); +static char *ngx_rtmp_notify_merge_srv_conf(ngx_conf_t *cf, void *parent, + void *child); +static void *ngx_rtmp_notify_create_app_conf(ngx_conf_t *cf); +static char *ngx_rtmp_notify_merge_app_conf(ngx_conf_t *cf, void *parent, + void *child); + +static char *ngx_rtmp_notify_on_main_event(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char *ngx_rtmp_notify_on_srv_event(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char *ngx_rtmp_notify_on_app_event(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + +#define NGX_RTMP_OCLP_START 0x01 +#define NGX_RTMP_OCLP_UPDATE 0x02 +#define NGX_RTMP_OCLP_DONE 0x04 + + +enum { + NGX_RTMP_OCLP_PROC, /* only notify */ + NGX_RTMP_OCLP_MAIN_MAX +}; + +enum { + NGX_RTMP_OCLP_CONNECT, /* only notify */ + NGX_RTMP_OCLP_SRV_MAX +}; + +enum { + NGX_RTMP_OCLP_PLAY, /* only notify */ + NGX_RTMP_OCLP_PUBLISH, /* only notify */ + NGX_RTMP_OCLP_PULL, /* relay pull */ + NGX_RTMP_OCLP_PUSH, /* relay push */ + NGX_RTMP_OCLP_STREAM, /* only notify */ + NGX_RTMP_OCLP_META, /* for transcode */ + NGX_RTMP_OCLP_RECORD, /* for record */ + NGX_RTMP_OCLP_APP_MAX +}; + +static char *ngx_rtmp_notify_stage[] = { + "", + "start", + "update", + "", + "done", +}; + +static char *ngx_rtmp_notify_app_type[] = { + "play", + "publish", + "pull", + "push", + "stream", + "meta", + "record", +}; + +typedef struct { + ngx_uint_t status; + char *code; + char *level; + char *desc; +} ngx_rtmp_notify_relay_error_t; + +static ngx_rtmp_notify_relay_error_t ngx_rtmp_notify_relay_errors[] = { + { 404, "NetStream.Play.StreamNotFound", "error", "No such stream" }, + { 400, "NetStream.Publish.BadName", "error", "Already publishing" }, + { 0, NULL, NULL, NULL }, +}; + + +#define NGX_RTMP_OCLP_META_VIDEO 0 +#define NGX_RTMP_OCLP_META_AUDIO 1 +#define NGX_RTMP_OCLP_META_BOTH 2 + +static ngx_conf_enum_t ngx_rtmp_notify_meta_type[] = { + { ngx_string("video"), NGX_RTMP_OCLP_META_VIDEO }, + { ngx_string("audio"), NGX_RTMP_OCLP_META_AUDIO }, + { ngx_string("both"), NGX_RTMP_OCLP_META_BOTH }, + { ngx_null_string, 0 } +}; + +typedef struct { + ngx_str_t url; + ngx_str_t args; + ngx_str_t groupid; + ngx_uint_t stage; + ngx_msec_t timeout; + ngx_msec_t update; +} ngx_rtmp_notify_event_t; + +typedef struct { + ngx_rtmp_notify_event_t events[NGX_RTMP_OCLP_MAIN_MAX]; +} ngx_rtmp_notify_main_conf_t; + +typedef struct { + ngx_rtmp_notify_event_t events[NGX_RTMP_OCLP_SRV_MAX]; +} ngx_rtmp_notify_srv_conf_t; + +typedef struct { + ngx_flag_t ignore_invalid_notify; + ngx_flag_t meta_once; + ngx_uint_t meta_type; + ngx_array_t events[NGX_RTMP_OCLP_APP_MAX]; +} ngx_rtmp_notify_app_conf_t; + + +typedef struct { + ngx_netcall_ctx_t *nctx; + ngx_netcall_ctx_t *rctx; + + ngx_rtmp_notify_event_t *event; + ngx_uint_t type; + ngx_live_relay_t *relay; + + ngx_rtmp_publish_t publish_v; + ngx_rtmp_play_t play_v; +} ngx_rtmp_notify_ctx_t; + + +static ngx_command_t ngx_rtmp_notify_commands[] = { + + { ngx_string("ignore_invalid_notify"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_notify_app_conf_t, ignore_invalid_notify), + NULL }, + + { ngx_string("on_proc"), + NGX_RTMP_MAIN_CONF|NGX_CONF_1MORE, + ngx_rtmp_notify_on_main_event, + NGX_RTMP_MAIN_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("on_connect"), + NGX_RTMP_SRV_CONF|NGX_CONF_1MORE, + ngx_rtmp_notify_on_srv_event, + NGX_RTMP_SRV_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("on_play"), + NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_notify_on_app_event, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("on_publish"), + NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_notify_on_app_event, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("on_pull"), + NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_notify_on_app_event, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("on_push"), + NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_notify_on_app_event, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("on_stream"), + NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_notify_on_app_event, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("on_meta"), + NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_notify_on_app_event, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("on_record"), + NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_notify_on_app_event, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("on_meta_once"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_notify_app_conf_t, meta_once), + NULL }, + + { ngx_string("on_meta_type"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_conf_set_enum_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_notify_app_conf_t, meta_type), + &ngx_rtmp_notify_meta_type }, +//////////////////////////////////////////////////////////////////// + + { ngx_string("oclp_proc"), + NGX_RTMP_MAIN_CONF|NGX_CONF_1MORE, + ngx_rtmp_notify_on_main_event, + NGX_RTMP_MAIN_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("oclp_connect"), + NGX_RTMP_SRV_CONF|NGX_CONF_1MORE, + ngx_rtmp_notify_on_srv_event, + NGX_RTMP_SRV_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("oclp_play"), + NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_notify_on_app_event, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("oclp_publish"), + NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_notify_on_app_event, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("oclp_pull"), + NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_notify_on_app_event, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("oclp_push"), + NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_notify_on_app_event, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("oclp_stream"), + NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_notify_on_app_event, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("oclp_meta"), + NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_notify_on_app_event, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("oclp_record"), + NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_notify_on_app_event, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("oclp_meta_once"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_notify_app_conf_t, meta_once), + NULL }, + + { ngx_string("oclp_meta_type"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_conf_set_enum_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_notify_app_conf_t, meta_type), + &ngx_rtmp_notify_meta_type }, + + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_rtmp_notify_module_ctx = { + NULL, /* preconfiguration */ + ngx_rtmp_notify_postconfiguration, /* postconfiguration */ + ngx_rtmp_notify_create_main_conf, /* create main configuration */ + ngx_rtmp_notify_init_main_conf, /* init main configuration */ + ngx_rtmp_notify_create_srv_conf, /* create server configuration */ + ngx_rtmp_notify_merge_srv_conf, /* merge server configuration */ + ngx_rtmp_notify_create_app_conf, /* create app configuration */ + ngx_rtmp_notify_merge_app_conf /* merge app configuration */ +}; + + +ngx_module_t ngx_rtmp_notify_module = { + NGX_MODULE_V1, + &ngx_rtmp_notify_module_ctx, /* module context */ + ngx_rtmp_notify_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + ngx_rtmp_notify_init_process, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void * +ngx_rtmp_notify_create_main_conf(ngx_conf_t *cf) +{ + ngx_rtmp_notify_main_conf_t *omcf; + + omcf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_notify_main_conf_t)); + if (omcf == NULL) { + return NULL; + } + + return omcf; +} + +static char * +ngx_rtmp_notify_init_main_conf(ngx_conf_t *cf, void *conf) +{ + return NGX_CONF_OK; +} + +static void * +ngx_rtmp_notify_create_srv_conf(ngx_conf_t *cf) +{ + ngx_rtmp_notify_srv_conf_t *oscf; + + oscf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_notify_srv_conf_t)); + if (oscf == NULL) { + return NULL; + } + + return oscf; +} + +static char * +ngx_rtmp_notify_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) +{ + return NGX_CONF_OK; +} + +static void * +ngx_rtmp_notify_create_app_conf(ngx_conf_t *cf) +{ + ngx_rtmp_notify_app_conf_t *oacf; + ngx_uint_t n; + + oacf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_notify_app_conf_t)); + if (oacf == NULL) { + return NULL; + } + + for (n = 0; n < NGX_RTMP_OCLP_APP_MAX; ++n) { + if (ngx_array_init(&oacf->events[n], cf->pool, NGX_RTMP_MAX_OCLP, + sizeof(ngx_rtmp_notify_event_t)) == NGX_ERROR) + { + return NULL; + } + } + + oacf->meta_once = NGX_CONF_UNSET; + oacf->meta_type = NGX_CONF_UNSET_UINT; + oacf->ignore_invalid_notify = NGX_CONF_UNSET; + + return oacf; +} + +static char * +ngx_rtmp_notify_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_rtmp_notify_app_conf_t *prev = parent; + ngx_rtmp_notify_app_conf_t *conf = child; + + ngx_conf_merge_value(conf->meta_once, prev->meta_once, 1); + ngx_conf_merge_uint_value(conf->meta_type, prev->meta_type, + NGX_RTMP_OCLP_META_VIDEO); + ngx_conf_merge_value(conf->ignore_invalid_notify, + prev->ignore_invalid_notify, 1); + + return NGX_CONF_OK; +} + +static ngx_int_t +ngx_rtmp_notify_create_event(ngx_conf_t *cf, ngx_rtmp_notify_event_t *event, + ngx_str_t *values, ngx_uint_t n) +{ + ngx_request_url_t ru; + ngx_str_t tmp; + ngx_uint_t i; + u_char *p, *last; + + for (i = 0; i < n; ++i) { + if (ngx_strncmp(values[i].data, "args=", 5) == 0) { + tmp = values[i]; + tmp.len -= 5; + tmp.data += 5; + ngx_rtmp_variable_transform_index(cf, &tmp, &event->args); +// event->args.len = values[i].len - 5; +// event->args.data = values[i].data + 5; + continue; + } + + if (ngx_strncmp(values[i].data, "groupid=", 8) == 0) { + event->groupid.len = values[i].len - 8; + event->groupid.data = values[i].data + 8; + continue; + } + + if (ngx_strncmp(values[i].data, "stage=", 6) == 0) { + p = values[i].data + 6; + last = values[i].data + values[i].len; + + while (1) { + tmp.data = p; + p = ngx_strlchr(p, last, ','); + if (p == NULL) { + tmp.len = last - tmp.data; + } else { + tmp.len = p - tmp.data; + } + + switch (tmp.len) { + case 4: + if (ngx_strncmp(tmp.data, "done", 4) == 0) { + event->stage |= NGX_RTMP_OCLP_DONE; + } else { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "unknown stage \"%V\"", &tmp); + return NGX_ERROR; + } + break; + case 5: + if (ngx_strncmp(tmp.data, "start", 5) == 0) { + event->stage |= NGX_RTMP_OCLP_START; + } else { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "unknown stage \"%V\"", &tmp); + return NGX_ERROR; + } + break; + case 6: + if (ngx_strncmp(tmp.data, "update", 6) == 0) { + event->stage |= NGX_RTMP_OCLP_UPDATE; + } else { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "unknown stage \"%V\"", &tmp); + return NGX_ERROR; + } + break; + default: + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "unknown stage \"%V\"", &tmp); + return NGX_ERROR; + } + + if (p == last || p == NULL) { + break; + } + + ++p; + } + + continue; + } + + if (ngx_strncmp(values[i].data, "timeout=", 8) == 0) { + tmp.len = values[i].len - 8; + tmp.data = values[i].data + 8; + + event->timeout = ngx_parse_time(&tmp, 0); + if (event->timeout == (ngx_msec_t) NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "unknown timeout timer format \"%V\"", &tmp); + return NGX_ERROR; + } + + continue; + } + + if (ngx_strncmp(values[i].data, "update=", 7) == 0) { + tmp.len = values[i].len - 7; + tmp.data = values[i].data + 7; + + event->update = ngx_parse_time(&tmp, 0); + if (event->update == (ngx_msec_t) NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "unknown cont timer format \"%V\"", &tmp); + return NGX_ERROR; + } + + continue; + } + + if (ngx_strncmp(values[i].data, "http://", 7) != 0) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "must be http url"); + return NGX_ERROR; + } + + if (event->url.len != 0) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "duplicate url"); + return NGX_ERROR; + } + + event->url.len = values[i].len; + event->url.data = values[i].data; + if (values[i].data[values[i].len - 1] != '/') { + event->url.data = ngx_pcalloc(cf->pool, values[i].len + 1); + event->url.len = values[i].len + 1; + ngx_snprintf(event->url.data, event->url.len, "%V/", &values[i]); + } + + if (ngx_parse_request_url(&ru, &event->url) != NGX_OK) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "request url format error"); + return NGX_ERROR; + } + + ngx_dynamic_resolver_add_domain(&ru.host, cf->cycle); + } + + if (event->url.len == 0) { + return NGX_ERROR; + } else { + if (event->timeout == 0) { + event->timeout = 3000; + } + + if (event->update == 0) { + event->update = 60000; + } + + event->stage |= NGX_RTMP_OCLP_START; + + return NGX_OK; + } +} + +static char * +ngx_rtmp_notify_on_main_event(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_rtmp_notify_main_conf_t *omcf; + ngx_str_t *name, *value; + ngx_uint_t n; + + omcf = conf; + + value = cf->args->elts; + + name = &value[0]; + + n = 0; + + switch (name->len) { + case sizeof("on_proc") - 1: + n = NGX_RTMP_OCLP_PROC; + break; + case sizeof("oclp_proc") - 1: + n = NGX_RTMP_OCLP_PROC; + break; + } + + if (ngx_rtmp_notify_create_event(cf, &omcf->events[n], &value[1], + cf->args->nelts - 1) == NGX_ERROR) + { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + +static char * +ngx_rtmp_notify_on_srv_event(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_rtmp_notify_srv_conf_t *oscf; + ngx_str_t *name, *value; + ngx_uint_t n; + + oscf = conf; + + value = cf->args->elts; + + name = &value[0]; + + n = 0; + + switch (name->len) { + case sizeof("on_connect") - 1: + n = NGX_RTMP_OCLP_CONNECT; + break; + case sizeof("oclp_connect") - 1: + n = NGX_RTMP_OCLP_CONNECT; + break; + } + + if (ngx_rtmp_notify_create_event(cf, &oscf->events[n], &value[1], + cf->args->nelts - 1) == NGX_ERROR) + { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + +static char * +ngx_rtmp_notify_on_app_event(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_rtmp_notify_app_conf_t *oacf; + ngx_rtmp_notify_event_t *event; + ngx_str_t *name, *value; + ngx_uint_t n; + + oacf = conf; + + value = cf->args->elts; + + name = &value[0]; + + n = 0; + + if (name->data[1] == 'c') { + switch (name->len) { + case sizeof("oclp_play") - 1: + if (name->data[8] == 'y') { /* oclp_play */ + n = NGX_RTMP_OCLP_PLAY; + if (oacf->events[n].nelts != 0) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "duplicate %V", name); + return NGX_CONF_ERROR; + } + } else if (name->data[8] == 'l') { /* oclp_pull */ + n = NGX_RTMP_OCLP_PULL; + if (oacf->events[n].nelts != 0) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "duplicate %V", name); + return NGX_CONF_ERROR; + } + } else if (name->data[8] == 'h') { /* oclp_push */ + n = NGX_RTMP_OCLP_PUSH; + } else if (name->data[8] == 'a') { /* oclp_meta */ + n = NGX_RTMP_OCLP_META; + } + break; + case sizeof("oclp_publish") - 1: + n = NGX_RTMP_OCLP_PUBLISH; + if (oacf->events[n].nelts != 0) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "duplicate %V", name); + return NGX_CONF_ERROR; + } + break; + case sizeof("oclp_stream") - 1: + if (name->data[5] == 's') { /* oclp_stream */ + n = NGX_RTMP_OCLP_STREAM; + } else if (name->data[5] == 'r') { /* oclp_record */ + n = NGX_RTMP_OCLP_RECORD; + } + + if (oacf->events[n].nelts != 0) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "duplicate %V", name); + return NGX_CONF_ERROR; + } + break; + } + } else { + switch (name->len) { + case sizeof("on_play") - 1: + if (name->data[6] == 'y') { /* on_play */ + n = NGX_RTMP_OCLP_PLAY; + if (oacf->events[n].nelts != 0) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "duplicate %V", name); + return NGX_CONF_ERROR; + } + } else if (name->data[6] == 'l') { /* on_pull */ + n = NGX_RTMP_OCLP_PULL; + if (oacf->events[n].nelts != 0) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "duplicate %V", name); + return NGX_CONF_ERROR; + } + } else if (name->data[6] == 'h') { /* on_push */ + n = NGX_RTMP_OCLP_PUSH; + } else if (name->data[6] == 'a') { /* on_meta */ + n = NGX_RTMP_OCLP_META; + } + break; + case sizeof("on_publish") - 1: + n = NGX_RTMP_OCLP_PUBLISH; + if (oacf->events[n].nelts != 0) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "duplicate %V", name); + return NGX_CONF_ERROR; + } + break; + case sizeof("on_stream") - 1: + if (name->data[3] == 's') { /* on_stream */ + n = NGX_RTMP_OCLP_STREAM; + } else if (name->data[3] == 'r') { /* on_record */ + n = NGX_RTMP_OCLP_RECORD; + } + + if (oacf->events[n].nelts != 0) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "duplicate %V", name); + return NGX_CONF_ERROR; + } + break; + } + } + + + if (oacf->events[n].nelts >= NGX_RTMP_MAX_OCLP) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "too much %V events", name); + return NGX_CONF_ERROR; + } + + event = ngx_array_push(&oacf->events[n]); + ngx_memzero(event, sizeof(ngx_rtmp_notify_event_t)); + if (ngx_rtmp_notify_create_event(cf, event, &value[1], cf->args->nelts - 1) + == NGX_ERROR) + { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + +static void +ngx_rtmp_notify_init_process_url(ngx_str_t *url, ngx_pool_t *pool, + ngx_rtmp_notify_event_t *event) +{ + size_t len; + u_char *p; + + len = event->url.len + sizeof("?call=init_process&worker_id=") - 1 + + sizeof("256") - 1; + + url->data = ngx_pcalloc(pool, len); + if (url->data == NULL) { + return; + } + + p = url->data; + p = ngx_snprintf(p, len, "%V?call=init_process&worker_id=%ui", + &event->url, ngx_worker); + url->len = p - url->data; +} + +static void +ngx_rtmp_notify_init_process_handle(ngx_netcall_ctx_t *nctx, ngx_int_t code) +{ + if (code != NGX_HTTP_OK) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, + "notify init process notify error: %i", code); + } + + return; +} + +static void +ngx_rtmp_notify_init_process_create(ngx_event_t *ev) +{ + ngx_netcall_ctx_t *nctx; + + nctx = ev->data; + + ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, 0, "notify init process create"); + + ngx_netcall_create(nctx, ngx_cycle->log); +} + +static ngx_int_t +ngx_rtmp_notify_init_process(ngx_cycle_t *cycle) +{ + ngx_rtmp_notify_main_conf_t *omcf; + ngx_rtmp_notify_event_t *event; + ngx_rtmp_conf_ctx_t *ctx; + ngx_netcall_ctx_t *nctx; + ngx_event_t *ev; + + if (ngx_process != NGX_PROCESS_WORKER && + ngx_process != NGX_PROCESS_SINGLE) + { + return NGX_OK; + } + + if (ngx_rtmp_core_main_conf == NULL) { + return NGX_OK; + } + + ctx = (ngx_rtmp_conf_ctx_t *) ngx_get_conf(cycle->conf_ctx, + ngx_rtmp_module); + omcf = (ngx_rtmp_notify_main_conf_t *) + ctx->main_conf[ngx_rtmp_notify_module.ctx_index]; + + if (omcf->events[NGX_RTMP_OCLP_PROC].url.len == 0) { + return NGX_OK; + } + + event = &omcf->events[NGX_RTMP_OCLP_PROC]; + + nctx = ngx_netcall_create_ctx(NGX_RTMP_OCLP_PROC, &event->groupid, + event->stage, event->timeout, event->update, 0); + if (nctx == NULL) { + return NGX_ERROR; + } + + ngx_rtmp_notify_init_process_url(&nctx->url, nctx->pool, event); + nctx->handler = ngx_rtmp_notify_init_process_handle; + nctx->data = nctx; + + ev = &nctx->ev; + ev->handler = ngx_rtmp_notify_init_process_create; + + ngx_post_event(ev, &ngx_rtmp_init_queue); + + return NGX_OK; +} + +static void +ngx_rtmp_notify_common_url(ngx_str_t *url, ngx_rtmp_session_t *s, + ngx_rtmp_notify_event_t *event, ngx_netcall_ctx_t *nctx, ngx_uint_t stage) +{ + ngx_request_url_t ru; + size_t len; + u_char *p, *buf; + ngx_live_record_ctx_t *lrctx; + unsigned fill = 0; + ngx_str_t args; + + ngx_memzero(&ru, sizeof(ngx_request_url_t)); + ngx_parse_request_url(&ru, &event->url); + + len = NGX_NETCALL_MAX_URL_LEN; + buf = url->data; + p = ngx_snprintf(buf, len , "%V", &event->url); + if (fill) { + *p++ = '/'; + } + len -= p - buf; + buf = p; + + if (ru.args.len) { // url already has args + p = ngx_snprintf(buf, len, + "&call=%s&act=%s&domain=%V&app=%V&name=%V&clientid=%D", + ngx_rtmp_notify_app_type[nctx->type], + ngx_rtmp_notify_stage[stage], + &s->domain, &s->app, &s->name, s->number); + } else { + p = ngx_snprintf(buf, len, + "?call=%s&act=%s&domain=%V&app=%V&name=%V&clientid=%D", + ngx_rtmp_notify_app_type[nctx->type], + ngx_rtmp_notify_stage[stage], + &s->domain, &s->app, &s->name, s->number); + } + len -= p - buf; + buf = p; + + if (nctx->type == NGX_RTMP_OCLP_RECORD && stage != NGX_RTMP_OCLP_START) { + lrctx = ngx_rtmp_get_module_ctx(s, ngx_live_record_module); + p = ngx_snprintf(buf, len, "&begintime=%M&endtime=%M&index=%V&file=%V", + lrctx->begintime, lrctx->endtime, &lrctx->index.name, + &lrctx->file.name); + + len -= p - buf; + buf = p; + } + + if (event->groupid.len) { + p = ngx_snprintf(buf, len, "&groupid=%V", &event->groupid); + len -= p - buf; + buf = p; + } + + if (event->args.len) { + ngx_rtmp_fetch_variable(s, s->pool, &event->args, &args); + if (args.len > 0) { + p = ngx_snprintf(buf, len, "&%V", &args); + len -= p - buf; + buf = p; + } + } + + url->len = p - url->data; +} + +static void +ngx_rtmp_notify_common_timer(ngx_event_t *ev) +{ + ngx_netcall_ctx_t *nctx; + + nctx = ev->data; + + ngx_log_error(NGX_LOG_INFO, ev->log, 0, "notify %s update create %V", + ngx_rtmp_notify_app_type[nctx->type], &nctx->url); + + ngx_netcall_create(nctx, ev->log); +} + +static void +ngx_rtmp_notify_common_update_handle(ngx_netcall_ctx_t *nctx, ngx_int_t code) +{ + ngx_event_t *ev; + + ev = &nctx->ev; + ev->handler = ngx_rtmp_notify_common_timer; + + if (code != NGX_HTTP_OK) { + ngx_log_error(NGX_LOG_ERR, ev->log, 0, + "notify %s update notify error: %i", + ngx_rtmp_notify_app_type[nctx->type], code); + } + + ngx_add_timer(ev, nctx->update); +} + +static void +ngx_rtmp_notify_common_update_create(ngx_rtmp_session_t *s, + ngx_netcall_ctx_t *nctx) +{ + ngx_rtmp_notify_app_conf_t *oacf; + ngx_rtmp_notify_event_t *event; + ngx_event_t *ev; + + if ((nctx->stage & NGX_RTMP_OCLP_UPDATE) == NGX_RTMP_OCLP_UPDATE) { + oacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module); + + event = oacf->events[nctx->type].elts; + event += nctx->idx; + + ngx_rtmp_notify_common_url(&nctx->url, s, event, nctx, + NGX_RTMP_OCLP_UPDATE); + nctx->handler = ngx_rtmp_notify_common_update_handle; + + ev = &nctx->ev; + ev->data = nctx; + ev->handler = ngx_rtmp_notify_common_timer; + + ngx_add_timer(ev, nctx->update); + } +} + +static void +ngx_rtmp_notify_common_done(ngx_rtmp_session_t *s, ngx_netcall_ctx_t *nctx) +{ + ngx_rtmp_notify_app_conf_t *oacf; + ngx_rtmp_notify_event_t *event; + + if (nctx == NULL) { + return; + } + + if ((nctx->stage & NGX_RTMP_OCLP_DONE) == NGX_RTMP_OCLP_DONE) { + oacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module); + event = oacf->events[nctx->type].elts; + + ngx_rtmp_notify_common_url(&nctx->url, s, event, nctx, + NGX_RTMP_OCLP_DONE); + + ngx_log_error(NGX_LOG_INFO, s->log, 0, + "notify %s done create %V %p", + ngx_rtmp_notify_app_type[nctx->type], &nctx->url, nctx); + + ngx_netcall_create(nctx, s->log); + } + + ngx_netcall_destroy(nctx); +} + +static void +ngx_rtmp_notify_pnotify_start_handle(ngx_netcall_ctx_t *nctx, ngx_int_t code) +{ + ngx_rtmp_session_t *s; + ngx_rtmp_notify_ctx_t *octx; + ngx_rtmp_notify_app_conf_t *oacf; + + s = nctx->data; + + octx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_notify_module); + + oacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module); + + s->notify_status = code; + + if (code < NGX_HTTP_OK || code > NGX_HTTP_SPECIAL_RESPONSE) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "notify %s start notify error: %i", + ngx_rtmp_notify_app_type[nctx->type], code); + + if (code != -1 || !oacf->ignore_invalid_notify) { + goto error; + } + + goto next; + } + +next: + if (octx->type == NGX_RTMP_OCLP_PUBLISH) { + if (next_publish(s, &octx->publish_v) != NGX_OK) { + goto error; + } + } else { + if (next_play(s, &octx->play_v) != NGX_OK) { + goto error; + } + } + + ngx_rtmp_notify_common_update_create(s, nctx); + + return; + +error: + if (octx->type == NGX_RTMP_OCLP_PUBLISH) { + ngx_rtmp_send_status(s, "NetStream.Publish.Forbidden", "status", + "Publish stream Forbidden"); + } else if (s->live_type == NGX_RTMP_LIVE) { + s->status = 403; + ngx_rtmp_send_status(s, "NetStream.Play.Forbidden", "status", + "Play stream Forbidden"); + } + s->finalize_reason = NGX_LIVE_OCLP_NOTIFY_ERR; + ngx_rtmp_finalize_session(s); +} + +static ngx_int_t +ngx_rtmp_notify_pnotify_start(ngx_rtmp_session_t *s, ngx_uint_t type) +{ + ngx_rtmp_notify_app_conf_t *oacf; + ngx_rtmp_notify_event_t *event; + ngx_rtmp_notify_ctx_t *ctx; + ngx_netcall_ctx_t *nctx; + + oacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module); + + if (oacf->events[type].nelts == 0) { + return NGX_DECLINED; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_notify_module); + if (ctx == NULL) { + ctx = ngx_pcalloc(s->pool, sizeof(ngx_rtmp_notify_ctx_t)); + if (ctx == NULL) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, "palloc notify ctx failed"); + return NGX_ERROR; + } + ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_notify_module); + } + + event = oacf->events[type].elts; + + nctx = ngx_netcall_create_ctx(type, &event->groupid, + event->stage, event->timeout, event->update, 0); + nctx->ev.log = s->log; + + ngx_rtmp_notify_common_url(&nctx->url, s, event, nctx, + NGX_RTMP_OCLP_START); + nctx->handler = ngx_rtmp_notify_pnotify_start_handle; + nctx->data = s; + + ctx->nctx = nctx; + ctx->type = type; + + ngx_log_error(NGX_LOG_INFO, s->log, 0, "notify %s start create %V", + ngx_rtmp_notify_app_type[nctx->type], &nctx->url); + + ngx_netcall_create(nctx, s->log); + + return NGX_OK; +} + +static void +ngx_rtmp_notify_pnotify_done(ngx_rtmp_session_t *s) +{ + ngx_rtmp_notify_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_notify_module); + if (ctx == NULL) { + return; + } + + ngx_rtmp_notify_common_done(s, ctx->nctx); +} + +static void +ngx_rtmp_notify_relay_error(ngx_rtmp_session_t *s, ngx_uint_t status) +{ + ngx_rtmp_core_ctx_t *cctx; + size_t i; + + for (i = 0; ngx_rtmp_notify_relay_errors[i].status; ++i) { + if (status != ngx_rtmp_notify_relay_errors[i].status) { + continue; + } + + if (s->publishing) { // relay pull + cctx = s->live_stream->play_ctx; + } else { // relay push + cctx = s->live_stream->publish_ctx; + } + + for (; cctx; cctx = cctx->next) { + if (!cctx->session->static_pull) { + cctx->session->status = status; + cctx->session->finalize_reason = NGX_LIVE_RELAY_TRANSIT; + ngx_rtmp_finalize_session(cctx->session); + } + } + } +} + +static void +ngx_rtmp_notify_relay_start_handle(ngx_netcall_ctx_t *nctx, ngx_int_t code) +{ + ngx_rtmp_session_t *s; + ngx_live_relay_t relay; + ngx_live_relay_url_t *url; + ngx_str_t *local_name; + ngx_str_t *local_domain; + u_char *p, *last; + ngx_rtmp_notify_ctx_t *octx; + ngx_live_relay_ctx_t *ctx; + + static ngx_str_t location = ngx_string("location"); + static ngx_str_t domain = ngx_string("domain"); + + s = nctx->data; + // relay pull, no player or relay push no publisher + if ((s->publishing && s->live_stream->play_ctx == NULL) + || (!s->publishing && s->live_stream->publish_ctx == NULL)) + { + return; + } + + s->notify_status = code; + + if (code == -1) { // wait for notify relay reconnect + ngx_log_error(NGX_LOG_ERR, s->log, 0, "notify relay start failed"); + + s->finalize_reason = NGX_LIVE_OCLP_RELAY_ERR; + ngx_rtmp_finalize_session(s); // only reconnect immediately + + return; + } + + if (code >= 400) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, + "notify: relay_start_handle| " + "notify error: %i", code); + + if (s->publishing) { // relay pull + ngx_rtmp_notify_relay_error(s, 404); + } else { // relay push + ngx_rtmp_notify_relay_error(s, 400); + } + s->finalize_reason = NGX_LIVE_OCLP_RELAY_ERR; + ngx_rtmp_finalize_session(s); + + return; + } + + if (code == NGX_HTTP_OK) { // successd but no need to relay + ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module); + ctx->giveup = 1; + s->finalize_reason = NGX_LIVE_NORMAL_CLOSE; + ngx_rtmp_finalize_session(s); + return; + } + + /* redirect */ + local_name = ngx_netcall_header(nctx, &location); + if (local_name == NULL) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "notify relay start has no Location when redirect"); + s->finalize_reason = NGX_LIVE_OCLP_PARA_ERR; + ngx_rtmp_finalize_session(s); + + return; + } + + ngx_memzero(&relay, sizeof(ngx_live_relay_t)); + if (ngx_array_init(&relay.urls, s->pool, 1, sizeof(ngx_live_relay_url_t)) + != NGX_OK) + { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "notify relay start, init relay urls failed"); + ngx_rtmp_finalize_session(s); + + return; + } + + url = ngx_array_push(&relay.urls); + if (url == NULL) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "notify relay start, alloc url from relay urls failed"); + ngx_rtmp_finalize_session(s); + + return; + } + ngx_memzero(url, sizeof(ngx_live_relay_url_t)); + relay.tag = &ngx_rtmp_notify_module; + + if (ngx_parse_request_url(&url->url, local_name) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "notify relay start, request url format error: %V", &location); + s->finalize_reason = NGX_LIVE_OCLP_PARA_ERR; + ngx_rtmp_finalize_session(s); + + return; + } + + if (ngx_strncasecmp(url->url.scheme.data, (u_char *) "rtmp://", 7) == 0) { + url->relay_type = NGX_LIVE_RELAY_RTMP; + } + + url->port = ngx_request_port(&url->url.scheme, &url->url.port); + if (url->port == 0) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "notify relay start, request url port error"); + s->finalize_reason = NGX_LIVE_OCLP_PARA_ERR; + ngx_rtmp_finalize_session(s); + + return; + } + + // set domain + local_domain = ngx_netcall_header(nctx, &domain); + if (local_domain && local_domain->len) { + relay.domain = *local_domain; + } + + if (url->url.path.len == 0) { // no app and stream + goto relay_create; + } + + p = url->url.path.data; + last = url->url.path.data + url->url.path.len; + + // set app + relay.app.data = p; + p = ngx_strlchr(p, last, '/'); + if (p == NULL) { // only has app + relay.app.len = last - relay.app.data; + goto relay_create; + } else { + relay.app.len = p - relay.app.data; + } + + ++p; + if (p == last) { + goto relay_create; + } + + // set stream name + relay.name.data = p; + relay.name.len = last - relay.name.data; + + // set args + if (url->url.args.len) { + relay.pargs = url->url.args; + } + +relay_create: + octx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_notify_module); + ngx_rtmp_notify_common_update_create(s, octx->nctx); + + ngx_live_relay_create(s, &relay); + + return; +} + +static void +ngx_rtmp_notify_relay_handler(ngx_event_t *ev) +{ + ngx_rtmp_session_t *s; + ngx_live_relay_app_conf_t *lracf; + ngx_live_relay_ctx_t *ctx; + ngx_rtmp_notify_ctx_t *octx; + ngx_rtmp_notify_event_t *event; + ngx_netcall_ctx_t *nctx; + + s = ev->data; + ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module); + + octx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_notify_module); + event = octx->event; + + if (!ctx->failed_delay && ev->timedout) { // connect timeout + ngx_log_error(NGX_LOG_ERR, s->log, NGX_ETIMEDOUT, + "notify relay, relay timeout"); + s->finalize_reason = NGX_LIVE_RELAY_TIMEOUT; + ngx_rtmp_finalize_session(s); + + if (octx->nctx->hcr) { + ngx_http_client_detach(octx->nctx->hcr); + octx->nctx->hcr = NULL; + + if (octx->nctx->ev.timer_set) { + ngx_del_timer(&octx->nctx->ev); + } + + if (octx->nctx->ev.posted) { + ngx_delete_posted_event(&octx->nctx->ev); + } + } + + return; + } + + // relay pull, no player or relay push no publisher + if ((s->publishing && s->live_stream->play_ctx == NULL) + || (!s->publishing && s->live_stream->publish_ctx == NULL)) + { + return; + } + + lracf = ngx_rtmp_get_module_app_conf(s, ngx_live_relay_module); + + // this timer control start netcall until relay create success + ngx_add_timer(&ctx->reconnect, lracf->relay_reconnect); + + nctx = ngx_netcall_create_ctx(octx->type, &event->groupid, event->stage, + event->timeout, event->update, 0); + if (nctx == NULL) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, "notify relay, create nctx failed"); + ngx_rtmp_finalize_session(s); + + return; + } + nctx->ev.log = s->log; + + ngx_rtmp_notify_common_url(&nctx->url, s, event, nctx, + NGX_RTMP_OCLP_START); + nctx->handler = ngx_rtmp_notify_relay_start_handle; + nctx->data = s; + + ngx_log_error(NGX_LOG_INFO, s->log, 0, "notify %s start create %V", + ngx_rtmp_notify_app_type[nctx->type], &nctx->url); + + octx->nctx = nctx; + + ngx_netcall_create(nctx, s->log); +} + +static ngx_int_t +ngx_rtmp_notify_relay_start(ngx_rtmp_session_t *s, ngx_rtmp_notify_event_t *event, + ngx_uint_t type, unsigned publishing) +{ + ngx_rtmp_session_t *rs; + ngx_live_relay_ctx_t *ctx, *pctx; + ngx_live_relay_app_conf_t *lracf; + ngx_rtmp_notify_ctx_t *octx; + + rs = ngx_rtmp_create_relay_session(s, &ngx_rtmp_notify_module); + if (rs == NULL) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "notify relay start, create relay session failed"); + return NGX_DECLINED; + } + rs->publishing = publishing; + rs->groupid = event->groupid; + rs->live_stream = s->live_stream; + ngx_live_create_ctx(rs, publishing); + + octx = ngx_pcalloc(rs->pool, sizeof(ngx_rtmp_notify_ctx_t)); + if (octx == NULL) { + ngx_log_error(NGX_LOG_ERR, rs->log, 0, + "notify relay start, create notify ctx failed"); + ngx_rtmp_finalize_session(rs); + + return NGX_OK; + } + ngx_rtmp_set_ctx(rs, octx, ngx_rtmp_notify_module); + octx->event = event; + octx->type = type; + + ctx = ngx_rtmp_get_module_ctx(rs, ngx_live_relay_module); + ctx->reconnect.log = rs->log; + ctx->reconnect.data = rs; + ctx->reconnect.handler = ngx_rtmp_notify_relay_handler; + + if (s->publishing != rs->publishing) { + ngx_post_event(&ctx->reconnect, &ngx_posted_events); + return NGX_OK; + } + + // normal publisher close, need to trigger pull + if (s->publishing && !s->relay) { + ngx_post_event(&ctx->reconnect, &ngx_posted_events); + return NGX_OK; + } + + // reconnect + pctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module); + if (pctx->successd) { // prev relay successd + ngx_post_event(&ctx->reconnect, &ngx_posted_events); + return NGX_OK; + } + + ctx->idx = pctx->idx; + ctx->failed_reconnect = pctx->failed_reconnect; + + lracf = ngx_rtmp_get_module_app_conf(rs, ngx_live_relay_module); + + if (!pctx->reconnect.timer_set) { // prev relay timeout + ctx->failed_reconnect = ngx_min(pctx->failed_reconnect * 2, + lracf->relay_reconnect); + ngx_post_event(&ctx->reconnect, &ngx_posted_events); + return NGX_OK; + } + + if (pctx->failed_reconnect) { + ctx->failed_reconnect = ngx_min(pctx->failed_reconnect * 2, + lracf->relay_reconnect); + } else { + ctx->failed_reconnect = lracf->failed_reconnect; + } + + ctx->failed_delay = 1; + ngx_add_timer(&ctx->reconnect, ctx->failed_reconnect); + + return NGX_OK; +} + +static void +ngx_rtmp_notify_relay_done(ngx_rtmp_session_t *s) +{ + ngx_rtmp_notify_ctx_t *octx; + ngx_netcall_ctx_t *nctx; + + octx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_notify_module); + nctx = octx->nctx; + ngx_rtmp_notify_common_done(s, nctx); +} + +static void +ngx_rtmp_notify_stream_start_handle(ngx_netcall_ctx_t *nctx, ngx_int_t code) +{ + ngx_live_stream_t *st; + ngx_rtmp_session_t *s; + + st = nctx->data; + + if (code != NGX_HTTP_OK) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, + "notify stream start notify error: %i", code); + + return; + } + + if (st->play_ctx) { + s = st->play_ctx->session; + } else if (st->publish_ctx) { + s = st->publish_ctx->session; + } else { + ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, 0, + "notify stream start handle but all stream session close"); + return; + } + + ngx_rtmp_notify_common_update_create(s, nctx); +} + +void +ngx_rtmp_notify_stream_start(ngx_rtmp_session_t *s) +{ + ngx_rtmp_notify_app_conf_t *oacf; + ngx_rtmp_notify_event_t *event; + ngx_netcall_ctx_t *nctx; + + oacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module); + + if (oacf->events[NGX_RTMP_OCLP_STREAM].nelts == 0) { + return; + } + + nctx = s->live_stream->stream_nctx; + if (nctx) { /* stream start has been sent */ + return; + } + + event = oacf->events[NGX_RTMP_OCLP_STREAM].elts; + + if ((event->stage & NGX_RTMP_OCLP_START) == NGX_RTMP_OCLP_START) { + nctx = ngx_netcall_create_ctx(NGX_RTMP_OCLP_STREAM, &event->groupid, + event->stage, event->timeout, event->update, 0); + + ngx_rtmp_notify_common_url(&nctx->url, s, event, nctx, + NGX_RTMP_OCLP_START); + nctx->handler = ngx_rtmp_notify_stream_start_handle; + nctx->data = s->live_stream; + + s->live_stream->stream_nctx = nctx; + + ngx_log_error(NGX_LOG_INFO, s->log, 0, + "notify stream start create %V", &nctx->url); + + ngx_netcall_create(nctx, ngx_cycle->log); + } +} + +void +ngx_rtmp_notify_stream_done(ngx_rtmp_session_t *s) +{ + ngx_netcall_ctx_t *nctx; + + nctx = s->live_stream->stream_nctx; + ngx_rtmp_notify_common_done(s, nctx); + s->live_stream->stream_nctx = NULL; +} + +static void +ngx_rtmp_notify_record_start_handle(ngx_netcall_ctx_t *nctx, ngx_int_t code) +{ + ngx_rtmp_session_t *s; + ngx_live_record_ctx_t *ctx; + + s = nctx->data; + ctx = ngx_rtmp_get_module_ctx(s, ngx_live_record_module); + + if (code == NGX_HTTP_OK) { + ctx->open = 1; + } else if (code != -1) { + ctx->open = 0; + } + + ngx_log_error(NGX_LOG_INFO, s->log, 0, + "notify record receive code %i, open: %d", code, ctx->open); + + if (next_record_start(s) != NGX_OK) { + ngx_rtmp_finalize_session(s); + } +} + +static ngx_int_t +ngx_rtmp_notify_record_start(ngx_rtmp_session_t *s) +{ + ngx_rtmp_notify_app_conf_t *oacf; + ngx_netcall_ctx_t *nctx; + ngx_rtmp_notify_ctx_t *octx; + ngx_rtmp_notify_event_t *event; + + oacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module); + + if (oacf->events[NGX_RTMP_OCLP_RECORD].nelts == 0) { + goto next; + } + + octx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_notify_module); + if (octx == NULL) { + octx = ngx_pcalloc(s->pool, sizeof(ngx_rtmp_notify_ctx_t)); + if (octx == NULL) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, "pcalloc notify ctx failed"); + goto next; + } + ngx_rtmp_set_ctx(s, octx, ngx_rtmp_notify_module); + } + + event = oacf->events[NGX_RTMP_OCLP_RECORD].elts; + + nctx = ngx_netcall_create_ctx(NGX_RTMP_OCLP_RECORD, &event->groupid, + event->stage, event->timeout, event->update, 0); + + ngx_rtmp_notify_common_url(&nctx->url, s, event, nctx, + NGX_RTMP_OCLP_START); + nctx->handler = ngx_rtmp_notify_record_start_handle; + nctx->data = s; + + octx->rctx = nctx; + octx->type = NGX_RTMP_OCLP_RECORD; + + ngx_log_error(NGX_LOG_INFO, s->log, 0, "notify record start create %V", + &nctx->url); + + ngx_netcall_create(nctx, s->log); + + return NGX_OK; + +next: + return next_record_start(s); +} + +static void +ngx_rtmp_notify_record_update_handle(ngx_netcall_ctx_t *nctx, ngx_int_t code) +{ + ngx_rtmp_session_t *s; + + s = nctx->data; + + if (code != NGX_HTTP_OK) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "notify record update notify error: %i", code); + } +} + +static ngx_int_t +ngx_rtmp_notify_record_update(ngx_rtmp_session_t *s) +{ + ngx_rtmp_notify_app_conf_t *oacf; + ngx_netcall_ctx_t *nctx; + ngx_rtmp_notify_ctx_t *octx; + ngx_rtmp_notify_event_t *event; + + octx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_notify_module); + if (octx == NULL || octx->rctx == NULL) { + goto next; + } + + nctx = octx->rctx; + + if ((nctx->stage & NGX_RTMP_OCLP_UPDATE) == NGX_RTMP_OCLP_UPDATE) { + oacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module); + event = oacf->events[nctx->type].elts; + + ngx_rtmp_notify_common_url(&nctx->url, s, event, nctx, + NGX_RTMP_OCLP_UPDATE); + nctx->handler = ngx_rtmp_notify_record_update_handle; + + ngx_log_error(NGX_LOG_INFO, s->log, 0, + "notify record update create %V %p", &nctx->url, nctx); + + ngx_netcall_create(nctx, s->log); + } + +next: + return next_record_update(s); +} + +static ngx_int_t +ngx_rtmp_notify_record_done(ngx_rtmp_session_t *s) +{ + ngx_rtmp_notify_app_conf_t *oacf; + ngx_netcall_ctx_t *nctx; + ngx_rtmp_notify_ctx_t *octx; + ngx_rtmp_notify_event_t *event; + + octx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_notify_module); + if (octx == NULL || octx->rctx == NULL) { + goto next; + } + + nctx = octx->rctx; + + if ((nctx->stage & NGX_RTMP_OCLP_DONE) == NGX_RTMP_OCLP_DONE) { + oacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module); + event = oacf->events[nctx->type].elts; + + ngx_rtmp_notify_common_url(&nctx->url, s, event, nctx, + NGX_RTMP_OCLP_DONE); + + ngx_log_error(NGX_LOG_INFO, s->log, 0, + "notify record done create %V %p", &nctx->url, nctx); + + ngx_netcall_create(nctx, s->log); + } + + ngx_netcall_destroy(nctx); + +next: + return next_record_done(s); +} + +static ngx_int_t +ngx_rtmp_notify_push(ngx_rtmp_session_t *s) +{ + ngx_rtmp_notify_app_conf_t *oacf; + ngx_rtmp_notify_event_t *event; + ngx_uint_t i; + + oacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module); + if (oacf->events[NGX_RTMP_OCLP_PUSH].nelts == 0) { // not configured + goto next; + } + + if (s->relay && s->static_pull == 0) { + goto next; + } + + event = oacf->events[NGX_RTMP_OCLP_PUSH].elts; + for (i = 0; i < oacf->events[NGX_RTMP_OCLP_PUSH].nelts; ++i, ++event) { + ngx_rtmp_notify_relay_start(s, event, NGX_RTMP_OCLP_PUSH, 0); + } + +next: + return next_push(s); +} + +static ngx_int_t +ngx_rtmp_notify_pull(ngx_rtmp_session_t *s) +{ + ngx_rtmp_notify_app_conf_t *oacf; + ngx_rtmp_notify_event_t *event; + + oacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module); + if (oacf->events[NGX_RTMP_OCLP_PULL].nelts == 0) { // not configured + goto next; + } + + if (!s->publishing && s->relay) { // relay push + goto next; + } + + if (s->live_stream->publish_ctx) { // already has publisher + goto next; + } + + event = oacf->events[NGX_RTMP_OCLP_PULL].elts; + + if (ngx_rtmp_notify_relay_start(s, event, NGX_RTMP_OCLP_PULL, 1) == NGX_OK) { + return NGX_OK; + } + +next: + return next_pull(s); +} + +static ngx_int_t +ngx_rtmp_notify_push_close(ngx_rtmp_session_t *s) +{ + ngx_live_relay_ctx_t *ctx; + ngx_rtmp_notify_ctx_t *octx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module); + if (ctx->tag != &ngx_rtmp_notify_module) { + goto next; + } + + ngx_rtmp_notify_relay_done(s); // nctx will close in relay done + + // relay push and has publisher in stream + if (!ctx->giveup && s->live_stream->publish_ctx) { + octx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_notify_module); + + ngx_rtmp_notify_relay_start(s, octx->event, octx->type, 0); + } + +next: + return next_push_close(s); +} + +static ngx_int_t +ngx_rtmp_notify_pull_close(ngx_rtmp_session_t *s) +{ + ngx_live_relay_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module); + if (ctx == NULL) { + goto next; + } + + if (ctx->tag != &ngx_rtmp_notify_module) { + goto next; + } + + ngx_rtmp_notify_relay_done(s); // nctx will close in relay done + +next: + return next_pull_close(s); +} + +static ngx_int_t +ngx_rtmp_notify_av(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, ngx_chain_t *in) +{ + ngx_rtmp_notify_app_conf_t *oacf; + ngx_rtmp_notify_event_t *event; + ngx_uint_t i; + + if (ngx_rtmp_is_codec_header(in)) { + oacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module); + + if (oacf->meta_once && s->live_stream->notify_meta) { + return NGX_OK; + } + + switch (oacf->meta_type) { + case NGX_RTMP_OCLP_META_VIDEO: + if (h->type == NGX_RTMP_MSG_AUDIO) { + return NGX_OK; + } + + break; + case NGX_RTMP_OCLP_META_AUDIO: + if (h->type == NGX_RTMP_MSG_VIDEO) { + return NGX_OK; + } + + break; + default: + break; + } + + s->live_stream->notify_meta = 1; + + event = oacf->events[NGX_RTMP_OCLP_META].elts; + for (i = 0; i < oacf->events[NGX_RTMP_OCLP_META].nelts; ++i, ++event) { + ngx_rtmp_notify_relay_start(s, event, NGX_RTMP_OCLP_META, 0); + } + } + + return NGX_OK; +} + +static ngx_int_t +ngx_rtmp_notify_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) +{ + ngx_rtmp_notify_ctx_t *octx; + + if (s->relay || s->interprocess) { + goto next; + } + + switch (ngx_rtmp_notify_pnotify_start(s, NGX_RTMP_OCLP_PUBLISH)) { + case NGX_OK: + break; + case NGX_DECLINED: + goto next; + default: + return NGX_ERROR; + } + + octx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_notify_module); + ngx_memcpy(&octx->publish_v, v, sizeof(ngx_rtmp_publish_t)); + + return NGX_OK; + +next: + return next_publish(s, v); +} + +static ngx_int_t +ngx_rtmp_notify_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) +{ + ngx_rtmp_notify_ctx_t *octx; + + if (s->relay || s->interprocess) { + goto next; + } + + switch (ngx_rtmp_notify_pnotify_start(s, NGX_RTMP_OCLP_PLAY)) { + case NGX_OK: + break; + case NGX_DECLINED: + goto next; + default: + return NGX_ERROR; + } + + octx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_notify_module); + ngx_memcpy(&octx->play_v, v, sizeof(ngx_rtmp_play_t)); + + return NGX_OK; + +next: + return next_play(s, v); +} + +static ngx_int_t +ngx_rtmp_notify_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v) +{ + if (s->relay || s->interprocess) { + goto next; + } + + ngx_rtmp_notify_pnotify_done(s); + +next: + return next_close_stream(s, v); +} + +static ngx_int_t +ngx_rtmp_notify_postconfiguration(ngx_conf_t *cf) +{ + ngx_rtmp_core_main_conf_t *cmcf; + ngx_rtmp_handler_pt *h; + + cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); + + h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AUDIO]); + *h = ngx_rtmp_notify_av; + + h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_VIDEO]); + *h = ngx_rtmp_notify_av; + + /* record */ + next_record_start = ngx_live_record_start; + ngx_live_record_start = ngx_rtmp_notify_record_start; + + next_record_update = ngx_live_record_update; + ngx_live_record_update = ngx_rtmp_notify_record_update; + + next_record_done = ngx_live_record_done; + ngx_live_record_done = ngx_rtmp_notify_record_done; + + /* pull & push */ + next_push = ngx_live_push; + ngx_live_push = ngx_rtmp_notify_push; + + next_pull = ngx_live_pull; + ngx_live_pull = ngx_rtmp_notify_pull; + + next_push_close = ngx_live_push_close; + ngx_live_push_close = ngx_rtmp_notify_push_close; + + next_pull_close = ngx_live_pull_close; + ngx_live_pull_close = ngx_rtmp_notify_pull_close; + + /* publish & play */ + next_publish = ngx_rtmp_publish; + ngx_rtmp_publish = ngx_rtmp_notify_publish; + + next_play = ngx_rtmp_play; + ngx_rtmp_play = ngx_rtmp_notify_play; + + next_close_stream = ngx_rtmp_close_stream; + ngx_rtmp_close_stream = ngx_rtmp_notify_close_stream; + + return NGX_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_notify_module.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_notify_module.h new file mode 100644 index 0000000000..889d10bb5c --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_notify_module.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + * + */ + + +#ifndef _NGX_RTMP_OCLP_MODULE_H_INCLUDED_ +#define _NGX_RTMP_OCLP_MODULE_H_INCLUDED_ + + +#include "ngx_rtmp.h" +#include "ngx_netcall.h" + + +void ngx_rtmp_notify_stream_start(ngx_rtmp_session_t *s); +void ngx_rtmp_notify_stream_done(ngx_rtmp_session_t *s); + + +#endif diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_proxy_protocol.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_proxy_protocol.c new file mode 100644 index 0000000000..1b240556b1 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_proxy_protocol.c @@ -0,0 +1,197 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include +#include "ngx_rtmp_proxy_protocol.h" + + +static void ngx_rtmp_proxy_protocol_recv(ngx_event_t *rev); + + +void +ngx_rtmp_proxy_protocol(ngx_rtmp_session_t *s) +{ + ngx_event_t *rev; + ngx_connection_t *c; + + c = s->connection; + rev = c->read; + rev->handler = ngx_rtmp_proxy_protocol_recv; + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0, + "proxy_protocol: start"); + + if (rev->ready) { + /* the deferred accept(), rtsig, aio, iocp */ + + if (ngx_use_accept_mutex) { + ngx_post_event(rev, &ngx_posted_events); + return; + } + + rev->handler(rev); + return; + } + + ngx_add_timer(rev, s->timeout); + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_rtmp_finalize_session(s); + return; + } +} + + +static void +ngx_rtmp_proxy_protocol_recv(ngx_event_t *rev) +{ + u_char buf[107], *p, *pp, *text; + size_t len; + ssize_t n; + ngx_err_t err; + ngx_int_t i; + ngx_addr_t addr; + ngx_connection_t *c; + ngx_rtmp_session_t *s; + + c = rev->data; + s = c->data; + + if (c->destroyed) { + return; + } + + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, + "proxy_protocol: recv: client timed out"); + c->timedout = 1; + ngx_rtmp_finalize_session(s); + return; + } + + if (rev->timer_set) { + ngx_del_timer(rev); + } + + n = recv(c->fd, (char *) buf, sizeof(buf), MSG_PEEK); + + err = ngx_socket_errno; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, c->log, 0, "recv(): %d", n); + + if (n == -1) { + + if (err == NGX_EAGAIN) { + ngx_add_timer(rev, s->timeout); + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + ngx_rtmp_finalize_session(s); + } + + return; + } + + ngx_rtmp_finalize_session(s); + + return; + } + + p = buf; + + if (n <= 8 && ngx_strncmp(p, "PROXY ", 6) != 0) { + goto bad_header; + } + + n -= 6; + p += 6; + + ngx_memzero(&addr, sizeof(ngx_addr_t)); + + if (n >= 7 && ngx_strncmp(p, "UNKNOWN", 7) == 0) { + n -= 7; + p += 7; + goto skip; + } + + if (n < 5 || ngx_strncmp(p, "TCP", 3) != 0 + || (p[3] != '4' && p[3] != '6') || p[4] != ' ') + { + goto bad_header; + } + + n -= 5; + p += 5; + + pp = ngx_strlchr(p, p + n, ' '); + + if (pp == NULL) { + goto bad_header; + } + + if (ngx_parse_addr(s->pool, &addr, p, pp - p) != NGX_OK) { + goto bad_header; + } + + n -= pp - p; + p = pp; + +skip: + + for (i = 0; i + 1 < n; i++) { + if (p[i] == CR && p[i + 1] == LF) { + break; + } + } + + if (i + 1 >= n) { + goto bad_header; + } + + n = p - buf + i + 2; + + if (c->recv(c, buf, n) != n) { + goto failed; + } + + if (addr.socklen) { + text = ngx_palloc(s->pool, NGX_SOCKADDR_STRLEN); + + if (text == NULL) { + goto failed; + } + + len = ngx_sock_ntop(addr.sockaddr, +#if (nginx_version >= 1005003) + addr.socklen, +#endif + text, NGX_SOCKADDR_STRLEN, 0); + if (len == 0) { + goto failed; + } + + c->sockaddr = addr.sockaddr; + c->socklen = addr.socklen; + c->addr_text.data = text; + c->addr_text.len = len; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, c->log, 0, + "proxy_protocol: remote_addr:'%V'", &c->addr_text); + } + + ngx_rtmp_handshake(s); + + return; + +bad_header: + + ngx_log_error(NGX_LOG_INFO, c->log, 0, "proxy_protocol: bad header"); + +failed: + + ngx_rtmp_finalize_session(s); +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_proxy_protocol.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_proxy_protocol.h new file mode 100644 index 0000000000..e873c3c0ca --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_proxy_protocol.h @@ -0,0 +1,19 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#ifndef _NGX_RTMP_PROXY_PROTOCOL_H_INCLUDED_ +#define _NGX_RTMP_PROXY_PROTOCOL_H_INCLUDED_ + + +#include +#include +#include "ngx_rtmp.h" + + +void ngx_rtmp_proxy_protocol(ngx_rtmp_session_t *c); + + +#endif /* _NGX_RTMP_PROXY_PROTOCOL_H_INCLUDED_ */ diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_receive.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_receive.c new file mode 100644 index 0000000000..dbf662a5f7 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_receive.c @@ -0,0 +1,464 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp.h" +#include "ngx_rtmp_amf.h" +#include "ngx_rtmp_cmd_module.h" +#include + + +ngx_int_t +ngx_rtmp_protocol_message_handler(ngx_rtmp_session_t *s, + ngx_rtmp_header_t *h, ngx_chain_t *in) +{ + ngx_buf_t *b; + u_char *p; + uint32_t val; + uint8_t limit; + + b = in->buf; + + if (b->last - b->pos < 4) { + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0, + "too small buffer for %d message: %d", + (int)h->type, b->last - b->pos); + return NGX_OK; + } + + p = (u_char*)&val; + p[0] = b->pos[3]; + p[1] = b->pos[2]; + p[2] = b->pos[1]; + p[3] = b->pos[0]; + + switch(h->type) { + case NGX_RTMP_MSG_CHUNK_SIZE: + /* set chunk size =val */ + ngx_rtmp_set_chunk_size(s, val); + break; + + case NGX_RTMP_MSG_ABORT: + /* abort chunk stream =val */ + break; + + case NGX_RTMP_MSG_ACK: + /* receive ack with sequence number =val */ + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "receive ack seq=%uD", val); + break; + + case NGX_RTMP_MSG_ACK_SIZE: + /* receive window size =val */ + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "receive ack_size=%uD", val); + s->ack_size = val; + break; + + case NGX_RTMP_MSG_BANDWIDTH: + if (b->last - b->pos >= 5) { + limit = *(uint8_t*)&b->pos[4]; + + (void)val; + (void)limit; + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0, + "receive bandwidth=%uD limit=%d", + val, (int)limit); + + /* receive window size =val + * && limit */ + } + break; + + default: + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_user_message_handler(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_buf_t *b; + u_char *p; + uint16_t evt; + uint32_t val; + + b = in->buf; + + if (b->last - b->pos < 6) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "too small buffer for user message: %d", + b->last - b->pos); + return NGX_OK; + } + + p = (u_char*)&evt; + + p[0] = b->pos[1]; + p[1] = b->pos[0]; + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0, + "RTMP recv user evt %s (%i)", + ngx_rtmp_user_message_type(evt), (ngx_int_t) evt); + + p = (u_char *) &val; + + p[0] = b->pos[5]; + p[1] = b->pos[4]; + p[2] = b->pos[3]; + p[3] = b->pos[2]; + + switch(evt) { + case NGX_RTMP_USER_STREAM_BEGIN: + { + ngx_rtmp_stream_begin_t v; + + v.msid = val; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "receive: stream_begin msid=%uD", v.msid); + + return ngx_rtmp_stream_begin(s, &v); + } + + case NGX_RTMP_USER_STREAM_EOF: + { + ngx_rtmp_stream_eof_t v; + + v.msid = val; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "receive: stream_eof msid=%uD", v.msid); + + return ngx_rtmp_stream_eof(s, &v); + } + + case NGX_RTMP_USER_STREAM_DRY: + { + ngx_rtmp_stream_dry_t v; + + v.msid = val; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "receive: stream_dry msid=%uD", v.msid); + + return ngx_rtmp_stream_dry(s, &v); + } + + case NGX_RTMP_USER_SET_BUFLEN: + { + ngx_rtmp_set_buflen_t v; + + v.msid = val; + + if (b->last - b->pos < 10) { + return NGX_OK; + } + + p = (u_char *) &v.buflen; + + p[0] = b->pos[9]; + p[1] = b->pos[8]; + p[2] = b->pos[7]; + p[3] = b->pos[6]; + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0, + "receive: set_buflen msid=%uD buflen=%uD", + v.msid, v.buflen); + + /*TODO: move this to play module */ + s->buflen = v.buflen; + + return ngx_rtmp_set_buflen(s, &v); + } + + case NGX_RTMP_USER_RECORDED: + { + ngx_rtmp_recorded_t v; + + v.msid = val; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "receive: recorded msid=%uD", v.msid); + + return ngx_rtmp_recorded(s, &v); + } + + case NGX_RTMP_USER_PING_REQUEST: + return ngx_rtmp_send_ping_response(s, val); + + case NGX_RTMP_USER_PING_RESPONSE: + + /* val = incoming timestamp */ + + ngx_rtmp_reset_ping(s); + + return NGX_OK; + + default: + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "unexpected user event: %i", (ngx_int_t) evt); + + return NGX_OK; + } +} + + +static ngx_int_t +ngx_rtmp_fetch(ngx_chain_t **in, u_char *ret) +{ + while (*in && (*in)->buf->pos >= (*in)->buf->last) { + *in = (*in)->next; + } + + if (*in == NULL) { + return NGX_DONE; + } + + *ret = *(*in)->buf->pos++; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_fetch_uint8(ngx_chain_t **in, uint8_t *ret) +{ + return ngx_rtmp_fetch(in, (u_char *) ret); +} + + +static ngx_int_t +ngx_rtmp_fetch_uint32(ngx_chain_t **in, uint32_t *ret, ngx_int_t n) +{ + u_char *r = (u_char *) ret; + ngx_int_t rc; + + *ret = 0; + + while (--n >= 0) { + rc = ngx_rtmp_fetch(in, &r[n]); + if (rc != NGX_OK) { + return rc; + } + } + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_aggregate_message_handler(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + uint32_t base_time, timestamp, prev_size; + size_t len; + ngx_int_t first; + u_char *last; + ngx_int_t rc; + ngx_buf_t *b; + ngx_chain_t *cl, *next; + ngx_rtmp_header_t ch; + + ch = *h; + + first = 1; + base_time = 0; + + while (in) { + if (ngx_rtmp_fetch_uint8(&in, &ch.type) != NGX_OK) { + return NGX_OK; + } + + if (ngx_rtmp_fetch_uint32(&in, &ch.mlen, 3) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_rtmp_fetch_uint32(&in, ×tamp, 3) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_rtmp_fetch_uint8(&in, (uint8_t *) ×tamp + 3) != NGX_OK) + { + return NGX_ERROR; + } + + if (ngx_rtmp_fetch_uint32(&in, &ch.msid, 3) != NGX_OK) + { + return NGX_ERROR; + } + + if (first) { + base_time = timestamp; + first = 0; + } + + ngx_log_debug6(NGX_LOG_DEBUG_RTMP, s->log, 0, + "RTMP aggregate %s (%d) len=%uD time=%uD (+%D) msid=%uD", + ngx_rtmp_message_type(ch.type), + (ngx_int_t) ch.type, ch.mlen, ch.timestamp, + timestamp - base_time, ch.msid); + + /* limit chain */ + + len = 0; + cl = in; + while (cl) { + b = cl->buf; + len += (b->last - b->pos); + if (len > ch.mlen) { + break; + } + cl = cl->next; + } + + if (cl == NULL) { + ngx_log_error(NGX_LOG_INFO, s->log, 0, + "RTMP error parsing aggregate"); + return NGX_ERROR; + } + + next = cl->next; + cl->next = NULL; + b = cl->buf; + last = b->last; + b->last -= (len - ch.mlen); + + /* handle aggregated message */ + + ch.timestamp = h->timestamp + timestamp - base_time; + + rc = ngx_rtmp_receive_message(s, &ch, in); + + /* restore chain before checking the result */ + + in = cl; + in->next = next; + b->pos = b->last; + b->last = last; + + if (rc != NGX_OK) { + return rc; + } + + /* read 32-bit previous tag size */ + + if (ngx_rtmp_fetch_uint32(&in, &prev_size, 4) != NGX_OK) { + return NGX_OK; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "RTMP aggregate prev_size=%uD", prev_size); + } + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_amf_message_handler(ngx_rtmp_session_t *s, + ngx_rtmp_header_t *h, ngx_chain_t *in) +{ + ngx_rtmp_amf_ctx_t act; + ngx_rtmp_core_main_conf_t *cmcf; + ngx_array_t *ch; + ngx_rtmp_handler_pt *ph; + size_t len, n; + + static u_char func[128]; + + static ngx_rtmp_amf_elt_t elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + func, sizeof(func) }, + }; + + /* AMF command names come with string type, but shared object names + * come without type */ + if (h->type == NGX_RTMP_MSG_AMF_SHARED || + h->type == NGX_RTMP_MSG_AMF3_SHARED) + { + elts[0].type |= NGX_RTMP_AMF_TYPELESS; + } else { + elts[0].type &= ~NGX_RTMP_AMF_TYPELESS; + } + + if ((h->type == NGX_RTMP_MSG_AMF3_SHARED || + h->type == NGX_RTMP_MSG_AMF3_META || + h->type == NGX_RTMP_MSG_AMF3_CMD) + && in->buf->last > in->buf->pos) + { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "AMF3 prefix: %ui", (ngx_int_t)*in->buf->pos); + ++in->buf->pos; + } + + cmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_core_module); + + /* read AMF func name & transaction id */ + ngx_memzero(&act, sizeof(act)); + act.link = in; + act.log = s->log; + memset(func, 0, sizeof(func)); + + if (ngx_rtmp_amf_read(&act, elts, + sizeof(elts) / sizeof(elts[0])) != NGX_OK) + { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0, + "AMF cmd failed"); + return NGX_ERROR; + } + + /* skip name */ + in = act.link; + in->buf->pos += act.offset; + + len = ngx_strlen(func); + + ch = ngx_hash_find(&cmcf->amf_hash, + ngx_hash_strlow(func, func, len), func, len); + + if (ch && ch->nelts) { + ph = ch->elts; + for (n = 0; n < ch->nelts; ++n, ++ph) { + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->log, 0, + "AMF func '%s' passed to handler %d/%d", + func, n, ch->nelts); + switch ((*ph)(s, h, in)) { + case NGX_ERROR: + return NGX_ERROR; + case NGX_DONE: + return NGX_OK; + } + } + } else { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "AMF cmd '%s' no handler", func); + } + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_receive_amf(ngx_rtmp_session_t *s, ngx_chain_t *in, + ngx_rtmp_amf_elt_t *elts, size_t nelts) +{ + ngx_rtmp_amf_ctx_t act; + + ngx_memzero(&act, sizeof(act)); + act.link = in; + act.log = s->log; + + return ngx_rtmp_amf_read(&act, elts, nelts); +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_record_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_record_module.c new file mode 100644 index 0000000000..4cff47e60f --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_record_module.c @@ -0,0 +1,1308 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp.h" +#include "ngx_rtmp_cmd_module.h" +#include "ngx_rtmp_codec_module.h" +#include "ngx_rtmp_record_module.h" + + +ngx_rtmp_record_done_pt ngx_rtmp_record_done; + + +static ngx_rtmp_publish_pt next_publish; +static ngx_rtmp_close_stream_pt next_close_stream; +static ngx_rtmp_stream_begin_pt next_stream_begin; +static ngx_rtmp_stream_eof_pt next_stream_eof; + + +static char *ngx_rtmp_record_recorder(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static ngx_int_t ngx_rtmp_record_postconfiguration(ngx_conf_t *cf); +static void * ngx_rtmp_record_create_app_conf(ngx_conf_t *cf); +static char * ngx_rtmp_record_merge_app_conf(ngx_conf_t *cf, + void *parent, void *child); +static ngx_int_t ngx_rtmp_record_write_frame(ngx_rtmp_session_t *s, + ngx_rtmp_record_rec_ctx_t *rctx, + ngx_rtmp_header_t *h, ngx_chain_t *in, ngx_int_t inc_nframes); +static ngx_int_t ngx_rtmp_record_av(ngx_rtmp_session_t *s, + ngx_rtmp_header_t *h, ngx_chain_t *in); +static ngx_int_t ngx_rtmp_record_node_av(ngx_rtmp_session_t *s, + ngx_rtmp_record_rec_ctx_t *rctx, ngx_rtmp_header_t *h, ngx_chain_t *in); +static ngx_int_t ngx_rtmp_record_node_open(ngx_rtmp_session_t *s, + ngx_rtmp_record_rec_ctx_t *rctx); +static ngx_int_t ngx_rtmp_record_node_close(ngx_rtmp_session_t *s, + ngx_rtmp_record_rec_ctx_t *rctx); +static void ngx_rtmp_record_make_path(ngx_rtmp_session_t *s, + ngx_rtmp_record_rec_ctx_t *rctx, ngx_str_t *path); +static ngx_int_t ngx_rtmp_record_init(ngx_rtmp_session_t *s); + + +static ngx_conf_bitmask_t ngx_rtmp_record_mask[] = { + { ngx_string("off"), NGX_RTMP_RECORD_OFF }, + { ngx_string("all"), NGX_RTMP_RECORD_AUDIO | + NGX_RTMP_RECORD_VIDEO }, + { ngx_string("audio"), NGX_RTMP_RECORD_AUDIO }, + { ngx_string("video"), NGX_RTMP_RECORD_VIDEO }, + { ngx_string("keyframes"), NGX_RTMP_RECORD_KEYFRAMES }, + { ngx_string("manual"), NGX_RTMP_RECORD_MANUAL }, + { ngx_null_string, 0 } +}; + + +static ngx_command_t ngx_rtmp_record_commands[] = { + + { ngx_string("record"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF| + NGX_RTMP_REC_CONF|NGX_CONF_1MORE, + ngx_conf_set_bitmask_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_record_app_conf_t, flags), + ngx_rtmp_record_mask }, + + { ngx_string("record_path"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF| + NGX_RTMP_REC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_record_app_conf_t, path), + NULL }, + + { ngx_string("record_suffix"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF| + NGX_RTMP_REC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_record_app_conf_t, suffix), + NULL }, + + { ngx_string("record_unique"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF| + NGX_RTMP_REC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_record_app_conf_t, unique), + NULL }, + + { ngx_string("record_append"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF| + NGX_RTMP_REC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_record_app_conf_t, append), + NULL }, + + { ngx_string("record_lock"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF| + NGX_RTMP_REC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_record_app_conf_t, lock_file), + NULL }, + + { ngx_string("record_max_size"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF| + NGX_RTMP_REC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_record_app_conf_t, max_size), + NULL }, + + { ngx_string("record_max_frames"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF| + NGX_RTMP_REC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_record_app_conf_t, max_frames), + NULL }, + + { ngx_string("record_interval"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF| + NGX_RTMP_REC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_record_app_conf_t, interval), + NULL }, + + { ngx_string("record_notify"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF| + NGX_RTMP_REC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_record_app_conf_t, notify), + NULL }, + + { ngx_string("recorder"), + NGX_RTMP_APP_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE1, + ngx_rtmp_record_recorder, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + + + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_rtmp_record_module_ctx = { + NULL, /* preconfiguration */ + ngx_rtmp_record_postconfiguration, /* postconfiguration */ + NULL, /* create main configuration */ + NULL, /* init main configuration */ + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + ngx_rtmp_record_create_app_conf, /* create app configuration */ + ngx_rtmp_record_merge_app_conf /* merge app configuration */ +}; + + +ngx_module_t ngx_rtmp_record_module = { + NGX_MODULE_V1, + &ngx_rtmp_record_module_ctx, /* module context */ + ngx_rtmp_record_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void * +ngx_rtmp_record_create_app_conf(ngx_conf_t *cf) +{ + ngx_rtmp_record_app_conf_t *racf; + + racf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_record_app_conf_t)); + + if (racf == NULL) { + return NULL; + } + + racf->max_size = NGX_CONF_UNSET_SIZE; + racf->max_frames = NGX_CONF_UNSET_SIZE; + racf->interval = NGX_CONF_UNSET_MSEC; + racf->unique = NGX_CONF_UNSET; + racf->append = NGX_CONF_UNSET; + racf->lock_file = NGX_CONF_UNSET; + racf->notify = NGX_CONF_UNSET; + racf->url = NGX_CONF_UNSET_PTR; + + if (ngx_array_init(&racf->rec, cf->pool, 1, sizeof(void *)) != NGX_OK) { + return NULL; + } + + return racf; +} + + +static char * +ngx_rtmp_record_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_rtmp_record_app_conf_t *prev = parent; + ngx_rtmp_record_app_conf_t *conf = child; + ngx_rtmp_record_app_conf_t **rracf; + + ngx_conf_merge_str_value(conf->path, prev->path, ""); + ngx_conf_merge_str_value(conf->suffix, prev->suffix, ".flv"); + ngx_conf_merge_size_value(conf->max_size, prev->max_size, 0); + ngx_conf_merge_size_value(conf->max_frames, prev->max_frames, 0); + ngx_conf_merge_value(conf->unique, prev->unique, 0); + ngx_conf_merge_value(conf->append, prev->append, 0); + ngx_conf_merge_value(conf->lock_file, prev->lock_file, 0); + ngx_conf_merge_value(conf->notify, prev->notify, 0); + ngx_conf_merge_msec_value(conf->interval, prev->interval, + (ngx_msec_t) NGX_CONF_UNSET); + ngx_conf_merge_bitmask_value(conf->flags, prev->flags, 0); + ngx_conf_merge_ptr_value(conf->url, prev->url, NULL); + + if (conf->flags) { + rracf = ngx_array_push(&conf->rec); + if (rracf == NULL) { + return NGX_CONF_ERROR; + } + + *rracf = conf; + } + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_rtmp_record_write_header(ngx_file_t *file) +{ + static u_char flv_header[] = { + 0x46, /* 'F' */ + 0x4c, /* 'L' */ + 0x56, /* 'V' */ + 0x01, /* version = 1 */ + 0x05, /* 00000 1 0 1 = has audio & video */ + 0x00, + 0x00, + 0x00, + 0x09, /* header size */ + 0x00, + 0x00, + 0x00, + 0x00 /* PreviousTagSize0 (not actually a header) */ + }; + + return ngx_write_file(file, flv_header, sizeof(flv_header), 0) == NGX_ERROR + ? NGX_ERROR + : NGX_OK; +} + + +static ngx_rtmp_record_rec_ctx_t * +ngx_rtmp_record_get_node_ctx(ngx_rtmp_session_t *s, ngx_uint_t n) +{ + ngx_rtmp_record_ctx_t *ctx; + ngx_rtmp_record_rec_ctx_t *rctx; + + if (ngx_rtmp_record_init(s) != NGX_OK) { + return NULL; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_record_module); + + if (n >= ctx->rec.nelts) { + return NULL; + } + + rctx = ctx->rec.elts; + + return &rctx[n]; +} + + +ngx_int_t +ngx_rtmp_record_open(ngx_rtmp_session_t *s, ngx_uint_t n, ngx_str_t *path) +{ + ngx_rtmp_record_rec_ctx_t *rctx; + ngx_int_t rc; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "record: #%ui manual open", n); + + rctx = ngx_rtmp_record_get_node_ctx(s, n); + + if (rctx == NULL) { + return NGX_ERROR; + } + + rc = ngx_rtmp_record_node_open(s, rctx); + if (rc != NGX_OK) { + return rc; + } + + if (path) { + ngx_rtmp_record_make_path(s, rctx, path); + } + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_record_close(ngx_rtmp_session_t *s, ngx_uint_t n, ngx_str_t *path) +{ + ngx_rtmp_record_rec_ctx_t *rctx; + ngx_int_t rc; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "record: #%ui manual close", n); + + rctx = ngx_rtmp_record_get_node_ctx(s, n); + + if (rctx == NULL) { + return NGX_ERROR; + } + + rc = ngx_rtmp_record_node_close(s, rctx); + if (rc != NGX_OK) { + return rc; + } + + if (path) { + ngx_rtmp_record_make_path(s, rctx, path); + } + + return NGX_OK; +} + + +ngx_uint_t +ngx_rtmp_record_find(ngx_rtmp_record_app_conf_t *racf, ngx_str_t *id) +{ + ngx_rtmp_record_app_conf_t **pracf, *rracf; + ngx_uint_t n; + + pracf = racf->rec.elts; + + for (n = 0; n < racf->rec.nelts; ++n, ++pracf) { + rracf = *pracf; + + if (rracf->id.len == id->len && + ngx_strncmp(rracf->id.data, id->data, id->len) == 0) + { + return n; + } + } + + return NGX_CONF_UNSET_UINT; +} + + +/* This funcion returns pointer to a static buffer */ +static void +ngx_rtmp_record_make_path(ngx_rtmp_session_t *s, + ngx_rtmp_record_rec_ctx_t *rctx, ngx_str_t *path) +{ + ngx_rtmp_record_ctx_t *ctx; + ngx_rtmp_record_app_conf_t *rracf; + u_char *p, *l; + struct tm tm; + + static u_char buf[NGX_TIME_T_LEN + 1]; + static u_char pbuf[NGX_MAX_PATH + 1]; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_record_module); + + rracf = rctx->conf; + + /* create file path */ + p = pbuf; + l = pbuf + sizeof(pbuf) - 1; + + p = ngx_cpymem(p, rracf->path.data, + ngx_min(rracf->path.len, (size_t)(l - p - 1))); + *p++ = '/'; + p = (u_char *)ngx_escape_uri(p, ctx->name, ngx_min(ngx_strlen(ctx->name), + (size_t)(l - p)), NGX_ESCAPE_URI_COMPONENT); + + /* append timestamp */ + if (rracf->unique) { + p = ngx_cpymem(p, buf, ngx_min(ngx_sprintf(buf, "-%T", + rctx->timestamp) - buf, l - p)); + } + + if (ngx_strchr(rracf->suffix.data, '%')) { + ngx_libc_localtime(rctx->timestamp, &tm); + p += strftime((char *) p, l - p, (char *) rracf->suffix.data, &tm); + } else { + p = ngx_cpymem(p, rracf->suffix.data, + ngx_min(rracf->suffix.len, (size_t)(l - p))); + } + + *p = 0; + path->data = pbuf; + path->len = p - pbuf; + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0, + "record: %V path: '%V'", &rracf->id, path); +} + + +static void +ngx_rtmp_record_notify_error(ngx_rtmp_session_t *s, + ngx_rtmp_record_rec_ctx_t *rctx) +{ + ngx_rtmp_record_app_conf_t *rracf = rctx->conf; + + rctx->failed = 1; + + if (!rracf->notify) { + return; + } + + ngx_rtmp_send_status(s, "NetStream.Record.Failed", "error", + rracf->id.data ? (char *) rracf->id.data : ""); +} + + +static ngx_int_t +ngx_rtmp_record_node_open(ngx_rtmp_session_t *s, + ngx_rtmp_record_rec_ctx_t *rctx) +{ + ngx_rtmp_record_app_conf_t *rracf; + ngx_err_t err; + ngx_str_t path; + ngx_int_t mode, create_mode; + u_char buf[8], *p; + off_t file_size; + uint32_t tag_size, mlen, timestamp; + + rracf = rctx->conf; + tag_size = 0; + + if (rctx->file.fd != NGX_INVALID_FILE) { + return NGX_AGAIN; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "record: %V opening", &rracf->id); + + ngx_memzero(rctx, sizeof(*rctx)); + rctx->conf = rracf; + rctx->last = *ngx_cached_time; + rctx->timestamp = ngx_cached_time->sec; + + ngx_rtmp_record_make_path(s, rctx, &path); + + mode = rracf->append ? NGX_FILE_RDWR : NGX_FILE_WRONLY; + create_mode = rracf->append ? NGX_FILE_CREATE_OR_OPEN : NGX_FILE_TRUNCATE; + + ngx_memzero(&rctx->file, sizeof(rctx->file)); + rctx->file.offset = 0; + rctx->file.log = s->log; + rctx->file.fd = ngx_open_file(path.data, mode, create_mode, + NGX_FILE_DEFAULT_ACCESS); + ngx_str_set(&rctx->file.name, "recorded"); + + if (rctx->file.fd == NGX_INVALID_FILE) { + err = ngx_errno; + + if (err != NGX_ENOENT) { + ngx_log_error(NGX_LOG_CRIT, s->log, err, + "record: %V failed to open file '%V'", + &rracf->id, &path); + } + + ngx_rtmp_record_notify_error(s, rctx); + + return NGX_OK; + } + +#if !(NGX_WIN32) + if (rracf->lock_file) { + err = ngx_lock_fd(rctx->file.fd); + if (err) { + ngx_log_error(NGX_LOG_CRIT, s->log, err, + "record: %V lock failed", &rracf->id); + } + } +#endif + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0, + "record: %V opened '%V'", &rracf->id, &path); + + if (rracf->notify) { + ngx_rtmp_send_status(s, "NetStream.Record.Start", "status", + rracf->id.data ? (char *) rracf->id.data : ""); + } + + if (rracf->append) { + + file_size = 0; + timestamp = 0; + +#if (NGX_WIN32) + { + LONG lo, hi; + + lo = 0; + hi = 0; + lo = SetFilePointer(rctx->file.fd, lo, &hi, FILE_END); + file_size = (lo == INVALID_SET_FILE_POINTER ? + (off_t) -1 : (off_t) hi << 32 | (off_t) lo); + } +#else + file_size = lseek(rctx->file.fd, 0, SEEK_END); +#endif + if (file_size == (off_t) -1) { + ngx_log_error(NGX_LOG_CRIT, s->log, ngx_errno, + "record: %V seek failed", &rracf->id); + goto done; + } + + if (file_size < 4) { + goto done; + } + + if (ngx_read_file(&rctx->file, buf, 4, file_size - 4) != 4) { + ngx_log_error(NGX_LOG_CRIT, s->log, ngx_errno, + "record: %V tag size read failed", &rracf->id); + goto done; + } + + p = (u_char *) &tag_size; + p[0] = buf[3]; + p[1] = buf[2]; + p[2] = buf[1]; + p[3] = buf[0]; + + if (tag_size == 0 || tag_size + 4 > file_size) { + file_size = 0; + goto done; + } + + if (ngx_read_file(&rctx->file, buf, 8, file_size - tag_size - 4) != 8) + { + ngx_log_error(NGX_LOG_CRIT, s->log, ngx_errno, + "record: %V tag read failed", &rracf->id); + goto done; + } + + p = (u_char *) &mlen; + p[0] = buf[3]; + p[1] = buf[2]; + p[2] = buf[1]; + p[3] = 0; + + if (tag_size != mlen + 11) { + ngx_log_error(NGX_LOG_CRIT, s->log, ngx_errno, + "record: %V tag size mismatch: " + "tag_size=%uD, mlen=%uD", &rracf->id, tag_size, mlen); + goto done; + } + + p = (u_char *) ×tamp; + p[3] = buf[7]; + p[0] = buf[6]; + p[1] = buf[5]; + p[2] = buf[4]; + +done: + rctx->file.offset = file_size; + rctx->time_shift = timestamp; + + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->log, 0, + "record: append offset=%O, time=%uD, tag_size=%uD", + file_size, timestamp, tag_size); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_record_init(ngx_rtmp_session_t *s) +{ + ngx_rtmp_record_app_conf_t *racf, **rracf; + ngx_rtmp_record_rec_ctx_t *rctx; + ngx_rtmp_record_ctx_t *ctx; + ngx_uint_t n; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_record_module); + + if (ctx) { + return NGX_OK; + } + + racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_record_module); + + if (racf == NULL || racf->rec.nelts == 0) { + return NGX_OK; + } + + ctx = ngx_pcalloc(s->connection->pool, sizeof(ngx_rtmp_record_ctx_t)); + + if (ctx == NULL) { + return NGX_ERROR; + } + + ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_record_module); + + if (ngx_array_init(&ctx->rec, s->connection->pool, racf->rec.nelts, + sizeof(ngx_rtmp_record_rec_ctx_t)) + != NGX_OK) + { + return NGX_ERROR; + } + + rracf = racf->rec.elts; + + rctx = ngx_array_push_n(&ctx->rec, racf->rec.nelts); + + if (rctx == NULL) { + return NGX_ERROR; + } + + for (n = 0; n < racf->rec.nelts; ++n, ++rracf, ++rctx) { + ngx_memzero(rctx, sizeof(*rctx)); + + rctx->conf = *rracf; + rctx->file.fd = NGX_INVALID_FILE; + } + + return NGX_OK; +} + + +static void +ngx_rtmp_record_start(ngx_rtmp_session_t *s) +{ + ngx_rtmp_record_app_conf_t *racf; + ngx_rtmp_record_rec_ctx_t *rctx; + ngx_rtmp_record_ctx_t *ctx; + ngx_uint_t n; + + racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_record_module); + if (racf == NULL || racf->rec.nelts == 0) { + return; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_record_module); + if (ctx == NULL) { + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0, + "record: start"); + + rctx = ctx->rec.elts; + for (n = 0; n < ctx->rec.nelts; ++n, ++rctx) { + if (rctx->conf->flags & (NGX_RTMP_RECORD_OFF|NGX_RTMP_RECORD_MANUAL)) { + continue; + } + ngx_rtmp_record_node_open(s, rctx); + } +} + + +static void +ngx_rtmp_record_stop(ngx_rtmp_session_t *s) +{ + ngx_rtmp_record_app_conf_t *racf; + ngx_rtmp_record_rec_ctx_t *rctx; + ngx_rtmp_record_ctx_t *ctx; + ngx_uint_t n; + + racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_record_module); + if (racf == NULL || racf->rec.nelts == 0) { + return; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_record_module); + if (ctx == NULL) { + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0, + "record: stop"); + + rctx = ctx->rec.elts; + for (n = 0; n < ctx->rec.nelts; ++n, ++rctx) { + ngx_rtmp_record_node_close(s, rctx); + } +} + + +static ngx_int_t +ngx_rtmp_record_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) +{ + ngx_rtmp_record_app_conf_t *racf; + ngx_rtmp_record_ctx_t *ctx; + u_char *p; + + if (s->interprocess) { + goto next; + } + + racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_record_module); + + if (racf == NULL || racf->rec.nelts == 0) { + goto next; + } + + if (ngx_rtmp_record_init(s) != NGX_OK) { + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "record: publish %ui nodes", + racf->rec.nelts); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_record_module); + + ngx_memcpy(ctx->name, v->name, sizeof(ctx->name)); + ngx_memcpy(ctx->args, v->args, sizeof(ctx->args)); + + /* terminate name on /../ */ + for (p = ctx->name; *p; ++p) { + if (ngx_path_separator(p[0]) && + p[1] == '.' && p[2] == '.' && + ngx_path_separator(p[3])) + { + *p = 0; + break; + } + } + + ngx_rtmp_record_start(s); + +next: + return next_publish(s, v); +} + + +static ngx_int_t +ngx_rtmp_record_stream_begin(ngx_rtmp_session_t *s, ngx_rtmp_stream_begin_t *v) +{ + if (s->interprocess) { + goto next; + } + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0, + "record: stream_begin"); + + ngx_rtmp_record_start(s); + +next: + return next_stream_begin(s, v); +} + + +static ngx_int_t +ngx_rtmp_record_stream_eof(ngx_rtmp_session_t *s, ngx_rtmp_stream_begin_t *v) +{ + if (s->interprocess) { + goto next; + } + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0, + "record: stream_eof"); + + ngx_rtmp_record_stop(s); + +next: + return next_stream_eof(s, v); +} + + +static ngx_int_t +ngx_rtmp_record_node_close(ngx_rtmp_session_t *s, + ngx_rtmp_record_rec_ctx_t *rctx) +{ + ngx_rtmp_record_app_conf_t *rracf; + ngx_err_t err; + void **app_conf; + ngx_int_t rc; + ngx_rtmp_record_done_t v; + u_char av; + + rracf = rctx->conf; + + if (rctx->file.fd == NGX_INVALID_FILE) { + return NGX_AGAIN; + } + + if (rctx->initialized) { + av = 0; + + if (rctx->video) { + av |= 0x01; + } + + if (rctx->audio) { + av |= 0x04; + } + + if (ngx_write_file(&rctx->file, &av, 1, 4) == NGX_ERROR) { + ngx_log_error(NGX_LOG_CRIT, s->log, ngx_errno, + "record: %V error writing av mask", &rracf->id); + } + } + + if (ngx_close_file(rctx->file.fd) == NGX_FILE_ERROR) { + err = ngx_errno; + ngx_log_error(NGX_LOG_CRIT, s->log, err, + "record: %V error closing file", &rracf->id); + + ngx_rtmp_record_notify_error(s, rctx); + } + + rctx->file.fd = NGX_INVALID_FILE; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "record: %V closed", &rracf->id); + + if (rracf->notify) { + ngx_rtmp_send_status(s, "NetStream.Record.Stop", "status", + rracf->id.data ? (char *) rracf->id.data : ""); + } + + app_conf = s->app_conf; + + if (rracf->rec_conf) { + s->app_conf = rracf->rec_conf; + } + + v.recorder = rracf->id; + ngx_rtmp_record_make_path(s, rctx, &v.path); + + rc = ngx_rtmp_record_done(s, &v); + + s->app_conf = app_conf; + + return rc; +} + + +static ngx_int_t +ngx_rtmp_record_close_stream(ngx_rtmp_session_t *s, + ngx_rtmp_close_stream_t *v) +{ + if (s->interprocess) { + goto next; + } + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0, + "record: close_stream"); + + ngx_rtmp_record_stop(s); + +next: + return next_close_stream(s, v); +} + + +static ngx_int_t +ngx_rtmp_record_write_frame(ngx_rtmp_session_t *s, + ngx_rtmp_record_rec_ctx_t *rctx, + ngx_rtmp_header_t *h, ngx_chain_t *in, + ngx_int_t inc_nframes) +{ + u_char hdr[11], *p, *ph; + uint32_t timestamp, tag_size; + ngx_rtmp_record_app_conf_t *rracf; + + rracf = rctx->conf; + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0, + "record: %V frame: mlen=%uD", + &rracf->id, h->mlen); + + if (h->type == NGX_RTMP_MSG_VIDEO) { + rctx->video = 1; + } else { + rctx->audio = 1; + } + + timestamp = h->timestamp - rctx->epoch; + + if ((int32_t) timestamp < 0) { + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0, + "record: %V cut timestamp=%D", &rracf->id, timestamp); + + timestamp = 0; + } + + /* write tag header */ + ph = hdr; + + *ph++ = (u_char)h->type; + + p = (u_char*)&h->mlen; + *ph++ = p[2]; + *ph++ = p[1]; + *ph++ = p[0]; + + p = (u_char*)×tamp; + *ph++ = p[2]; + *ph++ = p[1]; + *ph++ = p[0]; + *ph++ = p[3]; + + *ph++ = 0; + *ph++ = 0; + *ph++ = 0; + + tag_size = (ph - hdr) + h->mlen; + + if (ngx_write_file(&rctx->file, hdr, ph - hdr, rctx->file.offset) + == NGX_ERROR) + { + ngx_rtmp_record_notify_error(s, rctx); + + ngx_close_file(rctx->file.fd); + + return NGX_ERROR; + } + + /* write tag body + * FIXME: NGINX + * ngx_write_chain seems to fit best + * but it suffers from uncontrollable + * allocations. + * we're left with plain writing */ + for(; in; in = in->next) { + if (in->buf->pos == in->buf->last) { + continue; + } + + if (ngx_write_file(&rctx->file, in->buf->pos, in->buf->last + - in->buf->pos, rctx->file.offset) + == NGX_ERROR) + { + return NGX_ERROR; + } + } + + /* write tag size */ + ph = hdr; + p = (u_char*)&tag_size; + + *ph++ = p[3]; + *ph++ = p[2]; + *ph++ = p[1]; + *ph++ = p[0]; + + if (ngx_write_file(&rctx->file, hdr, ph - hdr, + rctx->file.offset) + == NGX_ERROR) + { + return NGX_ERROR; + } + + rctx->nframes += inc_nframes; + + /* watch max size */ + if ((rracf->max_size && rctx->file.offset >= (ngx_int_t) rracf->max_size) || + (rracf->max_frames && rctx->nframes >= rracf->max_frames)) + { + ngx_rtmp_record_node_close(s, rctx); + } + + return NGX_OK; +} + + +static size_t +ngx_rtmp_record_get_chain_mlen(ngx_chain_t *in) +{ + size_t ret; + + for (ret = 0; in; in = in->next) { + ret += (in->buf->last - in->buf->pos); + } + + return ret; +} + + +static ngx_int_t +ngx_rtmp_record_av(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_rtmp_record_ctx_t *ctx; + ngx_rtmp_record_rec_ctx_t *rctx; + ngx_uint_t n; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_record_module); + + if (ctx == NULL || s->pause) { + return NGX_OK; + } + + rctx = ctx->rec.elts; + + for (n = 0; n < ctx->rec.nelts; ++n, ++rctx) { + ngx_rtmp_record_node_av(s, rctx, h, in); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_record_node_av(ngx_rtmp_session_t *s, ngx_rtmp_record_rec_ctx_t *rctx, + ngx_rtmp_header_t *h, ngx_chain_t *in) +{ + ngx_time_t next; + ngx_rtmp_header_t ch; + ngx_rtmp_codec_ctx_t *codec_ctx; + ngx_int_t keyframe, brkframe; + ngx_rtmp_record_app_conf_t *rracf; + + rracf = rctx->conf; + + if (rracf->flags & NGX_RTMP_RECORD_OFF) { + ngx_rtmp_record_node_close(s, rctx); + return NGX_OK; + } + + keyframe = (h->type == NGX_RTMP_MSG_VIDEO) + ? (ngx_rtmp_get_video_frame_type(in) == NGX_RTMP_VIDEO_KEY_FRAME) + : 0; + + brkframe = (h->type == NGX_RTMP_MSG_VIDEO) + ? keyframe + : (rracf->flags & NGX_RTMP_RECORD_VIDEO) == 0; + + if (brkframe && (rracf->flags & NGX_RTMP_RECORD_MANUAL) == 0) { + + if (rracf->interval != (ngx_msec_t) NGX_CONF_UNSET) { + + next = rctx->last; + next.msec += rracf->interval; + next.sec += (next.msec / 1000); + next.msec %= 1000; + + if (ngx_cached_time->sec > next.sec || + (ngx_cached_time->sec == next.sec && + ngx_cached_time->msec > next.msec)) + { + ngx_rtmp_record_node_close(s, rctx); + ngx_rtmp_record_node_open(s, rctx); + } + + } else if (!rctx->failed) { + ngx_rtmp_record_node_open(s, rctx); + } + } + + if ((rracf->flags & NGX_RTMP_RECORD_MANUAL) && + !brkframe && rctx->nframes == 0) + { + return NGX_OK; + } + + if (rctx->file.fd == NGX_INVALID_FILE) { + return NGX_OK; + } + + if (h->type == NGX_RTMP_MSG_AUDIO && + (rracf->flags & NGX_RTMP_RECORD_AUDIO) == 0) + { + return NGX_OK; + } + + if (h->type == NGX_RTMP_MSG_VIDEO && + (rracf->flags & NGX_RTMP_RECORD_VIDEO) == 0 && + ((rracf->flags & NGX_RTMP_RECORD_KEYFRAMES) == 0 || !keyframe)) + { + return NGX_OK; + } + + if (!rctx->initialized) { + + rctx->initialized = 1; + rctx->epoch = h->timestamp - rctx->time_shift; + + if (rctx->file.offset == 0 && + ngx_rtmp_record_write_header(&rctx->file) != NGX_OK) + { + ngx_rtmp_record_node_close(s, rctx); + return NGX_OK; + } + } + + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + if (codec_ctx) { + ch = *h; + + /* AAC header */ + if (!rctx->aac_header_sent && codec_ctx->aac_header && + (rracf->flags & NGX_RTMP_RECORD_AUDIO)) + { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "record: %V writing AAC header", &rracf->id); + + ch.type = NGX_RTMP_MSG_AUDIO; + ch.mlen = + ngx_rtmp_record_get_chain_mlen(codec_ctx->aac_header->chain); + + if (ngx_rtmp_record_write_frame(s, rctx, &ch, + codec_ctx->aac_header->chain, 0) + != NGX_OK) + { + return NGX_OK; + } + + rctx->aac_header_sent = 1; + } + + /* AVC header */ + if (!rctx->avc_header_sent && codec_ctx->avc_header && + (rracf->flags & (NGX_RTMP_RECORD_VIDEO| + NGX_RTMP_RECORD_KEYFRAMES))) + { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "record: %V writing AVC header", &rracf->id); + + ch.type = NGX_RTMP_MSG_VIDEO; + ch.mlen = + ngx_rtmp_record_get_chain_mlen(codec_ctx->avc_header->chain); + + if (ngx_rtmp_record_write_frame(s, rctx, &ch, + codec_ctx->avc_header->chain, 0) + != NGX_OK) + { + return NGX_OK; + } + + rctx->avc_header_sent = 1; + } + } + + if (h->type == NGX_RTMP_MSG_VIDEO) { + if (codec_ctx && codec_ctx->video_codec_id == NGX_RTMP_VIDEO_H264 && + !rctx->avc_header_sent) + { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "record: %V skipping until H264 header", &rracf->id); + return NGX_OK; + } + + if (ngx_rtmp_get_video_frame_type(in) == NGX_RTMP_VIDEO_KEY_FRAME && + ((codec_ctx && codec_ctx->video_codec_id != NGX_RTMP_VIDEO_H264) || + !ngx_rtmp_is_codec_header(in))) + { + rctx->video_key_sent = 1; + } + + if (!rctx->video_key_sent) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "record: %V skipping until keyframe", &rracf->id); + return NGX_OK; + } + + } else { + if (codec_ctx && codec_ctx->audio_codec_id == NGX_RTMP_AUDIO_AAC && + !rctx->aac_header_sent) + { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "record: %V skipping until AAC header", &rracf->id); + return NGX_OK; + } + } + + return ngx_rtmp_record_write_frame(s, rctx, h, in, 1); +} + + +static ngx_int_t +ngx_rtmp_record_done_init(ngx_rtmp_session_t *s, ngx_rtmp_record_done_t *v) +{ + return NGX_OK; +} + + +static char * +ngx_rtmp_record_recorder(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + char *rv; + ngx_int_t i; + ngx_str_t *value; + ngx_conf_t save; + ngx_module_t **modules; + ngx_rtmp_module_t *module; + ngx_rtmp_core_app_conf_t *cacf, **pcacf, *rcacf; + ngx_rtmp_record_app_conf_t *racf, **pracf, *rracf; + ngx_rtmp_conf_ctx_t *ctx, *pctx; + + value = cf->args->elts; + + cacf = ngx_rtmp_conf_get_module_app_conf(cf, ngx_rtmp_core_module); + + racf = ngx_rtmp_conf_get_module_app_conf(cf, ngx_rtmp_record_module); + + ctx = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_conf_ctx_t)); + if (ctx == NULL) { + return NGX_CONF_ERROR; + } + + pctx = cf->ctx; + + ctx->main_conf = pctx->main_conf; + ctx->srv_conf = pctx->srv_conf; + + ctx->app_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module); + if (ctx->app_conf == NULL) { + return NGX_CONF_ERROR; + } + +#if (nginx_version >= 1009011) + modules = cf->cycle->modules; +#else + modules = ngx_modules; +#endif + + for (i = 0; modules[i]; i++) { + if (modules[i]->type != NGX_RTMP_MODULE) { + continue; + } + + module = modules[i]->ctx; + + if (module->create_app_conf) { + ctx->app_conf[modules[i]->ctx_index] = module->create_app_conf(cf); + if (ctx->app_conf[modules[i]->ctx_index] == NULL) { + return NGX_CONF_ERROR; + } + } + } + + /* add to sub-applications */ + rcacf = ctx->app_conf[ngx_rtmp_core_module.ctx_index]; + rcacf->app_conf = ctx->app_conf; + pcacf = ngx_array_push(&cacf->applications); + if (pcacf == NULL) { + return NGX_CONF_ERROR; + } + *pcacf = rcacf; + + /* add to recorders */ + rracf = ctx->app_conf[ngx_rtmp_record_module.ctx_index]; + rracf->rec_conf = ctx->app_conf; + pracf = ngx_array_push(&racf->rec); + if (pracf == NULL) { + return NGX_CONF_ERROR; + } + *pracf = rracf; + + rracf->id = value[1]; + + + save = *cf; + cf->ctx = ctx; + cf->cmd_type = NGX_RTMP_REC_CONF; + + rv = ngx_conf_parse(cf, NULL); + *cf= save; + + return rv; +} + + +static ngx_int_t +ngx_rtmp_record_postconfiguration(ngx_conf_t *cf) +{ + ngx_rtmp_core_main_conf_t *cmcf; + ngx_rtmp_handler_pt *h; + + ngx_rtmp_record_done = ngx_rtmp_record_done_init; + + cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); + + h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AUDIO]); + *h = ngx_rtmp_record_av; + + h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_VIDEO]); + *h = ngx_rtmp_record_av; + + next_publish = ngx_rtmp_publish; + ngx_rtmp_publish = ngx_rtmp_record_publish; + + next_close_stream = ngx_rtmp_close_stream; + ngx_rtmp_close_stream = ngx_rtmp_record_close_stream; + + next_stream_begin = ngx_rtmp_stream_begin; + ngx_rtmp_stream_begin = ngx_rtmp_record_stream_begin; + + next_stream_eof = ngx_rtmp_stream_eof; + ngx_rtmp_stream_eof = ngx_rtmp_record_stream_eof; + + return NGX_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_record_module.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_record_module.h new file mode 100644 index 0000000000..6450dcbf9e --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_record_module.h @@ -0,0 +1,96 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#ifndef _NGX_RTMP_RECORD_H_INCLUDED_ +#define _NGX_RTMP_RECORD_H_INCLUDED_ + + +#include +#include +#include "ngx_rtmp.h" + + +#define NGX_RTMP_RECORD_OFF 0x01 +#define NGX_RTMP_RECORD_AUDIO 0x02 +#define NGX_RTMP_RECORD_VIDEO 0x04 +#define NGX_RTMP_RECORD_KEYFRAMES 0x08 +#define NGX_RTMP_RECORD_MANUAL 0x10 + + +typedef struct { + ngx_str_t id; + ngx_uint_t flags; + ngx_str_t path; + size_t max_size; + size_t max_frames; + ngx_msec_t interval; + ngx_str_t suffix; + ngx_flag_t unique; + ngx_flag_t append; + ngx_flag_t lock_file; + ngx_flag_t notify; + ngx_url_t *url; + + void **rec_conf; + ngx_array_t rec; /* ngx_rtmp_record_app_conf_t * */ +} ngx_rtmp_record_app_conf_t; + + +typedef struct { + ngx_rtmp_record_app_conf_t *conf; + ngx_file_t file; + ngx_uint_t nframes; + uint32_t epoch, time_shift; + ngx_time_t last; + time_t timestamp; + unsigned failed:1; + unsigned initialized:1; + unsigned aac_header_sent:1; + unsigned avc_header_sent:1; + unsigned video_key_sent:1; + unsigned audio:1; + unsigned video:1; +} ngx_rtmp_record_rec_ctx_t; + + +typedef struct { + ngx_array_t rec; /* ngx_rtmp_record_rec_ctx_t */ + u_char name[NGX_RTMP_MAX_NAME]; + u_char args[NGX_RTMP_MAX_ARGS]; +} ngx_rtmp_record_ctx_t; + + +ngx_uint_t ngx_rtmp_record_find(ngx_rtmp_record_app_conf_t *racf, + ngx_str_t *id); + + +/* Manual recording control, + * 'n' is record node index in config array. + * Note: these functions allocate path in static buffer */ + +ngx_int_t ngx_rtmp_record_open(ngx_rtmp_session_t *s, ngx_uint_t n, + ngx_str_t *path); +ngx_int_t ngx_rtmp_record_close(ngx_rtmp_session_t *s, ngx_uint_t n, + ngx_str_t *path); + + +typedef struct { + ngx_str_t recorder; + ngx_str_t path; +} ngx_rtmp_record_done_t; + + +typedef ngx_int_t (*ngx_rtmp_record_done_pt)(ngx_rtmp_session_t *s, + ngx_rtmp_record_done_t *v); + + +extern ngx_rtmp_record_done_pt ngx_rtmp_record_done; + + +extern ngx_module_t ngx_rtmp_record_module; + + +#endif /* _NGX_RTMP_RECORD_H_INCLUDED_ */ diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_send.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_send.c new file mode 100644 index 0000000000..cc54afc692 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_send.c @@ -0,0 +1,699 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp.h" +#include "ngx_rtmp_amf.h" +#include "ngx_rtmp_streams.h" +#include "ngx_rbuf.h" + + +#define NGX_RTMP_USER_START(s, tp) \ + ngx_chain_t *__l; \ + ngx_buf_t *__b; \ + ngx_rtmp_core_srv_conf_t *__cscf; \ + ngx_rtmp_frame_t *__f; \ + \ + __cscf = ngx_rtmp_get_module_srv_conf( \ + s, ngx_rtmp_core_module); \ + \ + __f = ngx_rtmp_shared_alloc_frame(__cscf->chunk_size, NULL, 1); \ + __f->hdr.type = tp; \ + __f->hdr.csid = 2; \ + __l = __f->chain; \ + if (__l == NULL) { \ + return NULL; \ + } \ + __b = __l->buf; + +#define NGX_RTMP_UCTL_START(s, type, utype) \ + NGX_RTMP_USER_START(s, type); \ + *(__b->last++) = (u_char)((utype) >> 8); \ + *(__b->last++) = (u_char)(utype); + +#define NGX_RTMP_USER_OUT1(v) \ + *(__b->last++) = ((u_char*)&v)[0]; + +#define NGX_RTMP_USER_OUT4(v) \ + *(__b->last++) = ((u_char*)&v)[3]; \ + *(__b->last++) = ((u_char*)&v)[2]; \ + *(__b->last++) = ((u_char*)&v)[1]; \ + *(__b->last++) = ((u_char*)&v)[0]; + +#define NGX_RTMP_USER_END(s) \ + return __f; + + +static ngx_int_t +ngx_rtmp_send_shared_packet(ngx_rtmp_session_t *s, ngx_rtmp_frame_t *frame) +{ + ngx_int_t rc; + + if (frame == NULL) { + return NGX_ERROR; + } + + rc = ngx_rtmp_send_message(s, frame, 0); + + ngx_rtmp_shared_free_frame(frame); + + return rc; +} + + +/* Protocol control messages */ + +ngx_rtmp_frame_t * +ngx_rtmp_create_chunk_size(ngx_rtmp_session_t *s, uint32_t chunk_size) +{ + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "chunk_size=%uD", chunk_size); + + { + NGX_RTMP_USER_START(s, NGX_RTMP_MSG_CHUNK_SIZE); + + NGX_RTMP_USER_OUT4(chunk_size); + + NGX_RTMP_USER_END(s); + } +} + + +ngx_int_t +ngx_rtmp_send_chunk_size(ngx_rtmp_session_t *s, uint32_t chunk_size) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_chunk_size(s, chunk_size)); +} + + +ngx_rtmp_frame_t * +ngx_rtmp_create_abort(ngx_rtmp_session_t *s, uint32_t csid) +{ + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "create: abort csid=%uD", csid); + + { + NGX_RTMP_USER_START(s, NGX_RTMP_MSG_CHUNK_SIZE); + + NGX_RTMP_USER_OUT4(csid); + + NGX_RTMP_USER_END(s); + } +} + + +ngx_int_t +ngx_rtmp_send_abort(ngx_rtmp_session_t *s, uint32_t csid) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_abort(s, csid)); +} + + +ngx_rtmp_frame_t * +ngx_rtmp_create_ack(ngx_rtmp_session_t *s, uint32_t seq) +{ + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "create: ack seq=%uD", seq); + + { + NGX_RTMP_USER_START(s, NGX_RTMP_MSG_ACK); + + NGX_RTMP_USER_OUT4(seq); + + NGX_RTMP_USER_END(s); + } +} + + +ngx_int_t +ngx_rtmp_send_ack(ngx_rtmp_session_t *s, uint32_t seq) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_ack(s, seq)); +} + + +ngx_rtmp_frame_t * +ngx_rtmp_create_ack_size(ngx_rtmp_session_t *s, uint32_t ack_size) +{ + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "create: ack_size=%uD", ack_size); + + { + NGX_RTMP_USER_START(s, NGX_RTMP_MSG_ACK_SIZE); + + NGX_RTMP_USER_OUT4(ack_size); + + NGX_RTMP_USER_END(s); + } +} + + +ngx_int_t +ngx_rtmp_send_ack_size(ngx_rtmp_session_t *s, uint32_t ack_size) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_ack_size(s, ack_size)); +} + + +ngx_rtmp_frame_t * +ngx_rtmp_create_bandwidth(ngx_rtmp_session_t *s, uint32_t ack_size, + uint8_t limit_type) +{ + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0, + "create: bandwidth ack_size=%uD limit=%d", + ack_size, (int)limit_type); + + { + NGX_RTMP_USER_START(s, NGX_RTMP_MSG_BANDWIDTH); + + NGX_RTMP_USER_OUT4(ack_size); + NGX_RTMP_USER_OUT1(limit_type); + + NGX_RTMP_USER_END(s); + } +} + + +ngx_int_t +ngx_rtmp_send_bandwidth(ngx_rtmp_session_t *s, uint32_t ack_size, + uint8_t limit_type) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_bandwidth(s, ack_size, limit_type)); +} + + +/* User control messages */ + +ngx_rtmp_frame_t * +ngx_rtmp_create_stream_begin(ngx_rtmp_session_t *s, uint32_t msid) +{ + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "create: stream_begin msid=%uD", msid); + + { + NGX_RTMP_UCTL_START(s, NGX_RTMP_MSG_USER, NGX_RTMP_USER_STREAM_BEGIN); + + NGX_RTMP_USER_OUT4(msid); + + NGX_RTMP_USER_END(s); + } +} + + +ngx_int_t +ngx_rtmp_send_stream_begin(ngx_rtmp_session_t *s, uint32_t msid) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_stream_begin(s, msid)); +} + + +ngx_rtmp_frame_t * +ngx_rtmp_create_stream_eof(ngx_rtmp_session_t *s, uint32_t msid) +{ + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "create: stream_end msid=%uD", msid); + + { + NGX_RTMP_UCTL_START(s, NGX_RTMP_MSG_USER, NGX_RTMP_USER_STREAM_EOF); + + NGX_RTMP_USER_OUT4(msid); + + NGX_RTMP_USER_END(s); + } +} + + +ngx_int_t +ngx_rtmp_send_stream_eof(ngx_rtmp_session_t *s, uint32_t msid) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_stream_eof(s, msid)); +} + + +ngx_rtmp_frame_t * +ngx_rtmp_create_stream_dry(ngx_rtmp_session_t *s, uint32_t msid) +{ + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "create: stream_dry msid=%uD", msid); + + { + NGX_RTMP_UCTL_START(s, NGX_RTMP_MSG_USER, NGX_RTMP_USER_STREAM_DRY); + + NGX_RTMP_USER_OUT4(msid); + + NGX_RTMP_USER_END(s); + } +} + + +ngx_int_t +ngx_rtmp_send_stream_dry(ngx_rtmp_session_t *s, uint32_t msid) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_stream_dry(s, msid)); +} + + +ngx_rtmp_frame_t * +ngx_rtmp_create_set_buflen(ngx_rtmp_session_t *s, uint32_t msid, + uint32_t buflen_msec) +{ + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0, + "create: set_buflen msid=%uD buflen=%uD", + msid, buflen_msec); + + { + NGX_RTMP_UCTL_START(s, NGX_RTMP_MSG_USER, NGX_RTMP_USER_SET_BUFLEN); + + NGX_RTMP_USER_OUT4(msid); + NGX_RTMP_USER_OUT4(buflen_msec); + + NGX_RTMP_USER_END(s); + } +} + + +ngx_int_t +ngx_rtmp_send_set_buflen(ngx_rtmp_session_t *s, uint32_t msid, + uint32_t buflen_msec) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_set_buflen(s, msid, buflen_msec)); +} + + +ngx_rtmp_frame_t * +ngx_rtmp_create_recorded(ngx_rtmp_session_t *s, uint32_t msid) +{ + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "create: recorded msid=%uD", msid); + + { + NGX_RTMP_UCTL_START(s, NGX_RTMP_MSG_USER, NGX_RTMP_USER_RECORDED); + + NGX_RTMP_USER_OUT4(msid); + + NGX_RTMP_USER_END(s); + } +} + + +ngx_int_t +ngx_rtmp_send_recorded(ngx_rtmp_session_t *s, uint32_t msid) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_recorded(s, msid)); +} + + +ngx_rtmp_frame_t * +ngx_rtmp_create_ping_request(ngx_rtmp_session_t *s, uint32_t timestamp) +{ + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "create: ping_request timestamp=%uD", timestamp); + + { + NGX_RTMP_UCTL_START(s, NGX_RTMP_MSG_USER, NGX_RTMP_USER_PING_REQUEST); + + NGX_RTMP_USER_OUT4(timestamp); + + NGX_RTMP_USER_END(s); + } +} + + +ngx_int_t +ngx_rtmp_send_ping_request(ngx_rtmp_session_t *s, uint32_t timestamp) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_ping_request(s, timestamp)); +} + + +ngx_rtmp_frame_t * +ngx_rtmp_create_ping_response(ngx_rtmp_session_t *s, uint32_t timestamp) +{ + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "create: ping_response timestamp=%uD", timestamp); + + { + NGX_RTMP_UCTL_START(s, NGX_RTMP_MSG_USER, NGX_RTMP_USER_PING_RESPONSE); + + NGX_RTMP_USER_OUT4(timestamp); + + NGX_RTMP_USER_END(s); + } +} + + +ngx_int_t +ngx_rtmp_send_ping_response(ngx_rtmp_session_t *s, uint32_t timestamp) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_ping_response(s, timestamp)); +} + + +static ngx_chain_t * +ngx_rtmp_alloc_amf_buf(void *arg) +{ + return ngx_get_chainbuf(((ngx_rtmp_core_srv_conf_t *)arg)->chunk_size, 1); +} + + +/* AMF sender */ + +/* NOTE: this function does not free shared bufs on error */ +ngx_int_t +ngx_rtmp_append_amf(ngx_rtmp_session_t *s, + ngx_chain_t **first, ngx_chain_t **last, + ngx_rtmp_amf_elt_t *elts, size_t nelts) +{ + ngx_rtmp_amf_ctx_t act; + ngx_rtmp_core_srv_conf_t *cscf; + ngx_int_t rc; + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + memset(&act, 0, sizeof(act)); + act.arg = cscf; + act.alloc = ngx_rtmp_alloc_amf_buf; + act.log = s->log; + + if (first) { + act.first = *first; + } + + if (last) { + act.link = *last; + } + + rc = ngx_rtmp_amf_write(&act, elts, nelts); + + if (first) { + *first = act.first; + } + + if (last) { + *last = act.link; + } + + return rc; +} + + +ngx_rtmp_frame_t * +ngx_rtmp_create_amf(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_rtmp_amf_elt_t *elts, size_t nelts) +{ + ngx_int_t rc; + ngx_rtmp_core_srv_conf_t *cscf; + ngx_rtmp_frame_t *frame; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, + "create: amf nelts=%ui", nelts); + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + frame = ngx_rtmp_shared_alloc_frame(cscf->chunk_size, NULL, 1); + frame->hdr = *h; + + rc = ngx_rtmp_append_amf(s, &frame->chain, &frame->chain, elts, nelts); + + if (rc != NGX_OK) { + ngx_rtmp_shared_free_frame(frame); + return NULL; + } + + return frame; +} + + +ngx_int_t +ngx_rtmp_send_amf(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_rtmp_amf_elt_t *elts, size_t nelts) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_amf(s, h, elts, nelts)); +} + + +ngx_rtmp_frame_t * +ngx_rtmp_create_error(ngx_rtmp_session_t *s, char *code, char* level, + char *desc) +{ + ngx_rtmp_header_t h; + static double trans; + + static ngx_rtmp_amf_elt_t out_inf[] = { + + { NGX_RTMP_AMF_STRING, + ngx_string("level"), + NULL, 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_string("code"), + NULL, 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_string("description"), + NULL, 0 }, + }; + + static ngx_rtmp_amf_elt_t out_elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + "_error", 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &trans, 0 }, + + { NGX_RTMP_AMF_NULL, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_OBJECT, + ngx_null_string, + out_inf, + sizeof(out_inf) }, + }; + + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->log, 0, + "create: error code='%s' level='%s' desc='%s'", + code, level, desc); + + out_inf[0].data = level; + out_inf[1].data = code; + out_inf[2].data = desc; + + memset(&h, 0, sizeof(h)); + + h.type = NGX_RTMP_MSG_AMF_CMD; + h.csid = NGX_RTMP_CSID_AMF; + h.msid = NGX_RTMP_MSID; + + return ngx_rtmp_create_amf(s, &h, out_elts, + sizeof(out_elts) / sizeof(out_elts[0])); +} + + +ngx_int_t +ngx_rtmp_send_error(ngx_rtmp_session_t *s, char *code, char* level, char *desc) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_error(s, code, level, desc)); +} + + +ngx_rtmp_frame_t * +ngx_rtmp_create_status(ngx_rtmp_session_t *s, char *code, char* level, + char *desc) +{ + ngx_rtmp_header_t h; + static double trans; + + static ngx_rtmp_amf_elt_t out_inf[] = { + + { NGX_RTMP_AMF_STRING, + ngx_string("level"), + NULL, 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_string("code"), + NULL, 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_string("description"), + NULL, 0 }, + }; + + static ngx_rtmp_amf_elt_t out_elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + "onStatus", 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &trans, 0 }, + + { NGX_RTMP_AMF_NULL, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_OBJECT, + ngx_null_string, + out_inf, + sizeof(out_inf) }, + }; + + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->log, 0, + "create: status code='%s' level='%s' desc='%s'", + code, level, desc); + + out_inf[0].data = level; + out_inf[1].data = code; + out_inf[2].data = desc; + + memset(&h, 0, sizeof(h)); + + h.type = NGX_RTMP_MSG_AMF_CMD; + h.csid = NGX_RTMP_CSID_AMF; + h.msid = NGX_RTMP_MSID; + + return ngx_rtmp_create_amf(s, &h, out_elts, + sizeof(out_elts) / sizeof(out_elts[0])); +} + + +ngx_int_t +ngx_rtmp_send_status(ngx_rtmp_session_t *s, char *code, char* level, char *desc) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_status(s, code, level, desc)); +} + + +ngx_rtmp_frame_t * +ngx_rtmp_create_play_status(ngx_rtmp_session_t *s, char *code, char* level, + ngx_uint_t duration, ngx_uint_t bytes) +{ + ngx_rtmp_header_t h; + static double dduration; + static double dbytes; + + static ngx_rtmp_amf_elt_t out_inf[] = { + + { NGX_RTMP_AMF_STRING, + ngx_string("code"), + NULL, 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_string("level"), + NULL, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("duration"), + &dduration, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("bytes"), + &dbytes, 0 }, + }; + + static ngx_rtmp_amf_elt_t out_elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + "onPlayStatus", 0 }, + + { NGX_RTMP_AMF_OBJECT, + ngx_null_string, + out_inf, + sizeof(out_inf) }, + }; + + ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->log, 0, + "create: play_status code='%s' level='%s' " + "duration=%ui bytes=%ui", + code, level, duration, bytes); + + out_inf[0].data = code; + out_inf[1].data = level; + + dduration = duration; + dbytes = bytes; + + memset(&h, 0, sizeof(h)); + + h.type = NGX_RTMP_MSG_AMF_META; + h.csid = NGX_RTMP_CSID_AMF; + h.msid = NGX_RTMP_MSID; + h.timestamp = duration; + + return ngx_rtmp_create_amf(s, &h, out_elts, + sizeof(out_elts) / sizeof(out_elts[0])); +} + + +ngx_int_t +ngx_rtmp_send_play_status(ngx_rtmp_session_t *s, char *code, char* level, + ngx_uint_t duration, ngx_uint_t bytes) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_play_status(s, code, level, duration, bytes)); +} + + +ngx_rtmp_frame_t * +ngx_rtmp_create_sample_access(ngx_rtmp_session_t *s) +{ + ngx_rtmp_header_t h; + + static int access = 1; + + static ngx_rtmp_amf_elt_t access_elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + "|RtmpSampleAccess", 0 }, + + { NGX_RTMP_AMF_BOOLEAN, + ngx_null_string, + &access, 0 }, + + { NGX_RTMP_AMF_BOOLEAN, + ngx_null_string, + &access, 0 }, + }; + + memset(&h, 0, sizeof(h)); + + h.type = NGX_RTMP_MSG_AMF_META; + h.csid = NGX_RTMP_CSID_AMF; + h.msid = NGX_RTMP_MSID; + + return ngx_rtmp_create_amf(s, &h, access_elts, + sizeof(access_elts) / sizeof(access_elts[0])); +} + + +ngx_int_t +ngx_rtmp_send_sample_access(ngx_rtmp_session_t *s) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_sample_access(s)); +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_shared_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_shared_module.c new file mode 100644 index 0000000000..cdbf5d0de4 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_shared_module.c @@ -0,0 +1,402 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#include +#include +#include "ngx_rtmp.h" +#include "ngx_rbuf.h" + + +static void *ngx_rtmp_shared_create_conf(ngx_cycle_t *cycle); +static char *ngx_rtmp_shared_init_conf(ngx_cycle_t *cycle, void *conf); + + +/* 1316 == 188 * 7 RTP pack 7 MPEG-TS packets as a RTP package */ +#define NGX_MPEGTS_BUF_SIZE 1316 + +typedef struct { + ngx_rtmp_frame_t *free_frame; + ngx_mpegts_frame_t *free_mpegts_frame; + ngx_pool_t *pool; + + ngx_uint_t nalloc_frame; + ngx_uint_t nfree_frame; +} ngx_rtmp_shared_conf_t; + + +static ngx_command_t ngx_rtmp_shared_commands[] = { + + ngx_null_command +}; + + +static ngx_core_module_t ngx_rtmp_shared_module_ctx = { + ngx_string("rtmp_shared"), + ngx_rtmp_shared_create_conf, /* create conf */ + ngx_rtmp_shared_init_conf /* init conf */ +}; + + +ngx_module_t ngx_rtmp_shared_module = { + NGX_MODULE_V1, + &ngx_rtmp_shared_module_ctx, /* module context */ + ngx_rtmp_shared_commands, /* module directives */ + NGX_CORE_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void * +ngx_rtmp_shared_create_conf(ngx_cycle_t *cycle) +{ + ngx_rtmp_shared_conf_t *rscf; + + rscf = ngx_pcalloc(cycle->pool, sizeof(ngx_rtmp_shared_conf_t)); + if (rscf == NULL) { + return NULL; + } + + return rscf; +} + + +static char * +ngx_rtmp_shared_init_conf(ngx_cycle_t *cycle, void *conf) +{ + ngx_rtmp_shared_conf_t *rscf = conf; + + rscf->pool = ngx_create_pool(4096, cycle->log); + if (rscf->pool == NULL) { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + +ngx_int_t +ngx_rtmp_prepare_merge_frame(ngx_rtmp_session_t *s) +{ + ngx_rtmp_core_app_conf_t *cacf; + ngx_chain_t *ll, **ln; + ngx_uint_t n; + + // merge frame not send completely + if (s->out_chain) { + return NGX_OK; + } + + cacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_core_module); + + ngx_rtmp_free_merge_frame(s); + + ln = &s->out_chain; + + for (n = 0; n < cacf->merge_frame && s->out_pos != s->out_last; ++n) { + // save frame chain with rtmp chunk or flv tag + s->merge[n] = s->prepare_handler(s); + + if (s->out_pos == s->out_last) { + break; + } + + // normal link to chain all merge frames + for (ll = s->merge[n]; ll; ll = ll->next, ln = &(*ln)->next) { + *ln = ngx_alloc_chain_link(s->pool); + if (*ln == NULL) { + s->nframe = n; + return NGX_ERROR; + } + (*ln)->next = NULL; + + (*ln)->buf = ll->buf; + } + + // save frame prepare to send + if (s->live_type == NGX_MPEGTS_LIVE) { + s->prepare_mpegts_frame[n] = s->mpegts_out[s->out_pos]; + } else { + s->prepare_frame[n] = s->out[s->out_pos]; + } + + ++s->out_pos; + s->out_pos %= s->out_queue; + } + + s->nframe = n; + + return NGX_OK; +} + +void +ngx_rtmp_free_merge_frame(ngx_rtmp_session_t *s) +{ + ngx_uint_t n; + + if (s->live_type == NGX_MPEGTS_LIVE) { + for (n = 0; n < s->nframe; ++n) { + ngx_put_chainbufs(s->merge[n]); + s->merge[n] = NULL; + + ngx_rtmp_shared_free_mpegts_frame(s->prepare_mpegts_frame[n]); + s->prepare_mpegts_frame[n] = NULL; + } + } else { + for (n = 0; n < s->nframe; ++n) { + ngx_put_chainbufs(s->merge[n]); + s->merge[n] = NULL; + + ngx_rtmp_shared_free_frame(s->prepare_frame[n]); + s->prepare_frame[n] = NULL; + } + } + + s->nframe = 0; +} + +void +ngx_rtmp_shared_append_chain(ngx_rtmp_frame_t *frame, size_t size, + ngx_chain_t *cl, ngx_flag_t mandatory) +{ + ngx_chain_t **ll; + u_char *p; + size_t len; + + for (ll = &frame->chain; (*ll) && (*ll)->next; ll = &(*ll)->next); + + if (cl == NULL) { + if (mandatory) { + *ll = ngx_get_chainbuf(size, 1); + } + return; + } + + p = cl->buf->pos; + + for (;;) { + if ((*ll) && (*ll)->buf->last == (*ll)->buf->end) { + ll = &(*ll)->next; + } + + if (*ll == NULL) { + *ll = ngx_get_chainbuf(size, 1); + } + + while ((*ll)->buf->end - (*ll)->buf->last >= cl->buf->last - p) { + len = cl->buf->last - p; + (*ll)->buf->last = ngx_cpymem((*ll)->buf->last, p, len); + cl = cl->next; + if (cl == NULL) { + return; + } + p = cl->buf->pos; + } + + len = (*ll)->buf->end - (*ll)->buf->last; + (*ll)->buf->last = ngx_cpymem((*ll)->buf->last, p, len); + p += len; + } +} + +ngx_rtmp_frame_t * +ngx_rtmp_shared_alloc_frame(size_t size, ngx_chain_t *cl, ngx_flag_t mandatory) +{ + ngx_rtmp_shared_conf_t *rscf; + ngx_rtmp_frame_t *frame; + + rscf = (ngx_rtmp_shared_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_rtmp_shared_module); + + frame = rscf->free_frame; + if (frame) { + rscf->free_frame = frame->next; + frame->chain = NULL; + --rscf->nfree_frame; + } else { + frame = ngx_pcalloc(rscf->pool, sizeof(ngx_rtmp_frame_t)); + if (frame == NULL) { + return NULL; + } + ++rscf->nalloc_frame; + } + + frame->ref = 1; + frame->next = NULL; + + ngx_rtmp_shared_append_chain(frame, size, cl, mandatory); + + return frame; +} + +void +ngx_rtmp_shared_free_frame(ngx_rtmp_frame_t *frame) +{ + ngx_rtmp_shared_conf_t *rscf; + ngx_chain_t *cl; + + rscf = (ngx_rtmp_shared_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_rtmp_shared_module); + + if (frame == NULL || --frame->ref) { + return; + } + + /* recycle chainbuf */ + cl = frame->chain; + while (cl) { + frame->chain = cl->next; + ngx_put_chainbuf(cl); + cl = frame->chain; + } + + /* recycle frame */ + frame->next = rscf->free_frame; + rscf->free_frame = frame; + ++rscf->nfree_frame; +} + +void +ngx_mpegts_shared_append_chain(ngx_mpegts_frame_t *frame, ngx_chain_t *cl, + ngx_flag_t mandatory) +{ + ngx_chain_t **ll; + u_char *p; + size_t len; + + for (ll = &frame->chain; (*ll) && (*ll)->next; ll = &(*ll)->next); + + if (cl == NULL) { + if (mandatory) { + *ll = ngx_get_chainbuf(NGX_MPEGTS_BUF_SIZE, 1); + } + return; + } + + p = cl->buf->pos; + + for (;;) { + if ((*ll) && (*ll)->buf->last == (*ll)->buf->end) { + ll = &(*ll)->next; + } + + if (*ll == NULL) { + *ll = ngx_get_chainbuf(NGX_MPEGTS_BUF_SIZE, 1); + } + + while ((*ll)->buf->end - (*ll)->buf->last >= cl->buf->last - p) { + len = cl->buf->last - p; + (*ll)->buf->last = ngx_cpymem((*ll)->buf->last, p, len); + cl = cl->next; + if (cl == NULL) { + return; + } + p = cl->buf->pos; + } + + len = (*ll)->buf->end - (*ll)->buf->last; + (*ll)->buf->last = ngx_cpymem((*ll)->buf->last, p, len); + p += len; + } +} + +ngx_mpegts_frame_t * +ngx_rtmp_shared_alloc_mpegts_frame(ngx_chain_t *cl, ngx_flag_t mandatory) +{ + ngx_rtmp_shared_conf_t *rscf; + ngx_mpegts_frame_t *frame; + + rscf = (ngx_rtmp_shared_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_rtmp_shared_module); + + frame = rscf->free_mpegts_frame; + if (frame) { + rscf->free_mpegts_frame = frame->next; + --rscf->nfree_frame; + } else { + frame = ngx_pcalloc(rscf->pool, sizeof(ngx_mpegts_frame_t)); + if (frame == NULL) { + return NULL; + } + ++rscf->nalloc_frame; + } + + ngx_memset(frame, 0, sizeof(ngx_mpegts_frame_t)); + frame->ref = 1; + frame->next = NULL; + + ngx_mpegts_shared_append_chain(frame, cl, mandatory); + + return frame; +} + +void +ngx_rtmp_shared_free_mpegts_frame(ngx_mpegts_frame_t *frame) +{ + ngx_rtmp_shared_conf_t *rscf; + ngx_chain_t *cl; + + rscf = (ngx_rtmp_shared_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_rtmp_shared_module); + + if (frame == NULL || --frame->ref) { + return; + } + + /* recycle chainbuf */ + cl = frame->chain; + while (cl) { + frame->chain = cl->next; + ngx_put_chainbuf(cl); + cl = frame->chain; + } + + /* recycle frame */ + frame->next = rscf->free_mpegts_frame; + rscf->free_mpegts_frame = frame; + ++rscf->nfree_frame; +} + +ngx_chain_t * +ngx_rtmp_shared_state(ngx_http_request_t *r) +{ + ngx_rtmp_shared_conf_t *rscf; + ngx_chain_t *cl; + ngx_buf_t *b; + size_t len; + + rscf = (ngx_rtmp_shared_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_rtmp_shared_module); + + len = sizeof("##########rtmp shared state##########\n") - 1 + + sizeof("ngx_rtmp_shared alloc frame: \n") - 1 + NGX_OFF_T_LEN + + sizeof("ngx_rtmp_shared free frame: \n") - 1 + NGX_OFF_T_LEN; + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NULL; + } + cl->next = NULL; + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NULL; + } + cl->buf = b; + + b->last = ngx_snprintf(b->last, len, + "##########rtmp shared state##########\n" + "ngx_rtmp_shared alloc frame: %ui\n" + "ngx_rtmp_shared free frame: %ui\n", + rscf->nalloc_frame, rscf->nfree_frame); + + return cl; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_stat_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_stat_module.c new file mode 100644 index 0000000000..7a5aed0067 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_stat_module.c @@ -0,0 +1,754 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include +#include +#include "ngx_rtmp.h" +#include "ngx_live.h" +#include "ngx_rtmp_version.h" +#include "ngx_rtmp_live_module.h" +#include "ngx_rtmp_codec_module.h" + + +static ngx_int_t ngx_rtmp_stat_init_process(ngx_cycle_t *cycle); +static char *ngx_rtmp_stat(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static ngx_int_t ngx_rtmp_stat_postconfiguration(ngx_conf_t *cf); +static void * ngx_rtmp_stat_create_loc_conf(ngx_conf_t *cf); +static char * ngx_rtmp_stat_merge_loc_conf(ngx_conf_t *cf, + void *parent, void *child); + + +static time_t start_time; + + +#define NGX_RTMP_STAT_ALL 0xff +#define NGX_RTMP_STAT_GLOBAL 0x01 +#define NGX_RTMP_STAT_LIVE 0x02 +#define NGX_RTMP_STAT_CLIENTS 0x04 +#define NGX_RTMP_STAT_PLAY 0x08 + +/* + * global: stat-{bufs-{total,free,used}, total bytes in/out, bw in/out} - cscf +*/ + + +typedef struct { + ngx_uint_t stat; + ngx_str_t stylesheet; +} ngx_rtmp_stat_loc_conf_t; + + +static ngx_conf_bitmask_t ngx_rtmp_stat_masks[] = { + { ngx_string("all"), NGX_RTMP_STAT_ALL }, + { ngx_string("global"), NGX_RTMP_STAT_GLOBAL }, + { ngx_string("live"), NGX_RTMP_STAT_LIVE }, + { ngx_string("clients"), NGX_RTMP_STAT_CLIENTS }, + { ngx_null_string, 0 } +}; + + +static ngx_command_t ngx_rtmp_stat_commands[] = { + + { ngx_string("rtmp_stat"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, + ngx_rtmp_stat, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_rtmp_stat_loc_conf_t, stat), + ngx_rtmp_stat_masks }, + + { ngx_string("rtmp_stat_stylesheet"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_rtmp_stat_loc_conf_t, stylesheet), + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_rtmp_stat_module_ctx = { + NULL, /* preconfiguration */ + ngx_rtmp_stat_postconfiguration, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + ngx_rtmp_stat_create_loc_conf, /* create location configuration */ + ngx_rtmp_stat_merge_loc_conf, /* merge location configuration */ +}; + + +ngx_module_t ngx_rtmp_stat_module = { + NGX_MODULE_V1, + &ngx_rtmp_stat_module_ctx, /* module context */ + ngx_rtmp_stat_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + ngx_rtmp_stat_init_process, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +#define NGX_RTMP_STAT_BUFSIZE 256 + + +static ngx_int_t +ngx_rtmp_stat_init_process(ngx_cycle_t *cycle) +{ + /* + * HTTP process initializer is called + * after event module initializer + * so we can run posted events here + */ + + ngx_event_process_posted(cycle, &ngx_rtmp_init_queue); + + return NGX_OK; +} + + +/* ngx_escape_html does not escape characters out of ASCII range + * which are bad for xslt */ + +static void * +ngx_rtmp_stat_escape(ngx_http_request_t *r, void *data, size_t len) +{ + u_char *p, *np; + void *new_data; + size_t n; + + p = data; + + for (n = 0; n < len; ++n, ++p) { + if (*p < 0x20 || *p >= 0x7f) { + break; + } + } + + if (n == len) { + return data; + } + + new_data = ngx_palloc(r->pool, len); + if (new_data == NULL) { + return NULL; + } + + p = data; + np = new_data; + + for (n = 0; n < len; ++n, ++p, ++np) { + *np = (*p < 0x20 || *p >= 0x7f) ? (u_char) ' ' : *p; + } + + return new_data; +} + +#if (NGX_WIN32) +/* + * Fix broken MSVC memcpy optimization for 4-byte data + * when this function is inlined + */ +__declspec(noinline) +#endif + +static void +ngx_rtmp_stat_output(ngx_http_request_t *r, ngx_chain_t ***lll, + void *data, size_t len, ngx_uint_t escape) +{ + ngx_chain_t *cl; + ngx_buf_t *b; + size_t real_len; + + if (len == 0) { + return; + } + + if (escape) { + data = ngx_rtmp_stat_escape(r, data, len); + if (data == NULL) { + return; + } + } + + real_len = escape + ? len + ngx_escape_html(NULL, data, len) + : len; + + cl = **lll; + if (cl && cl->buf->last + real_len > cl->buf->end) { + *lll = &cl->next; + } + + if (**lll == NULL) { + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return; + } + b = ngx_create_temp_buf(r->pool, + ngx_max(NGX_RTMP_STAT_BUFSIZE, real_len)); + if (b == NULL || b->pos == NULL) { + return; + } + cl->next = NULL; + cl->buf = b; + **lll = cl; + } + + b = (**lll)->buf; + + if (escape) { + b->last = (u_char *)ngx_escape_html(b->last, data, len); + } else { + b->last = ngx_cpymem(b->last, data, len); + } +} + + +/* These shortcuts assume 2 variables exist in current context: + * ngx_http_request_t *r + * ngx_chain_t ***lll */ + +/* plain data */ +#define NGX_RTMP_STAT(data, len) ngx_rtmp_stat_output(r, lll, data, len, 0) + +/* escaped data */ +#define NGX_RTMP_STAT_E(data, len) ngx_rtmp_stat_output(r, lll, data, len, 1) + +/* literal */ +#define NGX_RTMP_STAT_L(s) NGX_RTMP_STAT((s), sizeof(s) - 1) + +/* ngx_str_t */ +#define NGX_RTMP_STAT_S(s) NGX_RTMP_STAT((s)->data, (s)->len) + +/* escaped ngx_str_t */ +#define NGX_RTMP_STAT_ES(s) NGX_RTMP_STAT_E((s)->data, (s)->len) + +/* C string */ +#define NGX_RTMP_STAT_CS(s) NGX_RTMP_STAT((s), ngx_strlen(s)) + +/* escaped C string */ +#define NGX_RTMP_STAT_ECS(s) NGX_RTMP_STAT_E((s), ngx_strlen(s)) + + +#define NGX_RTMP_STAT_BW 0x01 +#define NGX_RTMP_STAT_BYTES 0x02 +#define NGX_RTMP_STAT_BW_BYTES 0x03 + + +static void +ngx_rtmp_stat_bw(ngx_http_request_t *r, ngx_chain_t ***lll, + ngx_rtmp_bandwidth_t *bw, char *name, + ngx_uint_t flags) +{ + u_char buf[NGX_INT64_LEN + 9]; + + ngx_rtmp_update_bandwidth(bw, 0); + + if (flags & NGX_RTMP_STAT_BW) { + NGX_RTMP_STAT_L("%uLbandwidth * 8) + - buf); + NGX_RTMP_STAT_CS(name); + NGX_RTMP_STAT_L(">\r\n"); + } + + if (flags & NGX_RTMP_STAT_BYTES) { + NGX_RTMP_STAT_L("%uLbytes) + - buf); + NGX_RTMP_STAT_CS(name); + NGX_RTMP_STAT_L(">\r\n"); + } +} + + +#ifdef NGX_RTMP_POOL_DEBUG +static void +ngx_rtmp_stat_get_pool_size(ngx_pool_t *pool, ngx_uint_t *nlarge, + ngx_uint_t *size) +{ + ngx_pool_large_t *l; + ngx_pool_t *p, *n; + + *nlarge = 0; + for (l = pool->large; l; l = l->next) { + ++*nlarge; + } + + *size = 0; + for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) { + *size += (p->d.last - (u_char *)p); + if (n == NULL) { + break; + } + } +} + + +static void +ngx_rtmp_stat_dump_pool(ngx_http_request_t *r, ngx_chain_t ***lll, + ngx_pool_t *pool) +{ + ngx_uint_t nlarge, size; + u_char buf[NGX_INT_T_LEN]; + + size = 0; + nlarge = 0; + ngx_rtmp_stat_get_pool_size(pool, &nlarge, &size); + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), "%ui", nlarge) - buf); + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), "%ui", size) - buf); + NGX_RTMP_STAT_L("\r\n"); +} +#endif + + + +static void +ngx_rtmp_stat_client(ngx_http_request_t *r, ngx_chain_t ***lll, + ngx_rtmp_session_t *s) +{ + u_char buf[NGX_INT_T_LEN]; + +#ifdef NGX_RTMP_POOL_DEBUG + ngx_rtmp_stat_dump_pool(r, lll, s->pool); +#endif + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), "%ui", + (ngx_uint_t) s->number) - buf); + NGX_RTMP_STAT_L(""); + + NGX_RTMP_STAT_L("
"); + NGX_RTMP_STAT_ES(s->addr_text); + NGX_RTMP_STAT_L("
"); + + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT_ES(&(s->remote_addr_text)); + NGX_RTMP_STAT_L(""); + + NGX_RTMP_STAT_L(""); + + if (s->flashver.len) { + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT_ES(&s->flashver); + NGX_RTMP_STAT_L(""); + } + + if (s->page_url.len) { + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT_ES(&s->page_url); + NGX_RTMP_STAT_L(""); + } + + if (s->swf_url.len) { + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT_ES(&s->swf_url); + NGX_RTMP_STAT_L(""); + } +} + + +static char * +ngx_rtmp_stat_get_aac_profile(ngx_uint_t p, ngx_uint_t sbr, ngx_uint_t ps) { + switch (p) { + case 1: + return "Main"; + case 2: + if (ps) { + return "HEv2"; + } + if (sbr) { + return "HE"; + } + return "LC"; + case 3: + return "SSR"; + case 4: + return "LTP"; + case 5: + return "SBR"; + default: + return ""; + } +} + + +static char * +ngx_rtmp_stat_get_avc_profile(ngx_uint_t p) { + switch (p) { + case 66: + return "Baseline"; + case 77: + return "Main"; + case 100: + return "High"; + default: + return ""; + } +} + + +static void +ngx_rtmp_stat_live(ngx_http_request_t *r, ngx_chain_t ***lll, + ngx_live_server_t *srv) +{ + ngx_live_stream_t *stream; + ngx_rtmp_codec_ctx_t *codec; + ngx_rtmp_live_ctx_t *ctx; + ngx_rtmp_session_t *s; + size_t n; + ngx_uint_t nclients, total_nclients; + u_char buf[NGX_INT_T_LEN]; + u_char bbuf[NGX_INT32_LEN]; + ngx_rtmp_stat_loc_conf_t *slcf; + ngx_live_conf_t *lcf; + u_char *cname; + u_char *p; + + slcf = ngx_http_get_module_loc_conf(r, ngx_rtmp_stat_module); + lcf = (ngx_live_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_live_module); + + NGX_RTMP_STAT_L("\r\n"); + + total_nclients = 0; + for (n = 0; n < lcf->stream_buckets; ++n) { + for (stream = srv->streams[n]; stream; stream = stream->next) { + NGX_RTMP_STAT_L("\r\n"); + + NGX_RTMP_STAT_L(""); + p = stream->name + ngx_strlen(stream->name); + while (*p != '/' && p != stream->name) { + p--; + } + + if (p != stream->name) { + p++; + } + + NGX_RTMP_STAT_ECS(p); + NGX_RTMP_STAT_L("\r\n"); + + NGX_RTMP_STAT_L(""); + + ngx_rtmp_stat_bw(r, lll, &stream->bw_in, "in", + NGX_RTMP_STAT_BW_BYTES); + ngx_rtmp_stat_bw(r, lll, &stream->bw_out, "out", + NGX_RTMP_STAT_BW_BYTES); + ngx_rtmp_stat_bw(r, lll, &stream->bw_in_audio, "audio", + NGX_RTMP_STAT_BW); + ngx_rtmp_stat_bw(r, lll, &stream->bw_in_video, "video", + NGX_RTMP_STAT_BW); + + nclients = 0; + codec = NULL; + for (ctx = stream->ctx; ctx; ctx = ctx->next, ++nclients) { + s = ctx->session; + if (slcf->stat & NGX_RTMP_STAT_CLIENTS) { + NGX_RTMP_STAT_L(""); + + ngx_rtmp_stat_client(r, lll, s); + + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), + "%ui", ctx->ndropped) - buf); + NGX_RTMP_STAT_L(""); + + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT(bbuf, ngx_snprintf(bbuf, sizeof(bbuf), + "%D", ctx->cs[1].timestamp - + ctx->cs[0].timestamp) - bbuf); + NGX_RTMP_STAT_L(""); + + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT(bbuf, ngx_snprintf(bbuf, sizeof(bbuf), + "%D", s->current_time) - bbuf); + NGX_RTMP_STAT_L(""); + + if (ctx->publishing) { + NGX_RTMP_STAT_L(""); + } + + if (ctx->active) { + NGX_RTMP_STAT_L(""); + } + + NGX_RTMP_STAT_L("\r\n"); + } + if (ctx->publishing) { + codec = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + } + } + total_nclients += nclients; + + if (codec) { + NGX_RTMP_STAT_L(""); + + NGX_RTMP_STAT_L(""); + + NGX_RTMP_STAT_L(""); + + NGX_RTMP_STAT_L("\r\n"); + } + + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), + "%ui", nclients) - buf); + NGX_RTMP_STAT_L("\r\n"); + + if (stream->publishing) { + NGX_RTMP_STAT_L("\r\n"); + } + + if (stream->active) { + NGX_RTMP_STAT_L("\r\n"); + } + + NGX_RTMP_STAT_L("\r\n"); + } + } + + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), + "%ui", total_nclients) - buf); + NGX_RTMP_STAT_L("\r\n"); + + NGX_RTMP_STAT_L("\r\n"); +} + + +static ngx_int_t +ngx_rtmp_stat_handler(ngx_http_request_t *r) +{ + ngx_rtmp_stat_loc_conf_t *slcf; + ngx_live_conf_t *lcf; + ngx_live_server_t *psrv; + ngx_chain_t *cl, *l, **ll, ***lll; + size_t n; + off_t len; + static u_char tbuf[NGX_TIME_T_LEN]; + static u_char nbuf[NGX_INT_T_LEN]; + + slcf = ngx_http_get_module_loc_conf(r, ngx_rtmp_stat_module); + if (slcf->stat == 0) { + return NGX_DECLINED; + } + + cl = NULL; + ll = &cl; + lll = ≪ + + NGX_RTMP_STAT_L("\r\n"); + if (slcf->stylesheet.len) { + NGX_RTMP_STAT_L("stylesheet); + NGX_RTMP_STAT_L("\" ?>\r\n"); + } + + NGX_RTMP_STAT_L("\r\n"); + +#ifdef NGINX_VERSION + NGX_RTMP_STAT_L("" NGINX_VERSION "\r\n"); +#endif + +#ifdef NGINX_RTMP_VERSION + NGX_RTMP_STAT_L("" NGINX_RTMP_VERSION "\r\n"); +#endif + +#ifdef NGX_COMPILER + NGX_RTMP_STAT_L("" NGX_COMPILER "\r\n"); +#endif + NGX_RTMP_STAT_L("" __DATE__ " " __TIME__ "\r\n"); + + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT(nbuf, ngx_snprintf(nbuf, sizeof(nbuf), + "%ui", (ngx_uint_t) ngx_getpid()) - nbuf); + NGX_RTMP_STAT_L("\r\n"); + + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT(tbuf, ngx_snprintf(tbuf, sizeof(tbuf), + "%T", ngx_cached_time->sec - start_time) - tbuf); + NGX_RTMP_STAT_L("\r\n"); + + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT(nbuf, ngx_snprintf(nbuf, sizeof(nbuf), + "%ui", ngx_rtmp_naccepted) - nbuf); + NGX_RTMP_STAT_L("\r\n"); + + ngx_rtmp_stat_bw(r, lll, &ngx_rtmp_bw_in, "in", NGX_RTMP_STAT_BW_BYTES); + ngx_rtmp_stat_bw(r, lll, &ngx_rtmp_bw_out, "out", NGX_RTMP_STAT_BW_BYTES); + + lcf = (ngx_live_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_live_module); + + for (n = 0; n < lcf->server_buckets; ++n) { + for (psrv = lcf->servers[n]; psrv; psrv = psrv->next) { + NGX_RTMP_STAT_L("\r\n"); + + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT(psrv->serverid, ngx_strlen(psrv->serverid)); + NGX_RTMP_STAT_L("\r\n"); + + ngx_rtmp_stat_live(r, lll, psrv); + NGX_RTMP_STAT_L("\r\n"); + } + } + + NGX_RTMP_STAT_L("\r\n"); + + len = 0; + for (l = cl; l; l = l->next) { + len += (l->buf->last - l->buf->pos); + } + ngx_str_set(&r->headers_out.content_type, "text/xml"); + r->headers_out.content_length_n = len; + r->headers_out.status = NGX_HTTP_OK; + ngx_http_send_header(r); + (*ll)->buf->last_buf = 1; + return ngx_http_output_filter(r, cl); +} + + +static void * +ngx_rtmp_stat_create_loc_conf(ngx_conf_t *cf) +{ + ngx_rtmp_stat_loc_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_stat_loc_conf_t)); + if (conf == NULL) { + return NULL; + } + + conf->stat = 0; + + return conf; +} + + +static char * +ngx_rtmp_stat_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_rtmp_stat_loc_conf_t *prev = parent; + ngx_rtmp_stat_loc_conf_t *conf = child; + + ngx_conf_merge_bitmask_value(conf->stat, prev->stat, 0); + ngx_conf_merge_str_value(conf->stylesheet, prev->stylesheet, ""); + + return NGX_CONF_OK; +} + + +static char * +ngx_rtmp_stat(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_core_loc_conf_t *clcf; + + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + clcf->handler = ngx_rtmp_stat_handler; + + return ngx_conf_set_bitmask_slot(cf, cmd, conf); +} + + +static ngx_int_t +ngx_rtmp_stat_postconfiguration(ngx_conf_t *cf) +{ + start_time = ngx_cached_time->sec; + + return NGX_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_streams.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_streams.h new file mode 100644 index 0000000000..d957b8e5d5 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_streams.h @@ -0,0 +1,19 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#ifndef _NGX_RTMP_STREAMS_H_INCLUDED_ +#define _NGX_RTMP_STREAMS_H_INCLUDED_ + + +#define NGX_RTMP_MSID 1 + +#define NGX_RTMP_CSID_AMF_INI 3 +#define NGX_RTMP_CSID_AMF 5 +#define NGX_RTMP_CSID_AUDIO 6 +#define NGX_RTMP_CSID_VIDEO 7 + + +#endif /* _NGX_RTMP_STREAMS_H_INCLUDED_ */ diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_sys_stat_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_sys_stat_module.c new file mode 100644 index 0000000000..7c7fae742e --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_sys_stat_module.c @@ -0,0 +1,170 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#include +#include +#include +#include +#include "ngx_rtmp.h" +#include "ngx_rbuf.h" +#include "ngx_poold.h" +#include "ngx_stream_zone_module.h" +#include "ngx_event_timer_module.h" +#include "ngx_event_resolver.h" +#include "ngx_dynamic_resolver.h" +#include "ngx_live.h" + + +static char *ngx_rtmp_sys_stat(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); + + +extern ngx_chain_t *ngx_live_relay_static_state(ngx_http_request_t *r); + + +static ngx_command_t ngx_rtmp_sys_stat_commands[] = { + + { ngx_string("sys_stat"), + NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS, + ngx_rtmp_sys_stat, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_rtmp_sys_stat_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_rtmp_sys_stat_module = { + NGX_MODULE_V1, + &ngx_rtmp_sys_stat_module_ctx, /* module context */ + ngx_rtmp_sys_stat_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_int_t +ngx_rtmp_sys_stat_handler(ngx_http_request_t *r) +{ + ngx_chain_t **ll, *out; + ngx_buf_t *b; + ngx_str_t detail_arg; + ngx_uint_t detail = 0; + size_t len; + + if (ngx_http_arg(r, (u_char *) "detail", sizeof("detail") - 1, &detail_arg) + == NGX_OK) + { + detail = 1; + } + + r->headers_out.status = NGX_HTTP_OK; + ngx_http_send_header(r); + + ll = &out; + + len = sizeof("--------------------------------------------------\n") - 1 + + sizeof("ngx_worker: ngx_process_slot: pid: \n") - 1 + + 3 * NGX_OFF_T_LEN; + + *ll = ngx_alloc_chain_link(r->pool); + if (*ll == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + (*ll)->next = NULL; + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + (*ll)->buf = b; + + b->last = ngx_snprintf(b->last, len, + "--------------------------------------------------\n" + "ngx_worker: %i ngx_process_slot: %i pid: %i\n", + ngx_worker, ngx_process_slot, ngx_pid); + + if (*ll) { + ll = &(*ll)->next; + } + *ll = ngx_live_state(r); + + if (*ll) { + ll = &(*ll)->next; + } + *ll = ngx_live_relay_static_state(r); + + if (*ll) { + ll = &(*ll)->next; + } + *ll = ngx_rtmp_shared_state(r); + + if (*ll) { + ll = &(*ll)->next; + } + *ll = ngx_rbuf_state(r, detail); + + if (*ll) { + ll = &(*ll)->next; + } + *ll = ngx_stream_zone_state(r, 0); + + if (*ll) { + ll = &(*ll)->next; + } + *ll = ngx_event_timer_state(r); + + if (*ll) { + ll = &(*ll)->next; + } + *ll = ngx_event_resolver_state(r); + + if (*ll) { + ll = &(*ll)->next; + } + *ll = ngx_dynamic_resolver_state(r); + + if (*ll) { + ll = &(*ll)->next; + } + *ll = ngx_poold_state(r, detail); + + (*ll)->buf->last_buf = 1; + + return ngx_http_output_filter(r, out); +} + +static char * +ngx_rtmp_sys_stat(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_core_loc_conf_t *clcf; + + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + clcf->handler = ngx_rtmp_sys_stat_handler; + + return NGX_CONF_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_variables.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_variables.c new file mode 100644 index 0000000000..fbb92caff2 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_variables.c @@ -0,0 +1,1578 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#include +#include +#include +#include "ngx_rtmp.h" +#include "ngx_rtmp_variables.h" +#include "ngx_live_relay.h" + + +static ngx_int_t ngx_rtmp_variable_session_get_str(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_rtmp_variable_session_get_uint(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_rtmp_variable_session_get_int(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_rtmp_variable_session_get_msec(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data); + +static ngx_int_t ngx_rtmp_variable_relay_get_str(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_rtmp_variable_relay_get_uint(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data); + +static ngx_int_t ngx_rtmp_variable_finalize_reason(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_rtmp_variable_stage(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_rtmp_variable_remote_addr(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_rtmp_variable_remote_port(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_rtmp_variable_local_addr(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_rtmp_variable_local_port(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_rtmp_variable_nginx_version(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_rtmp_variable_pid(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_rtmp_variable_msec(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_rtmp_variable_time_iso8601(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_rtmp_variable_time_local(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_rtmp_variable_ngx_worker(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_rtmp_variable_argument(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data); + + +static ngx_rtmp_variable_t ngx_rtmp_core_variables[] = { + + { ngx_string("domain"), NULL, ngx_rtmp_variable_session_get_str, + offsetof(ngx_rtmp_session_t, domain), NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("app"), NULL, ngx_rtmp_variable_session_get_str, + offsetof(ngx_rtmp_session_t, app), NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("name"), NULL, ngx_rtmp_variable_session_get_str, + offsetof(ngx_rtmp_session_t, name), NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("stream"), NULL, ngx_rtmp_variable_session_get_str, + offsetof(ngx_rtmp_session_t, stream), NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("pargs"), NULL, ngx_rtmp_variable_session_get_str, + offsetof(ngx_rtmp_session_t, pargs), NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("args"), NULL, ngx_rtmp_variable_session_get_str, + offsetof(ngx_rtmp_session_t, args), NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("flashver"), NULL, ngx_rtmp_variable_session_get_str, + offsetof(ngx_rtmp_session_t, flashver), NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("swf_url"), NULL, ngx_rtmp_variable_session_get_str, + offsetof(ngx_rtmp_session_t, swf_url), NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("tc_url"), NULL, ngx_rtmp_variable_session_get_str, + offsetof(ngx_rtmp_session_t, tc_url), NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("page_url"), NULL, ngx_rtmp_variable_session_get_str, + offsetof(ngx_rtmp_session_t, page_url), NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("acodecs"), NULL, ngx_rtmp_variable_session_get_uint, + offsetof(ngx_rtmp_session_t, acodecs), NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("vcodecs"), NULL, ngx_rtmp_variable_session_get_uint, + offsetof(ngx_rtmp_session_t, vcodecs), NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("scheme"), NULL, ngx_rtmp_variable_session_get_str, + offsetof(ngx_rtmp_session_t, scheme), NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("serverid"), NULL, ngx_rtmp_variable_session_get_str, + offsetof(ngx_rtmp_session_t, serverid), NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("groupid"), NULL, ngx_rtmp_variable_session_get_str, + offsetof(ngx_rtmp_session_t, groupid), NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("notify_status"), NULL, ngx_rtmp_variable_session_get_int, + offsetof(ngx_rtmp_session_t, notify_status), NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("finalize_reason"), NULL, ngx_rtmp_variable_finalize_reason, + 0, NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("stage"), NULL, ngx_rtmp_variable_stage, + 0, NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("init"), NULL, ngx_rtmp_variable_session_get_msec, + offsetof(ngx_rtmp_session_t, init_time), NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("handshake_done"), NULL, ngx_rtmp_variable_session_get_msec, + offsetof(ngx_rtmp_session_t, handshake_done_time), + NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("connect"), NULL, ngx_rtmp_variable_session_get_msec, + offsetof(ngx_rtmp_session_t, connect_time), NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("create_stream"), NULL, ngx_rtmp_variable_session_get_msec, + offsetof(ngx_rtmp_session_t, create_stream_time), + NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("ptime"), NULL, ngx_rtmp_variable_session_get_msec, + offsetof(ngx_rtmp_session_t, ptime), NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("first_data"), NULL, ngx_rtmp_variable_session_get_msec, + offsetof(ngx_rtmp_session_t, first_data), NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("first_metadata"), NULL, ngx_rtmp_variable_session_get_msec, + offsetof(ngx_rtmp_session_t, first_metadata), + NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("first_audio"), NULL, ngx_rtmp_variable_session_get_msec, + offsetof(ngx_rtmp_session_t, first_audio), NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("first_video"), NULL, ngx_rtmp_variable_session_get_msec, + offsetof(ngx_rtmp_session_t, first_video), NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("close_stream"), NULL, ngx_rtmp_variable_session_get_msec, + offsetof(ngx_rtmp_session_t, close_stream_time), + NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("relay_domain"), NULL, ngx_rtmp_variable_relay_get_str, + offsetof(ngx_live_relay_ctx_t, domain), NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("relay_app"), NULL, ngx_rtmp_variable_relay_get_str, + offsetof(ngx_live_relay_ctx_t, app), NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("relay_name"), NULL, ngx_rtmp_variable_relay_get_str, + offsetof(ngx_live_relay_ctx_t, name), NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("relay_args"), NULL, ngx_rtmp_variable_relay_get_str, + offsetof(ngx_live_relay_ctx_t, args), NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("relay_pargs"), NULL, ngx_rtmp_variable_relay_get_str, + offsetof(ngx_live_relay_ctx_t, pargs), NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("relay_referer"), NULL, ngx_rtmp_variable_relay_get_str, + offsetof(ngx_live_relay_ctx_t, referer), NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("relay_user_agent"), NULL, ngx_rtmp_variable_relay_get_str, + offsetof(ngx_live_relay_ctx_t, user_agent), NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("relay_swf_url"), NULL, ngx_rtmp_variable_relay_get_str, + offsetof(ngx_live_relay_ctx_t, swf_url), NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("relay_acodecs"), NULL, ngx_rtmp_variable_relay_get_uint, + offsetof(ngx_live_relay_ctx_t, acodecs), NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("relay_vcodecs"), NULL, ngx_rtmp_variable_relay_get_uint, + offsetof(ngx_live_relay_ctx_t, vcodecs), NGX_RTMP_VAR_NOCACHEABLE, 0}, + + { ngx_string("remote_addr"), NULL, ngx_rtmp_variable_remote_addr, + 0, NGX_RTMP_VAR_NOCACHEABLE, 0 }, + + { ngx_string("remote_port"), NULL, ngx_rtmp_variable_remote_port, + 0, NGX_RTMP_VAR_NOCACHEABLE, 0 }, + + { ngx_string("server_addr"), NULL, ngx_rtmp_variable_local_addr, + 0, NGX_RTMP_VAR_NOCACHEABLE, 0 }, + + { ngx_string("server_port"), NULL, ngx_rtmp_variable_local_port, + 0, NGX_RTMP_VAR_NOCACHEABLE, 0 }, + + { ngx_string("nginx_version"), NULL, + ngx_rtmp_variable_nginx_version, 0, 0, 0}, + + { ngx_string("pid"), NULL, ngx_rtmp_variable_pid, 0, 0, 0}, + + { ngx_string("msec"), NULL, ngx_rtmp_variable_msec, + 0, NGX_RTMP_VAR_NOCACHEABLE, 0 }, + + { ngx_string("time_iso8601"), NULL, ngx_rtmp_variable_time_iso8601, + 0, NGX_RTMP_VAR_NOCACHEABLE, 0 }, + + { ngx_string("time_local"), NULL, ngx_rtmp_variable_time_local, + 0, NGX_RTMP_VAR_NOCACHEABLE, 0 }, + + { ngx_string("ngx_worker"), NULL, ngx_rtmp_variable_ngx_worker, + 0, 0, 0}, + + { ngx_string("parg_"), NULL, ngx_rtmp_variable_argument, + 0, NGX_RTMP_VAR_NOCACHEABLE|NGX_RTMP_VAR_PREFIX, 0 }, + + { ngx_null_string, NULL, NULL, 0, 0, 0 } +}; + + +ngx_rtmp_variable_value_t ngx_rtmp_variable_null_value = + ngx_rtmp_variable(""); +ngx_rtmp_variable_value_t ngx_rtmp_variable_true_value = + ngx_rtmp_variable("1"); + + +static ngx_uint_t ngx_rtmp_variable_depth = 100; + + +extern char *ngx_live_stage[]; +extern char *ngx_live_err[]; + + +static ngx_int_t +ngx_rtmp_variable_session_get_str(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_str_t *str; + + str = (ngx_str_t *) ((u_char *) s + data); + + v->data = str->data; + v->len = str->len; + v->valid = 1; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_variable_session_get_uint(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_uint_t *ui; + + ui = (ngx_uint_t *) ((u_char *) s + data); + + if (v->data == NULL) { + v->data = ngx_pnalloc(s->pool, NGX_INT_T_LEN); + if (v->data == NULL) { + return NGX_ERROR; + } + } + + v->len = ngx_sprintf(v->data, "%ui", *ui) - v->data; + v->valid = 1; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_variable_session_get_int(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_int_t *i; + + i = (ngx_int_t *) ((u_char *) s + data); + + if (v->data == NULL) { + v->data = ngx_pnalloc(s->pool, NGX_INT_T_LEN); + if (v->data == NULL) { + return NGX_ERROR; + } + } + + v->len = ngx_sprintf(v->data, "%i", *i) - v->data; + v->valid = 1; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_variable_session_get_msec(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_msec_t *m; + + m = (ngx_msec_t *) ((u_char *) s + data); + + if (v->data == NULL) { + v->data = ngx_pnalloc(s->pool, NGX_INT_T_LEN); + if (v->data == NULL) { + return NGX_ERROR; + } + } + + v->len = ngx_sprintf(v->data, "%i", *m) - v->data; + v->valid = 1; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_variable_relay_get_str(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_live_relay_ctx_t *ctx; + ngx_str_t *str; + + if (!s->relay) { + v->not_found = 1; + return NGX_OK; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module); + + str = (ngx_str_t *) ((u_char *) ctx + data); + + v->data = str->data; + v->len = str->len; + v->valid = 1; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_variable_relay_get_uint(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_live_relay_ctx_t *ctx; + ngx_uint_t *ui; + + if (!s->relay) { + v->not_found = 1; + return NGX_OK; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module); + + ui = (ngx_uint_t *) ((u_char *) ctx + data); + + if (v->data == NULL) { + v->data = ngx_pnalloc(s->pool, NGX_INT_T_LEN); + if (v->data == NULL) { + return NGX_ERROR; + } + } + + v->len = ngx_sprintf(v->data, "%ui", *ui) - v->data; + v->valid = 1; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_variable_finalize_reason(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data) +{ + v->data = (u_char *) ngx_live_err[s->finalize_reason]; + v->len = ngx_strlen(v->data); + v->valid = 1; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_variable_stage(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data) +{ + v->data = (u_char *) ngx_live_stage[s->stage]; + v->len = ngx_strlen(v->data); + v->valid = 1; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_variable_remote_addr(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_sockaddr_t nsa; + struct sockaddr *sa; + socklen_t len; + + if (s->connection == NULL || s->connection->fd == (ngx_socket_t) -1) { + v->not_found = 1; + return NGX_OK; + } + + sa = (struct sockaddr *) &nsa; + len = sizeof(nsa); + + if (getpeername(s->connection->fd, sa, &len) == -1) { + v->not_found = 1; + return NGX_OK; + } + + if (v->data == NULL) { + v->data = ngx_pnalloc(s->pool, NGX_SOCKADDRLEN); + if (v->data == NULL) { + return NGX_ERROR; + } + } + + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->len = ngx_sock_ntop(sa, sizeof(nsa), v->data, NGX_SOCKADDRLEN, 0); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_variable_remote_port(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_sockaddr_t nsa; + struct sockaddr *sa; + socklen_t len; + in_port_t port; + + if (s->connection == NULL || s->connection->fd == (ngx_socket_t) -1) { + v->not_found = 1; + return NGX_OK; + } + + sa = (struct sockaddr *) &nsa; + len = sizeof(nsa); + + if (getpeername(s->connection->fd, sa, &len) == -1) { + v->not_found = 1; + return NGX_OK; + } + + if (v->data == NULL) { + v->data = ngx_pnalloc(s->pool, sizeof("65535") - 1); + if (v->data == NULL) { + return NGX_ERROR; + } + } + + port = ngx_inet_get_port(sa); + if (port == 0) { + v->not_found = 1; + return NGX_OK; + } + + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->len = ngx_sprintf(v->data, "%ui", port) - v->data; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_variable_local_addr(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_sockaddr_t nsa; + struct sockaddr *sa; + socklen_t len; + + if (s->connection == NULL || s->connection->fd == (ngx_socket_t) -1) { + v->not_found = 1; + return NGX_OK; + } + + sa = (struct sockaddr *) &nsa; + len = sizeof(nsa); + + if (getsockname(s->connection->fd, sa, &len) == -1) { + v->not_found = 1; + return NGX_OK; + } + + if (v->data == NULL) { + v->data = ngx_pnalloc(s->pool, NGX_SOCKADDRLEN); + if (v->data == NULL) { + return NGX_ERROR; + } + } + + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->len = ngx_sock_ntop(sa, sizeof(nsa), v->data, NGX_SOCKADDRLEN, 0); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_variable_local_port(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_sockaddr_t nsa; + struct sockaddr *sa; + socklen_t len; + in_port_t port; + + if (s->connection == NULL || s->connection->fd == (ngx_socket_t) -1) { + v->not_found = 1; + return NGX_OK; + } + + sa = (struct sockaddr *) &nsa; + len = sizeof(nsa); + + if (getsockname(s->connection->fd, sa, &len) == -1) { + v->not_found = 1; + return NGX_OK; + } + + if (v->data == NULL) { + v->data = ngx_pnalloc(s->pool, sizeof("65535") - 1); + if (v->data == NULL) { + return NGX_ERROR; + } + } + + port = ngx_inet_get_port(sa); + if (port == 0) { + v->not_found = 1; + return NGX_OK; + } + + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->len = ngx_sprintf(v->data, "%ui", port) - v->data; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_variable_nginx_version(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data) +{ + v->len = sizeof(NGINX_VERSION) - 1; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = (u_char *) NGINX_VERSION; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_variable_pid(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data) +{ + if (v->data == NULL) { + v->data = ngx_pnalloc(s->pool, NGX_INT64_LEN); + if (v->data == NULL) { + return NGX_ERROR; + } + } + + v->len = ngx_sprintf(v->data, "%P", ngx_pid) - v->data; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_variable_msec(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_time_t *tp; + + if (v->data == NULL) { + v->data = ngx_pnalloc(s->pool, NGX_TIME_T_LEN + 4); + if (v->data == NULL) { + return NGX_ERROR; + } + } + + tp = ngx_timeofday(); + + v->len = ngx_sprintf(v->data, "%T.%03M", tp->sec, tp->msec) - v->data; + v->valid = 1; + v->no_cacheable = 1; + v->not_found = 0; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_variable_time_iso8601(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data) +{ + if (v->data == NULL) { + v->data = ngx_pnalloc(s->pool, ngx_cached_http_log_iso8601.len); + if (v->data == NULL) { + return NGX_ERROR; + } + } + + ngx_memcpy(v->data, ngx_cached_http_log_iso8601.data, + ngx_cached_http_log_iso8601.len); + + v->len = ngx_cached_http_log_iso8601.len; + v->valid = 1; + v->no_cacheable = 1; + v->not_found = 0; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_variable_time_local(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data) +{ + if (v->data == NULL) { + v->data = ngx_pnalloc(s->pool, ngx_cached_http_log_time.len); + if (v->data == NULL) { + return NGX_ERROR; + } + } + + ngx_memcpy(v->data, ngx_cached_http_log_time.data, + ngx_cached_http_log_time.len); + + v->len = ngx_cached_http_log_time.len; + v->valid = 1; + v->no_cacheable = 1; + v->not_found = 0; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_variable_ngx_worker(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data) +{ + if (v->data == NULL) { + v->data = ngx_pnalloc(s->pool, NGX_INT64_LEN); + if (v->data == NULL) { + return NGX_ERROR; + } + } + + v->len = ngx_sprintf(v->data, "%ui", ngx_worker) - v->data; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_variable_argument(ngx_rtmp_session_t *s, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_str_t *name = (ngx_str_t *) data; + + u_char *arg; + size_t len; + ngx_str_t value; + + len = name->len - (sizeof("parg_") - 1); + arg = name->data + sizeof("parg_") - 1; + + if (ngx_rtmp_arg(s, arg, len, &value) != NGX_OK) { + v->not_found = 1; + return NGX_OK; + } + + v->data = value.data; + v->len = value.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + return NGX_OK; +} + + +static ngx_rtmp_variable_t * +ngx_rtmp_add_prefix_variable(ngx_conf_t *cf, ngx_str_t *name, ngx_uint_t flags) +{ + ngx_uint_t i; + ngx_rtmp_variable_t *v; + ngx_rtmp_core_main_conf_t *cmcf; + + cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); + + v = cmcf->prefix_variables.elts; + for (i = 0; i < cmcf->prefix_variables.nelts; i++) { + if (name->len != v[i].name.len + || ngx_strncasecmp(name->data, v[i].name.data, name->len) != 0) + { + continue; + } + + v = &v[i]; + + if (!(v->flags & NGX_HTTP_VAR_CHANGEABLE)) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the duplicate \"%V\" variable", name); + return NULL; + } + + v->flags &= flags | ~NGX_HTTP_VAR_WEAK; + + return v; + } + + v = ngx_array_push(&cmcf->prefix_variables); + if (v == NULL) { + return NULL; + } + + v->name.len = name->len; + v->name.data = ngx_pnalloc(cf->pool, name->len); + if (v->name.data == NULL) { + return NULL; + } + + ngx_strlow(v->name.data, name->data, name->len); + + v->set_handler = NULL; + v->get_handler = NULL; + v->data = 0; + v->flags = flags; + v->index = 0; + + return v; +} + + +static u_char * +ngx_rtmp_strlechr(u_char *p, u_char *last) +{ + while (p != last) { + if ((*p >= '0' && *p <= '9') || + (*p >= 'a' && *p <= 'z') || + (*p >= 'A' && *p <= 'Z') || + *p == '_') + { + p++; + continue; + } + + return p; + } + + return NULL; +} + + +ngx_int_t +ngx_rtmp_variable_transform_index(ngx_conf_t *cf, ngx_str_t *origin, ngx_str_t *target) +{ + u_char *p, *e, *t; + u_char *wp, *we; + ngx_str_t str, var; + ngx_buf_t *buf; + ngx_int_t index; + + p = origin->data; + e = origin->data + origin->len; + + buf = ngx_create_temp_buf(cf->pool, 2 * origin->len); + if (buf == NULL) { + return NGX_ERROR; + } + + wp = buf->start; + we = buf->end; + + while (p < e) { + t = ngx_strlchr(p, e, '$'); + if (t == NULL) { + t = e; + } + str.data = p; + str.len = t - p; + wp = ngx_slprintf(wp, we, "%V", &str); + + if (t == e) { + break; + } + + var.data = ++t; + t = ngx_rtmp_strlechr(t, e); + if (t == NULL) { + t = e; + } + var.len = t - var.data; + + index = ngx_rtmp_get_variable_index(cf, &var); + if (index == NGX_ERROR) { + return NGX_ERROR; + } + + wp = ngx_slprintf(wp, we, "$%d", index); + p = t; + } + + target->data = buf->start; + target->len = wp - buf->start; + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_fetch_variable(ngx_rtmp_session_t *s, ngx_pool_t *pool, + ngx_str_t *origin, ngx_str_t *target) +{ + ngx_rtmp_variable_value_t *vv; + u_char *p, *e, *t; + u_char *wp, *we; + ngx_chain_t *ch, *cl, *ct; + u_char *pt; + ngx_uint_t length; + ngx_int_t index; + ngx_str_t var; + + length = 0; + p = origin->data; + e = p + origin->len; + + #define NGX_RTMP_NOTIFY_BUF(__start__, __end__) \ + ct = cl; \ + pt = ngx_pcalloc(pool, sizeof(ngx_chain_t) + \ + sizeof(ngx_buf_t) + __end__ - __start__); \ + cl = (ngx_chain_t*)pt; \ + cl->buf = (ngx_buf_t*)(pt + sizeof(ngx_chain_t)); \ + cl->buf->start = \ + cl->buf->pos = \ + cl->buf->last = pt + sizeof(ngx_chain_t) + sizeof(ngx_buf_t); \ + if (ch == NULL) { \ + ch = cl; \ + } else { \ + ct->next = cl; \ + } \ + cl->buf->last = ngx_cpymem(cl->buf->pos, __start__, __end__ - __start__); \ + length += __end__ - __start__ + + ch = cl = ct = NULL; + + while(p < e) { + t = ngx_strlchr(p, e, '$'); + if (t == NULL) { + t = e; + } + NGX_RTMP_NOTIFY_BUF(p, t); + if (t == e) { + break; + } + + var.data = ++t; + t = ngx_rtmp_strlechr(t, e); + if (t == NULL) { + t = e; + } + var.len = t - var.data; + index = ngx_atoi(var.data, var.len); + vv = ngx_rtmp_get_indexed_variable(s, index); + if (vv == NULL) { + p = t; + continue; + } + wp = vv->data; + we = vv->data + vv->len; + + NGX_RTMP_NOTIFY_BUF(wp, we); + p = t; + } + + #undef NGX_RTMP_NOTIFY_BUF + + wp = ngx_pcalloc(pool, length); + we = wp; + + for (ct = ch; ct;) { + we = ngx_cpymem(we, ct->buf->pos, ct->buf->last - ct->buf->pos); + cl = ct->next; + ngx_pfree(pool, ct); + ct = cl; + } + target->data = wp; + target->len = we - wp; + if (target->len != length) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "variable: fetch_variable| target len = %d, content length = %d", + target->len, length); + return NGX_ERROR; + } + + return NGX_OK; +} + +ngx_rtmp_variable_t * +ngx_rtmp_add_variable(ngx_conf_t *cf, ngx_str_t *name, ngx_uint_t flags) +{ + ngx_int_t rc; + ngx_uint_t i; + ngx_hash_key_t *key; + ngx_rtmp_variable_t *v; + ngx_rtmp_core_main_conf_t *cmcf; + + if (name->len == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid variable name \"$\""); + return NULL; + } + + if (flags & NGX_HTTP_VAR_PREFIX) { + return ngx_rtmp_add_prefix_variable(cf, name, flags); + } + + cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); + + key = cmcf->variables_keys->keys.elts; + for (i = 0; i < cmcf->variables_keys->keys.nelts; i++) { + if (name->len != key[i].key.len + || ngx_strncasecmp(name->data, key[i].key.data, name->len) != 0) + { + continue; + } + + v = key[i].value; + + if (!(v->flags & NGX_HTTP_VAR_CHANGEABLE)) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the duplicate \"%V\" variable", name); + return NULL; + } + + v->flags &= flags | ~NGX_HTTP_VAR_WEAK; + + return v; + } + + v = ngx_palloc(cf->pool, sizeof(ngx_rtmp_variable_t)); + if (v == NULL) { + return NULL; + } + + v->name.len = name->len; + v->name.data = ngx_pnalloc(cf->pool, name->len); + if (v->name.data == NULL) { + return NULL; + } + + ngx_strlow(v->name.data, name->data, name->len); + + v->set_handler = NULL; + v->get_handler = NULL; + v->data = 0; + v->flags = flags; + v->index = 0; + + rc = ngx_hash_add_key(cmcf->variables_keys, &v->name, v, 0); + + if (rc == NGX_ERROR) { + return NULL; + } + + if (rc == NGX_BUSY) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "conflicting variable name \"%V\"", name); + return NULL; + } + + return v; +} + + +ngx_int_t +ngx_rtmp_get_variable_index(ngx_conf_t *cf, ngx_str_t *name) +{ + ngx_uint_t i = 0, n; + ngx_rtmp_variable_t *v, *av; + ngx_rtmp_core_main_conf_t *cmcf; + ngx_hash_key_t *key; + + if (name->len == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid variable name \"$\""); + return NGX_ERROR; + } + + cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); + + v = cmcf->variables.elts; + key = cmcf->variables_keys->keys.elts; + + if (v == NULL) { + if (ngx_array_init(&cmcf->variables, cf->pool, 4, + sizeof(ngx_http_variable_t)) + != NGX_OK) + { + return NGX_ERROR; + } + + } else { + for (i = 0; i < cmcf->variables.nelts; i++) { + if (name->len != v[i].name.len + || ngx_strncasecmp(name->data, v[i].name.data, name->len) != 0) + { + continue; + } + + return i; + } + } + + v = ngx_array_push(&cmcf->variables); + if (v == NULL) { + return NGX_ERROR; + } + + v->name.len = name->len; + v->name.data = ngx_pnalloc(cf->pool, name->len); + if (v->name.data == NULL) { + return NGX_ERROR; + } + + ngx_strlow(v->name.data, name->data, name->len); + + for (n = 0; n < cmcf->variables_keys->keys.nelts; n++) { + av = key[n].value; + if (av->get_handler + && v->name.len == key[n].key.len + && ngx_strncmp(v->name.data, key[n].key.data, v->name.len) == 0) + { + v->get_handler = av->get_handler; + v->data = av->data; + av->flags= NGX_RTMP_VAR_INDEXED; + v->flags = av->flags; + av->index = i; + + goto next; + } + } + + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "variables: get_variable_index| unknown \"%V\" variable", + &v->name); + + return NGX_ERROR; + +next: + v->set_handler = NULL; + v->index = cmcf->variables.nelts - 1; + + return v->index; +} + + +ngx_rtmp_variable_value_t * +ngx_rtmp_get_indexed_variable(ngx_rtmp_session_t *s, ngx_uint_t index) +{ + ngx_rtmp_variable_t *v; + ngx_rtmp_core_main_conf_t *cmcf; + + cmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_core_module); + + if (cmcf->variables.nelts <= index) { + ngx_log_error(NGX_LOG_ALERT, s->log, 0, + "unknown variable index: %ui", index); + return NULL; + } + + if (s->variables[index].not_found || s->variables[index].valid) { + return &s->variables[index]; + } + + v = cmcf->variables.elts; + + if (ngx_rtmp_variable_depth == 0) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "cycle while evaluating variable \"%V\"", + &v[index].name); + return NULL; + } + + ngx_rtmp_variable_depth--; + + if (v[index].get_handler(s, &s->variables[index], v[index].data) + == NGX_OK) + { + ngx_rtmp_variable_depth++; + + if (v[index].flags & NGX_HTTP_VAR_NOCACHEABLE) { + s->variables[index].no_cacheable = 1; + } + + return &s->variables[index]; + } + + ngx_rtmp_variable_depth++; + + s->variables[index].valid = 0; + s->variables[index].not_found = 1; + + return NULL; +} + + +ngx_rtmp_variable_value_t * +ngx_rtmp_get_flushed_variable(ngx_rtmp_session_t *s, ngx_uint_t index) +{ + ngx_rtmp_variable_value_t *v; + + v = &s->variables[index]; + + if (v->valid || v->not_found) { + if (!v->no_cacheable) { + return v; + } + + v->valid = 0; + v->not_found = 0; + } + + return ngx_rtmp_get_indexed_variable(s, index); +} + + +ngx_rtmp_variable_value_t * +ngx_rtmp_get_variable(ngx_rtmp_session_t *s, ngx_str_t *name, ngx_uint_t key) +{ + size_t len; + ngx_uint_t i, n; + ngx_rtmp_variable_t *v; + ngx_rtmp_variable_value_t *vv; + ngx_rtmp_core_main_conf_t *cmcf; + + cmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_core_module); + + v = ngx_hash_find(&cmcf->variables_hash, key, name->data, name->len); + + if (v) { + if (v->flags & NGX_HTTP_VAR_INDEXED) { + return ngx_rtmp_get_flushed_variable(s, v->index); + } + + if (ngx_rtmp_variable_depth == 0) { + ngx_log_error(NGX_LOG_ERR, s->log, 0, + "cycle while evaluating variable \"%V\"", name); + return NULL; + } + + ngx_rtmp_variable_depth--; + + vv = ngx_palloc(s->pool, sizeof(ngx_rtmp_variable_value_t)); + + if (vv && v->get_handler(s, vv, v->data) == NGX_OK) { + ngx_rtmp_variable_depth++; + return vv; + } + + ngx_rtmp_variable_depth++; + return NULL; + } + + vv = ngx_palloc(s->pool, sizeof(ngx_rtmp_variable_value_t)); + if (vv == NULL) { + return NULL; + } + + len = 0; + + v = cmcf->prefix_variables.elts; + n = cmcf->prefix_variables.nelts; + + for (i = 0; i < cmcf->prefix_variables.nelts; i++) { + if (name->len >= v[i].name.len && name->len > len + && ngx_strncmp(name->data, v[i].name.data, v[i].name.len) == 0) + { + len = v[i].name.len; + n = i; + } + } + + if (n != cmcf->prefix_variables.nelts) { + if (v[n].get_handler(s, vv, (uintptr_t) name) == NGX_OK) { + return vv; + } + + return NULL; + } + + vv->not_found = 1; + + return vv; +} + + +void * +ngx_rtmp_map_find(ngx_rtmp_session_t *s, ngx_rtmp_map_t *map, ngx_str_t *match) +{ + void *value; + u_char *low; + size_t len; + ngx_uint_t key; + + len = match->len; + + if (len) { + low = ngx_pnalloc(s->pool, len); + if (low == NULL) { + return NULL; + } + + } else { + low = NULL; + } + + key = ngx_hash_strlow(low, match->data, len); + + value = ngx_hash_find_combined(&map->hash, key, low, len); + if (value) { + return value; + } + +#if (NGX_PCRE) + + if (len && map->nregex) { + ngx_int_t n; + ngx_uint_t i; + ngx_rtmp_map_regex_t *reg; + + reg = map->regex; + + for (i = 0; i < map->nregex; i++) { + + n = ngx_rtmp_regex_exec(s, reg[i].regex, match); + + if (n == NGX_OK) { + return reg[i].value; + } + + if (n == NGX_DECLINED) { + continue; + } + + /* NGX_ERROR */ + + return NULL; + } + } + +#endif + + return NULL; +} + + +#if (NGX_PCRE) + +static ngx_int_t +ngx_rtmp_variable_not_found(ngx_rtmp_session_t *s, ngx_rtmp_variable_value_t *v, + uintptr_t data) +{ + v->not_found = 1; + return NGX_OK; +} + + +ngx_rtmp_regex_t * +ngx_rtmp_regex_compile(ngx_conf_t *cf, ngx_regex_compile_t *rc) +{ + u_char *p; + size_t size; + ngx_str_t name; + ngx_uint_t i, n; + ngx_rtmp_variable_t *v; + ngx_rtmp_regex_t *re; + ngx_rtmp_regex_variable_t *rv; + ngx_rtmp_core_main_conf_t *cmcf; + + rc->pool = cf->pool; + + if (ngx_regex_compile(rc) != NGX_OK) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V", &rc->err); + return NULL; + } + + re = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_regex_t)); + if (re == NULL) { + return NULL; + } + + re->regex = rc->regex; + re->ncaptures = rc->captures; + re->name = rc->pattern; + + cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); + cmcf->ncaptures = ngx_max(cmcf->ncaptures, re->ncaptures); + + n = (ngx_uint_t) rc->named_captures; + + if (n == 0) { + return re; + } + + rv = ngx_palloc(rc->pool, n * sizeof(ngx_rtmp_regex_variable_t)); + if (rv == NULL) { + return NULL; + } + + re->variables = rv; + re->nvariables = n; + + size = rc->name_size; + p = rc->names; + + for (i = 0; i < n; i++) { + rv[i].capture = 2 * ((p[0] << 8) + p[1]); + + name.data = &p[2]; + name.len = ngx_strlen(name.data); + + v = ngx_rtmp_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE); + if (v == NULL) { + return NULL; + } + + rv[i].index = ngx_rtmp_get_variable_index(cf, &name); + if (rv[i].index == NGX_ERROR) { + return NULL; + } + + v->get_handler = ngx_rtmp_variable_not_found; + + p += size; + } + + return re; +} + + +ngx_int_t +ngx_rtmp_regex_exec(ngx_rtmp_session_t *s, ngx_rtmp_regex_t *re, ngx_str_t *str) +{ + ngx_int_t rc, index; + ngx_uint_t i, n, len; + ngx_rtmp_variable_value_t *vv; + ngx_rtmp_core_main_conf_t *cmcf; + + cmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_core_module); + + if (re->ncaptures) { + len = cmcf->ncaptures; + + if (s->captures == NULL) { + s->captures = ngx_palloc(s->pool, len * sizeof(int)); + if (s->captures == NULL) { + return NGX_ERROR; + } + } + + } else { + len = 0; + } + + rc = ngx_regex_exec(re->regex, str, s->captures, len); + + if (rc == NGX_REGEX_NO_MATCHED) { + return NGX_DECLINED; + } + + if (rc < 0) { + ngx_log_error(NGX_LOG_ALERT, s->log, 0, + ngx_regex_exec_n " failed: %i on \"%V\" using \"%V\"", + rc, str, &re->name); + return NGX_ERROR; + } + + for (i = 0; i < re->nvariables; i++) { + + n = re->variables[i].capture; + index = re->variables[i].index; + vv = &s->variables[index]; + + vv->len = s->captures[n + 1] - s->captures[n]; + vv->valid = 1; + vv->no_cacheable = 0; + vv->not_found = 0; + vv->data = &str->data[s->captures[n]]; + +#if (NGX_DEBUG) + { + ngx_rtmp_variable_t *v; + + v = cmcf->variables.elts; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, s->log, 0, + "http regex set $%V to \"%v\"", &v[index].name, vv); + } +#endif + } + + s->ncaptures = rc * 2; + s->captures_data = str->data; + + return NGX_OK; +} + +#endif + + +ngx_int_t +ngx_rtmp_variables_add_core_vars(ngx_conf_t *cf) +{ + ngx_rtmp_variable_t *cv, *v; + ngx_rtmp_core_main_conf_t *cmcf; + + cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); + + cmcf->variables_keys = ngx_pcalloc(cf->temp_pool, + sizeof(ngx_hash_keys_arrays_t)); + if (cmcf->variables_keys == NULL) { + return NGX_ERROR; + } + + cmcf->variables_keys->pool = cf->pool; + cmcf->variables_keys->temp_pool = cf->pool; + + if (ngx_hash_keys_array_init(cmcf->variables_keys, NGX_HASH_SMALL) + != NGX_OK) + { + return NGX_ERROR; + } + + if (ngx_array_init(&cmcf->prefix_variables, cf->pool, 8, + sizeof(ngx_rtmp_variable_t)) + != NGX_OK) + { + return NGX_ERROR; + } + + for (cv = ngx_rtmp_core_variables; cv->name.len; cv++) { + v = ngx_rtmp_add_variable(cf, &cv->name, cv->flags); + if (v == NULL) { + return NGX_ERROR; + } + + *v = *cv; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_variables_init_vars(ngx_conf_t *cf) +{ + size_t len; + ngx_uint_t i, n; + ngx_hash_key_t *key; + ngx_hash_init_t hash; + ngx_rtmp_variable_t *v, *av, *pv; + ngx_rtmp_core_main_conf_t *cmcf; + + /* set the handlers for the indexed http variables */ + + cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); + + v = cmcf->variables.elts; + pv = cmcf->prefix_variables.elts; + key = cmcf->variables_keys->keys.elts; + + for (i = 0; i < cmcf->variables.nelts; i++) { + + for (n = 0; n < cmcf->variables_keys->keys.nelts; n++) { + + av = key[n].value; + + if (v[i].name.len == key[n].key.len + && ngx_strncmp(v[i].name.data, key[n].key.data, v[i].name.len) + == 0) + { + v[i].get_handler = av->get_handler; + v[i].data = av->data; + + av->flags |= NGX_HTTP_VAR_INDEXED; + v[i].flags = av->flags; + + av->index = i; + + if (av->get_handler == NULL + || (av->flags & NGX_HTTP_VAR_WEAK)) + { + break; + } + + goto next; + } + } + + len = 0; + av = NULL; + + for (n = 0; n < cmcf->prefix_variables.nelts; n++) { + if (v[i].name.len >= pv[n].name.len && v[i].name.len > len + && ngx_strncmp(v[i].name.data, pv[n].name.data, pv[n].name.len) + == 0) + { + av = &pv[n]; + len = pv[n].name.len; + } + } + + if (av) { + v[i].get_handler = av->get_handler; + v[i].data = (uintptr_t) &v[i].name; + v[i].flags = av->flags; + + goto next; + } + + if (v[i].get_handler == NULL) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "unknown \"%V\" variable", &v[i].name); + + return NGX_ERROR; + } + + next: + continue; + } + + + for (n = 0; n < cmcf->variables_keys->keys.nelts; n++) { + av = key[n].value; + + if (av->flags & NGX_HTTP_VAR_NOHASH) { + key[n].key.data = NULL; + } + } + + + hash.hash = &cmcf->variables_hash; + hash.key = ngx_hash_key; + hash.max_size = cmcf->variables_hash_max_size; + hash.bucket_size = cmcf->variables_hash_bucket_size; + hash.name = "variables_hash"; + hash.pool = cf->pool; + hash.temp_pool = NULL; + + if (ngx_hash_init(&hash, cmcf->variables_keys->keys.elts, + cmcf->variables_keys->keys.nelts) + != NGX_OK) + { + return NGX_ERROR; + } + + cmcf->variables_keys = NULL; + + return NGX_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_variables.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_variables.h new file mode 100644 index 0000000000..3b85ee4b75 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_variables.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#ifndef _NGX_RTMP_VARIABLES_H_INCLUDED_ +#define _NGX_RTMP_VARIABLES_H_INCLUDED_ + + +#include +#include +#include "ngx_rtmp.h" + + +typedef ngx_variable_value_t ngx_rtmp_variable_value_t; + +#define ngx_rtmp_variable(v) { sizeof(v) - 1, 1, 0, 0, 0, (u_char *) v } + +typedef struct ngx_rtmp_variable_s ngx_rtmp_variable_t; + +typedef void (*ngx_rtmp_set_variable_pt) (ngx_rtmp_session_t *s, + ngx_rtmp_variable_value_t *v, uintptr_t data); +typedef ngx_int_t (*ngx_rtmp_get_variable_pt) (ngx_rtmp_session_t *s, + ngx_rtmp_variable_value_t *v, uintptr_t data); + + +#define NGX_RTMP_VAR_CHANGEABLE 1 +#define NGX_RTMP_VAR_NOCACHEABLE 2 +#define NGX_RTMP_VAR_INDEXED 4 +#define NGX_RTMP_VAR_NOHASH 8 +#define NGX_RTMP_VAR_WEAK 16 +#define NGX_RTMP_VAR_PREFIX 32 + + +struct ngx_rtmp_variable_s { + ngx_str_t name; /* must be first to build the hash */ + ngx_rtmp_set_variable_pt set_handler; + ngx_rtmp_get_variable_pt get_handler; + uintptr_t data; + ngx_uint_t flags; + ngx_uint_t index; +}; + + +ngx_rtmp_variable_t *ngx_rtmp_add_variable(ngx_conf_t *cf, ngx_str_t *name, + ngx_uint_t flags); +ngx_int_t ngx_rtmp_get_variable_index(ngx_conf_t *cf, ngx_str_t *name); +ngx_rtmp_variable_value_t *ngx_rtmp_get_indexed_variable(ngx_rtmp_session_t *s, + ngx_uint_t index); +ngx_rtmp_variable_value_t *ngx_rtmp_get_flushed_variable(ngx_rtmp_session_t *s, + ngx_uint_t index); + +ngx_rtmp_variable_value_t *ngx_rtmp_get_variable(ngx_rtmp_session_t *s, + ngx_str_t *name, ngx_uint_t key); + +ngx_int_t ngx_rtmp_variable_unknown_header(ngx_rtmp_variable_value_t *v, + ngx_str_t *var, ngx_list_part_t *part, size_t prefix); + + +typedef struct { + ngx_hash_combined_t hash; +#if (NGX_PCRE) + ngx_rtmp_map_regex_t *regex; + ngx_uint_t nregex; +#endif +} ngx_rtmp_map_t; + + +void *ngx_rtmp_map_find(ngx_rtmp_session_t *s, ngx_rtmp_map_t *map, + ngx_str_t *match); + + +ngx_int_t ngx_rtmp_variables_add_core_vars(ngx_conf_t *cf); +ngx_int_t ngx_rtmp_variables_init_vars(ngx_conf_t *cf); + +ngx_int_t ngx_rtmp_variable_transform_index(ngx_conf_t *cf, + ngx_str_t *origin, ngx_str_t *target); +ngx_int_t ngx_rtmp_fetch_variable(ngx_rtmp_session_t *s, ngx_pool_t *pool, + ngx_str_t *origin, ngx_str_t *target); + +extern ngx_rtmp_variable_value_t ngx_rtmp_variable_null_value; +extern ngx_rtmp_variable_value_t ngx_rtmp_variable_true_value; + + +#endif diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_version.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_version.h new file mode 100644 index 0000000000..1bdce4984b --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-rtmp-module/ngx_rtmp_version.h @@ -0,0 +1,15 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#ifndef _NGX_RTMP_VERSION_H_INCLUDED_ +#define _NGX_RTMP_VERSION_H_INCLUDED_ + + +#define nginx_rtmp_version 1001004 +#define NGINX_RTMP_VERSION "1.1.4" + + +#endif /* _NGX_RTMP_VERSION_H_INCLUDED_ */ diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/COPYRIGHT b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/COPYRIGHT new file mode 100644 index 0000000000..837a6682d6 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/COPYRIGHT @@ -0,0 +1,24 @@ +Copyright (C) 2016-2020, by Jie Wu "AlexWoo" . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/README.md b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/README.md new file mode 100644 index 0000000000..672e6a0fd4 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/README.md @@ -0,0 +1,37 @@ +# Module nginx-toolkit-module +--- +## Instructions + +Nginx event toolkit modules. It contains modules below, and will add more in the future. + +- [ngx-event-timer-module](doc/ngx-event-timer-module.md) + + Independent timer for nginx + +- [ngx-event-resolver-module](doc/ngx-event-resolver-module.md) + + Common resovler in event modules, just like http resolver, stream resolver in nginx, but can be used by http modules, stream modules and other modules + +- [ngx-dynamic-resolver-module](doc/ngx-dynamic-resolver-module.md) + + System will resolver domain in dynamic resolver every few seconds configured + + The module will return addr whose domain is resolved in dynamic resolver, otherwise, the module will add domain into dynamic resolver, resolv domain by event resolver, and call callback when resolved + +- [ngx-dynamic-conf-module](doc/ngx-dynamic-conf-module.md) + + System will reload conf when nginx dynamic config file change. Developer can use this module to reload file without reload nginx worker + + Now it support NGX\_CORE\_MODULE and NGX\_HTTP\_MODULE + +- [ngx-map](doc/ngx-map.md) + + A map implement use ngx\_rbtree + +- [ngx-rbuf](doc/ngx-rbuf.md) + + A recycled chainbuf for nginx + +- [ngx-toolkit-misc](doc/ngx-toolkit-misc.md) + + Misc toolkit functions diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/config b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/config new file mode 100644 index 0000000000..6f476df6be --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/config @@ -0,0 +1,47 @@ +ngx_addon_name=ngx_toolkit_module + + +CORE_MODULES="$CORE_MODULES \ + ngx_http_dynamic_module \ + " + +EVENT_MODULES="$EVENT_MODULES \ + ngx_event_timer_module \ + ngx_dynamic_conf_module \ + ngx_event_resolver_module \ + ngx_dynamic_resolver_module \ + " + +HTTP_MODULES="$HTTP_MODULES \ + ngx_http_dynamic_core_module \ + ngx_http_trace_module \ + " + +NGX_ADDON_SRCS="$NGX_ADDON_SRCS \ + $ngx_addon_dir/ngx_event_timer_module.c \ + $ngx_addon_dir/ngx_event_resolver.c \ + $ngx_addon_dir/ngx_dynamic_resolver.c \ + $ngx_addon_dir/ngx_dynamic_conf.c \ + $ngx_addon_dir/ngx_http_dynamic.c \ + $ngx_addon_dir/ngx_toolkit_misc.c \ + $ngx_addon_dir/ngx_map.c \ + $ngx_addon_dir/ngx_rbuf.c \ + $ngx_addon_dir/ngx_poold.c \ + $ngx_addon_dir/ngx_timerd.c \ + $ngx_addon_dir/ngx_http_trace_module.c \ + " + +NGX_ADDON_DEPS="$NGX_ADDON_DEPS \ + $ngx_addon_dir/ngx_event_timer_module.h \ + $ngx_addon_dir/ngx_event_resolver.h \ + $ngx_addon_dir/ngx_dynamic_resolver.h \ + $ngx_addon_dir/ngx_dynamic_conf.h \ + $ngx_addon_dir/ngx_http_dynamic.h \ + $ngx_addon_dir/ngx_toolkit_misc.h \ + $ngx_addon_dir/ngx_map.h \ + $ngx_addon_dir/ngx_rbuf.h \ + $ngx_addon_dir/ngx_poold.h \ + $ngx_addon_dir/ngx_timerd.h \ + " + +CFLAGS="$CFLAGS -I $ngx_addon_dir" diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/doc/ngx-dynamic-conf-module.md b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/doc/ngx-dynamic-conf-module.md new file mode 100644 index 0000000000..31264bd695 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/doc/ngx-dynamic-conf-module.md @@ -0,0 +1,343 @@ +# Module ngx-dynamic-conf-module +--- +## Instructions + +System will reload conf when nginx dynamic config file change. Developer can use this module to reload file without reload nginx worker. + +Now it support NGX\_CORE\_MODULE and NGX\_HTTP\_MODULE + +## Directives + + Syntax : dynamic_conf dynamic_file time; + Default : - + Context : main + +Set dynamic config file and interval system checked file changed. + + Syntax : dynamic_log log_file level; + Default : - + Context : main + +Set dynamic conf load log file and log level. If not configured, use cycle log as default. + +Example: + + dynamic_conf conf/nginx_dynamic.conf 10; + dynamic_log logs/error_dynamic.log info; + +## API + +### MAIN dynamic conf + +**header file** + +For using this API, You should include the header file as below: + + #include "ngx_dynamic_conf.h" + +**dynamic module define** + + typedef struct { + ngx_str_t name; + void *(*create_conf)(ngx_conf_t *cf); + char *(*init_conf)(ngx_conf_t *cf, void *conf); + } ngx_dynamic_core_module_t; + +dynamic conf module define as below + + ngx_module_t ngx_test_module = { + NGX_MODULE_V1, + &ngx_test_module_ctx, /* module context */ + ngx_test_commands, /* module directives */ + NGX_CORE_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + (uintptr_t) &ngx_test_module_dctx, /* module dynamic context */ + (uintptr_t) ngx_test_dcommands, /* module dynamic directives */ + NGX_MODULE_V1_DYNAMIC_PADDING + }; + +**module dynamic context** struct define as above, **module dynamic directives** define use ngx\_command\_t. Use ngx\_dynamic\_core\_test\_module define in t/ngx\_dynamic\_conf\_test\_module.c as reference + +**ngx\_dynamic\_conf\_parse** + + ngx_int_t ngx_dynamic_conf_parse(ngx_conf_t *cf, unsigned init) + +- return value: + + - return NGX\_OK for successd, NGX\_ERROR for failed + +- paras: + + - cf : ngx\_conf\_t passed from ngx\_dynamic\_conf\_load_conf + - init : only ngx\_dynamic\_conf\_load\_conf set 1, otherwise set 0 + +This interface is supported for other dynamic conf module, such as ngx\_conf\_parse + +**ngx\_dynamic\_regex\_compile** + + typedef struct { + ngx_regex_t *regex; + ngx_str_t name; + } ngx_dynamic_regex_t; + + ngx_dynamic_regex_t *ngx_dynamic_regex_compile(ngx_conf_t *cf, + ngx_regex_compile_t *rc); + +- return value: + + - return regex context + +- paras: + + - cf: ngx\_conf\_t passed in dynamic cmd handler + - rc: regex options + +compile regex + +**ngx\_get\_dconf** + + void *ngx_get_dconf(ngx_module_t *m) + +return NGX\_CORE\_MODULE dynamic config for module + +### HTTP dynamic conf + +**header file** + +For using this API, You should include the header file as below: + + #include "ngx_dynamic_conf.h" + +**dynamic module define** + +dynamic conf module define is same as MAIN dynamic conf + +http dynamic conf context define as below: + + typedef struct { + void *(*create_main_conf)(ngx_conf_t *cf); + char *(*init_main_conf)(ngx_conf_t *cf, void *conf); + + void *(*create_srv_conf)(ngx_conf_t *cf); + char *(*init_srv_conf)(ngx_conf_t *cf, void *conf); + + void *(*create_loc_conf)(ngx_conf_t *cf); + char *(*init_loc_conf)(ngx_conf_t *cf, void *conf); + } ngx_http_dynamic_module_t; + +**notice:** http dynamic conf do not support merge + +**ngx\_http\_get\_module\_main\_dconf** + + void *ngx_http_get_module_main_dconf(ngx_http_request_t *r, ngx_module_t *m); + +return http request main dynamic conf for module m + +**ngx\_http\_get\_module\_srv\_dconf** + + void *ngx_http_get_module_srv_dconf(ngx_http_request_t *r, ngx_module_t *m); + +return http request srv dynamic conf for module m + +**ngx\_http\_get\_module\_loc\_dconf** + + void *ngx_http_get_module_loc_dconf(ngx_http_request_t *r, ngx_module_t *m); + +return http request loc dynamic conf for module m + +## Build + +cd to NGINX source directory & run this: + + ./configure --add-module=/path/to/nginx-toolkit-module/ + make && make install + +## Example + +See + +- t/ngx\_dynamic\_conf\_test\_module.c as MAIN conf for usage of dynamic conf +- t/ngx\_http\_dynamic\_test\_module.c as HTTP conf for usage of http dynamic conf + +**Build:** + + ./configure --with-debug --add-module=/path/to/nginx-toolkit-module/ --add-module=/path/to/nginx-toolkit-module/t + make && make install + +**Configure:** + + dynamic_conf conf/nginx_dynamic.conf 10; + dynamic_log logs/error_dynamic.log info; + + http { + + ... + + server { + + ... + + location /dynamic_conf_test/ { + dynamic_conf_test; + } + } + } + +**Dynamic Configure:** + + dynamic_test_i 200; + dynamic_test_s hello_world; + + http { + main_int 1000; + main_str gogogo; + + #defult server + server { + srv_int 1; + srv_str default; + } + + #wildcard_head + server { + srv_int 2; + srv_str wildcard_head; + serverid baidu; + server_name *.baidu.com; + } + + #wildcard_tail + server { + srv_int 3; + srv_str wildcard_tail; + serverid google; + server_name www.google.*; + } + + #hash + server { + srv_int 4; + srv_str hash; + serverid sina; + server_name sports.sina.com.cn; + + location = / { + loc_int 1; + loc_str =/; + } + + location / { + loc_int 2; + loc_str /; + } + + location ^~ /test1/ { + loc_int 3; + loc_str ^~/test1/; + } + + location ~* \.(gif|jpg|jpeg)$ { + loc_int 4; + loc_str ~*\.(gif|jpg|jpeg)$; + } + + location /test { + loc_int 5; + loc_str /test; + } + } + + #pcre + server { + srv_int 5; + srv_str pcre; + serverid test; + server_name ~^flv(?!.*(dl\.))[A-Za-z0-9]*\.test\.com$; + } + + #multi + server { + srv_int 6; + srv_str multi; + serverid others; + server_name ~^flv(?!.*(dl\.))[A-Za-z0-9]*\.haha\.com$ www.sohu.com; + server_name *.qq.com; + } + } + +**Test:** + +- Main for dynamic config + + get conf configured in dynamic config file for test module + + curl -v 'http://127.0.0.1/dynamic_conf_test/test' + + change config in dynamic config, the test api will return new config value after dynamic conf refresh + +- Main for http + + curl -v 'http://127.0.0.1/' + + change config in http block of dynamic config, the test api will return new config value after dynamic conf refresh + +- Server for http + + - defult server + + curl -v 'http://127.0.0.1/http_dynamic_test/test' + curl -v -H 'Host: github.com' 'http://127.0.0.1/http_dynamic_test/test' + + - wildcard_head + + curl -v -H 'Host: map.baidu.com' 'http://127.0.0.1/http_dynamic_test/test' + + - wildcard_tail + + curl -v -H 'Host: www.google.co.jp' 'http://127.0.0.1/http_dynamic_test/test' + + - hash + + curl -v -H 'Host: sports.sina.com.cn' 'http://127.0.0.1/http_dynamic_test/test' + + - pcre + + curl -v -H 'Host: flvdl7a8e4223.test.com' 'http://127.0.0.1/http_dynamic_test/test' + + - multi + + curl -v -H 'Host: flvdl7a8e4223.haha.com' 'http://127.0.0.1/http_dynamic_test/test' + curl -v -H 'Host: www.sohu.com' 'http://127.0.0.1/http_dynamic_test/test' + curl -v -H 'Host: v.qq.com' 'http://127.0.0.1/http_dynamic_test/test' + +- Location for http + + - no location + + curl -v -H 'Host: flvdl7a8e4223.haha.com' 'http://127.0.0.1/' + + - location = / + + curl -v -H 'Host: sports.sina.com.cn' 'http://127.0.0.1/' + + - location / + + curl -v -H 'Host: sports.sina.com.cn' 'http://127.0.0.1/t' + + - location ^~ /test1/ + + curl -v -H 'Host: sports.sina.com.cn' 'http://127.0.0.1/test1/123' + + - ~* \.(gif|jpg|jpeg)$ + + curl -v -H 'Host: sports.sina.com.cn' 'http://127.0.0.1/test/123.gif' + + - /test + + curl -v -H 'Host: sports.sina.com.cn' 'http://127.0.0.1/test/123' diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/doc/ngx-dynamic-resolver-module.md b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/doc/ngx-dynamic-resolver-module.md new file mode 100644 index 0000000000..4eeda28fd4 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/doc/ngx-dynamic-resolver-module.md @@ -0,0 +1,157 @@ +# Module ngx-dynamic-resolver-module +--- +## Instructions + +System will resolver domain in dynamic resolver every few seconds configured. + +The module will return addr whose domain is resolved in dynamic resolver, otherwise, the module will add domain into dynamic resolver, resolv domain by event resolver, and call callback when resolved. + +## Directives + + Syntax : dynamic_refresh_interval time; + Default : dynamic_refresh_interval 5m; + Context : events + +Set time interval for DNS query frequency, 0 for shutdown this function. + + Syntax : dynamic_domain_buckets number; + Default : dynamic_domain_buckets 101; + Context : events + +Bucket for dynamic resolver domain hash table. Use prime key is Recommended. + +Example: + + events { + resolver 192.168.84.254 valid=1m; + dynamic_refresh_interval 5m; + dynamic_domain_buckets 1001; + } + +## API + +**header file** + +For using this API, You should include the header file as below: + + #include "ngx_dynamic_resolver.h" + +**start resolver** + + void ngx_dynamic_resolver_start_resolver(ngx_str_t *domain, + ngx_dynamic_resolver_handler_pt h, void *data); + +- return value: + + - None + +- paras: + + - domain: domain for DNS query + - h : callback handler + - data : data for callback + +h's protype is: + + typedef void (* ngx_dynamic_resolver_handler_pt)(void *data, + struct sockaddr *sa, socklen_t socklen); + +- return value: + + - None + +- paras: + + - data : user private data set in ngx\_dynamic\_resolver\_start\_resolver + - sa : sock address get + - socklen: sock address len, 0 for get none address + +**gethostbyname** + + socklen_t ngx_dynamic_resolver_gethostbyname(ngx_str_t *domain, struct sockaddr *sa); + +- return value: + + - socklen for successd + - 0 for failed + +- paras: + + - domain: domain for query + - sa : sock address get + +**add domain** + + void ngx_dynamic_resolver_add_domain(ngx_str_t *domain); + +- return value: + + - None + +- paras: + + - domain: domain for query + + +**del domain** + + void ngx_dynamic_resolver_del_domain(ngx_str_t *domain); + +- return value: + + - None + +- paras: + + - domain: domain for DNS query + +## Build + +cd to NGINX source directory & run this: + + ./configure --add-module=/path/to/nginx-toolkit-module/ + make && make install + +## Example + +See t/ngx\_dynamic\_resolver\_test\_module.c as reference + +Build: + + ./configure --with-debug --add-module=/path/to/nginx-toolkit-module/ --add-module=/path/to/nginx-toolkit-module/t + make && make install + +Configure: + + events { + resolver 192.168.84.254 valid=1m; + dynamic_refresh_interval 5m; + } + + http { + + ... + + server { + + ... + + location /dynamic_resolver_test/ { + dynamic_resolver_test 192.168.84.4 www.test1.com; + } + } + } + +Test: + +- sync resolver + + curl -v 'http://127.0.0.1/dynamic_resolver_test/resolver?domain=www.test1.com&sync=1' + +- start resolver + + curl -v 'http://127.0.0.1/dynamic_resolver_test/resolver?domain=www.test.com' + +- del domain + + curl -XDELETE -v 'http://127.0.0.1/dynamic_resolver_test/resolver?domain=www.test.com' diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/doc/ngx-event-resolver-module.md b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/doc/ngx-event-resolver-module.md new file mode 100644 index 0000000000..4f43e2f3d6 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/doc/ngx-event-resolver-module.md @@ -0,0 +1,126 @@ +# Module ngx-event-resolver-module +--- +## Instructions + +Common resovler in event modules, just like http resolver, stream resolver in nginx, but can be used by http modules, stream modules and other modules + +## Directives + + Syntax : resolver address ... [valid=time] [ipv6=on|off]; + Default : - + Context : events + +Configures name servers used to resolve names into addresses, for example: + +> resolver 127.0.0.1 [::1]:5353; + +An address can be specified as a domain name or IP address, and an optional port. If port is not specified, the port 53 is used. Name servers are queried in a round-robin fashion. + +By default, nginx will look up both IPv4 and IPv6 addresses while resolving. If looking up of IPv6 addresses is not desired, the ipv6=off parameter can be specified. + +By default, nginx caches answers using the TTL value of a response. The optional valid parameter allows overriding it: + +> resolver 127.0.0.1 [::1]:5353 valid=30s; + + Syntax : resolver_timeout time; + Default : resolver_timeout 60s; + Context : events + +Sets a timeout for name resolution, for example: + +> resolver\_timeout 5s; + +Example: + + events { + resolver 192.168.84.254 valid=20s; + resolver_timeout 10s; + } + +## API + +**header file** + +For using this API, You should include the header file as below: + + #include "ngx_event_resolver.h" + +**resolver a domain** + + void ngx_event_resolver_start_resolver(ngx_str_t *domain, + ngx_event_resolver_handler_pt h, void *data); + +- return value: + + None + +- paras: + + - domain: domain for resolving + - h : callback handler + - data : data for callback + +h's protype is: + + typedef void (* ngx_event_resolver_handler_pt)(void *data, + ngx_resolver_addr_t *addrs, ngx_uint_t naddrs); + +- return value: + + None + +- paras: + + - data : user private data set in ngx\_event\_resolver\_start\_resolver + - addrs : addrs resolv by DNS + - naddrs: number of addrs resolv by DNS + + +## Build + +cd to NGINX source directory & run this: + + ./configure --add-module=/path/to/nginx-toolkit-module/ + make && make install + +## Example + +See t/ngx\_event\_resolver\_test\_module.c as reference + +Build: + + ./configure --with-debug --add-module=/path/to/nginx-toolkit-module/ --add-module=/path/to/nginx-toolkit-module/t + make && make install + +Configure: + + events { + resolver 192.168.84.254 114.114.114.114 valid=20s; + resolver_timeout 10s; + } + + http { + + ... + + server { + + ... + + location /event_resolver_test/ { + event_resolver_test; + } + } + } + +Install bind server + + /path/to/nginx-toolkit-module/t/dns_install.sh + +modify /var/named/test.com.zone dns ip address to fit your enviroment + +Test: + +- add domain for resolving + + curl -v "http://127.0.0.1/event_resolver_test/domain?domain=www.test.com" diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/doc/ngx-event-timer-module.md b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/doc/ngx-event-timer-module.md new file mode 100644 index 0000000000..333d1206cf --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/doc/ngx-event-timer-module.md @@ -0,0 +1,112 @@ +# Module ngx-event-timer-module +--- +## Instructions + +Independent timer for nginx + +## Directives + + Syntax : worker_timers number; + Default : worker_timers 1024; + Context : events + +Sets the maximum number of timers that can be used in worker process. + + events { + ... + worker_timers 1024; + } + +## API + +**header file** + +For using this API, You should include the header file as below: + + #include "ngx_event_timer_module.h" + +**registe domain** + + ngx_int_t ngx_event_timer_add_timer(ngx_msec_t tv, + ngx_timer_handler_pt h, void *data); + +- return value: + + return timerid for successd, NGX_ERROR for failed. + + Error: + + - h is NULL + - not enough timer for assigned + +- paras: + + - tv : timer interval to trigger handler + - h : timer handler + - data : data of h para + +Register a timer handler, timer interval is tv, measured by millisecond. When timer triggered, h will be called, using data as function parameters. + +h's protype is: + + typedef void (* ngx_timer_handler_pt)(void *data); + +- return value: + + None + +- paras: + + - data: data set in ngx\_event\_timer\_add\_timer, for paras transmit + +**del timer** + + void ngx_event_timer_del_timer(ngx_uint_t timerid); + +- return value: + + void + +- paras: + + - timerid: return by ngx\_event\_timer\_add\_timer + +Deregister timer handler. + +## Build + +cd to NGINX source directory & run this: + + ./configure --add-module=/path/to/nginx-toolkit-module/ + make && make install + +## Example + +See t/ngx\_event\_timer\_test\_module.c as reference + +Build: + + ./configure --add-module=/path/to/nginx-toolkit-module/t/ --add-module=/path/to/nginx-toolkit-module/ + make && make install + +Configure: + + location /event_timer_test/ { + event_timer_test; + } + +Test: + +add timer + + curl -XPOST -v "http://127.0.0.1/event_timer_test/timer?time=3s" + +Test module will start a timer when process init. It will log after 3 seconds, set in args: + + 2016/12/10 18:48:37 [error] 20295#0: event timer test timer handler + +del timer + + curl -XDELETE -v "http://127.0.0.1/event_timer_test/timer?timerid=1" + +Test module will stop a timer whose timerid is 1 diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/doc/ngx-map.md b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/doc/ngx-map.md new file mode 100644 index 0000000000..52f094ddaa --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/doc/ngx-map.md @@ -0,0 +1,112 @@ +# ngx-map +--- +## Instructions + +A map implement use ngx_rbtree + +## API + +**header file** + +For using this API, You should include the header file as below: + + #include "ngx_map.h" + +**structure** + + typedef struct { + ngx_rbtree_t rbtree; + ngx_rbtree_node_t sentinel; + ngx_map_hash_pt hash; + ngx_cmp_pt cmp; + } ngx_map_t; + + typedef struct { + ngx_rbtree_node_t rn; + intptr_t raw_key; + ngx_map_t *map; + } ngx_map_node_t; + +use ngx\_map\_t instance as a map, use ngx\_map\_init to initial. Put ngx\_map\_node\_t in your structure, and set raw_key, then you can insert your node in the map. Use raw_key, you can find node in map, or delete node in map. + +**ngx\_map\_init** + + void ngx_map_init(ngx_map_t *map, ngx_map_hash_pt hash, ngx_cmp_pt cmp) + +Interface for init a map, hash is hash func handler to calculate hashkey for rawkey, cmp is compare func handler for compare two raw_key, when raw_keys' hashkey is same. + +We support base hash and cmp func as below: + + /* ngx_str_t */ + ngx_rbtree_key_t ngx_map_hash_str(intptr_t key) + int ngx_cmp_str(intptr_t key1, intptr_t key2) + + /* ngx_uint_t */ + ngx_rbtree_key_t ngx_map_hash_uint(intptr_t key) + int ngx_cmp_uint(intptr_t key1, intptr_t key2) + + /* ngx_int_t */ + ngx_rbtree_key_t ngx_map_hash_int(intptr_t key) + int ngx_cmp_int(intptr_t key1, intptr_t key2) + +User also can use own hash and cmp func with protype below: + + /* + * key: key for map node + */ + typedef ngx_rbtree_key_t (* ngx_map_hash_pt)(intptr_t key); + + /* + * if key1 < key2, return -1 + * if key1 = key2, return 0 + * if key1 > key2, return 1 + */ + typedef int (* ngx_cmp_pt)(intptr_t key1, intptr_t key2); + +**ngx\_map\_empty** + + #define ngx_map_empty(map) (map->rbtree.root == map->rbtree.sentinel) + +If map is empty, return 1, else, return false + +**ngx\_map\_begin** + + ngx_map_node_t *ngx_map_begin(ngx_map_t *map) + +Return the mininum key node of map + +**ngx\_map\_rbegin** + + ngx_map_node_t *ngx_map_rbegin(ngx_map_t *map) + +Return the maxinum key node of map + +**ngx\_map\_next** + + ngx_map_node_t *ngx_map_next(ngx_map_node_t *n) + +Return the next node of n, if n is the maximum key node in map, return NULL + +**ngx\_map\_prev** + + ngx_map_node_t *ngx_map_prev(ngx_map_node_t *n) + +Return the previous node of n, if n is the mininum key node in map, return NULL + +**ngx\_map\_insert** + + void ngx_map_insert(ngx_map_t *map, ngx_map_node_t *node, ngx_flag_t covered) + +Interface for insert a node in map. New node will force replace old node in map if raw_key is same when covered set to 1. Otherwise, new node can't insert in map + +**ngx\_map\_delete** + + void ngx_map_delete(ngx_map_t *map, intptr_t key) + +Interface for delete a node in map, if node's raw_key equal to key in paras. If node is not exist, do nothing. + +**ngx\_map\_find** + + ngx_map_node_t *ngx_map_find(ngx_map_t *map, intptr_t key) + +Interface for find a node in map, if node's raw_key equal to key in paras. If node is not exist, return NULL. \ No newline at end of file diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/doc/ngx-poold.md b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/doc/ngx-poold.md new file mode 100644 index 0000000000..a631ffa7cd --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/doc/ngx-poold.md @@ -0,0 +1,19 @@ +# ngx-poold +--- +## Instructions + +Pool debug for check whether pool is destroy or destroy twice + +## API + +**NGX\_CREATE\_POOL** + + #define NGX_CREATE_POOL(size, log) + +Replace NGX\_CREATE\_POOL instead of ngx\_create\_pool, it will record position creating pool. must use with NGX\_DESTROY\_POOL + +**NGX\_DESTROY\_POOL** + + #define NGX_DESTROY_POOL(pool) + +Replace NGX\_DESTROY\_POOL instead of ngx\_destroy\_pool, it will delete info which NGX\_CREATE\_POOL add. If pool not register in record pool, the pool will not destroy, and log delete twice log and position call the NGX\_DESTROY\_POOL. \ No newline at end of file diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/doc/ngx-rbuf.md b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/doc/ngx-rbuf.md new file mode 100644 index 0000000000..84d22998d2 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/doc/ngx-rbuf.md @@ -0,0 +1,40 @@ +# ngx-rbuf +--- +## Instructions + +A recycled chainbuf for nginx + +## API + +**header file** + +For using this API, You should include the header file as below: + + #include "ngx_rbuf.h" + +**ngx\_get\_chainbuf** + + ngx_chain_t *ngx_get_chainbuf(size_t size, ngx_flag_t alloc_rbuf); + +Interface for get a chain with buf, if alloc\_rbuf is set to 1, rbuf will alloc a buf with size set in paras; if alloc\_rbuf is set to 0, size is nonsense here. + +Notice, for recycled buf better memory manager, the size should be same for the same usage. Such as chunk size in nginx rtmp module. + + +**ngx\_put\_chainbuf** + + void ngx_put_chainbuf(ngx_chain_t *cl); + +Interface for recycle chain with buf alloc from rbuf + +**ngx\_rbuf\_statee** + + ngx_chain_t *ngx_rbuf_state(ngx_http_request_t *r); + +Interface for query rbuf state, result: + + ngx_rbuf_nalloc_node: 1 + ngx_rbuf_nalloc_buf: 1 + ngx_rbuf_nfree_buf: 1 + ngx_rbuf_nalloc_chain: 3 + ngx_rbuf_nalloc_chain: 3 diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/doc/ngx-toolkit-misc.md b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/doc/ngx-toolkit-misc.md new file mode 100644 index 0000000000..23e61be21b --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/doc/ngx-toolkit-misc.md @@ -0,0 +1,89 @@ +# ngx-toolkit-misc +--- +## Instructions + +Misc toolkit functions + +## API + +**header file** + +For using this API, You should include the header file as below: + + #include "ngx_toolkit_misc.h" + +**structure** + + /* + * scheme://[user@]host[:port]/path[?args][#fragment] + */ + typedef struct { + ngx_str_t scheme; + ngx_str_t user; + ngx_str_t host; + ngx_str_t port; + ngx_str_t path; + ngx_str_t args; + ngx_str_t fragment; + + ngx_str_t host_with_port; /* host[:port] */ + ngx_str_t uri_with_args; /* /path[?args][#fragment] */ + } ngx_request_url_t; + +**ngx\_parse\_request\_url** + + ngx_int_t ngx_parse_request_url(ngx_request_url_t *request_url, ngx_str_t *url); + +parse request url format as: scheme://[user@]host[:port]/path[?args][#fragment] + +- return value: + + - NGX_OK: parse success + - NGX_ERROR: request url format error + +- paras: + + - request_url: url parse result return to user, all paras in request url is segment point to url + - url: request url for parse + + +**ngx\_request\_port** + + in_port_t ngx_request_port(ngx_str_t *scheme, ngx_str_t *port); + +convert port to in_port_t according to scheme and port + +- return value: + + - If port is set to correct number range in [1, 65535], return port + - If port is set to non correct value, return 0 + - If port is not set, return default value for scheme: + + - 80 for http + - 443 for https + - 1935 for rtmp + - 0 for others now + +- values: + + - scheme : sheme string like http https or rtmp + - port : port for convert to in_port_t + + +**ngx\_md5\_file** + + #define NGX_MD5KEY_LEN 32 + + ngx_int_t ngx_md5_file(ngx_fd_t fd, u_char md5key[NGX_MD5KEY_LEN]); + +calculating file md5key as md5sum in shell + +- return value: + + - NGX_OK: calculating success + - NGX_ERROR: calculating error such as file is not exist + +- paras: + + - fd: file desc for calculating md5key + - md5key: md5key result \ No newline at end of file diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_dynamic_conf.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_dynamic_conf.c new file mode 100644 index 0000000000..fd8f15b8f6 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_dynamic_conf.c @@ -0,0 +1,963 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#include "ngx_dynamic_conf.h" +#include "ngx_toolkit_misc.h" +#include "ngx_event_timer_module.h" + + +static ngx_int_t ngx_dynamic_conf_process_init(ngx_cycle_t *cycle); + +static void *ngx_dynamic_conf_module_create_conf(ngx_cycle_t *cycle); +static char *ngx_dynamic_conf_module_init_conf(ngx_cycle_t *cycle, void *conf); + +static char *ngx_dynamic_conf(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static char *ngx_dynamic_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); + + +#define NGX_CONF_BUFFER 4096 + +ngx_uint_t ngx_core_max_module; + +static ngx_uint_t arg_number[] = { + NGX_CONF_NOARGS, + NGX_CONF_TAKE1, + NGX_CONF_TAKE2, + NGX_CONF_TAKE3, + NGX_CONF_TAKE4, + NGX_CONF_TAKE5, + NGX_CONF_TAKE6, + NGX_CONF_TAKE7 +}; + +typedef struct { + void **module_conf; /* index is module index */ + ngx_pool_t *pool; /* dynamic conf alloc pool */ +} ngx_dynamic_conf_ctx_t; + +typedef struct { + ngx_dynamic_conf_ctx_t *conf[2]; + unsigned used; /* 0 and 1 for index of conf */ + + ngx_str_t file; + ngx_msec_t refresh; + ngx_log_t *log; + + u_char md5key[NGX_MD5KEY_LEN + 1]; +} ngx_dynamic_conf_conf_t; + + +static ngx_command_t ngx_dynamic_conf_commands[] = { + + { ngx_string("dynamic_conf"), + NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE2, + ngx_dynamic_conf, + 0, + 0, + NULL }, + + { ngx_string("dynamic_log"), + NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_1MORE, + ngx_dynamic_log, + 0, + 0, + NULL }, + + ngx_null_command +}; + + +ngx_core_module_t ngx_dynamic_conf_module_ctx = { + ngx_string("dynamic_conf"), + ngx_dynamic_conf_module_create_conf, + ngx_dynamic_conf_module_init_conf +}; + + +ngx_module_t ngx_dynamic_conf_module = { + NGX_MODULE_V1, + &ngx_dynamic_conf_module_ctx, /* module context */ + ngx_dynamic_conf_commands, /* module directives */ + NGX_CORE_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + ngx_dynamic_conf_process_init, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void * +ngx_dynamic_conf_module_create_conf(ngx_cycle_t *cycle) +{ + ngx_dynamic_conf_conf_t *conf; + + conf = ngx_pcalloc(cycle->pool, sizeof(ngx_dynamic_conf_conf_t)); + if (conf == NULL) { + return NULL; + } + + conf->refresh = NGX_CONF_UNSET_MSEC; + + return conf; +} + +static char * +ngx_dynamic_conf_module_init_conf(ngx_cycle_t *cycle, void *conf) +{ + ngx_dynamic_conf_conf_t *dccf; + + dccf = conf; + + ngx_conf_init_msec_value(dccf->refresh, 60000); + + if (dccf->log == NULL) { + dccf->log = &cycle->new_log; + } + + return NGX_CONF_OK; +} + +static char * +ngx_dynamic_conf(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_dynamic_conf_conf_t *dccf; + ngx_str_t *value; + ngx_fd_t fd; + + dccf = conf; + + if (dccf->file.len) { + return "is duplicate"; + } + + value = cf->args->elts; + + dccf->file = value[1]; + + if (ngx_conf_full_name(cf->cycle, &dccf->file, 0)) { + return NGX_CONF_ERROR; + } + + /* test file, ngx_conf_full_name will fill '\0' at end of file name */ + fd = ngx_open_file(dccf->file.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); + if (fd == NGX_INVALID_FILE) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno, "dynamic conf " + ngx_open_file_n " \"%s\" failed", dccf->file.data); + return NGX_CONF_ERROR; + } + ngx_close_file(fd); + + dccf->refresh = ngx_parse_time(&value[2], 0); + if (dccf->refresh == (ngx_msec_t) NGX_ERROR) { + return "invalid refresh"; + } + + dccf->used = 1; + + return NGX_CONF_OK; +} + +static char * +ngx_dynamic_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_dynamic_conf_conf_t *dccf = conf; + + return ngx_log_set_log(cf, &dccf->log); +} + + +static ngx_int_t +ngx_dynamic_conf_handler(ngx_conf_t *cf, ngx_int_t last) +{ + char *rv; + ngx_uint_t i, found; + ngx_str_t *name; + ngx_command_t *cmd; + void *conf; + + name = cf->args->elts; + + found = 0; + + for (i = 0; cf->cycle->modules[i]; ++i) { + + cmd = (ngx_command_t *) cf->cycle->modules[i]->spare_hook1; + if (cmd == NULL) { + continue; + } + + for (/* void */; cmd->name.len; ++cmd) { + + if (name->len != cmd->name.len) { + continue; + } + + if (ngx_strcmp(name->data, cmd->name.data) != 0) { + continue; + } + + found = 1; + + if (cf->cycle->modules[i]->type != NGX_CONF_MODULE + && cf->cycle->modules[i]->type != cf->module_type) + { + continue; + } + + /* is the directive's location right ? */ + + if (!(cmd->type & cf->cmd_type)) { + continue; + } + + if (!(cmd->type & NGX_CONF_BLOCK) && last != NGX_OK) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "directive \"%s\" is not terminated by \";\"", + name->data); + return NGX_ERROR; + } + + if ((cmd->type & NGX_CONF_BLOCK) && last != NGX_CONF_BLOCK_START) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "directive \"%s\" has no opening \"{\"", + name->data); + return NGX_ERROR; + } + + /* is the directive's argument count right ? */ + + if (!(cmd->type & NGX_CONF_ANY)) { + + if (cmd->type & NGX_CONF_FLAG) { + + if (cf->args->nelts != 2) { + goto invalid; + } + + } else if (cmd->type & NGX_CONF_1MORE) { + + if (cf->args->nelts < 2) { + goto invalid; + } + + } else if (cmd->type & NGX_CONF_2MORE) { + + if (cf->args->nelts < 3) { + goto invalid; + } + + } else if (cf->args->nelts > NGX_CONF_MAX_ARGS) { + + goto invalid; + + } else if (!(cmd->type & arg_number[cf->args->nelts - 1])) { + goto invalid; + } + } + + /* set up the directive's configuration context */ + + conf = ((void **) cf->ctx)[cf->cycle->modules[i]->ctx_index]; + + rv = cmd->set(cf, cmd, conf); + + if (rv == NGX_CONF_OK) { + return NGX_OK; + } + + if (rv == NGX_CONF_ERROR) { + return NGX_ERROR; + } + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"%s\" directive %s", name->data, rv); + + return NGX_ERROR; + } + } + + if (found) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"%s\" directive is not allowed here", name->data); + + return NGX_ERROR; + } + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "unknown directive \"%s\"", name->data); + + return NGX_ERROR; + +invalid: + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid number of arguments in \"%s\" directive", + name->data); + + return NGX_ERROR; +} + +/* same as ngx_conf_read_token in ngx_conf_file.c */ +static ngx_int_t +ngx_dynamic_conf_read_token(ngx_conf_t *cf) +{ + u_char *start, ch, *src, *dst; + off_t file_size; + size_t len; + ssize_t n, size; + ngx_uint_t found, need_space, last_space, sharp_comment, + variable; + ngx_uint_t quoted, s_quoted, d_quoted, start_line; + ngx_str_t *word; + ngx_buf_t *b; + + found = 0; + need_space = 0; + last_space = 1; + sharp_comment = 0; + variable = 0; + quoted = 0; + s_quoted = 0; + d_quoted = 0; + + cf->args->nelts = 0; + b = cf->conf_file->buffer; + start = b->pos; + start_line = cf->conf_file->line; + + file_size = ngx_file_size(&cf->conf_file->file.info); + + for ( ;; ) { + + if (b->pos >= b->last) { + + if (cf->conf_file->file.offset >= file_size) { + + if (cf->args->nelts > 0 || !last_space) { + + if (cf->conf_file->file.fd == NGX_INVALID_FILE) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "unexpected end of parameter, " + "expecting \";\""); + return NGX_ERROR; + } + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "unexpected end of file, " + "expecting \";\" or \"}\""); + return NGX_ERROR; + } + + return NGX_CONF_FILE_DONE; + } + + len = b->pos - start; + + if (len == NGX_CONF_BUFFER) { + cf->conf_file->line = start_line; + + if (d_quoted) { + ch = '"'; + + } else if (s_quoted) { + ch = '\''; + + } else { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "too long parameter \"%*s...\" started", + 10, start); + return NGX_ERROR; + } + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "too long parameter, probably " + "missing terminating \"%c\" character", ch); + return NGX_ERROR; + } + + if (len) { + ngx_memmove(b->start, start, len); + } + + size = (ssize_t) (file_size - cf->conf_file->file.offset); + + if (size > b->end - (b->start + len)) { + size = b->end - (b->start + len); + } + + n = ngx_read_file(&cf->conf_file->file, b->start + len, size, + cf->conf_file->file.offset); + + if (n == NGX_ERROR) { + return NGX_ERROR; + } + + if (n != size) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + ngx_read_file_n " returned " + "only %z bytes instead of %z", + n, size); + return NGX_ERROR; + } + + b->pos = b->start + len; + b->last = b->pos + n; + start = b->start; + } + + ch = *b->pos++; + + if (ch == LF) { + cf->conf_file->line++; + + if (sharp_comment) { + sharp_comment = 0; + } + } + + if (sharp_comment) { + continue; + } + + if (quoted) { + quoted = 0; + continue; + } + + if (need_space) { + if (ch == ' ' || ch == '\t' || ch == CR || ch == LF) { + last_space = 1; + need_space = 0; + continue; + } + + if (ch == ';') { + return NGX_OK; + } + + if (ch == '{') { + return NGX_CONF_BLOCK_START; + } + + if (ch == ')') { + last_space = 1; + need_space = 0; + + } else { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "unexpected \"%c\"", ch); + return NGX_ERROR; + } + } + + if (last_space) { + if (ch == ' ' || ch == '\t' || ch == CR || ch == LF) { + continue; + } + + start = b->pos - 1; + start_line = cf->conf_file->line; + + switch (ch) { + + case ';': + case '{': + if (cf->args->nelts == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "unexpected \"%c\"", ch); + return NGX_ERROR; + } + + if (ch == '{') { + return NGX_CONF_BLOCK_START; + } + + return NGX_OK; + + case '}': + if (cf->args->nelts != 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "unexpected \"}\""); + return NGX_ERROR; + } + + return NGX_CONF_BLOCK_DONE; + + case '#': + sharp_comment = 1; + continue; + + case '\\': + quoted = 1; + last_space = 0; + continue; + + case '"': + start++; + d_quoted = 1; + last_space = 0; + continue; + + case '\'': + start++; + s_quoted = 1; + last_space = 0; + continue; + + default: + last_space = 0; + } + + } else { + if (ch == '{' && variable) { + continue; + } + + variable = 0; + + if (ch == '\\') { + quoted = 1; + continue; + } + + if (ch == '$') { + variable = 1; + continue; + } + + if (d_quoted) { + if (ch == '"') { + d_quoted = 0; + need_space = 1; + found = 1; + } + + } else if (s_quoted) { + if (ch == '\'') { + s_quoted = 0; + need_space = 1; + found = 1; + } + + } else if (ch == ' ' || ch == '\t' || ch == CR || ch == LF + || ch == ';' || ch == '{') + { + last_space = 1; + found = 1; + } + + if (found) { + word = ngx_array_push(cf->args); + if (word == NULL) { + return NGX_ERROR; + } + + word->data = ngx_pnalloc(cf->pool, b->pos - 1 - start + 1); + if (word->data == NULL) { + return NGX_ERROR; + } + + for (dst = word->data, src = start, len = 0; + src < b->pos - 1; + len++) + { + if (*src == '\\') { + switch (src[1]) { + case '"': + case '\'': + case '\\': + src++; + break; + + case 't': + *dst++ = '\t'; + src += 2; + continue; + + case 'r': + *dst++ = '\r'; + src += 2; + continue; + + case 'n': + *dst++ = '\n'; + src += 2; + continue; + } + + } + *dst++ = *src++; + } + *dst = '\0'; + word->len = len; + + if (ch == ';') { + return NGX_OK; + } + + if (ch == '{') { + return NGX_CONF_BLOCK_START; + } + + found = 0; + } + } + } +} + +ngx_int_t +ngx_dynamic_conf_parse(ngx_conf_t *cf, unsigned init) +{ + ngx_int_t rc; + enum { + parse_init = 0, + parse_block + } type; + + type = init ? parse_init : parse_block; + + for (;;) { + rc = ngx_dynamic_conf_read_token(cf); + + /* + * ngx_conf_read_token() may return + * + * NGX_ERROR there is error + * NGX_OK the token terminated by ";" was found + * NGX_CONF_BLOCK_START the token terminated by "{" was found + * NGX_CONF_BLOCK_DONE the "}" was found + * NGX_CONF_FILE_DONE the configuration file is done + */ + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_CONF_BLOCK_DONE) { + + if (type != parse_block) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unexpected \"}\""); + return NGX_ERROR; + } + + return NGX_OK; + } + + if (rc == NGX_CONF_FILE_DONE) { + + if (type == parse_block) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "unexpected end of file, expecting \"}\""); + return NGX_ERROR; + } + + return NGX_OK; + } + + rc = ngx_dynamic_conf_handler(cf, rc); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + } +} + +static ngx_int_t +ngx_dynamic_conf_load_conf(ngx_fd_t fd, ngx_dynamic_conf_conf_t *dccf) +{ + ngx_dynamic_conf_ctx_t *conf; + ngx_dynamic_core_module_t *module; + ngx_pool_t *pool; + ngx_conf_t cf; + ngx_buf_t buf; + ngx_conf_file_t conf_file; + u_char buffer[NGX_CONF_BUFFER]; + ngx_uint_t i; + void *rv; + + ngx_memzero(&cf, sizeof(ngx_conf_t)); + + pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, dccf->log); + if (pool == NULL) { + return NGX_ERROR; + } + pool->log = dccf->log; + + conf = ngx_pcalloc(pool, sizeof(ngx_dynamic_conf_ctx_t)); + if (conf == NULL) { + goto failed; + } + conf->pool = pool; + + ngx_core_max_module = ngx_count_modules((ngx_cycle_t *) ngx_cycle, + NGX_CORE_MODULE); + + conf->module_conf = ngx_pcalloc(pool, sizeof(void *) * ngx_core_max_module); + if (conf->module_conf == NULL) { + goto failed; + } + + cf.args = ngx_array_create(pool, 10, sizeof(ngx_str_t)); + if (cf.args == NULL) { + goto failed; + } + + cf.temp_pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, dccf->log); + if (cf.temp_pool == NULL) { + goto failed; + } + + cf.ctx = conf->module_conf; + cf.cycle = (ngx_cycle_t *) ngx_cycle; + cf.pool = pool; + cf.log = dccf->log; + cf.module_type = NGX_CORE_MODULE; + cf.cmd_type = NGX_MAIN_CONF; + + cf.conf_file = &conf_file; + + if (ngx_fd_info(fd, &cf.conf_file->file.info) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_EMERG, cf.log, ngx_errno, + ngx_fd_info_n " failed"); + } + + cf.conf_file->buffer = &buf; + + buf.pos = buf.last = buf.start = buffer; + buf.end = buf.start + NGX_CONF_BUFFER; + buf.temporary = 1; + + cf.conf_file->file.fd = fd; + cf.conf_file->file.name.len = dccf->file.len; + cf.conf_file->file.name.data = dccf->file.data; + cf.conf_file->file.offset = 0; + cf.conf_file->file.log = cf.log; + cf.conf_file->line = 1; + + for (i = 0; ngx_cycle->modules[i]; ++i) { + if (ngx_cycle->modules[i]->type != NGX_CORE_MODULE) { + continue; + } + + module = (ngx_dynamic_core_module_t *) + ngx_cycle->modules[i]->spare_hook0; + if (module == NULL) { /* dctx not configured */ + continue; + } + + if (module->create_conf) { + rv = module->create_conf(&cf); + if (rv == NULL) { + goto failed; + } + conf->module_conf[ngx_cycle->modules[i]->ctx_index] = rv; + } + } + + if (ngx_dynamic_conf_parse(&cf, 1) != NGX_OK) { + goto failed; + } + + for (i = 0; ngx_cycle->modules[i]; ++i) { + if (ngx_cycle->modules[i]->type != NGX_CORE_MODULE) { + continue; + } + + module = (ngx_dynamic_core_module_t *) ngx_cycle->modules[i]-> + spare_hook0; + if (module == NULL) { + continue; + } + + if (module->init_conf) { + + rv = conf->module_conf[ngx_cycle->modules[i]->ctx_index]; + if (module->init_conf(&cf, rv) == NGX_CONF_ERROR) { + goto failed; + } + } + } + + /* load conf ok, switch conf to new conf */ + if (dccf->conf[dccf->used]) { + pool = dccf->conf[dccf->used]->pool; + dccf->conf[dccf->used] = NULL; + ngx_destroy_pool(pool); + } + + dccf->used = dccf->used ? 0 : 1; + dccf->conf[dccf->used] = conf; + + return NGX_OK; + +failed: + if (cf.temp_pool) { + ngx_destroy_pool(cf.temp_pool); + } + + ngx_destroy_pool(pool); + + return NGX_ERROR; +} + +static void +ngx_dynamic_conf_check_conf(void *data) +{ + ngx_dynamic_conf_conf_t *dccf; + ngx_fd_t fd; + u_char md5key[NGX_MD5KEY_LEN]; + NGX_START_TIMING + + dccf = data; + + if (ngx_exiting) { /* avoid nginx reload worker hungup */ + return; + } + + fd = ngx_open_file(dccf->file.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); + if (fd == NGX_INVALID_FILE) { + ngx_log_error(NGX_LOG_ERR, dccf->log, ngx_errno, + ngx_open_file_n "\"%V\" failed", &dccf->file); + goto done; + } + + /* check md5key for dynamic conf file */ + if (ngx_md5_file(fd, md5key) == NGX_ERROR) { + ngx_log_error(NGX_LOG_ERR, dccf->log, 0, "md5 file \"%V\" failed", + &dccf->file); + goto done; + } + + if (ngx_memcmp(dccf->md5key, md5key, NGX_MD5KEY_LEN) == 0) { + ngx_log_debug1(NGX_LOG_DEBUG_CORE, dccf->log, 0, + "\"%V\" md5key not change", &dccf->file); + goto done; + } + + /* parse dynamic conf */ + if (ngx_dynamic_conf_load_conf(fd, dccf) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, dccf->log, 0, "load file \"%V\" failed", + &dccf->file); + goto done; + } + + ngx_memcpy(dccf->md5key, md5key, NGX_MD5KEY_LEN); + + ngx_log_error(NGX_LOG_INFO, dccf->log, 0, + "dynamic conf load successd, md5key: \"%s\"", dccf->md5key); + +done: + ngx_close_file(fd); + + ngx_event_timer_add_timer(dccf->refresh, ngx_dynamic_conf_check_conf, dccf); + + NGX_STOP_TIMING(dccf->log, "ngx_dynamic_conf_check_conf") +} + +static ngx_int_t ngx_dynamic_conf_process_init(ngx_cycle_t *cycle) +{ + ngx_dynamic_conf_conf_t *dccf; + ngx_fd_t fd; + u_char md5key[NGX_MD5KEY_LEN]; + + NGX_START_TIMING + + dccf = (ngx_dynamic_conf_conf_t *) ngx_get_conf(cycle->conf_ctx, + ngx_dynamic_conf_module); + + if (dccf->file.len == 0) { /* dynamic conf not configured */ + return NGX_OK; + } + + fd = ngx_open_file(dccf->file.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); + if (fd == NGX_INVALID_FILE) { + ngx_log_error(NGX_LOG_ERR, dccf->log, ngx_errno, + ngx_open_file_n "\"%V\" failed", &dccf->file); + return NGX_ERROR; + } + + /* parse dynamic conf */ + if (ngx_dynamic_conf_load_conf(fd, dccf) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, dccf->log, 0, "load file \"%V\" failed", + &dccf->file); + return NGX_ERROR; + } + + /* calc md5key for dynamic conf file */ + if (ngx_md5_file(fd, md5key) == NGX_ERROR) { + ngx_log_error(NGX_LOG_ERR, dccf->log, 0, "md5 file \"%V\" failed", + &dccf->file); + return NGX_ERROR; + } + + ngx_close_file(fd); + + ngx_memcpy(dccf->md5key, md5key, NGX_MD5KEY_LEN); + + ngx_log_error(NGX_LOG_INFO, dccf->log, 0, + "dynamic conf load successd, md5key: \"%s\"", dccf->md5key); + + /* add dynamic conf parse timer */ + ngx_event_timer_add_timer(dccf->refresh, ngx_dynamic_conf_check_conf, dccf); + + NGX_STOP_TIMING(dccf->log, "ngx_dynamic_conf_process_init") + + return NGX_OK; +} + +#if (NGX_PCRE) +ngx_dynamic_regex_t * +ngx_dynamic_regex_compile(ngx_conf_t *cf, ngx_regex_compile_t *rc) +{ + ngx_dynamic_regex_t *re; + + rc->pool = cf->pool; + + if (ngx_regex_compile(rc) != NGX_OK) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V", &rc->err); + return NULL; + } + + re = ngx_pcalloc(cf->pool, sizeof(ngx_regex_elt_t)); + if (re == NULL) { + return NULL; + } + + re->regex = rc->regex; + re->name = rc->pattern; + + return re; +} +#endif + +int +ngx_dynamic_cmp_dns_wildcards(const void *one, const void *two) +{ + ngx_hash_key_t *first, *second; + + first = (ngx_hash_key_t *) one; + second = (ngx_hash_key_t *) two; + + return ngx_dns_strcmp(first->key.data, second->key.data); +} + +void * +ngx_get_dconf(ngx_module_t *m) +{ + ngx_dynamic_conf_conf_t *dccf; + + dccf = (ngx_dynamic_conf_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_dynamic_conf_module); + + if (dccf->conf[dccf->used] == 0) { /* dynamic conf not configured */ + return NULL; + } + + return dccf->conf[dccf->used]->module_conf[m->ctx_index]; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_dynamic_conf.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_dynamic_conf.h new file mode 100644 index 0000000000..3d2f6d168a --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_dynamic_conf.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + + +#ifndef _NGX_DYNAMIC_COMMAND_H_INCLUDED_ +#define _NGX_DYNAMIC_COMMAND_H_INCLUDED_ + + +#include +#include +#include + + +typedef struct { + ngx_str_t name; + void *(*create_conf)(ngx_conf_t *cf); + char *(*init_conf)(ngx_conf_t *cf, void *conf); +} ngx_dynamic_core_module_t; + + +#if (NGX_PCRE) +typedef struct { + ngx_regex_t *regex; + ngx_str_t name; +} ngx_dynamic_regex_t; +#endif + +#if 0 +/* + * if use dynamic conf, module define should use + * NGX_MODULE_V1_DYNAMIC_PADDING and set + * module dynamic context and + * module dynamic directives as bellow + */ +ngx_module_t ngx_test_module = { + NGX_MODULE_V1, + &ngx_test_module_ctx, /* module context */ + ngx_test_commands, /* module directives */ + NGX_CORE_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + &ngx_test_module_dctx, /* module dynamic context */ + ngx_test_dcommands, /* module dynamic directives */ + NGX_MODULE_V1_DYNAMIC_PADDING +}; +#endif +#define NGX_MODULE_V1_DYNAMIC_PADDING 0, 0, 0, 0, 0, 0 + +extern ngx_uint_t ngx_core_max_module; + +/* + * return value: + * return NGX_OK for successd, NGX_ERROR for failed + * paras: + * cf: ngx_conf_t passed from ngx_dynamic_conf_load_conf + * init: only ngx_dynamic_conf_load_conf set 1, otherwise set 0 + */ +ngx_int_t ngx_dynamic_conf_parse(ngx_conf_t *cf, unsigned init); + +/* + * return value: + * return regex context + * paras: + * cf: ngx_conf_t passed in dynamic cmd handler + * rc: regex options + */ +#if (NGX_PCRE) +ngx_dynamic_regex_t *ngx_dynamic_regex_compile(ngx_conf_t *cf, + ngx_regex_compile_t *rc); +#endif + +int ngx_dynamic_cmp_dns_wildcards(const void *one, const void *two); + +void *ngx_get_dconf(ngx_module_t *m); + + +#endif diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_dynamic_resolver.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_dynamic_resolver.c new file mode 100644 index 0000000000..6f1592ff26 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_dynamic_resolver.c @@ -0,0 +1,700 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#include +#include +#include +#include "ngx_event_timer_module.h" +#include "ngx_event_resolver.h" +#include "ngx_dynamic_resolver.h" +#include "ngx_toolkit_misc.h" + + +static ngx_int_t ngx_dynamic_resolver_process_init(ngx_cycle_t *cycle); + +static void *ngx_dynamic_resolver_create_conf(ngx_cycle_t *cycle); +static char *ngx_dynamic_resolver_init_conf(ngx_cycle_t *cycle, void *conf); + + +#define MAX_DOMAIN_LEN 128 +#define MAX_ADDRS 8 + + +typedef struct ngx_dynamic_resolver_ctx_s ngx_dynamic_resolver_ctx_t; +typedef struct ngx_dynamic_resolver_domain_s ngx_dynamic_resolver_domain_t; + +struct ngx_dynamic_resolver_ctx_s { + ngx_dynamic_resolver_handler_pt h; + void *data; + + ngx_dynamic_resolver_ctx_t *next; +}; + +typedef struct { + struct sockaddr sockaddr; + socklen_t socklen; + u_short priority; + u_short weight; +} ngx_dynamic_resolver_addr_t; + +struct ngx_dynamic_resolver_domain_s { + ngx_str_t domain; + u_char domain_cstr[MAX_DOMAIN_LEN]; + + ngx_uint_t naddrs; + ngx_dynamic_resolver_addr_t addrs[MAX_ADDRS]; + + ngx_dynamic_resolver_ctx_t *ctx; + + ngx_dynamic_resolver_domain_t *next; +}; + +typedef struct { + ngx_msec_t refresh_interval; + + size_t domain_buckets; + ngx_dynamic_resolver_domain_t **resolver_hash; + + ngx_dynamic_resolver_ctx_t *free_ctx; + ngx_dynamic_resolver_domain_t *free_domain; + + ngx_uint_t nalloc_ctx; + ngx_uint_t nfree_ctx; + ngx_uint_t nalloc_domain; + ngx_uint_t nfree_domain; +} ngx_dynamic_resolver_conf_t; + + +static ngx_str_t dynamic_resolver_name = ngx_string("dynamic_resolver"); + + +static ngx_command_t ngx_dynamic_resolver_commands[] = { + + { ngx_string("dynamic_refresh_interval"), + NGX_EVENT_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + 0, + offsetof(ngx_dynamic_resolver_conf_t, refresh_interval), + NULL }, + + { ngx_string("dynamic_domain_buckets"), + NGX_EVENT_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + 0, + offsetof(ngx_dynamic_resolver_conf_t, domain_buckets), + NULL }, + + ngx_null_command +}; + + +ngx_event_module_t ngx_dynamic_resolver_module_ctx = { + &dynamic_resolver_name, + ngx_dynamic_resolver_create_conf, /* create configuration */ + ngx_dynamic_resolver_init_conf, /* init configuration */ + + { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL } +}; + + +/* this module use ngx_cycle->log */ +ngx_module_t ngx_dynamic_resolver_module = { + NGX_MODULE_V1, + &ngx_dynamic_resolver_module_ctx, /* module context */ + ngx_dynamic_resolver_commands, /* module directives */ + NGX_EVENT_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + ngx_dynamic_resolver_process_init, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void * +ngx_dynamic_resolver_create_conf(ngx_cycle_t *cycle) +{ + ngx_dynamic_resolver_conf_t *conf; + + conf = ngx_pcalloc(cycle->pool, sizeof(ngx_dynamic_resolver_conf_t)); + if (conf == NULL) { + return NULL; + } + + conf->refresh_interval = NGX_CONF_UNSET_MSEC; + conf->domain_buckets = NGX_CONF_UNSET_UINT; + + return conf; +} + +static char * +ngx_dynamic_resolver_init_conf(ngx_cycle_t *cycle, void *conf) +{ + ngx_dynamic_resolver_conf_t *drcf = conf; + + ngx_conf_init_msec_value(drcf->refresh_interval, 5000); + ngx_conf_init_uint_value(drcf->domain_buckets, 101); + + if (drcf->refresh_interval > 0 && drcf->domain_buckets > 0) { + drcf->resolver_hash = ngx_pcalloc(cycle->pool, + sizeof(ngx_dynamic_resolver_domain_t *) * drcf->domain_buckets); + } + + return NGX_CONF_OK; +} + +/* reuse for ngx_dynamic_resolver_ctx_t */ +static ngx_dynamic_resolver_ctx_t * +ngx_dynamic_resolver_get_ctx(ngx_cycle_t *cycle) +{ + ngx_dynamic_resolver_conf_t *drcf; + ngx_dynamic_resolver_ctx_t *ctx; + + drcf = ngx_event_get_conf(cycle->conf_ctx, ngx_dynamic_resolver_module); + + ctx = drcf->free_ctx; + + if (ctx == NULL) { + ctx = ngx_pcalloc(cycle->pool, sizeof(ngx_dynamic_resolver_ctx_t)); + + if (ctx == NULL) { + ngx_log_error(NGX_LOG_ERR, cycle->log, 0, "dynamic resolver, " + "alloc memory dynamic resolver ctx failed"); + return NULL; + } + ++drcf->nalloc_ctx; + } else { + drcf->free_ctx = drcf->free_ctx->next; + ngx_memzero(ctx, sizeof(ngx_dynamic_resolver_ctx_t)); + --drcf->nfree_ctx; + } + + return ctx; +} + +static void +ngx_dynamic_resolver_put_ctx(ngx_dynamic_resolver_ctx_t *ctx, + ngx_cycle_t *cycle) +{ + ngx_dynamic_resolver_conf_t *drcf; + + drcf = ngx_event_get_conf(cycle->conf_ctx, ngx_dynamic_resolver_module); + + ctx->next = drcf->free_ctx; + drcf->free_ctx = ctx; + ++drcf->nfree_ctx; +} + +/* reuse for ngx_dynamic_resolver_domain_t */ +static ngx_dynamic_resolver_domain_t * +ngx_dynamic_resolver_get_domain(ngx_cycle_t *cycle) +{ + ngx_dynamic_resolver_conf_t *drcf; + ngx_dynamic_resolver_domain_t *domain; + + drcf = ngx_event_get_conf(cycle->conf_ctx, ngx_dynamic_resolver_module); + + domain = drcf->free_domain; + + if (domain == NULL) { + domain = ngx_pcalloc(cycle->pool, + sizeof(ngx_dynamic_resolver_domain_t)); + + if (domain == NULL) { + ngx_log_error(NGX_LOG_ERR, cycle->log, 0, "dynamic resolver, " + "alloc memory dynamic resolver domain failed"); + return NULL; + } + ++drcf->nalloc_domain; + } else { + drcf->free_domain = drcf->free_domain->next; + ngx_memzero(domain, sizeof(ngx_dynamic_resolver_domain_t)); + --drcf->nfree_domain; + } + + return domain; +} + +static void +ngx_dynamic_resolver_put_domain(ngx_dynamic_resolver_domain_t *domain, + ngx_cycle_t *cycle) +{ + ngx_dynamic_resolver_conf_t *drcf; + + drcf = ngx_event_get_conf(cycle->conf_ctx, ngx_dynamic_resolver_module); + + domain->next = drcf->free_domain; + drcf->free_domain = domain; + ++drcf->nfree_domain; +} + + +static void +ngx_dynamic_resolver_on_result(void *data, ngx_resolver_addr_t *addrs, + ngx_uint_t naddrs) +{ + ngx_dynamic_resolver_domain_t *domain; + ngx_dynamic_resolver_ctx_t *ctx; + ngx_uint_t i, n; + + domain = data; + + if (domain == NULL) { + ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, 0, "dynamic resolver, " + "%V has been deleted", &domain->domain); + return; + } + + if (naddrs == 0) { + ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, 0, "dynamic resolver, " + "domain '%V' resolver failed", &domain->domain); + + while (domain->ctx) { + ctx = domain->ctx; + domain->ctx = ctx->next; + + ctx->h(ctx->data, NULL, 0); + ngx_dynamic_resolver_put_ctx(ctx, (ngx_cycle_t *) ngx_cycle); + } + + return; + } + + domain->naddrs = ngx_min(naddrs, MAX_ADDRS); + for (i = 0; i < domain->naddrs; ++i) { + ngx_memcpy(&domain->addrs[i].sockaddr, addrs[i].sockaddr, + addrs[i].socklen); + domain->addrs[i].socklen = addrs[i].socklen; + domain->addrs[i].priority = addrs[i].priority; + domain->addrs[i].weight = addrs[i].weight; + + n = ngx_random() % domain->naddrs; + + while (domain->ctx) { + ngx_log_debug1(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, + "dynamic resolver, resolver '%V' successd", + &domain->domain); + ctx = domain->ctx; + domain->ctx = ctx->next; + + ctx->h(ctx->data, &domain->addrs[n].sockaddr, + domain->addrs[n].socklen); + ngx_dynamic_resolver_put_ctx(ctx, (ngx_cycle_t *) ngx_cycle); + + ++n; + n %= domain->naddrs; + } + } +} + +static void +ngx_dynamic_resolver_on_timer(void *data) +{ + ngx_dynamic_resolver_conf_t *drcf; + ngx_dynamic_resolver_domain_t *domain; + ngx_uint_t i; + + if (ngx_exiting) { + return; + } + + drcf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_dynamic_resolver_module); + + for (i = 0; i < drcf->domain_buckets; ++i) { + domain = drcf->resolver_hash[i]; + while (domain) { + ngx_log_debug1(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, + "dynamic resolver, on timer start resolver %V", + &domain->domain); + + ngx_event_resolver_start_resolver(&domain->domain, + ngx_dynamic_resolver_on_result, domain); + domain = domain->next; + } + } + + ngx_event_timer_add_timer(drcf->refresh_interval, + ngx_dynamic_resolver_on_timer, NULL); +} + +static ngx_int_t +ngx_dynamic_resolver_process_init(ngx_cycle_t *cycle) +{ + ngx_dynamic_resolver_conf_t *drcf; + + drcf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_dynamic_resolver_module); + if (drcf->refresh_interval == 0) { + return NGX_OK; + } + + ngx_event_timer_add_timer(0, ngx_dynamic_resolver_on_timer, NULL); + + return NGX_OK; +} + + +void +ngx_dynamic_resolver_add_domain(ngx_str_t *domain, ngx_cycle_t *cycle) +{ + ngx_dynamic_resolver_conf_t *drcf; + ngx_dynamic_resolver_domain_t *d; + ngx_uint_t idx; + struct sockaddr sa; + socklen_t len; + u_char temp[MAX_DOMAIN_LEN]; + + if (domain == NULL || domain->len == 0) { + ngx_log_error(NGX_LOG_ERR, cycle->log, 0, + "dynamic resolver add, domain is NULL"); + return; + } + + len = ngx_sock_pton(&sa, domain->data, domain->len); + /* addr is IP address */ + if (len) { + ngx_log_error(NGX_LOG_DEBUG_CORE, cycle->log, 0, + "dynamic resolver add, domain is ipv4/ipv6/unix address"); + + return; + } + + drcf = ngx_event_get_conf(cycle->conf_ctx, ngx_dynamic_resolver_module); + if (drcf->refresh_interval == 0) { + ngx_log_error(NGX_LOG_ERR, cycle->log, 0, "dynamic resolver add, " + "dynamic resolver closed when add domain"); + return; + } + + if (domain->len > MAX_DOMAIN_LEN) { + ngx_log_error(NGX_LOG_ERR, cycle->log, 0, "dynamic resolver add, " + "domain length(%z) is too long", domain->len); + return; + } + + ngx_memzero(temp, MAX_DOMAIN_LEN); + idx = ngx_hash_strlow(temp, domain->data, domain->len); + idx %= drcf->domain_buckets; + + ngx_log_debug2(NGX_LOG_DEBUG_CORE, cycle->log, 0, "dynamic resolver add, " + "prepare add %V in %d slot", domain, idx); + + for (d = drcf->resolver_hash[idx]; d; d = d->next) { + + if (d->domain.len == domain->len && + ngx_memcmp(d->domain.data, temp, domain->len) == 0) + { + ngx_log_debug1(NGX_LOG_DEBUG_CORE, cycle->log, 0, + "dynamic resolver add, %V is in dynamic resolv hash table", + domain); + return; + } + } + + d = ngx_dynamic_resolver_get_domain(cycle); + if (d == NULL) { + return; + } + + /* add domain in dynamic resolver */ + d->next = drcf->resolver_hash[idx]; + drcf->resolver_hash[idx] = d; + + ngx_memcpy(d->domain_cstr, temp, MAX_DOMAIN_LEN); + d->domain.data = d->domain_cstr; + d->domain.len = domain->len; +} + +void +ngx_dynamic_resolver_del_domain(ngx_str_t *domain) +{ + ngx_dynamic_resolver_conf_t *drcf; + ngx_dynamic_resolver_domain_t **pd, *d; + ngx_dynamic_resolver_ctx_t *ctx; + ngx_uint_t idx; + struct sockaddr sa; + socklen_t len; + u_char temp[MAX_DOMAIN_LEN]; + + if (domain == NULL || domain->len == 0) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, + "dynamic resolver del, domain is NULL"); + return; + } + + len = ngx_sock_pton(&sa, domain->data, domain->len); + /* addr is IP address */ + if (len) { + ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, + "dynamic resolver del, domain is ipv4/ipv6/unix address"); + + return; + } + + drcf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_dynamic_resolver_module); + if (drcf->refresh_interval == 0) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "dynamic resolver del, " + "dynamic resolver closed when del domain"); + return; + } + + if (domain->len > MAX_DOMAIN_LEN) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "dynamic resolver del, " + "domain length(%z) is too long", domain->len); + return; + } + + ngx_memzero(temp, MAX_DOMAIN_LEN); + idx = ngx_hash_strlow(temp, domain->data, domain->len); + idx %= drcf->domain_buckets; + + ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, 0, "dynamic resolver del, " + "prepare del %V in %d slot", domain, idx); + + for (pd = &drcf->resolver_hash[idx]; *pd; pd = &(*pd)->next) { + + if ((*pd)->domain.len == domain->len && + ngx_memcmp((*pd)->domain.data, temp, domain->len) == 0) + { + d= *pd; + *pd = (*pd)->next; + + while (d->ctx) { + ctx = d->ctx; + d->ctx = ctx->next; + + ctx->h(ctx->data, NULL, 0); + ngx_dynamic_resolver_put_ctx(ctx, (ngx_cycle_t *) ngx_cycle); + } + + ngx_dynamic_resolver_put_domain(d, (ngx_cycle_t *) ngx_cycle); + + return; + } + } + + ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, 0, "dynamic resolver del, " + "%V is not in dynamic resolv hash table", domain); +} + +void +ngx_dynamic_resolver_start_resolver(ngx_str_t *domain, + ngx_dynamic_resolver_handler_pt h, void *data) +{ + ngx_dynamic_resolver_conf_t *drcf; + ngx_dynamic_resolver_domain_t *d; + ngx_dynamic_resolver_ctx_t *ctx; + ngx_uint_t idx, n; + struct sockaddr sa; + socklen_t len; + u_char temp[MAX_DOMAIN_LEN]; + + if (domain == NULL || domain->len == 0) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, + "dynamic resolver async, domain is NULL"); + return; + } + + len = ngx_sock_pton(&sa, domain->data, domain->len); + /* addr is ipv4/ipv6/unix address */ + if (len) { + ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, + "dynamic resolver async, domain is ipv4/ipv6/unix address"); + + h(data, &sa, len); + + return; + } + + drcf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_dynamic_resolver_module); + if (drcf->refresh_interval == 0) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "dynamic resolver async, " + "dynamic resolver closed when start resolver"); + goto failed; + } + + if (domain->len > MAX_DOMAIN_LEN) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "dynamic resolver async, " + "domain length(%z) is too long", domain->len); + goto failed; + } + + ngx_memzero(temp, MAX_DOMAIN_LEN); + idx = ngx_hash_strlow(temp, domain->data, domain->len); + idx %= drcf->domain_buckets; + + d = drcf->resolver_hash[idx]; + while (d) { + if (d->domain.len == domain->len && + ngx_memcmp(d->domain.data, temp, domain->len) == 0) + { + break; + } + d = d->next; + } + + if (d == NULL) { /* not found */ + d = ngx_dynamic_resolver_get_domain((ngx_cycle_t *) ngx_cycle); + if (d == NULL) { + goto failed; + } + + /* add domain in dynamic resolver */ + d->next = drcf->resolver_hash[idx]; + drcf->resolver_hash[idx] = d; + + ngx_memcpy(d->domain_cstr, temp, MAX_DOMAIN_LEN); + d->domain.data = d->domain_cstr; + d->domain.len = domain->len; + } + + /* domain is not resolved */ + if (d->naddrs == 0) { + + /* add call back in resolver list */ + ctx = ngx_dynamic_resolver_get_ctx((ngx_cycle_t *) ngx_cycle); + if (ctx == NULL) { + goto failed; + } + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, + "dynamic resolver async, domain '%V' is not resolved", + &d->domain); + ctx->h = h; + ctx->data = data; + + ctx->next = d->ctx; + d->ctx = ctx; + + ngx_event_resolver_start_resolver(&d->domain, + ngx_dynamic_resolver_on_result, d); + + return; + } + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, + "dynamic resolver async, domain '%V' is resolved, call directly", + &d->domain); + + /* call callback */ + n = ngx_random() % d->naddrs; + h(data, &d->addrs[n].sockaddr, d->addrs[n].socklen); + + return; + +failed: + + h(data, NULL, 0); +} + +socklen_t +ngx_dynamic_resolver_gethostbyname(ngx_str_t *domain, struct sockaddr *sa) +{ + ngx_dynamic_resolver_conf_t *drcf; + ngx_dynamic_resolver_domain_t *d; + ngx_uint_t idx, n; + socklen_t len; + u_char temp[MAX_DOMAIN_LEN]; + + if (domain == NULL) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, + "dynamic resolver sync, domain is NULL"); + return 0; + } + + len = ngx_sock_pton(sa, domain->data, domain->len); + /* addr is ipv4/ipv6/unix address */ + if (len) { + ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, + "dynamic resolver sync, domain is ipv4/ipv6/unix address"); + + return len; + } + + drcf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_dynamic_resolver_module); + if (drcf->refresh_interval == 0) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "dynamic resolver sync, " + "dynamic resolver closed when start resolver"); + return 0; + } + + if (domain->len > MAX_DOMAIN_LEN) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "dynamic resolver sync, " + "domain length(%z) is too long", domain->len); + return 0; + } + + ngx_memzero(temp, MAX_DOMAIN_LEN); + idx = ngx_hash_strlow(temp, domain->data, domain->len); + idx %= drcf->domain_buckets; + + d = drcf->resolver_hash[idx]; + while (d) { + if (d->domain.len == domain->len && + ngx_memcmp(d->domain.data, temp, domain->len) == 0) + { + if (d->naddrs == 0) { + ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, 0, + "dynamic resolver sync, domain '%V' is not resolved", + &d->domain); + return 0; + } + + n = ngx_random() % d->naddrs; + ngx_memcpy(sa, &d->addrs[n].sockaddr, d->addrs[n].socklen); + + return d->addrs[n].socklen; + } + + d = d->next; + } + + ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, 0, "dynamic resolver sync, " + "domain '%V' is not in dynamic resolver table", domain); + + return 0; +} + +ngx_chain_t * +ngx_dynamic_resolver_state(ngx_http_request_t *r) +{ + ngx_dynamic_resolver_conf_t *drcf; + ngx_chain_t *cl; + ngx_buf_t *b; + size_t len; + + drcf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_dynamic_resolver_module); + + len = sizeof("##########dynamic resolver state##########\n") - 1 + + sizeof("ngx_dynamic_resolver alloc ctx: \n") - 1 + NGX_OFF_T_LEN + + sizeof("ngx_dynamic_resolver free ctx: \n") - 1 + NGX_OFF_T_LEN + + sizeof("ngx_dynamic_resolver alloc domain: \n") - 1 + NGX_OFF_T_LEN + + sizeof("ngx_dynamic_resolver free domain: \n") - 1 + NGX_OFF_T_LEN; + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NULL; + } + cl->next = NULL; + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NULL; + } + cl->buf = b; + + b->last = ngx_snprintf(b->last, len, + "##########dynamic resolver state##########\n" + "ngx_dynamic_resolver alloc ctx: %ui\n" + "ngx_dynamic_resolver free ctx: %ui\n" + "ngx_dynamic_resolver alloc domain: %ui\n" + "ngx_dynamic_resolver free domain: %ui\n", + drcf->nalloc_ctx, drcf->nfree_ctx, + drcf->nalloc_domain, drcf->nfree_domain); + + return cl; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_dynamic_resolver.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_dynamic_resolver.h new file mode 100644 index 0000000000..4bce494803 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_dynamic_resolver.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#ifndef _NGX_DYNAMIC_RESOLVER_H_INCLUDED_ +#define _NGX_DYNAMIC_RESOLVER_H_INCLUDED_ + + +#include +#include +#include + + +/* + * paras: + * data : user private data set in ngx_dynamic_resolver_start_resolver + * sa : sock address get + * socklen: sock address len, 0 for get none address + */ +typedef void (* ngx_dynamic_resolver_handler_pt)(void *data, + struct sockaddr *sa, socklen_t socklen); + +/* + * add domain into dynamic resolver + * + * paras: + * domain: domain for DNS query + */ + +void ngx_dynamic_resolver_add_domain(ngx_str_t *domain, ngx_cycle_t *cycle); + +/* + * del domain from dynamic resolver + * + * paras: + * domain: domain for DNS query + */ +void ngx_dynamic_resolver_del_domain(ngx_str_t *domain); + +/* + * get addr from dynamic resolver by domain if exist, + * otherwise, get from resolver configured into event resolver + * and add domain into dynamic resolver + * + * paras: + * domain: domain for DNS query + * h : callback handler + * data : data for callback + */ +void ngx_dynamic_resolver_start_resolver(ngx_str_t *domain, + ngx_dynamic_resolver_handler_pt h, void *data); + +/* + * get addr from dynamic resolver by domain, + * + * return: + * 0 for failed + * socklen for successd + * + * paras: + * domain: domain for query + * sa : sockaddr for domain + */ +socklen_t ngx_dynamic_resolver_gethostbyname(ngx_str_t *domain, + struct sockaddr *sa); + +/* + * paras: + * r: http request to query status of dynamic resolver + */ +ngx_chain_t *ngx_dynamic_resolver_state(ngx_http_request_t *r); + + +#endif diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_event_resolver.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_event_resolver.c new file mode 100644 index 0000000000..acd194e217 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_event_resolver.c @@ -0,0 +1,281 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#include "ngx_event_resolver.h" + + +static void *ngx_event_resolver_create_conf(ngx_cycle_t *cycle); +static char *ngx_event_resolver_init_conf(ngx_cycle_t *cycle, void *conf); + +static char *ngx_event_resolver(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); + + +typedef struct ngx_event_resolver_ctx_s ngx_event_resolver_ctx_t; + +struct ngx_event_resolver_ctx_s { + ngx_event_resolver_handler_pt handler; + void *data; + ngx_event_resolver_ctx_t *next; +}; + +typedef struct { + ngx_msec_t resolver_timeout; + ngx_resolver_t *resolver; + ngx_event_resolver_ctx_t *free_ctx; + + ngx_uint_t nalloc; + ngx_uint_t nfree; +} ngx_event_resolver_conf_t; + + +static ngx_str_t event_resolver_name = ngx_string("event_resolver"); + + +static ngx_command_t ngx_event_resolver_commands[] = { + + { ngx_string("resolver"), + NGX_EVENT_CONF|NGX_CONF_1MORE, + ngx_event_resolver, + 0, + 0, + NULL }, + + { ngx_string("resolver_timeout"), + NGX_EVENT_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + 0, + offsetof(ngx_event_resolver_conf_t, resolver_timeout), + NULL }, + + ngx_null_command +}; + + +ngx_event_module_t ngx_event_resolver_module_ctx = { + &event_resolver_name, + ngx_event_resolver_create_conf, /* create configuration */ + ngx_event_resolver_init_conf, /* init configuration */ + + { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL } +}; + + +/* this module use ngx_cycle->log */ +ngx_module_t ngx_event_resolver_module = { + NGX_MODULE_V1, + &ngx_event_resolver_module_ctx, /* module context */ + ngx_event_resolver_commands, /* module directives */ + NGX_EVENT_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void * +ngx_event_resolver_create_conf(ngx_cycle_t *cycle) +{ + ngx_event_resolver_conf_t *conf; + + conf = ngx_pcalloc(cycle->pool, sizeof(ngx_event_resolver_conf_t)); + if (conf == NULL) { + return NULL; + } + + conf->resolver_timeout = NGX_CONF_UNSET_MSEC; + + return conf; +} + +static char * +ngx_event_resolver_init_conf(ngx_cycle_t *cycle, void *conf) +{ + ngx_event_resolver_conf_t *ercf = conf; + + ngx_conf_init_msec_value(ercf->resolver_timeout, 60000); + + return NGX_CONF_OK; +} + +static char * +ngx_event_resolver(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_event_resolver_conf_t *ercf = conf; + + ngx_str_t *value; + + if (ercf->resolver) { + return "is duplicate"; + } + + value = cf->args->elts; + + ercf->resolver = ngx_resolver_create(cf, &value[1], cf->args->nelts - 1); + if (ercf->resolver == NULL) { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + +static ngx_event_resolver_ctx_t * +ngx_event_resolver_get_ctx() +{ + ngx_event_resolver_ctx_t *ctx; + ngx_event_resolver_conf_t *ercf; + + ercf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_event_resolver_module); + + ctx = ercf->free_ctx; + if (ctx == NULL) { + ctx = ngx_pcalloc(ngx_cycle->pool, sizeof(ngx_event_resolver_ctx_t)); + ++ercf->nalloc; + } else { + ercf->free_ctx = ctx->next; + ctx->next = NULL; + --ercf->nfree; + } + + return ctx; +} + +static void +ngx_event_resolver_put_ctx(ngx_event_resolver_ctx_t *ctx) +{ + ngx_event_resolver_conf_t *ercf; + + ercf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_event_resolver_module); + + ctx->next = ercf->free_ctx; + ercf->free_ctx = ctx; + ++ercf->nfree; +} + +static void +ngx_event_resolver_handler(ngx_resolver_ctx_t *ctx) +{ + ngx_event_resolver_ctx_t *erctx; + + erctx = ctx->data; + + if (ctx->state) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, + "event resolver, domain '%V' could not be resolved (%i: %s)", + &ctx->name, ctx->state, ngx_resolver_strerror(ctx->state)); + erctx->handler(erctx->data, NULL, 0); + + goto failed; + } + + erctx->handler(erctx->data, ctx->addrs, ctx->naddrs); + +failed: + ngx_resolve_name_done(ctx); + ngx_event_resolver_put_ctx(erctx); +} + +void +ngx_event_resolver_start_resolver(ngx_str_t *domain, + ngx_event_resolver_handler_pt h, void *data) +{ + ngx_event_resolver_conf_t *ercf; + ngx_event_resolver_ctx_t *erctx; + ngx_resolver_ctx_t *ctx, temp; + + ercf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_event_resolver_module); + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, "event resolver, " + "start resolv domain '%V'", domain); + + if (ercf->resolver == NULL) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "event resolver, " + "resolver does not configured"); + h(data, NULL, 0); + + return; + } + + temp.name = *domain; + + erctx = ngx_event_resolver_get_ctx(); + if (erctx == NULL) { + return; + } + erctx->handler = h; + erctx->data = data; + + ctx = ngx_resolve_start(ercf->resolver, &temp); + if (ctx == NULL) { + goto failed; + } + + if (ctx == NGX_NO_RESOLVER) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "event resolver, " + "no resolver defined to resolv %V", domain); + goto failed; + } + + ctx->name = *domain; + ctx->handler = ngx_event_resolver_handler; + ctx->data = erctx; + ctx->timeout = ercf->resolver_timeout; + + if (ngx_resolve_name(ctx) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "event resolver, " + "resolv %V failed", domain); + + goto failed; + } + + return; + +failed: + h(data, NULL, 0); + + if (ctx == NULL || ctx == NGX_NO_RESOLVER) { + ngx_resolve_name_done(ctx); + ngx_event_resolver_put_ctx(erctx); + } +} + +ngx_chain_t * +ngx_event_resolver_state(ngx_http_request_t *r) +{ + ngx_event_resolver_conf_t *ercf; + ngx_chain_t *cl; + ngx_buf_t *b; + size_t len; + + ercf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_event_resolver_module); + + len = sizeof("##########event resolver state##########\n") - 1 + + sizeof("ngx_event_resolver alloc: \n") - 1 + NGX_OFF_T_LEN + + sizeof("ngx_event_resolver free: \n") - 1 + NGX_OFF_T_LEN; + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NULL; + } + cl->next = NULL; + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NULL; + } + cl->buf = b; + + b->last = ngx_snprintf(b->last, len, + "##########event resolver state##########\n" + "ngx_event_resolver alloc: %ui\nngx_event_resolver free: %ui\n", + ercf->nalloc, ercf->nfree); + + return cl; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_event_resolver.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_event_resolver.h new file mode 100644 index 0000000000..89668e4de2 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_event_resolver.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + + +#ifndef _NGX_EVENT_RESOLVER_H_INCLUDED_ +#define _NGX_EVENT_RESOLVER_H_INCLUDED_ + + +#include +#include +#include +#include + + +/* + * paras: + * data : user private data set in ngx_event_resolver_start_resolver + * addrs : addrs resolv by DNS + * naddrs: number of addrs resolv by DNS + */ +typedef void (* ngx_event_resolver_handler_pt)(void *data, + ngx_resolver_addr_t *addrs, ngx_uint_t naddrs); + + +/* + * paras: + * domain: domain for resolving + * h : callback handler + * data : data for callback + */ +void ngx_event_resolver_start_resolver(ngx_str_t *domain, + ngx_event_resolver_handler_pt h, void *data); + +/* + * paras: + * r: http request to query status of event resolver + */ +ngx_chain_t *ngx_event_resolver_state(ngx_http_request_t *r); + + +#endif diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_event_timer_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_event_timer_module.c new file mode 100644 index 0000000000..c63787ee1e --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_event_timer_module.c @@ -0,0 +1,271 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#include "ngx_event_timer_module.h" + + +static ngx_int_t ngx_event_timer_process_init(ngx_cycle_t *cycle); + +static void *ngx_event_timer_create_conf(ngx_cycle_t *cycle); +static char *ngx_event_timer_init_conf(ngx_cycle_t *cycle, void *conf); + + +typedef struct { + ngx_uint_t timerid; + + ngx_event_t event; + + ngx_timer_handler_pt handler; + void *data; +} ngx_event_timer_ctx_t; + +typedef struct { + ngx_uint_t timer_n; + + ngx_event_timer_ctx_t *timer_ctx; /* array to store timers */ + + ngx_event_timer_ctx_t *free_timers; /* timer unused */ + ngx_uint_t free_timer_n; +} ngx_event_timer_conf_t; + + +static ngx_str_t event_timer_name = ngx_string("event_timer"); + + +static ngx_command_t ngx_event_timer_commands[] = { + + { ngx_string("worker_timers"), + NGX_EVENT_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + 0, + offsetof(ngx_event_timer_conf_t, timer_n), + NULL }, + + ngx_null_command +}; + + +ngx_event_module_t ngx_event_timer_module_ctx = { + &event_timer_name, + ngx_event_timer_create_conf, /* create configuration */ + ngx_event_timer_init_conf, /* init configuration */ + + { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL } +}; + + +/* this module use ngx_cycle->log */ +ngx_module_t ngx_event_timer_module = { + NGX_MODULE_V1, + &ngx_event_timer_module_ctx, /* module context */ + ngx_event_timer_commands, /* module directives */ + NGX_EVENT_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + ngx_event_timer_process_init, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void * +ngx_event_timer_create_conf(ngx_cycle_t *cycle) +{ + ngx_event_timer_conf_t *conf; + + conf = ngx_pcalloc(cycle->pool, sizeof(ngx_event_timer_conf_t)); + if (conf == NULL) { + return NULL; + } + + conf->timer_n = NGX_CONF_UNSET_UINT; + + return conf; +} + +static char * +ngx_event_timer_init_conf(ngx_cycle_t *cycle, void *conf) +{ + ngx_event_timer_conf_t *etcf = conf; + + ngx_conf_init_uint_value(etcf->timer_n, 1024); + + return NGX_CONF_OK; +} + +static ngx_int_t +ngx_event_timer_process_init(ngx_cycle_t *cycle) +{ + ngx_event_timer_conf_t *etcf; + ngx_event_timer_ctx_t *t, *next; + ngx_uint_t i; + + etcf = ngx_event_get_conf(cycle->conf_ctx, ngx_event_timer_module); + + if (etcf->timer_n == 0) { + return NGX_OK; + } + + etcf->timer_ctx = + ngx_alloc(sizeof(ngx_event_timer_ctx_t) * etcf->timer_n, cycle->log); + if (etcf->timer_ctx == NULL) { + return NGX_ERROR; + } + + t = etcf->timer_ctx; + + i = etcf->timer_n; + next = NULL; + + do { + --i; + + t[i].timerid = i; + t[i].event.timer_set = 0; + t[i].event.log = NULL; + t[i].handler = NULL; + t[i].data = next; + + next = &t[i]; + } while (i); + + etcf->free_timers = next; + etcf->free_timer_n = etcf->timer_n; + + return NGX_OK; +} + +static ngx_event_timer_ctx_t * +ngx_event_timer_get_timer() +{ + ngx_event_timer_conf_t *etcf; + ngx_event_timer_ctx_t *free; + + etcf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_event_timer_module); + + free = etcf->free_timers; + if (etcf->free_timer_n == 0) { + ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, + "nginx event timer module, no free timer"); + return NULL; + } + + etcf->free_timers = free->data; + --etcf->free_timer_n; + + return free; +} + +static void +ngx_event_timer_free_timer(ngx_event_timer_ctx_t *ctx) +{ + ngx_event_timer_conf_t *etcf; + + etcf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_event_timer_module); + + ctx->data = etcf->free_timers; + etcf->free_timers = ctx; + ++etcf->free_timer_n; +} + +static void +ngx_event_timer_event_handler(ngx_event_t *e) +{ + ngx_event_timer_ctx_t *ctx; + + ctx = e->data; + + if (ctx->handler) { + ctx->handler(ctx->data); + } + + ngx_event_timer_free_timer(ctx); +} + +ngx_int_t +ngx_event_timer_add_timer(ngx_msec_t tv, ngx_timer_handler_pt h, void *data) +{ + ngx_event_timer_ctx_t *ctx; + + if (h == NULL) { + return NGX_ERROR; + } + + ctx = ngx_event_timer_get_timer(); + if (ctx == NULL) { + return NGX_ERROR; + } + + ctx->event.handler = ngx_event_timer_event_handler; + ctx->event.data = ctx; + + ctx->handler = h; + ctx->data = data; + + if (ctx->event.log == NULL) { + ctx->event.log = ngx_cycle->log; + } + + ngx_event_add_timer(&ctx->event, tv); + + return ctx->timerid; +} + +void +ngx_event_timer_del_timer(ngx_uint_t timerid) +{ + ngx_event_timer_ctx_t *ctx; + ngx_event_timer_conf_t *etcf; + + etcf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_event_timer_module); + + ctx = &etcf->timer_ctx[timerid]; + + if (!ctx->event.timer_set) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0, + "timer has already deleted"); + return; + } + + ngx_event_del_timer(&ctx->event); + ngx_event_timer_free_timer(ctx); +} + +ngx_chain_t * +ngx_event_timer_state(ngx_http_request_t *r) +{ + ngx_event_timer_conf_t *etcf; + ngx_chain_t *cl; + ngx_buf_t *b; + size_t len; + + etcf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_event_timer_module); + + len = sizeof("##########event timer state##########\n") - 1 + + sizeof("ngx_event_timer alloc: \n") - 1 + NGX_OFF_T_LEN + + sizeof("ngx_event_timer free: \n") - 1 + NGX_OFF_T_LEN; + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NULL; + } + cl->next = NULL; + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NULL; + } + cl->buf = b; + + b->last = ngx_snprintf(b->last, len, + "##########event timer state##########\n" + "ngx_event_timer alloc: %ui\nngx_event_timer free: %ui\n", + etcf->timer_n, etcf->free_timer_n); + + return cl; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_event_timer_module.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_event_timer_module.h new file mode 100644 index 0000000000..9b02f71536 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_event_timer_module.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#ifndef _NGX_EVENT_TIMER_MODULE_H_INCLUDED_ +#define _NGX_EVENT_TIMER_MODULE_H_INCLUDED_ + + +#include +#include +#include +#include + + +typedef void (* ngx_timer_handler_pt)(void *data); + +/* + * return value: + * return timerid for successd, NGX_ERROR for failed + * paras: + * tv : timer interval to trigger handler + * h : timer handler + * data : data of h para + */ +ngx_int_t ngx_event_timer_add_timer(ngx_msec_t tv, + ngx_timer_handler_pt h, void *data); + +/* + * paras: + * timerid: value return in ngx_timer_add_timer + */ +void ngx_event_timer_del_timer(ngx_uint_t timerid); + +/* + * paras: + * r: http request to query status of event timer + */ +ngx_chain_t *ngx_event_timer_state(ngx_http_request_t *r); + + +#endif diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_http_dynamic.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_http_dynamic.c new file mode 100644 index 0000000000..440e7444cb --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_http_dynamic.c @@ -0,0 +1,1677 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#include +#include +#include +#include "ngx_dynamic_conf.h" +#include "ngx_http_dynamic.h" + + +static void *ngx_http_dynamic_create_conf(ngx_conf_t *cf); +static void *ngx_http_dynamic_core_create_main_conf(ngx_conf_t *cf); +static char *ngx_http_dynamic_core_init_main_conf(ngx_conf_t *cf, void *conf); +static void *ngx_http_dynamic_core_create_srv_conf(ngx_conf_t *cf); +static char *ngx_http_dynamic_core_init_srv_conf(ngx_conf_t *cf, void *conf); +static void *ngx_http_dynamic_core_create_loc_conf(ngx_conf_t *cf); +static char *ngx_http_dynamic_core_init_loc_conf(ngx_conf_t *cf, void *conf); + +static char *ngx_http_dynamic_block(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char *ngx_http_dynamic_core_server(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char *ngx_http_dynamic_core_server_name(ngx_conf_t *cf, + ngx_command_t *cmd, void *conf); +static char *ngx_http_dynamic_core_location(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + + +typedef struct ngx_http_dynamic_location_tree_node_s + ngx_http_dynamic_location_tree_node_t; +typedef struct ngx_http_dynamic_core_loc_conf_s + ngx_http_dynamic_core_loc_conf_t; + +struct ngx_http_dynamic_core_loc_conf_s { + ngx_str_t name; /* location name */ + +#if (NGX_PCRE) + ngx_dynamic_regex_t *regex; +#endif + + unsigned exact_match:1; + unsigned noregex:1; + + ngx_queue_t *locations; + + ngx_http_dynamic_location_tree_node_t *static_locations; +#if (NGX_PCRE) + ngx_http_dynamic_core_loc_conf_t **regex_locations; +#endif + + void **loc_conf; +}; + +typedef struct { + ngx_queue_t queue; + ngx_http_dynamic_core_loc_conf_t *exact; + ngx_http_dynamic_core_loc_conf_t *inclusive; + ngx_str_t *name; + u_char *file_name; + ngx_uint_t line; + ngx_queue_t list; +} ngx_http_dynamic_location_queue_t; + +struct ngx_http_dynamic_location_tree_node_s { + ngx_http_dynamic_location_tree_node_t *left; + ngx_http_dynamic_location_tree_node_t *right; + ngx_http_dynamic_location_tree_node_t *tree; + + ngx_http_dynamic_core_loc_conf_t *exact; + ngx_http_dynamic_core_loc_conf_t *inclusive; + + u_char len; + u_char name[1]; +}; + + +typedef struct { + /* array of the ngx_http_dynamic_server_name_t, "server_name" directive */ + ngx_array_t server_names; + + ngx_http_dynamic_core_loc_conf_t *hdclcf; + + void **srv_conf; +} ngx_http_dynamic_core_srv_conf_t; + +typedef struct { +#if (NGX_PCRE) + ngx_dynamic_regex_t *regex; +#endif + ngx_http_dynamic_core_srv_conf_t *server; /* virtual name server conf */ + ngx_str_t name; +} ngx_http_dynamic_server_name_t; + + +typedef struct { + /* ngx_http_dynamic_srv_conf_t */ + ngx_array_t servers; + + ngx_uint_t server_names_hash_max_size; + ngx_uint_t server_names_hash_bucket_size; + + /* the default server configuration for this address:port */ + ngx_http_dynamic_core_srv_conf_t *default_server; + + ngx_hash_combined_t names; + +#if (NGX_PCRE) + ngx_uint_t nregex; + ngx_http_dynamic_server_name_t *regex; +#endif +} ngx_http_dynamic_core_main_conf_t; + + +typedef struct { + void **main_conf; +} ngx_http_dynamic_conf_t; + + +static ngx_core_module_t ngx_http_dynamic_module_ctx = { + ngx_string("http_dynamic"), + NULL, + NULL +}; + +static ngx_command_t ngx_http_dynamic_dcommands[] = { + + { ngx_string("http"), + NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, + ngx_http_dynamic_block, + 0, + 0, + NULL }, + + ngx_null_command +}; + +static ngx_dynamic_core_module_t ngx_http_dynamic_module_dctx = { + ngx_string("http_dynamic"), + ngx_http_dynamic_create_conf, + NULL +}; + +ngx_module_t ngx_http_dynamic_module = { + NGX_MODULE_V1, + &ngx_http_dynamic_module_ctx, /* module context */ + NULL, /* module directives */ + NGX_CORE_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + (uintptr_t) &ngx_http_dynamic_module_dctx, /* module dynamic context */ + (uintptr_t) ngx_http_dynamic_dcommands, /* module dynamic directives */ + NGX_MODULE_V1_DYNAMIC_PADDING +}; + + +static ngx_http_module_t ngx_http_dynamic_core_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +static ngx_command_t ngx_http_dynamic_core_dcommands[] = { + + { ngx_string("server_names_hash_max_size"), + NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + 0, + offsetof(ngx_http_dynamic_core_main_conf_t, server_names_hash_max_size), + NULL }, + + { ngx_string("server_names_hash_bucket_size"), + NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + 0, + offsetof(ngx_http_dynamic_core_main_conf_t, + server_names_hash_bucket_size), + NULL }, + + { ngx_string("server"), + NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, + ngx_http_dynamic_core_server, + 0, + 0, + NULL }, + + { ngx_string("server_name"), + NGX_HTTP_SRV_CONF|NGX_CONF_1MORE, + ngx_http_dynamic_core_server_name, + 0, + 0, + NULL }, + + { ngx_string("location"), + NGX_HTTP_SRV_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE12, + ngx_http_dynamic_core_location, + 0, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_http_dynamic_module_t ngx_http_dynamic_core_module_dctx = { + ngx_http_dynamic_core_create_main_conf, /* create main configuration */ + ngx_http_dynamic_core_init_main_conf, /* init main configuration */ + + ngx_http_dynamic_core_create_srv_conf, /* create server configuration */ + ngx_http_dynamic_core_init_srv_conf, /* init server configuration */ + + ngx_http_dynamic_core_create_loc_conf, /* create location configuration */ + ngx_http_dynamic_core_init_loc_conf /* init location configuration */ +}; + + +ngx_module_t ngx_http_dynamic_core_module = { + NGX_MODULE_V1, + &ngx_http_dynamic_core_module_ctx, /* module context */ + NULL, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + (uintptr_t) &ngx_http_dynamic_core_module_dctx, /* module dynamic context */ + (uintptr_t) ngx_http_dynamic_core_dcommands, /* module dynamic directives */ + NGX_MODULE_V1_DYNAMIC_PADDING +}; + + +static void * +ngx_http_dynamic_create_conf(ngx_conf_t *cf) +{ + ngx_http_dynamic_conf_t *hdccf; + + hdccf = ngx_pcalloc(cf->pool, sizeof(ngx_http_dynamic_conf_t)); + if (hdccf == NULL) { + return NULL; + } + + return hdccf; +} + +static void * +ngx_http_dynamic_core_create_main_conf(ngx_conf_t *cf) +{ + ngx_http_dynamic_core_main_conf_t *hdcmcf; + + hdcmcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_dynamic_core_main_conf_t)); + if (hdcmcf == NULL) { + return NULL; + } + + if (ngx_array_init(&hdcmcf->servers, cf->pool, 4, + sizeof(ngx_http_dynamic_core_srv_conf_t *)) != NGX_OK) + { + return NULL; + } + + hdcmcf->server_names_hash_max_size = NGX_CONF_UNSET_UINT; + hdcmcf->server_names_hash_bucket_size = NGX_CONF_UNSET_UINT; + + return hdcmcf; +} + +static char * +ngx_http_dynamic_core_init_main_conf(ngx_conf_t *cf, void *conf) +{ + ngx_http_dynamic_core_main_conf_t *hdcmcf; + + hdcmcf = conf; + + ngx_conf_init_uint_value(hdcmcf->server_names_hash_max_size, 512); + ngx_conf_init_uint_value(hdcmcf->server_names_hash_bucket_size, + ngx_cacheline_size); + + return NGX_CONF_OK; +} + +static void * +ngx_http_dynamic_core_create_srv_conf(ngx_conf_t *cf) +{ + ngx_http_dynamic_core_srv_conf_t *hdcscf; + + hdcscf = ngx_pcalloc(cf->pool, sizeof(ngx_http_dynamic_core_srv_conf_t)); + if (hdcscf == NULL) { + return NULL; + } + + if (ngx_array_init(&hdcscf->server_names, cf->pool, 4, + sizeof(ngx_http_dynamic_server_name_t)) != NGX_OK) + { + return NULL; + } + + return hdcscf; +} + +static char * +ngx_http_dynamic_core_init_srv_conf(ngx_conf_t *cf, void *conf) +{ + return NGX_CONF_OK; +} + +static void * +ngx_http_dynamic_core_create_loc_conf(ngx_conf_t *cf) +{ + ngx_http_dynamic_core_loc_conf_t *hdclcf; + + hdclcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_dynamic_core_loc_conf_t)); + if (hdclcf == NULL) { + return NULL; + } + + return hdclcf; +} + +static char * +ngx_http_dynamic_core_init_loc_conf(ngx_conf_t *cf, void *conf) +{ + return NGX_CONF_OK; +} + +static ngx_int_t +ngx_http_dynamic_core_regex_location(ngx_conf_t *cf, + ngx_http_dynamic_core_loc_conf_t *hdclcf, + ngx_str_t *regex, ngx_uint_t caseless) +{ +#if (NGX_PCRE) + ngx_regex_compile_t rc; + u_char errstr[NGX_MAX_CONF_ERRSTR]; + + ngx_memzero(&rc, sizeof(ngx_regex_compile_t)); + + rc.pattern = *regex; + rc.err.len = NGX_MAX_CONF_ERRSTR; + rc.err.data = errstr; + +#if (NGX_HAVE_CASELESS_FILESYSTEM) + rc.options = NGX_REGEX_CASELESS; +#else + rc.options = caseless ? NGX_REGEX_CASELESS : 0; +#endif + + hdclcf->regex = ngx_dynamic_regex_compile(cf, &rc); + if (hdclcf->regex == NULL) { + return NGX_ERROR; + } + + hdclcf->name = *regex; + + return NGX_OK; + +#else + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "using regex \"%V\" requires PCRE library", + regex); + return NGX_ERROR; + +#endif +} + +static ngx_int_t +ngx_http_dynamic_core_predeal_location(ngx_conf_t *cf, + ngx_http_dynamic_core_loc_conf_t *hdclcf) +{ + ngx_str_t *value, *name; + size_t len; + u_char *mod; + + value = cf->args->elts; + + if (cf->args->nelts == 3) { + + len = value[1].len; + mod = value[1].data; + name = &value[2]; + + if (len == 1 && mod[0] == '=') { + + hdclcf->name = *name; + hdclcf->exact_match = 1; + + } else if (len == 2 && mod[0] == '^' && mod[1] == '~') { + + hdclcf->name = *name; + hdclcf->noregex = 1; + + } else if (len == 1 && mod[0] == '~') { + + if (ngx_http_dynamic_core_regex_location(cf, hdclcf, name, 0) + != NGX_OK) + { + return NGX_ERROR; + } + + } else if (len == 2 && mod[0] == '~' && mod[1] == '*') { + + if (ngx_http_dynamic_core_regex_location(cf, hdclcf, name, 1) + != NGX_OK) + { + return NGX_ERROR; + } + + } else { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid location modifier \"%V\"", &value[1]); + return NGX_ERROR; + } + } else { /* cf->args->nelts == 2 */ + + name = &value[1]; + + if (name->data[0] == '=') { + + hdclcf->name.len = name->len - 1; + hdclcf->name.data = name->data + 1; + hdclcf->exact_match = 1; + + } else if (name->data[0] == '^' && name->data[1] == '~') { + + hdclcf->name.len = name->len - 2; + hdclcf->name.data = name->data + 2; + hdclcf->noregex = 1; + + } else if (name->data[0] == '~') { + + name->len--; + name->data++; + + if (name->data[0] == '*') { + + name->len--; + name->data++; + + if (ngx_http_dynamic_core_regex_location(cf, hdclcf, name, 1) + != NGX_OK) + { + return NGX_ERROR; + } + + } else { + if (ngx_http_dynamic_core_regex_location(cf, hdclcf, name, 0) + != NGX_OK) + { + return NGX_ERROR; + } + } + + } else { + + hdclcf->name = *name; + } + } + + return NGX_OK; +} + +static ngx_int_t +ngx_http_dynamic_core_cmp_locations(const ngx_queue_t *one, + const ngx_queue_t *two) +{ + ngx_int_t rc; + ngx_http_dynamic_core_loc_conf_t *first, *second; + ngx_http_dynamic_location_queue_t *lq1, *lq2; + + lq1 = (ngx_http_dynamic_location_queue_t *) one; + lq2 = (ngx_http_dynamic_location_queue_t *) two; + + first = lq1->exact ? lq1->exact : lq1->inclusive; + second = lq2->exact ? lq2->exact : lq2->inclusive; + +#if (NGX_PCRE) + if (first->regex && !second->regex) { + /* shift the regex matches to the end */ + return 1; + } + + if (!first->regex && second->regex) { + /* shift the regex matches to the end */ + return -1; + } + + if (first->regex || second->regex) { + /* do not sort the regex matches */ + return 0; + } +#endif + + rc = ngx_filename_cmp(first->name.data, second->name.data, + ngx_min(first->name.len, second->name.len) + 1); + + if (rc == 0 && !first->exact_match && second->exact_match) { + /* an exact match must be before the same inclusive one */ + return 1; + } + + return rc; +} + +static ngx_int_t +ngx_http_dynamic_core_join_exact_locations(ngx_conf_t *cf, + ngx_queue_t *locations) +{ + ngx_queue_t *q, *x; + ngx_http_dynamic_location_queue_t *lq, *lx; + + q = ngx_queue_head(locations); + + while (q != ngx_queue_last(locations)) { + + x = ngx_queue_next(q); + + lq = (ngx_http_dynamic_location_queue_t *) q; + lx = (ngx_http_dynamic_location_queue_t *) x; + + if (lq->name->len == lx->name->len + && ngx_filename_cmp(lq->name->data, lx->name->data, lx->name->len) + == 0) + { + if ((lq->exact && lx->exact) || (lq->inclusive && lx->inclusive)) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "duplicate location \"%V\" in %s:%ui", + lx->name, lx->file_name, lx->line); + + return NGX_ERROR; + } + + lq->inclusive = lx->inclusive; + + ngx_queue_remove(x); + + continue; + } + + q = ngx_queue_next(q); + } + + return NGX_OK; +} + +static void +ngx_http_dynamic_core_create_locations_list(ngx_queue_t *locations, + ngx_queue_t *q) +{ + u_char *name; + size_t len; + ngx_queue_t *x, tail; + ngx_http_dynamic_location_queue_t *lq, *lx; + + if (q == ngx_queue_last(locations)) { + return; + } + + lq = (ngx_http_dynamic_location_queue_t *) q; + + if (lq->inclusive == NULL) { + ngx_http_dynamic_core_create_locations_list(locations, + ngx_queue_next(q)); + return; + } + + len = lq->name->len; + name = lq->name->data; + + for (x = ngx_queue_next(q); + x != ngx_queue_sentinel(locations); + x = ngx_queue_next(x)) + { + lx = (ngx_http_dynamic_location_queue_t *) x; + + if (len > lx->name->len + || ngx_filename_cmp(name, lx->name->data, len) != 0) + { + break; + } + } + + q = ngx_queue_next(q); + + if (q == x) { + ngx_http_dynamic_core_create_locations_list(locations, x); + return; + } + + ngx_queue_split(locations, q, &tail); + ngx_queue_add(&lq->list, &tail); + + if (x == ngx_queue_sentinel(locations)) { + ngx_http_dynamic_core_create_locations_list(&lq->list, + ngx_queue_head(&lq->list)); + return; + } + + ngx_queue_split(&lq->list, x, &tail); + ngx_queue_add(locations, &tail); + + ngx_http_dynamic_core_create_locations_list(&lq->list, + ngx_queue_head(&lq->list)); + + ngx_http_dynamic_core_create_locations_list(locations, x); +} + +/* + * to keep cache locality for left leaf nodes, allocate nodes in following + * order: node, left subtree, right subtree, inclusive subtree + */ + +static ngx_http_dynamic_location_tree_node_t * +ngx_http_dynamic_core_create_locations_tree(ngx_conf_t *cf, + ngx_queue_t *locations, size_t prefix) +{ + size_t len; + ngx_queue_t *q, tail; + ngx_http_dynamic_location_queue_t *lq; + ngx_http_dynamic_location_tree_node_t *node; + + q = ngx_queue_middle(locations); + + lq = (ngx_http_dynamic_location_queue_t *) q; + len = lq->name->len - prefix; + + node = ngx_palloc(cf->pool, + offsetof(ngx_http_dynamic_location_tree_node_t, name) + len); + if (node == NULL) { + return NULL; + } + + node->left = NULL; + node->right = NULL; + node->tree = NULL; + node->exact = lq->exact; + node->inclusive = lq->inclusive; + + node->len = (u_char) len; + ngx_memcpy(node->name, &lq->name->data[prefix], len); + + ngx_queue_split(locations, q, &tail); + + if (ngx_queue_empty(locations)) { + /* + * ngx_queue_split() insures that if left part is empty, + * then right one is empty too + */ + goto inclusive; + } + + node->left = ngx_http_dynamic_core_create_locations_tree(cf, locations, + prefix); + if (node->left == NULL) { + return NULL; + } + + ngx_queue_remove(q); + + if (ngx_queue_empty(&tail)) { + goto inclusive; + } + + node->right = ngx_http_dynamic_core_create_locations_tree(cf, &tail, + prefix); + if (node->right == NULL) { + return NULL; + } + +inclusive: + + if (ngx_queue_empty(&lq->list)) { + return node; + } + + node->tree = ngx_http_dynamic_core_create_locations_tree(cf, &lq->list, + prefix + len); + if (node->tree == NULL) { + return NULL; + } + + return node; +} + +static ngx_int_t +ngx_http_dynamic_core_init_locations(ngx_conf_t *cf, + ngx_http_dynamic_core_loc_conf_t *phdclcf) +{ + ngx_queue_t *q, *locations, tail; + ngx_http_dynamic_core_loc_conf_t *hdclcf; + ngx_http_dynamic_location_queue_t *lq; + ngx_http_dynamic_core_loc_conf_t **hdclcfp; +#if (NGX_PCRE) + ngx_uint_t r; + ngx_queue_t *regex; +#endif + + locations = phdclcf->locations; + + if (locations == NULL) { + return NGX_OK; + } + + ngx_queue_sort(locations, ngx_http_dynamic_core_cmp_locations); + +#if (NGX_PCRE) + regex = NULL; + r = 0; +#endif + + for (q = ngx_queue_head(locations); + q != ngx_queue_sentinel(locations); + q = ngx_queue_next(q)) + { + lq = (ngx_http_dynamic_location_queue_t *) q; + + hdclcf = lq->exact ? lq->exact : lq->inclusive; + +#if (NGX_PCRE) + + if (hdclcf->regex) { + r++; + + if (regex == NULL) { + regex = q; + } + + continue; + } + +#endif + } + + if (q != ngx_queue_sentinel(locations)) { + ngx_queue_split(locations, q, &tail); + } + +#if (NGX_PCRE) + + if (regex) { + + hdclcfp = ngx_palloc(cf->pool, + (r + 1) * sizeof(ngx_http_dynamic_core_loc_conf_t *)); + if (hdclcfp == NULL) { + return NGX_ERROR; + } + + phdclcf->regex_locations = hdclcfp; + + for (q = regex; + q != ngx_queue_sentinel(locations); + q = ngx_queue_next(q)) + { + lq = (ngx_http_dynamic_location_queue_t *) q; + + *(hdclcfp++) = lq->exact; + } + + *hdclcfp = NULL; + + ngx_queue_split(locations, regex, &tail); + } + +#endif + + return NGX_OK; +} + +static ngx_int_t +ngx_http_dynamic_core_init_static_location_trees(ngx_conf_t *cf, + ngx_http_dynamic_core_loc_conf_t *phdclcf) +{ + ngx_queue_t *locations; + + locations = phdclcf->locations; + + if (locations == NULL) { + return NGX_OK; + } + + if (ngx_queue_empty(locations)) { + return NGX_OK; + } + + if (ngx_http_dynamic_core_join_exact_locations(cf, locations) != NGX_OK) { + return NGX_ERROR; + } + + ngx_http_dynamic_core_create_locations_list(locations, + ngx_queue_head(locations)); + + phdclcf->static_locations = + ngx_http_dynamic_core_create_locations_tree(cf, locations, 0); + if (phdclcf->static_locations == NULL) { + return NGX_ERROR; + } + + return NGX_OK; +} + +static ngx_int_t +ngx_http_dynamic_core_add_location(ngx_conf_t *cf, ngx_queue_t **locations, + ngx_http_dynamic_core_loc_conf_t *hdclcf) +{ + ngx_http_dynamic_location_queue_t *lq; + + if (ngx_http_dynamic_core_predeal_location(cf, hdclcf) != NGX_OK) { + return NGX_ERROR; + } + + if (*locations == NULL) { + *locations = ngx_pcalloc(cf->temp_pool, + sizeof(ngx_http_location_queue_t)); + if (*locations == NULL) { + return NGX_ERROR; + } + + ngx_queue_init(*locations); + } + + lq = ngx_pcalloc(cf->temp_pool, sizeof(ngx_http_location_queue_t)); + if (lq == NULL) { + return NGX_ERROR; + } + + if (hdclcf->exact_match +#if (NGX_PCRE) + || hdclcf->regex +#endif + ) + { + lq->exact = hdclcf; + lq->inclusive = NULL; + } else { + lq->exact = NULL; + lq->inclusive = hdclcf; + } + + lq->name = &hdclcf->name; + lq->file_name = cf->conf_file->file.name.data; + lq->line = cf->conf_file->line; + + ngx_queue_init(&lq->list); + + ngx_queue_insert_tail(*locations, &lq->queue); + + return NGX_OK; +} + +/* + * NGX_OK - exact or regex match + * NGX_AGAIN - inclusive match + * NGX_DECLINED - no match + */ + +static ngx_int_t +ngx_http_dynamic_core_find_static_location(ngx_http_request_t *r, + ngx_http_dynamic_core_loc_conf_t *phdclcf, + ngx_http_dynamic_core_loc_conf_t **hdclcf) +{ + ngx_http_dynamic_location_tree_node_t *node; + u_char *uri; + size_t len, n; + ngx_int_t rc, rv; + + len = r->uri.len; + uri = r->uri.data; + + rv = NGX_DECLINED; + + node = phdclcf->static_locations; + + for ( ;; ) { + + if (node == NULL) { + return rv; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "test location: \"%*s\"", + (size_t) node->len, node->name); + + n = (len <= (size_t) node->len) ? len : node->len; + + rc = ngx_filename_cmp(uri, node->name, n); + + if (rc != 0) { + node = (rc < 0) ? node->left : node->right; + + continue; + } + + if (len > (size_t) node->len) { + + if (node->inclusive) { + + *hdclcf = node->inclusive-> + loc_conf[ngx_http_dynamic_core_module.ctx_index]; + rv = NGX_AGAIN; + + node = node->tree; + uri += n; + len -= n; + + continue; + } + + /* exact only */ + + node = node->right; + + continue; + } + + if (len == (size_t) node->len) { + + if (node->exact) { + *hdclcf = node->exact-> + loc_conf[ngx_http_dynamic_core_module.ctx_index]; + return NGX_OK; + + } else { + *hdclcf = node->inclusive-> + loc_conf[ngx_http_dynamic_core_module.ctx_index]; + return NGX_AGAIN; + } + } + + /* len < node->len */ + + node = node->left; + } +} + +/* + * NGX_OK - exact or regex match + * NGX_AGAIN - inclusive match + * NGX_ERROR - regex error + * NGX_DECLINED - no match + */ + +static ngx_int_t +ngx_http_dynamic_core_find_location(ngx_http_request_t *r, + ngx_http_dynamic_core_srv_conf_t *hdcscf, + ngx_http_dynamic_core_loc_conf_t **hdclcf) +{ + ngx_int_t rc; + ngx_http_dynamic_core_loc_conf_t *phdclcf; +#if (NGX_PCRE) + ngx_int_t n; + ngx_uint_t noregex; + ngx_http_dynamic_core_loc_conf_t **hdclcfp; + + noregex = 0; +#endif + + phdclcf = hdcscf->hdclcf; + + rc = ngx_http_dynamic_core_find_static_location(r, phdclcf, hdclcf); + + if (rc == NGX_AGAIN) { + +#if (NGX_PCRE) + noregex = (*hdclcf)->noregex; +#endif + + } + + if (rc == NGX_OK || rc == NGX_DONE) { + return rc; + } + + /* rc == NGX_DECLINED or rc == NGX_AGAIN in nested location */ + +#if (NGX_PCRE) + + if (noregex == 0 && phdclcf->regex_locations) { + + for (hdclcfp = phdclcf->regex_locations; *hdclcfp; hdclcfp++) { + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "test location: ~ \"%V\"", &(*hdclcfp)->name); + + n = ngx_regex_exec((*hdclcfp)->regex->regex, &r->uri, NULL, 0); + + if (n == NGX_OK) { + *hdclcf = (*hdclcfp)-> + loc_conf[ngx_http_dynamic_core_module.ctx_index]; + + return NGX_OK; + } + + if (n == NGX_REGEX_NO_MATCHED) { + continue; + } + + return NGX_ERROR; + } + } +#endif + + return rc; +} + +static ngx_int_t +ngx_http_dynamic_core_init_virtual_servers(ngx_conf_t *cf, + ngx_http_dynamic_conf_t *hdcf) +{ + ngx_http_dynamic_core_main_conf_t *hdcmcf; + ngx_http_dynamic_core_srv_conf_t **hdcscfp; + ngx_hash_init_t hash; + ngx_hash_keys_arrays_t ha; + ngx_http_dynamic_server_name_t *name; + ngx_uint_t s, n; + ngx_int_t rc; +#if (NGX_PCRE) + ngx_uint_t regex, i; + + regex = 0; +#endif + + hdcmcf = hdcf->main_conf[ngx_http_dynamic_core_module.ctx_index]; + + ngx_memzero(&ha, sizeof(ngx_hash_keys_arrays_t)); + + ha.temp_pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log); + if (ha.temp_pool == NULL) { + return NGX_ERROR; + } + + ha.pool = cf->pool; + + if (ngx_hash_keys_array_init(&ha, NGX_HASH_LARGE) != NGX_OK) { + goto failed; + } + + hdcscfp = hdcmcf->servers.elts; + hdcmcf->default_server = hdcscfp[0]; + + for (s = 0; s < hdcmcf->servers.nelts; ++s) { + + name = hdcscfp[s]->server_names.elts; + + for (n = 0; n < hdcscfp[s]->server_names.nelts; ++n) { +#if (NGX_PCRE) + if (name[n].regex) { + ++regex; + continue; + } +#endif + + rc = ngx_hash_add_key(&ha, &name[n].name, name[n].server, + NGX_HASH_WILDCARD_KEY); + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_DECLINED) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "invalid server name or wildcard \"%V\"", + &name[n].name); + return NGX_ERROR; + } + + if (rc == NGX_BUSY) { + ngx_log_error(NGX_LOG_WARN, cf->log, 0, + "conflicting server name \"%V\", ignored", + &name[n].name); + } + } + } + + hash.key = ngx_hash_key_lc; + hash.max_size = hdcmcf->server_names_hash_max_size; + hash.bucket_size = hdcmcf->server_names_hash_bucket_size; + hash.name = "http_dynamic_server_names_hash"; + hash.pool = cf->pool; + + if (ha.keys.nelts) { + hash.hash = &hdcmcf->names.hash; + hash.temp_pool = NULL; + + if (ngx_hash_init(&hash, ha.keys.elts, ha.keys.nelts) != NGX_OK) { + goto failed; + } + } + + if (ha.dns_wc_head.nelts) { + + ngx_qsort(ha.dns_wc_head.elts, (size_t) ha.dns_wc_head.nelts, + sizeof(ngx_hash_key_t), ngx_dynamic_cmp_dns_wildcards); + + hash.hash = NULL; + hash.temp_pool = ha.temp_pool; + + if (ngx_hash_wildcard_init(&hash, ha.dns_wc_head.elts, + ha.dns_wc_head.nelts) != NGX_OK) + { + goto failed; + } + + hdcmcf->names.wc_head = (ngx_hash_wildcard_t *) hash.hash; + } + + if (ha.dns_wc_tail.nelts) { + + ngx_qsort(ha.dns_wc_tail.elts, (size_t) ha.dns_wc_tail.nelts, + sizeof(ngx_hash_key_t), ngx_dynamic_cmp_dns_wildcards); + + hash.hash = NULL; + hash.temp_pool = ha.temp_pool; + + if (ngx_hash_wildcard_init(&hash, ha.dns_wc_tail.elts, + ha.dns_wc_tail.nelts) != NGX_OK) + { + goto failed; + } + + hdcmcf->names.wc_tail = (ngx_hash_wildcard_t *) hash.hash; + + } + + ngx_destroy_pool(ha.temp_pool); + +#if (NGX_PCRE) + if (regex == 0) { + return NGX_OK; + } + + hdcmcf->nregex = regex; + hdcmcf->regex = ngx_pcalloc(cf->pool, + regex * sizeof(ngx_http_dynamic_server_name_t)); + if (hdcmcf->regex == NULL) { + return NGX_ERROR; + } + + i = 0; + + for (s = 0; s < hdcmcf->servers.nelts; ++s) { + + name = hdcscfp[s]->server_names.elts; + + for (n = 0; n < hdcscfp[s]->server_names.nelts; ++n) { + if (name[n].regex) { + hdcmcf->regex[i++] = name[n]; + } + } + } +#endif + + return NGX_OK; + +failed: + ngx_destroy_pool(ha.temp_pool); + + return NGX_ERROR; +} + +static ngx_int_t +ngx_http_dynamic_core_find_virtual_server(ngx_http_request_t *r, + ngx_http_dynamic_core_main_conf_t *hdcmcf, + ngx_http_dynamic_core_srv_conf_t **hdcscfp) +{ + ngx_http_dynamic_core_srv_conf_t *hdcscf; + ngx_str_t *server; + + server = &r->headers_in.server; + + hdcscf = ngx_hash_find_combined(&hdcmcf->names, + ngx_hash_key(server->data, server->len), + server->data, server->len); + + if (hdcscf) { + *hdcscfp = hdcscf; + return NGX_OK; + } + +#if (NGX_PCRE) + + if (server->len && hdcmcf->nregex) { + ngx_int_t n; + ngx_uint_t i; + ngx_http_dynamic_server_name_t *sn; + + sn = hdcmcf->regex; + + for (i = 0; i < hdcmcf->nregex; ++i) { + + n = ngx_regex_exec(sn[i].regex->regex, server, NULL, 0); + + if (n == NGX_REGEX_NO_MATCHED) { + continue; + } + + if (n >= 0) { + *hdcscfp = sn[i].server; + return NGX_OK; + } + + return NGX_ERROR; + } + } +#endif + + return NGX_DECLINED; +} + +static char * +ngx_http_dynamic_core_location(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_dynamic_module_t *module; + ngx_http_dynamic_core_loc_conf_t *hdclcf, *phdclcf; + ngx_http_dynamic_core_srv_conf_t *hdcscf; + ngx_conf_t pcf; + void *mconf, **loc_conf; + ngx_uint_t i, ci; + char *rv = NULL; + + hdcscf = conf; + phdclcf = hdcscf->hdclcf; + + /* loc_conf is temp cause hdclcf does not created now */ + loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module); + if (loc_conf == NULL) { + return NGX_CONF_ERROR; + } + + for (i = 0; cf->cycle->modules[i]; ++i) { + if (cf->cycle->modules[i]->type != NGX_HTTP_MODULE) { + continue; + } + + module = (ngx_http_dynamic_module_t *) + cf->cycle->modules[i]->spare_hook0; + + if (module == NULL) { + continue; + } + + if (module->create_loc_conf) { + mconf = module->create_loc_conf(cf); + if (mconf == NULL) { + return NGX_CONF_ERROR; + } + + loc_conf[cf->cycle->modules[i]->ctx_index] = mconf; + } + } + + hdclcf = loc_conf[ngx_http_dynamic_core_module.ctx_index]; + hdclcf->loc_conf = loc_conf; + + if (ngx_http_dynamic_core_add_location(cf, &phdclcf->locations, hdclcf) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + pcf = *cf; + cf->ctx = hdclcf->loc_conf; + cf->cmd_type = NGX_HTTP_LOC_CONF; + + if (ngx_dynamic_conf_parse(cf, 0) != NGX_OK) { + goto failed; + } + + for (i = 0; cf->cycle->modules[i]; ++i) { + if (cf->cycle->modules[i]->type != NGX_HTTP_MODULE) { + continue; + } + + module = (ngx_http_dynamic_module_t *) + cf->cycle->modules[i]->spare_hook0; + ci = cf->cycle->modules[i]->ctx_index; + if (module == NULL) { + continue; + } + + if (module->init_loc_conf) { + rv = module->init_loc_conf(cf, hdclcf->loc_conf[ci]); + if (rv != NGX_CONF_OK) { + goto failed; + } + } + } + + *cf = pcf; + + return NGX_CONF_OK; + +failed: + *cf = pcf; + + if (rv) { + return rv; + } + + return NGX_CONF_ERROR; +} + +static char * +ngx_http_dynamic_core_server_name(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ + ngx_http_dynamic_core_srv_conf_t *hdcscf; + u_char ch; + ngx_str_t *value; + ngx_uint_t i; + ngx_http_dynamic_server_name_t *sn; + + hdcscf = conf; + + value = cf->args->elts; + + for (i = 1; i < cf->args->nelts; i++) { + + ch = value[i].data[0]; + + if ((ch == '*' && (value[i].len < 3 || value[i].data[1] != '.')) + || (ch == '.' && value[i].len < 2)) + { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "server name \"%V\" is invalid", &value[i]); + return NGX_CONF_ERROR; + } + + if (ngx_strchr(value[i].data, '/')) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "server name \"%V\" has suspicious symbols", + &value[i]); + } + + sn = ngx_array_push(&hdcscf->server_names); + if (sn == NULL) { + return NGX_CONF_ERROR; + } + +#if (NGX_PCRE) + sn->regex = NULL; +#endif + sn->server = hdcscf; + + if (ngx_strcasecmp(value[i].data, (u_char *) "$hostname") == 0) { + sn->name = cf->cycle->hostname; + + } else { + sn->name = value[i]; + } + + if (value[i].data[0] != '~') { + ngx_strlow(sn->name.data, sn->name.data, sn->name.len); + continue; + } + +#if (NGX_PCRE) + { + u_char *p; + ngx_regex_compile_t rc; + u_char errstr[NGX_MAX_CONF_ERRSTR]; + + if (value[i].len == 1) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "empty regex in server name \"%V\"", &value[i]); + return NGX_CONF_ERROR; + } + + value[i].len--; + value[i].data++; + + ngx_memzero(&rc, sizeof(ngx_regex_compile_t)); + + rc.pattern = value[i]; + rc.err.len = NGX_MAX_CONF_ERRSTR; + rc.err.data = errstr; + + for (p = value[i].data; p < value[i].data + value[i].len; p++) { + if (*p >= 'A' && *p <= 'Z') { + rc.options = NGX_REGEX_CASELESS; + break; + } + } + + sn->regex = ngx_dynamic_regex_compile(cf, &rc); + if (sn->regex == NULL) { + return NGX_CONF_ERROR; + } + + sn->name = value[i]; + } +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "using regex \"%V\" " + "requires PCRE library", &value[i]); + + return NGX_CONF_ERROR; +#endif + } + + return NGX_CONF_OK; +} + +static char * +ngx_http_dynamic_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_dynamic_module_t *module; + ngx_http_dynamic_core_main_conf_t *hdcmcf; + ngx_http_dynamic_core_srv_conf_t *hdcscf, **hdcscfp; + ngx_http_dynamic_core_loc_conf_t *phdclcf; + ngx_conf_t pcf; + void *mconf, **srv_conf; + ngx_uint_t i, ci; + char *rv = NULL; + + hdcmcf = conf; + + /* srv_conf is temp cause hdcscf does not created now */ + srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module); + if (srv_conf == NULL) { + return NGX_CONF_ERROR; + } + + for (i = 0; cf->cycle->modules[i]; ++i) { + if (cf->cycle->modules[i]->type != NGX_HTTP_MODULE) { + continue; + } + + module = (ngx_http_dynamic_module_t *) + cf->cycle->modules[i]->spare_hook0; + if (module == NULL) { + continue; + } + + if (module->create_srv_conf) { + mconf = module->create_srv_conf(cf); + if (mconf == NULL) { + return NGX_CONF_ERROR; + } + + srv_conf[cf->cycle->modules[i]->ctx_index] = mconf; + } + } + + hdcscf = srv_conf[ngx_http_dynamic_core_module.ctx_index]; + hdcscf->srv_conf = srv_conf; + + hdcscf->hdclcf = ngx_pcalloc(cf->pool, + sizeof(ngx_http_dynamic_core_loc_conf_t)); + + /* save hdcscf into hdcmcf */ + hdcscfp = ngx_array_push(&hdcmcf->servers); + if (hdcscfp == NULL) { + return NGX_CONF_ERROR; + } + *hdcscfp = hdcscf; + + pcf = *cf; + cf->ctx = hdcscf->srv_conf; + cf->cmd_type = NGX_HTTP_SRV_CONF; + + if (ngx_dynamic_conf_parse(cf, 0) != NGX_OK) { + goto failed; + } + + for (i = 0; cf->cycle->modules[i]; ++i) { + if (cf->cycle->modules[i]->type != NGX_HTTP_MODULE) { + continue; + } + + module = (ngx_http_dynamic_module_t *) + cf->cycle->modules[i]->spare_hook0; + ci = cf->cycle->modules[i]->ctx_index; + if (module == NULL) { + continue; + } + + if (module->init_srv_conf) { + rv = module->init_srv_conf(cf, hdcscf->srv_conf[ci]); + if (rv != NGX_CONF_OK) { + goto failed; + } + } + } + + *cf = pcf; + + phdclcf = hdcscf->hdclcf; + + if (ngx_http_dynamic_core_init_locations(cf, phdclcf) != NGX_OK) { + return NGX_CONF_ERROR; + } + + if (ngx_http_dynamic_core_init_static_location_trees(cf, phdclcf) != NGX_OK) { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; + +failed: + *cf = pcf; + + if (rv) { + return rv; + } + + return NGX_CONF_ERROR; + +} + +static char * +ngx_http_dynamic_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_dynamic_conf_t *hdccf; + ngx_http_dynamic_module_t *module; + ngx_conf_t pcf; + ngx_uint_t i, ci; + char *rv = NULL; + + hdccf = conf; + + /* create main_conf ctx */ + hdccf->main_conf = ngx_pcalloc(cf->pool, sizeof(void *) + * ngx_http_max_module); + if (hdccf->main_conf == NULL) { + return NGX_CONF_ERROR; + } + + /* create http dynamic conf for all http module */ + for (i = 0; cf->cycle->modules[i]; ++i) { + if (cf->cycle->modules[i]->type != NGX_HTTP_MODULE) { + continue; + } + + module = (ngx_http_dynamic_module_t *) + cf->cycle->modules[i]->spare_hook0; + ci = cf->cycle->modules[i]->ctx_index; + + if (module == NULL) { + continue; + } + + if (module->create_main_conf) { + hdccf->main_conf[ci] = module->create_main_conf(cf); + if (hdccf->main_conf[ci] == NULL) { + return NGX_CONF_ERROR; + } + } + } + + /* save conf for recovery */ + pcf = *cf; + + cf->ctx = hdccf->main_conf; + cf->module_type = NGX_HTTP_MODULE; + cf->cmd_type = NGX_HTTP_MAIN_CONF; + + if (ngx_dynamic_conf_parse(cf, 0) != NGX_OK) { + goto failed; + } + + for (i = 0; cf->cycle->modules[i]; ++i) { + if (cf->cycle->modules[i]->type != NGX_HTTP_MODULE) { + continue; + } + + module = (ngx_http_dynamic_module_t *) + cf->cycle->modules[i]->spare_hook0; + ci = cf->cycle->modules[i]->ctx_index; + + if (module == NULL) { + continue; + } + + if (module->init_main_conf) { + rv = module->init_main_conf(cf, hdccf->main_conf[ci]); + if (rv != NGX_CONF_OK) { + goto failed; + } + } + } + + if (ngx_http_dynamic_core_init_virtual_servers(cf, hdccf) != NGX_OK) { + goto failed; + } + + *cf = pcf; + + return NGX_CONF_OK; + +failed: + *cf = pcf; + + if (rv) { + return rv; + } + + return NGX_CONF_ERROR; +} + + +/* interface from here */ + +void * +ngx_http_get_module_main_dconf(ngx_http_request_t *r, ngx_module_t *m) +{ + ngx_http_dynamic_conf_t *hdcf; + + hdcf = ngx_get_dconf(&ngx_http_dynamic_module); + if (hdcf == NULL) { + return NULL; + } + + return hdcf->main_conf[m->ctx_index]; +} + +void * +ngx_http_get_module_srv_dconf(ngx_http_request_t *r, ngx_module_t *m) +{ + ngx_http_dynamic_core_main_conf_t *hdcmcf; + ngx_http_dynamic_core_srv_conf_t *hdcscf; + ngx_int_t rc; + + hdcmcf = ngx_http_get_module_main_dconf(r, &ngx_http_dynamic_core_module); + if (hdcmcf == NULL) { + return NULL; + } + + rc = ngx_http_dynamic_core_find_virtual_server(r, hdcmcf, &hdcscf); + switch (rc) { + case NGX_ERROR: + return NULL; + case NGX_DECLINED: /* virtual server not found */ + return hdcmcf->default_server->srv_conf[m->ctx_index]; + default: + return hdcscf->srv_conf[m->ctx_index]; + } +} + +void * +ngx_http_get_module_loc_dconf(ngx_http_request_t *r, ngx_module_t *m) +{ + ngx_http_dynamic_core_srv_conf_t *hdcscf; + ngx_http_dynamic_core_loc_conf_t *hdclcf; + ngx_int_t rc; + + hdcscf = ngx_http_get_module_srv_dconf(r, &ngx_http_dynamic_core_module); + if (hdcscf == NULL) { + return NULL; + } + + hdclcf = NULL; + rc = ngx_http_dynamic_core_find_location(r, hdcscf, &hdclcf); + if (rc == NGX_ERROR) { + return NULL; + } + + if (hdclcf) { + return hdclcf->loc_conf[m->ctx_index]; + } + + return NULL; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_http_dynamic.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_http_dynamic.h new file mode 100644 index 0000000000..04d2a961d2 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_http_dynamic.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#ifndef _NGX_HTTP_DYNAMIC_H_INCLUDED_ +#define _NGX_HTTP_DYNAMIC_H_INCLUDED_ + + +#include +#include + + +typedef struct { + void *(*create_main_conf)(ngx_conf_t *cf); + char *(*init_main_conf)(ngx_conf_t *cf, void *conf); + + void *(*create_srv_conf)(ngx_conf_t *cf); + char *(*init_srv_conf)(ngx_conf_t *cf, void *conf); + + void *(*create_loc_conf)(ngx_conf_t *cf); + char *(*init_loc_conf)(ngx_conf_t *cf, void *conf); +} ngx_http_dynamic_module_t; + + +void *ngx_http_get_module_main_dconf(ngx_http_request_t *r, ngx_module_t *m); +void *ngx_http_get_module_srv_dconf(ngx_http_request_t *r, ngx_module_t *m); +void *ngx_http_get_module_loc_dconf(ngx_http_request_t *r, ngx_module_t *m); + + +#endif diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_http_trace_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_http_trace_module.c new file mode 100644 index 0000000000..20fa12a4a7 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_http_trace_module.c @@ -0,0 +1,527 @@ +#include +#include +#include + + +typedef struct { + ngx_flag_t trace; +} ngx_http_trace_main_conf_t; + + +typedef struct { + u_char traceid[32]; /* X-NTM-Traceid */ + u_char cid[32]; /* X-NTM-Currentid */ + u_char pid[32]; /* X-NTM-Parentid */ + ngx_flag_t debug; /* X-NTM-Debug */ +} ngx_http_trace_ctx_t; + + +static void *ngx_http_trace_create_main_conf(ngx_conf_t *cf); +static char *ngx_http_trace_init_main_conf(ngx_conf_t *cf, void *conf); + +static char *ngx_http_trace(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); + +static ngx_int_t ngx_http_trace_add_variables(ngx_conf_t *cf); +static ngx_int_t ngx_http_trace_init(ngx_conf_t *cf); + +static ngx_int_t ngx_http_trace_traceid_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_trace_currentid_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_trace_parentid_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_trace_debug_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_trace_newid_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); + + +static ngx_command_t ngx_http_trace_commands[] = { + + { ngx_string("http_trace"), + NGX_HTTP_MAIN_CONF|NGX_CONF_NOARGS, + ngx_http_trace, + 0, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_http_trace_module_ctx = { + ngx_http_trace_add_variables, /* preconfiguration */ + ngx_http_trace_init, /* postconfiguration */ + + ngx_http_trace_create_main_conf, /* create main configuration */ + ngx_http_trace_init_main_conf, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_http_trace_module = { + NGX_MODULE_V1, + &ngx_http_trace_module_ctx, /* module context */ + ngx_http_trace_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_http_variable_t ngx_http_trace_vars[] = { + + { ngx_string("ntm_traceid"), NULL, + ngx_http_trace_traceid_variable, 0, 0, 0 }, + + { ngx_string("ntm_currentid"), NULL, + ngx_http_trace_currentid_variable, 0, 0, 0 }, + + { ngx_string("ntm_parentid"), NULL, + ngx_http_trace_parentid_variable, 0, 0, 0 }, + + { ngx_string("ntm_debug"), NULL, + ngx_http_trace_debug_variable, 0, 0, 0 }, + + { ngx_string("ntm_newid"), NULL, + ngx_http_trace_newid_variable, 0, 0, 0 }, + + { ngx_null_string, NULL, NULL, 0, 0, 0 } +}; + + +static void +ngx_http_trace_genid(u_char *id) { + ngx_sprintf(id, "%08xD%08xD%08xD%08xD", + (uint32_t) ngx_random(), (uint32_t) ngx_random(), + (uint32_t) ngx_random(), (uint32_t) ngx_random()); +} + + +static u_char * +ngx_http_trace_log_error(ngx_log_t *log, u_char *buf, size_t len) +{ + u_char *p; + ngx_http_request_t *r; + ngx_http_log_ctx_t *ctx; + ngx_http_trace_ctx_t *tctx; + ngx_str_t traceid, currentid, parentid; + + if (log->action) { + p = ngx_snprintf(buf, len, " while %s", log->action); + len -= p - buf; + buf = p; + } + + ctx = log->data; + + p = ngx_snprintf(buf, len, ", client: %V", &ctx->connection->addr_text); + len -= p - buf; + buf = p; + + r = ctx->request; + + if (r) { + p = r->log_handler(r, ctx->current_request, p, len); + len -= p - buf; + buf = p; + + } else { + p = ngx_snprintf(p, len, ", server: %V", + &ctx->connection->listening->addr_text); + len -= p - buf; + buf = p; + } + + tctx = ngx_http_get_module_ctx(r, ngx_http_trace_module); + if (tctx) { + traceid.data = tctx->traceid; + traceid.len = sizeof(tctx->traceid); + + currentid.data = tctx->cid; + currentid.len = sizeof(tctx->cid); + + parentid.data = tctx->pid; + parentid.len = sizeof(tctx->pid); + + p = ngx_snprintf(p, len, ", [NGINX-TRACE] traceid: %V, currentid: %V, " + "parentid: %V [NGINX-TRACE-END]", + &traceid, ¤tid, &parentid); + } + + return p; +} + + +// First request without X-NTM-Traceid X-NTM-Currentid or X-NTM-Parentid +// Need to gen these ids and set X-NTM headers in main request +static ngx_int_t +ngx_http_trace_first_request(ngx_http_request_t *r, ngx_http_trace_ctx_t *ctx) +{ + ngx_table_elt_t *h; + + ngx_http_trace_genid(ctx->traceid); + ngx_http_trace_genid(ctx->cid); + ngx_sprintf(ctx->pid, "00000000000000000000000000000000"); + + // Set X-NTM-Traceid + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->key.data = (u_char *) "X-NTM-Traceid"; + h->key.len = sizeof("X-NTM-Traceid") - 1; + + h->value.data = ctx->traceid; + h->value.len = sizeof(ctx->traceid); + + h->lowcase_key = ngx_pnalloc(r->pool, h->key.len); + if (h->lowcase_key == NULL) { + return NGX_ERROR; + } + h->hash = ngx_hash_strlow(h->lowcase_key, h->key.data, h->key.len); + + // Set X-NTM-Currentid + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->key.data = (u_char *) "X-NTM-Currentid"; + h->key.len = sizeof("X-NTM-Currentid") - 1; + + h->value.data = ctx->cid; + h->value.len = sizeof(ctx->cid); + + h->lowcase_key = ngx_pnalloc(r->pool, h->key.len); + if (h->lowcase_key == NULL) { + return NGX_ERROR; + } + h->hash = ngx_hash_strlow(h->lowcase_key, h->key.data, h->key.len); + + // Set X-NTM-Parentid + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->key.data = (u_char *) "X-NTM-Parentid"; + h->key.len = sizeof("X-NTM-Parentid") - 1; + + h->value.data = ctx->pid; + h->value.len = sizeof(ctx->pid); + + h->lowcase_key = ngx_pnalloc(r->pool, h->key.len); + if (h->lowcase_key == NULL) { + return NGX_ERROR; + } + h->hash = ngx_hash_strlow(h->lowcase_key, h->key.data, h->key.len); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_trace_handler(ngx_http_request_t *r) +{ + ngx_http_trace_main_conf_t *tmcf; + ngx_http_trace_ctx_t *ctx; + ngx_http_variable_value_t v; + ngx_str_t header; + + if (r != r->main) { // subrequest + return NGX_DECLINED; + } + + tmcf = ngx_http_get_module_main_conf(r, ngx_http_trace_module); + if (!tmcf->trace) { + return NGX_DECLINED; + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_trace_module); + if (ctx) { // ctx already create, enter twice + return NGX_DECLINED; + } + + ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_trace_ctx_t)); + if (ctx == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + ngx_http_set_ctx(r, ctx, ngx_http_trace_module); + + // traceid currentid parentid log into nginx error log + r->connection->log->handler = ngx_http_trace_log_error; + + // Get X-NTM-Debug + header.data = (u_char *) "http_x_ntm_debug"; + header.len = sizeof("http_x_ntm_debug") - 1; + ngx_http_variable_unknown_header(&v, &header, &r->headers_in.headers.part, + sizeof("http_") - 1); + if (!v.not_found && v.len == 1 && v.data[0] == '1') { + // Has header X-NTM-Debug: 1 + ctx->debug = 1; + } + + // Get X-NTM-Traceid + header.data = (u_char *) "http_x_ntm_traceid"; + header.len = sizeof("http_x_ntm_traceid") - 1; + ngx_http_variable_unknown_header(&v, &header, &r->headers_in.headers.part, + sizeof("http_") - 1); + if (v.not_found) { + goto notfound; + } + + ngx_memcpy(ctx->traceid, v.data, ngx_min(v.len, sizeof(ctx->traceid))); + + // Get X-NTM-Currentid + header.data = (u_char *) "http_x_ntm_currentid"; + header.len = sizeof("http_x_ntm_currentid") - 1; + ngx_http_variable_unknown_header(&v, &header, &r->headers_in.headers.part, + sizeof("http_") - 1); + if (v.not_found) { + goto notfound; + } + + ngx_memcpy(ctx->cid, v.data, ngx_min(v.len, sizeof(ctx->cid))); + + // Get X-NTM-Parentid + header.data = (u_char *) "http_x_ntm_parentid"; + header.len = sizeof("http_x_ntm_parentid") - 1; + ngx_http_variable_unknown_header(&v, &header, &r->headers_in.headers.part, + sizeof("http_") - 1); + if (v.not_found) { + goto notfound; + } + + ngx_memcpy(ctx->pid, v.data, ngx_min(v.len, sizeof(ctx->pid))); + + return NGX_DECLINED; + +notfound: + + if (ngx_http_trace_first_request(r, ctx) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + return NGX_DECLINED; +} + + +static ngx_int_t +ngx_http_trace_traceid_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_http_trace_ctx_t *ctx; + + ctx = ngx_http_get_module_ctx(r, ngx_http_trace_module); + if (ctx == NULL) { + v->not_found = 1; + return NGX_OK; + } + + v->data = ctx->traceid; + v->len = sizeof(ctx->traceid); + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_trace_currentid_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_http_trace_ctx_t *ctx; + + ctx = ngx_http_get_module_ctx(r, ngx_http_trace_module); + if (ctx == NULL) { + v->not_found = 1; + return NGX_OK; + } + + v->data = ctx->cid; + v->len = sizeof(ctx->cid); + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_trace_parentid_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_http_trace_ctx_t *ctx; + + ctx = ngx_http_get_module_ctx(r, ngx_http_trace_module); + if (ctx == NULL) { + v->not_found = 1; + return NGX_OK; + } + + v->data = ctx->pid; + v->len = sizeof(ctx->pid); + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_trace_debug_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_http_trace_ctx_t *ctx; + + ctx = ngx_http_get_module_ctx(r, ngx_http_trace_module); + if (ctx == NULL) { + v->not_found = 1; + return NGX_OK; + } + + if (ctx->debug) { + v->data = (u_char *) "1"; + } else { + v->data = (u_char *) "0"; + } + + v->len = sizeof("1") - 1; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_trace_newid_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_http_trace_ctx_t *ctx; + u_char *newid; + + ctx = ngx_http_get_module_ctx(r, ngx_http_trace_module); + if (ctx == NULL) { + v->not_found = 1; + return NGX_OK; + } + + newid = ngx_pcalloc(r->pool, sizeof(ctx->cid)); + if (newid == NULL) { + return NGX_ERROR; + } + + // fill newid + ngx_http_trace_genid(newid); + + v->data = newid; + v->len = sizeof(ctx->cid); + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + return NGX_OK; +} + + +static void * +ngx_http_trace_create_main_conf(ngx_conf_t *cf) +{ + ngx_http_trace_main_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_trace_main_conf_t)); + if (conf == NULL) { + return NULL; + } + + conf->trace = NGX_CONF_UNSET; + + return conf; +} + + +static char * +ngx_http_trace_init_main_conf(ngx_conf_t *cf, void *conf) +{ + ngx_http_trace_main_conf_t *tmcf; + + tmcf = conf; + + ngx_conf_init_value(tmcf->trace, 0); + + return NGX_CONF_OK; +} + + +static char * +ngx_http_trace(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_trace_main_conf_t *tmcf; + + tmcf = conf; + + if (tmcf->trace != NGX_CONF_UNSET) { + return "is duplicate"; + } + + tmcf->trace = 1; + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_http_trace_add_variables(ngx_conf_t *cf) +{ + ngx_http_variable_t *var, *v; + + for (v = ngx_http_trace_vars; v->name.len; v++) { + var = ngx_http_add_variable(cf, &v->name, v->flags); + if (var == NULL) { + return NGX_ERROR; + } + + var->get_handler = v->get_handler; + var->data = v->data; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_trace_init(ngx_conf_t *cf) +{ + ngx_http_handler_pt *h; + ngx_http_core_main_conf_t *cmcf; + + cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); + + h = ngx_array_push(&cmcf->phases[NGX_HTTP_POST_READ_PHASE].handlers); + if (h == NULL) { + return NGX_ERROR; + } + + *h = ngx_http_trace_handler; + + return NGX_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_map.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_map.c new file mode 100644 index 0000000000..c41bc16d4b --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_map.c @@ -0,0 +1,327 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#include "ngx_map.h" + + +static void +ngx_map_rbtree_insert_value(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, + ngx_rbtree_node_t *sentinel) +{ + ngx_rbtree_node_t **p; + intptr_t *raw_key, *raw_key_temp; + ngx_map_t **map; + + for (;;) { + + if (node->key < temp->key) { + p = &temp->left; + } else if (node->key > temp->key) { + p = &temp->right; + } else { + raw_key = (intptr_t *)((char *) node + + offsetof(ngx_map_node_t, raw_key)); + raw_key_temp = (intptr_t *)((char *) temp + + offsetof(ngx_map_node_t, raw_key)); + map = (ngx_map_t **)((char *) node + offsetof(ngx_map_node_t, map)); + + switch ((*map)->cmp(*raw_key, *raw_key_temp)) { + case -1: + p = &temp->left; + break; + case 1: + p = &temp->right; + break; + default: /* key is duplicate */ + return; + } + } + + if (*p == sentinel) { + break; + } + + temp = *p; + } + + *p = node; + node->parent = temp; + node->left = sentinel; + node->right = sentinel; + ngx_rbt_red(node); +} + +void +ngx_map_init(ngx_map_t *map, ngx_map_hash_pt hash, ngx_cmp_pt cmp) +{ + ngx_rbtree_init(&map->rbtree, &map->sentinel, ngx_map_rbtree_insert_value); + map->hash = hash; + map->cmp = cmp; +} + +ngx_map_node_t * +ngx_map_begin(ngx_map_t *map) +{ + ngx_rbtree_node_t *p; + + if (ngx_map_empty(map)) { + return NULL; + } + + p = map->rbtree.root; + + for (;;) { + if (p->left == map->rbtree.sentinel) { + break; + } + + p = p->left; + } + + return (ngx_map_node_t *) p; +} + +ngx_map_node_t * +ngx_map_rbegin(ngx_map_t *map) +{ + ngx_rbtree_node_t *p; + + if (ngx_map_empty(map)) { + return NULL; + } + + p = map->rbtree.root; + + for (;;) { + if (p->right == map->rbtree.sentinel) { + break; + } + + p = p->right; + } + + return (ngx_map_node_t *) p; +} + +ngx_map_node_t * +ngx_map_next(ngx_map_node_t *n) +{ + ngx_map_t *map; + ngx_rbtree_node_t *p, *top; + + map = n->map; + p = &n->rn; + top = NULL; + + if (p->right != map->rbtree.sentinel) { + // current node has right subtree + top = p->right; + } else { + for (;;) { + if (p == map->rbtree.root) { + return NULL; + } + + if (p->parent->left == p) { + return (ngx_map_node_t *) p->parent; + } + + // p->parent->right == p + p = p->parent; + } + } + + // get the mininum node + p = top; + for (;;) { + if (p->left == map->rbtree.sentinel) { + break; + } + + p = p->left; + } + + return (ngx_map_node_t *) p; +} + +ngx_map_node_t * +ngx_map_prev(ngx_map_node_t *n) +{ + ngx_map_t *map; + ngx_rbtree_node_t *p, *top; + + map = n->map; + p = &n->rn; + top = NULL; + + if (p->left != map->rbtree.sentinel) { + // current node has left subtree + top = p->left; + } else { + for (;;) { + if (p == map->rbtree.root) { + return NULL; + } + + if (p->parent->right == p) { + return (ngx_map_node_t *) p->parent; + } + + // p->parent->left == p + p = p->parent; + } + } + + // get the maximum node + p = top; + for (;;) { + if (p->right == map->rbtree.sentinel) { + break; + } + + p = p->right; + } + + return (ngx_map_node_t *) p; +} + +void +ngx_map_insert(ngx_map_t *map, ngx_map_node_t *node, ngx_flag_t covered) +{ + ngx_map_node_t *n; + + node->rn.key = map->hash(node->raw_key); + node->map = map; + + n = ngx_map_find(map, node->raw_key); + if (n == NULL) { + ngx_rbtree_insert(&map->rbtree, &node->rn); + } else if (covered) { + ngx_map_delete(map, node->raw_key); + ngx_rbtree_insert(&map->rbtree, &node->rn); + } +} + +void +ngx_map_delete(ngx_map_t *map, intptr_t key) +{ + ngx_map_node_t *node; + + node = ngx_map_find(map, key); + if (node) { + ngx_rbtree_delete(&map->rbtree, &node->rn); + } +} + +ngx_map_node_t * +ngx_map_find(ngx_map_t *map, intptr_t key) +{ + ngx_rbtree_node_t *p; + ngx_rbtree_key_t k; + intptr_t *key_temp; + + if (ngx_map_empty(map)) { + return NULL; + } + + k = map->hash(key); + p = map->rbtree.root; + + for (;;) { + if (k < p->key) { + p = p->left; + } else if (k > p->key) { + p = p->right; + } else { + key_temp = (intptr_t *)((char *) p + + offsetof(ngx_map_node_t, raw_key)); + + switch (map->cmp(key, *key_temp)) { + case -1: + p = p->left; + break; + case 1: + p = p->right; + break; + case 0: + return (ngx_map_node_t *) p; + } + } + + if (p == map->rbtree.sentinel) { + return NULL; + } + } +} + + +/* ngx_str_t */ +ngx_rbtree_key_t +ngx_map_hash_str(intptr_t key) +{ + ngx_str_t *k; + + k = (ngx_str_t *) key; + + return ngx_hash_key(k->data, k->len); +} + +int +ngx_cmp_str(intptr_t key1, intptr_t key2) +{ + ngx_str_t *k1, *k2; + ngx_int_t rc; + + k1 = (ngx_str_t *) key1; + k2 = (ngx_str_t *) key2; + + rc = ngx_memn2cmp(k1->data, k2->data, k1->len, k2->len); + + if (rc < 0) { + return -1; + } else if (rc > 0) { + return 1; + } else { + return 0; + } +} + +/* ngx_uint_t */ +ngx_rbtree_key_t +ngx_map_hash_uint(intptr_t key) +{ + return (ngx_rbtree_key_t) key; +} + +int +ngx_cmp_uint(intptr_t key1, intptr_t key2) +{ + if ((ngx_uint_t) key1 < (ngx_uint_t) key2) { + return -1; + } else if ((ngx_uint_t) key1 > (ngx_uint_t) key2) { + return 1; + } else { + return 0; + } +} + +/* ngx_int_t */ +ngx_rbtree_key_t +ngx_map_hash_int(intptr_t key) +{ + return (ngx_rbtree_key_t) key; +} + +int +ngx_cmp_int(intptr_t key1, intptr_t key2) +{ + if ((ngx_int_t) key1 < (ngx_int_t) key2) { + return -1; + } else if ((ngx_int_t) key1 > (ngx_int_t) key2) { + return 1; + } else { + return 0; + } +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_map.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_map.h new file mode 100644 index 0000000000..c7626d3a92 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_map.h @@ -0,0 +1,131 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#ifndef _NGX_MAP_H_INCLUDED_ +#define _NGX_MAP_H_INCLUDED_ + + +#include +#include + + +/* + * key: key for map node + */ +typedef ngx_rbtree_key_t (* ngx_map_hash_pt)(intptr_t key); + +/* + * if key1 < key2, return -1 + * if key1 = key2, return 0 + * if key1 > key2, return 1 + */ +typedef int (* ngx_cmp_pt)(intptr_t key1, intptr_t key2); + + +/* ngx_str_t */ +ngx_rbtree_key_t ngx_map_hash_str(intptr_t key); +int ngx_cmp_str(intptr_t key1, intptr_t key2); + +/* ngx_uint_t */ +ngx_rbtree_key_t ngx_map_hash_uint(intptr_t key); +int ngx_cmp_uint(intptr_t key1, intptr_t key2); + +/* ngx_int_t */ +ngx_rbtree_key_t ngx_map_hash_int(intptr_t key); +int ngx_cmp_int(intptr_t key1, intptr_t key2); + + +typedef struct { + ngx_rbtree_t rbtree; + ngx_rbtree_node_t sentinel; + ngx_map_hash_pt hash; + ngx_cmp_pt cmp; +} ngx_map_t; + +typedef struct { + ngx_rbtree_node_t rn; + intptr_t raw_key; + ngx_map_t *map; +} ngx_map_node_t; + + +/* + * return value: + * None + * paras: + * map : map for initial + * hash: hash func for calc key's hash + * cmp : cmp func for cmp two keys + */ +void ngx_map_init(ngx_map_t *map, ngx_map_hash_pt hash, ngx_cmp_pt cmp); + +/* + * return value: + * if map is empty return 1, else return 0 + */ +#define ngx_map_empty(map) (map->rbtree.root == map->rbtree.sentinel) + +/* + * return value: + * the mininum key map node, if map is empty, return NULL + * paras: + * map: map for operate + */ +ngx_map_node_t *ngx_map_begin(ngx_map_t *map); + +/* + * return value: + * the maximum key map node, if map is empty, return NULL + * paras: + * map: map for operate + */ +ngx_map_node_t *ngx_map_rbegin(ngx_map_t *map); + +/* + * return value: + * the next bigger key map node, if none, return NULL + * paras: + * n : current node + */ +ngx_map_node_t *ngx_map_next(ngx_map_node_t *n); + +/* + * return value: + * the next smaller key map node, if none, return NULL + * paras: + * n : current node + */ +ngx_map_node_t *ngx_map_prev(ngx_map_node_t *n); + +/* + * return value: + * None + * paras: + * map : map for operate + * node : map node for inserting into map + * covered: 1 for covered if key is same, 0 do nothing if key is same + */ +void ngx_map_insert(ngx_map_t *map, ngx_map_node_t *node, ngx_flag_t covered); + +/* + * return value: + * None + * paras: + * map: map for operate + * key: map node key for deleting from map + */ +void ngx_map_delete(ngx_map_t *map, intptr_t key); + +/* + * return value: + * node in map searching by key, NULL for not found + * paras: + * map: map for operate + * key: node key for searching + */ +ngx_map_node_t *ngx_map_find(ngx_map_t *map, intptr_t key); + + +#endif diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_poold.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_poold.c new file mode 100644 index 0000000000..bab46342b4 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_poold.c @@ -0,0 +1,190 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#include "ngx_poold.h" +#include "ngx_map.h" + + +typedef struct ngx_poold_node_s ngx_poold_node_t; + +static ngx_pool_t *ngx_poold_pool; + +static ngx_map_t ngx_poold_map; +static ngx_poold_node_t *ngx_poold_free_node; + +static ngx_uint_t ngx_poold_nalloc; +static ngx_uint_t ngx_poold_nfree; + + +struct ngx_poold_node_s { + ngx_map_node_t m; /* map node */ + ngx_poold_node_t *next; /* free node */ + + ngx_pool_t *pool; + char *file; /* file create pool */ + int line; /* line create pool */ +}; + + +static ngx_int_t +ngx_poold_init() +{ + ngx_poold_pool = ngx_create_pool(4096, ngx_cycle->log); + if (ngx_poold_pool == NULL) { + return NGX_ERROR; + } + + ngx_map_init(&ngx_poold_map, ngx_map_hash_uint, ngx_cmp_uint); + ngx_poold_free_node = NULL; + + ngx_poold_nalloc = 0; + ngx_poold_nfree = 0; + + return NGX_OK; +} + + +static ngx_poold_node_t * +ngx_poold_get_node() +{ + ngx_poold_node_t *n; + + n = ngx_poold_free_node; + if (n == NULL) { + n = ngx_pcalloc(ngx_poold_pool, sizeof(ngx_poold_node_t)); + if (n == NULL) { + return NULL; + } + + ++ngx_poold_nalloc; + } else { + ngx_poold_free_node = n->next; + ngx_memzero(n, sizeof(ngx_poold_node_t)); + + --ngx_poold_nfree; + } + + return n; +} + + +static void +ngx_poold_put_node(ngx_poold_node_t *node) +{ + if (ngx_poold_pool == NULL) { + return; + } + + if (node == NULL) { + return; + } + + node->next = ngx_poold_free_node; + ngx_poold_free_node = node; + + ++ngx_poold_nfree; +} + + +ngx_pool_t * +ngx_create_pool_debug(size_t size, ngx_log_t *log, char *file, int line) +{ + ngx_poold_node_t *node; + + if (ngx_poold_pool == NULL) { + ngx_poold_init(); + } + + /* construct a poold node */ + node = ngx_poold_get_node(); + node->pool = ngx_create_pool(size, log); + node->file = file; + node->line = line; + + /* record node in poold map */ + node->m.raw_key = (intptr_t) node->pool; + ngx_map_insert(&ngx_poold_map, &node->m, 0); + + return node->pool; +} + + +void +ngx_destroy_pool_debug(ngx_pool_t *pool, char *file, int line) +{ + ngx_poold_node_t *node; + ngx_map_node_t *m; + + /* get node by pool */ + m = ngx_map_find(&ngx_poold_map, (intptr_t) pool); + if (m == NULL) { + ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, 0, + "destroy pool twice: %s:%d", file, line); + return; + } + ngx_map_delete(&ngx_poold_map, (intptr_t) pool); + node = (ngx_poold_node_t *) ((char *) m - offsetof(ngx_poold_node_t, m)); + + ngx_destroy_pool(pool); + + /* put node in poold map */ + ngx_poold_put_node(node); +} + + +ngx_chain_t * +ngx_poold_state(ngx_http_request_t *r, unsigned detail) +{ + ngx_chain_t *cl; + ngx_buf_t *b; + ngx_map_node_t *node; + ngx_poold_node_t *pn; + size_t len, len1; + ngx_uint_t n; + + len = sizeof("##########ngx debug pool##########\n") - 1 + + sizeof("ngx_poold nalloc node: \n") - 1 + NGX_OFF_T_LEN + + sizeof("ngx_poold nfree node: \n") - 1 + NGX_OFF_T_LEN; + + len1 = 0; + + /* node for create pool */ + if (detail) { + n = ngx_poold_nalloc - ngx_poold_nfree; + /* " file:line\n" */ + len1 = 4 + 256 + 1 + NGX_OFF_T_LEN + 1; + len += len1 * n; + } + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NULL; + } + cl->next = NULL; + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NULL; + } + cl->buf = b; + + b->last = ngx_snprintf(b->last, len, + "##########ngx debug pool##########\n" + "ngx_poold nalloc node: %ui\nngx_poold nfree node: %ui\n", + ngx_poold_nalloc, ngx_poold_nfree); + + if (detail) { + for (node = ngx_map_begin(&ngx_poold_map); node; + node = ngx_map_next(node)) + { + /* m is first element of ngx_poold_node_t */ + pn = (ngx_poold_node_t *) node; + b->last = ngx_snprintf(b->last, len1, " %s:%d\n", + pn->file, pn->line); + } + } + + return cl; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_poold.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_poold.h new file mode 100644 index 0000000000..588283901c --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_poold.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#ifndef _NGX_POOL_DEBUG_H_INCLUDED_ +#define _NGX_POOL_DEBUG_H_INCLUDED_ + + +#include +#include +#include + + +#define NGX_CREATE_POOL(size, log) \ + ngx_create_pool_debug(size, log, __FILE__, __LINE__) + +#define NGX_DESTROY_POOL(pool) \ + ngx_destroy_pool_debug(pool, __FILE__, __LINE__) + + +/* + * create pool and record where to create it for debugging + * such as pool not destroy + * + * paras: + * size: pool size for pool creating interface in raw ngx_create_pool + * log: log for pool creating interface in raw ngx_create_pool + * file: use __FILE__ for recording file + * line: use __LINE__ for recording line + * return: + * pool for successd, NULL for failed + */ +ngx_pool_t *ngx_create_pool_debug(size_t size, ngx_log_t *log, + char *file, int line); + + +/* + * destroy pool for debugging, use as pair for ngx_create_pool_debug + * + * paras: + * pool: pool for pool destroying interface in ran ngx_destroy_pool + * file: use __FILE__ for recording file + * line: use __LINE__ for recording line + */ +void ngx_destroy_pool_debug(ngx_pool_t *pool, char *file, int line); + + +/* + * paras: + * r: http request to query status of rbuf + */ +ngx_chain_t *ngx_poold_state(ngx_http_request_t *r, unsigned detail); + + +#endif diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_rbuf.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_rbuf.c new file mode 100644 index 0000000000..6b80bd59c4 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_rbuf.c @@ -0,0 +1,292 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#include +#include +#include +#include "ngx_map.h" + + +static ngx_pool_t *ngx_rbuf_pool; + +static ngx_map_t ngx_rbuf_map; +static ngx_chain_t *ngx_rbuf_free_chain; + +static ngx_uint_t ngx_rbuf_nalloc_node; + +static ngx_uint_t ngx_rbuf_nalloc_buf; +static ngx_uint_t ngx_rbuf_nfree_buf; + +static ngx_uint_t ngx_rbuf_nalloc_chain; +static ngx_uint_t ngx_rbuf_nfree_chain; + +static ngx_map_t ngx_rbuf_using; + +#define ngx_rbuf_buf(b) \ + (ngx_rbuf_t *) ((u_char *) (b) - offsetof(ngx_rbuf_t, buf)) + +typedef struct ngx_rbuf_s ngx_rbuf_t; + +struct ngx_rbuf_s { + size_t size; + ngx_rbuf_t *next; + u_char buf[]; +}; + +typedef struct { + ngx_map_node_t node; + ngx_rbuf_t *rbuf; +} ngx_rbuf_node_t; + +typedef struct { + ngx_chain_t cl; + ngx_buf_t buf; + unsigned alloc; + + ngx_map_node_t node; + char *file; + int line; +} ngx_chainbuf_t; + + +static ngx_int_t +ngx_rbuf_init() +{ + ngx_rbuf_pool = ngx_create_pool(4096, ngx_cycle->log); + if (ngx_rbuf_pool == NULL) { + return NGX_ERROR; + } + + ngx_map_init(&ngx_rbuf_map, ngx_map_hash_uint, ngx_cmp_uint); + ngx_map_init(&ngx_rbuf_using, ngx_map_hash_uint, ngx_cmp_uint); + + ngx_rbuf_nalloc_node = 0; + ngx_rbuf_nalloc_buf = 0; + ngx_rbuf_nfree_buf = 0; + ngx_rbuf_nalloc_chain = 0; + ngx_rbuf_nfree_chain = 0; + + return NGX_OK; +} + + +static ngx_rbuf_t * +ngx_rbuf_get_buf(size_t key) +{ + ngx_rbuf_node_t *rn; + ngx_map_node_t *node; + ngx_rbuf_t *rb; + + node = ngx_map_find(&ngx_rbuf_map, key); + if (node == NULL) { /* new key */ + rn = ngx_pcalloc(ngx_rbuf_pool, sizeof(ngx_rbuf_node_t)); + if (rn == NULL) { + return NULL; + } + + node = &rn->node; + node->raw_key = key; + ngx_map_insert(&ngx_rbuf_map, node, 0); + + ++ngx_rbuf_nalloc_node; + } else { + rn = (ngx_rbuf_node_t *) node; + } + + rb = rn->rbuf; + if (rb == NULL) { + rb = ngx_pcalloc(ngx_rbuf_pool, sizeof(ngx_rbuf_t) + key); + if (rb == NULL) { + return NULL; + } + rb->size = key; + + ++ngx_rbuf_nalloc_buf; + } else { + rn->rbuf = rb->next; + rb->next = NULL; + + --ngx_rbuf_nfree_buf; + } + + return rb; +} + +static void +ngx_rbuf_put_buf(ngx_rbuf_t *rb) +{ + ngx_rbuf_node_t *rn; + ngx_map_node_t *node; + + node = ngx_map_find(&ngx_rbuf_map, rb->size); + if (node == NULL) { + return; + } + + rn = (ngx_rbuf_node_t *) node; + rb->next = rn->rbuf; + rn->rbuf = rb; + + ++ngx_rbuf_nfree_buf; +} + + +static u_char * +ngx_rbuf_alloc(size_t size) +{ + ngx_rbuf_t *rb; + + rb = ngx_rbuf_get_buf(size); + + return rb->buf; +} + +static void +ngx_rbuf_free(u_char *rb) +{ + ngx_rbuf_t *rbuf; + + rbuf = ngx_rbuf_buf(rb); + ngx_rbuf_put_buf(rbuf); +} + + +ngx_chain_t * +ngx_get_chainbuf_debug(size_t size, ngx_flag_t alloc_rbuf, char *file, int line) +{ + ngx_chainbuf_t *cb; + ngx_chain_t *cl; + + if (ngx_rbuf_pool == NULL) { + ngx_rbuf_init(); + } + + cl = ngx_rbuf_free_chain; + if (cl) { + ngx_rbuf_free_chain = cl->next; + cl->next = NULL; + cb = (ngx_chainbuf_t *) cl; + + --ngx_rbuf_nfree_chain; + } else { + cb = ngx_pcalloc(ngx_rbuf_pool, sizeof(ngx_chainbuf_t)); + if (cb == NULL) { + return NULL; + } + + cl = &cb->cl; + cl->buf = &cb->buf; + + ++ngx_rbuf_nalloc_chain; + } + + if (alloc_rbuf) { + cl->buf->last = cl->buf->pos = cl->buf->start = ngx_rbuf_alloc(size); + cl->buf->end = cl->buf->start + size; + cb->alloc = 1; + } else { + cl->buf->pos = cl->buf->last = cl->buf->start = cl->buf->end = NULL; + cb->alloc = 0; + } + cl->buf->memory = 1; + + // record chainbuf in using map + cb->file = file; + cb->line = line; + cb->node.raw_key = (intptr_t) cl; + ngx_map_insert(&ngx_rbuf_using, &cb->node, 0); + + return cl; +} + +void +ngx_put_chainbuf_debug(ngx_chain_t *cl, char *file, int line) +{ + ngx_chainbuf_t *cb; + + if (ngx_rbuf_pool == NULL) { + return; + } + + if (cl == NULL) { + return; + } + + cb = (ngx_chainbuf_t *) cl; + + if (cb->alloc) { + ngx_rbuf_free(cl->buf->start); + } + cl->next = ngx_rbuf_free_chain; + ngx_rbuf_free_chain = cl; + ++ngx_rbuf_nfree_chain; + + // delete chainbuf from using map + if (ngx_map_find(&ngx_rbuf_using, (intptr_t) cl) == NULL) { + ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, 0, + "destroy chainbuf twice: %s:%d", file, line); + return; + } + ngx_map_delete(&ngx_rbuf_using, (intptr_t) cl); +} + +ngx_chain_t * +ngx_rbuf_state(ngx_http_request_t *r, unsigned detail) +{ + ngx_chain_t *cl; + ngx_buf_t *b; + size_t len, len1; + ngx_uint_t n; + ngx_chainbuf_t *cb; + ngx_map_node_t *node; + + len = sizeof("##########ngx rbuf state##########\n") - 1 + + sizeof("ngx_rbuf nalloc node: \n") - 1 + NGX_OFF_T_LEN + + sizeof("ngx_rbuf nalloc buf: \n") - 1 + NGX_OFF_T_LEN + + sizeof("ngx_rbuf nfree buf: \n") - 1 + NGX_OFF_T_LEN + + sizeof("ngx_rbuf nalloc chain: \n") - 1 + NGX_OFF_T_LEN + + sizeof("ngx_rbuf nfree chain: \n") - 1 + NGX_OFF_T_LEN; + + len1 = 0; + + if (detail) { + n = ngx_rbuf_nalloc_chain - ngx_rbuf_nfree_chain; + /* " file:line\n" */ + len1 = 4 + 256 + 1 + NGX_OFF_T_LEN + 1; + len += len1 * n; + } + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NULL; + } + cl->next = NULL; + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NULL; + } + cl->buf = b; + + b->last = ngx_snprintf(b->last, len, + "##########ngx rbuf state##########\nngx_rbuf nalloc node: %ui\n" + "ngx_rbuf nalloc buf: %ui\nngx_rbuf nfree buf: %ui\n" + "ngx_rbuf nalloc chain: %ui\nngx_rbuf nfree chain: %ui\n", + ngx_rbuf_nalloc_node, ngx_rbuf_nalloc_buf, ngx_rbuf_nfree_buf, + ngx_rbuf_nalloc_chain, ngx_rbuf_nfree_chain); + + if (detail) { + for (node = ngx_map_begin(&ngx_rbuf_using); node; + node = ngx_map_next(node)) + { + cb = (ngx_chainbuf_t *) ((char *) node + - offsetof(ngx_chainbuf_t, node)); + b->last = ngx_snprintf(b->last, len1, " %s:%d\n", + cb->file, cb->line); + } + } + + return cl; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_rbuf.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_rbuf.h new file mode 100644 index 0000000000..7002d55905 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_rbuf.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#ifndef _NGX_RBUF_H_INCLUDED_ +#define _NGX_RBUF_H_INCLUDED_ + + +#include +#include +#include + + +ngx_chain_t *ngx_get_chainbuf_debug(size_t size, ngx_flag_t alloc_rbuf, + char *file, int line); +void ngx_put_chainbuf_debug(ngx_chain_t *cl, char *file, int len); + +/* + * paras: + * size: buffer size for allocate + * alloc_rbuf: whether alloc rbuf + * return: + * nginx chain + */ +#define ngx_get_chainbuf(size, alloc_rbuf) \ + ngx_get_chainbuf_debug(size, alloc_rbuf, __FILE__, __LINE__); + +/* + * paras: + * cl: nginx chain return by ngx_rtmp_shared_get_chainbuf + */ +#define ngx_put_chainbuf(cl) \ + ngx_put_chainbuf_debug(cl, __FILE__, __LINE__); + + +/* + * paras: + * cl: nginx chain return by ngx_rtmp_shared_get_chainbuf + */ +#define ngx_put_chainbufs(cl) \ +{ \ + ngx_chain_t *l; \ + \ + l = cl; \ + while (l) { \ + cl = l->next; \ + ngx_put_chainbuf_debug(l, __FILE__, __LINE__); \ + l = cl; \ + } \ +} + +/* + * paras: + * r: http request to query status of rbuf + */ +ngx_chain_t *ngx_rbuf_state(ngx_http_request_t *r, unsigned detail); + +#endif diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_timerd.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_timerd.c new file mode 100644 index 0000000000..a28109bca7 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_timerd.c @@ -0,0 +1,269 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#include "ngx_timerd.h" +#include "ngx_map.h" + + +typedef struct ngx_timerd_node_s ngx_timerd_node_t; + +static ngx_pool_t *ngx_timerd_pool; + +static ngx_map_t ngx_timerd_map; +static ngx_timerd_node_t *ngx_timerd_free_node; + +static ngx_uint_t timerd_footprint; +static ngx_uint_t ngx_timerd_nalloc; +static ngx_uint_t ngx_timerd_nfree; + + +struct ngx_timerd_node_s { + ngx_map_node_t m; /* map node */ + ngx_timerd_node_t *next; /* free node */ + + ngx_uint_t footprint; + off_t fpoff; + ngx_event_t ev; + + ngx_event_t *uev; + char *file; /* file create pool */ + int line; /* line create pool */ +}; + + +static ngx_int_t +ngx_timerd_init() +{ + ngx_timerd_pool = ngx_create_pool(4096, ngx_cycle->log); + if (ngx_timerd_pool == NULL) { + return NGX_ERROR; + } + + ngx_map_init(&ngx_timerd_map, ngx_map_hash_uint, ngx_cmp_uint); + ngx_timerd_free_node = NULL; + + ngx_timerd_nalloc = 0; + ngx_timerd_nfree = 0; + + return NGX_OK; +} + + +static ngx_timerd_node_t * +ngx_timerd_get_node() +{ + ngx_timerd_node_t *n; + + n = ngx_timerd_free_node; + if (n == NULL) { + n = ngx_pcalloc(ngx_timerd_pool, sizeof(ngx_timerd_node_t)); + if (n == NULL) { + return NULL; + } + + ++ngx_timerd_nalloc; + } else { + ngx_timerd_free_node = n->next; + ngx_memzero(n, sizeof(ngx_timerd_node_t)); + + --ngx_timerd_nfree; + } + + return n; +} + + +static void +ngx_timerd_put_node(ngx_timerd_node_t *node) +{ + if (ngx_timerd_pool == NULL) { + return; + } + + if (node == NULL) { + return; + } + + node->next = ngx_timerd_free_node; + ngx_timerd_free_node = node; + + ++ngx_timerd_nfree; +} + + +static void +ngx_timerd_wrap(ngx_event_t *ev) +{ + ngx_timerd_node_t *node; + ngx_event_t *uev; + ngx_uint_t *fp; + + node = ev->data; + uev = node->uev; + + fp = (ngx_uint_t *) ((char *) (uev->data) + node->fpoff); + if (*fp != node->footprint) { + ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, 0, + "timer wrap, timer trigger but not timer owner: %s:%d", + node->file, node->line); + return; + } + + ngx_map_delete(&ngx_timerd_map, (intptr_t) uev); + + uev->timer_set = 0; + uev->timedout = 1; + + uev->handler(uev); + + ngx_timerd_put_node(node); +} + + +ngx_uint_t +ngx_timerd_footprint() +{ + return timerd_footprint++; +} + + +void +ngx_add_timer_debug(ngx_event_t *ev, ngx_msec_t timer, off_t fpoff, + char *file, int line) +{ + ngx_timerd_node_t *node; + ngx_map_node_t *m; + ngx_uint_t *fp; + + if (ngx_timerd_pool == NULL) { + ngx_timerd_init(); + } + + fp = (ngx_uint_t *) ((char *) ev->data + fpoff); + + m = ngx_map_find(&ngx_timerd_map, (intptr_t) ev); + if (m == NULL) { // first add + node = ngx_timerd_get_node(); + + node->m.raw_key = (intptr_t) ev; + ngx_map_insert(&ngx_timerd_map, &node->m, 0); + + node->footprint = *fp; + node->fpoff = fpoff; + + node->ev.log = ngx_cycle->log; + node->ev.data = node; + node->ev.handler = ngx_timerd_wrap; + + node->uev = ev; + node->file = file; + node->line = line; + } else { + node = (ngx_timerd_node_t *) ((char *) m - + offsetof(ngx_timerd_node_t, m)); + if (node->footprint != *fp) { + ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, 0, + "add timer but not timer owner(%s:%d): %s:%d", + node->file, node->line, file, line); + return; + } + } + + ev->timer_set = 1; + ngx_add_timer(&node->ev, timer); +} + + +void +ngx_del_timer_debug(ngx_event_t *ev, ngx_uint_t footprint, char *file, int line) +{ + ngx_timerd_node_t *node; + ngx_map_node_t *m; + + /* get node by pool */ + m = ngx_map_find(&ngx_timerd_map, (intptr_t) ev); + if (m == NULL) { + ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, 0, + "delete timer twice: %s:%d", file, line); + return; + } + + node = (ngx_timerd_node_t *) ((char *) m - offsetof(ngx_timerd_node_t, m)); + if (node->footprint != footprint) { + ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, 0, + "delete timer but not timer owner: %s:%d", file, line); + return; + } + + ngx_map_delete(&ngx_timerd_map, (intptr_t) ev); + + if (node->ev.timer_set) { + ev->timer_set = 0; + ngx_del_timer(&node->ev); + } + + if (node->ev.posted) { + ngx_delete_posted_event(&node->ev); + } + + /* put node in timerd map */ + ngx_timerd_put_node(node); +} + + +ngx_chain_t * +ngx_timerd_state(ngx_http_request_t *r, unsigned detail) +{ + ngx_chain_t *cl; + ngx_buf_t *b; + ngx_map_node_t *node; + ngx_timerd_node_t *pn; + size_t len, len1; + ngx_uint_t n; + + len = sizeof("##########ngx debug pool##########\n") - 1 + + sizeof("ngx_timerd nalloc node: \n") - 1 + NGX_OFF_T_LEN + + sizeof("ngx_timerd nfree node: \n") - 1 + NGX_OFF_T_LEN; + + len1 = 0; + + /* node for create pool */ + if (detail) { + n = ngx_timerd_nalloc - ngx_timerd_nfree; + /* " file:line\n" */ + len1 = 4 + 256 + 1 + NGX_OFF_T_LEN + 1; + len += len1 * n; + } + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NULL; + } + cl->next = NULL; + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NULL; + } + cl->buf = b; + + b->last = ngx_snprintf(b->last, len, + "##########ngx debug pool##########\n" + "ngx_timerd nalloc node: %ui\nngx_timerd nfree node: %ui\n", + ngx_timerd_nalloc, ngx_timerd_nfree); + + if (detail) { + for (node = ngx_map_begin(&ngx_timerd_map); node; + node = ngx_map_next(node)) + { + /* m is first element of ngx_timerd_node_t */ + pn = (ngx_timerd_node_t *) node; + b->last = ngx_snprintf(b->last, len1, " %s:%d\n", + pn->file, pn->line); + } + } + + return cl; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_timerd.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_timerd.h new file mode 100644 index 0000000000..7c64d4267d --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_timerd.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#ifndef _NGX_TIMER_DEBUG_H_INCLUDED_ +#define _NGX_TIMER_DEBUG_H_INCLUDED_ + + +#include +#include +#include + + +#define NGX_ADD_TIMER(ev, timer, fpoff) \ + ngx_add_timer_debug(ev, timer, fpoff, __FILE__, __LINE__) + +#define NGX_DEL_TIMER(ev, footprint) \ + ngx_del_timer_debug(ev, footprint, __FILE__, __LINE__) + + +/* + * generate a new footprint + * + * return: + * footprint + */ +ngx_uint_t ngx_timerd_footprint(); + + +/* + * add timer and record where to add it for debugging + * such as timer not destroy + * + * paras: + * ev: nginx event for timer + * timer: timer interval for triggering timer + * fpoff: footprint offset in event data + * file: use __FILE__ for recording file + * line: use __LINE__ for recording line + * return: + * pool for successd, NULL for failed + */ +void ngx_add_timer_debug(ngx_event_t *ev, ngx_msec_t timer, off_t fpoff, + char *file, int line); + + +/* + * destroy pool for debugging, use as pair for ngx_create_pool_debug + * + * paras: + * ev: nginx event for timer + * footprint: footprint value in user event data + * file: use __FILE__ for recording file + * line: use __LINE__ for recording line + */ +void ngx_del_timer_debug(ngx_event_t *ev, ngx_uint_t footprint, + char *file, int line); + + +/* + * paras: + * r: http request to query status of rbuf + */ +ngx_chain_t *ngx_timerd_state(ngx_http_request_t *r, unsigned detail); + + +#endif diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_toolkit_misc.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_toolkit_misc.c new file mode 100644 index 0000000000..804546075f --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_toolkit_misc.c @@ -0,0 +1,383 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#include "ngx_toolkit_misc.h" +#include + + +typedef struct { + ngx_str_t scheme; + in_port_t port; +} ngx_scheme_port_t; + + +ngx_scheme_port_t ngx_sheme_port[] = { + { ngx_string("http"), 80 }, + { ngx_string("https"), 443 }, + { ngx_string("rtmp"), 1935 }, + { ngx_null_string, 0 } +}; + + +#define FILEBUFSIZE 8192 + + +socklen_t +ngx_sock_pton_unix(struct sockaddr *sa, u_char *text, size_t len) +{ +#if NGX_HAVE_UNIX_DOMAIN + u_char *path; + struct sockaddr_un *saun; + + // skip 'unix:' + path = text + 5; + len -= 5; + + if (len == 0) { + ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, 0, + "no path in the unix domain"); + return 0; + } + + ++len; // sun_path need '\0' as end + if (len > sizeof(saun->sun_path)) { + ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, 0, + "too long path in the unix domain socket"); + return 0; + } + + saun = (struct sockaddr_un *) sa; + saun->sun_family = AF_UNIX; + (void) ngx_cpystrn((u_char *) saun->sun_path, path, len); + + return sizeof(struct sockaddr_un); + +#else + ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, 0, + "the unix domain sockets are not supported on this platform"); + + return 0; +#endif +} + +socklen_t +ngx_sock_pton_inet6(struct sockaddr *sa, u_char *text, size_t len) +{ +#if NGX_HAVE_INET6 + struct in6_addr inaddr6; + struct sockaddr_in6 *sin6; + u_char *p, *last; + ngx_int_t port; + size_t alen, plen; + + last = text + len; + p = ngx_strlchr(text, last, ']'); + + if (p == NULL || p == last - 1) { + ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, 0, + "not a ipv6 address format"); + return 0; + } + + ++text; + alen = p - text; + + /* + * prevent MSVC8 warning: + * potentially uninitialized local variable 'inaddr6' used + */ + ngx_memzero(&inaddr6, sizeof(struct in6_addr)); + + if (ngx_inet6_addr(text, alen, inaddr6.s6_addr) != NGX_OK) { + // not ipv6 address + return 0; + } + + sa->sa_family = AF_INET6; + + sin6 = (struct sockaddr_in6 *) sa; + ngx_memcpy(sin6->sin6_addr.s6_addr, inaddr6.s6_addr, 16); + + ++p; + if (*p == ':') { // has port + ++p; + plen = last - p; + + port = ngx_atoi(p, plen); + if (port < 0 || port > 65535) { + ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, 0, + "invalid port: %i", port); + return 0; + } + + ngx_inet_set_port(sa, (in_port_t) port); + } + + return sizeof(struct sockaddr_in6); + +#else + ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, 0, + "the INET6 sockets are not supported on this platform"); + + return 0; +#endif +} + +socklen_t +ngx_sock_pton_inet(struct sockaddr *sa, u_char *text, size_t len) +{ + in_addr_t inaddr; + struct sockaddr_in *sin; + u_char *p, *last; + ngx_int_t port; + size_t alen, plen; + + last = text + len; + p = ngx_strlchr(text, last, ':'); + alen = len; + + if (p != NULL) { // have port + alen = p - text; + } + + inaddr = ngx_inet_addr(text, alen); + if (inaddr == INADDR_NONE) { + // not ipv4 address + return 0; + } + + sa->sa_family = AF_INET; + + sin = (struct sockaddr_in *) sa; + sin->sin_addr.s_addr = inaddr; + + if (p != NULL) { // has port + ++p; + plen = last - p; + + port = ngx_atoi(p, plen); + if (port < 0 || port > 65535) { + ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, 0, + "invalid port: %i", port); + return 0; + } + + ngx_inet_set_port(sa, (in_port_t) port); + } + + return sizeof(struct sockaddr_in); +} + +ngx_int_t +ngx_parse_request_url(ngx_request_url_t *request_url, ngx_str_t *url) +{ + u_char *last, *host_last; + u_char *scheme; + u_char *user, *host, *port; + u_char *path, *args, *fragment; + + /* NULL url */ + if (url->len == 0) { + return NGX_ERROR; + } + + last = url->data + url->len; + + scheme = url->data; + + user = (u_char *) ngx_strnstr(scheme, "://", last - scheme); + + if (user == NULL) { + return NGX_ERROR; + } + + request_url->scheme.data = scheme; + request_url->scheme.len = user - scheme; + + user += 3; + if (user >= last) { + return NGX_ERROR; + } + + host_last = ngx_strlchr(user, last, '/'); + if (host_last == NULL) { + host_last = last; + } + + host = ngx_strlchr(user, host_last, '@'); + if (host == NULL) { /* no user */ + host = user; + } else { + if (user == host) { /* user is "" */ + return NGX_ERROR; + } + + request_url->user.data = user; + request_url->user.len = host - user; + ++host; + if (host == host_last) { /* no host */ + return NGX_ERROR; + } + } + + port = ngx_strlchr(host, host_last, ':'); + if (port == NULL) { /* no port */ + request_url->host.data = host; + request_url->host.len = host_last - host; + + request_url->host_with_port = request_url->host; + } else { + request_url->host.data = host; + request_url->host.len = port - host; + ++port; + if (port == host_last) { /* port error */ + return NGX_ERROR; + } + + request_url->port.data = port; + request_url->port.len = host_last - port; + + request_url->host_with_port.data = host; + request_url->host_with_port.len = host_last - host; + } + + path = ++host_last; + if (path >= last) { /* no path */ + goto done; + } + + args = ngx_strlchr(path, last, '?'); + if (args == NULL) { /* no args */ + request_url->path.data = path; + request_url->path.len = last - path; + + request_url->uri_with_args = request_url->path; + + goto done; + } else { + request_url->path.data = path; + request_url->path.len = args - path; + ++args; + if (args == last) { /* args error */ + return NGX_ERROR; + } + } + + fragment = ngx_strlchr(args, last, '#'); + if (fragment == NULL) { /* no fragment */ + request_url->args.data = args; + request_url->args.len = last - args; + } else { + request_url->args.data = args; + request_url->args.len = fragment - args; + ++fragment; + if (fragment == last) { /* fragment error */ + return NGX_ERROR; + } + + request_url->fragment.data = fragment; + request_url->fragment.len = last - fragment; + } + + request_url->uri_with_args.data = path; + request_url->uri_with_args.len = last - path; + +done: + return NGX_OK; +} + +in_port_t +ngx_request_port(ngx_str_t *scheme, ngx_str_t *port) +{ + ngx_int_t p; + ngx_scheme_port_t *sp; + + if (port->len) { + p = ngx_atoi(port->data, port->len); + if (p < 1 || p > 65535) { /* invalid port */ + return 0; + } + + return p; + } + + for (sp = ngx_sheme_port; sp->port != 0; ++sp) { + if (sp->scheme.len == scheme->len + && ngx_memcmp(sp->scheme.data, scheme->data, scheme->len) == 0) + { + return sp->port; + } + } + + return 0; +} + +ngx_int_t +ngx_md5_file(ngx_fd_t fd, u_char md5key[NGX_MD5KEY_LEN]) +{ + ngx_md5_t ctx; + u_char buf[FILEBUFSIZE]; + u_char md5[16]; + ssize_t n; + ngx_uint_t i; + u_char *p; + + ngx_md5_init(&ctx); + + for (;;) { + n = ngx_read_fd(fd, buf, FILEBUFSIZE); + + if (n == 0) { /* read eof of file */ + break; + } + + if (n == NGX_FILE_ERROR) { + return NGX_ERROR; + } + + ngx_md5_update(&ctx, buf, n); + } + + ngx_md5_final(md5, &ctx); + + p = md5key; + for (i = 0; i < 16; ++i) { + p = ngx_sprintf(p, "%02xi", md5[i]); + } + + return NGX_OK; +} + +ngx_int_t +ngx_copy_str(ngx_pool_t *pool, ngx_str_t *dst, ngx_str_t *src) +{ + if (src->len == 0) { + return NGX_OK; + } + + dst->len = src->len; + dst->data = ngx_palloc(pool, src->len); + if (dst->data == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(dst->data, src->data, src->len); + + return NGX_OK; +} + +socklen_t +ngx_sock_pton(struct sockaddr *sa, u_char *text, size_t len) +{ + if (len >= 5 && ngx_strncasecmp(text, (u_char *) "unix:", 5) == 0) { + return ngx_sock_pton_unix(sa, text, len); + } + + if (len && text[0] == '[') { + return ngx_sock_pton_inet6(sa, text, len); + } + + return ngx_sock_pton_inet(sa, text, len); +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_toolkit_misc.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_toolkit_misc.h new file mode 100644 index 0000000000..eefeb7704d --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/ngx_toolkit_misc.h @@ -0,0 +1,126 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#ifndef _NGX_TOOLKIT_MISC_H_INCLUDED_ +#define _NGX_TOOLKIT_MISC_H_INCLUDED_ + + +#include +#include + + +/* + * scheme://[user@]host[:port]/path[?args][#fragment] + */ +typedef struct { + ngx_str_t scheme; + ngx_str_t user; + ngx_str_t host; + ngx_str_t port; + ngx_str_t path; + ngx_str_t args; + ngx_str_t fragment; + + ngx_str_t host_with_port; /* host[:port] */ + ngx_str_t uri_with_args; /* /path[?args][#fragment] */ +} ngx_request_url_t; + + +#define NGX_MD5KEY_LEN 32 + + +/* + * parse request url format as: + * scheme://[user@]host[:port]/path[?args][#fragment] + * + * return value: + * NGX_OK : parse success + * NGX_ERROR: request url format error + * paras: + * request_url: url parse result return to user, + * all paras in request url is segment point to url + * url : request url for parse + */ +ngx_int_t ngx_parse_request_url(ngx_request_url_t *request_url, ngx_str_t *url); + +/* + * convert port to in_port_t according to scheme and port + * + * return value: + * If port is set to correct number range in [1, 65535], return port + * If port is set to non correct value, return 0 + * If port is not set, return default value for scheme: + * 80 for http + * 443 for https + * 1935 for rtmp + * 0 for others now + * values: + * scheme : sheme string like http https or rtmp + * port : port for convert to in_port_t + */ +in_port_t ngx_request_port(ngx_str_t *scheme, ngx_str_t *port); + +/* + * calculating file md5key as md5sum in shell + * + * return value: + * NGX_OK : calculating success + * NGX_ERROR: calculating error such as file is not exist + * paras: + * fd : file desc for calculating md5key + * md5key: md5key result + */ +ngx_int_t ngx_md5_file(ngx_fd_t fd, u_char md5key[NGX_MD5KEY_LEN]); + + +/* + * copy str + * + * return value: + * NGX_OK: copy successd + * NGX_ERROR: copy failed + * paras: + * pool: for alloc space for storing str + * dst: str copy to + * src: str copy from + */ +ngx_int_t ngx_copy_str(ngx_pool_t *pool, ngx_str_t *dst, ngx_str_t *src); + + +/* + * parse text to struct sockaddr + * + * return value: + * 0: if text is not ipv4/ipv6/unix address + * >0: socklen for sa + * + * paras: + * sa: struct sockadd parse to + * text: ipv4/ipv6/unix address for parsing, text could have port + * len: text's length + */ +socklen_t ngx_sock_pton(struct sockaddr *sa, u_char *text, size_t len); + + +#ifdef NGX_DEBUG + +#define NGX_START_TIMING \ + struct timeval __start, __end; \ + ngx_gettimeofday(&__start); + +#define NGX_STOP_TIMING(log, msg) \ + ngx_gettimeofday(&__end); \ + ngx_log_error(NGX_LOG_INFO, log, 0, msg " spend %ui us", \ + (__end.tv_sec - __start.tv_sec) * 1000000 \ + + (__end.tv_usec - __start.tv_usec)); + +#else + +#define NGX_START_TIMING +#define NGX_STOP_TIMING(log, msg) + +#endif + +#endif diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/config b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/config new file mode 100644 index 0000000000..23541ea221 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/config @@ -0,0 +1,32 @@ +ngx_addon_name=ngx_toolkit_test_module + + +CORE_MODULES="$CORE_MODULES + ngx_dynamic_core_test_module \ + " + +HTTP_MODULES="$HTTP_MODULES \ + ngx_event_timer_test_module \ + ngx_event_resolver_test_module \ + ngx_dynamic_resolver_test_module \ + ngx_dynamic_conf_test_module \ + ngx_http_dynamic_test_module \ + ngx_toolkit_misc_test_module \ + ngx_map_test_module \ + ngx_rbuf_test_module \ + ngx_poold_test_module \ + ngx_timerd_test_module \ + " + +NGX_ADDON_SRCS="$NGX_ADDON_SRCS \ + $ngx_addon_dir/ngx_event_timer_test_module.c \ + $ngx_addon_dir/ngx_event_resolver_test_module.c \ + $ngx_addon_dir/ngx_dynamic_resolver_test_module.c \ + $ngx_addon_dir/ngx_dynamic_conf_test_module.c \ + $ngx_addon_dir/ngx_http_dynamic_test_module.c \ + $ngx_addon_dir/ngx_toolkit_misc_test_module.c \ + $ngx_addon_dir/ngx_map_test_module.c \ + $ngx_addon_dir/ngx_rbuf_test_module.c \ + $ngx_addon_dir/ngx_poold_test_module.c \ + $ngx_addon_dir/ngx_timerd_test_module.c \ + " diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/dns_install.sh b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/dns_install.sh new file mode 100644 index 0000000000..5ba6c24ec6 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/dns_install.sh @@ -0,0 +1,7 @@ +#! /bin/bash + +yum install -y bind bind-chroot bind-utils +cp -f named.conf /etc/ +cp -f test.com.zone /var/named/ +chgrp named /etc/named.conf +chgrp named /var/named/test.com.zone diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/md5testfile b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/md5testfile new file mode 100644 index 0000000000..837a6682d6 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/md5testfile @@ -0,0 +1,24 @@ +Copyright (C) 2016-2020, by Jie Wu "AlexWoo" . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/named.conf b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/named.conf new file mode 100644 index 0000000000..79e0d0f67a --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/named.conf @@ -0,0 +1,25 @@ +options { + directory "/var/named"; + recursion no; +}; + +logging { + channel default_debug { + file "data/named.run"; + severity dynamic; + }; +}; + +zone "test.com." { + type master; + file "test.com.zone"; +}; + +zone "test1.com." { + type master; + file "test1.com.zone"; +}; + +include "/etc/named.rfc1912.zones"; +include "/etc/named.root.key"; + diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/nginx.conf b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/nginx.conf new file mode 100644 index 0000000000..b7d89ce44b --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/nginx.conf @@ -0,0 +1,94 @@ + +user root; +worker_processes 1; + +#error_log logs/error.log; +#error_log logs/error.log notice; +error_log logs/error.log info; +#error_log logs/error.log debug; + +#pid logs/nginx.pid; + +dynamic_conf conf/nginx_dynamic.conf 10; +dynamic_log logs/error_dynamic.log info; + +events { + worker_connections 1024; + worker_timers 1024; + resolver 192.168.84.254 valid=20s; + resolver_timeout 10s; + dynamic_refresh_interval 5m; +} + +http { + include mime.types; + default_type application/octet-stream; + + sendfile on; + + keepalive_timeout 65; + + server { + listen 80; + server_name localhost; + + location /trace1/ { + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Connection ""; + proxy_set_header X-NTM-Parentid $ntm_currentid; + proxy_set_header X-NTM-Currentid $ntm_newid; + proxy_pass http://127.0.0.1/trace/; + } + + location /trace/ { + echo $http_x_ntm_traceid; + echo $http_x_ntm_currentid; + echo $http_x_ntm_parentid; + echo $http_x_ntm_debug; + echo "trace variables"; + echo $ntm_traceid; + echo $ntm_currentid; + echo $ntm_parentid; + echo $ntm_debug; + } + + location /event_timer_test/ { + event_timer_test; + } + + location /event_resolver_test/ { + event_resolver_test; + } + + location /dynamic_resolver_test/ { + dynamic_resolver_test 192.168.84.4 www.test1.com; + } + + location /dynamic_conf_test/ { + dynamic_conf_test; + } + + location / { + http_dynamic_test; + } + + location /map_test/ { + map_test; + } + + location /toolkit_misc_test/ { + toolkit_misc_test; + } + + location /rbuf_test/ { + rbuf_test; + } + + location /poold_test/ { + poold_test; + } + } +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/nginx_dynamic.conf b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/nginx_dynamic.conf new file mode 100644 index 0000000000..6bef7d59b2 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/nginx_dynamic.conf @@ -0,0 +1,74 @@ +dynamic_test_i 200; +dynamic_test_s hello_world; + +http { + main_int 1000; + main_str gogogo; + + #defult server + server { + srv_int 1; + srv_str default; + } + + #wildcard_head + server { + srv_int 2; + srv_str wildcard_head; + server_name *.baidu.com; + } + + #wildcard_tail + server { + srv_int 3; + srv_str wildcard_tail; + server_name www.google.*; + } + + #hash + server { + srv_int 4; + srv_str hash; + server_name sports.sina.com.cn; + + location = / { + loc_int 1; + loc_str =/; + } + + location / { + loc_int 2; + loc_str /; + } + + location ^~ /test1/ { + loc_int 3; + loc_str ^~/test1/; + } + + location ~* \.(gif|jpg|jpeg)$ { + loc_int 4; + loc_str ~*\.(gif|jpg|jpeg)$; + } + + location /test { + loc_int 5; + loc_str /test; + } + } + + #pcre + server { + srv_int 5; + srv_str pcre; + server_name ~^flv(?!.*(dl\.))[A-Za-z0-9]*\.test\.com$; + } + + #multi + server { + srv_int 6; + srv_str multi; + server_name ~^flv(?!.*(dl\.))[A-Za-z0-9]*\.haha\.com$ www.sohu.com; + server_name *.qq.com; + } +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_dynamic_conf_test_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_dynamic_conf_test_module.c new file mode 100644 index 0000000000..ec24ad39c9 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_dynamic_conf_test_module.c @@ -0,0 +1,200 @@ +#include +#include +#include +#include "ngx_dynamic_conf.h" + + +/******************************************************************************/ +static void *ngx_dynamic_core_test_create_conf(ngx_conf_t *cf); +static char *ngx_dynamic_core_test_init_conf(ngx_conf_t *cf, void *conf); + + +typedef struct { + ngx_uint_t i; + ngx_str_t s; +} ngx_dynamic_core_test_conf_t; + + +ngx_core_module_t ngx_dynamic_core_test_module_ctx = { + ngx_string("dynamic_core_test"), + NULL, + NULL +}; + +static ngx_command_t ngx_dynamic_core_test_dcommands[] = { + + { ngx_string("dynamic_test_i"), + NGX_MAIN_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + 0, + offsetof(ngx_dynamic_core_test_conf_t, i), + NULL }, + + { ngx_string("dynamic_test_s"), + NGX_MAIN_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + 0, + offsetof(ngx_dynamic_core_test_conf_t, s), + NULL }, + + ngx_null_command +}; + + +static ngx_dynamic_core_module_t ngx_dynamic_core_test_module_dctx = { + ngx_string("dynamic_core_test"), + ngx_dynamic_core_test_create_conf, + ngx_dynamic_core_test_init_conf +}; + + +ngx_module_t ngx_dynamic_core_test_module = { + NGX_MODULE_V1, + &ngx_dynamic_core_test_module_ctx, /* module context */ + NULL, /* module directives */ + NGX_CORE_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + (uintptr_t) &ngx_dynamic_core_test_module_dctx, /* module dynamic context */ + (uintptr_t) ngx_dynamic_core_test_dcommands, /* module dynamic directives */ + NGX_MODULE_V1_DYNAMIC_PADDING +}; + + +static void * +ngx_dynamic_core_test_create_conf(ngx_conf_t *cf) +{ + ngx_dynamic_core_test_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_dynamic_core_test_conf_t)); + if (conf == NULL) { + return NULL; + } + + conf->i = NGX_CONF_UNSET_UINT; + + return conf; +} + + +static char * +ngx_dynamic_core_test_init_conf(ngx_conf_t *cf, void *conf) +{ + ngx_dynamic_core_test_conf_t *dctcf; + + dctcf = conf; + + ngx_conf_init_uint_value(dctcf->i, 1000); + + return NGX_CONF_OK; +} +/******************************************************************************/ + + +static char *ngx_dynamic_conf_test(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + + +#define MAXBUFSIZE 4096 + + +static ngx_command_t ngx_dynamic_conf_test_commands[] = { + + { ngx_string("dynamic_conf_test"), + NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS, + ngx_dynamic_conf_test, + 0, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_dynamic_conf_test_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_dynamic_conf_test_module = { + NGX_MODULE_V1, + &ngx_dynamic_conf_test_module_ctx, /* module context */ + ngx_dynamic_conf_test_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_int_t +ngx_dynamic_conf_test_handler(ngx_http_request_t *r) +{ + ngx_chain_t cl; + ngx_buf_t *b; + ngx_dynamic_core_test_conf_t *dctcf; + + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "dynamic conf test handler"); + + b = ngx_create_temp_buf(r->pool, MAXBUFSIZE); + + if (b == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + dctcf = (ngx_dynamic_core_test_conf_t *) + ngx_get_dconf(&ngx_dynamic_core_test_module); + if (dctcf == NULL) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, + "dynamic conf test handler, dynamic conf not configured"); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + r->headers_out.status = NGX_HTTP_OK; + + ngx_http_send_header(r); + + b->last = ngx_snprintf(b->last, MAXBUFSIZE, "dynamic core test %ui [%V]\n", + dctcf->i, &dctcf->s); + b->last_buf = 1; + b->last_in_chain = 1; + + cl.buf = b; + cl.next = NULL; + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "send body"); + + return ngx_http_output_filter(r, &cl); +} + + +static char * +ngx_dynamic_conf_test(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_core_loc_conf_t *clcf; + + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + clcf->handler = ngx_dynamic_conf_test_handler; + + return NGX_CONF_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_dynamic_resolver_test_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_dynamic_resolver_test_module.c new file mode 100644 index 0000000000..4bd53c95e0 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_dynamic_resolver_test_module.c @@ -0,0 +1,193 @@ +#include +#include +#include +#include "ngx_dynamic_resolver.h" + + +static char *ngx_dynamic_resolver_test(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + + +typedef struct { + ngx_flag_t sync; +} ngx_dynamic_resolver_test_ctx_t; + + +static ngx_command_t ngx_dynamic_resolver_test_commands[] = { + + { ngx_string("dynamic_resolver_test"), + NGX_HTTP_LOC_CONF|NGX_CONF_ANY, + ngx_dynamic_resolver_test, + 0, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_dynamic_resolver_test_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_dynamic_resolver_test_module = { + NGX_MODULE_V1, + &ngx_dynamic_resolver_test_module_ctx, /* module context */ + ngx_dynamic_resolver_test_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void +ngx_dynamic_resolver_test_result(void *data, struct sockaddr *sa, socklen_t len) +{ + ngx_dynamic_resolver_test_ctx_t *ctx; + ngx_chain_t cl; + ngx_buf_t *b; + ngx_int_t rc; + ngx_http_request_t *r; + u_char text[NGX_SOCKADDRLEN]; + + r = data; + + r->headers_out.status = NGX_HTTP_OK; + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "send header"); + + if (len == 0) { + r->headers_out.content_length_n = 0; + r->header_only = 1; + } + + ctx = ngx_http_get_module_ctx(r, ngx_dynamic_resolver_test_module); + if (ctx == NULL) { + ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + rc = ngx_http_send_header(r); + if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { + ngx_http_finalize_request(r, rc); + return; + } + + ngx_memzero(text, sizeof(text)); + len = ngx_sock_ntop(sa, len, text, NGX_SOCKADDRLEN, 0) + 1; + + b = ngx_create_temp_buf(r->pool, len); + + if (b == NULL) { + ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + b->last = ngx_snprintf(b->last, len, "%s\n", text); + b->last_buf = 1; + b->last_in_chain = 1; + + cl.buf = b; + cl.next = NULL; + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "send body"); + + ngx_http_output_filter(r, &cl); + + if (ctx->sync) { + return; + } + + ngx_http_finalize_request(r, NGX_OK); +} + +static ngx_int_t +ngx_dynamic_resolver_test_handler(ngx_http_request_t *r) +{ + ngx_dynamic_resolver_test_ctx_t *ctx; + ngx_str_t domain, sync; + struct sockaddr sa; + socklen_t len; + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "dynamic resolver test handler"); + + ctx = ngx_pcalloc(r->pool, sizeof(ngx_dynamic_resolver_test_ctx_t)); + if (ctx == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + ngx_http_set_ctx(r, ctx, ngx_dynamic_resolver_test_module); + + if (ngx_http_arg(r, (u_char *) "domain", 6, &domain) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "dynamic resolver test, no domain in http args"); + return NGX_HTTP_BAD_REQUEST; + } + + if (ngx_http_arg(r, (u_char *) "sync", 4, &sync) == NGX_OK) { + ctx->sync = 1; + } + + if (r->method == NGX_HTTP_GET && ctx->sync == 0) { + r->count++; + ngx_dynamic_resolver_start_resolver(&domain, + ngx_dynamic_resolver_test_result, r); + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "dynamic resolver test, after start resolver"); + return NGX_DONE; + } else if (r->method == NGX_HTTP_GET && ctx->sync) { + ngx_memzero(&sa, sizeof(sa)); + len = ngx_dynamic_resolver_gethostbyname(&domain, &sa); + ngx_dynamic_resolver_test_result(r, &sa, len); + + return NGX_OK; + } else if (r->method != NGX_HTTP_DELETE) { + return NGX_HTTP_BAD_REQUEST; + } + + ngx_dynamic_resolver_del_domain(&domain); + + r->headers_out.status = NGX_HTTP_OK; + r->headers_out.content_length_n = 0; + r->header_only = 1; + + return ngx_http_send_header(r); +} + + +static char * +ngx_dynamic_resolver_test(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_core_loc_conf_t *clcf; + ngx_str_t *value; + ngx_uint_t i; + + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + clcf->handler = ngx_dynamic_resolver_test_handler; + + value = cf->args->elts; + + for (i = 1; i < cf->args->nelts; ++i) { + ngx_dynamic_resolver_add_domain(&value[i], cf->cycle); + } + + return NGX_CONF_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_event_resolver_test_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_event_resolver_test_module.c new file mode 100644 index 0000000000..fbfcb988a8 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_event_resolver_test_module.c @@ -0,0 +1,150 @@ +#include +#include +#include +#include "ngx_event_resolver.h" + + +static char *ngx_event_resolver_test(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + + +static ngx_command_t ngx_event_resolver_test_commands[] = { + + { ngx_string("event_resolver_test"), + NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS, + ngx_event_resolver_test, + 0, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_event_resolver_test_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_event_resolver_test_module = { + NGX_MODULE_V1, + &ngx_event_resolver_test_module_ctx, /* module context */ + ngx_event_resolver_test_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void +ngx_event_resolver_test_result(void *data, ngx_resolver_addr_t *addrs, + ngx_uint_t naddrs) +{ + ngx_chain_t *cl, **ll; + ngx_buf_t *b; + size_t len; + ngx_int_t rc; + ngx_uint_t i; + ngx_http_request_t *r; + u_char text[NGX_SOCKADDRLEN]; + + r = data; + + r->headers_out.status = NGX_HTTP_OK; + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "send header"); + + if (naddrs == 0) { + r->headers_out.content_length_n = 0; + r->header_only = 1; + } + + rc = ngx_http_send_header(r); + if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { + ngx_http_finalize_request(r, rc); + return; + } + + ll = &cl; + for (i = 0; i < naddrs; ++i) { + len = ngx_sock_ntop(addrs[i].sockaddr, addrs[i].socklen, text, + NGX_SOCKADDRLEN, 0) + 1; + + b = ngx_create_temp_buf(r->pool, len); + + if (b == NULL) { + ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + b->last = ngx_snprintf(b->last, len, "%s\n", text); + if (i == naddrs - 1) { + b->last_buf = 1; + b->last_in_chain = 1; + } + + *ll = ngx_alloc_chain_link(r->pool); + (*ll)->buf = b; + (*ll)->next = NULL; + ll = &(*ll)->next; + } + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "send body"); + + ngx_http_output_filter(r, cl); + + ngx_http_finalize_request(r, NGX_OK); +} + +static ngx_int_t +ngx_event_resolver_test_handler(ngx_http_request_t *r) +{ + ngx_str_t domain; + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "event resolver test handler"); + + if (ngx_http_arg(r, (u_char *) "domain", sizeof("domain") - 1, &domain) + != NGX_OK) + { + return NGX_HTTP_BAD_REQUEST; + } + + ++r->count; + + ngx_event_resolver_start_resolver(&domain, + ngx_event_resolver_test_result, r); + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "after start resolver"); + + return NGX_DONE; +} + + +static char * +ngx_event_resolver_test(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_core_loc_conf_t *clcf; + + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + clcf->handler = ngx_event_resolver_test_handler; + + return NGX_CONF_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_event_timer_test_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_event_timer_test_module.c new file mode 100644 index 0000000000..320b803a8f --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_event_timer_test_module.c @@ -0,0 +1,151 @@ +#include +#include +#include +#include "ngx_event_timer_module.h" + + +static char *ngx_event_timer_test(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + +static ngx_command_t ngx_event_timer_test_commands[] = { + + { ngx_string("event_timer_test"), + NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS, + ngx_event_timer_test, + 0, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_event_timer_test_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_event_timer_test_module = { + NGX_MODULE_V1, + &ngx_event_timer_test_module_ctx, /* module context */ + ngx_event_timer_test_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void +ngx_event_timer_test_timer_handler(void *data) +{ + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, + "event timer test timer handler"); +} + +static ngx_int_t +ngx_event_timer_test_handler(ngx_http_request_t *r) +{ + ngx_chain_t cl; + ngx_buf_t *b; + size_t len; + ngx_int_t rc, timerid; + ngx_msec_t time; + ngx_str_t arg; + + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, + "event timer test handler"); + + if (r->method == NGX_HTTP_DELETE) { + len = sizeof("delete timer timerid=NGX_OFF_T_LEN\n") - 1; + + if (ngx_http_arg(r, (u_char *) "timerid", 7, &arg) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "event timer test, no timerid in http args"); + return NGX_HTTP_BAD_REQUEST; + } + + timerid = ngx_atoi(arg.data, arg.len); + if (timerid == NGX_ERROR) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "event timer test, timerid arg not int type"); + return NGX_HTTP_BAD_REQUEST; + } + + ngx_event_timer_del_timer(timerid); + } else if (r->method == NGX_HTTP_POST) { + len = sizeof("add timer timerid=NGX_OFF_T_LEN\n") - 1; + + if (ngx_http_arg(r, (u_char *) "time", 4, &arg) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "event timer test, no time in http args"); + return NGX_HTTP_BAD_REQUEST; + } + + time = ngx_parse_time(&arg, 0); + if (time == (ngx_msec_t) NGX_ERROR) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "event timer test, time arg not time type"); + return NGX_HTTP_BAD_REQUEST; + } + + timerid = ngx_event_timer_add_timer(time, + ngx_event_timer_test_timer_handler, NULL); + } else { + return NGX_HTTP_BAD_REQUEST; + } + + r->headers_out.status = NGX_HTTP_OK; + + rc = ngx_http_send_header(r); + if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { + return rc; + } + + b = ngx_create_temp_buf(r->pool, len); + + if (b == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + if (r->method == NGX_HTTP_DELETE) { + b->last = ngx_snprintf(b->last, len, + "delete timer timerid=%i\n", timerid); + } else { + b->last = ngx_snprintf(b->last, len, + "add timer timerid=%i\n", timerid); + } + b->last_buf = 1; + b->last_in_chain = 1; + + cl.buf = b; + cl.next = NULL; + + return ngx_http_output_filter(r, &cl); +} + +static char * +ngx_event_timer_test(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_core_loc_conf_t *clcf; + + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + clcf->handler = ngx_event_timer_test_handler; + + return NGX_CONF_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_http_dynamic_test_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_http_dynamic_test_module.c new file mode 100644 index 0000000000..eb0f09a834 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_http_dynamic_test_module.c @@ -0,0 +1,300 @@ +#include +#include +#include +#include "ngx_dynamic_conf.h" +#include "ngx_http_dynamic.h" + + +static char *ngx_http_dynamic_test(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + +static void *ngx_http_dynamic_test_create_main_conf(ngx_conf_t *cf); +static char *ngx_http_dynamic_test_init_main_conf(ngx_conf_t *cf, void *conf); +static void *ngx_http_dynamic_test_create_srv_conf(ngx_conf_t *cf); +static char *ngx_http_dynamic_test_init_srv_conf(ngx_conf_t *cf, void *conf); +static void *ngx_http_dynamic_test_create_loc_conf(ngx_conf_t *cf); +static char *ngx_http_dynamic_test_init_loc_conf(ngx_conf_t *cf, void *conf); + +#define MAXBUFSIZE 4096 + +typedef struct { + ngx_uint_t mi; + ngx_str_t ms; +} ngx_http_dynamic_test_main_conf_t; + +typedef struct { + ngx_uint_t si; + ngx_str_t ss; +} ngx_http_dynamic_test_srv_conf_t; + +typedef struct { + ngx_uint_t li; + ngx_str_t ls; +} ngx_http_dynamic_test_loc_conf_t; + + +static ngx_command_t ngx_http_dynamic_test_commands[] = { + + { ngx_string("http_dynamic_test"), + NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS, + ngx_http_dynamic_test, + 0, + 0, + NULL }, + + ngx_null_command +}; + +static ngx_http_module_t ngx_http_dynamic_test_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + +static ngx_command_t ngx_http_dynamic_test_dcommands[] = { + + { ngx_string("main_int"), + NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + 0, + offsetof(ngx_http_dynamic_test_main_conf_t, mi), + NULL }, + + { ngx_string("main_str"), + NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + 0, + offsetof(ngx_http_dynamic_test_main_conf_t, ms), + NULL }, + + { ngx_string("srv_int"), + NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + 0, + offsetof(ngx_http_dynamic_test_srv_conf_t, si), + NULL }, + + { ngx_string("srv_str"), + NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + 0, + offsetof(ngx_http_dynamic_test_srv_conf_t, ss), + NULL }, + + { ngx_string("loc_int"), + NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + 0, + offsetof(ngx_http_dynamic_test_loc_conf_t, li), + NULL }, + + { ngx_string("loc_str"), + NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + 0, + offsetof(ngx_http_dynamic_test_loc_conf_t, ls), + NULL }, + + ngx_null_command +}; + +static ngx_http_dynamic_module_t ngx_http_dynamic_test_module_dctx = { + ngx_http_dynamic_test_create_main_conf, /* create main configuration */ + ngx_http_dynamic_test_init_main_conf, /* init main configuration */ + + ngx_http_dynamic_test_create_srv_conf, /* create server configuration */ + ngx_http_dynamic_test_init_srv_conf, /* init server configuration */ + + ngx_http_dynamic_test_create_loc_conf, /* create location configuration */ + ngx_http_dynamic_test_init_loc_conf /* init location configuration */ +}; + +ngx_module_t ngx_http_dynamic_test_module = { + NGX_MODULE_V1, + &ngx_http_dynamic_test_module_ctx, /* module context */ + ngx_http_dynamic_test_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + (uintptr_t) &ngx_http_dynamic_test_module_dctx, /* module dynamic context */ + (uintptr_t) ngx_http_dynamic_test_dcommands, /* module dynamic directives */ + NGX_MODULE_V1_DYNAMIC_PADDING +}; + + +static void * +ngx_http_dynamic_test_create_main_conf(ngx_conf_t *cf) +{ + ngx_http_dynamic_test_main_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_dynamic_test_main_conf_t)); + if (conf == NULL) { + return NULL; + } + + conf->mi = NGX_CONF_UNSET_UINT; + + return conf; +} + +static char * +ngx_http_dynamic_test_init_main_conf(ngx_conf_t *cf, void *conf) +{ + ngx_http_dynamic_test_main_conf_t *hdtmcf; + + hdtmcf = conf; + + ngx_conf_init_uint_value(hdtmcf->mi, 100); + + if (hdtmcf->ms.len == 0) { + ngx_str_set(&hdtmcf->ms, "test for ms"); + } + + return NGX_CONF_OK; +} + +static void * +ngx_http_dynamic_test_create_srv_conf(ngx_conf_t *cf) +{ + ngx_http_dynamic_test_srv_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_dynamic_test_srv_conf_t)); + if (conf == NULL) { + return NULL; + } + + conf->si = NGX_CONF_UNSET_UINT; + + return conf; +} + +static char * +ngx_http_dynamic_test_init_srv_conf(ngx_conf_t *cf, void *conf) +{ + ngx_http_dynamic_test_srv_conf_t *hdtscf; + + hdtscf = conf; + + ngx_conf_init_uint_value(hdtscf->si, 100); + + if (hdtscf->ss.len == 0) { + ngx_str_set(&hdtscf->ss, "test for ss"); + } + + return NGX_CONF_OK; +} + +static void * +ngx_http_dynamic_test_create_loc_conf(ngx_conf_t *cf) +{ + ngx_http_dynamic_test_loc_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_dynamic_test_loc_conf_t)); + if (conf == NULL) { + return NULL; + } + + conf->li = NGX_CONF_UNSET_UINT; + + return conf; +} + +static char * +ngx_http_dynamic_test_init_loc_conf(ngx_conf_t *cf, void *conf) +{ + ngx_http_dynamic_test_loc_conf_t *hdtlcf; + + hdtlcf = conf; + + ngx_conf_init_uint_value(hdtlcf->li, 100); + + if (hdtlcf->ls.len == 0) { + ngx_str_set(&hdtlcf->ls, "test for ls"); + } + + return NGX_CONF_OK; +} + +static ngx_int_t +ngx_http_dynamic_test_handler(ngx_http_request_t *r) +{ + ngx_chain_t cl; + ngx_buf_t *b; + ngx_http_dynamic_test_main_conf_t *hdtmcf; + ngx_http_dynamic_test_srv_conf_t *hdtscf; + ngx_http_dynamic_test_loc_conf_t *hdtlcf; + + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "http dynamic test handler"); + + b = ngx_create_temp_buf(r->pool, MAXBUFSIZE); + + if (b == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + hdtmcf = ngx_http_get_module_main_dconf(r, &ngx_http_dynamic_test_module); + if (hdtmcf == NULL) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, + "dynamic conf test handler, dynamic main conf not configured"); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + hdtscf = ngx_http_get_module_srv_dconf(r, &ngx_http_dynamic_test_module); + if (hdtscf == NULL) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, + "dynamic conf test handler, dynamic srv conf not configured"); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + hdtlcf = ngx_http_get_module_loc_dconf(r, &ngx_http_dynamic_test_module); + if (hdtlcf == NULL) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, + "dynamic conf test handler, dynamic loc conf not configured"); + } + + r->headers_out.status = NGX_HTTP_OK; + + ngx_http_send_header(r); + + b->last = ngx_snprintf(b->last, MAXBUFSIZE, + "dynamic core test mi:%ui ms:%V\n", hdtmcf->mi, &hdtmcf->ms); + b->last = ngx_snprintf(b->last, MAXBUFSIZE, + "dynamic core test si:%ui ss:%V\n", hdtscf->si, &hdtscf->ss); + if (hdtlcf) { + b->last = ngx_snprintf(b->last, MAXBUFSIZE, + "dynamic core test li:%ui ls:%V\n", hdtlcf->li, &hdtlcf->ls); + } + b->last_buf = 1; + b->last_in_chain = 1; + + cl.buf = b; + cl.next = NULL; + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "send body"); + + return ngx_http_output_filter(r, &cl); +} + +static char * +ngx_http_dynamic_test(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_core_loc_conf_t *clcf; + + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + clcf->handler = ngx_http_dynamic_test_handler; + + return NGX_CONF_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_map_test_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_map_test_module.c new file mode 100644 index 0000000000..d0762bfe40 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_map_test_module.c @@ -0,0 +1,251 @@ +#include +#include +#include +#include "ngx_map.h" +#include "ngx_test_macro.h" + + +static char *ngx_map_test(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); + + +static ngx_command_t ngx_map_test_commands[] = { + + { ngx_string("map_test"), + NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS, + ngx_map_test, + 0, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_map_test_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_map_test_module = { + NGX_MODULE_V1, + &ngx_map_test_module_ctx, /* module context */ + ngx_map_test_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +#define MAP_NODE_INIT_STR(n, k) \ + ngx_map_node_t n; \ + static ngx_str_t k = ngx_string(#k); \ + ngx_memzero(&n, sizeof(ngx_map_node_t)); \ + n.raw_key = (intptr_t) &k; + +#define MAP_NODE_PRINT_KEY_STR(n) \ + { \ + ngx_str_t *s = (ngx_str_t *) (n)->raw_key; \ + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, \ + "!!!!!!!!!!!!!!!!%V", s); \ + } + +static ngx_int_t +ngx_map_test_handler(ngx_http_request_t *r) +{ + ngx_buf_t *b; + ngx_chain_t cl; + size_t len; + ngx_map_t map; + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "map test handler"); + + ngx_memzero(&map, sizeof(ngx_map_t)); + ngx_map_init(&map, ngx_map_hash_str, ngx_cmp_str); + + MAP_NODE_INIT_STR(n1, k1) + MAP_NODE_INIT_STR(n2, k2) + MAP_NODE_INIT_STR(n3, k3) + MAP_NODE_INIT_STR(n4, k4) + MAP_NODE_INIT_STR(n5, k5) + MAP_NODE_INIT_STR(n6, k6) + MAP_NODE_INIT_STR(n7, k7) + MAP_NODE_INIT_STR(n8, k8) + MAP_NODE_INIT_STR(n9, k9) + MAP_NODE_INIT_STR(n10, k10) + ngx_map_node_t n11; + static ngx_str_t k11 = ngx_string("k5"); + ngx_memzero(&n11, sizeof(ngx_map_node_t)); + n11.raw_key = (intptr_t) &k11; + + ngx_map_insert(&map, &n1, 0); + ngx_map_insert(&map, &n2, 0); + ngx_map_insert(&map, &n3, 0); + ngx_map_insert(&map, &n4, 0); + ngx_map_insert(&map, &n5, 0); + ngx_map_insert(&map, &n6, 0); + ngx_map_insert(&map, &n7, 0); + ngx_map_insert(&map, &n8, 0); + ngx_map_insert(&map, &n9, 0); + ngx_map_insert(&map, &n10, 0); + ngx_map_insert(&map, &n11, 0); + + NGX_TEST_INIT + + ngx_map_node_t *n; + n = ngx_map_begin(&map); + MAP_NODE_PRINT_KEY_STR(n) + NGX_TEST_ISOK(&n1 == n) + + n = ngx_map_next(n); + MAP_NODE_PRINT_KEY_STR(n) + NGX_TEST_ISOK(&n2 == n) + + n = ngx_map_next(n); + MAP_NODE_PRINT_KEY_STR(n) + NGX_TEST_ISOK(&n3 == n) + + n = ngx_map_next(n); + MAP_NODE_PRINT_KEY_STR(n) + NGX_TEST_ISOK(&n4 == n) + + n = ngx_map_next(n); + MAP_NODE_PRINT_KEY_STR(n) + NGX_TEST_ISOK(&n5 == n) + + n = ngx_map_next(n); + MAP_NODE_PRINT_KEY_STR(n) + NGX_TEST_ISOK(&n6 == n) + + n = ngx_map_next(n); + MAP_NODE_PRINT_KEY_STR(n) + NGX_TEST_ISOK(&n7 == n) + + n = ngx_map_next(n); + MAP_NODE_PRINT_KEY_STR(n) + NGX_TEST_ISOK(&n8 == n) + + n = ngx_map_next(n); + MAP_NODE_PRINT_KEY_STR(n) + NGX_TEST_ISOK(&n9 == n) + + n = ngx_map_next(n); + MAP_NODE_PRINT_KEY_STR(n) + NGX_TEST_ISOK(&n10 == n) + + n = ngx_map_next(n); + NGX_TEST_ISOK(NULL == n) + + n = ngx_map_rbegin(&map); + MAP_NODE_PRINT_KEY_STR(n) + NGX_TEST_ISOK(&n10 == n) + + n = ngx_map_prev(n); + MAP_NODE_PRINT_KEY_STR(n) + NGX_TEST_ISOK(&n9 == n) + + n = ngx_map_prev(n); + MAP_NODE_PRINT_KEY_STR(n) + NGX_TEST_ISOK(&n8 == n) + + n = ngx_map_prev(n); + MAP_NODE_PRINT_KEY_STR(n) + NGX_TEST_ISOK(&n7 == n) + + n = ngx_map_prev(n); + MAP_NODE_PRINT_KEY_STR(n) + NGX_TEST_ISOK(&n6 == n) + + n = ngx_map_prev(n); + MAP_NODE_PRINT_KEY_STR(n) + NGX_TEST_ISOK(&n5 == n) + + n = ngx_map_prev(n); + MAP_NODE_PRINT_KEY_STR(n) + NGX_TEST_ISOK(&n4 == n) + + n = ngx_map_prev(n); + MAP_NODE_PRINT_KEY_STR(n) + NGX_TEST_ISOK(&n3 == n) + + n = ngx_map_prev(n); + MAP_NODE_PRINT_KEY_STR(n) + NGX_TEST_ISOK(&n2 == n) + + n = ngx_map_prev(n); + MAP_NODE_PRINT_KEY_STR(n) + NGX_TEST_ISOK(&n1 == n) + + n = ngx_map_prev(n); + NGX_TEST_ISOK(NULL == n) + + static ngx_str_t kk1 = ngx_string("k100"); + NGX_TEST_ISOK(NULL == ngx_map_find(&map, (intptr_t) &kk1)) + + static ngx_str_t kk2 = ngx_string("k5"); + NGX_TEST_ISOK(&n5 == ngx_map_find(&map, (intptr_t) &kk2)) + + ngx_map_insert(&map, &n11, 1); + NGX_TEST_ISOK(&n11 == ngx_map_find(&map, (intptr_t) &kk2)) + + static ngx_str_t kk3 = ngx_string("k8"); + ngx_map_delete(&map, (intptr_t) &kk3); + NGX_TEST_ISOK(NULL == ngx_map_find(&map, (intptr_t) &kk3)) + + static ngx_str_t kk4 = ngx_string("k8"); + ngx_map_insert(&map, &n8, 1); + NGX_TEST_ISOK(&n8 == ngx_map_find(&map, (intptr_t) &kk4)) + + static ngx_str_t kk5 = ngx_string("k7"); + NGX_TEST_ISOK(&n7 == ngx_map_find(&map, (intptr_t) &kk5)) + + r->headers_out.status = NGX_HTTP_OK; + + ngx_http_send_header(r); + + len = sizeof("TEST cases 4294967296, 4294967296 pass\n") - 1; + b = ngx_create_temp_buf(r->pool, len); + + if (b == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + b->last = ngx_snprintf(b->last, len, "TEST cases %d, %d pass\n", + count, pass); + b->last_buf = 1; + b->last_in_chain = 1; + + cl.buf = b; + cl.next = NULL; + + return ngx_http_output_filter(r, &cl); +} + + +static char * +ngx_map_test(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_core_loc_conf_t *clcf; + + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + clcf->handler = ngx_map_test_handler; + + return NGX_CONF_OK; +} + diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_poold_test_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_poold_test_module.c new file mode 100644 index 0000000000..da93021d69 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_poold_test_module.c @@ -0,0 +1,90 @@ +#include +#include +#include +#include "ngx_poold.h" + + +static char *ngx_poold_test(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); + + +static ngx_command_t ngx_poold_test_commands[] = { + + { ngx_string("poold_test"), + NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS, + ngx_poold_test, + 0, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_poold_test_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_poold_test_module = { + NGX_MODULE_V1, + &ngx_poold_test_module_ctx, /* module context */ + ngx_poold_test_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_int_t +ngx_poold_test_handler(ngx_http_request_t *r) +{ + ngx_chain_t *out; + ngx_pool_t *pool; + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "poold test handler"); + + pool = NGX_CREATE_POOL(4096, r->connection->log); + + pool = NGX_CREATE_POOL(1024, r->connection->log); + + NGX_DESTROY_POOL(pool); + + NGX_DESTROY_POOL(pool); + + r->headers_out.status = NGX_HTTP_OK; + ngx_http_send_header(r); + + out = ngx_poold_state(r, 1); + out->buf->last_buf = 1; + + return ngx_http_output_filter(r, out); +} + + +static char * +ngx_poold_test(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_core_loc_conf_t *clcf; + + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + clcf->handler = ngx_poold_test_handler; + + return NGX_CONF_OK; +} + diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_rbuf_test_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_rbuf_test_module.c new file mode 100644 index 0000000000..3a112ecec7 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_rbuf_test_module.c @@ -0,0 +1,116 @@ +#include +#include +#include +#include "../ngx_rbuf.h" + + +static char *ngx_rbuf_test(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); + + +static ngx_command_t ngx_rbuf_test_commands[] = { + + { ngx_string("rbuf_test"), + NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS, + ngx_rbuf_test, + 0, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_rbuf_test_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_rbuf_test_module = { + NGX_MODULE_V1, + &ngx_rbuf_test_module_ctx, /* module context */ + ngx_rbuf_test_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_int_t +ngx_rbuf_test_handler(ngx_http_request_t *r) +{ + ngx_str_t size; + ngx_int_t key; + ngx_chain_t *cl, *cl1, *cl2; + ngx_chain_t *out; + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "rbuf test handler"); + + if (ngx_http_arg(r, (u_char *) "size", sizeof("size") - 1, &size) + != NGX_OK) + { + return NGX_HTTP_BAD_REQUEST; + } + + key = ngx_atoi(size.data, size.len); + cl = ngx_get_chainbuf(key, 1); + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "cl: %p", cl); + + cl1 = ngx_get_chainbuf(key, 0); + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "cl1: %p", cl1); + cl1->buf->start = cl->buf->start; + cl1->buf->end = cl->buf->end; + cl1->buf->pos = cl->buf->pos; + cl1->buf->last = cl->buf->last; + + cl2 = ngx_get_chainbuf(key, 0); + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "cl2: %p", cl2); + cl2->buf->start = cl->buf->start; + cl2->buf->end = cl->buf->end; + cl2->buf->pos = cl->buf->pos; + cl2->buf->last = cl->buf->last; + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "rbuf free"); + + ngx_put_chainbuf(cl2); + + ngx_put_chainbuf(cl); + + ngx_put_chainbuf(cl1); + + r->headers_out.status = NGX_HTTP_OK; + ngx_http_send_header(r); + + out = ngx_rbuf_state(r, 1); + out->buf->last_buf = 1; + + return ngx_http_output_filter(r, out); +} + + +static char * +ngx_rbuf_test(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_core_loc_conf_t *clcf; + + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + clcf->handler = ngx_rbuf_test_handler; + + return NGX_CONF_OK; +} + diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_test_macro.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_test_macro.h new file mode 100644 index 0000000000..6da2f68cae --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_test_macro.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com + */ + + +#ifndef _NGX_TEST_MACRO_H_INCLUDE_ +#define _NGX_TEST_MACRO_H_INCLUDE_ + + +#include +#include + + +static ngx_int_t count = 0; +static ngx_int_t pass = 0; + +#define NGX_TEST_INIT count = 0, pass = 0; + +#define NGX_TEST_ISOK(testcase) \ +{ \ + ngx_int_t __ret = testcase; \ + ++count; \ + if (__ret) ++pass; \ + ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, 0, " TEST "#testcase"%s",\ + (__ret ? " ...OK" : " ...ERROR")); \ +} + +#define NGX_TEST_INT(di, si) \ + (di == si) + +static ngx_inline ngx_int_t +ngx_test_str(ngx_str_t *nstr, char *cstr) +{ + size_t len; + + len = ngx_strlen(cstr); + + return (nstr->len == len && ngx_memcmp(nstr->data, cstr, len) == 0); +} + +#endif diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_timerd_test_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_timerd_test_module.c new file mode 100644 index 0000000000..df3064960a --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_timerd_test_module.c @@ -0,0 +1,198 @@ +#include +#include +#include +#include "ngx_timerd.h" + + +static char *ngx_timerd_test(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); + + +typedef struct { + ngx_pool_t *pool; + ngx_event_t ev; + ngx_uint_t footprint; +} ngx_timer_test_data_t; + + +static ngx_command_t ngx_timerd_test_commands[] = { + + { ngx_string("timerd_test"), + NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS, + ngx_timerd_test, + 0, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_timerd_test_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_timerd_test_module = { + NGX_MODULE_V1, + &ngx_timerd_test_module_ctx, /* module context */ + ngx_timerd_test_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void +ngx_timerd_test_timer(ngx_event_t *ev) +{ + ngx_timer_test_data_t *data; + + data = ev->data; + + ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, 0, "timerd test timer: %p %ui", + data, data->footprint); +} + + +static ngx_int_t +ngx_timerd_test_handler(ngx_http_request_t *r) +{ + ngx_chain_t *out; + + // normal timer trigger + ngx_timer_test_data_t *test; + ngx_pool_t *pool; + + pool = ngx_create_pool(4096, r->connection->log); + test = ngx_pcalloc(pool, sizeof(ngx_timer_test_data_t)); + + test->pool = pool; + test->footprint = ngx_timerd_footprint(); + test->ev.data = test; + test->ev.handler = ngx_timerd_test_timer; + NGX_ADD_TIMER(&test->ev, 5000, offsetof(ngx_timer_test_data_t, footprint)); + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "!!!!!!!!!!normal timer trigger: %p %ui", test, test->footprint); + + // delete timer twice + ngx_timer_test_data_t *test1; + ngx_pool_t *pool1; + + pool1 = ngx_create_pool(4096, r->connection->log); + test1 = ngx_pcalloc(pool1, sizeof(ngx_timer_test_data_t)); + + test1->pool = pool1; + test1->footprint = ngx_timerd_footprint(); + test1->ev.data = test1; + test1->ev.handler = ngx_timerd_test_timer; + NGX_ADD_TIMER(&test1->ev, 5000, offsetof(ngx_timer_test_data_t, footprint)); + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "!!!!!!!!!!delete timer twice: %p %ui", test1, test1->footprint); + NGX_DEL_TIMER(&test1->ev, test1->footprint); + NGX_DEL_TIMER(&test1->ev, test1->footprint); + + // timer trigger after event destroy + ngx_timer_test_data_t *test2; + ngx_pool_t *pool2; + + pool2 = ngx_create_pool(4096, r->connection->log); + test2 = ngx_pcalloc(pool2, sizeof(ngx_timer_test_data_t)); + + test2->pool = pool2; + test2->footprint = ngx_timerd_footprint(); + test2->ev.data = test2; + test2->ev.handler = ngx_timerd_test_timer; + NGX_ADD_TIMER(&test2->ev, 5000, offsetof(ngx_timer_test_data_t, footprint)); + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "!!!!!!!!!!timer trigger after event destroy: %p %ui", + test2, test2->footprint); + test2->footprint = ngx_timerd_footprint(); + + ngx_destroy_pool(pool2); + + // delete timer after event destroy + ngx_timer_test_data_t *test3; + ngx_pool_t *pool3; + + pool3 = ngx_create_pool(4096, r->connection->log); + test3 = ngx_pcalloc(pool3, sizeof(ngx_timer_test_data_t)); + + test3->pool = pool3; + test3->footprint = ngx_timerd_footprint(); + test3->ev.data = test3; + test3->ev.handler = ngx_timerd_test_timer; + NGX_ADD_TIMER(&test3->ev, 5000, offsetof(ngx_timer_test_data_t, footprint)); + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "!!!!!!!!!!delete timer after event destroy: %p %ui", + test3, test3->footprint); + test3->footprint = ngx_timerd_footprint(); + + NGX_DEL_TIMER(&test3->ev, test3->footprint); + + ngx_destroy_pool(pool3); + + + // add timer after event destroy + ngx_timer_test_data_t *test4; + ngx_pool_t *pool4; + + pool4 = ngx_create_pool(4096, r->connection->log); + test4 = ngx_pcalloc(pool4, sizeof(ngx_timer_test_data_t)); + + test4->pool = pool4; + test4->footprint = ngx_timerd_footprint(); + test4->ev.data = test4; + test4->ev.handler = ngx_timerd_test_timer; + NGX_ADD_TIMER(&test4->ev, 5000, offsetof(ngx_timer_test_data_t, footprint)); + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "!!!!!!!!!!add timer after event destroy: %p %ui", + test4, test4->footprint); + test4->footprint = ngx_timerd_footprint(); + + NGX_ADD_TIMER(&test4->ev, 5000, offsetof(ngx_timer_test_data_t, footprint)); + + ngx_destroy_pool(pool4); + + + r->headers_out.status = NGX_HTTP_OK; + ngx_http_send_header(r); + + out = ngx_timerd_state(r, 1); + out->buf->last_buf = 1; + + return ngx_http_output_filter(r, out); +} + + +static char * +ngx_timerd_test(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_core_loc_conf_t *clcf; + + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + clcf->handler = ngx_timerd_test_handler; + + return NGX_CONF_OK; +} + diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_toolkit_misc_test_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_toolkit_misc_test_module.c new file mode 100644 index 0000000000..6c58f71ecc --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/ngx_toolkit_misc_test_module.c @@ -0,0 +1,285 @@ +#include +#include +#include +#include "ngx_toolkit_misc.h" +#include "ngx_test_macro.h" + + +static char *ngx_toolkit_misc_test(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); + + +static ngx_command_t ngx_toolkit_misc_test_commands[] = { + + { ngx_string("toolkit_misc_test"), + NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS, + ngx_toolkit_misc_test, + 0, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_toolkit_misc_test_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_toolkit_misc_test_module = { + NGX_MODULE_V1, + &ngx_toolkit_misc_test_module_ctx, /* module context */ + ngx_toolkit_misc_test_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_int_t +ngx_parse_toolkit_misc_test(ngx_int_t ret, char *url, char *scheme, char *user, + char *host, char *port, char *path, char *args, char *fragment, + char *host_with_port, char *uri_with_args) +{ + ngx_int_t _ret; + ngx_str_t _url; + ngx_request_url_t rurl; + + ngx_memzero(&rurl, sizeof(ngx_request_url_t)); + _url.data = (u_char *) url; + _url.len = ngx_strlen(url); + + _ret = ngx_parse_request_url(&rurl, &_url); + if (ret != _ret) { + return 0; + } + + if (_ret == NGX_ERROR) { + return 1; + } + +#define TEST(para) \ + if (para == NULL && rurl.para.len) { \ + return 0; \ + } else if (para && ngx_test_str(&rurl.para, para) == NGX_ERROR) { \ + return 0; \ + } + + TEST(scheme) + TEST(user) + TEST(host) + TEST(port) + TEST(path) + TEST(args) + TEST(fragment) + TEST(host_with_port) + TEST(uri_with_args) + +#undef TEST + + return 1; +} + +static ngx_int_t +ngx_request_port_test(in_port_t expect, char *scheme, char *port) +{ + in_port_t ret; + ngx_str_t _scheme, _port; + + _scheme.data = (u_char *) scheme; + _scheme.len = ngx_strlen(scheme); + + _port.data = (u_char *) port; + _port.len = ngx_strlen(port); + + ret = ngx_request_port(&_scheme, &_port); + + return ret == expect; +} + +static ngx_int_t +ngx_toolkit_misc_test_handler(ngx_http_request_t *r) +{ + ngx_buf_t *b; + ngx_chain_t cl; + size_t len; + ngx_fd_t fd; + u_char md5key[NGX_MD5KEY_LEN]; + ngx_str_t file; + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "request url test handler"); + + NGX_TEST_INIT + + NGX_TEST_ISOK(ngx_parse_toolkit_misc_test(NGX_ERROR, "test", + NULL, + NULL, NULL, NULL, + NULL, NULL, NULL, + NULL, NULL)); + NGX_TEST_ISOK(ngx_parse_toolkit_misc_test(NGX_ERROR, "http://", + NULL, + NULL, NULL, NULL, + NULL, NULL, NULL, + NULL, NULL)); + NGX_TEST_ISOK(ngx_parse_toolkit_misc_test(NGX_ERROR, "http://alex@", + NULL, + NULL, NULL, NULL, + NULL, NULL, NULL, + NULL, NULL)); + NGX_TEST_ISOK(ngx_parse_toolkit_misc_test(NGX_OK, "http://alex@test", + "http", + "alex", "test", NULL, + NULL, NULL, NULL, + "test", NULL)); + NGX_TEST_ISOK(ngx_parse_toolkit_misc_test(NGX_ERROR, "http://alex@test:", + NULL, + NULL, NULL, NULL, + NULL, NULL, NULL, + NULL, NULL)); + NGX_TEST_ISOK(ngx_parse_toolkit_misc_test(NGX_OK, "http://alex@test:8080", + "http", + "alex", "test", "8080", + NULL, NULL, NULL, + "test:8080", NULL)); + NGX_TEST_ISOK(ngx_parse_toolkit_misc_test(NGX_ERROR, "http://@test:8080", + NULL, + NULL, NULL, NULL, + NULL, NULL, NULL, + NULL, NULL)); + NGX_TEST_ISOK(ngx_parse_toolkit_misc_test(NGX_OK, "http://test", + "http", + NULL, "test", NULL, + NULL, NULL, NULL, + "test", NULL)); + NGX_TEST_ISOK(ngx_parse_toolkit_misc_test(NGX_ERROR, "http://test:", + NULL, + NULL, NULL, NULL, + NULL, NULL, NULL, + NULL, NULL)); + NGX_TEST_ISOK(ngx_parse_toolkit_misc_test(NGX_OK, "http://test:8080", + "http", + NULL, "test", "8080", + NULL, NULL, NULL, + "test:8080", NULL)); + NGX_TEST_ISOK(ngx_parse_toolkit_misc_test(NGX_OK, "http://test:8080/", + "http", + NULL, "test", "8080", + NULL, NULL, NULL, + "test:8080", NULL)); + NGX_TEST_ISOK(ngx_parse_toolkit_misc_test(NGX_OK, "http://test:8080/live", + "http", + NULL, "test", "8080", + "live", NULL, NULL, + "test:8080", "live")); + NGX_TEST_ISOK(ngx_parse_toolkit_misc_test(NGX_OK, + "http://alex@test/live/test", + "http", + "alex", "test", "8080", + "live/test", NULL, NULL, + "test", "live/test")); + NGX_TEST_ISOK(ngx_parse_toolkit_misc_test(NGX_ERROR, + "http://alex@test:80/live/test?", + NULL, + NULL, NULL, NULL, + NULL, NULL, NULL, + NULL, NULL)); + NGX_TEST_ISOK(ngx_parse_toolkit_misc_test(NGX_OK, + "http://alex@test:80/live/test?a=b&c=d", + "http", + "alex", "test", "80", + "live/test", "a=b&c=d", NULL, + "test:80", "live/test?a=b&c=d")); + NGX_TEST_ISOK(ngx_parse_toolkit_misc_test(NGX_ERROR, + "http://alex@/live/test?a=b&c=d", + NULL, + NULL, NULL, NULL, + NULL, NULL, NULL, + NULL, NULL)); + NGX_TEST_ISOK(ngx_parse_toolkit_misc_test(NGX_OK, + "http://alex@test:8080/live/test?a=b&c=d#test", + "http", + "alex", "test", "8080", + "live/test", "a=b&c=d", "test", + "test:8080", "live/test?a=b&c=d#test")); + + NGX_TEST_ISOK(ngx_request_port_test(0, "", "abcd")); + NGX_TEST_ISOK(ngx_request_port_test(1234, "", "1234")); + NGX_TEST_ISOK(ngx_request_port_test(0, "", "102222")); + NGX_TEST_ISOK(ngx_request_port_test(0, "rtp", "")); + NGX_TEST_ISOK(ngx_request_port_test(80, "http", "")); + NGX_TEST_ISOK(ngx_request_port_test(443, "https", "")); + NGX_TEST_ISOK(ngx_request_port_test(1935, "rtmp", "")); + + file.data = (u_char *) "/usr/local/nginx/conf/md5testfile"; + file.len = sizeof("/usr/local/nginx/conf/md5testfile") - 1; + + fd = ngx_open_file(file.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); + if (fd == NGX_INVALID_FILE) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, ngx_errno, + ngx_open_file_n " %s failed", file.data); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + if (ngx_md5_file(fd, md5key)) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "md5 file %V failed", &file); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + NGX_TEST_ISOK(ngx_memcmp(md5key, "d4309a79dbeaeb6d7811734ce483db59", 32) + == 0); + + ngx_close_file(fd); + + + r->headers_out.status = NGX_HTTP_OK; + + ngx_http_send_header(r); + + len = sizeof("TEST cases 4294967296, 4294967296 pass\n") - 1; + b = ngx_create_temp_buf(r->pool, len); + + if (b == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + b->last = ngx_snprintf(b->last, len, "TEST cases %d, %d pass\n", + count, pass); + b->last_buf = 1; + b->last_in_chain = 1; + + cl.buf = b; + cl.next = NULL; + + return ngx_http_output_filter(r, &cl); +} + + +static char * +ngx_toolkit_misc_test(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_core_loc_conf_t *clcf; + + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + clcf->handler = ngx_toolkit_misc_test_handler; + + return NGX_CONF_OK; +} + diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/test.com.zone b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/test.com.zone new file mode 100644 index 0000000000..6432d87d15 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/test.com.zone @@ -0,0 +1,14 @@ +$TTL 3H +@ IN SOA @ wj19840501.gmail.com. ( + 0 ; serial + 1D ; refresh + 1H ; retry + 1W ; expire + 3H ; minimum + ) + +@ IN NS dns +dns IN A 192.168.84.254 +www IN A 1.1.1.1 +www IN A 1.1.1.2 +www IN A 1.1.1.3 diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/test1.com.zone b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/test1.com.zone new file mode 100644 index 0000000000..27ed83c7f9 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-toolkit-module/t/test1.com.zone @@ -0,0 +1,14 @@ +$TTL 3H +@ IN SOA @ wj19840501.gmail.com. ( + 0 ; serial + 1D ; refresh + 1H ; retry + 1W ; expire + 3H ; minimum + ) + +@ IN NS dns +dns IN A 192.168.84.254 +www IN A 1.1.1.4 +www IN A 1.1.1.5 +www IN A 1.1.1.6 diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/LICENSE b/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/LICENSE new file mode 100644 index 0000000000..2bc5ec20be --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/LICENSE @@ -0,0 +1,22 @@ +Copyright (C) 2017 Roman Arutyunyan +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/README.rst b/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/README.rst new file mode 100644 index 0000000000..b32e846f5e --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/README.rst @@ -0,0 +1,256 @@ +************************* +NGINX MPEG-TS Live Module +************************* + + +.. contents:: + + +Features +======== + +- receives MPEG-TS over HTTP +- produces and manages live HLS_ +- produces and manages live MPEG-DASH_ + + +Compatibility +============= + +- `nginx `_ version >= 1.11.5 + + +Build +===== + +Building nginx with the module: + +.. code-block:: bash + + # static module + $ ./configure --add-module=/path/to/nginx-ts-module + + # dynamic module + $ ./configure --add-dynamic-module=/path/to/nginx-ts-module + + +Directives +========== + +ts +-- + +========== ======== +*Syntax:* ``ts`` +*Context:* location +========== ======== + +Sets up a live MPEG-TS handler for the location. +This directive is **required** for HLS or MPEG-DASH generation. + +The last URI component is used as a stream name. +For example, if the URI is ``/foo/bar/baz``, the stream name is ``baz``. + +A simple way to stream MPEG-TS over HTTP is by running ``ffmpeg``: + +.. code-block:: bash + + $ ffmpeg ... -f mpegts http://127.0.0.1:8000/foo + + +By default, HTTP request body size is limited in nginx. +To enable live streaming without size limitation, use the directive +``client_max_body_size 0``. + + +ts_hls +------ + +========== ======== +*Syntax:* ``ts_hls path=PATH [segment=MIN[:MAX]] [segments=NUMBER] [max_size=SIZE] [noclean]`` +*Context:* location +========== ======== + +Enables generating live HLS in the location. +The ``PATH`` parameter specifies a directory where HLS playlist and segment +files will be created. +The directory is created if missing. +For every publshed stream a subdirectory with the stream name is created under +the ``PATH`` directory. +The HLS playlist file created in the stream subdirectory is named +``index.m3u8``. +A path handler is installed to watch files in the directory. +The old files in the directory are automatically deleted once they get old +enough and are not supposed to be accessed by clients anymore. +It is not allowed to reuse the path in other ``ts_hls`` or ``ts_dash`` +directives. + +The ``segment`` parameter specifies minimum and maximum segment durations. +If the stream has video, segments are started at video key frames. +If a key frame does not appear within ``MAX`` duration, the segment is +truncated. +The default value for minimum segment duration is 5 seconds. +If unspecified, maximum segment duration is set to be twice as much as the +minimum. + +The ``segments`` parameter specifies the maximum number of segments in a +playlist. +As new segments are added to the playlist, the oldest segments are removed from +it. + +The ``max_size`` parameter specifies the maximum size of a segment. +A segment is truncated once it reaches this size. + +The ``noclean`` parameter indicates that the old files (segments and the +playlist) should not be automatically deleted from disk. + +Example:: + + location / { + ts; + ts_hls path=/var/hls segment=10s; + } + + +ts_dash +------- + +========== ======== +*Syntax:* ``ts_dash path=PATH [segment=MIN[:MAX]] [segments=NUMBER] [max_size=SIZE] [noclean]`` +*Context:* location +========== ======== + +Enables generating live MPEG-DASH in the location. +The ``PATH`` parameter specifies a directory where MPEG-DASH manifest and +segment files will be created. +The directory is created if missing. +For every publshed stream a subdirectory with the stream name is created under +the ``PATH`` directory. +The MPEG-DASH manifest file created in the stream subdirectory is named +``index.mpd``. +A path handler is installed to watch files in the directory. +The old files in the directory are automatically deleted once they get old +enough and are not supposed to be accessed by clients anymore. +It is not allowed to reuse the path in other ``ts_hls`` or ``ts_dash`` +directives. + +The ``segment`` parameter specifies minimum and maximum segment durations. +If the stream has video, segments are started at video key frames. +If a key frame does not appear within ``MAX`` duration, the segment is +truncated. +The default value for minimum segment duration is 5 seconds. +If unspecified, maximum segment duration is set to be twice as much as the +minimum. + + When setting an explicit value for the ``MAX`` parameter, the following + note should be taken into account. + If the next segment is shorter than the previous one by a factor more that + two, dash.js_ can end up in a busy cycle requesting the second segment over + and over again. + +The ``segments`` parameter specifies the maximum number of segments in a +manifest. +As new segments are added to the manifest, the oldest segments are removed from +it. + +The ``max_size`` parameter specifies the maximum size of a segment. +A segment is truncated once it reaches this size. + +The ``noclean`` parameter indicates that the old files (segments and the +manifest) should not be automatically deleted from disk. + +Example:: + + location / { + ts; + ts_dash path=/var/hls segment=10s; + } + + +Example +======= + +nginx.conf:: + + # nginx.conf + + events { + } + + http { + server { + listen 8000; + + location / { + root html; + } + + location /publish/ { + ts; + ts_hls path=/var/media/hls segment=10s; + ts_dash path=/var/media/dash segment=10s; + + client_max_body_size 0; + } + + location /play/ { + types { + application/x-mpegURL m3u8; + application/dash+xml mpd; + video/MP2T ts; + video/mp4 mp4; + } + alias /var/media/; + } + } + } + +HLS in HTML: + +.. code-block:: html + + + + + +MPEG-DASH in HTML using the dash.js_ player: + +.. code-block:: html + + + + + + + +Broadcasting a single-bitrate mp4 file: + +.. code-block:: bash + + $ ffmpeg -re -i ~/Movies/sintel.mp4 -bsf:v h264_mp4toannexb + -c copy -f mpegts http://127.0.0.1:8000/publish/sintel + +Broadcasting an mp4 file in multiple bitrates. +For proper HLS generation streams should be grouped into MPEG-TS programs with +the ``-program`` option of ``ffmpeg``: + +.. code-block:: bash + + $ ffmpeg -re -i ~/Movies/sintel.mp4 -bsf:v h264_mp4toannexb + -map 0:0 -map 0:1 -map 0:0 -map 0:1 + -c:v:0 copy + -c:a:0 copy + -c:v:1 libx264 -b:v:1 100k + -c:a:1 libfaac -ac:a:1 1 -b:a:1 32k + -program "st=0:st=1" -program "st=2:st=3" + -f mpegts http://127.0.0.1:8000/publish/sintel + + +.. _HLS: https://tools.ietf.org/html/draft-pantos-http-live-streaming-23 +.. _MPEG-DASH: https://en.wikipedia.org/wiki/Dynamic_Adaptive_Streaming_over_HTTP +.. _dash.js: https://github.com/Dash-Industry-Forum/dash.js diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/config b/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/config new file mode 100644 index 0000000000..aebcdab2c3 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/config @@ -0,0 +1,18 @@ +ngx_module_type=HTTP +ngx_addon_name=ngx_http_ts_module +ngx_module_name=ngx_http_ts_module +ngx_module_incs= +ngx_module_deps="$ngx_addon_dir/src/ngx_ts_stream.h \ + $ngx_addon_dir/src/ngx_ts_hls.h \ + $ngx_addon_dir/src/ngx_ts_dash.h \ + $ngx_addon_dir/src/ngx_ts_avc.h \ + $ngx_addon_dir/src/ngx_ts_aac.h" +ngx_module_srcs="$ngx_addon_dir/src/ngx_http_ts_module.c \ + $ngx_addon_dir/src/ngx_ts_stream.c \ + $ngx_addon_dir/src/ngx_ts_hls.c \ + $ngx_addon_dir/src/ngx_ts_dash.c \ + $ngx_addon_dir/src/ngx_ts_dash_mp4.c \ + $ngx_addon_dir/src/ngx_ts_avc.c \ + $ngx_addon_dir/src/ngx_ts_aac.c" + +. auto/module diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_http_ts_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_http_ts_module.c new file mode 100644 index 0000000000..316cf61c6d --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_http_ts_module.c @@ -0,0 +1,263 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include + +#include "ngx_ts_stream.h" +#include "ngx_ts_hls.h" +#include "ngx_ts_dash.h" + + +typedef struct { + ngx_ts_hls_conf_t *hls; + ngx_ts_dash_conf_t *dash; +} ngx_http_ts_loc_conf_t; + + +typedef struct { + ngx_ts_stream_t *ts; +} ngx_http_ts_ctx_t; + + +static ngx_int_t ngx_http_ts_handler(ngx_http_request_t *r); +static void ngx_http_ts_init(ngx_http_request_t *r); +static void ngx_http_ts_read_event_handler(ngx_http_request_t *r); + +static char *ngx_http_ts(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static void *ngx_http_ts_create_conf(ngx_conf_t *cf); +static char *ngx_http_ts_merge_conf(ngx_conf_t *cf, void *parent, void *child); + + +static ngx_command_t ngx_http_ts_commands[] = { + + { ngx_string("ts"), + NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS, + ngx_http_ts, + 0, + 0, + NULL }, + + { ngx_string("ts_hls"), + NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, + ngx_ts_hls_set_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_ts_loc_conf_t, hls), + NULL }, + + { ngx_string("ts_dash"), + NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, + ngx_ts_dash_set_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_ts_loc_conf_t, dash), + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_http_ts_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + ngx_http_ts_create_conf, /* create location configuration */ + ngx_http_ts_merge_conf /* merge location configuration */ +}; + + +ngx_module_t ngx_http_ts_module = { + NGX_MODULE_V1, + &ngx_http_ts_module_ctx, /* module context */ + ngx_http_ts_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_int_t +ngx_http_ts_handler(ngx_http_request_t *r) +{ + ngx_int_t rc; + ngx_str_t name; + ngx_uint_t n; + ngx_http_ts_ctx_t *ctx; + ngx_http_ts_loc_conf_t *tlcf; + + ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_ts_ctx_t)); + if (ctx == NULL) { + return NGX_ERROR; + } + + ctx->ts = ngx_pcalloc(r->pool, sizeof(ngx_ts_stream_t)); + if (ctx->ts == NULL) { + return NGX_ERROR; + } + + ctx->ts->pool = r->pool; + ctx->ts->log = r->connection->log; + + for (n = 0; n < r->uri.len; n++) { + if (r->uri.data[r->uri.len - 1 - n] == '/') { + break; + } + } + + name.data = &r->uri.data[r->uri.len - n]; + name.len = n; + + /* XXX detect streams with the same name, add shared zone */ + + tlcf = ngx_http_get_module_loc_conf(r, ngx_http_ts_module); + + if (tlcf->hls) { + if (ngx_ts_hls_create(tlcf->hls, ctx->ts, &name) == NULL) { + return NGX_ERROR; + } + } + + if (tlcf->dash) { + if (ngx_ts_dash_create(tlcf->dash, ctx->ts, &name) == NULL) { + return NGX_ERROR; + } + } + + ngx_http_set_ctx(r, ctx, ngx_http_ts_module); + + r->request_body_no_buffering = 1; + + rc = ngx_http_read_client_request_body(r, ngx_http_ts_init); + + if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { + return rc; + } + + return NGX_DONE; +} + + +static void +ngx_http_ts_init(ngx_http_request_t *r) +{ + ngx_http_ts_ctx_t *ctx; + ngx_http_request_body_t *rb; + + rb = r->request_body; + + if (rb == NULL) { + ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_ts_module); + + if (ngx_ts_read(ctx->ts, rb->bufs) != NGX_OK) { + ngx_http_finalize_request(r, NGX_ERROR); + return; + } + + if (r->reading_body) { + r->read_event_handler = ngx_http_ts_read_event_handler; + } +} + + +static void +ngx_http_ts_read_event_handler(ngx_http_request_t *r) +{ + ngx_int_t rc; + ngx_http_ts_ctx_t *ctx; + ngx_http_request_body_t *rb; + + if (ngx_exiting || ngx_terminate) { + ngx_http_finalize_request(r, NGX_HTTP_CLOSE); + return; + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_ts_module); + + rb = r->request_body; + + for ( ;; ) { + rc = ngx_http_read_unbuffered_request_body(r); + + if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { + ngx_http_finalize_request(r, rc); + return; + } + + if (rb->bufs == NULL) { + return; + } + + if (ngx_ts_read(ctx->ts, rb->bufs) != NGX_OK) { + ngx_http_finalize_request(r, NGX_ERROR); + return; + } + + if (rc == NGX_OK) { + ngx_http_finalize_request(r, NGX_HTTP_NO_CONTENT); + return; + } + + rb->bufs = NULL; + } +} + + +static char * +ngx_http_ts(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_core_loc_conf_t *clcf; + + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + clcf->handler = ngx_http_ts_handler; + + return NGX_CONF_OK; +} + + +static void * +ngx_http_ts_create_conf(ngx_conf_t *cf) +{ + ngx_http_ts_loc_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_ts_loc_conf_t)); + if (conf == NULL) { + return NULL; + } + + conf->hls = NGX_CONF_UNSET_PTR; + conf->dash = NGX_CONF_UNSET_PTR; + + return conf; +} + + +static char * +ngx_http_ts_merge_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_http_ts_loc_conf_t *prev = parent; + ngx_http_ts_loc_conf_t *conf = child; + + ngx_conf_merge_ptr_value(conf->hls, prev->hls, NULL); + ngx_conf_merge_ptr_value(conf->dash, prev->dash, NULL); + + return NGX_CONF_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_stream_ts_module.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_stream_ts_module.c new file mode 100644 index 0000000000..aff1447477 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_stream_ts_module.c @@ -0,0 +1,243 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include + +#include "ngx_ts_stream.h" +#include "ngx_ts_hls.h" +#include "ngx_ts_dash.h" + + +typedef struct { + ngx_ts_hls_conf_t *hls; + ngx_ts_dash_conf_t *dash; +} ngx_stream_ts_srv_conf_t; + + +typedef struct { + ngx_ts_stream_t *ts; +} ngx_stream_ts_ctx_t; + + +static void ngx_stream_ts_handler(ngx_stream_session_t *s); +static char *ngx_stream_ts(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static void *ngx_stream_ts_create_conf(ngx_conf_t *cf); +static char *ngx_stream_ts_merge_conf(ngx_conf_t *cf, void *parent, + void *child); + + +static ngx_command_t ngx_stream_ts_commands[] = { + + { ngx_string("ts"), + NGX_STREAM_SRV_CONF|NGX_CONF_NOARGS, + ngx_stream_ts, + 0, + 0, + NULL }, + + { ngx_string("ts_hls"), + NGX_STREAM_SRV_CONF|NGX_CONF_1MORE, + ngx_ts_hls_set_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ts_srv_conf_t, hls), + NULL }, + + { ngx_string("ts_dash"), + NGX_STREAM_SRV_CONF|NGX_CONF_1MORE, + ngx_ts_dash_set_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ts_srv_conf_t, dash), + NULL }, + + ngx_null_command +}; + + +static ngx_stream_module_t ngx_stream_ts_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + ngx_stream_ts_create_conf, /* create server configuration */ + ngx_stream_ts_merge_conf /* merge server configuration */ +}; + + +ngx_module_t ngx_stream_ts_module = { + NGX_MODULE_V1, + &ngx_stream_ts_module_ctx, /* module context */ + ngx_stream_ts_commands, /* module directives */ + NGX_STREAM_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void +ngx_stream_ts_handler(ngx_stream_session_t *s); +{ + ngx_str_t name; + ngx_connection_t *c; + ngx_stream_ts_ctx_t *ctx; + ngx_stream_ts_srv_conf_t *tscf; + + c = s->connection; + + ctx = ngx_pcalloc(c->pool, sizeof(ngx_stream_ts_ctx_t)); + if (ctx == NULL) { + ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + ctx->ts = ngx_pcalloc(c->pool, sizeof(ngx_ts_stream_t)); + if (ctx->ts == NULL) { + ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + ctx->ts->pool = c->pool; + ctx->ts->log = c->log; + + /* XXX */ + ngx_ste_set(&name, "foo"); + + /* XXX detect streams with the same name, add shared zone */ + + tscf = ngx_stream_get_module_srv_conf(s, ngx_stream_ts_module); + + if (tscf->hls) { + if (ngx_ts_hls_create(tscf->hls, ctx->ts, &name) == NULL) { + ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + } + + if (tscf->dash) { + if (ngx_ts_dash_create(tscf->dash, ctx->ts, &name) == NULL) { + ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + } + + ngx_stream_set_ctx(r, ctx, ngx_stream_ts_module); + + c->write->handler = ngx_stream_ts_read_handler; + + ngx_stream_ts_read_handler(c->write); +} + + +static voi +ngx_stream_ts_read_handler(ngx_event_t *rev) +{ + ssize_t n; + ngx_buf_t b; + ngx_chain_t in; + ngx_connection_t *c; + ngx_stream_ts_ctx_t *ctx; + ngx_stream_session_t *s; + u_char buf[4096]; + + c = rev->data; + s = c->data; + + if (ngx_exiting || ngx_terminate) { + ngx_stream_finalize_request(s, NGX_STREAM_OK); + return; + } + + if (rev->timedout) { + ngx_stream_finalize_session(s, NGX_STREAM_OK); + return; + } + + ctx = ngx_stream_get_module_ctx(s, ngx_stream_ts_module); + + in.buf = &b; + in.next = NULL; + + ngx_memzero(&b, sizeof(ngx_buf_t)); + + while (rev->ready) { + n = c->recv(c, buf, sizeof(buf)); + + if (n == NGX_ERROR || n == 0) { + ngx_stream_finalize_session(s, NGX_STREAM_OK); + return; + } + + if (n == NGX_AGAIN) { + break; + } + + b.pos = buf; + b.last = buf + n; + + if (ngx_ts_read(ctx->ts, &in) != NGX_OK) { + ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + } + + ngx_add_timer(rev, 5000); /* XXX */ + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } +} + + +static char * +ngx_stream_ts(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_stream_core_srv_conf_t *cscf; + + cscf = ngx_stream_conf_get_module_srv_conf(cf, ngx_stream_core_module); + cscf->handler = ngx_stream_ts_handler; + + return NGX_CONF_OK; +} + + +static void * +ngx_stream_ts_create_conf(ngx_conf_t *cf) +{ + ngx_stream_ts_srv_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_ts_srv_conf_t)); + if (conf == NULL) { + return NULL; + } + + conf->hls = NGX_CONF_UNSET_PTR; + conf->dash = NGX_CONF_UNSET_PTR; + + return conf; +} + + +static char * +ngx_stream_ts_merge_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_stream_ts_srv_conf_t *prev = parent; + ngx_stream_ts_srv_conf_t *conf = child; + + ngx_conf_merge_ptr_value(conf->hls, prev->hls, NULL); + ngx_conf_merge_ptr_value(conf->dash, prev->dash, NULL); + + return NGX_CONF_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_aac.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_aac.c new file mode 100644 index 0000000000..b060694c61 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_aac.c @@ -0,0 +1,69 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include + +#include "ngx_ts_aac.h" + + +ngx_ts_aac_params_t * +ngx_ts_aac_decode_params(ngx_ts_stream_t *ts, u_char *adts, size_t adts_len) +{ + /* + * XXX + * https://wiki.multimedia.cx/index.php/ADTS + */ + + ngx_ts_aac_params_t *aac; + + static ngx_uint_t freq[] = { + 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, + 16000, 12000, 11025, 8000, 7350, 0, 0, 0 + }; + + if (adts_len < 7) { + return NULL; + } + + aac = ngx_pcalloc(ts->pool, sizeof(ngx_ts_aac_params_t)); + if (aac == NULL) { + return NULL; + } + + /* MPEG Version: 0 for MPEG-4, 1 for MPEG-2 */ + aac->version = (adts[1] & 0x08) ? 1 : 0; + + /* + * MPEG-4 Audio Object Types: + * 1: AAC Main + * 2: AAC LC (Low Complexity) + * 3: AAC SSR (Scalable Sample Rate) + * 4: AAC LTP (Long Term Prediction) + */ + aac->profile = (adts[2] >> 6) + 1; + + aac->freq_index = (adts[2] >> 2) & 0x0f; + + aac->freq = freq[aac->freq_index]; + if (aac->freq == 0) { + goto failed; + } + + aac->chan = ((adts[2] & 0x01) << 2) | (adts[3] >> 6); + + ngx_log_debug4(NGX_LOG_DEBUG_CORE, ts->log, 0, + "ts aac version:%ui, profile:%ui, freq:%ui, chan:%ui", + aac->version, aac->profile, aac->freq, aac->chan); + + return aac; + +failed: + + ngx_log_error(NGX_LOG_ERR, ts->log, 0, "failed to parse AAC parameters"); + + return NULL; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_aac.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_aac.h new file mode 100644 index 0000000000..4274021a42 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_aac.h @@ -0,0 +1,30 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include + +#include "ngx_ts_stream.h" + + +#ifndef _NGX_TS_AAC_H_INCLUDED_ +#define _NGX_TS_AAC_H_INCLUDED_ + + +typedef struct { + ngx_uint_t version; + ngx_uint_t profile; + ngx_uint_t freq_index; + ngx_uint_t freq; + ngx_uint_t chan; +} ngx_ts_aac_params_t; + + +ngx_ts_aac_params_t *ngx_ts_aac_decode_params(ngx_ts_stream_t *ts, u_char *adts, + size_t adts_len); + + +#endif /* _NGX_TS_AAC_H_INCLUDED_ */ diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_avc.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_avc.c new file mode 100644 index 0000000000..6d22eed903 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_avc.c @@ -0,0 +1,298 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include + +#include "ngx_ts_avc.h" + + +typedef struct { + u_char *pos; + u_char *last; + ngx_uint_t shift; + ngx_uint_t err; /* unsigned err:1; */ + const char *name; + ngx_log_t *log; +} ngx_ts_avc_reader_t; + + +static void ngx_ts_avc_init_reader(ngx_ts_avc_reader_t *br, u_char *buf, + size_t len, ngx_log_t *log); +static uint64_t ngx_ts_avc_read(ngx_ts_avc_reader_t *br, ngx_uint_t bits); +static uint64_t ngx_ts_avc_read_golomb(ngx_ts_avc_reader_t *br); + + +static void +ngx_ts_avc_init_reader(ngx_ts_avc_reader_t *br, u_char *buf, size_t len, + ngx_log_t *log) +{ + ngx_memzero(br, sizeof(ngx_ts_avc_reader_t)); + + br->pos = buf; + br->last = buf + len; + br->log = log; +} + + +static uint64_t +ngx_ts_avc_read(ngx_ts_avc_reader_t *br, ngx_uint_t bits) +{ + uint64_t v; + ngx_uint_t k, n; + + if (br->err) { + return 0; + } + + v = 0; + n = bits; + + while (n) { + if (br->pos == br->last) { + br->err = 1; + break; + } + + k = ngx_min(8 - br->shift, n); + + /* + * [-------------|||||||||--------------] + * br->shift k + */ + + v = (v << k) | (*br->pos & (0xff >> br->shift)) >> (8 - br->shift - k); + + n -= k; + br->shift += k; + + if (br->shift == 8) { + br->shift = 0; + br->pos++; + } + } + + ngx_log_debug3(NGX_LOG_DEBUG_CORE, br->log, 0, + "ts avc %s[%uL]:%uL", br->name, bits, v); + + return v; +} + + +static uint64_t +ngx_ts_avc_read_golomb(ngx_ts_avc_reader_t *br) +{ + /* + * ISO/IEC 14496-10:2004(E) + * 9.1 Parsing process for Exp-Golomb codes, p. 159 + */ + + uint64_t v; + ngx_uint_t n; + + if (br->err) { + return 0; + } + + n = 0; + + while (ngx_ts_avc_read(br, 1) == 0) { + if (br->err) { + return 0; + } + + n++; + } + + v = ((uint64_t) 1 << n) - 1 + ngx_ts_avc_read(br, n); + + ngx_log_debug2(NGX_LOG_DEBUG_CORE, br->log, 0, "ts avc %s:%uL", + br->name, v); + + return v; +} + + +ngx_ts_avc_params_t * +ngx_ts_avc_decode_params(ngx_ts_stream_t *ts, u_char *sps, size_t sps_len, + u_char *pps, size_t pps_len) +{ + /* + * ISO/IEC 14496-10:2004(E) + * 7.3.2.1 Sequence parameter set RBSP syntax, p. 31 + */ + + ngx_uint_t type, n, i; + ngx_ts_avc_reader_t br; + ngx_ts_avc_params_t *avc; + + /* ignore PPS so far */ + + avc = ngx_pcalloc(ts->pool, sizeof(ngx_ts_avc_params_t)); + if (avc == NULL) { + return NULL; + } + + ngx_ts_avc_init_reader(&br, sps, sps_len, ts->log); + + br.name = "nalu_type"; + type = ngx_ts_avc_read(&br, 8); + if ((type & 0x1f) != 7) { + goto failed; + } + + br.name = "profile_idc"; + avc->profile_idc = ngx_ts_avc_read(&br, 8); + + br.name = "constraints"; + avc->constraints = ngx_ts_avc_read(&br, 8); + + br.name = "level_idc"; + avc->level_idc = ngx_ts_avc_read(&br, 8); + + br.name = "seq_parameter_set_id"; + avc->sps_id = ngx_ts_avc_read_golomb(&br); + + if (avc->profile_idc == 100 + || avc->profile_idc == 110 + || avc->profile_idc == 122 + || avc->profile_idc == 244 + || avc->profile_idc == 44 + || avc->profile_idc == 83 + || avc->profile_idc == 86 + || avc->profile_idc == 118 + || avc->profile_idc == 128 + || avc->profile_idc == 138 + || avc->profile_idc == 139 + || avc->profile_idc == 134) + { + br.name = "chroma_format_idc"; + avc->chroma_format_idc = ngx_ts_avc_read_golomb(&br); + + if (avc->chroma_format_idc == 3) { + br.name = + "residual_colour_transform_flagseparate_colour_plane_flag"; + avc->residual_colour_transform_flagseparate_colour_plane_flag = + ngx_ts_avc_read(&br, 1); + } + + br.name = "bit_depth_luma_minus8"; + avc->bit_depth_luma = ngx_ts_avc_read_golomb(&br) + 8; + + br.name = "bit_depth_chroma_minus8"; + avc->bit_depth_chroma = ngx_ts_avc_read_golomb(&br) + 8; + + br.name = "qpprime_y_zero_transform_bypass_flag"; + avc->qpprime_y_zero_transform_bypass_flag = ngx_ts_avc_read(&br, 1); + + br.name = "seq_scaling_matrix_present_flag"; + avc->seq_scaling_matrix_present_flag = ngx_ts_avc_read(&br, 1); + + if (avc->seq_scaling_matrix_present_flag) { + n = (avc->chroma_format_idc != 3) ? 8 : 12; + + for (i = 0; i < n; i++) { + br.name = "seq_scaling_list_present_flag[i]"; + if (ngx_ts_avc_read(&br, 1)) { + goto failed; + } + } + } + } + + br.name = "log2_max_frame_num_minus4"; + avc->max_frame_num = (1 << (ngx_ts_avc_read_golomb(&br) + 4)); + + br.name = "pic_order_cnt_type"; + avc->pic_order_cnt_type = ngx_ts_avc_read_golomb(&br); + + if (avc->pic_order_cnt_type == 0) { + br.name = "log2_max_pic_order_cnt_lsb_minus4"; + avc->max_pic_order_cnt_lsb = (1 << (ngx_ts_avc_read_golomb(&br) + 4)); + + } else if (avc->pic_order_cnt_type == 1) { + br.name = "delta_pic_order_always_zero_flag"; + avc->delta_pic_order_always_zero_flag = ngx_ts_avc_read(&br, 1); + + br.name = "offset_for_non_ref_pic"; + avc->offset_for_non_ref_pic = ngx_ts_avc_read_golomb(&br); + + br.name = "offset_for_top_to_bottom_field"; + avc->offset_for_top_to_bottom_field = ngx_ts_avc_read_golomb(&br); + + br.name = "num_ref_frames_in_pic_order_cnt_cycle"; + n = ngx_ts_avc_read_golomb(&br); + + for (i = 0; i < n; i++) { + br.name = "offset_for_ref_frame[i]"; + (void) ngx_ts_avc_read_golomb(&br); + } + } + + br.name = "num_ref_frames"; + avc->num_ref_frames = ngx_ts_avc_read_golomb(&br); + + br.name = "gaps_in_frame_num_value_allowed_flag"; + avc->gaps_in_frame_num_value_allowed_flag = ngx_ts_avc_read(&br, 1); + + br.name = "pic_width_in_mbs_minus1"; + avc->pic_width_in_mbs = ngx_ts_avc_read_golomb(&br) + 1; + + br.name = "pic_height_in_map_units_minus1"; + avc->pic_height_in_map_units = ngx_ts_avc_read_golomb(&br) + 1; + + br.name = "frame_mbs_only_flag"; + avc->frame_mbs_only_flag = ngx_ts_avc_read(&br, 1); + + if (!avc->frame_mbs_only_flag) { + br.name = "mb_adaptive_frame_field_flag"; + avc->mb_adaptive_frame_field_flag = ngx_ts_avc_read(&br, 1); + } + + br.name = "direct_8x8_inference_flag"; + avc->direct_8x8_inference_flag = ngx_ts_avc_read(&br, 1); + + br.name = "frame_cropping_flag"; + avc->frame_cropping_flag = ngx_ts_avc_read(&br, 1); + + if (avc->frame_cropping_flag) { + br.name = "frame_crop_left_offset"; + avc->frame_crop_left_offset = ngx_ts_avc_read_golomb(&br); + + br.name = "frame_crop_right_offset"; + avc->frame_crop_right_offset = ngx_ts_avc_read_golomb(&br); + + br.name = "frame_crop_top_offset"; + avc->frame_crop_top_offset = ngx_ts_avc_read_golomb(&br); + + br.name = "frame_crop_bottom_offset"; + avc->frame_crop_bottom_offset = ngx_ts_avc_read_golomb(&br); + } + + if (br.err) { + goto failed; + } + + avc->width = + avc->pic_width_in_mbs * 16 + - (avc->frame_crop_left_offset + avc->frame_crop_right_offset) * 2; + + avc->height = + (2 - avc->frame_mbs_only_flag) * avc->pic_height_in_map_units * 16 + - (avc->frame_crop_top_offset + avc->frame_crop_bottom_offset) * 2; + + ngx_log_debug2(NGX_LOG_DEBUG_CORE, ts->log, 0, + "ts avc width:%ui, height:%ui", avc->width, avc->height); + + return avc; + +failed: + + ngx_log_error(NGX_LOG_ERR, ts->log, 0, + "failed to parse AVC parameters"); + + return NULL; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_avc.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_avc.h new file mode 100644 index 0000000000..e1fe42ee0a --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_avc.h @@ -0,0 +1,57 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include + +#include "ngx_ts_stream.h" + + +#ifndef _NGX_TS_AVC_H_INCLUDED_ +#define _NGX_TS_AVC_H_INCLUDED_ + + +typedef struct { + ngx_uint_t profile_idc; + ngx_uint_t constraints; + ngx_uint_t level_idc; + ngx_uint_t sps_id; + ngx_uint_t chroma_format_idc; + ngx_uint_t bit_depth_luma; + ngx_uint_t bit_depth_chroma; + ngx_uint_t max_frame_num; + ngx_uint_t pic_order_cnt_type; + ngx_uint_t max_pic_order_cnt_lsb; + ngx_int_t offset_for_non_ref_pic; + ngx_int_t offset_for_top_to_bottom_field; + ngx_uint_t num_ref_frames_in_pic_order_cnt_cycle; + ngx_uint_t num_ref_frames; + ngx_uint_t pic_width_in_mbs; + ngx_uint_t pic_height_in_map_units; + ngx_uint_t frame_crop_left_offset; + ngx_uint_t frame_crop_right_offset; + ngx_uint_t frame_crop_top_offset; + ngx_uint_t frame_crop_bottom_offset; + ngx_uint_t width; + ngx_uint_t height; + + unsigned delta_pic_order_always_zero_flag:1; + unsigned gaps_in_frame_num_value_allowed_flag:1; + unsigned frame_mbs_only_flag:1; + unsigned mb_adaptive_frame_field_flag:1; + unsigned direct_8x8_inference_flag:1; + unsigned frame_cropping_flag:1; + unsigned residual_colour_transform_flagseparate_colour_plane_flag:1; + unsigned qpprime_y_zero_transform_bypass_flag:1; + unsigned seq_scaling_matrix_present_flag:1; +} ngx_ts_avc_params_t; + + +ngx_ts_avc_params_t *ngx_ts_avc_decode_params(ngx_ts_stream_t *ts, u_char *sps, + size_t sps_len, u_char *pps, size_t pps_len); + + +#endif /* _NGX_TS_AVC_H_INCLUDED_ */ diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_dash.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_dash.c new file mode 100644 index 0000000000..de8b55d247 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_dash.c @@ -0,0 +1,1426 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include + +#include "ngx_ts_dash.h" + + +#define NGX_TS_DASH_DATETIME_LEN sizeof("2000-12-31T23:59:59Z") +#define NGX_TS_DASH_CODEC_LEN sizeof("avc1.PPPCCCLLL") + + +static void ngx_ts_dash_cleanup(void *data); +static ngx_int_t ngx_ts_dash_handler(ngx_ts_handler_data_t *hd); +static ngx_int_t ngx_ts_dash_pmt_handler(ngx_ts_dash_t *dash); +static ngx_int_t ngx_ts_dash_pes_handler(ngx_ts_dash_t *dash, + ngx_ts_program_t *prog, ngx_ts_es_t *es, ngx_chain_t *in); +static void ngx_ts_dash_update_bandwidth(ngx_ts_dash_t *dash, + ngx_ts_dash_rep_t *rep, ngx_chain_t *in, uint64_t dts); +static ngx_int_t ngx_ts_dash_copy_avc(ngx_ts_dash_t *dash, + ngx_ts_dash_rep_t *rep, ngx_chain_t *in); +static ngx_int_t ngx_ts_dash_copy_aac(ngx_ts_dash_t *dash, + ngx_ts_dash_rep_t *rep, ngx_chain_t *in); +static ngx_int_t ngx_ts_dash_copy_default(ngx_ts_dash_t *dash, + ngx_ts_dash_rep_t *rep, ngx_chain_t *in); +static ngx_int_t ngx_ts_dash_close_segment(ngx_ts_dash_t *dash, + ngx_ts_dash_rep_t *rep); +static ngx_int_t ngx_ts_dash_update_playlist(ngx_ts_dash_t *dash); +static ngx_int_t ngx_ts_dash_write_file(u_char *path1, u_char *path2, + u_char *data, size_t len, ngx_log_t *log); +static void ngx_ts_dash_format_datetime(u_char *p, time_t t); +static void ngx_ts_dash_format_codec(u_char *p, ngx_ts_dash_rep_t *rep); +static ngx_int_t ngx_ts_dash_update_init_segments(ngx_ts_dash_t *dash); +static ngx_int_t ngx_ts_dash_open_segment(ngx_ts_dash_t *dash, + ngx_ts_dash_rep_t *rep); + +static ngx_msec_t ngx_ts_dash_file_manager(void *data); +static ngx_int_t ngx_ts_dash_manage_file(ngx_tree_ctx_t *ctx, ngx_str_t *path); +static ngx_int_t ngx_ts_dash_manage_directory(ngx_tree_ctx_t *ctx, + ngx_str_t *path); +static ngx_int_t ngx_ts_dash_delete_directory(ngx_tree_ctx_t *ctx, + ngx_str_t *path); +static ngx_int_t ngx_ts_dash_delete_file(ngx_tree_ctx_t *ctx, ngx_str_t *path); + + +ngx_ts_dash_t * +ngx_ts_dash_create(ngx_ts_dash_conf_t *conf, ngx_ts_stream_t *ts, + ngx_str_t *name) +{ + size_t len; + ngx_ts_dash_t *dash; + ngx_pool_cleanup_t *cln; + + ngx_log_debug0(NGX_LOG_DEBUG_CORE, ts->log, 0, "ts dash create"); + + dash = ngx_pcalloc(ts->pool, sizeof(ngx_ts_dash_t)); + if (dash == NULL) { + return NULL; + } + + dash->conf = conf; + dash->ts = ts; + dash->playlist_len = 128; + dash->init_seg_len = 256; + + dash->path.len = conf->path->name.len + 1 + name->len; + dash->path.data = ngx_pnalloc(ts->pool, dash->path.len + 1); + if (dash->path.data == NULL) { + return NULL; + } + + ngx_sprintf(dash->path.data, "%V/%V%Z", &conf->path->name, name); + + /* index.mpd */ + + len = dash->path.len + sizeof("/index.mpd"); + + dash->mpd_path = ngx_pnalloc(ts->pool, len); + if (dash->mpd_path == NULL) { + return NULL; + } + + ngx_sprintf(dash->mpd_path, "%V/index.mpd%Z", &dash->path); + + /* index.mpd.tmp */ + + len += sizeof(".tmp") - 1; + + dash->mpd_tmp_path = ngx_pnalloc(ts->pool, len); + if (dash->mpd_tmp_path == NULL) { + return NULL; + } + + ngx_sprintf(dash->mpd_tmp_path, "%s.tmp%Z", dash->mpd_path); + + cln = ngx_pool_cleanup_add(ts->pool, 0); + if (cln == NULL) { + return NULL; + } + + cln->handler = ngx_ts_dash_cleanup; + cln->data = dash; + + if (ngx_ts_add_handler(ts, ngx_ts_dash_handler, dash) != NGX_OK) { + return NULL; + } + + return dash; +} + + +static void +ngx_ts_dash_cleanup(void *data) +{ + ngx_ts_dash_t *dash = data; + + ngx_uint_t i, j; + ngx_ts_dash_set_t *set; + ngx_ts_dash_rep_t *rep; + + ngx_log_debug0(NGX_LOG_DEBUG_CORE, dash->ts->log, 0, "ts dash cleanup"); + + dash->flush = 1; + + for (i = 0; i < dash->nsets; i++) { + set = &dash->sets[i]; + + for (j = 0; j < set->nreps; j++) { + rep = &set->reps[j]; + + if (ngx_ts_dash_close_segment(dash, rep) != NGX_OK) { + return; + } + } + } +} + + +static ngx_int_t +ngx_ts_dash_handler(ngx_ts_handler_data_t *hd) +{ + ngx_ts_dash_t *dash = hd->data; + + switch (hd->event) { + + case NGX_TS_PMT: + return ngx_ts_dash_pmt_handler(dash); + + case NGX_TS_PES: + return ngx_ts_dash_pes_handler(dash, hd->prog, hd->es, hd->bufs); + + default: + return NGX_OK; + } +} + + +static ngx_int_t +ngx_ts_dash_pmt_handler(ngx_ts_dash_t *dash) +{ + size_t len; + ngx_uint_t i, j, n; + ngx_ts_es_t *es; + ngx_ts_stream_t *ts; + ngx_ts_program_t *prog; + ngx_ts_dash_rep_t *rep; + ngx_ts_dash_set_t *set, *aset, *vset; + + if (dash->sets) { + return NGX_OK; + } + + ts = dash->ts; + + ngx_log_debug0(NGX_LOG_DEBUG_CORE, ts->log, 0, "ts dash pmt"); + + n = 0; + + for (i = 0; i < ts->nprogs; i++) { + prog = &ts->progs[i]; + + if (prog->es == NULL) { + return NGX_OK; + } + + n += prog->nes; + } + + dash->nsets = 0; + dash->sets = ngx_pcalloc(ts->pool, 2 * sizeof(ngx_ts_dash_set_t)); + if (dash->sets == NULL) { + return NGX_ERROR; + } + + aset = NULL; + vset = NULL; + + for (i = 0; i < ts->nprogs; i++) { + prog = &ts->progs[i]; + + for (j = 0; j < prog->nes; j++) { + es = &prog->es[j]; + + switch (es->type) { + case NGX_TS_VIDEO_MPEG1: + case NGX_TS_VIDEO_MPEG2: + case NGX_TS_VIDEO_MPEG4: + case NGX_TS_VIDEO_AVC: + if (vset == NULL) { + vset = &dash->sets[dash->nsets++]; + vset->video = 1; + } + + set = vset; + break; + + case NGX_TS_AUDIO_MPEG1: + case NGX_TS_AUDIO_MPEG2: + case NGX_TS_AUDIO_AAC: + if (aset == NULL) { + aset = &dash->sets[dash->nsets++]; + } + + set = aset; + break; + + default: + continue; + } + + if (set->reps == NULL) { + set->nreps = 0; + set->reps = ngx_pcalloc(ts->pool, + n * sizeof(ngx_ts_dash_rep_t)); + if (set->reps == NULL) { + return NGX_ERROR; + } + } + + rep = &set->reps[set->nreps++]; + + rep->es = es; + + rep->nsegs = dash->conf->nsegs; + rep->segs = ngx_pcalloc(ts->pool, + rep->nsegs * sizeof(ngx_ts_dash_segment_t)); + if (rep->segs == NULL) { + return NGX_ERROR; + } + + len = dash->path.len + 1 + NGX_INT_T_LEN + 1 + NGX_INT64_LEN + + sizeof(".mp4"); + + rep->path.data = ngx_pnalloc(ts->pool, len); + if (rep->path.data == NULL) { + return NGX_ERROR; + } + + rep->path.len = ngx_sprintf(rep->path.data, "%V/%ui.", + &dash->path, (ngx_uint_t) es->pid) + - rep->path.data; + + /* init.mp4 */ + + len = dash->path.len + 1 + NGX_INT_T_LEN + sizeof(".init.mp4"); + + rep->init_path = ngx_pnalloc(ts->pool, len); + if (rep->init_path == NULL) { + return NGX_ERROR; + } + + ngx_sprintf(rep->init_path, "%V/%ui.init.mp4%Z", + &dash->path, (ngx_uint_t) es->pid); + + /* init.mp4.tmp */ + + len += sizeof(".tmp") - 1; + + rep->init_tmp_path = ngx_pnalloc(ts->pool, len); + if (rep->init_tmp_path == NULL) { + return NGX_ERROR; + } + + ngx_sprintf(rep->init_tmp_path, "%s.tmp%Z", rep->init_path); + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_ts_dash_pes_handler(ngx_ts_dash_t *dash, ngx_ts_program_t *prog, + ngx_ts_es_t *es, ngx_chain_t *in) +{ + ngx_uint_t i, j; + ngx_ts_dash_set_t *set; + ngx_ts_dash_rep_t *rep; + + if (!es->ptsf) { + return NGX_OK; + } + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, dash->ts->log, 0, "ts dash pes pid:%ud", + (unsigned) es->pid); + + for (i = 0; i < dash->nsets; i++) { + set = &dash->sets[i]; + + for (j = 0; j < set->nreps; j++) { + rep = &set->reps[j]; + + if (rep->es == es) { + goto found; + } + } + } + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, dash->ts->log, 0, + "ts dash skip es pid:%ud", (unsigned) es->pid); + + return NGX_OK; + +found: + + ngx_ts_dash_update_bandwidth(dash, rep, in, es->dts); + + if (ngx_ts_dash_close_segment(dash, rep) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_ts_dash_open_segment(dash, rep) != NGX_OK) { + return NGX_ERROR; + } + + switch (es->type) { + case NGX_TS_VIDEO_AVC: + return ngx_ts_dash_copy_avc(dash, rep, in); + + case NGX_TS_AUDIO_AAC: + return ngx_ts_dash_copy_aac(dash, rep, in); +/* + case NGX_TS_AUDIO_MPEG1: + case NGX_TS_AUDIO_MPEG2: + return ngx_ts_dash_copy_mp3(dash, rep, in); +*/ + + default: + return ngx_ts_dash_copy_default(dash, rep, in); + } +} + + +static void +ngx_ts_dash_update_bandwidth(ngx_ts_dash_t *dash, ngx_ts_dash_rep_t *rep, + ngx_chain_t *in, uint64_t dts) +{ + int64_t d, analyze; + + if (rep->bandwidth) { + return; + } + + if (rep->bandwidth_bytes == 0) { + rep->bandwidth_dts = dts; + } + + while (in) { + rep->bandwidth_bytes += in->buf->last - in->buf->pos; + in = in->next; + } + + d = dts - rep->bandwidth_dts; + analyze = (int64_t) dash->conf->analyze * 90; + + if (d >= analyze) { + rep->bandwidth = rep->bandwidth_bytes * 8 * 90000 / d; + } + + ngx_log_debug2(NGX_LOG_DEBUG_CORE, dash->ts->log, 0, + "ts dash bandwidth:%ui, pid:%ud", + rep->bandwidth, (unsigned) rep->es->pid); +} + + +static ngx_int_t +ngx_ts_dash_copy_avc(ngx_ts_dash_t *dash, ngx_ts_dash_rep_t *rep, + ngx_chain_t *in) +{ + /* + * Source format: + * ISO/IEC 14496-10:2004(E) + * Annex B. Byte Stream Format, p. 216 + * + * Destination format: + * ISO/IEC 14496-15:2004(E) + * 5.3.4.2 Sample format, p. 15 + */ + + size_t n, size, len; + u_char *p, *s, *spec, c, type, buf[4]; + ngx_uint_t z; + ngx_chain_t *cl; + + if (in == NULL) { + return NGX_OK; + } + + p = in->buf->pos; + + size = 0; + + while (in) { + z = 0; + len = 0; + type = 0; + cl = in; + s = p; + + for ( ;; ) { + if (p == in->buf->last) { + in = in->next; + if (in == NULL) { + break; + } + + p = in->buf->pos; + continue; + } + + c = *p++; + + if (c == 0) { + z++; + continue; + } + + if (c == 1 && z >= 2) { + break; + } + + if (z >= 3) { + goto failed; + } + + if (len == 0) { + type = z ? 0 : (c & 0x1f); + } + + len += z + 1; + z = 0; + } + + if (len == 0) { + continue; + } + + if (type == 7 || type == 8) { + if ((type == 7 && rep->sps) || (type == 8 && rep->pps)) { + continue; + } + + if (len > 0xffff) { + goto failed; + } + + spec = ngx_pnalloc(dash->ts->pool, len); + if (spec == NULL) { + return NGX_ERROR; + } + + if (type == 7) { + rep->sps = spec; + rep->sps_len = len; + + } else if (type == 8) { + rep->pps = spec; + rep->pps_len = len; + } + + while (len) { + if (s == cl->buf->last) { + cl = cl->next; + s = cl->buf->pos; + } + + n = ngx_min((size_t) (cl->buf->last - s), len); + + spec = ngx_cpymem(spec, s, n); + + s += n; + len -= n; + } + + if (rep->sps && rep->pps) { + rep->avc = ngx_ts_avc_decode_params(dash->ts, + rep->sps, rep->sps_len, + rep->pps, rep->pps_len); + if (rep->avc == NULL) { + return NGX_ERROR; + } + } + + continue; + } + + buf[0] = len >> 24; + buf[1] = len >> 16; + buf[2] = len >> 8; + buf[3] = len; + + if (ngx_ts_dash_append_data(dash, rep, buf, 4) != NGX_OK) { + return NGX_ERROR; + } + + size += 4; + + while (len) { + if (s == cl->buf->last) { + cl = cl->next; + s = cl->buf->pos; + } + + n = ngx_min((size_t) (cl->buf->last - s), len); + + if (ngx_ts_dash_append_data(dash, rep, s, n) != NGX_OK) { + return NGX_ERROR; + } + + size += n; + s += n; + len -= n; + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_CORE, dash->ts->log, 0, + "ts dash AVC frame:%uz, dts:%uL", size, rep->es->dts); + + return ngx_ts_dash_append_meta(dash, rep, size, rep->es->dts); + +failed: + + ngx_log_error(NGX_LOG_ERR, dash->ts->log, 0, "invalid AVC frame"); + + return NGX_ERROR; + +} + + +static ngx_int_t +ngx_ts_dash_copy_aac(ngx_ts_dash_t *dash, ngx_ts_dash_rep_t *rep, + ngx_chain_t *in) +{ + /* + * XXX + * ISO/IEC CD 14496-3 Subpart 4: 1998 + * 1.1.2 Audio_Data_Transport_Stream frame, ADTS, p. 10 + */ + + size_t len, n; + u_char *p, adts[9]; + uint64_t dts; + ngx_uint_t i; + + if (in == NULL) { + return NGX_OK; + } + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, dash->ts->log, 0, "ts dash AAC ts:%uL", + rep->es->dts); + + i = 0; + + p = in->buf->pos; + + for ( ;; ) { + n = 0; + + /* protection_absent */ + adts[1] = 0; + + while (n < ((adts[1] & 0x01) ? 7 : 9 )) { + if (p == in->buf->last) { + in = in->next; + + if (in == NULL) { + if (n == 0) { + return NGX_OK; + } + + goto failed; + } + + p = in->buf->pos; + continue; + } + + adts[n++] = *p++; + } + + if (rep->aac == NULL) { + rep->aac = ngx_ts_aac_decode_params(dash->ts, adts, 7); + if (rep->aac == NULL) { + return NGX_ERROR; + } + } + + len = adts[3] & 0x03; + len = (len << 8) + adts[4]; + len = (len << 3) + (adts[5] >> 5); + + if (len < n) { + goto failed; + } + + len -= n; + + dts = rep->es->dts + (uint64_t) 90000 * 1024 * i++ / rep->aac->freq; + + ngx_log_debug3(NGX_LOG_DEBUG_CORE, dash->ts->log, 0, + "ts dash AAC adts:%uz, frame:%uz, ts:%uL", n, len, dts); + + if (ngx_ts_dash_append_meta(dash, rep, len, dts) != NGX_OK) { + return NGX_ERROR; + } + + while (len) { + if (p == in->buf->last) { + in = in->next; + if (in == NULL) { + goto failed; + } + + p = in->buf->pos; + } + + n = ngx_min((size_t) (in->buf->last - p), len); + + if (ngx_ts_dash_append_data(dash, rep, p, n) != NGX_OK) { + return NGX_ERROR; + } + + p += n; + len -= n; + } + } + +failed: + + ngx_log_error(NGX_LOG_ERR, dash->ts->log, 0, "invalid AAC ADTS frame"); + + return NGX_ERROR; +} + + +static ngx_int_t +ngx_ts_dash_copy_default(ngx_ts_dash_t *dash, ngx_ts_dash_rep_t *rep, + ngx_chain_t *in) +{ + size_t n, size; + + for (size = 0; in; in = in->next) { + n = in->buf->last - in->buf->pos; + + if (ngx_ts_dash_append_data(dash, rep, in->buf->pos, n) != NGX_OK) { + return NGX_ERROR; + } + + size += n; + } + + return ngx_ts_dash_append_meta(dash, rep, size, rep->es->dts); +} + + +static ngx_int_t +ngx_ts_dash_close_segment(ngx_ts_dash_t *dash, ngx_ts_dash_rep_t *rep) +{ + size_t max_size; + ssize_t n; + int64_t d, min_seg, max_seg; + ngx_err_t err; + ngx_str_t *path; + ngx_file_t file; + ngx_uint_t try; + ngx_chain_t *out; + ngx_ts_es_t *es; + ngx_ts_stream_t *ts; + ngx_ts_dash_segment_t *seg; + + if (rep->meta == NULL) { + return NGX_OK; + } + + ts = dash->ts; + + es = rep->es; + + d = es->dts - rep->seg_dts; + + min_seg = (int64_t) dash->conf->min_seg * 90; + max_seg = (int64_t) dash->conf->max_seg * 90; + max_size = dash->conf->max_size; + + if (!dash->flush) { + if (d < min_seg + || (d < max_seg && es->video && !es->rand)) + { + if (max_size == 0 || rep->nmeta + rep->ndata < max_size) { + return NGX_OK; + } + + ngx_log_error(NGX_LOG_WARN, ts->log, 0, + "closing DASH segment \"%V%uL.mp4\" on size limit", + &rep->path, rep->seg_dts); + } + } + + path = &rep->path; + + ngx_memzero(&file, sizeof(ngx_file_t)); + + file.log = ts->log; + file.name.data = path->data; + file.name.len = ngx_sprintf(path->data + path->len, "%uL.mp4%Z", + rep->seg_dts) + - path->data - 1; + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, ts->log, 0, + "ts dash close segment \"%s\"", file.name.data); + + for (try = 0; /* void */; try++) { + file.fd = ngx_open_file(path->data, + NGX_FILE_WRONLY, + NGX_FILE_TRUNCATE, + NGX_FILE_DEFAULT_ACCESS); + + if (file.fd != NGX_INVALID_FILE) { + break; + } + + err = ngx_errno; + + if (try || (err != NGX_ENOENT && err != NGX_ENOTDIR)) { + ngx_log_error(NGX_LOG_EMERG, ts->log, err, + ngx_open_file_n " \"%s\" failed", path->data); + return NGX_ERROR; + } + + /* XXX dir access mode */ + if (ngx_create_dir(dash->path.data, 0700) == NGX_FILE_ERROR) { + err = ngx_errno; + + if (err != NGX_EEXIST) { + ngx_log_error(NGX_LOG_CRIT, ts->log, err, + ngx_create_dir_n " \"%s\" failed", + dash->path.data); + return NGX_ERROR; + } + } + } + + out = ngx_ts_dash_end_segment(dash, rep); + + n = ngx_write_chain_to_file(&file, out, 0, ts->pool); + + if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ALERT, ts->log, ngx_errno, + ngx_close_file_n " \"%s\" failed", path->data); + } + + ngx_ts_dash_free_segment(dash, rep, out); + + if (n == NGX_ERROR) { + return NGX_ERROR; + } + + seg = &rep->segs[rep->seg++ % rep->nsegs]; + seg->start = rep->seg_dts; + seg->duration = d; + + if (ngx_ts_dash_update_playlist(dash) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_ts_dash_update_playlist(ngx_ts_dash_t *dash) +{ + u_char *p, *last, *data; + time_t now; + size_t len; + ngx_uint_t i, j, k, pid, bandwidth, min_update, min_buftime, + buf_depth; + ngx_ts_stream_t *ts; + ngx_ts_dash_set_t *set; + ngx_ts_dash_rep_t *rep; + ngx_ts_dash_segment_t *seg; + u_char codec[NGX_TS_DASH_CODEC_LEN]; + u_char avail_start_time[NGX_TS_DASH_DATETIME_LEN]; + u_char pub_time[NGX_TS_DASH_DATETIME_LEN]; + + if (ngx_ts_dash_update_init_segments(dash) != NGX_OK) { + return NGX_ERROR; + } + + ts = dash->ts; + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, ts->log, 0, + "ts dash update playlist \"%s\"", dash->mpd_path); + + now = ngx_time(); + + if (dash->availability_start == 0) { + dash->availability_start = now; + } + + ngx_ts_dash_format_datetime(avail_start_time, dash->availability_start); + ngx_ts_dash_format_datetime(pub_time, now); + + /* + * timeShiftBufferDepth + * ---------------------------------------- + * | | + * -----///////----------------*-----------------> now + * | | | + * ---------------- ----------------- + * liveDelay lastSegDuration + * = 2 * minBufferTime + * + */ + + min_update = dash->conf->min_seg / 1000; + min_buftime = dash->conf->min_seg / 1000; + buf_depth = 2 * min_buftime + dash->conf->max_seg / 1000 + 1; + + for ( ;; ) { + ngx_log_debug1(NGX_LOG_DEBUG_CORE, ts->log, 0, + "ts dash playlist len:%uz", dash->playlist_len); + + data = ngx_alloc(dash->playlist_len, ts->log); + if (data == NULL) { + return NGX_ERROR; + } + + p = data; + last = data + dash->playlist_len; + + p = ngx_slprintf(p, last, + "\n" + "\n" + " \n", + avail_start_time, pub_time, min_update, min_buftime, buf_depth); + + for (i = 0; i < dash->nsets; i++) { + set = &dash->sets[i]; + + p = ngx_slprintf(p, last, + " \n", + set->video ? "video" : "audio"); + + for (j = 0; j < set->nreps; j++) { + rep = &set->reps[j]; + + if (rep->bandwidth == 0) { + ngx_log_debug0(NGX_LOG_DEBUG_CORE, ts->log, 0, + "ts dash bandwidth not available"); + ngx_free(data); + return NGX_OK; + } + + pid = rep->es->pid; + bandwidth = rep->bandwidth; + + ngx_ts_dash_format_codec(codec, rep); + + p = ngx_slprintf(p, last, + " \n" + " \n" + " \n", + pid, codec, bandwidth); + + for (k = 0; k < rep->nsegs; k++) { + seg = &rep->segs[(rep->seg + k) % rep->nsegs]; + + if (seg->duration) { + p = ngx_slprintf(p, last, + " \n", + seg->start, seg->duration); + } + } + + p = ngx_slprintf(p, last, + " \n" + " \n" + " \n"); + } + + p = ngx_slprintf(p, last, + " \n"); + } + + p = ngx_slprintf(p, last, + " \n" + "\n"); + + if (p != last) { + break; + } + + ngx_free(data); + + dash->playlist_len *= 2; + } + + len = p - data; + + if (ngx_ts_dash_write_file(dash->mpd_tmp_path, dash->mpd_path, data, len, + ts->log) + != NGX_OK) + { + ngx_free(data); + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_ts_dash_write_file(u_char *path1, u_char *path2, u_char *data, size_t len, + ngx_log_t *log) +{ + ssize_t n; + ngx_fd_t fd; + ngx_err_t err; + + fd = ngx_open_file(path1, + NGX_FILE_WRONLY, + NGX_FILE_TRUNCATE, + NGX_FILE_DEFAULT_ACCESS); + + if (fd == NGX_INVALID_FILE) { + ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, + ngx_open_file_n " \"%s\" failed", path1); + return NGX_ERROR; + } + + n = ngx_write_fd(fd, data, len); + + err = errno; + + if (ngx_close_file(fd) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + ngx_close_file_n " \"%s\" failed", path1); + return NGX_ERROR; + } + + if (n < 0) { + ngx_log_error(NGX_LOG_ALERT, log, err, + ngx_write_fd_n " to \"%s\" failed", path1); + return NGX_ERROR; + } + + if ((size_t) n != len) { + ngx_log_error(NGX_LOG_ALERT, log, 0, + "incomplete write to \"%s\"", path1); + return NGX_ERROR; + } + + if (path2) { + if (ngx_rename_file(path1, path2) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + ngx_rename_file_n " \"%s\" to \"%s\" failed", + path1, path2); + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static void +ngx_ts_dash_format_datetime(u_char *p, time_t t) +{ + struct tm tm; + + ngx_libc_gmtime(t, &tm); + + if (strftime((char *) p, NGX_TS_DASH_DATETIME_LEN, "%Y-%m-%dT%H:%M:%SZ", + &tm) + == 0) + { + *p = 0; + } +} + + +static void +ngx_ts_dash_format_codec(u_char *p, ngx_ts_dash_rep_t *rep) +{ + ngx_uint_t oti; + + if (rep->avc) { + ngx_sprintf(p, "avc1.%02uXi%02uXi%02uXi%Z", + rep->avc->profile_idc, + rep->avc->constraints, + rep->avc->level_idc); + return; + } + + if (rep->aac) { + ngx_sprintf(p, "mp4a.40.%ui%Z", rep->aac->profile); + return; + } + + oti = ngx_ts_dash_get_oti(rep->es->type); + + ngx_sprintf(p, "mp4%c.%02uXi%Z", rep->es->video ? 'v' : 'a', oti); +} + + +static ngx_int_t +ngx_ts_dash_update_init_segments(ngx_ts_dash_t *dash) +{ + u_char *data; + ngx_buf_t out; + ngx_uint_t i, j; + ngx_ts_stream_t *ts; + ngx_ts_dash_set_t *set; + ngx_ts_dash_rep_t *rep; + + ts = dash->ts; + + for (i = 0; i < dash->nsets; i++) { + set = &dash->sets[i]; + + for (j = 0; j < set->nreps; j++) { + rep = &set->reps[j]; + + if (rep->bandwidth == 0) { + continue; + } + + for ( ;; ) { + ngx_log_debug2(NGX_LOG_DEBUG_CORE, ts->log, 0, + "ts dash write init segment \"%s\", buf:%uz", + rep->init_path, dash->init_seg_len); + + ngx_memzero(&out, sizeof(ngx_buf_t)); + + data = ngx_alloc(dash->init_seg_len, ts->log); + if (data == NULL) { + return NGX_ERROR; + } + + out.start = data; + out.pos = data; + out.last = data; + out.end = data + dash->init_seg_len; + + ngx_ts_dash_write_init_segment(&out, rep); + + if (out.last < out.end) { + break; + } + + ngx_free(data); + + dash->init_seg_len *= 2; + } + + if (ngx_ts_dash_write_file(rep->init_tmp_path, rep->init_path, + out.pos, out.last - out.pos, ts->log) + != NGX_OK) + { + ngx_free(data); + return NGX_ERROR; + } + + ngx_free(data); + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_ts_dash_open_segment(ngx_ts_dash_t *dash, ngx_ts_dash_rep_t *rep) +{ + ngx_ts_es_t *es; + + if (rep->meta) { + return NGX_OK; + } + + ngx_log_debug2(NGX_LOG_DEBUG_CORE, dash->ts->log, 0, + "ts dash open segment \"%V%uL.mp4\"", + &rep->path, rep->seg_dts); + + es = rep->es; + + rep->seg_pts = es->pts; + rep->seg_dts = es->dts; + + return ngx_ts_dash_start_segment(dash, rep); +} + + +static ngx_msec_t +ngx_ts_dash_file_manager(void *data) +{ + ngx_ts_dash_conf_t *dash = data; + + ngx_tree_ctx_t tree; + + ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, + "ts dash file manager"); + + tree.init_handler = NULL; + tree.file_handler = ngx_ts_dash_manage_file; + tree.pre_tree_handler = ngx_ts_dash_manage_directory; + tree.post_tree_handler = ngx_ts_dash_delete_directory; + tree.spec_handler = ngx_ts_dash_delete_file; + tree.data = dash; + tree.alloc = 0; + tree.log = ngx_cycle->log; + + (void) ngx_walk_tree(&tree, &dash->path->name); + + return dash->max_seg * dash->nsegs; +} + + +static ngx_int_t +ngx_ts_dash_manage_file(ngx_tree_ctx_t *ctx, ngx_str_t *path) +{ + ngx_ts_dash_conf_t *dash = ctx->data; + + time_t age, max_age; + + age = ngx_time() - ctx->mtime; + + max_age = 0; + + if (path->len >= 4 + && ngx_memcmp(path->data + path->len - 4, ".mpd", 4) == 0) + { + max_age = dash->max_seg * dash->nsegs / 1000; + } + + if (path->len >= 4 + && ngx_memcmp(path->data + path->len - 4, ".mp4", 4) == 0) + { + max_age = dash->max_seg * dash->nsegs / 500; + } + + if (path->len >= 4 + && ngx_memcmp(path->data + path->len - 4, ".tmp", 4) == 0) + { + max_age = 10; + } + + ngx_log_debug3(NGX_LOG_DEBUG_CORE, ctx->log, 0, + "ts dash file \"%s\", age:%T, max_age:%T", + path->data, age, max_age); + + if (age < max_age) { + return NGX_OK; + } + + return ngx_ts_dash_delete_file(ctx, path); +} + + +static ngx_int_t +ngx_ts_dash_manage_directory(ngx_tree_ctx_t *ctx, ngx_str_t *path) +{ + return NGX_OK; +} + + +static ngx_int_t +ngx_ts_dash_delete_directory(ngx_tree_ctx_t *ctx, ngx_str_t *path) +{ + ngx_log_debug1(NGX_LOG_DEBUG_CORE, ctx->log, 0, + "ts dash delete dir: \"%s\"", path->data); + + /* non-empty directory will not be removed anyway */ + + /* TODO count files instead */ + + (void) ngx_delete_dir(path->data); + + return NGX_OK; +} + + +static ngx_int_t +ngx_ts_dash_delete_file(ngx_tree_ctx_t *ctx, ngx_str_t *path) +{ + ngx_log_debug1(NGX_LOG_DEBUG_CORE, ctx->log, 0, + "ts dash file delete: \"%s\"", path->data); + + if (ngx_delete_file(path->data) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_CRIT, ctx->log, ngx_errno, + ngx_delete_file_n " \"%s\" failed", path->data); + } + + return NGX_OK; +} + + +char * +ngx_ts_dash_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + char *p = conf; + + ssize_t max_size; + ngx_str_t *value, s, ss, path; + ngx_int_t v; + ngx_uint_t i, nsegs, clean; + ngx_msec_t min_seg, max_seg, analyze; + ngx_ts_dash_conf_t *dash, **field; + + field = (ngx_ts_dash_conf_t **) (p + cmd->offset); + + if (*field != NGX_CONF_UNSET_PTR) { + return "is duplicate"; + } + + value = cf->args->elts; + + ngx_str_null(&path); + + min_seg = 5000; + max_seg = 10000; + analyze = 0; + max_size = 16 * 1024 * 1024; + nsegs = 6; + clean = 1; + + for (i = 1; i < cf->args->nelts; i++) { + + if (ngx_strncmp(value[i].data, "path=", 5) == 0) { + + path.len = value[i].len - 5; + path.data = value[i].data + 5; + + if (path.data[path.len - 1] == '/') { + path.len--; + } + + if (ngx_conf_full_name(cf->cycle, &path, 0) != NGX_OK) { + return NGX_CONF_ERROR; + } + + continue; + } + + if (ngx_strncmp(value[i].data, "segment=", 8) == 0) { + + s.len = value[i].len - 8; + s.data = value[i].data + 8; + + ss.data = (u_char *) ngx_strchr(s.data, ':'); + + if (ss.data) { + ss.len = s.data + s.len - ss.data - 1; + s.len = ss.data - s.data; + ss.data++; + } + + min_seg = ngx_parse_time(&s, 0); + if (min_seg == (ngx_msec_t) NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid segment duration value \"%V\"", + &value[i]); + return NGX_CONF_ERROR; + } + + if (ss.data) { + max_seg = ngx_parse_time(&ss, 0); + if (max_seg == (ngx_msec_t) NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid segment duration value \"%V\"", + &value[i]); + return NGX_CONF_ERROR; + } + + } else { + max_seg = min_seg * 2; + } + + continue; + } + + if (ngx_strncmp(value[i].data, "analyze=", 8) == 0) { + + s.len = value[i].len - 8; + s.data = value[i].data + 8; + + analyze = ngx_parse_time(&s, 0); + if (analyze == (ngx_msec_t) NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid analyze duration value \"%V\"", + &value[i]); + return NGX_CONF_ERROR; + } + + continue; + } + + if (ngx_strncmp(value[i].data, "max_size=", 9) == 0) { + + s.len = value[i].len - 9; + s.data = value[i].data + 9; + + max_size = ngx_parse_size(&s); + if (max_size == NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid max segment size value \"%V\"", + &value[i]); + return NGX_CONF_ERROR; + } + + continue; + } + + if (ngx_strncmp(value[i].data, "segments=", 9) == 0) { + + v = ngx_atoi(value[i].data + 9, value[i].len - 9); + if (v == NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid segments number value \"%V\"", + &value[i]); + return NGX_CONF_ERROR; + } + + nsegs = v; + + continue; + } + + if (ngx_strcmp(value[i].data, "noclean") == 0) { + clean = 0; + continue; + } + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\"", &value[i]); + return NGX_CONF_ERROR; + } + + if (path.len == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"%V\" must have \"path\" parameter", &cmd->name); + return NGX_CONF_ERROR; + } + + dash = ngx_pcalloc(cf->pool, sizeof(ngx_ts_dash_conf_t)); + if (dash == NULL) { + return NGX_CONF_ERROR; + } + + dash->path = ngx_pcalloc(cf->pool, sizeof(ngx_path_t)); + if (dash->path == NULL) { + return NGX_CONF_ERROR; + } + + dash->path->name = path; + + dash->min_seg = min_seg; + dash->max_seg = max_seg; + dash->analyze = analyze ? analyze : min_seg; + dash->max_size = max_size; + dash->nsegs = nsegs; + + if (clean) { + dash->path->manager = ngx_ts_dash_file_manager; + } + + dash->path->data = dash; + dash->path->conf_file = cf->conf_file->file.name.data; + dash->path->line = cf->conf_file->line; + + if (ngx_add_path(cf, &dash->path) != NGX_OK) { + return NGX_CONF_ERROR; + } + + *field = dash; + + return NGX_CONF_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_dash.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_dash.h new file mode 100644 index 0000000000..5a1a8f5849 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_dash.h @@ -0,0 +1,134 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include + +#include "ngx_ts_stream.h" +#include "ngx_ts_avc.h" +#include "ngx_ts_aac.h" + + +#ifndef _NGX_TS_DASH_H_INCLUDED_ +#define _NGX_TS_DASH_H_INCLUDED_ + + +typedef struct { + ngx_path_t *path; + ngx_msec_t min_seg; + ngx_msec_t max_seg; + ngx_msec_t analyze; + size_t max_size; + ngx_uint_t nsegs; +} ngx_ts_dash_conf_t; + + +typedef struct { + uint64_t start; + uint64_t duration; +} ngx_ts_dash_segment_t; + + +typedef struct { + u_char *dts; /* 64-bit */ + u_char *pts; /* 64-bit */ + u_char *seq; + u_char *duration; + u_char *sample_duration; + u_char *nsamples; + u_char *traf; + u_char *trun; + u_char *moof; + u_char *moof_mdat; + u_char *moof_data; + u_char *mdat; +} ngx_ts_dash_subs_t; + + +typedef struct { + ngx_ts_es_t *es; + + ngx_ts_dash_segment_t *segs; + ngx_uint_t nsegs; + ngx_uint_t seg; + uint64_t seg_pts; + uint64_t seg_dts; + uint64_t dts; + + ngx_uint_t bandwidth; + ngx_uint_t bandwidth_bytes; + uint64_t bandwidth_dts; + + u_char *sps; + u_char *pps; + size_t sps_len; + size_t pps_len; + + ngx_ts_avc_params_t *avc; + ngx_ts_aac_params_t *aac; + + ngx_str_t path; + u_char *init_path; + u_char *init_tmp_path; + + ngx_chain_t *meta; + ngx_chain_t *last_meta; + ngx_chain_t *data; + ngx_chain_t *last_data; + + ngx_uint_t nsamples; + ngx_uint_t nmeta; + ngx_uint_t ndata; + + ngx_ts_dash_subs_t subs; +} ngx_ts_dash_rep_t; + + +typedef struct { + ngx_ts_dash_rep_t *reps; + ngx_uint_t nreps; + ngx_uint_t video; /* unsigned video:1; */ +} ngx_ts_dash_set_t; + + +typedef struct { + ngx_ts_stream_t *ts; + ngx_ts_dash_conf_t *conf; + + u_char *mpd_path; + u_char *mpd_tmp_path; + ngx_str_t path; + size_t playlist_len; + size_t init_seg_len; + time_t availability_start; + + ngx_chain_t *free; + ngx_ts_dash_set_t *sets; + ngx_uint_t nsets; + + ngx_uint_t flush; /* unsigned flush:1; */ +} ngx_ts_dash_t; + + +ngx_ts_dash_t *ngx_ts_dash_create(ngx_ts_dash_conf_t *conf, ngx_ts_stream_t *ts, + ngx_str_t *name); +char *ngx_ts_dash_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); + +void ngx_ts_dash_write_init_segment(ngx_buf_t *b, ngx_ts_dash_rep_t *rep); + +ngx_int_t ngx_ts_dash_start_segment(ngx_ts_dash_t *dash, + ngx_ts_dash_rep_t *rep); +ngx_chain_t *ngx_ts_dash_end_segment(ngx_ts_dash_t *dash, + ngx_ts_dash_rep_t *rep); +void ngx_ts_dash_free_segment(ngx_ts_dash_t *dash, ngx_ts_dash_rep_t *rep, + ngx_chain_t *out); +ngx_int_t ngx_ts_dash_append_meta(ngx_ts_dash_t *dash, ngx_ts_dash_rep_t *rep, + size_t size, uint64_t dts); +ngx_int_t ngx_ts_dash_append_data(ngx_ts_dash_t *dash, ngx_ts_dash_rep_t *rep, + u_char *data, size_t len); + + +#endif /* _NGX_TS_DASH_H_INCLUDED_ */ diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_dash_mp4.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_dash_mp4.c new file mode 100644 index 0000000000..6ed34faa4f --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_dash_mp4.c @@ -0,0 +1,1486 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include + +#include "ngx_ts_dash.h" + + +#define NGX_TS_DASH_BUFFER_SIZE 1024 + +#define NGX_TS_DASH_DEFAULT_WIDTH 400 +#define NGX_TS_DASH_DEFAULT_HEIGHT 400 + + +/* + * ISO base media file format + * ISO/IEC 14496-12:2008(E) + */ + + +static void ngx_ts_dash_box_styp(ngx_buf_t *b); +static void ngx_ts_dash_box_sidx(ngx_buf_t *b, ngx_ts_dash_rep_t *rep); +static void ngx_ts_dash_box_moof(ngx_buf_t *b, ngx_ts_dash_rep_t *rep); +static void ngx_ts_dash_box_mfhd(ngx_buf_t *b, ngx_ts_dash_rep_t *rep); +static void ngx_ts_dash_box_traf(ngx_buf_t *b, ngx_ts_dash_rep_t *rep); +static void ngx_ts_dash_box_tfhd(ngx_buf_t *b); +static void ngx_ts_dash_box_tfdt(ngx_buf_t *b, ngx_ts_dash_rep_t *rep); +static void ngx_ts_dash_box_trun(ngx_buf_t *b, ngx_ts_dash_rep_t *rep); +static void ngx_ts_dash_box_mdat(ngx_buf_t *b, ngx_ts_dash_rep_t *rep); +static void ngx_ts_dash_box_ftyp(ngx_buf_t *b); +static void ngx_ts_dash_box_moov(ngx_buf_t *b, ngx_ts_dash_rep_t *rep); +static void ngx_ts_dash_box_mvhd(ngx_buf_t *b); +static void ngx_ts_dash_box_mvex(ngx_buf_t *b); +static void ngx_ts_dash_box_trex(ngx_buf_t *b); +static void ngx_ts_dash_box_trak(ngx_buf_t *b, ngx_ts_dash_rep_t *rep); +static void ngx_ts_dash_box_tkhd(ngx_buf_t *b, ngx_ts_dash_rep_t *rep); +static void ngx_ts_dash_box_mdia(ngx_buf_t *b, ngx_ts_dash_rep_t *rep); +static void ngx_ts_dash_box_mdhd(ngx_buf_t *b); +static void ngx_ts_dash_box_hdlr(ngx_buf_t *b, ngx_ts_dash_rep_t *rep); +static void ngx_ts_dash_box_minf(ngx_buf_t *b, ngx_ts_dash_rep_t *rep); +static void ngx_ts_dash_box_vmhd(ngx_buf_t *b); +static void ngx_ts_dash_box_smhd(ngx_buf_t *b); +static void ngx_ts_dash_box_dinf(ngx_buf_t *b); +static void ngx_ts_dash_box_dref(ngx_buf_t *b); +static void ngx_ts_dash_box_url(ngx_buf_t *b); +static void ngx_ts_dash_box_stbl(ngx_buf_t *b, ngx_ts_dash_rep_t *rep); +static void ngx_ts_dash_box_stts(ngx_buf_t *b); +static void ngx_ts_dash_box_stsc(ngx_buf_t *b); +static void ngx_ts_dash_box_stsz(ngx_buf_t *b); +static void ngx_ts_dash_box_stco(ngx_buf_t *b); +static void ngx_ts_dash_box_stsd(ngx_buf_t *b, ngx_ts_dash_rep_t *rep); +static void ngx_ts_dash_box_video(ngx_buf_t *b, ngx_ts_dash_rep_t *rep); +static void ngx_ts_dash_box_audio(ngx_buf_t *b, ngx_ts_dash_rep_t *rep); +static void ngx_ts_dash_box_avcc(ngx_buf_t *b, ngx_ts_dash_rep_t *rep); +static void ngx_ts_dash_box_esds(ngx_buf_t *b, ngx_ts_dash_rep_t *rep); + +static u_char *ngx_ts_dash_box(ngx_buf_t *b, const char type[4]); +static u_char *ngx_ts_dash_box_full(ngx_buf_t *b, const char type[4], + u_char version, uint32_t flags); +static void ngx_ts_dash_box_update(ngx_buf_t *b, u_char *p); + +static void ngx_ts_dash_desc_es(ngx_buf_t *b, ngx_ts_dash_rep_t *rep); +static void ngx_ts_dash_desc_dec_conf(ngx_buf_t *b, ngx_ts_dash_rep_t *rep); +static void ngx_ts_dash_desc_dec_spec(ngx_buf_t *b, ngx_ts_dash_rep_t *rep); +static void ngx_ts_dash_desc_sl_conf(ngx_buf_t *b); + +static u_char *ngx_ts_dash_desc(ngx_buf_t *b, u_char tag); +static void ngx_ts_dash_desc_update(ngx_buf_t *b, u_char *p); + +static void ngx_ts_dash_cpymem(ngx_buf_t *b, const void *data, size_t size); +static void ngx_ts_dash_write64(ngx_buf_t *b, uint64_t v); +static void ngx_ts_dash_write32(ngx_buf_t *b, uint32_t v); +static void ngx_ts_dash_write24(ngx_buf_t *b, uint32_t v); +static void ngx_ts_dash_write16(ngx_buf_t *b, uint16_t v); +static void ngx_ts_dash_write8(ngx_buf_t *b, u_char v); + +static void ngx_ts_dash_set_sub(ngx_buf_t *b, u_char **p); +static uint32_t ngx_ts_dash_get32(u_char *p); +static void ngx_ts_dash_put32(u_char *p, uint32_t v); +static void ngx_ts_dash_put64(u_char *p, uint64_t v); + +static ngx_chain_t *ngx_ts_dash_get_buffer(ngx_ts_dash_t *dash); + + +void +ngx_ts_dash_write_init_segment(ngx_buf_t *b, ngx_ts_dash_rep_t *rep) +{ + ngx_ts_dash_box_ftyp(b); + ngx_ts_dash_box_moov(b, rep); +} + + +static void +ngx_ts_dash_box_styp(ngx_buf_t *b) +{ + /* TODO + * ETSI TS 126 244 V12.3.0 (2014-10) + * 13.2 Segment types, p. 52 + */ + + u_char *p; + + p = ngx_ts_dash_box(b, "styp"); + + /* major_brand */ + ngx_ts_dash_cpymem(b, "iso6", 4); /* XXX 3gh9 */ + + /* TODO version */ + /* minor_version */ + ngx_ts_dash_write32(b, 1); + + /* TODO brands */ + /* compatible_brands */ + ngx_ts_dash_cpymem(b, "isom", 4); + ngx_ts_dash_cpymem(b, "iso6", 4); + ngx_ts_dash_cpymem(b, "dash", 4); + + ngx_ts_dash_box_update(b, p); +} + + +static void +ngx_ts_dash_box_sidx(ngx_buf_t *b, ngx_ts_dash_rep_t *rep) +{ + /* TODO + * ETSI TS 126 244 V12.3.0 (2014-10) + * 13.4 Segment Index Box, p. 53 + */ + + u_char *p; + + p = ngx_ts_dash_box_full(b, "sidx", 1, 0); + + /* reference_ID */ + ngx_ts_dash_write32(b, 1); + + /* timescale */ + ngx_ts_dash_write32(b, 90000); + + /* earliest_presentation_time */ + ngx_ts_dash_set_sub(b, &rep->subs.pts); + ngx_ts_dash_write64(b, 0); + + /* first_offset */ + ngx_ts_dash_write64(b, 0); + + /* reference_count */ + ngx_ts_dash_write32(b, 1); + + /* referenced_size */ + ngx_ts_dash_set_sub(b, &rep->subs.moof_mdat); + ngx_ts_dash_write32(b, 0); + + /* subsegment_duration */ + ngx_ts_dash_set_sub(b, &rep->subs.duration); + ngx_ts_dash_write32(b, 0); + + /* starts_with_SAP, SAP_type, SAP_delta_time */ + ngx_ts_dash_write32(b, 0x80000000); + + ngx_ts_dash_box_update(b, p); +} + + +static void +ngx_ts_dash_box_moof(ngx_buf_t *b, ngx_ts_dash_rep_t *rep) +{ + u_char *p; + + ngx_ts_dash_set_sub(b, &rep->subs.moof); + + p = ngx_ts_dash_box(b, "moof"); + + ngx_ts_dash_box_mfhd(b, rep); + ngx_ts_dash_box_traf(b, rep); + + ngx_ts_dash_box_update(b, p); +} + + +static void +ngx_ts_dash_box_mfhd(ngx_buf_t *b, ngx_ts_dash_rep_t *rep) +{ + u_char *p; + + p = ngx_ts_dash_box_full(b, "mfhd", 0, 0); + + /* sequence_number */ + ngx_ts_dash_set_sub(b, &rep->subs.seq); + ngx_ts_dash_write32(b, 0); + + ngx_ts_dash_box_update(b, p); +} + + +static void +ngx_ts_dash_box_traf(ngx_buf_t *b, ngx_ts_dash_rep_t *rep) +{ + u_char *p; + + ngx_ts_dash_set_sub(b, &rep->subs.traf); + + p = ngx_ts_dash_box(b, "traf"); + + ngx_ts_dash_box_tfhd(b); + ngx_ts_dash_box_tfdt(b, rep); + ngx_ts_dash_box_trun(b, rep); + + ngx_ts_dash_box_update(b, p); +} + + +static void +ngx_ts_dash_box_tfhd(ngx_buf_t *b) +{ + u_char *p; + + p = ngx_ts_dash_box_full(b, "tfhd", 0, 0); + + /* track_ID */ + ngx_ts_dash_write32(b, 1); + + ngx_ts_dash_box_update(b, p); +} + + +static void +ngx_ts_dash_box_tfdt(ngx_buf_t *b, ngx_ts_dash_rep_t *rep) +{ + /* + * ETSI TS 126 244 V12.3.0 (2014-10) + * 13.5 Track Fragment Decode Time Box, p. 55 + */ + + u_char *p; + + p = ngx_ts_dash_box_full(b, "tfdt", 1, 0); + + /* baseMediaDecodeTime */ + ngx_ts_dash_set_sub(b, &rep->subs.dts); + ngx_ts_dash_write64(b, 0); + + ngx_ts_dash_box_update(b, p); +} + + +static void +ngx_ts_dash_box_trun(ngx_buf_t *b, ngx_ts_dash_rep_t *rep) +{ + u_char *p; + uint32_t flags; + + flags = 0x000001 /* data-offset-present */ + | 0x000100 /* sample-duration-present */ + | 0x000200; /* sample-size-present */ + + if (rep->es->video) { + flags |= 0x000400 /* sample-flags-present */ + | 0x000800; /* sample-composition-time-offset-present */ + } + + ngx_ts_dash_set_sub(b, &rep->subs.trun); + + p = ngx_ts_dash_box_full(b, "trun", 0, flags); + + /* sample_count */ + ngx_ts_dash_set_sub(b, &rep->subs.nsamples); + ngx_ts_dash_write32(b, 0); + + /* data_offset */ + ngx_ts_dash_set_sub(b, &rep->subs.moof_data); + ngx_ts_dash_write32(b, 0); + + ngx_ts_dash_box_update(b, p); +} + + +static void +ngx_ts_dash_box_mdat(ngx_buf_t *b, ngx_ts_dash_rep_t *rep) +{ + u_char *p; + + ngx_ts_dash_set_sub(b, &rep->subs.mdat); + + p = ngx_ts_dash_box(b, "mdat"); + + ngx_ts_dash_box_update(b, p); +} + + +static void +ngx_ts_dash_box_ftyp(ngx_buf_t *b) +{ + /* + * ISO/IEC 14496-12:2008(E) + * 4.3 File Type Box, p. 4 + */ + + u_char *p; + + p = ngx_ts_dash_box(b, "ftyp"); + + /* major_brand */ + ngx_ts_dash_cpymem(b, "iso6", 4); + + /* minor_version */ + ngx_ts_dash_write32(b, 1); + + /* TODO brands */ + /* compatible_brands */ + ngx_ts_dash_cpymem(b, "isom", 4); + ngx_ts_dash_cpymem(b, "iso6", 4); + ngx_ts_dash_cpymem(b, "dash", 4); + + ngx_ts_dash_box_update(b, p); +} + + +static void +ngx_ts_dash_box_moov(ngx_buf_t *b, ngx_ts_dash_rep_t *rep) +{ + u_char *p; + + p = ngx_ts_dash_box(b, "moov"); + + ngx_ts_dash_box_mvhd(b); + ngx_ts_dash_box_mvex(b); + ngx_ts_dash_box_trak(b, rep); + + ngx_ts_dash_box_update(b, p); +} + + +static void +ngx_ts_dash_box_mvhd(ngx_buf_t *b) +{ + u_char *p; + time_t now; + + p = ngx_ts_dash_box_full(b, "mvhd", 0, 0); + + now = ngx_time(); + + /* creation_time */ + ngx_ts_dash_write32(b, now); + + /* modification_time */ + ngx_ts_dash_write32(b, now); + + /* timescale */ + ngx_ts_dash_write32(b, 90000); + + /* duration */ + ngx_ts_dash_write32(b, 0); + + /* rate */ + ngx_ts_dash_write32(b, 0x00010000); + + /* volume */ + ngx_ts_dash_write32(b, 0x01000000); + + /* reserved */ + ngx_ts_dash_write64(b, 0); + + /* matrix */ + ngx_ts_dash_write32(b, 0x00010000); + ngx_ts_dash_write32(b, 0x00000000); + ngx_ts_dash_write32(b, 0x00000000); + ngx_ts_dash_write32(b, 0x00000000); + ngx_ts_dash_write32(b, 0x00010000); + ngx_ts_dash_write32(b, 0x00000000); + ngx_ts_dash_write32(b, 0x00000000); + ngx_ts_dash_write32(b, 0x00000000); + ngx_ts_dash_write32(b, 0x40000000); + + /* pre_defined */ + ngx_ts_dash_write32(b, 0); + ngx_ts_dash_write32(b, 0); + ngx_ts_dash_write32(b, 0); + ngx_ts_dash_write32(b, 0); + ngx_ts_dash_write32(b, 0); + ngx_ts_dash_write32(b, 0); + + /* next_track_ID */ + ngx_ts_dash_write32(b, 1); + + ngx_ts_dash_box_update(b, p); +} + + +static void +ngx_ts_dash_box_mvex(ngx_buf_t *b) +{ + u_char *p; + + p = ngx_ts_dash_box(b, "mvex"); + + ngx_ts_dash_box_trex(b); + + ngx_ts_dash_box_update(b, p); +} + + +static void +ngx_ts_dash_box_trex(ngx_buf_t *b) +{ + u_char *p; + + p = ngx_ts_dash_box_full(b, "trex", 0, 0); + + /* track_ID */ + ngx_ts_dash_write32(b, 1); + + /* default_sample_description_index */ + ngx_ts_dash_write32(b, 1); + + /* default_sample_duration */ + ngx_ts_dash_write32(b, 0); + + /* default_sample_size */ + ngx_ts_dash_write32(b, 0); + + /* default_sample_flags */ + ngx_ts_dash_write32(b, 0); + + ngx_ts_dash_box_update(b, p); +} + + +static void +ngx_ts_dash_box_trak(ngx_buf_t *b, ngx_ts_dash_rep_t *rep) +{ + u_char *p; + + p = ngx_ts_dash_box(b, "trak"); + + ngx_ts_dash_box_tkhd(b, rep); + ngx_ts_dash_box_mdia(b, rep); + + ngx_ts_dash_box_update(b, p); +} + + +static void +ngx_ts_dash_box_tkhd(ngx_buf_t *b, ngx_ts_dash_rep_t *rep) +{ + u_char *p; + time_t now; + + /* flags: Track_enabled (0x01), Track_in_movie (0x02) */ + p = ngx_ts_dash_box_full(b, "tkhd", 0, 0x03); + + now = ngx_time(); + + /* creation_time */ + ngx_ts_dash_write32(b, now); + + /* modification_time */ + ngx_ts_dash_write32(b, now); + + /* track_ID */ + ngx_ts_dash_write32(b, 1); + + /* reserved */ + ngx_ts_dash_write32(b, 0); + + /* duration */ + ngx_ts_dash_write32(b, 0xffffffff); + + /* reserved */ + ngx_ts_dash_write32(b, 0); + ngx_ts_dash_write32(b, 0); + + /* layer, alternate_group */ + ngx_ts_dash_write32(b, 0); + + /* volume */ + ngx_ts_dash_write32(b, rep->es->video ? 0x00000000 : 0x01000000); + + /* matrix */ + ngx_ts_dash_write32(b, 0x00010000); + ngx_ts_dash_write32(b, 0x00000000); + ngx_ts_dash_write32(b, 0x00000000); + ngx_ts_dash_write32(b, 0x00000000); + ngx_ts_dash_write32(b, 0x00010000); + ngx_ts_dash_write32(b, 0x00000000); + ngx_ts_dash_write32(b, 0x00000000); + ngx_ts_dash_write32(b, 0x00000000); + ngx_ts_dash_write32(b, 0x40000000); + + ngx_ts_dash_write32(b, (rep->avc ? rep->avc->width + : NGX_TS_DASH_DEFAULT_WIDTH) << 16); + + ngx_ts_dash_write32(b, (rep->avc ? rep->avc->height + : NGX_TS_DASH_DEFAULT_HEIGHT) << 16); + + ngx_ts_dash_box_update(b, p); +} + + +static void +ngx_ts_dash_box_mdia(ngx_buf_t *b, ngx_ts_dash_rep_t *rep) +{ + u_char *p; + + p = ngx_ts_dash_box(b, "mdia"); + + ngx_ts_dash_box_mdhd(b); + ngx_ts_dash_box_hdlr(b, rep); + ngx_ts_dash_box_minf(b, rep); + + ngx_ts_dash_box_update(b, p); +} + + +static void +ngx_ts_dash_box_mdhd(ngx_buf_t *b) +{ + u_char *p; + + time_t now; + + p = ngx_ts_dash_box_full(b, "mdhd", 0, 0); + + now = ngx_time(); + + /* creation_time */ + ngx_ts_dash_write32(b, now); + + /* modification_time */ + ngx_ts_dash_write32(b, now); + + /* timescale */ + ngx_ts_dash_write32(b, 90000); + + /* duration */ + ngx_ts_dash_write32(b, 0); + + /* language: und */ + ngx_ts_dash_write32(b, 0x55c40000); + + ngx_ts_dash_box_update(b, p); +} + + +static void +ngx_ts_dash_box_hdlr(ngx_buf_t *b, ngx_ts_dash_rep_t *rep) +{ + u_char *p; + + p = ngx_ts_dash_box_full(b, "hdlr", 0, 0); + + /* pre_defined */ + ngx_ts_dash_write32(b, 0); + + /* handler_type */ + ngx_ts_dash_cpymem(b, rep->es->video ? "vide" : "soun", 4); + + /* reserved */ + ngx_ts_dash_write32(b, 0); + ngx_ts_dash_write32(b, 0); + ngx_ts_dash_write32(b, 0); + + /* name */ + ngx_ts_dash_cpymem(b, rep->es->video ? "video" : "audio", 6); + + ngx_ts_dash_box_update(b, p); +} + + +static void +ngx_ts_dash_box_minf(ngx_buf_t *b, ngx_ts_dash_rep_t *rep) +{ + u_char *p; + + p = ngx_ts_dash_box(b, "minf"); + + if (rep->es->video) { + ngx_ts_dash_box_vmhd(b); + + } else { + ngx_ts_dash_box_smhd(b); + } + + ngx_ts_dash_box_dinf(b); + ngx_ts_dash_box_stbl(b, rep); + + ngx_ts_dash_box_update(b, p); +} + + +static void +ngx_ts_dash_box_vmhd(ngx_buf_t *b) +{ + u_char *p; + + p = ngx_ts_dash_box_full(b, "vmhd", 0, 1); + + /* graphicsmode, opcolor */ + ngx_ts_dash_write32(b, 0); + ngx_ts_dash_write32(b, 0); + + ngx_ts_dash_box_update(b, p); +} + + +static void +ngx_ts_dash_box_smhd(ngx_buf_t *b) +{ + u_char *p; + + p = ngx_ts_dash_box_full(b, "smhd", 0, 0); + + /* balance, reserved */ + ngx_ts_dash_write32(b, 0); + + ngx_ts_dash_box_update(b, p); +} + + +static void +ngx_ts_dash_box_dinf(ngx_buf_t *b) +{ + u_char *p; + + p = ngx_ts_dash_box(b, "dinf"); + + ngx_ts_dash_box_dref(b); + + ngx_ts_dash_box_update(b, p); +} + + +static void +ngx_ts_dash_box_dref(ngx_buf_t *b) +{ + u_char *p; + + p = ngx_ts_dash_box_full(b, "dref", 0, 0); + + /* entry_count */ + ngx_ts_dash_write32(b, 1); + + ngx_ts_dash_box_url(b); + + ngx_ts_dash_box_update(b, p); +} + + +static void +ngx_ts_dash_box_url(ngx_buf_t *b) +{ + u_char *p; + + p = ngx_ts_dash_box_full(b, "url ", 0, 0x01); + + ngx_ts_dash_box_update(b, p); +} + + +static void +ngx_ts_dash_box_stbl(ngx_buf_t *b, ngx_ts_dash_rep_t *rep) +{ + u_char *p; + + p = ngx_ts_dash_box(b, "stbl"); + + ngx_ts_dash_box_stsd(b, rep); + ngx_ts_dash_box_stts(b); + ngx_ts_dash_box_stsc(b); + ngx_ts_dash_box_stsz(b); + ngx_ts_dash_box_stco(b); + + ngx_ts_dash_box_update(b, p); +} + + +static void +ngx_ts_dash_box_stts(ngx_buf_t *b) +{ + u_char *p; + + p = ngx_ts_dash_box_full(b, "stts", 0, 0); + + /* entry_count */ + ngx_ts_dash_write32(b, 0); + + ngx_ts_dash_box_update(b, p); +} + + +static void +ngx_ts_dash_box_stsc(ngx_buf_t *b) +{ + u_char *p; + + p = ngx_ts_dash_box_full(b, "stsc", 0, 0); + + /* entry_count */ + ngx_ts_dash_write32(b, 0); + + ngx_ts_dash_box_update(b, p); +} + + +static void +ngx_ts_dash_box_stsz(ngx_buf_t *b) +{ + u_char *p; + + p = ngx_ts_dash_box_full(b, "stsz", 0, 0); + + /* sample_size */ + ngx_ts_dash_write32(b, 0); + + /* sample_count */ + ngx_ts_dash_write32(b, 0); + + ngx_ts_dash_box_update(b, p); +} + + +static void +ngx_ts_dash_box_stco(ngx_buf_t *b) +{ + u_char *p; + + p = ngx_ts_dash_box_full(b, "stco", 0, 0); + + /* entry_count */ + ngx_ts_dash_write32(b, 0); + + ngx_ts_dash_box_update(b, p); +} + + +static void +ngx_ts_dash_box_stsd(ngx_buf_t *b, ngx_ts_dash_rep_t *rep) +{ + u_char *p; + + p = ngx_ts_dash_box_full(b, "stsd", 0, 0); + + /* entry_count */ + ngx_ts_dash_write32(b, 1); + + if (rep->es->video) { + ngx_ts_dash_box_video(b, rep); + + } else { + ngx_ts_dash_box_audio(b, rep); + } + + ngx_ts_dash_box_update(b, p); +} + + +static void +ngx_ts_dash_box_video(ngx_buf_t *b, ngx_ts_dash_rep_t *rep) +{ + u_char *p; + ngx_uint_t avc1; + + avc1 = (rep->es->type == NGX_TS_VIDEO_AVC); + + p = ngx_ts_dash_box(b, avc1 ? "avc1" : "mp4v"); + + /* reserved */ + ngx_ts_dash_write32(b, 0); + ngx_ts_dash_write16(b, 0); + + /* data_reference_index */ + ngx_ts_dash_write16(b, 1); + + /* pre_defined, reserved */ + ngx_ts_dash_write32(b, 0); + ngx_ts_dash_write32(b, 0); + ngx_ts_dash_write32(b, 0); + ngx_ts_dash_write32(b, 0); + + ngx_ts_dash_write16(b, rep->avc ? rep->avc->width + : NGX_TS_DASH_DEFAULT_WIDTH); + + ngx_ts_dash_write16(b, rep->avc ? rep->avc->height + : NGX_TS_DASH_DEFAULT_HEIGHT); + + /* horizresolution */ + ngx_ts_dash_write32(b, 0x00480000); + + /* vertresolution */ + ngx_ts_dash_write32(b, 0x00480000); + + /* reserved */ + ngx_ts_dash_write32(b, 0); + + /* frame_count */ + ngx_ts_dash_write16(b, 1); + + /* compressorname */ + ngx_ts_dash_write32(b, 0); + ngx_ts_dash_write32(b, 0); + ngx_ts_dash_write32(b, 0); + ngx_ts_dash_write32(b, 0); + ngx_ts_dash_write32(b, 0); + ngx_ts_dash_write32(b, 0); + ngx_ts_dash_write32(b, 0); + ngx_ts_dash_write32(b, 0); + + /* depth */ + ngx_ts_dash_write16(b, 0x0018); + + /* pre_defined */ + ngx_ts_dash_write16(b, 0xffff); + + if (avc1) { + ngx_ts_dash_box_avcc(b, rep); + + } else { + ngx_ts_dash_box_esds(b, rep); + } + + ngx_ts_dash_box_update(b, p); +} + + +static void +ngx_ts_dash_box_audio(ngx_buf_t *b, ngx_ts_dash_rep_t *rep) +{ + u_char *p; + + p = ngx_ts_dash_box(b, "mp4a"); + + /* reserved */ + ngx_ts_dash_write32(b, 0); + ngx_ts_dash_write16(b, 0); + + /* data_reference_index */ + ngx_ts_dash_write16(b, 1); + + /* reserved */ + ngx_ts_dash_write32(b, 0); + ngx_ts_dash_write32(b, 0); + + /* channel_count */ + ngx_ts_dash_write16(b, 2); + + /* samplesize */ + ngx_ts_dash_write16(b, 16); + + /* pre_defined, reserved */ + ngx_ts_dash_write32(b, 0); + + /* XXX samplerate */ + ngx_ts_dash_write32(b, 90000); + + ngx_ts_dash_box_esds(b, rep); + + ngx_ts_dash_box_update(b, p); +} + + +static void +ngx_ts_dash_box_avcc(ngx_buf_t *b, ngx_ts_dash_rep_t *rep) +{ + u_char *p, *sps, *pps; + size_t sps_len, pps_len; + + sps_len = rep->sps_len; + pps_len = rep->pps_len; + + if (sps_len < 4 || pps_len == 0) { + return; + } + + sps = rep->sps; + pps = rep->pps; + + p = ngx_ts_dash_box(b, "avcC"); + + /* + * ISO/IEC 14496-15:2004(E) + * 5.2.4.1 AVC decoder configuration record, p. 12 + */ + + /* configurationVersion */ + ngx_ts_dash_write8(b, 1); + + /* AVCProfileIndication, profile_compatibility, AVCLevelIndication */ + ngx_ts_dash_cpymem(b, &sps[1], 3); + + /* lengthSizeMinusOne (lengthSize = 4) */ + ngx_ts_dash_write8(b, 0xff); + + /* numOfSequenceParameterSets = 1 */ + ngx_ts_dash_write8(b, 0xe1); + + /* sequenceParameterSetLength */ + ngx_ts_dash_write16(b, sps_len); + + /* sequenceParameterSetNALUnit */ + ngx_ts_dash_cpymem(b, sps, sps_len); + + /* numOfPictureParameterSets */ + ngx_ts_dash_write8(b, 1); + + /* pictureParameterSetLength */ + ngx_ts_dash_write16(b, pps_len); + + /* pictureParameterSetNALUnit */ + ngx_ts_dash_cpymem(b, pps, pps_len); + + ngx_ts_dash_box_update(b, p); +} + + +static void +ngx_ts_dash_box_esds(ngx_buf_t *b, ngx_ts_dash_rep_t *rep) +{ + u_char *p; + + p = ngx_ts_dash_box_full(b, "esds", 0, 0); + + ngx_ts_dash_desc_es(b, rep); + + ngx_ts_dash_box_update(b, p); +} + + +static u_char * +ngx_ts_dash_box(ngx_buf_t *b, const char type[4]) +{ + /* + * class Box + * ISO/IEC 14496-12:2008(E) + * 4.2 Object Structure, p. 4 + */ + + u_char *p; + + p = b->last; + + /* size */ + ngx_ts_dash_write32(b, 0); + + /* type */ + ngx_ts_dash_cpymem(b, type, 4); + + return p; +} + + +static u_char * +ngx_ts_dash_box_full(ngx_buf_t *b, const char type[4], u_char version, + uint32_t flags) +{ + /* + * class Box + * ISO/IEC 14496-12:2008(E) + * 4.2 Object Structure, p. 4 + */ + + u_char *p; + + p = ngx_ts_dash_box(b, type); + + /* version */ + ngx_ts_dash_write8(b, version); + + /* flags */ + ngx_ts_dash_write24(b, flags); + + return p; +} + + +static void +ngx_ts_dash_box_update(ngx_buf_t *b, u_char *p) +{ + uint32_t size; + + if (p + 4 > b->end) { + return; + } + + size = b->last - p; + + *p++ = (u_char) (size >> 24); + *p++ = (u_char) (size >> 16); + *p++ = (u_char) (size >> 8); + *p++ = (u_char) size; +} + + +static void +ngx_ts_dash_desc_es(ngx_buf_t *b, ngx_ts_dash_rep_t *rep) +{ + /* + * ISO/IEC 14496-1:2001(E) + * 8.6.5 ES_Descriptor, p. 28 + */ + + u_char *p; + + p = ngx_ts_dash_desc(b, 0x03); + + /* ES_ID */ + ngx_ts_dash_write16(b, 1); + + /* flags */ + ngx_ts_dash_write8(b, 0); + + ngx_ts_dash_desc_dec_conf(b, rep); + ngx_ts_dash_desc_sl_conf(b); + + ngx_ts_dash_desc_update(b, p); +} + + +static void +ngx_ts_dash_desc_dec_conf(ngx_buf_t *b, ngx_ts_dash_rep_t *rep) +{ + /* + * ISO/IEC 14496-1:2001(E) + * 8.6.6 DecoderConfigDescriptor, p. 30 + */ + + u_char *p; + + p = ngx_ts_dash_desc(b, 0x04); + + /* objectTypeIndication */ + ngx_ts_dash_write8(b, ngx_ts_dash_get_oti(rep->es->type)); + + /* streamType, upStream, reserved */ + ngx_ts_dash_write8(b, (rep->es->video ? 0x04 : 0x05) << 2); + + /* bufferSizeDB */ + ngx_ts_dash_write24(b, 0); + + /* maxBitrate */ + ngx_ts_dash_write32(b, rep->bandwidth * 2); + + /* avgBitrate */ + ngx_ts_dash_write32(b, rep->bandwidth); + + ngx_ts_dash_desc_dec_spec(b, rep); + + ngx_ts_dash_desc_update(b, p); +} + + +static void +ngx_ts_dash_desc_dec_spec(ngx_buf_t *b, ngx_ts_dash_rep_t *rep) +{ + u_char *p; + + if (rep->aac == NULL) { + return; + } + + /* TODO + * AudioSpecificConfig + * https://wiki.multimedia.cx/index.php/MPEG-4_Audio#Audio_Specific_Config + */ + + p = ngx_ts_dash_desc(b, 0x05); + + ngx_ts_dash_write8(b, + (rep->aac->profile << 3) + (rep->aac->freq_index >> 1)); + ngx_ts_dash_write8(b, (rep->aac->freq_index << 7) + (rep->aac->chan << 3)); + + ngx_ts_dash_desc_update(b, p); +} + + +static void +ngx_ts_dash_desc_sl_conf(ngx_buf_t *b) +{ + /* + * ISO/IEC 14496-1:2001(E) + * 10.2.3 SL Packet Header Configuration, p. 227 + */ + + u_char *p; + + p = ngx_ts_dash_desc(b, 0x06); + + /* predefined */ + ngx_ts_dash_write8(b, 0x02); + + ngx_ts_dash_desc_update(b, p); +} + + +static u_char * +ngx_ts_dash_desc(ngx_buf_t *b, u_char tag) +{ + /* + * ISO/IEC 14496-1:2001(E) + * 8.2.2 BaseDescriptor, p. 16 + */ + + u_char *p; + + ngx_ts_dash_write8(b, tag); + + p = b->last; + + /* size */ + ngx_ts_dash_write32(b, 0); + + return p; +} + + +static void +ngx_ts_dash_desc_update(ngx_buf_t *b, u_char *p) +{ + uint32_t size; + + if (p + 4 > b->end) { + return; + } + + size = b->last - p - 4; + + *p++ = (size >> 21) | 0x80; + *p++ = (size >> 14) | 0x80; + *p++ = (size >> 7) | 0x80; + *p++ = size & 0x7f; +} + + +static void +ngx_ts_dash_cpymem(ngx_buf_t *b, const void *data, size_t size) +{ + size_t n; + + n = ngx_min((size_t) (b->end - b->last), size); + + b->last = ngx_cpymem(b->last, data, n); +} + + +static void +ngx_ts_dash_write64(ngx_buf_t *b, uint64_t v) +{ + ngx_int_t n; + + for (n = 56; n >= 0 && b->last < b->end; n -= 8) { + *b->last++ = (u_char) (v >> n); + } +} + + +static void +ngx_ts_dash_write32(ngx_buf_t *b, uint32_t v) +{ + ngx_int_t n; + + for (n = 24; n >= 0 && b->last < b->end; n -= 8) { + *b->last++ = (u_char) (v >> n); + } +} + + +static void +ngx_ts_dash_write24(ngx_buf_t *b, uint32_t v) +{ + ngx_int_t n; + + for (n = 16; n >= 0 && b->last < b->end; n -= 8) { + *b->last++ = (u_char) (v >> n); + } +} + + +static void +ngx_ts_dash_write16(ngx_buf_t *b, uint16_t v) +{ + ngx_int_t n; + + for (n = 8; n >= 0 && b->last < b->end; n -= 8) { + *b->last++ = (u_char) (v >> n); + } +} + + +static void +ngx_ts_dash_write8(ngx_buf_t *b, u_char v) +{ + if (b->last < b->end) { + *b->last++ = v; + } +} + + +ngx_int_t +ngx_ts_dash_start_segment(ngx_ts_dash_t *dash, ngx_ts_dash_rep_t *rep) +{ + ngx_buf_t *b; + + ngx_memzero(&rep->subs, sizeof(ngx_ts_dash_subs_t)); + + rep->nsamples = 0; + rep->nmeta = 0; + rep->ndata = 0; + + rep->meta = ngx_ts_dash_get_buffer(dash); + if (rep->meta == NULL) { + return NGX_ERROR; + } + + rep->last_meta = rep->meta; + + b = rep->meta->buf; + + ngx_ts_dash_box_styp(b); + ngx_ts_dash_box_sidx(b, rep); + ngx_ts_dash_box_moof(b, rep); + + if (b->last == b->end) { + return NGX_ERROR; + } + + rep->data = ngx_ts_dash_get_buffer(dash); + if (rep->data == NULL) { + return NGX_ERROR; + } + + rep->last_data = rep->data; + + b = rep->data->buf; + + ngx_ts_dash_box_mdat(b, rep); + + if (b->last == b->end) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_chain_t * +ngx_ts_dash_end_segment(ngx_ts_dash_t *dash, ngx_ts_dash_rep_t *rep) +{ + int32_t d; + uint32_t traf, trun, moof, mdat; + ngx_chain_t *out; + ngx_ts_dash_subs_t *subs; + + subs = &rep->subs; + + ngx_ts_dash_put64(subs->pts, rep->seg_pts); + ngx_ts_dash_put64(subs->dts, rep->seg_dts); + + ngx_ts_dash_put32(subs->seq, rep->seg); + ngx_ts_dash_put32(subs->nsamples, rep->nsamples); + ngx_ts_dash_put32(subs->duration, rep->es->dts - rep->seg_dts); + + d = rep->es->dts - rep->dts; + + if (subs->sample_duration && d > 0) { + ngx_ts_dash_put32(subs->sample_duration, d); + } + + traf = ngx_ts_dash_get32(subs->traf) + rep->nmeta; + ngx_ts_dash_put32(subs->traf, traf); + + trun = ngx_ts_dash_get32(subs->trun) + rep->nmeta; + ngx_ts_dash_put32(subs->trun, trun); + + moof = ngx_ts_dash_get32(subs->moof) + rep->nmeta; + ngx_ts_dash_put32(subs->moof, moof); + + mdat = ngx_ts_dash_get32(subs->mdat) + rep->ndata; + ngx_ts_dash_put32(subs->mdat, mdat); + + ngx_ts_dash_put32(subs->moof_mdat, moof + mdat); + ngx_ts_dash_put32(subs->moof_data, moof + 8); + + out = rep->meta; + rep->last_meta->next = rep->data; + + rep->meta = NULL; + rep->data = NULL; + rep->last_meta = NULL; + rep->last_data = NULL; + + return out; +} + + +static void +ngx_ts_dash_set_sub(ngx_buf_t *b, u_char **p) +{ + *p = b->last; +} + + +static uint32_t +ngx_ts_dash_get32(u_char *p) +{ + uint32_t v; + + v = *p++; + v = (v << 8) + *p++; + v = (v << 8) + *p++; + v = (v << 8) + *p; + + return v; +} + + +static void +ngx_ts_dash_put32(u_char *p, uint32_t v) +{ + *p++ = (u_char) (v >> 24); + *p++ = (u_char) (v >> 16); + *p++ = (u_char) (v >> 8); + *p++ = (u_char) v; +} + + +static void +ngx_ts_dash_put64(u_char *p, uint64_t v) +{ + *p++ = (u_char) (v >> 56); + *p++ = (u_char) (v >> 48); + *p++ = (u_char) (v >> 40); + *p++ = (u_char) (v >> 32); + *p++ = (u_char) (v >> 24); + *p++ = (u_char) (v >> 16); + *p++ = (u_char) (v >> 8); + *p++ = (u_char) v; +} + + +void +ngx_ts_dash_free_segment(ngx_ts_dash_t *dash, ngx_ts_dash_rep_t *rep, + ngx_chain_t *out) +{ + ngx_chain_t *cl; + + for (cl = out; cl->next; cl = cl->next); + + cl->next = dash->free; + dash->free = out; +} + + +ngx_int_t +ngx_ts_dash_append_meta(ngx_ts_dash_t *dash, ngx_ts_dash_rep_t *rep, + size_t size, uint64_t dts) +{ + size_t n; + ngx_buf_t *b; + ngx_chain_t *cl; + ngx_ts_es_t *es; + + es = rep->es; + + rep->ndata += size; + rep->nsamples++; + + n = es->video ? 16 : 8; + + cl = rep->last_meta; + b = cl->buf; + + if ((size_t) (b->end - b->last) < n) { + cl->next = ngx_ts_dash_get_buffer(dash); + if (cl->next == NULL) { + return NGX_ERROR; + } + + cl = cl->next; + rep->last_meta = cl; + b = cl->buf; + } + + rep->nmeta += n; + + /* sample_duration */ + if (rep->subs.sample_duration) { + ngx_ts_dash_put32(rep->subs.sample_duration, dts - rep->dts); + } + + ngx_ts_dash_set_sub(b, &rep->subs.sample_duration); + ngx_ts_dash_write32(b, 0); + + /* sample_size */ + ngx_ts_dash_write32(b, size); + + if (es->video) { + + /* + * ISO/IEC 14496-12:2008(E) + * 8.8.3 Track Extends Box, Sample flags, p. 44 + * sample_is_difference_sample for non-key sample + */ + + ngx_ts_dash_write32(b, es->rand ? 0x00000000 : 0x00010000); + + /* sample_composition_time_offset */ + ngx_ts_dash_write32(b, es->pts - es->dts); + } + + rep->dts = dts; + + return NGX_OK; +} + + +ngx_int_t +ngx_ts_dash_append_data(ngx_ts_dash_t *dash, ngx_ts_dash_rep_t *rep, + u_char *data, size_t len) +{ + size_t n; + ngx_chain_t *cl; + + cl = rep->last_data; + + while (len) { + if (cl->buf->last == cl->buf->end) { + cl->next = ngx_ts_dash_get_buffer(dash); + if (cl->next == NULL) { + return NGX_ERROR; + } + + cl = cl->next; + rep->last_data = cl; + } + + n = ngx_min((size_t) (cl->buf->end - cl->buf->last), len); + + cl->buf->last = ngx_cpymem(cl->buf->last, data, n); + + data += n; + len -= n; + } + + return NGX_OK; +} + + +static ngx_chain_t * +ngx_ts_dash_get_buffer(ngx_ts_dash_t *dash) +{ + ngx_buf_t *b; + ngx_chain_t *out; + + if (dash->free) { + out = dash->free; + dash->free = out->next; + out->next = NULL; + b = out->buf; + + } else { + out = ngx_alloc_chain_link(dash->ts->pool); + if (out == NULL) { + return NULL; + } + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, dash->ts->log, 0, + "ts dash alloc buffer:%d", NGX_TS_DASH_BUFFER_SIZE); + + b = ngx_create_temp_buf(dash->ts->pool, NGX_TS_DASH_BUFFER_SIZE); + if (b == NULL) { + return NULL; + } + + out->buf = b; + out->next = NULL; + } + + b->pos = b->start; + b->last = b->start; + + return out; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_hls.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_hls.c new file mode 100644 index 0000000000..8ce47e7090 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_hls.c @@ -0,0 +1,1002 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include + +#include "ngx_ts_hls.h" + + +static void ngx_ts_hls_cleanup(void *data); +static ngx_int_t ngx_ts_hls_handler(ngx_ts_handler_data_t *hd); +static ngx_int_t ngx_ts_hls_pat_handler(ngx_ts_hls_t *hls); +static ngx_int_t ngx_ts_hls_pes_handler(ngx_ts_hls_t *hls, + ngx_ts_program_t *prog, ngx_ts_es_t *es, ngx_chain_t *bufs); +static void ngx_ts_hls_update_bandwidth(ngx_ts_hls_t *hls, + ngx_ts_hls_variant_t *var, ngx_chain_t *bufs, uint64_t dts); +static ngx_int_t ngx_ts_hls_close_segment(ngx_ts_hls_t *hls, + ngx_ts_hls_variant_t *var, ngx_ts_es_t *es); +static ngx_int_t ngx_ts_hls_update_playlist(ngx_ts_hls_t *hls, + ngx_ts_hls_variant_t *var); +static ngx_int_t ngx_ts_hls_update_master_playlist(ngx_ts_hls_t *hls); +static ngx_int_t ngx_ts_hls_write_file(u_char *path, u_char *tmp_path, + u_char *data, size_t len, ngx_log_t *log); +static ngx_int_t ngx_ts_hls_open_segment(ngx_ts_hls_t *hls, + ngx_ts_hls_variant_t *var); + +static ngx_msec_t ngx_ts_hls_file_manager(void *data); +static ngx_int_t ngx_ts_hls_manage_file(ngx_tree_ctx_t *ctx, ngx_str_t *path); +static ngx_int_t ngx_ts_hls_manage_directory(ngx_tree_ctx_t *ctx, + ngx_str_t *path); +static ngx_int_t ngx_ts_hls_delete_directory(ngx_tree_ctx_t *ctx, + ngx_str_t *path); +static ngx_int_t ngx_ts_hls_delete_file(ngx_tree_ctx_t *ctx, ngx_str_t *path); + + +ngx_ts_hls_t * +ngx_ts_hls_create(ngx_ts_hls_conf_t *conf, ngx_ts_stream_t *ts, ngx_str_t *name) +{ + ngx_ts_hls_t *hls; + ngx_pool_cleanup_t *cln; + + ngx_log_debug0(NGX_LOG_DEBUG_CORE, ts->log, 0, "ts hls create"); + + hls = ngx_pcalloc(ts->pool, sizeof(ngx_ts_hls_t)); + if (hls == NULL) { + return NULL; + } + + hls->conf = conf; + hls->ts = ts; + + hls->path.len = conf->path->name.len + 1 + name->len; + hls->path.data = ngx_pnalloc(ts->pool, hls->path.len + 1); + if (hls->path.data == NULL) { + return NULL; + } + + ngx_sprintf(hls->path.data, "%V/%V%Z", &conf->path->name, name); + + cln = ngx_pool_cleanup_add(ts->pool, 0); + if (cln == NULL) { + return NULL; + } + + cln->handler = ngx_ts_hls_cleanup; + cln->data = hls; + + if (ngx_ts_add_handler(ts, ngx_ts_hls_handler, hls) != NGX_OK) { + return NULL; + } + + return hls; +} + + +static void +ngx_ts_hls_cleanup(void *data) +{ + ngx_ts_hls_t *hls = data; + + int64_t d, maxd; + ngx_uint_t n, i; + ngx_ts_es_t *es; + ngx_ts_stream_t *ts; + ngx_ts_hls_segment_t *seg; + ngx_ts_hls_variant_t *var; + + hls->done = 1; + + ts = hls->ts; + + ngx_log_debug0(NGX_LOG_DEBUG_CORE, ts->log, 0, "ts hls cleanup"); + + for (n = 0; n < hls->nvars; n++) { + var = &hls->vars[n]; + + if (var->file.fd != NGX_INVALID_FILE) { + if (ngx_close_file(var->file.fd) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ALERT, ts->log, ngx_errno, + ngx_close_file_n " \"%s\" failed", + var->file.name.data); + } + + var->file.fd = NGX_INVALID_FILE; + } + + maxd = 0; + + for (i = 0; i < var->prog->nes; i++) { + es = &var->prog->es[i]; + + d = es->dts - var->seg_dts; + if (maxd < d) { + maxd = d; + } + } + + seg = &var->segs[var->seg % var->nsegs]; + seg->id = var->seg++; + seg->duration = maxd; + + (void) ngx_ts_hls_update_playlist(hls, var); + } +} + + +static ngx_int_t +ngx_ts_hls_handler(ngx_ts_handler_data_t *hd) +{ + ngx_ts_hls_t *hls = hd->data; + + switch (hd->event) { + + case NGX_TS_PAT: + return ngx_ts_hls_pat_handler(hls); + + case NGX_TS_PES: + return ngx_ts_hls_pes_handler(hls, hd->prog, hd->es, hd->bufs); + + default: + return NGX_OK; + } +} + + +static ngx_int_t +ngx_ts_hls_pat_handler(ngx_ts_hls_t *hls) +{ + size_t len; + u_char *p; + ngx_uint_t n; + ngx_ts_stream_t *ts; + ngx_ts_program_t *prog; + ngx_ts_hls_variant_t *var; + + if (hls->vars) { + return NGX_OK; + } + + ts = hls->ts; + + ngx_log_debug0(NGX_LOG_DEBUG_CORE, ts->log, 0, "ts hls pat"); + + hls->nvars = ts->nprogs; + hls->vars = ngx_pcalloc(ts->pool, + sizeof(ngx_ts_hls_variant_t) * ts->nprogs); + if (hls->vars == NULL) { + return NGX_ERROR; + } + + if (hls->nvars > 1) { + /* index.m3u8 */ + + len = hls->path.len + sizeof("/index.m3u8"); + + hls->m3u8_path = ngx_pnalloc(ts->pool, len); + if (hls->m3u8_path == NULL) { + return NGX_ERROR; + } + + ngx_sprintf(hls->m3u8_path, "%V/index.m3u8%Z", &hls->path); + + /* index.m3u8.tmp */ + + len += sizeof(".tmp") - 1; + + hls->m3u8_tmp_path = ngx_pnalloc(ts->pool, len); + if (hls->m3u8_tmp_path == NULL) { + return NGX_ERROR; + } + + ngx_sprintf(hls->m3u8_tmp_path, "%s.tmp%Z", hls->m3u8_path); + } + + for (n = 0; n < ts->nprogs; n++) { + prog = &ts->progs[n]; + var = &hls->vars[n]; + + var->prog = prog; + var->file.fd = NGX_INVALID_FILE; + var->file.log = ts->log; + + var->nsegs = hls->conf->nsegs; + var->segs = ngx_pcalloc(ts->pool, + sizeof(ngx_ts_hls_segment_t) * hls->conf->nsegs); + if (var->segs == NULL) { + return NGX_ERROR; + } + + /* [.].ts */ + + len = hls->path.len + 1 + NGX_INT_T_LEN + sizeof(".ts"); + + if (hls->nvars > 1) { + len += NGX_INT_T_LEN + 1; + } + + p = ngx_pnalloc(ts->pool, len); + if (p == NULL) { + return NGX_ERROR; + } + + var->path.data = p; + + if (hls->nvars > 1) { + p = ngx_sprintf(p, "%V/%ui.", + &hls->path, (ngx_uint_t) prog->number); + + } else { + p = ngx_sprintf(p, "%V/", &hls->path); + } + + var->path.len = p - var->path.data; + + /* (|index).m3u8 */ + + len = hls->path.len + 1 + sizeof(".m3u8"); + + if (hls->nvars > 1) { + len += NGX_INT_T_LEN; + + } else { + len += sizeof("index") - 1; + } + + var->m3u8_path = ngx_pnalloc(ts->pool, len); + if (var->m3u8_path == NULL) { + return NGX_ERROR; + } + + if (hls->nvars > 1) { + ngx_sprintf(var->m3u8_path, "%V/%ui.m3u8%Z", + &hls->path, (ngx_uint_t) prog->number); + + } else { + ngx_sprintf(var->m3u8_path, "%V/index.m3u8%Z", &hls->path); + } + + /* (|index).m3u8.tmp */ + + len += sizeof(".tmp") - 1; + + var->m3u8_tmp_path = ngx_pnalloc(ts->pool, len); + if (var->m3u8_tmp_path == NULL) { + return NGX_ERROR; + } + + ngx_sprintf(var->m3u8_tmp_path, "%s.tmp%Z", var->m3u8_path); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_ts_hls_pes_handler(ngx_ts_hls_t *hls, ngx_ts_program_t *prog, + ngx_ts_es_t *es, ngx_chain_t *bufs) +{ + ngx_uint_t n; + ngx_chain_t *out; + ngx_ts_stream_t *ts; + ngx_ts_hls_variant_t *var; + + if (!es->ptsf) { + return NGX_OK; + } + + ts = hls->ts; + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, ts->log, 0, "ts hls pes pid:%ud", + (unsigned) es->pid); + + for (n = 0; n < hls->nvars; n++) { + var = &hls->vars[n]; + if (prog == var->prog) { + goto found; + } + } + + ngx_log_error(NGX_LOG_ERR, ts->log, 0, "TS program not found"); + + return NGX_ERROR; + +found: + + ngx_ts_hls_update_bandwidth(hls, var, bufs, es->pts); + + if (ngx_ts_hls_close_segment(hls, var, es) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_ts_hls_open_segment(hls, var) != NGX_OK) { + return NGX_ERROR; + } + + out = ngx_ts_write_pes(ts, prog, es, bufs); + if (out == NULL) { + return NGX_ERROR; + } + + if (ngx_write_chain_to_file(&var->file, out, var->file.offset, ts->pool) + == NGX_ERROR) + { + return NGX_ERROR; + } + + ngx_ts_free_chain(ts, &out); + + return NGX_OK; +} + +static void +ngx_ts_hls_update_bandwidth(ngx_ts_hls_t *hls, ngx_ts_hls_variant_t *var, + ngx_chain_t *bufs, uint64_t dts) +{ + int64_t d, analyze; + + if (var->bandwidth) { + return; + } + + if (var->bandwidth_bytes == 0) { + var->bandwidth_dts = dts; + } + + while (bufs) { + var->bandwidth_bytes += bufs->buf->last - bufs->buf->pos; + bufs = bufs->next; + } + + d = dts - var->bandwidth_dts; + analyze = (int64_t) hls->conf->analyze * 90; + + if (d >= analyze) { + var->bandwidth = var->bandwidth_bytes * 8 * 90000 / d; + } + + ngx_log_debug2(NGX_LOG_DEBUG_CORE, hls->ts->log, 0, + "ts hls bandwidth:%ui, pid:%ud", + var->bandwidth, (unsigned) var->prog->pid); +} + + +static ngx_int_t +ngx_ts_hls_close_segment(ngx_ts_hls_t *hls, ngx_ts_hls_variant_t *var, + ngx_ts_es_t *es) +{ + off_t max_size; + int64_t d, min_seg, max_seg; + ngx_ts_stream_t *ts; + ngx_ts_hls_segment_t *seg; + + ts = hls->ts; + + if (var->file.fd == NGX_INVALID_FILE) { + var->seg_dts = es->dts; + return NGX_OK; + } + + d = es->dts - var->seg_dts; + + min_seg = (int64_t) hls->conf->min_seg * 90; + max_seg = (int64_t) hls->conf->max_seg * 90; + max_size = hls->conf->max_size; + + if (d < min_seg + || (d < max_seg && es->video && !es->rand) + || (d < max_seg && !es->video && var->prog->video)) + { + if (max_size == 0 || var->file.offset < max_size) { + return NGX_OK; + } + + ngx_log_error(NGX_LOG_WARN, ts->log, 0, + "closing HLS segment \"%s\" on size limit", + var->file.name.data); + } + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, ts->log, 0, + "ts hls close segment \"%s\"", var->file.name.data); + + var->seg_dts = es->dts; + + seg = &var->segs[var->seg % var->nsegs]; + seg->id = var->seg++; + seg->duration = d; + seg->size = var->file.offset; + + if (ngx_close_file(var->file.fd) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ALERT, ts->log, ngx_errno, + ngx_close_file_n " \"%s\" failed", var->file.name.data); + } + + ngx_memzero(&var->file, sizeof(ngx_file_t)); + + var->file.fd = NGX_INVALID_FILE; + var->file.log = ts->log; + + if (ngx_ts_hls_update_playlist(hls, var) != NGX_OK) { + return NGX_ERROR; + } + + return ngx_ts_hls_update_master_playlist(hls); +} + + +static ngx_int_t +ngx_ts_hls_update_playlist(ngx_ts_hls_t *hls, ngx_ts_hls_variant_t *var) +{ + size_t len; + u_char *p, *data; + uint64_t maxd; + ngx_int_t rc; + ngx_uint_t i, ms, td; + ngx_ts_stream_t *ts; + ngx_ts_hls_segment_t *seg; + + ts = hls->ts; + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, ts->log, 0, + "ts hls update playlist \"%s\"", var->m3u8_path); + + len = sizeof("#EXTM3U\n" + "#EXT-X-VERSION:3\n" + "#EXT-X-MEDIA-SEQUENCE:\n" + "#EXT-X-TARGETDURATION:\n\n") - 1 + + 2 * NGX_INT_T_LEN; + + maxd = 0; + + for (i = 0; i < var->nsegs; i++) { + seg = &var->segs[(var->seg + i) % var->nsegs]; + + if (seg->duration) { + if (maxd < seg->duration) { + maxd = seg->duration; + } + + len += sizeof("#EXTINF:.xxx,\n" + ".ts\n") - 1 + + 2 * NGX_INT_T_LEN; + + if (hls->nvars > 1) { + len += NGX_INT_T_LEN + 1; + } + } + } + + if (hls->done) { + len += sizeof("\n#EXT-X-ENDLIST\n") - 1; + } + + data = ngx_alloc(len, ts->log); + if (data == NULL) { + return NGX_ERROR; + } + + p = data; + + ms = var->seg <= var->nsegs ? 0 : var->seg - var->nsegs; + td = (hls->conf->max_seg + 999) / 1000; + + p = ngx_sprintf(p, "#EXTM3U\n" + "#EXT-X-VERSION:3\n" + "#EXT-X-MEDIA-SEQUENCE:%ui\n" + "#EXT-X-TARGETDURATION:%ui\n\n", ms, td); + + for (i = 0; i < var->nsegs; i++) { + seg = &var->segs[(var->seg + i) % var->nsegs]; + + if (seg->duration) { + p = ngx_sprintf(p, "#EXTINF:%.3f,\n", seg->duration / 90000.); + + if (hls->nvars > 1) { + p = ngx_sprintf(p, "%ui.%ui.ts\n", + (ngx_uint_t) var->prog->number, seg->id); + + } else { + p = ngx_sprintf(p, "%ui.ts\n", seg->id); + } + } + } + + if (hls->done) { + p = ngx_cpymem(p, "\n#EXT-X-ENDLIST\n", + sizeof("\n#EXT-X-ENDLIST\n") - 1); + } + + rc = ngx_ts_hls_write_file(var->m3u8_path, var->m3u8_tmp_path, data, + p - data, ts->log); + + ngx_free(data); + + return rc; +} + + +static ngx_int_t +ngx_ts_hls_update_master_playlist(ngx_ts_hls_t *hls) +{ + size_t len; + u_char *p, *data; + ngx_int_t rc; + ngx_uint_t n; + ngx_ts_stream_t *ts; + ngx_ts_hls_variant_t *var; + + /* TODO touch file if it exists*/ + + if (hls->nvars == 1) { + return NGX_OK; + } + + ts = hls->ts; + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, ts->log, 0, + "ts hls update master playlist \"%s\"", hls->m3u8_path); + + len = sizeof("#EXTM3U\n") - 1; + + for (n = 0; n < hls->nvars; n++) { + var = &hls->vars[n]; + + if (var->bandwidth == 0) { + ngx_log_debug0(NGX_LOG_DEBUG_CORE, ts->log, 0, + "ts hls bandwidth not available"); + return NGX_OK; + } + + len += sizeof("#EXT-X-STREAM-INF:BANDWIDTH=\n") - 1 + NGX_INT_T_LEN + + NGX_INT_T_LEN + sizeof(".m3u8\n") - 1; + } + + data = ngx_alloc(len, ts->log); + if (data == NULL) { + return NGX_ERROR; + } + + p = data; + + p = ngx_cpymem(p, "#EXTM3U\n", sizeof("#EXTM3U\n") - 1); + + for (n = 0; n < hls->nvars; n++) { + var = &hls->vars[n]; + + p = ngx_sprintf(p, "#EXT-X-STREAM-INF:BANDWIDTH=%ui\n%ui.m3u8\n", + var->bandwidth, (ngx_uint_t) var->prog->number); + } + + rc = ngx_ts_hls_write_file(hls->m3u8_path, hls->m3u8_tmp_path, data, + p - data, ts->log); + + ngx_free(data); + + return rc; +} + + +static ngx_int_t +ngx_ts_hls_write_file(u_char *path, u_char *tmp_path, u_char *data, size_t len, + ngx_log_t *log) +{ + ssize_t n; + ngx_fd_t fd; + ngx_err_t err; + + fd = ngx_open_file(tmp_path, + NGX_FILE_WRONLY, + NGX_FILE_TRUNCATE, + NGX_FILE_DEFAULT_ACCESS); + + if (fd == NGX_INVALID_FILE) { + ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, + ngx_open_file_n " \"%s\" failed", tmp_path); + return NGX_ERROR; + } + + n = ngx_write_fd(fd, data, len); + + err = ngx_errno; + + if (ngx_close_file(fd) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + ngx_close_file_n " \"%s\" failed", tmp_path); + return NGX_ERROR; + } + + if (n < 0) { + ngx_log_error(NGX_LOG_ALERT, log, err, + ngx_write_fd_n " to \"%s\" failed", tmp_path); + return NGX_ERROR; + } + + if ((size_t) n != len) { + ngx_log_error(NGX_LOG_ALERT, log, 0, + "incomplete write to \"%s\"", tmp_path); + return NGX_ERROR; + } + + if (ngx_rename_file(tmp_path, path) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + ngx_rename_file_n " \"%s\" to \"%s\" failed", + tmp_path, path); + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_ts_hls_open_segment(ngx_ts_hls_t *hls, ngx_ts_hls_variant_t *var) +{ + size_t n; + ngx_err_t err; + ngx_str_t *path; + ngx_uint_t try; + ngx_chain_t *out, **ll; + ngx_ts_stream_t *ts; + + if (var->file.fd != NGX_INVALID_FILE) { + return NGX_OK; + } + + ts = hls->ts; + + path = &var->path; + + n = ngx_sprintf(path->data + path->len, "%ui.ts%Z", var->seg) - path->data + - 1; + + var->file.name.data = path->data; + var->file.name.len = n; + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, ts->log, 0, + "ts hls open segment \"%s\"", var->file.name.data); + + for (try = 0; /* void */; try++) { + var->file.fd = ngx_open_file(path->data, + NGX_FILE_WRONLY, + NGX_FILE_TRUNCATE, + NGX_FILE_DEFAULT_ACCESS); + + if (var->file.fd != NGX_INVALID_FILE) { + break; + } + + err = ngx_errno; + + if (try || (err != NGX_ENOENT && err != NGX_ENOTDIR)) { + ngx_log_error(NGX_LOG_EMERG, ts->log, err, + ngx_open_file_n " \"%s\" failed", path->data); + return NGX_ERROR; + } + + /* XXX dir access mode */ + if (ngx_create_dir(hls->path.data, 0700) == NGX_FILE_ERROR) { + err = ngx_errno; + + if (err != NGX_EEXIST) { + ngx_log_error(NGX_LOG_CRIT, ts->log, err, + ngx_create_dir_n " \"%s\" failed", + hls->path.data); + return NGX_ERROR; + } + } + } + + if (var->prologue == NULL) { + out = ngx_ts_write_pat(ts, var->prog); + if (out == NULL) { + return NGX_ERROR; + } + + for (ll = &out; *ll; ll = &(*ll)->next); + + *ll = ngx_ts_write_pmt(ts, var->prog); + if (*ll == NULL) { + return NGX_ERROR; + } + + var->prologue = out; + } + + if (ngx_write_chain_to_file(&var->file, var->prologue, 0, ts->pool) + == NGX_ERROR) + { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_msec_t +ngx_ts_hls_file_manager(void *data) +{ + ngx_ts_hls_conf_t *hls = data; + + ngx_tree_ctx_t tree; + + ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, + "ts hls file manager"); + + tree.init_handler = NULL; + tree.file_handler = ngx_ts_hls_manage_file; + tree.pre_tree_handler = ngx_ts_hls_manage_directory; + tree.post_tree_handler = ngx_ts_hls_delete_directory; + tree.spec_handler = ngx_ts_hls_delete_file; + tree.data = hls; + tree.alloc = 0; + tree.log = ngx_cycle->log; + + (void) ngx_walk_tree(&tree, &hls->path->name); + + return hls->max_seg * hls->nsegs; +} + + +static ngx_int_t +ngx_ts_hls_manage_file(ngx_tree_ctx_t *ctx, ngx_str_t *path) +{ + ngx_ts_hls_conf_t *hls = ctx->data; + + time_t age, max_age; + + age = ngx_time() - ctx->mtime; + + max_age = 0; + + if (path->len >= 5 + && ngx_memcmp(path->data + path->len - 5, ".m3u8", 5) == 0) + { + max_age = hls->max_seg * hls->nsegs / 1000; + } + + if (path->len >= 3 + && ngx_memcmp(path->data + path->len - 3, ".ts", 3) == 0) + { + max_age = hls->max_seg * hls->nsegs / 500; + } + + if (path->len >= 4 + && ngx_memcmp(path->data + path->len - 4, ".tmp", 3) == 0) + { + max_age = 10; + } + + ngx_log_debug3(NGX_LOG_DEBUG_CORE, ctx->log, 0, + "ts hls file \"%s\", age:%T, max_age:%T", + path->data, age, max_age); + + if (age < max_age) { + return NGX_OK; + } + + return ngx_ts_hls_delete_file(ctx, path); +} + + +static ngx_int_t +ngx_ts_hls_manage_directory(ngx_tree_ctx_t *ctx, ngx_str_t *path) +{ + return NGX_OK; +} + + +static ngx_int_t +ngx_ts_hls_delete_directory(ngx_tree_ctx_t *ctx, ngx_str_t *path) +{ + ngx_log_debug1(NGX_LOG_DEBUG_CORE, ctx->log, 0, + "ts hls delete dir: \"%s\"", path->data); + + /* non-empty directory will not be removed anyway */ + + /* TODO count files instead */ + + (void) ngx_delete_dir(path->data); + + return NGX_OK; +} + + +static ngx_int_t +ngx_ts_hls_delete_file(ngx_tree_ctx_t *ctx, ngx_str_t *path) +{ + ngx_log_debug1(NGX_LOG_DEBUG_CORE, ctx->log, 0, + "ts hls file delete: \"%s\"", path->data); + + if (ngx_delete_file(path->data) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_CRIT, ctx->log, ngx_errno, + ngx_delete_file_n " \"%s\" failed", path->data); + } + + return NGX_OK; +} + + +char * +ngx_ts_hls_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + char *p = conf; + + ssize_t max_size; + ngx_str_t *value, s, ss, path; + ngx_int_t v; + ngx_uint_t i, nsegs, clean; + ngx_msec_t min_seg, max_seg, analyze; + ngx_ts_hls_conf_t *hls, **field; + + field = (ngx_ts_hls_conf_t **) (p + cmd->offset); + + if (*field != NGX_CONF_UNSET_PTR) { + return "is duplicate"; + } + + value = cf->args->elts; + + ngx_str_null(&path); + + min_seg = 5000; + max_seg = 10000; + analyze = 0; + max_size = 16 * 1024 * 1024; + nsegs = 6; + clean = 1; + + for (i = 1; i < cf->args->nelts; i++) { + + if (ngx_strncmp(value[i].data, "path=", 5) == 0) { + + path.len = value[i].len - 5; + path.data = value[i].data + 5; + + if (path.data[path.len - 1] == '/') { + path.len--; + } + + if (ngx_conf_full_name(cf->cycle, &path, 0) != NGX_OK) { + return NGX_CONF_ERROR; + } + + continue; + } + + if (ngx_strncmp(value[i].data, "segment=", 8) == 0) { + + s.len = value[i].len - 8; + s.data = value[i].data + 8; + + ss.data = (u_char *) ngx_strchr(s.data, ':'); + + if (ss.data) { + ss.len = s.data + s.len - ss.data - 1; + s.len = ss.data - s.data; + ss.data++; + } + + min_seg = ngx_parse_time(&s, 0); + if (min_seg == (ngx_msec_t) NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid segment duration value \"%V\"", + &value[i]); + return NGX_CONF_ERROR; + } + + if (ss.data) { + max_seg = ngx_parse_time(&ss, 0); + if (max_seg == (ngx_msec_t) NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid segment duration value \"%V\"", + &value[i]); + return NGX_CONF_ERROR; + } + + } else { + max_seg = min_seg * 2; + } + + continue; + } + + if (ngx_strncmp(value[i].data, "analyze=", 8) == 0) { + + s.len = value[i].len - 8; + s.data = value[i].data + 8; + + analyze = ngx_parse_time(&s, 0); + if (analyze == (ngx_msec_t) NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid analyze duration value \"%V\"", + &value[i]); + return NGX_CONF_ERROR; + } + + continue; + } + + if (ngx_strncmp(value[i].data, "max_size=", 9) == 0) { + + s.len = value[i].len - 9; + s.data = value[i].data + 9; + + max_size = ngx_parse_size(&s); + if (max_size == NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid max segment size value \"%V\"", + &value[i]); + return NGX_CONF_ERROR; + } + + continue; + } + + if (ngx_strncmp(value[i].data, "segments=", 9) == 0) { + + v = ngx_atoi(value[i].data + 9, value[i].len - 9); + if (v == NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid segments number value \"%V\"", + &value[i]); + return NGX_CONF_ERROR; + } + + nsegs = v; + + continue; + } + + if (ngx_strcmp(value[i].data, "noclean") == 0) { + clean = 0; + continue; + } + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\"", &value[i]); + return NGX_CONF_ERROR; + } + + if (path.len == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"%V\" must have \"path\" parameter", &cmd->name); + return NGX_CONF_ERROR; + } + + hls = ngx_pcalloc(cf->pool, sizeof(ngx_ts_hls_conf_t)); + if (hls == NULL) { + return NGX_CONF_ERROR; + } + + hls->path = ngx_pcalloc(cf->pool, sizeof(ngx_path_t)); + if (hls->path == NULL) { + return NGX_CONF_ERROR; + } + + hls->path->name = path; + + hls->min_seg = min_seg; + hls->max_seg = max_seg; + hls->analyze = analyze ? analyze : min_seg; + hls->max_size = max_size; + hls->nsegs = nsegs; + + if (clean) { + hls->path->manager = ngx_ts_hls_file_manager; + } + + hls->path->data = hls; + hls->path->conf_file = cf->conf_file->file.name.data; + hls->path->line = cf->conf_file->line; + + if (ngx_add_path(cf, &hls->path) != NGX_OK) { + return NGX_CONF_ERROR; + } + + *field = hls; + + return NGX_CONF_OK; +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_hls.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_hls.h new file mode 100644 index 0000000000..031f018c52 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_hls.h @@ -0,0 +1,75 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include + +#include "ngx_ts_stream.h" + + +#ifndef _NGX_TS_HLS_H_INCLUDED_ +#define _NGX_TS_HLS_H_INCLUDED_ + + +typedef struct { + ngx_path_t *path; + ngx_msec_t min_seg; + ngx_msec_t max_seg; + ngx_msec_t analyze; + size_t max_size; + ngx_uint_t nsegs; +} ngx_ts_hls_conf_t; + + +typedef struct { + ngx_uint_t id; + uint64_t duration; + off_t size; +} ngx_ts_hls_segment_t; + + +typedef struct { + ngx_file_t file; + ngx_chain_t *prologue; + + ngx_uint_t bandwidth; + ngx_uint_t bandwidth_bytes; + uint64_t bandwidth_dts; + + ngx_ts_hls_segment_t *segs; + ngx_uint_t nsegs; + ngx_uint_t seg; + uint64_t seg_dts; + + u_char *m3u8_path; + u_char *m3u8_tmp_path; + ngx_str_t path; + + ngx_ts_program_t *prog; +} ngx_ts_hls_variant_t; + + +typedef struct { + ngx_ts_stream_t *ts; + ngx_ts_hls_conf_t *conf; + + u_char *m3u8_path; + u_char *m3u8_tmp_path; + ngx_str_t path; + + ngx_ts_hls_variant_t *vars; + ngx_uint_t nvars; + + ngx_uint_t done; /* unsigned done:1; */ +} ngx_ts_hls_t; + + +ngx_ts_hls_t *ngx_ts_hls_create(ngx_ts_hls_conf_t *conf, ngx_ts_stream_t *ts, + ngx_str_t *name); +char *ngx_ts_hls_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); + + +#endif /* _NGX_TS_HLS_H_INCLUDED_ */ diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_stream.c b/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_stream.c new file mode 100644 index 0000000000..b778040d94 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_stream.c @@ -0,0 +1,1496 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include + +#include "ngx_ts_stream.h" + + +#define NGX_TS_PACKET_SIZE 188 + + +typedef struct { + ngx_chain_t *cl; + u_char *p; +} ngx_ts_byte_read_t; + + +typedef struct { + unsigned pusi:1; + unsigned cont:4; + unsigned rand:1; + unsigned pcrf:1; + unsigned stuff:1; + uint16_t pid; + uint64_t pcr; +} ngx_ts_header_t; + + +static ngx_int_t ngx_ts_run_handlers(ngx_ts_event_e event, ngx_ts_stream_t *ts, + ngx_ts_program_t *prog, ngx_ts_es_t *es, ngx_chain_t *bufs); +static void ngx_ts_byte_read_init(ngx_ts_byte_read_t *br, ngx_chain_t *cl); +static ngx_int_t ngx_ts_byte_read(ngx_ts_byte_read_t *br, u_char *dst, + size_t len); +static ngx_int_t ngx_ts_byte_read_skip(ngx_ts_byte_read_t *br, size_t len); +static ngx_int_t ngx_ts_byte_read8(ngx_ts_byte_read_t *br, u_char *v); +static ngx_int_t ngx_ts_byte_read16(ngx_ts_byte_read_t *br, uint16_t *v); + +static ssize_t ngx_ts_read_header(ngx_ts_stream_t *ts, u_char *p, + ngx_ts_header_t *h); +static ngx_int_t ngx_ts_read_packet(ngx_ts_stream_t *ts, ngx_buf_t *b); +static ngx_int_t ngx_ts_read_pat(ngx_ts_stream_t *ts, ngx_ts_header_t *h, + ngx_buf_t *b); +static ngx_int_t ngx_ts_read_pmt(ngx_ts_stream_t *ts, ngx_ts_program_t *prog, + ngx_ts_header_t *h, ngx_buf_t *b); +static ngx_int_t ngx_ts_read_pes(ngx_ts_stream_t *ts, ngx_ts_program_t *prog, + ngx_ts_es_t *es, ngx_ts_header_t *h, ngx_buf_t *b); +static ngx_chain_t *ngx_ts_packetize(ngx_ts_stream_t *ts, ngx_ts_header_t *h, + ngx_chain_t *in); + +static ngx_int_t ngx_ts_free_buf(ngx_ts_stream_t *ts, ngx_buf_t *b); +static ngx_int_t ngx_ts_append_buf(ngx_ts_stream_t *ts, ngx_ts_header_t *h, + ngx_chain_t **ll, ngx_buf_t *b); +static uint32_t ngx_ts_crc32(u_char *p, size_t len); + + +static uint32_t ngx_ts_crc32_table_ieee[] = { + 0x00000000, 0xb71dc104, 0x6e3b8209, 0xd926430d, + 0xdc760413, 0x6b6bc517, 0xb24d861a, 0x0550471e, + 0xb8ed0826, 0x0ff0c922, 0xd6d68a2f, 0x61cb4b2b, + 0x649b0c35, 0xd386cd31, 0x0aa08e3c, 0xbdbd4f38, + 0x70db114c, 0xc7c6d048, 0x1ee09345, 0xa9fd5241, + 0xacad155f, 0x1bb0d45b, 0xc2969756, 0x758b5652, + 0xc836196a, 0x7f2bd86e, 0xa60d9b63, 0x11105a67, + 0x14401d79, 0xa35ddc7d, 0x7a7b9f70, 0xcd665e74, + 0xe0b62398, 0x57abe29c, 0x8e8da191, 0x39906095, + 0x3cc0278b, 0x8bdde68f, 0x52fba582, 0xe5e66486, + 0x585b2bbe, 0xef46eaba, 0x3660a9b7, 0x817d68b3, + 0x842d2fad, 0x3330eea9, 0xea16ada4, 0x5d0b6ca0, + 0x906d32d4, 0x2770f3d0, 0xfe56b0dd, 0x494b71d9, + 0x4c1b36c7, 0xfb06f7c3, 0x2220b4ce, 0x953d75ca, + 0x28803af2, 0x9f9dfbf6, 0x46bbb8fb, 0xf1a679ff, + 0xf4f63ee1, 0x43ebffe5, 0x9acdbce8, 0x2dd07dec, + 0x77708634, 0xc06d4730, 0x194b043d, 0xae56c539, + 0xab068227, 0x1c1b4323, 0xc53d002e, 0x7220c12a, + 0xcf9d8e12, 0x78804f16, 0xa1a60c1b, 0x16bbcd1f, + 0x13eb8a01, 0xa4f64b05, 0x7dd00808, 0xcacdc90c, + 0x07ab9778, 0xb0b6567c, 0x69901571, 0xde8dd475, + 0xdbdd936b, 0x6cc0526f, 0xb5e61162, 0x02fbd066, + 0xbf469f5e, 0x085b5e5a, 0xd17d1d57, 0x6660dc53, + 0x63309b4d, 0xd42d5a49, 0x0d0b1944, 0xba16d840, + 0x97c6a5ac, 0x20db64a8, 0xf9fd27a5, 0x4ee0e6a1, + 0x4bb0a1bf, 0xfcad60bb, 0x258b23b6, 0x9296e2b2, + 0x2f2bad8a, 0x98366c8e, 0x41102f83, 0xf60dee87, + 0xf35da999, 0x4440689d, 0x9d662b90, 0x2a7bea94, + 0xe71db4e0, 0x500075e4, 0x892636e9, 0x3e3bf7ed, + 0x3b6bb0f3, 0x8c7671f7, 0x555032fa, 0xe24df3fe, + 0x5ff0bcc6, 0xe8ed7dc2, 0x31cb3ecf, 0x86d6ffcb, + 0x8386b8d5, 0x349b79d1, 0xedbd3adc, 0x5aa0fbd8, + 0xeee00c69, 0x59fdcd6d, 0x80db8e60, 0x37c64f64, + 0x3296087a, 0x858bc97e, 0x5cad8a73, 0xebb04b77, + 0x560d044f, 0xe110c54b, 0x38368646, 0x8f2b4742, + 0x8a7b005c, 0x3d66c158, 0xe4408255, 0x535d4351, + 0x9e3b1d25, 0x2926dc21, 0xf0009f2c, 0x471d5e28, + 0x424d1936, 0xf550d832, 0x2c769b3f, 0x9b6b5a3b, + 0x26d61503, 0x91cbd407, 0x48ed970a, 0xfff0560e, + 0xfaa01110, 0x4dbdd014, 0x949b9319, 0x2386521d, + 0x0e562ff1, 0xb94beef5, 0x606dadf8, 0xd7706cfc, + 0xd2202be2, 0x653deae6, 0xbc1ba9eb, 0x0b0668ef, + 0xb6bb27d7, 0x01a6e6d3, 0xd880a5de, 0x6f9d64da, + 0x6acd23c4, 0xddd0e2c0, 0x04f6a1cd, 0xb3eb60c9, + 0x7e8d3ebd, 0xc990ffb9, 0x10b6bcb4, 0xa7ab7db0, + 0xa2fb3aae, 0x15e6fbaa, 0xccc0b8a7, 0x7bdd79a3, + 0xc660369b, 0x717df79f, 0xa85bb492, 0x1f467596, + 0x1a163288, 0xad0bf38c, 0x742db081, 0xc3307185, + 0x99908a5d, 0x2e8d4b59, 0xf7ab0854, 0x40b6c950, + 0x45e68e4e, 0xf2fb4f4a, 0x2bdd0c47, 0x9cc0cd43, + 0x217d827b, 0x9660437f, 0x4f460072, 0xf85bc176, + 0xfd0b8668, 0x4a16476c, 0x93300461, 0x242dc565, + 0xe94b9b11, 0x5e565a15, 0x87701918, 0x306dd81c, + 0x353d9f02, 0x82205e06, 0x5b061d0b, 0xec1bdc0f, + 0x51a69337, 0xe6bb5233, 0x3f9d113e, 0x8880d03a, + 0x8dd09724, 0x3acd5620, 0xe3eb152d, 0x54f6d429, + 0x7926a9c5, 0xce3b68c1, 0x171d2bcc, 0xa000eac8, + 0xa550add6, 0x124d6cd2, 0xcb6b2fdf, 0x7c76eedb, + 0xc1cba1e3, 0x76d660e7, 0xaff023ea, 0x18ede2ee, + 0x1dbda5f0, 0xaaa064f4, 0x738627f9, 0xc49be6fd, + 0x09fdb889, 0xbee0798d, 0x67c63a80, 0xd0dbfb84, + 0xd58bbc9a, 0x62967d9e, 0xbbb03e93, 0x0cadff97, + 0xb110b0af, 0x060d71ab, 0xdf2b32a6, 0x6836f3a2, + 0x6d66b4bc, 0xda7b75b8, 0x035d36b5, 0xb440f7b1 +}; + + +ngx_int_t +ngx_ts_add_handler(ngx_ts_stream_t *ts, ngx_ts_handler_pt handler, void *data) +{ + ngx_ts_handler_t *h; + + h = ngx_palloc(ts->pool, sizeof(ngx_ts_handler_t)); + if (h == NULL) { + return NGX_ERROR; + } + + h->handler = handler; + h->data = data; + + h->next = ts->handlers; + ts->handlers = h; + + return NGX_OK; +} + + +static ngx_int_t +ngx_ts_run_handlers(ngx_ts_event_e event, ngx_ts_stream_t *ts, + ngx_ts_program_t *prog, ngx_ts_es_t *es, ngx_chain_t *bufs) +{ + ngx_ts_handler_t *h; + ngx_ts_handler_data_t hd; + + hd.event = event; + hd.ts = ts; + hd.prog = prog; + hd.es = es; + hd.bufs = bufs; + + for (h = ts->handlers; h; h = h->next) { + hd.data = h->data; + + if (h->handler(&hd) != NGX_OK) { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static void +ngx_ts_byte_read_init(ngx_ts_byte_read_t *br, ngx_chain_t *cl) +{ + br->cl = cl; + br->p = cl ? cl->buf->pos : NULL; +} + + +static ngx_int_t +ngx_ts_byte_read(ngx_ts_byte_read_t *br, u_char *dst, size_t len) +{ + size_t n; + + while (br->cl && len) { + n = ngx_min((size_t) (br->cl->buf->last - br->p), len); + + if (dst) { + dst = ngx_cpymem(dst, br->p, n); + } + + br->p += n; + len -= n; + + if (br->p == br->cl->buf->last) { + br->cl = br->cl->next; + br->p = br->cl ? br->cl->buf->pos : NULL; + } + } + + return len ? NGX_AGAIN : NGX_OK; +} + + +static ngx_int_t +ngx_ts_byte_read_skip(ngx_ts_byte_read_t *br, size_t len) +{ + return ngx_ts_byte_read(br, NULL, len); +} + + +static ngx_int_t +ngx_ts_byte_read8(ngx_ts_byte_read_t *br, u_char *v) +{ + return ngx_ts_byte_read(br, v, 1); +} + + +static ngx_int_t +ngx_ts_byte_read16(ngx_ts_byte_read_t *br, uint16_t *v) +{ + if (ngx_ts_byte_read(br, (u_char *) v, 2) == NGX_AGAIN) { + return NGX_AGAIN; + } + +#if (NGX_HAVE_LITTLE_ENDIAN) + *v = htons(*v); +#endif + + return NGX_OK; +} + + +ngx_int_t +ngx_ts_read(ngx_ts_stream_t *ts, ngx_chain_t *in) +{ + size_t n, size; + ngx_buf_t *b; + ngx_chain_t *cl; + + ngx_log_debug0(NGX_LOG_DEBUG_CORE, ts->log, 0, "ts read"); + + for (/* void */; in; in = in->next) { + + while (in->buf->pos != in->buf->last) { + b = ts->buf; + + if (b == NULL) { + if (ts->free) { + cl = ts->free; + ts->free = cl->next; + + b = cl->buf; + ngx_free_chain(ts->pool, cl); + + b->pos = b->start; + b->last = b->start; + + } else { + b = ngx_create_temp_buf(ts->pool, NGX_TS_PACKET_SIZE); + if (b == NULL) { + return NGX_ERROR; + } + } + + ts->buf = b; + } + + n = b->end - b->last; + size = in->buf->last - in->buf->pos; + + if (n > size) { + n = size; + } + + b->last = ngx_cpymem(b->last, in->buf->pos, n); + in->buf->pos += n; + + if (b->last == b->end) { + if (ngx_ts_read_packet(ts, b) != NGX_OK) { + return NGX_ERROR; + } + + ts->buf = NULL; + } + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_ts_read_packet(ngx_ts_stream_t *ts, ngx_buf_t *b) +{ + ssize_t n; + ngx_uint_t i, j; + ngx_chain_t *cl; + ngx_ts_es_t *es; + ngx_ts_header_t h; + ngx_ts_program_t *prog; + + n = ngx_ts_read_header(ts, b->pos, &h); + + if (n == NGX_ERROR) { + return NGX_ERROR; + } + + if (n == NGX_DONE) { + b->pos = b->last; + + } else { + b->pos += n; + } + + if (h.pid == 0) { + return ngx_ts_read_pat(ts, &h, b); + } + + for (i = 0; i < ts->nprogs; i++) { + prog = &ts->progs[i]; + + if (h.pid == prog->pid) { + return ngx_ts_read_pmt(ts, prog, &h, b); + } + + for (j = 0; j < prog->nes; j++) { + es = &prog->es[j]; + + if (h.pid == es->pid) { + return ngx_ts_read_pes(ts, prog, es, &h, b); + } + } + } + + ngx_log_error(NGX_LOG_INFO, ts->log, 0, + "dropping unexpected TS packet pid:0x%04uxd", + (unsigned) h.pid); + + cl = ngx_alloc_chain_link(ts->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + cl->buf = b; + cl->next = ts->free; + ts->free = cl; + + return NGX_OK; +} + + +static ssize_t +ngx_ts_read_header(ngx_ts_stream_t *ts, u_char *p, ngx_ts_header_t *h) +{ + /* + * TS Packet Header + * ISO/IEC 13818-1 : 2000 (E) + * 2.4.3.2 Transport Stream packet layer, p. 18 + */ + + u_char alen, afic; + ssize_t n; + uint64_t pcrb, pcre; + + /* sync_byte */ + if (*p++ != 0x47) { + ngx_log_error(NGX_LOG_ERR, ts->log, 0, "invalid TS sync byte"); + return NGX_ERROR; + } + + ngx_memzero(h, sizeof(ngx_ts_header_t)); + + /* payload_unit_start_indicator */ + h->pusi = (*p & 0x40) ? 1 : 0; + + /* PID */ + h->pid = *p++ & 0x1f; + h->pid = (h->pid << 8) + *p++; + + /* adaptation_field_control */ + afic = (*p & 0x30) >> 4; + + /* continuity_counter */ + h->cont = *p++ & 0x0f; + + if (afic == 0) { + ngx_log_error(NGX_LOG_ERR, ts->log, 0, "invalid TS packet"); + return NGX_ERROR; + } + + n = 4; + + if (afic & 0x02) { + /* adaptation_field_length */ + alen = *p++; + + if (alen > 183) { + ngx_log_error(NGX_LOG_ERR, ts->log, 0, + "too long TS adaptation field"); + return NGX_ERROR; + } + + if (afic & 0x01) { + n += alen + 1; + + } else { + n = NGX_DONE; + } + + if (alen) { + /* random_access_indicator */ + h->rand = (*p & 0x40) ? 1 : 0; + + /* PCR_flag */ + h->pcrf = (*p & 0x10) ? 1 : 0; + + p++; + + if (h->pcrf) { + /* program_clock_reference_base */ + pcrb = *p++; + pcrb = (pcrb << 8) + *p++; + pcrb = (pcrb << 8) + *p++; + pcrb = (pcrb << 8) + *p++; + pcrb = (pcrb << 1) + (*p >> 7); + + /* program_clock_reference_extension */ + pcre = *p++ & 0x01; + pcre = (pcre << 8) + *p++; + + h->pcr = pcrb * 300 + pcre; + } + } + } + + ngx_log_debug6(NGX_LOG_DEBUG_CORE, ts->log, 0, "ts packet " + "pid:0x%04uxd, pusi:%d, c:%02d, r:%d, pcr:%uL, n:%uz", + (unsigned) h->pid, h->pusi, h->cont, h->rand, h->pcr, + n == NGX_DONE ? 0 : NGX_TS_PACKET_SIZE - n); + + return n; +} + + +static ngx_int_t +ngx_ts_read_pat(ngx_ts_stream_t *ts, ngx_ts_header_t *h, ngx_buf_t *b) +{ + /* + * PAT + * ISO/IEC 13818-1 : 2000 (E) + * 2.4.4.3 Program association Table, p. 43 + */ + + u_char ptr; + uint16_t len, number, pid; + ngx_uint_t nprogs, n; + ngx_ts_program_t *prog; + ngx_ts_byte_read_t br, pr; + + ngx_log_debug0(NGX_LOG_DEBUG_CORE, ts->log, 0, "ts pat"); + + if (ts->nprogs) { + ngx_log_debug0(NGX_LOG_DEBUG_CORE, ts->log, 0, + "ts dropping successive pat"); + return ngx_ts_free_buf(ts, b); + } + + if (ngx_ts_append_buf(ts, h, &ts->bufs, b) != NGX_OK) { + return NGX_ERROR; + } + + ngx_ts_byte_read_init(&br, ts->bufs); + + /* pointer_field */ + if (ngx_ts_byte_read8(&br, &ptr) == NGX_AGAIN) { + return NGX_OK; + } + + /* skipped bytes + table_id */ + if (ngx_ts_byte_read_skip(&br, ptr + 1) == NGX_AGAIN) { + return NGX_OK; + } + + /* section_length */ + if (ngx_ts_byte_read16(&br, &len) == NGX_AGAIN) { + return NGX_OK; + } + + len &= 0x0fff; + + if (len < 9) { + ngx_log_error(NGX_LOG_ERR, ts->log, 0, "malformed PAT"); + return NGX_ERROR; + } + + if (len > 0x03fd) { + ngx_log_error(NGX_LOG_ERR, ts->log, 0, "too big PAT: %ud", + (unsigned) len); + return NGX_ERROR; + } + + pr = br; + + if (ngx_ts_byte_read_skip(&pr, len) == NGX_AGAIN) { + return NGX_OK; + } + + /* PAT is fully available */ + + /* transport_stream_id .. last_section_number */ + ngx_ts_byte_read_skip(&br, 5); + + nprogs = (len - 9) / 4; + + ts->progs = ngx_pcalloc(ts->pool, + nprogs * sizeof(ngx_ts_program_t)); + if (ts->progs == NULL) { + return NGX_ERROR; + } + + prog = ts->progs; + + for (n = 0; n < nprogs; n++) { + /* program_number */ + (void) ngx_ts_byte_read16(&br, &number); + + /* network_PID / program_map_PID */ + (void) ngx_ts_byte_read16(&br, &pid); + + if (number) { + pid = pid & 0x1fff; + + prog->number = number; + prog->pid = pid; + prog++; + + ngx_log_debug2(NGX_LOG_DEBUG_CORE, ts->log, 0, + "ts program %ud, pid:0x%04uxd", + (unsigned) number, (unsigned) pid); + } + } + + ts->nprogs = prog - ts->progs; + + if (ngx_ts_run_handlers(NGX_TS_PAT, ts, NULL, NULL, NULL) != NGX_OK) { + return NGX_ERROR; + } + + ngx_ts_free_chain(ts, &ts->bufs); + + return NGX_OK; +} + + +static ngx_int_t +ngx_ts_read_pmt(ngx_ts_stream_t *ts, ngx_ts_program_t *prog, ngx_ts_header_t *h, + ngx_buf_t *b) +{ + /* + * PMT + * ISO/IEC 13818-1 : 2000 (E) + * 2.4.4.8 Program Map Table, p. 46 + */ + + u_char ptr, type; + uint16_t len, pilen, elen, pid; + ngx_uint_t nes, n; + ngx_ts_es_t *es; + ngx_ts_byte_read_t br, pr; + + ngx_log_debug0(NGX_LOG_DEBUG_CORE, ts->log, 0, "ts pmt"); + + if (prog->nes) { + ngx_log_debug0(NGX_LOG_DEBUG_CORE, ts->log, 0, + "ts dropping successive pmt"); + return ngx_ts_free_buf(ts, b); + } + + if (ngx_ts_append_buf(ts, h, &prog->bufs, b) != NGX_OK) { + return NGX_ERROR; + } + + ngx_ts_byte_read_init(&br, prog->bufs); + + /* pointer_field */ + if (ngx_ts_byte_read8(&br, &ptr) == NGX_AGAIN) { + return NGX_OK; + } + + /* skipped bytes + table_id */ + if (ngx_ts_byte_read_skip(&br, ptr + 1) == NGX_AGAIN) { + return NGX_OK; + } + + /* section_length */ + if (ngx_ts_byte_read16(&br, &len) == NGX_AGAIN) { + return NGX_OK; + } + + len &= 0x0fff; + + if (len < 13) { + ngx_log_error(NGX_LOG_ERR, ts->log, 0, "malformed PMT"); + return NGX_ERROR; + } + + if (len > 0x03fd) { + ngx_log_error(NGX_LOG_ERR, ts->log, 0, "too big PMT: %ud", + (unsigned) len); + return NGX_ERROR; + } + + pr = br; + + if (ngx_ts_byte_read_skip(&pr, len) == NGX_AGAIN) { + return NGX_OK; + } + + /* PMT is fully available */ + + /* program_number .. last_sesion_number */ + (void) ngx_ts_byte_read_skip(&br, 5); + + /* PCR_PID */ + (void) ngx_ts_byte_read16(&br, &pid); + + prog->pcr_pid = pid & 0x1fff; + + /* program_info_length */ + (void) ngx_ts_byte_read16(&br, &pilen); + + pilen &= 0x0fff; + + if (ngx_ts_byte_read_skip(&br, pilen) == NGX_AGAIN + || len < 13 + pilen) + { + ngx_log_error(NGX_LOG_ERR, ts->log, 0, "malformed PMT"); + return NGX_ERROR; + } + + len -= 13 + pilen; + + pr = br; + + for (nes = 0; len > 0; nes++) { + if (ngx_ts_byte_read(&pr, NULL, 3) == NGX_AGAIN + || ngx_ts_byte_read16(&pr, &elen) == NGX_AGAIN + || ngx_ts_byte_read(&pr, NULL, elen & 0x0fff) == NGX_AGAIN + || len < 5 + (elen & 0x0fff)) + { + ngx_log_error(NGX_LOG_ERR, ts->log, 0, "malformed PMT"); + return NGX_ERROR; + } + + len -= 5 + (elen & 0x0fff); + } + + es = ngx_pcalloc(ts->pool, nes * sizeof(ngx_ts_es_t)); + if (es == NULL) { + return NGX_ERROR; + } + + prog->es = es; + prog->nes = nes; + + for (n = 0; n < nes; n++, es++) { + /* stream_type */ + (void) ngx_ts_byte_read8(&br, &type); + + /* elementary_PID */ + (void) ngx_ts_byte_read16(&br, &pid); + + /* ES_info_length */ + (void) ngx_ts_byte_read16(&br, &elen); + + /* ES_info */ + (void) ngx_ts_byte_read_skip(&br, elen & 0x0fff); + + pid = pid & 0x1fff; + + es->type = type; + es->pid = pid; + + if (type == NGX_TS_VIDEO_MPEG1 + || type == NGX_TS_VIDEO_MPEG2 + || type == NGX_TS_VIDEO_MPEG4 + || type == NGX_TS_VIDEO_AVC) + { + es->video = 1; + prog->video = 1; + } + + ngx_log_debug3(NGX_LOG_DEBUG_CORE, ts->log, 0, + "ts es type:%ui, video:%d, pid:0x%04uxd", + (ngx_uint_t) type, es->video, (unsigned) pid); + } + + if (ngx_ts_run_handlers(NGX_TS_PMT, ts, prog, NULL, NULL) != NGX_OK) { + return NGX_ERROR; + } + + ngx_ts_free_chain(ts, &prog->bufs); + + return NGX_OK; +} + + +static ngx_int_t +ngx_ts_read_pes(ngx_ts_stream_t *ts, ngx_ts_program_t *prog, ngx_ts_es_t *es, + ngx_ts_header_t *h, ngx_buf_t *b) +{ + /* + * PES Packet + * ISO/IEC 13818-1 : 2000 (E) + * 2.4.3.6 PES packet, p. 31 + */ + + u_char sid, pfx[3], v8, hlen; + uint16_t len, flags, v16; + uint64_t pts, dts; + ngx_uint_t ptsf; + ngx_ts_byte_read_t br, pr; + + ngx_log_debug0(NGX_LOG_DEBUG_CORE, ts->log, 0, "ts pes"); + + if (es->bufs && h->pusi && b) { + if (ngx_ts_read_pes(ts, prog, es, h, NULL) != NGX_OK) { + return NGX_ERROR; + } + } + + if (es->bufs == NULL) { + es->rand = h->rand; + } + + if (h->pcrf && prog->pcr_pid == es->pid) { + prog->pcr = h->pcr; + } + + if (ngx_ts_append_buf(ts, h, &es->bufs, b) != NGX_OK) { + return NGX_ERROR; + } + + ngx_ts_byte_read_init(&br, es->bufs); + + /* packet_start_code_prefix */ + if (ngx_ts_byte_read(&br, pfx, 3) == NGX_AGAIN) { + return NGX_OK; + } + + if (pfx[0] != 0 || pfx[1] != 0 || pfx[2] != 1) { + ngx_log_error(NGX_LOG_INFO, ts->log, 0, "missing PES start prefix"); + return NGX_ERROR; + } + + /* stream_id */ + if (ngx_ts_byte_read8(&br, &sid) == NGX_AGAIN) { + return NGX_OK; + } + + es->sid = sid; + + /* PES_packet_length */ + if (ngx_ts_byte_read16(&br, &len) == NGX_AGAIN) { + return NGX_OK; + } + + if (len) { + pr = br; + + if (ngx_ts_byte_read_skip(&pr, len) == NGX_AGAIN) { + return NGX_OK; + } + + } else if (b) { + /* wait for PUSI */ + return NGX_OK; + } + + /* PES is fully available */ + + if (sid == 0xbe) { /* padding_stream */ + ngx_ts_free_chain(ts, &es->bufs); + return NGX_OK; + } + + ptsf = 0; + + if (sid != 0xbc /* program_stream_map */ + && sid != 0xbf /* private_stream_2 */ + && sid != 0xf0 /* ECM_stream */ + && sid != 0xf1 /* EMM_stream */ + && sid != 0xff /* program_stream_directory */ + && sid != 0xf2 /* DSMCC_stream */ + && sid != 0xf8) /* ITU-T Rec. H.222.1 type E stream */ + { + /* PES_scrambling_control .. PES_extension_flag */ + if (ngx_ts_byte_read16(&br, &flags) == NGX_AGAIN) { + return NGX_OK; + } + + /* PES_header_data_length */ + if (ngx_ts_byte_read8(&br, &hlen) == NGX_AGAIN) { + return NGX_OK; + } + + if (len) { + if (len < 3 + hlen) { + ngx_log_error(NGX_LOG_INFO, ts->log, 0, "malformed PES"); + return NGX_ERROR; + } + + len -= 3 + hlen; + } + + pr = br; + + if (ngx_ts_byte_read_skip(&br, hlen) == NGX_AGAIN) { + return NGX_OK; + } + + if ((flags & 0x00c0) == 0x0080) { /* PTS_DTS_flags == '10' */ + ptsf = 1; + + /* PTS[32..30] */ + if (ngx_ts_byte_read8(&pr, &v8) == NGX_AGAIN) { + return NGX_OK; + } + + pts = (uint64_t) (v8 & 0x0e) << 29; + + /* PTS[29..15] */ + if (ngx_ts_byte_read16(&pr, &v16) == NGX_AGAIN) { + return NGX_OK; + } + + pts |= (uint64_t) (v16 & 0xfffe) << 14; + + /* PTS[14..0] */ + if (ngx_ts_byte_read16(&pr, &v16) == NGX_AGAIN) { + return NGX_OK; + } + + pts |= v16 >> 1; + + es->pts = pts; + es->dts = pts; + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, ts->log, 0, + "ts pes pts:%uL", pts); + } + + if ((flags & 0x00c0) == 0x00c0) { /* PTS_DTS_flags == '11' */ + ptsf = 1; + + /* PTS[32..30] */ + if (ngx_ts_byte_read8(&pr, &v8) == NGX_AGAIN) { + return NGX_OK; + } + + pts = (uint64_t) (v8 & 0x0e) << 29; + + /* PTS[29..15] */ + if (ngx_ts_byte_read16(&pr, &v16) == NGX_AGAIN) { + return NGX_OK; + } + + pts |= (uint64_t) (v16 & 0xfffe) << 14; + + /* PTS[14..0] */ + if (ngx_ts_byte_read16(&pr, &v16) == NGX_AGAIN) { + return NGX_OK; + } + + pts |= v16 >> 1; + + /* DTS[32..30] */ + if (ngx_ts_byte_read8(&pr, &v8) == NGX_AGAIN) { + return NGX_OK; + } + + dts = (uint64_t) (v8 & 0x0e) << 29; + + /* DTS[29..15] */ + if (ngx_ts_byte_read16(&pr, &v16) == NGX_AGAIN) { + return NGX_OK; + } + + dts |= (uint64_t) (v16 & 0xfffe) << 14; + + /* DTS[14..0] */ + if (ngx_ts_byte_read16(&pr, &v16) == NGX_AGAIN) { + return NGX_OK; + } + + dts |= v16 >> 1; + + es->pts = pts; + es->dts = dts; + + ngx_log_debug2(NGX_LOG_DEBUG_CORE, ts->log, 0, + "ts pes pts:%uL, dts:%uL", pts, dts); + } + } + + if (br.cl) { + br.cl->buf->pos = br.p; + } + + if (len) { + pr = br; + + if (ngx_ts_byte_read_skip(&pr, len) == NGX_AGAIN) { + return NGX_OK; + } + + if (pr.cl) { + pr.cl->buf->last = pr.p; + } + } + + es->ptsf = ptsf; + + if (ngx_ts_run_handlers(NGX_TS_PES, ts, prog, es, br.cl) != NGX_OK) { + return NGX_ERROR; + } + + ngx_ts_free_chain(ts, &es->bufs); + + return NGX_OK; +} + + +ngx_chain_t * +ngx_ts_write_pat(ngx_ts_stream_t *ts, ngx_ts_program_t *prog) +{ + size_t len; + u_char *p, *data; + uint32_t crc; + ngx_buf_t b; + ngx_uint_t n, nprogs; + ngx_chain_t in; + ngx_ts_header_t h; + + if (prog) { + nprogs = 1; + + } else { + nprogs = ts->nprogs; + prog = ts->progs; + } + + len = 9 + nprogs * 4; + + data = ngx_pnalloc(ts->pool, 4 + len); + if (data == NULL) { + return NULL; + } + + p = data; + + /* pointer_field */ + *p++ = 0; + + /* table_id */ + *p++ = 0; + + /* section_syntax_indicator, section_length */ + *p++ = 0x80 | (u_char) (len >> 8); + *p++ = (u_char) len; + + /* transport_stream_id */ + *p++ = 0; + *p++ = 0; + + /* version_number, current_next_indicator */ + *p++ = 0x01; + + /* section_number */ + *p++ = 0; + + /* last_section_number */ + *p++ = 0; + + for (n = 0; n < nprogs; n++, prog++) { + /* program_number */ + *p++ = (u_char) (prog->number >> 8); + *p++ = (u_char) prog->number; + + /* program_map_PID */ + *p++ = (u_char) (prog->pid >> 8); + *p++ = (u_char) prog->pid; + } + + crc = ngx_ts_crc32(data + 1, p - data - 1); + + *p++ = (u_char) crc; + *p++ = (u_char) (crc >> 8); + *p++ = (u_char) (crc >> 16); + *p++ = (u_char) (crc >> 24); + + ngx_memzero(&b, sizeof(ngx_buf_t)); + + b.start = data; + b.pos = data; + b.last = p; + b.end = p; + + in.buf = &b; + in.next = NULL; + + ngx_memzero(&h, sizeof(ngx_ts_header_t)); + + return ngx_ts_packetize(ts, &h, &in); +} + + +ngx_chain_t * +ngx_ts_write_pmt(ngx_ts_stream_t *ts, ngx_ts_program_t *prog) +{ + size_t len; + u_char *p, *data; + uint32_t crc; + ngx_buf_t b; + ngx_uint_t n; + ngx_chain_t in; + ngx_ts_es_t *es; + ngx_ts_header_t h; + + len = 13 + 5 * prog->nes; + + data = ngx_pnalloc(ts->pool, 4 + len); + if (data == NULL) { + return NULL; + } + + p = data; + + /* pointer_field */ + *p++ = 0; + + /* table_id */ + *p++ = 0x02; + + /* section_syntax_indicator */ + *p++ = 0x80 | (u_char) (len >> 8); + *p++ = (u_char) len; + + /* program_number */ + *p++ = (u_char) (prog->number >> 8); + *p++ = (u_char) prog->number; + + /* version_number, current_next_indicator */ + *p++ = 0x01; + + /* section_number */ + *p++ = 0; + + /* last_section_number */ + *p++ = 0; + + /* PCR_PID */ + *p++ = (u_char) (prog->pcr_pid >> 8); + *p++ = (u_char) prog->pcr_pid; + + /* program_info_length */ + *p++ = 0; + *p++ = 0; + + for (n = 0; n < prog->nes; n++) { + es = &prog->es[n]; + + /* stream_type */ + *p++ = es->type; + + /* elementary_PID */ + *p++ = (u_char) (es->pid >> 8); + *p++ = (u_char) es->pid; + + /* ES_info_length */ + *p++ = 0; + *p++ = 0; + } + + crc = ngx_ts_crc32(data + 1, p - data - 1); + + *p++ = (u_char) crc; + *p++ = (u_char) (crc >> 8); + *p++ = (u_char) (crc >> 16); + *p++ = (u_char) (crc >> 24); + + ngx_memzero(&b, sizeof(ngx_buf_t)); + + b.start = data; + b.pos = data; + b.last = p; + b.end = p; + + in.buf = &b; + in.next = NULL; + + ngx_memzero(&h, sizeof(ngx_ts_header_t)); + + h.pid = prog->pid; + + return ngx_ts_packetize(ts, &h, &in); +} + + +ngx_chain_t * +ngx_ts_write_pes(ngx_ts_stream_t *ts, ngx_ts_program_t *prog, ngx_ts_es_t *es, + ngx_chain_t *bufs) +{ + size_t len; + u_char *p, *plen, *phflags, *phlen; + ngx_buf_t b; + ngx_chain_t in, *cl, *out; + ngx_ts_header_t h; + u_char buf[19]; + + p = buf; + + /* packet_start_code_prefix */ + *p++ = 0; + *p++ = 0; + *p++ = 1; + + /* stream_id */ + *p++ = es->sid; + + /* PES_packet_length */ + plen = p; + *p++ = 0; + *p++ = 0; + + if (es->sid != 0xbc /* program_stream_map */ + && es->sid != 0xbe /* padding_stream */ + && es->sid != 0xbf /* private_stream_2 */ + && es->sid != 0xf0 /* ECM_stream */ + && es->sid != 0xf1 /* EMM_stream */ + && es->sid != 0xff /* program_stream_directory */ + && es->sid != 0xf2 /* DSMCC_stream */ + && es->sid != 0xf8) /* ITU-T Rec. H.222.1 type E stream */ + { + /* PES_scrambling_control .. original_or_copy */ + *p++ = 0x80; + + /* XXX ptsf */ + + /* PTS_DTS_flags .. PES_extension_flag */ + phflags = p; + *p++ = 0; + + /* PES_header_data_lenth */ + phlen = p; + *p++ = 0; + + if (es->pts == es->dts) { + *phlen += 5; + *phflags |= 0x80; + + /* PTS */ + *p++ = 0x21 | (u_char) ((es->pts >> 29) & 0x0e); + *p++ = (u_char) (es->pts >> 22); + *p++ = 0x01 | (u_char) (es->pts >> 14); + *p++ = (u_char) (es->pts >> 7); + *p++ = 0x01 | (u_char) (es->pts << 1); + + } else { + *phlen += 10; + *phflags |= 0xc0; + + /* PTS */ + *p++ = 0x31 | (u_char) ((es->pts >> 29) & 0x0e); + *p++ = (u_char) (es->pts >> 22); + *p++ = 0x01 | (u_char) (es->pts >> 14); + *p++ = (u_char) (es->pts >> 7); + *p++ = 0x01 | (u_char) (es->pts << 1); + + /* DTS */ + *p++ = 0x11 | (u_char) ((es->dts >> 29) & 0x0e); + *p++ = (u_char) (es->dts >> 22); + *p++ = 0x01 | (u_char) (es->dts >> 14); + *p++ = (u_char) (es->dts >> 7); + *p++ = 0x01 | (u_char) (es->dts << 1); + } + } + + len = p - plen - 2; + for (cl = bufs; cl; cl = cl->next) { + len += cl->buf->last - cl->buf->pos; + } + + if (len <= 0xffff) { + *plen++ = (u_char) (len >> 8); + *plen++ = (u_char) len; + } + + ngx_memzero(&b, sizeof(ngx_buf_t)); + + b.start = buf; + b.pos = buf; + b.last = p; + b.end = p; + + in.buf = &b; + in.next = bufs; + + ngx_memzero(&h, sizeof(ngx_ts_header_t)); + + h.pid = es->pid; + h.rand = es->rand; + h.cont = es->cont; + h.stuff = 1; + + if (prog->pcr_pid == es->pid) { + h.pcrf = 1; + h.pcr = prog->pcr; + } + + out = ngx_ts_packetize(ts, &h, &in); + if (out == NULL) { + return NULL; + } + + es->cont = h.cont; + + return out; +} + + +static ngx_chain_t * +ngx_ts_packetize(ngx_ts_stream_t *ts, ngx_ts_header_t *h, ngx_chain_t *in) +{ + u_char *p, *ip, *af, *aflen; + size_t n, left; + uint64_t pcrb, pcre; + ngx_buf_t *b; + ngx_chain_t *out, *cl, **ll; + + left = 0; + + for (cl = in; cl; cl = cl->next) { + left += cl->buf->last - cl->buf->pos; + } + + ip = in->buf->pos; + + ll = &out; + + do { + if (ts->free) { + cl = ts->free; + ts->free = cl->next; + b = cl->buf; + + } else { + ngx_log_debug1(NGX_LOG_DEBUG_CORE, ts->log, 0, + "ts alloc buffer:%d", NGX_TS_PACKET_SIZE); + + b = ngx_create_temp_buf(ts->pool, NGX_TS_PACKET_SIZE); + if (b == NULL) { + return NULL; + } + + cl = ngx_alloc_chain_link(ts->pool); + if (cl == NULL) { + return NULL; + } + + cl->buf = b; + } + + b->pos = b->start; + b->last = b->end; + + p = b->pos; + + /* sync_byte */ + *p++ = 0x47; + + /* payload_unit_start_indicator, PID */ + *p++ = (ll == &out ? 0x40 : 0x00) | (u_char) (h->pid >> 8); + *p++ = (u_char) h->pid; + + /* payload_present, continuity_counter */ + af = p; + *p++ = 0x10 | h->cont; + h->cont = (h->cont + 1) & 0x0f; + + if (h->rand || h->pcrf || (h->stuff && left < 184)) { + /* adaptation_field_control */ + *af |= 0x20; + + /* adaptation_field_length */ + aflen = p; + *p++ = 1; + + /* random_access_indicator, PCR_flag */ + *p++ = (h->rand ? 0x40 : 0x00) | (h->pcrf ? 0x10 : 0x00); + + if (h->pcrf) { + pcrb = h->pcr / 300; + pcre = h->pcr % 300; + + /* + * program_clock_reference_base, + * program_clock_reference_extension + */ + *p++ = (u_char) (pcrb >> 25); + *p++ = (u_char) (pcrb >> 17); + *p++ = (u_char) (pcrb >> 9); + *p++ = (u_char) (pcrb >> 1); + *p++ = (u_char) (pcrb << 7) | (u_char) ((pcre >> 8) & 0x01); + *p++ = (u_char) pcre; + + *aflen += 6; + } + + if (h->stuff && left < (size_t) (b->end - p)) { + n = b->end - p - left; + + /* stuffing_byte */ + ngx_memset(p, '\xff', n); + p += n; + *aflen += n; + } + } + + /* data_byte */ + + while (p != b->end) { + n = ngx_min(in->buf->last - ip, b->end - p); + + p = ngx_cpymem(p, ip, n); + + left -= n; + ip += n; + + if (ip == in->buf->last) { + in = in->next; + if (in == NULL) { + ngx_memset(p, '\xff', b->end - p); + break; + } + + ip = in->buf->pos; + } + } + + h->rand = 0; + h->pcrf = 0; + + *ll = cl; + ll = &cl->next; + + } while (in); + + *ll = NULL; + + return out; +} + + +static ngx_int_t +ngx_ts_free_buf(ngx_ts_stream_t *ts, ngx_buf_t *b) +{ + ngx_chain_t *cl; + + cl = ngx_alloc_chain_link(ts->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + cl->buf = b; + cl->next = ts->free; + ts->free = cl; + + return NGX_OK; +} + + +void +ngx_ts_free_chain(ngx_ts_stream_t *ts, ngx_chain_t **ll) +{ + ngx_chain_t **fl; + + if (*ll == NULL) { + return; + } + + fl = ll; + + while (*ll) { + ll = &(*ll)->next; + } + + *ll = ts->free; + ts->free = *fl; + + *fl = NULL; +} + + +static ngx_int_t +ngx_ts_append_buf(ngx_ts_stream_t *ts, ngx_ts_header_t *h, ngx_chain_t **ll, + ngx_buf_t *b) +{ + ngx_chain_t *cl; + + if (b == NULL) { + return NGX_OK; + } + + if (!h->pusi && *ll == NULL) { + ngx_log_error(NGX_LOG_INFO, ts->log, 0, "dropping orhaned TS packet"); + return ngx_ts_free_buf(ts, b); + } + + if (h->pusi && *ll) { + ngx_log_error(NGX_LOG_INFO, ts->log, 0, + "dropping unfinished TS packets"); + ngx_ts_free_chain(ts, ll); + } + + while (*ll) { + ll = &(*ll)->next; + } + + cl = ngx_alloc_chain_link(ts->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + cl->buf = b; + cl->next = NULL; + + *ll = cl; + + return NGX_OK; +} + + +static uint32_t +ngx_ts_crc32(u_char *p, size_t len) +{ + uint32_t crc; + + crc = 0xffffffff; + + while (len--) { + crc = ngx_ts_crc32_table_ieee[(crc ^ *p++) & 0xff] ^ (crc >> 8); + } + + return crc; +} + + +ngx_uint_t +ngx_ts_dash_get_oti(u_char type) +{ + /* + * ISO/IEC 14496-1:2001(E) + * Table 8 - objectTypeIndication Values, p. 30 + */ + + switch (type) { + case NGX_TS_VIDEO_MPEG1: + return 0x6a; + + case NGX_TS_VIDEO_MPEG2: + /* treat as Main Profile */ + return 0x61; + + case NGX_TS_VIDEO_MPEG4: + return 0x20; + + case NGX_TS_VIDEO_AVC: + return 0x21; + + case NGX_TS_AUDIO_MPEG1: + return 0x6b; + + case NGX_TS_AUDIO_MPEG2: + return 0x69; + + case NGX_TS_AUDIO_AAC: + /* consider as ISO/IEC 14496-3 Audio */ + return 0x40; + + default: + return 0; + } +} diff --git a/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_stream.h b/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_stream.h new file mode 100644 index 0000000000..bd031abacb --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/modules/nginx-ts-module/src/ngx_ts_stream.h @@ -0,0 +1,113 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include + + +#ifndef _NGX_TS_STREAM_H_INCLUDED_ +#define _NGX_TS_STREAM_H_INCLUDED_ + + +/* + * ISO/IEC 13818-1 : 2000 (E) + * Table 2-29 – Stream type assignments, p. 48 + * + * ISO/IEC 13818-1:2007/Amd.3:2009 (E) + * Table 2-34 – Stream type assignments, p. 6 + */ + +#define NGX_TS_VIDEO_MPEG1 0x01 /* ISO/IEC 11172-2, MPEG-1 Video */ +#define NGX_TS_VIDEO_MPEG2 0x02 /* ISO/IEC 13818-2, MPEG-2 Video */ +#define NGX_TS_VIDEO_MPEG4 0x10 /* ISO/IEC 14496-2, MPEG-4 Video */ +#define NGX_TS_VIDEO_AVC 0x1b /* ISO/IEC 14496-10, AVC */ + +#define NGX_TS_AUDIO_MPEG1 0x03 /* ISO/IEC 11172-3, MPEG-1 Audio */ +#define NGX_TS_AUDIO_MPEG2 0x04 /* ISO/IEC 13818-3, MPEG-2 Audio */ +#define NGX_TS_AUDIO_AAC 0x0f /* ISO/IEC 13818-7, MPEG-2 AAC ADTS Audio */ + + +typedef enum { + NGX_TS_PAT = 0, + NGX_TS_PMT, + NGX_TS_PES +} ngx_ts_event_e; + + +typedef struct { + u_char type; + u_char sid; + u_char cont; + uint16_t pid; + uint64_t pts; + uint64_t dts; + unsigned ptsf:1; + unsigned rand:1; + unsigned video:1; + ngx_chain_t *bufs; /* ES */ +} ngx_ts_es_t; + + +typedef struct { + uint16_t number; + uint16_t pid; + uint16_t pcr_pid; + uint64_t pcr; + ngx_uint_t video; /* unisgned video:1; */ + ngx_uint_t nes; + ngx_ts_es_t *es; + ngx_chain_t *bufs; /* PMT */ +} ngx_ts_program_t; + + +typedef struct ngx_ts_handler_s ngx_ts_handler_t; + + +typedef struct { + ngx_uint_t nprogs; + ngx_ts_program_t *progs; + ngx_log_t *log; + ngx_pool_t *pool; + ngx_buf_t *buf; + ngx_chain_t *free; + ngx_chain_t *bufs; /* PAT */ + ngx_ts_handler_t *handlers; + void *data; +} ngx_ts_stream_t; + + +typedef struct { + ngx_ts_event_e event; + ngx_ts_stream_t *ts; + ngx_ts_program_t *prog; + ngx_ts_es_t *es; + ngx_chain_t *bufs; + void *data; +} ngx_ts_handler_data_t; + + +typedef ngx_int_t (*ngx_ts_handler_pt)(ngx_ts_handler_data_t *hd); + + +struct ngx_ts_handler_s { + ngx_ts_handler_pt handler; + void *data; + ngx_ts_handler_t *next; +}; + + +ngx_int_t ngx_ts_add_handler(ngx_ts_stream_t *ts, ngx_ts_handler_pt handler, + void *data); +ngx_int_t ngx_ts_read(ngx_ts_stream_t *ts, ngx_chain_t *in); +ngx_chain_t *ngx_ts_write_pat(ngx_ts_stream_t *ts, ngx_ts_program_t *prog); +ngx_chain_t *ngx_ts_write_pmt(ngx_ts_stream_t *ts, ngx_ts_program_t *prog); +ngx_chain_t *ngx_ts_write_pes(ngx_ts_stream_t *ts, ngx_ts_program_t *prog, + ngx_ts_es_t *es, ngx_chain_t *bufs); +void ngx_ts_free_chain(ngx_ts_stream_t *ts, ngx_chain_t **ll); +ngx_uint_t ngx_ts_dash_get_oti(u_char type); + + +#endif /* _NGX_TS_STREAM_H_INCLUDED_ */ diff --git a/package/lienol/luci-app-nginx-pingos/patches/101-feature_test_fix.patch b/package/lienol/luci-app-nginx-pingos/patches/101-feature_test_fix.patch new file mode 100644 index 0000000000..b867c88dd2 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/patches/101-feature_test_fix.patch @@ -0,0 +1,116 @@ +--- a/auto/cc/name ++++ b/auto/cc/name +@@ -7,7 +7,7 @@ if [ "$NGX_PLATFORM" != win32 ]; then + + ngx_feature="C compiler" + ngx_feature_name= +- ngx_feature_run=yes ++ ngx_feature_run= + ngx_feature_incs= + ngx_feature_path= + ngx_feature_libs= +--- a/auto/cc/conf ++++ b/auto/cc/conf +@@ -183,7 +183,7 @@ if [ "$NGX_PLATFORM" != win32 ]; then + else + ngx_feature="gcc builtin atomic operations" + ngx_feature_name=NGX_HAVE_GCC_ATOMIC +- ngx_feature_run=yes ++ ngx_feature_run=no + ngx_feature_incs= + ngx_feature_path= + ngx_feature_libs= +@@ -204,7 +204,7 @@ if [ "$NGX_PLATFORM" != win32 ]; then + else + ngx_feature="C99 variadic macros" + ngx_feature_name="NGX_HAVE_C99_VARIADIC_MACROS" +- ngx_feature_run=yes ++ ngx_feature_run=no + ngx_feature_incs="#include + #define var(dummy, ...) sprintf(__VA_ARGS__)" + ngx_feature_path= +@@ -218,7 +218,7 @@ if [ "$NGX_PLATFORM" != win32 ]; then + + ngx_feature="gcc variadic macros" + ngx_feature_name="NGX_HAVE_GCC_VARIADIC_MACROS" +- ngx_feature_run=yes ++ ngx_feature_run=no + ngx_feature_incs="#include + #define var(dummy, args...) sprintf(args)" + ngx_feature_path= +--- a/auto/os/linux ++++ b/auto/os/linux +@@ -36,7 +36,7 @@ fi + + ngx_feature="epoll" + ngx_feature_name="NGX_HAVE_EPOLL" +-ngx_feature_run=yes ++ngx_feature_run=no + ngx_feature_incs="#include " + ngx_feature_path= + ngx_feature_libs= +@@ -110,7 +110,7 @@ ngx_feature_test="int fd; struct stat sb + CC_AUX_FLAGS="$cc_aux_flags -D_GNU_SOURCE" + ngx_feature="sendfile()" + ngx_feature_name="NGX_HAVE_SENDFILE" +-ngx_feature_run=yes ++ngx_feature_run=no + ngx_feature_incs="#include + #include " + ngx_feature_path= +@@ -131,7 +131,7 @@ fi + CC_AUX_FLAGS="$cc_aux_flags -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64" + ngx_feature="sendfile64()" + ngx_feature_name="NGX_HAVE_SENDFILE64" +-ngx_feature_run=yes ++ngx_feature_run=no + ngx_feature_incs="#include + #include " + ngx_feature_path= +@@ -149,7 +149,7 @@ ngx_include="sys/prctl.h"; . auto/includ + + ngx_feature="prctl(PR_SET_DUMPABLE)" + ngx_feature_name="NGX_HAVE_PR_SET_DUMPABLE" +-ngx_feature_run=yes ++ngx_feature_run=no + ngx_feature_incs="#include " + ngx_feature_path= + ngx_feature_libs= +--- a/auto/unix ++++ b/auto/unix +@@ -840,7 +840,7 @@ ngx_feature_test="void *p; p = memalign( + + ngx_feature="mmap(MAP_ANON|MAP_SHARED)" + ngx_feature_name="NGX_HAVE_MAP_ANON" +-ngx_feature_run=yes ++ngx_feature_run=no + ngx_feature_incs="#include " + ngx_feature_path= + ngx_feature_libs= +@@ -853,7 +853,7 @@ ngx_feature_test="void *p; + + ngx_feature='mmap("/dev/zero", MAP_SHARED)' + ngx_feature_name="NGX_HAVE_MAP_DEVZERO" +-ngx_feature_run=yes ++ngx_feature_run=no + ngx_feature_incs="#include + #include + #include " +@@ -868,7 +868,7 @@ ngx_feature_test='void *p; int fd; + + ngx_feature="System V shared memory" + ngx_feature_name="NGX_HAVE_SYSVSHM" +-ngx_feature_run=yes ++ngx_feature_run=no + ngx_feature_incs="#include + #include " + ngx_feature_path= +@@ -882,7 +882,7 @@ ngx_feature_test="int id; + + ngx_feature="POSIX semaphores" + ngx_feature_name="NGX_HAVE_POSIX_SEM" +-ngx_feature_run=yes ++ngx_feature_run=no + ngx_feature_incs="#include " + ngx_feature_path= + ngx_feature_libs= diff --git a/package/lienol/luci-app-nginx-pingos/patches/102-sizeof_test_fix.patch b/package/lienol/luci-app-nginx-pingos/patches/102-sizeof_test_fix.patch new file mode 100644 index 0000000000..7d2430eab9 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/patches/102-sizeof_test_fix.patch @@ -0,0 +1,27 @@ +--- a/auto/types/sizeof ++++ b/auto/types/sizeof +@@ -25,8 +25,14 @@ $NGX_INCLUDE_UNISTD_H + $NGX_INCLUDE_INTTYPES_H + $NGX_INCLUDE_AUTO_CONFIG_H + ++char object_code_block[] = { ++ '\n', 'e', '4', 'V', 'A', ++ '0', 'x', ('0' + sizeof($ngx_type)), ++ 'Y', '3', 'p', 'M', '\n' ++}; ++ + int main(void) { +- printf("%d", (int) sizeof($ngx_type)); ++ printf("dummy use of object_code_block to avoid gc-section: %c", object_code_block[0]); + return 0; + } + +@@ -40,7 +46,7 @@ eval "$ngx_test >> $NGX_AUTOCONF_ERR 2>& + + + if [ -x $NGX_AUTOTEST ]; then +- ngx_size=`$NGX_AUTOTEST` ++ ngx_size=`sed -ne 's/^e4VA0x\(.\)Y3pM$/\1/p' < $NGX_AUTOTEST` + echo " $ngx_size bytes" + fi + diff --git a/package/lienol/luci-app-nginx-pingos/patches/103-sys_nerr.patch b/package/lienol/luci-app-nginx-pingos/patches/103-sys_nerr.patch new file mode 100644 index 0000000000..5f5d106fed --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/patches/103-sys_nerr.patch @@ -0,0 +1,12 @@ +--- a/src/os/unix/ngx_errno.c ++++ b/src/os/unix/ngx_errno.c +@@ -8,6 +8,9 @@ + #include + #include + ++#ifndef NGX_SYS_NERR ++#define NGX_SYS_NERR 128 ++#endif + + /* + * The strerror() messages are copied because: diff --git a/package/lienol/luci-app-nginx-pingos/patches/104-endianness_fix.patch b/package/lienol/luci-app-nginx-pingos/patches/104-endianness_fix.patch new file mode 100644 index 0000000000..081bcdb00a --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/patches/104-endianness_fix.patch @@ -0,0 +1,21 @@ +diff --git a/auto/endianness b/auto/endianness +index 1b552b6b..2b6f9ea4 100644 +--- a/auto/endianness ++++ b/auto/endianness +@@ -12,6 +12,16 @@ checking for system byte ordering + + END + ++if [ "${CONFIG_BIG_ENDIAN}" != "y" ]; then ++ echo " little endian" ++ have=NGX_HAVE_LITTLE_ENDIAN . auto/have ++else ++ echo " big endian" ++fi ++ ++return ++ ++ + + cat << END > $NGX_AUTOTEST.c + diff --git a/package/lienol/luci-app-nginx-pingos/patches/200-config.patch b/package/lienol/luci-app-nginx-pingos/patches/200-config.patch new file mode 100644 index 0000000000..f35009576a --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/patches/200-config.patch @@ -0,0 +1,18 @@ +--- a/conf/nginx.conf ++++ b/conf/nginx.conf +@@ -1,5 +1,5 @@ + +-#user nobody; ++user nobody nogroup; + worker_processes 1; + + #error_log logs/error.log; +@@ -16,7 +16,7 @@ events { + + http { + include mime.types; +- default_type application/octet-stream; ++ #default_type application/octet-stream; + + #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + # '$status $body_bytes_sent "$http_referer" ' diff --git a/package/lienol/luci-app-nginx-pingos/patches/201-ignore-invalid-options.patch b/package/lienol/luci-app-nginx-pingos/patches/201-ignore-invalid-options.patch new file mode 100644 index 0000000000..88b521353c --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/patches/201-ignore-invalid-options.patch @@ -0,0 +1,12 @@ +--- a/auto/options ++++ b/auto/options +@@ -396,8 +396,7 @@ $0: warning: the \"--with-sha1-asm\" opt + --test-build-solaris-sendfilev) NGX_TEST_BUILD_SOLARIS_SENDFILEV=YES ;; + + *) +- echo "$0: error: invalid option \"$option\"" +- exit 1 ++ echo "$0: error: ignoring invalid option \"$option\"" + ;; + esac + done diff --git a/package/lienol/luci-app-nginx-pingos/po/zh-cn/pingos.po b/package/lienol/luci-app-nginx-pingos/po/zh-cn/pingos.po new file mode 100644 index 0000000000..39eebbeade --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/po/zh-cn/pingos.po @@ -0,0 +1,41 @@ +msgid "Running Status" +msgstr "运行状态" + +msgid "Status" +msgstr "状态" + +msgid "Collecting data..." +msgstr "正在收集数据..." + +msgid "NOT RUNNING" +msgstr "未运行" + +msgid "RUNNING" +msgstr "运行中" + +msgid "If you need external network access, please open the port by yourself." +msgstr "如需外网访问,请自行放行端口。" + +msgid "Global Settings" +msgstr "全局设置" + +msgid "Edit Template" +msgstr "编辑模板" + +msgid "Edit the template that is used for generating the %s configuration." +msgstr "编辑生成 %s 的模板" + +msgid "This is the content of the file '%s'" +msgstr "这是文件内容:%s" + +msgid "Values enclosed by pipe symbols ('|') should not be changed. They get their values from the '%s' tab." +msgstr "由管道符(“|”)包围的值不应更改。它们将从 '%s' 标签中获取其值。" + +msgid "Record" +msgstr "录像" + +msgid "Path" +msgstr "路径" + +msgid "Stream name" +msgstr "流名" diff --git a/package/lienol/luci-app-nginx-pingos/root/etc/config/pingos b/package/lienol/luci-app-nginx-pingos/root/etc/config/pingos new file mode 100644 index 0000000000..2c6bfc8581 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/root/etc/config/pingos @@ -0,0 +1,10 @@ + +config global + option https '0' + option enable '0' + option ts_record '0' + option flv_record '0' + option http_port '18080' + option rtmp_port '1935' + option hls '1' + option hls2 '1' diff --git a/package/lienol/luci-app-nginx-pingos/root/etc/init.d/pingos b/package/lienol/luci-app-nginx-pingos/root/etc/init.d/pingos new file mode 100644 index 0000000000..a27f54390f --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/root/etc/init.d/pingos @@ -0,0 +1,65 @@ +#!/bin/sh /etc/rc.common +# Copyright (C) 2020 Lienol + +START=99 + +CONFIG="pingos" + +TEMP_PATH="/var/etc/$CONFIG" +NGINX_CONFIG="$TEMP_PATH/nginx.conf" + +config_t_get() { + local index=0 + [ -n "$4" ] && index=$4 + local ret=$(uci get $CONFIG.@$1[$index].$2 2>/dev/null) + echo ${ret:=$3} +} + +gen_nginx_config() { + http_port=$(config_t_get global http_port) + [ "$(config_t_get global https 0)" == "1" ] && { + http_port="$http_port ssl" + ssl_certificate="ssl_certificate $(config_t_get global certificate);" + ssl_certificate_key="ssl_certificate_key $(config_t_get global key);" + } + rtmp_port=$(config_t_get global rtmp_port) + hls=$(config_t_get global hls 0) + [ $hls == 1 ] && hls=on || hls=off + hls2=$(config_t_get global hls2 0) + [ $hls2 == 1 ] && hls2=on || hls2=off + ts_record=$(config_t_get global ts_record 0) + [ $ts_record == 1 ] && ts_record=on || ts_record=off + flv_record=$(config_t_get global flv_record 0) + [ $flv_record == 1 ] && flv_record=all || flv_record=off + record_path=$(config_t_get global record_path "$TEMP_PATH/record") + + sed -e "s#|TMP_PATH|#$TEMP_PATH#g" \ + -e "s#|HTTP_PORT|#$http_port#g" \ + -e "s#|ssl_certificate|#$ssl_certificate#g" \ + -e "s#|ssl_certificate_key|#$ssl_certificate_key#g" \ + -e "s#|RTMP_PORT|#$rtmp_port#g" \ + -e "s#|HLS|#$hls#g" \ + -e "s#|HLS2|#$hls2#g" \ + -e "s#|TS_RECORD|#$ts_record#g" \ + -e "s#|FLV_RECORD|#$flv_record#g" \ + -e "s#|RECORD_PATH|#$record_path#g" \ + /etc/$CONFIG.template > $1 +} + +start() { + ENABLED=$(config_t_get global enable 0) + [ "$ENABLED" = "0" ] && return 0 + mkdir -p $TEMP_PATH $TEMP_PATH/lib + gen_nginx_config $NGINX_CONFIG + /usr/sbin/pingos -c $NGINX_CONFIG >/dev/null 2>&1 & +} + +stop() { + /usr/sbin/pingos -c $NGINX_CONFIG -s stop >/dev/null 2>&1 + rm -rf $TEMP_PATH +} + +restart() { + stop + start +} \ No newline at end of file diff --git a/package/lienol/luci-app-nginx-pingos/root/etc/pingos.template b/package/lienol/luci-app-nginx-pingos/root/etc/pingos.template new file mode 100644 index 0000000000..402d75b24e --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/root/etc/pingos.template @@ -0,0 +1,207 @@ +user root; +daemon on; +master_process on; +worker_processes 1; +#worker_rlimit 4g; + +#error_log |TMP_PATH|/error.log; +#error_log |TMP_PATH|/error.log notice; +error_log |TMP_PATH|/error.log info; + +worker_rlimit_nofile 102400; +worker_rlimit_core 2G; +working_directory |TMP_PATH|; + +pid |TMP_PATH|/pingos.pid; + +events { + use epoll; + worker_connections 4096; + multi_listen unix:|TMP_PATH|/http |HTTP_PORT|; + multi_listen unix:|TMP_PATH|/rtmp |RTMP_PORT|; + + dynamic_refresh_interval 5s; + dynamic_domain_buckets 1001; + resolver 114.114.114.114 valid=1m; + resolver_timeout 30s; +} + +#stream_zone buckets=1024 streams=4096; + +#dynamic_conf conf/nginx_dynamic.conf 10; +#dynamic_log |TMP_PATH|/dynamic.log info; + +rtmp { + log_format log_bandwidth '{"app":"$app","name":"$name","bitrate":$bitrate,"args":"$args","timestamp":$ntp,"ts":"$time_local","type":"$command","remote_addr":"$remote_addr","domain":"$domain"}'; + access_log |TMP_PATH|/bandwidth.log log_bandwidth trunc=60s; + + server { + listen |RTMP_PORT|; + serverid 000; + out_queue 2048; + server_name live.pingos.io; + rtmp_auto_pull on; + rtmp_auto_pull_port unix:|TMP_PATH|/rtmp; + + application push { + live on; + push rtmp://127.0.0.1:|RTMP_PORT|/live app=live; + } + + application live { + live_record |TS_RECORD|; + live_record_path |RECORD_PATH|; + + recorder r1{ + record |FLV_RECORD|; + record_path |RECORD_PATH|; + } + + live on; + hls |HLS|; + hls_path |TMP_PATH|/hls; + hls_fragment 4000ms; + #hls_max_fragment 10000ms; + hls_playlist_length 12000ms; + hls_type live; + + hls2 |HLS2|; + mpegts_cache_time 20s; + + hls2_fragment 2000ms; + hls2_max_fragment 3000ms; + hls2_playlist_length 6000ms; + + wait_key on; + wait_video on; + cache_time 1s; + send_all on; + low_latency off; + fix_timestamp 2s; + # h265 codecid, default 12 + hevc_codecid 12; + } + } +} + +http { + include /usr/share/pingos/conf/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_X-Forwarded-For" "$http_X-Real-IP" "$host"'; + + + access_log |TMP_PATH|/access.log main; + + sendfile on; + #tcp_nopush on; + + #keepalive_timeout 0; + keepalive_timeout 65; + + #reset_server_name www.test1.com www.test2.com; + #gzip on; + + upstream hlsm { + #hash $remote_addr consistent; + hash $arg_session consistent; + # 这里需要注意,你要开几个进程,就要按这个规则写几条记录 + server unix:|TMP_PATH|/http.0; + #server unix:|TMP_PATH|/http.1; + } + + server { + listen |HTTP_PORT|; + |ssl_certificate| + |ssl_certificate_key| + ssl_session_cache shared:SSL:1m; + ssl_session_timeout 5m; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + + location /rtmp_stat { + rtmp_stat all; + rtmp_stat_stylesheet /stat.xsl; + } + + location /xstat { + rtmp_stat all; + } + + location /sys_stat { + sys_stat; + } + location ~ .mp4$ { + root /usr/share/pingos/html; + #mp4; + } + + location /control { + rtmp_control all; + } + location /flv { + flv_live |RTMP_PORT| app=live; + add_header 'Access-Control-Allow-Origin' '*'; + add_header "Access-Control-Allow-Credentials" "true"; + add_header "Access-Control-Allow-Methods" "*"; + add_header "Access-Control-Allow-Headers" "Content-Type,Access-Token"; + add_header "Access-Control-Expose-Headers" "*"; + } + location /ts { + ts_live |RTMP_PORT| app=live; + expires -1; + add_header 'Access-Control-Allow-Origin' '*'; + add_header "Access-Control-Allow-Credentials" "true"; + add_header "Access-Control-Allow-Methods" "*"; + add_header "Access-Control-Allow-Headers" "Content-Type,Access-Token"; + add_header "Access-Control-Expose-Headers" "*"; + } + location /hls { + # Serve HLS fragments + types { + application/vnd.apple.mpegurl m3u8; + video/mp2t ts; + } + root |TMP_PATH|; + expires -1; + add_header Cache-Control no-cache; + add_header 'Access-Control-Allow-Origin' '*'; + add_header "Access-Control-Allow-Credentials" "true"; + add_header "Access-Control-Allow-Methods" "*"; + add_header "Access-Control-Allow-Headers" "Content-Type,Access-Token"; + add_header "Access-Control-Expose-Headers" "*"; + } + + location /hlsm { + hls2_live |RTMP_PORT| app=live; + add_header 'Access-Control-Allow-Origin' '*'; + add_header Cache-Control no-cache; + add_header "Access-Control-Allow-Credentials" "true"; + add_header "Access-Control-Allow-Methods" "*"; + add_header "Access-Control-Allow-Headers" "Content-Type,Access-Token"; + add_header "Access-Control-Expose-Headers" "*"; + } + + location /hls2 { + proxy_buffering on; + proxy_buffer_size 4k; + proxy_buffers 8 1M; + proxy_busy_buffers_size 2M; + proxy_max_temp_file_size 0; + set $hls_args location=/hls2&scheme=$scheme; +# if ($args) { +# set $hls_args $args&location=/hls2&scheme=$scheme; +# } + proxy_set_header Host $host:$server_port; + rewrite ^/(.*)/(.*)\.(.*)$ /hlsm/$2.$3?$hls_args break; + proxy_pass http://hlsm; + } + + location / { + chunked_transfer_encoding on; + root /usr/share/pingos/html/; + } + } +} diff --git a/package/lienol/luci-app-nginx-pingos/root/etc/uci-defaults/luci-app-nginx-pingos b/package/lienol/luci-app-nginx-pingos/root/etc/uci-defaults/luci-app-nginx-pingos new file mode 100644 index 0000000000..cd6d3ff86c --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/root/etc/uci-defaults/luci-app-nginx-pingos @@ -0,0 +1,11 @@ +#!/bin/sh + +uci -q batch <<-EOF >/dev/null + delete ucitrack.@pingos[-1] + add ucitrack pingos + set ucitrack.@pingos[-1].init=pingos + commit ucitrack +EOF + +rm -rf /tmp/luci-*cache +exit 0 diff --git a/package/lienol/luci-app-nginx-pingos/root/resource/conf-template/nginx.conf b/package/lienol/luci-app-nginx-pingos/root/resource/conf-template/nginx.conf new file mode 100644 index 0000000000..e22f688b57 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/root/resource/conf-template/nginx.conf @@ -0,0 +1,205 @@ +user root; +daemon on; +master_process on; +worker_processes 4; +#worker_rlimit 4g; + +#error_log logs/error.log; +#error_log logs/error.log notice; +error_log logs/error.log info; + +worker_rlimit_nofile 102400; +worker_rlimit_core 2G; +working_directory /tmp; + +pid logs/nginx.pid; + +events { + use epoll; + worker_connections 4096; + multi_listen unix:/tmp/http 8080; + multi_listen unix:/tmp/rtmp 1935; + + dynamic_refresh_interval 5s; + dynamic_domain_buckets 1001; + resolver 114.114.114.114 valid=1m; + resolver_timeout 30s; +} + +#stream_zone buckets=1024 streams=4096; + +#dynamic_conf conf/nginx_dynamic.conf 10; +#dynamic_log logs/dynamic.log info; + +rtmp { + log_format log_bandwidth '{"app":"$app","name":"$name","bitrate":$bitrate,"args":"$args","timestamp":$ntp,"ts":"$time_local","type":"$command","remote_addr":"$remote_addr","domain":"$domain"}'; + access_log logs/bandwidth.log log_bandwidth trunc=60s; + + server { + listen 1935; + serverid 000; + out_queue 2048; + server_name live.pingos.io; + rtmp_auto_pull on; + rtmp_auto_pull_port unix:/tmp/rtmp; + + application push { + live on; + push rtmp://127.0.0.1/live app=live; + } + + application live { + live_record on; + live_record_path /data/record; + + recorder r1{ + record all; + record_path /data/record; + } + + live on; + hls on; + hls_path /tmp/hls; + hls_fragment 4000ms; +# hls_max_fragment 10000ms; + hls_playlist_length 12000ms; + hls_type live; + + hls2 on; + mpegts_cache_time 20s; + + hls2_fragment 2000ms; + hls2_max_fragment 3000ms; + hls2_playlist_length 6000ms; + + wait_key on; + wait_video on; + cache_time 1s; + send_all on; + low_latency off; + fix_timestamp 2s; +# h265 codecid, default 12 + hevc_codecid 12; + } + } +} + +http { + include mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_X-Forwarded-For" "$http_X-Real-IP" "$host"'; + + + access_log logs/access.log main; + + sendfile on; + #tcp_nopush on; + + #keepalive_timeout 0; + keepalive_timeout 65; + + #reset_server_name www.test1.com www.test2.com; + #gzip on; + + upstream hlsm { +# hash $remote_addr consistent; + hash $arg_session consistent; + # 这里需要注意,你要开几个进程,就要按这个规则写几条记录 + server unix:/tmp/http.0; + server unix:/tmp/http.1; + server unix:/tmp/http.2; + server unix:/tmp/http.3; + } + + server { + listen 8080; +# listen 443 ssl; +# ssl_certificate /usr/local/pingos/cert/full_chain.pem; +# ssl_certificate_key /usr/local/pingos/cert/privkey.pem; + location /rtmp_stat { + rtmp_stat all; + rtmp_stat_stylesheet /stat.xsl; + } + + location /xstat { + rtmp_stat all; + } + + location /sys_stat { + sys_stat; + } + location ~ .mp4$ { + root html; + #mp4; + } + + location /control { + rtmp_control all; + } + location /flv { + flv_live 1935 app=live; + add_header 'Access-Control-Allow-Origin' '*'; + add_header "Access-Control-Allow-Credentials" "true"; + add_header "Access-Control-Allow-Methods" "*"; + add_header "Access-Control-Allow-Headers" "Content-Type,Access-Token"; + add_header "Access-Control-Expose-Headers" "*"; + } + location /ts { + ts_live 1935 app=live; + expires -1; + add_header 'Access-Control-Allow-Origin' '*'; + add_header "Access-Control-Allow-Credentials" "true"; + add_header "Access-Control-Allow-Methods" "*"; + add_header "Access-Control-Allow-Headers" "Content-Type,Access-Token"; + add_header "Access-Control-Expose-Headers" "*"; + } + location /hls { + # Serve HLS fragments + types { + application/vnd.apple.mpegurl m3u8; + video/mp2t ts; + } + root /tmp; + expires -1; + add_header Cache-Control no-cache; + add_header 'Access-Control-Allow-Origin' '*'; + add_header "Access-Control-Allow-Credentials" "true"; + add_header "Access-Control-Allow-Methods" "*"; + add_header "Access-Control-Allow-Headers" "Content-Type,Access-Token"; + add_header "Access-Control-Expose-Headers" "*"; + } + + location /hlsm { + hls2_live 1935 app=live; + add_header 'Access-Control-Allow-Origin' '*'; + add_header Cache-Control no-cache; + add_header "Access-Control-Allow-Credentials" "true"; + add_header "Access-Control-Allow-Methods" "*"; + add_header "Access-Control-Allow-Headers" "Content-Type,Access-Token"; + add_header "Access-Control-Expose-Headers" "*"; + } + + location /hls2 { + proxy_buffering on; + proxy_buffer_size 4k; + proxy_buffers 8 1M; + proxy_busy_buffers_size 2M; + proxy_max_temp_file_size 0; + set $hls_args location=/hls2&scheme=$scheme; +# if ($args) { +# set $hls_args $args&location=/hls2&scheme=$scheme; +# } + proxy_set_header Host $host:$server_port; + rewrite ^/(.*)/(.*)\.(.*)$ /hlsm/$2.$3?$hls_args break; + proxy_pass http://hlsm; + } + + location / { + chunked_transfer_encoding on; + root html/; + } + } +} diff --git a/package/lienol/luci-app-nginx-pingos/root/resource/crossdomain.xml b/package/lienol/luci-app-nginx-pingos/root/resource/crossdomain.xml new file mode 100644 index 0000000000..e5b559ad4c --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/root/resource/crossdomain.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/package/lienol/luci-app-nginx-pingos/root/resource/stat.xsl b/package/lienol/luci-app-nginx-pingos/root/resource/stat.xsl new file mode 100644 index 0000000000..0e433c7509 --- /dev/null +++ b/package/lienol/luci-app-nginx-pingos/root/resource/stat.xsl @@ -0,0 +1,355 @@ + + + + + + + + + + + + + RTMP statistics + + + +
+ Generated by + PingOS , + nginx , + pid , + built   + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RTMP#clientsVideoAudioIn bytesOut bytesIn bits/sOut bits/sStateTime
Accepted: codecbits/ssizefpscodecbits/sfreqchan + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + live streams + + + + + + + + + + + + vod streams + + + + + + + + + + + + + #cccccc + #dddddd + + + + + + var d=document.getElementById('-'); + d.style.display=d.style.display=='none'?'':'none'; + return false + + + + [EMPTY] + + + + + +    + + + + + + + + + + + + + + + +   + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + +
IdStateAddressFlash versionPage URLSWF URLDroppedTimestampA-VTime
+ + +
+ + + + + + + + + + + d + + + + h + + + + m + + + s + + + + + + + + + + + + + T + + + G + + + M + + K + + + + b + B + + /s + + + + + + active + idle + + + + + + + publishing + playing + + + + + + + + + #cccccc + #eeeeee + + + + + + + + http://apps.db.ripe.net/search/query.html?searchtext= + + whois + + + + + + + + + + + + + + + + + + + + + + + + + + publishing + + + + active + + + + x + + +