├── .github └── workflows │ └── run_test_case.yaml ├── .gitignore ├── .travis.yml ├── CHANGES ├── LICENSE ├── Makefile ├── README.md ├── docs ├── proxy-protocol.txt └── reuseaddr.txt ├── examples ├── README.md ├── active_n │ └── recv_eprof.erl ├── async_recv │ └── async_recv_echo_server.erl ├── client │ └── echo_client.erl ├── dtls │ └── dtls_echo_server.erl ├── dtls_psk │ ├── dtls_psk_client.erl │ └── dtls_psk_echo_server.erl ├── gen_server │ └── gen_echo_server.erl ├── plain │ └── plain_echo_server.erl ├── proxy_protocol │ ├── haproxy.cfg │ └── proxy_protocol_server.erl ├── simple │ └── simple_echo_server.erl ├── tcp_window │ └── tcp_window.erl ├── tls │ └── ssl_echo_server.erl └── udp │ ├── .exrc │ └── udp_echo_server.erl ├── include ├── esockd.hrl └── esockd_proxy.hrl ├── priv ├── demo.crt └── demo.key ├── rebar.config ├── src ├── esockd.app.src ├── esockd.appup.src ├── esockd.erl ├── esockd_acceptor.erl ├── esockd_acceptor_sup.erl ├── esockd_access.erl ├── esockd_app.erl ├── esockd_cidr.erl ├── esockd_connection_sup.erl ├── esockd_dtls_acceptor.erl ├── esockd_dtls_listener.erl ├── esockd_generic_limiter.erl ├── esockd_limiter.erl ├── esockd_listener.erl ├── esockd_listener_sup.erl ├── esockd_peercert.erl ├── esockd_proxy_protocol.erl ├── esockd_rate_limit.erl ├── esockd_server.erl ├── esockd_ssl.erl ├── esockd_sup.erl ├── esockd_transport.erl ├── esockd_udp.erl └── udp_proxy │ ├── doc │ ├── proxy.plantuml │ └── proxy.png │ ├── esockd_udp_proxy.erl │ ├── esockd_udp_proxy_connection.erl │ └── esockd_udp_proxy_db.erl └── test ├── async_echo_server.erl ├── certs ├── ca.crt ├── ca.key ├── change.crt ├── change.key ├── test.crt └── test.key ├── const_server.erl ├── const_udp_server.erl ├── dtls_echo_server.erl ├── echo_server.erl ├── esockd_SUITE.erl ├── esockd_acceptor_SUITE.erl ├── esockd_access_SUITE.erl ├── esockd_cidr_SUITE.erl ├── esockd_connection_sup_SUITE.erl ├── esockd_ct.erl ├── esockd_dtls_SUITE.erl ├── esockd_generic_limiter_SUITE.erl ├── esockd_limiter_SUITE.erl ├── esockd_peercert_SUITE.erl ├── esockd_proxy_protocol_SUITE.erl ├── esockd_rate_limit_SUITE.erl ├── esockd_server_SUITE.erl ├── esockd_ssl_SUITE.erl ├── esockd_transport_SUITE.erl ├── esockd_udp_SUITE.erl └── udp_echo_server.erl /.github/workflows/run_test_case.yaml: -------------------------------------------------------------------------------- 1 | name: Run test case 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | 7 | run_test_case: 8 | 9 | runs-on: ubuntu-latest 10 | 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | otp: 15 | - erlang:27 16 | - erlang:26 17 | - erlang:25 18 | 19 | container: 20 | image: ${{ matrix.otp }} 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | - name: Code dialyzer 25 | run: | 26 | make xref 27 | make dialyzer 28 | - name: Run tests 29 | run: | 30 | make eunit 31 | make ct 32 | make cover 33 | 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .eunit 2 | deps 3 | *.o 4 | *.beam 5 | *.plt 6 | erl_crash.dump 7 | ebin 8 | rel/example_project 9 | .concrete/DEV_MODE 10 | .rebar 11 | .idea 12 | examples/*/ebin 13 | .idea/ 14 | .erlang.mk/ 15 | esockd.d 16 | test/ct.cover.spec 17 | examples/*/*.d 18 | docs/_build/ 19 | ct.coverdata 20 | eunit.coverdata 21 | logs/ 22 | examples/async_recv/async_recv_echo_server.d 23 | examples/client/echo_client.d 24 | examples/gen_server/gen_echo_server.d 25 | examples/plain/echo_server.d 26 | examples/ssl/ssl_echo_server.d 27 | cover/ 28 | .DS_Store 29 | erlang.mk 30 | _build/ 31 | rebar3.crashdump 32 | *.swp 33 | .rebar3/ 34 | rebar.lock 35 | TEST-*.xml 36 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: erlang 2 | 3 | otp_release: 4 | - 21.0.4 5 | 6 | script: 7 | - make 8 | 9 | sudo: false 10 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | 2 | eSockd ChangeLog 3 | ================ 4 | 5 | 5.4-beta (2018/07/20) 6 | --------------------- 7 | 8 | Support UDP/DTLS servers. 9 | 10 | 5.3-beta (2018/05/20) 11 | --------------------- 12 | 13 | Erlang/OTP R21 Compatible 14 | 15 | 5.0-beta (2017/03/25) 16 | --------------------- 17 | 18 | Proxy Protocol(http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt) 19 | 20 | 4.2 (2017/03/14) 21 | ------------------- 22 | 23 | API Breaking Change '{ssl, list()}' to '{sslopts, [ssl:ssl_option()]}' 24 | 25 | Add esockd_connection:peercert/1, esockd_transport:peercert/1 functions 26 | 27 | Add esockd_connection:gc/1, esockd_transport:gc/1 functions. 28 | 29 | 4.1.1 (2017/01/06) 30 | --------------------- 31 | 32 | Fast close the TCP socket if ssl:ssl_accept failed (#41) 33 | 34 | 4.1-beta (2016/06/30) 35 | --------------------- 36 | 37 | UDP Server 38 | 39 | 4.0-beta (2016/05/24) 40 | --------------------- 41 | 42 | IPv6 Support 43 | 44 | Listen on specific IP Address 45 | 46 | 3.2-beta (2016/05/23) 47 | --------------------- 48 | 49 | Issue #25 - {accept_error,econnaborted} crash report 50 | 51 | Issue #27 - acceptor crasher: {accept_error,econnaborted} 52 | 53 | Issue #26 - acceptor - system_limit error 54 | 55 | 3.1-beta (2016/01/31) 56 | ---------------------- 57 | 58 | Replace rebar with erlang.mk 59 | 60 | 3.0-beta (2015/10/30) 61 | ------------------------ 62 | 63 | Parameterized Connection Module 64 | 65 | Rate Limit 66 | 67 | Add esockd_gen.erl 68 | 69 | 70 | 2.8.0-beta (2015/10/28) 71 | ------------------------ 72 | 73 | issue #16 - Rate Limiting based on Token Bucket Algorithm 74 | 75 | 76 | 2.7.1-beta (2015/09/10) 77 | ------------------------ 78 | 79 | Improve esock:with_listener/2 80 | 81 | 82 | 2.7.0-beta (2015/09/08) 83 | ------------------------ 84 | 85 | issue #10: inet:peername(Sock) call return {error, 'enotconn'} on AliYun node 86 | 87 | issue #11: Too many connection_shutdown report logs when connections from unstable mobile network 88 | 89 | issue #12: Fast close the socket when server side error occurred? 90 | 91 | esockd.erl: add get_shutdown_count/1, with_listener/2 api 92 | 93 | 94 | 2.6.0-beta (2015/07/28) 95 | ------------------------ 96 | 97 | Keepalive 98 | 99 | 100 | 2.5.2-beta (2015/05/04) 101 | ------------------------ 102 | 103 | dialyzer support 104 | 105 | 106 | 2.5.1-beta (2015/05/01) 107 | ------------------------ 108 | 109 | update README.md 110 | 111 | update esockd:option/0 type 112 | 113 | 114 | 2.5.0-beta (2015/05/01) 115 | ------------------------ 116 | 117 | support 'sockopts' option 118 | 119 | remove esockd:sockopts/1 120 | 121 | 122 | 2.4.0-beta (2015/04/29) 123 | ------------------------ 124 | 125 | esockd_transport: add type/1 API 126 | 127 | Changes: update comments format 128 | 129 | 130 | 2.3.0-beta (2015/04/09) 131 | ------------------------ 132 | 133 | Bugfix: issue #9 - enotconn error occured when call ‘inet:peername(Sock)’ in esockd_acceptor.erl sometimes 134 | 135 | 136 | 2.2.3-alpha (2015/03/26) 137 | ------------------------ 138 | 139 | esockd_access: rewrite 'atoi/1' 140 | 141 | 142 | 2.2.2-alpha (2015/03/26) 143 | ------------------------ 144 | 145 | esockd_access: export 'cidr(), range()' types 146 | 147 | esockd_access: -spec range(cidr()) -> {pos_integer(), pos_integer()}. 148 | 149 | 150 | 2.2.1-alpha (2015/03/23) 151 | ------------------------ 152 | 153 | fix stupid '2500k' in README, CHANGELOG... 154 | 155 | 156 | 2.2.0-alpha (2015/03/22) 157 | ------------------------ 158 | 159 | esockd_connection_sup: merge supervisor:terminate/2 code 160 | 161 | esockd_connection_sup: add 'shutdown' option 162 | 163 | esockd: rename 'callback' to 'mfargs' 164 | 165 | esockd: add 'shutdown' option 166 | 167 | 168 | 2.1.0-alpha (2015/03/18) 169 | ------------------------ 170 | 171 | esockd_connections_sup: do not user supervisor, write a new implematation 172 | 173 | remove esockd_manager, merge its functions to the new esockd_connectioins_sup 174 | 175 | esockd_acceptor: fix the tune buffer issue. when set buffer as max of sndbuf, recbuf, esockd will consume too much memories 176 | 177 | esockd: add 'tune_buffer' option to tune 'buffer' for hight throughput socket 178 | 179 | esockd_transport: add recv/3 function 180 | 181 | Benchmark this release on one 8 cores, 32G memory ubuntu/14.04 server from QingCloud: 182 | 183 | ``` 184 | 250K concurrent connections, 50K messages/sec, 40Mbps In/Out Consumed 5G memory, 20% CPU/core 185 | ``` 186 | 187 | 188 | 2.0.1-alpha (2015/03/15) 189 | ------------------------ 190 | 191 | esockd.app.src: update vsn to 2.0.1 192 | 193 | update change logs 194 | 195 | 196 | 2.0.0-alpha (2015/03/15) 197 | ------------------------ 198 | 199 | Support [Allow/Deny](https://github.com/emqx/esockd/wiki/Allow-and-Deny) features like NGINX. 200 | 201 | esockd to add allow/2, deny/2, get_access_rules/1 APIs 202 | 203 | esockd_manager to add allow/2, deny/2, access_rules/1 APIs 204 | 205 | new esockd_access file 206 | 207 | 208 | v1.2.0-beta (2015/03/13) 209 | ------------------------ 210 | 211 | rename 'acceptor_pool' option to 'acceptors' 212 | 213 | 214 | v1.1.0-beta (2015/03/10) 215 | ------------------------ 216 | 217 | Make esockd_stats table public and write_concurrency 218 | 219 | esockd_acceptor: fix format of Accept log 220 | 221 | 222 | v1.0.0-beta (2015/03/03) 223 | ------------------------ 224 | 225 | First public release with stable API. 226 | 227 | 228 | v0.3.0-beta (2015/03/03) 229 | ------------------------ 230 | 231 | esockd add close/1, get_acceptor_pool/1, get_stats/1 apis 232 | 233 | esockd_acceptor_sup add count_acceptors/1 api 234 | 235 | esockd_acceptor add 'statsfun' to state 236 | 237 | esockd_net add format/2 api 238 | 239 | esockd_listener_sup: change 'restart strategy' from one_for_all to rest_for_one 240 | 241 | esockd.hrl: remove 'sock_args' record 242 | 243 | esockd_server add stats_fun/2, inc_stats/3, dec_stats/3, get_stats/1, del_stats/1 244 | 245 | 246 | v0.2.0-beta (2015/03/02) 247 | ------------------------ 248 | 249 | esockd.erl: add get_max_clients/1, set_max_clients/2, get_current_clients/1 250 | 251 | esockd_connection.erl: 'erlang:apply(M, F, [SockArgs|Args])' 252 | 253 | esockd_acceptor.erl: log protocol name 254 | 255 | gen_logger to replace error_logger, and support 'logger' option 256 | 257 | 258 | 0.1.1-beta (2015/03/01) 259 | ------------------------ 260 | 261 | Doc: update README.md 262 | 263 | 264 | 0.1.0-beta (2015/03/01) 265 | ------------------------ 266 | 267 | Add more exmaples 268 | 269 | Add esockd_server.erl 270 | 271 | Redesign esockd_manager.erl, esockd_connection_sup.erl 272 | 273 | 274 | 0.1.0-alpha (2015/02/28) 275 | ------------------------ 276 | 277 | First public release 278 | 279 | TCP/SSL Socket Server 280 | 281 | 282 | 0.0.2 (2014/12/07) 283 | ------------------------ 284 | 285 | acceptor suspend 100ms when accept 'emfile' error 286 | 287 | esockd_client_sup to control max connections 288 | 289 | esockd_client_sup: 'M:go(Client, Sock)' to tell gen_server the socket is ready 290 | 291 | esockd_acceptor: add 'tune_buffer_size' 292 | 293 | 294 | 0.0.1 (2014/11/30) 295 | ------------------------ 296 | 297 | redesign listener api 298 | 299 | first release... 300 | 301 | 302 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | REBAR := rebar3 2 | 3 | .PHONY: all 4 | all: compile 5 | 6 | compile: 7 | $(REBAR) compile 8 | 9 | .PHONY: clean 10 | clean: distclean 11 | 12 | .PHONY: distclean 13 | distclean: 14 | @rm -rf _build erl_crash.dump rebar3.crashdump rebar.lock 15 | 16 | .PHONY: xref 17 | xref: 18 | $(REBAR) xref 19 | 20 | .PHONY: eunit 21 | eunit: compile 22 | $(REBAR) eunit verbose=truen 23 | 24 | .PHONY: ct 25 | ct: compile 26 | $(REBAR) as test ct -v 27 | 28 | cover: 29 | $(REBAR) cover 30 | 31 | .PHONY: coveralls 32 | coveralls: 33 | @rebar3 as test coveralls send 34 | 35 | .PHONY: dialyzer 36 | dialyzer: 37 | $(REBAR) dialyzer 38 | 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # esockd [![Build status](https://github.com/emqx/esockd/actions/workflows/run_test_case.yaml/badge.svg)](https://github.com/emqx/esockd/actions/workflows/run_test_case.yaml) 2 | 3 | Erlang General Non-blocking TCP/SSL Socket Server. 4 | 5 | ## Features 6 | 7 | * General Non-blocking TCP/SSL Socket Server 8 | * Acceptor Pool and Asynchronous TCP Accept 9 | * UDP/DTLS Server 10 | * Max connections management 11 | * Allow/Deny by peer address 12 | * Proxy Protocol V1/V2 13 | * Keepalive Support 14 | * Rate Limit 15 | * IPv6 Support 16 | 17 | ## Usage 18 | 19 | A Simple TCP Echo Server: 20 | 21 | -module(echo_server). 22 | 23 | -export([start_link/2, init/2]). 24 | 25 | start_link(Transport, Sock) -> 26 | {ok, spawn_link(?MODULE, init, [Transport, Sock])}. 27 | 28 | init(Transport, Sock) -> 29 | case Transport:wait(Sock) of 30 | {ok, NewSock} -> 31 | loop(Transport, NewSock); 32 | Error -> Error 33 | end. 34 | 35 | loop(Transport, Sock) -> 36 | case Transport:recv(Sock, 0) of 37 | {ok, Data} -> 38 | {ok, Peername} = Transport:peername(Sock), 39 | Transport:send(Sock, Data), 40 | loop(Transport, Sock); 41 | {error, Reason} -> 42 | io:format("TCP Error: ~s~n", [Reason]), 43 | {stop, Reason} 44 | end. 45 | 46 | Setup Echo Server: 47 | 48 | %% Start esockd application 49 | ok = esockd:start(). 50 | Options = [{acceptors, 10}, {max_connections, 1024}, {tcp_options, [binary, {reuseaddr, true}]}]. 51 | MFArgs = {echo_server, start_link, []}, 52 | esockd:open(echo, 5000, Options, MFArgs). 53 | 54 | ## Examples 55 | 56 | Example | Description 57 | ------------------------|--------------------------- 58 | examples/async_recv | prim_net async recv/send 59 | examples/gen_server | gen_server behaviour 60 | examples/simple | simple echo server 61 | examples/ssl | ssl echo server 62 | examples/proxy_protocol | proxy protocol v1/2 63 | examples/udp | udp echo server 64 | examples/dtls | dtls echo server 65 | 66 | ## API 67 | 68 | ### Open a listener 69 | 70 | esockd:open(echo, 5000, [{tcp_options, [binary, {reuseaddr, true}]}], 71 | {echo_server, start_link, []}). 72 | 73 | esockd:open(echo, {"127.0.0.1", 6000}, [{tcp_options, [binary, {reuseaddr, true}]}], 74 | {echo_server, start_link, []}). 75 | 76 | Spec: 77 | 78 | -spec(open(Protocol, ListenOn, Options, MFArgs) -> {ok, pid()} | {error, term()} when 79 | Protocol :: atom(), 80 | ListenOn :: inet:port_number() | {host(), inet:port_number()}), 81 | Options :: [option()], 82 | MFArgs :: esockd:mfargs()). 83 | 84 | Option: 85 | 86 | -type(option() :: {acceptors, pos_integer()} 87 | | {max_connections, pos_integer()} 88 | | {max_conn_rate, pos_integer()} 89 | | {access_rules, [esockd_access:rule()]} 90 | | {shutdown, brutal_kill | infinity | pos_integer()} 91 | | tune_buffer | {tune_buffer, boolean()} 92 | | proxy_protocol | {proxy_protocol, boolean()} 93 | | {proxy_protocol_timeout, timeout()} 94 | | {ssl_options, [ssl:ssl_option()]} 95 | | {udp_options, [gen_udp:option()]} 96 | | {dtls_options, [gen_udp:option() | ssl:ssl_option()]}). 97 | 98 | MFArgs: 99 | 100 | -type(mfargs() :: atom() | {atom(), atom()} | {module(), atom(), [term()]}). 101 | 102 | ### Get Setting and Stats 103 | 104 | Get stats: 105 | 106 | esockd:get_stats({echo, 5000}). 107 | 108 | Get acceptors: 109 | 110 | esockd:get_acceptors({echo, {"127.0.0.1", 6000}}). 111 | 112 | Get/Set max connections: 113 | 114 | esockd:get_max_connections({echo, 5000}). 115 | esockd:set_max_connections({echo, 5000}, 100000). 116 | 117 | ### Allow/Deny 118 | 119 | Same to Allow/Deny Syntax of nginx: 120 | 121 | allow address | CIDR | all; 122 | 123 | deny address | CIDR | all; 124 | 125 | allow/deny by options: 126 | 127 | esockd:open(echo, 5000, [{access, [{deny, "192.168.1.1"}, {allow, "192.168.1.0/24"}, {deny, all}]}], MFArgs). 128 | 129 | allow/deny by API: 130 | 131 | esockd:allow({echo, 5000}, all). 132 | esockd:allow({echo, 5000}, "192.168.0.1/24"). 133 | esockd:deny({echo, 5000}, all). 134 | esockd:deny({echo, 5000}, "10.10.0.0/16"). 135 | 136 | ### Close a listener 137 | 138 | esockd:close(echo, 5000). 139 | esockd:close(echo, {"127.0.0.1", 6000}). 140 | 141 | Spec: 142 | 143 | -spec(close(Protocol, ListenOn) -> ok when Protocol :: atom(), ListenOn :: inet:port_number() | {host(), inet:port_number()}). 144 | 145 | ### SSL 146 | 147 | Connecting to ssl_echo_server: 148 | 149 | openssl s_client -connect 127.0.0.1:5000 -ssl3 150 | 151 | openssl s_client -connect 127.0.0.1:5000 -tls1 152 | 153 | ## Design 154 | 155 | ### Supervisor Tree 156 | 157 | esockd_sup 158 | -> esockd_listener_sup 159 | -> esockd_listener 160 | -> esockd_acceptor_sup 161 | -> esockd_acceptor 162 | -> esockd_acceptor 163 | -> ...... 164 | -> esockd_connection_sup 165 | -> esockd_connection 166 | -> esockd_connection 167 | -> ...... 168 | 169 | ### Acceptor 170 | 171 | 1. Acceptor Pool 172 | 173 | 2. Suspend for one second when e{n, m}file errors happened 174 | 175 | ### Connection Sup 176 | 177 | 1. Create a connection, and let it run... 178 | 179 | 2. Control maximum connections 180 | 181 | 3. Count active connections 182 | 183 | 4. Count shutdown reasons 184 | 185 | ### CIDR 186 | 187 | CIDR Wiki: https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing 188 | 189 | ## Benchmark 190 | 191 | Benchmark 2.1.0-alpha release on one 8 cores, 32G memory ubuntu/14.04 server:: 192 | 193 | 250K concurrent connections, 50K messages/sec, 40Mbps In/Out consumed 5G memory, 20% CPU/core 194 | 195 | ## License 196 | 197 | Apache License Version 2.0 198 | 199 | ## Author 200 | 201 | EMQ X Team. 202 | 203 | -------------------------------------------------------------------------------- /docs/proxy-protocol.txt: -------------------------------------------------------------------------------- 1 | https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt 2 | -------------------------------------------------------------------------------- /docs/reuseaddr.txt: -------------------------------------------------------------------------------- 1 | 2 | # SO_REUSEADDR and SO_REUSEPORT 3 | 4 | https://stackoverflow.com/questions/14388706/socket-options-so-reuseaddr-and-so-reuseport-how-do-they-differ-do-they-mean-t 5 | 6 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | ## active_n 2 | 3 | Benchmark the `{active, N}` and `async_recv`. 4 | 5 | ``` 6 | recv_eprof:start(Port = 6000, async_recv, 10000). 7 | 8 | recv_eprof:start(Port = 5000, active_n, 10000). 9 | ``` 10 | 11 | ## async_recv 12 | 13 | ## client 14 | 15 | TCP echo client. 16 | 17 | ## dtls 18 | 19 | The DTLS echo server which listens on port 5000. 20 | 21 | Start a DTLS client: 22 | 23 | ``` 24 | openssl s_client -dtls1 -connect 127.0.0.1:5000 -debug 25 | ``` 26 | 27 | ## dtls_psk 28 | 29 | The DTLS echo server which listens on port 5000. 30 | 31 | Start a DTLS client: 32 | ``` 33 | Client = dtls_psk_client:connect(). 34 | dtls_psk_client:send(Client, <<"hi">>). 35 | ``` 36 | 37 | ## gen_server 38 | 39 | ## plain 40 | 41 | ## proxy_protocol 42 | 43 | ## simple 44 | 45 | ## ssl 46 | 47 | Start the SSL echo server which listens on port 5000: 48 | 49 | ``` 50 | ssl_echo_server:start(5000). 51 | ``` 52 | 53 | Start a SSL client: 54 | ``` 55 | openssl s_client -tls1 -debug -connect 127.0.0.1:5000 56 | ``` 57 | 58 | ## tcp_window 59 | 60 | `rebar3 shell` and run `tcp_window` test: 61 | 62 | ``` 63 | tcp_window:start(5000, sync). 64 | tcp_window:start(5000, async). 65 | tcp_window:start(5000, async_force). 66 | tcp_window:start(5000, async_nosuspend). 67 | ``` 68 | 69 | TCP Window Full: 70 | 71 | Send Function | Socket Options | TCP Window full 72 | ----------------------------------------|--------------------------|------------------------------------------- 73 | gen_tcp:send(Socket, Data) | {send_timeout, infinity} | Block forever. 74 | port_command(Socket, Data) | {send_timeout, infinity} | Block forever. 75 | port_command(Socket, Data, [force]) | {send_timeout, infinity} | Return true always. Write to TCP Stack. 76 | port_command(Socket, Data, [nosuspend]) | {send_timeout, infinity} | Return false always. Drop the packets silently. 77 | gen_tcp:send(Socket, Data) | {send_timeout, 5000} | Return {error, timeout}. 78 | port_command(Socket, Data) | {send_timeout, 5000} | Return true always. Pause 5 seconds and Drop packets silently. 79 | port_command(Socket, Data, [force]) | {send_timeout, 5000} | Return true always. Write to TCP Stack. 80 | port_command(Socket, Data, [nosuspend]) | {send_timeout, 5000} | Return false first, and true after timeout. Drop the packets silently. 81 | 82 | Conclusions: 83 | 84 | 1. Should set the `send_timeout` option if using `gen_tcp:send` 85 | 2. Should not set the `nosuspend` option if using `port_command`, for the busy sock will be killed at once. 86 | 87 | ## udp 88 | 89 | Start the UDP echo server which listens on port 5000. 90 | 91 | ``` 92 | udp_echo_server:start(5000). 93 | ``` 94 | 95 | -------------------------------------------------------------------------------- /examples/active_n/recv_eprof.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2019 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(recv_eprof). 18 | 19 | -export([start/3]). 20 | 21 | -define(TCP_OPTS, [ 22 | binary, 23 | {packet, raw}, 24 | {nodelay,true}, 25 | {active, false}, 26 | {reuseaddr, true}, 27 | {keepalive,true}, 28 | {backlog,500} 29 | ]). 30 | 31 | -spec(start(inet:port_number(), active_n|async_recv, pos_integer()) -> any()). 32 | start(Port, How, N) -> 33 | eprof:start(), 34 | P = server(How, Port), 35 | eprof:start_profiling([P]), 36 | send(Port, N), 37 | eprof:stop_profiling(), 38 | eprof:analyze(procs, [{sort,time}]). 39 | 40 | send(Port, N) -> 41 | {ok, Sock} = gen_tcp:connect("localhost", Port, [binary, {packet, raw}]), 42 | lists:foreach(fun(_) -> 43 | ok = gen_tcp:send(Sock, <<"hello">>) 44 | end, lists:seq(1, N)), 45 | ok = gen_tcp:close(Sock). 46 | 47 | server(How, Port) -> 48 | spawn_link(fun() -> listen(How, Port) end). 49 | 50 | listen(How, Port) -> 51 | {ok, L} = gen_tcp:listen(Port, ?TCP_OPTS), 52 | accept(How, L). 53 | 54 | accept(How, L) -> 55 | {ok, Sock} = gen_tcp:accept(L), 56 | _ = case How of 57 | active_n -> 58 | inet:setopts(Sock, [{active, 100}]); 59 | async_recv -> ok 60 | end, 61 | _ = recv_loop(How, Sock), 62 | accept(How, L). 63 | 64 | recv_loop(How = active_n, Sock) -> 65 | receive 66 | {tcp, Sock, _Data} -> 67 | recv_loop(How, Sock); 68 | {tcp_passive, Sock} -> 69 | inet:setopts(Sock, [{active, 100}]); 70 | {tcp_closed, Sock}-> ok; 71 | {tcp_error, Sock, Reason} -> 72 | {error, Reason} 73 | end; 74 | 75 | recv_loop(How = async_recv, Sock) -> 76 | {ok, Ref} = prim_inet:async_recv(Sock, 0, -1), 77 | receive 78 | {inet_async, Sock, Ref, {ok, _Data}} -> 79 | recv_loop(How, Sock); 80 | {inet_async, Sock, Ref, {error, Reason}} -> 81 | {error, Reason} 82 | end. 83 | 84 | -------------------------------------------------------------------------------- /examples/async_recv/async_recv_echo_server.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2019 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | %% @doc Async Recv Echo Server. 18 | -module(async_recv_echo_server). 19 | 20 | -include("esockd.hrl"). 21 | 22 | -behaviour(gen_server). 23 | 24 | %% start 25 | -export([start/1]). 26 | 27 | -export([start_link/2]). 28 | 29 | %% gen_server Function Exports 30 | -export([ init/1 31 | , handle_call/3 32 | , handle_cast/2 33 | , handle_info/2 34 | , terminate/2 35 | , code_change/3 36 | ]). 37 | 38 | -record(state, {transport, socket}). 39 | 40 | start(Port) -> 41 | ok = esockd:start(), 42 | TcpOpts = [binary, 43 | {reuseaddr, true}, 44 | {backlog, 512}, 45 | {nodelay, false} 46 | ], 47 | SslOpts = [{certfile, "./crt/demo.crt"}, 48 | {keyfile, "./crt/demo.key"} 49 | ], 50 | Options = [{acceptors, 8}, 51 | {max_connections, 100000}, 52 | {tcp_options, TcpOpts}, 53 | {ssl_options, SslOpts} 54 | ], 55 | MFArgs = {?MODULE, start_link, []}, 56 | esockd:open(echo, Port, Options, MFArgs). 57 | 58 | %% esockd callbacks 59 | start_link(Transport, Sock) -> 60 | {ok, proc_lib:spawn_link(?MODULE, init, [[Transport, Sock]])}. 61 | 62 | init([Transport, Sock]) -> 63 | case Transport:wait(Sock) of 64 | {ok, NewSock} -> 65 | Transport:async_recv(Sock, 0, infinity), 66 | State = #state{transport = Transport, socket = NewSock}, 67 | gen_server:enter_loop(?MODULE, [], State); 68 | Error -> Error 69 | end. 70 | 71 | handle_call(_Request, _From, State) -> 72 | {reply, ignore, State}. 73 | 74 | handle_cast(_Msg, State) -> 75 | {noreply, State}. 76 | 77 | handle_info({inet_async, _Sock, _Ref, {ok, Data}}, 78 | State = #state{transport = Transport, socket = Sock}) -> 79 | {ok, Peername} = Transport:peername(Sock), 80 | io:format("Data from ~s: ~s~n", [esockd:format(Peername), Data]), 81 | Transport:async_send(Sock, Data), 82 | Transport:async_recv(Sock, 0, infinity), 83 | {noreply, State}; 84 | 85 | handle_info({inet_async, _Sock, _Ref, {error, Reason}}, State) -> 86 | io:format("Shutdown for ~p~n", [Reason]), 87 | shutdown(Reason, State); 88 | 89 | handle_info({inet_reply, _Sock ,ok}, State) -> 90 | {noreply, State}; 91 | 92 | handle_info({inet_reply, _Sock, {error, Reason}}, State) -> 93 | io:format("Shutdown for ~p~n", [Reason]), 94 | shutdown(Reason, State); 95 | 96 | handle_info(_Info, State) -> 97 | {noreply, State}. 98 | 99 | terminate(_Reason, #state{transport = Transport, socket = Sock}) -> 100 | Transport:fast_close(Sock). 101 | 102 | code_change(_OldVsn, State, _Extra) -> 103 | {ok, State}. 104 | 105 | shutdown(Reason, State) -> 106 | {stop, {shutdown, Reason}, State}. 107 | 108 | -------------------------------------------------------------------------------- /examples/client/echo_client.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2019 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | %% @doc Echo test client 18 | -module(echo_client). 19 | 20 | -export([start/3, send/2, run/4, connect/4, loop/2]). 21 | 22 | -define(TCP_OPTIONS, [binary, {packet, raw}, {active, true}]). 23 | 24 | start(Host, Port, Num) -> 25 | spawn(?MODULE, run, [self(), Host, Port, Num]), 26 | mainloop(1). 27 | 28 | mainloop(Count) -> 29 | receive 30 | {connected, _Sock} -> 31 | io:format("conneted: ~p~n", [Count]), 32 | mainloop(Count+1) 33 | end. 34 | 35 | run(_Parent, _Host, _Port, 0) -> 36 | ok; 37 | run(Parent, Host, Port, Num) -> 38 | spawn(?MODULE, connect, [Parent, Host, Port, Num]), 39 | timer:sleep(5), 40 | run(Parent, Host, Port, Num-1). 41 | 42 | connect(Parent, Host, Port, Num) -> 43 | case gen_tcp:connect(Host, Port, ?TCP_OPTIONS, 6000) of 44 | {ok, Sock} -> 45 | Parent ! {connected, Sock}, 46 | loop(Num, Sock); 47 | {error, Reason} -> 48 | io:format("Client ~p connect error: ~p~n", [Num, Reason]) 49 | end. 50 | 51 | loop(Num, Sock) -> 52 | Timeout = 5000 + rand:uniform(5000), 53 | receive 54 | {tcp, Sock, Data} -> 55 | io:format("Client ~w received: ~s~n", [Num, Data]), 56 | loop(Num, Sock); 57 | {tcp_closed, Sock} -> 58 | io:format("Client ~w socket closed~n", [Num]); 59 | {tcp_error, Sock, Reason} -> 60 | io:format("Client ~w socket error: ~p~n", [Num, Reason]); 61 | Other -> 62 | io:format("Client ~w unexpected: ~p", [Num, Other]) 63 | after 64 | Timeout -> 65 | _ = send(Num, Sock), loop(Num, Sock) 66 | end. 67 | 68 | send(N, Sock) -> 69 | gen_tcp:send(Sock, [integer_to_list(N), ":", <<"Hello, eSockd!">>]). 70 | 71 | -------------------------------------------------------------------------------- /examples/dtls/dtls_echo_server.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2019 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(dtls_echo_server). 18 | 19 | -export([start/1]). 20 | 21 | -export([start_link/2, loop/2]). 22 | 23 | start(Port) -> 24 | ok = esockd:start(), 25 | PrivDir = code:priv_dir(esockd), 26 | DtlsOpts = [{mode, binary}, {reuseaddr, true}, {active, 100}, 27 | {certfile, filename:join(PrivDir, "demo.crt")}, 28 | {keyfile, filename:join(PrivDir, "demo.key")} 29 | ], 30 | Opts = [{acceptors, 4}, 31 | {max_connections, 1000}, 32 | {dtls_options, DtlsOpts} 33 | ], 34 | MFArgs = {?MODULE, start_link, []}, 35 | {ok, _} = esockd:open_dtls('echo/dtls', Port, Opts, MFArgs). 36 | 37 | start_link(Transport, Socket) -> 38 | Args = [Transport, Socket], 39 | CPid = proc_lib:spawn_link(?MODULE, loop, Args), 40 | {ok, CPid}. 41 | 42 | loop(Transport, RawSocket) -> 43 | case Transport:wait(RawSocket) of 44 | {ok, Socket} -> 45 | {ok, Peername} = Transport:peername(Socket), 46 | run_loop(Peername, Transport, Socket); 47 | {error, Reason} -> 48 | ok = Transport:fast_close(RawSocket), 49 | io:format("Wait socket upgrade error ~p~n", [Reason]) 50 | end. 51 | 52 | run_loop(Peername, Transport, Socket) -> 53 | receive 54 | {ssl, _RawSocket, Packet} -> 55 | io:format("~s - ~p~n", [esockd:format(Peername), Packet]), 56 | Transport:async_send(Socket, Packet), 57 | run_loop(Peername, Transport, Socket); 58 | {ssl_passive, _RawSocket} -> 59 | io:format("~s - enter the passive mode.~n", [esockd:format(Peername)]), 60 | Transport:setopts(Socket, [{active, 100}]), 61 | run_loop(Peername, Transport, Socket); 62 | {inet_reply, _RawSocket, ok} -> 63 | run_loop(Peername, Transport, Socket); 64 | {ssl_closed, _RawSocket} -> 65 | ok; 66 | {ssl_error, _RawSocket, Reason} -> 67 | io:format("~s error: ~p~n", [esockd:format(Peername), Reason]) 68 | end. 69 | 70 | -------------------------------------------------------------------------------- /examples/dtls_psk/dtls_psk_client.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2019 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(dtls_psk_client). 18 | 19 | -export([ connect/0 20 | , connect/1 21 | , send/2 22 | , recv/2 23 | , recv/3 24 | , user_lookup/3 25 | ]). 26 | 27 | connect() -> 28 | connect(5000). 29 | connect(Port) -> 30 | {ok, Client} = ssl:connect({127, 0, 0, 1}, Port, opts()), 31 | Client. 32 | 33 | send(Client, Msg) -> 34 | ssl:send(Client, Msg). 35 | 36 | recv(Client, Len) -> 37 | ssl:recv(Client, Len). 38 | 39 | recv(Client, Len, Timeout) -> 40 | ssl:recv(Client, Len, Timeout). 41 | 42 | user_lookup(psk, ServerHint, _UserState = PSKs) -> 43 | ServerPskId = server_suggested_psk_id(ServerHint), 44 | ClientPsk = maps:get(ServerPskId, PSKs), 45 | io:format("ServerHint:~p, ServerSuggestedPSKID:~p, ClientPickedPSK: ~p~n", 46 | [ServerHint, ServerPskId, ClientPsk]), 47 | {ok, ClientPsk}. 48 | 49 | server_suggested_psk_id(ServerHint) -> 50 | [_, Psk] = binary:split(ServerHint, <<"plz_use_">>), 51 | Psk. 52 | 53 | opts() -> 54 | [{ssl_imp, new}, 55 | {active, false}, 56 | {verify, verify_none}, 57 | {versions, [dtlsv1]}, 58 | {protocol, dtls}, 59 | {ciphers, [{psk, aes_128_cbc, sha}]}, 60 | {psk_identity, "psk_b"}, 61 | {user_lookup_fun, {fun user_lookup/3, 62 | #{<<"psk_a">> => <<"shared_secret_a">>, 63 | <<"psk_b">> => <<"shared_secret_b">>}}}, 64 | {cb_info, {gen_udp, udp, udp_close, udp_error}} 65 | ]. 66 | 67 | -------------------------------------------------------------------------------- /examples/dtls_psk/dtls_psk_echo_server.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2019 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(dtls_psk_echo_server). 18 | 19 | -export([start/1]). 20 | 21 | -export([start_link/2, loop/2]). 22 | 23 | -dialyzer({nowarn_function, [start/1]}). 24 | 25 | start(Port) -> 26 | _ = [application:ensure_all_started(App) || App <- [sasl, crypto, ssl, esockd]], 27 | DtlsOpts = [{mode, binary}, {reuseaddr, true}] ++ psk_opts(), 28 | Opts = [{acceptors, 4}, {max_clients, 1000}, {dtls_options, DtlsOpts}], 29 | {ok, _} = esockd:open_dtls('echo/dtls', Port, Opts, {?MODULE, start_link, []}). 30 | 31 | start_link(Transport, Socket) -> 32 | {ok, spawn_link(?MODULE, loop, [Transport, Socket])}. 33 | 34 | loop(Transport, RawSocket) -> 35 | case Transport:wait(RawSocket) of 36 | {ok, Socket} -> 37 | {ok, Peername} = Transport:peername(Socket), 38 | run_loop(Peername, Transport, Socket); 39 | {error, Reason} -> 40 | ok = Transport:fast_close(RawSocket), 41 | io:format("Wait socket upgrade error ~p~n", [Reason]) 42 | end. 43 | 44 | run_loop(Peername, Transport, Socket) -> 45 | receive 46 | {ssl, _Sock, Packet} -> 47 | io:format("~s - ~p~n", [esockd:format(Peername), Packet]), 48 | Transport:async_send(Socket, Packet), 49 | run_loop(Peername, Transport, Socket) 50 | end. 51 | 52 | psk_opts() -> 53 | [{verify, verify_none}, 54 | {protocol, dtls}, 55 | {versions, [dtlsv1, 'dtlsv1.2']}, 56 | {ciphers, [{psk,aes_128_cbc,sha}, {rsa_psk,aes_128_cbc,sha256}]}, 57 | {psk_identity, "plz_use_psk_a"}, 58 | {user_lookup_fun, 59 | {fun user_lookup/3, #{<<"psk_a">> => <<"shared_secret_a">>, 60 | <<"psk_b">> => <<"shared_secret_b">>}}} 61 | ]. 62 | 63 | user_lookup(psk, ClientPSKID, _UserState = PSKs) -> 64 | ServerPickedPsk = maps:get(<<"psk_a">>, PSKs), 65 | io:format("ClientPSKID: ~p, ServerPickedPSK: ~p~n", [ClientPSKID, ServerPickedPsk]), 66 | {ok, ServerPickedPsk}. 67 | 68 | -------------------------------------------------------------------------------- /examples/gen_server/gen_echo_server.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2019 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | %% @doc Simple echo server. 18 | -module(gen_echo_server). 19 | 20 | -include("esockd.hrl"). 21 | 22 | -behaviour(gen_server). 23 | 24 | %% start 25 | -export([start/1]). 26 | 27 | %% esockd callback 28 | -export([start_link/2]). 29 | 30 | %% gen_server function exports 31 | -export([ init/1 32 | , handle_call/3 33 | , handle_cast/2 34 | , handle_info/2 35 | , terminate/2 36 | , code_change/3 37 | ]). 38 | 39 | -record(state, {transport, socket}). 40 | 41 | start(Port) -> 42 | ok = esockd:start(), 43 | Opts = [{acceptors, 2}, 44 | {max_connections, 100000}, 45 | {max_conn_rate, 10} 46 | ], 47 | MFArgs = {?MODULE, start_link, []}, 48 | esockd:open(echo, Port, Opts, MFArgs). 49 | 50 | start_link(Transport, Sock) -> 51 | {ok, proc_lib:spawn_link(?MODULE, init, [[Transport, Sock]])}. 52 | 53 | init([Transport, Sock]) -> 54 | case Transport:wait(Sock) of 55 | {ok, NewSock} -> 56 | Transport:setopts(Sock, [{active, once}]), 57 | gen_server:enter_loop(?MODULE, [], #state{transport = Transport, socket = NewSock}); 58 | {error, Reason} -> 59 | {stop, Reason} 60 | end. 61 | 62 | handle_call(_Request, _From, State) -> 63 | {reply, ok, State}. 64 | 65 | handle_cast(_Msg, State) -> 66 | {noreply, State}. 67 | 68 | handle_info({tcp, Sock, Data}, State = #state{transport = Transport, socket = Sock}) -> 69 | {ok, Peername} = Transport:peername(Sock), 70 | io:format("Data from ~s: ~s~n", [esockd:format(Peername), Data]), 71 | Transport:send(Sock, Data), 72 | Transport:setopts(Sock, [{active, once}]), 73 | {noreply, State}; 74 | 75 | handle_info({tcp_error, Sock, Reason}, State = #state{socket = Sock}) -> 76 | io:format("Error from: ~p~n", [Sock]), 77 | io:format("tcp_error: ~s~n", [Reason]), 78 | {stop, {shutdown, Reason}, State}; 79 | 80 | handle_info({tcp_closed, Sock}, State = #state{socket = Sock}) -> 81 | io:format("tcp_closed~n"), 82 | {stop, normal, State}; 83 | 84 | handle_info(_Info, State) -> 85 | {noreply, State}. 86 | 87 | terminate(_Reason, _State) -> 88 | ok. 89 | 90 | code_change(_OldVsn, State, _Extra) -> 91 | {ok, State}. 92 | 93 | -------------------------------------------------------------------------------- /examples/plain/plain_echo_server.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2019 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | %% @doc Simple echo server. 18 | -module(plain_echo_server). 19 | 20 | -export([start/1]). 21 | 22 | -export([accept/2, recvloop/1]). 23 | 24 | %% @doc Start echo server. 25 | start(Port) -> 26 | ok = esockd:start(), 27 | {ok, LSock} = gen_tcp:listen(Port, [{active, false}]), 28 | io:format("Listening on ~p~n", [Port]), 29 | self() ! accept, 30 | mainloop(self(), LSock). 31 | 32 | mainloop(Parent, LSock) -> 33 | receive 34 | accept -> 35 | spawn(?MODULE, accept, [Parent, LSock]), 36 | mainloop(Parent, LSock); 37 | stop -> stop 38 | end. 39 | 40 | accept(Parent, LSock) -> 41 | {ok, Sock} = gen_tcp:accept(LSock), 42 | {ok, Peername} = inet:peername(Sock), 43 | io:format("Connection from ~p~n", [Peername]), 44 | Parent ! accept, 45 | recvloop(Sock). 46 | 47 | recvloop(Sock) -> 48 | case gen_tcp:recv(Sock, 0, 30000) of 49 | {ok, Data} -> 50 | {ok, Peername} = inet:peername(Sock), 51 | io:format("Data from ~s: ~s~n", [esockd:format(Peername), Data]), 52 | _ = gen_tcp:send(Sock, Data), 53 | recvloop(Sock); 54 | {error, Reason} -> 55 | io:format("TCP closed for ~s~n", [Reason]), 56 | {stop, Reason} 57 | end. 58 | 59 | -------------------------------------------------------------------------------- /examples/proxy_protocol/haproxy.cfg: -------------------------------------------------------------------------------- 1 | listen proxy 2 | bind *:5050 3 | mode tcp 4 | maxconn 50000 5 | timeout client 600s 6 | default_backend nodes 7 | 8 | backend nodes 9 | mode tcp 10 | balance source 11 | timeout server 600s 12 | timeout connect 60s 13 | #timeout check 5000 14 | server node 127.0.0.1:5000 check inter 10000 fall 2 rise 5 weight 1 send-proxy-v2 15 | -------------------------------------------------------------------------------- /examples/proxy_protocol/proxy_protocol_server.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2019 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | %%% @doc Proxy protcol -> Echo server. 18 | -module(proxy_protocol_server). 19 | 20 | -include("esockd.hrl"). 21 | 22 | -behaviour(gen_server). 23 | 24 | -export([start/1]). 25 | 26 | %% esockd callback 27 | -export([start_link/2]). 28 | 29 | -export([ init/1 30 | , handle_call/3 31 | , handle_cast/2 32 | , handle_info/2 33 | , terminate/2 34 | , code_change/3 35 | ]). 36 | 37 | -record(state, {transport, socket}). 38 | 39 | start(Port) -> 40 | ok = esockd:start(), 41 | Opts = [{tcp_options, [binary, {packet, raw}]}, 42 | proxy_protocol, 43 | {proxy_protocol_timeout, 1000} 44 | ], 45 | MFArgs = {?MODULE, start_link, []}, 46 | esockd:open(echo, Port, Opts, MFArgs). 47 | 48 | start_link(Transport, Sock) -> 49 | {ok, proc_lib:spawn_link(?MODULE, init, [[Transport, Sock]])}. 50 | 51 | init([Transport, Sock]) -> 52 | case Transport:wait(Sock) of 53 | {ok, NewSock} -> 54 | io:format("Proxy Sock: ~p~n", [NewSock]), 55 | Transport:setopts(Sock, [{active, once}]), 56 | gen_server:enter_loop(?MODULE, [], #state{transport = Transport, socket = NewSock}); 57 | {error, Reason} -> 58 | io:format("Proxy Sock Error: ~p~n", [Reason]), 59 | {stop, Reason} 60 | end. 61 | 62 | handle_call(_Request, _From, State) -> 63 | {reply, ignore, State}. 64 | 65 | handle_cast(_Msg, State) -> 66 | {noreply, State}. 67 | 68 | handle_info({tcp, _Sock, Data}, State = #state{transport = Transport, socket = Sock}) -> 69 | {ok, Peername} = Transport:peername(Sock), 70 | io:format("Data from ~s: ~s~n", [esockd:format(Peername), Data]), 71 | Transport:send(Sock, Data), 72 | Transport:setopts(Sock, [{active, once}]), 73 | {noreply, State}; 74 | 75 | handle_info({tcp_error, _Sock, Reason}, State) -> 76 | io:format("TCP Error: ~s~n", [Reason]), 77 | {stop, {shutdown, Reason}, State}; 78 | 79 | handle_info({tcp_closed, _Sock}, State) -> 80 | io:format("TCP closed~n"), 81 | {stop, normal, State}; 82 | 83 | handle_info(_Info, State) -> 84 | {noreply, State}. 85 | 86 | terminate(_Reason, _State) -> 87 | ok. 88 | 89 | code_change(_OldVsn, State, _Extra) -> 90 | {ok, State}. 91 | 92 | -------------------------------------------------------------------------------- /examples/simple/simple_echo_server.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2019 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(simple_echo_server). 18 | 19 | -export([start/1]). 20 | 21 | -export([start_link/2, init/2, loop/2]). 22 | 23 | -define(TCP_OPTIONS, [binary, {reuseaddr, true}, {nodelay, false}]). 24 | 25 | %% @doc Start echo server. 26 | start(Port) -> 27 | ok = esockd:start(), 28 | Access = application:get_env(esockd, access, [{allow, all}]), 29 | SockOpts = [{access_rules, Access}, 30 | {acceptors, 8}, 31 | {shutdown, infinity}, 32 | {max_connections, 1000000}, 33 | {tcp_options, ?TCP_OPTIONS} 34 | ], 35 | MFArgs = {?MODULE, start_link, []}, 36 | esockd:open(echo, Port, SockOpts, MFArgs). 37 | 38 | %%-------------------------------------------------------------------- 39 | %% esockd callback 40 | %%-------------------------------------------------------------------- 41 | 42 | start_link(Transport, Sock) -> 43 | {ok, spawn_link(?MODULE, init, [Transport, Sock])}. 44 | 45 | init(Transport, Sock) -> 46 | case Transport:wait(Sock) of 47 | {ok, NewSock} -> 48 | loop(Transport, NewSock); 49 | Error -> Error 50 | end. 51 | 52 | loop(Transport, Sock) -> 53 | case Transport:recv(Sock, 0) of 54 | {ok, Data} -> 55 | {ok, Peername} = Transport:peername(Sock), 56 | io:format("~s - ~s~n", [esockd:format(Peername), Data]), 57 | Transport:send(Sock, Data), 58 | loop(Transport, Sock); 59 | {error, Reason} -> 60 | io:format("TCP ~s~n", [Reason]), 61 | {stop, Reason} 62 | end. 63 | 64 | -------------------------------------------------------------------------------- /examples/tcp_window/tcp_window.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2019 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(tcp_window). 18 | 19 | -export([start/2]). 20 | 21 | -export([ start_server/3 22 | , server_init/3 23 | , server_loop/3 24 | ]). 25 | 26 | -define(TCP_OPTIONS, [binary, 27 | {packet, raw}, 28 | {active, false}, 29 | {recbuf, 1024}, 30 | {sndbuf, 1024}, 31 | {send_timeout, 5000}, 32 | {send_timeout_close, true} 33 | ]). 34 | 35 | -spec(start(inet:port_number(), sync|async|async_force|async_nosuspend) -> ok). 36 | start(Port, How) when is_integer(Port) -> 37 | lists:foreach(fun application:ensure_all_started/1, [sasl, crypto, esockd]), 38 | Options = [{acceptors, 1}, 39 | {shutdown, infinity}, 40 | {max_connections, 100}, 41 | {tcp_options, ?TCP_OPTIONS}], 42 | _ = esockd:open(tcp_block, Port, Options, {?MODULE, start_server, [How]}), 43 | start_client(Port). 44 | 45 | start_server(Transport, Sock, How) -> 46 | {ok, spawn_link(?MODULE, server_init, [Transport, Sock, How])}. 47 | 48 | server_init(Transport, Sock, How) -> 49 | case Transport:wait(Sock) of 50 | {ok, NewSock} -> 51 | SockOpts = Transport:getopts(NewSock, [send_timeout, send_timeout_close]), 52 | io:format("Sockopts: ~p~n", [SockOpts]), 53 | SendFun = send_fun(How, Transport, Sock), 54 | server_loop(Transport, Sock, SendFun); 55 | {error, Reason} -> 56 | {stop, Reason} 57 | end. 58 | 59 | server_loop(Transport, Sock, SendFun) -> 60 | case Transport:recv(Sock, 0) of 61 | {ok, Data} -> 62 | {ok, Peername} = Transport:peername(Sock), 63 | io:format("Server recv: ~p from (~s)~n", [Data, esockd:format(Peername)]), 64 | SendFun(Data), 65 | %% flood the tcp window of client 66 | send_loop(Transport, Sock, SendFun, Data, 0); 67 | {error, Reason} -> 68 | io:format("server tcp error ~s~n", [Reason]), 69 | Transport:fast_close(Sock), 70 | {stop, Reason} 71 | end. 72 | 73 | send_loop(Transport, Sock, SendFun, Data, Count) -> 74 | case SendFun(Data) of 75 | true -> 76 | io:format("Send ~w~n", [Count]), 77 | io:format("Stats: ~p~n", [Transport:getstat(Sock, [send_cnt])]), 78 | send_loop(Transport, Sock, SendFun, Data, Count + iolist_size(Data)); 79 | false -> 80 | io:format("False Send ~w~n", [Count]), 81 | io:format("Stats: ~p~n", [Transport:getstat(Sock, [send_cnt])]), 82 | send_loop(Transport, Sock, SendFun, Data, Count + iolist_size(Data)); 83 | ok -> 84 | io:format("Send ~w~n", [Count]), 85 | send_loop(Transport, Sock, SendFun, Data, Count + iolist_size(Data)); 86 | {error, Error} -> 87 | io:format("Server tcp error: ~p~n", [Error]), 88 | Transport:fast_close(Sock) 89 | end. 90 | 91 | start_client(Port) -> 92 | case gen_tcp:connect("127.0.0.1", Port, ?TCP_OPTIONS, 60000) of 93 | {ok, Sock} -> 94 | _ = inet:setopts(Sock, [{active, false}]), 95 | client_loop(Sock, 0); 96 | {error, Reason} -> 97 | io:format("client failed to connect: ~p~n", [Reason]) 98 | end. 99 | 100 | client_loop(Sock, I) -> 101 | _ = gen_tcp:send(Sock, crypto:strong_rand_bytes(1024*1024)), 102 | timer:sleep(1000), 103 | case gen_tcp:recv(Sock, 0) of 104 | {ok, Data} -> 105 | io:format("client recv: ~p~n", [Data]), 106 | client_loop(Sock, I+1); 107 | {error, Reason} -> 108 | io:format("client tcp error: ~p~n", [Reason]), 109 | gen_tcp:close(Sock) 110 | end. 111 | 112 | send_fun(sync, Transport, Sock) -> 113 | fun(Data) -> Transport:send(Sock, Data) end; 114 | send_fun(async, _Transport, Sock) -> 115 | fun(Data) -> port_command(Sock, Data) end; 116 | send_fun(async_force, _Transport, Sock) -> 117 | fun(Data) -> port_command(Sock, Data, [force]) end; 118 | send_fun(async_nosuspend, Transport, Sock) -> 119 | fun(Data) -> Transport:async_send(Sock, Data) end. 120 | 121 | -------------------------------------------------------------------------------- /examples/tls/ssl_echo_server.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2019 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(ssl_echo_server). 18 | 19 | -export([start/1]). 20 | 21 | -export([start_link/2, init/2, loop/2]). 22 | 23 | start(Port) -> 24 | ok = esockd:start(), 25 | PrivDir = code:priv_dir(esockd), 26 | TcpOpts = [binary, {reuseaddr, true}], 27 | SslOpts = [{certfile, filename:join(PrivDir, "demo.crt")}, 28 | {keyfile, filename:join(PrivDir, "demo.key")} 29 | ], 30 | Opts = [{acceptors, 4}, 31 | {max_connections, 1000}, 32 | {tcp_options, TcpOpts}, 33 | {ssl_options, SslOpts} 34 | ], 35 | {ok, _} = esockd:open('echo/ssl', Port, Opts, ?MODULE). 36 | 37 | start_link(Transport, Sock) -> 38 | {ok, spawn_link(?MODULE, init, [Transport, Sock])}. 39 | 40 | init(Transport, Sock) -> 41 | case Transport:wait(Sock) of 42 | {ok, NewSock} -> 43 | loop(Transport, NewSock); 44 | Error -> Error 45 | end. 46 | 47 | loop(Transport, Sock) -> 48 | case Transport:recv(Sock, 0) of 49 | {ok, Data} -> 50 | {ok, PeerName} = Transport:peername(Sock), 51 | io:format("~s - ~p~n", [esockd:format(PeerName), Data]), 52 | Transport:send(Sock, Data), 53 | loop(Transport, Sock); 54 | {error, Reason} -> 55 | io:format("tcp ~s~n", [Reason]), 56 | {stop, Reason} 57 | end. 58 | 59 | -------------------------------------------------------------------------------- /examples/udp/.exrc: -------------------------------------------------------------------------------- 1 | if &cp | set nocp | endif 2 | let s:cpo_save=&cpo 3 | set cpo&vim 4 | vmap gx NetrwBrowseXVis 5 | nmap gx NetrwBrowseX 6 | vnoremap NetrwBrowseXVis :call netrw#BrowseXVis() 7 | nnoremap NetrwBrowseX :call netrw#BrowseX(netrw#GX(),netrw#CheckIfRemote(netrw#GX())) 8 | cabbr rename =getcmdpos() == 1 && getcmdtype() == ":" ? "Rename" : "rename" 9 | let &cpo=s:cpo_save 10 | unlet s:cpo_save 11 | set background=dark 12 | set backspace=2 13 | set expandtab 14 | set fileencodings=ucs-bom,utf-8,default,latin1 15 | set modelines=0 16 | set ruler 17 | set runtimepath=~/.vim,~/.vim/bundle/Vundle.vim,~/.vim/bundle/nerdtree,~/.vim/bundle/vim-erlang-runtime,~/.vim/bundle/vim-erlang-tags,~/.vim/bundle/vim-erlang-omnicomplete,~/.vim/bundle/vim-erlang-skeletons,~/.vim/bundle/vim-trailing-whitespace,~/.vim/bundle/rename.vim,~/.vim/bundle/Vundle.vim,~/.vim/bundle/nerdtree,~/.vim/bundle/rename.vim,~/.vim/bundle/vim-erlang-omnicomplete,~/.vim/bundle/vim-erlang-runtime,~/.vim/bundle/vim-erlang-skeletons,~/.vim/bundle/vim-erlang-tags,~/.vim/bundle/vim-trailing-whitespace,/usr/share/vim/vimfiles,/usr/share/vim/vim80,/usr/share/vim/vimfiles/after,~/.vim/after,~/.vim/bundle/Vundle.vim/after,~/.vim/bundle/nerdtree/after,~/.vim/bundle/vim-erlang-runtime/after,~/.vim/bundle/vim-erlang-tags/after,~/.vim/bundle/vim-erlang-omnicomplete/after,~/.vim/bundle/vim-erlang-skeletons/after,~/.vim/bundle/vim-trailing-whitespace/after,~/.vim/bundle/rename.vim/after 18 | set shiftwidth=4 19 | set tabstop=4 20 | set window=0 21 | " vim: set ft=vim : 22 | -------------------------------------------------------------------------------- /examples/udp/udp_echo_server.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2019 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(udp_echo_server). 18 | 19 | -export([start/1]). 20 | 21 | -export([start_link/2, loop/2]). 22 | 23 | -define(UDP_OPTS, [binary, {reuseaddr, true}]). 24 | 25 | start(Port) -> 26 | ok = esockd:start(), 27 | Opts = [{udp_options, ?UDP_OPTS}], 28 | MFA = {?MODULE, start_link, []}, 29 | esockd:open_udp('echo/udp', Port, Opts, MFA). 30 | 31 | start_link(Transport, Peer) -> 32 | {ok, spawn_link(?MODULE, loop, [Transport, Peer])}. 33 | 34 | loop(Transport = {udp, Server, Sock}, Peer = {IP, Port}) -> 35 | receive 36 | {datagram, Server, Packet} -> 37 | io:format("~s - ~p~n", [esockd:format(Peer), Packet]), 38 | %% Reply by pid 39 | Server ! {datagram, Peer, Packet}, 40 | %% Reply by sock 41 | ok = gen_udp:send(Sock, IP, Port, Packet), 42 | loop(Transport, Peer) 43 | end. 44 | 45 | -------------------------------------------------------------------------------- /include/esockd.hrl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2019 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -ifndef(ESOCKD_HRL). 18 | -define(ESOCKD_HRL, true). 19 | 20 | %%-------------------------------------------------------------------- 21 | %% Default Timeout 22 | %%-------------------------------------------------------------------- 23 | 24 | -define(SSL_CLOSE_TIMEOUT, 5000). 25 | -define(SSL_HANDSHAKE_TIMEOUT, 15000). 26 | -define(PROXY_RECV_TIMEOUT, 5000). 27 | 28 | %%-------------------------------------------------------------------- 29 | %% SSL socket wrapper 30 | %%-------------------------------------------------------------------- 31 | 32 | -record(ssl_socket, {tcp :: inet:socket() | undefined, %% dtls 33 | ssl :: ssl:sslsocket()}). 34 | 35 | -define(IS_SSL(Sock), is_record(Sock, ssl_socket)). 36 | 37 | %%-------------------------------------------------------------------- 38 | %% Proxy-Protocol Socket Wrapper 39 | %%-------------------------------------------------------------------- 40 | 41 | -type(pp2_additional_ssl_field() :: {pp2_ssl_client, boolean()} 42 | | {pp2_ssl_client_cert_conn, boolean()} 43 | | {pp2_ssl_client_cert_sess, boolean()} 44 | | {pp2_ssl_verify, success | failed} 45 | | {pp2_ssl_version, binary()} % US-ASCII string 46 | | {pp2_ssl_cn, binary()} % UTF8-encoded string 47 | | {pp2_ssl_cipher, binary()} % US-ASCII string 48 | | {pp2_ssl_sig_alg, binary()} % US-ASCII string 49 | | {pp2_ssl_key_alg, binary()}).% US-ASCII string 50 | 51 | -type(pp2_additional_field() :: {pp2_alpn, binary()} % byte sequence 52 | | {pp2_authority, binary()} % UTF8-encoded string 53 | | {pp2_crc32c, integer()} % 32-bit number 54 | | {pp2_netns, binary()} % US-ASCII string 55 | | {pp2_ssl, list(pp2_additional_ssl_field())}). 56 | 57 | -record(proxy_socket, {inet :: inet4 | inet6 | 'unix' | 'unspec', 58 | socket :: inet:socket() | #ssl_socket{}, 59 | src_addr :: inet:ip_address() | undefined, 60 | dst_addr :: inet:ip_address() | undefined, 61 | src_port :: inet:port_number() | undefined, 62 | dst_port :: inet:port_number() | undefined, 63 | %% Proxy protocol v2 addtional fields 64 | pp2_additional_info = [] :: list(pp2_additional_field())}). 65 | 66 | -define(IS_PROXY(Sock), is_record(Sock, proxy_socket)). 67 | 68 | -if(?OTP_RELEASE >= 26). 69 | -type ssl_option() :: ssl:tls_option(). 70 | -else. 71 | -type ssl_option() :: ssl:ssl_option(). 72 | -endif. % OTP_RELEASE 73 | 74 | 75 | -define(ERROR_MAXLIMIT, maxlimit). 76 | 77 | -define(ARG_ACCEPTED, accepted). 78 | -define(ARG_CLOSED_SYS_LIMIT, closed_sys_limit). 79 | -define(ARG_CLOSED_MAX_LIMIT, closed_max_limit). 80 | -define(ARG_CLOSED_OVERLOADED, closed_overloaded). 81 | -define(ARG_CLOSED_RATE_LIMITED, closed_rate_limited). 82 | -define(ARG_CLOSED_OTHER_REASONS, closed_other_reasons). 83 | 84 | -define(ACCEPT_RESULT_GROUPS, 85 | [ 86 | ?ARG_ACCEPTED, 87 | ?ARG_CLOSED_SYS_LIMIT, 88 | ?ARG_CLOSED_MAX_LIMIT, 89 | ?ARG_CLOSED_OVERLOADED, 90 | ?ARG_CLOSED_RATE_LIMITED, 91 | ?ARG_CLOSED_OTHER_REASONS 92 | ]). 93 | 94 | -endif. % ESOCKD_HRL 95 | -------------------------------------------------------------------------------- /include/esockd_proxy.hrl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2024 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -ifndef(ESOCKD_PROXY_HRL). 18 | -define(ESOCKD_PROXY_HRL, true). 19 | 20 | -define(SSL_TRANSPORT, esockd_transport). 21 | -define(PROXY_TRANSPORT, esockd_udp_proxy). 22 | 23 | -type proxy_id() :: pid(). 24 | -type socket_packet() :: binary(). 25 | -type socket() :: inet:socket() | ssl:sslsocket(). 26 | 27 | -type transport() :: {udp, pid(), socket()} | ?SSL_TRANSPORT. 28 | -type proxy_transport() :: {?PROXY_TRANSPORT, pid(), socket()}. 29 | -type address() :: {inet:ip_address(), inet:port_number()}. 30 | -type peer() :: socket() | address(). 31 | 32 | -type connection_module() :: atom(). 33 | -type connection_state() :: term(). 34 | -type connection_packet() :: term(). 35 | 36 | -type connection_id() :: 37 | peer() 38 | | integer() 39 | | string() 40 | | binary(). 41 | 42 | -type proxy_packet() :: 43 | {?PROXY_TRANSPORT, proxy_id(), binary(), connection_packet()}. 44 | 45 | %% Routing information search results 46 | 47 | %% send raw socket packet 48 | -type get_connection_id_result() :: 49 | %% send decoded packet 50 | {ok, connection_id(), connection_packet(), connection_state()} 51 | | {error, binary()} 52 | | invalid. 53 | 54 | -type connection_options() :: #{ 55 | esockd_proxy_opts := proxy_options(), 56 | atom() => term() 57 | }. 58 | 59 | -type proxy_options() :: #{ 60 | connection_mod := connection_module(), 61 | heartbeat => non_neg_integer() 62 | }. 63 | 64 | -endif. 65 | -------------------------------------------------------------------------------- /priv/demo.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICuTCCAiICCQC8+3PPaqATfDANBgkqhkiG9w0BAQUFADCBoDELMAkGA1UEBhMC 3 | Q0gxETAPBgNVBAgTCFpoZUppYW5nMREwDwYDVQQHEwhIYW5nWmhvdTEUMBIGA1UE 4 | ChMLWGlhb0xpIFRlY2gxHzAdBgNVBAsTFkluZm9ybWF0aW9uIFRlY2hub2xvZ3kx 5 | EzARBgNVBAMTCnQuZW1xdHQuaW8xHzAdBgkqhkiG9w0BCQEWEGZlbmcgYXQgZW1x 6 | dHQuaW8wHhcNMTUwMjI1MTc0NjQwWhcNMTYwMjI1MTc0NjQwWjCBoDELMAkGA1UE 7 | BhMCQ0gxETAPBgNVBAgTCFpoZUppYW5nMREwDwYDVQQHEwhIYW5nWmhvdTEUMBIG 8 | A1UEChMLWGlhb0xpIFRlY2gxHzAdBgNVBAsTFkluZm9ybWF0aW9uIFRlY2hub2xv 9 | Z3kxEzARBgNVBAMTCnQuZW1xdHQuaW8xHzAdBgkqhkiG9w0BCQEWEGZlbmcgYXQg 10 | ZW1xdHQuaW8wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALAtN2OHsvltOk+9 11 | AtlwMtKuaWW2WpV/S0lRRG9x9k8pyd5PJeeYAr2jVsoWnZInb1CoEOHFcwxZLjv3 12 | gEvz+X+//W02YyI9hnvCJUpT/+6P0gJEbmTmqL078M6vbtwtiF1YC7mdo0nGAZuK 13 | qedpIoEZbVJavf4S0vXWTsb3s5unAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAgUR3 14 | z4uDUsAl+xUorPMBIOS/ncHHVk1XucVv9Wi4chzzZ+4/Y77/fFqP6oxhQ59C9Q8i 15 | iT5wjaE4R1eCge18lPSw3yb1tsTe5B3WkRTzziPq/Q/AsC+DifkkE1YW67leuJV/ 16 | vz74sEi0dudmOVoe6peYxjEH8xXoIUqhnwXt/4Q= 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /priv/demo.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXAIBAAKBgQCwLTdjh7L5bTpPvQLZcDLSrmlltlqVf0tJUURvcfZPKcneTyXn 3 | mAK9o1bKFp2SJ29QqBDhxXMMWS4794BL8/l/v/1tNmMiPYZ7wiVKU//uj9ICRG5k 4 | 5qi9O/DOr27cLYhdWAu5naNJxgGbiqnnaSKBGW1SWr3+EtL11k7G97ObpwIDAQAB 5 | AoGBAKU1cbiLG0GdtU3rME3ZUj+RQNMZ4u5IVcBmTie4FcN8q4ombKQ2P3O4RX3z 6 | IUZaZp+bS2F8uHt+8cVYPl57Zp5fwbIlv6jWgGpvXLsX8JBQl2OTw38B+hVwJvAM 7 | h0mBzprUOs3KGZyF5cyA4osrZ4QvCZhwId9fAjwLGBF9i1yBAkEA4jWAF1sWQiwF 8 | vY476m+0ihpRwGKjldKHWFZmvoB/AnNV/rXO+HRl3MB5wmO+Dqg3gJZrjGBgDeaV 9 | g9hoQjK6ZwJBAMdg57iKLd8uUb7c4pR8fDdDbeeI5X7WDf2k9emT3BMPJPQ3EiSf 10 | CStn1hRfp31U9CXEnw94rKHhrdMFrYjdzMECQCcWD3f5qTLt4GAMf5XWj199hLq1 11 | UIbGxdQhuccY9Nk7jJRiXczYb/Fg4KkSCvkFX/G8DAFJdc9xFEyfzAQEN+kCQH3a 12 | nMrvZn9gBLffRKOIZPyZctHZp0xGIHTA4X39GMlrIN+Lt8coIKimlgssSlSiAK+q 13 | iuFAQnC5PXlcNyuTHsECQAMNMY6jXikgSUZfVXitAFX3g9+IbjT9eJ92f60QneW8 14 | mxWQoqP3fqCSbTEysb7NojEEwppSZtaNgnBb5R4E+mU= 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {minimum_otp_vsn, "21.0"}. 2 | 3 | {erl_opts, [warn_unused_vars, 4 | warn_shadow_vars, 5 | warn_unused_import, 6 | warn_obsolete_guard, 7 | debug_info, 8 | compressed %% for edge 9 | ]}. 10 | 11 | {xref_checks, [undefined_function_calls, undefined_functions, 12 | locals_not_used, deprecated_function_calls, 13 | warnings_as_errors, deprecated_functions 14 | ]}. 15 | 16 | {eunit_opts, [verbose, {report,{eunit_surefire,[{dir,"."}]}}]}. 17 | 18 | {src_dirs, ["src"]}. 19 | 20 | {cover_enabled, true}. 21 | {cover_opts, [verbose]}. 22 | {cover_export_enabled, true}. 23 | 24 | {dialyzer, [{warnings, [unmatched_returns, 25 | error_handling]}]}. 26 | 27 | {profiles, 28 | [{test, 29 | [{deps, [{meck, "0.8.13"}]}, 30 | {erl_opts, [debug_info]}, 31 | {extra_src_dirs, 32 | ["examples/client", 33 | "examples/active_n", 34 | "examples/async_recv", 35 | "examples/client", 36 | "examples/dtls", 37 | "examples/dtls_psk", 38 | "examples/gen_server", 39 | "examples/plain", 40 | "examples/proxy_protocol", 41 | "examples/simple", 42 | "examples/tls", 43 | "examples/tcp_window", 44 | "examples/udp" 45 | ]} 46 | ]} 47 | ]}. 48 | -------------------------------------------------------------------------------- /src/esockd.app.src: -------------------------------------------------------------------------------- 1 | {application, esockd, 2 | [{description, "General Non-blocking TCP/SSL and UDP/DTLS Server"}, 3 | {id, "esockd"}, 4 | {vsn, git}, 5 | {modules, []}, 6 | {registered, []}, 7 | {applications, [kernel, stdlib, sasl, ssl, public_key]}, 8 | {mod, {esockd_app, []}}, 9 | {env, []}, 10 | {licenses, ["Apache-2.0"]}, 11 | {maintainers, ["EMQ X Team "]}, 12 | {links, [{"Homepage", "https://emqx.io/"}, 13 | {"Github", "https://github.com/emqx/esockd"} 14 | ]} 15 | ]}. 16 | -------------------------------------------------------------------------------- /src/esockd.appup.src: -------------------------------------------------------------------------------- 1 | %%-*- mode: erlang; -*- 2 | {"5.9.9", 3 | [ 4 | {"5.9.8", 5 | [ {load_module,esockd_udp,brutal_purge,soft_purge,[]} 6 | ] 7 | }, 8 | {"5.9.7", 9 | [ {load_module,esockd_transport,brutal_purge,soft_purge,[]} 10 | ] 11 | }, 12 | {"5.9.6", 13 | [ {load_module,esockd_proxy_protocol,brutal_purge,soft_purge,[]} 14 | , {load_module,esockd_connection_sup,brutal_purge,soft_purge,[]} 15 | , {load_module,esockd_transport,brutal_purge,soft_purge,[]} 16 | ] 17 | }, 18 | {"5.9.5", 19 | [ {load_module,esockd_transport,brutal_purge,soft_purge,[]} 20 | , {load_module,esockd_proxy_protocol,brutal_purge,soft_purge,[]} 21 | , {load_module,esockd_acceptor,brutal_purge,soft_purge,[]} 22 | , {load_module,esockd_connection_sup,brutal_purge,soft_purge,[]} 23 | ] 24 | }, 25 | {"5.9.4", 26 | [ {load_module,esockd_transport,brutal_purge,soft_purge,[]} 27 | , {load_module,esockd_proxy_protocol,brutal_purge,soft_purge,[]} 28 | , {load_module,esockd_acceptor,brutal_purge,soft_purge,[]} 29 | , {load_module,esockd_connection_sup,brutal_purge,soft_purge,[]} 30 | ] 31 | }, 32 | {"5.9.3", 33 | [ {load_module,esockd_transport,brutal_purge,soft_purge,[]} 34 | , {load_module,esockd_proxy_protocol,brutal_purge,soft_purge,[]} 35 | , {load_module,esockd_acceptor,brutal_purge,soft_purge,[]} 36 | , {load_module,esockd_connection_sup,brutal_purge,soft_purge,[]} 37 | ] 38 | }, 39 | {"5.9.2", 40 | [ {load_module,esockd_udp,brutal_purge,soft_purge,[]} 41 | , {load_module,esockd_transport,brutal_purge,soft_purge,[]} 42 | , {load_module,esockd_proxy_protocol,brutal_purge,soft_purge,[]} 43 | , {load_module,esockd_acceptor,brutal_purge,soft_purge,[]} 44 | , {load_module,esockd_connection_sup,brutal_purge,soft_purge,[]} 45 | ] 46 | }, 47 | {"5.9.1", 48 | [ {load_module,esockd_acceptor,brutal_purge,soft_purge,[]} 49 | , {load_module,esockd_udp,brutal_purge,soft_purge,[]} 50 | , {load_module,esockd_proxy_protocol,brutal_purge,soft_purge,[]} 51 | , {load_module,esockd_connection_sup,brutal_purge,soft_purge,[]} 52 | ] 53 | }, 54 | {"5.9.0", 55 | [ {load_module,esockd_acceptor_sup,brutal_purge,soft_purge,[]} 56 | , {load_module,esockd_acceptor,brutal_purge,soft_purge,[]} 57 | , {load_module,esockd_dtls_acceptor,brutal_purge,soft_purge,[]} 58 | , {load_module,esockd_dtls_acceptor_sup,brutal_purge,soft_purge,[]} 59 | , {load_module,esockd_limiter,brutal_purge,soft_purge,[]} 60 | , {load_module,esockd_listener_sup,brutal_purge,soft_purge,[]} 61 | , {load_module,esockd_udp,brutal_purge,soft_purge,[]} 62 | , {load_module,esockd_proxy_protocol,brutal_purge,soft_purge,[]} 63 | , {add_module,esockd_generic_limiter} 64 | , {load_module,esockd_connection_sup,brutal_purge,soft_purge,[]} 65 | ] 66 | }, 67 | {<<".*">>, []} 68 | ], 69 | [ 70 | {"5.9.8", 71 | [ {load_module,esockd_udp,brutal_purge,soft_purge,[]} 72 | ]}, 73 | {"5.9.7", 74 | [ {load_module,esockd_transport,brutal_purge,soft_purge,[]} 75 | ] 76 | }, 77 | {"5.9.6", 78 | [ {load_module,esockd_proxy_protocol,brutal_purge,soft_purge,[]} 79 | , {load_module,esockd_connection_sup,brutal_purge,soft_purge,[]} 80 | , {load_module,esockd_transport,brutal_purge,soft_purge,[]} 81 | ] 82 | }, 83 | {"5.9.5", 84 | [ {load_module,esockd_transport,brutal_purge,soft_purge,[]} 85 | , {load_module,esockd_proxy_protocol,brutal_purge,soft_purge,[]} 86 | , {load_module,esockd_acceptor,brutal_purge,soft_purge,[]} 87 | , {load_module,esockd_connection_sup,brutal_purge,soft_purge,[]} 88 | ] 89 | }, 90 | {"5.9.4", 91 | [ {load_module,esockd_transport,brutal_purge,soft_purge,[]} 92 | , {load_module,esockd_proxy_protocol,brutal_purge,soft_purge,[]} 93 | , {load_module,esockd_acceptor,brutal_purge,soft_purge,[]} 94 | , {load_module,esockd_connection_sup,brutal_purge,soft_purge,[]} 95 | ] 96 | }, 97 | {"5.9.3", 98 | [ {load_module,esockd_transport,brutal_purge,soft_purge,[]} 99 | , {load_module,esockd_proxy_protocol,brutal_purge,soft_purge,[]} 100 | , {load_module,esockd_acceptor,brutal_purge,soft_purge,[]} 101 | , {load_module,esockd_connection_sup,brutal_purge,soft_purge,[]} 102 | ] 103 | }, 104 | {"5.9.2", 105 | [ {load_module,esockd_udp,brutal_purge,soft_purge,[]} 106 | , {load_module,esockd_transport,brutal_purge,soft_purge,[]} 107 | , {load_module,esockd_proxy_protocol,brutal_purge,soft_purge,[]} 108 | , {load_module,esockd_acceptor,brutal_purge,soft_purge,[]} 109 | , {load_module,esockd_connection_sup,brutal_purge,soft_purge,[]} 110 | ] 111 | }, 112 | {"5.9.1", 113 | [ {load_module,esockd_acceptor,brutal_purge,soft_purge,[]} 114 | , {load_module,esockd_udp,brutal_purge,soft_purge,[]} 115 | , {load_module,esockd_proxy_protocol,brutal_purge,soft_purge,[]} 116 | , {load_module,esockd_connection_sup,brutal_purge,soft_purge,[]} 117 | ] 118 | }, 119 | {"5.9.0", 120 | [ {load_module,esockd_acceptor_sup,brutal_purge,soft_purge,[]} 121 | , {load_module,esockd_acceptor,brutal_purge,soft_purge,[]} 122 | , {load_module,esockd_dtls_acceptor,brutal_purge,soft_purge,[]} 123 | , {load_module,esockd_dtls_acceptor_sup,brutal_purge,soft_purge,[]} 124 | , {load_module,esockd_limiter,brutal_purge,soft_purge,[]} 125 | , {load_module,esockd_listener_sup,brutal_purge,soft_purge,[]} 126 | , {load_module,esockd_udp,brutal_purge,soft_purge,[]} 127 | , {load_module,esockd_proxy_protocol,brutal_purge,soft_purge,[]} 128 | , {delete_module,esockd_generic_limiter} 129 | , {load_module,esockd_connection_sup,brutal_purge,soft_purge,[]} 130 | ] 131 | }, 132 | {<<".*">>, []} 133 | ] 134 | }. 135 | -------------------------------------------------------------------------------- /src/esockd_acceptor_sup.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2020 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(esockd_acceptor_sup). 18 | 19 | -behaviour(supervisor). 20 | 21 | -export([ start_supervised/1 22 | ]). 23 | 24 | -export([ start_acceptors/2 25 | , start_acceptor/2 26 | , count_acceptors/1 27 | , check_options/1 28 | ]). 29 | 30 | %% Supervisor callbacks 31 | -export([init/1]). 32 | 33 | %% callbacks 34 | -export([tune_socket/2]). 35 | 36 | %% Test 37 | -export([tune_socket_fun/1]). 38 | 39 | -define(ACCEPTOR_POOL, 16). 40 | 41 | %%-------------------------------------------------------------------- 42 | %% API 43 | %%-------------------------------------------------------------------- 44 | 45 | %% @doc Start Acceptor Supervisor. 46 | -spec start_supervised(esockd:listener_ref()) -> {ok, pid()}. 47 | start_supervised(ListenerRef = {Proto, ListenOn}) -> 48 | Type = esockd_server:get_listener_prop(ListenerRef, type), 49 | Opts = esockd_server:get_listener_prop(ListenerRef, options), 50 | case check_options(Opts) of 51 | ok -> 52 | TuneFun = tune_socket_fun(Opts), 53 | UpgradeFuns = upgrade_funs(Type, Opts), 54 | LimiterOpts = esockd_listener_sup:conn_limiter_opts(Opts, {listener, Proto, ListenOn}), 55 | Limiter = esockd_listener_sup:conn_rate_limiter(LimiterOpts), 56 | AcceptorMod = case Type of 57 | dtls -> esockd_dtls_acceptor; 58 | _ -> esockd_acceptor 59 | end, 60 | ConnSup = esockd_server:get_listener_prop(ListenerRef, connection_sup), 61 | AcceptorArgs = [Proto, ListenOn, ConnSup, TuneFun, UpgradeFuns, Limiter], 62 | case supervisor:start_link(?MODULE, {AcceptorMod, AcceptorArgs}) of 63 | {ok, Pid} -> 64 | _ = esockd_server:set_listener_prop(ListenerRef, acceptor_sup, Pid), 65 | {ok, Pid}; 66 | {error, _} = Error -> 67 | Error 68 | end; 69 | {error, _} = Error -> 70 | Error 71 | end. 72 | 73 | %% @doc Start acceptors. 74 | -spec start_acceptors(esockd:listener_ref(), inet:socket()) -> ok. 75 | start_acceptors(ListenerRef, LSock) -> 76 | Opts = esockd_server:get_listener_prop(ListenerRef, options), 77 | AcceptorNum = proplists:get_value(acceptors, Opts, ?ACCEPTOR_POOL), 78 | AcceptorSup = esockd_server:get_listener_prop(ListenerRef, acceptor_sup), 79 | lists:foreach( 80 | fun(_) -> {ok, _} = start_acceptor(AcceptorSup, LSock) end, 81 | lists:seq(1, AcceptorNum) 82 | ). 83 | 84 | %% @doc Start a acceptor. 85 | -spec(start_acceptor(pid(), inet:socket()) -> {ok, pid()} | {error, term()}). 86 | start_acceptor(AcceptorSup, LSock) -> 87 | supervisor:start_child(AcceptorSup, [LSock]). 88 | 89 | %% @doc Count acceptors. 90 | -spec(count_acceptors(AcceptorSup :: pid()) -> pos_integer()). 91 | count_acceptors(AcceptorSup) -> 92 | proplists:get_value(active, supervisor:count_children(AcceptorSup), 0). 93 | 94 | %%-------------------------------------------------------------------- 95 | %% Supervisor callbacks 96 | %%-------------------------------------------------------------------- 97 | 98 | init({AcceptorMod, AcceptorArgs}) -> 99 | SupFlags = #{strategy => simple_one_for_one, 100 | intensity => 100000, 101 | period => 1 102 | }, 103 | Acceptor = #{id => acceptor, 104 | start => {AcceptorMod, start_link, AcceptorArgs}, 105 | restart => transient, 106 | shutdown => 1000, 107 | type => worker, 108 | modules => [AcceptorMod] 109 | }, 110 | {ok, {SupFlags, [Acceptor]}}. 111 | 112 | %%-------------------------------------------------------------------- 113 | %% Internal functions 114 | %% ------------------------------------------------------------------- 115 | 116 | tune_socket_fun(Opts) -> 117 | Opts1 = case proplists:get_bool(tune_buffer, Opts) of 118 | true -> 119 | [{tune_buffer, true}]; 120 | false -> 121 | [] 122 | end, 123 | Opts2 = case proplists:get_value(tune_fun, Opts) of 124 | undefined -> 125 | []; 126 | MFA -> 127 | [{tune_fun, MFA}] 128 | end, 129 | TuneOpts = Opts1 ++ Opts2, 130 | {fun ?MODULE:tune_socket/2, [TuneOpts]}. 131 | 132 | upgrade_funs(Type, Opts) -> 133 | lists:append([proxy_upgrade_fun(Opts), ssl_upgrade_fun(Type, Opts)]). 134 | 135 | proxy_upgrade_fun(Opts) -> 136 | case proplists:get_bool(proxy_protocol, Opts) of 137 | false -> []; 138 | true -> [esockd_transport:proxy_upgrade_fun(Opts)] 139 | end. 140 | 141 | ssl_upgrade_fun(Type, Opts) -> 142 | Key = case Type of 143 | dtls -> dtls_options; 144 | _ -> ssl_options 145 | end, 146 | case proplists:get_value(Key, Opts) of 147 | undefined -> []; 148 | SslOpts -> [esockd_transport:ssl_upgrade_fun(SslOpts)] 149 | end. 150 | 151 | tune_socket(Sock, []) -> 152 | {ok, Sock}; 153 | tune_socket(Sock, [{tune_buffer, true}|More]) -> 154 | case esockd_transport:getopts(Sock, [sndbuf, recbuf, buffer]) of 155 | {ok, BufSizes} -> 156 | BufSz = lists:max([Sz || {_Opt, Sz} <- BufSizes]), 157 | case esockd_transport:setopts(Sock, [{buffer, BufSz}]) of 158 | ok -> 159 | tune_socket(Sock, More); 160 | Error -> 161 | Error 162 | end; 163 | Error -> 164 | Error 165 | end; 166 | tune_socket(Sock, [{tune_fun, {M, F, A}} | More]) -> 167 | case apply(M, F, A) of 168 | ok -> 169 | tune_socket(Sock, More); 170 | Error -> Error 171 | end; 172 | tune_socket(Sock, [_|More]) -> 173 | tune_socket(Sock, More). 174 | 175 | -spec check_options(list()) -> ok | {error, any()}. 176 | check_options(Opts) -> 177 | try 178 | ok = check_ssl_opts(ssl_options, Opts), 179 | ok = check_ssl_opts(dtls_options, Opts) 180 | catch 181 | throw : Reason -> 182 | {error, Reason} 183 | end. 184 | 185 | check_ssl_opts(Key, Opts) -> 186 | case proplists:get_value(Key, Opts) of 187 | undefined -> 188 | ok; 189 | SslOpts -> 190 | try 191 | {ok, _} = ssl:handle_options(SslOpts, server, undefined), 192 | ok 193 | catch 194 | _ : {error, Reason} -> 195 | throw_invalid_ssl_option(Key, Reason); 196 | _ : Wat : Stack -> 197 | %% It's a function_clause for OTP 25 198 | %% And, maybe OTP decide to change some day, who knows 199 | throw_invalid_ssl_option(Key, {Wat, Stack}) 200 | end 201 | end. 202 | 203 | -spec throw_invalid_ssl_option(_, _) -> no_return(). 204 | throw_invalid_ssl_option(Key, Reason) -> 205 | throw(#{error => invalid_ssl_option, 206 | reason => Reason, 207 | key => Key}). 208 | -------------------------------------------------------------------------------- /src/esockd_access.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2020 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(esockd_access). 18 | 19 | -type(rule() :: {allow, all} | {allow, string()} 20 | | {deny, all} | {deny, string()}). 21 | 22 | -type(compiled_rule() :: {allow, all} 23 | | {allow, esockd_cidr:cidr()} 24 | | {deny, all} 25 | | {deny, esockd_cidr:cidr()}). 26 | -export_type([rule/0]). 27 | 28 | -export([compile/1, match/2]). 29 | 30 | %% @doc Build CIDR, compile rule. 31 | -spec(compile(rule()) -> compiled_rule()). 32 | compile({allow, all}) -> 33 | {allow, all}; 34 | compile({allow, CIDR}) when is_list(CIDR) -> 35 | compile(allow, CIDR); 36 | compile({deny, CIDR}) when is_list(CIDR) -> 37 | compile(deny, CIDR); 38 | compile({deny, all}) -> 39 | {deny, all}. 40 | compile(Type, CIDR) when is_list(CIDR) -> 41 | {Type, esockd_cidr:parse(CIDR, true)}. %% Adjust??? 42 | 43 | %% @doc Match Addr with Access Rules. 44 | -spec(match(inet:ip_address(), [compiled_rule()]) 45 | -> {matched, allow} | {matched, deny} | nomatch). 46 | match(Addr, Rules) when is_tuple(Addr) -> 47 | match2(Addr, Rules). 48 | 49 | match2(_Addr, []) -> 50 | nomatch; 51 | match2(_Addr, [{allow, all} | _]) -> 52 | {matched, allow}; 53 | match2(_Addr, [{deny, all} | _]) -> 54 | {matched, deny}; 55 | match2(Addr, [{Access, CIDR = {_StartAddr, _EndAddr, _Len}} | Rules]) 56 | when Access == allow orelse Access == deny -> 57 | case esockd_cidr:match(Addr, CIDR) of 58 | true -> {matched, Access}; 59 | false -> match2(Addr, Rules) 60 | end. 61 | 62 | -------------------------------------------------------------------------------- /src/esockd_app.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2020 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(esockd_app). 18 | 19 | -behaviour(application). 20 | 21 | -export([start/2, stop/1]). 22 | 23 | start(_StartType, _StartArgs) -> 24 | esockd_sup:start_link(). 25 | 26 | stop(_State) -> 27 | ok. 28 | 29 | -------------------------------------------------------------------------------- /src/esockd_cidr.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2016 Benoît Chesneau 2 | %% Copyright (c) 2020 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %% 16 | %% @doc 17 | %% 18 | %% CIDR Wiki: https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing 19 | %% 20 | %% The module is copied from inet_cidr.erl to avoid one file depencency. 21 | %% 22 | %% @end 23 | 24 | -module(esockd_cidr). 25 | 26 | -export([ parse/1 27 | , parse/2 28 | , match/2 29 | , count/1 30 | , to_string/1 31 | ]). 32 | 33 | -export([ is_ipv4/1 34 | , is_ipv6/1 35 | ]). 36 | 37 | -export_type([ cidr_string/0 38 | , cidr/0 39 | ]). 40 | 41 | -type(cidr_string() :: string()). 42 | -type(cidr() :: {inet:ip_address(), inet:ip_address(), 0..128}). 43 | 44 | %%-------------------------------------------------------------------- 45 | %% API 46 | %%-------------------------------------------------------------------- 47 | 48 | %% @doc Parse CIDR. 49 | -spec(parse(string()) -> cidr()). 50 | parse(S) -> 51 | parse(S, false). 52 | 53 | -spec(parse(string(), boolean()) -> cidr()). 54 | parse(S, Adjust) -> 55 | case string:tokens(S, "/") of 56 | [AddrStr] -> parse_addr(AddrStr); 57 | [AddrStr, LenStr] -> parse_cidr(AddrStr, LenStr, Adjust) 58 | end. 59 | 60 | parse_addr(AddrStr) -> 61 | {ok, Addr} = inet:parse_address(AddrStr), 62 | {Addr, Addr, bit_count(Addr)}. 63 | 64 | parse_cidr(AddrStr, LenStr, Adjust) -> 65 | {ok, Addr} = inet:parse_address(AddrStr), 66 | PrefixLen = list_to_integer(LenStr), 67 | StartAddr = band_with_mask(Addr, start_mask(Addr, PrefixLen)), 68 | if 69 | Adjust /= true, StartAddr /= Addr -> error(invalid_cidr); 70 | true -> ok 71 | end, 72 | EndAddr = calc_end_address(StartAddr, PrefixLen), 73 | {StartAddr, EndAddr, PrefixLen}. 74 | 75 | %% @doc Check if the IP address is in the CIDR block. 76 | -spec(match(inet:ip_address(), cidr()) -> boolean()). 77 | match({W, X, Y, Z}, {{A, B, C, D}, {E, F, G, H}, _Len}) when 78 | ((W >= A) andalso (W =< E)), 79 | ((X >= B) andalso (X =< F)), 80 | ((Y >= C) andalso (Y =< G)), 81 | ((Z >= D) andalso (Z =< H)) -> 82 | true; 83 | match({R, S, T, U, V, W, X, Y}, {{A, B, C, D, E, F, G, H}, {I, J, K, L, M, N, O, P}, _Len}) when 84 | ((R >= A) andalso (R =< I)), 85 | ((S >= B) andalso (S =< J)), 86 | ((T >= C) andalso (T =< K)), 87 | ((U >= D) andalso (U =< L)), 88 | ((V >= E) andalso (V =< M)), 89 | ((W >= F) andalso (W =< N)), 90 | ((X >= G) andalso (X =< O)), 91 | ((Y >= H) andalso (Y =< P)) -> 92 | true; 93 | match(_, _) -> 94 | false. 95 | 96 | count({{_, _, _, _}, _EndAddr, Len}) -> 97 | 1 bsl (32 - Len); 98 | count({{_, _, _, _, _, _, _, _}, _EndAddr, Len}) -> 99 | 1 bsl (128 - Len). 100 | 101 | to_string({StartAddr, _EndAddr, Len}) -> 102 | inet:ntoa(StartAddr) ++ "/" ++ integer_to_list(Len). 103 | 104 | %% @doc Return true if the value is an ipv4 address 105 | is_ipv4({A, B, C, D}) -> 106 | ((A >= 0) and (A =< 255)) and 107 | ((B >= 0) and (B =< 255)) and 108 | ((C >= 0) and (C =< 255)) and 109 | ((D >= 0) and (D =< 255)); 110 | is_ipv4(_) -> 111 | false. 112 | 113 | %% @doc Return true if the value is an ipv6 address 114 | is_ipv6({A, B, C, D, E, F, G, H}) -> 115 | ((A >= 0) and (A =< 65535)) and 116 | ((B >= 0) and (B =< 65535)) and 117 | ((C >= 0) and (C =< 65535)) and 118 | ((D >= 0) and (D =< 65535)) and 119 | ((E >= 0) and (E =< 65535)) and 120 | ((F >= 0) and (F =< 65535)) and 121 | ((G >= 0) and (G =< 65535)) and 122 | ((H >= 0) and (H =< 65535)); 123 | is_ipv6(_) -> 124 | false. 125 | 126 | %%-------------------------------------------------------------------- 127 | %% Internal Functions 128 | %%-------------------------------------------------------------------- 129 | 130 | start_mask({_, _, _, _} = Addr, Len) when 0 =< Len, Len =< 32 -> 131 | {A, B, C, D} = end_mask(Addr, Len), 132 | {bnot A, bnot B, bnot C, bnot D}; 133 | 134 | start_mask({_, _, _, _, _, _, _, _} = Addr, Len) when 0 =< Len, Len =< 128 -> 135 | {A, B, C, D, E, F, G, H} = end_mask(Addr, Len), 136 | {bnot A, bnot B, bnot C, bnot D, bnot E, bnot F, bnot G, bnot H}. 137 | 138 | end_mask({_, _, _, _}, Len) when 0 =< Len, Len =< 32 -> 139 | if 140 | Len == 32 -> {0, 0, 0, 0}; 141 | Len >= 24 -> {0, 0, 0, bmask(Len, 8)}; 142 | Len >= 16 -> {0, 0, bmask(Len, 8), 16#FF}; 143 | Len >= 8 -> {0, bmask(Len, 8), 16#FF, 16#FF}; 144 | Len >= 0 -> {bmask(Len, 8), 16#FF, 16#FF, 16#FF} 145 | end; 146 | 147 | end_mask({_, _, _, _, _, _, _, _}, Len) when 0 =< Len, Len =< 128 -> 148 | if 149 | Len == 128 -> {0, 0, 0, 0, 0, 0, 0, 0}; 150 | Len >= 112 -> {0, 0, 0, 0, 0, 0, 0, bmask(Len, 16)}; 151 | Len >= 96 -> {0, 0, 0, 0, 0, 0, bmask(Len, 16), 16#FFFF}; 152 | Len >= 80 -> {0, 0, 0, 0, 0, bmask(Len, 16), 16#FFFF, 16#FFFF}; 153 | Len >= 64 -> {0, 0, 0, 0, bmask(Len, 16), 16#FFFF, 16#FFFF, 16#FFFF}; 154 | Len >= 49 -> {0, 0, 0, bmask(Len, 16), 16#FFFF, 16#FFFF, 16#FFFF, 16#FFFF}; 155 | Len >= 32 -> {0, 0, bmask(Len, 16), 16#FFFF, 16#FFFF, 16#FFFF, 16#FFFF, 16#FFFF}; 156 | Len >= 16 -> {0, bmask(Len, 16), 16#FFFF, 16#FFFF, 16#FFFF, 16#FFFF, 16#FFFF, 16#FFFF}; 157 | Len >= 0 -> {bmask(Len, 16), 16#FFFF, 16#FFFF, 16#FFFF, 16#FFFF, 16#FFFF, 16#FFFF, 16#FFFF} 158 | end. 159 | 160 | bmask(I, 8) when 0 =< I, I =< 32 -> 161 | 16#FF bsr (I rem 8); 162 | bmask(I, 16) when 0 =< I, I =< 128 -> 163 | 16#FFFF bsr (I rem 16). 164 | 165 | calc_end_address(Addr, Len) -> 166 | bor_with_mask(Addr, end_mask(Addr, Len)). 167 | 168 | bor_with_mask({A, B, C, D}, {E, F, G, H}) -> 169 | {A bor E, B bor F, C bor G, D bor H}; 170 | bor_with_mask({A, B, C, D, E, F, G, H}, {I, J, K, L, M, N, O, P}) -> 171 | {A bor I, B bor J, C bor K, D bor L, E bor M, F bor N, G bor O, H bor P}. 172 | 173 | band_with_mask({A, B, C, D}, {E, F, G, H}) -> 174 | {A band E, B band F, C band G, D band H}; 175 | band_with_mask({A, B, C, D, E, F, G, H}, {I, J, K, L, M, N, O, P}) -> 176 | {A band I, B band J, C band K, D band L, E band M, F band N, G band O, H band P}. 177 | 178 | bit_count({_, _, _, _}) -> 32; 179 | bit_count({_, _, _, _, _, _, _, _}) -> 128. 180 | 181 | -------------------------------------------------------------------------------- /src/esockd_dtls_acceptor.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2020 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(esockd_dtls_acceptor). 18 | 19 | -behaviour(gen_statem). 20 | 21 | -include("esockd.hrl"). 22 | 23 | -export([start_link/7]). 24 | 25 | -export([ accepting/3 26 | , suspending/3 27 | ]). 28 | 29 | %% gen_statem callbacks 30 | -export([ init/1 31 | , callback_mode/0 32 | , terminate/3 33 | , code_change/4 34 | ]). 35 | 36 | -record(state, { 37 | proto :: atom(), 38 | listen_on :: esockd:listen_on(), 39 | lsock :: ssl:sslsocket(), 40 | sockname :: {inet:ip_address(), inet:port_number()}, 41 | tune_fun :: esockd:sock_fun(), 42 | upgrade_funs :: [esockd:sock_fun()], 43 | conn_limiter :: undefined | esockd_generic_limiter:limiter(), 44 | conn_sup :: pid() 45 | }). 46 | 47 | %% @doc Start an acceptor 48 | -spec(start_link(atom(), esockd:listen_on(), pid(), 49 | esockd:sock_fun(), [esockd:sock_fun()], 50 | esockd_generic_limiter:limiter(), inet:socket()) 51 | -> {ok, pid()} | {error, term()}). 52 | start_link(Proto, ListenOn, ConnSup, 53 | TuneFun, UpgradeFuns, Limiter, LSock) -> 54 | gen_statem:start_link(?MODULE, [Proto, ListenOn, ConnSup, 55 | TuneFun, UpgradeFuns, Limiter, LSock], []). 56 | 57 | %%-------------------------------------------------------------------- 58 | %% gen_statem callbacks 59 | %%-------------------------------------------------------------------- 60 | 61 | init([Proto, ListenOn, ConnSup, 62 | TuneFun, UpgradeFuns, Limiter, LSock]) -> 63 | _ = rand:seed(exsplus, erlang:timestamp()), 64 | {ok, Sockname} = ssl:sockname(LSock), 65 | {ok, accepting, #state{proto = Proto, 66 | listen_on = ListenOn, 67 | lsock = LSock, 68 | sockname = Sockname, 69 | tune_fun = TuneFun, 70 | upgrade_funs = UpgradeFuns, 71 | conn_limiter = Limiter, 72 | conn_sup = ConnSup}, 73 | {next_event, internal, accept}}. 74 | 75 | callback_mode() -> state_functions. 76 | 77 | accepting(internal, accept, 78 | State = #state{proto = Proto, 79 | listen_on = ListenOn, 80 | lsock = LSock, 81 | sockname = Sockname, 82 | tune_fun = TuneFun, 83 | upgrade_funs = UpgradeFuns, 84 | conn_sup = ConnSup}) -> 85 | case ssl:transport_accept(LSock) of 86 | {ok, Sock} -> 87 | %% Inc accepted stats. 88 | _ = esockd_server:inc_stats({Proto, ListenOn}, accepted, 1), 89 | _ = case eval_tune_socket_fun(TuneFun, Sock) of 90 | {ok, Sock} -> 91 | case esockd_connection_sup:start_connection(ConnSup, Sock, UpgradeFuns) of 92 | {ok, _Pid} -> ok; 93 | {error, enotconn} -> 94 | close(Sock); %% quiet...issue #10 95 | {error, einval} -> 96 | close(Sock); %% quiet... haproxy check 97 | {error, Reason} -> 98 | error_logger:error_msg("Failed to start connection on ~s: ~p", 99 | [esockd:format(Sockname), Reason]), 100 | close(Sock) 101 | end; 102 | {error, enotconn} -> 103 | close(Sock); 104 | {error, einval} -> 105 | close(Sock); 106 | {error, closed} -> 107 | close(Sock); 108 | {error, Reason} -> 109 | error_logger:error_msg("Tune buffer failed on ~s: ~s", 110 | [esockd:format(Sockname), Reason]), 111 | close(Sock) 112 | end, 113 | rate_limit(State); 114 | {error, Reason} when Reason =:= emfile; 115 | Reason =:= enfile -> 116 | {next_state, suspending, State, 1000}; 117 | {error, closed} -> 118 | {stop, normal, State}; 119 | {error, Reason} -> 120 | {stop, Reason, State} 121 | end. 122 | 123 | suspending(timeout, _Timeout, State) -> 124 | {next_state, accepting, State, {next_event, internal, accept}}; 125 | 126 | suspending(cast, {set_conn_limiter, Limiter}, State) -> 127 | {keep_state, State#state{conn_limiter = Limiter}}. 128 | 129 | terminate(_Reason, _StateName, _State) -> 130 | ok. 131 | 132 | code_change(_OldVsn, StateName, State, _Extra) -> 133 | {ok, StateName, State}. 134 | 135 | %%-------------------------------------------------------------------- 136 | %% Internal funcs 137 | %%-------------------------------------------------------------------- 138 | 139 | close(Sock) -> ssl:close(Sock). 140 | 141 | rate_limit(State = #state{conn_limiter = Limiter}) -> 142 | case esockd_generic_limiter:consume(1, Limiter) of 143 | {ok, Limiter2} -> 144 | {keep_state, 145 | State#state{conn_limiter = Limiter2}, 146 | {next_event, internal, accept}}; 147 | {pause, PauseTime, Limiter2} -> 148 | {next_state, suspending, State#state{conn_limiter = Limiter2}, PauseTime} 149 | end. 150 | 151 | eval_tune_socket_fun({Fun, Args1}, Sock) -> 152 | apply(Fun, [Sock|Args1]). 153 | -------------------------------------------------------------------------------- /src/esockd_dtls_listener.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2020 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(esockd_dtls_listener). 18 | 19 | -behaviour(gen_server). 20 | 21 | -include("esockd.hrl"). 22 | 23 | -export([ start_link/3 24 | , start_supervised/1 25 | ]). 26 | 27 | -export([ options/1 28 | , get_port/1 29 | , get_state/1 30 | , set_options/2 31 | ]). 32 | 33 | %% gen_server callbacks 34 | -export([ init/1 35 | , handle_call/3 36 | , handle_cast/2 37 | , handle_info/2 38 | , terminate/2 39 | , code_change/3 40 | ]). 41 | 42 | -record(state, { 43 | proto :: atom(), 44 | listen_on :: esockd:listen_on(), 45 | lsock :: ssl:sslsocket(), 46 | laddr :: inet:ip_address(), 47 | lport :: inet:port_number(), 48 | sockopts :: [gen_udp:option()], 49 | dtlsopts :: [ssl:tls_server_option()] 50 | }). 51 | 52 | -type option() :: {dtls_options, [gen_udp:option()]}. 53 | 54 | -define(DEFAULT_SOCK_OPTIONS, 55 | [{mode, binary}, 56 | {reuseaddr, true}]). 57 | 58 | -define(DEFAULT_DTLS_OPTIONS, 59 | [{protocol, dtls}]). 60 | 61 | -spec start_link(atom(), esockd:listen_on(), [esockd:option()]) 62 | -> {ok, pid()} | ignore | {error, term()}. 63 | start_link(Proto, ListenOn, Opts) -> 64 | gen_server:start_link(?MODULE, {Proto, ListenOn, Opts}, []). 65 | 66 | -spec start_supervised(esockd:listener_ref()) 67 | -> {ok, pid()} | ignore | {error, term()}. 68 | start_supervised(ListenerRef = {Proto, ListenOn}) -> 69 | Opts = esockd_server:get_listener_prop(ListenerRef, options), 70 | case start_link(Proto, ListenOn, Opts) of 71 | {ok, Pid} -> 72 | _ = esockd_server:set_listener_prop(ListenerRef, listener, {?MODULE, Pid}), 73 | {ok, Pid}; 74 | Error -> 75 | Error 76 | end. 77 | 78 | -spec(options(pid()) -> [esockd:option()]). 79 | options(Listener) -> 80 | gen_server:call(Listener, options). 81 | 82 | -spec(get_port(pid()) -> inet:port_number()). 83 | get_port(Listener) -> 84 | gen_server:call(Listener, get_port). 85 | 86 | -spec get_state(pid()) -> proplists:proplist(). 87 | get_state(Listener) -> 88 | gen_server:call(Listener, get_state). 89 | 90 | -spec set_options(pid(), [option()]) -> ok. 91 | set_options(Listener, Opts) -> 92 | gen_server:call(Listener, {set_options, Opts}). 93 | 94 | %%-------------------------------------------------------------------- 95 | %% gen_server callbacks 96 | %%-------------------------------------------------------------------- 97 | 98 | init({Proto, ListenOn, Opts}) -> 99 | Port = port(ListenOn), 100 | process_flag(trap_exit, true), 101 | esockd_server:ensure_stats({Proto, ListenOn}), 102 | SockOpts = merge_sock_defaults(merge_addr(ListenOn, sockopts(Opts))), 103 | DTLSOpts = merge_dtls_defaults(dtlsopts(Opts)), 104 | %% Don't active the socket... 105 | case ssl:listen(Port, SockOpts ++ DTLSOpts) of 106 | %%case ssl:listen(Port, [{active, false} | proplists:delete(active, SockOpts)]) of 107 | {ok, LSock} -> 108 | {ok, {LAddr, LPort}} = ssl:sockname(LSock), 109 | {ok, #state{proto = Proto, listen_on = ListenOn, 110 | lsock = LSock, laddr = LAddr, lport = LPort, 111 | sockopts = SockOpts, dtlsopts = DTLSOpts}}; 112 | {error, Reason} -> 113 | error_logger:error_msg("~s failed to listen on ~p - ~p (~s)", 114 | [Proto, Port, Reason, inet:format_error(Reason)]), 115 | {stop, Reason} 116 | end. 117 | 118 | dtlsopts(Opts) -> 119 | %% Filter out `esockd:ssl_custom_option()`, otherwise DTLS listener will 120 | %% fail to start. 121 | lists:foldl( 122 | fun proplists:delete/2, 123 | proplists:get_value(dtls_options, Opts, []), 124 | [handshake_timeout, gc_after_handshake] 125 | ). 126 | 127 | sockopts(Opts) -> 128 | proplists:get_value(udp_options, Opts, []). 129 | 130 | port(Port) when is_integer(Port) -> Port; 131 | port({_Addr, Port}) -> Port. 132 | 133 | merge_sock_defaults(SockOpts) -> 134 | esockd:merge_opts(?DEFAULT_SOCK_OPTIONS, SockOpts). 135 | 136 | merge_dtls_defaults(SockOpts) -> 137 | esockd:merge_opts(?DEFAULT_DTLS_OPTIONS, SockOpts). 138 | 139 | merge_addr(Port, SockOpts) when is_integer(Port) -> 140 | SockOpts; 141 | merge_addr({Addr, _Port}, SockOpts) -> 142 | lists:keystore(ip, 1, SockOpts, {ip, Addr}). 143 | 144 | handle_call(get_port, _From, State = #state{lport = LPort}) -> 145 | {reply, LPort, State}; 146 | 147 | handle_call(get_state, _From, State = #state{lsock = LSock, lport = LPort}) -> 148 | Reply = [ {listen_sock, LSock} 149 | , {listen_port, LPort} 150 | ], 151 | {reply, Reply, State}; 152 | 153 | handle_call({set_options, Opts} 154 | , _From 155 | , State = #state{lsock = LSock, sockopts = SockOpts, dtlsopts = DTLSOpts} 156 | ) -> 157 | SockOptsIn = sockopts(Opts), 158 | DTLSOptsIn = dtlsopts(Opts), 159 | case esockd:changed_opts(DTLSOptsIn, DTLSOpts) of 160 | [] -> 161 | SockOptsChanged = esockd:changed_opts(SockOptsIn, SockOpts), 162 | case ssl:setopts(LSock, SockOptsChanged) of 163 | ok -> 164 | {reply, ok, State#state{sockopts = SockOptsIn}}; 165 | Error = {error, _} -> 166 | {reply, Error, State} 167 | end; 168 | [_ | _] -> 169 | %% If the dTLS option set is different, bail out. 170 | %% Setting dTLS options on listening socket always succeeds, 171 | %% even if the options are invalid. 172 | {reply, {error, not_supported}, State} 173 | end; 174 | 175 | handle_call(Req, _From, State) -> 176 | error_logger:error_msg("[~s] Unexpected call: ~p", [?MODULE, Req]), 177 | {noreply, State}. 178 | 179 | handle_cast(Msg, State) -> 180 | error_logger:error_msg("[~s] Unexpected cast: ~p", [?MODULE, Msg]), 181 | {noreply, State}. 182 | 183 | handle_info(Info, State) -> 184 | error_logger:error_msg("[~s] Unexpected info: ~p", [?MODULE, Info]), 185 | {noreply, State}. 186 | 187 | terminate(_Reason, #state{proto = Proto, listen_on = ListenOn, 188 | lsock = LSock, laddr = Addr, lport = Port}) -> 189 | error_logger:info_msg("~s stopped on ~s~n", [Proto, esockd:format({Addr, Port})]), 190 | esockd_limiter:delete({listener, Proto, ListenOn}), 191 | esockd_server:del_stats({Proto, ListenOn}), 192 | esockd_transport:fast_close(LSock). 193 | 194 | code_change(_OldVsn, State, _Extra) -> 195 | {ok, State}. 196 | 197 | -------------------------------------------------------------------------------- /src/esockd_generic_limiter.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2021 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(esockd_generic_limiter). 18 | 19 | -export([create/1, consume/2, delete/1]). 20 | -export_type([limiter/0, create_options/0, consume_result/0]). 21 | 22 | -type pause_time() :: non_neg_integer(). 23 | 24 | -type limiter() :: #{ module := atom() 25 | , name := atom() 26 | 27 | %% other context 28 | , atom() => term() 29 | }. 30 | 31 | -type create_options() :: #{ module := atom() 32 | , atom() => term() 33 | }. 34 | 35 | -type consume_result() :: {ok, limiter()} | 36 | {pause, pause_time(), limiter()}. 37 | 38 | -callback create(create_options()) -> limiter(). 39 | 40 | -callback consume(integer(), limiter()) -> consume_result(). 41 | 42 | -callback delete(limiter()) -> ok. 43 | 44 | %%-------------------------------------------------------------------- 45 | %% Callbacks 46 | %%-------------------------------------------------------------------- 47 | create(#{module := Module} = Opts) -> 48 | Module:create(Opts). 49 | 50 | consume(Token, #{module := Module} = Limiter) -> 51 | Module:consume(Token, Limiter); 52 | 53 | %% Limiter maybe undefined, it means no limit 54 | consume(_Token, undefined) -> 55 | {ok, undefined}. 56 | 57 | delete(#{module := Module} = Limiter) -> 58 | Module:delete(Limiter); 59 | 60 | delete(undefined) -> 61 | ok. 62 | -------------------------------------------------------------------------------- /src/esockd_limiter.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2020 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | %% @doc A simple ets-based rate limit server. 18 | -module(esockd_limiter). 19 | 20 | -behaviour(gen_server). 21 | -behaviour(esockd_generic_limiter). 22 | 23 | -export([ start_link/0 24 | , get_all/0 25 | , stop/0 26 | ]). 27 | 28 | -export([ create/1 29 | , create/2 30 | , create/3 31 | , lookup/1 32 | , consume/1 33 | , consume/2 34 | , delete/1 35 | ]). 36 | 37 | %% gen_server callbacks 38 | -export([ init/1 39 | , handle_call/3 40 | , handle_cast/2 41 | , handle_info/2 42 | , terminate/2 43 | , code_change/3 44 | ]). 45 | 46 | -type(bucket_name() :: term()). 47 | 48 | -type(bucket_info() :: #{name => bucket_name(), 49 | capacity => pos_integer(), 50 | interval => pos_integer(), 51 | tokens => pos_integer(), 52 | lasttime => integer() 53 | }). 54 | 55 | -export_type([bucket_info/0]). 56 | 57 | -define(TAB, ?MODULE). 58 | -define(SERVER, ?MODULE). 59 | 60 | %%-------------------------------------------------------------------- 61 | %% APIs 62 | %%-------------------------------------------------------------------- 63 | 64 | -spec(start_link() -> {ok, pid()}). 65 | start_link() -> 66 | gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). 67 | 68 | -spec(get_all() -> list(bucket_info())). 69 | get_all() -> 70 | [bucket_info(Bucket) || Bucket = {{bucket, _}, _, _, _} <- ets:tab2list(?TAB)]. 71 | 72 | bucket_info({{bucket, Name}, Capacity, Interval, LastTime}) -> 73 | #{name => Name, 74 | capacity => Capacity, 75 | interval => Interval, 76 | tokens => tokens(Name), 77 | lasttime => LastTime 78 | }. 79 | 80 | tokens(Name) -> 81 | ets:lookup_element(?TAB, {tokens, Name}, 2). 82 | 83 | -spec(stop() -> ok). 84 | stop() -> 85 | gen_server:stop(?SERVER). 86 | 87 | -spec(create(bucket_name(), pos_integer()) -> ok). 88 | create(Name, Capacity) when is_integer(Capacity), Capacity > 0 -> 89 | create(Name, Capacity, 1). 90 | 91 | -spec(create(bucket_name(), pos_integer(), pos_integer()) -> ok). 92 | create(Name, Capacity, Interval) when is_integer(Capacity), Capacity > 0, 93 | is_integer(Interval), Interval > 0 -> 94 | gen_server:call(?SERVER, {create, Name, Capacity, Interval}). 95 | 96 | -spec(lookup(bucket_name()) -> undefined | bucket_info()). 97 | lookup(Name) -> 98 | case ets:lookup(?TAB, {bucket, Name}) of 99 | [] -> undefined; 100 | [Bucket] -> bucket_info(Bucket) 101 | end. 102 | 103 | -spec(consume(bucket_name()) 104 | -> {Remaing :: integer(), PasueMillSec :: integer()}). 105 | consume(Name) -> 106 | consume(Name, 1). 107 | 108 | %% The dialyzer cannot support this type specification 109 | %% Because the dialyzer think consume/2 can never return {0, _} 110 | %%-spec consume(bucket_name(), pos_integer()) -> {non_neg_integer(), non_neg_integer()}; 111 | %% (pos_integer(), esockd_generic_limiter:limiter()) -> esockd_generic_limiter:co 112 | consume(Tokens, #{name := Name} = Limiter) -> 113 | case consume(Name, Tokens) of 114 | {0, PauseTime} -> 115 | {pause, PauseTime, Limiter}; 116 | _ -> 117 | {ok, Limiter} 118 | end; 119 | 120 | consume(Name, Tokens) when is_integer(Tokens), Tokens > 0 -> 121 | try ets:update_counter(?TAB, {tokens, Name}, {2, -Tokens, 0, 0}) of 122 | 0 -> {0, pause_time(Name, erlang:system_time(millisecond))}; 123 | I -> {I, 0} 124 | catch 125 | error:badarg -> {1, 0} 126 | end. 127 | 128 | %% @private 129 | -spec pause_time(_, pos_integer()) -> pos_integer(). 130 | pause_time(Name, Now) -> 131 | case ets:lookup(?TAB, {bucket, Name}) of 132 | [] -> 1000; %% Pause 1 second if the bucket is deleted. 133 | [{_Bucket, _Capacity, Interval, LastTime}] -> 134 | max(1, LastTime + (Interval * 1000) - Now) 135 | end. 136 | 137 | -spec delete(esockd_generic_limiter:limiter() | bucket_name()) -> ok. 138 | delete(#{name := Name}) -> 139 | delete(Name); 140 | delete(Name) -> 141 | gen_server:cast(?SERVER, {delete, Name}). 142 | 143 | -spec create(esockd_generic_limiter:create_options()) -> esockd_generic_limiter:limiter(). 144 | create(#{name := LimiterName, capacity := Capacity, interval := Interval}) -> 145 | create(LimiterName, Capacity, Interval), 146 | #{name => LimiterName, module => ?MODULE}. 147 | 148 | %%-------------------------------------------------------------------- 149 | %% gen_server callbacks 150 | %%-------------------------------------------------------------------- 151 | 152 | init([]) -> 153 | _ = ets:new(?TAB, [public, set, named_table, {write_concurrency, true}]), 154 | {ok, #{countdown => #{}, timer => undefined}}. 155 | 156 | handle_call({create, Name, Capacity, Interval}, _From, State = #{countdown := Countdown}) -> 157 | true = ets:insert(?TAB, {{tokens, Name}, Capacity}), 158 | true = ets:insert(?TAB, {{bucket, Name}, Capacity, Interval, erlang:system_time(millisecond)}), 159 | NCountdown = maps:put({bucket, Name}, Interval, Countdown), 160 | {reply, ok, ensure_countdown_timer(State#{countdown := NCountdown})}; 161 | 162 | handle_call(Req, _From, State) -> 163 | error_logger:error_msg("Unexpected call: ~p", [Req]), 164 | {reply, ignore, State}. 165 | 166 | handle_cast({delete, Name}, State = #{countdown := Countdown}) -> 167 | true = ets:delete(?TAB, {bucket, Name}), 168 | true = ets:delete(?TAB, {tokens, Name}), 169 | NCountdown = maps:remove({bucket, Name}, Countdown), 170 | {noreply, State#{countdown := NCountdown}}; 171 | 172 | handle_cast(Msg, State) -> 173 | error_logger:error_msg("Unexpected cast: ~p~n", [Msg]), 174 | {noreply, State}. 175 | 176 | handle_info({timeout, Timer, countdown}, State = #{countdown := Countdown, timer := Timer}) -> 177 | Countdown1 = maps:fold( 178 | fun(Key = {bucket, Name}, 1, Map) -> 179 | [{_Key, Capacity, Interval, _LastTime}] = ets:lookup(?TAB, Key), 180 | true = ets:update_element(?TAB, {tokens, Name}, {2, Capacity}), 181 | true = ets:update_element(?TAB, {bucket, Name}, {4, erlang:system_time(millisecond)}), 182 | maps:put(Key, Interval, Map); 183 | (Key, C, Map) when C > 1 -> 184 | maps:put(Key, C-1, Map) 185 | end, #{}, Countdown), 186 | NState = State#{countdown := Countdown1, timer := undefined}, 187 | {noreply, ensure_countdown_timer(NState)}; 188 | 189 | handle_info(Info, State) -> 190 | error_logger:error_msg("Unexpected info: ~p~n", [Info]), 191 | {noreply, State}. 192 | 193 | terminate(_Reason, _State) -> 194 | ok. 195 | 196 | code_change(_OldVsn, State, _Extra) -> 197 | {ok, State}. 198 | 199 | %%-------------------------------------------------------------------- 200 | %% Internal functions 201 | %%-------------------------------------------------------------------- 202 | 203 | ensure_countdown_timer(State = #{timer := undefined}) -> 204 | TRef = erlang:start_timer(timer:seconds(1), self(), countdown), 205 | State#{timer := TRef}; 206 | ensure_countdown_timer(State = #{timer := _TRef}) -> State. 207 | -------------------------------------------------------------------------------- /src/esockd_listener.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2020 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(esockd_listener). 18 | 19 | -behaviour(gen_server). 20 | 21 | -include("esockd.hrl"). 22 | 23 | -export([ start_link/3 24 | , start_supervised/1 25 | ]). 26 | 27 | -export([ get_port/1 28 | , get_lsock/1 29 | , get_state/1 30 | , set_options/2 31 | ]). 32 | 33 | %% gen_server callbacks 34 | -export([ init/1 35 | , handle_call/3 36 | , handle_cast/2 37 | , handle_info/2 38 | , terminate/2 39 | , code_change/3 40 | ]). 41 | 42 | -record(state, { 43 | proto :: atom(), 44 | listen_on :: esockd:listen_on(), 45 | lsock :: inet:socket(), 46 | laddr :: inet:ip_address(), 47 | lport :: inet:port_number(), 48 | sockopts :: [gen_tcp:listen_option()] 49 | }). 50 | 51 | -type option() :: {tcp_options, [gen_tcp:option()]}. 52 | 53 | -define(DEFAULT_TCP_OPTIONS, 54 | [{nodelay, true}, 55 | {reuseaddr, true}, 56 | {send_timeout, 30000}, 57 | {send_timeout_close, true} 58 | ]). 59 | 60 | -spec start_link(atom(), esockd:listen_on(), [esockd:option()]) 61 | -> {ok, pid()} | ignore | {error, term()}. 62 | start_link(Proto, ListenOn, Opts) -> 63 | gen_server:start_link(?MODULE, {Proto, ListenOn, Opts}, []). 64 | 65 | -spec start_supervised(esockd:listener_ref()) 66 | -> {ok, pid()} | ignore | {error, term()}. 67 | start_supervised(ListenerRef = {Proto, ListenOn}) -> 68 | Opts = esockd_server:get_listener_prop(ListenerRef, options), 69 | case start_link(Proto, ListenOn, Opts) of 70 | {ok, Pid} -> 71 | _ = esockd_server:set_listener_prop(ListenerRef, listener, {?MODULE, Pid}), 72 | {ok, Pid}; 73 | Error -> 74 | Error 75 | end. 76 | 77 | -spec get_port(pid()) -> inet:port_number(). 78 | get_port(Listener) -> 79 | gen_server:call(Listener, get_port). 80 | 81 | -spec get_lsock(pid()) -> inet:socket(). 82 | get_lsock(Listener) -> 83 | gen_server:call(Listener, get_lsock). 84 | 85 | -spec get_state(pid()) -> proplists:proplist(). 86 | get_state(Listener) -> 87 | gen_server:call(Listener, get_state). 88 | 89 | -spec set_options(pid(), [option()]) -> ok. 90 | set_options(Listener, Opts) -> 91 | gen_server:call(Listener, {set_options, Opts}). 92 | 93 | %%-------------------------------------------------------------------- 94 | %% gen_server callbacks 95 | %%-------------------------------------------------------------------- 96 | 97 | init({Proto, ListenOn, Opts}) -> 98 | Port = port(ListenOn), 99 | process_flag(trap_exit, true), 100 | esockd_server:ensure_stats({Proto, ListenOn}), 101 | SockOpts = merge_addr(ListenOn, merge_defaults(sockopts(Opts))), 102 | case esockd_transport:listen(Port, SockOpts) of 103 | {ok, LSock} -> 104 | {ok, {LAddr, LPort}} = inet:sockname(LSock), 105 | %%error_logger:info_msg("~s listen on ~s:~p with ~p acceptors.~n", 106 | %% [Proto, inet:ntoa(LAddr), LPort, AcceptorNum]), 107 | {ok, #state{proto = Proto, listen_on = ListenOn, lsock = LSock, 108 | laddr = LAddr, lport = LPort, sockopts = SockOpts}}; 109 | {error, Reason} -> 110 | error_logger:error_msg("~s failed to listen on ~p - ~p (~s)", 111 | [Proto, Port, Reason, inet:format_error(Reason)]), 112 | {stop, Reason} 113 | end. 114 | 115 | sockopts(Opts) -> 116 | %% Don't active the socket... 117 | SockOpts = proplists:get_value(tcp_options, Opts, []), 118 | [{active, false} | proplists:delete(active, SockOpts)]. 119 | 120 | merge_defaults(SockOpts) -> 121 | esockd:merge_opts(?DEFAULT_TCP_OPTIONS, SockOpts). 122 | 123 | port(Port) when is_integer(Port) -> Port; 124 | port({_Addr, Port}) -> Port. 125 | 126 | merge_addr(Port, SockOpts) when is_integer(Port) -> 127 | SockOpts; 128 | merge_addr({Addr, _Port}, SockOpts) -> 129 | lists:keystore(ip, 1, SockOpts, {ip, Addr}). 130 | 131 | handle_call(get_port, _From, State = #state{lport = LPort}) -> 132 | {reply, LPort, State}; 133 | 134 | handle_call(get_lsock, _From, State = #state{lsock = LSock}) -> 135 | {reply, LSock, State}; 136 | 137 | handle_call(get_state, _From, State = #state{lsock = LSock, lport = LPort}) -> 138 | Reply = [ {listen_sock, LSock} 139 | , {listen_port, LPort} 140 | ], 141 | {reply, Reply, State}; 142 | 143 | handle_call({set_options, Opts}, _From, State = #state{lsock = LSock, sockopts = SockOpts}) -> 144 | SockOptsIn = sockopts(Opts), 145 | SockOptsChanged = esockd:changed_opts(SockOptsIn, SockOpts), 146 | case inet:setopts(LSock, SockOptsChanged) of 147 | ok -> 148 | {reply, ok, State#state{sockopts = SockOptsIn}}; 149 | Error = {error, _} -> 150 | {reply, Error, State} 151 | end; 152 | 153 | handle_call(Req, _From, State) -> 154 | error_logger:error_msg("[~s] Unexpected call: ~p", [?MODULE, Req]), 155 | {noreply, State}. 156 | 157 | handle_cast(Msg, State) -> 158 | error_logger:error_msg("[~s] Unexpected cast: ~p", [?MODULE, Msg]), 159 | {noreply, State}. 160 | 161 | handle_info({'EXIT', LSock, _}, #state{lsock = LSock} = State) -> 162 | error_logger:error_msg("~s Lsocket ~p closed", [?MODULE, LSock]), 163 | {stop, lsock_closed, State}; 164 | handle_info(Info, State) -> 165 | error_logger:error_msg("[~s] Unexpected info: ~p", [?MODULE, Info]), 166 | {noreply, State}. 167 | 168 | terminate(_Reason, #state{proto = Proto, listen_on = ListenOn, 169 | lsock = LSock, laddr = Addr, lport = Port}) -> 170 | error_logger:info_msg("~s stopped on ~s~n", [Proto, esockd:format({Addr, Port})]), 171 | esockd_limiter:delete({listener, Proto, ListenOn}), 172 | esockd_server:del_stats({Proto, ListenOn}), 173 | esockd_transport:fast_close(LSock). 174 | 175 | code_change(_OldVsn, State, _Extra) -> 176 | {ok, State}. 177 | 178 | -------------------------------------------------------------------------------- /src/esockd_listener_sup.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2020 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(esockd_listener_sup). 18 | 19 | -behaviour(supervisor). 20 | 21 | -include("esockd.hrl"). 22 | 23 | -export([ start_link/2 24 | , listener/1 25 | , acceptor_sup/1 26 | , connection_sup/1 27 | ]). 28 | 29 | %% get/set 30 | -export([ get_options/2 31 | , get_acceptors/1 32 | , get_max_connections/1 33 | , get_max_conn_rate/3 34 | , get_current_connections/1 35 | , get_shutdown_count/1 36 | ]). 37 | 38 | -export([ set_options/3 39 | , set_max_connections/3 40 | , set_max_conn_rate/3 41 | ]). 42 | 43 | -export([ get_access_rules/1 44 | , allow/2 45 | , deny/2 46 | ]). 47 | 48 | -export([conn_rate_limiter/1, conn_limiter_opts/2, conn_limiter_opt/2]). 49 | 50 | %% supervisor callbacks 51 | -export([init/1]). 52 | 53 | %% callbacks 54 | -export([ tune_socket/2 55 | , start_acceptors/1 56 | ]). 57 | 58 | %%-------------------------------------------------------------------- 59 | %% APIs 60 | %%-------------------------------------------------------------------- 61 | 62 | %% @doc Start listener supervisor 63 | -spec start_link(atom(), esockd:listen_on()) 64 | -> {ok, pid()} | {error, term()}. 65 | start_link(Proto, ListenOn) -> 66 | ListenerRef = {Proto, ListenOn}, 67 | supervisor:start_link(?MODULE, ListenerRef). 68 | 69 | %% @doc Get listener. 70 | -spec(listener(pid()) -> {module(), pid()}). 71 | listener(Sup) -> 72 | Child = get_sup_child(Sup, listener), 73 | {get_child_mod(Child), get_child_pid(Child)}. 74 | 75 | %% @doc Get connection supervisor. 76 | -spec(connection_sup(pid()) -> pid()). 77 | connection_sup(Sup) -> 78 | get_child_pid(get_sup_child(Sup, connection_sup)). 79 | 80 | %% @doc Get acceptor supervisor. 81 | -spec(acceptor_sup(pid()) -> pid()). 82 | acceptor_sup(Sup) -> 83 | get_child_pid(get_sup_child(Sup, acceptor_sup)). 84 | 85 | %% @doc Get child pid with id. 86 | get_sup_child(Sup, ChildId) -> 87 | lists:keyfind(ChildId, 1, supervisor:which_children(Sup)). 88 | 89 | get_child_pid({_, Pid, _, _}) -> Pid. 90 | get_child_mod({_, _, _, [Mod | _]}) -> Mod. 91 | 92 | %%-------------------------------------------------------------------- 93 | %% Get/Set APIs 94 | %%-------------------------------------------------------------------- 95 | 96 | get_options(ListenerRef, _Sup) -> 97 | esockd_server:get_listener_prop(ListenerRef, options). 98 | 99 | set_options(ListenerRef, Sup, Opts) -> 100 | case esockd_acceptor_sup:check_options(Opts) of 101 | ok -> 102 | do_set_options(ListenerRef, Sup, Opts); 103 | {error, Reason} -> 104 | {error, Reason} 105 | end. 106 | 107 | do_set_options(ListenerRef, Sup, Opts) -> 108 | OptsWas = esockd_server:set_listener_prop(ListenerRef, options, Opts), 109 | ConnSup = esockd_server:get_listener_prop(ListenerRef, connection_sup), 110 | {Listener, ListenerPid} = esockd_server:get_listener_prop(ListenerRef, listener), 111 | try 112 | ensure_ok(esockd_connection_sup:set_options(ConnSup, Opts)), 113 | ensure_ok(Listener:set_options(ListenerPid, Opts)), 114 | restart_acceptor_sup(ListenerRef, Sup) 115 | catch 116 | throw:{?MODULE, Error} -> 117 | %% Restore previous options 118 | _ = esockd_server:set_listener_prop(ListenerRef, options, OptsWas), 119 | ok = esockd_connection_sup:set_options(ConnSup, OptsWas), 120 | %% Listener has failed to set options, no need to restore 121 | Error 122 | end. 123 | 124 | restart_acceptor_sup(ListenerRef, Sup) -> 125 | _ = supervisor:terminate_child(Sup, acceptor_sup), 126 | {ok, _Child} = supervisor:restart_child(Sup, acceptor_sup), 127 | _ = start_acceptors(ListenerRef), 128 | ok. 129 | 130 | ensure_ok(ok) -> 131 | ok; 132 | ensure_ok({error, _} = Error) -> 133 | throw({?MODULE, Error}). 134 | 135 | get_acceptors(Sup) -> 136 | esockd_acceptor_sup:count_acceptors(acceptor_sup(Sup)). 137 | 138 | get_max_connections(Sup) -> 139 | esockd_connection_sup:get_max_connections(connection_sup(Sup)). 140 | 141 | set_max_connections(ListenerRef, Sup, MaxConns) -> 142 | set_options(ListenerRef, Sup, [{max_connections, MaxConns}]). 143 | 144 | get_max_conn_rate(_Sup, Proto, ListenOn) -> 145 | case esockd_limiter:lookup({listener, Proto, ListenOn}) of 146 | undefined -> 147 | {error, not_found}; 148 | #{capacity := Capacity, interval := Interval} -> 149 | {Capacity, Interval} 150 | end. 151 | 152 | set_max_conn_rate(ListenerRef, Sup, Opt) -> 153 | set_options(ListenerRef, Sup, [{limiter, Opt}]). 154 | 155 | get_current_connections(Sup) -> 156 | esockd_connection_sup:count_connections(connection_sup(Sup)). 157 | 158 | get_shutdown_count(Sup) -> 159 | esockd_connection_sup:get_shutdown_count(connection_sup(Sup)). 160 | 161 | get_access_rules(Sup) -> 162 | esockd_connection_sup:access_rules(connection_sup(Sup)). 163 | 164 | allow(Sup, CIDR) -> 165 | esockd_connection_sup:allow(connection_sup(Sup), CIDR). 166 | 167 | deny(Sup, CIDR) -> 168 | esockd_connection_sup:deny(connection_sup(Sup), CIDR). 169 | 170 | %%-------------------------------------------------------------------- 171 | %% Supervisor callbacks 172 | %%-------------------------------------------------------------------- 173 | 174 | init(ListenerRef) -> 175 | ConnSup = #{id => connection_sup, 176 | start => {esockd_connection_sup, start_supervised, [ListenerRef]}, 177 | restart => transient, 178 | shutdown => infinity, 179 | type => supervisor, 180 | modules => [esockd_connection_sup]}, 181 | ListenerMod = case esockd_server:get_listener_prop(ListenerRef, type) of 182 | dtls -> esockd_dtls_listener; 183 | _ -> esockd_listener 184 | end, 185 | Listener = #{id => listener, 186 | start => {ListenerMod, start_supervised, [ListenerRef]}, 187 | restart => transient, 188 | shutdown => 16#ffffffff, 189 | type => worker, 190 | modules => [ListenerMod]}, 191 | AcceptorSup = #{id => acceptor_sup, 192 | start => {esockd_acceptor_sup, start_supervised, [ListenerRef]}, 193 | restart => transient, 194 | shutdown => infinity, 195 | type => supervisor, 196 | modules => [esockd_acceptor_sup]}, 197 | Starter = #{id => starter, 198 | start => {?MODULE, start_acceptors, [ListenerRef]}, 199 | restart => transient, 200 | shutdown => infinity, 201 | type => worker, 202 | modules => []}, 203 | {ok, { {rest_for_one, 10, 3600} 204 | , [ConnSup, Listener, AcceptorSup, Starter] 205 | }}. 206 | 207 | -spec start_acceptors(esockd:listener_ref()) -> ignore. 208 | start_acceptors(ListenerRef) -> 209 | {LMod, LPid} = esockd_server:get_listener_prop(ListenerRef, listener), 210 | LState = LMod:get_state(LPid), 211 | LSock = proplists:get_value(listen_sock, LState), 212 | ok = esockd_acceptor_sup:start_acceptors(ListenerRef, LSock), 213 | ignore. 214 | 215 | %%-------------------------------------------------------------------- 216 | %% Sock tune/upgrade functions 217 | %%-------------------------------------------------------------------- 218 | 219 | tune_socket(Sock, Tunings) -> 220 | esockd_acceptor_sup:tune_socket(Sock, Tunings). 221 | 222 | conn_limiter_opts(Opts, DefName) -> 223 | Opt = proplists:get_value(limiter, Opts, undefined), 224 | conn_limiter_opt(Opt, DefName). 225 | 226 | conn_limiter_opt(#{name := _} = Opt, _DefName) -> 227 | Opt; 228 | conn_limiter_opt(Opt, DefName) when is_map(Opt) -> 229 | Opt#{name => DefName}; 230 | conn_limiter_opt(_Opt, _DefName) -> 231 | undefined. 232 | 233 | conn_rate_limiter(undefined) -> 234 | undefined; 235 | conn_rate_limiter(Opts) -> 236 | esockd_generic_limiter:create(Opts). 237 | -------------------------------------------------------------------------------- /src/esockd_peercert.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2020 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(esockd_peercert). 18 | 19 | -export([subject/1, common_name/1]). 20 | 21 | -export_type([peercert/0]). 22 | 23 | -type(peercert() :: binary() | proplists:proplist()). 24 | 25 | -spec(subject(nossl | undefined | peercert()) -> undefined | binary()). 26 | subject(nossl) -> undefined; 27 | subject(undefined) -> undefined; 28 | subject(Cert) when is_binary(Cert) -> 29 | esockd_ssl:peer_cert_subject(Cert); 30 | subject(PP2Info) when is_list(PP2Info) -> 31 | %%Notice: DN is not available in ppv2 additional info 32 | proplists:get_value(pp2_ssl_cn, PP2Info). 33 | 34 | -spec(common_name(nossl | undefined | peercert()) -> undefined | binary()). 35 | common_name(nossl) -> undefined; 36 | common_name(undefined) -> undefined; 37 | common_name(Cert) when is_binary(Cert) -> 38 | esockd_ssl:peer_cert_common_name(Cert); 39 | common_name(PP2Info) when is_list(PP2Info) -> 40 | proplists:get_value(pp2_ssl_cn, PP2Info). 41 | 42 | -------------------------------------------------------------------------------- /src/esockd_rate_limit.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2020 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | %% @doc Token-bucket based Rate Limit. 18 | %% 19 | %% [Token Bucket](https://en.wikipedia.org/wiki/Token_bucket). 20 | %% 21 | %% @end 22 | -module(esockd_rate_limit). 23 | 24 | -export([ new/1 25 | , new/2 26 | , info/1 27 | , check/2 28 | ]). 29 | 30 | -export_type([config/0, bucket/0]). 31 | 32 | -record(bucket, { 33 | rate :: float(), 34 | burst :: pos_integer(), 35 | tokens :: non_neg_integer(), 36 | time :: pos_integer() 37 | }). 38 | 39 | -opaque(bucket() :: #bucket{}). 40 | 41 | -opaque(config() :: {float()|pos_integer(), pos_integer()}). 42 | 43 | %% @doc Create a rate limit. The time unit of rate is second. 44 | -spec(new(config()) -> bucket()). 45 | new({Rate, Burst}) -> new(Rate, Burst). 46 | 47 | -spec(new(float()|pos_integer(), pos_integer()) -> bucket()). 48 | new(Rate, Burst) when is_integer(Burst), 0 < Rate andalso Rate =< Burst -> 49 | #bucket{rate = Rate, 50 | burst = Burst, 51 | tokens = Burst, 52 | time = erlang:system_time(milli_seconds) 53 | }. 54 | 55 | -spec(info(bucket()) -> map()). 56 | info(#bucket{rate = Rate, burst = Burst, tokens = Tokens, time = Lastime}) -> 57 | #{rate => Rate, 58 | burst => Burst, 59 | tokens => Tokens, 60 | time => Lastime 61 | }. 62 | 63 | %% The pause time P: 64 | %% if Tokens < Limit => P = 0; 65 | %% if Tokens = Limit => P = 1/r; 66 | %% if Tokens > Limit => P = (Tokens-Limit)/r 67 | -spec(check(pos_integer(), bucket()) -> {non_neg_integer(), bucket()}). 68 | check(Tokens, Bucket) -> 69 | check(Tokens, erlang:system_time(milli_seconds), Bucket). 70 | 71 | -spec(check(pos_integer(), integer(), bucket()) -> {non_neg_integer(), bucket()}). 72 | check(Tokens, Now, Bucket = #bucket{rate = Rate, 73 | burst = Burst, 74 | tokens = Remaining, 75 | time = Lastime}) -> 76 | Limit = min(Burst, Remaining + round((Rate * (Now - Lastime)) / 1000)), 77 | case Limit > Tokens of 78 | true -> %% Tokens available 79 | {0, Bucket#bucket{tokens = Limit - Tokens, time = Now}}; 80 | false -> %% Tokens not enough 81 | Pause = round(max(Tokens - Limit, 1) * 1000 / Rate), 82 | {Pause, Bucket#bucket{tokens = 0, time = Now}} 83 | end. 84 | 85 | -------------------------------------------------------------------------------- /src/esockd_server.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2020 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(esockd_server). 18 | 19 | -behaviour(gen_server). 20 | 21 | -export([start_link/0, stop/0]). 22 | 23 | %% stats API 24 | -export([ stats_fun/2 25 | , init_stats/2 26 | , get_stats/1 27 | , inc_stats/3 28 | , dec_stats/3 29 | , del_stats/1 30 | , ensure_stats/1 31 | ]). 32 | 33 | %% listener properties API 34 | -export([ get_listener_prop/2 35 | , list_listener_props/1 36 | , set_listener_prop/3 37 | , erase_listener_props/1 38 | ]). 39 | 40 | %% gen_server callbacks 41 | -export([ init/1 42 | , handle_call/3 43 | , handle_cast/2 44 | , handle_info/2 45 | , terminate/2 46 | , code_change/3 47 | ]). 48 | 49 | -include("esockd.hrl"). 50 | 51 | -record(state, { 52 | listener_props :: #{esockd:listener_ref() => #{_Name => _Value}} 53 | }). 54 | 55 | -define(SERVER, ?MODULE). 56 | -define(STATS_TAB, esockd_stats). 57 | 58 | %%-------------------------------------------------------------------- 59 | %% API 60 | %%-------------------------------------------------------------------- 61 | 62 | -spec(start_link() -> {ok, pid()} | ignore | {error, term()}). 63 | start_link() -> 64 | gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). 65 | 66 | -spec(stop() -> ok). 67 | stop() -> gen_server:stop(?SERVER). 68 | 69 | -spec(stats_fun({atom(), esockd:listen_on()}, atom()) -> fun()). 70 | stats_fun({Protocol, ListenOn}, Metric) -> 71 | init_stats({Protocol, ListenOn}, [Metric]), 72 | fun({inc, Num}) -> esockd_server:inc_stats({Protocol, ListenOn}, Metric, Num); 73 | ({dec, Num}) -> esockd_server:dec_stats({Protocol, ListenOn}, Metric, Num) 74 | end. 75 | 76 | -spec(init_stats({atom(), esockd:listen_on()}, [atom()]) -> ok). 77 | init_stats({Protocol, ListenOn}, Metrics) -> 78 | gen_server:call(?SERVER, {init, {Protocol, ListenOn}, Metrics}). 79 | 80 | -spec(get_stats({atom(), esockd:listen_on()}) -> [{atom(), non_neg_integer()}]). 81 | get_stats({Protocol, ListenOn}) -> 82 | [{Metric, Val} || [Metric, Val] 83 | <- ets:match(?STATS_TAB, {{{Protocol, ListenOn}, '$1'}, '$2'})]. 84 | 85 | -spec(inc_stats({atom(), esockd:listen_on()}, atom(), pos_integer()) -> any()). 86 | inc_stats({Protocol, ListenOn}, Metric, Num) when is_integer(Num) -> 87 | update_counter({{Protocol, ListenOn}, Metric}, Num). 88 | 89 | -spec(dec_stats({atom(), esockd:listen_on()}, atom(), pos_integer()) -> any()). 90 | dec_stats({Protocol, ListenOn}, Metric, Num) when is_integer(Num) -> 91 | update_counter({{Protocol, ListenOn}, Metric}, -Num). 92 | 93 | update_counter(Key, Num) -> 94 | ets:update_counter(?STATS_TAB, Key, {2, Num}). 95 | 96 | -spec(del_stats({atom(), esockd:listen_on()}) -> ok). 97 | del_stats({Protocol, ListenOn}) -> 98 | gen_server:cast(?SERVER, {del, {Protocol, ListenOn}}). 99 | 100 | -spec ensure_stats({atom(), esockd:listen_on()}) -> ok. 101 | ensure_stats(StatsKey) -> 102 | ok = ?MODULE:init_stats(StatsKey, ?ACCEPT_RESULT_GROUPS), 103 | ok. 104 | 105 | -spec get_listener_prop(esockd:listener_ref(), _Name) -> _Value | undefined. 106 | get_listener_prop(ListenerRef = {_Proto, _ListenOn}, Name) -> 107 | gen_server:call(?SERVER, {get_listener_prop, ListenerRef, Name}, infinity). 108 | 109 | -spec list_listener_props(esockd:listener_ref()) -> [{_Name, _Value}]. 110 | list_listener_props(ListenerRef = {_Proto, _ListenOn}) -> 111 | gen_server:call(?SERVER, {list_listener_props, ListenerRef}, infinity). 112 | 113 | -spec set_listener_prop(esockd:listener_ref(), _Name, _Value) -> _ValueWas. 114 | set_listener_prop(ListenerRef = {_Proto, _ListenOn}, Name, Value) -> 115 | gen_server:call(?SERVER, {set_listener_prop, ListenerRef, Name, Value}, infinity). 116 | 117 | -spec erase_listener_props(esockd:listener_ref()) -> [{_Name, _ValueWas}]. 118 | erase_listener_props(ListenerRef = {_Proto, _ListenOn}) -> 119 | gen_server:call(?SERVER, {erase_listener_props, ListenerRef}, infinity). 120 | 121 | %%-------------------------------------------------------------------- 122 | %% gen_server callbacks 123 | %%-------------------------------------------------------------------- 124 | 125 | init([]) -> 126 | _ = ets:new(?STATS_TAB, [public, set, named_table, 127 | {write_concurrency, true}]), 128 | {ok, #state{listener_props = #{}}}. 129 | 130 | handle_call({init, {Protocol, ListenOn}, Metrics}, _From, State) -> 131 | lists:foreach(fun(Metric) -> 132 | true = ets:insert(?STATS_TAB, {{{Protocol, ListenOn}, Metric}, 0}) 133 | end, Metrics), 134 | {reply, ok, State, hibernate}; 135 | 136 | handle_call({get_listener_prop, ListenerRef, Name}, _From, 137 | State = #state{listener_props = LProps}) -> 138 | {reply, lprops_get(ListenerRef, Name, LProps), State}; 139 | 140 | handle_call({set_listener_prop, ListenerRef, Name, NValue}, _From, 141 | State = #state{listener_props = LProps}) -> 142 | Value = lprops_get(ListenerRef, Name, LProps), 143 | NLProps = lprops_set(ListenerRef, Name, NValue, LProps), 144 | {reply, Value, State#state{listener_props = NLProps}}; 145 | 146 | handle_call({list_listener_props, ListenerRef}, _From, 147 | State = #state{listener_props = LProps}) -> 148 | {reply, lprops_list(ListenerRef, LProps), State}; 149 | 150 | handle_call({erase_listener_props, ListenerRef}, _From, 151 | State = #state{listener_props = LProps}) -> 152 | Props = lprops_list(ListenerRef, LProps), 153 | {reply, Props, State#state{listener_props = lprops_erase(ListenerRef, LProps)}}; 154 | 155 | handle_call(Req, _From, State) -> 156 | error_logger:error_msg("[~s] Unexpected call: ~p", [?MODULE, Req]), 157 | {reply, ignore, State}. 158 | 159 | handle_cast({del, {Protocol, ListenOn}}, State) -> 160 | ets:match_delete(?STATS_TAB, {{{Protocol, ListenOn}, '_'}, '_'}), 161 | {noreply, State, hibernate}; 162 | 163 | handle_cast(Msg, State) -> 164 | error_logger:error_msg("[~s] Unexpected cast: ~p", [?MODULE, Msg]), 165 | {noreply, State}. 166 | 167 | handle_info(Info, State) -> 168 | error_logger:error_msg("[~s] Unexpected info: ~p", [?MODULE, Info]), 169 | {noreply, State}. 170 | 171 | terminate(_Reason, _State) -> 172 | ok. 173 | 174 | code_change(_OldVsn, State, _Extra) -> 175 | {ok, State}. 176 | 177 | %% 178 | 179 | lprops_get(ListenerRef, Name, LProps) -> 180 | case LProps of 181 | #{ListenerRef := Props} -> 182 | maps:get(Name, Props, undefined); 183 | #{} -> 184 | undefined 185 | end. 186 | 187 | lprops_set(ListenerRef, Name, Value, LProps) -> 188 | Props = maps:get(ListenerRef, LProps, #{}), 189 | LProps#{ListenerRef => Props#{Name => Value}}. 190 | 191 | lprops_list(ListenerRef, LProps) -> 192 | Props = maps:get(ListenerRef, LProps, #{}), 193 | maps:to_list(Props). 194 | 195 | lprops_erase(ListenerRef, LProps) -> 196 | maps:remove(ListenerRef, LProps). 197 | -------------------------------------------------------------------------------- /src/esockd_sup.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2020 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(esockd_sup). 18 | 19 | -behaviour(supervisor). 20 | 21 | -export([start_link/0]). 22 | 23 | -export([child_id/2]). 24 | 25 | -export([ start_child/1 26 | , stop_listener/2 27 | , restart_listener/2 28 | ]). 29 | 30 | -export([ listeners/0 31 | , listener/1 32 | , listener_and_module/1 33 | ]). 34 | 35 | -export([ child_spec/3 36 | , udp_child_spec/3 37 | , dtls_child_spec/3 38 | ]). 39 | 40 | %% supervisor callback 41 | -export([init/1]). 42 | 43 | %%-------------------------------------------------------------------- 44 | %% API 45 | %%-------------------------------------------------------------------- 46 | 47 | -spec(start_link() -> {ok, pid()} | ignore | {error, term()}). 48 | start_link() -> 49 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 50 | 51 | -spec child_spec(atom(), esockd:listen_on(), [esockd:option()]) 52 | -> supervisor:child_spec(). 53 | child_spec(Proto, ListenOn, Opts) when is_atom(Proto) -> 54 | ListenerRef = {Proto, ListenOn}, 55 | _ = esockd_server:set_listener_prop(ListenerRef, type, tcp), 56 | _ = esockd_server:set_listener_prop(ListenerRef, options, Opts), 57 | #{id => child_id(Proto, ListenOn), 58 | start => {esockd_listener_sup, start_link, [Proto, ListenOn]}, 59 | restart => transient, 60 | shutdown => infinity, 61 | type => supervisor, 62 | modules => [esockd_listener_sup]}. 63 | 64 | -spec udp_child_spec(atom(), esockd:listen_on(), [esockd:option()]) 65 | -> supervisor:child_spec(). 66 | udp_child_spec(Proto, Port, Opts) when is_atom(Proto) -> 67 | #{id => child_id(Proto, Port), 68 | start => {esockd_udp, server, [Proto, Port, Opts]}, 69 | restart => transient, 70 | shutdown => 5000, 71 | type => worker, 72 | modules => [esockd_udp]}. 73 | 74 | -spec dtls_child_spec(atom(), esockd:listen_on(), [esockd:option()]) 75 | -> supervisor:child_spec(). 76 | dtls_child_spec(Proto, Port, Opts) when is_atom(Proto) -> 77 | ListenerRef = {Proto, Port}, 78 | _ = esockd_server:set_listener_prop(ListenerRef, type, dtls), 79 | _ = esockd_server:set_listener_prop(ListenerRef, options, Opts), 80 | #{id => child_id(Proto, Port), 81 | start => {esockd_listener_sup, start_link, [Proto, Port]}, 82 | restart => transient, 83 | shutdown => infinity, 84 | type => supervisor, 85 | modules => [esockd_listener_sup]}. 86 | 87 | -spec(start_child(supervisor:child_spec()) -> {ok, pid()} | {error, term()}). 88 | start_child(ChildSpec) -> 89 | supervisor:start_child(?MODULE, ChildSpec). 90 | 91 | -spec(stop_listener(atom(), esockd:listen_on()) -> ok | {error, term()}). 92 | stop_listener(Proto, ListenOn) -> 93 | ListenerRef = {Proto, ListenOn}, 94 | case match_listeners(Proto, ListenOn) of 95 | [] -> {error, not_found}; 96 | Listeners -> 97 | Results = [terminate_and_delete(ChildId) || ChildId <- Listeners], 98 | case ok_or_error(Results) of 99 | ok -> 100 | _ = esockd_server:erase_listener_props(ListenerRef), 101 | ok; 102 | Error -> 103 | Error 104 | end 105 | end. 106 | 107 | terminate_and_delete(ChildId) -> 108 | case supervisor:terminate_child(?MODULE, ChildId) of 109 | ok -> supervisor:delete_child(?MODULE, ChildId); 110 | Error -> Error 111 | end. 112 | 113 | -spec(listeners() -> [{term(), pid()}]). 114 | listeners() -> 115 | [{Id, Pid} || {{listener_sup, Id}, Pid, _Type, _} <- supervisor:which_children(?MODULE)]. 116 | 117 | -spec(listener({atom(), esockd:listen_on()}) -> pid()). 118 | listener({Proto, ListenOn}) -> 119 | ChildId = child_id(Proto, ListenOn), 120 | case [Pid || {Id, Pid, _Type, _} <- supervisor:which_children(?MODULE), Id =:= ChildId] of 121 | [] -> error(not_found); 122 | L -> hd(L) 123 | end. 124 | 125 | -spec(listener_and_module({atom(), esockd:listen_on()}) 126 | -> undefined 127 | | {ListenerSup :: pid(), Mod :: esockd_listener_sup | esockd_udp}). 128 | listener_and_module({Proto, ListenOn}) -> 129 | ChildId = child_id(Proto, ListenOn), 130 | case [{Pid, Mod} || {Id, Pid, _Type, [Mod|_]} <- supervisor:which_children(?MODULE), Id =:= ChildId] of 131 | [] -> undefined; 132 | L -> hd(L) 133 | end. 134 | 135 | -spec(restart_listener(atom(), esockd:listen_on()) -> ok | {error, term()}). 136 | restart_listener(Proto, ListenOn) -> 137 | case match_listeners(Proto, ListenOn) of 138 | [] -> {error, not_found}; 139 | Listeners -> 140 | ok_or_error([terminate_and_restart(ChildId) || ChildId <- Listeners]) 141 | end. 142 | 143 | terminate_and_restart(ChildId) -> 144 | case supervisor:terminate_child(?MODULE, ChildId) of 145 | ok -> supervisor:restart_child(?MODULE, ChildId); 146 | Error -> Error 147 | end. 148 | 149 | match_listeners(Proto, ListenOn) -> 150 | [ChildId || {ChildId, _Pid, _Type, _} <- supervisor:which_children(?MODULE), 151 | match_listener(Proto, ListenOn, ChildId)]. 152 | 153 | match_listener(Proto, ListenOn, {listener_sup, {Proto, ListenOn}}) -> 154 | true; 155 | match_listener(Proto, Port, {listener_sup, {Proto, {_IP, Port}}}) -> 156 | true; 157 | match_listener(_Proto, _ListenOn, _ChildId) -> 158 | false. 159 | 160 | child_id(Proto, ListenOn) -> 161 | {listener_sup, {Proto, ListenOn}}. 162 | 163 | ok_or_error([]) -> ok; 164 | ok_or_error([ok|Results]) -> 165 | ok_or_error(Results); 166 | ok_or_error([{ok, _Pid}|Results]) -> 167 | ok_or_error(Results); 168 | ok_or_error([{error, Reason}|_]) -> 169 | {error, Reason}. 170 | 171 | %%-------------------------------------------------------------------- 172 | %% Supervisor callbacks 173 | %%-------------------------------------------------------------------- 174 | 175 | init([]) -> 176 | SupFlags = #{strategy => one_for_one, 177 | intensity => 10, 178 | period => 100 179 | }, 180 | Limiter = #{id => esockd_limiter, 181 | start => {esockd_limiter, start_link, []}, 182 | restart => permanent, 183 | shutdown => 5000, 184 | type => worker, 185 | modules => [esockd_limiter] 186 | }, 187 | Server = #{id => esockd_server, 188 | start => {esockd_server, start_link, []}, 189 | restart => permanent, 190 | shutdown => 5000, 191 | type => worker, 192 | modules => [esockd_server] 193 | }, 194 | ProxyDB = #{id => esockd_udp_proxy_db, 195 | start => {esockd_udp_proxy_db, start_link, []}, 196 | restart => permanent, 197 | shutdown => 5000, 198 | type => worker, 199 | modules => [esockd_udp_proxy_db] 200 | }, 201 | {ok, {SupFlags, [Limiter, Server, ProxyDB]}}. 202 | -------------------------------------------------------------------------------- /src/udp_proxy/doc/proxy.plantuml: -------------------------------------------------------------------------------- 1 | @startuml 2 | skinparam dpi 300 3 | left to right direction 4 | 5 | frame client { 6 | card clinet1 7 | card client2 8 | card client3 9 | } 10 | 11 | frame proxy { 12 | label "proxy converts the udp connection\nto long connection in the application" 13 | agent proxy1 14 | agent proxy2 15 | agent proxy3 16 | } 17 | 18 | frame connection { 19 | card connection1 20 | card connection2 21 | card connection3 22 | } 23 | 24 | clinet1 .[#red].> proxy1 25 | client2 .[#blue].> proxy1 26 | client3 ..> proxy2 27 | client3 ..> proxy3 28 | 29 | proxy1 -[#red]-> connection1 : client1 30 | proxy1 -[#blue]-> connection2 : client2 31 | proxy2 --> connection3 : client3 32 | proxy3 --> connection3 : client3 33 | 34 | note bottom of proxy1 35 | different clients use the same UDP channel,e.g NAT, the proxy will first disconnect the existing connection and then connect to the new one 36 | endnote 37 | 38 | 39 | note right of proxy 40 | proxy2 and proxy3 are multiple channels used by a client,e.g LB 41 | endnote 42 | 43 | @enduml 44 | -------------------------------------------------------------------------------- /src/udp_proxy/doc/proxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emqx/esockd/f2e2302db9380097315a42142957bbded0f9ef98/src/udp_proxy/doc/proxy.png -------------------------------------------------------------------------------- /src/udp_proxy/esockd_udp_proxy_connection.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2024 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(esockd_udp_proxy_connection). 18 | 19 | -include("include/esockd_proxy.hrl"). 20 | 21 | -export([ 22 | initialize/2, 23 | find_or_create/5, 24 | get_connection_id/5, 25 | dispatch/4, 26 | close/3 27 | ]). 28 | 29 | -export_type([connection_id/0, connection_module/0]). 30 | 31 | %%-------------------------------------------------------------------- 32 | %%- Callbacks 33 | %%-------------------------------------------------------------------- 34 | -callback initialize(connection_options()) -> connection_state(). 35 | 36 | %% Create new connection 37 | -callback find_or_create(connection_id(), proxy_transport(), peer(), connection_options()) -> 38 | gen_server:start_ret(). 39 | 40 | %% Find routing information 41 | -callback get_connection_id( 42 | proxy_transport(), peer(), connection_state(), socket_packet() 43 | ) -> 44 | get_connection_id_result(). 45 | 46 | %% Dispacth message 47 | -callback dispatch(pid(), connection_state(), proxy_packet()) -> ok. 48 | 49 | %% Close Connection 50 | -callback close(pid(), connection_state()) -> ok. 51 | 52 | %%-------------------------------------------------------------------- 53 | %%- API 54 | %%-------------------------------------------------------------------- 55 | initialize(Mod, Opts) -> 56 | Mod:initialize(Opts). 57 | 58 | find_or_create(Mod, CId, Transport, Peer, Opts) -> 59 | Mod:find_or_create(CId, Transport, Peer, Opts). 60 | 61 | get_connection_id(Mod, Transport, Peer, State, Data) -> 62 | Mod:get_connection_id(Transport, Peer, State, Data). 63 | 64 | dispatch(Mod, Pid, State, Packet) -> 65 | Mod:dispatch(Pid, State, Packet). 66 | 67 | close(Mod, Pid, State) -> 68 | Mod:close(Pid, State). 69 | -------------------------------------------------------------------------------- /src/udp_proxy/esockd_udp_proxy_db.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2020 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(esockd_udp_proxy_db). 18 | 19 | -behaviour(gen_server). 20 | 21 | -include("include/esockd_proxy.hrl"). 22 | 23 | %% API 24 | -export([ 25 | start_link/0, 26 | attach/2, 27 | detach/2 28 | ]). 29 | 30 | %% gen_server callbacks 31 | -export([ 32 | init/1, 33 | handle_call/3, 34 | handle_cast/2, 35 | handle_info/2, 36 | terminate/2 37 | ]). 38 | 39 | -define(ID(Mod, CId), {Mod, CId}). 40 | 41 | -record(connection, { 42 | id :: ?ID(connection_module(), connection_id()), 43 | %% Reference Counter 44 | proxy :: pid() 45 | }). 46 | 47 | -define(TAB, esockd_udp_proxy_db). 48 | 49 | %%-------------------------------------------------------------------- 50 | %%- API 51 | %%-------------------------------------------------------------------- 52 | -spec start_link() -> {ok, pid()}. 53 | start_link() -> 54 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 55 | 56 | -spec attach(connection_module(), connection_id()) -> true. 57 | attach(Mod, CId) -> 58 | ID = ?ID(Mod, CId), 59 | case ets:lookup(?TAB, ID) of 60 | [] -> 61 | ok; 62 | [#connection{proxy = ProxyId}] -> 63 | esockd_udp_proxy:takeover(ProxyId, CId) 64 | end, 65 | ets:insert(?TAB, #connection{id = ID, proxy = self()}). 66 | 67 | -spec detach(connection_module(), connection_id()) -> boolean(). 68 | detach(Mod, CId) -> 69 | ProxyId = self(), 70 | ID = ?ID(Mod, CId), 71 | case ets:lookup(?TAB, ID) of 72 | [#connection{proxy = ProxyId}] -> 73 | ets:delete(?TAB, ID), 74 | true; 75 | _ -> 76 | false 77 | end. 78 | 79 | %%-------------------------------------------------------------------- 80 | %%- gen_server callbacks 81 | %%-------------------------------------------------------------------- 82 | init([]) -> 83 | ?TAB = ets:new(?TAB, [ 84 | set, 85 | public, 86 | named_table, 87 | {keypos, #connection.id}, 88 | {write_concurrency, true}, 89 | {read_concurrency, true} 90 | ]), 91 | {ok, #{}}. 92 | 93 | handle_call(Req, _From, State) -> 94 | error_logger:error_msg("Unexpected call: ~p", [Req]), 95 | {reply, ignore, State}. 96 | 97 | handle_cast(Msg, State) -> 98 | error_logger:error_msg("Unexpected cast: ~p~n", [Msg]), 99 | {noreply, State}. 100 | 101 | handle_info(Info, State) -> 102 | error_logger:error_msg("Unexpected info: ~p~n", [Info]), 103 | {noreply, State}. 104 | 105 | terminate(_Reason, _State) -> 106 | ets:delete(?TAB), 107 | ok. 108 | -------------------------------------------------------------------------------- /test/async_echo_server.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2020 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(async_echo_server). 18 | 19 | -export([start_link/2]). 20 | 21 | %% Callbacks 22 | -export([init/2, loop/2]). 23 | 24 | start_link(Transport, RawSock) -> 25 | {ok, spawn_link(?MODULE, init, [Transport, RawSock])}. 26 | 27 | init(Transport, RawSock) -> 28 | case Transport:wait(RawSock) of 29 | {ok, Sock} -> 30 | loop(Transport, Sock); 31 | {error, Reason} -> 32 | {error, Reason} 33 | end. 34 | 35 | loop(Transport, Sock) -> 36 | {ok, Ref} = Transport:async_recv(Sock, 0), 37 | receive 38 | {inet_async, _Sock, Ref, {ok, Data}} -> 39 | %%{ok, Peername} = Transport:peername(Sock), 40 | %%io:format("RECV from ~s: ~s~n", [esockd:format(Peername), Data]), 41 | ok = Transport:async_send(Sock, Data), 42 | loop(Transport, Sock); 43 | {inet_async, _Sock, Ref, {error, Reason}} -> 44 | exit(Reason); 45 | {inet_reply, _Sock ,ok} -> 46 | loop(Transport, Sock); 47 | {inet_reply, _Sock, {error, Reason}} -> 48 | exit(Reason) 49 | end. 50 | 51 | -------------------------------------------------------------------------------- /test/certs/ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDhjCCAm6gAwIBAgIUfwwGXeWa3QV8RhQVZ2yyxISu0CEwDQYJKoZIhvcNAQEL 3 | BQAwTDE7MDkGA1UEAwwyVExTR2VuU2VsZlNpZ25lZHRSb290Q0EgMjAyNC0wNC0x 4 | NVQxOTozODowNS45NzIxMjYxDTALBgNVBAcMBCQkJCQwHhcNMjQwNDE1MTEzODA2 5 | WhcNMzQwNDEzMTEzODA2WjBMMTswOQYDVQQDDDJUTFNHZW5TZWxmU2lnbmVkdFJv 6 | b3RDQSAyMDI0LTA0LTE1VDE5OjM4OjA1Ljk3MjEyNjENMAsGA1UEBwwEJCQkJDCC 7 | ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJpu2yIeg/fvxodZbbhbeCdy 8 | EK3jla7RzPbm40cbGuvZGo0xANmfm12kIb5LeyeAG51/2mLv/fvS+V9HQ7PYKqJJ 9 | aaMe8iBu9xNu2gKElkqQah1Iehv8dj2iaS48CE37nEN+hnwoeX5NWCNUIpnoqdWQ 10 | uS9XTaSutxOEQGbrKSFms24l+jmaaBmMwJ62tKQQniNot6YIT/lV1Yajvdx8U3tK 11 | wv5fGBQpEokePEHlvs4yMgB+d2B+NjPAoFPJODE3PsNQYnhjV+QZLxJf83BkxLSm 12 | 5FYTOSduWLKXpa61DXNSYTurqnB64rvj0EZi1JCPkFrlSnDVhLkTb0u6KL+PDmUC 13 | AwEAAaNgMF4wDwYDVR0TAQH/BAUwAwEB/zALBgNVHQ8EBAMCAQYwHQYDVR0OBBYE 14 | FOmdAzWug/SSwKYS8kHvxF/ccwSRMB8GA1UdIwQYMBaAFOmdAzWug/SSwKYS8kHv 15 | xF/ccwSRMA0GCSqGSIb3DQEBCwUAA4IBAQBve2+4i4+P+zm9O0IMiBJci+T5eQvd 16 | RGRDn12RhK7fjmSUvnZk5hfSO79hJyWKkzz2yNy2eQT6dV7hKuNgLPEigAT0F4F8 17 | GrPZTzDIyHTV9jDXVWFhvsmcSJdS6FwqlJi6AjSMOtuHvvmp/VTHP34RO0nxXDty 18 | p006AI8T9jysGsr9pahiHm5O7QIfqME4JccDBIajr14j1/WlZqdvwY0gHFkePiO5 19 | WNTqYBJ3Jo3mS2XsmqlsvKZOUMWwYsh/ffISmyys0kJInc3dIgUB+5H8TcbXi2xY 20 | VK29S1IgyGdiet4XUhl7i3dNKzinROB/sqYooA3xna/pOVTAbNIIch1h 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /test/certs/ca.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCabtsiHoP378aH 3 | WW24W3gnchCt45Wu0cz25uNHGxrr2RqNMQDZn5tdpCG+S3sngBudf9pi7/370vlf 4 | R0Oz2CqiSWmjHvIgbvcTbtoChJZKkGodSHob/HY9omkuPAhN+5xDfoZ8KHl+TVgj 5 | VCKZ6KnVkLkvV02krrcThEBm6ykhZrNuJfo5mmgZjMCetrSkEJ4jaLemCE/5VdWG 6 | o73cfFN7SsL+XxgUKRKJHjxB5b7OMjIAfndgfjYzwKBTyTgxNz7DUGJ4Y1fkGS8S 7 | X/NwZMS0puRWEzknbliyl6WutQ1zUmE7q6pweuK749BGYtSQj5Ba5Upw1YS5E29L 8 | uii/jw5lAgMBAAECggEAPQe9QvUTSyrvZrLhSP+g8tnBnK65iy5vVU22mPWtxHH2 9 | a8DnaJmKUU3ZZfY3c8tNXodV206jrV7ZXR1xNIQjiSIBBM1oe4fOzde7gU47hLkg 10 | rnTjiObTJZVnArYHxCXAwe2hITTvmonfYIrR58gKQhOhE4Ppc8D2KdjUiLe7d+6c 11 | BbrMoaFk9bwoKPj1lCj8MoGTbrK0Fhb9/rkux6BWS0A6BBaNQHPVUdE7F2OzdA63 12 | uc9SlylikmlVblfGdDs5oyzEE7ce1Nc/YMXk+vOFuPFK1RKEl9nQ7qgg/azFr5wf 13 | yzvo/TRUgLduYfWz0aXAZicrdHNq7PEniEdZjjN26wKBgQDZeQby20ICT8hkqavl 14 | WTnYL67+wCC9sEIgmQsZZJX3LEgym3MfhW1zsCY6PbI9iXvcQ/bAZB01ex+3Ezig 15 | XxyRjltbYzLZJeqiOfxVbnDceW+04ThAZSPRkSwpzN32uiHl1ZBPxNcqUW2EufH/ 16 | 1Ec/psomB8UI5WpAY/yIgAYH3wKBgQC1ys7aaQPlM4OCSttopaLVFITi0eMszgVW 17 | yEp6FgskHXv2CfyO98NllLyN6dyGLapId2dD8mahMNzkGIQroF6Itbi+0sZDMKRX 18 | BlQ1vnUWzogs1BzHuA/MEHj2+BjgINf2rZGagKY4FPF5GMhyuoR2DC9UeM+6P3gD 19 | g3hRh1SCOwKBgQDCFPjWlEb8uScmbyYxsMem4CdUwzJ1F3BslykIqHQN+z8Tqxe6 20 | 8eEUdggnwhfoQ2J+2UjFupM2v4M2Z7kJpthgLqPpk0Y19VjJHubLd5zRpq7Cxpzl 21 | G140EVNQmr0dDFBkRYxdJFbCNZl/8lK+9jv/2mJePmDh55xDivg8+cRoEQKBgH6g 22 | Cl9FwKnYrOSHYbbNkNMc3Mbk7Xka88sT7hSF4DLCusKRCDNJTvgllwS+fZWUn0Sk 23 | rvA8GtJyJ5pVnVKJ/O76rPl19RV9/yqCxQs+Y26EY4PeJ0HR6mb4nllgTGtpVSSE 24 | l6Pnsl/j7qKr6YtIcdWL/Te4gvC3HtBLtfYL7ohbAoGATlMuaabMt6uyNKxDviv7 25 | WpHtsA6xRksb21E+W8Dpv5Bc2sgv47OcjL1/igzjaNK8YZVEbO+EbpSkwpUp21Y5 26 | vbhfMIpJA0Z9EOkgEIFuGMX/rjscqeuOmMwW/ninpAtxgb8MgYemoikjORWQtHnn 27 | H0kEw1bktZJ7CcsyeC6U/n4= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /test/certs/change.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDzjCCAragAwIBAgIBAzANBgkqhkiG9w0BAQsFADBMMTswOQYDVQQDDDJUTFNH 3 | ZW5TZWxmU2lnbmVkdFJvb3RDQSAyMDI0LTA0LTE1VDE5OjM4OjA1Ljk3MjEyNjEN 4 | MAsGA1UEBwwEJCQkJDAeFw0yNDA0MTUxMTM4NDJaFw0zNDA0MTMxMTM4NDJaMCMx 5 | EDAOBgNVBAMMB0NoYW5nZWQxDzANBgNVBAoMBnNlcnZlcjCCASIwDQYJKoZIhvcN 6 | AQEBBQADggEPADCCAQoCggEBAKlBf7oc9NmFuRIFMvsagdNiCfDYwNUUbkVddKqw 7 | 9Tg93JRcUSnrmEQkypnIL1Jl+qYs8tvqBuLcv4uJWQZvZ/E44qR4oVmzkpChmQuz 8 | zB6zQTzk2Mwn5rcVa8SDzJS5N+CaPJvaoM/WBAY+16QvS36tjZZIbtNGLD/q5DCP 9 | 7ebLKvBgKje5PjbwTBXRsRcXlNHsfOxbwCL0mYgi2JyIhczcfF5zFTaVdgFOu4jc 10 | flCOPsZ8XDsa1n4OQN0PvLwhb8am2e71TOF0X2JbXHvMaStr5vRj7WVCEoruhyEb 11 | 5goBra9iZ35UPHRWcvOVx1G9Dfn3ccLnvbNKqyrzoRbHN0cCAwEAAaOB4zCB4DAJ 12 | BgNVHRMEAjAAMAsGA1UdDwQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATA+BgNV 13 | HREENzA1ggdDaGFuZ2Vkgh9kZW5nemhvbmd3ZW5kZU1hY0Jvb2stUHJvLmxvY2Fs 14 | gglsb2NhbGhvc3QwHQYDVR0OBBYEFJ8LKzHWMMvXYCgVfpEkAP/jWvvxMB8GA1Ud 15 | IwQYMBaAFOmdAzWug/SSwKYS8kHvxF/ccwSRMDEGA1UdHwQqMCgwJqAkoCKGIGh0 16 | dHA6Ly9jcmwtc2VydmVyOjgwMDAvYmFzaWMuY3JsMA0GCSqGSIb3DQEBCwUAA4IB 17 | AQCDHpNVcbMxMnvd+Nu5SO2SkBxBV2VRron9qL0hmHSNqKVTs8eCvjHcwOqyS0Hc 18 | vOfx0WjsSNV1Kz3aD1HY6J0RzsRM11kje8jO7DTyPb8x0JtMEj7jp2hqmgSBz6XN 19 | FiUzIQuqV7rbC8Kn+TgRy8ZOsx0EDafnDAtBI5BfPVXr78r7Tdi2/DZ/NYIjMS1m 20 | ASC707N7lJ4qm8ysWufqPbVfa787PbYZbOy7RRJA3WL/csPEct3YmB73BU4rHY1r 21 | Cn5x4zUb4SmUUC+3035FOLfDxqVzyaaLBlmjUQ4ihPf7WhJv5ZUovk5lJOexcCXd 22 | 3/sHSn58tmKyXIAk3hqfUVFO 23 | -----END CERTIFICATE----- 24 | -------------------------------------------------------------------------------- /test/certs/change.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCpQX+6HPTZhbkS 3 | BTL7GoHTYgnw2MDVFG5FXXSqsPU4PdyUXFEp65hEJMqZyC9SZfqmLPLb6gbi3L+L 4 | iVkGb2fxOOKkeKFZs5KQoZkLs8wes0E85NjMJ+a3FWvEg8yUuTfgmjyb2qDP1gQG 5 | PtekL0t+rY2WSG7TRiw/6uQwj+3myyrwYCo3uT428EwV0bEXF5TR7HzsW8Ai9JmI 6 | IticiIXM3HxecxU2lXYBTruI3H5Qjj7GfFw7GtZ+DkDdD7y8IW/Gptnu9UzhdF9i 7 | W1x7zGkra+b0Y+1lQhKK7ochG+YKAa2vYmd+VDx0VnLzlcdRvQ3593HC572zSqsq 8 | 86EWxzdHAgMBAAECggEAGJPm+khY0yV0mcX2KznO1GU40FKJXefDmNWuAANbozkd 9 | yNAcW8asVmjeoAojulwN/lzUlKlH1WBSKERfkGxTtFbwV7XJ/alH2odnc/2TNyS3 10 | OnfOINyXx3TthfHQSWBursd25E2RFd0cDylyTZMt6/fucX4/KaAtA3MWg77CwG/6 11 | FW320Tgz5h8SCRJTjwQzKYTW91Q/WfgKuue39a3tYNScSRiG6GmBlYrjzItMHTGm 12 | AaUgTMPN1+gOnAACRD2MW37pWplqkt/DmEh6pdPZ16QljZGroVJNJHTAa/sK8l4u 13 | g3V1kEGlRcTaF667BrAIn8M9dPAZa1THXMh7CtQooQKBgQDnpX1sX9OIahwPs1Y/ 14 | oiMdDFBECtHxvAVYLDgs1MnZmZ8iCQSvpGfRd54FRZFCndAVEdiMkQvvzfL+uwIN 15 | JH2e4ZhrurjZrWTYjVBHPiCqfjy2cs87LFMZVjklCLVV+gBnT+oLo2HTI01vZbpo 16 | qHbo3mElvqmUIDVlzkKM/KBfnwKBgQC7DNWt7CcQv/ZuRZBRlr+BbXw3QzBs8iZ9 17 | TZNmyd0vU7xBfGD26g8aJy/gugBzcKoZVV1n7BYcqE7f9rpAmHOvOThDqT/ForgI 18 | 4mJAF/YbFVL9BuIVJwmp9J4CnakbVOSTWlwfOtu8/ekK0ht/qA/X0fAlhs9ughOs 19 | lLO8xFNnWQKBgQDDEvQ6jcA/wsv61NWvJMVZMwNtKYd/gQ0nIaX06wVJADuol9ug 20 | Zl0fYxJp8hnP9cJD3BF23rckrVUS+z1RAAfiAq0vG8GLXp/sVaTjOgicS06pfqnR 21 | dRRN4/SfoWPEEZLBbeT1BBPfkH0ncI24kRYCKTO6a4O3QCB3qWkV/bC4lQKBgBrA 22 | aisWtDucCMWao1pX4gnDu9k3fcyHEWQoXSV9R2oGYs1G2aNWtFQ8OIe+cmQQP/Oh 23 | lttPE5aDp8w2QEsOwrTTzS04HovIiuOSZRTfmG4lLZpMircVvzcUaijbnZzwFkTQ 24 | zo4U3+p2sga/J/mo8qMePmdPkYkvh1rOZapgtcD5AoGAengjFD0tTCdWXR8Nevrm 25 | 2EXiI1hlXKmf65VIJC2CZiBwy1o8L8Lt6Cv9Y8L1BL0a3rMsaszO261/TO21vN4u 26 | gFcpFlGyxAIf2rhSAbB/uksmhXkoFjoaUz0kxcuXqrrNFa/akXYZ8mfO8wvQUKiA 27 | AdfT9bl2SIL4udrqGAGMJWo= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /test/certs/test.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDzDCCArSgAwIBAgIBATANBgkqhkiG9w0BAQsFADBMMTswOQYDVQQDDDJUTFNH 3 | ZW5TZWxmU2lnbmVkdFJvb3RDQSAyMDI0LTA0LTE1VDE5OjM4OjA1Ljk3MjEyNjEN 4 | MAsGA1UEBwwEJCQkJDAeFw0yNDA0MTUxMTM4MDZaFw0zNDA0MTMxMTM4MDZaMCIx 5 | DzANBgNVBAMMBlNlcnZlcjEPMA0GA1UECgwGc2VydmVyMIIBIjANBgkqhkiG9w0B 6 | AQEFAAOCAQ8AMIIBCgKCAQEAuEEEKlKMIhLPFimElB0aE5kvFZbPJydpE7OMWyHL 7 | k1ws5K7Edh+ZGEJE4TrlSBp6e0bcyiEclf7ORsqF6h89ucdqwF1rqD0bgr5HysA1 8 | 8xgpDIoF7/duLNLaEB6ZIclHhalOPEuFrdnCMDChaI604ZNV136kV9SByXbv+rw1 9 | UiRFbiqiKea/IVR0LeufAcj1i8W2tlQG6DVGiZg1ZoVg1pN+YRjIdIWwWnPQztVy 10 | p4mBn15UfvaoyzrDFgPstuTLPwGQCbGSgQQZHMZyCmLbH9ugubfHrf0Ul7u+mgci 11 | Wc06ymCZxyldRxOa+Wzq763J9K30CUPrglcqj2Z9o7n7HwIDAQABo4HiMIHfMAkG 12 | A1UdEwQCMAAwCwYDVR0PBAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMD0GA1Ud 13 | EQQ2MDSCBlNlcnZlcoIfZGVuZ3pob25nd2VuZGVNYWNCb29rLVByby5sb2NhbIIJ 14 | bG9jYWxob3N0MB0GA1UdDgQWBBR81drHAlyjb0lqg3FoHK3DtQfOiTAfBgNVHSME 15 | GDAWgBTpnQM1roP0ksCmEvJB78Rf3HMEkTAxBgNVHR8EKjAoMCagJKAihiBodHRw 16 | Oi8vY3JsLXNlcnZlcjo4MDAwL2Jhc2ljLmNybDANBgkqhkiG9w0BAQsFAAOCAQEA 17 | fUSKA5ukIcMaRWobYj3NeXIO8fFI+FFXg9f8yZ3hE3CvsncE8Z3Gvl8HuEzsQkid 18 | HaEYT6CiqUKKqjU9hpoIxH1M54hy20v1kRrJDefjHyKC0lWqz72fioqypf30vm8c 19 | LgZE667E4qSBkU5KzWl22enV/mRTll8nESN/HoaEkmNar+0v2MsYA/+FZEOdCWOV 20 | 3w2HNHDsXpc1/pRpz92Yn+l8ru7fwZClDzuM1JF8yGh375AJTMRpiY3nH90aksqa 21 | Yx34gMIfDoy/byac4ChssMQ1/42lJdCm6F6g1v2st7dyUzx3b4OkygLNvs8jZd3I 22 | iae4hZ0Ljcm2Y3yump/tMQ== 23 | -----END CERTIFICATE----- 24 | -------------------------------------------------------------------------------- /test/certs/test.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC4QQQqUowiEs8W 3 | KYSUHRoTmS8Vls8nJ2kTs4xbIcuTXCzkrsR2H5kYQkThOuVIGnp7RtzKIRyV/s5G 4 | yoXqHz25x2rAXWuoPRuCvkfKwDXzGCkMigXv924s0toQHpkhyUeFqU48S4Wt2cIw 5 | MKFojrThk1XXfqRX1IHJdu/6vDVSJEVuKqIp5r8hVHQt658ByPWLxba2VAboNUaJ 6 | mDVmhWDWk35hGMh0hbBac9DO1XKniYGfXlR+9qjLOsMWA+y25Ms/AZAJsZKBBBkc 7 | xnIKYtsf26C5t8et/RSXu76aByJZzTrKYJnHKV1HE5r5bOrvrcn0rfQJQ+uCVyqP 8 | Zn2jufsfAgMBAAECggEABcyMVfDzTWpgj3I4ElmYaktZlPJLXhBRZxf6WRzHHGZp 9 | od97HrvESWQZseC+J+knJUbVmtNG9kJv+bdnDW4+bZek74tqTltw2tx/jTrWc0X+ 10 | v5oRVenod7kdJtzeh6joKNxrVt5ToPd6MzUTebWbiMmv0nUatS709MM2d+uu8IEy 11 | NUvN19qOnI80+utghBrOmnLNM2mL2wJoSTesq56R2cbI7xDaGFB0/oVy09b0SUgN 12 | LRrhWmA6Swy+QBb+yIGkWNB78EYudAJ2Ob/3Ma3D3GjaCS5sIlmqk0vbN4nmZhor 13 | YjddfyJFyTLoTKGpAChes1hnQHjIZhDpoxnCChglYQKBgQDeAFGT6EzdIV6O4N4k 14 | c1VPu7p657G4yqAZ3xMPXSU4AcFhXa5QbP7IutWYdqraEOh3hXlsuTXq8Zt1jDrz 15 | erlH2gvFnqw9YtnSuZPhNZJIqDKeDZn/ejkSRk2dlvS41m5ZcFZLhPVTfnwZAVqW 16 | DUu15XRze2m/ZvuxbhAZOK9O/wKBgQDUeMt7yKXj/Sn3BAMq7DkaUFgDoyw7YGJ9 17 | sjYMsKZN+A1J+DFLiDmtZArPkQKtn3GG/gGSXC0dgahKV0Rv4vE5ofAoEH4MUd5c 18 | GUGAbjbReitTm32MiEIHLkwMre8TDPw9NZCInkUxhVfWkI7PGspnW003E8P0WIqp 19 | 94ZIR9dz4QKBgQCGETcy1YUlvfM24gIFjUkl/78cXjPr3yJUvWzrSFsjuZupS0Pr 20 | +gbb/kjj7DY9vxxEoey0hh1Tp1k04dzwEVs3UyzsyWhHIh1drwjpZsrS6hC3dotb 21 | x9oTeTWCSBm+TnERvk+/B6qII8iZ6UFlgN4KGWn8G7m/B1hUzJ0YPi1QzQKBgCFg 22 | ZF6qk1ywLUJvb+vzNbkre9f+bvlY17/1GcZ+jXFcEQJYqa261YymVYhruCb8EAJr 23 | rmWGc1PaRUyATvDgjpJGHQq+JH7c8Xg4Q3DgX14iueGlYGCOGwkzvaCURiUQ+iLH 24 | YlADxMzVrIm5JaXlCHyZ/BQ116dvrUCr8H00Q+UhAoGAS3DWg2rW6NVqhj3vm/DB 25 | NTpv4wqpmigSSECrRzG0WYuzVDyXc6ot7x2SS2q5aqpN9nnrDGrVWNZy6CVeUvDv 26 | 0salC/TDMYgHd4qcKEzBf+WRtjGB/yaEbaG/63ROHvWtwl9htLzmHQw+mH+82Af2 27 | Kyps5vprSrMp4/xBk0lixq4= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /test/const_server.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2020 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(const_server). 18 | 19 | -export([start_link/3]). 20 | 21 | %% Callbacks 22 | -export([init/3, loop/3]). 23 | 24 | start_link(Transport, RawSock, Resp) -> 25 | {ok, spawn_link(?MODULE, init, [Transport, RawSock, Resp])}. 26 | 27 | init(Transport, RawSock, Resp) -> 28 | case Transport:wait(RawSock) of 29 | {ok, Sock} -> 30 | loop(Transport, Sock, Resp); 31 | {error, Reason} -> 32 | {error, Reason} 33 | end. 34 | 35 | loop(Transport, Sock, Resp) -> 36 | case Transport:recv(Sock, 0) of 37 | {ok, _Data} -> 38 | Transport:send(Sock, Resp), 39 | loop(Transport, Sock, Resp); 40 | {shutdown, Reason} -> 41 | exit({shutdown, Reason}); 42 | {error, Reason} -> 43 | exit({shutdown, Reason}) 44 | end. 45 | -------------------------------------------------------------------------------- /test/const_udp_server.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2020 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(const_udp_server). 18 | 19 | -export([start_link/3]). 20 | 21 | -export([init/3, loop/3]). 22 | 23 | start_link(Transport, Peer, Resp) -> 24 | {ok, spawn_link(?MODULE, init, [Transport, Peer, Resp])}. 25 | 26 | init(Transport, Peer, Resp) -> 27 | loop(Transport, Peer, Resp). 28 | 29 | loop(Transport, Peer, Resp) -> 30 | receive 31 | {datagram, _From, <<"stop">>} -> 32 | exit(normal); 33 | {datagram, From, _Packet} -> 34 | From ! {datagram, Peer, Resp}, 35 | loop(Transport, Peer, Resp) 36 | end. 37 | -------------------------------------------------------------------------------- /test/dtls_echo_server.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2020 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(dtls_echo_server). 18 | 19 | -export([start/1]). 20 | 21 | -export([start_link/2, loop/3]). 22 | 23 | start(Port) -> 24 | ok = esockd:start(), 25 | PrivDir = code:priv_dir(esockd), 26 | DtlsOpts = [{mode, binary}, {reuseaddr, true}, 27 | {certfile, filename:join(PrivDir, "demo.crt")}, 28 | {keyfile, filename:join(PrivDir, "demo.key")} 29 | ], 30 | Opts = [{acceptors, 4}, 31 | {max_connections, 1000}, 32 | {dtls_options, DtlsOpts} 33 | ], 34 | MFArgs = {?MODULE, start_link, []}, 35 | {ok, _} = esockd:open_dtls('echo/dtls', Port, Opts, MFArgs). 36 | 37 | start_link(Transport, Socket) -> 38 | Args = [self(), Transport, Socket], 39 | CPid = proc_lib:spawn_link(?MODULE, loop, Args), 40 | {ok, CPid}. 41 | 42 | loop(Parent, Transport, RawSocket) -> 43 | case Transport:wait(RawSocket) of 44 | {ok, Socket} -> 45 | run_loop(Parent, Transport, Socket); 46 | {error, _} -> 47 | ok = Transport:fast_close(RawSocket) 48 | end. 49 | 50 | run_loop(Parent, Transport, Socket) -> 51 | receive 52 | {ssl, _RawSocket, Packet} -> 53 | Transport:async_send(Socket, Packet), 54 | run_loop(Parent, Transport, Socket); 55 | {inet_reply, _Socket, ok} -> 56 | run_loop(Parent, Transport, Socket) 57 | end. 58 | 59 | -------------------------------------------------------------------------------- /test/echo_server.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2020 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(echo_server). 18 | 19 | -export([start_link/2]). 20 | 21 | %% Callbacks 22 | -export([init/2, loop/2]). 23 | 24 | start_link(Transport, RawSock) -> 25 | {ok, spawn_link(?MODULE, init, [Transport, RawSock])}. 26 | 27 | init(Transport, RawSock) -> 28 | case Transport:wait(RawSock) of 29 | {ok, Sock} -> 30 | loop(Transport, Sock); 31 | {error, Reason} -> 32 | {error, Reason} 33 | end. 34 | 35 | loop(Transport, Sock) -> 36 | case Transport:recv(Sock, 0) of 37 | {ok, Data} -> 38 | %%{ok, Peername} = Transport:peername(Sock), 39 | %%io:format("RECV from ~s: ~s~n", [esockd:format(Peername), Data]), 40 | Transport:send(Sock, Data), 41 | loop(Transport, Sock); 42 | {shutdown, Reason} -> 43 | exit({shutdown, Reason}); 44 | {error, Reason} -> 45 | exit({shutdown, Reason}) 46 | end. 47 | 48 | -------------------------------------------------------------------------------- /test/esockd_access_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2020 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(esockd_access_SUITE). 18 | 19 | -compile(export_all). 20 | -compile(nowarn_export_all). 21 | 22 | -include_lib("eunit/include/eunit.hrl"). 23 | 24 | all() -> esockd_ct:all(?MODULE). 25 | 26 | %%-------------------------------------------------------------------- 27 | %% Access 28 | %%-------------------------------------------------------------------- 29 | 30 | t_match(_) -> 31 | Rules = [esockd_access:compile({deny, "192.168.1.1"}), 32 | esockd_access:compile({allow, "192.168.1.0/24"}), 33 | esockd_access:compile({deny, all})], 34 | ?assertEqual({matched, deny}, esockd_access:match({192,168,1,1}, Rules)), 35 | ?assertEqual({matched, allow}, esockd_access:match({192,168,1,4}, Rules)), 36 | ?assertEqual({matched, allow}, esockd_access:match({192,168,1,60}, Rules)), 37 | ?assertEqual({matched, deny}, esockd_access:match({10,10,10,10}, Rules)). 38 | 39 | t_nomatch(_) -> 40 | Rules = [esockd_access:compile({deny, "192.168.1.1"}), 41 | esockd_access:compile({allow, "192.168.1.0/24"}) 42 | ], 43 | ?assertEqual(nomatch, esockd_access:match({10,10,10,10}, Rules)). 44 | 45 | t_match_localhost(_) -> 46 | Rules = [esockd_access:compile({allow, "127.0.0.1"}), 47 | esockd_access:compile({deny, all})], 48 | ?assertEqual({matched, allow}, esockd_access:match({127,0,0,1}, Rules)), 49 | ?assertEqual({matched, deny}, esockd_access:match({192,168,0,1}, Rules)). 50 | 51 | t_match_allow(_) -> 52 | Rules = [esockd_access:compile({deny, "10.10.0.0/16"}), 53 | esockd_access:compile({allow, all})], 54 | ?assertEqual({matched, deny}, esockd_access:match({10,10,0,10}, Rules)), 55 | ?assertEqual({matched, allow}, esockd_access:match({127,0,0,1}, Rules)), 56 | ?assertEqual({matched, allow}, esockd_access:match({192,168,0,1}, Rules)). 57 | 58 | t_match_ipv6(_) -> 59 | Rules = [esockd_access:compile({deny, "2001:abcd::/64"}), 60 | esockd_access:compile({allow, all})], 61 | {ok, Addr1} = inet:parse_address("2001:abcd::10"), 62 | {ok, Addr2} = inet:parse_address("2001::10"), 63 | ?assertEqual({matched, deny}, esockd_access:match(Addr1, Rules)), 64 | ?assertEqual({matched, allow}, esockd_access:match(Addr2, Rules)). 65 | 66 | -------------------------------------------------------------------------------- /test/esockd_cidr_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2020 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(esockd_cidr_SUITE). 18 | 19 | -compile(export_all). 20 | -compile(nowarn_export_all). 21 | 22 | -include_lib("eunit/include/eunit.hrl"). 23 | 24 | all() -> esockd_ct:all(?MODULE). 25 | 26 | %%-------------------------------------------------------------------- 27 | %% CIDR Test Cases 28 | %%-------------------------------------------------------------------- 29 | 30 | t_parse_ipv4(_) -> 31 | ?assertEqual({{192,168,0,0}, {192,168,0,0}, 32}, esockd_cidr:parse("192.168.0.0")), 32 | ?assertEqual({{1,2,3,4}, {1,2,3,4}, 32}, esockd_cidr:parse("1.2.3.4")), 33 | ?assertEqual({{0,0,0,0}, {255,255,255,255}, 0}, esockd_cidr:parse("192.168.0.0/0", true)), 34 | ?assertEqual({{192,0,0,0}, {192,255,255,255}, 8}, esockd_cidr:parse("192.168.0.0/8", true)), 35 | ?assertEqual({{192,168,0,0}, {192,169,255,255}, 15}, esockd_cidr:parse("192.168.0.0/15", true)), 36 | ?assertEqual({{192,168,0,0}, {192,168,255,255}, 16}, esockd_cidr:parse("192.168.0.0/16")), 37 | ?assertEqual({{192,168,0,0}, {192,168,127,255}, 17}, esockd_cidr:parse("192.168.0.0/17")), 38 | ?assertEqual({{192,168,0,0}, {192,168,63,255}, 18}, esockd_cidr:parse("192.168.0.0/18")), 39 | ?assertEqual({{192,168,0,0}, {192,168,31,255}, 19}, esockd_cidr:parse("192.168.0.0/19")), 40 | ?assertEqual({{192,168,0,0}, {192,168,15,255}, 20}, esockd_cidr:parse("192.168.0.0/20")), 41 | ?assertEqual({{192,168,0,0}, {192,168,7,255}, 21}, esockd_cidr:parse("192.168.0.0/21")), 42 | ?assertEqual({{192,168,0,0}, {192,168,3,255}, 22}, esockd_cidr:parse("192.168.0.0/22")), 43 | ?assertEqual({{192,168,0,0}, {192,168,1,255}, 23}, esockd_cidr:parse("192.168.0.0/23")), 44 | ?assertEqual({{192,168,0,0}, {192,168,0,255}, 24}, esockd_cidr:parse("192.168.0.0/24")), 45 | ?assertEqual({{192,168,0,0}, {192,168,0,1}, 31}, esockd_cidr:parse("192.168.0.0/31")), 46 | ?assertEqual({{192,168,0,0}, {192,168,0,0}, 32}, esockd_cidr:parse("192.168.0.0/32")). 47 | 48 | t_parse_ipv6(_) -> 49 | ?assertEqual({{0, 0, 0, 0, 0, 0, 0, 0}, {65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535}, 0}, 50 | esockd_cidr:parse("2001:abcd::/0", true)), 51 | ?assertEqual({{8193, 43981, 0, 0, 0, 0, 0, 0}, {8193, 43981, 65535, 65535, 65535, 65535, 65535, 65535}, 32}, 52 | esockd_cidr:parse("2001:abcd::/32")), 53 | ?assertEqual({{8193, 43981, 0, 0, 0, 0, 0, 0}, {8193, 43981, 32767, 65535, 65535, 65535, 65535, 65535}, 33}, 54 | esockd_cidr:parse("2001:abcd::/33")), 55 | ?assertEqual({{8193, 43981, 0, 0, 0, 0, 0, 0}, {8193, 43981, 16383, 65535, 65535, 65535, 65535, 65535}, 34}, 56 | esockd_cidr:parse("2001:abcd::/34")), 57 | ?assertEqual({{8193, 43981, 0, 0, 0, 0, 0, 0}, {8193, 43981, 8191, 65535, 65535, 65535, 65535, 65535}, 35}, 58 | esockd_cidr:parse("2001:abcd::/35")), 59 | ?assertEqual({{8193, 43981, 0, 0, 0, 0, 0, 0}, {8193, 43981, 4095, 65535, 65535, 65535, 65535, 65535}, 36}, 60 | esockd_cidr:parse("2001:abcd::/36")), 61 | ?assertEqual({{8193, 43981, 0, 0, 0, 0, 0, 0}, {8193, 43981, 0, 0, 0, 0, 0, 0}, 128}, 62 | esockd_cidr:parse("2001:abcd::/128")). 63 | 64 | t_ipv4_address_count(_) -> 65 | ?assertEqual(4294967296, esockd_cidr:count(esockd_cidr:parse("192.168.0.0/0", true))), 66 | ?assertEqual(65536, esockd_cidr:count(esockd_cidr:parse("192.168.0.0/16", true))), 67 | ?assertEqual(32768, esockd_cidr:count(esockd_cidr:parse("192.168.0.0/17", true))), 68 | ?assertEqual(256, esockd_cidr:count(esockd_cidr:parse("192.168.0.0/24", true))), 69 | ?assertEqual(1, esockd_cidr:count(esockd_cidr:parse("192.168.0.0/32", true))). 70 | 71 | t_ipv6_address_count(_) -> 72 | ?assert(esockd_cidr:count(esockd_cidr:parse("2001::abcd/0", true)) == math:pow(2, 128)), 73 | ?assert(esockd_cidr:count(esockd_cidr:parse("2001::abcd/64", true)) == math:pow(2, 64)), 74 | ?assert(esockd_cidr:count(esockd_cidr:parse("2001::abcd/128")) == 1). 75 | 76 | t_to_string(_) -> 77 | ?assertEqual("192.168.0.0/16", esockd_cidr:to_string({{192,168,0,0}, {192,168,255,255}, 16})), 78 | ?assertEqual("2001:abcd::/32", esockd_cidr:to_string({{8193, 43981, 0, 0, 0, 0, 0, 0}, 79 | {8193, 43981, 65535, 65535, 65535, 65535, 65535, 65535}, 32})). 80 | 81 | t_ipv4_match(_) -> 82 | CIDR = esockd_cidr:parse("192.168.0.0/16"), 83 | ?assert(esockd_cidr:match({192,168,0,0}, CIDR)), 84 | ?assert(esockd_cidr:match({192,168,0,1}, CIDR)), 85 | ?assert(esockd_cidr:match({192,168,1,0}, CIDR)), 86 | ?assert(esockd_cidr:match({192,168,0,255}, CIDR)), 87 | ?assert(esockd_cidr:match({192,168,255,0}, CIDR)), 88 | ?assert(esockd_cidr:match({192,168,255,255}, CIDR)), 89 | ?assertNot(esockd_cidr:match({192,168,255,256}, CIDR)), 90 | ?assertNot(esockd_cidr:match({192,169,0,0}, CIDR)), 91 | ?assertNot(esockd_cidr:match({192,167,255,255}, CIDR)). 92 | 93 | t_ipv6_match(_) -> 94 | CIDR = {{8193, 43981, 0, 0, 0, 0, 0, 0}, 95 | {8193, 43981, 8191, 65535, 65535, 65535, 65535, 65535}, 35}, 96 | ?assert(esockd_cidr:match({8193, 43981, 0, 0, 0, 0, 0, 0}, CIDR)), 97 | ?assert(esockd_cidr:match({8193, 43981, 0, 0, 0, 0, 0, 1}, CIDR)), 98 | ?assert(esockd_cidr:match({8193, 43981, 8191, 65535, 65535, 65535, 65535, 65534}, CIDR)), 99 | ?assert(esockd_cidr:match({8193, 43981, 8191, 65535, 65535, 65535, 65535, 65535}, CIDR)), 100 | ?assertNot(esockd_cidr:match({8193, 43981, 8192, 65535, 65535, 65535, 65535, 65535}, CIDR)), 101 | ?assertNot(esockd_cidr:match({65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535}, CIDR)). 102 | 103 | t_is_ipv4(_) -> 104 | ?assert(esockd_cidr:is_ipv4({127,0,0,1})), 105 | ?assertNot(esockd_cidr:is_ipv4({8193, 43981, 0, 0, 0, 0, 0, 0})). 106 | 107 | t_is_ipv6(_) -> 108 | ?assertNot(esockd_cidr:is_ipv6({127,0,0,1})), 109 | ?assert(esockd_cidr:is_ipv6({8193, 43981, 0, 0, 0, 0, 0, 0})). 110 | 111 | -------------------------------------------------------------------------------- /test/esockd_connection_sup_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2020 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(esockd_connection_sup_SUITE). 18 | 19 | -compile(export_all). 20 | -compile(nowarn_export_all). 21 | 22 | -include_lib("eunit/include/eunit.hrl"). 23 | 24 | all() -> esockd_ct:all(?MODULE). 25 | 26 | t_start_connection(_) -> 27 | ok = meck_esockd_transport(fun(_, _) -> 28 | timer:sleep(100), 29 | {ok, <<"Hi">>} 30 | end), 31 | with_conn_sup([{max_connections, 1024}], 32 | fun(ConnSup) -> 33 | {ok, ConnPid} = esockd_connection_sup:start_connection(ConnSup, sock, []), 34 | ?assert(is_process_alive(ConnPid)) 35 | end), 36 | ok = meck:unload(esockd_transport). 37 | 38 | t_shutdown_connection_count(_) -> 39 | RecvFn = fun(_, _) -> 40 | receive 41 | {shutdown, Reason} -> 42 | {shutdown, Reason} 43 | end 44 | end, 45 | ok = meck_esockd_transport(RecvFn), 46 | StartThenShutdown = 47 | fun(ConnSup, Reason) -> 48 | {ok, ConnPid} = esockd_connection_sup:start_connection(ConnSup, sock, []), 49 | ?assert(is_process_alive(ConnPid)), 50 | _ = monitor(process, ConnPid), 51 | ConnPid ! {shutdown, Reason}, 52 | ConnPid 53 | end, 54 | WaitDown = 55 | fun(ConnPid, ExpectedReason) -> 56 | receive 57 | {'DOWN', _, _, ConnPid, Reason} -> 58 | ?assertEqual({shutdown, ExpectedReason}, Reason) 59 | after 60 | 1000 -> 61 | error(timeout) 62 | end 63 | end, 64 | with_conn_sup([{max_connections, 1024}], 65 | fun(ConnSup) -> 66 | Reason1 = {ssl_error, bar}, 67 | Reason2 = #{shutdown_count => foo, reason => bar}, 68 | Pid1 = StartThenShutdown(ConnSup, Reason1), 69 | Pid2 = StartThenShutdown(ConnSup, Reason2), 70 | WaitDown(Pid1, Reason1), 71 | WaitDown(Pid2, Reason2), 72 | Counts = esockd_connection_sup:get_shutdown_count(ConnSup), 73 | ?assertEqual([{foo, 1}, {ssl_error, 1}], lists:sort(Counts)) 74 | end), 75 | ok = meck:unload(esockd_transport). 76 | 77 | t_allow_deny(_) -> 78 | AccessRules = [{allow, "192.168.1.0/24"}], 79 | with_conn_sup([{access_rules, AccessRules}], 80 | fun(ConnSup) -> 81 | ?assertEqual([{allow, "192.168.1.0/24"}], 82 | esockd_connection_sup:access_rules(ConnSup)), 83 | ok = esockd_connection_sup:allow(ConnSup, "10.10.0.0/16"), 84 | ok = esockd_connection_sup:deny(ConnSup, "172.16.1.1/16"), 85 | ?assertEqual([{deny, "172.16.0.0/16"}, 86 | {allow, "10.10.0.0/16"}, 87 | {allow, "192.168.1.0/24"} 88 | ], esockd_connection_sup:access_rules(ConnSup)) 89 | end). 90 | 91 | t_get_shutdown_count(_) -> 92 | with_conn_sup([{max_connections, 1024}], 93 | fun(ConnSup) -> 94 | ?assertEqual([], esockd_connection_sup:get_shutdown_count(ConnSup)) 95 | end). 96 | 97 | t_count_connections(_) -> 98 | with_conn_sup([{max_connections, 1024}], 99 | fun(ConnSup) -> 100 | ?assertEqual(0, esockd_connection_sup:count_connections(ConnSup)) 101 | end). 102 | 103 | t_get_set_max_connections(_) -> 104 | with_conn_sup([{max_connections, 100}], 105 | fun(ConnSup) -> 106 | ?assertEqual(100, esockd_connection_sup:get_max_connections(ConnSup)), 107 | ok = esockd_connection_sup:set_options(ConnSup, [{max_connections, 200}]), 108 | ?assertEqual(200, esockd_connection_sup:get_max_connections(ConnSup)) 109 | end). 110 | 111 | t_handle_unexpected(_) -> 112 | {reply, ignore, state} = esockd_connection_sup:handle_call(req, from, state), 113 | {noreply, state} = esockd_connection_sup:handle_cast(msg, state), 114 | {noreply, state} = esockd_connection_sup:handle_info(info, state). 115 | 116 | with_conn_sup(Opts, Fun) -> 117 | {ok, ConnSup} = esockd_connection_sup:start_link([{connection_mfargs, echo_server} | Opts]), 118 | Fun(ConnSup), 119 | ok = esockd_connection_sup:stop(ConnSup). 120 | 121 | meck_esockd_transport(RecvFn) -> 122 | ok = meck:new(esockd_transport, [non_strict, passthrough, no_history]), 123 | ok = meck:expect(esockd_transport, peername, fun(_Sock) -> {ok, {{127,0,0,1}, 3456}} end), 124 | ok = meck:expect(esockd_transport, wait, fun(Sock) -> {ok, Sock} end), 125 | ok = meck:expect(esockd_transport, recv, RecvFn), 126 | ok = meck:expect(esockd_transport, send, fun(_Sock, _Data) -> ok end), 127 | ok = meck:expect(esockd_transport, controlling_process, fun(_Sock, _ConnPid) -> ok end), 128 | ok = meck:expect(esockd_transport, ready, fun(_ConnPid, _Sock, []) -> ok end), 129 | ok. 130 | 131 | -------------------------------------------------------------------------------- /test/esockd_ct.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2020 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(esockd_ct). 18 | 19 | -export([ all/1 20 | , cacertfile/1 21 | , certfile/1 22 | , keyfile/1 23 | , certfile/2 24 | , keyfile/2 25 | ]). 26 | 27 | %% @doc Get all the test cases in a CT suite. 28 | all(Suite) -> 29 | lists:usort([F || {F, 1} <- Suite:module_info(exports), 30 | string:substr(atom_to_list(F), 1, 2) == "t_" 31 | ]). 32 | 33 | cacertfile(Config) -> 34 | filename:join([test_dir(Config), "certs", "ca.crt"]). 35 | 36 | certfile(Config) -> 37 | certfile(Config, "test.crt"). 38 | 39 | keyfile(Config) -> 40 | keyfile(Config, "test.key"). 41 | 42 | certfile(Config, Name) -> 43 | filename:join([test_dir(Config), "certs", Name]). 44 | 45 | keyfile(Config, Name) -> 46 | filename:join([test_dir(Config), "certs", Name]). 47 | 48 | test_dir(Config) -> 49 | filename:dirname(filename:dirname(proplists:get_value(data_dir, Config))). 50 | -------------------------------------------------------------------------------- /test/esockd_dtls_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2020 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(esockd_dtls_SUITE). 18 | 19 | -compile(export_all). 20 | -compile(nowarn_export_all). 21 | 22 | -include_lib("eunit/include/eunit.hrl"). 23 | 24 | all() -> esockd_ct:all(?MODULE). 25 | 26 | init_per_suite(Config) -> 27 | {ok, _} = application:ensure_all_started(esockd), 28 | Config. 29 | 30 | end_per_suite(_Config) -> 31 | application:stop(esockd). 32 | 33 | %%-------------------------------------------------------------------- 34 | %% Test cases for DTLS Server 35 | %%-------------------------------------------------------------------- 36 | 37 | t_dtls_server(Config) -> 38 | DtlsOpts = [{mode, binary}, 39 | {reuseaddr, true}, 40 | {certfile, esockd_ct:certfile(Config)}, 41 | {keyfile, esockd_ct:keyfile(Config)}, 42 | {verify, verify_none} 43 | ], 44 | Options = [{acceptors, 4}, 45 | {max_connections, 1000}, 46 | {max_conn_rate, 10}, 47 | {dtls_options, DtlsOpts}], 48 | 49 | ClientOpts = [binary, 50 | {protocol, dtls}, 51 | {active, false}, 52 | {verify, verify_none}], 53 | {ok, _} = esockd:open_dtls('echo/dtls', 9876, Options, {?MODULE, dtls_echo_init, []}), 54 | {ok, Sock} = ssl:connect({127,0,0,1}, 9876, ClientOpts, 5000), 55 | ok = ssl:send(Sock, <<"hello">>), 56 | {ok, <<"hello">>} = ssl:recv(Sock, 5, 3000), 57 | ok = ssl:send(Sock, <<"world">>), 58 | {ok, <<"world">>} = ssl:recv(Sock, 5, 3000), 59 | ok = esockd:close('echo/dtls', 9876). 60 | 61 | %%-------------------------------------------------------------------- 62 | %% DTLS echo server 63 | %%-------------------------------------------------------------------- 64 | 65 | dtls_echo_init(Transport, Socket) -> 66 | {ok, spawn_link(?MODULE, dtls_echo_loop, [Transport, Socket])}. 67 | 68 | dtls_echo_loop(Transport, RawSocket) -> 69 | {ok, Socket} = Transport:wait(RawSocket), 70 | run_dtls_echo_loop(Transport, Socket). 71 | 72 | run_dtls_echo_loop(Transport, Socket) -> 73 | receive 74 | {ssl, _RawSocket, Packet} -> 75 | io:format("~p - ~p~n", [esockd_transport:peername(Socket), 76 | esockd_transport:getstat(Socket, [send_oct,recv_oct])]), 77 | Transport:async_send(Socket, Packet), 78 | run_dtls_echo_loop(Transport, Socket) 79 | end. 80 | 81 | -------------------------------------------------------------------------------- /test/esockd_generic_limiter_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2020 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(esockd_generic_limiter_SUITE). 18 | 19 | -compile(export_all). 20 | -compile(nowarn_export_all). 21 | 22 | -include_lib("eunit/include/eunit.hrl"). 23 | 24 | all() -> esockd_ct:all(?MODULE). 25 | 26 | %%-------------------------------------------------------------------- 27 | %% Test cases for limiter 28 | %%-------------------------------------------------------------------- 29 | 30 | t_create(_) -> 31 | {ok, _} = esockd_limiter:start_link(), 32 | Limiter = esockd_generic_limiter:create(#{module => esockd_limiter, 33 | name => bucket1, 34 | capacity => 10, 35 | interval => 1}), 36 | ?assertMatch(#{module := _, name := _}, Limiter), 37 | 38 | #{name := bucket1, 39 | capacity := 10, 40 | interval := 1, 41 | tokens := 10 42 | } = esockd_limiter:lookup(bucket1), 43 | ok = esockd_limiter:stop(). 44 | 45 | t_consume(_) -> 46 | {ok, _} = esockd_limiter:start_link(), 47 | Limiter = esockd_generic_limiter:create(#{module => esockd_limiter, 48 | name => bucket, 49 | capacity => 10, 50 | interval => 2}), 51 | #{name := bucket, 52 | capacity := 10, 53 | interval := 2, 54 | tokens := 10 55 | } = esockd_limiter:lookup(bucket), 56 | 57 | {ok, Limiter2} = esockd_generic_limiter:consume(1, Limiter), 58 | 59 | #{tokens := 9} = esockd_limiter:lookup(bucket), 60 | 61 | {ok, Limiter3} = esockd_generic_limiter:consume(4, Limiter2), 62 | #{tokens := 5} = esockd_limiter:lookup(bucket), 63 | 64 | {pause, PauseTime, Limiter4} = esockd_limiter:consume(6, Limiter3), 65 | ?assertEqual(PauseTime =< 2000 andalso PauseTime >= 1900, true), 66 | 67 | timer:sleep(PauseTime + 100), 68 | {ok, _Limiter5} = esockd_limiter:consume(6, Limiter4), 69 | ok = esockd_limiter:stop(). 70 | 71 | t_undefined(_) -> 72 | {ok, undefined} = esockd_generic_limiter:consume(10, undefined), 73 | ok = esockd_generic_limiter:delete(undefined), 74 | ok. 75 | -------------------------------------------------------------------------------- /test/esockd_limiter_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2020 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(esockd_limiter_SUITE). 18 | 19 | -compile(export_all). 20 | -compile(nowarn_export_all). 21 | 22 | -include_lib("eunit/include/eunit.hrl"). 23 | 24 | all() -> esockd_ct:all(?MODULE). 25 | 26 | %%-------------------------------------------------------------------- 27 | %% Test cases for limiter 28 | %%-------------------------------------------------------------------- 29 | 30 | t_crud_limiter(_) -> 31 | {ok, _} = esockd_limiter:start_link(), 32 | ok = esockd_limiter:create(bucket1, 10), 33 | ok = esockd_limiter:create(bucket2, 1000, 10), 34 | #{name := bucket1, 35 | capacity := 10, 36 | interval := 1, 37 | tokens := 10 38 | } = esockd_limiter:lookup(bucket1), 39 | #{name := bucket2, 40 | capacity := 1000, 41 | interval := 10, 42 | tokens := 1000 43 | } = esockd_limiter:lookup(bucket2), 44 | Limiters = esockd_limiter:get_all(), 45 | ?assertEqual(2, length(Limiters)), 46 | ok = esockd_limiter:delete(bucket1), 47 | ok = esockd_limiter:delete(bucket2), 48 | timer:sleep(500), %% wait for deleting 49 | undefined = esockd_limiter:lookup(bucket1), 50 | undefined = esockd_limiter:lookup(bucket2), 51 | ok = esockd_limiter:stop(). 52 | 53 | t_twice_create(_) -> 54 | {ok, _} = esockd_limiter:start_link(), 55 | ok = esockd_limiter:create(bucket1, 10), 56 | #{name := bucket1, 57 | capacity := 10, 58 | interval := 1, 59 | tokens := 10 60 | } = esockd_limiter:lookup(bucket1), 61 | {5, 0} = esockd_limiter:consume(bucket1, 5), 62 | ok = esockd_limiter:create(bucket1, 100), 63 | #{name := bucket1, 64 | capacity := 100, 65 | interval := 1, 66 | tokens := 100 67 | } = esockd_limiter:lookup(bucket1), 68 | ok = esockd_limiter:stop(). 69 | 70 | t_consume(_) -> 71 | {ok, _} = esockd_limiter:start_link(), 72 | ok = esockd_limiter:create(bucket, 10, 2), 73 | #{name := bucket, 74 | capacity := 10, 75 | interval := 2, 76 | tokens := 10 77 | } = esockd_limiter:lookup(bucket), 78 | {9, 0} = esockd_limiter:consume(bucket), 79 | #{tokens := 9} = esockd_limiter:lookup(bucket), 80 | {5, 0} = esockd_limiter:consume(bucket, 4), 81 | #{tokens := 5} = esockd_limiter:lookup(bucket), 82 | {0, PauseTime} = esockd_limiter:consume(bucket, 5), 83 | ?assertEqual(PauseTime =< 2000 andalso PauseTime >= 1900, true), 84 | 85 | #{tokens := 0} = esockd_limiter:lookup(bucket), 86 | ok = timer:sleep(1000), 87 | #{tokens := 0} = esockd_limiter:lookup(bucket), 88 | ok = timer:sleep(1020), 89 | #{tokens := 10} = esockd_limiter:lookup(bucket), 90 | {5, 0} = esockd_limiter:consume(bucket, 5), 91 | {1, 0} = esockd_limiter:consume(notexisted, 1), 92 | ok = esockd_limiter:stop(). 93 | 94 | t_concurrent_consume(_) -> 95 | {ok, _} = esockd_limiter:start_link(), 96 | ConnRate = 10000, 97 | ok = esockd_limiter:create(bucket, ConnRate, 1), 98 | Parent = self(), 99 | Consumer = fun() -> 100 | {X, P} = esockd_limiter:consume(bucket, 1), 101 | Parent ! {consumer, X, P} 102 | end, 103 | Collect = fun _F(0, Acc) -> Acc; 104 | _F(N, Acc) -> 105 | _F(N-1, [receive M -> M after 1000 -> error(timeout) end | Acc]) 106 | end, 107 | %% Case1: N*1 108 | [spawn(Consumer) || _ <- lists:seq(1, ConnRate)], 109 | ReceviedTokens = Collect(ConnRate, []), 110 | ct:pal("~p~n", [ReceviedTokens]), 111 | ?assertEqual(ConnRate, length(lists:usort(ReceviedTokens))), 112 | 113 | %% Case2: N/10 * N 114 | ok = esockd_limiter:create(bucket, ConnRate, 1), 115 | [spawn(fun() -> 116 | [Consumer() || _ <- lists:seq(1, ConnRate div 10)] 117 | end) || _ <- lists:seq(1, 10)], 118 | ReceviedTokens2 = Collect(ConnRate, []), 119 | ct:pal("~p~n", [ReceviedTokens2]), 120 | ?assertEqual(ConnRate, length(lists:usort(ReceviedTokens2))). 121 | 122 | t_handle_call(_) -> 123 | {reply, ignore, state} = esockd_limiter:handle_call(req, '_From', state). 124 | 125 | t_handle_cast(_) -> 126 | {noreply, state} = esockd_limiter:handle_cast(msg, state). 127 | 128 | t_handle_info(_) -> 129 | {noreply, state} = esockd_limiter:handle_info(info, state). 130 | 131 | t_code_change(_) -> 132 | {ok, state} = esockd_limiter:code_change('OldVsn', state, 'Extra'). 133 | 134 | -------------------------------------------------------------------------------- /test/esockd_peercert_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2020 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(esockd_peercert_SUITE). 18 | 19 | -compile(export_all). 20 | -compile(nowarn_export_all). 21 | 22 | -include_lib("eunit/include/eunit.hrl"). 23 | 24 | all() -> esockd_ct:all(?MODULE). 25 | 26 | t_subject(Config) -> 27 | undefined = esockd_peercert:subject(nossl), 28 | undefined = esockd_peercert:subject(undefined), 29 | DerCert = pem_decode(esockd_ct:certfile(Config)), 30 | _Subject = esockd_peercert:subject(DerCert), 31 | <<"C=CH">> = esockd_peercert:subject([{pp2_ssl_cn, <<"C=CH">>}]). 32 | 33 | t_common_name(Config) -> 34 | undefined = esockd_peercert:common_name(nossl), 35 | undefined = esockd_peercert:common_name(undefined), 36 | DerCert = pem_decode(esockd_ct:certfile(Config)), 37 | _CN = esockd_peercert:common_name(DerCert), 38 | <<"C=CH">> = esockd_peercert:common_name([{pp2_ssl_cn, <<"C=CH">>}]). 39 | 40 | pem_decode(CertFile) -> 41 | {ok, CertBin} = file:read_file(CertFile), 42 | [{'Certificate', DerCert, _}] = public_key:pem_decode(CertBin), 43 | DerCert. 44 | 45 | -------------------------------------------------------------------------------- /test/esockd_rate_limit_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2020 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(esockd_rate_limit_SUITE). 18 | 19 | -compile(export_all). 20 | -compile(nowarn_export_all). 21 | 22 | -include_lib("eunit/include/eunit.hrl"). 23 | 24 | all() -> esockd_ct:all(?MODULE). 25 | 26 | t_info(_) -> 27 | Rl = esockd_rate_limit:new({1, 10}), 28 | Info = esockd_rate_limit:info(Rl), 29 | ?assertMatch(#{rate := 1, 30 | burst := 10, 31 | tokens := 10 32 | }, Info), 33 | ?assert(erlang:system_time(milli_seconds) >= maps:get(time, Info)). 34 | 35 | t_check(_) -> 36 | Rl = esockd_rate_limit:new({1, 10}), 37 | #{tokens := 10} = esockd_rate_limit:info(Rl), 38 | {0, Rl1} = esockd_rate_limit:check(5, Rl), 39 | #{tokens := 5} = esockd_rate_limit:info(Rl1), 40 | %% P = 1/r = 1000ms 41 | {1000, Rl2} = esockd_rate_limit:check(5, Rl1), 42 | #{tokens := 0} = esockd_rate_limit:info(Rl2), 43 | %% P = (Tokens-Limit)/r = 5000ms 44 | {5000, Rl3} = esockd_rate_limit:check(5, Rl2), 45 | #{tokens := 0} = esockd_rate_limit:info(Rl3), 46 | ok = timer:sleep(1000), 47 | %% P = (Tokens-Limit)/r = 1000ms 48 | {1000, _} = esockd_rate_limit:check(2, Rl3). 49 | 50 | t_check_bignum(_) -> 51 | R1 = 30000000, 52 | R2 = 15000000, 53 | Rl = esockd_rate_limit:new({R1, R1}), 54 | #{tokens := R1} = esockd_rate_limit:info(Rl), 55 | {0, Rl1} = esockd_rate_limit:check(R2, Rl), 56 | #{tokens := R2} = esockd_rate_limit:info(Rl1), 57 | %% P = 1/r = 0.00003333 ~= 0ms 58 | {0, Rl2} = esockd_rate_limit:check(R2, Rl1), 59 | #{tokens := 0} = esockd_rate_limit:info(Rl2), 60 | 61 | timer:sleep(1000), 62 | %% P = (Tokens-Limit)/r = 1000ms 63 | {1000, Rl3} = esockd_rate_limit:check(R1*2, Rl2), 64 | #{tokens := 0} = esockd_rate_limit:info(Rl3), 65 | %% P = (Tokens-Limit)/r = 0.5ms ~= 1ms 66 | {1, Rl4} = esockd_rate_limit:check(R1*(1+0.0005), Rl2), 67 | #{tokens := 0} = esockd_rate_limit:info(Rl4). 68 | 69 | -------------------------------------------------------------------------------- /test/esockd_server_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2020 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(esockd_server_SUITE). 18 | 19 | -compile(export_all). 20 | -compile(nowarn_export_all). 21 | 22 | -include_lib("eunit/include/eunit.hrl"). 23 | 24 | all() -> esockd_ct:all(?MODULE). 25 | 26 | t_inc_dec_stats(_) -> 27 | {ok, _} = esockd_server:start_link(), 28 | Name = {echo, 3000}, 29 | esockd_server:init_stats(Name, [accepting]), 30 | esockd_server:inc_stats(Name, accepting, 2), 31 | esockd_server:inc_stats(Name, accepting, 2), 32 | esockd_server:dec_stats(Name, accepting, 1), 33 | [{accepting, 3}] = esockd_server:get_stats(Name), 34 | ok = esockd_server:del_stats(Name), 35 | ok = timer:sleep(100), 36 | [] = esockd_server:get_stats(Name), 37 | ok = esockd_server:stop(). 38 | 39 | t_stats_fun(_) -> 40 | {ok, _} = esockd_server:start_link(), 41 | StatsFun = esockd_server:stats_fun({echo, 3000}, accepting), 42 | ok = lists:foreach(StatsFun, [{inc, 1}, {inc, 2}, {inc, 3}, {dec, 1}]), 43 | [{accepting, 5}] = esockd_server:get_stats({echo, 3000}), 44 | ok = esockd_server:stop(). 45 | 46 | t_handle_unexpected(_) -> 47 | {reply, ignore, state} = esockd_server:handle_call(req, from, state), 48 | {noreply, state} = esockd_server:handle_cast(msg, state), 49 | {noreply, state} = esockd_server:handle_info(info, state), 50 | {ok, state} = esockd_server:code_change('OldVsn', state, extra). 51 | 52 | -------------------------------------------------------------------------------- /test/esockd_ssl_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2020 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(esockd_ssl_SUITE). 18 | 19 | -compile(export_all). 20 | -compile(nowarn_export_all). 21 | 22 | -include_lib("eunit/include/eunit.hrl"). 23 | 24 | all() -> esockd_ct:all(?MODULE). 25 | 26 | init_per_testcase(_TestCase, Config) -> 27 | CertFile = esockd_ct:certfile(Config), 28 | [{cert, pem_decode(CertFile)}|Config]. 29 | 30 | end_per_testcase(_TestCase, Config) -> 31 | Config. 32 | 33 | pem_decode(CertFile) -> 34 | {ok, CertBin} = file:read_file(CertFile), 35 | [{'Certificate', DerCert, _}] = public_key:pem_decode(CertBin), 36 | DerCert. 37 | 38 | cert(Config) -> proplists:get_value(cert, Config). 39 | 40 | t_peer_cert_issuer(Config) -> 41 | esockd_ssl:peer_cert_issuer(cert(Config)). 42 | 43 | t_peer_cert_subject_items(Config) -> 44 | esockd_ssl:peer_cert_subject_items(cert(Config), {0,9,2342,19200300,100,1,1}). 45 | 46 | t_peer_cert_validity(Config) -> 47 | esockd_ssl:peer_cert_validity(cert(Config)). 48 | 49 | t_peer_cert_common_name(Config) -> 50 | esockd_ssl:peer_cert_common_name(cert(Config)). 51 | 52 | t_peer_cert_subject(Config) -> 53 | esockd_ssl:peer_cert_subject(cert(Config)). 54 | 55 | -------------------------------------------------------------------------------- /test/esockd_udp_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2020 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(esockd_udp_SUITE). 18 | 19 | -compile(export_all). 20 | -compile(nowarn_export_all). 21 | 22 | -include_lib("eunit/include/eunit.hrl"). 23 | -record(state, {proto, sock, port, rate_limit, 24 | conn_limiter, limit_timer, max_peers, 25 | peers, options, access_rules, mfa, health_check_request, health_check_reply}). 26 | 27 | all() -> esockd_ct:all(?MODULE). 28 | 29 | init_per_testcase(_TestCase, Config) -> 30 | Config. 31 | 32 | end_per_testcase(_TestCase, Config) -> 33 | Config. 34 | 35 | %%-------------------------------------------------------------------- 36 | %% Test cases for UDP Server 37 | %%-------------------------------------------------------------------- 38 | 39 | t_udp_server(_) -> 40 | with_udp_server( 41 | fun(_Srv, Port) -> 42 | {ok, Sock} = gen_udp:open(0, [binary, {active, false}]), 43 | ok = udp_send_and_recv(Sock, Port, <<"hello">>), 44 | ok = udp_send_and_recv(Sock, Port, <<"world">>) 45 | end). 46 | 47 | t_count_peers(_) -> 48 | with_udp_server( 49 | fun(Srv, Port) -> 50 | {ok, Sock1} = gen_udp:open(0, [binary, {active, false}]), 51 | ok = udp_send_and_recv(Sock1, Port, <<"hello">>), 52 | {ok, Sock2} = gen_udp:open(0, [binary, {active, false}]), 53 | ok = udp_send_and_recv(Sock2, Port, <<"world">>), 54 | ?assertEqual(2, esockd_udp:count_peers(Srv)) 55 | end). 56 | 57 | t_peer_down(_) -> 58 | with_udp_server( 59 | fun(Srv, Port) -> 60 | {ok, Sock} = gen_udp:open(0, [binary, {active, false}]), 61 | ok = udp_send_and_recv(Sock, Port, <<"hello">>), 62 | ?assertEqual(1, esockd_udp:count_peers(Srv)), 63 | ok = gen_udp:send(Sock, {127,0,0,1}, Port, <<"stop">>), 64 | timer:sleep(100), 65 | ?assertEqual(0, esockd_udp:count_peers(Srv)) 66 | end). 67 | 68 | t_udp_error(_) -> 69 | with_udp_server( 70 | fun(Srv, Port) -> 71 | process_flag(trap_exit, true), 72 | {ok, Sock} = gen_udp:open(0, [binary, {active, false}]), 73 | ok = udp_send_and_recv(Sock, Port, <<"hello">>), 74 | ?assertEqual(1, esockd_udp:count_peers(Srv)), 75 | #state{sock = SrvSock, peers = Peers} = sys:get_state(Srv), 76 | ?assertEqual(2, maps:size(Peers)), 77 | erlang:send(Srv, {udp_error, SrvSock, unknown}), 78 | receive 79 | Msg -> 80 | ?assertMatch({'EXIT', _, {udp_error,unknown}}, Msg) 81 | after 1000 -> 82 | throw(udp_error_timeout) 83 | end, 84 | ?assertEqual(false, erlang:is_process_alive(Srv)), 85 | maps:foreach(fun(K, V) -> 86 | ?assertNot(is_pid(K) andalso erlang:is_process_alive(K)), 87 | ?assertNot(is_pid(V) andalso erlang:is_process_alive(V)) 88 | end, Peers) 89 | end). 90 | 91 | t_health_check(_) -> 92 | with_udp_server(fun(_Srv, Port) -> 93 | {ok, Sock} = gen_udp:open(0, [binary, {active, false}]), 94 | ok = udp_send_and_recv(Sock, Port, <<"hello">>), 95 | ok = udp_send_and_recv(Sock, Port, <<"request">>, <<"reply">>) 96 | end, 97 | [{health_check, #{request => <<"request">>, reply => <<"reply">>}}]). 98 | 99 | udp_send_and_recv(Sock, Port, Data) -> 100 | udp_send_and_recv(Sock, Port, Data, Data). 101 | 102 | udp_send_and_recv(Sock, Port, Send, Recv) -> 103 | ok = gen_udp:send(Sock, {127,0,0,1}, Port, Send), 104 | {ok, {_Addr, Port, Recv}} = gen_udp:recv(Sock, 0), 105 | ok. 106 | 107 | with_udp_server(TestFun) -> 108 | with_udp_server(TestFun, []). 109 | 110 | with_udp_server(TestFun, ExtraOpts) -> 111 | dbg:tracer(), 112 | dbg:p(all, c), 113 | dbg:tpl({emqx_connection_sup, '_', '_'}, x), 114 | MFA = {?MODULE, udp_echo_init}, 115 | {ok, Srv} = esockd_udp:server(test, 116 | {{127,0,0,1}, 6000}, 117 | [{connection_mfargs, MFA}] ++ ExtraOpts), 118 | TestFun(Srv, 6000), 119 | is_process_alive(Srv) andalso (ok = esockd_udp:stop(Srv)). 120 | 121 | udp_echo_init(Transport, Peer) -> 122 | {ok, spawn(fun() -> udp_echo_loop(Transport, Peer) end)}. 123 | 124 | udp_echo_loop(Transport, Peer) -> 125 | receive 126 | {datagram, _From, <<"stop">>} -> 127 | exit(normal); 128 | {datagram, From, Packet} -> 129 | From ! {datagram, Peer, Packet}, 130 | udp_echo_loop(Transport, Peer) 131 | end. 132 | 133 | t_handle_call(_) -> 134 | {reply, ignore, state} = esockd_udp:handle_call(req, from, state). 135 | 136 | t_handle_cast(_) -> 137 | {noreply, state} = esockd_udp:handle_cast(msg, state). 138 | 139 | t_handle_info(_) -> 140 | {noreply, state} = esockd_udp:handle_info(info, state). 141 | 142 | t_code_change(_) -> 143 | {ok, state} = esockd_udp:code_change(oldvsn, state, extra). 144 | -------------------------------------------------------------------------------- /test/udp_echo_server.erl: -------------------------------------------------------------------------------- 1 | %%-------------------------------------------------------------------- 2 | %% Copyright (c) 2020 EMQ Technologies Co., Ltd. All Rights Reserved. 3 | %% 4 | %% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %% you may not use this file except in compliance with the License. 6 | %% You may obtain a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, software 11 | %% distributed under the License is distributed on an "AS IS" BASIS, 12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %% See the License for the specific language governing permissions and 14 | %% limitations under the License. 15 | %%-------------------------------------------------------------------- 16 | 17 | -module(udp_echo_server). 18 | 19 | -export([start_link/2]). 20 | 21 | -export([init/2, loop/2]). 22 | 23 | start_link(Transport, Peer) -> 24 | {ok, spawn_link(?MODULE, init, [Transport, Peer])}. 25 | 26 | init(Transport, Peer) -> 27 | loop(Transport, Peer). 28 | 29 | loop(Transport, Peer) -> 30 | receive 31 | {datagram, _From, <<"stop">>} -> 32 | exit(normal); 33 | {datagram, From, Packet} -> 34 | From ! {datagram, Peer, Packet}, 35 | loop(Transport, Peer) 36 | end. 37 | 38 | --------------------------------------------------------------------------------