Nginx'te Slashdot efektini algılama


10

Bir yönlendirenden gelen isabetler bir eşiğin ötesine geçerse Nginx'i bana bildirebilmemin bir yolu var mı?

Örneğin, web sitem Slashdot'ta bulunuyorsa ve aniden bir saat içinde 2K isabet aldığımda saatte 1K isabetin ötesine geçtiğinde bildirim almak istiyorum.

Bunu Nginx'te yapmak mümkün olacak mı? Muhtemelen lua olmadan? (benim ürün lua derlenmiş değil çünkü)


4
"Slashdot" nedir ??
ewwhite

Ben ngix ddos ​​tespit etmek için böyle bir şey yaptım. Erişim günlüğünü ayrıştırarak başardım. Erişim günlüğünü ayrıştırmak ve saatte benzersiz ip bağlantıları saymak için bir cron işi yaptım.
Hex

8
Yani Nginx'in Dice tarafından satın alındığını tespit edebilmesini mi istiyorsun?
MDMarra

1
@Hex That (ve belki senaryonuzdan birkaç parçacık) bu soruya mükemmel bir cevap verebilir :)
voretaq7

3
Muhtemelen artık Slashdotted almak için endişelenmenize gerek yok. Web sunucunuz saatte fazladan 4 bağlantı yapabilmelidir. Yine de Redditted almak için endişe etmek isteyebilirsiniz ...
HopelessN00b

Yanıtlar:


3

En verimli çözüm olacak bir cini yazmak için olabilir takip ve alanın.tail -faccess.log$http_referer

Bununla birlikte, hızlı ve kirli bir çözüm, fazladan bir access_logdosya eklemek , yalnızca $http_refererdeğişkeni bir özel ile log_formatgünlüğe kaydetmek ve günlüğü her X dakikada bir otomatik olarak döndürmek olacaktır.

  • Bu, dosyaların yeniden açılması için nginx'in zarif bir şekilde yeniden başlatılması gerekebilecek standart logrotate komut dosyalarının yardımıyla gerçekleştirilebilir (örneğin, standart prosedür, basit bir süre için SO'ya / a / 15183322'ye bakın. tabanlı komut dosyası)…

  • Ya da, içindeki değişkenleri kullanarak access_log, büyük olasılıkla ya da bir direktifin $time_iso8601yardımıyla dakika belirtimini çıkararak (nereye koymak istediğinize bağlı olarak ).mapifaccess_log

Bu nedenle, yukarıdakilerle, her biri 10 dakikalık bir süreyi kapsayan 6 günlük dosyanız olabilir http_referer.Txx{0,1,2,3,4,5}x.log, örneğin, her dosyayı farklılaştırmak için dakikanın ilk basamağını alarak.

Şimdi elinizdeki deyişle, her 10 dakikada çalıştırabilir basit bir kabuk komut dosyası var yapmak cat, boruyu o araya yukarıdaki tüm dosyaları sort, boru o kadar uniq -c, hiç sort -rn, hiç head -16, ve sen 16 en yaygın bir listesi var Referervaryasyonları - herhangi bir sayı ve alan kombinasyonunun ölçütlerinizi aşıp aşmadığına karar vermek ve bir bildirimde bulunmak.

Daha sonra, tek bir başarılı bildirimden sonra, bu 6 dosyanın tümünü kaldırabilirsiniz ve sonraki çalıştırmalarda, dosyaların altısının tümü (ve / veya uygun gördüğünüz gibi belirli bir sayı) OLMADIĞINDA herhangi bir bildirim YOKTUR.


Bu süper kullanışlı görünüyor. Çok fazla şey soruyor olabilirim ama önceki cevap gibi, bir senaryoya yardım eder misiniz?
Quintin Par

@QuintinPar Kulağa ekstra müfredat geliyor! ;-) İsterseniz, ben işe ve danışmanlık için hazırım; e-postam Constantine.SU'da
cnst++@FreeBSD.org

Tamamen anlamak. Şimdiye kadar yardımlarınız için çok teşekkürler. Umarım bir gün karşılayabilirim :-)
Quintin Par

1
@QuintinPar Rica ederim! Endişelenmeyin, yukarıdaki özelliklerle oldukça basit bir komut dosyası olmalıdır; sadece test etme, yapılandırma ve paketleme meselesi. :)
cnst

1
Sen bir süper kahramansın!
Quintin Par

13

