mirror of
https://github.com/go-i2p/go-sam-go.git
synced 2025-07-12 21:59:33 -04:00
part out sam3
This commit is contained in:
5
README.md
Normal file
5
README.md
Normal 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
273
common/SAM.go
Normal 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
501
common/config.go
Normal 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
32
common/const.go
Normal 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
436
common/emit-options.go
Normal 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
106
common/emitter.go
Normal 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
10
common/log.go
Normal 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
82
common/resolver.go
Normal 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
69
common/sam3.go
Normal 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
82
common/types.go
Normal 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
59
common/util.go
Normal 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
78
datagram/datagram.go
Normal 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
10
datagram/log.go
Normal 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
209
datagram/session.go
Normal 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
21
datagram/types.go
Normal 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
10
go.mod
Normal 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
17
go.sum
Normal 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
51
log/log.go
Normal 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
3
primary/const.go
Normal file
@ -0,0 +1,3 @@
|
||||
package primary
|
||||
|
||||
const SESSION_ADDOK = "SESSION STATUS RESULT=OK"
|
73
primary/datagram.go
Normal file
73
primary/datagram.go
Normal 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
105
primary/dialers.go
Normal 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
101
primary/generic.go
Normal 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
10
primary/log.go
Normal 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
46
primary/primary.go
Normal 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
74
primary/raw.go
Normal 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
57
primary/stream.go
Normal 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
30
primary/types.go
Normal 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
10
raw/log.go
Normal 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
73
raw/raw.go
Normal 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
21
raw/types.go
Normal 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
58
stream/conn.go
Normal 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
11
stream/const.go
Normal 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
138
stream/dialers.go
Normal 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
11
stream/listen.go
Normal 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
133
stream/listener.go
Normal 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
10
stream/log.go
Normal 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
107
stream/session.go
Normal 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
56
stream/stream.go
Normal 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
29
stream/types.go
Normal 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
13
stream/util.go
Normal 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
|
||||
}
|
Reference in New Issue
Block a user