diff --git a/SAMConn.go b/SAMConn.go index 40ff955..c236ea1 100644 --- a/SAMConn.go +++ b/SAMConn.go @@ -7,64 +7,60 @@ import ( "github.com/go-i2p/i2pkeys" ) -/* -import ( - . "github.com/go-i2p/i2pkeys" -) -*/ +// SAMConn sets up a SAM connection. // Implements net.Conn type SAMConn struct { laddr i2pkeys.I2PAddr raddr i2pkeys.I2PAddr - conn net.Conn + net.Conn } -// Implements net.Conn +// Read implements net.Conn func (sc *SAMConn) Read(buf []byte) (int, error) { - n, err := sc.conn.Read(buf) + n, err := sc.Conn.Read(buf) return n, err } -// Implements net.Conn +// Write Implements net.Conn func (sc *SAMConn) Write(buf []byte) (int, error) { - n, err := sc.conn.Write(buf) + n, err := sc.Conn.Write(buf) return n, err } -// Implements net.Conn +// Close Implements net.Conn func (sc *SAMConn) Close() error { - return sc.conn.Close() + return sc.Conn.Close() } +// LocalAddr Implements net.Conn func (sc *SAMConn) LocalAddr() net.Addr { return sc.localAddr() } -// Implements net.Conn func (sc *SAMConn) localAddr() i2pkeys.I2PAddr { return sc.laddr } +// RemoteAddr Implements net.Conn func (sc *SAMConn) RemoteAddr() net.Addr { return sc.remoteAddr() } -// Implements net.Conn func (sc *SAMConn) remoteAddr() i2pkeys.I2PAddr { return sc.raddr } -// Implements net.Conn +// SetDeadline Implements net.Conn func (sc *SAMConn) SetDeadline(t time.Time) error { - return sc.conn.SetDeadline(t) + return sc.Conn.SetDeadline(t) } -// Implements net.Conn +// SetReadDeadline Implements net.Conn func (sc *SAMConn) SetReadDeadline(t time.Time) error { - return sc.conn.SetReadDeadline(t) + return sc.Conn.SetReadDeadline(t) } -// Implements net.Conn +// SetWriteDeadline Implements net.Conn func (sc *SAMConn) SetWriteDeadline(t time.Time) error { - return sc.conn.SetWriteDeadline(t) + return sc.Conn.SetWriteDeadline(t) } diff --git a/config.go b/config.go index ad8a1bd..7759a96 100644 --- a/config.go +++ b/config.go @@ -60,6 +60,7 @@ type I2PConfig struct { AccessList []string } +// Sam returns the SAM address in the form of "host:port" func (f *I2PConfig) Sam() string { host := "127.0.0.1" port := "7656" @@ -76,6 +77,7 @@ func (f *I2PConfig) Sam() string { return host + ":" + port } +// SetSAMAddress sets the SAM address from a string in the form of "host:port" func (f *I2PConfig) SetSAMAddress(addr string) { hp := strings.Split(addr, ":") if len(hp) == 1 { @@ -92,6 +94,7 @@ func (f *I2PConfig) SetSAMAddress(addr string) { }).Debug("SAM address set") } +// ID returns the tunnel name in the form of "ID=name" func (f *I2PConfig) ID() string { if f.TunName == "" { b := make([]byte, 12) @@ -104,6 +107,7 @@ func (f *I2PConfig) ID() string { return " ID=" + f.TunName + " " } +// Leasesetsettings returns the lease set settings in the form of "i2cp.leaseSetKey=key i2cp.leaseSetPrivateKey=key i2cp.leaseSetPrivateSigningKey=key" func (f *I2PConfig) Leasesetsettings() (string, string, string) { var r, s, t string if f.LeaseSetKey != "" { @@ -123,6 +127,7 @@ func (f *I2PConfig) Leasesetsettings() (string, string, string) { return r, s, t } +// FromPort returns the from port setting in the form of "FROM_PORT=port" func (f *I2PConfig) FromPort() string { if f.samMax() < 3.1 { log.Debug("SAM version < 3.1, FromPort not applicable") @@ -136,6 +141,7 @@ func (f *I2PConfig) FromPort() string { return "" } +// ToPort returns the to port setting in the form of "TO_PORT=port" func (f *I2PConfig) ToPort() string { if f.samMax() < 3.1 { log.Debug("SAM version < 3.1, ToPort not applicable") @@ -149,6 +155,7 @@ func (f *I2PConfig) ToPort() string { return "" } +// SessionStyle returns the session style setting in the form of "STYLE=style" func (f *I2PConfig) SessionStyle() string { if f.Style != "" { log.WithField("style", f.Style).Debug("Session style set") @@ -168,6 +175,7 @@ func (f *I2PConfig) samMax() float64 { return float64(i) } +// MinSAM returns the minimum SAM version required in major.minor form func (f *I2PConfig) MinSAM() string { if f.SamMin == "" { log.Debug("Using default MinSAM: 3.0") @@ -177,6 +185,7 @@ func (f *I2PConfig) MinSAM() string { return f.SamMin } +// MaxSAM returns the maximum SAM version required in major.minor form func (f *I2PConfig) MaxSAM() string { if f.SamMax == "" { log.Debug("Using default MaxSAM: 3.1") @@ -186,6 +195,7 @@ func (f *I2PConfig) MaxSAM() string { return f.SamMax } +// DestinationKey returns the destination key setting in the form of "DESTINATION=key" func (f *I2PConfig) DestinationKey() string { if &f.DestinationKeys != nil { log.WithField("destinationKey", f.DestinationKeys.String()).Debug("Destination key set") @@ -195,6 +205,7 @@ func (f *I2PConfig) DestinationKey() string { return " DESTINATION=TRANSIENT " } +// SignatureType returns the signature type setting in the form of "SIGNATURE_TYPE=type" func (f *I2PConfig) SignatureType() string { if f.samMax() < 3.1 { log.Debug("SAM version < 3.1, SignatureType not applicable") @@ -208,6 +219,7 @@ func (f *I2PConfig) SignatureType() string { return "" } +// EncryptLease returns the lease set encryption setting in the form of "i2cp.encryptLeaseSet=true" func (f *I2PConfig) EncryptLease() string { if f.EncryptLeaseSet == "true" { log.Debug("Lease set encryption enabled") @@ -217,6 +229,7 @@ func (f *I2PConfig) EncryptLease() string { return "" } +// Reliability returns the message reliability setting in the form of "i2cp.messageReliability=reliability" func (f *I2PConfig) Reliability() string { if f.MessageReliability != "" { log.WithField("reliability", f.MessageReliability).Debug("Message reliability set") @@ -226,6 +239,7 @@ func (f *I2PConfig) Reliability() string { return "" } +// Reduce returns the reduce idle settings in the form of "i2cp.reduceOnIdle=true i2cp.reduceIdleTime=time i2cp.reduceQuantity=quantity" func (f *I2PConfig) Reduce() string { if f.ReduceIdle == "true" { log.WithFields(logrus.Fields{ @@ -239,6 +253,7 @@ func (f *I2PConfig) Reduce() string { return "" } +// Close returns the close idle settings in the form of "i2cp.closeOnIdle=true i2cp.closeIdleTime=time" func (f *I2PConfig) Close() string { if f.CloseIdle == "true" { log.WithFields(logrus.Fields{ @@ -251,6 +266,7 @@ func (f *I2PConfig) Close() string { return "" } +// DoZero returns the zero hop settings in the form of "inbound.allowZeroHop=true outbound.allowZeroHop=true fastRecieve=true" func (f *I2PConfig) DoZero() string { r := "" if f.InAllowZeroHop == "true" { @@ -266,6 +282,7 @@ func (f *I2PConfig) DoZero() string { return r } +// Print returns the full config as a string func (f *I2PConfig) Print() []string { lsk, lspk, lspsk := f.Leasesetsettings() return []string{ @@ -292,6 +309,7 @@ func (f *I2PConfig) Print() []string { } } +// Accesslisttype returns the access list type func (f *I2PConfig) Accesslisttype() string { if f.AccessListType == "whitelist" { log.Debug("Access list type set to whitelist") @@ -307,6 +325,7 @@ func (f *I2PConfig) Accesslisttype() string { return "" } +// Accesslist returns the access list in the form of "i2cp.accessList=list" func (f *I2PConfig) Accesslist() string { if f.AccessListType != "" && len(f.AccessList) > 0 { r := "" @@ -320,6 +339,7 @@ func (f *I2PConfig) Accesslist() string { return "" } +// LeaseSetEncryptionType returns the lease set encryption type in the form of "i2cp.leaseSetEncType=type" func (f *I2PConfig) LeaseSetEncryptionType() string { if f.LeaseSetEncryption == "" { log.Debug("Using default lease set encryption type: 4,0") @@ -335,6 +355,9 @@ func (f *I2PConfig) LeaseSetEncryptionType() string { return "i2cp.leaseSetEncType=" + f.LeaseSetEncryption } +const DEFAULT_LEASESET_TYPE = "i2cp.leaseSetEncType=4" + +// NewConfig returns a new config with default values or updates them with functional arguments func NewConfig(opts ...func(*I2PConfig) error) (*I2PConfig, error) { var config I2PConfig config.SamHost = "127.0.0.1" @@ -366,6 +389,7 @@ func NewConfig(opts ...func(*I2PConfig) error) (*I2PConfig, error) { config.CloseIdle = "false" config.CloseIdleTime = "300000" config.MessageReliability = "none" + config.LeaseSetEncryption = DEFAULT_LEASESET_TYPE for _, o := range opts { if err := o(&config); err != nil { return nil, err @@ -374,10 +398,10 @@ func NewConfig(opts ...func(*I2PConfig) error) (*I2PConfig, error) { return &config, nil } -// options map +// Options a map of options type Options map[string]string -// obtain sam options as list of strings +// AsList obtain sam options as list of strings func (opts Options) AsList() (ls []string) { for k, v := range opts { ls = append(ls, fmt.Sprintf("%s=%s", k, v)) @@ -393,7 +417,7 @@ type Config struct { Keyfile string } -// create new sam connector from config with a stream session +// StreamSession create new sam connector from config with a stream session func (cfg *Config) StreamSession() (session *StreamSession, err error) { // connect var s *SAM @@ -410,7 +434,7 @@ func (cfg *Config) StreamSession() (session *StreamSession, err error) { return } -// create new sam datagram session from config +// DatagramSession create new sam datagram session from config func (cfg *Config) DatagramSession() (session *DatagramSession, err error) { // connect var s *SAM diff --git a/datagram.go b/datagram.go index 8833f36..d25effe 100644 --- a/datagram.go +++ b/datagram.go @@ -95,7 +95,7 @@ func (s *DatagramSession) B32() string { return b32 } -func (s *DatagramSession) Dial(net string, addr string) (*DatagramSession, error) { +func (s *DatagramSession) Dial(net, addr string) (*DatagramSession, error) { log.WithFields(logrus.Fields{ "net": net, "addr": addr, @@ -194,6 +194,11 @@ func (s *DatagramSession) Read(b []byte) (n int, err error) { return rint, rerr } +const ( + MAX_DATAGRAM_SIZE = 31744 // Max reliable size + RECOMMENDED_SIZE = 11264 // 11KB recommended max +) + // Sends one signed datagram to the destination specified. At the time of // writing, maximum size is 31 kilobyte, but this may change in the future. // Implements net.PacketConn. @@ -202,8 +207,14 @@ func (s *DatagramSession) WriteTo(b []byte, addr net.Addr) (n int, err error) { "addr": addr, "datagramLen": len(b), }).Debug("Writing datagram") + if len(b) > MAX_DATAGRAM_SIZE { + return 0, errors.New("datagram exceeds maximum size") + } + if len(b) > RECOMMENDED_SIZE { + log.Warning("datagram exceeds recommended size of 11KB") + } if s.DatagramOptions != nil { - return s.WriteToWithOptions(b, addr.(i2pkeys.I2PAddr)) + return s.writeToWithOptions(b, addr.(i2pkeys.I2PAddr)) } header := []byte("3.1 " + s.id + " " + addr.String() + "\n") msg := append(header, b...) @@ -223,7 +234,7 @@ type DatagramOptions struct { SendLeaseset bool } -func (s *DatagramSession) WriteToWithOptions(b []byte, addr i2pkeys.I2PAddr) (n int, err error) { +func (s *DatagramSession) writeToWithOptions(b []byte, addr i2pkeys.I2PAddr) (n int, err error) { var header bytes.Buffer header.WriteString(fmt.Sprintf("3.3 %s %s", s.id, addr.String())) diff --git a/primary.go b/primary.go index bf0cfc5..023cb5c 100644 --- a/primary.go +++ b/primary.go @@ -110,7 +110,7 @@ func (sam *PrimarySession) DialTCP(network string, laddr, raddr net.Addr) (net.C return ts.Dial(network, raddr.String()) } -func (sam *PrimarySession) DialTCPI2P(network string, laddr, raddr string) (net.Conn, error) { +func (sam *PrimarySession) DialTCPI2P(network, laddr, raddr string) (net.Conn, error) { log.WithFields(logrus.Fields{"network": network, "laddr": laddr, "raddr": raddr}).Debug("DialTCPI2P() called") ts, ok := sam.stsess[network+raddr[0:4]] var err error @@ -195,7 +195,7 @@ func (sam *SAM) NewPrimarySession(id string, keys i2pkeys.I2PKeys, options []str return sam.newPrimarySession(PrimarySessionSwitch, id, keys, options) } -func (sam *SAM) newPrimarySession(primarySessionSwitch string, id string, keys i2pkeys.I2PKeys, options []string) (*PrimarySession, error) { +func (sam *SAM) newPrimarySession(primarySessionSwitch, id string, keys i2pkeys.I2PKeys, options []string) (*PrimarySession, error) { log.WithFields(logrus.Fields{ "primarySessionSwitch": primarySessionSwitch, "id": id, diff --git a/sam3.go b/sam3.go index 5ec3439..baa392c 100644 --- a/sam3.go +++ b/sam3.go @@ -24,6 +24,7 @@ func init() { } // Used for controlling I2Ps SAMv3. +// This implements the "Control Socket" for all connections. type SAM struct { address string conn net.Conn @@ -177,9 +178,15 @@ func (sam *SAM) EnsureKeyfile(fname string) (keys i2pkeys.I2PKeys, err error) { // Creates the I2P-equivalent of an IP address, that is unique and only the one // who has the private keys can send messages from. The public keys are the I2P // desination (the address) that anyone can send messages to. + +// Add constant for recommended sig type +const ( + DEFAULT_SIG_TYPE = "SIGNATURE_TYPE=7" // EdDSA_SHA512_Ed25519 +) + func (sam *SAM) NewKeys(sigType ...string) (i2pkeys.I2PKeys, error) { log.WithField("sigType", sigType).Debug("Generating new keys") - sigtmp := "" + sigtmp := DEFAULT_SIG_TYPE if len(sigType) > 0 { sigtmp = sigType[0] } @@ -228,12 +235,12 @@ func (sam *SAM) Lookup(name string) (i2pkeys.I2PAddr, error) { // I2CP/streaminglib-options as specified. Extra arguments can be specified by // setting extra to something else than []string{}. // This sam3 instance is now a session -func (sam *SAM) newGenericSession(style, id string, keys i2pkeys.I2PKeys, options []string, extras []string) (net.Conn, error) { +func (sam *SAM) newGenericSession(style, id string, keys i2pkeys.I2PKeys, options, extras []string) (net.Conn, error) { log.WithFields(logrus.Fields{"style": style, "id": id}).Debug("Creating new generic session") return sam.newGenericSessionWithSignature(style, id, keys, Sig_NONE, options, extras) } -func (sam *SAM) newGenericSessionWithSignature(style, id string, keys i2pkeys.I2PKeys, sigType string, options []string, extras []string) (net.Conn, error) { +func (sam *SAM) newGenericSessionWithSignature(style, id string, keys i2pkeys.I2PKeys, sigType string, options, extras []string) (net.Conn, error) { log.WithFields(logrus.Fields{"style": style, "id": id, "sigType": sigType}).Debug("Creating new generic session with signature") return sam.newGenericSessionWithSignatureAndPorts(style, id, "0", "0", keys, sigType, options, extras) } @@ -243,7 +250,7 @@ func (sam *SAM) newGenericSessionWithSignature(style, id string, keys i2pkeys.I2 // I2CP/streaminglib-options as specified. Extra arguments can be specified by // setting extra to something else than []string{}. // This sam3 instance is now a session -func (sam *SAM) newGenericSessionWithSignatureAndPorts(style, id, from, to string, keys i2pkeys.I2PKeys, sigType string, options []string, extras []string) (net.Conn, error) { +func (sam *SAM) newGenericSessionWithSignatureAndPorts(style, id, from, to string, keys i2pkeys.I2PKeys, sigType string, options, extras []string) (net.Conn, error) { log.WithFields(logrus.Fields{"style": style, "id": id, "from": from, "to": to, "sigType": sigType}).Debug("Creating new generic session with signature and ports") optStr := GenerateOptionString(options) @@ -315,8 +322,18 @@ func (sam *SAM) newGenericSessionWithSignatureAndPorts(style, id, from, to strin } } -// close this sam session +// Close this sam session func (sam *SAM) Close() error { log.Debug("Closing SAM session") return sam.conn.Close() } + +// CloseNotify the socket with a QUIT message +func (sam *SAM) CloseNotify() error { + log.Debug("Quitting SAM session") + _, err := sam.conn.Write([]byte("QUIT\n")) + if err != nil { + return fmt.Errorf("close notification failed: %v", err) + } + return nil +} diff --git a/streamListener.go b/streamListener.go index be378a8..a55be6e 100644 --- a/streamListener.go +++ b/streamListener.go @@ -115,6 +115,18 @@ func (l *StreamListener) AcceptI2P() (*SAMConn, error) { if strings.HasPrefix(line, "STREAM STATUS RESULT=OK") { // we gud read destination line destline, err := rd.ReadString(10) + if err != nil { + if err == io.EOF { + return nil, errors.New("connection closed after OK") + } + return nil, errors.New("error reading destination: " + err.Error()) + } + + // Validate destination format + dest := ExtractDest(destline) + if !strings.HasPrefix(dest, "DEST=") { + return nil, errors.New("invalid destination format") + } if err == nil { dest := ExtractDest(destline) l.session.from = ExtractPairString(destline, "FROM_PORT") @@ -129,7 +141,7 @@ func (l *StreamListener) AcceptI2P() (*SAMConn, error) { return &SAMConn{ laddr: l.laddr, raddr: i2pkeys.I2PAddr(dest), - conn: s.conn, + Conn: s.conn, }, nil } else { log.WithError(err).Error("Failed to read destination line")