腾讯曾在其微信公众账号中介绍过HTTPDNS的实现,具体为什么要设计httpdns,大家可以搜一下腾讯httpdns。httpdns为手机APP提供了一种简单稳定可靠的方式来快速获取智能DNS解析结果,而且httpdns绕过了传统dns的交互步骤,使得后端智能解析决策DNS服务器可以直接看到真实客户端地址,这解决了当今edns-client-subnet还不能完全被支持的窘境,优势很大。
而实际上这个看着逼格很高的技术通过F5的irule也可以快速的实现,对已经部署GTM/LTM设备的F5客户来说,这一切很容易实现。
技术原理:
1 客户端通过发起一个普通的http请求, 请求格式为http://your-httpdns-vs-ip/?sip=1.1.1.1&dn=www.myf5.net ,sip的值为要传递的真是客户端公网IP,dn为需要解析的域名
2.LTM上的httpdns vs接收到该请求后,通过irule处理,irule内部调用了同一台上的另一个layer vs(这个vs并不对外,只提供给irule内部调用),此时http 请求并转化为标准dns 请求
3. layer vs在接收到DNS请求后,使用irule对该请求进行一些必要的处理后,将处理后的DNS请求发送给F5 GTM,此时发送给GTM的请求的源地址已经变为了sip参数所指定的IP
4. F5 GTM像处理普通DNS请求一样,执行智能解析,并返回智能解析的结果,注意此时GTM利用 Auto lasthop功能直接将解析结果送给LTM,而不是通过路由送给上游路由器。
5. LTM的layer vs会匹配处理GTM送回的DNS应答并做相应处理原路返回给HTTPdns VS
6. 最后原路返回给客户端
测试案例:
通过在GTM上设计如果SIP是192.168.232.200则返回IPA,如果是192.168.232.100则返回IPB,其他任意地址则RR返回,以下是模拟测试结果:
技术关键点:
1. httpdns vs 的irule模拟HTTP 应答,执行相关合法性检查,使用RESOLV::lookup命令调度DNS查询,注意系统TMM会自动根据dns的TTL对DNS 响应进行缓存,所以需要通过layer vs层将dns response处理修改为TTL=0,不应将原始dns应答中的ttl透传给httpdns v。
2.layer vs接收到的DNS请求实际上来自TMM 内部,所以layer vs看到的请求源地址实际上是内部的127地址,如何透传真是客户端IP给layer vs是个关键。
3.layer vs要根据透传过来的真实客户端地址执行SNAT
4.LTM和GTM,也就是layer vs和GTM之间要能实现auto lasthop,否则除非你的网络结构上GTM得默认网关是该LTM设备。
配置关键点:
1. httpdns vs, 80服务,关联http profile,不配置任何实际pool,关联一个irule
2. layer vs,提供53端口转发,关联dns profile(注意你的设备要有对应的license)该dns profile中建议关闭gtm/DNSSEC/DNS CACHE/DNSEXPRESS/local BIND这些功能,只是因为irule中的命令需要这个profile而已。同时该vs关联一个udp的profile,这个profile启用datagram LB,timeout建议设置为较短的超时值,关联一个pool指向GTM的listener ip, 关联一个irule
irule:
httpdns vs关联的irule:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
when HTTP_REQUEST { set uriq "?[URI::query [HTTP::uri]]" set srcip [URI::query $uriq sip] set hostname [URI::query $uriq dn] #Here, should also check the ip and the query name format if {$srcip == "" or $hostname == ""} { HTTP::respond 200 content "Bie Nao! Please give enough inputs!" "Cache-Control" "no-cache" } elseif {!($srcip matches_regex {^[0-9\.]+$})}{ HTTP::respond 200 content "Bie Nao! Need correct ip!" "Cache-Control" "no-cache" } elseif {!($hostname matches_regex {^[\w\.\-\_]+$})}{ HTTP::respond 200 content "Bie Nao! Need correct hostname!" "Cache-Control" "no-cache" } else { #Please note, it does not support CNAME answer as using resolve command set resolveip [RESOLV::lookup @/Common/layer_httpdns "$hostname|$srcip"] HTTP::respond 200 content "The Client IP is $srcip , The ip of the query name $hostname is $resolveip <br>Currently dont support CNAME answer!" "Cache-Control" "no-cache" } } |
上述irule为功能测试irule,实际使用建议优化相关正则表达式以提高性能,同时上述对IP地址的正则表达式并不严格。
layer vs 关联的irule:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
when DNS_REQUEST { set ori_qname [DNS::question name] set qname [getfield [DNS::question name] "|" 1] set dns_srcip [getfield [DNS::question name] "|" 2] log local0. "qnmae is $qname" log local0. "question name before changing: $ori_qname" DNS::question name $qname log local0. "question name: [DNS::question name]" snat $dns_srcip } when DNS_RESPONSE { #change query name back to original which include ip DNS::question name $ori_qname #change all name in answer section to original which include ip #also need change ttl to 0 to avoid the upstream vs caching result set rrs [DNS::answer] foreach rr $rrs { DNS::name $rr $ori_qname } } |
实际使用中关闭log命令,同时加上修改ttl=0的处理(上述irule没有加,自己加一下吧 :) )或通过修改tmm resolv cache 条目为0的db key来规避resolve命令对解析的缓存
上述用法,配置都是为了实现功能,如需实际使用请考虑加强性能、以及bug测试。