7 Commits
master ... ramp

Author SHA1 Message Date
idk
6026a5bc5f tolerate FROM_PORT and TO_PORT on datagrams 2019-04-29 17:04:13 -04:00
idk
15b139ea53 go fmt 2019-04-29 16:17:24 -04:00
idk
cbcc989bf6 use more retainable configs 2019-04-29 16:16:24 -04:00
idk
9e6b5e1031 further use of ramp 2019-04-29 11:47:38 -04:00
idk
cfcbc666ad try and reduce some complexity with ramp 2019-04-29 11:10:25 -04:00
idk
d4bff7e8c4 forgot to add moved i2pkeys dir 2019-04-20 14:53:50 -04:00
idk
f34c6f2619 export Conn and Resolver 2019-04-11 18:40:05 -04:00
43 changed files with 699 additions and 4255 deletions

View File

@@ -1,20 +0,0 @@
name: Auto Assign
on:
issues:
types: [opened]
pull_request:
types: [opened]
jobs:
run:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- name: 'Auto-assign issue'
uses: pozil/auto-assign-issue@v1
with:
repo-token:${{ secrets.GITHUB_TOKEN }}
assignees: eyedeekay
numOfAssignee: 1

1
.gitignore vendored
View File

@@ -1,4 +1,3 @@
*~
*.swp
README.md.asc
log

View File

View File

@@ -1,30 +0,0 @@
USER_GH=go-i2p
VERSION=0.33.92
CREDIT='contributors to this release: @hkh4n, @eyedeekay'
packagename=sam3
echo:
@echo "type make version to do release $(VERSION)"
version:
github-release release -s $(GITHUB_TOKEN) -u $(USER_GH) -r $(packagename) -t v$(VERSION) -d "version $(VERSION) $(CREDIT)"
del:
github-release delete -s $(GITHUB_TOKEN) -u $(USER_GH) -r $(packagename) -t v$(VERSION)
tar:
tar --exclude .git \
--exclude .go \
--exclude bin \
-cJvf ../$(packagename)_$(VERSION).orig.tar.xz .
copier:
echo '#! /usr/bin/env sh' > deb/copy.sh
echo 'for f in $$(ls); do scp $$f/*.deb user@192.168.99.106:~/DEBIAN_PKGS/$$f/main/; done' >> deb/copy.sh
fmt:
find . -name '*.go' -exec gofumpt -w -s -extra {} \;
upload-linux:
github-release upload -R -u $(USER_GH) -r "$(packagename)" -t $(VERSION) -l `sha256sum ` -n "$(packagename)" -f "$(packagename)"

View File

