cURL 对ipv4、ipv6是这样一个处理过程:
- 解析被请求的域名,通常会获得一个ip地址列表,同时包含了ipv4和ipv6地址;
- cURL会首先发起连接ipv6地址,如果200ms内连接成功,则直接使用此地址;
- 如果200ms未连接成功ipv6地址,cURL会对列表中地址并发发起连接;
- 最后等待第一个成功建立的连接,将作为后续cURL传输地址。
这是cURL作者的描述,看这里。
从上面流程可以看出几点:ipv6优先机制、快乐眼球(Happy Eyeballs)机制(也叫快速回退Fast Fallback机制)、轮询调度机制(Round-robin DNS)。
Round-robin DNS 是什么?
这是DNS服务器的轮询调度算法,至于为啥叫Round-robin?这个词源于法语ruban rond(round ribbon),意思是环形丝带,其出处还挺有意思的,可阅读这里
轮询调度经常用在负载均衡的处理上,如为一个域名配置多个ip地址:
server IN A 192.168.0.1
server IN A 10.0.0.1
server IN A 127.0.0.1
当对域名的并发请求过来时,服务器会按顺序轮流返回ip地址。这个算法的好处是实现简单、机会公平均等。缺点也比较明显,就是不同时刻的请求可能会获得不同的地址,这会对客户端基于ip地址的连接共享、缓存等造成影响。如果一个服务器ip结点宕机了,它的ip地址可能还会被返回使用。
Happy Eyeballs 是什么?
“快乐眼球”是客户端的一个算法,它叫这么个名字,是想表达这个算法是用在互联网中的用户端(客户端)而不是服务器端。这里有一个比较好的文章。
cURL的实现不是简单的执行:getaddrinfo() -> 遍历每个ip地址然后并发发起 connect()。它的做法将全部地址分开到一个ipv4专用和一个ipv6专用的两个线程队列,然后再并发发起连接,最先收到回复的连接作为最终使用的连接。
这个算法的提出是在2011年 World IPv6 Day,为了避免 ipv6 起步阶段网络环境不理想。
除了cURL,目前支持 Happy Eyeballs 其它软件包括 Chrome 浏览器, Opera 浏览器, Firefox 13+, 苹果操作系统 OS X 等。
关于上面算法看cURL作者的说明,GETADDRINFO WITH ROUND ROBIN DNS AND HAPPY EYEBALLS
为 cURL 指定 ipv4,ipv6
通过命令行参数 --ipv4 或 -4 强制使用 ipv4 协议:
% curl --ipv4 https://example.org/
通过命令行参数 --ipv6 或 -6 强制使用 ipv6 协议:
% curl --ipv6 https://example.org/
加上参数 -v 可以看到连接的过程:
% curl -v http://example.org/ ~
* Trying 192.168.199.217...
* TCP_NODELAY set
* Connection failed
* connect to 192.168.199.217 port 80 failed: Connection refused
* Trying 2001::1...
* TCP_NODELAY set
* Connected to example.org (2001::1) port 80 (#0)
> GET / HTTP/1.1
> Host: example.org
> User-Agent: curl/7.64.1
> Accept: */*
>
html body
小工具
nslookup 做 DNS 解析
# type 和 query、querytype 三个相同作用的参数,用于设置查询类型,默认值为 A
% nslookup -type=A example.test
Server: 192.168.199.1
Address: 192.168.199.1#53
Name: example.test
Address: 192.168.199.169
% nslookup -type=AAAA example.test
Server: 192.168.199.1
Address: 192.168.199.1#53
example.test has AAAA address 2001::2
getaddrinfo() 同时解析 ipv4 和 ipv6
#include <cstring> //memset
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <iostream>
#include <string>
#include <vector>
using std::vector;
using std::string;
using std::cout;
using std::endl;
vector<string> dns_lookup(const string &host_name, int ipv=4);
int main(int argc, char* argv[])
{
int ipv;
vector<string> domains;
cout << "dns both ipv4 and ipv6: example.test" << endl;
domains = dns_lookup("example.test", ipv=0);
for(int i=0,ix=domains.size(); i<ix; i++)
cout << " " << domains[i] << endl;
return 0;
}
vector<string> dns_lookup(const string &host_name, int ipv)
{
vector<string> output;
struct addrinfo hints, *res, *p;
int status, ai_family;
char ip_address[INET6_ADDRSTRLEN];
ai_family = ipv==6 ? AF_INET6 : AF_INET; //v4 vs v6?
ai_family = ipv==0 ? AF_UNSPEC : ai_family; // AF_UNSPEC (any), or chosen
memset(&hints, 0, sizeof hints);
hints.ai_family = ai_family;
hints.ai_socktype = SOCK_STREAM;
if ((status = getaddrinfo(host_name.c_str(), NULL, &hints, &res)) != 0) {
//cerr << "getaddrinfo: "<< gai_strerror(status) << endl;
return output;
}
//cout << "DNS Lookup: " << host_name << " ipv:" << ipv << endl;
for(p = res;p != NULL; p = p->ai_next) {
void *addr;
if (p->ai_family == AF_INET) { // IPv4
struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
addr = &(ipv4->sin_addr);
} else { // IPv6
struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
addr = &(ipv6->sin6_addr);
}
// convert the IP to a string
inet_ntop(p->ai_family, addr, ip_address, sizeof ip_address);
output.push_back(ip_address);
}
freeaddrinfo(res); // free the linked list
return output;
}
dig 解析 DNS
# 同时解析 ipv4 和 ipv6
% dig example.test AAAA example.test A +short
2001::2
192.168.199.169
# @域名服务器在前面表示全球服务器
% dig @8.8.8.8 tv.ipv6.edu.cn AAAA
; <<>> DiG 9.10.6 <<>> @8.8.8.8 tv.ipv6.edu.cn AAAA
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 84
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;tv.ipv6.edu.cn. IN AAAA
;; ANSWER SECTION:
tv.ipv6.edu.cn. 3600 IN AAAA 2001:da8:217:1::234
;; Query time: 158 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Mon Sep 28 09:11:02 CST 2020
;; MSG SIZE rcvd: 60
# @域名服务器在后面表示本地服务器
% dig wikimedia.org MX @ns0.wikimedia.org
; <<>> DiG 9.6.1 <<>> wikimedia.org MX @ns0.wikimedia.org
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 61144
;; flags: qr aa rd; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 2
;; WARNING: recursion requested but not available
;; QUESTION SECTION:
;wikimedia.org. IN MX
;; ANSWER SECTION:
wikimedia.org. 3600 IN MX 10 mchenry.wikimedia.org.
wikimedia.org. 3600 IN MX 50 lists.wikimedia.org.
;; ADDITIONAL SECTION:
mchenry.wikimedia.org. 3600 IN A 208.80.152.186
lists.wikimedia.org. 3600 IN A 91.198.174.5
;; Query time: 73 msec
;; SERVER: 208.80.152.130#53(208.80.152.130)
;; WHEN: Wed Aug 12 11:51:03 2009
;; MSG SIZE rcvd: 109
名词
- ipv6:Internet Protocol version 6
- A Record:Address record,是一组32位ipv4地址
- AAAA Record:ipv6 address record,是一组128位ipv6地址