تخطَّ إلى المحتوى

تحليل تقني معمّق

تصليب Tomcat ضد هجمات DDoS: مجمّعات خيوط Connector والمُهَل والطبقة الأمامية

آخر تحديث: أغسطس 2026 · مجمّع Connector والمُهَل والطبقة الأمامية الإلزامية · زمن القراءة ~17 دقيقة

مجمّع ثابت من خيوط عمل خضراء مسحوبة من خزّان محدود، وطابور انتظار قصير عند حافته؛ حين يمتلئ المجمّع والطابور تُرفض الوافدات الجديدة بدل أن تغرق المجمّع.

يعيش تعرّض Tomcat لهجمات DDoS في Connector الخاص به. إنّ maxThreads وacceptCount وmaxConnections وconnectionTimeout هي الإعدادات الحاملة، وبروتوكول NIO يمنع الاتصال البطيء من أن يكلّف خيطاً كاملاً. لكنّ القرار الأكبر معماريّ: Tomcat عارٍ على المنفذ 8080 مكشوف للإنترنت بطرقٍ لا تصلحها أيّ قيمة في Connector، ولذلك تأتي الطبقة الأمامية أولاً.

يصلّب هذا الدليل Apache Tomcat نفسه ضد الجزء المستنفِد للاتصالات والخيوط من هجوم DDoS. وهو يختلف عن أدلة خوادم الويب في نقطة بنيوية يجب قولها أولاً: Tomcat خادم تطبيقات، والتصميم الإنتاجي القياسي لا يعرّضه مباشرةً. أمامه يقف خادم ويب مُصلَّب أو موازِن حِمل، يُنهي الاتصال ويمرّر الطلبات النظيفة عبر شبكة خاصة. ومعظم دفاعات هذه السلسلة تعيش في تلك الطبقة الأمامية.

لذلك فضبط Connector أدناه هو الخطّ الثاني، المطبَّق خلف الطبقة الأمامية. وهو مهمّ، لأنّ الطبقة الأمامية قد تتعطّل أو تُتجاوَز في شبكة داخلية أو تمرّر هجوماً لم تلتقطه. لكنّه ليس بديلاً عن وضع طبقة مُصلَّبة في المقدّمة. وكلّ إعداد يأتي بقيمته وبعدّاد JMX أو السجل الذي يُظهر عمله.

أشكال الهجوم وإعدادات Tomcat التي تجيب عنها
الجهازشكل الهجومإعداد Tomcatالتحقّق
ترويسة بطيئة / جسم بطيء (Slowloris)connectionTimeout؛ بروتوكول NIO؛ keepAliveTimeoutManager: خيوط في الحالة R؛ ‏%D في سجل الوصول
سيل الاتصالاتmaxConnections؛ acceptCount (طابور نظام التشغيل)jmx Connector connectionCount
استنفاد مجمّع الخيوطmaxThreads؛ Executor مشترك بين الموصّلاتjmx ThreadPool currentThreadsBusy
إساءة الطلبات الكبيرةmaxHttpHeaderSize، maxParameterCount، maxPostSizeنسبة 400 في سجل الوصول
التعرّض المباشر للإنترنتلا تعرّض 8080؛ الطبقة الأمامية تُنهي أولاًnetstat: 8080 مربوط بـ localhost/الداخلي فقط

يعيد الصفّ الأخير تأطير البقية: Tomcat خادم تطبيقات، والتصميم القياسي الأكثر قابلية للدفاع يضع أمامه خادم ويب مُصلَّباً أو وكيلاً. وضبط Connector أدناه يفترض وجود تلك الطبقة الأمامية.

0. أولاً القياسات الأساسية: اكشف مقاييس Connector

الحالة الحيّة لـ Tomcat في JMX وسجل الوصول. فعّل سجل وصول يسجّل زمن المعالجة، واقرأ مجمّع خيوط Connector طوال أسبوع عادي:

