Skip to content

Commit c1a367e

Browse files
authored
Feat/ip from server (#272)
* feat: Add DialServerForIP method to dmsg client This commit adds a new method `DialServerForIP` to the `Client` struct in the `pkg/dmsg/client.go` file. This method dials to dmsg servers to retrieve the public IP address of the client. It iterates through a list of server entries, attempts to dial each server, and returns the first public IP address it receives. The purpose of this change is to provide a way for the client to obtain its public IP address from dmsg servers. * feat: Add new commandline tool dmsgip * chore: fix linting * feat: Add support for dmsg server public keys in DialServerForIP method This commit modifies the `DialServerForIP` method in the `pkg/dmsg/client.go` file to accept a slice of dmsg server public keys as an argument. If the `servers` argument is nil, the method retrieves the server entries using the `discoverServers` function and populates the `servers` slice with the static public keys from the entries. Then, it iterates through the `servers` slice and attempts to dial each server to retrieve the public IP address of the client. The purpose of this change is to allow the `DialServerForIP` method to support custom dmsg server public keys, providing more flexibility in obtaining the client's public IP address from dmsg servers. * feat: Add error handling for non-public IP address in DialServerForIP method * refactor: Improve error handling and connection logic in DialServerForIP method This commit refactors the `DialServerForIP` method in the `pkg/dmsg/client.go` file to improve error handling and connection logic. It introduces two separate loops to handle delegated servers and attempts to connect to each server individually. Additionally, it properly closes if the session is created just for the IP after dialing the server. The purpose of this change is to enhance the reliability and stability of the `DialServerForIP` method when retrieving the public IP address from dmsg servers. * refactor: Improve error handling and add MinSessions to startDmsg * feat: Add dmsgip commandline tool to dmsg asa a subcommand * refactor: Rename DialServerForIP to LookupIP in dmsg client This commit renames the `DialServerForIP` method to `LookupIP` in the `pkg/dmsg/client.go` file. The functionality remains the same, but the new name better reflects the purpose of the method, which is to lookup the public IP address of the client from dmsg servers. The purpose of this change is to improve the clarity and consistency of the method name, making it more intuitive for developers working with the dmsg client. * chore: Move code around * refactor: Improve error handling in LookupIP method * test: add unit tests for LookupIP * refactor: improve error handling in dmsgip cmd * test: update IP lookup logic in stream_test.go This commit updates the IP lookup logic in the `stream_test.go` file. It introduces conditional checks based on the operating system to handle different IP address formats. On Windows, the IP address is expected to be "127.0.0.1", while on other operating systems, it is expected to be "::1". The purpose of this change is to ensure that the IP lookup tests pass correctly on different operating systems, improving the reliability and consistency of the test suite.
1 parent 1c2fcec commit c1a367e

File tree

10 files changed

+490
-1
lines changed

10 files changed

+490
-1
lines changed

cmd/dmsg/dmsg.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
dmsgsocks "github.com/skycoin/dmsg/cmd/dmsg-socks5/commands"
1616
dmsgcurl "github.com/skycoin/dmsg/cmd/dmsgcurl/commands"
1717
dmsghttp "github.com/skycoin/dmsg/cmd/dmsghttp/commands"
18+
dmsgip "github.com/skycoin/dmsg/cmd/dmsgip/commands"
1819
dmsgptycli "github.com/skycoin/dmsg/cmd/dmsgpty-cli/commands"
1920
dmsgptyhost "github.com/skycoin/dmsg/cmd/dmsgpty-host/commands"
2021
dmsgptyui "github.com/skycoin/dmsg/cmd/dmsgpty-ui/commands"
@@ -35,6 +36,7 @@ func init() {
3536
dmsgcurl.RootCmd,
3637
dmsgweb.RootCmd,
3738
dmsgsocks.RootCmd,
39+
dmsgip.RootCmd,
3840
)
3941
dmsgdisc.RootCmd.Use = "disc"
4042
dmsgserver.RootCmd.Use = "server"
@@ -45,6 +47,7 @@ func init() {
4547
dmsgptycli.RootCmd.Use = "cli"
4648
dmsgptyhost.RootCmd.Use = "host"
4749
dmsgptyui.RootCmd.Use = "ui"
50+
dmsgip.RootCmd.Use = "ip"
4851

4952
var helpflag bool
5053
RootCmd.SetUsageTemplate(help)

cmd/dmsgip/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
2+
3+
4+
```
5+
6+
7+
┌┬┐┌┬┐┌─┐┌─┐ ┬┌─┐
8+
│││││└─┐│ ┬ │├─┘
9+
─┴┘┴ ┴└─┘└─┘ ┴┴
10+
DMSG ip utility
11+
12+
Usage:
13+
dmsgip
14+
15+
Flags:
16+
-c, --dmsg-disc string dmsg discovery url default:
17+
http://dmsgd.skywire.dev
18+
-l, --loglvl string [ debug | warn | error | fatal | panic | trace | info ] (default "fatal")
19+
-s, --sk cipher.SecKey a random key is generated if unspecified
20+
(default 0000000000000000000000000000000000000000000000000000000000000000)
21+
-v, --version version for dmsgip
22+
23+
```

cmd/dmsgip/commands/dmsgip.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// Package commands cmd/dmsgcurl/commands/dmsgcurl.go
2+
package commands
3+
4+
import (
5+
"context"
6+
"fmt"
7+
"log"
8+
"net/http"
9+
"os"
10+
"path/filepath"
11+
"strings"
12+
13+
"github.com/skycoin/skywire-utilities/pkg/buildinfo"
14+
"github.com/skycoin/skywire-utilities/pkg/cipher"
15+
"github.com/skycoin/skywire-utilities/pkg/cmdutil"
16+
"github.com/skycoin/skywire-utilities/pkg/logging"
17+
"github.com/skycoin/skywire-utilities/pkg/skyenv"
18+
"github.com/spf13/cobra"
19+
20+
"github.com/skycoin/dmsg/pkg/disc"
21+
"github.com/skycoin/dmsg/pkg/dmsg"
22+
)
23+
24+
var (
25+
dmsgDisc string
26+
sk cipher.SecKey
27+
logLvl string
28+
dmsgServers []string
29+
)
30+
31+
func init() {
32+
RootCmd.Flags().StringVarP(&dmsgDisc, "dmsg-disc", "c", "", "dmsg discovery url default:\n"+skyenv.DmsgDiscAddr)
33+
RootCmd.Flags().StringVarP(&logLvl, "loglvl", "l", "fatal", "[ debug | warn | error | fatal | panic | trace | info ]\033[0m")
34+
if os.Getenv("DMSGIP_SK") != "" {
35+
sk.Set(os.Getenv("DMSGIP_SK")) //nolint
36+
}
37+
RootCmd.Flags().StringSliceVarP(&dmsgServers, "srv", "d", []string{}, "dmsg server public keys\n\r")
38+
RootCmd.Flags().VarP(&sk, "sk", "s", "a random key is generated if unspecified\n\r")
39+
}
40+
41+
// RootCmd containsa the root dmsgcurl command
42+
var RootCmd = &cobra.Command{
43+
Use: func() string {
44+
return strings.Split(filepath.Base(strings.ReplaceAll(strings.ReplaceAll(fmt.Sprintf("%v", os.Args), "[", ""), "]", "")), " ")[0]
45+
}(),
46+
Short: "DMSG ip utility",
47+
Long: `
48+
┌┬┐┌┬┐┌─┐┌─┐ ┬┌─┐
49+
│││││└─┐│ ┬ │├─┘
50+
─┴┘┴ ┴└─┘└─┘ ┴┴
51+
DMSG ip utility`,
52+
SilenceErrors: true,
53+
SilenceUsage: true,
54+
DisableSuggestions: true,
55+
DisableFlagsInUseLine: true,
56+
Version: buildinfo.Version(),
57+
PreRun: func(cmd *cobra.Command, args []string) {
58+
if dmsgDisc == "" {
59+
dmsgDisc = skyenv.DmsgDiscAddr
60+
}
61+
},
62+
RunE: func(cmd *cobra.Command, args []string) error {
63+
log := logging.MustGetLogger("dmsgip")
64+
65+
if logLvl != "" {
66+
if lvl, err := logging.LevelFromString(logLvl); err == nil {
67+
logging.SetLevel(lvl)
68+
}
69+
}
70+
71+
var srvs []cipher.PubKey
72+
for _, srv := range dmsgServers {
73+
var pk cipher.PubKey
74+
if err := pk.Set(srv); err != nil {
75+
return fmt.Errorf("failed to parse server public key: %w", err)
76+
}
77+
srvs = append(srvs, pk)
78+
}
79+
80+
ctx, cancel := cmdutil.SignalContext(context.Background(), log)
81+
defer cancel()
82+
83+
pk, err := sk.PubKey()
84+
if err != nil {
85+
pk, sk = cipher.GenerateKeyPair()
86+
}
87+
88+
dmsgC, closeDmsg, err := startDmsg(ctx, log, pk, sk)
89+
if err != nil {
90+
log.WithError(err).Error("failed to start dmsg")
91+
}
92+
defer closeDmsg()
93+
94+
ip, err := dmsgC.LookupIP(ctx, srvs)
95+
if err != nil {
96+
log.WithError(err).Error("failed to lookup IP")
97+
}
98+
99+
fmt.Printf("%v\n", ip)
100+
fmt.Print("\n")
101+
return nil
102+
},
103+
}
104+
105+
func startDmsg(ctx context.Context, log *logging.Logger, pk cipher.PubKey, sk cipher.SecKey) (dmsgC *dmsg.Client, stop func(), err error) {
106+
dmsgC = dmsg.NewClient(pk, sk, disc.NewHTTP(dmsgDisc, &http.Client{}, log), &dmsg.Config{MinSessions: dmsg.DefaultMinSessions})
107+
go dmsgC.Serve(context.Background())
108+
109+
stop = func() {
110+
err := dmsgC.Close()
111+
log.WithError(err).Debug("Disconnected from dmsg network.")
112+
fmt.Printf("\n")
113+
}
114+
log.WithField("public_key", pk.String()).WithField("dmsg_disc", dmsgDisc).
115+
Debug("Connecting to dmsg network...")
116+
117+
select {
118+
case <-ctx.Done():
119+
stop()
120+
return nil, nil, ctx.Err()
121+
122+
case <-dmsgC.Ready():
123+
log.Debug("Dmsg network ready.")
124+
return dmsgC, stop, nil
125+
}
126+
}
127+
128+
// Execute executes root CLI command.
129+
func Execute() {
130+
if err := RootCmd.Execute(); err != nil {
131+
log.Fatal("Failed to execute command: ", err)
132+
}
133+
}

cmd/dmsgip/dmsgip.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// package main cmd/dmsgcurl/dmsgcurl.go
2+
package main
3+
4+
import (
5+
cc "github.com/ivanpirog/coloredcobra"
6+
"github.com/spf13/cobra"
7+
8+
"github.com/skycoin/dmsg/cmd/dmsgip/commands"
9+
)
10+
11+
func init() {
12+
var helpflag bool
13+
commands.RootCmd.SetUsageTemplate(help)
14+
commands.RootCmd.PersistentFlags().BoolVarP(&helpflag, "help", "h", false, "help for dmsgpty-cli")
15+
commands.RootCmd.SetHelpCommand(&cobra.Command{Hidden: true})
16+
commands.RootCmd.PersistentFlags().MarkHidden("help") //nolint
17+
}
18+
19+
func main() {
20+
cc.Init(&cc.Config{
21+
RootCmd: commands.RootCmd,
22+
Headings: cc.HiBlue + cc.Bold,
23+
Commands: cc.HiBlue + cc.Bold,
24+
CmdShortDescr: cc.HiBlue,
25+
Example: cc.HiBlue + cc.Italic,
26+
ExecName: cc.HiBlue + cc.Bold,
27+
Flags: cc.HiBlue + cc.Bold,
28+
FlagsDescr: cc.HiBlue,
29+
NoExtraNewlines: true,
30+
NoBottomNewline: true,
31+
})
32+
33+
commands.Execute()
34+
}
35+
36+
const help = "Usage:\r\n" +
37+
" {{.UseLine}}{{if .HasAvailableSubCommands}}{{end}} {{if gt (len .Aliases) 0}}\r\n\r\n" +
38+
"{{.NameAndAliases}}{{end}}{{if .HasAvailableSubCommands}}\r\n\r\n" +
39+
"Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand)}}\r\n " +
40+
"{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}\r\n\r\n" +
41+
"Flags:\r\n" +
42+
"{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}\r\n\r\n" +
43+
"Global Flags:\r\n" +
44+
"{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}\r\n\r\n"

pkg/dmsg/client.go

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,78 @@ func (ce *Client) DialStream(ctx context.Context, addr Addr) (*Stream, error) {
366366
return nil, ErrCannotConnectToDelegated
367367
}
368368

369+
// LookupIP dails to dmsg servers for public IP of the client.
370+
func (ce *Client) LookupIP(ctx context.Context, servers []cipher.PubKey) (myIP net.IP, err error) {
371+
372+
cancellabelCtx, cancel := context.WithCancel(ctx)
373+
defer cancel()
374+
375+
if servers == nil {
376+
entries, err := ce.discoverServers(cancellabelCtx, true)
377+
if err != nil {
378+
return nil, err
379+
}
380+
for _, entry := range entries {
381+
servers = append(servers, entry.Static)
382+
}
383+
}
384+
385+
// Range client's delegated servers.
386+
// See if we are already connected to a delegated server.
387+
for _, srvPK := range servers {
388+
if dSes, ok := ce.clientSession(ce.porter, srvPK); ok {
389+
ip, err := dSes.LookupIP(Addr{PK: dSes.RemotePK(), Port: 1})
390+
if err != nil {
391+
ce.log.WithError(err).WithField("server_pk", srvPK).Warn("Failed to dial server for IP.")
392+
continue
393+
}
394+
395+
// If the client is test client then ignore Public IP check
396+
if ce.conf.ClientType == "test" {
397+
return ip, nil
398+
}
399+
400+
// Check if the IP is public
401+
if !netutil.IsPublicIP(ip) {
402+
return nil, errors.New("received non-public IP address from dmsg server")
403+
}
404+
return ip, nil
405+
}
406+
}
407+
408+
// Range client's delegated servers.
409+
// Attempt to connect to a delegated server.
410+
// And Close it after getting the IP.
411+
for _, srvPK := range servers {
412+
dSes, err := ce.EnsureAndObtainSession(ctx, srvPK)
413+
if err != nil {
414+
continue
415+
}
416+
ip, err := dSes.LookupIP(Addr{PK: dSes.RemotePK(), Port: 1})
417+
if err != nil {
418+
ce.log.WithError(err).WithField("server_pk", srvPK).Warn("Failed to dial server for IP.")
419+
continue
420+
}
421+
err = dSes.Close()
422+
if err != nil {
423+
ce.log.WithError(err).WithField("server_pk", srvPK).Warn("Failed to close session")
424+
}
425+
426+
// If the client is test client then ignore Public IP check
427+
if ce.conf.ClientType == "test" {
428+
return ip, nil
429+
}
430+
431+
// Check if the IP is public
432+
if !netutil.IsPublicIP(ip) {
433+
return nil, errors.New("received non-public IP address from dmsg server")
434+
}
435+
return ip, nil
436+
}
437+
438+
return nil, ErrCannotConnectToDelegated
439+
}
440+
369441
// Session obtains an established session.
370442
func (ce *Client) Session(pk cipher.PubKey) (ClientSession, bool) {
371443
return ce.clientSession(ce.porter, pk)
@@ -403,7 +475,6 @@ func (ce *Client) EnsureAndObtainSession(ctx context.Context, srvPK cipher.PubKe
403475
if err != nil {
404476
return ClientSession{}, err
405477
}
406-
407478
return ce.dialSession(ctx, srvEntry)
408479
}
409480

pkg/dmsg/client_session.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,50 @@ func (cs *ClientSession) DialStream(dst Addr) (dStr *Stream, err error) {
6969
return dStr, err
7070
}
7171

72+
// LookupIP attempts to dial a stream to the server for the IP address of the client.
73+
func (cs *ClientSession) LookupIP(dst Addr) (myIP net.IP, err error) {
74+
log := cs.log.
75+
WithField("func", "ClientSession.LookupIP").
76+
WithField("dst_addr", cs.rPK)
77+
78+
dStr, err := newInitiatingStream(cs)
79+
if err != nil {
80+
return nil, err
81+
}
82+
83+
// Close stream on failure.
84+
defer func() {
85+
if err != nil {
86+
log.WithError(err).
87+
WithField("close_error", dStr.Close()).
88+
Debug("Stream closed on failure.")
89+
}
90+
}()
91+
92+
// Prepare deadline.
93+
if err = dStr.SetDeadline(time.Now().Add(HandshakeTimeout)); err != nil {
94+
return nil, err
95+
}
96+
97+
// Do stream handshake.
98+
req, err := dStr.writeIPRequest(dst)
99+
if err != nil {
100+
return nil, err
101+
}
102+
103+
myIP, err = dStr.readIPResponse(req)
104+
if err != nil {
105+
return nil, err
106+
}
107+
108+
err = dStr.Close()
109+
if err != nil {
110+
return nil, err
111+
}
112+
113+
return myIP, err
114+
}
115+
72116
// serve accepts incoming streams from remote clients.
73117
func (cs *ClientSession) serve() error {
74118
defer func() {

0 commit comments

Comments
 (0)