在 curl 中用 IP 替换服务器地址

在 curl 中用 IP 替换服务器地址

我的意思是使用 IP 而不是服务器名称,直接curl从 中获取文件http://...。我在 Msys2、Win 10 下(这就是为什么在这里发帖而不是在 askubuntu 中发帖的原因),但我猜在 Linux 中应该是一样的。

我无法做到这一点。我在下面发布了我尝试的详细信息。我使用类似的失败wget。我写了一篇单独的文章,因为我不确定解释和解决方案是否与这里的相同。

正确的做法是什么?

注意:使用curl ftp://<IP>/... 代替 可以curl http://<IP>/... 达到良好的效果。


这是我尝试过的:

  1. 获取服务器的 IP 地址
    $ ping us.archive.ubuntu.com
    
    Haciendo ping a us.archive.ubuntu.com [91.189.91.38] con 32 bytes de datos:
    Respuesta desde 91.189.91.38: bytes=32 tiempo=173ms TTL=52
    Respuesta desde 91.189.91.38: bytes=32 tiempo=166ms TTL=52
    Respuesta desde 91.189.91.38: bytes=32 tiempo=172ms TTL=52
    
    Estadísticas de ping para 91.189.91.38:
        Paquetes: enviados = 3, recibidos = 3, perdidos = 0
        (0% perdidos),
    Tiempos aproximados de ida y vuelta en milisegundos:
        Mínimo = 166ms, Máximo = 173ms, Media = 170ms
    Control-C
  1. 尝试curl使用服务器名称访问该文件运行正常
$ curl -L http://us.archive.ubuntu.com/ubuntu/pool/universe/y/yudit/yudit-common_2.9.6-7_all.deb --output yudit-common_2.9.6-7_all.deb
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 1599k  100 1599k    0     0   256k      0  0:00:06  0:00:06 --:--:--  344k
  1. 尝试curl使用 IP 地址访问该文件这是行不通的。附加--header "Host:us.archive.ubuntu.com"到命令行会产生完全相同的结果。我不确定这是否会消除“主机头”问题。
$ curl -L http://91.189.91.39/ubuntu/pool/universe/y/yudit/yudit-common_2.9.6-7_all.deb --output yudit-common_2.9.6-7_all.deb
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   274  100   274    0     0     76      0  0:00:03  0:00:03 --:--:--    76

$ cat yudit-common_2.9.6-7_all.deb
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL was not found on this server.</p>
<hr>
<address>Apache/2.4.29 (Ubuntu) Server at 91.189.91.39 Port 80</address>
</body></html>

编辑 下列的gronostaj 的回答我尝试了两个命令。

