docs: http-lp forwarding goes over the agent's WebSocket, by long polls only without one - said so in the panel, the code and the README
This commit is contained in:
1 parent
e93a2bbd3a
commit
e8d45f2f7b
7 files changed
+22
-15
No files matched your search
@@ -182,7 +182,8 @@ curl -fsSL https://panel.example/install.sh | sudo HOST=https://panel.example EN
|
|||||||
- **lp** - только long-polling.
|
- **lp** - только long-polling.
|
||||||
|
|
||||||
## Проброс портов и прямой туннель
|
## Проброс портов и прямой туннель
|
||||||
Правила создаются на странице машины «Порты». Режим `http` идёт через адрес панели,
|
Правила создаются на странице машины «Порты». Режим `http` (в панели - http-lp) идёт через
|
||||||
|
соединение агента с панелью: по WebSocket, а если он недоступен - по long-polling;
|
||||||
`tcp`/`udp` - через прямой туннель: агент держит исходящее соединение к порту туннеля хоста,
|
`tcp`/`udp` - через прямой туннель: агент держит исходящее соединение к порту туннеля хоста,
|
||||||
по нему мультиплексируются все соединения.
|
по нему мультиплексируются все соединения.
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ func (a *Agent) runTerminal(parent context.Context, sid, user, dir string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// runForward dials 127.0.0.1:port on the client and bridges it to the session,
|
// runForward dials 127.0.0.1:port on the client and bridges it to the session,
|
||||||
// giving the host a reverse TCP tunnel over the same long-poll transport.
|
// giving the host a reverse TCP tunnel over the agent's link to the server:
|
||||||
|
// the WebSocket, or the long polls without one.
|
||||||
func (a *Agent) runForward(parent context.Context, sid string, port int) {
|
func (a *Agent) runForward(parent context.Context, sid string, port int) {
|
||||||
d := net.Dialer{Timeout: 10 * time.Second}
|
d := net.Dialer{Timeout: 10 * time.Second}
|
||||||
conn, err := d.DialContext(parent, "tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(port)))
|
conn, err := d.DialContext(parent, "tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(port)))
|
||||||
@@ -92,7 +93,8 @@ func (a *Agent) bridgeStream(parent context.Context, sid string, rw io.ReadWrite
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// pollIn performs one downstream long-poll and applies the frames.
|
// pollIn takes the next input - pushed on the WebSocket, else by a long
|
||||||
|
// poll - and applies the frames.
|
||||||
// Returns false when the session is gone and the loop should stop.
|
// Returns false when the session is gone and the loop should stop.
|
||||||
func (a *Agent) pollIn(ctx context.Context, sid string, w io.Writer, onResize func(cols, rows uint16)) bool {
|
func (a *Agent) pollIn(ctx context.Context, sid string, w io.Writer, onResize func(cols, rows uint16)) bool {
|
||||||
// On the WebSocket the input comes pushed; if it drops, by a long poll.
|
// On the WebSocket the input comes pushed; if it drops, by a long poll.
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
// Package proto describes the wire messages shared by the server and the agent.
|
// Package proto describes the wire messages shared by the server and the agent.
|
||||||
//
|
//
|
||||||
// All agent<->server traffic is plain JSON over HTTP(S) long-polling so it
|
// All agent<->server traffic is plain JSON, on a WebSocket the agent opens or,
|
||||||
// survives NAT and restrictive egress firewalls: the agent only ever makes
|
// where that does not get through, over HTTP(S) long polls, so it survives
|
||||||
// outbound requests.
|
// NAT and restrictive egress firewalls: the agent only ever connects out.
|
||||||
package proto
|
package proto
|
||||||
|
|
||||||
import "strings"
|
import "strings"
|
||||||
@@ -11,7 +11,7 @@ import "encoding/json"
|
|||||||
|
|
||||||
// Control poll message types (server -> agent, returned from /api/agent/poll).
|
// Control poll message types (server -> agent, returned from /api/agent/poll).
|
||||||
const (
|
const (
|
||||||
// CtrlIdle is returned when the long-poll times out with nothing to do.
|
// CtrlIdle is returned when the control poll times out with nothing to do.
|
||||||
CtrlIdle = "idle"
|
CtrlIdle = "idle"
|
||||||
// CtrlOpenTerminal tells the agent to spawn a PTY for the session id.
|
// CtrlOpenTerminal tells the agent to spawn a PTY for the session id.
|
||||||
CtrlOpenTerminal = "open_terminal"
|
CtrlOpenTerminal = "open_terminal"
|
||||||
@@ -56,7 +56,8 @@ type EnrollResponse struct {
|
|||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ControlMessage is the body returned by the control long-poll.
|
// ControlMessage is a command for the agent: on the WebSocket, or the body of
|
||||||
|
// the control long poll.
|
||||||
type ControlMessage struct {
|
type ControlMessage struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
SessionID string `json:"session_id,omitempty"`
|
SessionID string `json:"session_id,omitempty"`
|
||||||
|
|||||||
@@ -12,8 +12,9 @@ import (
|
|||||||
|
|
||||||
// forwardManager owns the host-side listeners for port-forward rules. It
|
// forwardManager owns the host-side listeners for port-forward rules. It
|
||||||
// supports three transports:
|
// supports three transports:
|
||||||
// - "http": tunneled over the HTTP long-poll channel (works anywhere nginx
|
// - "http": over the agent's own link to the panel - its WebSocket, or the
|
||||||
// reaches, highest latency);
|
// HTTP long polls while there is none (works anywhere nginx reaches,
|
||||||
|
// highest latency);
|
||||||
// - "tcp": direct TCP multiplexed over the tunnel port (low latency);
|
// - "tcp": direct TCP multiplexed over the tunnel port (low latency);
|
||||||
// - "udp": direct UDP over the tunnel port.
|
// - "udp": direct UDP over the tunnel port.
|
||||||
type forwardManager struct {
|
type forwardManager struct {
|
||||||
@@ -103,7 +104,8 @@ func (m *forwardManager) bridgeDirect(rule ForwardRule, conn net.Conn) {
|
|||||||
at.openStream(rule.ClientPort, conn)
|
at.openStream(rule.ClientPort, conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// bridgeHTTP wires a connection through the HTTP long-poll hub session.
|
// bridgeHTTP wires a connection through a hub session: the agent carries it
|
||||||
|
// over its WebSocket, or by long polls while it has none.
|
||||||
func (m *forwardManager) bridgeHTTP(rule ForwardRule, conn net.Conn) {
|
func (m *forwardManager) bridgeHTTP(rule ForwardRule, conn net.Conn) {
|
||||||
sess := m.hub.CreateForward(rule.AgentID, rule.ClientPort)
|
sess := m.hub.CreateForward(rule.AgentID, rule.ClientPort)
|
||||||
defer m.hub.CloseSession(sess.ID)
|
defer m.hub.CloseSession(sess.ID)
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ import (
|
|||||||
"github.com/x-grk/longpolling-remote-access/internal/proto"
|
"github.com/x-grk/longpolling-remote-access/internal/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Hub is the in-memory broker between agents (HTTP long-polling) and the panel.
|
// Hub is the in-memory broker between agents (on a WebSocket, or by HTTP
|
||||||
// It carries three kinds of work to an agent through its single control poll:
|
// long polls) and the panel. It carries three kinds of work to an agent
|
||||||
|
// through its single control channel:
|
||||||
// terminal sessions, port-forward streams, and one-shot tasks (file manager).
|
// terminal sessions, port-forward streams, and one-shot tasks (file manager).
|
||||||
// Nothing here is persisted.
|
// Nothing here is persisted.
|
||||||
type Hub struct {
|
type Hub struct {
|
||||||
|
|||||||
@@ -75,7 +75,7 @@
|
|||||||
App.register('forwards', {
|
App.register('forwards', {
|
||||||
mount(root) {
|
mount(root) {
|
||||||
var notes = {
|
var notes = {
|
||||||
http: 'Трафик идёт через порт панели ' + root.dataset.panelPort + ' (long-poll). Порт ' + root.dataset.tunnelPort + ' не требуется.',
|
http: 'Трафик идёт через соединение агента с панелью (порт ' + root.dataset.panelPort + '): по WebSocket, без него - long-poll. Порт ' + root.dataset.tunnelPort + ' не требуется.',
|
||||||
tcp: 'Требуется открытый порт ' + root.dataset.tunnelPort + '/tcp на хосте. Самая низкая задержка.',
|
tcp: 'Требуется открытый порт ' + root.dataset.tunnelPort + '/tcp на хосте. Самая низкая задержка.',
|
||||||
udp: 'Требуется открытый порт ' + root.dataset.tunnelPort + '/udp на хосте.'
|
udp: 'Требуется открытый порт ' + root.dataset.tunnelPort + '/udp на хосте.'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -108,7 +108,7 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<h2>Режимы</h2>
|
<h2>Режимы</h2>
|
||||||
<ul class="muted" style="font-size:14px; line-height:1.7">
|
<ul class="muted" style="font-size:14px; line-height:1.7">
|
||||||
<li><b>http-lp</b> - туннель поверх HTTP long-poll (через адрес панели, порт {{.PanelPort}}). Работает везде, где доступна панель, но задержка выше.</li>
|
<li><b>http-lp</b> - через соединение агента с панелью (порт {{.PanelPort}}): по WebSocket, а если он недоступен - по HTTP long-poll. Работает везде, где доступна панель, но задержка выше, чем у tcp.</li>
|
||||||
<li><b>tcp</b> - прямой TCP-мультиплексор через порт <b>{{.TunnelPort}}</b> хоста. Минимальная задержка, keep-alive, TCP_NODELAY. Нужно открыть {{.TunnelPort}}/tcp на хосте.</li>
|
<li><b>tcp</b> - прямой TCP-мультиплексор через порт <b>{{.TunnelPort}}</b> хоста. Минимальная задержка, keep-alive, TCP_NODELAY. Нужно открыть {{.TunnelPort}}/tcp на хосте.</li>
|
||||||
<li><b>udp</b> - прямой UDP через порт <b>{{.TunnelPort}}</b> хоста. Нужно открыть {{.TunnelPort}}/udp.</li>
|
<li><b>udp</b> - прямой UDP через порт <b>{{.TunnelPort}}</b> хоста. Нужно открыть {{.TunnelPort}}/udp.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
Reference in new issue
Block a user