part out sam3

This commit is contained in:
eyedeekay
2025-02-17 21:46:19 -05:00
commit 5a1b4e14d9
40 changed files with 3220 additions and 0 deletions

5
README.md Normal file
View File

@ -0,0 +1,5 @@
go-sam-go
=========
Yet another pure-Go SAMv3.3 library.
Matches the API of `go-i2p/sam3` exactly but is a ground-up rewrite.

273
common/SAM.go Normal file
View File

@ -0,0 +1,273 @@
package common
import (
"bufio"
"bytes"
"fmt"
"io"
"net"
"os"
"strings"
"github.com/go-i2p/i2pkeys"
"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")
k = sam.SAMEmit.I2PConfig.DestinationKeys
return
}
// read public/private keys from an io.Reader
func (sam *SAM) ReadKeys(r io.Reader) (err error) {
log.Debug("Reading keys from io.Reader")
var keys i2pkeys.I2PKeys
keys, err = i2pkeys.LoadKeysIncompat(r)
if err == nil {
log.Debug("Keys loaded successfully")
sam.SAMEmit.I2PConfig.DestinationKeys = &keys
}
log.WithError(err).Error("Failed to load keys")
return
}
// if keyfile fname does not exist
func (sam *SAM) EnsureKeyfile(fname string) (keys i2pkeys.I2PKeys, err error) {
log.WithError(err).Error("Failed to load keys")
if fname == "" {
// transient
keys, err = sam.NewKeys()
if err == nil {
sam.SAMEmit.I2PConfig.DestinationKeys = &keys
log.WithFields(logrus.Fields{
"keys": keys,
}).Debug("Generated new transient keys")
}
} else {
// persistent
_, err = os.Stat(fname)
if os.IsNotExist(err) {
// make the keys
keys, err = sam.NewKeys()
if err == nil {
sam.SAMEmit.I2PConfig.DestinationKeys = &keys
// save keys
var f io.WriteCloser
f, err = os.OpenFile(fname, os.O_WRONLY|os.O_CREATE, 0600)
if err == nil {
err = i2pkeys.StoreKeysIncompat(keys, f)
f.Close()
log.Debug("Generated and saved new keys")
}
}
} else if err == nil {
// we haz key file
var f *os.File
f, err = os.Open(fname)
if err == nil {
keys, err = i2pkeys.LoadKeysIncompat(f)
if err == nil {
sam.SAMEmit.I2PConfig.DestinationKeys = &keys
log.Debug("Loaded existing keys from file")
}
}
}
}
if err != nil {
log.WithError(err).Error("Failed to ensure keyfile")
}
return
}
// 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.
func (sam *SAM) NewKeys(sigType ...string) (i2pkeys.I2PKeys, error) {
log.WithField("sigType", sigType).Debug("Generating new keys")
sigtmp := ""
if len(sigType) > 0 {
sigtmp = sigType[0]
}
if _, err := sam.Conn.Write([]byte("DEST GENERATE " + sigtmp + "\n")); err != nil {
log.WithError(err).Error("Failed to write DEST GENERATE command")
return i2pkeys.I2PKeys{}, fmt.Errorf("error with writing in SAM: %w", err)
}
buf := make([]byte, 8192)
n, err := sam.Conn.Read(buf)
if err != nil {
log.WithError(err).Error("Failed to read SAM response for key generation")
return i2pkeys.I2PKeys{}, fmt.Errorf("error with reading in SAM: %w", err)
}
s := bufio.NewScanner(bytes.NewReader(buf[:n]))
s.Split(bufio.ScanWords)
var pub, priv string
for s.Scan() {
text := s.Text()
if text == "DEST" {
continue
} else if text == "REPLY" {
continue
} else if strings.HasPrefix(text, "PUB=") {
pub = text[4:]
} else if strings.HasPrefix(text, "PRIV=") {
priv = text[5:]
} else {
log.Error("Failed to parse keys from SAM response")
return i2pkeys.I2PKeys{}, fmt.Errorf("Failed to parse keys.")
}
}
log.Debug("Successfully generated new keys")
return i2pkeys.NewKeys(i2pkeys.I2PAddr(pub), priv), nil
}
// Performs a lookup, probably this order: 1) routers known addresses, cached
// addresses, 3) by asking peers in the I2P network.
func (sam *SAM) Lookup(name string) (i2pkeys.I2PAddr, error) {
log.WithField("name", name).Debug("Looking up address")
return sam.SAMResolver.Resolve(name)
}
// Creates a new session with the style of either "STREAM", "DATAGRAM" or "RAW",
// for a new I2P tunnel with name id, using the cypher keys specified, with the
// 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, 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_EdDSA_SHA512_Ed25519, extras)
}
func (sam *SAM) NewGenericSessionWithSignature(style, id string, keys i2pkeys.I2PKeys, sigType string, 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, extras)
}
// Creates a new session with the style of either "STREAM", "DATAGRAM" or "RAW",
// for a new I2P tunnel with name id, using the cypher keys specified, with the
// 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, 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 := sam.SamOptionsString()
extraStr := strings.Join(extras, " ")
conn := sam.Conn
fp := ""
tp := ""
if from != "0" {
fp = " FROM_PORT=" + from
}
if to != "0" {
tp = " TO_PORT=" + to
}
scmsg := []byte("SESSION CREATE STYLE=" + style + fp + tp + " ID=" + id + " DESTINATION=" + keys.String() + " " + optStr + extraStr + "\n")
log.WithField("message", string(scmsg)).Debug("Sending SESSION CREATE message")
for m, i := 0, 0; m != len(scmsg); i++ {
if i == 15 {
log.Error("Failed to write SESSION CREATE message after 15 attempts")
conn.Close()
return nil, fmt.Errorf("writing to SAM failed")
}
n, err := conn.Write(scmsg[m:])
if err != nil {
log.WithError(err).Error("Failed to write to SAM connection")
conn.Close()
return nil, fmt.Errorf("writing to connection failed: %w", err)
}
m += n
}
buf := make([]byte, 4096)
n, err := conn.Read(buf)
if err != nil {
log.WithError(err).Error("Failed to read SAM response")
conn.Close()
return nil, fmt.Errorf("reading from connection failed: %w", err)
}
text := string(buf[:n])
log.WithField("response", text).Debug("Received SAM response")
if strings.HasPrefix(text, SESSION_OK) {
if keys.String() != text[len(SESSION_OK):len(text)-1] {
log.Error("SAM created a tunnel with different keys than requested")
conn.Close()
return nil, fmt.Errorf("SAMv3 created a tunnel with keys other than the ones we asked it for")
}
log.Debug("Successfully created new session")
return conn, nil //&StreamSession{id, conn, keys, nil, sync.RWMutex{}, nil}, nil
} else if text == SESSION_DUPLICATE_ID {
log.Error("Duplicate tunnel name")
conn.Close()
return nil, fmt.Errorf("Duplicate tunnel name")
} else if text == SESSION_DUPLICATE_DEST {
log.Error("Duplicate destination")
conn.Close()
return nil, fmt.Errorf("Duplicate destination")
} else if text == SESSION_INVALID_KEY {
log.Error("Invalid key for SAM session")
conn.Close()
return nil, fmt.Errorf("Invalid key - SAM session")
} else if strings.HasPrefix(text, SESSION_I2P_ERROR) {
log.WithField("error", text[len(SESSION_I2P_ERROR):]).Error("I2P error")
conn.Close()
return nil, fmt.Errorf("I2P error " + text[len(SESSION_I2P_ERROR):])
} else {
log.WithField("reply", text).Error("Unable to parse SAMv3 reply")
conn.Close()
return nil, fmt.Errorf("Unable to parse SAMv3 reply: " + text)
}
}
// close this sam session
func (sam *SAM) Close() error {
log.Debug("Closing SAM session")
return sam.Conn.Close()
}

501
common/config.go Normal file
View File

