diff --git a/config.go b/config.go new file mode 100644 index 00000000..e725c17d --- /dev/null +++ b/config.go @@ -0,0 +1,204 @@ +package sam3 + +import ( + "strconv" + + "github.com/go-i2p/go-sam-go/common" +) + +// I2PConfig manages I2P configuration options +type I2PConfig struct { + *common.I2PConfig +} + +// NewConfig creates a new I2PConfig +func NewConfig(opts ...func(*I2PConfig) error) (*I2PConfig, error) { + baseConfig, err := common.NewConfig() + if err != nil { + return nil, err + } + + config := &I2PConfig{ + I2PConfig: baseConfig, + } + + for _, opt := range opts { + if err := opt(config); err != nil { + return nil, err + } + } + + return config, nil +} + +// All the configuration method forwards +func (f *I2PConfig) SetSAMAddress(addr string) { + f.I2PConfig.SetSAMAddress(addr) +} + +func (f *I2PConfig) Sam() string { + return f.I2PConfig.Sam() +} + +func (f *I2PConfig) SAMAddress() string { + return f.I2PConfig.SAMAddress() +} + +func (f *I2PConfig) ID() string { + return f.I2PConfig.ID() +} + +func (f *I2PConfig) Print() []string { + return f.I2PConfig.Print() +} + +func (f *I2PConfig) SessionStyle() string { + return f.I2PConfig.SessionStyle() +} + +func (f *I2PConfig) MinSAM() string { + return f.I2PConfig.MinSAM() +} + +func (f *I2PConfig) MaxSAM() string { + return f.I2PConfig.MaxSAM() +} + +func (f *I2PConfig) DestinationKey() string { + return f.I2PConfig.DestinationKey() +} + +func (f *I2PConfig) SignatureType() string { + return f.I2PConfig.SignatureType() +} + +func (f *I2PConfig) ToPort() string { + return f.I2PConfig.ToPort() +} + +func (f *I2PConfig) Reduce() string { + return f.I2PConfig.Reduce() +} + +func (f *I2PConfig) Reliability() string { + return f.I2PConfig.Reliability() +} + +// Configuration option setters for all the missing Set* functions +func SetInAllowZeroHop(s string) func(*I2PConfig) error { + return func(e *I2PConfig) error { + e.InAllowZeroHop = s == "true" + return nil + } +} + +func SetOutAllowZeroHop(s string) func(*I2PConfig) error { + return func(e *I2PConfig) error { + e.OutAllowZeroHop = s == "true" + return nil + } +} + +func SetInLength(s string) func(*I2PConfig) error { + return func(e *I2PConfig) error { + if i, err := strconv.Atoi(s); err == nil { + e.InLength = i + } + return nil + } +} + +func SetOutLength(s string) func(*I2PConfig) error { + return func(e *I2PConfig) error { + if i, err := strconv.Atoi(s); err == nil { + e.OutLength = i + } + return nil + } +} + +func SetInQuantity(s string) func(*I2PConfig) error { + return func(e *I2PConfig) error { + if i, err := strconv.Atoi(s); err == nil { + e.InQuantity = i + } + return nil + } +} + +func SetOutQuantity(s string) func(*I2PConfig) error { + return func(e *I2PConfig) error { + if i, err := strconv.Atoi(s); err == nil { + e.OutQuantity = i + } + return nil + } +} + +func SetInVariance(s string) func(*I2PConfig) error { + return func(e *I2PConfig) error { + if i, err := strconv.Atoi(s); err == nil { + e.InVariance = i + } + return nil + } +} + +func SetOutVariance(s string) func(*I2PConfig) error { + return func(e *I2PConfig) error { + if i, err := strconv.Atoi(s); err == nil { + e.OutVariance = i + } + return nil + } +} + +func SetInBackupQuantity(s string) func(*I2PConfig) error { + return func(e *I2PConfig) error { + if i, err := strconv.Atoi(s); err == nil { + e.InBackupQuantity = i + } + return nil + } +} + +func SetOutBackupQuantity(s string) func(*I2PConfig) error { + return func(e *I2PConfig) error { + if i, err := strconv.Atoi(s); err == nil { + e.OutBackupQuantity = i + } + return nil + } +} + +func SetUseCompression(s string) func(*I2PConfig) error { + return func(e *I2PConfig) error { + e.UseCompression = s == "true" + return nil + } +} + +func SetReduceIdleTime(s string) func(*I2PConfig) error { + return func(e *I2PConfig) error { + if i, err := strconv.Atoi(s); err == nil { + e.ReduceIdleTime = i + } + return nil + } +} + +func SetCloseIdleTime(s string) func(*I2PConfig) error { + return func(e *I2PConfig) error { + if i, err := strconv.Atoi(s); err == nil { + e.CloseIdleTime = i + } + return nil + } +} + +func SetAccessListType(s string) func(*I2PConfig) error { + return func(e *I2PConfig) error { + e.AccessListType = s + return nil + } +} diff --git a/conn.go b/conn.go new file mode 100644 index 00000000..bee04f5f --- /dev/null +++ b/conn.go @@ -0,0 +1,51 @@ +package sam3 + +import ( + "net" + "time" +) + +// SAMConn implements net.Conn for I2P connections +type SAMConn struct { + conn net.Conn +} + +// Read reads data from the connection +func (sc *SAMConn) Read(buf []byte) (int, error) { + return sc.conn.Read(buf) +} + +// Write writes data to the connection +func (sc *SAMConn) Write(buf []byte) (int, error) { + return sc.conn.Write(buf) +} + +// Close closes the connection +func (sc *SAMConn) Close() error { + return sc.conn.Close() +} + +// LocalAddr returns the local address +func (sc *SAMConn) LocalAddr() net.Addr { + return sc.conn.LocalAddr() +} + +// RemoteAddr returns the remote address +func (sc *SAMConn) RemoteAddr() net.Addr { + return sc.conn.RemoteAddr() +} + +// SetDeadline sets read and write deadlines +func (sc *SAMConn) SetDeadline(t time.Time) error { + return sc.conn.SetDeadline(t) +} + +// SetReadDeadline sets read deadline +func (sc *SAMConn) SetReadDeadline(t time.Time) error { + return sc.conn.SetReadDeadline(t) +} + +// SetWriteDeadline sets write deadline +func (sc *SAMConn) SetWriteDeadline(t time.Time) error { + return sc.conn.SetWriteDeadline(t) +} diff --git a/datagram.go b/datagram.go new file mode 100644 index 00000000..2a8388f0 --- /dev/null +++ b/datagram.go @@ -0,0 +1,129 @@ +package sam3 + +import ( + "net" + "time" + + "github.com/go-i2p/go-sam-go/common" + "github.com/go-i2p/go-sam-go/datagram" + "github.com/go-i2p/i2pkeys" +) + +// DatagramSession implements net.PacketConn for I2P datagrams +type DatagramSession struct { + session *datagram.DatagramSession + sam *common.SAM +} + +// NewDatagramSession creates a new datagram session +func (s *SAM) NewDatagramSession(id string, keys i2pkeys.I2PKeys, options []string, udpPort int) (*DatagramSession, error) { + session, err := datagram.NewDatagramSession(s.sam, id, keys, options) + if err != nil { + return nil, err + } + + return &DatagramSession{ + session: session, + sam: s.sam, + }, nil +} + +// ReadFrom reads a datagram from the session +func (s *DatagramSession) ReadFrom(b []byte) (n int, addr net.Addr, err error) { + return s.session.ReadFrom(b) +} + +// WriteTo writes a datagram to the specified address +func (s *DatagramSession) WriteTo(b []byte, addr net.Addr) (n int, err error) { + return s.session.WriteTo(b, addr) +} + +// Close closes the datagram session +func (s *DatagramSession) Close() error { + return s.session.Close() +} + +// LocalAddr returns the local address +func (s *DatagramSession) LocalAddr() net.Addr { + return s.session.LocalAddr() +} + +// LocalI2PAddr returns the I2P destination +func (s *DatagramSession) LocalI2PAddr() i2pkeys.I2PAddr { + return s.session.Addr() +} + +// SetDeadline sets read and write deadlines +func (s *DatagramSession) SetDeadline(t time.Time) error { + return s.session.SetDeadline(t) +} + +// SetReadDeadline sets read deadline +func (s *DatagramSession) SetReadDeadline(t time.Time) error { + return s.session.SetReadDeadline(t) +} + +// SetWriteDeadline sets write deadline +func (s *DatagramSession) SetWriteDeadline(t time.Time) error { + return s.session.SetWriteDeadline(t) +} + +// Read reads from the session +func (s *DatagramSession) Read(b []byte) (n int, err error) { + return s.session.Read(b) +} + +// Write writes to the session +func (s *DatagramSession) Write(b []byte) (int, error) { + return s.session.Write(b) +} + +// Addr returns the session address +func (s *DatagramSession) Addr() net.Addr { + return s.session.LocalAddr() +} + +// B32 returns the base32 address +func (s *DatagramSession) B32() string { + return s.session.Addr().Base32() +} + +// RemoteAddr returns the remote address +func (s *DatagramSession) RemoteAddr() net.Addr { + return s.session.RemoteAddr() +} + +// Lookup performs name lookup +func (s *DatagramSession) Lookup(name string) (a net.Addr, err error) { + addr, err := s.sam.Lookup(name) + if err != nil { + return nil, err + } + return addr, nil +} + +// Accept accepts connections (not applicable for datagrams) +func (s *DatagramSession) Accept() (net.Conn, error) { + return nil, net.ErrClosed +} + +// Dial dials a connection (returns self for datagrams) +func (s *DatagramSession) Dial(net string, addr string) (*DatagramSession, error) { + return s, nil +} + +// DialI2PRemote dials to I2P remote +func (s *DatagramSession) DialI2PRemote(net string, addr net.Addr) (*DatagramSession, error) { + return s, nil +} + +// DialRemote dials to remote address +func (s *DatagramSession) DialRemote(net, addr string) (net.PacketConn, error) { + return s, nil +} + +// SetWriteBuffer sets write buffer size +func (s *DatagramSession) SetWriteBuffer(bytes int) error { + // Not implemented in underlying library + return nil +} diff --git a/emit.go b/emit.go new file mode 100644 index 00000000..c17c0654 --- /dev/null +++ b/emit.go @@ -0,0 +1,97 @@ +package sam3 + +import ( + "github.com/go-i2p/go-sam-go/common" +) + +// SAMEmit handles SAM protocol message generation +type SAMEmit struct { + I2PConfig + emit *common.SAMEmit +} + +// NewEmit creates a new SAMEmit +func NewEmit(opts ...func(*SAMEmit) error) (*SAMEmit, error) { + config, err := NewConfig() + if err != nil { + return nil, err + } + + emit := &SAMEmit{ + I2PConfig: *config, + emit: &common.SAMEmit{I2PConfig: *config.I2PConfig}, + } + + for _, opt := range opts { + if err := opt(emit); err != nil { + return nil, err + } + } + + return emit, nil +} + +// Hello generates hello message +func (e *SAMEmit) Hello() string { + return e.emit.Hello() +} + +// HelloBytes generates hello message as bytes +func (e *SAMEmit) HelloBytes() []byte { + return e.emit.HelloBytes() +} + +// Create generates session create message +func (e *SAMEmit) Create() string { + return e.emit.Create() +} + +// CreateBytes generates session create message as bytes +func (e *SAMEmit) CreateBytes() []byte { + return e.emit.CreateBytes() +} + +// Connect generates connect message +func (e *SAMEmit) Connect(dest string) string { + return e.emit.Connect(dest) +} + +// ConnectBytes generates connect message as bytes +func (e *SAMEmit) ConnectBytes(dest string) []byte { + return e.emit.ConnectBytes(dest) +} + +// Accept generates accept message +func (e *SAMEmit) Accept() string { + return e.emit.Accept() +} + +// AcceptBytes generates accept message as bytes +func (e *SAMEmit) AcceptBytes() []byte { + return e.emit.AcceptBytes() +} + +// Lookup generates lookup message +func (e *SAMEmit) Lookup(name string) string { + return e.emit.Lookup(name) +} + +// LookupBytes generates lookup message as bytes +func (e *SAMEmit) LookupBytes(name string) []byte { + return e.emit.LookupBytes(name) +} + +// GenerateDestination generates destination message +func (e *SAMEmit) GenerateDestination() string { + return e.emit.GenerateDestination() +} + +// GenerateDestinationBytes generates destination message as bytes +func (e *SAMEmit) GenerateDestinationBytes() []byte { + return e.emit.GenerateDestinationBytes() +} + +// SamOptionsString returns SAM options as string +func (e *SAMEmit) SamOptionsString() string { + return e.emit.SamOptionsString() +} diff --git a/listener.go b/listener.go new file mode 100644 index 00000000..b7d7f4d4 --- /dev/null +++ b/listener.go @@ -0,0 +1,50 @@ +package sam3 + +import ( + "net" + + "github.com/go-i2p/go-sam-go/stream" +) + +// StreamListener implements net.Listener for I2P streams +type StreamListener struct { + listener *stream.StreamListener +} + +// Accept accepts new inbound connections +func (l *StreamListener) Accept() (net.Conn, error) { + conn, err := l.listener.Accept() + if err != nil { + return nil, err + } + return &SAMConn{conn: conn}, nil +} + +// AcceptI2P accepts a new inbound I2P connection +func (l *StreamListener) AcceptI2P() (*SAMConn, error) { + conn, err := l.listener.Accept() + if err != nil { + return nil, err + } + return &SAMConn{conn: conn}, nil +} + +// Addr returns the listener's address +func (l *StreamListener) Addr() net.Addr { + return l.listener.Addr() +} + +// Close closes the listener +func (l *StreamListener) Close() error { + return l.listener.Close() +} + +// From returns the from port +func (l *StreamListener) From() string { + return "" +} + +// To returns the to port +func (l *StreamListener) To() string { + return "" +} diff --git a/primary.go b/primary.go new file mode 100644 index 00000000..2de48f61 --- /dev/null +++ b/primary.go @@ -0,0 +1,165 @@ +package sam3 + +import ( + "net" + "time" + + "github.com/go-i2p/go-sam-go/common" + "github.com/go-i2p/i2pkeys" +) + +// PrimarySession represents a primary session +type PrimarySession struct { + Timeout time.Duration + Deadline time.Time + Config SAMEmit + sam *common.SAM + keys i2pkeys.I2PKeys + id string +} + +// NewPrimarySession creates a new PrimarySession +func (sam *SAM) NewPrimarySession(id string, keys i2pkeys.I2PKeys, options []string) (*PrimarySession, error) { + return &PrimarySession{ + Config: sam.Config, + sam: sam.sam, + keys: keys, + id: id, + }, nil +} + +// NewPrimarySessionWithSignature creates a new PrimarySession with signature +func (sam *SAM) NewPrimarySessionWithSignature(id string, keys i2pkeys.I2PKeys, options []string, sigType string) (*PrimarySession, error) { + return &PrimarySession{ + Config: sam.Config, + sam: sam.sam, + keys: keys, + id: id, + }, nil +} + +// ID returns the session ID +func (ss *PrimarySession) ID() string { + return ss.id +} + +// Keys returns the session keys +func (ss *PrimarySession) Keys() i2pkeys.I2PKeys { + return ss.keys +} + +// Addr returns the I2P address +func (ss *PrimarySession) Addr() i2pkeys.I2PAddr { + return ss.keys.Addr() +} + +// Close closes the session +func (ss *PrimarySession) Close() error { + return nil +} + +// NewStreamSubSession creates a new stream sub-session +func (sam *PrimarySession) NewStreamSubSession(id string) (*StreamSession, error) { + samWrapper := &SAM{sam: sam.sam} + return samWrapper.NewStreamSession(id, sam.keys, nil) +} + +// NewStreamSubSessionWithPorts creates a new stream sub-session with ports +func (sam *PrimarySession) NewStreamSubSessionWithPorts(id, from, to string) (*StreamSession, error) { + samWrapper := &SAM{sam: sam.sam} + return samWrapper.NewStreamSessionWithSignatureAndPorts(id, from, to, sam.keys, nil, Sig_EdDSA_SHA512_Ed25519) +} + +// NewUniqueStreamSubSession creates a unique stream sub-session +func (sam *PrimarySession) NewUniqueStreamSubSession(id string) (*StreamSession, error) { + return sam.NewStreamSubSession(id + RandString()) +} + +// NewDatagramSubSession creates a new datagram sub-session +func (s *PrimarySession) NewDatagramSubSession(id string, udpPort int) (*DatagramSession, error) { + samWrapper := &SAM{sam: s.sam} + return samWrapper.NewDatagramSession(id, s.keys, nil, udpPort) +} + +// NewRawSubSession creates a new raw sub-session +func (s *PrimarySession) NewRawSubSession(id string, udpPort int) (*RawSession, error) { + samWrapper := &SAM{sam: s.sam} + return samWrapper.NewRawSession(id, s.keys, nil, udpPort) +} + +// Dial implements net.Dialer +func (sam *PrimarySession) Dial(network, addr string) (net.Conn, error) { + ss, err := sam.NewStreamSubSession("dial-" + RandString()) + if err != nil { + return nil, err + } + return ss.Dial(network, addr) +} + +// DialTCP implements x/dialer +func (sam *PrimarySession) DialTCP(network string, laddr, raddr net.Addr) (net.Conn, error) { + return sam.Dial(network, raddr.String()) +} + +// DialTCPI2P dials TCP over I2P +func (sam *PrimarySession) DialTCPI2P(network string, laddr, raddr string) (net.Conn, error) { + return sam.Dial(network, raddr) +} + +// DialUDP implements x/dialer +func (sam *PrimarySession) DialUDP(network string, laddr, raddr net.Addr) (net.PacketConn, error) { + ds, err := sam.NewDatagramSubSession("udp-"+RandString(), 0) + if err != nil { + return nil, err + } + return ds, nil +} + +// DialUDPI2P dials UDP over I2P +func (sam *PrimarySession) DialUDPI2P(network, laddr, raddr string) (*DatagramSession, error) { + return sam.NewDatagramSubSession("udp-"+RandString(), 0) +} + +// Lookup performs name lookup +func (s *PrimarySession) Lookup(name string) (a net.Addr, err error) { + addr, err := s.sam.Lookup(name) + if err != nil { + return nil, err + } + return addr, nil +} + +// Resolve resolves network address +func (sam *PrimarySession) Resolve(network, addr string) (net.Addr, error) { + return sam.Lookup(addr) +} + +// ResolveTCPAddr resolves TCP address +func (sam *PrimarySession) ResolveTCPAddr(network, dest string) (net.Addr, error) { + return sam.Lookup(dest) +} + +// ResolveUDPAddr resolves UDP address +func (sam *PrimarySession) ResolveUDPAddr(network, dest string) (net.Addr, error) { + return sam.Lookup(dest) +} + +// LocalAddr returns local address +func (ss *PrimarySession) LocalAddr() net.Addr { + return ss.keys.Addr() +} + +// From returns from port +func (ss *PrimarySession) From() string { + return "" +} + +// To returns to port +func (ss *PrimarySession) To() string { + return "" +} + +// SignatureType returns signature type +func (ss *PrimarySession) SignatureType() string { + return "" +} diff --git a/raw.go b/raw.go new file mode 100644 index 00000000..ef3c3876 --- /dev/null +++ b/raw.go @@ -0,0 +1,63 @@ +package sam3 + +import ( + "time" + + "github.com/go-i2p/go-sam-go/common" + "github.com/go-i2p/go-sam-go/raw" + "github.com/go-i2p/i2pkeys" +) + +// RawSession provides raw datagram messaging +type RawSession struct { + session *raw.RawSession + sam *common.SAM +} + +// NewRawSession creates a new raw session +func (s *SAM) NewRawSession(id string, keys i2pkeys.I2PKeys, options []string, udpPort int) (*RawSession, error) { + session, err := raw.NewRawSession(s.sam, id, keys, options) + if err != nil { + return nil, err + } + + return &RawSession{ + session: session, + sam: s.sam, + }, nil +} + +// Read reads one raw datagram +func (s *RawSession) Read(b []byte) (n int, err error) { + return s.session.Read(b) +} + +// WriteTo sends one raw datagram to the destination +func (s *RawSession) WriteTo(b []byte, addr i2pkeys.I2PAddr) (n int, err error) { + return s.session.WriteTo(b, addr) +} + +// Close closes the raw session +func (s *RawSession) Close() error { + return s.session.Close() +} + +// LocalAddr returns the local I2P destination +func (s *RawSession) LocalAddr() i2pkeys.I2PAddr { + return s.session.Addr() +} + +// SetDeadline sets read and write deadlines +func (s *RawSession) SetDeadline(t time.Time) error { + return s.session.SetDeadline(t) +} + +// SetReadDeadline sets read deadline +func (s *RawSession) SetReadDeadline(t time.Time) error { + return s.session.SetReadDeadline(t) +} + +// SetWriteDeadline sets write deadline +func (s *RawSession) SetWriteDeadline(t time.Time) error { + return s.session.SetWriteDeadline(t) +} diff --git a/resolver.go b/resolver.go new file mode 100644 index 00000000..a5bb6ff0 --- /dev/null +++ b/resolver.go @@ -0,0 +1,44 @@ +package sam3 + +import ( + "github.com/go-i2p/go-sam-go/common" + "github.com/go-i2p/i2pkeys" +) + +// SAMResolver provides name resolution functionality +type SAMResolver struct { + *SAM + resolver *common.SAMResolver +} + +// NewSAMResolver creates a new SAMResolver from existing SAM +func NewSAMResolver(parent *SAM) (*SAMResolver, error) { + resolver, err := common.NewSAMResolver(parent.sam) + if err != nil { + return nil, err + } + + return &SAMResolver{ + SAM: parent, + resolver: resolver, + }, nil +} + +// NewFullSAMResolver creates a new full SAMResolver +func NewFullSAMResolver(address string) (*SAMResolver, error) { + resolver, err := common.NewFullSAMResolver(address) + if err != nil { + return nil, err + } + + sam := &SAM{sam: resolver.SAM} + return &SAMResolver{ + SAM: sam, + resolver: resolver, + }, nil +} + +// Resolve performs a lookup +func (sam *SAMResolver) Resolve(name string) (i2pkeys.I2PAddr, error) { + return sam.resolver.Resolve(name) +} diff --git a/sam3.go b/sam3.go new file mode 100644 index 00000000..30c7ea57 --- /dev/null +++ b/sam3.go @@ -0,0 +1,134 @@ +// Package sam3 provides a compatibility layer for the go-i2p/sam3 library using go-sam-go as the backend +package sam3 + +import ( + "io" + + "github.com/go-i2p/go-sam-go/common" + "github.com/go-i2p/i2pkeys" +) + +// Constants from original sam3 +const ( + Sig_NONE = "SIGNATURE_TYPE=EdDSA_SHA512_Ed25519" + Sig_DSA_SHA1 = "SIGNATURE_TYPE=DSA_SHA1" + Sig_ECDSA_SHA256_P256 = "SIGNATURE_TYPE=ECDSA_SHA256_P256" + Sig_ECDSA_SHA384_P384 = "SIGNATURE_TYPE=ECDSA_SHA384_P384" + Sig_ECDSA_SHA512_P521 = "SIGNATURE_TYPE=ECDSA_SHA512_P521" + Sig_EdDSA_SHA512_Ed25519 = "SIGNATURE_TYPE=EdDSA_SHA512_Ed25519" +) + +// Predefined option sets (keeping your existing definitions) +var ( + Options_Humongous = []string{ + "inbound.length=3", "outbound.length=3", + "inbound.lengthVariance=1", "outbound.lengthVariance=1", + "inbound.backupQuantity=3", "outbound.backupQuantity=3", + "inbound.quantity=6", "outbound.quantity=6", + } + + Options_Large = []string{ + "inbound.length=3", "outbound.length=3", + "inbound.lengthVariance=1", "outbound.lengthVariance=1", + "inbound.backupQuantity=1", "outbound.backupQuantity=1", + "inbound.quantity=4", "outbound.quantity=4", + } + + Options_Wide = []string{ + "inbound.length=1", "outbound.length=1", + "inbound.lengthVariance=1", "outbound.lengthVariance=1", + "inbound.backupQuantity=2", "outbound.backupQuantity=2", + "inbound.quantity=3", "outbound.quantity=3", + } + + Options_Medium = []string{ + "inbound.length=3", "outbound.length=3", + "inbound.lengthVariance=1", "outbound.lengthVariance=1", + "inbound.backupQuantity=0", "outbound.backupQuantity=0", + "inbound.quantity=2", "outbound.quantity=2", + } + + Options_Default = []string{ + "inbound.length=3", "outbound.length=3", + "inbound.lengthVariance=0", "outbound.lengthVariance=0", + "inbound.backupQuantity=1", "outbound.backupQuantity=1", + "inbound.quantity=1", "outbound.quantity=1", + } + + Options_Small = []string{ + "inbound.length=3", "outbound.length=3", + "inbound.lengthVariance=1", "outbound.lengthVariance=1", + "inbound.backupQuantity=0", "outbound.backupQuantity=0", + "inbound.quantity=1", "outbound.quantity=1", + } + + Options_Warning_ZeroHop = []string{ + "inbound.length=0", "outbound.length=0", + "inbound.lengthVariance=0", "outbound.lengthVariance=0", + "inbound.backupQuantity=0", "outbound.backupQuantity=0", + "inbound.quantity=2", "outbound.quantity=2", + } +) + +// Global variables from original sam3 +var ( + PrimarySessionSwitch string = PrimarySessionString() + SAM_HOST = getEnv("sam_host", "127.0.0.1") + SAM_PORT = getEnv("sam_port", "7656") +) + +// SAM represents the main controller for I2P router's SAM bridge +type SAM struct { + Config SAMEmit + sam *common.SAM +} + +// NewSAM creates a new controller for the I2P routers SAM bridge +func NewSAM(address string) (*SAM, error) { + samInstance, err := common.NewSAM(address) + if err != nil { + return nil, err + } + + config, err := NewConfig() + if err != nil { + return nil, err + } + + emit := SAMEmit{I2PConfig: *config} + + return &SAM{ + Config: emit, + sam: samInstance, + }, nil +} + +// Close closes this sam session +func (sam *SAM) Close() error { + return sam.sam.Close() +} + +// Keys returns the keys associated with this SAM instance +func (sam *SAM) Keys() (k *i2pkeys.I2PKeys) { + return sam.sam.Keys() +} + +// NewKeys creates the I2P-equivalent of an IP address +func (sam *SAM) NewKeys(sigType ...string) (i2pkeys.I2PKeys, error) { + return sam.sam.NewKeys(sigType...) +} + +// ReadKeys reads public/private keys from an io.Reader +func (sam *SAM) ReadKeys(r io.Reader) (err error) { + return sam.sam.ReadKeys(r) +} + +// EnsureKeyfile ensures keyfile exists +func (sam *SAM) EnsureKeyfile(fname string) (keys i2pkeys.I2PKeys, err error) { + return sam.sam.EnsureKeyfile(fname) +} + +// Lookup performs a name lookup +func (sam *SAM) Lookup(name string) (i2pkeys.I2PAddr, error) { + return sam.sam.Lookup(name) +} diff --git a/stream.go b/stream.go new file mode 100644 index 00000000..c4004e46 --- /dev/null +++ b/stream.go @@ -0,0 +1,191 @@ +package sam3 + +import ( + "context" + "net" + "time" + + "github.com/go-i2p/go-sam-go/common" + "github.com/go-i2p/go-sam-go/stream" + "github.com/go-i2p/i2pkeys" +) + +// StreamSession represents a streaming session +type StreamSession struct { + Timeout time.Duration + Deadline time.Time + session *stream.StreamSession + sam *common.SAM + fromPort string + toPort string + signatureType string +} + +// NewStreamSession creates a new StreamSession +func (sam *SAM) NewStreamSession(id string, keys i2pkeys.I2PKeys, options []string) (*StreamSession, error) { + session, err := stream.NewStreamSession(sam.sam, id, keys, options) + if err != nil { + return nil, err + } + + return &StreamSession{ + session: session, + sam: sam.sam, + fromPort: "", + toPort: "", + signatureType: common.SIG_EdDSA_SHA512_Ed25519, + }, nil +} + +// NewStreamSessionWithSignature creates a new StreamSession with custom signature +func (sam *SAM) NewStreamSessionWithSignature(id string, keys i2pkeys.I2PKeys, options []string, sigType string) (*StreamSession, error) { + streamSAM := &stream.SAM{SAM: sam.sam} + session, err := streamSAM.NewStreamSessionWithSignature(id, keys, options, sigType) + if err != nil { + return nil, err + } + + return &StreamSession{ + session: session, + sam: sam.sam, + fromPort: "", + toPort: "", + signatureType: sigType, + }, nil +} + +// NewStreamSessionWithSignatureAndPorts creates a new StreamSession with signature and ports +func (sam *SAM) NewStreamSessionWithSignatureAndPorts(id, from, to string, keys i2pkeys.I2PKeys, options []string, sigType string) (*StreamSession, error) { + streamSAM := &stream.SAM{SAM: sam.sam} + session, err := streamSAM.NewStreamSessionWithPorts(id, from, to, keys, options) + if err != nil { + return nil, err + } + + return &StreamSession{ + session: session, + sam: sam.sam, + fromPort: from, + toPort: to, + signatureType: sigType, + }, nil +} + +// ID returns the local tunnel name +func (s *StreamSession) ID() string { + return s.session.ID() +} + +// Keys returns the keys associated with the session +func (s *StreamSession) Keys() i2pkeys.I2PKeys { + return s.session.Keys() +} + +// Addr returns the I2P destination address +func (s *StreamSession) Addr() i2pkeys.I2PAddr { + return s.session.Keys().Addr() +} + +// Close closes the session +func (s *StreamSession) Close() error { + return s.session.Close() +} + +// Listen creates a new stream listener +func (s *StreamSession) Listen() (*StreamListener, error) { + listener, err := s.session.Listen() + if err != nil { + return nil, err + } + + return &StreamListener{ + listener: listener, + }, nil +} + +// Dial establishes a connection to an address +func (s *StreamSession) Dial(n, addr string) (c net.Conn, err error) { + dialer := s.session.NewDialer() + return dialer.Dial(n, addr) +} + +// DialContext establishes a connection with context +func (s *StreamSession) DialContext(ctx context.Context, n, addr string) (net.Conn, error) { + dialer := s.session.NewDialer() + return dialer.DialContext(ctx, n, addr) +} + +// DialContextI2P establishes an I2P connection with context +func (s *StreamSession) DialContextI2P(ctx context.Context, n, addr string) (*SAMConn, error) { + dialer := s.session.NewDialer() + conn, err := dialer.DialContext(ctx, n, addr) + if err != nil { + return nil, err + } + return &SAMConn{conn: conn}, nil +} + +// DialI2P dials to an I2P destination +func (s *StreamSession) DialI2P(addr i2pkeys.I2PAddr) (*SAMConn, error) { + dialer := s.session.NewDialer() + conn, err := dialer.DialI2P(addr) + if err != nil { + return nil, err + } + return &SAMConn{conn: conn}, nil +} + +// Lookup performs name lookup +func (s *StreamSession) Lookup(name string) (i2pkeys.I2PAddr, error) { + return s.sam.Lookup(name) +} + +// Read reads data from the stream +func (s *StreamSession) Read(buf []byte) (int, error) { + return s.session.Read(buf) +} + +// Write sends data over the stream +func (s *StreamSession) Write(data []byte) (int, error) { + return s.session.Write(data) +} + +// LocalAddr returns the local address +func (s *StreamSession) LocalAddr() net.Addr { + return s.session.LocalAddr() +} + +// RemoteAddr returns the remote address +func (s *StreamSession) RemoteAddr() net.Addr { + return s.session.RemoteAddr() +} + +// SetDeadline sets read and write deadlines +func (s *StreamSession) SetDeadline(t time.Time) error { + return s.session.SetDeadline(t) +} + +// SetReadDeadline sets read deadline +func (s *StreamSession) SetReadDeadline(t time.Time) error { + return s.session.SetReadDeadline(t) +} + +// SetWriteDeadline sets write deadline +func (s *StreamSession) SetWriteDeadline(t time.Time) error { + return s.session.SetWriteDeadline(t) +} + +// From returns the from port +func (s *StreamSession) From() string { + return s.fromPort +} + +// To returns the to port +func (s *StreamSession) To() string { + return s.toPort +} + +// SignatureType returns the signature type +func (s *StreamSession) SignatureType() string { + return s.signatureType +} diff --git a/types.go b/types.go new file mode 100644 index 00000000..f54079e3 --- /dev/null +++ b/types.go @@ -0,0 +1,15 @@ +package sam3 + +// Options represents a map of configuration options +type Options map[string]string + +// AsList returns options as a list of strings +func (opts Options) AsList() (ls []string) { + for k, v := range opts { + ls = append(ls, k+"="+v) + } + return +} + +// Option is a functional option for SAMEmit +type Option func(*SAMEmit) error diff --git a/utils.go b/utils.go new file mode 100644 index 00000000..a65d16ce --- /dev/null +++ b/utils.go @@ -0,0 +1,122 @@ +package sam3 + +import ( + "crypto/rand" + "math/big" + "os" + "strconv" + "strings" + + "github.com/sirupsen/logrus" +) + +// getEnv gets environment variable with fallback +func getEnv(key, fallback string) string { + if value := os.Getenv(key); value != "" { + return value + } + return fallback +} + +// RandString generates a random string +func RandString() string { + const chars = "abcdefghijklmnopqrstuvwxyz" + result := make([]byte, 12) + for i := range result { + n, _ := rand.Int(rand.Reader, big.NewInt(int64(len(chars)))) + result[i] = chars[n.Int64()] + } + return string(result) +} + +// PrimarySessionString returns primary session string +func PrimarySessionString() string { + return "primary" +} + +// SAMDefaultAddr returns default SAM address +func SAMDefaultAddr(fallforward string) string { + if fallforward != "" { + return fallforward + } + return SAM_HOST + ":" + SAM_PORT +} + +// ExtractDest extracts destination from input +func ExtractDest(input string) string { + parts := strings.Fields(input) + for _, part := range parts { + if strings.HasPrefix(part, "DEST=") { + return part[5:] + } + } + return "" +} + +// ExtractPairString extracts string value from key=value pair +func ExtractPairString(input, value string) string { + prefix := value + "=" + parts := strings.Fields(input) + for _, part := range parts { + if strings.HasPrefix(part, prefix) { + return part[len(prefix):] + } + } + return "" +} + +// ExtractPairInt extracts integer value from key=value pair +func ExtractPairInt(input, value string) int { + str := ExtractPairString(input, value) + if str == "" { + return 0 + } + i, _ := strconv.Atoi(str) + return i +} + +// GenerateOptionString generates option string from slice +func GenerateOptionString(opts []string) string { + return strings.Join(opts, " ") +} + +// IgnorePortError ignores port-related errors +func IgnorePortError(err error) error { + if err != nil && strings.Contains(err.Error(), "port") { + return nil + } + return err +} + +// Logging functions +var sam3Logger *logrus.Logger + +// InitializeSAM3Logger initializes the logger +func InitializeSAM3Logger() { + sam3Logger = logrus.New() + sam3Logger.SetLevel(logrus.InfoLevel) +} + +// GetSAM3Logger returns the initialized logger +func GetSAM3Logger() *logrus.Logger { + if sam3Logger == nil { + InitializeSAM3Logger() + } + return sam3Logger +} + +// Additional utility functions that may be needed for compatibility +func ConvertOptionsToSlice(opts Options) []string { + return opts.AsList() +} + +func ConvertSliceToOptions(slice []string) Options { + opts := make(Options) + for _, opt := range slice { + parts := strings.SplitN(opt, "=", 2) + if len(parts) == 2 { + opts[parts[0]] = parts[1] + } + } + return opts +}