diff --git a/README.md b/README.md index 78a1912..fcc4727 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![Ligolo Logo](doc/logo.png) -An advanced, yet simple, tunneling tool that uses a TUN interface. +An advanced, yet simple, tunneling tool that uses TUN interfaces. [![GPLv3](https://img.shields.io/badge/License-GPLv3-brightgreen.svg)](https://www.gnu.org/licenses/gpl-3.0) [![Go Report](https://goreportcard.com/badge/github.com/nicocha30/ligolo-ng)](https://goreportcard.com/report/github.com/nicocha30/ligolo-ng) @@ -58,6 +58,7 @@ tunnels from a reverse TCP/TLS connection using a **tun interface** (without the - Does not require high privileges - Socket listening/binding on the *agent* - Multiple platforms supported for the *agent* +- Can handle multiple tunnels ## How is this different from Ligolo/Chisel/Meterpreter... ? @@ -196,10 +197,18 @@ Idx Mét MTU État Nom Start the tunnel on the proxy: ``` -[Agent : nchatelain@nworkstation] » start +[Agent : nchatelain@nworkstation] » start_tunnel [Agent : nchatelain@nworkstation] » INFO[0690] Starting tunnel to nchatelain@nworkstation ``` +You can also specify a custom tuntap interface using the ``--tun iface`` option: + +``` +[Agent : nchatelain@nworkstation] » start_tunnel --tun mycustomtuntap +[Agent : nchatelain@nworkstation] » INFO[0690] Starting tunnel to nchatelain@nworkstation +``` + + You can now access the *192.168.0.0/24* *agent* network from the *proxy* server. ```shell diff --git a/cmd/proxy/app/app.go b/cmd/proxy/app/app.go index 9525904..3946c6f 100644 --- a/cmd/proxy/app/app.go +++ b/cmd/proxy/app/app.go @@ -1,17 +1,16 @@ package app import ( + "context" "errors" "fmt" "github.com/AlecAivazis/survey/v2" "github.com/desertbit/grumble" "github.com/jedib0t/go-pretty/v6/table" "github.com/nicocha30/ligolo-ng/pkg/controller" - "github.com/nicocha30/ligolo-ng/pkg/protocol" + "github.com/nicocha30/ligolo-ng/pkg/proxy" "github.com/nicocha30/ligolo-ng/pkg/proxy/netstack" - "github.com/nicocha30/ligolo-ng/pkg/relay" "github.com/sirupsen/logrus" - "io" "net" "strconv" "strings" @@ -19,7 +18,7 @@ import ( "time" ) -var AgentList map[int]controller.LigoloAgent +var AgentList map[int]*controller.LigoloAgent var AgentListMutex sync.Mutex var ListenerList map[int]controller.Listener var ListenerListMutex sync.Mutex @@ -30,31 +29,21 @@ var ( ErrNotRunning = errors.New("no tunnel started") ) -const ( - MaxConnectionHandler = 4096 -) - -func RegisterAgent(agent controller.LigoloAgent) error { +func RegisterAgent(agent *controller.LigoloAgent) error { AgentListMutex.Lock() AgentList[agent.Id] = agent AgentListMutex.Unlock() return nil } -func Run(stackSettings netstack.StackSettings) { +func Run() { // CurrentAgent points to the selected agent in the UI (when running session) - var CurrentAgent controller.LigoloAgent - // ListeningAgent points to the currently running agent (forwarding packets) - var ListeningAgent controller.LigoloAgent + var CurrentAgentID int // AgentList contains all the connected agents - AgentList = make(map[int]controller.LigoloAgent) + AgentList = make(map[int]*controller.LigoloAgent) // ListenerList contains all listener relays ListenerList = make(map[int]controller.Listener) - // Create a new stack, but without connPool. - // The connPool will be created when using the *start* command - nstack := netstack.NewStack(stackSettings, nil) - App.AddCommand(&grumble.Command{ Name: "session", Help: "Change the current relay agent", @@ -89,79 +78,84 @@ func Run(stackSettings netstack.StackSettings) { return err } - CurrentAgent = AgentList[sessionID] + CurrentAgentID = sessionID - c.App.SetPrompt(fmt.Sprintf("[Agent : %s] » ", CurrentAgent.Name)) + c.App.SetPrompt(fmt.Sprintf("[Agent : %s] » ", AgentList[CurrentAgentID].Name)) return nil }, }) App.AddCommand(&grumble.Command{ - Name: "start", + Name: "tunnel_start", Help: "Start relaying connection to the current agent", - Usage: "start", + Usage: "tunnel_start --tun ligolo", HelpGroup: "Tunneling", + Aliases: []string{"start"}, + Flags: func(f *grumble.Flags) { + f.StringL("tun", "ligolo", "the interface to run the proxy on") + }, Run: func(c *grumble.Context) error { + if _, ok := AgentList[CurrentAgentID]; !ok { + return ErrInvalidAgent + } + CurrentAgent := AgentList[CurrentAgentID] + if CurrentAgent.Session == nil { return ErrInvalidAgent } - if ListeningAgent.Session != nil { - if ListeningAgent.Id == CurrentAgent.Id { - return ErrAlreadyRunning - } + if CurrentAgent.Running { + return ErrAlreadyRunning + } - if !ListeningAgent.Session.IsClosed() { - var switchConfirm bool - askSwitch := survey.Confirm{ - Message: fmt.Sprintf("Tunnel already running, switch from %s to %s?", ListeningAgent.Name, CurrentAgent.Name), - } - if err := survey.AskOne(&askSwitch, &switchConfirm); err != nil { - return err + for _, agent := range AgentList { + if agent.Running { + if agent.Interface == c.Flags.String("tun") { + return errors.New("a tunnel is already using this interface name. Please use a different name using the --tun option") } - if !switchConfirm { - return nil - } - // Close agent - ListeningAgent.CloseChan <- true - } } - ListeningAgent = CurrentAgent - go func() { - logrus.Infof("Starting tunnel to %s", ListeningAgent.Name) - - // Create a new, empty, connpool to store connections/packets - connPool := netstack.NewConnPool(MaxConnectionHandler) - nstack.SetConnPool(&connPool) + logrus.Infof("Starting tunnel to %s", CurrentAgent.Name) + ligoloStack, err := proxy.NewLigoloTunnel(netstack.StackSettings{ + TunName: c.Flags.String("tun"), + MaxInflight: 4096, + }) + if err != nil { + logrus.Error("Unable to create tunnel, err:", err) + return + } + ifName, err := ligoloStack.GetStack().Interface().Name() + if err != nil { + logrus.Warn("unable to get interface name, err:", err) + ifName = c.Flags.String("tun") + } + CurrentAgent.Interface = ifName + CurrentAgent.Running = true - // Cleanup pool if channel is closed - defer connPool.Close() + ctx, cancelTunnel := context.WithCancel(context.Background()) + go ligoloStack.HandleSession(CurrentAgent.Session, ctx) for { select { - case <-ListeningAgent.CloseChan: // User stopped - logrus.Infof("Closing tunnel to %s...", ListeningAgent.Name) + case <-CurrentAgent.CloseChan: // User stopped + logrus.Infof("Closing tunnel to %s...", CurrentAgent.Name) + cancelTunnel() return - case <-ListeningAgent.Session.CloseChan(): // Agent closed - logrus.Warnf("Lost connection with agent %s!", ListeningAgent.Name) + case <-CurrentAgent.Session.CloseChan(): // Agent closed + logrus.Warnf("Lost connection with agent %s!", CurrentAgent.Name) // Connection lost, we need to delete the Agent from the list AgentListMutex.Lock() - delete(AgentList, ListeningAgent.Id) + delete(AgentList, CurrentAgent.Id) AgentListMutex.Unlock() - if CurrentAgent.Id == ListeningAgent.Id { + if CurrentAgent.Id == CurrentAgent.Id { App.SetDefaultPrompt() CurrentAgent.Session = nil } + cancelTunnel() return - case <-connPool.CloseChan: // pool closed, we can't process packets! - logrus.Infof("Connection pool closed") - return - case tunnelPacket := <-connPool.Pool: // Process connections/packets - go netstack.HandlePacket(nstack.GetStack(), tunnelPacket, ListeningAgent.Session) } } }() @@ -169,16 +163,58 @@ func Run(stackSettings netstack.StackSettings) { }, }) - App.AddCommand(&grumble.Command{Name: "stop", + App.AddCommand(&grumble.Command{Name: "tunnel_list", + Help: "List active tunnels", + Usage: "tunnel_list", + HelpGroup: "Tunneling", + Run: func(c *grumble.Context) error { + t := table.NewWriter() + t.SetStyle(table.StyleLight) + t.SetTitle("Active tunnels") + t.AppendHeader(table.Row{"#", "Agent", "Interface"}) + + AgentListMutex.Lock() + + for _, agent := range AgentList { + + if agent.Running { + t.AppendRow(table.Row{agent.Id, agent.Name, agent.Interface}) + } + } + AgentListMutex.Unlock() + App.Println(t.Render()) + + return nil + }, + }) + + App.AddCommand(&grumble.Command{Name: "tunnel_stop", Help: "Stop the tunnel", Usage: "stop", HelpGroup: "Tunneling", + Aliases: []string{"stop"}, + Flags: func(f *grumble.Flags) { + f.IntL("agent", -1, "The agent to stop") + }, Run: func(c *grumble.Context) error { - if ListeningAgent.Session == nil { + var selectedAgent int + if c.Flags.Int("agent") != -1 { + selectedAgent = c.Flags.Int("agent") + } else { + selectedAgent = CurrentAgentID + } + if _, ok := AgentList[selectedAgent]; !ok { + return ErrInvalidAgent + } + + CurrentAgent := AgentList[selectedAgent] + + if CurrentAgent.Session == nil || !CurrentAgent.Running { return ErrNotRunning } - ListeningAgent.CloseChan <- true - ListeningAgent.Session = nil + CurrentAgent.CloseChan <- true + CurrentAgent.Running = false + return nil }, }) @@ -187,6 +223,10 @@ func Run(stackSettings netstack.StackSettings) { Help: "Show agent interfaces", Usage: "ifconfig", Run: func(c *grumble.Context) error { + if _, ok := AgentList[CurrentAgentID]; !ok { + return ErrInvalidAgent + } + CurrentAgent := AgentList[CurrentAgentID] // Note: Network information is not refreshed when calling this command if CurrentAgent.Session == nil { return ErrInvalidAgent @@ -250,6 +290,10 @@ func Run(stackSettings netstack.StackSettings) { a.Int("id", "listener id") }, Run: func(c *grumble.Context) error { + if _, ok := AgentList[CurrentAgentID]; !ok { + return ErrInvalidAgent + } + CurrentAgent := AgentList[CurrentAgentID] ListenerListMutex.Lock() if _, ok := ListenerList[c.Args.Int("id")]; !ok { ListenerListMutex.Unlock() @@ -259,32 +303,9 @@ func Run(stackSettings netstack.StackSettings) { ListenerListMutex.Unlock() listener.Session.Close() - yamuxConnectionSession, err := CurrentAgent.Session.Open() - if err != nil { + if err := proxy.ListenerStop(CurrentAgent.Session, listener.ListenerID); err != nil { return err } - protocolEncoder := protocol.NewEncoder(yamuxConnectionSession) - protocolDecoder := protocol.NewDecoder(yamuxConnectionSession) - - // Send close request - closeRequest := protocol.ListenerCloseRequestPacket{ListenerID: listener.ListenerID} - if err := protocolEncoder.Encode(protocol.Envelope{ - Type: protocol.MessageListenerCloseRequest, - Payload: closeRequest, - }); err != nil { - return err - } - - // Process close response - if err := protocolDecoder.Decode(); err != nil { - return err - - } - response := protocolDecoder.Envelope.Payload - - if err := response.(protocol.ListenerCloseResponsePacket).Err; err != false { - return errors.New(response.(protocol.ListenerCloseResponsePacket).ErrString) - } logrus.Info("Listener closed.") @@ -300,16 +321,21 @@ func Run(stackSettings netstack.StackSettings) { App.AddCommand(&grumble.Command{ Name: "listener_add", Help: "Listen on the agent and redirect connections to the desired address", - Usage: "listener_add --addr [agent_listening_address:port] --to [local_listening_address:port] --tcp/--udp", + Usage: "listener_add --addr [agent_listening_address:port] --to [local_listening_address:port] --tcp/--udp (--no-retry)", HelpGroup: "Listeners", Flags: func(f *grumble.Flags) { f.BoolL("tcp", false, "Use TCP listener") f.BoolL("udp", false, "Use UDP listener") f.StringL("addr", "", "The agent listening address:port") f.StringL("to", "", "Where to redirect connections") + f.BoolL("no-retry", false, "Do not restart relay on listener error") }, Run: func(c *grumble.Context) error { + if _, ok := AgentList[CurrentAgentID]; !ok { + return ErrInvalidAgent + } + CurrentAgent := AgentList[CurrentAgentID] if CurrentAgent.Session == nil { return errors.New("please, select an agent using the session command") } @@ -340,147 +366,42 @@ func Run(stackSettings netstack.StackSettings) { return err } - // Open a new Yamux Session - yamuxConnectionSession, err := CurrentAgent.Session.Open() + proxyListener, err := proxy.NewListener(CurrentAgent.Session, c.Flags.String("addr"), netProto, c.Flags.String("to")) if err != nil { return err } - protocolEncoder := protocol.NewEncoder(yamuxConnectionSession) - protocolDecoder := protocol.NewDecoder(yamuxConnectionSession) - - // Request to open a new port on the agent - listenerPacket := protocol.ListenerRequestPacket{Address: c.Flags.String("addr"), Network: netProto} - if err := protocolEncoder.Encode(protocol.Envelope{ - Type: protocol.MessageListenerRequest, - Payload: listenerPacket, - }); err != nil { - return err - } - // Get response from agent - if err := protocolDecoder.Decode(); err != nil { - return err - } - response := protocolDecoder.Envelope.Payload.(protocol.ListenerResponsePacket) - if err := response.Err; err != false { - return errors.New(response.ErrString) - } - - logrus.Info("Listener created on remote agent!") + logrus.Infof("Listener %d created on remote agent!", proxyListener.ID) // Register the listener in the UI listener := controller.Listener{ - Agent: CurrentAgent, + Agent: *CurrentAgent, Network: netProto, ListenerAddr: c.Flags.String("addr"), RedirectAddr: c.Flags.String("to"), - Session: yamuxConnectionSession, - ListenerID: response.ListenerID, + Session: proxyListener.Conn, + ListenerID: proxyListener.ID, } ListenerListMutex.Lock() ListenerList[controller.ListenerCounter] = listener ListenerListMutex.Unlock() - currentListener := controller.ListenerCounter controller.ListenerCounter++ - if netProto == "udp" { - - // relay connections - go func() { - for { - // Check if deleted - if _, ok := ListenerList[currentListener]; !ok { - return - } - // Dial the "to" target - lconn, err := net.Dial(netProto, c.Flags.String("to")) - if 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 - } - - // We received a new BindResponse! - response := protocolDecoder.Envelope.Payload.(protocol.ListenerBindReponse) - - if err := response.Err; err != false { - logrus.Error(response.ErrString) - return + go func() { + for { + err := proxyListener.StartRelay() + if err != nil { + logrus.WithFields(logrus.Fields{"listener": listener.String()}).Error("Listener relay failed with error: ", err) + if !c.Flags.Bool("no-retry") { + logrus.Warning("Listener failed. Restarting in 5 seconds...") + time.Sleep(time.Second * 5) + continue } - - 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 } - - }() - } + logrus.WithFields(logrus.Fields{"listener": listener.String()}).Warning("Listener ended without error.") + } + }() return nil }, diff --git a/cmd/proxy/app/cli.go b/cmd/proxy/app/cli.go index 68ec5e7..561de13 100644 --- a/cmd/proxy/app/cli.go +++ b/cmd/proxy/app/cli.go @@ -18,6 +18,6 @@ func init() { a.Println(" / /___/ / /_/ / /_/ / / /_/ /_____/ / / / /_/ / ") a.Println("/_____/_/\\__, /\\____/_/\\____/ /_/ /_/\\__, / ") a.Println(" /____/ /____/ \n") - a.Println("Made in France ♥ by @Nicocha30!\n") + a.Println(" Made in France ♥ by @Nicocha30!\n") }) } diff --git a/cmd/proxy/main.go b/cmd/proxy/main.go index 2c4bfca..d737b12 100644 --- a/cmd/proxy/main.go +++ b/cmd/proxy/main.go @@ -6,7 +6,6 @@ import ( "github.com/hashicorp/yamux" "github.com/nicocha30/ligolo-ng/cmd/proxy/app" "github.com/nicocha30/ligolo-ng/pkg/controller" - "github.com/nicocha30/ligolo-ng/pkg/proxy/netstack" "github.com/sirupsen/logrus" "os" "strings" @@ -15,14 +14,12 @@ import ( func main() { var allowDomains []string var verboseFlag = flag.Bool("v", false, "enable verbose mode") - var tunInterface = flag.String("tun", "ligolo", "tun interface name") var listenInterface = flag.String("laddr", "0.0.0.0:11601", "listening address ") var enableAutocert = flag.Bool("autocert", false, "automatically request letsencrypt certificates, requires port 80 to be accessible") var enableSelfcert = flag.Bool("selfcert", false, "dynamically generate self-signed certificates") var certFile = flag.String("certfile", "certs/cert.pem", "TLS server certificate") var keyFile = flag.String("keyfile", "certs/key.pem", "TLS server key") var domainWhitelist = flag.String("allow-domains", "", "autocert authorised domains, if empty, allow all domains, multiple domains should be comma-separated.") - var maxInflight = flag.Int("maxinflight", 4096, "max inflight TCP connections") flag.Parse() @@ -37,10 +34,7 @@ func main() { allowDomains = strings.Split(*domainWhitelist, ",") } - app.Run(netstack.StackSettings{ - TunName: *tunInterface, - MaxInflight: *maxInflight, - }) + app.Run() proxyController := controller.New(controller.ControllerConfig{ EnableAutocert: *enableAutocert, diff --git a/pkg/controller/agent.go b/pkg/controller/agent.go index f735dd6..3643d14 100644 --- a/pkg/controller/agent.go +++ b/pkg/controller/agent.go @@ -16,6 +16,8 @@ type LigoloAgent struct { Network []protocol.NetInterface Session *yamux.Session CloseChan chan bool + Interface string + Running bool } type Listener struct { @@ -36,10 +38,10 @@ func (la *LigoloAgent) String() string { return fmt.Sprintf("#%d - %s - %s", la.Id, la.Name, la.Session.RemoteAddr()) } -func NewAgent(session *yamux.Session) (LigoloAgent, error) { +func NewAgent(session *yamux.Session) (*LigoloAgent, error) { yamuxConnectionSession, err := session.Open() if err != nil { - return LigoloAgent{}, err + return nil, err } infoRequest := protocol.InfoRequestPacket{} @@ -51,17 +53,17 @@ func NewAgent(session *yamux.Session) (LigoloAgent, error) { Type: protocol.MessageInfoRequest, Payload: infoRequest, }); err != nil { - return LigoloAgent{}, err + return nil, err } if err := protocolDecoder.Decode(); err != nil { - return LigoloAgent{}, err + return nil, err } response := protocolDecoder.Envelope.Payload reply := response.(protocol.InfoReplyPacket) AgentCounter++ - return LigoloAgent{ + return &LigoloAgent{ Id: AgentCounter, Name: reply.Name, Network: reply.Interfaces, diff --git a/pkg/protocol/encdec.go b/pkg/protocol/encdec.go new file mode 100644 index 0000000..442a8f9 --- /dev/null +++ b/pkg/protocol/encdec.go @@ -0,0 +1,15 @@ +package protocol + +import "io" + +type LigoloEncoderDecoder struct { + LigoloDecoder + LigoloEncoder +} + +func NewEncoderDecoder(rw io.ReadWriter) LigoloEncoderDecoder { + return LigoloEncoderDecoder{ + NewDecoder(rw), + NewEncoder(rw), + } +} diff --git a/pkg/proxy/listeners.go b/pkg/proxy/listeners.go new file mode 100644 index 0000000..9a85f51 --- /dev/null +++ b/pkg/proxy/listeners.go @@ -0,0 +1,177 @@ +package proxy + +import ( + "context" + "errors" + "github.com/hashicorp/yamux" + "github.com/nicocha30/ligolo-ng/pkg/protocol" + "github.com/nicocha30/ligolo-ng/pkg/relay" + "github.com/sirupsen/logrus" + "io" + "net" +) + +func ListenerStop(sess *yamux.Session, listenerId int32) error { + // Open Yamux connection + yamuxConnectionSession, err := sess.Open() + if err != nil { + return err + } + + ligoloProtocol := protocol.NewEncoderDecoder(yamuxConnectionSession) + + // Send close request + closeRequest := protocol.ListenerCloseRequestPacket{ListenerID: listenerId} + if err := ligoloProtocol.Encode(protocol.Envelope{ + Type: protocol.MessageListenerCloseRequest, + Payload: closeRequest, + }); err != nil { + return err + } + + // Process close response + if err := ligoloProtocol.Decode(); err != nil { + return err + + } + response := ligoloProtocol.Envelope.Payload + + if err := response.(protocol.ListenerCloseResponsePacket).Err; err != false { + return errors.New(response.(protocol.ListenerCloseResponsePacket).ErrString) + } + return nil +} + +type LigoloListener struct { + ID int32 + ctx context.Context + sess *yamux.Session + Conn net.Conn + addr string + network string + to string +} + +func NewListener(sess *yamux.Session, addr string, network string, to string) (LigoloListener, error) { + // Open a new Yamux Session + conn, err := sess.Open() + if err != nil { + return LigoloListener{}, err + } + + ligoloProtocol := protocol.NewEncoderDecoder(conn) + + // Request to open a new port on the agent + listenerPacket := protocol.ListenerRequestPacket{Address: addr, Network: network} + if err := ligoloProtocol.Encode(protocol.Envelope{ + Type: protocol.MessageListenerRequest, + Payload: listenerPacket, + }); err != nil { + return LigoloListener{}, err + } + + // Get response from agent + if err := ligoloProtocol.Decode(); err != nil { + return LigoloListener{}, err + } + response := ligoloProtocol.Envelope.Payload.(protocol.ListenerResponsePacket) + if err := response.Err; err != false { + return LigoloListener{}, errors.New(response.ErrString) + } + return LigoloListener{ID: response.ListenerID, sess: sess, Conn: conn, addr: addr, network: network, to: to}, nil +} + +func (l *LigoloListener) StartRelay() error { + if l.network == "tcp" { + return l.relayTCP() + } else if l.network == "udp" { + return l.relayUDP() + } + return errors.New("invalid network") +} + +func (l *LigoloListener) relayTCP() error { + ligoloProtocol := protocol.NewEncoderDecoder(l.Conn) + for { + // Wait for BindResponses + if err := ligoloProtocol.Decode(); err != nil { + if err == io.EOF { + // Listener closed. + return err + } + return err + } + + // We received a new BindResponse! + response := ligoloProtocol.Envelope.Payload.(protocol.ListenerBindReponse) + + if err := response.Err; err != false { + return errors.New(response.ErrString) + } + + logrus.Debugf("New socket opened : %d", response.SockID) + + // relay connection + go func(sockID int32) { + forwarderSession, err := l.sess.Open() + if err != nil { + logrus.Error(err) + return + } + + forwarderProtocolEncDec := protocol.NewEncoderDecoder(forwarderSession) + + // Request socket access + socketRequestPacket := protocol.ListenerSockRequestPacket{SockID: sockID} + if err := forwarderProtocolEncDec.Encode(protocol.Envelope{ + Type: protocol.MessageListenerSockRequest, + Payload: socketRequestPacket, + }); err != nil { + logrus.Error(err) + return + } + if err := forwarderProtocolEncDec.Decode(); err != nil { + logrus.Error(err) + return + } + + response := forwarderProtocolEncDec.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(l.network, l.to) + if err != nil { + logrus.Error(err) + return + } + + // relay connections + if err := relay.StartRelay(lconn, forwarderSession); err != nil { + logrus.Error(err) + return + } + }(response.SockID) + + } + +} + +func (l *LigoloListener) relayUDP() error { + // Dial the "to" target + lconn, err := net.Dial(l.network, l.to) + if err != nil { + return err + } + // Relay conn + err = relay.StartPacketRelay(lconn, l.Conn) + if err != nil { + return err + } + return nil +} diff --git a/pkg/proxy/netstack/stack.go b/pkg/proxy/netstack/stack.go index 249b153..7308c1f 100644 --- a/pkg/proxy/netstack/stack.go +++ b/pkg/proxy/netstack/stack.go @@ -1,6 +1,7 @@ package netstack import ( + "errors" "fmt" "github.com/nicocha30/gvisor-ligolo/pkg/tcpip" "github.com/nicocha30/gvisor-ligolo/pkg/tcpip/header" @@ -78,6 +79,7 @@ type ICMPConn struct { type NetStack struct { pool *ConnPool stack *stack.Stack + iface *tun.TunInterface sync.Mutex } @@ -87,10 +89,12 @@ type StackSettings struct { } // NewStack registers a new GVisor Network Stack -func NewStack(settings StackSettings, connPool *ConnPool) *NetStack { +func NewStack(settings StackSettings, connPool *ConnPool) (*NetStack, error) { ns := NetStack{pool: connPool} - ns.new(settings) - return &ns + if _, err := ns.new(settings); err != nil { + return nil, err + } + return &ns, nil } // GetStack returns the current Gvisor stack.Stack object @@ -106,7 +110,7 @@ func (s *NetStack) SetConnPool(connPool *ConnPool) { } // New creates a new userland network stack (using Gvisor) that listen on a tun interface. -func (s *NetStack) new(stackSettings StackSettings) *stack.Stack { +func (s *NetStack) new(stackSettings StackSettings) (*stack.Stack, error) { // Create a new gvisor userland network stack. ns := stack.New(stack.Options{ @@ -176,18 +180,20 @@ func (s *NetStack) new(stackSettings StackSettings) *stack.Stack { ns.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpHandler.HandlePacket) ns.SetTransportProtocolHandler(udp.ProtocolNumber, udpHandler.HandlePacket) - linkEP, err := tun.Open(stackSettings.TunName) + iface, err := tun.New(stackSettings.TunName) if err != nil { - logrus.Fatalf("unable to create tun interface: (tun.Open %v), make sure you created the tun interface", err) + return nil, fmt.Errorf("unable to create tun interface '%s' (tun.New %v), make sure you've created the tun interface and that it's not in use", stackSettings.TunName, err) } + s.iface = iface + // Create a new NIC - if err := ns.CreateNIC(1, linkEP); err != nil { - panic(fmt.Errorf("CreateNIC: %v", err)) + if err := ns.CreateNIC(1, iface.LinkEP); err != nil { + return nil, errors.New(err.String()) } // Start a endpoint that will reply to ICMP echo queries if err := icmpResponder(s); err != nil { - logrus.Fatal(err) + return nil, err } // Allow all routes by default @@ -219,5 +225,21 @@ func (s *NetStack) new(stackSettings StackSettings) *stack.Stack { ns.SetPromiscuousMode(1, true) ns.SetSpoofing(1, true) - return ns + return ns, nil +} + +func (n *NetStack) Interface() *tun.TunInterface { + return n.iface +} + +func (n *NetStack) Close() { + if n.stack != nil { + n.stack.Destroy() + } + if n.iface != nil { + err := n.iface.Close() + if err != nil { + logrus.Warn("NetStack.Close() => iface close err: ", err) + } + } } diff --git a/pkg/proxy/netstack/tun/gvisor.go b/pkg/proxy/netstack/tun/gvisor.go index a4fa9ff..68c5242 100644 --- a/pkg/proxy/netstack/tun/gvisor.go +++ b/pkg/proxy/netstack/tun/gvisor.go @@ -8,9 +8,17 @@ import ( "github.com/nicocha30/gvisor-ligolo/pkg/tcpip/link/rawfile" "github.com/nicocha30/gvisor-ligolo/pkg/tcpip/link/tun" "github.com/nicocha30/gvisor-ligolo/pkg/tcpip/stack" + "golang.org/x/sys/unix" ) -func Open(tunName string) (stack.LinkEndpoint, error) { +type TunInterface struct { + LinkEP stack.LinkEndpoint + fd int + name string +} + +func New(tunName string) (*TunInterface, error) { + tunIface := TunInterface{} mtu, err := rawfile.GetMTU(tunName) if err != nil { return nil, err @@ -20,10 +28,22 @@ func Open(tunName string) (stack.LinkEndpoint, error) { if err != nil { return nil, err } + tunIface.fd = fd + tunIface.name = tunName linkEP, err := fdbased.New(&fdbased.Options{FDs: []int{fd}, MTU: mtu}) if err != nil { return nil, err } - return linkEP, nil + tunIface.LinkEP = linkEP + + return &tunIface, nil +} + +func (t TunInterface) Name() (string, error) { + return t.name, nil +} + +func (t *TunInterface) Close() error { + return unix.Close(t.fd) } diff --git a/pkg/proxy/netstack/tun/wireguard.go b/pkg/proxy/netstack/tun/wireguard.go index e16187d..8197582 100644 --- a/pkg/proxy/netstack/tun/wireguard.go +++ b/pkg/proxy/netstack/tun/wireguard.go @@ -4,15 +4,29 @@ package tun import ( - "github.com/nicocha30/gvisor-ligolo/pkg/tcpip/stack" "golang.zx2c4.com/wireguard/tun" ) -func Open(tunName string) (stack.LinkEndpoint, error) { +type TunInterface struct { + LinkEP *RWEndpoint + device tun.Device +} + +func New(tunName string) (*TunInterface, error) { + tunIface := TunInterface{} wgtun, err := tun.CreateTUN(tunName, 1500) if err != nil { return nil, err } + tunIface.LinkEP = NewRWEndpoint(wgtun, 1500) + tunIface.device = wgtun + return &tunIface, nil +} + +func (i TunInterface) Name() (string, error) { + return i.device.Name() +} - return NewRWEndpoint(wgtun, 1500), nil +func (i TunInterface) Close() error { + return i.device.Close() } diff --git a/pkg/proxy/proxy.go b/pkg/proxy/proxy.go new file mode 100644 index 0000000..a5f284b --- /dev/null +++ b/pkg/proxy/proxy.go @@ -0,0 +1,58 @@ +package proxy + +import ( + "context" + "github.com/hashicorp/yamux" + "github.com/nicocha30/ligolo-ng/pkg/proxy/netstack" + "github.com/sirupsen/logrus" +) + +const ( + MaxConnectionHandler = 4096 +) + +type LigoloTunnel struct { + nstack *netstack.NetStack +} + +func NewLigoloTunnel(stackSettings netstack.StackSettings) (*LigoloTunnel, error) { + // Create a new stack, but without connPool. + // The connPool will be created when using the *start* command + nstack, err := netstack.NewStack(stackSettings, nil) + if err != nil { + return nil, err + } + return &LigoloTunnel{nstack: nstack}, nil +} + +func (t *LigoloTunnel) HandleSession(session *yamux.Session, ctx context.Context) { + + // Create a new, empty, connpool to store connections/packets + connPool := netstack.NewConnPool(MaxConnectionHandler) + t.nstack.SetConnPool(&connPool) + + // Cleanup pool if channel is closed + defer connPool.Close() + + for { + select { + case <-ctx.Done(): + t.Close() + return + case <-connPool.CloseChan: // pool closed, we can't process packets! + logrus.Infof("Connection pool closed") + t.Close() + return + case tunnelPacket := <-connPool.Pool: // Process connections/packets + go netstack.HandlePacket(t.nstack.GetStack(), tunnelPacket, session) + } + } +} + +func (t *LigoloTunnel) GetStack() *netstack.NetStack { + return t.nstack +} + +func (t *LigoloTunnel) Close() { + t.nstack.Close() +}