@ -0,0 +1,501 @@
package common
import (
"fmt"
"math/rand"
"net"
"strconv"
"strings"
"time"
"github.com/sirupsen/logrus"
)
// Sam returns the SAM bridge address as a string in the format "host:port"
func (f *I2PConfig) Sam() string {
// Set default values
host := "127.0.0.1"
port := 7656
// Override defaults if config values are set
if f.SamHost != "" {
host = f.SamHost
}
if f.SamPort != 0 {
port = f.SamPort
}
// Log the SAM address being constructed
log.WithFields(logrus.Fields{
"host": host,
"port": port,
}).Debug("SAM address constructed")
// Return formatted SAM address
return net.JoinHostPort(host, strconv.Itoa(port))
}
// 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)
}
// Log the configured SAM address
log.WithFields(logrus.Fields{
"host": f.SamHost,
"port": f.SamPort,
}).Debug("SAM address set")
}
// ID returns the tunnel name as a formatted string. If no tunnel name is set,
// generates a random 12-character name using lowercase letters.
func (f *I2PConfig) ID() string {
generator := rand.New(rand.NewSource(time.Now().UnixNano()))
// If no tunnel name set, generate random one
if f.TunName == "" {
// Generate 12 random lowercase letters
b := make([]byte, 12)
for i := range b {
b[i] = "abcdefghijklmnopqrstuvwxyz"[generator.Intn(26)]
}
f.TunName = string(b)
// Log the generated name
log.WithField("TunName", f.TunName).Debug("Generated random tunnel name")
}
// Return formatted ID string
return fmt.Sprintf("ID=%s", f.TunName)
}
// Leasesetsettings returns the lease set configuration strings for I2P
// Returns three strings: lease set key, private key, and private signing key settings
func (f *I2PConfig) LeaseSetSettings() (string, string, string) {
// Initialize empty strings for each setting
var leaseSetKey, privateKey, privateSigningKey string
// Set lease set key if configured
if f.LeaseSetKey != "" {
leaseSetKey = fmt.Sprintf(" i2cp.leaseSetKey=%s ", f.LeaseSetKey)
}
// Set lease set private key if configured
if f.LeaseSetPrivateKey != "" {
privateKey = fmt.Sprintf(" i2cp.leaseSetPrivateKey=%s ", f.LeaseSetPrivateKey)
}
// Set lease set private signing key if configured
if f.LeaseSetPrivateSigningKey != "" {
privateSigningKey = fmt.Sprintf(" i2cp.leaseSetPrivateSigningKey=%s ", f.LeaseSetPrivateSigningKey)
}
// Log the constructed settings
log.WithFields(logrus.Fields{
"leaseSetKey": leaseSetKey,
"leaseSetPrivateKey": privateKey,
"leaseSetPrivateSigningKey": privateSigningKey,
}).Debug("Lease set settings constructed")
return leaseSetKey, privateKey, privateSigningKey
}
// FromPort returns the FROM_PORT configuration string for SAM bridges >= 3.1
// Returns an empty string if SAM version < 3.1 or if fromport is "0"
func (f *I2PConfig) FromPort() string {
// Check SAM version compatibility
if f.SamMax == "" || f.samMax() < 3.1 {
log.Debug("SAM version < 3.1, FromPort not applicable")
return ""
}
// Return formatted FROM_PORT if fromport is set
if f.Fromport != "0" {
log.WithField("fromPort", f.Fromport).Debug("FromPort set")
return fmt.Sprintf(" FROM_PORT=%s ", f.Fromport)
}
log.Debug("FromPort not set")
return ""
}
// ToPort returns the TO_PORT configuration string for SAM bridges >= 3.1
// Returns an empty string if SAM version < 3.1 or if toport is "0"
func (f *I2PConfig) ToPort() string {
// Check SAM version compatibility
if f.samMax() < 3.1 {
log.Debug("SAM version < 3.1, ToPort not applicable")
return ""
}
// Return formatted TO_PORT if toport is set
if f.Toport != "0" {
log.WithField("toPort", f.Toport).Debug("ToPort set")
return fmt.Sprintf(" TO_PORT=%s ", f.Toport)
}
log.Debug("ToPort not set")
return ""
}
// SessionStyle returns the SAM session style configuration string
// If no style is set, defaults to "STREAM"
func (f *I2PConfig) SessionStyle() string {
if f.Style != "" {
// Log custom style setting
log.WithField("style", f.Style).Debug("Session style set")
return fmt.Sprintf(" STYLE=%s ", f.Style)
}
// Log default style
log.Debug("Using default STREAM style")
return " STYLE=STREAM "
}
// samMax returns the maximum SAM version supported as a float64
// If parsing fails, returns default value 3.1
func (f *I2PConfig) samMax() float64 {
// Parse SAM max version to integer
i, err := strconv.ParseFloat(f.SamMax, 64)
if err != nil {
log.WithError(err).Warn("Failed to parse SamMax, using default 3.1")
return 3.1
}
// Log the parsed version and return
log.WithField("samMax", i).Debug("SAM max version parsed")
return i
}
// MinSAM returns the minimum SAM version supported as a string
// If no minimum version is set, returns default value "3.0"
func (f *I2PConfig) MinSAM() string {
if f.SamMin == "" {
log.Debug("Using default MinSAM: 3.0")
return "3.0"
}
log.WithField("minSAM", f.SamMin).Debug("MinSAM set")
return f.SamMin
}
// MaxSAM returns the maximum SAM version supported as a string
// If no maximum version is set, returns default value "3.1"
func (f *I2PConfig) MaxSAM() string {
if f.SamMax == "" {
log.Debug("Using default MaxSAM: 3.1")
return "3.1"
}
log.WithField("maxSAM", f.SamMax).Debug("MaxSAM set")
return f.SamMax
}
// DestinationKey returns the DESTINATION configuration string for the SAM bridge
// If destination keys are set, returns them as a string, otherwise returns "TRANSIENT"
func (f *I2PConfig) DestinationKey() string {
// Check if destination keys are set
if f.DestinationKeys != nil {
// Log the destination key being used
log.WithField("destinationKey", f.DestinationKeys.String()).Debug("Destination key set")
return fmt.Sprintf(" DESTINATION=%s ", f.DestinationKeys.String())
}
// Log and return transient destination
log.Debug("Using TRANSIENT destination")
return " DESTINATION=TRANSIENT "
}
// SignatureType returns the SIGNATURE_TYPE configuration string for SAM bridges >= 3.1
// Returns empty string if SAM version < 3.1 or if no signature type is set
func (f *I2PConfig) SignatureType() string {
// Check SAM version compatibility
if f.samMax() < 3.1 {
log.Debug("SAM version < 3.1, SignatureType not applicable")
return ""
}
// Return formatted signature type if set
if f.SigType != "" {
log.WithField("sigType", f.SigType).Debug("Signature type set")
return fmt.Sprintf(" SIGNATURE_TYPE=%s ", f.SigType)
}
log.Debug("Signature type not set")
return ""
}
// EncryptLease returns the lease set encryption configuration string
// Returns "i2cp.encryptLeaseSet=true" if encryption is enabled, empty string otherwise
func (f *I2PConfig) EncryptLease() string {
if f.EncryptLeaseSet == true {
log.Debug("Lease set encryption enabled")
return fmt.Sprintf(" i2cp.encryptLeaseSet=true ")
}
log.Debug("Lease set encryption not enabled")
return ""
}
// Reliability returns the message reliability configuration string for the SAM bridge
// If a reliability setting is specified, returns formatted i2cp.messageReliability setting
func (f *I2PConfig) Reliability() string {
if f.MessageReliability != "" {
// Log the reliability setting being used
log.WithField("reliability", f.MessageReliability).Debug("Message reliability set")
return fmt.Sprintf(" i2cp.messageReliability=%s ", f.MessageReliability)
}
// Log when reliability is not set
log.Debug("Message reliability not set")
return ""
}
// Reduce returns I2CP reduce-on-idle configuration settings as a string if enabled
func (f *I2PConfig) Reduce() string {
// If reduce idle is enabled, return formatted configuration string
if f.ReduceIdle == true {
// Log the reduce idle settings being applied
log.WithFields(logrus.Fields{
"reduceIdle": f.ReduceIdle,
"reduceIdleTime": f.ReduceIdleTime,
"reduceIdleQuantity": f.ReduceIdleQuantity,
}).Debug("Reduce idle settings applied")
// Return formatted configuration string using Sprintf
return fmt.Sprintf("i2cp.reduceOnIdle=%t"+
"i2cp.reduceIdleTime=%d"+
"i2cp.reduceQuantity=%d",
f.ReduceIdle,
f.ReduceIdleTime,
f.ReduceIdleQuantity)
}
// Log when reduce idle is not enabled
log.Debug("Reduce idle settings not applied")
return ""
}
// Close returns I2CP close-on-idle configuration settings as a string if enabled
func (f *I2PConfig) Close() string {
// If close idle is enabled, return formatted configuration string
if f.CloseIdle == true {
// Log the close idle settings being applied
log.WithFields(logrus.Fields{
"closeIdle": f.CloseIdle,
"closeIdleTime": f.CloseIdleTime,
}).Debug("Close idle settings applied")
// Return formatted configuration string using Sprintf
return fmt.Sprintf("i2cp.closeOnIdle=%t"+
"i2cp.closeIdleTime=%d",
f.CloseIdle,
f.CloseIdleTime)
}
// Log when close idle is not enabled
log.Debug("Close idle settings not applied")
return ""
}
// DoZero returns the zero hop and fast receive configuration string settings
func (f *I2PConfig) DoZero() string {
// Build settings using slices for cleaner concatenation
var settings []string
// Add inbound zero hop setting if enabled
if f.InAllowZeroHop == true {
settings = append(settings, fmt.Sprintf("inbound.allowZeroHop=%t", f.InAllowZeroHop))
}
// Add outbound zero hop setting if enabled
if f.OutAllowZeroHop == true {
settings = append(settings, fmt.Sprintf("outbound.allowZeroHop=%t", f.OutAllowZeroHop))
}
// Add fast receive setting if enabled
if f.FastRecieve == true {
settings = append(settings, fmt.Sprintf("i2cp.fastRecieve=%t", f.FastRecieve))
}
// Join all settings with spaces
result := strings.Join(settings, " ")
// Log the final settings
log.WithField("zeroHopSettings", result).Debug("Zero hop settings applied")
return result
}
func (f *I2PConfig) InboundLength() string {
return fmt.Sprintf("inbound.length=%d", f.InLength)
}
func (f *I2PConfig) OutboundLength() string {
return fmt.Sprintf("outbound.length=%d", f.OutLength)
}
func (f *I2PConfig) InboundLengthVariance() string {
return fmt.Sprintf("inbound.lengthVariance=%d", f.InVariance)
}
func (f *I2PConfig) OutboundLengthVariance() string {
return fmt.Sprintf("outbound.lengthVariance=%d", f.OutVariance)
}
func (f *I2PConfig) InboundBackupQuantity() string {
return fmt.Sprintf("inbound.backupQuantity=%d", f.InBackupQuantity)
}
func (f *I2PConfig) OutboundBackupQuantity() string {
return fmt.Sprintf("outbound.backupQuantity=%d", f.OutBackupQuantity)
}
func (f *I2PConfig) InboundQuantity() string {
return fmt.Sprintf("inbound.quantity=%d", f.InQuantity)
}
func (f *I2PConfig) OutboundQuantity() string {
return fmt.Sprintf("outbound.quantity=%d", f.OutQuantity)
}
func (f *I2PConfig) UsingCompression() string {
return fmt.Sprintf("i2cp.gzip=%t", f.UseCompression)
}
// Print returns a slice of strings containing all the I2P configuration settings
func (f *I2PConfig) Print() []string {
// Get lease set settings
lsk, lspk, lspsk := f.LeaseSetSettings()
// Build the configuration settings slice
settings := []string{
f.InboundLength(),
f.OutboundLength(),
f.InboundLengthVariance(),
f.OutboundLengthVariance(),
f.InboundBackupQuantity(),
f.OutboundBackupQuantity(),
f.InboundQuantity(),
f.OutboundQuantity(),
f.UsingCompression(),
f.DoZero(), // Zero hop settings
f.Reduce(), // Reduce idle settings
f.Close(), // Close idle settings
f.Reliability(), // Message reliability
f.EncryptLease(), // Lease encryption
lsk, lspk, lspsk, // Lease set keys
f.Accesslisttype(), // Access list type
f.Accesslist(), // Access list
f.LeaseSetEncryptionType(), // Lease set encryption type
}
return settings
}
// Accesslisttype returns the I2CP access list configuration string based on the AccessListType setting
func (f *I2PConfig) Accesslisttype() string {
switch f.AccessListType {
case "whitelist":
log.Debug("Access list type set to whitelist")
return fmt.Sprintf("i2cp.enableAccessList=true")
case "blacklist":
log.Debug("Access list type set to blacklist")
return fmt.Sprintf("i2cp.enableBlackList=true")
case "none":
log.Debug("Access list type set to none")
return ""
default:
log.Debug("Access list type not set")
return ""
}
}
// Accesslist generates the I2CP access list configuration string based on the configured access list
func (f *I2PConfig) Accesslist() string {
// Only proceed if access list type and values are set
if f.AccessListType != "" && len(f.AccessList) > 0 {
// Join access list entries with commas
accessList := strings.Join(f.AccessList, ",")
// Log the generated access list
log.WithField("accessList", accessList).Debug("Access list generated")
// Return formatted access list configuration
return fmt.Sprintf("i2cp.accessList=%s", accessList)
}
// Log when access list is not set
log.Debug("Access list not set")
return ""
}
// LeaseSetEncryptionType returns the I2CP lease set encryption type configuration string.
// If no encryption type is set, returns default value "4,0".
// Validates that all encryption types are valid integers.
func (f *I2PConfig) LeaseSetEncryptionType() string {
// Use default encryption type if none specified
if f.LeaseSetEncryption == "" {
log.Debug("Using default lease set encryption type: 4,0")
return "i2cp.leaseSetEncType=4,0"
}
// Validate each encryption type is a valid integer
for _, s := range strings.Split(f.LeaseSetEncryption, ",") {
if _, err := strconv.Atoi(s); err != nil {
log.WithField("invalidType", s).Panic("Invalid encrypted leaseSet type")
//panic("Invalid encrypted leaseSet type: " + s)
}
}
// Log and return the configured encryption type
log.WithField("leaseSetEncType", f.LeaseSetEncryption).Debug("Lease set encryption type set")
return fmt.Sprintf("i2cp.leaseSetEncType=%s", f.LeaseSetEncryption)
}
func NewConfig(opts ...func(*I2PConfig) error) (*I2PConfig, error) {
var config I2PConfig
config.SamHost = "127.0.0.1"
config.SamPort = 7656
config.SamMin = DEFAULT_SAM_MIN
config.SamMax = DEFAULT_SAM_MAX
config.TunName = ""
config.TunType = "server"
config.Style = "STREAM"
config.InLength = 3
config.OutLength = 3
config.InQuantity = 2
config.OutQuantity = 2
config.InVariance = 1
config.OutVariance = 1
config.InBackupQuantity = 3
config.OutBackupQuantity = 3
config.InAllowZeroHop = false
config.OutAllowZeroHop = false
config.EncryptLeaseSet = false
config.LeaseSetKey = ""
config.LeaseSetPrivateKey = ""
config.LeaseSetPrivateSigningKey = ""
config.FastRecieve = false
config.UseCompression = true
config.ReduceIdle = false
config.ReduceIdleTime = 15
config.ReduceIdleQuantity = 4
config.CloseIdle = false
config.CloseIdleTime = 300000
config.MessageReliability = "none"
for _, opt := range opts {
if err := opt(&config); err != nil {
return nil, err
}
}
return &config, nil
}

32
common/const.go Normal file
View File

@ -0,0 +1,32 @@
package common
const DEFAULT_SAM_MIN = "3.1"
const DEFAULT_SAM_MAX = "3.3"
const (
SESSION_OK = "SESSION STATUS RESULT=OK DESTINATION="
SESSION_DUPLICATE_ID = "SESSION STATUS RESULT=DUPLICATED_ID\n"
SESSION_DUPLICATE_DEST = "SESSION STATUS RESULT=DUPLICATED_DEST\n"
SESSION_INVALID_KEY = "SESSION STATUS RESULT=INVALID_KEY\n"
SESSION_I2P_ERROR = "SESSION STATUS RESULT=I2P_ERROR MESSAGE="
)
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"
)
const (
SAM_RESULT_OK = "RESULT=OK"
SAM_RESULT_INVALID_KEY = "RESULT=INVALID_KEY"
SAM_RESULT_KEY_NOT_FOUND = "RESULT=KEY_NOT_FOUND"
)
const (
HELLO_REPLY_OK = "HELLO REPLY RESULT=OK"
HELLO_REPLY_NOVERSION = "HELLO REPLY RESULT=NOVERSION\n"
)

436
common/emit-options.go Normal file
View File

