Phát hiện hiệu ứng Slashdot trong nginx


10

Có cách nào để tôi có thể khiến Nginx thông báo cho tôi nếu lượt truy cập từ người giới thiệu vượt quá ngưỡng không?

ví dụ: Nếu trang web của tôi được giới thiệu tại Slashdot và đột nhiên tôi có 2K lượt truy cập trong một giờ tôi muốn được thông báo khi vượt quá 1K lượt truy cập một giờ.

Nó sẽ có thể làm điều này trong Nginx? Có thể không có lua? (vì prod của tôi không được biên dịch)


4
"Slashdot" là gì ??
ewwhite

Tôi đã làm một cái gì đó như thế này để phát hiện ddos ​​trên ngix. Tôi đã đạt được nó bằng cách phân tích nhật ký truy cập. Tôi đã làm một công việc định kỳ để phân tích nhật ký truy cập và đếm các kết nối ip duy nhất mỗi giờ.
Hex

8
Bạn có nghĩa là bạn muốn nginx có thể phát hiện nếu bạn đã được Dice mua?
MDMarra

1
@Hex Điều đó (và có thể một vài đoạn trong kịch bản của bạn) sẽ tạo ra một câu trả lời tuyệt vời cho câu hỏi này :)
voretaq7

3
Có lẽ không cần phải lo lắng về việc bị chém nữa. Máy chủ web của bạn sẽ có thể xử lý thêm 4 kết nối mỗi giờ. Có thể muốn lo lắng về việc Redditted, mặc dù ...
HopelessN00b

Câu trả lời:


3

Giải pháp hiệu quả nhất có thể để viết một daemon đó sẽ tail -faccess.log, và theo dõi các $http_refererlĩnh vực.

Tuy nhiên, một giải pháp nhanh và bẩn sẽ là thêm một access_logtệp bổ sung , chỉ ghi nhật ký $http_refererbiến với một tùy chỉnh log_formatvà tự động xoay nhật ký mỗi X phút.

  • Điều này có thể được thực hiện với sự trợ giúp của các tập lệnh logrotate tiêu chuẩn, có thể cần phải khởi động lại một cách duyên dáng của nginx để mở lại các tệp (ví dụ: quy trình chuẩn, hãy xem / a / 15183322 trên SO trong một thời gian đơn giản- kịch bản dựa trên)

  • Hoặc, bằng cách sử dụng các biến trong access_log, có thể bằng cách lấy thông số kỹ thuật phút ra $time_iso8601với sự trợ giúp của maphoặc ifchỉ thị (tùy thuộc vào nơi bạn muốn đặt access_log).

Vì vậy, với cách trên, bạn có thể có 6 tệp nhật ký, mỗi tệp trong khoảng thời gian 10 phút http_referer.Txx{0,1,2,3,4,5}x.log, ví dụ: bằng cách lấy chữ số đầu tiên của phút để phân biệt từng tệp.

Bây giờ, tất cả những gì bạn phải làm là có một tập lệnh shell đơn giản, có thể chạy cứ sau 10 phút, cattất cả các tệp trên cùng nhau, chuyển nó thành sort, dẫn nó uniq -cđến sort -rn, đến head -16, và, bạn có một danh sách 16 biến Refererthể phổ biến nhất - tự do quyết định xem có bất kỳ kết hợp số và trường nào vượt quá tiêu chí của bạn không và thực hiện thông báo.

Sau đó, sau một thông báo thành công, bạn có thể xóa tất cả 6 tệp này và trong các lần chạy tiếp theo, không đưa ra bất kỳ thông báo nào KHÔNG GIỚI HẠN tất cả sáu tệp có mặt (và / hoặc một số khác mà bạn thấy phù hợp).


Điều này có vẻ siêu hữu ích. Tôi có thể yêu cầu quá nhiều nhưng giống như câu trả lời trước đó, bạn có phiền khi giúp đỡ với một kịch bản không?
Quintin Par

@QuintinPar Điều đó nghe có vẻ ngoại khóa! ;-) Nếu bạn muốn, tôi sẵn sàng cho thuê và tư vấn; email của tôi là cnst++@FreeBSD.org, cũng tại Constantine.SU
cnst

Hiểu hoàn toàn. Cảm ơn rất nhiều vì tất cả sự giúp đỡ cho đến bây giờ. Hy vọng tôi có thể đủ khả năng cho bạn một ngày nào đó :-)
Quintin Par

1
@QuintinPar bạn được chào đón! Đừng lo lắng, nó phải là một kịch bản khá đơn giản với thông số trên; về cơ bản chỉ là vấn đề kiểm tra, cấu hình và đóng gói. :)
cnst

1
Bạn là một siêu anh hùng!
Quintin Par

13

Tôi nghĩ rằng điều này sẽ được thực hiện tốt hơn nhiều với logtail và grep. Ngay cả khi có thể thực hiện với lua nội tuyến, bạn không muốn chi phí đó cho mọi yêu cầu và bạn đặc biệt không muốn nó khi bạn đã bị Chém.

