Add support tunnels via Pinggy #1853
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
# Pinggy
|
||||
|
||||
[Pinggy](https://pinggy.io/) - nice service for public tunnels to your local services.
|
||||
|
||||
**Features:**
|
||||
|
||||
- A free account does not require registration.
|
||||
- It does not require downloading third-party binaries and works over the SSH protocol.
|
||||
- Works with HTTP, TCP and UDP protocols.
|
||||
- Creates HTTPS for your HTTP services.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> A free account creates a tunnel with a random address that only works for an hour. It is suitable for testing purposes ONLY.
|
||||
|
||||
> [!CAUTION]
|
||||
> Public access to go2rtc without authorization puts your entire home network at risk. Use with caution.
|
||||
|
||||
**Why:**
|
||||
|
||||
- It's easy to set up HTTPS for testing two-way audio.
|
||||
- It's easy to check whether external access via WebRTC technology will work.
|
||||
- It's easy to share direct access to your RTSP or HTTP camera with the go2rtc developer. If such access is necessary to debug your problem.
|
||||
|
||||
## Configuration
|
||||
|
||||
You will find public links in the go2rtc log after startup.
|
||||
|
||||
**Tunnel to go2rtc WebUI.**
|
||||
|
||||
```yaml
|
||||
pinggy:
|
||||
tunnel: http://localhost:1984
|
||||
```
|
||||
|
||||
**Tunnel to RTSP camera.**
|
||||
|
||||
For example, you have camera: `rtsp://admin:password@192.168.1.123/cam/realmonitor?channel=1&subtype=0`
|
||||
|
||||
```yaml
|
||||
pinggy:
|
||||
tunnel: tcp://192.168.10.91:554
|
||||
```
|
||||
|
||||
In go2rtc logs you will get similar output:
|
||||
|
||||
```
|
||||
16:17:43.167 INF [pinggy] proxy url=tcp://abcde-123-123-123-123.a.free.pinggy.link:12345
|
||||
```
|
||||
|
||||
Now you have working stream:
|
||||
|
||||
```
|
||||
rtsp://admin:password@abcde-123-123-123-123.a.free.pinggy.link:12345/cam/realmonitor?channel=1&subtype=0
|
||||
```
|
||||
@@ -0,0 +1,60 @@
|
||||
package pinggy
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/AlexxIT/go2rtc/internal/app"
|
||||
"github.com/AlexxIT/go2rtc/pkg/pinggy"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
func Init() {
|
||||
var cfg struct {
|
||||
Mod struct {
|
||||
Tunnel string `yaml:"tunnel"`
|
||||
} `yaml:"pinggy"`
|
||||
}
|
||||
|
||||
app.LoadConfig(&cfg)
|
||||
|
||||
if cfg.Mod.Tunnel == "" {
|
||||
return
|
||||
}
|
||||
|
||||
log = app.GetLogger("pinggy")
|
||||
|
||||
u, err := url.Parse(cfg.Mod.Tunnel)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Send()
|
||||
return
|
||||
}
|
||||
|
||||
go proxy(u.Scheme, u.Host)
|
||||
}
|
||||
|
||||
var log zerolog.Logger
|
||||
|
||||
func proxy(proto, address string) {
|
||||
client, err := pinggy.NewClient(proto)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Send()
|
||||
return
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
urls, err := client.GetURLs()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Send()
|
||||
return
|
||||
}
|
||||
|
||||
for _, s := range urls {
|
||||
log.Info().Str("url", s).Msgf("[pinggy] proxy")
|
||||
}
|
||||
|
||||
err = client.Proxy(address)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Send()
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
"github.com/AlexxIT/go2rtc/internal/nest"
|
||||
"github.com/AlexxIT/go2rtc/internal/ngrok"
|
||||
"github.com/AlexxIT/go2rtc/internal/onvif"
|
||||
"github.com/AlexxIT/go2rtc/internal/pinggy"
|
||||
"github.com/AlexxIT/go2rtc/internal/ring"
|
||||
"github.com/AlexxIT/go2rtc/internal/roborock"
|
||||
"github.com/AlexxIT/go2rtc/internal/rtmp"
|
||||
@@ -99,6 +100,7 @@ func main() {
|
||||
// Helper modules
|
||||
{"debug", debug.Init},
|
||||
{"ngrok", ngrok.Init},
|
||||
{"pinggy", pinggy.Init},
|
||||
{"srtp", srtp.Init},
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
package pinggy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
SSH *ssh.Client
|
||||
TCP net.Listener
|
||||
API *http.Client
|
||||
}
|
||||
|
||||
func NewClient(proto string) (*Client, error) {
|
||||
switch proto {
|
||||
case "http", "tcp", "tls", "tlstcp":
|
||||
case "":
|
||||
proto = "http"
|
||||
default:
|
||||
return nil, errors.New("pinggy: unsupported proto: " + proto)
|
||||
}
|
||||
|
||||
config := &ssh.ClientConfig{
|
||||
User: "auth+" + proto,
|
||||
Auth: []ssh.AuthMethod{ssh.Password("nopass")},
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
client, err := ssh.Dial("tcp", "a.pinggy.io:443", config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ln, err := client.Listen("tcp", "0.0.0.0:0")
|
||||
if err != nil {
|
||||
_ = client.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c := &Client{
|
||||
SSH: client,
|
||||
TCP: ln,
|
||||
API: &http.Client{
|
||||
Transport: &http.Transport{
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return client.Dial(network, addr)
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if proto == "http" {
|
||||
if err = c.NewSession(); err != nil {
|
||||
_ = client.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *Client) Close() error {
|
||||
return errors.Join(c.SSH.Close(), c.TCP.Close())
|
||||
}
|
||||
|
||||
func (c *Client) NewSession() error {
|
||||
session, err := c.SSH.NewSession()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return session.Shell()
|
||||
}
|
||||
|
||||
func (c *Client) GetURLs() ([]string, error) {
|
||||
res, err := c.API.Get("http://localhost:4300/urls")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
var v struct {
|
||||
URLs []string `json:"urls"`
|
||||
}
|
||||
|
||||
if err = json.NewDecoder(res.Body).Decode(&v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return v.URLs, nil
|
||||
}
|
||||
|
||||
func (c *Client) Proxy(address string) error {
|
||||
defer c.TCP.Close()
|
||||
|
||||
for {
|
||||
conn, err := c.TCP.Accept()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go proxy(conn, address)
|
||||
}
|
||||
}
|
||||
|
||||
func proxy(conn1 net.Conn, address string) {
|
||||
defer conn1.Close()
|
||||
|
||||
conn2, err := net.Dial("tcp", address)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer conn2.Close()
|
||||
|
||||
go io.Copy(conn2, conn1)
|
||||
io.Copy(conn1, conn2)
|
||||
}
|
||||
|
||||
// DialTLS like ssh.Dial but with TLS
|
||||
//func DialTLS(network, addr, sni string, config *ssh.ClientConfig) (*ssh.Client, error) {
|
||||
// conn, err := net.DialTimeout(network, addr, config.Timeout)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// conn = tls.Client(conn, &tls.Config{ServerName: sni, InsecureSkipVerify: sni == ""})
|
||||
// c, chans, reqs, err := ssh.NewClientConn(conn, addr, config)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// return ssh.NewClient(c, chans, reqs), nil
|
||||
//}
|
||||
Reference in New Issue
Block a user