Sanırım bu logtail ve grep ile çok daha iyi olurdu. Lua inline ile yapmak mümkün olsa bile, her istek için bu yükü istemezsiniz ve özellikle Slashdotted olduğunuzda bunu istemezsiniz.

İşte 5 saniyelik bir versiyon. Bir betiğe yapıştırın ve etrafına daha okunabilir metinler koyun ve siz altınsınız.

5 * * * * logtail -f /var/log/nginx/access_log -o /tmp/nginx-logtail.offset | grep -c "http://[^ ]slashdot.org"

Tabii ki, bu tamamen reddit.com ve facebook.com ve size çok fazla trafik gönderebilecek milyonlarca sitenin tamamını yok sayar. Her biri 20 ziyaretçi gönderen 100 farklı siteden bahsetmiyorum bile. Yönlendiriciden bağımsız olarak, size bir e-postanın gönderilmesine neden olan basit bir eski trafik eşiğiniz olmalıdır.


1
Sorun proaktif olmak. Herhangi bir siteden bilmem gerek. Başka bir soru, eşiği nereye koyacağım? Başka günlük ayrıştırma mı demek istediniz? Ayrıca fourmilab.ch/webtools/logtail adresinde
Quintin Par

Eşik, sunucularınızın ne kadar trafik işleyebileceğine bağlı olacaktır. Bunu sadece siz ayarlayabilirsiniz. Daha hızlı bildirim istiyorsanız, her saat yerine beş dakikada bir çalıştırın ve eşiği 12'ye bölün. Bu -o seçenek bir ofset dosyası içindir, böylece bir dahaki sefere nerede okumaya başlayacağını bilir.
Ladadadada

@Ladadadada, ek yükün önemli olacağına katılmıyorum, çözümüme bakın - serverfault.com/a/870537/110020 - Bu, doğru şekilde uygulanırsa ek yükün oldukça az olacağına inanıyorum, özellikle, (1) gerçekten yavaş, o zaman bu ek yük ihmal edilebilir, veya, (2), arka ucunuz zaten oldukça snippy ve / veya düzgün bir şekilde önbelleğe alınmışsa, ilk etapta trafik işlemeyle ilgili küçük sorunlarınız olmalı ve biraz ekstra yük kazanacaksınız ' Bir çentik yapmak. Genel olarak, bu sorunun iki kullanım durumu olduğu anlaşılıyor (1), yeni bilgilendiriliyor ve (2) otomatik ölçeklendirme.
cnst

4

Nginx limit_req_zone yönergesi, bölgelerini $ http_referrer dahil olmak üzere herhangi bir değişkene dayandırabilir.