@ -0,0 +1,436 @@
package common
import (
"fmt"
"strconv"
"strings"
"github.com/sirupsen/logrus"
)
// Option is a SAMEmit Option
type Option func(*SAMEmit) error
// SetType sets the type of the forwarder server
func SetType(s string) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if s == "STREAM" {
c.Style = s
log.WithField("style", s).Debug("Set session style")
return nil
} else if s == "DATAGRAM" {
c.Style = s
log.WithField("style", s).Debug("Set session style")
return nil
} else if s == "RAW" {
c.Style = s
log.WithField("style", s).Debug("Set session style")
return nil
}
log.WithField("style", s).Error("Invalid session style")
return fmt.Errorf("Invalid session STYLE=%s, must be STREAM, DATAGRAM, or RAW", s)
}
}
// SetSAMAddress sets the SAM address all-at-once
func SetSAMAddress(s string) func(*SAMEmit) error {
return func(c *SAMEmit) error {
sp := strings.Split(s, ":")
if len(sp) > 2 {
log.WithField("address", s).Error("Invalid SAM address")
return fmt.Errorf("Invalid address string: %s", sp)
}
if len(sp) == 2 {
var err error
c.I2PConfig.SamPort, err = strconv.Atoi(sp[1])
if err != nil {
log.WithField("port", sp[1]).Error("Invalid SAM port")
return fmt.Errorf("Invalid SAM Port %s; non-number", sp[1])
}
}
c.I2PConfig.SamHost = sp[0]
log.WithFields(logrus.Fields{
"host": c.I2PConfig.SamHost,
"port": c.I2PConfig.SamPort,
}).Debug("Set SAM address")
return nil
}
}
// SetSAMHost sets the host of the SAMEmit's SAM bridge
func SetSAMHost(s string) func(*SAMEmit) error {
return func(c *SAMEmit) error {
c.I2PConfig.SamHost = s
log.WithField("host", s).Debug("Set SAM host")
return nil
}
}
// SetSAMPort sets the port of the SAMEmit's SAM bridge using a string
func SetSAMPort(s string) func(*SAMEmit) error {
return func(c *SAMEmit) error {
port, err := strconv.Atoi(s)
if err != nil {
log.WithField("port", s).Error("Invalid SAM port: non-number")
return fmt.Errorf("Invalid SAM Port %s; non-number", s)
}
if port < 65536 && port > -1 {
c.I2PConfig.SamPort = port
log.WithField("port", s).Debug("Set SAM port")
return nil
}
log.WithField("port", port).Error("Invalid SAM port")
return fmt.Errorf("Invalid port")
}
}
// SetName sets the host of the SAMEmit's SAM bridge
func SetName(s string) func(*SAMEmit) error {
return func(c *SAMEmit) error {
c.I2PConfig.TunName = s
log.WithField("name", s).Debug("Set tunnel name")
return nil
}
}
// SetInLength sets the number of hops inbound
func SetInLength(u int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if u < 7 && u >= 0 {
c.I2PConfig.InLength = u
log.WithField("inLength", u).Debug("Set inbound tunnel length")
return nil
}
log.WithField("inLength", u).Error("Invalid inbound tunnel length")
return fmt.Errorf("Invalid inbound tunnel length")
}
}
// SetOutLength sets the number of hops outbound
func SetOutLength(u int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if u < 7 && u >= 0 {
c.I2PConfig.OutLength = u
log.WithField("outLength", u).Debug("Set outbound tunnel length")
return nil
}
log.WithField("outLength", u).Error("Invalid outbound tunnel length")
return fmt.Errorf("Invalid outbound tunnel length")
}
}
// SetInVariance sets the variance of a number of hops inbound
func SetInVariance(i int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if i < 7 && i > -7 {
c.I2PConfig.InVariance = i
log.WithField("inVariance", i).Debug("Set inbound tunnel variance")
return nil
}
log.WithField("inVariance", i).Error("Invalid inbound tunnel variance")
return fmt.Errorf("Invalid inbound tunnel length")
}
}
// SetOutVariance sets the variance of a number of hops outbound
func SetOutVariance(i int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if i < 7 && i > -7 {
c.I2PConfig.OutVariance = i
log.WithField("outVariance", i).Debug("Set outbound tunnel variance")
return nil
}
log.WithField("outVariance", i).Error("Invalid outbound tunnel variance")
return fmt.Errorf("Invalid outbound tunnel variance")
}
}
// SetInQuantity sets the inbound tunnel quantity
func SetInQuantity(u int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if u <= 16 && u > 0 {
c.I2PConfig.InQuantity = u
log.WithField("inQuantity", u).Debug("Set inbound tunnel quantity")
return nil
}
log.WithField("inQuantity", u).Error("Invalid inbound tunnel quantity")
return fmt.Errorf("Invalid inbound tunnel quantity")
}
}
// SetOutQuantity sets the outbound tunnel quantity
func SetOutQuantity(u int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if u <= 16 && u > 0 {
c.I2PConfig.OutQuantity = u
log.WithField("outQuantity", u).Debug("Set outbound tunnel quantity")
return nil
}
log.WithField("outQuantity", u).Error("Invalid outbound tunnel quantity")
return fmt.Errorf("Invalid outbound tunnel quantity")
}
}
// SetInBackups sets the inbound tunnel backups
func SetInBackups(u int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if u < 6 && u >= 0 {
c.I2PConfig.InBackupQuantity = u
log.WithField("inBackups", u).Debug("Set inbound tunnel backups")
return nil
}
log.WithField("inBackups", u).Error("Invalid inbound tunnel backup quantity")
return fmt.Errorf("Invalid inbound tunnel backup quantity")
}
}
// SetOutBackups sets the inbound tunnel backups
func SetOutBackups(u int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if u < 6 && u >= 0 {
c.I2PConfig.OutBackupQuantity = u
log.WithField("outBackups", u).Debug("Set outbound tunnel backups")
return nil
}
log.WithField("outBackups", u).Error("Invalid outbound tunnel backup quantity")
return fmt.Errorf("Invalid outbound tunnel backup quantity")
}
}
// SetEncrypt tells the router to use an encrypted leaseset
func SetEncrypt(b bool) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if b {
c.I2PConfig.EncryptLeaseSet = b
return nil
}
c.I2PConfig.EncryptLeaseSet = b
log.WithField("encrypt", b).Debug("Set lease set encryption")
return nil
}
}
// SetLeaseSetKey sets the host of the SAMEmit's SAM bridge
func SetLeaseSetKey(s string) func(*SAMEmit) error {
return func(c *SAMEmit) error {
c.I2PConfig.LeaseSetKey = s
log.WithField("leaseSetKey", s).Debug("Set lease set key")
return nil
}
}
// SetLeaseSetPrivateKey sets the host of the SAMEmit's SAM bridge
func SetLeaseSetPrivateKey(s string) func(*SAMEmit) error {
return func(c *SAMEmit) error {
c.I2PConfig.LeaseSetPrivateKey = s
log.WithField("leaseSetPrivateKey", s).Debug("Set lease set private key")
return nil
}
}
// SetLeaseSetPrivateSigningKey sets the host of the SAMEmit's SAM bridge
func SetLeaseSetPrivateSigningKey(s string) func(*SAMEmit) error {
return func(c *SAMEmit) error {
c.I2PConfig.LeaseSetPrivateSigningKey = s
log.WithField("leaseSetPrivateSigningKey", s).Debug("Set lease set private signing key")
return nil
}
}
// SetMessageReliability sets the host of the SAMEmit's SAM bridge
func SetMessageReliability(s string) func(*SAMEmit) error {
return func(c *SAMEmit) error {
c.I2PConfig.MessageReliability = s
log.WithField("messageReliability", s).Debug("Set message reliability")
return nil
}
}
// SetAllowZeroIn tells the tunnel to accept zero-hop peers
func SetAllowZeroIn(b bool) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if b {
c.I2PConfig.InAllowZeroHop = true
return nil
}
c.I2PConfig.InAllowZeroHop = false
log.WithField("allowZeroIn", b).Debug("Set allow zero-hop inbound")
return nil
}
}
// SetAllowZeroOut tells the tunnel to accept zero-hop peers
func SetAllowZeroOut(b bool) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if b {
c.I2PConfig.OutAllowZeroHop = true
return nil
}
c.I2PConfig.OutAllowZeroHop = false
log.WithField("allowZeroOut", b).Debug("Set allow zero-hop outbound")
return nil
}
}
// SetCompress tells clients to use compression
func SetCompress(b bool) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if b {
c.I2PConfig.UseCompression = true
return nil
}
c.I2PConfig.UseCompression = false
log.WithField("compress", b).Debug("Set compression")
return nil
}
}
// SetFastRecieve tells clients to use compression
func SetFastRecieve(b bool) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if b {
c.I2PConfig.FastRecieve = true
return nil
}
c.I2PConfig.FastRecieve = false
log.WithField("fastReceive", b).Debug("Set fast receive")
return nil
}
}
// SetReduceIdle tells the connection to reduce it's tunnels during extended idle time.
func SetReduceIdle(b bool) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if b {
c.I2PConfig.ReduceIdle = true
return nil
}
c.I2PConfig.ReduceIdle = false
log.WithField("reduceIdle", b).Debug("Set reduce idle")
return nil
}
}
// SetReduceIdleTime sets the time to wait before reducing tunnels to idle levels
func SetReduceIdleTime(u int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
c.I2PConfig.ReduceIdleTime = 300000
if u >= 6 {
idleTime := (u * 60) * 1000
c.I2PConfig.ReduceIdleTime = idleTime
log.WithField("reduceIdleTime", idleTime).Debug("Set reduce idle time")
return nil
}
log.WithField("minutes", u).Error("Invalid reduce idle timeout")
return fmt.Errorf("Invalid reduce idle timeout(Measured in minutes) %v", u)
}
}
// SetReduceIdleTimeMs sets the time to wait before reducing tunnels to idle levels in milliseconds
func SetReduceIdleTimeMs(u int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
c.I2PConfig.ReduceIdleTime = 300000
if u >= 300000 {
c.I2PConfig.ReduceIdleTime = u
log.WithField("reduceIdleTimeMs", u).Debug("Set reduce idle time in milliseconds")
return nil
}
log.WithField("milliseconds", u).Error("Invalid reduce idle timeout")
return fmt.Errorf("Invalid reduce idle timeout(Measured in milliseconds) %v", u)
}
}
// SetReduceIdleQuantity sets minimum number of tunnels to reduce to during idle time
func SetReduceIdleQuantity(u int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if u < 5 {
c.I2PConfig.ReduceIdleQuantity = u
log.WithField("reduceIdleQuantity", u).Debug("Set reduce idle quantity")
return nil
}
log.WithField("quantity", u).Error("Invalid reduce tunnel quantity")
return fmt.Errorf("Invalid reduce tunnel quantity")
}
}
// SetCloseIdle tells the connection to close it's tunnels during extended idle time.
func SetCloseIdle(b bool) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if b {
c.I2PConfig.CloseIdle = true
return nil
}
c.I2PConfig.CloseIdle = false
return nil
}
}
// SetCloseIdleTime sets the time to wait before closing tunnels to idle levels
func SetCloseIdleTime(u int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
c.I2PConfig.CloseIdleTime = 300000
if u >= 6 {
idleTime := (u * 60) * 1000
c.I2PConfig.CloseIdleTime = idleTime
log.WithFields(logrus.Fields{
"minutes": u,
"milliseconds": idleTime,
}).Debug("Set close idle time")
return nil
}
log.WithField("minutes", u).Error("Invalid close idle timeout")
return fmt.Errorf("Invalid close idle timeout(Measured in minutes) %v", u)
}
}
// SetCloseIdleTimeMs sets the time to wait before closing tunnels to idle levels in milliseconds
func SetCloseIdleTimeMs(u int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
c.I2PConfig.CloseIdleTime = 300000
if u >= 300000 {
c.I2PConfig.CloseIdleTime = u
log.WithField("closeIdleTimeMs", u).Debug("Set close idle time in milliseconds")
return nil
}
return fmt.Errorf("Invalid close idle timeout(Measured in milliseconds) %v", u)
}
}
// SetAccessListType tells the system to treat the AccessList as a whitelist
func SetAccessListType(s string) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if s == "whitelist" {
c.I2PConfig.AccessListType = "whitelist"
log.Debug("Set access list type to whitelist")
return nil
} else if s == "blacklist" {
c.I2PConfig.AccessListType = "blacklist"
log.Debug("Set access list type to blacklist")
return nil
} else if s == "none" {
c.I2PConfig.AccessListType = ""
log.Debug("Set access list type to none")
return nil
} else if s == "" {
c.I2PConfig.AccessListType = ""
log.Debug("Set access list type to none")
return nil
}
return fmt.Errorf("Invalid Access list type(whitelist, blacklist, none)")
}
}
// SetAccessList tells the system to treat the AccessList as a whitelist
func SetAccessList(s []string) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if len(s) > 0 {
for _, a := range s {
c.I2PConfig.AccessList = append(c.I2PConfig.AccessList, a)
}
log.WithField("accessList", s).Debug("Set access list")
return nil
}
log.Debug("No access list set (empty list provided)")
return nil
}
}

106
common/emitter.go Normal file
View File