@@ -1,11 +1,5 @@
# README #
STATUS: This project is maintained. I will respond to issues, pull requests, and feature requests within a few days.
[![Go Report Card](https://goreportcard.com/badge/github.com/go-i2p/sam3)](https://goreportcard.com/report/github.com/go-i2p/sam3)
# README #
go library for the I2P [SAMv3.0](https://geti2p.net/en/docs/api/samv3) bridge, used to build anonymous/pseudonymous end-to-end encrypted sockets.
This library is much better than ccondom (that use BOB), much more stable and much easier to maintain.
@@ -29,7 +23,7 @@ This library is much better than ccondom (that use BOB), much more stable and mu
**Does not work:**
* Stream Forwarding
* Everything works! :D
* Probably needs some real-world testing
## Documentation ##
@@ -44,17 +38,16 @@ This library is much better than ccondom (that use BOB), much more stable and mu
package main
import (
"github.com/go-i2p/sam3"
"github.com/go-i2p/sam3/i2pkeys"
"github.com/majestrate/i2p-tools/sam3"
"fmt"
)
const yoursam = "127.0.0.1:7656" // sam bridge
func client(server i2pkeys.I2PAddr) {
sam, _ := sam3.NewSAM(yoursam)
func client(server I2PAddr) {
sam, _ := NewSAM(yoursam)
keys, _ := sam.NewKeys()
stream, _ := sam.NewStreamSession("clientTun", keys, sam3.Options_Small)
stream, _ := sam.NewStreamSession("clientTun", keys, Options_Small)
fmt.Println("Client: Connecting to " + server.Base32())
conn, _ := stream.DialI2P(server)
conn.Write([]byte("Hello world!"))
@@ -64,9 +57,9 @@ func client(server i2pkeys.I2PAddr) {
func main() {
sam, _ := NewSAM(yoursam)
keys, _ := sam.NewKeys()
stream, _ := sam.NewStreamSession("serverTun", keys, sam3.Options_Medium)
listener, _ := stream.Listen()
go client(keys.Addr())
stream, _ := sam.NewStreamSession("serverTun", keys, Options_Medium)
listener, _ := stream.Listen()
conn, _ := listener.Accept()
buf := make([]byte, 4096)
n, _ := conn.Read(buf)
@@ -85,29 +78,9 @@ Error handling was omitted in the above code for readability.
## Testing ##
* `go test -tags=nettest` runs the whole suite (takes 90+ sec to perform!)
* `go test` runs the whole suite (takes 90+ sec to perform!)
* `go test -short` runs the shorter variant, does not connect to anything
## Verbosity ##
Logging can be enabled and configured using the DEBUG_I2P environment variable. By default, logging is disabled.
There are three available log levels:
- Debug
```shell
export DEBUG_I2P=debug
```
- Warn
```shell
export DEBUG_I2P=warn
```
- Error
```shell
export DEBUG_I2P=error
```
If DEBUG_I2P is set to an unrecognized variable, it will fall back to "debug".
## License ##
Public domain.
@@ -115,6 +88,4 @@ Public domain.
## Author ##
* Kalle Vedin `kalle.vedin@fripost.org`
* Unknown Name (majestrate)
* idk
* qiwenmin
* Unknown Name (majestrate)

View File

@@ -3,64 +3,65 @@ package sam3
import (
"net"
"time"
"github.com/go-i2p/i2pkeys"
)
// SAMConn sets up a SAM connection.
import (
. "github.com/eyedeekay/sam3/i2pkeys"
)
// Implements net.Conn
type SAMConn struct {
laddr i2pkeys.I2PAddr
raddr i2pkeys.I2PAddr
net.Conn
laddr I2PAddr
raddr I2PAddr
conn net.Conn
}
// Read implements net.Conn
// Implements net.Conn
func (sc *SAMConn) Read(buf []byte) (int, error) {
n, err := sc.Conn.Read(buf)
n, err := sc.conn.Read(buf)
return n, err
}
// Write Implements net.Conn
// Implements net.Conn
func (sc *SAMConn) Write(buf []byte) (int, error) {
n, err := sc.Conn.Write(buf)
n, err := sc.conn.Write(buf)
return n, err
}
// Close Implements net.Conn
// Implements net.Conn
func (sc *SAMConn) Close() error {
return sc.Conn.Close()
return sc.conn.Close()
}
// LocalAddr Implements net.Conn
func (sc *SAMConn) LocalAddr() net.Addr {
return sc.localAddr()
}
func (sc *SAMConn) localAddr() i2pkeys.I2PAddr {
// Implements net.Conn
func (sc *SAMConn) localAddr() I2PAddr {
return sc.laddr
}
// RemoteAddr Implements net.Conn
func (sc *SAMConn) RemoteAddr() net.Addr {
return sc.remoteAddr()
}
func (sc *SAMConn) remoteAddr() i2pkeys.I2PAddr {
// Implements net.Conn
func (sc *SAMConn) remoteAddr() I2PAddr {
return sc.raddr
}
// SetDeadline Implements net.Conn
// Implements net.Conn
func (sc *SAMConn) SetDeadline(t time.Time) error {
return sc.Conn.SetDeadline(t)
return sc.conn.SetDeadline(t)
}
// SetReadDeadline Implements net.Conn
// Implements net.Conn
func (sc *SAMConn) SetReadDeadline(t time.Time) error {
return sc.Conn.SetReadDeadline(t)
return sc.conn.SetReadDeadline(t)
}
// SetWriteDeadline Implements net.Conn
// Implements net.Conn
func (sc *SAMConn) SetWriteDeadline(t time.Time) error {
return sc.Conn.SetWriteDeadline(t)
return sc.conn.SetWriteDeadline(t)
}

View File

@@ -1,60 +0,0 @@
package common
import (
"fmt"
"strings"
)
type SAMFormatter struct {
Version ProtocolVersion
}
// Common SAM protocol message types
const (
HelloMsg = "HELLO"
SessionMsg = "SESSION"
StreamMsg = "STREAM"
DatagramMsg = "DATAGRAM"
RawMsg = "RAW"
PrimaryMSG = "PRIMARY"
NamingMsg = "NAMING"
)
func NewSAMFormatter(version ProtocolVersion) *SAMFormatter {
return &SAMFormatter{Version: version}
}
// FormatHello formats the initial handshake message
func (f *SAMFormatter) FormatHello() string {
return fmt.Sprintf("HELLO VERSION MIN=%s MAX=%s\n", f.Version, f.Version)
}
// FormatSession formats a session creation message
func (f *SAMFormatter) FormatSession(style, id string, options map[string]string) string {
optStr := formatOptions(options)
return fmt.Sprintf("SESSION CREATE STYLE=%s ID=%s%s\n", style, id, optStr)
}
// FormatDatagram formats a datagram message
func (f *SAMFormatter) FormatDatagram(sessionID, dest string, options map[string]string) string {
optStr := formatOptions(options)
return fmt.Sprintf("DATAGRAM SEND ID=%s DESTINATION=%s%s\n", sessionID, dest, optStr)
}
// FormatNamingLookup formats a naming lookup message
func (f *SAMFormatter) FormatNamingLookup(name string) string {
return fmt.Sprintf("NAMING LOOKUP NAME=%s\n", name)
}
// Helper function to format options
func formatOptions(options map[string]string) string {
if len(options) == 0 {
return ""
}
var opts []string
for k, v := range options {
opts = append(opts, fmt.Sprintf(" %s=%s", k, v))
}
return strings.Join(opts, "")
}

View File

@@ -1,87 +0,0 @@
// common/reply.go
package common
import (
"fmt"
"strings"
)
// Reply represents a parsed SAM bridge response
type Reply struct {
Topic string // e.g., "HELLO", "SESSION", "STREAM", etc.
Type string // Usually "REPLY"
Result string // "OK" or error message
KeyValues map[string]string // Additional key-value pairs in the response
}
// ParseReply parses a raw SAM bridge response into a structured Reply
func ParseReply(response string) (*Reply, error) {
parts := strings.Fields(response)
if len(parts) < 3 {
return nil, fmt.Errorf("invalid reply format: %s", response)
}
reply := &Reply{
Topic: parts[0],
Type: parts[1],
KeyValues: make(map[string]string),
}
// Parse remaining key=value pairs
for _, part := range parts[2:] {
if kv := strings.SplitN(part, "=", 2); len(kv) == 2 {
key := strings.ToUpper(kv[0])
if key == "RESULT" {
reply.Result = kv[1]
} else {
reply.KeyValues[key] = kv[1]
}
}
}
if reply.Result == "" {
return nil, fmt.Errorf("missing RESULT in reply: %s", response)
}
return reply, nil
}
// IsOk returns true if the reply indicates success
func (r *Reply) IsOk() bool {
return r.Result == "OK"
}
// Error returns an error if the reply indicates failure
func (r *Reply) Error() error {
if r.IsOk() {
return nil
}
return fmt.Errorf("%s failed: %s", r.Topic, r.Result)
}
// Value safely retrieves a value from KeyValues
func (r *Reply) Value(key string) (string, bool) {
v, ok := r.KeyValues[strings.ToUpper(key)]
return v, ok
}
// MustValue gets a value or panics if not found
func (r *Reply) MustValue(key string) string {
if v, ok := r.Value(key); ok {
return v
}
panic(fmt.Sprintf("required key not found: %s", key))
}
// Specific reply type checkers
func (r *Reply) IsHello() bool {
return r.Topic == HelloMsg && r.Type == "REPLY"
}
func (r *Reply) IsSession() bool {
return r.Topic == SessionMsg && r.Type == "REPLY"
}
func (r *Reply) IsNaming() bool {
return r.Topic == NamingMsg && r.Type == "REPLY"
}

View File

@@ -1,140 +0,0 @@
package common
import (
"errors"
"fmt"
"net"
"strconv"
"time"
"github.com/go-i2p/logger"
)
// Package common provides shared UDP common functionality for SAM sessions
//
// It handles:
// - UDP port validation and defaults
// - Address resolution
// - Connection setup
// - Logging
//
// Example Usage:
//
// cfg := &UDPSessionConfig{
// Port: 7655,
// ParentConn: samConn,
// Log: logger,
// }
//
// session, err := NewUDPSession(cfg)
// if err != nil {
// // Handle error
// }
// defer session.Close()
// UDPSessionConfig holds all UDP session configuration
type UDPSessionConfig struct {
Port int
ParentConn net.Conn
Log *logger.Logger
DefaultPort int
AllowZeroPort bool
Style string
FromPort string
ToPort string
ReadTimeout time.Duration
WriteTimeout time.Duration
}
// UDPSession represents an established UDP session
type UDPSession struct {
LocalAddr *net.UDPAddr
RemoteAddr *net.UDPAddr
Conn *net.UDPConn
}
func (u *UDPSession) SetReadTimeout(timeout time.Duration) error {
if u.Conn != nil {
return u.Conn.SetReadDeadline(time.Now().Add(timeout))
}
return nil
}
func (u *UDPSession) SetWriteTimeout(timeout time.Duration) error {
if u.Conn != nil {
return u.Conn.SetWriteDeadline(time.Now().Add(timeout))
}
return nil
}
func (u UDPSession) LocalPort() int {
return u.LocalAddr.Port
}
func (u UDPSession) Close() {
u.Conn.Close()
}
// NewUDPSession creates and configures a new UDP session
func NewUDPSession(cfg *UDPSessionConfig) (*UDPSession, error) {
if err := validatePort(cfg.Port, cfg.AllowZeroPort); err != nil {
cfg.Log.WithError(err).Error("Invalid UDP port configuration")
return nil, err
}
port := cfg.Port
if port == 0 {
port = cfg.DefaultPort
cfg.Log.WithField("port", port).Debug("Using default UDP port")
}
laddr, raddr, err := resolveAddresses(cfg.ParentConn, port)
if err != nil {
return nil, fmt.Errorf("address resolution failed: %w", err)
}
conn, err := net.ListenUDP("udp4", laddr)
if err != nil {
return nil, fmt.Errorf("UDP listen failed: %w", err)
}
return &UDPSession{
LocalAddr: laddr,
RemoteAddr: raddr,
Conn: conn,
}, nil
}
func validatePort(port int, allowZero bool) error {
if port < 0 || port > 65535 {
return errors.New("port must be between 0-65535")
}
if port == 0 && !allowZero {
return errors.New("port 0 not allowed in this context")
}
return nil
}
func resolveAddresses(parent net.Conn, remotePort int) (*net.UDPAddr, *net.UDPAddr, error) {
lhost, _, err := net.SplitHostPort(parent.LocalAddr().String())
if err != nil {
return nil, nil, err
}
laddr, err := net.ResolveUDPAddr("udp4", lhost+":0")
if err != nil {
return nil, nil, err
}
rhost, _, err := net.SplitHostPort(parent.RemoteAddr().String())
if err != nil {
return nil, nil, err
}
raddr, err := net.ResolveUDPAddr("udp4", rhost+":"+strconv.Itoa(remotePort))
if err != nil {
return nil, nil, err
}
return laddr, raddr, nil
}

View File

@@ -1,19 +0,0 @@
package common
type ProtocolVersion string
type Version struct {
String ProtocolVersion
Number float64
}
var (
SAM31Version = Version{
String: "3.1",
Number: 3.1,
}
SAM33Version = Version{
String: "3.3",
Number: 3.3,
}
)

268
config.go
View File

@@ -2,228 +2,76 @@ package sam3
import (
"fmt"
"math/rand"
"strings"
"time"
"github.com/sirupsen/logrus"
"github.com/go-i2p/i2pkeys"
"github.com/go-i2p/sam3/common"
"github.com/go-i2p/sam3/config"
"net"
"strconv"
)
const DEFAULT_LEASESET_TYPE = "i2cp.leaseSetEncType=4"
import (
. "github.com/eyedeekay/sam3/i2pkeys"
)
// I2PConfig is a struct which manages I2P configuration options
type I2PConfig struct {
common.SAMFormatter
config.SessionOptions
config.TransportOptions
config.TunnelOptions
config.EncryptedLeaseSetOptions
DestinationKeys i2pkeys.I2PKeys
// sam config
// Streaming Library options
AccessListType string
AccessList []string
// 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
}
// Sam returns the SAM address in the form of "host:port"
func (f *I2PConfig) Sam() string {
host := "127.0.0.1"
port := "7656"
if f.SamHost != "" {
host = f.SamHost
}
if f.SamPort != "" {
port = f.SamPort
}
log.WithFields(logrus.Fields{
"host": host,
"port": port,
}).Debug("SAM address constructed")
return fmt.Sprintf("%s:%s", host, port)
// Config is the config type for the sam connector api for i2p which allows applications to 'speak' with i2p
type Config struct {
Addr string
Opts Options
Session string
Keyfile string
}
// SetSAMAddress sets the SAM address from a string in the form of "host:port"
func (f *I2PConfig) SetSAMAddress(addr string) {
hp := strings.Split(addr, ":")
if len(hp) == 1 {
f.SamHost = hp[0]
} else if len(hp) == 2 {
f.SamPort = hp[1]
f.SamHost = hp[0]
} else {
if f.SamHost == "" {
f.SamHost = "127.0.0.1"
}
if f.SamPort == "" {
f.SamPort = "7656"
// create new sam connector from config with a stream session
func (cfg *Config) StreamSession() (session *StreamSession, err error) {
// connect
var s *SAM
s, err = NewSAM(cfg.Addr)
if err == nil {
// ensure keys exist
var keys I2PKeys
keys, err = s.EnsureKeyfile(cfg.Keyfile)
if err == nil {
// create session
session, err = s.NewStreamSession(cfg.Session, keys, cfg.Opts.AsList())
}
}
log.WithFields(logrus.Fields{
"host": f.SamHost,
"port": f.SamPort,
}).Debug("SAM address set")
i2pkeys.DefaultSAMAddress = f.Sam()
return
}
// ID returns the tunnel name in the form of "ID=name"
func (f *I2PConfig) ID() string {
if f.NickName == "" {
b := make([]byte, 12)
for i := range b {
b[i] = "abcdefghijklmnopqrstuvwxyz"[rand.Intn(len("abcdefghijklmnopqrstuvwxyz"))]
}
f.NickName = string(b)
log.WithField("NickName", f.NickName).Debug("Generated random tunnel name")
}
return fmt.Sprintf(" ID=%s ", f.NickName)
}
// MinSAM returns the minimum SAM version required in major.minor form
func (f *I2PConfig) MinSAM() string {
min, _ := f.GetVersions()
return string(min)
}
// MaxSAM returns the maximum SAM version required in major.minor form
func (f *I2PConfig) MaxSAM() string {
_, max := f.GetVersions()
return string(max)
}
func (f *I2PConfig) GetVersions() (min, max common.ProtocolVersion) {
if f.SamMin == "" {
min = common.SAM31Version.String
} else {
min = common.ProtocolVersion(f.SamMin)
}
if f.SamMax == "" {
max = common.SAM33Version.String
log.Debug("Using default MaxSAM: 3.3")
} else {
max = common.ProtocolVersion(f.SamMax)
}
return min, max
}
// DestinationKey returns the destination key setting in the form of "DESTINATION=key"
func (f *I2PConfig) DestinationKey() string {
if &f.DestinationKeys != nil {
log.WithField("destinationKey", f.DestinationKeys.String()).Debug("Destination key set")
fmt.Sprintf(" DESTINATION=%s ", f.DestinationKeys.String())
}
log.Debug("Using TRANSIENT destination")
return " DESTINATION=TRANSIENT "
}
// Print returns the full config as a string
func (f *I2PConfig) Print() []string {
lsk, lspk, lspsk := f.Leasesetsettings()
return []string{
// f.targetForPort443(),
f.InboundLength(),
f.OutboundLength(),
f.InboundVariance(),
f.OutboundVariance(),
f.InboundBackupQuantity(),
f.OutboundBackupQuantity(),
f.InboundQuantity(),
f.OutboundQuantity(),
f.InboundDoZero(),
f.OutboundDoZero(),
//"i2cp.fastRecieve=" + f.FastRecieve,
f.DoFastReceive(),
f.UsesCompression(),
f.Reduce(),
f.Close(),
f.Reliability(),
f.EncryptLease(),
lsk, lspk, lspsk,
f.Accesslisttype(),
f.Accesslist(),
f.LeaseSetEncryptionType(),
}
}
// Accesslisttype returns the access list type
func (f *I2PConfig) Accesslisttype() string {
if f.AccessListType == "whitelist" {
log.Debug("Access list type set to whitelist")
return "i2cp.enableAccessList=true"
} else if f.AccessListType == "blacklist" {
log.Debug("Access list type set to blacklist")
return "i2cp.enableBlackList=true"
} else if f.AccessListType == "none" {
log.Debug("Access list type set to none")
return ""
}
log.Debug("Access list type not set")
return ""
}
// Accesslist returns the access list in the form of "i2cp.accessList=list"
func (f *I2PConfig) Accesslist() string {
if f.AccessListType != "" && len(f.AccessList) > 0 {
r := strings.Join(f.AccessList, ",")
log.WithField("accessList", r).Debug("Access list generated")
return fmt.Sprintf(" i2cp.accessList=%s ", r)
}
log.Debug("Access list not set")
return ""
}
// NewConfig returns a new config with default values or updates them with functional arguments
func NewConfig(opts ...func(*I2PConfig) error) (*I2PConfig, error) {
config := I2PConfig{
EncryptedLeaseSetOptions: config.EncryptedLeaseSetOptions{
EncryptLeaseSet: false,
LeaseSetKey: "",
LeaseSetPrivateKey: "",
LeaseSetPrivateSigningKey: "",
LeaseSetEncryption: DEFAULT_LEASESET_TYPE,
},
TunnelOptions: config.TunnelOptions{
InAllowZeroHop: false,
OutAllowZeroHop: false,
InLength: 3,
OutLength: 3,
InQuantity: 2,
OutQuantity: 2,
InVariance: 1,
OutVariance: 1,
InBackupQuantity: 3,
OutBackupQuantity: 3,
},
SessionOptions: config.SessionOptions{
NickName: "",
Style: "STREAM",
SigType: "EdDSA_SHA512_Ed25519",
InFromPort: "",
OutToPort: "",
Protocol: "",
UDPPort: 0,
SamHost: "127.0.0.1",
SamPort: "7656",
SamMin: string(common.SAM31Version.String),
SamMax: string(common.SAM33Version.String),
},
TransportOptions: config.TransportOptions{
UseCompression: "true",
FastReceive: "false",
MessageReliability: "none",
CloseIdleTimeout: 5 * time.Minute,
ReduceIdleQuantity: 1,
ReduceIdle: false,
CloseIdle: false,
},
}
for _, o := range opts {
if err := o(&config); err != nil {
return nil, err
// create new sam datagram session from config
func (cfg *Config) DatagramSession() (session *DatagramSession, err error) {
// connect
var s *SAM
s, err = NewSAM(cfg.Addr)
if err == nil {
// ensure keys exist
var keys I2PKeys
keys, err = s.EnsureKeyfile(cfg.Keyfile)
if err == nil {
// determine udp port
var portstr string
_, portstr, err = net.SplitHostPort(cfg.Addr)
if err == nil {
var port int
port, err = strconv.Atoi(portstr)
if err == nil && port > 0 {
// udp port is 1 lower
port--
// create session
session, err = s.NewDatagramSession(cfg.Session, keys, cfg.Opts.AsList(), port)
}
}
}
}
return &config, nil
return
}

View File

@@ -1,83 +0,0 @@
package config
import (
"fmt"
"strconv"
"strings"
"github.com/go-i2p/i2pkeys"
"github.com/sirupsen/logrus"
)
type EncryptedLeaseSetOptions struct {
EncryptLeaseSet bool
LeaseSetKey string
LeaseSetPrivateKey string
LeaseSetPrivateSigningKey string
LeaseSetKeys i2pkeys.I2PKeys
LeaseSetEncryption string
}
// EncryptLease returns the lease set encryption setting in the form of "i2cp.encryptLeaseSet=true"
func (f *EncryptedLeaseSetOptions) EncryptLease() string {
if f.EncryptLeaseSet {
log.Debug("Lease set encryption enabled")
return " i2cp.encryptLeaseSet=true "
}
log.Debug("Lease set encryption not enabled")
return ""
}
// LeaseSetEncryptionType returns the lease set encryption type in the form of "i2cp.leaseSetEncType=type"
func (f *EncryptedLeaseSetOptions) LeaseSetEncryptionType() string {
if f.LeaseSetEncryption == "" {
log.Debug("Using default lease set encryption type: 4,0")
return "i2cp.leaseSetEncType=4,0"
}
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.WithField("leaseSetEncType", f.LeaseSetEncryption).Debug("Lease set encryption type set")
return fmt.Sprintf(" i2cp.leaseSetEncType=%s ", f.LeaseSetEncryption)
}
func (f *EncryptedLeaseSetOptions) leaseSetKey() string {
if f.LeaseSetKey != "" {
return fmt.Sprintf(" i2cp.leaseSetKey=%s ", f.LeaseSetKey)
}
return ""
}
func (f *EncryptedLeaseSetOptions) leaseSetPrivateKey() string {
if f.LeaseSetPrivateKey != "" {
return fmt.Sprintf(" i2cp.leaseSetPrivateKey=%s ", f.LeaseSetPrivateKey)
}
return ""
}
func (f *EncryptedLeaseSetOptions) leaseSetPrivateSigningKey() string {
if f.LeaseSetPrivateSigningKey != "" {
return fmt.Sprintf(" i2cp.leaseSetPrivateSigningKey=%s ", f.LeaseSetPrivateSigningKey)
}
return ""
}
// Leasesetsettings returns the lease set settings in the form of "i2cp.leaseSetKey=key i2cp.leaseSetPrivateKey=key i2cp.leaseSetPrivateSigningKey=key"
func (f *EncryptedLeaseSetOptions) Leasesetsettings() (string, string, string) {
if f.EncryptLeaseSet {
var r, s, t string
r = f.leaseSetKey()
s = f.leaseSetPrivateKey()
t = f.leaseSetPrivateSigningKey()
log.WithFields(logrus.Fields{
"leaseSetKey": r,
"leaseSetPrivateKey": s,
"leaseSetPrivateSigningKey": t,
}).Debug("Lease set settings constructed")
return r, s, t
}
return "", "", ""
}

View File

@@ -1,5 +0,0 @@
package config
import logger "github.com/go-i2p/sam3/log"
var log = logger.GetSAM3Logger()

View File

@@ -1,84 +0,0 @@
package config
import (
"fmt"
"strconv"
"github.com/go-i2p/sam3/common"
)
type SessionOptions struct {
NickName string
Style string
SigType string
InFromPort string
OutToPort string
Protocol string
UDPPort int
SamHost string
SamPort string
SamMin string
SamMax string
}
func (f *SessionOptions) samMax() float64 {
i, err := strconv.Atoi(f.SamMax)
if err != nil {
log.WithError(err).Warn("Failed to parse SamMax, using default 3.1")
return 3.1
}
log.WithField("samMax", float64(i)).Debug("SAM max version parsed")
return float64(i)
}
// SignatureType returns the signature type setting in the form of "SIGNATURE_TYPE=type"
func (f *SessionOptions) SignatureType() string {
if f.samMax() < common.SAM31Version.Number {
log.Debug("SAM version < 3.1, SignatureType not applicable")
return ""
}
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 ""
}
// FromPort returns the from port setting in the form of "FROM_PORT=port"
func (f *SessionOptions) FromPort() string {
if f.samMax() < common.SAM31Version.Number {
log.Debug("SAM version < 3.1, FromPort not applicable")
return ""
}
if f.InFromPort != "0" {
log.WithField("fromPort", f.InFromPort).Debug("FromPort set")
return fmt.Sprintf(" FROM_PORT=%s ", f.InFromPort)
}
log.Debug("FromPort not set")
return ""
}
// ToPort returns the to port setting in the form of "TO_PORT=port"
func (f *SessionOptions) ToPort() string {
if f.samMax() < common.SAM31Version.Number {
log.Debug("SAM version < 3.1, ToPort not applicable")
return ""
}
if f.OutToPort != "0" {
log.WithField("toPort", f.OutToPort).Debug("ToPort set")
return fmt.Sprintf(" TO_PORT=%s ", f.OutToPort)
}
log.Debug("ToPort not set")
return ""
}
// SessionStyle returns the session style setting in the form of "STYLE=style"
func (f *SessionOptions) SessionStyle() string {
if f.Style != "" {
log.WithField("style", f.Style).Debug("Session style set")
return fmt.Sprintf(" STYLE=%s ", f.Style)
}
log.Debug("Using default STREAM style")
return " STYLE=STREAM "
}

View File

@@ -1,95 +0,0 @@
package config
import (
"fmt"
"strconv"
"time"
"github.com/sirupsen/logrus"
)
func boolToStr(b bool) string {
if b {
return "true"
}
return "false"
}
// Add transport options
type TransportOptions struct {
UseCompression string
FastReceive string
MessageReliability string
CloseIdleTimeout time.Duration
CloseIdle bool
ReduceIdleTimeout time.Duration
ReduceIdle bool
ReduceIdleQuantity int
}
func (f *TransportOptions) ReduceOnIdle() string {
return boolToStr(f.ReduceIdle)
}
func (f *TransportOptions) ReduceQuantity() string {
return strconv.Itoa(f.ReduceIdleQuantity)
}
func (f *TransportOptions) CloseOnIdle() string {
return boolToStr(f.CloseIdle)
}
func (f *TransportOptions) DoFastReceive() string {
if f.FastReceive == "true" {
log.Debug("Fast receive enabled")
return " i2cp.fastReceive=true "
}
log.Debug("Fast receive disabled")
return ""
}
// Reliability returns the message reliability setting in the form of "i2cp.messageReliability=reliability"
func (f *TransportOptions) Reliability() string {
if f.MessageReliability != "" {
log.WithField("reliability", f.MessageReliability).Debug("Message reliability set")
return fmt.Sprintf(" i2cp.messageReliability=%s ", f.MessageReliability)
}
log.Debug("Message reliability not set")
return ""
}
// Reduce returns the reduce idle settings in the form of "i2cp.reduceOnIdle=true i2cp.reduceIdleTime=time i2cp.reduceQuantity=quantity"
func (f *TransportOptions) Reduce() string {
if f.ReduceIdle {
log.WithFields(logrus.Fields{
"reduceIdle": f.ReduceIdle,
"reduceIdleTime": f.ReduceIdleTimeout.String(),
"reduceIdleQuantity": f.ReduceIdleQuantity,
}).Debug("Reduce idle settings applied")
return fmt.Sprintf(" i2cp.reduceOnIdle=%s i2cp.reduceIdleTime=%s i2cp.reduceQuantity=%d ", f.ReduceOnIdle(), f.ReduceIdleTimeout.String(), f.ReduceIdleQuantity)
}
log.Debug("Reduce idle settings not applied")
return ""
}
// Close returns the close idle settings in the form of "i2cp.closeOnIdle=true i2cp.closeIdleTime=time"
func (f *TransportOptions) Close() string {
if f.CloseIdle {
log.WithFields(logrus.Fields{
"closeIdle": f.CloseIdle,
"closeIdleTime": f.CloseIdleTimeout.String(),
}).Debug("Close idle settings applied")
return fmt.Sprintf(" i2cp.closeOnIdle=%s i2cp.closeIdleTime=%s ", f.CloseOnIdle(), f.CloseIdleTimeout.String())
}
log.Debug("Close idle settings not applied")
return ""
}
func (f *TransportOptions) UsesCompression() string {
if f.UseCompression == "true" {
log.Debug("Compression enabled")
return " i2cp.useCompression=true "
}
log.Debug("Compression disabled")
return ""
}

View File

@@ -1,69 +0,0 @@
package config
import (
"fmt"
"strconv"
)
type TunnelOptions struct {
InAllowZeroHop bool
OutAllowZeroHop bool
InLength int
OutLength int
InQuantity int
OutQuantity int
InVariance int
OutVariance int
InBackupQuantity int
OutBackupQuantity int
}
func (f *TunnelOptions) InboundDoZero() string {
val := boolToStr(f.InAllowZeroHop)
return fmt.Sprintf(" inbound.allowZeroHop=%s ", val)
}
func (f *TunnelOptions) OutboundDoZero() string {
val := boolToStr(f.OutAllowZeroHop)
return fmt.Sprintf(" outbound.allowZeroHop=%s ", val)
}
func (f *TunnelOptions) InboundLength() string {
val := strconv.Itoa(f.InLength)
return fmt.Sprintf(" inbound.length=%s ", val)
}
func (f *TunnelOptions) OutboundLength() string {
val := strconv.Itoa(f.OutLength)
return fmt.Sprintf(" outbound.length=%s ", val)
}
func (f *TunnelOptions) InboundQuantity() string {
val := strconv.Itoa(f.InQuantity)
return fmt.Sprintf(" inbound.quantity=%s ", val)
}
func (f *TunnelOptions) OutboundQuantity() string {
val := strconv.Itoa(f.OutQuantity)
return fmt.Sprintf(" outbound.quantity=%s ", val)
}
func (f *TunnelOptions) InboundVariance() string {
val := strconv.Itoa(f.InVariance)
return fmt.Sprintf(" inbound.variance=%s ", val)
}
func (f *TunnelOptions) OutboundVariance() string {
val := strconv.Itoa(f.OutVariance)
return fmt.Sprintf(" outbound.variance=%s ", val)
}
func (f *TunnelOptions) InboundBackupQuantity() string {
val := strconv.Itoa(f.InBackupQuantity)
return fmt.Sprintf(" inbound.backupQuantity=%s ", val)
}
func (f *TunnelOptions) OutboundBackupQuantity() string {
val := strconv.Itoa(f.OutBackupQuantity)
return fmt.Sprintf(" outbound.backupQuantity=%s ", val)
}

View File

@@ -3,15 +3,13 @@ package sam3
import (
"bytes"
"errors"
"fmt"
"net"
"sync"
"strconv"
"time"
)
"github.com/sirupsen/logrus"
"github.com/go-i2p/i2pkeys"
"github.com/go-i2p/sam3/common"
import (
. "github.com/eyedeekay/sam3/i2pkeys"
)
// The DatagramSession implements net.PacketConn. It works almost like ordinary
@@ -19,160 +17,83 @@ import (
// also end-to-end encrypted, signed and includes replay-protection. And they
// are also built to be surveillance-resistant (yey!).
type DatagramSession struct {
samAddr string // address to the sam bridge (ipv4:port)
id string // tunnel name
conn net.Conn // connection to sam bridge
keys i2pkeys.I2PKeys // i2p destination keys
remoteAddr *i2pkeys.I2PAddr // optional remote I2P address
common.UDPSession
*DatagramOptions
samAddr string // address to the sam bridge (ipv4:port)
id string // tunnel name
conn net.Conn // connection to sam bridge
udpconn *net.UDPConn // used to deliver datagrams
keys I2PKeys // i2p destination keys
rUDPAddr *net.UDPAddr // the SAM bridge UDP-port
}
// 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, datagramOptions ...DatagramOptions) (*DatagramSession, error) {
log.WithFields(logrus.Fields{
"id": id,
"udpPort": udpPort,
}).Debug("Creating new DatagramSession")
udpSessionConfig := &common.UDPSessionConfig{
Port: udpPort,
ParentConn: s.conn,
Log: log,
DefaultPort: 7655,
AllowZeroPort: true,
// Add required session parameters
Style: "DATAGRAM",
FromPort: "0", // Allow dynamic port assignment
ToPort: "0",
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
func (s *SAM) NewDatagramSession(id string, keys I2PKeys, options []string, udpPort int) (*DatagramSession, error) {
if udpPort > 65335 || udpPort < 0 {
return nil, errors.New("udpPort needs to be in the intervall 0-65335")
}
udpconn, err := common.NewUDPSession(udpSessionConfig)
if err != nil {
log.WithError(err).Error("Failed to create UDP session")
return nil, err
if udpPort == 0 {
udpPort = 7655
}
_, lport, err := net.SplitHostPort(udpconn.Conn.LocalAddr().String())
lhost, _, err := net.SplitHostPort(s.Conn.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, options, []string{" PORT=" + lport})
lUDPAddr, err := net.ResolveUDPAddr("udp4", lhost+":0")
if err != nil {
log.WithError(err).Error("Failed to create generic session")
return nil, err
}
if len(datagramOptions) > 0 {
return &DatagramSession{
samAddr: s.address,
id: id,
conn: conn,
keys: keys,
UDPSession: *udpconn,
DatagramOptions: &datagramOptions[0],
}, nil
udpconn, err := net.ListenUDP("udp4", lUDPAddr)
if err != nil {
return nil, err
}
log.WithField("id", id).Info("DatagramSession created successfully")
// return &DatagramSession{s.address, id, conn, udpconn, keys, rUDPAddr, nil, nil}, nil
return &DatagramSession{
samAddr: s.address,
id: id,
conn: conn,
keys: keys,
UDPSession: *udpconn,
}, nil
rhost, _, err := net.SplitHostPort(s.Conn.RemoteAddr().String())
if err != nil {
s.Close()
return nil, err
}
rUDPAddr, err := net.ResolveUDPAddr("udp4", rhost+":"+strconv.Itoa(udpPort))
if err != nil {
return nil, err
}
_, lport, err := net.SplitHostPort(udpconn.LocalAddr().String())
conn, err := s.newGenericSession("DATAGRAM", id, keys, options, []string{"PORT=" + lport})
if err != nil {
return nil, err
}
return &DatagramSession{s.Config.I2PConfig.Sam(), id, conn, udpconn, keys, rUDPAddr}, nil
}
func (s *DatagramSession) B32() string {
b32 := s.keys.Addr().Base32()
log.WithField("b32", b32).Debug("Generated B32 address")
return b32
}
func (s *DatagramSession) Dial(net, 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.remoteAddr = addr.(*i2pkeys.I2PAddr)
case i2pkeys.I2PAddr:
i2paddr := addr.(i2pkeys.I2PAddr)
s.remoteAddr = &i2paddr
}
return s, nil
}
func (s *DatagramSession) RemoteAddr() net.Addr {
log.WithField("remoteAddr", s.remoteAddr).Debug("Getting remote address")
return s.remoteAddr
return s.keys.Addr().Base32()
}
// 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")
// Use sync.Pool for buffers
bufPool := sync.Pool{
New: func() interface{} {
return make([]byte, len(b)+4096)
},
}
buf := bufPool.Get().([]byte)
defer bufPool.Put(buf)
// 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.UDPSession.Conn.ReadFromUDP(buf)
n, saddr, err = s.udpconn.ReadFromUDP(buf)
if err != nil {
log.WithError(err).Error("Failed to read from UDP")
return 0, i2pkeys.I2PAddr(""), err
return 0, I2PAddr(""), err
}
if bytes.Equal(saddr.IP, s.UDPSession.RemoteAddr.IP) {
if bytes.Equal(saddr.IP, s.rUDPAddr.IP) {
continue
}
break
}
i := bytes.IndexByte(buf, byte(' '))
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.")
return 0, I2PAddr(""), errors.New("Could not parse incomming message remote address.")
}
raddr, err := i2pkeys.NewI2PAddrFromString(string(buf[:i]))
raddr, err := 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())
return 0, I2PAddr(""), errors.New("Could not parse incomming message remote address: " + string(buf[:i]) + err.Error())
}
// shift out the incomming address to contain only the data received
if (n - i + 1) > len(b) {
@@ -180,154 +101,33 @@ func (s *DatagramSession) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
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
}
const (
MAX_DATAGRAM_SIZE = 31744 // Max reliable size
RECOMMENDED_SIZE = 11264 // 11KB recommended max
)
// Sends one signed datagram to the destination specified. At the time of
// writing, maximum size is 31 kilobyte, but this may change in the future.
// Implements net.PacketConn.
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")
if len(b) > MAX_DATAGRAM_SIZE {
return 0, errors.New("datagram exceeds maximum size")
}
// Use chunking for anything above recommended size
if len(b) > RECOMMENDED_SIZE {
return s.writeChunked(b, addr)
}
// Single message path
if s.DatagramOptions != nil {
return s.writeToWithOptions(b, addr.(i2pkeys.I2PAddr))
}
header := []byte(fmt.Sprintf("3.1 %s %s\n", s.id, addr.(i2pkeys.I2PAddr).String()))
header := []byte("3.0 " + s.id + " " + addr.String() + "\n")
msg := append(header, b...)
n, err = s.UDPSession.Conn.WriteToUDP(msg, s.UDPSession.RemoteAddr)
if err != nil {
log.WithError(err).Error("Failed to write to UDP")
} else {
log.WithField("bytesWritten", n).Debug("Datagram written successfully")
}
n, err = s.udpconn.WriteToUDP(msg, s.rUDPAddr)
return n, err
}
func (s *DatagramSession) writeChunked(b []byte, addr net.Addr) (total int, err error) {
chunkSize := RECOMMENDED_SIZE - 256 // Allow for header overhead
chunks := (len(b) + chunkSize - 1) / chunkSize
log.WithFields(logrus.Fields{
"totalSize": len(b),
"chunks": chunks,
}).Debug("Splitting datagram into chunks")
for i := 0; i < chunks; i++ {
start := i * chunkSize
end := start + chunkSize
if end > len(b) {
end = len(b)
}
chunk := b[start:end]
var n int
// Single write path that handles both cases
if s.DatagramOptions != nil {
n, err = s.writeToWithOptions(chunk, addr.(i2pkeys.I2PAddr))
} else {
header := []byte(fmt.Sprintf("3.1 %s %s %d %d\n", s.id, addr.(i2pkeys.I2PAddr).String(), i, chunks))
msg := append(header, chunk...)
n, err = s.UDPSession.Conn.WriteToUDP(msg, s.UDPSession.RemoteAddr)
}
if err != nil {
return total, fmt.Errorf("chunk %d/%d failed: %w", i+1, chunks, err)
}
total += n
if i < chunks-1 {
time.Sleep(50 * time.Millisecond)
}
}
return total, nil
}
type DatagramOptions struct {
SendTags int
TagThreshold int
Expires int
SendLeaseset bool
}
func (s *DatagramSession) writeToWithOptions(b []byte, addr i2pkeys.I2PAddr) (n int, err error) {
header := []byte(fmt.Sprintf("3.3 %s %s", s.id, addr.String()))
if s.DatagramOptions != nil {
if s.DatagramOptions.SendTags > 0 {
header = append(header, []byte(fmt.Sprintf(" SEND_TAGS=%d", s.DatagramOptions.SendTags))...)
}
if s.DatagramOptions.TagThreshold > 0 {
header = append(header, []byte(fmt.Sprintf(" TAG_THRESHOLD=%d", s.DatagramOptions.TagThreshold))...)
}
if s.DatagramOptions.Expires > 0 {
header = append(header, []byte(fmt.Sprintf(" EXPIRES=%d", s.DatagramOptions.Expires))...)
}
if s.DatagramOptions.SendLeaseset {
header = append(header, []byte(" SEND_LEASESET=true")...)
}
}
header = append(header, '\n')
msg := append(header, b...)
return s.UDPSession.Conn.WriteToUDP(msg, s.UDPSession.RemoteAddr)
}
func (s *DatagramSession) Write(b []byte) (int, error) {
log.WithField("dataLen", len(b)).Debug("Writing to DatagramSession")
return s.WriteTo(b, s.remoteAddr)
}
// Closes the DatagramSession. Implements net.PacketConn
func (s *DatagramSession) Close() error {
log.Debug("Closing DatagramSession")
err := s.conn.Close()
err2 := s.UDPSession.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.keys.Addr()
log.WithField("localI2PAddr", addr).Debug("Getting local I2P address")
return addr
func (s *DatagramSession) LocalI2PAddr() I2PAddr {
return s.keys.Addr()
}
// Implements net.PacketConn
@@ -335,19 +135,13 @@ 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 *SAM
sam, err = NewSAM(s.samAddr)
if err == nil {
defer sam.Close()
a, err = sam.Lookup(name)
}
log.WithField("address", a).Debug("Lookup successful")
return
}
@@ -355,23 +149,15 @@ func (s *DatagramSession) Lookup(name string) (a net.Addr, err error) {
// 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.UDPSession.Conn.SetDeadline(t)
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.UDPSession.Conn.SetReadDeadline(t)
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.UDPSession.Conn.SetWriteDeadline(t)
}
func (s *DatagramSession) SetWriteBuffer(bytes int) error {
log.WithField("bytes", bytes).Debug("Setting write buffer")
return s.UDPSession.Conn.SetWriteBuffer(bytes)
return s.udpconn.SetWriteDeadline(t)
}

View File

@@ -1,11 +1,11 @@
// +build nettest
package sam3
import (
"fmt"
"testing"
"time"
sam3opts "github.com/go-i2p/sam3/opts"
)
func Test_DatagramServerClient(t *testing.T) {
@@ -27,7 +27,7 @@ func Test_DatagramServerClient(t *testing.T) {
}
// fmt.Println("\tServer: My address: " + keys.Addr().Base32())
fmt.Println("\tServer: Creating tunnel")
ds, err := sam.NewDatagramSession("DGserverTun", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"}, 0)
ds, err := sam.NewDatagramSession("DGserverTun", keys, []string{"inbound.length=1", "outbound.length=1", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"}, 0)
if err != nil {
fmt.Println("Server: Failed to create tunnel: " + err.Error())
t.Fail()
@@ -47,14 +47,14 @@ func Test_DatagramServerClient(t *testing.T) {
return
}
fmt.Println("\tClient: Creating tunnel")
ds2, err := sam2.NewDatagramSession("DGclientTun", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"}, 0)
ds2, err := sam2.NewDatagramSession("DGclientTun", keys, []string{"inbound.length=1", "outbound.length=1", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"}, 0)
if err != nil {
c <- false
return
}
defer ds2.Close()
// fmt.Println("\tClient: Servers address: " + ds.LocalAddr().Base32())
// fmt.Println("\tClient: Clients address: " + ds2.LocalAddr().Base32())
fmt.Println("\tClient: Servers address: " + ds.B32())
fmt.Println("\tClient: Clients address: " + ds2.B32())
fmt.Println("\tClient: Tries to send datagram to server")
for {
select {
@@ -104,7 +104,7 @@ func ExampleDatagramSession() {
myself := keys.Addr()
// See the example Option_* variables.
dg, err := sam.NewDatagramSession("DGTUN", keys, sam3opts.Options_Small, 0)
dg, err := sam.NewDatagramSession("DGTUN", keys, Options_Small, 0)
if err != nil {
fmt.Println(err.Error())
return
@@ -124,61 +124,9 @@ func ExampleDatagramSession() {
fmt.Println(err.Error())
return
}
fmt.Println("Got message: '" + string(buf[:n]) + "'")
fmt.Println("Got message: " + string(buf[:n]))
return
// Output:
// Got message: Hello myself!
}
func ExampleMiniDatagramSession() {
// Creates a new DatagramSession, which behaves just like a net.PacketConn.
const samBridge = "127.0.0.1:7656"
sam, err := NewSAM(samBridge)
if err != nil {
fmt.Println(err.Error())
return
}
keys, err := sam.NewKeys()
if err != nil {
fmt.Println(err.Error())
return
}
myself := keys.Addr()
// See the example Option_* variables.
dg, err := sam.NewDatagramSession("MINIDGTUN", keys, sam3opts.Options_Small, 0)
if err != nil {
fmt.Println(err.Error())
return
}
someone, err := sam.Lookup("zzz.i2p")
if err != nil {
fmt.Println(err.Error())
return
}
err = dg.SetWriteBuffer(14 * 1024)
if err != nil {
fmt.Println(err.Error())
return
}
dg.WriteTo([]byte("Hello stranger!"), someone)
dg.WriteTo([]byte("Hello myself!"), myself)
buf := make([]byte, 31*1024)
n, _, err := dg.ReadFrom(buf)
if err != nil {
fmt.Println(err.Error())
return
}
log.Println("Got message: '" + string(buf[:n]) + "'")
fmt.Println("Got message: " + string(buf[:n]))
return
// Output:
// Got message: Hello myself!
//Got message: Hello myself!
}

53
debian/changelog vendored
View File

@@ -1,56 +1,3 @@
golang-github-eyedeekay-sam3 (0.3.2.32) unreleased; urgency=medium
[ idk ]
* Include "Convenience Functions" in /helper
-- idk <hankhill19580@gmail.com> Sun, 22 Nov 2020 12:49:13 -0500
golang-github-eyedeekay-sam3 (0.3.2.31) unreleased; urgency=medium
[ idk ]
* Incorporate address fix from qiwenmin
* Shortcut for saving keys to text file
-- idk <hankhill19580@gmail.com> Sun, 18 Oct 2020 1:01:03 -0500
golang-github-eyedeekay-sam3 (0.3.2.3) unreleased; urgency=medium
[ idk ]
* Purge ramp, re-release
-- idk <hankhill19580@gmail.com> Mon, 22 Jun 2020 17:55:03 -0500
golang-github-eyedeekay-sam3 (0.3.2.2) bionic; urgency=medium
[ idk ]
* Purge ramp
-- idk <hankhill19580@gmail.com> Sat, 7 Dec 2019 22:05:34 -0500
golang-github-eyedeekay-sam3 (0.3.2.1) bionic; urgency=medium
[ idk ]
* Add support for 44-character destination hashes.
-- idk <hankhill19580@gmail.com> Sat, 7 Dec 2019 17:30:30 -0500
golang-github-eyedeekay-sam3 (0.3.2.01) bionic; urgency=medium
[ idk ]
* completely remove the old i2pkeys version and replace it with the new one.
-- idk <hankhill19580@gmail.com> Sat, 25 May 2019 14:38:11 -0500
golang-github-eyedeekay-sam3 (0.3.2.0) bionic; urgency=medium
[ idk ]
* Bug fixes, create stable branch
* Move i2pkeys
-- idk <hankhill19580@gmail.com> Sat, 18 May 2019 18:32:51 -0500
golang-github-eyedeekay-sam3 (0.0~git20190223.af5a3f3) bionic; urgency=medium
[ idk ]

6
debian/control vendored
View File

@@ -5,8 +5,7 @@ Maintainer: Debian Go Packaging Team <team+pkg-go@tracker.debian.org>
Uploaders: idk <hankhill19580@gmail.com>
Build-Depends: debhelper (>= 11),
dh-golang,
git,
golang-any | golang-go,
golang-any
Standards-Version: 4.2.1
Homepage: https://github.com/eyedeekay/sam3
Vcs-Browser: https://github.com/eyedeekay/sam3
@@ -16,8 +15,7 @@ Testsuite: autopkgtest-pkg-go
Package: golang-github-eyedeekay-sam3-dev
Architecture: all
Depends: ${misc:Depends},
i2p | i2pd
Depends: ${misc:Depends}, (i2p | i2pd)
Description: Go library for the I2P SAMv3.0 bridge,
used to build anonymous/pseudonymous end-to-end encrypted sockets.
README go library for the I2P SAMv3.0

1
debian/files vendored
View File

@@ -1 +0,0 @@
golang-github-eyedeekay-sam3_0.3.2.32_source.buildinfo devel optional

View File

@@ -1,414 +0,0 @@
package sam3
import (
"fmt"
"strconv"
"strings"
"time"
"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 {
c.I2PConfig.SamPort = 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 = s
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.SessionOptions.NickName = 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 {
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.TransportOptions.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 {
c.I2PConfig.InAllowZeroHop = b
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 {
c.I2PConfig.OutAllowZeroHop = b
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.TransportOptions.FastReceive = "true"
return nil
}
c.I2PConfig.TransportOptions.FastReceive = "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 {
c.I2PConfig.ReduceIdle = b
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.TransportOptions.ReduceIdleTimeout = 300000
if u >= 6 {
idleTime := (u * 60) * 1000
c.I2PConfig.TransportOptions.ReduceIdleTimeout = time.Duration(idleTime)
log.WithFields(logrus.Fields{
"minutes": u,
"milliseconds": 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.TransportOptions.ReduceIdleTimeout = 300000
if u >= 300000 {
c.I2PConfig.TransportOptions.ReduceIdleTimeout = time.Duration(u)
log.WithField("reduceIdleTimeMs", u).Debug("Set reduce idle time in milliseconds")
return nil
}
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 {
c.I2PConfig.CloseIdle = b
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.TransportOptions.CloseIdleTimeout = 300000
if u >= 6 {
idleTime := (u * 60) * 1000
c.I2PConfig.TransportOptions.CloseIdleTimeout = time.Duration(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.TransportOptions.CloseIdleTimeout = 300000
if u >= 300000 {
c.I2PConfig.TransportOptions.CloseIdleTimeout = time.Duration(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
}
}

119
emit.go
View File

@@ -1,119 +0,0 @@
package sam3
import (
"fmt"
"net"
"strings"
"github.com/sirupsen/logrus"
)
type SAMEmit struct {
I2PConfig
}
func (e *SAMEmit) OptStr() 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) 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
}
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
}

13
go.mod
View File

@@ -1,13 +0,0 @@
module github.com/go-i2p/sam3
go 1.23.3
require (
github.com/go-i2p/i2pkeys v0.33.92
github.com/go-i2p/logger v0.0.0-20241123010126-3050657e5d0c
github.com/sirupsen/logrus v1.9.3
)
require golang.org/x/sys v0.27.0 // indirect
replace github.com/go-i2p/i2pkeys v0.33.92 => ../i2pkeys

18
go.sum
View File

@@ -1,18 +0,0 @@
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/logger v0.0.0-20241123010126-3050657e5d0c h1:VTiECn3dFEmUlZjto+wOwJ7SSJTHPLyNprQMR5HzIMI=
github.com/go-i2p/logger v0.0.0-20241123010126-3050657e5d0c/go.mod h1:te7Zj3g3oMeIl8uBXAgO62UKmZ6m6kHRNg1Mm+X8Hzk=
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/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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=

188
i2pkeys/I2PAddr.go Normal file
View File

@@ -0,0 +1,188 @@
package i2pkeys
import (
"bytes"
"crypto/sha256"
"encoding/base32"
"encoding/base64"
"errors"
"io"
"strings"
)
var (
i2pB64enc *base64.Encoding = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-~")
i2pB32enc *base32.Encoding = base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567")
)
// The public and private keys associated with an I2P destination. I2P hides the
// details of exactly what this is, so treat them as blobs, but generally: One
// pair of DSA keys, one pair of ElGamal keys, and sometimes (almost never) also
// a certificate. String() returns you the full content of I2PKeys and Addr()
// returns the public keys.
type I2PKeys struct {
addr I2PAddr // only the public key
both string // both public and private keys
}
// Creates I2PKeys from an I2PAddr and a public/private keypair string (as
// generated by String().)
func NewKeys(addr I2PAddr, both string) I2PKeys {
return I2PKeys{addr, both}
}
// load keys from non standard format
func LoadKeysIncompat(r io.Reader) (k I2PKeys, err error) {
var buff bytes.Buffer
_, err = io.Copy(&buff, r)
if err == nil {
parts := strings.Split(buff.String(), "\n")
k = I2PKeys{I2PAddr(parts[0]), parts[1]}
}
return
}
// store keys in non standard format
func StoreKeysIncompat(k I2PKeys, w io.Writer) (err error) {
_, err = io.WriteString(w, k.addr.Base64()+"\n"+k.both)
return
}
// Returns the public keys of the I2PKeys.
func (k I2PKeys) Addr() I2PAddr {
return k.addr
}
// Returns the keys (both public and private), in I2Ps base64 format. Use this
// when you create sessions.
func (k I2PKeys) String() string {
return k.both
}
// I2PAddr represents an I2P destination, almost equivalent to an IP address.
// This is the humongously huge base64 representation of such an address, which
// really is just a pair of public keys and also maybe a certificate. (I2P hides
// the details of exactly what it is. Read the I2P specifications for more info.)
type I2PAddr string
// an i2p destination hash, the .b32.i2p address if you will
type I2PDestHash [32]byte
// create a desthash from a string b32.i2p address
func DestHashFromString(str string) (dhash I2PDestHash, err error) {
if strings.HasSuffix(str, ".b32.i2p") && len(str) == 60 {
// valid
_, err = i2pB32enc.Decode(dhash[:], []byte(str[:52]+"===="))
} else {
// invalid
err = errors.New("invalid desthash format")
}
return
}
// get string representation of i2p dest hash
func (h I2PDestHash) String() string {
b32addr := make([]byte, 56)
i2pB32enc.Encode(b32addr, h[:])
return string(b32addr[:52]) + ".b32.i2p"
}
// Returns "I2P"
func (h *I2PDestHash) Network() string {
return "I2P"
}
// Returns the base64 representation of the I2PAddr
func (a I2PAddr) Base64() string {
return string(a)
}
// Returns the I2P destination (base64-encoded)
func (a I2PAddr) String() string {
return string(a)
}
// Returns "I2P"
func (a I2PAddr) Network() string {
return "I2P"
}
// Creates a new I2P address from a base64-encoded string. Checks if the address
// addr is in correct format. (If you know for sure it is, use I2PAddr(addr).)
func NewI2PAddrFromString(addr string) (I2PAddr, error) {
if strings.HasSuffix(addr, ".i2p") {
if strings.HasSuffix(addr, ".b32.i2p") {
return I2PAddr(""), errors.New("cannot convert .b32.i2p to full destination")
}
// strip off .i2p if it's there
addr = addr[:len(addr)-4]
}
addr = strings.Trim(addr, "\t\n\r\f ")
// very basic check
if len(addr) > 4096 || len(addr) < 516 {
return I2PAddr(""), errors.New("Not an I2P address")
}
buf := make([]byte, i2pB64enc.DecodedLen(len(addr)))
if _, err := i2pB64enc.Decode(buf, []byte(addr)); err != nil {
return I2PAddr(""), errors.New("Address is not base64-encoded")
}
return I2PAddr(addr), nil
}
func FiveHundredAs() I2PAddr {
s := ""
for x := 0; x < 517; x++ {
s += "A"
}
r, _ := NewI2PAddrFromString(s)
return r
}
// Creates a new I2P address from a byte array. The inverse of ToBytes().
func NewI2PAddrFromBytes(addr []byte) (I2PAddr, error) {
if len(addr) > 4096 || len(addr) < 384 {
return I2PAddr(""), errors.New("Not an I2P address")
}
buf := make([]byte, i2pB64enc.EncodedLen(len(addr)))
i2pB64enc.Encode(buf, addr)
return I2PAddr(string(buf)), nil
}
// Turns an I2P address to a byte array. The inverse of NewI2PAddrFromBytes().
func (addr I2PAddr) ToBytes() ([]byte, error) {
buf := make([]byte, i2pB64enc.DecodedLen(len(addr)))
if _, err := i2pB64enc.Decode(buf, []byte(addr)); err != nil {
return buf, errors.New("Address is not base64-encoded")
}
return buf, nil
}
func (addr I2PAddr) Bytes() []byte {
b, _ := addr.ToBytes()
return b
}
// Returns the *.b32.i2p address of the I2P address. It is supposed to be a
// somewhat human-manageable 64 character long pseudo-domain name equivalent of
// the 516+ characters long default base64-address (the I2PAddr format). It is
// not possible to turn the base32-address back into a usable I2PAddr without
// performing a Lookup(). Lookup only works if you are using the I2PAddr from
// which the b32 address was generated.
func (addr I2PAddr) Base32() (str string) {
return addr.DestHash().String()
}
func (addr I2PAddr) DestHash() (h I2PDestHash) {
hash := sha256.New()
b, _ := addr.ToBytes()
hash.Write(b)
digest := hash.Sum(nil)
copy(h[:], digest)
return
}
// Makes any string into a *.b32.i2p human-readable I2P address. This makes no
// sense, unless "anything" is an I2P destination of some sort.
func Base32(anything string) string {
return I2PAddr(anything).Base32()
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -1,331 +0,0 @@
<html>
<head>
<title>
README
</title>
<meta name="author" content="eyedeekay" />
<meta name="description" content="sam3" />
<meta name="keywords" content="master" />
<link rel="stylesheet" type="text/css" href="style.css" />
<link rel="stylesheet" type="text/css" href="showhider.css" />
</head>
<body>
<div id="navbar">
<a href="#shownav">
Show navigation
</a>
<div id="shownav">
<div id="hidenav">
<ul>
<li>
<a href="index.html">
index
</a>
</li>
</ul>
<br>
<a href="#hidenav">
Hide Navigation
</a>
</div>
</div>
</div>
<h1>
<a href="#readme" rel="nofollow">
<span></span>
</a>
README
</h1>
<h2>
<a href="#important" rel="nofollow">
<span></span>
</a>
!!IMPORTANT!!
</h2>
<p>
In the next version, I&#39;ll be moving the
<code>
i2pkeys
</code>
directory to it&#39;s own repository
so I can avoid import cycle headaches. Please migrate to the new
<code>
i2pkeys
</code>
repository
before upgrading your sam3 dependencies. You can probably do this by running:
</p>
<div>
<pre>find . -name &#39;*.go&#39; -exec sed -i &#39;s|github.com/eyedeekay/sam3/i2pkeys|github.com/eyedeekay/i2pkeys|g&#39; {} \;
</pre>
</div>
<p>
STATUS: This project is maintained. I will respond to issues, pull requests, and feature requests within a few days.
</p>
<h1>
<a href="#readme" rel="nofollow">
<span></span>
</a>
README
</h1>
<p>
go library for the I2P
<a href="https://geti2p.net/en/docs/api/samv3" rel="nofollow">
SAMv3.0
</a>
bridge, used to build anonymous/pseudonymous end-to-end encrypted sockets.
</p>
<p>
This library is much better than ccondom (that use BOB), much more stable and much easier to maintain.
</p>
<h2>
<a href="#support-todo" rel="nofollow">
<span></span>
</a>
Support/TODO
</h2>
<p>
<strong>
What works:
</strong>
</p>
<ul>
<li>
Utils
<ul>
<li>
Resolving domain names to I2P destinations
</li>
<li>
.b32.i2p hashes
</li>
<li>
Generating keys/i2p destinations
</li>
</ul>
</li>
<li>
Streaming
<ul>
<li>
DialI2P() - Connecting to stuff in I2P
</li>
<li>
Listen()/Accept() - Handling incomming connections
</li>
<li>
Implements net.Conn and net.Listener
</li>
</ul>
</li>
<li>
Datagrams
<ul>
<li>
Implements net.PacketConn
</li>
</ul>
</li>
<li>
Raw datagrams
<ul>
<li>
Like datagrams, but without addresses
</li>
</ul>
</li>
</ul>
<p>
<strong>
Does not work:
</strong>
</p>
<ul>
<li>
Stream Forwarding
</li>
<li>
Probably needs some real-world testing
</li>
</ul>
<h2>
<a href="#documentation" rel="nofollow">
<span></span>
</a>
Documentation
</h2>
<ul>
<li>
Latest version-documentation:
<ul>
<li>
set your GOPATH
</li>
<li>
Enter
<code>
godoc -http=:8081
</code>
into your terminal and hit enter.
</li>
<li>
Goto
<a href="http://localhost:8081" rel="nofollow">
http://localhost:8081
</a>
, click packages, and navigate to sam3
</li>
</ul>
</li>
</ul>
<h2>
<a href="#examples" rel="nofollow">
<span></span>
</a>
Examples
</h2>
<div>
<pre>package main
import (
&#34;github.com/eyedeekay/sam3&#34;
&#34;github.com/eyedeekay/sam3/i2pkeys&#34;
&#34;fmt&#34;
)
const yoursam = &#34;127.0.0.1:7656&#34; // sam bridge
func client(server i2pkeys.I2PAddr) {
sam, _ := sam3.NewSAM(yoursam)
keys, _ := sam.NewKeys()
stream, _ := sam.NewStreamSession(&#34;clientTun&#34;, keys, sam3.Options_Small)
fmt.Println(&#34;Client: Connecting to &#34; + server.Base32())
conn, _ := stream.DialI2P(server)
conn.Write([]byte(&#34;Hello world!&#34;))
return
}
func main() {
sam, _ := NewSAM(yoursam)
keys, _ := sam.NewKeys()
stream, _ := sam.NewStreamSession(&#34;serverTun&#34;, keys, sam3.Options_Medium)
listener, _ := stream.Listen()
go client(keys.Addr())
conn, _ := listener.Accept()
buf := make([]byte, 4096)
n, _ := conn.Read(buf)
fmt.Println(&#34;Server received: &#34; + string(buf[:n]))
}
</pre>
</div>
<p>
The above will write to the terminal:
</p>
<div>
<pre>Client: Connecting to zjnvfh4hs3et5vtz35ogwzrws26zvwkcad5uo5esecvg4qpk5b4a.b32.i2p
Server received: Hello world!
</pre>
</div>
<p>
Error handling was omitted in the above code for readability.
</p>
<h2>
<a href="#testing" rel="nofollow">
<span></span>
</a>
Testing
</h2>
<ul>
<li>
<code>
go test -tags=nettest
</code>
runs the whole suite (takes 90+ sec to perform!)
</li>
<li>
<code>
go test -short
</code>
runs the shorter variant, does not connect to anything
</li>
</ul>
<h2>
<a href="#license" rel="nofollow">
<span></span>
</a>
License
</h2>
<p>
Public domain.
</p>
<h2>
<a href="#author" rel="nofollow">
<span></span>
</a>
Author
</h2>
<ul>
<li>
Kalle Vedin
<code>
kalle.vedin@fripost.org
</code>
</li>
<li>
Unknown Name (majestrate)
</li>
<li>
idk
</li>
<li>
qiwenmin
</li>
</ul>
<div>
<a href="#show">
Show license
</a>
<div id="show">
<div id="hide">
<pre><code>This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
</code></pre>
<a href="#hide">
Hide license
</a>
</div>
</div>
</div>
<div>
<iframe src="https://snowflake.torproject.org/embed.html" width="320" height="240" frameborder="0" scrolling="no"></iframe>
</div>
<div>
<a href="https://geti2p.net/">
<img src="i2plogo.png"></img>
I2P
</a>
</div>
</body>
</html>

5
log.go
View File

@@ -1,5 +0,0 @@
package sam3
import logger "github.com/go-i2p/sam3/log"
var log = logger.GetSAM3Logger()

View File

@@ -1,123 +0,0 @@
package sam3opts
import (
"net"
"os"
"strings"
logger "github.com/go-i2p/sam3/log"
"github.com/sirupsen/logrus"
)
var log = logger.GetSAM3Logger()
// Examples and suggestions for options when creating sessions.
var (
// Suitable options if you are shuffling A LOT of traffic. If unused, this
// will waste your resources.
Options_Humongous = []string{
"inbound.length=3", "outbound.length=3",
"inbound.lengthVariance=1", "outbound.lengthVariance=1",
"inbound.backupQuantity=3", "outbound.backupQuantity=3",
"inbound.quantity=6", "outbound.quantity=6",
}
// Suitable for shuffling a lot of traffic.
Options_Large = []string{
"inbound.length=3", "outbound.length=3",
"inbound.lengthVariance=1", "outbound.lengthVariance=1",
"inbound.backupQuantity=1", "outbound.backupQuantity=1",
"inbound.quantity=4", "outbound.quantity=4",
}
// Suitable for shuffling a lot of traffic quickly with minimum
// anonymity. Uses 1 hop and multiple tunnels.
Options_Wide = []string{
"inbound.length=1", "outbound.length=1",
"inbound.lengthVariance=1", "outbound.lengthVariance=1",
"inbound.backupQuantity=2", "outbound.backupQuantity=2",
"inbound.quantity=3", "outbound.quantity=3",
}
// Suitable for shuffling medium amounts of traffic.
Options_Medium = []string{
"inbound.length=3", "outbound.length=3",
"inbound.lengthVariance=1", "outbound.lengthVariance=1",
"inbound.backupQuantity=0", "outbound.backupQuantity=0",
"inbound.quantity=2", "outbound.quantity=2",
}
// Sensible defaults for most people
Options_Default = []string{
"inbound.length=3", "outbound.length=3",
"inbound.lengthVariance=0", "outbound.lengthVariance=0",
"inbound.backupQuantity=1", "outbound.backupQuantity=1",
"inbound.quantity=1", "outbound.quantity=1",
}
// Suitable only for small dataflows, and very short lasting connections:
// You only have one tunnel in each direction, so if any of the nodes
// through which any of your two tunnels pass through go offline, there will
// be a complete halt in the dataflow, until a new tunnel is built.
Options_Small = []string{
"inbound.length=3", "outbound.length=3",
"inbound.lengthVariance=1", "outbound.lengthVariance=1",
"inbound.backupQuantity=0", "outbound.backupQuantity=0",
"inbound.quantity=1", "outbound.quantity=1",
}
// Does not use any anonymization, you connect directly to others tunnel
// endpoints, thus revealing your identity but not theirs. Use this only
// if you don't care.
Options_Warning_ZeroHop = []string{
"inbound.length=0", "outbound.length=0",
"inbound.lengthVariance=0", "outbound.lengthVariance=0",
"inbound.backupQuantity=0", "outbound.backupQuantity=0",
"inbound.quantity=2", "outbound.quantity=2",
}
)
func getEnv(key, fallback string) string {
logger.InitializeSAM3Logger()
value, ok := os.LookupEnv(key)
if !ok {
log.WithFields(logrus.Fields{
"key": key,
"fallback": fallback,
}).Debug("Environment variable not set, using fallback")
return fallback
}
log.WithFields(logrus.Fields{
"key": key,
"value": value,
}).Debug("Retrieved environment variable")
return value
}
var (
SAM_HOST = getEnv("sam_host", "127.0.0.1")
SAM_PORT = getEnv("sam_port", "7656")
)
func SAMDefaultAddr(fallforward string) string {
if fallforward == "" {
addr := net.JoinHostPort(SAM_HOST, SAM_PORT)
log.WithField("addr", addr).Debug("Using default SAM address")
return addr
}
log.WithField("addr", fallforward).Debug("Using fallforward SAM address")
return fallforward
}
func GenerateOptionString(opts []string) string {
optStr := strings.Join(opts, " ")
log.WithField("options", optStr).Debug("Generating option string")
if strings.Contains(optStr, "i2cp.leaseSetEncType") {
log.Debug("i2cp.leaseSetEncType already present in options")
return optStr
}
finalOpts := optStr + " i2cp.leaseSetEncType=4,0"
log.WithField("finalOptions", finalOpts).Debug("Added default i2cp.leaseSetEncType to options")
return finalOpts
// return optStr + " i2cp.leaseSetEncType=4,0"
}

View File

@@ -1,522 +0,0 @@
package sam3
import (
"errors"
"fmt"
"math/rand"
"net"
"strconv"
"strings"
"sync"
"time"
"github.com/sirupsen/logrus"
"github.com/go-i2p/i2pkeys"
"github.com/go-i2p/sam3/common"
)
const (
session_ADDOK = "SESSION STATUS RESULT=OK"
)
func randport() string {
s := rand.NewSource(time.Now().UnixNano())
r := rand.New(s)
p := r.Intn(55534) + 10000
port := strconv.Itoa(p)
log.WithField("port", port).Debug("Generated random port")
return strconv.Itoa(p)
}
// Represents a primary session.
type PrimarySession struct {
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 SAMEmit
stsess map[string]*StreamSession
dgsess map[string]*DatagramSession
sync.RWMutex
// from string
// to string
}
func (ss *PrimarySession) From() string {
return "0"
}
func (ss *PrimarySession) To() string {
return "0"
}
func (ss *PrimarySession) SignatureType() string {
return ss.sigType
}
// Returns the local tunnel name of the I2P tunnel used for the stream session
func (ss *PrimarySession) ID() string {
return ss.id
}
func (ss *PrimarySession) Close() error {
return ss.conn.Close()
}
// Returns the I2P destination (the address) of the stream session
func (ss *PrimarySession) Addr() i2pkeys.I2PAddr {
return ss.keys.Addr()
}
func (ss *PrimarySession) LocalAddr() net.Addr {
aa := ss.keys.Addr()
return &aa
}
// Returns the keys associated with the stream session
func (ss *PrimarySession) Keys() i2pkeys.I2PKeys {
return ss.keys
}
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")
sam.RLock()
ts, ok := sam.stsess[network+raddr.String()[0:4]]
sam.RUnlock()
if !ok {
sam.Lock()
ts, err := sam.NewUniqueStreamSubSession(network + raddr.String()[0:4])
if err != nil {
log.WithError(err).Error("Failed to create new unique stream sub-session")
sam.Unlock()
return nil, err
}
sam.stsess[network+raddr.String()[0:4]] = ts
sam.Unlock()
}
return ts.Dial(network, raddr.String())
}
func (sam *PrimarySession) DialTCPI2P(network, laddr, raddr string) (net.Conn, error) {
log.WithFields(logrus.Fields{"network": network, "laddr": laddr, "raddr": raddr}).Debug("DialTCPI2P() called")
sam.RLock()
ts, ok := sam.stsess[network+raddr[0:4]]
sam.RUnlock()
if !ok {
sam.Lock()
ts, err := sam.NewUniqueStreamSubSession(network + laddr)
if err != nil {
log.WithError(err).Error("Failed to create new unique stream sub-session")
sam.Unlock()
return nil, err
}
sam.stsess[network+raddr[0:4]] = ts
sam.Unlock()
}
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")
sam.RLock()
ds, ok := sam.dgsess[network+raddr.String()[0:4]]
sam.RUnlock()
if !ok {
sam.Lock()
ds, err := sam.NewDatagramSubSession(network+raddr.String()[0:4], 0)
if err != nil {
log.WithError(err).Error("Failed to create new datagram sub-session")
sam.Unlock()
return nil, err
}
sam.dgsess[network+raddr.String()[0:4]] = ds
sam.Unlock()
}
return ds.Dial(network, raddr.String())
}
func (sam *PrimarySession) DialUDPI2P(network, laddr, raddr string) (*DatagramSession, error) {
log.WithFields(logrus.Fields{"network": network, "laddr": laddr, "raddr": raddr}).Debug("DialUDPI2P() called")
sam.RLock()
ds, ok := sam.dgsess[network+raddr[0:4]]
sam.RUnlock()
if !ok {
sam.Lock()
ds, err := sam.NewDatagramSubSession(network+laddr, 0)
if err != nil {
log.WithError(err).Error("Failed to create new datagram sub-session")
sam.Unlock()
return nil, err
}
sam.dgsess[network+raddr[0:4]] = ds
sam.Unlock()
}
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 *SAM
name = strings.Split(name, ":")[0]
sam, err = 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
}
func (sam *PrimarySession) Resolve(network, addr string) (net.Addr, error) {
log.WithFields(logrus.Fields{"network": network, "addr": addr}).Debug("Resolve() called")
return sam.Lookup(addr)
}
func (sam *PrimarySession) ResolveTCPAddr(network, dest string) (net.Addr, error) {
log.WithFields(logrus.Fields{"network": network, "dest": dest}).Debug("ResolveTCPAddr() called")
return sam.Lookup(dest)
}
func (sam *PrimarySession) ResolveUDPAddr(network, dest string) (net.Addr, error) {
log.WithFields(logrus.Fields{"network": network, "dest": dest}).Debug("ResolveUDPAddr() called")
return sam.Lookup(dest)
}
// 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, 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{
samAddr: sam.SAMEmit.I2PConfig.Sam(),
id: id,
conn: conn,
keys: keys,
Timeout: time.Duration(600 * time.Second),
Deadline: time.Now(),
sigType: Sig_NONE,
Config: sam.SAMEmit,
stsess: ssesss,
dgsess: dsesss,
RWMutex: sync.RWMutex{},
}, 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]*StreamSession)
dsesss := make(map[string]*DatagramSession)
return &PrimarySession{
samAddr: sam.SAMEmit.I2PConfig.Sam(),
id: id,
conn: conn,
keys: keys,
Timeout: time.Duration(600 * time.Second),
Deadline: time.Now(),
sigType: sigType,
Config: sam.SAMEmit,
stsess: ssesss,
dgsess: dsesss,
RWMutex: sync.RWMutex{},
}, nil
}
// 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
scmsg := []byte(fmt.Sprintf("SESSION ADD STYLE=%s ID=%s FROM_PORT=%s TO_PORT=%s %s\n", style, id, from, to, strings.Join(extras, " ")))
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(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 == session_DUPLICATE_ID {
log.Error("Duplicate tunnel name")
conn.Close()
return nil, errors.New("Duplicate tunnel name")
} else if text == session_DUPLICATE_DEST {
log.Error("Duplicate destination")
conn.Close()
return nil, errors.New("Duplicate destination")
} else if text == session_INVALID_KEY {
log.Error("Invalid key - Primary Session")
conn.Close()
return nil, errors.New("Invalid key - Primary Session")
} else if strings.HasPrefix(text, session_I2P_ERROR) {
log.WithField("error", text[len(session_I2P_ERROR):]).Error("I2P error")
conn.Close()
return nil, errors.New("I2P error " + text[len(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)
}
}
// Creates a new 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) (*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
}
return &StreamSession{sam.Config.I2PConfig.Sam(), id, conn, sam.keys, time.Duration(600 * time.Second), time.Now(), Sig_NONE, "0", "0"}, 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 *PrimarySession) NewUniqueStreamSubSession(id string) (*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 := randport(), randport()
log.WithFields(logrus.Fields{"fromPort": fromPort, "toPort": toPort}).Debug("Generated random ports")
// return &StreamSession{sam.Config.I2PConfig.Sam(), id, conn, sam.keys, time.Duration(600 * time.Second), time.Now(), Sig_NONE, randport(), randport()}, nil
return &StreamSession{sam.Config.I2PConfig.Sam(), id, conn, sam.keys, time.Duration(600 * time.Second), time.Now(), Sig_NONE, fromPort, toPort}, 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 *PrimarySession) NewStreamSubSessionWithPorts(id, from, to string) (*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
}
return &StreamSession{sam.Config.I2PConfig.Sam(), id, conn, sam.keys, time.Duration(600 * time.Second), time.Now(), Sig_NONE, from, to}, nil
}
/*
func (s *PrimarySession) I2PListener(name string) (*StreamListener, error) {
listener, err := s.NewStreamSubSession(name)
if err != nil {
return nil, err
}
return listener.Listen()
}
*/
// 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, datagramOptions ...DatagramOptions) (*DatagramSession, error) {
log.WithFields(logrus.Fields{"id": id, "udpPort": udpPort}).Debug("NewDatagramSubSession called")
udpSessionConfig := &common.UDPSessionConfig{
Port: udpPort,
ParentConn: s.conn,
Log: log,
DefaultPort: 7655,
AllowZeroPort: true,
// Add required session parameters
Style: "DATAGRAM",
FromPort: "0", // Allow dynamic port assignment
ToPort: "0",
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
}
udpConn, err := common.NewUDPSession(udpSessionConfig)
if err != nil {
log.WithError(err).Error("Failed to create UDP session")
return nil, err
}
_, lport, err := net.SplitHostPort(udpConn.Conn.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
}
if len(datagramOptions) > 0 {
// return &DatagramSession{s.Config.I2PConfig.Sam(), id, conn, udpconn, s.keys, rUDPAddr, nil, &datagramOptions[0]}, nil
return &DatagramSession{
samAddr: s.Config.I2PConfig.Sam(),
id: id,
conn: conn,
keys: s.keys,
UDPSession: *udpConn,
DatagramOptions: &datagramOptions[0],
}, nil
}
opts := &DatagramOptions{
SendTags: 0,
TagThreshold: 0,
Expires: 0,
SendLeaseset: false,
}
log.WithFields(logrus.Fields{"id": id, "localPort": lport}).Debug("Created new datagram sub-session")
// return &DatagramSession{s.Config.I2PConfig.Sam(), id, conn, udpconn, s.keys, rUDPAddr, nil, opts}, nil
return &DatagramSession{
samAddr: s.Config.I2PConfig.Sam(),
id: id,
conn: conn,
keys: s.keys,
UDPSession: *udpConn,
DatagramOptions: opts,
}, nil
}
// 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) (*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 := 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 := 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")
return &RawSession{s.Config.I2PConfig.Sam(), id, conn, udpconn, s.keys, rUDPAddr}, nil
}

View File

@@ -1,148 +0,0 @@
package sam3
import (
"fmt"
"testing"
"time"
)
func Test_PrimaryDatagramServerClient(t *testing.T) {
if testing.Short() {
return
}
fmt.Println("Test_PrimaryDatagramServerClient")
earlysam, err := NewSAM(yoursam)
if err != nil {
t.Fail()
return
}
defer earlysam.Close()
keys, err := earlysam.NewKeys()
if err != nil {
t.Fail()
return
}
sam, err := earlysam.NewPrimarySession("PrimaryTunnel", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"})
if err != nil {
t.Fail()
return
}
defer sam.Close()
// fmt.Println("\tServer: My address: " + keys.Addr().Base32())
fmt.Println("\tServer: Creating tunnel")
ds, err := sam.NewDatagramSubSession("PrimaryTunnel"+RandString(), 0)
if err != nil {
fmt.Println("Server: Failed to create tunnel: " + err.Error())
t.Fail()
return
}
defer ds.Close()
c, w := make(chan bool), make(chan bool)
go func(c, w chan (bool)) {
sam2, err := NewSAM(yoursam)
if err != nil {
c <- false
return
}
defer sam2.Close()
keys, err := sam2.NewKeys()
if err != nil {
c <- false
return
}
fmt.Println("\tClient: Creating tunnel")
ds2, err := sam2.NewDatagramSession("PRIMARYClientTunnel", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"}, 0)
if err != nil {
c <- false
return
}
defer ds2.Close()
// fmt.Println("\tClient: Servers address: " + ds.LocalAddr().Base32())
// fmt.Println("\tClient: Clients address: " + ds2.LocalAddr().Base32())
fmt.Println("\tClient: Tries to send primary to server")
for {
select {
default:
_, err = ds2.WriteTo([]byte("Hello primary-world! <3 <3 <3 <3 <3 <3"), ds.LocalAddr())
if err != nil {
fmt.Println("\tClient: Failed to send primary: " + err.Error())
c <- false
return
}
time.Sleep(5 * time.Second)
case <-w:
fmt.Println("\tClient: Sent primary, quitting.")
return
}
}
c <- true
}(c, w)
buf := make([]byte, 512)
fmt.Println("\tServer: ReadFrom() waiting...")
n, _, err := ds.ReadFrom(buf)
w <- true
if err != nil {
fmt.Println("\tServer: Failed to ReadFrom(): " + err.Error())
t.Fail()
return
}
fmt.Println("\tServer: Received primary: " + string(buf[:n]))
// fmt.Println("\tServer: Senders address was: " + saddr.Base32())
}
func ExamplePrimaryDatagramSession() {
// Creates a new PrimarySession, then creates a Datagram subsession on top of it
const samBridge = "127.0.0.1:7656"
earlysam, err := NewSAM(samBridge)
if err != nil {
fmt.Println(err.Error())
return
}
defer earlysam.Close()
keys, err := earlysam.NewKeys()
if err != nil {
fmt.Println(err.Error())
return
}
myself := keys.Addr()
sam, err := earlysam.NewPrimarySession("PrimaryTunnel", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"})
if err != nil {
fmt.Println(err.Error())
return
}
defer sam.Close()
// See the example Option_* variables.
dg, err := sam.NewDatagramSubSession("DGTUN"+RandString(), 0)
if err != nil {
fmt.Println(err.Error())
return
}
defer dg.Close()
someone, err := earlysam.Lookup("zzz.i2p")
if err != nil {
fmt.Println(err.Error())
return
}
dg.WriteTo([]byte("Hello stranger!"), someone)
dg.WriteTo([]byte("Hello myself!"), myself)
buf := make([]byte, 31*1024)
n, _, err := dg.ReadFrom(buf)
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Println("Got message: '" + string(buf[:n]) + "'")
fmt.Println("Got message: " + string(buf[:n]))
return
// Output:
// Got message: Hello myself!
}

View File

@@ -1,306 +0,0 @@
package sam3
import (
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"time"
)
func Test_PrimaryStreamingDial(t *testing.T) {
if testing.Short() {
return
}
fmt.Println("Test_PrimaryStreamingDial")
earlysam, err := NewSAM(yoursam)
if err != nil {
t.Fail()
return
}
defer earlysam.Close()
keys, err := earlysam.NewKeys()
if err != nil {
t.Fail()
return
}
sam, err := earlysam.NewPrimarySession("PrimaryTunnel", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"})
if err != nil {
t.Fail()
return
}
defer sam.Close()
fmt.Println("\tBuilding tunnel")
ss, err := sam.NewStreamSubSession("primaryStreamTunnel")
if err != nil {
fmt.Println(err.Error())
t.Fail()
return
}
defer ss.Close()
fmt.Println("\tNotice: This may fail if your I2P node is not well integrated in the I2P network.")
fmt.Println("\tLooking up idk.i2p")
forumAddr, err := ss.Lookup("idk.i2p")
if err != nil {
fmt.Println(err.Error())
t.Fail()
return
}
fmt.Println("\tDialing idk.i2p(", forumAddr.Base32(), forumAddr.DestHash().Hash(), ")")
conn, err := ss.DialI2P(forumAddr)
if err != nil {
fmt.Println(err.Error())
t.Fail()
return
}
defer conn.Close()
fmt.Println("\tSending HTTP GET /")
if _, err := conn.Write([]byte("GET /\n")); err != nil {
fmt.Println(err.Error())
t.Fail()
return
}
buf := make([]byte, 4096)
n, err := conn.Read(buf)
if !strings.Contains(strings.ToLower(string(buf[:n])), "http") && !strings.Contains(strings.ToLower(string(buf[:n])), "html") {
fmt.Printf("\tProbably failed to StreamSession.DialI2P(idk.i2p)? It replied %d bytes, but nothing that looked like http/html", n)
} else {
fmt.Println("\tRead HTTP/HTML from idk.i2p")
}
}
func Test_PrimaryStreamingServerClient(t *testing.T) {
if testing.Short() {
return
}
fmt.Println("Test_StreamingServerClient")
earlysam, err := NewSAM(yoursam)
if err != nil {
t.Fail()
return
}
defer earlysam.Close()
keys, err := earlysam.NewKeys()
if err != nil {
t.Fail()
return
}
sam, err := earlysam.NewPrimarySession("PrimaryServerClientTunnel", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"})
if err != nil {
t.Fail()
return
}
defer sam.Close()
fmt.Println("\tServer: Creating tunnel")
ss, err := sam.NewUniqueStreamSubSession("PrimaryServerClientTunnel")
if err != nil {
return
}
defer ss.Close()
time.Sleep(time.Second * 10)
c, w := make(chan bool), make(chan bool)
go func(c, w chan (bool)) {
if !(<-w) {
return
}
/*
sam2, err := NewSAM(yoursam)
if err != nil {
c <- false
return
}
defer sam2.Close()
keys, err := sam2.NewKeys()
if err != nil {
c <- false
return
}
*/
fmt.Println("\tClient: Creating tunnel")
ss2, err := sam.NewStreamSubSession("primaryExampleClientTun")
if err != nil {
c <- false
return
}
defer ss2.Close()
fmt.Println("\tClient: Connecting to server")
conn, err := ss2.DialI2P(ss.Addr())
if err != nil {
c <- false
return
}
fmt.Println("\tClient: Connected to tunnel")
defer conn.Close()
_, err = conn.Write([]byte("Hello world <3 <3 <3 <3 <3 <3"))
if err != nil {
c <- false
return
}
c <- true
}(c, w)
l, err := ss.Listen()
if err != nil {
fmt.Println("ss.Listen(): " + err.Error())
t.Fail()
w <- false
return
}
defer l.Close()
w <- true
fmt.Println("\tServer: Accept()ing on tunnel")
conn, err := l.Accept()
if err != nil {
t.Fail()
fmt.Println("Failed to Accept(): " + err.Error())
return
}
defer conn.Close()
buf := make([]byte, 512)
n, err := conn.Read(buf)
fmt.Printf("\tClient exited successfully: %t\n", <-c)
fmt.Println("\tServer: received from Client: " + string(buf[:n]))
}
func ExamplePrimaryStreamSession() {
// Creates a new StreamingSession, dials to idk.i2p and gets a SAMConn
// which behaves just like a normal net.Conn.
const samBridge = "127.0.0.1:7656"
earlysam, err := NewSAM(yoursam)
if err != nil {
log.Fatal(err.Error())
return
}
defer earlysam.Close()
keys, err := earlysam.NewKeys()
if err != nil {
log.Fatal(err.Error())
return
}
sam, err := earlysam.NewPrimarySession("PrimaryStreamSessionTunnel", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"})
if err != nil {
log.Fatal(err.Error())
return
}
defer sam.Close()
conn, err := sam.Dial("tcp", "idk.i2p") // someone.Base32())
if err != nil {
fmt.Println(err.Error())
return
}
defer conn.Close()
fmt.Println("Sending HTTP GET /")
if _, err := conn.Write([]byte("GET /\n")); err != nil {
fmt.Println(err.Error())
return
}
buf := make([]byte, 4096)
n, err := conn.Read(buf)
if !strings.Contains(strings.ToLower(string(buf[:n])), "http") && !strings.Contains(strings.ToLower(string(buf[:n])), "html") {
fmt.Printf("Probably failed to StreamSession.DialI2P(idk.i2p)? It replied %d bytes, but nothing that looked like http/html", n)
log.Printf("Probably failed to StreamSession.DialI2P(idk.i2p)? It replied %d bytes, but nothing that looked like http/html", n)
} else {
fmt.Println("Read HTTP/HTML from idk.i2p")
log.Println("Read HTTP/HTML from idk.i2p")
}
// Output:
// Sending HTTP GET /
// Read HTTP/HTML from idk.i2p
}
func ExamplePrimaryStreamListener() {
// One server Accept()ing on a StreamListener, and one client that Dials
// through I2P to the server. Server writes "Hello world!" through a SAMConn
// (which implements net.Conn) and the client prints the message.
const samBridge = "127.0.0.1:7656"
var ss *StreamSession
go func() {
earlysam, err := NewSAM(yoursam)
if err != nil {
log.Fatal(err.Error())
return
}
defer earlysam.Close()
keys, err := earlysam.NewKeys()
if err != nil {
log.Fatal(err.Error())
return
}
sam, err := earlysam.NewPrimarySession("PrimaryListenerTunnel", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"})
if err != nil {
log.Fatal(err.Error())
return
}
defer sam.Close()
ss, err = sam.NewStreamSubSession("PrimaryListenerServerTunnel2")
if err != nil {
fmt.Println(err.Error())
return
}
defer ss.Close()
l, err := ss.Listen()
if err != nil {
fmt.Println(err.Error())
return
}
defer l.Close()
// fmt.Println("Serving on primary listener", l.Addr().String())
if err := http.Serve(l, &exitHandler{}); err != nil {
fmt.Println(err.Error())
}
}()
time.Sleep(time.Second * 10)
latesam, err := NewSAM(yoursam)
if err != nil {
log.Fatal(err.Error())
return
}
defer latesam.Close()
keys2, err := latesam.NewKeys()
if err != nil {
log.Fatal(err.Error())
return
}
sc, err := latesam.NewStreamSession("PrimaryListenerClientTunnel2", keys2, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"})
if err != nil {
fmt.Println(err.Error())
return
}
defer sc.Close()
client := http.Client{
Transport: &http.Transport{
Dial: sc.Dial,
},
}
// resp, err := client.Get("http://" + "idk.i2p") //ss.Addr().Base32())
resp, err := client.Get("http://" + ss.Addr().Base32())
if err != nil {
fmt.Println(err.Error())
return
}
defer resp.Body.Close()
r, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Println("Got response: " + string(r))
// Output:
// Got response: Hello world!
}
type exitHandler struct{}
func (e *exitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello world!"))
}

91
raw.go
View File

@@ -1,14 +1,15 @@
package sam3
import (
"bytes"
"errors"
"net"
"strconv"
"time"
)
"github.com/sirupsen/logrus"
"github.com/go-i2p/i2pkeys"
import (
. "github.com/eyedeekay/sam3/i2pkeys"
)
// The RawSession provides no authentication of senders, and there is no sender
@@ -18,137 +19,93 @@ import (
// 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 {
samAddr string // address to the sam bridge (ipv4:port)
id string // tunnel name
conn net.Conn // connection to sam bridge
udpconn *net.UDPConn // used to deliver datagrams
keys i2pkeys.I2PKeys // i2p destination keys
rUDPAddr *net.UDPAddr // the SAM bridge UDP-port
samAddr string // address to the sam bridge (ipv4:port)
id string // tunnel name
conn net.Conn // connection to sam bridge
udpconn *net.UDPConn // used to deliver datagrams
keys I2PKeys // i2p destination keys
rUDPAddr *net.UDPAddr // the SAM bridge UDP-port
}
// 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")
func (s *SAM) NewRawSession(id string, keys I2PKeys, options []string, udpPort int) (*RawSession, error) {
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")
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 := SplitHostPort(s.conn.LocalAddr().String())
lhost, _, err := net.SplitHostPort(s.Conn.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 := SplitHostPort(s.conn.RemoteAddr().String())
rhost, _, err := net.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")
return nil, err
}
conn, err := s.newGenericSession("RAW", id, keys, options, []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")
return &RawSession{s.SAMEmit.I2PConfig.Sam(), id, conn, udpconn, keys, rUDPAddr}, nil
return &RawSession{s.Config.I2PConfig.Sam(), id, conn, udpconn, keys, rUDPAddr}, nil
}
// Read one raw datagram sent to the destination of the DatagramSession. Returns
// Reads one raw datagram sent to the destination of the DatagramSession. Returns
// the number of bytes read. Who sent the raw message can not be determined at
// this layer - you need to do it (in a secure way!).
func (s *RawSession) Read(b []byte) (n int, err error) {
log.Debug("Attempting to read raw datagram")
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(b)
if err != nil {
log.WithError(err).Error("Failed to read from UDP")
return 0, err
}
// Verify source is SAM bridge
if saddr.IP.Equal(s.rUDPAddr.IP) && saddr.Port == s.rUDPAddr.Port {
log.WithField("bytesRead", n).Debug("Successfully read raw datagram")
return n, nil
if bytes.Equal(saddr.IP, s.rUDPAddr.IP) {
continue
}
// Log unexpected source
log.Printf("Ignored datagram from unauthorized source: %v", saddr)
continue
break
}
return n, nil
}
// Sends one raw datagram to the destination specified. At the time of writing,
// maximum size is 32 kilobyte, but this may change in the future.
func (s *RawSession) WriteTo(b []byte, addr i2pkeys.I2PAddr) (n int, err error) {
log.WithFields(logrus.Fields{
"destAddr": addr.String(),
"dataLen": len(b),
}).Debug("Attempting to write raw datagram")
func (s *RawSession) WriteTo(b []byte, addr I2PAddr) (n int, err error) {
header := []byte("3.0 " + s.id + " " + addr.String() + "\n")
msg := append(header, b...)
n, err = s.udpconn.WriteToUDP(msg, s.rUDPAddr)
if err != nil {
log.WithError(err).Error("Failed to write to UDP")
}
log.WithField("bytesWritten", n).Debug("Successfully wrote raw datagram")
return n, err
}
// Closes the RawSession.
func (s *RawSession) Close() error {
log.Debug("Closing RawSession")
err := s.conn.Close()
err2 := s.udpconn.Close()
if err != nil {
log.WithError(err).Error("Failed to close connection")
return err
}
err2 := s.udpconn.Close()
if err2 != nil {
log.WithError(err2).Error("Failed to close UDP connection")
}
log.Debug("RawSession closed")
return err2
}
// Returns the local I2P destination of the RawSession.
func (s *RawSession) LocalAddr() i2pkeys.I2PAddr {
func (s *RawSession) LocalAddr() I2PAddr {
return s.keys.Addr()
}

View File

@@ -2,123 +2,72 @@ package sam3
import (
"bufio"
"context"
"fmt"
"bytes"
"errors"
"strings"
"time"
"github.com/go-i2p/i2pkeys"
)
// SAMResolver handles name resolution for I2P addresses
import (
. "github.com/eyedeekay/sam3/i2pkeys"
)
type SAMResolver struct {
sam *SAM
*SAM
}
// ResolveResult represents the possible outcomes of name resolution
type ResolveResult struct {
Address i2pkeys.I2PAddr
Error error
}
const (
defaultTimeout = 30 * time.Second
samReplyPrefix = "NAMING REPLY "
)
// NewSAMResolver creates a resolver from an existing SAM instance
func NewSAMResolver(parent *SAM) (*SAMResolver, error) {
if parent == nil {
return nil, fmt.Errorf("parent SAM instance required")
}
return &SAMResolver{sam: parent}, nil
var s SAMResolver
s.SAM = parent
return &s, nil
}
// NewFullSAMResolver creates a new resolver with its own SAM connection
func NewFullSAMResolver(address string) (*SAMResolver, error) {
sam, err := NewSAM(address)
if err != nil {
return nil, fmt.Errorf("creating SAM connection: %w", err)
}
return &SAMResolver{sam: sam}, nil
var s SAMResolver
var err error
s.SAM, err = NewSAM(address)
if err != nil {
return nil, err
}
return &s, nil
}
func (r *SAMResolver) Resolve(name string) (i2pkeys.I2PAddr, error) {
return r.ResolveWithContext(context.Background(), name)
// 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) (I2PAddr, error) {
if _, err := sam.Conn.Write([]byte("NAMING LOOKUP NAME=" + name + "\n")); err != nil {
sam.Close()
return I2PAddr(""), err
}
buf := make([]byte, 4096)
n, err := sam.Conn.Read(buf)
if err != nil {
sam.Close()
return I2PAddr(""), err
}
if n <= 13 || !strings.HasPrefix(string(buf[:n]), "NAMING REPLY ") {
return 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()
if text == "RESULT=OK" {
continue
} else if text == "RESULT=INVALID_KEY" {
errStr += "Invalid key."
} else if text == "RESULT=KEY_NOT_FOUND" {
errStr += "Unable to resolve " + name
} else if text == "NAME="+name {
continue
} else if strings.HasPrefix(text, "VALUE=") {
return I2PAddr(text[6:]), nil
} else if strings.HasPrefix(text, "MESSAGE=") {
errStr += " " + text[8:]
} else {
continue
}
}
return I2PAddr(""), errors.New(errStr)
}
// Resolve looks up an I2P address by name with context support
func (r *SAMResolver) ResolveWithContext(ctx context.Context, name string) (i2pkeys.I2PAddr, error) {
if name == "" {
return "", fmt.Errorf("name cannot be empty")
}
// Create query
query := fmt.Sprintf("NAMING LOOKUP NAME=%s\n", name)
// Set up timeout if context doesn't have one
if _, hasTimeout := ctx.Deadline(); !hasTimeout {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, defaultTimeout)
defer cancel()
}
// Write query with context awareness
if err := r.writeWithContext(ctx, query); err != nil {
return "", fmt.Errorf("writing query: %w", err)
}
// Read and parse response
return r.readResponse(ctx, name)
}
func (r *SAMResolver) writeWithContext(ctx context.Context, query string) error {
done := make(chan error, 1)
go func() {
_, err := r.sam.conn.Write([]byte(query))
done <- err
}()
select {
case err := <-done:
return err
case <-ctx.Done():
return ctx.Err()
}
}
func (r *SAMResolver) readResponse(ctx context.Context, name string) (i2pkeys.I2PAddr, error) {
reader := bufio.NewReader(r.sam.conn)
// Read first line
line, err := reader.ReadString('\n')
if err != nil {
return "", fmt.Errorf("reading response: %w", err)
}
if !strings.HasPrefix(line, samReplyPrefix) {
return "", fmt.Errorf("invalid response format")
}
// Parse response
fields := strings.Fields(strings.TrimPrefix(line, samReplyPrefix))
for _, field := range fields {
switch {
case field == "RESULT=OK":
continue
case field == "RESULT=INVALID_KEY":
return "", fmt.Errorf("invalid key")
case field == "RESULT=KEY_NOT_FOUND":
return "", fmt.Errorf("name not found: %s", name)
case field == "NAME="+name:
continue
case strings.HasPrefix(field, "VALUE="):
return i2pkeys.I2PAddr(strings.TrimPrefix(field, "VALUE=")), nil
case strings.HasPrefix(field, "MESSAGE="):
return "", fmt.Errorf("SAM error: %s", strings.TrimPrefix(field, "MESSAGE="))
}
}
return "", fmt.Errorf("unable to resolve %s", name)
}

241
sam3.go
View File

@@ -4,35 +4,25 @@ package sam3
import (
"bufio"
"bytes"
"fmt"
"errors"
"io"
"math/rand"
"net"
"os"
"strings"
"github.com/sirupsen/logrus"
"github.com/go-i2p/i2pkeys"
"github.com/go-i2p/sam3/common"
logger "github.com/go-i2p/sam3/log"
)
func init() {
logger.InitializeSAM3Logger()
}
import (
"github.com/eyedeekay/ramp/config"
. "github.com/eyedeekay/ramp/emit"
. "github.com/eyedeekay/sam3/i2pkeys"
)
// Used for controlling I2Ps SAMv3.
// This implements the "Control Socket" for all connections.
type SAM struct {
address string
conn net.Conn
keys *i2pkeys.I2PKeys
sigType int
formatter *common.SAMFormatter
version common.Version
SAMEmit
*SAMResolver
//address string
Conn net.Conn
Resolver *SAMResolver
Config SAMEmit
}
const (
@@ -44,7 +34,7 @@ const (
)
const (
Sig_NONE = "SIGNATURE_TYPE=EdDSA_SHA512_Ed25519"
Sig_NONE = ""
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"
@@ -52,116 +42,82 @@ const (
Sig_EdDSA_SHA512_Ed25519 = "SIGNATURE_TYPE=EdDSA_SHA512_Ed25519"
)
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
func RandString() string {
n := 4
b := make([]rune, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
log.WithField("randomString", string(b)).Debug("Generated random string")
return string(b)
}
// Creates a new controller for the I2P routers SAM bridge.
func NewSAM(address string) (*SAM, error) {
log.WithField("address", address).Debug("Creating new SAM instance")
s := SAM{
address: address,
version: common.SAM31Version,
formatter: common.NewSAMFormatter(common.SAM31Version.String),
}
var s SAM
// TODO: clean this up
conn, err := net.Dial("tcp", address)
s.Config.I2PConfig.SetSAMAddress(address)
s.Config.SamMax = "3.2"
conn, err := net.Dial("tcp", s.Config.I2PConfig.Sam())
if err != nil {
log.WithError(err).Error("Failed to dial SAM address")
return nil, fmt.Errorf("error dialing to address '%s': %w", address, err)
return nil, err
}
if _, err := conn.Write(s.SAMEmit.HelloBytes()); err != nil {
log.WithError(err).Error("Failed to write hello message")
if _, err := conn.Write(s.Config.HelloBytes()); err != nil {
conn.Close()
return nil, fmt.Errorf("error writing to address '%s': %w", address, err)
return nil, err
}
/*buf := make([]byte, 256)
n, err := conn.Read(buf)*/
reader := bufio.NewReader(conn)
response, err := reader.ReadString('\n')
buf := make([]byte, 256)
n, err := conn.Read(buf)
if err != nil {
conn.Close()
return nil, fmt.Errorf("error reading SAM response: %w", err)
return nil, err
}
buf := []byte(response)
n := len(buf)
if strings.Contains(string(buf[:n]), "HELLO REPLY RESULT=OK") {
log.Debug("SAM hello successful")
s.SAMEmit.I2PConfig.SetSAMAddress(address)
s.conn = conn
// s.Config.I2PConfig.DestinationKeys = nil
s.SAMResolver, err = NewSAMResolver(&s)
s.Conn = conn
//s.Config.I2PConfig.DestinationKeys = nil
s.Resolver, 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 nil, err
}
return &s, nil
//return &SAM{address, conn, nil, nil}, nil
} else if string(buf[:n]) == "HELLO REPLY RESULT=NOVERSION\n" {
log.Error("SAM bridge does not support SAMv3")
conn.Close()
return nil, fmt.Errorf("That SAM bridge does not support SAMv3.")
return nil, errors.New("That SAM bridge does not support SAMv3.")
} else {
log.WithField("response", string(buf[:n])).Error("Unexpected SAM response")
conn.Close()
return nil, fmt.Errorf(string(buf[:n]))
return nil, errors.New(string(buf[:n]))
}
}
func (sam *SAM) Keys() (k *i2pkeys.I2PKeys) {
// TODO: copy them?
log.Debug("Retrieving SAM keys")
k = &sam.SAMEmit.I2PConfig.DestinationKeys
func (sam *SAM) Keys() (k *I2PKeys) {
//TODO: copy them?
k = &sam.Config.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)
var keys I2PKeys
keys, err = LoadKeysIncompat(r)
if err == nil {
log.Debug("Keys loaded successfully")
sam.SAMEmit.I2PConfig.DestinationKeys = keys
sam.Config.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")
func (sam *SAM) EnsureKeyfile(fname string) (keys I2PKeys, err error) {
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")
sam.Config.I2PConfig.DestinationKeys = keys
}
} else {
// persistent
// persistant
_, err = os.Stat(fname)
if os.IsNotExist(err) {
// make the keys
keys, err = sam.NewKeys()
if err == nil {
sam.SAMEmit.I2PConfig.DestinationKeys = keys
sam.Config.I2PConfig.DestinationKeys = keys
// save keys
var f io.WriteCloser
f, err = os.OpenFile(fname, os.O_WRONLY|os.O_CREATE, 0o600)
f, err = os.OpenFile(fname, os.O_WRONLY|os.O_CREATE, 0600)
if err == nil {
err = i2pkeys.StoreKeysIncompat(keys, f)
err = StoreKeysIncompat(keys, f)
f.Close()
log.Debug("Generated and saved new keys")
}
}
} else if err == nil {
@@ -169,44 +125,31 @@ func (sam *SAM) EnsureKeyfile(fname string) (keys i2pkeys.I2PKeys, err error) {
var f *os.File
f, err = os.Open(fname)
if err == nil {
keys, err = i2pkeys.LoadKeysIncompat(f)
keys, err = LoadKeysIncompat(f)
if err == nil {
sam.SAMEmit.I2PConfig.DestinationKeys = keys
log.Debug("Loaded existing keys from file")
sam.Config.I2PConfig.DestinationKeys = keys
}
}
}
}
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.
// Add constant for recommended sig type
const (
DEFAULT_SIG_TYPE = "SIGNATURE_TYPE=7" // EdDSA_SHA512_Ed25519
)
func (sam *SAM) NewKeys(sigType ...string) (i2pkeys.I2PKeys, error) {
log.WithField("sigType", sigType).Debug("Generating new keys")
if sigType == nil {
sigType = []string{DEFAULT_SIG_TYPE}
func (sam *SAM) NewKeys(sigType ...string) (I2PKeys, error) {
sigtmp := ""
if len(sigType) > 0 {
sigtmp = sigType[0]
}
scmsg := []byte(fmt.Sprintf("DEST GENERATE %s\n", sigType[0]))
if _, err := sam.conn.Write(scmsg); err != nil {
log.WithError(err).Error("Failed to write DEST GENERATE command")
return i2pkeys.I2PKeys{}, fmt.Errorf("error with writing in SAM: %w", err)
if _, err := sam.Conn.Write([]byte("DEST GENERATE " + sigtmp + "\n")); err != nil {
return I2PKeys{}, err
}
buf := make([]byte, 8192)
n, err := sam.conn.Read(buf)
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)
return I2PKeys{}, err
}
s := bufio.NewScanner(bytes.NewReader(buf[:n]))
s.Split(bufio.ScanWords)
@@ -223,19 +166,16 @@ func (sam *SAM) NewKeys(sigType ...string) (i2pkeys.I2PKeys, error) {
} 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.")
return I2PKeys{}, errors.New("Failed to parse keys.")
}
}
log.Debug("Successfully generated new keys")
return i2pkeys.NewKeys(i2pkeys.I2PAddr(pub), priv), nil
return NewKeys(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)
func (sam *SAM) Lookup(name string) (I2PAddr, error) {
return sam.Resolver.Resolve(name)
}
// Creates a new session with the style of either "STREAM", "DATAGRAM" or "RAW",
@@ -243,100 +183,69 @@ func (sam *SAM) Lookup(name string) (i2pkeys.I2PAddr, error) {
// I2CP/streaminglib-options as specified. Extra arguments can be specified by
// setting extra to something else than []string{}.
// This sam3 instance is now a session
func (sam *SAM) newGenericSession(style, id string, keys i2pkeys.I2PKeys, options, extras []string) (net.Conn, error) {
log.WithFields(logrus.Fields{"style": style, "id": id}).Debug("Creating new generic session")
func (sam *SAM) newGenericSession(style, id string, keys I2PKeys, options []string, extras []string) (net.Conn, error) {
return sam.newGenericSessionWithSignature(style, id, keys, Sig_NONE, options, extras)
}
func (sam *SAM) newGenericSessionWithSignature(style, id string, keys i2pkeys.I2PKeys, sigType string, options, extras []string) (net.Conn, error) {
log.WithFields(logrus.Fields{"style": style, "id": id, "sigType": sigType}).Debug("Creating new generic session with signature")
return sam.newGenericSessionWithSignatureAndPorts(style, id, "0", "0", keys, sigType, options, extras)
}
// 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, options, extras []string) (net.Conn, error) {
log.WithFields(logrus.Fields{"style": style, "id": id, "from": from, "to": to, "sigType": sigType}).Debug("Creating new generic session with signature and ports")
optStr := GenerateOptionString(options)
conn := sam.conn
scmsg := []byte(fmt.Sprintf("SESSION CREATE STYLE=%s FROM_PORT=%s TO_PORT=%s ID=%s DESTINATION=%s %s %s\n", style, from, to, id, keys.String(), optStr, strings.Join(extras, " ")))
if style == "PRIMARY" || style == "MASTER" {
scmsg = []byte(fmt.Sprintf("SESSION CREATE STYLE=%s ID=%s DESTINATION=%s %s %s\n", style, id, keys.String(), optStr, strings.Join(extras, " ")))
}
log.WithField("message", string(scmsg)).Debug("Sending SESSION CREATE message", string(scmsg))
func (sam *SAM) newGenericSessionWithSignature(style, id string, keys I2PKeys, sigType string, options []string, extras []string) (net.Conn, error) {
conf, _ := i2pconfig.ConstructEqualsConfig(append(options))
sam.Config.I2PConfig = *conf
sam.Config.SigType = sigType
sam.Config.Style = style
sam.Config.DestinationKeys = keys
sam.Config.TunName = id
conn := sam.Conn
scmsg := []byte("SESSION CREATE " + sam.Config.SessionStyle() + sam.Config.ID() + sam.Config.DestinationKey() + sam.Config.SignatureType() + sam.Config.FromPort() + sam.Config.ToPort() + sam.Config.OptStr() + strings.Join(extras, " ") + "\n")
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")
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, fmt.Errorf("writing to connection failed: %w", err)
return nil, 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)
return nil, 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")
return nil, errors.New("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")
return nil, errors.New("Duplicate tunnel name")
} else if text == session_DUPLICATE_DEST {
log.Error("Duplicate destination")
conn.Close()
return nil, fmt.Errorf("Duplicate destination")
return nil, errors.New("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")
return nil, errors.New("Invalid key")
} 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 %s", text[len(session_I2P_ERROR):])
return nil, errors.New("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: %s", text)
return nil, errors.New("Unable to parse SAMv3 reply: " + text)
}
}
// Close this sam session
// close this sam session
func (sam *SAM) Close() error {
log.Debug("Closing SAM session")
return sam.conn.Close()
}
// CloseNotify the socket with a QUIT message
func (sam *SAM) CloseNotify() error {
log.Debug("Quitting SAM session")
_, err := sam.conn.Write([]byte("QUIT\n"))
if err != nil {
return fmt.Errorf("close notification failed: %v", err)
}
return nil
return sam.Conn.Close()
}

View File

@@ -1,3 +1,5 @@
// +build nettest
package sam3
import (

View File

@@ -1,10 +0,0 @@
/* edgar showhider CSS file */
#show {display:none; }
#hide {display:block; }
#show:target {display: block; }
#hide:target {display: none; }
#shownav {display:none; }
#hidenav {display:block; }
#shownav:target {display: block; }
#hidenav:target {display: none; }

264
stream.go
View File

@@ -5,23 +5,24 @@ import (
"bytes"
"context"
"errors"
"fmt"
"io"
"log"
"net"
"strconv"
"strings"
"time"
)
"github.com/sirupsen/logrus"
"github.com/go-i2p/i2pkeys"
import (
. "github.com/eyedeekay/sam3/i2pkeys"
)
// Represents a streaming session.
type StreamSession struct {
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
samAddr string // address to the sam bridge (ipv4:port)
id string // tunnel name
conn net.Conn // connection to sam
keys I2PKeys // i2p destination keys
Timeout time.Duration
Deadline time.Time
sigType string
@@ -29,132 +30,86 @@ type StreamSession struct {
to string
}
// Read reads data from the stream.
func (s *StreamSession) Read(buf []byte) (int, error) {
return s.conn.Read(buf)
func (ss *StreamSession) From() string {
return ss.from
}
// Write sends data over the stream.
func (s *StreamSession) Write(data []byte) (int, error) {
return s.conn.Write(data)
func (ss *StreamSession) To() string {
return ss.to
}
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.from
}
func (s *StreamSession) To() string {
return s.to
}
func (s *StreamSession) SignatureType() string {
return s.sigType
func (ss *StreamSession) SignatureType() string {
return ss.sigType
}
// Returns the local tunnel name of the I2P tunnel used for the stream session
func (s *StreamSession) ID() string {
return s.id
func (ss *StreamSession) ID() string {
return ss.id
}
func (s *StreamSession) Close() error {
log.WithField("id", s.id).Debug("Closing StreamSession")
return s.conn.Close()
func (ss *StreamSession) Close() error {
return ss.conn.Close()
}
// Returns the I2P destination (the address) of the stream session
func (s *StreamSession) Addr() i2pkeys.I2PAddr {
return s.keys.Addr()
}
func (s *StreamSession) LocalAddr() net.Addr {
return s.keys.Addr()
func (ss *StreamSession) Addr() I2PAddr {
return ss.keys.Addr()
}
// Returns the keys associated with the stream session
func (s *StreamSession) Keys() i2pkeys.I2PKeys {
return s.keys
func (ss *StreamSession) Keys() I2PKeys {
return ss.keys
}
// 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")
func (sam *SAM) NewStreamSession(id string, keys I2PKeys, options []string) (*StreamSession, error) {
conn, err := sam.newGenericSession("STREAM", id, keys, options, []string{})
if err != nil {
return nil, err
}
log.WithField("id", id).Debug("Created new StreamSession")
return &StreamSession{sam.SAMEmit.I2PConfig.Sam(), id, conn, keys, time.Duration(600 * time.Second), time.Now(), Sig_NONE, "0", "0"}, nil
return &StreamSession{sam.Config.I2PConfig.Sam(), id, conn, keys, time.Duration(600 * time.Second), time.Now(), Sig_NONE, "0", "0"}, 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")
func (sam *SAM) NewStreamSessionWithSignature(id string, keys I2PKeys, options []string, sigType string) (*StreamSession, error) {
conn, err := sam.newGenericSessionWithSignature("STREAM", id, keys, sigType, options, []string{})
if err != nil {
return nil, err
}
log.WithFields(logrus.Fields{"id": id, "sigType": sigType}).Debug("Created new StreamSession with signature")
return &StreamSession{sam.SAMEmit.I2PConfig.Sam(), id, conn, keys, time.Duration(600 * time.Second), time.Now(), sigType, "0", "0"}, nil
return &StreamSession{sam.Config.I2PConfig.Sam(), id, conn, keys, time.Duration(600 * time.Second), time.Now(), sigType, "0", "0"}, 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, options, []string{})
func (sam *SAM) NewStreamSessionWithSignatureAndPorts(id string, keys I2PKeys, options []string, sigType string) (*StreamSession, error) {
conn, err := sam.newGenericSessionWithSignature("STREAM", id, keys, sigType, options, []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")
return &StreamSession{sam.SAMEmit.I2PConfig.Sam(), id, conn, keys, time.Duration(600 * time.Second), time.Now(), sigType, from, to}, nil
return &StreamSession{sam.Config.I2PConfig.Sam(), id, conn, keys, time.Duration(600 * time.Second), time.Now(), sigType, sam.Config.Fromport, sam.Config.Toport}, nil
}
// lookup name, convenience function
func (s *StreamSession) Lookup(name string) (i2pkeys.I2PAddr, error) {
log.WithField("name", name).Debug("Looking up address")
// lookup name, convienence function
func (s *StreamSession) Lookup(name string) (I2PAddr, error) {
sam, err := NewSAM(s.samAddr)
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")
}
sam.Close()
return addr, err
}
log.WithError(err).Error("Failed to create SAM instance for lookup")
return i2pkeys.I2PAddr(""), err
return I2PAddr(""), err
}
// 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) (*SAMConn, 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())
@@ -166,9 +121,8 @@ func (s *StreamSession) DialContextI2P(ctx context.Context, n, addr string) (*SA
}
}
i2paddr, err := i2pkeys.NewI2PAddrFromString(addr)
i2paddr, err := NewI2PAddrFromString(addr)
if err != nil {
log.WithError(err).Error("Failed to create I2P address from string")
return nil, err
}
return s.DialI2P(i2paddr)
@@ -195,7 +149,6 @@ func minNonzeroTime(a, b time.Time) time.Time {
// - 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
@@ -209,57 +162,42 @@ func (s *StreamSession) deadline(ctx context.Context, now time.Time) (earliest t
// 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 i2paddr I2PAddr
var host string
host, _, err = SplitHostPort(addr)
// log.Println("Dialing:", host)
if err = IgnorePortError(err); err == nil {
host, _, err = net.SplitHostPort(addr)
if 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")
i2paddr = I2PAddr(host)
}
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) (*SAMConn, error) {
log.WithField("addr", addr).Debug("DialI2P called")
func (s *StreamSession) DialI2P(addr I2PAddr) (*SAMConn, error) {
sam, err := NewSAM(s.samAddr)
if err != nil {
log.WithError(err).Error("Failed to create new SAM instance")
return nil, err
}
conn := sam.conn
cmd := fmt.Sprintf("STREAM CONNECT ID=%s DESTINATION=%s FROM_PORT=%s TO_PORT=%s SILENT=false\n",
s.id,
addr.Base64(),
s.from,
s.to)
_, err = conn.Write([]byte(cmd))
conn := sam.Conn
// + sam.Config.ID()
_, err = conn.Write([]byte("STREAM CONNECT ID=" + s.id + sam.Config.FromPort() + sam.Config.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
}
@@ -272,44 +210,138 @@ func (s *StreamSession) DialI2P(addr i2pkeys.I2PAddr) (*SAMConn, error) {
case "STATUS":
continue
case "RESULT=OK":
log.Debug("Successfully connected to I2P destination")
return &SAMConn{s.keys.Addr(), addr, conn}, nil
case "RESULT=CANT_REACH_PEER":
log.Error("Can't reach peer")
conn.Close()
return nil, errors.New("Can not reach peer")
case "RESULT=I2P_ERROR":
log.Error("I2P internal error")
conn.Close()
return nil, errors.New("I2P internal error")
case "RESULT=INVALID_KEY":
log.Error("Invalid key - Stream Session")
conn.Close()
return nil, errors.New("Invalid key - Stream Session")
return nil, errors.New("Invalid key")
case "RESULT=INVALID_ID":
log.Error("Invalid tunnel ID")
conn.Close()
return nil, errors.New("Invalid tunnel ID")
case "RESULT=TIMEOUT":
log.Error("Connection timeout")
conn.Close()
return nil, errors.New("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]))
return nil, errors.New("Unknown error: " + scanner.Text() + " : " + string(buf[:n]))
}
}
log.Panic("Unexpected end of StreamSession.DialI2P()")
panic("sam3 go library error in StreamSession.DialI2P()")
}
// create a new stream listener to accept inbound connections
func (s *StreamSession) Listen() (*StreamListener, error) {
log.WithFields(logrus.Fields{"id": s.id, "laddr": s.keys.Addr()}).Debug("Creating new StreamListener")
return &StreamListener{
session: s,
id: s.id,
laddr: s.keys.Addr(),
}, nil
}
type StreamListener struct {
// parent stream session
session *StreamSession
// our session id
id string
// our local address for this sam socket
laddr I2PAddr
}
func (l *StreamListener) From() string {
return l.session.from
}
func (l *StreamListener) To() string {
return l.session.to
}
// get our address
// implements net.Listener
func (l *StreamListener) Addr() net.Addr {
return l.laddr
}
// 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 {
parts := strings.Split(input, " ")
for _, part := range parts {
if strings.HasPrefix(part, value) {
kv := strings.SplitN(input, "=", 2)
if len(kv) == 2 {
return kv[1]
}
}
}
return ""
}
func ExtractPairInt(input, value string) int {
rv, err := strconv.Atoi(ExtractPairString(input, value))
if err != nil {
return 0
}
return rv
}
func ExtractDest(input string) string {
return strings.Split(input, " ")[0]
}
// accept a new inbound connection
func (l *StreamListener) AcceptI2P() (*SAMConn, error) {
s, err := NewSAM(l.session.samAddr)
if err == nil {
// we connected to sam
// send accept() command
_, err = io.WriteString(s.Conn, "STREAM ACCEPT ID="+l.id+" SILENT=false\n")
// read reply
rd := bufio.NewReader(s.Conn)
// read first line
line, err := rd.ReadString(10)
log.Println(line)
if err == nil {
if strings.HasPrefix(line, "STREAM STATUS RESULT=OK") {
// we gud read destination line
destline, err := rd.ReadString(10)
log.Println(destline)
if err == nil {
dest := ExtractDest(destline)
l.session.from = ExtractPairString(destline, "FROM_PORT")
l.session.to = ExtractPairString(destline, "TO_PORT")
// return wrapped connection
dest = strings.Trim(dest, "\n")
return &SAMConn{
laddr: l.laddr,
raddr: I2PAddr(dest),
conn: s.Conn,
}, nil
} else {
s.Close()
return nil, err
}
} else {
s.Close()
return nil, errors.New("invalid sam line: " + line)
}
} else {
s.Close()
return nil, err
}
}
s.Close()
return nil, err
}

View File

@@ -1,178 +0,0 @@
package sam3
import (
"bufio"
"context"
"fmt"
"io"
"net"
"strconv"
"strings"
"time"
"github.com/sirupsen/logrus"
"github.com/go-i2p/i2pkeys"
)
type StreamListener struct {
// parent stream session
session *StreamSession
// our session id
id string
// our local address for this sam socket
laddr i2pkeys.I2PAddr
}
func (l *StreamListener) From() string {
return l.session.from
}
func (l *StreamListener) To() string {
return l.session.to
}
// get our address
// implements net.Listener
func (l *StreamListener) Addr() net.Addr {
return l.laddr
}
// implements net.Listener
func (l *StreamListener) Close() error {
return l.session.Close()
}
// implements net.Listener
func (l *StreamListener) Accept() (net.Conn, error) {
conn, err := l.AcceptI2P()
if err != nil {
// Clean up on accept failure
if conn != nil {
conn.Close()
}
return nil, err
}
return conn, nil
}
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", dest)
return strings.Split(input, " ")[0]
}
// accept a new inbound connection
func (l *StreamListener) AcceptI2P() (*SAMConn, error) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
done := make(chan struct{})
var conn *SAMConn
var err error
go func() {
defer close(done)
log.Debug("StreamListener.AcceptI2P() called")
s, err := NewSAM(l.session.samAddr)
if err == nil {
log.Debug("Connected to SAM bridge")
// we connected to sam
// send accept() command
acceptFmt := fmt.Sprintf("STREAM ACCEPT ID=%s SILENT=false", l.id)
_, err = io.WriteString(s.conn, acceptFmt)
if err != nil {
log.WithError(err).Error("Failed to send STREAM ACCEPT command")
s.Close()
}
// 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
}
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 {
if err == io.EOF {
err = fmt.Errorf("connection closed after OK")
}
err = fmt.Errorf("error reading destination: %s", err.Error())
}
if err == nil {
// Validate destination format
dest := ExtractDest(destline)
if !strings.HasPrefix(dest, "") {
err = fmt.Errorf("invalid destination format")
}
l.session.from = ExtractPairString(destline, "FROM_PORT")
l.session.to = ExtractPairString(destline, "TO_PORT")
// return wrapped connection
dest = strings.Trim(dest, "\n")
log.WithFields(logrus.Fields{
"dest": dest,
"from": l.session.from,
"to": l.session.to,
}).Debug("Accepted new I2P connection")
conn = &SAMConn{
laddr: l.laddr,
raddr: i2pkeys.I2PAddr(dest),
Conn: s.conn,
}
err = nil
} else {
log.WithError(err).Error("Failed to read destination line")
s.Close()
return
}
} else {
log.WithField("line", line).Error("Invalid SAM response")
s.Close()
err = fmt.Errorf("invalid sam line: %s", line)
}
} else {
log.WithError(err).Error("Failed to connect to SAM bridge")
s.Close()
}
}()
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-done:
return conn, err
}
}

View File

@@ -1,12 +1,15 @@
// +build nettest
package sam3
import (
"fmt"
"strings"
"testing"
)
"github.com/go-i2p/i2pkeys"
sam3opts "github.com/go-i2p/sam3/opts"
import (
. "github.com/eyedeekay/sam3/i2pkeys"
)
func Test_StreamingDial(t *testing.T) {
@@ -28,7 +31,7 @@ func Test_StreamingDial(t *testing.T) {
return
}
fmt.Println("\tBuilding tunnel")
ss, err := sam.NewStreamSession("streamTun", keys, []string{"inbound.length=1", "outbound.length=1", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"})
ss, err := sam.NewStreamSession("streamTun", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"})
if err != nil {
fmt.Println(err.Error())
t.Fail()
@@ -42,7 +45,7 @@ func Test_StreamingDial(t *testing.T) {
t.Fail()
return
}
fmt.Println("\tDialing i2p-projekt.i2p(", forumAddr.Base32(), forumAddr.DestHash().Hash(), ")")
fmt.Println("\tDialing i2p-projekt.i2p")
conn, err := ss.DialI2P(forumAddr)
if err != nil {
fmt.Println(err.Error())
@@ -148,7 +151,7 @@ func Test_StreamingServerClient(t *testing.T) {
}
func ExampleStreamSession() {
// Creates a new StreamingSession, dials to idk.i2p and gets a SAMConn
// Creates a new StreamingSession, dials to zzz.i2p and gets a SAMConn
// which behaves just like a normal net.Conn.
const samBridge = "127.0.0.1:7656"
@@ -165,12 +168,12 @@ func ExampleStreamSession() {
return
}
// See the example Option_* variables.
ss, err := sam.NewStreamSession("stream_example", keys, sam3opts.Options_Small)
ss, err := sam.NewStreamSession("stream_example", keys, Options_Small)
if err != nil {
fmt.Println(err.Error())
return
}
someone, err := sam.Lookup("idk.i2p")
someone, err := sam.Lookup("zzz.i2p")
if err != nil {
fmt.Println(err.Error())
return
@@ -190,17 +193,15 @@ func ExampleStreamSession() {
buf := make([]byte, 4096)
n, err := conn.Read(buf)
if !strings.Contains(strings.ToLower(string(buf[:n])), "http") && !strings.Contains(strings.ToLower(string(buf[:n])), "html") {
fmt.Printf("Probably failed to StreamSession.DialI2P(idk.i2p)? It replied %d bytes, but nothing that looked like http/html", n)
log.Printf("Probably failed to StreamSession.DialI2P(idk.i2p)? It replied %d bytes, but nothing that looked like http/html", n)
fmt.Printf("Probably failed to StreamSession.DialI2P(zzz.i2p)? It replied %d bytes, but nothing that looked like http/html", n)
} else {
fmt.Println("Read HTTP/HTML from idk.i2p")
log.Println("Read HTTP/HTML from idk.i2p")
fmt.Println("Read HTTP/HTML from zzz.i2p")
}
return
// Output:
// Sending HTTP GET /
// Read HTTP/HTML from idk.i2p
//Sending HTTP GET /
//Read HTTP/HTML from zzz.i2p
}
func ExampleStreamListener() {
@@ -225,7 +226,7 @@ func ExampleStreamListener() {
quit := make(chan bool)
// Client connecting to the server
go func(server i2pkeys.I2PAddr) {
go func(server I2PAddr) {
csam, err := NewSAM(samBridge)
if err != nil {
fmt.Println(err.Error())
@@ -237,7 +238,7 @@ func ExampleStreamListener() {
fmt.Println(err.Error())
return
}
cs, err := csam.NewStreamSession("client_example", keys, sam3opts.Options_Small)
cs, err := csam.NewStreamSession("client_example", keys, Options_Small)
if err != nil {
fmt.Println(err.Error())
quit <- false
@@ -260,7 +261,7 @@ func ExampleStreamListener() {
quit <- true
}(keys.Addr()) // end of client
ss, err := sam.NewStreamSession("server_example", keys, sam3opts.Options_Small)
ss, err := sam.NewStreamSession("server_example", keys, Options_Small)
if err != nil {
fmt.Println(err.Error())
return
@@ -280,5 +281,5 @@ func ExampleStreamListener() {
<-quit // waits for client to die, for example only
// Output:
// Hello world!
//Hello world!
}

157
style.css
View File

@@ -1,157 +0,0 @@
/* edgar default CSS file */
body {
font-family: "Roboto";
font-family: monospace;
text-align: justify;
background-color: #373636;
color: whitesmoke;
font-size: 1.15em;
}
ul {
width: 55%;
display: block;
}
ol {
width: 55%;
display: block;
}
li {
margin-top: 1%;
}
p {
max-width: 90%;
margin-top: 1%;
margin-left: 3%;
margin-right: 3%;
}
img {
float: left;
top: 5%;
left: 5%;
max-width: 60%;
display: inline;
}
.inline {
display: inline;
}
.link-button:focus {
outline: none;
}
.link-button:active {
color: red;
}
code {
font-family: monospace;
border-radius: 5%;
padding: 1%;
border-color: darkgray;
font-size: .9em;
}
a {
color: #C6D9FE;
padding: 1%;
}
ul li {
color: #C6D9FE;
}
iframe {
background: aliceblue;
border-radius: 15%;
margin: 2%;
}
.container {
width: 36vw;
height: 64vh;
display: inline-block;
margin: 0;
padding: 0;
}
.editor-toolbar a {
display: inline-block;
text-align: center;
text-decoration: none !important;
color: whitesmoke !important;
}
#feed {
width: 60vw;
height: unset !important;
margin: 0;
padding: 0;
float: right;
background-color: #373636;
color: whitesmoke;
border: #C6D9FE solid 1px;
}
.thread-post,
.thread {
color: whitesmoke !important;
background-color: #373636;
border: 1px solid darkgray;
font-size: inherit;
padding-top: 1%;
padding-bottom: 1%;
}
.thread-post {
margin-left: 4%;
}
input {
text-align: center;
color: whitesmoke !important;
background-color: #373636;
border: 1px solid darkgray;
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
padding-top: 1%;
padding-bottom: 1%;
}
.thread-hash {
text-align: right;
color: whitesmoke !important;
background-color: #373636;
border: 1px solid darkgray;
font-size: inherit;
padding-top: 1%;
padding-bottom: 1%;
}
.post-body {
text-align: left;
color: whitesmoke !important;
font-size: inherit;
padding-top: 1%;
padding-bottom: 1%;
}
#show {display:none; }
#hide {display:block; }
#show:target {display: block; }
#hide:target {display: none; }
#shownav {display:none; }
#hidenav {display:block; }
#shownav:target {display: block; }
#hidenav:target {display: none; }
#navbar {
float: right;
width: 10%;
}

View File

@@ -1,95 +1,40 @@
package sam3
import (
"fmt"
"net"
"net/http"
"os"
"strings"
logger "github.com/go-i2p/sam3/log"
sam3opts "github.com/go-i2p/sam3/opts"
"github.com/sirupsen/logrus"
)
func PrimarySessionString() string {
log.Debug("Determining primary session type")
_, err := http.Get("http://127.0.0.1:7070")
if err != nil {
log.WithError(err).Debug("Failed to connect to 127.0.0.1:7070, trying 127.0.0.1:7657")
_, err := http.Get("http://127.0.0.1:7657")
if err != nil {
return "MASTER"
}
log.Debug("Connected to 127.0.0.1:7657, attempting to create a PRIMARY session")
// at this point we're probably running on Java I2P and thus probably
// have a PRIMARY session. Just to be sure, try to make one, check
// for errors, then immediately close it.
testSam, err := NewSAM(SAMDefaultAddr(""))
if err != nil {
log.WithError(err).Debug("Failed to create SAM instance, assuming MASTER session")
return "MASTER"
}
newKeys, err := testSam.NewKeys()
if err != nil {
log.WithError(err).Debug("Failed to create new keys, assuming MASTER session")
return "MASTER"
}
primarySession, err := testSam.newPrimarySession("PRIMARY", "primaryTestTunnel", newKeys, sam3opts.Options_Small)
if err != nil {
log.WithError(err).Debug("Failed to create primary session, assuming MASTER session")
return "MASTER"
}
primarySession.Close()
log.Debug("Successfully created and closed a PRIMARY session")
return "PRIMARY"
}
log.Debug("Connected to 127.0.0.1:7070, assuming MASTER session")
return "MASTER"
}
var PrimarySessionSwitch string = PrimarySessionString()
func getEnv(key, fallback string) string {
logger.InitializeSAM3Logger()
value, ok := os.LookupEnv(key)
if !ok {
log.WithFields(logrus.Fields{
"key": key,
"fallback": fallback,
}).Debug("Environment variable not set, using fallback")
return fallback
}
log.WithFields(logrus.Fields{
"key": key,
"value": value,
}).Debug("Retrieved environment variable")
return value
}
// Examples and suggestions for options when creating sessions.
var (
SAM_HOST = getEnv("sam_host", "127.0.0.1")
SAM_PORT = getEnv("sam_port", "7656")
// Suitable options if you are shuffling A LOT of traffic. If unused, this
// will waste your resources.
Options_Humongous = []string{"inbound.length=3", "outbound.length=3",
"inbound.lengthVariance=1", "outbound.lengthVariance=1",
"inbound.backupQuantity=3", "outbound.backupQuantity=3",
"inbound.quantity=6", "outbound.quantity=6"}
// Suitable for shuffling a lot of traffic.
Options_Fat = []string{"inbound.length=3", "outbound.length=3",
"inbound.lengthVariance=1", "outbound.lengthVariance=1",
"inbound.backupQuantity=1", "outbound.backupQuantity=1",
"inbound.quantity=4", "outbound.quantity=4"}
// Suitable for shuffling medium amounts of traffic.
Options_Medium = []string{"inbound.length=3", "outbound.length=3",
"inbound.lengthVariance=1", "outbound.lengthVariance=1",
"inbound.backupQuantity=0", "outbound.backupQuantity=0",
"inbound.quantity=2", "outbound.quantity=2"}
// Suitable only for small dataflows, and very short lasting connections:
// You only have one tunnel in each direction, so if any of the nodes
// through which any of your two tunnels pass through go offline, there will
// be a complete halt in the dataflow, until a new tunnel is built.
Options_Small = []string{"inbound.length=3", "outbound.length=3",
"inbound.lengthVariance=1", "outbound.lengthVariance=1",
"inbound.backupQuantity=0", "outbound.backupQuantity=0",
"inbound.quantity=1", "outbound.quantity=1"}
// Does not use any anonymization, you connect directly to others tunnel
// endpoints, thus revealing your identity but not theirs. Use this only
// if you don't care.
Options_Warning_ZeroHop = []string{"inbound.length=0", "outbound.length=0",
"inbound.lengthVariance=0", "outbound.lengthVariance=0",
"inbound.backupQuantity=0", "outbound.backupQuantity=0",
"inbound.quantity=2", "outbound.quantity=2"}
)
func SAMDefaultAddr(fallforward string) string {
if fallforward == "" {
addr := net.JoinHostPort(SAM_HOST, SAM_PORT)
log.WithField("addr", addr).Debug("Using default SAM address")
return addr
}
log.WithField("addr", fallforward).Debug("Using fallforward SAM address")
return fallforward
}
func GenerateOptionString(opts []string) string {
optStr := strings.Join(opts, " ")
log.WithField("options", optStr).Debug("Generating option string")
if strings.Contains(optStr, "i2cp.leaseSetEncType") {
log.Debug("i2cp.leaseSetEncType already present in options")
return optStr
}
finalOpts := fmt.Sprintf("%s i2cp.leaseSetEncType=4,0", optStr)
log.WithField("finalOptions", finalOpts).Debug("Added default i2cp.leaseSetEncType to options")
return finalOpts
}