http {
    limit_req_zone  $http_referrer  zone=one:10m   rate=1r/s;

    ...

    server {

        ...

        location /search/ {
            limit_req   zone=one  burst=5;
        }

Ayrıca, yönlendirme başlıkları oldukça uzun ve çeşitli olabileceğinden ve bir infinte çeşidi görebileceğiniz için web sunucusunda gereken durum miktarını sınırlamak için bir şeyler yapmak isteyeceksiniz. Yönlendirme başlığının karmasını temel alan tüm istekler için bir değişken ayarlamak üzere nginx split_clients özelliğini kullanabilirsiniz. Aşağıdaki örnekte yalnızca 10 kova kullanılır, ancak 1000 ile kolayca yapabilirsiniz. Bu nedenle, eğik çizgi aldığınız takdirde, yönlendireni slashdot URL'si ile aynı grupta karma yapan kullanıcılar da engellenir, ancak split_clients içinde 1000 kova kullanarak ziyaretçilerin% 0,1'ini sınırlayabilirsiniz.

Böyle bir şeye benzeyecektir (tamamen test edilmemiş, ancak yön olarak doğru):

http {

split_clients $http_referrer $refhash {
               10%               x01;
               10%               x02;
               10%               x03;
               10%               x04;
               10%               x05;
               10%               x06;
               10%               x07;
               10%               x08;
               10%               x09;
               *                 x10;
               }

limit_req_zone  $refhash  zone=one:10m   rate=1r/s;

...

server {

    ...

    location /search/ {
        limit_req   zone=one  burst=5;
    }

Bu ilginç bir yaklaşım; ancak, Slashdot etkisi gerçekleşirken sorunun otomatik bir uyarı ile ilgili olduğuna inanıyorum; çözümünüz, kullanıcıların yaklaşık% 10'unu rastgele engelleme konusunda çözümleniyor gibi görünüyor. Dahası, kullanım nedeninizin split_clientsyanlış bilgilendirilebileceğine inanıyorum - limit_reqbir "sızdıran kova" ya dayanıyor, bu da genel durumun asla belirtilen bölgenin boyutunu geçmemesi gerektiği anlamına geliyor.
cnst

2

Evet, elbette NGINX'te mümkün!

Yapabileceğiniz şu DFA'yı uygulamaktır :

  1. Değerleri normalleştirmek için $http_referermuhtemelen bir regex ile a kullanarak oran sınırlama uygulayın map. Sınır aşıldığında, ilgili bir soruya göre bir error_pageişleyiciden yakalayabileceğiniz , dahili bir yönlendirme olarak yeni bir dahili konuma (istemci tarafından görülemez) gidebileceğiniz bir iç hata sayfası açılır .

  2. Yukarıdaki konumda aşılmış sınırlar için, harici mantığın bildirimi gerçekleştirmesine izin veren bir uyarı isteği gerçekleştirirsiniz; bu istek daha sonra önbelleğe alınır ve belirli bir zaman aralığı başına yalnızca 1 benzersiz istek alırsınız.

  3. Önceki isteğin HTTP Durum kodunu yakalayın (≥ 300 durum kodunu döndürerek ve proxy_intercept_errors onalternatif olarak varsayılan olarak yerleşik olmayanı kullanarak auth_requestveya add_after_body"ücretsiz" alt istekte bulunarak) ve orijinal isteği sanki önceki adım dahil edilmedi. error_pageBunun çalışması için özyinelemeli işlemeyi etkinleştirmemiz gerektiğini unutmayın .

İşte benim PoC ve bir MVP, ayrıca https://github.com/cnst/StackOverflow.cnst.nginx.conf/blob/master/sf.432636.detecting-slashdot-effect-in-nginx.conf :

limit_req_zone $http_referer zone=slash:10m rate=1r/m;  # XXX: how many req/minute?
server {
    listen 2636;
    location / {
        limit_req zone=slash nodelay;
        #limit_req_status 429;  #nginx 1.3.15
        #error_page 429 = @dot;
        error_page 503 = @dot;
        proxy_pass http://localhost:2635;
        # an outright `return 200` has a higher precedence over the limit
    }
    recursive_error_pages on;
    location @dot {
        proxy_pass http://127.0.0.1:2637/?ref=$http_referer;
        # if you don't have `resolver`, no URI modification is allowed:
        #proxy_pass http://localhost:2637;
        proxy_intercept_errors on;
        error_page 429 = @slash;
    }
    location @slash {
        # XXX: placeholder for your content:
        return 200 "$uri: we're too fast!\n";
    }
}
server {
    listen 2635;
    # XXX: placeholder for your content:
    return 200 "$uri: going steady\n";
}
proxy_cache_path /tmp/nginx/slashdotted inactive=1h
        max_size=64m keys_zone=slashdotted:10m;
server {
    # we need to flip the 200 status into the one >=300, so that
    # we can then catch it through proxy_intercept_errors above
    listen 2637;
    error_page 429 @/.;
    return 429;
    location @/. {
        proxy_cache slashdotted;
        proxy_cache_valid 200 60s;  # XXX: how often to get notifications?
        proxy_pass http://localhost:2638;
    }
}
server {
    # IRL this would be an actual script, or
    # a proxy_pass redirect to an HTTP to SMS or SMTP gateway
    listen 2638;
    return 200 authorities_alerted\n;
}

Bunun beklendiği gibi çalıştığını unutmayın:

% sh -c 'rm /tmp/slashdotted.nginx/*; mkdir /tmp/slashdotted.nginx; nginx -s reload; for i in 1 2 3; do curl -H "Referer: test" localhost:2636; sleep 2; done; tail /var/log/nginx/access.log'
/: going steady
/: we're too fast!
/: we're too fast!

127.0.0.1 - - [26/Aug/2017:02:05:49 +0200] "GET / HTTP/1.1" 200 16 "test" "curl/7.26.0"
127.0.0.1 - - [26/Aug/2017:02:05:49 +0200] "GET / HTTP/1.0" 200 16 "test" "curl/7.26.0"

127.0.0.1 - - [26/Aug/2017:02:05:51 +0200] "GET / HTTP/1.1" 200 19 "test" "curl/7.26.0"
127.0.0.1 - - [26/Aug/2017:02:05:51 +0200] "GET /?ref=test HTTP/1.0" 200 20 "test" "curl/7.26.0"
127.0.0.1 - - [26/Aug/2017:02:05:51 +0200] "GET /?ref=test HTTP/1.0" 429 20 "test" "curl/7.26.0"

127.0.0.1 - - [26/Aug/2017:02:05:53 +0200] "GET / HTTP/1.1" 200 19 "test" "curl/7.26.0"
127.0.0.1 - - [26/Aug/2017:02:05:53 +0200] "GET /?ref=test HTTP/1.0" 429 20 "test" "curl/7.26.0"
%

Beklendiği gibi bir ön uç ve bir arka uç ilk istek sonuçları, isabet görebilirsiniz (ben sahip konuma bir kukla arka uç eklemek zorunda limit_reqbir nedeni, return 200sınırlar önceliklidir alacağını, gerçek bir arka uç gerekli değildir işlemin geri kalanı için).

İkinci istek sınırın üzerindedir, bu nedenle, uyarıyı göndeririz (alınıyor 200) ve önbelleğe alıyoruz , geri dönüyoruz 429(bu, daha sonra ön uç tarafından yakalanan 300'den küçük isteklerin yakalanamaması nedeniyle gerekli) , şimdi ne isterse yapmakta özgürdür.

Üçüncü istek yine de sınırı aşıyor, ancak uyarıyı zaten gönderdik, bu nedenle yeni bir uyarı gönderilmiyor.

Bitti! GitHub'a koymayı unutmayın!


İki hız sınırlama koşulu birlikte çalışabilir mi? Bunu şu anda kullanıyorum: serverfault.com/a/869793/26763
Quintin Par

@QuintinPar :-) Bence nasıl kullandığınıza bağlı olacaktır - bariz sorun hangi sınırı koşulu tanıttı tek bir yerde ayırt etmek olacaktır; ama bu bir limit_reqve diğeri bir ise limit_conn, o zaman sadece limit_req_status 429yukarıdakini kullanın (çok yeni nginx gerektirir) ve bence altın olmalısınız; başka seçenekler de olabilir (emin olmak için çalışmak için nginx w / zinciridir set_real_ip_from, ancak tam olarak ne yapmak istediğinize bağlı olarak, daha verimli seçenekler olabilir).
cnst

@QuintinPar Cevabımda eksik bir şey varsa, bana bildirin. BTW, sınıra ulaşıldığında ve betiğinizin nginx tarafından düzgün bir şekilde önbelleğe alınana kadar çağrılması gerektiğini unutmayın, ardından içeriğiniz gecikebilir; örneğin, komut dosyasını eşzamansız olarak benzer bir şeyle uygulamak golangveya yukarı akışlar için zaman aşımı seçeneklerine bakmak isteyebilirsiniz ; Ayrıca, kullanmak da isteyebilir proxy_cache_lock onve muhtemelen komut dosyası başarısız olursa ne yapılacağına dair bazı hata işlemleri ekleyebilir (örn . tekrar kullanmak error_pagegibi proxy_intercept_errors). İnanıyorum ki POC'm iyi bir başlangıç. :)
cnst

Bunu denediğiniz için teşekkürler. Benim için hala büyük bir sorun, zaten http düzeyinde limit_req ve limit_conn kullanıyorum ve sahip olduğum tüm web siteleri için geçerlidir. Bunu geçersiz kılamam. Yani bu çözüm başka bir şey için tasarlanmış bir işlev kullanıyor. Bu çözüme başka bir yaklaşım var mı?
Quintin Par

@QuintinPar İç içe nginx örneklerine sahip olmak, her birinin tek bir limit_req/ / set kullanacağı durum ne olacak limit_conn? Örneğin, yukarıdaki yapılandırmayı geçerli ön uç sunucunuzun önüne koymanız yeterlidir. set_real_ip_fromIP'lerin doğru şekilde hesaplandığından emin olmak için akış yukarı nginx'te kullanabilirsiniz . Yoksa, hala uymuyorsa, kesin kısıtlamalarınızı ve spesifikasyonunuzu daha canlı bir şekilde ifade etmek zorundasınız - hangi trafik seviyelerinden bahsediyoruz? Statün ne sıklıkla çalışması gerekir (1dk / 5dk / 1h)? Eski logtailçözümün nesi var ?
cnst
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.