Правильний спосіб отримання IP-адрес клієнта з http.Request


78

Який правильний спосіб отримати всі IP-адреси клієнта http.Request? У PHPньому є багато змінних, які я повинен перевірити. Це те саме на Go?

Я знайшов:

req.RemoteAddr

І чи чутливий регістр запиту? наприклад x-forwarded-forте саме, що X-Forwarded-Forі X-FORWARDED-FOR? (від req.Header.Get("X-FORWARDED-FOR"))

Відповіді:


85

Переглядаючи http.Request, ви можете знайти такі змінні члени:

// HTTP defines that header names are case-insensitive.
// The request parser implements this by canonicalizing the
// name, making the first character and any characters
// following a hyphen uppercase and the rest lowercase.
//
// For client requests certain headers are automatically
// added and may override values in Header.
//
// See the documentation for the Request.Write method.
Header Header

// RemoteAddr allows HTTP servers and other software to record
// the network address that sent the request, usually for
// logging. This field is not filled in by ReadRequest and
// has no defined format. The HTTP server in this package
// sets RemoteAddr to an "IP:port" address before invoking a
// handler.
// This field is ignored by the HTTP client.
RemoteAddr string

Ви можете використовувати RemoteAddrIP-адресу та порт віддаленого клієнта (формат "IP: порт"), що є адресою оригінального запитувача або останнього проксі-сервера (наприклад, балансувальника навантаження, який живе перед вашим сервером).

Це все, що у вас є напевно.

Потім ви можете дослідити заголовки, які не враховують регістр (згідно з документацією вище), тобто всі ваші приклади працюватимуть і дадуть однаковий результат:

req.Header.Get("X-Forwarded-For") // capitalisation
req.Header.Get("x-forwarded-for") // doesn't
req.Header.Get("X-FORWARDED-FOR") // matter

Це тому, що внутрішньо http.Header.Getнормалізує ключ для вас. (Якщо ви хочете отримати доступ до карти заголовків безпосередньо, а не через Get, вам спочатку потрібно буде використовувати http.CanonicalHeaderKey .)

Нарешті, "X-Forwarded-For"це, мабуть, те поле, яке ви хочете поглянути, щоб отримати більше інформації про IP клієнта. Це дуже залежить від програмного забезпечення HTTP, яке використовується на віддаленій стороні, оскільки клієнт може помістити туди все, що завгодно. Крім того, зверніть увагу, що очікуваний формат цього поля - це розділений комами + пробіл список IP-адрес. Вам потрібно буде проаналізувати його трохи, щоб отримати один обраний вами IP (можливо, перший у списку), наприклад:

// Assuming format is as expected
ips := strings.Split("10.0.0.1, 10.0.0.2, 10.0.0.3", ", ")
for _, ip := range ips {
    fmt.Println(ip)
}

буде виробляти:

10.0.0.1
10.0.0.2
10.0.0.3

1
Те, як я читаю документацію req.Header, вам потрібно лише зробити req.Header.Get("X-Forwarded-For"), оскільки інші випадки аналізуються канонічно на це парсером.
Ларс Хогсет

2
Так, звичайно, моя відповідь говорить, що всі варіації будуть працювати, оскільки заголовки не враховують регістр. Я згадав їх лише тому, що OP запитував про чутливість до регістру, але бачу, що формулювання може заплутати. Буде оновлено.
tomasz

так, заголовки чутливі до регістру, але ви використовуєте http.CanonicalHeaderKeyзамість того, щоб спробувати всі можливі комбінації великих та малих літер.
bithavoc

Вам слід остерігатися підміни клієнтом "X-Forwarded-For". Перший зовнішній шлюз вашої послуги, ймовірно, повинен видалити будь-який такий заголовок, наданий клієнтом.
Falco

22

Тут цілком робочий приклад

package main

import (  
    // Standard library packages
    "fmt"
    "strconv"
    "log"
    "net"
    "net/http"

    // Third party packages
    "github.com/julienschmidt/httprouter"
    "github.com/skratchdot/open-golang/open"
)



