62 Commits

Author SHA1 Message Date
ca51512ef9 refactor resolver 2024-12-09 14:15:48 -05:00
18dc0e6a9f Delete some unused functions 2024-12-08 16:21:20 -05:00
8279daa0e9 More of the great + purge, where we make all our formatters consistent. Oh also it looks like past idk was like 'hmm, I don't know for sure if I should leave NewKeys in sam3 now that I've moved i2pkeys to it's own library. That won't be confusing to future idk, who will never have a week like future idk is having right now.' Screw that guy(past idk). 2024-12-08 13:49:52 -05:00
bfbe7f638a Fix formatting when creating sessions with ports, which was breaking primary sessions 2024-12-08 13:34:42 -05:00
493db0aaaf move options 2024-12-08 13:22:39 -05:00
d7e8c77275 fix bug in address setting function, work on removing some more redundant code and config options 2024-12-01 10:10:58 -05:00
058a02e6cc Move just a spectacular amount of config stuff into a config package because 2024-11-30 19:18:01 -05:00
86e5b7c368 Move just a spectacular amount of config stuff into a config package because 2024-11-30 18:49:30 -05:00
1c1652f234 Move just a spectacular amount of config stuff into a config package because 2024-11-30 18:34:01 -05:00
1f27cb656e Expand the common package some more to include reply parser from goSam, move version to smarter structure 2024-11-29 21:07:26 -05:00
82eeba6275 Start abstracting away UDP session management 2024-11-24 19:11:04 -05:00
eea1f916cd split on space not newline 2024-11-24 01:08:43 -05:00
ac4ef13405 automatically chunk datagrams 2024-11-23 14:19:02 -05:00
dd2d0c029f Work on refactoring and potential concurrency issues, but I think primary is broken on master, users should stick to tagged versions unless they want to help 2024-11-23 00:47:49 -05:00
d8dafef068 fmt 2024-11-22 20:35:39 -05:00
2dc0890d17 if AcceptI2P errors out in an Accept call, close the resulting conn and return nil 2024-11-22 20:05:43 -05:00
5303ea6c34 if AcceptI2P errors out in an Accept call, close the resulting conn and return nil 2024-11-22 20:05:34 -05:00
fdfa4240fb Add the ability to set and use DatagramOptions in a DatagramSession 2024-11-22 19:16:35 -05:00
a22cde30eb use Sprintf instead of concatenation for command 2024-11-22 18:48:09 -05:00
88786afa0c Use external logger 2024-11-21 18:46:29 -05:00
b5cacb3ece remove the helper library 2024-11-20 23:41:25 -05:00
37205fc8a2 organize imports 2024-11-20 23:32:26 -05:00
ba5b2ca853 update release process 2024-11-16 16:22:38 -05:00
f4ca627cd8 update examples 2024-11-14 10:43:00 -05:00
7fc3116088 Bump version 2024-11-13 14:41:50 -05:00
d3fb670d66 Fix go mods 2024-11-13 14:40:27 -05:00
25751504b9 Fix up import paths 2024-11-09 11:54:54 -05:00
a745742ee1 setup auto-assign workflow 2024-11-08 15:01:19 -05:00
def28bbf7c Merge branch 'master' of github.com:eyedeekay/sam3 2024-11-08 12:55:50 -05:00
e6c161ed99 change module path 2024-11-08 12:54:40 -05:00
idk
3ebfb85f8a Merge pull request #13 from hkh4n/refactor
Refactor
2024-11-03 21:42:55 +00:00
14d0b22a28 add Read() and Write() methods 2024-11-02 23:26:10 -04:00
67554060fb minor typo fix 2024-11-02 22:20:52 -04:00
8b09ca7502 more uniform naming 2024-11-02 21:57:11 -04:00
idk
97d1c812d3 Merge pull request #12 from hkh4n/logging
logging naming convention hotfix
2024-10-23 17:34:38 +00:00
5be3e27599 Attempt to fix naming collision with other libs 2024-10-23 00:03:16 -04:00
ecba767d91 grammar 2024-10-17 14:51:19 -04:00
idk
5149b7e504 Merge pull request #11 from hkh4n/logging
Added logging
2024-10-17 18:51:15 +00:00
67c0c9288a Updated README.md to reflect logging 2024-10-17 14:42:19 -04:00
10f42af061 .Info -> .Debug 2024-10-16 17:21:44 -04:00
aa63210c3c WORKING FIX 2024-10-16 11:23:49 -04:00
4e1b426230 name collision 2024-10-16 10:51:21 -04:00
a372049be9 added logging to suggestedOptions.go
-fixed name collision in stream_test.go
2024-10-16 10:35:43 -04:00
6e2cc71a92 added logging to streamListener.go
-added dest in ExtractDest()
2024-10-16 10:24:30 -04:00
988769ed5a fixed name collision in primary_stream_test.go 2024-10-16 10:18:06 -04:00
504b7fb48b added logging to stream.go
-added better error handling to Lookup()
2024-10-16 10:14:57 -04:00
c10b6b284c finished up sam3.go 2024-10-16 10:02:47 -04:00
c2ad35a952 added logging to sam3.go & bumped i2pkeys v0.33.7 -> v0.33.8
-removed name collisions with "log"
2024-10-15 23:01:00 -04:00
9e6d0489cf added logging to resolver.go 2024-10-15 22:26:33 -04:00
d05428754b added logging to raw.go 2024-10-15 22:20:33 -04:00
41d9bd0150 Merge branch 'master' into logging
# Conflicts:
#	go.mod
#	go.sum
2024-10-15 22:10:49 -04:00
fd22f227b1 finished up primary.go 2024-10-15 22:09:18 -04:00
0415adf35d added logging to primary.go
-added fromPort, toPort in NewUniqueStreamSubsession
2024-10-15 21:01:36 -04:00
3655462ca4 added logging to emit-options.go 2024-10-15 17:01:43 -04:00
4bb6f81c40 added logging to emit.go 2024-10-15 12:54:17 -04:00
21e7b5a177 added logging to datagram.go 2024-10-15 12:39:20 -04:00
4166a2c827 added log.go & logging for config.go 2024-10-15 12:23:24 -04:00
cd2a4f072e update i2pkeys library to new version 2024-09-17 19:40:10 -04:00
818b5249bc Add credit for contribution to release info 2024-09-17 19:35:15 -04:00
idk
83f9866de4 Merge pull request #10 from hkh4n/refactor
Refactored error handling in sam3.go
2024-09-12 12:26:56 -04:00
b4293f755e refactored error handling in NewKeys() and newGenericSessionWithSignatureAndPorts() 2024-09-10 19:55:28 -04:00
2bde2687b3 refactored error handling in NewSAM(), included "fmt" 2024-09-10 19:21:06 -04:00
34 changed files with 2036 additions and 906 deletions

20
.github/workflows/auto-assign.yml vendored Normal file
View File

@ -0,0 +1,20 @@
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,3 +1,4 @@
*~
*.swp
README.md.asc
log

View File

@ -1,13 +1,14 @@
USER_GH=eyedeekay
VERSION=0.33.7
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)"
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)
@ -23,7 +24,7 @@ copier:
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 gofmt -w -s {} \;
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,17 +1,9 @@
# README #
## !!IMPORTANT!!
In the next version, I'll be moving the `i2pkeys` directory to it's own repository
so I can avoid import cycle headaches. Please migrate to the new `i2pkeys` repository
before upgrading your sam3 dependencies. You can probably do this by running:
```sh
find . -name '*.go' -exec sed -i 's|github.com/eyedeekay/sam3/i2pkeys|github.com/eyedeekay/i2pkeys|g' {} \;
```
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.
@ -52,8 +44,8 @@ This library is much better than ccondom (that use BOB), much more stable and mu
package main
import (
"github.com/eyedeekay/sam3"
"github.com/eyedeekay/sam3/i2pkeys"
"github.com/go-i2p/sam3"
"github.com/go-i2p/sam3/i2pkeys"
"fmt"
)
@ -96,6 +88,26 @@ Error handling was omitted in the above code for readability.
* `go test -tags=nettest` 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.

View File

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

60
common/formatter.go Normal file
View File

@ -0,0 +1,60 @@
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, "")
}

87
common/reply.go Normal file
View File

@ -0,0 +1,87 @@
// 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"
}

140
common/udp.go Normal file
View File

@ -0,0 +1,140 @@
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
}

19
common/version.go Normal file
View File

@ -0,0 +1,19 @@
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,
}
)

411
config.go
View File

