From 8169b3548251b6e1cb78fa1c7561469bad4e1692 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Thu, 30 Jul 2015 11:31:38 -0700 Subject: [PATCH 01/11] Kill the devcon.exe dependency by dynamically loading cfgmgr32, newdev, and setupapi and using these functions directly. --- ext/bin/devcon/README.txt | 7 - ext/bin/devcon/devcon_x64.exe | Bin 90456 -> 0 bytes ext/bin/devcon/devcon_x86.exe | Bin 86360 -> 0 bytes ext/installfiles/windows/ZeroTier One.aip | 23 +- one.cpp | 35 +- osdep/WindowsEthernetTap.cpp | 569 ++++++++++++++-------- osdep/WindowsEthernetTap.hpp | 42 +- service/OneService.cpp | 2 +- windows/ZeroTierOne/ZeroTierOne.vcxproj | 8 +- 9 files changed, 442 insertions(+), 244 deletions(-) delete mode 100644 ext/bin/devcon/README.txt delete mode 100644 ext/bin/devcon/devcon_x64.exe delete mode 100644 ext/bin/devcon/devcon_x86.exe diff --git a/ext/bin/devcon/README.txt b/ext/bin/devcon/README.txt deleted file mode 100644 index 15cf1478c..000000000 --- a/ext/bin/devcon/README.txt +++ /dev/null @@ -1,7 +0,0 @@ -This is the Microsoft "devcon" utility, which as far as I know is -fair game to redistribute. It's packaged with OpenVPN and several -other things and also distributed in source code form as an example -program by Microsoft. - -It's called by zerotier-one.exe to automagically install and remove -instances of the tap device. diff --git a/ext/bin/devcon/devcon_x64.exe b/ext/bin/devcon/devcon_x64.exe deleted file mode 100644 index 5181aeb676d452591a9c30729fc69f7f88d8278e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 90456 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⪼TP!m3i%$bUwbDX41P!2h^i$OE_I-l$U zy_ODbwIAK%jV*CF@Qr{L<)baLr$I@_ItV&~T_izCDOxWvD3Vgj^R7=t_X7m~PBi~8 z^G;lEK)MNEJas0{Bk+anD`Vtd{hhdOh{gT|QtpdC)mNS=;QQtNiG4kuQ{cPJ;z1bq z*!f;99K#d)`F!EkZPQ_Ukn*p=Zq9){M9O#eq~>9t2PyylLVTgz=jS^>+*k{7Z=Y|> z*pI^$JlDYU4LLW-GZcKR{JvY{zWu|vuKEMlXAv01^TF{M=y5)H?}1#~jJzu$@fFG( z-1BNaN@nBV7+mFJwC4$3@hIa2#2Dnz8nCPs@-R-+8iTtEU>ygkb4D|3`HF`$N((~Uu1{_&i zr~jsbimO3W8F)|o;l)Uky2=opB*#iY^G3%Ewn)8?P95T75av%r$Xxv`xG8 zD~v#~sAG;;N9i)DoG#>`0Pscl(xwGlq$JT=*bFHw2VQBFX&1@O&g4^K?h3CQD@rx{ z7B1bKD`-=LEt+y~6S76yWkZiCshqFQGUTznKA3%@6)Y9l+G~c?A0PC>1Bl>=&{|3j zDij>yNRa>GbV@BS_07~Aj#)A4&ok;&hHWp@wzc65@-rOo@#=CcJ9?BsnVHq8jG9ux7JsTtNa@c8UK0!FH~KXk z-{G)L)Pk<1N?O;@`H1rzb?INj7wl_%lsN~P`e9-W<@Qh45!zz*F(?(`HidrY@p}B9 z)FWxXXp3kiOur?_k8t1Rm!F@c9*4GP>M+x;^!UeEC^_!Zx|%-6PuFLTE9Wy>GWsn) zPOm%GGIcZ`z48H9vBKG(R-iqs>5rL~AiS@pMLoU+2(QDtv};q=qvqEyNr{PdBL{pHAv z3LBY}wjSb4=D)SgVBe~`wYT?N`AUzZqm?(yD1q(!9rPGY-gai;=^a#{?n050i_)L% zsNpWmXa8xd&o!WF@8xyqD)p!MVm4*0@PgaVA|0{*@0&lIh_o^uM1NiSfO54w29iYn z$=S~@>7SO^HCvArpC0v^@pL5V+FH%N*3YBvV&IYOe<*&#?`d>bb zIbG!WF{yF&bf73NNl@BwFrk{O4jZTtlAl zCd@Tv=j#?JvnRZXj&tA%Z{mbE@qgKy=sGfUPun~h8kgzHf9Pta;k-f*mg~`y<6!+R|vm_kud3V%5{N>L$0#IXUWiYg6Z-766OZxQaboTa)%_E5rA!EE?8M6C#LDDEoRgL< zG|tI0wVlQAg0?kiQ~q_!W?I{|9c=A15$9%vuL4mZ88hzM&eC>v+=JqN9#7eY;yB@5 zo2N1Vx9oET&+&!sve_`=^54%tDe>cGfFB2SVn2-gav3M~!(dPUFYJdY*nc_xJn`e~ za_63}IX^7#Fh8hb=Tz?2{?vJaJFlEEgxf=QM z43peZAVp^ECY0kmjVX7|d7kCOt~t*D9)AtN732>+HNhv0{`FP4tKE2s-^$}XmaS64-ybqrFG_CY6v6tJ?o(E(3p)&%f)S`$J(5l-LVeD8p1_q)c(r;vC8Hh7+xSc7W~ zK4bo4Fmk`vhnb3}q)nWx+n(lNe*@@mj&a(bmfL)8HI!EKw6l3$mt4q48+@wUD`t<* z+prmPODMmBXYNB=s1#h3J2=6&3UtLcuD{3|!UyLp6FU4224+u#u-Jz|w?_Vgr z<{JwrSDQQd2J#tqQx-Z~yM{yj&-n-BPy4f8K@TA~3cs9x5cCdQkUjHB3)4HWo$wFD zld$dIgK@$?IN=}2_rx%EcH)UH`h`FCALC!9osqGypk3?giLS2wgK$gTm51Xiu{_Z|?sE{ezBem>xoSNpKWHXX)el2;@XKtocmxPw^3geq+}@ zLO7*C?;yE@Z!itsiHyMVEpwgu2H_AVe1p&($^W_7!HJ0OiKnVV&&i&cgMaM&OSoTl z4i2}7UCqJYU{3m>H9GBn(E4(R@ZYsg|0QSPj#QiT@Y-K+9`39ab0!Wi`=Ocm_^WS| zc0QZ^L$QVMcIA`4G9bC zxtHAW6cgoK$`jAO%D0&E+`QRGz8^>SG4vU>P&$7cl_oyMD?)3x_ivBaQxgZzYMHT} zQ13{do(<(2<4)`s-@IqG#J5nC89prE3TnRBEE(ti=^x3_3BKco+8W&1mOBdaJ;3G; zJ>Q@dj@uhY#IRm+)QrZ{)WFqaS?RgM{zwnRLnl z-;u%d2SE=Ie_yT(9A+er@j#Qi@@ZhnrSMbB!9MW)2;nqQE;(+*Xx3%SQNHun%n3aM zZDK3$fUE2Nh0!EM>2A+J6}1@p}|(qeiq5E)$_zDs{O?$nkt3 zzkFilO6X|{-cidl5usMed@{$32FmCyBdJ%5NN1>Y2WeRGvuOxwXn0S|apu|~bVN+a zG3hnmDk6GDdLGneQwMmSsWbjio7hisJQ#uh zu~<8^O(sU0a(al2h4RJ)9p>AJ%~7N$#r7$Y=Dc?f=r!T^#3;`?$+w($gyz~c+&;=4 z@%`+f?^y3x6Mw%Eb+a%^rI0heU779hDGIrpVZQ0JV@=v*`DSohO}59~kFMd_pY}9T zHW(vr-}~7eU}@um?|BccA@?jsf_I~g{R>{Fgj}Z@*OIdxc@-QlzM-6Nq3wuY)|K-_ zsCEVG770z7E5^~>E0Go^89bf^8YB+-L*}@0SBG!?r6h2^rJXW$k1+C7IsFoEn_RGd zI3I&FNIr63v;w`57VtE@S>28rrx_SH-e&uzw(yPg+{2RRqq(!gxoR#bAPz}4-=SyL z1}`bDxmX)i-<)%{b9e@qtH_goIgW**xq}QeCcQ*OrWo%?y-HjH>zKI%D9_W!kaC2%!n@BiL5)kq|X z$d#o?ye(pkrKTD}q>_*&l{C_#NQ8=>GZzuz3;8e{AT%m{`7I{-h1A2p7Si<=Q+=L&ilgNAUm{TcRvPgOh4(4Iy93} zOzSnNzSBE6FnxHg$7q4qiY2}Jl2_g`r1F2_7SLp7`BD`^LON*W1Mr&Jt5k*OAKC8% zZ);u?1eBo_L6{56#lwqQ|qt4A+}f|{!|dT$AKx{kFH+Bq9f7fuLmtz6(S*41oZBiw4O4CjEOjf#t6x3Ux!r>LnL8-5!doh0;2O4 zdN{<^)D~$q7!aDz45JPXpQ9J7U)iY`T!p8+V8w`MtYD@rnDoI z&5O~SkEMhhq_lgIS!4dfZ&>sN8ijSG;>d*e6%z!=&!`?qW+Glwh-tzYJonjijDX6J zDW1;cD{6tNI%tYGj^()o-4jR4Fd#_>+z#CLlMG*aC zd41@W19{Sp56NlZ9<4&lqS=3(3ZqZKqWbVhZH3194D+h97%82Gy-}`E%Fq|nXVx!$ z0-z?s3i%Ii`KxV|&5Lq|>N2l!v`SM-Qv6J)wMkjLOon``cAWS3$qBj_FT&c<>Zg)P zX^%It9TxjRhyK^J1)hY?j+Y|*ny($@4Mnz~N+KaoX?Rig^UUgSn#z08Jb zt{HvNERH5pP>a#lWu~os_D{IGHbI^J#VA-)0~>g)2ClQKc^iUGYigl_v<#~BYm{bo zB{JE#w>K!K>v`mwX!c6z^&<#Qj38}s;u2#puSh=!Z}1c@I!oxAG`{|I+J!Oqif|EB zvitBrN^HjX{b#U`Nt|C5Q_0i>3wSBebrvL5ljwr)8*<{MO+Bt=j6v5akSS=bUaCxb z^yDq{iXtA%0D*V#vsBh%3j&wvxAYo}hP))(S>xkI#(z!iL=Si#trSN7+vG=cV2~o@ zAeC)m_DOed;FYMov0NRsH{@%0TS+5xr``ZQMtFaHsS&F3%S=+zyoNnXJp)oZFAnN? zZ?7OvGl2iHC$pglAdaPG6Z(0}{_}IFPG@w@YEU4gf^2(_(Sawh{LZhK$&iessvBK*K24i4mZxKPm}EN|bqQ^-Rf?)IQoSW7^P)eRdybm>0@(AYsx5Yi z_~%U05wNGtuF>Adplz_Br*N4np`+6NQq^1GK}iiPxQx0CK$NUt|RGpyLey^47z zY$Mep_Iwzvfkf(b=Wo1LpF$pJwv+0&ouOvVG=NabJgZdBRQNq~8J5HLU+^CBFf@}| zhc5r6ou{JNe17#Ktog(mTz6I!(r2DX7_(^VE_(Sp_fBf|Tq|KE^_x4ezIoCVYzTVl zL0)NQ3H!iro;1bK@6Z1R#<0Cm%_#Eq&m{@*P!kRLSOocNnnl#cOL!jOBwiWv3S*F+ zf(%nsMUj(lO?x5n)iOiu3xqo^n*%1`Sg56054K1}l zXLcaW^t0zfhml^HM0Jf%_ZqPVK)#sDNmv^lSjpq+xkBSFtnb)W{9WE_dNtiIdj0EJ z%!(QCx`#MOyDugMmYL(N?5=^Po(VI!i1uL%&?jw;6J`&@vA6I##s{%Y{Z;W2B%(WF z;w@aLw~RY1)~DR0I?Cs8tB-1E+c&Yyn^@*eEQ5QY{gpp|G?ob=_^6Hf49PTi<%BpU zb+<+ycPCkC;wZmb7uGXM;V3YRQ9z>&ngyhP12-9Nvu|lm8=ek67cH=cr@N^*{ekL1 z=JkPeQk9kBx#DVu)bOH0v<7{&1`@l=!kSXV1zzfC0eMyGCnCsr{9Rs9xr=&V-05XB z`JwR%C`IifYW#Uj4imJXIe#G)pimg)(ft9Om_l37D6WeW#}h1144$z#j?c2J5VMz}n)WZlS zX8>u?wF%=SZy`LL9Rzw;RiB!6&1(?$T`?AW+A*?p%7_M^D7{VjHskXB@eQ2w13pSKpUa0sOh2d1~o$M zrO4qxo}&(~c&J~U)US@}*XHVq4Mqv@QOpma9`Ywms7C%bXO5APa|G(qDjLiaJIXk# z2aBL9JGPppnuE%+2?U?VRLGl3f!8j|8Qn8Nr5xSi29Arz<58I6GdRsw$JnjG=?&qt zujp8;1$e{L;h^5%pg`n z45QEdX6?nZ#a=#6n53x3VqE5J4Bx>Y^OEnc;S1Ok=erSFg+G2oM%dHkXDuFCJd5f% zE98?HCxJ%HCZstIsR(JKf9sNhxOax~v=0xBe*DQSVUdj2x@ODo6M-YhO3)KIkQtRA z%aV)rDN)Vsa54g@r7?t%6TDpLT^@zAq&vjaV=*0s-utS@gufB$c6B3(`{Cy)q=v2F zg{UQ^chd5pJ7}8POJf>P4YZT$jYUF;Z80OM9vEesNC$MJG7$4annIRM63>}de=*9c z*JP%xki{T^Veqj0%%pq)#eO^=P8>JWF{No0Tbpj)GR`pi=^Rl#=tOKE)jL&dog<-L z_`|dgu`i8k{*2D?z3?0obDvt)|CYvy;{~Dq0eP#EYE3i{`h?0aF_QeZwNC6e{|Oxw zY8{RIC>6~``lt|8n$QSYsOc1i<46oZ?FFl;KZ?XPw`N`3qNlK>Y+TH3$d*zZul5SO z)@Y-jrXIka*$B`Qp*6u$rD+RPN2pjcCy>ABnt*y78l?dRq>u}g3H6)i*lVK2V*Jv? zbl&fQULWF3adMI3BRdHy_89z1Lp*0b*3q?|RFLz$@SkzyH@{-{Byse+0O5J9cWyW~IRKy{x|z`M#si)PxVZt#%C4@U1V`F9fhuMpr_JpJYRzoUZfXC zA}{>L4=BNGp*5&pqv&8yzDL~}U;Thj@LA0`PUY&PzJ?!1XanFR>r*qTvL{;cdLwDh zNcTuWU-~-Z2j+-3FlIik;F6dd2P;x5J@=729sClf;Q(uNRQA!7Rf+vkPokw z_T*~qHn{Hfv;~wnRZ9tCOBTuNrbV;WAETyvzG#jk%^9>xQs--FoNG_+&miU?ukqN! zjFCy@vuRX`XkCbqc+YYeuw?ime2=^VpXt%Wdny@>b(oChrM7;$))x9gSSj)@=pUJ2 ztbrBkJ4Nv;ZXT5|s<3v7dPUI0aKZROBJRh{1RrZMruHerFwz^e5g8t=%B0T$XvR^@=Y;OcvXL-OFTy(@ z0L@xqmXIvC*0$ohs%h5UhcF(X=Y%2%y>v9l-$DK44OnrK+S;hb0XtI2Cy3pEBJ_@z z9t1`Dlb;|mo3`rzY!1TTpv9M5CC(1f^X7bPB29}2x%Txc&NTDiG+zL%k=`fcbHYGi zYB{6GOmSKO(LG&RuoJ+{o_I{A{9zKO7)}ul($Yo{U@+#ANW5xJ z@ux!=cc^Tbq^gn(l?`}I_h7(lcomlK#mY%aW2nwiD-y<))G~w-C9@4`uZSVAx51y~ zpC^-whj{OXxa&hUD{-$T8qqX#(_H--a!kksP%j~*lFqe`#+BlJYpK75jsO=T zWc4)OOHpbjY9sDXo4^0sPwL8KmV`Lt97T>p&d8iGIo7$dG#RlqnXInTTIrzVlpe}n z$`Iv9WxR5da<+1zGDo>ixl?&ic}jU+d0lx|DN`A%tW^pXr|PWgrSeyeRE<$hQl+UD zsS6pBs`pCO1C!U(1{S2ZV(*Ba_tA zlW`f%`WTtC&Fq!=A2WF^y_}3@UCBqC_2hErw>T3cL&rM$dW{Wb+!!NMMC0s~iq*DhO-M+FpZ)|curbdP$lb}}t~=LU(5HcAr^6?Ao0i^~ zJ7@Ff330ayeYucpH~t^gfqi7?7i{C)d-ftN&Sfd2!0nkIG$Y&uMhmeGX?< zNwM6J&^Ag09IlFQlU7R`fzaiwU z8|TisRlMbTifO3FnNu2!S&z=GxE6GfChQ|cQ1rOS#H6rs@rr=R#Q3<_h{*9=hTKj# zKe?eyKSOR#>NL=HUq?C0girHMnH zExT~4@BS~pI%+<>b=LZw&pY4wvb2k`*QvfY*S}{!^ki1O#Vf4VzAV|)%w3OFXI1*j&ZYgW zY46;9V(W5iX{(a^FUJMV-2MHv?@s?{vt{4lDN8=!VKc&W1RBDvQ-q1*$bWo} zzv1vvo`z3wJzMyoN6>~bFt_n!26*LD7g(5}4O$?{jjQn<1(Wzq=h~iot5QVz4b%qP zw1t5+XO(8zKxW}sKYQEqTIIHvSMMKW<`|`SoNEdR*D1B3g^o9Y(5=9MllAI)j76P3FTI8|~ zN&7rxeJ3_(HK^N}Ci<_Y`h4SKF}lnla^(5T@8mxf%uzM??We+^}Q%|qm zY2EJi`nE?D7u@W`!&7}{?wh4tb>xcqI-_ByI>ZjY)ad%%SvP;~x20Q*Vt!)Ww6n9U zf6hGf$@5h==Z(MNX}&XId*^nK+id&3$?BChw@0+yd8w1)O7Wh%r($AM`Ng00{ypew zYqxIgcNRD{+41|KZZA(e+4?_R^3CHHcM>o9Znt^(=#^?=!P}NL=gSjp-p_va)Z-KH z^=;}596o914&{tP@$$Q)J+5s|al7K^r~kRruNQ+xS(krrGIw>}+1lUqZ@hnbt4Yzz z=X^JK&QF(T-#rsGJV~`O*PD2o^~BpOs_-^uw-#PED!eYZC6<%=>k&3|RsMzS(D1rNNc5z|;hPa=OoQsRQ zGwF=uhH%MZqAJ{vaeZUI=;l`79Smv=xob10Wqfa8w`mY z9~TrIIbP8(GATK3{MbZA-@ab#dwm_>6S%9|U2V4d`0(OoKBn!>+I;w8oJVeP+<<=b zPG+t+aOIa5<4Z3t4oz4zE39yDuZwd#6;2=d*+ZX_`AwG=xmww5UlY3IgMopy-2H4q zR@{Ev?Ph#yH~XwaFIms8?&&y532eWVWeg?7q+jy~fJNZteWe`J`;i z_5I63hP-cY-8QJG?mT<{oOPS?w>2=3&)uFLmRDfm*T0GRm>#trSogH-@??ZdYU+cO zf^BY(cKeLZEHX*i^25;gPyQSb{q(n@`R`3@v1H!VuM`KrIyC9h=}#U6uJO#2-!OCe_d%`LnAyy-aVWWL5bi;9Mol0p%({uEL`xX~b{hIA86ows#D= z*mQbyRF7S%!vQn8lrE@s)ZU=W)W%cKO_LX#>%4Nn4==-4?K~K+^xJG&xVYhO`_cm@ z<_sz-3T}FNR$1WC^QEeQ-meN*jqY`2+w>QYx?Xs&VEWBR+gsNAWS`}t+@y9{lL}5Z z^l!cKyT(^LjJEB5(L63}Z{|zifj#nkip!U0ywlX?{E2{;?MGW@nD_a~Hr~BxMT6Rl zI&QUTXI|9V8f#I%o$1!q-Q))%=Dw5rK~Z?WLE{pJ zUsZXIe*5FYj|bekKKy2&Q8(+bk240pJ^NJ4%_$Fq*2kWkW0${l&czn{-tQ>u*d_b= zH#VxkYo9cqtiPvd5Lp%(eNCKIUWK#LJAC@uhrP$t4*qD3!L(_9BQ%{=jMy){zO!=V z+Q2{P*SFKpCxp?$D*> zjpV@=$3`1?eQnk{V~$6o`|WO)^({*^Y`pW;Y?J=2JN?#0`P43Ec42Ruu=|_q4v*V= zI_1;B)+4XoX|r9wt@6aZ8IAf!8zwrKJhQ92*e+*bqsD!m@1JozKhtYV*YH=_7ZoKY zjzI-CU%Wq~tnM0%ghyx3U9p(_{m;QePTZ6a9TPdUphrl6>-vsi{p^a^(6dRpA zY2#--ZCBPVi<2I$&wt|nL4Je0o{MLWt+i+G_xV4ylGU3d8&lAx>&@;?clZ+>SB5D!yP=knv3KV-7d8T?Pt>s}d|VFz%Wn}ITk^GG1@(7gf!Lnz^^1FLpG$2*h+M3_e0V8j}P>e zf0y05v9aE7;Sr99G$2{+#e8q2j@-Zbx5Cu?BfI-VT(*k-CG%;UAAZXIV5-OH)~+4A z2j=B1ywNe<)NTKn%|Y$wN6OAlc2RVXX}vRh{o7mjEMjy?;Zt*yO*~(W* zAsKGVC-`slUT{6@#MHUw&L1DUbfzAcon&?N+E|~7YnI55yUHf_`aLewxiHw#)$aSp z@~^y4%=@xvc1fw#)w17z>3+rJ;K~#)ujvCr4L2>Wd$gU|*n_KXPP$f-e0JxBPqL@) z?wjSfa-VYc)*rVV>RYnxQ~jfxN7#6qyLM`DwfI$s;4@arp#!5@ZmCC^x$ zx4NF;wERiY*Y7S>r6qdAm#hj73SED=#M5X&q3d%$pGBeRpOv>;@z`qcj-c@aU{t?mfy_BwpWHvj(K+K$Ul9UW%zZu*rOu^EXyTKBPBxOwns)59O_FF9efdDG0e zUNb-K@3HG%-}_s#pZ$KqV_4|#>+W^fa(j;5{jt`OTMH65ox6Abq^)UE=({_|Oxy8& zvZ;M=s`)5o4;RakOJCG7SZ%_*)nwwWCTe-BcVLUH4BR*u21xqy%D?m~|I)Ag%b@Zv zWCcrbITka8bNnOi3G|9}V(4qO2(}1~a*EV1IVB9z68tANcXz%%VFWlkKa&70PUkz=uCE#Sh4dJ>{5`IlzwYH@mZm(1~-I#1d-VOYb4ZsU7?uq62Otv&8DlfP}L zdSK9K;QF$wWgA8ve>T@?;nxRCwlCQ?y#H3ik~-ZQueThUo9R4i-8$FOPv2g9_|b`` zlO`@{l~~$#!^ssB`;?FPx{p<9pZLDAIkwM_r0nf$G3e!pE4F*}hOK+~tXAj#W=DrD z+GQT)bNuX#^0F+mkG4k7>;G)<&n?VL@;1v-@2q&Y&dEjR>~~n*F+a2~)imc$JL7>L z*zLS!-PrGW+vb~mooDo1nw)THt4*IiP3o>lwk%lLzr&EF#%=1hcFr&$_+TK@lgl|0 zx%?9g9lg3*nM;2EF2{pwRJmzpqVHT=uz6=dHm2APpoxJq$(a9|^2KfWBvYRSW=5Wq zt*8309mpGeeJ=Kz$F{p&#b&se7=7E z`lp*uH=gP>TD>$oc z@Yq8ZnKrn%{tCz9AjN^zUB3v)l6#!G`SCZKmUm5f@XRAUG^elZne0I0$C<+yJiA%H zIK|1Zx7VQgS@p_(J@-xL^j8h$%>HcFs(aBJ?)8i)P=0=3R|f zInDRFVA09?u8Gyd=bnvsZTr@&b>j{5HztmV4A^*VYU*a$F^jt2hB)NUc(7{lfy?jM zzpsdB8^8V9_7{2i9`7a#?aYZy`Smg(uw0-on1N((Nc-{4Ml-Z z9u)+(&29Gf4>!Vo898=YwuDOfCbyd&wk>tqkj#U(RI^q+ef!f7Q$KHZKDm6)#0~OK z%-ebAnfLx_s%Jv#QMU&d%50V_PwQlIsAQ1w!IqH^OD&%_4LNP!FTt<8-m(?LrX6py z{&?ZaZiR#IT)6kvtWjmZ_dj^};l-TTF>}+Of4OeS%$J6R+cLbLq+c!@do%Xse7kRt zpWg4&WMY9?pjIG;PAH3Q{k$%ZysclrJ zSkJGGOn*JrG<1H@)1OL9YCAMMI%IH5%b2)lGupKX7;c+3+bqqbqS#+H{2dNs9yfo%<{E6ZnjwDQ#yW*?Ee7qbsJp( diff --git a/ext/bin/devcon/devcon_x86.exe b/ext/bin/devcon/devcon_x86.exe deleted file mode 100644 index ce752b6e905f4355fa0a489d5a2adc98affd09b7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 86360 zcmeFa4SZC^)jxidY#>5l111PcHE2*2P;_|<39v}QizTojkPr}KBng`sl9=rBV#NZR zRkoK^TcwXrf7<$9`_#6!m4b>IUL;_B0kjsy7qn;>liIZMKvZP^-|x&$?!B80FV;To z^M6P_v-jSaGiT16IdkUBnVEa@=HI0yXqsk22!}Oo6R!Moir*9egpl0(f@gYbPo4G3 zpiOZzUKupUUD=RcS6{ohzH~|YqSBh0T2J~7<>~d_n)J$=^qg4*=}T(M$}jKLt4Brz z^{X$;&i&qd+5J>l`m@{n1@XM}XRG@)itDO=uZpX`pI^X@?>AgLf7LI5>o59W-A@eB9v=_>IaiB^bS5A(^UFJV!{1e!_5iPU0rjtV6ZyL;PMdcD z?wxVk*hEcBN8W)dEyi_3nze22U88C35t3qnYnOY>(pEb{Oh)A)orU*uYx=z+_vhrHLFq+6LWKO{eC}Wdc=rTu2rkFByGqg zV_ik7wC>uN5hGl#waAt}WYthhHIm>5n2Oc)A7`xQP3DK z?}h;CQapc0TPfo6v|KF@kP>=?HddRC{3EsHz%v+ljAw~wx;6{Y2K>kNugC}TZqMLo+<$9iohQkIC?YABJg!;M#yQ;m5C)Jd>s!LbolrODZRNhb&DIY}4tgV?|<0-E% zUE~3H9!4pMcL$#|P)m6uU!rA7(}syS$v?KTsMFk<%9;jGX?69i+&t6_MM3pWN4rhI zb(WB_%RsG+(aT4Dmd9MNO28lCwG{?98X4W zs_sg}lp)2qF9mM4jvLU`-~s>KsC^~ih=VyA(4q>iTF~S%)j>n4<)E<^{~Uef^AbEQ zMZ9{ZJeC5N@mvd@*CSTaN*uDBdVxjCk)nihMH&`qHv)mYrsyw}g(7Kiw<@}-MvYc8)VyFr#Q7T3$D0L*`SxX^K=NN@M=^(3wSA{-8XcWL7D zMOyb&{v*rBI*uTENWrtm5k2(np`QnBXZxExeeD}<_6ppbRS}5uZHd3e`{xDzz24rw zL-EhwjGQetaF?tg+;;Klt6B~<47>K-BlROJ?1J5dy@m+ zV}))F<3Kp=);k2Yir)J-%`PzGVY+2j9-2pdi zO3R1-Bc48b?y-u%z~#6&c2xvsERPR+lfvF(;j}X3DJ}-&0w8z?$V;xLihEbkVFaXbcHSfzkJ)17jCs_MkRf%=?`jX z9>g0UYnz-WjWi#O}nmTS~%?u zAoR80>|N|zc{t8n3ig7q_Hde;d2-wLgox5PKL$GB^E^phVQ)LQa2VGVv^1L~EypM7 z`vA}pLq#BY3It`>)@03p#NN0JTt!`2MDd|2JQ%#KLVltMU5aRblQ$Vy#q$6>FWj1B zYKDSB-J9fSD$?K6+bc4=dHOq!6ok`uqdviblYQIbr&eS-yni9gt}5I7;MDj|b=c=< zD)#U9To!OZ48Ntfx1<&ZJkQ!9NJdWa?TDWWDbMWY{c|AaSzBn4|(Xx6&gxR~4kIySFS_x~k&$gyuVTC2Wns8!~6&iN)(D5%DU7c!fe=iJJCC zwj@;n)Rr=5h*y4ok?bNdE74x;58E4WM_m{ViZf3y+4tr=k}f-B)~GQ?c__puC`W zx;vT<*^pMG-)hsFhjw!u+0%|rV&B+3u<{rby}j{W)UWEY;)<*--rgX<#@^q4=y!?eO%g$ZGcVK~+Sl`v3+Y???K|W4gDE{V$=V7g9+?vA&Nz zwy3ZO5k~!bc4TfjD>$$rn7s`3Lh&rjKNydTvrdVZd)gC)IP41HpY?;wCS( zDBwT9yWAB0?}6YOxP{;)&ky)D0Qz1FAN^2&D-b-4_&_iPQGsAP?xC{-8{WZ9vp*S; z91Q~ZC*jVd$%_O2HbAh|DnW_9o*2<~{=K)}s&}jKjUDeT_BF*r>t-f-X5@!AkhGvc zpI;Rh3_76_@=?E#(9vFdM=!PeIVfb_F9>ODO;ETU`zK2xP)OG6w+_&4KXx1ea_@N+ znRW5rv`A`8yyHl-pLKzxY5MO2!DApZ>ur1RRX(pL(fT(1epc_srexpNq=c;je=6X0 zn+~#_feo!l3Tz1R*MAVd-g8{dent|bln$t>86XTF6f=)+lodc7pqho5Ot1v7VauBHd4J6-p)_-FIG-ci@<>$<|Xy zNTKWKTZQNC3i{bDg%@$Wb|RnN?nAMSH4p(^$V}G0l}`P$8;>L>Y#=9xZj-!o?9aR| z$pddUNJ++lt1yg-HlBY=!@4J$4m}LI`Alp^cEUp&=cQ~yLIVejuJVmPI=@!^P z&IJ7~(ByJ7HH({pXav`x7vc!VWX;~V8V``-g#)-pbca|?10YNhNp#6|j#n0h zR={JjYio*WaP_tJr%jez)fE;7ns zq|oOk!2})-?l)Xr z93`ctkNXBBw0xAA+vZ6O%|#u){K6S}3Q$$uBUtYOyQ?tsOxD|3 z$Z9-dk{R}1i0Rci?%r1Xe{Kthi(7IwX?-vf#KW|I6;O#!C*jeR@9t#cvBwID`+-}?jo_iU;&3?w38`$317o8%!fu?iN zPne?c(o|Q_&rFczRYE;@MshlSmal!V-Txt48pFtxxX=S2)}3Olz~_Gjo+)vM-UgH| zkmx&Ce?X(cbFNR&(9hdPW-n#-mcKw=UvM0mQlNi}O#+9&5PgS!JTw`p;SD6n9Rf`F zbswhgp&tRgL<>RsnYIdO2MO?0B0FRN-qZnDT>N;Fr#JNI-w@^ggyn(1-T{btpE0dQ zrffVz9(OEgNryrWH8Hu5wSEH-hbbd{4JDj`FC7;xv%Z7vFwzT)D6K1#9lP>_IT`(3 z`9-*->wj|vW@Ka_3<-EL(lM78?6v!^Mk8dcOcsiEH{zK2mW-4tO=Lqm+#3Nr1ipqn z8R&lQGJAvkv>A{?C2wQ zKLknB$G-u_KTCo4-_LY!f0dCvsw$qnEBr?c4qi*Vts{P+%!l44i29btiXfo_a3p1P z;9uAwSWrfi{dYT$zIEQN)muIVC9okoR9smrPjm(c5ndvE(nSW}|{3A&_?Xfaf znKLi6WIk%n{7!V{0=+Gq_5|t)CxL$`GGf}8c|WUQ_YcIQzJaY(3)t_*>r*DM6m`4 z8!4?PBT zt$*3l+h~NHs0Vm>7J3^+M(PqVt+>!(QYMjAh1IRM^fv1SUvM2PGFWp1!7t8gH=_cD zuF%q~hz{VhOMhK|tyv@yEc8P;3%bY|`smLX+CSsSZ>YQph@+M~^c2LRP&LFp!CC&I z*9fEqm<&Z4!R$DhfLs-sV&-ITyfX5DZ1%W)a?_Wpo(x{3+|_hsCIgyx)#8D{zP()WBLN6Q%*(=hC65pX4yo&y*+p)#nsBeMV~ z=W~$(L?vq|t`1mX14=NZy+4vgrNlviVe=-t+sPC;PpJPB#Vxr?i*)10jC)zGytr^B zouIg*C!5sibciB&xD_Y>_;?Qhx<5BipCeHN;(-VRFuUAe7!d#cp(wm1K_Xfq!9kN|2Eq1?v}1j?sx-gk%SsnT>LZ2 zpGN?FfJk?O0W`q`l&*j_6Ub%&4KM*Y70`bWDA52qd_WK-iqqmS^CeC71nOo0y#^p? zt(=VH;5s4T`A!!F921~)HGJCr7r_>hVlVWfCnE)#FulbF)~1kU1Bvyh!TJ>v>kfTg z#&+~-I7S>VhxQCb^L@0?q!U-SjzD7=UW0<5@C>;0w~7My)5MQ83wj08AQxuEWDW3axhuDZc@fy1SDf#t{7M z`w=alADpkch_7}iN-Gm2h z=0Ix){7txr1>@}{?NWW;p=Tgr!Qdmz;2ws`yPf!fbi5;ay(g!@&CQ^g3xQ_H4`xFE zMfyE5JwqSG*=Qu)y^WQ6vnstLq1hF>`(3GF&y_Il?dHU#NGvXnpmV!a$>9wov7`8g zlN660agc;X%Et^6#9#?N3(G8K!Xseew1blD^Ay1^3YtjmNAE~#`?z;>#O*;`ak0A$ z)L|KJ4LP_OHc7KcC0uyD z%<3H;S;L+buO09}y}SXuOG)NG^drR za^c*_}YKI+d8 z!!HW$fm}Kk=O(ozpsRzv0a#(6W4Xi4bIsf2d`e55KAv$*My+?**Ea=ix!bXp0AH1G zybc1O;|M?_%N-LjUt2Xq6yv!t>`-7|K>TD5_v4TexyuT6e}vk5NuDoD^KQceQqzK6 zTT@MIN2^<1)-efwY&uY@Ql4F5VjD`*4H5fkLb-u z_Z6YvAr5Y|p)F*Y!QW5zfxnM31I*+U-*W`h?6>_881?sn6EiZ)BF zN*60C^MiStLz9smI_D+<>4qtRp1&C=2ru=G_Z(z@Mx+Fp5=xO-CPV;<-2H@Tig{>Y zT~ri(tt!P?obZ;cca~s;eN8pm&i5#}i|LlJg9f6b3!ckGzs8H$5v&c@XlXb|U4gzZ zxe$!?y&CqdOvMI9qI*ZAFIf67f_M8W#x{Xj{%Cc z)Aj3+D|2qD{f>=@fbRj1y09UAc&^~O>CL#AT@Vc3gCy*HU|~hBmTOHium%w51X2bF z7Lfshc9H?HiO1eJA9<=U_3;xPE*Zkg;)~cXL!$9KKZ3Ud324U4Wnuw(?lv(Y{(h8X z?0!@9JjdQcyU?tx+ASidz42*0>&=1Z?!xozg4tE^vuC>k>+i!;WLrR+?|9gg6ZAiT zD46wy&Ek$r^Wn&HuIFO?erAZkOfG=P&k!VfTLU6R7cGJsDG3a3V0j&4*xxLMoBtq2 zrnS>SnaE-mMU^okkLN|nUhbBg29801_24Z)toz9y zi1eg{tyNk;#05m${461B_BFFG4ut)b(1>6Lc@LMN6#T5bRQtMFhzYEx?3>s4SYr1` ziMr?+CFpfx+(}E1?rzYEslM;pG;d#MYwR})?SVNL?tX}?I-=iEi605|L@dn-AEJ*; zwZ(bhX~SaWyNJf4Gx9h9oWNterw5V!KJ>VJw#nzuLYv@kL3)y>r@k^NbQ8SzC>;^2 zJiQQs48PzCtB6X8pxlWi9zdjfdgysc`s<-J^j>zyM5L+2>hIU*_Q!q!>IZHa@FqeJ zwA|<`4{5&gRw!|7sttQnpT2dH6BGZ!&>D&Z8L7~GK=uS>e%ifbw zvr9mnA~iK+j-p602D>JpJGIaar$kZlNhuohO;D6bigsftIysM;_neGJj|zxbJjzWr z8coViC6voiZa^2p2Lrmv|3P~N`^2eR^;tH3 zOI6&odDw^qXA$-zwf4oulNiWrYZ>P&Z`Z0)#g-=Q-4D4qw7M`*z!vOLP>8v?aIEW7 zZPwh#Aalw*&AEqBJ|ruWwa~~4MAMX&hoOBlh6=MY)pPz)e-q+X#et;0h5E9T&>&3C znx`fqLTfn-G07o2V#3|ULux1<4;W2oy}{-VZ6Ck^#k^^2b?Y(YHJCw-40#4G`b*=C zE>`rb!`Rw2N`nfV27+W{e!IL=tIFXuPAzMP-S;!3(rA4U z*9c5F?HCfp;;4>T_jWOv4IxgrK5urvg9s>L=_;c;8oFp2aK{yMvXvnelYmNm-L^KPw*>{P2+iQ z_u295=5CRon9_4G?q*B@Asu>i#2@e!=nZK*#WY~;`x@tN(%TU9-v_Kv%tQ{fWj>By z_Qq!gOe7UdLv&;h`h!Rb`d1OWDh_DyAW^`*fE8&b$oXfTK;>B?sRHiZAQ`?S@Ipg@ zBuQ{xpu!~ldI>*1g1o3|fLfXMT3xjY*7nFRFgaWGeOOKH$GuqsS7dGX93X3%?$*wRqdp=}xyPVdxkce1s?qYQSP0&Y zeBAg+Dk3@GJq9NOHowe076GMI*2%rq4y)J@2r*UDy{Q;xlY;&vEO(HTyju5XZUN`Q z8wMi2%T2g)N8cd78@jwI1w#O?Vy6IagbCxn9(Od#oBd_Diz7~Ddt8J40Er!3&kp)W zpf(~qZ!ES)|D*nqh!eI)vOXoHd5W|;67(s_;coCUPT|&3?#qwb^J}s{a$s|wrv1?k z^v1S7w#3%)Ta`LD>-cRdQtEiVRHO4~d7{Dc=~1&H_J>8YbBODt+57(0qIN#q9Vd?mV!#8m$3RZsHs|3B+j?QkDjJ=@}KGnUP)Jd0cZvg|^RvzQYL7u~P1FZ9e z#MkOF#9q%!E+};DrEw{pgYkj9O@Wn7&AIQ;A`Z;muD`u@Zi{{0LZ}SAyKw2v#_eo4 zfVT7SQeJBpCzmb(ROZ6%_QvyZ;~t1!9A1WloT)Ha<*9>bxfa>-E%%VM7KCp1-t=%{A4KCaaLJn-|C3g44xp-PPvcB0C@E(NU#@_fVDxjT?*g*?9UOl<=-+Q|T!j3Ht=oaqCq=l&${aWo$UOiZ zb&ew(`i~bOUqnh^2Z2|jndb#^H)EZ^en%_V=GdE=dOJ26;a0cZZr?aII4wS~a$g{C zw=X>2dx3ppW(QJ4^MvDDaTqJMfcLx6YjY1^oQ}kWo%Y5YCWZ&u8?(gCQ11Z$krjP# zh|PN-^K8%gQ!-P3>;0nT?1Z)!yDvQ4?tcJafQT0mFQ64y70m|IunBn$7PfNtacr?; zVt_r-yU9Mpa&{+nJ3onsuy-Fe1K@^~eGKg(&J*Q5mD$sCmNPTi=lvp(`xKnCHkc%x zk0G}gN9yzhb2jNaaoqc2ecRCHDjO`?+$Z(-0^T?D_F~<3P9Q#E&$qot^o1|$d2i?| z_vv$=(sOqQY?U`P@U1|z}y z3DNEiCjXY~{|km6U)aeN7$RuN)ZcW3Th7ua*{%-61(Wa2%=CVye;Hc44JCnl_C`(( zQRWzyxi>KPfd2lD+?F^9ik`a}7&d3+rmj2>!gA<+0HS|EbhJaa8~5MKZD~Zy{2b9} z)*JQj9Mg}&PV=O~z7fN=IR5O|Tanq%+dA|pjzaG_o^XURfBPh0!F>5(QB`-e^iE`G zV)J1q4Pq51V!52DYzY1QwmJ!H;%*eG_R7iM$a?`fS}FIPLk|K_)%afAqcr7XeF8=5x$m?jz*LP4OAA83!Y&`HLS5ZTx0*?B z@reA{h=_>uI@JI>l4>lnYv28-mE-l#qq|K>=61%8I{qHsFAluerjG?H zk{uchrKGn9p5swGcvjOwL$`?fvX1N1@tfJreusm~2FH*` z-eup|&DTEO+YQIq?Jei|+K1vD1G`%K`r3zk6Opwg$=5!}+q3F;al&U3i4ZBdQloDT ztfxrdAl49bde@H;s9MhKxyhhA_~~Pf3?gi6Pyx5*0x)kByKdlf7kMq2=qt= zoh67~IbFY>l`)DEg6bjM(0{A&Xg0IQ9hwg%$!_*CI zUV4>kn01+(EMKev(kLyAvua-@0Zo;g7_I2Z_w3;2ea7g&uy7O4T z&$iXucxX7?!5!9y7O2Ra;Qdq=f}#tsp=}ADb*O`TA=QPlC|65-em+Djyn$Up%2|h* z-{5EpPf5{7iKWPW7D0X!vdUAWerCf<5S;pJf#=wt{7tv^cIQym-z?UHj0s=qo+h4k z0qn$PkL25Znb<-hdZB>v?sU5%d89ByLau;n3Y`Tu9vY0hs)PkCe=?57VI=}SFEK-7 zFUMkhZU_U(0QVa_sy6aj!;tXUr)C|$<EsBs&`&(bs0?wtFt11?6pbKWy#Ek3U1h zFr{UzFtY{%$o+^F;x+-1+|tkekTqcm61Xq+&>Zm0sOjp~fiRHFnyo)JAiz_8UEn!! z?)o{Nl+Ik4D(9g(NX2q0<1iiNY&ri~PC>D>DJP^D;PSJ!HRJ`wfooS07D;(t8+DGu zO1zj}&tOj1axspS0L1;Qb%M9~Y1TDaKZ?3MZB2R*NvsR~ZsOq8SExg@ z#!i6>FzV%dloNL7dK^i096_JLWLsA0Ju2ZKQ&o+`1ly>Py^#`)&D;Qyue2~v9?@hH zz&!^dhHlcf2F!;j)%}8%0S{~vym9RB%o{(FplFVWCB%cIWgEC8RuBJ(G0e&(Yyn=~TGk-| z-2Qt#p6K)CK4;~epu6A&Hggd+ZkB(?gx#aZC?Y5xuy+gZIiXqKi|lj zwdBi5ihB!F<>cf0B1PmENW_P(z>1hC3r%E`^7krkHQef-VY~RcjB3wva>UP|;^kI}oM4gqnI)occwyNSD4tbnfD7l;9Y!2oqzFq^D zL(}+rrSno;98MdFSiRRVzyE2r6!e1gGe|A>8rd zJ=iIquma$1%>Vm)+B9&V7;e*)(&h0 z$JXlOEh+90mAprIEsJb9D3Op}v8JGDj5i=|xQr!Kagx$&b30;p~ zB}UbA@O~DIU*Sz*lz8Y3`~*h5fht6Bo##$s9*ubtL0)ljb0Sg30}!T8azTW;k)Xjh zegsl9oU7(FG>;@etMw?rVgu+$TzC|@79K^mg-4NZp=4-ph}y%u@!%WJ*3`VC(Mz0c zr@U4@PS*mt2lHK_ji_sJ@ix6Ygm*q!zjUi84~V(0$#sNUMZa zgRVmX)LH2AtX_Npl{aB4BA)?}i0t@5U)JYjq+ok-sy-v5|J>QG{CxM=8=P8Mc$9`e zsV2Acv}IXfFk=8ZLZ6oe=wXfldR?;b_;I|6XimmR-xtSWiv4}*<-w6lgV+8xMeiAy zmhAfuHXyw0j)Uy$laoUIAa&fgA|8g|q2aiF{; zeOi)ZXXq-l;#}R`KU|j_u1g8mrG_RT2E1J;U{H-f{3b9_+cpvft>1};2;B*^Fj@jQKs%fdN}+sPKHq7!6o8; z{|2X)A9@x5Aax;1sm@56osXU9siCL9Zc<`BMnPL4NuNt|Ar*}NPLgBnj$8~i-6KPL zup!0QejHmQX4w*6btI1n?**_vD_NhKq@HtAjyP=T;eR!msPOhtW>f4ee|X zEmGs5zU3(i-od&vb$oi_-2*&n;|KB5d;EpGbRVxd|1-|pEu5BtzQ!}Ckr%tU?=C~< zP8;T9tW<>I!E*p3Iu%r%b|cu=4EAwSl?08L!&%D2LAl4Ck;6qF?@&EI+rM{t#_BIn z!IXx9L0b_Jacm3=e zr(FxTPes;FPcPr+30}N>&k6C)O^WE^@ZcGWRz*Gt7YG6Lw4^YJgBfy7%TCzoXj+{q zMzZ8*ID}PX=J&^D*9bPHNK}52125i4EwFDa?&fRi4LTlS{aNVF z39n{-Uf<(tU(=AR*XvPF(V-MFMNidncRG0KzRV4M^dM!yMc53%EDQ7^ zn&OD?=((-j9J`vYQEsx|vqj&+rip;FiK8=!Y6yn-T2lq7zVgE`@!Pe+q8Dauy_|rIv%g&OHl@@n^fF5a*qYE zptJ=b($knBa?NB0DJv8;}_9)ceUWLF#{DIHRQ z;YSc39z0CR2%aVX<#+&1n-ZwbNC_egJzzA&F*F5Q02329kR!Z-a_VRuSegc#BxCbU zMud5l#QnWe4#=Q0^t&(EhW)K4QZk_8AJ&$_+Ua%7i{G?%{(kE)e@C_PcYG^;E7qm; z2T;YjUdiI}t&oUJJSZ-M4~Wa~cW}Ay&cqae=(*a%HgUB*oP=vIH|gP2KKtLnWU@Jt zuOyDc|AS0?I2ljPi5h;-vT04ti8hgng_Fb-+;J3YPE5reI;|W;AMTH*2g&}22l9>; z1cyWn+R>ezf(m13v7AX{7pJ0u?!)`4aN}la9dD3KMod&k6z%u&%Y|sD)SgY=As7ic zx+gpK9{n3$6NAAN*EZ~rEJOTGeFv`zds?RA{#wM$$w(&5oWl8n+EWj>3YvxcSoHr*U)v+IUNw`Zs`u5R%zPB~~B^<4N zi{)IWwvvPg4>8GBLH@JpU_?vL$PLI%4tzT)FxeKkF?nvjYhLlf;swxp;oVYNBGr4D zM6zlj5w1sJg`A22~Y*j7!H5tI{mj2jZO_&pQ*1`!A9@ z?w$7~cig+S**oqt4);~}tA2QIKXsqJ`S}6z-uA+-^X?eQ`v?E>@~h`V`i4ix=;&bnagG)!>iLy<^sZ7N7H`AO3Ou$0u#}-#GV@%;3?w1Gikg`Q1C-`~3bdTWC4zql|<|M3szhZjE9 z_F#I`oJZG&<&M(V)e^&56YWn*Ad*Zg&hm{ZbaNao^_xx?*Zu>=z zQ_q|F#EU=ubfWXI-&I!sD)G57x3x@kjNg3q%^&^Yo)_Ny^Te%Ref4T-W1nR&|NP*@ zD`qVCAaDEg_pJYT*Tf%eFUa!dypsC-kA5}ry~WSmlbQRA^MC!?Jrgr7+VSD$XR@v= z)o-3yI=6Vst-mg~rSG#P6aV<>6~S)L|G=O2WzNKmiH{!2eDsn}zWexP6IVX*;K9vh zw@lgn?%WBBzSHgHi{>ozg}=Xg{P({YH{j+)gC}3v^hEZcz@D*drvAq!?W;d#y|Z>+ z(s{oQ{pq&H60&wQ=3d?XcmL@fv;XpOj++MVy=ebcpH9DPe&%DhKi^dJ_M>YaduYQ| zAG&|j=cluOl2g1fW!T{92ZxUyIji88uiiNL&SKjmP48`8`DJLw`9J>i+WAG_&wjz> zyRiSvZ(JrVT-O63^e7?$n;fZm3NNO?Y0L z@O*ZzQ+o^Rtosn!@ci0Tr#2U`a}cytr#AI!r}j6%wjm_r`As|@#r+Y4 zfq0&o<-Jj>qQ`|$h zp-mCjA3^6yLc_+3vqXu;;u&ATEtCTt#WGGbwTtyANrXI?gxm|02k}; zF2p~AunA!|LI}YII{P9FK^TW{4Z^hu{JRC$yAU2i_$9*g2rnb-L-+)t9U%#IN=LW~ zVG6=Lgc^jK5x$3T?;XH{P>nDfAsgXhgk*$v(E2vQW`xHOevH7sGSu}}gohBeB1}Mh zI)V+MZ5?Pp_#MKJ5pG4e0U-zBa)f>eU+9o+gl7;QLbwf~2Em0e8et$pH-y&j;ZrpT zPa*sW;Z}qyggFRf5&9$85IzZL+N%gpAp8(vIl?@Iu?XiQ90QGSBg{bHUnA&Tj!=!j zzbC_tchTszwRQ-n7Wwjw-*@LvcIA^ZqoEy7X+H^MxGYY?0WBM>qW&PC{f za2)s!BYceT9s>W)jnFY7ay`@T|2NycZ~Uyf@)}otEk4iDFsJs$@*2o*{Ot0@`QGyS z6@{hM-tye#MVj`DxEZyzH+t(____x^8nU=tBo%2FXp_pSybT_awIi`XyE|@rO{J$2 z-vcRF(cmdxQsDM_%4(O@5ZOH9pIlwrQ2y=m75MUlxGgBJ=Ql#8mDZH;nqOr4v83+%wr* zUteD1iPTtoDh^*NSyJlBL-kP*@P4aXb%UqAy1WMWRm?G?w84{GUte3VX?I9kBSgNE zFr#)+Y4w!)@^WoBA#+e&i4J);#W7(< zEOMjne@`gEZRMgRb@z)EP}2k zj(Q?_#a7v{thBn$U5XagCQfuXizm%WIBBpsoTGfTk15 zleny|zOu$s0l#y}yhU}a3_c^HJ&h9UN=g>hF5wr?V0XrqlrCvlTvEQg(xYh~Aa&W| za?g^|%9_&p#VBJhVj9XlC8c$BC7u;^$Z@w#!Y+!FnNs6Q7Ri|BCGHA|`3;%V+fYuq zahKPF%6)Mql@NCjvP4_buyj$qM@rn^ZA;3RpcqXXqfKNgiuaT|S?hW8wM$S!ZH=eC zwz>j59i2GaQ$4q48CpcE@jze&iezo_jDqPoQ|fD%6rf>i7HhTD<&nlI(kADXpb<;t z=XFY^=ah)P`cRzsB2G>v(k54zHZ&l;L8NRkrN~U^F_jSa6%aY_+XDeur(kl*kx(1? zb`L(OQ#%8Yl)VV{+#2r^iG+#TY85}ZzMP){5}*80*+l72DG1PIP0lIzlvY+3X&)*G zDkTF1Qn^&6hzd>hR+cr8C+rUb_kAkesB^=j`pP;_Wv$RT+f3>53q>SNmD@XB)TyIW z&8VzFr}|RmL82l?d}7E|itp|5Yeu!I1w>aYnNhpAWO8jy1)2et64R&xN@kZgpotnv zlx~|ROJf~DgzSw$RhHtq_==KZpQhcU(kJ5+M}h~_<>!td`-KKRB7^4kLY@m{4$=7| z#c1y`ZuhyRml985gr?93vWZPJ(Vkf<6^Sdv$|$SvgZyQ-X6JxyEFRVw9Gr3nfnVkA4Z z=tY;XyqwAgs3mie+N&xb>OOtu6p<0=Ok9-xqq8=-Ugn%!TeqU)!(6{(DYMJVp+E2e zFiVxSn^j4o40dmJbM|)<_}?lO?G~Zl(gX#$bLP4*;!Ev2Qk(29t#{RzFD=rp6!#Rw zBJGNJj>S?UinO@}xwF-1SCUvzU0zaoA7s?0n&GG40j?ehanYTJ(;!MIk$0m#{S9yH}ZIGHp<&$f@ z{3x21L1?R`GQCV2Vv3#PhL$Zuq;{CGh6ypLfjvqX6Db_w=Tt7bQJ}>_ae>?}_U)TvKqp9#>pUfAQL8JV9};dJfmSI;C^@1r}5n z@l7$}v0S3;460girxy)WPB4c4;U8#Z!NxuR|{MoEnPs@1G*OR{4$>UX5$R#^|+3*)t4ek zQ=JJ4TgL>I^2?hIz{f96%I`X+qX8GFXGB642;D-I&gAK90ffD185sPb2YkDasnB^+H+XmHdJwzie1-;z9x1 zYYx6b>55Qyxt0UI%|K2H;N{3W12tGIa*>mD;2~?h7^|8vEVZA3qEqMN<$#`q@~d$* zu#ivkJBIucBfoxFhLZU~Thcx&0?F1V?W+CxB){%?qB>OJi==U=9b36f^P;^yrrK*U zX#XXsv9=;(W})V!PRa!3kZrp{d{eO;bnx$L&EBc3=+-?^+Y(0(LL7QgDaukcH6$HA zBJ>e>vIHgk!X&?lX-KICUGSu>4 zr7xQ=TI*jX{pdXYHDiVJq4P{tD;5yw@2B5?&KkLPz=IC=!T&|S`J)J{eggWzk-?Da zbjT^c>&Y+J%5TpyZUH2|URxNsTY{8p@t=)0;dd|HqCb{v69hyyVyF$6#xHtK5V^AP zR1d5ZAa(p-2)XM4?o$V(1D4lv+))Claq2~W#r0uG@jwzN9|l$GyP(9xzx0keRG>8Z zC0 zsTax3Sn??`cZFBB6{VVWvr0F|3QCSqqLTY`AzS<&Y&vSp_T-m)FOA4mbbnBFqZZ_M zgcZMxo+0}Wzy8Xvzx|8MtlqQ~Bjze#4g~t1l%JAEk>Uiqa=iR@mnJ zwlFbCZ76#{*HTJM);@n?ZA(ohr8-&{D~ak#s@W5}rbAL;@GLzdb+jlX|0sF1BMiM@ z*4vZ?Wj#y~Iy+nNlHXdc$0$IWrM@5?R%oRcl)f=Io!)`d;HMSuiSn{7W%*`{Qb`xq ziek#Bk_wi1x-!99V(kog6&8+fv}-ul4uWoCFX&pTq;@rrN0e>$rLTw2sB8ZOH3q5v zp)i`cefoWbx|nq|q{6CGXm_5d#{bHGB=wi-)yi%$_+hnOzWMP<_G5E-st@zpRX;uz z4JF%MYFA}*$Wi2U`ZL>Ae&4(Xy_ohMEh%gN>)fw9=Td#N6uG^?S*daKrxu8YrTo(# zQ<}h9SEW&{6e#Sm^PEfvT#vpb`{rmodec|p)p2AMiQ)p|C7s9b)%k}BWysa)1`ICv074kCFt#nKL*{( z^2SQ$H-Jw`fq5jhj+Bae4N_*MpB5LTQr6(~TSnHGEpwHnWgHzXBiqKzvv0Ocqi(D4 zu9Fno{cv=Vb$h%Bj$wg`ZckbcSLD#=p z>vImM^u4?{&r;>QnDeib+hXb3cwq%ck0PD1{_h(A+4%OFb?_T@0E?$8^A( zM^nb+Al~R-UD4qLRGrXGoc}J5P!hXmDS9sH;Ecgbqm53DFQq7zeq8#A>7|t3fFar? zG3luWZF1%<=RXGL($SMTbFy4W7k!7)n@}qFtGqsC+OP;`4XVHSJMxhBmlv%r+taW; zNRO-!$F&=wvm|e&R{i&}s+3m#_p+?UYO(ApHO@qjGRC+y1Nmk`+RXBzS|)mA;{21f z=9nhOIBISKm-B1kGV;&qsDqrh84?|fBKn1BkHw&WFxe6G|J# zp0`MuJ!4Inb=nzg;*2#x&G&!Xn&{d)dvC`$Y4*#g5iHt%=xU_lxIzn-^U;5FjA3hW z?@;u-xgLYh%~O94C~{&0FKzmg&xSA-}!^%u}IAfH1S2#k*HPz_x?H|+r)bSi?h+q0l>G?iYtuA3t-Fy1aTPHg+GdVLeIpgQ-tX->5qFW7} z^7S>F(zcsB>Dt-C&rJ_sJ-k42zTCB*rS9y!2F3L}?y@uEIAdL_-I)Jd*13#*eCAcQ zbhNnq|HpSqoZ1L*YEWm^!?-SYW(*P%9>9JTA(PKePAYjIKX3Cpa@F=sYJl*`Ddny56d5tvXg|cf6~o z(|We_t1rUNi&OQgmt#i>PYiKSNi08$^nXZ<-{dTS{*ROGc5}i1;zCX2>40i6`jki3 zu<7j~r=?%%5|K6uab;1fL)?W!uM6j0<-lc}zqtYNau1VSQ6NRicVp%_cVo(xbM9w3 zvue&gfG3_qa0Yp@QxiO4^sk+noSAc+nRC#d{l7EkIGJzibjE@c&pdEew$t1r7aF(} zr~KsU&CatAb2{6_tb_S%?Duqx|(nO!#G)5baKGGw{ zlf(5m$rru;&e4hIOx2!A!_y>B6`#&JfpHG@^i~Z1eb2d`87;Wi+t?kg#$aAgZD(>< zJEe)!*rj=9kJXtyR@8g^%hqD~sLXmgT1y*qlQ!g)dy+frajpbY+wpXr8WFg6oiXyH zH6i4QaN7Rry#q?`ca4#!khlZZ*iTHX#$1DE%ufX)*K55Psklp8;bh)unvME~P~SO` z-Tu_v>fEZCR<+w%?bjt2N>K(+b$i6<5xb1^$k<}$m$5I(Ttb83qFlk@oQ3aY=?YSw zHX?5bZ@h0naoPF?165PwjJn+4Z7w~!er9^r8w)5`J3HA1@{GHZg;;Y}E5!eteL((1 z@AWb)1cSHVoPA(e2h$;Y>ZFCT4${xq2jV1b^m{PQ*av6q1Noj9`p(Xr=%QVCs{izV zDLo^7VTNAo>O@!9_JLJXcV!`*ihaQIWt=U^IpaUiLii@_gJ_!7xY_y*+6SG>P!@tU z#b`zID19Ouft;|ysx!%_VF?;F~eD}?{7dHOdw3U{Vj zjl-M2!ErcNDQYCPrk!jgKJn~Z(ay8kC-W^>%atd6$yxqA63K78X>qn`=Ff3c&HYa_ zo8`$0!tzbFazs`;f*8j&J)RwqYssBgwkhXQp1A+jcxR}pBj1lB>u5g1W~TGhs8smq zuQ2D1wr@{VQ-yM?o%$0m&QCIdG z`JIE-H{J&*;bWairyTGd8QgzhSb*|faItVGPaOS$io5dNB9cqiUCTxt_twv1va!#uL}bD5Fu7 zVU{^-FO@v05OTZ(+>vWa3vhMd9ktvOVb)6OWRCI%O7AVXdV}zEn)huOG_3oFX^7o> zWb2$raumJlt&;M6Q)*m{=89bRl z^&L=h?~mfl5WvV85A`kgv8WYPD;(E$CC$+~_X^DI)tkpV=QsPxtyHUiZuDhEH}^Be z;t#pZda`F39{5wm8cR1RjOp4%CK)sHW;*&X-+rfBQRy>Eq#E~zfnEj2bIsiAq}oo_ z(tzgN)v6z5jrc}R^BXZ^mBaV9X&W^igvmdDkP zuHjjqXc{RS^btqb=h{xNa>o=ks64M`-7yWiMfDgj~GMY24pFU_OK6uf_*);y((Irx@T)@(U`xr!_MpZWcP zst?@Tsjkw3P!dNPZV*tnaWUzpOPRb>>!;eCezql`a_xy5>e~ z07|{#$5(w?wp>Hx&1oDHsT0k4WZ&-$N8Hq%Y<c)ky)n1rFN`w*AQ(y*;BhJpLEVR1vvKq0^m~LXP6A^lnT~aO1>O(Ra=sl9Kb8; zRUaeOlmpg{SH6Q;@p&Nf%mh~U5rtLZqa2?IA0>rao$Gm?4vctLKJw)vjl5;s5lVg^ zL(-XwE(Ou0hhkH!Z2O zbfg>qu}^?Wj?ctb2UPzf6~^8xVl!I9n7>I47Rx%4aYmb%`7c}DOtV2B?_$#!>(nBx zAyOH_n9UK2B|37NI6q4>zZ?r09}WyMNQC zlB84(vGgi_oj~p>`QNrLKlBn( zf2s1i(i7%BaynXq-=~uHlSMITscDs!O>cl2z2%-UgOXFhW#qjDW52{6 zy?@m#31)s%dX3Vkow}mh%1Q3mooTW!r0pnYZ1PNr`Uc4yjHhNz$Qglrd#2n^S&cGj zr$`%NnHYaK>rooX>|bPF<4ZuY-sTaGwzcewayJ+?wBZ>pg_Xj1V&VK%or+;De$#~7 zo!?ktO_evz%r|9;n@4zAj`YxQgr+rb)!ru>wVX5tGQ=M?>|q4$**8ZO1^Gik#;_5 zU=Kc7kBKGI>~E4Qk}~2_@~rSqhjh@Ym8%sh*35r$%Y_;l^^%;CvTR74+@%?-rHq=$ z-WIFJYa0En(w|{oD2(2P@FMT8Iy{ZozUrUo-oM=rUdrW6pR&r^0|IDjvUg7;^ zDeNoM4Te-xudCcfAEHc0OJO=7=ZGFJspYTANl!N4?M*Jo`8<71(tBkd^~<0qN&w3f zmn=h#iZY(O;a9knhSGpPmdyY2q>D1X*`gOorFy@DxyG#dKcf3oiyO0I*)pf13yfBf z^DMU1saO{V?o-j2a-PCAC9NlKRn>a(qa8!~yLiy@sf1|&~=qsyGcR3m; z`&scSRtYB>msLxt{8nDGKgaq8WM{uPsW*>`mx%F;-!zaKz-mja1^q_P{>STZGMVYB z9xwk(>7X35517xJe7#YEy6l~Q>$)guruLF!Ni2T=}c-gwxD} zXt-E-hb2b)OlN~qHL}L?nVQon%j`L7{e{o8hbft|cXTy=b@o%8?5!PLdqvs$Y9ul& zR9YF)Z%5_KbB^e_?9GM`B-VNAe|+ua)V^zF-bsDt4XkIrG{rtdId$S!dX~5jeCA72 zO8foeS6Ys0Ei~GS%DoO<*S;(+*7(G4RY;p~ve5Bre#?;{2fc+JaKyq26X zuqp34{SwQNBYKn~zoN$>>ANzIJI1aBa+J~(Yb+wI3z$+)@F;A zGqVSm);{|J<*Bp8nY{;A+uNK^@xj`s{#o`CTO?XiPk+COA)|&5_vBWMggs-+oUvuj z*fM+<+CTIE>)JB&pdUNye5J`~#~AB}#{M3*XRNn18hUqmzi2&b%+}5O8Dr^DY8yp? zJWVXUfbyPtQ|WD$Dt+16tx2EU z1Ig88j_GPYCGCRA!dl=S2RTlZp*+bsuHbOzF8BNLO)nMrm-Y!M<=#i`@i(llQWJ#q z`J1UAg=Q;Ht`G3U6k$oDb+1~qZ&rGo%yyjNWsTPN#(s(DQ@Gy&gC^nQ@035YYxfcon5$>G!&p%;q!Mbl#RFY~U4N|Y^mWo<>ZkF|3fvi7m4 zmiS}ireD%bzuNsi1pFYUWvj@Q4#^?41Ee!AR6SysWR^F|C}l>@RICy#`=YsZ6kd6q zE@WCt9kD1bu_oqRCo92dZ!3PT9a3I9mtdBWlh=T)A?GHFlSU6Q=)A%tcPGLb-LH+l z%UKP}QeQi!mrh9x?M3p%fVOfaS`ts(D_DxGm0Z`!0hf$#!gp1Mv{}^*N3H*|KHR@? z18Ad{OQlQANPiZ0^3V$>>p;&YrBUf`IeQ{CX6cohV+g*ZPOdn`?{M)uRQwJSJ2sR? zrGB*XBYM=3`@*F}ev>t4$@Do=>&aa-tS48Lc~*}aNzUx7Gg|H}^FlwnBkisBg;+V$ zb&a@FPOfmv8J)R2LkmltBXzzkfvYt7XR%0bc6fT2aM;4uZv}7VF4yvT!eoUgOL5ugV@%_C zY_$BWsIfqOaeh2#HUHui>~p`hLTucVH)ue56Z5t%cv7#uQFqNxAhC2>}J z!7P950VitJQ;|T*6L)@DHOV(Eb=GnEUm=5LsgstUq+*ao9@9-bO{9fvmUO9wYhiQ9 z{=(VR*VV+Q-kZDPmR#kG)yJ*eX4^`N>!7`?wTjk$(Q?54tV*Din0pg@YHZpHgoVmE z^G&#ua{@WyNGpw6AeQE$Je%cP`q)oJimm!9x>kk;BE7j7O=+P@{isfYT1O0HreUqK zVe51?pG?PjUXGu&xEeF|NwA}@MwlGA=0vW!Sji_%az#LTP-SlGtjr0yOxFehhmvEy zrP-1vOka-sZma+{PB?* z@;XV>G*YC0T2f zd)l0vhn5(s72}t((3x}dfRPfYEE%>N+sBZ~OOU%W4D+3yw2+eFC#8h8rLyF^rX?Ec z>rr!ZzC@Q3n=^#+2!E}#b1%XB8LS*MB%W)S)mkinwzMi~t(!F@pOp`TJ(*(>)9E)b zyfD%FUbYO&+K#d&i{3Bi+UBv4y_9|z;-@E=bKrFGrYK{^t!y<(m3i-!92H5^iPzYP zf!<|$eWX`e={a-RR$sF`4Ww$p{Rk(j1O3uwTxu@Q`ajZL^7`Z}*Yf=WJfkH~j!VC= zyjMQmbc}rT0%Uhs0!zTPhR8W{IZ~Z$gE|2~PzHNhTYPFD}_X2QkNf|Uo zIMoKqW|lJ`N|w>al!iHw4>_~4`tHo9&p7k%>g*r=Slm}I9GK;K2<2suT_Wv@Qz^6a zDZ^4wOKGHshqE#%ag+;cz4C~*ingQFh;OvIT(6K8PV8?@NWE?RQnPk-4p5$jqZfj- zDISs+r>Q}?&x#{Q zZ2e&NGO960@`jd!^rTUfs}f~xhITx6UB0wFl;6athxDFyMU5Oc=slvBM)g#su_%#p zmS=C@PRP{yWt`~SE=C#Lt7q_n zGO`#H%C`>45%EN}f$A%4mCk?N;Sr&JR`12{6ZNcUrKVK&X)g;5ZD!--R}w~1%Cdp6 zZ>N}8ID1DyS+SlKWld8y?IMmoCVB$oC}E~j*0pz3PAlH-B9$^iy-=q0iF%GjX)g*jo`mA4NeUkNgR=@1Q*`uM(nxrFIRqXW%gCBtCAe4j`JKDj?s=P z$4W=g@uK69qsPdzM~)tuJ#y;EypeZ~+%@v8k(Z7dH){H*!cj{`tr&IhsFy~yjXG!a zwWE)Yo;7CCnB`->H|A$!{xD|$n7@umADch6Xsmne>apv`ZW{aP*woAmGoQ$OKePKd z=eQ~37LVIB?yYfu9;bo+aanV-evtKAR&w@*+1F;T%YG>P#q2fX*Nwk)!Zj1-PWbMG zy%V}m>@#ul#QceOOx!f_rHLO+{QufH6L2WI{*TW_h?Ff`lC7tNbI*Ox%xzXesfc7L zNo3D5n8{WuQz>b)m3k-@g;u1MN<^qcMCnPQKT+CO;(y<2O7%SN`@Glxy59HyURN|` z&V8SA=6BBT%p~f)5`hGEEH6tIhS`pPl87LdIK)ujl zGzl$0PobC5hv-LC-PF$1$<*JJZ+gzO%2bj$nmL}yVs2nIG9NMJS)QyA)@oJ~D~olI zRmr-`BD3Y#Q`vycV=rZ=u#dA(v#Z$MYy*xf$A`0tQ_MNf>Emc{gSqFqwcID%@jNr$ zYF-xa9Iu`Cgog!oLJ8J1b>IoMfD_<0cmn1@?oc>&C-p3q4C}));Y9c}dtU74ZFuxGe4R$v|>qOq7C?w~KxDOgKduog_l zJQ2yvWU8?oS<6{lSvN7OUD!glh`kZBc{gTrFI$QOIIB2KoF^P@F2uFxdUFNbbZ!av zGPi@rKpgl4j3GP7A1Z*Vpj*&8NQ;V6 zH&T&k7vYVV1Ag*bLP46ym&#p za9#{=4Q~rCjrTk67_W$TmUn?y&b!LH&b!5H#oOvZAar9-fh|}7mVs~(308tV zAR8RUJXZ>8K|N>&ZQvF70EU1BqyVWxI*>Uu6PgFPKpv0}v>7Ues-Y%oF{ z$V22gGJuWN44O6FmcE1@OHZQjq{}dj7^Vzs#yo~AgU?up^~EkmA>$I`I-`fdM$J(V z)E5mx!_g>o6}leXjHaNw(S2wxdK^8CUPP;Zej zp|B8M1FwU(!>Moar6cHk8kafs*Bo)a*vXK+mXvW_0*UzsW_|*fydf-%iBtoNsw4{XL7&1v+f&ly_Weg>W6rzYhB2uzkvH1i% z;)*=bCld)-+WWQEXkwBa{%eOV7x9Cc;sXR+Ox)*!Nh9i{D1`|?KwRG$bGe#D1q6wbl6m~ z6Gc*y%7Zu2>QQbbx$Gf|=jjFb>Fn zwn0iKfj5l_>#6`K(LQM=ouWU?cgH_<%f9tgaZ*#XJq?`5(Kh{D3AGxC_@qv ziG*_jv*T>}IZ7(|o38s^m~*i*VA}%C2hrVHnF|ck@47XvJn*5_aI*rveBgt(WWo8% zx#=?F<=WGF%})022u{Cva9ea;$)@BL!5ONO^ICFL=TnK3)jYe`nYUFw@2(k)Ji4T1 z&x*|+{Pk`#N&~y(*WBnLm#>8CPWw1j_CMGq)hTf8-+A|@&4rVvD&JR1 zGPA93+Vtejn7pVid0a|{%AvuAqZ4(mXdNjFdsZ^Ll_+i5mab{!cP@7m8LpmJ{ElQR zoozd~#{EjORe7vbJ`uhKx7Y!d&LJJDaqlYD^AB3o-ZcG&mxIBM>&`VjYOMc5<9O-9 z6{+h1k%SF2;sFtv21H~CjKR8ef;^EpM4^yLqt*afJgtbQNq_)U@RUBDk_9r7QrG~N zkQhal!us+een*=kBA&t$*mxuEla%&|w3>7dUE3VzoWNWccs=;W|Da_Yw<< zxG0KmIEf`Cd#q;MCfsgwtR`Ge!DUlip2dZxkw@onnJ^f8;MeijxZs0-JpsRhmlq4) zc#XJi_^}13%<|m&Vih7iRG1&uujZqrbX{tUUaIouErm z1uAQllnDxkis^;BMm^PP+ke4D&d`r^9ZbMIJcgn|QSvgWZi`M{QLI~YzCF7SUe)n1 z^CO7GZ%?E|V2g7!i=KtVQ*j?-F7^r!g1?InCgP9lmyj^a5)TN*SiYZ2B7e7Jq@Yw$#%Ib5HBY}S z=M2^xY1zL{z54!BpP+SCvBv8+Lpj^aOqDcRo`z{GOYi?^w%amq$`~j2s57Ut*OrG8 zpZl6UIu?zz8`_fh8U0VI<5Jb9P11?`^KZ*uwNt;aOFt@LS3={$g!^q9pWpIx7v^Lh zw8UtWhtVeK3vJ|jx8FlCz}h)0&Tb%3A0 z_VD=f4Ds^Ow%~jFXwMJu3keAE8-X8)N)_`4!w(HmsdNad45JZ<{NAW9_>oeSvJLR^ z5AgF05mRxLqO6stkRPJ$;29d=Er7IuCT@(3qOz+`INveACtQ23k1&!S9w^YZv9S=} zC;!ELEZqHLTpd%{QIWFKO2$}j%BD}jW(RBe^X6`@i_f^&{_sJsbL zL`L;$kG-$08n#YItASNCiVwJ@u6LS0if*g1AmhnbCbuw0b>$InHpF+YFPSLmdYiCPps3o_EM-quFtX$7iQsFSZ?gz53%> z#&LF^FiP&IUP!5%vUMOaumEMX2-B7F?4B-gT{c;Ds$V>&h?VZ<{2j8B1^nB1yxWgwp6ZF5M8yN(A4sA<;2Qbc>mNhm0H*u)=DYW@}8hHtf zr~raIhXdMA`F{v#Kjs$^EAThNL3$n=3a7D0(YGj#gESj3#X$=Lsqq&}e&VP=5M<;P zDliK73FQj{g#7SmENd`U{uf&R9cYb<7dr{#Q4EOsn>?}?mja}COYtE4M?Avg+}ALN zN4YOS?h8?cKg{30pXU$5s+i{3I$?EyAMb2Vg~M8A=eALmlPSzt_1NZB#Ij~IbKadn zuRUijd1c!klc`P_|Ex65AtKAArp9?f+lH?Bi&{E!9A*zy@A0*6FI@fU4g2n^ZL53U z6zh%KU8UZME)i>jM#~KcJ!4;0 zk^G$dTA}os3}x9QT7kwi`5KhkNVW>H54bI{KrLhRQiIJ^X`=%b6sO4)?B@_KdMC0D zuCMW$>k=I1-jQSOYr3Q2{k-08_n!HZ997R9qJ^d#Z|WV3e(jhS(wv}El$Ow{Te^%! zpfS_CuWIDXf3$m24EcPG3$`qhyo|vr{|l^074?rc&Gwgd-g1DlYL)GhA7K^rJs19U zSQ&yTI3LK0I)4Q#D`#y7K3|A&YIux)UrC3~+GY}=1slA-m(`D1OQbPUA&_-!H1I9CE_D7wdjn%U?JYOCg>&`E!kN$n3 zs>h?gDaGWe**9LSRkI6_5d0?HuQN79Cu_T!x()R5mSM{}i{ossq4ZYm25CdbvYt=N z)^?3Opb+-vc5}NzOw$8r*Bd>=MgBgE%6JPL;5?e=T%Fpu8u|f+TFKjW8n&vd&L*E# zs53KY*-c+xq?|u9WnJK?^CeA1_w@M-S z^S>tyF@AU{{S#qC#By=iKZnKW;Wd<`)R*-W`B&5*UdrHm^#8vXR0_BghZoCIKoVOe zlZXVcS{%|b5C}ln-vq%daVZQ0oPQYvR?c_;u`tJ`AVLtg4Yz6{C2j+VTL;XtxLNFY zrnt(VCNScl^c`46)ED$~4L?k2{&@}GR+4`@Wy7T-@k+FSTOL4ue{m+UG2K92iuBCO z+pzqHkUUJWS-mSgE*yJSy|U=axjEi#Dgh7UKTf%GKYe|ynXdtCV(FBhzx^>SR0g?l z>zJeQRv*Ic7^*fm$ly$Ro@qfzT2=nzH+?gFD~q}n-WTo691<=NA-k5_AF4|#O5PAE|-QDS{&)Fm!W|n4eEcoMixlO~)-^rE7 zmS|YY!wlt)+94C?TPoR$ocuEP*R&!*vVz&gE3!`&J*JVOLZ1*~Y6s@n>Cn$Bt*hRa z^oKlq`R1C@8@n)yUB@U^{DoqYpFWUxkdO8dWO{j%&!5xyq04~J|CM5TKnL>@MllVs zyFahCJ_~#!@CE1?ygqAsxW}&GhqBH(8T6K$zc73S1JUuOEjle9lVUM$;TV6$j zx#YHLc)->w$t`a0(`gy+RTj4GDb`cIoPPFXaAm%$@k>y77a=&e#K07JrbsS)8W@<_DhgF0`guSM+HWy)2P6+;y{ZvBLDY_O&4*0ncEL z()MEuePt@PTxhrWCOfZ`Uz*LMDtV-R8b#SJEv8irMyrUQXvM-mi~1A zO#J$a^z{|_>nqCFSGX$JfEULXGu2q)3idcDDf5MD1R~`Z*Zxbm_MK4pXON2)dmV#Z zFbMpckh2h%!XU>G6X_qVcD@RRBk>l8kFklvi!5dqCOyU@;;Q}!(1b!;}7g+nV zvfqIAr^@L_pjq|qKmIzoRs)RE-~vF+b>^gC{8VPw=0ln7{gJ|Q(AN|9$K243wQo* zdZ^;fjR{c^srrJ>sfX(_BIZ0@a(RwQ=bTU*LW0)7mFN;11((4k?OG+I#kqt1qfk4! z%Ed`%<^8O#-(LH)D_L$!LBM9a{)G>8vhibs;b)#Or3Oe7PNL|T4dP00*yIyG{1pj|rOlY?I5>*pFf9dWxm4cgezvp@%&=%QAN6HEK_sfA3lGv=6%j7bq>} z!Rl)Be9erW)m)f*#*#U=&)qA+Kr!!>)z#5G^{XDUHZUDh4ycB9_qi4+Ti-AKsC=#| zw)K_5qN_`sl_uTF(K+ICM}cp?_@G1FfN^-cMQzvo(o;7QpBUXR{&?ZM`iK00Vf;7T>2wCVw%^y9shvvWYY=zF*VR$-SZJ9MbM@EMXJ#q z1B>$IyrH|*S7@<&T_ko`b?@FMBFO_02@5x0_D`iHArWx^97Oy(CJcYFJ37Ma zi-GDlAOM;ADD@M6qX*|;?AiSSP< z`ja7%yJ=tcQ2m>wbp!{-z=c?X%IM^Jt4Egvtk$#5g>G5_{mE0um|K!4Fr>zOgahIfZ;?!Pn7 zYpLsyu{=2Oq`PxtqN+fBBSL+w?)&ywp>|EE#d5c+?pc@ANGC|^(u!J&8ZWafcPNXYFP*=7i=rCa8fWjd1Y=}&-B7R=`yrO@%_-&E%Vm1OI#0sNNuTl zP#fhlp4Tu%W-4)_TcB*!dq@3}z5LB-az#5L>&=qyoPAN@ZF?nUt;T5M62;e>w2cy4 Mm+UbyFl#3KFM#Pj9smFU diff --git a/ext/installfiles/windows/ZeroTier One.aip b/ext/installfiles/windows/ZeroTier One.aip index 8b26171cf..5bdebe40c 100644 --- a/ext/installfiles/windows/ZeroTier One.aip +++ b/ext/installfiles/windows/ZeroTier One.aip @@ -62,8 +62,6 @@ - - @@ -73,15 +71,13 @@ - + - - @@ -234,10 +230,8 @@ - - - - + + @@ -259,10 +253,8 @@ - - - - + + @@ -306,6 +298,11 @@ + + + + + diff --git a/one.cpp b/one.cpp index d63459193..d384270d3 100644 --- a/one.cpp +++ b/one.cpp @@ -42,6 +42,7 @@ #include #include #include +#include "osdep/WindowsEthernetTap.hpp" #include "windows/ZeroTierOne/ServiceInstaller.h" #include "windows/ZeroTierOne/ServiceBase.h" #include "windows/ZeroTierOne/ZeroTierOneService.h" @@ -914,17 +915,20 @@ static void printHelp(const char *cn,FILE *out) fprintf(out," -U - Run as unprivileged user (skip privilege check)"ZT_EOL_S); fprintf(out," -p - Port for UDP and TCP/HTTP (default: 9993)"ZT_EOL_S); //fprintf(out," -T - Override root topology, do not authenticate or update"ZT_EOL_S); + #ifdef __UNIX_LIKE__ fprintf(out," -d - Fork and run as daemon (Unix-ish OSes)"ZT_EOL_S); #endif // __UNIX_LIKE__ - fprintf(out," -i - Generate and manage identities (zerotier-idtool)"ZT_EOL_S); - fprintf(out," -q - Query API (zerotier-cli)"ZT_EOL_S); + #ifdef __WINDOWS__ fprintf(out," -C - Run from command line instead of as service (Windows)"ZT_EOL_S); fprintf(out," -I - Install Windows service (Windows)"ZT_EOL_S); fprintf(out," -R - Uninstall Windows service (Windows)"ZT_EOL_S); - fprintf(out," -D - Load tap driver into system driver store (Windows)"ZT_EOL_S); + fprintf(out," -D - Remove all instances of Windows tap device (Windows)"ZT_EOL_S); #endif // __WINDOWS__ + + fprintf(out," -i - Generate and manage identities (zerotier-idtool)"ZT_EOL_S); + fprintf(out," -q - Query API (zerotier-cli)"ZT_EOL_S); } #ifdef __WINDOWS__ @@ -1059,26 +1063,15 @@ int main(int argc,char **argv) return 0; } break; -#if 0 - case 'D': { // Install Windows driver (since PNPUTIL.EXE seems to be weirdly unreliable) - std::string pathToInf; -#ifdef _WIN64 - pathToInf = ZT_DEFAULTS.defaultHomePath + "\\tap-windows\\x64\\zttap200.inf"; -#else - pathToInf = ZT_DEFAULTS.defaultHomePath + "\\tap-windows\\x86\\zttap200.inf"; -#endif - printf("Installing ZeroTier One virtual Ethernet port driver."ZT_EOL_S""ZT_EOL_S"NOTE: If you don't see a confirmation window to allow driver installation,"ZT_EOL_S"check to make sure it didn't appear under the installer."ZT_EOL_S); - BOOL needReboot = FALSE; - if (DiInstallDriverA(NULL,pathToInf.c_str(),DIIRFLAG_FORCE_INF,&needReboot)) { - printf("%s: driver successfully installed from %s"ZT_EOL_S,argv[0],pathToInf.c_str()); - return 0; - } else { - printf("%s: failed installing %s: %d"ZT_EOL_S,argv[0],pathToInf.c_str(),(int)GetLastError()); + case 'D': { + std::string err = WindowsEthernetTap::destroyAllPersistentTapDevices(); + if (err.length() > 0) { + fprintf(stderr,"%s: unable to uninstall one or more persistent tap devices: %s"ZT_EOL_S,argv[0],err.c_str()); return 3; } + return 0; } break; #endif // __WINDOWS__ -#endif case 'h': case '?': @@ -1134,6 +1127,10 @@ int main(int argc,char **argv) #endif // __UNIX_LIKE__ #ifdef __WINDOWS__ + // Uninstall legacy tap devices. New devices will automatically be installed and configured + // when tap instances are created. + WindowsEthernetTap::destroyAllLegacyPersistentTapDevices(); + if (winRunFromCommandLine) { // Running in "interactive" mode (mostly for debugging) if (IsCurrentUserLocalAdministrator() != TRUE) { diff --git a/osdep/WindowsEthernetTap.cpp b/osdep/WindowsEthernetTap.cpp index b2d3ed8b8..d8ba1f982 100644 --- a/osdep/WindowsEthernetTap.cpp +++ b/osdep/WindowsEthernetTap.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -42,6 +43,9 @@ #include #include #include +#include +#include +#include #include #include @@ -55,15 +59,28 @@ #include "..\windows\TapDriver6\tap-windows.h" -// ff:ff:ff:ff:ff:ff with no ADI -//static const ZeroTier::MulticastGroup _blindWildcardMulticastGroup(ZeroTier::MAC(0xff),0); - +// Create a fake unused default route to force detection of network type on networks without gateways #define ZT_WINDOWS_CREATE_FAKE_DEFAULT_ROUTE +// Function signatures of dynamically loaded functions, from newdev.h, setupapi.h, and cfgmgr32.h +typedef BOOL (WINAPI *UpdateDriverForPlugAndPlayDevicesA_t)(_In_opt_ HWND hwndParent,_In_ LPCSTR HardwareId,_In_ LPCSTR FullInfPath,_In_ DWORD InstallFlags,_Out_opt_ PBOOL bRebootRequired); +typedef BOOL (WINAPI *SetupDiGetINFClassA_t)(_In_ PCSTR InfName,_Out_ LPGUID ClassGuid,_Out_writes_(ClassNameSize) PSTR ClassName,_In_ DWORD ClassNameSize,_Out_opt_ PDWORD RequiredSize); +typedef HDEVINFO (WINAPI *SetupDiCreateDeviceInfoList_t)(_In_opt_ CONST GUID *ClassGuid,_In_opt_ HWND hwndParent); +typedef BOOL (WINAPI *SetupDiCreateDeviceInfoA_t)(_In_ HDEVINFO DeviceInfoSet,_In_ PCSTR DeviceName,_In_ CONST GUID *ClassGuid,_In_opt_ PCSTR DeviceDescription,_In_opt_ HWND hwndParent,_In_ DWORD CreationFlags,_Out_opt_ PSP_DEVINFO_DATA DeviceInfoData); +typedef BOOL (WINAPI *SetupDiSetDeviceRegistryPropertyA_t)(_In_ HDEVINFO DeviceInfoSet,_Inout_ PSP_DEVINFO_DATA DeviceInfoData,_In_ DWORD Property,_In_reads_bytes_opt_(PropertyBufferSize) CONST BYTE *PropertyBuffer,_In_ DWORD PropertyBufferSize); +typedef BOOL (WINAPI *SetupDiCallClassInstaller_t)(_In_ DI_FUNCTION InstallFunction,_In_ HDEVINFO DeviceInfoSet,_In_opt_ PSP_DEVINFO_DATA DeviceInfoData); +typedef BOOL (WINAPI *SetupDiDestroyDeviceInfoList_t)(_In_ HDEVINFO DeviceInfoSet); +typedef HDEVINFO (WINAPI *SetupDiGetClassDevsExA_t)(_In_opt_ CONST GUID *ClassGuid,_In_opt_ PCSTR Enumerator,_In_opt_ HWND hwndParent,_In_ DWORD Flags,_In_opt_ HDEVINFO DeviceInfoSet,_In_opt_ PCSTR MachineName,_Reserved_ PVOID Reserved); +typedef BOOL (WINAPI *SetupDiOpenDeviceInfoA_t)(_In_ HDEVINFO DeviceInfoSet,_In_ PCSTR DeviceInstanceId,_In_opt_ HWND hwndParent,_In_ DWORD OpenFlags,_Out_opt_ PSP_DEVINFO_DATA DeviceInfoData); +typedef BOOL (WINAPI *SetupDiEnumDeviceInfo_t)(_In_ HDEVINFO DeviceInfoSet,_In_ DWORD MemberIndex,_Out_ PSP_DEVINFO_DATA DeviceInfoData); +typedef BOOL (WINAPI *SetupDiSetClassInstallParamsA_t)(_In_ HDEVINFO DeviceInfoSet,_In_opt_ PSP_DEVINFO_DATA DeviceInfoData,_In_reads_bytes_opt_(ClassInstallParamsSize) PSP_CLASSINSTALL_HEADER ClassInstallParams,_In_ DWORD ClassInstallParamsSize); +typedef CONFIGRET (WINAPI *CM_Get_Device_ID_ExA_t)(_In_ DEVINST dnDevInst,_Out_writes_(BufferLen) PSTR Buffer,_In_ ULONG BufferLen,_In_ ULONG ulFlags,_In_opt_ HMACHINE hMachine); + namespace ZeroTier { namespace { +// Static/singleton class that when initialized loads a bunch of environment information and a few dynamically loaded DLLs class WindowsEthernetTapEnv { public: @@ -71,29 +88,349 @@ public: { #ifdef _WIN64 is64Bit = TRUE; - devcon = "\\devcon_x64.exe"; - tapDriverNdis5 = "\\tap-windows\\x64\\zttap200.inf"; - tapDriverNdis6 = "\\tap-windows\\x64\\zttap300.inf"; + tapDriverPath = "\\tap-windows\\x64\\zttap300.inf"; #else is64Bit = FALSE; IsWow64Process(GetCurrentProcess(),&is64Bit); - devcon = ((is64Bit == TRUE) ? "\\devcon_x64.exe" : "\\devcon_x86.exe"); - tapDriverNdis5 = ((is64Bit == TRUE) ? "\\tap-windows\\x64\\zttap200.inf" : "\\tap-windows\\x86\\zttap200.inf"); - tapDriverNdis6 = ((is64Bit == TRUE) ? "\\tap-windows\\x64\\zttap300.inf" : "\\tap-windows\\x86\\zttap300.inf"); + if (is64Bit) { + fprintf(stderr,"FATAL: you must use the 64-bit ZeroTier One service on 64-bit Windows systems\r\n"); + _exit(1); + } + tapDriverPath = "\\tap-windows\\x86\\zttap300.inf"; #endif + tapDriverName = "zttap300"; + + setupApiMod = LoadLibraryA("setupapi.dll"); + if (!setupApiMod) { + fprintf(stderr,"FATAL: unable to dynamically load setupapi.dll\r\n"); + _exit(1); + } + if (!(this->SetupDiGetINFClassA = (SetupDiGetINFClassA_t)GetProcAddress(setupApiMod,"SetupDiGetINFClassA"))) { + fprintf(stderr,"FATAL: SetupDiGetINFClassA not found in setupapi.dll\r\n"); + _exit(1); + } + if (!(this->SetupDiCreateDeviceInfoList = (SetupDiCreateDeviceInfoList_t)GetProcAddress(setupApiMod,"SetupDiCreateDeviceInfoList"))) { + fprintf(stderr,"FATAL: SetupDiCreateDeviceInfoList not found in setupapi.dll\r\n"); + _exit(1); + } + if (!(this->SetupDiCreateDeviceInfoA = (SetupDiCreateDeviceInfoA_t)GetProcAddress(setupApiMod,"SetupDiCreateDeviceInfoA"))) { + fprintf(stderr,"FATAL: SetupDiCreateDeviceInfoA not found in setupapi.dll\r\n"); + _exit(1); + } + if (!(this->SetupDiSetDeviceRegistryPropertyA = (SetupDiSetDeviceRegistryPropertyA_t)GetProcAddress(setupApiMod,"SetupDiSetDeviceRegistryPropertyA"))) { + fprintf(stderr,"FATAL: SetupDiSetDeviceRegistryPropertyA not found in setupapi.dll\r\n"); + _exit(1); + } + if (!(this->SetupDiCallClassInstaller = (SetupDiCallClassInstaller_t)GetProcAddress(setupApiMod,"SetupDiCallClassInstaller"))) { + fprintf(stderr,"FATAL: SetupDiCallClassInstaller not found in setupapi.dll\r\n"); + _exit(1); + } + if (!(this->SetupDiDestroyDeviceInfoList = (SetupDiDestroyDeviceInfoList_t)GetProcAddress(setupApiMod,"SetupDiDestroyDeviceInfoList"))) { + fprintf(stderr,"FATAL: SetupDiDestroyDeviceInfoList not found in setupapi.dll\r\n"); + _exit(1); + } + if (!(this->SetupDiGetClassDevsExA = (SetupDiGetClassDevsExA_t)GetProcAddress(setupApiMod,"SetupDiGetClassDevsExA"))) { + fprintf(stderr,"FATAL: SetupDiGetClassDevsExA not found in setupapi.dll\r\n"); + _exit(1); + } + if (!(this->SetupDiOpenDeviceInfoA = (SetupDiOpenDeviceInfoA_t)GetProcAddress(setupApiMod,"SetupDiOpenDeviceInfoA"))) { + fprintf(stderr,"FATAL: SetupDiOpenDeviceInfoA not found in setupapi.dll\r\n"); + _exit(1); + } + if (!(this->SetupDiEnumDeviceInfo = (SetupDiEnumDeviceInfo_t)GetProcAddress(setupApiMod,"SetupDiEnumDeviceInfo"))) { + fprintf(stderr,"FATAL: SetupDiEnumDeviceInfo not found in setupapi.dll\r\n"); + _exit(1); + } + if (!(this->SetupDiSetClassInstallParamsA = (SetupDiSetClassInstallParamsA_t)GetProcAddress(setupApiMod,"SetupDiSetClassInstallParamsA"))) { + fprintf(stderr,"FATAL: SetupDiSetClassInstallParamsA not found in setupapi.dll\r\n"); + _exit(1); + } + + newDevMod = LoadLibraryA("newdev.dll"); + if (!newDevMod) { + fprintf(stderr,"FATAL: unable to dynamically load newdev.dll\r\n"); + _exit(1); + } + if (!(this->UpdateDriverForPlugAndPlayDevicesA = (UpdateDriverForPlugAndPlayDevicesA_t)GetProcAddress(newDevMod,"UpdateDriverForPlugAndPlayDevicesA"))) { + fprintf(stderr,"FATAL: UpdateDriverForPlugAndPlayDevicesA not found in newdev.dll\r\n"); + _exit(1); + } + + cfgMgrMod = LoadLibraryA("cfgmgr32.dll"); + if (!cfgMgrMod) { + fprintf(stderr,"FATAL: unable to dynamically load cfgmgr32.dll\r\n"); + _exit(1); + } + if (!(this->CM_Get_Device_ID_ExA = (CM_Get_Device_ID_ExA_t)GetProcAddress(cfgMgrMod,"CM_Get_Device_ID_ExA"))) { + fprintf(stderr,"FATAL: CM_Get_Device_ID_ExA not found in cfgmgr32.dll\r\n"); + _exit(1); + } } - BOOL is64Bit; - const char *devcon; - const char *tapDriverNdis5; - const char *tapDriverNdis6; + + BOOL is64Bit; // is the system 64-bit, regardless of whether this binary is or not + std::string tapDriverPath; + std::string tapDriverName; + + UpdateDriverForPlugAndPlayDevicesA_t UpdateDriverForPlugAndPlayDevicesA; + + SetupDiGetINFClassA_t SetupDiGetINFClassA; + SetupDiCreateDeviceInfoList_t SetupDiCreateDeviceInfoList; + SetupDiCreateDeviceInfoA_t SetupDiCreateDeviceInfoA; + SetupDiSetDeviceRegistryPropertyA_t SetupDiSetDeviceRegistryPropertyA; + SetupDiCallClassInstaller_t SetupDiCallClassInstaller; + SetupDiDestroyDeviceInfoList_t SetupDiDestroyDeviceInfoList; + SetupDiGetClassDevsExA_t SetupDiGetClassDevsExA; + SetupDiOpenDeviceInfoA_t SetupDiOpenDeviceInfoA; + SetupDiEnumDeviceInfo_t SetupDiEnumDeviceInfo; + SetupDiSetClassInstallParamsA_t SetupDiSetClassInstallParamsA; + + CM_Get_Device_ID_ExA_t CM_Get_Device_ID_ExA; + +private: + HMODULE setupApiMod; + HMODULE newDevMod; + HMODULE cfgMgrMod; }; static const WindowsEthernetTapEnv WINENV; // Only create or delete devices one at a time static Mutex _systemTapInitLock; +// Only perform installation or uninstallation options one at a time +static Mutex _systemDeviceManagementLock; + } // anonymous namespace +std::string WindowsEthernetTap::addNewPersistentTapDevice(const char *pathToInf) +{ + Mutex::Lock _l(_systemDeviceManagementLock); + + GUID classGuid; + char className[4096]; + if (!WINENV.SetupDiGetINFClassA(pathToInf,&classGuid,className,sizeof(className),(PDWORD)0)) { + return std::string("SetupDiGetINFClassA() failed -- unable to read zttap driver INF file"); + } + + HDEVINFO deviceInfoSet = WINENV.SetupDiCreateDeviceInfoList(&classGuid,(HWND)0); + if (deviceInfoSet == INVALID_HANDLE_VALUE) { + return std::string("SetupDiCreateDeviceInfoList() failed"); + } + + SP_DEVINFO_DATA deviceInfoData; + memset(&deviceInfoData,0,sizeof(deviceInfoData)); + deviceInfoData.cbSize = sizeof(deviceInfoData); + if (!WINENV.SetupDiCreateDeviceInfoA(deviceInfoSet,className,&classGuid,(PCSTR)0,(HWND)0,DICD_GENERATE_ID,&deviceInfoData)) { + WINENV.SetupDiDestroyDeviceInfoList(deviceInfoSet); + return std::string("SetupDiCreateDeviceInfoA() failed"); + } + + if (!WINENV.SetupDiSetDeviceRegistryPropertyA(deviceInfoSet,&deviceInfoData,SPDRP_HARDWAREID,(const BYTE *)WINENV.tapDriverName.c_str(),(DWORD)(WINENV.tapDriverName.length() + 1))) { + WINENV.SetupDiDestroyDeviceInfoList(deviceInfoSet); + return std::string("SetupDiSetDeviceRegistryPropertyA() failed"); + } + + if (!WINENV.SetupDiCallClassInstaller(DIF_REGISTERDEVICE,deviceInfoSet,&deviceInfoData)) { + WINENV.SetupDiDestroyDeviceInfoList(deviceInfoSet); + return std::string("SetupDiCallClassInstaller(DIF_REGISTERDEVICE) failed"); + } + + BOOL rebootRequired = FALSE; + if (!WINENV.UpdateDriverForPlugAndPlayDevicesA((HWND)0,WINENV.tapDriverName.c_str(),pathToInf,INSTALLFLAG_FORCE|INSTALLFLAG_NONINTERACTIVE,&rebootRequired)) { + WINENV.SetupDiDestroyDeviceInfoList(deviceInfoSet); + return std::string("UpdateDriverForPlugAndPlayDevices() failed -- unable to install driver on device"); + } + + WINENV.SetupDiDestroyDeviceInfoList(deviceInfoSet); + + return std::string(); +} + +std::string WindowsEthernetTap::destroyAllLegacyPersistentTapDevices() +{ + char subkeyName[4096]; + char subkeyClass[4096]; + char data[4096]; + + std::set instanceIdPathsToRemove; + { + HKEY nwAdapters; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}",0,KEY_READ|KEY_WRITE,&nwAdapters) != ERROR_SUCCESS) + return std::string("Could not open registry key"); + + for(DWORD subkeyIndex=0;;++subkeyIndex) { + DWORD type; + DWORD dataLen; + DWORD subkeyNameLen = sizeof(subkeyName); + DWORD subkeyClassLen = sizeof(subkeyClass); + FILETIME lastWriteTime; + if (RegEnumKeyExA(nwAdapters,subkeyIndex,subkeyName,&subkeyNameLen,(DWORD *)0,subkeyClass,&subkeyClassLen,&lastWriteTime) == ERROR_SUCCESS) { + type = 0; + dataLen = sizeof(data); + if (RegGetValueA(nwAdapters,subkeyName,"ComponentId",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) { + data[dataLen] = '\0'; + + if ((!strnicmp(data,"zttap",5))&&(WINENV.tapDriverName != data)) { + std::string instanceIdPath; + type = 0; + dataLen = sizeof(data); + if (RegGetValueA(nwAdapters,subkeyName,"DeviceInstanceID",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) + instanceIdPath.assign(data,dataLen); + if (instanceIdPath.length() != 0) + instanceIdPathsToRemove.insert(instanceIdPath); + } + } + } else break; // end of list or failure + } + + RegCloseKey(nwAdapters); + } + + for(std::set::iterator iidp(instanceIdPathsToRemove.begin());iidp!=instanceIdPathsToRemove.end();++iidp) { + std::string err = deletePersistentTapDevice(iidp->c_str()); + if (err.length() > 0) + return err; + } + + return std::string(); +} + +std::string WindowsEthernetTap::destroyAllPersistentTapDevices() +{ + char subkeyName[4096]; + char subkeyClass[4096]; + char data[4096]; + + std::set instanceIdPathsToRemove; + { + HKEY nwAdapters; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}",0,KEY_READ|KEY_WRITE,&nwAdapters) != ERROR_SUCCESS) + return std::string("Could not open registry key"); + + for(DWORD subkeyIndex=0;;++subkeyIndex) { + DWORD type; + DWORD dataLen; + DWORD subkeyNameLen = sizeof(subkeyName); + DWORD subkeyClassLen = sizeof(subkeyClass); + FILETIME lastWriteTime; + if (RegEnumKeyExA(nwAdapters,subkeyIndex,subkeyName,&subkeyNameLen,(DWORD *)0,subkeyClass,&subkeyClassLen,&lastWriteTime) == ERROR_SUCCESS) { + type = 0; + dataLen = sizeof(data); + if (RegGetValueA(nwAdapters,subkeyName,"ComponentId",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) { + data[dataLen] = '\0'; + + if (!strnicmp(data,"zttap",5)) { + std::string instanceIdPath; + type = 0; + dataLen = sizeof(data); + if (RegGetValueA(nwAdapters,subkeyName,"DeviceInstanceID",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) + instanceIdPath.assign(data,dataLen); + if (instanceIdPath.length() != 0) + instanceIdPathsToRemove.insert(instanceIdPath); + } + } + } else break; // end of list or failure + } + + RegCloseKey(nwAdapters); + } + + for(std::set::iterator iidp(instanceIdPathsToRemove.begin());iidp!=instanceIdPathsToRemove.end();++iidp) { + std::string err = deletePersistentTapDevice(iidp->c_str()); + if (err.length() > 0) + return err; + } + + return std::string(); +} + +std::string WindowsEthernetTap::deletePersistentTapDevice(const char *instanceId) +{ + char iid[256]; + SP_REMOVEDEVICE_PARAMS rmdParams; + + memset(&rmdParams,0,sizeof(rmdParams)); + rmdParams.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER); + rmdParams.ClassInstallHeader.InstallFunction = DIF_REMOVE; + rmdParams.Scope = DI_REMOVEDEVICE_GLOBAL; + rmdParams.HwProfile = 0; + + Mutex::Lock _l(_systemDeviceManagementLock); + + HDEVINFO devInfo = WINENV.SetupDiGetClassDevsExA((const GUID *)0,(PCSTR)0,(HWND)0,DIGCF_ALLCLASSES,(HDEVINFO)0,(PCSTR)0,(PVOID)0); + if (devInfo == INVALID_HANDLE_VALUE) + return std::string("SetupDiGetClassDevsExA() failed"); + WINENV.SetupDiOpenDeviceInfoA(devInfo,instanceId,(HWND)0,0,(PSP_DEVINFO_DATA)0); + + SP_DEVINFO_DATA devInfoData; + memset(&devInfoData,0,sizeof(devInfoData)); + devInfoData.cbSize = sizeof(devInfoData); + for(DWORD devIndex=0;WINENV.SetupDiEnumDeviceInfo(devInfo,devIndex,&devInfoData);devIndex++) { + if ((WINENV.CM_Get_Device_ID_ExA(devInfoData.DevInst,iid,sizeof(iid),0,(HMACHINE)0) == CR_SUCCESS)&&(!strcmp(iid,instanceId))) { + if (!WINENV.SetupDiSetClassInstallParamsA(devInfo,&devInfoData,&rmdParams.ClassInstallHeader,sizeof(rmdParams))) { + WINENV.SetupDiDestroyDeviceInfoList(devInfo); + return std::string("SetupDiSetClassInstallParams() failed"); + } + + if (!WINENV.SetupDiCallClassInstaller(DIF_REMOVE,devInfo,&devInfoData)) { + WINENV.SetupDiDestroyDeviceInfoList(devInfo); + return std::string("SetupDiCallClassInstaller(DIF_REMOVE) failed"); + } + + WINENV.SetupDiDestroyDeviceInfoList(devInfo); + return std::string(); + } + } + + WINENV.SetupDiDestroyDeviceInfoList(devInfo); + return std::string("instance ID not found"); +} + +bool WindowsEthernetTap::setPersistentTapDeviceState(const char *instanceId,bool enabled) +{ + char iid[256]; + SP_PROPCHANGE_PARAMS params; + + Mutex::Lock _l(_systemDeviceManagementLock); + + HDEVINFO devInfo = WINENV.SetupDiGetClassDevsExA((const GUID *)0,(PCSTR)0,(HWND)0,DIGCF_ALLCLASSES,(HDEVINFO)0,(PCSTR)0,(PVOID)0); + if (devInfo == INVALID_HANDLE_VALUE) + return false; + WINENV.SetupDiOpenDeviceInfoA(devInfo,instanceId,(HWND)0,0,(PSP_DEVINFO_DATA)0); + + SP_DEVINFO_DATA devInfoData; + memset(&devInfoData,0,sizeof(devInfoData)); + devInfoData.cbSize = sizeof(devInfoData); + for(DWORD devIndex=0;WINENV.SetupDiEnumDeviceInfo(devInfo,devIndex,&devInfoData);devIndex++) { + if ((WINENV.CM_Get_Device_ID_ExA(devInfoData.DevInst,iid,sizeof(iid),0,(HMACHINE)0) == CR_SUCCESS)&&(!strcmp(iid,instanceId))) { + memset(¶ms,0,sizeof(params)); + params.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER); + params.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE; + params.StateChange = enabled ? DICS_ENABLE : DICS_DISABLE; + params.Scope = DICS_FLAG_GLOBAL; + params.HwProfile = 0; + + WINENV.SetupDiSetClassInstallParamsA(devInfo,&devInfoData,¶ms.ClassInstallHeader,sizeof(params)); + WINENV.SetupDiCallClassInstaller(DIF_PROPERTYCHANGE,devInfo,&devInfoData); + + memset(¶ms,0,sizeof(params)); + params.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER); + params.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE; + params.StateChange = enabled ? DICS_ENABLE : DICS_DISABLE; + params.Scope = DICS_FLAG_CONFIGSPECIFIC; + params.HwProfile = 0; + + WINENV.SetupDiSetClassInstallParamsA(devInfo,&devInfoData,¶ms.ClassInstallHeader,sizeof(params)); + WINENV.SetupDiCallClassInstaller(DIF_PROPERTYCHANGE,devInfo,&devInfoData); + + WINENV.SetupDiDestroyDeviceInfoList(devInfo); + return true; + } + } + + WINENV.SetupDiDestroyDeviceInfoList(devInfo); + return false; +} + WindowsEthernetTap::WindowsEthernetTap( const char *hp, const MAC &mac, @@ -118,35 +455,21 @@ WindowsEthernetTap::WindowsEthernetTap( char subkeyClass[4096]; char data[4096]; char tag[24]; + std::set existingDeviceInstances; + std::string mySubkeyName; if (mtu > 2800) throw std::runtime_error("MTU too large for Windows tap"); - Mutex::Lock _l(_systemTapInitLock); + // We "tag" registry entries with the network ID to identify persistent devices + Utils::snprintf(tag,sizeof(tag),"%.16llx",(unsigned long long)nwid); - // Use NDIS5 if it's installed, since we don't want to switch out the driver on - // pre-existing installs (yet). We won't ship NDIS5 anymore so new installs will - // use NDIS6. - std::string tapDriverPath(_pathToHelpers + WINENV.tapDriverNdis5); - const char *tapDriverName = "zttap200"; - if (::PathFileExistsA(tapDriverPath.c_str()) == FALSE) { - tapDriverPath = _pathToHelpers + WINENV.tapDriverNdis6; - tapDriverName = "zttap300"; - if (::PathFileExistsA(tapDriverPath.c_str()) == FALSE) { - throw std::runtime_error("no tap driver available: cannot find zttap300.inf (NDIS6) or zttap200.inf (NDIS5) under home path"); - } - } + Mutex::Lock _l(_systemTapInitLock); HKEY nwAdapters; if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}",0,KEY_READ|KEY_WRITE,&nwAdapters) != ERROR_SUCCESS) throw std::runtime_error("unable to open registry key for network adapter enumeration"); - std::set existingDeviceInstances; - std::string mySubkeyName; - - // We "tag" registry entries with the network ID to identify persistent devices - Utils::snprintf(tag,sizeof(tag),"%.16llx",(unsigned long long)nwid); - // Look for the tap instance that corresponds with this network for(DWORD subkeyIndex=0;;++subkeyIndex) { DWORD type; @@ -158,8 +481,9 @@ WindowsEthernetTap::WindowsEthernetTap( type = 0; dataLen = sizeof(data); if (RegGetValueA(nwAdapters,subkeyName,"ComponentId",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) { - data[dataLen] = '\0'; - if (!strnicmp(data,"zttap",5)) { + data[dataLen] = (char)0; + + if (WINENV.tapDriverName == data) { std::string instanceId; type = 0; dataLen = sizeof(data); @@ -196,34 +520,9 @@ WindowsEthernetTap::WindowsEthernetTap( // If there is no device, try to create one bool creatingNewDevice = (_netCfgInstanceId.length() == 0); if (creatingNewDevice) { - // Log devcon output to a file - HANDLE devconLog = CreateFileA((_pathToHelpers + "\\devcon.log").c_str(),GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); - if (devconLog != INVALID_HANDLE_VALUE) - SetFilePointer(devconLog,0,0,FILE_END); - - // Execute devcon to create a new tap device - STARTUPINFOA startupInfo; - startupInfo.cb = sizeof(startupInfo); - if (devconLog != INVALID_HANDLE_VALUE) { - SetFilePointer(devconLog,0,0,FILE_END); - startupInfo.hStdOutput = devconLog; - startupInfo.hStdError = devconLog; - } - PROCESS_INFORMATION processInfo; - memset(&startupInfo,0,sizeof(STARTUPINFOA)); - memset(&processInfo,0,sizeof(PROCESS_INFORMATION)); - if (!CreateProcessA(NULL,(LPSTR)(std::string("\"") + _pathToHelpers + WINENV.devcon + "\" install \"" + tapDriverPath + "\" " + tapDriverName).c_str(),NULL,NULL,FALSE,0,NULL,NULL,&startupInfo,&processInfo)) { - RegCloseKey(nwAdapters); - if (devconLog != INVALID_HANDLE_VALUE) - CloseHandle(devconLog); - throw std::runtime_error(std::string("unable to find or execute devcon at ") + WINENV.devcon); - } - WaitForSingleObject(processInfo.hProcess,INFINITE); - CloseHandle(processInfo.hProcess); - CloseHandle(processInfo.hThread); - - if (devconLog != INVALID_HANDLE_VALUE) - CloseHandle(devconLog); + std::string errm = addNewPersistentTapDevice((std::string(_pathToHelpers) + WINENV.tapDriverPath).c_str()); + if (errm.length() != 0) + throw std::runtime_error(errm); // Scan for the new instance by simply looking for taps that weren't originally there... for(DWORD subkeyIndex=0;;++subkeyIndex) { @@ -237,7 +536,8 @@ WindowsEthernetTap::WindowsEthernetTap( dataLen = sizeof(data); if (RegGetValueA(nwAdapters,subkeyName,"ComponentId",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) { data[dataLen] = '\0'; - if (!strnicmp(data,"zttap",5)) { + + if (WINENV.tapDriverName == data) { type = 0; dataLen = sizeof(data); if (RegGetValueA(nwAdapters,subkeyName,"NetCfgInstanceId",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) { @@ -281,6 +581,7 @@ WindowsEthernetTap::WindowsEthernetTap( RegSetKeyValueA(nwAdapters,mySubkeyName.c_str(),"*IfType",REG_DWORD,(LPCVOID)&tmp,sizeof(tmp)); if (creatingNewDevice) { + // Set EnableDHCP to 0 by default on new devices tmp = 0; RegSetKeyValueA(nwAdapters,mySubkeyName.c_str(),"EnableDHCP",REG_DWORD,(LPCVOID)&tmp,sizeof(tmp)); } @@ -291,7 +592,7 @@ WindowsEthernetTap::WindowsEthernetTap( } { - char nobraces[128]; + char nobraces[128]; // strip braces from GUID before converting it, because Windows const char *nbtmp1 = _netCfgInstanceId.c_str(); char *nbtmp2 = nobraces; while (*nbtmp1) { @@ -304,17 +605,15 @@ WindowsEthernetTap::WindowsEthernetTap( throw std::runtime_error("unable to convert instance ID GUID to native GUID (invalid NetCfgInstanceId in registry?)"); } - // Look up interface LUID... why are there (at least) four fucking ways to refer to a network device in Windows? + // Get the LUID, which is one of like four fucking ways to refer to a network device in Windows if (ConvertInterfaceGuidToLuid(&_deviceGuid,&_deviceLuid) != NO_ERROR) throw std::runtime_error("unable to convert device interface GUID to LUID"); - // Certain functions can now work (e.g. ips()) _initialized = true; if (friendlyName) setFriendlyName(friendlyName); - // Start background thread that actually performs I/O _injectSemaphore = CreateSemaphore(NULL,0,1,NULL); _thread = Thread::start(this); } @@ -325,7 +624,7 @@ WindowsEthernetTap::~WindowsEthernetTap() ReleaseSemaphore(_injectSemaphore,1,NULL); Thread::join(_thread); CloseHandle(_injectSemaphore); - _disableTapDevice(); + setPersistentTapDeviceState(_deviceInstanceId.c_str(),false); } void WindowsEthernetTap::setEnabled(bool en) @@ -572,14 +871,15 @@ void WindowsEthernetTap::threadMain() try { while (_run) { - _enableTapDevice(); + setPersistentTapDeviceState(_deviceInstanceId.c_str(),true); Sleep(500); _tap = CreateFileA(tapPath,GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_SYSTEM|FILE_FLAG_OVERLAPPED,NULL); if (_tap == INVALID_HANDLE_VALUE) { - _disableTapDevice(); - _enableTapDevice(); - Sleep(1000); + setPersistentTapDeviceState(_deviceInstanceId.c_str(),false); + Sleep(500); + setPersistentTapDeviceState(_deviceInstanceId.c_str(),true); + Sleep(500); continue; } @@ -761,131 +1061,6 @@ void WindowsEthernetTap::threadMain() } catch ( ... ) {} // catch unexpected exceptions -- this should not happen but would prevent program crash or other weird issues since threads should not throw } -void WindowsEthernetTap::destroyAllPersistentTapDevices(const char *pathToHelpers) -{ - char subkeyName[4096]; - char subkeyClass[4096]; - char data[4096]; - - std::set instanceIdPathsToRemove; - { - HKEY nwAdapters; - if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}",0,KEY_READ|KEY_WRITE,&nwAdapters) != ERROR_SUCCESS) - return; - - for(DWORD subkeyIndex=0;;++subkeyIndex) { - DWORD type; - DWORD dataLen; - DWORD subkeyNameLen = sizeof(subkeyName); - DWORD subkeyClassLen = sizeof(subkeyClass); - FILETIME lastWriteTime; - if (RegEnumKeyExA(nwAdapters,subkeyIndex,subkeyName,&subkeyNameLen,(DWORD *)0,subkeyClass,&subkeyClassLen,&lastWriteTime) == ERROR_SUCCESS) { - type = 0; - dataLen = sizeof(data); - if (RegGetValueA(nwAdapters,subkeyName,"ComponentId",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) { - data[dataLen] = '\0'; - if (!strnicmp(data,"zttap",5)) { - std::string instanceIdPath; - type = 0; - dataLen = sizeof(data); - if (RegGetValueA(nwAdapters,subkeyName,"DeviceInstanceID",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) - instanceIdPath.assign(data,dataLen); - if (instanceIdPath.length() != 0) - instanceIdPathsToRemove.insert(instanceIdPath); - } - } - } else break; // end of list or failure - } - - RegCloseKey(nwAdapters); - } - - for(std::set::iterator iidp(instanceIdPathsToRemove.begin());iidp!=instanceIdPathsToRemove.end();++iidp) - deletePersistentTapDevice(pathToHelpers,iidp->c_str()); -} - -void WindowsEthernetTap::deletePersistentTapDevice(const char *pathToHelpers,const char *instanceId) -{ - HANDLE devconLog = CreateFileA((std::string(pathToHelpers) + "\\devcon.log").c_str(),GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); - STARTUPINFOA startupInfo; - startupInfo.cb = sizeof(startupInfo); - if (devconLog != INVALID_HANDLE_VALUE) { - SetFilePointer(devconLog,0,0,FILE_END); - startupInfo.hStdOutput = devconLog; - startupInfo.hStdError = devconLog; - } - PROCESS_INFORMATION processInfo; - memset(&startupInfo,0,sizeof(STARTUPINFOA)); - memset(&processInfo,0,sizeof(PROCESS_INFORMATION)); - if (CreateProcessA(NULL,(LPSTR)(std::string("\"") + pathToHelpers + WINENV.devcon + "\" remove @" + instanceId).c_str(),NULL,NULL,FALSE,0,NULL,NULL,&startupInfo,&processInfo)) { - WaitForSingleObject(processInfo.hProcess,INFINITE); - CloseHandle(processInfo.hProcess); - CloseHandle(processInfo.hThread); - } - if (devconLog != INVALID_HANDLE_VALUE) - CloseHandle(devconLog); -} - -bool WindowsEthernetTap::_disableTapDevice() -{ - HANDLE devconLog = CreateFileA((_pathToHelpers + "\\devcon.log").c_str(),GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); - if (devconLog != INVALID_HANDLE_VALUE) - SetFilePointer(devconLog,0,0,FILE_END); - - STARTUPINFOA startupInfo; - startupInfo.cb = sizeof(startupInfo); - if (devconLog != INVALID_HANDLE_VALUE) { - startupInfo.hStdOutput = devconLog; - startupInfo.hStdError = devconLog; - } - PROCESS_INFORMATION processInfo; - memset(&startupInfo,0,sizeof(STARTUPINFOA)); - memset(&processInfo,0,sizeof(PROCESS_INFORMATION)); - if (!CreateProcessA(NULL,(LPSTR)(std::string("\"") + _pathToHelpers + WINENV.devcon + "\" disable @" + _deviceInstanceId).c_str(),NULL,NULL,FALSE,0,NULL,NULL,&startupInfo,&processInfo)) { - if (devconLog != INVALID_HANDLE_VALUE) - CloseHandle(devconLog); - return false; - } - WaitForSingleObject(processInfo.hProcess,INFINITE); - CloseHandle(processInfo.hProcess); - CloseHandle(processInfo.hThread); - - if (devconLog != INVALID_HANDLE_VALUE) - CloseHandle(devconLog); - - return true; -} - -bool WindowsEthernetTap::_enableTapDevice() -{ - HANDLE devconLog = CreateFileA((_pathToHelpers + "\\devcon.log").c_str(),GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); - if (devconLog != INVALID_HANDLE_VALUE) - SetFilePointer(devconLog,0,0,FILE_END); - - STARTUPINFOA startupInfo; - startupInfo.cb = sizeof(startupInfo); - if (devconLog != INVALID_HANDLE_VALUE) { - startupInfo.hStdOutput = devconLog; - startupInfo.hStdError = devconLog; - } - PROCESS_INFORMATION processInfo; - memset(&startupInfo,0,sizeof(STARTUPINFOA)); - memset(&processInfo,0,sizeof(PROCESS_INFORMATION)); - if (!CreateProcessA(NULL,(LPSTR)(std::string("\"") + _pathToHelpers + WINENV.devcon + "\" enable @" + _deviceInstanceId).c_str(),NULL,NULL,FALSE,0,NULL,NULL,&startupInfo,&processInfo)) { - if (devconLog != INVALID_HANDLE_VALUE) - CloseHandle(devconLog); - return false; - } - WaitForSingleObject(processInfo.hProcess,INFINITE); - CloseHandle(processInfo.hProcess); - CloseHandle(processInfo.hThread); - - if (devconLog != INVALID_HANDLE_VALUE) - CloseHandle(devconLog); - - return true; -} - NET_IFINDEX WindowsEthernetTap::_getDeviceIndex() { MIB_IF_TABLE2 *ift = (MIB_IF_TABLE2 *)0; diff --git a/osdep/WindowsEthernetTap.hpp b/osdep/WindowsEthernetTap.hpp index 944b53f33..97113d973 100644 --- a/osdep/WindowsEthernetTap.hpp +++ b/osdep/WindowsEthernetTap.hpp @@ -48,6 +48,45 @@ namespace ZeroTier { class WindowsEthernetTap { public: + /** + * Installs a new instance of the ZT tap driver + * + * @param pathToInf Path to zttap driver .inf file + * @return Empty string on success, otherwise an error message + */ + static std::string addNewPersistentTapDevice(const char *pathToInf); + + /** + * Uninstalls all persistent tap devices that have legacy drivers + * + * @return Empty string on success, otherwise an error message + */ + static std::string destroyAllLegacyPersistentTapDevices(); + + /** + * Uninstalls all persistent tap devices on the system + * + * @return Empty string on success, otherwise an error message + */ + static std::string destroyAllPersistentTapDevices(); + + /** + * Uninstall a specific persistent tap device by instance ID + * + * @param instanceId Device instance ID + * @return Empty string on success, otherwise an error message + */ + static std::string deletePersistentTapDevice(const char *instanceId); + + /** + * Disable a persistent tap device by instance ID + * + * @param instanceId Device instance ID + * @param enabled Enable device? + * @return True if device was found and disabled + */ + static bool setPersistentTapDeviceState(const char *instanceId,bool enabled); + WindowsEthernetTap( const char *hp, const MAC &mac, @@ -77,9 +116,6 @@ public: void threadMain() throw(); - static void destroyAllPersistentTapDevices(const char *pathToHelpers); - static void deletePersistentTapDevice(const char *pathToHelpers,const char *instanceId); - private: bool _disableTapDevice(); bool _enableTapDevice(); diff --git a/service/OneService.cpp b/service/OneService.cpp index 06e37a45f..4ee473f87 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -982,7 +982,7 @@ public: _tapAssignedIps.erase(nwid); #ifdef __WINDOWS__ if ((op == ZT1_VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY)&&(winInstanceId.length() > 0)) - WindowsEthernetTap::deletePersistentTapDevice(_homePath.c_str(),winInstanceId.c_str()); + WindowsEthernetTap::deletePersistentTapDevice(winInstanceId.c_str()); #endif } break; diff --git a/windows/ZeroTierOne/ZeroTierOne.vcxproj b/windows/ZeroTierOne/ZeroTierOne.vcxproj index 14bf7c3eb..0a43a6f60 100644 --- a/windows/ZeroTierOne/ZeroTierOne.vcxproj +++ b/windows/ZeroTierOne/ZeroTierOne.vcxproj @@ -217,7 +217,7 @@ true - $(SolutionDir)..\ext\bin\miniupnpc\windows-x86\miniupnpc.lib;wsock32.lib;ws2_32.lib;newdev.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies) + $(SolutionDir)..\ext\bin\miniupnpc\windows-x86\miniupnpc.lib;wsock32.lib;ws2_32.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies) false @@ -232,7 +232,7 @@ true - $(SolutionDir)..\ext\bin\miniupnpc\windows-x64\miniupnpc.lib;wsock32.lib;ws2_32.lib;newdev.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies) + $(SolutionDir)..\ext\bin\miniupnpc\windows-x64\miniupnpc.lib;wsock32.lib;ws2_32.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies) false @@ -257,7 +257,7 @@ true true true - $(SolutionDir)..\ext\bin\miniupnpc\windows-x86\miniupnpc.lib;wsock32.lib;ws2_32.lib;newdev.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies) + $(SolutionDir)..\ext\bin\miniupnpc\windows-x86\miniupnpc.lib;wsock32.lib;ws2_32.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies) false @@ -282,7 +282,7 @@ true true true - $(SolutionDir)..\ext\bin\miniupnpc\windows-x64\miniupnpc.lib;wsock32.lib;ws2_32.lib;newdev.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies) + $(SolutionDir)..\ext\bin\miniupnpc\windows-x64\miniupnpc.lib;wsock32.lib;ws2_32.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies) false From 6f46f0e0e1f0dd01f0e7bdaa700b11d2c54d0794 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Thu, 30 Jul 2015 11:57:48 -0700 Subject: [PATCH 02/11] Because Windows. --- osdep/WindowsEthernetTap.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osdep/WindowsEthernetTap.cpp b/osdep/WindowsEthernetTap.cpp index d8ba1f982..426dd3cbb 100644 --- a/osdep/WindowsEthernetTap.cpp +++ b/osdep/WindowsEthernetTap.cpp @@ -871,15 +871,19 @@ void WindowsEthernetTap::threadMain() try { while (_run) { + // Because Windows + setPersistentTapDeviceState(_deviceInstanceId.c_str(),false); + Sleep(500); + setPersistentTapDeviceState(_deviceInstanceId.c_str(),true); + Sleep(500); + setPersistentTapDeviceState(_deviceInstanceId.c_str(),false); + Sleep(500); setPersistentTapDeviceState(_deviceInstanceId.c_str(),true); Sleep(500); _tap = CreateFileA(tapPath,GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_SYSTEM|FILE_FLAG_OVERLAPPED,NULL); if (_tap == INVALID_HANDLE_VALUE) { - setPersistentTapDeviceState(_deviceInstanceId.c_str(),false); - Sleep(500); - setPersistentTapDeviceState(_deviceInstanceId.c_str(),true); - Sleep(500); + Sleep(1000); continue; } From 1e3d5c4d87ce6e92bbb42d0f9675ec36f9e4929c Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Thu, 30 Jul 2015 12:05:56 -0700 Subject: [PATCH 03/11] Suppress icacls output on lockDownFile(). --- osdep/OSUtils.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osdep/OSUtils.cpp b/osdep/OSUtils.cpp index 0ff7bfc46..45fbf1003 100644 --- a/osdep/OSUtils.cpp +++ b/osdep/OSUtils.cpp @@ -125,7 +125,7 @@ void OSUtils::lockDownFile(const char *path,bool isDir) startupInfo.cb = sizeof(startupInfo); memset(&startupInfo,0,sizeof(STARTUPINFOA)); memset(&processInfo,0,sizeof(PROCESS_INFORMATION)); - if (CreateProcessA(NULL,(LPSTR)(std::string("C:\\Windows\\System32\\icacls.exe \"") + path + "\" /inheritance:d /Q").c_str(),NULL,NULL,FALSE,0,NULL,NULL,&startupInfo,&processInfo)) { + if (CreateProcessA(NULL,(LPSTR)(std::string("C:\\Windows\\System32\\icacls.exe \"") + path + "\" /inheritance:d /Q").c_str(),NULL,NULL,FALSE,CREATE_NO_WINDOW,NULL,NULL,&startupInfo,&processInfo)) { WaitForSingleObject(processInfo.hProcess,INFINITE); CloseHandle(processInfo.hProcess); CloseHandle(processInfo.hThread); @@ -134,7 +134,7 @@ void OSUtils::lockDownFile(const char *path,bool isDir) startupInfo.cb = sizeof(startupInfo); memset(&startupInfo,0,sizeof(STARTUPINFOA)); memset(&processInfo,0,sizeof(PROCESS_INFORMATION)); - if (CreateProcessA(NULL,(LPSTR)(std::string("C:\\Windows\\System32\\icacls.exe \"") + path + "\" /remove *S-1-5-32-545 /Q").c_str(),NULL,NULL,FALSE,0,NULL,NULL,&startupInfo,&processInfo)) { + if (CreateProcessA(NULL,(LPSTR)(std::string("C:\\Windows\\System32\\icacls.exe \"") + path + "\" /remove *S-1-5-32-545 /Q").c_str(),NULL,NULL,FALSE,CREATE_NO_WINDOW,NULL,NULL,&startupInfo,&processInfo)) { WaitForSingleObject(processInfo.hProcess,INFINITE); CloseHandle(processInfo.hProcess); CloseHandle(processInfo.hThread); From 499b2dccad985f93003c53d8311b34d10a4b1ea3 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Thu, 30 Jul 2015 13:30:10 -0700 Subject: [PATCH 04/11] 1.0.4 installer GUID --- ext/installfiles/windows/ZeroTier One.aip | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/installfiles/windows/ZeroTier One.aip b/ext/installfiles/windows/ZeroTier One.aip index 5bdebe40c..b7670078a 100644 --- a/ext/installfiles/windows/ZeroTier One.aip +++ b/ext/installfiles/windows/ZeroTier One.aip @@ -23,7 +23,7 @@ - + From 922d9657b9d90df5cdf313b3ebe4d0280d0ff47a Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Thu, 30 Jul 2015 14:10:32 -0700 Subject: [PATCH 05/11] Save enumeration of statically assigned IPs so they will always be reassigned on device "power cycle." --- ext/installfiles/windows/ZeroTier One.aip | 2 +- osdep/WindowsEthernetTap.cpp | 118 +++++++++++++--------- osdep/WindowsEthernetTap.hpp | 7 +- 3 files changed, 76 insertions(+), 51 deletions(-) diff --git a/ext/installfiles/windows/ZeroTier One.aip b/ext/installfiles/windows/ZeroTier One.aip index b7670078a..7ca1270e6 100644 --- a/ext/installfiles/windows/ZeroTier One.aip +++ b/ext/installfiles/windows/ZeroTier One.aip @@ -23,7 +23,7 @@ - + diff --git a/osdep/WindowsEthernetTap.cpp b/osdep/WindowsEthernetTap.cpp index 426dd3cbb..2c3385161 100644 --- a/osdep/WindowsEthernetTap.cpp +++ b/osdep/WindowsEthernetTap.cpp @@ -639,63 +639,28 @@ bool WindowsEthernetTap::enabled() const bool WindowsEthernetTap::addIp(const InetAddress &ip) { - if (!_initialized) - return false; if (!ip.netmaskBits()) // sanity check... netmask of 0.0.0.0 is WUT? return false; - - std::vector haveIps(ips()); - - try { - // Add IP to interface at the netlink level if not already assigned. - if (!std::binary_search(haveIps.begin(),haveIps.end(),ip)) { - MIB_UNICASTIPADDRESS_ROW ipr; - - InitializeUnicastIpAddressEntry(&ipr); - if (ip.isV4()) { - ipr.Address.Ipv4.sin_family = AF_INET; - ipr.Address.Ipv4.sin_addr.S_un.S_addr = *((const uint32_t *)ip.rawIpData()); - ipr.OnLinkPrefixLength = ip.port(); - if (ipr.OnLinkPrefixLength >= 32) - return false; - } else if (ip.isV6()) { - ipr.Address.Ipv6.sin6_family = AF_INET6; - memcpy(ipr.Address.Ipv6.sin6_addr.u.Byte,ip.rawIpData(),16); - ipr.OnLinkPrefixLength = ip.port(); - if (ipr.OnLinkPrefixLength >= 128) - return false; - } else return false; - - ipr.PrefixOrigin = IpPrefixOriginManual; - ipr.SuffixOrigin = IpSuffixOriginManual; - ipr.ValidLifetime = 0xffffffff; - ipr.PreferredLifetime = 0xffffffff; - - ipr.InterfaceLuid = _deviceLuid; - ipr.InterfaceIndex = _getDeviceIndex(); - - if (CreateUnicastIpAddressEntry(&ipr) != NO_ERROR) - return false; - } - - std::vector regIps(_getRegistryIPv4Value("IPAddress")); - if (std::find(regIps.begin(),regIps.end(),ip.toIpString()) == regIps.end()) { - std::vector regSubnetMasks(_getRegistryIPv4Value("SubnetMask")); - regIps.push_back(ip.toIpString()); - regSubnetMasks.push_back(ip.netmask().toIpString()); - _setRegistryIPv4Value("IPAddress",regIps); - _setRegistryIPv4Value("SubnetMask",regSubnetMasks); - } - } catch ( ... ) { - return false; - } + Mutex::Lock _l(_assignedIps_m); + if (std::find(_assignedIps.begin(),_assignedIps.end(),ip) != _assignedIps.end()) + return true; + _assignedIps.push_back(ip); + _syncIps(); return true; } bool WindowsEthernetTap::removeIp(const InetAddress &ip) { + { + Mutex::Lock _l(_assignedIps_m); + std::vector::iterator aip(std::find(_assignedIps.begin(),_assignedIps.end(),ip)); + if (aip != _assignedIps.end()) + _assignedIps.erase(aip); + } + if (!_initialized) return false; + try { MIB_UNICASTIPADDRESS_TABLE *ipt = (MIB_UNICASTIPADDRESS_TABLE *)0; if (GetUnicastIpAddressTable(AF_UNSPEC,&ipt) == NO_ERROR) { @@ -972,6 +937,12 @@ void WindowsEthernetTap::threadMain() } #endif + // Assign or re-assign any should-be-assigned IPs in case we have restarted + { + Mutex::Lock _l(_assignedIps_m); + _syncIps(); + } + memset(&tapOvlRead,0,sizeof(tapOvlRead)); tapOvlRead.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL); memset(&tapOvlWrite,0,sizeof(tapOvlWrite)); @@ -1135,4 +1106,55 @@ void WindowsEthernetTap::_setRegistryIPv4Value(const char *regKey,const std::vec } } +void WindowsEthernetTap::_syncIps() +{ + // assumes _assignedIps_m is locked + + if (!_initialized) + return; + + std::vector haveIps(ips()); + + for(std::vector::const_iterator aip(_assignedIps.begin());aip!=_assignedIps.end();++aip) { + if (std::find(haveIps.begin(),haveIps.end(),*aip) == haveIps.end()) { + MIB_UNICASTIPADDRESS_ROW ipr; + + InitializeUnicastIpAddressEntry(&ipr); + if (aip->isV4()) { + ipr.Address.Ipv4.sin_family = AF_INET; + ipr.Address.Ipv4.sin_addr.S_un.S_addr = *((const uint32_t *)aip->rawIpData()); + ipr.OnLinkPrefixLength = aip->netmaskBits(); + if (ipr.OnLinkPrefixLength >= 32) + continue; + } else if (aip->isV6()) { + ipr.Address.Ipv6.sin6_family = AF_INET6; + memcpy(ipr.Address.Ipv6.sin6_addr.u.Byte,aip->rawIpData(),16); + ipr.OnLinkPrefixLength = aip->netmaskBits(); + if (ipr.OnLinkPrefixLength >= 128) + continue; + } else continue; + + ipr.PrefixOrigin = IpPrefixOriginManual; + ipr.SuffixOrigin = IpSuffixOriginManual; + ipr.ValidLifetime = 0xffffffff; + ipr.PreferredLifetime = 0xffffffff; + + ipr.InterfaceLuid = _deviceLuid; + ipr.InterfaceIndex = _getDeviceIndex(); + + CreateUnicastIpAddressEntry(&ipr); + } + + std::string ipStr(aip->toString()); + std::vector regIps(_getRegistryIPv4Value("IPAddress")); + if (std::find(regIps.begin(),regIps.end(),ipStr) == regIps.end()) { + std::vector regSubnetMasks(_getRegistryIPv4Value("SubnetMask")); + regIps.push_back(ipStr); + regSubnetMasks.push_back(aip->netmask().toIpString()); + _setRegistryIPv4Value("IPAddress",regIps); + _setRegistryIPv4Value("SubnetMask",regSubnetMasks); + } + } +} + } // namespace ZeroTier diff --git a/osdep/WindowsEthernetTap.hpp b/osdep/WindowsEthernetTap.hpp index 97113d973..dd60c0bf6 100644 --- a/osdep/WindowsEthernetTap.hpp +++ b/osdep/WindowsEthernetTap.hpp @@ -41,6 +41,7 @@ #include "../node/Mutex.hpp" #include "../node/Array.hpp" #include "../node/MulticastGroup.hpp" +#include "../node/InetAddress.hpp" #include "../osdep/Thread.hpp" namespace ZeroTier { @@ -117,11 +118,10 @@ public: throw(); private: - bool _disableTapDevice(); - bool _enableTapDevice(); NET_IFINDEX _getDeviceIndex(); // throws on failure std::vector _getRegistryIPv4Value(const char *regKey); void _setRegistryIPv4Value(const char *regKey,const std::vector &value); + void _syncIps(); void (*_handler)(void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int); void *_arg; @@ -137,6 +137,9 @@ private: std::string _netCfgInstanceId; std::string _deviceInstanceId; + std::vector _assignedIps; // IPs assigned with addIp + Mutex _assignedIps_m; + std::vector _multicastGroups; std::queue< std::pair< Array,unsigned int > > _injectPending; From 620562f7cfcfd1c7d9d0903556230fb80e2199fe Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Thu, 30 Jul 2015 17:00:57 -0700 Subject: [PATCH 06/11] Because Windows, because Windows. Now it upgrades correctly from 1.0.1, including automatic driver update from NDIS5 to NDIS6. Also a bit more robust on creating new ports, just in case. --- ext/installfiles/windows/ZeroTier One.aip | 6 +- osdep/WindowsEthernetTap.cpp | 125 +++++++++++++--------- service/OneService.cpp | 11 ++ 3 files changed, 88 insertions(+), 54 deletions(-) diff --git a/ext/installfiles/windows/ZeroTier One.aip b/ext/installfiles/windows/ZeroTier One.aip index 7ca1270e6..c0a6d91b9 100644 --- a/ext/installfiles/windows/ZeroTier One.aip +++ b/ext/installfiles/windows/ZeroTier One.aip @@ -23,7 +23,7 @@ - + @@ -101,8 +101,8 @@ - - + + diff --git a/osdep/WindowsEthernetTap.cpp b/osdep/WindowsEthernetTap.cpp index 2c3385161..c6b434e12 100644 --- a/osdep/WindowsEthernetTap.cpp +++ b/osdep/WindowsEthernetTap.cpp @@ -234,10 +234,19 @@ std::string WindowsEthernetTap::addNewPersistentTapDevice(const char *pathToInf) return std::string("SetupDiCallClassInstaller(DIF_REGISTERDEVICE) failed"); } - BOOL rebootRequired = FALSE; - if (!WINENV.UpdateDriverForPlugAndPlayDevicesA((HWND)0,WINENV.tapDriverName.c_str(),pathToInf,INSTALLFLAG_FORCE|INSTALLFLAG_NONINTERACTIVE,&rebootRequired)) { + // HACK: During upgrades, this can fail while the installer is still running. So make 60 attempts + // with a 1s delay between each attempt. + bool driverInstalled = false; + for(int retryCounter=0;retryCounter<60;++retryCounter) { + BOOL rebootRequired = FALSE; + if (WINENV.UpdateDriverForPlugAndPlayDevicesA((HWND)0,WINENV.tapDriverName.c_str(),pathToInf,INSTALLFLAG_FORCE|INSTALLFLAG_NONINTERACTIVE,&rebootRequired)) { + driverInstalled = true; + break; + } else Sleep(1000); + } + if (!driverInstalled) { WINENV.SetupDiDestroyDeviceInfoList(deviceInfoSet); - return std::string("UpdateDriverForPlugAndPlayDevices() failed -- unable to install driver on device"); + return std::string("UpdateDriverForPlugAndPlayDevices() failed (made 60 attempts)"); } WINENV.SetupDiDestroyDeviceInfoList(deviceInfoSet); @@ -285,13 +294,16 @@ std::string WindowsEthernetTap::destroyAllLegacyPersistentTapDevices() RegCloseKey(nwAdapters); } + std::string errlist; for(std::set::iterator iidp(instanceIdPathsToRemove.begin());iidp!=instanceIdPathsToRemove.end();++iidp) { std::string err = deletePersistentTapDevice(iidp->c_str()); - if (err.length() > 0) - return err; + if (err.length() > 0) { + if (errlist.length() > 0) + errlist.push_back(','); + errlist.append(err); + } } - - return std::string(); + return errlist; } std::string WindowsEthernetTap::destroyAllPersistentTapDevices() @@ -334,13 +346,16 @@ std::string WindowsEthernetTap::destroyAllPersistentTapDevices() RegCloseKey(nwAdapters); } + std::string errlist; for(std::set::iterator iidp(instanceIdPathsToRemove.begin());iidp!=instanceIdPathsToRemove.end();++iidp) { std::string err = deletePersistentTapDevice(iidp->c_str()); - if (err.length() > 0) - return err; + if (err.length() > 0) { + if (errlist.length() > 0) + errlist.push_back(','); + errlist.append(err); + } } - - return std::string(); + return errlist; } std::string WindowsEthernetTap::deletePersistentTapDevice(const char *instanceId) @@ -455,7 +470,6 @@ WindowsEthernetTap::WindowsEthernetTap( char subkeyClass[4096]; char data[4096]; char tag[24]; - std::set existingDeviceInstances; std::string mySubkeyName; if (mtu > 2800) @@ -487,10 +501,8 @@ WindowsEthernetTap::WindowsEthernetTap( std::string instanceId; type = 0; dataLen = sizeof(data); - if (RegGetValueA(nwAdapters,subkeyName,"NetCfgInstanceId",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) { + if (RegGetValueA(nwAdapters,subkeyName,"NetCfgInstanceId",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) instanceId.assign(data,dataLen); - existingDeviceInstances.insert(instanceId); - } std::string instanceIdPath; type = 0; @@ -520,50 +532,61 @@ WindowsEthernetTap::WindowsEthernetTap( // If there is no device, try to create one bool creatingNewDevice = (_netCfgInstanceId.length() == 0); if (creatingNewDevice) { - std::string errm = addNewPersistentTapDevice((std::string(_pathToHelpers) + WINENV.tapDriverPath).c_str()); - if (errm.length() != 0) - throw std::runtime_error(errm); + for(int getNewAttemptCounter=0;getNewAttemptCounter<2;++getNewAttemptCounter) { + for(DWORD subkeyIndex=0;;++subkeyIndex) { + DWORD type; + DWORD dataLen; + DWORD subkeyNameLen = sizeof(subkeyName); + DWORD subkeyClassLen = sizeof(subkeyClass); + FILETIME lastWriteTime; + if (RegEnumKeyExA(nwAdapters,subkeyIndex,subkeyName,&subkeyNameLen,(DWORD *)0,subkeyClass,&subkeyClassLen,&lastWriteTime) == ERROR_SUCCESS) { + type = 0; + dataLen = sizeof(data); + if (RegGetValueA(nwAdapters,subkeyName,"ComponentId",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) { + data[dataLen] = '\0'; - // Scan for the new instance by simply looking for taps that weren't originally there... - for(DWORD subkeyIndex=0;;++subkeyIndex) { - DWORD type; - DWORD dataLen; - DWORD subkeyNameLen = sizeof(subkeyName); - DWORD subkeyClassLen = sizeof(subkeyClass); - FILETIME lastWriteTime; - if (RegEnumKeyExA(nwAdapters,subkeyIndex,subkeyName,&subkeyNameLen,(DWORD *)0,subkeyClass,&subkeyClassLen,&lastWriteTime) == ERROR_SUCCESS) { - type = 0; - dataLen = sizeof(data); - if (RegGetValueA(nwAdapters,subkeyName,"ComponentId",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) { - data[dataLen] = '\0'; - - if (WINENV.tapDriverName == data) { - type = 0; - dataLen = sizeof(data); - if (RegGetValueA(nwAdapters,subkeyName,"NetCfgInstanceId",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) { - if (existingDeviceInstances.count(std::string(data,dataLen)) == 0) { - RegSetKeyValueA(nwAdapters,subkeyName,"_ZeroTierTapIdentifier",REG_SZ,tag,(DWORD)(strlen(tag)+1)); - _netCfgInstanceId.assign(data,dataLen); + if (WINENV.tapDriverName == data) { + type = 0; + dataLen = sizeof(data); + if ((RegGetValueA(nwAdapters,subkeyName,"_ZeroTierTapIdentifier",RRF_RT_ANY,&type,(PVOID)data,&dataLen) != ERROR_SUCCESS)||(dataLen <= 0)) { type = 0; dataLen = sizeof(data); - if (RegGetValueA(nwAdapters,subkeyName,"DeviceInstanceID",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) - _deviceInstanceId.assign(data,dataLen); - mySubkeyName = subkeyName; + if (RegGetValueA(nwAdapters,subkeyName,"NetCfgInstanceId",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) { + RegSetKeyValueA(nwAdapters,subkeyName,"_ZeroTierTapIdentifier",REG_SZ,tag,(DWORD)(strlen(tag)+1)); - // Disable DHCP by default on newly created devices - HKEY tcpIpInterfaces; - if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\services\\Tcpip\\Parameters\\Interfaces",0,KEY_READ|KEY_WRITE,&tcpIpInterfaces) == ERROR_SUCCESS) { - DWORD enable = 0; - RegSetKeyValueA(tcpIpInterfaces,_netCfgInstanceId.c_str(),"EnableDHCP",REG_DWORD,&enable,sizeof(enable)); - RegCloseKey(tcpIpInterfaces); + _netCfgInstanceId.assign(data,dataLen); + + type = 0; + dataLen = sizeof(data); + if (RegGetValueA(nwAdapters,subkeyName,"DeviceInstanceID",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) + _deviceInstanceId.assign(data,dataLen); + + mySubkeyName = subkeyName; + + // Disable DHCP by default on new devices + HKEY tcpIpInterfaces; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\services\\Tcpip\\Parameters\\Interfaces",0,KEY_READ|KEY_WRITE,&tcpIpInterfaces) == ERROR_SUCCESS) { + DWORD enable = 0; + RegSetKeyValueA(tcpIpInterfaces,_netCfgInstanceId.c_str(),"EnableDHCP",REG_DWORD,&enable,sizeof(enable)); + RegCloseKey(tcpIpInterfaces); + } + + break; // found an unused zttap device } - - break; // found it! } } } - } - } else break; // no more keys or error occurred + } else break; // no more keys or error occurred + } + + if (_netCfgInstanceId.length() > 0) { + break; // found an unused zttap device + } else { + // no unused zttap devices, so create one + std::string errm = addNewPersistentTapDevice((std::string(_pathToHelpers) + WINENV.tapDriverPath).c_str()); + if (errm.length() > 0) + throw std::runtime_error(std::string("unable to create new device instance: ")+errm); + } } } diff --git a/service/OneService.cpp b/service/OneService.cpp index 4ee473f87..670d5641a 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -943,6 +943,17 @@ public: friendlyName, StapFrameHandler, (void *)this))).first; + } catch (std::exception &exc) { +#ifdef __WINDOWS__ + FILE *tapFailLog = fopen((_homePath + ZT_PATH_SEPARATOR_S"port_error_log.txt").c_str(),"a"); + if (tapFailLog) { + fprintf(tapFailLog,"%.16llx: %s"ZT_EOL_S,(unsigned long long)nwid,exc.what()); + fclose(tapFailLog); + } +#else + fprintf(stderr,"ERROR: unable to configure virtual network port: %s"ZT_EOL_S,exc.what()); +#endif + return -999; } catch ( ... ) { return -999; // tap init failed } From c826cec1d48204fb9134fc4e07ea35c1b0637973 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Thu, 30 Jul 2015 17:52:35 -0700 Subject: [PATCH 07/11] Bring back _winPokeAHole() to dynamically allocate firewall exception. Shouldn't be needed but seems to help on Windows 8. --- ext/installfiles/windows/ZeroTier One.aip | 20 ++++++++++---------- one.cpp | 13 +++++-------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/ext/installfiles/windows/ZeroTier One.aip b/ext/installfiles/windows/ZeroTier One.aip index c0a6d91b9..6f98c32ad 100644 --- a/ext/installfiles/windows/ZeroTier One.aip +++ b/ext/installfiles/windows/ZeroTier One.aip @@ -23,7 +23,7 @@ - + @@ -116,10 +116,7 @@ - - - - + @@ -198,16 +195,16 @@ + - - + - + @@ -257,9 +254,9 @@ - + - + @@ -302,6 +299,9 @@ + + + diff --git a/one.cpp b/one.cpp index d384270d3..f5cf9c005 100644 --- a/one.cpp +++ b/one.cpp @@ -766,8 +766,6 @@ static BOOL WINAPI _winConsoleCtrlHandler(DWORD dwCtrlType) return FALSE; } -// Pokes a hole in the Windows firewall (advfirewall) for the running program -/* -- now done by Advanced Installer static void _winPokeAHole() { char myPath[MAX_PATH]; @@ -779,7 +777,7 @@ static void _winPokeAHole() startupInfo.cb = sizeof(startupInfo); memset(&startupInfo,0,sizeof(STARTUPINFOA)); memset(&processInfo,0,sizeof(PROCESS_INFORMATION)); - if (CreateProcessA(NULL,(LPSTR)(std::string("C:\\Windows\\System32\\netsh.exe advfirewall firewall delete rule name=\"ZeroTier One\" program=\"") + myPath + "\"").c_str(),NULL,NULL,FALSE,0,NULL,NULL,&startupInfo,&processInfo)) { + if (CreateProcessA(NULL,(LPSTR)(std::string("C:\\Windows\\System32\\netsh.exe advfirewall firewall delete rule name=\"ZeroTier One\" program=\"") + myPath + "\"").c_str(),NULL,NULL,FALSE,CREATE_NO_WINDOW,NULL,NULL,&startupInfo,&processInfo)) { WaitForSingleObject(processInfo.hProcess,INFINITE); CloseHandle(processInfo.hProcess); CloseHandle(processInfo.hThread); @@ -788,7 +786,7 @@ static void _winPokeAHole() startupInfo.cb = sizeof(startupInfo); memset(&startupInfo,0,sizeof(STARTUPINFOA)); memset(&processInfo,0,sizeof(PROCESS_INFORMATION)); - if (CreateProcessA(NULL,(LPSTR)(std::string("C:\\Windows\\System32\\netsh.exe advfirewall firewall add rule name=\"ZeroTier One\" dir=in action=allow program=\"") + myPath + "\" enable=yes").c_str(),NULL,NULL,FALSE,0,NULL,NULL,&startupInfo,&processInfo)) { + if (CreateProcessA(NULL,(LPSTR)(std::string("C:\\Windows\\System32\\netsh.exe advfirewall firewall add rule name=\"ZeroTier One\" dir=in action=allow program=\"") + myPath + "\" enable=yes").c_str(),NULL,NULL,FALSE,CREATE_NO_WINDOW,NULL,NULL,&startupInfo,&processInfo)) { WaitForSingleObject(processInfo.hProcess,INFINITE); CloseHandle(processInfo.hProcess); CloseHandle(processInfo.hThread); @@ -797,14 +795,13 @@ static void _winPokeAHole() startupInfo.cb = sizeof(startupInfo); memset(&startupInfo,0,sizeof(STARTUPINFOA)); memset(&processInfo,0,sizeof(PROCESS_INFORMATION)); - if (CreateProcessA(NULL,(LPSTR)(std::string("C:\\Windows\\System32\\netsh.exe advfirewall firewall add rule name=\"ZeroTier One\" dir=out action=allow program=\"") + myPath + "\" enable=yes").c_str(),NULL,NULL,FALSE,0,NULL,NULL,&startupInfo,&processInfo)) { + if (CreateProcessA(NULL,(LPSTR)(std::string("C:\\Windows\\System32\\netsh.exe advfirewall firewall add rule name=\"ZeroTier One\" dir=out action=allow program=\"") + myPath + "\" enable=yes").c_str(),NULL,NULL,FALSE,CREATE_NO_WINDOW,NULL,NULL,&startupInfo,&processInfo)) { WaitForSingleObject(processInfo.hProcess,INFINITE); CloseHandle(processInfo.hProcess); CloseHandle(processInfo.hThread); } } } -*/ // Returns true if this is running as the local administrator static BOOL IsCurrentUserLocalAdministrator(void) @@ -1139,13 +1136,13 @@ int main(int argc,char **argv) return 1; } } else { - //_winPokeAHole(); + _winPokeAHole(); } SetConsoleCtrlHandler(&_winConsoleCtrlHandler,TRUE); // continues on to ordinary command line execution code below... } else { // Running from service manager - //_winPokeAHole(); + _winPokeAHole(); ZeroTierOneService zt1Service; if (CServiceBase::Run(zt1Service) == TRUE) { return 0; From f6ced547be10ba6c64696e145a8306ff66da7d1b Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Fri, 31 Jul 2015 08:56:31 -0700 Subject: [PATCH 08/11] Dead code removal. --- node/Node.hpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/node/Node.hpp b/node/Node.hpp index 0e966aa64..2a283eab9 100644 --- a/node/Node.hpp +++ b/node/Node.hpp @@ -248,9 +248,6 @@ private: ZT1_VirtualNetworkConfigFunction _virtualNetworkConfigFunction; ZT1_EventCallback _eventCallback; - //Dictionary _localConfig; // persisted as local.conf - //Mutex _localConfig_m; - std::vector< std::pair< uint64_t, SharedPtr > > _networks; Mutex _networks_m; From 8d09c37140ef589d9e23993e20a080936b90a7aa Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Fri, 31 Jul 2015 09:37:13 -0700 Subject: [PATCH 09/11] Remove a bit of redundant logic, and also announce MULTICAST_LIKEs to controllers (for future use). --- node/Network.cpp | 2 +- node/Peer.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/node/Network.cpp b/node/Network.cpp index 549219d74..8922483d8 100644 --- a/node/Network.cpp +++ b/node/Network.cpp @@ -533,7 +533,7 @@ public: inline void operator()(Topology &t,const SharedPtr &p) { - if ( ( (p->hasActiveDirectPath(_now)) && (_network->_isAllowed(p->address())) ) || (std::find(_rootAddresses.begin(),_rootAddresses.end(),p->address()) != _rootAddresses.end()) ) { + if ( ( (p->hasActiveDirectPath(_now)) && ( (_network->_isAllowed(p->address())) || (p->address() == _network->controller()) ) || (std::find(_rootAddresses.begin(),_rootAddresses.end(),p->address()) != _rootAddresses.end()) ) { Packet outp(p->address(),RR->identity.address(),Packet::VERB_MULTICAST_LIKE); for(std::vector::iterator mg(_allMulticastGroups.begin());mg!=_allMulticastGroups.end();++mg) { diff --git a/node/Peer.cpp b/node/Peer.cpp index 3cf0ec4e5..c27afa8fd 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -133,7 +133,7 @@ void Peer::received( Packet outp(_id.address(),RR->identity.address(),Packet::VERB_MULTICAST_LIKE); const std::vector< SharedPtr > networks(RR->node->allNetworks()); for(std::vector< SharedPtr >::const_iterator n(networks.begin());n!=networks.end();++n) { - if ( (isRoot) || ((*n)->isAllowed(_id.address())) ) { + if ( (isRoot) || ((*n)->isAllowed(_id.address())) || (_id.address() == (*n)->controller()) ) { const std::vector mgs((*n)->allMulticastGroups()); for(std::vector::const_iterator mg(mgs.begin());mg!=mgs.end();++mg) { if ((outp.size() + 18) > ZT_UDP_DEFAULT_PAYLOAD_MTU) { @@ -211,7 +211,7 @@ void Peer::attemptToContactAt(const RuntimeEnvironment *RR,const InetAddress &at void Peer::doPingAndKeepalive(const RuntimeEnvironment *RR,uint64_t now) { RemotePath *const bestPath = getBestPath(now); - if ((bestPath)&&(bestPath->active(now))) { + if (bestPath) { if ((now - bestPath->lastReceived()) >= ZT_PEER_DIRECT_PING_DELAY) { TRACE("PING %s(%s)",_id.address().toString().c_str(),bestPath->address().toString().c_str()); attemptToContactAt(RR,bestPath->address(),now); @@ -239,7 +239,7 @@ void Peer::pushDirectPaths(const RuntimeEnvironment *RR,RemotePath *path,uint64_ ps.push_back(','); ps.append(p->address().toString()); } - TRACE("pushing %u direct paths (local interface addresses) to %s: %s",(unsigned int)dps.size(),_id.address().toString().c_str(),ps.c_str()); + TRACE("pushing %u direct paths to %s: %s",(unsigned int)dps.size(),_id.address().toString().c_str(),ps.c_str()); } #endif From facb009a1dbed53ac722180aec1250bbe6334001 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Fri, 31 Jul 2015 09:50:55 -0700 Subject: [PATCH 10/11] Add security notice to auto-update info in -h output, and fix a missing paren. --- node/Network.cpp | 2 +- one.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/node/Network.cpp b/node/Network.cpp index 8922483d8..39042fabd 100644 --- a/node/Network.cpp +++ b/node/Network.cpp @@ -533,7 +533,7 @@ public: inline void operator()(Topology &t,const SharedPtr &p) { - if ( ( (p->hasActiveDirectPath(_now)) && ( (_network->_isAllowed(p->address())) || (p->address() == _network->controller()) ) || (std::find(_rootAddresses.begin(),_rootAddresses.end(),p->address()) != _rootAddresses.end()) ) { + if ( ( (p->hasActiveDirectPath(_now)) && ( (_network->_isAllowed(p->address())) || (p->address() == _network->controller()) ) ) || (std::find(_rootAddresses.begin(),_rootAddresses.end(),p->address()) != _rootAddresses.end()) ) { Packet outp(p->address(),RR->identity.address(),Packet::VERB_MULTICAST_LIKE); for(std::vector::iterator mg(_allMulticastGroups.begin());mg!=_allMulticastGroups.end();++mg) { diff --git a/one.cpp b/one.cpp index f5cf9c005..30d95cb9f 100644 --- a/one.cpp +++ b/one.cpp @@ -904,7 +904,7 @@ static void printHelp(const char *cn,FILE *out) fprintf(out,"Licensed under the GNU General Public License v3"ZT_EOL_S""ZT_EOL_S); std::string updateUrl(OneService::autoUpdateUrl()); if (updateUrl.length()) - fprintf(out,"Automatic update enabled:"ZT_EOL_S" %s"ZT_EOL_S""ZT_EOL_S,updateUrl.c_str()); + fprintf(out,"Automatic updates enabled:"ZT_EOL_S" %s"ZT_EOL_S" (all updates are securely authenticated by 256-bit ECDSA signature)"ZT_EOL_S""ZT_EOL_S,updateUrl.c_str()); fprintf(out,"Usage: %s [-switches] [home directory]"ZT_EOL_S""ZT_EOL_S,cn); fprintf(out,"Available switches:"ZT_EOL_S); fprintf(out," -h - Display this help"ZT_EOL_S); From bf193dd3cfee7c343c5efd7b06df034d8b731d47 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Fri, 31 Jul 2015 11:33:52 -0700 Subject: [PATCH 11/11] VERSION 1.0.4: Stability, LAN, and NAT traversal improvements ZeroTier One version 1.0.4 brings several improvements to stability, connectivity between hosts on the same LAN, and NAT traversal. Direct connectivity improvements: - ZeroTier One now opens port mappings using uPnP and/or NAT-PMP if they are available on your network. These are then made available to other (1.0.4 or newer) nodes. This should greatly improve direct connectivity success rates for users on networks that support port mapping. To build with this option, you must include ZT_USE_MINIUPNPC=1 on the make path. Pre-build binaries are included for many common architectures to make this easier. - A new message has been introduced whereby nodes can "push" IP address suggestions to other nodes. This is only done to nodes with whom you have a trust relationship, which right now means they are members of a network you've joined. The IP addresses sent include local interface addresses and possibly uPnP mappings if any are available. When nodes receive pushed IPs, they can attempt connectivity at these addresses. This greatly improves connectivity on local LANs, since the old broadcast mechanism proved too unreliable under many real world scenarios. - IPv6 addresses are also "pushed" via the aforementioned message, allowing direct connectivity over IPv6 if both hosts have an IPv6 address. - Some of the aggressive port-scanning NAT-t behavior has been removed, since this occasionally triggered intrusion alarms on some networks and proved ineffective in the field. uPnP will be a much bigger win, and is less "hacky." - The rate of (tiny) UDP keepalive packet generation was slightly increased. We were as surprised as you to learn that there are many NAT routers in the wild with timeouts as short as 20 seconds even though the RFC stipulates that they should be no shorter than two minutes (120 seconds). All of these connectivity improvements rely upon a new message introduced with 1.0.4, so they'll only work between 1.0.4 nodes. Older methods of connectivity establishment will continue to work with earlier versions. Platform-specific improvements: - Many improvements have been made to Windows support and stability. The NDIS6 driver is now used exclusively. If you have ports that use NDIS5, these will automatically be re-created using the NDIS6 driver. You may see a "select this network's type" notification after 1.0.4 upgrade for this reason. - The dependency on the external "devcon.exe" binary on Windows has been completely removed in favor of internal direct calls to the Windows setup API to add and remove network ports. These are done via dynamically loaded instances of the system setup DLLs to use the most recent setup API code on your system for improved compatibility. - This version is tested with Windows 10 release, and was confirmed to work on a clean install. - The ARM32/Raspbian build is now back to using Debian Wheezy for library backward compatibility (binary build only). - The Mac icon is now a bit smaller to look better in the dock. - The ui/ subfolder is now distributed with the Linux binary installer and packages. This means Linux users can navigate to the UI at http://127.0.0.1:9993/ and enter their authtoken.secret to use the GUI locally. (This port could also be accessed via SSH port forwarding or other mechanisms to administrate graphically from a remote system.) Other improvements: - The new beta SQLite-backed controller microservice found in controller/ and built with the ZT_ENABLE_NETWORK_CONTROLLER=1 make option is now in a much more "working" state. Feel free to give it a try! If you tried it before, delete controller.db before starting the new version. - A few tweaks were made to the path selection logic in the hope of eliminating some flaky network behavior reported by users. The next version of ZeroTier One will focus on performance and memory footprint reduction, and may also include perfect forward security/secrecy (a.k.a. PFS) once our design is finalized and reviewed.