diff --git a/cmd/dmsgweb/README.md b/cmd/dmsgweb/README.md
index ac418b38..c5fe77fa 100644
--- a/cmd/dmsgweb/README.md
+++ b/cmd/dmsgweb/README.md
@@ -2,31 +2,79 @@
```
+$ go run cmd/dmsgweb/dmsgweb.go --help
-┌┬┐┌┬┐┌─┐┌─┐┬ ┬┌─┐┌┐
- │││││└─┐│ ┬│││├┤ ├┴┐
-─┴┘┴ ┴└─┘└─┘└┴┘└─┘└─┘
-DMSG resolving proxy & browser client - access websites over dmsg
+ ┌┬┐┌┬┐┌─┐┌─┐┬ ┬┌─┐┌┐
+ │││││└─┐│ ┬│││├┤ ├┴┐
+ ─┴┘┴ ┴└─┘└─┘└┴┘└─┘└─┘
+DMSG resolving proxy & browser client - access websites and http interfaces over dmsg
+.conf file may also be specified with
+DMSGWEB=/path/to/dmsgweb.conf skywire dmsg web
Usage:
-web
+ dmsgweb
Available Commands:
-completion Generate the autocompletion script for the specified shell
-gen-keys generate public / secret keypair
+ completion Generate the autocompletion script for the specified shell
+ srv Serve HTTP or raw TCP from local port over DMSG
Flags:
--d, --dmsg-disc string dmsg discovery url default:
- http://dmsgd.skywire.skycoin.com
--f, --filter string domain suffix to filter (default ".dmsg")
--l, --loglvl string [ debug | warn | error | fatal | panic | trace | info ]
--p, --port string port to serve the web application (default "8080")
--r, --proxy string configure additional socks5 proxy for dmsgweb (i.e. 127.0.0.1:1080)
--t, --resolve string resolve the specified dmsg address:port on the local port & disable proxy
--e, --sess int number of dmsg servers to connect to (default 1)
--s, --sk cipher.SecKey a random key is generated if unspecified
-(default 0000000000000000000000000000000000000000000000000000000000000000)
--q, --socks string port to serve the socks5 proxy (default "4445")
--v, --version version for web
+ -r, --addproxy string configure additional socks5 proxy for dmsgweb (i.e. 127.0.0.1:1080)
+
+ -D, --dmsg-disc string dmsg discovery url
+ (default "http://dmsgd.skywire.skycoin.com")
+ -z, --envs show example .conf file
+
+ -f, --filter string domain suffix to filter
+ (default ".dmsg")
+ -l, --loglvl string [ debug | warn | error | fatal | panic | trace | info ]
+
+ -p, --port uints port(s) to serve the web application
+ (default [8080])
+ -x, --proxy string connect to dmsg via proxy (i.e. '127.0.0.1:1080')
+
+ -t, --resolve strings resolve the specified dmsg address:port on the local port & disable proxy
+
+ -c, --rt bools proxy local port as raw TCP
+ (default [false])
+ -e, --sess int number of dmsg servers to connect to
+ (default 1)
+ -s, --sk cipher.SecKey a random key is generated if unspecified
+ (default 0000000000000000000000000000000000000000000000000000000000000000)
+ -q, --socks uint port to serve the socks5 proxy
+ (default 4445)
+ -v, --version version for dmsgweb
+
+```
+
+```
+$ go run cmd/dmsgweb/dmsgweb.go srv --help
+DMSG web server - serve HTTP or raw TCP interface from local port over DMSG
+ .conf file may also be specified with DMSGWEBSRV=/path/to/dmsgwebsrv.conf skywire dmsg web srv
+
+Usage:
+ dmsgweb srv [flags]
+
+Flags:
+ -D, --dmsg-disc string DMSG discovery URL
+ (default "http://dmsgd.skywire.skycoin.com")
+ -d, --dport uints DMSG port(s) to serve
+ (default [80])
+ -e, --dsess int DMSG sessions
+ (default 1)
+ -z, --envs show example .conf file
+
+ -l, --loglvl string [ debug | warn | error | fatal | panic | trace | info ]
+
+ -p, --lport uints local application interface port(s)
+ (default [8086])
+ -x, --proxy string connect to DMSG via proxy (e.g., '127.0.0.1:1080')
+
+ -c, --rt bools proxy local port as raw TCP, comma separated
+ (default [false])
+ -s, --sk cipher.SecKey a random key is generated if unspecified
+ (default 0000000000000000000000000000000000000000000000000000000000000000)
+ -w, --wl strings whitelisted keys for DMSG authenticated routes
+
```
diff --git a/cmd/dmsgweb/commands/dmsgweb.go b/cmd/dmsgweb/commands/dmsgweb.go
index 2eb6e971..7db2a4c1 100644
--- a/cmd/dmsgweb/commands/dmsgweb.go
+++ b/cmd/dmsgweb/commands/dmsgweb.go
@@ -5,19 +5,17 @@ import (
"context"
"fmt"
"io"
- "log"
"net"
"net/http"
"os"
"os/signal"
"path/filepath"
"regexp"
- "runtime"
"strconv"
"strings"
+ "sync"
"syscall"
- "github.com/bitfield/script"
"github.com/chen3feng/safecast"
"github.com/confiant-inc/go-socks5"
"github.com/gin-gonic/gin"
@@ -36,7 +34,6 @@ import (
type customResolver struct{}
func (r *customResolver) Resolve(ctx context.Context, name string) (context.Context, net.IP, error) {
- // Handle custom name resolution for .dmsg domains
regexPattern := `\.` + filterDomainSuffix + `(:[0-9]+)?$`
match, _ := regexp.MatchString(regexPattern, name) //nolint:errcheck
if match {
@@ -44,37 +41,44 @@ func (r *customResolver) Resolve(ctx context.Context, name string) (context.Cont
if ip == nil {
return ctx, nil, fmt.Errorf("failed to parse IP address")
}
- // Modify the context to include the desired port
ctx = context.WithValue(ctx, "port", fmt.Sprintf("%v", webPort)) //nolint
return ctx, ip, nil
}
- // Use default name resolution for other domains
return ctx, nil, nil
}
-const dmsgwebenvname = "DMSGWEB"
+const dwenv = "DMSGWEB"
-var dmsgwebconffile = os.Getenv(dmsgwebenvname)
+var dwcfg = os.Getenv(dwenv)
func init() {
- RootCmd.Flags().StringVarP(&filterDomainSuffix, "filter", "f", ".dmsg", "domain suffix to filter")
- RootCmd.Flags().UintVarP(&proxyPort, "socks", "q", scriptExecUint("${PROXYPORT:-4445}", dmsgwebconffile), "port to serve the socks5 proxy")
- RootCmd.Flags().StringVarP(&addProxy, "addproxy", "r", scriptExecString("${ADDPROXY}", dmsgwebconffile), "configure additional socks5 proxy for dmsgweb (i.e. 127.0.0.1:1080)")
- RootCmd.Flags().UintSliceVarP(&webPort, "port", "p", scriptExecUintSlice("${WEBPORT[@]:-8080}", dmsgwebconffile), "port(s) to serve the web application")
- RootCmd.Flags().StringSliceVarP(&resolveDmsgAddr, "resolve", "t", scriptExecStringSlice("${RESOLVEPK[@]}", dmsgwebconffile), "resolve the specified dmsg address:port on the local port & disable proxy")
- RootCmd.Flags().StringVarP(&dmsgDisc, "dmsg-disc", "D", dmsgDisc, "dmsg discovery url(s)")
- RootCmd.Flags().StringVarP(&proxyAddr, "proxy", "x", "", "connect to dmsg via proxy (i.e. '127.0.0.1:1080')")
- RootCmd.Flags().IntVarP(&dmsgSessions, "sess", "e", scriptExecInt("${DMSGSESSIONS:-1}", dmsgwebconffile), "number of dmsg servers to connect to")
- RootCmd.Flags().BoolSliceVarP(&rawTCP, "rt", "c", scriptExecBoolSlice("${RAWTCP[@]:-false}", dmsgwebconffile), "proxy local port as raw TCP")
- RootCmd.Flags().StringVarP(&logLvl, "loglvl", "l", "", "[ debug | warn | error | fatal | panic | trace | info ]\033[0m")
+ dmsgDisc = dmsg.DiscAddr(false)
+ webPort = scriptExecUintSlice("${WEBPORT[@]:-8080}", dwcfg)
+ proxyPort = scriptExecUint("${PROXYPORT:-4445}", dwcfg)
+ addProxy = scriptExecString("${ADDPROXY}", dwcfg)
+ resolveDmsgAddr = scriptExecStringSlice("${RESOLVEPK[@]}", dwcfg)
+ dmsgSess = scriptExecInt("${DMSGSESSIONS:-1}", dwcfg)
+ rawTCP = scriptExecBoolSlice("${RAWTCP[@]:-false}", dwcfg)
if os.Getenv("DMSGWEBSK") != "" {
sk.Set(os.Getenv("DMSGWEBSK")) //nolint
}
- if scriptExecString("${DMSGWEBSK}", dmsgwebconffile) != "" {
- sk.Set(scriptExecString("${DMSGWEBSK}", dmsgwebconffile)) //nolint
+ if scriptExecString("${DMSGWEBSK}", dwcfg) != "" {
+ sk.Set(scriptExecString("${DMSGWEBSK}", dwcfg)) //nolint
}
+ pk, _ = sk.PubKey() //nolint
+
+ RootCmd.Flags().StringVarP(&filterDomainSuffix, "filter", "f", ".dmsg", "domain suffix to filter\033[0m\n\r")
+ RootCmd.Flags().UintVarP(&proxyPort, "socks", "q", proxyPort, "port to serve the socks5 proxy\033[0m\n\r")
+ RootCmd.Flags().StringVarP(&addProxy, "addproxy", "r", addProxy, "configure additional socks5 proxy for dmsgweb (i.e. 127.0.0.1:1080)\033[0m\n\r")
+ RootCmd.Flags().UintSliceVarP(&webPort, "port", "p", webPort, "port(s) to serve the web application\033[0m\n\r")
+ RootCmd.Flags().StringSliceVarP(&resolveDmsgAddr, "resolve", "t", resolveDmsgAddr, "resolve the specified dmsg address:port on the local port & disable proxy\033[0m\n\r")
+ RootCmd.Flags().StringVarP(&dmsgDisc, "dmsg-disc", "D", dmsgDisc, "dmsg discovery url\033[0m\n\r")
+ RootCmd.Flags().StringVarP(&proxyAddr, "proxy", "x", "", "connect to dmsg via proxy (i.e. '127.0.0.1:1080')\033[0m\n\r")
+ RootCmd.Flags().IntVarP(&dmsgSessions, "sess", "e", dmsgSess, "number of dmsg servers to connect to\033[0m\n\r")
+ RootCmd.Flags().BoolSliceVarP(&rawTCP, "rt", "c", rawTCP, "proxy local port as raw TCP\033[0m\n\r")
+ RootCmd.Flags().StringVarP(&logLvl, "loglvl", "l", "debug", "[ debug | warn | error | fatal | panic | trace | info ]\033[0m\n\r")
RootCmd.Flags().VarP(&sk, "sk", "s", "a random key is generated if unspecified\n\r")
- RootCmd.Flags().BoolVarP(&isEnvs, "envs", "z", false, "show example .conf file")
+ RootCmd.Flags().BoolVarP(&isEnvs, "envs", "z", false, "show example .conf file\033[0m\n\r")
}
@@ -89,65 +93,44 @@ var RootCmd = &cobra.Command{
│││││└─┐│ ┬│││├┤ ├┴┐
─┴┘┴ ┴└─┘└─┘└┴┘└─┘└─┘
DMSG resolving proxy & browser client - access websites and http interfaces over dmsg` + func() string {
- if _, err := os.Stat(dmsgwebconffile); err == nil {
+ if _, err := os.Stat(dwcfg); err == nil {
return `
-dmsgweb conf file detected: ` + dmsgwebconffile
+dmsgweb conf file detected: ` + dwcfg
}
return `
.conf file may also be specified with
-` + dmsgwebenvname + `=/path/to/dmsgweb.conf skywire dmsg web`
+` + dwenv + `=/path/to/dmsgweb.conf skywire dmsg web`
}(),
SilenceErrors: true,
SilenceUsage: true,
DisableSuggestions: true,
DisableFlagsInUseLine: true,
Version: buildinfo.Version(),
- Run: func(_ *cobra.Command, _ []string) {
+ PreRun: func(_ *cobra.Command, _ []string) {
if isEnvs {
- envfile := envfileLinux
- if runtime.GOOS == "windows" {
- envfileslice, _ := script.Echo(envfile).Slice() //nolint
- for i := range envfileslice {
- efs, _ := script.Echo(envfileslice[i]).Reject("##").Reject("#-").Reject("# ").Replace("#", "#$").String() //nolint
- if efs != "" && efs != "\n" {
- envfileslice[i] = strings.ReplaceAll(efs, "\n", "")
- }
- }
- envfile = strings.Join(envfileslice, "\n")
- }
- fmt.Println(envfile)
- os.Exit(0)
- }
-
- c := make(chan os.Signal, 1)
- signal.Notify(c, os.Interrupt, syscall.SIGTERM) //nolint
- go func() {
- <-c
- os.Exit(1)
- }()
- if dmsgWebLog == nil {
- dmsgWebLog = logging.MustGetLogger("dmsgweb")
+ printEnvs(srvenvfileLinux)
}
if logLvl != "" {
if lvl, err := logging.LevelFromString(logLvl); err == nil {
logging.SetLevel(lvl)
}
}
+ dLog = logging.MustGetLogger("dmsgweb")
if dmsgDisc == "" {
- dmsgWebLog.Fatal("Dmsg Discovery URL not specified")
+ dLog.Fatal("Dmsg Discovery URL not specified")
}
if len(resolveDmsgAddr) > 0 && len(webPort) != len(resolveDmsgAddr) {
- dmsgWebLog.Fatal("-resolve --t flag cannot contain a different number of elements than -port -p flag")
+ dLog.Fatal("-resolve --t flag cannot contain a different number of elements than -port -p flag")
}
if len(resolveDmsgAddr) == 0 && len(webPort) > 1 {
- dmsgWebLog.Fatal("-port --p flag cannot specify multiple ports without specifying multiple dmsg address:port(s) to -resolve --t flag")
+ dLog.Fatal("-port --p flag cannot specify multiple ports without specifying multiple dmsg address:port(s) to -resolve --t flag")
}
seenResolveDmsgAddr := make(map[string]bool)
for _, item := range resolveDmsgAddr {
if seenResolveDmsgAddr[item] {
- dmsgWebLog.Fatal("-resolve --t flag cannot contain duplicates")
+ dLog.Fatal("-resolve --t flag cannot contain duplicates")
}
seenResolveDmsgAddr[item] = true
}
@@ -155,7 +138,7 @@ dmsgweb conf file detected: ` + dmsgwebconffile
seenWebPort := make(map[uint]bool)
for _, item := range webPort {
if seenWebPort[item] {
- dmsgWebLog.Fatal("-port --p flag cannot contain duplicates")
+ dLog.Fatal("-port --p flag cannot contain duplicates")
}
seenWebPort[item] = true
}
@@ -167,47 +150,68 @@ dmsgweb conf file detected: ` + dmsgwebconffile
} else if len(rawTCP) > len(resolveDmsgAddr) {
rawTCP = rawTCP[:len(resolveDmsgAddr)]
}
-
+ if len(webPort) == 0 {
+ dLog.Fatal("webPort is empty. Ensure at least one port is specified.")
+ }
if filterDomainSuffix == "" {
- dmsgWebLog.Fatal("domain suffix to filter cannot be an empty string")
+ dLog.Fatal("domain suffix to filter cannot be an empty string")
}
- ctx, cancel := cmdutil.SignalContext(context.Background(), dmsgWebLog)
+ },
+ Run: func(_ *cobra.Command, _ []string) {
+ c := make(chan os.Signal, 1)
+ signal.Notify(c, os.Interrupt, syscall.SIGTERM) //nolint
+ go func() {
+ <-c
+ os.Exit(0)
+ }()
+
+ ctx, cancel := cmdutil.SignalContext(context.Background(), dLog)
defer cancel()
- pk, err := sk.PubKey()
+ pk, err = sk.PubKey()
if err != nil {
pk, sk = cipher.GenerateKeyPair()
}
- dmsgWebLog.Info("dmsg client pk: ", pk.String())
+ dLog.Info("dmsg client pk: ", pk.String())
if len(resolveDmsgAddr) > 0 {
dialPK = make([]cipher.PubKey, len(resolveDmsgAddr))
- dmsgPorts = make([]uint, dmsgSessions)
+ dmsgPorts = make([]uint, len(resolveDmsgAddr))
+
for i, dmsgaddr := range resolveDmsgAddr {
- dmsgWebLog.Info("dmsg address to dial: ", dmsgaddr)
- dmsgAddr = strings.Split(dmsgaddr, ":")
- var setpk cipher.PubKey
- err := setpk.Set(dmsgAddr[0])
- if err != nil {
- log.Fatalf("failed to parse dmsg
: : %v", err)
+ dLog.Info("dmsg address to dial: ", dmsgaddr)
+
+ // Split the address into public key and port
+ parts := strings.Split(dmsgaddr, ":")
+ if len(parts) < 1 || parts[0] == "" {
+ dLog.Fatal("Invalid dmsg address format. Expected [:]")
}
- dialPK[i] = setpk
- if len(dmsgAddr) > 1 {
- dport, err := strconv.ParseUint(dmsgAddr[1], 10, 64)
+
+ // Parse the public key
+ var pk cipher.PubKey
+ if err := pk.Set(parts[0]); err != nil {
+ dLog.WithError(err).Fatal("Failed to parse public key from dmsg address")
+ }
+ dialPK[i] = pk
+ dLog.Info("Parsed public key: ", pk)
+
+ // Parse the port or use the default (80)
+ port := uint(80) // Default port
+ if len(parts) > 1 && parts[1] != "" {
+ parsedPort, err := strconv.ParseUint(parts[1], 10, 16) // Ports are 16-bit unsigned integers
if err != nil {
- log.Fatalf("Failed to parse dmsg port: %v", err)
+ dLog.WithError(err).Fatal("Failed to parse dmsg port")
}
- dmsgPorts[i] = uint(dport)
- } else {
- dmsgPorts[i] = uint(80)
+ port = uint(parsedPort)
}
+ dmsgPorts[i] = port
+ dLog.Info("Parsed port: ", port)
}
}
if proxyAddr != "" {
- // Use SOCKS5 proxy dialer if specified
dialer, err = proxy.SOCKS5("tcp", proxyAddr, nil, proxy.Direct)
if err != nil {
- log.Fatalf("Error creating SOCKS5 dialer: %v", err)
+ dLog.WithError(err).Fatal("Error creating SOCKS5 dialer")
}
transport := &http.Transport{
Dial: dialer.Dial,
@@ -218,9 +222,9 @@ dmsgweb conf file detected: ` + dmsgwebconffile
ctx = context.WithValue(context.Background(), "socks5_proxy", proxyAddr) //nolint
}
- dmsgC, closeDmsg, err := cli.StartDmsg(ctx, dmsgWebLog, pk, sk, &httpC, dmsgDisc, dmsgSessions)
+ dmsgC, closeDmsg, err = cli.StartDmsg(ctx, dLog, pk, sk, &http.Client{}, dmsgDisc, dmsgSessions)
if err != nil {
- dmsgWebLog.WithError(err).Fatal("failed to start dmsg")
+ dLog.WithError(err).Fatal("failed to start dmsg")
}
defer closeDmsg()
@@ -234,7 +238,6 @@ dmsgweb conf file detected: ` + dmsgwebconffile
httpC = http.Client{Transport: dmsghttp.MakeHTTPTransport(ctx, dmsgC)}
if len(resolveDmsgAddr) == 0 {
- // Create a SOCKS5 server with custom name resolution
conf := &socks5.Config{
Resolver: &customResolver{},
Dial: func(ctx context.Context, network, addr string) (net.Conn, error) {
@@ -252,7 +255,6 @@ dmsgweb conf file detected: ` + dmsgwebconffile
addr = "localhost:" + port
} else {
if addProxy != "" {
- // Fallback to another SOCKS5 proxy
dialer, err := proxy.SOCKS5("tcp", addProxy, nil, proxy.Direct)
if err != nil {
return nil, err
@@ -260,44 +262,48 @@ dmsgweb conf file detected: ` + dmsgwebconffile
return dialer.Dial(network, addr)
}
}
- dmsgWebLog.Debug("Dialing address:", addr)
+ dLog.Debug("Dialing address:", addr)
return net.Dial(network, addr)
},
}
- // Start the SOCKS5 server
socksAddr := fmt.Sprintf("127.0.0.1:%v", proxyPort)
- log.Printf("SOCKS5 proxy server started on %s", socksAddr)
+ dLog.Debug("SOCKS5 proxy server started on", socksAddr)
server, err := socks5.New(conf)
if err != nil {
- log.Fatalf("Failed to create SOCKS5 server: %v", err)
+ dLog.WithError(err).Fatal("Failed to create SOCKS5 server")
}
wg.Add(1)
go func() {
- dmsgWebLog.Debug("Serving SOCKS5 proxy on " + socksAddr)
+ dLog.Debug("Serving SOCKS5 proxy on " + socksAddr)
err := server.ListenAndServe("tcp", socksAddr)
if err != nil {
- log.Fatalf("Failed to start SOCKS5 server: %v", err)
+ dLog.WithError(err).Fatal("Failed to start SOCKS5 server")
}
- defer server.Close()
- dmsgWebLog.Debug("Stopped serving SOCKS5 proxy on " + socksAddr)
+ defer server.Close() //nolint
+ dLog.Debug("Stopped serving SOCKS5 proxy on " + socksAddr)
}()
}
if len(resolveDmsgAddr) == 0 && len(webPort) == 1 {
if rawTCP[0] {
+ dLog.Debug("proxyTCPConn(-1)")
proxyTCPConn(-1)
} else {
+ dLog.Debug("proxyHTTPConn(-1)")
proxyHTTPConn(-1)
}
} else {
for i := range resolveDmsgAddr {
+ wg.Add(1)
if rawTCP[i] {
- proxyTCPConn(i)
+ dLog.Debug("proxyTCPConn(" + fmt.Sprintf("%v", i) + ")")
+ go proxyTCPConn(i)
} else {
- proxyHTTPConn(i)
+ dLog.Debug("proxyHTTPConn(" + fmt.Sprintf("%v", i) + ")")
+ go proxyHTTPConn(i)
}
}
}
@@ -305,6 +311,69 @@ dmsgweb conf file detected: ` + dmsgwebconffile
},
}
+func proxyTCPConn(n int) {
+ var thiswebport uint
+ if n == -1 {
+ thiswebport = webPort[0]
+ } else {
+ thiswebport = webPort[n]
+ }
+ listener, err := net.Listen("tcp", fmt.Sprintf(":%v", thiswebport))
+ if err != nil {
+ dLog.WithError(err).Fatal(fmt.Sprintf("Failed to start TCP listener on port: %v", thiswebport))
+ }
+ defer listener.Close() //nolint
+ dLog.Debug("Serving TCP on 127.0.0.1:", thiswebport)
+ if dmsgC == nil {
+ dLog.Fatal("dmsgC is nil")
+ }
+
+ for {
+ conn, err := listener.Accept()
+ if err != nil {
+ dLog.WithError(err).Warn("Failed to accept connection")
+ continue
+ }
+
+ go func(conn net.Conn, n int, dmsgC *dmsg.Client) {
+ defer conn.Close() //nolint
+ dp, ok := safecast.To[uint16](dmsgPorts[n])
+ if !ok {
+ dLog.Fatal("uint16 overflow when converting dmsg port")
+ }
+ dLog.Debug(fmt.Sprintf("Dialing %v:%v", dialPK[n].String(), dp))
+ dmsgConn, err := dmsgC.DialStream(context.Background(), dmsg.Addr{PK: dialPK[n], Port: dp}) //nolint
+ if err != nil {
+ dLog.WithError(err).Warn(fmt.Sprintf("Failed to dial dmsg address %v port %v", dialPK[n].String(), dmsgPorts[n]))
+ return
+ }
+
+ defer dmsgConn.Close() //nolint
+
+ var wg sync.WaitGroup
+ wg.Add(2)
+
+ go func() {
+ defer wg.Done()
+ _, err := io.Copy(dmsgConn, conn)
+ if err != nil {
+ dLog.WithError(err).Warn("Error on io.Copy(dmsgConn, conn)")
+ }
+ }()
+
+ go func() {
+ defer wg.Done()
+ _, err := io.Copy(conn, dmsgConn)
+ if err != nil {
+ dLog.WithError(err).Warn("Error on io.Copy(conn, dmsgConn)")
+ }
+ }()
+
+ wg.Wait()
+ }(conn, n, dmsgC)
+ }
+}
+
func proxyHTTPConn(n int) {
r := gin.New()
@@ -333,10 +402,11 @@ func proxyHTTPConn(n int) {
}
}
- fmt.Printf("Proxying request: %s %s\n", c.Request.Method, urlStr)
+ dLog.Debug(fmt.Sprintf("Proxying request: %s %s", c.Request.Method, urlStr))
req, err := http.NewRequest(c.Request.Method, urlStr, c.Request.Body)
if err != nil {
c.String(http.StatusInternalServerError, "Failed to create HTTP request")
+ dLog.WithError(err).Warn("Failed to create HTTP request")
return
}
@@ -349,7 +419,7 @@ func proxyHTTPConn(n int) {
resp, err := httpC.Do(req)
if err != nil {
c.String(http.StatusInternalServerError, "Failed to connect to HTTP server")
- fmt.Printf("Error: %v\n", err)
+ dLog.WithError(err).Warn("Failed to connect to HTTP server")
return
}
defer resp.Body.Close() //nolint
@@ -363,7 +433,7 @@ func proxyHTTPConn(n int) {
c.Status(resp.StatusCode)
if _, err := io.Copy(c.Writer, resp.Body); err != nil {
c.String(http.StatusInternalServerError, "Failed to copy response body")
- fmt.Printf("Error copying response body: %v\n", err)
+ dLog.WithError(err).Warn("Failed to copy response body")
}
})
wg.Add(1)
@@ -374,66 +444,12 @@ func proxyHTTPConn(n int) {
} else {
thiswebport = webPort[n]
}
- dmsgWebLog.Debug(fmt.Sprintf("Serving http on: http://127.0.0.1:%v", thiswebport))
+ dLog.Debug(fmt.Sprintf("Serving http on: http://127.0.0.1:%v", thiswebport))
r.Run(":" + fmt.Sprintf("%v", thiswebport)) //nolint
- dmsgWebLog.Debug(fmt.Sprintf("Stopped serving http on: http://127.0.0.1:%v", thiswebport))
+ dLog.Debug(fmt.Sprintf("Stopped serving http on: http://127.0.0.1:%v", thiswebport))
wg.Done()
}()
}
-func proxyTCPConn(n int) {
- var thiswebport uint
- if n == -1 {
- thiswebport = webPort[0]
- } else {
- thiswebport = webPort[n]
- }
- listener, err := net.Listen("tcp", fmt.Sprintf(":%v", thiswebport))
- if err != nil {
- dmsgWebLog.Fatalf("Failed to start TCP listener on port %v: %v", thiswebport, err)
- }
- defer listener.Close() //nolint
- log.Printf("Serving TCP on 127.0.0.1:%v", thiswebport)
-
- for {
- conn, err := listener.Accept()
- if err != nil {
- log.Printf("Failed to accept connection: %v", err)
- continue
- }
-
- wg.Add(1)
- go func(conn net.Conn, n int) {
- defer wg.Done()
- defer conn.Close() //nolint
- dp, ok := safecast.To[uint16](dmsgPorts[n])
- if !ok {
- dmsgWebLog.Fatal("uint16 overflow when converting dmsg port")
- }
- dmsgConn, err := dmsgC.DialStream(context.Background(), dmsg.Addr{PK: dialPK[n], Port: dp}) //nolint
- if err != nil {
- log.Printf("Failed to dial dmsg address %v:%v %v", dialPK[n].String(), dmsgPorts[n], err)
- return
- }
- defer dmsgConn.Close() //nolint
-
- go func() {
- _, err := io.Copy(dmsgConn, conn)
- if err != nil {
- log.Printf("Error copying data to dmsg server: %v", err)
- }
- dmsgConn.Close() //nolint
- }()
-
- go func() {
- _, err := io.Copy(conn, dmsgConn)
- if err != nil {
- log.Printf("Error copying data from dmsg server: %v", err)
- }
- conn.Close() //nolint
- }()
- }(conn, n)
- }
-}
const envfileLinux = `
#########################################################################
diff --git a/cmd/dmsgweb/commands/dmsgwebsrv.go b/cmd/dmsgweb/commands/dmsgwebsrv.go
index 314716c1..84f0a916 100644
--- a/cmd/dmsgweb/commands/dmsgwebsrv.go
+++ b/cmd/dmsgweb/commands/dmsgwebsrv.go
@@ -10,13 +10,9 @@ import (
"net/http/httputil"
"net/url"
"os"
- "runtime"
- "strings"
"sync"
"time"
- "github.com/bitfield/script"
- "github.com/chen3feng/safecast"
"github.com/gin-gonic/gin"
"github.com/skycoin/skywire/pkg/skywire-utilities/pkg/cipher"
"github.com/skycoin/skywire/pkg/skywire-utilities/pkg/cmdutil"
@@ -28,250 +24,255 @@ import (
dmsg "github.com/skycoin/dmsg/pkg/dmsg"
)
-const dmsgwebsrvenvname = "DMSGWEBSRV"
+const dwsenv = "DMSGWEBSRV"
-var dmsgwebsrvconffile = os.Getenv(dmsgwebsrvenvname)
+var dwscfg = os.Getenv(dwsenv)
func init() {
- RootCmd.AddCommand(srvCmd)
- srvCmd.Flags().UintSliceVarP(&localPort, "lport", "l", scriptExecUintSlice("${LOCALPORT[@]:-8086}", dmsgwebsrvconffile), "local application http interface port(s)")
- srvCmd.Flags().UintSliceVarP(&dmsgPort, "dport", "d", scriptExecUintSlice("${DMSGPORT[@]:-80}", dmsgwebsrvconffile), "dmsg port(s) to serve")
- srvCmd.Flags().StringSliceVarP(&wl, "wl", "w", scriptExecStringSlice("${WHITELISTPKS[@]}", dmsgwebsrvconffile), "whitelisted keys for dmsg authenticated routes\r")
- srvCmd.Flags().StringVarP(&dmsgDisc, "dmsg-disc", "D", dmsgDisc, "dmsg discovery url(s)")
- srvCmd.Flags().StringVarP(&proxyAddr, "proxy", "x", proxyAddr, "connect to dmsg via proxy (i.e. '127.0.0.1:1080')")
- srvCmd.Flags().IntVarP(&dmsgSess, "dsess", "e", scriptExecInt("${DMSGSESSIONS:-1}", dmsgwebsrvconffile), "dmsg sessions")
- srvCmd.Flags().BoolSliceVarP(&rawTCP, "rt", "c", scriptExecBoolSlice("${RAWTCP[@]:-false}", dmsgwebsrvconffile), "proxy local port as raw TCP")
+ dmsgPort = scriptExecUintSlice("${DMSGPORT[@]:-80}", dwscfg)
+ dmsgSess = scriptExecInt("${DMSGSESSIONS:-1}", dwscfg)
+ wl = scriptExecStringSlice("${WHITELISTPKS[@]}", dwscfg)
+ localPort = scriptExecUintSlice("${LOCALPORT[@]:-8086}", dwscfg)
+ rawTCP = scriptExecBoolSlice("${RAWTCP[@]:-false}", dwscfg)
if os.Getenv("DMSGWEBSRVSK") != "" {
sk.Set(os.Getenv("DMSGWEBSRVSK")) //nolint
}
- if scriptExecString("${DMSGWEBSRVSK}", dmsgwebsrvconffile) != "" {
- sk.Set(scriptExecString("${DMSGWEBSRVSK}", dmsgwebsrvconffile)) //nolint
+ if scriptExecString("${DMSGWEBSRVSK}", dwscfg) != "" {
+ sk.Set(scriptExecString("${DMSGWEBSRVSK}", dwscfg)) //nolint
}
pk, _ = sk.PubKey() //nolint
- srvCmd.Flags().VarP(&sk, "sk", "s", "a random key is generated if unspecified\n\r")
- srvCmd.Flags().BoolVarP(&isEnvs, "envs", "z", false, "show example .conf file")
+ RootCmd.AddCommand(srvCmd)
+ srvCmd.Flags().UintSliceVarP(&localPort, "lport", "p", localPort, "local application interface port(s)\033[0m\n\r")
+ srvCmd.Flags().UintSliceVarP(&dmsgPort, "dport", "d", dmsgPort, "DMSG port(s) to serve\033[0m\n\r")
+ srvCmd.Flags().StringSliceVarP(&wl, "wl", "w", wl, "whitelisted keys for DMSG authenticated routes\033[0m\n\r")
+ srvCmd.Flags().StringVarP(&dmsgDisc, "dmsg-disc", "D", dmsgDisc, "DMSG discovery URL\033[0m\n\r")
+ srvCmd.Flags().StringVarP(&proxyAddr, "proxy", "x", proxyAddr, "connect to DMSG via proxy (e.g., '127.0.0.1:1080')\033[0m\n\r")
+ srvCmd.Flags().IntVarP(&dmsgSess, "dsess", "e", dmsgSess, "DMSG sessions\033[0m\n\r")
+ srvCmd.Flags().BoolSliceVarP(&rawTCP, "rt", "c", rawTCP, "proxy local port as raw TCP, comma separated\033[0m\n\r")
+ srvCmd.Flags().StringVarP(&logLvl, "loglvl", "l", "debug", "[ debug | warn | error | fatal | panic | trace | info ]\033[0m\n\r")
+ srvCmd.Flags().BoolVarP(&isEnvs, "envs", "z", false, "show example .conf file\033[0m\n\r")
+ srvCmd.Flags().VarP(&sk, "sk", "s", "a random key is generated if unspecified\033[0m\n\r")
srvCmd.CompletionOptions.DisableDefaultCmd = true
}
var srvCmd = &cobra.Command{
Use: "srv",
- Short: "serve http or raw TCP from local port over dmsg",
- Long: `DMSG web server - serve http or raw TCP interface from local port over dmsg` + func() string {
- if _, err := os.Stat(dmsgwebsrvconffile); err == nil {
- return `
- dmsenv file detected: ` + dmsgwebsrvconffile
+ Short: "Serve HTTP or raw TCP from local port over DMSG",
+ Long: `DMSG web server - serve HTTP or raw TCP interface from local port over DMSG` + func() string {
+ if _, err := os.Stat(dwscfg); err == nil {
+ return "\n\t.dmsenv file detected: " + dwscfg
}
- return `
- .conf file may also be specified with
- ` + dmsgwebsrvenvname + `=/path/to/dmsgwebsrv.conf skywire dmsg web srv`
+ return "\n\t.conf file may also be specified with " + dwsenv + `=/path/to/dmsgwebsrv.conf skywire dmsg web srv`
}(),
- Run: func(_ *cobra.Command, _ []string) {
+ PreRun: func(_ *cobra.Command, _ []string) {
if isEnvs {
- envfile := srvenvfileLinux
- if runtime.GOOS == "windows" {
- envfileslice, _ := script.Echo(envfile).Slice() //nolint
- for i := range envfileslice {
- efs, _ := script.Echo(envfileslice[i]).Reject("##").Reject("#-").Reject("# ").Replace("#", "#$").String() //nolint
- if efs != "" && efs != "\n" {
- envfileslice[i] = strings.ReplaceAll(efs, "\n", "")
- }
+ printEnvs(srvenvfileLinux)
+ }
+ if logLvl != "" {
+ if lvl, err := logging.LevelFromString(logLvl); err == nil {
+ logging.SetLevel(lvl)
+ }
+ }
+ dLog = logging.MustGetLogger("dmsgwebsrv")
+ if len(localPort) != len(dmsgPort) || len(localPort) != len(rawTCP) {
+ dLog.Fatal("The number of local ports, DMSG ports, and raw TCP flags must be the same")
+ }
+ pk, err = sk.PubKey()
+ if err != nil {
+ pk, sk = cipher.GenerateKeyPair()
+ }
+ dLog.Debugf("DMSG client public key: %v", pk.String())
+
+ if len(wl) > 0 {
+ for _, key := range wl {
+ var pk cipher.PubKey
+ if err := pk.Set(key); err == nil {
+ wlkeys = append(wlkeys, pk)
}
- envfile = strings.Join(envfileslice, "\n")
}
- fmt.Println(envfile)
- os.Exit(0)
+ dLog.Infof("%d keys whitelisted", len(wlkeys))
}
+ if proxyAddr != "" {
+ var err error
+ dialer, err = proxy.SOCKS5("tcp", proxyAddr, nil, proxy.Direct)
+ if err != nil {
+ dLog.Fatalf("Error creating SOCKS5 dialer: %v", err)
+ }
+ httpClient = &http.Client{Transport: &http.Transport{Dial: dialer.Dial}}
+ }
+ },
+ Run: func(_ *cobra.Command, _ []string) {
server()
},
}
func server() {
- log := logging.MustGetLogger("dmsgwebsrv")
- if len(localPort) != len(dmsgPort) {
- log.Fatal(fmt.Sprintf("the same number of local ports as dmsg ports must be specified ; local ports: %v ; dmsg ports: %v", len(localPort), len(dmsgPort)))
- }
-
- seenLocalPort := make(map[uint]bool)
- for _, item := range localPort {
- if seenLocalPort[item] {
- log.Fatal("-lport --l flag cannot contain duplicates")
- }
- seenLocalPort[item] = true
- }
-
- seenDmsgPort := make(map[uint]bool)
- for _, item := range dmsgPort {
- if seenDmsgPort[item] {
- log.Fatal("-dport --d flag cannot contain duplicates")
- }
- seenDmsgPort[item] = true
- }
-
- ctx, cancel := cmdutil.SignalContext(context.Background(), log)
+ ctx, cancel := cmdutil.SignalContext(context.Background(), dLog)
defer cancel()
- pk, err = sk.PubKey()
- if err != nil {
- pk, sk = cipher.GenerateKeyPair()
- }
- log.Infof("dmsg client pk: %v", pk.String())
-
- if len(wl) > 0 {
- for _, key := range wl {
- var pk0 cipher.PubKey
- err := pk0.Set(key)
- if err == nil {
- wlkeys = append(wlkeys, pk0)
- }
- }
- }
- if len(wlkeys) > 0 {
- if len(wlkeys) == 1 {
- log.Info(fmt.Sprintf("%d key whitelisted", len(wlkeys)))
- } else {
- log.Info(fmt.Sprintf("%d keys whitelisted", len(wlkeys)))
- }
- }
- if proxyAddr != "" {
- // Use SOCKS5 proxy dialer if specified
- dialer, err = proxy.SOCKS5("tcp", proxyAddr, nil, proxy.Direct)
- if err != nil {
- log.Fatalf("Error creating SOCKS5 dialer: %v", err)
- }
- transport := &http.Transport{
- Dial: dialer.Dial,
- }
- httpClient = &http.Client{
- Transport: transport,
- }
- ctx = context.WithValue(context.Background(), "socks5_proxy", proxyAddr) //nolint
- }
-
- dmsgC := dmsg.NewClient(pk, sk, disc.NewHTTP(dmsgDisc, &http.Client{}, log), dmsg.DefaultConfig())
+ dmsgClient := dmsg.NewClient(pk, sk, disc.NewHTTP(dmsgDisc, &http.Client{}, dLog), dmsg.DefaultConfig())
defer func() {
- if err := dmsgC.Close(); err != nil {
- log.WithError(err).Error()
+ if err := dmsgClient.Close(); err != nil {
+ dLog.WithError(err).Error()
}
}()
-
- go dmsgC.Serve(context.Background())
+ go dmsgClient.Serve(ctx)
select {
case <-ctx.Done():
- log.WithError(ctx.Err()).Warn()
+ dLog.WithError(ctx.Err()).Warn()
return
-
- case <-dmsgC.Ready():
+ case <-dmsgClient.Ready():
}
- var listN []net.Listener
-
- for _, dport := range dmsgPort {
- dp, ok := safecast.To[uint16](dport)
- if !ok {
- log.Fatal("uint16 overflow when converting dmsg port")
- }
- lis, err := dmsgC.Listen(dp)
+ wg := sync.WaitGroup{}
+ for i := range localPort {
+ lis, err := dmsgClient.Listen(uint16(dmsgPort[i])) //nolint
if err != nil {
- log.Fatalf("Error listening on port %d: %v", dport, err)
+ dLog.Fatalf("Error listening on DMSG port %d: %v", dmsgPort[i], err)
}
-
- listN = append(listN, lis)
-
- dport := dp
- go func(l net.Listener, port uint16) {
- <-ctx.Done()
- if err := l.Close(); err != nil {
- log.Printf("Error closing listener on port %d: %v", port, err)
- log.WithError(err).Error()
- }
- }(lis, dport)
- }
-
- wg := new(sync.WaitGroup)
-
- for i, lpt := range localPort {
wg.Add(1)
- go func(localPort uint, rtcp bool, lis net.Listener) {
+ go func(ctx context.Context, localPort uint, rawTCP bool, listener net.Listener) {
defer wg.Done()
- if rtcp {
- proxyTCPConnections(localPort, lis, log)
+ defer listener.Close() //nolint
+
+ if rawTCP {
+ proxyTCPConnections(ctx, localPort, listener)
} else {
- proxyHTTPConnections(localPort, lis, log)
+ proxyHTTPConnections(ctx, localPort, listener)
}
- }(lpt, rawTCP[i], listN[i])
+ }(ctx, localPort[i], rawTCP[i], lis)
}
-
wg.Wait()
}
-func proxyHTTPConnections(localPort uint, lis net.Listener, log *logging.Logger) {
- r1 := gin.New()
- r1.Use(gin.Recovery())
- r1.Use(loggingMiddleware())
+func proxyHTTPConnections(ctx context.Context, localPort uint, listener net.Listener) {
+ router := gin.New()
+ router.Use(gin.Recovery())
+ router.Use(loggingMiddleware())
- authRoute := r1.Group("/")
+ authRoute := router.Group("/")
if len(wlkeys) > 0 {
authRoute.Use(whitelistAuth(wlkeys))
}
authRoute.Any("/*path", func(c *gin.Context) {
- targetURL, _ := url.Parse(fmt.Sprintf("http://127.0.0.1:%v%s?%s", localPort, c.Request.URL.Path, c.Request.URL.RawQuery)) //nolint
- proxy := httputil.ReverseProxy{
- Director: func(req *http.Request) {
- req.URL = targetURL
- req.Host = targetURL.Host
- req.Method = c.Request.Method
- },
- Transport: &http.Transport{},
- }
+ targetURL := fmt.Sprintf("http://127.0.0.1:%d%s?%s", localPort, c.Request.URL.Path, c.Request.URL.RawQuery)
+ proxy := httputil.ReverseProxy{Director: func(req *http.Request) {
+ req.URL, _ = url.Parse(targetURL) //nolint
+ req.Host = req.URL.Host
+ }}
proxy.ServeHTTP(c.Writer, c.Request)
})
- serve := &http.Server{
- Handler: &ginHandler{Router: r1},
+
+ server := &http.Server{
+ Handler: router,
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
+ IdleTimeout: 60 * time.Second,
+ MaxHeaderBytes: 1 << 20,
}
- log.Printf("Serving HTTP on dmsg port %v with DMSG listener %s", localPort, lis.Addr().String())
- if err := serve.Serve(lis); err != nil && err != http.ErrServerClosed {
- log.Fatalf("Serve: %v", err)
+
+ // Graceful shutdown on context cancellation
+ go func() {
+ <-ctx.Done()
+ if err := server.Shutdown(context.Background()); err != nil {
+ dLog.Errorf("HTTP server shutdown error: %v", err)
+ }
+ }()
+
+ if err := server.Serve(listener); err != nil && err != http.ErrServerClosed {
+ dLog.Fatalf("HTTP server error: %v", err)
}
}
-func proxyTCPConnections(localPort uint, lis net.Listener, log *logging.Logger) {
+func proxyTCPConnections(ctx context.Context, localPort uint, listener net.Listener) {
+ // To track active connections for cleanup
+ var connWg sync.WaitGroup
+ connChan := make(chan net.Conn)
+ activeConns := make(map[net.Conn]struct{})
+ connMutex := &sync.Mutex{} // Protect access to activeConns
+
+ // Goroutine to accept new connections
+ go func() {
+ defer close(connChan)
+ for {
+ conn, err := listener.Accept()
+ if err != nil {
+ select {
+ case <-ctx.Done():
+ // Listener closed due to context cancellation
+ return
+ default:
+ dLog.Errorf("Error accepting connection: %v", err)
+ return
+ }
+ }
+ connChan <- conn
+ }
+ }()
+
for {
- conn, err := lis.Accept()
- if err != nil {
- log.Printf("Error accepting connection: %v", err)
+ select {
+ case <-ctx.Done():
+ dLog.Info("Shutting down TCP proxy connections...")
+ listener.Close() //nolint
+
+ connMutex.Lock()
+ for conn := range activeConns {
+ conn.Close() //nolint
+ }
+ connMutex.Unlock()
+
+ connWg.Wait()
return
- }
- go handleTCPConnection(conn, localPort, log)
- }
-}
+ case conn, ok := <-connChan:
+ if !ok {
+ return
+ }
-func handleTCPConnection(dmsgConn net.Conn, localPort uint, log *logging.Logger) {
- defer dmsgConn.Close() //nolint
+ connMutex.Lock()
+ activeConns[conn] = struct{}{}
+ connMutex.Unlock()
- localConn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", localPort))
- if err != nil {
- log.Printf("Error connecting to local port %d: %v", localPort, err)
- return
- }
- defer localConn.Close() //nolint
+ connWg.Add(1)
+ go func(dmsgConn net.Conn) {
+ defer connWg.Done()
+ defer dmsgConn.Close() //nolint
- copyConn := func(dst net.Conn, src net.Conn) {
- _, err := io.Copy(dst, src)
- if err != nil {
- log.Printf("Error during copy: %v", err)
+ localConn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", localPort))
+ if err != nil {
+ dLog.Errorf("Error connecting to local port %d: %v", localPort, err)
+
+ connMutex.Lock()
+ delete(activeConns, dmsgConn)
+ connMutex.Unlock()
+
+ return
+ }
+ defer localConn.Close() //nolint
+
+ go func() {
+ _, err1 := io.Copy(dmsgConn, localConn)
+ if err1 != nil {
+ dLog.WithError(err1).Warn("Error on io.Copy(dmsgConn, localConn)")
+ }
+ }()
+ _, err2 := io.Copy(localConn, dmsgConn)
+ if err2 != nil {
+ dLog.WithError(err2).Warn("Error on io.Copy(localConn, dmsgConn)")
+ }
+
+ connMutex.Lock()
+ delete(activeConns, dmsgConn)
+ connMutex.Unlock()
+ }(conn)
}
}
-
- go copyConn(dmsgConn, localConn)
- go copyConn(localConn, dmsgConn)
}
const srvenvfileLinux = `
diff --git a/cmd/dmsgweb/commands/root.go b/cmd/dmsgweb/commands/root.go
index 31671b66..0f8b6c32 100644
--- a/cmd/dmsgweb/commands/root.go
+++ b/cmd/dmsgweb/commands/root.go
@@ -6,6 +6,7 @@ import (
"log"
"net"
"net/http"
+ "os"
"runtime"
"strconv"
"strings"
@@ -23,8 +24,10 @@ import (
)
var (
+ dLog *logging.Logger
httpC http.Client
dmsgC *dmsg.Client
+ closeDmsg func()
dmsgDisc = dmsg.DiscAddr(false)
proxyAddr string
dmsgSessions int
@@ -85,6 +88,22 @@ func startDmsg(ctx context.Context, pk cipher.PubKey, sk cipher.SecKey, dmsgDisc
}
}
*/
+
+func printEnvs(envfile string) {
+ if runtime.GOOS == "windows" {
+ envfileslice, _ := script.Echo(envfile).Slice() //nolint
+ for i := range envfileslice {
+ efs, _ := script.Echo(envfileslice[i]).Reject("##").Reject("#-").Reject("# ").Replace("#", "#$").String() //nolint
+ if efs != "" && efs != "\n" {
+ envfileslice[i] = strings.ReplaceAll(efs, "\n", "")
+ }
+ }
+ envfile = strings.Join(envfileslice, "\n")
+ }
+ fmt.Println(envfile)
+ os.Exit(0)
+}
+
//TODO: these functions are more or less duplicated in several places - need to standardize and put in it's own library import in "github.com/skycoin/skywire/pkg/skywire-utilities/pkg/..."
func scriptExecString(s, envfile string) string {
diff --git a/examples/dmsghttp-client/dmsghttp-client.go b/examples/dmsghttp-client/dmsghttp-client.go
new file mode 100644
index 00000000..05efc83b
--- /dev/null
+++ b/examples/dmsghttp-client/dmsghttp-client.go
@@ -0,0 +1,50 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "os"
+
+ "github.com/skycoin/skywire/pkg/skywire-utilities/pkg/cipher"
+ "github.com/skycoin/skywire/pkg/skywire-utilities/pkg/logging"
+
+ "github.com/skycoin/dmsg/internal/cli"
+ "github.com/skycoin/dmsg/pkg/dmsg"
+ "github.com/skycoin/dmsg/pkg/dmsghttp"
+)
+
+func main() {
+ dLog := logging.MustGetLogger("dmsghttp-client")
+ dmsgDisc := dmsg.DiscAddr(false)
+ parsedURL, err := url.Parse(os.Args[1])
+ if err != nil {
+ dLog.Fatalf("Failed to parse URL: %v", err)
+ }
+ pk, sk := cipher.GenerateKeyPair()
+ ctx := context.Background()
+ dmsgClient, closeDmsg, err := cli.StartDmsg(ctx, dLog, pk, sk, &http.Client{}, dmsgDisc, 1)
+ if err != nil {
+ dLog.Fatalf("Failed to start DMSG client: %v", err)
+ }
+ dLog.Println("started dmsg client")
+ defer closeDmsg()
+ if dmsgClient == nil {
+ dLog.Fatal("DMSG client initialization failed. Exiting.")
+ }
+ httpClient := &http.Client{
+ Transport: dmsghttp.MakeHTTPTransport(ctx, dmsgClient),
+ }
+ resp, err := httpClient.Get(parsedURL.String())
+ if err != nil {
+ dLog.Fatalf("Failed to perform GET request: %v", err)
+ }
+ defer resp.Body.Close()
+ body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ dLog.Fatalf("Failed to read response body: %v", err)
+ }
+ fmt.Println(string(body))
+}
diff --git a/examples/dmsghttp/README.md b/examples/dmsghttp/README.md
new file mode 100644
index 00000000..06089b03
--- /dev/null
+++ b/examples/dmsghttp/README.md
@@ -0,0 +1,72 @@
+## example hello world via HTTP over DMSG
+
+### Generate keys:
+
+```
+go run ../gen-keys/gen-keys.go | tee dmsgtest.keys
+```
+OR
+```
+go run ../gen-keys/gen-keys.go > dmsgtest.keys
+```
+
+
+### Start application using secret key
+
+
+```
+$ go run dmsghttp.go -s $(tail -n1 dmsgtest.key)
+[2025-01-28T15:45:26.218738525-06:00] DEBUG disc.NewHTTP [dmsghttp]: Created HTTP client. addr="http://dmsgd.skywire.skycoin.com"
+[2025-01-28T15:45:26.218798474-06:00] DEBUG [dmsg_client]: Discovering dmsg servers...
+[2025-01-28T15:45:26.494061772-06:00] DEBUG [dmsg_client]: Dialing session... remote_pk=0281a102c82820e811368c8d028cf11b1a985043b726b1bcdb8fce89b27384b2cb
+[2025-01-28T15:45:27.047394122-06:00] DEBUG [dmsg_client]: Serving session. remote_pk=0281a102c82820e811368c8d028cf11b1a985043b726b1bcdb8fce89b27384b2cb
+[2025-01-28T15:45:27.047406948-06:00] INFO [dmsghttp]: Serving Hello World on DMSG address 03f45df9890955214bbfe2e06487741489266a60c487365989b9723680b45e0f6e:80
+[2025-01-28T15:46:02.992699297-06:00] INFO [dmsghttp]: Received request from 0352b141c5a423a3788ad7202745b083e2240dea5ce304742a272ec4a80ece6c1c:49153
+
+```
+
+### Get using dmsgcurl
+
+
+```
+$ go run ../../cmd/dmsgcurl/dmsgcurl.go -l debug dmsg://$(head -n1 dmsgtest.key):80
+[2025-01-28T15:46:00.785331607-06:00] DEBUG disc.NewHTTP [dmsgcurl]: Created HTTP client. addr="http://dmsgd.skywire.skycoin.com"
+[2025-01-28T15:46:00.785375317-06:00] DEBUG [dmsgcurl]: Connecting to dmsg network... dmsg_disc="http://dmsgd.skywire.skycoin.com" public_key="0352b141c5a423a3788ad7202745b083e2240dea5ce304742a272ec4a80ece6c1c"
+[2025-01-28T15:46:00.785475179-06:00] DEBUG [dmsg_client]: Discovering dmsg servers...
+[2025-01-28T15:46:01.053319739-06:00] DEBUG [dmsg_client]: Dialing session... remote_pk=02a2d4c346dabd165fd555dfdba4a7f4d18786fe7e055e562397cd5102bdd7f8dd
+[2025-01-28T15:46:01.611696253-06:00] DEBUG [dmsg_client]: Serving session. remote_pk=02a2d4c346dabd165fd555dfdba4a7f4d18786fe7e055e562397cd5102bdd7f8dd
+[2025-01-28T15:46:01.611738628-06:00] DEBUG [dmsgcurl]: Dmsg network ready.
+[2025-01-28T15:46:02.018101877-06:00] DEBUG [dmsg_client]: Dialing session... remote_pk=0281a102c82820e811368c8d028cf11b1a985043b726b1bcdb8fce89b27384b2cb
+[2025-01-28T15:46:02.431323641-06:00] DEBUG [dmsg_client]: Updating entry. entry= version: 0.0.1
+ sequence: 0
+ registered at: 1738100761468088283
+ static public key: 0352b141c5a423a3788ad7202745b083e2240dea5ce304742a272ec4a80ece6c1c
+ signature: 4a761f8e84f021683957f272832a007890de622414d155db49bf0cdac944477d6160f846e07f68d6d78c93209ebe593b3dc4c48d1ef729f7ee7c52fb79d295a200
+ entry is registered as client. Related info:
+ delegated servers:
+ 02a2d4c346dabd165fd555dfdba4a7f4d18786fe7e055e562397cd5102bdd7f8dd
+ 0281a102c82820e811368c8d028cf11b1a985043b726b1bcdb8fce89b27384b2cb
+
+
+[2025-01-28T15:46:02.569096852-06:00] DEBUG [dmsg_client]: Serving session. remote_pk=0281a102c82820e811368c8d028cf11b1a985043b726b1bcdb8fce89b27384b2cb
+Hello, World![2025-01-28T15:46:03.127242853-06:00] DEBUG [dmsg_client]: Stopped serving client!
+[2025-01-28T15:46:03.127263963-06:00] DEBUG [dmsg_client]: Stopped accepting streams. error="session shutdown" session=02a2d4c346dabd165fd555dfdba4a7f4d18786fe7e055e562397cd5102bdd7f8dd
+[2025-01-28T15:46:03.12746417-06:00] DEBUG [dmsg_client]: Session closed. error=
+[2025-01-28T15:46:03.127561533-06:00] DEBUG [dmsg_client]: Stopped accepting streams. error="session shutdown" session=0281a102c82820e811368c8d028cf11b1a985043b726b1bcdb8fce89b27384b2cb
+[2025-01-28T15:46:03.127602412-06:00] DEBUG [dmsg_client]: Session closed. error=
+[2025-01-28T15:46:03.127623574-06:00] DEBUG [dmsg_client]: All sessions closed.
+[2025-01-28T15:46:03.395174115-06:00] DEBUG [dmsg_client]: Deleting entry. entry= version: 0.0.1
+ sequence: 1
+ registered at: 1738100762431422654
+ static public key: 0352b141c5a423a3788ad7202745b083e2240dea5ce304742a272ec4a80ece6c1c
+ signature: 40a80e385094c38a420d9229d517172a3859f28b79057b5b79cf2600d3021f9d7af57fa26d0f5c9c0643fa45d07bad8169860b842851543d866455a8f170cf6701
+ entry is registered as client. Related info:
+ delegated servers:
+ 02a2d4c346dabd165fd555dfdba4a7f4d18786fe7e055e562397cd5102bdd7f8dd
+ 0281a102c82820e811368c8d028cf11b1a985043b726b1bcdb8fce89b27384b2cb
+
+
+[2025-01-28T15:46:03.533465754-06:00] DEBUG [dmsg_client]: Entry Deleted successfully.
+[2025-01-28T15:46:03.533508976-06:00] DEBUG [dmsgcurl]: Disconnected from dmsg network. error=
+
+```
diff --git a/examples/dmsghttp/dmsghttp.go b/examples/dmsghttp/dmsghttp.go
new file mode 100644
index 00000000..ec01e75c
--- /dev/null
+++ b/examples/dmsghttp/dmsghttp.go
@@ -0,0 +1,153 @@
+// Example Hello World HTTP over DMSG
+package main
+
+import (
+ "context"
+ "log"
+ "net/http"
+ "os"
+ "strings"
+ "time"
+
+ cc "github.com/ivanpirog/coloredcobra"
+ "github.com/skycoin/skywire/pkg/skywire-utilities/pkg/cipher"
+ "github.com/skycoin/skywire/pkg/skywire-utilities/pkg/cmdutil"
+ "github.com/skycoin/skywire/pkg/skywire-utilities/pkg/logging"
+ "github.com/spf13/cobra"
+
+ "github.com/skycoin/dmsg/pkg/disc"
+ dmsg "github.com/skycoin/dmsg/pkg/dmsg"
+)
+
+var (
+ sk cipher.SecKey
+ dmsgDisc string
+ dmsgPort uint
+)
+
+func init() {
+ RootCmd.Flags().UintVarP(&dmsgPort, "port", "p", 80, "DMSG port to serve from")
+ RootCmd.Flags().StringVarP(&dmsgDisc, "dmsg-disc", "D", dmsg.DiscAddr(false), "DMSG discovery URL")
+ if os.Getenv("DMSGHTTP_SK") != "" {
+ sk.Set(os.Getenv("DMSGHTTP_SK")) //nolint
+ }
+ RootCmd.Flags().VarP(&sk, "sk", "s", "A random key is generated if unspecified\n\r")
+}
+
+// RootCmd contains the root DMSG HTTP command
+var RootCmd = &cobra.Command{
+ Use: func() string {
+ return strings.Split(os.Args[0], " ")[0]
+ }(),
+ Short: "DMSG HTTP Hello World server",
+ Long: "DMSG HTTP Hello World server",
+ SilenceErrors: true,
+ SilenceUsage: true,
+ DisableSuggestions: true,
+ DisableFlagsInUseLine: true,
+ Run: func(_ *cobra.Command, _ []string) {
+ log := logging.MustGetLogger("dmsghttp")
+ if dmsgDisc == "" {
+ log.Fatal("DMSG discovery URL not specified")
+ }
+
+ ctx, cancel := cmdutil.SignalContext(context.Background(), log)
+ defer cancel()
+
+ // Generate keys if not provided
+ pk, err := sk.PubKey()
+ if err != nil {
+ pk, sk = cipher.GenerateKeyPair()
+ }
+
+ // Initialize the DMSG client
+ c := dmsg.NewClient(pk, sk, disc.NewHTTP(dmsgDisc, &http.Client{}, log), dmsg.DefaultConfig())
+ defer func() {
+ if err := c.Close(); err != nil {
+ log.WithError(err).Error("Failed to close DMSG client")
+ }
+ }()
+ go c.Serve(context.Background())
+
+ // Wait for the DMSG client to be ready
+ select {
+ case <-ctx.Done():
+ log.WithError(ctx.Err()).Warn()
+ return
+ case <-c.Ready():
+ }
+
+ // Listen on the specified DMSG port
+ lis, err := c.Listen(uint16(dmsgPort))
+ if err != nil {
+ log.WithError(err).Fatal("Failed to listen on DMSG port")
+ }
+ defer lis.Close()
+
+ log.Infof("Serving Hello World on DMSG address %s", lis.Addr())
+
+ // Set up HTTP server to respond with "Hello, World!"
+ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ log.Infof("Received request from %s", r.RemoteAddr)
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte("Hello, World!"))
+ })
+
+ // Start the HTTP server
+ server := &http.Server{
+ ReadHeaderTimeout: 3 * time.Second,
+ }
+
+ // Graceful shutdown handler
+ go func() {
+ <-ctx.Done()
+ log.Info("Shutdown signal received, shutting down HTTP server...")
+ server.Shutdown(context.Background())
+ log.Info("HTTP server successfully shut down")
+ }()
+
+ // Start serving HTTP requests
+ log.Fatal(server.Serve(lis))
+ },
+}
+
+// Execute executes the root CLI command
+func Execute() {
+ if err := RootCmd.Execute(); err != nil {
+ log.Fatal("Failed to execute command: ", err)
+ }
+}
+
+func init() {
+ var helpflag bool
+ RootCmd.SetUsageTemplate(help)
+ RootCmd.PersistentFlags().BoolVarP(&helpflag, "help", "h", false, "help for dmsghttp-cli")
+ RootCmd.SetHelpCommand(&cobra.Command{Hidden: true})
+ RootCmd.PersistentFlags().MarkHidden("help") //nolint
+}
+
+func main() {
+ cc.Init(&cc.Config{
+ RootCmd: RootCmd,
+ Headings: cc.HiBlue + cc.Bold,
+ Commands: cc.HiBlue + cc.Bold,
+ CmdShortDescr: cc.HiBlue,
+ Example: cc.HiBlue + cc.Italic,
+ ExecName: cc.HiBlue + cc.Bold,
+ Flags: cc.HiBlue + cc.Bold,
+ FlagsDescr: cc.HiBlue,
+ NoExtraNewlines: true,
+ NoBottomNewline: true,
+ })
+ Execute()
+}
+
+const help = "Usage:\r\n" +
+ " {{.UseLine}}{{if .HasAvailableSubCommands}}{{end}} {{if gt (len .Aliases) 0}}\r\n\r\n" +
+ "{{.NameAndAliases}}{{end}}{{if .HasAvailableSubCommands}}\r\n\r\n" +
+ "Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand)}}\r\n " +
+ "{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}\r\n\r\n" +
+ "Flags:\r\n" +
+ "{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}\r\n\r\n" +
+ "Global Flags:\r\n" +
+ "{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}\r\n\r\n"
diff --git a/examples/dmsgtcp/README.md b/examples/dmsgtcp/README.md
new file mode 100644
index 00000000..44cf8d62
--- /dev/null
+++ b/examples/dmsgtcp/README.md
@@ -0,0 +1 @@
+example hello world via TCP over DMSG
diff --git a/examples/dmsgtcp/dmsgtcp.go b/examples/dmsgtcp/dmsgtcp.go
new file mode 100644
index 00000000..1a0592f2
--- /dev/null
+++ b/examples/dmsgtcp/dmsgtcp.go
@@ -0,0 +1,165 @@
+// Example hello world TCP over DMSG
+package main
+
+import (
+ "context"
+ "log"
+ "net"
+ "net/http"
+ "os"
+ "os/signal"
+ "strings"
+ "syscall"
+
+ cc "github.com/ivanpirog/coloredcobra"
+ "github.com/skycoin/skywire/pkg/skywire-utilities/pkg/cipher"
+ "github.com/skycoin/skywire/pkg/skywire-utilities/pkg/cmdutil"
+ "github.com/skycoin/skywire/pkg/skywire-utilities/pkg/logging"
+ "github.com/spf13/cobra"
+
+ "github.com/skycoin/dmsg/internal/cli"
+ dmsg "github.com/skycoin/dmsg/pkg/dmsg"
+)
+
+var (
+ sk cipher.SecKey
+ dmsgDisc string
+ dmsgPort uint
+)
+
+func init() {
+ RootCmd.Flags().UintVarP(&dmsgPort, "port", "p", 80, "DMSG port to serve from")
+ RootCmd.Flags().StringVarP(&dmsgDisc, "dmsg-disc", "D", dmsg.DiscAddr(false), "DMSG discovery URL")
+ if os.Getenv("DMSGTCP_SK") != "" {
+ sk.Set(os.Getenv("DMSGTCP_SK")) //nolint
+ }
+ RootCmd.Flags().VarP(&sk, "sk", "s", "A random key is generated if unspecified\n\r")
+}
+
+// RootCmd contains the root DMSG TCP command
+var RootCmd = &cobra.Command{
+ Use: func() string {
+ return strings.Split(os.Args[0], " ")[0]
+ }(),
+ Short: "DMSG TCP Hello World server",
+ Long: "DMSG TCP Hello World server",
+ Run: func(_ *cobra.Command, _ []string) {
+ log := logging.MustGetLogger("dmsgtcp")
+ if dmsgDisc == "" {
+ log.Fatal("DMSG discovery URL not specified")
+ }
+
+ // Create the context and cancel function
+ ctx, cancel := cmdutil.SignalContext(context.Background(), log)
+ defer cancel()
+
+ // Generate keys if not provided
+ pk, err := sk.PubKey()
+ if err != nil {
+ pk, sk = cipher.GenerateKeyPair()
+ }
+
+ // Initialize the DMSG client
+ dmsgC, closeDmsg, err := cli.StartDmsg(ctx, log, pk, sk, &http.Client{}, dmsgDisc, 1)
+ if err != nil {
+ log.WithError(err).Fatal("failed to start dmsg")
+ }
+ defer closeDmsg()
+
+ go func() {
+ <-ctx.Done()
+ cancel()
+ closeDmsg()
+ os.Exit(0)
+ }()
+
+ // Listen on the specified DMSG port
+ lis, err := dmsgC.Listen(uint16(dmsgPort))
+ if err != nil {
+ log.WithError(err).Fatal("Failed to listen on DMSG port")
+ }
+ defer lis.Close()
+
+ log.Infof("Serving Hello World TCP on DMSG address %s", lis.Addr())
+
+ // Handle system interrupt (Ctrl + C)
+ signalChan := make(chan os.Signal, 1)
+ signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
+
+ // Start a goroutine to wait for shutdown signals
+ go func() {
+ <-signalChan
+ log.Info("Received shutdown signal.")
+ lis.Close()
+ closeDmsg()
+ cancel() // Cancel context to terminate DMSG client and server
+ }()
+
+ // Accept TCP connections and respond with "Hello, World!"
+ for {
+ conn, err := lis.Accept()
+ if err != nil {
+ // If the server was closed or a signal was received, break out
+ if ctx.Err() != nil {
+ log.Info("Server shutting down...")
+ return
+ }
+ log.WithError(err).Error("Failed to accept connection")
+ continue
+ }
+ go handleConnection(conn, log)
+ }
+ },
+}
+
+func handleConnection(conn net.Conn, log *logging.Logger) {
+ defer conn.Close()
+ log.Infof("Received connection from %s", conn.RemoteAddr())
+
+ // Write "Hello, World!" message to the connection
+ _, err := conn.Write([]byte("Hello, World!\n"))
+ if err != nil {
+ log.WithError(err).Error("Failed to write response")
+ }
+}
+
+// Execute executes the root CLI command
+func Execute() {
+ if err := RootCmd.Execute(); err != nil {
+ log.Fatal("Failed to execute command: ", err)
+ }
+}
+
+func init() {
+ var helpflag bool
+ RootCmd.SetUsageTemplate(help)
+ RootCmd.PersistentFlags().BoolVarP(&helpflag, "help", "h", false, "help for dmsgpty-cli")
+ RootCmd.SetHelpCommand(&cobra.Command{Hidden: true})
+ RootCmd.PersistentFlags().MarkHidden("help") //nolint
+}
+
+func main() {
+ cc.Init(&cc.Config{
+ RootCmd: RootCmd,
+ Headings: cc.HiBlue + cc.Bold,
+ Commands: cc.HiBlue + cc.Bold,
+ CmdShortDescr: cc.HiBlue,
+ Example: cc.HiBlue + cc.Italic,
+ ExecName: cc.HiBlue + cc.Bold,
+ Flags: cc.HiBlue + cc.Bold,
+ FlagsDescr: cc.HiBlue,
+ NoExtraNewlines: true,
+ NoBottomNewline: true,
+ })
+ Execute()
+}
+
+const help = "Usage:\r\n" +
+ " {{.UseLine}}{{if .HasAvailableSubCommands}}{{end}} {{if gt (len .Aliases) 0}}\r\n\r\n" +
+ "{{.NameAndAliases}}{{end}}{{if .HasAvailableSubCommands}}\r\n\r\n" +
+ "Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand)}}\r\n " +
+ "{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}\r\n\r\n" +
+ "Flags:\r\n" +
+ "{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}\r\n\r\n" +
+ "Global Flags:\r\n" +
+ "{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}\r\n\r\n"
diff --git a/examples/dmsgweb/commands/dmsgweb.go b/examples/dmsgweb/commands/dmsgweb.go
new file mode 100644
index 00000000..03e2a2e9
--- /dev/null
+++ b/examples/dmsgweb/commands/dmsgweb.go
@@ -0,0 +1,200 @@
+// Package commands cmd/dmsgweb/commands/dmsgweb.go
+package commands
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "log"
+ "net"
+ "net/http"
+ "os"
+ "os/signal"
+ "regexp"
+ "strings"
+ "syscall"
+
+ "github.com/confiant-inc/go-socks5"
+ "github.com/gin-gonic/gin"
+ "github.com/skycoin/skywire/pkg/skywire-utilities/pkg/buildinfo"
+ "github.com/skycoin/skywire/pkg/skywire-utilities/pkg/cipher"
+ "github.com/skycoin/skywire/pkg/skywire-utilities/pkg/cmdutil"
+ "github.com/skycoin/skywire/pkg/skywire-utilities/pkg/logging"
+ "github.com/spf13/cobra"
+ "golang.org/x/net/proxy"
+
+ "github.com/skycoin/dmsg/pkg/dmsghttp"
+)
+
+func init() {
+ RootCmd.AddCommand(genKeysCmd)
+ RootCmd.Flags().StringVarP(&filterDomainSuffix, "filter", "f", ".dmsg", "domain suffix to filter")
+ RootCmd.Flags().StringVarP(&proxyPort, "socks", "q", "4445", "port to serve the socks5 proxy")
+ RootCmd.Flags().StringVarP(&addProxy, "proxy", "r", "", "configure additional socks5 proxy for dmsgweb (i.e. 127.0.0.1:1080)")
+ RootCmd.Flags().StringVarP(&webPort, "port", "p", "8080", "port to serve the web application")
+ RootCmd.Flags().StringVarP(&resolveDmsgAddr, "resolve", "t", "", "resolve the specified dmsg address:port on the local port & disable proxy")
+ RootCmd.Flags().StringVarP(&dmsgDisc, "dmsg-disc", "D", dmsgDisc, "dmsg discovery url")
+ RootCmd.Flags().IntVarP(&dmsgSessions, "sess", "e", 1, "number of dmsg servers to connect to")
+ RootCmd.Flags().StringVarP(&logLvl, "loglvl", "l", "", "[ debug | warn | error | fatal | panic | trace | info ]\033[0m")
+ if os.Getenv("DMSGGET_SK") != "" {
+ sk.Set(os.Getenv("DMSGGET_SK")) //nolint
+ }
+ RootCmd.Flags().VarP(&sk, "sk", "s", "a random key is generated if unspecified\n\r")
+}
+
+// RootCmd contains the root command for dmsgweb
+var RootCmd = &cobra.Command{
+ Use: "web",
+ Short: "DMSG resolving proxy & browser client",
+ Long: `
+ ┌┬┐┌┬┐┌─┐┌─┐┬ ┬┌─┐┌┐
+ │││││└─┐│ ┬│││├┤ ├┴┐
+ ─┴┘┴ ┴└─┘└─┘└┴┘└─┘└─┘
+ ` + "DMSG resolving proxy & browser client - access websites over dmsg",
+ SilenceErrors: true,
+ SilenceUsage: true,
+ DisableSuggestions: true,
+ DisableFlagsInUseLine: true,
+ Version: buildinfo.Version(),
+ Run: func(cmd *cobra.Command, _ []string) {
+ c := make(chan os.Signal, 1)
+ signal.Notify(c, os.Interrupt, syscall.SIGTERM) //nolint
+ go func() {
+ <-c
+ os.Exit(1)
+ }()
+ if dmsgWebLog == nil {
+ dmsgWebLog = logging.MustGetLogger("dmsgweb")
+ }
+ if logLvl != "" {
+ if lvl, err := logging.LevelFromString(logLvl); err == nil {
+ logging.SetLevel(lvl)
+ }
+ }
+
+ if filterDomainSuffix == "" {
+ dmsgWebLog.Fatal("domain suffix to filter cannot be an empty string")
+ }
+ ctx, cancel := cmdutil.SignalContext(context.Background(), dmsgWebLog)
+ defer cancel()
+
+ pk, err := sk.PubKey()
+ if err != nil {
+ pk, sk = cipher.GenerateKeyPair()
+ }
+
+ dmsgC, closeDmsg, err := startDmsg(ctx, pk, sk)
+ if err != nil {
+ dmsgWebLog.WithError(err).Fatal("failed to start dmsg")
+ }
+ defer closeDmsg()
+
+ go func() {
+ <-ctx.Done()
+ cancel()
+ closeDmsg()
+ os.Exit(0) //this should not be necessary
+ }()
+
+ httpC = http.Client{Transport: dmsghttp.MakeHTTPTransport(ctx, dmsgC)}
+
+ if resolveDmsgAddr == "" {
+ // Create a SOCKS5 server with custom name resolution
+ conf := &socks5.Config{
+ Resolver: &customResolver{},
+ Dial: func(ctx context.Context, network, addr string) (net.Conn, error) {
+ host, _, err := net.SplitHostPort(addr)
+ if err != nil {
+ return nil, err
+ }
+ regexPattern := `\` + filterDomainSuffix + `(:[0-9]+)?$`
+ match, _ := regexp.MatchString(regexPattern, host) //nolint:errcheck
+ if match {
+ port, ok := ctx.Value("port").(string)
+ if !ok {
+ port = webPort
+ }
+ addr = "localhost:" + port
+ } else {
+ if addProxy != "" {
+ // Fallback to another SOCKS5 proxy
+ dialer, err := proxy.SOCKS5("tcp", addProxy, nil, proxy.Direct)
+ if err != nil {
+ return nil, err
+ }
+ return dialer.Dial(network, addr)
+ }
+ }
+ dmsgWebLog.Debug("Dialing address:", addr)
+ return net.Dial(network, addr)
+ },
+ }
+
+ // Start the SOCKS5 server
+ socksAddr := "127.0.0.1:" + proxyPort
+ log.Printf("SOCKS5 proxy server started on %s", socksAddr)
+
+ server, err := socks5.New(conf)
+ if err != nil {
+ log.Fatalf("Failed to create SOCKS5 server: %v", err)
+ }
+
+ wg.Add(1)
+ go func() {
+ dmsgWebLog.Debug("Serving SOCKS5 proxy on " + socksAddr)
+ err := server.ListenAndServe("tcp", socksAddr)
+ if err != nil {
+ log.Fatalf("Failed to start SOCKS5 server: %v", err)
+ }
+ defer server.Close()
+ dmsgWebLog.Debug("Stopped serving SOCKS5 proxy on " + socksAddr)
+ }()
+ }
+ r := gin.New()
+
+ r.Use(gin.Recovery())
+
+ r.Use(loggingMiddleware())
+
+ r.Any("/*path", func(c *gin.Context) {
+ var urlStr string
+ if resolveDmsgAddr != "" {
+ urlStr = fmt.Sprintf("dmsg://%s%s", resolveDmsgAddr, c.Param("path"))
+ } else {
+
+ hostParts := strings.Split(c.Request.Host, ":")
+ var dmsgp string
+ if len(hostParts) > 1 {
+ dmsgp = hostParts[1]
+ } else {
+ dmsgp = "80"
+ }
+ urlStr = fmt.Sprintf("dmsg://%s:%s%s", strings.TrimRight(hostParts[0], filterDomainSuffix), dmsgp, c.Param("path"))
+ }
+
+ req, err := http.NewRequest(http.MethodGet, urlStr, nil)
+ if err != nil {
+ c.String(http.StatusInternalServerError, "Failed to create HTTP request")
+ return
+ }
+
+ resp, err := httpC.Do(req)
+ if err != nil {
+ c.String(http.StatusInternalServerError, "Failed to connect to HTTP server")
+ return
+ }
+ defer resp.Body.Close() //nolint
+
+ c.Status(http.StatusOK)
+ io.Copy(c.Writer, resp.Body) //nolint
+ })
+ wg.Add(1)
+ go func() {
+ dmsgWebLog.Debug("Serving http on " + webPort)
+ r.Run(":" + webPort) //nolint
+ dmsgWebLog.Debug("Stopped serving http on " + webPort)
+ wg.Done()
+ }()
+ wg.Wait()
+ },
+}
diff --git a/examples/dmsgweb/commands/root.go b/examples/dmsgweb/commands/root.go
new file mode 100644
index 00000000..894fe977
--- /dev/null
+++ b/examples/dmsgweb/commands/root.go
@@ -0,0 +1,179 @@
+// Package commands cmd/dmsgweb/commands/dmsgweb.go
+package commands
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "net"
+ "net/http"
+ "os"
+ "regexp"
+ "sync"
+ "time"
+
+ "github.com/gin-gonic/gin"
+ "github.com/skycoin/skywire/pkg/skywire-utilities/pkg/cipher"
+ "github.com/skycoin/skywire/pkg/skywire-utilities/pkg/logging"
+ "github.com/spf13/cobra"
+
+ "github.com/skycoin/dmsg/pkg/disc"
+ dmsg "github.com/skycoin/dmsg/pkg/dmsg"
+)
+
+// RootCmd contains commands that interact with the config of local skywire-visor
+var genKeysCmd = &cobra.Command{
+ Use: "gen-keys",
+ Short: "generate public / secret keypair",
+ Run: func(cmd *cobra.Command, args []string) {
+ pk, sk := cipher.GenerateKeyPair()
+ fmt.Println(pk)
+ fmt.Println(sk)
+ },
+}
+
+type customResolver struct{}
+
+func (r *customResolver) Resolve(ctx context.Context, name string) (context.Context, net.IP, error) {
+ // Handle custom name resolution for .dmsg domains
+ regexPattern := `\.` + filterDomainSuffix + `(:[0-9]+)?$`
+ match, _ := regexp.MatchString(regexPattern, name) //nolint:errcheck
+ if match {
+ ip := net.ParseIP("127.0.0.1")
+ if ip == nil {
+ return ctx, nil, fmt.Errorf("failed to parse IP address")
+ }
+ // Modify the context to include the desired port
+ ctx = context.WithValue(ctx, "port", webPort) //nolint
+ return ctx, ip, nil
+ }
+ // Use default name resolution for other domains
+ return ctx, nil, nil
+}
+
+var (
+ httpC http.Client
+ dmsgDisc = dmsg.DiscAddr(false)
+ dmsgSessions int
+ filterDomainSuffix string
+ sk cipher.SecKey
+ dmsgWebLog *logging.Logger
+ logLvl string
+ webPort string
+ proxyPort string
+ addProxy string
+ resolveDmsgAddr string
+ wg sync.WaitGroup
+)
+
+func startDmsg(ctx context.Context, pk cipher.PubKey, sk cipher.SecKey) (dmsgC *dmsg.Client, stop func(), err error) {
+ dmsgC = dmsg.NewClient(pk, sk, disc.NewHTTP(dmsgDisc, &http.Client{}, dmsgWebLog), &dmsg.Config{MinSessions: dmsgSessions})
+ go dmsgC.Serve(context.Background())
+
+ stop = func() {
+ err := dmsgC.Close()
+ dmsgWebLog.WithError(err).Debug("Disconnected from dmsg network.")
+ fmt.Printf("\n")
+ }
+ dmsgWebLog.WithField("public_key", pk.String()).WithField("dmsg_disc", dmsgDisc).
+ Debug("Connecting to dmsg network...")
+
+ select {
+ case <-ctx.Done():
+ stop()
+ os.Exit(0)
+ return nil, nil, ctx.Err()
+
+ case <-dmsgC.Ready():
+ dmsgWebLog.Debug("Dmsg network ready.")
+ return dmsgC, stop, nil
+ }
+}
+
+func loggingMiddleware() gin.HandlerFunc {
+ return func(c *gin.Context) {
+ start := time.Now()
+ c.Next()
+ latency := time.Since(start)
+ if latency > time.Minute {
+ latency = latency.Truncate(time.Second)
+ }
+ statusCode := c.Writer.Status()
+ method := c.Request.Method
+ path := c.Request.URL.Path
+ // Get the background color based on the status code
+ statusCodeBackgroundColor := getBackgroundColor(statusCode)
+ // Get the method color
+ methodColor := getMethodColor(method)
+ // Print the logging in a custom format which includes the publickeyfrom c.Request.RemoteAddr ex.:
+ // [DMSGHTTP] 2023/05/18 - 19:43:15 | 200 | 10.80885ms | | 02b5ee5333aa6b7f5fc623b7d5f35f505cb7f974e98a70751cf41962f84c8c4637:49153 | GET /node-info.json
+ fmt.Printf("[DMSGHTTP] %s |%s %3d %s| %13v | %15s | %72s |%s %-7s %s %s\n",
+ time.Now().Format("2006/01/02 - 15:04:05"),
+ statusCodeBackgroundColor,
+ statusCode,
+ resetColor(),
+ latency,
+ c.ClientIP(),
+ c.Request.RemoteAddr,
+ methodColor,
+ method,
+ resetColor(),
+ path,
+ )
+ }
+}
+func getBackgroundColor(statusCode int) string {
+ switch {
+ case statusCode >= http.StatusOK && statusCode < http.StatusMultipleChoices:
+ return green
+ case statusCode >= http.StatusMultipleChoices && statusCode < http.StatusBadRequest:
+ return white
+ case statusCode >= http.StatusBadRequest && statusCode < http.StatusInternalServerError:
+ return yellow
+ default:
+ return red
+ }
+}
+
+func getMethodColor(method string) string {
+ switch method {
+ case http.MethodGet:
+ return blue
+ case http.MethodPost:
+ return cyan
+ case http.MethodPut:
+ return yellow
+ case http.MethodDelete:
+ return red
+ case http.MethodPatch:
+ return green
+ case http.MethodHead:
+ return magenta
+ case http.MethodOptions:
+ return white
+ default:
+ return reset
+ }
+}
+
+func resetColor() string {
+ return reset
+}
+
+const (
+ green = "\033[97;42m"
+ white = "\033[90;47m"
+ yellow = "\033[90;43m"
+ red = "\033[97;41m"
+ blue = "\033[97;44m"
+ magenta = "\033[97;45m"
+ cyan = "\033[97;46m"
+ reset = "\033[0m"
+)
+
+// Execute executes root CLI command.
+func Execute() {
+ if err := RootCmd.Execute(); err != nil {
+ log.Fatal("Failed to execute command: ", err)
+ }
+}
diff --git a/examples/dmsgweb/dmsgweb.go b/examples/dmsgweb/dmsgweb.go
new file mode 100644
index 00000000..58198978
--- /dev/null
+++ b/examples/dmsgweb/dmsgweb.go
@@ -0,0 +1,44 @@
+// Package main cmd/dmsgweb/dmsgweb.go
+package main
+
+import (
+ cc "github.com/ivanpirog/coloredcobra"
+ "github.com/spf13/cobra"
+
+ "github.com/skycoin/dmsg/examples/dmsgweb/commands"
+)
+
+func init() {
+ var helpflag bool
+ commands.RootCmd.SetUsageTemplate(help)
+ commands.RootCmd.PersistentFlags().BoolVarP(&helpflag, "help", "h", false, "help for dmsgweb")
+ commands.RootCmd.SetHelpCommand(&cobra.Command{Hidden: true})
+ commands.RootCmd.PersistentFlags().MarkHidden("help") //nolint
+}
+
+func main() {
+ cc.Init(&cc.Config{
+ RootCmd: commands.RootCmd,
+ Headings: cc.HiBlue + cc.Bold,
+ Commands: cc.HiBlue + cc.Bold,
+ CmdShortDescr: cc.HiBlue,
+ Example: cc.HiBlue + cc.Italic,
+ ExecName: cc.HiBlue + cc.Bold,
+ Flags: cc.HiBlue + cc.Bold,
+ //FlagsDataType: cc.HiBlue,
+ FlagsDescr: cc.HiBlue,
+ NoExtraNewlines: true,
+ NoBottomNewline: true,
+ })
+ commands.Execute()
+}
+
+const help = "Usage:\r\n" +
+ " {{.UseLine}}{{if .HasAvailableSubCommands}}{{end}} {{if gt (len .Aliases) 0}}\r\n\r\n" +
+ "{{.NameAndAliases}}{{end}}{{if .HasAvailableSubCommands}}\r\n\r\n" +
+ "Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand)}}\r\n " +
+ "{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}\r\n\r\n" +
+ "Flags:\r\n" +
+ "{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}\r\n\r\n" +
+ "Global Flags:\r\n" +
+ "{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}\r\n\r\n"
diff --git a/examples/gen-keys/README.md b/examples/gen-keys/README.md
new file mode 100644
index 00000000..2c6c673e
--- /dev/null
+++ b/examples/gen-keys/README.md
@@ -0,0 +1,11 @@
+## public / secret key pair generation
+
+Returns:
+* Public Key
+* Secret Key
+
+```
+$ go run gen-keys.go
+03620454a0ba368051defa4baac2fd92a434449292d2dd2aff8b81041403a9d3d1
+54ce2ff7310f8ab33e01d9b0f5be7f79a62ba92d812a113b1a8a993c95f162ac
+```
diff --git a/examples/gen-keys/gen-keys.go b/examples/gen-keys/gen-keys.go
new file mode 100644
index 00000000..cf7175af
--- /dev/null
+++ b/examples/gen-keys/gen-keys.go
@@ -0,0 +1,13 @@
+// example keypair generation
+package main
+
+import (
+ "fmt"
+
+ "github.com/skycoin/skywire/pkg/skywire-utilities/pkg/cipher"
+)
+
+func main() {
+ pk, sk := cipher.GenerateKeyPair()
+ fmt.Printf("%s\n%s\n", pk, sk)
+}
diff --git a/examples/http/README.md b/examples/http/README.md
new file mode 100644
index 00000000..7e79b8ee
--- /dev/null
+++ b/examples/http/README.md
@@ -0,0 +1 @@
+example hello world via HTTP
diff --git a/examples/http/http.go b/examples/http/http.go
new file mode 100644
index 00000000..90cc98d5
--- /dev/null
+++ b/examples/http/http.go
@@ -0,0 +1,34 @@
+// example hello world HTTP
+package main
+
+import (
+ "fmt"
+ "log"
+ "net"
+ "net/http"
+ "os"
+)
+
+func main() {
+ // Define the HTTP handler
+ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ log.Printf("Received request: %s %s", r.Method, r.URL.Path)
+ fmt.Fprintf(w, "Hello, World!\n")
+ })
+
+ // Use the specified port from command-line arguments
+ address := os.Args[1]
+ listener, err := net.Listen("tcp", address)
+ if err != nil {
+ log.Fatal("Failed to start HTTP server:", err)
+ return
+ }
+ defer listener.Close()
+
+ log.Println("HTTP server started on", address)
+ // Start the HTTP server
+ err = http.Serve(listener, nil)
+ if err != nil {
+ log.Fatal("HTTP server stopped with error:", err)
+ }
+}
diff --git a/examples/tcp-multi-proxy-dmsg/tcp-multi-proxy-dmsg.go b/examples/tcp-multi-proxy-dmsg/tcp-multi-proxy-dmsg.go
new file mode 100644
index 00000000..b7a60352
--- /dev/null
+++ b/examples/tcp-multi-proxy-dmsg/tcp-multi-proxy-dmsg.go
@@ -0,0 +1,174 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "net"
+ "net/http"
+ "sync"
+
+ cc "github.com/ivanpirog/coloredcobra"
+ "github.com/skycoin/skywire/pkg/skywire-utilities/pkg/cipher"
+ "github.com/skycoin/skywire/pkg/skywire-utilities/pkg/cmdutil"
+ "github.com/skycoin/skywire/pkg/skywire-utilities/pkg/logging"
+ "github.com/spf13/cobra"
+
+ "github.com/skycoin/dmsg/pkg/disc"
+ dmsg "github.com/skycoin/dmsg/pkg/dmsg"
+)
+
+func main() {
+ cc.Init(&cc.Config{
+ RootCmd: srvCmd,
+ Headings: cc.HiBlue + cc.Bold,
+ Commands: cc.HiBlue + cc.Bold,
+ CmdShortDescr: cc.HiBlue,
+ Example: cc.HiBlue + cc.Italic,
+ ExecName: cc.HiBlue + cc.Bold,
+ Flags: cc.HiBlue + cc.Bold,
+ FlagsDescr: cc.HiBlue,
+ NoExtraNewlines: true,
+ NoBottomNewline: true,
+ })
+ srvCmd.Execute()
+}
+
+const help = "Usage:\r\n" +
+ " {{.UseLine}}{{if .HasAvailableSubCommands}}{{end}} {{if gt (len .Aliases) 0}}\r\n\r\n" +
+ "{{.NameAndAliases}}{{end}}{{if .HasAvailableSubCommands}}\r\n\r\n" +
+ "Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand)}}\r\n " +
+ "{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}\r\n\r\n" +
+ "Flags:\r\n" +
+ "{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}\r\n\r\n" +
+ "Global Flags:\r\n" +
+ "{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}\r\n\r\n"
+
+var (
+ localPorts []uint
+ dmsgPorts []uint
+ dmsgDisc string
+ dmsgSess int
+ sk cipher.SecKey
+)
+
+func init() {
+ srvCmd.Flags().UintSliceVarP(&localPorts, "lport", "l", nil, "local application HTTP interface port(s) (comma-separated)")
+ srvCmd.Flags().UintSliceVarP(&dmsgPorts, "dport", "d", nil, "DMSG port(s) to serve (comma-separated)")
+ srvCmd.Flags().StringVarP(&dmsgDisc, "dmsg-disc", "D", dmsg.DiscAddr(false), "DMSG discovery URL")
+ srvCmd.Flags().IntVarP(&dmsgSess, "dsess", "e", 1, "DMSG sessions")
+ srvCmd.Flags().VarP(&sk, "sk", "s", "a random key is generated if unspecified\n\r")
+
+ srvCmd.CompletionOptions.DisableDefaultCmd = true
+ var helpFlag bool
+ srvCmd.SetUsageTemplate(help)
+ srvCmd.PersistentFlags().BoolVarP(&helpFlag, "help", "h", false, "help for dmsgweb")
+ srvCmd.SetHelpCommand(&cobra.Command{Hidden: true})
+ srvCmd.PersistentFlags().MarkHidden("help") //nolint
+}
+
+var srvCmd = &cobra.Command{
+ Use: "srv",
+ Short: "Serve raw TCP from local ports over DMSG",
+ Long: `DMSG web server - serve HTTP or raw TCP interface from local ports over DMSG`,
+ Run: func(_ *cobra.Command, _ []string) {
+ server()
+ },
+}
+
+func server() {
+ log := logging.MustGetLogger("dmsgwebsrv")
+ ctx, cancel := cmdutil.SignalContext(context.Background(), log)
+ defer cancel()
+
+ if len(localPorts) != len(dmsgPorts) {
+ log.Fatalf("The number of local ports (%d) must match the number of DMSG ports (%d).", len(localPorts), len(dmsgPorts))
+ }
+
+ pk, err := sk.PubKey()
+ if err != nil {
+ pk, sk = cipher.GenerateKeyPair()
+ }
+ log.Infof("DMSG client public key: %v", pk.String())
+
+ dmsgC := dmsg.NewClient(pk, sk, disc.NewHTTP(dmsgDisc, &http.Client{}, log), dmsg.DefaultConfig())
+ defer func() {
+ if err := dmsgC.Close(); err != nil {
+ log.WithError(err).Error("Error closing DMSG client")
+ }
+ }()
+ go dmsgC.Serve(ctx)
+
+ select {
+ case <-ctx.Done():
+ log.WithError(ctx.Err()).Warn()
+ return
+ case <-dmsgC.Ready():
+ log.Info("DMSG client is ready.")
+ }
+
+ wg := new(sync.WaitGroup)
+ for i, localPort := range localPorts {
+ dmsgPort := dmsgPorts[i]
+ wg.Add(1)
+
+ go func(localPort, dmsgPort uint) {
+ defer wg.Done()
+ proxyPort(ctx, dmsgC, localPort, dmsgPort, log)
+ }(localPort, dmsgPort)
+ }
+
+ wg.Wait()
+}
+
+func proxyPort(ctx context.Context, dmsgC *dmsg.Client, localPort, dmsgPort uint, log *logging.Logger) {
+ listener, err := dmsgC.Listen(uint16(dmsgPort))
+ if err != nil {
+ log.Fatalf("Error listening on DMSG port %d: %v", dmsgPort, err)
+ }
+ defer listener.Close()
+
+ log.Infof("Started proxying local port %d to DMSG port %d", localPort, dmsgPort)
+
+ go func() {
+ <-ctx.Done()
+ listener.Close()
+ }()
+
+ for {
+ conn, err := listener.Accept()
+ if err != nil {
+ log.Printf("Error accepting connection on DMSG port %d: %v", dmsgPort, err)
+ return
+ }
+
+ go handleTCPConnection(conn, localPort, log)
+ }
+}
+
+func handleTCPConnection(dmsgConn net.Conn, localPort uint, log *logging.Logger) {
+ defer dmsgConn.Close()
+
+ localConn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", localPort))
+ if err != nil {
+ log.Printf("Failed to connect to local port %d: %v", localPort, err)
+ return
+ }
+ defer localConn.Close()
+
+ var wg sync.WaitGroup
+ wg.Add(2)
+
+ go func() {
+ defer wg.Done()
+ io.Copy(dmsgConn, localConn)
+ }()
+
+ go func() {
+ defer wg.Done()
+ io.Copy(localConn, dmsgConn)
+ }()
+
+ wg.Wait()
+ log.Printf("Closed connection between local port %d and DMSG connection", localPort)
+}
diff --git a/examples/tcp-proxy-dmsg/tcp-proxy-dmsg.go b/examples/tcp-proxy-dmsg/tcp-proxy-dmsg.go
new file mode 100644
index 00000000..e707329a
--- /dev/null
+++ b/examples/tcp-proxy-dmsg/tcp-proxy-dmsg.go
@@ -0,0 +1,256 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "net"
+ "net/http"
+ "os"
+ "sync"
+
+ cc "github.com/ivanpirog/coloredcobra"
+ "github.com/skycoin/skywire/pkg/skywire-utilities/pkg/cipher"
+ "github.com/skycoin/skywire/pkg/skywire-utilities/pkg/cmdutil"
+ "github.com/skycoin/skywire/pkg/skywire-utilities/pkg/logging"
+ "github.com/spf13/cobra"
+
+ "github.com/skycoin/dmsg/pkg/disc"
+ dmsg "github.com/skycoin/dmsg/pkg/dmsg"
+)
+
+func main() {
+ cc.Init(&cc.Config{
+ RootCmd: srvCmd,
+ Headings: cc.HiBlue + cc.Bold,
+ Commands: cc.HiBlue + cc.Bold,
+ CmdShortDescr: cc.HiBlue,
+ Example: cc.HiBlue + cc.Italic,
+ ExecName: cc.HiBlue + cc.Bold,
+ Flags: cc.HiBlue + cc.Bold,
+ FlagsDescr: cc.HiBlue,
+ NoExtraNewlines: true,
+ NoBottomNewline: true,
+ })
+ srvCmd.Execute()
+}
+
+const help = "Usage:\r\n" +
+ " {{.UseLine}}{{if .HasAvailableSubCommands}}{{end}} {{if gt (len .Aliases) 0}}\r\n\r\n" +
+ "{{.NameAndAliases}}{{end}}{{if .HasAvailableSubCommands}}\r\n\r\n" +
+ "Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand)}}\r\n " +
+ "{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}\r\n\r\n" +
+ "Flags:\r\n" +
+ "{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}\r\n\r\n" +
+ "Global Flags:\r\n" +
+ "{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}\r\n\r\n"
+
+var (
+ httpC http.Client
+ dmsgC *dmsg.Client
+ closeDmsg func()
+ dmsgDisc string
+ dmsgSessions int
+ dmsgAddr []string
+ dialPK []cipher.PubKey
+ filterDomainSuffix string
+ sk cipher.SecKey
+ pk cipher.PubKey
+ dmsgWebLog *logging.Logger
+ logLvl string
+ webPort []uint
+ proxyPort uint
+ addProxy string
+ resolveDmsgAddr []string
+ wg sync.WaitGroup
+ isEnvs bool
+ dmsgPort uint
+ dmsgPorts []uint
+ dmsgSess int
+ wl []string
+ wlkeys []cipher.PubKey
+ localPort uint
+ err error
+ rawTCP []bool
+ RootCmd = srvCmd
+)
+
+func init() {
+ srvCmd.Flags().UintVarP(&localPort, "lport", "l", 8086, "local application http interface port(s)")
+ srvCmd.Flags().UintVarP(&dmsgPort, "dport", "d", 8086, "dmsg port(s) to serve")
+ srvCmd.Flags().StringVarP(&dmsgDisc, "dmsg-disc", "D", dmsg.DiscAddr(false), "dmsg discovery url")
+ srvCmd.Flags().IntVarP(&dmsgSess, "dsess", "e", 1, "dmsg sessions")
+ srvCmd.Flags().VarP(&sk, "sk", "s", "a random key is generated if unspecified\n\r")
+
+ srvCmd.CompletionOptions.DisableDefaultCmd = true
+ var helpflag bool
+ srvCmd.SetUsageTemplate(help)
+ srvCmd.PersistentFlags().BoolVarP(&helpflag, "help", "h", false, "help for dmsgweb")
+ srvCmd.SetHelpCommand(&cobra.Command{Hidden: true})
+ srvCmd.PersistentFlags().MarkHidden("help") //nolint
+}
+
+var srvCmd = &cobra.Command{
+ Use: "srv",
+ Short: "serve raw TCP from local port over dmsg",
+ Long: `DMSG web server - serve http or raw TCP interface from local port over dmsg`,
+ Run: func(_ *cobra.Command, _ []string) {
+ server()
+ },
+}
+
+func server() {
+ log := logging.MustGetLogger("dmsgwebsrv")
+
+ ctx, cancel := cmdutil.SignalContext(context.Background(), log)
+
+ defer cancel()
+ pk, err = sk.PubKey()
+ if err != nil {
+ pk, sk = cipher.GenerateKeyPair()
+ }
+ log.Infof("dmsg client pk: %v", pk.String())
+
+ dmsgC := dmsg.NewClient(pk, sk, disc.NewHTTP(dmsgDisc, &http.Client{}, log), dmsg.DefaultConfig())
+ defer func() {
+ if err := dmsgC.Close(); err != nil {
+ log.WithError(err).Error()
+ }
+ }()
+
+ go dmsgC.Serve(context.Background())
+
+ select {
+ case <-ctx.Done():
+ log.WithError(ctx.Err()).Warn()
+ return
+
+ case <-dmsgC.Ready():
+ }
+
+ lis, err := dmsgC.Listen(uint16(dmsgPort))
+ if err != nil {
+ log.Fatalf("Error listening on port %d: %v", dmsgPort, err)
+ }
+
+ go func(l net.Listener, port uint) {
+ <-ctx.Done()
+ if err := l.Close(); err != nil {
+ log.Printf("Error closing listener on port %d: %v", port, err)
+ log.WithError(err).Error()
+ }
+ }(lis, dmsgPort)
+
+ wg := new(sync.WaitGroup)
+
+ wg.Add(1)
+ go func(localPort uint, lis net.Listener) {
+ defer wg.Done()
+ proxyTCPConnections(localPort, lis, log)
+ }(localPort, lis)
+
+ wg.Wait()
+}
+
+func proxyTCPConnections(localPort uint, lis net.Listener, log *logging.Logger) {
+ for {
+ conn, err := lis.Accept()
+ if err != nil {
+ log.Printf("Error accepting connection: %v", err)
+ return
+ }
+
+ go handleTCPConnection(conn, localPort, log)
+ }
+}
+
+func handleTCPConnection(dmsgConn net.Conn, localPort uint, log *logging.Logger) {
+ defer dmsgConn.Close() // Ensure the dmsg connection is closed.
+
+ localConn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", localPort))
+ if err != nil {
+ log.Printf("Failed to dial server %s: %v", fmt.Sprintf("127.0.0.1:%d", localPort), err)
+ return
+ }
+ defer localConn.Close() // Ensure the local connection is closed.
+
+ var wg sync.WaitGroup
+ wg.Add(2)
+
+ // Log data copied from localConn to dmsgConn
+ go func() {
+ defer wg.Done()
+ reader := io.TeeReader(localConn, logWriter("local -> dmsg", log))
+ _, err := io.Copy(dmsgConn, reader)
+ if err != nil && !isClosedConnErr(err) {
+ log.Printf("Error copying from local to dmsg: %v", err)
+ }
+ }()
+
+ // Log data copied from dmsgConn to localConn
+ go func() {
+ defer wg.Done()
+ reader := io.TeeReader(dmsgConn, logWriter("dmsg -> local", log))
+ _, err := io.Copy(localConn, reader)
+ if err != nil && !isClosedConnErr(err) {
+ log.Printf("Error copying from dmsg to local: %v", err)
+ }
+ }()
+
+ wg.Wait()
+ dmsgConn.Close()
+ localConn.Close()
+ log.Printf("Closed connection between DMSG and local port %d", localPort)
+}
+
+// logWriter creates a writer that logs the copied data with a prefix.
+func logWriter(direction string, log *logging.Logger) io.Writer {
+ return &logWriterImpl{
+ prefix: direction,
+ log: log,
+ }
+}
+
+// logWriterImpl is an implementation of io.Writer that logs data as it is written.
+type logWriterImpl struct {
+ prefix string
+ log *logging.Logger
+}
+
+func (lw *logWriterImpl) Write(p []byte) (int, error) {
+ lw.log.Printf("[%s] %s", lw.prefix, string(p)) // Log the data as a string.
+ return len(p), nil
+}
+
+// isClosedConnErr checks if the error indicates a closed connection.
+func isClosedConnErr(err error) bool {
+ if err == io.EOF {
+ return true
+ }
+ netErr, ok := err.(net.Error)
+ return ok && netErr.Timeout() // Check for timeout error indicating closed connection
+}
+
+func startDmsg(ctx context.Context, pk cipher.PubKey, sk cipher.SecKey) (dmsgC *dmsg.Client, stop func(), err error) {
+ dmsgC = dmsg.NewClient(pk, sk, disc.NewHTTP(dmsgDisc, &http.Client{}, dmsgWebLog), &dmsg.Config{MinSessions: dmsgSessions})
+ go dmsgC.Serve(context.Background())
+
+ stop = func() {
+ err := dmsgC.Close()
+ dmsgWebLog.WithError(err).Debug("Disconnected from dmsg network.")
+ fmt.Printf("\n")
+ }
+ dmsgWebLog.WithField("public_key", pk.String()).WithField("dmsg_disc", dmsgDisc).
+ Debug("Connecting to dmsg network...")
+
+ select {
+ case <-ctx.Done():
+ stop()
+ os.Exit(0)
+ return nil, nil, ctx.Err()
+
+ case <-dmsgC.Ready():
+ dmsgWebLog.Debug("Dmsg network ready.")
+ return dmsgC, stop, nil
+ }
+}
diff --git a/examples/tcp-proxy/tcp-proxy.go b/examples/tcp-proxy/tcp-proxy.go
new file mode 100644
index 00000000..d126ae45
--- /dev/null
+++ b/examples/tcp-proxy/tcp-proxy.go
@@ -0,0 +1,84 @@
+package main
+
+import (
+ "fmt"
+ "io"
+ "log"
+ "net"
+ "os"
+ "strconv"
+ "sync"
+)
+
+func main() {
+ if len(os.Args) < 3 {
+ log.Fatalf("requires two arguments; usage: tcp-proxy ")
+ }
+ sourcePort, err := strconv.Atoi(os.Args[2])
+ if err != nil {
+ log.Fatalf("Failed to parse tcp source port string \"%v\" to int: %v", sourcePort, err)
+ }
+ targetPort, err := strconv.Atoi(os.Args[1])
+ if err != nil {
+ log.Fatalf("Failed to parse tcp target port string \"%v\" to int: %v", targetPort, err)
+ }
+ listener, err := net.Listen("tcp", fmt.Sprintf(":%d", sourcePort))
+ if err != nil {
+ log.Fatalf("Failed to start TCP listener on port %d: %v", sourcePort, err)
+ }
+ defer listener.Close()
+ log.Printf("TCP proxy started: Listening on port %d and forwarding to port %d", sourcePort, targetPort)
+
+ for {
+ conn, err := listener.Accept()
+ if err != nil {
+ log.Printf("Failed to accept connection: %v", err)
+ continue
+ }
+
+ go handleConnection(conn, targetPort)
+ }
+}
+
+func handleConnection(conn net.Conn, targetPort int) {
+ defer conn.Close()
+
+ targetAddr := fmt.Sprintf("localhost:%d", targetPort)
+ target, err := net.Dial("tcp", targetAddr)
+ if err != nil {
+ log.Printf("Failed to dial target server %s: %v", targetAddr, err)
+ return
+ }
+ defer target.Close()
+
+ var wg sync.WaitGroup
+ wg.Add(2)
+
+ go func() {
+ _, err := io.Copy(target, conn)
+ if err != nil && !isClosedConnErr(err) {
+ log.Printf("Error copying from client to target: %v", err)
+ }
+ target.Close()
+ wg.Done()
+ }()
+
+ go func() {
+ _, err := io.Copy(conn, target)
+ if err != nil && !isClosedConnErr(err) {
+ log.Printf("Error copying from target to client: %v", err)
+ }
+ conn.Close()
+ wg.Done()
+ }()
+
+ wg.Wait()
+}
+
+func isClosedConnErr(err error) bool {
+ if err == io.EOF {
+ return true
+ }
+ netErr, ok := err.(net.Error)
+ return ok && netErr.Timeout()
+}
diff --git a/examples/tcp-reverse-proxy-dmsg/tcp-reverse-proxy-dmsg.go b/examples/tcp-reverse-proxy-dmsg/tcp-reverse-proxy-dmsg.go
new file mode 100644
index 00000000..1bbd240f
--- /dev/null
+++ b/examples/tcp-reverse-proxy-dmsg/tcp-reverse-proxy-dmsg.go
@@ -0,0 +1,249 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "log"
+ "net"
+ "net/http"
+ "os"
+ "os/signal"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "sync"
+ "syscall"
+
+ cc "github.com/ivanpirog/coloredcobra"
+ "github.com/skycoin/skywire/pkg/skywire-utilities/pkg/cipher"
+ "github.com/skycoin/skywire/pkg/skywire-utilities/pkg/cmdutil"
+ "github.com/skycoin/skywire/pkg/skywire-utilities/pkg/logging"
+ "github.com/spf13/cobra"
+
+ "github.com/skycoin/dmsg/pkg/disc"
+ dmsg "github.com/skycoin/dmsg/pkg/dmsg"
+)
+
+func main() {
+ cc.Init(&cc.Config{
+ RootCmd: RootCmd,
+ Headings: cc.HiBlue + cc.Bold,
+ Commands: cc.HiBlue + cc.Bold,
+ CmdShortDescr: cc.HiBlue,
+ Example: cc.HiBlue + cc.Italic,
+ ExecName: cc.HiBlue + cc.Bold,
+ Flags: cc.HiBlue + cc.Bold,
+ FlagsDescr: cc.HiBlue,
+ NoExtraNewlines: true,
+ NoBottomNewline: true,
+ })
+ RootCmd.Execute()
+}
+
+const help = "Usage:\r\n" +
+ " {{.UseLine}}{{if .HasAvailableSubCommands}}{{end}} {{if gt (len .Aliases) 0}}\r\n\r\n" +
+ "{{.NameAndAliases}}{{end}}{{if .HasAvailableSubCommands}}\r\n\r\n" +
+ "Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand)}}\r\n " +
+ "{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}\r\n\r\n" +
+ "Flags:\r\n" +
+ "{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}\r\n\r\n" +
+ "Global Flags:\r\n" +
+ "{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}\r\n\r\n"
+
+var (
+ httpC http.Client
+ dmsgC *dmsg.Client
+ closeDmsg func()
+ dmsgDisc string
+ dmsgSessions int
+ dmsgAddr []string
+ dialPK cipher.PubKey
+ sk cipher.SecKey
+ pk cipher.PubKey
+ dmsgWebLog *logging.Logger
+ logLvl string
+ webPort uint
+ resolveDmsgAddr string
+ wg sync.WaitGroup
+ dmsgPort uint
+ dmsgSess int
+ err error
+)
+
+func init() {
+ RootCmd.Flags().UintVarP(&webPort, "port", "p", 8080, "port to serve the web application")
+ RootCmd.Flags().StringVarP(&resolveDmsgAddr, "resolve", "t", "", "resolve the specified dmsg address:port on the local port & disable proxy")
+ RootCmd.Flags().StringVarP(&dmsgDisc, "dmsg-disc", "d", dmsg.DiscAddr(false), "dmsg discovery url")
+ RootCmd.Flags().IntVarP(&dmsgSessions, "sess", "e", 1, "number of dmsg servers to connect to")
+ RootCmd.Flags().StringVarP(&logLvl, "loglvl", "l", "", "[ debug | warn | error | fatal | panic | trace | info ]\033[0m")
+ RootCmd.Flags().VarP(&sk, "sk", "s", "a random key is generated if unspecified\n\r")
+}
+
+// RootCmd contains the root command for dmsgweb
+var RootCmd = &cobra.Command{
+ Use: func() string {
+ return strings.Split(filepath.Base(strings.ReplaceAll(strings.ReplaceAll(fmt.Sprintf("%v", os.Args), "[", ""), "]", "")), " ")[0]
+ }(),
+ Short: "DMSG reverse tcp proxy",
+ Long: "DMSG reverse tcp proxy",
+ SilenceErrors: true,
+ SilenceUsage: true,
+ DisableSuggestions: true,
+ DisableFlagsInUseLine: true,
+ Run: func(cmd *cobra.Command, _ []string) {
+
+ c := make(chan os.Signal, 1)
+ signal.Notify(c, os.Interrupt, syscall.SIGTERM) //nolint
+ go func() {
+ <-c
+ os.Exit(1)
+ }()
+ if dmsgWebLog == nil {
+ dmsgWebLog = logging.MustGetLogger("dmsgweb")
+ }
+ if logLvl != "" {
+ if lvl, err := logging.LevelFromString(logLvl); err == nil {
+ logging.SetLevel(lvl)
+ }
+ }
+
+ ctx, cancel := cmdutil.SignalContext(context.Background(), dmsgWebLog)
+ defer cancel()
+
+ pk, err := sk.PubKey()
+ if err != nil {
+ pk, sk = cipher.GenerateKeyPair()
+ }
+ dmsgWebLog.Info("dmsg client pk: ", pk.String())
+
+ dmsgWebLog.Info("dmsg address to dial: ", resolveDmsgAddr)
+ dmsgAddr = strings.Split(resolveDmsgAddr, ":")
+ var setpk cipher.PubKey
+ err = setpk.Set(dmsgAddr[0])
+ if err != nil {
+ log.Fatalf("failed to parse dmsg : : %v", err)
+ }
+ dialPK = setpk
+ if len(dmsgAddr) > 1 {
+ dport, err := strconv.ParseUint(dmsgAddr[1], 10, 64)
+ if err != nil {
+ log.Fatalf("Failed to parse dmsg port: %v", err)
+ }
+ dmsgPort = uint(dport)
+ } else {
+ dmsgPort = uint(80)
+ }
+
+ dmsgC, closeDmsg, err = startDmsg(ctx, pk, sk)
+ if err != nil {
+ dmsgWebLog.WithError(err).Fatal("failed to start dmsg")
+ }
+ defer closeDmsg()
+
+ go func() {
+ <-ctx.Done()
+ cancel()
+ closeDmsg()
+ os.Exit(0)
+ }()
+
+ proxyTCPConn()
+ wg.Wait()
+ },
+}
+
+func proxyTCPConn() {
+ listener, err := net.Listen("tcp", fmt.Sprintf(":%v", webPort))
+ if err != nil {
+ dmsgWebLog.Fatalf("Failed to start TCP listener on port %v: %v", webPort, err)
+ }
+ defer listener.Close() //nolint
+ log.Printf("Serving TCP on 127.0.0.1:%v", webPort)
+ if dmsgC == nil {
+ log.Fatal("dmsgC is nil")
+ }
+
+ for {
+ conn, err := listener.Accept()
+ if err != nil {
+ log.Printf("Failed to accept connection: %v", err)
+ continue
+ }
+
+ wg.Add(1)
+ go func(conn net.Conn) {
+ defer wg.Done()
+
+ log.Println(fmt.Sprintf("Dialing dmsg address: %v ; port: %v", dialPK.String(), dmsgPort))
+ dmsgConn, err := dmsgC.DialStream(context.Background(), dmsg.Addr{PK: dialPK, Port: uint16(dmsgPort)})
+ if err != nil {
+ log.Printf("Failed to dial dmsg address %v:%v %v", dialPK.String(), dmsgPort, err)
+ return
+ }
+ // Log data copied to the dmsg connection (from the client connection)
+ go func() {
+ defer dmsgConn.Close()
+ reader := io.TeeReader(conn, logWriter("client -> dmsg", dmsgWebLog))
+ _, err := io.Copy(dmsgConn, reader)
+ if err != nil {
+ log.Printf("Error copying data to dmsg client: %v", err)
+ }
+ }()
+
+ // Log data copied from the dmsg connection (to the client connection)
+ go func() {
+ defer conn.Close() //nolint
+ reader := io.TeeReader(dmsgConn, logWriter("dmsg -> client", dmsgWebLog))
+ _, err := io.Copy(conn, reader)
+ if err != nil {
+ log.Printf("Error copying data from dmsg client: %v", err)
+ }
+ }()
+ }(conn)
+ wg.Wait()
+ }
+}
+
+// logWriter creates a writer that logs the copied data with a prefix.
+func logWriter(direction string, log *logging.Logger) io.Writer {
+ return &logWriterImpl{
+ prefix: direction,
+ log: log,
+ }
+}
+
+// logWriterImpl is an implementation of io.Writer that logs data as it is written.
+type logWriterImpl struct {
+ prefix string
+ log *logging.Logger
+}
+
+func (lw *logWriterImpl) Write(p []byte) (int, error) {
+ lw.log.Printf("[%s] %s", lw.prefix, string(p)) // Log the data as a string.
+ return len(p), nil
+}
+
+func startDmsg(ctx context.Context, pk cipher.PubKey, sk cipher.SecKey) (dmsgC *dmsg.Client, stop func(), err error) {
+ dmsgC = dmsg.NewClient(pk, sk, disc.NewHTTP(dmsgDisc, &http.Client{}, dmsgWebLog), &dmsg.Config{MinSessions: dmsgSessions})
+ go dmsgC.Serve(context.Background())
+
+ stop = func() {
+ err := dmsgC.Close()
+ dmsgWebLog.WithError(err).Debug("Disconnected from dmsg network.")
+ fmt.Printf("\n")
+ }
+ dmsgWebLog.WithField("public_key", pk.String()).WithField("dmsg_disc", dmsgDisc).
+ Debug("Connecting to dmsg network...")
+
+ select {
+ case <-ctx.Done():
+ stop()
+ os.Exit(0)
+ return nil, nil, ctx.Err()
+
+ case <-dmsgC.Ready():
+ dmsgWebLog.Debug("Dmsg network ready.")
+ return dmsgC, stop, nil
+ }
+}
diff --git a/examples/tcp/README.md b/examples/tcp/README.md
new file mode 100644
index 00000000..e7f52381
--- /dev/null
+++ b/examples/tcp/README.md
@@ -0,0 +1 @@
+example hello world via TCP
diff --git a/examples/tcp/tcp.go b/examples/tcp/tcp.go
new file mode 100644
index 00000000..c9569f1c
--- /dev/null
+++ b/examples/tcp/tcp.go
@@ -0,0 +1,41 @@
+// example hello world TCP
+package main
+
+import (
+ "log"
+ "net"
+ "os"
+)
+
+func main() {
+ // Start a TCP server listening on port 8000
+ listener, err := net.Listen("tcp", os.Args[1]) //":8000")
+ if err != nil {
+ log.Fatal("Failed to start server:", err)
+ return
+ }
+ defer listener.Close()
+ log.Println("TCP server started on port", os.Args[1])
+
+ // Accept and handle incoming connections
+ for {
+ conn, err := listener.Accept()
+ if err != nil {
+ log.Println("Failed to accept connection:", err)
+ continue
+ }
+ go handleConnection(conn)
+ }
+}
+
+func handleConnection(conn net.Conn) {
+ defer conn.Close()
+ log.Println("Handling Connection")
+ // Send a greeting message to the client
+ message := "Hello, World!\n"
+ _, err := conn.Write([]byte(message))
+ if err != nil {
+ log.Println("Error writing response:", err)
+ return
+ }
+}