Đây là phiên bản 5 giây. Dán nó trong một kịch bản và đặt một số văn bản dễ đọc hơn xung quanh nó và bạn là vàng.

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

Tất nhiên, điều đó hoàn toàn bỏ qua reddit.com và facebook.com và tất cả hàng triệu trang web khác có thể gửi cho bạn nhiều lưu lượng truy cập. Chưa kể 100 trang web khác nhau gửi cho bạn 20 khách truy cập mỗi. Bạn có thể chỉ nên có một ngưỡng lưu lượng truy cập cũ đơn giản khiến email được gửi cho bạn, bất kể người giới thiệu.


1
Vấn đề là phải chủ động. Tôi cần biết từ bất kỳ trang web. Một câu hỏi khác là tôi đặt ngưỡng ở đâu? Ý của bạn là một số phân tích nhật ký bổ sung? Ngoài ra, tôi đã không tìm thấy tích trong bốnmilab.ch/webtools/logtail
Quintin Par

Ngưỡng sẽ phụ thuộc vào lưu lượng truy cập mà máy chủ của bạn có thể xử lý. Chỉ có bạn mới có thể thiết lập điều đó. Nếu bạn muốn thông báo nhanh hơn, hãy chạy nó cứ sau năm phút thay vì mỗi giờ và chia ngưỡng cho 12. -o Tùy chọn dành cho một tệp bù để nó biết nơi bắt đầu đọc vào lần sau.
Ladadadada

@Ladadadada, tôi không đồng ý rằng chi phí sẽ rất lớn, hãy xem giải pháp của tôi - serverfault.com/a/870537/110020 - Tôi tin rằng chi phí sẽ khá tối thiểu nếu điều này được thực hiện đúng, đặc biệt, (1), nếu phần phụ trợ của bạn là thực sự chậm, thì chi phí này sẽ không đáng kể, hoặc, (2), nếu phần phụ trợ của bạn đã khá nhanh và / hoặc được lưu trữ đúng cách, thì bạn sẽ gặp một chút vấn đề với việc xử lý lưu lượng ở vị trí đầu tiên và tải thêm một chút sẽ thắng ' t làm một vết lõm Nhìn chung, có vẻ như câu hỏi này có hai trường hợp sử dụng, (1), vừa được thông báo, và, (2), tự động mở rộng.
cnst

4

Lệnh nginx limit_Vq_zone có thể căn cứ vào các vùng của nó trên bất kỳ biến nào, bao gồm $ http_Vferrer.

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

    ...

    server {

        ...

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

Tuy nhiên, bạn cũng sẽ muốn làm một cái gì đó để giới hạn số lượng trạng thái cần thiết trên máy chủ web, vì các tiêu đề của người giới thiệu có thể khá dài và đa dạng và bạn có thể thấy một variet infinte. Bạn có thể sử dụng tính năng nginx split_clents để đặt biến cho tất cả các yêu cầu dựa trên hàm băm của tiêu đề người giới thiệu. Ví dụ dưới đây chỉ sử dụng 10 thùng, nhưng bạn có thể làm điều đó với 1000 dễ dàng như vậy. Vì vậy, nếu bạn bị gạch chéo, những người có người giới thiệu đã băm vào cùng một nhóm vì URL slashdot cũng sẽ bị chặn, nhưng bạn có thể giới hạn ở mức 0,1% khách truy cập bằng cách sử dụng 1000 nhóm trong split_clents.

Nó sẽ trông giống như thế này (hoàn toàn chưa được kiểm tra, nhưng chính xác theo hướng):

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;
    }

Đây là một cách tiếp cận thú vị; tuy nhiên, tôi tin rằng câu hỏi là về cảnh báo tự động khi hiệu ứng Slashdot đang diễn ra; giải pháp của bạn dường như giải quyết xung quanh việc chặn ngẫu nhiên khoảng 10% người dùng. Hơn nữa, tôi tin rằng lý do sử dụng của bạn split_clientscó thể bị hiểu sai - limit_reqdựa trên "thùng bị rò rỉ", điều đó có nghĩa là trạng thái chung không bao giờ vượt quá kích thước của vùng được chỉ định.
cnst

2

Vâng, tất nhiên là có thể trong NGINX!

Những gì bạn có thể làm là thực hiện DFA sau :

  1. Thực hiện giới hạn tỷ lệ, dựa trên $http_referer, có thể sử dụng một số biểu thức chính quy thông qua a mapđể chuẩn hóa các giá trị. Khi vượt quá giới hạn, một trang lỗi nội bộ được đưa ra, bạn có thể bắt gặp một người error_pagexử lý theo một câu hỏi liên quan , đi đến một vị trí nội bộ mới dưới dạng chuyển hướng nội bộ (không hiển thị cho khách hàng).

  2. Ở vị trí trên vượt quá giới hạn, bạn thực hiện yêu cầu cảnh báo, để logic bên ngoài thực hiện thông báo; yêu cầu này sau đó được lưu trữ, đảm bảo bạn sẽ chỉ nhận được 1 yêu cầu duy nhất cho mỗi cửa sổ thời gian nhất định.

  3. Bắt mã Trạng thái HTTP của yêu cầu trước đó (bằng cách trả lại mã trạng thái ≥ 300 và sử dụng proxy_intercept_errors on, hoặc, thay vào đó, sử dụng mã không được xây dựng theo mặc định auth_requesthoặc add_after_bodyđể thực hiện một yêu cầu con "miễn phí" và hoàn thành yêu cầu ban đầu như thể bước trước không tham gia. Lưu ý rằng chúng ta cần kích hoạt error_pagexử lý đệ quy để điều này hoạt động.

Đây là PoC của tôi và MVP, cũng tại 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;
}