@ -3,61 +3,33 @@ package sam3
import (
"fmt"
"math/rand"
"net"
"strconv"
"strings"
"time"
"github.com/eyedeekay/i2pkeys"
"github.com/sirupsen/logrus"
"github.com/go-i2p/i2pkeys"
"github.com/go-i2p/sam3/common"
"github.com/go-i2p/sam3/config"
)
const DEFAULT_LEASESET_TYPE = "i2cp.leaseSetEncType=4"
// I2PConfig is a struct which manages I2P configuration options
type I2PConfig struct {
SamHost string
SamPort string
TunName string
SamMin string
SamMax string
Fromport string
Toport string
Style string
TunType string
common.SAMFormatter
config.SessionOptions
config.TransportOptions
config.TunnelOptions
config.EncryptedLeaseSetOptions
DestinationKeys i2pkeys.I2PKeys
SigType string
EncryptLeaseSet string
LeaseSetKey string
LeaseSetPrivateKey string
LeaseSetPrivateSigningKey string
LeaseSetKeys i2pkeys.I2PKeys
InAllowZeroHop string
OutAllowZeroHop string
InLength string
OutLength string
InQuantity string
OutQuantity string
InVariance string
OutVariance string
InBackupQuantity string
OutBackupQuantity string
FastRecieve string
UseCompression string
MessageReliability string
CloseIdle string
CloseIdleTime string
ReduceIdle string
ReduceIdleTime string
ReduceIdleQuantity string
LeaseSetEncryption string
//Streaming Library options
// Streaming Library options
AccessListType string
AccessList []string
}
// Sam returns the SAM address in the form of "host:port"
func (f *I2PConfig) Sam() string {
host := "127.0.0.1"
port := "7656"
@ -67,9 +39,14 @@ func (f *I2PConfig) Sam() string {
if f.SamPort != "" {
port = f.SamPort
}
return host + ":" + port
log.WithFields(logrus.Fields{
"host": host,
"port": port,
}).Debug("SAM address constructed")
return fmt.Sprintf("%s:%s", host, port)
}
// SetSAMAddress sets the SAM address from a string in the form of "host:port"
func (f *I2PConfig) SetSAMAddress(addr string) {
hp := strings.Split(addr, ":")
if len(hp) == 1 {
@ -77,158 +54,89 @@ func (f *I2PConfig) SetSAMAddress(addr string) {
} 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"
}
}
f.SamPort = "7656"
f.SamHost = "127.0.0.1"
log.WithFields(logrus.Fields{
"host": f.SamHost,
"port": f.SamPort,
}).Debug("SAM address set")
i2pkeys.DefaultSAMAddress = f.Sam()
}
// ID returns the tunnel name in the form of "ID=name"
func (f *I2PConfig) ID() string {
if f.TunName == "" {
if f.NickName == "" {
b := make([]byte, 12)
for i := range b {
b[i] = "abcdefghijklmnopqrstuvwxyz"[rand.Intn(len("abcdefghijklmnopqrstuvwxyz"))]
}
f.TunName = string(b)
f.NickName = string(b)
log.WithField("NickName", f.NickName).Debug("Generated random tunnel name")
}
return " ID=" + f.TunName + " "
}
func (f *I2PConfig) Leasesetsettings() (string, string, string) {
var r, s, t string
if f.LeaseSetKey != "" {
r = " i2cp.leaseSetKey=" + f.LeaseSetKey + " "
}
if f.LeaseSetPrivateKey != "" {
s = " i2cp.leaseSetPrivateKey=" + f.LeaseSetPrivateKey + " "
}
if f.LeaseSetPrivateSigningKey != "" {
t = " i2cp.leaseSetPrivateSigningKey=" + f.LeaseSetPrivateSigningKey + " "
}
return r, s, t
}
func (f *I2PConfig) FromPort() string {
if f.samMax() < 3.1 {
return ""
}
if f.Fromport != "0" {
return " FROM_PORT=" + f.Fromport + " "
}
return ""
}
func (f *I2PConfig) ToPort() string {
if f.samMax() < 3.1 {
return ""
}
if f.Toport != "0" {
return " TO_PORT=" + f.Toport + " "
}
return ""
}
func (f *I2PConfig) SessionStyle() string {
if f.Style != "" {
return " STYLE=" + f.Style + " "
}
return " STYLE=STREAM "
}
func (f *I2PConfig) samMax() float64 {
i, err := strconv.Atoi(f.SamMax)
if err != nil {
return 3.1
}
return float64(i)
return fmt.Sprintf(" ID=%s ", f.NickName)
}
// MinSAM returns the minimum SAM version required in major.minor form
func (f *I2PConfig) MinSAM() string {
if f.SamMin == "" {
return "3.0"
}
return f.SamMin
min, _ := f.GetVersions()
return string(min)
}
// MaxSAM returns the maximum SAM version required in major.minor form
func (f *I2PConfig) MaxSAM() string {
if f.SamMax == "" {
return "3.1"
}
return f.SamMax
_, 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 {
return " DESTINATION=" + f.DestinationKeys.String() + " "
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 "
}
func (f *I2PConfig) SignatureType() string {
if f.samMax() < 3.1 {
return ""
}
if f.SigType != "" {
return " SIGNATURE_TYPE=" + f.SigType + " "
}
return ""
}
func (f *I2PConfig) EncryptLease() string {
if f.EncryptLeaseSet == "true" {
return " i2cp.encryptLeaseSet=true "
}
return ""
}
func (f *I2PConfig) Reliability() string {
if f.MessageReliability != "" {
return " i2cp.messageReliability=" + f.MessageReliability + " "
}
return ""
}
func (f *I2PConfig) Reduce() string {
if f.ReduceIdle == "true" {
return "i2cp.reduceOnIdle=" + f.ReduceIdle + "i2cp.reduceIdleTime=" + f.ReduceIdleTime + "i2cp.reduceQuantity=" + f.ReduceIdleQuantity
}
return ""
}
func (f *I2PConfig) Close() string {
if f.CloseIdle == "true" {
return "i2cp.closeOnIdle=" + f.CloseIdle + "i2cp.closeIdleTime=" + f.CloseIdleTime
}
return ""
}
func (f *I2PConfig) DoZero() string {
r := ""
if f.InAllowZeroHop == "true" {
r += " inbound.allowZeroHop=" + f.InAllowZeroHop + " "
}
if f.OutAllowZeroHop == "true" {
r += " outbound.allowZeroHop= " + f.OutAllowZeroHop + " "
}
if f.FastRecieve == "true" {
r += " " + f.FastRecieve + " "
}
return r
}
// Print returns the full config as a string
func (f *I2PConfig) Print() []string {
lsk, lspk, lspsk := f.Leasesetsettings()
return []string{
//f.targetForPort443(),
"inbound.length=" + f.InLength,
"outbound.length=" + f.OutLength,
"inbound.lengthVariance=" + f.InVariance,
"outbound.lengthVariance=" + f.OutVariance,
"inbound.backupQuantity=" + f.InBackupQuantity,
"outbound.backupQuantity=" + f.OutBackupQuantity,
"inbound.quantity=" + f.InQuantity,
"outbound.quantity=" + f.OutQuantity,
f.DoZero(),
// 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,
"i2cp.gzip=" + f.UseCompression,
f.DoFastReceive(),
f.UsesCompression(),
f.Reduce(),
f.Close(),
f.Reliability(),
@ -240,71 +148,78 @@ func (f *I2PConfig) Print() []string {
}
}
// Accesslisttype returns the access list type
func (f *I2PConfig) Accesslisttype() string {
if f.AccessListType == "whitelist" {
log.Debug("Access list type set to whitelist")
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 := ""
for _, s := range f.AccessList {
r += s + ","
}
return "i2cp.accessList=" + strings.TrimSuffix(r, ",")
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 ""
}
func (f *I2PConfig) LeaseSetEncryptionType() string {
if f.LeaseSetEncryption == "" {
return "i2cp.leaseSetEncType=4,0"
}
for _, s := range strings.Split(f.LeaseSetEncryption, ",") {
if _, err := strconv.Atoi(s); err != nil {
panic("Invalid encrypted leaseSet type: " + s)
}
}
return "i2cp.leaseSetEncType=" + f.LeaseSetEncryption
}
// NewConfig returns a new config with default values or updates them with functional arguments
func NewConfig(opts ...func(*I2PConfig) error) (*I2PConfig, error) {
var config I2PConfig
config.SamHost = "127.0.0.1"
config.SamPort = "7656"
config.SamMin = "3.0"
config.SamMax = "3.2"
config.TunName = ""
config.TunType = "server"
config.Style = "STREAM"
config.InLength = "3"
config.OutLength = "3"
config.InQuantity = "2"
config.OutQuantity = "2"
config.InVariance = "1"
config.OutVariance = "1"
config.InBackupQuantity = "3"
config.OutBackupQuantity = "3"
config.InAllowZeroHop = "false"
config.OutAllowZeroHop = "false"
config.EncryptLeaseSet = "false"
config.LeaseSetKey = ""
config.LeaseSetPrivateKey = ""
config.LeaseSetPrivateSigningKey = ""
config.FastRecieve = "false"
config.UseCompression = "true"
config.ReduceIdle = "false"
config.ReduceIdleTime = "15"
config.ReduceIdleQuantity = "4"
config.CloseIdle = "false"
config.CloseIdleTime = "300000"
config.MessageReliability = "none"
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
@ -312,67 +227,3 @@ func NewConfig(opts ...func(*I2PConfig) error) (*I2PConfig, error) {
}
return &config, nil
}
// 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
}
// 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
}
// 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.I2PKeys
keys, err = s.EnsureKeyfile(cfg.Keyfile)
if err == nil {
// create session
session, err = s.NewStreamSession(cfg.Session, keys, cfg.Opts.AsList())
}
}
return
}
// 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.I2PKeys
keys, err = s.EnsureKeyfile(cfg.Keyfile)
if err == nil {
// determine udp port
var portstr string
_, portstr, err = net.SplitHostPort(cfg.Addr)
if IgnorePortError(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
}

83
config/leaseset.go Normal file
View File

@ -0,0 +1,83 @@
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 "", "", ""
}

5
config/log.go Normal file
View File

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

84
config/session.go Normal file
View File

@ -0,0 +1,84 @@
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 "
}

95
config/transport.go Normal file
View File

@ -0,0 +1,95 @@
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 ""
}

69
config/tunnel.go Normal file
View File

