You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2022/02/18 20:18:44 UTC

[tomcat] branch 8.5.x updated: Back-port the portOffset feature BZ 61171

This is an automated email from the ASF dual-hosted git repository.

markt pushed a commit to branch 8.5.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/8.5.x by this push:
     new b036c9e  Back-port the portOffset feature BZ 61171
b036c9e is described below

commit b036c9e034d496d434a596a50b02df8ab326a087
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Fri Feb 18 20:18:31 2022 +0000

    Back-port the portOffset feature BZ 61171
    
    https://bz.apache.org/bugzilla/show_bug.cgi?id=61171
---
 java/org/apache/catalina/Server.java               | 31 +++++++++++++++
 java/org/apache/catalina/connector/Connector.java  | 45 ++++++++++++++++++++--
 .../catalina/connector/mbeans-descriptors.xml      | 21 ++++++++--
 .../apache/catalina/core/LocalStrings.properties   |  2 +
 .../catalina/core/LocalStrings_fr.properties       |  2 +
 .../catalina/core/LocalStrings_ja.properties       |  2 +
 .../catalina/core/LocalStrings_ko.properties       |  2 +
 .../catalina/core/LocalStrings_zh_CN.properties    |  2 +
 java/org/apache/catalina/core/StandardServer.java  | 43 ++++++++++++++++++---
 .../apache/catalina/core/mbeans-descriptors.xml    | 11 +++++-
 java/org/apache/catalina/mbeans/MBeanFactory.java  |  2 +-
 java/org/apache/catalina/realm/RealmBase.java      |  2 +-
 .../apache/catalina/startup/AddPortOffsetRule.java | 36 +++++++++++++++++
 java/org/apache/catalina/startup/Catalina.java     | 27 ++++++++-----
 .../catalina/startup/LocalStrings.properties       |  3 +-
 .../catalina/startup/LocalStrings_fr.properties    |  3 +-
 .../catalina/startup/LocalStrings_ja.properties    |  2 +
 .../catalina/startup/LocalStrings_ko.properties    |  2 +
 .../catalina/startup/LocalStrings_zh_CN.properties |  2 +
 .../apache/catalina/valves/RemoteAddrValve.java    |  2 +-
 .../apache/catalina/valves/RemoteHostValve.java    |  3 +-
 java/org/apache/coyote/AbstractProtocol.java       | 27 +++++++++++--
 java/org/apache/coyote/LocalStrings.properties     |  1 +
 java/org/apache/coyote/LocalStrings_fr.properties  |  1 +
 java/org/apache/coyote/LocalStrings_ja.properties  |  1 +
 java/org/apache/coyote/LocalStrings_ko.properties  |  1 +
 .../apache/coyote/LocalStrings_zh_CN.properties    |  1 +
 .../apache/tomcat/util/net/AbstractEndpoint.java   | 23 ++++++++++-
 java/org/apache/tomcat/util/net/AprEndpoint.java   |  3 +-
 .../apache/tomcat/util/net/LocalStrings.properties |  1 +
 .../tomcat/util/net/LocalStrings_fr.properties     |  1 +
 .../tomcat/util/net/LocalStrings_ja.properties     |  1 +
 .../tomcat/util/net/LocalStrings_ko.properties     |  1 +
 .../tomcat/util/net/LocalStrings_zh_CN.properties  |  1 +
 java/org/apache/tomcat/util/net/Nio2Endpoint.java  |  2 +-
 java/org/apache/tomcat/util/net/NioEndpoint.java   |  6 +--
 webapps/docs/changelog.xml                         |  6 +++
 webapps/docs/config/server.xml                     |  6 +++
 38 files changed, 288 insertions(+), 40 deletions(-)