@ -0,0 +1,106 @@
package common
import (
"fmt"
"strings"
)
func (e *SAMEmit) SamOptionsString() string {
optStr := strings.Join(e.I2PConfig.Print(), " ")
log.WithField("optStr", optStr).Debug("Generated option string")
return optStr
}
func (e *SAMEmit) Hello() string {
hello := fmt.Sprintf("HELLO VERSION MIN=%s MAX=%s \n", e.I2PConfig.MinSAM(), e.I2PConfig.MaxSAM())
log.WithField("hello", hello).Debug("Generated HELLO command")
return hello
}
func (e *SAMEmit) HelloBytes() []byte {
return []byte(e.Hello())
}
func (e *SAMEmit) GenerateDestination() string {
dest := fmt.Sprintf("DEST GENERATE %s \n", e.I2PConfig.SignatureType())
log.WithField("destination", dest).Debug("Generated DEST GENERATE command")
return dest
}
func (e *SAMEmit) GenerateDestinationBytes() []byte {
return []byte(e.GenerateDestination())
}
func (e *SAMEmit) Lookup(name string) string {
lookup := fmt.Sprintf("NAMING LOOKUP NAME=%s \n", name)
log.WithField("lookup", lookup).Debug("Generated NAMING LOOKUP command")
return lookup
}
func (e *SAMEmit) LookupBytes(name string) []byte {
return []byte(e.Lookup(name))
}
func (e *SAMEmit) Create() string {
create := fmt.Sprintf(
// //1 2 3 4 5 6 7
"SESSION CREATE %s%s%s%s%s%s%s \n",
e.I2PConfig.SessionStyle(), //1
e.I2PConfig.FromPort(), //2
e.I2PConfig.ToPort(), //3
e.I2PConfig.ID(), //4
e.I2PConfig.DestinationKey(), //5
e.I2PConfig.SignatureType(), //6
e.SamOptionsString(), //7
)
log.WithField("create", create).Debug("Generated SESSION CREATE command")
return create
}
func (e *SAMEmit) CreateBytes() []byte {
fmt.Println("sam command: " + e.Create())
return []byte(e.Create())
}
func (e *SAMEmit) Connect(dest string) string {
connect := fmt.Sprintf(
"STREAM CONNECT ID=%s %s %s DESTINATION=%s \n",
e.I2PConfig.ID(),
e.I2PConfig.FromPort(),
e.I2PConfig.ToPort(),
dest,
)
log.WithField("connect", connect).Debug("Generated STREAM CONNECT command")
return connect
}
func (e *SAMEmit) ConnectBytes(dest string) []byte {
return []byte(e.Connect(dest))
}
func (e *SAMEmit) Accept() string {
accept := fmt.Sprintf(
"STREAM ACCEPT ID=%s %s %s",
e.I2PConfig.ID(),
e.I2PConfig.FromPort(),
e.I2PConfig.ToPort(),
)
log.WithField("accept", accept).Debug("Generated STREAM ACCEPT command")
return accept
}
func (e *SAMEmit) AcceptBytes() []byte {
return []byte(e.Accept())
}
func NewEmit(opts ...func(*SAMEmit) error) (*SAMEmit, error) {
var emit SAMEmit
for _, o := range opts {
if err := o(&emit); err != nil {
log.WithError(err).Error("Failed to apply option")
return nil, err
}
}
log.Debug("New SAMEmit instance created")
return &emit, nil
}

10
common/log.go Normal file
View File

@ -0,0 +1,10 @@
package common
import logger "github.com/go-i2p/go-sam-go/log"
var log = logger.GetSAM3Logger()
func init() {
logger.InitializeSAM3Logger()
log = logger.GetSAM3Logger()
}

82
common/resolver.go Normal file
View File

@ -0,0 +1,82 @@
package common
import (
"bufio"
"bytes"
"errors"
"strings"
"github.com/go-i2p/i2pkeys"
)
func NewSAMResolver(parent *SAM) (*SAMResolver, error) {
log.Debug("Creating new SAMResolver from existing SAM instance")
var s SAMResolver
s.SAM = parent
return &s, nil
}
func NewFullSAMResolver(address string) (*SAMResolver, error) {
log.WithField("address", address).Debug("Creating new full SAMResolver")
var s SAMResolver
var err error
s.SAM, err = NewSAM(address)
if err != nil {
log.WithError(err).Error("Failed to create new SAM instance")
return nil, err
}
return &s, nil
}
// Performs a lookup, probably this order: 1) routers known addresses, cached
// addresses, 3) by asking peers in the I2P network.
func (sam *SAMResolver) Resolve(name string) (i2pkeys.I2PAddr, error) {
log.WithField("name", name).Debug("Resolving name")
if _, err := sam.Conn.Write([]byte("NAMING LOOKUP NAME=" + name + "\r\n")); err != nil {
log.WithError(err).Error("Failed to write to SAM connection")
sam.Close()
return i2pkeys.I2PAddr(""), err
}
buf := make([]byte, 4096)
n, err := sam.Conn.Read(buf)
if err != nil {
log.WithError(err).Error("Failed to read from SAM connection")
sam.Close()
return i2pkeys.I2PAddr(""), err
}
if n <= 13 || !strings.HasPrefix(string(buf[:n]), "NAMING REPLY ") {
log.Error("Failed to parse SAM response")
return i2pkeys.I2PAddr(""), errors.New("Failed to parse.")
}
s := bufio.NewScanner(bytes.NewReader(buf[13:n]))
s.Split(bufio.ScanWords)
errStr := ""
for s.Scan() {
text := s.Text()
log.WithField("text", text).Debug("Parsing SAM response token")
//log.Println("SAM3", text)
if text == SAM_RESULT_OK {
continue
} else if text == SAM_RESULT_INVALID_KEY {
errStr += "Invalid key - resolver."
log.Error("Invalid key in resolver")
} else if text == SAM_RESULT_KEY_NOT_FOUND {
errStr += "Unable to resolve " + name
log.WithField("name", name).Error("Unable to resolve name")
} else if text == "NAME="+name {
continue
} else if strings.HasPrefix(text, "VALUE=") {
addr := i2pkeys.I2PAddr(text[6:])
log.WithField("addr", addr).Debug("Name resolved successfully")
return i2pkeys.I2PAddr(text[6:]), nil
} else if strings.HasPrefix(text, "MESSAGE=") {
errStr += " " + text[8:]
log.WithField("message", text[8:]).Warn("Received message from SAM")
} else {
continue
}
}
return i2pkeys.I2PAddr(""), errors.New(errStr)
}

69
common/sam3.go Normal file
View File

@ -0,0 +1,69 @@
package common
import (
"fmt"
"net"
"strings"
)
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
}
func connectToSAM(address string) (net.Conn, error) {
conn, err := net.Dial("tcp", address)
if err != nil {
return nil, fmt.Errorf("failed to connect to SAM bridge at %s: %w", address, err)
}
return conn, nil
}
func sendHelloAndValidate(conn net.Conn, s *SAM) error {
if _, err := conn.Write(s.SAMEmit.HelloBytes()); err != nil {
return fmt.Errorf("failed to send hello message: %w", err)
}
buf := make([]byte, 256)
n, err := conn.Read(buf)
if err != nil {
return fmt.Errorf("failed to read SAM response: %w", err)
}
response := string(buf[:n])
switch {
case strings.Contains(response, HELLO_REPLY_OK):
log.Debug("SAM hello successful")
return nil
case response == HELLO_REPLY_NOVERSION:
return fmt.Errorf("SAM bridge does not support SAMv3")
default:
return fmt.Errorf("unexpected SAM response: %s", response)
}
}

82
common/types.go Normal file
View File

@ -0,0 +1,82 @@
package common
import (
"fmt"
"net"
"github.com/go-i2p/i2pkeys"
)
// I2PConfig is a struct which manages I2P configuration options.
type I2PConfig struct {
SamHost string
SamPort int
TunName string
SamMin string
SamMax string
Fromport string
Toport string
Style string
TunType string
DestinationKeys *i2pkeys.I2PKeys
SigType string
EncryptLeaseSet bool
LeaseSetKey string
LeaseSetPrivateKey string
LeaseSetPrivateSigningKey string
LeaseSetKeys i2pkeys.I2PKeys
InAllowZeroHop bool
OutAllowZeroHop bool
InLength int
OutLength int
InQuantity int
OutQuantity int
InVariance int
OutVariance int
InBackupQuantity int
OutBackupQuantity int
FastRecieve bool
UseCompression bool
MessageReliability string
CloseIdle bool
CloseIdleTime int
ReduceIdle bool
ReduceIdleTime int
ReduceIdleQuantity int
LeaseSetEncryption string
//Streaming Library options
AccessListType string
AccessList []string
}
type SAMEmit struct {
I2PConfig
}
// Used for controlling I2Ps SAMv3.
type SAM struct {
SAMEmit
*SAMResolver
net.Conn
}
type SAMResolver struct {
*SAM
}
// options map
type Options map[string]string
// 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))
}
return
}

59
common/util.go Normal file
View File

@ -0,0 +1,59 @@
package common
import (
"math/rand"
"net"
"strconv"
"strings"
"time"
"github.com/sirupsen/logrus"
)
func IgnorePortError(err error) error {
if err == nil {
return nil
}
if strings.Contains(err.Error(), "missing port in address") {
log.Debug("Ignoring 'missing port in address' error")
err = nil
}
return err
}
func SplitHostPort(hostport string) (string, string, error) {
host, port, err := net.SplitHostPort(hostport)
if err != nil {
if IgnorePortError(err) == nil {
log.WithField("host", hostport).Debug("Using full string as host, port set to 0")
host = hostport
port = "0"
}
}
log.WithFields(logrus.Fields{
"host": host,
"port": port,
}).Debug("Split host and port")
return host, port, nil
}
func RandPort() string {
for {
s := rand.NewSource(time.Now().UnixNano())
r := rand.New(s)
p := r.Intn(55534) + 10000
port := strconv.Itoa(p)
if l, e := net.Listen("tcp", net.JoinHostPort("localhost", port)); e != nil {
continue
} else {
defer l.Close()
if l, e := net.Listen("udp", net.JoinHostPort("localhost", port)); e != nil {
continue
} else {
defer l.Close()
return strconv.Itoa(l.Addr().(*net.UDPAddr).Port)
}
}
}
}

78
datagram/datagram.go Normal file
View File

@ -0,0 +1,78 @@
package datagram
import (
"errors"
"net"
"strconv"
"github.com/go-i2p/go-sam-go/common"
"github.com/go-i2p/i2pkeys"
"github.com/sirupsen/logrus"
)
// Creates a new datagram session. udpPort is the UDP port SAM is listening on,
// and if you set it to zero, it will use SAMs standard UDP port.
func (s *SAM) NewDatagramSession(id string, keys i2pkeys.I2PKeys, options []string, udpPort int) (*DatagramSession, error) {
log.WithFields(logrus.Fields{
"id": id,
"udpPort": udpPort,
}).Debug("Creating new DatagramSession")
if udpPort > 65335 || udpPort < 0 {
log.WithField("udpPort", udpPort).Error("Invalid UDP port")
return nil, errors.New("udpPort needs to be in the intervall 0-65335")
}
if udpPort == 0 {
udpPort = 7655
log.Debug("Using default UDP port 7655")
}
lhost, _, err := common.SplitHostPort(s.LocalAddr().String())
if err != nil {
log.WithError(err).Error("Failed to split local host port")
s.Close()
return nil, err
}
lUDPAddr, err := net.ResolveUDPAddr("udp4", lhost+":0")
if err != nil {
log.WithError(err).Error("Failed to resolve local UDP address")
return nil, err
}
udpconn, err := net.ListenUDP("udp4", lUDPAddr)
if err != nil {
log.WithError(err).Error("Failed to listen on UDP")
return nil, err
}
rhost, _, err := common.SplitHostPort(s.RemoteAddr().String())
if err != nil {
log.WithError(err).Error("Failed to split remote host port")
s.Close()
return nil, err
}
rUDPAddr, err := net.ResolveUDPAddr("udp4", rhost+":"+strconv.Itoa(udpPort))
if err != nil {
log.WithError(err).Error("Failed to resolve remote UDP address")
return nil, err
}
_, lport, err := net.SplitHostPort(udpconn.LocalAddr().String())
if err != nil {
log.WithError(err).Error("Failed to get local port")
s.Close()
return nil, err
}
conn, err := s.NewGenericSession("DATAGRAM", id, keys, []string{" PORT=" + lport})
if err != nil {
log.WithError(err).Error("Failed to create generic session")
return nil, err
}
log.WithField("id", id).Info("DatagramSession created successfully")
datagramSession := &DatagramSession{
SAM: s,
UDPConn: udpconn,
SAMUDPAddress: rUDPAddr,
RemoteI2PAddr: nil,
}
datagramSession.Conn = conn
return datagramSession, nil
//return &DatagramSession{s.address, id, conn, udpconn, keys, rUDPAddr, nil}, nil
}

