From 7229c7a8dd9036d26e44e5072f6cbbac9385186a Mon Sep 17 00:00:00 2001 From: Wade Barnes Date: Fri, 27 May 2022 13:27:06 -0700 Subject: [PATCH 1/5] Update setup_iptables script - Retain overall connection limit - Limit the number of connections per IP address - Rate limit the connections per IP address. - Add support for deleting (-d) the rules added by the script. - Update usage and warning documentation. Signed-off-by: Wade Barnes --- scripts/setup_iptables | 185 ++++++++++++++++++++++++++++++++++------- 1 file changed, 157 insertions(+), 28 deletions(-) diff --git a/scripts/setup_iptables b/scripts/setup_iptables index ea6e8c326..a94428f46 100755 --- a/scripts/setup_iptables +++ b/scripts/setup_iptables @@ -1,49 +1,178 @@ #!/bin/bash -if [ $# -lt 2 ]; then - echo "" - echo "Usage: $0 client_port connlimit"; - echo " client_port - node client port"; - echo " connlimit - clients connections limit"; - echo "" - exit 1; -fi +usage () { + cat <<-EOF + + Usage: + + $0 [-d] [per_ip_connlimit] [conn_rate_period] [conn_rate_limit] + + Options: + -d - Delete the corresponding rules. + Removes the rules corresponding to the supplied input parameters. + + Input Parameters: + client_port - Required. The node's client port. + overall_connlimit - Required. The overall connection limit for all clients. + per_ip_connlimit - Optional. The connection limit per IP address; defaults to 3. + conn_rate_period - Optional. The period for connection rate limiting; defaults to 60 seconds. + conn_rate_limit - Optional. The connection limit for connection rate limiting; default to 10. + + Example: + $0 9701 300 + +EOF + exit 1 +} + +check_setup () { + cat <<-EOF + + Warning: iptables and/or iptables-persistent is not installed, or permission denied. Client connections limit is not set. + + Please ensure iptables and iptables-persistent are both installed and iptables-persistent is enabled, and try running with sudo. + # To install iptables-persistent: + sudo apt-get install -y iptables-persistent + + # Make sure services are enabled on Debian or Ubuntu using the systemctl command: + sudo systemctl is-enabled netfilter-persistent.service + + # If not enable it: + sudo systemctl enable netfilter-persistent.service + + # Get status: + sudo systemctl status netfilter-persistent.service + +EOF + exit 1 +} -DPORT=$1 -CONN_LIMIT=$2 LOG_CHAIN=LOG_CONN_REJECT +OPERATION="add_rule" + +while getopts dh FLAG; do + case $FLAG in + d) + OPERATION="delete_rule" + DELETE=1 + ;; + h) + usage + ;; + \?) + usage + ;; + esac +done +shift $((OPTIND-1)) -add_rule_if_not_exist() -{ - RULE="$1" +DPORT=${1} +OVER_ALL_CONN_LIMIT=${2} + +# Default to 3 connections per IP. +CONN_LIMIT_PER_IP=${3:-3} + +# Default: Allow an IP to make up to 10 connection attempts every 100 seconds. +CONN_RATE_LIMIT_PERIOD=${4:-60} +CONN_RATE_LIMIT_LIMIT=${5:-10} + +add() { + if [ -z ${DELETE} ]; then + return 0 + else + return 1 + fi +} - cmd="iptables -C $RULE 2>/dev/null 1>&2" +delete() { + if [ ! -z ${DELETE} ]; then + return 0 + else + return 1 + fi +} + +rule_exists() { + RULE="${1}" + cmd="iptables -C ${RULE} 2>/dev/null 1>&2" + # echo $cmd eval $cmd + rtnCd=$? + if (( ${rtnCd} == 0 )); then + return 0 + else + return 1 + fi +} - if [ $? -eq 1 ]; then - cmd="iptables -A $RULE" +add_rule() { + RULE="${1}" + if ! rule_exists "${RULE}"; then + cmd="iptables -A ${RULE}" + # echo $cmd eval $cmd fi } +delete_rule() { + RULE="${1}" + if rule_exists "${RULE}"; then + cmd="iptables -D ${RULE}" + # echo $cmd + eval $cmd + fi +} + +save_rules() { + su -c "iptables-save > /etc/iptables/rules.v4 && ip6tables-save > /etc/iptables/rules.v6" +} + +if [ $# -lt 2 ]; then + usage +fi + # Check whether iptables installed and works -dpkg -s iptables 2>/dev/null 1>&2 && iptables -nL 2>/dev/null 1>&2 +dpkg -s iptables 2>/dev/null 1>&2 && iptables -nL 2>/dev/null 1>&2 && dpkg -s iptables-persistent 2>/dev/null 1>&2 if [ $? -eq 0 ]; then - # Create logging chain for rejected connections - iptables -N $LOG_CHAIN 2>/dev/null 1>&2 + + if add; then + echo "Adding iptable rules ..." + # Create logging chain for rejected connections + iptables -N ${LOG_CHAIN} 2>/dev/null 1>&2 + else + echo "Removing iptable rules ..." + fi # Append a rule that sets log level and log prefix - RULE="$LOG_CHAIN -j LOG --log-level warning --log-prefix \"connlimit: \"" - add_rule_if_not_exist "$RULE" + RULE="${LOG_CHAIN} -j LOG --log-level warning --log-prefix \"connlimit: \"" + ${OPERATION} "${RULE}" # Append a rule that finally rejects connection - RULE="$LOG_CHAIN -p tcp -j REJECT --reject-with tcp-reset" - add_rule_if_not_exist "$RULE" + RULE="${LOG_CHAIN} -p tcp -j REJECT --reject-with tcp-reset" + ${OPERATION} "${RULE}" + + # Append a rule to limit the total number of simultaneous client connections + RULE="INPUT -p tcp --syn --dport ${DPORT} -m connlimit --connlimit-above ${OVER_ALL_CONN_LIMIT} --connlimit-mask 0 -j ${LOG_CHAIN}" + ${OPERATION} "${RULE}" - # Append a rule to limit the number of simultaneous clients connections - RULE="INPUT -p tcp --syn --dport $DPORT -m connlimit --connlimit-above $CONN_LIMIT --connlimit-mask 0 -j $LOG_CHAIN" - add_rule_if_not_exist "$RULE" + # Append a rule to limit the number connections per IP address + RULE="INPUT -p tcp -m tcp --dport ${DPORT} --tcp-flags FIN,SYN,RST,ACK SYN -m connlimit --connlimit-above ${CONN_LIMIT_PER_IP} --connlimit-mask 32 --connlimit-saddr -j ${LOG_CHAIN}" + ${OPERATION} "${RULE}" + + # Append rules to rate limit connections + RULE="INPUT -p tcp -m tcp --dport ${DPORT} -m conntrack --ctstate NEW -m recent --set --name DEFAULT --mask 255.255.255.255 --rsource" + ${OPERATION} "${RULE}" + RULE="INPUT -p tcp -m tcp --dport ${DPORT} -m conntrack --ctstate NEW -m recent --update --seconds ${CONN_RATE_LIMIT_PERIOD} --hitcount ${CONN_RATE_LIMIT_LIMIT} --name DEFAULT --mask 255.255.255.255 --rsource -j ${LOG_CHAIN}" + ${OPERATION} "${RULE}" + + if delete; then + # Remove logging chain for rejected connections + iptables -X ${LOG_CHAIN} 2>/dev/null 1>&2 + fi + + # Save the rules + save_rules else - echo "Warning: iptables is not installed or permission denied, clients connections limit is not set." -fi + check_setup +fi \ No newline at end of file From 656b797d17ee679d9793027f343813bc6dedf40d Mon Sep 17 00:00:00 2001 From: Wade Barnes Date: Mon, 13 Jun 2022 12:34:43 -0700 Subject: [PATCH 2/5] Update setup_iptables - Add support for disabling IPv6. Signed-off-by: Wade Barnes --- scripts/setup_iptables | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/scripts/setup_iptables b/scripts/setup_iptables index a94428f46..e453a96d9 100755 --- a/scripts/setup_iptables +++ b/scripts/setup_iptables @@ -128,6 +128,33 @@ save_rules() { su -c "iptables-save > /etc/iptables/rules.v4 && ip6tables-save > /etc/iptables/rules.v6" } +disable_ipv6() { + echo "Disabling IPv6 ..." + ip6_conf_file="/etc/sysctl.d/60-custom-disable-ipv6.conf" + mkdir -p ${ip6_conf_file%/*} + + cat <<-EOF > ${ip6_conf_file} +net.ipv6.conf.all.disable_ipv6 = 1 +net.ipv6.conf.default.disable_ipv6 = 1 +net.ipv6.conf.lo.disable_ipv6 = 1 +EOF + + sysctl -p + systemctl restart procps +} + +enable_ipv6() { + echo "Enabling IPv6 ..." + ip6_conf_file="/etc/sysctl.d/60-custom-disable-ipv6.conf" + + if [ -f ${ip6_conf_file} ]; then + rm ${ip6_conf_file} + fi + sysctl -p + systemctl restart procps +} + + if [ $# -lt 2 ]; then usage fi @@ -173,6 +200,12 @@ if [ $? -eq 0 ]; then # Save the rules save_rules + + if add; then + disable_ipv6 + else + enable_ipv6 + fi else check_setup fi \ No newline at end of file From 69374ee9f9cc05fc31ddd3b5aa4d19a2194cafca Mon Sep 17 00:00:00 2001 From: Wade Barnes Date: Wed, 3 Aug 2022 14:08:29 -0700 Subject: [PATCH 3/5] Update setup_iptables script - Adjust defaults based on testing. - Remove default logging rule. - Add support for setting an explicit logging level. Recommend debug. Signed-off-by: Wade Barnes --- scripts/setup_iptables | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/scripts/setup_iptables b/scripts/setup_iptables index e453a96d9..3c387b688 100755 --- a/scripts/setup_iptables +++ b/scripts/setup_iptables @@ -14,12 +14,15 @@ usage () { Input Parameters: client_port - Required. The node's client port. overall_connlimit - Required. The overall connection limit for all clients. - per_ip_connlimit - Optional. The connection limit per IP address; defaults to 3. + per_ip_connlimit - Optional. The connection limit per IP address; defaults to 10. conn_rate_period - Optional. The period for connection rate limiting; defaults to 60 seconds. - conn_rate_limit - Optional. The connection limit for connection rate limiting; default to 10. + conn_rate_limit - Optional. The connection limit for connection rate limiting; default to 20. + logging_level - Optional. If used, this should be set to a level such as 'debug' so they can + easily be filtered from the logs and included only as needed. + Default is no logging. Example: - $0 9701 300 + $0 9702 15000 EOF exit 1 @@ -70,12 +73,14 @@ shift $((OPTIND-1)) DPORT=${1} OVER_ALL_CONN_LIMIT=${2} -# Default to 3 connections per IP. -CONN_LIMIT_PER_IP=${3:-3} +# Default to 10 connections per IP. +CONN_LIMIT_PER_IP=${3:-10} -# Default: Allow an IP to make up to 10 connection attempts every 100 seconds. +# Default: Allow an IP to make up to 20 connection attempts every 60 seconds. CONN_RATE_LIMIT_PERIOD=${4:-60} -CONN_RATE_LIMIT_LIMIT=${5:-10} +CONN_RATE_LIMIT_LIMIT=${5:-20} + +CONN_LOGGING_LEVEL=${6} add() { if [ -z ${DELETE} ]; then @@ -171,9 +176,16 @@ if [ $? -eq 0 ]; then echo "Removing iptable rules ..." fi - # Append a rule that sets log level and log prefix + # Make sure the previous default logging rule is removed. It causes too much CPU overhead under load. RULE="${LOG_CHAIN} -j LOG --log-level warning --log-prefix \"connlimit: \"" - ${OPERATION} "${RULE}" + delete_rule "${RULE}" + + # Append a rule that sets log level and log prefix + # Default to no logging unless a logging level is explicitly supplied. + if [ ! -z ${CONN_LOGGING_LEVEL} ]; then + RULE="${LOG_CHAIN} -j LOG --log-level ${CONN_LOGGING_LEVEL} --log-prefix \"connlimit: \"" + ${OPERATION} "${RULE}" + fi # Append a rule that finally rejects connection RULE="${LOG_CHAIN} -p tcp -j REJECT --reject-with tcp-reset" From 572162fdaa6daa6dfa258527f84561321f45d94f Mon Sep 17 00:00:00 2001 From: Wade Barnes Date: Thu, 18 Aug 2022 13:00:03 -0700 Subject: [PATCH 4/5] Update setup_iptables script - Make rate limiting optional. Defaults to off. - Add test mode `-t`, so you can see how your input settings will be applied before using them. Signed-off-by: Wade Barnes --- scripts/setup_iptables | 57 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/scripts/setup_iptables b/scripts/setup_iptables index 3c387b688..d29847d03 100755 --- a/scripts/setup_iptables +++ b/scripts/setup_iptables @@ -5,18 +5,19 @@ usage () { Usage: - $0 [-d] [per_ip_connlimit] [conn_rate_period] [conn_rate_limit] + $0 [-d] [per_ip_connlimit] [conn_rate_limit] [conn_rate_period] [logging_level] Options: -d - Delete the corresponding rules. Removes the rules corresponding to the supplied input parameters. + -t - Test mode. Prints out the list of input settings and exits. Input Parameters: client_port - Required. The node's client port. overall_connlimit - Required. The overall connection limit for all clients. per_ip_connlimit - Optional. The connection limit per IP address; defaults to 10. + conn_rate_limit - Optional. The connection limit for connection rate limiting; default to -1, off. conn_rate_period - Optional. The period for connection rate limiting; defaults to 60 seconds. - conn_rate_limit - Optional. The connection limit for connection rate limiting; default to 20. logging_level - Optional. If used, this should be set to a level such as 'debug' so they can easily be filtered from the logs and included only as needed. Default is no logging. @@ -51,15 +52,38 @@ EOF exit 1 } +print_settings() { + if (( ${CONN_RATE_LIMIT_LIMIT} <= 0 || ${CONN_RATE_LIMIT_PERIOD} <= 0 )); then + RATE_LIMIT_MESSAGE=" - Connection rate limiting is turned off." + fi + + cat <<-EOF + + client_port: ${DPORT} + overall_connlimit: ${OVER_ALL_CONN_LIMIT} + per_ip_connlimit: ${CONN_LIMIT_PER_IP} + conn_rate_limit: ${CONN_RATE_LIMIT_LIMIT} ${RATE_LIMIT_MESSAGE} + conn_rate_period: ${CONN_RATE_LIMIT_PERIOD} ${RATE_LIMIT_MESSAGE} + logging_level: ${CONN_LOGGING_LEVEL:-Not set, (off) default} + + OPERATION: ${OPERATION} + DELETE: ${DELETE} + TEST_MODE: ${TEST_MODE} +EOF +} + LOG_CHAIN=LOG_CONN_REJECT OPERATION="add_rule" -while getopts dh FLAG; do +while getopts dth FLAG; do case $FLAG in d) OPERATION="delete_rule" DELETE=1 ;; + t) + TEST_MODE=1 + ;; h) usage ;; @@ -76,9 +100,11 @@ OVER_ALL_CONN_LIMIT=${2} # Default to 10 connections per IP. CONN_LIMIT_PER_IP=${3:-10} -# Default: Allow an IP to make up to 20 connection attempts every 60 seconds. -CONN_RATE_LIMIT_PERIOD=${4:-60} -CONN_RATE_LIMIT_LIMIT=${5:-20} +# Default: Rate limiting disabled; -1. +CONN_RATE_LIMIT_LIMIT=${4:--1} + +# Default to a per minute rate limit. +CONN_RATE_LIMIT_PERIOD=${5:-60} CONN_LOGGING_LEVEL=${6} @@ -159,11 +185,15 @@ enable_ipv6() { systemctl restart procps } - if [ $# -lt 2 ]; then usage fi +if [ ! -z ${TEST_MODE} ]; then + print_settings + exit 0 +fi + # Check whether iptables installed and works dpkg -s iptables 2>/dev/null 1>&2 && iptables -nL 2>/dev/null 1>&2 && dpkg -s iptables-persistent 2>/dev/null 1>&2 if [ $? -eq 0 ]; then @@ -200,10 +230,15 @@ if [ $? -eq 0 ]; then ${OPERATION} "${RULE}" # Append rules to rate limit connections - RULE="INPUT -p tcp -m tcp --dport ${DPORT} -m conntrack --ctstate NEW -m recent --set --name DEFAULT --mask 255.255.255.255 --rsource" - ${OPERATION} "${RULE}" - RULE="INPUT -p tcp -m tcp --dport ${DPORT} -m conntrack --ctstate NEW -m recent --update --seconds ${CONN_RATE_LIMIT_PERIOD} --hitcount ${CONN_RATE_LIMIT_LIMIT} --name DEFAULT --mask 255.255.255.255 --rsource -j ${LOG_CHAIN}" - ${OPERATION} "${RULE}" + if (( ${CONN_RATE_LIMIT_LIMIT} > 0 && ${CONN_RATE_LIMIT_PERIOD} > 0 )); then + echo "Including settings for rate limiting ..." + RULE="INPUT -p tcp -m tcp --dport ${DPORT} -m conntrack --ctstate NEW -m recent --set --name DEFAULT --mask 255.255.255.255 --rsource" + ${OPERATION} "${RULE}" + RULE="INPUT -p tcp -m tcp --dport ${DPORT} -m conntrack --ctstate NEW -m recent --update --seconds ${CONN_RATE_LIMIT_PERIOD} --hitcount ${CONN_RATE_LIMIT_LIMIT} --name DEFAULT --mask 255.255.255.255 --rsource -j ${LOG_CHAIN}" + ${OPERATION} "${RULE}" + else + echo "Rate limiting is disabled, skipping settings for rate limiting ..." + fi if delete; then # Remove logging chain for rejected connections From 300be0b3afa17ebde078bd327b31092fa080fd54 Mon Sep 17 00:00:00 2001 From: Wade Barnes Date: Fri, 9 Sep 2022 07:59:17 -0700 Subject: [PATCH 5/5] Update the setup-iptables documentation. - Update with the latest connection limit recommendations. - Add instructions for updating the scripts and recommended settings on a node. Signed-off-by: Wade Barnes --- docs/source/setup-iptables.md | 95 ++++++++++++++++++----------------- 1 file changed, 48 insertions(+), 47 deletions(-) diff --git a/docs/source/setup-iptables.md b/docs/source/setup-iptables.md index 768cd3318..058c8b31c 100644 --- a/docs/source/setup-iptables.md +++ b/docs/source/setup-iptables.md @@ -1,70 +1,71 @@ # Setup iptables rules (recommended) -It is strongly recommended to add iptables (or some other firewall) rule that limits the number of simultaneous clients -connections for client port. -There are at least two important reasons for this: - - preventing the indy-node process from reaching of open file descriptors limit caused by clients connections - - preventing the indy-node process from large memory usage as ZeroMQ creates the separate queue for each TCP connection. +It is strongly recommended to add iptables (or some other firewall) rules to limit the number of simultaneous clients +connections to your node's client port. -NOTE: limitation of the number of *simultaneous clients connections* does not mean that we limit the -number of *simultaneous clients* the indy-node works with in any time. The IndySDK client does not keep -connection infinitely, it uses the same connection for request-response session with some optimisations, -so it's just about **connections**, **not** about **clients**. +There are at least two important reasons for this: + - preventing the indy-node process from exceeding the limit of open file descriptors due to an excessive number of clients connections. + - controlling the indy-node process's memory use, as ZeroMQ creates a separate queue for each TCP connection. -Also iptables can be used to deal with various DoS attacks (e.g. syn flood) but rules' parameters are not estimated yet. +NOTE: The limitation of the number of *simultaneous clients connections* does not mean that we limit the +number of *simultaneous clients* indy-node works with in any time. Connections are not left open infinitely. The same connection is used for a request-response session with some optimisations and then closed, therefore it's just about **connections**, **not** about **clients**. -NOTE: you should be a root to operate with iptables. +NOTE: You will need to have sudo privileges to work with iptables. +## Using indy scripts -## Setting up clients connections limit +For ease of use and for people that are not familiar with iptables we've added two scripts: + - [`setup_iptables`](https://github.com/hyperledger/indy-node/blob/main/scripts/setup_iptables): + - By default this scripts adds rules to iptables to limit the number of simultaneous clients connections for a specified port. + - To get a full list of options run `./setup_iptables -h` from the scripts directory. -#### Using raw iptables command or iptables front-end + - [`setup_indy_node_iptables`](https://github.com/hyperledger/indy-node/blob/main/scripts/setup_indy_node_iptables): + - A wrapper around `setup_iptables` which gets client port and connection limit settings from the `/etc/indy/indy.env` that is created by the `init_indy_node` script. -In case of deb installation the indy-node environment file /etc/indy/indy.env is created by `init_indy_node` script. -This environment file contains client port (NODE_CLIENT_PORT) and recommended clients connections limit (CLIENT_CONNECTIONS_LIMIT). -This parameters can be used to add the iptables rule for chain INPUT: +Which one you use depends on how you installed indy-node on your server. Refer to the [For deb package based installations](#for-deb-package-based-installations), and [For pip based installations](#for-pip-based-installations) sections below. -``` -# iptables -I INPUT -p tcp --syn --dport 9702 -m connlimit --connlimit-above 500 --connlimit-mask 0 -j REJECT --reject-with tcp-reset -``` -Some key options: - - --dport - a port for which limit is set - - --connlimit-above - connections limit, exceeding new connections will be rejected using TCP reset - - --connlimit-mask - group hosts using the prefix length, 0 means "all subnets" +### Updating the scripts and configuration -Corresponding fields should be set in case of some iptables front-end usage. +Before you run the scripts you should ensure you are using the latest scripts and recommended settings by following these steps while logged into your node: +1. Make a backup copy of the existing `setup_iptables` script by executing the command: + ``` + sudo cp /usr/local/bin/setup_iptables /usr/local/bin/setup_iptables_$(date "+%Y%m%d-%H%M%S") + ``` -#### Using indy scripts +1. Update the default client connection limit to 15000 in `/etc/indy/indy.env`. + - NOTE: + - `/etc/indy/indy.env` only exists for deb package based installations. + - `\1` is an excape sequence `\115000` is not a typo. + ``` + sudo sed -i -re "s/(^CLIENT_CONNECTIONS_LIMIT=).*$/\115000/" /etc/indy/indy.env + ``` -For ease of use and for people that are not familiar with iptables we've -added two scripts: - - setup_iptables: adds a rule to iptables to limit the number of simultaneous - clients connections for specified port; - - setup_indy_node_iptables: a wrapper for setup_iptables script which gets client - port and recommended connections limit from indy-node environment file that is created by init_indy_node script. +1. Download the latest version of the script. + ``` + sudo curl -o /usr/local/bin/setup_iptables https://raw.githubusercontent.com/hyperledger/indy-node/main/scripts/setup_iptables + ``` + The sha256 checksum for the current version of the script is `a0e4451cc49897dc38946091b245368c1f1360201f374a3ad121925f9aa80664` -Links to these scripts: - - https://github.com/hyperledger/indy-node/blob/master/scripts/setup_iptables - - https://github.com/hyperledger/indy-node/blob/master/scripts/setup_indy_node_iptables - -NOTE: for now the iptables chain for which the rule is added is not parameterized, -the rule is always added for INPUT chain, we can parameterize it in future if needed. +### For deb package based installations -###### For deb installation -To setup the limit of the number of simultaneous clients connections it is enough to run the following script without parameters +Run: ``` -# setup_indy_node_iptables +setup_indy_node_iptables ``` -This script gets client port and recommended connections limit from the indy-node environment file. +NOTE: + - This script should only be called *after* your node has been initialized using `init_indy_node`, to ensure `/etc/indy/indy.env` has been created. -NOTE: this script should be called *after* `init_indy_node` script. +### For pip based installations -###### For pip installation -The `setup_indy_node_iptables` script can not be used in case of pip installation as indy-node environment file does not exist, -use the `setup_iptables` script instead (9702 is a client port, 500 is recommended limit for now) +For pip based installations `/etc/indy/indy.env` does not exist, therefore `setup_indy_node_iptables` cannot be used. Instead you run `setup_iptables` directly. + +For example, if your client port is 9702, you would run: ``` -# setup_iptables 9702 500 +setup_iptables 9702 15000 ``` -In fact, the `setup_indy_node_iptables` script is just a wrapper for the `setup_iptables` script. + +## Using raw iptables command or iptables front-end + +If you are confident with using iptables, you may add additional rules as you see fit using iptables directly. \ No newline at end of file