From d0ae589ad37fe54d267a8ca0ab57176ead3acc33 Mon Sep 17 00:00:00 2001 From: Nicolas Chatelain Date: Mon, 25 Dec 2023 22:30:16 +0100 Subject: [PATCH] fix #49, implement udp listeners --- cmd/agent/main.go | 159 +++++++++++++++++++++++++++------------- cmd/proxy/app/app.go | 139 +++++++++++++++++++++-------------- doc/logo.png | Bin 14913 -> 17929 bytes doc/tnplogo.png | Bin 14700 -> 0 bytes pkg/protocol/packets.go | 4 + pkg/proxy/agent.go | 6 +- pkg/relay/relay.go | 37 +++++++--- 7 files changed, 226 insertions(+), 119 deletions(-) delete mode 100644 doc/tnplogo.png diff --git a/cmd/agent/main.go b/cmd/agent/main.go index ea539a7..fa63fdc 100644 --- a/cmd/agent/main.go +++ b/cmd/agent/main.go @@ -21,7 +21,7 @@ import ( ) var listenerConntrack map[int32]net.Conn -var listenerMap map[int32]net.Listener +var listenerMap map[int32]interface{} var connTrackID int32 var listenerID int32 @@ -59,7 +59,7 @@ func main() { var conn net.Conn listenerConntrack = make(map[int32]net.Conn) - listenerMap = make(map[int32]net.Listener) + listenerMap = make(map[int32]interface{}) for { var err error @@ -146,6 +146,25 @@ func (s *Listener) Close() error { return s.Listener.Close() } +// UDPListener is the base class implementing UDP listeners for Ligolo +type UDPListener struct { + *net.UDPConn +} + +// NewUDPListener register a new UDP listener +func NewUDPListener(network string, addr string) (UDPListener, error) { + udpaddr, err := net.ResolveUDPAddr(network, addr) + if err != nil { + return UDPListener{}, nil + } + + udplis, err := net.ListenUDP(network, udpaddr) + if err != nil { + return UDPListener{}, err + } + return UDPListener{udplis}, err +} + func handleConn(conn net.Conn) { decoder := protocol.NewDecoder(conn) if err := decoder.Decode(); err != nil { @@ -251,7 +270,12 @@ func handleConn(conn net.Conn) { var err error if lis, ok := listenerMap[closeRequest.ListenerID]; ok { - err = lis.Close() + if l, ok := lis.(net.Listener); ok { + l.Close() + } + if l, ok := lis.(*net.UDPConn); ok { + l.Close() + } } else { err = errors.New("invalid listener id") } @@ -276,12 +300,27 @@ func handleConn(conn net.Conn) { connTrackChan := make(chan int32) stopChan := make(chan error) - listener, err := NewListener(listenRequest.Network, listenRequest.Address) - if err != nil { + if listenRequest.Network == "tcp" { + listener, err := NewListener(listenRequest.Network, listenRequest.Address) + if err != nil { + listenerResponse := protocol.ListenerResponsePacket{ + ListenerID: 0, + Err: true, + ErrString: err.Error(), + } + if err := encoder.Encode(protocol.Envelope{ + Type: protocol.MessageListenerResponse, + Payload: listenerResponse, + }); err != nil { + logrus.Error(err) + } + return + } + listenerMap[listenerID] = listener.Listener listenerResponse := protocol.ListenerResponsePacket{ - ListenerID: 0, - Err: true, - ErrString: err.Error(), + ListenerID: listenerID, + Err: false, + ErrString: "", } if err := encoder.Encode(protocol.Envelope{ Type: protocol.MessageListenerResponse, @@ -289,59 +328,75 @@ func handleConn(conn net.Conn) { }); err != nil { logrus.Error(err) } - return - } - - listenerResponse := protocol.ListenerResponsePacket{ - ListenerID: listenerID, - Err: false, - ErrString: "", - } - listenerMap[listenerID] = listener.Listener - listenerID++ - - if err := encoder.Encode(protocol.Envelope{ - Type: protocol.MessageListenerResponse, - Payload: listenerResponse, - }); err != nil { - logrus.Error(err) - } - - go func() { - if err := listener.ListenAndServe(connTrackChan); err != nil { - stopChan <- err - } - }() - defer listener.Close() - - for { - var bindResponse protocol.ListenerBindReponse - select { - case err := <-stopChan: - logrus.Error(err) - bindResponse = protocol.ListenerBindReponse{ - SockID: 0, - Err: true, - ErrString: err.Error(), + go func() { + if err := listener.ListenAndServe(connTrackChan); err != nil { + stopChan <- err + } + }() + defer listener.Close() + + } else if listenRequest.Network == "udp" { + udplistener, err := NewUDPListener(listenRequest.Network, listenRequest.Address) + if err != nil { + listenerResponse := protocol.ListenerResponsePacket{ + ListenerID: 0, + Err: true, + ErrString: err.Error(), } - case connTrackID := <-connTrackChan: - bindResponse = protocol.ListenerBindReponse{ - SockID: connTrackID, - Err: false, + if err := encoder.Encode(protocol.Envelope{ + Type: protocol.MessageListenerResponse, + Payload: listenerResponse, + }); err != nil { + logrus.Error(err) } + return + } + listenerMap[listenerID] = udplistener.UDPConn + listenerResponse := protocol.ListenerResponsePacket{ + ListenerID: listenerID, + Err: false, + ErrString: "", } - if err := encoder.Encode(protocol.Envelope{ - Type: protocol.MessageListenerBindResponse, - Payload: bindResponse, + Type: protocol.MessageListenerResponse, + Payload: listenerResponse, }); err != nil { logrus.Error(err) } + go relay.StartRelay(conn, udplistener) + } - if bindResponse.Err { - break - } + listenerID++ + if listenRequest.Network == "tcp" { + for { + var bindResponse protocol.ListenerBindReponse + select { + case err := <-stopChan: + logrus.Error(err) + bindResponse = protocol.ListenerBindReponse{ + SockID: 0, + Err: true, + ErrString: err.Error(), + } + case connTrackID := <-connTrackChan: + bindResponse = protocol.ListenerBindReponse{ + SockID: connTrackID, + Err: false, + } + } + if err := encoder.Encode(protocol.Envelope{ + Type: protocol.MessageListenerBindResponse, + Payload: bindResponse, + }); err != nil { + logrus.Error(err) + } + + if bindResponse.Err { + break + } + + } } case protocol.MessageListenerSockRequest: sockRequest := e.(protocol.ListenerSockRequestPacket) diff --git a/cmd/proxy/app/app.go b/cmd/proxy/app/app.go index 2443016..05591e3 100644 --- a/cmd/proxy/app/app.go +++ b/cmd/proxy/app/app.go @@ -16,6 +16,7 @@ import ( "strconv" "strings" "sync" + "time" ) var AgentList map[int]proxy.LigoloAgent @@ -228,11 +229,11 @@ func Run(stackSettings netstack.StackSettings) { t := table.NewWriter() t.SetStyle(table.StyleLight) t.SetTitle("Active listeners") - t.AppendHeader(table.Row{"#", "Agent", "Agent listener address", "Proxy redirect address"}) + t.AppendHeader(table.Row{"#", "Agent", "Network", "Agent listener address", "Proxy redirect address"}) ListenerListMutex.Lock() for id, listener := range ListenerList { - t.AppendRow(table.Row{id, listener.Agent.Name, listener.ListenerAddr, listener.RedirectAddr}) + t.AppendRow(table.Row{id, listener.Agent.String(), listener.Network, listener.ListenerAddr, listener.RedirectAddr}) } ListenerListMutex.Unlock() c.App.Println(t.Render()) @@ -379,79 +380,107 @@ func Run(stackSettings netstack.StackSettings) { ListenerListMutex.Lock() ListenerList[proxy.ListenerCounter] = listener ListenerListMutex.Unlock() + currentListener := proxy.ListenerCounter proxy.ListenerCounter++ - go func() { - for { - // Wait for BindResponses - if err := protocolDecoder.Decode(); err != nil { - if err == io.EOF { - // Listener closed. + if netProto == "udp" { + + // relay connections + go func() { + for { + // Check if deleted + if _, ok := ListenerList[currentListener]; !ok { return } - logrus.Error(err) - return - } - - // We received a new BindResponse! - response := protocolDecoder.Envelope.Payload.(protocol.ListenerBindReponse) - - if err := response.Err; err != false { - logrus.Error(response.ErrString) - return - } - - logrus.Debugf("New socket opened : %d", response.SockID) - - // relay connection - go func(sockID int32) { - - forwarderSession, err := CurrentAgent.Session.Open() + // Dial the "to" target + lconn, err := net.Dial(netProto, c.Flags.String("to")) if err != nil { logrus.Error(err) return } - - protocolEncoder := protocol.NewEncoder(forwarderSession) - protocolDecoder := protocol.NewDecoder(forwarderSession) - - // Request socket access - socketRequestPacket := protocol.ListenerSockRequestPacket{SockID: sockID} - if err := protocolEncoder.Encode(protocol.Envelope{ - Type: protocol.MessageListenerSockRequest, - Payload: socketRequestPacket, - }); err != nil { - logrus.Error(err) - return + // Relay conn + err = relay.StartPacketRelay(lconn, yamuxConnectionSession) + if err != nil { + logrus.WithFields(logrus.Fields{"listener": ListenerList[currentListener].String(), "error": err}).Error("Failed to relay UDP connection. Make sure that you are 'to' host is listening! Retrying...") } + time.Sleep(2 * time.Second) + } + }() + } + + if netProto == "tcp" { + go func() { + for { + // Wait for BindResponses if err := protocolDecoder.Decode(); err != nil { + if err == io.EOF { + // Listener closed. + return + } logrus.Error(err) return } - response := protocolDecoder.Envelope.Payload - if err := response.(protocol.ListenerSockResponsePacket).Err; err != false { - logrus.Error(response.(protocol.ListenerSockResponsePacket).ErrString) - return - } - // Got socket access! - - logrus.Debug("Listener relay established!") + // We received a new BindResponse! + response := protocolDecoder.Envelope.Payload.(protocol.ListenerBindReponse) - // Dial the "to" target - lconn, err := net.Dial(netProto, c.Flags.String("to")) - if err != nil { - logrus.Error(err) + if err := response.Err; err != false { + logrus.Error(response.ErrString) return } - // relay connections - relay.StartRelay(lconn, forwarderSession) - }(response.SockID) + logrus.Debugf("New socket opened : %d", response.SockID) + + // relay connection + go func(sockID int32) { + + forwarderSession, err := CurrentAgent.Session.Open() + if err != nil { + logrus.Error(err) + return + } + + protocolEncoder := protocol.NewEncoder(forwarderSession) + protocolDecoder := protocol.NewDecoder(forwarderSession) + + // Request socket access + socketRequestPacket := protocol.ListenerSockRequestPacket{SockID: sockID} + if err := protocolEncoder.Encode(protocol.Envelope{ + Type: protocol.MessageListenerSockRequest, + Payload: socketRequestPacket, + }); err != nil { + logrus.Error(err) + return + } + if err := protocolDecoder.Decode(); err != nil { + logrus.Error(err) + return + } + + response := protocolDecoder.Envelope.Payload + if err := response.(protocol.ListenerSockResponsePacket).Err; err != false { + logrus.Error(response.(protocol.ListenerSockResponsePacket).ErrString) + return + } + // Got socket access! + + logrus.Debug("Listener relay established!") + + // Dial the "to" target + lconn, err := net.Dial(netProto, c.Flags.String("to")) + if err != nil { + logrus.Error(err) + return + } + + // relay connections + relay.StartRelay(lconn, forwarderSession) + }(response.SockID) - } + } - }() + }() + } return nil }, diff --git a/doc/logo.png b/doc/logo.png index 882b4c48ae3ff763755cd0025c6624489b30382c..902a5a2eb55e146b09eec24f175cec1882993606 100644 GIT binary patch literal 17929 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 H7zh1-C0Rt$ literal 14913 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 diff --git a/doc/tnplogo.png b/doc/tnplogo.png deleted file mode 100644 index c981fb7cf18ce1401e266933f5e1eec69157cc52..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14700 zcmeHtbySpJ_b=Tk2-1j1$1p<0WAR<8XB>Rvb-)D8u~HnIRy^~ z^?CCt(-RGioIOC_*jpFk%k1vy26J$RGJE^GLz$s|4lpz{zi(xkW}d7~#0s}w_boAG zB1m~+SwG|LBL~i8(iIoRL8nQNO1BJE3`{av-94B-tY2;Cp8dwP;(9D;dTBpp`dv+f zS1aVfX5aMhfSXn}m8JcT;Qnb}|Bw*5-R<2$?&$Sjzw5TAx7W5$mNX~YbH&DTTOXdh zB7L#Hy`IZIzkU{dv~MWr)x5c0akzThCbcJ-yP)CnA}?6@r0LVqoP(%)RnxDK(!-6b zimUmh*uw`7?|ENK?AH1@3-nL?PDqdju6-e2_aA^geXX@8vEJ(CZ$(!KA809HQCNK_ za%R&rz|pi5q{?~m;;N0xzrtz%q@JjJrz_7yH1pv3?S%E?WlV1+* z!(+Jd!1VKBh@kwQ(bAUL@)F~c)ri(pUhm3wD5JCc`xCLAkH0X@*07(fG=4e4wfZXA zoNo1kW4fG2k$_iOL-i%whxK`d+8;8JY}_LXNAECooqVV=>s~0j;fQ|!7W94~(7F8g z5jbBq)X|}EoQ^vU_QCLA91)ghqBd#jRMPg$5g}D3=u%fVWi~j!Q|MIDcCx6{Af-eD z-m=e67I0P{FnrMOIMhrG%1U$642I1xqjy48{)fLQWENSMQV{&QNGro=V5-k zM_X~q$R42euoEN{px9SUz_34tj+T=TcZhR*Hmop`3Vr_$MrD#O!sj6M)jp50|KyAB zeu(#;+u3g3ROn>Gl`r^Ae*1RA~O6UprOG$n}xKZH^8;|TEc|qP{ z+@pw()sN|rLW{H_Io)DGOVA#P($b0b$zKXo-NTm{o=9(ZgYV)r=Q}nr}X^UFG}ZB-!QhIe!b}FRnJit3QgN zomzg;`H>?8!XeB<_e6)Gq&b#8QxyE1l-osM4uZ}Z*R0wQW*57-sZOH=Ilvc;40)=> z@hdI8<)@me#TFy_ft7IO((kTBG=Q1UM z?iIS7`XyqbGrb-^az{3YZX&mqN!hUmI5z3-j5`!wg?#v668B#76IX}E$L@GzlSlzQ zfl5Sq_llbrn@y}`@k?w3x)z73>^#nJ!R%^HuW`JHgQ}h`M0yR-yjSvZH|6nRt1#kJ z3|iT}rJQ~_`#Cv>p&;tWEoCu?Q}Vh9j;ymzYw(;t$-%)Hp~=T`p*8T2negABa=zE@ zYUap;R}K@Q-ZbG~(ZwHD(hy6h<;w^wS1VaP4zs5ldETsqB$mKraTJ_bv4RVJt(_Y- zWxDzC3e7?NHfLAK{Gs&^#G3>***8u#_FqOWM0)s<3f9%#90mEJZ?|k>n~S0YJEA$I zWT@kc<(F*aNA0KvCEOZ7%mw|Q){Q+D0^C6+%xzU4f^1How^eR1eti6yq83B+BJvmn zF0MTG|7h|_K5$%r>VQt9K-nlm@IQnXEYFR*l&B-f*k%w>vDuJf< zX@U!jgtLo#Fyn#`Uhv9Nh5YhuN-6#gOy1RS^qkX0JgH}JgG7)=rV07I+t7le*&yI? zLRu?$70WODD^FCtRMDg!wRb>e+*K57X}THjoN{o`qnl1X4t#hgUPYNUHkzVosi2Pn z{(((0uN{mm02C$4#`tyx=Gf~NL1GWxh)DJDyg--#;L?^=LJjPct*@^n)e6J9kNv^+ zi5~jkoQTgnIn+h7`U^-oJZ=z6h_b>7I39(})kaeG z2Vd(TAjbD&!)1qKo<~h~gZO6pRV-viz;9y)|xw*#X)0w5&U-c?$OH$T0Jbb!VyxG>CTwd_+A(`8?-s;)?Je# zkZ1?vF6(;<+Sa`@B%mapw5SzDw#yglSP_NHjifa4j6r@R!g#uP!sjWS)c3JALqDTE z^1KmWk>~)BFs@Kvprs8Lj50(7Bp%|9<#@-r0$sD}buAyHMgR+Dm{r}(!8R-u-R3gy z8bz4rAH;TkQui`f;9?*R@ET-n-OR9IS**!mah@z#lGm(F*(3=Jh{qks=9|(E%^or% zl(?S)Ln!X{`+pXfOUaU+nf5pcAsg#$1+WnR#9!l%oOu!DoiFpFu`0n8XHaqPBO@E3 zmcvdg^5Phs&TxZF=4vjLk~~ zN7d{%#hr3>^CT7bfta76WW~7Kw2kJ2Bzj)=F%`Zq{!r#Q?m93XPR3u=$1NFJ={tDb z(isSn2;g3vx)%3ux_E*h4yl~A2d;kOx_;%4Q-0(z zR3@Mf+GLpWD3{D+EuF1s{agB*eCIjvR+AZPVj! zW^eGAqsJ6`{V1jCnb*eLq3o5GP%>!@mDkB&P9+#)9Hez6TZ-?oaUsciH2aLAN6)%o zAn!(J9Zhs7y?dEBVf3JGkxa3KeWl52df>ZvpL1f(&)67Jj=-}R5E;>Mu&vs{Q)^2= z`X|Ci`Bg;sE49YPKOKt{Bs}yg1qxUHfE!4KbM_HVxpTW^paniHMwg)Gu>e8)0&%eY z0qZ4VGc4b5=Gl;)Qx9k-e#K{TQeIpDr`+RMcZFcDuP5GWaCmnHnwRG*+JlVLJ7%32uD7l$oOo%$#&jJus0H9gV1f`|xdNd^%BnsR-`NS+)_# zt+gRt{w``16F!@!L26IS19Au-p%lSJ1M8||J@i)&WX3pimC`j4 zgmcPBW#B8kusm)jeRt(u<+m zRgtmU%NnpL*HwHE=`CZ9rrgw?-Pw@-y?VGYP@MzN^0CF4&Q`A%xGq69rO7itOsay> zN8-iPU({!+EP9YO^#=DBO;h4j$cT}xGG?~dJbB^;{ERFOe?XO}=JjG18}@lvMbJ_< zrHa5$Ehe3IOdW5B0GER^LRheRQ6T_5kBJB?wWgRRC}Lw$b#8+YTUtC@q3+Om`RS8b z6$Ph@A#P~N2H4a8bb|J0&d5rvm^^UMSZL30DDqx-_1VGN~KJju2hYZjayox%EQ#W7A;!%HfGOc2z`9fuzQlEKFwBKi3*^^!6 z-JRvo$Vv}TjMzPJwLc5W(eGY}BHc7kosVSLUq=t{7@3ga+oNP%E*sqgP@p|wmZ=Oo z!!ybxGfF(lbegA3Ad7O2TH_-%?XK-L?$Dfc=-VvlOWA7xsoz)MA(H;7UuT_AsfvlM z*2wS7N{d0y=udaC=<|4hKos82;<{%&;B4iaaLFYwB+qaU9f$J)jvOXL8DmzBk%$8J zxf$TXDnY+-uNhsVp|P;x{&26hweCuT++w*uZ33xKZ|;z2Iv~2l@8I)5LXY`!=?5lE zML_3VuSF;&LPPWweo-F<4ssP~`}V|!Tuf|FGPWR(rPmtfRA-}jkBSU4&Lzwj&o|bZ zl}hylKQP$-e8%VDQ5BonL+7>87+gM#xO;m9dhIyhP3+=cp*>Out@i#FIISVIj3htR z6wmZkAEF_|QaY>*sXC=eP<|0>_}qqB3V|%xc}kBtE&k3%oG-dxj?qThn7*01K^2Zk zK9WSkl6a1T&BLfD38hLQw&VNuETyV`_$}75f1er|g=K1`4}#b@l~2+;J;=ZMlLpDR zK%8D4j*n|$_jv7+H)-Xo4FS|D1QOP!pXHq8>%{2d3AyE(CYINR6>61wSQpfrt6 z)kW@)ySVa~RzqMK+^Yj{crdm9dpo65P&bsqEmE62tWgy5W`z9L2P@smi1!8}ep!t% zn4i3|SXg=ZKNG3prb*%|px{!fO)z_IT|jw4_dmrjXZu+Ys1mArerGZxzcA|SdaL8lr-6-FA#{5WNN4Awmh4p_D^%h z4@H%2q8;Sqv{mHf{!yht8OjcRE~(u8guchhtWbfA#D{D%B`+ZYTZde!OqQH0$~4aa zH%+bMt%_Xg_wO8yRP5%}=m7sy zjk*ZWcjE7x8fxpUTJ(^|Di5XD89^HnpOVdAS%j>bd(Vu39x1X(YSBNWN?_pe1#r`F z9a4Iz>j-^0gIgGix_KCK3r#h%heAXv%6PnTV3aC6Hux-9pNsR72zWj@OezaJe}5en z8#;QJ_#?I70OwI3#z*akVj&08=L=;f8QJb>UHES=d+McQ!j}0)OXp6{s$4|0!jK^M z!n*589KexEKwDYQv#@rTZ0I|N)9wM328rfh^-UUo7n~mcW{+t*>CA!VRo>SWHA!b0BE?&LG2xs13aO6 z0h;=@0Zz7Jb}Z6T1QLE=6o3oV8^Y}8;_T`L_LF4!g9}DI-!=2IF#i$pc9LW<*3f2_ zbMu5U3-Sr_0eBVs9DIN*QUuHro^~*>uDs&kAy99UEcV{s?qGg?UteE7Ul5<0C!AkE zOiYX)0OSV(c~KI)UjDA$5IPbp8{Loh?7q8R~)(^+H({_&1YEDjM4V)VQMn?%?A7M+-&v zze##K!2U(nzxj68^Cz6YI)YOFC+@#V|0DK4!YC;X4Y0hME#l5S6?sXPJNsZeH(Lig z@SjH!ArKT~3$*1G0N8?f1x0Ovykf#aw!9)hA*i4&)K)|QCh`|36<05Bh^sC14hjX% z=YYb2!hj%As4$Qh28F>Cmc0h>GA1FIpu%er%3j{Ts4lWQl zl;7PI{-@)PaIlQFiX;n=5Ad%RZD)u#45c8+qVC{|@cUPXzJm)?&l_^brhqU|L{LN+ z004>#hzf}U|0QGy_4Gnz;vJ>{fDb76r{`{1z^GtQ#6s@!6b1079Tf{$&JzmpcJtJC zb90ttxpRs6PV-NCGfVt!P?R0KP!j%k8UOd3*MoZe?d@+A(uHvk9*09g3{9x(sito}#D68!%MClY@I{;~|9^#0a{T3%4A75_h%tG{!0 zN8|s;pTCdA|3?cb=>Hn|kNEvBUH_%)KVslN68>*?{g zuBcOxFX}o2pLZ@nU4?LL)Rg4We%<}@$tMk>TJYVKO}x<1_#WN;q5Bm|`=Am!~$jj*aecN5|Z=_df>O0&IM}y$wRe#n*ukPo{0tAomn9D?P zL_H3(u^UTQnB}9kdsU){-efq_{gK03n{pOIw<=bF$cfTK+!;cx%#8oAzbRCIeZ_q* zbMK88jBVU2BzIlxcJSEyq*bqIseEheBcHr{K;`fb$-GW=HzRp;Khw!}zjb|zEHews zyC^;u78cbF)W=^Q|M&MlU4QBS%j16_n&TRWG8w$#+9lOOX-Hl-s!*Wt4$wql+=w3< zYUHgjAxr&yitQw(VYCcHP#S0YPpAH3?k|u3f#v^;C{&0QkE;kRk%RKY43g8d^Q%nc zwaKUbe1QKuhK?2ljKVsXZTBXULw%TOLo?#-FAMA4dxm8WNS(*gq;06WQovX4yb}XHwZrB+`_OY-;y+&Bg>)QS4up_$4-0Sd_9BuN5w?GO% zvSJKhjI%xugk@&C93>m|mX&FN|HRtj;uJcc-UBRS`WbRj;&X0*5FQsbmJF04T2cPR z=)elv_TUOZYdZ+bKy;i$y4{+{DCS~yyofI_g4nx` zipODeWuQcE%2Wv4T9-_}7fVI~p&*ZM@oj=SjF1wizY5@vao}4wrSM|3Fqpw1XVwUk zf+38tM;h@3ogn%SqKLZ;b~WOyXG9nC*UT6WtinlbbElkH z34*j~N)k?cbu#@oPgl2?sE)&p;libdQ!hBwVN>UW7rd@<>WN)H3=HT{Mqy^;jlzDex1xBb#it?djUix=$@ctP)@`_9dtXb~uL77w&t`QT{kJSZR; zSXSzAvM#)fClgc#3h!b@%+QLOK>ox6#A;jw*JR5+M2Pa@acy%Eq2|R`%PFjKU6f{G z-e!~n_#>1($2JP_bAv56^wC*$bEvnRRsfb zl(X2-B*f1OoeYWh+R8F;Oz{^hPx1jxA2#ZzGV`5SkNb5pu1uv{ylrGV=$B5WR4*~O z1tA^DXry&pYD5+Ri$J7|%V`#0OfKzdqE1FoF&7_pl#KSQ)U%Q^Zc3KNnj0SG*C8c@ z#1UVnG*mZyKY5%?7zUGGKB{r0WAhF>2~!J`)9)#KYm5Ha`}w``w~fPyN`xZ5MZ!0B zJT7idB9_OldE8hGyEfZ{aws43wFT$hmsfEtjVlO(7-txzO-4tlLUR?ubnMZo?*>s# z?fZU_8+CL)Pjr4BW5Vjz4s1shk4p_R7K0tbsrEpep*V_9*K*5qKPU@R+Tg*LN6$*e zzc*f}Sz|6sSi2@BEOOy-m26S};TutlfJJp_H-C}>G5lx}KL|-oSde>8eV0}~bqJV7 zbl!{jYes8!Yg596IR)}R9rZjR?Y>X5IN-XWSP$^MMVgBFIltYF(+$S5dURf`%C+We zX?vHa%*Ca}_KB!OycF*N*VJ@ro5C^NgqxBy?!!=VaQ3-{n-?bbBzi43vi1*@oPW}X@uIw{qig#bSnHPC3gw-){isC89V? z4ak^BOLFq4f*Zpz6gr^sLHx@y%Rtjdf5O7`7o)0 z+6;_V;L*&*LGy2edE-0kszNTZo}n3Xj9~8I7zB*4eL;@T|q-zDX5R@f?<7~AROfr&rCsxz(ETY^`-4{ zVnRrtALDsl7c+`o!o`O0g6K8ko)KfVoRwqI@{m$M+>5NeJYPlbYsu|Fjw=?vve%&& z)R8$W50`PGWxk-6MWQ-(?$>TMlx%KXd1@`}C%C1cqWs^}g^f)}9S`$e*KvyDk4W0{ zA~dw-aMJKssMrjobC0Xp-1UZq#WBxNN;Fqk()fd}1kscq_&v;BvZ9SkL5b*Axa~MZ zebQfyoaShd`RA4&#KS}h(p;|#fEk>5zJ}awb6_uy1JTM<78V*dbay7B0`_ z-g-&2obTX%Un!^r?M#F@^Q=}dBz~5?6m?WYCReHbuT0YhepB@ z{n_B`Pb<`XpDMU{hkyx|<*I0YtAK`>(;M+S=G%flAcft~&z9LWUO%&{gjradSaoErVNnt#TTivSL^?Wjw=@MS&DNKiX z1t3UEzBs>+g-=s-b62+u3LA@(@qmUWF|Q zYYadq_{i$KZ$YtWy`}?An%4U3M%T#t0pd!Yb$e_({`s-kc}HQNM=&KDzp$tWakq4G z2m8&`C$6j8BEz31@Ei22`{$3`jcBt>w`K2#R$>_gruCp zKp-jc#{_AhfX9cnVLInkt8x2A`QSf*_P^$x}M)iUo-3bR$$$O5DNe2w3Ryeh_dw-Mc;>+38^(+(ewrTY!b?D=dvalNpwr*zzt8ei%=%3(n-aezhw|ng&8nQsv>C&FdbBjI zW+NU-U>S?9Ub`J1eybPj8y|;*>dP?B3Qs&gB4AF|-Py)U2>oOxG)V8|y8VmGWtFX4 zjAw?d;6&={Rf|(;)TqaX{O4o1bqx<7-H1VBsXwuo%q$!nu20xyP4C|lDyv9ix z?7)$Dy01ewm26Q;oP@cjQ8dIWv_1F+>4IQA^6~N*zI-%ZB$@EZ#^7ovzHSh+Twj*S zT;-apRvpHE399sM>J!PfqNLJ( zh~GFtg$!Y_r69U=(aZ{bbFFQvEXNq911HjyF}jV;pUBEwo{J9in97{@-z)>g3UMAT z$_Ago=la2Rq&Hk?Rbu4_f4(|{JDU8`W zRje>2HP*yFeFfC49m{EjFqD^k9>9?! zAVBDvm=?izg&(r%Pzo|zaf3|$Ld9x!lK-;ZqW3cY^WN(bTKidz zGvm;W=s{WOrw7sfWmB0T5FZg0dOQAAWAZPfp5&wvPo>w{@M(??y2ehh7t@W{@!9yU zxXa6?R|jVkZYYWz@sn@amN6Vj{pN?jH9@Ft?@UK)iY0I{CJ?o?S>jv7b^UPJ9_&2d z6rwhi-}3Ym=aDx3b>aH~@soF1m3Dln8Q;AH@zeC`XafYSXUMFnxGjIF9N0uAioVpd z_)79pb;AKT0={i0p*Q~V0G0Q=yDnMd;r&-Q%Lg3lQam1#x1YhFXWzydNL991XJ}7Y z2a4G6ze;-N=rf3&UC6&SzU{@_#-z%aeY?O&Fz=FM!eX1116&N`bzz_uyU8rTOm&>$ z%c-wxu<-P`P7CK=wA;kmTI74vO~a!t=dDQ4wwCk)Gdn1Y_;$n3}c2l)T*Avxm{+gz0?& zZS5N2>6_NVye^r(zRC=tC{A&;okF4$5U?^1_4micMcZgrgEPBAZEpCSd4xI{gEhnS zcoH)HaA@kU>2BYHGg|)2;UGTA-@B=n_NVadzK2tpiSGcJ6Wb@PnHy6@`>wxkOiSRh zT1r3R3pN?|qWkG0TfydI(~@hcbXm_wTMibsCHrjc5DlS@fcc;;&sV?M$3HkYy*C?6 z5iU-yP=a_GP_pH}1DHr^bSZsrfg~=n@4ed>8Z&EqBeP#A!&BVa%#PCM#RF8qq3@(gQIb2HWoL%LTyWP&6r{-3)%&tP_Fio*~%%RSQ zjk*gD&IVn3i%q<-bU1u?=IYU*ahl|{U5NVLBc2>>t3_z*Oy|g0@}t1?VI3jUZOOL8 zP)gL%##1YuX@$KcqugG(#suzDH(a_n(hR2P(DX?!mm2nRTYgnux_19cKkMm5OUty! zNcYJ3tI2mu!RXhla%CVj>KWHHHpcG29Mh$7^tUDr6eOzrPim+LnuR5`8jiWtzL;&I zmL~e>Tu!wDlYR-L^skdV#Lr?5b=SQ<*Y`hKmozH)PC3+@%Rr>);?w7Yj>tgNjk6}+ zDwO3QI_XTSON*+8nF#mhMa>Y&$IfdX6-B}z;w$6l#6YP~g))%|*;l42*VE^F51+$W zk4_Gql1Y1{O+3;NKkGwyemCh2Usg={9dAx$kK{}y@9S%6b)RRSVQvprAzrj)B4DKm zHn*lRmb5kcS9()8$i>LoEfPPBat?L2R7u2cM$#E$T$f$-z2@USfgk*a%(`yh&1gdR zTh4YI1JTmTKq4r@8itAbXGer59?(z7m310)bZOO@4YW^XQV@pZ_LPFC(MQbSiqu5i zlGry5e3ir58FfLW2w0x?^$FJj!&P;S94NJ)ez`2$)3<)ud}6vM9PyJ7nfpO9G`MJP z6kMaryJ!*^9u|_)sj5f!eAXr(&dDdZG>tDX8UVeY(%_5vtiiW2p;72ISa?gVo$f=*Hdg)9o7PL zzI+5l5xt(1;K5ZISBRj;bFm){$ck(qEC8)^q{Wo|LEAyv@(&u~i5gZ{qt4y>!c%0u zQ+TreeW$5-LtY*8ebvZUKV9BN@hLFGSoLb#F|!YveWhAo(BRveuPtp|{cZM5_RZMx zuO*BS?YCEFUlSwOY0YoEmnUeUn$Tqm<%PM<$>UzCYkrAvrc3Ql-Kcs;c@*E%k3suI zWtqwMiD|4YUZa)`*YmwSI9={HVaa<46|ylzn|cQb@?l1_ZXIk#2>Xfx~1J( zm=36M+MglitxHh##QnZ1iKGsZ3$KKc`%7~~%kP zLZDyjMJs9%Ky8rhS5Ine!{@?$3HDgf`7r9^yI6U?@@9JQjOPQy&;bsFKAxS4Nug*( z=&0Kjsx5PJPLRewH1!8`B%X9}ShlkBm&ro@!O3oI+lIdh@L7M;RMax9Vn-Bd>Mgv8dE1>FO6~ z$?}k2rW6BAF-#h_zmIQSjK5JFklavpK9ZasWwbWo{+|297+X3ul;p;vGd9tZ)!Sau z{J{*RF|P4DC59l9jBLJUT{~%<4nbGnis`~#mR zmb9Tvl_tGKk+5%7sczv+zvqZR?*P9wFt=tIHCi_s!Ou#L$jJMptw=hPT{1A|!>rJp z@@jp5j3yDg#bv!9pB;`a^I^qQT-krm* zi|)^dfZeqz*y^e7UT3#Qy? [Proxy] %s", l.Agent.Id, l.Agent.Name, l.Network, l.ListenerAddr, l.RedirectAddr) +} + func (la *LigoloAgent) String() string { - return fmt.Sprintf("%s - %s", la.Name, la.Session.RemoteAddr()) + return fmt.Sprintf("#%d - %s - %s", la.Id, la.Name, la.Session.RemoteAddr()) } func NewAgent(session *yamux.Session) (LigoloAgent, error) { diff --git a/pkg/relay/relay.go b/pkg/relay/relay.go index fd8a58f..37eb41e 100644 --- a/pkg/relay/relay.go +++ b/pkg/relay/relay.go @@ -5,22 +5,37 @@ import ( "net" ) -func relay(src net.Conn, dst net.Conn, stop chan bool) { - io.Copy(dst, src) - dst.Close() - src.Close() - stop <- true +func relay(src net.Conn, dst net.Conn, stop chan error, closeOnError bool) { + _, err := io.Copy(dst, src) + if closeOnError { + dst.Close() + src.Close() + } + + stop <- err return } -func StartRelay(src net.Conn, dst net.Conn) { - stop := make(chan bool, 2) +func StartRelay(src net.Conn, dst net.Conn) error { + stop := make(chan error, 2) + + go relay(src, dst, stop, true) + go relay(dst, src, stop, true) + + select { + case err := <-stop: + return err + } +} + +func StartPacketRelay(src net.Conn, dst net.Conn) error { + stop := make(chan error, 2) - go relay(src, dst, stop) - go relay(dst, src, stop) + go relay(src, dst, stop, false) + go relay(dst, src, stop, false) select { - case <-stop: - return + case err := <-stop: + return err } }