10
datagram/log.go Normal file
View File

@ -0,0 +1,10 @@
package datagram
import logger "github.com/go-i2p/go-sam-go/log"
var log = logger.GetSAM3Logger()
func init() {
logger.InitializeSAM3Logger()
log = logger.GetSAM3Logger()
}

209
datagram/session.go Normal file
View File

@ -0,0 +1,209 @@
package datagram
import (
"bytes"
"errors"
"net"
"time"
"github.com/go-i2p/go-sam-go/common"
"github.com/go-i2p/i2pkeys"
"github.com/sirupsen/logrus"
)
func (s *DatagramSession) B32() string {
b32 := s.DestinationKeys.Addr().Base32()
log.WithField("b32", b32).Debug("Generated B32 address")
return b32
}
func (s *DatagramSession) Dial(net string, addr string) (*DatagramSession, error) {
log.WithFields(logrus.Fields{
"net": net,
"addr": addr,
}).Debug("Dialing address")
netaddr, err := s.Lookup(addr)
if err != nil {
log.WithError(err).Error("Lookup failed")
return nil, err
}
return s.DialI2PRemote(net, netaddr)
}
func (s *DatagramSession) DialRemote(net, addr string) (net.PacketConn, error) {
log.WithFields(logrus.Fields{
"net": net,
"addr": addr,
}).Debug("Dialing remote address")
netaddr, err := s.Lookup(addr)
if err != nil {
log.WithError(err).Error("Lookup failed")
return nil, err
}
return s.DialI2PRemote(net, netaddr)
}
func (s *DatagramSession) DialI2PRemote(net string, addr net.Addr) (*DatagramSession, error) {
log.WithFields(logrus.Fields{
"net": net,
"addr": addr,
}).Debug("Dialing I2P remote address")
switch addr.(type) {
case *i2pkeys.I2PAddr:
s.RemoteI2PAddr = addr.(*i2pkeys.I2PAddr)
case i2pkeys.I2PAddr:
i2paddr := addr.(i2pkeys.I2PAddr)
s.RemoteI2PAddr = &i2paddr
}
return s, nil
}
func (s *DatagramSession) RemoteAddr() net.Addr {
log.WithField("remoteAddr", s.RemoteI2PAddr).Debug("Getting remote address")
return s.RemoteI2PAddr
}
// Reads one datagram sent to the destination of the DatagramSession. Returns
// the number of bytes read, from what address it was sent, or an error.
// implements net.PacketConn
func (s *DatagramSession) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
log.Debug("Reading datagram")
// extra bytes to read the remote address of incomming datagram
buf := make([]byte, len(b)+4096)
for {
// very basic protection: only accept incomming UDP messages from the IP of the SAM bridge
var saddr *net.UDPAddr
n, saddr, err = s.UDPConn.ReadFromUDP(buf)
if err != nil {
log.WithError(err).Error("Failed to read from UDP")
return 0, i2pkeys.I2PAddr(""), err
}
if bytes.Equal(saddr.IP, s.SAMUDPAddress.IP) {
continue
}
break
}
i := bytes.IndexByte(buf, byte('\n'))
if i > 4096 || i > n {
log.Error("Could not parse incoming message remote address")
return 0, i2pkeys.I2PAddr(""), errors.New("Could not parse incomming message remote address.")
}
raddr, err := i2pkeys.NewI2PAddrFromString(string(buf[:i]))
if err != nil {
log.WithError(err).Error("Could not parse incoming message remote address")
return 0, i2pkeys.I2PAddr(""), errors.New("Could not parse incomming message remote address: " + err.Error())
}
// shift out the incomming address to contain only the data received
if (n - i + 1) > len(b) {
copy(b, buf[i+1:i+1+len(b)])
return n - (i + 1), raddr, errors.New("Datagram did not fit into your buffer.")
} else {
copy(b, buf[i+1:n])
log.WithField("bytesRead", n-(i+1)).Debug("Datagram read successfully")
return n - (i + 1), raddr, nil
}
}
func (s *DatagramSession) Accept() (net.Conn, error) {
log.Debug("Accept called on DatagramSession")
return s, nil
}
func (s *DatagramSession) Read(b []byte) (n int, err error) {
log.Debug("Reading from DatagramSession")
rint, _, rerr := s.ReadFrom(b)
return rint, rerr
}
// 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.
func (s *DatagramSession) WriteTo(b []byte, addr net.Addr) (n int, err error) {
log.WithFields(logrus.Fields{
"addr": addr,
"datagramLen": len(b),
}).Debug("Writing datagram")
header := []byte("3.1 " + s.ID() + " " + addr.String() + "\n")
msg := append(header, b...)
n, err = s.UDPConn.WriteToUDP(msg, s.SAMUDPAddress)
if err != nil {
log.WithError(err).Error("Failed to write to UDP")
} else {
log.WithField("bytesWritten", n).Debug("Datagram written successfully")
}
return n, err
}
func (s *DatagramSession) Write(b []byte) (int, error) {
log.WithField("dataLen", len(b)).Debug("Writing to DatagramSession")
return s.WriteTo(b, s.RemoteI2PAddr)
}
// Closes the DatagramSession. Implements net.PacketConn
func (s *DatagramSession) Close() error {
log.Debug("Closing DatagramSession")
err := s.Conn.Close()
err2 := s.UDPConn.Close()
if err != nil {
log.WithError(err).Error("Failed to close connection")
return err
}
if err2 != nil {
log.WithError(err2).Error("Failed to close UDP connection")
}
return err2
}
// Returns the I2P destination of the DatagramSession.
func (s *DatagramSession) LocalI2PAddr() i2pkeys.I2PAddr {
addr := s.DestinationKeys.Addr()
log.WithField("localI2PAddr", addr).Debug("Getting local I2P address")
return addr
}
// Implements net.PacketConn
func (s *DatagramSession) LocalAddr() net.Addr {
return s.LocalI2PAddr()
}
func (s *DatagramSession) Addr() net.Addr {
return s.LocalI2PAddr()
}
func (s *DatagramSession) Lookup(name string) (a net.Addr, err error) {
log.WithField("name", name).Debug("Looking up address")
var sam *common.SAM
sam, err = common.NewSAM(s.Sam())
if err == nil {
defer sam.Close()
a, err = sam.Lookup(name)
}
log.WithField("address", a).Debug("Lookup successful")
return
}
// Sets read and write deadlines for the DatagramSession. Implements
// net.PacketConn and does the same thing. Setting write deadlines for datagrams
// is seldom done.
func (s *DatagramSession) SetDeadline(t time.Time) error {
log.WithField("deadline", t).Debug("Setting deadline")
return s.UDPConn.SetDeadline(t)
}
// Sets read deadline for the DatagramSession. Implements net.PacketConn
func (s *DatagramSession) SetReadDeadline(t time.Time) error {
log.WithField("readDeadline", t).Debug("Setting read deadline")
return s.UDPConn.SetReadDeadline(t)
}
// Sets the write deadline for the DatagramSession. Implements net.Packetconn.
func (s *DatagramSession) SetWriteDeadline(t time.Time) error {
log.WithField("writeDeadline", t).Debug("Setting write deadline")
return s.UDPConn.SetWriteDeadline(t)
}
func (s *DatagramSession) SetWriteBuffer(bytes int) error {
log.WithField("bytes", bytes).Debug("Setting write buffer")
return s.UDPConn.SetWriteBuffer(bytes)
}

21
datagram/types.go Normal file
View File

@ -0,0 +1,21 @@
package datagram
import (
"net"
"github.com/go-i2p/go-sam-go/common"
"github.com/go-i2p/i2pkeys"
)
type SAM common.SAM
// The DatagramSession implements net.PacketConn. It works almost like ordinary
// UDP, except that datagrams may be at most 31kB large. These datagrams are
// also end-to-end encrypted, signed and includes replay-protection. And they
// are also built to be surveillance-resistant (yey!).
type DatagramSession struct {
*SAM
UDPConn *net.UDPConn // used to deliver datagrams
SAMUDPAddress *net.UDPAddr // the SAM bridge UDP-port
RemoteI2PAddr *i2pkeys.I2PAddr // optional remote I2P address
}

10
go.mod Normal file
View File

@ -0,0 +1,10 @@
module github.com/go-i2p/go-sam-go
go 1.23.5
require (
github.com/go-i2p/i2pkeys v0.33.92
github.com/sirupsen/logrus v1.9.3
)
require golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect

17
go.sum Normal file
View File

@ -0,0 +1,17 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-i2p/i2pkeys v0.33.92 h1:e2vx3vf7tNesaJ8HmAlGPOcfiGM86jzeIGxh27I9J2Y=
github.com/go-i2p/i2pkeys v0.33.92/go.mod h1:BRURQ/twxV0WKjZlFSKki93ivBi+MirZPWudfwTzMpE=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

51
log/log.go Normal file
View File

@ -0,0 +1,51 @@
package log
import (
"io/ioutil"
"os"
"strings"
"sync"
"github.com/sirupsen/logrus"
)
var (
log *logrus.Logger
once sync.Once
)
func InitializeSAM3Logger() {
once.Do(func() {
log = logrus.New()
// We do not want to log by default
log.SetOutput(ioutil.Discard)
log.SetLevel(logrus.PanicLevel)
// Check if DEBUG_I2P is set
if logLevel := os.Getenv("DEBUG_I2P"); logLevel != "" {
log.SetOutput(os.Stdout)
switch strings.ToLower(logLevel) {
case "debug":
log.SetLevel(logrus.DebugLevel)
case "warn":
log.SetLevel(logrus.WarnLevel)
case "error":
log.SetLevel(logrus.ErrorLevel)
default:
log.SetLevel(logrus.DebugLevel)
}
log.WithField("level", log.GetLevel()).Debug("Logging enabled.")
}
})
}
// GetSAM3Logger returns the initialized logger
func GetSAM3Logger() *logrus.Logger {
if log == nil {
InitializeSAM3Logger()
}
return log
}
func init() {
InitializeSAM3Logger()
}

3
primary/const.go Normal file
View File

@ -0,0 +1,3 @@
package primary
const SESSION_ADDOK = "SESSION STATUS RESULT=OK"

73
primary/datagram.go Normal file
View File

@ -0,0 +1,73 @@
package primary
import (
"errors"
"net"
"strconv"
"github.com/go-i2p/go-sam-go/common"
"github.com/go-i2p/go-sam-go/datagram"
"github.com/sirupsen/logrus"
)
// Creates a new datagram session. udpPort is the UDP port SAM is listening on,
// and if you set it to zero, it will use SAMs standard UDP port.
func (s *PrimarySession) NewDatagramSubSession(id string, udpPort int) (*datagram.DatagramSession, error) {
log.WithFields(logrus.Fields{"id": id, "udpPort": udpPort}).Debug("NewDatagramSubSession called")
if udpPort > 65335 || udpPort < 0 {
log.WithField("udpPort", udpPort).Error("Invalid UDP port")
return nil, errors.New("udpPort needs to be in the intervall 0-65335")
}
if udpPort == 0 {
udpPort = 7655
log.Debug("Using default UDP port 7655")
}
lhost, _, err := common.SplitHostPort(s.conn.LocalAddr().String())
if err != nil {
log.WithError(err).Error("Failed to split local host port")
s.Close()
return nil, err
}
lUDPAddr, err := net.ResolveUDPAddr("udp4", lhost+":0")
if err != nil {
log.WithError(err).Error("Failed to resolve local UDP address")
return nil, err
}
udpconn, err := net.ListenUDP("udp4", lUDPAddr)
if err != nil {
log.WithError(err).Error("Failed to listen on UDP")
return nil, err
}
rhost, _, err := common.SplitHostPort(s.conn.RemoteAddr().String())
if err != nil {
log.WithError(err).Error("Failed to split remote host port")
s.Close()
return nil, err
}
rUDPAddr, err := net.ResolveUDPAddr("udp4", rhost+":"+strconv.Itoa(udpPort))
if err != nil {
log.WithError(err).Error("Failed to resolve remote UDP address")
return nil, err
}
_, lport, err := net.SplitHostPort(udpconn.LocalAddr().String())
if err != nil {
log.WithError(err).Error("Failed to get local port")
s.Close()
return nil, err
}
conn, err := s.NewGenericSubSession("DATAGRAM", id, []string{"PORT=" + lport})
if err != nil {
log.WithError(err).Error("Failed to create new generic sub-session")
return nil, err
}
log.WithFields(logrus.Fields{"id": id, "localPort": lport}).Debug("Created new datagram sub-session")
datagramSession := &datagram.DatagramSession{
SAM: (*datagram.SAM)(s.SAM),
SAMUDPAddress: rUDPAddr,
UDPConn: udpconn,
RemoteI2PAddr: nil,
}
datagramSession.Conn = conn
return datagramSession, nil
}