diff --git a/java/org/apache/catalina/Server.java b/java/org/apache/catalina/Server.java
index b9e254f..9cba300 100644
--- a/java/org/apache/catalina/Server.java
+++ b/java/org/apache/catalina/Server.java
@@ -71,6 +71,9 @@ public interface Server extends Lifecycle {
 
     /**
      * @return the port number we listen to for shutdown commands.
+     *
+     * @see #getPortOffset()
+     * @see #getPortWithOffset()
      */
     public int getPort();
 
@@ -79,9 +82,37 @@ public interface Server extends Lifecycle {
      * Set the port number we listen to for shutdown commands.
      *
      * @param port The new port number
+     *
+     * @see #setPortOffset(int)
      */
     public void setPort(int port);
 
+    /**
+     * Get the number that offsets the port used for shutdown commands.
+     * For example, if port is 8005, and portOffset is 1000,
+     * the server listens at 9005.
+     *
+     * @return the port offset
+     */
+    public int getPortOffset();
+
+    /**
+     * Set the number that offsets the server port used for shutdown commands.
+     * For example, if port is 8005, and you set portOffset to 1000,
+     * connector listens at 9005.
+     *
+     * @param portOffset sets the port offset
+     */
+    public void setPortOffset(int portOffset);
+
+    /**
+     * Get the actual port on which server is listening for the shutdown commands.
+     * If you do not set port offset, port is returned. If you set
+     * port offset, port offset + port is returned.
+     *
+     * @return the port with offset
+     */
+    public int getPortWithOffset();
 
     /**
      * @return the address on which we listen to for shutdown commands.
diff --git a/java/org/apache/catalina/connector/Connector.java b/java/org/apache/catalina/connector/Connector.java
index 320e1c4..c13b80e 100644
--- a/java/org/apache/catalina/connector/Connector.java
+++ b/java/org/apache/catalina/connector/Connector.java
@@ -33,6 +33,7 @@ import org.apache.catalina.LifecycleState;
 import org.apache.catalina.Service;
 import org.apache.catalina.core.AprLifecycleListener;
 import org.apache.catalina.util.LifecycleMBeanBase;
+import org.apache.coyote.AbstractProtocol;
 import org.apache.coyote.Adapter;
 import org.apache.coyote.ProtocolHandler;
 import org.apache.coyote.UpgradeProtocol;
@@ -598,6 +599,37 @@ public class Connector extends LifecycleMBeanBase  {
     }
 
 
+    public int getPortOffset() {
+        // Try shortcut that should work for nearly all uses first as it does
+        // not use reflection and is therefore faster.
+        if (protocolHandler instanceof AbstractProtocol<?>) {
+            return ((AbstractProtocol<?>) protocolHandler).getPortOffset();
+        }
+        // Fall back for custom protocol handlers not based on AbstractProtocol
+        Object port = getProperty("portOffset");
+        if (port instanceof Integer) {
+            return ((Integer) port).intValue();
+        }
+        // Usually means an invalid protocol has been configured.
+        return 0;
+    }
+
+
+    public void setPortOffset(int portOffset) {
+        setProperty("portOffset", String.valueOf(portOffset));
+    }
+
+
+    public int getPortWithOffset() {
+        int port = getPort();
+        // Zero is a special case and negative values are invalid
+        if (port > 0) {
+            return port + getPortOffset();
+        }
+        return port;
+    }
+
+
     /**
      * @return the port number on which this connector is listening to requests.
      * If the special value for {@link #getPort} of zero is used then this method
@@ -754,6 +786,11 @@ public class Connector extends LifecycleMBeanBase  {
     }
 
 
+    public int getRedirectPortWithOffset() {
+        return getRedirectPort() + getPortOffset();
+    }
+
+
     /**
      * @return the scheme that will be assigned to requests received
      * through this connector.  Default value is "http".
@@ -992,7 +1029,7 @@ public class Connector extends LifecycleMBeanBase  {
         StringBuilder sb = new StringBuilder("type=");
         sb.append(type);
         sb.append(",port=");
-        int port = getPort();
+        int port = getPortWithOffset();
         if (port > 0) {
             sb.append(port);
         } else {
@@ -1088,9 +1125,9 @@ public class Connector extends LifecycleMBeanBase  {
     protected void startInternal() throws LifecycleException {
 
         // Validate settings before starting
-        if (getPort() < 0) {
+        if (getPortWithOffset() < 0) {
             throw new LifecycleException(sm.getString(
-                    "coyoteConnector.invalidPort", Integer.valueOf(getPort())));
+                    "coyoteConnector.invalidPort", Integer.valueOf(getPortWithOffset())));
         }
 
         setState(LifecycleState.STARTING);
@@ -1150,7 +1187,7 @@ public class Connector extends LifecycleMBeanBase  {
         StringBuilder sb = new StringBuilder("Connector[");
         sb.append(getProtocol());
         sb.append('-');
-        int port = getPort();
+        int port = getPortWithOffset();
         if (port > 0) {
             sb.append(port);
         } else {
diff --git a/java/org/apache/catalina/connector/mbeans-descriptors.xml b/java/org/apache/catalina/connector/mbeans-descriptors.xml
index 0bae24c..ccbb8fe 100644
--- a/java/org/apache/catalina/connector/mbeans-descriptors.xml
+++ b/java/org/apache/catalina/connector/mbeans-descriptors.xml
@@ -137,8 +137,18 @@
                  type="int"/>
 
     <attribute   name="port"
-          description="The port number on which this connector is configured to listen for requests. The special value of 0 means select a random free port when the socket is bound."
-                type="int"/>
+          description="The port number (excluding any offset) on which this connector is configured to listen for requests. The special value of 0 means select a random free port when the socket is bound."
+                 type="int"/>
+
+    <attribute   name="portOffset"
+          description="The offset that will be applied to port to determine the actual port number used."
+                 type="int"
+            writeable="false"/>
+
+    <attribute   name="portWithOffset"
+          description="The actual port number (including any offset) on which this connector is configured to listen for requests."
+                 type="int"
+            writeable="false"/>
 
     <!-- Common -->
     <attribute   name="processorCache"
@@ -163,9 +173,14 @@
                  type="int"/>
 
     <attribute   name="redirectPort"
-          description="The redirect port for non-SSL to SSL redirects"
+          description="The redirect port (excluding any offset) for non-SSL to SSL redirects"
                  type="int"/>
 
+    <attribute   name="redirectPortWithOffset"
+          description="The actual redirect port (including any offset) for non-SSL to SSL redirects."
+                 type="int"
+            writeable="false"/>
+
     <attribute   name="scheme"
           description="Protocol name for this Connector (http, https)"
                  type="java.lang.String"/>
diff --git a/java/org/apache/catalina/core/LocalStrings.properties b/java/org/apache/catalina/core/LocalStrings.properties
index 1e5d56e..eefd403 100644
--- a/java/org/apache/catalina/core/LocalStrings.properties
+++ b/java/org/apache/catalina/core/LocalStrings.properties
@@ -257,7 +257,9 @@ standardServer.accept.error=An IO exception occurred trying to accept on the soc
 standardServer.accept.readError=An IO exception occurred trying to read the shutdown command
 standardServer.accept.security=A security error occurred trying to accept on the socket listening for the shutdown command
 standardServer.accept.timeout=The socket listening for the shutdown command experienced an unexpected timeout [{0}] milliseconds after the call to accept(). Is this an instance of bug 56684?
+standardServer.awaitSocket.fail=Failed to create server shutdown socket on address [{0}] and port [{1}] (base port [{2}] and offset [{3}])
 standardServer.invalidShutdownCommand=Invalid shutdown command [{0}] received
+standardServer.portOffset.invalid=The value [{0}] for portOffset is not valid as portOffset may not be negative
 standardServer.shutdownViaPort=A valid shutdown command was received via the shutdown port. Stopping the Server instance.
 standardServer.storeConfig.contextError=Error storing context [{0}] configuration
 standardServer.storeConfig.error=Error storing server configuration
diff --git a/java/org/apache/catalina/core/LocalStrings_fr.properties b/java/org/apache/catalina/core/LocalStrings_fr.properties
index 57a8c85..053f94b 100644
--- a/java/org/apache/catalina/core/LocalStrings_fr.properties
+++ b/java/org/apache/catalina/core/LocalStrings_fr.properties
@@ -254,7 +254,9 @@ standardServer.accept.error=Une erreur d'IO s'est produite en essayant d'accepte
 standardServer.accept.readError=Une erreur d'IO s'est produite lors de la lecture de la commande d'arrêt
 standardServer.accept.security=Une erreur de sécurité s'est produite en essayant d'accepter sur le socket qui attend la commande d'arrêt
 standardServer.accept.timeout=Le socket qui écoute en attendant la commande d''arrêt a rencontré un délai d''attente dépassé inattendu [{0}] millisecondes après l''appel à accept()
+standardServer.awaitSocket.fail=Impossible de créer le sokcet d''arrêt du serveur à l''adresse [{0}] et au port [{1}] (port de base [{2}] et offset [{3}])
 standardServer.invalidShutdownCommand=Une commande d''arrêt invalide [{0}] a été reçue
+standardServer.portOffset.invalid=La valeur [{0}] pour portOffset est invalide car elle ne peut pas être négative
 standardServer.shutdownViaPort=Une commande d'arrêt valide a été reçue sur le port d'arrêt, arrêt de l'instance du serveur
 standardServer.storeConfig.contextError=Erreur lors de l''enregistrement de la configuration du contexte [{0}]
 standardServer.storeConfig.error=Erreur lors de l'enregistrement de la configuration du serveur
diff --git a/java/org/apache/catalina/core/LocalStrings_ja.properties b/java/org/apache/catalina/core/LocalStrings_ja.properties
index ceb9a19..1d4e635 100644
--- a/java/org/apache/catalina/core/LocalStrings_ja.properties
+++ b/java/org/apache/catalina/core/LocalStrings_ja.properties
@@ -254,7 +254,9 @@ standardServer.accept.error=シャットダウンコマンドを受信するソ
 standardServer.accept.readError=シャットダウンコマンドの読み取り時に入出力例外が発生しました。
 standardServer.accept.security=シャットダウンコマンドを受信するソケットの accept でセキュリティエラーを発生しました。
 standardServer.accept.timeout=シャットダウンコマンドをリスンするソケットは、accept()の呼び出し後に予期しないタイムアウト [{0}] ミリ秒を経験しました。 これはバグ56684の一例ですか?
+standardServer.awaitSocket.fail=アドレス [{0}] のポート番号 [{1}] にサーバー停止ソケットを作成できませんでした (基本ポート番号は [{2}]、オフセットは [{3}] です)
 standardServer.invalidShutdownCommand=不正なシャットダウンコマンド [{0}] を受信しました。
+standardServer.portOffset.invalid=portOffsetが負でない可能性があるため、portOffsetの値[{0}]は無効です
 standardServer.shutdownViaPort=有効なシャットダウンコマンドがシャットダウンポート経由で受信されました。 サーバーインスタンスを停止します。
 standardServer.storeConfig.contextError=コンテキスト [{0}] の構成格納中のエラー
 standardServer.storeConfig.error=サーバー構成格納中のエラー
diff --git a/java/org/apache/catalina/core/LocalStrings_ko.properties b/java/org/apache/catalina/core/LocalStrings_ko.properties
index ef3ec3a..7200f11 100644
--- a/java/org/apache/catalina/core/LocalStrings_ko.properties
+++ b/java/org/apache/catalina/core/LocalStrings_ko.properties
@@ -254,7 +254,9 @@ standardServer.accept.error=셧다운 명령을 위해 listen하고 있는 소
 standardServer.accept.readError=셧다운 명령을 읽으려 시도하는 중 IOException이 발생했습니다.
 standardServer.accept.security=셧다운 명령을 위해 listen하고 있는 소켓에서, accept를 시도하는 중, 보안 오류가 발생했습니다.
 standardServer.accept.timeout=셧다운 명령을 위해 listen하고 있는 소켓이, accept()를 호출 한 후, 예기치 않은 제한 시간 초과([{0}] 밀리초)를 발생시켰습니다. 버그 56684가 발생한 경우일까요?
+standardServer.awaitSocket.fail=주소 [{0}]와(과) 포트 [{1}]에, 서버 셧다운 소켓을 생성하지 못했습니다. (base 포트 [{2}], offset [{3}])
 standardServer.invalidShutdownCommand=유효하지 않은 셧다운 명령 [{0}]을(를) 받았습니다.
+standardServer.portOffset.invalid=portOffset이 음수여서는 안되기에, portOffset의 값 [{0}]은(는) 유효하지 않습니다.
 standardServer.shutdownViaPort=셧다운 포트를 통해 유효한 셧다운 명령을 받았습니다. 서버 인스턴스를 중지시킵니다.
 standardServer.storeConfig.contextError=컨텍스트 [{0}]의 설정을 저장하는 중 오류 발생
 standardServer.storeConfig.error=서버 설정을 저장하는 중 오류 발생
diff --git a/java/org/apache/catalina/core/LocalStrings_zh_CN.properties b/java/org/apache/catalina/core/LocalStrings_zh_CN.properties
index 6d929e4..4eba3c5 100644
--- a/java/org/apache/catalina/core/LocalStrings_zh_CN.properties
+++ b/java/org/apache/catalina/core/LocalStrings_zh_CN.properties
@@ -254,7 +254,9 @@ standardServer.accept.error=尝试在侦听shutdown命令的套接字上接受IO
 standardServer.accept.readError=尝试读取关机命令时发生IO异常
 standardServer.accept.security=试图在侦听shutdown命令的套接字上接受时发生安全错误
 standardServer.accept.timeout=在调用accept()方法之后,侦听shutdown命令的套接字经历了意外的超时[{0}]毫秒。 这是bug 56684的一个例子?
+standardServer.awaitSocket.fail=无法在地址[{0}]和端口[{1}]上创建服务器关闭套接字(基本端口[{2}]和偏移量[{3}])
 standardServer.invalidShutdownCommand=收到无效的关闭命令[{0}]
+standardServer.portOffset.invalid=portOffset [{0}] 值是无效的,因为portOffset不能是负数
 standardServer.shutdownViaPort=通过关闭端口接收到有效的关闭命令。正在停止服务器实例。
 standardServer.storeConfig.contextError=存储上下文[{0}]配置时出错
 standardServer.storeConfig.error=存储服务器配置时出错
diff --git a/java/org/apache/catalina/core/StandardServer.java b/java/org/apache/catalina/core/StandardServer.java
index 03e512b..9f35d2e 100644
--- a/java/org/apache/catalina/core/StandardServer.java
+++ b/java/org/apache/catalina/core/StandardServer.java
@@ -113,6 +113,8 @@ public final class StandardServer extends LifecycleMBeanBase implements Server {
      */
     private int port = 8005;
 
+    private int portOffset = 0;
+
     /**
      * The address on which we wait for shutdown commands.
      */
@@ -270,6 +272,35 @@ public final class StandardServer extends LifecycleMBeanBase implements Server {
     }
 
 
+    @Override
+    public int getPortOffset() {
+        return portOffset;
+    }
+
+
+    @Override
+    public void setPortOffset(int portOffset) {
+        if (portOffset < 0) {
+            throw new IllegalArgumentException(
+                    sm.getString("standardServer.portOffset.invalid", Integer.valueOf(portOffset)));
+        }
+        this.portOffset = portOffset;
+    }
+
+
+    @Override
+    public int getPortWithOffset() {
+        // Non-positive port values have special meanings and the offset should
+        // not apply.
+        int port = getPort();
+        if (port > 0) {
+            return port + getPortOffset();
+        } else {
+            return port;
+        }
+    }
+
+
     /**
      * Return the address on which we listen to for shutdown commands.
      */
@@ -389,11 +420,11 @@ public final class StandardServer extends LifecycleMBeanBase implements Server {
     @Override
     public void await() {
         // Negative values - don't wait on port - tomcat is embedded or we just don't like ports
-        if (port == -2) {
+        if (getPortWithOffset() == -2) {
             // undocumented yet - for embedding apps that are around, alive.
             return;
         }
-        if (port==-1) {
+        if (getPortWithOffset() == -1) {
             try {
                 awaitThread = Thread.currentThread();
                 while(!stopAwait) {
@@ -411,12 +442,12 @@ public final class StandardServer extends LifecycleMBeanBase implements Server {
 
         // Set up a server socket to wait on
         try {
-            awaitSocket = new ServerSocket(port, 1,
+            awaitSocket = new ServerSocket(getPortWithOffset(), 1,
                     InetAddress.getByName(address));
         } catch (IOException e) {
-            log.error("StandardServer.await: create[" + address
-                               + ":" + port
-                               + "]: ", e);
+            log.error(sm.getString("standardServer.awaitSocket.fail", address,
+                    String.valueOf(getPortWithOffset()), String.valueOf(getPort()),
+                    String.valueOf(getPortOffset())), e);
             return;
         }
 
diff --git a/java/org/apache/catalina/core/mbeans-descriptors.xml b/java/org/apache/catalina/core/mbeans-descriptors.xml
index ca33d5e..e99ab0e 100644
--- a/java/org/apache/catalina/core/mbeans-descriptors.xml
+++ b/java/org/apache/catalina/core/mbeans-descriptors.xml
@@ -1312,9 +1312,18 @@
                type="java.lang.Object"/>
 
     <attribute name="port"
-               description="TCP port for shutdown messages"
+               description="TCP port (excluding any offset) for shutdown messages"
                type="int"/>
 
+    <attribute name="portOffset"
+               description="The offset applied to port and to the port attributes of any nested connectors"
+               type="int"/>
+
+    <attribute name="portWithOffset"
+               description="Actual TCP port (including any offset) for shutdown messages"
+               type="int"
+               writeable="false"/>
+
     <attribute name="serverInfo"
                description="Tomcat server release identifier"
                type="java.lang.String"
diff --git a/java/org/apache/catalina/mbeans/MBeanFactory.java b/java/org/apache/catalina/mbeans/MBeanFactory.java
index 05f1b2b..3099638 100644
--- a/java/org/apache/catalina/mbeans/MBeanFactory.java
+++ b/java/org/apache/catalina/mbeans/MBeanFactory.java
@@ -743,7 +743,7 @@ public class MBeanFactory {
             if (objConnAddress != null) {
                 connAddress = ((InetAddress) objConnAddress).getHostAddress();
             }
-            String connPort = "" + conn.getPort();
+            String connPort = "" + conn.getPortWithOffset();
 
             if (address == null) {
                 // Don't combine this with outer if or we could get an NPE in
diff --git a/java/org/apache/catalina/realm/RealmBase.java b/java/org/apache/catalina/realm/RealmBase.java
index 1eb43b3..8e17a71 100644
--- a/java/org/apache/catalina/realm/RealmBase.java
+++ b/java/org/apache/catalina/realm/RealmBase.java
@@ -1038,7 +1038,7 @@ public abstract class RealmBase extends LifecycleMBeanBase implements org.apache
             return true;
         }
         // Initialize variables we need to determine the appropriate action
-        int redirectPort = request.getConnector().getRedirectPort();
+        int redirectPort = request.getConnector().getRedirectPortWithOffset();
 
         // Is redirecting disabled?
         if (redirectPort <= 0) {
diff --git a/java/org/apache/catalina/startup/AddPortOffsetRule.java b/java/org/apache/catalina/startup/AddPortOffsetRule.java
new file mode 100644
index 0000000..9b961cb
--- /dev/null
+++ b/java/org/apache/catalina/startup/AddPortOffsetRule.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.catalina.startup;
+
+import org.apache.catalina.Server;
+import org.apache.catalina.connector.Connector;
+import org.apache.tomcat.util.digester.Rule;
+import org.xml.sax.Attributes;
+
+public class AddPortOffsetRule extends Rule {
+
+    // Set portOffset on all the connectors based on portOffset in the Server
+    @Override
+    public void begin(String namespace, String name, Attributes attributes) throws Exception {
+
+        Connector conn = (Connector) digester.peek();
+        Server server = (Server) digester.peek(2);
+
+        int portOffset = server.getPortOffset();
+        conn.setPortOffset(portOffset);
+    }
+}
\ No newline at end of file
diff --git a/java/org/apache/catalina/startup/Catalina.java b/java/org/apache/catalina/startup/Catalina.java
index 15e16a9..9c6b263 100644
--- a/java/org/apache/catalina/startup/Catalina.java
+++ b/java/org/apache/catalina/startup/Catalina.java
@@ -35,6 +35,7 @@ import org.apache.catalina.Container;
 import org.apache.catalina.LifecycleException;
 import org.apache.catalina.LifecycleState;
 import org.apache.catalina.Server;
+import org.apache.catalina.connector.Connector;
 import org.apache.catalina.core.StandardContext;
 import org.apache.catalina.security.SecurityConfig;
 import org.apache.juli.ClassLoaderLogManager;
@@ -281,6 +282,7 @@ public class Catalina {
         digester.setValidating(false);
         digester.setRulesValidation(true);
         Map<Class<?>, List<String>> fakeAttributes = new HashMap<>();
+        // Ignore className on all elements
         List<String> objectAttrs = new ArrayList<>();
         objectAttrs.add("className");
         fakeAttributes.put(Object.class, objectAttrs);
@@ -288,6 +290,10 @@ public class Catalina {
         List<String> contextAttrs = new ArrayList<>();
         contextAttrs.add("source");
         fakeAttributes.put(StandardContext.class, contextAttrs);
+        // Ignore Connector attribute used internally but set on Server
+        List<String> connectorAttrs = new ArrayList<>();
+        connectorAttrs.add("portOffset");
+        fakeAttributes.put(Connector.class, connectorAttrs);
         digester.setFakeAttributes(fakeAttributes);
         digester.setUseContextClassLoader(true);
 
@@ -341,7 +347,6 @@ public class Catalina {
                             "addExecutor",
                             "org.apache.catalina.Executor");
 
-
         digester.addRule("Server/Service/Connector",
                          new ConnectorCreateRule());
         digester.addRule("Server/Service/Connector",
@@ -350,6 +355,8 @@ public class Catalina {
                             "addConnector",
                             "org.apache.catalina.connector.Connector");
 
+        digester.addRule("Server/Service/Connector", new AddPortOffsetRule());
+
         digester.addObjectCreate("Server/Service/Connector/SSLHostConfig",
                                  "org.apache.tomcat.util.net.SSLHostConfig");
         digester.addSetProperties("Server/Service/Connector/SSLHostConfig");
@@ -484,7 +491,7 @@ public class Catalina {
                 digester.push(this);
                 digester.parse(is);
             } catch (Exception e) {
-                log.error("Catalina.stop: ", e);
+                log.error(sm.getString("catalina.stopError"));
                 System.exit(1);
             }
         } else {
@@ -493,15 +500,15 @@ public class Catalina {
                 s.stop();
                 s.destroy();
             } catch (LifecycleException e) {
-                log.error("Catalina.stop: ", e);
+                log.error(sm.getString("catalina.stopError"), e);
             }
             return;
         }
 
         // Stop the existing server
         s = getServer();
-        if (s.getPort()>0) {
-            try (Socket socket = new Socket(s.getAddress(), s.getPort());
+        if (s.getPortWithOffset() > 0) {
+            try (Socket socket = new Socket(s.getAddress(), s.getPortWithOffset());
                     OutputStream stream = socket.getOutputStream()) {
                 String shutdown = s.getShutdown();
                 for (int i = 0; i < shutdown.length(); i++) {
@@ -509,13 +516,13 @@ public class Catalina {
                 }
                 stream.flush();
             } catch (ConnectException ce) {
-                log.error(sm.getString("catalina.stopServer.connectException",
-                                       s.getAddress(),
-                                       String.valueOf(s.getPort())));
-                log.error("Catalina.stop: ", ce);
+                log.error(sm.getString("catalina.stopServer.connectException", s.getAddress(),
+                        String.valueOf(s.getPortWithOffset()), String.valueOf(s.getPort()),
+                        String.valueOf(s.getPortOffset())));
+                log.error(sm.getString("catalina.stopError"), ce);
                 System.exit(1);
             } catch (IOException e) {
-                log.error("Catalina.stop: ", e);
+                log.error(sm.getString("catalina.stopError"), e);
                 System.exit(1);
             }
         } else {
diff --git a/java/org/apache/catalina/startup/LocalStrings.properties b/java/org/apache/catalina/startup/LocalStrings.properties
index bc68705..34ad65c 100644
--- a/java/org/apache/catalina/startup/LocalStrings.properties
+++ b/java/org/apache/catalina/startup/LocalStrings.properties
@@ -18,8 +18,9 @@ catalina.noCluster=Cluster RuleSet not found due to [{0}]. Cluster configuration
 catalina.noNaming=Naming environment is disabled
 catalina.serverStartFail=The required Server component failed to start so Tomcat is unable to start.
 catalina.shutdownHookFail=The shutdown hook experienced an error while trying to stop the server
+catalina.stopError=Error stopping Catalina
 catalina.stopServer=No shutdown port configured. Shut down server through OS signal. Server not shut down.
-catalina.stopServer.connectException=Could not contact [{0}:{1}]. Tomcat may not be running.
+catalina.stopServer.connectException=Could not contact [{0}:{1}] (base port [{2}] and offset [{3}]). Tomcat may not be running.
 
 connector.noSetExecutor=Connector [{0}] does not support external executors. Method setExecutor(java.util.concurrent.Executor) not found.
 connector.noSetSSLImplementationName=Connector [{0}] does not support changing the SSL implementation. Method setSslImplementationName(String) not found.
diff --git a/java/org/apache/catalina/startup/LocalStrings_fr.properties b/java/org/apache/catalina/startup/LocalStrings_fr.properties
index 74a7d65..9a5d592 100644
--- a/java/org/apache/catalina/startup/LocalStrings_fr.properties
+++ b/java/org/apache/catalina/startup/LocalStrings_fr.properties
@@ -18,7 +18,8 @@ catalina.noCluster=le RuleSet du cluster n''a pas été trouvé à cause de [{0}
 catalina.noNaming=L'environnement de noms JNDI est désactivé
 catalina.serverStartFail=Le composant Server requis n'a pas démarré, en conséquence Tomcat ne peut démarrer.
 catalina.shutdownHookFail=Le crochet d'arrêt a rencontré une erreur en tentant d'arrêter le serveur
-catalina.stopServer=Pas de port d'arrêt configuré, l'arrêt du serveur se fera via un signal du système d'exploitation ; le serveur est en cours d'exécution
+catalina.stopError=Erreur lors de l'arrêt de Catalina
+catalina.stopServer.connectException=Impossible de se connecter à [{0} : {1}] (port de base [{2}] et offset [{3}]), Tomcat peut ne pas être en cours d''exécution
 
 connector.noSetExecutor=Le connecteur [{0}] ne supporte pas les exécuteurs externes, la méthode setExecutor(java.util.concurrent.Executor) n''a pas été trouvée
 connector.noSetSSLImplementationName=Le connecteur [{0}] ne supporte pas le changement de l''implémentation SSL, car la méthode setSslImplementationName(String) n''a pas été trouvée
diff --git a/java/org/apache/catalina/startup/LocalStrings_ja.properties b/java/org/apache/catalina/startup/LocalStrings_ja.properties
index 7d2e815..0a7668e 100644
--- a/java/org/apache/catalina/startup/LocalStrings_ja.properties
+++ b/java/org/apache/catalina/startup/LocalStrings_ja.properties
@@ -18,7 +18,9 @@ catalina.noCluster=[{0}]のためにクラスタルールセットが見つか
 catalina.noNaming=ネーミング環境が無効です。
 catalina.serverStartFail=必要なサーバーコンポーネントを開始できなかったため、Tomcat を開始できませんでした。
 catalina.shutdownHookFail=サーバーの停止中にシャットダウンフックでエラーが発生しました。
+catalina.stopError=Catalinaの停止エラー
 catalina.stopServer=シャットダウンポートが設定されていません。 OSシグナルでServerをシャットダウンします。 サーバはシャットダウンしません。
+catalina.stopServer.connectException=[{0}:{1}] に接続できませんでした (base ポート [{2}]、offset [{3}])。Tomcatが実行中でない可能性があります。
 
 connector.noSetExecutor=Connector {0}]は外部エグゼキュータをサポートしていません。 メソッドsetExecutor(java.util.concurrent.Executor)が見つかりません。
 connector.noSetSSLImplementationName=コネクター [{0}] は SSL 実装の変更に対応していません。setSslImplementationName(String) メソッドがありません。
diff --git a/java/org/apache/catalina/startup/LocalStrings_ko.properties b/java/org/apache/catalina/startup/LocalStrings_ko.properties
index 2b4bd56..9284c0e 100644
--- a/java/org/apache/catalina/startup/LocalStrings_ko.properties
+++ b/java/org/apache/catalina/startup/LocalStrings_ko.properties
@@ -18,7 +18,9 @@ catalina.noCluster=[{0}](으)로 인하여 클러스터 RuleSet을 찾을 수 
 catalina.noNaming=Naming 환경은 사용 불능 상태입니다.
 catalina.serverStartFail=필수 항목인 서버 구성요소가 제대로 시작되지 못하여, Tomcat이 시작될 수 없습니다.
 catalina.shutdownHookFail=서버를 중지시키려는 과정에서, 셧다운 훅에서 오류가 발생했습니다.
+catalina.stopError=Catalina를 중지시키는 중 오류 발생
 catalina.stopServer=셧다운 포트가 설정되지 않았습니다. OS 시그널을 통해 서버를 셧다운합니다. 서버는 아직 셧다운되지 않았습니다.
+catalina.stopServer.connectException=[{0}:{1}] (base 포트 [{2}] 그리고 offset [{3}])와(과) 연결할 수 없었습니다. Tomcat이 실행 중이지 않을 수 있습니다.
 
 connector.noSetExecutor=Connector [{0}]은(는) 외부 Executor들을 지원하지 않습니다. 메소드 setExecutor(java.util.concurrent.Executor)를 찾을 수 없습니다.
 connector.noSetSSLImplementationName=Connector [{0}]은(는) SSL 구현을 변경하는 것을 지원하지 않습니다. setSslImplementationName(String) 메소드를 찾을 수 없습니다.
diff --git a/java/org/apache/catalina/startup/LocalStrings_zh_CN.properties b/java/org/apache/catalina/startup/LocalStrings_zh_CN.properties
index cc4477c..28543df 100644
--- a/java/org/apache/catalina/startup/LocalStrings_zh_CN.properties
+++ b/java/org/apache/catalina/startup/LocalStrings_zh_CN.properties
@@ -18,7 +18,9 @@ catalina.noCluster=由于[{0}]未找到群集Ruleset。已禁用群集配置。
 catalina.noNaming=命名环境已禁用
 catalina.serverStartFail=所必需的服务组件启动失败,所以无法启动Tomcat
 catalina.shutdownHookFail=关闭挂钩在尝试停止服务器时遇到错误
+catalina.stopError=停止 Catalina 时出错
 catalina.stopServer=未配置关闭端口。通过OS信号关闭服务器。服务器未关闭。
+catalina.stopServer.connectException=无法联系[{0}:{1}](基端口[{2}]和偏移量[{3}])。Tomcat可能不在运行。
 
 connector.noSetExecutor=连接器[{0}]不支持外部执行器。找不到方法setExecutor(java.util.concurrent.Executor)
 connector.noSetSSLImplementationName=连接器[{0}]不支持更改SSL实现。找不到方法setslimplementationname(String)。
diff --git a/java/org/apache/catalina/valves/RemoteAddrValve.java b/java/org/apache/catalina/valves/RemoteAddrValve.java
index 77183ac..cea229c 100644
--- a/java/org/apache/catalina/valves/RemoteAddrValve.java
+++ b/java/org/apache/catalina/valves/RemoteAddrValve.java
@@ -51,7 +51,7 @@ public final class RemoteAddrValve extends RequestFilterValve {
         }
         if (getAddConnectorPort()) {
             property = property + ";" +
-                request.getConnector().getPort();
+                request.getConnector().getPortWithOffset();
         }
         process(property, request, response);
     }
diff --git a/java/org/apache/catalina/valves/RemoteHostValve.java b/java/org/apache/catalina/valves/RemoteHostValve.java
index d94389b..90c6b8b 100644
--- a/java/org/apache/catalina/valves/RemoteHostValve.java
+++ b/java/org/apache/catalina/valves/RemoteHostValve.java
@@ -43,7 +43,8 @@ public final class RemoteHostValve extends RequestFilterValve {
     public void invoke(Request request, Response response) throws IOException, ServletException {
         String property;
         if (getAddConnectorPort()) {
-            property = request.getRequest().getRemoteHost() + ";" + request.getConnector().getPort();
+            property = request.getRequest().getRemoteHost() + ";" +
+                    request.getConnector().getPortWithOffset();
         } else {
             property = request.getRequest().getRemoteHost();
         }
diff --git a/java/org/apache/coyote/AbstractProtocol.java b/java/org/apache/coyote/AbstractProtocol.java
index 59aff52..aa67737 100644
--- a/java/org/apache/coyote/AbstractProtocol.java
+++ b/java/org/apache/coyote/AbstractProtocol.java
@@ -310,6 +310,15 @@ public abstract class AbstractProtocol<S> implements ProtocolHandler,
     }
 
 
+    public int getPortOffset() { return endpoint.getPortOffset(); }
+    public void setPortOffset(int portOffset) {
+        endpoint.setPortOffset(portOffset);
+    }
+
+
+    public int getPortWithOffset() { return endpoint.getPortWithOffset(); }
+
+
     public int getLocalPort() { return endpoint.getLocalPort(); }
 
     /*
@@ -406,7 +415,7 @@ public abstract class AbstractProtocol<S> implements ProtocolHandler,
             name.append(getAddress().getHostAddress());
             name.append('-');
         }
-        int port = getPort();
+        int port = getPortWithOffset();
         if (port == 0) {
             // Auto binding is in use. Check if port is known
             name.append("auto-");
@@ -571,9 +580,9 @@ public abstract class AbstractProtocol<S> implements ProtocolHandler,
 
         StringBuilder name = new StringBuilder(getDomain());
         name.append(":type=ProtocolHandler,port=");
-        int port = getPort();
+        int port = getPortWithOffset();
         if (port > 0) {
-            name.append(getPort());
+            name.append(port);
         } else {
             name.append("auto-");
             name.append(getNameIndex());
@@ -599,6 +608,7 @@ public abstract class AbstractProtocol<S> implements ProtocolHandler,
     public void init() throws Exception {
         if (getLog().isInfoEnabled()) {
             getLog().info(sm.getString("abstractProtocolHandler.init", getName()));
+            logPortOffset();
         }
 
         if (oname == null) {
@@ -628,6 +638,7 @@ public abstract class AbstractProtocol<S> implements ProtocolHandler,
     public void start() throws Exception {
         if (getLog().isInfoEnabled()) {
             getLog().info(sm.getString("abstractProtocolHandler.start", getName()));
+            logPortOffset();
         }
 
         endpoint.start();
@@ -669,6 +680,7 @@ public abstract class AbstractProtocol<S> implements ProtocolHandler,
     public void stop() throws Exception {
         if(getLog().isInfoEnabled()) {
             getLog().info(sm.getString("abstractProtocolHandler.stop", getName()));
+            logPortOffset();
         }
 
         if (asyncTimeout != null) {
@@ -683,6 +695,7 @@ public abstract class AbstractProtocol<S> implements ProtocolHandler,
     public void destroy() throws Exception {
         if(getLog().isInfoEnabled()) {
             getLog().info(sm.getString("abstractProtocolHandler.destroy", getName()));
+            logPortOffset();
         }
 
         try {
@@ -724,6 +737,14 @@ public abstract class AbstractProtocol<S> implements ProtocolHandler,
     }
 
 
+    private void logPortOffset() {
+        if (getPort() != getPortWithOffset()) {
+            getLog().info(sm.getString("abstractProtocolHandler.portOffset", getName(),
+                    String.valueOf(getPort()), String.valueOf(getPortOffset())));
+        }
+    }
+
+
     // ------------------------------------------- Connection handler base class
 
     protected static class ConnectionHandler<S> implements AbstractEndpoint.Handler<S> {
diff --git a/java/org/apache/coyote/LocalStrings.properties b/java/org/apache/coyote/LocalStrings.properties
index a56c2a7..ed7f4c0 100644
--- a/java/org/apache/coyote/LocalStrings.properties
+++ b/java/org/apache/coyote/LocalStrings.properties
@@ -45,6 +45,7 @@ abstractProtocolHandler.destroyError=Failed to destroy end point associated with
 abstractProtocolHandler.getAttribute=Get attribute [{0}] with value [{1}]
 abstractProtocolHandler.init=Initializing ProtocolHandler [{0}]
 abstractProtocolHandler.pause=Pausing ProtocolHandler [{0}]
+abstractProtocolHandler.portOffset=ProtocolHandler [{0}] is configured with a base port of [{1}] and a port offset of [{2}]
 abstractProtocolHandler.pauseError=Failed to pause end point associated with ProtocolHandler [{0}]
 abstractProtocolHandler.resume=Resuming ProtocolHandler [{0}]
 abstractProtocolHandler.resumeError=Failed to resume end point associated with ProtocolHandler [{0}]
diff --git a/java/org/apache/coyote/LocalStrings_fr.properties b/java/org/apache/coyote/LocalStrings_fr.properties
index 9373185..fca89d3 100644
--- a/java/org/apache/coyote/LocalStrings_fr.properties
+++ b/java/org/apache/coyote/LocalStrings_fr.properties
@@ -43,6 +43,7 @@ abstractProtocol.waitingProcessor.remove=Retrait du processeur [{0}] des process
 abstractProtocolHandler.destroy=Destruction du gestionnaire de protocole [{0}]
 abstractProtocolHandler.init=Initialisation du gestionnaire de protocole [{0}]
 abstractProtocolHandler.pause=Le gestionnaire de protocole [{0}] est mis en pause
+abstractProtocolHandler.portOffset=Le gestionnaire de protocole [{0}] est configuré avec un port de base [{1}] et un offset de port [{2}]
 abstractProtocolHandler.resume=Reprise du gestionnaire de protocole [{0}]
 abstractProtocolHandler.setAttribute=Fixe l''attribut [{0}] avec la valeur [{1}]
 abstractProtocolHandler.start=Démarrage du gestionnaire de protocole [{0}]
diff --git a/java/org/apache/coyote/LocalStrings_ja.properties b/java/org/apache/coyote/LocalStrings_ja.properties
index 085a461..3edf6f3 100644
--- a/java/org/apache/coyote/LocalStrings_ja.properties
+++ b/java/org/apache/coyote/LocalStrings_ja.properties
@@ -43,6 +43,7 @@ abstractProtocol.waitingProcessor.remove=待機中のプロセッサから [{0}]
 abstractProtocolHandler.destroy=ProtocolHandler [{0}] を破棄します。
 abstractProtocolHandler.init=プロトコルハンドラ [{0}] を初期化します。
 abstractProtocolHandler.pause=ProtocolHandler [{0}] を一時停止します。
+abstractProtocolHandler.portOffset=ProtocolHandler [{0}] は、ベースポート [{1}] とポートオフセット [{2}] で構成されています
 abstractProtocolHandler.resume=プロトコルハンドラー [{0}] を再開します。
 abstractProtocolHandler.setAttribute=属性[{0}]に値[{1}]を設定する
 abstractProtocolHandler.start=プロトコルハンドラー [{0}] を開始しました。
diff --git a/java/org/apache/coyote/LocalStrings_ko.properties b/java/org/apache/coyote/LocalStrings_ko.properties
index 1ff7979..f737b26 100644
--- a/java/org/apache/coyote/LocalStrings_ko.properties
+++ b/java/org/apache/coyote/LocalStrings_ko.properties
@@ -43,6 +43,7 @@ abstractProtocol.waitingProcessor.remove=대기 프로세서에서 제거된 프
 abstractProtocolHandler.destroy=프로토콜 핸들러 [{0}]을(를) 소멸시킵니다.
 abstractProtocolHandler.init=프로토콜 핸들러 [{0}]을(를) 초기화합니다.
 abstractProtocolHandler.pause=프로토콜 핸들러 [{0}]을(를) 일시 정지 중
+abstractProtocolHandler.portOffset=프로토콜 핸들러 [{0}]이(가), base port [{1}], 그리고 port offset [{2}](으)로 설정됩니다.
 abstractProtocolHandler.resume=프로토콜 핸들러 [{0}]을(를) 재개합니다.
 abstractProtocolHandler.setAttribute=속성 [{0}]에 값 [{1}]을(를) 설정
 abstractProtocolHandler.start=프로토콜 핸들러 [{0}]을(를) 시작합니다.
diff --git a/java/org/apache/coyote/LocalStrings_zh_CN.properties b/java/org/apache/coyote/LocalStrings_zh_CN.properties
index 5fd8e35..68ac675 100644
--- a/java/org/apache/coyote/LocalStrings_zh_CN.properties
+++ b/java/org/apache/coyote/LocalStrings_zh_CN.properties
@@ -43,6 +43,7 @@ abstractProtocol.waitingProcessor.remove=从等待的处理器中移除处理器
 abstractProtocolHandler.destroy=正在摧毁协议处理器 [{0}]
 abstractProtocolHandler.init=初始化协议处理器 [{0}]
 abstractProtocolHandler.pause=暂停ProtocolHandler[{0}]
+abstractProtocolHandler.portOffset=ProtocolHandler[{0}]的基本端口为[{1}],端口偏移量为[{2}]
 abstractProtocolHandler.resume=正在恢复ProtocolHandler[{0}]
 abstractProtocolHandler.setAttribute=使用值[{1}]设置属性[{0}]
 abstractProtocolHandler.start=开始协议处理句柄[{0}]
diff --git a/java/org/apache/tomcat/util/net/AbstractEndpoint.java b/java/org/apache/tomcat/util/net/AbstractEndpoint.java
index f6d4b6d..4397151 100644
--- a/java/org/apache/tomcat/util/net/AbstractEndpoint.java
+++ b/java/org/apache/tomcat/util/net/AbstractEndpoint.java
@@ -565,6 +565,27 @@ public abstract class AbstractEndpoint<S,U> {
     public void setPort(int port ) { this.port=port; }
 
 
+    private int portOffset = 0;
+    public int getPortOffset() { return portOffset; }
+    public void setPortOffset(int portOffset ) {
+        if (portOffset < 0) {
+            throw new IllegalArgumentException(
+                    sm.getString("endpoint.portOffset.invalid", Integer.valueOf(portOffset)));
+        }
+        this.portOffset = portOffset;
+    }
+
+
+    public int getPortWithOffset() {
+        // Zero is a special case and negative values are invalid
+        int port = getPort();
+        if (port > 0) {
+            return port + getPortOffset();
+        }
+        return port;
+    }
+
+
     public final int getLocalPort() {
         try {
             InetSocketAddress localAddress = getLocalAddress();
@@ -1074,7 +1095,7 @@ public abstract class AbstractEndpoint<S,U> {
             ExceptionUtils.handleThrowable(t);
             if (getLog().isDebugEnabled()) {
                 getLog().debug(sm.getString(
-                        "endpoint.debug.unlock.fail", String.valueOf(getPort())), t);
+                        "endpoint.debug.unlock.fail", String.valueOf(getPortWithOffset())), t);
             }
         }
     }
diff --git a/java/org/apache/tomcat/util/net/AprEndpoint.java b/java/org/apache/tomcat/util/net/AprEndpoint.java
index e61c2d5..11f3f9f 100644
--- a/java/org/apache/tomcat/util/net/AprEndpoint.java
+++ b/java/org/apache/tomcat/util/net/AprEndpoint.java
@@ -303,8 +303,7 @@ public class AprEndpoint extends AbstractEndpoint<Long,Long> implements SNICallB
         }
         int family = Socket.APR_UNSPEC;
 
-        long inetAddress = Address.info(addressStr, family,
-                getPort(), 0, rootPool);
+        long inetAddress = Address.info(addressStr, family, getPortWithOffset(), 0, rootPool);
         // Create the APR server socket
         int saFamily = Address.getInfo(inetAddress).family;
         serverSock = Socket.create(saFamily,
diff --git a/java/org/apache/tomcat/util/net/LocalStrings.properties b/java/org/apache/tomcat/util/net/LocalStrings.properties
index a715752..22a161d 100644
--- a/java/org/apache/tomcat/util/net/LocalStrings.properties
+++ b/java/org/apache/tomcat/util/net/LocalStrings.properties
@@ -113,6 +113,7 @@ endpoint.poll.fail=Critical poller failure (restarting poller): [{0}] [{1}]
 endpoint.poll.initfail=Poller creation failed
 endpoint.poll.limitedpollsize=Failed to create poller with specified size of [{0}]
 endpoint.pollerThreadStop=The poller thread failed to stop in a timely manner
+endpoint.portOffset.invalid=The value [{0}] for portOffset is not valid as portOffset may not be negative
 endpoint.process.fail=Error allocating socket processor
 endpoint.processing.fail=Error running socket processor
 endpoint.rejectedExecution=Socket processing request was rejected for [{0}]
diff --git a/java/org/apache/tomcat/util/net/LocalStrings_fr.properties b/java/org/apache/tomcat/util/net/LocalStrings_fr.properties
index 08b7dd0..402d02e 100644
--- a/java/org/apache/tomcat/util/net/LocalStrings_fr.properties
+++ b/java/org/apache/tomcat/util/net/LocalStrings_fr.properties
@@ -109,6 +109,7 @@ endpoint.poll.fail=Echec critique du poller, redémarrage : [{0}] [{1}]
 endpoint.poll.initfail=Echec de création du poller
 endpoint.poll.limitedpollsize=Echec de création d''un poller avec la taille spécifiée [{0}]
 endpoint.pollerThreadStop=Le thread du poller ne s'est pas arrêté dans le temps imparti
+endpoint.portOffset.invalid=La valeur [{0}] pour portOffset est invalide car elle ne peut être négative
 endpoint.process.fail=Erreur lors de l'allocation d'un processeur de socket
 endpoint.processing.fail=Erreur lors de l’exécution du processeur du socket
 endpoint.rejectedExecution=Le traitement du socket a été rejeté pour [{0}]
diff --git a/java/org/apache/tomcat/util/net/LocalStrings_ja.properties b/java/org/apache/tomcat/util/net/LocalStrings_ja.properties
index 5e98182..64e6dc6 100644
--- a/java/org/apache/tomcat/util/net/LocalStrings_ja.properties
+++ b/java/org/apache/tomcat/util/net/LocalStrings_ja.properties
@@ -109,6 +109,7 @@ endpoint.poll.fail=重大なPoller障害(Pollerの再始動):[{0}] [{1}]
 endpoint.poll.initfail=Pollerの作成に失敗しました。
 endpoint.poll.limitedpollsize=サイズ [{0}] の Poller インスタンスを作成できません。
 endpoint.pollerThreadStop=時間内にPoller スレッドを停止できませんでした。
+endpoint.portOffset.invalid=portOffset に不正な値 [{0}] が指定されました。負の値は指定できません。
 endpoint.process.fail=ソケットプロセッサーの割り当て中にエラーが発生しました。
 endpoint.processing.fail=ソケットプロセッサの実行中エラー
 endpoint.rejectedExecution=[{0}]のため、ソケット処理要求が拒否されました。
diff --git a/java/org/apache/tomcat/util/net/LocalStrings_ko.properties b/java/org/apache/tomcat/util/net/LocalStrings_ko.properties
index b7be7b1..53ff26e 100644
--- a/java/org/apache/tomcat/util/net/LocalStrings_ko.properties
+++ b/java/org/apache/tomcat/util/net/LocalStrings_ko.properties
@@ -109,6 +109,7 @@ endpoint.poll.fail=심각한 poller 실패 (poller를 재시작합니다): [{0}]
 endpoint.poll.initfail=Poller 생성이 실패했습니다.
 endpoint.poll.limitedpollsize=지정된 크기 [{0}]로 poller를 생성하지 못했습니다.
 endpoint.pollerThreadStop=Poller 쓰레드가 적절한 시간 내에 중지되지 못했습니다.
+endpoint.portOffset.invalid=portOffset 값은 음수일 수 없기에, portOffset을 위한 값 [{0}]은(는) 유효하지 않습니다.
 endpoint.process.fail=소켓 프로세서를 할당하는 중 오류 발생
 endpoint.processing.fail=소켓 프로세서 실행 중 오류 발생
 endpoint.rejectedExecution=[{0}]을(를) 위한 소켓 처리 요청이 거절되었습니다.
diff --git a/java/org/apache/tomcat/util/net/LocalStrings_zh_CN.properties b/java/org/apache/tomcat/util/net/LocalStrings_zh_CN.properties
index eaee514..ef0920f 100644
--- a/java/org/apache/tomcat/util/net/LocalStrings_zh_CN.properties
+++ b/java/org/apache/tomcat/util/net/LocalStrings_zh_CN.properties
@@ -109,6 +109,7 @@ endpoint.poll.fail=严重轮询器故障(重新启动轮询器)[{0}] [{1}]
 endpoint.poll.initfail=轮询器创建失败。
 endpoint.poll.limitedpollsize=创建轮询器失败,大小:[{0}]
 endpoint.pollerThreadStop=轮询线程未能及时停止。
+endpoint.portOffset.invalid=portOffset的值[{0}]无效,因为portOffset不能为负。
 endpoint.process.fail=分配 socket 处理器出错
 endpoint.processing.fail=运行.套接字处理器出错
 endpoint.rejectedExecution=[{0}]的套接字处理请求被拒绝
diff --git a/java/org/apache/tomcat/util/net/Nio2Endpoint.java b/java/org/apache/tomcat/util/net/Nio2Endpoint.java
index 60d24d2..b82dae2 100644
--- a/java/org/apache/tomcat/util/net/Nio2Endpoint.java
+++ b/java/org/apache/tomcat/util/net/Nio2Endpoint.java
@@ -137,7 +137,7 @@ public class Nio2Endpoint extends AbstractJsseEndpoint<Nio2Channel,AsynchronousS
 
         serverSock = AsynchronousServerSocketChannel.open(threadGroup);
         socketProperties.setProperties(serverSock);
-        InetSocketAddress addr = (getAddress()!=null?new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));
+        InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
         serverSock.bind(addr, getAcceptCount());
 
         // Initialize SSL if needed
diff --git a/java/org/apache/tomcat/util/net/NioEndpoint.java b/java/org/apache/tomcat/util/net/NioEndpoint.java
index 8c8fdf2..87e44f0 100644
--- a/java/org/apache/tomcat/util/net/NioEndpoint.java
+++ b/java/org/apache/tomcat/util/net/NioEndpoint.java
@@ -220,7 +220,7 @@ public class NioEndpoint extends AbstractJsseEndpoint<NioChannel,SocketChannel>
         } else {
             serverSock = ServerSocketChannel.open();
             socketProperties.setProperties(serverSock.socket());
-            InetSocketAddress addr = (getAddress()!=null?new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));
+            InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
             serverSock.socket().bind(addr,getAcceptCount());
         }
         serverSock.configureBlocking(true); //mimic APR behavior
@@ -318,7 +318,7 @@ public class NioEndpoint extends AbstractJsseEndpoint<NioChannel,SocketChannel>
     public void unbind() throws Exception {
         if (log.isDebugEnabled()) {
             log.debug("Destroy initiated for " +
-                    new InetSocketAddress(getAddress(),getPort()));
+                    new InetSocketAddress(getAddress(),getPortWithOffset()));
         }
         if (running) {
             stop();
@@ -335,7 +335,7 @@ public class NioEndpoint extends AbstractJsseEndpoint<NioChannel,SocketChannel>
         }
         if (log.isDebugEnabled()) {
             log.debug("Destroy completed for " +
-                    new InetSocketAddress(getAddress(), getPort()));
+                    new InetSocketAddress(getAddress(), getPortWithOffset()));
         }
     }
 
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 691d311..ccaddf4 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -108,6 +108,12 @@
   <subsection name="Catalina">
     <changelog>
       <add>
+        <bug>61171</bug>: Add the <code>portOffset</code> attribute to the
+        <code>Server</code> element which is added to the configured shutdown
+        and <code>Connector</code> ports. Based on a patch by Marek Czernek.
+        (markt)
+      </add>
+      <add>
         <bug>64080</bug>: Enhance the graceful shutdown feature. Includes a new
         option for <code>StandardService</code>,
         <code>gracefulStopAwaitMillis</code>, that allows a time to be
diff --git a/webapps/docs/config/server.xml b/webapps/docs/config/server.xml
index 8227a40..36b7055 100644
--- a/webapps/docs/config/server.xml
+++ b/webapps/docs/config/server.xml
@@ -75,6 +75,12 @@
       gracefully.</p>
     </attribute>
 
+    <attribute name="portOffset" required="false">
+      <p>The offset to apply to <code>port</code> and to the ports of any
+      nested connectors. It must be a non-negative integer. If not specified,
+      the default value of <code>0</code> is used.</p>
+    </attribute>
+
     <attribute name="shutdown" required="true">
       <p>The command string that must be received via a TCP/IP connection
       to the specified port number, in order to shut down Tomcat.</p>

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org