diff --git a/.ci/docker-compose-file/conf.cluster.env b/.ci/docker-compose-file/conf.cluster.env index d8294a785..2a10bd321 100644 --- a/.ci/docker-compose-file/conf.cluster.env +++ b/.ci/docker-compose-file/conf.cluster.env @@ -1,7 +1,8 @@ EMQX_NAME=emqx -EMQX_CLUSTER__DISCOVERY=static -EMQX_CLUSTER__STATIC__SEEDS="emqx@node1.emqx.io, emqx@node2.emqx.io" -EMQX_LISTENER__TCP__EXTERNAL__PROXY_PROTOCOL=on -EMQX_LISTENER__WS__EXTERNAL__PROXY_PROTOCOL=on -EMQX_LOG__LEVEL=debug -EMQX_LOADED_PLUGINS=emqx_sn +EMQX_CLUSTER__DISCOVERY_STRATEGY=static +EMQX_CLUSTER__STATIC__SEEDS="[emqx@node1.emqx.io, emqx@node2.emqx.io]" +EMQX_ZONES__DEFAULT__LISTENERS__MQTT_TCP__PROXY_PROTOCOL=true +EMQX_ZONES__DEFAULT__LISTENERS__MQTT_WS__PROXY_PROTOCOL=true +EMQX_LOG__CONSOLE_HANDLER__ENABLE=true +EMQX_LOG__CONSOLE_HANDLER__LEVEL=debug +EMQX_LOG__PRIMARY_LEVEL=debug diff --git a/.ci/docker-compose-file/conf.env b/.ci/docker-compose-file/conf.env index 0b1b7c512..d80c79b12 100644 --- a/.ci/docker-compose-file/conf.env +++ b/.ci/docker-compose-file/conf.env @@ -10,5 +10,4 @@ EMQX_AUTH__PGSQL__PASSWORD=public EMQX_AUTH__PGSQL__DATABASE=mqtt EMQX_AUTH__REDIS__SERVER=redis_server:6379 EMQX_AUTH__REDIS__PASSWORD=public -CUTTLEFISH_ENV_OVERRIDE_PREFIX=EMQX_ HOCON_ENV_OVERRIDE_PREFIX=EMQX_ diff --git a/.github/workflows/run_fvt_tests.yaml b/.github/workflows/run_fvt_tests.yaml index 280173bf7..c6b160304 100644 --- a/.github/workflows/run_fvt_tests.yaml +++ b/.github/workflows/run_fvt_tests.yaml @@ -36,10 +36,9 @@ jobs: timeout-minutes: 5 run: | set -e -u -x - echo "CUTTLEFISH_ENV_OVERRIDE_PREFIX=EMQX_" >> .ci/docker-compose-file/conf.cluster.env echo "HOCON_ENV_OVERRIDE_PREFIX=EMQX_" >> .ci/docker-compose-file/conf.cluster.env - echo "EMQX_ZONE__EXTERNAL__RETRY_INTERVAL=2s" >> .ci/docker-compose-file/conf.cluster.env - echo "EMQX_MQTT__MAX_TOPIC_ALIAS=10" >> .ci/docker-compose-file/conf.cluster.env + echo "EMQX_ZONES__DEFAULT__MQTT__RETRY_INTERVAL=2s" >> .ci/docker-compose-file/conf.cluster.env + echo "EMQX_ZONES__DEFAULT__MQTT__MAX_TOPIC_ALIAS=10" >> .ci/docker-compose-file/conf.cluster.env docker-compose \ -f .ci/docker-compose-file/docker-compose-emqx-cluster.yaml \ -f .ci/docker-compose-file/docker-compose-python.yaml \ @@ -118,8 +117,8 @@ jobs: --set image.pullPolicy=Never \ --set emqxAclConfig="" \ --set image.pullPolicy=Never \ - --set emqxConfig.EMQX_ZONE__EXTERNAL__RETRY_INTERVAL=2s \ - --set emqxConfig.EMQX_MQTT__MAX_TOPIC_ALIAS=10 \ + --set emqxConfig.EMQX_ZONES__DEFAULT__MQTT__RETRY_INTERVAL=2s \ + --set emqxConfig.EMQX_ZONES__DEFAULT__MQTT__MAX_TOPIC_ALIAS=10 \ deploy/charts/emqx \ --debug diff --git a/apps/emqx/etc/emqx.conf b/apps/emqx/etc/emqx.conf index 7e1eedbab..58fd05810 100644 --- a/apps/emqx/etc/emqx.conf +++ b/apps/emqx/etc/emqx.conf @@ -1,525 +1,467 @@ -## EMQ X Configuration 5.0 +## master-88df1713 -##-------------------------------------------------------------------- -## Cluster -##-------------------------------------------------------------------- -cluster: { - ## Cluster name. - ## - ## Value: String - name: emqxcl +## NOTE: The configurations in this file will be overridden by +## `/data/emqx_overrides.conf` - ## Cluster auto-discovery strategy. - ## - ## Value: Enum - ## - manual: Manual join command - ## - static: Static node list - ## - mcast: IP Multicast - ## - dns: DNS A Record - ## - etcd: etcd - ## - k8s: Kubernetes - ## - ## Default: manual - discovery: manual - - ## Autoclean down node. A down node will be removed from the cluster - ## if this value > 0. - ## - ## Value: Duration - ## -h: hour, e.g. '2h' for 2 hours - ## -m: minute, e.g. '5m' for 5 minutes - ## -s: second, e.g. '30s' for 30 seconds - ## - ## Default: 5m - autoclean: "5m" - - ## Enable cluster autoheal from network partition. - ## - ## Value: on | off - ## - ## Default: on - autoheal: on - - ## Specify the erlang distributed protocol. - ## - ## Value: Enum - ## - inet_tcp: the default; handles TCP streams with IPv4 addressing. - ## - inet6_tcp: handles TCP with IPv6 addressing. - ## - inet_tls: using TLS for Erlang Distribution. - ## - ## vm.args: -proto_dist inet_tcp - proto_dist: inet_tcp - - ## Cluster using static node list - static: { - ## Node list of the cluster. - ## - ## Value: String - ## seeds: "emqx1@127.0.0.1,emqx2@127.0.0.1" - } - - ##-------------------------------------------------------------------- - ## Cluster using IP Multicast. - mcast: { - ## IP Multicast Address. - ## - ## Value: IP Address - ## addr: "239.192.0.1" - - ## Multicast Ports. - ## - ## Value: Port List - ## ports: "4369,4370" - - ## Multicast Iface. - ## - ## Value: Iface Address - ## - ## Default: "0.0.0.0" - ## iface: "0.0.0.0" - - ## Multicast Ttl. - ## - ## Value: 0-255 - ## ttl: 255 - - ## Multicast loop. - ## - ## Value: on | off - ## loop: on - } - - ##-------------------------------------------------------------------- - ## Cluster using DNS A records. - dns: { - ## DNS name. - ## - ## Value: String - ## name: localhost - - ## The App name is used to build 'node.name' with IP address. - ## - ## Value: String - ## app: emqx - } - - ##-------------------------------------------------------------------- - ## Cluster using etcd - etcd: { - ## Etcd server list, seperated by ','. - ## - ## Value: String - ## server: "http://127.0.0.1:2379" - - ## Etcd api version - ## - ## Value: Enum - ## - v2 - ## - v3 - ## version: v3 - - ## The prefix helps build nodes path in etcd. Each node in the cluster - ## will create a path in etcd: v2/keys/// - ## - ## Value: String - ## prefix: emqxcl - - ## The TTL for node's path in etcd. - ## - ## Value: Duration - ## - ## Default: 1m, 1 minute - ## node_ttl: 1m - - ## ssl:{ - ## Path to a file containing the client's private PEM-encoded key. - ## Value: File - ## keyfile: "{{ platform_etc_dir }}/certs/client-key.pem" - - ## The path to a file containing the client's certificate. - ## Value: File - ## certfile: "{{ platform_etc_dir }}/certs/client.pem" - - ## Path to the file containing PEM-encoded CA certificates. The CA certificates - ## are used during server authentication and when building the client certificate chain. - ## Value: File - ## cacertfile: "{{ platform_etc_dir }}/certs/ca.pem" - # } - } - ##-------------------------------------------------------------------- - ## Cluster using Kubernetes - k8s: { - # Kubernetes API server list, seperated by ','. - # Value: String - # apiserver: "http://10.110.111.204:8080" - - # The service name helps lookup EMQ nodes in the cluster. - # Value: String - # service_name: emqx - - # The address type is used to extract host from k8s service. - # Value: ip | dns | hostname - # address_type: ip - - # The app name helps build 'node.name'. - # Value: String - # app_name: emqx - - # The suffix added to dns and hostname get from k8s service - # Value: String - # suffix: pod.cluster.local - - # Kubernetes Namespace - # Value: String - # namespace: default - } - db_backend: mnesia - rlog: { - # role: core - # core_nodes: [] - } - -} - -##-------------------------------------------------------------------- +##================================================================== ## Node -##-------------------------------------------------------------------- -node: { - ## Node name. - ## - ## See: http://erlang.org/doc/reference_manual/distributed.html - ## - ## Value: @ - ## - ## Default: emqx@127.0.0.1 - name: "emqx@127.0.0.1" +##================================================================== +node { + ## Node name. + ## See: http://erlang.org/doc/reference_manual/distributed.html + ## + ## @doc node.name + ## ValueType: NodeName + ## Default: emqx@127.0.0.1 + name: "emqx@127.0.0.1" - ## Cookie for distributed node communication. - ## - ## Value: String - cookie: "emqxsecretcookie" + ## Cookie for distributed node communication. + ## + ## @doc node.cookie + ## ValueType: String + ## Default: emqxsecretcookie + cookie: emqxsecretcookie - ## Data dir for the node - ## - ## Value: Folder - data_dir: "{{ platform_data_dir }}" + ## Data dir for the node + ## + ## @doc node.data_dir + ## ValueType: Folder + ## Default: "{{ platform_data_dir }}/" + data_dir: "{{ platform_data_dir }}/" - ## The config file dir for the node - ## - ## Value: Folder - etc_dir: "{{ platform_etc_dir }}" + ## Dir of crash dump file. + ## + ## @doc node.crash_dump_dir + ## ValueType: Folder + ## Default: "{{ platform_log_dir }}/" + crash_dump_dir: "{{ platform_log_dir }}/" - ## Heartbeat monitoring of an Erlang runtime system. Comment the line to disable - ## heartbeat, or set the value as 'on' - ## - ## Value: on - ## - ## vm.args: -heart - ## heartbeat: on + ## Global GC Interval. + ## + ## @doc node.global_gc_interval + ## ValueType: Duration + ## Default: 15m + global_gc_interval: 15m - ## Sets the number of threads in async thread pool. Valid range is 0-1024. - ## - ## See: http://erlang.org/doc/man/erl.html - ## - ## Value: 0-1024 - ## - ## vm.args: +A Number - ## async_threads: 4 + ## Sets the net_kernel tick time in seconds. + ## Notice that all communicating nodes are to have the same + ## TickTime value specified. + ## + ## See: http://www.erlang.org/doc/man/kernel_app.html#net_ticktime + ## + ## @doc node.dist_net_ticktime + ## ValueType: Number + ## Default: 15m + dist_net_ticktime: 120 - ## Sets the maximum number of simultaneously existing processes for this - ## system if a Number is passed as value. - ## - ## See: http://erlang.org/doc/man/erl.html - ## - ## Value: Number [1024-134217727] - ## - ## vm.args: +P Number - ## process_limit: 2097152 + ## Sets the port range for the listener socket of a distributed + ## Erlang node. + ## Note that if there are firewalls between clustered nodes, this + ## port segment for nodes’ communication should be allowed. + ## + ## See: http://www.erlang.org/doc/man/kernel_app.html + ## + ## @doc node.dist_listen_min + ## ValueType: Integer + ## Range: [1024,65535] + ## Default: 6369 + dist_listen_min: 6369 - ## Sets the maximum number of simultaneously existing ports for this system. - ## - ## See: http://erlang.org/doc/man/erl.html - ## - ## Value: Number [1024-134217727] - ## - ## vm.args: +Q Number - ## max_ports: 1048576 + ## Sets the port range for the listener socket of a distributed + ## Erlang node. + ## Note that if there are firewalls between clustered nodes, this + ## port segment for nodes’ communication should be allowed. + ## + ## See: http://www.erlang.org/doc/man/kernel_app.html + ## + ## @doc node.dist_listen_max + ## ValueType: Integer + ## Range: [1024,65535] + ## Default: 6369 + dist_listen_max: 6369 - ## Sets the distribution buffer busy limit (dist_buf_busy_limit). - ## - ## See: http://erlang.org/doc/man/erl.html - ## - ## Value: Number [1KB-2GB] - ## - ## vm.args: +zdbbl size - ## dist_buffer_size: 8MB + ## Sets the maximum depth of call stack back-traces in the exit + ## reason element of 'EXIT' tuples. + ## The flag also limits the stacktrace depth returned by + ## process_info item current_stacktrace. + ## + ## @doc node.backtrace_depth + ## ValueType: Integer + ## Range: [0,1024] + ## Default: 16 + backtrace_depth: 16 - ## Sets the maximum number of ETS tables. Note that mnesia and SSL will - ## create temporary ETS tables. - ## - ## Value: Number - ## - ## vm.args: +e Number - ## max_ets_tables: 262144 - - ## Global GC Interval. - ## - ## Value: Duration - ## - ## Examples: - ## - 2h: 2 hours - ## - 30m: 30 minutes - ## - 20s: 20 seconds - ## - ## Defaut: 15 minutes - global_gc_interval: 15m - - ## Tweak GC to run more often. - ## - ## Value: Number [0-65535] - ## - ## vm.args: -env ERL_FULLSWEEP_AFTER Number - ## fullsweep_after: 1000 - - ## Crash dump log file. - ## - ## Value: Log file - crash_dump: "{{ platform_log_dir }}/crash.dump" - - ## Specify SSL Options in the file if using SSL for Erlang Distribution. - ## - ## Value: File - ## - ## vm.args: -ssl_dist_optfile - ## node.ssl_dist_optfile: "{{ platform_etc_dir }}/ssl_dist.conf" - - ## Sets the net_kernel tick time. TickTime is specified in seconds. - ## Notice that all communicating nodes are to have the same TickTime - ## value specified. - ## - ## See: http://www.erlang.org/doc/man/kernel_app.html#net_ticktime - ## - ## Value: Number - ## - ## vm.args: -kernel net_ticktime Number - ## dist_net_ticktime: 120 - - ## Sets the port range for the listener socket of a distributed Erlang node. - ## Note that if there are firewalls between clustered nodes, this port segment - ## for nodes’ communication should be allowed. - ## - ## See: http://www.erlang.org/doc/man/kernel_app.html - ## - ## Value: Port [1024-65535] - dist_listen_min: 6369 - dist_listen_max: 6369 - backtrace_depth: 16 } -##-------------------------------------------------------------------- -## RPC -##-------------------------------------------------------------------- +##================================================================== +## Cluster +##================================================================== +cluster { + ## Cluster name. + ## + ## @doc cluster.name + ## ValueType: String + ## Default: emqxcl + name: emqxcl -rpc: { - ## RPC Mode. + ## Enable cluster autoheal from network partition. ## - ## Value: sync | async - mode: async + ## @doc cluster.autoheal + ## ValueType: Boolean + ## Default: true + autoheal: true - ## Max batch size of async RPC requests. + ## Autoclean down node. A down node will be removed from the cluster + ## if this value > 0. ## - ## Value: Integer - ## Zero or negative value disables rpc batching. - ## - ## NOTE: RPC batch won't work when rpc.mode: sync - async_batch_size: 256 + ## @doc cluster.autoclean + ## ValueType: Duration + ## Default: 5m + autoclean: 5m - ## RPC port discovery + ## Node discovery strategy to join the cluster. ## - ## The strategy for discovering the RPC listening port of other nodes. + ## @doc cluster.discovery_strategy + ## ValueType: manual | static | mcast | dns | etcd | k8s + ## - manual: Manual join command + ## - static: Static node list + ## - mcast: IP Multicast + ## - dns: DNS A Record + ## - etcd: etcd + ## - k8s: Kubernetes ## - ## Value: Enum - ## - manual: discover ports by `tcp_server_port` and `tcp_client_port`. - ## - stateless: discover ports in a stateless manner. - ## If node name is `emqx@127.0.0.1`, where the `` is an integer, - ## then the listening port will be `5370 + ` - ## - ## Defaults to `stateless`. - port_discovery: stateless + ## Default: manual + discovery_strategy: manual - ## TCP port number for RPC server to listen on. - ## - ## Only takes effect when `rpc.port_discovery`: `manual`. - ## - ## NOTE: All nodes in the cluster should agree to this same config. - ## - ## Value: Port [1024-65535] - ## tcp_server_port: 5369 + ##---------------------------------------------------------------- + ## Cluster using static node list + ##---------------------------------------------------------------- + static { + ## Node list of the cluster + ## + ## @doc cluster.static.seeds + ## ValueType: Array + ## Default: ["emqx1@127.0.0.1", "emqx2@127.0.0.1"] + seeds: ["emqx1@127.0.0.1", "emqx2@127.0.0.1"] + } - ## Number of outgoing RPC connections. - ## - ## Value: Interger [0-256] - ## Default: 1 - ## tcp_client_num: 1 + ##---------------------------------------------------------------- + ## Cluster using IP Multicast + ##---------------------------------------------------------------- + mcast { + ## IP Multicast Address. + ## + ## @doc cluster.mcast.addr + ## ValueType: IPAddress + ## Default: "239.192.0.1" + addr: "239.192.0.1" - ## RCP Client connect timeout. - ## - ## Value: Seconds - connect_timeout: 5s + ## Multicast Ports. + ## + ## @doc cluster.mcast.ports + ## ValueType: Array + ## Default: [4369, 4370] + ports: [4369, 4370] - ## TCP send timeout of RPC client and server. - ## - ## Value: Seconds - send_timeout: 5s + ## Multicast Iface. + ## + ## @doc cluster.mcast.iface + ## ValueType: IPAddress + ## Default: "0.0.0.0" + iface: "0.0.0.0" - ## Authentication timeout - ## - ## Value: Seconds - authentication_timeout: 5s + ## Multicast Ttl. + ## + ## @doc cluster.mcast.ttl + ## ValueType: Integer + ## Range: [0,255] + ## Default: 255 + ttl: 255 - ## Default receive timeout for call() functions - ## - ## Value: Seconds - call_receive_timeout: 15s + ## Multicast loop. + ## + ## @doc cluster.mcast.loop + ## ValueType: Boolean + ## Default: true + loop: true + } - ## Socket idle keepalive. - ## - ## Value: Seconds - socket_keepalive_idle: 900s + ##---------------------------------------------------------------- + ## Cluster using DNS A records + ##---------------------------------------------------------------- + dns { + ## DNS name. + ## + ## @doc cluster.dns.name + ## ValueType: String + ## Default: localhost + name: localhost - ## TCP Keepalive probes interval. - ## - ## Value: Seconds - socket_keepalive_interval: 75s + ## The App name is used to build 'node.name' with IP address. + ## + ## @doc cluster.dns.app + ## ValueType: String + ## Default: emqx + app: emqx + } - ## Probes lost to close the connection - ## - ## Value: Integer - socket_keepalive_count: 9 + ##---------------------------------------------------------------- + ## Cluster using etcd + ##---------------------------------------------------------------- + etcd { + ## Etcd server list, seperated by ','. + ## + ## @doc cluster.etcd.server + ## ValueType: URL + ## Required: true + server: "http://127.0.0.1:2379" - ## Size of TCP send buffer. - ## - ## Value: Bytes - socket_sndbuf: 1MB + ## The prefix helps build nodes path in etcd. Each node in the cluster + ## will create a path in etcd: v2/keys/// + ## + ## @doc cluster.etcd.prefix + ## ValueType: String + ## Default: emqxcl + prefix: emqxcl - ## Size of TCP receive buffer. - ## - ## Value: Seconds - socket_recbuf: 1MB + ## The TTL for node's path in etcd. + ## + ## @doc cluster.etcd.node_ttl + ## ValueType: Duration + ## Default: 1m + node_ttl: 1m + + ## Path to the file containing the user's private PEM-encoded key. + ## + ## @doc cluster.etcd.ssl.keyfile + ## ValueType: File + ## Default: "{{ platform_etc_dir }}/certs/key.pem" + ssl.keyfile: "{{ platform_etc_dir }}/certs/key.pem" + + ## Path to a file containing the user certificate. + ## + ## @doc cluster.etcd.ssl.certfile + ## ValueType: File + ## Default: "{{ platform_etc_dir }}/certs/cert.pem" + ssl.certfile: "{{ platform_etc_dir }}/certs/cert.pem" + + ## Path to the file containing PEM-encoded CA certificates. The CA certificates + ## are used during server authentication and when building the client certificate chain. + ## + ## @doc cluster.etcd.ssl.cacertfile + ## ValueType: File + ## Default: "{{ platform_etc_dir }}/certs/cacert.pem" + ssl.cacertfile: "{{ platform_etc_dir }}/certs/cacert.pem" + } + + ##---------------------------------------------------------------- + ## Cluster using Kubernetes + ##---------------------------------------------------------------- + k8s { + ## Kubernetes API server list, seperated by ','. + ## + ## @doc cluster.k8s.apiserver + ## ValueType: URL + ## Required: true + apiserver: "http://10.110.111.204:8080" + + ## The service name helps lookup EMQ nodes in the cluster. + ## + ## @doc cluster.k8s.service_name + ## ValueType: String + ## Default: emqx + service_name: emqx + + ## The address type is used to extract host from k8s service. + ## + ## @doc cluster.k8s.address_type + ## ValueType: ip | dns | hostname + ## Default: ip + address_type: ip + + ## The app name helps build 'node.name'. + ## + ## @doc cluster.k8s.app_name + ## ValueType: String + ## Default: emqx + app_name: emqx + + ## The suffix added to dns and hostname get from k8s service + ## + ## @doc cluster.k8s.suffix + ## ValueType: String + ## Default: "pod.local" + suffix: "pod.local" + + ## Kubernetes Namespace + ## + ## @doc cluster.k8s.namespace + ## ValueType: String + ## Default: default + namespace: default + } + + db_backend: mnesia + + rlog: { + # role: core + # core_nodes: [] + } - ## Size of user-level software socket buffer. - ## - ## Value: Seconds - socket_buffer: 1MB } -##-------------------------------------------------------------------- +##================================================================== ## Log -##-------------------------------------------------------------------- - -log: { - ## Where to emit the logs. - ## Enable the console (standard output) logs. +##================================================================== +log { + ## The primary log level ## - ## Value: file | console | both - ## - file: write logs only to file - ## - console: write logs only to standard I/O - ## - both: write logs both to file and standard I/O - to: file - - ## The log severity level. + ## - all the log messages with levels lower than this level will + ## be dropped. + ## - all the log messages with levels higher than this level will + ## go into the log handlers. The handlers then decide to log it + ## out or drop it according to the level setting of the handler. ## - ## Value: debug | info | notice | warning | error | critical | alert | emergency - ## - ## Note: Only the messages with severity level higher than or equal to - ## this level will be logged. + ## Note: Only the messages with severity level higher than or + ## equal to this level will be logged. ## + ## @doc log.primary_level + ## ValueType: debug | info | notice | warning | error | critical | alert | emergency ## Default: warning - level: warning + primary_level: warning + + ##---------------------------------------------------------------- + ## The console log handler send log messages to emqx console + ##---------------------------------------------------------------- + ## Log to single line + ## @doc log.console_handler.enable + ## ValueType: Boolean + ## Default: false + console_handler.enable: false + + ## The log level of this handler + ## All the log messages with levels lower than this level will + ## be dropped. + ## + ## @doc log.console_handler.level + ## ValueType: debug | info | notice | warning | error | critical | alert | emergency + ## Default: warning + console_handler.level: warning + + ##---------------------------------------------------------------- + ## The file log handlers send log messages to files + ##---------------------------------------------------------------- + ## file_handlers. + file_handlers.emqx_log: { + ## The log level filter of this handler + ## All the log messages with levels lower than this level will + ## be dropped. + ## + ## @doc log.file_handlers..level + ## ValueType: debug | info | notice | warning | error | critical | alert | emergency + ## Default: warning + level: warning + + ## The log file for specified level. + ## + ## If `rotation` is disabled, this is the file of the log files. + ## + ## If `rotation` is enabled, this is the base name of the files. + ## Each file in a rotated log is named .N, where N is an integer. + ## + ## Note: Log files for a specific log level will only contain all the logs + ## that higher than or equal to that level + ## + ## @doc log.file_handlers..file + ## ValueType: File + ## Required: true + file: "{{ platform_log_dir }}/emqx.log" + + ## Enables the log rotation. + ## With this enabled, new log files will be created when the current + ## log file is full, max to `rotation_count` files will be created. + ## + ## @doc log.file_handlers..rotation.enable + ## ValueType: Boolean + ## Default: true + rotation.enable: true + + ## Maximum rotation count of log files. + ## + ## @doc log.file_handlers..rotation.count + ## ValueType: Integer + ## Range: [1, 2048] + ## Default: 10 + rotation.count: 10 + + ## Maximum size of each log file. + ## + ## If the max_size reached and `rotation` is disabled, the handler + ## will stop sending log messages, if the `rotation` is enabled, + ## the file rotates. + ## + ## @doc log.file_handlers..max_size + ## ValueType: Size | infinity + ## Default: 10MB + max_size: 10MB + } + + ## file_handlers. + ## + ## You could also create multiple file handlers for different + ## log level for example: + file_handlers.emqx_error_log: { + level: error + file: "{{ platform_log_dir }}/error.log" + } ## Timezone offset to display in logs - ## Value: + ## + ## @doc log.time_offset + ## ValueType: system | utc | String ## - "system" use system zone ## - "utc" for Universal Coordinated Time (UTC) ## - "+hh:mm" or "-hh:mm" for a specified offset + ## Default: system time_offset: system - ## The dir for log files. - ## - ## Value: Folder - dir: "{{ platform_log_dir }}" - - ## The log filename for logs of level specified in "log.level". - ## - ## If `log.rotation` is enabled, this is the base name of the - ## files. Each file in a rotated log is named .N, where N is an integer. - ## - ## Value: String - ## Default: emqx.log - file: emqx.log - ## Limits the total number of characters printed for each log event. ## - ## Value: Integer - ## Default: No Limit - ## chars_limit: 8192 + ## @doc log.chars_limit + ## ValueType: Integer | infinity + ## Default: infinity + chars_limit: 8192 ## Maximum depth for Erlang term log formatting ## and Erlang process message queue inspection. ## - ## Value: Integer or 'unlimited' (without quotes) + ## @doc log.max_depth + ## ValueType: Integer | infinity ## Default: 80 - ## max_depth: 80 + max_depth: 80 ## Log formatter - ## Value: text | json - ## formatter: text + ## @doc log.formatter + ## ValueType: text | json + ## Default: text + formatter: text ## Log to single line - ## Value: Boolean - ## single_line: true - - ## Enables the log rotation. - ## With this enabled, new log files will be created when the current - ## log file is full, max to `rotation.size` files will be created. - ## - ## Value: on | off - ## Default: on - rotation.enable: on - - ## Maximum size of each log file. - ## - ## Value: Number - ## Default: 10M - ## Supported Unit: KB | MB | GB - rotation.size: 10MB - - ## Maximum rotation count of log files. - ## - ## Value: Number - ## Default: 5 - rotation.count: 5 - - ## To create additional log files for specific log levels. - ## - ## Value: File Name - ## Format: log.$level.file: $filename, - ## where "$level" can be one of: debug, info, notice, warning, - ## error, critical, alert, emergency - ## Note: Log files for a specific log level will only contain all the logs - ## that higher than or equal to that level - ## - ## info.file: info.log - ## error.file: error.log + ## @doc log.single_line + ## ValueType: Boolean + ## Default: true + single_line: true ## The max allowed queue length before switching to sync mode. ## ## Log overload protection parameter. If the message queue grows ## larger than this value the handler switches from anync to sync mode. ## + ## @doc log.sync_mode_qlen + ## ValueType: Integer + ## Range: [0, ${log.drop_mode_qlen}] ## Default: 100 - ## - ## sync_mode_qlen: 100 + sync_mode_qlen: 100 ## The max allowed queue length before switching to drop mode. ## @@ -527,9 +469,11 @@ log: { ## larger than this threshold, the handler switches to a mode in which ## it drops all new events that senders want to log. ## + ## @doc log.drop_mode_qlen + ## ValueType: Integer + ## Range: [${log.sync_mode_qlen}, ${log.flush_qlen}] ## Default: 3000 - ## - ## drop_mode_qlen: 3000 + drop_mode_qlen: 3000 ## The max allowed queue length before switching to flush mode. ## @@ -538,9 +482,11 @@ log: { ## To flush events, the handler discards the messages in the message queue ## by receiving them in a loop without logging. ## + ## @doc log.flush_qlen + ## ValueType: Integer + ## Range: [${log.drop_mode_qlen}, ) ## Default: 8000 - ## - ## flush_qlen: 8000 + flush_qlen: 8000 ## Kill the log handler when it gets overloaded. ## @@ -550,9 +496,10 @@ log: { ## We could kill the log handler in these cases and restart it after a ## few seconds. ## - ## Default: on - ## - ## overload_kill: on + ## @doc log.overload_kill.enable + ## ValueType: Boolean + ## Default: true + overload_kill.enable: true ## The max allowed queue length before killing the log hanlder. ## @@ -560,9 +507,11 @@ log: { ## length. If the message queue grows larger than this, the handler ## process is terminated. ## + ## @doc log.overload_kill.qlen + ## ValueType: Integer + ## Range: [0, 1048576] ## Default: 20000 - ## - ## overload_kill_qlen: 20000 + overload_kill.qlen: 20000 ## The max allowed memory size before killing the log hanlder. ## @@ -570,21 +519,22 @@ log: { ## that the handler process is allowed to use. If the handler grows ## larger than this, the process is terminated. ## + ## @doc log.overload_kill.mem_size + ## ValueType: Size ## Default: 30MB - ## - ## overload_kill_mem_size: 30MB + overload_kill.mem_size: 30MB ## Restart the log hanlder after some seconds. ## ## Log overload protection parameter. If the handler is terminated, ## it restarts automatically after a delay specified in seconds. - ## The value "infinity" prevents restarts. ## + ## @doc log.overload_kill.restart_after + ## ValueType: Duration ## Default: 5s - ## - ## overload_kill_restart_after: 5s + overload_kill.restart_after: 5s - ## Max burst count and time window for burst control. + ## Controlling Bursts of Log Requests. ## ## Log overload protection parameter. Large bursts of log events - many ## events received by the handler under a short period of time - can @@ -592,842 +542,1654 @@ log: { ## to be handled within a certain time frame, the handler can avoid ## choking the log with massive amounts of printouts. ## - ## This config controls the maximum number of events to handle within - ## a time frame. After the limit is reached, successive events are - ## dropped until the end of the time frame. - ## ## Note that there would be no warning if any messages were ## dropped because of burst control. ## - ## Comment this config out to disable the burst control feature. + ## @doc log.burst_limit.enable + ## ValueType: Boolean + ## Default: false + burst_limit.enable: false + + ## This config controls the maximum number of events to handle within + ## a time frame. After the limit is reached, successive events are + ## dropped until the end of the time frame defined by `window_time`. ## - ## Value: MaxBurstCount,TimeWindow - ## Default: disabled + ## @doc log.burst_limit.max_count + ## ValueType: Integer + ## Default: 10000 + burst_limit.max_count: 10000 + + ## See the previous description of burst_limit_max_count. ## - ## burst_limit: "20000, 1s" + ## @doc log.burst_limit.window_time + ## ValueType: duration + ## Default: 1s + burst_limit.window_time: 1s } -##-------------------------------------------------------------------- -## Authentication/Access Control -##-------------------------------------------------------------------- -acl: { - ## Allow anonymous authentication by default if no auth plugins loaded. - ## Notice: Disable the option in production deployment! - ## - ## Value: true | false - allow_anonymous: true +##================================================================== +## RPC +##================================================================== +rpc { + ## RPC Mode. + ## + ## @doc rpc.mode + ## ValueType: sync | async + ## Default: async + mode: async - ## Allow or deny if no ACL rules matched. - ## - ## Value: allow | deny - acl_nomatch: allow + ## Max batch size of async RPC requests. + ## + ## NOTE: RPC batch won't work when rpc.mode = sync + ## Zero value disables rpc batching. + ## + ## @doc rpc.async_batch_size + ## ValueType: Integer + ## Range: [0, 1048576] + ## Default: 0 + async_batch_size: 256 - ## Default ACL File. + ## RPC port discovery + ## + ## The strategy for discovering the RPC listening port of + ## other nodes. + ## + ## @doc cluster.discovery_strategy + ## ValueType: manual | stateless + ## - manual: discover ports by `tcp_server_port`. + ## - stateless: discover ports in a stateless manner. + ## If node name is `emqx@127.0.0.1`, where the `` is + ## an integer, then the listening port will be `5370 + ` + ## + ## Default: `stateless`. + port_discovery: stateless + + ## TCP server port for RPC. + ## + ## Only takes effect when `rpc.port_discovery` = `manual`. + ## + ## @doc rpc.tcp_server_port + ## ValueType: Integer + ## Range: [1024-65535] + ## Defaults: 5369 + tcp_server_port: 5369 + + ## Number of outgoing RPC connections. + ## + ## Set this to 1 to keep the message order sent from the same + ## client. + ## + ## @doc rpc.tcp_client_num + ## ValueType: Integer + ## Range: [1, 256] + ## Defaults: 1 + tcp_client_num: 1 + + ## RCP Client connect timeout. + ## + ## @doc rpc.connect_timeout + ## ValueType: Duration + ## Default: 5s + connect_timeout: 5s + + ## TCP send timeout of RPC client and server. + ## + ## @doc rpc.send_timeout + ## ValueType: Duration + ## Default: 5s + send_timeout: 5s + + ## Authentication timeout + ## + ## @doc rpc.authentication_timeout + ## ValueType: Duration + ## Default: 5s + authentication_timeout: 5s + + ## Default receive timeout for call() functions + ## + ## @doc rpc.call_receive_timeout + ## ValueType: Duration + ## Default: 15s + call_receive_timeout: 15s + + ## Socket idle keepalive. + ## + ## @doc rpc.socket_keepalive_idle + ## ValueType: Duration + ## Default: 900s + socket_keepalive_idle: 900s + + ## TCP Keepalive probes interval. + ## + ## @doc rpc.socket_keepalive_interval + ## ValueType: Duration + ## Default: 75s + socket_keepalive_interval: 75s + + ## Probes lost to close the connection + ## + ## @doc rpc.socket_keepalive_count + ## ValueType: Integer + ## Default: 9 + socket_keepalive_count: 9 + + ## Size of TCP send buffer. + ## + ## @doc rpc.socket_sndbuf + ## ValueType: Size + ## Default: 1MB + socket_sndbuf: 1MB + + ## Size of TCP receive buffer. + ## + ## @doc rpc.socket_recbuf + ## ValueType: Size + ## Default: 1MB + socket_recbuf: 1MB + + ## Size of user-level software socket buffer. + ## + ## @doc rpc.socket_buffer + ## ValueType: Size + ## Default: 1MB + socket_buffer: 1MB +} + +##================================================================== +## Broker +##================================================================== +broker { + ## System interval of publishing $SYS messages. + ## + ## @doc broker.sys_msg_interval + ## ValueType: Duration | disabled + ## Default: 1m + sys_msg_interval: 1m + + ## System heartbeat interval of publishing following heart beat message: + ## - "$SYS/brokers//uptime" + ## - "$SYS/brokers//datetime" + ## + ## @doc broker.sys_heartbeat_interval + ## ValueType: Duration + ## Default: 30s | disabled + sys_heartbeat_interval: 30s + + ## Session locking strategy in a cluster. + ## + ## @doc broker.session_locking_strategy + ## ValueType: local | one | quorum | all + ## - local: only lock the session locally on the current node + ## - one: select only one remove node to lock the session + ## - quorum: select some nodes to lock the session + ## - all: lock the session on all of the nodes in the cluster + ## Default: quorum + session_locking_strategy: quorum + + ## Dispatch strategy for shared subscription + ## + ## @doc broker.shared_subscription_strategy + ## ValueType: random | round_robin | sticky | hash + ## - random: dispatch the message to a random selected subscriber + ## - round_robin: select the subscribers in a round-robin manner + ## - sticky: always use the last selected subscriber to dispatch, + ## until the susbcriber disconnected. + ## - hash: select the subscribers by the hash of clientIds + ## Default: round_robin + shared_subscription_strategy: round_robin + + ## Enable/disable shared dispatch acknowledgement for QoS1 and QoS2 messages + ## This should allow messages to be dispatched to a different subscriber in + ## the group in case the picked (based on shared_subscription_strategy) one # is offline + ## + ## @doc broker.shared_dispatch_ack_enabled + ## ValueType: Boolean + ## Default: false + shared_dispatch_ack_enabled: false + + ## Enable batch clean for deleted routes. + ## + ## @doc broker.route_batch_clean + ## ValueType: Boolean + ## Default: true + route_batch_clean: true + + ## Performance toggle for subscribe/unsubscribe wildcard topic. + ## Change this toggle only when there are many wildcard topics. + ## + ## NOTE: when changing from/to 'global' lock, it requires all + ## nodes in the cluster to be stopped before the change. + ## + ## @doc broker.perf.route_lock_type + ## ValueType: key | tab | global + ## - key: mnesia translational updates with per-key locks. recommended for single node setup. + ## - tab: mnesia translational updates with table lock. recommended for multi-nodes setup. + ## - global: global lock protected updates. recommended for larger cluster. + ## Default: key + perf.route_lock_type: key + + ## Enable trie path compaction. + ## Enabling it significantly improves wildcard topic subscribe + ## rate, if wildcard topics have unique prefixes like: + ## 'sensor/{{id}}/+/', where ID is unique per subscriber. + ## + ## Topic match performance (when publishing) may degrade if messages + ## are mostly published to topics with large number of levels. + ## + ## NOTE: This is a cluster-wide configuration. + ## It rquires all nodes to be stopped before changing it. + ## + ## @doc broker.perf.trie_compaction + ## ValueType: Boolean + ## Default: true + perf.trie_compaction: true +} + +##================================================================== +## Zones and Listeners +##================================================================== +## A zone contains a set of configurations for listeners. +## +## The configurations defined in zone can be overridden by the ones +## defined in listeners with the same key. +## +## For example given the following config: +## ``` +## +## zone.x { +## a: {b:1, c: 1} +## listeners.y { +## a: {b: 2} +## } +## } +## ``` +## The config "a" in zone "x" is overridden by the configs inside +## the listener "y". So the value of config "a" in listener "y" +## is `a: {b:2, c: 1}`. +## +## All the configs that can be set in zones and be overridden in listenser are: +## - `auth.*` +## - `stats.*` +## - `mqtt.*` +## - `acl.*` +## - `flapping_detect.*` +## - `force_shutdown.*` +## - `conn_congestion.*` +## +## Syntax: zones. {} +zones.default { + ## Enable authentication + ## + ## @doc zones..auth.enable + ## ValueType: Boolean + ## Default: false + auth.enable: false + + ## Enable per connection statistics. + ## + ## @doc zones..stats.enable + ## ValueType: Boolean + ## Default: true + stats.enable: true + + ## Maximum number of concurrent connections in this zone. + ## + ## This value must be larger than the sum of `max_connections` set + ## in the listeners under this zone. + ## + ## @doc zones..overall_max_connections + ## ValueType: Number | infinity + ## Default: infinity + overall_max_connections: infinity + + mqtt { + ## When publishing or subscribing, prefix all topics with a mountpoint string. + ## The prefixed string will be removed from the topic name when the message + ## is delivered to the subscriber. The mountpoint is a way that users can use + ## to implement isolation of message routing between different listeners. ## - ## Value: File Name - acl_file: "{{ platform_etc_dir }}/acl.conf" + ## For example if a clientA subscribes to "t" with `zones..mqtt.mountpoint` + ## set to "some_tenant", then the client accually subscribes to the topic + ## "some_tenant/t". Similarly if another clientB (connected to the same listener + ## with the clientA) send a message to topic "t", the message is accually route + ## to all the clients subscribed "some_tenant/t", so clientA will receive the + ## message, with topic name "t". + ## + ## Set to "" to disable the feature. + ## + ## Variables in mountpoint string: + ## - %c: clientid + ## - %u: username + ## + ## @doc zones..listeners..mountpoint + ## ValueType: String + ## Default: "" + mountpoint: "" + + ## How long time the MQTT connection will be disconnected if the + ## TCP connection is established but MQTT CONNECT has not been + ## received. + ## + ## @doc zones..mqtt.idle_timeout + ## ValueType: Duration + ## Default: 15s + idle_timeout: 15s + + ## Maximum MQTT packet size allowed. + ## + ## @doc zones..mqtt.max_packet_size + ## ValueType: Bytes + ## Default: 1MB + max_packet_size: 1MB + + ## Maximum length of MQTT clientId allowed. + ## + ## @doc zones..mqtt.max_clientid_len + ## ValueType: Integer + ## Range: [23, 65535] + ## Default: 65535 + max_clientid_len: 65535 + + ## Maximum topic levels allowed. + ## + ## @doc zones..mqtt.max_topic_levels + ## ValueType: Integer + ## Range: [1, 65535] + ## Default: 65535 + max_topic_levels: 65535 + + ## Maximum QoS allowed. + ## + ## @doc zones..mqtt.max_qos_allowed + ## ValueType: 0 | 1 | 2 + ## Default: 2 + max_qos_allowed: 2 + + ## Maximum Topic Alias, 0 means no topic alias supported. + ## + ## @doc zones..mqtt.max_topic_alias + ## ValueType: Integer + ## Range: [0, 65535] + ## Default: 65535 + max_topic_alias: 65535 + + ## Whether the Server supports MQTT retained messages. + ## + ## @doc zones..mqtt.retain_available + ## ValueType: Boolean + ## Default: true + retain_available: true + + ## Whether the Server supports MQTT Wildcard Subscriptions + ## + ## @doc zones..mqtt.wildcard_subscription + ## ValueType: Boolean + ## Default: true + wildcard_subscription: true + + ## Whether the Server supports MQTT Shared Subscriptions. + ## + ## @doc zones..mqtt.shared_subscription + ## ValueType: Boolean + ## Default: true + shared_subscription: true + + ## Whether to ignore loop delivery of messages.(for mqtt v3.1.1) + ## + ## @doc zones..mqtt.ignore_loop_deliver + ## ValueType: Boolean + ## Default: false + ignore_loop_deliver: false + + ## Whether to parse the MQTT frame in strict mode + ## + ## @doc zones..mqtt.strict_mode + ## ValueType: Boolean + ## Default: false + strict_mode: false + + ## Specify the response information returned to the client + ## + ## This feature is disabled if not set + ## + ## @doc zones..mqtt.response_information + ## ValueType: String + ## Default: "" + response_information: "" + + ## Server Keep Alive of MQTT 5.0 + ## + ## @doc zones..mqtt.server_keepalive + ## ValueType: Number | disabled + ## Default: disabled + server_keepalive: disabled + + ## The backoff for MQTT keepalive timeout. The broker will kick a connection out + ## until 'Keepalive * backoff * 2' timeout. + ## + ## @doc zones..mqtt.keepalive_backoff + ## ValueType: Float + ## Range: (0.5, 1] + ## Default: 0.75 + keepalive_backoff: 0.75 + + ## Maximum number of subscriptions allowed. + ## + ## @doc zones..mqtt.max_subscriptions + ## ValueType: Integer | infinity + ## Range: [1, ) + ## Default: infinity + max_subscriptions: infinity + + ## Force to upgrade QoS according to subscription. + ## + ## @doc zones..mqtt.upgrade_qos + ## ValueType: Boolean + ## Default: false + upgrade_qos: false + + ## Maximum size of the Inflight Window storing QoS1/2 messages delivered but unacked. + ## + ## @doc zones..mqtt.max_inflight + ## ValueType: Integer + ## Range: [1, 65535] + ## Default: 32 + max_inflight: 32 + + ## Retry interval for QoS1/2 message delivering. + ## + ## @doc zones..mqtt.retry_interval + ## ValueType: Duration + ## Default: 30s + retry_interval: 30s + + ## Maximum QoS2 packets (Client -> Broker) awaiting PUBREL. + ## + ## @doc zones..mqtt.max_awaiting_rel + ## ValueType: Integer | infinity + ## Range: [1, ) + ## Default: 100 + max_awaiting_rel: 100 + + ## The QoS2 messages (Client -> Broker) will be dropped if awaiting PUBREL timeout. + ## + ## @doc zones..mqtt.await_rel_timeout + ## ValueType: Duration + ## Default: 300s + await_rel_timeout: 300s + + ## Default session expiry interval for MQTT V3.1.1 connections. + ## + ## @doc zones..mqtt.session_expiry_interval + ## ValueType: Duration + ## Default: 2h + session_expiry_interval: 2h + + ## Maximum queue length. Enqueued messages when persistent client disconnected, + ## or inflight window is full. + ## + ## @doc zones..mqtt.max_mqueue_len + ## ValueType: Integer | infinity + ## Range: [0, ) + ## Default: 1000 + max_mqueue_len: 1000 + + ## Topic priorities. + ## + ## There's no priority table by default, hence all messages + ## are treated equal. + ## + ## Priority number [1-255] + ## + ## NOTE: comma and equal signs are not allowed for priority topic names + ## NOTE: Messages for topics not in the priority table are treated as + ## either highest or lowest priority depending on the configured + ## value for mqtt.mqueue_default_priority + ## + ## @doc zones..mqtt.mqueue_priorities + ## ValueType: Map | disabled + ## Examples: + ## To configure "topic/1" > "topic/2": + ## mqueue_priorities: {"topic/1": 10, "topic/2": 8} + ## Default: disabled + mqueue_priorities: disabled + + ## Default to highest priority for topics not matching priority table + ## + ## @doc zones..mqtt.mqueue_default_priority + ## ValueType: highest | lowest + ## Default: lowest + mqueue_default_priority: lowest + + ## Whether to enqueue QoS0 messages. + ## + ## @doc zones..mqtt.mqueue_store_qos0 + ## ValueType: Boolean + ## Default: true + mqueue_store_qos0: true + + ## Whether use username replace client id + ## + ## @doc zones..mqtt.use_username_as_clientid + ## ValueType: Boolean + ## Default: false + use_username_as_clientid: false + + ## Use the CN, DN or CRT field from the client certificate as a username. + ## Only works for SSL connection. + ## + ## @doc zones..mqtt.peer_cert_as_username + ## ValueType: cn | dn | crt | disabled + ## Default: disabled + peer_cert_as_username: disabled + + ## Use the CN, DN or CRT field from the client certificate as a clientid. + ## Only works for SSL connection. + ## + ## @doc zones..mqtt.peer_cert_as_clientid + ## ValueType: cn | dn | crt | disabled + ## Default: disabled + peer_cert_as_clientid: disabled + + } + + acl { + + ## Enable ACL check. + ## + ## @doc zones..acl.enable + ## ValueType: Boolean + ## Default: false + enable: false + + ## The action when acl check reject current operation + ## + ## @doc zones..acl.deny_action + ## ValueType: ignore | disconnect + ## Default: ignore + deny_action: ignore ## Whether to enable ACL cache. ## ## If enabled, ACLs roles for each client will be cached in the memory ## - ## Value: on | off - enable_acl_cache: on + ## @doc zones..acl.cache.enable + ## ValueType: Boolean + ## Default: true + cache.enable: true ## The maximum count of ACL entries can be cached for a client. ## - ## Value: Integer greater than 0 + ## @doc zones..acl.cache.max_size + ## ValueType: Integer + ## Range: [0, 1048576] ## Default: 32 - acl_cache_max_size: 32 + cache.max_size: 32 ## The time after which an ACL cache entry will be deleted ## - ## Value: Duration - ## Default: 1 minute - acl_cache_ttl: 1m + ## @doc zones..acl.cache.ttl + ## ValueType: Duration + ## Default: 1m + cache.ttl: 1m + } - ## The action when acl check reject current operation + flapping_detect { + ## Enable Flapping Detection. ## - ## Value: ignore | disconnect - ## Default: ignore - acl_deny_action: ignore + ## This config controls the allowed maximum number of CONNECT received + ## from the same clientid in a time frame defined by `window_time`. + ## After the limit is reached, successive CONNECT requests are forbidden + ## (banned) until the end of the time period defined by `ban_time`. + ## + ## @doc zones..flapping_detect.enable + ## ValueType: Boolean + ## Default: true + enable: false - ## Specify the global flapping detect policy. - ## The value is a string composed of flapping threshold, duration and banned interval. - ## 1. threshold: an integer to specfify the disconnected times of a MQTT Client; - ## 2. duration: the time window for flapping detect; - ## 3. banned interval: the banned interval if a flapping is detected. + ## The max disconnect allowed of a MQTT Client in `window_time` ## - ## Value: Integer,Duration,Duration - flapping_detect_policy: "30, 1m, 5m" + ## @doc zones..flapping_detect.max_count + ## ValueType: Integer + ## Default: 15 + max_count: 15 + + ## The time window for flapping detect + ## + ## @doc zones..flapping_detect.window_time + ## ValueType: Duration + ## Default: 1m + window_time: 1m + + ## How long the clientid will be banned + ## + ## @doc zones..flapping_detect.ban_time + ## ValueType: Duration + ## Default: 5m + ban_time: 5m + + } + + force_shutdown: { + ## Enable force_shutdown + ## + ## @doc zones..force_shutdown.enable + ## ValueType: Boolean + ## Default: true + enable: true + + ## Max message queue length + ## @doc zones..force_shutdown.max_message_queue_len + ## ValueType: Integer + ## Range: (0, ) + ## Default: 1000 + max_message_queue_len: 1000 + + ## Total heap size + ## + ## @doc zones..force_shutdown.max_heap_size + ## ValueType: Size + ## Default: 32MB + max_heap_size: 32MB + } + + force_gc: { + ## Force the MQTT connection process GC after this number of + ## messages or bytes passed through. + ## + ## @doc zones..force_gc.enable + ## ValueType: Boolean + ## Default: true + enable: true + + ## GC the process after how many messages received + ## @doc zones..force_gc.max_message_queue_len + ## ValueType: Integer + ## Range: (0, ) + ## Default: 16000 + count: 16000 + + ## GC the process after how much bytes passed through + ## + ## @doc zones..force_gc.bytes + ## ValueType: Size + ## Default: 16MB + bytes: 16MB + } + + conn_congestion: { + ## Whether to alarm the congested connections. + ## + ## Sometimes the mqtt connection (usually an MQTT subscriber) may + ## get "congested" because there're too many packets to sent. + ## The socket trys to buffer the packets until the buffer is + ## full. If more packets comes after that, the packets will be + ## "pending" in a queue and we consider the connection is + ## "congested". + ## + ## Enable this to send an alarm when there's any bytes pending in + ## the queue. You could set the `sndbuf` to a larger value if the + ## alarm is triggered too often. + ## + ## The name of the alarm is of format "conn_congestion//". + ## Where the is the client-id of the congested MQTT connection. + ## And the is the username or "unknown_user" of not provided by the client. + ## + ## @doc zones..conn_congestion.enable_alarm + ## ValueType: Boolean + ## Default: true + enable_alarm: true + + ## Won't clear the congested alarm in how long time. + ## The alarm is cleared only when there're no pending bytes in + ## the queue, and also it has been `min_alarm_sustain_duration` + ## time since the last time we considered the connection is "congested". + ## + ## This is to avoid clearing and sending the alarm again too often. + ## + ## @doc zones..conn_congestion.min_alarm_sustain_duration + ## ValueType: Duration + ## Default: 1m + min_alarm_sustain_duration: 1m + } + + listeners.mqtt_tcp: + #${example_common_tcp_options} # common options can be written in a separate config entry and reference it from here. + { + + ## The type of the listener. + ## + ## @doc zones..listeners..type + ## ValueType: tcp | ws + ## - tcp: MQTT over TCP + ## - ws: MQTT over Websocket + ## - quic: MQTT over QUIC + ## Required: true + type: tcp + + ## The IP address and port that the listener will bind. + ## + ## @doc zones..listeners..bind + ## ValueType: IPAddress | Port | IPAddrPort + ## Required: true + ## Examples: 1883, 127.0.0.1:1883, ::1:1883 + bind: "0.0.0.0:1883" + + ## The size of the acceptor pool for this listener. + ## + ## @doc zones..listeners..acceptors + ## ValueType: Number + ## Default: 16 + acceptors: 16 + + ## Maximum number of concurrent connections. + ## + ## @doc zones..listeners..max_connections + ## ValueType: Number | infinity + ## Default: infinity + max_connections: 1024000 + + ## The access control rules for this listener. + ## + ## See: https://github.com/emqtt/esockd#allowdeny + ## + ## @doc zones..listeners..access_rules + ## ValueType: Array + ## Default: [] + ## Examples: + ## access_rules: [ + ## "deny 192.168.0.0/24", + ## "all all" + ## ] + access_rules: [ + "allow all" + ] + + ## Enable the Proxy Protocol V1/2 if the EMQ X cluster is deployed + ## behind HAProxy or Nginx. + ## + ## See: https://www.haproxy.com/blog/haproxy/proxy-protocol/ + ## + ## @doc zones..listeners..proxy_protocol + ## ValueType: Boolean + ## Default: false + proxy_protocol: false + + ## Sets the timeout for proxy protocol. EMQ X will close the TCP connection + ## if no proxy protocol packet recevied within the timeout. + ## + ## @doc zones..listeners..proxy_protocol_timeout + ## ValueType: Duration + ## Default: 3s + proxy_protocol_timeout: 3s + + rate_limit { + ## Maximum connections per second. + ## + ## @doc zones..max_conn_rate + ## ValueType: Number | infinity + ## Default: 1000 + ## Examples: + ## max_conn_rate: 1000 + max_conn_rate: 1000 + + ## Message limit for the a external MQTT connection. + ## + ## @doc zones..rate_limit.conn_messages_in + ## ValueType: String | infinity + ## Default: infinity + ## Examples: 100 messages per 10 seconds. + ## conn_messages_in: "100,10s" + conn_messages_in: "100,10s" + + ## Limit the rate of receiving packets for a MQTT connection. + ## The rate is counted by bytes of packets per second. + ## + ## The connection won't accept more messages if the messages come + ## faster than the limit. + ## + ## @doc zones..rate_limit.conn_bytes_in + ## ValueType: String | infinity + ## Default: infinity + ## Examples: 100KB incoming per 10 seconds. + ## conn_bytes_in: "100KB,10s" + ## + conn_bytes_in: "100KB,10s" + + ## Messages quota for the each of external MQTT connection. + ## This value consumed by the number of recipient on a message. + ## + ## @doc zones..rate_limit.quota.conn_messages_routing + ## ValueType: String | infinity + ## Default: infinity + ## Examples: 100 messaegs per 1s: + ## quota.conn_messages_routing: "100,1s" + quota.conn_messages_routing: "100,1s" + + ## Messages quota for the all of external MQTT connections. + ## This value consumed by the number of recipient on a message. + ## + ## @doc zones..rate_limit.quota.overall_messages_routing + ## ValueType: String | infinity + ## Default: infinity + ## Examples: 200000 messages per 1s: + ## quota.overall_messages_routing: "200000,1s" + ## + quota.overall_messages_routing: "200000,1s" + } + + ## TCP options + ## See ${example_common_tcp_options} for more information + tcp.backlog: 1024 + tcp.buffer: 4KB + } + + ## MQTT/SSL - SSL Listener for MQTT Protocol + listeners.mqtt_ssl: + #${example_common_tcp_options} ${example_common_ssl_options} # common options can be written in a separate config entry and reference it from here. + { + + ## The type of the listener. + ## + ## @doc zones..listeners..type + ## ValueType: tcp | ws + ## - tcp: MQTT over TCP + ## - ws: MQTT over Websocket + ## - quic: MQTT over QUIC + ## Required: true + type: tcp + + ## The IP address and port that the listener will bind. + ## + ## @doc zones..listeners..bind + ## ValueType: IPAddress | Port | IPAddrPort + ## Required: true + ## Examples: 8883, 127.0.0.1:8883, ::1:8883 + bind: "0.0.0.0:8883" + + ## The size of the acceptor pool for this listener. + ## + ## @doc zones..listeners..acceptors + ## ValueType: Number + ## Default: 16 + acceptors: 16 + + ## Maximum number of concurrent connections. + ## + ## @doc zones..listeners..max_connections + ## ValueType: Number | infinity + ## Default: infinity + max_connections: 512000 + + ## The access control rules for this listener. + ## + ## See: https://github.com/emqtt/esockd#allowdeny + ## + ## @doc zones..listeners..access_rules + ## ValueType: Array + ## Default: [] + ## Examples: + ## access_rules: [ + ## "deny 192.168.0.0/24", + ## "all all" + ## ] + access_rules: [ + "allow all" + ] + + ## Enable the Proxy Protocol V1/2 if the EMQ X cluster is deployed + ## behind HAProxy or Nginx. + ## + ## See: https://www.haproxy.com/blog/haproxy/proxy-protocol/ + ## + ## @doc zones..listeners..proxy_protocol + ## ValueType: Boolean + ## Default: true + proxy_protocol: false + + ## Sets the timeout for proxy protocol. EMQ X will close the TCP connection + ## if no proxy protocol packet recevied within the timeout. + ## + ## @doc zones..listeners..proxy_protocol_timeout + ## ValueType: Duration + ## Default: 3s + proxy_protocol_timeout: 3s + + rate_limit { + ## Maximum connections per second. + ## + ## @doc zones..max_conn_rate + ## ValueType: Number | infinity + ## Default: 1000 + ## Examples: + ## max_conn_rate: 1000 + max_conn_rate: 1000 + + ## Message limit for the a external MQTT connection. + ## + ## @doc zones..rate_limit.conn_messages_in + ## ValueType: String | infinity + ## Default: infinity + ## Examples: 100 messages per 10 seconds. + ## conn_messages_in: "100,10s" + conn_messages_in: "100,10s" + + ## Limit the rate of receiving packets for a MQTT connection. + ## The rate is counted by bytes of packets per second. + ## + ## The connection won't accept more messages if the messages come + ## faster than the limit. + ## + ## @doc zones..rate_limit.conn_bytes_in + ## ValueType: String | infinity + ## Default: infinity + ## Examples: 100KB incoming per 10 seconds. + ## conn_bytes_in: "100KB,10s" + ## + conn_bytes_in: "100KB,10s" + + ## Messages quota for the each of external MQTT connection. + ## This value consumed by the number of recipient on a message. + ## + ## @doc zones..rate_limit.quota.conn_messages_routing + ## ValueType: String | infinity + ## Default: infinity + ## Examples: 100 messaegs per 1s: + ## quota.conn_messages_routing: "100,1s" + quota.conn_messages_routing: "100,1s" + + ## Messages quota for the all of external MQTT connections. + ## This value consumed by the number of recipient on a message. + ## + ## @doc zones..rate_limit.quota.overall_messages_routing + ## ValueType: String | infinity + ## Default: infinity + ## Examples: 200000 messages per 1s: + ## quota.overall_messages_routing: "200000,1s" + ## + quota.overall_messages_routing: "200000,1s" + } + + ## SSL options + ## See ${example_common_ssl_options} for more information + ssl.enable: true + ssl.versions: ["tlsv1.3", "tlsv1.2", "tlsv1.1", "tlsv1"] + ssl.keyfile: "{{ platform_etc_dir }}/certs/key.pem" + ssl.certfile: "{{ platform_etc_dir }}/certs/cert.pem" + ssl.cacertfile: "{{ platform_etc_dir }}/certs/cacert.pem" + + ## TCP options + ## See ${example_common_tcp_options} for more information + tcp.backlog: 1024 + tcp.buffer: 4KB + } + + listeners.mqtt_quic: + { + + ## The type of the listener. + ## + ## @doc zones..listeners..type + ## ValueType: tcp | ws + ## - tcp: MQTT over TCP + ## - ws: MQTT over Websocket + ## - quic: MQTT over QUIC + ## Required: true + type: quic + + ## The IP address and port that the listener will bind. + ## + ## @doc zones..listeners..bind + ## ValueType: IPAddress | Port | IPAddrPort + ## Required: true + ## Examples: 14567, 127.0.0.1:14567, ::1:14567 + bind: "0.0.0.0:14567" + + ## The size of the acceptor pool for this listener. + ## + ## @doc zones..listeners..acceptors + ## ValueType: Number + ## Default: 16 + acceptors: 16 + + ## Maximum number of concurrent connections. + ## + ## @doc zones..listeners..max_connections + ## ValueType: Number | infinity + ## Default: infinity + max_connections: 1024000 + + ## Path to the file containing the user's private PEM-encoded key. + ## + ## @doc zones..listeners..keyfile + ## ValueType: String + ## Default: "{{ platform_etc_dir }}/certs/key.pem" + keyfile: "{{ platform_etc_dir }}/certs/key.pem" + + ## Path to a file containing the user certificate. + ## + ## @doc zones..listeners..certfile + ## ValueType: String + ## Default: "{{ platform_etc_dir }}/certs/cert.pem" + certfile: "{{ platform_etc_dir }}/certs/cert.pem" + } + + listeners.mqtt_ws: + #${example_common_tcp_options} ${example_common_websocket_options} # common options can be written in a separate config entry and reference it from here. + { + + ## The type of the listener. + ## + ## @doc zones..listeners..type + ## ValueType: tcp | ws + ## - tcp: MQTT over TCP + ## - ws: MQTT over Websocket + ## - quic: MQTT over QUIC + ## Required: true + type: ws + + ## The IP address and port that the listener will bind. + ## + ## @doc zones..listeners..bind + ## ValueType: IPAddress | Port | IPAddrPort + ## Required: true + ## Examples: 8083, 127.0.0.1:8083, ::1:8083 + bind: "0.0.0.0:8083" + + ## The size of the acceptor pool for this listener. + ## + ## @doc zones..listeners..acceptors + ## ValueType: Number + ## Default: 16 + acceptors: 16 + + ## Maximum number of concurrent connections. + ## + ## @doc zones..listeners..max_connections + ## ValueType: Number | infinity + ## Default: infinity + max_connections: 1024000 + + ## The access control rules for this listener. + ## + ## See: https://github.com/emqtt/esockd#allowdeny + ## + ## @doc zones..listeners..access_rules + ## ValueType: Array + ## Default: [] + ## Examples: + ## access_rules: [ + ## "deny 192.168.0.0/24", + ## "all all" + ## ] + access_rules: [ + "allow all" + ] + + ## Enable the Proxy Protocol V1/2 if the EMQ X cluster is deployed + ## behind HAProxy or Nginx. + ## + ## See: https://www.haproxy.com/blog/haproxy/proxy-protocol/ + ## + ## @doc zones..listeners..proxy_protocol + ## ValueType: Boolean + ## Default: true + proxy_protocol: false + + ## Sets the timeout for proxy protocol. EMQ X will close the TCP connection + ## if no proxy protocol packet recevied within the timeout. + ## + ## @doc zones..listeners..proxy_protocol_timeout + ## ValueType: Duration + ## Default: 3s + proxy_protocol_timeout: 3s + + rate_limit { + ## Maximum connections per second. + ## + ## @doc zones..max_conn_rate + ## ValueType: Number | infinity + ## Default: 1000 + ## Examples: + ## max_conn_rate: 1000 + max_conn_rate: 1000 + + ## Message limit for the a external MQTT connection. + ## + ## @doc zones..rate_limit.conn_messages_in + ## ValueType: String | infinity + ## Default: infinity + ## Examples: 100 messages per 10 seconds. + ## conn_messages_in: "100,10s" + conn_messages_in: "100,10s" + + ## Limit the rate of receiving packets for a MQTT connection. + ## The rate is counted by bytes of packets per second. + ## + ## The connection won't accept more messages if the messages come + ## faster than the limit. + ## + ## @doc zones..rate_limit.conn_bytes_in + ## ValueType: String | infinity + ## Default: infinity + ## Examples: 100KB incoming per 10 seconds. + ## conn_bytes_in: "100KB,10s" + ## + conn_bytes_in: "100KB,10s" + + ## Messages quota for the each of external MQTT connection. + ## This value consumed by the number of recipient on a message. + ## + ## @doc zones..rate_limit.quota.conn_messages_routing + ## ValueType: String | infinity + ## Default: infinity + ## Examples: 100 messaegs per 1s: + ## quota.conn_messages_routing: "100,1s" + quota.conn_messages_routing: "100,1s" + + ## Messages quota for the all of external MQTT connections. + ## This value consumed by the number of recipient on a message. + ## + ## @doc zones..rate_limit.quota.overall_messages_routing + ## ValueType: String | infinity + ## Default: infinity + ## Examples: 200000 messages per 1s: + ## quota.overall_messages_routing: "200000,1s" + ## + quota.overall_messages_routing: "200000,1s" + } + + ## TCP options + ## See ${example_common_tcp_options} for more information + tcp.backlog: 1024 + tcp.buffer: 4KB + + ## Websocket options + ## See ${example_common_websocket_options} for more information + websocket.idle_timeout: 86400s + } + + listeners.mqtt_wss: + #${example_common_tcp_options} ${example_common_ssl_options} ${example_common_websocket_options} # common options can be written in a separate config entry and reference it from here. + { + + ## The type of the listener. + ## + ## @doc zones..listeners..type + ## ValueType: tcp | ws + ## - tcp: MQTT over TCP + ## - ws: MQTT over Websocket + ## - quic: MQTT over QUIC + ## Required: true + type: ws + + ## The IP address and port that the listener will bind. + ## + ## @doc zones..listeners..bind + ## ValueType: IPAddress | Port | IPAddrPort + ## Required: true + ## Examples: 8084, 127.0.0.1:8084, ::1:8084 + bind: "0.0.0.0:8084" + + ## The size of the acceptor pool for this listener. + ## + ## @doc zones..listeners..acceptors + ## ValueType: Number + ## Default: 16 + acceptors: 16 + + ## Maximum number of concurrent connections. + ## + ## @doc zones..listeners..max_connections + ## ValueType: Number | infinity + ## Default: infinity + max_connections: 512000 + + ## The access control rules for this listener. + ## + ## See: https://github.com/emqtt/esockd#allowdeny + ## + ## @doc zones..listeners..access_rules + ## ValueType: Array + ## Default: [] + ## Examples: + ## access_rules: [ + ## "deny 192.168.0.0/24", + ## "all all" + ## ] + access_rules: [ + "allow all" + ] + + ## Enable the Proxy Protocol V1/2 if the EMQ X cluster is deployed + ## behind HAProxy or Nginx. + ## + ## See: https://www.haproxy.com/blog/haproxy/proxy-protocol/ + ## + ## @doc zones..listeners..proxy_protocol + ## ValueType: Boolean + ## Default: true + proxy_protocol: false + + ## Sets the timeout for proxy protocol. EMQ X will close the TCP connection + ## if no proxy protocol packet recevied within the timeout. + ## + ## @doc zones..listeners..proxy_protocol_timeout + ## ValueType: Duration + ## Default: 3s + proxy_protocol_timeout: 3s + + rate_limit { + ## Maximum connections per second. + ## + ## @doc zones..max_conn_rate + ## ValueType: Number | infinity + ## Default: 1000 + ## Examples: + ## max_conn_rate: 1000 + max_conn_rate: 1000 + + ## Message limit for the a external MQTT connection. + ## + ## @doc zones..rate_limit.conn_messages_in + ## ValueType: String | infinity + ## Default: infinity + ## Examples: 100 messages per 10 seconds. + ## conn_messages_in: "100,10s" + conn_messages_in: "100,10s" + + ## Limit the rate of receiving packets for a MQTT connection. + ## The rate is counted by bytes of packets per second. + ## + ## The connection won't accept more messages if the messages come + ## faster than the limit. + ## + ## @doc zones..rate_limit.conn_bytes_in + ## ValueType: String | infinity + ## Default: infinity + ## Examples: 100KB incoming per 10 seconds. + ## conn_bytes_in: "100KB,10s" + ## + conn_bytes_in: "100KB,10s" + + ## Messages quota for the each of external MQTT connection. + ## This value consumed by the number of recipient on a message. + ## + ## @doc zones..rate_limit.quota.conn_messages_routing + ## ValueType: String | infinity + ## Default: infinity + ## Examples: 100 messaegs per 1s: + ## quota.conn_messages_routing: "100,1s" + quota.conn_messages_routing: "100,1s" + + ## Messages quota for the all of external MQTT connections. + ## This value consumed by the number of recipient on a message. + ## + ## @doc zones..rate_limit.quota.overall_messages_routing + ## ValueType: String | infinity + ## Default: infinity + ## Examples: 200000 messages per 1s: + ## quota.overall_messages_routing: "200000,1s" + ## + quota.overall_messages_routing: "200000,1s" + } + + ## SSL options + ## See ${example_common_ssl_options} for more information + ssl.enable: true + ssl.keyfile: "{{ platform_etc_dir }}/certs/key.pem" + ssl.certfile: "{{ platform_etc_dir }}/certs/cert.pem" + ssl.cacertfile: "{{ platform_etc_dir }}/certs/cacert.pem" + + ## TCP options + ## See ${example_common_tcp_options} for more information + tcp.backlog: 1024 + tcp.buffer: 4KB + + ## Websocket options + ## See ${example_common_websocket_options} for more information + websocket.idle_timeout: 86400s + } } -##-------------------------------------------------------------------- -## MQTT Protocol -##-------------------------------------------------------------------- -mqtt: { - ## Maximum MQTT packet size allowed. - ## - ## Value: Bytes - ## Default: 1MB - max_packet_size: "1MB" - - ## Maximum length of MQTT clientId allowed. - ## - ## Value: Number [23-65535] - max_clientid_len: 65535 - - ## Maximum topic levels allowed. 0 means no limit. - ## - ## Value: Number - max_topic_levels: 0 - - ## Maximum QoS allowed. - ## - ## Value: 0 | 1 | 2 - max_qos_allowed: 2 - - ## Maximum Topic Alias, 0 means no topic alias supported. - ## - ## Value: 0-65535 - max_topic_alias: 65535 - - ## Whether the Server supports MQTT retained messages. - ## - ## Value: boolean - retain_available: true - - ## Whether the Server supports MQTT Wildcard Subscriptions - ## - ## Value: boolean - wildcard_subscription: true - - ## Whether the Server supports MQTT Shared Subscriptions. - ## - ## Value: boolean - shared_subscription: true - - ## Whether to ignore loop delivery of messages.(for mqtt v3.1.1) - ## - ## Value: true | false - ignore_loop_deliver: false - - ## Whether to parse the MQTT frame in strict mode - ## - ## Value: true | false - strict_mode: false - - ## Specify the response information returned to the client - ## - ## Value: String - ## response_information: example +#This is an example zone which has less "strict" settings. +#It's useful to clients connecting the broker from trusted networks. +zones.internal { + acl.enable: false + auth.enable: false + listeners.mqtt_internal: { + type: tcp + bind: "127.0.0.1:11883" + acceptors: 4 + max_connections: 1024000 + tcp.active_n: 1000 + tcp.backlog: 512 + } } -##-------------------------------------------------------------------- -## External Zone -zone.external { - ## Idle timeout of the external MQTT connections. +##================================================================== +## System Monitor +##================================================================== +sysmon { + ## The time interval for the periodic process limit check ## - ## Value: duration - idle_timeout: 15s + ## @doc sysmon.vm.process_check_interval + ## ValueType: Duration + ## Default: 30s + vm.process_check_interval: 30s - ## Enable ACL check. + ## The threshold, as percentage of processes, for how many processes can simultaneously exist at the local node before the corresponding alarm is set. ## - ## Value: Flag - enable_acl: on + ## @doc sysmon.vm.process_high_watermark + ## ValueType: Percentage + ## Default: 80% + vm.process_high_watermark: 80% - ## Enable ban check. + ## The threshold, as percentage of processes, for how many processes can simultaneously exist at the local node before the corresponding alarm is clear. ## - ## Value: Flag - enable_ban: on + ## @doc sysmon.vm.process_low_watermark + ## ValueType: Percentage + ## Default: 60% + vm.process_low_watermark: 60% - ## Enable per connection statistics. + ## Enable Long GC monitoring. + ## Notice: don't enable the monitor in production for: + ## https://github.com/erlang/otp/blob/feb45017da36be78d4c5784d758ede619fa7bfd3/erts/emulator/beam/erl_gc.c#L421 ## - ## Value: on | off - enable_stats: on + ## @doc sysmon.vm.long_gc + ## ValueType: Duration | disabled + ## Default: disabled + vm.long_gc: disabled - ## The action when acl check reject current operation + ## Enable Long Schedule(ms) monitoring. ## - ## Value: ignore | disconnect - ## Default: ignore - acl_deny_action: ignore + ## See: http://erlang.org/doc/man/erlang.html#system_monitor-2 + ## + ## @doc sysmon.vm.long_schedule + ## ValueType: Duration | disabled + ## Default: disabled + vm.long_schedule: 240ms - ## Force the MQTT connection process GC after this number of - ## messages | bytes passed through. + ## Enable Large Heap monitoring. ## - ## Numbers delimited by `|'. Zero or negative is to disable. - force_gc_policy: "16000|16MB" + ## See: http://erlang.org/doc/man/erlang.html#system_monitor-2 + ## + ## @doc sysmon.vm.large_heap + ## ValueType: Size | disabled + ## Default: 32MB + vm.large_heap: 32MB - ## Max message queue length and total heap size to force shutdown - ## connection/session process. - ## Message queue here is the Erlang process mailbox, but not the number - ## of queued MQTT messages of QoS 1 and 2. + ## Enable Busy Port monitoring. ## - ## Numbers delimited by `|'. Zero or negative is to disable. + ## See: http://erlang.org/doc/man/erlang.html#system_monitor-2 ## - ## Default: - ## - "10000|64MB" on ARCH_64 system - ## - "1000|32MB" on ARCH_32 sytem - #force_shutdown_policy: "10000|64MB" + ## @doc sysmon.vm.busy_port + ## ValueType: Boolean + ## Default: true + vm.busy_port: true - ## Maximum MQTT packet size allowed. + ## Enable Busy Dist Port monitoring. ## - ## Value: Bytes - ## Default: 1MB - ## max_packet_size: 64KB + ## See: http://erlang.org/doc/man/erlang.html#system_monitor-2 + ## + ## @doc sysmon.vm.busy_dist_port + ## ValueType: Boolean + ## Default: true + vm.busy_dist_port: true - ## Maximum length of MQTT clientId allowed. + ## The time interval for the periodic cpu check ## - ## Value: Number [23-65535] - ## max_clientid_len: 1024 + ## @doc sysmon.os.cpu_check_interval + ## ValueType: Duration + ## Default: 60s + os.cpu_check_interval: 60s - ## Maximum topic levels allowed. 0 means no limit. + ## The threshold, as percentage of system cpu, for how much system cpu can be used before the corresponding alarm is set. ## - ## Value: Number - ## max_topic_levels: 7 + ## @doc sysmon.os.cpu_high_watermark + ## ValueType: Percentage + ## Default: 80% + os.cpu_high_watermark: 80% - ## Maximum QoS allowed. + ## The threshold, as percentage of system cpu, for how much system cpu can be used before the corresponding alarm is clear. ## - ## Value: 0 | 1 | 2 - ## max_qos_allowed: 2 + ## @doc sysmon.os.cpu_low_watermark + ## ValueType: Percentage + ## Default: 60% + os.cpu_low_watermark: 60% - ## Maximum Topic Alias, 0 means no limit. + ## The time interval for the periodic memory check ## - ## Value: 0-65535 - ## max_topic_alias: 65535 + ## @doc sysmon.os.mem_check_interval + ## ValueType: Duration | disabled + ## Default: 60s + os.mem_check_interval: 60s - ## Whether the Server supports retained messages. + ## The threshold, as percentage of system memory, for how much system memory can be allocated before the corresponding alarm is set. ## - ## Value: boolean - ## retain_available: true + ## @doc sysmon.os.sysmem_high_watermark + ## ValueType: Percentage + ## Default: 70% + os.sysmem_high_watermark: 70% - ## Whether the Server supports Wildcard Subscriptions + ## The threshold, as percentage of system memory, for how much system memory can be allocated by one Erlang process before the corresponding alarm is set. ## - ## Value: boolean - ## wildcard_subscription: false - - ## Whether the Server supports Shared Subscriptions - ## - ## Value: boolean - ## shared_subscription: false - - ## Server Keep Alive - ## - ## Value: Number - ## server_keepalive: 0 - - ## The backoff for MQTT keepalive timeout. The broker will kick a connection out - ## until 'Keepalive * backoff * 2' timeout. - ## - ## Value: Float > 0.5 - keepalive_backoff: 0.75 - - ## Maximum number of subscriptions allowed, 0 means no limit. - ## - ## Value: Number - max_subscriptions: 0 - - ## Force to upgrade QoS according to subscription. - ## - ## Value: on | off - upgrade_qos: off - - ## Maximum size of the Inflight Window storing QoS1/2 messages delivered but unacked. - ## - ## Value: Number - max_inflight: 32 - - ## Retry interval for QoS1/2 message delivering. - ## - ## Value: Duration - retry_interval: 30s - - ## Maximum QoS2 packets (Client -> Broker) awaiting PUBREL, 0 means no limit. - ## - ## Value: Number - max_awaiting_rel: 100 - - ## The QoS2 messages (Client -> Broker) will be dropped if awaiting PUBREL timeout. - ## - ## Value: Duration - await_rel_timeout: 300s - - ## Default session expiry interval for MQTT V3.1.1 connections. - ## - ## Value: Duration - ## -d: day - ## -h: hour - ## -m: minute - ## -s: second - ## - ## Default: 2h, 2 hours - session_expiry_interval: 2h - - ## Maximum queue length. Enqueued messages when persistent client disconnected, - ## or inflight window is full. 0 means no limit. - ## - ## Value: Number >= 0 - max_mqueue_len: 1000 - - ## Topic priorities. - ## 'none' to indicate no priority table (by default), hence all messages - ## are treated equal - ## - ## Priority number [1-255] - ## Example: "topic/1=10,topic/2=8" - ## NOTE: comma and equal signs are not allowed for priority topic names - ## NOTE: messages for topics not in the priority table are treated as - ## either highest or lowest priority depending on the configured - ## value for mqueue_default_priority - ## - mqueue_priorities: none - - ## Default to highest priority for topics not matching priority table - ## - ## Value: highest | lowest - mqueue_default_priority: highest - - ## Whether to enqueue QoS0 messages. - ## - ## Value: false | true - mqueue_store_qos0: true - - ## Whether to turn on flapping detect - ## - ## Value: on | off - enable_flapping_detect: off - - ## Message limit for the a external MQTT connection. - ## - ## Value: Number,Duration - ## Example: 100 messages per 10 seconds. - #rate_limit.conn_messages_in: "100,10s" - - ## Bytes limit for a external MQTT connections. - ## - ## Value: Number,Duration - ## Example: 100KB incoming per 10 seconds. - #rate_limit.conn_bytes_in: "100KB,10s" - - ## Whether to alarm the congested connections. - ## - ## Sometimes the mqtt connection (usually an MQTT subscriber) may get "congested" because - ## there're too many packets to sent. The socket trys to buffer the packets until the buffer is - ## full. If more packets comes after that, the packets will be "pending" in a queue - ## and we consider the connection is "congested". - ## - ## Enable this to send an alarm when there's any bytes pending in the queue. You could set - ## the `listener.tcp..sndbuf` to a larger value if the alarm is triggered too often. - ## - ## The name of the alarm is of format "conn_congestion//". - ## Where the is the client-id of the congested MQTT connection. - ## And the is the username or "unknown_user" of not provided by the client. - ## Default: off - #conn_congestion.alarm: off - - ## Won't clear the congested alarm in how long time. - ## The alarm is cleared only when there're no pending bytes in the queue, and also it has been - ## `min_alarm_sustain_duration` time since the last time we considered the connection is "congested". - ## - ## This is to avoid clearing and sending the alarm again too often. - ## Default: 1m - #conn_congestion.min_alarm_sustain_duration: 1m - - ## Messages quota for the each of external MQTT connection. - ## This value consumed by the number of recipient on a message. - ## - ## Value: Number, Duration - ## - ## Example: 100 messages per 1s - #quota.conn_messages_routing: "100,1s" - - ## Messages quota for the all of external MQTT connections. - ## This value consumed by the number of recipient on a message. - ## - ## Value: Number, Duration - ## - ## Example: 200000 messages per 1s - #quota.overall_messages_routing: "200000,1s" - - ## All the topics will be prefixed with the mountpoint path if this option is enabled. - ## - ## Variables in mountpoint path: - ## - %c: clientid - ## - %u: username - ## - ## Value: String - ## mountpoint: "devicebound/" - - ## Whether use username replace client id - ## - ## Value: boolean - ## Default: false - use_username_as_clientid: false - - ## Whether to ignore loop delivery of messages.(for mqtt v3.1.1) - ## - ## Value: true | false - ignore_loop_deliver: false - - ## Whether to parse the MQTT frame in strict mode - ## - ## Value: true | false - strict_mode: false - - ## Specify the response information returned to the client - ## - ## Value: String - #response_information: example + ## @doc sysmon.os.procmem_high_watermark + ## ValueType: Percentage + ## Default: 5% + os.procmem_high_watermark: 5% } -##-------------------------------------------------------------------- -## Internal Zone -zone.internal { - ## Allow anonymous authentication by default if no auth plugins loaded. - ## Notice: Disable the option in production deployment! +##================================================================== +## Alarm +##================================================================== +alarm { + ## Specifies the actions to take when an alarm is activated ## - ## Value: true | false - allow_anonymous: true + ## @doc alarm.actions + ## ValueType: Array + ## Default: [log, publish] + actions: [log, publish] - ## Enable per connection stats. + ## The maximum number of deactivated alarms ## - ## Value: Flag - enable_stats: on + ## @doc alarm.size_limit + ## ValueType: Integer + ## Default: 1000 + size_limit: 1000 - ## Enable ACL check. + ## Validity Period of deactivated alarms ## - ## Value: Flag - enable_acl: off - - ## The action when acl check reject current operation - ## - ## Value: ignore | disconnect - ## Default: ignore - acl_deny_action: ignore - - ## See zone.$name.force_gc_policy - ## force_gc_policy: "128000|128MB" - - ## See zone.$name.wildcard_subscription. - ## - ## Value: boolean - ## wildcard_subscription: true - - ## See zone.$name.shared_subscription. - ## - ## Value: boolean - ## shared_subscription: true - - ## See zone.$name.max_subscriptions. - ## - ## Value: Integer - max_subscriptions: 0 - - ## See zone.$name.max_inflight - ## - ## Value: Number - max_inflight: 128 - - ## See zone.$name.max_awaiting_rel - ## - ## Value: Number - max_awaiting_rel: 1000 - - ## See zone.$name.max_mqueue_len - ## - ## Value: Number >= 0 - max_mqueue_len: 10000 - - ## Whether to enqueue Qos0 messages. - ## - ## Value: false | true - mqueue_store_qos0: true - - ## Whether to turn on flapping detect - ## - ## Value: on | off - enable_flapping_detect: off - - ## See zone.$name.force_shutdown_policy - ## - ## Default: - ## - "10000|64MB" on ARCH_64 system - ## - "1000|32MB" on ARCH_32 sytem - #force_shutdown_policy: 10000|64MB - - ## All the topics will be prefixed with the mountpoint path if this option is enabled. - ## - ## Variables in mountpoint path: - ## - %c: clientid - ## - %u: username - ## - ## Value: String - ## mountpoint: "cloudbound/" - - ## Whether to ignore loop delivery of messages.(for mqtt v3.1.1) - ## - ## Value: true | false - ignore_loop_deliver: false - - ## Whether to parse the MQTT frame in strict mode - ## - ## Value: true | false - strict_mode: false - - ## Specify the response information returned to the client - ## - ## Value: String - ## response_information: example - - ## Allow the zone's clients to bypass authentication step - ## - ## Value: true | false - bypass_auth_plugins: true + ## @doc alarm.validity_period + ## ValueType: Duration + ## Default: 24h + validity_period: 24h } -##-------------------------------------------------------------------- -## MQTT/TCP - External TCP Listener for MQTT Protocol -listener.tcp.external { - ## listener.tcp.$name.endpoint is the IP address and port that the MQTT/TCP - ## listener will bind. - ## - ## Value: IP:Port | Port - ## - ## Examples: 1883, "127.0.0.1:1883", "::1:1883" - endpoint: "0.0.0.0:1883" +## Config references for listeners - ## The acceptor pool for external MQTT/TCP listener. +## Socket options for TCP connections +## See: http://erlang.org/doc/man/inet.html +example_common_tcp_options { + ## Specify the {active, N} option for this Socket. ## - ## Value: Number - acceptors: 8 + ## See: https://erlang.org/doc/man/inet.html#setopts-2 + ## + ## @doc listeners..tcp.active_n + ## ValueType: Number + ## Default: 100 + tcp.active_n: 100 - ## Maximum number of concurrent MQTT/TCP connections. + ## TCP backlog defines the maximum length that the queue of + ## pending connections can grow to. ## - ## Value: Number - max_connections: 1024000 + ## @doc listeners..tcp.backlog + ## ValueType: Number + ## Range: [0, 1048576] + ## Default: 128 + tcp.backlog: 128 - ## Maximum external connections per second. + ## The TCP send timeout for the connections. ## - ## Value: Number - max_conn_rate: 1000 + ## @doc listeners..tcp.send_timeout + ## ValueType: Duration + ## Default: 15s + tcp.send_timeout: 15s - ## Specify the {active, N} option for the external MQTT/TCP Socket. + ## Close the connection if send timeout. ## - ## Value: Number - active_n: 100 + ## @doc listeners..tcp.send_timeout_close + ## ValueType: Boolean + ## Default: true + tcp.send_timeout_close: true - ## Zone of the external MQTT/TCP listener belonged to. + ## The TCP receive buffer(os kernel) for the connections. ## - ## See: zone.$name.* - ## - ## Value: String - zone: external + ## @doc listeners..tcp.recbuf + ## ValueType: Size + ## Default: 2KB + tcp.recbuf: 2KB - ## The access control rules for the MQTT/TCP listener. + ## The TCP send buffer(os kernel) for the connections. ## - ## See: https://github.com/emqtt/esockd#allowdeny - ## - ## Value: ACL Rule - ## - ## Example: "allow 192.168.0.0/24" - access.1: "allow all" - - ## Enable the Proxy Protocol V1/2 if the EMQ X cluster is deployed - ## behind HAProxy or Nginx. - ## - ## See: https://www.haproxy.com/blog/haproxy/proxy-protocol/ - ## - ## Value: on | off - ## proxy_protocol: on - - ## Sets the timeout for proxy protocol. EMQ X will close the TCP connection - ## if no proxy protocol packet recevied within the timeout. - ## - ## Value: Duration - ## proxy_protocol_timeout: 3s - - ## Enable the option for X.509 certificate based authentication. - ## EMQX will use the common name of certificate as MQTT username. - ## Only support Proxy Protocol V2, the CN is available in Proxy Protocol V2 additional info - ## - ## Value: cn - ## peer_cert_as_username: cn - - ## Enable the option for X.509 certificate based authentication. - ## EMQX will use the common name of certificate as MQTT clientid. - ## Only support Proxy Protocol V2, the CN is available in Proxy Protocol V2 additional info - ## - ## Value: cn - ## peer_cert_as_clientid: cn - - ## The TCP backlog defines the maximum length that the queue of pending - ## connections can grow to. - ## - ## Value: Number >= 0 - backlog: 1024 - - ## The TCP send timeout for external MQTT connections. - ## - ## Value: Duration - send_timeout: 15s - - ## Close the TCP connection if send timeout. - ## - ## Value: on | off - send_timeout_close: on - - ## The TCP receive buffer(os kernel) for MQTT connections. - ## - ## See: http://erlang.org/doc/man/inet.html - ## - ## Value: Bytes - ## recbuf: 2KB - - ## The TCP send buffer(os kernel) for MQTT connections. - ## - ## See: http://erlang.org/doc/man/inet.html - ## - ## Value: Bytes - ## sndbuf: 2KB + ## @doc listeners..tcp.sndbuf + ## ValueType: Size + ## Default: 2KB + tcp.sndbuf: 2KB ## The size of the user-level software buffer used by the driver. - ## Not to be confused with options sndbuf and recbuf, which correspond - ## to the Kernel socket buffers. It is recommended to have val(buffer) - ## >= max(val(sndbuf),val(recbuf)) to avoid performance issues because - ## of unnecessary copying. val(buffer) is automatically set to the above - ## maximum when values sndbuf or recbuf are set. ## - ## See: http://erlang.org/doc/man/inet.html - ## - ## Value: Bytes - ## buffer: 2KB + ## @doc listeners..tcp.buffer + ## ValueType: Size + ## Default: 2KB + tcp.buffer: 2KB ## Sets the 'buffer: max(sndbuf, recbuf)' if this option is enabled. ## - ## Value: on | off - ## tune_buffer: off + ## @doc listeners..tcp.tune_buffer + ## ValueType: Boolean + ## Default: false + tcp.tune_buffer: false ## The socket is set to a busy state when the amount of data queued internally ## by the ERTS socket implementation reaches this limit. ## - ## Value: on | off - ## Defaults to 1MB - ## high_watermark: 1MB + ## @doc listeners..tcp.high_watermark + ## ValueType: Size + ## Default: 1MB + tcp.high_watermark: 1MB - ## The TCP_NODELAY flag for MQTT connections. Small amounts of data are - ## sent immediately if the option is enabled. + ## The TCP_NODELAY flag for the connections. ## - ## Value: true | false - nodelay: true + ## @doc listeners..tcp.nodelay + ## ValueType: Boolean + ## Default: true + tcp.nodelay: true - ## The SO_REUSEADDR flag for TCP listener. + ## The SO_REUSEADDR flag for the connections. ## - ## Value: true | false - reuseaddr: true + ## @doc listeners..tcp.reuseaddr + ## ValueType: Boolean + ## Default: true + tcp.reuseaddr: true } -##-------------------------------------------------------------------- -## Internal TCP Listener for MQTT Protocol +## Socket options for SSL connections +## See: http://erlang.org/doc/man/ssl.html +example_common_ssl_options { -listener.tcp.internal { - ## The IP address and port that the internal MQTT/TCP protocol listener - ## will bind. + ## A performance optimization setting, it allows clients to reuse + ## pre-existing sessions, instead of initializing new ones. + ## Read more about it here. ## - ## Value: IP:Port, Port - ## - ## Examples: 11883, "127.0.0.1:11883", "::1:11883" - endpoint: "127.0.0.1:11883" + ## @doc listeners..ssl.reuse_sessions + ## ValueType: Boolean + ## Default: true + ssl.reuse_sessions: true - ## The acceptor pool for internal MQTT/TCP listener. + ## SSL parameter renegotiation is a feature that allows a client and a server + ## to renegotiate the parameters of the SSL connection on the fly. + ## RFC 5746 defines a more secure way of doing this. By enabling secure renegotiation, + ## you drop support for the insecure renegotiation, prone to MitM attacks. ## - ## Value: Number - acceptors: 4 + ## @doc listeners..ssl.secure_renegotiate + ## ValueType: Boolean + ## Default: true + ssl.secure_renegotiate: true - ## Maximum number of concurrent MQTT/TCP connections. + ## An important security setting, it forces the cipher to be set based + ## on the server-specified order instead of the client-specified order, + ## hence enforcing the (usually more properly configured) security + ## ordering of the server administrator. ## - ## Value: Number - max_connections: 1024000 - - ## Maximum internal connections per second. - ## - ## Value: Number - max_conn_rate: 1000 - - ## Specify the {active, N} option for the internal MQTT/TCP Socket. - ## - ## Value: Number - active_n: 1000 - - ## Zone of the internal MQTT/TCP listener belonged to. - ## - ## Value: String - zone: internal - - ## The TCP backlog of internal MQTT/TCP Listener. - ## - ## See: listener.tcp.$name.backlog - ## - ## Value: Number >= 0 - backlog: 512 - - ## The TCP send timeout for internal MQTT connections. - ## - ## See: listener.tcp.$name.send_timeout - ## - ## Value: Duration - send_timeout: 5s - - ## Close the MQTT/TCP connection if send timeout. - ## - ## See: listener.tcp.$name.send_timeout_close - ## - ## Value: on | off - send_timeout_close: on - - ## The TCP receive buffer(os kernel) for internal MQTT connections. - ## - ## See: listener.tcp.$name.recbuf - ## - ## Value: Bytes - recbuf: 64KB - - ## The TCP send buffer(os kernel) for internal MQTT connections. - ## - ## See: http://erlang.org/doc/man/inet.html - ## - ## Value: Bytes - sndbuf: 64KB - - ## The size of the user-level software buffer used by the driver. - ## - ## See: listener.tcp.$name.buffer - ## - ## Value: Bytes - ## buffer: 16KB - - ## Sets the 'buffer: max(sndbuf, recbuf)' if this option is enabled. - ## - ## See: listener.tcp.$name.tune_buffer - ## - ## Value: on | off - ## tune_buffer: off - - ## The TCP_NODELAY flag for internal MQTT connections. - ## - ## See: listener.tcp.$name.nodelay - ## - ## Value: true | false - nodelay: false - - ## The SO_REUSEADDR flag for MQTT/TCP Listener. - ## - ## Value: true | false - reuseaddr: true -} - -##-------------------------------------------------------------------- -## MQTT/SSL - External SSL Listener for MQTT Protocol -listener.ssl.external { - ## listener.ssl.$name is the IP address and port that the MQTT/SSL - ## listener will bind. - ## - ## Value: IP:Port | Port - ## - ## Examples: 8883, "127.0.0.1:8883", "::1:8883" - endpoint: 8883 - - ## The acceptor pool for external MQTT/SSL listener. - ## - ## Value: Number - acceptors: 16 - - ## Maximum number of concurrent MQTT/SSL connections. - ## - ## Value: Number - max_connections: 102400 - - ## Maximum MQTT/SSL connections per second. - ## - ## Value: Number - max_conn_rate: 500 - - ## Specify the {active, N} option for the internal MQTT/SSL Socket. - ## - ## Value: Number - active_n: 100 - - ## Zone of the external MQTT/SSL listener belonged to. - ## - ## Value: String - zone: external - - ## The access control rules for the MQTT/SSL listener. - ## - ## See: listener.tcp.$name.access - ## - ## Value: ACL Rule - access.1: "allow all" - - ## Enable the Proxy Protocol V1/2 if the EMQ cluster is deployed behind - ## HAProxy or Nginx. - ## - ## See: listener.tcp.$name.proxy_protocol - ## - ## Value: on | off - ## proxy_protocol: on - - ## Sets the timeout for proxy protocol. - ## - ## See: listener.tcp.$name.proxy_protocol_timeout - ## - ## Value: Duration - ## proxy_protocol_timeout: 3s + ## @doc listeners..ssl.honor_cipher_order + ## ValueType: Boolean + ## Default: true + ssl.honor_cipher_order: true ## TLS versions only to protect from POODLE attack. ## - ## See: http://erlang.org/doc/man/ssl.html - ## - ## Value: String, seperated by ',' - ## NOTE: Do not use tlsv1.3 if emqx is running on OTP-22 or earlier - ## tls_versions: "tlsv1.3,tlsv1.2,tlsv1.1,tlsv1" + ## @doc listeners..ssl.versions + ## ValueType: Array + ## Default: ["tlsv1.3", "tlsv1.2", "tlsv1.1", "tlsv1"] + ssl.versions: ["tlsv1.3", "tlsv1.2", "tlsv1.1", "tlsv1"] ## TLS Handshake timeout. ## - ## Value: Duration - handshake_timeout: 15s + ## @doc listeners..ssl.handshake_timeout + ## ValueType: Duration + ## Default: 15s + ssl.handshake_timeout: 15s ## Maximum number of non-self-issued intermediate certificates that ## can follow the peer certificate in a valid certification path. ## - ## Value: Number - ## depth: 10 - - ## String containing the user's password. Only used if the private keyfile - ## is password-protected. - ## - ## Value: String - ## key_password: yourpass + ## @doc listeners..ssl.depth + ## ValueType: Integer + ## Default: 10 + ssl.depth: 10 ## Path to the file containing the user's private PEM-encoded key. ## - ## See: http://erlang.org/doc/man/ssl.html - ## - ## Value: File - keyfile: "{{ platform_etc_dir }}/certs/key.pem" + ## @doc listeners..ssl.keyfile + ## ValueType: File + ## Default: "{{ platform_etc_dir }}/certs/key.pem" + ssl.keyfile: "{{ platform_etc_dir }}/certs/key.pem" ## Path to a file containing the user certificate. ## - ## See: http://erlang.org/doc/man/ssl.html - ## - ## Value: File - certfile: "{{ platform_etc_dir }}/certs/cert.pem" + ## @doc listeners..ssl.certfile + ## ValueType: File + ## Default: "{{ platform_etc_dir }}/certs/cert.pem" + ssl.certfile: "{{ platform_etc_dir }}/certs/cert.pem" ## Path to the file containing PEM-encoded CA certificates. The CA certificates ## are used during server authentication and when building the client certificate chain. ## - ## Value: File - ## cacertfile: "{{ platform_etc_dir }}/certs/cacert.pem" + ## @doc listeners..ssl.cacertfile + ## ValueType: File + ## Default: "{{ platform_etc_dir }}/certs/cacert.pem" + ssl.cacertfile: "{{ platform_etc_dir }}/certs/cacert.pem" + + ## Maximum number of non-self-issued intermediate certificates that + ## can follow the peer certificate in a valid certification path. + ## + ## @doc listeners..ssl.depth + ## ValueType: Number + ## Default: 10 + ssl.depth: 10 + + ## String containing the user's password. Only used if the private keyfile + ## is password-protected. + ## + ## See: listener.ssl.$name.key_password + ## + ## @doc listeners..ssl.depth + ## ValueType: String + ## Default: "" + #ssl.key_password: "" ## The Ephemeral Diffie-Helman key exchange is a very effective way of ## ensuring Forward Secrecy by exchanging a set of keys that never hit @@ -1443,8 +2205,10 @@ listener.ssl.external { ## OpenSSL provides us with a tool to do that. Simply run: ## openssl dhparam -out dh-params.pem 2048 ## - ## Value: File - ## dhfile: "{{ platform_etc_dir }}/certs/dh-params.pem" + ## @doc listeners..ssl.dhfile + ## ValueType: File + ## Default: "{{ platform_etc_dir }}/certs/dh-params.pem" + #ssl.dhfile: "{{ platform_etc_dir }}/certs/dh-params.pem" ## A server only does x509-path validation in mode verify_peer, ## as it then sends a certificate request to the client (this @@ -1452,15 +2216,19 @@ listener.ssl.external { ## You can then also want to specify option fail_if_no_peer_cert. ## More information at: http://erlang.org/doc/man/ssl.html ## - ## Value: verify_peer | verify_none - ## verify: verify_peer + ## @doc listeners..ssl.verify + ## ValueType: verify_peer | verify_none + ## Default: verify_none + ssl.verify: verify_none ## Used together with {verify, verify_peer} by an SSL server. If set to true, ## the server fails if the client does not have a certificate to send, that is, ## sends an empty certificate. ## - ## Value: true | false - ## fail_if_no_peer_cert: true + ## @doc listeners..ssl.fail_if_no_peer_cert + ## ValueType: Boolean + ## Default: true + ssl.fail_if_no_peer_cert: false ## This is the single most important configuration option of an Erlang SSL ## application. Ciphers (and their ordering) define the way the client and @@ -1478,1083 +2246,161 @@ listener.ssl.external { ## ## Most of it was copied from Mozilla’s Server Side TLS article ## - ## Value: Ciphers - ciphers: "TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256,TLS_CHACHA20_POLY1305_SHA256,TLS_AES_128_CCM_SHA256,TLS_AES_128_CCM_8_SHA256,ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-AES256-SHA384,ECDHE-RSA-AES256-SHA384,ECDHE-ECDSA-DES-CBC3-SHA,ECDH-ECDSA-AES256-GCM-SHA384,ECDH-RSA-AES256-GCM-SHA384,ECDH-ECDSA-AES256-SHA384,ECDH-RSA-AES256-SHA384,DHE-DSS-AES256-GCM-SHA384,DHE-DSS-AES256-SHA256,AES256-GCM-SHA384,AES256-SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES128-SHA256,ECDHE-RSA-AES128-SHA256,ECDH-ECDSA-AES128-GCM-SHA256,ECDH-RSA-AES128-GCM-SHA256,ECDH-ECDSA-AES128-SHA256,ECDH-RSA-AES128-SHA256,DHE-DSS-AES128-GCM-SHA256,DHE-DSS-AES128-SHA256,AES128-GCM-SHA256,AES128-SHA256,ECDHE-ECDSA-AES256-SHA,ECDHE-RSA-AES256-SHA,DHE-DSS-AES256-SHA,ECDH-ECDSA-AES256-SHA,ECDH-RSA-AES256-SHA,AES256-SHA,ECDHE-ECDSA-AES128-SHA,ECDHE-RSA-AES128-SHA,DHE-DSS-AES128-SHA,ECDH-ECDSA-AES128-SHA,ECDH-RSA-AES128-SHA,AES128-SHA" + ## @doc listeners..ssl.ciphers + ## ValueType: Array + ## Default: [ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-AES256-SHA384,ECDHE-RSA-AES256-SHA384,ECDHE-ECDSA-DES-CBC3-SHA,ECDH-ECDSA-AES256-GCM-SHA384,ECDH-RSA-AES256-GCM-SHA384,ECDH-ECDSA-AES256-SHA384,ECDH-RSA-AES256-SHA384,DHE-DSS-AES256-GCM-SHA384,DHE-DSS-AES256-SHA256,AES256-GCM-SHA384,AES256-SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES128-SHA256,ECDHE-RSA-AES128-SHA256,ECDH-ECDSA-AES128-GCM-SHA256,ECDH-RSA-AES128-GCM-SHA256,ECDH-ECDSA-AES128-SHA256,ECDH-RSA-AES128-SHA256,DHE-DSS-AES128-GCM-SHA256,DHE-DSS-AES128-SHA256,AES128-GCM-SHA256,AES128-SHA256,ECDHE-ECDSA-AES256-SHA,ECDHE-RSA-AES256-SHA,DHE-DSS-AES256-SHA,ECDH-ECDSA-AES256-SHA,ECDH-RSA-AES256-SHA,AES256-SHA,ECDHE-ECDSA-AES128-SHA,ECDHE-RSA-AES128-SHA,DHE-DSS-AES128-SHA,ECDH-ECDSA-AES128-SHA,ECDH-RSA-AES128-SHA,AES128-SHA,PSK-AES128-CBC-SHA,PSK-AES256-CBC-SHA,PSK-3DES-EDE-CBC-SHA,PSK-RC4-SHA] + ssl.ciphers: [ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-AES256-SHA384,ECDHE-RSA-AES256-SHA384,ECDHE-ECDSA-DES-CBC3-SHA,ECDH-ECDSA-AES256-GCM-SHA384,ECDH-RSA-AES256-GCM-SHA384,ECDH-ECDSA-AES256-SHA384,ECDH-RSA-AES256-SHA384,DHE-DSS-AES256-GCM-SHA384,DHE-DSS-AES256-SHA256,AES256-GCM-SHA384,AES256-SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES128-SHA256,ECDHE-RSA-AES128-SHA256,ECDH-ECDSA-AES128-GCM-SHA256,ECDH-RSA-AES128-GCM-SHA256,ECDH-ECDSA-AES128-SHA256,ECDH-RSA-AES128-SHA256,DHE-DSS-AES128-GCM-SHA256,DHE-DSS-AES128-SHA256,AES128-GCM-SHA256,AES128-SHA256,ECDHE-ECDSA-AES256-SHA,ECDHE-RSA-AES256-SHA,DHE-DSS-AES256-SHA,ECDH-ECDSA-AES256-SHA,ECDH-RSA-AES256-SHA,AES256-SHA,ECDHE-ECDSA-AES128-SHA,ECDHE-RSA-AES128-SHA,DHE-DSS-AES128-SHA,ECDH-ECDSA-AES128-SHA,ECDH-RSA-AES128-SHA,AES128-SHA,PSK-AES128-CBC-SHA,PSK-AES256-CBC-SHA,PSK-3DES-EDE-CBC-SHA,PSK-RC4-SHA] - ## Ciphers for TLS PSK. - ## Note that 'ciphers' and 'psk_ciphers' cannot - ## be configured at the same time. - ## See 'https://tools.ietf.org/html/rfc4279#section-2'. - #psk_ciphers: "PSK-AES128-CBC-SHA,PSK-AES256-CBC-SHA,PSK-3DES-EDE-CBC-SHA,PSK-RC4-SHA" - - ## SSL parameter renegotiation is a feature that allows a client and a server - ## to renegotiate the parameters of the SSL connection on the fly. - ## RFC 5746 defines a more secure way of doing this. By enabling secure renegotiation, - ## you drop support for the insecure renegotiation, prone to MitM attacks. - ## - ## Value: on | off - ## secure_renegotiate: off - - ## A performance optimization setting, it allows clients to reuse - ## pre-existing sessions, instead of initializing new ones. - ## Read more about it here. - ## - ## See: http://erlang.org/doc/man/ssl.html - ## - ## Value: on | off - ## reuse_sessions: on - - ## An important security setting, it forces the cipher to be set based - ## on the server-specified order instead of the client-specified order, - ## hence enforcing the (usually more properly configured) security - ## ordering of the server administrator. - ## - ## Value: on | off - ## honor_cipher_order: on - - ## Use the CN, DN or CRT field from the client certificate as a username. - ## Notice that 'verify' should be set as 'verify_peer'. - ## 'pem' encodes CRT in base64, and md5 is the md5 hash of CRT. - ## - ## Value: cn | dn | crt | pem | md5 - ## peer_cert_as_username: cn - - ## Use the CN, DN or CRT field from the client certificate as a username. - ## Notice that 'verify' should be set as 'verify_peer'. - ## 'pem' encodes CRT in base64, and md5 is the md5 hash of CRT. - ## - ## Value: cn | dn | crt | pem | md5 - ## peer_cert_as_clientid: cn - - ## TCP backlog for the SSL connection. - ## - ## See listener.tcp.$name.backlog - ## - ## Value: Number >= 0 - ## backlog: 1024 - - ## The TCP send timeout for the SSL connection. - ## - ## See listener.tcp.$name.send_timeout - ## - ## Value: Duration - ## send_timeout: 15s - - ## Close the SSL connection if send timeout. - ## - ## See: listener.tcp.$name.send_timeout_close - ## - ## Value: on | off - ## send_timeout_close: on - - ## The TCP receive buffer(os kernel) for the SSL connections. - ## - ## See: listener.tcp.$name.recbuf - ## - ## Value: Bytes - ## recbuf: 4KB - - ## The TCP send buffer(os kernel) for internal MQTT connections. - ## - ## See: listener.tcp.$name.sndbuf - ## - ## Value: Bytes - ## sndbuf: 4KB - - ## The size of the user-level software buffer used by the driver. - ## - ## See: listener.tcp.$name.buffer - ## - ## Value: Bytes - ## buffer: 4KB - - ## Sets the 'buffer: max(sndbuf, recbuf)' if this option is enabled. - ## - ## See: listener.tcp.$name.tune_buffer - ## - ## Value: on | off - ## tune_buffer: off - - ## The TCP_NODELAY flag for SSL connections. - ## - ## See: listener.tcp.$name.nodelay - ## - ## Value: true | false - ## nodelay: true - - ## The SO_REUSEADDR flag for MQTT/SSL Listener. - ## - ## Value: true | false - reuseaddr: true } -##-------------------------------------------------------------------- -## External WebSocket listener for MQTT protocol - -listener.ws.external { - ## $name is the IP address and port that the MQTT/WebSocket - ## listener will bind. - ## - ## Value: IP:Port | Port - ## - ## Examples: 8083, "127.0.0.1:8083", "::1:8083" - endpoint: 8083 - +## Socket options for websocket connections +example_common_websocket_options { ## The path of WebSocket MQTT endpoint ## - ## Value: URL Path - mqtt_path: "/mqtt" - - ## The acceptor pool for external MQTT/WebSocket listener. - ## - ## Value: Number - acceptors: 4 - - ## Maximum number of concurrent MQTT/WebSocket connections. - ## - ## Value: Number - max_connections: 102400 - - ## Maximum MQTT/WebSocket connections per second. - ## - ## Value: Number - max_conn_rate: 1000 - - ## Simulate the {active, N} option for the MQTT/WebSocket connections. - ## - ## Value: Number - active_n: 100 - - ## Zone of the external MQTT/WebSocket listener belonged to. - ## - ## Value: String - zone: external - - ## The access control for the MQTT/WebSocket listener. - ## - ## See: $name.access - ## - ## Value: ACL Rule - access.1: "allow all" - - ## If set to true, the server fails if the client does not have a Sec-WebSocket-Protocol to send. - ## Set to false for WeChat MiniApp. - ## - ## Value: true | false - ## fail_if_no_subprotocol: true - - ## Supported subprotocols - ## - ## Default: mqtt, mqtt-v3, mqtt-v3.1.1, mqtt-v5 - ## supported_subprotocols: "mqtt, mqtt-v3, mqtt-v3.1.1, mqtt-v5" - - ## Specify which HTTP header for real source IP if the EMQ X cluster is - ## deployed behind NGINX or HAProxy. - ## - ## Default: X-Forwarded-For - ## proxy_address_header: X-Forwarded-For - - ## Specify which HTTP header for real source port if the EMQ X cluster is - ## deployed behind NGINX or HAProxy. - ## - ## Default: X-Forwarded-Port - ## proxy_port_header: X-Forwarded-Port - - ## Enable the Proxy Protocol V1/2 if the EMQ cluster is deployed behind - ## HAProxy or Nginx. - ## - ## See: $name.proxy_protocol - ## - ## Value: on | off - ## proxy_protocol: on - - ## Sets the timeout for proxy protocol. - ## - ## See: $name.proxy_protocol_timeout - ## - ## Value: Duration - ## proxy_protocol_timeout: 3s - - ## Enable the option for X.509 certificate based authentication. - ## EMQX will use the common name of certificate as MQTT username. - ## Only support Proxy Protocol V2, the CN is available in Proxy Protocol V2 additional info - ## - ## Value: cn - ## peer_cert_as_username: cn - - ## Enable the option for X.509 certificate based authentication. - ## EMQX will use the common name of certificate as MQTT clientid. - ## Only support Proxy Protocol V2, the CN is available in Proxy Protocol V2 additional info - ## - ## Value: cn - ## peer_cert_as_clientid: cn - - ## The TCP backlog of external MQTT/WebSocket Listener. - ## - ## See: $name.backlog - ## - ## Value: Number >= 0 - backlog: 1024 - - ## The TCP send timeout for external MQTT/WebSocket connections. - ## - ## See: $name.send_timeout - ## - ## Value: Duration - send_timeout: 15s - - ## Close the MQTT/WebSocket connection if send timeout. - ## - ## See: $name.send_timeout_close - ## - ## Value: on | off - send_timeout_close: on - - ## The TCP receive buffer(os kernel) for external MQTT/WebSocket connections. - ## - ## See: $name.recbuf - ## - ## Value: Bytes - ## recbuf: 2KB - - ## The TCP send buffer(os kernel) for external MQTT/WebSocket connections. - ## - ## See: $name.sndbuf - ## - ## Value: Bytes - ## sndbuf: 2KB - - ## The size of the user-level software buffer used by the driver. - ## - ## See: $name.buffer - ## - ## Value: Bytes - ## buffer: 2KB - - ## Sets the 'buffer: max(sndbuf, recbuf)' if this option is enabled. - ## - ## See: $name.tune_buffer - ## - ## Value: on | off - ## tune_buffer: off - - ## The TCP_NODELAY flag for external MQTT/WebSocket connections. - ## - ## See: $name.nodelay - ## - ## Value: true | false - nodelay: true - - ## The compress flag for external MQTT/WebSocket connections. - ## - ## If this Value is set true,the websocket message would be compressed - ## - ## Value: true | false - ## compress: true - - ## The level of deflate options for external MQTT/WebSocket connections. - ## - ## See: $name.deflate_opts.level - ## - ## Value: none | default | best_compression | best_speed - ## deflate_opts.level: default - - ## The mem_level of deflate options for external MQTT/WebSocket connections. - ## - ## See: $name.deflate_opts.mem_level - ## - ## Valid range is 1-9 - ## deflate_opts.mem_level: 8 - - ## The strategy of deflate options for external MQTT/WebSocket connections. - ## - ## See: $name.deflate_opts.strategy - ## - ## Value: default | filtered | huffman_only | rle - ## deflate_opts.strategy: default - - ## The deflate option for external MQTT/WebSocket connections. - ## - ## See: $name.deflate_opts.server_context_takeover - ## - ## Value: takeover | no_takeover - ## deflate_opts.server_context_takeover: takeover - - ## The deflate option for external MQTT/WebSocket connections. - ## - ## See: $name.deflate_opts.client_context_takeover - ## - ## Value: takeover | no_takeover - ## deflate_opts.client_context_takeover: takeover - - ## The deflate options for external MQTT/WebSocket connections. - ## - ## See: $name.deflate_opts.server_max_window_bits - ## - ## Valid range is 8-15 - ## deflate_opts.server_max_window_bits: 15 - - ## The deflate options for external MQTT/WebSocket connections. - ## - ## See: $name.deflate_opts.client_max_window_bits - ## - ## Valid range is 8-15 - ## deflate_opts.client_max_window_bits: 15 - - ## The idle timeout for external MQTT/WebSocket connections. - ## - ## See: $name.idle_timeout - ## - ## Value: Duration - ## idle_timeout: 60s - - ## The max frame size for external MQTT/WebSocket connections. - ## - ## - ## Value: Number - ## max_frame_size: 0 + ## @doc listeners..websocket.mqtt_path + ## ValueType: Path + ## Default: "/mqtt" + websocket.mqtt_path: "/mqtt" ## Whether a WebSocket message is allowed to contain multiple MQTT packets ## - ## Value: single | multiple - mqtt_piggyback: multiple + ## @doc listeners..websocket.mqtt_piggyback + ## ValueType: single | multiple + ## Default: multiple + websocket.mqtt_piggyback: multiple - ## By default, EMQX web socket connection does not restrict connections to specific origins. - ## It also, by default, does not enforce the presence of origin in request headers for WebSocket connections. - ## Because of this, a malicious user could potentially hijack an existing web-socket connection to EMQX. + ## The compress flag for external WebSocket connections. + ## + ## If this Value is set true,the websocket message would be compressed + ## + ## @doc listeners..websocket.compress + ## ValueType: Boolean + ## Default: false + websocket.compress: false - ## To prevent this, users can set allowed origin headers in their ws connection to EMQX. - ## Example for WS connection - ## To enables origin check in header for websocket connnection, - ## set `check_origin_enable: true`. By default it is false, - ## When it is set to true and no origin is present in the header of a ws connection request, the request fails. + ## The idle timeout for external WebSocket connections. + ## + ## @doc listeners..websocket.idle_timeout + ## ValueType: Duration | infinity + ## Default: infinity + websocket.idle_timeout: infinity - ## To allow origins to be absent in header in the websocket connection when check_origin_enable is true, - ## set `allow_origin_absence: true` + ## The max frame size for external WebSocket connections. + ## + ## @doc listeners..websocket.max_frame_size + ## ValueType: Size + ## Default: infinity + websocket.max_frame_size: infinity - ## Enabling origin check implies there are specific valid origins allowed for ws connection. - ## To set the list of allowed origins in header for websocket connection - ## check_origins: http://localhost:18083(localhost dashboard url), http://yourapp.com` - ## check_origins config allows a comma separated list of origins so you can specify as many origins are you want. - ## With these configs, you can allow only connections from only authorized origins to your broker + ## If set to true, the server fails if the client does not + ## have a Sec-WebSocket-Protocol to send. + ## Set to false for WeChat MiniApp. + ## + ## @doc listeners..websocket.fail_if_no_subprotocol + ## ValueType: Boolean + ## Default: true + websocket.fail_if_no_subprotocol: true + + ## Supported subprotocols + ## + ## @doc listeners..websocket.supported_subprotocols + ## ValueType: String + ## Default: mqtt, mqtt-v3, mqtt-v3.1.1, mqtt-v5 + websocket.supported_subprotocols: "mqtt, mqtt-v3, mqtt-v3.1.1, mqtt-v5" ## Enable origin check in header for websocket connection ## - ## Value: true | false (default false) - check_origin_enable: false + ## @doc listeners..websocket.check_origin_enable + ## ValueType: Boolean + ## Default: false + websocket.check_origin_enable: false - ## Allow origin to be absent in header in websocket connection when check_origin_enable is true + ## Allow origin to be absent in header in websocket connection + ## when check_origin_enable is true ## - ## Value: true | false (default true) - allow_origin_absence: true + ## @doc listeners..websocket.allow_origin_absence + ## ValueType: Boolean + ## Default: true + websocket.allow_origin_absence: true ## Comma separated list of allowed origin in header for websocket connection ## - ## Value: http://url eg. local http dashboard url - http://localhost:18083, http://127.0.0.1:18083 - check_origins: "http://localhost:18083, http://127.0.0.1:18083" -} - -##-------------------------------------------------------------------- -## External WebSocket/SSL listener for MQTT Protocol -listener.wss.external { - ## listener.wss.$name.endpoint is the IP address and port that the MQTT/WebSocket/SSL - ## listener will bind. - ## - ## Value: IP:Port | Port - ## - ## Examples: 8084, "127.0.0.1:8084", "::1:8084" - endpoint: 8084 - - ## The path of WebSocket MQTT endpoint - ## - ## Value: URL Path - mqtt_path: "/mqtt" - - ## The acceptor pool for external MQTT/WebSocket/SSL listener. - ## - ## Value: Number - acceptors: 4 - - ## Maximum number of concurrent MQTT/Webwocket/SSL connections. - ## - ## Value: Number - max_connections: 102400 - - ## Maximum MQTT/WebSocket/SSL connections per second. - ## - ## See: listener.tcp.$name.max_conn_rate - ## - ## Value: Number - max_conn_rate: 1000 - - ## Simulate the {active, N} option for the MQTT/WebSocket/SSL connections. - ## - ## Value: Number - active_n: 100 - - ## Zone of the external MQTT/WebSocket/SSL listener belonged to. - ## - ## Value: String - zone: external - - ## The access control rules for the MQTT/WebSocket/SSL listener. - ## - ## See: listener.tcp.$name.access. - ## - ## Value: ACL Rule - access.1: "allow all" - - ## If set to true, the server fails if the client does not have a Sec-WebSocket-Protocol to send. - ## Set to false for WeChat MiniApp. - ## - ## Value: true | false - ## fail_if_no_subprotocol: true - - ## Supported subprotocols - ## - ## Default: mqtt, mqtt-v3, mqtt-v3.1.1, mqtt-v5 - ## supported_subprotocols: "mqtt, mqtt-v3, mqtt-v3.1.1, mqtt-v5" + ## @doc listeners..websocket.check_origins + ## ValueType: String + ## Examples: + ## local http dashboard url + ## check_origins: "http://localhost:18083, http://127.0.0.1:18083" + ## Default: "" + websocket.check_origins: "http://localhost:18083, http://127.0.0.1:18083" ## Specify which HTTP header for real source IP if the EMQ X cluster is ## deployed behind NGINX or HAProxy. ## + ## @doc listeners..websocket.proxy_address_header + ## ValueType: String ## Default: X-Forwarded-For - ## proxy_address_header: X-Forwarded-For + websocket.proxy_address_header: X-Forwarded-For ## Specify which HTTP header for real source port if the EMQ X cluster is ## deployed behind NGINX or HAProxy. ## + ## @doc listeners..websocket.proxy_port_header + ## ValueType: String ## Default: X-Forwarded-Port - ## proxy_port_header: X-Forwarded-Port + websocket.proxy_port_header: X-Forwarded-Port - ## Enable the Proxy Protocol V1/2 support. - ## - ## See: listener.tcp.$name.proxy_protocol - ## - ## Value: on | off - ## proxy_protocol: on + websocket.deflate_opts { + ## The level of deflate options for external WebSocket connections. + ## + ## @doc listeners..websocket.deflate_opts.level + ## ValueType: none | default | best_compression | best_speed + ## Default: default + level: default - ## Sets the timeout for proxy protocol. - ## - ## See: listener.tcp.$name.proxy_protocol_timeout - ## - ## Value: Duration - ## proxy_protocol_timeout: 3s + ## The mem_level of deflate options for external WebSocket connections. + ## + ## @doc listeners..websocket.deflate_opts.mem_level + ## ValueType: Integer + ## Range: [1,9] + ## Default: 8 + mem_level: 8 - ## TLS versions only to protect from POODLE attack. - ## - ## See: listener.ssl.$name.tls_versions - ## - ## Value: String, seperated by ',' - ## NOTE: Do not use tlsv1.3 if emqx is running on OTP-22 or earlier - ## tls_versions: "tlsv1.3,tlsv1.2,tlsv1.1,tlsv1" + ## The strategy of deflate options for external WebSocket connections. + ## + ## @doc listeners..websocket.deflate_opts.strategy + ## ValueType: default | filtered | huffman_only | rle + ## Default: default + strategy: default - ## Path to the file containing the user's private PEM-encoded key. - ## - ## See: listener.ssl.$name.keyfile - ## - ## Value: File - keyfile: "{{ platform_etc_dir }}/certs/key.pem" + ## The deflate option for external WebSocket connections. + ## + ## @doc listeners..websocket.deflate_opts.server_context_takeover + ## ValueType: takeover | no_takeover + ## Default: takeover + server_context_takeover: takeover - ## Path to a file containing the user certificate. - ## - ## See: listener.ssl.$name.certfile - ## - ## Value: File - certfile: "{{ platform_etc_dir }}/certs/cert.pem" + ## The deflate option for external WebSocket connections. + ## + ## @doc listeners..websocket.deflate_opts.client_context_takeover + ## ValueType: takeover | no_takeover + ## Default: takeover + client_context_takeover: takeover - ## Path to the file containing PEM-encoded CA certificates. - ## - ## See: listener.ssl.$name.cacert - ## - ## Value: File - ## cacertfile: "{{ platform_etc_dir }}/certs/cacert.pem" + ## The deflate options for external WebSocket connections. + ## + ## + ## @doc listeners..websocket.deflate_opts.server_max_window_bits + ## ValueType: Integer + ## Range: [8,15] + ## Default: 15 + server_max_window_bits: 15 - ## Maximum number of non-self-issued intermediate certificates that - ## can follow the peer certificate in a valid certification path. - ## - ## See: listener.ssl.external.depth - ## - ## Value: Number - ## depth: 10 - - ## String containing the user's password. Only used if the private keyfile - ## is password-protected. - ## - ## See: listener.ssl.$name.key_password - ## - ## Value: String - ## key_password: yourpass - - ## See: listener.ssl.$name.dhfile - ## - ## Value: File - ## listener.ssl.external.dhfile: "{{ platform_etc_dir }}/certs/dh-params.pem" - - ## See: listener.ssl.$name.verify - ## - ## Value: verify_peer | verify_none - ## verify: verify_peer - - ## See: listener.ssl.$name.fail_if_no_peer_cert - ## - ## Value: false | true - ## fail_if_no_peer_cert: true - - ## See: listener.ssl.$name.ciphers - ## - ## Value: Ciphers - ciphers: "TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256,TLS_CHACHA20_POLY1305_SHA256,TLS_AES_128_CCM_SHA256,TLS_AES_128_CCM_8_SHA256,ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-AES256-SHA384,ECDHE-RSA-AES256-SHA384,ECDHE-ECDSA-DES-CBC3-SHA,ECDH-ECDSA-AES256-GCM-SHA384,ECDH-RSA-AES256-GCM-SHA384,ECDH-ECDSA-AES256-SHA384,ECDH-RSA-AES256-SHA384,DHE-DSS-AES256-GCM-SHA384,DHE-DSS-AES256-SHA256,AES256-GCM-SHA384,AES256-SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES128-SHA256,ECDHE-RSA-AES128-SHA256,ECDH-ECDSA-AES128-GCM-SHA256,ECDH-RSA-AES128-GCM-SHA256,ECDH-ECDSA-AES128-SHA256,ECDH-RSA-AES128-SHA256,DHE-DSS-AES128-GCM-SHA256,DHE-DSS-AES128-SHA256,AES128-GCM-SHA256,AES128-SHA256,ECDHE-ECDSA-AES256-SHA,ECDHE-RSA-AES256-SHA,DHE-DSS-AES256-SHA,ECDH-ECDSA-AES256-SHA,ECDH-RSA-AES256-SHA,AES256-SHA,ECDHE-ECDSA-AES128-SHA,ECDHE-RSA-AES128-SHA,DHE-DSS-AES128-SHA,ECDH-ECDSA-AES128-SHA,ECDH-RSA-AES128-SHA,AES128-SHA" - - ## Ciphers for TLS PSK. - ## Note that 'ciphers' and 'psk_ciphers' cannot - ## be configured at the same time. - ## See 'https://tools.ietf.org/html/rfc4279#section-2'. - ## psk_ciphers: "PSK-AES128-CBC-SHA,PSK-AES256-CBC-SHA,PSK-3DES-EDE-CBC-SHA,PSK-RC4-SHA" - - ## See: listener.ssl.$name.secure_renegotiate - ## - ## Value: on | off - ## secure_renegotiate: off - - ## See: listener.ssl.$name.reuse_sessions - ## - ## Value: on | off - ## reuse_sessions: on - - ## See: listener.ssl.$name.honor_cipher_order - ## - ## Value: on | off - ## honor_cipher_order: on - - ## See: listener.ssl.$name.peer_cert_as_username - ## - ## Value: cn | dn | crt | pem | md5 - ## peer_cert_as_username: cn - - ## See: listener.ssl.$name.peer_cert_as_clientid - ## - ## Value: cn | dn | crt | pem | md5 - ## peer_cert_as_clientid: cn - - ## TCP backlog for the WebSocket/SSL connection. - ## - ## See: listener.tcp.$name.backlog - ## - ## Value: Number >= 0 - backlog: 1024 - - ## The TCP send timeout for the WebSocket/SSL connection. - ## - ## See: listener.tcp.$name.send_timeout - ## - ## Value: Duration - send_timeout: 15s - - ## Close the WebSocket/SSL connection if send timeout. - ## - ## See: listener.tcp.$name.send_timeout_close - ## - ## Value: on | off - send_timeout_close: on - - ## The TCP receive buffer(os kernel) for the WebSocket/SSL connections. - ## - ## See: listener.tcp.$name.recbuf - ## - ## Value: Bytes - ## recbuf: 4KB - - ## The TCP send buffer(os kernel) for the WebSocket/SSL connections. - ## - ## See: listener.tcp.$name.sndbuf - ## - ## Value: Bytes - ## sndbuf: 4KB - - ## The size of the user-level software buffer used by the driver. - ## - ## See: listener.tcp.$name.buffer - ## - ## Value: Bytes - ## buffer: 4KB - - ## The TCP_NODELAY flag for WebSocket/SSL connections. - ## - ## See: listener.tcp.$name.nodelay - ## - ## Value: true | false - ## nodelay: true - - ## The compress flag for external WebSocket/SSL connections. - ## - ## If this Value is set true,the websocket message would be compressed - ## - ## Value: true | false - ## compress: true - - ## The level of deflate options for external WebSocket/SSL connections. - ## - ## See: listener.wss.$name.deflate_opts.level - ## - ## Value: none | default | best_compression | best_speed - ## deflate_opts.level: default - - ## The mem_level of deflate options for external WebSocket/SSL connections. - ## - ## See: listener.wss.$name.deflate_opts.mem_level - ## - ## Valid range is 1-9 - ## deflate_opts.mem_level: 8 - - ## The strategy of deflate options for external WebSocket/SSL connections. - ## - ## See: listener.wss.$name.deflate_opts.strategy - ## - ## Value: default | filtered | huffman_only | rle - ## deflate_opts.strategy: default - - ## The deflate option for external WebSocket/SSL connections. - ## - ## See: listener.wss.$name.deflate_opts.server_context_takeover - ## - ## Value: takeover | no_takeover - ## deflate_opts.server_context_takeover: takeover - - ## The deflate option for external WebSocket/SSL connections. - ## - ## See: listener.wss.$name.deflate_opts.client_context_takeover - ## - ## Value: takeover | no_takeover - ## deflate_opts.client_context_takeover: takeover - - ## The deflate options for external WebSocket/SSL connections. - ## - ## See: listener.wss.$name.deflate_opts.server_max_window_bits - ## - ## Valid range is 8-15 - ## deflate_opts.server_max_window_bits: 15 - - ## The deflate options for external WebSocket/SSL connections. - ## - ## See: listener.wss.$name.deflate_opts.client_max_window_bits - ## - ## Valid range is 8-15 - ## deflate_opts.client_max_window_bits: 15 - - ## The idle timeout for external WebSocket/SSL connections. - ## - ## See: listener.wss.$name.idle_timeout - ## - ## Value: Duration - ## idle_timeout: 60s - - ## The max frame size for external WebSocket/SSL connections. - ## - ## Value: Number - ## max_frame_size: 0 - - ## Whether a WebSocket message is allowed to contain multiple MQTT packets - ## - ## Value: single | multiple - mqtt_piggyback: multiple - ## Enable origin check in header for secure websocket connection - ## - ## Value: true | false (default false) - check_origin_enable: false - ## Allow origin to be absent in header in secure websocket connection when check_origin_enable is true - ## - ## Value: true | false (default true) - allow_origin_absence: true - ## Comma separated list of allowed origin in header for secure websocket connection - ## - ## Value: http://url eg. https://localhost:8084, https://127.0.0.1:8084 - check_origins: "https://localhost:8084, https://127.0.0.1:8084" -} - -##-------------------------------------------------------------------- -## External QUIC listener for MQTT Protocol - -listener.quic.external { - ## listener.quic.$name.endpoint is the IP address and port that the MQTT/QUIC - ## listener will bind. - ## - ## Value: IP:Port | Port - ## - ## Examples: 14567, 127.0.0.1:14567, ::1:14567 - endpoint: 14567 - - ## The acceptor pool for external MQTT/QUIC listener. - ## - ## Value: Number - acceptors: 4 - - ## Maximum number of concurrent MQTT/Webwocket/SSL connections. - ## - ## Value: Number - max_connections: 16 - - ## Maximum MQTT/QUIC connections per second. - ## - ## See: listener.tcp.$name.max_conn_rate - ## - ## Value: Number - max_conn_rate: 1000 - - ## Simulate the {active, N} option for the MQTT/QUIC connections. - ## @todo - ## Value: Number - ## active_n: 100 - - ## Zone of the external MQTT/QUIC listener belonged to. - ## - ## Value: String - zone: external - - ## Path to the file containing the user's private PEM-encoded key. - ## - ## See: listener.ssl.$name.keyfile - ## - ## Value: File - keyfile: "{{ platform_etc_dir }}/certs/key.pem" - - ## Path to a file containing the user certificate. - ## - ## See: listener.ssl.$name.certfile - ## - ## Value: File - certfile: "{{ platform_etc_dir }}/certs/cert.pem" - - ## Path to the file containing PEM-encoded CA certificates. - ## @todo - ## See: listener.ssl.$name.cacert - ## - ## Value: File - ## cacertfile: {{ platform_etc_dir }}/certs/cacert.pem - - ## String containing the user's password. Only used if the private keyfile - ## is password-protected. - ## @todo - ## See: listener.ssl.$name.key_password - ## - ## Value: String - ## key_password: yourpass - - ## See: listener.ssl.$name.verify - ## @todo - ## Value: verify_peer | verify_none - ## verify: verify_peer - - ## See: listener.ssl.$name.fail_if_no_peer_cert - ## @todo - ## Value: false | true - ## fail_if_no_peer_cert: true - - ## See: listener.ssl.$name.ciphers - ## @todo - ## Value: Ciphers - ciphers: "TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256,TLS_CHACHA20_POLY1305_SHA256" - - ## Ciphers for TLS PSK. - ## @todo - ## Note that 'ciphers' and 'psk_ciphers' cannot - ## be configured at the same time. - ## See 'https://tools.ietf.org/html/rfc4279#section-2'. - ## psk_ciphers: PSK-AES128-CBC-SHA,PSK-AES256-CBC-SHA,PSK-3DES-EDE-CBC-SHA,PSK-RC4-SHA - - ## See: listener.ssl.$name.honor_cipher_order - ## @todo - ## Value: on | off - ## honor_cipher_order: on - - ## The send timeout for the QUIC stream. - ## @todo - ## - ## Value: Duration - # send_timeout: 15s - - ## Close the QUIC connection if send timeout. - ## @todo - ## See: listener.tcp.$name.send_timeout_close - ## - ## Value: on | off - ## send_timeout_close: on - - ## The receive buffer for the QUIC connections. - ## @todo - ## See: listener.tcp.$name.recbuf - ## - ## Value: Bytes - ## recbuf: 4KB - - ## The TCP send buffer(os kernel) for the QUIC connections. - ## @todo - ## See: listener.tcp.$name.sndbuf - ## - ## Value: Bytes - ## sndbuf: 4KB - - ## The size of the user-level software buffer used by the driver. - ## @todo - ## See: listener.tcp.$name.buffer - ## - ## Value: Bytes - ## buffer: 4KB - - ## The idle timeout for external QUIC connections. - ## @todo - ## See: listener.quic.$name.idle_timeout - ## - ## Value: Duration - ## idle_timeout: 60s - - ## The max frame size for external QUIC connections. - ## @todo - ## Value: Number - ## max_frame_size: 0 -} - -##------------------------------------------------------------------- -## Plugins -##------------------------------------------------------------------- -plugins: { - ## The directory of extension plugins. - ## - ## Value: File - expand_plugins_dir: "{{ platform_plugins_dir }}/" - -} - -##-------------------------------------------------------------------- -## Broker -##-------------------------------------------------------------------- -broker: { - ## System interval of publishing $SYS messages. - ## - ## Value: Duration - ## Default: 1m, 1 minute - sys_interval: "1m" - - ## System heartbeat interval of publishing following heart beat message: - ## - "$SYS/brokers//uptime" - ## - "$SYS/brokers//datetime" - ## - ## Value: Duration - ## Default: 30s - sys_heartbeat: "30s" - - ## Session locking strategy in a cluster. - ## - ## Value: Enum - ## - local - ## - leader - ## - quorum - ## - all - session_locking_strategy: quorum - - ## Dispatch strategy for shared subscription - ## - ## Value: Enum - ## - random - ## - round_robin - ## - sticky - ## - hash # same as hash_clientid - ## - hash_clientid - ## - hash_topic - shared_subscription_strategy: random - - ## Enable/disable shared dispatch acknowledgement for QoS1 and QoS2 messages - ## This should allow messages to be dispatched to a different subscriber in - ## the group in case the picked (based on shared_subscription_strategy) one # is offline - ## - ## Value: Enum - ## - true - ## - false - shared_dispatch_ack_enabled: false - - ## Enable batch clean for deleted routes. - ## - ## Value: Flag - route_batch_clean: off - - perf: { - ## Performance toggle for subscribe/unsubscribe wildcard topic. - ## Change this toggle only when there are many wildcard topics. - ## Value: Enum - ## - key: mnesia translational updates with per-key locks. recommended for single node setup. - ## - tab: mnesia translational updates with table lock. recommended for multi-nodes setup. - ## - global: global lock protected updates. recommended for larger cluster. - ## NOTE: when changing from/to 'global' lock, it requires all nodes in the cluster - ## to be stopped before the change. - # route_lock_type: key - - ## Enable trie path compaction. - ## Enabling it significantly improves wildcard topic subscribe rate, - ## if wildcard topics have unique prefixes like: 'sensor/{{id}}/+/', - ## where ID is unique per subscriber. - ## - ## Topic match performance (when publishing) may degrade if messages - ## are mostly published to topics with large number of levels. - ## - ## NOTE: This is a cluster-wide configuration. - ## It rquires all nodes to be stopped before changing it. - ## - ## Value: Enum - ## - true: enable trie path compaction - ## - false: disable trie path compaction - # trie_compaction: true - } -} - -sysmon: { - ## Enable Long GC monitoring. Disable if the value is 0. - ## Notice: don't enable the monitor in production for: - ## https://github.com/erlang/otp/blob/feb45017da36be78d4c5784d758ede619fa7bfd3/erts/emulator/beam/erl_gc.c#L421 - ## - ## Value: Duration - ## - h: hour - ## - m: minute - ## - s: second - ## - ms: milliseconds - ## - ## Examples: - ## - 2h: 2 hours - ## - 30m: 30 minutes - ## - 0.1s: 0.1 seconds - ## - 100ms : 100 milliseconds - ## - ## Default: 0ms - long_gc: 0 - - ## Enable Long Schedule(ms) monitoring. - ## - ## See: http://erlang.org/doc/man/erlang.html#system_monitor-2 - ## - ## Value: Duration - ## - h: hour - ## - m: minute - ## - s: second - ## - ms: milliseconds - ## - ## Examples: - ## - 2h: 2 hours - ## - 30m: 30 minutes - ## - 100ms: 100 milliseconds - ## - ## Default: 0ms - long_schedule: "240ms" - - ## Enable Large Heap monitoring. - ## - ## See: http://erlang.org/doc/man/erlang.html#system_monitor-2 - ## - ## Value: bytes - ## - ## Default: 8M words. 32MB on 32-bit VM, 64MB on 64-bit VM. - large_heap: "8MB" - - ## Enable Busy Port monitoring. - ## - ## See: http://erlang.org/doc/man/erlang.html#system_monitor-2 - ## - ## Value: true | false - busy_port: false - - ## Enable Busy Dist Port monitoring. - ## - ## See: http://erlang.org/doc/man/erlang.html#system_monitor-2 - ## - ## Value: true | false - busy_dist_port: true -} - -os_mon: { - ## The time interval for the periodic cpu check - ## - ## Value: Duration - ## -h: hour, e.g. '2h' for 2 hours - ## -m: minute, e.g. '5m' for 5 minutes - ## -s: second, e.g. '30s' for 30 seconds - ## - ## Default: 60s - cpu_check_interval: "60s" - - ## The threshold, as percentage of system cpu, for how much system cpu can be used before the corresponding alarm is set. - ## - ## Default: 80% - cpu_high_watermark: "80%" - - ## The threshold, as percentage of system cpu, for how much system cpu can be used before the corresponding alarm is clear. - ## - ## Default: 60% - cpu_low_watermark: "60%" - - ## The time interval for the periodic memory check - ## - ## Value: Duration - ## -h: hour, e.g. '2h' for 2 hours - ## -m: minute, e.g. '5m' for 5 minutes - ## -s: second, e.g. '30s' for 30 seconds - ## - ## Default: 60s - mem_check_interval: "60s" - - ## The threshold, as percentage of system memory, for how much system memory can be allocated before the corresponding alarm is set. - ## - ## Default: 70% - sysmem_high_watermark: "70%" - - ## The threshold, as percentage of system memory, for how much system memory can be allocated by one Erlang process before the corresponding alarm is set. - ## - ## Default: 5% - procmem_high_watermark: "5%" - -} - -vm_mon: { - ## The time interval for the periodic process limit check - ## - ## Value: Duration - ## - ## Default: 30s - check_interval: "30s" - - ## The threshold, as percentage of processes, for how many processes can simultaneously exist at the local node before the corresponding alarm is set. - ## - ## Default: 80% - process_high_watermark: "80%" - - ## The threshold, as percentage of processes, for how many processes can simultaneously exist at the local node before the corresponding alarm is clear. - ## - ## Default: 60% - process_low_watermark: "60%" -} - -alarm: { - ## Specifies the actions to take when an alarm is activated - ## - ## Value: String - ## - log - ## - publish - ## - ## Default: "log,publish" - actions: "log,publish" - - ## The maximum number of deactivated alarms - ## - ## Value: Integer - ## - ## Default: 1000 - size_limit: 1000 - - ## Validity Period of deactivated alarms - ## - ## Value: Duration - ## - h: hour - ## - m: minute - ## - s: second - ## - ms: milliseconds - ## - ## Default: 24h - validity_period: "24h" + ## The deflate options for external WebSocket connections. + ## + ## @doc listeners..websocket.deflate_opts.client_max_window_bits + ## ValueType: Integer + ## Range: [8,15] + ## Default: 15 + client_max_window_bits: 15 + } } diff --git a/apps/emqx/include/emqx.hrl b/apps/emqx/include/emqx.hrl index a11c30cb4..60dccd9a3 100644 --- a/apps/emqx/include/emqx.hrl +++ b/apps/emqx/include/emqx.hrl @@ -35,12 +35,6 @@ -define(ERTS_MINIMUM_REQUIRED, "10.0"). -%%-------------------------------------------------------------------- -%% Configs -%%-------------------------------------------------------------------- - --define(NO_PRIORITY_TABLE, none). - %%-------------------------------------------------------------------- %% Topics' prefix: $SYS | $queue | $share %%-------------------------------------------------------------------- diff --git a/apps/emqx/rebar.config b/apps/emqx/rebar.config index 92ff5a9b3..b05ea48dd 100644 --- a/apps/emqx/rebar.config +++ b/apps/emqx/rebar.config @@ -12,7 +12,7 @@ [ {gproc, {git, "https://github.com/uwiger/gproc", {tag, "0.8.0"}}} , {jiffy, {git, "https://github.com/emqx/jiffy", {tag, "1.0.5"}}} , {cowboy, {git, "https://github.com/emqx/cowboy", {tag, "2.8.2"}}} - , {esockd, {git, "https://github.com/emqx/esockd", {tag, "5.8.0"}}} + , {esockd, {git, "https://github.com/emqx/esockd", {tag, "5.8.2"}}} , {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.10.3"}}} , {gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.5.1"}}} , {hocon, {git, "https://github.com/emqx/hocon.git", {tag, "0.10.3"}}} diff --git a/apps/emqx/src/emqx_access_control.erl b/apps/emqx/src/emqx_access_control.erl index f0c39aad6..71531c421 100644 --- a/apps/emqx/src/emqx_access_control.erl +++ b/apps/emqx/src/emqx_access_control.erl @@ -28,29 +28,23 @@ -spec(authenticate(emqx_types:clientinfo()) -> ok | {ok, binary()} | {continue, map()} | {continue, binary(), map()} | {error, term()}). -authenticate(Credential = #{zone := Zone}) -> - %% TODO: Rename to bypass_authentication - case emqx_zone:get_env(Zone, bypass_auth_plugins, false) of - true -> - ok; - false -> - run_hooks('client.authenticate', [Credential], ok) - end. +authenticate(Credential) -> + run_hooks('client.authenticate', [Credential], ok). %% @doc Check ACL --spec(authorize(emqx_types:clientinfo(), emqx_types:pubsub(), emqx_types:topic()) - -> allow | deny). -authorize(ClientInfo, PubSub, Topic) -> - case emqx_acl_cache:is_enabled() of +-spec authorize(emqx_types:clientinfo(), emqx_types:pubsub(), emqx_types:topic()) + -> allow | deny. +authorize(ClientInfo = #{zone := Zone, listener := Listener}, PubSub, Topic) -> + case emqx_acl_cache:is_enabled(Zone, Listener) of true -> check_authorization_cache(ClientInfo, PubSub, Topic); false -> do_authorize(ClientInfo, PubSub, Topic) end. -check_authorization_cache(ClientInfo, PubSub, Topic) -> - case emqx_acl_cache:get_acl_cache(PubSub, Topic) of +check_authorization_cache(ClientInfo = #{zone := Zone, listener := Listener}, PubSub, Topic) -> + case emqx_acl_cache:get_acl_cache(Zone, Listener, PubSub, Topic) of not_found -> AclResult = do_authorize(ClientInfo, PubSub, Topic), - emqx_acl_cache:put_acl_cache(PubSub, Topic, AclResult), + emqx_acl_cache:put_acl_cache(Zone, Listener, PubSub, Topic, AclResult), AclResult; AclResult -> AclResult end. diff --git a/apps/emqx/src/emqx_acl_cache.erl b/apps/emqx/src/emqx_acl_cache.erl index 4cbe6b06a..d4c7cfbdb 100644 --- a/apps/emqx/src/emqx_acl_cache.erl +++ b/apps/emqx/src/emqx_acl_cache.erl @@ -18,15 +18,15 @@ -include("emqx.hrl"). --export([ list_acl_cache/0 - , get_acl_cache/2 - , put_acl_cache/3 - , cleanup_acl_cache/0 +-export([ list_acl_cache/2 + , get_acl_cache/4 + , put_acl_cache/5 + , cleanup_acl_cache/2 , empty_acl_cache/0 , dump_acl_cache/0 - , get_cache_max_size/0 - , get_cache_ttl/0 - , is_enabled/0 + , get_cache_max_size/2 + , get_cache_ttl/2 + , is_enabled/2 , drain_cache/0 ]). @@ -50,43 +50,45 @@ cache_k(PubSub, Topic)-> {PubSub, Topic}. cache_v(AclResult)-> {AclResult, time_now()}. drain_k() -> {?MODULE, drain_timestamp}. --spec(is_enabled() -> boolean()). -is_enabled() -> - application:get_env(emqx, enable_acl_cache, true). +-spec(is_enabled(atom(), atom()) -> boolean()). +is_enabled(Zone, Listener) -> + emqx_config:get_listener_conf(Zone, Listener, [acl, cache, enable]). --spec(get_cache_max_size() -> integer()). -get_cache_max_size() -> - application:get_env(emqx, acl_cache_max_size, 32). +-spec(get_cache_max_size(atom(), atom()) -> integer()). +get_cache_max_size(Zone, Listener) -> + emqx_config:get_listener_conf(Zone, Listener, [acl, cache, max_size]). --spec(get_cache_ttl() -> integer()). -get_cache_ttl() -> - application:get_env(emqx, acl_cache_ttl, 60000). +-spec(get_cache_ttl(atom(), atom()) -> integer()). +get_cache_ttl(Zone, Listener) -> + emqx_config:get_listener_conf(Zone, Listener, [acl, cache, ttl]). --spec(list_acl_cache() -> [acl_cache_entry()]). -list_acl_cache() -> - cleanup_acl_cache(), +-spec(list_acl_cache(atom(), atom()) -> [acl_cache_entry()]). +list_acl_cache(Zone, Listener) -> + cleanup_acl_cache(Zone, Listener), map_acl_cache(fun(Cache) -> Cache end). %% We'll cleanup the cache before replacing an expired acl. --spec(get_acl_cache(emqx_types:pubsub(), emqx_topic:topic()) -> (acl_result() | not_found)). -get_acl_cache(PubSub, Topic) -> +-spec get_acl_cache(atom(), atom(), emqx_types:pubsub(), emqx_topic:topic()) -> + acl_result() | not_found. +get_acl_cache(Zone, Listener, PubSub, Topic) -> case erlang:get(cache_k(PubSub, Topic)) of undefined -> not_found; {AclResult, CachedAt} -> - if_expired(CachedAt, + if_expired(get_cache_ttl(Zone, Listener), CachedAt, fun(false) -> AclResult; (true) -> - cleanup_acl_cache(), + cleanup_acl_cache(Zone, Listener), not_found end) end. %% If the cache get full, and also the latest one %% is expired, then delete all the cache entries --spec(put_acl_cache(emqx_types:pubsub(), emqx_topic:topic(), acl_result()) -> ok). -put_acl_cache(PubSub, Topic, AclResult) -> - MaxSize = get_cache_max_size(), true = (MaxSize =/= 0), +-spec put_acl_cache(atom(), atom(), emqx_types:pubsub(), emqx_topic:topic(), acl_result()) + -> ok. +put_acl_cache(Zone, Listener, PubSub, Topic, AclResult) -> + MaxSize = get_cache_max_size(Zone, Listener), true = (MaxSize =/= 0), Size = get_cache_size(), case Size < MaxSize of true -> @@ -94,7 +96,7 @@ put_acl_cache(PubSub, Topic, AclResult) -> false -> NewestK = get_newest_key(), {_AclResult, CachedAt} = erlang:get(NewestK), - if_expired(CachedAt, + if_expired(get_cache_ttl(Zone, Listener), CachedAt, fun(true) -> % all cache expired, cleanup first empty_acl_cache(), @@ -121,10 +123,10 @@ evict_acl_cache() -> decr_cache_size(). %% cleanup all the expired cache entries --spec(cleanup_acl_cache() -> ok). -cleanup_acl_cache() -> +-spec(cleanup_acl_cache(atom(), atom()) -> ok). +cleanup_acl_cache(Zone, Listener) -> keys_queue_set( - cleanup_acl(keys_queue_get())). + cleanup_acl(get_cache_ttl(Zone, Listener), keys_queue_get())). get_oldest_key() -> keys_queue_pick(queue_front()). @@ -174,16 +176,16 @@ update_acl(K, V) -> erlang:put(K, V), keys_queue_update(K). -cleanup_acl(KeysQ) -> +cleanup_acl(TTL, KeysQ) -> case queue:out(KeysQ) of {{value, OldestK}, KeysQ2} -> {_AclResult, CachedAt} = erlang:get(OldestK), - if_expired(CachedAt, + if_expired(TTL, CachedAt, fun(false) -> KeysQ; (true) -> erlang:erase(OldestK), decr_cache_size(), - cleanup_acl(KeysQ2) + cleanup_acl(TTL, KeysQ2) end); {empty, KeysQ} -> KeysQ end. @@ -246,8 +248,7 @@ queue_rear() -> fun queue:get_r/1. time_now() -> erlang:system_time(millisecond). -if_expired(CachedAt, Fun) -> - TTL = get_cache_ttl(), +if_expired(TTL, CachedAt, Fun) -> Now = time_now(), CurrentEvictTimestamp = persistent_term:get(drain_k(), 0), case CachedAt =< CurrentEvictTimestamp orelse (CachedAt + TTL) =< Now of diff --git a/apps/emqx/src/emqx_alarm.erl b/apps/emqx/src/emqx_alarm.erl index a3a7420e3..21581e71a 100644 --- a/apps/emqx/src/emqx_alarm.erl +++ b/apps/emqx/src/emqx_alarm.erl @@ -17,6 +17,7 @@ -module(emqx_alarm). -behaviour(gen_server). +-behaviour(emqx_config_handler). -include("emqx.hrl"). -include("logger.hrl"). @@ -29,7 +30,9 @@ -boot_mnesia({mnesia, [boot]}). -copy_mnesia({mnesia, [copy]}). --export([ start_link/1 +-export([handle_update_config/2]). + +-export([ start_link/0 , stop/0 ]). @@ -75,17 +78,9 @@ }). -record(state, { - actions :: [action()], - - size_limit :: non_neg_integer(), - - validity_period :: non_neg_integer(), - - timer = undefined :: undefined | reference() + timer :: reference() }). --type action() :: log | publish | event. - -define(ACTIVATED_ALARM, emqx_activated_alarm). -define(DEACTIVATED_ALARM, emqx_deactivated_alarm). @@ -93,7 +88,6 @@ -rlog_shard({?COMMON_SHARD, ?ACTIVATED_ALARM}). -rlog_shard({?COMMON_SHARD, ?DEACTIVATED_ALARM}). - -ifdef(TEST). -compile(export_all). -compile(nowarn_export_all). @@ -124,8 +118,8 @@ mnesia(copy) -> %% API %%-------------------------------------------------------------------- -start_link(Opts) -> - gen_server:start_link({local, ?MODULE}, ?MODULE, [Opts], []). +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). stop() -> gen_server:stop(?MODULE). @@ -157,27 +151,26 @@ get_alarms(activated) -> get_alarms(deactivated) -> gen_server:call(?MODULE, {get_alarms, deactivated}). +handle_update_config(#{<<"validity_period">> := Period0} = NewConf, OldConf) -> + ?MODULE ! {update_timer, hocon_postprocess:duration(Period0)}, + maps:merge(OldConf, NewConf); +handle_update_config(NewConf, OldConf) -> + maps:merge(OldConf, NewConf). + %%-------------------------------------------------------------------- %% gen_server callbacks %%-------------------------------------------------------------------- init([]) -> - Opts = [{actions, [log, publish]}], - init([Opts]); -init([Opts]) -> deactivate_all_alarms(), - Actions = proplists:get_value(actions, Opts), - SizeLimit = proplists:get_value(size_limit, Opts), - ValidityPeriod = timer:seconds(proplists:get_value(validity_period, Opts)), - {ok, ensure_delete_timer(#state{actions = Actions, - size_limit = SizeLimit, - validity_period = ValidityPeriod})}. + emqx_config_handler:add_handler([alarm], ?MODULE), + {ok, #state{timer = ensure_timer(undefined, get_validity_period())}}. %% suppress dialyzer warning due to dirty read/write race condition. %% TODO: change from dirty_read/write to transactional. %% TODO: handle mnesia write errors. -dialyzer([{nowarn_function, [handle_call/3]}]). -handle_call({activate_alarm, Name, Details}, _From, State = #state{actions = Actions}) -> +handle_call({activate_alarm, Name, Details}, _From, State) -> case mnesia:dirty_read(?ACTIVATED_ALARM, Name) of [#activated_alarm{name = Name}] -> {reply, {error, already_existed}, State}; @@ -187,17 +180,16 @@ handle_call({activate_alarm, Name, Details}, _From, State = #state{actions = Act message = normalize_message(Name, Details), activate_at = erlang:system_time(microsecond)}, ekka_mnesia:dirty_write(?ACTIVATED_ALARM, Alarm), - do_actions(activate, Alarm, Actions), + do_actions(activate, Alarm, emqx_config:get([alarm, actions])), {reply, ok, State} end; -handle_call({deactivate_alarm, Name, Details}, _From, State = #state{ - actions = Actions, size_limit = SizeLimit}) -> +handle_call({deactivate_alarm, Name, Details}, _From, State) -> case mnesia:dirty_read(?ACTIVATED_ALARM, Name) of [] -> {reply, {error, not_found}, State}; [Alarm] -> - deactivate_alarm(Details, SizeLimit, Actions, Alarm), + deactivate_alarm(Details, Alarm), {reply, ok, State} end; @@ -232,11 +224,15 @@ handle_cast(Msg, State) -> ?LOG(error, "Unexpected msg: ~p", [Msg]), {noreply, State}. -handle_info({timeout, TRef, delete_expired_deactivated_alarm}, - State = #state{timer = TRef, - validity_period = ValidityPeriod}) -> - delete_expired_deactivated_alarms(erlang:system_time(microsecond) - ValidityPeriod * 1000), - {noreply, ensure_delete_timer(State)}; +handle_info({timeout, _TRef, delete_expired_deactivated_alarm}, + #state{timer = TRef} = State) -> + Period = get_validity_period(), + delete_expired_deactivated_alarms(erlang:system_time(microsecond) - Period * 1000), + {noreply, State#state{timer = ensure_timer(TRef, Period)}}; + +handle_info({update_timer, Period}, #state{timer = TRef} = State) -> + ?LOG(warning, "update the 'validity_period' timer to ~p", [Period]), + {noreply, State#state{timer = ensure_timer(TRef, Period)}}; handle_info(Info, State) -> ?LOG(error, "Unexpected info: ~p", [Info]), @@ -252,11 +248,13 @@ code_change(_OldVsn, State, _Extra) -> %% Internal functions %%------------------------------------------------------------------------------ -deactivate_alarm(Details, SizeLimit, Actions, #activated_alarm{ - activate_at = ActivateAt, name = Name, details = Details0, - message = Msg0}) -> - case SizeLimit > 0 andalso - (mnesia:table_info(?DEACTIVATED_ALARM, size) >= SizeLimit) of +get_validity_period() -> + timer:seconds(emqx_config:get([alarm, validity_period])). + +deactivate_alarm(Details, #activated_alarm{activate_at = ActivateAt, name = Name, + details = Details0, message = Msg0}) -> + SizeLimit = emqx_config:get([alarm, size_limit]), + case SizeLimit > 0 andalso (mnesia:table_info(?DEACTIVATED_ALARM, size) >= SizeLimit) of true -> case mnesia:dirty_first(?DEACTIVATED_ALARM) of '$end_of_table' -> ok; @@ -272,7 +270,7 @@ deactivate_alarm(Details, SizeLimit, Actions, #activated_alarm{ erlang:system_time(microsecond)), ekka_mnesia:dirty_write(?DEACTIVATED_ALARM, HistoryAlarm), ekka_mnesia:dirty_delete(?ACTIVATED_ALARM, Name), - do_actions(deactivate, DeActAlarm, Actions). + do_actions(deactivate, DeActAlarm, emqx_config:get([alarm, actions])). make_deactivated_alarm(ActivateAt, Name, Details, Message, DeActivateAt) -> #deactivated_alarm{ @@ -308,9 +306,12 @@ clear_table(TableName) -> ok end. -ensure_delete_timer(State = #state{validity_period = ValidityPeriod}) -> - TRef = emqx_misc:start_timer(ValidityPeriod, delete_expired_deactivated_alarm), - State#state{timer = TRef}. +ensure_timer(OldTRef, Period) -> + _ = case is_reference(OldTRef) of + true -> erlang:cancel_timer(OldTRef); + false -> ok + end, + emqx_misc:start_timer(Period, delete_expired_deactivated_alarm). delete_expired_deactivated_alarms(Checkpoint) -> delete_expired_deactivated_alarms(mnesia:dirty_first(?DEACTIVATED_ALARM), Checkpoint). @@ -381,9 +382,9 @@ normalize_message(high_system_memory_usage, #{high_watermark := HighWatermark}) normalize_message(high_process_memory_usage, #{high_watermark := HighWatermark}) -> list_to_binary(io_lib:format("Process memory usage is higher than ~p%", [HighWatermark])); normalize_message(high_cpu_usage, #{usage := Usage}) -> - list_to_binary(io_lib:format("~p% cpu usage", [Usage])); + list_to_binary(io_lib:format("~s cpu usage", [Usage])); normalize_message(too_many_processes, #{usage := Usage}) -> - list_to_binary(io_lib:format("~p% process usage", [Usage])); + list_to_binary(io_lib:format("~s process usage", [Usage])); normalize_message(partition, #{occurred := Node}) -> list_to_binary(io_lib:format("Partition occurs at node ~s", [Node])); normalize_message(<<"resource", _/binary>>, #{type := Type, id := ID}) -> diff --git a/apps/emqx/src/emqx_alarm_handler.erl b/apps/emqx/src/emqx_alarm_handler.erl index a69913afd..2307b79db 100644 --- a/apps/emqx/src/emqx_alarm_handler.erl +++ b/apps/emqx/src/emqx_alarm_handler.erl @@ -56,20 +56,22 @@ init({_Args, {alarm_handler, _ExistingAlarms}}) -> init(_) -> {ok, []}. -handle_event({set_alarm, {system_memory_high_watermark, []}}, State) -> - emqx_alarm:activate(high_system_memory_usage, #{high_watermark => emqx_os_mon:get_sysmem_high_watermark()}), +handle_event({set_alarm, {system_memory_high_watermark, []}}, State) -> + emqx_alarm:activate(high_system_memory_usage, + #{high_watermark => emqx_os_mon:get_sysmem_high_watermark()}), {ok, State}; -handle_event({set_alarm, {process_memory_high_watermark, Pid}}, State) -> - emqx_alarm:activate(high_process_memory_usage, #{pid => Pid, - high_watermark => emqx_os_mon:get_procmem_high_watermark()}), +handle_event({set_alarm, {process_memory_high_watermark, Pid}}, State) -> + emqx_alarm:activate(high_process_memory_usage, + #{pid => list_to_binary(pid_to_list(Pid)), + high_watermark => emqx_os_mon:get_procmem_high_watermark()}), {ok, State}; -handle_event({clear_alarm, system_memory_high_watermark}, State) -> +handle_event({clear_alarm, system_memory_high_watermark}, State) -> emqx_alarm:deactivate(high_system_memory_usage), {ok, State}; -handle_event({clear_alarm, process_memory_high_watermark}, State) -> +handle_event({clear_alarm, process_memory_high_watermark}, State) -> emqx_alarm:deactivate(high_process_memory_usage), {ok, State}; diff --git a/apps/emqx/src/emqx_channel.erl b/apps/emqx/src/emqx_channel.erl index 887a24052..4d9b8ae12 100644 --- a/apps/emqx/src/emqx_channel.erl +++ b/apps/emqx/src/emqx_channel.erl @@ -31,6 +31,8 @@ -export([ info/1 , info/2 + , get_mqtt_conf/3 + , get_mqtt_conf/4 , set_conn_state/2 , get_session/1 , set_session/2 @@ -63,7 +65,7 @@ , maybe_apply/2 ]). --export_type([channel/0]). +-export_type([channel/0, opts/0]). -record(channel, { %% MQTT ConnInfo @@ -98,6 +100,8 @@ -type(channel() :: #channel{}). +-type(opts() :: #{zone := atom(), listener := atom(), atom() => term()}). + -type(conn_state() :: idle | connecting | connected | reauthenticating | disconnected). -type(reply() :: {outgoing, emqx_types:packet()} @@ -151,7 +155,9 @@ info(connected_at, #channel{conninfo = ConnInfo}) -> info(clientinfo, #channel{clientinfo = ClientInfo}) -> ClientInfo; info(zone, #channel{clientinfo = ClientInfo}) -> - maps:get(zone, ClientInfo, undefined); + maps:get(zone, ClientInfo); +info(listener, #channel{clientinfo = ClientInfo}) -> + maps:get(listener, ClientInfo); info(clientid, #channel{clientinfo = ClientInfo}) -> maps:get(clientid, ClientInfo, undefined); info(username, #channel{clientinfo = ClientInfo}) -> @@ -187,25 +193,28 @@ stats(#channel{session = Session})-> emqx_session:stats(Session). -spec(caps(channel()) -> emqx_types:caps()). -caps(#channel{clientinfo = #{zone := Zone}}) -> - emqx_mqtt_caps:get_caps(Zone). +caps(#channel{clientinfo = #{zone := Zone, listener := Listener}}) -> + emqx_mqtt_caps:get_caps(Zone, Listener). %%-------------------------------------------------------------------- %% Init the channel %%-------------------------------------------------------------------- --spec(init(emqx_types:conninfo(), proplists:proplist()) -> channel()). +-spec(init(emqx_types:conninfo(), opts()) -> channel()). init(ConnInfo = #{peername := {PeerHost, _Port}, - sockname := {_Host, SockPort}}, Options) -> - Zone = proplists:get_value(zone, Options), + sockname := {_Host, SockPort}}, #{zone := Zone, listener := Listener}) -> Peercert = maps:get(peercert, ConnInfo, undefined), Protocol = maps:get(protocol, ConnInfo, mqtt), - MountPoint = emqx_zone:mountpoint(Zone), - QuotaPolicy = emqx_zone:quota_policy(Zone), - ClientInfo = setting_peercert_infos( + MountPoint = case get_mqtt_conf(Zone, Listener, mountpoint) of + <<>> -> undefined; + MP -> MP + end, + QuotaPolicy = emqx_config:get_listener_conf(Zone, Listener, [rate_limit, quota]), + ClientInfo = set_peercert_infos( Peercert, #{zone => Zone, + listener => Listener, protocol => Protocol, peerhost => PeerHost, sockport => SockPort, @@ -214,7 +223,7 @@ init(ConnInfo = #{peername := {PeerHost, _Port}, mountpoint => MountPoint, is_bridge => false, is_superuser => false - }, Options), + }, Zone, Listener), {NClientInfo, NConnInfo} = take_ws_cookie(ClientInfo, ConnInfo), #channel{conninfo = NConnInfo, clientinfo = NClientInfo, @@ -222,7 +231,7 @@ init(ConnInfo = #{peername := {PeerHost, _Port}, outbound => #{} }, auth_cache = #{}, - quota = emqx_limiter:init(Zone, QuotaPolicy), + quota = emqx_limiter:init(Zone, quota_policy(QuotaPolicy)), timers = #{}, conn_state = idle, takeover = false, @@ -230,30 +239,32 @@ init(ConnInfo = #{peername := {PeerHost, _Port}, pendings = [] }. -setting_peercert_infos(NoSSL, ClientInfo, _Options) +quota_policy(RawPolicy) -> + [{Name, {list_to_integer(StrCount), + erlang:trunc(hocon_postprocess:duration(StrWind) / 1000)}} + || {Name, [StrCount, StrWind]} <- maps:to_list(RawPolicy)]. + +set_peercert_infos(NoSSL, ClientInfo, _, _) when NoSSL =:= nossl; NoSSL =:= undefined -> ClientInfo#{username => undefined}; -setting_peercert_infos(Peercert, ClientInfo, Options) -> +set_peercert_infos(Peercert, ClientInfo, Zone, Listener) -> {DN, CN} = {esockd_peercert:subject(Peercert), esockd_peercert:common_name(Peercert)}, - Username = peer_cert_as(peer_cert_as_username, Options, Peercert, DN, CN), - ClientId = peer_cert_as(peer_cert_as_clientid, Options, Peercert, DN, CN), - ClientInfo#{username => Username, clientid => ClientId, dn => DN, cn => CN}. - --dialyzer([{nowarn_function, [peer_cert_as/5]}]). -% esockd_peercert:peercert is opaque -% https://github.com/emqx/esockd/blob/master/src/esockd_peercert.erl -peer_cert_as(Key, Options, Peercert, DN, CN) -> - case proplists:get_value(Key, Options) of + PeercetAs = fun(Key) -> + case get_mqtt_conf(Zone, Listener, Key) of cn -> CN; dn -> DN; crt -> Peercert; - pem -> base64:encode(Peercert); - md5 -> emqx_passwd:hash(md5, Peercert); + pem when is_binary(Peercert) -> base64:encode(Peercert); + md5 when is_binary(Peercert) -> emqx_passwd:hash(md5, Peercert); _ -> undefined - end. + end + end, + Username = PeercetAs(peer_cert_as_username), + ClientId = PeercetAs(peer_cert_as_clientid), + ClientInfo#{username => Username, clientid => ClientId, dn => DN, cn => CN}. take_ws_cookie(ClientInfo, ConnInfo) -> case maps:take(ws_cookie, ConnInfo) of @@ -415,16 +426,17 @@ handle_in(?PUBCOMP_PACKET(PacketId, _ReasonCode), Channel = #channel{session = S end; handle_in(Packet = ?SUBSCRIBE_PACKET(PacketId, Properties, TopicFilters), - Channel = #channel{clientinfo = ClientInfo = #{zone := Zone}}) -> + Channel = #channel{clientinfo = ClientInfo = #{zone := Zone, listener := Listener}}) -> case emqx_packet:check(Packet) of ok -> TopicFilters0 = parse_topic_filters(TopicFilters), TopicFilters1 = put_subid_in_subopts(Properties, TopicFilters0), TupleTopicFilters0 = check_sub_acls(TopicFilters1, Channel), - case emqx_zone:get_env(Zone, acl_deny_action, ignore) =:= disconnect andalso - lists:any(fun({_TopicFilter, ReasonCode}) -> - ReasonCode =:= ?RC_NOT_AUTHORIZED - end, TupleTopicFilters0) of + HasAclDeny = lists:any(fun({_TopicFilter, ReasonCode}) -> + ReasonCode =:= ?RC_NOT_AUTHORIZED + end, TupleTopicFilters0), + DenyAction = emqx_config:get_listener_conf(Zone, Listener, [acl, deny_action]), + case DenyAction =:= disconnect andalso HasAclDeny of true -> handle_out(disconnect, ?RC_NOT_AUTHORIZED, Channel); false -> Replace = fun @@ -526,7 +538,7 @@ process_connect(AckProps, Channel = #channel{conninfo = ConnInfo, %%-------------------------------------------------------------------- process_publish(Packet = ?PUBLISH_PACKET(QoS, Topic, PacketId), - Channel = #channel{clientinfo = #{zone := Zone}}) -> + Channel = #channel{clientinfo = #{zone := Zone, listener := Listener}}) -> case pipeline([fun check_quota_exceeded/2, fun process_alias/2, fun check_pub_alias/2, @@ -539,7 +551,7 @@ process_publish(Packet = ?PUBLISH_PACKET(QoS, Topic, PacketId), {error, Rc = ?RC_NOT_AUTHORIZED, NChannel} -> ?LOG(warning, "Cannot publish message to ~s due to ~s.", [Topic, emqx_reason_codes:text(Rc)]), - case emqx_zone:get_env(Zone, acl_deny_action, ignore) of + case emqx_config:get_listener_conf(Zone, Listener, [acl_deny_action]) of ignore -> case QoS of ?QOS_0 -> {ok, NChannel}; @@ -944,8 +956,9 @@ handle_call({takeover, 'end'}, Channel = #channel{session = Session, AllPendings = lists:append(Delivers, Pendings), disconnect_and_shutdown(takeovered, AllPendings, Channel); -handle_call(list_acl_cache, Channel) -> - {reply, emqx_acl_cache:list_acl_cache(), Channel}; +handle_call(list_acl_cache, #channel{clientinfo = #{zone := Zone, listener := Listener}} + = Channel) -> + {reply, emqx_acl_cache:list_acl_cache(Zone, Listener), Channel}; handle_call({quota, Policy}, Channel) -> Zone = info(zone, Channel), @@ -982,9 +995,9 @@ handle_info({sock_closed, Reason}, Channel = #channel{conn_state = connecting}) handle_info({sock_closed, Reason}, Channel = #channel{conn_state = ConnState, - clientinfo = ClientInfo = #{zone := Zone}}) - when ConnState =:= connected orelse ConnState =:= reauthenticating -> - emqx_zone:enable_flapping_detect(Zone) + clientinfo = ClientInfo = #{zone := Zone, listener := Listener}}) + when ConnState =:= connected orelse ConnState =:= reauthenticating -> + emqx_config:get_listener_conf(Zone, Listener, [flapping_detect, enable]) andalso emqx_flapping:detect(ClientInfo), Channel1 = ensure_disconnected(Reason, mabye_publish_will_msg(Channel)), case maybe_shutdown(Reason, Channel1) of @@ -1145,9 +1158,9 @@ enrich_conninfo(ConnPkt = #mqtt_packet_connect{ username = Username }, Channel = #channel{conninfo = ConnInfo, - clientinfo = #{zone := Zone} + clientinfo = #{zone := Zone, listener := Listener} }) -> - ExpiryInterval = expiry_interval(Zone, ConnPkt), + ExpiryInterval = expiry_interval(Zone, Listener, ConnPkt), NConnInfo = ConnInfo#{proto_name => ProtoName, proto_ver => ProtoVer, clean_start => CleanStart, @@ -1156,22 +1169,21 @@ enrich_conninfo(ConnPkt = #mqtt_packet_connect{ username => Username, conn_props => ConnProps, expiry_interval => ExpiryInterval, - receive_maximum => receive_maximum(Zone, ConnProps) + receive_maximum => receive_maximum(Zone, Listener, ConnProps) }, {ok, Channel#channel{conninfo = NConnInfo}}. %% If the Session Expiry Interval is absent the value 0 is used. --compile({inline, [expiry_interval/2]}). -expiry_interval(_Zone, #mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V5, +expiry_interval(_, _, #mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V5, properties = ConnProps}) -> emqx_mqtt_props:get('Session-Expiry-Interval', ConnProps, 0); -expiry_interval(Zone, #mqtt_packet_connect{clean_start = false}) -> - emqx_zone:session_expiry_interval(Zone); -expiry_interval(_Zone, #mqtt_packet_connect{clean_start = true}) -> +expiry_interval(Zone, Listener, #mqtt_packet_connect{clean_start = false}) -> + get_mqtt_conf(Zone, Listener, session_expiry_interval); +expiry_interval(_, _, #mqtt_packet_connect{clean_start = true}) -> 0. -receive_maximum(Zone, ConnProps) -> - MaxInflightConfig = case emqx_zone:max_inflight(Zone) of +receive_maximum(Zone, Listener, ConnProps) -> + MaxInflightConfig = case get_mqtt_conf(Zone, Listener, max_inflight) of 0 -> ?RECEIVE_MAXIMUM_LIMIT; N -> N end, @@ -1194,8 +1206,8 @@ run_conn_hooks(ConnPkt, Channel = #channel{conninfo = ConnInfo}) -> %%-------------------------------------------------------------------- %% Check Connect Packet -check_connect(ConnPkt, #channel{clientinfo = #{zone := Zone}}) -> - emqx_packet:check(ConnPkt, emqx_mqtt_caps:get_caps(Zone)). +check_connect(ConnPkt, #channel{clientinfo = #{zone := Zone, listener := Listener}}) -> + emqx_packet:check(ConnPkt, emqx_mqtt_caps:get_caps(Zone, Listener)). %%-------------------------------------------------------------------- %% Enrich Client Info @@ -1220,8 +1232,9 @@ set_bridge_mode(_ConnPkt, _ClientInfo) -> ok. maybe_username_as_clientid(_ConnPkt, ClientInfo = #{username := undefined}) -> {ok, ClientInfo}; -maybe_username_as_clientid(_ConnPkt, ClientInfo = #{zone := Zone, username := Username}) -> - case emqx_zone:use_username_as_clientid(Zone) of +maybe_username_as_clientid(_ConnPkt, ClientInfo = #{zone := Zone, listener := Listener, + username := Username}) -> + case get_mqtt_conf(Zone, Listener, use_username_as_clientid) of true -> {ok, ClientInfo#{clientid => Username}}; false -> ok end. @@ -1249,8 +1262,8 @@ set_log_meta(_ConnPkt, #channel{clientinfo = #{clientid := ClientId}}) -> %%-------------------------------------------------------------------- %% Check banned -check_banned(_ConnPkt, #channel{clientinfo = ClientInfo = #{zone := Zone}}) -> - case emqx_zone:enable_ban(Zone) andalso emqx_banned:check(ClientInfo) of +check_banned(_ConnPkt, #channel{clientinfo = ClientInfo}) -> + case emqx_banned:check(ClientInfo) of true -> {error, ?RC_BANNED}; false -> ok end. @@ -1419,8 +1432,8 @@ check_pub_caps(#mqtt_packet{header = #mqtt_packet_header{qos = QoS, retain = Retain}, variable = #mqtt_packet_publish{topic_name = Topic} }, - #channel{clientinfo = #{zone := Zone}}) -> - emqx_mqtt_caps:check_pub(Zone, #{qos => QoS, retain => Retain, topic => Topic}). + #channel{clientinfo = #{zone := Zone, listener := Listener}}) -> + emqx_mqtt_caps:check_pub(Zone, Listener, #{qos => QoS, retain => Retain, topic => Topic}). %%-------------------------------------------------------------------- %% Check Sub ACL @@ -1448,8 +1461,9 @@ check_sub_acl(TopicFilter, #channel{clientinfo = ClientInfo}) -> %%-------------------------------------------------------------------- %% Check Sub Caps -check_sub_caps(TopicFilter, SubOpts, #channel{clientinfo = #{zone := Zone}}) -> - emqx_mqtt_caps:check_sub(Zone, TopicFilter, SubOpts). +check_sub_caps(TopicFilter, SubOpts, #channel{clientinfo = #{zone := Zone, + listener := Listener}}) -> + emqx_mqtt_caps:check_sub(Zone, Listener, TopicFilter, SubOpts). %%-------------------------------------------------------------------- %% Enrich SubId @@ -1463,21 +1477,23 @@ put_subid_in_subopts(_Properties, TopicFilters) -> TopicFilters. enrich_subopts(SubOpts, _Channel = ?IS_MQTT_V5) -> SubOpts; -enrich_subopts(SubOpts, #channel{clientinfo = #{zone := Zone, is_bridge := IsBridge}}) -> - NL = flag(emqx_zone:ignore_loop_deliver(Zone)), +enrich_subopts(SubOpts, #channel{clientinfo = #{zone := Zone, is_bridge := IsBridge, + listener := Listener}}) -> + NL = flag(get_mqtt_conf(Zone, Listener, ignore_loop_deliver)), SubOpts#{rap => flag(IsBridge), nl => NL}. %%-------------------------------------------------------------------- %% Enrich ConnAck Caps -enrich_connack_caps(AckProps, ?IS_MQTT_V5 = #channel{clientinfo = #{zone := Zone}}) -> +enrich_connack_caps(AckProps, ?IS_MQTT_V5 = #channel{clientinfo = #{ + zone := Zone, listener := Listener}}) -> #{max_packet_size := MaxPktSize, max_qos_allowed := MaxQoS, retain_available := Retain, max_topic_alias := MaxAlias, shared_subscription := Shared, wildcard_subscription := Wildcard - } = emqx_mqtt_caps:get_caps(Zone), + } = emqx_mqtt_caps:get_caps(Zone, Listener), NAckProps = AckProps#{'Retain-Available' => flag(Retain), 'Maximum-Packet-Size' => MaxPktSize, 'Topic-Alias-Maximum' => MaxAlias, @@ -1499,9 +1515,9 @@ enrich_connack_caps(AckProps, _Channel) -> AckProps. %%-------------------------------------------------------------------- %% Enrich server keepalive -enrich_server_keepalive(AckProps, #channel{clientinfo = #{zone := Zone}}) -> - case emqx_zone:server_keepalive(Zone) of - undefined -> AckProps; +enrich_server_keepalive(AckProps, #channel{clientinfo = #{zone := Zone, listener := Listener}}) -> + case get_mqtt_conf(Zone, Listener, server_keepalive) of + disabled -> AckProps; Keepalive -> AckProps#{'Server-Keep-Alive' => Keepalive} end. @@ -1509,10 +1525,14 @@ enrich_server_keepalive(AckProps, #channel{clientinfo = #{zone := Zone}}) -> %% Enrich response information enrich_response_information(AckProps, #channel{conninfo = #{conn_props := ConnProps}, - clientinfo = #{zone := Zone}}) -> + clientinfo = #{zone := Zone, listener := Listener}}) -> case emqx_mqtt_props:get('Request-Response-Information', ConnProps, 0) of 0 -> AckProps; - 1 -> AckProps#{'Response-Information' => emqx_zone:response_information(Zone)} + 1 -> AckProps#{'Response-Information' => + case get_mqtt_conf(Zone, Listener, response_information, "") of + "" -> undefined; + RspInfo -> RspInfo + end} end. %%-------------------------------------------------------------------- @@ -1542,9 +1562,9 @@ ensure_connected(Channel = #channel{conninfo = ConnInfo, init_alias_maximum(#mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V5, properties = Properties}, - #{zone := Zone} = _ClientInfo) -> + #{zone := Zone, listener := Listener} = _ClientInfo) -> #{outbound => emqx_mqtt_props:get('Topic-Alias-Maximum', Properties, 0), - inbound => emqx_mqtt_caps:get_caps(Zone, max_topic_alias, ?MAX_TOPIC_AlIAS) + inbound => maps:get(max_topic_alias, emqx_mqtt_caps:get_caps(Zone, Listener)) }; init_alias_maximum(_ConnPkt, _ClientInfo) -> undefined. @@ -1560,8 +1580,10 @@ ensure_keepalive(_AckProps, Channel = #channel{conninfo = ConnInfo}) -> ensure_keepalive_timer(maps:get(keepalive, ConnInfo), Channel). ensure_keepalive_timer(0, Channel) -> Channel; -ensure_keepalive_timer(Interval, Channel = #channel{clientinfo = #{zone := Zone}}) -> - Backoff = emqx_zone:keepalive_backoff(Zone), +ensure_keepalive_timer(disabled, Channel) -> Channel; +ensure_keepalive_timer(Interval, Channel = #channel{clientinfo = #{zone := Zone, + listener := Listener}}) -> + Backoff = get_mqtt_conf(Zone, Listener, keepalive_backoff), Keepalive = emqx_keepalive:init(round(timer:seconds(Interval) * Backoff)), ensure_timer(alive_timer, Channel#channel{keepalive = Keepalive}). @@ -1602,10 +1624,8 @@ maybe_shutdown(Reason, Channel = #channel{conninfo = ConnInfo}) -> %%-------------------------------------------------------------------- %% Is ACL enabled? - --compile({inline, [is_acl_enabled/1]}). -is_acl_enabled(#{zone := Zone, is_superuser := IsSuperuser}) -> - (not IsSuperuser) andalso emqx_zone:enable_acl(Zone). +is_acl_enabled(#{zone := Zone, listener := Listener, is_superuser := IsSuperuser}) -> + (not IsSuperuser) andalso emqx_config:get_listener_conf(Zone, Listener, [acl, enable]). %%-------------------------------------------------------------------- %% Parse Topic Filters @@ -1716,6 +1736,12 @@ sp(false) -> 0. flag(true) -> 1; flag(false) -> 0. +get_mqtt_conf(Zone, Listener, Key) -> + emqx_config:get_listener_conf(Zone, Listener, [mqtt, Key]). + +get_mqtt_conf(Zone, Listener, Key, Default) -> + emqx_config:get_listener_conf(Zone, Listener, [mqtt, Key], Default). + %%-------------------------------------------------------------------- %% For CT tests %%-------------------------------------------------------------------- @@ -1723,4 +1749,3 @@ flag(false) -> 0. set_field(Name, Value, Channel) -> Pos = emqx_misc:index_of(Name, record_info(fields, channel)), setelement(Pos+1, Channel, Value). - diff --git a/apps/emqx/src/emqx_config.erl b/apps/emqx/src/emqx_config.erl index 213eb7a8b..64895fbc8 100644 --- a/apps/emqx/src/emqx_config.erl +++ b/apps/emqx/src/emqx_config.erl @@ -20,10 +20,17 @@ -export([ get/0 , get/1 , get/2 + , find/1 , put/1 , put/2 ]). +-export([ get_listener_conf/3 + , get_listener_conf/4 + , put_listener_conf/4 + , find_listener_conf/3 + ]). + -export([ update_config/2 ]). @@ -35,43 +42,67 @@ , put_raw/2 ]). --export([ deep_get/2 - , deep_get/3 - , deep_put/3 - , safe_atom_key_map/1 - , unsafe_atom_key_map/1 - ]). - -define(CONF, ?MODULE). -define(RAW_CONF, {?MODULE, raw}). --export_type([update_request/0, raw_config/0, config_key/0, config_key_path/0]). +-export_type([update_request/0, raw_config/0, config/0]). -type update_request() :: term(). --type raw_config() :: hocon:config() | undefined. --type config_key() :: atom() | binary(). --type config_key_path() :: [config_key()]. +-type raw_config() :: #{binary() => term()} | undefined. +-type config() :: #{atom() => term()} | undefined. -spec get() -> map(). get() -> persistent_term:get(?CONF, #{}). --spec get(config_key_path()) -> term(). +-spec get(emqx_map_lib:config_key_path()) -> term(). get(KeyPath) -> - deep_get(KeyPath, get()). + emqx_map_lib:deep_get(KeyPath, get()). --spec get(config_key_path(), term()) -> term(). +-spec get(emqx_map_lib:config_key_path(), term()) -> term(). get(KeyPath, Default) -> - deep_get(KeyPath, get(), Default). + emqx_map_lib:deep_get(KeyPath, get(), Default). + +-spec find(emqx_map_lib:config_key_path()) -> + {ok, term()} | {not_found, emqx_map_lib:config_key_path(), term()}. +find(KeyPath) -> + emqx_map_lib:deep_find(KeyPath, get()). + +-spec get_listener_conf(atom(), atom(), emqx_map_lib:config_key_path()) -> term(). +get_listener_conf(Zone, Listener, KeyPath) -> + case find_listener_conf(Zone, Listener, KeyPath) of + {not_found, SubKeyPath, Data} -> error({not_found, SubKeyPath, Data}); + {ok, Data} -> Data + end. + +-spec get_listener_conf(atom(), atom(), emqx_map_lib:config_key_path(), term()) -> term(). +get_listener_conf(Zone, Listener, KeyPath, Default) -> + case find_listener_conf(Zone, Listener, KeyPath) of + {not_found, _, _} -> Default; + {ok, Data} -> Data + end. + +-spec put_listener_conf(atom(), atom(), emqx_map_lib:config_key_path(), term()) -> ok. +put_listener_conf(Zone, Listener, KeyPath, Conf) -> + ?MODULE:put([zones, Zone, listeners, Listener | KeyPath], Conf). + +-spec find_listener_conf(atom(), atom(), emqx_map_lib:config_key_path()) -> + {ok, term()} | {not_found, emqx_map_lib:config_key_path(), term()}. +find_listener_conf(Zone, Listener, KeyPath) -> + %% the configs in listener is prior to the ones in the zone + case find([zones, Zone, listeners, Listener | KeyPath]) of + {not_found, _, _} -> find([zones, Zone | KeyPath]); + {ok, Data} -> {ok, Data} + end. -spec put(map()) -> ok. put(Config) -> persistent_term:put(?CONF, Config). --spec put(config_key_path(), term()) -> ok. +-spec put(emqx_map_lib:config_key_path(), term()) -> ok. put(KeyPath, Config) -> - put(deep_put(KeyPath, get(), Config)). + put(emqx_map_lib:deep_put(KeyPath, get(), Config)). --spec update_config(config_key_path(), update_request()) -> +-spec update_config(emqx_map_lib:config_key_path(), update_request()) -> ok | {error, term()}. update_config(ConfKeyPath, UpdateReq) -> emqx_config_handler:update_config(ConfKeyPath, UpdateReq, get_raw()). @@ -80,67 +111,18 @@ update_config(ConfKeyPath, UpdateReq) -> get_raw() -> persistent_term:get(?RAW_CONF, #{}). --spec get_raw(config_key_path()) -> term(). +-spec get_raw(emqx_map_lib:config_key_path()) -> term(). get_raw(KeyPath) -> - deep_get(KeyPath, get_raw()). + emqx_map_lib:deep_get(KeyPath, get_raw()). --spec get_raw(config_key_path(), term()) -> term(). +-spec get_raw(emqx_map_lib:config_key_path(), term()) -> term(). get_raw(KeyPath, Default) -> - deep_get(KeyPath, get_raw(), Default). + emqx_map_lib:deep_get(KeyPath, get_raw(), Default). -spec put_raw(map()) -> ok. put_raw(Config) -> persistent_term:put(?RAW_CONF, Config). --spec put_raw(config_key_path(), term()) -> ok. +-spec put_raw(emqx_map_lib:config_key_path(), term()) -> ok. put_raw(KeyPath, Config) -> - put_raw(deep_put(KeyPath, get_raw(), Config)). - -%%----------------------------------------------------------------- --dialyzer([{nowarn_function, [deep_get/2]}]). --spec deep_get(config_key_path(), map()) -> term(). -deep_get(ConfKeyPath, Map) -> - do_deep_get(ConfKeyPath, Map, fun(KeyPath, Data) -> - error({not_found, KeyPath, Data}) end). - --spec deep_get(config_key_path(), map(), term()) -> term(). -deep_get(ConfKeyPath, Map, Default) -> - do_deep_get(ConfKeyPath, Map, fun(_, _) -> Default end). - --spec deep_put(config_key_path(), map(), term()) -> map(). -deep_put([], Map, Config) when is_map(Map) -> - Config; -deep_put([Key | KeyPath], Map, Config) -> - SubMap = deep_put(KeyPath, maps:get(Key, Map, #{}), Config), - Map#{Key => SubMap}. - -unsafe_atom_key_map(Map) -> - covert_keys_to_atom(Map, fun(K) -> binary_to_atom(K, utf8) end). - -safe_atom_key_map(Map) -> - covert_keys_to_atom(Map, fun(K) -> binary_to_existing_atom(K, utf8) end). - -%%--------------------------------------------------------------------------- - --spec do_deep_get(config_key_path(), map(), fun((config_key(), term()) -> any())) -> term(). -do_deep_get([], Map, _) -> - Map; -do_deep_get([Key | KeyPath], Map, OnNotFound) when is_map(Map) -> - case maps:find(Key, Map) of - {ok, SubMap} -> do_deep_get(KeyPath, SubMap, OnNotFound); - error -> OnNotFound(Key, Map) - end; -do_deep_get([Key | _KeyPath], Data, OnNotFound) -> - OnNotFound(Key, Data). - -covert_keys_to_atom(BinKeyMap, Conv) when is_map(BinKeyMap) -> - maps:fold( - fun(K, V, Acc) when is_binary(K) -> - Acc#{Conv(K) => covert_keys_to_atom(V, Conv)}; - (K, V, Acc) when is_atom(K) -> - %% richmap keys - Acc#{K => covert_keys_to_atom(V, Conv)} - end, #{}, BinKeyMap); -covert_keys_to_atom(ListV, Conv) when is_list(ListV) -> - [covert_keys_to_atom(V, Conv) || V <- ListV]; -covert_keys_to_atom(Val, _) -> Val. + put_raw(emqx_map_lib:deep_put(KeyPath, get_raw(), Config)). diff --git a/apps/emqx/src/emqx_config_handler.erl b/apps/emqx/src/emqx_config_handler.erl index bc915d778..a067f6632 100644 --- a/apps/emqx/src/emqx_config_handler.erl +++ b/apps/emqx/src/emqx_config_handler.erl @@ -45,11 +45,15 @@ -type handler_name() :: module(). -type handlers() :: #{emqx_config:config_key() => handlers(), ?MOD => handler_name()}. --optional_callbacks([handle_update_config/2]). +-optional_callbacks([ handle_update_config/2 + , post_update_config/2 + ]). -callback handle_update_config(emqx_config:update_request(), emqx_config:raw_config()) -> emqx_config:update_request(). +-callback post_update_config(emqx_config:config(), emqx_config:config()) -> any(). + -type state() :: #{ handlers := handlers(), atom() => term() @@ -72,22 +76,23 @@ add_handler(ConfKeyPath, HandlerName) -> -spec init(term()) -> {ok, state()}. init(_) -> - {ok, RawConf} = hocon:load(emqx_conf_name(), #{format => richmap}), + RawConf = load_config_file(), {_MappedEnvs, Conf} = hocon_schema:map_translate(emqx_schema, RawConf, #{}), ok = save_config_to_emqx(to_plainmap(Conf), to_plainmap(RawConf)), {ok, #{handlers => #{?MOD => ?MODULE}}}. handle_call({add_child, ConfKeyPath, HandlerName}, _From, State = #{handlers := Handlers}) -> {reply, ok, State#{handlers => - emqx_config:deep_put(ConfKeyPath, Handlers, #{?MOD => HandlerName})}}; + emqx_map_lib:deep_put(ConfKeyPath, Handlers, #{?MOD => HandlerName})}}; handle_call({update_config, ConfKeyPath, UpdateReq, RawConf}, _From, #{handlers := Handlers} = State) -> + OldConf = emqx_config:get(), try {RootKeys, Conf} = do_update_config(ConfKeyPath, Handlers, RawConf, UpdateReq), - {reply, save_configs(RootKeys, Conf), State} + Result = save_configs(RootKeys, Conf), + do_post_update_config(ConfKeyPath, Handlers, OldConf, emqx_config:get()), + {reply, Result, State} catch - throw: Reason -> - {reply, {error, Reason}, State}; Error : Reason : ST -> ?LOG(error, "update config failed: ~p", [{Error, Reason, ST}]), {reply, {error, Reason}, State} @@ -109,29 +114,40 @@ terminate(_Reason, _State) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. -do_update_config([], Handlers, OldConf, UpdateReq) -> - call_handle_update_config(Handlers, OldConf, UpdateReq); -do_update_config([ConfKey | ConfKeyPath], Handlers, OldConf, UpdateReq) -> +do_update_config([], Handlers, OldRawConf, UpdateReq) -> + call_handle_update_config(Handlers, OldRawConf, UpdateReq); +do_update_config([ConfKey | ConfKeyPath], Handlers, OldRawConf, UpdateReq) -> + SubOldRawConf = get_sub_config(bin(ConfKey), OldRawConf), + SubHandlers = maps:get(ConfKey, Handlers, #{}), + NewUpdateReq = do_update_config(ConfKeyPath, SubHandlers, SubOldRawConf, UpdateReq), + call_handle_update_config(Handlers, OldRawConf, #{bin(ConfKey) => NewUpdateReq}). + +do_post_update_config([], Handlers, OldConf, NewConf) -> + call_post_update_config(Handlers, OldConf, NewConf); +do_post_update_config([ConfKey | ConfKeyPath], Handlers, OldConf, NewConf) -> SubOldConf = get_sub_config(ConfKey, OldConf), - case maps:find(ConfKey, Handlers) of - error -> throw({handler_not_found, ConfKey}); - {ok, SubHandlers} -> - NewUpdateReq = do_update_config(ConfKeyPath, SubHandlers, SubOldConf, UpdateReq), - call_handle_update_config(Handlers, OldConf, #{bin(ConfKey) => NewUpdateReq}) - end. + SubNewConf = get_sub_config(ConfKey, NewConf), + SubHandlers = maps:get(ConfKey, Handlers, #{}), + _ = do_post_update_config(ConfKeyPath, SubHandlers, SubOldConf, SubNewConf), + call_post_update_config(Handlers, OldConf, NewConf). -get_sub_config(_, undefined) -> - undefined; -get_sub_config(ConfKey, OldConf) when is_map(OldConf) -> - maps:get(bin(ConfKey), OldConf, undefined); -get_sub_config(_, OldConf) -> - OldConf. +get_sub_config(ConfKey, Conf) when is_map(Conf) -> + maps:get(ConfKey, Conf, undefined); +get_sub_config(_, _Conf) -> %% the Conf is a primitive + undefined. -call_handle_update_config(Handlers, OldConf, UpdateReq) -> +call_handle_update_config(Handlers, OldRawConf, UpdateReq) -> HandlerName = maps:get(?MOD, Handlers, undefined), case erlang:function_exported(HandlerName, handle_update_config, 2) of - true -> HandlerName:handle_update_config(UpdateReq, OldConf); - false -> UpdateReq %% the default behaviour is overwriting the old config + true -> HandlerName:handle_update_config(UpdateReq, OldRawConf); + false -> merge_to_old_config(UpdateReq, OldRawConf) + end. + +call_post_update_config(Handlers, OldConf, NewConf) -> + HandlerName = maps:get(?MOD, Handlers, undefined), + case erlang:function_exported(HandlerName, post_update_config, 2) of + true -> _ = HandlerName:post_update_config(NewConf, OldConf); + false -> ok end. %% callbacks for the top-level handler @@ -139,11 +155,15 @@ handle_update_config(UpdateReq, OldConf) -> FullRawConf = merge_to_old_config(UpdateReq, OldConf), {maps:keys(UpdateReq), FullRawConf}. -%% default callback of config handlers -merge_to_old_config(UpdateReq, undefined) -> - merge_to_old_config(UpdateReq, #{}); -merge_to_old_config(UpdateReq, RawConf) -> - maps:merge(RawConf, UpdateReq). +%% The default callback of config handlers +%% the behaviour is overwriting the old config if: +%% 1. the old config is undefined +%% 2. either the old or the new config is not of map type +%% the behaviour is merging the new the config to the old config if they are maps. +merge_to_old_config(UpdateReq, RawConf) when is_map(UpdateReq), is_map(RawConf) -> + maps:merge(RawConf, UpdateReq); +merge_to_old_config(UpdateReq, _RawConf) -> + UpdateReq. %%============================================================================ save_configs(RootKeys, RawConf) -> @@ -160,7 +180,8 @@ save_configs(RootKeys, RawConf) -> % end, MappedEnvs). save_config_to_emqx(Conf, RawConf) -> - emqx_config:put(emqx_config:unsafe_atom_key_map(Conf)), + ?LOG(debug, "set config: ~p", [Conf]), + emqx_config:put(emqx_map_lib:unsafe_atom_key_map(Conf)), emqx_config:put_raw(RawConf). save_config_to_disk(RootKeys, Conf) -> @@ -191,14 +212,16 @@ read_old_config(FileName) -> _ -> #{} end. -emqx_conf_name() -> - filename:join([etc_dir(), "emqx.conf"]). +load_config_file() -> + lists:foldl(fun(ConfFile, Acc) -> + {ok, RawConf} = hocon:load(ConfFile, #{format => richmap}), + emqx_map_lib:deep_merge(Acc, RawConf) + end, #{}, emqx:get_env(config_files, [])). emqx_override_conf_name() -> - filename:join([emqx:get_env(data_dir), "emqx_override.conf"]). - -etc_dir() -> - emqx:get_env(etc_dir). + File = filename:join([emqx:get_env(data_dir), "emqx_override.conf"]), + ok = filelib:ensure_dir(File), + File. to_richmap(Map) -> {ok, RichMap} = hocon:binary(jsx:encode(Map), #{format => richmap}), diff --git a/apps/emqx/src/emqx_connection.erl b/apps/emqx/src/emqx_connection.erl index 7300281d7..9e688fc0f 100644 --- a/apps/emqx/src/emqx_connection.erl +++ b/apps/emqx/src/emqx_connection.erl @@ -83,8 +83,6 @@ sockname :: emqx_types:peername(), %% Sock State sockstate :: emqx_types:sockstate(), - %% The {active, N} option - active_n :: pos_integer(), %% Limiter limiter :: maybe(emqx_limiter:limiter()), %% Limit Timer @@ -102,13 +100,17 @@ %% Idle Timeout idle_timeout :: integer(), %% Idle Timer - idle_timer :: maybe(reference()) + idle_timer :: maybe(reference()), + %% Zone name + zone :: atom(), + %% Listener Name + listener :: atom() }). -type(state() :: #state{}). -define(ACTIVE_N, 100). --define(INFO_KEYS, [socktype, peername, sockname, sockstate, active_n]). +-define(INFO_KEYS, [socktype, peername, sockname, sockstate]). -define(CONN_STATS, [recv_pkt, recv_msg, send_pkt, send_msg]). -define(SOCK_STATS, [recv_oct, recv_cnt, send_oct, send_cnt, send_pend]). @@ -134,7 +136,7 @@ , system_code_change/4 ]}). --spec(start_link(esockd:transport(), esockd:socket(), proplists:proplist()) +-spec(start_link(esockd:transport(), esockd:socket(), emqx_channel:opts()) -> {ok, pid()}). start_link(Transport, Socket, Options) -> Args = [self(), Transport, Socket, Options], @@ -165,8 +167,6 @@ info(sockname, #state{sockname = Sockname}) -> Sockname; info(sockstate, #state{sockstate = SockSt}) -> SockSt; -info(active_n, #state{active_n = ActiveN}) -> - ActiveN; info(stats_timer, #state{stats_timer = StatsTimer}) -> StatsTimer; info(limit_timer, #state{limit_timer = LimitTimer}) -> @@ -243,7 +243,7 @@ init(Parent, Transport, RawSocket, Options) -> exit_on_sock_error(Reason) end. -init_state(Transport, Socket, Options) -> +init_state(Transport, Socket, #{zone := Zone, listener := Listener} = Opts) -> {ok, Peername} = Transport:ensure_ok_or_exit(peername, [Socket]), {ok, Sockname} = Transport:ensure_ok_or_exit(sockname, [Socket]), Peercert = Transport:ensure_ok_or_exit(peercert, [Socket]), @@ -253,26 +253,29 @@ init_state(Transport, Socket, Options) -> peercert => Peercert, conn_mod => ?MODULE }, - Zone = proplists:get_value(zone, Options), - ActiveN = proplists:get_value(active_n, Options, ?ACTIVE_N), - PubLimit = emqx_zone:publish_limit(Zone), - BytesIn = proplists:get_value(rate_limit, Options), - RateLimit = emqx_zone:ratelimit(Zone), - Limiter = emqx_limiter:init(Zone, PubLimit, BytesIn, RateLimit), - FrameOpts = emqx_zone:mqtt_frame_options(Zone), + Limiter = emqx_limiter:init(Zone, undefined, undefined, []), + FrameOpts = #{ + strict_mode => emqx_config:get_listener_conf(Zone, Listener, [mqtt, strict_mode]), + max_size => emqx_config:get_listener_conf(Zone, Listener, [mqtt, max_packet_size]) + }, ParseState = emqx_frame:initial_parse_state(FrameOpts), Serialize = emqx_frame:serialize_opts(), - Channel = emqx_channel:init(ConnInfo, Options), - GcState = emqx_zone:init_gc_state(Zone), - StatsTimer = emqx_zone:stats_timer(Zone), - IdleTimeout = emqx_zone:idle_timeout(Zone), + Channel = emqx_channel:init(ConnInfo, Opts), + GcState = case emqx_config:get_listener_conf(Zone, Listener, [force_gc]) of + #{enable := false} -> undefined; + GcPolicy -> emqx_gc:init(GcPolicy) + end, + StatsTimer = case emqx_config:get_listener_conf(Zone, Listener, [stats, enable]) of + true -> undefined; + false -> disabled + end, + IdleTimeout = emqx_channel:get_mqtt_conf(Zone, Listener, idle_timeout), IdleTimer = start_timer(IdleTimeout, idle_timeout), #state{transport = Transport, socket = Socket, peername = Peername, sockname = Sockname, sockstate = idle, - active_n = ActiveN, limiter = Limiter, parse_state = ParseState, serialize = Serialize, @@ -280,7 +283,9 @@ init_state(Transport, Socket, Options) -> gc_state = GcState, stats_timer = StatsTimer, idle_timeout = IdleTimeout, - idle_timer = IdleTimer + idle_timer = IdleTimer, + zone = Zone, + listener = Listener }. run_loop(Parent, State = #state{transport = Transport, @@ -288,8 +293,9 @@ run_loop(Parent, State = #state{transport = Transport, peername = Peername, channel = Channel}) -> emqx_logger:set_metadata_peername(esockd:format(Peername)), - emqx_misc:tune_heap_size(emqx_zone:oom_policy( - emqx_channel:info(zone, Channel))), + ShutdownPolicy = emqx_config:get_listener_conf(emqx_channel:info(zone, Channel), + emqx_channel:info(listener, Channel), [force_shutdown]), + emqx_misc:tune_heap_size(ShutdownPolicy), case activate_socket(State) of {ok, NState} -> hibernate(Parent, NState); {error, Reason} -> @@ -458,14 +464,15 @@ handle_msg({Passive, _Sock}, State) NState1 = check_oom(run_gc(InStats, NState)), handle_info(activate_socket, NState1); -handle_msg(Deliver = {deliver, _Topic, _Msg}, - #state{active_n = ActiveN} = State) -> +handle_msg(Deliver = {deliver, _Topic, _Msg}, #state{zone = Zone, + listener = Listener} = State) -> + ActiveN = get_active_n(Zone, Listener), Delivers = [Deliver|emqx_misc:drain_deliver(ActiveN)], with_channel(handle_deliver, [Delivers], State); %% Something sent -handle_msg({inet_reply, _Sock, ok}, State = #state{active_n = ActiveN}) -> - case emqx_pd:get_counter(outgoing_pubs) > ActiveN of +handle_msg({inet_reply, _Sock, ok}, State = #state{zone = Zone, listener = Listener}) -> + case emqx_pd:get_counter(outgoing_pubs) > get_active_n(Zone, Listener) of true -> Pubs = emqx_pd:reset_counter(outgoing_pubs), Bytes = emqx_pd:reset_counter(outgoing_bytes), @@ -794,15 +801,14 @@ run_gc(Stats, State = #state{gc_state = GcSt}) -> end. check_oom(State = #state{channel = Channel}) -> - Zone = emqx_channel:info(zone, Channel), - OomPolicy = emqx_zone:oom_policy(Zone), - ?tp(debug, check_oom, #{policy => OomPolicy}), - case ?ENABLED(OomPolicy) andalso emqx_misc:check_oom(OomPolicy) of + ShutdownPolicy = emqx_config:get_listener_conf(emqx_channel:info(zone, Channel), + emqx_channel:info(listener, Channel), [force_shutdown]), + ?tp(debug, check_oom, #{policy => ShutdownPolicy}), + case emqx_misc:check_oom(ShutdownPolicy) of {shutdown, Reason} -> %% triggers terminate/2 callback immediately erlang:exit({shutdown, Reason}); - _Other -> - ok + _ -> ok end, State. @@ -814,10 +820,10 @@ activate_socket(State = #state{sockstate = closed}) -> {ok, State}; activate_socket(State = #state{sockstate = blocked}) -> {ok, State}; -activate_socket(State = #state{transport = Transport, - socket = Socket, - active_n = N}) -> - case Transport:setopts(Socket, [{active, N}]) of +activate_socket(State = #state{transport = Transport, socket = Socket, + zone = Zone, listener = Listener}) -> + ActiveN = get_active_n(Zone, Listener), + case Transport:setopts(Socket, [{active, ActiveN}]) of ok -> {ok, State#state{sockstate = running}}; Error -> Error end. @@ -898,3 +904,9 @@ get_state(Pid) -> State = sys:get_state(Pid), maps:from_list(lists:zip(record_info(fields, state), tl(tuple_to_list(State)))). + +get_active_n(Zone, Listener) -> + case emqx_config:get([zones, Zone, listeners, Listener, type]) of + quic -> 100; + _ -> emqx_config:get_listener_conf(Zone, Listener, [tcp, active_n]) + end. diff --git a/apps/emqx/src/emqx_flapping.erl b/apps/emqx/src/emqx_flapping.erl index a0eab9c18..64633833b 100644 --- a/apps/emqx/src/emqx_flapping.erl +++ b/apps/emqx/src/emqx_flapping.erl @@ -45,16 +45,16 @@ -define(FLAPPING_DURATION, 60000). -define(FLAPPING_BANNED_INTERVAL, 300000). -define(DEFAULT_DETECT_POLICY, - #{threshold => ?FLAPPING_THRESHOLD, - duration => ?FLAPPING_DURATION, - banned_interval => ?FLAPPING_BANNED_INTERVAL + #{max_count => ?FLAPPING_THRESHOLD, + window_time => ?FLAPPING_DURATION, + ban_time => ?FLAPPING_BANNED_INTERVAL }). -record(flapping, { clientid :: emqx_types:clientid(), peerhost :: emqx_types:peerhost(), started_at :: pos_integer(), - detect_cnt :: pos_integer() + detect_cnt :: integer() }). -opaque(flapping() :: #flapping{}). @@ -69,33 +69,28 @@ stop() -> gen_server:stop(?MODULE). %% @doc Detect flapping when a MQTT client disconnected. -spec(detect(emqx_types:clientinfo()) -> boolean()). -detect(Client) -> detect(Client, get_policy()). - -detect(#{clientid := ClientId, peerhost := PeerHost}, Policy = #{threshold := Threshold}) -> - try ets:update_counter(?FLAPPING_TAB, ClientId, {#flapping.detect_cnt, 1}) of +detect(#{clientid := ClientId, peerhost := PeerHost, zone := Zone, listener := Listener}) -> + Policy = #{max_count := Threshold} = get_policy(Zone, Listener), + %% The initial flapping record sets the detect_cnt to 0. + InitVal = #flapping{ + clientid = ClientId, + peerhost = PeerHost, + started_at = erlang:system_time(millisecond), + detect_cnt = 0 + }, + case ets:update_counter(?FLAPPING_TAB, ClientId, {#flapping.detect_cnt, 1}, InitVal) of Cnt when Cnt < Threshold -> false; - _Cnt -> case ets:take(?FLAPPING_TAB, ClientId) of - [Flapping] -> - ok = gen_server:cast(?MODULE, {detected, Flapping, Policy}), - true; - [] -> false - end - catch - error:badarg -> - %% Create a flapping record. - Flapping = #flapping{clientid = ClientId, - peerhost = PeerHost, - started_at = erlang:system_time(millisecond), - detect_cnt = 1 - }, - true = ets:insert(?FLAPPING_TAB, Flapping), - false + _Cnt -> + case ets:take(?FLAPPING_TAB, ClientId) of + [Flapping] -> + ok = gen_server:cast(?MODULE, {detected, Flapping, Policy}), + true; + [] -> false + end end. --compile({inline, [get_policy/0, now_diff/1]}). - -get_policy() -> - emqx:get_env(flapping_detect_policy, ?DEFAULT_DETECT_POLICY). +get_policy(Zone, Listener) -> + emqx_config:get_listener_conf(Zone, Listener, [flapping_detect]). now_diff(TS) -> erlang:system_time(millisecond) - TS. @@ -105,11 +100,12 @@ now_diff(TS) -> erlang:system_time(millisecond) - TS. init([]) -> ok = emqx_tables:new(?FLAPPING_TAB, [public, set, - {keypos, 2}, + {keypos, #flapping.clientid}, {read_concurrency, true}, {write_concurrency, true} ]), - {ok, ensure_timer(#{}), hibernate}. + start_timers(), + {ok, #{}, hibernate}. handle_call(Req, _From, State) -> ?LOG(error, "Unexpected call: ~p", [Req]), @@ -119,17 +115,17 @@ handle_cast({detected, #flapping{clientid = ClientId, peerhost = PeerHost, started_at = StartedAt, detect_cnt = DetectCnt}, - #{duration := Duration, banned_interval := Interval}}, State) -> - case now_diff(StartedAt) < Duration of + #{window_time := WindTime, ban_time := Interval}}, State) -> + case now_diff(StartedAt) < WindTime of true -> %% Flapping happened:( ?LOG(error, "Flapping detected: ~s(~s) disconnected ~w times in ~wms", - [ClientId, inet:ntoa(PeerHost), DetectCnt, Duration]), + [ClientId, inet:ntoa(PeerHost), DetectCnt, WindTime]), Now = erlang:system_time(second), Banned = #banned{who = {clientid, ClientId}, by = <<"flapping detector">>, reason = <<"flapping is detected">>, at = Now, - until = Now + Interval}, + until = Now + (Interval div 1000)}, emqx_banned:create(Banned); false -> ?LOG(warning, "~s(~s) disconnected ~w times in ~wms", @@ -141,11 +137,13 @@ handle_cast(Msg, State) -> ?LOG(error, "Unexpected cast: ~p", [Msg]), {noreply, State}. -handle_info({timeout, TRef, expired_detecting}, State = #{expired_timer := TRef}) -> - Timestamp = erlang:system_time(millisecond) - maps:get(duration, get_policy()), +handle_info({timeout, _TRef, {garbage_collect, Zone, Listener}}, State) -> + Timestamp = erlang:system_time(millisecond) + - maps:get(window_time, get_policy(Zone, Listener)), MatchSpec = [{{'_', '_', '_', '$1', '_'},[{'<', '$1', Timestamp}], [true]}], ets:select_delete(?FLAPPING_TAB, MatchSpec), - {noreply, ensure_timer(State), hibernate}; + start_timer(Zone, Listener), + {noreply, State, hibernate}; handle_info(Info, State) -> ?LOG(error, "Unexpected info: ~p", [Info]), @@ -157,7 +155,13 @@ terminate(_Reason, _State) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. -ensure_timer(State) -> - Timeout = maps:get(duration, get_policy()), - TRef = emqx_misc:start_timer(Timeout, expired_detecting), - State#{expired_timer => TRef}. \ No newline at end of file +start_timer(Zone, Listener) -> + WindTime = maps:get(window_time, get_policy(Zone, Listener)), + emqx_misc:start_timer(WindTime, {garbage_collect, Zone, Listener}). + +start_timers() -> + lists:foreach(fun({Zone, ZoneConf}) -> + lists:foreach(fun({Listener, _}) -> + start_timer(Zone, Listener) + end, maps:to_list(maps:get(listeners, ZoneConf, #{}))) + end, maps:to_list(emqx_config:get([zones], #{}))). \ No newline at end of file diff --git a/apps/emqx/src/emqx_frame.erl b/apps/emqx/src/emqx_frame.erl index 37063c65f..082801bad 100644 --- a/apps/emqx/src/emqx_frame.erl +++ b/apps/emqx/src/emqx_frame.erl @@ -81,11 +81,7 @@ initial_parse_state() -> -spec(initial_parse_state(options()) -> {none, options()}). initial_parse_state(Options) when is_map(Options) -> - ?none(merge_opts(Options)). - -%% @pivate -merge_opts(Options) -> - maps:merge(?DEFAULT_OPTIONS, Options). + ?none(maps:merge(?DEFAULT_OPTIONS, Options)). %%-------------------------------------------------------------------- %% Parse MQTT Frame @@ -643,7 +639,7 @@ serialize_properties(Props) when is_map(Props) -> Bin = << <<(serialize_property(Prop, Val))/binary>> || {Prop, Val} <- maps:to_list(Props) >>, [serialize_variable_byte_integer(byte_size(Bin)), Bin]. -serialize_property(_, undefined) -> +serialize_property(_, Disabled) when Disabled =:= disabled; Disabled =:= undefined -> <<>>; serialize_property('Payload-Format-Indicator', Val) -> <<16#01, Val>>; diff --git a/apps/emqx/src/emqx_kernel_sup.erl b/apps/emqx/src/emqx_kernel_sup.erl index 4e29431e2..1c6b0617e 100644 --- a/apps/emqx/src/emqx_kernel_sup.erl +++ b/apps/emqx/src/emqx_kernel_sup.erl @@ -27,14 +27,15 @@ start_link() -> init([]) -> {ok, {{one_for_one, 10, 100}, - [child_spec(emqx_global_gc, worker), - child_spec(emqx_pool_sup, supervisor), - child_spec(emqx_hooks, worker), - child_spec(emqx_stats, worker), - child_spec(emqx_metrics, worker), - child_spec(emqx_ctl, worker), - child_spec(emqx_zone, worker), - child_spec(emqx_config_handler, worker) + %% always start emqx_config_handler first to load the emqx.conf to emqx_config + [ child_spec(emqx_config_handler, worker) + , child_spec(emqx_global_gc, worker) + , child_spec(emqx_pool_sup, supervisor) + , child_spec(emqx_hooks, worker) + , child_spec(emqx_stats, worker) + , child_spec(emqx_metrics, worker) + , child_spec(emqx_ctl, worker) + , child_spec(emqx_zone, worker) ]}}. child_spec(M, Type) -> diff --git a/apps/emqx/src/emqx_listeners.erl b/apps/emqx/src/emqx_listeners.erl index 3e688b91e..8cf3852e2 100644 --- a/apps/emqx/src/emqx_listeners.erl +++ b/apps/emqx/src/emqx_listeners.erl @@ -21,7 +21,6 @@ %% APIs -export([ start/0 - , ensure_all_started/0 , restart/0 , stop/0 ]). @@ -29,90 +28,29 @@ -export([ start_listener/1 , start_listener/3 , stop_listener/1 + , stop_listener/3 , restart_listener/1 , restart_listener/3 ]). --export([ find_id_by_listen_on/1 - , find_by_listen_on/1 - , find_by_id/1 - , identifier/1 - , format_listen_on/1 - ]). - --type(listener() :: #{ name := binary() - , proto := esockd:proto() - , listen_on := esockd:listen_on() - , opts := [esockd:option()] - }). - -%% @doc Find listener identifier by listen-on. -%% Return empty string (binary) if listener is not found in config. --spec(find_id_by_listen_on(esockd:listen_on()) -> binary() | false). -find_id_by_listen_on(ListenOn) -> - case find_by_listen_on(ListenOn) of - false -> false; - L -> identifier(L) - end. - -%% @doc Find listener by listen-on. -%% Return 'false' if not found. --spec(find_by_listen_on(esockd:listen_on()) -> listener() | false). -find_by_listen_on(ListenOn) -> - find_by_listen_on(ListenOn, emqx:get_env(listeners, [])). - -%% @doc Find listener by identifier. -%% Return 'false' if not found. --spec(find_by_id(string() | binary()) -> listener() | false). -find_by_id(Id) -> - find_by_id(iolist_to_binary(Id), emqx:get_env(listeners, [])). - -%% @doc Return the ID of the given listener. --spec identifier(listener()) -> binary(). -identifier(#{proto := Proto, name := Name}) -> - identifier(Proto, Name). - %% @doc Start all listeners. -spec(start() -> ok). start() -> - lists:foreach(fun start_listener/1, emqx:get_env(listeners, [])). + foreach_listeners(fun start_listener/3). -%% @doc Ensure all configured listeners are started. -%% Raise exception if any of them failed to start. --spec(ensure_all_started() -> ok). -ensure_all_started() -> - ensure_all_started(emqx:get_env(listeners, []), []). +-spec start_listener(atom()) -> ok | {error, term()}. +start_listener(ListenerId) -> + apply_on_listener(ListenerId, fun start_listener/3). -ensure_all_started([], []) -> ok; -ensure_all_started([], Failed) -> error(Failed); -ensure_all_started([L | Rest], Results) -> - #{proto := Proto, listen_on := ListenOn, opts := Options} = L, - NewResults = - case start_listener(Proto, ListenOn, Options) of - {ok, _Pid} -> - Results; - {error, {already_started, _Pid}} -> - Results; - {error, Reason} -> - [{identifier(L), Reason} | Results] - end, - ensure_all_started(Rest, NewResults). - -%% @doc Format address:port for logging. --spec(format_listen_on(esockd:listen_on()) -> [char()]). -format_listen_on(ListenOn) -> format(ListenOn). - --spec(start_listener(listener()) -> ok). -start_listener(#{proto := Proto, name := Name, listen_on := ListenOn, opts := Options}) -> - ID = identifier(Proto, Name), - case start_listener(Proto, ListenOn, Options) of - {ok, skipped} -> - console_print("Start ~s listener on ~s skpped.~n", [ID, format(ListenOn)]); +-spec start_listener(atom(), atom(), map()) -> ok | {error, term()}. +start_listener(ZoneName, ListenerName, #{type := Type, bind := Bind} = Conf) -> + case do_start_listener(ZoneName, ListenerName, Conf) of {ok, _} -> - console_print("Start ~s listener on ~s successfully.~n", [ID, format(ListenOn)]); + console_print("Start ~s listener ~s on ~s successfully.~n", + [Type, listener_id(ZoneName, ListenerName), format(Bind)]); {error, Reason} -> - io:format(standard_error, "Failed to start mqtt listener ~s on ~s: ~0p~n", - [ID, format(ListenOn), Reason]), + io:format(standard_error, "Failed to start ~s listener ~s on ~s: ~0p~n", + [Type, listener_id(ZoneName, ListenerName), format(Bind), Reason]), error(Reason) end. @@ -124,154 +62,128 @@ console_print(_Fmt, _Args) -> ok. -endif. %% Start MQTT/TCP listener --spec(start_listener(esockd:proto(), esockd:listen_on(), [esockd:option()]) - -> {ok, pid() | skipped} | {error, term()}). -start_listener(tcp, ListenOn, Options) -> - start_mqtt_listener('mqtt:tcp', ListenOn, Options); - -%% Start MQTT/TLS listener -start_listener(Proto, ListenOn, Options) when Proto == ssl; Proto == tls -> - start_mqtt_listener('mqtt:ssl', ListenOn, Options); +-spec(do_start_listener(atom(), atom(), map()) + -> {ok, pid()} | {error, term()}). +do_start_listener(ZoneName, ListenerName, #{type := tcp, bind := ListenOn} = Opts) -> + esockd:open(listener_id(ZoneName, ListenerName), ListenOn, merge_default(esockd_opts(Opts)), + {emqx_connection, start_link, + [#{zone => ZoneName, listener => ListenerName}]}); %% Start MQTT/WS listener -start_listener(Proto, ListenOn, Options) when Proto == http; Proto == ws -> - start_http_listener(fun cowboy:start_clear/3, 'mqtt:ws', ListenOn, - ranch_opts(Options), ws_opts(Options)); - -%% Start MQTT/WSS listener -start_listener(Proto, ListenOn, Options) when Proto == https; Proto == wss -> - start_http_listener(fun cowboy:start_tls/3, 'mqtt:wss', ListenOn, - ranch_opts(Options), ws_opts(Options)); +do_start_listener(ZoneName, ListenerName, #{type := ws, bind := ListenOn} = Opts) -> + Id = listener_id(ZoneName, ListenerName), + RanchOpts = ranch_opts(ListenOn, Opts), + WsOpts = ws_opts(ZoneName, ListenerName, Opts), + case is_ssl(Opts) of + false -> + cowboy:start_clear(Id, RanchOpts, WsOpts); + true -> + cowboy:start_tls(Id, RanchOpts, WsOpts) + end; %% Start MQTT/QUIC listener -start_listener(quic, ListenOn, Options) -> - case [ A || {quicer, _, _} = A<-application:which_applications() ] of - [_] -> - %% @fixme unsure why we need reopen lib and reopen config. - quicer_nif:open_lib(), - quicer_nif:reg_open(), - SSLOpts = proplists:get_value(ssl_options, Options), - DefAcceptors = erlang:system_info(schedulers_online) * 8, - ListenOpts = [ {cert, proplists:get_value(certfile, SSLOpts)} - , {key, proplists:get_value(keyfile, SSLOpts)} - , {alpn, ["mqtt"]} - , {conn_acceptors, proplists:get_value(acceptors, Options, DefAcceptors)} - , {idle_timeout_ms, proplists:get_value(idle_timeout, Options, 60000)} - ], - ConnectionOpts = [ {conn_callback, emqx_quic_connection} - , {peer_unidi_stream_count, 1} - , {peer_bidi_stream_count, 10} - | Options - ], - StreamOpts = [], - quicer:start_listener('mqtt:quic', ListenOn, {ListenOpts, ConnectionOpts, StreamOpts}); - [] -> - io:format(standard_error, "INFO: quicer application is unavailable/disabled~n", - []), - {ok, skipped} - end. +do_start_listener(ZoneName, ListenerName, #{type := quic, bind := ListenOn} = Opts) -> + %% @fixme unsure why we need reopen lib and reopen config. + quicer_nif:open_lib(), + quicer_nif:reg_open(), + DefAcceptors = erlang:system_info(schedulers_online) * 8, + ListenOpts = [ {cert, maps:get(certfile, Opts)} + , {key, maps:get(keyfile, Opts)} + , {alpn, ["mqtt"]} + , {conn_acceptors, maps:get(acceptors, Opts, DefAcceptors)} + , {idle_timeout_ms, emqx_config:get_listener_conf(ZoneName, ListenerName, + [mqtt, idle_timeout])} + ], + ConnectionOpts = #{conn_callback => emqx_quic_connection + , peer_unidi_stream_count => 1 + , peer_bidi_stream_count => 10 + , zone => ZoneName + , listener => ListenerName + }, + StreamOpts = [], + quicer:start_listener(listener_id(ZoneName, ListenerName), + port(ListenOn), {ListenOpts, ConnectionOpts, StreamOpts}). -replace(Opts, Key, Value) -> [{Key, Value} | proplists:delete(Key, Opts)]. +esockd_opts(Opts0) -> + Opts1 = maps:with([acceptors, max_connections, proxy_protocol, proxy_protocol_timeout], Opts0), + Opts2 = case emqx_map_lib:deep_get([rate_limit, max_conn_rate], Opts0) of + infinity -> Opts1; + Rate -> Opts1#{max_conn_rate => Rate} + end, + Opts3 = Opts2#{access_rules => esockd_access_rules(maps:get(access_rules, Opts0, []))}, + maps:to_list(case is_ssl(Opts0) of + false -> + Opts3#{tcp_options => tcp_opts(Opts0)}; + true -> + Opts3#{ssl_options => ssl_opts(Opts0), tcp_options => tcp_opts(Opts0)} + end). -drop_tls13_for_old_otp(Options) -> - case proplists:get_value(ssl_options, Options) of - undefined -> Options; - SslOpts -> - SslOpts1 = emqx_tls_lib:drop_tls13_for_old_otp(SslOpts), - replace(Options, ssl_options, SslOpts1) - end. - -start_mqtt_listener(Name, ListenOn, Options0) -> - Options = drop_tls13_for_old_otp(Options0), - SockOpts = esockd:parse_opt(Options), - esockd:open(Name, ListenOn, merge_default(SockOpts), - {emqx_connection, start_link, [Options -- SockOpts]}). - -start_http_listener(Start, Name, ListenOn, RanchOpts, ProtoOpts) -> - Start(ws_name(Name, ListenOn), with_port(ListenOn, RanchOpts), ProtoOpts). - -mqtt_path(Options) -> - proplists:get_value(mqtt_path, Options, "/mqtt"). - -ws_opts(Options) -> - WsPaths = [{mqtt_path(Options), emqx_ws_connection, Options}], +ws_opts(ZoneName, ListenerName, Opts) -> + WsPaths = [{maps:get(mqtt_path, Opts, "/mqtt"), emqx_ws_connection, + #{zone => ZoneName, listener => ListenerName}}], Dispatch = cowboy_router:compile([{'_', WsPaths}]), - ProxyProto = proplists:get_value(proxy_protocol, Options, false), + ProxyProto = maps:get(proxy_protocol, Opts, false), #{env => #{dispatch => Dispatch}, proxy_header => ProxyProto}. -ranch_opts(Options0) -> - Options = drop_tls13_for_old_otp(Options0), - NumAcceptors = proplists:get_value(acceptors, Options, 4), - MaxConnections = proplists:get_value(max_connections, Options, 1024), - TcpOptions = proplists:get_value(tcp_options, Options, []), - RanchOpts = #{num_acceptors => NumAcceptors, - max_connections => MaxConnections, - socket_opts => TcpOptions}, - case proplists:get_value(ssl_options, Options) of - undefined -> RanchOpts; - SslOptions -> RanchOpts#{socket_opts => TcpOptions ++ SslOptions} - end. +ranch_opts(ListenOn, Opts) -> + NumAcceptors = maps:get(acceptors, Opts, 4), + MaxConnections = maps:get(max_connections, Opts, 1024), + SocketOpts = case is_ssl(Opts) of + true -> tcp_opts(Opts) ++ proplists:delete(handshake_timeout, ssl_opts(Opts)); + false -> tcp_opts(Opts) + end, + #{num_acceptors => NumAcceptors, + max_connections => MaxConnections, + handshake_timeout => maps:get(handshake_timeout, Opts, 15000), + socket_opts => ip_port(ListenOn) ++ SocketOpts}. -with_port(Port, Opts = #{socket_opts := SocketOption}) when is_integer(Port) -> - Opts#{socket_opts => [{port, Port}| SocketOption]}; -with_port({Addr, Port}, Opts = #{socket_opts := SocketOption}) -> - Opts#{socket_opts => [{ip, Addr}, {port, Port}| SocketOption]}. +ip_port(Port) when is_integer(Port) -> + [{port, Port}]; +ip_port({Addr, Port}) -> + [{ip, Addr}, {port, Port}]. + +port(Port) when is_integer(Port) -> Port; +port({_Addr, Port}) when is_integer(Port) -> Port. + +esockd_access_rules(StrRules) -> + Access = fun(S) -> + [A, CIDR] = string:tokens(S, " "), + {list_to_atom(A), case CIDR of "all" -> all; _ -> CIDR end} + end, + [Access(R) || R <- StrRules]. %% @doc Restart all listeners -spec(restart() -> ok). restart() -> - lists:foreach(fun restart_listener/1, emqx:get_env(listeners, [])). + foreach_listeners(fun restart_listener/3). --spec(restart_listener(listener() | string() | binary()) -> ok | {error, any()}). -restart_listener(#{proto := Proto, listen_on := ListenOn, opts := Options}) -> - restart_listener(Proto, ListenOn, Options); -restart_listener(Identifier) -> - case emqx_listeners:find_by_id(Identifier) of - false -> {error, {no_such_listener, Identifier}}; - Listener -> restart_listener(Listener) +-spec(restart_listener(atom()) -> ok | {error, term()}). +restart_listener(ListenerId) -> + apply_on_listener(ListenerId, fun restart_listener/3). + +-spec(restart_listener(atom(), atom(), map()) -> ok | {error, term()}). +restart_listener(ZoneName, ListenerName, Conf) -> + case stop_listener(ZoneName, ListenerName, Conf) of + ok -> start_listener(ZoneName, ListenerName, Conf); + Error -> Error end. --spec(restart_listener(esockd:proto(), esockd:listen_on(), [esockd:option()]) -> - ok | {error, any()}). -restart_listener(tcp, ListenOn, _Options) -> - esockd:reopen('mqtt:tcp', ListenOn); -restart_listener(Proto, ListenOn, _Options) when Proto == ssl; Proto == tls -> - esockd:reopen('mqtt:ssl', ListenOn); -restart_listener(Proto, ListenOn, Options) when Proto == http; Proto == ws -> - _ = cowboy:stop_listener(ws_name('mqtt:ws', ListenOn)), - ok(start_listener(Proto, ListenOn, Options)); -restart_listener(Proto, ListenOn, Options) when Proto == https; Proto == wss -> - _ = cowboy:stop_listener(ws_name('mqtt:wss', ListenOn)), - ok(start_listener(Proto, ListenOn, Options)); -restart_listener(Proto, ListenOn, _Opts) -> - esockd:reopen(Proto, ListenOn). - -ok({ok, _}) -> ok; -ok(Other) -> Other. - %% @doc Stop all listeners. -spec(stop() -> ok). stop() -> - lists:foreach(fun stop_listener/1, emqx:get_env(listeners, [])). + foreach_listeners(fun stop_listener/3). --spec(stop_listener(listener()) -> ok | {error, term()}). -stop_listener(#{proto := Proto, listen_on := ListenOn, opts := Opts}) -> - stop_listener(Proto, ListenOn, Opts). +-spec(stop_listener(atom()) -> ok | {error, term()}). +stop_listener(ListenerId) -> + apply_on_listener(ListenerId, fun stop_listener/3). --spec(stop_listener(esockd:proto(), esockd:listen_on(), [esockd:option()]) - -> ok | {error, term()}). -stop_listener(tcp, ListenOn, _Opts) -> - esockd:close('mqtt:tcp', ListenOn); -stop_listener(Proto, ListenOn, _Opts) when Proto == ssl; Proto == tls -> - esockd:close('mqtt:ssl', ListenOn); -stop_listener(Proto, ListenOn, _Opts) when Proto == http; Proto == ws -> - cowboy:stop_listener(ws_name('mqtt:ws', ListenOn)); -stop_listener(Proto, ListenOn, _Opts) when Proto == https; Proto == wss -> - cowboy:stop_listener(ws_name('mqtt:wss', ListenOn)); -stop_listener(quic, _ListenOn, _Opts) -> - quicer:stop_listener('mqtt:quic'); -stop_listener(Proto, ListenOn, _Opts) -> - esockd:close(Proto, ListenOn). +-spec(stop_listener(atom(), atom(), map()) -> ok | {error, term()}). +stop_listener(ZoneName, ListenerName, #{type := tcp, bind := ListenOn}) -> + esockd:close(listener_id(ZoneName, ListenerName), ListenOn); +stop_listener(ZoneName, ListenerName, #{type := ws}) -> + cowboy:stop_listener(listener_id(ZoneName, ListenerName)); +stop_listener(ZoneName, ListenerName, #{type := quic}) -> + quicer:stop_listener(listener_id(ZoneName, ListenerName)). merge_default(Options) -> case lists:keytake(tcp_options, 1, Options) of @@ -288,23 +200,46 @@ format({Addr, Port}) when is_list(Addr) -> format({Addr, Port}) when is_tuple(Addr) -> io_lib:format("~s:~w", [inet:ntoa(Addr), Port]). -ws_name(Name, {_Addr, Port}) -> - ws_name(Name, Port); -ws_name(Name, Port) -> - list_to_atom(lists:concat([Name, ":", Port])). +listener_id(ZoneName, ListenerName) -> + list_to_atom(lists:append([atom_to_list(ZoneName), ":", atom_to_list(ListenerName)])). -identifier(Proto, Name) when is_atom(Proto) -> - identifier(atom_to_list(Proto), Name); -identifier(Proto, Name) -> - iolist_to_binary(["mqtt", ":", Proto, ":", Name]). - -find_by_listen_on(_ListenOn, []) -> false; -find_by_listen_on(ListenOn, [#{listen_on := ListenOn} = L | _]) -> L; -find_by_listen_on(ListenOn, [_ | Rest]) -> find_by_listen_on(ListenOn, Rest). - -find_by_id(_Id, []) -> false; -find_by_id(Id, [L | Rest]) -> - case identifier(L) =:= Id of - true -> L; - false -> find_by_id(Id, Rest) +decode_listener_id(Id) -> + case string:split(atom_to_list(Id), ":", leading) of + [Zone, Listen] -> {list_to_atom(Zone), list_to_atom(Listen)}; + _ -> error({invalid_listener_id, Id}) + end. + +ssl_opts(Opts) -> + maps:to_list( + emqx_tls_lib:drop_tls13_for_old_otp( + maps:without([enable], + maps:get(ssl, Opts, #{})))). + +tcp_opts(Opts) -> + maps:to_list( + maps:without([active_n], + maps:get(tcp, Opts, #{}))). + +is_ssl(Opts) -> + emqx_map_lib:deep_get([ssl, enable], Opts, false). + +foreach_listeners(Do) -> + lists:foreach(fun({ZoneName, ZoneConf}) -> + lists:foreach(fun({LName, LConf}) -> + Do(ZoneName, LName, merge_zone_and_listener_confs(ZoneConf, LConf)) + end, maps:to_list(maps:get(listeners, ZoneConf, #{}))) + end, maps:to_list(emqx_config:get([zones], #{}))). + +%% merge the configs in zone and listeners in a manner that +%% all config entries in the listener are prior to the ones in the zone. +merge_zone_and_listener_confs(ZoneConf, ListenerConf) -> + ConfsInZonesOnly = [listeners, overall_max_connections], + BaseConf = maps:without(ConfsInZonesOnly, ZoneConf), + emqx_map_lib:deep_merge(BaseConf, ListenerConf). + +apply_on_listener(ListenerId, Do) -> + {ZoneName, ListenerName} = decode_listener_id(ListenerId), + case emqx_config:find([zones, ZoneName, listeners, ListenerName]) of + {not_found, _, _} -> error({not_found, ListenerId}); + {ok, Conf} -> Do(ZoneName, ListenerName, Conf) end. diff --git a/apps/emqx/src/emqx_map_lib.erl b/apps/emqx/src/emqx_map_lib.erl new file mode 100644 index 000000000..c0d922de6 --- /dev/null +++ b/apps/emqx/src/emqx_map_lib.erl @@ -0,0 +1,101 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- +-module(emqx_map_lib). + +-export([ deep_get/2 + , deep_get/3 + , deep_find/2 + , deep_put/3 + , deep_merge/2 + , safe_atom_key_map/1 + , unsafe_atom_key_map/1 + ]). + +-export_type([config_key/0, config_key_path/0]). +-type config_key() :: atom() | binary(). +-type config_key_path() :: [config_key()]. + +%%----------------------------------------------------------------- +-spec deep_get(config_key_path(), map()) -> term(). +deep_get(ConfKeyPath, Map) -> + case deep_find(ConfKeyPath, Map) of + {not_found, KeyPath, Data} -> error({not_found, KeyPath, Data}); + {ok, Data} -> Data + end. + +-spec deep_get(config_key_path(), map(), term()) -> term(). +deep_get(ConfKeyPath, Map, Default) -> + case deep_find(ConfKeyPath, Map) of + {not_found, _KeyPath, _Data} -> Default; + {ok, Data} -> Data + end. + +-spec deep_find(config_key_path(), map()) -> + {ok, term()} | {not_found, config_key_path(), term()}. +deep_find([], Map) -> + {ok, Map}; +deep_find([Key | KeyPath] = Path, Map) when is_map(Map) -> + case maps:find(Key, Map) of + {ok, SubMap} -> deep_find(KeyPath, SubMap); + error -> {not_found, Path, Map} + end; +deep_find(_KeyPath, Data) -> + {not_found, _KeyPath, Data}. + +-spec deep_put(config_key_path(), map(), term()) -> map(). +deep_put([], Map, Config) when is_map(Map) -> + Config; +deep_put([], _Map, Config) -> %% not map, replace it + Config; +deep_put([Key | KeyPath], Map, Config) -> + SubMap = deep_put(KeyPath, maps:get(Key, Map, #{}), Config), + Map#{Key => SubMap}. + +%% #{a => #{b => 3, c => 2}, d => 4} +%% = deep_merge(#{a => #{b => 1, c => 2}, d => 4}, #{a => #{b => 3}}). +-spec deep_merge(map(), map()) -> map(). +deep_merge(BaseMap, NewMap) -> + NewKeys = maps:keys(NewMap) -- maps:keys(BaseMap), + MergedBase = maps:fold(fun(K, V, Acc) -> + case maps:find(K, NewMap) of + error -> + Acc#{K => V}; + {ok, NewV} when is_map(V), is_map(NewV) -> + Acc#{K => deep_merge(V, NewV)}; + {ok, NewV} -> + Acc#{K => NewV} + end + end, #{}, BaseMap), + maps:merge(MergedBase, maps:with(NewKeys, NewMap)). + +unsafe_atom_key_map(Map) -> + covert_keys_to_atom(Map, fun(K) -> binary_to_atom(K, utf8) end). + +safe_atom_key_map(Map) -> + covert_keys_to_atom(Map, fun(K) -> binary_to_existing_atom(K, utf8) end). + +%%--------------------------------------------------------------------------- +covert_keys_to_atom(BinKeyMap, Conv) when is_map(BinKeyMap) -> + maps:fold( + fun(K, V, Acc) when is_binary(K) -> + Acc#{Conv(K) => covert_keys_to_atom(V, Conv)}; + (K, V, Acc) when is_atom(K) -> + %% richmap keys + Acc#{K => covert_keys_to_atom(V, Conv)} + end, #{}, BinKeyMap); +covert_keys_to_atom(ListV, Conv) when is_list(ListV) -> + [covert_keys_to_atom(V, Conv) || V <- ListV]; +covert_keys_to_atom(Val, _) -> Val. diff --git a/apps/emqx/src/emqx_misc.erl b/apps/emqx/src/emqx_misc.erl index 04af5f72c..d45b6f7ce 100644 --- a/apps/emqx/src/emqx_misc.erl +++ b/apps/emqx/src/emqx_misc.erl @@ -197,7 +197,8 @@ check_oom(Policy) -> check_oom(self(), Policy). -spec(check_oom(pid(), emqx_types:oom_policy()) -> ok | {shutdown, term()}). -check_oom(Pid, #{message_queue_len := MaxQLen, +check_oom(_Pid, #{enable := false}) -> ok; +check_oom(Pid, #{max_message_queue_len := MaxQLen, max_heap_size := MaxHeapSize}) -> case process_info(Pid, [message_queue_len, total_heap_size]) of undefined -> ok; @@ -214,13 +215,26 @@ do_check_oom([{Val, Max, Reason}|Rest]) -> false -> do_check_oom(Rest) end. -tune_heap_size(#{max_heap_size := MaxHeapSize}) -> - %% If set to zero, the limit is disabled. - erlang:process_flag(max_heap_size, #{size => MaxHeapSize, - kill => false, - error_logger => true - }); -tune_heap_size(undefined) -> ok. +tune_heap_size(#{enable := false}) -> + ok; +%% If the max_heap_size is set to zero, the limit is disabled. +tune_heap_size(#{max_heap_size := MaxHeapSize}) when MaxHeapSize > 0 -> + MaxSize = case erlang:system_info(wordsize) of + 8 -> % arch_64 + (1 bsl 59) - 1; + 4 -> % arch_32 + (1 bsl 27) - 1 + end, + OverflowedSize = case erlang:trunc(MaxHeapSize * 1.5) of + SZ when SZ > MaxSize -> MaxSize; + SZ -> SZ + end, + erlang:process_flag(max_heap_size, #{ + size => OverflowedSize, + kill => true, + error_logger => true + }). + -spec(proc_name(atom(), pos_integer()) -> atom()). proc_name(Mod, Id) -> diff --git a/apps/emqx/src/emqx_mqtt_caps.erl b/apps/emqx/src/emqx_mqtt_caps.erl index b1be5d5a5..883f7354d 100644 --- a/apps/emqx/src/emqx_mqtt_caps.erl +++ b/apps/emqx/src/emqx_mqtt_caps.erl @@ -20,19 +20,13 @@ -include("emqx_mqtt.hrl"). -include("types.hrl"). --export([ check_pub/2 - , check_sub/3 +-export([ check_pub/3 + , check_sub/4 ]). --export([ get_caps/1 - , get_caps/2 - , get_caps/3 +-export([ get_caps/2 ]). --export([default_caps/0]). - --export([default/0]). - -export_type([caps/0]). -type(caps() :: #{max_packet_size => integer(), @@ -46,7 +40,7 @@ shared_subscription => boolean() }). --define(UNLIMITED, 0). +-define(MAX_TOPIC_LEVELS, 65535). -define(PUBCAP_KEYS, [max_topic_levels, max_qos_allowed, @@ -62,7 +56,7 @@ -define(DEFAULT_CAPS, #{max_packet_size => ?MAX_PACKET_SIZE, max_clientid_len => ?MAX_CLIENTID_LEN, max_topic_alias => ?MAX_TOPIC_AlIAS, - max_topic_levels => ?UNLIMITED, + max_topic_levels => ?MAX_TOPIC_LEVELS, max_qos_allowed => ?QOS_2, retain_available => true, wildcard_subscription => true, @@ -70,18 +64,18 @@ shared_subscription => true }). --spec(check_pub(emqx_types:zone(), +-spec(check_pub(emqx_types:zone(), atom(), #{qos := emqx_types:qos(), retain := boolean(), topic := emqx_topic:topic()}) -> ok_or_error(emqx_types:reason_code())). -check_pub(Zone, Flags) when is_map(Flags) -> +check_pub(Zone, Listener, Flags) when is_map(Flags) -> do_check_pub(case maps:take(topic, Flags) of {Topic, Flags1} -> Flags1#{topic_levels => emqx_topic:levels(Topic)}; error -> Flags - end, get_caps(Zone, publish)). + end, maps:with(?PUBCAP_KEYS, get_caps(Zone, Listener))). do_check_pub(#{topic_levels := Levels}, #{max_topic_levels := Limit}) when Limit > 0, Levels > Limit -> @@ -93,12 +87,12 @@ do_check_pub(#{retain := true}, #{retain_available := false}) -> {error, ?RC_RETAIN_NOT_SUPPORTED}; do_check_pub(_Flags, _Caps) -> ok. --spec(check_sub(emqx_types:zone(), +-spec(check_sub(emqx_types:zone(), atom(), emqx_types:topic(), emqx_types:subopts()) -> ok_or_error(emqx_types:reason_code())). -check_sub(Zone, Topic, SubOpts) -> - Caps = get_caps(Zone, subscribe), +check_sub(Zone, Listener, Topic, SubOpts) -> + Caps = maps:with(?SUBCAP_KEYS, get_caps(Zone, Listener)), Flags = lists:foldl( fun(max_topic_levels, Map) -> Map#{topic_levels => emqx_topic:levels(Topic)}; @@ -119,42 +113,7 @@ do_check_sub(#{is_shared := true}, #{shared_subscription := false}) -> {error, ?RC_SHARED_SUBSCRIPTIONS_NOT_SUPPORTED}; do_check_sub(_Flags, _Caps) -> ok. -default_caps() -> - ?DEFAULT_CAPS. - -get_caps(Zone, Cap, Def) -> - emqx_zone:get_env(Zone, Cap, Def). - -get_caps(Zone, publish) -> - with_env(Zone, '$mqtt_pub_caps', - fun() -> - filter_caps(?PUBCAP_KEYS, get_caps(Zone)) - end); - -get_caps(Zone, subscribe) -> - with_env(Zone, '$mqtt_sub_caps', - fun() -> - filter_caps(?SUBCAP_KEYS, get_caps(Zone)) - end). - -get_caps(Zone) -> - with_env(Zone, '$mqtt_caps', - fun() -> - maps:map(fun(Cap, Def) -> - emqx_zone:get_env(Zone, Cap, Def) - end, ?DEFAULT_CAPS) - end). - -filter_caps(Keys, Caps) -> - maps:filter(fun(Key, _Val) -> lists:member(Key, Keys) end, Caps). - --spec(default() -> caps()). -default() -> ?DEFAULT_CAPS. - -with_env(Zone, Key, InitFun) -> - case emqx_zone:get_env(Zone, Key) of - undefined -> Caps = InitFun(), - ok = emqx_zone:set_env(Zone, Key, Caps), - Caps; - ZoneCaps -> ZoneCaps - end. +get_caps(Zone, Listener) -> + lists:foldl(fun({K, V}, Acc) -> + Acc#{K => emqx_config:get_listener_conf(Zone, Listener, [mqtt, K], V)} + end, #{}, maps:to_list(?DEFAULT_CAPS)). diff --git a/apps/emqx/src/emqx_mqueue.erl b/apps/emqx/src/emqx_mqueue.erl index d0c6365ff..d625209ca 100644 --- a/apps/emqx/src/emqx_mqueue.erl +++ b/apps/emqx/src/emqx_mqueue.erl @@ -67,6 +67,8 @@ , dropped/1 ]). +-define(NO_PRIORITY_TABLE, disabled). + -export_type([mqueue/0, options/0]). -type(topic() :: emqx_topic:topic()). diff --git a/apps/emqx/src/emqx_os_mon.erl b/apps/emqx/src/emqx_os_mon.erl index d6579cac9..b70c27e1b 100644 --- a/apps/emqx/src/emqx_os_mon.erl +++ b/apps/emqx/src/emqx_os_mon.erl @@ -22,15 +22,9 @@ -logger_header("[OS_MON]"). --export([start_link/1]). +-export([start_link/0]). --export([ get_cpu_check_interval/0 - , set_cpu_check_interval/1 - , get_cpu_high_watermark/0 - , set_cpu_high_watermark/1 - , get_cpu_low_watermark/0 - , set_cpu_low_watermark/1 - , get_mem_check_interval/0 +-export([ get_mem_check_interval/0 , set_mem_check_interval/1 , get_sysmem_high_watermark/0 , set_sysmem_high_watermark/1 @@ -51,31 +45,13 @@ -define(OS_MON, ?MODULE). -start_link(Opts) -> - gen_server:start_link({local, ?OS_MON}, ?MODULE, [Opts], []). +start_link() -> + gen_server:start_link({local, ?OS_MON}, ?MODULE, [], []). %%-------------------------------------------------------------------- %% API %%-------------------------------------------------------------------- -get_cpu_check_interval() -> - call(get_cpu_check_interval). - -set_cpu_check_interval(Seconds) -> - call({set_cpu_check_interval, Seconds}). - -get_cpu_high_watermark() -> - call(get_cpu_high_watermark). - -set_cpu_high_watermark(Float) -> - call({set_cpu_high_watermark, Float}). - -get_cpu_low_watermark() -> - call(get_cpu_low_watermark). - -set_cpu_low_watermark(Float) -> - call({set_cpu_low_watermark, Float}). - get_mem_check_interval() -> memsup:get_check_interval() div 1000. @@ -88,47 +64,25 @@ get_sysmem_high_watermark() -> memsup:get_sysmem_high_watermark(). set_sysmem_high_watermark(Float) -> - memsup:set_sysmem_high_watermark(Float / 100). + memsup:set_sysmem_high_watermark(Float). get_procmem_high_watermark() -> memsup:get_procmem_high_watermark(). set_procmem_high_watermark(Float) -> - memsup:set_procmem_high_watermark(Float / 100). - -call(Req) -> - gen_server:call(?OS_MON, Req, infinity). + memsup:set_procmem_high_watermark(Float). %%-------------------------------------------------------------------- %% gen_server callbacks %%-------------------------------------------------------------------- -init([Opts]) -> - set_mem_check_interval(proplists:get_value(mem_check_interval, Opts)), - set_sysmem_high_watermark(proplists:get_value(sysmem_high_watermark, Opts)), - set_procmem_high_watermark(proplists:get_value(procmem_high_watermark, Opts)), - {ok, ensure_check_timer(#{cpu_high_watermark => proplists:get_value(cpu_high_watermark, Opts), - cpu_low_watermark => proplists:get_value(cpu_low_watermark, Opts), - cpu_check_interval => proplists:get_value(cpu_check_interval, Opts), - timer => undefined})}. - -handle_call(get_cpu_check_interval, _From, State) -> - {reply, maps:get(cpu_check_interval, State, undefined), State}; - -handle_call({set_cpu_check_interval, Seconds}, _From, State) -> - {reply, ok, State#{cpu_check_interval := Seconds}}; - -handle_call(get_cpu_high_watermark, _From, State) -> - {reply, maps:get(cpu_high_watermark, State, undefined), State}; - -handle_call({set_cpu_high_watermark, Float}, _From, State) -> - {reply, ok, State#{cpu_high_watermark := Float}}; - -handle_call(get_cpu_low_watermark, _From, State) -> - {reply, maps:get(cpu_low_watermark, State, undefined), State}; - -handle_call({set_cpu_low_watermark, Float}, _From, State) -> - {reply, ok, State#{cpu_low_watermark := Float}}; +init([]) -> + Opts = emqx_config:get([sysmon, os]), + set_mem_check_interval(maps:get(mem_check_interval, Opts)), + set_sysmem_high_watermark(maps:get(sysmem_high_watermark, Opts)), + set_procmem_high_watermark(maps:get(procmem_high_watermark, Opts)), + _ = start_check_timer(), + {ok, #{}}. handle_call(Req, _From, State) -> ?LOG(error, "Unexpected call: ~p", [Req]), @@ -138,32 +92,30 @@ handle_cast(Msg, State) -> ?LOG(error, "Unexpected cast: ~p", [Msg]), {noreply, State}. -handle_info({timeout, Timer, check}, State = #{timer := Timer, - cpu_high_watermark := CPUHighWatermark, - cpu_low_watermark := CPULowWatermark}) -> - NState = - case emqx_vm:cpu_util() of %% TODO: should be improved? - 0 -> - State#{timer := undefined}; +handle_info({timeout, _Timer, check}, State) -> + CPUHighWatermark = emqx_config:get([sysmon, os, cpu_high_watermark]) * 100, + CPULowWatermark = emqx_config:get([sysmon, os, cpu_low_watermark]) * 100, + _ = case emqx_vm:cpu_util() of %% TODO: should be improved? + 0 -> ok; Busy when Busy >= CPUHighWatermark -> - emqx_alarm:activate(high_cpu_usage, #{usage => Busy, + emqx_alarm:activate(high_cpu_usage, #{usage => io_lib:format("~p%", [Busy]), high_watermark => CPUHighWatermark, low_watermark => CPULowWatermark}), - ensure_check_timer(State); + start_check_timer(); Busy when Busy =< CPULowWatermark -> emqx_alarm:deactivate(high_cpu_usage), - ensure_check_timer(State); + start_check_timer(); _Busy -> - ensure_check_timer(State) + start_check_timer() end, - {noreply, NState}; + {noreply, State}; handle_info(Info, State) -> ?LOG(error, "unexpected info: ~p", [Info]), {noreply, State}. -terminate(_Reason, #{timer := Timer}) -> - emqx_misc:cancel_timer(Timer). +terminate(_Reason, _State) -> + ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. @@ -172,8 +124,9 @@ code_change(_OldVsn, State, _Extra) -> %% Internal functions %%-------------------------------------------------------------------- -ensure_check_timer(State = #{cpu_check_interval := Interval}) -> +start_check_timer() -> + Interval = emqx_config:get([sysmon, os, cpu_check_interval]), case erlang:system_info(system_architecture) of - "x86_64-pc-linux-musl" -> State; - _ -> State#{timer := emqx_misc:start_timer(timer:seconds(Interval), check)} + "x86_64-pc-linux-musl" -> ok; + _ -> emqx_misc:start_timer(timer:seconds(Interval), check) end. diff --git a/apps/emqx/src/emqx_quic_connection.erl b/apps/emqx/src/emqx_quic_connection.erl index b83522c6e..cd41e74a7 100644 --- a/apps/emqx/src/emqx_quic_connection.erl +++ b/apps/emqx/src/emqx_quic_connection.erl @@ -21,6 +21,4 @@ ]). new_conn(Conn, {_L, COpts, _S}) when is_map(COpts) -> - new_conn(Conn, maps:to_list(COpts)); -new_conn(Conn, COpts) -> emqx_connection:start_link(emqx_quic_stream, Conn, COpts). diff --git a/apps/emqx/src/emqx_schema.erl b/apps/emqx/src/emqx_schema.erl index 2a58b34c3..ad0abb829 100644 --- a/apps/emqx/src/emqx_schema.erl +++ b/apps/emqx/src/emqx_schema.erl @@ -9,54 +9,59 @@ -include_lib("typerefl/include/types.hrl"). -type log_level() :: debug | info | notice | warning | error | critical | alert | emergency | all. --type flag() :: true | false. -type duration() :: integer(). -type duration_s() :: integer(). -type duration_ms() :: integer(). -type bytesize() :: integer(). +-type wordsize() :: bytesize(). -type percent() :: float(). -type file() :: string(). -type comma_separated_list() :: list(). -type comma_separated_atoms() :: [atom()]. -type bar_separated_list() :: list(). -type ip_port() :: tuple(). +-type cipher() :: map(). --typerefl_from_string({flag/0, emqx_schema, to_flag}). -typerefl_from_string({duration/0, emqx_schema, to_duration}). -typerefl_from_string({duration_s/0, emqx_schema, to_duration_s}). -typerefl_from_string({duration_ms/0, emqx_schema, to_duration_ms}). -typerefl_from_string({bytesize/0, emqx_schema, to_bytesize}). +-typerefl_from_string({wordsize/0, emqx_schema, to_wordsize}). -typerefl_from_string({percent/0, emqx_schema, to_percent}). -typerefl_from_string({comma_separated_list/0, emqx_schema, to_comma_separated_list}). -typerefl_from_string({bar_separated_list/0, emqx_schema, to_bar_separated_list}). -typerefl_from_string({ip_port/0, emqx_schema, to_ip_port}). +-typerefl_from_string({cipher/0, emqx_schema, to_erl_cipher_suite}). -typerefl_from_string({comma_separated_atoms/0, emqx_schema, to_comma_separated_atoms}). % workaround: prevent being recognized as unused functions --export([to_duration/1, to_duration_s/1, to_duration_ms/1, to_bytesize/1, - to_flag/1, to_percent/1, to_comma_separated_list/1, +-export([to_duration/1, to_duration_s/1, to_duration_ms/1, + to_bytesize/1, to_wordsize/1, + to_percent/1, to_comma_separated_list/1, to_bar_separated_list/1, to_ip_port/1, + to_erl_cipher_suite/1, to_comma_separated_atoms/1]). -behaviour(hocon_schema). --reflect_type([ log_level/0, flag/0, duration/0, duration_s/0, duration_ms/0, - bytesize/0, percent/0, file/0, +-reflect_type([ log_level/0, duration/0, duration_s/0, duration_ms/0, + bytesize/0, wordsize/0, percent/0, file/0, comma_separated_list/0, bar_separated_list/0, ip_port/0, + cipher/0, comma_separated_atoms/0]). -export([structs/0, fields/1, translations/0, translation/1]). -export([t/1, t/3, t/4, ref/1]). -export([conf_get/2, conf_get/3, keys/2, filter/1]). --export([ssl/2, tr_ssl/2, tr_password_hash/2]). +-export([ssl/1]). %% will be used by emqx_ct_helper to find the dependent apps -export([includes/0]). structs() -> ["cluster", "node", "rpc", "log", "lager", - "acl", "mqtt", "zone", "listener", "broker", - "plugins", "sysmon", "os_mon", "vm_mon", "alarm"] - ++ includes(). + "zones", "listeners", "broker", + "plugins", "sysmon", "alarm"] + ++ ?MODULE:includes(). -ifdef(TEST). includes() ->[]. @@ -77,9 +82,10 @@ includes() -> fields("cluster") -> [ {"name", t(atom(), "ekka.cluster_name", emqxcl)} - , {"discovery", t(atom(), undefined, manual)} + , {"discovery_strategy", t(union([manual, static, mcast, dns, etcd, k8s]), + undefined, manual)} , {"autoclean", t(duration(), "ekka.cluster_autoclean", undefined)} - , {"autoheal", t(flag(), "ekka.cluster_autoheal", false)} + , {"autoheal", t(boolean(), "ekka.cluster_autoheal", false)} , {"static", ref("static")} , {"mcast", ref("mcast")} , {"proto_dist", t(union([inet_tcp, inet6_tcp, inet_tls]), "ekka.proto_dist", inet_tcp)} @@ -91,21 +97,22 @@ fields("cluster") -> ]; fields("static") -> - [ {"seeds", t(comma_separated_list())}]; + [ {"seeds", t(hoconsc:array(string()))}]; fields("mcast") -> [ {"addr", t(string(), undefined, "239.192.0.1")} - , {"ports", t(comma_separated_list(), undefined, "4369")} + , {"ports", t(hoconsc:array(integer()), undefined, [4369, 4370])} , {"iface", t(string(), undefined, "0.0.0.0")} , {"ttl", t(integer(), undefined, 255)} - , {"loop", t(flag(), undefined, true)} + , {"loop", t(boolean(), undefined, true)} , {"sndbuf", t(bytesize(), undefined, "16KB")} , {"recbuf", t(bytesize(), undefined, "16KB")} , {"buffer", t(bytesize(), undefined, "32KB")} ]; fields("dns") -> - [ {"app", t(string())}]; + [ {"name", t(string())} + , {"app", t(string())}]; fields("etcd") -> [ {"server", t(comma_separated_list())} @@ -115,7 +122,7 @@ fields("etcd") -> ]; fields("etcd_ssl") -> - ssl(undefined, #{}); + ssl(#{}); fields("k8s") -> [ {"apiserver", t(string())} @@ -133,24 +140,17 @@ fields("rlog") -> fields("node") -> [ {"name", t(string(), "vm_args.-name", "emqx@127.0.0.1", "EMQX_NODE_NAME")} - , {"ssl_dist_optfile", t(string(), "vm_args.-ssl_dist_optfile", undefined)} , {"cookie", hoconsc:t(string(), #{mapping => "vm_args.-setcookie", default => "emqxsecretcookie", sensitive => true, override_env => "EMQX_NODE_COOKIE" })} , {"data_dir", t(string(), "emqx.data_dir", undefined)} - , {"etc_dir", t(string(), "emqx.etc_dir", undefined)} - , {"heartbeat", t(flag(), undefined, false)} - , {"async_threads", t(range(1, 1024), "vm_args.+A", undefined)} - , {"process_limit", t(integer(), "vm_args.+P", undefined)} - , {"max_ports", t(range(1024, 134217727), "vm_args.+Q", undefined, "EMQX_MAX_PORTS")} - , {"dist_buffer_size", fun node__dist_buffer_size/1} + , {"config_files", t(list(string()), "emqx.config_files", + [ filename:join([os:getenv("RUNNER_ETC_DIR"), "emqx.conf"]) + ])} , {"global_gc_interval", t(duration_s(), "emqx.global_gc_interval", undefined)} - , {"fullsweep_after", t(non_neg_integer(), - "vm_args.-env ERL_FULLSWEEP_AFTER", 1000)} - , {"max_ets_tables", t(integer(), "vm_args.+e", 256000)} - , {"crash_dump", t(file(), "vm_args.-env ERL_CRASH_DUMP", undefined)} + , {"crash_dump_dir", t(file(), "vm_args.-env ERL_CRASH_DUMP", undefined)} , {"dist_net_ticktime", t(integer(), "vm_args.-kernel net_ticktime", undefined)} , {"dist_listen_min", t(integer(), "kernel.inet_dist_listen_min", undefined)} , {"dist_listen_max", t(integer(), "kernel.inet_dist_listen_max", undefined)} @@ -176,258 +176,258 @@ fields("rpc") -> ]; fields("log") -> - [ {"to", t(union([file, console, both]), undefined, file)} - , {"level", t(log_level(), undefined, warning)} + [ {"primary_level", t(log_level(), undefined, warning)} + , {"console_handler", ref("console_handler")} + , {"file_handlers", ref("file_handlers")} , {"time_offset", t(string(), undefined, "system")} - , {"primary_log_level", t(log_level(), undefined, warning)} - , {"dir", t(string(), undefined, "log")} - , {"file", t(file(), undefined, "emqx.log")} - , {"chars_limit", t(integer(), undefined, -1)} + , {"chars_limit", maybe_infinity(integer())} , {"supervisor_reports", t(union([error, progress]), undefined, error)} , {"max_depth", t(union([infinity, integer()]), "kernel.error_logger_format_depth", 80)} , {"formatter", t(union([text, json]), undefined, text)} , {"single_line", t(boolean(), undefined, true)} - , {"rotation", ref("rotation")} - , {"size", t(union(bytesize(), infinity), undefined, infinity)} , {"sync_mode_qlen", t(integer(), undefined, 100)} , {"drop_mode_qlen", t(integer(), undefined, 3000)} , {"flush_qlen", t(integer(), undefined, 8000)} - , {"overload_kill", t(flag(), undefined, true)} - , {"overload_kill_mem_size", t(bytesize(), undefined, "30MB")} - , {"overload_kill_qlen", t(integer(), undefined, 20000)} - , {"overload_kill_restart_after", t(union(duration(), infinity), undefined, "5s")} - , {"burst_limit", t(comma_separated_list(), undefined, "disabled")} + , {"overload_kill", ref("log_overload_kill")} + , {"burst_limit", ref("log_burst_limit")} , {"error_logger", t(atom(), "kernel.error_logger", silent)} - , {"debug", ref("additional_log_file")} - , {"info", ref("additional_log_file")} - , {"notice", ref("additional_log_file")} - , {"warning", ref("additional_log_file")} - , {"error", ref("additional_log_file")} - , {"critical", ref("additional_log_file")} - , {"alert", ref("additional_log_file")} - , {"emergency", ref("additional_log_file")} ]; -fields("additional_log_file") -> - [ {"file", t(string())}]; +fields("console_handler") -> + [ {"enable", t(boolean(), undefined, false)} + , {"level", t(log_level(), undefined, warning)} + ]; -fields("rotation") -> - [ {"enable", t(flag(), undefined, true)} - , {"size", t(bytesize(), undefined, "10MB")} - , {"count", t(integer(), undefined, 5)} +fields("file_handlers") -> + [ {"$name", ref("log_file_handler")} + ]; + +fields("log_file_handler") -> + [ {"level", t(log_level(), undefined, warning)} + , {"file", t(file(), undefined, undefined)} + , {"rotation", ref("log_rotation")} + , {"max_size", maybe_infinity(bytesize(), "10MB")} + ]; + +fields("log_rotation") -> + [ {"enable", t(boolean(), undefined, true)} + , {"count", t(range(1, 2048), undefined, 10)} + ]; + +fields("log_overload_kill") -> + [ {"enable", t(boolean(), undefined, true)} + , {"mem_size", t(bytesize(), undefined, "30MB")} + , {"qlen", t(integer(), undefined, 20000)} + , {"restart_after", t(union(duration(), infinity), undefined, "5s")} + ]; + +fields("log_burst_limit") -> + [ {"enable", t(boolean(), undefined, true)} + , {"max_count", t(integer(), undefined, 10000)} + , {"window_time", t(duration(), undefined, "1s")} ]; fields("lager") -> [ {"handlers", t(string(), "lager.handlers", "")} - , {"crash_log", t(flag(), "lager.crash_log", false)} + , {"crash_log", t(boolean(), "lager.crash_log", false)} + ]; + +fields("stats") -> + [ {"enable", t(boolean(), undefined, true)} + ]; + +fields("auth") -> + [ {"enable", t(boolean(), undefined, false)} ]; fields("acl") -> - [ {"allow_anonymous", t(boolean(), "emqx.allow_anonymous", false)} - , {"acl_nomatch", t(union(allow, deny), "emqx.acl_nomatch", deny)} - , {"acl_file", t(string(), "emqx.acl_file", undefined)} - , {"enable_acl_cache", t(flag(), "emqx.enable_acl_cache", true)} - , {"acl_cache_ttl", t(duration(), "emqx.acl_cache_ttl", "1m")} - , {"acl_cache_max_size", t(range(1, inf), "emqx.acl_cache_max_size", 32)} - , {"acl_deny_action", t(union(ignore, disconnect), "emqx.acl_deny_action", ignore)} - , {"flapping_detect_policy", t(comma_separated_list(), undefined, "30,1m,5m")} + [ {"enable", t(boolean(), undefined, false)} + , {"cache", ref("acl_cache")} + , {"deny_action", t(union(ignore, disconnect), undefined, ignore)} + ]; + +fields("acl_cache") -> + [ {"enable", t(boolean(), undefined, true)} + , {"max_size", maybe_infinity(range(1, 1048576), 32)} + , {"ttl", t(duration(), undefined, "1m")} ]; fields("mqtt") -> - [ {"max_packet_size", t(bytesize(), "emqx.max_packet_size", "1MB", "EMQX_MAX_PACKET_SIZE")} - , {"max_clientid_len", t(integer(), "emqx.max_clientid_len", 65535)} - , {"max_topic_levels", t(integer(), "emqx.max_topic_levels", 0)} - , {"max_qos_allowed", t(range(0, 2), "emqx.max_qos_allowed", 2)} - , {"max_topic_alias", t(integer(), "emqx.max_topic_alias", 65535)} - , {"retain_available", t(boolean(), "emqx.retain_available", true)} - , {"wildcard_subscription", t(boolean(), "emqx.wildcard_subscription", true)} - , {"shared_subscription", t(boolean(), "emqx.shared_subscription", true)} - , {"ignore_loop_deliver", t(boolean(), "emqx.ignore_loop_deliver", true)} - , {"strict_mode", t(boolean(), "emqx.strict_mode", false)} - , {"response_information", t(string(), "emqx.response_information", undefined)} + [ {"mountpoint", t(binary(), undefined, <<>>)} + , {"idle_timeout", maybe_infinity(duration(), "15s")} + , {"max_packet_size", t(bytesize(), undefined, "1MB")} + , {"max_clientid_len", t(integer(), undefined, 65535)} + , {"max_topic_levels", t(integer(), undefined, 65535)} + , {"max_qos_allowed", t(range(0, 2), undefined, 2)} + , {"max_topic_alias", t(integer(), undefined, 65535)} + , {"retain_available", t(boolean(), undefined, true)} + , {"wildcard_subscription", t(boolean(), undefined, true)} + , {"shared_subscription", t(boolean(), undefined, true)} + , {"ignore_loop_deliver", t(boolean())} + , {"strict_mode", t(boolean(), undefined, false)} + , {"response_information", t(string(), undefined, "")} + , {"server_keepalive", maybe_disabled(integer())} + , {"keepalive_backoff", t(float(), undefined, 0.75)} + , {"max_subscriptions", maybe_infinity(integer())} + , {"upgrade_qos", t(boolean(), undefined, false)} + , {"max_inflight", t(range(1, 65535))} + , {"retry_interval", t(duration_s(), undefined, "30s")} + , {"max_awaiting_rel", maybe_infinity(duration())} + , {"await_rel_timeout", t(duration_s(), undefined, "300s")} + , {"session_expiry_interval", t(duration_s(), undefined, "2h")} + , {"max_mqueue_len", maybe_infinity(integer(), 1000)} + , {"mqueue_priorities", maybe_disabled(map())} + , {"mqueue_default_priority", t(union(highest, lowest), undefined, lowest)} + , {"mqueue_store_qos0", t(boolean(), undefined, true)} + , {"use_username_as_clientid", t(boolean(), undefined, false)} + , {"peer_cert_as_username", maybe_disabled(union([cn, dn, crt, pem, md5]))} + , {"peer_cert_as_clientid", maybe_disabled(union([cn, dn, crt, pem, md5]))} ]; -fields("zone") -> +fields("zones") -> [ {"$name", ref("zone_settings")}]; fields("zone_settings") -> - [ {"idle_timeout", t(duration(), undefined, "15s")} - , {"allow_anonymous", t(boolean())} - , {"acl_nomatch", t(union(allow, deny))} - , {"enable_acl", t(flag(), undefined, false)} - , {"acl_deny_action", t(union(ignore, disconnect), undefined, ignore)} - , {"enable_ban", t(flag(), undefined, false)} - , {"enable_stats", t(flag(), undefined, false)} - , {"max_packet_size", t(bytesize())} - , {"max_clientid_len", t(integer())} - , {"max_topic_levels", t(integer())} - , {"max_qos_allowed", t(range(0, 2))} - , {"max_topic_alias", t(integer())} - , {"retain_available", t(boolean())} - , {"wildcard_subscription", t(boolean())} - , {"shared_subscription", t(boolean())} - , {"server_keepalive", t(integer())} - , {"keepalive_backoff", t(float(), undefined, 0.75)} - , {"max_subscriptions", t(integer(), undefined, 0)} - , {"upgrade_qos", t(flag(), undefined, false)} - , {"max_inflight", t(range(0, 65535))} - , {"retry_interval", t(duration_s(), undefined, "30s")} - , {"max_awaiting_rel", t(duration(), undefined, 0)} - , {"await_rel_timeout", t(duration_s(), undefined, "300s")} - , {"ignore_loop_deliver", t(boolean())} - , {"session_expiry_interval", t(duration_s(), undefined, "2h")} - , {"max_mqueue_len", t(integer(), undefined, 1000)} - , {"mqueue_priorities", t(comma_separated_list(), undefined, "none")} - , {"mqueue_default_priority", t(union(highest, lowest), undefined, lowest)} - , {"mqueue_store_qos0", t(boolean(), undefined, true)} - , {"enable_flapping_detect", t(flag(), undefined, false)} - , {"rate_limit", ref("rate_limit")} + [ {"mqtt", ref("mqtt")} + , {"acl", ref("acl")} + , {"auth", ref("auth")} + , {"stats", ref("stats")} + , {"flapping_detect", ref("flapping_detect")} + , {"force_shutdown", ref("force_shutdown")} , {"conn_congestion", ref("conn_congestion")} - , {"quota", ref("quota")} - , {"force_gc_policy", t(bar_separated_list())} - , {"force_shutdown_policy", t(bar_separated_list(), undefined, "default")} - , {"mountpoint", t(string())} - , {"use_username_as_clientid", t(boolean(), undefined, false)} - , {"strict_mode", t(boolean(), undefined, false)} - , {"response_information", t(string())} - , {"bypass_auth_plugins", t(boolean(), undefined, false)} + , {"force_gc", ref("force_gc")} + , {"overall_max_connections", maybe_infinity(integer())} + , {"listeners", t("listeners")} ]; fields("rate_limit") -> - [ {"conn_messages_in", t(comma_separated_list())} - , {"conn_bytes_in", t(comma_separated_list())} + [ {"max_conn_rate", maybe_infinity(integer(), 1000)} + , {"conn_messages_in", maybe_infinity(comma_separated_list())} + , {"conn_bytes_in", maybe_infinity(comma_separated_list())} + , {"quota", ref("rate_limit_quota")} + ]; + +fields("rate_limit_quota") -> + [ {"conn_messages_routing", maybe_infinity(comma_separated_list())} + , {"overall_messages_routing", maybe_infinity(comma_separated_list())} + ]; + +fields("flapping_detect") -> + [ {"enable", t(boolean(), undefined, false)} + , {"max_count", t(integer(), undefined, 15)} + , {"window_time", t(duration(), undefined, "1m")} + , {"ban_time", t(duration(), undefined, "5m")} + ]; + +fields("force_shutdown") -> + [ {"enable", t(boolean(), undefined, true)} + , {"max_message_queue_len", t(range(0, inf), undefined, 1000)} + , {"max_heap_size", t(wordsize(), undefined, "32MB", undefined, + fun(Siz) -> + MaxSiz = case erlang:system_info(wordsize) of + 8 -> % arch_64 + (1 bsl 59) - 1; + 4 -> % arch_32 + (1 bsl 27) - 1 + end, + case Siz > MaxSiz of + true -> + error(io_lib:format("force_shutdown_policy: heap-size ~s is too large", [Siz])); + false -> + ok + end + end)} ]; fields("conn_congestion") -> - [ {"alarm", t(flag(), undefined, false)} + [ {"enable_alarm", t(boolean(), undefined, false)} , {"min_alarm_sustain_duration", t(duration(), undefined, "1m")} ]; -fields("quota") -> - [ {"conn_messages_routing", t(comma_separated_list())} - , {"overall_messages_routing", t(comma_separated_list())} +fields("force_gc") -> + [ {"enable", t(boolean(), undefined, true)} + , {"count", t(range(0, inf), undefined, 16000)} + , {"bytes", t(bytesize(), undefined, "16MB")} ]; -fields("listener") -> - [ {"tcp", ref("tcp_listener")} - , {"ssl", ref("ssl_listener")} - , {"ws", ref("ws_listener")} - , {"wss", ref("wss_listener")} - , {"quic", ref("quic_listener")} +fields("listeners") -> + [ {"$name", hoconsc:union( + [ hoconsc:ref("mqtt_tcp_listener") + , hoconsc:ref("mqtt_ws_listener") + , hoconsc:ref("mqtt_quic_listener") + ])} ]; -fields("tcp_listener") -> - [ {"$name", ref("tcp_listener_settings")}]; +fields("mqtt_tcp_listener") -> + [ {"type", t(tcp)} + , {"tcp", ref("tcp_opts")} + , {"ssl", ref("ssl_opts")} + ] ++ mqtt_listener(); -fields("ssl_listener") -> - [ {"$name", ref("ssl_listener_settings")}]; +fields("mqtt_ws_listener") -> + [ {"type", t(ws)} + , {"tcp", ref("tcp_opts")} + , {"ssl", ref("ssl_opts")} + , {"websocket", ref("ws_opts")} + ] ++ mqtt_listener(); -fields("ws_listener") -> - [ {"$name", ref("ws_listener_settings")}]; +fields("mqtt_quic_listener") -> + [ {"type", t(quic)} + , {"certfile", t(string(), undefined, undefined)} + , {"keyfile", t(string(), undefined, undefined)} + , {"ciphers", t(comma_separated_list(), undefined, "TLS_AES_256_GCM_SHA384," + "TLS_AES_128_GCM_SHA256,TLS_CHACHA20_POLY1305_SHA256")} + , {"idle_timeout", t(duration(), undefined, 60000)} + ] ++ base_listener(); -fields("wss_listener") -> - [ {"$name", ref("wss_listener_settings")}]; +fields("ws_opts") -> + [ {"mqtt_path", t(string(), undefined, "/mqtt")} + , {"mqtt_piggyback", t(union(single, multiple), undefined, multiple)} + , {"compress", t(boolean(), undefined, false)} + , {"idle_timeout", t(duration(), undefined, "15s")} + , {"max_frame_size", maybe_infinity(integer())} + , {"fail_if_no_subprotocol", t(boolean(), undefined, true)} + , {"supported_subprotocols", t(comma_separated_list(), undefined, + "mqtt, mqtt-v3, mqtt-v3.1.1, mqtt-v5")} + , {"check_origin_enable", t(boolean(), undefined, false)} + , {"allow_origin_absence", t(boolean(), undefined, true)} + , {"check_origins", t(hoconsc:array(binary()), undefined, [])} + , {"proxy_address_header", t(string(), undefined, "x-forwarded-for")} + , {"proxy_port_header", t(string(), undefined, "x-forwarded-port")} + , {"deflate_opts", ref("deflate_opts")} + ]; -fields("quic_listener") -> - [ {"$name", ref("quic_listener_settings")}]; - -fields("listener_settings") -> - [ {"endpoint", t(union([ip_port(), integer(), ""]))} - , {"acceptors", t(integer(), undefined, 8)} - , {"max_connections", t(integer(), undefined, 1024)} - , {"max_conn_rate", t(integer())} - , {"active_n", t(integer(), undefined, 100)} - , {"zone", t(string())} - , {"rate_limit", t(comma_separated_list())} - , {"access", ref("access")} - , {"proxy_protocol", t(flag())} - , {"proxy_protocol_timeout", t(duration())} +fields("tcp_opts") -> + [ {"active_n", t(integer(), undefined, 100)} , {"backlog", t(integer(), undefined, 1024)} , {"send_timeout", t(duration(), undefined, "15s")} - , {"send_timeout_close", t(flag(), undefined, true)} + , {"send_timeout_close", t(boolean(), undefined, true)} , {"recbuf", t(bytesize())} , {"sndbuf", t(bytesize())} , {"buffer", t(bytesize())} + , {"tune_buffer", t(boolean())} , {"high_watermark", t(bytesize(), undefined, "1MB")} - , {"tune_buffer", t(flag())} , {"nodelay", t(boolean())} , {"reuseaddr", t(boolean())} ]; -fields("tcp_listener_settings") -> - [ {"peer_cert_as_username", t(cn)} - , {"peer_cert_as_clientid", t(cn)} - ] ++ fields("listener_settings"); - -fields("ssl_listener_settings") -> - [ {"peer_cert_as_username", t(union([cn, dn, crt, pem, md5]))} - , {"peer_cert_as_clientid", t(union([cn, dn, crt, pem, md5]))} - ] ++ - ssl(undefined, #{handshake_timeout => "15s" - , depth => 10 - , reuse_sessions => true}) ++ fields("listener_settings"); - -fields("ws_listener_settings") -> - [ {"mqtt_path", t(string(), undefined, "/mqtt")} - , {"fail_if_no_subprotocol", t(boolean(), undefined, true)} - , {"supported_subprotocols", t(string(), undefined, "mqtt, mqtt-v3, mqtt-v3.1.1, mqtt-v5")} - , {"proxy_address_header", t(string(), undefined, "x-forwarded-for")} - , {"proxy_port_header", t(string(), undefined, "x-forwarded-port")} - , {"compress", t(boolean())} - , {"deflate_opts", ref("deflate_opts")} - , {"idle_timeout", t(duration())} - , {"max_frame_size", t(integer())} - , {"mqtt_piggyback", t(union(single, multiple), undefined, multiple)} - , {"check_origin_enable", t(boolean(), undefined, false)} - , {"allow_origin_absence", t(boolean(), undefined, true)} - , {"check_origins", t(comma_separated_list())} - % @fixme - ] ++ lists:keydelete("high_watermark", 1, fields("tcp_listener_settings")); - -fields("wss_listener_settings") -> - % @fixme - Ssl = ssl(undefined, #{depth => 10 - , reuse_sessions => true}) ++ fields("listener_settings"), - Settings = lists:ukeymerge(1, Ssl, fields("ws_listener_settings")), - lists:keydelete("high_watermark", 1, Settings); - -fields("quic_listener_settings") -> - Unsupported = [ "access" - , "proxy_protocol" - , "proxy_protocol_timeout" - , "backlog" - , "send_timeout" - , "send_timeout_close" - , "recvbuf" - , "sndbuf" - , "buffer" - , "high_watermark" - , "tune_buffer" - , "nodelay" - , "reuseaddr" - ], - lists:foldl(fun(K, Acc) -> - lists:keydelete(K, 1, Acc) - end, - [ {"certfile", t(string(), undefined, undefined)} - , {"keyfile", t(string(), undefined, undefined)} - , {"ciphers", t(comma_separated_list(), undefined, "TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256,TLS_CHACHA20_POLY1305_SHA256")} - , {"idle_timeout", t(duration(), undefined, 60000)} - | fields("listener_settings")], - Unsupported); - -fields("access") -> - [ {"$id", t(string(), undefined, undefined)}]; +fields("ssl_opts") -> + ssl(#{handshake_timeout => "15s" + , depth => 10 + , reuse_sessions => true + , versions => default_tls_vsns() + , ciphers => default_ciphers() + }); fields("deflate_opts") -> [ {"level", t(union([none, default, best_compression, best_speed]))} - , {"mem_level", t(range(1, 9))} + , {"mem_level", t(range(1, 9), undefined, 8)} , {"strategy", t(union([default, filtered, huffman_only, rle]))} , {"server_context_takeover", t(union(takeover, no_takeover))} , {"client_context_takeover", t(union(takeover, no_takeover))} - , {"server_max_window_bits", t(integer())} - , {"client_max_window_bits", t(integer())} + , {"server_max_window_bits", t(range(8, 15), undefined, 15)} + , {"client_max_window_bits", t(range(8, 15), undefined, 15)} ]; @@ -445,7 +445,6 @@ fields("subscription_settings") -> , {"rh", t(range(0, 2), undefined, 0)} ]; - fields("rewrite") -> [ {"rule", ref("rule")} , {"pub_rule", ref("rule")} @@ -460,15 +459,13 @@ fields("plugins") -> ]; fields("broker") -> - [ {"sys_interval", t(duration(), "emqx.broker_sys_interval", "1m")} - , {"sys_heartbeat", t(duration(), "emqx.broker_sys_heartbeat", "30s")} - , {"enable_session_registry", t(flag(), "emqx.enable_session_registry", true)} - , {"session_locking_strategy", t(union([local, leader, quorum, all]), - "emqx.session_locking_strategy", quorum)} - , {"shared_subscription_strategy", t(union(random, round_robin), - "emqx.shared_subscription_strategy", round_robin)} - , {"shared_dispatch_ack_enabled", t(boolean(), "emqx.shared_dispatch_ack_enabled", false)} - , {"route_batch_clean", t(flag(), "emqx.route_batch_clean", true)} + [ {"sys_msg_interval", maybe_disabled(duration(), "1m")} + , {"sys_heartbeat_interval", maybe_disabled(duration(), "30s")} + , {"enable_session_registry", t(boolean(), undefined, true)} + , {"session_locking_strategy", t(union([local, leader, quorum, all]), undefined, quorum)} + , {"shared_subscription_strategy", t(union(random, round_robin), undefined, round_robin)} + , {"shared_dispatch_ack_enabled", t(boolean(), undefined, false)} + , {"route_batch_clean", t(boolean(), undefined, true)} , {"perf", ref("perf")} ]; @@ -478,118 +475,70 @@ fields("perf") -> ]; fields("sysmon") -> - [ {"long_gc", t(duration(), undefined, 0)} - , {"long_schedule", t(duration(), undefined, 240)} - , {"large_heap", t(bytesize(), undefined, "8MB")} + [ {"vm", ref("sysmon_vm")} + , {"os", ref("sysmon_os")} + ]; + +fields("sysmon_vm") -> + [ {"process_check_interval", t(duration(), undefined, "30s")} + , {"process_high_watermark", t(percent(), undefined, "80%")} + , {"process_low_watermark", t(percent(), undefined, "60%")} + , {"long_gc", maybe_disabled(duration())} + , {"long_schedule", maybe_disabled(duration(), 240)} + , {"large_heap", maybe_disabled(bytesize(), "8MB")} , {"busy_dist_port", t(boolean(), undefined, true)} , {"busy_port", t(boolean(), undefined, false)} ]; -fields("os_mon") -> +fields("sysmon_os") -> [ {"cpu_check_interval", t(duration_s(), undefined, 60)} , {"cpu_high_watermark", t(percent(), undefined, "80%")} , {"cpu_low_watermark", t(percent(), undefined, "60%")} - , {"mem_check_interval", t(duration_s(), undefined, 60)} + , {"mem_check_interval", maybe_disabled(duration_s(), 60)} , {"sysmem_high_watermark", t(percent(), undefined, "70%")} , {"procmem_high_watermark", t(percent(), undefined, "5%")} ]; -fields("vm_mon") -> - [ {"check_interval", t(duration_s(), undefined, 30)} - , {"process_high_watermark", t(percent(), undefined, "80%")} - , {"process_low_watermark", t(percent(), undefined, "60%")} - ]; - fields("alarm") -> - [ {"actions", t(comma_separated_list(), undefined, "log,publish")} + [ {"actions", t(hoconsc:array(atom()), undefined, [log, publish])} , {"size_limit", t(integer(), undefined, 1000)} , {"validity_period", t(duration_s(), undefined, "24h")} ]; fields(ExtraField) -> - Mod = list_to_atom(ExtraField++"_schema"), + Mod = to_atom(ExtraField++"_schema"), Mod:fields(ExtraField). -translations() -> ["ekka", "vm_args", "gen_rpc", "kernel", "emqx"]. +mqtt_listener() -> + base_listener() ++ + [ {"access_rules", t(hoconsc:array(string()))} + , {"proxy_protocol", t(boolean(), undefined, false)} + , {"proxy_protocol_timeout", t(duration())} + ]. + +base_listener() -> + [ {"bind", t(union(ip_port(), integer()))} + , {"acceptors", t(integer(), undefined, 16)} + , {"max_connections", maybe_infinity(integer(), infinity)} + , {"rate_limit", ref("rate_limit")} + ]. + +translations() -> ["ekka", "kernel"]. translation("ekka") -> [ {"cluster_discovery", fun tr_cluster__discovery/1}]; -translation("vm_args") -> - [ {"+zdbbl", fun tr_zdbbl/1} - , {"-heart", fun tr_heart/1}]; - -translation("gen_rpc") -> - [ {"tcp_client_num", fun tr_tcp_client_num/1} - , {"tcp_client_port", fun tr_tcp_client_port/1}]; - translation("kernel") -> [ {"logger_level", fun tr_logger_level/1} - , {"logger", fun tr_logger/1}]; - -translation("emqx") -> - [ {"flapping_detect_policy", fun tr_flapping_detect_policy/1} - , {"zones", fun tr_zones/1} - , {"listeners", fun tr_listeners/1} - , {"sysmon", fun tr_sysmon/1} - , {"os_mon", fun tr_os_mon/1} - , {"vm_mon", fun tr_vm_mon/1} - , {"alarm", fun tr_alarm/1} - ]. + , {"logger", fun tr_logger/1}]. tr_cluster__discovery(Conf) -> - Strategy = conf_get("cluster.discovery", Conf), + Strategy = conf_get("cluster.discovery_strategy", Conf), {Strategy, filter(options(Strategy, Conf))}. -tr_heart(Conf) -> - case conf_get("node.heartbeat", Conf) of - true -> ""; - "on" -> ""; - _ -> undefined - end. - -%% @doc http://www.erlang.org/doc/man/erl.html#%2bzdbbl -node__dist_buffer_size(type) -> bytesize(); -node__dist_buffer_size(validator) -> - fun(ZDBBL) -> - case ZDBBL >= 1024 andalso ZDBBL =< 2147482624 of - true -> - ok; - false -> - {error, "must be between 1KB and 2097151KB"} - end - end; -node__dist_buffer_size(_) -> undefined. - -tr_zdbbl(Conf) -> - case conf_get("node.dist_buffer_size", Conf) of - undefined -> undefined; - X when is_integer(X) -> ceiling(X / 1024); %% Bytes to Kilobytes; - _ -> undefined - end. - -%% Force client to use server listening port, because we do no provide -%% per-node listening port manual mapping from configs. -%% i.e. all nodes in the cluster should agree to the same -%% listening port number. -tr_tcp_client_num(Conf) -> - case conf_get("rpc.tcp_client_num", Conf) of - 0 -> max(1, erlang:system_info(schedulers) div 2); - V -> V - end. - -tr_tcp_client_port(Conf) -> - conf_get("rpc.tcp_server_port", Conf). - -tr_logger_level(Conf) -> conf_get("log.level", Conf). +tr_logger_level(Conf) -> conf_get("log.primary_level", Conf). tr_logger(Conf) -> - LogTo = conf_get("log.to", Conf), - LogLevel = conf_get("log.level", Conf), - LogType = case conf_get("log.rotation.enable", Conf) of - true -> wrap; - _ -> halt - end, CharsLimit = case conf_get("log.chars_limit", Conf) of -1 -> unlimited; V -> V @@ -597,294 +546,56 @@ tr_logger(Conf) -> SingleLine = conf_get("log.single_line", Conf), FmtName = conf_get("log.formatter", Conf), Formatter = formatter(FmtName, CharsLimit, SingleLine), - BurstLimit = conf_get("log.burst_limit", Conf), - {BustLimitOn, {MaxBurstCount, TimeWindow}} = burst_limit(BurstLimit), - FileConf = fun (Filename) -> - BasicConf = - #{type => LogType, - file => filename:join(conf_get("log.dir", Conf), Filename), - max_no_files => conf_get("log.rotation.count", Conf), - sync_mode_qlen => conf_get("log.sync_mode_qlen", Conf), - drop_mode_qlen => conf_get("log.drop_mode_qlen", Conf), - flush_qlen => conf_get("log.flush_qlen", Conf), - overload_kill_enable => conf_get("log.overload_kill", Conf), - overload_kill_qlen => conf_get("log.overload_kill_qlen", Conf), - overload_kill_mem_size => conf_get("log.overload_kill_mem_size", Conf), - overload_kill_restart_after => conf_get("log.overload_kill_restart_after", Conf), - burst_limit_enable => BustLimitOn, - burst_limit_max_count => MaxBurstCount, - burst_limit_window_time => TimeWindow - }, - MaxNoBytes = case LogType of - wrap -> conf_get("log.rotation.size", Conf); - halt -> conf_get("log.size", Conf) - end, - BasicConf#{max_no_bytes => MaxNoBytes} end, - + BasicConf = #{ + sync_mode_qlen => conf_get("log.sync_mode_qlen", Conf), + drop_mode_qlen => conf_get("log.drop_mode_qlen", Conf), + flush_qlen => conf_get("log.flush_qlen", Conf), + overload_kill_enable => conf_get("log.overload_kill.enable", Conf), + overload_kill_qlen => conf_get("log.overload_kill.qlen", Conf), + overload_kill_mem_size => conf_get("log.overload_kill.mem_size", Conf), + overload_kill_restart_after => conf_get("log.overload_kill.restart_after", Conf), + burst_limit_enable => conf_get("log.burst_limit.enable", Conf), + burst_limit_max_count => conf_get("log.burst_limit.max_count", Conf), + burst_limit_window_time => conf_get("log.burst_limit.window_time", Conf) + }, Filters = case conf_get("log.supervisor_reports", Conf) of error -> [{drop_progress_reports, {fun logger_filters:progress/2, stop}}]; progress -> [] end, - %% For the default logger that outputs to console - DefaultHandler = - if LogTo =:= console orelse LogTo =:= both -> - [{handler, console, logger_std_h, - #{level => LogLevel, - config => #{type => standard_io}, - formatter => Formatter, - filters => Filters - } - }]; + ConsoleHandler = + case conf_get("log.console_handler.enable", Conf) of true -> - [{handler, default, undefined}] - end, - - %% For the file logger - FileHandler = - if LogTo =:= file orelse LogTo =:= both -> - [{handler, file, logger_disk_log_h, - #{level => LogLevel, - config => FileConf(conf_get("log.file", Conf)), + [{handler, console, logger_std_h, #{ + level => conf_get("log.console_handler.level", Conf), + config => BasicConf#{type => standard_io}, formatter => Formatter, - filesync_repeat_interval => no_repeat, filters => Filters }}]; - true -> [] + false -> [] end, - - AdditionalLogFiles = additional_log_files(Conf), - AdditionalHandlers = - [{handler, list_to_atom("file_for_"++Level), logger_disk_log_h, - #{level => list_to_atom(Level), - config => FileConf(Filename), + %% For the file logger + FileHandlers = + [{handler, binary_to_atom(HandlerName, latin1), logger_disk_log_h, #{ + level => conf_get("level", SubConf), + config => BasicConf#{ + type => case conf_get("rotation.enable", SubConf) of + true -> wrap; + _ -> halt + end, + file => conf_get("file", SubConf), + max_no_files => conf_get("rotation.count", SubConf), + max_no_bytes => conf_get("max_size", SubConf) + }, formatter => Formatter, - filesync_repeat_interval => no_repeat}} - || {Level, Filename} <- AdditionalLogFiles], + filters => Filters, + filesync_repeat_interval => no_repeat + }} + || {HandlerName, SubConf} <- maps:to_list(conf_get("log.file_handlers", Conf))], - DefaultHandler ++ FileHandler ++ AdditionalHandlers. - -tr_flapping_detect_policy(Conf) -> - [Threshold, Duration, Interval] = conf_get("acl.flapping_detect_policy", Conf), - ParseDuration = fun(S, F) -> - case F(S) of - {ok, I} -> I; - {error, Reason} -> error({duration, Reason}) - end end, - #{threshold => list_to_integer(Threshold), - duration => ParseDuration(Duration, fun to_duration/1), - banned_interval => ParseDuration(Interval, fun to_duration_s/1) - }. - -tr_zones(Conf) -> - Names = lists:usort(keys("zone", Conf)), - lists:foldl( - fun(Name, Zones) -> - Zone = keys("zone." ++ Name, Conf), - Mapped = lists:flatten([map_zones(K, conf_get(["zone", Name, K], Conf)) || K <- Zone]), - [{list_to_atom(Name), lists:filter(fun ({K, []}) when K =:= ratelimit; K =:= quota -> false; - ({_, undefined}) -> false; - (_) -> true end, Mapped)} | Zones] - end, [], Names). - -tr_listeners(Conf) -> - Atom = fun(undefined) -> undefined; - (B) when is_binary(B)-> binary_to_atom(B, utf8); - (S) when is_list(S) -> list_to_atom(S) end, - - Access = fun(S) -> - [A, CIDR] = string:tokens(S, " "), - {list_to_atom(A), case CIDR of "all" -> all; _ -> CIDR end} - end, - - AccOpts = fun(Prefix) -> - case keys(Prefix ++ ".access", Conf) of - [] -> []; - Ids -> - [{access_rules, [Access(conf_get(Prefix ++ ".access." ++ Id, Conf)) || Id <- Ids]}] - end end, - - RateLimit = fun(undefined) -> - undefined; - ([L, D]) -> - Limit = case to_bytesize(L) of - {ok, I0} -> I0; - {error, R0} -> error({bytesize, R0}) - end, - Duration = case to_duration_s(D) of - {ok, I1} -> I1; - {error, R1} -> error({duration, R1}) - end, - {Limit, Duration} - end, - - CheckOrigin = fun(S) -> [ list_to_binary(string:trim(O)) || O <- S] end, - - WsOpts = fun(Prefix) -> - case conf_get(Prefix ++ ".check_origins", Conf) of - undefined -> undefined; - Rules -> lists:flatten(CheckOrigin(Rules)) - end - end, - - LisOpts = fun(Prefix) -> - filter([{acceptors, conf_get(Prefix ++ ".acceptors", Conf)}, - {mqtt_path, conf_get(Prefix ++ ".mqtt_path", Conf)}, - {max_connections, conf_get(Prefix ++ ".max_connections", Conf)}, - {max_conn_rate, conf_get(Prefix ++ ".max_conn_rate", Conf)}, - {active_n, conf_get(Prefix ++ ".active_n", Conf)}, - {tune_buffer, conf_get(Prefix ++ ".tune_buffer", Conf)}, - {zone, Atom(conf_get(Prefix ++ ".zone", Conf))}, - {rate_limit, RateLimit(conf_get(Prefix ++ ".rate_limit", Conf))}, - {proxy_protocol, conf_get(Prefix ++ ".proxy_protocol", Conf)}, - {proxy_address_header, list_to_binary(string:lowercase(conf_get(Prefix ++ ".proxy_address_header", Conf, <<"">>)))}, - {proxy_port_header, list_to_binary(string:lowercase(conf_get(Prefix ++ ".proxy_port_header", Conf, <<"">>)))}, - {proxy_protocol_timeout, conf_get(Prefix ++ ".proxy_protocol_timeout", Conf)}, - {fail_if_no_subprotocol, conf_get(Prefix ++ ".fail_if_no_subprotocol", Conf)}, - {supported_subprotocols, string:tokens(conf_get(Prefix ++ ".supported_subprotocols", Conf, ""), ", ")}, - {peer_cert_as_username, conf_get(Prefix ++ ".peer_cert_as_username", Conf)}, - {peer_cert_as_clientid, conf_get(Prefix ++ ".peer_cert_as_clientid", Conf)}, - {compress, conf_get(Prefix ++ ".compress", Conf)}, - {idle_timeout, conf_get(Prefix ++ ".idle_timeout", Conf)}, - {max_frame_size, conf_get(Prefix ++ ".max_frame_size", Conf)}, - {mqtt_piggyback, conf_get(Prefix ++ ".mqtt_piggyback", Conf)}, - {check_origin_enable, conf_get(Prefix ++ ".check_origin_enable", Conf)}, - {allow_origin_absence, conf_get(Prefix ++ ".allow_origin_absence", Conf)}, - {check_origins, WsOpts(Prefix)} | AccOpts(Prefix)]) - end, - DeflateOpts = fun(Prefix) -> - filter([{level, conf_get(Prefix ++ ".deflate_opts.level", Conf)}, - {mem_level, conf_get(Prefix ++ ".deflate_opts.mem_level", Conf)}, - {strategy, conf_get(Prefix ++ ".deflate_opts.strategy", Conf)}, - {server_context_takeover, conf_get(Prefix ++ ".deflate_opts.server_context_takeover", Conf)}, - {client_context_takeover, conf_get(Prefix ++ ".deflate_opts.client_context_takeover", Conf)}, - {server_max_windows_bits, conf_get(Prefix ++ ".deflate_opts.server_max_window_bits", Conf)}, - {client_max_windows_bits, conf_get(Prefix ++ ".deflate_opts.client_max_window_bits", Conf)}]) - end, - TcpOpts = fun(Prefix) -> - filter([{backlog, conf_get(Prefix ++ ".backlog", Conf)}, - {send_timeout, conf_get(Prefix ++ ".send_timeout", Conf)}, - {send_timeout_close, conf_get(Prefix ++ ".send_timeout_close", Conf)}, - {recbuf, conf_get(Prefix ++ ".recbuf", Conf)}, - {sndbuf, conf_get(Prefix ++ ".sndbuf", Conf)}, - {buffer, conf_get(Prefix ++ ".buffer", Conf)}, - {high_watermark, conf_get(Prefix ++ ".high_watermark", Conf)}, - {nodelay, conf_get(Prefix ++ ".nodelay", Conf, true)}, - {reuseaddr, conf_get(Prefix ++ ".reuseaddr", Conf)}]) - end, - - SslOpts = fun(Prefix) -> - Opts = tr_ssl(Prefix, Conf), - case lists:keyfind(ciphers, 1, Opts) of - false -> - error(Prefix ++ ".ciphers or " ++ Prefix ++ ".psk_ciphers is absent"); - _ -> - Opts - end end, - - TcpListeners = fun(Type, Name) -> - Prefix = string:join(["listener", Type, Name], "."), - ListenOnN = case conf_get(Prefix ++ ".endpoint", Conf) of - "" -> []; - undefined -> []; - ListenOn -> ListenOn - end, - [#{ proto => Atom(Type) - , name => Name - , listen_on => ListenOnN - , opts => [ {deflate_options, DeflateOpts(Prefix)} - , {tcp_options, TcpOpts(Prefix)} - | LisOpts(Prefix) - ] - } - ] - end, - SslListeners = fun(Type, Name) -> - Prefix = string:join(["listener", Type, Name], "."), - case conf_get(Prefix ++ ".endpoint", Conf) of - "" -> - []; - undefined -> - []; - ListenOn -> - [#{ proto => Atom(Type) - , name => Name - , listen_on => ListenOn - , opts => [ {deflate_options, DeflateOpts(Prefix)} - , {tcp_options, TcpOpts(Prefix)} - , {ssl_options, SslOpts(Prefix)} - | LisOpts(Prefix) - ] - } - ] - end end, - - - lists:flatten([TcpListeners("tcp", Name) || Name <- keys("listener.tcp", Conf)] - ++ [TcpListeners("ws", Name) || Name <- keys("listener.ws", Conf)] - ++ [SslListeners("ssl", Name) || Name <- keys("listener.ssl", Conf)] - ++ [SslListeners("wss", Name) || Name <- keys("listener.wss", Conf)] - ++ [SslListeners("quic", Name) || Name <- keys("listener.quic", Conf)] - ). - -tr_sysmon(Conf) -> - Keys = maps:to_list(conf_get("sysmon", Conf, #{})), - [{binary_to_atom(K, utf8), maps:get(value, V)} || {K, V} <- Keys]. - -tr_os_mon(Conf) -> - [{cpu_check_interval, conf_get("os_mon.cpu_check_interval", Conf)} - , {cpu_high_watermark, conf_get("os_mon.cpu_high_watermark", Conf) * 100} - , {cpu_low_watermark, conf_get("os_mon.cpu_low_watermark", Conf) * 100} - , {mem_check_interval, conf_get("os_mon.mem_check_interval", Conf)} - , {sysmem_high_watermark, conf_get("os_mon.sysmem_high_watermark", Conf) * 100} - , {procmem_high_watermark, conf_get("os_mon.procmem_high_watermark", Conf) * 100} - ]. - -tr_vm_mon(Conf) -> - [ {check_interval, conf_get("vm_mon.check_interval", Conf)} - , {process_high_watermark, conf_get("vm_mon.process_high_watermark", Conf) * 100} - , {process_low_watermark, conf_get("vm_mon.process_low_watermark", Conf) * 100} - ]. - -tr_alarm(Conf) -> - [ {actions, [list_to_atom(Action) || Action <- conf_get("alarm.actions", Conf)]} - , {size_limit, conf_get("alarm.size_limit", Conf)} - , {validity_period, conf_get("alarm.validity_period", Conf)} - ]. + [{handler, default, undefined}] ++ ConsoleHandler ++ FileHandlers. %% helpers - -options(static, Conf) -> - [{seeds, [list_to_atom(S) || S <- conf_get("cluster.static.seeds", Conf, "")]}]; -options(mcast, Conf) -> - {ok, Addr} = inet:parse_address(conf_get("cluster.mcast.addr", Conf)), - {ok, Iface} = inet:parse_address(conf_get("cluster.mcast.iface", Conf)), - Ports = [list_to_integer(S) || S <- conf_get("cluster.mcast.ports", Conf)], - [{addr, Addr}, {ports, Ports}, {iface, Iface}, - {ttl, conf_get("cluster.mcast.ttl", Conf, 1)}, - {loop, conf_get("cluster.mcast.loop", Conf, true)}]; -options(dns, Conf) -> - [{name, conf_get("cluster.dns.name", Conf)}, - {app, conf_get("cluster.dns.app", Conf)}]; -options(etcd, Conf) -> - Namespace = "cluster.etcd.ssl", - SslOpts = fun(C) -> - Options = keys(Namespace, C), - lists:map(fun(Key) -> {list_to_atom(Key), conf_get([Namespace, Key], Conf)} end, Options) end, - [{server, conf_get("cluster.etcd.server", Conf)}, - {prefix, conf_get("cluster.etcd.prefix", Conf, "emqxcl")}, - {node_ttl, conf_get("cluster.etcd.node_ttl", Conf, 60)}, - {ssl_options, filter(SslOpts(Conf))}]; -options(k8s, Conf) -> - [{apiserver, conf_get("cluster.k8s.apiserver", Conf)}, - {service_name, conf_get("cluster.k8s.service_name", Conf)}, - {address_type, conf_get("cluster.k8s.address_type", Conf, ip)}, - {app_name, conf_get("cluster.k8s.app_name", Conf)}, - {namespace, conf_get("cluster.k8s.namespace", Conf)}, - {suffix, conf_get("cluster.k8s.suffix", Conf, "")}]; -options(manual, _Conf) -> - []. - formatter(json, CharsLimit, SingleLine) -> {emqx_logger_jsonfmt, #{chars_limit => CharsLimit, @@ -906,158 +617,7 @@ formatter(text, CharsLimit, SingleLine) -> single_line => SingleLine }}. -burst_limit(["disabled"]) -> - {false, {20000, 1000}}; -burst_limit([Count, Window]) -> - {true, {list_to_integer(Count), - case to_duration(Window) of - {ok, I} -> I; - {error, R} -> error({duration, R}) - end}}. - -%% For creating additional log files for specific log levels. -additional_log_files(Conf) -> - LogLevel = ["debug", "info", "notice", "warning", - "error", "critical", "alert", "emergency"], - additional_log_files(Conf, LogLevel, []). - -additional_log_files(_Conf, [], Acc) -> - Acc; -additional_log_files(Conf, [L | More], Acc) -> - case conf_get(["log", L, "file"], Conf) of - undefined -> additional_log_files(Conf, More, Acc); - F -> additional_log_files(Conf, More, [{L, F} | Acc]) - end. - -rate_limit_byte_dur([L, D]) -> - Limit = case to_bytesize(L) of - {ok, I0} -> I0; - {error, R0} -> error({bytesize, R0}) - end, - Duration = case to_duration_s(D) of - {ok, I1} -> I1; - {error, R1} -> error({duration, R1}) - end, - {Limit, Duration}. - -rate_limit_num_dur([L, D]) -> - Limit = case string:to_integer(L) of - {Int, []} when is_integer(Int) -> Int; - _ -> error("failed to parse bytesize string") - end, - Duration = case to_duration_s(D) of - {ok, I} -> I; - {error, Reason} -> error(Reason) - end, - {Limit, Duration}. - -map_zones(_, undefined) -> - {undefined, undefined}; -map_zones("force_gc_policy", [Count, Bytes]) -> - GcPolicy = case to_bytesize(Bytes) of - {error, Reason} -> - error({bytesize, Reason}); - {ok, Bytes1} -> - #{bytes => Bytes1, - count => list_to_integer(Count)} - end, - {force_gc_policy, GcPolicy}; -map_zones("force_shutdown_policy", ["default"]) -> - WordSize = erlang:system_info(wordsize), - {DefaultLen, DefaultSize} = - case WordSize of - 8 -> % arch_64 - {10000, hocon_postprocess:bytesize("64MB")}; - 4 -> % arch_32 - {1000, hocon_postprocess:bytesize("32MB")} - end, - {force_shutdown_policy, #{message_queue_len => DefaultLen, - max_heap_size => DefaultSize div WordSize - }}; -map_zones("force_shutdown_policy", [Len, Siz]) -> - WordSize = erlang:system_info(wordsize), - MaxSiz = case WordSize of - 8 -> % arch_64 - (1 bsl 59) - 1; - 4 -> % arch_32 - (1 bsl 27) - 1 - end, - ShutdownPolicy = - case to_bytesize(Siz) of - {error, Reason} -> - error(Reason); - {ok, Siz1} when Siz1 > MaxSiz -> - error(io_lib:format("force_shutdown_policy: heap-size ~s is too large", [Siz])); - {ok, Siz1} -> - #{message_queue_len => list_to_integer(Len), - max_heap_size => Siz1 div WordSize} - end, - {force_shutdown_policy, ShutdownPolicy}; -map_zones("mqueue_priorities", Val) -> - case Val of - ["none"] -> {mqueue_priorities, none}; % NO_PRIORITY_TABLE - _ -> - MqueuePriorities = lists:foldl(fun(T, Acc) -> - %% NOTE: space in "= " is intended - [Topic, Prio] = string:tokens(T, "= "), - P = list_to_integer(Prio), - (P < 0 orelse P > 255) andalso error({bad_priority, Topic, Prio}), - maps:put(iolist_to_binary(Topic), P, Acc) - end, #{}, Val), - {mqueue_priorities, MqueuePriorities} - end; -map_zones("mountpoint", Val) -> - {mountpoint, iolist_to_binary(Val)}; -map_zones("response_information", Val) -> - {response_information, iolist_to_binary(Val)}; -map_zones("rate_limit", Conf) -> - Messages = case conf_get("conn_messages_in", #{value => Conf}) of - undefined -> - []; - M -> - [{conn_messages_in, rate_limit_num_dur(M)}] - end, - Bytes = case conf_get("conn_bytes_in", #{value => Conf}) of - undefined -> - []; - B -> - [{conn_bytes_in, rate_limit_byte_dur(B)}] - end, - {ratelimit, Messages ++ Bytes}; -map_zones("conn_congestion", Conf) -> - Alarm = case conf_get("alarm", #{value => Conf}) of - undefined -> - []; - A -> - [{conn_congestion_alarm_enabled, A}] - end, - MinAlarm = case conf_get("min_alarm_sustain_duration", #{value => Conf}) of - undefined -> - []; - M -> - [{conn_congestion_min_alarm_sustain_duration, M}] - end, - Alarm ++ MinAlarm; -map_zones("quota", Conf) -> - Conn = case conf_get("conn_messages_routing", #{value => Conf}) of - undefined -> - []; - C -> - [{conn_messages_routing, rate_limit_num_dur(C)}] - end, - Overall = case conf_get("overall_messages_routing", #{value => Conf}) of - undefined -> - []; - O -> - [{overall_messages_routing, rate_limit_num_dur(O)}] - end, - {quota, Conn ++ Overall}; -map_zones(Opt, Val) -> - {list_to_atom(Opt), Val}. - - %% utils - -spec(conf_get(string() | [string()], hocon:config()) -> term()). conf_get(Key, Conf) -> V = hocon_schema:deep_get(Key, Conf, value), @@ -1081,104 +641,67 @@ filter(Opts) -> [{K, V} || {K, V} <- Opts, V =/= undefined]. %% generate a ssl field. -%% ssl("emqx", #{"verify" => verify_peer}) will return -%% [ {"cacertfile", t(string(), "emqx.cacertfile", undefined)} -%% , {"certfile", t(string(), "emqx.certfile", undefined)} -%% , {"keyfile", t(string(), "emqx.keyfile", undefined)} -%% , {"verify", t(union(verify_peer, verify_none), "emqx.verify", verify_peer)} -%% , {"server_name_indication", "emqx.server_name_indication", undefined)} -%% ... -ssl(Mapping, Defaults) -> - M = fun (Field) -> - case (Mapping) of - undefined -> undefined; - _ -> Mapping ++ "." ++ Field - end end, - D = fun (Field) -> maps:get(list_to_atom(Field), Defaults, undefined) end, - [ {"enable", t(flag(), M("enable"), D("enable"))} - , {"cacertfile", t(string(), M("cacertfile"), D("cacertfile"))} - , {"certfile", t(string(), M("certfile"), D("certfile"))} - , {"keyfile", t(string(), M("keyfile"), D("keyfile"))} - , {"verify", t(union(verify_peer, verify_none), M("verify"), D("verify"))} - , {"fail_if_no_peer_cert", t(boolean(), M("fail_if_no_peer_cert"), D("fail_if_no_peer_cert"))} - , {"secure_renegotiate", t(flag(), M("secure_renegotiate"), D("secure_renegotiate"))} - , {"reuse_sessions", t(flag(), M("reuse_sessions"), D("reuse_sessions"))} - , {"honor_cipher_order", t(flag(), M("honor_cipher_order"), D("honor_cipher_order"))} - , {"handshake_timeout", t(duration(), M("handshake_timeout"), D("handshake_timeout"))} - , {"depth", t(integer(), M("depth"), D("depth"))} - , {"password", hoconsc:t(string(), #{mapping => M("key_password"), - default => D("key_password"), +%% ssl(#{"verify" => verify_peer}) will return: +%% [ {"cacertfile", t(string(), undefined, undefined)} +%% , {"certfile", t(string(), undefined, undefined)} +%% , {"keyfile", t(string(), undefined, undefined)} +%% , {"verify", t(union(verify_peer, verify_none), undefined, verify_peer)} +%% , {"server_name_indication", undefined, undefined)} +%% ...] +ssl(Defaults) -> + D = fun (Field) -> maps:get(to_atom(Field), Defaults, undefined) end, + [ {"enable", t(boolean(), undefined, D("enable"))} + , {"cacertfile", t(string(), undefined, D("cacertfile"))} + , {"certfile", t(string(), undefined, D("certfile"))} + , {"keyfile", t(string(), undefined, D("keyfile"))} + , {"verify", t(union(verify_peer, verify_none), undefined, D("verify"))} + , {"fail_if_no_peer_cert", t(boolean(), undefined, D("fail_if_no_peer_cert"))} + , {"secure_renegotiate", t(boolean(), undefined, D("secure_renegotiate"))} + , {"reuse_sessions", t(boolean(), undefined, D("reuse_sessions"))} + , {"honor_cipher_order", t(boolean(), undefined, D("honor_cipher_order"))} + , {"handshake_timeout", t(duration(), undefined, D("handshake_timeout"))} + , {"depth", t(integer(), undefined, D("depth"))} + , {"password", hoconsc:t(string(), #{default => D("key_password"), sensitive => true })} - , {"dhfile", t(string(), M("dhfile"), D("dhfile"))} - , {"server_name_indication", t(union(disable, string()), M("server_name_indication"), + , {"dhfile", t(string(), undefined, D("dhfile"))} + , {"server_name_indication", t(union(disable, string()), undefined, D("server_name_indication"))} - , {"tls_versions", t(comma_separated_list(), M("tls_versions"), D("tls_versions"))} - , {"ciphers", t(comma_separated_list(), M("ciphers"), D("ciphers"))} - , {"psk_ciphers", t(comma_separated_list(), M("ciphers"), D("ciphers"))}]. + , {"versions", #{ type => list(atom()) + , default => maps:get(versions, Defaults, default_tls_vsns()) + , converter => fun (Vsns) -> [tls_vsn(V) || V <- Vsns] end + }} + , {"ciphers", t(hoconsc:array(string()), undefined, D("ciphers"))} + , {"user_lookup_fun", t(any(), undefined, {fun emqx_psk:lookup/3, <<>>})} + ]. -tr_ssl(Field, Conf) -> - Versions = case conf_get([Field, "tls_versions"], Conf) of - undefined -> undefined; - Vs -> [list_to_existing_atom(V) || V <- Vs] - end, - TLSCiphers = conf_get([Field, "ciphers"], Conf), - PSKCiphers = conf_get([Field, "psk_ciphers"], Conf), - Ciphers = ciphers(TLSCiphers, PSKCiphers, Field), - case emqx_schema:conf_get([Field, "enable"], Conf) of - X when X =:= true orelse X =:= undefined -> - filter([{versions, Versions}, - {ciphers, Ciphers}, - {user_lookup_fun, user_lookup_fun(PSKCiphers)}, - {handshake_timeout, conf_get([Field, "handshake_timeout"], Conf)}, - {depth, conf_get([Field, "depth"], Conf)}, - {password, conf_get([Field, "key_password"], Conf)}, - {dhfile, conf_get([Field, "dhfile"], Conf)}, - {keyfile, emqx_schema:conf_get([Field, "keyfile"], Conf)}, - {certfile, emqx_schema:conf_get([Field, "certfile"], Conf)}, - {cacertfile, emqx_schema:conf_get([Field, "cacertfile"], Conf)}, - {verify, emqx_schema:conf_get([Field, "verify"], Conf)}, - {fail_if_no_peer_cert, conf_get([Field, "fail_if_no_peer_cert"], Conf)}, - {secure_renegotiate, conf_get([Field, "secure_renegotiate"], Conf)}, - {reuse_sessions, conf_get([Field, "reuse_sessions"], Conf)}, - {honor_cipher_order, conf_get([Field, "honor_cipher_order"], Conf)}, - {server_name_indication, emqx_schema:conf_get([Field, "server_name_indication"], Conf)} - ]); - _ -> - [] - end. +%% on erl23.2.7.2-emqx-2, sufficient_crypto_support('tlsv1.3') -> false +default_tls_vsns() -> [<<"tlsv1.2">>, <<"tlsv1.1">>, <<"tlsv1">>]. +tls_vsn(<<"tlsv1.3">>) -> 'tlsv1.3'; +tls_vsn(<<"tlsv1.2">>) -> 'tlsv1.2'; +tls_vsn(<<"tlsv1.1">>) -> 'tlsv1.1'; +tls_vsn(<<"tlsv1">>) -> 'tlsv1'. -map_psk_ciphers(PSKCiphers) -> - lists:map( - fun("PSK-AES128-CBC-SHA") -> {psk, aes_128_cbc, sha}; - ("PSK-AES256-CBC-SHA") -> {psk, aes_256_cbc, sha}; - ("PSK-3DES-EDE-CBC-SHA") -> {psk, '3des_ede_cbc', sha}; - ("PSK-RC4-SHA") -> {psk, rc4_128, sha} - end, PSKCiphers). - -ciphers(undefined, undefined, _) -> - undefined; -ciphers(TLSCiphers, undefined, _) -> - TLSCiphers; -ciphers(undefined, PSKCiphers, _) -> - map_psk_ciphers(PSKCiphers); -ciphers(_, _, Field) -> - error(Field ++ ".ciphers and " ++ Field ++ ".psk_ciphers cannot be configured at the same time"). - -user_lookup_fun(undefined) -> - undefined; -user_lookup_fun(_PSKCiphers) -> - {fun emqx_psk:lookup/3, <<>>}. - -tr_password_hash(Field, Conf) -> - case emqx_schema:conf_get([Field, "password_hash"], Conf) of - [Hash] -> list_to_atom(Hash); - [Prefix, Suffix] -> {list_to_atom(Prefix), list_to_atom(Suffix)}; - [Hash, MacFun, Iterations, Dklen] -> {list_to_atom(Hash), list_to_atom(MacFun), - list_to_integer(Iterations), list_to_integer(Dklen)}; - _ -> plain - end. +default_ciphers() -> [ + "TLS_AES_256_GCM_SHA384", "TLS_AES_128_GCM_SHA256", "TLS_CHACHA20_POLY1305_SHA256", + "TLS_AES_128_CCM_SHA256", "TLS_AES_128_CCM_8_SHA256", "ECDHE-ECDSA-AES256-GCM-SHA384", + "ECDHE-RSA-AES256-GCM-SHA384", "ECDHE-ECDSA-AES256-SHA384", "ECDHE-RSA-AES256-SHA384", + "ECDHE-ECDSA-DES-CBC3-SHA", "ECDH-ECDSA-AES256-GCM-SHA384", "ECDH-RSA-AES256-GCM-SHA384", + "ECDH-ECDSA-AES256-SHA384", "ECDH-RSA-AES256-SHA384", "DHE-DSS-AES256-GCM-SHA384", + "DHE-DSS-AES256-SHA256", "AES256-GCM-SHA384", "AES256-SHA256", + "ECDHE-ECDSA-AES128-GCM-SHA256", "ECDHE-RSA-AES128-GCM-SHA256", + "ECDHE-ECDSA-AES128-SHA256", "ECDHE-RSA-AES128-SHA256", "ECDH-ECDSA-AES128-GCM-SHA256", + "ECDH-RSA-AES128-GCM-SHA256", "ECDH-ECDSA-AES128-SHA256", "ECDH-RSA-AES128-SHA256", + "DHE-DSS-AES128-GCM-SHA256", "DHE-DSS-AES128-SHA256", "AES128-GCM-SHA256", "AES128-SHA256", + "ECDHE-ECDSA-AES256-SHA", "ECDHE-RSA-AES256-SHA", "DHE-DSS-AES256-SHA", + "ECDH-ECDSA-AES256-SHA", "ECDH-RSA-AES256-SHA", "AES256-SHA", "ECDHE-ECDSA-AES128-SHA", + "ECDHE-RSA-AES128-SHA", "DHE-DSS-AES128-SHA", "ECDH-ECDSA-AES128-SHA", + "ECDH-RSA-AES128-SHA", "AES128-SHA" + ] ++ psk_ciphers(). +psk_ciphers() -> [ + "PSK-AES128-CBC-SHA", "PSK-AES256-CBC-SHA", "PSK-3DES-EDE-CBC-SHA", "PSK-RC4-SHA" + ]. %% @private return a list of keys in a parent field -spec(keys(string(), hocon:config()) -> [string()]). @@ -1207,11 +730,30 @@ t(Type, Mapping, Default, OverrideEnv) -> , override_env => OverrideEnv }). +t(Type, Mapping, Default, OverrideEnv, Validator) -> + hoconsc:t(Type, #{ mapping => Mapping + , default => Default + , override_env => OverrideEnv + , validator => Validator + }). + ref(Field) -> fun (type) -> Field; (_) -> undefined end. -to_flag(Str) -> - {ok, hocon_postprocess:onoff(Str)}. +maybe_disabled(T) -> + maybe_sth(disabled, T, disabled). + +maybe_disabled(T, Default) -> + maybe_sth(disabled, T, Default). + +maybe_infinity(T) -> + maybe_sth(infinity, T, infinity). + +maybe_infinity(T, Default) -> + maybe_sth(infinity, T, Default). + +maybe_sth(What, Type, Default) -> + t(union([What, Type]), undefined, Default). to_duration(Str) -> case hocon_postprocess:duration(Str) of @@ -1237,6 +779,13 @@ to_bytesize(Str) -> _ -> {error, Str} end. +to_wordsize(Str) -> + WordSize = erlang:system_info(wordsize), + case to_bytesize(Str) of + {ok, Bytes} -> {ok, Bytes div WordSize}; + Error -> Error + end. + to_percent(Str) -> {ok, hocon_postprocess:percent(Str)}. @@ -1244,7 +793,7 @@ to_comma_separated_list(Str) -> {ok, string:tokens(Str, ", ")}. to_comma_separated_atoms(Str) -> - {ok, lists:map(fun list_to_atom/1, string:tokens(Str, ", "))}. + {ok, lists:map(fun to_atom/1, string:tokens(Str, ", "))}. to_bar_separated_list(Str) -> {ok, string:tokens(Str, "| ")}. @@ -1258,3 +807,49 @@ to_ip_port(Str) -> end; _ -> {error, Str} end. + +to_erl_cipher_suite(Str) -> + case ssl:str_to_suite(Str) of + {error, Reason} -> error({invalid_cipher, Reason}); + Cipher -> Cipher + end. + +options(static, Conf) -> + [{seeds, [to_atom(S) || S <- conf_get("cluster.static.seeds", Conf, [])]}]; +options(mcast, Conf) -> + {ok, Addr} = inet:parse_address(conf_get("cluster.mcast.addr", Conf)), + {ok, Iface} = inet:parse_address(conf_get("cluster.mcast.iface", Conf)), + Ports = conf_get("cluster.mcast.ports", Conf), + [{addr, Addr}, {ports, Ports}, {iface, Iface}, + {ttl, conf_get("cluster.mcast.ttl", Conf, 1)}, + {loop, conf_get("cluster.mcast.loop", Conf, true)}]; +options(dns, Conf) -> + [{name, conf_get("cluster.dns.name", Conf)}, + {app, conf_get("cluster.dns.app", Conf)}]; +options(etcd, Conf) -> + Namespace = "cluster.etcd.ssl", + SslOpts = fun(C) -> + Options = keys(Namespace, C), + lists:map(fun(Key) -> {to_atom(Key), conf_get([Namespace, Key], Conf)} end, Options) end, + [{server, conf_get("cluster.etcd.server", Conf)}, + {prefix, conf_get("cluster.etcd.prefix", Conf, "emqxcl")}, + {node_ttl, conf_get("cluster.etcd.node_ttl", Conf, 60)}, + {ssl_options, filter(SslOpts(Conf))}]; +options(k8s, Conf) -> + [{apiserver, conf_get("cluster.k8s.apiserver", Conf)}, + {service_name, conf_get("cluster.k8s.service_name", Conf)}, + {address_type, conf_get("cluster.k8s.address_type", Conf, ip)}, + {app_name, conf_get("cluster.k8s.app_name", Conf)}, + {namespace, conf_get("cluster.k8s.namespace", Conf)}, + {suffix, conf_get("cluster.k8s.suffix", Conf, "")}]; +options(manual, _Conf) -> + []. + +to_atom(#{value := Val}= _RichMap) -> + to_atom(Val); +to_atom(Atom) when is_atom(Atom) -> + Atom; +to_atom(Str) when is_list(Str) -> + list_to_atom(Str); +to_atom(Bin) when is_binary(Bin) -> + list_to_atom(binary_to_list(Bin)). diff --git a/apps/emqx/src/emqx_session.erl b/apps/emqx/src/emqx_session.erl index 9463345d4..c1f6767b4 100644 --- a/apps/emqx/src/emqx_session.erl +++ b/apps/emqx/src/emqx_session.erl @@ -92,13 +92,11 @@ -export_type([session/0]). --import(emqx_zone, [get_env/3]). - -record(session, { %% Client’s Subscriptions. subscriptions :: map(), %% Max subscriptions allowed - max_subscriptions :: non_neg_integer(), + max_subscriptions :: non_neg_integer() | infinity, %% Upgrade QoS? upgrade_qos :: boolean(), %% Client <- Broker: QoS1/2 messages sent to the client but @@ -117,7 +115,7 @@ %% have not been completely acknowledged awaiting_rel :: map(), %% Maximum number of awaiting QoS2 messages allowed - max_awaiting_rel :: non_neg_integer(), + max_awaiting_rel :: non_neg_integer() | infinity, %% Awaiting PUBREL Timeout (Unit: millsecond) await_rel_timeout :: timeout(), %% Created at @@ -159,27 +157,28 @@ %%-------------------------------------------------------------------- -spec(init(emqx_types:clientinfo(), emqx_types:conninfo()) -> session()). -init(#{zone := Zone}, #{receive_maximum := MaxInflight}) -> - #session{max_subscriptions = get_env(Zone, max_subscriptions, 0), +init(#{zone := Zone, listener := Listener}, #{receive_maximum := MaxInflight}) -> + #session{max_subscriptions = get_conf(Zone, Listener, max_subscriptions), subscriptions = #{}, - upgrade_qos = get_env(Zone, upgrade_qos, false), + upgrade_qos = get_conf(Zone, Listener, upgrade_qos), inflight = emqx_inflight:new(MaxInflight), - mqueue = init_mqueue(Zone), + mqueue = init_mqueue(Zone, Listener), next_pkt_id = 1, - retry_interval = timer:seconds(get_env(Zone, retry_interval, 0)), + retry_interval = timer:seconds(get_conf(Zone, Listener, retry_interval)), awaiting_rel = #{}, - max_awaiting_rel = get_env(Zone, max_awaiting_rel, 100), - await_rel_timeout = timer:seconds(get_env(Zone, await_rel_timeout, 300)), + max_awaiting_rel = get_conf(Zone, Listener, max_awaiting_rel), + await_rel_timeout = timer:seconds(get_conf(Zone, Listener, await_rel_timeout)), created_at = erlang:system_time(millisecond) }. %% @private init mq -init_mqueue(Zone) -> - emqx_mqueue:init(#{max_len => get_env(Zone, max_mqueue_len, 1000), - store_qos0 => get_env(Zone, mqueue_store_qos0, true), - priorities => get_env(Zone, mqueue_priorities, none), - default_priority => get_env(Zone, mqueue_default_priority, lowest) - }). +init_mqueue(Zone, Listener) -> + emqx_mqueue:init(#{ + max_len => get_conf(Zone, Listener, max_mqueue_len), + store_qos0 => get_conf(Zone, Listener, mqueue_store_qos0), + priorities => get_conf(Zone, Listener, mqueue_priorities), + default_priority => get_conf(Zone, Listener, mqueue_default_priority) + }). %%-------------------------------------------------------------------- %% Info, Stats @@ -253,7 +252,7 @@ subscribe(ClientInfo = #{clientid := ClientId}, TopicFilter, SubOpts, end. -compile({inline, [is_subscriptions_full/1]}). -is_subscriptions_full(#session{max_subscriptions = 0}) -> +is_subscriptions_full(#session{max_subscriptions = infinity}) -> false; is_subscriptions_full(#session{subscriptions = Subs, max_subscriptions = MaxLimit}) -> @@ -302,7 +301,7 @@ publish(_PacketId, Msg, Session) -> {ok, emqx_broker:publish(Msg), Session}. -compile({inline, [is_awaiting_full/1]}). -is_awaiting_full(#session{max_awaiting_rel = 0}) -> +is_awaiting_full(#session{max_awaiting_rel = infinity}) -> false; is_awaiting_full(#session{awaiting_rel = AwaitingRel, max_awaiting_rel = MaxLimit}) -> @@ -697,3 +696,5 @@ set_field(Name, Value, Session) -> Pos = emqx_misc:index_of(Name, record_info(fields, session)), setelement(Pos+1, Session, Value). +get_conf(Zone, Listener, Key) -> + emqx_config:get_listener_conf(Zone, Listener, [mqtt, Key]). diff --git a/apps/emqx/src/emqx_sys.erl b/apps/emqx/src/emqx_sys.erl index 2d816569d..2f3f782e6 100644 --- a/apps/emqx/src/emqx_sys.erl +++ b/apps/emqx/src/emqx_sys.erl @@ -32,8 +32,6 @@ , uptime/0 , datetime/0 , sysdescr/0 - , sys_interval/0 - , sys_heatbeat_interval/0 ]). -export([info/0]). @@ -104,15 +102,11 @@ datetime() -> io_lib:format( "~4..0w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w", [Y, M, D, H, MM, S])). -%% @doc Get sys interval --spec(sys_interval() -> pos_integer()). sys_interval() -> - emqx:get_env(broker_sys_interval, 60000). + emqx_config:get([broker, sys_msg_interval]). -%% @doc Get sys heatbeat interval --spec(sys_heatbeat_interval() -> pos_integer()). sys_heatbeat_interval() -> - emqx:get_env(broker_sys_heartbeat, 30000). + emqx_config:get([broker, sys_heartbeat_interval]). %% @doc Get sys info -spec(info() -> list(tuple())). diff --git a/apps/emqx/src/emqx_sys_mon.erl b/apps/emqx/src/emqx_sys_mon.erl index 152f975eb..54a5c533a 100644 --- a/apps/emqx/src/emqx_sys_mon.erl +++ b/apps/emqx/src/emqx_sys_mon.erl @@ -23,7 +23,7 @@ -logger_header("[SYSMON]"). --export([start_link/1]). +-export([start_link/0]). %% compress unused warning -export([procinfo/1]). @@ -37,25 +37,19 @@ , code_change/3 ]). --type(option() :: {long_gc, non_neg_integer()} - | {long_schedule, non_neg_integer()} - | {large_heap, non_neg_integer()} - | {busy_port, boolean()} - | {busy_dist_port, boolean()}). - -define(SYSMON, ?MODULE). %% @doc Start the system monitor. --spec(start_link(list(option())) -> startlink_ret()). -start_link(Opts) -> - gen_server:start_link({local, ?SYSMON}, ?MODULE, [Opts], []). +-spec(start_link() -> startlink_ret()). +start_link() -> + gen_server:start_link({local, ?SYSMON}, ?MODULE, [], []). %%-------------------------------------------------------------------- %% gen_server callbacks %%-------------------------------------------------------------------- -init([Opts]) -> - _ = erlang:system_monitor(self(), parse_opt(Opts)), +init([]) -> + _ = erlang:system_monitor(self(), sysm_opts()), emqx_logger:set_proc_metadata(#{sysmon => true}), %% Monitor cluster partition event @@ -66,30 +60,28 @@ init([Opts]) -> start_timer(State) -> State#{timer := emqx_misc:start_timer(timer:seconds(2), reset)}. -parse_opt(Opts) -> - parse_opt(Opts, []). -parse_opt([], Acc) -> +sysm_opts() -> + sysm_opts(maps:to_list(emqx_config:get([sysmon, vm])), []). +sysm_opts([], Acc) -> Acc; -parse_opt([{long_gc, 0}|Opts], Acc) -> - parse_opt(Opts, Acc); -parse_opt([{long_gc, Ms}|Opts], Acc) when is_integer(Ms) -> - parse_opt(Opts, [{long_gc, Ms}|Acc]); -parse_opt([{long_schedule, 0}|Opts], Acc) -> - parse_opt(Opts, Acc); -parse_opt([{long_schedule, Ms}|Opts], Acc) when is_integer(Ms) -> - parse_opt(Opts, [{long_schedule, Ms}|Acc]); -parse_opt([{large_heap, Size}|Opts], Acc) when is_integer(Size) -> - parse_opt(Opts, [{large_heap, Size}|Acc]); -parse_opt([{busy_port, true}|Opts], Acc) -> - parse_opt(Opts, [busy_port|Acc]); -parse_opt([{busy_port, false}|Opts], Acc) -> - parse_opt(Opts, Acc); -parse_opt([{busy_dist_port, true}|Opts], Acc) -> - parse_opt(Opts, [busy_dist_port|Acc]); -parse_opt([{busy_dist_port, false}|Opts], Acc) -> - parse_opt(Opts, Acc); -parse_opt([_Opt|Opts], Acc) -> - parse_opt(Opts, Acc). +sysm_opts([{_, disabled}|Opts], Acc) -> + sysm_opts(Opts, Acc); +sysm_opts([{long_gc, Ms}|Opts], Acc) when is_integer(Ms) -> + sysm_opts(Opts, [{long_gc, Ms}|Acc]); +sysm_opts([{long_schedule, Ms}|Opts], Acc) when is_integer(Ms) -> + sysm_opts(Opts, [{long_schedule, Ms}|Acc]); +sysm_opts([{large_heap, Size}|Opts], Acc) when is_integer(Size) -> + sysm_opts(Opts, [{large_heap, Size}|Acc]); +sysm_opts([{busy_port, true}|Opts], Acc) -> + sysm_opts(Opts, [busy_port|Acc]); +sysm_opts([{busy_port, false}|Opts], Acc) -> + sysm_opts(Opts, Acc); +sysm_opts([{busy_dist_port, true}|Opts], Acc) -> + sysm_opts(Opts, [busy_dist_port|Acc]); +sysm_opts([{busy_dist_port, false}|Opts], Acc) -> + sysm_opts(Opts, Acc); +sysm_opts([_Opt|Opts], Acc) -> + sysm_opts(Opts, Acc). handle_call(Req, _From, State) -> ?LOG(error, "Unexpected call: ~p", [Req]), diff --git a/apps/emqx/src/emqx_sys_sup.erl b/apps/emqx/src/emqx_sys_sup.erl index 50d086156..61342fd0e 100644 --- a/apps/emqx/src/emqx_sys_sup.erl +++ b/apps/emqx/src/emqx_sys_sup.erl @@ -27,10 +27,10 @@ start_link() -> init([]) -> Childs = [child_spec(emqx_sys), - child_spec(emqx_alarm, [config(alarm)]), - child_spec(emqx_sys_mon, [config(sysmon)]), - child_spec(emqx_os_mon, [config(os_mon)]), - child_spec(emqx_vm_mon, [config(vm_mon)])], + child_spec(emqx_alarm), + child_spec(emqx_sys_mon), + child_spec(emqx_os_mon), + child_spec(emqx_vm_mon)], {ok, {{one_for_one, 10, 100}, Childs}}. %%-------------------------------------------------------------------- @@ -48,6 +48,3 @@ child_spec(Mod, Args) -> type => worker, modules => [Mod] }. - -config(Name) -> emqx:get_env(Name, []). - diff --git a/apps/emqx/src/emqx_tls_lib.erl b/apps/emqx/src/emqx_tls_lib.erl index 72a955962..24a9a15cf 100644 --- a/apps/emqx/src/emqx_tls_lib.erl +++ b/apps/emqx/src/emqx_tls_lib.erl @@ -161,17 +161,16 @@ drop_tls13_for_old_otp(SslOpts) -> , "TLS_AES_128_CCM_8_SHA256" ]). drop_tls13(SslOpts0) -> - SslOpts1 = case proplists:get_value(versions, SslOpts0) of - undefined -> SslOpts0; - Vsns -> replace(SslOpts0, versions, Vsns -- ['tlsv1.3']) + SslOpts1 = case maps:find(versions, SslOpts0) of + error -> SslOpts0; + {ok, Vsns} -> SslOpts0#{versions => (Vsns -- ['tlsv1.3'])} end, - case proplists:get_value(ciphers, SslOpts1) of - undefined -> SslOpts1; - Ciphers -> replace(SslOpts1, ciphers, Ciphers -- ?TLSV13_EXCLUSIVE_CIPHERS) + case maps:find(ciphers, SslOpts1) of + error -> SslOpts1; + {ok, Ciphers} -> + SslOpts1#{ciphers => Ciphers -- ?TLSV13_EXCLUSIVE_CIPHERS} end. -replace(Opts, Key, Value) -> [{Key, Value} | proplists:delete(Key, Opts)]. - -if(?OTP_RELEASE > 22). -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). @@ -181,13 +180,13 @@ drop_tls13_test() -> ?assert(lists:member('tlsv1.3', Versions)), Ciphers = default_ciphers(), ?assert(has_tlsv13_cipher(Ciphers)), - Opts0 = [{versions, Versions}, {ciphers, Ciphers}, other, {bool, true}], + Opts0 = #{versions => Versions, ciphers => Ciphers, other => true}, Opts = drop_tls13(Opts0), - ?assertNot(lists:member('tlsv1.3', proplists:get_value(versions, Opts))), - ?assertNot(has_tlsv13_cipher(proplists:get_value(ciphers, Opts))). + ?assertNot(lists:member('tlsv1.3', maps:get(versions, Opts, undefined))), + ?assertNot(has_tlsv13_cipher(maps:get(ciphers, Opts, undefined))). drop_tls13_no_versions_cipers_test() -> - Opts0 = [other, {bool, true}], + Opts0 = #{other => 0, bool => true}, Opts = drop_tls13(Opts0), ?_assertEqual(Opts0, Opts). diff --git a/apps/emqx/src/emqx_types.erl b/apps/emqx/src/emqx_types.erl index fbe62e4b2..09ec54b9d 100644 --- a/apps/emqx/src/emqx_types.erl +++ b/apps/emqx/src/emqx_types.erl @@ -209,7 +209,8 @@ -type(infos() :: #{atom() => term()}). -type(stats() :: [{atom(), term()}]). --type(oom_policy() :: #{message_queue_len => non_neg_integer(), - max_heap_size => non_neg_integer() +-type(oom_policy() :: #{max_message_queue_len => non_neg_integer(), + max_heap_size => non_neg_integer(), + enable => boolean() }). diff --git a/apps/emqx/src/emqx_vm_mon.erl b/apps/emqx/src/emqx_vm_mon.erl index ce34fff43..13a470959 100644 --- a/apps/emqx/src/emqx_vm_mon.erl +++ b/apps/emqx/src/emqx_vm_mon.erl @@ -21,15 +21,7 @@ -include("logger.hrl"). %% APIs --export([start_link/1]). - --export([ get_check_interval/0 - , set_check_interval/1 - , get_process_high_watermark/0 - , set_process_high_watermark/1 - , get_process_low_watermark/0 - , set_process_low_watermark/1 - ]). +-export([start_link/0]). %% gen_server callbacks -export([ init/1 @@ -42,61 +34,19 @@ -define(VM_MON, ?MODULE). -start_link(Opts) -> - gen_server:start_link({local, ?VM_MON}, ?MODULE, [Opts], []). - %%-------------------------------------------------------------------- %% API %%-------------------------------------------------------------------- - -get_check_interval() -> - call(get_check_interval). - -set_check_interval(Seconds) -> - call({set_check_interval, Seconds}). - -get_process_high_watermark() -> - call(get_process_high_watermark). - -set_process_high_watermark(Float) -> - call({set_process_high_watermark, Float}). - -get_process_low_watermark() -> - call(get_process_low_watermark). - -set_process_low_watermark(Float) -> - call({set_process_low_watermark, Float}). - -call(Req) -> - gen_server:call(?VM_MON, Req, infinity). +start_link() -> + gen_server:start_link({local, ?VM_MON}, ?MODULE, [], []). %%-------------------------------------------------------------------- %% gen_server callbacks %%-------------------------------------------------------------------- -init([Opts]) -> - {ok, ensure_check_timer(#{check_interval => proplists:get_value(check_interval, Opts), - process_high_watermark => proplists:get_value(process_high_watermark, Opts), - process_low_watermark => proplists:get_value(process_low_watermark, Opts), - timer => undefined})}. - -handle_call(get_check_interval, _From, State) -> - {reply, maps:get(check_interval, State, undefined), State}; - -handle_call({set_check_interval, Seconds}, _From, State) -> - {reply, ok, State#{check_interval := Seconds}}; - -handle_call(get_process_high_watermark, _From, State) -> - {reply, maps:get(process_high_watermark, State, undefined), State}; - -handle_call({set_process_high_watermark, Float}, _From, State) -> - {reply, ok, State#{process_high_watermark := Float}}; - -handle_call(get_process_low_watermark, _From, State) -> - {reply, maps:get(process_low_watermark, State, undefined), State}; - -handle_call({set_process_low_watermark, Float}, _From, State) -> - {reply, ok, State#{process_low_watermark := Float}}; +init([]) -> + start_check_timer(), + {ok, #{}}. handle_call(Req, _From, State) -> ?LOG(error, "[VM_MON] Unexpected call: ~p", [Req]), @@ -106,29 +56,30 @@ handle_cast(Msg, State) -> ?LOG(error, "[VM_MON] Unexpected cast: ~p", [Msg]), {noreply, State}. -handle_info({timeout, Timer, check}, - State = #{timer := Timer, - process_high_watermark := ProcHighWatermark, - process_low_watermark := ProcLowWatermark}) -> +handle_info({timeout, _Timer, check}, State) -> + ProcHighWatermark = emqx_config:get([sysmon, vm, process_high_watermark]), + ProcLowWatermark = emqx_config:get([sysmon, vm, process_low_watermark]), ProcessCount = erlang:system_info(process_count), - case ProcessCount / erlang:system_info(process_limit) * 100 of + case ProcessCount / erlang:system_info(process_limit) of Percent when Percent >= ProcHighWatermark -> - emqx_alarm:activate(too_many_processes, #{usage => Percent, - high_watermark => ProcHighWatermark, - low_watermark => ProcLowWatermark}); + emqx_alarm:activate(too_many_processes, #{ + usage => io_lib:format("~p%", [Percent*100]), + high_watermark => ProcHighWatermark, + low_watermark => ProcLowWatermark}); Percent when Percent < ProcLowWatermark -> emqx_alarm:deactivate(too_many_processes); _Precent -> ok end, - {noreply, ensure_check_timer(State)}; + start_check_timer(), + {noreply, State}; handle_info(Info, State) -> ?LOG(error, "[VM_MON] Unexpected info: ~p", [Info]), {noreply, State}. -terminate(_Reason, #{timer := Timer}) -> - emqx_misc:cancel_timer(Timer). +terminate(_Reason, _State) -> + ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. @@ -137,5 +88,6 @@ code_change(_OldVsn, State, _Extra) -> %% Internal functions %%-------------------------------------------------------------------- -ensure_check_timer(State = #{check_interval := Interval}) -> - State#{timer := emqx_misc:start_timer(timer:seconds(Interval), check)}. +start_check_timer() -> + Interval = emqx_config:get([sysmon, vm, process_check_interval]), + emqx_misc:start_timer(Interval, check). diff --git a/apps/emqx/src/emqx_ws_connection.erl b/apps/emqx/src/emqx_ws_connection.erl index d686e1611..947658035 100644 --- a/apps/emqx/src/emqx_ws_connection.erl +++ b/apps/emqx/src/emqx_ws_connection.erl @@ -62,8 +62,6 @@ sockname :: emqx_types:peername(), %% Sock state sockstate :: emqx_types:sockstate(), - %% Simulate the active_n opt - active_n :: pos_integer(), %% MQTT Piggyback mqtt_piggyback :: single | multiple, %% Limiter @@ -85,7 +83,11 @@ %% Idle Timeout idle_timeout :: timeout(), %% Idle Timer - idle_timer :: maybe(reference()) + idle_timer :: maybe(reference()), + %% Zone name + zone :: atom(), + %% Listener Name + listener :: atom() }). -type(state() :: #state{}). @@ -93,7 +95,7 @@ -type(ws_cmd() :: {active, boolean()}|close). -define(ACTIVE_N, 100). --define(INFO_KEYS, [socktype, peername, sockname, sockstate, active_n]). +-define(INFO_KEYS, [socktype, peername, sockname, sockstate]). -define(SOCK_STATS, [recv_oct, recv_cnt, send_oct, send_cnt]). -define(CONN_STATS, [recv_pkt, recv_msg, send_pkt, send_msg]). @@ -124,8 +126,6 @@ info(sockname, #state{sockname = Sockname}) -> Sockname; info(sockstate, #state{sockstate = SockSt}) -> SockSt; -info(active_n, #state{active_n = ActiveN}) -> - ActiveN; info(limiter, #state{limiter = Limiter}) -> maybe_apply(fun emqx_limiter:info/1, Limiter); info(channel, #state{channel = Channel}) -> @@ -174,21 +174,13 @@ call(WsPid, Req, Timeout) when is_pid(WsPid) -> %% WebSocket callbacks %%-------------------------------------------------------------------- -init(Req, Opts) -> +init(Req, #{zone := Zone, listener := Listener} = Opts) -> %% WS Transport Idle Timeout - IdleTimeout = proplists:get_value(idle_timeout, Opts, 7200000), - DeflateOptions = maps:from_list(proplists:get_value(deflate_options, Opts, [])), - MaxFrameSize = case proplists:get_value(max_frame_size, Opts, 0) of - 0 -> infinity; - I -> I - end, - Compress = proplists:get_bool(compress, Opts), - WsOpts = #{compress => Compress, - deflate_opts => DeflateOptions, - max_frame_size => MaxFrameSize, - idle_timeout => IdleTimeout + WsOpts = #{compress => get_ws_opts(Zone, Listener, compress), + deflate_opts => get_ws_opts(Zone, Listener, deflate_opts), + max_frame_size => get_ws_opts(Zone, Listener, max_frame_size), + idle_timeout => get_ws_opts(Zone, Listener, idle_timeout) }, - case check_origin_header(Req, Opts) of {error, Message} -> ?LOG(error, "Invalid Origin Header ~p~n", [Message]), @@ -196,18 +188,17 @@ init(Req, Opts) -> ok -> parse_sec_websocket_protocol(Req, Opts, WsOpts) end. -parse_sec_websocket_protocol(Req, Opts, WsOpts) -> - FailIfNoSubprotocol = proplists:get_value(fail_if_no_subprotocol, Opts), +parse_sec_websocket_protocol(Req, #{zone := Zone, listener := Listener} = Opts, WsOpts) -> case cowboy_req:parse_header(<<"sec-websocket-protocol">>, Req) of undefined -> - case FailIfNoSubprotocol of + case get_ws_opts(Zone, Listener, fail_if_no_subprotocol) of true -> {ok, cowboy_req:reply(400, Req), WsOpts}; false -> {cowboy_websocket, Req, [Req, Opts], WsOpts} end; Subprotocols -> - SupportedSubprotocols = proplists:get_value(supported_subprotocols, Opts), + SupportedSubprotocols = get_ws_opts(Zone, Listener, supported_subprotocols), NSupportedSubprotocols = [list_to_binary(Subprotocol) || Subprotocol <- SupportedSubprotocols], case pick_subprotocol(Subprotocols, NSupportedSubprotocols) of @@ -231,31 +222,30 @@ pick_subprotocol([Subprotocol | Rest], SupportedSubprotocols) -> pick_subprotocol(Rest, SupportedSubprotocols) end. -parse_header_fun_origin(Req, Opts) -> +parse_header_fun_origin(Req, #{zone := Zone, listener := Listener}) -> case cowboy_req:header(<<"origin">>, Req) of undefined -> - case proplists:get_bool(allow_origin_absence, Opts) of + case get_ws_opts(Zone, Listener, allow_origin_absence) of true -> ok; false -> {error, origin_header_cannot_be_absent} end; Value -> - Origins = proplists:get_value(check_origins, Opts, []), - case lists:member(Value, Origins) of + case lists:member(Value, get_ws_opts(Zone, Listener, check_origins)) of true -> ok; false -> {origin_not_allowed, Value} end end. -check_origin_header(Req, Opts) -> - case proplists:get_bool(check_origin_enable, Opts) of +check_origin_header(Req, #{zone := Zone, listener := Listener} = Opts) -> + case get_ws_opts(Zone, Listener, check_origin_enable) of true -> parse_header_fun_origin(Req, Opts); false -> ok end. -websocket_init([Req, Opts]) -> +websocket_init([Req, #{zone := Zone, listener := Listener} = Opts]) -> {Peername, Peercert} = - case proplists:get_bool(proxy_protocol, Opts) - andalso maps:get(proxy_header, Req) of + case emqx_config:get_listener_conf(Zone, Listener, [proxy_protocol]) andalso + maps:get(proxy_header, Req) of #{src_address := SrcAddr, src_port := SrcPort, ssl := SSL} -> SourceName = {SrcAddr, SrcPort}, %% Notice: Only CN is available in Proxy Protocol V2 additional info @@ -266,7 +256,7 @@ websocket_init([Req, Opts]) -> {SourceName, SourceSSL}; #{src_address := SrcAddr, src_port := SrcPort} -> SourceName = {SrcAddr, SrcPort}, - {SourceName , nossl}; + {SourceName, nossl}; _ -> {get_peer(Req, Opts), cowboy_req:cert(Req)} end, @@ -288,28 +278,35 @@ websocket_init([Req, Opts]) -> ws_cookie => WsCookie, conn_mod => ?MODULE }, - Zone = proplists:get_value(zone, Opts), - PubLimit = emqx_zone:publish_limit(Zone), - BytesIn = proplists:get_value(rate_limit, Opts), - RateLimit = emqx_zone:ratelimit(Zone), - Limiter = emqx_limiter:init(Zone, PubLimit, BytesIn, RateLimit), - ActiveN = proplists:get_value(active_n, Opts, ?ACTIVE_N), - MQTTPiggyback = proplists:get_value(mqtt_piggyback, Opts, multiple), - FrameOpts = emqx_zone:mqtt_frame_options(Zone), + Limiter = emqx_limiter:init(Zone, undefined, undefined, []), + MQTTPiggyback = get_ws_opts(Zone, Listener, mqtt_piggyback), + FrameOpts = #{ + strict_mode => emqx_config:get_listener_conf(Zone, Listener, [mqtt, strict_mode]), + max_size => emqx_config:get_listener_conf(Zone, Listener, [mqtt, max_packet_size]) + }, ParseState = emqx_frame:initial_parse_state(FrameOpts), Serialize = emqx_frame:serialize_opts(), Channel = emqx_channel:init(ConnInfo, Opts), - GcState = emqx_zone:init_gc_state(Zone), - StatsTimer = emqx_zone:stats_timer(Zone), + GcState = case emqx_config:get_listener_conf(Zone, Listener, [force_gc]) of + #{enable := false} -> undefined; + GcPolicy -> emqx_gc:init(GcPolicy) + end, + StatsTimer = case emqx_config:get_listener_conf(Zone, Listener, [stats, enable]) of + true -> undefined; + false -> disabled + end, %% MQTT Idle Timeout - IdleTimeout = emqx_zone:idle_timeout(Zone), + IdleTimeout = emqx_channel:get_mqtt_conf(Zone, Listener, idle_timeout), IdleTimer = start_timer(IdleTimeout, idle_timeout), - emqx_misc:tune_heap_size(emqx_zone:oom_policy(Zone)), + case emqx_config:get_listener_conf(emqx_channel:info(zone, Channel), + emqx_channel:info(listener, Channel), [force_shutdown]) of + #{enable := false} -> ok; + ShutdownPolicy -> emqx_misc:tune_heap_size(ShutdownPolicy) + end, emqx_logger:set_metadata_peername(esockd:format(Peername)), {ok, #state{peername = Peername, sockname = Sockname, sockstate = running, - active_n = ActiveN, mqtt_piggyback = MQTTPiggyback, limiter = Limiter, parse_state = ParseState, @@ -319,7 +316,9 @@ websocket_init([Req, Opts]) -> postponed = [], stats_timer = StatsTimer, idle_timeout = IdleTimeout, - idle_timer = IdleTimer + idle_timer = IdleTimer, + zone = Zone, + listener = Listener }, hibernate}. websocket_handle({binary, Data}, State) when is_list(Data) -> @@ -372,7 +371,8 @@ websocket_info({check_gc, Stats}, State) -> return(check_oom(run_gc(Stats, State))); websocket_info(Deliver = {deliver, _Topic, _Msg}, - State = #state{active_n = ActiveN}) -> + State = #state{zone = Zone, listener = Listener}) -> + ActiveN = emqx_config:get_listener_conf(Zone, Listener, [tcp, active_n]), Delivers = [Deliver|emqx_misc:drain_deliver(ActiveN)], with_channel(handle_deliver, [Delivers], State); @@ -521,11 +521,16 @@ run_gc(Stats, State = #state{gc_state = GcSt}) -> end. check_oom(State = #state{channel = Channel}) -> - OomPolicy = emqx_zone:oom_policy(emqx_channel:info(zone, Channel)), - case ?ENABLED(OomPolicy) andalso emqx_misc:check_oom(OomPolicy) of - Shutdown = {shutdown, _Reason} -> - postpone(Shutdown, State); - _Other -> State + ShutdownPolicy = emqx_config:get_listener_conf(emqx_channel:info(zone, Channel), + emqx_channel:info(listener, Channel), [force_shutdown]), + case ShutdownPolicy of + #{enable := false} -> State; + #{enable := true} -> + case emqx_misc:check_oom(ShutdownPolicy) of + Shutdown = {shutdown, _Reason} -> + postpone(Shutdown, State); + _Other -> State + end end. %%-------------------------------------------------------------------- @@ -554,11 +559,12 @@ parse_incoming(Data, State = #state{parse_state = ParseState}) -> %% Handle incoming packet %%-------------------------------------------------------------------- -handle_incoming(Packet, State = #state{active_n = ActiveN}) +handle_incoming(Packet, State = #state{zone = Zone, listener = Listener}) when is_record(Packet, mqtt_packet) -> ?LOG(debug, "RECV ~s", [emqx_packet:format(Packet)]), ok = inc_incoming_stats(Packet), - NState = case emqx_pd:get_counter(incoming_pubs) > ActiveN of + NState = case emqx_pd:get_counter(incoming_pubs) > + emqx_config:get_listener_conf(Zone, Listener, [tcp, active_n]) of true -> postpone({cast, rate_limit}, State); false -> State end, @@ -589,11 +595,13 @@ with_channel(Fun, Args, State = #state{channel = Channel}) -> %% Handle outgoing packets %%-------------------------------------------------------------------- -handle_outgoing(Packets, State = #state{active_n = ActiveN, mqtt_piggyback = MQTTPiggyback}) -> +handle_outgoing(Packets, State = #state{mqtt_piggyback = MQTTPiggyback, + zone = Zone, listener = Listener}) -> IoData = lists:map(serialize_and_inc_stats_fun(State), Packets), Oct = iolist_size(IoData), ok = inc_sent_stats(length(Packets), Oct), - NState = case emqx_pd:get_counter(outgoing_pubs) > ActiveN of + NState = case emqx_pd:get_counter(outgoing_pubs) > + emqx_config:get_listener_conf(Zone, Listener, [tcp, active_n]) of true -> Stats = #{cnt => emqx_pd:reset_counter(outgoing_pubs), oct => emqx_pd:reset_counter(outgoing_bytes) @@ -742,9 +750,10 @@ classify([Event|More], Packets, Cmds, Events) -> trigger(Event) -> erlang:send(self(), Event). -get_peer(Req, Opts) -> +get_peer(Req, #{zone := Zone, listener := Listener}) -> {PeerAddr, PeerPort} = cowboy_req:peer(Req), - AddrHeader = cowboy_req:header(proplists:get_value(proxy_address_header, Opts), Req, <<>>), + AddrHeader = cowboy_req:header( + get_ws_opts(Zone, Listener, proxy_address_header), Req, <<>>), ClientAddr = case string:tokens(binary_to_list(AddrHeader), ", ") of [] -> undefined; @@ -757,7 +766,8 @@ get_peer(Req, Opts) -> _ -> PeerAddr end, - PortHeader = cowboy_req:header(proplists:get_value(proxy_port_header, Opts), Req, <<>>), + PortHeader = cowboy_req:header( + get_ws_opts(Zone, Listener, proxy_port_header), Req, <<>>), ClientPort = case string:tokens(binary_to_list(PortHeader), ", ") of [] -> undefined; @@ -778,3 +788,5 @@ set_field(Name, Value, State) -> Pos = emqx_misc:index_of(Name, record_info(fields, state)), setelement(Pos+1, State, Value). +get_ws_opts(Zone, Listener, Key) -> + emqx_config:get_listener_conf(Zone, Listener, [websocket, Key]). diff --git a/apps/emqx/test/emqx_SUITE.erl b/apps/emqx/test/emqx_SUITE.erl index 4213a5aac..dca66eca9 100644 --- a/apps/emqx/test/emqx_SUITE.erl +++ b/apps/emqx/test/emqx_SUITE.erl @@ -27,6 +27,7 @@ all() -> emqx_ct:all(?MODULE). init_per_suite(Config) -> emqx_ct_helpers:start_apps([]), + ct:pal("------------config: ~p", [emqx_config:get()]), Config. end_per_suite(_Config) -> diff --git a/apps/emqx/test/emqx_access_control_SUITE.erl b/apps/emqx/test/emqx_access_control_SUITE.erl index cb5e69362..f5bf35acf 100644 --- a/apps/emqx/test/emqx_access_control_SUITE.erl +++ b/apps/emqx/test/emqx_access_control_SUITE.erl @@ -39,25 +39,14 @@ t_authorize(_) -> Publish = ?PUBLISH_PACKET(?QOS_0, <<"t">>, 1, <<"payload">>), ?assertEqual(allow, emqx_access_control:authorize(clientinfo(), Publish, <<"t">>)). -t_bypass_auth_plugins(_) -> - ClientInfo = clientinfo(), - emqx_zone:set_env(bypass_zone, bypass_auth_plugins, true), - emqx:hook('client.authenticate',{?MODULE, auth_fun, []}), - ?assertMatch(ok, emqx_access_control:authenticate(ClientInfo#{zone => bypass_zone})), - ?assertMatch({error, bad_username_or_password}, emqx_access_control:authenticate(ClientInfo)). - %%-------------------------------------------------------------------- %% Helper functions %%-------------------------------------------------------------------- -auth_fun(#{zone := bypass_zone}, _) -> - {stop, ok}; -auth_fun(#{zone := _}, _) -> - {stop, {error, bad_username_or_password}}. - clientinfo() -> clientinfo(#{}). clientinfo(InitProps) -> - maps:merge(#{zone => zone, + maps:merge(#{zone => default, + listener => mqtt_tcp, protocol => mqtt, peerhost => {127,0,0,1}, clientid => <<"clientid">>, @@ -67,3 +56,6 @@ clientinfo(InitProps) -> peercert => undefined, mountpoint => undefined }, InitProps). + +toggle_auth(Bool) when is_boolean(Bool) -> + emqx_config:put_listener_conf(default, mqtt_tcp, [auth, enable], Bool). diff --git a/apps/emqx/test/emqx_acl_cache_SUITE.erl b/apps/emqx/test/emqx_acl_cache_SUITE.erl index be7c29055..f631f18cb 100644 --- a/apps/emqx/test/emqx_acl_cache_SUITE.erl +++ b/apps/emqx/test/emqx_acl_cache_SUITE.erl @@ -26,6 +26,7 @@ all() -> emqx_ct:all(?MODULE). init_per_suite(Config) -> emqx_ct_helpers:boot_modules(all), emqx_ct_helpers:start_apps([]), + toggle_acl(true), Config. end_per_suite(_Config) -> @@ -55,7 +56,6 @@ t_clean_acl_cache(_) -> ?assertEqual(0, length(gen_server:call(ClientPid, list_acl_cache))), emqtt:stop(Client). - t_drain_acl_cache(_) -> {ok, Client} = emqtt:start_link([{clientid, <<"emqx_c">>}]), {ok, _} = emqtt:connect(Client), @@ -79,70 +79,5 @@ t_drain_acl_cache(_) -> ?assert(length(gen_server:call(ClientPid, list_acl_cache)) > 0), emqtt:stop(Client). -% optimize?? -t_reload_aclfile_and_cleanall(_Config) -> - - RasieMsg = fun() -> Self = self(), #{puback => fun(Msg) -> Self ! {puback, Msg} end, - disconnected => fun(_) -> ok end, - publish => fun(_) -> ok end } end, - - {ok, Client} = emqtt:start_link([{clientid, <<"emqx_c">>}, {proto_ver, v5}, - {msg_handler, RasieMsg()}]), - {ok, _} = emqtt:connect(Client), - - {ok, PktId} = emqtt:publish(Client, <<"t1">>, <<"{\"x\":1}">>, qos1), - - %% Success publish to broker - receive - {puback, #{packet_id := PktId, reason_code := Rc}} -> - ?assertEqual(16#10, Rc); - _ -> - ?assert(false) - end, - - %% Check acl cache list - [ClientPid] = emqx_cm:lookup_channels(<<"emqx_c">>), - ?assert(length(gen_server:call(ClientPid, list_acl_cache)) > 0), - emqtt:stop(Client). - -%% @private -testdir(DataPath) -> - Ls = filename:split(DataPath), - filename:join(lists:sublist(Ls, 1, length(Ls) - 1)). - -% t_cache_k(_) -> -% error('TODO'). - -% t_cache_v(_) -> -% error('TODO'). - -% t_cleanup_acl_cache(_) -> -% error('TODO'). - -% t_get_oldest_key(_) -> -% error('TODO'). - -% t_get_newest_key(_) -> -% error('TODO'). - -% t_get_cache_max_size(_) -> -% error('TODO'). - -% t_get_cache_size(_) -> -% error('TODO'). - -% t_dump_acl_cache(_) -> -% error('TODO'). - -% t_empty_acl_cache(_) -> -% error('TODO'). - -% t_put_acl_cache(_) -> -% error('TODO'). - -% t_get_acl_cache(_) -> -% error('TODO'). - -% t_is_enabled(_) -> -% error('TODO'). - +toggle_acl(Bool) when is_boolean(Bool) -> + emqx_config:put_listener_conf(default, mqtt_tcp, [acl, enable], Bool). diff --git a/apps/emqx/test/emqx_alarm_SUITE.erl b/apps/emqx/test/emqx_alarm_SUITE.erl index db6cdfe7f..e21dff30a 100644 --- a/apps/emqx/test/emqx_alarm_SUITE.erl +++ b/apps/emqx/test/emqx_alarm_SUITE.erl @@ -27,27 +27,17 @@ all() -> emqx_ct:all(?MODULE). init_per_testcase(t_size_limit, Config) -> emqx_ct_helpers:boot_modules(all), - emqx_ct_helpers:start_apps([], - fun(emqx) -> - application:set_env(emqx, alarm, [{actions, [log,publish]}, - {size_limit, 2}, - {validity_period, 3600}]), - ok; - (_) -> - ok - end), + emqx_ct_helpers:start_apps([]), + emqx_config:update_config([alarm], #{ + <<"size_limit">> => 2 + }), Config; init_per_testcase(t_validity_period, Config) -> emqx_ct_helpers:boot_modules(all), - emqx_ct_helpers:start_apps([], - fun(emqx) -> - application:set_env(emqx, alarm, [{actions, [log,publish]}, - {size_limit, 1000}, - {validity_period, 1}]), - ok; - (_) -> - ok - end), + emqx_ct_helpers:start_apps([]), + emqx_config:update_config([alarm], #{ + <<"validity_period">> => <<"1s">> + }), Config; init_per_testcase(_, Config) -> emqx_ct_helpers:boot_modules(all), @@ -89,7 +79,7 @@ t_size_limit(_) -> ok = emqx_alarm:activate(b), ok = emqx_alarm:deactivate(b), ?assertNotEqual({error, not_found}, get_alarm(a, emqx_alarm:get_alarms(deactivated))), - ?assertNotEqual({error, not_found}, get_alarm(a, emqx_alarm:get_alarms(deactivated))), + ?assertNotEqual({error, not_found}, get_alarm(b, emqx_alarm:get_alarms(deactivated))), ok = emqx_alarm:activate(c), ok = emqx_alarm:deactivate(c), ?assertNotEqual({error, not_found}, get_alarm(c, emqx_alarm:get_alarms(deactivated))), diff --git a/apps/emqx/test/emqx_broker_SUITE.erl b/apps/emqx/test/emqx_broker_SUITE.erl index d6fa36c18..fe754e9df 100644 --- a/apps/emqx/test/emqx_broker_SUITE.erl +++ b/apps/emqx/test/emqx_broker_SUITE.erl @@ -42,19 +42,19 @@ end_per_suite(_Config) -> %%-------------------------------------------------------------------- t_stats_fun(_) -> - ?assertEqual(0, emqx_stats:getstat('subscribers.count')), - ?assertEqual(0, emqx_stats:getstat('subscriptions.count')), - ?assertEqual(0, emqx_stats:getstat('suboptions.count')), + Subscribers = emqx_stats:getstat('subscribers.count'), + Subscriptions = emqx_stats:getstat('subscriptions.count'), + Subopts = emqx_stats:getstat('suboptions.count'), ok = emqx_broker:subscribe(<<"topic">>, <<"clientid">>), ok = emqx_broker:subscribe(<<"topic2">>, <<"clientid">>), emqx_broker:stats_fun(), ct:sleep(10), - ?assertEqual(2, emqx_stats:getstat('subscribers.count')), - ?assertEqual(2, emqx_stats:getstat('subscribers.max')), - ?assertEqual(2, emqx_stats:getstat('subscriptions.count')), - ?assertEqual(2, emqx_stats:getstat('subscriptions.max')), - ?assertEqual(2, emqx_stats:getstat('suboptions.count')), - ?assertEqual(2, emqx_stats:getstat('suboptions.max')). + ?assertEqual(Subscribers + 2, emqx_stats:getstat('subscribers.count')), + ?assertEqual(Subscribers + 2, emqx_stats:getstat('subscribers.max')), + ?assertEqual(Subscriptions + 2, emqx_stats:getstat('subscriptions.count')), + ?assertEqual(Subscriptions + 2, emqx_stats:getstat('subscriptions.max')), + ?assertEqual(Subopts + 2, emqx_stats:getstat('suboptions.count')), + ?assertEqual(Subopts + 2, emqx_stats:getstat('suboptions.max')). t_subscribed(_) -> emqx_broker:subscribe(<<"topic">>), diff --git a/apps/emqx/test/emqx_channel_SUITE.erl b/apps/emqx/test/emqx_channel_SUITE.erl index 22f97ffd7..87b93a8e7 100644 --- a/apps/emqx/test/emqx_channel_SUITE.erl +++ b/apps/emqx/test/emqx_channel_SUITE.erl @@ -24,7 +24,152 @@ -include_lib("eunit/include/eunit.hrl"). -all() -> emqx_ct:all(?MODULE). +all() -> + emqx_ct:all(?MODULE). + +mqtt_conf() -> + #{await_rel_timeout => 300, + idle_timeout => 15000, + ignore_loop_deliver => false, + keepalive_backoff => 0.75, + max_awaiting_rel => 100, + max_clientid_len => 65535, + max_inflight => 32, + max_mqueue_len => 1000, + max_packet_size => 1048576, + max_qos_allowed => 2, + max_subscriptions => infinity, + max_topic_alias => 65535, + max_topic_levels => 65535, + mountpoint => <<>>, + mqueue_default_priority => lowest, + mqueue_priorities => #{}, + mqueue_store_qos0 => true, + peer_cert_as_clientid => disabled, + peer_cert_as_username => disabled, + response_information => [], + retain_available => true, + retry_interval => 30, + server_keepalive => disabled, + session_expiry_interval => 7200, + shared_subscription => true, + strict_mode => false, + upgrade_qos => false, + use_username_as_clientid => false, + wildcard_subscription => true}. + +listener_mqtt_tcp_conf() -> + #{acceptors => 16, + access_rules => ["allow all"], + bind => {{0,0,0,0},1883}, + max_connections => 1024000, + proxy_protocol => false, + proxy_protocol_timeout => 3000, + rate_limit => + #{conn_bytes_in => + ["100KB","10s"], + conn_messages_in => + ["100","10s"], + max_conn_rate => 1000, + quota => + #{conn_messages_routing => infinity, + overall_messages_routing => infinity}}, + tcp => + #{active_n => 100, + backlog => 1024, + buffer => 4096, + high_watermark => 1048576, + send_timeout => 15000, + send_timeout_close => + true}, + type => tcp}. + +listener_mqtt_ws_conf() -> + #{acceptors => 16, + access_rules => ["allow all"], + bind => {{0,0,0,0},8083}, + max_connections => 1024000, + proxy_protocol => false, + proxy_protocol_timeout => 3000, + rate_limit => + #{conn_bytes_in => + ["100KB","10s"], + conn_messages_in => + ["100","10s"], + max_conn_rate => 1000, + quota => + #{conn_messages_routing => infinity, + overall_messages_routing => infinity}}, + tcp => + #{active_n => 100, + backlog => 1024, + buffer => 4096, + high_watermark => 1048576, + send_timeout => 15000, + send_timeout_close => + true}, + type => ws, + websocket => + #{allow_origin_absence => + true, + check_origin_enable => + false, + check_origins => [], + compress => false, + deflate_opts => + #{client_max_window_bits => + 15, + mem_level => 8, + server_max_window_bits => + 15}, + fail_if_no_subprotocol => + true, + idle_timeout => 86400000, + max_frame_size => infinity, + mqtt_path => "/mqtt", + mqtt_piggyback => multiple, + proxy_address_header => + "x-forwarded-for", + proxy_port_header => + "x-forwarded-port", + supported_subprotocols => + ["mqtt","mqtt-v3", + "mqtt-v3.1.1", + "mqtt-v5"]}}. + +default_zone_conf() -> + #{zones => + #{default => + #{ acl => #{ + cache => #{enable => true,max_size => 32, ttl => 60000}, + deny_action => ignore, + enable => false + }, + auth => #{enable => false}, + overall_max_connections => infinity, + stats => #{enable => true}, + conn_congestion => + #{enable_alarm => true, min_alarm_sustain_duration => 60000}, + flapping_detect => + #{ban_time => 300000,enable => false, + max_count => 15,window_time => 60000}, + force_gc => + #{bytes => 16777216,count => 16000, + enable => true}, + force_shutdown => + #{enable => true, + max_heap_size => 4194304, + max_message_queue_len => 1000}, + mqtt => mqtt_conf(), + listeners => + #{mqtt_tcp => listener_mqtt_tcp_conf(), + mqtt_ws => listener_mqtt_ws_conf()} + } + } + }. + +set_default_zone_conf() -> + emqx_config:put(default_zone_conf()). %%-------------------------------------------------------------------- %% CT Callbacks @@ -50,6 +195,9 @@ init_per_suite(Config) -> ok = meck:new(emqx_metrics, [passthrough, no_history, no_link]), ok = meck:expect(emqx_metrics, inc, fun(_) -> ok end), ok = meck:expect(emqx_metrics, inc, fun(_, _) -> ok end), + %% Ban + meck:new(emqx_banned, [passthrough, no_history, no_link]), + ok = meck:expect(emqx_banned, check, fun(_ConnInfo) -> false end), Config. end_per_suite(_Config) -> @@ -58,15 +206,15 @@ end_per_suite(_Config) -> emqx_session, emqx_broker, emqx_hooks, - emqx_cm + emqx_cm, + emqx_banned ]). init_per_testcase(_TestCase, Config) -> - meck:new(emqx_zone, [passthrough, no_history, no_link]), + set_default_zone_conf(), Config. end_per_testcase(_TestCase, Config) -> - meck:unload([emqx_zone]), Config. %%-------------------------------------------------------------------- @@ -83,7 +231,7 @@ t_chan_caps(_) -> #{max_clientid_len := 65535, max_qos_allowed := 2, max_topic_alias := 65535, - max_topic_levels := 0, + max_topic_levels := 65535, retain_available := true, shared_subscription := true, subscription_identifiers := true, @@ -250,7 +398,7 @@ t_bad_receive_maximum(_) -> fun(true, _ClientInfo, _ConnInfo) -> {ok, #{session => session(), present => false}} end), - ok = meck:expect(emqx_zone, response_information, fun(_) -> test end), + emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, response_information], test), C1 = channel(#{conn_state => idle}), {shutdown, protocol_error, _, _} = emqx_channel:handle_in( @@ -263,8 +411,8 @@ t_override_client_receive_maximum(_) -> fun(true, _ClientInfo, _ConnInfo) -> {ok, #{session => session(), present => false}} end), - ok = meck:expect(emqx_zone, response_information, fun(_) -> test end), - ok = meck:expect(emqx_zone, max_inflight, fun(_) -> 0 end), + emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, response_information], test), + emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, max_inflight], 0), C1 = channel(#{conn_state => idle}), ClientCapacity = 2, {ok, [{event, connected}, _ConnAck], C2} = @@ -486,7 +634,7 @@ t_handle_deliver_nl(_) -> Channel = channel(#{clientinfo => ClientInfo, session => Session}), Msg = emqx_message:make(<<"clientid">>, ?QOS_1, <<"t1">>, <<"qos1">>), NMsg = emqx_message:set_flag(nl, Msg), - {ok, Channel} = emqx_channel:handle_deliver([{deliver, <<"t1">>, NMsg}], Channel). + {ok, _} = emqx_channel:handle_deliver([{deliver, <<"t1">>, NMsg}], Channel). %%-------------------------------------------------------------------- %% Test cases for handle_out @@ -515,7 +663,7 @@ t_handle_out_connack_response_information(_) -> fun(true, _ClientInfo, _ConnInfo) -> {ok, #{session => session(), present => false}} end), - ok = meck:expect(emqx_zone, response_information, fun(_) -> test end), + emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, response_information], test), IdleChannel = channel(#{conn_state => idle}), {ok, [{event, connected}, {connack, ?CONNACK_PACKET(?RC_SUCCESS, 0, #{'Response-Information' := test})}], @@ -529,7 +677,7 @@ t_handle_out_connack_not_response_information(_) -> fun(true, _ClientInfo, _ConnInfo) -> {ok, #{session => session(), present => false}} end), - ok = meck:expect(emqx_zone, response_information, fun(_) -> test end), + emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, response_information], test), IdleChannel = channel(#{conn_state => idle}), {ok, [{event, connected}, {connack, ?CONNACK_PACKET(?RC_SUCCESS, 0, AckProps)}], _} = emqx_channel:handle_in( @@ -669,9 +817,6 @@ t_enrich_conninfo(_) -> t_enrich_client(_) -> {ok, _ConnPkt, _Chan} = emqx_channel:enrich_client(connpkt(), channel()). -t_check_banned(_) -> - ok = emqx_channel:check_banned(connpkt(), channel()). - t_auth_connect(_) -> {ok, _, _Chan} = emqx_channel:authenticate(?CONNECT_PACKET(connpkt()), channel()). @@ -718,7 +863,7 @@ t_packing_alias(_) -> channel())). t_check_pub_acl(_) -> - ok = meck:expect(emqx_zone, enable_acl, fun(_) -> true end), + emqx_config:put_listener_conf(default, mqtt_tcp, [acl, enable], true), Publish = ?PUBLISH_PACKET(?QOS_0, <<"t">>, 1, <<"payload">>), ok = emqx_channel:check_pub_acl(Publish, channel()). @@ -728,14 +873,14 @@ t_check_pub_alias(_) -> ok = emqx_channel:check_pub_alias(#mqtt_packet{variable = Publish}, Channel). t_check_sub_acls(_) -> - ok = meck:expect(emqx_zone, enable_acl, fun(_) -> true end), + emqx_config:put_listener_conf(default, mqtt_tcp, [acl, enable], true), TopicFilter = {<<"t">>, ?DEFAULT_SUBOPTS}, [{TopicFilter, 0}] = emqx_channel:check_sub_acls([TopicFilter], channel()). t_enrich_connack_caps(_) -> ok = meck:new(emqx_mqtt_caps, [passthrough, no_history]), ok = meck:expect(emqx_mqtt_caps, get_caps, - fun(_Zone) -> + fun(_Zone, _Listener) -> #{max_packet_size => 1024, max_qos_allowed => ?QOS_2, retain_available => true, @@ -772,7 +917,7 @@ t_ws_cookie_init(_) -> conn_mod => emqx_ws_connection, ws_cookie => WsCookie }, - Channel = emqx_channel:init(ConnInfo, [{zone, zone}]), + Channel = emqx_channel:init(ConnInfo, #{zone => default, listener => mqtt_tcp}), ?assertMatch(#{ws_cookie := WsCookie}, emqx_channel:info(clientinfo, Channel)). %%-------------------------------------------------------------------- @@ -797,7 +942,7 @@ channel(InitFields) -> maps:fold(fun(Field, Value, Channel) -> emqx_channel:set_field(Field, Value, Channel) end, - emqx_channel:init(ConnInfo, [{zone, zone}]), + emqx_channel:init(ConnInfo, #{zone => default, listener => mqtt_tcp}), maps:merge(#{clientinfo => clientinfo(), session => session(), conn_state => connected @@ -805,7 +950,8 @@ channel(InitFields) -> clientinfo() -> clientinfo(#{}). clientinfo(InitProps) -> - maps:merge(#{zone => zone, + maps:merge(#{zone => default, + listener => mqtt_tcp, protocol => mqtt, peerhost => {127,0,0,1}, clientid => <<"clientid">>, @@ -837,7 +983,8 @@ session(InitFields) when is_map(InitFields) -> maps:fold(fun(Field, Value, Session) -> emqx_session:set_field(Field, Value, Session) end, - emqx_session:init(#{zone => channel}, #{receive_maximum => 0}), + emqx_session:init(#{zone => default, listener => mqtt_tcp}, + #{receive_maximum => 0}), InitFields). %% conn: 5/s; overall: 10/s diff --git a/apps/emqx/test/emqx_client_SUITE.erl b/apps/emqx/test/emqx_client_SUITE.erl index 73a92024b..552ad307c 100644 --- a/apps/emqx/test/emqx_client_SUITE.erl +++ b/apps/emqx/test/emqx_client_SUITE.erl @@ -78,17 +78,14 @@ groups() -> init_per_suite(Config) -> emqx_ct_helpers:boot_modules(all), - emqx_ct_helpers:start_apps([], fun set_special_confs/1), + emqx_ct_helpers:start_apps([]), + emqx_config:put_listener_conf(default, mqtt_ssl, [ssl, verify], verify_peer), + emqx_listeners:restart_listener('default:mqtt_ssl'), Config. end_per_suite(_Config) -> emqx_ct_helpers:stop_apps([]). -set_special_confs(emqx) -> - emqx_ct_helpers:change_emqx_opts(ssl_twoway, [{peer_cert_as_username, cn}]); -set_special_confs(_) -> - ok. - %%-------------------------------------------------------------------- %% Test cases for MQTT v3 %%-------------------------------------------------------------------- @@ -104,8 +101,7 @@ t_basic_v4(_Config) -> t_basic([{proto_ver, v4}]). t_cm(_) -> - IdleTimeout = emqx_zone:get_env(external, idle_timeout, 30000), - emqx_zone:set_env(external, idle_timeout, 1000), + emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, idle_timeout], 1000), ClientId = <<"myclient">>, {ok, C} = emqtt:start_link([{clientid, ClientId}]), {ok, _} = emqtt:connect(C), @@ -115,7 +111,7 @@ t_cm(_) -> ct:sleep(1200), Stats = emqx_cm:get_chan_stats(ClientId), ?assertEqual(1, proplists:get_value(subscriptions_cnt, Stats)), - emqx_zone:set_env(external, idle_timeout, IdleTimeout). + emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, idle_timeout], 15000). t_cm_registry(_) -> Info = supervisor:which_children(emqx_cm_sup), @@ -273,15 +269,13 @@ t_basic(_Opts) -> ok = emqtt:disconnect(C). t_username_as_clientid(_) -> - emqx_zone:set_env(external, use_username_as_clientid, true), + emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, use_username_as_clientid], true), Username = <<"usera">>, {ok, C} = emqtt:start_link([{username, Username}]), {ok, _} = emqtt:connect(C), #{clientinfo := #{clientid := Username}} = emqx_cm:get_chan_info(Username), emqtt:disconnect(C). - - t_certcn_as_clientid_default_config_tls(_) -> tls_certcn_as_clientid(default). @@ -329,7 +323,7 @@ tls_certcn_as_clientid(TLSVsn) -> tls_certcn_as_clientid(TLSVsn, RequiredTLSVsn) -> CN = <<"Client">>, - emqx_zone:set_env(external, use_username_as_clientid, true), + emqx_config:put_listener_conf(default, mqtt_ssl, [mqtt, peer_cert_as_clientid], cn), SslConf = emqx_ct_helpers:client_ssl_twoway(TLSVsn), {ok, Client} = emqtt:start_link([{port, 8883}, {ssl, true}, {ssl_opts, SslConf}]), {ok, _} = emqtt:connect(Client), diff --git a/apps/emqx/test/emqx_cm_SUITE.erl b/apps/emqx/test/emqx_cm_SUITE.erl index 3f6950b3b..75d0a899c 100644 --- a/apps/emqx/test/emqx_cm_SUITE.erl +++ b/apps/emqx/test/emqx_cm_SUITE.erl @@ -89,7 +89,7 @@ t_open_session(_) -> ok = meck:expect(emqx_connection, call, fun(_, _) -> ok end), ok = meck:expect(emqx_connection, call, fun(_, _, _) -> ok end), - ClientInfo = #{zone => external, + ClientInfo = #{zone => default, listener => mqtt_tcp, clientid => <<"clientid">>, username => <<"username">>, peerhost => {127,0,0,1}}, @@ -114,7 +114,7 @@ rand_client_id() -> t_open_session_race_condition(_) -> ClientId = rand_client_id(), - ClientInfo = #{zone => external, + ClientInfo = #{zone => default, listener => mqtt_tcp, clientid => ClientId, username => <<"username">>, peerhost => {127,0,0,1}}, diff --git a/apps/emqx/test/emqx_connection_SUITE.erl b/apps/emqx/test/emqx_connection_SUITE.erl index a6b2b614a..3e6281fc0 100644 --- a/apps/emqx/test/emqx_connection_SUITE.erl +++ b/apps/emqx/test/emqx_connection_SUITE.erl @@ -57,6 +57,7 @@ init_per_suite(Config) -> ok = meck:expect(emqx_alarm, deactivate, fun(_) -> ok end), ok = meck:expect(emqx_alarm, deactivate, fun(_, _) -> ok end), + emqx_channel_SUITE:set_default_zone_conf(), Config. end_per_suite(_Config) -> @@ -120,14 +121,13 @@ t_info(_) -> end end), #{sockinfo := SockInfo} = emqx_connection:info(CPid), - ?assertMatch(#{active_n := 100, - peername := {{127,0,0,1},3456}, + ?assertMatch(#{ peername := {{127,0,0,1},3456}, sockname := {{127,0,0,1},1883}, sockstate := idle, socktype := tcp}, SockInfo). t_info_limiter(_) -> - St = st(#{limiter => emqx_limiter:init(external, [])}), + St = st(#{limiter => emqx_limiter:init(default, [])}), ?assertEqual(undefined, emqx_connection:info(limiter, St)). t_stats(_) -> @@ -219,8 +219,10 @@ t_handle_msg_deliver(_) -> t_handle_msg_inet_reply(_) -> ok = meck:expect(emqx_pd, get_counter, fun(_) -> 10 end), - ?assertMatch({ok, _St}, handle_msg({inet_reply, for_testing, ok}, st(#{active_n => 0}))), - ?assertEqual(ok, handle_msg({inet_reply, for_testing, ok}, st(#{active_n => 100}))), + emqx_config:put_listener_conf(default, mqtt_tcp, [tcp, active_n], 0), + ?assertMatch({ok, _St}, handle_msg({inet_reply, for_testing, ok}, st())), + emqx_config:put_listener_conf(default, mqtt_tcp, [tcp, active_n], 100), + ?assertEqual(ok, handle_msg({inet_reply, for_testing, ok}, st())), ?assertMatch({stop, {shutdown, for_testing}, _St}, handle_msg({inet_reply, for_testing, {error, for_testing}}, st())). @@ -331,12 +333,12 @@ t_ensure_rate_limit(_) -> ?assertEqual(undefined, emqx_connection:info(limiter, State)), ok = meck:expect(emqx_limiter, check, - fun(_, _) -> {ok, emqx_limiter:init(external, [])} end), + fun(_, _) -> {ok, emqx_limiter:init(default, [])} end), State1 = emqx_connection:ensure_rate_limit(#{}, st(#{limiter => #{}})), ?assertEqual(undefined, emqx_connection:info(limiter, State1)), ok = meck:expect(emqx_limiter, check, - fun(_, _) -> {pause, 3000, emqx_limiter:init(external, [])} end), + fun(_, _) -> {pause, 3000, emqx_limiter:init(default, [])} end), State2 = emqx_connection:ensure_rate_limit(#{}, st(#{limiter => #{}})), ?assertEqual(undefined, emqx_connection:info(limiter, State2)), ?assertEqual(blocked, emqx_connection:info(sockstate, State2)). @@ -386,8 +388,7 @@ t_start_link_exit_on_activate(_) -> t_get_conn_info(_) -> with_conn(fun(CPid) -> #{sockinfo := SockInfo} = emqx_connection:info(CPid), - ?assertEqual(#{active_n => 100, - peername => {{127,0,0,1},3456}, + ?assertEqual(#{peername => {{127,0,0,1},3456}, sockname => {{127,0,0,1},1883}, sockstate => running, socktype => tcp @@ -397,16 +398,12 @@ t_get_conn_info(_) -> t_oom_shutdown(init, Config) -> ok = snabbkaffe:start_trace(), ok = meck:new(emqx_misc, [non_strict, passthrough, no_history, no_link]), - ok = meck:new(emqx_zone, [non_strict, passthrough, no_history, no_link]), - meck:expect(emqx_zone, oom_policy, - fun(_Zone) -> #{message_queue_len => 10, max_heap_size => 8000000} end), meck:expect(emqx_misc, check_oom, fun(_) -> {shutdown, "fake_oom"} end), Config; t_oom_shutdown('end', _Config) -> snabbkaffe:stop(), meck:unload(emqx_misc), - meck:unload(emqx_zone), ok. t_oom_shutdown(_) -> @@ -455,13 +452,11 @@ exit_on_activate_error(SockErr, Reason) -> with_conn(TestFun) -> with_conn(TestFun, #{trap_exit => false}). -with_conn(TestFun, Options) when is_map(Options) -> - with_conn(TestFun, maps:to_list(Options)); - -with_conn(TestFun, Options) -> - TrapExit = proplists:get_value(trap_exit, Options, false), +with_conn(TestFun, Opts) when is_map(Opts) -> + TrapExit = maps:get(trap_exit, Opts, false), process_flag(trap_exit, TrapExit), - {ok, CPid} = emqx_connection:start_link(emqx_transport, sock, Options), + {ok, CPid} = emqx_connection:start_link(emqx_transport, sock, + maps:merge(Opts, #{zone => default, listener => mqtt_tcp})), TestFun(CPid), TrapExit orelse emqx_connection:stop(CPid), ok. @@ -483,7 +478,8 @@ st() -> st(#{}, #{}). st(InitFields) when is_map(InitFields) -> st(InitFields, #{}). st(InitFields, ChannelFields) when is_map(InitFields) -> - St = emqx_connection:init_state(emqx_transport, sock, [#{zone => external}]), + St = emqx_connection:init_state(emqx_transport, sock, #{zone => default, + listener => mqtt_tcp}), maps:fold(fun(N, V, S) -> emqx_connection:set_field(N, V, S) end, emqx_connection:set_field(channel, channel(ChannelFields), St), InitFields @@ -503,7 +499,8 @@ channel(InitFields) -> receive_maximum => 100, expiry_interval => 0 }, - ClientInfo = #{zone => zone, + ClientInfo = #{zone => default, + listener => mqtt_tcp, protocol => mqtt, peerhost => {127,0,0,1}, clientid => <<"clientid">>, @@ -512,13 +509,13 @@ channel(InitFields) -> peercert => undefined, mountpoint => undefined }, - Session = emqx_session:init(#{zone => external}, + Session = emqx_session:init(#{zone => default, listener => mqtt_tcp}, #{receive_maximum => 0} ), maps:fold(fun(Field, Value, Channel) -> - emqx_channel:set_field(Field, Value, Channel) + emqx_channel:set_field(Field, Value, Channel) end, - emqx_channel:init(ConnInfo, [{zone, zone}]), + emqx_channel:init(ConnInfo, #{zone => default, listener => mqtt_tcp}), maps:merge(#{clientinfo => ClientInfo, session => Session, conn_state => connected diff --git a/apps/emqx/test/emqx_flapping_SUITE.erl b/apps/emqx/test/emqx_flapping_SUITE.erl index 8f069b747..b4318ff64 100644 --- a/apps/emqx/test/emqx_flapping_SUITE.erl +++ b/apps/emqx/test/emqx_flapping_SUITE.erl @@ -25,26 +25,23 @@ all() -> emqx_ct:all(?MODULE). init_per_suite(Config) -> emqx_ct_helpers:boot_modules(all), - emqx_ct_helpers:start_apps([], fun set_special_configs/1), + emqx_ct_helpers:start_apps([]), + emqx_config:put_listener_conf(default, mqtt_tcp, [flapping_detect], + #{max_count => 3, + window_time => 100, % 0.1s + ban_time => 2000 %% 2s + }), Config. -set_special_configs(emqx) -> - emqx_zone:set_env(external, enable_flapping_detect, true), - application:set_env(emqx, flapping_detect_policy, - #{threshold => 3, - duration => 100, - banned_interval => 2 - }); -set_special_configs(_App) -> ok. - end_per_suite(_Config) -> emqx_ct_helpers:stop_apps([]), ekka_mnesia:delete_schema(), %% Clean emqx_banned table ok. t_detect_check(_) -> - ClientInfo = #{zone => external, - clientid => <<"clientid">>, + ClientInfo = #{zone => default, + listener => mqtt_tcp, + clientid => <<"client007">>, peerhost => {127,0,0,1} }, false = emqx_flapping:detect(ClientInfo), @@ -53,6 +50,8 @@ t_detect_check(_) -> false = emqx_banned:check(ClientInfo), true = emqx_flapping:detect(ClientInfo), timer:sleep(50), + ct:pal("the table emqx_banned: ~p, nowsec: ~p", [ets:tab2list(emqx_banned), + erlang:system_time(second)]), true = emqx_banned:check(ClientInfo), timer:sleep(3000), false = emqx_banned:check(ClientInfo), @@ -64,12 +63,13 @@ t_detect_check(_) -> ok = emqx_flapping:stop(). t_expired_detecting(_) -> - ClientInfo = #{zone => external, - clientid => <<"clientid">>, + ClientInfo = #{zone => default, + listener => mqtt_tcp, + clientid => <<"client008">>, peerhost => {127,0,0,1}}, false = emqx_flapping:detect(ClientInfo), - ?assertEqual(true, lists:any(fun({flapping, <<"clientid">>, _, _, _}) -> true; + ?assertEqual(true, lists:any(fun({flapping, <<"client008">>, _, _, _}) -> true; (_) -> false end, ets:tab2list(emqx_flapping))), timer:sleep(200), - ?assertEqual(true, lists:all(fun({flapping, <<"clientid">>, _, _, _}) -> false; + ?assertEqual(true, lists:all(fun({flapping, <<"client008">>, _, _, _}) -> false; (_) -> true end, ets:tab2list(emqx_flapping))). \ No newline at end of file diff --git a/apps/emqx/test/emqx_misc_SUITE.erl b/apps/emqx/test/emqx_misc_SUITE.erl index f933fb498..c3580545a 100644 --- a/apps/emqx/test/emqx_misc_SUITE.erl +++ b/apps/emqx/test/emqx_misc_SUITE.erl @@ -119,8 +119,9 @@ t_index_of(_) -> ?assertEqual(3, emqx_misc:index_of(a, [b, c, a, e, f])). t_check(_) -> - Policy = #{message_queue_len => 10, - max_heap_size => 1024 * 1024 * 8}, + Policy = #{max_message_queue_len => 10, + max_heap_size => 1024 * 1024 * 8, + enable => true}, [self() ! {msg, I} || I <- lists:seq(1, 5)], ?assertEqual(ok, emqx_misc:check_oom(Policy)), [self() ! {msg, I} || I <- lists:seq(1, 6)], diff --git a/apps/emqx/test/emqx_mqtt_SUITE.erl b/apps/emqx/test/emqx_mqtt_SUITE.erl index c86d6334a..42a4e5780 100644 --- a/apps/emqx/test/emqx_mqtt_SUITE.erl +++ b/apps/emqx/test/emqx_mqtt_SUITE.erl @@ -156,6 +156,15 @@ t_async_set_keepalive('end', _Config) -> ok. t_async_set_keepalive(_) -> + case os:type() of + {unix, darwin} -> + %% Mac OSX don't support the feature + ok; + _ -> + do_async_set_keepalive() + end. + +do_async_set_keepalive() -> ClientID = <<"client-tcp-keepalive">>, {ok, Client} = emqtt:start_link([{host, "localhost"}, {proto_ver,v5}, diff --git a/apps/emqx/test/emqx_mqtt_caps_SUITE.erl b/apps/emqx/test/emqx_mqtt_caps_SUITE.erl index d6cd5925b..ac6b71c9f 100644 --- a/apps/emqx/test/emqx_mqtt_caps_SUITE.erl +++ b/apps/emqx/test/emqx_mqtt_caps_SUITE.erl @@ -25,39 +25,36 @@ all() -> emqx_ct:all(?MODULE). t_check_pub(_) -> - PubCaps = #{max_qos_allowed => ?QOS_1, - retain_available => false - }, - emqx_zone:set_env(zone, '$mqtt_pub_caps', PubCaps), + OldConf = emqx_config:get(), + emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, max_qos_allowed], ?QOS_1), + emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, retain_available], false), timer:sleep(50), - ok = emqx_mqtt_caps:check_pub(zone, #{qos => ?QOS_1, - retain => false}), + ok = emqx_mqtt_caps:check_pub(default, mqtt_tcp, #{qos => ?QOS_1, retain => false}), PubFlags1 = #{qos => ?QOS_2, retain => false}, ?assertEqual({error, ?RC_QOS_NOT_SUPPORTED}, - emqx_mqtt_caps:check_pub(zone, PubFlags1)), + emqx_mqtt_caps:check_pub(default, mqtt_tcp, PubFlags1)), PubFlags2 = #{qos => ?QOS_1, retain => true}, ?assertEqual({error, ?RC_RETAIN_NOT_SUPPORTED}, - emqx_mqtt_caps:check_pub(zone, PubFlags2)), - emqx_zone:unset_env(zone, '$mqtt_pub_caps'). + emqx_mqtt_caps:check_pub(default, mqtt_tcp, PubFlags2)), + emqx_config:put(OldConf). t_check_sub(_) -> + OldConf = emqx_config:get(), SubOpts = #{rh => 0, rap => 0, nl => 0, qos => ?QOS_2 }, - SubCaps = #{max_topic_levels => 2, - max_qos_allowed => ?QOS_2, - shared_subscription => false, - wildcard_subscription => false - }, - emqx_zone:set_env(zone, '$mqtt_sub_caps', SubCaps), + emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, max_topic_levels], 2), + emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, max_qos_allowed], ?QOS_1), + emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, shared_subscription], false), + emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, wildcard_subscription], false), timer:sleep(50), - ok = emqx_mqtt_caps:check_sub(zone, <<"topic">>, SubOpts), + ok = emqx_mqtt_caps:check_sub(default, mqtt_tcp, <<"topic">>, SubOpts), ?assertEqual({error, ?RC_TOPIC_FILTER_INVALID}, - emqx_mqtt_caps:check_sub(zone, <<"a/b/c/d">>, SubOpts)), + emqx_mqtt_caps:check_sub(default, mqtt_tcp, <<"a/b/c/d">>, SubOpts)), ?assertEqual({error, ?RC_WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED}, - emqx_mqtt_caps:check_sub(zone, <<"+/#">>, SubOpts)), + emqx_mqtt_caps:check_sub(default, mqtt_tcp, <<"+/#">>, SubOpts)), ?assertEqual({error, ?RC_SHARED_SUBSCRIPTIONS_NOT_SUPPORTED}, - emqx_mqtt_caps:check_sub(zone, <<"topic">>, SubOpts#{share => true})), - emqx_zone:unset_env(zone, '$mqtt_pub_caps'). + emqx_mqtt_caps:check_sub(default, mqtt_tcp, <<"topic">>, SubOpts#{share => true})), + emqx_config:put(OldConf). diff --git a/apps/emqx/test/emqx_mqtt_protocol_v5_SUITE.erl b/apps/emqx/test/emqx_mqtt_protocol_v5_SUITE.erl index 2f3048277..607bee44b 100644 --- a/apps/emqx/test/emqx_mqtt_protocol_v5_SUITE.erl +++ b/apps/emqx/test/emqx_mqtt_protocol_v5_SUITE.erl @@ -217,10 +217,14 @@ t_connect_will_message(Config) -> ok = emqtt:disconnect(Client4). t_batch_subscribe(init, Config) -> + emqx_config:put_listener_conf(default, mqtt_tcp, [acl, enable], true), + emqx_config:put_listener_conf(default, mqtt_quic, [acl, enable], true), ok = meck:new(emqx_access_control, [non_strict, passthrough, no_history, no_link]), meck:expect(emqx_access_control, authorize, fun(_, _, _) -> deny end), Config; t_batch_subscribe('end', _Config) -> + emqx_config:put_listener_conf(default, mqtt_tcp, [acl, enable], false), + emqx_config:put_listener_conf(default, mqtt_quic, [acl, enable], false), meck:unload(emqx_access_control). t_batch_subscribe(Config) -> @@ -284,52 +288,22 @@ t_connect_will_retain(Config) -> t_connect_idle_timeout(_Config) -> IdleTimeout = 2000, - emqx_zone:set_env(external, idle_timeout, IdleTimeout), - + emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, idle_timeout], IdleTimeout), + emqx_config:put_listener_conf(default, mqtt_quic, [mqtt, idle_timeout], IdleTimeout), {ok, Sock} = emqtt_sock:connect({127,0,0,1}, 1883, [], 60000), timer:sleep(IdleTimeout), ?assertMatch({error, closed}, emqtt_sock:recv(Sock,1024)). -t_connect_limit_timeout(init, Config) -> - ok = meck:new(proplists, [non_strict, passthrough, no_history, no_link, unstick]), - meck:expect(proplists, get_value, fun(active_n, _Options, _Default) -> 1; - (Arg1, ARg2, Arg3) -> meck:passthrough([Arg1, ARg2, Arg3]) - end), - Config; -t_connect_limit_timeout('end', _Config) -> - catch meck:unload(proplists). - -t_connect_limit_timeout(Config) -> - ConnFun = ?config(conn_fun, Config), - Topic = nth(1, ?TOPICS), - emqx_zone:set_env(external, publish_limit, {3, 5}), - - {ok, Client} = emqtt:start_link([{proto_ver, v5},{keepalive, 60} | Config]), - {ok, _} = emqtt:ConnFun(Client), - [ClientPid] = emqx_cm:lookup_channels(client_info(clientid, Client)), - - ?assertEqual(undefined, emqx_connection:info(limit_timer, sys:get_state(ClientPid))), - Payload = <<"t_shared_subscriptions_client_terminates_when_qos_eq_2">>, - {ok, 2} = emqtt:publish(Client, Topic, Payload, 1), - {ok, 3} = emqtt:publish(Client, Topic, Payload, 1), - {ok, 4} = emqtt:publish(Client, Topic, Payload, 1), - timer:sleep(250), - ?assert(is_reference(emqx_connection:info(limit_timer, sys:get_state(ClientPid)))), - - ok = emqtt:disconnect(Client), - emqx_zone:set_env(external, publish_limit, undefined), - meck:unload(proplists). - t_connect_emit_stats_timeout(init, Config) -> NewIdleTimeout = 1000, - OldIdleTimeout = emqx_zone:get_env(external, idle_timeout), - emqx_zone:set_env(external, idle_timeout, NewIdleTimeout), + emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, idle_timeout], NewIdleTimeout), + emqx_config:put_listener_conf(default, mqtt_quic, [mqtt, idle_timeout], NewIdleTimeout), ok = snabbkaffe:start_trace(), - [{idle_timeout, NewIdleTimeout}, {old_idle_timeout, OldIdleTimeout} | Config]; -t_connect_emit_stats_timeout('end', Config) -> + [{idle_timeout, NewIdleTimeout} | Config]; +t_connect_emit_stats_timeout('end', _Config) -> snabbkaffe:stop(), - {_, OldIdleTimeout} = lists:keyfind(old_idle_timeout, 1, Config), - emqx_zone:set_env(external, idle_timeout, OldIdleTimeout), + emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, idle_timeout], 15000), + emqx_config:put_listener_conf(default, mqtt_quic, [mqtt, idle_timeout], 15000), ok. t_connect_emit_stats_timeout(Config) -> @@ -497,7 +471,8 @@ t_connack_session_present(Config) -> t_connack_max_qos_allowed(init, Config) -> Config; t_connack_max_qos_allowed('end', _Config) -> - emqx_zone:set_env(external, max_qos_allowed, 2), + emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, max_qos_allowed], 2), + emqx_config:put_listener_conf(default, mqtt_quic, [mqtt, max_qos_allowed], 2), ok. t_connack_max_qos_allowed(Config) -> ConnFun = ?config(conn_fun, Config), @@ -505,9 +480,8 @@ t_connack_max_qos_allowed(Config) -> Topic = nth(1, ?TOPICS), %% max_qos_allowed = 0 - emqx_zone:set_env(external, max_qos_allowed, 0), - persistent_term:erase({emqx_zone, external, '$mqtt_caps'}), - persistent_term:erase({emqx_zone, external, '$mqtt_pub_caps'}), + emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, max_qos_allowed], 0), + emqx_config:put_listener_conf(default, mqtt_quic, [mqtt, max_qos_allowed], 0), {ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]), {ok, Connack1} = emqtt:ConnFun(Client1), @@ -532,9 +506,8 @@ t_connack_max_qos_allowed(Config) -> waiting_client_process_exit(Client2), %% max_qos_allowed = 1 - emqx_zone:set_env(external, max_qos_allowed, 1), - persistent_term:erase({emqx_zone, external, '$mqtt_caps'}), - persistent_term:erase({emqx_zone, external, '$mqtt_pub_caps'}), + emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, max_qos_allowed], 1), + emqx_config:put_listener_conf(default, mqtt_quic, [mqtt, max_qos_allowed], 1), {ok, Client3} = emqtt:start_link([{proto_ver, v5} | Config]), {ok, Connack3} = emqtt:ConnFun(Client3), @@ -559,9 +532,8 @@ t_connack_max_qos_allowed(Config) -> waiting_client_process_exit(Client4), %% max_qos_allowed = 2 - emqx_zone:set_env(external, max_qos_allowed, 2), - persistent_term:erase({emqx_zone, external, '$mqtt_caps'}), - persistent_term:erase({emqx_zone, external, '$mqtt_pub_caps'}), + emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, max_qos_allowed], 2), + emqx_config:put_listener_conf(default, mqtt_quic, [mqtt, max_qos_allowed], 2), {ok, Client5} = emqtt:start_link([{proto_ver, v5} | Config]), {ok, Connack5} = emqtt:ConnFun(Client5), diff --git a/apps/emqx/test/emqx_os_mon_SUITE.erl b/apps/emqx/test/emqx_os_mon_SUITE.erl index f7abd094b..6c9ac51c2 100644 --- a/apps/emqx/test/emqx_os_mon_SUITE.erl +++ b/apps/emqx/test/emqx_os_mon_SUITE.erl @@ -24,46 +24,34 @@ all() -> emqx_ct:all(?MODULE). init_per_suite(Config) -> + emqx_config:put([sysmon, os], #{ + cpu_check_interval => 60,cpu_high_watermark => 0.8, + cpu_low_watermark => 0.6,mem_check_interval => 60, + procmem_high_watermark => 0.05,sysmem_high_watermark => 0.7}), application:ensure_all_started(os_mon), Config. end_per_suite(_Config) -> application:stop(os_mon). -% t_set_mem_check_interval(_) -> -% error('TODO'). - -% t_set_sysmem_high_watermark(_) -> -% error('TODO'). - -% t_set_procmem_high_watermark(_) -> -% error('TODO'). - t_api(_) -> gen_event:swap_handler(alarm_handler, {emqx_alarm_handler, swap}, {alarm_handler, []}), - {ok, _} = emqx_os_mon:start_link([{cpu_check_interval, 1}, - {cpu_high_watermark, 5}, - {cpu_low_watermark, 80}, - {mem_check_interval, 60}, - {sysmem_high_watermark, 70}, - {procmem_high_watermark, 5}]), - ?assertEqual(1, emqx_os_mon:get_cpu_check_interval()), - ?assertEqual(5, emqx_os_mon:get_cpu_high_watermark()), - ?assertEqual(80, emqx_os_mon:get_cpu_low_watermark()), - ?assertEqual(60, emqx_os_mon:get_mem_check_interval()), - ?assertEqual(70, emqx_os_mon:get_sysmem_high_watermark()), - ?assertEqual(5, emqx_os_mon:get_procmem_high_watermark()), - % timer:sleep(2000), - % ?assertEqual(true, lists:keymember(cpu_high_watermark, 1, alarm_handler:get_alarms())), + {ok, _} = emqx_os_mon:start_link(), + + ?assertEqual(60, emqx_os_mon:get_mem_check_interval()), + ?assertEqual(ok, emqx_os_mon:set_mem_check_interval(30)), + ?assertEqual(60, emqx_os_mon:get_mem_check_interval()), + ?assertEqual(ok, emqx_os_mon:set_mem_check_interval(122)), + ?assertEqual(120, emqx_os_mon:get_mem_check_interval()), + + ?assertEqual(70, emqx_os_mon:get_sysmem_high_watermark()), + ?assertEqual(ok, emqx_os_mon:set_sysmem_high_watermark(0.8)), + ?assertEqual(80, emqx_os_mon:get_sysmem_high_watermark()), + + ?assertEqual(5, emqx_os_mon:get_procmem_high_watermark()), + ?assertEqual(ok, emqx_os_mon:set_procmem_high_watermark(0.11)), + ?assertEqual(11, emqx_os_mon:get_procmem_high_watermark()), - emqx_os_mon:set_cpu_check_interval(0.05), - emqx_os_mon:set_cpu_high_watermark(80), - emqx_os_mon:set_cpu_low_watermark(75), - ?assertEqual(0.05, emqx_os_mon:get_cpu_check_interval()), - ?assertEqual(80, emqx_os_mon:get_cpu_high_watermark()), - ?assertEqual(75, emqx_os_mon:get_cpu_low_watermark()), - % timer:sleep(3000), - % ?assertEqual(false, lists:keymember(cpu_high_watermark, 1, alarm_handler:get_alarms())), ?assertEqual(ignored, gen_server:call(emqx_os_mon, ignored)), ?assertEqual(ok, gen_server:cast(emqx_os_mon, ignored)), emqx_os_mon ! ignored, diff --git a/apps/emqx/test/emqx_session_SUITE.erl b/apps/emqx/test/emqx_session_SUITE.erl index cb7c10cae..67d06c281 100644 --- a/apps/emqx/test/emqx_session_SUITE.erl +++ b/apps/emqx/test/emqx_session_SUITE.erl @@ -29,6 +29,7 @@ all() -> emqx_ct:all(?MODULE). %%-------------------------------------------------------------------- init_per_suite(Config) -> + emqx_channel_SUITE:set_default_zone_conf(), ok = meck:new([emqx_hooks, emqx_metrics, emqx_broker], [passthrough, no_history, no_link]), ok = meck:expect(emqx_metrics, inc, fun(_) -> ok end), @@ -50,15 +51,16 @@ end_per_testcase(_TestCase, Config) -> %%-------------------------------------------------------------------- t_session_init(_) -> - Session = emqx_session:init(#{zone => zone}, #{receive_maximum => 64}), + Session = emqx_session:init(#{zone => default, listener => mqtt_tcp}, + #{receive_maximum => 64}), ?assertEqual(#{}, emqx_session:info(subscriptions, Session)), ?assertEqual(0, emqx_session:info(subscriptions_cnt, Session)), - ?assertEqual(0, emqx_session:info(subscriptions_max, Session)), + ?assertEqual(infinity, emqx_session:info(subscriptions_max, Session)), ?assertEqual(false, emqx_session:info(upgrade_qos, Session)), ?assertEqual(0, emqx_session:info(inflight_cnt, Session)), ?assertEqual(64, emqx_session:info(inflight_max, Session)), ?assertEqual(1, emqx_session:info(next_pkt_id, Session)), - ?assertEqual(0, emqx_session:info(retry_interval, Session)), + ?assertEqual(30, emqx_session:info(retry_interval, Session)), ?assertEqual(0, emqx_mqueue:len(emqx_session:info(mqueue, Session))), ?assertEqual(0, emqx_session:info(awaiting_rel_cnt, Session)), ?assertEqual(100, emqx_session:info(awaiting_rel_max, Session)), @@ -72,13 +74,13 @@ t_session_init(_) -> t_session_info(_) -> ?assertMatch(#{subscriptions := #{}, upgrade_qos := false, - retry_interval := 0, + retry_interval := 30, await_rel_timeout := 300 }, emqx_session:info(session())). t_session_stats(_) -> Stats = emqx_session:stats(session()), - ?assertMatch(#{subscriptions_max := 0, + ?assertMatch(#{subscriptions_max := infinity, inflight_max := 0, mqueue_len := 0, mqueue_max := 1000, @@ -99,7 +101,7 @@ t_subscribe(_) -> ?assertEqual(1, emqx_session:info(subscriptions_cnt, Session)). t_is_subscriptions_full_false(_) -> - Session = session(#{max_subscriptions => 0}), + Session = session(#{max_subscriptions => infinity}), ?assertNot(emqx_session:is_subscriptions_full(Session)). t_is_subscriptions_full_true(_) -> @@ -152,7 +154,7 @@ t_publish_qos2_with_error_return(_) -> {error, ?RC_RECEIVE_MAXIMUM_EXCEEDED} = emqx_session:publish(3, Msg, Session1). t_is_awaiting_full_false(_) -> - Session = session(#{max_awaiting_rel => 0}), + Session = session(#{max_awaiting_rel => infinity}), ?assertNot(emqx_session:is_awaiting_full(Session)). t_is_awaiting_full_true(_) -> @@ -375,7 +377,8 @@ session(InitFields) when is_map(InitFields) -> maps:fold(fun(Field, Value, Session) -> emqx_session:set_field(Field, Value, Session) end, - emqx_session:init(#{zone => channel}, #{receive_maximum => 0}), + emqx_session:init(#{zone => default, listener => mqtt_tcp}, + #{receive_maximum => 0}), InitFields). diff --git a/apps/emqx/test/emqx_vm_mon_SUITE.erl b/apps/emqx/test/emqx_vm_mon_SUITE.erl index 5f9f4084c..5b39746a1 100644 --- a/apps/emqx/test/emqx_vm_mon_SUITE.erl +++ b/apps/emqx/test/emqx_vm_mon_SUITE.erl @@ -23,17 +23,16 @@ all() -> emqx_ct:all(?MODULE). -init_per_testcase(t_api, Config) -> +init_per_testcase(t_alarms, Config) -> emqx_ct_helpers:boot_modules(all), - emqx_ct_helpers:start_apps([], - fun(emqx) -> - application:set_env(emqx, vm_mon, [{check_interval, 1}, - {process_high_watermark, 80}, - {process_low_watermark, 75}]), - ok; - (_) -> - ok - end), + emqx_ct_helpers:start_apps([]), + emqx_config:put([sysmon, vm], #{ + process_high_watermark => 0, + process_low_watermark => 0, + process_check_interval => 100 %% 1s + }), + ok = supervisor:terminate_child(emqx_sys_sup, emqx_vm_mon), + {ok, _} = supervisor:restart_child(emqx_sys_sup, emqx_vm_mon), Config; init_per_testcase(_, Config) -> emqx_ct_helpers:boot_modules(all), @@ -43,18 +42,12 @@ init_per_testcase(_, Config) -> end_per_testcase(_, _Config) -> emqx_ct_helpers:stop_apps([]). -t_api(_) -> - ?assertEqual(1, emqx_vm_mon:get_check_interval()), - ?assertEqual(80, emqx_vm_mon:get_process_high_watermark()), - ?assertEqual(75, emqx_vm_mon:get_process_low_watermark()), - emqx_vm_mon:set_process_high_watermark(0), - emqx_vm_mon:set_process_low_watermark(60), - ?assertEqual(0, emqx_vm_mon:get_process_high_watermark()), - ?assertEqual(60, emqx_vm_mon:get_process_low_watermark()), - timer:sleep(emqx_vm_mon:get_check_interval() * 1000 * 2), +t_alarms(_) -> + timer:sleep(500), ?assert(is_existing(too_many_processes, emqx_alarm:get_alarms(activated))), - emqx_vm_mon:set_process_high_watermark(70), - timer:sleep(emqx_vm_mon:get_check_interval() * 1000 * 2), + emqx_config:put([sysmon, vm, process_high_watermark], 70), + emqx_config:put([sysmon, vm, process_low_watermark], 60), + timer:sleep(500), ?assertNot(is_existing(too_many_processes, emqx_alarm:get_alarms(activated))). is_existing(Name, [#{name := Name} | _More]) -> diff --git a/apps/emqx/test/emqx_ws_connection_SUITE.erl b/apps/emqx/test/emqx_ws_connection_SUITE.erl index 93c192b86..a00442de6 100644 --- a/apps/emqx/test/emqx_ws_connection_SUITE.erl +++ b/apps/emqx/test/emqx_ws_connection_SUITE.erl @@ -48,6 +48,7 @@ init_per_testcase(TestCase, Config) when TestCase =/= t_ws_pingreq_before_connected, TestCase =/= t_ws_non_check_origin -> + emqx_channel_SUITE:set_default_zone_conf(), %% Mock cowboy_req ok = meck:new(cowboy_req, [passthrough, no_history, no_link]), ok = meck:expect(cowboy_req, header, fun(_, _, _) -> <<>> end), @@ -55,13 +56,6 @@ init_per_testcase(TestCase, Config) when ok = meck:expect(cowboy_req, sock, fun(_) -> {{127,0,0,1}, 18083} end), ok = meck:expect(cowboy_req, cert, fun(_) -> undefined end), ok = meck:expect(cowboy_req, parse_cookies, fun(_) -> error(badarg) end), - %% Mock emqx_zone - ok = meck:new(emqx_zone, [passthrough, no_history, no_link]), - ok = meck:expect(emqx_zone, oom_policy, - fun(_) -> #{max_heap_size => 838860800, - message_queue_len => 8000 - } - end), %% Mock emqx_access_control ok = meck:new(emqx_access_control, [passthrough, no_history, no_link]), ok = meck:expect(emqx_access_control, authorize, fun(_, _, _) -> allow end), @@ -85,6 +79,7 @@ init_per_testcase(TestCase, Config) when Config; init_per_testcase(_, Config) -> + ok = emqx_ct_helpers:start_apps([]), Config. end_per_testcase(TestCase, _Config) when @@ -96,7 +91,6 @@ end_per_testcase(TestCase, _Config) when -> lists:foreach(fun meck:unload/1, [cowboy_req, - emqx_zone, emqx_access_control, emqx_broker, emqx_hooks, @@ -104,6 +98,7 @@ end_per_testcase(TestCase, _Config) when ]); end_per_testcase(_, Config) -> + emqx_ct_helpers:stop_apps([]), Config. %%-------------------------------------------------------------------- @@ -118,18 +113,21 @@ t_info(_) -> end), #{sockinfo := SockInfo} = ?ws_conn:call(WsPid, info), #{socktype := ws, - active_n := 100, peername := {{127,0,0,1}, 3456}, sockname := {{127,0,0,1}, 18083}, sockstate := running } = SockInfo. +set_ws_opts(Key, Val) -> + emqx_config:put_listener_conf(default, mqtt_ws, [websocket, Key], Val). + t_header(_) -> - ok = meck:expect(cowboy_req, header, fun(<<"x-forwarded-for">>, _, _) -> <<"100.100.100.100, 99.99.99.99">>; - (<<"x-forwarded-port">>, _, _) -> <<"1000">> end), - {ok, St, _} = ?ws_conn:websocket_init([req, [{zone, external}, - {proxy_address_header, <<"x-forwarded-for">>}, - {proxy_port_header, <<"x-forwarded-port">>}]]), + ok = meck:expect(cowboy_req, header, + fun(<<"x-forwarded-for">>, _, _) -> <<"100.100.100.100, 99.99.99.99">>; + (<<"x-forwarded-port">>, _, _) -> <<"1000">> end), + set_ws_opts(proxy_address_header, <<"x-forwarded-for">>), + set_ws_opts(proxy_port_header, <<"x-forwarded-port">>), + {ok, St, _} = ?ws_conn:websocket_init([req, #{zone => default, listener => mqtt_ws}]), WsPid = spawn(fun() -> receive {call, From, info} -> gen_server:reply(From, ?ws_conn:info(St)) @@ -175,12 +173,10 @@ t_call(_) -> ?assertEqual(Info, ?ws_conn:call(WsPid, info)). t_ws_pingreq_before_connected(_) -> - ok = emqx_ct_helpers:start_apps([]), {ok, _} = application:ensure_all_started(gun), {ok, WPID} = gun:open("127.0.0.1", 8083), ws_pingreq(#{}), - gun:close(WPID), - emqx_ct_helpers:stop_apps([]). + gun:close(WPID). ws_pingreq(State) -> receive @@ -209,14 +205,11 @@ ws_pingreq(State) -> end. t_ws_sub_protocols_mqtt(_) -> - ok = emqx_ct_helpers:start_apps([]), {ok, _} = application:ensure_all_started(gun), ?assertMatch({gun_upgrade, _}, - start_ws_client(#{protocols => [<<"mqtt">>]})), - emqx_ct_helpers:stop_apps([]). + start_ws_client(#{protocols => [<<"mqtt">>]})). t_ws_sub_protocols_mqtt_equivalents(_) -> - ok = emqx_ct_helpers:start_apps([]), {ok, _} = application:ensure_all_started(gun), %% also support mqtt-v3, mqtt-v3.1.1, mqtt-v5 ?assertMatch({gun_upgrade, _}, @@ -226,58 +219,39 @@ t_ws_sub_protocols_mqtt_equivalents(_) -> ?assertMatch({gun_upgrade, _}, start_ws_client(#{protocols => [<<"mqtt-v5">>]})), ?assertMatch({gun_response, {_, 400, _}}, - start_ws_client(#{protocols => [<<"not-mqtt">>]})), - emqx_ct_helpers:stop_apps([]). + start_ws_client(#{protocols => [<<"not-mqtt">>]})). t_ws_check_origin(_) -> - emqx_ct_helpers:start_apps([], - fun(emqx) -> - {ok, Listeners} = application:get_env(emqx, listeners), - NListeners = lists:map(fun(#{listen_on := 8083, opts := Opts} = Listener) -> - NOpts = proplists:delete(check_origin_enable, Opts), - Listener#{opts => [{check_origin_enable, true} | NOpts]}; - (Listener) -> - Listener - end, Listeners), - application:set_env(emqx, listeners, NListeners), - ok; - (_) -> ok - end), + emqx_config:put_listener_conf(default, mqtt_ws, [websocket, check_origin_enable], true), + emqx_config:put_listener_conf(default, mqtt_ws, [websocket, check_origins], + [<<"http://localhost:18083">>]), {ok, _} = application:ensure_all_started(gun), ?assertMatch({gun_upgrade, _}, start_ws_client(#{protocols => [<<"mqtt">>], headers => [{<<"origin">>, <<"http://localhost:18083">>}]})), ?assertMatch({gun_response, {_, 500, _}}, start_ws_client(#{protocols => [<<"mqtt">>], - headers => [{<<"origin">>, <<"http://localhost:18080">>}]})), - emqx_ct_helpers:stop_apps([]). + headers => [{<<"origin">>, <<"http://localhost:18080">>}]})). t_ws_non_check_origin(_) -> - emqx_ct_helpers:start_apps([]), + emqx_config:put_listener_conf(default, mqtt_ws, [websocket, check_origin_enable], false), + emqx_config:put_listener_conf(default, mqtt_ws, [websocket, check_origins], []), {ok, _} = application:ensure_all_started(gun), ?assertMatch({gun_upgrade, _}, start_ws_client(#{protocols => [<<"mqtt">>], headers => [{<<"origin">>, <<"http://localhost:18083">>}]})), ?assertMatch({gun_upgrade, _}, start_ws_client(#{protocols => [<<"mqtt">>], - headers => [{<<"origin">>, <<"http://localhost:18080">>}]})), - emqx_ct_helpers:stop_apps([]). - + headers => [{<<"origin">>, <<"http://localhost:18080">>}]})). t_init(_) -> - Opts = [{idle_timeout, 300000}, - {fail_if_no_subprotocol, false}, - {supported_subprotocols, ["mqtt"]}], - WsOpts = #{compress => false, - deflate_opts => #{}, - max_frame_size => infinity, - idle_timeout => 300000 - }, + Opts = #{listener => mqtt_ws, zone => default}, ok = meck:expect(cowboy_req, parse_header, fun(_, req) -> undefined end), - {cowboy_websocket, req, [req, Opts], WsOpts} = ?ws_conn:init(req, Opts), + ok = meck:expect(cowboy_req, reply, fun(_, Req) -> Req end), + {ok, req, _} = ?ws_conn:init(req, Opts), ok = meck:expect(cowboy_req, parse_header, fun(_, req) -> [<<"mqtt">>] end), ok = meck:expect(cowboy_req, set_resp_header, fun(_, <<"mqtt">>, req) -> resp end), - {cowboy_websocket, resp, [req, Opts], WsOpts} = ?ws_conn:init(req, Opts). + {cowboy_websocket, resp, [req, Opts], _} = ?ws_conn:init(req, Opts). t_websocket_handle_binary(_) -> {ok, _} = websocket_handle({binary, <<>>}, st()), @@ -450,15 +424,6 @@ t_run_gc(_) -> WsSt = st(#{gc_state => GcSt}), ?ws_conn:run_gc(#{cnt => 100, oct => 10000}, WsSt). -t_check_oom(_) -> - %%Policy = #{max_heap_size => 10, message_queue_len => 10}, - %%meck:expect(emqx_zone, oom_policy, fun(_) -> Policy end), - _St = ?ws_conn:check_oom(st()), - ok = timer:sleep(10). - %%receive {shutdown, proc_heap_too_large} -> ok - %%after 0 -> error(expect_shutdown) - %%end. - t_enqueue(_) -> Packet = ?PUBLISH_PACKET(?QOS_0), St = ?ws_conn:enqueue(Packet, st()), @@ -473,7 +438,7 @@ t_shutdown(_) -> st() -> st(#{}). st(InitFields) when is_map(InitFields) -> - {ok, St, _} = ?ws_conn:websocket_init([req, [{zone, external}]]), + {ok, St, _} = ?ws_conn:websocket_init([req, #{zone => default, listener => mqtt_ws}]), maps:fold(fun(N, V, S) -> ?ws_conn:set_field(N, V, S) end, ?ws_conn:set_field(channel, channel(), St), InitFields @@ -493,7 +458,8 @@ channel(InitFields) -> receive_maximum => 100, expiry_interval => 0 }, - ClientInfo = #{zone => zone, + ClientInfo = #{zone => default, + listener => mqtt_ws, protocol => mqtt, peerhost => {127,0,0,1}, clientid => <<"clientid">>, @@ -502,13 +468,13 @@ channel(InitFields) -> peercert => undefined, mountpoint => undefined }, - Session = emqx_session:init(#{zone => external}, + Session = emqx_session:init(#{zone => default, listener => mqtt_ws}, #{receive_maximum => 0} ), maps:fold(fun(Field, Value, Channel) -> emqx_channel:set_field(Field, Value, Channel) end, - emqx_channel:init(ConnInfo, [{zone, zone}]), + emqx_channel:init(ConnInfo, #{zone => default, listener => mqtt_ws}), maps:merge(#{clientinfo => ClientInfo, session => Session, conn_state => connected diff --git a/apps/emqx/test/props/prop_emqx_sys.erl b/apps/emqx/test/props/prop_emqx_sys.erl index 67718ec37..170611061 100644 --- a/apps/emqx/test/props/prop_emqx_sys.erl +++ b/apps/emqx/test/props/prop_emqx_sys.erl @@ -59,6 +59,8 @@ prop_sys() -> do_setup() -> ok = emqx_logger:set_log_level(emergency), + emqx_config:put([broker, sys_msg_interval], 60000), + emqx_config:put([broker, sys_heartbeat_interval], 30000), [mock(Mod) || Mod <- ?mock_modules], ok. @@ -98,8 +100,6 @@ command(_State) -> {call, emqx_sys, uptime, []}, {call, emqx_sys, datetime, []}, {call, emqx_sys, sysdescr, []}, - {call, emqx_sys, sys_interval, []}, - {call, emqx_sys, sys_heatbeat_interval, []}, %------------ unexpected message ----------------------% {call, emqx_sys, handle_call, [emqx_sys, other, state]}, {call, emqx_sys, handle_cast, [emqx_sys, other]}, diff --git a/apps/emqx_authn/src/emqx_authn.erl b/apps/emqx_authn/src/emqx_authn.erl index 611b47cc2..731ac31fe 100644 --- a/apps/emqx_authn/src/emqx_authn.erl +++ b/apps/emqx_authn/src/emqx_authn.erl @@ -49,6 +49,7 @@ -export([mnesia/1]). -boot_mnesia({mnesia, [boot]}). +-copy_mnesia({mnesia, [copy]}). -define(CHAIN_TAB, emqx_authn_chain). @@ -69,7 +70,10 @@ mnesia(boot) -> {record_name, chain}, {local_content, true}, {attributes, record_info(fields, chain)}, - {storage_properties, StoreProps}]). + {storage_properties, StoreProps}]); + +mnesia(copy) -> + ok = ekka_mnesia:copy_table(?CHAIN_TAB, ram_copies). enable() -> case emqx:hook('client.authenticate', {?MODULE, authenticate, []}) of diff --git a/apps/emqx_authn/test/emqx_authn_SUITE.erl b/apps/emqx_authn/test/emqx_authn_SUITE.erl index b93e32e3d..1cf607bf2 100644 --- a/apps/emqx_authn/test/emqx_authn_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_SUITE.erl @@ -42,7 +42,7 @@ end_per_suite(_) -> set_special_configs(emqx_authn) -> application:set_env(emqx, plugins_etc_dir, emqx_ct_helpers:deps_path(emqx_authn, "test")), - Conf = #{<<"emqx_authn">> => #{<<"authenticators">> => []}}, + Conf = #{<<"emqx_authn">> => #{<<"authenticators">> => [], <<"enable">> => false}}, ok = file:write_file(filename:join(emqx:get_env(plugins_etc_dir), 'emqx_authn.conf'), jsx:encode(Conf)), ok; set_special_configs(_App) -> diff --git a/apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl b/apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl index 008deca3d..9952e43bd 100644 --- a/apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl @@ -41,7 +41,7 @@ end_per_suite(_) -> set_special_configs(emqx_authn) -> application:set_env(emqx, plugins_etc_dir, emqx_ct_helpers:deps_path(emqx_authn, "test")), - Conf = #{<<"emqx_authn">> => #{<<"authenticators">> => []}}, + Conf = #{<<"emqx_authn">> => #{<<"authenticators">> => [], <<"enable">> => false}}, ok = file:write_file(filename:join(emqx:get_env(plugins_etc_dir), 'emqx_authn.conf'), jsx:encode(Conf)), ok; set_special_configs(_App) -> diff --git a/apps/emqx_authn/test/emqx_authn_mnesia_SUITE.erl b/apps/emqx_authn/test/emqx_authn_mnesia_SUITE.erl index fe7d244cd..5a0fe06b3 100644 --- a/apps/emqx_authn/test/emqx_authn_mnesia_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_mnesia_SUITE.erl @@ -41,7 +41,7 @@ end_per_suite(_) -> set_special_configs(emqx_authn) -> application:set_env(emqx, plugins_etc_dir, emqx_ct_helpers:deps_path(emqx_authn, "test")), - Conf = #{<<"emqx_authn">> => #{<<"authenticators">> => []}}, + Conf = #{<<"emqx_authn">> => #{<<"authenticators">> => [], <<"enable">> => false}}, ok = file:write_file(filename:join(emqx:get_env(plugins_etc_dir), 'emqx_authn.conf'), jsx:encode(Conf)), ok; set_special_configs(_App) -> diff --git a/apps/emqx_authz/etc/emqx_authz.conf b/apps/emqx_authz/etc/emqx_authz.conf index b984a19e6..1e6a3f6be 100644 --- a/apps/emqx_authz/etc/emqx_authz.conf +++ b/apps/emqx_authz/etc/emqx_authz.conf @@ -57,7 +57,7 @@ emqx_authz:{ # type: mongo # config: { # mongo_type: single - # servers: "127.0.0.1:27017" + # server: "127.0.0.1:27017" # pool_size: 1 # database: mqtt # ssl: {enable: false} diff --git a/apps/emqx_authz/src/emqx_authz.erl b/apps/emqx_authz/src/emqx_authz.erl index e4006c6db..784889954 100644 --- a/apps/emqx_authz/src/emqx_authz.erl +++ b/apps/emqx_authz/src/emqx_authz.erl @@ -15,6 +15,7 @@ %%-------------------------------------------------------------------- -module(emqx_authz). +-behaviour(emqx_config_handler). -include("emqx_authz.hrl"). -include_lib("emqx/include/logger.hrl"). @@ -23,33 +24,55 @@ -export([ register_metrics/0 , init/0 - , compile/1 + , init_rule/1 , lookup/0 - , update/1 + , update/2 , authorize/5 , match/4 ]). +-export([post_update_config/2, handle_update_config/2]). + +-define(CONF_KEY_PATH, [emqx_authz, rules]). + -spec(register_metrics() -> ok). register_metrics() -> lists:foreach(fun emqx_metrics:ensure/1, ?AUTHZ_METRICS). init() -> ok = register_metrics(), - Rules = emqx_config:get([emqx_authz, rules], []), - NRules = [compile(Rule) || Rule <- Rules], - ok = emqx_hooks:add('client.authorize', {?MODULE, authorize, [NRules]}, -1). + emqx_config_handler:add_handler(?CONF_KEY_PATH, ?MODULE), + NRules = [init_rule(Rule) || Rule <- lookup()], + ok = emqx_hooks:add('client.authorize', {?MODULE, authorize, [NRules]}, -1). lookup() -> - emqx_config:get([emqx_authz, rules], []). + emqx_config:get(?CONF_KEY_PATH, []). -update(Rules) -> - emqx_config:put([emqx_authz], #{rules => Rules}), - NRules = [compile(Rule) || Rule <- Rules], +update(Cmd, Rules) -> + emqx_config:update_config(?CONF_KEY_PATH, {Cmd, Rules}). + +%% For now we only support re-creating the entire rule list +handle_update_config({head, Rule}, OldConf) when is_map(Rule), is_list(OldConf) -> + [Rule | OldConf]; +handle_update_config({tail, Rule}, OldConf) when is_map(Rule), is_list(OldConf) -> + OldConf ++ [Rule]; +handle_update_config({_, NewConf}, _OldConf) -> + %% overwrite the entire config! + case is_list(NewConf) of + true -> NewConf; + false -> [NewConf] + end. + +post_update_config(undefined, _OldConf) -> + %_ = [release_rules(Rule) || Rule <- OldConf], + ok; +post_update_config(NewRules, _OldConf) -> + %_ = [release_rules(Rule) || Rule <- OldConf], + InitedRules = [init_rule(Rule) || Rule <- NewRules], Action = find_action_in_hooks(), ok = emqx_hooks:del('client.authorize', Action), - ok = emqx_hooks:add('client.authorize', {?MODULE, authorize, [NRules]}, -1), - ok = emqx_acl_cache:empty_acl_cache(). + ok = emqx_hooks:add('client.authorize', {?MODULE, authorize, [InitedRules]}, -1), + ok = emqx_acl_cache:drain_cache(). %%-------------------------------------------------------------------- %% Internal functions @@ -77,35 +100,35 @@ create_resource(#{type := DB, error({load_config_error, Reason}) end. --spec(compile(rule()) -> rule()). -compile(#{topics := Topics, - action := Action, - permission := Permission, - principal := Principal +-spec(init_rule(rule()) -> rule()). +init_rule(#{topics := Topics, + action := Action, + permission := Permission, + principal := Principal } = Rule) when ?ALLOW_DENY(Permission), ?PUBSUB(Action), is_list(Topics) -> NTopics = [compile_topic(Topic) || Topic <- Topics], Rule#{principal => compile_principal(Principal), topics => NTopics }; -compile(#{principal := Principal, - type := http, - config := #{url := Url} = Config - } = Rule) -> +init_rule(#{principal := Principal, + type := http, + config := #{url := Url} = Config + } = Rule) -> NConfig = maps:merge(Config, #{base_url => maps:remove(query, Url)}), NRule = create_resource(Rule#{config := NConfig}), NRule#{principal => compile_principal(Principal)}; -compile(#{principal := Principal, - type := DB +init_rule(#{principal := Principal, + type := DB } = Rule) when DB =:= redis; DB =:= mongo -> NRule = create_resource(Rule), NRule#{principal => compile_principal(Principal)}; -compile(#{principal := Principal, - type := DB, - sql := SQL +init_rule(#{principal := Principal, + type := DB, + sql := SQL } = Rule) when DB =:= mysql; DB =:= pgsql -> Mod = list_to_existing_atom(io_lib:format("~s_~s",[?APP, DB])), diff --git a/apps/emqx_authz/src/emqx_authz_api.erl b/apps/emqx_authz/src/emqx_authz_api.erl index 99ec2841c..974f72dbe 100644 --- a/apps/emqx_authz/src/emqx_authz_api.erl +++ b/apps/emqx_authz/src/emqx_authz_api.erl @@ -56,28 +56,23 @@ lookup_authz(_Bindings, _Params) -> return({ok, emqx_authz:lookup()}). update_authz(_Bindings, Params) -> - Rules = get_rules(Params), - return(emqx_authz:update(Rules)). + Rules = form_rules(Params), + return(emqx_authz:update(replace, Rules)). append_authz(_Bindings, Params) -> - Rules = get_rules(Params), - NRules = lists:append(emqx_authz:lookup(), Rules), - return(emqx_authz:update(NRules)). + Rules = form_rules(Params), + return(emqx_authz:update(tail, Rules)). push_authz(_Bindings, Params) -> - Rules = get_rules(Params), - NRules = lists:append(Rules, emqx_authz:lookup()), - return(emqx_authz:update(NRules)). + Rules = form_rules(Params), + return(emqx_authz:update(head, Rules)). %%------------------------------------------------------------------------------ %% Interval Funcs %%------------------------------------------------------------------------------ -get_rules(Params) -> - {ok, Conf} = hocon:binary(jsx:encode(#{<<"emqx_authz">> => Params}), #{format => richmap}), - CheckConf = hocon_schema:check(emqx_authz_schema, Conf, #{atom_key => true}), - #{emqx_authz := #{rules := Rules}} = hocon_schema:richmap_to_map(CheckConf), - Rules. +form_rules(Params) -> + Params. %%-------------------------------------------------------------------- %% EUnits diff --git a/apps/emqx_authz/src/emqx_authz_mongo.erl b/apps/emqx_authz/src/emqx_authz_mongo.erl index a32054997..c615582d4 100644 --- a/apps/emqx_authz/src/emqx_authz_mongo.erl +++ b/apps/emqx_authz/src/emqx_authz_mongo.erl @@ -71,7 +71,7 @@ match(Client, PubSub, Topic, #{<<"simple_rule">> => Rule}, #{atom_key => true}, [simple_rule]), - case emqx_authz:match(Client, PubSub, Topic, emqx_authz:compile(NRule)) of + case emqx_authz:match(Client, PubSub, Topic, emqx_authz:init_rule(NRule)) of true -> {matched, NPermission}; false -> nomatch end. diff --git a/apps/emqx_authz/src/emqx_authz_mysql.erl b/apps/emqx_authz/src/emqx_authz_mysql.erl index 0ab1418f2..980e9d5c6 100644 --- a/apps/emqx_authz/src/emqx_authz_mysql.erl +++ b/apps/emqx_authz/src/emqx_authz_mysql.erl @@ -90,7 +90,7 @@ match(Client, PubSub, Topic, #{<<"simple_rule">> => Rule}, #{atom_key => true}, [simple_rule]), - case emqx_authz:match(Client, PubSub, Topic, emqx_authz:compile(NRule)) of + case emqx_authz:match(Client, PubSub, Topic, emqx_authz:init_rule(NRule)) of true -> {matched, NPermission}; false -> nomatch end. diff --git a/apps/emqx_authz/src/emqx_authz_pgsql.erl b/apps/emqx_authz/src/emqx_authz_pgsql.erl index c990a29d3..607ba3afa 100644 --- a/apps/emqx_authz/src/emqx_authz_pgsql.erl +++ b/apps/emqx_authz/src/emqx_authz_pgsql.erl @@ -94,7 +94,7 @@ match(Client, PubSub, Topic, #{<<"simple_rule">> => Rule}, #{atom_key => true}, [simple_rule]), - case emqx_authz:match(Client, PubSub, Topic, emqx_authz:compile(NRule)) of + case emqx_authz:match(Client, PubSub, Topic, emqx_authz:init_rule(NRule)) of true -> {matched, NPermission}; false -> nomatch end. diff --git a/apps/emqx_authz/src/emqx_authz_redis.erl b/apps/emqx_authz/src/emqx_authz_redis.erl index 8d24b4534..43e06dd13 100644 --- a/apps/emqx_authz/src/emqx_authz_redis.erl +++ b/apps/emqx_authz/src/emqx_authz_redis.erl @@ -74,7 +74,7 @@ match(Client, PubSub, Topic, #{<<"simple_rule">> => Rule}, #{atom_key => true}, [simple_rule]), - case emqx_authz:match(Client, PubSub, Topic, emqx_authz:compile(NRule)) of + case emqx_authz:match(Client, PubSub, Topic, emqx_authz:init_rule(NRule)) of true -> {matched, allow}; false -> nomatch end. diff --git a/apps/emqx_authz/src/emqx_authz_schema.erl b/apps/emqx_authz/src/emqx_authz_schema.erl index 9554aade4..81e5d056d 100644 --- a/apps/emqx_authz/src/emqx_authz_schema.erl +++ b/apps/emqx_authz/src/emqx_authz_schema.erl @@ -2,10 +2,6 @@ -include_lib("typerefl/include/types.hrl"). --type action() :: publish | subscribe | all. --type permission() :: allow | deny. --type url() :: emqx_http_lib:uri_map(). - -reflect_type([ permission/0 , action/0 , url/0 @@ -13,6 +9,18 @@ -typerefl_from_string({url/0, emqx_http_lib, uri_parse}). +-type action() :: publish | subscribe | all. +-type permission() :: allow | deny. +-type url() :: #{ + scheme := http | https, + host := string(), + port := non_neg_integer(), + path => string(), + query => string(), + fragment => string(), + userinfo => string() +}. + -export([ structs/0 , fields/1 ]). @@ -51,9 +59,8 @@ fields(http_get) -> end } } - , {method, #{type => get, - default => get - }} + , {method, #{type => get, default => get }} + , {request_timeout, #{type => timeout(), default => 30000 }} ] ++ proplists:delete(base_url, emqx_connector_http:fields(config)); fields(http_post) -> [ {url, #{type => url()}} diff --git a/apps/emqx_authz/test/emqx_authz_SUITE.erl b/apps/emqx_authz/test/emqx_authz_SUITE.erl index 66b2e62de..f0f885cda 100644 --- a/apps/emqx_authz/test/emqx_authz_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_SUITE.erl @@ -29,23 +29,16 @@ groups() -> []. init_per_suite(Config) -> - ok = emqx_ct_helpers:start_apps([emqx_authz], fun set_special_configs/1), + ok = emqx_ct_helpers:start_apps([emqx_authz]), + ok = emqx_config:update_config([zones, default, acl, cache, enable], false), + ok = emqx_config:update_config([zones, default, acl, enable], true), + emqx_authz:update(replace, []), Config. end_per_suite(_Config) -> file:delete(filename:join(emqx:get_env(plugins_etc_dir), 'authz.conf')), emqx_ct_helpers:stop_apps([emqx_authz]). -set_special_configs(emqx) -> - application:set_env(emqx, allow_anonymous, true), - application:set_env(emqx, enable_acl_cache, false), - ok; -set_special_configs(emqx_authz) -> - emqx_config:put([emqx_authz], #{rules => []}), - ok; -set_special_configs(_App) -> - ok. - -define(RULE1, #{principal => all, topics => [<<"#">>], action => all, @@ -81,19 +74,19 @@ set_special_configs(_App) -> %%------------------------------------------------------------------------------ %% Testcases %%------------------------------------------------------------------------------ -t_compile(_) -> +t_init_rule(_) -> ?assertEqual(#{permission => deny, action => all, principal => all, topics => [['#']] - },emqx_authz:compile(?RULE1)), + }, emqx_authz:init_rule(?RULE1)), ?assertEqual(#{permission => allow, action => all, principal => #{ipaddress => {{127,0,0,1},{127,0,0,1},32}}, topics => [#{eq => ['#']}, #{eq => ['+']}] - }, emqx_authz:compile(?RULE2)), + }, emqx_authz:init_rule(?RULE2)), ?assertMatch( #{permission := allow, action := publish, @@ -103,7 +96,7 @@ t_compile(_) -> ] }, topics := [[<<"test">>]] - }, emqx_authz:compile(?RULE3)), + }, emqx_authz:init_rule(?RULE3)), ?assertMatch( #{permission := deny, action := publish, @@ -115,31 +108,39 @@ t_compile(_) -> topics := [#{pattern := [<<"%u">>]}, #{pattern := [<<"%c">>]} ] - }, emqx_authz:compile(?RULE4)), + }, emqx_authz:init_rule(?RULE4)), ok. t_authz(_) -> ClientInfo1 = #{clientid => <<"test">>, username => <<"test">>, - peerhost => {127,0,0,1} + peerhost => {127,0,0,1}, + zone => default, + listener => mqtt_tcp }, ClientInfo2 = #{clientid => <<"test">>, username => <<"test">>, - peerhost => {192,168,0,10} + peerhost => {192,168,0,10}, + zone => default, + listener => mqtt_tcp }, ClientInfo3 = #{clientid => <<"test">>, username => <<"fake">>, - peerhost => {127,0,0,1} + peerhost => {127,0,0,1}, + zone => default, + listener => mqtt_tcp }, ClientInfo4 = #{clientid => <<"fake">>, username => <<"test">>, - peerhost => {127,0,0,1} + peerhost => {127,0,0,1}, + zone => default, + listener => mqtt_tcp }, - Rules1 = [emqx_authz:compile(Rule) || Rule <- [?RULE1, ?RULE2]], - Rules2 = [emqx_authz:compile(Rule) || Rule <- [?RULE2, ?RULE1]], - Rules3 = [emqx_authz:compile(Rule) || Rule <- [?RULE3, ?RULE4]], - Rules4 = [emqx_authz:compile(Rule) || Rule <- [?RULE4, ?RULE1]], + Rules1 = [emqx_authz:init_rule(Rule) || Rule <- [?RULE1, ?RULE2]], + Rules2 = [emqx_authz:init_rule(Rule) || Rule <- [?RULE2, ?RULE1]], + Rules3 = [emqx_authz:init_rule(Rule) || Rule <- [?RULE3, ?RULE4]], + Rules4 = [emqx_authz:init_rule(Rule) || Rule <- [?RULE4, ?RULE1]], ?assertEqual({stop, deny}, emqx_authz:authorize(ClientInfo1, subscribe, <<"#">>, deny, [])), diff --git a/apps/emqx_authz/test/emqx_authz_api_SUITE.erl b/apps/emqx_authz/test/emqx_authz_api_SUITE.erl index 789de9fcc..12724a1fe 100644 --- a/apps/emqx_authz/test/emqx_authz_api_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_api_SUITE.erl @@ -18,61 +18,66 @@ -compile(nowarn_export_all). -compile(export_all). --include("emqx_authz.hrl"). --include_lib("eunit/include/eunit.hrl"). --include_lib("common_test/include/ct.hrl"). +% -include("emqx_authz.hrl"). +% -include_lib("eunit/include/eunit.hrl"). +% -include_lib("common_test/include/ct.hrl"). --import(emqx_ct_http, [ request_api/3 - , request_api/5 - , get_http_data/1 - , create_default_app/0 - , delete_default_app/0 - , default_auth_header/0 - ]). +% -import(emqx_ct_http, [ request_api/3 +% , request_api/5 +% , get_http_data/1 +% , create_default_app/0 +% , delete_default_app/0 +% , default_auth_header/0 +% ]). --define(HOST, "http://127.0.0.1:8081/"). --define(API_VERSION, "v4"). --define(BASE_PATH, "api"). +% -define(HOST, "http://127.0.0.1:8081/"). +% -define(API_VERSION, "v4"). +% -define(BASE_PATH, "api"). all() -> %% TODO: V5 API %% emqx_ct:all(?MODULE). - []. + [t_api_unit_test]. groups() -> []. init_per_suite(Config) -> - ok = emqx_ct_helpers:start_apps([emqx_authz, emqx_management], fun set_special_configs/1), - create_default_app(), + %% important! let emqx_schema include the current app! + meck:new(emqx_schema, [non_strict, passthrough, no_history, no_link]), + meck:expect(emqx_schema, includes, fun() -> ["emqx_authz"] end ), + + ok = emqx_ct_helpers:start_apps([emqx_authz]), + %create_default_app(), Config. end_per_suite(_Config) -> - delete_default_app(), + %delete_default_app(), file:delete(filename:join(emqx:get_env(plugins_etc_dir), 'authz.conf')), - emqx_ct_helpers:stop_apps([emqx_authz, emqx_management]). + meck:unload(emqx_schema), + emqx_ct_helpers:stop_apps([emqx_authz]). -set_special_configs(emqx) -> - application:set_env(emqx, allow_anonymous, true), - application:set_env(emqx, enable_acl_cache, false), - ok; -set_special_configs(emqx_authz) -> - emqx_config:put([emqx_authz], #{rules => []}), - ok; +% set_special_configs(emqx) -> +% application:set_env(emqx, allow_anonymous, true), +% application:set_env(emqx, enable_acl_cache, false), +% ok; +% set_special_configs(emqx_authz) -> +% emqx_config:put([emqx_authz], #{rules => []}), +% ok; -set_special_configs(emqx_management) -> - emqx_config:put([emqx_management], #{listeners => [#{protocol => http, port => 8081}], - applications =>[#{id => "admin", secret => "public"}]}), - ok; +% set_special_configs(emqx_management) -> +% emqx_config:put([emqx_management], #{listeners => [#{protocol => http, port => 8081}], +% applications =>[#{id => "admin", secret => "public"}]}), +% ok; -set_special_configs(_App) -> - ok. +% set_special_configs(_App) -> +% ok. -%%------------------------------------------------------------------------------ -%% Testcases -%%------------------------------------------------------------------------------ +% %%------------------------------------------------------------------------------ +% %% Testcases +% %%------------------------------------------------------------------------------ -t_api(_Config) -> +t_api_unit_test(_Config) -> Rule1 = #{<<"principal">> => #{<<"and">> => [#{<<"username">> => <<"^test?">>}, #{<<"clientid">> => <<"^test?">>} @@ -81,53 +86,70 @@ t_api(_Config) -> <<"topics">> => [<<"%u">>], <<"permission">> => <<"allow">> }, - {ok, _} = request_http_rest_add(["authz/push"], #{rules => [Rule1]}), - {ok, Result1} = request_http_rest_lookup(["authz"]), - ?assertMatch([Rule1 | _ ], get_http_data(Result1)), + ok = emqx_authz_api:push_authz(#{}, Rule1), + [#{action := subscribe, + permission := allow, + principal := + #{'and' := [#{username := <<"^test?">>}, + #{clientid := <<"^test?">>}]}, + topics := [<<"%u">>]}] = emqx_config:get([emqx_authz, rules]). - Rule2 = #{<<"principal">> => #{<<"ipaddress">> => <<"127.0.0.1">>}, - <<"action">> => <<"publish">>, - <<"topics">> => [#{<<"eq">> => <<"#">>}, - #{<<"eq">> => <<"+">>} - ], - <<"permission">> => <<"deny">> - }, - {ok, _} = request_http_rest_add(["authz/append"], #{rules => [Rule2]}), - {ok, Result2} = request_http_rest_lookup(["authz"]), - ?assertEqual(Rule2#{<<"principal">> => #{<<"ipaddress">> => "127.0.0.1"}}, - lists:last(get_http_data(Result2))), +% t_api(_Config) -> +% Rule1 = #{<<"principal">> => +% #{<<"and">> => [#{<<"username">> => <<"^test?">>}, +% #{<<"clientid">> => <<"^test?">>} +% ]}, +% <<"action">> => <<"subscribe">>, +% <<"topics">> => [<<"%u">>], +% <<"permission">> => <<"allow">> +% }, +% {ok, _} = request_http_rest_add(["authz/push"], #{rules => [Rule1]}), +% {ok, Result1} = request_http_rest_lookup(["authz"]), +% ?assertMatch([Rule1 | _ ], get_http_data(Result1)), - {ok, _} = request_http_rest_update(["authz"], #{rules => []}), - {ok, Result3} = request_http_rest_lookup(["authz"]), - ?assertEqual([], get_http_data(Result3)), - ok. +% Rule2 = #{<<"principal">> => #{<<"ipaddress">> => <<"127.0.0.1">>}, +% <<"action">> => <<"publish">>, +% <<"topics">> => [#{<<"eq">> => <<"#">>}, +% #{<<"eq">> => <<"+">>} +% ], +% <<"permission">> => <<"deny">> +% }, +% {ok, _} = request_http_rest_add(["authz/append"], #{rules => [Rule2]}), +% {ok, Result2} = request_http_rest_lookup(["authz"]), +% ?assertEqual(Rule2#{<<"principal">> => #{<<"ipaddress">> => "127.0.0.1"}}, +% lists:last(get_http_data(Result2))), -%%-------------------------------------------------------------------- -%% HTTP Request -%%-------------------------------------------------------------------- +% {ok, _} = request_http_rest_update(["authz"], #{rules => []}), +% {ok, Result3} = request_http_rest_lookup(["authz"]), +% ?assertEqual([], get_http_data(Result3)), +% ok. -request_http_rest_list(Path) -> - request_api(get, uri(Path), default_auth_header()). +% %%-------------------------------------------------------------------- +% %% HTTP Request +% %%-------------------------------------------------------------------- -request_http_rest_lookup(Path) -> - request_api(get, uri([Path]), default_auth_header()). +% request_http_rest_list(Path) -> +% request_api(get, uri(Path), default_auth_header()). -request_http_rest_add(Path, Params) -> - request_api(post, uri(Path), [], default_auth_header(), Params). +% request_http_rest_lookup(Path) -> +% request_api(get, uri([Path]), default_auth_header()). -request_http_rest_update(Path, Params) -> - request_api(put, uri([Path]), [], default_auth_header(), Params). +% request_http_rest_add(Path, Params) -> +% request_api(post, uri(Path), [], default_auth_header(), Params). -request_http_rest_delete(Login) -> - request_api(delete, uri([Login]), default_auth_header()). +% request_http_rest_update(Path, Params) -> +% request_api(put, uri([Path]), [], default_auth_header(), Params). -uri() -> uri([]). -uri(Parts) when is_list(Parts) -> - NParts = [b2l(E) || E <- Parts], - ?HOST ++ filename:join([?BASE_PATH, ?API_VERSION | NParts]). +% request_http_rest_delete(Login) -> +% request_api(delete, uri([Login]), default_auth_header()). -%% @private -b2l(B) when is_binary(B) -> - binary_to_list(B); -b2l(L) when is_list(L) -> - L. +% uri() -> uri([]). +% uri(Parts) when is_list(Parts) -> +% NParts = [b2l(E) || E <- Parts], +% ?HOST ++ filename:join([?BASE_PATH, ?API_VERSION | NParts]). + +% %% @private +% b2l(B) when is_binary(B) -> +% binary_to_list(B); +% b2l(L) when is_list(L) -> +% L. diff --git a/apps/emqx_authz/test/emqx_authz_http_SUITE.erl b/apps/emqx_authz/test/emqx_authz_http_SUITE.erl index 77f78bf89..b8d4da71c 100644 --- a/apps/emqx_authz/test/emqx_authz_http_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_http_SUITE.erl @@ -29,41 +29,33 @@ groups() -> []. init_per_suite(Config) -> + %% important! let emqx_schema include the current app! + meck:new(emqx_schema, [non_strict, passthrough, no_history, no_link]), + meck:expect(emqx_schema, includes, fun() -> ["emqx_authz"] end ), + meck:new(emqx_resource, [non_strict, passthrough, no_history, no_link]), meck:expect(emqx_resource, create, fun(_, _, _) -> {ok, meck_data} end ), - ok = emqx_ct_helpers:start_apps([emqx_authz], fun set_special_configs/1), + ok = emqx_ct_helpers:start_apps([emqx_authz]), + ok = emqx_config:update_config([zones, default, acl, cache, enable], false), + ok = emqx_config:update_config([zones, default, acl, enable], true), + Rules = [#{ <<"config">> => #{ + <<"url">> => <<"https://fake.com:443/">>, + <<"headers">> => #{}, + <<"method">> => <<"get">>, + <<"request_timeout">> => 5000 + }, + <<"principal">> => <<"all">>, + <<"type">> => <<"http">>} + ], + ok = emqx_authz:update(replace, Rules), Config. end_per_suite(_Config) -> file:delete(filename:join(emqx:get_env(plugins_etc_dir), 'authz.conf')), emqx_ct_helpers:stop_apps([emqx_authz, emqx_resource]), + meck:unload(emqx_schema), meck:unload(emqx_resource). -set_special_configs(emqx) -> - application:set_env(emqx, allow_anonymous, true), - application:set_env(emqx, enable_acl_cache, false), - application:set_env(emqx, acl_nomatch, deny), - application:set_env(emqx, plugins_loaded_file, - emqx_ct_helpers:deps_path(emqx, "test/loaded_plguins")), - ok; -set_special_configs(emqx_authz) -> - Rules = [#{config =>#{ - url => #{host => "fake.com", - path => "/", - port => 443, - scheme => https}, - headers => #{}, - method => get, - request_timeout => 5000 - }, - principal => all, - type => http} - ], - emqx_config:put([emqx_authz], #{rules => Rules}), - ok; -set_special_configs(_App) -> - ok. - %%------------------------------------------------------------------------------ %% Testcases %%------------------------------------------------------------------------------ @@ -73,7 +65,9 @@ t_authz(_) -> username => <<"username">>, peerhost => {127,0,0,1}, protocol => mqtt, - mountpoint => <<"fake">> + mountpoint => <<"fake">>, + zone => default, + listener => mqtt_tcp }, meck:expect(emqx_resource, query, fun(_, _) -> {ok, 204, fake_headers} end), diff --git a/apps/emqx_authz/test/emqx_authz_mongo_SUITE.erl b/apps/emqx_authz/test/emqx_authz_mongo_SUITE.erl index d2792e388..c2e17f433 100644 --- a/apps/emqx_authz/test/emqx_authz_mongo_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_mongo_SUITE.erl @@ -31,33 +31,35 @@ groups() -> init_per_suite(Config) -> meck:new(emqx_resource, [non_strict, passthrough, no_history, no_link]), meck:expect(emqx_resource, create, fun(_, _, _) -> {ok, meck_data} end ), - ok = emqx_ct_helpers:start_apps([emqx_authz], fun set_special_configs/1), + + %% important! let emqx_schema include the current app! + meck:new(emqx_schema, [non_strict, passthrough, no_history, no_link]), + meck:expect(emqx_schema, includes, fun() -> ["emqx_authz"] end ), + + ok = emqx_ct_helpers:start_apps([emqx_authz]), + ct:pal("---- emqx_hooks: ~p", [ets:tab2list(emqx_hooks)]), + ok = emqx_config:update_config([zones, default, acl, cache, enable], false), + ok = emqx_config:update_config([zones, default, acl, enable], true), + Rules = [#{ <<"config">> => #{ + <<"mongo_type">> => <<"single">>, + <<"server">> => <<"127.0.0.1:27017">>, + <<"pool_size">> => 1, + <<"database">> => <<"mqtt">>, + <<"ssl">> => #{<<"enable">> => false}}, + <<"principal">> => <<"all">>, + <<"collection">> => <<"fake">>, + <<"find">> => #{<<"a">> => <<"b">>}, + <<"type">> => <<"mongo">>} + ], + ok = emqx_authz:update(replace, Rules), Config. end_per_suite(_Config) -> file:delete(filename:join(emqx:get_env(plugins_etc_dir), 'authz.conf')), emqx_ct_helpers:stop_apps([emqx_authz, emqx_resource]), + meck:unload(emqx_schema), meck:unload(emqx_resource). -set_special_configs(emqx) -> - application:set_env(emqx, allow_anonymous, true), - application:set_env(emqx, enable_acl_cache, false), - application:set_env(emqx, acl_nomatch, deny), - application:set_env(emqx, plugins_loaded_file, - emqx_ct_helpers:deps_path(emqx, "test/loaded_plguins")), - ok; -set_special_configs(emqx_authz) -> - Rules = [#{config =>#{}, - principal => all, - collection => <<"fake">>, - find => #{<<"a">> => <<"b">>}, - type => mongo} - ], - emqx_config:put([emqx_authz], #{rules => Rules}), - ok; -set_special_configs(_App) -> - ok. - -define(RULE1,[#{<<"topics">> => [<<"#">>], <<"permission">> => <<"deny">>, <<"action">> => <<"all">>}]). @@ -78,15 +80,21 @@ set_special_configs(_App) -> t_authz(_) -> ClientInfo1 = #{clientid => <<"test">>, username => <<"test">>, - peerhost => {127,0,0,1} + peerhost => {127,0,0,1}, + zone => default, + listener => mqtt_tcp }, ClientInfo2 = #{clientid => <<"test_clientid">>, username => <<"test_username">>, - peerhost => {192,168,0,10} + peerhost => {192,168,0,10}, + zone => default, + listener => mqtt_tcp }, ClientInfo3 = #{clientid => <<"test_clientid">>, username => <<"fake_username">>, - peerhost => {127,0,0,1} + peerhost => {127,0,0,1}, + zone => default, + listener => mqtt_tcp }, meck:expect(emqx_resource, query, fun(_, _) -> [] end), diff --git a/apps/emqx_authz/test/emqx_authz_mysql_SUITE.erl b/apps/emqx_authz/test/emqx_authz_mysql_SUITE.erl index a9acf5e36..a668352cd 100644 --- a/apps/emqx_authz/test/emqx_authz_mysql_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_mysql_SUITE.erl @@ -29,34 +29,36 @@ groups() -> []. init_per_suite(Config) -> + %% important! let emqx_schema include the current app! + meck:new(emqx_schema, [non_strict, passthrough, no_history, no_link]), + meck:expect(emqx_schema, includes, fun() -> ["emqx_authz"] end ), + meck:new(emqx_resource, [non_strict, passthrough, no_history, no_link]), meck:expect(emqx_resource, create, fun(_, _, _) -> {ok, meck_data} end ), - ok = emqx_ct_helpers:start_apps([emqx_authz], fun set_special_configs/1), + ok = emqx_ct_helpers:start_apps([emqx_authz]), + ok = emqx_config:update_config([zones, default, acl, cache, enable], false), + ok = emqx_config:update_config([zones, default, acl, enable], true), + Rules = [#{ <<"config">> => #{ + <<"server">> => <<"127.0.0.1:27017">>, + <<"pool_size">> => 1, + <<"database">> => <<"mqtt">>, + <<"username">> => <<"xx">>, + <<"password">> => <<"ee">>, + <<"auto_reconnect">> => true, + <<"ssl">> => #{<<"enable">> => false} + }, + <<"principal">> => <<"all">>, + <<"sql">> => <<"abcb">>, + <<"type">> => <<"mysql">> }], + emqx_authz:update(replace, Rules), Config. end_per_suite(_Config) -> file:delete(filename:join(emqx:get_env(plugins_etc_dir), 'authz.conf')), emqx_ct_helpers:stop_apps([emqx_authz, emqx_resource]), + meck:unload(emqx_schema), meck:unload(emqx_resource). -set_special_configs(emqx) -> - application:set_env(emqx, allow_anonymous, false), - application:set_env(emqx, enable_acl_cache, false), - application:set_env(emqx, acl_nomatch, deny), - application:set_env(emqx, plugins_loaded_file, - emqx_ct_helpers:deps_path(emqx, "test/loaded_plguins")), - ok; -set_special_configs(emqx_authz) -> - Rules = [#{config =>#{}, - principal => all, - sql => <<"fake">>, - type => mysql} - ], - emqx_config:put([emqx_authz], #{rules => Rules}), - ok; -set_special_configs(_App) -> - ok. - -define(COLUMNS, [ <<"ipaddress">> , <<"username">> , <<"clientid">> @@ -76,15 +78,21 @@ set_special_configs(_App) -> t_authz(_) -> ClientInfo1 = #{clientid => <<"test">>, username => <<"test">>, - peerhost => {127,0,0,1} + peerhost => {127,0,0,1}, + zone => default, + listener => mqtt_tcp }, ClientInfo2 = #{clientid => <<"test_clientid">>, username => <<"test_username">>, - peerhost => {192,168,0,10} + peerhost => {192,168,0,10}, + zone => default, + listener => mqtt_tcp }, ClientInfo3 = #{clientid => <<"test_clientid">>, username => <<"fake_username">>, - peerhost => {127,0,0,1} + peerhost => {127,0,0,1}, + zone => default, + listener => mqtt_tcp }, meck:expect(emqx_resource, query, fun(_, _) -> {ok, ?COLUMNS, []} end), diff --git a/apps/emqx_authz/test/emqx_authz_pgsql_SUITE.erl b/apps/emqx_authz/test/emqx_authz_pgsql_SUITE.erl index 03bec2415..4049dc531 100644 --- a/apps/emqx_authz/test/emqx_authz_pgsql_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_pgsql_SUITE.erl @@ -29,34 +29,35 @@ groups() -> []. init_per_suite(Config) -> + %% important! let emqx_schema include the current app! + meck:new(emqx_schema, [non_strict, passthrough, no_history, no_link]), + meck:expect(emqx_schema, includes, fun() -> ["emqx_authz"] end ), + meck:new(emqx_resource, [non_strict, passthrough, no_history, no_link]), meck:expect(emqx_resource, create, fun(_, _, _) -> {ok, meck_data} end ), - ok = emqx_ct_helpers:start_apps([emqx_authz], fun set_special_configs/1), + ok = emqx_ct_helpers:start_apps([emqx_authz]), + ok = emqx_config:update_config([zones, default, acl, cache, enable], false), + ok = emqx_config:update_config([zones, default, acl, enable], true), + Rules = [#{ <<"config">> => #{ + <<"server">> => <<"127.0.0.1:27017">>, + <<"pool_size">> => 1, + <<"database">> => <<"mqtt">>, + <<"username">> => <<"xx">>, + <<"password">> => <<"ee">>, + <<"auto_reconnect">> => true, + <<"ssl">> => #{<<"enable">> => false} + }, + <<"sql">> => <<"abcb">>, + <<"type">> => <<"pgsql">> }], + emqx_authz:update(replace, Rules), Config. end_per_suite(_Config) -> file:delete(filename:join(emqx:get_env(plugins_etc_dir), 'authz.conf')), emqx_ct_helpers:stop_apps([emqx_authz, emqx_resource]), + meck:unload(emqx_schema), meck:unload(emqx_resource). -set_special_configs(emqx) -> - application:set_env(emqx, allow_anonymous, false), - application:set_env(emqx, enable_acl_cache, false), - application:set_env(emqx, acl_nomatch, deny), - application:set_env(emqx, plugins_loaded_file, - emqx_ct_helpers:deps_path(emqx, "test/loaded_plguins")), - ok; -set_special_configs(emqx_authz) -> - Rules = [#{config =>#{}, - principal => all, - sql => <<"fake">>, - type => pgsql} - ], - emqx_config:put([emqx_authz], #{rules => Rules}), - ok; -set_special_configs(_App) -> - ok. - -define(COLUMNS, [ {column, <<"ipaddress">>, meck, meck, meck, meck, meck, meck, meck} , {column, <<"username">>, meck, meck, meck, meck, meck, meck, meck} , {column, <<"clientid">>, meck, meck, meck, meck, meck, meck, meck} @@ -76,15 +77,21 @@ set_special_configs(_App) -> t_authz(_) -> ClientInfo1 = #{clientid => <<"test">>, username => <<"test">>, - peerhost => {127,0,0,1} + peerhost => {127,0,0,1}, + zone => default, + listener => mqtt_tcp }, ClientInfo2 = #{clientid => <<"test_clientid">>, username => <<"test_username">>, - peerhost => {192,168,0,10} + peerhost => {192,168,0,10}, + zone => default, + listener => mqtt_tcp }, ClientInfo3 = #{clientid => <<"test_clientid">>, username => <<"fake_username">>, - peerhost => {127,0,0,1} + peerhost => {127,0,0,1}, + zone => default, + listener => mqtt_tcp }, meck:expect(emqx_resource, query, fun(_, _) -> {ok, ?COLUMNS, []} end), diff --git a/apps/emqx_authz/test/emqx_authz_redis_SUITE.erl b/apps/emqx_authz/test/emqx_authz_redis_SUITE.erl index 7530c3183..efeeaea75 100644 --- a/apps/emqx_authz/test/emqx_authz_redis_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_redis_SUITE.erl @@ -29,34 +29,34 @@ groups() -> []. init_per_suite(Config) -> + %% important! let emqx_schema include the current app! + meck:new(emqx_schema, [non_strict, passthrough, no_history, no_link]), + meck:expect(emqx_schema, includes, fun() -> ["emqx_authz"] end ), + meck:new(emqx_resource, [non_strict, passthrough, no_history, no_link]), meck:expect(emqx_resource, create, fun(_, _, _) -> {ok, meck_data} end ), - ok = emqx_ct_helpers:start_apps([emqx_authz], fun set_special_configs/1), + ok = emqx_ct_helpers:start_apps([emqx_authz]), + ok = emqx_config:update_config([zones, default, acl, cache, enable], false), + ok = emqx_config:update_config([zones, default, acl, enable], true), + Rules = [#{ <<"config">> => #{ + <<"server">> => <<"127.0.0.1:27017">>, + <<"pool_size">> => 1, + <<"database">> => 0, + <<"password">> => <<"ee">>, + <<"auto_reconnect">> => true, + <<"ssl">> => #{<<"enable">> => false} + }, + <<"cmd">> => <<"HGETALL mqtt_acl:%u">>, + <<"type">> => <<"redis">> }], + emqx_authz:update(replace, Rules), Config. end_per_suite(_Config) -> file:delete(filename:join(emqx:get_env(plugins_etc_dir), 'authz.conf')), emqx_ct_helpers:stop_apps([emqx_authz, emqx_resource]), + meck:unload(emqx_schema), meck:unload(emqx_resource). -set_special_configs(emqx) -> - application:set_env(emqx, allow_anonymous, true), - application:set_env(emqx, enable_acl_cache, false), - application:set_env(emqx, acl_nomatch, deny), - application:set_env(emqx, plugins_loaded_file, - emqx_ct_helpers:deps_path(emqx, "test/loaded_plguins")), - ok; -set_special_configs(emqx_authz) -> - Rules = [#{config =>#{}, - principal => all, - cmd => <<"fake">>, - type => redis} - ], - emqx_config:put([emqx_authz], #{rules => Rules}), - ok; -set_special_configs(_App) -> - ok. - -define(RULE1, [<<"test/%u">>, <<"publish">>]). -define(RULE2, [<<"test/%c">>, <<"publish">>]). -define(RULE3, [<<"#">>, <<"subscribe">>]). @@ -68,7 +68,9 @@ set_special_configs(_App) -> t_authz(_) -> ClientInfo = #{clientid => <<"clientid">>, username => <<"username">>, - peerhost => {127,0,0,1} + peerhost => {127,0,0,1}, + zone => default, + listener => mqtt_tcp }, meck:expect(emqx_resource, query, fun(_, _) -> {ok, []} end), diff --git a/apps/emqx_data_bridge/src/emqx_data_bridge_app.erl b/apps/emqx_data_bridge/src/emqx_data_bridge_app.erl index 967791643..ccd98f010 100644 --- a/apps/emqx_data_bridge/src/emqx_data_bridge_app.erl +++ b/apps/emqx_data_bridge/src/emqx_data_bridge_app.erl @@ -34,7 +34,10 @@ stop(_State) -> handle_update_config({update, Bridge = #{<<"name">> := Name}}, OldConf) -> [Bridge | remove_bridge(Name, OldConf)]; handle_update_config({delete, Name}, OldConf) -> - remove_bridge(Name, OldConf). + remove_bridge(Name, OldConf); +handle_update_config(NewConf, _OldConf) when is_list(NewConf) -> + %% overwrite the entire config! + NewConf. remove_bridge(_Name, undefined) -> []; diff --git a/apps/emqx_gateway/etc/emqx_gateway.conf b/apps/emqx_gateway/etc/emqx_gateway.conf index 591f2523d..8cf036480 100644 --- a/apps/emqx_gateway/etc/emqx_gateway.conf +++ b/apps/emqx_gateway/etc/emqx_gateway.conf @@ -29,46 +29,46 @@ emqx_gateway: { } } - mqttsn.1: { - ## The MQTT-SN Gateway ID in ADVERTISE message. - gateway_id: 1 + # mqttsn.1: { + # ## The MQTT-SN Gateway ID in ADVERTISE message. + # gateway_id: 1 - ## Enable broadcast this gateway to WLAN - broadcast: true + # ## Enable broadcast this gateway to WLAN + # broadcast: true - ## To control whether write statistics data into ETS table - ## for dashbord to read. - enable_stats: true + # ## To control whether write statistics data into ETS table + # ## for dashbord to read. + # enable_stats: true - ## To control whether accept and process the received - ## publish message with qos=-1. - enable_qos3: true + # ## To control whether accept and process the received + # ## publish message with qos=-1. + # enable_qos3: true - ## Idle timeout for a MQTT-SN channel - idle_timeout: 30s + # ## Idle timeout for a MQTT-SN channel + # idle_timeout: 30s - ## The pre-defined topic name corresponding to the pre-defined topic - ## id of N. - ## Note that the pre-defined topic id of 0 is reserved. - predefined: [ - { id: 1 - topic: "/predefined/topic/name/hello" - }, - { id: 2 - topic: "/predefined/topic/name/nice" - } - ] + # ## The pre-defined topic name corresponding to the pre-defined topic + # ## id of N. + # ## Note that the pre-defined topic id of 0 is reserved. + # predefined: [ + # { id: 1 + # topic: "/predefined/topic/name/hello" + # }, + # { id: 2 + # topic: "/predefined/topic/name/nice" + # } + # ] - ### ClientInfo override - clientinfo_override: { - username: "mqtt_sn_user" - password: "abc" - } + # ### ClientInfo override + # clientinfo_override: { + # username: "mqtt_sn_user" + # password: "abc" + # } - listener.udp.1: { - bind: 1884 - max_connections: 10240000 - max_conn_rate: 1000 - } - } + # listener.udp.1: { + # bind: 1884 + # max_connections: 10240000 + # max_conn_rate: 1000 + # } + # } } diff --git a/apps/emqx_gateway/src/coap/emqx_coap_mqtt_adapter.erl b/apps/emqx_gateway/src/coap/emqx_coap_mqtt_adapter.erl index da3d5eafa..de62cb5ca 100644 --- a/apps/emqx_gateway/src/coap/emqx_coap_mqtt_adapter.erl +++ b/apps/emqx_gateway/src/coap/emqx_coap_mqtt_adapter.erl @@ -371,7 +371,8 @@ clientinfo(#state{peername = {PeerHost, _}, clientid = ClientId, username = Username, password = Password}) -> - #{zone => undefined, + #{zone => default, + listener => mqtt_tcp, %% FIXME: this won't work protocol => coap, peerhost => PeerHost, sockport => 5683, %% FIXME: diff --git a/apps/emqx_gateway/src/emqx_gateway_cli.erl b/apps/emqx_gateway/src/emqx_gateway_cli.erl index beb3e5eae..2fbc96bce 100644 --- a/apps/emqx_gateway/src/emqx_gateway_cli.erl +++ b/apps/emqx_gateway/src/emqx_gateway_cli.erl @@ -39,7 +39,10 @@ unload() -> lists:foreach(fun(Cmd) -> emqx_ctl:unregister_command(Cmd) end, Cmds). is_cmd(Fun) -> - not lists:member(Fun, [init, load, module_info]). + case atom_to_list(Fun) of + "gateway" ++ _ -> true; + _ -> false + end. %%-------------------------------------------------------------------- diff --git a/apps/emqx_gateway/src/emqx_gateway_ctx.erl b/apps/emqx_gateway/src/emqx_gateway_ctx.erl index f43620996..3af6fde20 100644 --- a/apps/emqx_gateway/src/emqx_gateway_ctx.erl +++ b/apps/emqx_gateway/src/emqx_gateway_ctx.erl @@ -68,7 +68,8 @@ | {error, any()}. authenticate(_Ctx = #{auth := ChainId}, ClientInfo0) -> ClientInfo = ClientInfo0#{ - zone => undefined, + zone => default, + listener => mqtt_tcp, chain_id => ChainId }, case emqx_access_control:authenticate(ClientInfo) of diff --git a/apps/emqx_gateway/src/emqx_gateway_schema.erl b/apps/emqx_gateway/src/emqx_gateway_schema.erl index f1b1f9fa4..92dc834c9 100644 --- a/apps/emqx_gateway/src/emqx_gateway_schema.erl +++ b/apps/emqx_gateway/src/emqx_gateway_schema.erl @@ -8,13 +8,11 @@ -include_lib("typerefl/include/types.hrl"). --type flag() :: true | false. -type duration() :: integer(). -type bytesize() :: integer(). -type comma_separated_list() :: list(). -type ip_port() :: tuple(). --typerefl_from_string({flag/0, emqx_schema, to_flag}). -typerefl_from_string({duration/0, emqx_schema, to_duration}). -typerefl_from_string({bytesize/0, emqx_schema, to_bytesize}). -typerefl_from_string({comma_separated_list/0, emqx_schema, to_comma_separated_list}). @@ -22,8 +20,7 @@ -behaviour(hocon_schema). --reflect_type([ flag/0 - , duration/0 +-reflect_type([ duration/0 , bytesize/0 , comma_separated_list/0 , ip_port/0 @@ -114,16 +111,16 @@ fields(listener_settings) -> %, {zone, t(string())} %, {rate_limit, t(comma_separated_list())} , {access, t(ref(access))} - , {proxy_protocol, t(flag())} + , {proxy_protocol, t(boolean())} , {proxy_protocol_timeout, t(duration())} , {backlog, t(integer(), undefined, 1024)} , {send_timeout, t(duration(), undefined, "15s")} - , {send_timeout_close, t(flag(), undefined, true)} + , {send_timeout_close, t(boolean(), undefined, true)} , {recbuf, t(bytesize())} , {sndbuf, t(bytesize())} , {buffer, t(bytesize())} , {high_watermark, t(bytesize(), undefined, "1MB")} - , {tune_buffer, t(flag())} + , {tune_buffer, t(boolean())} , {nodelay, t(boolean())} , {reuseaddr, t(boolean())} ]; @@ -202,15 +199,15 @@ ssl(Mapping, Defaults) -> _ -> Mapping ++ "." ++ Field end end, D = fun (Field) -> maps:get(list_to_atom(Field), Defaults, undefined) end, - [ {"enable", t(flag(), M("enable"), D("enable"))} + [ {"enable", t(boolean(), M("enable"), D("enable"))} , {"cacertfile", t(string(), M("cacertfile"), D("cacertfile"))} , {"certfile", t(string(), M("certfile"), D("certfile"))} , {"keyfile", t(string(), M("keyfile"), D("keyfile"))} , {"verify", t(union(verify_peer, verify_none), M("verify"), D("verify"))} , {"fail_if_no_peer_cert", t(boolean(), M("fail_if_no_peer_cert"), D("fail_if_no_peer_cert"))} - , {"secure_renegotiate", t(flag(), M("secure_renegotiate"), D("secure_renegotiate"))} - , {"reuse_sessions", t(flag(), M("reuse_sessions"), D("reuse_sessions"))} - , {"honor_cipher_order", t(flag(), M("honor_cipher_order"), D("honor_cipher_order"))} + , {"secure_renegotiate", t(boolean(), M("secure_renegotiate"), D("secure_renegotiate"))} + , {"reuse_sessions", t(boolean(), M("reuse_sessions"), D("reuse_sessions"))} + , {"honor_cipher_order", t(boolean(), M("honor_cipher_order"), D("honor_cipher_order"))} , {"handshake_timeout", t(duration(), M("handshake_timeout"), D("handshake_timeout"))} , {"depth", t(integer(), M("depth"), D("depth"))} , {"password", hoconsc:t(string(), #{mapping => M("key_password"), diff --git a/apps/emqx_gateway/src/exproto/emqx_exproto_conn.erl b/apps/emqx_gateway/src/exproto/emqx_exproto_conn.erl index da655bcb4..256a69b30 100644 --- a/apps/emqx_gateway/src/exproto/emqx_exproto_conn.erl +++ b/apps/emqx_gateway/src/exproto/emqx_exproto_conn.erl @@ -219,7 +219,8 @@ send(Data, #state{socket = {esockd_transport, Sock}}) -> -define(DEFAULT_GC_OPTS, #{count => 1000, bytes => 1024*1024}). -define(DEFAULT_IDLE_TIMEOUT, 30000). --define(DEFAULT_OOM_POLICY, #{max_heap_size => 4194304,message_queue_len => 32000}). +-define(DEFAULT_OOM_POLICY, #{enable => true, max_heap_size => 4194304, + max_message_queue_len => 32000}). init(Parent, WrappedSock, Peername, Options) -> case esockd_wait(WrappedSock) of diff --git a/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_protocol.erl b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_protocol.erl index 729404bb5..11a52327a 100644 --- a/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_protocol.erl +++ b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_protocol.erl @@ -440,7 +440,8 @@ take_place(Text, Placeholder, Value) -> clientinfo(#lwm2m_state{peername = {PeerHost, _}, endpoint_name = EndpointName, mountpoint = Mountpoint}) -> - #{zone => undefined, + #{zone => default, + listener => mqtt_tcp, %% FIXME: this won't work protocol => lwm2m, peerhost => PeerHost, sockport => 5683, %% FIXME: diff --git a/apps/emqx_gateway/src/mqttsn/emqx_sn_gateway.erl b/apps/emqx_gateway/src/mqttsn/emqx_sn_gateway.erl index 28d461b9b..cae1cf78a 100644 --- a/apps/emqx_gateway/src/mqttsn/emqx_sn_gateway.erl +++ b/apps/emqx_gateway/src/mqttsn/emqx_sn_gateway.erl @@ -104,7 +104,7 @@ -define(STAT_TIMEOUT, 10000). -define(IDLE_TIMEOUT, 30000). --define(DEFAULT_CHAN_OPTIONS, [{max_packet_size, 256}, {zone, external}]). +-define(DEFAULT_CHAN_OPTIONS, #{zone => default, listener => mqtt_tcp}). -define(NEG_QOS_CLIENT_ID, <<"NegQoS-Client">>). diff --git a/apps/emqx_gateway/src/stomp/emqx_stomp_channel.erl b/apps/emqx_gateway/src/stomp/emqx_stomp_channel.erl index 322baa120..87bba1939 100644 --- a/apps/emqx_gateway/src/stomp/emqx_stomp_channel.erl +++ b/apps/emqx_gateway/src/stomp/emqx_stomp_channel.erl @@ -113,7 +113,8 @@ init(ConnInfo = #{peername := {PeerHost, _}, Mountpoint = maps:get(mountpoint, Option, undefined), ClientInfo = setting_peercert_infos( Peercert, - #{ zone => undefined + #{ zone => default + , listener => mqtt_tcp , protocol => stomp , peerhost => PeerHost , sockport => SockPort @@ -583,7 +584,8 @@ handle_call(discard, Channel) -> % shutdown_and_reply(takeovered, AllPendings, Channel); handle_call(list_acl_cache, Channel) -> - {reply, emqx_acl_cache:list_acl_cache(), Channel}; + %% This won't work + {reply, emqx_acl_cache:list_acl_cache(default, mqtt_tcp), Channel}; %% XXX: No Quota Now % handle_call({quota, Policy}, Channel) -> diff --git a/apps/emqx_gateway/src/stomp/emqx_stomp_connection.erl b/apps/emqx_gateway/src/stomp/emqx_stomp_connection.erl index a4b87fcd4..bc149a58d 100644 --- a/apps/emqx_gateway/src/stomp/emqx_stomp_connection.erl +++ b/apps/emqx_gateway/src/stomp/emqx_stomp_connection.erl @@ -245,7 +245,9 @@ init_state(Transport, Socket, Options) -> peername => Peername, sockname => Sockname, peercert => Peercert, - conn_mod => ?MODULE + conn_mod => ?MODULE, + zone => default, + listener => mqtt_tcp }, ActiveN = emqx_gateway_utils:active_n(Options), %% TODO: RateLimit ? How ? diff --git a/apps/emqx_management/etc/emqx_management.conf b/apps/emqx_management/etc/emqx_management.conf index 89cc0293b..127a21e3b 100644 --- a/apps/emqx_management/etc/emqx_management.conf +++ b/apps/emqx_management/etc/emqx_management.conf @@ -14,7 +14,7 @@ emqx_management:{ port: 8081 backlog: 512 send_timeout: 15s - send_timeout_close: on + send_timeout_close: true inet6: false ipv6_v6only: false } @@ -25,7 +25,7 @@ emqx_management:{ ## acceptors: 2 ## backlog: 512 ## send_timeout: 15s -## send_timeout_close: on +## send_timeout_close: true ## inet6: false ## ipv6_v6only: false ## certfile = "etc/certs/cert.pem" diff --git a/apps/emqx_management/src/emqx_management_schema.erl b/apps/emqx_management/src/emqx_management_schema.erl index 3bee25031..f9543697f 100644 --- a/apps/emqx_management/src/emqx_management_schema.erl +++ b/apps/emqx_management/src/emqx_management_schema.erl @@ -43,13 +43,13 @@ fields("http") -> , {"max_connections", emqx_schema:t(integer(), undefined, 512)} , {"backlog", emqx_schema:t(integer(), undefined, 1024)} , {"send_timeout", emqx_schema:t(emqx_schema:duration(), undefined, "15s")} - , {"send_timeout_close", emqx_schema:t(emqx_schema:flag(), undefined, true)} + , {"send_timeout_close", emqx_schema:t(boolean(), undefined, true)} , {"inet6", emqx_schema:t(boolean(), undefined, false)} , {"ipv6_v6only", emqx_schema:t(boolean(), undefined, false)} ]; fields("https") -> - emqx_schema:ssl(undefined, #{enable => true}) ++ fields("http"). + emqx_schema:ssl(#{enable => true}) ++ fields("http"). max_row_limit(type) -> integer(); max_row_limit(default) -> 1000; diff --git a/apps/emqx_management/src/emqx_mgmt.erl b/apps/emqx_management/src/emqx_mgmt.erl index dc7b24d57..b539b223b 100644 --- a/apps/emqx_management/src/emqx_mgmt.erl +++ b/apps/emqx_management/src/emqx_mgmt.erl @@ -457,7 +457,7 @@ list_listeners(Node) when Node =:= node() -> Tcp = lists:map(fun({{Protocol, ListenOn}, _Pid}) -> #{protocol => Protocol, listen_on => ListenOn, - identifier => emqx_listeners:find_id_by_listen_on(ListenOn), + identifier => Protocol, acceptors => esockd:get_acceptors({Protocol, ListenOn}), max_conns => esockd:get_max_connections({Protocol, ListenOn}), current_conns => esockd:get_current_connections({Protocol, ListenOn}), @@ -476,6 +476,7 @@ list_listeners(Node) when Node =:= node() -> list_listeners(Node) -> rpc_call(Node, list_listeners, [Node]). +-spec restart_listener(node(), atom()) -> ok | {error, term()}. restart_listener(Node, Identifier) when Node =:= node() -> emqx_listeners:restart_listener(Identifier); diff --git a/apps/emqx_management/src/emqx_mgmt_api_listeners.erl b/apps/emqx_management/src/emqx_mgmt_api_listeners.erl index fd2b575d8..a2bc7309d 100644 --- a/apps/emqx_management/src/emqx_mgmt_api_listeners.erl +++ b/apps/emqx_management/src/emqx_mgmt_api_listeners.erl @@ -30,13 +30,13 @@ -rest_api(#{name => restart_listener, method => 'PUT', - path => "/listeners/:bin:identifier/restart", + path => "/listeners/:atom:identifier/restart", func => restart, descr => "Restart a listener in the cluster"}). -rest_api(#{name => restart_node_listener, method => 'PUT', - path => "/nodes/:atom:node/listeners/:bin:identifier/restart", + path => "/nodes/:atom:node/listeners/:atom:identifier/restart", func => restart, descr => "Restart a listener on a node"}). @@ -57,10 +57,7 @@ restart(#{node := Node, identifier := Identifier}, _Params) -> ok -> emqx_mgmt:return({ok, "Listener restarted."}); {error, Error} -> emqx_mgmt:return({error, Error}) end; - -%% Restart listeners in the cluster. -restart(#{identifier := <<"http", _/binary>>}, _Params) -> - {403, <<"http_listener_restart_unsupported">>}; +%% Restart listeners on all nodes in the cluster. restart(#{identifier := Identifier}, _Params) -> Results = [{Node, emqx_mgmt:restart_listener(Node, Identifier)} || {Node, _Info} <- emqx_mgmt:list_nodes()], case lists:filter(fun({_, Result}) -> Result =/= ok end, Results) of diff --git a/apps/emqx_management/src/emqx_mgmt_cli.erl b/apps/emqx_management/src/emqx_mgmt_cli.erl index 9c5f02b62..8d759eb18 100644 --- a/apps/emqx_management/src/emqx_mgmt_cli.erl +++ b/apps/emqx_management/src/emqx_mgmt_cli.erl @@ -463,86 +463,57 @@ trace_off(Who, Name) -> listeners([]) -> lists:foreach(fun({{Protocol, ListenOn}, _Pid}) -> - Info = [{listen_on, {string, emqx_listeners:format_listen_on(ListenOn)}}, + Info = [{listen_on, {string, format_listen_on(ListenOn)}}, {acceptors, esockd:get_acceptors({Protocol, ListenOn})}, {max_conns, esockd:get_max_connections({Protocol, ListenOn})}, {current_conn, esockd:get_current_connections({Protocol, ListenOn})}, {shutdown_count, esockd:get_shutdown_count({Protocol, ListenOn})} ], - emqx_ctl:print("~s~n", [listener_identifier(Protocol, ListenOn)]), + emqx_ctl:print("~s~n", [Protocol]), lists:foreach(fun indent_print/1, Info) end, esockd:listeners()), lists:foreach(fun({Protocol, Opts}) -> Port = proplists:get_value(port, Opts), - Info = [{listen_on, {string, emqx_listeners:format_listen_on(Port)}}, + Info = [{listen_on, {string, format_listen_on(Port)}}, {acceptors, maps:get(num_acceptors, proplists:get_value(transport_options, Opts, #{}), 0)}, {max_conns, proplists:get_value(max_connections, Opts)}, {current_conn, proplists:get_value(all_connections, Opts)}, {shutdown_count, []}], - emqx_ctl:print("~s~n", [listener_identifier(Protocol, Port)]), + emqx_ctl:print("~s~n", [Protocol]), lists:foreach(fun indent_print/1, Info) end, ranch:info()); -listeners(["stop", Name = "http" ++ _N | _MaybePort]) -> - %% _MaybePort is to be backward compatible, to stop http listener, there is no need for the port number - case minirest:stop(list_to_atom(Name)) of +listeners(["stop", ListenerId]) -> + case emqx_listeners:stop_listener(list_to_atom(ListenerId)) of ok -> - emqx_ctl:print("Stop ~s listener successfully.~n", [Name]); + emqx_ctl:print("Stop ~s listener successfully.~n", [ListenerId]); {error, Error} -> - emqx_ctl:print("Failed to stop ~s listener: ~0p~n", [Name, Error]) + emqx_ctl:print("Failed to stop ~s listener: ~0p~n", [ListenerId, Error]) end; -listeners(["stop", "mqtt:" ++ _ = Identifier]) -> - stop_listener(emqx_listeners:find_by_id(Identifier), Identifier); - -listeners(["stop", _Proto, ListenOn]) -> - %% this clause is kept to be backward compatible - ListenOn1 = case string:tokens(ListenOn, ":") of - [Port] -> list_to_integer(Port); - [IP, Port] -> {IP, list_to_integer(Port)} - end, - stop_listener(emqx_listeners:find_by_listen_on(ListenOn1), ListenOn1); - -listeners(["restart", "http:management"]) -> - restart_http_listener(http, emqx_management); - -listeners(["restart", "https:management"]) -> - restart_http_listener(https, emqx_management); - -listeners(["restart", "http:dashboard"]) -> - restart_http_listener(http, emqx_dashboard); - -listeners(["restart", "https:dashboard"]) -> - restart_http_listener(https, emqx_dashboard); - -listeners(["restart", Identifier]) -> - case emqx_listeners:restart_listener(Identifier) of +listeners(["start", ListenerId]) -> + case emqx_listeners:start_listener(list_to_atom(ListenerId)) of ok -> - emqx_ctl:print("Restarted ~s listener successfully.~n", [Identifier]); + emqx_ctl:print("Started ~s listener successfully.~n", [ListenerId]); {error, Error} -> - emqx_ctl:print("Failed to restart ~s listener: ~0p~n", [Identifier, Error]) + emqx_ctl:print("Failed to start ~s listener: ~0p~n", [ListenerId, Error]) + end; + +listeners(["restart", ListenerId]) -> + case emqx_listeners:restart_listener(list_to_atom(ListenerId)) of + ok -> + emqx_ctl:print("Restarted ~s listener successfully.~n", [ListenerId]); + {error, Error} -> + emqx_ctl:print("Failed to restart ~s listener: ~0p~n", [ListenerId, Error]) end; listeners(_) -> emqx_ctl:usage([{"listeners", "List listeners"}, {"listeners stop ", "Stop a listener"}, - {"listeners stop ", "Stop a listener"}, + {"listeners start ", "Start a listener"}, {"listeners restart ", "Restart a listener"} ]). -stop_listener(false, Input) -> - emqx_ctl:print("No such listener ~p~n", [Input]); -stop_listener(#{listen_on := ListenOn} = Listener, _Input) -> - ID = emqx_listeners:identifier(Listener), - ListenOnStr = emqx_listeners:format_listen_on(ListenOn), - case emqx_listeners:stop_listener(Listener) of - ok -> - emqx_ctl:print("Stop ~s listener on ~s successfully.~n", [ID, ListenOnStr]); - {error, Reason} -> - emqx_ctl:print("Failed to stop ~s listener on ~s: ~0p~n", - [ID, ListenOnStr, Reason]) - end. - %%-------------------------------------------------------------------- %% @doc acl Command @@ -661,24 +632,9 @@ indent_print({Key, {string, Val}}) -> indent_print({Key, Val}) -> emqx_ctl:print(" ~-16s: ~w~n", [Key, Val]). -listener_identifier(Protocol, ListenOn) -> - case emqx_listeners:find_id_by_listen_on(ListenOn) of - false -> - atom_to_list(Protocol); - ID -> - ID - end. - -restart_http_listener(Scheme, AppName) -> - Listeners = application:get_env(AppName, listeners, []), - case lists:keyfind(Scheme, 1, Listeners) of - false -> - emqx_ctl:print("Listener ~s not exists!~n", [AppName]); - {Scheme, Port, Options} -> - ModName = http_mod_name(AppName), - ModName:stop_listener({Scheme, Port, Options}), - ModName:start_listener({Scheme, Port, Options}) - end. - -http_mod_name(emqx_management) -> emqx_mgmt_http; -http_mod_name(Name) -> Name. +format_listen_on(Port) when is_integer(Port) -> + io_lib:format("0.0.0.0:~w", [Port]); +format_listen_on({Addr, Port}) when is_list(Addr) -> + io_lib:format("~s:~w", [Addr, Port]); +format_listen_on({Addr, Port}) when is_tuple(Addr) -> + io_lib:format("~s:~w", [inet:ntoa(Addr), Port]). \ No newline at end of file diff --git a/apps/emqx_management/test/emqx_mgmt_api_test_util.erl b/apps/emqx_management/test/emqx_mgmt_api_test_util.erl index 1925b52e5..568ed46a6 100644 --- a/apps/emqx_management/test/emqx_mgmt_api_test_util.erl +++ b/apps/emqx_management/test/emqx_mgmt_api_test_util.erl @@ -20,24 +20,6 @@ -define(SERVER, "http://127.0.0.1:8081"). -define(BASE_PATH, "/api/v5"). -default_init() -> - ekka_mnesia:start(), - emqx_mgmt_auth:mnesia(boot), - emqx_ct_helpers:start_apps([emqx_management], fun set_special_configs/1), - ok. - - -default_end() -> - emqx_ct_helpers:stop_apps([emqx_management]). - -set_special_configs(emqx_management) -> - emqx_config:put([emqx_management], #{listeners => [#{protocol => http, port => 8081}], - applications =>[#{id => "admin", secret => "public"}]}), - ok; -set_special_configs(_App) -> - ok. - - request_api(Method, Url) -> request_api(Method, Url, [], auth_header_(), []). diff --git a/apps/emqx_management/test/emqx_mgmt_clients_api_SUITE.erl b/apps/emqx_management/test/emqx_mgmt_clients_api_SUITE.erl index 688989211..ac63db0fb 100644 --- a/apps/emqx_management/test/emqx_mgmt_clients_api_SUITE.erl +++ b/apps/emqx_management/test/emqx_mgmt_clients_api_SUITE.erl @@ -25,11 +25,20 @@ all() -> emqx_ct:all(?MODULE). init_per_suite(Config) -> - emqx_mgmt_api_test_util:default_init(), + ekka_mnesia:start(), + emqx_mgmt_auth:mnesia(boot), + emqx_ct_helpers:start_apps([emqx_management], fun set_special_configs/1), Config. end_per_suite(_) -> - emqx_mgmt_api_test_util:default_end(). + emqx_ct_helpers:stop_apps([emqx_management]). + +set_special_configs(emqx_management) -> + emqx_config:put([emqx_management], #{listeners => [#{protocol => http, port => 8081}], + applications =>[#{id => "admin", secret => "public"}]}), + ok; +set_special_configs(_App) -> + ok. t_clients(_) -> process_flag(trap_exit, true), diff --git a/apps/emqx_retainer/src/emqx_retainer.erl b/apps/emqx_retainer/src/emqx_retainer.erl index dcb34ccb2..9da2a4373 100644 --- a/apps/emqx_retainer/src/emqx_retainer.erl +++ b/apps/emqx_retainer/src/emqx_retainer.erl @@ -112,7 +112,7 @@ dispatch(Context, Pid, Topic, Cursor) -> case Cursor =/= undefined orelse emqx_topic:wildcard(Topic) of false -> {ok, Result} = Mod:read_message(Context, Topic), - deliver(Result, Context, Pid, Topic, undefiend); + deliver(Result, Context, Pid, Topic, undefined); true -> {ok, Result, NewCursor} = Mod:match_messages(Context, Topic, Cursor), deliver(Result, Context, Pid, Topic, NewCursor) diff --git a/apps/emqx_retainer/test/emqx_retainer_api_SUITE.erl b/apps/emqx_retainer/test/emqx_retainer_api_SUITE.erl index d9ea06c2f..6ce64ae2e 100644 --- a/apps/emqx_retainer/test/emqx_retainer_api_SUITE.erl +++ b/apps/emqx_retainer/test/emqx_retainer_api_SUITE.erl @@ -50,7 +50,7 @@ init_per_suite(Config) -> end_per_suite(_Config) -> delete_default_app(), - emqx_ct_helpers:stop_apps([emqx_retainer]). + emqx_ct_helpers:stop_apps([emqx_management, emqx_retainer]). init_per_testcase(_, Config) -> Config. diff --git a/bin/emqx b/bin/emqx index 048bbc34f..d6c5be067 100755 --- a/bin/emqx +++ b/bin/emqx @@ -205,6 +205,7 @@ relx_nodetool() { call_hocon() { export RUNNER_ROOT_DIR + export RUNNER_ETC_DIR export REL_VSN "$ERTS_DIR/bin/escript" "$ROOTDIR/bin/nodetool" hocon "$@" \ || die "ERROR: call_hocon failed: $*" $? @@ -592,7 +593,7 @@ case "$1" in # set before generate_config if [ "${_EMQX_START_MODE:-}" = '' ]; then - export EMQX_LOG__TO="${EMQX_LOG__TO:-console}" + export EMQX_LOG__CONSOLE_HANDLER__ENABLE="${EMQX_LOG__CONSOLE_HANDLER__ENABLE:-true}" fi #generate app.config and vm.args @@ -636,7 +637,7 @@ case "$1" in # or other supervision services # set before generate_config - export EMQX_LOG__TO="${EMQX_LOG__TO:-console}" + export EMQX_LOG__CONSOLE_HANDLER__ENABLE="${EMQX_LOG__CONSOLE_HANDLER__ENABLE:-true}" #generate app.config and vm.args generate_config diff --git a/bin/emqx.cmd b/bin/emqx.cmd index 8b9686462..768e30d2c 100644 --- a/bin/emqx.cmd +++ b/bin/emqx.cmd @@ -20,8 +20,6 @@ @set erts_vsn={{ erts_vsn }} @set erl_opts={{ erl_opts }} -@set "CUTTLEFISH_ENV_OVERRIDE_PREFIX=EMQX_" - @set script=%~n0 :: Discover the release root directory from the directory diff --git a/deploy/charts/emqx/templates/StatefulSet.yaml b/deploy/charts/emqx/templates/StatefulSet.yaml index 38006895a..410fbd09a 100644 --- a/deploy/charts/emqx/templates/StatefulSet.yaml +++ b/deploy/charts/emqx/templates/StatefulSet.yaml @@ -116,7 +116,7 @@ spec: value: {{ .Release.Name }} - name: EMQX_CLUSTER__K8S__APP_NAME value: {{ .Release.Name }} - - name: EMQX_CLUSTER__DISCOVERY + - name: EMQX_CLUSTER__DISCOVERY_STRATEGY value: k8s - name: EMQX_CLUSTER__K8S__SERVICE_NAME value: {{ include "emqx.fullname" . }}-headless diff --git a/deploy/docker/README.md b/deploy/docker/README.md index ac494b430..0d816821e 100644 --- a/deploy/docker/README.md +++ b/deploy/docker/README.md @@ -48,8 +48,8 @@ You can change the prefix by overriding "HOCON_ENV_OVERRIDE_PREFIX". Example: ```bash -EMQX_LISTENER__SSL__EXTERNAL__ACCEPTORS <--> listener.ssl.external.acceptors -EMQX_MQTT__MAX_PACKET_SIZE <--> mqtt.max_packet_size +EMQX_ZONES__DEFAULT__LISTENERS__MQTT_SSL__ACCEPTORS <--> zones.default.listeners.mqtt_ssl.acceptors +EMQX_ZONES__DEFAULT__MQTT__MAX_PACKET_SIZE <--> zones.default.mqtt.max_packet_size ``` + Prefix ``EMQX_`` is removed @@ -87,7 +87,7 @@ If set ``EMQX_NAME`` and ``EMQX_HOST``, and unset ``EMQX_NODE_NAME``, ``EMQX_NOD For example, set mqtt tcp port to 1883 -``docker run -d --name emqx -e EMQX_LISTENER__TCP__EXTERNAL=1883 -p 18083:18083 -p 1883:1883 emqx/emqx:latest`` +``docker run -d --name emqx -e EMQX_ZONES__DEFAULT__LISTENERS__MQTT_TCP__BIND=1883 -p 18083:18083 -p 1883:1883 emqx/emqx:latest`` #### EMQ Loaded Modules Configuration @@ -213,8 +213,8 @@ Let's create a static node list cluster from docker-compose. environment: - "EMQX_NAME=emqx" - "EMQX_HOST=node1.emqx.io" - - "EMQX_CLUSTER__DISCOVERY=static" - - "EMQX_CLUSTER__STATIC__SEEDS=emqx@node1.emqx.io, emqx@node2.emqx.io" + - "EMQX_CLUSTER__DISCOVERY_STRATEGY=static" + - "EMQX_CLUSTER__STATIC__SEEDS=[emqx@node1.emqx.io, emqx@node2.emqx.io]" networks: emqx-bridge: aliases: @@ -225,8 +225,8 @@ Let's create a static node list cluster from docker-compose. environment: - "EMQX_NAME=emqx" - "EMQX_HOST=node2.emqx.io" - - "EMQX_CLUSTER__DISCOVERY=static" - - "EMQX_CLUSTER__STATIC__SEEDS=emqx@node1.emqx.io, emqx@node2.emqx.io" + - "EMQX_CLUSTER__DISCOVERY_STRATEGY=static" + - "EMQX_CLUSTER__STATIC__SEEDS=[emqx@node1.emqx.io, emqx@node2.emqx.io]" networks: emqx-bridge: aliases: diff --git a/deploy/docker/docker-entrypoint.sh b/deploy/docker/docker-entrypoint.sh index 24498dc10..6af99ac97 100755 --- a/deploy/docker/docker-entrypoint.sh +++ b/deploy/docker/docker-entrypoint.sh @@ -42,52 +42,6 @@ if [[ -z "$EMQX_NODE_NAME" ]]; then export EMQX_NODE_NAME="$EMQX_NAME@$EMQX_HOST" fi -# Set hosts to prevent cluster mode failed - -if [[ -z "$EMQX_NODE__PROCESS_LIMIT" ]]; then - export EMQX_NODE__PROCESS_LIMIT=2097152 -fi - -if [[ -z "$EMQX_NODE__MAX_PORTS" ]]; then - export EMQX_NODE__MAX_PORTS=1048576 -fi - -if [[ -z "$EMQX_NODE__MAX_ETS_TABLES" ]]; then - export EMQX_NODE__MAX_ETS_TABLES=2097152 -fi - -if [[ -z "$EMQX_LISTENER__TCP__EXTERNAL__ACCEPTORS" ]]; then - export EMQX_LISTENER__TCP__EXTERNAL__ACCEPTORS=64 -fi - -if [[ -z "$EMQX_LISTENER__TCP__EXTERNAL__MAX_CONNECTIONS" ]]; then - export EMQX_LISTENER__TCP__EXTERNAL__MAX_CONNECTIONS=1024000 -fi - -if [[ -z "$EMQX_LISTENER__SSL__EXTERNAL__ACCEPTORS" ]]; then - export EMQX_LISTENER__SSL__EXTERNAL__ACCEPTORS=32 -fi - -if [[ -z "$EMQX_LISTENER__SSL__EXTERNAL__MAX_CONNECTIONS" ]]; then - export EMQX_LISTENER__SSL__EXTERNAL__MAX_CONNECTIONS=102400 -fi - -if [[ -z "$EMQX_LISTENER__WS__EXTERNAL__ACCEPTORS" ]]; then - export EMQX_LISTENER__WS__EXTERNAL__ACCEPTORS=16 -fi - -if [[ -z "$EMQX_LISTENER__WS__EXTERNAL__MAX_CONNECTIONS" ]]; then - export EMQX_LISTENER__WS__EXTERNAL__MAX_CONNECTIONS=102400 -fi - -if [[ -z "$EMQX_LISTENER__WSS__EXTERNAL__ACCEPTORS" ]]; then - export EMQX_LISTENER__WSS__EXTERNAL__ACCEPTORS=16 -fi - -if [[ -z "$EMQX_LISTENER__WSS__EXTERNAL__MAX_CONNECTIONS" ]]; then - export EMQX_LISTENER__WSS__EXTERNAL__MAX_CONNECTIONS=102400 -fi - # fill tuples on specific file # SYNOPSIS # fill_tuples FILE [ELEMENTS ...] diff --git a/rebar.config b/rebar.config index 56cf8c00b..8855eeeb4 100644 --- a/rebar.config +++ b/rebar.config @@ -47,7 +47,7 @@ , {gproc, {git, "https://github.com/uwiger/gproc", {tag, "0.8.0"}}} , {jiffy, {git, "https://github.com/emqx/jiffy", {tag, "1.0.5"}}} , {cowboy, {git, "https://github.com/emqx/cowboy", {tag, "2.8.2"}}} - , {esockd, {git, "https://github.com/emqx/esockd", {tag, "5.8.0"}}} + , {esockd, {git, "https://github.com/emqx/esockd", {tag, "5.8.2"}}} , {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.10.3"}}} , {gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.5.1"}}} , {minirest, {git, "https://github.com/emqx/minirest", {tag, "1.1.2"}}}