PNG  IHDRX cHRMz&u0`:pQ<bKGD pHYsodtIME MeqIDATxw]Wug^Qd˶ 6`!N:!@xI~)%7%@Bh&`lnjVF29gΨ4E$|>cɚ{gk= %,a KX%,a KX%,a KX%,a KX%,a KX%,a KX%, b` ǟzeאfp]<!SJmɤY޲ڿ,%c ~ع9VH.!Ͳz&QynֺTkRR.BLHi٪:l;@(!MԴ=žI,:o&N'Kù\vRmJ雵֫AWic H@" !: Cé||]k-Ha oݜ:y F())u]aG7*JV@J415p=sZH!=!DRʯvɱh~V\}v/GKY$n]"X"}t@ xS76^[bw4dsce)2dU0 CkMa-U5tvLƀ~mlMwfGE/-]7XAƟ`׮g ewxwC4\[~7@O-Q( a*XGƒ{ ՟}$_y3tĐƤatgvێi|K=uVyrŲlLӪuܿzwk$m87k( `múcE)"@rK( z4$D; 2kW=Xb$V[Ru819קR~qloѱDyįݎ*mxw]y5e4K@ЃI0A D@"BDk_)N\8͜9dz"fK0zɿvM /.:2O{ Nb=M=7>??Zuo32 DLD@D| &+֎C #B8ַ`bOb $D#ͮҪtx]%`ES`Ru[=¾!@Od37LJ0!OIR4m]GZRJu$‡c=%~s@6SKy?CeIh:[vR@Lh | (BhAMy=݃  G"'wzn޺~8ԽSh ~T*A:xR[ܹ?X[uKL_=fDȊ؂p0}7=D$Ekq!/t.*2ʼnDbŞ}DijYaȲ(""6HA;:LzxQ‘(SQQ}*PL*fc\s `/d'QXW, e`#kPGZuŞuO{{wm[&NBTiiI0bukcA9<4@SӊH*؎4U/'2U5.(9JuDfrޱtycU%j(:RUbArLֺN)udA':uGQN"-"Is.*+k@ `Ojs@yU/ H:l;@yyTn}_yw!VkRJ4P)~y#)r,D =ě"Q]ci'%HI4ZL0"MJy 8A{ aN<8D"1#IJi >XjX֔#@>-{vN!8tRݻ^)N_╗FJEk]CT՟ YP:_|H1@ CBk]yKYp|og?*dGvzنzӴzjֺNkC~AbZƷ`.H)=!QͷVTT(| u78y֮}|[8-Vjp%2JPk[}ԉaH8Wpqhwr:vWª<}l77_~{s۴V+RCģ%WRZ\AqHifɤL36: #F:p]Bq/z{0CU6ݳEv_^k7'>sq*+kH%a`0ԣisqにtү04gVgW΂iJiS'3w.w}l6MC2uԯ|>JF5`fV5m`Y**Db1FKNttu]4ccsQNnex/87+}xaUW9y>ͯ骵G{䩓Գ3+vU}~jJ.NFRD7<aJDB1#ҳgSb,+CS?/ VG J?|?,2#M9}B)MiE+G`-wo߫V`fio(}S^4e~V4bHOYb"b#E)dda:'?}׮4繏`{7Z"uny-?ǹ;0MKx{:_pÚmFמ:F " .LFQLG)Q8qN q¯¯3wOvxDb\. BKD9_NN &L:4D{mm o^tֽ:q!ƥ}K+<"m78N< ywsard5+вz~mnG)=}lYݧNj'QJS{S :UYS-952?&O-:W}(!6Mk4+>A>j+i|<<|;ر^߉=HE|V#F)Emm#}/"y GII웻Jі94+v뾧xu~5C95~ūH>c@덉pʃ1/4-A2G%7>m;–Y,cyyaln" ?ƻ!ʪ<{~h~i y.zZB̃/,雋SiC/JFMmBH&&FAbϓO^tubbb_hZ{_QZ-sύodFgO(6]TJA˯#`۶ɟ( %$&+V'~hiYy>922 Wp74Zkq+Ovn錄c>8~GqܲcWꂎz@"1A.}T)uiW4="jJ2W7mU/N0gcqܗOO}?9/wìXžΏ0 >֩(V^Rh32!Hj5`;O28؇2#ݕf3 ?sJd8NJ@7O0 b־?lldщ̡&|9C.8RTWwxWy46ah嘦mh٤&l zCy!PY?: CJyв]dm4ǜҐR޻RլhX{FƯanшQI@x' ao(kUUuxW_Ñ줮[w8 FRJ(8˼)_mQ _!RJhm=!cVmm ?sFOnll6Qk}alY}; "baӌ~M0w,Ggw2W:G/k2%R,_=u`WU R.9T"v,<\Ik޽/2110Ӿxc0gyC&Ny޽JҢrV6N ``یeA16"J³+Rj*;BϜkZPJaÍ<Jyw:NP8/D$ 011z֊Ⱳ3ι֘k1V_"h!JPIΣ'ɜ* aEAd:ݺ>y<}Lp&PlRfTb1]o .2EW\ͮ]38؋rTJsǏP@芎sF\> P^+dYJLbJ C-xϐn> ι$nj,;Ǖa FU *择|h ~izť3ᤓ`K'-f tL7JK+vf2)V'-sFuB4i+m+@My=O҈0"|Yxoj,3]:cо3 $#uŘ%Y"y죯LebqtҢVzq¼X)~>4L׶m~[1_k?kxֺQ`\ |ٛY4Ѯr!)N9{56(iNq}O()Em]=F&u?$HypWUeB\k]JɩSع9 Zqg4ZĊo oMcjZBU]B\TUd34ݝ~:7ڶSUsB0Z3srx 7`:5xcx !qZA!;%͚7&P H<WL!džOb5kF)xor^aujƍ7 Ǡ8/p^(L>ὴ-B,{ۇWzֺ^k]3\EE@7>lYBȝR.oHnXO/}sB|.i@ɥDB4tcm,@ӣgdtJ!lH$_vN166L__'Z)y&kH;:,Y7=J 9cG) V\hjiE;gya~%ks_nC~Er er)muuMg2;֫R)Md) ,¶ 2-wr#F7<-BBn~_(o=KO㭇[Xv eN_SMgSҐ BS헃D%g_N:/pe -wkG*9yYSZS.9cREL !k}<4_Xs#FmҶ:7R$i,fi!~' # !6/S6y@kZkZcX)%5V4P]VGYq%H1!;e1MV<!ϐHO021Dp= HMs~~a)ަu7G^];git!Frl]H/L$=AeUvZE4P\.,xi {-~p?2b#amXAHq)MWǾI_r`S Hz&|{ +ʖ_= (YS(_g0a03M`I&'9vl?MM+m~}*xT۲(fY*V4x@29s{DaY"toGNTO+xCAO~4Ϳ;p`Ѫ:>Ҵ7K 3}+0 387x\)a"/E>qpWB=1 ¨"MP(\xp߫́A3+J] n[ʼnӼaTbZUWb={~2ooKױӰp(CS\S筐R*JغV&&"FA}J>G֐p1ٸbk7 ŘH$JoN <8s^yk_[;gy-;߉DV{c B yce% aJhDȶ 2IdйIB/^n0tNtџdcKj4϶v~- CBcgqx9= PJ) dMsjpYB] GD4RDWX +h{y`,3ꊕ$`zj*N^TP4L:Iz9~6s) Ga:?y*J~?OrMwP\](21sZUD ?ܟQ5Q%ggW6QdO+\@ ̪X'GxN @'4=ˋ+*VwN ne_|(/BDfj5(Dq<*tNt1х!MV.C0 32b#?n0pzj#!38}޴o1KovCJ`8ŗ_"]] rDUy޲@ Ȗ-;xџ'^Y`zEd?0„ DAL18IS]VGq\4o !swV7ˣι%4FѮ~}6)OgS[~Q vcYbL!wG3 7띸*E Pql8=jT\꘿I(z<[6OrR8ºC~ډ]=rNl[g|v TMTղb-o}OrP^Q]<98S¤!k)G(Vkwyqyr޽Nv`N/e p/~NAOk \I:G6]4+K;j$R:Mi #*[AȚT,ʰ,;N{HZTGMoּy) ]%dHء9Պ䠬|<45,\=[bƟ8QXeB3- &dҩ^{>/86bXmZ]]yޚN[(WAHL$YAgDKp=5GHjU&99v簪C0vygln*P)9^͞}lMuiH!̍#DoRBn9l@ xA/_v=ȺT{7Yt2N"4!YN`ae >Q<XMydEB`VU}u]嫇.%e^ánE87Mu\t`cP=AD/G)sI"@MP;)]%fH9'FNsj1pVhY&9=0pfuJ&gޤx+k:!r˭wkl03׼Ku C &ѓYt{.O.zҏ z}/tf_wEp2gvX)GN#I ݭ߽v/ .& и(ZF{e"=V!{zW`, ]+LGz"(UJp|j( #V4, 8B 0 9OkRrlɱl94)'VH9=9W|>PS['G(*I1==C<5"Pg+x'K5EMd؞Af8lG ?D FtoB[je?{k3zQ vZ;%Ɠ,]E>KZ+T/ EJxOZ1i #T<@ I}q9/t'zi(EMqw`mYkU6;[t4DPeckeM;H}_g pMww}k6#H㶏+b8雡Sxp)&C $@'b,fPߑt$RbJ'vznuS ~8='72_`{q纶|Q)Xk}cPz9p7O:'|G~8wx(a 0QCko|0ASD>Ip=4Q, d|F8RcU"/KM opKle M3#i0c%<7׿p&pZq[TR"BpqauIp$ 8~Ĩ!8Սx\ւdT>>Z40ks7 z2IQ}ItԀ<-%S⍤};zIb$I 5K}Q͙D8UguWE$Jh )cu4N tZl+[]M4k8֦Zeq֮M7uIqG 1==tLtR,ƜSrHYt&QP윯Lg' I,3@P'}'R˪e/%-Auv·ñ\> vDJzlӾNv5:|K/Jb6KI9)Zh*ZAi`?S {aiVDԲuy5W7pWeQJk֤#5&V<̺@/GH?^τZL|IJNvI:'P=Ϛt"¨=cud S Q.Ki0 !cJy;LJR;G{BJy޺[^8fK6)=yʊ+(k|&xQ2`L?Ȓ2@Mf 0C`6-%pKpm')c$׻K5[J*U[/#hH!6acB JA _|uMvDyk y)6OPYjœ50VT K}cǻP[ $:]4MEA.y)|B)cf-A?(e|lɉ#P9V)[9t.EiQPDѠ3ϴ;E:+Օ t ȥ~|_N2,ZJLt4! %ա]u {+=p.GhNcŞQI?Nd'yeh n7zi1DB)1S | S#ًZs2|Ɛy$F SxeX{7Vl.Src3E℃Q>b6G ўYCmtկ~=K0f(=LrAS GN'ɹ9<\!a`)֕y[uՍ[09` 9 +57ts6}b4{oqd+J5fa/,97J#6yν99mRWxJyѡyu_TJc`~W>l^q#Ts#2"nD1%fS)FU w{ܯ R{ ˎ󅃏џDsZSQS;LV;7 Od1&1n$ N /.q3~eNɪ]E#oM~}v֯FڦwyZ=<<>Xo稯lfMFV6p02|*=tV!c~]fa5Y^Q_WN|Vs 0ҘދU97OI'N2'8N֭fgg-}V%y]U4 峧p*91#9U kCac_AFңĪy뚇Y_AiuYyTTYЗ-(!JFLt›17uTozc. S;7A&&<ԋ5y;Ro+:' *eYJkWR[@F %SHWP 72k4 qLd'J "zB6{AC0ƁA6U.'F3:Ȅ(9ΜL;D]m8ڥ9}dU "v!;*13Rg^fJyShyy5auA?ɩGHRjo^]׽S)Fm\toy 4WQS@mE#%5ʈfFYDX ~D5Ϡ9tE9So_aU4?Ѽm%&c{n>.KW1Tlb}:j uGi(JgcYj0qn+>) %\!4{LaJso d||u//P_y7iRJ߬nHOy) l+@$($VFIQ9%EeKʈU. ia&FY̒mZ=)+qqoQn >L!qCiDB;Y<%} OgBxB!ØuG)WG9y(Ą{_yesuZmZZey'Wg#C~1Cev@0D $a@˲(.._GimA:uyw֬%;@!JkQVM_Ow:P.s\)ot- ˹"`B,e CRtaEUP<0'}r3[>?G8xU~Nqu;Wm8\RIkբ^5@k+5(By'L&'gBJ3ݶ!/㮻w҅ yqPWUg<e"Qy*167΃sJ\oz]T*UQ<\FԎ`HaNmڜ6DysCask8wP8y9``GJ9lF\G g's Nn͵MLN֪u$| /|7=]O)6s !ĴAKh]q_ap $HH'\1jB^s\|- W1:=6lJBqjY^LsPk""`]w)󭃈,(HC ?䔨Y$Sʣ{4Z+0NvQkhol6C.婧/u]FwiVjZka&%6\F*Ny#8O,22+|Db~d ~Çwc N:FuuCe&oZ(l;@ee-+Wn`44AMK➝2BRՈt7g*1gph9N) *"TF*R(#'88pm=}X]u[i7bEc|\~EMn}P瘊J)K.0i1M6=7'_\kaZ(Th{K*GJyytw"IO-PWJk)..axӝ47"89Cc7ĐBiZx 7m!fy|ϿF9CbȩV 9V-՛^pV̌ɄS#Bv4-@]Vxt-Z, &ֺ*diؠ2^VXbs֔Ìl.jQ]Y[47gj=幽ex)A0ip׳ W2[ᎇhuE^~q흙L} #-b۸oFJ_QP3r6jr+"nfzRJTUqoaۍ /$d8Mx'ݓ= OՃ| )$2mcM*cЙj}f };n YG w0Ia!1Q.oYfr]DyISaP}"dIӗթO67jqR ҊƐƈaɤGG|h;t]䗖oSv|iZqX)oalv;۩meEJ\!8=$4QU4Xo&VEĊ YS^E#d,yX_> ۘ-e\ "Wa6uLĜZi`aD9.% w~mB(02G[6y.773a7 /=o7D)$Z 66 $bY^\CuP. (x'"J60׿Y:Oi;F{w佩b+\Yi`TDWa~|VH)8q/=9!g߆2Y)?ND)%?Ǐ`k/sn:;O299yB=a[Ng 3˲N}vLNy;*?x?~L&=xyӴ~}q{qE*IQ^^ͧvü{Huu=R|>JyUlZV, B~/YF!Y\u_ݼF{_C)LD]m {H 0ihhadd nUkf3oٺCvE\)QJi+֥@tDJkB$1!Đr0XQ|q?d2) Ӣ_}qv-< FŊ߫%roppVBwü~JidY4:}L6M7f٬F "?71<2#?Jyy4뷢<_a7_=Q E=S1И/9{+93֮E{ǂw{))?maÆm(uLE#lïZ  ~d];+]h j?!|$F}*"4(v'8s<ŏUkm7^7no1w2ؗ}TrͿEk>p'8OB7d7R(A 9.*Mi^ͳ; eeUwS+C)uO@ =Sy]` }l8^ZzRXj[^iUɺ$tj))<sbDJfg=Pk_{xaKo1:-uyG0M ԃ\0Lvuy'ȱc2Ji AdyVgVh!{]/&}}ċJ#%d !+87<;qN޼Nفl|1N:8ya  8}k¾+-$4FiZYÔXk*I&'@iI99)HSh4+2G:tGhS^繿 Kتm0 вDk}֚+QT4;sC}rՅE,8CX-e~>G&'9xpW,%Fh,Ry56Y–hW-(v_,? ; qrBk4-V7HQ;ˇ^Gv1JVV%,ik;D_W!))+BoS4QsTM;gt+ndS-~:11Sgv!0qRVh!"Ȋ(̦Yl.]PQWgٳE'`%W1{ndΗBk|Ž7ʒR~,lnoa&:ü$ 3<a[CBݮwt"o\ePJ=Hz"_c^Z.#ˆ*x z̝grY]tdkP*:97YľXyBkD4N.C_[;F9`8& !AMO c `@BA& Ost\-\NX+Xp < !bj3C&QL+*&kAQ=04}cC!9~820G'PC9xa!w&bo_1 Sw"ܱ V )Yl3+ס2KoXOx]"`^WOy :3GO0g;%Yv㐫(R/r (s } u B &FeYZh0y> =2<Ϟc/ -u= c&׭,.0"g"7 6T!vl#sc>{u/Oh Bᾈ)۴74]x7 gMӒ"d]U)}" v4co[ ɡs 5Gg=XR14?5A}D "b{0$L .\4y{_fe:kVS\\O]c^W52LSBDM! C3Dhr̦RtArx4&agaN3Cf<Ԉp4~ B'"1@.b_/xQ} _߃҉/gٓ2Qkqp0շpZ2fԫYz< 4L.Cyυι1t@鎫Fe sYfsF}^ V}N<_`p)alٶ "(XEAVZ<)2},:Ir*#m_YӼ R%a||EƼIJ,,+f"96r/}0jE/)s)cjW#w'Sʯ5<66lj$a~3Kʛy 2:cZ:Yh))+a߭K::N,Q F'qB]={.]h85C9cr=}*rk?vwV렵ٸW Rs%}rNAkDv|uFLBkWY YkX מ|)1!$#3%y?pF<@<Rr0}: }\J [5FRxY<9"SQdE(Q*Qʻ)q1E0B_O24[U'],lOb ]~WjHޏTQ5Syu wq)xnw8~)c 쫬gٲߠ H% k5dƝk> kEj,0% b"vi2Wس_CuK)K{n|>t{P1򨾜j>'kEkƗBg*H%'_aY6Bn!TL&ɌOb{c`'d^{t\i^[uɐ[}q0lM˕G:‚4kb祔c^:?bpg… +37stH:0}en6x˟%/<]BL&* 5&fK9Mq)/iyqtA%kUe[ڛKN]Ě^,"`/ s[EQQm?|XJ߅92m]G.E΃ח U*Cn.j_)Tѧj̿30ڇ!A0=͜ar I3$C^-9#|pk!)?7.x9 @OO;WƝZBFU keZ75F6Tc6"ZȚs2y/1 ʵ:u4xa`C>6Rb/Yм)^=+~uRd`/|_8xbB0?Ft||Z\##|K 0>>zxv8۴吅q 8ĥ)"6>~\8:qM}#͚'ĉ#p\׶ l#bA?)|g g9|8jP(cr,BwV (WliVxxᡁ@0Okn;ɥh$_ckCgriv}>=wGzβ KkBɛ[˪ !J)h&k2%07δt}!d<9;I&0wV/ v 0<H}L&8ob%Hi|޶o&h1L|u֦y~󛱢8fٲUsւ)0oiFx2}X[zVYr_;N(w]_4B@OanC?gĦx>мgx>ΛToZoOMp>40>V Oy V9iq!4 LN,ˢu{jsz]|"R޻&'ƚ{53ўFu(<٪9:΋]B;)B>1::8;~)Yt|0(pw2N%&X,URBK)3\zz&}ax4;ǟ(tLNg{N|Ǽ\G#C9g$^\}p?556]/RP.90 k,U8/u776s ʪ_01چ|\N 0VV*3H鴃J7iI!wG_^ypl}r*jɤSR 5QN@ iZ#1ٰy;_\3\BQQ x:WJv츟ٯ$"@6 S#qe딇(/P( Dy~TOϻ<4:-+F`0||;Xl-"uw$Цi󼕝mKʩorz"mϺ$F:~E'ҐvD\y?Rr8_He@ e~O,T.(ފR*cY^m|cVR[8 JҡSm!ΆԨb)RHG{?MpqrmN>߶Y)\p,d#xۆWY*,l6]v0h15M˙MS8+EdI='LBJIH7_9{Caз*Lq,dt >+~ّeʏ?xԕ4bBAŚjﵫ!'\Ը$WNvKO}ӽmSşذqsOy?\[,d@'73'j%kOe`1.g2"e =YIzS2|zŐƄa\U,dP;jhhhaxǶ?КZ՚.q SE+XrbOu%\GتX(H,N^~]JyEZQKceTQ]VGYqnah;y$cQahT&QPZ*iZ8UQQM.qo/T\7X"u?Mttl2Xq(IoW{R^ ux*SYJ! 4S.Jy~ BROS[V|žKNɛP(L6V^|cR7i7nZW1Fd@ Ara{詑|(T*dN]Ko?s=@ |_EvF]׍kR)eBJc" MUUbY6`~V޴dJKß&~'d3i WWWWWW
Current Directory: /usr/share/ruby
Viewing File: /usr/share/ruby/resolv.rb
# frozen_string_literal: true require 'socket' require 'timeout' require 'io/wait' begin require 'securerandom' rescue LoadError end # Resolv is a thread-aware DNS resolver library written in Ruby. Resolv can # handle multiple DNS requests concurrently without blocking the entire Ruby # interpreter. # # See also resolv-replace.rb to replace the libc resolver with Resolv. # # Resolv can look up various DNS resources using the DNS module directly. # # Examples: # # p Resolv.getaddress "www.ruby-lang.org" # p Resolv.getname "210.251.121.214" # # Resolv::DNS.open do |dns| # ress = dns.getresources "www.ruby-lang.org", Resolv::DNS::Resource::IN::A # p ress.map(&:address) # ress = dns.getresources "ruby-lang.org", Resolv::DNS::Resource::IN::MX # p ress.map { |r| [r.exchange.to_s, r.preference] } # end # # # == Bugs # # * NIS is not supported. # * /etc/nsswitch.conf is not supported. class Resolv ## # Looks up the first IP address for +name+. def self.getaddress(name) DefaultResolver.getaddress(name) end ## # Looks up all IP address for +name+. def self.getaddresses(name) DefaultResolver.getaddresses(name) end ## # Iterates over all IP addresses for +name+. def self.each_address(name, &block) DefaultResolver.each_address(name, &block) end ## # Looks up the hostname of +address+. def self.getname(address) DefaultResolver.getname(address) end ## # Looks up all hostnames for +address+. def self.getnames(address) DefaultResolver.getnames(address) end ## # Iterates over all hostnames for +address+. def self.each_name(address, &proc) DefaultResolver.each_name(address, &proc) end ## # Creates a new Resolv using +resolvers+. def initialize(resolvers=[Hosts.new, DNS.new]) @resolvers = resolvers end ## # Looks up the first IP address for +name+. def getaddress(name) each_address(name) {|address| return address} raise ResolvError.new("no address for #{name}") end ## # Looks up all IP address for +name+. def getaddresses(name) ret = [] each_address(name) {|address| ret << address} return ret end ## # Iterates over all IP addresses for +name+. def each_address(name) if AddressRegex =~ name yield name return end yielded = false @resolvers.each {|r| r.each_address(name) {|address| yield address.to_s yielded = true } return if yielded } end ## # Looks up the hostname of +address+. def getname(address) each_name(address) {|name| return name} raise ResolvError.new("no name for #{address}") end ## # Looks up all hostnames for +address+. def getnames(address) ret = [] each_name(address) {|name| ret << name} return ret end ## # Iterates over all hostnames for +address+. def each_name(address) yielded = false @resolvers.each {|r| r.each_name(address) {|name| yield name.to_s yielded = true } return if yielded } end ## # Indicates a failure to resolve a name or address. class ResolvError < StandardError; end ## # Indicates a timeout resolving a name or address. class ResolvTimeout < Timeout::Error; end ## # Resolv::Hosts is a hostname resolver that uses the system hosts file. class Hosts begin raise LoadError unless /mswin|mingw|cygwin/ =~ RUBY_PLATFORM require 'win32/resolv' DefaultFileName = Win32::Resolv.get_hosts_path rescue LoadError DefaultFileName = '/etc/hosts' end ## # Creates a new Resolv::Hosts, using +filename+ for its data source. def initialize(filename = DefaultFileName) @filename = filename @mutex = Thread::Mutex.new @initialized = nil end def lazy_initialize # :nodoc: @mutex.synchronize { unless @initialized @name2addr = {} @addr2name = {} File.open(@filename, 'rb') {|f| f.each {|line| line.sub!(/#.*/, '') addr, hostname, *aliases = line.split(/\s+/) next unless addr addr.untaint hostname.untaint @addr2name[addr] = [] unless @addr2name.include? addr @addr2name[addr] << hostname @addr2name[addr] += aliases @name2addr[hostname] = [] unless @name2addr.include? hostname @name2addr[hostname] << addr aliases.each {|n| n.untaint @name2addr[n] = [] unless @name2addr.include? n @name2addr[n] << addr } } } @name2addr.each {|name, arr| arr.reverse!} @initialized = true end } self end ## # Gets the IP address of +name+ from the hosts file. def getaddress(name) each_address(name) {|address| return address} raise ResolvError.new("#{@filename} has no name: #{name}") end ## # Gets all IP addresses for +name+ from the hosts file. def getaddresses(name) ret = [] each_address(name) {|address| ret << address} return ret end ## # Iterates over all IP addresses for +name+ retrieved from the hosts file. def each_address(name, &proc) lazy_initialize if @name2addr.include?(name) @name2addr[name].each(&proc) end end ## # Gets the hostname of +address+ from the hosts file. def getname(address) each_name(address) {|name| return name} raise ResolvError.new("#{@filename} has no address: #{address}") end ## # Gets all hostnames for +address+ from the hosts file. def getnames(address) ret = [] each_name(address) {|name| ret << name} return ret end ## # Iterates over all hostnames for +address+ retrieved from the hosts file. def each_name(address, &proc) lazy_initialize @addr2name[address]&.each(&proc) end end ## # Resolv::DNS is a DNS stub resolver. # # Information taken from the following places: # # * STD0013 # * RFC 1035 # * ftp://ftp.isi.edu/in-notes/iana/assignments/dns-parameters # * etc. class DNS ## # Default DNS Port Port = 53 ## # Default DNS UDP packet size UDPSize = 512 ## # Creates a new DNS resolver. See Resolv::DNS.new for argument details. # # Yields the created DNS resolver to the block, if given, otherwise # returns it. def self.open(*args) dns = new(*args) return dns unless block_given? begin yield dns ensure dns.close end end ## # Creates a new DNS resolver. # # +config_info+ can be: # # nil:: Uses /etc/resolv.conf. # String:: Path to a file using /etc/resolv.conf's format. # Hash:: Must contain :nameserver, :search and :ndots keys. # :nameserver_port can be used to specify port number of nameserver address. # # The value of :nameserver should be an address string or # an array of address strings. # - :nameserver => '8.8.8.8' # - :nameserver => ['8.8.8.8', '8.8.4.4'] # # The value of :nameserver_port should be an array of # pair of nameserver address and port number. # - :nameserver_port => [['8.8.8.8', 53], ['8.8.4.4', 53]] # # Example: # # Resolv::DNS.new(:nameserver => ['210.251.121.21'], # :search => ['ruby-lang.org'], # :ndots => 1) def initialize(config_info=nil) @mutex = Thread::Mutex.new @config = Config.new(config_info) @initialized = nil end # Sets the resolver timeouts. This may be a single positive number # or an array of positive numbers representing timeouts in seconds. # If an array is specified, a DNS request will retry and wait for # each successive interval in the array until a successful response # is received. Specifying +nil+ reverts to the default timeouts: # [ 5, second = 5 * 2 / nameserver_count, 2 * second, 4 * second ] # # Example: # # dns.timeouts = 3 # def timeouts=(values) @config.timeouts = values end def lazy_initialize # :nodoc: @mutex.synchronize { unless @initialized @config.lazy_initialize @initialized = true end } self end ## # Closes the DNS resolver. def close @mutex.synchronize { if @initialized @initialized = false end } end ## # Gets the IP address of +name+ from the DNS resolver. # # +name+ can be a Resolv::DNS::Name or a String. Retrieved address will # be a Resolv::IPv4 or Resolv::IPv6 def getaddress(name) each_address(name) {|address| return address} raise ResolvError.new("DNS result has no information for #{name}") end ## # Gets all IP addresses for +name+ from the DNS resolver. # # +name+ can be a Resolv::DNS::Name or a String. Retrieved addresses will # be a Resolv::IPv4 or Resolv::IPv6 def getaddresses(name) ret = [] each_address(name) {|address| ret << address} return ret end ## # Iterates over all IP addresses for +name+ retrieved from the DNS # resolver. # # +name+ can be a Resolv::DNS::Name or a String. Retrieved addresses will # be a Resolv::IPv4 or Resolv::IPv6 def each_address(name) each_resource(name, Resource::IN::A) {|resource| yield resource.address} if use_ipv6? each_resource(name, Resource::IN::AAAA) {|resource| yield resource.address} end end def use_ipv6? # :nodoc: begin list = Socket.ip_address_list rescue NotImplementedError return true end list.any? {|a| a.ipv6? && !a.ipv6_loopback? && !a.ipv6_linklocal? } end private :use_ipv6? ## # Gets the hostname for +address+ from the DNS resolver. # # +address+ must be a Resolv::IPv4, Resolv::IPv6 or a String. Retrieved # name will be a Resolv::DNS::Name. def getname(address) each_name(address) {|name| return name} raise ResolvError.new("DNS result has no information for #{address}") end ## # Gets all hostnames for +address+ from the DNS resolver. # # +address+ must be a Resolv::IPv4, Resolv::IPv6 or a String. Retrieved # names will be Resolv::DNS::Name instances. def getnames(address) ret = [] each_name(address) {|name| ret << name} return ret end ## # Iterates over all hostnames for +address+ retrieved from the DNS # resolver. # # +address+ must be a Resolv::IPv4, Resolv::IPv6 or a String. Retrieved # names will be Resolv::DNS::Name instances. def each_name(address) case address when Name ptr = address when IPv4::Regex ptr = IPv4.create(address).to_name when IPv6::Regex ptr = IPv6.create(address).to_name else raise ResolvError.new("cannot interpret as address: #{address}") end each_resource(ptr, Resource::IN::PTR) {|resource| yield resource.name} end ## # Look up the +typeclass+ DNS resource of +name+. # # +name+ must be a Resolv::DNS::Name or a String. # # +typeclass+ should be one of the following: # # * Resolv::DNS::Resource::IN::A # * Resolv::DNS::Resource::IN::AAAA # * Resolv::DNS::Resource::IN::ANY # * Resolv::DNS::Resource::IN::CNAME # * Resolv::DNS::Resource::IN::HINFO # * Resolv::DNS::Resource::IN::MINFO # * Resolv::DNS::Resource::IN::MX # * Resolv::DNS::Resource::IN::NS # * Resolv::DNS::Resource::IN::PTR # * Resolv::DNS::Resource::IN::SOA # * Resolv::DNS::Resource::IN::TXT # * Resolv::DNS::Resource::IN::WKS # # Returned resource is represented as a Resolv::DNS::Resource instance, # i.e. Resolv::DNS::Resource::IN::A. def getresource(name, typeclass) each_resource(name, typeclass) {|resource| return resource} raise ResolvError.new("DNS result has no information for #{name}") end ## # Looks up all +typeclass+ DNS resources for +name+. See #getresource for # argument details. def getresources(name, typeclass) ret = [] each_resource(name, typeclass) {|resource| ret << resource} return ret end ## # Iterates over all +typeclass+ DNS resources for +name+. See # #getresource for argument details. def each_resource(name, typeclass, &proc) fetch_resource(name, typeclass) {|reply, reply_name| extract_resources(reply, reply_name, typeclass, &proc) } end def fetch_resource(name, typeclass) lazy_initialize requester = make_udp_requester senders = {} begin @config.resolv(name) {|candidate, tout, nameserver, port| msg = Message.new msg.rd = 1 msg.add_question(candidate, typeclass) unless sender = senders[[candidate, nameserver, port]] sender = requester.sender(msg, candidate, nameserver, port) next if !sender senders[[candidate, nameserver, port]] = sender end reply, reply_name = requester.request(sender, tout) case reply.rcode when RCode::NoError if reply.tc == 1 and not Requester::TCP === requester requester.close # Retry via TCP: requester = make_tcp_requester(nameserver, port) senders = {} # This will use TCP for all remaining candidates (assuming the # current candidate does not already respond successfully via # TCP). This makes sense because we already know the full # response will not fit in an untruncated UDP packet. redo else yield(reply, reply_name) end return when RCode::NXDomain raise Config::NXDomain.new(reply_name.to_s) else raise Config::OtherResolvError.new(reply_name.to_s) end } ensure requester.close end end def make_udp_requester # :nodoc: nameserver_port = @config.nameserver_port if nameserver_port.length == 1 Requester::ConnectedUDP.new(*nameserver_port[0]) else Requester::UnconnectedUDP.new(*nameserver_port) end end def make_tcp_requester(host, port) # :nodoc: return Requester::TCP.new(host, port) end def extract_resources(msg, name, typeclass) # :nodoc: if typeclass < Resource::ANY n0 = Name.create(name) msg.each_resource {|n, ttl, data| yield data if n0 == n } end yielded = false n0 = Name.create(name) msg.each_resource {|n, ttl, data| if n0 == n case data when typeclass yield data yielded = true when Resource::CNAME n0 = data.name end end } return if yielded msg.each_resource {|n, ttl, data| if n0 == n case data when typeclass yield data end end } end if defined? SecureRandom def self.random(arg) # :nodoc: begin SecureRandom.random_number(arg) rescue NotImplementedError rand(arg) end end else def self.random(arg) # :nodoc: rand(arg) end end def self.rangerand(range) # :nodoc: base = range.begin len = range.end - range.begin if !range.exclude_end? len += 1 end base + random(len) end RequestID = {} # :nodoc: RequestIDMutex = Thread::Mutex.new # :nodoc: def self.allocate_request_id(host, port) # :nodoc: id = nil RequestIDMutex.synchronize { h = (RequestID[[host, port]] ||= {}) begin id = rangerand(0x0000..0xffff) end while h[id] h[id] = true } id end def self.free_request_id(host, port, id) # :nodoc: RequestIDMutex.synchronize { key = [host, port] if h = RequestID[key] h.delete id if h.empty? RequestID.delete key end end } end def self.bind_random_port(udpsock, bind_host="0.0.0.0") # :nodoc: begin port = rangerand(1024..65535) udpsock.bind(bind_host, port) rescue Errno::EADDRINUSE, # POSIX Errno::EACCES, # SunOS: See PRIV_SYS_NFS in privileges(5) Errno::EPERM # FreeBSD: security.mac.portacl.port_high is configurable. See mac_portacl(4). retry end end class Requester # :nodoc: def initialize @senders = {} @socks = nil end def request(sender, tout) start = Process.clock_gettime(Process::CLOCK_MONOTONIC) timelimit = start + tout begin sender.send rescue Errno::EHOSTUNREACH, # multi-homed IPv6 may generate this Errno::ENETUNREACH raise ResolvTimeout end while true before_select = Process.clock_gettime(Process::CLOCK_MONOTONIC) timeout = timelimit - before_select if timeout <= 0 raise ResolvTimeout end if @socks.size == 1 select_result = @socks[0].wait_readable(timeout) ? [ @socks ] : nil else select_result = IO.select(@socks, nil, nil, timeout) end if !select_result after_select = Process.clock_gettime(Process::CLOCK_MONOTONIC) next if after_select < timelimit raise ResolvTimeout end begin reply, from = recv_reply(select_result[0]) rescue Errno::ECONNREFUSED, # GNU/Linux, FreeBSD Errno::ECONNRESET # Windows # No name server running on the server? # Don't wait anymore. raise ResolvTimeout end begin msg = Message.decode(reply) rescue DecodeError next # broken DNS message ignored end if s = sender_for(from, msg) break else # unexpected DNS message ignored end end return msg, s.data end def sender_for(addr, msg) @senders[[addr,msg.id]] end def close socks = @socks @socks = nil socks&.each(&:close) end class Sender # :nodoc: def initialize(msg, data, sock) @msg = msg @data = data @sock = sock end end class UnconnectedUDP < Requester # :nodoc: def initialize(*nameserver_port) super() @nameserver_port = nameserver_port @initialized = false @mutex = Thread::Mutex.new end def lazy_initialize @mutex.synchronize { next if @initialized @initialized = true @socks_hash = {} @socks = [] @nameserver_port.each {|host, port| if host.index(':') bind_host = "::" af = Socket::AF_INET6 else bind_host = "0.0.0.0" af = Socket::AF_INET end next if @socks_hash[bind_host] begin sock = UDPSocket.new(af) rescue Errno::EAFNOSUPPORT next # The kernel doesn't support the address family. end @socks << sock @socks_hash[bind_host] = sock sock.do_not_reverse_lookup = true DNS.bind_random_port(sock, bind_host) } } self end def recv_reply(readable_socks) lazy_initialize reply, from = readable_socks[0].recvfrom(UDPSize) return reply, [from[3],from[1]] end def sender(msg, data, host, port=Port) host = Addrinfo.ip(host).ip_address lazy_initialize sock = @socks_hash[host.index(':') ? "::" : "0.0.0.0"] return nil if !sock service = [host, port] id = DNS.allocate_request_id(host, port) request = msg.encode request[0,2] = [id].pack('n') return @senders[[service, id]] = Sender.new(request, data, sock, host, port) end def close @mutex.synchronize { if @initialized super @senders.each_key {|service, id| DNS.free_request_id(service[0], service[1], id) } @initialized = false end } end class Sender < Requester::Sender # :nodoc: def initialize(msg, data, sock, host, port) super(msg, data, sock) @host = host @port = port end attr_reader :data def send raise "@sock is nil." if @sock.nil? @sock.send(@msg, 0, @host, @port) end end end class ConnectedUDP < Requester # :nodoc: def initialize(host, port=Port) super() @host = host @port = port @mutex = Thread::Mutex.new @initialized = false end def lazy_initialize @mutex.synchronize { next if @initialized @initialized = true is_ipv6 = @host.index(':') sock = UDPSocket.new(is_ipv6 ? Socket::AF_INET6 : Socket::AF_INET) @socks = [sock] sock.do_not_reverse_lookup = true DNS.bind_random_port(sock, is_ipv6 ? "::" : "0.0.0.0") sock.connect(@host, @port) } self end def recv_reply(readable_socks) lazy_initialize reply = readable_socks[0].recv(UDPSize) return reply, nil end def sender(msg, data, host=@host, port=@port) lazy_initialize unless host == @host && port == @port raise RequestError.new("host/port don't match: #{host}:#{port}") end id = DNS.allocate_request_id(@host, @port) request = msg.encode request[0,2] = [id].pack('n') return @senders[[nil,id]] = Sender.new(request, data, @socks[0]) end def close @mutex.synchronize do if @initialized super @senders.each_key {|from, id| DNS.free_request_id(@host, @port, id) } @initialized = false end end end class Sender < Requester::Sender # :nodoc: def send raise "@sock is nil." if @sock.nil? @sock.send(@msg, 0) end attr_reader :data end end class MDNSOneShot < UnconnectedUDP # :nodoc: def sender(msg, data, host, port=Port) lazy_initialize id = DNS.allocate_request_id(host, port) request = msg.encode request[0,2] = [id].pack('n') sock = @socks_hash[host.index(':') ? "::" : "0.0.0.0"] return @senders[id] = UnconnectedUDP::Sender.new(request, data, sock, host, port) end def sender_for(addr, msg) lazy_initialize @senders[msg.id] end end class TCP < Requester # :nodoc: def initialize(host, port=Port) super() @host = host @port = port sock = TCPSocket.new(@host, @port) @socks = [sock] @senders = {} end def recv_reply(readable_socks) len = readable_socks[0].read(2).unpack('n')[0] reply = @socks[0].read(len) return reply, nil end def sender(msg, data, host=@host, port=@port) unless host == @host && port == @port raise RequestError.new("host/port don't match: #{host}:#{port}") end id = DNS.allocate_request_id(@host, @port) request = msg.encode request[0,2] = [request.length, id].pack('nn') return @senders[[nil,id]] = Sender.new(request, data, @socks[0]) end class Sender < Requester::Sender # :nodoc: def send @sock.print(@msg) @sock.flush end attr_reader :data end def close super @senders.each_key {|from,id| DNS.free_request_id(@host, @port, id) } end end ## # Indicates a problem with the DNS request. class RequestError < StandardError end end class Config # :nodoc: def initialize(config_info=nil) @mutex = Thread::Mutex.new @config_info = config_info @initialized = nil @timeouts = nil end def timeouts=(values) if values values = Array(values) values.each do |t| Numeric === t or raise ArgumentError, "#{t.inspect} is not numeric" t > 0.0 or raise ArgumentError, "timeout=#{t} must be positive" end @timeouts = values else @timeouts = nil end end def Config.parse_resolv_conf(filename) nameserver = [] search = nil ndots = 1 File.open(filename, 'rb') {|f| f.each {|line| line.sub!(/[#;].*/, '') keyword, *args = line.split(/\s+/) args.each(&:untaint) next unless keyword case keyword when 'nameserver' nameserver += args when 'domain' next if args.empty? search = [args[0]] when 'search' next if args.empty? search = args when 'options' args.each {|arg| case arg when /\Andots:(\d+)\z/ ndots = $1.to_i end } end } } return { :nameserver => nameserver, :search => search, :ndots => ndots } end def Config.default_config_hash(filename="/etc/resolv.conf") if File.exist? filename config_hash = Config.parse_resolv_conf(filename) else if /mswin|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM require 'win32/resolv' search, nameserver = Win32::Resolv.get_resolv_info config_hash = {} config_hash[:nameserver] = nameserver if nameserver config_hash[:search] = [search].flatten if search end end config_hash || {} end def lazy_initialize @mutex.synchronize { unless @initialized @nameserver_port = [] @search = nil @ndots = 1 case @config_info when nil config_hash = Config.default_config_hash when String config_hash = Config.parse_resolv_conf(@config_info) when Hash config_hash = @config_info.dup if String === config_hash[:nameserver] config_hash[:nameserver] = [config_hash[:nameserver]] end if String === config_hash[:search] config_hash[:search] = [config_hash[:search]] end else raise ArgumentError.new("invalid resolv configuration: #{@config_info.inspect}") end if config_hash.include? :nameserver @nameserver_port = config_hash[:nameserver].map {|ns| [ns, Port] } end if config_hash.include? :nameserver_port @nameserver_port = config_hash[:nameserver_port].map {|ns, port| [ns, (port || Port)] } end @search = config_hash[:search] if config_hash.include? :search @ndots = config_hash[:ndots] if config_hash.include? :ndots if @nameserver_port.empty? @nameserver_port << ['0.0.0.0', Port] end if @search @search = @search.map {|arg| Label.split(arg) } else hostname = Socket.gethostname if /\./ =~ hostname @search = [Label.split($')] else @search = [[]] end end if !@nameserver_port.kind_of?(Array) || @nameserver_port.any? {|ns_port| !(Array === ns_port) || ns_port.length != 2 !(String === ns_port[0]) || !(Integer === ns_port[1]) } raise ArgumentError.new("invalid nameserver config: #{@nameserver_port.inspect}") end if !@search.kind_of?(Array) || !@search.all? {|ls| ls.all? {|l| Label::Str === l } } raise ArgumentError.new("invalid search config: #{@search.inspect}") end if !@ndots.kind_of?(Integer) raise ArgumentError.new("invalid ndots config: #{@ndots.inspect}") end @initialized = true end } self end def single? lazy_initialize if @nameserver_port.length == 1 return @nameserver_port[0] else return nil end end def nameserver_port @nameserver_port end def generate_candidates(name) candidates = nil name = Name.create(name) if name.absolute? candidates = [name] else if @ndots <= name.length - 1 candidates = [Name.new(name.to_a)] else candidates = [] end candidates.concat(@search.map {|domain| Name.new(name.to_a + domain)}) fname = Name.create("#{name}.") if !candidates.include?(fname) candidates << fname end end return candidates end InitialTimeout = 5 def generate_timeouts ts = [InitialTimeout] ts << ts[-1] * 2 / @nameserver_port.length ts << ts[-1] * 2 ts << ts[-1] * 2 return ts end def resolv(name) candidates = generate_candidates(name) timeouts = @timeouts || generate_timeouts begin candidates.each {|candidate| begin timeouts.each {|tout| @nameserver_port.each {|nameserver, port| begin yield candidate, tout, nameserver, port rescue ResolvTimeout end } } raise ResolvError.new("DNS resolv timeout: #{name}") rescue NXDomain end } rescue ResolvError end end ## # Indicates no such domain was found. class NXDomain < ResolvError end ## # Indicates some other unhandled resolver error was encountered. class OtherResolvError < ResolvError end end module OpCode # :nodoc: Query = 0 IQuery = 1 Status = 2 Notify = 4 Update = 5 end module RCode # :nodoc: NoError = 0 FormErr = 1 ServFail = 2 NXDomain = 3 NotImp = 4 Refused = 5 YXDomain = 6 YXRRSet = 7 NXRRSet = 8 NotAuth = 9 NotZone = 10 BADVERS = 16 BADSIG = 16 BADKEY = 17 BADTIME = 18 BADMODE = 19 BADNAME = 20 BADALG = 21 end ## # Indicates that the DNS response was unable to be decoded. class DecodeError < StandardError end ## # Indicates that the DNS request was unable to be encoded. class EncodeError < StandardError end module Label # :nodoc: def self.split(arg) labels = [] arg.scan(/[^\.]+/) {labels << Str.new($&)} return labels end class Str # :nodoc: def initialize(string) @string = string # case insensivity of DNS labels doesn't apply non-ASCII characters. [RFC 4343] # This assumes @string is given in ASCII compatible encoding. @downcase = string.b.downcase end attr_reader :string, :downcase def to_s return @string end def inspect return "#<#{self.class} #{self}>" end def ==(other) return self.class == other.class && @downcase == other.downcase end def eql?(other) return self == other end def hash return @downcase.hash end end end ## # A representation of a DNS name. class Name ## # Creates a new DNS name from +arg+. +arg+ can be: # # Name:: returns +arg+. # String:: Creates a new Name. def self.create(arg) case arg when Name return arg when String return Name.new(Label.split(arg), /\.\z/ =~ arg ? true : false) else raise ArgumentError.new("cannot interpret as DNS name: #{arg.inspect}") end end def initialize(labels, absolute=true) # :nodoc: labels = labels.map {|label| case label when String then Label::Str.new(label) when Label::Str then label else raise ArgumentError, "unexpected label: #{label.inspect}" end } @labels = labels @absolute = absolute end def inspect # :nodoc: "#<#{self.class}: #{self}#{@absolute ? '.' : ''}>" end ## # True if this name is absolute. def absolute? return @absolute end def ==(other) # :nodoc: return false unless Name === other return false unless @absolute == other.absolute? return @labels == other.to_a end alias eql? == # :nodoc: ## # Returns true if +other+ is a subdomain. # # Example: # # domain = Resolv::DNS::Name.create("y.z") # p Resolv::DNS::Name.create("w.x.y.z").subdomain_of?(domain) #=> true # p Resolv::DNS::Name.create("x.y.z").subdomain_of?(domain) #=> true # p Resolv::DNS::Name.create("y.z").subdomain_of?(domain) #=> false # p Resolv::DNS::Name.create("z").subdomain_of?(domain) #=> false # p Resolv::DNS::Name.create("x.y.z.").subdomain_of?(domain) #=> false # p Resolv::DNS::Name.create("w.z").subdomain_of?(domain) #=> false # def subdomain_of?(other) raise ArgumentError, "not a domain name: #{other.inspect}" unless Name === other return false if @absolute != other.absolute? other_len = other.length return false if @labels.length <= other_len return @labels[-other_len, other_len] == other.to_a end def hash # :nodoc: return @labels.hash ^ @absolute.hash end def to_a # :nodoc: return @labels end def length # :nodoc: return @labels.length end def [](i) # :nodoc: return @labels[i] end ## # returns the domain name as a string. # # The domain name doesn't have a trailing dot even if the name object is # absolute. # # Example: # # p Resolv::DNS::Name.create("x.y.z.").to_s #=> "x.y.z" # p Resolv::DNS::Name.create("x.y.z").to_s #=> "x.y.z" def to_s return @labels.join('.') end end class Message # :nodoc: @@identifier = -1 def initialize(id = (@@identifier += 1) & 0xffff) @id = id @qr = 0 @opcode = 0 @aa = 0 @tc = 0 @rd = 0 # recursion desired @ra = 0 # recursion available @rcode = 0 @question = [] @answer = [] @authority = [] @additional = [] end attr_accessor :id, :qr, :opcode, :aa, :tc, :rd, :ra, :rcode attr_reader :question, :answer, :authority, :additional def ==(other) return @id == other.id && @qr == other.qr && @opcode == other.opcode && @aa == other.aa && @tc == other.tc && @rd == other.rd && @ra == other.ra && @rcode == other.rcode && @question == other.question && @answer == other.answer && @authority == other.authority && @additional == other.additional end def add_question(name, typeclass) @question << [Name.create(name), typeclass] end def each_question @question.each {|name, typeclass| yield name, typeclass } end def add_answer(name, ttl, data) @answer << [Name.create(name), ttl, data] end def each_answer @answer.each {|name, ttl, data| yield name, ttl, data } end def add_authority(name, ttl, data) @authority << [Name.create(name), ttl, data] end def each_authority @authority.each {|name, ttl, data| yield name, ttl, data } end def add_additional(name, ttl, data) @additional << [Name.create(name), ttl, data] end def each_additional @additional.each {|name, ttl, data| yield name, ttl, data } end def each_resource each_answer {|name, ttl, data| yield name, ttl, data} each_authority {|name, ttl, data| yield name, ttl, data} each_additional {|name, ttl, data| yield name, ttl, data} end def encode return MessageEncoder.new {|msg| msg.put_pack('nnnnnn', @id, (@qr & 1) << 15 | (@opcode & 15) << 11 | (@aa & 1) << 10 | (@tc & 1) << 9 | (@rd & 1) << 8 | (@ra & 1) << 7 | (@rcode & 15), @question.length, @answer.length, @authority.length, @additional.length) @question.each {|q| name, typeclass = q msg.put_name(name) msg.put_pack('nn', typeclass::TypeValue, typeclass::ClassValue) } [@answer, @authority, @additional].each {|rr| rr.each {|r| name, ttl, data = r msg.put_name(name) msg.put_pack('nnN', data.class::TypeValue, data.class::ClassValue, ttl) msg.put_length16 {data.encode_rdata(msg)} } } }.to_s end class MessageEncoder # :nodoc: def initialize @data = ''.dup @names = {} yield self end def to_s return @data end def put_bytes(d) @data << d end def put_pack(template, *d) @data << d.pack(template) end def put_length16 length_index = @data.length @data << "\0\0" data_start = @data.length yield data_end = @data.length @data[length_index, 2] = [data_end - data_start].pack("n") end def put_string(d) self.put_pack("C", d.length) @data << d end def put_string_list(ds) ds.each {|d| self.put_string(d) } end def put_name(d) put_labels(d.to_a) end def put_labels(d) d.each_index {|i| domain = d[i..-1] if idx = @names[domain] self.put_pack("n", 0xc000 | idx) return else if @data.length < 0x4000 @names[domain] = @data.length end self.put_label(d[i]) end } @data << "\0" end def put_label(d) self.put_string(d.to_s) end end def Message.decode(m) o = Message.new(0) MessageDecoder.new(m) {|msg| id, flag, qdcount, ancount, nscount, arcount = msg.get_unpack('nnnnnn') o.id = id o.qr = (flag >> 15) & 1 o.opcode = (flag >> 11) & 15 o.aa = (flag >> 10) & 1 o.tc = (flag >> 9) & 1 o.rd = (flag >> 8) & 1 o.ra = (flag >> 7) & 1 o.rcode = flag & 15 (1..qdcount).each { name, typeclass = msg.get_question o.add_question(name, typeclass) } (1..ancount).each { name, ttl, data = msg.get_rr o.add_answer(name, ttl, data) } (1..nscount).each { name, ttl, data = msg.get_rr o.add_authority(name, ttl, data) } (1..arcount).each { name, ttl, data = msg.get_rr o.add_additional(name, ttl, data) } } return o end class MessageDecoder # :nodoc: def initialize(data) @data = data @index = 0 @limit = data.bytesize yield self end def inspect "\#<#{self.class}: #{@data.byteslice(0, @index).inspect} #{@data.byteslice(@index..-1).inspect}>" end def get_length16 len, = self.get_unpack('n') save_limit = @limit @limit = @index + len d = yield(len) if @index < @limit raise DecodeError.new("junk exists") elsif @limit < @index raise DecodeError.new("limit exceeded") end @limit = save_limit return d end def get_bytes(len = @limit - @index) raise DecodeError.new("limit exceeded") if @limit < @index + len d = @data.byteslice(@index, len) @index += len return d end def get_unpack(template) len = 0 template.each_byte {|byte| byte = "%c" % byte case byte when ?c, ?C len += 1 when ?n len += 2 when ?N len += 4 else raise StandardError.new("unsupported template: '#{byte.chr}' in '#{template}'") end } raise DecodeError.new("limit exceeded") if @limit < @index + len arr = @data.unpack("@#{@index}#{template}") @index += len return arr end def get_string raise DecodeError.new("limit exceeded") if @limit <= @index len = @data.getbyte(@index) raise DecodeError.new("limit exceeded") if @limit < @index + 1 + len d = @data.byteslice(@index + 1, len) @index += 1 + len return d end def get_string_list strings = [] while @index < @limit strings << self.get_string end strings end def get_name return Name.new(self.get_labels) end def get_labels prev_index = @index save_index = nil d = [] while true raise DecodeError.new("limit exceeded") if @limit <= @index case @data.getbyte(@index) when 0 @index += 1 if save_index @index = save_index end return d when 192..255 idx = self.get_unpack('n')[0] & 0x3fff if prev_index <= idx raise DecodeError.new("non-backward name pointer") end prev_index = idx if !save_index save_index = @index end @index = idx else d << self.get_label end end end def get_label return Label::Str.new(self.get_string) end def get_question name = self.get_name type, klass = self.get_unpack("nn") return name, Resource.get_class(type, klass) end def get_rr name = self.get_name type, klass, ttl = self.get_unpack('nnN') typeclass = Resource.get_class(type, klass) res = self.get_length16 do begin typeclass.decode_rdata self rescue => e raise DecodeError, e.message, e.backtrace end end res.instance_variable_set :@ttl, ttl return name, ttl, res end end end ## # A DNS query abstract class. class Query def encode_rdata(msg) # :nodoc: raise EncodeError.new("#{self.class} is query.") end def self.decode_rdata(msg) # :nodoc: raise DecodeError.new("#{self.class} is query.") end end ## # A DNS resource abstract class. class Resource < Query ## # Remaining Time To Live for this Resource. attr_reader :ttl ClassHash = {} # :nodoc: def encode_rdata(msg) # :nodoc: raise NotImplementedError.new end def self.decode_rdata(msg) # :nodoc: raise NotImplementedError.new end def ==(other) # :nodoc: return false unless self.class == other.class s_ivars = self.instance_variables s_ivars.sort! s_ivars.delete :@ttl o_ivars = other.instance_variables o_ivars.sort! o_ivars.delete :@ttl return s_ivars == o_ivars && s_ivars.collect {|name| self.instance_variable_get name} == o_ivars.collect {|name| other.instance_variable_get name} end def eql?(other) # :nodoc: return self == other end def hash # :nodoc: h = 0 vars = self.instance_variables vars.delete :@ttl vars.each {|name| h ^= self.instance_variable_get(name).hash } return h end def self.get_class(type_value, class_value) # :nodoc: return ClassHash[[type_value, class_value]] || Generic.create(type_value, class_value) end ## # A generic resource abstract class. class Generic < Resource ## # Creates a new generic resource. def initialize(data) @data = data end ## # Data for this generic resource. attr_reader :data def encode_rdata(msg) # :nodoc: msg.put_bytes(data) end def self.decode_rdata(msg) # :nodoc: return self.new(msg.get_bytes) end def self.create(type_value, class_value) # :nodoc: c = Class.new(Generic) c.const_set(:TypeValue, type_value) c.const_set(:ClassValue, class_value) Generic.const_set("Type#{type_value}_Class#{class_value}", c) ClassHash[[type_value, class_value]] = c return c end end ## # Domain Name resource abstract class. class DomainName < Resource ## # Creates a new DomainName from +name+. def initialize(name) @name = name end ## # The name of this DomainName. attr_reader :name def encode_rdata(msg) # :nodoc: msg.put_name(@name) end def self.decode_rdata(msg) # :nodoc: return self.new(msg.get_name) end end # Standard (class generic) RRs ClassValue = nil # :nodoc: ## # An authoritative name server. class NS < DomainName TypeValue = 2 # :nodoc: end ## # The canonical name for an alias. class CNAME < DomainName TypeValue = 5 # :nodoc: end ## # Start Of Authority resource. class SOA < Resource TypeValue = 6 # :nodoc: ## # Creates a new SOA record. See the attr documentation for the # details of each argument. def initialize(mname, rname, serial, refresh, retry_, expire, minimum) @mname = mname @rname = rname @serial = serial @refresh = refresh @retry = retry_ @expire = expire @minimum = minimum end ## # Name of the host where the master zone file for this zone resides. attr_reader :mname ## # The person responsible for this domain name. attr_reader :rname ## # The version number of the zone file. attr_reader :serial ## # How often, in seconds, a secondary name server is to check for # updates from the primary name server. attr_reader :refresh ## # How often, in seconds, a secondary name server is to retry after a # failure to check for a refresh. attr_reader :retry ## # Time in seconds that a secondary name server is to use the data # before refreshing from the primary name server. attr_reader :expire ## # The minimum number of seconds to be used for TTL values in RRs. attr_reader :minimum def encode_rdata(msg) # :nodoc: msg.put_name(@mname) msg.put_name(@rname) msg.put_pack('NNNNN', @serial, @refresh, @retry, @expire, @minimum) end def self.decode_rdata(msg) # :nodoc: mname = msg.get_name rname = msg.get_name serial, refresh, retry_, expire, minimum = msg.get_unpack('NNNNN') return self.new( mname, rname, serial, refresh, retry_, expire, minimum) end end ## # A Pointer to another DNS name. class PTR < DomainName TypeValue = 12 # :nodoc: end ## # Host Information resource. class HINFO < Resource TypeValue = 13 # :nodoc: ## # Creates a new HINFO running +os+ on +cpu+. def initialize(cpu, os) @cpu = cpu @os = os end ## # CPU architecture for this resource. attr_reader :cpu ## # Operating system for this resource. attr_reader :os def encode_rdata(msg) # :nodoc: msg.put_string(@cpu) msg.put_string(@os) end def self.decode_rdata(msg) # :nodoc: cpu = msg.get_string os = msg.get_string return self.new(cpu, os) end end ## # Mailing list or mailbox information. class MINFO < Resource TypeValue = 14 # :nodoc: def initialize(rmailbx, emailbx) @rmailbx = rmailbx @emailbx = emailbx end ## # Domain name responsible for this mail list or mailbox. attr_reader :rmailbx ## # Mailbox to use for error messages related to the mail list or mailbox. attr_reader :emailbx def encode_rdata(msg) # :nodoc: msg.put_name(@rmailbx) msg.put_name(@emailbx) end def self.decode_rdata(msg) # :nodoc: rmailbx = msg.get_string emailbx = msg.get_string return self.new(rmailbx, emailbx) end end ## # Mail Exchanger resource. class MX < Resource TypeValue= 15 # :nodoc: ## # Creates a new MX record with +preference+, accepting mail at # +exchange+. def initialize(preference, exchange) @preference = preference @exchange = exchange end ## # The preference for this MX. attr_reader :preference ## # The host of this MX. attr_reader :exchange def encode_rdata(msg) # :nodoc: msg.put_pack('n', @preference) msg.put_name(@exchange) end def self.decode_rdata(msg) # :nodoc: preference, = msg.get_unpack('n') exchange = msg.get_name return self.new(preference, exchange) end end ## # Unstructured text resource. class TXT < Resource TypeValue = 16 # :nodoc: def initialize(first_string, *rest_strings) @strings = [first_string, *rest_strings] end ## # Returns an Array of Strings for this TXT record. attr_reader :strings ## # Returns the concatenated string from +strings+. def data @strings.join("") end def encode_rdata(msg) # :nodoc: msg.put_string_list(@strings) end def self.decode_rdata(msg) # :nodoc: strings = msg.get_string_list return self.new(*strings) end end ## # Location resource class LOC < Resource TypeValue = 29 # :nodoc: def initialize(version, ssize, hprecision, vprecision, latitude, longitude, altitude) @version = version @ssize = Resolv::LOC::Size.create(ssize) @hprecision = Resolv::LOC::Size.create(hprecision) @vprecision = Resolv::LOC::Size.create(vprecision) @latitude = Resolv::LOC::Coord.create(latitude) @longitude = Resolv::LOC::Coord.create(longitude) @altitude = Resolv::LOC::Alt.create(altitude) end ## # Returns the version value for this LOC record which should always be 00 attr_reader :version ## # The spherical size of this LOC # in meters using scientific notation as 2 integers of XeY attr_reader :ssize ## # The horizontal precision using ssize type values # in meters using scientific notation as 2 integers of XeY # for precision use value/2 e.g. 2m = +/-1m attr_reader :hprecision ## # The vertical precision using ssize type values # in meters using scientific notation as 2 integers of XeY # for precision use value/2 e.g. 2m = +/-1m attr_reader :vprecision ## # The latitude for this LOC where 2**31 is the equator # in thousandths of an arc second as an unsigned 32bit integer attr_reader :latitude ## # The longitude for this LOC where 2**31 is the prime meridian # in thousandths of an arc second as an unsigned 32bit integer attr_reader :longitude ## # The altitude of the LOC above a reference sphere whose surface sits 100km below the WGS84 spheroid # in centimeters as an unsigned 32bit integer attr_reader :altitude def encode_rdata(msg) # :nodoc: msg.put_bytes(@version) msg.put_bytes(@ssize.scalar) msg.put_bytes(@hprecision.scalar) msg.put_bytes(@vprecision.scalar) msg.put_bytes(@latitude.coordinates) msg.put_bytes(@longitude.coordinates) msg.put_bytes(@altitude.altitude) end def self.decode_rdata(msg) # :nodoc: version = msg.get_bytes(1) ssize = msg.get_bytes(1) hprecision = msg.get_bytes(1) vprecision = msg.get_bytes(1) latitude = msg.get_bytes(4) longitude = msg.get_bytes(4) altitude = msg.get_bytes(4) return self.new( version, Resolv::LOC::Size.new(ssize), Resolv::LOC::Size.new(hprecision), Resolv::LOC::Size.new(vprecision), Resolv::LOC::Coord.new(latitude,"lat"), Resolv::LOC::Coord.new(longitude,"lon"), Resolv::LOC::Alt.new(altitude) ) end end ## # A Query type requesting any RR. class ANY < Query TypeValue = 255 # :nodoc: end ClassInsensitiveTypes = [ # :nodoc: NS, CNAME, SOA, PTR, HINFO, MINFO, MX, TXT, LOC, ANY ] ## # module IN contains ARPA Internet specific RRs. module IN ClassValue = 1 # :nodoc: ClassInsensitiveTypes.each {|s| c = Class.new(s) c.const_set(:TypeValue, s::TypeValue) c.const_set(:ClassValue, ClassValue) ClassHash[[s::TypeValue, ClassValue]] = c self.const_set(s.name.sub(/.*::/, ''), c) } ## # IPv4 Address resource class A < Resource TypeValue = 1 ClassValue = IN::ClassValue ClassHash[[TypeValue, ClassValue]] = self # :nodoc: ## # Creates a new A for +address+. def initialize(address) @address = IPv4.create(address) end ## # The Resolv::IPv4 address for this A. attr_reader :address def encode_rdata(msg) # :nodoc: msg.put_bytes(@address.address) end def self.decode_rdata(msg) # :nodoc: return self.new(IPv4.new(msg.get_bytes(4))) end end ## # Well Known Service resource. class WKS < Resource TypeValue = 11 ClassValue = IN::ClassValue ClassHash[[TypeValue, ClassValue]] = self # :nodoc: def initialize(address, protocol, bitmap) @address = IPv4.create(address) @protocol = protocol @bitmap = bitmap end ## # The host these services run on. attr_reader :address ## # IP protocol number for these services. attr_reader :protocol ## # A bit map of enabled services on this host. # # If protocol is 6 (TCP) then the 26th bit corresponds to the SMTP # service (port 25). If this bit is set, then an SMTP server should # be listening on TCP port 25; if zero, SMTP service is not # supported. attr_reader :bitmap def encode_rdata(msg) # :nodoc: msg.put_bytes(@address.address) msg.put_pack("n", @protocol) msg.put_bytes(@bitmap) end def self.decode_rdata(msg) # :nodoc: address = IPv4.new(msg.get_bytes(4)) protocol, = msg.get_unpack("n") bitmap = msg.get_bytes return self.new(address, protocol, bitmap) end end ## # An IPv6 address record. class AAAA < Resource TypeValue = 28 ClassValue = IN::ClassValue ClassHash[[TypeValue, ClassValue]] = self # :nodoc: ## # Creates a new AAAA for +address+. def initialize(address) @address = IPv6.create(address) end ## # The Resolv::IPv6 address for this AAAA. attr_reader :address def encode_rdata(msg) # :nodoc: msg.put_bytes(@address.address) end def self.decode_rdata(msg) # :nodoc: return self.new(IPv6.new(msg.get_bytes(16))) end end ## # SRV resource record defined in RFC 2782 # # These records identify the hostname and port that a service is # available at. class SRV < Resource TypeValue = 33 ClassValue = IN::ClassValue ClassHash[[TypeValue, ClassValue]] = self # :nodoc: # Create a SRV resource record. # # See the documentation for #priority, #weight, #port and #target # for +priority+, +weight+, +port and +target+ respectively. def initialize(priority, weight, port, target) @priority = priority.to_int @weight = weight.to_int @port = port.to_int @target = Name.create(target) end # The priority of this target host. # # A client MUST attempt to contact the target host with the # lowest-numbered priority it can reach; target hosts with the same # priority SHOULD be tried in an order defined by the weight field. # The range is 0-65535. Note that it is not widely implemented and # should be set to zero. attr_reader :priority # A server selection mechanism. # # The weight field specifies a relative weight for entries with the # same priority. Larger weights SHOULD be given a proportionately # higher probability of being selected. The range of this number is # 0-65535. Domain administrators SHOULD use Weight 0 when there # isn't any server selection to do, to make the RR easier to read # for humans (less noisy). Note that it is not widely implemented # and should be set to zero. attr_reader :weight # The port on this target host of this service. # # The range is 0-65535. attr_reader :port # The domain name of the target host. # # A target of "." means that the service is decidedly not available # at this domain. attr_reader :target def encode_rdata(msg) # :nodoc: msg.put_pack("n", @priority) msg.put_pack("n", @weight) msg.put_pack("n", @port) msg.put_name(@target) end def self.decode_rdata(msg) # :nodoc: priority, = msg.get_unpack("n") weight, = msg.get_unpack("n") port, = msg.get_unpack("n") target = msg.get_name return self.new(priority, weight, port, target) end end end end end ## # A Resolv::DNS IPv4 address. class IPv4 ## # Regular expression IPv4 addresses must match. Regex256 = /0 |1(?:[0-9][0-9]?)? |2(?:[0-4][0-9]?|5[0-5]?|[6-9])? |[3-9][0-9]?/x Regex = /\A(#{Regex256})\.(#{Regex256})\.(#{Regex256})\.(#{Regex256})\z/ def self.create(arg) case arg when IPv4 return arg when Regex if (0..255) === (a = $1.to_i) && (0..255) === (b = $2.to_i) && (0..255) === (c = $3.to_i) && (0..255) === (d = $4.to_i) return self.new([a, b, c, d].pack("CCCC")) else raise ArgumentError.new("IPv4 address with invalid value: " + arg) end else raise ArgumentError.new("cannot interpret as IPv4 address: #{arg.inspect}") end end def initialize(address) # :nodoc: unless address.kind_of?(String) raise ArgumentError, 'IPv4 address must be a string' end unless address.length == 4 raise ArgumentError, "IPv4 address expects 4 bytes but #{address.length} bytes" end @address = address end ## # A String representation of this IPv4 address. ## # The raw IPv4 address as a String. attr_reader :address def to_s # :nodoc: return sprintf("%d.%d.%d.%d", *@address.unpack("CCCC")) end def inspect # :nodoc: return "#<#{self.class} #{self}>" end ## # Turns this IPv4 address into a Resolv::DNS::Name. def to_name return DNS::Name.create( '%d.%d.%d.%d.in-addr.arpa.' % @address.unpack('CCCC').reverse) end def ==(other) # :nodoc: return @address == other.address end def eql?(other) # :nodoc: return self == other end def hash # :nodoc: return @address.hash end end ## # A Resolv::DNS IPv6 address. class IPv6 ## # IPv6 address format a:b:c:d:e:f:g:h Regex_8Hex = /\A (?:[0-9A-Fa-f]{1,4}:){7} [0-9A-Fa-f]{1,4} \z/x ## # Compressed IPv6 address format a::b Regex_CompressedHex = /\A ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) :: ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) \z/x ## # IPv4 mapped IPv6 address format a:b:c:d:e:f:w.x.y.z Regex_6Hex4Dec = /\A ((?:[0-9A-Fa-f]{1,4}:){6,6}) (\d+)\.(\d+)\.(\d+)\.(\d+) \z/x ## # Compressed IPv4 mapped IPv6 address format a::b:w.x.y.z Regex_CompressedHex4Dec = /\A ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) :: ((?:[0-9A-Fa-f]{1,4}:)*) (\d+)\.(\d+)\.(\d+)\.(\d+) \z/x ## # A composite IPv6 address Regexp. Regex = / (?:#{Regex_8Hex}) | (?:#{Regex_CompressedHex}) | (?:#{Regex_6Hex4Dec}) | (?:#{Regex_CompressedHex4Dec})/x ## # Creates a new IPv6 address from +arg+ which may be: # # IPv6:: returns +arg+. # String:: +arg+ must match one of the IPv6::Regex* constants def self.create(arg) case arg when IPv6 return arg when String address = ''.b if Regex_8Hex =~ arg arg.scan(/[0-9A-Fa-f]+/) {|hex| address << [hex.hex].pack('n')} elsif Regex_CompressedHex =~ arg prefix = $1 suffix = $2 a1 = ''.b a2 = ''.b prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')} suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')} omitlen = 16 - a1.length - a2.length address << a1 << "\0" * omitlen << a2 elsif Regex_6Hex4Dec =~ arg prefix, a, b, c, d = $1, $2.to_i, $3.to_i, $4.to_i, $5.to_i if (0..255) === a && (0..255) === b && (0..255) === c && (0..255) === d prefix.scan(/[0-9A-Fa-f]+/) {|hex| address << [hex.hex].pack('n')} address << [a, b, c, d].pack('CCCC') else raise ArgumentError.new("not numeric IPv6 address: " + arg) end elsif Regex_CompressedHex4Dec =~ arg prefix, suffix, a, b, c, d = $1, $2, $3.to_i, $4.to_i, $5.to_i, $6.to_i if (0..255) === a && (0..255) === b && (0..255) === c && (0..255) === d a1 = ''.b a2 = ''.b prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')} suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')} omitlen = 12 - a1.length - a2.length address << a1 << "\0" * omitlen << a2 << [a, b, c, d].pack('CCCC') else raise ArgumentError.new("not numeric IPv6 address: " + arg) end else raise ArgumentError.new("not numeric IPv6 address: " + arg) end return IPv6.new(address) else raise ArgumentError.new("cannot interpret as IPv6 address: #{arg.inspect}") end end def initialize(address) # :nodoc: unless address.kind_of?(String) && address.length == 16 raise ArgumentError.new('IPv6 address must be 16 bytes') end @address = address end ## # The raw IPv6 address as a String. attr_reader :address def to_s # :nodoc: address = sprintf("%X:%X:%X:%X:%X:%X:%X:%X", *@address.unpack("nnnnnnnn")) unless address.sub!(/(^|:)0(:0)+(:|$)/, '::') address.sub!(/(^|:)0(:|$)/, '::') end return address end def inspect # :nodoc: return "#<#{self.class} #{self}>" end ## # Turns this IPv6 address into a Resolv::DNS::Name. #-- # ip6.arpa should be searched too. [RFC3152] def to_name return DNS::Name.new( @address.unpack("H32")[0].split(//).reverse + ['ip6', 'arpa']) end def ==(other) # :nodoc: return @address == other.address end def eql?(other) # :nodoc: return self == other end def hash # :nodoc: return @address.hash end end ## # Resolv::MDNS is a one-shot Multicast DNS (mDNS) resolver. It blindly # makes queries to the mDNS addresses without understanding anything about # multicast ports. # # Information taken form the following places: # # * RFC 6762 class MDNS < DNS ## # Default mDNS Port Port = 5353 ## # Default IPv4 mDNS address AddressV4 = '224.0.0.251' ## # Default IPv6 mDNS address AddressV6 = 'ff02::fb' ## # Default mDNS addresses Addresses = [ [AddressV4, Port], [AddressV6, Port], ] ## # Creates a new one-shot Multicast DNS (mDNS) resolver. # # +config_info+ can be: # # nil:: # Uses the default mDNS addresses # # Hash:: # Must contain :nameserver or :nameserver_port like # Resolv::DNS#initialize. def initialize(config_info=nil) if config_info then super({ nameserver_port: Addresses }.merge(config_info)) else super(nameserver_port: Addresses) end end ## # Iterates over all IP addresses for +name+ retrieved from the mDNS # resolver, provided name ends with "local". If the name does not end in # "local" no records will be returned. # # +name+ can be a Resolv::DNS::Name or a String. Retrieved addresses will # be a Resolv::IPv4 or Resolv::IPv6 def each_address(name) name = Resolv::DNS::Name.create(name) return unless name.to_a.last.to_s == 'local' super(name) end def make_udp_requester # :nodoc: nameserver_port = @config.nameserver_port Requester::MDNSOneShot.new(*nameserver_port) end end module LOC ## # A Resolv::LOC::Size class Size Regex = /^(\d+\.*\d*)[m]$/ ## # Creates a new LOC::Size from +arg+ which may be: # # LOC::Size:: returns +arg+. # String:: +arg+ must match the LOC::Size::Regex constant def self.create(arg) case arg when Size return arg when String scalar = '' if Regex =~ arg scalar = [(($1.to_f*(1e2)).to_i.to_s[0].to_i*(2**4)+(($1.to_f*(1e2)).to_i.to_s.length-1))].pack("C") else raise ArgumentError.new("not a properly formed Size string: " + arg) end return Size.new(scalar) else raise ArgumentError.new("cannot interpret as Size: #{arg.inspect}") end end def initialize(scalar) @scalar = scalar end ## # The raw size attr_reader :scalar def to_s # :nodoc: s = @scalar.unpack("H2").join.to_s return ((s[0].to_i)*(10**(s[1].to_i-2))).to_s << "m" end def inspect # :nodoc: return "#<#{self.class} #{self}>" end def ==(other) # :nodoc: return @scalar == other.scalar end def eql?(other) # :nodoc: return self == other end def hash # :nodoc: return @scalar.hash end end ## # A Resolv::LOC::Coord class Coord Regex = /^(\d+)\s(\d+)\s(\d+\.\d+)\s([NESW])$/ ## # Creates a new LOC::Coord from +arg+ which may be: # # LOC::Coord:: returns +arg+. # String:: +arg+ must match the LOC::Coord::Regex constant def self.create(arg) case arg when Coord return arg when String coordinates = '' if Regex =~ arg && $1.to_f < 180 m = $~ hemi = (m[4][/[NE]/]) || (m[4][/[SW]/]) ? 1 : -1 coordinates = [ ((m[1].to_i*(36e5)) + (m[2].to_i*(6e4)) + (m[3].to_f*(1e3))) * hemi+(2**31) ].pack("N") orientation = m[4][/[NS]/] ? 'lat' : 'lon' else raise ArgumentError.new("not a properly formed Coord string: " + arg) end return Coord.new(coordinates,orientation) else raise ArgumentError.new("cannot interpret as Coord: #{arg.inspect}") end end def initialize(coordinates,orientation) unless coordinates.kind_of?(String) raise ArgumentError.new("Coord must be a 32bit unsigned integer in hex format: #{coordinates.inspect}") end unless orientation.kind_of?(String) && orientation[/^lon$|^lat$/] raise ArgumentError.new('Coord expects orientation to be a String argument of "lat" or "lon"') end @coordinates = coordinates @orientation = orientation end ## # The raw coordinates attr_reader :coordinates ## The orientation of the hemisphere as 'lat' or 'lon' attr_reader :orientation def to_s # :nodoc: c = @coordinates.unpack("N").join.to_i val = (c - (2**31)).abs fracsecs = (val % 1e3).to_i.to_s val = val / 1e3 secs = (val % 60).to_i.to_s val = val / 60 mins = (val % 60).to_i.to_s degs = (val / 60).to_i.to_s posi = (c >= 2**31) case posi when true hemi = @orientation[/^lat$/] ? "N" : "E" else hemi = @orientation[/^lon$/] ? "W" : "S" end return degs << " " << mins << " " << secs << "." << fracsecs << " " << hemi end def inspect # :nodoc: return "#<#{self.class} #{self}>" end def ==(other) # :nodoc: return @coordinates == other.coordinates end def eql?(other) # :nodoc: return self == other end def hash # :nodoc: return @coordinates.hash end end ## # A Resolv::LOC::Alt class Alt Regex = /^([+-]*\d+\.*\d*)[m]$/ ## # Creates a new LOC::Alt from +arg+ which may be: # # LOC::Alt:: returns +arg+. # String:: +arg+ must match the LOC::Alt::Regex constant def self.create(arg) case arg when Alt return arg when String altitude = '' if Regex =~ arg altitude = [($1.to_f*(1e2))+(1e7)].pack("N") else raise ArgumentError.new("not a properly formed Alt string: " + arg) end return Alt.new(altitude) else raise ArgumentError.new("cannot interpret as Alt: #{arg.inspect}") end end def initialize(altitude) @altitude = altitude end ## # The raw altitude attr_reader :altitude def to_s # :nodoc: a = @altitude.unpack("N").join.to_i return ((a.to_f/1e2)-1e5).to_s + "m" end def inspect # :nodoc: return "#<#{self.class} #{self}>" end def ==(other) # :nodoc: return @altitude == other.altitude end def eql?(other) # :nodoc: return self == other end def hash # :nodoc: return @altitude.hash end end end ## # Default resolver to use for Resolv class methods. DefaultResolver = self.new ## # Replaces the resolvers in the default resolver with +new_resolvers+. This # allows resolvers to be changed for resolv-replace. def DefaultResolver.replace_resolvers new_resolvers @resolvers = new_resolvers end ## # Address Regexp to use for matching IP addresses. AddressRegex = /(?:#{IPv4::Regex})|(?:#{IPv6::Regex})/ end