pax_global_header00006660000000000000000000000064143675372620014531gustar00rootroot0000000000000052 comment=041953ff740ad5df1e0a713f3a627509f4d2ddce turn-2.1.0/000077500000000000000000000000001436753726200125215ustar00rootroot00000000000000turn-2.1.0/.github/000077500000000000000000000000001436753726200140615ustar00rootroot00000000000000turn-2.1.0/.github/.gitignore000066400000000000000000000000121436753726200160420ustar00rootroot00000000000000.goassets turn-2.1.0/.github/fetch-scripts.sh000077500000000000000000000014351436753726200172010ustar00rootroot00000000000000#!/bin/sh # # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # set -eu SCRIPT_PATH="$(realpath "$(dirname "$0")")" GOASSETS_PATH="${SCRIPT_PATH}/.goassets" GOASSETS_REF=${GOASSETS_REF:-master} if [ -d "${GOASSETS_PATH}" ]; then if ! git -C "${GOASSETS_PATH}" diff --exit-code; then echo "${GOASSETS_PATH} has uncommitted changes" >&2 exit 1 fi git -C "${GOASSETS_PATH}" fetch origin git -C "${GOASSETS_PATH}" checkout ${GOASSETS_REF} git -C "${GOASSETS_PATH}" reset --hard origin/${GOASSETS_REF} else git clone -b ${GOASSETS_REF} https://github.com/pion/.goassets.git "${GOASSETS_PATH}" fi turn-2.1.0/.github/gopher-pion.png000066400000000000000000001437161436753726200170320ustar00rootroot00000000000000PNG  IHDRzTXtRaw profile type exifxYrrk^ /_Vӵ-"ϩrDs_X()zQF|k 9|疯o_3_|o۟??ߤu_r;'z]PNǯ?}|K|{.F0ÜtŕIwɬ ]^nũ*I29}U$$ޯ7]݄`} sU㯵I{rHϤzfk_]VO! {j5SqjH_bk3lU{…3m6#:5FK Ydso'q0il,,g:ڭzgbj6/S,/6>Vlm3; &Je% ZWtvtQ90r$ ~DټYפ #[s<2tM_gj/5uVvMu=W[=ٱzǰOceuED586HNl,s"UAmNoiMs^:K#`0B9% Mijik*F ,П0?z&5xJRѪ VTQ!? 2,|v?=;Cyz-yw#6:%,2Bך9%,2߁ycNmܠg^9}mlyJk.+<*K~= AUmxz.WoʝOmz"r:=1o|C{Li׺aYe*s*N ͓O$iCqozsJ&D c% @)םHlcdlZ R"V22RCq'"EPڟ"cj츫N~+,h&\LbY|or47|Dn1[f=T&l,׭t= ZiPz8q'/ȲUʼn B{feՄԢYTz&7gж]|5 4>󖪃MؗaZUŠ`"[,aCdPBkr/RcDfL(ț=UcMyr{΂Z$X8FZ#̜ǀgFb"mVT'dBQAv!K cl/@|&A9]l LFI ;B @ 9*gp9hР7IUvs{\NI*Q,`=@u,?M8}MлhD )[P`g48m}X䡚A-c@]jS`ǔ @I'Nq"711a`mPl2Zc@}0CVI̦΢VGQFQ%;7hdB闶vyp6"d d1>A?M`hr;FX\vQbs$,ܼ^%XM<`FbT% G}S'*b?ǻtB۳h /UMt@@EH_ s pLU^4) v"%@ρc\(:L'_t6X^D`.8I z׀5إu$)(cЖ:#Hp8H}g0_%tA _M هL+}y#Ep NZ0Q#I"h8TZ^$ګ[7TS$n\~ !jŖֱ@=ՋKz 7w,9W=8'('pRF j^Sj{.DG7kT? 9OdK8%+aT-ŽLMHG,|aKv}A#)n?$=GH~T B?u}5DGKPH[Ɋ&t9U(9٨"#q!HIa6q`!yK B!iSy`v>P%QfB-> =,$Xjhm>kT,|_'+CLj.`8/FZha|4!N(5 s xXaUx(#*ŇJ'hmi-}ei7hRф$MeP1\2F,"!rnpvG= P CO YUVnx9 7o*|Gh %cO> AYbČ,/]&LװU/p@GPԸ $jg,EBĽ\;HvTJ Ah/zG eF\k !CoK|įHbR:P+MO&хHaOlM4[%:-D xJD')K2mQ-Sgi4 3{GpA$F\;CP.xP`ҭtևl׊(2Ԩ ACT  iiD Z_n/y@d9b.yo{[JÈG,1G|J˘M f(l&O,boF4|!) Aw:>-% ā,BuT. Ѧ"k nO!.=+ZKb* K!Mp FF@[Xp3W >3}0/fZ^EmY ~ L| W9&d VޢӀ(}cHZwj+01q| ٯH)FNxc~`Z5+u6 Bat'P#wʨmOq4_rt6Z(7 Ryn]a$ jJg}j(U}1GRDBfA`)Ċt "X)kn)P fq[:sG\[Cu35!,U@HPm%:,g=* S@[US]XjlF )q Lc1_ t$FM;&y]Dh٨A4;kPht&$S^ƕ7xt;@M^"vޅx#1(=@x N8n usi"` |;fI/YB}wvA%İ]H\ӦشdH Ƒ 0 :xt|!$طJ"^iҪrt.9>,D_[ s,I~ B ]>|ըB7n"Xh#. R$tDu@P.D1dV"T ,݂Dkl%R'\ n`';9[ ףP{Awq}jDN D5"<&XRoD/t3#|zd!ڴ2\C^: TҬXzOeٜ'tf9KŞ#jt¸x@ՃBAtjH."A]AT#nRCP ɀ.ms1N{ֳ *ut*~jw^NZ:aK~М<A#cAHcczTxn:&,|BsVzZgxYMc!tW6wѐIOurPªء}^1xsp7&Ƥ*pwnAnŽ-azJtDz9IJMHnn,1\{E#<8zlbDzXO鄔9Q75[Ь1=|N:0cN"OJ<{ܤkL4=QA.d$?*婵scɑpC'[z )N^\े@.-OH\OɣFQ4A] 4Y@AARu+o2iB7M1;@=cg- O( ^m] u_Dͱ U T,=#є^o ,);ɄJXtqkMNhhN$ RAecW8nf%4~MBH:x4euAj,z*1@iwR`{a 8ѫ`v*gv=4rQ[u~#>K ʖQRd=;j*r6gwS1>:?zBkhbE@_4jƣ'v"kauTMxK#3zƧ~?\V84J yHH~= [;fwtBfZJTiW~QLm W =|= ,&l|d 'i+.tEr'ȖlZCC~]M>4n.Q!M=N 'ԋ@7]|~$pi!U&R?wiDe@h&Y0IkyWP}?q+ԮƪcP 4|OmTota.#~ȨB&+Q)D^Nbt r7|ctP{3\0٘-5radev/ЗH2HV>ZMj5KƼiwY %-F|t t`Tb]O5!֫P>+Ga6TgR E=bFo.qnҦ1\d#藎Se0}j;SG[As>݆P)\M=o뉗V,zӃ``DyTЇ]hԢ7䬡tRٳ-@Q ACo(v a*&տh=/_P/ \<bKGD pHYs  tIMEgܰ IDATxwչgfԥ}[u ƀ1@4R(KBrIJn~N$\n `b 1ƽoV:9?FGciWZK<>f3{}                                                                                                                                                 cF r @Q{`  =6@L$qcP@]UtB15Gtzh'Q{5tݽ-3Lݛ &C04E6&ua6 (Gr`Tc nLj3jʣr*Q!&`"/qF->;t0?-s*:lʵZh\aP\P mbvZaN2 "^>tbHTj7p ~nD9AL5q%D^x"υx*Sw>ӑ ieYDC$)>tz8 F8bJs,2<$2IQ"+<QyaU ¡  `Ⱥ>&+\,20ʆ=>>!ot7{ 1z `bF7@[L%X t߉ĨОʱ*:;l&PWbRa"k,5W 13U{*ora0APTDaU^oXɀ_{0,P2+cJvx :'Px |c[/bpXflx#<$''s ,a(P1j땠pnR'9g . |608VlI`iD11;7wvΛ]g6jA3*- tT_>؄sfN1$CqoPzsW *oNp& ZiɯoZ2Y_x J>pBp5HB#89?ZwpQA98/@ SWlpH\>hMY&U,ЮG>!(WZ6h^wB8}˙~jK&#w{Z%0I@V;\q0~2A9 O/1{JIb}BJ`@)|7o5!@[ 1¶X$I )M"13-㋬A$d7 5Vbi+y%m8QU|# l-{4;q%(>){v@kۆuVF=CALжE&ZXFLH_ |3[nA|4ܘu Pc3 Y9o**|>kaOxIɼ4w_PD&[yn`խC6S2U3n}]^_z^ p[OD~" cNHQԆ~uap/4^l]xt2k&b^ l)2f1h31cLMwrFwyd˻g5E Xh^^ L1sybCͮV|j f!{5"frK$&w㪜G 1Ƹ+ix%BSwStжp('f٥L1h ldGct(sJ[c7FVJ1??2s?Y^L\=/t) )+jA`B  Gc2ALY/zfd[78瑈¥w$v\uj-dӦ9~Fω_ߍ qqjmI3Ҍv&.c(e _+5ƴr9>}e5{͒pGk>2ʿVN␤oA9 .j)<Pz) bŪؠ5nF\ΘSc;ӋۆSqho},pŁ2%:PZE&̂W9BsP6K_6_E=_ ;IUVT8S+}@r:dU1Q;|+c "E8c'GRsʏ=۳/VϜJ'U 0G]Ϯ[+XCBRsgO-;INаvO E9乳]مŗ#=lD[?e[Wxah{Z &rI@XN*wʍo,uJJ*BU;lN E61f^XoZkDo^Qff{JAe5&IL[ގU;r5*ۻbUO՞P5nG H↖"h~ dɰW1&D7wa,,Ypm % fI0y6H5Yy |E&Q|a5t>hWUCzZ%"hSnM  kc3n9},WPK/_<l>-*Z":r^̿bg2OK'S#b8A}y^ wToPe{ *?Y^kt9X7b,0P_$9ǖ@pgwH=+69XML&Ђ6XBw &h`V8!)o=8'&U8Guu] ?Y^ݵSz79|xVkNw5&R%L5E3\cC@h"/`J$ǗpI$I`vײVI;CR§\RWH"w] @EBO.QfIz{vK&a"XEMQ|/#>voyۀl2oA+Id9?\?SJyMYʜōo!,sG@tXR60j3eM91Ut,wn0 ZA LLE<'\ECeoG6Y$_nJ-w.џg@ .+_ICXGDh"BLd[%eC'7:\.'wN-`JDu0~ bhە&2G&SP1^m!#!CI|I{Hpa}gg%C<+ V0k&6q3&i/:5-A$r~x> 1 mxDZ-X/C`"1;v;w~4kV3gZ}DUq]];30րI|IqÁ>w]G -'mDq⒲=ԴEo?a6YW/eI'AH;3p׾ZR⋃NI:,֡!}˟G0SnB( \dˮO|,]j WxWr .UT6r73z/^EK## KDe|~~]d}gwWUW.I3vpWso'n$#M[. -++ @+n.8|qe%x1B|mԍI}amxP άVS8,b/m)%̓- 3ۜ؋M**OkkeSl6%R;RmU _+GAh-0;8#.7Ձs~X97oB|9lJb]/u3nzi3|JIMJL$}AYu*Du\أ.5Z ³_ֻK a7 ;,zpc}=Ͽ/HXj䓇oZ\\OQKy/_~<!4S2e'&⺓KF "pUXqo y}Po >;{Z}pUɷAǗ.zhٷh\GT_ fA5Ʒ-Ȉjb p:wuAbLxzQj/_^lhtUжĹ*(0*F}c}0\ဿ@=ڻ| -DC,.qHY((Ѱ% V~왢;|?ʺZ0-ah֯XQ Aˎ:h{?J$hvsHۆ_7ЭY̵k 1q':Թ]"d$ƃ@X~q`do9z vu m"Ҕ)2Ĭ̪*.Pb.yRyª{ܺɄ Bw;>0Ϙ 9ZDE~﨩# SHrA梉3,:='?X~9 !,7v*a9syg;n+\[RBW ~vvmJ_k= 1cK7\,?8SK1Lζ΢-pAE-B$\$f1͍lN 0 *ސb{0(* 桠*N++3EDG<-cP\ _ۇ>x9Hn!o9F?cl19*w]-a}t31Q`1:I DDEx,[z`܌z-ݱB~?.o$MxyEېH׌]Ftr]ӛZR2bCύ`c"tbXa>ᠲi;,3[߯ZO@i2hl]pceDLY/\^~ YC.(6eTZuXb*lN57]4}?0@bӉu_3Z ֩S&no$<MmoK^⩎μ6@,>,6׆ ZN_b|FVco5 Z~8Yo~i<= m2 :=f̟o߿?pBQftkR^-LKҼyrʉ%O!,]4h6=ziNyvD"HKKKgqqrl–.K,;>x#䑵}"0LԭXoݑsOu -)F|'ZRTǴ aI0}o' )XjrE+ G"]yak֥B$y+Vk:w=_ihG!LR=djG_U7!{~CRmrMM3Tn,˞Zwee\N M৯-Y jK;ϟZ梮=fxG8<]%!q;_-Oy!{dmECAFI𼂂L[dVRAYf2Yn\D=|| tzKl(jg8ɾf/ xh]۟l3S+۹m۶]{' ͛g[paUT|x&߭~>rG$-gd}r"ا~_H |.Iʨ`JPQw||h[򆁁QN$aI0 p^2Ya6,=D+fe IDATϞ|qL& Ore+c<7IU@o>^꯮Fuu5>cw{9{96mjLpG2Q{ikgO];s6WG.3L>]kT g_y^PH!m cFAXVQ~1==ES:(G< p^s]{-O-))n=@й !iξqOjO@IX#zHkk+;wjV [Z߾S땃ԽGV߯]`8pDGD/=uiLuU[Hm6d:N Vk< ?+$ioL|?ىBߐǦACi(1d'Zۑ$/73fH$MXBD{ 8|I!|W0k0jRVR7Xz}p=;?`}:W|qrw!t{|P~YNj!Lң'eD[n~S'+22'ҿL7,!Y3])l]ЭIL\2q˰bbfѧMsf}{ \9QH~RY$?@bn`Drʰ_~0X\\fgojH΋[Ы\_ٴ/eٸ!z|bL.,\80/4 /" [[}mmߺuGq@^~_ FoG;qv]h/c,y@L3"QUۦλ(3׏BY:-jiZVU~.~ia] DInۼ9!ycnۍ_Fx o^$,̲ygpBv+?:\y6lد^6 SVVN@-ȑNۿ C>,G@/^0i>w$ cG`<#%S+IׄW x֬l-YBNuӛ0c>}\p3te =nGUydCAPĉi M~w.K)Pnd e۝KL}g]k\7{|c4LJk;ԩWsR#{ZqCom޼L;1Scw0uvv:Kn Ɖ!"s c黅M</#t&c^XtHݛ;fwyG;wԆ U݇)/Cbn/pOo\3ɟcK 8`ɔi p}*_,+{_:I>gep7Xu n[jG6"igy'؝W - pvETd} &;>c̒V9k׊߱9ɳ} -%D t(K3VeuM} . ㏏\yioG~$]w<{!csXw-X<;d>c.']ltz89~:Dcq^/6s~;Wݩ5QKIpEgV)0} 5kT$xYbYD> /ҵ^Db8/]kй`{W5Y;|pkwDKq=7#[*Zhkۇ\TI%t R f#{Hk-GЖnV>4Ӡԓ$G OBs)$!xe[}}ݩB"NYM$Q_E֣`0*ཟ RlWOКDNPV0X7z>spR_ƃCQQv*03g=Kiwo׏hM|45\8M\:yМv!YX!,d}{pBf}}}nj34IEDV-~s0?~nz9rT j5GxƉtPV֠zhqɞPDc?b😘-c pNy+͵s74W\u# H% NirdCPV"wqGF.h(ې[G&,+ߧwqsΟ@?]=`.X!m,{~shhi&[tw|/JGy m"lf!kО¡Kl2妛ni˰1n0 IdU.&6kn-~hXAeKk+\nd"cXb#4P}qA,Xe̚^hUUU6-ӽ 2B& -"6[kXP':r $ki a9({GXİjUd?&NY8>oJ¹] qK<ى@s1+AAYhuscP.;Sp~=vG.mPĈ2,׺31ğ=!Qr'KX^j=}C|hc绒^`B1jw|c`ook$0(l'S4f뙌ʑKZph@sꨵ?,1z]`B %viY=L pJCUZ{FT.{~)b-5r49rhgkGoyn/NrgH|mCz#TΕ䉈}gߞ1ъ{2>(P9 o'&n<蹹qTkbCr$c8 8$GVf A |Ѻ`MPIDs0rsEQ!޶n̨,2F:fTZpB*#5c>+3 О:!BIN xΉeO#nwnID\BطQ?Tf68.ٲu*V֗ϟq\EGzh7#6ցYVy,2[.,OCKRk]=C{z(d/N`扙TYyI/nQIY؃ Qv5|t@e LJ\Cl, G`I`a1 `TVV69L X3*-b0\|ЭOZ4Cuh&|쳱q-OJMf& c^ rg,n5b}}P}nE Yalp)lC`KEc&Xb]9] {i8cT,Έʳn ~o-/i6a6i6UQU +.]VlXl==%1%WcEwBj,0mA0qG&BܐI}"C I"."? M0S`3IY.~\o!pxG#~$26Q1Lԝˡe5g9P&,#gM_1Hk2>$Ĩk(]Ѕ6"C,~_DYoX/!ŏ95l_{ ? WoXcfM.0axւMO: Q[-jʏ=qwի#N6 ,!71nNP@OWl.)@L;mhK1o㠕kd]̙Gشp9ΝpGNSeꗝH+B_<2ɢP͜ ܋" O6K ;2`5<\`U\v:ovYv$3MZ* :,>q x`ֲa}HX}⣏,`0F ruLW5;DHT95/;b̺a^l|6bAq2Y`"xix@2ug& [,m{{{do|DWUy'x?v_b-m+GiK K~8Ɵ]!7z"{zw?(^kC-Z"{:Mu:ȲOXxqC&MP@p>%H2ƩQ z8{I8/P3sPPY_랖wao[oB׋eMM8+֮b:/ܸvQв. 0)Sc3~W=OoBۜ5ӥ{(5Vks*,! HLwD 80}~Y9mڴԄO&%nwo_F[h^Mg2a5 ^}6ݘqcli g۔bsx]ÉZ`Ll#MŦg<`";wGR '@l8BOڝB)ơ2gg]ld̆: Y+5&I∩EA8l Skؗ)0$ZWWkP{;daJ9nۢ/E,cl)B]Q[˲\[y$ ml r!yc'xRh? 9A &~w8IKq*$dZryWBy:twwOpK$ڎ010l,/%%~+](gj?q "%|8<Q M_Cs;[fJ$>[8LB!ȪR,ΊrfI$cg&U]V, `U 鯱bL(O7PzN-~#Sk{` 3aR'6؍ όo5w°bclYᵛRUKbukUQ"A(Oy iOcWN/|3c4cwVPM,)ȓ=[ޔ͌-``" kJO&Gy*XM5Nk[[ۤ-<vLרN"!u6"KYI߷;?t*]_obb%4mE#ۣamk p#)UT=Outw LLLPG`O@jX> 5.ٳg2יH\2\~| }(QBf6UDjn)*8$]ϞU`5&|17j椔aTpcl-|+j&8h#6`趨*drwwNQ6u*njP{@ې&H/LX1$Cۤ<!;zƘxr#c(J$jO۠&-Y$O@kڵvc8 {nWY$${aUOy(hp%^S>s{~8Z0\STTf h۲F\dィZh&Jìؘe8^91ZR[Z7 01TꍟܰaY pD Td+ $W_>rL3ƖxZl^ cl$ ;48`1Iyri@C};+32Ъc룂lQq} e+47 {jm  4[fe3H1jp(悎/6)w$P IDATrMx``" Ng;*6JKM"+B7J'|o89xZZZҊ!Y&CcXrKpwk8/[PKPRm?%B(/{fVZ[WX;}0tb/+0R͎`FtҐKL"af:HiMuBF!J֠ncRK>UP]]-]uUgÈ& \s~,5cl?#%_]U';,bmf:y}W=m=֐,ms4Fd,sJsuKEYlL+bZKJDppK L$2+͂&Z7'5؋[*Yb6lQp(Z8/)MA6IE eY$H h.!eڇ]dF]<P! ~h[Ĭoc,XhhCzʽj2ƘX C~6ɰ:z؂+7h,5 Mq9B$O >__Du6b А{XfD1wdٴ"sd%5^2e>駟9w)Pjk`A},"X'~>g sk mb=yeS=E)G86tkICֿxS#{}C{ࡠ_.}-޲eKQ,MC[=kΩuo5 Os]aW`<C7࿍Ϫ1 Y[~wqvGk BKR񑖖L8a x@Z8 ,/ukMHW|@e1 G]Wdvh[ [o#_}ۢz2o 8S?V˪ڙDtvc8DV0 0aGH{]Яm 欣꫍Ǘ/_~>cJ7e?c m|#,lmʍe5zz?9=WTEU{BޱB|\ȳd'|{#=z }K?{ba(f@CD>%AQ__xqUcK93] -=,bY.9,\ʖ4~O-D`͟>E-tڔg7*vao{OE(0I"\Zˊ&1CUQ,+2A_ٱK~Ӗ=3N)ƤUxC@n p<0")s\\UUSm+" HeX4y[Ƙuh5g1?sO\/"E.B/E1mUiӜ'} ;maގXF*ʷ>g?wKw:?K lR0Z/1ٵ M(lwW(4,II3N+shL5G8 7<1mFetAsň(LF8 "/A7jyAtaDxz'Z7X̠,ceZPy:cw1X@jkU'jL }+0sUiSHl7%>n>}zasssɳ>kwx`N[nuYŋ}Ѷo"zpU6@w~_vEq7QM)OvRq]2@+Yj$ u!:s7n4X{ҌX~{_HP_~b|8^%Oݿw۲r.c!cWc3M޽;ţm6u`` }ODtcdopPyI<$04NdY֮MXM t$}dWvw +gNww4g'teۺeH&&tG&FfTZVIh=N)3y ̡?ԩSST\/RQ9c#sU=d@y*+$xƀcl 0pG25yg:jť 0 d \5jKJJf\Pi pCMVQ`u% w];09+U9L4Hea$c0w :uq ^lɈ d@sf49wOtS]zBHP +:+ %H<)j,Ǹa;*p&JC('-|~yٌZlڴ 10"<ϧEӛL&^lܸQ,p $JX)lP;__Rb%rg7 );\ 2P;}ZZD8a8IL};X̼S9cSӧOPIR$qϞ=V(Ƙi/_z[1CCd``"kk+&SjktW'>[v7K9L-#2ު t?:#G zJmmm<cCF(1N 6.¹v!.Кx3?_S×jf9TOWTD3Z>H-`p8*)\x10 )8&I8_bJ6.MX6+0y/B]I34Jidn PWe9mx ZWn\{5kRO~#ݣQ@!0ۼý\]5>~<Ч'nx#"Uи}@$g(Bu U2C;f0_<G v:::ƌ8 J~fb]RFz IۼLx5ehPX>^ 6N M|dEIb4O1&Ss|KFiW|,ތSZx޴+RuybPNj>H k@u-d hdHlp]jGly@{{HחdIf~q7:'ܼ<7+B͕x\|+OI|PcV> _Z*EJMH<8^>3V^ݟCuxn͚Ùd"X?ij쏛G 4-^QQі^ꫵp)5,˸{@T_8cJӖWnu>+G ZqN+T > e|˖-r GܬyWEmJTWz DCf_m7|SmmIRn^Xe_yj?b ) ไř󡦗M䟡w+s=f5PIߔ+MC KmԤ o>WX!P^L[n5\c555aB8z(^|EKذa`幱a8o^xhhgb 9ILP]?$[08G҅_4si&aˠ.?TXG.,/,U j]Һ(j͉ xi\.H&YyշCKsDpڴ$ZBV~}ěoV啯͍bCjjBp8|t"'a4%nӳw^X`^](1 =u6p5TWj4㇠^Q@YZ֨=3Y<r%݊#XQbyBr7- E|ye9-+_W \BVȱfsr-Mv.+,(14E"dt3Ugko 1:L<`:wAԶZxrPʏ|0p2@*^轣HXI[?[*Hn++-:B|L|@S &. I3$p$328( cxGcR}'n^TMb5iK6ɅyȌۛܿ^?SUUq]*3~[LrA嵋/FÑfuʕE ]6-+tV?Z_¹X`v氟1Frnqv ϕbTcvͼ$pC8T|I 9c{$ET=, Qd8hS4K1J.8S>\XvW dpPs;ҩ8kݧUf^]K_|B\]3F?c Qj^4{zbJ9ju6$xDv]RtX$f o͆J}9S7!JF7ؑ*11v;Mֻ//?wYA\1TrĬEa,!ߎu/BM╮IyxT0.ʬVTLVD-D<VCun//eM)s?W:Rc9Qq8s~~kEӝsMp_:k ǔf b{CRwox"#b2 IRLcn+O N$3B/.+(*+GWXTx7ȊJZ.u܋-t ߻aΰU<~lg0*LFxw<^ -mLShQPO|hZQ]&2'GSO D0MḺx 5aid"B׷Wم5W&v ERX$юȩo?ߎ^(4ꉞ{oJNXhK&I'ټ  *7we;kdkKK*U]@Y>ɯrBU^:#K/*Ӻ$91e()NJ;N[f[z3ʠP=5/EPhq;Ң8ŏ/{,H C=K]]۵W/vꋳy󊬮]Vľ|Uq<. ~P_?r2cS\eSʢ"mЧs,Å SY1< '|?ښժiZVen.;v:+ P|]pۼ^$I&_Ɂnjj.Z}yluA 0fZ;:Fӗ+DJiCwT]ahD #.Iś!;0z$ cI$#c=[aܱ>2ժY$R M < Pl0lņ;&R`ǥ Oܹ eOEĕ<م«#=+H; zߙraEE AE`~ 8[;4= d4YSb#{IPGsm}O|qeEgs{a)qs~ܹ|}Z>_tSh Ojʭv"YjN}$!mvUQQn&ppmhxl=) IWKy!߶@)& bt)D)Eܾk]f3/ bWט -XLZxB|]I ,qX,)DZh-f?6 G 0rKGOP EJ4&)X$ap>yQaI$,? I nW5FC8 *`VMF}7f^hss:s{_7Xg?ZOxL@%Z-Ţk!.삯m2]$7i&-NCaO96 IDATۥx˕&v{Fkf@SԲ$rX̜MX P8&·ەt!ꡮ:ŷ@W@p?"所 Jα9MK:A\{'Ub ݶ7lzAI:w<\{PRwcv 8]vh*xXy>s~v{ S{/T҈wP vA]?YP{V;ϵxH\ 1pܐ`?gW2pDx͡06f|˥NȣV%΄ hYy>`* `9fo/[K $p?u겥aE[4{R::1IybArl^?k7~PjH<1Cg,`#V&USa:5@o$MG.Gd ](X f w&`Md$LQ+xoy3mLAJ gO, =y𗺺UBƵV3, 9MP]wxB@pʵ )1_3ـ_i7,@Ɵ1 )Ҝ4 N+d?gd8^T#nn)< pFJ|fa[/8ӓuG>j4\vT2 AZ2&76y<2cLD\"vEeh0nן ,fP㉶ڥGgz96]M<8ڱe$C.˔'P4}Сݙ|fK9nX$$v$i`E5vFk%Y #n^1`Pz{;ljOZюoDjou ؤNc}`ǣ]"Dkl oyx$> 'CSnSů>f]|؈$)G V&ӘjjJߕ+3JvvRTtrC#2q%J V]ݶoD<9NSM;w]Px|#jRLHsܲs؞=i/_tܱ|[o3Kh&@8{@bB=A8,0NA %o+x?|Ͱ,d|&^gooO<=@镐PsgJ=H@j7jkc}1- R71p* TO{jW,ظqcW4՜pEwA[{ǏwOuLsl$ΔU67R}&4H_c/,".,۞ fOPʶɄ[c8|mن 9uj@@9 ቶ6᥮gM} t?}ýGW~Q>)zeUhƯ'ΘMڍ{NEԾ}5kD=vL^|Nd? c"c2<1˘ e'eu"t 6CSINcZx<ux/=F_&ʺwޙ~@OO7p__ F4{$`ad.t-AxjZ68nOjVt.(ݰ@v;GΣ>UfYp*i%0A82>|ܹN|w歕\Kl$ PI'/14|>ejH Z~`7I }3:]m 2{& ]%qr\YTtVY,%6`$$SՅ SDAuX/ϊ1h\ N`LT V/Ya$1pϧ~tiۯ,M[&O*}=ȽLeB`K`1B7i.tuuxu/ ~) O(1*F3Q1~t= TɘWh,#J$E?T>ͨo42w(9`Qɪ*ϟ-" fRWC:KtВʼ{Zx& B 4Üx^ʲ^9_K;T:73$jbdQeFWCC(D0 a؛||g'⊺pqlө 0גd"$gb3-}p=xٟ|q QQL̼*7ק;nC=8$ũy bf89L&BaAC\QԈSJ$ڊڂV0?TbI+9khy bf̳!px`LuA]n"-O2 84eLa`T%>_X8ͦNv}-ڍl~Z fa횶9qeQQ꼼|ض}~ﶇSLl|N#Q,RZӅCQq͡%!& YʦC]jν{+,&V/鎧O @u(8Plh .'=8(,DKO@\ǛӕglinԽE0 X ,81(]V_K*,U;y b F,(?OLlxCq•EE9c^>X}b dF Xߞݷh;}3k.ZݚP $`v)v(x] 4L31oNQ%K~x1Wp,DzV#b-$F&G% c<LkT&\hkIk*%sN-Xf(Lk EV+&L}A\ F/z=BMK x~1,,j- [y&KT/3C{,E@SeNË|s$7ͬY'"Sy bPw-ads5ʭ2n昢׺&6'E͛RȈLK'f5)3s\!at"Y$fn;gcnbcԼ1gٴR>)!htLĦgj{c0 YAO.N p CeUS(vC(3u`Ѳ.8Ksf:xF՚*B7kߦ_a9.D7_^XxzZ.@-K26=[4K<;,.b.+]tyyڷ>Ӿơ d6d-K(=?s% ذn|s c p~tlSA(2tȒnR6-?19,szk'&" xr Λ\PZ#ii G *#AAL=N~f?kZ~ V~Ȍqq[v+@`0ZXnMӄi+!1Ț 8}ڭ[$ggţmm8 \*鎽גrC|IaQj^8$()sܹ2$$zz̉, r=8-?"y "4YٞeK:_`óX,nhqұ@`VZ'EDb cLnƦKi7m,Z V2Ig"4EB!L\Լ]26yNE9Y~娰$f.XpZx6rCzU$E1X$DبthN &D ",mqSY<9@#Uw__=@Zx6 $1,{e{E[ ю^^3X?&(qO, Ng' 3Ƹր7O Լ=,<ٴS|h`E IIg% P#v1H9k@ڍNBK#WMO@\`jfv'_z< CjЬApogIԼ10ƤX6,@W,uLnݱd*Rn_:64ߺkHALFAy b;iX2 fsc5oųw$SNZBahr6]?"z`Ȋ3 }Z`}ww_}'V% @'.k(^akIӖ^&)&0hRPf>G{}NBxQIgX2{"F( ˨y b9#pτ _:|xR䂞8;YmoIs-ɍ\`ˬ QRuoqNǑ~Ҽyo쩻W.(;h7QY%R'Mp2KsC-D% W^:DC<٦pLEf3g K*l$/$)$,'4 8 [Eڍ/{ &" q'q}]enmZ gP5/ALX.+2r@rox8F(p&Uq|\0`oK b0\nW6xE.dJrڜ$$c2fj^Z1Rl_j%yyiolzk@g-ǃASI,YgkIsC7Լ15D~ *o'_hs>+N`3}DE?ffS4Ǯ7[`[j^<A MS@[q)e p`*  ~Ij1sK.ѺZl( PFe.tX6??u~:dAmj/6j3NloG( mHv {ivcoKX%Q2KfSͯT?٩!>M{NB. _oZ&R˧NtF ."b4̀5<{eH$h"ooǵ۶G9_KJOZȔ &/o kxeV++,0 0 ΛmW"K.Xlin'1N1ų5Met%a%祗*)*b$$g:" ,/"9V>.%fݮwx+,ď/yn7@8˗kC!>8aKf3>QU%M{EʊE(? ,pS$2hnx<ڒ >#y@0qԲ_fyG\e oOn<r* Qk +$eV+XS ۻצ;,`31!`2ceP-<咴uFvCOn#2r F1o,-N_h*=pgeQ%A:x=˧L%฼p;^pLѴBSid}Wt*mrqrD:AFO@^~5}E+Q` IIFRWq4Z,.6BRZzDra#6cY< X @~7_s ">Kx6"(8<0 חh7%hzE;S`[P,|W'zAB >9 6ˌQY*~ՠ( B Mzc⦓Azy,6UYW]@_<=$g)m[$,mzx DF?n%tު ZxJ74 8ʲНmj!೉ոEUSg4* sg |-4&7GxrCD:O/)|,˧*1+*J)dbP MOV(G'Np!I !5 '7' QRH+',x&˧V 0 3۫ď@EL ~RÁnQ; iR%ZL8yhK%}n.371(΄xZx :&)vŸVP>U]- h( 4E1D8GxWԊo{&ex%>p;7_vRe/)>UU}t>`J8?"kwbݬݸH(Kw>^j%ೕGLq誫sf855(,!v) z-"mq<&&VfՉx8˧u&q{Y6^rqkYYWHflkk킀WԊ |=BSa@~H+u&pgy,9j#tj@KZu 4> .IBVBjblO<<N#~ge%L }zR$g;/tv cqhjs~J6ns1$jflguZt 7듃;/UAf٩=UsJDX$]hrI3 IDAT=W%:h\[oX&&=\i8m̚50]8RK*&G(*(8`y9paxFI Xـ< pO=jAI:~HjQ j*`BA@<Ԅj4@9NpE3_F֧yjBD²8Gh7i8up/-@׈/OELǃA$|s,p\: o%GdlkZc4DlX϶9tC#SHB[W}ZHhʓ)Jr\aL*0 515"uz*&OLf&F/KOzyKO M8`kai]ɍ-}}P4VvPZ 볝 m`1\g,Qa⁇?"csHEYaYŎpLw86 8ls4KAD "ieR˓C$$MP%$ć Ш&7DJ53as}ƲRaQphcn8:MyF( nۖSI?I>KM ''AkN˜?80(x ״E*UӂDbaVU[Y %_?C8nTCo' }\ogsuAY8k#{|Y @TQpֶH$@Q0PPFdWl@[b\ PS 9Ľ 7on|Br 6M7Gs@IQx(b}ww1Q{G,^;$rgfis/tD(Ca 8a~u7[=Y3]Ktۡf8  Wl#?jm`bl~5CQx~pժ,/Mn8v7@ozB|y 4׵"Њ'.@Yv*+V=P `- nDGZ[},,d-ZbhbOØ5>CyNf)I10Ea1u_qg-rsJl‘DP[RkB]M!o荓&W86XoHjsD#DZ@>miIBQ23KSb,nAw;m.q?MU1)(*ܾ9//mkB+5y}Os8Bs&p6ydP]` 㸯 3 ܎=a+:$f7 k2>1x)J۶rZK!1CfL?}λjjZQ ,e6[g%ǐ2 }nZ<遷Ѹ"NK\-=OYZ6:{XTjKc_?Ҟc0UiV1[K#N]s Lѱ@߀2_%e2-900y>4'qۭ=l _꠺SVn_yްv @^]PFك*\S&u] e<eyRS6<&ܻ_)=lϧG+H@ HY759ò:MP^lU[u?_p0k `禺5SZ2H\ii7[y".yN펧 M14u<Ȫj;˓4qN'5aM^J(1U<%!&&Aêj `o3'Di _x@mԠOwtW K!بћ c!&#EV).Ldދi&RYHE%Zi.W_PYM}eC s"솚5M>W㸴h>& &@]NQS˲%!L&o⹓ews=8;L%ݗt. ̟cZag Dw 015Di|A3-KbAFNM~[?Oo?מv k+e"we;Gvxk]9ah`Ꞁtt ^8 ǔ Ekzh)yRcE1YaLF_oMPӿZd<obs}|S漏PTbɰ-?d}q\ \Ǚ3MØ"+',YE.׏H7-XUF/'5˒zJdbGe}s4ˎd'!H ނybr׏@bw]a!NJV!(h u꼫;ԥ腸~"Q>XMu\sތ1w9ε 0mx'rt ƽv7xĐ#_/Kg]7-͵7U%!=qY{06ׂLy1KHj]3L57q|5e@QE`F(%a/@ LXeVnZnv3 /<KPT>(<v6q|bbOOw%F5ﹷ -tŅqxJclɀ_&j>5xZ c t#Ff²jyIA"{|>$E  ΃} L[kH ]S3!Q 7O ["(qd8p ]{.mG0+q;Y nMXi-b?SkҽN/e"&ǡCf_U.)(@삐寿 eEl&n I,%zE.S ƭ|cr$Ed'$c«G*+|0 |jj_$w-?~yvi\( 88 Y|Yk :Ksr6߱{w΢o`s$g%_L<5pJԼfuׁNtL\'~9ǫ ,߸b!y"sǗYEy`M”UX_(tefw55EޘO,hC5'ϼP4Xw]...(ʊoc!(*avi+"Š. lŸP +_tiKayCwܙÚZp^rC+V\ *h\UQY}}|/ ~5P݊K[ʛ _k"rH\VsrLtqڅDզ`(),>1 I֣Qk?GJeJ#˛F7L⻖{su WbE_Cjw X}B D߶ͦIW3F-rc%Ib健Kw:h`37lǯίuwX,EjoPwΫv3<3:qW5oyݼ1am<70q -c`$Qb}ٽ!TnMa(XPd ˴R^!Ml6pC/&tK2}xt*IiwjT?Su۶Y7y<{,> 01N@]LG**.^VnW۳)f6nԷ+>`6]sMaSou?}#a^d\:8UmRዣ\%ͼuߣ|ݞV?^M d.|nqJlCҾ6 \u}' 2pFӵJsSB'92aijfg.5xj֜Ps3$Ub6AHy8@IR3DZjFJy,%w4dN8dM=}!I/mQ>+Au}DP _$/I.HtyՑ]j:KtĿ~k?nL%5]nj۸5Ywjm͝ zvs9Ԝ*+ y>-Ώ]5J MrnYq-'ңFw&0'ؤyŽa06 >s6tZBȠ$+|dպ7cp>IΓd$,9X[c1EVx<YrE7y<ڤ6 ?)%PL'. eʸӡ裻cP}aPst˞1,oP7ˬVl޼aCP2NOK01(%_?QE9|}j 󛿿pa̘]"?Au/o΃;{e"o8ecdž &4 (H! a:ӆeJ $@ҤB !xı%mŒڟ޾{>Y#83sGo}w9;dUl[ZpRFǷ̪+H]Σnr)̍3uigݦM> 0t=ke7*eM]Yٳ5T(1v%ؤ!#s!:#Bncԕj`Oc97ɥkk`xuCC뭀z=O?S>}on8Y#k$;{ !]̬Y+ɓp /d*Փ^~a-5G}v}x֝/T=tB脪wBW ޞ bA*>'NLS7[lGLo.Νܿ35Ӭ0s$l7CCW@^[\z>D"ozf ,:˶ըXlnUɐi}U*%O޽bZ;At:1ӛPN[+!ӴCR)r*k~oוshJ_ m̬Zշ|bRAׇ^rï5oaA@^ F*%Y2q ~kҰ MLEŋsu>_2`d.(%S)''Wl? R3rwנǪ#ZAO[ ]إ|ZΛ7)r]Db{;AOL_L|CWeX$ז/ǪX, |'ϖə_k70ĸך.[YUݖh.{y'?f d2؝H`MM ZApS6Mu4 ð5|,N_ye6(7sݒt.ÝX论o_^H_L&jϞ[& ]B68^dƝ3ve,Ѓ!.cl:_Mkv7f;cR^Az P +JMER|;\NR|l 5;j, 5o#~toR_-PRfuBAvx/$8Nc،WKyxF3F3TYg)kgx(:hmz_J[ht̙8p@kjjPeY#Mr`wE4ҫ׻~! z8 (?9q"x =r =r/4:݀i&:+G}S',Z7XQXVۇ )˩y2; 1^#^K<^}2X~Ә@χmZl13 K˖! B [JtT*–aF68R)#}jdv ]uR뵲g\)ݩTdQnUCnZ1%d!BW9?1%Kr ~1l%;hE)ݜ;$px~&@\ZQK#$Iy)L^c-J Bw]m ܌ |pΜ=?XꪠifJ*~g^ziey~EXذulGǏ/p :Qp91dp1F:gV LaBoS]溦:; " q2Go6B>!Žq<>0pN@`^ohhX )Pف55誩qUU ajXL>?}TƵMMGn]M@حz,@ǟYi=/=]iڅy~1dBv10ntE=3MsB!] f>}Bm)OcӼ19;hRPT*JN$2Q H_xAI&˟㬎ŜUՖBllҪ*f2xI<0ua}!¦zs9S-/:>FmU=6lPWmڲ*aH;f 5>v'8N#/!mjgŊpeIT/D1d>ai~}R*n7Q["^gg Be* IDAT"ܢ\TC?;֮qX.g޶w|\#~ )/!bpz[Tո 㶍qAXm ⩡!>e]f- +1έ+Bq|l֭7~TAFlj\#zz"뫮ß\ *Ӵ\ĢH aL0:A<10';9}Oqg40r&j}>(~!7ġ̙E(H @AJDq1ƣ'Nx{}=njmŸm/l)#d#l8)8æ{W:m[  "  ðHu_ nٵk\9ѹsmK@wᛘHxCw TI/Hӭ[3##KKmґu]'O>>0 jK8UY־te0]AwSzi9kFh4 R[o Q]fnڹR*;1\ .-Ȉ[O?0g}wG+sCTU/58e׮Y(*Kp{OL[)YݕH"SrehoOyv [,@u@_RíXfuM*6MiR#𒧟 F"xlZ#-WevlysZչőt',WNsue-@"# Q[=Wz-"z϶mm;IIcc4 Np+aۙ{{~P(%mjcNd+qJeyk=?룻vڵ͚嚆(0͜Yŗ^tۭ˖].4BDr9h6kE)1R(cfz={u%`z3 zA*kmuyk, ~R ?E`^(~km aH$G]2k/mmm}wutC:6_-Δ*Uܾ}C>zsk+@45*ďr;lN__]ן[ϚW@Q蝇5{0Bԃ]]NeE1}Ѳ=s\a^(ͣs~8J>>~_?t[-1ͭ?^&Fk|&02Vyc|cKaD]]X[[iB}{[8!\`ܶ?~GD֮y{}}su}Kd2p³##K݉;I[8 @VLl|>4^+ul;mǽν~809cVp,-BbqԞRĩ/CB R9uu2MϽbSL|u2|؟L"8`D=M]. 8DQ"`7t:]M#6B'?q??[:t=Ɩ*0ijrz eK3YJsNC X*YZbܻ-w[»3.U.Y6*qGE2U~ٻo'""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""<_9%IENDB`turn-2.1.0/.github/install-hooks.sh000077500000000000000000000010771436753726200172140ustar00rootroot00000000000000#!/bin/sh # # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # SCRIPT_PATH="$(realpath "$(dirname "$0")")" . ${SCRIPT_PATH}/fetch-scripts.sh cp "${GOASSETS_PATH}/hooks/commit-msg.sh" "${SCRIPT_PATH}/../.git/hooks/commit-msg" cp "${GOASSETS_PATH}/hooks/pre-commit.sh" "${SCRIPT_PATH}/../.git/hooks/pre-commit" cp "${GOASSETS_PATH}/hooks/pre-push.sh" "${SCRIPT_PATH}/../.git/hooks/pre-push" turn-2.1.0/.github/workflows/000077500000000000000000000000001436753726200161165ustar00rootroot00000000000000turn-2.1.0/.github/workflows/codeql-analysis.yml000066400000000000000000000011551436753726200217330ustar00rootroot00000000000000# # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # If this repository should have package specific CI config, # remove the repository name from .goassets/.github/workflows/assets-sync.yml. # # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # name: CodeQL on: workflow_dispatch: schedule: - cron: '23 5 * * 0' pull_request: branches: - master paths: - '**.go' jobs: analyze: uses: pion/.goassets/.github/workflows/codeql-analysis.reusable.yml@master turn-2.1.0/.github/workflows/generate-authors.yml000066400000000000000000000011041436753726200221120ustar00rootroot00000000000000# # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # If this repository should have package specific CI config, # remove the repository name from .goassets/.github/workflows/assets-sync.yml. # # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # name: Generate Authors on: pull_request: jobs: generate: uses: pion/.goassets/.github/workflows/generate-authors.reusable.yml@master secrets: token: ${{ secrets.PIONBOT_PRIVATE_KEY }} turn-2.1.0/.github/workflows/lint.yaml000066400000000000000000000007521436753726200177540ustar00rootroot00000000000000# # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # If this repository should have package specific CI config, # remove the repository name from .goassets/.github/workflows/assets-sync.yml. # # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # name: Lint on: pull_request: jobs: lint: uses: pion/.goassets/.github/workflows/lint.reusable.yml@master turn-2.1.0/.github/workflows/release.yml000066400000000000000000000011051436753726200202560ustar00rootroot00000000000000# # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # If this repository should have package specific CI config, # remove the repository name from .goassets/.github/workflows/assets-sync.yml. # # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # name: Release on: push: tags: - 'v*' jobs: release: uses: pion/.goassets/.github/workflows/release.reusable.yml@master with: go-version: '1.19' # auto-update/latest-go-version turn-2.1.0/.github/workflows/renovate-go-sum-fix.yaml000066400000000000000000000011241436753726200226140ustar00rootroot00000000000000# # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # If this repository should have package specific CI config, # remove the repository name from .goassets/.github/workflows/assets-sync.yml. # # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # name: Fix go.sum on: push: branches: - renovate/* jobs: fix: uses: pion/.goassets/.github/workflows/renovate-go-sum-fix.reusable.yml@master secrets: token: ${{ secrets.PIONBOT_PRIVATE_KEY }} turn-2.1.0/.github/workflows/test.yaml000066400000000000000000000021121436753726200177550ustar00rootroot00000000000000# # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # If this repository should have package specific CI config, # remove the repository name from .goassets/.github/workflows/assets-sync.yml. # # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # name: Test on: push: branches: - master pull_request: jobs: test: uses: pion/.goassets/.github/workflows/test.reusable.yml@master strategy: matrix: go: ['1.19', '1.18'] # auto-update/supported-go-version-list fail-fast: false with: go-version: ${{ matrix.go }} test-i386: uses: pion/.goassets/.github/workflows/test-i386.reusable.yml@master strategy: matrix: go: ['1.19', '1.18'] # auto-update/supported-go-version-list fail-fast: false with: go-version: ${{ matrix.go }} test-wasm: uses: pion/.goassets/.github/workflows/test-wasm.reusable.yml@master with: go-version: '1.19' # auto-update/latest-go-version turn-2.1.0/.github/workflows/tidy-check.yaml000066400000000000000000000011371436753726200210300ustar00rootroot00000000000000# # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # If this repository should have package specific CI config, # remove the repository name from .goassets/.github/workflows/assets-sync.yml. # # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # name: Go mod tidy on: pull_request: push: branches: - master jobs: tidy: uses: pion/.goassets/.github/workflows/tidy-check.reusable.yml@master with: go-version: '1.19' # auto-update/latest-go-version turn-2.1.0/.gitignore000066400000000000000000000004661436753726200145170ustar00rootroot00000000000000### JetBrains IDE ### ##################### .idea/ ### Emacs Temporary Files ### ############################# *~ ### Folders ### ############### bin/ vendor/ node_modules/ ### Files ### ############# *.ivf *.ogg tags cover.out *.sw[poe] *.wasm examples/sfu-ws/cert.pem examples/sfu-ws/key.pem wasm_exec.js turn-2.1.0/.golangci.yml000066400000000000000000000172751436753726200151210ustar00rootroot00000000000000linters-settings: govet: check-shadowing: true misspell: locale: US exhaustive: default-signifies-exhaustive: true gomodguard: blocked: modules: - github.com/pkg/errors: recommendations: - errors linters: enable: - asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers - bidichk # Checks for dangerous unicode character sequences - bodyclose # checks whether HTTP response body is closed successfully - contextcheck # check the function whether use a non-inherited context - decorder # check declaration order and count of types, constants, variables and functions - depguard # Go linter that checks if package imports are in a list of acceptable packages - dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) - dupl # Tool for code clone detection - durationcheck # check for two durations multiplied together - errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases - errchkjson # Checks types passed to the json encoding functions. Reports unsupported types and optionally reports occations, where the check for the returned error can be omitted. - errname # Checks that sentinel errors are prefixed with the `Err` and error types are suffixed with the `Error`. - errorlint # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13. - exhaustive # check exhaustiveness of enum switch statements - exportloopref # checks for pointers to enclosing loop variables - forcetypeassert # finds forced type assertions - gci # Gci control golang package import order and make it always deterministic. - gochecknoglobals # Checks that no globals are present in Go code - gochecknoinits # Checks that no init functions are present in Go code - gocognit # Computes and checks the cognitive complexity of functions - goconst # Finds repeated strings that could be replaced by a constant - gocritic # The most opinionated Go source code linter - godox # Tool for detection of FIXME, TODO and other comment keywords - goerr113 # Golang linter to check the errors handling expressions - gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification - gofumpt # Gofumpt checks whether code was gofumpt-ed. - goheader # Checks is file header matches to pattern - goimports # Goimports does everything that gofmt does. Additionally it checks unused imports - gomoddirectives # Manage the use of 'replace', 'retract', and 'excludes' directives in go.mod. - gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations. - goprintffuncname # Checks that printf-like functions are named with `f` at the end - gosec # Inspects source code for security problems - gosimple # Linter for Go source code that specializes in simplifying a code - govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string - grouper # An analyzer to analyze expression groups. - importas # Enforces consistent import aliases - ineffassign # Detects when assignments to existing variables are not used - misspell # Finds commonly misspelled English words in comments - nakedret # Finds naked returns in functions greater than a specified function length - nilerr # Finds the code that returns nil even if it checks that the error is not nil. - nilnil # Checks that there is no simultaneous return of `nil` error and an invalid value. - noctx # noctx finds sending http request without context.Context - predeclared # find code that shadows one of Go's predeclared identifiers - revive # golint replacement, finds style mistakes - staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks - stylecheck # Stylecheck is a replacement for golint - tagliatelle # Checks the struct tags. - tenv # tenv is analyzer that detects using os.Setenv instead of t.Setenv since Go1.17 - tparallel # tparallel detects inappropriate usage of t.Parallel() method in your Go test codes - typecheck # Like the front-end of a Go compiler, parses and type-checks Go code - unconvert # Remove unnecessary type conversions - unparam # Reports unused function parameters - unused # Checks Go code for unused constants, variables, functions and types - wastedassign # wastedassign finds wasted assignment statements - whitespace # Tool for detection of leading and trailing whitespace disable: - containedctx # containedctx is a linter that detects struct contained context.Context field - cyclop # checks function and package cyclomatic complexity - exhaustivestruct # Checks if all struct's fields are initialized - forbidigo # Forbids identifiers - funlen # Tool for detection of long functions - gocyclo # Computes and checks the cyclomatic complexity of functions - godot # Check if comments end in a period - gomnd # An analyzer to detect magic numbers. - ifshort # Checks that your code uses short syntax for if-statements whenever possible - ireturn # Accept Interfaces, Return Concrete Types - lll # Reports long lines - maintidx # maintidx measures the maintainability index of each function. - makezero # Finds slice declarations with non-zero initial length - maligned # Tool to detect Go structs that would take less memory if their fields were sorted - nestif # Reports deeply nested if statements - nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity - nolintlint # Reports ill-formed or insufficient nolint directives - paralleltest # paralleltest detects missing usage of t.Parallel() method in your Go test - prealloc # Finds slice declarations that could potentially be preallocated - promlinter # Check Prometheus metrics naming via promlint - rowserrcheck # checks whether Err of rows is checked successfully - sqlclosecheck # Checks that sql.Rows and sql.Stmt are closed. - testpackage # linter that makes you use a separate _test package - thelper # thelper detects golang test helpers without t.Helper() call and checks the consistency of test helpers - varnamelen # checks that the length of a variable's name matches its scope - wrapcheck # Checks that errors returned from external packages are wrapped - wsl # Whitespace Linter - Forces you to use empty lines! issues: exclude-use-default: false exclude-rules: # Allow complex tests, better to be self contained - path: _test\.go linters: - gocognit # Allow complex main function in examples - path: examples text: "of func `main` is high" linters: - gocognit run: skip-dirs-use-default: false turn-2.1.0/.goreleaser.yml000066400000000000000000000000251436753726200154470ustar00rootroot00000000000000builds: - skip: true turn-2.1.0/AUTHORS.txt000066400000000000000000000032111436753726200144040ustar00rootroot00000000000000# Thank you to everyone that made Pion possible. If you are interested in contributing # we would love to have you https://github.com/pion/webrtc/wiki/Contributing # # This file is auto generated, using git to list all individuals contributors. # see https://github.com/pion/.goassets/blob/master/scripts/generate-authors.sh for the scripting Aaron France Aleksandr Razumov andrefsp Antonio Sorrentino Atsushi Watanabe backkem Caleb Phillips cnderrauber David Colburn Gabor Retvari Herman Banken Hugo Arregui Igor German Ingmar Wittkau Jannis Mattheis John Bradley jose nazario Juliusz Chroboczek lllf Lukas Rezek Marouane <6729798+nindolabs@users.noreply.github.com> Mészáros Mihály nindolabs <6729798+nindolabs@users.noreply.github.com> Onwuka Gideon Robert Eperjesi Sean DuBois Sean DuBois Sean DuBois Sean DuBois songjiayang Steffen Vogel ted Tom Clift Yusuke Nakamura Yutaka Takeda # List of contributors not appearing in Git history turn-2.1.0/DESIGN.md000066400000000000000000000037571436753726200140300ustar00rootroot00000000000000# Why Pion TURN TURN servers aren't exactly a hot technology, they are usually an after thought when building something. Most of the time beginners build an interesting WebRTC application, but at the very end realize they need a TURN server. It is really frustrating when you want to share your cool new project, only to realize you have to run another service. Then you find yourself building from source, fighting with config files and making changes you don't fully understand. Pion TURN was born hoping to solve these frustrations. These are the guiding principals/features that define pion-turn. ## Easy setup simple-turn is a statically built TURN server, configured by environment variables. The entire install setup is 5 commands, on any platform! The goal is that anyone should be able to run a TURN server on any platform. ## Integration first pion-turn makes no assumptions about how you authenticate users, how you log, or even your topology! Instead of running a dedicated TURN server you can inherit from github.com/pion/turn and set whatever logger you want. ## Embeddable You can add this to an existing service. This means all your config files stay homogeneous instead of having the mismatch that makes it harder to manage your services. For small setups it is usually an overkill to deploy dedicated TURN servers, this makes it easier to solve the problems you care about. ## Safe Golang provides a great foundation to build safe network services. Especially when running a networked service that is highly concurrent bugs can be devastating. ## Readable All network interaction is commented with a link to the spec. This makes learning and debugging easier, the TURN server was written to also serve as a guide for others. ## Tested Every commit is tested via travis-ci Go provides fantastic facilities for testing, and more will be added as time goes on. ## Shared libraries Every pion product is built using shared libraries, allowing others to build things using existing tested STUN and TURN tools. turn-2.1.0/FAQ.md000066400000000000000000000011201436753726200134440ustar00rootroot00000000000000 ## FAQ Q: Will pion/turn also act as a STUN server? A: Yes. Q: How do I implement token-based authentication? A: Replace the username with a token in the [AuthHandler](https://github.com/pion/turn/blob/6d0ff435910870eb9024b18321b93b61844fcfec/examples/turn-server/simple/main.go#L49). The password sent by the client can be any non-empty string, as long as it matches that used by the [GenerateAuthKey](https://github.com/pion/turn/blob/6d0ff435910870eb9024b18321b93b61844fcfec/examples/turn-server/simple/main.go#L41) function. Q: Will WebRTC prioritize using STUN over TURN? A: Yes.turn-2.1.0/LICENSE.md000066400000000000000000000020301436753726200141200ustar00rootroot00000000000000Copyright 2018 Pion LLC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. turn-2.1.0/README.md000066400000000000000000000106711436753726200140050ustar00rootroot00000000000000

Pion TURN
Pion TURN

A toolkit for building TURN clients and servers in Go

Pion TURN Slack Widget
Build Status GoDoc Coverage Status Go Report Card License: MIT


Pion TURN is a Go toolkit for building TURN servers and clients. We wrote it to solve problems we had when building RTC projects. * **Deployable** - Use modern tooling of the Go ecosystem. Stop generating config files. * **Embeddable** - Include `pion/turn` in your existing applications. No need to manage another service. * **Extendable** - TURN as an API so you can easily integrate with your existing monitoring and metrics. * **Maintainable** - `pion/turn` is simple and well documented. Designed for learning and easy debugging. * **Portable** - Quickly deploy to multiple architectures/platforms just by setting an environment variable. * **Safe** - Stability and safety is important for network services. Go provides everything we need. * **Scalable** - Create allocations and mutate state at runtime. Designed to make scaling easy. # Using `pion/turn` is an API for building STUN/TURN clients and servers, not a binary you deploy then configure. It may require copying our examples and making minor modifications to fit your need, no knowledge of Go is required however. You may be able to download the pre-made binaries of our examples if you wish to get started quickly. The advantage of this is that you don't need to deal with complicated config files, or custom APIs to modify the state of Pion TURN. After you instantiate an instance of a Pion TURN server or client you interact with it like any library. The quickest way to get started is to look at the [examples](examples) or [GoDoc](https://godoc.org/github.com/pion/turn) # Examples We try to cover most common use cases in [examples](examples). If more examples could be helpful please file an issue, we are always looking to expand and improve `pion/turn` to make it easier for developers. To build any example you just need to run `go build` in the directory of the example you care about. It is also very easy to [cross compile](https://dave.cheney.net/2015/08/22/cross-compilation-with-go-1-5) Go programs. You can also see `pion/turn` usage in [pion/ice](https://github.com/pion/ice) # [FAQ](https://github.com/pion/webrtc/wiki/FAQ) ### RFCs #### Implemented * [RFC 5389: Session Traversal Utilities for NAT (STUN)](https://tools.ietf.org/html/rfc5389) * [RFC 5766: Traversal Using Relays around NAT (TURN)](https://tools.ietf.org/html/rfc5766) #### Planned * [RFC 6062: Traversal Using Relays around NAT (TURN) Extensions for TCP Allocations](https://tools.ietf.org/html/rfc6062) * [RFC 6156: Traversal Using Relays around NAT (TURN) Extension for IPv6](https://tools.ietf.org/html/rfc6156) ### Community Pion has an active community on the [Golang Slack](https://pion.ly/slack). Sign up and join the **#pion** channel for discussions and support. We are always looking to support **your projects**. Please reach out if you have something to build! ### Contributing Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contributing)** to join the group of amazing people making this project possible. ### License MIT License - see [LICENSE.md](LICENSE.md) for full text turn-2.1.0/client.go000066400000000000000000000363771436753726200143460ustar00rootroot00000000000000package turn import ( b64 "encoding/base64" "fmt" "math" "net" "sync" "time" "github.com/pion/logging" "github.com/pion/stun" "github.com/pion/transport/v2" "github.com/pion/transport/v2/stdnet" "github.com/pion/transport/v2/vnet" "github.com/pion/turn/v2/internal/client" "github.com/pion/turn/v2/internal/proto" ) const ( defaultRTO = 200 * time.Millisecond maxRtxCount = 7 // total 7 requests (Rc) maxDataBufferSize = math.MaxUint16 // message size limit for Chromium ) // interval [msec] // 0: 0 ms +500 // 1: 500 ms +1000 // 2: 1500 ms +2000 // 3: 3500 ms +4000 // 4: 7500 ms +8000 // 5: 15500 ms +16000 // 6: 31500 ms +32000 // -: 63500 ms failed // ClientConfig is a bag of config parameters for Client. type ClientConfig struct { STUNServerAddr string // STUN server address (e.g. "stun.abc.com:3478") TURNServerAddr string // TURN server address (e.g. "turn.abc.com:3478") Username string Password string Realm string Software string RTO time.Duration Conn net.PacketConn // Listening socket (net.PacketConn) LoggerFactory logging.LoggerFactory Net transport.Net } // Client is a STUN server client type Client struct { conn net.PacketConn // read-only stunServ net.Addr // read-only turnServ net.Addr // read-only stunServStr string // read-only, used for de-multiplexing turnServStr string // read-only, used for de-multiplexing username stun.Username // read-only password string // read-only realm stun.Realm // read-only integrity stun.MessageIntegrity // read-only software stun.Software // read-only trMap *client.TransactionMap // thread-safe rto time.Duration // read-only relayedConn *client.UDPConn // protected by mutex *** allocTryLock client.TryLock // thread-safe listenTryLock client.TryLock // thread-safe net transport.Net // read-only mutex sync.RWMutex // thread-safe mutexTrMap sync.Mutex // thread-safe log logging.LeveledLogger // read-only } // NewClient returns a new Client instance. listeningAddress is the address and port to listen on, default "0.0.0.0:0" func NewClient(config *ClientConfig) (*Client, error) { loggerFactory := config.LoggerFactory if loggerFactory == nil { loggerFactory = logging.NewDefaultLoggerFactory() } log := loggerFactory.NewLogger("turnc") if config.Conn == nil { return nil, errNilConn } var err error if config.Net == nil { config.Net, err = stdnet.NewNet() // defaults to native operation if err != nil { return nil, err } } else if _, ok := config.Net.(*vnet.Net); ok { log.Warn("Virtual network is enabled") } var stunServ, turnServ net.Addr var stunServStr, turnServStr string if len(config.STUNServerAddr) > 0 { log.Debugf("resolving %s", config.STUNServerAddr) stunServ, err = config.Net.ResolveUDPAddr("udp4", config.STUNServerAddr) if err != nil { return nil, err } stunServStr = stunServ.String() log.Debugf("stunServ: %s", stunServStr) } if len(config.TURNServerAddr) > 0 { log.Debugf("resolving %s", config.TURNServerAddr) turnServ, err = config.Net.ResolveUDPAddr("udp4", config.TURNServerAddr) if err != nil { return nil, err } turnServStr = turnServ.String() log.Debugf("turnServ: %s", turnServStr) } rto := defaultRTO if config.RTO > 0 { rto = config.RTO } c := &Client{ conn: config.Conn, stunServ: stunServ, turnServ: turnServ, stunServStr: stunServStr, turnServStr: turnServStr, username: stun.NewUsername(config.Username), password: config.Password, realm: stun.NewRealm(config.Realm), software: stun.NewSoftware(config.Software), net: config.Net, trMap: client.NewTransactionMap(), rto: rto, log: log, } return c, nil } // TURNServerAddr return the TURN server address func (c *Client) TURNServerAddr() net.Addr { return c.turnServ } // STUNServerAddr return the STUN server address func (c *Client) STUNServerAddr() net.Addr { return c.stunServ } // Username returns username func (c *Client) Username() stun.Username { return c.username } // Realm return realm func (c *Client) Realm() stun.Realm { return c.realm } // WriteTo sends data to the specified destination using the base socket. func (c *Client) WriteTo(data []byte, to net.Addr) (int, error) { return c.conn.WriteTo(data, to) } // Listen will have this client start listening on the conn provided via the config. // This is optional. If not used, you will need to call HandleInbound method // to supply incoming data, instead. func (c *Client) Listen() error { if err := c.listenTryLock.Lock(); err != nil { return fmt.Errorf("%w: %s", errAlreadyListening, err.Error()) } go func() { buf := make([]byte, maxDataBufferSize) for { n, from, err := c.conn.ReadFrom(buf) if err != nil { c.log.Debugf("exiting read loop: %s", err.Error()) break } _, err = c.HandleInbound(buf[:n], from) if err != nil { c.log.Debugf("exiting read loop: %s", err.Error()) break } } c.listenTryLock.Unlock() }() return nil } // Close closes this client func (c *Client) Close() { c.mutexTrMap.Lock() defer c.mutexTrMap.Unlock() c.trMap.CloseAndDeleteAll() } // TransactionID & Base64: https://play.golang.org/p/EEgmJDI971P // SendBindingRequestTo sends a new STUN request to the given transport address func (c *Client) SendBindingRequestTo(to net.Addr) (net.Addr, error) { attrs := []stun.Setter{stun.TransactionID, stun.BindingRequest} if len(c.software) > 0 { attrs = append(attrs, c.software) } msg, err := stun.Build(attrs...) if err != nil { return nil, err } trRes, err := c.PerformTransaction(msg, to, false) if err != nil { return nil, err } var reflAddr stun.XORMappedAddress if err := reflAddr.GetFrom(trRes.Msg); err != nil { return nil, err } return &net.UDPAddr{ IP: reflAddr.IP, Port: reflAddr.Port, }, nil } // SendBindingRequest sends a new STUN request to the STUN server func (c *Client) SendBindingRequest() (net.Addr, error) { if c.stunServ == nil { return nil, errSTUNServerAddressNotSet } return c.SendBindingRequestTo(c.stunServ) } // Allocate sends a TURN allocation request to the given transport address func (c *Client) Allocate() (net.PacketConn, error) { if err := c.allocTryLock.Lock(); err != nil { return nil, fmt.Errorf("%w: %s", errOneAllocateOnly, err.Error()) } defer c.allocTryLock.Unlock() relayedConn := c.relayedUDPConn() if relayedConn != nil { return nil, fmt.Errorf("%w: %s", errAlreadyAllocated, relayedConn.LocalAddr().String()) } msg, err := stun.Build( stun.TransactionID, stun.NewType(stun.MethodAllocate, stun.ClassRequest), proto.RequestedTransport{Protocol: proto.ProtoUDP}, stun.Fingerprint, ) if err != nil { return nil, err } trRes, err := c.PerformTransaction(msg, c.turnServ, false) if err != nil { return nil, err } res := trRes.Msg // Anonymous allocate failed, trying to authenticate. var nonce stun.Nonce if err = nonce.GetFrom(res); err != nil { return nil, err } if err = c.realm.GetFrom(res); err != nil { return nil, err } c.realm = append([]byte(nil), c.realm...) c.integrity = stun.NewLongTermIntegrity( c.username.String(), c.realm.String(), c.password, ) // Trying to authorize. msg, err = stun.Build( stun.TransactionID, stun.NewType(stun.MethodAllocate, stun.ClassRequest), proto.RequestedTransport{Protocol: proto.ProtoUDP}, &c.username, &c.realm, &nonce, &c.integrity, stun.Fingerprint, ) if err != nil { return nil, err } trRes, err = c.PerformTransaction(msg, c.turnServ, false) if err != nil { return nil, err } res = trRes.Msg if res.Type.Class == stun.ClassErrorResponse { var code stun.ErrorCodeAttribute if err = code.GetFrom(res); err == nil { return nil, fmt.Errorf("%s (error %s)", res.Type, code) //nolint:goerr113 } return nil, fmt.Errorf("%s", res.Type) //nolint:goerr113 } // Getting relayed addresses from response. var relayed proto.RelayedAddress if err := relayed.GetFrom(res); err != nil { return nil, err } relayedAddr := &net.UDPAddr{ IP: relayed.IP, Port: relayed.Port, } // Getting lifetime from response var lifetime proto.Lifetime if err := lifetime.GetFrom(res); err != nil { return nil, err } relayedConn = client.NewUDPConn(&client.UDPConnConfig{ Observer: c, RelayedAddr: relayedAddr, Integrity: c.integrity, Nonce: nonce, Lifetime: lifetime.Duration, Log: c.log, }) c.setRelayedUDPConn(relayedConn) return relayedConn, nil } // CreatePermission Issues a CreatePermission request for the supplied addresses // as described in https://datatracker.ietf.org/doc/html/rfc5766#section-9 func (c *Client) CreatePermission(addrs ...net.Addr) error { return c.relayedUDPConn().CreatePermissions(addrs...) } // PerformTransaction performs STUN transaction func (c *Client) PerformTransaction(msg *stun.Message, to net.Addr, ignoreResult bool) (client.TransactionResult, error, ) { trKey := b64.StdEncoding.EncodeToString(msg.TransactionID[:]) raw := make([]byte, len(msg.Raw)) copy(raw, msg.Raw) tr := client.NewTransaction(&client.TransactionConfig{ Key: trKey, Raw: raw, To: to, Interval: c.rto, IgnoreResult: ignoreResult, }) c.trMap.Insert(trKey, tr) c.log.Tracef("start %s transaction %s to %s", msg.Type, trKey, tr.To.String()) _, err := c.conn.WriteTo(tr.Raw, to) if err != nil { return client.TransactionResult{}, err } tr.StartRtxTimer(c.onRtxTimeout) // If dontWait is true, get the transaction going and return immediately if ignoreResult { return client.TransactionResult{}, nil } res := tr.WaitForResult() if res.Err != nil { return res, res.Err } return res, nil } // OnDeallocated is called when de-allocation of relay address has been complete. // (Called by UDPConn) func (c *Client) OnDeallocated(relayedAddr net.Addr) { c.setRelayedUDPConn(nil) } // HandleInbound handles data received. // This method handles incoming packet de-multiplex it by the source address // and the types of the message. // This return a boolean (handled or not) and if there was an error. // Caller should check if the packet was handled by this client or not. // If not handled, it is assumed that the packet is application data. // If an error is returned, the caller should discard the packet regardless. func (c *Client) HandleInbound(data []byte, from net.Addr) (bool, error) { // +-------------------+-------------------------------+ // | Return Values | | // +-------------------+ Meaning / Action | // | handled | error | | // |=========+=========+===============================+ // | false | nil | Handle the packet as app data | // |---------+---------+-------------------------------+ // | true | nil | Nothing to do | // |---------+---------+-------------------------------+ // | false | error | (shouldn't happen) | // |---------+---------+-------------------------------+ // | true | error | Error occurred while handling | // +---------+---------+-------------------------------+ // Possible causes of the error: // - Malformed packet (parse error) // - STUN message was a request // - Non-STUN message from the STUN server switch { case stun.IsMessage(data): return true, c.handleSTUNMessage(data, from) case proto.IsChannelData(data): return true, c.handleChannelData(data) case len(c.stunServStr) != 0 && from.String() == c.stunServStr: // received from STUN server but it is not a STUN message return true, errNonSTUNMessage default: // assume, this is an application data c.log.Tracef("non-STUN/TURN packet, unhandled") } return false, nil } func (c *Client) handleSTUNMessage(data []byte, from net.Addr) error { raw := make([]byte, len(data)) copy(raw, data) msg := &stun.Message{Raw: raw} if err := msg.Decode(); err != nil { return fmt.Errorf("%w: %s", errFailedToDecodeSTUN, err.Error()) } if msg.Type.Class == stun.ClassRequest { return fmt.Errorf("%w : %s", errUnexpectedSTUNRequestMessage, msg.String()) } if msg.Type.Class == stun.ClassIndication { if msg.Type.Method == stun.MethodData { var peerAddr proto.PeerAddress if err := peerAddr.GetFrom(msg); err != nil { return err } from = &net.UDPAddr{ IP: peerAddr.IP, Port: peerAddr.Port, } var data proto.Data if err := data.GetFrom(msg); err != nil { return err } c.log.Debugf("data indication received from %s", from.String()) relayedConn := c.relayedUDPConn() if relayedConn == nil { c.log.Debug("no relayed conn allocated") return nil // silently discard } relayedConn.HandleInbound(data, from) } return nil } // This is a STUN response message (transactional) // The type is either: // - stun.ClassSuccessResponse // - stun.ClassErrorResponse trKey := b64.StdEncoding.EncodeToString(msg.TransactionID[:]) c.mutexTrMap.Lock() tr, ok := c.trMap.Find(trKey) if !ok { c.mutexTrMap.Unlock() // silently discard c.log.Debugf("no transaction for %s", msg.String()) return nil } // End the transaction tr.StopRtxTimer() c.trMap.Delete(trKey) c.mutexTrMap.Unlock() if !tr.WriteResult(client.TransactionResult{ Msg: msg, From: from, Retries: tr.Retries(), }) { c.log.Debugf("no listener for %s", msg.String()) } return nil } func (c *Client) handleChannelData(data []byte) error { chData := &proto.ChannelData{ Raw: make([]byte, len(data)), } copy(chData.Raw, data) if err := chData.Decode(); err != nil { return err } relayedConn := c.relayedUDPConn() if relayedConn == nil { c.log.Debug("no relayed conn allocated") return nil // silently discard } addr, ok := relayedConn.FindAddrByChannelNumber(uint16(chData.Number)) if !ok { return fmt.Errorf("%w: %d", errChannelBindNotFound, int(chData.Number)) } c.log.Tracef("channel data received from %s (ch=%d)", addr.String(), int(chData.Number)) relayedConn.HandleInbound(chData.Data, addr) return nil } func (c *Client) onRtxTimeout(trKey string, nRtx int) { c.mutexTrMap.Lock() defer c.mutexTrMap.Unlock() tr, ok := c.trMap.Find(trKey) if !ok { return // already gone } if nRtx == maxRtxCount { // all retransmissions failed c.trMap.Delete(trKey) if !tr.WriteResult(client.TransactionResult{ Err: fmt.Errorf("%w %s", errAllRetransmissionsFailed, trKey), }) { c.log.Debug("no listener for transaction") } return } c.log.Tracef("retransmitting transaction %s to %s (nRtx=%d)", trKey, tr.To.String(), nRtx) _, err := c.conn.WriteTo(tr.Raw, tr.To) if err != nil { c.trMap.Delete(trKey) if !tr.WriteResult(client.TransactionResult{ Err: fmt.Errorf("%w %s", errFailedToRetransmitTransaction, trKey), }) { c.log.Debug("no listener for transaction") } return } tr.StartRtxTimer(c.onRtxTimeout) } func (c *Client) setRelayedUDPConn(conn *client.UDPConn) { c.mutex.Lock() defer c.mutex.Unlock() c.relayedConn = conn } func (c *Client) relayedUDPConn() *client.UDPConn { c.mutex.RLock() defer c.mutex.RUnlock() return c.relayedConn } turn-2.1.0/client_test.go000066400000000000000000000111251436753726200153650ustar00rootroot00000000000000//go:build !js // +build !js package turn import ( "net" "testing" "time" "github.com/pion/logging" "github.com/pion/transport/v2/stdnet" "github.com/stretchr/testify/assert" ) func createListeningTestClient(t *testing.T, loggerFactory logging.LoggerFactory) (*Client, net.PacketConn, bool) { conn, err := net.ListenPacket("udp4", "0.0.0.0:0") assert.NoError(t, err) c, err := NewClient(&ClientConfig{ Conn: conn, Software: "TEST SOFTWARE", LoggerFactory: loggerFactory, }) assert.NoError(t, err) assert.NoError(t, c.Listen()) return c, conn, true } func createListeningTestClientWithSTUNServ(t *testing.T, loggerFactory logging.LoggerFactory) (*Client, net.PacketConn, bool) { conn, err := net.ListenPacket("udp4", "0.0.0.0:0") assert.NoError(t, err) nw, err := stdnet.NewNet() assert.NoError(t, err) c, err := NewClient(&ClientConfig{ STUNServerAddr: "stun1.l.google.com:19302", Conn: conn, Net: nw, LoggerFactory: loggerFactory, }) assert.NoError(t, err) assert.NoError(t, c.Listen()) return c, conn, true } func TestClientWithSTUN(t *testing.T) { loggerFactory := logging.NewDefaultLoggerFactory() log := loggerFactory.NewLogger("test") t.Run("SendBindingRequest", func(t *testing.T) { c, pc, ok := createListeningTestClientWithSTUNServ(t, loggerFactory) if !ok { return } defer c.Close() resp, err := c.SendBindingRequest() assert.NoError(t, err, "should succeed") log.Debugf("mapped-addr: %s", resp.String()) assert.Equal(t, 0, c.trMap.Size(), "should be no transaction left") assert.NoError(t, pc.Close()) }) t.Run("SendBindingRequestTo Parallel", func(t *testing.T) { c, pc, ok := createListeningTestClient(t, loggerFactory) if !ok { return } defer c.Close() // simple channel fo go routine start signaling started := make(chan struct{}) finished := make(chan struct{}) var err1 error to, err := net.ResolveUDPAddr("udp4", "stun1.l.google.com:19302") assert.NoError(t, err) // stun1.l.google.com:19302, more at https://gist.github.com/zziuni/3741933#file-stuns-L5 go func() { close(started) _, err1 = c.SendBindingRequestTo(to) close(finished) }() // block until go routine is started to make two almost parallel requests <-started if _, err = c.SendBindingRequestTo(to); err != nil { t.Fatal(err) } <-finished if err1 != nil { t.Fatal(err) } assert.NoError(t, pc.Close()) }) t.Run("NewClient should fail if Conn is nil", func(t *testing.T) { _, err := NewClient(&ClientConfig{ LoggerFactory: loggerFactory, }) assert.Error(t, err, "should fail") }) t.Run("SendBindingRequestTo timeout", func(t *testing.T) { c, pc, ok := createListeningTestClient(t, loggerFactory) if !ok { return } defer c.Close() to, err := net.ResolveUDPAddr("udp4", "127.0.0.1:9") assert.NoError(t, err) c.rto = 10 * time.Millisecond // force short timeout _, err = c.SendBindingRequestTo(to) assert.NotNil(t, err) assert.NoError(t, pc.Close()) }) } // Create an allocation, and then delete all nonces // The subsequent Write on the allocation will cause a CreatePermission // which will be forced to handle a stale nonce response func TestClientNonceExpiration(t *testing.T) { // lim := test.TimeOut(time.Second * 30) // defer lim.Stop() // report := test.CheckRoutines(t) // defer report() udpListener, err := net.ListenPacket("udp4", "0.0.0.0:3478") assert.NoError(t, err) server, err := NewServer(ServerConfig{ AuthHandler: func(username, realm string, srcAddr net.Addr) (key []byte, ok bool) { return GenerateAuthKey(username, realm, "pass"), true }, PacketConnConfigs: []PacketConnConfig{ { PacketConn: udpListener, RelayAddressGenerator: &RelayAddressGeneratorStatic{ RelayAddress: net.ParseIP("127.0.0.1"), Address: "0.0.0.0", }, }, }, Realm: "pion.ly", }) assert.NoError(t, err) conn, err := net.ListenPacket("udp4", "0.0.0.0:0") assert.NoError(t, err) client, err := NewClient(&ClientConfig{ Conn: conn, STUNServerAddr: "127.0.0.1:3478", TURNServerAddr: "127.0.0.1:3478", Username: "foo", Password: "pass", }) assert.NoError(t, err) assert.NoError(t, client.Listen()) allocation, err := client.Allocate() assert.NoError(t, err) server.nonces.Range(func(key, value interface{}) bool { server.nonces.Delete(key) return true }) _, err = allocation.WriteTo([]byte{0x00}, &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8080}) assert.NoError(t, err) // Shutdown assert.NoError(t, allocation.Close()) assert.NoError(t, conn.Close()) assert.NoError(t, server.Close()) } turn-2.1.0/codecov.yml000066400000000000000000000005521436753726200146700ustar00rootroot00000000000000# # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # coverage: status: project: default: # Allow decreasing 2% of total coverage to avoid noise. threshold: 2% patch: default: target: 70% only_pulls: true ignore: - "examples/*" - "examples/**/*" turn-2.1.0/errors.go000066400000000000000000000036471436753726200143760ustar00rootroot00000000000000package turn import "errors" var ( errRelayAddressInvalid = errors.New("turn: RelayAddress must be valid IP to use RelayAddressGeneratorStatic") errNoAvailableConns = errors.New("turn: PacketConnConfigs and ConnConfigs are empty, unable to proceed") errConnUnset = errors.New("turn: PacketConnConfig must have a non-nil Conn") errListenerUnset = errors.New("turn: ListenerConfig must have a non-nil Listener") errListeningAddressInvalid = errors.New("turn: RelayAddressGenerator has invalid ListeningAddress") errRelayAddressGeneratorUnset = errors.New("turn: RelayAddressGenerator in RelayConfig is unset") errMaxRetriesExceeded = errors.New("turn: max retries exceeded") errMaxPortNotZero = errors.New("turn: MaxPort must be not 0") errMinPortNotZero = errors.New("turn: MaxPort must be not 0") errNilConn = errors.New("turn: conn cannot not be nil") errTODO = errors.New("turn: TODO") errAlreadyListening = errors.New("turn: already listening") errFailedToClose = errors.New("turn: Server failed to close") errFailedToRetransmitTransaction = errors.New("turn: failed to retransmit transaction") errAllRetransmissionsFailed = errors.New("all retransmissions failed for") errChannelBindNotFound = errors.New("no binding found for channel") errSTUNServerAddressNotSet = errors.New("STUN server address is not set for the client") errOneAllocateOnly = errors.New("only one Allocate() caller is allowed") errAlreadyAllocated = errors.New("already allocated") errNonSTUNMessage = errors.New("non-STUN message from STUN server") errFailedToDecodeSTUN = errors.New("failed to decode STUN message") errUnexpectedSTUNRequestMessage = errors.New("unexpected STUN request message") ) turn-2.1.0/examples/000077500000000000000000000000001436753726200143375ustar00rootroot00000000000000turn-2.1.0/examples/README.md000066400000000000000000000111371436753726200156210ustar00rootroot00000000000000# Examples ## turn-server The `turn-server` directory contains 5 examples that show common Pion TURN usages. All of these except `lt-creds` take the following arguments. * -users : <username>=<password>[,<username>=<password>,...] pairs * -realm : Realm name (defaults to "pion.ly") * -port : Listening port (defaults to 3478) * -public-ip : IP that your TURN server is reachable on, for local development then can just be your local IP, avoid using `127.0.0.1` as some browsers discard from that IP. ```sh $ cd simple $ go build $ ./simple -public-ip 127.0.0.1 -users username=password,foo=bar ``` The five example servers are #### add-software-attribute This examples adds the SOFTWARE attribute with the value "CustomTURNServer" to every outbound STUN packet. This could be useful if you want to add debug info to your outbound packets. You could also use this same pattern to filter/modify packets if needed. #### log This example logs all inbound/outbound STUN packets. This could be useful if you want to store all inbound/outbound traffic or generate rich logs. You could also intercept these reads/writes if you want to filter traffic going to/from specific peers. #### simple This example is the most minimal invocation of a Pion TURN instance possible. It has no custom behavior, and could be a good starting place for running your own TURN server. #### tcp This example demonstrates listening on TCP. You could combine this example with `simple` and you will have a Pion TURN instance that is available via TCP and UDP. #### tls This example demonstrates listening on TLS. You could combine this example with `simple` and you will have a Pion TURN instance that is available via TLS and UDP. #### lt-creds This example shows how to use long term credentials. You can issue passwords that automatically expire, and you don't have the store them. The only downside is that you can't revoke a single username/password. You need to rotate the shared secret. Instead of `users` it has the follow arguments instead * -authSecret : Shared secret for the Long Term Credential Mechanism #### perm-filter This example demonstrates the use of a permission handler in the PION TURN server. The example implements a filtering policy that lets clients to connect back to their own host or server-reflexive address but will drop everything else. This will let the client ping-test through but will block essentially all other peer connection attempts. ## turn-client The `turn-client` directory contains 2 examples that show common Pion TURN usages. All of these examples take the following arguments. * -host : TURN server host * -ping : Run ping test * -port : Listening port (defaults to 3478) * -realm : Realm name (defaults to "pion.ly") * -user : <username>=<password> pair #### tcp Dials the requested TURN server via TCP #### udp Dials the requested TURN server via UDP ```sh $ go udp $ go build $ ./udp -host -user=user=pass ``` By adding `-ping`, it will perform a ping test. (it internally creates a 'pinger' and send a UDP packet every second, 10 times then exits. ```sh $ go build ./turn-client -host -user=user=pass -ping ``` Following diagram shows what turn-client does: ``` +----------------+ | TURN Server | | | +---o--------o---+ TURN port /^ / ^\ 3478_/ | / | \_relayConn (*1) | / | | _/ | mappedAddr_ | / | ___external IP:port (*2) \|v |/ for pingerConn +---o--------o---+ (*3) | | NAT | | +----------------+ | | TURN ___ | | __pingerConn listen \| |/ (sends `ping` to relayConn) port +---o--------o---+ (conn) | turn-client | +----------------+ ``` > (*1) The relayConn actually lives in the local turn-client, but it acts as if it is > listening on the TURN server. In fact, relayConn.LocalAddr() returns a transport address > on which the TURN server is listening. > (*2) For relayConn to send/receive packet to/from (*3), you will need to give relayConn permission > to send/receive packet to/from the IP address. In the example code, this is done by sending a > packet, "Hello" (content does not matter), to the mappedAddr. (assuming the IP address of > mappedAddr and the external IP:port (*3) are the same) This process is known as > "UDP hole punching" and TURN server exhibits "Address-restricted" behavior. Once it is done, > packets coming from (*3) will be received by relayConn. turn-2.1.0/examples/lt-cred-generator/000077500000000000000000000000001436753726200176555ustar00rootroot00000000000000turn-2.1.0/examples/lt-cred-generator/README.md000066400000000000000000000014631436753726200211400ustar00rootroot00000000000000# Usage This command generates credentials used by the Long-Term Credential Mechanism Defined in [RFC5389-10.2](https://tools.ietf.org/search/rfc5389#section-10.2). The idea is to use the expiry time of the credential as the username, and let the password contain some cryptographic hash of a (server-side) shared-secret and the expiry time. ```bash export SECRET=somesecret # Build binaries (cd examples/lt-cred-generator && go build .) (cd examples/turn-server/lt-cred && go build .) (cd examples/turn-client/udp && go build .) # Start server ./examples/turn-server/lt-cred/lt-cred -public-ip=127.0.0.1 -authSecret=$SECRET # Start client using generated credentials ./examples/lt-cred-generator/lt-cred-generator -authSecret=$SECRET | xargs -I{} ./examples/turn-client/udp/udp -host=127.0.0.1 -ping -user={} ``` turn-2.1.0/examples/lt-cred-generator/main.go000066400000000000000000000021671436753726200211360ustar00rootroot00000000000000// Package main implements a CLI tool for generating // long-term credentials. package main import ( "flag" "fmt" "log" "os" "time" "github.com/pion/turn/v2" ) // Outputs username & password according to the // Long-Term Credential Mechanism (RFC5389-10.2: https://tools.ietf.org/search/rfc5389#section-10.2) func main() { authSecret := flag.String("authSecret", "", "Shared secret for the Long Term Credential Mechanism") showHelp := flag.Bool("h", false, "Show usage") flag.Parse() if showHelp != nil && *showHelp { log.Println("Usage:") log.Println("$ lt-cred-generator | xargs go run examples/turn-client/udp/main.go -host localhost -ping=true -user=") return } if authSecret == nil || len(*authSecret) == 0 { log.Fatal("Missing -authSecret parameter") } u, p, _ := turn.GenerateLongTermCredentials(*authSecret, time.Minute) if _, err := os.Stdout.WriteString(fmt.Sprintf("%s=%s", u, p)); err != nil { // for use with xargs log.Panicf("Failed to write to stdout: %s", err) } if _, err := os.Stderr.WriteString("\n"); err != nil { // ignored by xargs log.Panicf("Failed to write to stderr: %s", err) } } turn-2.1.0/examples/turn-client/000077500000000000000000000000001436753726200166035ustar00rootroot00000000000000turn-2.1.0/examples/turn-client/tcp/000077500000000000000000000000001436753726200173715ustar00rootroot00000000000000turn-2.1.0/examples/turn-client/tcp/main.go000066400000000000000000000103141436753726200206430ustar00rootroot00000000000000// Package main implements a TURN client with support for TCP package main import ( "flag" "fmt" "log" "net" "strings" "time" "github.com/pion/logging" "github.com/pion/turn/v2" ) func main() { host := flag.String("host", "", "TURN Server name.") port := flag.Int("port", 3478, "Listening port.") user := flag.String("user", "", "A pair of username and password (e.g. \"user=pass\")") realm := flag.String("realm", "pion.ly", "Realm (defaults to \"pion.ly\")") ping := flag.Bool("ping", false, "Run ping test") flag.Parse() if len(*host) == 0 { log.Fatalf("'host' is required") } if len(*user) == 0 { log.Fatalf("'user' is required") } // Dial TURN Server turnServerAddr := fmt.Sprintf("%s:%d", *host, *port) conn, err := net.Dial("tcp", turnServerAddr) if err != nil { log.Panicf("Failed to connect to TURN server: %s", err) } cred := strings.SplitN(*user, "=", 2) // Start a new TURN Client and wrap our net.Conn in a STUNConn // This allows us to simulate datagram based communication over a net.Conn cfg := &turn.ClientConfig{ STUNServerAddr: turnServerAddr, TURNServerAddr: turnServerAddr, Conn: turn.NewSTUNConn(conn), Username: cred[0], Password: cred[1], Realm: *realm, LoggerFactory: logging.NewDefaultLoggerFactory(), } client, err := turn.NewClient(cfg) if err != nil { log.Panicf("Failed to create TURN client: %s", err) } defer client.Close() // Start listening on the conn provided. err = client.Listen() if err != nil { log.Panicf("Failed to listen: %s", err) } // Allocate a relay socket on the TURN server. On success, it // will return a net.PacketConn which represents the remote // socket. relayConn, err := client.Allocate() if err != nil { log.Panicf("Failed to allocate: %s", err) } defer func() { if closeErr := relayConn.Close(); closeErr != nil { log.Fatalf("Failed to close connection: %s", closeErr) } }() // The relayConn's local address is actually the transport // address assigned on the TURN server. log.Printf("relayed-address=%s", relayConn.LocalAddr().String()) // If you provided `-ping`, perform a ping test against the // relayConn we have just allocated. if *ping { err = doPingTest(client, relayConn) if err != nil { log.Panicf("Failed to ping: %s", err) } } } func doPingTest(client *turn.Client, relayConn net.PacketConn) error { // Send BindingRequest to learn our external IP mappedAddr, err := client.SendBindingRequest() if err != nil { return err } // Set up pinger socket (pingerConn) pingerConn, err := net.ListenPacket("udp4", "0.0.0.0:0") if err != nil { log.Panicf("Failed to listen: %s", err) } defer func() { if closeErr := pingerConn.Close(); closeErr != nil { log.Panicf("Failed to close connection: %s", closeErr) } }() // Punch a UDP hole for the relayConn by sending a data to the mappedAddr. // This will trigger a TURN client to generate a permission request to the // TURN server. After this, packets from the IP address will be accepted by // the TURN server. _, err = relayConn.WriteTo([]byte("Hello"), mappedAddr) if err != nil { return err } // Start read-loop on pingerConn go func() { buf := make([]byte, 1600) for { n, from, pingerErr := pingerConn.ReadFrom(buf) if pingerErr != nil { break } msg := string(buf[:n]) if sentAt, pingerErr := time.Parse(time.RFC3339Nano, msg); pingerErr == nil { rtt := time.Since(sentAt) log.Printf("%d bytes from from %s time=%d ms\n", n, from.String(), int(rtt.Seconds()*1000)) } } }() // Start read-loop on relayConn go func() { buf := make([]byte, 1600) for { n, from, readerErr := relayConn.ReadFrom(buf) if readerErr != nil { break } // Echo back if _, readerErr = relayConn.WriteTo(buf[:n], from); readerErr != nil { break } } }() time.Sleep(500 * time.Millisecond) // Send 10 packets from relayConn to the echo server for i := 0; i < 10; i++ { msg := time.Now().Format(time.RFC3339Nano) _, err = pingerConn.WriteTo([]byte(msg), relayConn.LocalAddr()) if err != nil { return err } // For simplicity, this example does not wait for the pong (reply). // Instead, sleep 1 second. time.Sleep(time.Second) } return nil } turn-2.1.0/examples/turn-client/udp/000077500000000000000000000000001436753726200173735ustar00rootroot00000000000000turn-2.1.0/examples/turn-client/udp/main.go000066400000000000000000000103121436753726200206430ustar00rootroot00000000000000// Package main implements a TURN client using UDP package main import ( "flag" "fmt" "log" "net" "strings" "time" "github.com/pion/logging" "github.com/pion/turn/v2" ) func main() { host := flag.String("host", "", "TURN Server name.") port := flag.Int("port", 3478, "Listening port.") user := flag.String("user", "", "A pair of username and password (e.g. \"user=pass\")") realm := flag.String("realm", "pion.ly", "Realm (defaults to \"pion.ly\")") ping := flag.Bool("ping", false, "Run ping test") flag.Parse() if len(*host) == 0 { log.Fatalf("'host' is required") } if len(*user) == 0 { log.Fatalf("'user' is required") } cred := strings.SplitN(*user, "=", 2) // TURN client won't create a local listening socket by itself. conn, err := net.ListenPacket("udp4", "0.0.0.0:0") if err != nil { log.Panicf("Failed to listen: %s", err) } defer func() { if closeErr := conn.Close(); closeErr != nil { log.Panicf("Failed to close connection: %s", closeErr) } }() turnServerAddr := fmt.Sprintf("%s:%d", *host, *port) cfg := &turn.ClientConfig{ STUNServerAddr: turnServerAddr, TURNServerAddr: turnServerAddr, Conn: conn, Username: cred[0], Password: cred[1], Realm: *realm, LoggerFactory: logging.NewDefaultLoggerFactory(), } client, err := turn.NewClient(cfg) if err != nil { log.Panicf("Failed to create TURN client: %s", err) } defer client.Close() // Start listening on the conn provided. err = client.Listen() if err != nil { log.Panicf("Failed to listen: %s", err) } // Allocate a relay socket on the TURN server. On success, it // will return a net.PacketConn which represents the remote // socket. relayConn, err := client.Allocate() if err != nil { log.Panicf("Failed to allocate: %s", err) } defer func() { if closeErr := relayConn.Close(); closeErr != nil { log.Panicf("Failed to close connection: %s", closeErr) } }() // The relayConn's local address is actually the transport // address assigned on the TURN server. log.Printf("relayed-address=%s", relayConn.LocalAddr().String()) // If you provided `-ping`, perform a ping test against the // relayConn we have just allocated. if *ping { err = doPingTest(client, relayConn) if err != nil { log.Panicf("Failed to ping: %s", err) } } } func doPingTest(client *turn.Client, relayConn net.PacketConn) error { // Send BindingRequest to learn our external IP mappedAddr, err := client.SendBindingRequest() if err != nil { return err } // Set up pinger socket (pingerConn) pingerConn, err := net.ListenPacket("udp4", "0.0.0.0:0") if err != nil { log.Panicf("Failed to listen: %s", err) } defer func() { if closeErr := pingerConn.Close(); closeErr != nil { log.Panicf("Failed to close connection: %s", closeErr) } }() // Punch a UDP hole for the relayConn by sending a data to the mappedAddr. // This will trigger a TURN client to generate a permission request to the // TURN server. After this, packets from the IP address will be accepted by // the TURN server. _, err = relayConn.WriteTo([]byte("Hello"), mappedAddr) if err != nil { return err } // Start read-loop on pingerConn go func() { buf := make([]byte, 1600) for { n, from, pingerErr := pingerConn.ReadFrom(buf) if pingerErr != nil { break } msg := string(buf[:n]) if sentAt, pingerErr := time.Parse(time.RFC3339Nano, msg); pingerErr == nil { rtt := time.Since(sentAt) log.Printf("%d bytes from from %s time=%d ms\n", n, from.String(), int(rtt.Seconds()*1000)) } } }() // Start read-loop on relayConn go func() { buf := make([]byte, 1600) for { n, from, readerErr := relayConn.ReadFrom(buf) if readerErr != nil { break } // Echo back if _, readerErr = relayConn.WriteTo(buf[:n], from); readerErr != nil { break } } }() time.Sleep(500 * time.Millisecond) // Send 10 packets from relayConn to the echo server for i := 0; i < 10; i++ { msg := time.Now().Format(time.RFC3339Nano) _, err = pingerConn.WriteTo([]byte(msg), relayConn.LocalAddr()) if err != nil { return err } // For simplicity, this example does not wait for the pong (reply). // Instead, sleep 1 second. time.Sleep(time.Second) } return nil } turn-2.1.0/examples/turn-server/000077500000000000000000000000001436753726200166335ustar00rootroot00000000000000turn-2.1.0/examples/turn-server/add-software-attribute/000077500000000000000000000000001436753726200232145ustar00rootroot00000000000000turn-2.1.0/examples/turn-server/add-software-attribute/main.go000066400000000000000000000060171436753726200244730ustar00rootroot00000000000000// Package main implements a TURN server // adding a software attribute. package main import ( "flag" "log" "net" "os" "os/signal" "regexp" "strconv" "syscall" "github.com/pion/stun" "github.com/pion/turn/v2" ) // attributeAdder wraps a PacketConn and appends the SOFTWARE attribute to STUN packets // This pattern could be used to capture/inspect/modify data as well type attributeAdder struct { net.PacketConn } func (s *attributeAdder) WriteTo(p []byte, addr net.Addr) (n int, err error) { if stun.IsMessage(p) { m := &stun.Message{Raw: p} if err = m.Decode(); err != nil { return } if err = stun.NewSoftware("CustomTURNServer").AddTo(m); err != nil { return } m.Encode() p = m.Raw } return s.PacketConn.WriteTo(p, addr) } func main() { publicIP := flag.String("public-ip", "", "IP Address that TURN can be contacted by.") port := flag.Int("port", 3478, "Listening port.") users := flag.String("users", "", "List of username and password (e.g. \"user=pass,user=pass\")") realm := flag.String("realm", "pion.ly", "Realm (defaults to \"pion.ly\")") flag.Parse() if len(*publicIP) == 0 { log.Fatalf("'public-ip' is required") } else if len(*users) == 0 { log.Fatalf("'users' is required") } // Create a UDP listener to pass into pion/turn // pion/turn itself doesn't allocate any UDP sockets, but lets the user pass them in // this allows us to add logging, storage or modify inbound/outbound traffic udpListener, err := net.ListenPacket("udp4", "0.0.0.0:"+strconv.Itoa(*port)) if err != nil { log.Panicf("Failed to create TURN server listener: %s", err) } // Cache -users flag for easy lookup later // If passwords are stored they should be saved to your DB hashed using turn.GenerateAuthKey usersMap := map[string][]byte{} for _, kv := range regexp.MustCompile(`(\w+)=(\w+)`).FindAllStringSubmatch(*users, -1) { usersMap[kv[1]] = turn.GenerateAuthKey(kv[1], *realm, kv[2]) } s, err := turn.NewServer(turn.ServerConfig{ Realm: *realm, // Set AuthHandler callback // This is called every time a user tries to authenticate with the TURN server // Return the key for that user, or false when no user is found AuthHandler: func(username string, realm string, srcAddr net.Addr) ([]byte, bool) { if key, ok := usersMap[username]; ok { return key, true } return nil, false }, // PacketConnConfigs is a list of UDP Listeners and the configuration around them PacketConnConfigs: []turn.PacketConnConfig{ { PacketConn: &attributeAdder{udpListener}, RelayAddressGenerator: &turn.RelayAddressGeneratorStatic{ RelayAddress: net.ParseIP(*publicIP), // Claim that we are listening on IP passed by user (This should be your Public IP) Address: "0.0.0.0", // But actually be listening on every interface }, }, }, }) if err != nil { log.Panic(err) } // Block until user sends SIGINT or SIGTERM sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) <-sigs if err = s.Close(); err != nil { log.Panic(err) } } turn-2.1.0/examples/turn-server/log/000077500000000000000000000000001436753726200174145ustar00rootroot00000000000000turn-2.1.0/examples/turn-server/log/main.go000066400000000000000000000063731436753726200207000ustar00rootroot00000000000000// Package main implements a TURN server with logging. package main import ( "flag" "fmt" "log" "net" "os" "os/signal" "regexp" "strconv" "syscall" "github.com/pion/stun" "github.com/pion/turn/v2" ) // stunLogger wraps a PacketConn and prints incoming/outgoing STUN packets // This pattern could be used to capture/inspect/modify data as well type stunLogger struct { net.PacketConn } func (s *stunLogger) WriteTo(p []byte, addr net.Addr) (n int, err error) { if n, err = s.PacketConn.WriteTo(p, addr); err == nil && stun.IsMessage(p) { msg := &stun.Message{Raw: p} if err = msg.Decode(); err != nil { return } fmt.Printf("Outbound STUN: %s \n", msg.String()) } return } func (s *stunLogger) ReadFrom(p []byte) (n int, addr net.Addr, err error) { if n, addr, err = s.PacketConn.ReadFrom(p); err == nil && stun.IsMessage(p) { msg := &stun.Message{Raw: p} if err = msg.Decode(); err != nil { return } fmt.Printf("Inbound STUN: %s \n", msg.String()) } return } func main() { publicIP := flag.String("public-ip", "", "IP Address that TURN can be contacted by.") port := flag.Int("port", 3478, "Listening port.") users := flag.String("users", "", "List of username and password (e.g. \"user=pass,user=pass\")") realm := flag.String("realm", "pion.ly", "Realm (defaults to \"pion.ly\")") flag.Parse() if len(*publicIP) == 0 { log.Fatalf("'public-ip' is required") } else if len(*users) == 0 { log.Fatalf("'users' is required") } // Create a UDP listener to pass into pion/turn // pion/turn itself doesn't allocate any UDP sockets, but lets the user pass them in // this allows us to add logging, storage or modify inbound/outbound traffic udpListener, err := net.ListenPacket("udp4", "0.0.0.0:"+strconv.Itoa(*port)) if err != nil { log.Panicf("Failed to create TURN server listener: %s", err) } // Cache -users flag for easy lookup later // If passwords are stored they should be saved to your DB hashed using turn.GenerateAuthKey usersMap := map[string][]byte{} for _, kv := range regexp.MustCompile(`(\w+)=(\w+)`).FindAllStringSubmatch(*users, -1) { usersMap[kv[1]] = turn.GenerateAuthKey(kv[1], *realm, kv[2]) } s, err := turn.NewServer(turn.ServerConfig{ Realm: *realm, // Set AuthHandler callback // This is called every time a user tries to authenticate with the TURN server // Return the key for that user, or false when no user is found AuthHandler: func(username string, realm string, srcAddr net.Addr) ([]byte, bool) { if key, ok := usersMap[username]; ok { return key, true } return nil, false }, // PacketConnConfigs is a list of UDP Listeners and the configuration around them PacketConnConfigs: []turn.PacketConnConfig{ { PacketConn: &stunLogger{udpListener}, RelayAddressGenerator: &turn.RelayAddressGeneratorStatic{ RelayAddress: net.ParseIP(*publicIP), // Claim that we are listening on IP passed by user (This should be your Public IP) Address: "0.0.0.0", // But actually be listening on every interface }, }, }, }) if err != nil { log.Panic(err) } // Block until user sends SIGINT or SIGTERM sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) <-sigs if err = s.Close(); err != nil { log.Panic(err) } } turn-2.1.0/examples/turn-server/lt-cred/000077500000000000000000000000001436753726200201655ustar00rootroot00000000000000turn-2.1.0/examples/turn-server/lt-cred/main.go000066400000000000000000000044061436753726200214440ustar00rootroot00000000000000// Package main implements a TURN server using // long-term credentials. package main import ( "flag" "log" "net" "os" "os/signal" "strconv" "syscall" "github.com/pion/logging" "github.com/pion/turn/v2" ) func main() { publicIP := flag.String("public-ip", "", "IP Address that TURN can be contacted by.") port := flag.Int("port", 3478, "Listening port.") authSecret := flag.String("authSecret", "", "Shared secret for the Long Term Credential Mechanism") realm := flag.String("realm", "pion.ly", "Realm (defaults to \"pion.ly\")") flag.Parse() if len(*publicIP) == 0 { log.Fatalf("'public-ip' is required") } else if len(*authSecret) == 0 { log.Fatalf("'authSecret' is required") } // Create a UDP listener to pass into pion/turn // pion/turn itself doesn't allocate any UDP sockets, but lets the user pass them in // this allows us to add logging, storage or modify inbound/outbound traffic udpListener, err := net.ListenPacket("udp4", "0.0.0.0:"+strconv.Itoa(*port)) if err != nil { log.Panicf("Failed to create TURN server listener: %s", err) } // NewLongTermAuthHandler takes a pion.LeveledLogger. This allows you to intercept messages // and process them yourself. logger := logging.NewDefaultLeveledLoggerForScope("lt-creds", logging.LogLevelTrace, os.Stdout) s, err := turn.NewServer(turn.ServerConfig{ Realm: *realm, // Set AuthHandler callback // This is called every time a user tries to authenticate with the TURN server // Return the key for that user, or false when no user is found AuthHandler: turn.NewLongTermAuthHandler(*authSecret, logger), // PacketConnConfigs is a list of UDP Listeners and the configuration around them PacketConnConfigs: []turn.PacketConnConfig{ { PacketConn: udpListener, RelayAddressGenerator: &turn.RelayAddressGeneratorStatic{ RelayAddress: net.ParseIP(*publicIP), // Claim that we are listening on IP passed by user (This should be your Public IP) Address: "0.0.0.0", // But actually be listening on every interface }, }, }, }) if err != nil { log.Panic(err) } // Block until user sends SIGINT or SIGTERM sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) <-sigs if err = s.Close(); err != nil { log.Panic(err) } } turn-2.1.0/examples/turn-server/perm-filter/000077500000000000000000000000001436753726200210615ustar00rootroot00000000000000turn-2.1.0/examples/turn-server/perm-filter/main.go000066400000000000000000000063741436753726200223460ustar00rootroot00000000000000// This example demonstrates the use of a permission handler in the PION TURN server. The // permission handler implements a filtering policy that lets clients to connect back to their own // host or server-reflexive address but will filter out everything else. This will let the client // ping-test through but will block essentially all other peer connection attempts. package main import ( "flag" "log" "net" "os" "os/signal" "regexp" "strconv" "strings" "syscall" "github.com/pion/turn/v2" ) func main() { publicIP := flag.String("public-ip", "", "IP Address that TURN can be contacted by.") port := flag.Int("port", 3478, "Listening port.") users := flag.String("users", "", "List of username and password (e.g. \"user=pass,user=pass\")") realm := flag.String("realm", "pion.ly", "Realm (defaults to \"pion.ly\")") flag.Parse() if len(*publicIP) == 0 { log.Fatalf("'public-ip' is required") } else if len(*users) == 0 { log.Fatalf("'users' is required") } // Create a UDP listener to pass into pion/turn // pion/turn itself doesn't allocate any UDP sockets, but lets the user pass them in // this allows us to add logging, storage or modify inbound/outbound traffic udpListener, err := net.ListenPacket("udp4", "0.0.0.0:"+strconv.Itoa(*port)) if err != nil { log.Panicf("Failed to create TURN server listener: %s", err) } // Cache -users flag for easy lookup later // If passwords are stored they should be saved to your DB hashed using turn.GenerateAuthKey usersMap := map[string][]byte{} for _, kv := range regexp.MustCompile(`(\w+)=(\w+)`).FindAllStringSubmatch(*users, -1) { usersMap[kv[1]] = turn.GenerateAuthKey(kv[1], *realm, kv[2]) } s, err := turn.NewServer(turn.ServerConfig{ Realm: *realm, // Set AuthHandler callback // This is called everytime a user tries to authenticate with the TURN server // Return the key for that user, or false when no user is found AuthHandler: func(username string, realm string, srcAddr net.Addr) ([]byte, bool) { if key, ok := usersMap[username]; ok { return key, true } return nil, false }, // PacketConnConfigs is a list of UDP Listeners and the configuration around them PacketConnConfigs: []turn.PacketConnConfig{ { PacketConn: udpListener, RelayAddressGenerator: &turn.RelayAddressGeneratorStatic{ RelayAddress: net.ParseIP(*publicIP), // Claim that we are listening on IP passed by user (This should be your Public IP) Address: "0.0.0.0", // But actually be listening on every interface }, // allow peer connections only to the client's own (host or server-reflexive) IP PermissionHandler: func(clientAddr net.Addr, peerIP net.IP) bool { clientIP := strings.SplitN(clientAddr.String(), ":", 2) if clientIP[0] != peerIP.String() { log.Printf("Blocking request from client IP %s to peer %s", clientIP[0], peerIP.String()) return false } log.Printf("Admitting request from client IP %s to peer %s", clientIP[0], peerIP.String()) return true }, }, }, }) if err != nil { log.Panic(err) } // Block until user sends SIGINT or SIGTERM sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) <-sigs if err = s.Close(); err != nil { log.Panic(err) } } turn-2.1.0/examples/turn-server/port-range/000077500000000000000000000000001436753726200207115ustar00rootroot00000000000000turn-2.1.0/examples/turn-server/port-range/main.go000066400000000000000000000047751436753726200222010ustar00rootroot00000000000000// Package main implements a TURN server with a // specified port range. package main import ( "flag" "log" "net" "os" "os/signal" "regexp" "strconv" "syscall" "github.com/pion/turn/v2" ) func main() { publicIP := flag.String("public-ip", "", "IP Address that TURN can be contacted by.") port := flag.Int("port", 3478, "Listening port.") users := flag.String("users", "", "List of username and password (e.g. \"user=pass,user=pass\")") realm := flag.String("realm", "pion.ly", "Realm (defaults to \"pion.ly\")") flag.Parse() if len(*publicIP) == 0 { log.Fatalf("'public-ip' is required") } else if len(*users) == 0 { log.Fatalf("'users' is required") } // Create a UDP listener to pass into pion/turn // pion/turn itself doesn't allocate any UDP sockets, but lets the user pass them in // this allows us to add logging, storage or modify inbound/outbound traffic udpListener, err := net.ListenPacket("udp4", "0.0.0.0:"+strconv.Itoa(*port)) if err != nil { log.Panicf("Failed to create TURN server listener: %s", err) } // Cache -users flag for easy lookup later // If passwords are stored they should be saved to your DB hashed using turn.GenerateAuthKey usersMap := map[string][]byte{} for _, kv := range regexp.MustCompile(`(\w+)=(\w+)`).FindAllStringSubmatch(*users, -1) { usersMap[kv[1]] = turn.GenerateAuthKey(kv[1], *realm, kv[2]) } s, err := turn.NewServer(turn.ServerConfig{ Realm: *realm, // Set AuthHandler callback // This is called every time a user tries to authenticate with the TURN server // Return the key for that user, or false when no user is found AuthHandler: func(username string, realm string, srcAddr net.Addr) ([]byte, bool) { if key, ok := usersMap[username]; ok { return key, true } return nil, false }, // PacketConnConfigs is a list of UDP Listeners and the configuration around them PacketConnConfigs: []turn.PacketConnConfig{ { PacketConn: udpListener, RelayAddressGenerator: &turn.RelayAddressGeneratorPortRange{ RelayAddress: net.ParseIP(*publicIP), // Claim that we are listening on IP passed by user (This should be your Public IP) Address: "0.0.0.0", // But actually be listening on every interface MinPort: 50000, MaxPort: 55000, }, }, }, }) if err != nil { log.Panic(err) } // Block until user sends SIGINT or SIGTERM sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) <-sigs if err = s.Close(); err != nil { log.Panic(err) } } turn-2.1.0/examples/turn-server/simple/000077500000000000000000000000001436753726200201245ustar00rootroot00000000000000turn-2.1.0/examples/turn-server/simple/main.go000066400000000000000000000046551436753726200214110ustar00rootroot00000000000000// Package main implements a simple TURN server package main import ( "flag" "log" "net" "os" "os/signal" "regexp" "strconv" "syscall" "github.com/pion/turn/v2" ) func main() { publicIP := flag.String("public-ip", "", "IP Address that TURN can be contacted by.") port := flag.Int("port", 3478, "Listening port.") users := flag.String("users", "", "List of username and password (e.g. \"user=pass,user=pass\")") realm := flag.String("realm", "pion.ly", "Realm (defaults to \"pion.ly\")") flag.Parse() if len(*publicIP) == 0 { log.Fatalf("'public-ip' is required") } else if len(*users) == 0 { log.Fatalf("'users' is required") } // Create a UDP listener to pass into pion/turn // pion/turn itself doesn't allocate any UDP sockets, but lets the user pass them in // this allows us to add logging, storage or modify inbound/outbound traffic udpListener, err := net.ListenPacket("udp4", "0.0.0.0:"+strconv.Itoa(*port)) if err != nil { log.Panicf("Failed to create TURN server listener: %s", err) } // Cache -users flag for easy lookup later // If passwords are stored they should be saved to your DB hashed using turn.GenerateAuthKey usersMap := map[string][]byte{} for _, kv := range regexp.MustCompile(`(\w+)=(\w+)`).FindAllStringSubmatch(*users, -1) { usersMap[kv[1]] = turn.GenerateAuthKey(kv[1], *realm, kv[2]) } s, err := turn.NewServer(turn.ServerConfig{ Realm: *realm, // Set AuthHandler callback // This is called every time a user tries to authenticate with the TURN server // Return the key for that user, or false when no user is found AuthHandler: func(username string, realm string, srcAddr net.Addr) ([]byte, bool) { if key, ok := usersMap[username]; ok { return key, true } return nil, false }, // PacketConnConfigs is a list of UDP Listeners and the configuration around them PacketConnConfigs: []turn.PacketConnConfig{ { PacketConn: udpListener, RelayAddressGenerator: &turn.RelayAddressGeneratorStatic{ RelayAddress: net.ParseIP(*publicIP), // Claim that we are listening on IP passed by user (This should be your Public IP) Address: "0.0.0.0", // But actually be listening on every interface }, }, }, }) if err != nil { log.Panic(err) } // Block until user sends SIGINT or SIGTERM sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) <-sigs if err = s.Close(); err != nil { log.Panic(err) } } turn-2.1.0/examples/turn-server/tcp/000077500000000000000000000000001436753726200174215ustar00rootroot00000000000000turn-2.1.0/examples/turn-server/tcp/main.go000066400000000000000000000044341436753726200207010ustar00rootroot00000000000000// Package main implements an example TURN server supporting TCP package main import ( "flag" "log" "net" "os" "os/signal" "regexp" "strconv" "syscall" "github.com/pion/turn/v2" ) func main() { publicIP := flag.String("public-ip", "", "IP Address that TURN can be contacted by.") port := flag.Int("port", 3478, "Listening port.") users := flag.String("users", "", "List of username and password (e.g. \"user=pass,user=pass\")") realm := flag.String("realm", "pion.ly", "Realm (defaults to \"pion.ly\")") flag.Parse() if len(*publicIP) == 0 { log.Fatalf("'public-ip' is required") } else if len(*users) == 0 { log.Fatalf("'users' is required") } // Create a TCP listener to pass into pion/turn // pion/turn itself doesn't allocate any TCP listeners, but lets the user pass them in // this allows us to add logging, storage or modify inbound/outbound traffic tcpListener, err := net.Listen("tcp4", "0.0.0.0:"+strconv.Itoa(*port)) if err != nil { log.Panicf("Failed to create TURN server listener: %s", err) } // Cache -users flag for easy lookup later // If passwords are stored they should be saved to your DB hashed using turn.GenerateAuthKey usersMap := map[string][]byte{} for _, kv := range regexp.MustCompile(`(\w+)=(\w+)`).FindAllStringSubmatch(*users, -1) { usersMap[kv[1]] = turn.GenerateAuthKey(kv[1], *realm, kv[2]) } s, err := turn.NewServer(turn.ServerConfig{ Realm: *realm, // Set AuthHandler callback // This is called every time a user tries to authenticate with the TURN server // Return the key for that user, or false when no user is found AuthHandler: func(username string, realm string, srcAddr net.Addr) ([]byte, bool) { if key, ok := usersMap[username]; ok { return key, true } return nil, false }, // ListenerConfig is a list of Listeners and the configuration around them ListenerConfigs: []turn.ListenerConfig{ { Listener: tcpListener, RelayAddressGenerator: &turn.RelayAddressGeneratorStatic{ RelayAddress: net.ParseIP(*publicIP), Address: "0.0.0.0", }, }, }, }) if err != nil { log.Panic(err) } // Block until user sends SIGINT or SIGTERM sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) <-sigs if err = s.Close(); err != nil { log.Panic(err) } } turn-2.1.0/examples/turn-server/tls/000077500000000000000000000000001436753726200174355ustar00rootroot00000000000000turn-2.1.0/examples/turn-server/tls/main.go000066400000000000000000000051561436753726200207170ustar00rootroot00000000000000// Package main implements a TURN server with TLS support package main import ( "crypto/tls" "flag" "log" "net" "os" "os/signal" "regexp" "strconv" "syscall" "github.com/pion/turn/v2" ) func main() { publicIP := flag.String("public-ip", "", "IP Address that TURN can be contacted by.") port := flag.Int("port", 5349, "Listening port.") users := flag.String("users", "", "List of username and password (e.g. \"user=pass,user=pass\")") realm := flag.String("realm", "pion.ly", "Realm (defaults to \"pion.ly\")") certFile := flag.String("cert", "server.crt", "Certificate (defaults to \"server.crt\")") keyFile := flag.String("key", "server.key", "Key (defaults to \"server.key\")") flag.Parse() if len(*publicIP) == 0 { log.Fatalf("'public-ip' is required") } else if len(*users) == 0 { log.Fatalf("'users' is required") } cer, err := tls.LoadX509KeyPair(*certFile, *keyFile) if err != nil { log.Println(err) return } // Create a TLS listener to pass into pion/turn // pion/turn itself doesn't allocate any TLS listeners, but lets the user pass them in // this allows us to add logging, storage or modify inbound/outbound traffic tlsListener, err := tls.Listen("tcp4", "0.0.0.0:"+strconv.Itoa(*port), &tls.Config{ MinVersion: tls.VersionTLS12, Certificates: []tls.Certificate{cer}, }) if err != nil { log.Println(err) return } // Cache -users flag for easy lookup later // If passwords are stored they should be saved to your DB hashed using turn.GenerateAuthKey usersMap := map[string][]byte{} for _, kv := range regexp.MustCompile(`(\w+)=(\w+)`).FindAllStringSubmatch(*users, -1) { usersMap[kv[1]] = turn.GenerateAuthKey(kv[1], *realm, kv[2]) } s, err := turn.NewServer(turn.ServerConfig{ Realm: *realm, // Set AuthHandler callback // This is called every time a user tries to authenticate with the TURN server // Return the key for that user, or false when no user is found AuthHandler: func(username string, realm string, srcAddr net.Addr) ([]byte, bool) { if key, ok := usersMap[username]; ok { return key, true } return nil, false }, // ListenerConfig is a list of Listeners and the configuration around them ListenerConfigs: []turn.ListenerConfig{ { Listener: tlsListener, RelayAddressGenerator: &turn.RelayAddressGeneratorStatic{ RelayAddress: net.ParseIP(*publicIP), Address: "0.0.0.0", }, }, }, }) if err != nil { log.Panic(err) } // Block until user sends SIGINT or SIGTERM sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) <-sigs if err = s.Close(); err != nil { log.Panic(err) } } turn-2.1.0/go.mod000066400000000000000000000003341436753726200136270ustar00rootroot00000000000000module github.com/pion/turn/v2 go 1.13 require ( github.com/pion/logging v0.2.2 github.com/pion/randutil v0.1.0 github.com/pion/stun v0.4.0 github.com/pion/transport/v2 v2.0.0 github.com/stretchr/testify v1.8.1 ) turn-2.1.0/go.sum000066400000000000000000000117351436753726200136630ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= github.com/pion/stun v0.4.0 h1:vgRrbBE2htWHy7l3Zsxckk7rkjnjOsSM7PHZnBwo8rk= github.com/pion/stun v0.4.0/go.mod h1:QPsh1/SbXASntw3zkkrIk3ZJVKz4saBY2G7S10P3wCw= github.com/pion/transport/v2 v2.0.0 h1:bsMYyqHCbkvHwj+eNCFBuxtlKndKfyGI2vaQmM3fIE4= github.com/pion/transport/v2 v2.0.0/go.mod h1:HS2MEBJTwD+1ZI2eSXSvHJx/HnzQqRy2/LXxt6eVMHc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= turn-2.1.0/internal/000077500000000000000000000000001436753726200143355ustar00rootroot00000000000000turn-2.1.0/internal/allocation/000077500000000000000000000000001436753726200164625ustar00rootroot00000000000000turn-2.1.0/internal/allocation/allocation.go000066400000000000000000000215001436753726200211340ustar00rootroot00000000000000// Package allocation contains all CRUD operations for allocations package allocation import ( "net" "sync" "sync/atomic" "time" "github.com/pion/logging" "github.com/pion/stun" "github.com/pion/turn/v2/internal/ipnet" "github.com/pion/turn/v2/internal/proto" ) type allocationResponse struct { transactionID [stun.TransactionIDSize]byte responseAttrs []stun.Setter } // Allocation is tied to a FiveTuple and relays traffic // use CreateAllocation and GetAllocation to operate type Allocation struct { RelayAddr net.Addr Protocol Protocol TurnSocket net.PacketConn RelaySocket net.PacketConn fiveTuple *FiveTuple permissionsLock sync.RWMutex permissions map[string]*Permission channelBindingsLock sync.RWMutex channelBindings []*ChannelBind lifetimeTimer *time.Timer closed chan interface{} log logging.LeveledLogger // some clients (Firefox or others using resiprocate's nICE lib) may retry allocation // with same 5 tuple when received 413, for compatible with these clients, // cache for response lost and client retry to implement 'stateless stack approach' // https://datatracker.ietf.org/doc/html/rfc5766#section-6.2 responseCache atomic.Value // *allocationResponse } func addr2IPFingerprint(addr net.Addr) string { switch a := addr.(type) { case *net.UDPAddr: return a.IP.String() case *net.TCPAddr: // Do we really need this case? return a.IP.String() } return "" // should never happen } // NewAllocation creates a new instance of NewAllocation. func NewAllocation(turnSocket net.PacketConn, fiveTuple *FiveTuple, log logging.LeveledLogger) *Allocation { return &Allocation{ TurnSocket: turnSocket, fiveTuple: fiveTuple, permissions: make(map[string]*Permission, 64), closed: make(chan interface{}), log: log, } } // GetPermission gets the Permission from the allocation func (a *Allocation) GetPermission(addr net.Addr) *Permission { a.permissionsLock.RLock() defer a.permissionsLock.RUnlock() return a.permissions[addr2IPFingerprint(addr)] } // AddPermission adds a new permission to the allocation func (a *Allocation) AddPermission(p *Permission) { fingerprint := addr2IPFingerprint(p.Addr) a.permissionsLock.RLock() existedPermission, ok := a.permissions[fingerprint] a.permissionsLock.RUnlock() if ok { existedPermission.refresh(permissionTimeout) return } p.allocation = a a.permissionsLock.Lock() a.permissions[fingerprint] = p a.permissionsLock.Unlock() p.start(permissionTimeout) } // RemovePermission removes the net.Addr's fingerprint from the allocation's permissions func (a *Allocation) RemovePermission(addr net.Addr) { a.permissionsLock.Lock() defer a.permissionsLock.Unlock() delete(a.permissions, addr2IPFingerprint(addr)) } // AddChannelBind adds a new ChannelBind to the allocation, it also updates the // permissions needed for this ChannelBind func (a *Allocation) AddChannelBind(c *ChannelBind, lifetime time.Duration) error { // Check that this channel id isn't bound to another transport address, and // that this transport address isn't bound to another channel number. channelByNumber := a.GetChannelByNumber(c.Number) if channelByNumber != a.GetChannelByAddr(c.Peer) { return errSameChannelDifferentPeer } // Add or refresh this channel. if channelByNumber == nil { a.channelBindingsLock.Lock() defer a.channelBindingsLock.Unlock() c.allocation = a a.channelBindings = append(a.channelBindings, c) c.start(lifetime) // Channel binds also refresh permissions. a.AddPermission(NewPermission(c.Peer, a.log)) } else { channelByNumber.refresh(lifetime) // Channel binds also refresh permissions. a.AddPermission(NewPermission(channelByNumber.Peer, a.log)) } return nil } // RemoveChannelBind removes the ChannelBind from this allocation by id func (a *Allocation) RemoveChannelBind(number proto.ChannelNumber) bool { a.channelBindingsLock.Lock() defer a.channelBindingsLock.Unlock() for i := len(a.channelBindings) - 1; i >= 0; i-- { if a.channelBindings[i].Number == number { a.channelBindings = append(a.channelBindings[:i], a.channelBindings[i+1:]...) return true } } return false } // GetChannelByNumber gets the ChannelBind from this allocation by id func (a *Allocation) GetChannelByNumber(number proto.ChannelNumber) *ChannelBind { a.channelBindingsLock.RLock() defer a.channelBindingsLock.RUnlock() for _, cb := range a.channelBindings { if cb.Number == number { return cb } } return nil } // GetChannelByAddr gets the ChannelBind from this allocation by net.Addr func (a *Allocation) GetChannelByAddr(addr net.Addr) *ChannelBind { a.channelBindingsLock.RLock() defer a.channelBindingsLock.RUnlock() for _, cb := range a.channelBindings { if ipnet.AddrEqual(cb.Peer, addr) { return cb } } return nil } // Refresh updates the allocations lifetime func (a *Allocation) Refresh(lifetime time.Duration) { if !a.lifetimeTimer.Reset(lifetime) { a.log.Errorf("Failed to reset allocation timer for %v", a.fiveTuple) } } // SetResponseCache cache allocation response for retransmit allocation request func (a *Allocation) SetResponseCache(transactionID [stun.TransactionIDSize]byte, attrs []stun.Setter) { a.responseCache.Store(&allocationResponse{ transactionID: transactionID, responseAttrs: attrs, }) } // GetResponseCache return response cache for retransmit allocation request func (a *Allocation) GetResponseCache() (id [stun.TransactionIDSize]byte, attrs []stun.Setter) { if res, ok := a.responseCache.Load().(*allocationResponse); ok && res != nil { id, attrs = res.transactionID, res.responseAttrs } return } // Close closes the allocation func (a *Allocation) Close() error { select { case <-a.closed: return nil default: } close(a.closed) a.lifetimeTimer.Stop() a.permissionsLock.RLock() for _, p := range a.permissions { p.lifetimeTimer.Stop() } a.permissionsLock.RUnlock() a.channelBindingsLock.RLock() for _, c := range a.channelBindings { c.lifetimeTimer.Stop() } a.channelBindingsLock.RUnlock() return a.RelaySocket.Close() } // https://tools.ietf.org/html/rfc5766#section-10.3 // When the server receives a UDP datagram at a currently allocated // relayed transport address, the server looks up the allocation // associated with the relayed transport address. The server then // checks to see whether the set of permissions for the allocation allow // the relaying of the UDP datagram as described in Section 8. // // If relaying is permitted, then the server checks if there is a // channel bound to the peer that sent the UDP datagram (see // Section 11). If a channel is bound, then processing proceeds as // described in Section 11.7. // // If relaying is permitted but no channel is bound to the peer, then // the server forms and sends a Data indication. The Data indication // MUST contain both an XOR-PEER-ADDRESS and a DATA attribute. The DATA // attribute is set to the value of the 'data octets' field from the // datagram, and the XOR-PEER-ADDRESS attribute is set to the source // transport address of the received UDP datagram. The Data indication // is then sent on the 5-tuple associated with the allocation. const rtpMTU = 1600 func (a *Allocation) packetHandler(m *Manager) { buffer := make([]byte, rtpMTU) for { n, srcAddr, err := a.RelaySocket.ReadFrom(buffer) if err != nil { m.DeleteAllocation(a.fiveTuple) return } a.log.Debugf("relay socket %s received %d bytes from %s", a.RelaySocket.LocalAddr().String(), n, srcAddr.String()) if channel := a.GetChannelByAddr(srcAddr); channel != nil { channelData := &proto.ChannelData{ Data: buffer[:n], Number: channel.Number, } channelData.Encode() if _, err = a.TurnSocket.WriteTo(channelData.Raw, a.fiveTuple.SrcAddr); err != nil { a.log.Errorf("Failed to send ChannelData from allocation %v %v", srcAddr, err) } } else if p := a.GetPermission(srcAddr); p != nil { udpAddr, ok := srcAddr.(*net.UDPAddr) if !ok { a.log.Errorf("Failed to send DataIndication from allocation %v %v", srcAddr, err) return } peerAddressAttr := proto.PeerAddress{IP: udpAddr.IP, Port: udpAddr.Port} dataAttr := proto.Data(buffer[:n]) msg, err := stun.Build(stun.TransactionID, stun.NewType(stun.MethodData, stun.ClassIndication), peerAddressAttr, dataAttr) if err != nil { a.log.Errorf("Failed to send DataIndication from allocation %v %v", srcAddr, err) return } a.log.Debugf("relaying message from %s to client at %s", srcAddr.String(), a.fiveTuple.SrcAddr.String()) if _, err = a.TurnSocket.WriteTo(msg.Raw, a.fiveTuple.SrcAddr); err != nil { a.log.Errorf("Failed to send DataIndication from allocation %v %v", srcAddr, err) } } else { a.log.Infof("No Permission or Channel exists for %v on allocation %v", srcAddr, a.RelayAddr.String()) } } } turn-2.1.0/internal/allocation/allocation_manager.go000066400000000000000000000125541436753726200226370ustar00rootroot00000000000000package allocation import ( "fmt" "net" "sync" "time" "github.com/pion/logging" ) // ManagerConfig a bag of config params for Manager. type ManagerConfig struct { LeveledLogger logging.LeveledLogger AllocatePacketConn func(network string, requestedPort int) (net.PacketConn, net.Addr, error) AllocateConn func(network string, requestedPort int) (net.Conn, net.Addr, error) PermissionHandler func(sourceAddr net.Addr, peerIP net.IP) bool } type reservation struct { token string port int } // Manager is used to hold active allocations type Manager struct { lock sync.RWMutex log logging.LeveledLogger allocations map[string]*Allocation reservations []*reservation allocatePacketConn func(network string, requestedPort int) (net.PacketConn, net.Addr, error) allocateConn func(network string, requestedPort int) (net.Conn, net.Addr, error) permissionHandler func(sourceAddr net.Addr, peerIP net.IP) bool } // NewManager creates a new instance of Manager. func NewManager(config ManagerConfig) (*Manager, error) { switch { case config.AllocatePacketConn == nil: return nil, errAllocatePacketConnMustBeSet case config.AllocateConn == nil: return nil, errAllocateConnMustBeSet case config.LeveledLogger == nil: return nil, errLeveledLoggerMustBeSet } return &Manager{ log: config.LeveledLogger, allocations: make(map[string]*Allocation, 64), allocatePacketConn: config.AllocatePacketConn, allocateConn: config.AllocateConn, permissionHandler: config.PermissionHandler, }, nil } // GetAllocation fetches the allocation matching the passed FiveTuple func (m *Manager) GetAllocation(fiveTuple *FiveTuple) *Allocation { m.lock.RLock() defer m.lock.RUnlock() return m.allocations[fiveTuple.Fingerprint()] } // AllocationCount returns the number of existing allocations func (m *Manager) AllocationCount() int { m.lock.RLock() defer m.lock.RUnlock() return len(m.allocations) } // Close closes the manager and closes all allocations it manages func (m *Manager) Close() error { m.lock.Lock() defer m.lock.Unlock() for _, a := range m.allocations { if err := a.Close(); err != nil { return err } } return nil } // CreateAllocation creates a new allocation and starts relaying func (m *Manager) CreateAllocation(fiveTuple *FiveTuple, turnSocket net.PacketConn, requestedPort int, lifetime time.Duration) (*Allocation, error) { switch { case fiveTuple == nil: return nil, errNilFiveTuple case fiveTuple.SrcAddr == nil: return nil, errNilFiveTupleSrcAddr case fiveTuple.DstAddr == nil: return nil, errNilFiveTupleDstAddr case turnSocket == nil: return nil, errNilTurnSocket case lifetime == 0: return nil, errLifetimeZero } if a := m.GetAllocation(fiveTuple); a != nil { return nil, fmt.Errorf("%w: %v", errDupeFiveTuple, fiveTuple) } a := NewAllocation(turnSocket, fiveTuple, m.log) conn, relayAddr, err := m.allocatePacketConn("udp4", requestedPort) if err != nil { return nil, err } a.RelaySocket = conn a.RelayAddr = relayAddr m.log.Debugf("listening on relay addr: %s", a.RelayAddr.String()) a.lifetimeTimer = time.AfterFunc(lifetime, func() { m.DeleteAllocation(a.fiveTuple) }) m.lock.Lock() m.allocations[fiveTuple.Fingerprint()] = a m.lock.Unlock() go a.packetHandler(m) return a, nil } // DeleteAllocation removes an allocation func (m *Manager) DeleteAllocation(fiveTuple *FiveTuple) { fingerprint := fiveTuple.Fingerprint() m.lock.Lock() allocation := m.allocations[fingerprint] delete(m.allocations, fingerprint) m.lock.Unlock() if allocation == nil { return } if err := allocation.Close(); err != nil { m.log.Errorf("Failed to close allocation: %v", err) } } // CreateReservation stores the reservation for the token+port func (m *Manager) CreateReservation(reservationToken string, port int) { time.AfterFunc(30*time.Second, func() { m.lock.Lock() defer m.lock.Unlock() for i := len(m.reservations) - 1; i >= 0; i-- { if m.reservations[i].token == reservationToken { m.reservations = append(m.reservations[:i], m.reservations[i+1:]...) return } } }) m.lock.Lock() m.reservations = append(m.reservations, &reservation{ token: reservationToken, port: port, }) m.lock.Unlock() } // GetReservation returns the port for a given reservation if it exists func (m *Manager) GetReservation(reservationToken string) (int, bool) { m.lock.RLock() defer m.lock.RUnlock() for _, r := range m.reservations { if r.token == reservationToken { return r.port, true } } return 0, false } // GetRandomEvenPort returns a random un-allocated udp4 port func (m *Manager) GetRandomEvenPort() (int, error) { for i := 0; i < 128; i++ { conn, addr, err := m.allocatePacketConn("udp4", 0) if err != nil { return 0, err } udpAddr, ok := addr.(*net.UDPAddr) err = conn.Close() if err != nil { return 0, err } if !ok { return 0, errFailedToCastUDPAddr } if udpAddr.Port%2 == 0 { return udpAddr.Port, nil } } return 0, errFailedToAllocateEvenPort } // GrantPermission handles permission requests by calling the permission handler callback // associated with the TURN server listener socket func (m *Manager) GrantPermission(sourceAddr net.Addr, peerIP net.IP) error { // no permission handler: open if m.permissionHandler == nil { return nil } if m.permissionHandler(sourceAddr, peerIP) { return nil } return errAdminProhibited } turn-2.1.0/internal/allocation/allocation_manager_test.go000066400000000000000000000132511436753726200236710ustar00rootroot00000000000000//go:build !js // +build !js package allocation import ( "io" "math/rand" "net" "strings" "testing" "time" "github.com/pion/logging" "github.com/pion/turn/v2/internal/proto" "github.com/stretchr/testify/assert" ) func TestManager(t *testing.T) { tt := []struct { name string f func(*testing.T, net.PacketConn) }{ {"CreateInvalidAllocation", subTestCreateInvalidAllocation}, {"CreateAllocation", subTestCreateAllocation}, {"CreateAllocationDuplicateFiveTuple", subTestCreateAllocationDuplicateFiveTuple}, {"DeleteAllocation", subTestDeleteAllocation}, {"AllocationTimeout", subTestAllocationTimeout}, {"Close", subTestManagerClose}, {"GetRandomEvenPort", subTestGetRandomEvenPort}, } network := "udp4" turnSocket, err := net.ListenPacket(network, "0.0.0.0:0") if err != nil { panic(err) } for _, tc := range tt { f := tc.f t.Run(tc.name, func(t *testing.T) { f(t, turnSocket) }) } } // test invalid Allocation creations func subTestCreateInvalidAllocation(t *testing.T, turnSocket net.PacketConn) { m, err := newTestManager() assert.NoError(t, err) if a, err := m.CreateAllocation(nil, turnSocket, 0, proto.DefaultLifetime); a != nil || err == nil { t.Errorf("Illegally created allocation with nil FiveTuple") } if a, err := m.CreateAllocation(randomFiveTuple(), nil, 0, proto.DefaultLifetime); a != nil || err == nil { t.Errorf("Illegally created allocation with nil turnSocket") } if a, err := m.CreateAllocation(randomFiveTuple(), turnSocket, 0, 0); a != nil || err == nil { t.Errorf("Illegally created allocation with 0 lifetime") } } // test valid Allocation creations func subTestCreateAllocation(t *testing.T, turnSocket net.PacketConn) { m, err := newTestManager() assert.NoError(t, err) fiveTuple := randomFiveTuple() if a, err := m.CreateAllocation(fiveTuple, turnSocket, 0, proto.DefaultLifetime); a == nil || err != nil { t.Errorf("Failed to create allocation %v %v", a, err) } if a := m.GetAllocation(fiveTuple); a == nil { t.Errorf("Failed to get allocation right after creation") } } // test that two allocations can't be created with the same FiveTuple func subTestCreateAllocationDuplicateFiveTuple(t *testing.T, turnSocket net.PacketConn) { m, err := newTestManager() assert.NoError(t, err) fiveTuple := randomFiveTuple() if a, err := m.CreateAllocation(fiveTuple, turnSocket, 0, proto.DefaultLifetime); a == nil || err != nil { t.Errorf("Failed to create allocation %v %v", a, err) } if a, err := m.CreateAllocation(fiveTuple, turnSocket, 0, proto.DefaultLifetime); a != nil || err == nil { t.Errorf("Was able to create allocation with same FiveTuple twice") } } func subTestDeleteAllocation(t *testing.T, turnSocket net.PacketConn) { m, err := newTestManager() assert.NoError(t, err) fiveTuple := randomFiveTuple() if a, err := m.CreateAllocation(fiveTuple, turnSocket, 0, proto.DefaultLifetime); a == nil || err != nil { t.Errorf("Failed to create allocation %v %v", a, err) } if a := m.GetAllocation(fiveTuple); a == nil { t.Errorf("Failed to get allocation right after creation") } m.DeleteAllocation(fiveTuple) if a := m.GetAllocation(fiveTuple); a != nil { t.Errorf("Get allocation with %v should be nil after delete", fiveTuple) } } // test that allocation should be closed if timeout func subTestAllocationTimeout(t *testing.T, turnSocket net.PacketConn) { m, err := newTestManager() assert.NoError(t, err) allocations := make([]*Allocation, 5) lifetime := time.Second for index := range allocations { fiveTuple := randomFiveTuple() a, err := m.CreateAllocation(fiveTuple, turnSocket, 0, lifetime) if err != nil { t.Errorf("Failed to create allocation with %v", fiveTuple) } allocations[index] = a } // make sure all allocations timeout time.Sleep(lifetime + time.Second) for _, alloc := range allocations { if !isClose(alloc.RelaySocket) { t.Error("Allocation relay socket should be closed if lifetime timeout") } } } // test for manager close func subTestManagerClose(t *testing.T, turnSocket net.PacketConn) { m, err := newTestManager() assert.NoError(t, err) allocations := make([]*Allocation, 2) a1, _ := m.CreateAllocation(randomFiveTuple(), turnSocket, 0, time.Second) allocations[0] = a1 a2, _ := m.CreateAllocation(randomFiveTuple(), turnSocket, 0, time.Minute) allocations[1] = a2 // make a1 timeout time.Sleep(2 * time.Second) if err := m.Close(); err != nil { t.Errorf("Manager close with error: %v", err) } for _, alloc := range allocations { if !isClose(alloc.RelaySocket) { t.Error("Manager's allocations should be closed") } } } func randomFiveTuple() *FiveTuple { /* #nosec */ return &FiveTuple{ SrcAddr: &net.UDPAddr{IP: nil, Port: rand.Int()}, DstAddr: &net.UDPAddr{IP: nil, Port: rand.Int()}, } } func newTestManager() (*Manager, error) { loggerFactory := logging.NewDefaultLoggerFactory() config := ManagerConfig{ LeveledLogger: loggerFactory.NewLogger("test"), AllocatePacketConn: func(network string, requestedPort int) (net.PacketConn, net.Addr, error) { conn, err := net.ListenPacket("udp4", "0.0.0.0:0") if err != nil { return nil, nil, err } return conn, conn.LocalAddr(), nil }, AllocateConn: func(network string, requestedPort int) (net.Conn, net.Addr, error) { return nil, nil, nil }, } return NewManager(config) } func isClose(conn io.Closer) bool { closeErr := conn.Close() return closeErr != nil && strings.Contains(closeErr.Error(), "use of closed network connection") } func subTestGetRandomEvenPort(t *testing.T, turnSocket net.PacketConn) { m, err := newTestManager() assert.NoError(t, err) port, err := m.GetRandomEvenPort() assert.NoError(t, err) assert.True(t, port > 0) assert.True(t, port%2 == 0) } turn-2.1.0/internal/allocation/allocation_test.go000066400000000000000000000223671436753726200222070ustar00rootroot00000000000000//go:build !js // +build !js package allocation import ( "fmt" "net" "sync" "testing" "time" "github.com/pion/stun" "github.com/pion/turn/v2/internal/ipnet" "github.com/pion/turn/v2/internal/proto" "github.com/stretchr/testify/assert" ) func TestAllocation(t *testing.T) { tt := []struct { name string f func(*testing.T) }{ {"GetPermission", subTestGetPermission}, {"AddPermission", subTestAddPermission}, {"RemovePermission", subTestRemovePermission}, {"AddChannelBind", subTestAddChannelBind}, {"GetChannelByNumber", subTestGetChannelByNumber}, {"GetChannelByAddr", subTestGetChannelByAddr}, {"RemoveChannelBind", subTestRemoveChannelBind}, {"Refresh", subTestAllocationRefresh}, {"Close", subTestAllocationClose}, {"packetHandler", subTestPacketHandler}, {"ResponseCache", subTestResponseCache}, } for _, tc := range tt { f := tc.f t.Run(tc.name, func(t *testing.T) { f(t) }) } } func subTestGetPermission(t *testing.T) { a := NewAllocation(nil, nil, nil) addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:3478") if err != nil { t.Fatalf("failed to resolve: %s", err) } addr2, err := net.ResolveUDPAddr("udp", "127.0.0.1:3479") if err != nil { t.Fatalf("failed to resolve: %s", err) } addr3, err := net.ResolveUDPAddr("udp", "127.0.0.2:3478") if err != nil { t.Fatalf("failed to resolve: %s", err) } p := &Permission{ Addr: addr, } p2 := &Permission{ Addr: addr2, } p3 := &Permission{ Addr: addr3, } a.AddPermission(p) a.AddPermission(p2) a.AddPermission(p3) foundP1 := a.GetPermission(addr) assert.Equal(t, p, foundP1, "Should keep the first one.") foundP2 := a.GetPermission(addr2) assert.Equal(t, p, foundP2, "Second one should be ignored.") foundP3 := a.GetPermission(addr3) assert.Equal(t, p3, foundP3, "Permission with another IP should be found") } func subTestAddPermission(t *testing.T) { a := NewAllocation(nil, nil, nil) addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:3478") if err != nil { t.Fatalf("failed to resolve: %s", err) } p := &Permission{ Addr: addr, } a.AddPermission(p) assert.Equal(t, a, p.allocation, "Permission's allocation should be the adder.") foundPermission := a.GetPermission(p.Addr) assert.Equal(t, p, foundPermission) } func subTestRemovePermission(t *testing.T) { a := NewAllocation(nil, nil, nil) addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:3478") if err != nil { t.Fatalf("failed to resolve: %s", err) } p := &Permission{ Addr: addr, } a.AddPermission(p) foundPermission := a.GetPermission(p.Addr) assert.Equal(t, p, foundPermission, "Got permission is not same as the the added.") a.RemovePermission(p.Addr) foundPermission = a.GetPermission(p.Addr) assert.Nil(t, foundPermission, "Got permission should be nil after removed.") } func subTestAddChannelBind(t *testing.T) { a := NewAllocation(nil, nil, nil) addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:3478") if err != nil { t.Fatalf("failed to resolve: %s", err) } c := NewChannelBind(proto.MinChannelNumber, addr, nil) err = a.AddChannelBind(c, proto.DefaultLifetime) assert.Nil(t, err, "should succeed") assert.Equal(t, a, c.allocation, "allocation should be the caller.") c2 := NewChannelBind(proto.MinChannelNumber+1, addr, nil) err = a.AddChannelBind(c2, proto.DefaultLifetime) assert.NotNil(t, err, "should failed with conflicted peer address") addr2, _ := net.ResolveUDPAddr("udp", "127.0.0.1:3479") c3 := NewChannelBind(proto.MinChannelNumber, addr2, nil) err = a.AddChannelBind(c3, proto.DefaultLifetime) assert.NotNil(t, err, "should fail with conflicted number.") } func subTestGetChannelByNumber(t *testing.T) { a := NewAllocation(nil, nil, nil) addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:3478") if err != nil { t.Fatalf("failed to resolve: %s", err) } c := NewChannelBind(proto.MinChannelNumber, addr, nil) _ = a.AddChannelBind(c, proto.DefaultLifetime) existChannel := a.GetChannelByNumber(c.Number) assert.Equal(t, c, existChannel) notExistChannel := a.GetChannelByNumber(proto.MinChannelNumber + 1) assert.Nil(t, notExistChannel, "should be nil for not existed channel.") } func subTestGetChannelByAddr(t *testing.T) { a := NewAllocation(nil, nil, nil) addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:3478") if err != nil { t.Fatalf("failed to resolve: %s", err) } c := NewChannelBind(proto.MinChannelNumber, addr, nil) _ = a.AddChannelBind(c, proto.DefaultLifetime) existChannel := a.GetChannelByAddr(c.Peer) assert.Equal(t, c, existChannel) addr2, _ := net.ResolveUDPAddr("udp", "127.0.0.1:3479") notExistChannel := a.GetChannelByAddr(addr2) assert.Nil(t, notExistChannel, "should be nil for not existed channel.") } func subTestRemoveChannelBind(t *testing.T) { a := NewAllocation(nil, nil, nil) addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:3478") if err != nil { t.Fatalf("failed to resolve: %s", err) } c := NewChannelBind(proto.MinChannelNumber, addr, nil) _ = a.AddChannelBind(c, proto.DefaultLifetime) a.RemoveChannelBind(c.Number) channelByNumber := a.GetChannelByNumber(c.Number) assert.Nil(t, channelByNumber) channelByAddr := a.GetChannelByAddr(c.Peer) assert.Nil(t, channelByAddr) } func subTestAllocationRefresh(t *testing.T) { a := NewAllocation(nil, nil, nil) var wg sync.WaitGroup wg.Add(1) a.lifetimeTimer = time.AfterFunc(proto.DefaultLifetime, func() { wg.Done() }) a.Refresh(0) wg.Wait() // lifetimeTimer has expired assert.False(t, a.lifetimeTimer.Stop()) } func subTestAllocationClose(t *testing.T) { network := "udp" l, err := net.ListenPacket(network, "0.0.0.0:0") if err != nil { panic(err) } a := NewAllocation(nil, nil, nil) a.RelaySocket = l // add mock lifetimeTimer a.lifetimeTimer = time.AfterFunc(proto.DefaultLifetime, func() {}) // add channel addr, err := net.ResolveUDPAddr(network, "127.0.0.1:3478") if err != nil { t.Fatalf("failed to resolve: %s", err) } c := NewChannelBind(proto.MinChannelNumber, addr, nil) _ = a.AddChannelBind(c, proto.DefaultLifetime) // add permission a.AddPermission(NewPermission(addr, nil)) err = a.Close() assert.Nil(t, err, "should succeed") assert.True(t, isClose(a.RelaySocket), "should be closed") } func subTestPacketHandler(t *testing.T) { network := "udp" m, _ := newTestManager() // turn server initialization turnSocket, err := net.ListenPacket(network, "127.0.0.1:0") if err != nil { panic(err) } // client listener initialization clientListener, err := net.ListenPacket(network, "127.0.0.1:0") if err != nil { panic(err) } dataCh := make(chan []byte) // client listener read data go func() { buffer := make([]byte, rtpMTU) for { n, _, err2 := clientListener.ReadFrom(buffer) if err2 != nil { return } dataCh <- buffer[:n] } }() a, err := m.CreateAllocation(&FiveTuple{ SrcAddr: clientListener.LocalAddr(), DstAddr: turnSocket.LocalAddr(), }, turnSocket, 0, proto.DefaultLifetime) assert.Nil(t, err, "should succeed") peerListener1, err := net.ListenPacket(network, "127.0.0.1:0") if err != nil { panic(err) } peerListener2, err := net.ListenPacket(network, "127.0.0.1:0") if err != nil { panic(err) } // add permission with peer1 address a.AddPermission(NewPermission(peerListener1.LocalAddr(), m.log)) // add channel with min channel number and peer2 address channelBind := NewChannelBind(proto.MinChannelNumber, peerListener2.LocalAddr(), m.log) _ = a.AddChannelBind(channelBind, proto.DefaultLifetime) _, port, _ := ipnet.AddrIPPort(a.RelaySocket.LocalAddr()) relayAddrWithHostStr := fmt.Sprintf("127.0.0.1:%d", port) relayAddrWithHost, _ := net.ResolveUDPAddr(network, relayAddrWithHostStr) // test for permission and data message targetText := "permission" _, _ = peerListener1.WriteTo([]byte(targetText), relayAddrWithHost) data := <-dataCh // resolve stun data message assert.True(t, stun.IsMessage(data), "should be stun message") var msg stun.Message err = stun.Decode(data, &msg) assert.Nil(t, err, "decode data to stun message failed") var msgData proto.Data err = msgData.GetFrom(&msg) assert.Nil(t, err, "get data from stun message failed") assert.Equal(t, targetText, string(msgData), "get message doesn't equal the target text") // test for channel bind and channel data targetText2 := "channel bind" _, _ = peerListener2.WriteTo([]byte(targetText2), relayAddrWithHost) data = <-dataCh // resolve channel data assert.True(t, proto.IsChannelData(data), "should be channel data") channelData := proto.ChannelData{ Raw: data, } err = channelData.Decode() assert.Nil(t, err, fmt.Sprintf("channel data decode with error: %v", err)) assert.Equal(t, channelBind.Number, channelData.Number, "get channel data's number is invalid") assert.Equal(t, targetText2, string(channelData.Data), "get data doesn't equal the target text.") // listeners close _ = m.Close() _ = clientListener.Close() _ = peerListener1.Close() _ = peerListener2.Close() } func subTestResponseCache(t *testing.T) { a := NewAllocation(nil, nil, nil) transactionID := [stun.TransactionIDSize]byte{1, 2, 3} responseAttrs := []stun.Setter{ &proto.Lifetime{ Duration: proto.DefaultLifetime, }, } a.SetResponseCache(transactionID, responseAttrs) cacheID, cacheAttr := a.GetResponseCache() assert.Equal(t, transactionID, cacheID) assert.Equal(t, responseAttrs, cacheAttr) } turn-2.1.0/internal/allocation/channel_bind.go000066400000000000000000000020661436753726200214210ustar00rootroot00000000000000package allocation import ( "net" "time" "github.com/pion/logging" "github.com/pion/turn/v2/internal/proto" ) // ChannelBind represents a TURN Channel // https://tools.ietf.org/html/rfc5766#section-2.5 type ChannelBind struct { Peer net.Addr Number proto.ChannelNumber allocation *Allocation lifetimeTimer *time.Timer log logging.LeveledLogger } // NewChannelBind creates a new ChannelBind func NewChannelBind(number proto.ChannelNumber, peer net.Addr, log logging.LeveledLogger) *ChannelBind { return &ChannelBind{ Number: number, Peer: peer, log: log, } } func (c *ChannelBind) start(lifetime time.Duration) { c.lifetimeTimer = time.AfterFunc(lifetime, func() { if !c.allocation.RemoveChannelBind(c.Number) { c.log.Errorf("Failed to remove ChannelBind for %v %x %v", c.Number, c.Peer, c.allocation.fiveTuple) } }) } func (c *ChannelBind) refresh(lifetime time.Duration) { if !c.lifetimeTimer.Reset(lifetime) { c.log.Errorf("Failed to reset ChannelBind timer for %v %x %v", c.Number, c.Peer, c.allocation.fiveTuple) } } turn-2.1.0/internal/allocation/channel_bind_test.go000066400000000000000000000021711436753726200224550ustar00rootroot00000000000000package allocation import ( "net" "testing" "time" "github.com/pion/turn/v2/internal/proto" ) func TestChannelBind(t *testing.T) { c := newChannelBind(2 * time.Second) if c.allocation.GetChannelByNumber(c.Number) != c { t.Errorf("GetChannelByNumber(%d) shouldn't be nil after added to allocation", c.Number) } } func TestChannelBindStart(t *testing.T) { c := newChannelBind(2 * time.Second) time.Sleep(3 * time.Second) if c.allocation.GetChannelByNumber(c.Number) != nil { t.Errorf("GetChannelByNumber(%d) should be nil if timeout", c.Number) } } func TestChannelBindReset(t *testing.T) { c := newChannelBind(3 * time.Second) time.Sleep(2 * time.Second) c.refresh(3 * time.Second) time.Sleep(2 * time.Second) if c.allocation.GetChannelByNumber(c.Number) == nil { t.Errorf("GetChannelByNumber(%d) shouldn't be nil after refresh", c.Number) } } func newChannelBind(lifetime time.Duration) *ChannelBind { a := NewAllocation(nil, nil, nil) addr, _ := net.ResolveUDPAddr("udp", "0.0.0.0:0") c := &ChannelBind{ Number: proto.MinChannelNumber, Peer: addr, } _ = a.AddChannelBind(c, lifetime) return c } turn-2.1.0/internal/allocation/errors.go000066400000000000000000000023461436753726200203320ustar00rootroot00000000000000package allocation import "errors" var ( errAllocatePacketConnMustBeSet = errors.New("AllocatePacketConn must be set") errAllocateConnMustBeSet = errors.New("AllocateConn must be set") errLeveledLoggerMustBeSet = errors.New("LeveledLogger must be set") errSameChannelDifferentPeer = errors.New("you cannot use the same channel number with different peer") errNilFiveTuple = errors.New("allocations must not be created with nil FivTuple") errNilFiveTupleSrcAddr = errors.New("allocations must not be created with nil FiveTuple.SrcAddr") errNilFiveTupleDstAddr = errors.New("allocations must not be created with nil FiveTuple.DstAddr") errNilTurnSocket = errors.New("allocations must not be created with nil turnSocket") errLifetimeZero = errors.New("allocations must not be created with a lifetime of 0") errDupeFiveTuple = errors.New("allocation attempt created with duplicate FiveTuple") errFailedToCastUDPAddr = errors.New("failed to cast net.Addr to *net.UDPAddr") errFailedToAllocateEvenPort = errors.New("failed to allocate an even port") errAdminProhibited = errors.New("permission request administratively prohibited") ) turn-2.1.0/internal/allocation/five_tuple.go000066400000000000000000000016051436753726200211550ustar00rootroot00000000000000package allocation import ( "fmt" "net" ) // Protocol is an enum for relay protocol type Protocol uint8 // Network protocols for relay const ( UDP Protocol = iota TCP ) // FiveTuple is the combination (client IP address and port, server IP // address and port, and transport protocol (currently one of UDP, // TCP, or TLS)) used to communicate between the client and the // server. The 5-tuple uniquely identifies this communication // stream. The 5-tuple also uniquely identifies the Allocation on // the server. type FiveTuple struct { Protocol SrcAddr, DstAddr net.Addr } // Equal asserts if two FiveTuples are equal func (f *FiveTuple) Equal(b *FiveTuple) bool { return f.Fingerprint() == b.Fingerprint() } // Fingerprint is the identity of a FiveTuple func (f *FiveTuple) Fingerprint() string { return fmt.Sprintf("%d_%s_%s", f.Protocol, f.SrcAddr.String(), f.DstAddr.String()) } turn-2.1.0/internal/allocation/five_tuple_test.go000066400000000000000000000026461436753726200222220ustar00rootroot00000000000000package allocation import ( "net" "testing" ) func TestFiveTupleProtocol(t *testing.T) { udpExpect := Protocol(0) tcpExpect := Protocol(1) if udpExpect != UDP { t.Errorf("Invalid UDP Protocol value, expect %d but %d", udpExpect, UDP) } if tcpExpect != TCP { t.Errorf("Invalid TCP Protocol value, expect %d but %d", tcpExpect, TCP) } } func TestFiveTupleEqual(t *testing.T) { srcAddr1, _ := net.ResolveUDPAddr("udp", "0.0.0.0:3478") srcAddr2, _ := net.ResolveUDPAddr("udp", "0.0.0.0:3479") dstAddr1, _ := net.ResolveUDPAddr("udp", "0.0.0.0:3480") dstAddr2, _ := net.ResolveUDPAddr("udp", "0.0.0.0:3481") tt := []struct { name string expect bool a *FiveTuple b *FiveTuple }{ { "Equal", true, &FiveTuple{UDP, srcAddr1, dstAddr1}, &FiveTuple{UDP, srcAddr1, dstAddr1}, }, { "DifferentProtocol", false, &FiveTuple{UDP, srcAddr1, dstAddr1}, &FiveTuple{TCP, srcAddr1, dstAddr1}, }, { "DifferentSrcAddr", false, &FiveTuple{UDP, srcAddr1, dstAddr1}, &FiveTuple{UDP, srcAddr2, dstAddr1}, }, { "DifferentDstAddr", false, &FiveTuple{UDP, srcAddr1, dstAddr1}, &FiveTuple{UDP, srcAddr1, dstAddr2}, }, } for _, tc := range tt { a := tc.a b := tc.b expect := tc.expect t.Run(tc.name, func(t *testing.T) { fact := a.Equal(b) if expect != fact { t.Errorf("%v, %v equal check should be %t, but %t", a, b, expect, fact) } }) } } turn-2.1.0/internal/allocation/permission.go000066400000000000000000000017371436753726200212110ustar00rootroot00000000000000package allocation import ( "net" "time" "github.com/pion/logging" ) const permissionTimeout = time.Duration(5) * time.Minute // Permission represents a TURN permission. TURN permissions mimic the address-restricted // filtering mechanism of NATs that comply with [RFC4787]. // https://tools.ietf.org/html/rfc5766#section-2.3 type Permission struct { Addr net.Addr allocation *Allocation lifetimeTimer *time.Timer log logging.LeveledLogger } // NewPermission create a new Permission func NewPermission(addr net.Addr, log logging.LeveledLogger) *Permission { return &Permission{ Addr: addr, log: log, } } func (p *Permission) start(lifetime time.Duration) { p.lifetimeTimer = time.AfterFunc(lifetime, func() { p.allocation.RemovePermission(p.Addr) }) } func (p *Permission) refresh(lifetime time.Duration) { if !p.lifetimeTimer.Reset(lifetime) { p.log.Errorf("Failed to reset permission timer for %v %v", p.Addr, p.allocation.fiveTuple) } } turn-2.1.0/internal/client/000077500000000000000000000000001436753726200156135ustar00rootroot00000000000000turn-2.1.0/internal/client/binding.go000066400000000000000000000056731436753726200175670ustar00rootroot00000000000000package client import ( "net" "sync" "sync/atomic" "time" ) // Channel number: // // 0x4000 through 0x7FFF: These values are the allowed channel // numbers (16,383 possible values). const ( minChannelNumber uint16 = 0x4000 maxChannelNumber uint16 = 0x7fff ) type bindingState int32 const ( bindingStateIdle bindingState = iota bindingStateRequest bindingStateReady bindingStateRefresh bindingStateFailed ) type binding struct { number uint16 // read-only st bindingState // thread-safe (atomic op) addr net.Addr // read-only mgr *bindingManager // read-only muBind sync.Mutex // thread-safe, for ChannelBind ops _refreshedAt time.Time // protected by mutex mutex sync.RWMutex // thread-safe } func (b *binding) setState(state bindingState) { atomic.StoreInt32((*int32)(&b.st), int32(state)) } func (b *binding) state() bindingState { return bindingState(atomic.LoadInt32((*int32)(&b.st))) } func (b *binding) setRefreshedAt(at time.Time) { b.mutex.Lock() defer b.mutex.Unlock() b._refreshedAt = at } func (b *binding) refreshedAt() time.Time { b.mutex.RLock() defer b.mutex.RUnlock() return b._refreshedAt } // Thread-safe binding map type bindingManager struct { chanMap map[uint16]*binding addrMap map[string]*binding next uint16 mutex sync.RWMutex } func newBindingManager() *bindingManager { return &bindingManager{ chanMap: map[uint16]*binding{}, addrMap: map[string]*binding{}, next: minChannelNumber, } } func (mgr *bindingManager) assignChannelNumber() uint16 { n := mgr.next if mgr.next == maxChannelNumber { mgr.next = minChannelNumber } else { mgr.next++ } return n } func (mgr *bindingManager) create(addr net.Addr) *binding { mgr.mutex.Lock() defer mgr.mutex.Unlock() b := &binding{ number: mgr.assignChannelNumber(), addr: addr, mgr: mgr, _refreshedAt: time.Now(), } mgr.chanMap[b.number] = b mgr.addrMap[b.addr.String()] = b return b } func (mgr *bindingManager) findByAddr(addr net.Addr) (*binding, bool) { mgr.mutex.RLock() defer mgr.mutex.RUnlock() b, ok := mgr.addrMap[addr.String()] return b, ok } func (mgr *bindingManager) findByNumber(number uint16) (*binding, bool) { mgr.mutex.RLock() defer mgr.mutex.RUnlock() b, ok := mgr.chanMap[number] return b, ok } func (mgr *bindingManager) deleteByAddr(addr net.Addr) bool { mgr.mutex.Lock() defer mgr.mutex.Unlock() b, ok := mgr.addrMap[addr.String()] if !ok { return false } delete(mgr.addrMap, addr.String()) delete(mgr.chanMap, b.number) return true } func (mgr *bindingManager) deleteByNumber(number uint16) bool { mgr.mutex.Lock() defer mgr.mutex.Unlock() b, ok := mgr.chanMap[number] if !ok { return false } delete(mgr.addrMap, b.addr.String()) delete(mgr.chanMap, number) return true } func (mgr *bindingManager) size() int { mgr.mutex.RLock() defer mgr.mutex.RUnlock() return len(mgr.chanMap) } turn-2.1.0/internal/client/binding_test.go000066400000000000000000000036741436753726200206250ustar00rootroot00000000000000package client import ( "net" "testing" "github.com/stretchr/testify/assert" ) func TestBindingManager(t *testing.T) { t.Run("number assignment", func(t *testing.T) { m := newBindingManager() var n uint16 for i := uint16(0); i < 10; i++ { n = m.assignChannelNumber() assert.Equal(t, minChannelNumber+i, n, "should match") } m.next = uint16(0x7ff0) for i := uint16(0); i < 16; i++ { n = m.assignChannelNumber() assert.Equal(t, 0x7ff0+i, n, "should match") } // back to min n = m.assignChannelNumber() assert.Equal(t, minChannelNumber, n, "should match") }) t.Run("method test", func(t *testing.T) { lo := net.IPv4(127, 0, 0, 1) count := 100 m := newBindingManager() for i := 0; i < count; i++ { addr := &net.UDPAddr{IP: lo, Port: 10000 + i} b0 := m.create(addr) b1, ok := m.findByAddr(addr) assert.True(t, ok, "should succeed") b2, ok := m.findByNumber(b0.number) assert.True(t, ok, "should succeed") assert.Equal(t, b0, b1, "should match") assert.Equal(t, b0, b2, "should match") } assert.Equal(t, count, m.size(), "should match") assert.Equal(t, count, len(m.addrMap), "should match") for i := 0; i < count; i++ { addr := &net.UDPAddr{IP: lo, Port: 10000 + i} if i%2 == 0 { assert.True(t, m.deleteByAddr(addr), "should return true") } else { assert.True(t, m.deleteByNumber(minChannelNumber+uint16(i)), "should return true") } } assert.Equal(t, 0, m.size(), "should match") assert.Equal(t, 0, len(m.addrMap), "should match") }) t.Run("failure test", func(t *testing.T) { addr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 7777} m := newBindingManager() var ok bool _, ok = m.findByAddr(addr) assert.False(t, ok, "should fail") _, ok = m.findByNumber(uint16(5555)) assert.False(t, ok, "should fail") ok = m.deleteByAddr(addr) assert.False(t, ok, "should fail") ok = m.deleteByNumber(uint16(5555)) assert.False(t, ok, "should fail") }) } turn-2.1.0/internal/client/conn.go000066400000000000000000000373121436753726200171050ustar00rootroot00000000000000// Package client implements the API for a TURN client package client import ( "errors" "fmt" "io" "math" "net" "sync" "time" "github.com/pion/logging" "github.com/pion/stun" "github.com/pion/turn/v2/internal/proto" ) const ( maxReadQueueSize = 1024 permRefreshInterval = 120 * time.Second maxRetryAttempts = 3 ) const ( timerIDRefreshAlloc int = iota timerIDRefreshPerms ) func noDeadline() time.Time { return time.Time{} } type inboundData struct { data []byte from net.Addr } // UDPConnObserver is an interface to UDPConn observer type UDPConnObserver interface { TURNServerAddr() net.Addr Username() stun.Username Realm() stun.Realm WriteTo(data []byte, to net.Addr) (int, error) PerformTransaction(msg *stun.Message, to net.Addr, dontWait bool) (TransactionResult, error) OnDeallocated(relayedAddr net.Addr) } // UDPConnConfig is a set of configuration params use by NewUDPConn type UDPConnConfig struct { Observer UDPConnObserver RelayedAddr net.Addr Integrity stun.MessageIntegrity Nonce stun.Nonce Lifetime time.Duration Log logging.LeveledLogger } // UDPConn is the implementation of the Conn and PacketConn interfaces for UDP network connections. // comatible with net.PacketConn and net.Conn type UDPConn struct { obs UDPConnObserver // read-only relayedAddr net.Addr // read-only permMap *permissionMap // thread-safe bindingMgr *bindingManager // thread-safe integrity stun.MessageIntegrity // read-only _nonce stun.Nonce // needs mutex x _lifetime time.Duration // needs mutex x readCh chan *inboundData // thread-safe closeCh chan struct{} // thread-safe readTimer *time.Timer // thread-safe refreshAllocTimer *PeriodicTimer // thread-safe refreshPermsTimer *PeriodicTimer // thread-safe mutex sync.RWMutex // thread-safe log logging.LeveledLogger // read-only } // NewUDPConn creates a new instance of UDPConn func NewUDPConn(config *UDPConnConfig) *UDPConn { c := &UDPConn{ obs: config.Observer, relayedAddr: config.RelayedAddr, permMap: newPermissionMap(), bindingMgr: newBindingManager(), integrity: config.Integrity, _nonce: config.Nonce, _lifetime: config.Lifetime, readCh: make(chan *inboundData, maxReadQueueSize), closeCh: make(chan struct{}), readTimer: time.NewTimer(time.Duration(math.MaxInt64)), log: config.Log, } c.log.Debugf("initial lifetime: %d seconds", int(c.lifetime().Seconds())) c.refreshAllocTimer = NewPeriodicTimer( timerIDRefreshAlloc, c.onRefreshTimers, c.lifetime()/2, ) c.refreshPermsTimer = NewPeriodicTimer( timerIDRefreshPerms, c.onRefreshTimers, permRefreshInterval, ) if c.refreshAllocTimer.Start() { c.log.Debugf("refreshAllocTimer started") } if c.refreshPermsTimer.Start() { c.log.Debugf("refreshPermsTimer started") } return c } // ReadFrom reads a packet from the connection, // copying the payload into p. It returns the number of // bytes copied into p and the return address that // was on the packet. // It returns the number of bytes read (0 <= n <= len(p)) // and any error encountered. Callers should always process // the n > 0 bytes returned before considering the error err. // ReadFrom can be made to time out and return // an Error with Timeout() == true after a fixed time limit; // see SetDeadline and SetReadDeadline. func (c *UDPConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { for { select { case ibData := <-c.readCh: n := copy(p, ibData.data) if n < len(ibData.data) { return 0, nil, io.ErrShortBuffer } return n, ibData.from, nil case <-c.readTimer.C: return 0, nil, &net.OpError{ Op: "read", Net: c.LocalAddr().Network(), Addr: c.LocalAddr(), Err: newTimeoutError("i/o timeout"), } case <-c.closeCh: return 0, nil, &net.OpError{ Op: "read", Net: c.LocalAddr().Network(), Addr: c.LocalAddr(), Err: errClosed, } } } } func (c *UDPConn) createPermission(perm *permission, addr net.Addr) error { perm.mutex.Lock() defer perm.mutex.Unlock() if perm.state() == permStateIdle { // punch a hole! (this would block a bit..) if err := c.CreatePermissions(addr); err != nil { c.permMap.delete(addr) return err } perm.setState(permStatePermitted) } return nil } // WriteTo writes a packet with payload p to addr. // WriteTo can be made to time out and return // an Error with Timeout() == true after a fixed time limit; // see SetDeadline and SetWriteDeadline. // On packet-oriented connections, write timeouts are rare. func (c *UDPConn) WriteTo(p []byte, addr net.Addr) (int, error) { //nolint: gocognit var err error _, ok := addr.(*net.UDPAddr) if !ok { return 0, errUDPAddrCast } // check if we have a permission for the destination IP addr perm, ok := c.permMap.find(addr) if !ok { perm = &permission{} c.permMap.insert(addr, perm) } for i := 0; i < maxRetryAttempts; i++ { // c.createPermission() would block, per destination IP (, or perm), // until the perm state becomes "requested". Purpose of this is to // guarantee the order of packets (within the same perm). // Note that CreatePermission transaction may not be complete before // all the data transmission. This is done assuming that the request // will be most likely successful and we can tolerate some loss of // UDP packet (or reorder), inorder to minimize the latency in most cases. if err = c.createPermission(perm, addr); !errors.Is(err, errTryAgain) { break } } if err != nil { return 0, err } // bind channel b, ok := c.bindingMgr.findByAddr(addr) if !ok { b = c.bindingMgr.create(addr) } bindSt := b.state() if bindSt == bindingStateIdle || bindSt == bindingStateRequest || bindSt == bindingStateFailed { func() { // block only callers with the same binding until // the binding transaction has been complete b.muBind.Lock() defer b.muBind.Unlock() // binding state may have been changed while waiting. check again. if b.state() == bindingStateIdle { b.setState(bindingStateRequest) go func() { err2 := c.bind(b) if err2 != nil { c.log.Warnf("bind() failed: %s", err2.Error()) b.setState(bindingStateFailed) // keep going... } else { b.setState(bindingStateReady) } }() } }() // send data using SendIndication peerAddr := addr2PeerAddress(addr) var msg *stun.Message msg, err = stun.Build( stun.TransactionID, stun.NewType(stun.MethodSend, stun.ClassIndication), proto.Data(p), peerAddr, stun.Fingerprint, ) if err != nil { return 0, err } // indication has no transaction (fire-and-forget) return c.obs.WriteTo(msg.Raw, c.obs.TURNServerAddr()) } // binding is either ready // check if the binding needs a refresh func() { b.muBind.Lock() defer b.muBind.Unlock() if b.state() == bindingStateReady && time.Since(b.refreshedAt()) > 5*time.Minute { b.setState(bindingStateRefresh) go func() { err = c.bind(b) if err != nil { c.log.Warnf("bind() for refresh failed: %s", err.Error()) b.setState(bindingStateFailed) // keep going... } else { b.setRefreshedAt(time.Now()) b.setState(bindingStateReady) } }() } }() // send via ChannelData _, err = c.sendChannelData(p, b.number) if err != nil { return 0, err } return len(p), nil } // Close closes the connection. // Any blocked ReadFrom or WriteTo operations will be unblocked and return errors. func (c *UDPConn) Close() error { c.refreshAllocTimer.Stop() c.refreshPermsTimer.Stop() select { case <-c.closeCh: return errAlreadyClosed default: close(c.closeCh) } c.obs.OnDeallocated(c.relayedAddr) return c.refreshAllocation(0, true /* dontWait=true */) } // LocalAddr returns the local network address. func (c *UDPConn) LocalAddr() net.Addr { return c.relayedAddr } // SetDeadline sets the read and write deadlines associated // with the connection. It is equivalent to calling both // SetReadDeadline and SetWriteDeadline. // // A deadline is an absolute time after which I/O operations // fail with a timeout (see type Error) instead of // blocking. The deadline applies to all future and pending // I/O, not just the immediately following call to ReadFrom or // WriteTo. After a deadline has been exceeded, the connection // can be refreshed by setting a deadline in the future. // // An idle timeout can be implemented by repeatedly extending // the deadline after successful ReadFrom or WriteTo calls. // // A zero value for t means I/O operations will not time out. func (c *UDPConn) SetDeadline(t time.Time) error { return c.SetReadDeadline(t) } // SetReadDeadline sets the deadline for future ReadFrom calls // and any currently-blocked ReadFrom call. // A zero value for t means ReadFrom will not time out. func (c *UDPConn) SetReadDeadline(t time.Time) error { var d time.Duration if t == noDeadline() { d = time.Duration(math.MaxInt64) } else { d = time.Until(t) } c.readTimer.Reset(d) return nil } // SetWriteDeadline sets the deadline for future WriteTo calls // and any currently-blocked WriteTo call. // Even if write times out, it may return n > 0, indicating that // some of the data was successfully written. // A zero value for t means WriteTo will not time out. func (c *UDPConn) SetWriteDeadline(t time.Time) error { // Write never blocks. return nil } func addr2PeerAddress(addr net.Addr) proto.PeerAddress { var peerAddr proto.PeerAddress switch a := addr.(type) { case *net.UDPAddr: peerAddr.IP = a.IP peerAddr.Port = a.Port case *net.TCPAddr: peerAddr.IP = a.IP peerAddr.Port = a.Port } return peerAddr } // CreatePermissions Issues a CreatePermission request for the supplied addresses // as described in https://datatracker.ietf.org/doc/html/rfc5766#section-9 func (c *UDPConn) CreatePermissions(addrs ...net.Addr) error { setters := []stun.Setter{ stun.TransactionID, stun.NewType(stun.MethodCreatePermission, stun.ClassRequest), } for _, addr := range addrs { setters = append(setters, addr2PeerAddress(addr)) } setters = append(setters, c.obs.Username(), c.obs.Realm(), c.nonce(), c.integrity, stun.Fingerprint) msg, err := stun.Build(setters...) if err != nil { return err } trRes, err := c.obs.PerformTransaction(msg, c.obs.TURNServerAddr(), false) if err != nil { return err } res := trRes.Msg if res.Type.Class == stun.ClassErrorResponse { var code stun.ErrorCodeAttribute if err = code.GetFrom(res); err == nil { if code.Code == stun.CodeStaleNonce { c.setNonceFromMsg(res) return errTryAgain } return fmt.Errorf("%s (error %s)", res.Type, code) //nolint:goerr113 } return fmt.Errorf("%s", res.Type) //nolint:goerr113 } return nil } // HandleInbound passes inbound data in UDPConn func (c *UDPConn) HandleInbound(data []byte, from net.Addr) { // copy data copied := make([]byte, len(data)) copy(copied, data) select { case c.readCh <- &inboundData{data: copied, from: from}: default: c.log.Warnf("receive buffer full") } } // FindAddrByChannelNumber returns a peer address associated with the // channel number on this UDPConn func (c *UDPConn) FindAddrByChannelNumber(chNum uint16) (net.Addr, bool) { b, ok := c.bindingMgr.findByNumber(chNum) if !ok { return nil, false } return b.addr, true } func (c *UDPConn) setNonceFromMsg(msg *stun.Message) { // Update nonce var nonce stun.Nonce if err := nonce.GetFrom(msg); err == nil { c.setNonce(nonce) c.log.Debug("refresh allocation: 438, got new nonce.") } else { c.log.Warn("refresh allocation: 438 but no nonce.") } } func (c *UDPConn) refreshAllocation(lifetime time.Duration, dontWait bool) error { msg, err := stun.Build( stun.TransactionID, stun.NewType(stun.MethodRefresh, stun.ClassRequest), proto.Lifetime{Duration: lifetime}, c.obs.Username(), c.obs.Realm(), c.nonce(), c.integrity, stun.Fingerprint, ) if err != nil { return fmt.Errorf("%w: %s", errFailedToBuildRefreshRequest, err.Error()) } c.log.Debugf("send refresh request (dontWait=%v)", dontWait) trRes, err := c.obs.PerformTransaction(msg, c.obs.TURNServerAddr(), dontWait) if err != nil { return fmt.Errorf("%w: %s", errFailedToRefreshAllocation, err.Error()) } if dontWait { c.log.Debug("refresh request sent") return nil } c.log.Debug("refresh request sent, and waiting response") res := trRes.Msg if res.Type.Class == stun.ClassErrorResponse { var code stun.ErrorCodeAttribute if err = code.GetFrom(res); err == nil { if code.Code == stun.CodeStaleNonce { c.setNonceFromMsg(res) return errTryAgain } return err } return fmt.Errorf("%s", res.Type) //nolint:goerr113 } // Getting lifetime from response var updatedLifetime proto.Lifetime if err := updatedLifetime.GetFrom(res); err != nil { return fmt.Errorf("%w: %s", errFailedToGetLifetime, err.Error()) } c.setLifetime(updatedLifetime.Duration) c.log.Debugf("updated lifetime: %d seconds", int(c.lifetime().Seconds())) return nil } func (c *UDPConn) refreshPermissions() error { addrs := c.permMap.addrs() if len(addrs) == 0 { c.log.Debug("no permission to refresh") return nil } if err := c.CreatePermissions(addrs...); err != nil { if errors.Is(err, errTryAgain) { return errTryAgain } c.log.Errorf("fail to refresh permissions: %s", err.Error()) return err } c.log.Debug("refresh permissions successful") return nil } func (c *UDPConn) bind(b *binding) error { setters := []stun.Setter{ stun.TransactionID, stun.NewType(stun.MethodChannelBind, stun.ClassRequest), addr2PeerAddress(b.addr), proto.ChannelNumber(b.number), c.obs.Username(), c.obs.Realm(), c.nonce(), c.integrity, stun.Fingerprint, } msg, err := stun.Build(setters...) if err != nil { return err } trRes, err := c.obs.PerformTransaction(msg, c.obs.TURNServerAddr(), false) if err != nil { c.bindingMgr.deleteByAddr(b.addr) return err } res := trRes.Msg if res.Type != stun.NewType(stun.MethodChannelBind, stun.ClassSuccessResponse) { return fmt.Errorf("unexpected response type %s", res.Type) //nolint:goerr113 } c.log.Debugf("channel binding successful: %s %d", b.addr.String(), b.number) // Success. return nil } func (c *UDPConn) sendChannelData(data []byte, chNum uint16) (int, error) { chData := &proto.ChannelData{ Data: data, Number: proto.ChannelNumber(chNum), } chData.Encode() _, err := c.obs.WriteTo(chData.Raw, c.obs.TURNServerAddr()) if err != nil { return 0, err } return len(data), nil } func (c *UDPConn) onRefreshTimers(id int) { c.log.Debugf("refresh timer %d expired", id) switch id { case timerIDRefreshAlloc: var err error lifetime := c.lifetime() // limit the max retries on errTryAgain to 3 // when stale nonce returns, sencond retry should succeed for i := 0; i < maxRetryAttempts; i++ { err = c.refreshAllocation(lifetime, false) if !errors.Is(err, errTryAgain) { break } } if err != nil { c.log.Warnf("refresh allocation failed") } case timerIDRefreshPerms: var err error for i := 0; i < maxRetryAttempts; i++ { err = c.refreshPermissions() if !errors.Is(err, errTryAgain) { break } } if err != nil { c.log.Warnf("refresh permissions failed") } } } func (c *UDPConn) nonce() stun.Nonce { c.mutex.RLock() defer c.mutex.RUnlock() return c._nonce } func (c *UDPConn) setNonce(nonce stun.Nonce) { c.mutex.Lock() defer c.mutex.Unlock() c.log.Debugf("set new nonce with %d bytes", len(nonce)) c._nonce = nonce } func (c *UDPConn) lifetime() time.Duration { c.mutex.RLock() defer c.mutex.RUnlock() return c._lifetime } func (c *UDPConn) setLifetime(lifetime time.Duration) { c.mutex.Lock() defer c.mutex.Unlock() c._lifetime = lifetime } turn-2.1.0/internal/client/conn_test.go000066400000000000000000000052111436753726200201350ustar00rootroot00000000000000package client import ( "net" "testing" "github.com/pion/stun" "github.com/stretchr/testify/assert" ) type dummyUDPConnObserver struct { turnServerAddr net.Addr username stun.Username realm stun.Realm _writeTo func(data []byte, to net.Addr) (int, error) _performTransaction func(msg *stun.Message, to net.Addr, dontWait bool) (TransactionResult, error) _onDeallocated func(relayedAddr net.Addr) } func (obs *dummyUDPConnObserver) TURNServerAddr() net.Addr { return obs.turnServerAddr } func (obs *dummyUDPConnObserver) Username() stun.Username { return obs.username } func (obs *dummyUDPConnObserver) Realm() stun.Realm { return obs.realm } func (obs *dummyUDPConnObserver) WriteTo(data []byte, to net.Addr) (int, error) { if obs._writeTo != nil { return obs._writeTo(data, to) } return 0, nil } func (obs *dummyUDPConnObserver) PerformTransaction(msg *stun.Message, to net.Addr, dontWait bool) (TransactionResult, error) { if obs._performTransaction != nil { return obs._performTransaction(msg, to, dontWait) } return TransactionResult{}, nil } func (obs *dummyUDPConnObserver) OnDeallocated(relayedAddr net.Addr) { if obs._onDeallocated != nil { obs._onDeallocated(relayedAddr) } } func TestUDPConn(t *testing.T) { t.Run("bind()", func(t *testing.T) { obs := &dummyUDPConnObserver{ _performTransaction: func(msg *stun.Message, to net.Addr, dontWait bool) (TransactionResult, error) { return TransactionResult{}, errFake }, } bm := newBindingManager() b := bm.create(&net.UDPAddr{ IP: net.ParseIP("127.0.0.1"), Port: 1234, }) conn := UDPConn{ obs: obs, bindingMgr: bm, } err := conn.bind(b) assert.Error(t, err, "should fail") assert.Equal(t, 0, len(bm.chanMap), "should be 0") assert.Equal(t, 0, len(bm.addrMap), "should be 0") }) t.Run("WriteTo()", func(t *testing.T) { obs := &dummyUDPConnObserver{ _performTransaction: func(msg *stun.Message, to net.Addr, dontWait bool) (TransactionResult, error) { return TransactionResult{}, errFake }, _writeTo: func(data []byte, to net.Addr) (int, error) { return len(data), nil }, } addr := &net.UDPAddr{ IP: net.ParseIP("127.0.0.1"), Port: 1234, } pm := newPermissionMap() assert.True(t, pm.insert(addr, &permission{ st: permStatePermitted, })) bm := newBindingManager() binding := bm.create(addr) binding.setState(bindingStateReady) conn := UDPConn{ obs: obs, permMap: pm, bindingMgr: bm, } buf := []byte("Hello") n, err := conn.WriteTo(buf, addr) assert.NoError(t, err, "should fail") assert.Equal(t, len(buf), n) }) } turn-2.1.0/internal/client/errors.go000066400000000000000000000022511436753726200174560ustar00rootroot00000000000000package client import ( "errors" ) var ( errFake = errors.New("fake error") errTryAgain = errors.New("try again") errClosed = errors.New("use of closed network connection") errUDPAddrCast = errors.New("addr is not a net.UDPAddr") errAlreadyClosed = errors.New("already closed") errDoubleLock = errors.New("try-lock is already locked") errTransactionClosed = errors.New("transaction closed") errWaitForResultOnNonResultTransaction = errors.New("WaitForResult called on non-result transaction") errFailedToBuildRefreshRequest = errors.New("failed to build refresh request") errFailedToRefreshAllocation = errors.New("failed to refresh allocation") errFailedToGetLifetime = errors.New("failed to get lifetime from refresh response") ) type timeoutError struct { msg string } func newTimeoutError(msg string) error { return &timeoutError{ msg: msg, } } func (e *timeoutError) Error() string { return e.msg } func (e *timeoutError) Timeout() bool { return true } turn-2.1.0/internal/client/periodic_timer.go000066400000000000000000000027401436753726200211430ustar00rootroot00000000000000package client import ( "sync" "time" ) // PeriodicTimerTimeoutHandler is a handler called on timeout type PeriodicTimerTimeoutHandler func(timerID int) // PeriodicTimer is a periodic timer type PeriodicTimer struct { id int interval time.Duration timeoutHandler PeriodicTimerTimeoutHandler stopFunc func() mutex sync.RWMutex } // NewPeriodicTimer create a new timer func NewPeriodicTimer(id int, timeoutHandler PeriodicTimerTimeoutHandler, interval time.Duration) *PeriodicTimer { return &PeriodicTimer{ id: id, interval: interval, timeoutHandler: timeoutHandler, } } // Start starts the timer. func (t *PeriodicTimer) Start() bool { t.mutex.Lock() defer t.mutex.Unlock() // this is a noop if the timer is always running if t.stopFunc != nil { return false } cancelCh := make(chan struct{}) go func() { canceling := false for !canceling { timer := time.NewTimer(t.interval) select { case <-timer.C: t.timeoutHandler(t.id) case <-cancelCh: canceling = true timer.Stop() } } }() t.stopFunc = func() { close(cancelCh) } return true } // Stop stops the timer. func (t *PeriodicTimer) Stop() { t.mutex.Lock() defer t.mutex.Unlock() if t.stopFunc != nil { t.stopFunc() t.stopFunc = nil } } // IsRunning tests if the timer is running. // Debug purpose only func (t *PeriodicTimer) IsRunning() bool { t.mutex.RLock() defer t.mutex.RUnlock() return (t.stopFunc != nil) } turn-2.1.0/internal/client/periodic_timer_test.go000066400000000000000000000025021436753726200221760ustar00rootroot00000000000000package client import ( "sync/atomic" "testing" "time" "github.com/stretchr/testify/assert" ) func TestPeriodicTimer(t *testing.T) { t.Run("basic", func(t *testing.T) { timerID := 3 var nCbs uint64 rt := NewPeriodicTimer(timerID, func(id int) { atomic.AddUint64(&nCbs, 1) assert.Equal(t, timerID, id) }, 50*time.Millisecond) assert.False(t, rt.IsRunning(), "should not be running yet") ok := rt.Start() assert.True(t, ok, "should be true") assert.True(t, rt.IsRunning(), "should be running") time.Sleep(100 * time.Millisecond) ok = rt.Start() assert.False(t, ok, "start again is noop") time.Sleep(120 * time.Millisecond) rt.Stop() assert.False(t, rt.IsRunning(), "should not be running") assert.Equal(t, 4, int(atomic.LoadUint64(&nCbs)), "should be called 4 times (actual: %d)", atomic.LoadUint64(&nCbs)) }) t.Run("stop inside handler", func(t *testing.T) { timerID := 4 var rt *PeriodicTimer rt = NewPeriodicTimer(timerID, func(id int) { assert.Equal(t, timerID, id) rt.Stop() }, 20*time.Millisecond) assert.False(t, rt.IsRunning(), "should not be running yet") ok := rt.Start() assert.True(t, ok, "should be true") assert.True(t, rt.IsRunning(), "should be running") time.Sleep(30 * time.Millisecond) assert.False(t, rt.IsRunning(), "should not be running") }) } turn-2.1.0/internal/client/permission.go000066400000000000000000000030211436753726200203260ustar00rootroot00000000000000package client import ( "net" "sync" "sync/atomic" ) type permState int32 const ( permStateIdle permState = iota permStatePermitted ) type permission struct { st permState // thread-safe (atomic op) mutex sync.RWMutex // thread-safe } func (p *permission) setState(state permState) { atomic.StoreInt32((*int32)(&p.st), int32(state)) } func (p *permission) state() permState { return permState(atomic.LoadInt32((*int32)(&p.st))) } // Thread-safe permission map type permissionMap struct { permMap map[string]*permission mutex sync.RWMutex } func (m *permissionMap) insert(addr net.Addr, p *permission) bool { m.mutex.Lock() defer m.mutex.Unlock() udpAddr, ok := addr.(*net.UDPAddr) if !ok { return false } m.permMap[udpAddr.IP.String()] = p return true } func (m *permissionMap) find(addr net.Addr) (*permission, bool) { m.mutex.RLock() defer m.mutex.RUnlock() udpAddr, ok := addr.(*net.UDPAddr) if !ok { return nil, false } p, ok := m.permMap[udpAddr.IP.String()] return p, ok } func (m *permissionMap) delete(addr net.Addr) { m.mutex.Lock() defer m.mutex.Unlock() udpAddr, ok := addr.(*net.UDPAddr) if !ok { return } delete(m.permMap, udpAddr.IP.String()) } func (m *permissionMap) addrs() []net.Addr { m.mutex.RLock() defer m.mutex.RUnlock() addrs := []net.Addr{} for k := range m.permMap { addrs = append(addrs, &net.UDPAddr{ IP: net.ParseIP(k), }) } return addrs } func newPermissionMap() *permissionMap { return &permissionMap{ permMap: map[string]*permission{}, } } turn-2.1.0/internal/client/permission_test.go000066400000000000000000000036411436753726200213750ustar00rootroot00000000000000package client import ( "net" "testing" "github.com/stretchr/testify/assert" ) func TestPermission(t *testing.T) { t.Run("Getter and setter", func(t *testing.T) { perm := &permission{} assert.Equal(t, permStateIdle, perm.state()) perm.setState(permStatePermitted) assert.Equal(t, permStatePermitted, perm.state()) }) } func TestPermissionMap(t *testing.T) { t.Run("Basic operations", func(t *testing.T) { pm := newPermissionMap() assert.NotNil(t, pm) assert.NotNil(t, pm.permMap) perm1 := &permission{st: permStateIdle} perm2 := &permission{st: permStatePermitted} udpAddr1, _ := net.ResolveUDPAddr("udp", "1.2.3.4:5000") udpAddr2, _ := net.ResolveUDPAddr("udp", "5.6.7.8:8888") tcpAddr, _ := net.ResolveTCPAddr("tcp", "1.2.3.4:5000") assert.True(t, pm.insert(udpAddr1, perm1)) assert.Equal(t, 1, len(pm.permMap)) assert.True(t, pm.insert(udpAddr2, perm2)) assert.Equal(t, 2, len(pm.permMap)) assert.False(t, pm.insert(tcpAddr, perm1)) assert.Equal(t, 2, len(pm.permMap)) p, ok := pm.find(udpAddr1) assert.True(t, ok) assert.Equal(t, perm1, p) assert.Equal(t, permStateIdle, p.st) p, ok = pm.find(udpAddr2) assert.True(t, ok) assert.Equal(t, perm2, p) assert.Equal(t, permStatePermitted, p.st) _, ok = pm.find(tcpAddr) assert.False(t, ok) addrs := pm.addrs() ips := []net.IP{} for _, addr := range addrs { udpAddr, err := net.ResolveUDPAddr(addr.Network(), addr.String()) assert.NoError(t, err) assert.Equal(t, 0, udpAddr.Port) ips = append(ips, udpAddr.IP) } assert.Equal(t, 2, len(ips)) if ips[0].Equal(udpAddr1.IP) { assert.True(t, ips[1].Equal(udpAddr2.IP)) } else { assert.True(t, ips[0].Equal(udpAddr2.IP)) assert.True(t, ips[1].Equal(udpAddr1.IP)) } pm.delete(tcpAddr) assert.Equal(t, 2, len(pm.permMap)) pm.delete(udpAddr1) assert.Equal(t, 1, len(pm.permMap)) pm.delete(udpAddr2) assert.Equal(t, 0, len(pm.permMap)) }) } turn-2.1.0/internal/client/transaction.go000066400000000000000000000101351436753726200204670ustar00rootroot00000000000000package client import ( "net" "sync" "time" "github.com/pion/stun" ) const ( maxRtxInterval time.Duration = 1600 * time.Millisecond ) // TransactionResult is a bag of result values of a transaction type TransactionResult struct { Msg *stun.Message From net.Addr Retries int Err error } // TransactionConfig is a set of config params used by NewTransaction type TransactionConfig struct { Key string Raw []byte To net.Addr Interval time.Duration IgnoreResult bool // true to throw away the result of this transaction (it will not be readable using WaitForResult) } // Transaction represents a transaction type Transaction struct { Key string // read-only Raw []byte // read-only To net.Addr // read-only nRtx int // modified only by the timer thread interval time.Duration // modified only by the timer thread timer *time.Timer // thread-safe, set only by the creator, and stopper resultCh chan TransactionResult // thread-safe mutex sync.RWMutex } // NewTransaction creates a new instance of Transaction func NewTransaction(config *TransactionConfig) *Transaction { var resultCh chan TransactionResult if !config.IgnoreResult { resultCh = make(chan TransactionResult) } return &Transaction{ Key: config.Key, // read-only Raw: config.Raw, // read-only To: config.To, // read-only interval: config.Interval, // modified only by the timer thread resultCh: resultCh, // thread-safe } } // StartRtxTimer starts the transaction timer func (t *Transaction) StartRtxTimer(onTimeout func(trKey string, nRtx int)) { t.mutex.Lock() defer t.mutex.Unlock() t.timer = time.AfterFunc(t.interval, func() { t.mutex.Lock() t.nRtx++ nRtx := t.nRtx t.interval *= 2 if t.interval > maxRtxInterval { t.interval = maxRtxInterval } t.mutex.Unlock() onTimeout(t.Key, nRtx) }) } // StopRtxTimer stop the transaction timer func (t *Transaction) StopRtxTimer() { t.mutex.Lock() defer t.mutex.Unlock() if t.timer != nil { t.timer.Stop() } } // WriteResult writes the result to the result channel func (t *Transaction) WriteResult(res TransactionResult) bool { if t.resultCh == nil { return false } t.resultCh <- res return true } // WaitForResult waits for the transaction result func (t *Transaction) WaitForResult() TransactionResult { if t.resultCh == nil { return TransactionResult{ Err: errWaitForResultOnNonResultTransaction, } } result, ok := <-t.resultCh if !ok { result.Err = errTransactionClosed } return result } // Close closes the transaction func (t *Transaction) Close() { if t.resultCh != nil { close(t.resultCh) } } // Retries returns the number of retransmission it has made func (t *Transaction) Retries() int { t.mutex.RLock() defer t.mutex.RUnlock() return t.nRtx } // TransactionMap is a thread-safe transaction map type TransactionMap struct { trMap map[string]*Transaction mutex sync.RWMutex } // NewTransactionMap create a new instance of the transaction map func NewTransactionMap() *TransactionMap { return &TransactionMap{ trMap: map[string]*Transaction{}, } } // Insert inserts a transaction to the map func (m *TransactionMap) Insert(key string, tr *Transaction) bool { m.mutex.Lock() defer m.mutex.Unlock() m.trMap[key] = tr return true } // Find looks up a transaction by its key func (m *TransactionMap) Find(key string) (*Transaction, bool) { m.mutex.RLock() defer m.mutex.RUnlock() tr, ok := m.trMap[key] return tr, ok } // Delete deletes a transaction by its key func (m *TransactionMap) Delete(key string) { m.mutex.Lock() defer m.mutex.Unlock() delete(m.trMap, key) } // CloseAndDeleteAll closes and deletes all transactions func (m *TransactionMap) CloseAndDeleteAll() { m.mutex.Lock() defer m.mutex.Unlock() for trKey, tr := range m.trMap { tr.Close() delete(m.trMap, trKey) } } // Size returns the length of the transaction map func (m *TransactionMap) Size() int { m.mutex.RLock() defer m.mutex.RUnlock() return len(m.trMap) } turn-2.1.0/internal/client/trylock.go000066400000000000000000000007101436753726200176270ustar00rootroot00000000000000package client import ( "sync/atomic" ) // TryLock implement the classic "try-lock" operation. type TryLock struct { n int32 } // Lock tries to lock the try-lock. If successful, it returns true. // Otherwise, it returns false immediately. func (c *TryLock) Lock() error { if !atomic.CompareAndSwapInt32(&c.n, 0, 1) { return errDoubleLock } return nil } // Unlock unlocks the try-lock. func (c *TryLock) Unlock() { atomic.StoreInt32(&c.n, 0) } turn-2.1.0/internal/client/trylock_test.go000066400000000000000000000021041436753726200206650ustar00rootroot00000000000000package client import ( "testing" "time" "github.com/stretchr/testify/assert" ) func TestTryLock(t *testing.T) { t.Run("success case", func(t *testing.T) { cl := &TryLock{} testFunc := func() error { if err := cl.Lock(); err != nil { return err } defer cl.Unlock() return nil } err := testFunc() assert.NoError(t, err, "should succeed") assert.Equal(t, int32(0), cl.n, "should match") }) t.Run("failure case", func(t *testing.T) { cl := &TryLock{} testFunc := func() error { if err := cl.Lock(); err != nil { return err } defer cl.Unlock() time.Sleep(50 * time.Millisecond) return nil } var err1, err2 error doneCh1 := make(chan struct{}) doneCh2 := make(chan struct{}) go func() { err1 = testFunc() close(doneCh1) }() go func() { err2 = testFunc() close(doneCh2) }() <-doneCh1 <-doneCh2 // Either one of them should fail if err1 == nil { assert.Error(t, err2, "should fail") } else { assert.Error(t, err1, "should fail") } assert.Equal(t, int32(0), cl.n, "should match") }) } turn-2.1.0/internal/ipnet/000077500000000000000000000000001436753726200154545ustar00rootroot00000000000000turn-2.1.0/internal/ipnet/util.go000066400000000000000000000015171436753726200167640ustar00rootroot00000000000000// Package ipnet contains helper functions around net and IP package ipnet import ( "errors" "net" ) var errFailedToCastAddr = errors.New("failed to cast net.Addr to *net.UDPAddr or *net.TCPAddr") // AddrIPPort extracts the IP and Port from a net.Addr func AddrIPPort(a net.Addr) (net.IP, int, error) { aUDP, ok := a.(*net.UDPAddr) if ok { return aUDP.IP, aUDP.Port, nil } aTCP, ok := a.(*net.TCPAddr) if ok { return aTCP.IP, aTCP.Port, nil } return nil, 0, errFailedToCastAddr } // AddrEqual asserts that two net.Addrs are equal // Currently only supports UDP but will be extended in the future to support others func AddrEqual(a, b net.Addr) bool { aUDP, ok := a.(*net.UDPAddr) if !ok { return false } bUDP, ok := b.(*net.UDPAddr) if !ok { return false } return aUDP.IP.Equal(bUDP.IP) && aUDP.Port == bUDP.Port } turn-2.1.0/internal/proto/000077500000000000000000000000001436753726200155005ustar00rootroot00000000000000turn-2.1.0/internal/proto/addr.go000066400000000000000000000021071436753726200167410ustar00rootroot00000000000000package proto import ( "fmt" "net" ) // Addr is ip:port. type Addr struct { IP net.IP Port int } // Network implements net.Addr. func (Addr) Network() string { return "turn" } // FromUDPAddr sets addr to UDPAddr. func (a *Addr) FromUDPAddr(n *net.UDPAddr) { a.IP = n.IP a.Port = n.Port } // Equal returns true if b == a. func (a Addr) Equal(b Addr) bool { if a.Port != b.Port { return false } return a.IP.Equal(b.IP) } // EqualIP returns true if a and b have equal IP addresses. func (a Addr) EqualIP(b Addr) bool { return a.IP.Equal(b.IP) } func (a Addr) String() string { return fmt.Sprintf("%s:%d", a.IP, a.Port) } // FiveTuple represents 5-TUPLE value. type FiveTuple struct { Client Addr Server Addr Proto Protocol } func (t FiveTuple) String() string { return fmt.Sprintf("%s->%s (%s)", t.Client, t.Server, t.Proto, ) } // Equal returns true if b == t. func (t FiveTuple) Equal(b FiveTuple) bool { if t.Proto != b.Proto { return false } if !t.Client.Equal(b.Client) { return false } if !t.Server.Equal(b.Server) { return false } return true } turn-2.1.0/internal/proto/addr_test.go000066400000000000000000000027251436753726200200060ustar00rootroot00000000000000package proto import ( "fmt" "net" "testing" ) func TestAddr_FromUDPAddr(t *testing.T) { u := &net.UDPAddr{ IP: net.IPv4(127, 0, 0, 1), Port: 1234, } a := new(Addr) a.FromUDPAddr(u) if !u.IP.Equal(a.IP) || u.Port != a.Port || u.String() != a.String() { t.Error("not equal") } if a.Network() != "turn" { t.Error("unexpected network") } } func TestAddr_EqualIP(t *testing.T) { a := Addr{ IP: net.IPv4(127, 0, 0, 1), Port: 1337, } b := Addr{ IP: net.IPv4(127, 0, 0, 1), Port: 1338, } if a.Equal(b) { t.Error("a != b") } if !a.EqualIP(b) { t.Error("a.IP should equal to b.IP") } } func TestFiveTuple_Equal(t *testing.T) { for _, tc := range []struct { name string a, b FiveTuple v bool }{ { name: "blank", v: true, }, { name: "proto", a: FiveTuple{ Proto: ProtoUDP, }, }, { name: "server", a: FiveTuple{ Server: Addr{ Port: 100, }, }, }, { name: "client", a: FiveTuple{ Client: Addr{ Port: 100, }, }, }, } { if v := tc.a.Equal(tc.b); v != tc.v { t.Errorf("(%s) %s [%v!=%v] %s", tc.name, tc.a, v, tc.v, tc.b, ) } } } func TestFiveTuple_String(t *testing.T) { s := fmt.Sprint(FiveTuple{ Proto: ProtoUDP, Server: Addr{ Port: 100, IP: net.IPv4(127, 0, 0, 1), }, Client: Addr{ Port: 200, IP: net.IPv4(127, 0, 0, 1), }, }) if s != "127.0.0.1:200->127.0.0.1:100 (UDP)" { t.Error("unexpected stringer output") } } turn-2.1.0/internal/proto/chandata.go000066400000000000000000000065641436753726200176050ustar00rootroot00000000000000package proto import ( "bytes" "encoding/binary" "errors" "io" ) // ChannelData represents The ChannelData Message. // // See RFC 5766 Section 11.4 type ChannelData struct { Data []byte // can be sub slice of Raw Length int // ignored while encoding, len(Data) is used Number ChannelNumber Raw []byte } // Equal returns true if b == c. func (c *ChannelData) Equal(b *ChannelData) bool { if c == nil && b == nil { return true } if c == nil || b == nil { return false } if c.Number != b.Number { return false } if len(c.Data) != len(b.Data) { return false } return bytes.Equal(c.Data, b.Data) } // grow ensures that internal buffer will fit v more bytes and // increases it capacity if necessary. // // Similar to stun.Message.grow method. func (c *ChannelData) grow(v int) { n := len(c.Raw) + v for cap(c.Raw) < n { c.Raw = append(c.Raw, 0) } c.Raw = c.Raw[:n] } // Reset resets Length, Data and Raw length. func (c *ChannelData) Reset() { c.Raw = c.Raw[:0] c.Length = 0 c.Data = c.Data[:0] } // Encode encodes ChannelData Message to Raw. func (c *ChannelData) Encode() { c.Raw = c.Raw[:0] c.WriteHeader() c.Raw = append(c.Raw, c.Data...) padded := nearestPaddedValueLength(len(c.Raw)) if bytesToAdd := padded - len(c.Raw); bytesToAdd > 0 { for i := 0; i < bytesToAdd; i++ { c.Raw = append(c.Raw, 0) } } } const padding = 4 func nearestPaddedValueLength(l int) int { n := padding * (l / padding) if n < l { n += padding } return n } // WriteHeader writes channel number and length. func (c *ChannelData) WriteHeader() { if len(c.Raw) < channelDataHeaderSize { // Making WriteHeader call valid even when c.Raw // is nil or len(c.Raw) is less than needed for header. c.grow(channelDataHeaderSize) } // Early bounds check to guarantee safety of writes below. _ = c.Raw[:channelDataHeaderSize] binary.BigEndian.PutUint16(c.Raw[:channelDataNumberSize], uint16(c.Number)) binary.BigEndian.PutUint16(c.Raw[channelDataNumberSize:channelDataHeaderSize], uint16(len(c.Data)), ) } // ErrBadChannelDataLength means that channel data length is not equal // to actual data length. var ErrBadChannelDataLength = errors.New("channelData length != len(Data)") // Decode decodes The ChannelData Message from Raw. func (c *ChannelData) Decode() error { buf := c.Raw if len(buf) < channelDataHeaderSize { return io.ErrUnexpectedEOF } num := binary.BigEndian.Uint16(buf[:channelDataNumberSize]) c.Number = ChannelNumber(num) l := binary.BigEndian.Uint16(buf[channelDataNumberSize:channelDataHeaderSize]) c.Data = buf[channelDataHeaderSize:] c.Length = int(l) if !c.Number.Valid() { return ErrInvalidChannelNumber } if int(l) < len(c.Data) { c.Data = c.Data[:int(l)] } if int(l) > len(buf[channelDataHeaderSize:]) { return ErrBadChannelDataLength } return nil } const ( channelDataLengthSize = 2 channelDataNumberSize = channelDataLengthSize channelDataHeaderSize = channelDataLengthSize + channelDataNumberSize ) // IsChannelData returns true if buf looks like the ChannelData Message. func IsChannelData(buf []byte) bool { if len(buf) < channelDataHeaderSize { return false } if int(binary.BigEndian.Uint16(buf[channelDataNumberSize:channelDataHeaderSize])) > len(buf[channelDataHeaderSize:]) { return false } // Quick check for channel number. num := binary.BigEndian.Uint16(buf[0:channelNumberSize]) return isChannelNumberValid(num) } turn-2.1.0/internal/proto/chandata_test.go000066400000000000000000000114461436753726200206370ustar00rootroot00000000000000package proto import ( "bufio" "bytes" "encoding/hex" "errors" "io" "testing" ) func TestChannelData_Encode(t *testing.T) { d := &ChannelData{ Data: []byte{1, 2, 3, 4}, Number: MinChannelNumber + 1, } d.Encode() b := &ChannelData{} b.Raw = append(b.Raw, d.Raw...) if err := b.Decode(); err != nil { t.Error(err) } if !b.Equal(d) { t.Error("not equal") } if !IsChannelData(b.Raw) || !IsChannelData(d.Raw) { t.Error("unexpected IsChannelData") } } func TestChannelData_Equal(t *testing.T) { for _, tc := range []struct { name string a, b *ChannelData value bool }{ { name: "nil", value: true, }, { name: "nil to non-nil", b: &ChannelData{}, }, { name: "equal", b: &ChannelData{ Number: MinChannelNumber, Data: []byte{1, 2, 3}, }, a: &ChannelData{ Number: MinChannelNumber, Data: []byte{1, 2, 3}, }, value: true, }, { name: "number", b: &ChannelData{ Number: MinChannelNumber, Data: []byte{1, 2, 3}, }, a: &ChannelData{ Number: MinChannelNumber + 1, Data: []byte{1, 2, 3}, }, }, { name: "length", b: &ChannelData{ Number: MinChannelNumber, Data: []byte{1, 2, 3}, }, a: &ChannelData{ Number: MinChannelNumber, Data: []byte{1, 2, 3, 4}, }, }, { name: "data", b: &ChannelData{ Number: MinChannelNumber, Data: []byte{1, 2, 3}, }, a: &ChannelData{ Number: MinChannelNumber, Data: []byte{1, 2, 2}, }, }, } { if v := tc.a.Equal(tc.b); v != tc.value { t.Errorf("unexpected: (%s) %v != %v", tc.name, tc.value, v) } } } func TestChannelData_Decode(t *testing.T) { for _, tc := range []struct { name string buf []byte err error }{ { name: "nil", err: io.ErrUnexpectedEOF, }, { name: "small", buf: []byte{1, 2, 3}, err: io.ErrUnexpectedEOF, }, { name: "zeroes", buf: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, err: ErrInvalidChannelNumber, }, { name: "bad chan number", buf: []byte{63, 255, 0, 0, 0, 4, 0, 0, 1, 2, 3, 4}, err: ErrInvalidChannelNumber, }, { name: "bad length", buf: []byte{0x40, 0x40, 0x02, 0x23, 0x16, 0, 0, 0, 0, 0, 0, 0}, err: ErrBadChannelDataLength, }, } { m := &ChannelData{ Raw: tc.buf, } if err := m.Decode(); !errors.Is(err, tc.err) { t.Errorf("unexpected: (%s) %v != %v", tc.name, tc.err, err) } } } func TestChannelData_Reset(t *testing.T) { d := &ChannelData{ Data: []byte{1, 2, 3, 4}, Number: MinChannelNumber + 1, } d.Encode() buf := make([]byte, len(d.Raw)) copy(buf, d.Raw) d.Reset() d.Raw = buf if err := d.Decode(); err != nil { t.Fatal(err) } } func TestIsChannelData(t *testing.T) { for _, tc := range []struct { name string buf []byte value bool }{ { name: "nil", }, { name: "small", buf: []byte{1, 2, 3, 4}, }, { name: "zeroes", buf: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, }, } { if v := IsChannelData(tc.buf); v != tc.value { t.Errorf("unexpected: (%s) %v != %v", tc.name, tc.value, v) } } } func BenchmarkIsChannelData(b *testing.B) { buf := []byte{64, 0, 0, 0, 0, 4, 0, 0, 1, 2, 3} b.ReportAllocs() b.SetBytes(int64(len(buf))) for i := 0; i < b.N; i++ { IsChannelData(buf) } } func BenchmarkChannelData_Encode(b *testing.B) { d := &ChannelData{ Data: []byte{1, 2, 3, 4}, Number: MinChannelNumber + 1, } b.ReportAllocs() b.SetBytes(4 + channelDataHeaderSize) for i := 0; i < b.N; i++ { d.Encode() } } func BenchmarkChannelData_Decode(b *testing.B) { d := &ChannelData{ Data: []byte{1, 2, 3, 4}, Number: MinChannelNumber + 1, } d.Encode() buf := make([]byte, len(d.Raw)) copy(buf, d.Raw) b.ReportAllocs() b.SetBytes(4 + channelDataHeaderSize) for i := 0; i < b.N; i++ { d.Reset() d.Raw = buf if err := d.Decode(); err != nil { b.Error(err) } } } func TestChromeChannelData(t *testing.T) { var ( r = bytes.NewReader(loadData(t, "02_chandata.hex")) s = bufio.NewScanner(r) data [][]byte messages []*ChannelData ) // Decoding hex data into binary. for s.Scan() { b, err := hex.DecodeString(s.Text()) if err != nil { t.Fatal(err) } data = append(data, b) } // All hex streams decoded to raw binary format and stored in data slice. // Decoding packets to messages. for i, packet := range data { m := new(ChannelData) m.Raw = packet if err := m.Decode(); err != nil { t.Errorf("Packet %d: %v", i, err) } encoded := &ChannelData{ Data: m.Data, Number: m.Number, } encoded.Encode() decoded := new(ChannelData) decoded.Raw = encoded.Raw if err := decoded.Decode(); err != nil { t.Error(err) } if !decoded.Equal(m) { t.Error("should be equal") } messages = append(messages, m) } if len(messages) != 2 { t.Error("unexpected message slice list") } } turn-2.1.0/internal/proto/chann.go000066400000000000000000000035241436753726200171220ustar00rootroot00000000000000package proto import ( "encoding/binary" "errors" "strconv" "github.com/pion/stun" ) // ChannelNumber represents CHANNEL-NUMBER attribute. // // The CHANNEL-NUMBER attribute contains the number of the channel. // // RFC 5766 Section 14.1 type ChannelNumber uint16 // encoded as uint16 func (n ChannelNumber) String() string { return strconv.Itoa(int(n)) } // 16 bits of uint + 16 bits of RFFU = 0. const channelNumberSize = 4 // AddTo adds CHANNEL-NUMBER to message. func (n ChannelNumber) AddTo(m *stun.Message) error { v := make([]byte, channelNumberSize) binary.BigEndian.PutUint16(v[:2], uint16(n)) // v[2:4] are zeroes (RFFU = 0) m.Add(stun.AttrChannelNumber, v) return nil } // GetFrom decodes CHANNEL-NUMBER from message. func (n *ChannelNumber) GetFrom(m *stun.Message) error { v, err := m.Get(stun.AttrChannelNumber) if err != nil { return err } if err = stun.CheckSize(stun.AttrChannelNumber, len(v), channelNumberSize); err != nil { return err } _ = v[channelNumberSize-1] // asserting length *n = ChannelNumber(binary.BigEndian.Uint16(v[:2])) // v[2:4] is RFFU and equals to 0. return nil } // See https://tools.ietf.org/html/rfc5766#section-11: // // 0x4000 through 0x7FFF: These values are the allowed channel // numbers (16,383 possible values). const ( MinChannelNumber = 0x4000 MaxChannelNumber = 0x7FFF ) // ErrInvalidChannelNumber means that channel number is not valid as by RFC 5766 Section 11. var ErrInvalidChannelNumber = errors.New("channel number not in [0x4000, 0x7FFF]") // isChannelNumberValid returns true if c in [0x4000, 0x7FFF]. func isChannelNumberValid(c uint16) bool { return c >= MinChannelNumber && c <= MaxChannelNumber } // Valid returns true if channel number has correct value that complies RFC 5766 Section 11 range. func (n ChannelNumber) Valid() bool { return isChannelNumberValid(uint16(n)) } turn-2.1.0/internal/proto/chann_test.go000066400000000000000000000051551436753726200201630ustar00rootroot00000000000000package proto import ( "errors" "testing" "github.com/pion/stun" "github.com/stretchr/testify/assert" ) func BenchmarkChannelNumber(b *testing.B) { b.Run("AddTo", func(b *testing.B) { b.ReportAllocs() m := new(stun.Message) for i := 0; i < b.N; i++ { n := ChannelNumber(12) if err := n.AddTo(m); err != nil { b.Fatal(err) } m.Reset() } }) b.Run("GetFrom", func(b *testing.B) { m := new(stun.Message) assert.NoError(b, ChannelNumber(12).AddTo(m)) for i := 0; i < b.N; i++ { var n ChannelNumber if err := n.GetFrom(m); err != nil { b.Fatal(err) } } }) } func TestChannelNumber(t *testing.T) { t.Run("String", func(t *testing.T) { n := ChannelNumber(112) if n.String() != "112" { t.Errorf("bad string %s, expected 112", n.String()) } }) t.Run("NoAlloc", func(t *testing.T) { m := &stun.Message{} if wasAllocs(func() { // Case with ChannelNumber on stack. n := ChannelNumber(6) n.AddTo(m) //nolint m.Reset() }) { t.Error("Unexpected allocations") } n := ChannelNumber(12) nP := &n if wasAllocs(func() { // On heap. nP.AddTo(m) //nolint m.Reset() }) { t.Error("Unexpected allocations") } }) t.Run("AddTo", func(t *testing.T) { m := new(stun.Message) n := ChannelNumber(6) if err := n.AddTo(m); err != nil { t.Error(err) } m.WriteHeader() t.Run("GetFrom", func(t *testing.T) { decoded := new(stun.Message) if _, err := decoded.Write(m.Raw); err != nil { t.Fatal("failed to decode message:", err) } var numDecoded ChannelNumber if err := numDecoded.GetFrom(decoded); err != nil { t.Fatal(err) } if numDecoded != n { t.Errorf("Decoded %d, expected %d", numDecoded, n) } if wasAllocs(func() { var num ChannelNumber num.GetFrom(decoded) //nolint }) { t.Error("Unexpected allocations") } t.Run("HandleErr", func(t *testing.T) { m := new(stun.Message) nHandle := new(ChannelNumber) if err := nHandle.GetFrom(m); !errors.Is(err, stun.ErrAttributeNotFound) { t.Errorf("%v should be not found", err) } m.Add(stun.AttrChannelNumber, []byte{1, 2, 3}) if !stun.IsAttrSizeInvalid(nHandle.GetFrom(m)) { t.Error("IsAttrSizeInvalid should be true") } }) }) }) } func TestChannelNumber_Valid(t *testing.T) { for _, tc := range []struct { n ChannelNumber value bool }{ {MinChannelNumber - 1, false}, {MinChannelNumber, true}, {MinChannelNumber + 1, true}, {MaxChannelNumber, true}, {MaxChannelNumber + 1, false}, } { if v := tc.n.Valid(); v != tc.value { t.Errorf("unexpected: (%s) %v != %v", tc.n.String(), tc.value, v) } } } turn-2.1.0/internal/proto/chrome_test.go000066400000000000000000000014411436753726200203430ustar00rootroot00000000000000package proto import ( "bufio" "bytes" "encoding/hex" "testing" "github.com/pion/stun" ) func TestChromeAllocRequest(t *testing.T) { var ( r = bytes.NewReader(loadData(t, "01_chromeallocreq.hex")) s = bufio.NewScanner(r) data [][]byte messages []*stun.Message ) // Decoding hex data into binary. for s.Scan() { b, err := hex.DecodeString(s.Text()) if err != nil { t.Fatal(err) } data = append(data, b) } // All hex streams decoded to raw binary format and stored in data slice. // Decoding packets to messages. for i, packet := range data { m := new(stun.Message) if _, err := m.Write(packet); err != nil { t.Errorf("Packet %d: %v", i, err) } messages = append(messages, m) } if len(messages) != 4 { t.Error("unexpected message slice list") } } turn-2.1.0/internal/proto/connection_id.go000066400000000000000000000016751436753726200206530ustar00rootroot00000000000000package proto import ( "encoding/binary" "github.com/pion/stun" ) // ConnectionID represents CONNECTION-ID attribute. // // The CONNECTION-ID attribute uniquely identifies a peer data // connection. It is a 32-bit unsigned integral value. // // RFC 6062 Section 6.2.1 type ConnectionID uint32 const connectionIDSize = 4 // uint32: 4 bytes, 32 bits // AddTo adds CONNECTION-ID to message. func (c ConnectionID) AddTo(m *stun.Message) error { v := make([]byte, lifetimeSize) binary.BigEndian.PutUint32(v, uint32(c)) m.Add(stun.AttrConnectionID, v) return nil } // GetFrom decodes CONNECTION-ID from message. func (c *ConnectionID) GetFrom(m *stun.Message) error { v, err := m.Get(stun.AttrConnectionID) if err != nil { return err } if err = stun.CheckSize(stun.AttrConnectionID, len(v), connectionIDSize); err != nil { return err } _ = v[connectionIDSize-1] // asserting length *(*uint32)(c) = binary.BigEndian.Uint32(v) return nil } turn-2.1.0/internal/proto/data.go000066400000000000000000000013241436753726200167400ustar00rootroot00000000000000package proto import "github.com/pion/stun" // Data represents DATA attribute. // // The DATA attribute is present in all Send and Data indications. The // value portion of this attribute is variable length and consists of // the application data (that is, the data that would immediately follow // the UDP header if the data was been sent directly between the client // and the peer). // // RFC 5766 Section 14.4 type Data []byte // AddTo adds DATA to message. func (d Data) AddTo(m *stun.Message) error { m.Add(stun.AttrData, d) return nil } // GetFrom decodes DATA from message. func (d *Data) GetFrom(m *stun.Message) error { v, err := m.Get(stun.AttrData) if err != nil { return err } *d = v return nil } turn-2.1.0/internal/proto/data_test.go000066400000000000000000000034751436753726200200100ustar00rootroot00000000000000package proto import ( "bytes" "errors" "testing" "github.com/pion/stun" "github.com/stretchr/testify/assert" ) func BenchmarkData(b *testing.B) { b.Run("AddTo", func(b *testing.B) { m := new(stun.Message) d := make(Data, 10) for i := 0; i < b.N; i++ { assert.NoError(b, d.AddTo(m)) m.Reset() } }) b.Run("AddToRaw", func(b *testing.B) { m := new(stun.Message) d := make([]byte, 10) // Overhead should be low. for i := 0; i < b.N; i++ { m.Add(stun.AttrData, d) m.Reset() } }) } func TestData(t *testing.T) { t.Run("NoAlloc", func(t *testing.T) { m := new(stun.Message) v := []byte{1, 2, 3, 4} if wasAllocs(func() { // On stack. d := Data(v) d.AddTo(m) //nolint m.Reset() }) { t.Error("Unexpected allocations") } d := &Data{1, 2, 3, 4} if wasAllocs(func() { // On heap. d.AddTo(m) //nolint m.Reset() }) { t.Error("Unexpected allocations") } }) t.Run("AddTo", func(t *testing.T) { m := new(stun.Message) d := Data{1, 2, 33, 44, 0x13, 0xaf} if err := d.AddTo(m); err != nil { t.Fatal(err) } m.WriteHeader() t.Run("GetFrom", func(t *testing.T) { decoded := new(stun.Message) if _, err := decoded.Write(m.Raw); err != nil { t.Fatal("failed to decode message:", err) } var dataDecoded Data if err := dataDecoded.GetFrom(decoded); err != nil { t.Fatal(err) } if !bytes.Equal(dataDecoded, d) { t.Error(dataDecoded, "!=", d, "(expected)") } if wasAllocs(func() { var dataDecoded Data dataDecoded.GetFrom(decoded) //nolint }) { t.Error("Unexpected allocations") } t.Run("HandleErr", func(t *testing.T) { m := new(stun.Message) var handle Data if err := handle.GetFrom(m); !errors.Is(err, stun.ErrAttributeNotFound) { t.Errorf("%v should be not found", err) } }) }) }) } turn-2.1.0/internal/proto/dontfrag.go000066400000000000000000000022541436753726200176360ustar00rootroot00000000000000package proto import ( "github.com/pion/stun" ) // DontFragmentAttr is a deprecated alias for DontFragment // Deprecated: Please use DontFragment type DontFragmentAttr = DontFragment // DontFragment represents DONT-FRAGMENT attribute. // // This attribute is used by the client to request that the server set // the DF (Don't Fragment) bit in the IP header when relaying the // application data onward to the peer. This attribute has no value // part and thus the attribute length field is 0. // // RFC 5766 Section 14.8 type DontFragment struct{} const dontFragmentSize = 0 // AddTo adds DONT-FRAGMENT attribute to message. func (DontFragment) AddTo(m *stun.Message) error { m.Add(stun.AttrDontFragment, nil) return nil } // GetFrom decodes DONT-FRAGMENT from message. func (d *DontFragment) GetFrom(m *stun.Message) error { v, err := m.Get(stun.AttrDontFragment) if err != nil { return err } if err = stun.CheckSize(stun.AttrDontFragment, len(v), dontFragmentSize); err != nil { return err } return nil } // IsSet returns true if DONT-FRAGMENT attribute is set. func (DontFragment) IsSet(m *stun.Message) bool { _, err := m.Get(stun.AttrDontFragment) return err == nil } turn-2.1.0/internal/proto/dontfrag_test.go000066400000000000000000000013701436753726200206730ustar00rootroot00000000000000package proto import ( "testing" "github.com/pion/stun" ) func TestDontFragment(t *testing.T) { var dontFrag DontFragment t.Run("False", func(t *testing.T) { m := new(stun.Message) m.WriteHeader() if dontFrag.IsSet(m) { t.Error("should not be set") } }) t.Run("AddTo", func(t *testing.T) { m := new(stun.Message) if err := dontFrag.AddTo(m); err != nil { t.Error(err) } m.WriteHeader() t.Run("IsSet", func(t *testing.T) { decoded := new(stun.Message) if _, err := decoded.Write(m.Raw); err != nil { t.Fatal("failed to decode message:", err) } if !dontFrag.IsSet(m) { t.Error("should be set") } if wasAllocs(func() { dontFrag.IsSet(m) }) { t.Error("unexpected allocations") } }) }) } turn-2.1.0/internal/proto/evenport.go000066400000000000000000000023271436753726200176750ustar00rootroot00000000000000package proto import "github.com/pion/stun" // EvenPort represents EVEN-PORT attribute. // // This attribute allows the client to request that the port in the // relayed transport address be even, and (optionally) that the server // reserve the next-higher port number. // // RFC 5766 Section 14.6 type EvenPort struct { // ReservePort means that the server is requested to reserve // the next-higher port number (on the same IP address) // for a subsequent allocation. ReservePort bool } func (p EvenPort) String() string { if p.ReservePort { return "reserve: true" } return "reserve: false" } const ( evenPortSize = 1 firstBitSet = (1 << 8) - 1 // 0b100000000 ) // AddTo adds EVEN-PORT to message. func (p EvenPort) AddTo(m *stun.Message) error { v := make([]byte, evenPortSize) if p.ReservePort { // Set first bit to 1. v[0] = firstBitSet } m.Add(stun.AttrEvenPort, v) return nil } // GetFrom decodes EVEN-PORT from message. func (p *EvenPort) GetFrom(m *stun.Message) error { v, err := m.Get(stun.AttrEvenPort) if err != nil { return err } if err = stun.CheckSize(stun.AttrEvenPort, len(v), evenPortSize); err != nil { return err } if v[0]&firstBitSet > 0 { p.ReservePort = true } return nil } turn-2.1.0/internal/proto/evenport_test.go000066400000000000000000000034651436753726200207400ustar00rootroot00000000000000package proto import ( "errors" "testing" "github.com/pion/stun" "github.com/stretchr/testify/assert" ) func TestEvenPort(t *testing.T) { t.Run("String", func(t *testing.T) { p := EvenPort{} if p.String() != "reserve: false" { t.Errorf("bad value %q for reserve: false", p.String()) } p.ReservePort = true if p.String() != "reserve: true" { t.Errorf("bad value %q for reserve: true", p.String()) } }) t.Run("False", func(t *testing.T) { m := new(stun.Message) p := EvenPort{ ReservePort: false, } if err := p.AddTo(m); err != nil { t.Error(err) } m.WriteHeader() decoded := new(stun.Message) var port EvenPort _, err := decoded.Write(m.Raw) assert.NoError(t, err) assert.NoError(t, port.GetFrom(m)) if port != p { t.Fatal("not equal") } }) t.Run("AddTo", func(t *testing.T) { m := new(stun.Message) p := EvenPort{ ReservePort: true, } if err := p.AddTo(m); err != nil { t.Error(err) } m.WriteHeader() t.Run("GetFrom", func(t *testing.T) { decoded := new(stun.Message) if _, err := decoded.Write(m.Raw); err != nil { t.Fatal("failed to decode message:", err) } port := EvenPort{} if err := port.GetFrom(decoded); err != nil { t.Fatal(err) } if port != p { t.Errorf("Decoded %q, expected %q", port.String(), p.String()) } if wasAllocs(func() { port.GetFrom(decoded) //nolint }) { t.Error("Unexpected allocations") } t.Run("HandleErr", func(t *testing.T) { m := new(stun.Message) var handle EvenPort if err := handle.GetFrom(m); !errors.Is(err, stun.ErrAttributeNotFound) { t.Errorf("%v should be not found", err) } m.Add(stun.AttrEvenPort, []byte{1, 2, 3}) if !stun.IsAttrSizeInvalid(handle.GetFrom(m)) { t.Error("IsAttrSizeInvalid should be true") } }) }) }) } turn-2.1.0/internal/proto/fuzz_test.go000066400000000000000000000051611436753726200200670ustar00rootroot00000000000000package proto import ( "encoding/binary" "errors" "fmt" "testing" "github.com/pion/stun" ) type attr interface { stun.Getter stun.Setter } type attrs []struct { g attr t stun.AttrType } func (a attrs) pick(v byte) struct { g attr t stun.AttrType } { idx := int(v) % len(a) return a[idx] } func FuzzSetters(f *testing.F) { f.Fuzz(func(t *testing.T, attrType byte, value []byte) { var ( m1 = &stun.Message{ Raw: make([]byte, 0, 2048), } m2 = &stun.Message{ Raw: make([]byte, 0, 2048), } m3 = &stun.Message{ Raw: make([]byte, 0, 2048), } ) attributes := attrs{ {new(ChannelNumber), stun.AttrChannelNumber}, {new(Lifetime), stun.AttrLifetime}, {new(XORPeerAddress), stun.AttrXORPeerAddress}, {new(Data), stun.AttrData}, {new(XORRelayedAddress), stun.AttrXORRelayedAddress}, {new(EvenPort), stun.AttrEvenPort}, {new(RequestedTransport), stun.AttrRequestedTransport}, {new(DontFragment), stun.AttrDontFragment}, {new(ReservationToken), stun.AttrReservationToken}, {new(ConnectionID), stun.AttrConnectionID}, {new(RequestedAddressFamily), stun.AttrRequestedAddressFamily}, } attr := attributes.pick(attrType) m1.WriteHeader() m1.Add(attr.t, value) if err := attr.g.GetFrom(m1); err != nil { if errors.Is(err, stun.ErrAttributeNotFound) { fmt.Println("unexpected 404") // nolint panic(err) // nolint } return } m2.WriteHeader() if err := attr.g.AddTo(m2); err != nil { fmt.Println("failed to add attribute to m2") // nolint panic(err) // nolint } m3.WriteHeader() v, err := m2.Get(attr.t) if err != nil { panic(err) // nolint } m3.Add(attr.t, v) if !m2.Equal(m3) { fmt.Println(m2, "not equal", m3) // nolint panic("not equal") // nolint } }) } func FuzzChannelData(f *testing.F) { d := &ChannelData{} f.Fuzz(func(t *testing.T, data []byte) { d.Reset() if len(data) > channelDataHeaderSize { // Make sure the channel id is in the proper range if b := binary.BigEndian.Uint16(data[0:4]); b > 20000 { binary.BigEndian.PutUint16(data[0:4], MinChannelNumber-1) } else if b > 40000 { binary.BigEndian.PutUint16(data[0:4], MinChannelNumber+(MaxChannelNumber-MinChannelNumber)%b) } } d.Raw = append(d.Raw, data...) if d.Decode() != nil { return } d.Encode() if !d.Number.Valid() { return } d2 := &ChannelData{} d2.Raw = d.Raw if err := d2.Decode(); err != nil { panic(err) //nolint } }) } func FuzzIsChannelData(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { IsChannelData(data) }) } turn-2.1.0/internal/proto/lifetime.go000066400000000000000000000024161436753726200176300ustar00rootroot00000000000000package proto import ( "encoding/binary" "time" "github.com/pion/stun" ) // DefaultLifetime in RFC 5766 is 10 minutes. // // RFC 5766 Section 2.2 const DefaultLifetime = time.Minute * 10 // Lifetime represents LIFETIME attribute. // // The LIFETIME attribute represents the duration for which the server // will maintain an allocation in the absence of a refresh. The value // portion of this attribute is 4-bytes long and consists of a 32-bit // unsigned integral value representing the number of seconds remaining // until expiration. // // RFC 5766 Section 14.2 type Lifetime struct { time.Duration } // uint32 seconds const lifetimeSize = 4 // 4 bytes, 32 bits // AddTo adds LIFETIME to message. func (l Lifetime) AddTo(m *stun.Message) error { v := make([]byte, lifetimeSize) binary.BigEndian.PutUint32(v, uint32(l.Seconds())) m.Add(stun.AttrLifetime, v) return nil } // GetFrom decodes LIFETIME from message. func (l *Lifetime) GetFrom(m *stun.Message) error { v, err := m.Get(stun.AttrLifetime) if err != nil { return err } if err = stun.CheckSize(stun.AttrLifetime, len(v), lifetimeSize); err != nil { return err } _ = v[lifetimeSize-1] // asserting length seconds := binary.BigEndian.Uint32(v) l.Duration = time.Second * time.Duration(seconds) return nil } turn-2.1.0/internal/proto/lifetime_test.go000066400000000000000000000042521436753726200206670ustar00rootroot00000000000000package proto import ( "errors" "testing" "time" "github.com/pion/stun" "github.com/stretchr/testify/assert" ) func BenchmarkLifetime(b *testing.B) { b.Run("AddTo", func(b *testing.B) { b.ReportAllocs() m := new(stun.Message) for i := 0; i < b.N; i++ { l := Lifetime{time.Second} if err := l.AddTo(m); err != nil { b.Fatal(err) } m.Reset() } }) b.Run("GetFrom", func(b *testing.B) { m := new(stun.Message) assert.NoError(b, Lifetime{time.Minute}.AddTo(m)) for i := 0; i < b.N; i++ { l := Lifetime{} if err := l.GetFrom(m); err != nil { b.Fatal(err) } } }) } func TestLifetime(t *testing.T) { t.Run("String", func(t *testing.T) { l := Lifetime{time.Second * 10} if l.String() != "10s" { t.Errorf("bad string %s, expedted 10s", l) } }) t.Run("NoAlloc", func(t *testing.T) { m := &stun.Message{} if wasAllocs(func() { // On stack. l := Lifetime{ Duration: time.Minute, } l.AddTo(m) //nolint m.Reset() }) { t.Error("Unexpected allocations") } l := &Lifetime{time.Second} if wasAllocs(func() { // On heap. l.AddTo(m) //nolint m.Reset() }) { t.Error("Unexpected allocations") } }) t.Run("AddTo", func(t *testing.T) { m := new(stun.Message) l := Lifetime{time.Second * 10} if err := l.AddTo(m); err != nil { t.Error(err) } m.WriteHeader() t.Run("GetFrom", func(t *testing.T) { decoded := new(stun.Message) if _, err := decoded.Write(m.Raw); err != nil { t.Fatal("failed to decode message:", err) } life := Lifetime{} if err := life.GetFrom(decoded); err != nil { t.Fatal(err) } if life != l { t.Errorf("Decoded %q, expected %q", life, l) } if wasAllocs(func() { life.GetFrom(decoded) //nolint }) { t.Error("Unexpected allocations") } t.Run("HandleErr", func(t *testing.T) { m := new(stun.Message) nHandle := new(Lifetime) if err := nHandle.GetFrom(m); !errors.Is(err, stun.ErrAttributeNotFound) { t.Errorf("%v should be not found", err) } m.Add(stun.AttrLifetime, []byte{1, 2, 3}) if !stun.IsAttrSizeInvalid(nHandle.GetFrom(m)) { t.Error("IsAttrSizeInvalid should be true") } }) }) }) } turn-2.1.0/internal/proto/peeraddr.go000066400000000000000000000021611436753726200176150ustar00rootroot00000000000000package proto import ( "net" "github.com/pion/stun" ) // PeerAddress implements XOR-PEER-ADDRESS attribute. // // The XOR-PEER-ADDRESS specifies the address and port of the peer as // seen from the TURN server. (For example, the peer's server-reflexive // transport address if the peer is behind a NAT.) // // RFC 5766 Section 14.3 type PeerAddress struct { IP net.IP Port int } func (a PeerAddress) String() string { return stun.XORMappedAddress(a).String() } // AddTo adds XOR-PEER-ADDRESS to message. func (a PeerAddress) AddTo(m *stun.Message) error { return stun.XORMappedAddress(a).AddToAs(m, stun.AttrXORPeerAddress) } // GetFrom decodes XOR-PEER-ADDRESS from message. func (a *PeerAddress) GetFrom(m *stun.Message) error { return (*stun.XORMappedAddress)(a).GetFromAs(m, stun.AttrXORPeerAddress) } // XORPeerAddress implements XOR-PEER-ADDRESS attribute. // // The XOR-PEER-ADDRESS specifies the address and port of the peer as // seen from the TURN server. (For example, the peer's server-reflexive // transport address if the peer is behind a NAT.) // // RFC 5766 Section 14.3 type XORPeerAddress = PeerAddress turn-2.1.0/internal/proto/peeraddr_test.go000066400000000000000000000011601436753726200206520ustar00rootroot00000000000000package proto import ( "net" "testing" "github.com/pion/stun" "github.com/stretchr/testify/assert" ) func TestPeerAddress(t *testing.T) { // Simple tests because already tested in stun. a := PeerAddress{ IP: net.IPv4(111, 11, 1, 2), Port: 333, } t.Run("String", func(t *testing.T) { if a.String() != "111.11.1.2:333" { t.Error("invalid string") } }) m := new(stun.Message) if err := a.AddTo(m); err != nil { t.Fatal(err) } m.WriteHeader() decoded := new(stun.Message) _, err := decoded.Write(m.Raw) assert.NoError(t, err) var aGot PeerAddress assert.NoError(t, aGot.GetFrom(decoded)) } turn-2.1.0/internal/proto/proto.go000066400000000000000000000021151436753726200171710ustar00rootroot00000000000000// Package proto implements RFC 5766 Traversal Using Relays around NAT. // // Merged from gortc/turn v0.80. package proto import ( "github.com/pion/stun" ) // Default ports for TURN from RFC 5766 Section 4. const ( // DefaultPort for TURN is same as STUN. DefaultPort = stun.DefaultPort // DefaultTLSPort is for TURN over TLS and is same as STUN. DefaultTLSPort = stun.DefaultTLSPort ) // CreatePermissionRequest is shorthand for create permission request type. func CreatePermissionRequest() stun.MessageType { return stun.NewType(stun.MethodCreatePermission, stun.ClassRequest) } // AllocateRequest is shorthand for allocation request message type. func AllocateRequest() stun.MessageType { return stun.NewType(stun.MethodAllocate, stun.ClassRequest) } // SendIndication is shorthand for send indication message type. func SendIndication() stun.MessageType { return stun.NewType(stun.MethodSend, stun.ClassIndication) } // RefreshRequest is shorthand for refresh request message type. func RefreshRequest() stun.MessageType { return stun.NewType(stun.MethodRefresh, stun.ClassRequest) } turn-2.1.0/internal/proto/proto_test.go000066400000000000000000000010571436753726200202340ustar00rootroot00000000000000package proto import ( "io/ioutil" "os" "path/filepath" "testing" ) const allocRuns = 10 // wasAllocs returns true if f allocates memory. func wasAllocs(f func()) bool { return testing.AllocsPerRun(allocRuns, f) > 0 } func loadData(tb testing.TB, name string) []byte { name = filepath.Join("testdata", name) f, err := os.Open(name) // #nosec if err != nil { tb.Fatal(err) } defer func() { if errClose := f.Close(); errClose != nil { tb.Fatal(errClose) } }() v, err := ioutil.ReadAll(f) if err != nil { tb.Fatal(err) } return v } turn-2.1.0/internal/proto/relayedaddr.go000066400000000000000000000020371436753726200203110ustar00rootroot00000000000000package proto import ( "net" "github.com/pion/stun" ) // RelayedAddress implements XOR-RELAYED-ADDRESS attribute. // // It specifies the address and port that the server allocated to the // client. It is encoded in the same way as XOR-MAPPED-ADDRESS. // // RFC 5766 Section 14.5 type RelayedAddress struct { IP net.IP Port int } func (a RelayedAddress) String() string { return stun.XORMappedAddress(a).String() } // AddTo adds XOR-PEER-ADDRESS to message. func (a RelayedAddress) AddTo(m *stun.Message) error { return stun.XORMappedAddress(a).AddToAs(m, stun.AttrXORRelayedAddress) } // GetFrom decodes XOR-PEER-ADDRESS from message. func (a *RelayedAddress) GetFrom(m *stun.Message) error { return (*stun.XORMappedAddress)(a).GetFromAs(m, stun.AttrXORRelayedAddress) } // XORRelayedAddress implements XOR-RELAYED-ADDRESS attribute. // // It specifies the address and port that the server allocated to the // client. It is encoded in the same way as XOR-MAPPED-ADDRESS. // // RFC 5766 Section 14.5 type XORRelayedAddress = RelayedAddress turn-2.1.0/internal/proto/relayedaddr_test.go000066400000000000000000000011711436753726200213460ustar00rootroot00000000000000package proto import ( "net" "testing" "github.com/pion/stun" "github.com/stretchr/testify/assert" ) func TestRelayedAddress(t *testing.T) { // Simple tests because already tested in stun. a := RelayedAddress{ IP: net.IPv4(111, 11, 1, 2), Port: 333, } t.Run("String", func(t *testing.T) { if a.String() != "111.11.1.2:333" { t.Error("invalid string") } }) m := new(stun.Message) if err := a.AddTo(m); err != nil { t.Fatal(err) } m.WriteHeader() decoded := new(stun.Message) _, err := decoded.Write(m.Raw) assert.NoError(t, err) var aGot RelayedAddress assert.NoError(t, aGot.GetFrom(decoded)) } turn-2.1.0/internal/proto/reqfamily.go000066400000000000000000000030671436753726200200260ustar00rootroot00000000000000package proto import ( "errors" "github.com/pion/stun" ) // RequestedAddressFamily represents the REQUESTED-ADDRESS-FAMILY Attribute as // defined in RFC 6156 Section 4.1.1. type RequestedAddressFamily byte const requestedFamilySize = 4 var errInvalidRequestedFamilyValue = errors.New("invalid value for requested family attribute") // GetFrom decodes REQUESTED-ADDRESS-FAMILY from message. func (f *RequestedAddressFamily) GetFrom(m *stun.Message) error { v, err := m.Get(stun.AttrRequestedAddressFamily) if err != nil { return err } if err = stun.CheckSize(stun.AttrRequestedAddressFamily, len(v), requestedFamilySize); err != nil { return err } switch v[0] { case byte(RequestedFamilyIPv4), byte(RequestedFamilyIPv6): *f = RequestedAddressFamily(v[0]) default: return errInvalidRequestedFamilyValue } return nil } func (f RequestedAddressFamily) String() string { switch f { case RequestedFamilyIPv4: return "IPv4" case RequestedFamilyIPv6: return "IPv6" default: return "unknown" } } // AddTo adds REQUESTED-ADDRESS-FAMILY to message. func (f RequestedAddressFamily) AddTo(m *stun.Message) error { v := make([]byte, requestedFamilySize) v[0] = byte(f) // b[1:4] is RFFU = 0. // The RFFU field MUST be set to zero on transmission and MUST be // ignored on reception. It is reserved for future uses. m.Add(stun.AttrRequestedAddressFamily, v) return nil } // Values for RequestedAddressFamily as defined in RFC 6156 Section 4.1.1. const ( RequestedFamilyIPv4 RequestedAddressFamily = 0x01 RequestedFamilyIPv6 RequestedAddressFamily = 0x02 ) turn-2.1.0/internal/proto/reqfamily_test.go000066400000000000000000000041151436753726200210600ustar00rootroot00000000000000package proto import ( "errors" "testing" "github.com/pion/stun" ) func TestRequestedAddressFamily(t *testing.T) { t.Run("String", func(t *testing.T) { if RequestedFamilyIPv4.String() != "IPv4" { t.Errorf("bad string %q, expected %q", RequestedFamilyIPv4, "IPv4", ) } if RequestedFamilyIPv6.String() != "IPv6" { t.Errorf("bad string %q, expected %q", RequestedFamilyIPv6, "IPv6", ) } if RequestedAddressFamily(0x04).String() != "unknown" { t.Error("should be unknown") } }) t.Run("NoAlloc", func(t *testing.T) { m := &stun.Message{} if wasAllocs(func() { // On stack. r := RequestedFamilyIPv4 r.AddTo(m) //nolint m.Reset() }) { t.Error("Unexpected allocations") } r := new(RequestedAddressFamily) *r = RequestedFamilyIPv4 if wasAllocs(func() { // On heap. r.AddTo(m) //nolint m.Reset() }) { t.Error("Unexpected allocations") } }) t.Run("AddTo", func(t *testing.T) { m := new(stun.Message) r := RequestedFamilyIPv4 if err := r.AddTo(m); err != nil { t.Error(err) } m.WriteHeader() t.Run("GetFrom", func(t *testing.T) { decoded := new(stun.Message) if _, err := decoded.Write(m.Raw); err != nil { t.Fatal("failed to decode message:", err) } var req RequestedAddressFamily if err := req.GetFrom(decoded); err != nil { t.Fatal(err) } if req != r { t.Errorf("Decoded %q, expected %q", req, r) } if wasAllocs(func() { r.GetFrom(decoded) //nolint }) { t.Error("Unexpected allocations") } t.Run("HandleErr", func(t *testing.T) { m := new(stun.Message) var handle RequestedAddressFamily if err := handle.GetFrom(m); !errors.Is(err, stun.ErrAttributeNotFound) { t.Errorf("%v should be not found", err) } m.Add(stun.AttrRequestedAddressFamily, []byte{1, 2, 3}) if !stun.IsAttrSizeInvalid(handle.GetFrom(m)) { t.Error("IsAttrSizeInvalid should be true") } m.Reset() m.Add(stun.AttrRequestedAddressFamily, []byte{5, 0, 0, 0}) if handle.GetFrom(m) == nil { t.Error("should error on invalid value") } }) }) }) } turn-2.1.0/internal/proto/reqtrans.go000066400000000000000000000030111436753726200176610ustar00rootroot00000000000000package proto import ( "strconv" "github.com/pion/stun" ) // Protocol is IANA assigned protocol number. type Protocol byte const ( // ProtoUDP is IANA assigned protocol number for UDP. ProtoUDP Protocol = 17 ) func (p Protocol) String() string { switch p { case ProtoUDP: return "UDP" default: return strconv.Itoa(int(p)) } } // RequestedTransport represents REQUESTED-TRANSPORT attribute. // // This attribute is used by the client to request a specific transport // protocol for the allocated transport address. RFC 5766 only allows the use of // code point 17 (User Datagram Protocol). // // RFC 5766 Section 14.7 type RequestedTransport struct { Protocol Protocol } func (t RequestedTransport) String() string { return "protocol: " + t.Protocol.String() } const requestedTransportSize = 4 // AddTo adds REQUESTED-TRANSPORT to message. func (t RequestedTransport) AddTo(m *stun.Message) error { v := make([]byte, requestedTransportSize) v[0] = byte(t.Protocol) // b[1:4] is RFFU = 0. // The RFFU field MUST be set to zero on transmission and MUST be // ignored on reception. It is reserved for future uses. m.Add(stun.AttrRequestedTransport, v) return nil } // GetFrom decodes REQUESTED-TRANSPORT from message. func (t *RequestedTransport) GetFrom(m *stun.Message) error { v, err := m.Get(stun.AttrRequestedTransport) if err != nil { return err } if err = stun.CheckSize(stun.AttrRequestedTransport, len(v), requestedTransportSize); err != nil { return err } t.Protocol = Protocol(v[0]) return nil } turn-2.1.0/internal/proto/reqtrans_test.go000066400000000000000000000037351436753726200207350ustar00rootroot00000000000000package proto import ( "errors" "testing" "github.com/pion/stun" ) func TestRequestedTransport(t *testing.T) { t.Run("String", func(t *testing.T) { r := RequestedTransport{ Protocol: ProtoUDP, } if r.String() != "protocol: UDP" { t.Errorf("bad string %q, expected %q", r, "protocol: UDP", ) } r.Protocol = 254 if r.String() != "protocol: 254" { if r.String() != "protocol: UDP" { t.Errorf("bad string %q, expected %q", r, "protocol: 254", ) } } }) t.Run("NoAlloc", func(t *testing.T) { m := &stun.Message{} if wasAllocs(func() { // On stack. r := RequestedTransport{ Protocol: ProtoUDP, } r.AddTo(m) //nolint m.Reset() }) { t.Error("Unexpected allocations") } r := &RequestedTransport{ Protocol: ProtoUDP, } if wasAllocs(func() { // On heap. r.AddTo(m) //nolint m.Reset() }) { t.Error("Unexpected allocations") } }) t.Run("AddTo", func(t *testing.T) { m := new(stun.Message) r := RequestedTransport{ Protocol: ProtoUDP, } if err := r.AddTo(m); err != nil { t.Error(err) } m.WriteHeader() t.Run("GetFrom", func(t *testing.T) { decoded := new(stun.Message) if _, err := decoded.Write(m.Raw); err != nil { t.Fatal("failed to decode message:", err) } req := RequestedTransport{ Protocol: ProtoUDP, } if err := req.GetFrom(decoded); err != nil { t.Fatal(err) } if req != r { t.Errorf("Decoded %q, expected %q", req, r) } if wasAllocs(func() { r.GetFrom(decoded) //nolint }) { t.Error("Unexpected allocations") } t.Run("HandleErr", func(t *testing.T) { m := new(stun.Message) var handle RequestedTransport if err := handle.GetFrom(m); !errors.Is(err, stun.ErrAttributeNotFound) { t.Errorf("%v should be not found", err) } m.Add(stun.AttrRequestedTransport, []byte{1, 2, 3}) if !stun.IsAttrSizeInvalid(handle.GetFrom(m)) { t.Error("IsAttrSizeInvalid should be true") } }) }) }) } turn-2.1.0/internal/proto/rsrvtoken.go000066400000000000000000000022551436753726200200700ustar00rootroot00000000000000package proto import "github.com/pion/stun" // ReservationToken represents RESERVATION-TOKEN attribute. // // The RESERVATION-TOKEN attribute contains a token that uniquely // identifies a relayed transport address being held in reserve by the // server. The server includes this attribute in a success response to // tell the client about the token, and the client includes this // attribute in a subsequent Allocate request to request the server use // that relayed transport address for the allocation. // // RFC 5766 Section 14.9 type ReservationToken []byte const reservationTokenSize = 8 // 8 bytes // AddTo adds RESERVATION-TOKEN to message. func (t ReservationToken) AddTo(m *stun.Message) error { if err := stun.CheckSize(stun.AttrReservationToken, len(t), reservationTokenSize); err != nil { return err } m.Add(stun.AttrReservationToken, t) return nil } // GetFrom decodes RESERVATION-TOKEN from message. func (t *ReservationToken) GetFrom(m *stun.Message) error { v, err := m.Get(stun.AttrReservationToken) if err != nil { return err } if err = stun.CheckSize(stun.AttrReservationToken, len(v), reservationTokenSize); err != nil { return err } *t = v return nil } turn-2.1.0/internal/proto/rsrvtoken_test.go000066400000000000000000000033731436753726200211310ustar00rootroot00000000000000package proto import ( "bytes" "errors" "testing" "github.com/pion/stun" ) func TestReservationToken(t *testing.T) { t.Run("NoAlloc", func(t *testing.T) { m := &stun.Message{} tok := make([]byte, 8) if wasAllocs(func() { // On stack. tk := ReservationToken(tok) tk.AddTo(m) //nolint m.Reset() }) { t.Error("Unexpected allocations") } tk := make(ReservationToken, 8) if wasAllocs(func() { // On heap. tk.AddTo(m) //nolint m.Reset() }) { t.Error("Unexpected allocations") } }) t.Run("AddTo", func(t *testing.T) { m := new(stun.Message) tk := make(ReservationToken, 8) tk[2] = 33 tk[7] = 1 if err := tk.AddTo(m); err != nil { t.Error(err) } m.WriteHeader() t.Run("HandleErr", func(t *testing.T) { badTk := ReservationToken{34, 45} if !stun.IsAttrSizeInvalid(badTk.AddTo(m)) { t.Error("IsAttrSizeInvalid should be true") } }) t.Run("GetFrom", func(t *testing.T) { decoded := new(stun.Message) if _, err := decoded.Write(m.Raw); err != nil { t.Fatal("failed to decode message:", err) } var tok ReservationToken if err := tok.GetFrom(decoded); err != nil { t.Fatal(err) } if !bytes.Equal(tok, tk) { t.Errorf("Decoded %v, expected %v", tok, tk) } if wasAllocs(func() { tok.GetFrom(decoded) //nolint }) { t.Error("Unexpected allocations") } t.Run("HandleErr", func(t *testing.T) { m := new(stun.Message) var handle ReservationToken if err := handle.GetFrom(m); !errors.Is(err, stun.ErrAttributeNotFound) { t.Errorf("%v should be not found", err) } m.Add(stun.AttrReservationToken, []byte{1, 2, 3}) if !stun.IsAttrSizeInvalid(handle.GetFrom(m)) { t.Error("IsAttrSizeInvalid should be true") } }) }) }) } turn-2.1.0/internal/proto/testdata/000077500000000000000000000000001436753726200173115ustar00rootroot00000000000000turn-2.1.0/internal/proto/testdata/01_chromeallocreq.hex000066400000000000000000000014441436753726200233220ustar00rootroot00000000000000000300242112a442626b4a6849664c3630526863802f0016687474703a2f2f6c6f63616c686f73743a333030302f00000019000411000000 011300582112a442626b4a6849664c36305268630009001000000401556e617574686f72697a656400150010356130323039623563623830363130360014000b61312e63796465762e7275758022001a436f7475726e2d342e352e302e33202764616e204569646572272300 0003006c2112a442324e50695a437a4634535034802f0016687474703a2f2f6c6f63616c686f73743a333030302f000000190004110000000006000665726e61646f00000014000b61312e63796465762e7275000015001035613032303962356362383036313036000800145c8743f3b64bec0880cdd8d476d37b801a6c3d33 010300582112a442324e50695a437a4634535034001600080001fb922b1ab211002000080001adb2f49f38ae000d0004000002588022001a436f7475726e2d342e352e302e33202764616e204569646572277475000800145d7e85b767a519ffce91dbf0a96775e370db92e3 turn-2.1.0/internal/proto/testdata/02_chandata.hex000066400000000000000000000024401436753726200220630ustar00rootroot0000000000000040000064000100502112a442453731722f2b322b6e4e7a5800060009443758343a33776c59000000c0570004000003e7802a00081d5136dab65b169300250000002400046e001eff0008001465d11a330e104a9f5f598af4abc6a805f26003cf802800046b334442 4000022316fefd0000000000000011012c0b000120000100000000012000011d00011a308201163081bda003020102020900afe52871340bd13e300a06082a8648ce3d0403023011310f300d06035504030c06576562525443301e170d3138303831313033353230305a170d3138303931313033353230305a3011310f300d06035504030c065765625254433059301306072a8648ce3d020106082a8648ce3d030107034200048080e348bd41469cfb7a7df316676fd72a06211765a50a0f0b07526c872dcf80093ed5caa3f5a40a725dd74b41b79bdd19ee630c5313c8601d6983286c8722c1300a06082a8648ce3d0403020348003045022100d13a0a131bc2a9f27abd3d4c547f7ef172996a0c0755c707b6a3e048d8762ded0220055fc8182818a644a3d3b5b157304cc3f1421fadb06263bfb451cd28be4bc9ee16fefd0000000000000012002d10000021000200000000002120f7e23c97df45a96e13cb3e76b37eff5e73e2aee0b6415d29443d0bd24f578b7e16fefd000000000000001300580f00004c000300000000004c040300483046022100fdbb74eab1aca1532e6ac0ab267d5b83a24bb4d5d7d504936e2785e6e388b2bd022100f6a457b9edd9ead52a9d0e9a19240b3a68b95699546c044f863cf8349bc8046214fefd000000000000001400010116fefd0001000000000004003000010000000000040aae2421e7d549632a7def8ed06898c3c5b53f5b812a963a39ab6cdd303b79bdb237f3314c1da21b turn-2.1.0/internal/proto/testdata/fuzz/000077500000000000000000000000001436753726200203075ustar00rootroot00000000000000turn-2.1.0/internal/proto/testdata/fuzz/FuzzChannelData/000077500000000000000000000000001436753726200233305ustar00rootroot00000000000000957e495ca04ae84d31a88d96966c784464d7ccd554158614bd24aa9be2c68c11000066400000000000000000000033661436753726200337000ustar00rootroot00000000000000turn-2.1.0/internal/proto/testdata/fuzz/FuzzChannelDatago test fuzz v1 []byte("@\x00\x00d\x00\x01\x00P!\x12\xa4BE71r/+2+nNzX\x00\x06\x00\tD7X4:3wlY\x00\x00\x00\xc0W\x00\x04\x00\x00\x03\xe7\x80*\x00\b\x1dQ6ڶ[\x16\x93\x00%\x00\x00\x00$\x00\x04n\x00\x1e\xff\x00\b\x00\x14e\xd1\x1a3\x0e\x10J\x9f_Y\x8a\xf4\xabƨ\x05\xf2`\x03π(\x00\x04k3DB@\x00\x02#\x16\xfe\xfd\x00\x00\x00\x00\x00\x00\x00\x11\x01,\v\x00\x01 \x00\x01\x00\x00\x00\x00\x01 \x00\x01\x1d\x00\x01\x1a0\x82\x01\x160\x81\xbd\xa0\x03\x02\x01\x02\x02\t\x00\xaf\xe5(q4\v\xd1>0\n\x06\b*\x86H\xce=\x04\x03\x020\x111\x0f0\r\x06\x03U\x04\x03\f\x06WebRTC0\x1e\x17\r180811035200Z\x17\r180911035200Z0\x111\x0f0\r\x06\x03U\x04\x03\f\x06WebRTC0Y0\x13\x06\a*\x86H\xce=\x02\x01\x06\b*\x86H\xce=\x03\x01\a\x03B\x00\x04\x80\x80\xe3H\xbdAF\x9c\xfbz}\xf3\x16go\xd7*\x06!\x17e\xa5\n\x0f\v\aRl\x87-π\t>\xd5ʣ\xf5\xa4\nr]\xd7KA\xb7\x9b\xdd\x19\xeec\fS\x13\xc8`\x1di\x83(l\x87\"\xc10\n\x06\b*\x86H\xce=\x04\x03\x02\x03H\x000E\x02!\x00\xd1:\n\x13\x1b©\xf2z\xbd=LT\x7f~\xf1r\x99j\f\aU\xc7\a\xb6\xa3\xe0H\xd8v-\xed\x02 \x05_\xc8\x18(\x18\xa6D\xa3ӵ\xb1W0L\xc3\xf1B\x1f\xad\xb0bc\xbf\xb4Q\xcd(\xbeK\xc9\xee\x16\xfe\xfd\x00\x00\x00\x00\x00\x00\x00\x12\x00-\x10\x00\x00!\x00\x02\x00\x00\x00\x00\x00! \xf7\xe2<\x97\xdfE\xa9n\x13\xcb>v\xb3~\xff^s\xe2\xae\xe0\xb6A])D=\v\xd2OW\x8b~\x16\xfe\xfd\x00\x00\x00\x00\x00\x00\x00\x13\x00X\x0f\x00\x00L\x00\x03\x00\x00\x00\x00\x00L\x04\x03\x00H0F\x02!\x00\xfd\xbbt걬\xa1S.j\xc0\xab&}[\x83\xa2K\xb4\xd5\xd7\xd5\x04\x93n'\x85\xe6㈲\xbd\x02!\x00\xf6\xa4W\xb9\xed\xd9\xea\xd5*\x9d\x0e\x9a\x19$\v:h\xb9V\x99Tl\x04O\x86<\xf84\x9b\xc8\x04b\x14\xfe\xfd\x00\x00\x00\x00\x00\x00\x00\x14\x00\x01\x01\x16\xfe\xfd\x00\x01\x00\x00\x00\x00\x00\x04\x000\x00\x01\x00\x00\x00\x00\x00\x04\n\xae$!\xe7\xd5Ic*}\xef\x8e\xd0h\x98\xc3ŵ?[\x81*\x96:9\xabl\xdd0;y\xbd\xb27\xf31L\x1d\xa2\x1b")a50da1417367eb74b5567c8264828b991bd8cee6e445582860492f99f1b4d676000066400000000000000000000027651436753726200335010ustar00rootroot00000000000000turn-2.1.0/internal/proto/testdata/fuzz/FuzzChannelDatago test fuzz v1 []byte("@\x00\x02#\x16\xfe\xfd\x00\x00\x00\x00\x00\x00\x00\x11\x01,\v\x00\x01 \x00\x01\x00\x00\x00\x00\x01 \x00\x01\x1d\x00\x01\x1a0\x82\x01\x160\x81\xbd\xa0\x03\x02\x01\x02\x02\t\x00\xaf\xe5(q4\v\xd1>0\n\x06\b*\x86H\xce=\x04\x03\x020\x111\x0f0\r\x06\x03U\x04\x03\f\x06WebRTC0\x1e\x17\r180811035200Z\x17\r180911035200Z0\x111\x0f0\r\x06\x03U\x04\x03\f\x06WebRTC0Y0\x13\x06\a*\x86H\xce=\x02\x01\x06\b*\x86H\xce=\x03\x01\a\x03B\x00\x04\x80\x80\xe3H\xbdAF\x9c\xfbz}\xf3\x16go\xd7*\x06!\x17e\xa5\n\x0f\v\aRl\x87-π\t>\xd5ʣ\xf5\xa4\nr]\xd7KA\xb7\x9b\xdd\x19\xeec\fS\x13\xc8`\x1di\x83(l\x87\"\xc10\n\x06\b*\x86H\xce=\x04\x03\x02\x03H\x000E\x02!\x00\xd1:\n\x13\x1b©\xf2z\xbd=LT\x7f~\xf1r\x99j\f\aU\xc7\a\xb6\xa3\xe0H\xd8v-\xed\x02 \x05_\xc8\x18(\x18\xa6D\xa3ӵ\xb1W0L\xc3\xf1B\x1f\xad\xb0bc\xbf\xb4Q\xcd(\xbeK\xc9\xee\x16\xfe\xfd\x00\x00\x00\x00\x00\x00\x00\x12\x00-\x10\x00\x00!\x00\x02\x00\x00\x00\x00\x00! \xf7\xe2<\x97\xdfE\xa9n\x13\xcb>v\xb3~\xff^s\xe2\xae\xe0\xb6A])D=\v\xd2OW\x8b~\x16\xfe\xfd\x00\x00\x00\x00\x00\x00\x00\x13\x00X\x0f\x00\x00L\x00\x03\x00\x00\x00\x00\x00L\x04\x03\x00H0F\x02!\x00\xfd\xbbt걬\xa1S.j\xc0\xab&}[\x83\xa2K\xb4\xd5\xd7\xd5\x04\x93n'\x85\xe6㈲\xbd\x02!\x00\xf6\xa4W\xb9\xed\xd9\xea\xd5*\x9d\x0e\x9a\x19$\v:h\xb9V\x99Tl\x04O\x86<\xf84\x9b\xc8\x04b\x14\xfe\xfd\x00\x00\x00\x00\x00\x00\x00\x14\x00\x01\x01\x16\xfe\xfd\x00\x01\x00\x00\x00\x00\x00\x04\x000\x00\x01\x00\x00\x00\x00\x00\x04\n\xae$!\xe7\xd5Ic*}\xef\x8e\xd0h\x98\xc3ŵ?[\x81*\x96:9\xabl\xdd0;y\xbd\xb27\xf31L\x1d\xa2\x1b")c1c1d815dfbe98c11d89ee9fb0436ede58f3451a3a5338cd4e0f6b1122fd600f000066400000000000000000000000341436753726200342660ustar00rootroot00000000000000turn-2.1.0/internal/proto/testdata/fuzz/FuzzChannelDatago test fuzz v1 []byte("*") turn-2.1.0/internal/server/000077500000000000000000000000001436753726200156435ustar00rootroot00000000000000turn-2.1.0/internal/server/errors.go000066400000000000000000000034531436753726200175130ustar00rootroot00000000000000package server import "errors" var ( errFailedToGenerateNonce = errors.New("failed to generate nonce") errFailedToSendError = errors.New("failed to send error message") errDuplicatedNonce = errors.New("duplicated Nonce generated, discarding request") errNoSuchUser = errors.New("no such user exists") errUnexpectedClass = errors.New("unexpected class") errUnexpectedMethod = errors.New("unexpected method") errFailedToHandle = errors.New("failed to handle") errUnhandledSTUNPacket = errors.New("unhandled STUN packet") errUnableToHandleChannelData = errors.New("unable to handle ChannelData") errFailedToCreateSTUNPacket = errors.New("failed to create stun message from packet") errFailedToCreateChannelData = errors.New("failed to create channel data from packet") errRelayAlreadyAllocatedForFiveTuple = errors.New("relay already allocated for 5-TUPLE") errRequestedTransportMustBeUDP = errors.New("RequestedTransport must be UDP") errNoDontFragmentSupport = errors.New("no support for DONT-FRAGMENT") errRequestWithReservationTokenAndEvenPort = errors.New("Request must not contain RESERVATION-TOKEN and EVEN-PORT") errNoAllocationFound = errors.New("no allocation found") errNoPermission = errors.New("unable to handle send-indication, no permission added") errShortWrite = errors.New("packet write smaller than packet") errNoSuchChannelBind = errors.New("no such channel bind") errFailedWriteSocket = errors.New("failed writing to socket") ) turn-2.1.0/internal/server/server.go000066400000000000000000000055431436753726200175070ustar00rootroot00000000000000// Package server implements the private API to implement a TURN server package server import ( "fmt" "net" "sync" "time" "github.com/pion/logging" "github.com/pion/stun" "github.com/pion/turn/v2/internal/allocation" "github.com/pion/turn/v2/internal/proto" ) // Request contains all the state needed to process a single incoming datagram type Request struct { // Current Request State Conn net.PacketConn SrcAddr net.Addr Buff []byte // Server State AllocationManager *allocation.Manager Nonces *sync.Map // User Configuration AuthHandler func(username string, realm string, srcAddr net.Addr) (key []byte, ok bool) Log logging.LeveledLogger Realm string ChannelBindTimeout time.Duration } // HandleRequest processes the give Request func HandleRequest(r Request) error { r.Log.Debugf("received %d bytes of udp from %s on %s", len(r.Buff), r.SrcAddr.String(), r.Conn.LocalAddr().String()) if proto.IsChannelData(r.Buff) { return handleDataPacket(r) } return handleTURNPacket(r) } func handleDataPacket(r Request) error { r.Log.Debugf("received DataPacket from %s", r.SrcAddr.String()) c := proto.ChannelData{Raw: r.Buff} if err := c.Decode(); err != nil { return fmt.Errorf("%w: %v", errFailedToCreateChannelData, err) } err := handleChannelData(r, &c) if err != nil { err = fmt.Errorf("%w from %v: %v", errUnableToHandleChannelData, r.SrcAddr, err) } return err } func handleTURNPacket(r Request) error { r.Log.Debug("handleTURNPacket") m := &stun.Message{Raw: append([]byte{}, r.Buff...)} if err := m.Decode(); err != nil { return fmt.Errorf("%w: %v", errFailedToCreateSTUNPacket, err) } h, err := getMessageHandler(m.Type.Class, m.Type.Method) if err != nil { return fmt.Errorf("%w %v-%v from %v: %v", errUnhandledSTUNPacket, m.Type.Method, m.Type.Class, r.SrcAddr, err) } err = h(r, m) if err != nil { return fmt.Errorf("%w %v-%v from %v: %v", errFailedToHandle, m.Type.Method, m.Type.Class, r.SrcAddr, err) } return nil } func getMessageHandler(class stun.MessageClass, method stun.Method) (func(r Request, m *stun.Message) error, error) { switch class { case stun.ClassIndication: switch method { case stun.MethodSend: return handleSendIndication, nil default: return nil, fmt.Errorf("%w: %s", errUnexpectedMethod, method) } case stun.ClassRequest: switch method { case stun.MethodAllocate: return handleAllocateRequest, nil case stun.MethodRefresh: return handleRefreshRequest, nil case stun.MethodCreatePermission: return handleCreatePermissionRequest, nil case stun.MethodChannelBind: return handleChannelBindRequest, nil case stun.MethodBinding: return handleBindingRequest, nil default: return nil, fmt.Errorf("%w: %s", errUnexpectedMethod, method) } default: return nil, fmt.Errorf("%w: %s", errUnexpectedClass, class) } } turn-2.1.0/internal/server/stun.go000066400000000000000000000007501436753726200171650ustar00rootroot00000000000000package server import ( "github.com/pion/stun" "github.com/pion/turn/v2/internal/ipnet" ) func handleBindingRequest(r Request, m *stun.Message) error { r.Log.Debugf("received BindingRequest from %s", r.SrcAddr.String()) ip, port, err := ipnet.AddrIPPort(r.SrcAddr) if err != nil { return err } attrs := buildMsg(m.TransactionID, stun.BindingSuccess, &stun.XORMappedAddress{ IP: ip, Port: port, }, stun.Fingerprint) return buildAndSend(r.Conn, r.SrcAddr, attrs...) } turn-2.1.0/internal/server/turn.go000066400000000000000000000325621436753726200171720ustar00rootroot00000000000000package server import ( "fmt" "net" "github.com/pion/stun" "github.com/pion/turn/v2/internal/allocation" "github.com/pion/turn/v2/internal/ipnet" "github.com/pion/turn/v2/internal/proto" ) // // https://tools.ietf.org/html/rfc5766#section-6.2 func handleAllocateRequest(r Request, m *stun.Message) error { r.Log.Debugf("received AllocateRequest from %s", r.SrcAddr.String()) // 1. The server MUST require that the request be authenticated. This // authentication MUST be done using the long-term credential // mechanism of [https://tools.ietf.org/html/rfc5389#section-10.2.2] // unless the client and server agree to use another mechanism through // some procedure outside the scope of this document. messageIntegrity, hasAuth, err := authenticateRequest(r, m, stun.MethodAllocate) if !hasAuth { return err } fiveTuple := &allocation.FiveTuple{ SrcAddr: r.SrcAddr, DstAddr: r.Conn.LocalAddr(), Protocol: allocation.UDP, } requestedPort := 0 reservationToken := "" badRequestMsg := buildMsg(m.TransactionID, stun.NewType(stun.MethodAllocate, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeBadRequest}) insufficientCapacityMsg := buildMsg(m.TransactionID, stun.NewType(stun.MethodAllocate, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeInsufficientCapacity}) // 2. The server checks if the 5-tuple is currently in use by an // existing allocation. If yes, the server rejects the request with // a 437 (Allocation Mismatch) error. if alloc := r.AllocationManager.GetAllocation(fiveTuple); alloc != nil { id, attrs := alloc.GetResponseCache() if id != m.TransactionID { msg := buildMsg(m.TransactionID, stun.NewType(stun.MethodAllocate, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeAllocMismatch}) return buildAndSendErr(r.Conn, r.SrcAddr, errRelayAlreadyAllocatedForFiveTuple, msg...) } // a retry allocation msg := buildMsg(m.TransactionID, stun.NewType(stun.MethodAllocate, stun.ClassSuccessResponse), append(attrs, messageIntegrity)...) return buildAndSend(r.Conn, r.SrcAddr, msg...) } // 3. The server checks if the request contains a REQUESTED-TRANSPORT // attribute. If the REQUESTED-TRANSPORT attribute is not included // or is malformed, the server rejects the request with a 400 (Bad // Request) error. Otherwise, if the attribute is included but // specifies a protocol other that UDP, the server rejects the // request with a 442 (Unsupported Transport Protocol) error. var requestedTransport proto.RequestedTransport if err = requestedTransport.GetFrom(m); err != nil { return buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) } else if requestedTransport.Protocol != proto.ProtoUDP { msg := buildMsg(m.TransactionID, stun.NewType(stun.MethodAllocate, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeUnsupportedTransProto}) return buildAndSendErr(r.Conn, r.SrcAddr, errRequestedTransportMustBeUDP, msg...) } // 4. The request may contain a DONT-FRAGMENT attribute. If it does, // but the server does not support sending UDP datagrams with the DF // bit set to 1 (see Section 12), then the server treats the DONT- // FRAGMENT attribute in the Allocate request as an unknown // comprehension-required attribute. if m.Contains(stun.AttrDontFragment) { msg := buildMsg(m.TransactionID, stun.NewType(stun.MethodAllocate, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeUnknownAttribute}, &stun.UnknownAttributes{stun.AttrDontFragment}) return buildAndSendErr(r.Conn, r.SrcAddr, errNoDontFragmentSupport, msg...) } // 5. The server checks if the request contains a RESERVATION-TOKEN // attribute. If yes, and the request also contains an EVEN-PORT // attribute, then the server rejects the request with a 400 (Bad // Request) error. Otherwise, it checks to see if the token is // valid (i.e., the token is in range and has not expired and the // corresponding relayed transport address is still available). If // the token is not valid for some reason, the server rejects the // request with a 508 (Insufficient Capacity) error. var reservationTokenAttr proto.ReservationToken if err = reservationTokenAttr.GetFrom(m); err == nil { var evenPort proto.EvenPort if err = evenPort.GetFrom(m); err == nil { return buildAndSendErr(r.Conn, r.SrcAddr, errRequestWithReservationTokenAndEvenPort, badRequestMsg...) } } // 6. The server checks if the request contains an EVEN-PORT attribute. // If yes, then the server checks that it can satisfy the request // (i.e., can allocate a relayed transport address as described // below). If the server cannot satisfy the request, then the // server rejects the request with a 508 (Insufficient Capacity) // error. var evenPort proto.EvenPort if err = evenPort.GetFrom(m); err == nil { var randomPort int randomPort, err = r.AllocationManager.GetRandomEvenPort() if err != nil { return buildAndSendErr(r.Conn, r.SrcAddr, err, insufficientCapacityMsg...) } requestedPort = randomPort reservationToken = randSeq(8) } // 7. At any point, the server MAY choose to reject the request with a // 486 (Allocation Quota Reached) error if it feels the client is // trying to exceed some locally defined allocation quota. The // server is free to define this allocation quota any way it wishes, // but SHOULD define it based on the username used to authenticate // the request, and not on the client's transport address. // 8. Also at any point, the server MAY choose to reject the request // with a 300 (Try Alternate) error if it wishes to redirect the // client to a different server. The use of this error code and // attribute follow the specification in [RFC5389]. lifetimeDuration := allocationLifeTime(m) a, err := r.AllocationManager.CreateAllocation( fiveTuple, r.Conn, requestedPort, lifetimeDuration) if err != nil { return buildAndSendErr(r.Conn, r.SrcAddr, err, insufficientCapacityMsg...) } // Once the allocation is created, the server replies with a success // response. The success response contains: // * An XOR-RELAYED-ADDRESS attribute containing the relayed transport // address. // * A LIFETIME attribute containing the current value of the time-to- // expiry timer. // * A RESERVATION-TOKEN attribute (if a second relayed transport // address was reserved). // * An XOR-MAPPED-ADDRESS attribute containing the client's IP address // and port (from the 5-tuple). srcIP, srcPort, err := ipnet.AddrIPPort(r.SrcAddr) if err != nil { return buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) } relayIP, relayPort, err := ipnet.AddrIPPort(a.RelayAddr) if err != nil { return buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) } responseAttrs := []stun.Setter{ &proto.RelayedAddress{ IP: relayIP, Port: relayPort, }, &proto.Lifetime{ Duration: lifetimeDuration, }, &stun.XORMappedAddress{ IP: srcIP, Port: srcPort, }, } if reservationToken != "" { r.AllocationManager.CreateReservation(reservationToken, relayPort) responseAttrs = append(responseAttrs, proto.ReservationToken([]byte(reservationToken))) } msg := buildMsg(m.TransactionID, stun.NewType(stun.MethodAllocate, stun.ClassSuccessResponse), append(responseAttrs, messageIntegrity)...) a.SetResponseCache(m.TransactionID, responseAttrs) return buildAndSend(r.Conn, r.SrcAddr, msg...) } func handleRefreshRequest(r Request, m *stun.Message) error { r.Log.Debugf("received RefreshRequest from %s", r.SrcAddr.String()) messageIntegrity, hasAuth, err := authenticateRequest(r, m, stun.MethodRefresh) if !hasAuth { return err } lifetimeDuration := allocationLifeTime(m) fiveTuple := &allocation.FiveTuple{ SrcAddr: r.SrcAddr, DstAddr: r.Conn.LocalAddr(), Protocol: allocation.UDP, } if lifetimeDuration != 0 { a := r.AllocationManager.GetAllocation(fiveTuple) if a == nil { return fmt.Errorf("%w %v:%v", errNoAllocationFound, r.SrcAddr, r.Conn.LocalAddr()) } a.Refresh(lifetimeDuration) } else { r.AllocationManager.DeleteAllocation(fiveTuple) } return buildAndSend(r.Conn, r.SrcAddr, buildMsg(m.TransactionID, stun.NewType(stun.MethodRefresh, stun.ClassSuccessResponse), []stun.Setter{ &proto.Lifetime{ Duration: lifetimeDuration, }, messageIntegrity, }...)...) } func handleCreatePermissionRequest(r Request, m *stun.Message) error { r.Log.Debugf("received CreatePermission from %s", r.SrcAddr.String()) a := r.AllocationManager.GetAllocation(&allocation.FiveTuple{ SrcAddr: r.SrcAddr, DstAddr: r.Conn.LocalAddr(), Protocol: allocation.UDP, }) if a == nil { return fmt.Errorf("%w %v:%v", errNoAllocationFound, r.SrcAddr, r.Conn.LocalAddr()) } messageIntegrity, hasAuth, err := authenticateRequest(r, m, stun.MethodCreatePermission) if !hasAuth { return err } addCount := 0 if err := m.ForEach(stun.AttrXORPeerAddress, func(m *stun.Message) error { var peerAddress proto.PeerAddress if err := peerAddress.GetFrom(m); err != nil { return err } if err := r.AllocationManager.GrantPermission(r.SrcAddr, peerAddress.IP); err != nil { r.Log.Infof("permission denied for client %s to peer %s", r.SrcAddr.String(), peerAddress.IP.String()) return err } r.Log.Debugf("adding permission for %s", fmt.Sprintf("%s:%d", peerAddress.IP.String(), peerAddress.Port)) a.AddPermission(allocation.NewPermission( &net.UDPAddr{ IP: peerAddress.IP, Port: peerAddress.Port, }, r.Log, )) addCount++ return nil }); err != nil { addCount = 0 } respClass := stun.ClassSuccessResponse if addCount == 0 { respClass = stun.ClassErrorResponse } return buildAndSend(r.Conn, r.SrcAddr, buildMsg(m.TransactionID, stun.NewType(stun.MethodCreatePermission, respClass), []stun.Setter{messageIntegrity}...)...) } func handleSendIndication(r Request, m *stun.Message) error { r.Log.Debugf("received SendIndication from %s", r.SrcAddr.String()) a := r.AllocationManager.GetAllocation(&allocation.FiveTuple{ SrcAddr: r.SrcAddr, DstAddr: r.Conn.LocalAddr(), Protocol: allocation.UDP, }) if a == nil { return fmt.Errorf("%w %v:%v", errNoAllocationFound, r.SrcAddr, r.Conn.LocalAddr()) } dataAttr := proto.Data{} if err := dataAttr.GetFrom(m); err != nil { return err } peerAddress := proto.PeerAddress{} if err := peerAddress.GetFrom(m); err != nil { return err } msgDst := &net.UDPAddr{IP: peerAddress.IP, Port: peerAddress.Port} if perm := a.GetPermission(msgDst); perm == nil { return fmt.Errorf("%w: %v", errNoPermission, msgDst) } l, err := a.RelaySocket.WriteTo(dataAttr, msgDst) if l != len(dataAttr) { return fmt.Errorf("%w %d != %d (expected) err: %v", errShortWrite, l, len(dataAttr), err) } return err } func handleChannelBindRequest(r Request, m *stun.Message) error { r.Log.Debugf("received ChannelBindRequest from %s", r.SrcAddr.String()) a := r.AllocationManager.GetAllocation(&allocation.FiveTuple{ SrcAddr: r.SrcAddr, DstAddr: r.Conn.LocalAddr(), Protocol: allocation.UDP, }) if a == nil { return fmt.Errorf("%w %v:%v", errNoAllocationFound, r.SrcAddr, r.Conn.LocalAddr()) } badRequestMsg := buildMsg(m.TransactionID, stun.NewType(stun.MethodChannelBind, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeBadRequest}) messageIntegrity, hasAuth, err := authenticateRequest(r, m, stun.MethodChannelBind) if !hasAuth { return err } var channel proto.ChannelNumber if err = channel.GetFrom(m); err != nil { return buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) } peerAddr := proto.PeerAddress{} if err = peerAddr.GetFrom(m); err != nil { return buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) } if err = r.AllocationManager.GrantPermission(r.SrcAddr, peerAddr.IP); err != nil { r.Log.Infof("permission denied for client %s to peer %s", r.SrcAddr.String(), peerAddr.IP.String()) unauthorizedRequestMsg := buildMsg(m.TransactionID, stun.NewType(stun.MethodChannelBind, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeUnauthorized}) return buildAndSendErr(r.Conn, r.SrcAddr, err, unauthorizedRequestMsg...) } r.Log.Debugf("binding channel %d to %s", channel, fmt.Sprintf("%s:%d", peerAddr.IP.String(), peerAddr.Port)) err = a.AddChannelBind(allocation.NewChannelBind( channel, &net.UDPAddr{IP: peerAddr.IP, Port: peerAddr.Port}, r.Log, ), r.ChannelBindTimeout) if err != nil { return buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) } return buildAndSend(r.Conn, r.SrcAddr, buildMsg(m.TransactionID, stun.NewType(stun.MethodChannelBind, stun.ClassSuccessResponse), []stun.Setter{messageIntegrity}...)...) } func handleChannelData(r Request, c *proto.ChannelData) error { r.Log.Debugf("received ChannelData from %s", r.SrcAddr.String()) a := r.AllocationManager.GetAllocation(&allocation.FiveTuple{ SrcAddr: r.SrcAddr, DstAddr: r.Conn.LocalAddr(), Protocol: allocation.UDP, }) if a == nil { return fmt.Errorf("%w %v:%v", errNoAllocationFound, r.SrcAddr, r.Conn.LocalAddr()) } channel := a.GetChannelByNumber(c.Number) if channel == nil { return fmt.Errorf("%w %x", errNoSuchChannelBind, uint16(c.Number)) } l, err := a.RelaySocket.WriteTo(c.Data, channel.Peer) if err != nil { return fmt.Errorf("%w: %s", errFailedWriteSocket, err.Error()) } else if l != len(c.Data) { return fmt.Errorf("%w %d != %d (expected)", errShortWrite, l, len(c.Data)) } return nil } turn-2.1.0/internal/server/turn_test.go000066400000000000000000000061171436753726200202260ustar00rootroot00000000000000//go:build !js // +build !js package server import ( "net" "sync" "testing" "time" "github.com/pion/logging" "github.com/pion/stun" "github.com/pion/turn/v2/internal/allocation" "github.com/pion/turn/v2/internal/proto" "github.com/stretchr/testify/assert" ) func TestAllocationLifeTime(t *testing.T) { t.Run("Parsing", func(t *testing.T) { lifetime := proto.Lifetime{ Duration: 5 * time.Second, } m := &stun.Message{} lifetimeDuration := allocationLifeTime(m) if lifetimeDuration != proto.DefaultLifetime { t.Errorf("Allocation lifetime should be default time duration") } assert.NoError(t, lifetime.AddTo(m)) lifetimeDuration = allocationLifeTime(m) if lifetimeDuration != lifetime.Duration { t.Errorf("Expect lifetimeDuration is %s, but %s", lifetime.Duration, lifetimeDuration) } }) // If lifetime is bigger than maximumLifetime t.Run("Overflow", func(t *testing.T) { lifetime := proto.Lifetime{ Duration: maximumAllocationLifetime * 2, } m2 := &stun.Message{} _ = lifetime.AddTo(m2) lifetimeDuration := allocationLifeTime(m2) if lifetimeDuration != proto.DefaultLifetime { t.Errorf("Expect lifetimeDuration is %s, but %s", proto.DefaultLifetime, lifetimeDuration) } }) t.Run("DeletionZeroLifetime", func(t *testing.T) { l, err := net.ListenPacket("udp4", "0.0.0.0:0") assert.NoError(t, err) defer func() { assert.NoError(t, l.Close()) }() logger := logging.NewDefaultLoggerFactory().NewLogger("turn") allocationManager, err := allocation.NewManager(allocation.ManagerConfig{ AllocatePacketConn: func(network string, requestedPort int) (net.PacketConn, net.Addr, error) { conn, listenErr := net.ListenPacket(network, "0.0.0.0:0") if err != nil { return nil, nil, listenErr } return conn, conn.LocalAddr(), nil }, AllocateConn: func(network string, requestedPort int) (net.Conn, net.Addr, error) { return nil, nil, nil }, LeveledLogger: logger, }) assert.NoError(t, err) staticKey := []byte("ABC") r := Request{ AllocationManager: allocationManager, Nonces: &sync.Map{}, Conn: l, SrcAddr: &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 5000}, Log: logger, AuthHandler: func(username string, realm string, srcAddr net.Addr) (key []byte, ok bool) { return staticKey, true }, } r.Nonces.Store(string(staticKey), time.Now()) fiveTuple := &allocation.FiveTuple{SrcAddr: r.SrcAddr, DstAddr: r.Conn.LocalAddr(), Protocol: allocation.UDP} _, err = r.AllocationManager.CreateAllocation(fiveTuple, r.Conn, 0, time.Hour) assert.NoError(t, err) assert.NotNil(t, r.AllocationManager.GetAllocation(fiveTuple)) m := &stun.Message{} assert.NoError(t, (proto.Lifetime{}).AddTo(m)) assert.NoError(t, (stun.MessageIntegrity(staticKey)).AddTo(m)) assert.NoError(t, (stun.Nonce(staticKey)).AddTo(m)) assert.NoError(t, (stun.Realm(staticKey)).AddTo(m)) assert.NoError(t, (stun.Username(staticKey)).AddTo(m)) assert.NoError(t, handleRefreshRequest(r, m)) assert.Nil(t, r.AllocationManager.GetAllocation(fiveTuple)) }) } turn-2.1.0/internal/server/util.go000066400000000000000000000104171436753726200171520ustar00rootroot00000000000000package server import ( "crypto/md5" //nolint:gosec,gci "fmt" "io" "math/rand" "net" "strconv" "time" "github.com/pion/stun" "github.com/pion/turn/v2/internal/proto" ) const ( maximumAllocationLifetime = time.Hour // https://tools.ietf.org/html/rfc5766#section-6.2 defines 3600 seconds recommendation nonceLifetime = time.Hour // https://tools.ietf.org/html/rfc5766#section-4 ) func randSeq(n int) string { letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") b := make([]rune, n) for i := range b { b[i] = letters[rand.Intn(len(letters))] //nolint:gosec } return string(b) } func buildNonce() (string, error) { /* #nosec */ h := md5.New() if _, err := io.WriteString(h, strconv.FormatInt(time.Now().Unix(), 10)); err != nil { return "", fmt.Errorf("%w: %v", errFailedToGenerateNonce, err) } if _, err := io.WriteString(h, strconv.FormatInt(rand.Int63(), 10)); err != nil { //nolint:gosec return "", fmt.Errorf("%w: %v", errFailedToGenerateNonce, err) } return fmt.Sprintf("%x", h.Sum(nil)), nil } func buildAndSend(conn net.PacketConn, dst net.Addr, attrs ...stun.Setter) error { msg, err := stun.Build(attrs...) if err != nil { return err } _, err = conn.WriteTo(msg.Raw, dst) return err } // Send a STUN packet and return the original error to the caller func buildAndSendErr(conn net.PacketConn, dst net.Addr, err error, attrs ...stun.Setter) error { if sendErr := buildAndSend(conn, dst, attrs...); sendErr != nil { err = fmt.Errorf("%w %v %v", errFailedToSendError, sendErr, err) } return err } func buildMsg(transactionID [stun.TransactionIDSize]byte, msgType stun.MessageType, additional ...stun.Setter) []stun.Setter { return append([]stun.Setter{&stun.Message{TransactionID: transactionID}, msgType}, additional...) } func authenticateRequest(r Request, m *stun.Message, callingMethod stun.Method) (stun.MessageIntegrity, bool, error) { respondWithNonce := func(responseCode stun.ErrorCode) (stun.MessageIntegrity, bool, error) { nonce, err := buildNonce() if err != nil { return nil, false, err } // Nonce has already been taken if _, keyCollision := r.Nonces.LoadOrStore(nonce, time.Now()); keyCollision { return nil, false, errDuplicatedNonce } return nil, false, buildAndSend(r.Conn, r.SrcAddr, buildMsg(m.TransactionID, stun.NewType(callingMethod, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: responseCode}, stun.NewNonce(nonce), stun.NewRealm(r.Realm), )...) } if !m.Contains(stun.AttrMessageIntegrity) { return respondWithNonce(stun.CodeUnauthorized) } nonceAttr := &stun.Nonce{} usernameAttr := &stun.Username{} realmAttr := &stun.Realm{} badRequestMsg := buildMsg(m.TransactionID, stun.NewType(callingMethod, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeBadRequest}) if err := nonceAttr.GetFrom(m); err != nil { return nil, false, buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) } // Assert Nonce exists and is not expired nonceCreationTime, nonceFound := r.Nonces.Load(string(*nonceAttr)) if !nonceFound { r.Nonces.Delete(nonceAttr) return respondWithNonce(stun.CodeStaleNonce) } if timeValue, ok := nonceCreationTime.(time.Time); !ok || time.Since(timeValue) >= nonceLifetime { r.Nonces.Delete(nonceAttr) return respondWithNonce(stun.CodeStaleNonce) } if err := realmAttr.GetFrom(m); err != nil { return nil, false, buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) } else if err := usernameAttr.GetFrom(m); err != nil { return nil, false, buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) } ourKey, ok := r.AuthHandler(usernameAttr.String(), realmAttr.String(), r.SrcAddr) if !ok { return nil, false, buildAndSendErr(r.Conn, r.SrcAddr, fmt.Errorf("%w %s", errNoSuchUser, usernameAttr.String()), badRequestMsg...) } if err := stun.MessageIntegrity(ourKey).Check(m); err != nil { return nil, false, buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) } return stun.MessageIntegrity(ourKey), true, nil } func allocationLifeTime(m *stun.Message) time.Duration { lifetimeDuration := proto.DefaultLifetime var lifetime proto.Lifetime if err := lifetime.GetFrom(m); err == nil { if lifetime.Duration < maximumAllocationLifetime { lifetimeDuration = lifetime.Duration } } return lifetimeDuration } turn-2.1.0/lt_cred.go000066400000000000000000000034111436753726200144630ustar00rootroot00000000000000package turn import ( //nolint:gci "crypto/hmac" "crypto/sha1" //nolint:gosec,gci "encoding/base64" "net" "strconv" "time" "github.com/pion/logging" ) // GenerateLongTermCredentials can be used to create credentials valid for [duration] time func GenerateLongTermCredentials(sharedSecret string, duration time.Duration) (string, string, error) { t := time.Now().Add(duration).Unix() username := strconv.FormatInt(t, 10) password, err := longTermCredentials(username, sharedSecret) return username, password, err } func longTermCredentials(username string, sharedSecret string) (string, error) { mac := hmac.New(sha1.New, []byte(sharedSecret)) _, err := mac.Write([]byte(username)) if err != nil { return "", err // Not sure if this will ever happen } password := mac.Sum(nil) return base64.StdEncoding.EncodeToString(password), nil } // NewLongTermAuthHandler returns a turn.AuthAuthHandler used with Long Term (or Time Windowed) Credentials. // https://tools.ietf.org/search/rfc5389#section-10.2 func NewLongTermAuthHandler(sharedSecret string, l logging.LeveledLogger) AuthHandler { if l == nil { l = logging.NewDefaultLoggerFactory().NewLogger("turn") } return func(username, realm string, srcAddr net.Addr) (key []byte, ok bool) { l.Tracef("Authentication username=%q realm=%q srcAddr=%v", username, realm, srcAddr) t, err := strconv.Atoi(username) if err != nil { l.Errorf("Invalid time-windowed username %q", username) return nil, false } if int64(t) < time.Now().Unix() { l.Errorf("Expired time-windowed username %q", username) return nil, false } password, err := longTermCredentials(username, sharedSecret) if err != nil { l.Error(err.Error()) return nil, false } return GenerateAuthKey(username, realm, password), true } } turn-2.1.0/lt_cred_test.go000066400000000000000000000033551436753726200155310ustar00rootroot00000000000000//go:build !js // +build !js package turn import ( "net" "testing" "time" "github.com/pion/logging" "github.com/stretchr/testify/assert" ) func TestLtCredMech(t *testing.T) { username := "1599491771" sharedSecret := "foobar" expectedPassword := "Tpz/nKkyvX/vMSLKvL4sbtBt8Vs=" //nolint:gosec actualPassword, _ := longTermCredentials(username, sharedSecret) if expectedPassword != actualPassword { t.Errorf("Expected %q, got %q", expectedPassword, actualPassword) } } func TestNewLongTermAuthHandler(t *testing.T) { const sharedSecret = "HELLO_WORLD" serverListener, err := net.ListenPacket("udp4", "0.0.0.0:3478") assert.NoError(t, err) server, err := NewServer(ServerConfig{ AuthHandler: NewLongTermAuthHandler(sharedSecret, nil), PacketConnConfigs: []PacketConnConfig{ { PacketConn: serverListener, RelayAddressGenerator: &RelayAddressGeneratorStatic{ RelayAddress: net.ParseIP("127.0.0.1"), Address: "0.0.0.0", }, }, }, Realm: "pion.ly", LoggerFactory: logging.NewDefaultLoggerFactory(), }) assert.NoError(t, err) conn, err := net.ListenPacket("udp4", "0.0.0.0:0") assert.NoError(t, err) username, password, err := GenerateLongTermCredentials(sharedSecret, time.Minute) assert.NoError(t, err) client, err := NewClient(&ClientConfig{ STUNServerAddr: "0.0.0.0:3478", TURNServerAddr: "0.0.0.0:3478", Conn: conn, Username: username, Password: password, LoggerFactory: logging.NewDefaultLoggerFactory(), }) assert.NoError(t, err) assert.NoError(t, client.Listen()) relayConn, err := client.Allocate() assert.NoError(t, err) client.Close() assert.NoError(t, relayConn.Close()) assert.NoError(t, conn.Close()) assert.NoError(t, server.Close()) } turn-2.1.0/relay_address_generator_none.go000066400000000000000000000026241436753726200207620ustar00rootroot00000000000000package turn import ( "fmt" "net" "strconv" "github.com/pion/transport/v2" "github.com/pion/transport/v2/stdnet" ) // RelayAddressGeneratorNone returns the listener with no modifications type RelayAddressGeneratorNone struct { // Address is passed to Listen/ListenPacket when creating the Relay Address string Net transport.Net } // Validate is called on server startup and confirms the RelayAddressGenerator is properly configured func (r *RelayAddressGeneratorNone) Validate() error { if r.Net == nil { var err error r.Net, err = stdnet.NewNet() if err != nil { return fmt.Errorf("failed to create network: %w", err) } } switch { case r.Address == "": return errListeningAddressInvalid default: return nil } } // AllocatePacketConn generates a new PacketConn to receive traffic on and the IP/Port to populate the allocation response with func (r *RelayAddressGeneratorNone) AllocatePacketConn(network string, requestedPort int) (net.PacketConn, net.Addr, error) { conn, err := r.Net.ListenPacket(network, r.Address+":"+strconv.Itoa(requestedPort)) if err != nil { return nil, nil, err } return conn, conn.LocalAddr(), nil } // AllocateConn generates a new Conn to receive traffic on and the IP/Port to populate the allocation response with func (r *RelayAddressGeneratorNone) AllocateConn(network string, requestedPort int) (net.Conn, net.Addr, error) { return nil, nil, errTODO } turn-2.1.0/relay_address_generator_range.go000066400000000000000000000054341436753726200211210ustar00rootroot00000000000000package turn import ( "fmt" "net" "github.com/pion/randutil" "github.com/pion/transport/v2" "github.com/pion/transport/v2/stdnet" ) // RelayAddressGeneratorPortRange can be used to only allocate connections inside a defined port range. // Similar to the RelayAddressGeneratorStatic a static ip address can be set. type RelayAddressGeneratorPortRange struct { // RelayAddress is the IP returned to the user when the relay is created RelayAddress net.IP // MinPort the minimum port to allocate MinPort uint16 // MaxPort the maximum (inclusive) port to allocate MaxPort uint16 // MaxRetries the amount of tries to allocate a random port in the defined range MaxRetries int // Rand the random source of numbers Rand randutil.MathRandomGenerator // Address is passed to Listen/ListenPacket when creating the Relay Address string Net transport.Net } // Validate is called on server startup and confirms the RelayAddressGenerator is properly configured func (r *RelayAddressGeneratorPortRange) Validate() error { if r.Net == nil { var err error r.Net, err = stdnet.NewNet() if err != nil { return fmt.Errorf("failed to create network: %w", err) } } if r.Rand == nil { r.Rand = randutil.NewMathRandomGenerator() } if r.MaxRetries == 0 { r.MaxRetries = 10 } switch { case r.MinPort == 0: return errMinPortNotZero case r.MaxPort == 0: return errMaxPortNotZero case r.RelayAddress == nil: return errRelayAddressInvalid case r.Address == "": return errListeningAddressInvalid default: return nil } } // AllocatePacketConn generates a new PacketConn to receive traffic on and the IP/Port to populate the allocation response with func (r *RelayAddressGeneratorPortRange) AllocatePacketConn(network string, requestedPort int) (net.PacketConn, net.Addr, error) { if requestedPort != 0 { conn, err := r.Net.ListenPacket(network, fmt.Sprintf("%s:%d", r.Address, requestedPort)) if err != nil { return nil, nil, err } relayAddr, ok := conn.LocalAddr().(*net.UDPAddr) if !ok { return nil, nil, errNilConn } relayAddr.IP = r.RelayAddress return conn, relayAddr, nil } for try := 0; try < r.MaxRetries; try++ { port := r.MinPort + uint16(r.Rand.Intn(int((r.MaxPort+1)-r.MinPort))) conn, err := r.Net.ListenPacket(network, fmt.Sprintf("%s:%d", r.Address, port)) if err != nil { continue } relayAddr, ok := conn.LocalAddr().(*net.UDPAddr) if !ok { return nil, nil, errNilConn } relayAddr.IP = r.RelayAddress return conn, relayAddr, nil } return nil, nil, errMaxRetriesExceeded } // AllocateConn generates a new Conn to receive traffic on and the IP/Port to populate the allocation response with func (r *RelayAddressGeneratorPortRange) AllocateConn(network string, requestedPort int) (net.Conn, net.Addr, error) { return nil, nil, errTODO } turn-2.1.0/relay_address_generator_static.go000066400000000000000000000035721436753726200213150ustar00rootroot00000000000000package turn import ( "fmt" "net" "strconv" "github.com/pion/transport/v2" "github.com/pion/transport/v2/stdnet" ) // RelayAddressGeneratorStatic can be used to return static IP address each time a relay is created. // This can be used when you have a single static IP address that you want to use type RelayAddressGeneratorStatic struct { // RelayAddress is the IP returned to the user when the relay is created RelayAddress net.IP // Address is passed to Listen/ListenPacket when creating the Relay Address string Net transport.Net } // Validate is called on server startup and confirms the RelayAddressGenerator is properly configured func (r *RelayAddressGeneratorStatic) Validate() error { if r.Net == nil { var err error r.Net, err = stdnet.NewNet() if err != nil { return fmt.Errorf("failed to create network: %w", err) } } switch { case r.RelayAddress == nil: return errRelayAddressInvalid case r.Address == "": return errListeningAddressInvalid default: return nil } } // AllocatePacketConn generates a new PacketConn to receive traffic on and the IP/Port to populate the allocation response with func (r *RelayAddressGeneratorStatic) AllocatePacketConn(network string, requestedPort int) (net.PacketConn, net.Addr, error) { conn, err := r.Net.ListenPacket(network, r.Address+":"+strconv.Itoa(requestedPort)) if err != nil { return nil, nil, err } // Replace actual listening IP with the user requested one of RelayAddressGeneratorStatic relayAddr, ok := conn.LocalAddr().(*net.UDPAddr) if !ok { return nil, nil, errNilConn } relayAddr.IP = r.RelayAddress return conn, relayAddr, nil } // AllocateConn generates a new Conn to receive traffic on and the IP/Port to populate the allocation response with func (r *RelayAddressGeneratorStatic) AllocateConn(network string, requestedPort int) (net.Conn, net.Addr, error) { return nil, nil, errTODO } turn-2.1.0/renovate.json000066400000000000000000000001731436753726200152400ustar00rootroot00000000000000{ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "github>pion/renovate-config" ] } turn-2.1.0/server.go000066400000000000000000000114201436753726200143540ustar00rootroot00000000000000// Package turn contains the public API for pion/turn, a toolkit for building TURN clients and servers package turn import ( "fmt" "net" "sync" "time" "github.com/pion/logging" "github.com/pion/turn/v2/internal/allocation" "github.com/pion/turn/v2/internal/proto" "github.com/pion/turn/v2/internal/server" ) const ( defaultInboundMTU = 1600 ) // Server is an instance of the Pion TURN Server type Server struct { log logging.LeveledLogger authHandler AuthHandler realm string channelBindTimeout time.Duration nonces *sync.Map packetConnConfigs []PacketConnConfig listenerConfigs []ListenerConfig allocationManagers []*allocation.Manager inboundMTU int } // NewServer creates the Pion TURN server // //nolint:gocognit func NewServer(config ServerConfig) (*Server, error) { if err := config.validate(); err != nil { return nil, err } loggerFactory := config.LoggerFactory if loggerFactory == nil { loggerFactory = logging.NewDefaultLoggerFactory() } mtu := defaultInboundMTU if config.InboundMTU != 0 { mtu = config.InboundMTU } s := &Server{ log: loggerFactory.NewLogger("turn"), authHandler: config.AuthHandler, realm: config.Realm, channelBindTimeout: config.ChannelBindTimeout, packetConnConfigs: config.PacketConnConfigs, listenerConfigs: config.ListenerConfigs, nonces: &sync.Map{}, inboundMTU: mtu, } if s.channelBindTimeout == 0 { s.channelBindTimeout = proto.DefaultLifetime } for _, cfg := range s.packetConnConfigs { am, err := s.createAllocationManager(cfg.RelayAddressGenerator, cfg.PermissionHandler) if err != nil { return nil, fmt.Errorf("failed to create AllocationManager: %w", err) } go s.readPacketConn(cfg, am) } for _, cfg := range s.listenerConfigs { am, err := s.createAllocationManager(cfg.RelayAddressGenerator, cfg.PermissionHandler) if err != nil { return nil, fmt.Errorf("failed to create AllocationManager: %w", err) } go s.readListener(cfg, am) } return s, nil } // AllocationCount returns the number of active allocations. It can be used to drain the server before closing func (s *Server) AllocationCount() int { allocs := 0 for _, am := range s.allocationManagers { allocs += am.AllocationCount() } return allocs } // Close stops the TURN Server. It cleans up any associated state and closes all connections it is managing func (s *Server) Close() error { var errors []error for _, cfg := range s.packetConnConfigs { if err := cfg.PacketConn.Close(); err != nil { errors = append(errors, err) } } for _, cfg := range s.listenerConfigs { if err := cfg.Listener.Close(); err != nil { errors = append(errors, err) } } if len(errors) == 0 { return nil } err := errFailedToClose for _, e := range errors { err = fmt.Errorf("%s; close error (%w) ", err, e) } return err } func (s *Server) readPacketConn(p PacketConnConfig, am *allocation.Manager) { s.readLoop(p.PacketConn, am) if err := am.Close(); err != nil { s.log.Errorf("Failed to close AllocationManager: %s", err) } } func (s *Server) readListener(l ListenerConfig, am *allocation.Manager) { defer func() { if err := am.Close(); err != nil { s.log.Errorf("Failed to close AllocationManager: %s", err) } }() for { conn, err := l.Listener.Accept() if err != nil { s.log.Debugf("Failed to accept: %s", err) return } go s.readLoop(NewSTUNConn(conn), am) } } func (s *Server) createAllocationManager(addrGenerator RelayAddressGenerator, handler PermissionHandler) (*allocation.Manager, error) { if handler == nil { handler = DefaultPermissionHandler } am, err := allocation.NewManager(allocation.ManagerConfig{ AllocatePacketConn: addrGenerator.AllocatePacketConn, AllocateConn: addrGenerator.AllocateConn, PermissionHandler: handler, LeveledLogger: s.log, }) if err != nil { return am, err } s.allocationManagers = append(s.allocationManagers, am) return am, err } func (s *Server) readLoop(p net.PacketConn, allocationManager *allocation.Manager) { buf := make([]byte, s.inboundMTU) for { n, addr, err := p.ReadFrom(buf) switch { case err != nil: s.log.Debugf("exit read loop on error: %s", err.Error()) return case n >= s.inboundMTU: s.log.Debugf("Read bytes exceeded MTU, packet is possibly truncated") } if err := server.HandleRequest(server.Request{ Conn: p, SrcAddr: addr, Buff: buf[:n], Log: s.log, AuthHandler: s.authHandler, Realm: s.realm, AllocationManager: allocationManager, ChannelBindTimeout: s.channelBindTimeout, Nonces: s.nonces, }); err != nil { s.log.Errorf("error when handling datagram: %v", err) } } } turn-2.1.0/server_config.go000066400000000000000000000112571436753726200157110ustar00rootroot00000000000000package turn import ( "crypto/md5" //nolint:gosec,gci "fmt" "net" "strings" "time" "github.com/pion/logging" ) // RelayAddressGenerator is used to generate a RelayAddress when creating an allocation. // You can use one of the provided ones or provide your own. type RelayAddressGenerator interface { // Validate confirms that the RelayAddressGenerator is properly initialized Validate() error // Allocate a PacketConn (UDP) RelayAddress AllocatePacketConn(network string, requestedPort int) (net.PacketConn, net.Addr, error) // Allocate a Conn (TCP) RelayAddress AllocateConn(network string, requestedPort int) (net.Conn, net.Addr, error) } // PermissionHandler is a callback to filter incoming CreatePermission and ChannelBindRequest // requests based on the client IP address and port and the peer IP address the client intends to // connect to. If the client is behind a NAT then the filter acts on the server reflexive // ("mapped") address instead of the real client IP address and port. Note that TURN permissions // are per-allocation and per-peer-IP-address, to mimic the address-restricted filtering mechanism // of NATs that comply with [RFC4787], see https://tools.ietf.org/html/rfc5766#section-2.3. type PermissionHandler func(clientAddr net.Addr, peerIP net.IP) (ok bool) // DefaultPermissionHandler is convince function that grants permission to all peers func DefaultPermissionHandler(clientAddr net.Addr, peerIP net.IP) (ok bool) { return true } // PacketConnConfig is a single net.PacketConn to listen/write on. This will be used for UDP listeners type PacketConnConfig struct { PacketConn net.PacketConn // When an allocation is generated the RelayAddressGenerator // creates the net.PacketConn and returns the IP/Port it is available at RelayAddressGenerator RelayAddressGenerator // PermissionHandler is a callback to filter peer addresses. Can be set as nil, in which // case the DefaultPermissionHandler is automatically instantiated to admit all peer // connections PermissionHandler PermissionHandler } func (c *PacketConnConfig) validate() error { if c.PacketConn == nil { return errConnUnset } if c.RelayAddressGenerator == nil { return errRelayAddressGeneratorUnset } return c.RelayAddressGenerator.Validate() } // ListenerConfig is a single net.Listener to accept connections on. This will be used for TCP, TLS and DTLS listeners type ListenerConfig struct { Listener net.Listener // When an allocation is generated the RelayAddressGenerator // creates the net.PacketConn and returns the IP/Port it is available at RelayAddressGenerator RelayAddressGenerator // PermissionHandler is a callback to filter peer addresses. Can be set as nil, in which // case the DefaultPermissionHandler is automatically instantiated to admit all peer // connections PermissionHandler PermissionHandler } func (c *ListenerConfig) validate() error { if c.Listener == nil { return errListenerUnset } if c.RelayAddressGenerator == nil { return errRelayAddressGeneratorUnset } return c.RelayAddressGenerator.Validate() } // AuthHandler is a callback used to handle incoming auth requests, allowing users to customize Pion TURN with custom behavior type AuthHandler func(username, realm string, srcAddr net.Addr) (key []byte, ok bool) // GenerateAuthKey is a convenience function to easily generate keys in the format used by AuthHandler func GenerateAuthKey(username, realm, password string) []byte { // #nosec h := md5.New() fmt.Fprint(h, strings.Join([]string{username, realm, password}, ":")) return h.Sum(nil) } // ServerConfig configures the Pion TURN Server type ServerConfig struct { // PacketConnConfigs and ListenerConfigs are a list of all the turn listeners // Each listener can have custom behavior around the creation of Relays PacketConnConfigs []PacketConnConfig ListenerConfigs []ListenerConfig // LoggerFactory must be set for logging from this server. LoggerFactory logging.LoggerFactory // Realm sets the realm for this server Realm string // AuthHandler is a callback used to handle incoming auth requests, allowing users to customize Pion TURN with custom behavior AuthHandler AuthHandler // ChannelBindTimeout sets the lifetime of channel binding. Defaults to 10 minutes. ChannelBindTimeout time.Duration // Sets the server inbound MTU(Maximum transmition unit). Defaults to 1600 bytes. InboundMTU int } func (s *ServerConfig) validate() error { if len(s.PacketConnConfigs) == 0 && len(s.ListenerConfigs) == 0 { return errNoAvailableConns } for _, s := range s.PacketConnConfigs { if err := s.validate(); err != nil { return err } } for _, s := range s.ListenerConfigs { if err := s.validate(); err != nil { return err } } return nil } turn-2.1.0/server_test.go000066400000000000000000000444331436753726200154250ustar00rootroot00000000000000//go:build !js // +build !js package turn import ( "fmt" "net" "testing" "time" "github.com/pion/logging" "github.com/pion/transport/v2/test" "github.com/pion/transport/v2/vnet" "github.com/pion/turn/v2/internal/proto" "github.com/stretchr/testify/assert" ) func TestServer(t *testing.T) { lim := test.TimeOut(time.Second * 30) defer lim.Stop() report := test.CheckRoutines(t) defer report() loggerFactory := logging.NewDefaultLoggerFactory() credMap := map[string][]byte{ "user": GenerateAuthKey("user", "pion.ly", "pass"), } t.Run("simple", func(t *testing.T) { udpListener, err := net.ListenPacket("udp4", "0.0.0.0:3478") assert.NoError(t, err) server, err := NewServer(ServerConfig{ AuthHandler: func(username, realm string, srcAddr net.Addr) (key []byte, ok bool) { if pw, ok := credMap[username]; ok { return pw, true } return nil, false }, PacketConnConfigs: []PacketConnConfig{ { PacketConn: udpListener, RelayAddressGenerator: &RelayAddressGeneratorStatic{ RelayAddress: net.ParseIP("127.0.0.1"), Address: "0.0.0.0", }, }, }, Realm: "pion.ly", LoggerFactory: loggerFactory, }) assert.NoError(t, err) assert.Equal(t, proto.DefaultLifetime, server.channelBindTimeout, "should match") conn, err := net.ListenPacket("udp4", "0.0.0.0:0") assert.NoError(t, err) client, err := NewClient(&ClientConfig{ Conn: conn, LoggerFactory: loggerFactory, }) assert.NoError(t, err) assert.NoError(t, client.Listen()) _, err = client.SendBindingRequestTo(&net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 3478}) assert.NoError(t, err, "should succeed") client.Close() assert.NoError(t, conn.Close()) assert.NoError(t, server.Close()) }) t.Run("default inboundMTU", func(t *testing.T) { udpListener, err := net.ListenPacket("udp4", "0.0.0.0:3478") assert.NoError(t, err) server, err := NewServer(ServerConfig{ LoggerFactory: loggerFactory, PacketConnConfigs: []PacketConnConfig{ { PacketConn: udpListener, RelayAddressGenerator: &RelayAddressGeneratorStatic{ RelayAddress: net.ParseIP("127.0.0.1"), Address: "0.0.0.0", }, }, }, }) assert.NoError(t, err) assert.Equal(t, server.inboundMTU, defaultInboundMTU) assert.NoError(t, server.Close()) }) t.Run("Set inboundMTU", func(t *testing.T) { udpListener, err := net.ListenPacket("udp4", "0.0.0.0:3478") assert.NoError(t, err) server, err := NewServer(ServerConfig{ InboundMTU: 2000, LoggerFactory: loggerFactory, PacketConnConfigs: []PacketConnConfig{ { PacketConn: udpListener, RelayAddressGenerator: &RelayAddressGeneratorStatic{ RelayAddress: net.ParseIP("127.0.0.1"), Address: "0.0.0.0", }, }, }, }) assert.NoError(t, err) assert.Equal(t, server.inboundMTU, 2000) assert.NoError(t, server.Close()) }) t.Run("Filter on client address and peer IP", func(t *testing.T) { udpListener, err := net.ListenPacket("udp4", "0.0.0.0:3478") assert.NoError(t, err) server, err := NewServer(ServerConfig{ AuthHandler: func(username, realm string, srcAddr net.Addr) (key []byte, ok bool) { if pw, ok := credMap[username]; ok { return pw, true } return nil, false }, PacketConnConfigs: []PacketConnConfig{ { PacketConn: udpListener, RelayAddressGenerator: &RelayAddressGeneratorStatic{ RelayAddress: net.ParseIP("127.0.0.1"), Address: "0.0.0.0", }, PermissionHandler: func(src net.Addr, peer net.IP) bool { return src.String() == "127.0.0.1:54321" && peer.Equal(net.ParseIP("127.0.0.4")) }, }, }, Realm: "pion.ly", LoggerFactory: loggerFactory, }) assert.NoError(t, err) // enforce corrent client IP and port conn, err := net.ListenPacket("udp4", "127.0.0.1:54321") assert.NoError(t, err) client, err := NewClient(&ClientConfig{ STUNServerAddr: "127.0.0.1:3478", TURNServerAddr: "127.0.0.1:3478", Conn: conn, Username: "user", Password: "pass", Realm: "pion.ly", LoggerFactory: loggerFactory, }) assert.NoError(t, err) assert.NoError(t, client.Listen()) relayConn, err := client.Allocate() assert.NoError(t, err) whiteAddr, errA := net.ResolveUDPAddr("udp", "127.0.0.4:12345") assert.NoError(t, errA, "should succeed") blackAddr, errB1 := net.ResolveUDPAddr("udp", "127.0.0.5:12345") assert.NoError(t, errB1, "should succeed") // explicit CreatePermission err = client.CreatePermission(whiteAddr) assert.NoError(t, err, "grant permission for whitelisted peer") err = client.CreatePermission(blackAddr) assert.ErrorContains(t, err, "error", "deny permission for blacklisted peer address") err = client.CreatePermission(whiteAddr, whiteAddr) assert.NoError(t, err, "grant permission for repeated whitelisted peer addresses") err = client.CreatePermission(blackAddr) assert.ErrorContains(t, err, "error", "deny permission for repeated blacklisted peer address") // rg0now: isn't this a cornercase in the spec? err = client.CreatePermission(whiteAddr, blackAddr) assert.ErrorContains(t, err, "error", "deny permission for mixed whitelisted and blacklisted peers") // implicit CreatePermission for ChannelBindRequests: WriteTo always tries to bind a channel _, err = relayConn.WriteTo([]byte("Hello"), whiteAddr) assert.NoError(t, err, "write to whitelisted peer address succeeds - 1") _, err = relayConn.WriteTo([]byte("Hello"), blackAddr) assert.ErrorContains(t, err, "error", "write to blacklisted peer address fails - 1") _, err = relayConn.WriteTo([]byte("Hello"), whiteAddr) assert.NoError(t, err, "write to whitelisted peer address succeeds - 2") _, err = relayConn.WriteTo([]byte("Hello"), blackAddr) assert.ErrorContains(t, err, "error", "write to blacklisted peer address fails - 2") _, err = relayConn.WriteTo([]byte("Hello"), whiteAddr) assert.NoError(t, err, "write to whitelisted peer address succeeds - 3") _, err = relayConn.WriteTo([]byte("Hello"), blackAddr) assert.ErrorContains(t, err, "error", "write to blacklisted peer address fails - 3") // let the previous transaction terminate time.Sleep(200 * time.Millisecond) assert.NoError(t, relayConn.Close()) client.Close() assert.NoError(t, conn.Close()) // enforce filtered source address conn2, err := net.ListenPacket("udp4", "127.0.0.133:54321") assert.NoError(t, err) client2, err := NewClient(&ClientConfig{ STUNServerAddr: "127.0.0.1:3478", TURNServerAddr: "127.0.0.1:3478", Conn: conn2, Username: "user", Password: "pass", Realm: "pion.ly", LoggerFactory: loggerFactory, }) assert.NoError(t, err) assert.NoError(t, client2.Listen()) relayConn2, err := client2.Allocate() assert.NoError(t, err) // explicit CreatePermission err = client2.CreatePermission(whiteAddr) assert.ErrorContains(t, err, "error", "deny permission from filtered source to whitelisted peer") err = client2.CreatePermission(blackAddr) assert.ErrorContains(t, err, "error", "deny permission from filtered source to blacklisted peer") // implicit CreatePermission for ChannelBindRequests: WriteTo always tries to bind a channel _, err = relayConn2.WriteTo([]byte("Hello"), whiteAddr) assert.ErrorContains(t, err, "error", "write from filtered source to whitelisted peer fails - 1") _, err = relayConn2.WriteTo([]byte("Hello"), blackAddr) assert.ErrorContains(t, err, "error", "write from filtered source to blacklisted peer fails - 1") _, err = relayConn2.WriteTo([]byte("Hello"), whiteAddr) assert.ErrorContains(t, err, "error", "write from filtered source to whitelisted peer fails - 2") _, err = relayConn2.WriteTo([]byte("Hello"), blackAddr) assert.ErrorContains(t, err, "error", "write from filtered source to blacklisted peer fails - 2") _, err = relayConn2.WriteTo([]byte("Hello"), whiteAddr) assert.ErrorContains(t, err, "error", "write from filtered source to whitelisted peer fails - 3") _, err = relayConn2.WriteTo([]byte("Hello"), blackAddr) assert.ErrorContains(t, err, "error", "write from filtered source to blacklisted peer fails - 3") // let the previous transaction terminate time.Sleep(200 * time.Millisecond) assert.NoError(t, relayConn2.Close()) client2.Close() assert.NoError(t, conn2.Close()) assert.NoError(t, server.Close()) }) } type VNet struct { wan *vnet.Router net0 *vnet.Net // net (0) on the WAN net1 *vnet.Net // net (1) on the WAN netL0 *vnet.Net // net (0) on the LAN server *Server } func (v *VNet) Close() error { if err := v.server.Close(); err != nil { return err } return v.wan.Stop() } func buildVNet() (*VNet, error) { loggerFactory := logging.NewDefaultLoggerFactory() // WAN wan, err := vnet.NewRouter(&vnet.RouterConfig{ CIDR: "0.0.0.0/0", LoggerFactory: loggerFactory, }) if err != nil { return nil, err } net0, err := vnet.NewNet(&vnet.NetConfig{ StaticIP: "1.2.3.4", // will be assigned to eth0 }) if err != nil { return nil, err } err = wan.AddNet(net0) if err != nil { return nil, err } net1, err := vnet.NewNet(&vnet.NetConfig{ StaticIP: "1.2.3.5", // will be assigned to eth0 }) if err != nil { return nil, err } err = wan.AddNet(net1) if err != nil { return nil, err } // LAN lan, err := vnet.NewRouter(&vnet.RouterConfig{ StaticIP: "5.6.7.8", // this router's external IP on eth0 CIDR: "192.168.0.0/24", NATType: &vnet.NATType{ MappingBehavior: vnet.EndpointIndependent, FilteringBehavior: vnet.EndpointIndependent, }, LoggerFactory: loggerFactory, }) if err != nil { return nil, err } netL0, err := vnet.NewNet(&vnet.NetConfig{}) if err != nil { return nil, err } if err = lan.AddNet(netL0); err != nil { return nil, err } if err = wan.AddRouter(lan); err != nil { return nil, err } if err = wan.Start(); err != nil { return nil, err } // start server... credMap := map[string][]byte{"user": GenerateAuthKey("user", "pion.ly", "pass")} udpListener, err := net0.ListenPacket("udp4", "0.0.0.0:3478") if err != nil { return nil, err } server, err := NewServer(ServerConfig{ AuthHandler: func(username, realm string, srcAddr net.Addr) (key []byte, ok bool) { if pw, ok := credMap[username]; ok { return pw, true } return nil, false }, Realm: "pion.ly", PacketConnConfigs: []PacketConnConfig{ { PacketConn: udpListener, RelayAddressGenerator: &RelayAddressGeneratorNone{ Address: "1.2.3.4", Net: net0, }, }, }, LoggerFactory: loggerFactory, }) if err != nil { return nil, err } // register host names err = wan.AddHost("stun.pion.ly", "1.2.3.4") if err != nil { return nil, err } err = wan.AddHost("turn.pion.ly", "1.2.3.4") if err != nil { return nil, err } err = wan.AddHost("echo.pion.ly", "1.2.3.5") if err != nil { return nil, err } return &VNet{ wan: wan, net0: net0, net1: net1, netL0: netL0, server: server, }, nil } func TestServerVNet(t *testing.T) { lim := test.TimeOut(time.Second * 30) defer lim.Stop() report := test.CheckRoutines(t) defer report() loggerFactory := logging.NewDefaultLoggerFactory() log := loggerFactory.NewLogger("test") t.Run("SendBindingRequest", func(t *testing.T) { v, err := buildVNet() assert.NoError(t, err) defer func() { assert.NoError(t, v.Close()) }() lconn, err := v.netL0.ListenPacket("udp4", "0.0.0.0:0") assert.NoError(t, err, "should succeed") defer func() { assert.NoError(t, lconn.Close()) }() log.Debug("creating a client.") client, err := NewClient(&ClientConfig{ STUNServerAddr: "1.2.3.4:3478", Conn: lconn, Net: v.netL0, LoggerFactory: loggerFactory, }) assert.NoError(t, err, "should succeed") assert.NoError(t, client.Listen(), "should succeed") defer client.Close() log.Debug("sending a binding request.") reflAddr, err := client.SendBindingRequest() assert.NoError(t, err) log.Debugf("mapped-address: %v", reflAddr.String()) udpAddr, ok := reflAddr.(*net.UDPAddr) assert.True(t, ok) // The mapped-address should have IP address that was assigned // to the LAN router. assert.True(t, udpAddr.IP.Equal(net.IPv4(5, 6, 7, 8)), "should match") }) t.Run("Echo via relay", func(t *testing.T) { v, err := buildVNet() assert.NoError(t, err) lconn, err := v.netL0.ListenPacket("udp4", "0.0.0.0:0") assert.NoError(t, err) log.Debug("creating a client.") client, err := NewClient(&ClientConfig{ STUNServerAddr: "stun.pion.ly:3478", TURNServerAddr: "turn.pion.ly:3478", Username: "user", Password: "pass", Conn: lconn, Net: v.netL0, LoggerFactory: loggerFactory, }) assert.NoError(t, err) assert.NoError(t, client.Listen()) log.Debug("sending a binding request.") conn, err := client.Allocate() assert.NoError(t, err) log.Debugf("laddr: %s", conn.LocalAddr().String()) echoConn, err := v.net1.ListenPacket("udp4", "1.2.3.5:5678") assert.NoError(t, err) // ensure allocation is counted assert.Equal(t, 1, v.server.AllocationCount()) go func() { buf := make([]byte, 1600) for { n, from, err2 := echoConn.ReadFrom(buf) if err2 != nil { break } // verify the message was received from the relay address assert.Equal(t, conn.LocalAddr().String(), from.String(), "should match") assert.Equal(t, "Hello", string(buf[:n]), "should match") // echo the data _, err2 = echoConn.WriteTo(buf[:n], from) assert.NoError(t, err2) } }() buf := make([]byte, 1600) for i := 0; i < 10; i++ { log.Debug("sending \"Hello\"..") _, err = conn.WriteTo([]byte("Hello"), echoConn.LocalAddr()) assert.NoError(t, err) _, from, err2 := conn.ReadFrom(buf) assert.NoError(t, err2) // verify the message was received from the relay address assert.Equal(t, echoConn.LocalAddr().String(), from.String(), "should match") time.Sleep(100 * time.Millisecond) } time.Sleep(100 * time.Millisecond) client.Close() assert.NoError(t, conn.Close(), "should succeed") assert.NoError(t, echoConn.Close(), "should succeed") assert.NoError(t, lconn.Close(), "should succeed") assert.NoError(t, v.Close(), "should succeed") }) } func TestConsumeSingleTURNFrame(t *testing.T) { type testCase struct { data []byte err error } cases := map[string]testCase{ "channel data": {data: []byte{0x40, 0x01, 0x00, 0x08, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, err: nil}, "partial data less than channel header": {data: []byte{1}, err: errIncompleteTURNFrame}, "partial stun message": {data: []byte{0x0, 0x16, 0x02, 0xDC, 0x21, 0x12, 0xA4, 0x42, 0x0, 0x0, 0x0}, err: errIncompleteTURNFrame}, "stun message": {data: []byte{0x0, 0x16, 0x00, 0x02, 0x21, 0x12, 0xA4, 0x42, 0xf7, 0x43, 0x81, 0xa3, 0xc9, 0xcd, 0x88, 0x89, 0x70, 0x58, 0xac, 0x73, 0x0, 0x0}}, } for name, cs := range cases { c := cs t.Run(name, func(t *testing.T) { n, e := consumeSingleTURNFrame(c.data) assert.Equal(t, c.err, e) if e == nil { assert.Equal(t, len(c.data), n) } }) } } func RunBenchmarkServer(b *testing.B, clientNum int) { loggerFactory := logging.NewDefaultLoggerFactory() credMap := map[string][]byte{ "user": GenerateAuthKey("user", "pion.ly", "pass"), } testSeq := []byte("benchmark-data") // Setup server serverAddr, err := net.ResolveUDPAddr("udp4", "0.0.0.0:3478") if err != nil { b.Fatalf("Failed to resolve server address: %s", err) } serverConn, err := net.ListenPacket(serverAddr.Network(), serverAddr.String()) if err != nil { b.Fatalf("Failed to allocate server listener at %s:%s", serverAddr.Network(), serverAddr.String()) } defer serverConn.Close() //nolint:errcheck server, err := NewServer(ServerConfig{ AuthHandler: func(username, realm string, srcAddr net.Addr) (key []byte, ok bool) { if pw, ok := credMap[username]; ok { return pw, true } return nil, false }, PacketConnConfigs: []PacketConnConfig{{ PacketConn: serverConn, RelayAddressGenerator: &RelayAddressGeneratorStatic{ RelayAddress: net.ParseIP("127.0.0.1"), Address: "0.0.0.0", }, }}, Realm: "pion.ly", LoggerFactory: loggerFactory, }) if err != nil { b.Fatalf("Failed to start server: %s", err) } defer server.Close() //nolint:errcheck // Create a sink sinkAddr, err := net.ResolveUDPAddr("udp4", "0.0.0.0:65432") if err != nil { b.Fatalf("Failed to resolve sink address: %s", err) } sink, err := net.ListenPacket(sinkAddr.Network(), sinkAddr.String()) if err != nil { b.Fatalf("Failed to allocate sink: %s", err) } defer sink.Close() //nolint:errcheck go func() { buf := make([]byte, 1600) for { // Ignore "use of closed network connection" errors if _, _, listenErr := sink.ReadFrom(buf); listenErr != nil { return } // Do not care about received data } }() // Setup client(s) clients := make([]net.PacketConn, clientNum) for i := 0; i < clientNum; i++ { clientConn, listenErr := net.ListenPacket("udp4", "0.0.0.0:0") if listenErr != nil { b.Fatalf("Failed to allocate socket for client %d: %s", i+1, err) } defer clientConn.Close() //nolint:errcheck client, err := NewClient(&ClientConfig{ STUNServerAddr: serverAddr.String(), TURNServerAddr: serverAddr.String(), Conn: clientConn, Username: "user", Password: "pass", Realm: "pion.ly", LoggerFactory: loggerFactory, }) if err != nil { b.Fatalf("Failed to start client %d: %s", i+1, err) } defer client.Close() if listenErr := client.Listen(); listenErr != nil { b.Fatalf("Client %d cannot listen: %s", i+1, listenErr) } // create an allocation turnConn, err := client.Allocate() if err != nil { b.Fatalf("Client %d cannot create allocation: %s", i+1, err) } defer turnConn.Close() //nolint:errcheck clients[i] = turnConn } // Run benchmark for i := 0; i < b.N; i++ { for i := 0; i < clientNum; i++ { if _, err := clients[i].WriteTo(testSeq, sinkAddr); err != nil { b.Fatalf("Client %d cannot send to TURN server: %s", i+1, err) } } } } // BenchmarkServer will benchmark the server with multiple simultaneous client connections func BenchmarkServer(b *testing.B) { for i := 1; i <= 4; i++ { b.Run(fmt.Sprintf("client_num_%d", i), func(b *testing.B) { RunBenchmarkServer(b, i) }) } } turn-2.1.0/stun_conn.go000066400000000000000000000064251436753726200150650ustar00rootroot00000000000000package turn import ( "encoding/binary" "errors" "net" "time" "github.com/pion/stun" "github.com/pion/turn/v2/internal/proto" ) var ( errInvalidTURNFrame = errors.New("data is not a valid TURN frame, no STUN or ChannelData found") errIncompleteTURNFrame = errors.New("data contains incomplete STUN or TURN frame") ) // STUNConn wraps a net.Conn and implements // net.PacketConn by being STUN aware and // packetizing the stream type STUNConn struct { nextConn net.Conn buff []byte } const ( stunHeaderSize = 20 channelDataLengthSize = 2 channelDataNumberSize = channelDataLengthSize channelDataHeaderSize = channelDataLengthSize + channelDataNumberSize channelDataPadding = 4 ) // Given a buffer give the last offset of the TURN frame // If the buffer isn't a valid STUN or ChannelData packet // or the length doesn't match return false func consumeSingleTURNFrame(p []byte) (int, error) { // Too short to determine if ChannelData or STUN if len(p) < 9 { return 0, errIncompleteTURNFrame } var datagramSize uint16 switch { case stun.IsMessage(p): datagramSize = binary.BigEndian.Uint16(p[2:4]) + stunHeaderSize case proto.ChannelNumber(binary.BigEndian.Uint16(p[0:2])).Valid(): datagramSize = binary.BigEndian.Uint16(p[channelDataNumberSize:channelDataHeaderSize]) if paddingOverflow := (datagramSize + channelDataPadding) % channelDataPadding; paddingOverflow != 0 { datagramSize = (datagramSize + channelDataPadding) - paddingOverflow } datagramSize += channelDataHeaderSize case len(p) < stunHeaderSize: return 0, errIncompleteTURNFrame default: return 0, errInvalidTURNFrame } if len(p) < int(datagramSize) { return 0, errIncompleteTURNFrame } return int(datagramSize), nil } // ReadFrom implements ReadFrom from net.PacketConn func (s *STUNConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { // First pass any buffered data from previous reads n, err = consumeSingleTURNFrame(s.buff) if errors.Is(err, errInvalidTURNFrame) { return 0, nil, err } else if err == nil { copy(p, s.buff[:n]) s.buff = s.buff[n:] return n, s.nextConn.RemoteAddr(), nil } // Then read from the nextConn, appending to our buff n, err = s.nextConn.Read(p) if err != nil { return 0, nil, err } s.buff = append(s.buff, append([]byte{}, p[:n]...)...) return s.ReadFrom(p) } // WriteTo implements WriteTo from net.PacketConn func (s *STUNConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { return s.nextConn.Write(p) } // Close implements Close from net.PacketConn func (s *STUNConn) Close() error { return s.nextConn.Close() } // LocalAddr implements LocalAddr from net.PacketConn func (s *STUNConn) LocalAddr() net.Addr { return s.nextConn.LocalAddr() } // SetDeadline implements SetDeadline from net.PacketConn func (s *STUNConn) SetDeadline(t time.Time) error { return s.nextConn.SetDeadline(t) } // SetReadDeadline implements SetReadDeadline from net.PacketConn func (s *STUNConn) SetReadDeadline(t time.Time) error { return s.nextConn.SetReadDeadline(t) } // SetWriteDeadline implements SetWriteDeadline from net.PacketConn func (s *STUNConn) SetWriteDeadline(t time.Time) error { return s.nextConn.SetWriteDeadline(t) } // NewSTUNConn creates a STUNConn func NewSTUNConn(nextConn net.Conn) *STUNConn { return &STUNConn{nextConn: nextConn} }