diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..bd801ab0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +review.md +SAMv3.md diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..8e18ca85 --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +fmt: + find . -name '*.go' -exec gofumpt -w -s -extra {} \; \ No newline at end of file diff --git a/common/SAM.go b/common/SAM.go index 26aa6c33..81831e45 100644 --- a/common/SAM.go +++ b/common/SAM.go @@ -13,49 +13,6 @@ import ( "github.com/sirupsen/logrus" ) -// Creates a new controller for the I2P routers SAM bridge. -func OldNewSAM(address string) (*SAM, error) { - log.WithField("address", address).Debug("Creating new SAM instance") - var s SAM - // TODO: clean this up by refactoring the connection setup and error handling logic - conn, err := net.Dial("tcp", address) - if err != nil { - log.WithError(err).Error("Failed to dial SAM address") - return nil, fmt.Errorf("error dialing to address '%s': %w", address, err) - } - if _, err := conn.Write(s.SAMEmit.HelloBytes()); err != nil { - log.WithError(err).Error("Failed to write hello message") - conn.Close() - return nil, fmt.Errorf("error writing to address '%s': %w", address, err) - } - buf := make([]byte, 256) - n, err := conn.Read(buf) - if err != nil { - log.WithError(err).Error("Failed to read SAM response") - conn.Close() - return nil, fmt.Errorf("error reading onto buffer: %w", err) - } - if strings.Contains(string(buf[:n]), HELLO_REPLY_OK) { - log.Debug("SAM hello successful") - s.SAMEmit.I2PConfig.SetSAMAddress(address) - s.Conn = conn - s.SAMResolver, err = NewSAMResolver(&s) - if err != nil { - log.WithError(err).Error("Failed to create SAM resolver") - return nil, fmt.Errorf("error creating resolver: %w", err) - } - return &s, nil - } else if string(buf[:n]) == HELLO_REPLY_NOVERSION { - log.Error("SAM bridge does not support SAMv3") - conn.Close() - return nil, fmt.Errorf("That SAM bridge does not support SAMv3.") - } else { - log.WithField("response", string(buf[:n])).Error("Unexpected SAM response") - conn.Close() - return nil, fmt.Errorf("%s", string(buf[:n])) - } -} - func (sam *SAM) Keys() (k *i2pkeys.I2PKeys) { // TODO: copy them? log.Debug("Retrieving SAM keys") @@ -71,6 +28,7 @@ func (sam *SAM) ReadKeys(r io.Reader) (err error) { if err == nil { log.Debug("Keys loaded successfully") sam.SAMEmit.I2PConfig.DestinationKeys = &keys + return } log.WithError(err).Error("Failed to load keys") return diff --git a/common/config.go b/common/config.go index ed3d2b43..7ed99877 100644 --- a/common/config.go +++ b/common/config.go @@ -38,25 +38,25 @@ func (f *I2PConfig) Sam() string { // SetSAMAddress sets the SAM bridge host and port from a combined address string. // If no address is provided, it sets default values for the host and port. func (f *I2PConfig) SetSAMAddress(addr string) { - // Set default values - f.SamHost = "127.0.0.1" - f.SamPort = 7656 - - // Split address into host and port components - host, port, err := net.SplitHostPort(addr) - if err != nil { - // If error occurs, assume only host is provided - f.SamHost = addr - } else { - f.SamHost = host - f.SamPort, _ = strconv.Atoi(port) + if addr == "" { + f.SamHost = "127.0.0.1" + f.SamPort = 7656 + return } - // Log the configured SAM address - log.WithFields(logrus.Fields{ - "host": f.SamHost, - "port": f.SamPort, - }).Debug("SAM address set") + host, port, err := net.SplitHostPort(addr) + if err != nil { + // Only set host if it looks valid + if net.ParseIP(addr) != nil || !strings.Contains(addr, ":") { + f.SamHost = addr + } + return + } + + f.SamHost = host + if p, err := strconv.Atoi(port); err == nil && p > 0 && p < 65536 { + f.SamPort = p + } } // ID returns the tunnel name as a formatted string. If no tunnel name is set, diff --git a/common/new.go b/common/new.go new file mode 100644 index 00000000..cdfb911d --- /dev/null +++ b/common/new.go @@ -0,0 +1,81 @@ +package common + +import ( + "fmt" + "net" + "strings" +) + +// Creates a new controller for the I2P routers SAM bridge. +func OldNewSAM(address string) (*SAM, error) { + log.WithField("address", address).Debug("Creating new SAM instance") + var s SAM + // TODO: clean this up by refactoring the connection setup and error handling logic + conn, err := net.Dial("tcp", address) + if err != nil { + log.WithError(err).Error("Failed to dial SAM address") + return nil, fmt.Errorf("error dialing to address '%s': %w", address, err) + } + if _, err := conn.Write(s.SAMEmit.HelloBytes()); err != nil { + log.WithError(err).Error("Failed to write hello message") + conn.Close() + return nil, fmt.Errorf("error writing to address '%s': %w", address, err) + } + buf := make([]byte, 256) + n, err := conn.Read(buf) + if err != nil { + log.WithError(err).Error("Failed to read SAM response") + conn.Close() + return nil, fmt.Errorf("error reading onto buffer: %w", err) + } + if strings.Contains(string(buf[:n]), HELLO_REPLY_OK) { + log.Debug("SAM hello successful") + s.SAMEmit.I2PConfig.SetSAMAddress(address) + s.Conn = conn + s.SAMResolver, err = NewSAMResolver(&s) + if err != nil { + log.WithError(err).Error("Failed to create SAM resolver") + return nil, fmt.Errorf("error creating resolver: %w", err) + } + return &s, nil + } else if string(buf[:n]) == HELLO_REPLY_NOVERSION { + log.Error("SAM bridge does not support SAMv3") + conn.Close() + return nil, fmt.Errorf("That SAM bridge does not support SAMv3.") + } else { + log.WithField("response", string(buf[:n])).Error("Unexpected SAM response") + conn.Close() + return nil, fmt.Errorf("%s", string(buf[:n])) + } +} + +func NewSAM(address string) (*SAM, error) { + logger := log.WithField("address", address) + logger.Debug("Creating new SAM instance") + + conn, err := connectToSAM(address) + if err != nil { + return nil, err + } + defer func() { + if err != nil { + conn.Close() + } + }() + + s := &SAM{ + Conn: conn, + } + + if err = sendHelloAndValidate(conn, s); err != nil { + return nil, err + } + + s.SAMEmit.I2PConfig.SetSAMAddress(address) + + if s.SAMResolver, err = NewSAMResolver(s); err != nil { + return nil, fmt.Errorf("failed to create SAM resolver: %w", err) + } + + return s, nil +} diff --git a/common/types.go b/common/types.go index c7f9e509..1afb3cde 100644 --- a/common/types.go +++ b/common/types.go @@ -1,8 +1,10 @@ package common import ( + "context" "fmt" "net" + "time" "github.com/go-i2p/i2pkeys" ) @@ -64,6 +66,11 @@ type SAM struct { SAMEmit *SAMResolver net.Conn + + // Timeout for SAM connections + Timeout time.Duration + // Context for control of lifecycle + Context context.Context } type SAMResolver struct { diff --git a/common/util.go b/common/util.go index 9ce5a96e..eab441d5 100644 --- a/common/util.go +++ b/common/util.go @@ -37,11 +37,12 @@ func SplitHostPort(hostport string) (string, string, error) { return host, port, nil } +var randSource = rand.NewSource(time.Now().UnixNano()) +var randGen = rand.New(randSource) + func RandPort() string { for { - s := rand.NewSource(time.Now().UnixNano()) - r := rand.New(s) - p := r.Intn(55534) + 10000 + p := randGen.Intn(55534) + 10000 port := strconv.Itoa(p) if l, e := net.Listen("tcp", net.JoinHostPort("localhost", port)); e != nil { continue @@ -54,6 +55,5 @@ func RandPort() string { return strconv.Itoa(l.Addr().(*net.UDPAddr).Port) } } - } }