@ -0,0 +1,69 @@
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,11 +3,15 @@ package sam3
import (
"bytes"
"errors"
"fmt"
"net"
"strconv"
"sync"
"time"
"github.com/eyedeekay/i2pkeys"
"github.com/sirupsen/logrus"
"github.com/go-i2p/i2pkeys"
"github.com/go-i2p/sam3/common"
)
// The DatagramSession implements net.PacketConn. It works almost like ordinary
@ -18,76 +22,106 @@ type DatagramSession 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
remoteAddr *i2pkeys.I2PAddr // optional remote I2P address
common.UDPSession
*DatagramOptions
}
// Creates a new datagram session. udpPort is the UDP port SAM is listening on,
// and if you set it to zero, it will use SAMs standard UDP port.
func (s *SAM) NewDatagramSession(id string, keys i2pkeys.I2PKeys, options []string, udpPort int) (*DatagramSession, error) {
if udpPort > 65335 || udpPort < 0 {
return nil, errors.New("udpPort needs to be in the intervall 0-65335")
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,
}
if udpPort == 0 {
udpPort = 7655
}
lhost, _, err := SplitHostPort(s.conn.LocalAddr().String())
udpconn, err := common.NewUDPSession(udpSessionConfig)
if err != nil {
s.Close()
log.WithError(err).Error("Failed to create UDP session")
return nil, err
}
lUDPAddr, err := net.ResolveUDPAddr("udp4", lhost+":0")
if err != nil {
return nil, err
}
udpconn, err := net.ListenUDP("udp4", lUDPAddr)
if err != nil {
return nil, err
}
rhost, _, err := 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())
_, 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.newGenericSession("DATAGRAM", id, keys, options, []string{" PORT=" + lport})
if err != nil {
log.WithError(err).Error("Failed to create generic session")
return nil, err
}
return &DatagramSession{s.address, id, conn, udpconn, keys, rUDPAddr, nil}, nil
if len(datagramOptions) > 0 {
return &DatagramSession{
samAddr: s.address,
id: id,
conn: conn,
keys: keys,
UDPSession: *udpconn,
DatagramOptions: &datagramOptions[0],
}, nil
}
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
}
func (s *DatagramSession) B32() string {
return s.keys.Addr().Base32()
b32 := s.keys.Addr().Base32()
log.WithField("b32", b32).Debug("Generated B32 address")
return b32
}
func (s *DatagramSession) Dial(net string, addr string) (*DatagramSession, error) {
func (s *DatagramSession) Dial(net, addr string) (*DatagramSession, error) {
log.WithFields(logrus.Fields{
"net": net,
"addr": addr,
}).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)
@ -99,6 +133,7 @@ func (s *DatagramSession) DialI2PRemote(net string, addr net.Addr) (*DatagramSes
}
func (s *DatagramSession) RemoteAddr() net.Addr {
log.WithField("remoteAddr", s.remoteAddr).Debug("Getting remote address")
return s.remoteAddr
}
@ -106,27 +141,37 @@ func (s *DatagramSession) RemoteAddr() net.Addr {
// 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) {
// extra bytes to read the remote address of incomming datagram
buf := make([]byte, len(b)+4096)
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)
for {
// very basic protection: only accept incomming UDP messages from the IP of the SAM bridge
var saddr *net.UDPAddr
n, saddr, err = s.udpconn.ReadFromUDP(buf)
n, saddr, err = s.UDPSession.Conn.ReadFromUDP(buf)
if err != nil {
log.WithError(err).Error("Failed to read from UDP")
return 0, i2pkeys.I2PAddr(""), err
}
if bytes.Equal(saddr.IP, s.rUDPAddr.IP) {
if bytes.Equal(saddr.IP, s.UDPSession.RemoteAddr.IP) {
continue
}
break
}
i := bytes.IndexByte(buf, byte('\n'))
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.")
}
raddr, err := i2pkeys.NewI2PAddrFromString(string(buf[:i]))
if err != nil {
log.WithError(err).Error("Could not parse incoming message remote address")
return 0, i2pkeys.I2PAddr(""), errors.New("Could not parse incomming message remote address: " + err.Error())
}
// shift out the incomming address to contain only the data received
@ -135,46 +180,154 @@ 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) {
header := []byte("3.1 " + s.id + " " + addr.String() + "\n")
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()))
msg := append(header, b...)
n, err = s.udpconn.WriteToUDP(msg, s.rUDPAddr)
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")
}
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.udpconn.Close()
err2 := s.UDPSession.Conn.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 {
return s.keys.Addr()
addr := s.keys.Addr()
log.WithField("localI2PAddr", addr).Debug("Getting local I2P address")
return addr
}
// Implements net.PacketConn
@ -187,12 +340,14 @@ func (s *DatagramSession) Addr() net.Addr {
}
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
}
@ -200,19 +355,23 @@ 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 {
return s.udpconn.SetDeadline(t)
log.WithField("deadline", t).Debug("Setting deadline")
return s.UDPSession.Conn.SetDeadline(t)
}
// Sets read deadline for the DatagramSession. Implements net.PacketConn
func (s *DatagramSession) SetReadDeadline(t time.Time) error {
return s.udpconn.SetReadDeadline(t)
log.WithField("readDeadline", t).Debug("Setting read deadline")
return s.UDPSession.Conn.SetReadDeadline(t)
}
// Sets the write deadline for the DatagramSession. Implements net.Packetconn.
func (s *DatagramSession) SetWriteDeadline(t time.Time) error {
return s.udpconn.SetWriteDeadline(t)
log.WithField("writeDeadline", t).Debug("Setting write deadline")
return s.UDPSession.Conn.SetWriteDeadline(t)
}
func (s *DatagramSession) SetWriteBuffer(bytes int) error {
return s.udpconn.SetWriteBuffer(bytes)
log.WithField("bytes", bytes).Debug("Setting write buffer")
return s.UDPSession.Conn.SetWriteBuffer(bytes)
}

View File