// https://blog.golang.org/context/userip/userip.go
func getIP(w http.ResponseWriter, req *http.Request, _ httprouter.Params){
    fmt.Fprintf(w, "<h1>static file server</h1><p><a href='./static'>folder</p></a>")

    ip, port, err := net.SplitHostPort(req.RemoteAddr)
    if err != nil {
        //return nil, fmt.Errorf("userip: %q is not IP:port", req.RemoteAddr)

        fmt.Fprintf(w, "userip: %q is not IP:port", req.RemoteAddr)
    }

    userIP := net.ParseIP(ip)
    if userIP == nil {
        //return nil, fmt.Errorf("userip: %q is not IP:port", req.RemoteAddr)
        fmt.Fprintf(w, "userip: %q is not IP:port", req.RemoteAddr)
        return
    }

    // This will only be defined when site is accessed via non-anonymous proxy
    // and takes precedence over RemoteAddr
    // Header.Get is case-insensitive
    forward := req.Header.Get("X-Forwarded-For")

    fmt.Fprintf(w, "<p>IP: %s</p>", ip)
    fmt.Fprintf(w, "<p>Port: %s</p>", port)
    fmt.Fprintf(w, "<p>Forwarded for: %s</p>", forward)
}


func main() {  
    myport := strconv.Itoa(10002);


    // Instantiate a new router
    r := httprouter.New()

    r.GET("/ip", getIP)

    // Add a handler on /test
    r.GET("/test", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
        // Simply write some test data for now
        fmt.Fprint(w, "Welcome!\n")
    })  


    l, err := net.Listen("tcp", "localhost:" + myport)
    if err != nil {
        log.Fatal(err)
    }
    // The browser can connect now because the listening socket is open.


    //err = open.Start("http://localhost:"+ myport + "/test")
    err = open.Start("http://localhost:"+ myport + "/ip")
    if err != nil {
         log.Println(err)
    }

    // Start the blocking server loop.
    log.Fatal(http.Serve(l, r)) 
}

Чому б не запустити ParseIP на "X-Forwarded-For"?

3
@kristen: Насправді X-Forwarded-For - це (потенційно) список IP-адрес (проксі-ланцюжок). Тож спочатку вам потрібно розділити на ",".
Stefan Steiger

11

Ось як я придумую ІР

func ReadUserIP(r *http.Request) string {
    IPAddress := r.Header.Get("X-Real-Ip")
    if IPAddress == "" {
        IPAddress = r.Header.Get("X-Forwarded-For")
    }
    if IPAddress == "" {
        IPAddress = r.RemoteAddr
    }
    return IPAddress
}
  • X-Real-Ip - отримує перший справжній IP (якщо запити розташовані за кількома джерелами NAT / балансиром навантаження)

  • X-Forwarded-For - якщо з якихось причин X-Real-Ip порожній і не повертає відповіді, зверніться до X-Forwarded-For

  • Віддалена адреса - остання інстанція (зазвичай не є надійною, оскільки це може бути останній ip або якщо це відкритий http-запит до сервера, тобто без балансу навантаження)

4
Попередження: Перевірте коментарі на цю відповідь - stackoverflow.com/a/55790/1584308 - «Клієнт може встановити X-Forwarded-Forабо X-Real-IPзаголовок для довільного значення він хоче Якщо у вас є довірений зворотний проксі - сервер, ви не повинні використовувати будь - коли. ці цінності ".
hewiefreeman

3

У PHP є багато змінних, які я повинен перевірити. Це те саме на Go?

Це не має нічого спільного з Go (або PHP). Це лише залежить від того, що надсилає клієнт, проксі-сервер, балансир навантаження або сервер. Отримайте той, який вам потрібен, залежно від вашого оточення.

http.Request.RemoteAddrмістить віддалену IP-адресу. Це може бути чи не ваш фактичний клієнт.

І чи чутливий регістр запиту? наприклад x-forwarded-for - це те саме, що X-Forwarded-For та X-FORWARDED-FOR? (з req.Header.Get ("X-FORWARDED-FOR"))

Ні, чому б не спробувати самостійно? http://play.golang.org/p/YMf_UBvDsH


1
Заголовки завжди перетворюються в однаковий формат MIME перед встановленням / отриманням, тому аргумент req.Header.Get не враховує регістр. Джерело golang.org/pkg/net/textproto/#CanonicalMIMEHeaderKey
k3a

1

Відповідно до Mozilla MDN: " Заголовок X-Forwarded-For (XFF) - це фактично стандартний заголовок для ідентифікації вихідної IP-адреси клієнта. "
Вони публікують чітку інформацію у своїй статті X-Forwarded-For .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.