Lưu ý rằng điều này hoạt động như mong đợi:

% 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"
%

Bạn có thể thấy rằng yêu cầu đầu tiên dẫn đến một lần truy cập trước và một lần truy cập phụ trợ, như mong đợi (tôi phải thêm một phụ trợ giả vào vị trí có limit_req, bởi vì return 200sẽ được ưu tiên hơn các giới hạn, một phụ trợ thực sự là không cần thiết cho phần còn lại của việc xử lý).

Yêu cầu thứ hai vượt quá giới hạn, vì vậy, chúng tôi gửi cảnh báo (nhận 200) và lưu lại bộ đệm, trả lại 429(điều này là cần thiết do giới hạn nói trên mà các yêu cầu dưới 300 không thể bắt được), sau đó bị bắt trước , đó là miễn phí bây giờ miễn phí để làm bất cứ điều gì nó muốn.

Yêu cầu thứ ba vẫn vượt quá giới hạn, nhưng chúng tôi đã gửi cảnh báo, vì vậy, không có thông báo mới nào được gửi.

Làm xong! Đừng quên fork nó trên GitHub!


Hai điều kiện giới hạn tỷ lệ có thể làm việc cùng nhau? Tôi đang sử dụng tính năng này ngay bây giờ: serverfault.com/a/869793/26763
Quintin Par

@QuintinPar :-) Tôi nghĩ rằng nó sẽ phụ thuộc vào cách bạn sử dụng nó - vấn đề rõ ràng sẽ là phân biệt ở một vị trí trong đó giới hạn đưa ra điều kiện; nhưng nếu cái này là a limit_req, và cái kia là a limit_conn, thì chỉ cần sử dụng cái limit_req_status 429trên (yêu cầu nginx rất mới), và tôi nghĩ bạn nên là vàng; có thể có các tùy chọn khác (một để hoạt động chắc chắn là chuỗi nginx w / set_real_ip_from, nhưng, tùy thuộc vào chính xác những gì bạn muốn làm, có thể có nhiều lựa chọn hiệu quả hơn).
cnst

@QuintinPar nếu có bất cứ điều gì còn thiếu trong câu trả lời của tôi, hãy cho tôi biết. BTW, lưu ý rằng một khi đạt đến giới hạn và tập lệnh của bạn sẽ được gọi, cho đến khi tập lệnh đó được lưu trữ đúng cách bởi nginx, thì nội dung của bạn có thể bị trì hoãn; ví dụ: bạn có thể muốn triển khai tập lệnh không đồng bộ với một cái gì đó như golang, hoặc xem xét các tùy chọn hết thời gian chờ để ngược dòng; đồng thời, cũng có thể muốn sử dụng proxy_cache_lock onvà có thể thêm một số xử lý lỗi để làm gì nếu tập lệnh thất bại (ví dụ: sử dụng error_pagecũng như proxy_intercept_errorsmột lần nữa). Tôi tin rằng POC của tôi là khởi đầu tốt. :)
cnst

Cảm ơn bạn đã cố gắng này. Một vấn đề lớn đối với tôi vẫn là, tôi đang sử dụng giới hạn và giới hạn đã ở mức http và nó áp dụng cho tất cả các trang web tôi có. Tôi không thể ghi đè lên nó. Vì vậy, giải pháp này đang sử dụng một chức năng có nghĩa là cho một cái gì đó khác. Bất cứ cách tiếp cận nào khác cho giải pháp này?
Quintin Par

@QuintinPar Điều gì về việc có các trường hợp nginx lồng nhau, trong đó mỗi trường hợp sẽ sử dụng một tập hợp limit_req/ limit_conn? Ví dụ, chỉ cần đặt cấu hình ở trên trước máy chủ ngoại vi hiện tại của bạn. Bạn có thể sử dụng set_real_ip_fromtrong nginx ngược dòng để đảm bảo IP được hạch toán chính xác xuống dòng. Khác, nếu nó vẫn không phù hợp, tôi nghĩ bạn phải nói rõ các ràng buộc chính xác của mình và thông số kỹ thuật rõ ràng hơn - chúng ta đang nói về mức lưu lượng nào? Bao lâu thì stat cần chạy (1 phút / 5 phút / 1h)? Có gì sai với logtailgiải pháp cũ ?
cnst
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.