Skip to content

Commit

Permalink
Feat/ip from server (#272)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
ersonp authored Jul 10, 2024
1 parent 1c2fcec commit c1a367e
Show file tree
Hide file tree
Showing 10 changed files with 490 additions and 1 deletion.
3 changes: 3 additions & 0 deletions cmd/dmsg/dmsg.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
dmsgsocks "github.com/skycoin/dmsg/cmd/dmsg-socks5/commands"
dmsgcurl "github.com/skycoin/dmsg/cmd/dmsgcurl/commands"
dmsghttp "github.com/skycoin/dmsg/cmd/dmsghttp/commands"
dmsgip "github.com/skycoin/dmsg/cmd/dmsgip/commands"
dmsgptycli "github.com/skycoin/dmsg/cmd/dmsgpty-cli/commands"
dmsgptyhost "github.com/skycoin/dmsg/cmd/dmsgpty-host/commands"
dmsgptyui "github.com/skycoin/dmsg/cmd/dmsgpty-ui/commands"
Expand All @@ -35,6 +36,7 @@ func init() {
dmsgcurl.RootCmd,
dmsgweb.RootCmd,
dmsgsocks.RootCmd,
dmsgip.RootCmd,
)
dmsgdisc.RootCmd.Use = "disc"
dmsgserver.RootCmd.Use = "server"
Expand All @@ -45,6 +47,7 @@ func init() {
dmsgptycli.RootCmd.Use = "cli"
dmsgptyhost.RootCmd.Use = "host"
dmsgptyui.RootCmd.Use = "ui"
dmsgip.RootCmd.Use = "ip"

var helpflag bool
RootCmd.SetUsageTemplate(help)
Expand Down
23 changes: 23 additions & 0 deletions cmd/dmsgip/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@



```
┌┬┐┌┬┐┌─┐┌─┐ ┬┌─┐
│││││└─┐│ ┬ │├─┘
─┴┘┴ ┴└─┘└─┘ ┴┴
DMSG ip utility
Usage:
dmsgip
Flags:
-c, --dmsg-disc string dmsg discovery url default:
http://dmsgd.skywire.dev
-l, --loglvl string [ debug | warn | error | fatal | panic | trace | info ] (default "fatal")
-s, --sk cipher.SecKey a random key is generated if unspecified
(default 0000000000000000000000000000000000000000000000000000000000000000)
-v, --version version for dmsgip
```
133 changes: 133 additions & 0 deletions cmd/dmsgip/commands/dmsgip.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Package commands cmd/dmsgcurl/commands/dmsgcurl.go
package commands

import (
"context"
"fmt"
"log"
"net/http"
"os"
"path/filepath"
"strings"

"github.com/skycoin/skywire-utilities/pkg/buildinfo"
"github.com/skycoin/skywire-utilities/pkg/cipher"
"github.com/skycoin/skywire-utilities/pkg/cmdutil"
"github.com/skycoin/skywire-utilities/pkg/logging"
"github.com/skycoin/skywire-utilities/pkg/skyenv"
"github.com/spf13/cobra"

"github.com/skycoin/dmsg/pkg/disc"
"github.com/skycoin/dmsg/pkg/dmsg"
)

var (
dmsgDisc string
sk cipher.SecKey
logLvl string
dmsgServers []string
)

func init() {
RootCmd.Flags().StringVarP(&dmsgDisc, "dmsg-disc", "c", "", "dmsg discovery url default:\n"+skyenv.DmsgDiscAddr)
RootCmd.Flags().StringVarP(&logLvl, "loglvl", "l", "fatal", "[ debug | warn | error | fatal | panic | trace | info ]\033[0m")
if os.Getenv("DMSGIP_SK") != "" {
sk.Set(os.Getenv("DMSGIP_SK")) //nolint
}
RootCmd.Flags().StringSliceVarP(&dmsgServers, "srv", "d", []string{}, "dmsg server public keys\n\r")
RootCmd.Flags().VarP(&sk, "sk", "s", "a random key is generated if unspecified\n\r")
}

// RootCmd containsa the root dmsgcurl command
var RootCmd = &cobra.Command{
Use: func() string {
return strings.Split(filepath.Base(strings.ReplaceAll(strings.ReplaceAll(fmt.Sprintf("%v", os.Args), "[", ""), "]", "")), " ")[0]
}(),
Short: "DMSG ip utility",
Long: `
┌┬┐┌┬┐┌─┐┌─┐ ┬┌─┐
│││││└─┐│ ┬ │├─┘
─┴┘┴ ┴└─┘└─┘ ┴┴
DMSG ip utility`,
SilenceErrors: true,
SilenceUsage: true,
DisableSuggestions: true,
DisableFlagsInUseLine: true,
Version: buildinfo.Version(),
PreRun: func(cmd *cobra.Command, args []string) {
if dmsgDisc == "" {
dmsgDisc = skyenv.DmsgDiscAddr
}
},
RunE: func(cmd *cobra.Command, args []string) error {
log := logging.MustGetLogger("dmsgip")

if logLvl != "" {
if lvl, err := logging.LevelFromString(logLvl); err == nil {
logging.SetLevel(lvl)
}
}

var srvs []cipher.PubKey
for _, srv := range dmsgServers {
var pk cipher.PubKey
if err := pk.Set(srv); err != nil {
return fmt.Errorf("failed to parse server public key: %w", err)
}
srvs = append(srvs, pk)
}

ctx, cancel := cmdutil.SignalContext(context.Background(), log)
defer cancel()

pk, err := sk.PubKey()
if err != nil {
pk, sk = cipher.GenerateKeyPair()
}

dmsgC, closeDmsg, err := startDmsg(ctx, log, pk, sk)
if err != nil {
log.WithError(err).Error("failed to start dmsg")
}
defer closeDmsg()

ip, err := dmsgC.LookupIP(ctx, srvs)
if err != nil {
log.WithError(err).Error("failed to lookup IP")
}

fmt.Printf("%v\n", ip)
fmt.Print("\n")
return nil
},
}

func startDmsg(ctx context.Context, log *logging.Logger, pk cipher.PubKey, sk cipher.SecKey) (dmsgC *dmsg.Client, stop func(), err error) {
dmsgC = dmsg.NewClient(pk, sk, disc.NewHTTP(dmsgDisc, &http.Client{}, log), &dmsg.Config{MinSessions: dmsg.DefaultMinSessions})
go dmsgC.Serve(context.Background())

stop = func() {
err := dmsgC.Close()
log.WithError(err).Debug("Disconnected from dmsg network.")
fmt.Printf("\n")
}
log.WithField("public_key", pk.String()).WithField("dmsg_disc", dmsgDisc).
Debug("Connecting to dmsg network...")

select {
case <-ctx.Done():
stop()
return nil, nil, ctx.Err()

case <-dmsgC.Ready():
log.Debug("Dmsg network ready.")
return dmsgC, stop, nil
}
}

// Execute executes root CLI command.
func Execute() {
if err := RootCmd.Execute(); err != nil {
log.Fatal("Failed to execute command: ", err)
}
}
44 changes: 44 additions & 0 deletions cmd/dmsgip/dmsgip.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// package main cmd/dmsgcurl/dmsgcurl.go
package main

import (
cc "github.com/ivanpirog/coloredcobra"
"github.com/spf13/cobra"

"github.com/skycoin/dmsg/cmd/dmsgip/commands"
)

func init() {
var helpflag bool
commands.RootCmd.SetUsageTemplate(help)
commands.RootCmd.PersistentFlags().BoolVarP(&helpflag, "help", "h", false, "help for dmsgpty-cli")
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,
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"
73 changes: 72 additions & 1 deletion pkg/dmsg/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,78 @@ func (ce *Client) DialStream(ctx context.Context, addr Addr) (*Stream, error) {
return nil, ErrCannotConnectToDelegated
}

// LookupIP dails to dmsg servers for public IP of the client.
func (ce *Client) LookupIP(ctx context.Context, servers []cipher.PubKey) (myIP net.IP, err error) {

cancellabelCtx, cancel := context.WithCancel(ctx)
defer cancel()

if servers == nil {
entries, err := ce.discoverServers(cancellabelCtx, true)
if err != nil {
return nil, err
}
for _, entry := range entries {
servers = append(servers, entry.Static)
}
}

// Range client's delegated servers.
// See if we are already connected to a delegated server.
for _, srvPK := range servers {
if dSes, ok := ce.clientSession(ce.porter, srvPK); ok {
ip, err := dSes.LookupIP(Addr{PK: dSes.RemotePK(), Port: 1})
if err != nil {
ce.log.WithError(err).WithField("server_pk", srvPK).Warn("Failed to dial server for IP.")
continue
}

// If the client is test client then ignore Public IP check
if ce.conf.ClientType == "test" {
return ip, nil
}

// Check if the IP is public
if !netutil.IsPublicIP(ip) {
return nil, errors.New("received non-public IP address from dmsg server")
}
return ip, nil
}
}

// Range client's delegated servers.
// Attempt to connect to a delegated server.
// And Close it after getting the IP.
for _, srvPK := range servers {
dSes, err := ce.EnsureAndObtainSession(ctx, srvPK)
if err != nil {
continue
}
ip, err := dSes.LookupIP(Addr{PK: dSes.RemotePK(), Port: 1})
if err != nil {
ce.log.WithError(err).WithField("server_pk", srvPK).Warn("Failed to dial server for IP.")
continue
}
err = dSes.Close()
if err != nil {
ce.log.WithError(err).WithField("server_pk", srvPK).Warn("Failed to close session")
}

// If the client is test client then ignore Public IP check
if ce.conf.ClientType == "test" {
return ip, nil
}

// Check if the IP is public
if !netutil.IsPublicIP(ip) {
return nil, errors.New("received non-public IP address from dmsg server")
}
return ip, nil
}

return nil, ErrCannotConnectToDelegated
}

// Session obtains an established session.
func (ce *Client) Session(pk cipher.PubKey) (ClientSession, bool) {
return ce.clientSession(ce.porter, pk)
Expand Down Expand Up @@ -403,7 +475,6 @@ func (ce *Client) EnsureAndObtainSession(ctx context.Context, srvPK cipher.PubKe
if err != nil {
return ClientSession{}, err
}

return ce.dialSession(ctx, srvEntry)
}

Expand Down
44 changes: 44 additions & 0 deletions pkg/dmsg/client_session.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,50 @@ func (cs *ClientSession) DialStream(dst Addr) (dStr *Stream, err error) {
return dStr, err
}

// LookupIP attempts to dial a stream to the server for the IP address of the client.
func (cs *ClientSession) LookupIP(dst Addr) (myIP net.IP, err error) {
log := cs.log.
WithField("func", "ClientSession.LookupIP").
WithField("dst_addr", cs.rPK)

dStr, err := newInitiatingStream(cs)
if err != nil {
return nil, err
}

// Close stream on failure.
defer func() {
if err != nil {
log.WithError(err).
WithField("close_error", dStr.Close()).
Debug("Stream closed on failure.")
}
}()

// Prepare deadline.
if err = dStr.SetDeadline(time.Now().Add(HandshakeTimeout)); err != nil {
return nil, err
}

// Do stream handshake.
req, err := dStr.writeIPRequest(dst)
if err != nil {
return nil, err
}

myIP, err = dStr.readIPResponse(req)
if err != nil {
return nil, err
}

err = dStr.Close()
if err != nil {
return nil, err
}

return myIP, err
}

// serve accepts incoming streams from remote clients.
func (cs *ClientSession) serve() error {
defer func() {
Expand Down
Loading

0 comments on commit c1a367e

Please sign in to comment.