<!-- server.xml، داخل <Host> — ‏%D زمن الطلب بالمللي ثانية، ‏%S الجلسة -->
<Valve className="org.apache.catalina.valves.AccessLogValve"
       directory="logs" prefix="access." suffix=".log"
       pattern="%h %t &quot;%r&quot; %s %b %D" />
# مجمّع الخيوط وعدد الاتصالات عبر JMX (jconsole أو jmxterm بلا واجهة)
# Catalina:type=ThreadPool,name="http-nio-8080"  -> currentThreadsBusy, connectionCount
# أبطأ الطلبات من سجل الوصول (الحقل الأخير %D بالمللي ثانية)
awk '{print $NF, $0}' logs/access.*.log | sort -rn | head

# عدد الطلبات لكل عميل، أسبوع عادي
awk '{print $1}' logs/access.*.log | sort | uniq -c | sort -rn | head

currentThreadsBusy مقابل maxThreads، وconnectionCount مقابل maxConnections، هما القياسان الأساسيان اللذان تُضبَط بهما كلّ قيمةٍ أدناه.

1. الطبقة الأمامية أولاً

قبل لمس server.xml، تأكّد أنّ Tomcat غير مكشوف مباشرةً:

# ينبغي أن يستمع HTTP Connector إلى localhost أو عنوان داخلي، لا إلى 0.0.0.0 على مضيف عام
ss -ltnp | grep 8080

إن كان 8080 مربوطاً بواجهة عامة فذلك أول ما يُصلَح. اربطه بالعنوان الداخلي الذي تستخدمه الطبقة الأمامية، ودع nginx أو Apache httpd يُنهي الاتصال المواجه للإنترنت. هاتان الطبقتان الأماميتان مُصلَّبتان في دليلَي nginx وApache، وهما تحملان TLS وطرد الاتصالات البطيئة وتحديد المعدّل أفضل بكثير من Connector.

<!-- server.xml — اربط بالواجهة الداخلية فقط -->
<Connector port="8080" address="10.0.0.5"
           protocol="org.apache.coyote.http11.Http11NioProtocol"
           ... />

2. بروتوكول Connector: NIO لا الحاجب

تحدّد خاصية protocol هل يكلّف الاتصال البطيء خيطاً. استخدم موصّل NIO؛ فالموصّل الحاجب القديم كان ينفق خيطاً لكل اتصال ويجعل Slowloris رخيصاً:

<Connector port="8080" address="10.0.0.5"
           protocol="org.apache.coyote.http11.Http11NioProtocol"
           connectionTimeout="20000"
           maxThreads="400"
           minSpareThreads="25"
           maxConnections="8192"
           acceptCount="200"
           maxKeepAliveRequests="100"
           keepAliveTimeout="15000" />
# أكّد البروتوكول العامل
grep -i 'protocol=' conf/server.xml
# في السجل عند الإقلاع: "Initializing ProtocolHandler [http-nio-8080]"
grep -i 'ProtocolHandler' logs/catalina.out | tail

3. حدود الاتصال الثلاثة، تُحجَّم معاً

maxConnections وacceptCount وmaxThreads ثلاثة حدود بالتتابع: اتصالات مُمسَكة، اتصالات في الطابور، طلبات مُعالَجة. حجّمها كمجموعة:

maxConnections  = 8192   # اتصالات مقبولة ومُمسَكة في آنٍ واحد
acceptCount     = 200    # طابور نظام التشغيل عند امتلاء maxConnections (يقابل listen backlog)
maxThreads      = 400    # طلبات تُعالَج في آنٍ واحد
minSpareThreads = 25     # خيوط تُبقى دافئة

maxConnections كبير خلف maxThreads صغير ليس حمايةً؛ بل طابور اتصالات طويل ينتظر خيوطاً قليلة، وينهار ببطء وإرباك. حجّم maxThreads بما يحتمله التطبيق والكومة فعلاً تحت الحِمل، ثم اضبط maxConnections مضاعفاً صحّياً فوقه وacceptCount طابوراً قصيراً لا كبيراً. الطابور الكبير يؤخّر فقط الرفضَ الذي يحمي المجمّع.