@ -2,9 +2,10 @@ package sam3
import (
"fmt"
"log"
"testing"
"time"
sam3opts "github.com/go-i2p/sam3/opts"
)
func Test_DatagramServerClient(t *testing.T) {
@ -103,7 +104,7 @@ func ExampleDatagramSession() {
myself := keys.Addr()
// See the example Option_* variables.
dg, err := sam.NewDatagramSession("DGTUN", keys, Options_Small, 0)
dg, err := sam.NewDatagramSession("DGTUN", keys, sam3opts.Options_Small, 0)
if err != nil {
fmt.Println(err.Error())
return
@ -123,12 +124,12 @@ func ExampleDatagramSession() {
fmt.Println(err.Error())
return
}
log.Println("Got message: '" + string(buf[:n]) + "'")
fmt.Println("Got message: '" + string(buf[:n]) + "'")
fmt.Println("Got message: " + string(buf[:n]))
return
// Output:
//Got message: Hello myself!
// Got message: Hello myself!
}
func ExampleMiniDatagramSession() {
@ -149,7 +150,7 @@ func ExampleMiniDatagramSession() {
myself := keys.Addr()
// See the example Option_* variables.
dg, err := sam.NewDatagramSession("MINIDGTUN", keys, Options_Small, 0)
dg, err := sam.NewDatagramSession("MINIDGTUN", keys, sam3opts.Options_Small, 0)
if err != nil {
fmt.Println(err.Error())
return
@ -179,5 +180,5 @@ func ExampleMiniDatagramSession() {
return
// Output:
//Got message: Hello myself!
// Got message: Hello myself!
}

View File

@ -4,6 +4,9 @@ import (
"fmt"
"strconv"
"strings"
"time"
"github.com/sirupsen/logrus"
)
// Option is a SAMEmit Option
@ -14,14 +17,18 @@ 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)
}
}
@ -31,12 +38,17 @@ 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
}
}
@ -45,6 +57,7 @@ func SetSAMAddress(s string) func(*SAMEmit) error {
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
}
}
@ -54,12 +67,15 @@ 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")
}
}
@ -67,7 +83,8 @@ func SetSAMPort(s string) func(*SAMEmit) error {
// SetName sets the host of the SAMEmit's SAM bridge
func SetName(s string) func(*SAMEmit) error {
return func(c *SAMEmit) error {
c.I2PConfig.TunName = s
c.I2PConfig.SessionOptions.NickName = s
log.WithField("name", s).Debug("Set tunnel name")
return nil
}
}
@ -76,9 +93,11 @@ func SetName(s string) func(*SAMEmit) error {
func SetInLength(u int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if u < 7 && u >= 0 {
c.I2PConfig.InLength = strconv.Itoa(u)
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")
}
}
@ -87,9 +106,11 @@ func SetInLength(u int) func(*SAMEmit) error {
func SetOutLength(u int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if u < 7 && u >= 0 {
c.I2PConfig.OutLength = strconv.Itoa(u)
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")
}
}
@ -98,9 +119,11 @@ func SetOutLength(u int) func(*SAMEmit) error {
func SetInVariance(i int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if i < 7 && i > -7 {
c.I2PConfig.InVariance = strconv.Itoa(i)
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")
}
}
@ -109,9 +132,11 @@ func SetInVariance(i int) func(*SAMEmit) error {
func SetOutVariance(i int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if i < 7 && i > -7 {
c.I2PConfig.OutVariance = strconv.Itoa(i)
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")
}
}
@ -120,9 +145,11 @@ func SetOutVariance(i int) func(*SAMEmit) error {
func SetInQuantity(u int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if u <= 16 && u > 0 {
c.I2PConfig.InQuantity = strconv.Itoa(u)
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")
}
}
@ -131,9 +158,11 @@ func SetInQuantity(u int) func(*SAMEmit) error {
func SetOutQuantity(u int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if u <= 16 && u > 0 {
c.I2PConfig.OutQuantity = strconv.Itoa(u)
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")
}
}
@ -142,9 +171,11 @@ func SetOutQuantity(u int) func(*SAMEmit) error {
func SetInBackups(u int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if u < 6 && u >= 0 {
c.I2PConfig.InBackupQuantity = strconv.Itoa(u)
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")
}
}
@ -153,9 +184,11 @@ func SetInBackups(u int) func(*SAMEmit) error {
func SetOutBackups(u int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if u < 6 && u >= 0 {
c.I2PConfig.OutBackupQuantity = strconv.Itoa(u)
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")
}
}
@ -163,11 +196,8 @@ func SetOutBackups(u int) func(*SAMEmit) error {
// SetEncrypt tells the router to use an encrypted leaseset
func SetEncrypt(b bool) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if b {
c.I2PConfig.EncryptLeaseSet = "true"
return nil
}
c.I2PConfig.EncryptLeaseSet = "false"
c.I2PConfig.EncryptLeaseSet = b
log.WithField("encrypt", b).Debug("Set lease set encryption")
return nil
}
}
@ -176,6 +206,7 @@ func SetEncrypt(b bool) func(*SAMEmit) error {
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
}
}
@ -184,6 +215,7 @@ func SetLeaseSetKey(s string) func(*SAMEmit) error {
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
}
}
@ -192,6 +224,7 @@ func SetLeaseSetPrivateKey(s string) func(*SAMEmit) error {
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
}
}
@ -199,7 +232,8 @@ func SetLeaseSetPrivateSigningKey(s string) func(*SAMEmit) error {
// SetMessageReliability sets the host of the SAMEmit's SAM bridge
func SetMessageReliability(s string) func(*SAMEmit) error {
return func(c *SAMEmit) error {
c.I2PConfig.MessageReliability = s
c.I2PConfig.TransportOptions.MessageReliability = s
log.WithField("messageReliability", s).Debug("Set message reliability")
return nil
}
}
@ -207,11 +241,8 @@ func SetMessageReliability(s string) func(*SAMEmit) error {
// SetAllowZeroIn tells the tunnel to accept zero-hop peers
func SetAllowZeroIn(b bool) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if b {
c.I2PConfig.InAllowZeroHop = "true"
return nil
}
c.I2PConfig.InAllowZeroHop = "false"
c.I2PConfig.InAllowZeroHop = b
log.WithField("allowZeroIn", b).Debug("Set allow zero-hop inbound")
return nil
}
}
@ -219,11 +250,8 @@ func SetAllowZeroIn(b bool) func(*SAMEmit) error {
// SetAllowZeroOut tells the tunnel to accept zero-hop peers
func SetAllowZeroOut(b bool) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if b {
c.I2PConfig.OutAllowZeroHop = "true"
return nil
}
c.I2PConfig.OutAllowZeroHop = "false"
c.I2PConfig.OutAllowZeroHop = b
log.WithField("allowZeroOut", b).Debug("Set allow zero-hop outbound")
return nil
}
}
@ -236,6 +264,7 @@ func SetCompress(b bool) func(*SAMEmit) error {
return nil
}
c.I2PConfig.UseCompression = "false"
log.WithField("compress", b).Debug("Set compression")
return nil
}
}
@ -244,10 +273,11 @@ func SetCompress(b bool) func(*SAMEmit) error {
func SetFastRecieve(b bool) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if b {
c.I2PConfig.FastRecieve = "true"
c.I2PConfig.TransportOptions.FastReceive = "true"
return nil
}
c.I2PConfig.FastRecieve = "false"
c.I2PConfig.TransportOptions.FastReceive = "false"
log.WithField("fastReceive", b).Debug("Set fast receive")
return nil
}
}
@ -255,11 +285,8 @@ func SetFastRecieve(b bool) func(*SAMEmit) error {
// SetReduceIdle tells the connection to reduce it's tunnels during extended idle time.
func SetReduceIdle(b bool) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if b {
c.I2PConfig.ReduceIdle = "true"
return nil
}
c.I2PConfig.ReduceIdle = "false"
c.I2PConfig.ReduceIdle = b
log.WithField("reduceIdle", b).Debug("Set reduce idle")
return nil
}
}
@ -267,11 +294,17 @@ func SetReduceIdle(b bool) func(*SAMEmit) error {
// SetReduceIdleTime sets the time to wait before reducing tunnels to idle levels
func SetReduceIdleTime(u int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
c.I2PConfig.ReduceIdleTime = "300000"
c.I2PConfig.TransportOptions.ReduceIdleTimeout = 300000
if u >= 6 {
c.I2PConfig.ReduceIdleTime = strconv.Itoa((u * 60) * 1000)
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)
}
}
@ -279,9 +312,10 @@ func SetReduceIdleTime(u int) func(*SAMEmit) error {
// SetReduceIdleTimeMs sets the time to wait before reducing tunnels to idle levels in milliseconds
func SetReduceIdleTimeMs(u int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
c.I2PConfig.ReduceIdleTime = "300000"
c.I2PConfig.TransportOptions.ReduceIdleTimeout = 300000
if u >= 300000 {
c.I2PConfig.ReduceIdleTime = strconv.Itoa(u)
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)
@ -292,9 +326,11 @@ func SetReduceIdleTimeMs(u int) func(*SAMEmit) error {
func SetReduceIdleQuantity(u int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if u < 5 {
c.I2PConfig.ReduceIdleQuantity = strconv.Itoa(u)
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")
}
}
@ -302,11 +338,7 @@ func SetReduceIdleQuantity(u int) func(*SAMEmit) error {
// SetCloseIdle tells the connection to close it's tunnels during extended idle time.
func SetCloseIdle(b bool) func(*SAMEmit) error {
return func(c *SAMEmit) error {
if b {
c.I2PConfig.CloseIdle = "true"
return nil
}
c.I2PConfig.CloseIdle = "false"
c.I2PConfig.CloseIdle = b
return nil
}
}
@ -314,11 +346,17 @@ func SetCloseIdle(b bool) func(*SAMEmit) error {
// SetCloseIdleTime sets the time to wait before closing tunnels to idle levels
func SetCloseIdleTime(u int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
c.I2PConfig.CloseIdleTime = "300000"
c.I2PConfig.TransportOptions.CloseIdleTimeout = 300000
if u >= 6 {
c.I2PConfig.CloseIdleTime = strconv.Itoa((u * 60) * 1000)
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)
}
}
@ -326,9 +364,10 @@ func SetCloseIdleTime(u int) func(*SAMEmit) error {
// SetCloseIdleTimeMs sets the time to wait before closing tunnels to idle levels in milliseconds
func SetCloseIdleTimeMs(u int) func(*SAMEmit) error {
return func(c *SAMEmit) error {
c.I2PConfig.CloseIdleTime = "300000"
c.I2PConfig.TransportOptions.CloseIdleTimeout = 300000
if u >= 300000 {
c.I2PConfig.CloseIdleTime = strconv.Itoa(u)
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)
@ -340,15 +379,19 @@ 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)")
@ -362,8 +405,10 @@ func SetAccessList(s []string) func(*SAMEmit) error {
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
}
}

52
emit.go
View File

@ -2,9 +2,10 @@ package sam3
import (
"fmt"
"log"
"net"
"strings"
"github.com/sirupsen/logrus"
)
type SAMEmit struct {
@ -13,11 +14,14 @@ type SAMEmit struct {
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 {
return fmt.Sprintf("HELLO VERSION MIN=%s MAX=%s \n", e.I2PConfig.MinSAM(), e.I2PConfig.MaxSAM())
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 {
@ -25,7 +29,9 @@ func (e *SAMEmit) HelloBytes() []byte {
}
func (e *SAMEmit) GenerateDestination() string {
return fmt.Sprintf("DEST GENERATE %s \n", e.I2PConfig.SignatureType())
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 {
@ -33,40 +39,25 @@ func (e *SAMEmit) GenerateDestinationBytes() []byte {
}
func (e *SAMEmit) Lookup(name string) string {
return fmt.Sprintf("NAMING LOOKUP NAME=%s \n", name)
lookup := fmt.Sprintf("NAMING LOOKUP NAME=%s \n", name)
log.WithField("lookup", lookup).Debug("Generated NAMING LOOKUP command")
return lookup
}
func (e *SAMEmit) LookupBytes(name string) []byte {
return []byte(e.Lookup(name))
}
func (e *SAMEmit) Create() string {
return fmt.Sprintf(
// //1 2 3 4 5 6 7
"SESSION CREATE %s%s%s%s%s%s%s \n",
e.I2PConfig.SessionStyle(), //1
e.I2PConfig.FromPort(), //2
e.I2PConfig.ToPort(), //3
e.I2PConfig.ID(), //4
e.I2PConfig.DestinationKey(), // 5
e.I2PConfig.SignatureType(), // 6
e.OptStr(), // 7
)
}
func (e *SAMEmit) CreateBytes() []byte {
log.Println("sam command: " + e.Create())
return []byte(e.Create())
}
func (e *SAMEmit) Connect(dest string) string {
return fmt.Sprintf(
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 {
@ -74,12 +65,14 @@ func (e *SAMEmit) ConnectBytes(dest string) []byte {
}
func (e *SAMEmit) Accept() string {
return fmt.Sprintf(
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 {
@ -90,9 +83,11 @@ 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
}
@ -101,6 +96,7 @@ func IgnorePortError(err error) error {
return nil
}
if strings.Contains(err.Error(), "missing port in address") {
log.Debug("Ignoring 'missing port in address' error")
err = nil
}
return err
@ -110,10 +106,14 @@ func SplitHostPort(hostport string) (string, string, error) {
host, port, err := net.SplitHostPort(hostport)
if err != nil {
if IgnorePortError(err) == nil {
log.Println("host: " + hostport)
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
}

14
go.mod
View File

@ -1,5 +1,13 @@
module github.com/eyedeekay/sam3
module github.com/go-i2p/sam3
go 1.12
go 1.23.3
require github.com/eyedeekay/i2pkeys v0.33.7
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

20
go.sum
View File

@ -1,2 +1,18 @@
github.com/eyedeekay/i2pkeys v0.33.7 h1:cxqHSkl6b2lHyPJUtIQZBiipYf7NQVYqM1d3ub0MI4k=
github.com/eyedeekay/i2pkeys v0.33.7/go.mod h1:W9KCm9lqZ+Ozwl3dwcgnpPXAML97+I8Jiht7o5A8YBM=
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=

View File

@ -1,130 +0,0 @@
package sam
import (
"io/ioutil"
"log"
"net"
"os"
"github.com/eyedeekay/i2pkeys"
"github.com/eyedeekay/sam3"
)
// HEY! If you're looking at this, there's a good chance that `github.com/eyedeekay/onramp`
// is a better fit! Check it out.
func NetListener(name, samaddr, keyspath string) (net.Listener, error) {
return I2PListener(name, sam3.SAMDefaultAddr(samaddr), keyspath)
}
// I2PListener is a convenience function which takes a SAM tunnel name, a SAM address and a filename.
// If the file contains I2P keys, it will create a service using that address. If the file does not
// exist, keys will be generated and stored in that file.
func I2PListener(name, samaddr, keyspath string) (*sam3.StreamListener, error) {
log.Printf("Starting and registering I2P service, please wait a couple of minutes...")
listener, err := I2PStreamSession(name, sam3.SAMDefaultAddr(samaddr), keyspath)
if err != nil {
return nil, err
}
if keyspath != "" {
err = ioutil.WriteFile(keyspath+".i2p.public.txt", []byte(listener.Keys().Addr().Base32()), 0644)
if err != nil {
log.Fatalf("error storing I2P base32 address in adjacent text file, %s", err)
}
}
log.Printf("Listening on: %s", listener.Addr().Base32())
return listener.Listen()
}
// I2PStreamSession is a convenience function which returns a sam3.StreamSession instead
// of a sam3.StreamListener. It also takes care of setting a persisitent key on behalf
// of the user.
func I2PStreamSession(name, samaddr, keyspath string) (*sam3.StreamSession, error) {
log.Printf("Starting and registering I2P session...")
sam, err := sam3.NewSAM(sam3.SAMDefaultAddr(samaddr))
if err != nil {
log.Fatalf("error connecting to SAM to %s: %s", sam3.SAMDefaultAddr(samaddr), err)
}
keys, err := GenerateOrLoadKeys(keyspath, sam)
if err != nil {
return nil, err
}
stream, err := sam.NewStreamSession(name, *keys, sam3.Options_Medium)
return stream, err
}
// I2PDataGramsession is a convenience function which returns a sam3.DatagramSession.
// It also takes care of setting a persisitent key on behalf of the user.
func I2PDatagramSession(name, samaddr, keyspath string) (*sam3.DatagramSession, error) {
log.Printf("Starting and registering I2P session...")
sam, err := sam3.NewSAM(sam3.SAMDefaultAddr(samaddr))
if err != nil {
log.Fatalf("error connecting to SAM to %s: %s", sam3.SAMDefaultAddr(samaddr), err)
}
keys, err := GenerateOrLoadKeys(keyspath, sam)
if err != nil {
return nil, err
}
gram, err := sam.NewDatagramSession(name, *keys, sam3.Options_Medium, 0)
return gram, err
}
// I2PPrimarySession is a convenience function which returns a sam3.PrimarySession.
// It also takes care of setting a persisitent key on behalf of the user.
func I2PPrimarySession(name, samaddr, keyspath string) (*sam3.PrimarySession, error) {
log.Printf("Starting and registering I2P session...")
sam, err := sam3.NewSAM(sam3.SAMDefaultAddr(samaddr))
if err != nil {
log.Fatalf("error connecting to SAM to %s: %s", sam3.SAMDefaultAddr(samaddr), err)
}
keys, err := GenerateOrLoadKeys(keyspath, sam)
if err != nil {
return nil, err
}
gram, err := sam.NewPrimarySession(name, *keys, sam3.Options_Medium)
return gram, err
}
// GenerateOrLoadKeys is a convenience function which takes a filename and a SAM session.
// if the SAM session is nil, a new one will be created with the defaults.
// The keyspath must be the path to a place to store I2P keys. The keyspath will be suffixed with
// .i2p.private for the private keys, and public.txt for the b32 addresses.
// If the keyspath.i2p.private file does not exist, keys will be generated and stored in that file.
// if the keyspath.i2p.private does exist, keys will be loaded from that location and returned
func GenerateOrLoadKeys(keyspath string, sam *sam3.SAM) (keys *i2pkeys.I2PKeys, err error) {
if sam == nil {
sam, err = sam3.NewSAM(sam3.SAMDefaultAddr("127.0.0.1:7656"))
if err != nil {
return nil, err
}
}
if _, err := os.Stat(keyspath + ".i2p.private"); os.IsNotExist(err) {
f, err := os.Create(keyspath + ".i2p.private")
if err != nil {
log.Fatalf("unable to open I2P keyfile for writing: %s", err)
}
defer f.Close()
tkeys, err := sam.NewKeys()
if err != nil {
log.Fatalf("unable to generate I2P Keys, %s", err)
}
keys = &tkeys
err = i2pkeys.StoreKeysIncompat(*keys, f)
if err != nil {
log.Fatalf("unable to save newly generated I2P Keys, %s", err)
}
} else {
tkeys, err := i2pkeys.LoadKeys(keyspath + ".i2p.private")
if err != nil {
log.Fatalf("unable to load I2P Keys: %e", err)
}
keys = &tkeys
}
return keys, nil
}
// GenerateKeys is a shorter version of GenerateOrLoadKeys which generates keys and stores them in a file.
// it always uses a new default SAM session.
func GenerateKeys(keyspath string) (keys *i2pkeys.I2PKeys, err error) {
return GenerateOrLoadKeys(keyspath, nil)
}

5
log.go Normal file
View File

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

123
opts/suggestedOptions.go Normal file
View File

@ -0,0 +1,123 @@
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

@ -7,9 +7,13 @@ import (
"net"
"strconv"
"strings"
"sync"
"time"
"github.com/eyedeekay/i2pkeys"
"github.com/sirupsen/logrus"
"github.com/go-i2p/i2pkeys"
"github.com/go-i2p/sam3/common"
)
const (
@ -20,6 +24,8 @@ 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)
}
@ -35,6 +41,7 @@ type PrimarySession struct {
Config SAMEmit
stsess map[string]*StreamSession
dgsess map[string]*DatagramSession
sync.RWMutex
// from string
// to string
}
@ -76,124 +83,192 @@ func (ss *PrimarySession) Keys() i2pkeys.I2PKeys {
}
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)
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)
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]]
var err error
sam.RUnlock()
if !ok {
ts, err = sam.NewUniqueStreamSubSession(network + raddr.String()[0:4])
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
ts, _ = sam.stsess[network+raddr.String()[0:4]]
sam.Unlock()
}
return ts.Dial(network, raddr.String())
}
func (sam *PrimarySession) DialTCPI2P(network string, laddr, raddr string) (net.Conn, error) {
func (sam *PrimarySession) DialTCPI2P(network, laddr, raddr string) (net.Conn, error) {
log.WithFields(logrus.Fields{"network": network, "laddr": laddr, "raddr": raddr}).Debug("DialTCPI2P() called")
sam.RLock()
ts, ok := sam.stsess[network+raddr[0:4]]
var err error
sam.RUnlock()
if !ok {
ts, err = sam.NewUniqueStreamSubSession(network + laddr)
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
ts, _ = sam.stsess[network+raddr[0:4]]
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]]
var err error
sam.RUnlock()
if !ok {
ds, err = sam.NewDatagramSubSession(network+raddr.String()[0:4], 0)
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
ds, _ = sam.dgsess[network+raddr.String()[0:4]]
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]]
var err error
sam.RUnlock()
if !ok {
ds, err = sam.NewDatagramSubSession(network+laddr, 0)
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
ds, _ = sam.dgsess[network+raddr[0:4]]
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 string, id string, keys i2pkeys.I2PKeys, options []string) (*PrimarySession, error) {
func (sam *SAM) newPrimarySession(primarySessionSwitch, id string, keys i2pkeys.I2PKeys, options []string) (*PrimarySession, error) {
log.WithFields(logrus.Fields{
"primarySessionSwitch": primarySessionSwitch,
"id": id,
"options": options,
}).Debug("newPrimarySession() called")
conn, err := sam.newGenericSession(primarySessionSwitch, id, keys, options, []string{})
if err != nil {
log.WithError(err).Error("Failed to create new generic session")
return nil, err
}
ssesss := make(map[string]*StreamSession)
dsesss := make(map[string]*DatagramSession)
return &PrimarySession{sam.Config.I2PConfig.Sam(), id, conn, keys, time.Duration(600 * time.Second), time.Now(), Sig_NONE, sam.Config, ssesss, dsesss}, nil
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{sam.Config.I2PConfig.Sam(), id, conn, keys, time.Duration(600 * time.Second), time.Now(), sigType, sam.Config, ssesss, dsesss}, nil
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",
@ -202,10 +277,12 @@ func (sam *SAM) NewPrimarySessionWithSignature(id string, keys i2pkeys.I2PKeys,
// 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)
}
@ -215,24 +292,22 @@ func (sam *PrimarySession) newGenericSubSessionWithSignature(style, id string, e
// setting extra to something else than []string{}.
// This sam3 instance is now a session
func (sam *PrimarySession) newGenericSubSessionWithSignatureAndPorts(style, id, from, to string, extras []string) (net.Conn, error) {
log.WithFields(logrus.Fields{"style": style, "id": id, "from": from, "to": to, "extras": extras}).Debug("newGenericSubSessionWithSignatureAndPorts called")
conn := sam.conn
fp := ""
tp := ""
if from != "0" && from != "" {
fp = " FROM_PORT=" + from
}
if to != "0" && to != "" {
tp = " TO_PORT=" + to
}
scmsg := []byte("SESSION ADD STYLE=" + style + " ID=" + id + fp + tp + " " + strings.Join(extras, " ") + "\n")
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
}
@ -241,30 +316,38 @@ func (sam *PrimarySession) newGenericSubSessionWithSignatureAndPorts(style, id,
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.Println("SAM:", text)
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)
}
@ -273,8 +356,10 @@ func (sam *PrimarySession) newGenericSubSessionWithSignatureAndPorts(style, id,
// 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
@ -283,18 +368,25 @@ func (sam *PrimarySession) NewStreamSubSession(id string) (*StreamSession, error
// 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
}
return &StreamSession{sam.Config.I2PConfig.Sam(), id, conn, sam.keys, time.Duration(600 * time.Second), time.Now(), Sig_NONE, randport(), randport()}, nil
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
@ -312,87 +404,119 @@ func (s *PrimarySession) I2PListener(name string) (*StreamListener, error) {
// 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) (*DatagramSession, error) {
if udpPort > 65335 || udpPort < 0 {
return nil, errors.New("udpPort needs to be in the intervall 0-65335")
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,
}
if udpPort == 0 {
udpPort = 7655
}
lhost, _, err := SplitHostPort(s.conn.LocalAddr().String())
udpConn, err := common.NewUDPSession(udpSessionConfig)
if err != nil {
s.Close()
log.WithError(err).Error("Failed to create UDP session")
return nil, err
}
lUDPAddr, err := net.ResolveUDPAddr("udp4", lhost+":0")
if err != nil {
return nil, err
}
udpconn, err := net.ListenUDP("udp4", lUDPAddr)
if err != nil {
return nil, err
}
rhost, _, err := 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())
_, 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
}
return &DatagramSession{s.Config.I2PConfig.Sam(), id, conn, udpconn, s.keys, rUDPAddr, nil}, nil
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

@ -2,7 +2,6 @@ package sam3
import (
"fmt"
"log"
"testing"
"time"
)
@ -140,10 +139,10 @@ func ExamplePrimaryDatagramSession() {
fmt.Println(err.Error())
return
}
log.Println("Got message: '" + string(buf[:n]) + "'")
fmt.Println("Got message: '" + string(buf[:n]) + "'")
fmt.Println("Got message: " + string(buf[:n]))
return
// Output:
//Got message: Hello myself!
// Got message: Hello myself!
}

View File

@ -3,7 +3,6 @@ package sam3
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"strings"
"testing"
@ -42,14 +41,14 @@ func Test_PrimaryStreamingDial(t *testing.T) {
}
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 i2p-projekt.i2p")
forumAddr, err := earlysam.Lookup("i2p-projekt.i2p")
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 i2p-projekt.i2p(", forumAddr.Base32(), forumAddr.DestHash().Hash(), ")")
fmt.Println("\tDialing idk.i2p(", forumAddr.Base32(), forumAddr.DestHash().Hash(), ")")
conn, err := ss.DialI2P(forumAddr)
if err != nil {
fmt.Println(err.Error())
@ -66,9 +65,9 @@ func Test_PrimaryStreamingDial(t *testing.T) {
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(i2p-projekt.i2p)? It replied %d bytes, but nothing that looked like http/html", n)
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 i2p-projekt.i2p")
fmt.Println("\tRead HTTP/HTML from idk.i2p")
}
}
@ -191,7 +190,7 @@ func ExamplePrimaryStreamSession() {
return
}
defer sam.Close()
conn, err := sam.Dial("tcp", "idk.i2p") //someone.Base32())
conn, err := sam.Dial("tcp", "idk.i2p") // someone.Base32())
if err != nil {
fmt.Println(err.Error())
return
@ -212,8 +211,8 @@ func ExamplePrimaryStreamSession() {
log.Println("Read HTTP/HTML from idk.i2p")
}
// Output:
//Sending HTTP GET /
//Read HTTP/HTML from idk.i2p
// Sending HTTP GET /
// Read HTTP/HTML from idk.i2p
}
func ExamplePrimaryStreamListener() {
@ -254,7 +253,7 @@ func ExamplePrimaryStreamListener() {
return
}
defer l.Close()
//fmt.Println("Serving on primary listener", l.Addr().String())
// fmt.Println("Serving on primary listener", l.Addr().String())
if err := http.Serve(l, &exitHandler{}); err != nil {
fmt.Println(err.Error())
}
@ -282,7 +281,7 @@ func ExamplePrimaryStreamListener() {
Dial: sc.Dial,
},
}
//resp, err := client.Get("http://" + "idk.i2p") //ss.Addr().Base32())
// 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())
@ -300,8 +299,7 @@ func ExamplePrimaryStreamListener() {
// Got response: Hello world!
}
type exitHandler struct {
}
type exitHandler struct{}
func (e *exitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello world!"))

64
raw.go
View File

@ -1,13 +1,14 @@
package sam3
import (
"bytes"
"errors"
"net"
"strconv"
"time"
"github.com/eyedeekay/i2pkeys"
"github.com/sirupsen/logrus"
"github.com/go-i2p/i2pkeys"
)
// The RawSession provides no authentication of senders, and there is no sender
@ -28,80 +29,121 @@ type RawSession struct {
// Creates a new raw session. udpPort is the UDP port SAM is listening on,
// and if you set it to zero, it will use SAMs standard UDP port.
func (s *SAM) NewRawSession(id string, keys i2pkeys.I2PKeys, options []string, udpPort int) (*RawSession, error) {
log.WithFields(logrus.Fields{"id": id, "udpPort": udpPort}).Debug("Creating new RawSession")
if udpPort > 65335 || udpPort < 0 {
return nil, errors.New("udpPort needs to be in the intervall 0-65335")
log.WithField("udpPort", udpPort).Error("Invalid UDP port")
return nil, errors.New("udpPort needs to be in the interval 0-65335")
}
if udpPort == 0 {
udpPort = 7655
log.Debug("Using default UDP port 7655")
}
lhost, _, err := 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())
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
}
return &RawSession{s.Config.I2PConfig.Sam(), id, conn, udpconn, keys, rUDPAddr}, nil
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
}
// Reads one raw datagram sent to the destination of the DatagramSession. Returns
// Read 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
}
if bytes.Equal(saddr.IP, s.rUDPAddr.IP) {
continue
// 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
}
break
// Log unexpected source
log.Printf("Ignored datagram from unauthorized source: %v", saddr)
continue
}
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")
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
}

View File

@ -2,71 +2,123 @@ package sam3
import (
"bufio"
"bytes"
"errors"
"context"
"fmt"
"strings"
"time"
"github.com/eyedeekay/i2pkeys"
"github.com/go-i2p/i2pkeys"
)
// SAMResolver handles name resolution for I2P addresses
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) {
var s SAMResolver
s.SAM = parent
return &s, nil
if parent == nil {
return nil, fmt.Errorf("parent SAM instance required")
}
return &SAMResolver{sam: parent}, nil
}
// NewFullSAMResolver creates a new resolver with its own SAM connection
func NewFullSAMResolver(address string) (*SAMResolver, error) {
var s SAMResolver
var err error
s.SAM, err = NewSAM(address)
if err != nil {
return nil, err
}
return &s, nil
sam, err := NewSAM(address)
if err != nil {
return nil, fmt.Errorf("creating SAM connection: %w", err)
}
return &SAMResolver{sam: sam}, nil
}
// Performs a lookup, probably this order: 1) routers known addresses, cached
// addresses, 3) by asking peers in the I2P network.
func (sam *SAMResolver) Resolve(name string) (i2pkeys.I2PAddr, error) {
if _, err := sam.conn.Write([]byte("NAMING LOOKUP NAME=" + name + "\r\n")); err != nil {
sam.Close()
return i2pkeys.I2PAddr(""), err
}
buf := make([]byte, 4096)
n, err := sam.conn.Read(buf)
if err != nil {
sam.Close()
return i2pkeys.I2PAddr(""), err
}
if n <= 13 || !strings.HasPrefix(string(buf[:n]), "NAMING REPLY ") {
return i2pkeys.I2PAddr(""), errors.New("Failed to parse.")
}
s := bufio.NewScanner(bytes.NewReader(buf[13:n]))
s.Split(bufio.ScanWords)
errStr := ""
for s.Scan() {
text := s.Text()
//log.Println("SAM3", text)
if text == "RESULT=OK" {
continue
} else if text == "RESULT=INVALID_KEY" {
errStr += "Invalid key - resolver."
} 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 i2pkeys.I2PAddr(text[6:]), nil
} else if strings.HasPrefix(text, "MESSAGE=") {
errStr += " " + text[8:]
} else {
continue
}
}
return i2pkeys.I2PAddr(""), errors.New(errStr)
func (r *SAMResolver) Resolve(name string) (i2pkeys.I2PAddr, error) {
return r.ResolveWithContext(context.Background(), name)
}
// 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)
}

192
sam3.go
View File

@ -4,26 +4,35 @@ package sam3
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"math/rand"
"net"
"os"
"strings"
"github.com/eyedeekay/i2pkeys"
"github.com/sirupsen/logrus"
. "github.com/eyedeekay/i2pkeys"
"github.com/go-i2p/i2pkeys"
"github.com/go-i2p/sam3/common"
logger "github.com/go-i2p/sam3/log"
)
func init() {
logger.InitializeSAM3Logger()
}
// Used for controlling I2Ps SAMv3.
// This implements the "Control Socket" for all connections.
type SAM struct {
address string
conn net.Conn
resolver *SAMResolver
Config SAMEmit
keys *i2pkeys.I2PKeys
sigType int
address string
conn net.Conn
keys *i2pkeys.I2PKeys
sigType int
formatter *common.SAMFormatter
version common.Version
SAMEmit
*SAMResolver
}
const (
@ -51,69 +60,92 @@ func RandString() string {
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) {
var s SAM
log.WithField("address", address).Debug("Creating new SAM instance")
s := SAM{
address: address,
version: common.SAM31Version,
formatter: common.NewSAMFormatter(common.SAM31Version.String),
}
// TODO: clean this up
conn, err := net.Dial("tcp", address)
if err != nil {
return nil, err
log.WithError(err).Error("Failed to dial SAM address")
return nil, fmt.Errorf("error dialing to address '%s': %w", address, err)
}
if _, err := conn.Write(s.Config.HelloBytes()); err != nil {
if _, err := conn.Write(s.SAMEmit.HelloBytes()); err != nil {
log.WithError(err).Error("Failed to write hello message")
conn.Close()
return nil, err
return nil, fmt.Errorf("error writing to address '%s': %w", address, err)
}
buf := make([]byte, 256)
n, err := conn.Read(buf)
/*buf := make([]byte, 256)
n, err := conn.Read(buf)*/
reader := bufio.NewReader(conn)
response, err := reader.ReadString('\n')
if err != nil {
conn.Close()
return nil, err
return nil, fmt.Errorf("error reading SAM response: %w", err)
}
buf := []byte(response)
n := len(buf)
if strings.Contains(string(buf[:n]), "HELLO REPLY RESULT=OK") {
s.Config.I2PConfig.SetSAMAddress(address)
log.Debug("SAM hello successful")
s.SAMEmit.I2PConfig.SetSAMAddress(address)
s.conn = conn
//s.Config.I2PConfig.DestinationKeys = nil
s.resolver, err = NewSAMResolver(&s)
// s.Config.I2PConfig.DestinationKeys = nil
s.SAMResolver, err = NewSAMResolver(&s)
if err != nil {
return nil, err
log.WithError(err).Error("Failed to create SAM resolver")
return nil, fmt.Errorf("error creating resolver: %w", 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, errors.New("That SAM bridge does not support SAMv3.")
return nil, fmt.Errorf("That SAM bridge does not support SAMv3.")
} else {
log.WithField("response", string(buf[:n])).Error("Unexpected SAM response")
conn.Close()
return nil, errors.New(string(buf[:n]))
return nil, fmt.Errorf(string(buf[:n]))
}
}
func (sam *SAM) Keys() (k *i2pkeys.I2PKeys) {
//TODO: copy them?
k = &sam.Config.I2PConfig.DestinationKeys
// TODO: copy them?
log.Debug("Retrieving SAM keys")
k = &sam.SAMEmit.I2PConfig.DestinationKeys
return
}
// read public/private keys from an io.Reader
func (sam *SAM) ReadKeys(r io.Reader) (err error) {
log.Debug("Reading keys from io.Reader")
var keys i2pkeys.I2PKeys
keys, err = i2pkeys.LoadKeysIncompat(r)
if err == nil {
sam.Config.I2PConfig.DestinationKeys = keys
log.Debug("Keys loaded successfully")
sam.SAMEmit.I2PConfig.DestinationKeys = keys
}
log.WithError(err).Error("Failed to load keys")
return
}
// if keyfile fname does not exist
func (sam *SAM) EnsureKeyfile(fname string) (keys i2pkeys.I2PKeys, err error) {
log.WithError(err).Error("Failed to load keys")
if fname == "" {
// transient
keys, err = sam.NewKeys()
if err == nil {
sam.Config.I2PConfig.DestinationKeys = keys
sam.SAMEmit.I2PConfig.DestinationKeys = keys
log.WithFields(logrus.Fields{
"keys": keys,
}).Debug("Generated new transient keys")
}
} else {
// persistent
@ -122,13 +154,14 @@ func (sam *SAM) EnsureKeyfile(fname string) (keys i2pkeys.I2PKeys, err error) {
// make the keys
keys, err = sam.NewKeys()
if err == nil {
sam.Config.I2PConfig.DestinationKeys = keys
sam.SAMEmit.I2PConfig.DestinationKeys = keys
// save keys
var f io.WriteCloser
f, err = os.OpenFile(fname, os.O_WRONLY|os.O_CREATE, 0600)
f, err = os.OpenFile(fname, os.O_WRONLY|os.O_CREATE, 0o600)
if err == nil {
err = i2pkeys.StoreKeysIncompat(keys, f)
f.Close()
log.Debug("Generated and saved new keys")
}
}
} else if err == nil {
@ -138,29 +171,42 @@ func (sam *SAM) EnsureKeyfile(fname string) (keys i2pkeys.I2PKeys, err error) {
if err == nil {
keys, err = i2pkeys.LoadKeysIncompat(f)
if err == nil {
sam.Config.I2PConfig.DestinationKeys = keys
sam.SAMEmit.I2PConfig.DestinationKeys = keys
log.Debug("Loaded existing keys from file")
}
}
}
}
if err != nil {
log.WithError(err).Error("Failed to ensure keyfile")
}
return
}
// Creates the I2P-equivalent of an IP address, that is unique and only the one
// who has the private keys can send messages from. The public keys are the I2P
// desination (the address) that anyone can send messages to.
// 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) {
sigtmp := ""
if len(sigType) > 0 {
sigtmp = sigType[0]
log.WithField("sigType", sigType).Debug("Generating new keys")
if sigType == nil {
sigType = []string{DEFAULT_SIG_TYPE}
}
if _, err := sam.conn.Write([]byte("DEST GENERATE " + sigtmp + "\n")); err != nil {
return i2pkeys.I2PKeys{}, err
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)
}
buf := make([]byte, 8192)
n, err := sam.conn.Read(buf)
if err != nil {
return i2pkeys.I2PKeys{}, err
log.WithError(err).Error("Failed to read SAM response for key generation")
return i2pkeys.I2PKeys{}, fmt.Errorf("error with reading in SAM: %w", err)
}
s := bufio.NewScanner(bytes.NewReader(buf[:n]))
s.Split(bufio.ScanWords)
@ -177,16 +223,19 @@ func (sam *SAM) NewKeys(sigType ...string) (i2pkeys.I2PKeys, error) {
} else if strings.HasPrefix(text, "PRIV=") {
priv = text[5:]
} else {
return i2pkeys.I2PKeys{}, errors.New("Failed to parse keys.")
log.Error("Failed to parse keys from SAM response")
return i2pkeys.I2PKeys{}, fmt.Errorf("Failed to parse keys.")
}
}
return NewKeys(I2PAddr(pub), priv), nil
log.Debug("Successfully generated new keys")
return i2pkeys.NewKeys(i2pkeys.I2PAddr(pub), priv), nil
}
// Performs a lookup, probably this order: 1) routers known addresses, cached
// addresses, 3) by asking peers in the I2P network.
func (sam *SAM) Lookup(name string) (i2pkeys.I2PAddr, error) {
return sam.resolver.Resolve(name)
log.WithField("name", name).Debug("Looking up address")
return sam.SAMResolver.Resolve(name)
}
// Creates a new session with the style of either "STREAM", "DATAGRAM" or "RAW",
@ -194,11 +243,13 @@ func (sam *SAM) Lookup(name string) (i2pkeys.I2PAddr, error) {
// I2CP/streaminglib-options as specified. Extra arguments can be specified by
// setting extra to something else than []string{}.
// This sam3 instance is now a session
func (sam *SAM) newGenericSession(style, id string, keys i2pkeys.I2PKeys, options []string, extras []string) (net.Conn, error) {
func (sam *SAM) newGenericSession(style, id string, keys i2pkeys.I2PKeys, options, extras []string) (net.Conn, error) {
log.WithFields(logrus.Fields{"style": style, "id": id}).Debug("Creating new generic session")
return sam.newGenericSessionWithSignature(style, id, keys, Sig_NONE, options, extras)
}
func (sam *SAM) newGenericSessionWithSignature(style, id string, keys i2pkeys.I2PKeys, sigType string, options []string, extras []string) (net.Conn, error) {
func (sam *SAM) newGenericSessionWithSignature(style, id string, keys i2pkeys.I2PKeys, sigType string, options, extras []string) (net.Conn, error) {
log.WithFields(logrus.Fields{"style": style, "id": id, "sigType": sigType}).Debug("Creating new generic session with signature")
return sam.newGenericSessionWithSignatureAndPorts(style, id, "0", "0", keys, sigType, options, extras)
}
@ -207,64 +258,85 @@ func (sam *SAM) newGenericSessionWithSignature(style, id string, keys i2pkeys.I2
// I2CP/streaminglib-options as specified. Extra arguments can be specified by
// setting extra to something else than []string{}.
// This sam3 instance is now a session
func (sam *SAM) newGenericSessionWithSignatureAndPorts(style, id, from, to string, keys i2pkeys.I2PKeys, sigType string, options []string, extras []string) (net.Conn, error) {
func (sam *SAM) newGenericSessionWithSignatureAndPorts(style, id, from, to string, keys i2pkeys.I2PKeys, sigType string, options, extras []string) (net.Conn, error) {
log.WithFields(logrus.Fields{"style": style, "id": id, "from": from, "to": to, "sigType": sigType}).Debug("Creating new generic session with signature and ports")
optStr := GenerateOptionString(options)
conn := sam.conn
fp := ""
tp := ""
if from != "0" {
fp = " FROM_PORT=" + from
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, " ")))
}
if to != "0" {
tp = " TO_PORT=" + to
}
scmsg := []byte("SESSION CREATE STYLE=" + style + fp + tp + " ID=" + id + " DESTINATION=" + keys.String() + " " + optStr + strings.Join(extras, " ") + "\n")
log.WithField("message", string(scmsg)).Debug("Sending SESSION CREATE message", string(scmsg))
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, errors.New("writing to SAM failed")
return nil, fmt.Errorf("writing to SAM failed")
}
n, err := conn.Write(scmsg[m:])
if err != nil {
log.WithError(err).Error("Failed to write to SAM connection")
conn.Close()
return nil, err
return nil, fmt.Errorf("writing to connection failed: %w", err)
}
m += n
}
buf := make([]byte, 4096)
n, err := conn.Read(buf)
if err != nil {
log.WithError(err).Error("Failed to read SAM response")
conn.Close()
return nil, err
return nil, fmt.Errorf("reading from connection failed: %w", err)
}
text := string(buf[:n])
log.WithField("response", text).Debug("Received SAM response")
if strings.HasPrefix(text, session_OK) {
if keys.String() != text[len(session_OK):len(text)-1] {
log.Error("SAM created a tunnel with different keys than requested")
conn.Close()
return nil, errors.New("SAMv3 created a tunnel with keys other than the ones we asked it for")
return nil, fmt.Errorf("SAMv3 created a tunnel with keys other than the ones we asked it for")
}
log.Debug("Successfully created new session")
return conn, nil //&StreamSession{id, conn, keys, nil, sync.RWMutex{}, nil}, nil
} else if text == session_DUPLICATE_ID {
log.Error("Duplicate tunnel name")
conn.Close()
return nil, errors.New("Duplicate tunnel name")
return nil, fmt.Errorf("Duplicate tunnel name")
} else if text == session_DUPLICATE_DEST {
log.Error("Duplicate destination")
conn.Close()
return nil, errors.New("Duplicate destination")
return nil, fmt.Errorf("Duplicate destination")
} else if text == session_INVALID_KEY {
log.Error("Invalid key for SAM session")
conn.Close()
return nil, errors.New("Invalid key - SAM session")
return nil, fmt.Errorf("Invalid key - SAM session")
} else if strings.HasPrefix(text, session_I2P_ERROR) {
log.WithField("error", text[len(session_I2P_ERROR):]).Error("I2P error")
conn.Close()
return nil, errors.New("I2P error " + text[len(session_I2P_ERROR):])
return nil, fmt.Errorf("I2P error %s", 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)
return nil, fmt.Errorf("Unable to parse SAMv3 reply: %s", 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
}

107
stream.go
View File

@ -5,12 +5,15 @@ import (
"bytes"
"context"
"errors"
"fmt"
"io"
"net"
"strings"
"time"
"github.com/eyedeekay/i2pkeys"
"github.com/sirupsen/logrus"
"github.com/go-i2p/i2pkeys"
)
// Represents a streaming session.
@ -26,102 +29,132 @@ type StreamSession struct {
to string
}
// Read reads data from the stream.
func (s *StreamSession) Read(buf []byte) (int, error) {
return s.conn.Read(buf)
}
// Write sends data over the stream.
func (s *StreamSession) Write(data []byte) (int, error) {
return s.conn.Write(data)
}
func (s *StreamSession) SetDeadline(t time.Time) error {
log.WithField("deadline", t).Debug("Setting deadline for StreamSession")
return s.conn.SetDeadline(t)
}
func (s *StreamSession) SetReadDeadline(t time.Time) error {
log.WithField("readDeadline", t).Debug("Setting read deadline for StreamSession")
return s.conn.SetReadDeadline(t)
}
func (s *StreamSession) SetWriteDeadline(t time.Time) error {
log.WithField("writeDeadline", t).Debug("Setting write deadline for StreamSession")
return s.conn.SetWriteDeadline(t)
}
func (ss *StreamSession) From() string {
return ss.from
func (s *StreamSession) From() string {
return s.from
}
func (ss *StreamSession) To() string {
return ss.to
func (s *StreamSession) To() string {
return s.to
}
func (ss *StreamSession) SignatureType() string {
return ss.sigType
func (s *StreamSession) SignatureType() string {
return s.sigType
}
// Returns the local tunnel name of the I2P tunnel used for the stream session
func (ss *StreamSession) ID() string {
return ss.id
func (s *StreamSession) ID() string {
return s.id
}
func (ss *StreamSession) Close() error {
return ss.conn.Close()
func (s *StreamSession) Close() error {
log.WithField("id", s.id).Debug("Closing StreamSession")
return s.conn.Close()
}
// Returns the I2P destination (the address) of the stream session
func (ss *StreamSession) Addr() i2pkeys.I2PAddr {
return ss.keys.Addr()
func (s *StreamSession) Addr() i2pkeys.I2PAddr {
return s.keys.Addr()
}
func (ss *StreamSession) LocalAddr() net.Addr {
return ss.keys.Addr()
func (s *StreamSession) LocalAddr() net.Addr {
return s.keys.Addr()
}
// Returns the keys associated with the stream session
func (ss *StreamSession) Keys() i2pkeys.I2PKeys {
return ss.keys
func (s *StreamSession) Keys() i2pkeys.I2PKeys {
return s.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")
conn, err := sam.newGenericSession("STREAM", id, keys, options, []string{})
if err != nil {
return nil, err
}
return &StreamSession{sam.Config.I2PConfig.Sam(), id, conn, keys, time.Duration(600 * time.Second), time.Now(), Sig_NONE, "0", "0"}, nil
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
}
// Creates a new StreamSession with the I2CP- and streaminglib options as
// specified. See the I2P documentation for a full list of options.
func (sam *SAM) NewStreamSessionWithSignature(id string, keys i2pkeys.I2PKeys, options []string, sigType string) (*StreamSession, error) {
log.WithFields(logrus.Fields{"id": id, "options": options, "sigType": sigType}).Debug("Creating new StreamSession with signature")
conn, err := sam.newGenericSessionWithSignature("STREAM", id, keys, sigType, options, []string{})
if err != nil {
return nil, err
}
return &StreamSession{sam.Config.I2PConfig.Sam(), id, conn, keys, time.Duration(600 * time.Second), time.Now(), sigType, "0", "0"}, nil
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
}
// 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{})
if err != nil {
return nil, err
}
return &StreamSession{sam.Config.I2PConfig.Sam(), id, conn, keys, time.Duration(600 * time.Second), time.Now(), sigType, from, to}, nil
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
}
// lookup name, convenience function
func (s *StreamSession) Lookup(name string) (i2pkeys.I2PAddr, error) {
log.WithField("name", name).Debug("Looking up address")
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")
}
return addr, err
}
log.WithError(err).Error("Failed to create SAM instance for lookup")
return i2pkeys.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())
@ -135,6 +168,7 @@ func (s *StreamSession) DialContextI2P(ctx context.Context, n, addr string) (*SA
i2paddr, err := i2pkeys.NewI2PAddrFromString(addr)
if err != nil {
log.WithError(err).Error("Failed to create I2P address from string")
return nil, err
}
return s.DialI2P(i2paddr)
@ -175,45 +209,57 @@ 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 host string
host, _, err = SplitHostPort(addr)
//log.Println("Dialing:", host)
// log.Println("Dialing:", host)
if err = IgnorePortError(err); err == nil {
// check for name
if strings.HasSuffix(host, ".b32.i2p") || strings.HasSuffix(host, ".i2p") {
// name lookup
i2paddr, err = s.Lookup(host)
//log.Println("Lookup:", i2paddr, err)
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)
// i2paddr = i2pkeys.I2PAddr(host)
// log.Println("Destination:", i2paddr, err)
log.WithFields(logrus.Fields{"host": host, "i2paddr": i2paddr}).Debug("Created I2P address from bytes")
}
if err == nil {
return s.DialI2P(i2paddr)
}
}
log.WithError(err).Error("Dial failed")
return
}
// Dials to an I2P destination and returns a SAMConn, which implements a net.Conn.
func (s *StreamSession) DialI2P(addr i2pkeys.I2PAddr) (*SAMConn, error) {
log.WithField("addr", addr).Debug("DialI2P called")
sam, err := NewSAM(s.samAddr)
if err != nil {
log.WithError(err).Error("Failed to create new SAM instance")
return nil, err
}
conn := sam.conn
_, err = conn.Write([]byte("STREAM CONNECT ID=" + s.id + " FROM_PORT=" + s.from + " TO_PORT=" + s.to + " DESTINATION=" + addr.Base64() + " SILENT=false\n"))
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))
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
}
@ -226,32 +272,41 @@ 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")
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, errors.New("Unknown error: " + scanner.Text() + " : " + string(buf[:n]))
return nil, fmt.Errorf("Unknown error: %s : %s", scanner.Text(), string(buf[:n]))
}
}
log.Panic("Unexpected end of StreamSession.DialI2P()")
panic("sam3 go library error in StreamSession.DialI2P()")
}
// 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,

View File

@ -2,14 +2,17 @@ package sam3
import (
"bufio"
"errors"
"context"
"fmt"
"io"
"log"
"net"
"strconv"
"strings"
"time"
"github.com/eyedeekay/i2pkeys"
"github.com/sirupsen/logrus"
"github.com/go-i2p/i2pkeys"
)
type StreamListener struct {
@ -42,78 +45,134 @@ func (l *StreamListener) Close() error {
// implements net.Listener
func (l *StreamListener) Accept() (net.Conn, error) {
return l.AcceptI2P()
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) {
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")
if err != nil {
s.Close()
return nil, err
}
// read reply
rd := bufio.NewReader(s.conn)
// read first line
line, err := rd.ReadString(10)
if err != nil {
s.Close()
return nil, err
}
log.Println(line)
if strings.HasPrefix(line, "STREAM STATUS RESULT=OK") {
// we gud read destination line
destline, err := rd.ReadString(10)
if err == nil {
dest := ExtractDest(destline)
l.session.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: i2pkeys.I2PAddr(dest),
conn: s.conn,
}, nil
} else {
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()
return nil, err
}
// read reply
rd := bufio.NewReader(s.conn)
// read first line
line, err := rd.ReadString(10)
if err != nil {
log.WithError(err).Error("Failed to read SAM bridge response")
s.Close()
return
}
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()
return nil, errors.New("invalid sam line: " + line)
}
} else {
s.Close()
return nil, err
}()
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-done:
return conn, err
}
}

View File

@ -2,11 +2,11 @@ package sam3
import (
"fmt"
"log"
"strings"
"testing"
"github.com/eyedeekay/i2pkeys"
"github.com/go-i2p/i2pkeys"
sam3opts "github.com/go-i2p/sam3/opts"
)
func Test_StreamingDial(t *testing.T) {
@ -165,7 +165,7 @@ func ExampleStreamSession() {
return
}
// See the example Option_* variables.
ss, err := sam.NewStreamSession("stream_example", keys, Options_Small)
ss, err := sam.NewStreamSession("stream_example", keys, sam3opts.Options_Small)
if err != nil {
fmt.Println(err.Error())
return
@ -199,8 +199,8 @@ func ExampleStreamSession() {
return
// Output:
//Sending HTTP GET /
//Read HTTP/HTML from idk.i2p
// Sending HTTP GET /
// Read HTTP/HTML from idk.i2p
}
func ExampleStreamListener() {
@ -237,7 +237,7 @@ func ExampleStreamListener() {
fmt.Println(err.Error())
return
}
cs, err := csam.NewStreamSession("client_example", keys, Options_Small)
cs, err := csam.NewStreamSession("client_example", keys, sam3opts.Options_Small)
if err != nil {
fmt.Println(err.Error())
quit <- false
@ -260,7 +260,7 @@ func ExampleStreamListener() {
quit <- true
}(keys.Addr()) // end of client
ss, err := sam.NewStreamSession("server_example", keys, Options_Small)
ss, err := sam.NewStreamSession("server_example", keys, sam3opts.Options_Small)
if err != nil {
fmt.Println(err.Error())
return
@ -280,5 +280,5 @@ func ExampleStreamListener() {
<-quit // waits for client to die, for example only
// Output:
//Hello world!
// Hello world!
}

View File

@ -1,116 +1,95 @@
package sam3
import (
"fmt"
"net"
"net/http"
"os"
"strings"
)
// 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"}
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, Options_Small)
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
}
var SAM_HOST = getEnv("sam_host", "127.0.0.1")
var SAM_PORT = getEnv("sam_port", "7656")
var (
SAM_HOST = getEnv("sam_host", "127.0.0.1")
SAM_PORT = getEnv("sam_port", "7656")
)
func SAMDefaultAddr(fallforward string) string {
if fallforward == "" {
return net.JoinHostPort(SAM_HOST, SAM_PORT)
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
}
return optStr + " i2cp.leaseSetEncType=4,0"
finalOpts := fmt.Sprintf("%s i2cp.leaseSetEncType=4,0", optStr)
log.WithField("finalOptions", finalOpts).Debug("Added default i2cp.leaseSetEncType to options")
return finalOpts
}