105
primary/dialers.go Normal file
View File

@ -0,0 +1,105 @@
package primary
import (
"fmt"
"net"
"strings"
"github.com/go-i2p/go-sam-go/common"
"github.com/go-i2p/go-sam-go/datagram"
"github.com/sirupsen/logrus"
)
func (sam *PrimarySession) Dial(network, addr string) (net.Conn, error) {
log.WithFields(logrus.Fields{"network": network, "addr": addr}).Debug("Dial() called")
if network == "udp" || network == "udp4" || network == "udp6" {
//return sam.DialUDPI2P(network, network+addr[0:4], addr)
return sam.DialUDPI2P(network, network+addr[0:4], addr)
}
if network == "tcp" || network == "tcp4" || network == "tcp6" {
//return sam.DialTCPI2P(network, network+addr[0:4], addr)
return sam.DialTCPI2P(network, network+addr[0:4], addr)
}
log.WithField("network", network).Error("Invalid network type")
return nil, fmt.Errorf("Error: Must specify a valid network type")
}
// DialTCP implements x/dialer
func (sam *PrimarySession) DialTCP(network string, laddr, raddr net.Addr) (net.Conn, error) {
log.WithFields(logrus.Fields{"network": network, "laddr": laddr, "raddr": raddr}).Debug("DialTCP() called")
ts, ok := sam.stsess[network+raddr.String()[0:4]]
var err error
if !ok {
ts, err = sam.NewUniqueStreamSubSession(network + raddr.String()[0:4])
if err != nil {
log.WithError(err).Error("Failed to create new unique stream sub-session")
return nil, err
}
sam.stsess[network+raddr.String()[0:4]] = ts
ts, _ = sam.stsess[network+raddr.String()[0:4]]
}
return ts.Dial(network, raddr.String())
}
func (sam *PrimarySession) DialTCPI2P(network string, 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
if !ok {
ts, err = sam.NewUniqueStreamSubSession(network + laddr)
if err != nil {
log.WithError(err).Error("Failed to create new unique stream sub-session")
return nil, err
}
sam.stsess[network+raddr[0:4]] = ts
ts, _ = sam.stsess[network+raddr[0:4]]
}
return ts.Dial(network, raddr)
}
// DialUDP implements x/dialer
func (sam *PrimarySession) DialUDP(network string, laddr, raddr net.Addr) (net.PacketConn, error) {
log.WithFields(logrus.Fields{"network": network, "laddr": laddr, "raddr": raddr}).Debug("DialUDP() called")
ds, ok := sam.dgsess[network+raddr.String()[0:4]]
var err error
if !ok {
ds, err = sam.NewDatagramSubSession(network+raddr.String()[0:4], 0)
if err != nil {
log.WithError(err).Error("Failed to create new datagram sub-session")
return nil, err
}
sam.dgsess[network+raddr.String()[0:4]] = ds
ds, _ = sam.dgsess[network+raddr.String()[0:4]]
}
return ds.Dial(network, raddr.String())
}
func (sam *PrimarySession) DialUDPI2P(network, laddr, raddr string) (*datagram.DatagramSession, error) {
log.WithFields(logrus.Fields{"network": network, "laddr": laddr, "raddr": raddr}).Debug("DialUDPI2P() called")
ds, ok := sam.dgsess[network+raddr[0:4]]
var err error
if !ok {
ds, err = sam.NewDatagramSubSession(network+laddr, 0)
if err != nil {
log.WithError(err).Error("Failed to create new datagram sub-session")
return nil, err
}
sam.dgsess[network+raddr[0:4]] = ds
ds, _ = sam.dgsess[network+raddr[0:4]]
}
return ds.Dial(network, raddr)
}
func (s *PrimarySession) Lookup(name string) (a net.Addr, err error) {
log.WithField("name", name).Debug("Lookup() called")
var sam *common.SAM
name = strings.Split(name, ":")[0]
sam, err = common.NewSAM(s.samAddr)
if err == nil {
log.WithField("addr", a).Debug("Lookup successful")
defer sam.Close()
a, err = sam.Lookup(name)
}
log.WithError(err).Error("Lookup failed")
return
}

101
primary/generic.go Normal file
View File

@ -0,0 +1,101 @@
package primary
import (
"errors"
"net"
"strings"
"github.com/sirupsen/logrus"
"github.com/go-i2p/go-sam-go/common"
)
// Creates a new session with the style of either "STREAM", "DATAGRAM" or "RAW",
// for a new I2P tunnel with name id, using the cypher keys specified, with the
// 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 *PrimarySession) NewGenericSubSession(style, id string, extras []string) (net.Conn, error) {
log.WithFields(logrus.Fields{"style": style, "id": id, "extras": extras}).Debug("newGenericSubSession called")
return sam.NewGenericSubSessionWithSignature(style, id, extras)
}
func (sam *PrimarySession) NewGenericSubSessionWithSignature(style, id string, extras []string) (net.Conn, error) {
log.WithFields(logrus.Fields{"style": style, "id": id, "extras": extras}).Debug("newGenericSubSessionWithSignature called")
return sam.NewGenericSubSessionWithSignatureAndPorts(style, id, "0", "0", extras)
}
// Creates a new session with the style of either "STREAM", "DATAGRAM" or "RAW",
// for a new I2P tunnel with name id, using the cypher keys specified, with the
// 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 *PrimarySession) NewGenericSubSessionWithSignatureAndPorts(style, id, from, to string, extras []string) (net.Conn, error) {
log.WithFields(logrus.Fields{"style": style, "id": id, "from": from, "to": to, "extras": extras}).Debug("newGenericSubSessionWithSignatureAndPorts called")
conn := sam.conn
fp := ""
tp := ""
if from != "0" && from != "" {
fp = " FROM_PORT=" + from
}
if to != "0" && to != "" {
tp = " TO_PORT=" + to
}
scmsg := []byte("SESSION ADD STYLE=" + style + " ID=" + id + fp + tp + " " + strings.Join(extras, " ") + "\n")
log.WithField("message", string(scmsg)).Debug("Sending SESSION ADD message")
for m, i := 0, 0; m != len(scmsg); i++ {
if i == 15 {
conn.Close()
log.Error("Writing to SAM failed after 15 attempts")
return nil, errors.New("writing to SAM failed")
}
n, err := conn.Write(scmsg[m:])
if err != nil {
log.WithError(err).Error("Failed to write to SAM connection")
conn.Close()
return nil, err
}
m += n
}
buf := make([]byte, 4096)
n, err := conn.Read(buf)
if err != nil {
log.WithError(err).Error("Failed to read from SAM connection")
conn.Close()
return nil, err
}
text := string(buf[:n])
log.WithField("response", text).Debug("Received response from SAM")
//log.Println("SAM:", text)
if strings.HasPrefix(text, SESSION_ADDOK) {
//if sam.keys.String() != text[len(common.SESSION_ADDOK):len(text)-1] {
//conn.Close()
//return nil, errors.New("SAMv3 created a tunnel with keys other than the ones we asked it for")
//}
log.Debug("Session added successfully")
return conn, nil //&StreamSession{id, conn, keys, nil, sync.RWMutex{}, nil}, nil
} else if text == common.SESSION_DUPLICATE_ID {
log.Error("Duplicate tunnel name")
conn.Close()
return nil, errors.New("Duplicate tunnel name")
} else if text == common.SESSION_DUPLICATE_DEST {
log.Error("Duplicate destination")
conn.Close()
return nil, errors.New("Duplicate destination")
} else if text == common.SESSION_INVALID_KEY {
log.Error("Invalid key - Primary Session")
conn.Close()
return nil, errors.New("Invalid key - Primary Session")
} else if strings.HasPrefix(text, common.SESSION_I2P_ERROR) {
log.WithField("error", text[len(common.SESSION_I2P_ERROR):]).Error("I2P error")
conn.Close()
return nil, errors.New("I2P error " + text[len(common.SESSION_I2P_ERROR):])
} else {
log.WithField("reply", text).Error("Unable to parse SAMv3 reply")
conn.Close()
return nil, errors.New("Unable to parse SAMv3 reply: " + text)
}
}

10
primary/log.go Normal file
View File

@ -0,0 +1,10 @@
package primary
import logger "github.com/go-i2p/go-sam-go/log"
var log = logger.GetSAM3Logger()
func init() {
logger.InitializeSAM3Logger()
log = logger.GetSAM3Logger()
}

46
primary/primary.go Normal file
View File

@ -0,0 +1,46 @@
package primary
/*
// Creates a new PrimarySession with the I2CP- and streaminglib options as
// specified. See the I2P documentation for a full list of options.
func (sam *SAM) NewPrimarySession(id string, keys i2pkeys.I2PKeys, options []string) (*PrimarySession, error) {
log.WithFields(logrus.Fields{"id": id, "options": options}).Debug("NewPrimarySession() called")
return sam.newPrimarySession(PrimarySessionSwitch, id, keys, options)
}
func (sam *SAM) newPrimarySession(primarySessionSwitch string, id string, keys i2pkeys.I2PKeys, options []string) (*PrimarySession, error) {
log.WithFields(logrus.Fields{
"primarySessionSwitch": primarySessionSwitch,
"id": id,
"options": options,
}).Debug("newPrimarySession() called")
conn, err := sam.newGenericSession(primarySessionSwitch, id, keys, options, []string{})
if err != nil {
log.WithError(err).Error("Failed to create new generic session")
return nil, err
}
ssesss := make(map[string]*StreamSession)
dsesss := make(map[string]*DatagramSession)
return &PrimarySession{sam.Config.I2PConfig.Sam(), id, conn, keys, time.Duration(600 * time.Second), time.Now(), Sig_NONE, sam.Config, ssesss, dsesss}, nil
}
// Creates a new PrimarySession with the I2CP- and PRIMARYinglib options as
// specified. See the I2P documentation for a full list of options.
func (sam *SAM) NewPrimarySessionWithSignature(id string, keys i2pkeys.I2PKeys, options []string, sigType string) (*PrimarySession, error) {
log.WithFields(logrus.Fields{
"id": id,
"options": options,
"sigType": sigType,
}).Debug("NewPrimarySessionWithSignature() called")
conn, err := sam.newGenericSessionWithSignature(PrimarySessionSwitch, id, keys, sigType, options, []string{})
if err != nil {
log.WithError(err).Error("Failed to create new generic session with signature")
return nil, err
}
ssesss := make(map[string]*stream.StreamSession)
dsesss := make(map[string]*datagram.DatagramSession)
return &PrimarySession{sam.Config.I2PConfig.Sam(), id, conn, keys, time.Duration(600 * time.Second), time.Now(), sigType, sam.Config, ssesss, dsesss}, nil
}
*/

74
primary/raw.go Normal file
View File

@ -0,0 +1,74 @@
package primary
import (
"errors"
"net"
"strconv"
"github.com/go-i2p/go-sam-go/common"
"github.com/go-i2p/go-sam-go/raw"
"github.com/sirupsen/logrus"
)
// Creates a new raw session. udpPort is the UDP port SAM is listening on,
// and if you set it to zero, it will use SAMs standard UDP port.
func (s *PrimarySession) NewRawSubSession(id string, udpPort int) (*raw.RawSession, error) {
log.WithFields(logrus.Fields{"id": id, "udpPort": udpPort}).Debug("NewRawSubSession called")
if udpPort > 65335 || udpPort < 0 {
log.WithField("udpPort", udpPort).Error("Invalid UDP port")
return nil, errors.New("udpPort needs to be in the intervall 0-65335")
}
if udpPort == 0 {
udpPort = 7655
log.Debug("Using default UDP port 7655")
}
lhost, _, err := common.SplitHostPort(s.conn.LocalAddr().String())
if err != nil {
log.WithError(err).Error("Failed to split local host port")
s.Close()
return nil, err
}
lUDPAddr, err := net.ResolveUDPAddr("udp4", lhost+":0")
if err != nil {
log.WithError(err).Error("Failed to resolve local UDP address")
return nil, err
}
udpconn, err := net.ListenUDP("udp4", lUDPAddr)
if err != nil {
log.WithError(err).Error("Failed to listen on UDP")
return nil, err
}
rhost, _, err := common.SplitHostPort(s.conn.RemoteAddr().String())
if err != nil {
log.WithError(err).Error("Failed to split remote host port")
s.Close()
return nil, err
}
rUDPAddr, err := net.ResolveUDPAddr("udp4", rhost+":"+strconv.Itoa(udpPort))
if err != nil {
log.WithError(err).Error("Failed to resolve remote UDP address")
return nil, err
}
_, lport, err := net.SplitHostPort(udpconn.LocalAddr().String())
if err != nil {
log.WithError(err).Error("Failed to get local port")
s.Close()
return nil, err
}
// conn, err := s.newGenericSubSession("RAW", id, s.keys, options, []string{"PORT=" + lport})
conn, err := s.NewGenericSubSession("RAW", id, []string{"PORT=" + lport})
if err != nil {
log.WithError(err).Error("Failed to create new generic sub-session")
return nil, err
}
log.WithFields(logrus.Fields{"id": id, "localPort": lport}).Debug("Created new raw sub-session")
rawSession := &raw.RawSession{
SAM: (*raw.SAM)(s.SAM),
SAMUDPConn: udpconn,
SAMUDPAddr: rUDPAddr,
}
rawSession.Conn = conn
return rawSession, nil
}

