-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #58 from reubenmiller/feat-remote-access-client
feat: remote access (local proxy) client
- Loading branch information
Showing
10 changed files
with
487 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package c8y | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net/http" | ||
) | ||
|
||
const ( | ||
RemoteAccessProtocolPassthrough = "PASSTHROUGH" | ||
RemoteAccessProtocolSSH = "SSH" | ||
RemoteAccessProtocolVNC = "VNC" | ||
RemoteAccessProtocolTelnet = "TELNET" | ||
) | ||
|
||
// RemoteAccessService | ||
type RemoteAccessService service | ||
|
||
// RemoteAccessCollectionOptions remote access collection filter options | ||
type RemoteAccessCollectionOptions struct { | ||
// Pagination options | ||
PaginationOptions | ||
} | ||
|
||
// RemoteAccessCollection collection of remote access configurations | ||
type RemoteAccessConfiguration struct { | ||
ID string `json:"id"` | ||
Name string `json:"name"` | ||
Hostname string `json:"hostname"` | ||
Port int `json:"port"` | ||
Protocol string `json:"protocol"` | ||
Credentials RemoteAccessCredentials `json:"credentials"` | ||
} | ||
|
||
// RemoteAccessCredentials | ||
type RemoteAccessCredentials struct { | ||
Type string `json:"type"` | ||
Username string `json:"username"` | ||
Password string `json:"password"` | ||
PublicKey string `json:"publicKey"` | ||
PrivateKey string `json:"privateKey"` | ||
HostKey string `json:"hostKey"` | ||
} | ||
|
||
func (s *RemoteAccessService) path(mo_id string) string { | ||
return fmt.Sprintf("/service/remoteaccess/devices/%s/configurations", mo_id) | ||
} | ||
func (s *RemoteAccessService) config_path(mo_id string, config_id string) string { | ||
return fmt.Sprintf("/service/remoteaccess/devices/%s/configurations/%s", mo_id, config_id) | ||
} | ||
|
||
// GetConfiguration return a specific remote access configuration for a given device | ||
func (s *RemoteAccessService) GetConfiguration(ctx context.Context, mo_id, config_id string) (*RemoteAccessConfiguration, *Response, error) { | ||
data := new(RemoteAccessConfiguration) | ||
resp, err := s.client.SendRequest(ctx, RequestOptions{ | ||
Method: http.MethodGet, | ||
Path: s.config_path(mo_id, config_id), | ||
ResponseData: data, | ||
}) | ||
return data, resp, err | ||
} | ||
|
||
// GetConfigurations returns a collection of Cumulocity remote access configurations | ||
// for a given managed object | ||
func (s *RemoteAccessService) GetConfigurations(ctx context.Context, mo_id string, opt *RemoteAccessCollectionOptions) ([]RemoteAccessConfiguration, *Response, error) { | ||
data := make([]RemoteAccessConfiguration, 0) | ||
resp, err := s.client.SendRequest(ctx, RequestOptions{ | ||
Method: http.MethodGet, | ||
Path: s.path(mo_id), | ||
Query: opt, | ||
ResponseData: &data, | ||
}) | ||
return data, resp, err | ||
} | ||
|
||
// DeleteConfiguration delete remote access configuration | ||
func (s *RemoteAccessService) DeleteConfiguration(ctx context.Context, mo_id string, config_id string, opt *RemoteAccessCollectionOptions) (*Response, error) { | ||
resp, err := s.client.SendRequest(ctx, RequestOptions{ | ||
Method: http.MethodDelete, | ||
Path: s.config_path(mo_id, config_id), | ||
Query: opt, | ||
}) | ||
return resp, err | ||
} | ||
|
||
// Create creates a new operation for a device | ||
func (s *RemoteAccessService) Create(ctx context.Context, mo_id string, config_id string, body interface{}) (*RemoteAccessConfiguration, *Response, error) { | ||
data := new(RemoteAccessConfiguration) | ||
resp, err := s.client.SendRequest(ctx, RequestOptions{ | ||
Method: http.MethodPost, | ||
Path: s.config_path(mo_id, config_id), | ||
Body: body, | ||
ResponseData: data, | ||
}) | ||
return data, resp, err | ||
} | ||
|
||
// Update updates a Cumulocity operation | ||
func (s *RemoteAccessService) Update(ctx context.Context, mo_id string, config_id string, body *OperationUpdateOptions) (*Operation, *Response, error) { | ||
data := new(Operation) | ||
resp, err := s.client.SendRequest(ctx, RequestOptions{ | ||
Method: http.MethodPut, | ||
Path: s.config_path(mo_id, config_id), | ||
Body: body, | ||
ResponseData: data, | ||
}) | ||
return data, resp, err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package proxy | ||
|
||
import ( | ||
"io" | ||
"net" | ||
|
||
"github.com/gorilla/websocket" | ||
"github.com/reubenmiller/go-c8y/pkg/c8y" | ||
"github.com/reubenmiller/go-c8y/pkg/wsconnadapter" | ||
) | ||
|
||
func chanFromConn(conn io.Reader) chan []byte { | ||
c := make(chan []byte) | ||
|
||
go func() { | ||
b := make([]byte, 1024) | ||
|
||
for { | ||
n, err := conn.Read(b) | ||
if n > 0 { | ||
res := make([]byte, n) | ||
// Copy the buffer so it doesn't get changed while read by the recipient. | ||
copy(res, b[:n]) | ||
c <- res | ||
} | ||
if err != nil { | ||
c <- nil | ||
break | ||
} | ||
} | ||
}() | ||
|
||
return c | ||
} | ||
|
||
// Copy accepts a websocket connection and TCP connection and copies data between them | ||
func Copy(gwsConn *websocket.Conn, tcpConn net.Conn) { | ||
wsConn := wsconnadapter.New(gwsConn) | ||
wsChan := chanFromConn(wsConn) | ||
tcpChan := chanFromConn(tcpConn) | ||
|
||
defer wsConn.Close() | ||
defer tcpConn.Close() | ||
for { | ||
select { | ||
case wsData := <-wsChan: | ||
if wsData == nil { | ||
c8y.Logger.Infof("Connection closed: D: %v, S: %v", tcpConn.LocalAddr(), wsConn.RemoteAddr()) | ||
return | ||
} else { | ||
tcpConn.Write(wsData) | ||
} | ||
case tcpData := <-tcpChan: | ||
if tcpData == nil { | ||
c8y.Logger.Infof("Connection closed: D: %v, S: %v", tcpConn.LocalAddr(), wsConn.LocalAddr()) | ||
return | ||
} else { | ||
wsConn.Write(tcpData) | ||
} | ||
} | ||
} | ||
|
||
} | ||
|
||
// Copy accepts a websocket connection and read/writer and copies data between them | ||
func CopyReadWriter(gwsConn *websocket.Conn, r io.ReadCloser, w io.Writer) { | ||
wsConn := wsconnadapter.New(gwsConn) | ||
wsChan := chanFromConn(wsConn) | ||
stdioChan := chanFromConn(r) | ||
|
||
defer wsConn.Close() | ||
defer r.Close() | ||
for { | ||
select { | ||
case wsData := <-wsChan: | ||
if wsData == nil { | ||
c8y.Logger.Infof("STDIO connection closed: D: %v, S: %v", "stdio", wsConn.RemoteAddr()) | ||
return | ||
} else { | ||
w.Write(wsData) | ||
} | ||
case tcpData := <-stdioChan: | ||
if tcpData == nil { | ||
c8y.Logger.Infof("STDIO connection closed: D: %v, S: %v", "stdio", wsConn.LocalAddr()) | ||
return | ||
} else { | ||
wsConn.Write(tcpData) | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.