# الخيوط المشغولة مقابل السقف، حيّاً
# JMX: Catalina:type=ThreadPool,name="http-nio-8080" currentThreadsBusy / maxThreads
# حين يصبح busy == maxThreads وترتفع connectionCount، فالاختناق هو المجمّع

4. Executor مشترك بين الموصّلات

إن كان للخادم أكثر من موصّل (HTTP وHTTPS)، فإنّ Executor مشتركاً يحدّ إجمالي الخيوط بين الاثنين بدل أن يمسك كلُّ موصّل مجمّعه:

<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
          maxThreads="400" minSpareThreads="25"
          maxIdleTime="60000" prestartminSpareThreads="true" />

<Connector executor="tomcatThreadPool" port="8080" ... />
<Connector executor="tomcatThreadPool" port="8443" ... />

هذا يمنع سيلاً على موصّل من تجويع الآخر، ويعطي رقماً واحداً تُحجَّم به الكومة.

5. المُهَل وحدود حجم الطلب

connectionTimeout شبكة الأمان ضد Slowloris؛ وحدود الحجم تغلق سطح الطلبات الكبيرة:

<Connector ...
   connectionTimeout="20000"       <!-- مللي ثانية لاستقبال سطر الطلب + الترويسات -->
   keepAliveTimeout="15000"        <!-- مللي ثانية خمول قبل إغلاق keep-alive -->
   maxKeepAliveRequests="100"
   maxHttpHeaderSize="8192"        <!-- بايت -->
   maxParameterCount="1000"        <!-- معاملات الطلب؛ تحدّ إساءة تصادم التجزئة -->
   maxPostSize="2097152"           <!-- جسم طلب 2 ميغابايت -->
   maxSwallowSize="2097152" />

يجدر ضبط maxParameterCount صراحةً: عددٌ غير محدود من المعاملات وسيلة رخيصة لحرق المعالج في تحليل الطلب. والقيمة السالبة تعني بلا حدّ، وهو افتراض خاطئ لخادم مجاور للإنترنت حتى خلف وكيل.

# الطلبات الكبيرة/المرفوضة تظهر كـ 400 في سجل الوصول
awk '$4==400' logs/access.*.log | wc -l

6. عنوان العميل خلف الطبقة الأمامية

مع وجود طبقة أمامية، يرى كلُّ سطرٍ في سجل الوصول وكلُّ قاعدة RemoteAddrValve الوكيلَ ما لم يُضبَط RemoteIpValve:

<!-- server.xml، داخل <Host> -->
<Valve className="org.apache.catalina.valves.RemoteIpValve"
       remoteIpHeader="X-Forwarded-For"
       protocolHeader="X-Forwarded-Proto"
       internalProxies="10\.\d+\.\d+\.\d+|172\.1[6-9]\.\d+\.\d+" />

بدونه تنسب السجلات كل طلب إلى الطبقة الأمامية، وتصبح كلُّ قاعدة تعتمد على العنوان بلا معنى، وهو انهيار الوكيل المتكرّر عبر هذه السلسلة كلها.

7. اختياري: StuckThreadDetectionValve

تحت هجومٍ يدفع التطبيق إلى طلبات بطيئة أو معلّقة، يسجّل صمّام الخيوط العالقة الخيوطَ التي تجاوزت عتبةً، فيحوّل تسرّب خيوطٍ خفيّاً إلى تنبيه:

<Valve className="org.apache.catalina.valves.StuckThreadDetectionValve"
       threshold="60" />

الإشارات التي تُوصَل بالمراقبة

# 1. الخيوط المشغولة / maxThreads (JMX currentThreadsBusy) — تشبّع المجمّع
# 2. connectionCount / maxConnections (JMX) — هجوم إمساك الاتصالات
# 3. نسبة 400 — سيل طلبات كبيرة/معطوبة
awk '$4==400' logs/access.*.log | wc -l
# 4. الطلبات البطيئة — عمود %D يرتفع
awk '{print $NF}' logs/access.*.log | sort -rn | head -1