A. 这有效(与上面的第 2 项相同),

    $ curl -v --resolve us.archive.ubuntu.com:80:91.189.91.39 -L http://us.archive.ubuntu.com/ubuntu/pool/universe/y/yudit/yudit-common_2.9.6-7_all.deb -- output yudit-common_2.9.6-7_all.deb
    ...
    <
    { [7725 bytes data]
      0 1599k    0  7725    0     0   5360      0  0:05:05  0:00:01  0:05:04  7874* STATE: PERFORM => DONE handle 0x800744e0; line 2199 (connection #0)
    * multi_done
    100 1599k  100 1599k    0     0   675k      0  0:00:02  0:00:02 --:--:--  838k
    * Connection #0 to host us.archive.ubuntu.com left intact

B. 这没有(与上面的第 3 项相同)。

    $ curl -v --resolve us.archive.ubuntu.com:80:91.189.91.39 -L http://91.189.91.39/ubuntu/pool/universe/y/yudit/yudit-common_2.9.6-7_all.deb --output yu dit-common_2.9.6-7_all.deb
    ...
    <
    { [274 bytes data]
    100   274  100   274    0     0    434      0 --:--:-- --:--:-- --:--:--   444* STATE: PERFORM => DONE handle 0x800744c8; line 2199 (connection #0)
    * multi_done
    100   274  100   274    0     0    430      0 --:--:-- --:--:-- --:--:--   439
    * Connection #0 to host 91.189.91.39 left intact

我想知道 B 是否是失败的第 3 项的正确修复方法,或者它实际上使用的是服务器名称而不是直接 IP(如第 2 项)。

答案1

服务器并不“仅仅知道”请求了哪个域名:客户端会自行解析域名并直接连接到 IP。事实证明,从单个 IP 为多个网站提供服务的能力非常方便,因此Host在 HTTP 标准的修订版中引入了标头。符合规范的 HTTP 客户端将从请求 URL 中提取域名并将其发送到标头中Host

示例 1

$ curl -v superuser.com 
* Rebuilt URL to: superuser.com/
*   Trying 151.101.1.69...
* TCP_NODELAY set
* Connected to superuser.com (151.101.1.69) port 80 (#0)
> GET / HTTP/1.1
> Host: superuser.com
> User-Agent: curl/7.58.0
> Accept: */*
> 
< HTTP/1.1 301 Moved Permanently
< cache-control: no-cache, no-store, must-revalidate
< location: https://superuser.com/
[...]
< 
* Connection #0 to host superuser.com left intact

客户端Host: superuser.com在请求中向superuser.com的 IP 发送标头。服务器回复请求重定向到网站的 HTTPS 版本。没有文档主体,这是有道理的,因为浏览器应该重定向您。curl没有 就不会这样做-L

现在我们尝试直接使用IP:

示例 2

$ curl -v 151.101.1.69             
* Rebuilt URL to: 151.101.1.69/
*   Trying 151.101.1.69...
* TCP_NODELAY set
* Connected to 151.101.1.69 (151.101.1.69) port 80 (#0)
> GET / HTTP/1.1
> Host: 151.101.1.69
> User-Agent: curl/7.58.0
> Accept: */*
> 
< HTTP/1.1 500 Domain Not Found
< Server: Varnish
[...]
< 

<html>
<head>
<title>Fastly error: unknown domain 151.101.1.69</title>
</head>
<body>
<p>Fastly error: unknown domain: 151.101.1.69. Please check that this domain has been added to a service.</p>
* Connection #0 to host 151.101.1.69 left intact
<p>Details: cache-ams21021-AMS</p></body></html>

curl在标头中发送了 IP Host,响应是 500 错误,正文详细说明了问题。服务器不提供Host标头中提供的域。

让我们手动提供标题:

示例 3

$ curl -H 'Host: superuser.com' -v 151.101.1.69
* Rebuilt URL to: 151.101.1.69/
*   Trying 151.101.1.69...
* TCP_NODELAY set
* Connected to 151.101.1.69 (151.101.1.69) port 80 (#0)
> GET / HTTP/1.1
> Host: superuser.com
> User-Agent: curl/7.58.0
> Accept: */*
> 
< HTTP/1.1 301 Moved Permanently
< cache-control: no-cache, no-store, must-revalidate
< location: https://superuser.com/
[...]
< 
* Connection #0 to host 151.101.1.69 left intact

正如预期的那样,我们再次获得了重定向。服务器并不“仅仅知道”请求是通过直接提供 IP 发出的,因为它总是这样发出的:客户端负责解析域名。事实证明,从单个 IP 为多个网站提供服务的能力非常方便,因此Host在 HTTP 标准的修订版中引入了标头。

不幸的是,这不适用于 HTTPS。HTTPS 基本上是包裹在 TLS 中的 HTTP。在通过 HTTP 发送任何内容之前,需要设置 TLS 连接。此过程涉及服务器为请求的域提供适当的证书。这需要了解域,所以我们又回到了原点。此问题由 SNI 解决,SNI 是 TLS 的扩展,它指定客户端如何将域传达给服务器,以便可以使用正确的证书。

您可以使用 curl 来模拟这种情况--resolve

示例 4

$ curl -v --resolve superuser.com:443:151.101.65.69 https://superuser.com
* Added superuser.com:443:151.101.65.69 to DNS cache
* Rebuilt URL to: https://superuser.com/
* Hostname superuser.com was found in DNS cache
[...]
* Connected to superuser.com (151.101.65.69) port 443 (#0)
[...]
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=*.stackexchange.com
*  start date: Aug  7 13:01:00 2020 GMT
*  expire date: Nov  5 13:01:00 2020 GMT
*  subjectAltName: host "superuser.com" matched cert's "superuser.com"
*  issuer: C=US; O=Let's Encrypt; CN=Let's Encrypt Authority X3
*  SSL certificate verify ok.
[...]
> GET / HTTP/2
> Host: superuser.com
> User-Agent: curl/7.58.0
> Accept: */*
> 
[...]
< HTTP/2 200 
< cache-control: private
< content-type: text/html; charset=utf-8
[...]
<!DOCTYPE html>
[...]

--resolve绕过给定主机的 DNS 解析。正如手册所述,它是“一种 /etc/hosts 替代方案”。参数语法是<host>:<port>:<ip>。因此此命令:

示例 5

curl -v --resolve superuser.com:443:151.101.65.69 https://superuser.com

方法:

  • -v:详细(打印标题和 TLS 详细信息)
  • --resolve superuser.com:443:151.101.65.69:如果连接到superuser.com端口443,则实际使用IP 151.101.65.69
  • https://superuser.com:使用 HTTPS 向 superuser.com 发出请求

至于为什么域名必须重复两次,当单次 curl 调用会产生多个请求时,这很有意义,例如由于重定向和-L提供:

示例 6

$ curl -v --resolve superuser.com:443:151.101.65.69 -L http://superuser.com

此命令将首先superuser.com使用 DNS 进行解析。--resolve不适用于此请求,因为它是为端口 443 指定的,而我们通过 HTTP 连接,在端口 80 上。服务器以 301 重定向响应到https://superuser.com。我们已指定-L,因此 curl 将向该 URL 发出第二个请求。这次它通过端口 443 上的 HTTPS 进行,并且我们已使用 为该主机和端口指定了一个 IP --resolve,因此将使用指定的 IP(先前的 DNS 查找将被忽略)。在两种情况下Host都会生成标头,superuser.com因为这就是我们所请求的。

这是实际的 curl 输出。请注意,第二个请求导致出现“在 DNS 缓存中找到主机名 superuser.com”消息,这是--resolve实际操作。

示例 6(续)

* Added superuser.com:443:151.101.65.69 to DNS cache
* Rebuilt URL to: http://superuser.com/
*   Trying 151.101.65.69...
* TCP_NODELAY set
* Connected to superuser.com (151.101.65.69) port 80 (#0)
> GET / HTTP/1.1
> Host: superuser.com
> User-Agent: curl/7.58.0
> Accept: */*
> 
< HTTP/1.1 301 Moved Permanently
< cache-control: no-cache, no-store, must-revalidate
< location: https://superuser.com/
[...]
* Ignoring the response-body
[...]
* Connection #0 to host superuser.com left intact
* Issue another request to this URL: 'https://superuser.com/'
* Hostname superuser.com was found in DNS cache
*   Trying 151.101.65.69...
* TCP_NODELAY set
* Connected to superuser.com (151.101.65.69) port 443 (#1)
[...]

进一步澄清正确使用--resolve

使用时--resolve,必须请求域名(,而不是直接请求IP。请求IP将会:

  • Host为 IP 而不是域名生成标头,
  • 在 SNI 步骤中声明你直接访问 IP,而不是通过域名访问(如果使用 HTTPS),
  • --resolve不适用的原因是因为--resolve绕过了域名解析,并且当没有提供域名时,就不需要进行域名解析。

所以你想要这个:

示例 7

curl --resolve example.com:80:93.184.216.34 http://example.com

而不是这样:

示例 8

curl --resolve example.com:80:93.184.216.34 http://93.184.216.34

在示例 7 中,curl将使用 提供的 IP 地址--resolve,而不是example.comDNS 解析的 IP 地址。

何时--resolve适用

每个--resolve(允许多个)由 3 个组件组成:主机、端口和 IP。--resolve如果主机和端口匹配,则适用于请求,在这种情况下,将绕过此特定请求的 DNS 解析并--resolve使用匹配的 IP。在许多情况下,单个curl调用只会发出一个请求,在这种情况下,--resolve只有当其主机和端口与请求的主机和端口匹配时才有意义。因此,此调用没有意义,因为--resolve由于端口不匹配而永远不会匹配(HTTPS 默认使用 443):

示例 9

curl --resolve example.com:80:93.184.216.34 https://example.com

什么时候curl每次调用都会发出多个请求?我知道的情况是,当-L提供时,第一个请求导致 3xx 响应(这是重定向响应系列,请参阅httpstatuses.com)。这些响应带有一个Location标头,告知浏览器向该标头中提供的地址发出另一个请求。如果没有-Lcurl则只会打印 3xx 响应。有了-L它将像浏览器一样发出另一个请求。(请注意,第二个请求也可能导致 3xx 响应,从而生成第三个响应,依此类推)。

例如,对 superuser.com 的 HTTP 请求会导致 301 响应并重定向到 HTTPS 版本,请参见Location显示标头的示例 1。这样,-L您将获得与首先请求 HTTPS 版本相同的响应。HTTP 和 HTTPS 使用不同的端口(80 和 443),因此--resolve在这种情况下您需要两个 s,每个端口一个。您还可以有意指定一个 s 来覆盖仅针对 HTTP(或 HTTPS)请求的域名解析,而让另一个指向 DNS 将返回的实际 IP。

相关内容