57
primary/stream.go Normal file
View File

@ -0,0 +1,57 @@
package primary
import (
"github.com/go-i2p/go-sam-go/common"
"github.com/go-i2p/go-sam-go/stream"
"github.com/sirupsen/logrus"
)
// Creates a new stream.StreamSession with the I2CP- and streaminglib options as
// specified. See the I2P documentation for a full list of options.
func (sam *PrimarySession) NewStreamSubSession(id string) (*stream.StreamSession, error) {
log.WithField("id", id).Debug("NewStreamSubSession called")
conn, err := sam.NewGenericSubSession("STREAM", id, []string{})
if err != nil {
log.WithError(err).Error("Failed to create new generic sub-session")
return nil, err
}
streamSession := &stream.StreamSession{
SAM: (*stream.SAM)(sam.SAM),
}
streamSession.Conn = conn
return streamSession, nil
}
// Creates a new stream.StreamSession with the I2CP- and streaminglib options as
// specified. See the I2P documentation for a full list of options.
func (sam *PrimarySession) NewUniqueStreamSubSession(id string) (*stream.StreamSession, error) {
log.WithField("id", id).Debug("NewUniqueStreamSubSession called")
conn, err := sam.NewGenericSubSession("STREAM", id, []string{})
if err != nil {
log.WithError(err).Error("Failed to create new generic sub-session")
return nil, err
}
fromPort, toPort := common.RandPort(), common.RandPort()
log.WithFields(logrus.Fields{"fromPort": fromPort, "toPort": toPort}).Debug("Generated random ports")
streamSession := &stream.StreamSession{
SAM: (*stream.SAM)(sam.SAM),
}
streamSession.Conn = conn
return streamSession, nil
}
// Creates a new stream.StreamSession with the I2CP- and streaminglib options as
// specified. See the I2P documentation for a full list of options.
func (sam *PrimarySession) NewStreamSubSessionWithPorts(id, from, to string) (*stream.StreamSession, error) {
log.WithFields(logrus.Fields{"id": id, "from": from, "to": to}).Debug("NewStreamSubSessionWithPorts called")
conn, err := sam.NewGenericSubSessionWithSignatureAndPorts("STREAM", id, from, to, []string{})
if err != nil {
log.WithError(err).Error("Failed to create new generic sub-session with signature and ports")
return nil, err
}
streamSession := &stream.StreamSession{
SAM: (*stream.SAM)(sam.SAM),
}
streamSession.Conn = conn
return streamSession, nil
}

30
primary/types.go Normal file
View File

@ -0,0 +1,30 @@
package primary
import (
"net"
"time"
"github.com/go-i2p/go-sam-go/common"
"github.com/go-i2p/go-sam-go/datagram"
"github.com/go-i2p/go-sam-go/stream"
"github.com/go-i2p/i2pkeys"
)
type SAM common.SAM
// Represents a primary session.
type PrimarySession struct {
*SAM
samAddr string // address to the sam bridge (ipv4:port)
id string // tunnel name
conn net.Conn // connection to sam
keys i2pkeys.I2PKeys // i2p destination keys
Timeout time.Duration
Deadline time.Time
sigType string
Config common.SAMEmit
stsess map[string]*stream.StreamSession
dgsess map[string]*datagram.DatagramSession
// from string
// to string
}

10
raw/log.go Normal file
View File

@ -0,0 +1,10 @@
package raw
import logger "github.com/go-i2p/go-sam-go/log"
var log = logger.GetSAM3Logger()
func init() {
logger.InitializeSAM3Logger()
log = logger.GetSAM3Logger()
}

73
raw/raw.go Normal file
View File

@ -0,0 +1,73 @@
package raw
import (
"errors"
"net"
"strconv"
"github.com/go-i2p/go-sam-go/common"
"github.com/go-i2p/i2pkeys"
"github.com/sirupsen/logrus"
)
// Creates a new raw session. udpPort is the UDP port SAM is listening on,
// and if you set it to zero, it will use SAMs standard UDP port.
func (s *SAM) NewRawSession(id string, keys i2pkeys.I2PKeys, options []string, udpPort int) (*RawSession, error) {
log.WithFields(logrus.Fields{"id": id, "udpPort": udpPort}).Debug("Creating new RawSession")
if udpPort > 65335 || udpPort < 0 {
log.WithField("udpPort", udpPort).Error("Invalid UDP port")
return nil, errors.New("udpPort needs to be in the interval 0-65335")
}
if udpPort == 0 {
udpPort = 7655
log.Debug("Using default UDP port 7655")
}
lhost, _, err := common.SplitHostPort(s.LocalAddr().String())
if err != nil {
log.Debug("Using default UDP port 7655")
s.Close()
return nil, err
}
lUDPAddr, err := net.ResolveUDPAddr("udp4", lhost+":0")
if err != nil {
log.WithError(err).Error("Failed to resolve local UDP address")
return nil, err
}
udpconn, err := net.ListenUDP("udp4", lUDPAddr)
if err != nil {
log.WithError(err).Error("Failed to listen on UDP")
return nil, err
}
rhost, _, err := common.SplitHostPort(s.RemoteAddr().String())
if err != nil {
log.WithError(err).Error("Failed to split remote host port")
s.Close()
return nil, err
}
rUDPAddr, err := net.ResolveUDPAddr("udp4", rhost+":"+strconv.Itoa(udpPort))
if err != nil {
log.WithError(err).Error("Failed to resolve remote UDP address")
return nil, err
}
_, lport, err := net.SplitHostPort(udpconn.LocalAddr().String())
if err != nil {
log.WithError(err).Error("Failed to get local port")
return nil, err
}
conn, err := s.NewGenericSession("RAW", id, keys, []string{"PORT=" + lport})
if err != nil {
log.WithError(err).Error("Failed to create new generic session")
return nil, err
}
log.WithFields(logrus.Fields{
"id": id,
"localPort": lport,
"remoteUDPAddr": rUDPAddr,
}).Debug("Created new RawSession")
rawSession := &RawSession{
SAM: s,
}
rawSession.Conn = conn
return rawSession, nil
}

21
raw/types.go Normal file
View File

@ -0,0 +1,21 @@
package raw
import (
"net"
"github.com/go-i2p/go-sam-go/common"
)
type SAM common.SAM
// The RawSession provides no authentication of senders, and there is no sender
// address attached to datagrams, so all communication is anonymous. The
// messages send are however still endpoint-to-endpoint encrypted. You
// need to figure out a way to identify and authenticate clients yourself, iff
// that is needed. Raw datagrams may be at most 32 kB in size. There is no
// overhead of authentication, which is the reason to use this..
type RawSession struct {
*SAM
SAMUDPConn *net.UDPConn // used to deliver datagrams
SAMUDPAddr *net.UDPAddr // the SAM bridge UDP-port
}

58
stream/conn.go Normal file
View File

@ -0,0 +1,58 @@
package stream
import (
"net"
"time"
"github.com/go-i2p/i2pkeys"
)
// Implements net.Conn
func (sc *StreamConn) Read(buf []byte) (int, error) {
n, err := sc.conn.Read(buf)
return n, err
}
// Implements net.Conn
func (sc *StreamConn) Write(buf []byte) (int, error) {
n, err := sc.conn.Write(buf)
return n, err
}
// Implements net.Conn
func (sc *StreamConn) Close() error {
return sc.conn.Close()
}
func (sc *StreamConn) LocalAddr() net.Addr {
return sc.localAddr()
}
// Implements net.Conn
func (sc *StreamConn) localAddr() i2pkeys.I2PAddr {
return sc.laddr
}
func (sc *StreamConn) RemoteAddr() net.Addr {
return sc.remoteAddr()
}
// Implements net.Conn
func (sc *StreamConn) remoteAddr() i2pkeys.I2PAddr {
return sc.raddr
}
// Implements net.Conn
func (sc *StreamConn) SetDeadline(t time.Time) error {
return sc.conn.SetDeadline(t)
}
// Implements net.Conn
func (sc *StreamConn) SetReadDeadline(t time.Time) error {
return sc.conn.SetReadDeadline(t)
}
// Implements net.Conn
func (sc *StreamConn) SetWriteDeadline(t time.Time) error {
return sc.conn.SetWriteDeadline(t)
}

11
stream/const.go Normal file
View File

@ -0,0 +1,11 @@
package stream
const (
ResultOK = "RESULT=OK"
ResultCantReachPeer = "RESULT=CANT_REACH_PEER"
ResultI2PError = "RESULT=I2P_ERROR"
ResultInvalidKey = "RESULT=INVALID_KEY"
ResultInvalidID = "RESULT=INVALID_ID"
ResultTimeout = "RESULT=TIMEOUT"
StreamConnectCommand = "STREAM CONNECT ID="
)

138
stream/dialers.go Normal file
View File

@ -0,0 +1,138 @@
package stream
import (
"bufio"
"bytes"
"context"
"fmt"
"io"
"net"
"strings"
"time"
"github.com/go-i2p/go-sam-go/common"
"github.com/go-i2p/i2pkeys"
"github.com/sirupsen/logrus"
)
// context-aware dialer, eventually...
func (s *StreamSession) DialContext(ctx context.Context, n, addr string) (net.Conn, error) {
log.WithFields(logrus.Fields{"network": n, "addr": addr}).Debug("DialContext called")
return s.DialContextI2P(ctx, n, addr)
}
// context-aware dialer, eventually...
func (s *StreamSession) DialContextI2P(ctx context.Context, n, addr string) (*StreamConn, error) {
log.WithFields(logrus.Fields{"network": n, "addr": addr}).Debug("DialContextI2P called")
if ctx == nil {
log.Panic("nil context")
panic("nil context")
}
deadline := s.deadline(ctx, time.Now())
if !deadline.IsZero() {
if d, ok := ctx.Deadline(); !ok || deadline.Before(d) {
subCtx, cancel := context.WithDeadline(ctx, deadline)
defer cancel()
ctx = subCtx
}
}
i2paddr, err := i2pkeys.NewI2PAddrFromString(addr)
if err != nil {
log.WithError(err).Error("Failed to create I2P address from string")
return nil, err
}
return s.DialI2P(i2paddr)
}
// implement net.Dialer
func (s *StreamSession) Dial(n, addr string) (c net.Conn, err error) {
log.WithFields(logrus.Fields{"network": n, "addr": addr}).Debug("Dial called")
var i2paddr i2pkeys.I2PAddr
var host string
host, _, err = net.SplitHostPort(addr)
//log.Println("Dialing:", host)
if err = common.IgnorePortError(err); err == nil {
// check for name
if strings.HasSuffix(host, ".b32.i2p") || strings.HasSuffix(host, ".i2p") {
// name lookup
i2paddr, err = s.Lookup(host)
log.WithFields(logrus.Fields{"host": host, "i2paddr": i2paddr}).Debug("Looked up I2P address")
} else {
// probably a destination
i2paddr, err = i2pkeys.NewI2PAddrFromBytes([]byte(host))
//i2paddr = i2pkeys.I2PAddr(host)
//log.Println("Destination:", i2paddr, err)
log.WithFields(logrus.Fields{"host": host, "i2paddr": i2paddr}).Debug("Created I2P address from bytes")
}
if err == nil {
return s.DialI2P(i2paddr)
}
}
log.WithError(err).Error("Dial failed")
return
}
// Dials to an I2P destination and returns a SAMConn, which implements a net.Conn.
func (s *StreamSession) DialI2P(addr i2pkeys.I2PAddr) (*StreamConn, error) {
log.WithField("addr", addr).Debug("DialI2P called")
sam, err := common.NewSAM(s.Sam())
if err != nil {
log.WithError(err).Error("Failed to create new SAM instance")
return nil, err
}
conn := sam.Conn
_, err = conn.Write([]byte("STREAM CONNECT ID=" + s.ID() + s.FromPort() + s.ToPort() + " DESTINATION=" + addr.Base64() + " SILENT=false\n"))
if err != nil {
log.WithError(err).Error("Failed to write STREAM CONNECT command")
conn.Close()
return nil, err
}
buf := make([]byte, 4096)
n, err := conn.Read(buf)
if err != nil && err != io.EOF {
log.WithError(err).Error("Failed to write STREAM CONNECT command")
conn.Close()
return nil, err
}
scanner := bufio.NewScanner(bytes.NewReader(buf[:n]))
scanner.Split(bufio.ScanWords)
for scanner.Scan() {
switch scanner.Text() {
case "STREAM":
continue
case "STATUS":
continue
case ResultOK:
log.Debug("Successfully connected to I2P destination")
return &StreamConn{s.Addr(), addr, conn}, nil
case ResultCantReachPeer:
log.Error("Can't reach peer")
conn.Close()
return nil, fmt.Errorf("Can not reach peer")
case ResultI2PError:
log.Error("I2P internal error")
conn.Close()
return nil, fmt.Errorf("I2P internal error")
case ResultInvalidKey:
log.Error("Invalid key - Stream Session")
conn.Close()
return nil, fmt.Errorf("Invalid key - Stream Session")
case ResultInvalidID:
log.Error("Invalid tunnel ID")
conn.Close()
return nil, fmt.Errorf("Invalid tunnel ID")
case ResultTimeout:
log.Error("Connection timeout")
conn.Close()
return nil, fmt.Errorf("Timeout")
default:
log.WithField("error", scanner.Text()).Error("Unknown error")
conn.Close()
return nil, fmt.Errorf("Unknown error: %s : %s", scanner.Text(), string(buf[:n]))
}
}
log.Panic("Unexpected end of StreamSession.DialI2P()")
panic("sam3 go library error in StreamSession.DialI2P()")
}