currentThreadsBusy عند maxThreads مع connectionCount مرتفع إشارةٌ إلى أنّ معدّل الطلبات تجاوز المجمّع. وهي النقطة التي على الطبقة الأمامية أو طبقةٍ أعلى أن تمتصّ فيها ما لا يستطيعه Tomcat.

الحدّ الصادق لتصليب Tomcat

كلّ ما هنا يدافع عن طبقة التطبيق ضد استنفاد الاتصالات ومجمّع الخيوط، خلف طبقةٍ أمامية ينبغي أن تلتقط معظمه أولاً. حالتان تقعان خارج ذلك.

الأولى تشبّع القناة: الحجم الذي يملأ الأنبوب لا يصل إلى الطبقة الأمامية ولا إلى Tomcat.

أين تتوقف الطبقة داخل المنشأة عن أن تنفع وصلة الوصول لديك: 10 غيغابت/ث الخط مُشبع أصلاً — الطبقة العليا فقط الجهاز داخل المنشأة يخفّف 2 غيغابت/ث 8 غيغابت/ث 25 غيغابت/ث 120 غيغابت/ث 1 تيرابت/ث+ حجم الهجوم (مقياس لوغاريتمي)
تحت القناة لحدود Connector نصيب؛ فوقها لا معنى لأيّ قيمة في server.xml.

الثانية كلُّ ما تحت Tomcat وطبقته الأمامية: مكدّس TCP لنظام التشغيل، وعلى المضيف الأمامي تصليبه الخاص، وهي في دليلَي Linux أو Windows. يفترض تصليب Tomcat أنّ كلّاً من الطبقة الأمامية وطبقة نظام التشغيل في مكانه. وفوق القناة يكون الجواب في الطبقة الشبكية.

ترتيب التطبيق

  1. تأكّد أنّ Connector غير مربوط بواجهة عامة؛ وضع أمامه طبقةً أمامية مُصلَّبة.
  2. سجّل القياسات الأساسية: الخيوط المشغولة، عدد الاتصالات، أزمنة الطلبات.
  3. حوّل بروتوكول Connector إلى NIO إن لم يكن عليه.
  4. حجّم maxThreads بحسب الكومة، ثم حوله maxConnections وacceptCount قصير.
  5. اضبط RemoteIpValve كي ترى السجلات والقواعد العميل الحقيقي.
  6. اضبط connectionTimeout وحدود حجم الطلب.
  7. صِل الإشارات الأربع بالمراقبة.

أكثر ما يغيّر النتيجة الخطوة الأولى: إعدادات Connector التي تليها هي الخطّ الثاني، والخطّ الثاني لا ينفع إلا بقدر متانة الخطّ الأول الذي يقف خلفه.

أسئلة متكررة