11
stream/listen.go Normal file
View File

@ -0,0 +1,11 @@
package stream
import "github.com/sirupsen/logrus"
// create a new stream listener to accept inbound connections
func (s *StreamSession) Listen() (*StreamListener, error) {
log.WithFields(logrus.Fields{"id": s.ID(), "laddr": s.Addr()}).Debug("Creating new StreamListener")
return &StreamListener{
session: s,
}, nil
}

133
stream/listener.go Normal file
View File

@ -0,0 +1,133 @@
package stream
import (
"bufio"
"errors"
"io"
"net"
"strconv"
"strings"
"github.com/sirupsen/logrus"
"github.com/go-i2p/go-sam-go/common"
"github.com/go-i2p/i2pkeys"
)
func (l *StreamListener) From() string {
return l.session.Fromport
}
func (l *StreamListener) To() string {
return l.session.Toport
}
// get our address
// implements net.Listener
func (l *StreamListener) Addr() net.Addr {
return l.session.DestinationKeys.Addr()
}
// implements net.Listener
func (l *StreamListener) Close() error {
return l.session.Close()
}
// implements net.Listener
func (l *StreamListener) Accept() (net.Conn, error) {
return l.AcceptI2P()
}
func ExtractPairString(input, value string) string {
log.WithFields(logrus.Fields{"input": input, "value": value}).Debug("ExtractPairString called")
parts := strings.Split(input, " ")
for _, part := range parts {
if strings.HasPrefix(part, value) {
kv := strings.SplitN(input, "=", 2)
if len(kv) == 2 {
log.WithFields(logrus.Fields{"key": kv[0], "value": kv[1]}).Debug("Pair extracted")
return kv[1]
}
}
}
log.WithFields(logrus.Fields{"input": input, "value": value}).Debug("No pair found")
return ""
}
func ExtractPairInt(input, value string) int {
rv, err := strconv.Atoi(ExtractPairString(input, value))
if err != nil {
log.WithFields(logrus.Fields{"input": input, "value": value}).Debug("No pair found")
return 0
}
log.WithField("result", rv).Debug("Pair extracted and converted to int")
return rv
}
func ExtractDest(input string) string {
log.WithField("input", input).Debug("ExtractDest called")
dest := strings.Split(input, " ")[0]
log.WithField("dest", dest).Debug("Destination extracted")
return strings.Split(input, " ")[0]
}
// accept a new inbound connection
func (l *StreamListener) AcceptI2P() (*StreamConn, error) {
log.Debug("StreamListener.AcceptI2P() called")
s, err := common.NewSAM(l.session.Sam())
if err == nil {
log.Debug("Connected to SAM bridge")
// we connected to sam
// send accept() command
_, err = io.WriteString(s.Conn, "STREAM ACCEPT ID="+l.session.ID()+" SILENT=false\n")
if err != nil {
log.WithError(err).Error("Failed to send STREAM ACCEPT command")
s.Close()
return nil, err
}
// read reply
rd := bufio.NewReader(s.Conn)
// read first line
line, err := rd.ReadString(10)
if err != nil {
log.WithError(err).Error("Failed to read SAM bridge response")
s.Close()
return nil, err
}
log.WithField("response", line).Debug("Received SAM bridge response")
log.Println(line)
if strings.HasPrefix(line, "STREAM STATUS RESULT=OK") {
// we gud read destination line
destline, err := rd.ReadString(10)
if err == nil {
dest := ExtractDest(destline)
l.session.Fromport = ExtractPairString(destline, "FROM_PORT")
l.session.Toport = ExtractPairString(destline, "TO_PORT")
// return wrapped connection
dest = strings.Trim(dest, "\n")
log.WithFields(logrus.Fields{
"dest": dest,
"from": l.From(),
"to": l.To(),
}).Debug("Accepted new I2P connection")
return &StreamConn{
laddr: l.session.Addr(),
raddr: i2pkeys.I2PAddr(dest),
conn: s.Conn,
}, nil
} else {
log.WithError(err).Error("Failed to read destination line")
s.Close()
return nil, err
}
} else {
log.WithField("line", line).Error("Invalid SAM response")
s.Close()
return nil, errors.New("invalid sam line: " + line)
}
} else {
log.WithError(err).Error("Failed to connect to SAM bridge")
s.Close()
return nil, err
}
}

10
stream/log.go Normal file
View File

@ -0,0 +1,10 @@
package stream
import logger "github.com/go-i2p/go-sam-go/log"
var log = logger.GetSAM3Logger()
func init() {
logger.InitializeSAM3Logger()
log = logger.GetSAM3Logger()
}

107
stream/session.go Normal file
View File

@ -0,0 +1,107 @@
package stream
import (
"context"
"net"
"time"
"github.com/go-i2p/go-sam-go/common"
"github.com/go-i2p/i2pkeys"
)
// Read reads data from the stream.
func (s *StreamSession) Read(buf []byte) (int, error) {
return s.Conn.Read(buf)
}
// Write sends data over the stream.
func (s *StreamSession) Write(data []byte) (int, error) {
return s.Conn.Write(data)
}
func (s *StreamSession) SetDeadline(t time.Time) error {
log.WithField("deadline", t).Debug("Setting deadline for StreamSession")
return s.Conn.SetDeadline(t)
}
func (s *StreamSession) SetReadDeadline(t time.Time) error {
log.WithField("readDeadline", t).Debug("Setting read deadline for StreamSession")
return s.Conn.SetReadDeadline(t)
}
func (s *StreamSession) SetWriteDeadline(t time.Time) error {
log.WithField("writeDeadline", t).Debug("Setting write deadline for StreamSession")
return s.Conn.SetWriteDeadline(t)
}
func (s *StreamSession) From() string {
return s.Fromport
}
func (s *StreamSession) To() string {
return s.Toport
}
func (s *StreamSession) SignatureType() string {
return s.SignatureType()
}
func (s *StreamSession) Close() error {
log.WithField("id", s.ID()).Debug("Closing StreamSession")
return s.Conn.Close()
}
// Returns the I2P destination (the address) of the stream session
func (s *StreamSession) Addr() i2pkeys.I2PAddr {
return s.Addr()
}
func (s *StreamSession) LocalAddr() net.Addr {
return s.Addr()
}
// Returns the keys associated with the stream session
func (s *StreamSession) Keys() i2pkeys.I2PKeys {
return *s.DestinationKeys
}
// lookup name, convenience function
func (s *StreamSession) Lookup(name string) (i2pkeys.I2PAddr, error) {
log.WithField("name", name).Debug("Looking up address")
sam, err := common.NewSAM(s.Sam())
if err == nil {
addr, err := sam.Lookup(name)
defer sam.Close()
if err != nil {
log.WithError(err).Error("Lookup failed")
} else {
log.WithField("addr", addr).Debug("Lookup successful")
}
return addr, err
}
log.WithError(err).Error("Failed to create SAM instance for lookup")
return i2pkeys.I2PAddr(""), err
}
/*
func (s *StreamSession) Cancel() chan *StreamSession {
ch := make(chan *StreamSession)
ch <- s
return ch
}*/
// deadline returns the earliest of:
// - now+Timeout
// - d.Deadline
// - the context's deadline
//
// Or zero, if none of Timeout, Deadline, or context's deadline is set.
func (s *StreamSession) deadline(ctx context.Context, now time.Time) (earliest time.Time) {
if s.Timeout != 0 { // including negative, for historical reasons
earliest = now.Add(s.Timeout)
}
if d, ok := ctx.Deadline(); ok {
earliest = minNonzeroTime(earliest, d)
}
return minNonzeroTime(earliest, s.Deadline)
}

56
stream/stream.go Normal file
View File

@ -0,0 +1,56 @@
package stream
import (
"github.com/go-i2p/i2pkeys"
"github.com/sirupsen/logrus"
)
// Creates a new StreamSession with the I2CP- and streaminglib options as
// specified. See the I2P documentation for a full list of options.
func (sam *SAM) NewStreamSession(id string, keys i2pkeys.I2PKeys, options []string) (*StreamSession, error) {
log.WithFields(logrus.Fields{"id": id, "options": options}).Debug("Creating new StreamSession")
conn, err := sam.NewGenericSession("STREAM", id, keys, []string{})
if err != nil {
return nil, err
}
log.WithField("id", id).Debug("Created new StreamSession")
streamSession := &StreamSession{
SAM: sam,
}
streamSession.Conn = conn
return streamSession, nil
}
// Creates a new StreamSession with the I2CP- and streaminglib options as
// specified. See the I2P documentation for a full list of options.
func (sam *SAM) NewStreamSessionWithSignature(id string, keys i2pkeys.I2PKeys, options []string, sigType string) (*StreamSession, error) {
log.WithFields(logrus.Fields{"id": id, "options": options, "sigType": sigType}).Debug("Creating new StreamSession with signature")
conn, err := sam.NewGenericSessionWithSignature("STREAM", id, keys, sigType, []string{})
if err != nil {
return nil, err
}
log.WithFields(logrus.Fields{"id": id, "sigType": sigType}).Debug("Created new StreamSession with signature")
log.WithField("id", id).Debug("Created new StreamSession")
streamSession := &StreamSession{
SAM: sam,
}
streamSession.Conn = conn
return streamSession, nil
}
// Creates a new StreamSession with the I2CP- and streaminglib options as
// specified. See the I2P documentation for a full list of options.
func (sam *SAM) NewStreamSessionWithSignatureAndPorts(id, from, to string, keys i2pkeys.I2PKeys, options []string, sigType string) (*StreamSession, error) {
log.WithFields(logrus.Fields{"id": id, "from": from, "to": to, "options": options, "sigType": sigType}).Debug("Creating new StreamSession with signature and ports")
conn, err := sam.NewGenericSessionWithSignatureAndPorts("STREAM", id, from, to, keys, sigType, []string{})
if err != nil {
return nil, err
}
log.WithFields(logrus.Fields{"id": id, "from": from, "to": to, "sigType": sigType}).Debug("Created new StreamSession with signature and ports")
log.WithField("id", id).Debug("Created new StreamSession")
streamSession := &StreamSession{
SAM: sam,
}
streamSession.Conn = conn
return streamSession, nil
}

29
stream/types.go Normal file
View File

@ -0,0 +1,29 @@
package stream
import (
"net"
"time"
"github.com/go-i2p/go-sam-go/common"
"github.com/go-i2p/i2pkeys"
)
type SAM common.SAM
// Represents a streaming session.
type StreamSession struct {
*SAM
Timeout time.Duration
Deadline time.Time
}
type StreamListener struct {
// parent stream session
session *StreamSession
}
type StreamConn struct {
laddr i2pkeys.I2PAddr
raddr i2pkeys.I2PAddr
conn net.Conn
}

13
stream/util.go Normal file
View File

@ -0,0 +1,13 @@
package stream
import "time"
func minNonzeroTime(a, b time.Time) time.Time {
if a.IsZero() {
return b
}
if b.IsZero() || a.Before(b) {
return a
}
return b
}