هل ينبغي أن يواجه Tomcat الإنترنت مباشرةً؟
نادراً، ويجدر قول ذلك صراحةً. Tomcat خادم تطبيقات؛ والتصميم الإنتاجي القياسي يُنهي الاتصالات عند طبقة أمامية مُصلَّبة. تكون هذه الطبقة nginx أو Apache httpd أو موازِن حِمل؛ تتولّى TLS وطرد الاتصالات البطيئة وتحديد المعدّل والمحتوى الساكن، وتمرّر الطلبات النظيفة إلى Tomcat عبر شبكة خاصة. ومعظم دفاعات DDoS في هذه السلسلة تعيش في تلك الطبقة الأمامية. وتعريض Connector 8080 للإنترنت مباشرةً يعني إعادة بناء ذلك كلّه داخل Connector، وهو يفعله بصورة أضعف. تصليب Connector هنا هو الخطّ الثاني خلف الطبقة الأمامية، لا بديلٌ عنها.
أيّ بروتوكول Connector للمتانة: NIO أم NIO2 أم APR؟
NIO أو NIO2، لا الموصّل الحاجب القديم. كان الموصّل الحاجب BIO ينفق خيطاً لكل اتصال طوال الطلب، ما جعل Slowloris رخيصاً ضدّه؛ وقد أُزيل في Tomcat 8.5. ويستخدم NIO وNIO2 دخلاً وخرجاً غير حاجب وقارئ أحداث، فيمسك الاتصال البطيء مقبساً لا خيط عملٍ مخصّصاً، وهي الميزة المعمارية نفسها التي يملكها nginx. وNIO هو الافتراضي والخيار الصحيح لأغلب الناس؛ والفرق بين NIO وNIO2 لهذا الغرض طفيف. تأكّد أنك على أحدهما لا على إعدادٍ قديمٍ مثبَّتٍ على BIO.
ما العلاقة بين maxThreads وmaxConnections وacceptCount؟
ثلاثة حدود بالتتابع. maxConnections عدد الاتصالات التي يقبلها Tomcat ويمسكها في آنٍ واحد؛ وacceptCount طابور نظام التشغيل للاتصالات المنتظرة قبولها بعد امتلاء maxConnections؛ وmaxThreads عدد الطلبات التي تُعالَج فعلياً. تحت السيل تمتلئ الاتصالات حتى maxConnections، ثم تصطفّ في acceptCount، ثم يرفضها نظام التشغيل. وmaxThreads سقف المعالجة خلف ذلك. ومهمّ أن تُحجَّم معاً: maxConnections ضخم خلف maxThreads صغير يعني ببساطة اتصالاتٍ كثيرة تنتظر خيوطاً قليلة، وهو بذاته انهيار بطيء.
كيف أوقف هجوم الطلبات البطيئة على Tomcat تحديداً؟
بـ connectionTimeout، وبموصّل NIO تحته. يحدّ connectionTimeout كم ينتظر Tomcat سطر الطلب والترويسات بعد فتح الاتصال؛ وعميل Slowloris الذي يسرّب الترويسات قطرةً قطرة يُسقَط عند المهلة. أبقِ قيمته في نطاق عشرات ثوانٍ منخفضة. وعلى NIO كان الاتصال العالق رخيصاً أصلاً، فالمهلة عالية الفعالية؛ وهذا المزيج هو مقابل client_header_timeout في nginx على حلقة الأحداث. وإن وُجدت طبقة أمامية فينبغي أن تلتقط هذا أولاً، ومهلة Connector هي شبكة الأمان.
من أين يأتي عنوان العميل خلف وكيل؟
من RemoteIpValve الذي يجب ضبطه، وإلا سجّل كلُّ قرارٍ بحسب العنوان وكلُّ سطر في سجل الوصول عنوانَ الوكيل. يقرأ RemoteIpValve الترويستين X-Forwarded-For وX-Forwarded-Proto ويضبط العنوان البعيد للطلب على العميل الحقيقي، فترى سجلات الوصول وأيّ قواعد RemoteAddrValve والتطبيق نفسه المصدرَ الحقيقي. وبدونه تُخفي الطبقة الأمامية كل عميل خلف عنوان واحد، وهي صورة الانهيار نفسها التي يسبّبها real_ip المضبوط خطأً في nginx.
هل توقف هذه الإعدادات هجوماً حجمياً؟
لا. إن ملأ الهجوم القناة أمام الخوادم، فلا الطبقة الأمامية ولا Tomcat يتلقّى الطلبات، ولا تنطبق أيّ قيمة في Connector. وكلّ ما هنا يدافع عن استنفاد الاتصالات ومجمّع الخيوط في طبقة التطبيق. والحجم فوق القناة مشكلة الطبقة الشبكية، ولا يُحلّ في server.xml.

النشر: أغسطس 2026

يُحدَّث هذا الدليل كلما طرح المصنّعون طرازات وشروط ترخيص جديدة. كيف نقارن بين المصنّعين