76 Commits

Author SHA1 Message Date
eyedeekay
ca51512ef9 refactor resolver 2024-12-09 14:15:48 -05:00
eyedeekay
18dc0e6a9f Delete some unused functions 2024-12-08 16:21:20 -05:00
eyedeekay
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
eyedeekay
bfbe7f638a Fix formatting when creating sessions with ports, which was breaking primary sessions 2024-12-08 13:34:42 -05:00
eyedeekay
493db0aaaf move options 2024-12-08 13:22:39 -05:00
eyedeekay
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
eyedeekay
058a02e6cc Move just a spectacular amount of config stuff into a config package because 2024-11-30 19:18:01 -05:00
eyedeekay
86e5b7c368 Move just a spectacular amount of config stuff into a config package because 2024-11-30 18:49:30 -05:00
eyedeekay
1c1652f234 Move just a spectacular amount of config stuff into a config package because 2024-11-30 18:34:01 -05:00
eyedeekay
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
eyedeekay
82eeba6275 Start abstracting away UDP session management 2024-11-24 19:11:04 -05:00
eyedeekay
eea1f916cd split on space not newline 2024-11-24 01:08:43 -05:00
eyedeekay
ac4ef13405 automatically chunk datagrams 2024-11-23 14:19:02 -05:00
eyedeekay
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
eyedeekay
d8dafef068 fmt 2024-11-22 20:35:39 -05:00
eyedeekay
2dc0890d17 if AcceptI2P errors out in an Accept call, close the resulting conn and return nil 2024-11-22 20:05:43 -05:00
eyedeekay
5303ea6c34 if AcceptI2P errors out in an Accept call, close the resulting conn and return nil 2024-11-22 20:05:34 -05:00
eyedeekay
fdfa4240fb Add the ability to set and use DatagramOptions in a DatagramSession 2024-11-22 19:16:35 -05:00
eyedeekay
a22cde30eb use Sprintf instead of concatenation for command 2024-11-22 18:48:09 -05:00
eyedeekay
88786afa0c Use external logger 2024-11-21 18:46:29 -05:00
eyedeekay
b5cacb3ece remove the helper library 2024-11-20 23:41:25 -05:00
eyedeekay
37205fc8a2 organize imports 2024-11-20 23:32:26 -05:00
eyedeekay
ba5b2ca853 update release process 2024-11-16 16:22:38 -05:00
eyedeekay
f4ca627cd8 update examples 2024-11-14 10:43:00 -05:00
eyedeekay
7fc3116088 Bump version 2024-11-13 14:41:50 -05:00
eyedeekay
d3fb670d66 Fix go mods 2024-11-13 14:40:27 -05:00
eyedeekay
25751504b9 Fix up import paths 2024-11-09 11:54:54 -05:00
eyedeekay
a745742ee1 setup auto-assign workflow 2024-11-08 15:01:19 -05:00
eyedeekay
def28bbf7c Merge branch 'master' of github.com:eyedeekay/sam3 2024-11-08 12:55:50 -05:00
eyedeekay
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
Haris Khan
14d0b22a28 add Read() and Write() methods 2024-11-02 23:26:10 -04:00
Haris Khan
67554060fb minor typo fix 2024-11-02 22:20:52 -04:00
Haris Khan
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
Haris Khan
5be3e27599 Attempt to fix naming collision with other libs 2024-10-23 00:03:16 -04:00
Haris Khan
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
Haris Khan
67c0c9288a Updated README.md to reflect logging 2024-10-17 14:42:19 -04:00
Haris Khan
10f42af061 .Info -> .Debug 2024-10-16 17:21:44 -04:00
Haris Khan
aa63210c3c WORKING FIX 2024-10-16 11:23:49 -04:00
Haris Khan
4e1b426230 name collision 2024-10-16 10:51:21 -04:00
Haris Khan
a372049be9 added logging to suggestedOptions.go
-fixed name collision in stream_test.go
2024-10-16 10:35:43 -04:00
Haris Khan
6e2cc71a92 added logging to streamListener.go
-added dest in ExtractDest()
2024-10-16 10:24:30 -04:00
Haris Khan
988769ed5a fixed name collision in primary_stream_test.go 2024-10-16 10:18:06 -04:00
Haris Khan
504b7fb48b added logging to stream.go
-added better error handling to Lookup()
2024-10-16 10:14:57 -04:00
Haris Khan
c10b6b284c finished up sam3.go 2024-10-16 10:02:47 -04:00
Haris Khan
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
Haris Khan
9e6d0489cf added logging to resolver.go 2024-10-15 22:26:33 -04:00
Haris Khan
d05428754b added logging to raw.go 2024-10-15 22:20:33 -04:00
Haris Khan
41d9bd0150 Merge branch 'master' into logging
# Conflicts:
#	go.mod
#	go.sum
2024-10-15 22:10:49 -04:00
Haris Khan
fd22f227b1 finished up primary.go 2024-10-15 22:09:18 -04:00
Haris Khan
0415adf35d added logging to primary.go
-added fromPort, toPort in NewUniqueStreamSubsession
2024-10-15 21:01:36 -04:00
Haris Khan
3655462ca4 added logging to emit-options.go 2024-10-15 17:01:43 -04:00
Haris Khan
4bb6f81c40 added logging to emit.go 2024-10-15 12:54:17 -04:00
Haris Khan
21e7b5a177 added logging to datagram.go 2024-10-15 12:39:20 -04:00
Haris Khan
4166a2c827 added log.go & logging for config.go 2024-10-15 12:23:24 -04:00
eyedeekay
cd2a4f072e update i2pkeys library to new version 2024-09-17 19:40:10 -04:00
eyedeekay
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
Haris Khan
b4293f755e refactored error handling in NewKeys() and newGenericSessionWithSignatureAndPorts() 2024-09-10 19:55:28 -04:00
Haris Khan
2bde2687b3 refactored error handling in NewSAM(), included "fmt" 2024-09-10 19:21:06 -04:00
eyedeekay
19d8d8e4a8 Update module to i2pkeys@v0.33.7 2024-01-09 14:45:40 -05:00
eyedeekay
1cec982a61 bump version 0.33.7 2024-01-09 14:22:29 -05:00
eyedeekay
b2df466212 Don't leave errors unchecked. gofmt again. 2024-01-09 14:20:35 -05:00
eyedeekay
bf2a12d78d fix misspellings 2024-01-09 13:40:42 -05:00
eyedeekay
760e0b44b2 gofmt -s 2024-01-09 13:37:49 -05:00
eyedeekay
9b7a798782 Add a space when specifying the port to a datagram session 2024-01-09 13:34:49 -05:00
eyedeekay
3dc49e391d force an enctype if one is not present 2024-01-07 12:09:13 -05:00
eyedeekay
b08d519a17 bump version 2023-07-21 15:10:09 -04:00
idk
256aaa7430 update go modules 2023-03-07 02:19:24 +00:00
idk
197aca0ece Never pass Sig_NONE again, if sig is unset, use the correct default sig 2023-01-16 04:18:11 +00:00
idk
fff37dbffa Never pass Sig_NONE again, if sig is unset, use the correct default sig 2023-01-16 04:17:51 +00:00
idk
dfd7cd886f update index.html 2022-08-28 13:40:47 -04:00
idk
c998e57a89 update index.html 2022-08-28 13:38:27 -04:00
idk
41317685c5 update index.html 2022-08-28 13:36:46 -04:00
36 changed files with 2123 additions and 1011 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 *.swp
README.md.asc README.md.asc
log

View File

@@ -1,13 +1,14 @@
USER_GH=eyedeekay USER_GH=go-i2p
VERSION=0.33.4 VERSION=0.33.92
CREDIT='contributors to this release: @hkh4n, @eyedeekay'
packagename=sam3 packagename=sam3
echo: echo:
@echo "type make version to do release $(VERSION)" @echo "type make version to do release $(VERSION)"
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: del:
github-release delete -s $(GITHUB_TOKEN) -u $(USER_GH) -r $(packagename) -t v$(VERSION) 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 echo 'for f in $$(ls); do scp $$f/*.deb user@192.168.99.106:~/DEBIAN_PKGS/$$f/main/; done' >> deb/copy.sh
fmt: fmt:
find . -name '*.go' -exec gofmt -w -s {} \; find . -name '*.go' -exec gofumpt -w -s -extra {} \;
upload-linux: upload-linux:
github-release upload -R -u $(USER_GH) -r "$(packagename)" -t $(VERSION) -l `sha256sum ` -n "$(packagename)" -f "$(packagename)" github-release upload -R -u $(USER_GH) -r "$(packagename)" -t $(VERSION) -l `sha256sum ` -n "$(packagename)" -f "$(packagename)"

View File

@@ -1,14 +1,8 @@
# README # # README #
## !!IMPORTANT!! STATUS: This project is maintained. I will respond to issues, pull requests, and feature requests within a few days.
In the next version, I'll be moving the `i2pkeys` directory to it's own repository [![Go Report Card](https://goreportcard.com/badge/github.com/go-i2p/sam3)](https://goreportcard.com/report/github.com/go-i2p/sam3)
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' {} \;
```
# README # # README #
@@ -50,8 +44,8 @@ This library is much better than ccondom (that use BOB), much more stable and mu
package main package main
import ( import (
"github.com/eyedeekay/sam3" "github.com/go-i2p/sam3"
"github.com/eyedeekay/sam3/i2pkeys" "github.com/go-i2p/sam3/i2pkeys"
"fmt" "fmt"
) )
@@ -94,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 -tags=nettest` runs the whole suite (takes 90+ sec to perform!)
* `go test -short` runs the shorter variant, does not connect to anything * `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 ## ## License ##
Public domain. Public domain.

View File

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

398
config.go
View File

@@ -3,59 +3,33 @@ package sam3
import ( import (
"fmt" "fmt"
"math/rand" "math/rand"
"net"
"strconv"
"strings" "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 // I2PConfig is a struct which manages I2P configuration options
type I2PConfig struct { type I2PConfig struct {
SamHost string common.SAMFormatter
SamPort string config.SessionOptions
TunName string config.TransportOptions
config.TunnelOptions
SamMin string config.EncryptedLeaseSetOptions
SamMax string
Fromport string
Toport string
Style string
TunType string
DestinationKeys i2pkeys.I2PKeys DestinationKeys i2pkeys.I2PKeys
SigType string // Streaming Library options
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
//Streaming Library options
AccessListType string AccessListType string
AccessList []string AccessList []string
} }
// Sam returns the SAM address in the form of "host:port"
func (f *I2PConfig) Sam() string { func (f *I2PConfig) Sam() string {
host := "127.0.0.1" host := "127.0.0.1"
port := "7656" port := "7656"
@@ -65,9 +39,14 @@ func (f *I2PConfig) Sam() string {
if f.SamPort != "" { if f.SamPort != "" {
port = 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) { func (f *I2PConfig) SetSAMAddress(addr string) {
hp := strings.Split(addr, ":") hp := strings.Split(addr, ":")
if len(hp) == 1 { if len(hp) == 1 {
@@ -75,158 +54,89 @@ func (f *I2PConfig) SetSAMAddress(addr string) {
} else if len(hp) == 2 { } else if len(hp) == 2 {
f.SamPort = hp[1] f.SamPort = hp[1]
f.SamHost = hp[0] f.SamHost = hp[0]
} else {
if f.SamHost == "" {
f.SamHost = "127.0.0.1"
}
if f.SamPort == "" {
f.SamPort = "7656"
}
} }
f.SamPort = "7656" log.WithFields(logrus.Fields{
f.SamHost = "127.0.0.1" "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 { func (f *I2PConfig) ID() string {
if f.TunName == "" { if f.NickName == "" {
b := make([]byte, 12) b := make([]byte, 12)
for i := range b { for i := range b {
b[i] = "abcdefghijklmnopqrstuvwxyz"[rand.Intn(len("abcdefghijklmnopqrstuvwxyz"))] 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 + " " return fmt.Sprintf(" ID=%s ", f.NickName)
}
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)
} }
// MinSAM returns the minimum SAM version required in major.minor form
func (f *I2PConfig) MinSAM() string { func (f *I2PConfig) MinSAM() string {
if f.SamMin == "" { min, _ := f.GetVersions()
return "3.0" return string(min)
}
return f.SamMin
} }
// MaxSAM returns the maximum SAM version required in major.minor form
func (f *I2PConfig) MaxSAM() string { func (f *I2PConfig) MaxSAM() string {
if f.SamMax == "" { _, max := f.GetVersions()
return "3.1" return string(max)
}
return f.SamMax
} }
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 { func (f *I2PConfig) DestinationKey() string {
if &f.DestinationKeys != nil { 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 " return " DESTINATION=TRANSIENT "
} }
func (f *I2PConfig) SignatureType() string { // Print returns the full config as a 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
}
func (f *I2PConfig) Print() []string { func (f *I2PConfig) Print() []string {
lsk, lspk, lspsk := f.Leasesetsettings() lsk, lspk, lspsk := f.Leasesetsettings()
return []string{ return []string{
//f.targetForPort443(), // f.targetForPort443(),
"inbound.length=" + f.InLength, f.InboundLength(),
"outbound.length=" + f.OutLength, f.OutboundLength(),
"inbound.lengthVariance=" + f.InVariance, f.InboundVariance(),
"outbound.lengthVariance=" + f.OutVariance, f.OutboundVariance(),
"inbound.backupQuantity=" + f.InBackupQuantity, f.InboundBackupQuantity(),
"outbound.backupQuantity=" + f.OutBackupQuantity, f.OutboundBackupQuantity(),
"inbound.quantity=" + f.InQuantity, f.InboundQuantity(),
"outbound.quantity=" + f.OutQuantity, f.OutboundQuantity(),
f.DoZero(), f.InboundDoZero(),
f.OutboundDoZero(),
//"i2cp.fastRecieve=" + f.FastRecieve, //"i2cp.fastRecieve=" + f.FastRecieve,
"i2cp.gzip=" + f.UseCompression, f.DoFastReceive(),
f.UsesCompression(),
f.Reduce(), f.Reduce(),
f.Close(), f.Close(),
f.Reliability(), f.Reliability(),
@@ -234,62 +144,82 @@ func (f *I2PConfig) Print() []string {
lsk, lspk, lspsk, lsk, lspk, lspsk,
f.Accesslisttype(), f.Accesslisttype(),
f.Accesslist(), f.Accesslist(),
f.LeaseSetEncryptionType(),
} }
} }
// Accesslisttype returns the access list type
func (f *I2PConfig) Accesslisttype() string { func (f *I2PConfig) Accesslisttype() string {
if f.AccessListType == "whitelist" { if f.AccessListType == "whitelist" {
log.Debug("Access list type set to whitelist")
return "i2cp.enableAccessList=true" return "i2cp.enableAccessList=true"
} else if f.AccessListType == "blacklist" { } else if f.AccessListType == "blacklist" {
log.Debug("Access list type set to blacklist")
return "i2cp.enableBlackList=true" return "i2cp.enableBlackList=true"
} else if f.AccessListType == "none" { } else if f.AccessListType == "none" {
log.Debug("Access list type set to none")
return "" return ""
} }
log.Debug("Access list type not set")
return "" return ""
} }
// Accesslist returns the access list in the form of "i2cp.accessList=list"
func (f *I2PConfig) Accesslist() string { func (f *I2PConfig) Accesslist() string {
if f.AccessListType != "" && len(f.AccessList) > 0 { if f.AccessListType != "" && len(f.AccessList) > 0 {
r := "" r := strings.Join(f.AccessList, ",")
for _, s := range f.AccessList { log.WithField("accessList", r).Debug("Access list generated")
r += s + "," return fmt.Sprintf(" i2cp.accessList=%s ", r)
}
return "i2cp.accessList=" + strings.TrimSuffix(r, ",")
} }
log.Debug("Access list not set")
return "" return ""
} }
// NewConfig returns a new config with default values or updates them with functional arguments
func NewConfig(opts ...func(*I2PConfig) error) (*I2PConfig, error) { func NewConfig(opts ...func(*I2PConfig) error) (*I2PConfig, error) {
var config I2PConfig config := I2PConfig{
config.SamHost = "127.0.0.1" EncryptedLeaseSetOptions: config.EncryptedLeaseSetOptions{
config.SamPort = "7656" EncryptLeaseSet: false,
config.SamMin = "3.0" LeaseSetKey: "",
config.SamMax = "3.2" LeaseSetPrivateKey: "",
config.TunName = "" LeaseSetPrivateSigningKey: "",
config.TunType = "server" LeaseSetEncryption: DEFAULT_LEASESET_TYPE,
config.Style = "STREAM" },
config.InLength = "3" TunnelOptions: config.TunnelOptions{
config.OutLength = "3" InAllowZeroHop: false,
config.InQuantity = "2" OutAllowZeroHop: false,
config.OutQuantity = "2" InLength: 3,
config.InVariance = "1" OutLength: 3,
config.OutVariance = "1" InQuantity: 2,
config.InBackupQuantity = "3" OutQuantity: 2,
config.OutBackupQuantity = "3" InVariance: 1,
config.InAllowZeroHop = "false" OutVariance: 1,
config.OutAllowZeroHop = "false" InBackupQuantity: 3,
config.EncryptLeaseSet = "false" OutBackupQuantity: 3,
config.LeaseSetKey = "" },
config.LeaseSetPrivateKey = "" SessionOptions: config.SessionOptions{
config.LeaseSetPrivateSigningKey = "" NickName: "",
config.FastRecieve = "false" Style: "STREAM",
config.UseCompression = "true" SigType: "EdDSA_SHA512_Ed25519",
config.ReduceIdle = "false" InFromPort: "",
config.ReduceIdleTime = "15" OutToPort: "",
config.ReduceIdleQuantity = "4" Protocol: "",
config.CloseIdle = "false" UDPPort: 0,
config.CloseIdleTime = "300000" SamHost: "127.0.0.1",
config.MessageReliability = "none" 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 { for _, o := range opts {
if err := o(&config); err != nil { if err := o(&config); err != nil {
return nil, err return nil, err
@@ -297,67 +227,3 @@ func NewConfig(opts ...func(*I2PConfig) error) (*I2PConfig, error) {
} }
return &config, nil 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 ( import (
"bytes" "bytes"
"errors" "errors"
"fmt"
"net" "net"
"strconv" "sync"
"time" "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 // 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) samAddr string // address to the sam bridge (ipv4:port)
id string // tunnel name id string // tunnel name
conn net.Conn // connection to sam bridge conn net.Conn // connection to sam bridge
udpconn *net.UDPConn // used to deliver datagrams
keys i2pkeys.I2PKeys // i2p destination keys keys i2pkeys.I2PKeys // i2p destination keys
rUDPAddr *net.UDPAddr // the SAM bridge UDP-port
remoteAddr *i2pkeys.I2PAddr // optional remote I2P address remoteAddr *i2pkeys.I2PAddr // optional remote I2P address
common.UDPSession
*DatagramOptions
} }
// Creates a new datagram session. udpPort is the UDP port SAM is listening on, // 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. // 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) { func (s *SAM) NewDatagramSession(id string, keys i2pkeys.I2PKeys, options []string, udpPort int, datagramOptions ...DatagramOptions) (*DatagramSession, error) {
if udpPort > 65335 || udpPort < 0 { log.WithFields(logrus.Fields{
return nil, errors.New("udpPort needs to be in the intervall 0-65335") "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 { udpconn, err := common.NewUDPSession(udpSessionConfig)
udpPort = 7655
}
lhost, _, err := SplitHostPort(s.conn.LocalAddr().String())
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create UDP session")
return nil, err
}
_, lport, err := net.SplitHostPort(udpconn.Conn.LocalAddr().String())
if err != nil {
log.WithError(err).Error("Failed to get local port")
s.Close() s.Close()
return nil, err return nil, err
} }
lUDPAddr, err := net.ResolveUDPAddr("udp4", lhost+":0") conn, err := s.newGenericSession("DATAGRAM", id, keys, options, []string{" PORT=" + lport})
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create generic session")
return nil, err return nil, err
} }
udpconn, err := net.ListenUDP("udp4", lUDPAddr) if len(datagramOptions) > 0 {
if err != nil { return &DatagramSession{
return nil, err samAddr: s.address,
id: id,
conn: conn,
keys: keys,
UDPSession: *udpconn,
DatagramOptions: &datagramOptions[0],
}, nil
} }
rhost, _, err := SplitHostPort(s.conn.RemoteAddr().String()) log.WithField("id", id).Info("DatagramSession created successfully")
if err != nil { // return &DatagramSession{s.address, id, conn, udpconn, keys, rUDPAddr, nil, nil}, nil
s.Close() return &DatagramSession{
return nil, err samAddr: s.address,
} id: id,
rUDPAddr, err := net.ResolveUDPAddr("udp4", rhost+":"+strconv.Itoa(udpPort)) conn: conn,
if err != nil { keys: keys,
return nil, err UDPSession: *udpconn,
} }, nil
_, lport, err := net.SplitHostPort(udpconn.LocalAddr().String())
if err != nil {
s.Close()
return nil, err
}
conn, err := s.newGenericSession("DATAGRAM", id, keys, options, []string{"PORT=" + lport})
if err != nil {
return nil, err
}
return &DatagramSession{s.address, id, conn, udpconn, keys, rUDPAddr, nil}, nil
} }
func (s *DatagramSession) B32() string { 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) netaddr, err := s.Lookup(addr)
if err != nil { if err != nil {
log.WithError(err).Error("Lookup failed")
return nil, err return nil, err
} }
return s.DialI2PRemote(net, netaddr) return s.DialI2PRemote(net, netaddr)
} }
func (s *DatagramSession) DialRemote(net, addr string) (net.PacketConn, error) { 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) netaddr, err := s.Lookup(addr)
if err != nil { if err != nil {
log.WithError(err).Error("Lookup failed")
return nil, err return nil, err
} }
return s.DialI2PRemote(net, netaddr) return s.DialI2PRemote(net, netaddr)
} }
func (s *DatagramSession) DialI2PRemote(net string, addr net.Addr) (*DatagramSession, error) { 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) { switch addr.(type) {
case *i2pkeys.I2PAddr: case *i2pkeys.I2PAddr:
s.remoteAddr = addr.(*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 { func (s *DatagramSession) RemoteAddr() net.Addr {
log.WithField("remoteAddr", s.remoteAddr).Debug("Getting remote address")
return s.remoteAddr 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. // the number of bytes read, from what address it was sent, or an error.
// implements net.PacketConn // implements net.PacketConn
func (s *DatagramSession) ReadFrom(b []byte) (n int, addr net.Addr, err error) { func (s *DatagramSession) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
// extra bytes to read the remote address of incomming datagram log.Debug("Reading datagram")
buf := make([]byte, len(b)+4096) // 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 { for {
// very basic protection: only accept incomming UDP messages from the IP of the SAM bridge // very basic protection: only accept incomming UDP messages from the IP of the SAM bridge
var saddr *net.UDPAddr var saddr *net.UDPAddr
n, saddr, err = s.udpconn.ReadFromUDP(buf) n, saddr, err = s.UDPSession.Conn.ReadFromUDP(buf)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to read from UDP")
return 0, i2pkeys.I2PAddr(""), err return 0, i2pkeys.I2PAddr(""), err
} }
if bytes.Equal(saddr.IP, s.rUDPAddr.IP) { if bytes.Equal(saddr.IP, s.UDPSession.RemoteAddr.IP) {
continue continue
} }
break break
} }
i := bytes.IndexByte(buf, byte('\n')) i := bytes.IndexByte(buf, byte(' '))
if i > 4096 || i > n { if i > 4096 || i > n {
log.Error("Could not parse incoming message remote address")
return 0, i2pkeys.I2PAddr(""), errors.New("Could not parse incomming message remote address.") return 0, i2pkeys.I2PAddr(""), errors.New("Could not parse incomming message remote address.")
} }
raddr, err := i2pkeys.NewI2PAddrFromString(string(buf[:i])) raddr, err := i2pkeys.NewI2PAddrFromString(string(buf[:i]))
if err != nil { if err != nil {
log.WithError(err).Error("Could not parse incoming message remote address")
return 0, i2pkeys.I2PAddr(""), errors.New("Could not parse incomming message remote address: " + err.Error()) return 0, i2pkeys.I2PAddr(""), errors.New("Could not parse incomming message remote address: " + err.Error())
} }
// shift out the incomming address to contain only the data received // 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.") return n - (i + 1), raddr, errors.New("Datagram did not fit into your buffer.")
} else { } else {
copy(b, buf[i+1:n]) copy(b, buf[i+1:n])
log.WithField("bytesRead", n-(i+1)).Debug("Datagram read successfully")
return n - (i + 1), raddr, nil return n - (i + 1), raddr, nil
} }
} }
func (s *DatagramSession) Accept() (net.Conn, error) { func (s *DatagramSession) Accept() (net.Conn, error) {
log.Debug("Accept called on DatagramSession")
return s, nil return s, nil
} }
func (s *DatagramSession) Read(b []byte) (n int, err error) { func (s *DatagramSession) Read(b []byte) (n int, err error) {
log.Debug("Reading from DatagramSession")
rint, _, rerr := s.ReadFrom(b) rint, _, rerr := s.ReadFrom(b)
return rint, rerr 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 // 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. // writing, maximum size is 31 kilobyte, but this may change in the future.
// Implements net.PacketConn. // Implements net.PacketConn.
func (s *DatagramSession) WriteTo(b []byte, addr net.Addr) (n int, err error) { 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...) 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 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) { func (s *DatagramSession) Write(b []byte) (int, error) {
log.WithField("dataLen", len(b)).Debug("Writing to DatagramSession")
return s.WriteTo(b, s.remoteAddr) return s.WriteTo(b, s.remoteAddr)
} }
// Closes the DatagramSession. Implements net.PacketConn // Closes the DatagramSession. Implements net.PacketConn
func (s *DatagramSession) Close() error { func (s *DatagramSession) Close() error {
log.Debug("Closing DatagramSession")
err := s.conn.Close() err := s.conn.Close()
err2 := s.udpconn.Close() err2 := s.UDPSession.Conn.Close()
if err != nil { if err != nil {
log.WithError(err).Error("Failed to close connection")
return err return err
} }
if err2 != nil {
log.WithError(err2).Error("Failed to close UDP connection")
}
return err2 return err2
} }
// Returns the I2P destination of the DatagramSession. // Returns the I2P destination of the DatagramSession.
func (s *DatagramSession) LocalI2PAddr() i2pkeys.I2PAddr { 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 // Implements net.PacketConn
@@ -187,12 +340,14 @@ func (s *DatagramSession) Addr() net.Addr {
} }
func (s *DatagramSession) Lookup(name string) (a net.Addr, err error) { func (s *DatagramSession) Lookup(name string) (a net.Addr, err error) {
log.WithField("name", name).Debug("Looking up address")
var sam *SAM var sam *SAM
sam, err = NewSAM(s.samAddr) sam, err = NewSAM(s.samAddr)
if err == nil { if err == nil {
defer sam.Close() defer sam.Close()
a, err = sam.Lookup(name) a, err = sam.Lookup(name)
} }
log.WithField("address", a).Debug("Lookup successful")
return 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 // net.PacketConn and does the same thing. Setting write deadlines for datagrams
// is seldom done. // is seldom done.
func (s *DatagramSession) SetDeadline(t time.Time) error { 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 // Sets read deadline for the DatagramSession. Implements net.PacketConn
func (s *DatagramSession) SetReadDeadline(t time.Time) error { 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. // Sets the write deadline for the DatagramSession. Implements net.Packetconn.
func (s *DatagramSession) SetWriteDeadline(t time.Time) error { 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 { 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 ( import (
"fmt" "fmt"
"log"
"testing" "testing"
"time" "time"
sam3opts "github.com/go-i2p/sam3/opts"
) )
func Test_DatagramServerClient(t *testing.T) { func Test_DatagramServerClient(t *testing.T) {
@@ -103,7 +104,7 @@ func ExampleDatagramSession() {
myself := keys.Addr() myself := keys.Addr()
// See the example Option_* variables. // 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 { if err != nil {
fmt.Println(err.Error()) fmt.Println(err.Error())
return return
@@ -123,12 +124,12 @@ func ExampleDatagramSession() {
fmt.Println(err.Error()) fmt.Println(err.Error())
return return
} }
log.Println("Got message: '" + string(buf[:n]) + "'") fmt.Println("Got message: '" + string(buf[:n]) + "'")
fmt.Println("Got message: " + string(buf[:n])) fmt.Println("Got message: " + string(buf[:n]))
return return
// Output: // Output:
//Got message: Hello myself! // Got message: Hello myself!
} }
func ExampleMiniDatagramSession() { func ExampleMiniDatagramSession() {
@@ -149,7 +150,7 @@ func ExampleMiniDatagramSession() {
myself := keys.Addr() myself := keys.Addr()
// See the example Option_* variables. // 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 { if err != nil {
fmt.Println(err.Error()) fmt.Println(err.Error())
return return
@@ -179,5 +180,5 @@ func ExampleMiniDatagramSession() {
return return
// Output: // Output:
//Got message: Hello myself! // Got message: Hello myself!
} }

View File

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

57
emit.go
View File

@@ -2,9 +2,10 @@ package sam3
import ( import (
"fmt" "fmt"
"log"
"net" "net"
"strings" "strings"
"github.com/sirupsen/logrus"
) )
type SAMEmit struct { type SAMEmit struct {
@@ -12,15 +13,15 @@ type SAMEmit struct {
} }
func (e *SAMEmit) OptStr() string { func (e *SAMEmit) OptStr() string {
optStr := "" optStr := strings.Join(e.I2PConfig.Print(), " ")
for _, opt := range e.I2PConfig.Print() { log.WithField("optStr", optStr).Debug("Generated option string")
optStr += opt + " "
}
return optStr return optStr
} }
func (e *SAMEmit) Hello() string { 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 { func (e *SAMEmit) HelloBytes() []byte {
@@ -28,7 +29,9 @@ func (e *SAMEmit) HelloBytes() []byte {
} }
func (e *SAMEmit) GenerateDestination() string { 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 { func (e *SAMEmit) GenerateDestinationBytes() []byte {
@@ -36,40 +39,25 @@ func (e *SAMEmit) GenerateDestinationBytes() []byte {
} }
func (e *SAMEmit) Lookup(name string) string { 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 { func (e *SAMEmit) LookupBytes(name string) []byte {
return []byte(e.Lookup(name)) 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 { func (e *SAMEmit) Connect(dest string) string {
return fmt.Sprintf( connect := fmt.Sprintf(
"STREAM CONNECT ID=%s %s %s DESTINATION=%s \n", "STREAM CONNECT ID=%s %s %s DESTINATION=%s \n",
e.I2PConfig.ID(), e.I2PConfig.ID(),
e.I2PConfig.FromPort(), e.I2PConfig.FromPort(),
e.I2PConfig.ToPort(), e.I2PConfig.ToPort(),
dest, dest,
) )
log.WithField("connect", connect).Debug("Generated STREAM CONNECT command")
return connect
} }
func (e *SAMEmit) ConnectBytes(dest string) []byte { func (e *SAMEmit) ConnectBytes(dest string) []byte {
@@ -77,12 +65,14 @@ func (e *SAMEmit) ConnectBytes(dest string) []byte {
} }
func (e *SAMEmit) Accept() string { func (e *SAMEmit) Accept() string {
return fmt.Sprintf( accept := fmt.Sprintf(
"STREAM ACCEPT ID=%s %s %s", "STREAM ACCEPT ID=%s %s %s",
e.I2PConfig.ID(), e.I2PConfig.ID(),
e.I2PConfig.FromPort(), e.I2PConfig.FromPort(),
e.I2PConfig.ToPort(), e.I2PConfig.ToPort(),
) )
log.WithField("accept", accept).Debug("Generated STREAM ACCEPT command")
return accept
} }
func (e *SAMEmit) AcceptBytes() []byte { func (e *SAMEmit) AcceptBytes() []byte {
@@ -93,9 +83,11 @@ func NewEmit(opts ...func(*SAMEmit) error) (*SAMEmit, error) {
var emit SAMEmit var emit SAMEmit
for _, o := range opts { for _, o := range opts {
if err := o(&emit); err != nil { if err := o(&emit); err != nil {
log.WithError(err).Error("Failed to apply option")
return nil, err return nil, err
} }
} }
log.Debug("New SAMEmit instance created")
return &emit, nil return &emit, nil
} }
@@ -104,6 +96,7 @@ func IgnorePortError(err error) error {
return nil return nil
} }
if strings.Contains(err.Error(), "missing port in address") { if strings.Contains(err.Error(), "missing port in address") {
log.Debug("Ignoring 'missing port in address' error")
err = nil err = nil
} }
return err return err
@@ -113,10 +106,14 @@ func SplitHostPort(hostport string) (string, string, error) {
host, port, err := net.SplitHostPort(hostport) host, port, err := net.SplitHostPort(hostport)
if err != nil { if err != nil {
if IgnorePortError(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 host = hostport
port = "0" port = "0"
} }
} }
log.WithFields(logrus.Fields{
"host": host,
"port": port,
}).Debug("Split host and port")
return host, port, nil return host, port, nil
} }

18
go.mod
View File

@@ -1,13 +1,13 @@
module github.com/eyedeekay/sam3 module github.com/go-i2p/sam3
go 1.12 go 1.23.3
require ( require (
github.com/eyedeekay/goSam v0.32.31-0.20210122211817-f97683379f23 github.com/go-i2p/i2pkeys v0.33.92
github.com/eyedeekay/i2pkeys v0.0.0-20220310052025-204d4ae6dcae // indirect github.com/go-i2p/logger v0.0.0-20241123010126-3050657e5d0c
github.com/google/renameio v1.0.0 // indirect github.com/sirupsen/logrus v1.9.3
github.com/riobard/go-x25519 v0.0.0-20190716001027-10cc4d8d0b33
github.com/rogpeppe/go-internal v1.6.2 // indirect
golang.org/x/tools v0.0.0-20201125231158-b5590deeca9b // indirect
honnef.co/go/tools v0.0.1-2020.1.6 // indirect
) )
require golang.org/x/sys v0.27.0 // indirect
replace github.com/go-i2p/i2pkeys v0.33.92 => ../i2pkeys

102
go.sum
View File

@@ -1,90 +1,18 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/agl/ed25519 v0.0.0-20200225211852-fd4d107ace12 h1:iPf1jQ8yKTms6k6L5vYSE7RZJpjEe5vLTOmzRZdpnKc=
github.com/cryptix/go v1.3.1 h1:I9opbROgEpldI0PwkMku0UY2DLFYgelZd9u0uaxmMgY=
github.com/cryptix/go v1.3.1/go.mod h1:mFQotm9rTzptzvNjJM+1vSIDa/rVOVqMu0889GIXg70=
github.com/cryptix/goSam v0.1.0 h1:lKXtrTv3Kd6+eIuNtcq3zPShJEVRqw+lQwmh49HmC7k=
github.com/cryptix/goSam v0.1.0/go.mod h1:7ewkjhXT8V5RG07pvWUOHHtMahvGbeKlEv8ukUyRiTA=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/eyedeekay/goSam v0.32.30 h1:mMlZNE2oISdjjjpgfN17W56tn9F8rD/Jc2tsjTDDFYg= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/eyedeekay/goSam v0.32.30/go.mod h1:UgJnih/LpotwKriwVPOEa6yPDM2NDdVrKfLtS5DOLPE= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/eyedeekay/goSam v0.32.31-0.20210122211024-dddd8ea916d6 h1:seMFdfTWvmAsyj9jYPZATBJiJu5gHPsvJJk4Ruo2npQ= github.com/go-i2p/logger v0.0.0-20241123010126-3050657e5d0c h1:VTiECn3dFEmUlZjto+wOwJ7SSJTHPLyNprQMR5HzIMI=
github.com/eyedeekay/goSam v0.32.31-0.20210122211024-dddd8ea916d6/go.mod h1:UgJnih/LpotwKriwVPOEa6yPDM2NDdVrKfLtS5DOLPE= github.com/go-i2p/logger v0.0.0-20241123010126-3050657e5d0c/go.mod h1:te7Zj3g3oMeIl8uBXAgO62UKmZ6m6kHRNg1Mm+X8Hzk=
github.com/eyedeekay/goSam v0.32.31-0.20210122211817-f97683379f23 h1:AHm/EzBilSQH+RFgEuslnlCpVQd88MQWx7KHW/VIQlc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/eyedeekay/goSam v0.32.31-0.20210122211817-f97683379f23/go.mod h1:UgJnih/LpotwKriwVPOEa6yPDM2NDdVrKfLtS5DOLPE=
github.com/eyedeekay/i2pkeys v0.0.0-20220310052025-204d4ae6dcae h1:SwegHeaf4pkDMB24UltIvJlj2+nd06QUZAbs8BDyfjM=
github.com/eyedeekay/i2pkeys v0.0.0-20220310052025-204d4ae6dcae/go.mod h1:W9KCm9lqZ+Ozwl3dwcgnpPXAML97+I8Jiht7o5A8YBM=
github.com/eyedeekay/sam3 v0.32.32/go.mod h1:qRA9KIIVxbrHlkj+ZB+OoxFGFgdKeGp1vSgPw26eOVU=
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY=
github.com/getlantern/errors v1.0.1/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A=
github.com/getlantern/go-socks5 v0.0.0-20171114193258-79d4dd3e2db5/go.mod h1:kGHRXch95rnGLHjER/GhhFiHvfnqNz7KqWD9kGfATHY=
github.com/getlantern/golog v0.0.0-20201105130739-9586b8bde3a9/go.mod h1:ZyIjgH/1wTCl+B+7yH1DqrWp6MPJqESmwmEQ89ZfhvA=
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7/go.mod h1:dD3CgOrwlzca8ed61CsZouQS5h5jIzkK9ZWrTcf0s+o=
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55/go.mod h1:6mmzY2kW1TOOrVy+r41Za2MxXM+hhqTtY3oBKd2AgFA=
github.com/getlantern/netx v0.0.0-20190110220209-9912de6f94fd/go.mod h1:wKdY0ikOgzrWSeB9UyBVKPRhjXQ+vTb+BPeJuypUuNE=
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
github.com/getlantern/ops v0.0.0-20200403153110-8476b16edcd6/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
github.com/go-kit/kit v0.6.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-stack/stack v1.7.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/renameio v1.0.0 h1:xhp2CnJmgQmpJU4RY8chagahUq5mbPPAbiSQstKpVMA=
github.com/google/renameio v1.0.0/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWUAweKUpk=
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/miolini/datacounter v0.0.0-20171104152933-fd4e42a1d5e0 h1:clkDYGefEWUCwyCrwYn900sOaVGDpinPJgD0W6ebEjs=
github.com/miolini/datacounter v0.0.0-20171104152933-fd4e42a1d5e0/go.mod h1:P6fDJzlxN+cWYR09KbE9/ta+Y6JofX9tAUhJpWkWPaM=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/riobard/go-x25519 v0.0.0-20190716001027-10cc4d8d0b33 h1:dyyWDK0yzlZ8ay89Oe5ZIRtscacUjFyPUFGChrgMXRg= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/riobard/go-x25519 v0.0.0-20190716001027-10cc4d8d0b33/go.mod h1:BjmVxzAnkLeoEbqHEerI4eSw6ua+RaIB0S4jMV21RAs= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.2 h1:aIihoIOHCiLZHxyoNQ+ABL4NKhFTgKLBdMLyEAh98m0=
github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef h1:RHORRhs540cYZYrzgU2CPUyykkwZM78hGdzocOo9P8A=
golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201125231158-b5590deeca9b h1:Lq5JUTFhiybGVf28jB6QRpqd13/JPOaCnET17PVzYJE=
golang.org/x/tools v0.0.0-20201125231158-b5590deeca9b/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
honnef.co/go/tools v0.0.1-2020.1.6 h1:W18jzjh8mfPez+AwGLxmOImucz/IFjpNlrKVnaj2YVc=
honnef.co/go/tools v0.0.1-2020.1.6/go.mod h1:pyyisuGw24ruLjrr1ddx39WE0y9OooInRzEYLhQB2YY=

View File

@@ -1,125 +0,0 @@
package sam
import (
"io/ioutil"
"log"
"net"
"os"
"github.com/eyedeekay/i2pkeys"
"github.com/eyedeekay/sam3"
)
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 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)
}

View File

@@ -7,6 +7,7 @@
<meta name="description" content="sam3" /> <meta name="description" content="sam3" />
<meta name="keywords" content="master" /> <meta name="keywords" content="master" />
<link rel="stylesheet" type="text/css" href="style.css" /> <link rel="stylesheet" type="text/css" href="style.css" />
<link rel="stylesheet" type="text/css" href="showhider.css" />
</head> </head>
<body> <body>
<div id="navbar"> <div id="navbar">
@@ -30,19 +31,23 @@
</div> </div>
</div> </div>
<h1> <h1>
<a href="/"> <a href="#readme" rel="nofollow">
README <span></span>
</a> </a>
README
</h1> </h1>
<h2> <h2>
<a href="#important" rel="nofollow">
<span></span>
</a>
!!IMPORTANT!! !!IMPORTANT!!
</h2> </h2>
<p> <p>
In the next version, Ill be moving the In the next version, I&#39;ll be moving the
<code> <code>
i2pkeys i2pkeys
</code> </code>
directory to its own repository directory to it&#39;s own repository
so I can avoid import cycle headaches. Please migrate to the new so I can avoid import cycle headaches. Please migrate to the new
<code> <code>
i2pkeys i2pkeys
@@ -50,9 +55,17 @@
repository repository
before upgrading your sam3 dependencies. You can probably do this by running: before upgrading your sam3 dependencies. You can probably do this by running:
</p> </p>
<pre><code>find . -name &#39;*.go&#39; -exec sed -i &#39;s|github.com/eyedeekay/sam3/i2pkeys|github.com/eyedeekay/i2pkeys|g&#39; {} \; <div>
</code></pre> <pre>find . -name &#39;*.go&#39; -exec sed -i &#39;s|github.com/eyedeekay/sam3/i2pkeys|github.com/eyedeekay/i2pkeys|g&#39; {} \;
</pre>
</div>
<p>
STATUS: This project is maintained. I will respond to issues, pull requests, and feature requests within a few days.
</p>
<h1> <h1>
<a href="#readme" rel="nofollow">
<span></span>
</a>
README README
</h1> </h1>
<p> <p>
@@ -66,6 +79,9 @@
This library is much better than ccondom (that use BOB), much more stable and much easier to maintain. This library is much better than ccondom (that use BOB), much more stable and much easier to maintain.
</p> </p>
<h2> <h2>
<a href="#support-todo" rel="nofollow">
<span></span>
</a>
Support/TODO Support/TODO
</h2> </h2>
<p> <p>
@@ -133,6 +149,9 @@
</li> </li>
</ul> </ul>
<h2> <h2>
<a href="#documentation" rel="nofollow">
<span></span>
</a>
Documentation Documentation
</h2> </h2>
<ul> <ul>
@@ -160,9 +179,13 @@
</li> </li>
</ul> </ul>
<h2> <h2>
<a href="#examples" rel="nofollow">
<span></span>
</a>
Examples Examples
</h2> </h2>
<pre><code>package main <div>
<pre>package main
import ( import (
&#34;github.com/eyedeekay/sam3&#34; &#34;github.com/eyedeekay/sam3&#34;
@@ -193,17 +216,23 @@ func main() {
n, _ := conn.Read(buf) n, _ := conn.Read(buf)
fmt.Println(&#34;Server received: &#34; + string(buf[:n])) fmt.Println(&#34;Server received: &#34; + string(buf[:n]))
} }
</code></pre> </pre>
</div>
<p> <p>
The above will write to the terminal: The above will write to the terminal:
</p> </p>
<pre><code>Client: Connecting to zjnvfh4hs3et5vtz35ogwzrws26zvwkcad5uo5esecvg4qpk5b4a.b32.i2p <div>
<pre>Client: Connecting to zjnvfh4hs3et5vtz35ogwzrws26zvwkcad5uo5esecvg4qpk5b4a.b32.i2p
Server received: Hello world! Server received: Hello world!
</code></pre> </pre>
</div>
<p> <p>
Error handling was omitted in the above code for readability. Error handling was omitted in the above code for readability.
</p> </p>
<h2> <h2>
<a href="#testing" rel="nofollow">
<span></span>
</a>
Testing Testing
</h2> </h2>
<ul> <ul>
@@ -221,12 +250,18 @@ Server received: Hello world!
</li> </li>
</ul> </ul>
<h2> <h2>
<a href="#license" rel="nofollow">
<span></span>
</a>
License License
</h2> </h2>
<p> <p>
Public domain. Public domain.
</p> </p>
<h2> <h2>
<a href="#author" rel="nofollow">
<span></span>
</a>
Author Author
</h2> </h2>
<ul> <ul>

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" "net"
"strconv" "strconv"
"strings" "strings"
"sync"
"time" "time"
"github.com/eyedeekay/i2pkeys" "github.com/sirupsen/logrus"
"github.com/go-i2p/i2pkeys"
"github.com/go-i2p/sam3/common"
) )
const ( const (
@@ -20,6 +24,8 @@ func randport() string {
s := rand.NewSource(time.Now().UnixNano()) s := rand.NewSource(time.Now().UnixNano())
r := rand.New(s) r := rand.New(s)
p := r.Intn(55534) + 10000 p := r.Intn(55534) + 10000
port := strconv.Itoa(p)
log.WithField("port", port).Debug("Generated random port")
return strconv.Itoa(p) return strconv.Itoa(p)
} }
@@ -35,6 +41,7 @@ type PrimarySession struct {
Config SAMEmit Config SAMEmit
stsess map[string]*StreamSession stsess map[string]*StreamSession
dgsess map[string]*DatagramSession dgsess map[string]*DatagramSession
sync.RWMutex
// from string // from string
// to string // to string
} }
@@ -76,124 +83,192 @@ func (ss *PrimarySession) Keys() i2pkeys.I2PKeys {
} }
func (sam *PrimarySession) Dial(network, addr string) (net.Conn, error) { 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" { 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) return sam.DialUDPI2P(network, network+addr[0:4], addr)
} }
if network == "tcp" || network == "tcp4" || network == "tcp6" { 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) 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") return nil, fmt.Errorf("Error: Must specify a valid network type")
} }
// DialTCP implements x/dialer // DialTCP implements x/dialer
func (sam *PrimarySession) DialTCP(network string, laddr, raddr net.Addr) (net.Conn, error) { 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]] ts, ok := sam.stsess[network+raddr.String()[0:4]]
var err error sam.RUnlock()
if !ok { 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 { if err != nil {
log.WithError(err).Error("Failed to create new unique stream sub-session")
sam.Unlock()
return nil, err return nil, err
} }
sam.stsess[network+raddr.String()[0:4]] = ts sam.stsess[network+raddr.String()[0:4]] = ts
ts, _ = sam.stsess[network+raddr.String()[0:4]] sam.Unlock()
} }
return ts.Dial(network, raddr.String()) 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]] ts, ok := sam.stsess[network+raddr[0:4]]
var err error sam.RUnlock()
if !ok { if !ok {
ts, err = sam.NewUniqueStreamSubSession(network + laddr) sam.Lock()
ts, err := sam.NewUniqueStreamSubSession(network + laddr)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create new unique stream sub-session")
sam.Unlock()
return nil, err return nil, err
} }
sam.stsess[network+raddr[0:4]] = ts sam.stsess[network+raddr[0:4]] = ts
ts, _ = sam.stsess[network+raddr[0:4]] sam.Unlock()
} }
return ts.Dial(network, raddr) return ts.Dial(network, raddr)
} }
// DialUDP implements x/dialer // DialUDP implements x/dialer
func (sam *PrimarySession) DialUDP(network string, laddr, raddr net.Addr) (net.PacketConn, error) { 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]] ds, ok := sam.dgsess[network+raddr.String()[0:4]]
var err error sam.RUnlock()
if !ok { 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 { if err != nil {
log.WithError(err).Error("Failed to create new datagram sub-session")
sam.Unlock()
return nil, err return nil, err
} }
sam.dgsess[network+raddr.String()[0:4]] = ds sam.dgsess[network+raddr.String()[0:4]] = ds
ds, _ = sam.dgsess[network+raddr.String()[0:4]] sam.Unlock()
} }
return ds.Dial(network, raddr.String()) return ds.Dial(network, raddr.String())
} }
func (sam *PrimarySession) DialUDPI2P(network, laddr, raddr string) (*DatagramSession, error) { 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]] ds, ok := sam.dgsess[network+raddr[0:4]]
var err error sam.RUnlock()
if !ok { if !ok {
ds, err = sam.NewDatagramSubSession(network+laddr, 0) sam.Lock()
ds, err := sam.NewDatagramSubSession(network+laddr, 0)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create new datagram sub-session")
sam.Unlock()
return nil, err return nil, err
} }
sam.dgsess[network+raddr[0:4]] = ds sam.dgsess[network+raddr[0:4]] = ds
ds, _ = sam.dgsess[network+raddr[0:4]] sam.Unlock()
} }
return ds.Dial(network, raddr) return ds.Dial(network, raddr)
} }
func (s *PrimarySession) Lookup(name string) (a net.Addr, err error) { func (s *PrimarySession) Lookup(name string) (a net.Addr, err error) {
log.WithField("name", name).Debug("Lookup() called")
var sam *SAM var sam *SAM
name = strings.Split(name, ":")[0] name = strings.Split(name, ":")[0]
sam, err = NewSAM(s.samAddr) sam, err = NewSAM(s.samAddr)
if err == nil { if err == nil {
log.WithField("addr", a).Debug("Lookup successful")
defer sam.Close() defer sam.Close()
a, err = sam.Lookup(name) a, err = sam.Lookup(name)
} }
log.WithError(err).Error("Lookup failed")
return return
} }
func (sam *PrimarySession) Resolve(network, addr string) (net.Addr, error) { 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) return sam.Lookup(addr)
} }
func (sam *PrimarySession) ResolveTCPAddr(network, dest string) (net.Addr, error) { 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) return sam.Lookup(dest)
} }
func (sam *PrimarySession) ResolveUDPAddr(network, dest string) (net.Addr, error) { 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) return sam.Lookup(dest)
} }
// Creates a new PrimarySession with the I2CP- and streaminglib options as // Creates a new PrimarySession with the I2CP- and streaminglib options as
// specified. See the I2P documentation for a full list of options. // specified. See the I2P documentation for a full list of options.
func (sam *SAM) NewPrimarySession(id string, keys i2pkeys.I2PKeys, options []string) (*PrimarySession, error) { 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) 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{}) conn, err := sam.newGenericSession(primarySessionSwitch, id, keys, options, []string{})
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create new generic session")
return nil, err return nil, err
} }
ssesss := make(map[string]*StreamSession) ssesss := make(map[string]*StreamSession)
dsesss := make(map[string]*DatagramSession) 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 // Creates a new PrimarySession with the I2CP- and PRIMARYinglib options as
// specified. See the I2P documentation for a full list of options. // 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) { 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{}) conn, err := sam.newGenericSessionWithSignature(PrimarySessionSwitch, id, keys, sigType, options, []string{})
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create new generic session with signature")
return nil, err return nil, err
} }
ssesss := make(map[string]*StreamSession) ssesss := make(map[string]*StreamSession)
dsesss := make(map[string]*DatagramSession) 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", // 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{}. // setting extra to something else than []string{}.
// This sam3 instance is now a session // This sam3 instance is now a session
func (sam *PrimarySession) newGenericSubSession(style, id string, extras []string) (net.Conn, error) { 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) return sam.newGenericSubSessionWithSignature(style, id, extras)
} }
func (sam *PrimarySession) newGenericSubSessionWithSignature(style, id string, extras []string) (net.Conn, error) { 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) 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{}. // setting extra to something else than []string{}.
// This sam3 instance is now a session // This sam3 instance is now a session
func (sam *PrimarySession) newGenericSubSessionWithSignatureAndPorts(style, id, from, to string, extras []string) (net.Conn, error) { 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 conn := sam.conn
fp := "" 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, " ")))
tp := ""
if from != "0" && from != "" { log.WithField("message", string(scmsg)).Debug("Sending SESSION ADD message")
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")
for m, i := 0, 0; m != len(scmsg); i++ { for m, i := 0, 0; m != len(scmsg); i++ {
if i == 15 { if i == 15 {
conn.Close() conn.Close()
log.Error("Writing to SAM failed after 15 attempts")
return nil, errors.New("writing to SAM failed") return nil, errors.New("writing to SAM failed")
} }
n, err := conn.Write(scmsg[m:]) n, err := conn.Write(scmsg[m:])
if err != nil { if err != nil {
log.WithError(err).Error("Failed to write to SAM connection")
conn.Close() conn.Close()
return nil, err return nil, err
} }
@@ -241,30 +316,38 @@ func (sam *PrimarySession) newGenericSubSessionWithSignatureAndPorts(style, id,
buf := make([]byte, 4096) buf := make([]byte, 4096)
n, err := conn.Read(buf) n, err := conn.Read(buf)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to read from SAM connection")
conn.Close() conn.Close()
return nil, err return nil, err
} }
text := string(buf[:n]) 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 strings.HasPrefix(text, session_ADDOK) {
//if sam.keys.String() != text[len(session_ADDOK):len(text)-1] { //if sam.keys.String() != text[len(session_ADDOK):len(text)-1] {
//conn.Close() //conn.Close()
//return nil, errors.New("SAMv3 created a tunnel with keys other than the ones we asked it for") //return nil, errors.New("SAMv3 created a tunnel with keys other than the ones we asked it for")
//} //}
log.Debug("Session added successfully")
return conn, nil //&StreamSession{id, conn, keys, nil, sync.RWMutex{}, nil}, nil return conn, nil //&StreamSession{id, conn, keys, nil, sync.RWMutex{}, nil}, nil
} else if text == session_DUPLICATE_ID { } else if text == session_DUPLICATE_ID {
log.Error("Duplicate tunnel name")
conn.Close() conn.Close()
return nil, errors.New("Duplicate tunnel name") return nil, errors.New("Duplicate tunnel name")
} else if text == session_DUPLICATE_DEST { } else if text == session_DUPLICATE_DEST {
log.Error("Duplicate destination")
conn.Close() conn.Close()
return nil, errors.New("Duplicate destination") return nil, errors.New("Duplicate destination")
} else if text == session_INVALID_KEY { } else if text == session_INVALID_KEY {
log.Error("Invalid key - Primary Session")
conn.Close() conn.Close()
return nil, errors.New("Invalid key - Primary Session") return nil, errors.New("Invalid key - Primary Session")
} else if strings.HasPrefix(text, session_I2P_ERROR) { } else if strings.HasPrefix(text, session_I2P_ERROR) {
log.WithField("error", text[len(session_I2P_ERROR):]).Error("I2P error")
conn.Close() conn.Close()
return nil, errors.New("I2P error " + text[len(session_I2P_ERROR):]) return nil, errors.New("I2P error " + text[len(session_I2P_ERROR):])
} else { } else {
log.WithField("reply", text).Error("Unable to parse SAMv3 reply")
conn.Close() conn.Close()
return nil, errors.New("Unable to parse SAMv3 reply: " + text) 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 // Creates a new StreamSession with the I2CP- and streaminglib options as
// specified. See the I2P documentation for a full list of options. // specified. See the I2P documentation for a full list of options.
func (sam *PrimarySession) NewStreamSubSession(id string) (*StreamSession, error) { func (sam *PrimarySession) NewStreamSubSession(id string) (*StreamSession, error) {
log.WithField("id", id).Debug("NewStreamSubSession called")
conn, err := sam.newGenericSubSession("STREAM", id, []string{}) conn, err := sam.newGenericSubSession("STREAM", id, []string{})
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create new generic sub-session")
return nil, err 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 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 // Creates a new StreamSession with the I2CP- and streaminglib options as
// specified. See the I2P documentation for a full list of options. // specified. See the I2P documentation for a full list of options.
func (sam *PrimarySession) NewUniqueStreamSubSession(id string) (*StreamSession, error) { func (sam *PrimarySession) NewUniqueStreamSubSession(id string) (*StreamSession, error) {
log.WithField("id", id).Debug("NewUniqueStreamSubSession called")
conn, err := sam.newGenericSubSession("STREAM", id, []string{}) conn, err := sam.newGenericSubSession("STREAM", id, []string{})
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create new generic sub-session")
return nil, err 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 // Creates a new StreamSession with the I2CP- and streaminglib options as
// specified. See the I2P documentation for a full list of options. // specified. See the I2P documentation for a full list of options.
func (sam *PrimarySession) NewStreamSubSessionWithPorts(id, from, to string) (*StreamSession, error) { 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{}) conn, err := sam.newGenericSubSessionWithSignatureAndPorts("STREAM", id, from, to, []string{})
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create new generic sub-session with signature and ports")
return nil, err 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 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, // 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. // and if you set it to zero, it will use SAMs standard UDP port.
func (s *PrimarySession) NewDatagramSubSession(id string, udpPort int) (*DatagramSession, error) { func (s *PrimarySession) NewDatagramSubSession(id string, udpPort int, datagramOptions ...DatagramOptions) (*DatagramSession, error) {
if udpPort > 65335 || udpPort < 0 { log.WithFields(logrus.Fields{"id": id, "udpPort": udpPort}).Debug("NewDatagramSubSession called")
return nil, errors.New("udpPort needs to be in the intervall 0-65335") 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 { udpConn, err := common.NewUDPSession(udpSessionConfig)
udpPort = 7655
}
lhost, _, err := SplitHostPort(s.conn.LocalAddr().String())
if err != nil { if err != nil {
s.Close() log.WithError(err).Error("Failed to create UDP session")
return nil, err return nil, err
} }
lUDPAddr, err := net.ResolveUDPAddr("udp4", lhost+":0") _, lport, err := net.SplitHostPort(udpConn.Conn.LocalAddr().String())
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())
if err != nil { if err != nil {
log.WithError(err).Error("Failed to get local port")
s.Close() s.Close()
return nil, err return nil, err
} }
conn, err := s.newGenericSubSession("DATAGRAM", id, []string{"PORT=" + lport}) conn, err := s.newGenericSubSession("DATAGRAM", id, []string{"PORT=" + lport})
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create new generic sub-session")
return nil, err 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, // 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. // and if you set it to zero, it will use SAMs standard UDP port.
func (s *PrimarySession) NewRawSubSession(id string, udpPort int) (*RawSession, error) { 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 { 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") return nil, errors.New("udpPort needs to be in the intervall 0-65335")
} }
if udpPort == 0 { if udpPort == 0 {
udpPort = 7655 udpPort = 7655
log.Debug("Using default UDP port 7655")
} }
lhost, _, err := SplitHostPort(s.conn.LocalAddr().String()) lhost, _, err := SplitHostPort(s.conn.LocalAddr().String())
if err != nil { if err != nil {
log.WithError(err).Error("Failed to split local host port")
s.Close() s.Close()
return nil, err return nil, err
} }
lUDPAddr, err := net.ResolveUDPAddr("udp4", lhost+":0") lUDPAddr, err := net.ResolveUDPAddr("udp4", lhost+":0")
if err != nil { if err != nil {
log.WithError(err).Error("Failed to resolve local UDP address")
return nil, err return nil, err
} }
udpconn, err := net.ListenUDP("udp4", lUDPAddr) udpconn, err := net.ListenUDP("udp4", lUDPAddr)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to listen on UDP")
return nil, err return nil, err
} }
rhost, _, err := SplitHostPort(s.conn.RemoteAddr().String()) rhost, _, err := SplitHostPort(s.conn.RemoteAddr().String())
if err != nil { if err != nil {
log.WithError(err).Error("Failed to split remote host port")
s.Close() s.Close()
return nil, err return nil, err
} }
rUDPAddr, err := net.ResolveUDPAddr("udp4", rhost+":"+strconv.Itoa(udpPort)) rUDPAddr, err := net.ResolveUDPAddr("udp4", rhost+":"+strconv.Itoa(udpPort))
if err != nil { if err != nil {
log.WithError(err).Error("Failed to resolve remote UDP address")
return nil, err return nil, err
} }
_, lport, err := net.SplitHostPort(udpconn.LocalAddr().String()) _, lport, err := net.SplitHostPort(udpconn.LocalAddr().String())
if err != nil { if err != nil {
log.WithError(err).Error("Failed to get local port")
s.Close() s.Close()
return nil, err return nil, err
} }
// conn, err := s.newGenericSubSession("RAW", id, s.keys, options, []string{"PORT=" + lport}) // conn, err := s.newGenericSubSession("RAW", id, s.keys, options, []string{"PORT=" + lport})
conn, err := s.newGenericSubSession("RAW", id, []string{"PORT=" + lport}) conn, err := s.newGenericSubSession("RAW", id, []string{"PORT=" + lport})
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create new generic sub-session")
return nil, err 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 return &RawSession{s.Config.I2PConfig.Sam(), id, conn, udpconn, s.keys, rUDPAddr}, nil
} }

View File

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

View File

@@ -3,7 +3,6 @@ package sam3
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log"
"net/http" "net/http"
"strings" "strings"
"testing" "testing"
@@ -42,14 +41,14 @@ func Test_PrimaryStreamingDial(t *testing.T) {
} }
defer ss.Close() defer ss.Close()
fmt.Println("\tNotice: This may fail if your I2P node is not well integrated in the I2P network.") 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") fmt.Println("\tLooking up idk.i2p")
forumAddr, err := earlysam.Lookup("i2p-projekt.i2p") forumAddr, err := ss.Lookup("idk.i2p")
if err != nil { if err != nil {
fmt.Println(err.Error()) fmt.Println(err.Error())
t.Fail() t.Fail()
return 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) conn, err := ss.DialI2P(forumAddr)
if err != nil { if err != nil {
fmt.Println(err.Error()) fmt.Println(err.Error())
@@ -66,9 +65,9 @@ func Test_PrimaryStreamingDial(t *testing.T) {
buf := make([]byte, 4096) buf := make([]byte, 4096)
n, err := conn.Read(buf) n, err := conn.Read(buf)
if !strings.Contains(strings.ToLower(string(buf[:n])), "http") && !strings.Contains(strings.ToLower(string(buf[:n])), "html") { 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 { } 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 return
} }
defer sam.Close() defer sam.Close()
conn, err := sam.Dial("tcp", "idk.i2p") //someone.Base32()) conn, err := sam.Dial("tcp", "idk.i2p") // someone.Base32())
if err != nil { if err != nil {
fmt.Println(err.Error()) fmt.Println(err.Error())
return return
@@ -212,8 +211,8 @@ func ExamplePrimaryStreamSession() {
log.Println("Read HTTP/HTML from idk.i2p") log.Println("Read HTTP/HTML from idk.i2p")
} }
// Output: // Output:
//Sending HTTP GET / // Sending HTTP GET /
//Read HTTP/HTML from idk.i2p // Read HTTP/HTML from idk.i2p
} }
func ExamplePrimaryStreamListener() { func ExamplePrimaryStreamListener() {
@@ -254,7 +253,7 @@ func ExamplePrimaryStreamListener() {
return return
} }
defer l.Close() 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 { if err := http.Serve(l, &exitHandler{}); err != nil {
fmt.Println(err.Error()) fmt.Println(err.Error())
} }
@@ -282,7 +281,7 @@ func ExamplePrimaryStreamListener() {
Dial: sc.Dial, 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()) resp, err := client.Get("http://" + ss.Addr().Base32())
if err != nil { if err != nil {
fmt.Println(err.Error()) fmt.Println(err.Error())
@@ -300,8 +299,7 @@ func ExamplePrimaryStreamListener() {
// Got response: Hello world! // Got response: Hello world!
} }
type exitHandler struct { type exitHandler struct{}
}
func (e *exitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (e *exitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello world!")) w.Write([]byte("Hello world!"))

69
raw.go
View File

@@ -1,13 +1,14 @@
package sam3 package sam3
import ( import (
"bytes"
"errors" "errors"
"net" "net"
"strconv" "strconv"
"time" "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 // The RawSession provides no authentication of senders, and there is no sender
@@ -28,77 +29,121 @@ type RawSession struct {
// Creates a new raw session. udpPort is the UDP port SAM is listening on, // 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. // 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) { 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 { 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 { if udpPort == 0 {
udpPort = 7655 udpPort = 7655
log.Debug("Using default UDP port 7655")
} }
lhost, _, err := SplitHostPort(s.conn.LocalAddr().String()) lhost, _, err := SplitHostPort(s.conn.LocalAddr().String())
if err != nil { if err != nil {
log.Debug("Using default UDP port 7655")
s.Close() s.Close()
return nil, err return nil, err
} }
lUDPAddr, err := net.ResolveUDPAddr("udp4", lhost+":0") lUDPAddr, err := net.ResolveUDPAddr("udp4", lhost+":0")
if err != nil { if err != nil {
log.WithError(err).Error("Failed to resolve local UDP address")
return nil, err return nil, err
} }
udpconn, err := net.ListenUDP("udp4", lUDPAddr) udpconn, err := net.ListenUDP("udp4", lUDPAddr)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to listen on UDP")
return nil, err return nil, err
} }
rhost, _, err := SplitHostPort(s.conn.RemoteAddr().String()) rhost, _, err := SplitHostPort(s.conn.RemoteAddr().String())
if err != nil { if err != nil {
log.WithError(err).Error("Failed to split remote host port")
s.Close() s.Close()
return nil, err return nil, err
} }
rUDPAddr, err := net.ResolveUDPAddr("udp4", rhost+":"+strconv.Itoa(udpPort)) rUDPAddr, err := net.ResolveUDPAddr("udp4", rhost+":"+strconv.Itoa(udpPort))
if err != nil { if err != nil {
log.WithError(err).Error("Failed to resolve remote UDP address")
return nil, err return nil, err
} }
_, lport, err := net.SplitHostPort(udpconn.LocalAddr().String()) _, lport, err := net.SplitHostPort(udpconn.LocalAddr().String())
conn, err := s.newGenericSession("RAW", id, keys, options, []string{"PORT=" + lport})
if err != nil { if err != nil {
log.WithError(err).Error("Failed to get local port")
return nil, err return nil, err
} }
return &RawSession{s.Config.I2PConfig.Sam(), id, conn, udpconn, keys, rUDPAddr}, nil conn, err := s.newGenericSession("RAW", id, keys, options, []string{"PORT=" + lport})
if err != nil {
log.WithError(err).Error("Failed to create new generic session")
return nil, err
}
log.WithFields(logrus.Fields{
"id": id,
"localPort": lport,
"remoteUDPAddr": rUDPAddr,
}).Debug("Created new RawSession")
return &RawSession{s.SAMEmit.I2PConfig.Sam(), id, conn, udpconn, keys, rUDPAddr}, nil
} }
// 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 // 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!). // this layer - you need to do it (in a secure way!).
func (s *RawSession) Read(b []byte) (n int, err error) { func (s *RawSession) Read(b []byte) (n int, err error) {
log.Debug("Attempting to read raw datagram")
for { for {
// very basic protection: only accept incomming UDP messages from the IP of the SAM bridge
var saddr *net.UDPAddr var saddr *net.UDPAddr
n, saddr, err = s.udpconn.ReadFromUDP(b) n, saddr, err = s.udpconn.ReadFromUDP(b)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to read from UDP")
return 0, err 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, // 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. // 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) { 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") header := []byte("3.0 " + s.id + " " + addr.String() + "\n")
msg := append(header, b...) msg := append(header, b...)
n, err = s.udpconn.WriteToUDP(msg, s.rUDPAddr) 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 return n, err
} }
// Closes the RawSession. // Closes the RawSession.
func (s *RawSession) Close() error { func (s *RawSession) Close() error {
log.Debug("Closing RawSession")
err := s.conn.Close() err := s.conn.Close()
err2 := s.udpconn.Close()
if err != nil { if err != nil {
log.WithError(err).Error("Failed to close connection")
return err return err
} }
err2 := s.udpconn.Close()
if err2 != nil {
log.WithError(err2).Error("Failed to close UDP connection")
}
log.Debug("RawSession closed")
return err2 return err2
} }

View File

@@ -2,71 +2,123 @@ package sam3
import ( import (
"bufio" "bufio"
"bytes" "context"
"errors" "fmt"
"strings" "strings"
"time"
"github.com/eyedeekay/i2pkeys" "github.com/go-i2p/i2pkeys"
) )
// SAMResolver handles name resolution for I2P addresses
type SAMResolver struct { 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) { func NewSAMResolver(parent *SAM) (*SAMResolver, error) {
var s SAMResolver if parent == nil {
s.SAM = parent return nil, fmt.Errorf("parent SAM instance required")
return &s, nil }
return &SAMResolver{sam: parent}, nil
} }
// NewFullSAMResolver creates a new resolver with its own SAM connection
func NewFullSAMResolver(address string) (*SAMResolver, error) { func NewFullSAMResolver(address string) (*SAMResolver, error) {
var s SAMResolver sam, err := NewSAM(address)
var err error if err != nil {
s.SAM, err = NewSAM(address) return nil, fmt.Errorf("creating SAM connection: %w", err)
if err != nil { }
return nil, err return &SAMResolver{sam: sam}, nil
}
return &s, nil
} }
// Performs a lookup, probably this order: 1) routers known addresses, cached func (r *SAMResolver) Resolve(name string) (i2pkeys.I2PAddr, error) {
// addresses, 3) by asking peers in the I2P network. return r.ResolveWithContext(context.Background(), name)
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)
} }
// 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)
}

201
sam3.go
View File

@@ -4,26 +4,35 @@ package sam3
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"errors" "fmt"
"io" "io"
"math/rand" "math/rand"
"net" "net"
"os" "os"
"strings" "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. // Used for controlling I2Ps SAMv3.
// This implements the "Control Socket" for all connections.
type SAM struct { type SAM struct {
address string address string
conn net.Conn conn net.Conn
resolver *SAMResolver keys *i2pkeys.I2PKeys
Config SAMEmit sigType int
keys *i2pkeys.I2PKeys formatter *common.SAMFormatter
sigType int version common.Version
SAMEmit
*SAMResolver
} }
const ( const (
@@ -35,7 +44,7 @@ const (
) )
const ( const (
Sig_NONE = "" Sig_NONE = "SIGNATURE_TYPE=EdDSA_SHA512_Ed25519"
Sig_DSA_SHA1 = "SIGNATURE_TYPE=DSA_SHA1" Sig_DSA_SHA1 = "SIGNATURE_TYPE=DSA_SHA1"
Sig_ECDSA_SHA256_P256 = "SIGNATURE_TYPE=ECDSA_SHA256_P256" Sig_ECDSA_SHA256_P256 = "SIGNATURE_TYPE=ECDSA_SHA256_P256"
Sig_ECDSA_SHA384_P384 = "SIGNATURE_TYPE=ECDSA_SHA384_P384" Sig_ECDSA_SHA384_P384 = "SIGNATURE_TYPE=ECDSA_SHA384_P384"
@@ -51,84 +60,108 @@ func RandString() string {
for i := range b { for i := range b {
b[i] = letters[rand.Intn(len(letters))] b[i] = letters[rand.Intn(len(letters))]
} }
log.WithField("randomString", string(b)).Debug("Generated random string")
return string(b) return string(b)
} }
// Creates a new controller for the I2P routers SAM bridge. // Creates a new controller for the I2P routers SAM bridge.
func NewSAM(address string) (*SAM, error) { 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 // TODO: clean this up
conn, err := net.Dial("tcp", address) conn, err := net.Dial("tcp", address)
if err != nil { 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() conn.Close()
return nil, err return nil, fmt.Errorf("error writing to address '%s': %w", address, err)
} }
buf := make([]byte, 256) /*buf := make([]byte, 256)
n, err := conn.Read(buf) n, err := conn.Read(buf)*/
reader := bufio.NewReader(conn)
response, err := reader.ReadString('\n')
if err != nil { if err != nil {
conn.Close() 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") { 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.conn = conn
//s.Config.I2PConfig.DestinationKeys = nil // s.Config.I2PConfig.DestinationKeys = nil
s.resolver, err = NewSAMResolver(&s) s.SAMResolver, err = NewSAMResolver(&s)
if err != nil { 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 &s, nil
//return &SAM{address, conn, nil, nil}, nil
} else if string(buf[:n]) == "HELLO REPLY RESULT=NOVERSION\n" { } else if string(buf[:n]) == "HELLO REPLY RESULT=NOVERSION\n" {
log.Error("SAM bridge does not support SAMv3")
conn.Close() 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 { } else {
log.WithField("response", string(buf[:n])).Error("Unexpected SAM response")
conn.Close() conn.Close()
return nil, errors.New(string(buf[:n])) return nil, fmt.Errorf(string(buf[:n]))
} }
} }
func (sam *SAM) Keys() (k *i2pkeys.I2PKeys) { func (sam *SAM) Keys() (k *i2pkeys.I2PKeys) {
//TODO: copy them? // TODO: copy them?
k = &sam.Config.I2PConfig.DestinationKeys log.Debug("Retrieving SAM keys")
k = &sam.SAMEmit.I2PConfig.DestinationKeys
return return
} }
// read public/private keys from an io.Reader // read public/private keys from an io.Reader
func (sam *SAM) ReadKeys(r io.Reader) (err error) { func (sam *SAM) ReadKeys(r io.Reader) (err error) {
log.Debug("Reading keys from io.Reader")
var keys i2pkeys.I2PKeys var keys i2pkeys.I2PKeys
keys, err = i2pkeys.LoadKeysIncompat(r) keys, err = i2pkeys.LoadKeysIncompat(r)
if err == nil { 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 return
} }
// if keyfile fname does not exist // if keyfile fname does not exist
func (sam *SAM) EnsureKeyfile(fname string) (keys i2pkeys.I2PKeys, err error) { func (sam *SAM) EnsureKeyfile(fname string) (keys i2pkeys.I2PKeys, err error) {
log.WithError(err).Error("Failed to load keys")
if fname == "" { if fname == "" {
// transient // transient
keys, err = sam.NewKeys() keys, err = sam.NewKeys()
if err == nil { 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 { } else {
// persistant // persistent
_, err = os.Stat(fname) _, err = os.Stat(fname)
if os.IsNotExist(err) { if os.IsNotExist(err) {
// make the keys // make the keys
keys, err = sam.NewKeys() keys, err = sam.NewKeys()
if err == nil { if err == nil {
sam.Config.I2PConfig.DestinationKeys = keys sam.SAMEmit.I2PConfig.DestinationKeys = keys
// save keys // save keys
var f io.WriteCloser 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 { if err == nil {
err = i2pkeys.StoreKeysIncompat(keys, f) err = i2pkeys.StoreKeysIncompat(keys, f)
f.Close() f.Close()
log.Debug("Generated and saved new keys")
} }
} }
} else if err == nil { } else if err == nil {
@@ -138,29 +171,42 @@ func (sam *SAM) EnsureKeyfile(fname string) (keys i2pkeys.I2PKeys, err error) {
if err == nil { if err == nil {
keys, err = i2pkeys.LoadKeysIncompat(f) keys, err = i2pkeys.LoadKeysIncompat(f)
if err == nil { 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 return
} }
// Creates the I2P-equivalent of an IP address, that is unique and only the one // 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 // who has the private keys can send messages from. The public keys are the I2P
// desination (the address) that anyone can send messages to. // 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) { func (sam *SAM) NewKeys(sigType ...string) (i2pkeys.I2PKeys, error) {
sigtmp := "" log.WithField("sigType", sigType).Debug("Generating new keys")
if len(sigType) > 0 { if sigType == nil {
sigtmp = sigType[0] sigType = []string{DEFAULT_SIG_TYPE}
} }
if _, err := sam.conn.Write([]byte("DEST GENERATE " + sigtmp + "\n")); err != nil { scmsg := []byte(fmt.Sprintf("DEST GENERATE %s\n", sigType[0]))
return i2pkeys.I2PKeys{}, err 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) buf := make([]byte, 8192)
n, err := sam.conn.Read(buf) n, err := sam.conn.Read(buf)
if err != nil { 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 := bufio.NewScanner(bytes.NewReader(buf[:n]))
s.Split(bufio.ScanWords) s.Split(bufio.ScanWords)
@@ -177,16 +223,19 @@ func (sam *SAM) NewKeys(sigType ...string) (i2pkeys.I2PKeys, error) {
} else if strings.HasPrefix(text, "PRIV=") { } else if strings.HasPrefix(text, "PRIV=") {
priv = text[5:] priv = text[5:]
} else { } 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 // Performs a lookup, probably this order: 1) routers known addresses, cached
// addresses, 3) by asking peers in the I2P network. // addresses, 3) by asking peers in the I2P network.
func (sam *SAM) Lookup(name string) (i2pkeys.I2PAddr, error) { 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", // 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 // I2CP/streaminglib-options as specified. Extra arguments can be specified by
// setting extra to something else than []string{}. // setting extra to something else than []string{}.
// This sam3 instance is now a session // 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) 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) return sam.newGenericSessionWithSignatureAndPorts(style, id, "0", "0", keys, sigType, options, extras)
} }
@@ -207,67 +258,85 @@ func (sam *SAM) newGenericSessionWithSignature(style, id string, keys i2pkeys.I2
// I2CP/streaminglib-options as specified. Extra arguments can be specified by // I2CP/streaminglib-options as specified. Extra arguments can be specified by
// setting extra to something else than []string{}. // setting extra to something else than []string{}.
// This sam3 instance is now a session // 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 := "" optStr := GenerateOptionString(options)
for _, opt := range options {
optStr += opt + " "
}
conn := sam.conn conn := sam.conn
fp := "" 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, " ")))
tp := "" if style == "PRIMARY" || style == "MASTER" {
if from != "0" { scmsg = []byte(fmt.Sprintf("SESSION CREATE STYLE=%s ID=%s DESTINATION=%s %s %s\n", style, id, keys.String(), optStr, strings.Join(extras, " ")))
fp = " FROM_PORT=" + from
} }
if to != "0" {
tp = " TO_PORT=" + to log.WithField("message", string(scmsg)).Debug("Sending SESSION CREATE message", string(scmsg))
}
scmsg := []byte("SESSION CREATE STYLE=" + style + fp + tp + " ID=" + id + " DESTINATION=" + keys.String() + " " + optStr + strings.Join(extras, " ") + "\n")
for m, i := 0, 0; m != len(scmsg); i++ { for m, i := 0, 0; m != len(scmsg); i++ {
if i == 15 { if i == 15 {
log.Error("Failed to write SESSION CREATE message after 15 attempts")
conn.Close() conn.Close()
return nil, errors.New("writing to SAM failed") return nil, fmt.Errorf("writing to SAM failed")
} }
n, err := conn.Write(scmsg[m:]) n, err := conn.Write(scmsg[m:])
if err != nil { if err != nil {
log.WithError(err).Error("Failed to write to SAM connection")
conn.Close() conn.Close()
return nil, err return nil, fmt.Errorf("writing to connection failed: %w", err)
} }
m += n m += n
} }
buf := make([]byte, 4096) buf := make([]byte, 4096)
n, err := conn.Read(buf) n, err := conn.Read(buf)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to read SAM response")
conn.Close() conn.Close()
return nil, err return nil, fmt.Errorf("reading from connection failed: %w", err)
} }
text := string(buf[:n]) text := string(buf[:n])
log.WithField("response", text).Debug("Received SAM response")
if strings.HasPrefix(text, session_OK) { if strings.HasPrefix(text, session_OK) {
if keys.String() != text[len(session_OK):len(text)-1] { if keys.String() != text[len(session_OK):len(text)-1] {
log.Error("SAM created a tunnel with different keys than requested")
conn.Close() 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 return conn, nil //&StreamSession{id, conn, keys, nil, sync.RWMutex{}, nil}, nil
} else if text == session_DUPLICATE_ID { } else if text == session_DUPLICATE_ID {
log.Error("Duplicate tunnel name")
conn.Close() conn.Close()
return nil, errors.New("Duplicate tunnel name") return nil, fmt.Errorf("Duplicate tunnel name")
} else if text == session_DUPLICATE_DEST { } else if text == session_DUPLICATE_DEST {
log.Error("Duplicate destination")
conn.Close() conn.Close()
return nil, errors.New("Duplicate destination") return nil, fmt.Errorf("Duplicate destination")
} else if text == session_INVALID_KEY { } else if text == session_INVALID_KEY {
log.Error("Invalid key for SAM session")
conn.Close() 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) { } else if strings.HasPrefix(text, session_I2P_ERROR) {
log.WithField("error", text[len(session_I2P_ERROR):]).Error("I2P error")
conn.Close() 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 { } else {
log.WithField("reply", text).Error("Unable to parse SAMv3 reply")
conn.Close() 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 { func (sam *SAM) Close() error {
log.Debug("Closing SAM session")
return sam.conn.Close() 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
}

10
showhider.css Normal file
View File

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

110
stream.go
View File

@@ -5,12 +5,15 @@ import (
"bytes" "bytes"
"context" "context"
"errors" "errors"
"fmt"
"io" "io"
"net" "net"
"strings" "strings"
"time" "time"
"github.com/eyedeekay/i2pkeys" "github.com/sirupsen/logrus"
"github.com/go-i2p/i2pkeys"
) )
// Represents a streaming session. // Represents a streaming session.
@@ -26,102 +29,132 @@ type StreamSession struct {
to string 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 { func (s *StreamSession) SetDeadline(t time.Time) error {
log.WithField("deadline", t).Debug("Setting deadline for StreamSession")
return s.conn.SetDeadline(t) return s.conn.SetDeadline(t)
} }
func (s *StreamSession) SetReadDeadline(t time.Time) error { func (s *StreamSession) SetReadDeadline(t time.Time) error {
log.WithField("readDeadline", t).Debug("Setting read deadline for StreamSession")
return s.conn.SetReadDeadline(t) return s.conn.SetReadDeadline(t)
} }
func (s *StreamSession) SetWriteDeadline(t time.Time) error { func (s *StreamSession) SetWriteDeadline(t time.Time) error {
log.WithField("writeDeadline", t).Debug("Setting write deadline for StreamSession")
return s.conn.SetWriteDeadline(t) return s.conn.SetWriteDeadline(t)
} }
func (ss *StreamSession) From() string { func (s *StreamSession) From() string {
return ss.from return s.from
} }
func (ss *StreamSession) To() string { func (s *StreamSession) To() string {
return ss.to return s.to
} }
func (ss *StreamSession) SignatureType() string { func (s *StreamSession) SignatureType() string {
return ss.sigType return s.sigType
} }
// Returns the local tunnel name of the I2P tunnel used for the stream session // Returns the local tunnel name of the I2P tunnel used for the stream session
func (ss *StreamSession) ID() string { func (s *StreamSession) ID() string {
return ss.id return s.id
} }
func (ss *StreamSession) Close() error { func (s *StreamSession) Close() error {
return ss.conn.Close() log.WithField("id", s.id).Debug("Closing StreamSession")
return s.conn.Close()
} }
// Returns the I2P destination (the address) of the stream session // Returns the I2P destination (the address) of the stream session
func (ss *StreamSession) Addr() i2pkeys.I2PAddr { func (s *StreamSession) Addr() i2pkeys.I2PAddr {
return ss.keys.Addr() return s.keys.Addr()
} }
func (ss *StreamSession) LocalAddr() net.Addr { func (s *StreamSession) LocalAddr() net.Addr {
return ss.keys.Addr() return s.keys.Addr()
} }
// Returns the keys associated with the stream session // Returns the keys associated with the stream session
func (ss *StreamSession) Keys() i2pkeys.I2PKeys { func (s *StreamSession) Keys() i2pkeys.I2PKeys {
return ss.keys return s.keys
} }
// Creates a new StreamSession with the I2CP- and streaminglib options as // Creates a new StreamSession with the I2CP- and streaminglib options as
// specified. See the I2P documentation for a full list of options. // specified. See the I2P documentation for a full list of options.
func (sam *SAM) NewStreamSession(id string, keys i2pkeys.I2PKeys, options []string) (*StreamSession, error) { 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{}) conn, err := sam.newGenericSession("STREAM", id, keys, options, []string{})
if err != nil { if err != nil {
return nil, err 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 // Creates a new StreamSession with the I2CP- and streaminglib options as
// specified. See the I2P documentation for a full list of options. // 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) { 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{}) conn, err := sam.newGenericSessionWithSignature("STREAM", id, keys, sigType, options, []string{})
if err != nil { if err != nil {
return nil, err 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 // Creates a new StreamSession with the I2CP- and streaminglib options as
// specified. See the I2P documentation for a full list of options. // 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) { 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{}) conn, err := sam.newGenericSessionWithSignatureAndPorts("STREAM", id, from, to, keys, sigType, options, []string{})
if err != nil { if err != nil {
return nil, err 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, convienence function // lookup name, convenience function
func (s *StreamSession) Lookup(name string) (i2pkeys.I2PAddr, error) { func (s *StreamSession) Lookup(name string) (i2pkeys.I2PAddr, error) {
log.WithField("name", name).Debug("Looking up address")
sam, err := NewSAM(s.samAddr) sam, err := NewSAM(s.samAddr)
if err == nil { if err == nil {
addr, err := sam.Lookup(name) addr, err := sam.Lookup(name)
defer sam.Close() defer sam.Close()
if err != nil {
log.WithError(err).Error("Lookup failed")
} else {
log.WithField("addr", addr).Debug("Lookup successful")
}
return addr, err return addr, err
} }
log.WithError(err).Error("Failed to create SAM instance for lookup")
return i2pkeys.I2PAddr(""), err return i2pkeys.I2PAddr(""), err
} }
// context-aware dialer, eventually... // context-aware dialer, eventually...
func (s *StreamSession) DialContext(ctx context.Context, n, addr string) (net.Conn, error) { 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) return s.DialContextI2P(ctx, n, addr)
} }
// context-aware dialer, eventually... // context-aware dialer, eventually...
func (s *StreamSession) DialContextI2P(ctx context.Context, n, addr string) (*SAMConn, error) { 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 { if ctx == nil {
log.Panic("nil context")
panic("nil context") panic("nil context")
} }
deadline := s.deadline(ctx, time.Now()) 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) i2paddr, err := i2pkeys.NewI2PAddrFromString(addr)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create I2P address from string")
return nil, err return nil, err
} }
return s.DialI2P(i2paddr) return s.DialI2P(i2paddr)
@@ -161,6 +195,7 @@ func minNonzeroTime(a, b time.Time) time.Time {
// - now+Timeout // - now+Timeout
// - d.Deadline // - d.Deadline
// - the context's deadline // - the context's deadline
//
// Or zero, if none of Timeout, Deadline, or context's deadline is set. // Or zero, if none of Timeout, Deadline, or context's deadline is set.
func (s *StreamSession) deadline(ctx context.Context, now time.Time) (earliest time.Time) { func (s *StreamSession) deadline(ctx context.Context, now time.Time) (earliest time.Time) {
if s.Timeout != 0 { // including negative, for historical reasons if s.Timeout != 0 { // including negative, for historical reasons
@@ -174,45 +209,57 @@ func (s *StreamSession) deadline(ctx context.Context, now time.Time) (earliest t
// implement net.Dialer // implement net.Dialer
func (s *StreamSession) Dial(n, addr string) (c net.Conn, err error) { func (s *StreamSession) Dial(n, addr string) (c net.Conn, err error) {
log.WithFields(logrus.Fields{"network": n, "addr": addr}).Debug("Dial called")
var i2paddr i2pkeys.I2PAddr var i2paddr i2pkeys.I2PAddr
var host string var host string
host, _, err = SplitHostPort(addr) host, _, err = SplitHostPort(addr)
//log.Println("Dialing:", host) // log.Println("Dialing:", host)
if err = IgnorePortError(err); err == nil { if err = IgnorePortError(err); err == nil {
// check for name // check for name
if strings.HasSuffix(host, ".b32.i2p") || strings.HasSuffix(host, ".i2p") { if strings.HasSuffix(host, ".b32.i2p") || strings.HasSuffix(host, ".i2p") {
// name lookup // name lookup
i2paddr, err = s.Lookup(host) i2paddr, err = s.Lookup(host)
//log.Println("Lookup:", i2paddr, err) log.WithFields(logrus.Fields{"host": host, "i2paddr": i2paddr}).Debug("Looked up I2P address")
} else { } else {
// probably a destination // probably a destination
i2paddr, err = i2pkeys.NewI2PAddrFromBytes([]byte(host)) i2paddr, err = i2pkeys.NewI2PAddrFromBytes([]byte(host))
//i2paddr = i2pkeys.I2PAddr(host) // i2paddr = i2pkeys.I2PAddr(host)
//log.Println("Destination:", i2paddr, err) // log.Println("Destination:", i2paddr, err)
log.WithFields(logrus.Fields{"host": host, "i2paddr": i2paddr}).Debug("Created I2P address from bytes")
} }
if err == nil { if err == nil {
return s.DialI2P(i2paddr) return s.DialI2P(i2paddr)
} }
} }
log.WithError(err).Error("Dial failed")
return return
} }
// Dials to an I2P destination and returns a SAMConn, which implements a net.Conn. // Dials to an I2P destination and returns a SAMConn, which implements a net.Conn.
func (s *StreamSession) DialI2P(addr i2pkeys.I2PAddr) (*SAMConn, error) { func (s *StreamSession) DialI2P(addr i2pkeys.I2PAddr) (*SAMConn, error) {
log.WithField("addr", addr).Debug("DialI2P called")
sam, err := NewSAM(s.samAddr) sam, err := NewSAM(s.samAddr)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create new SAM instance")
return nil, err return nil, err
} }
conn := sam.conn 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 { if err != nil {
log.WithError(err).Error("Failed to write STREAM CONNECT command")
conn.Close() conn.Close()
return nil, err return nil, err
} }
buf := make([]byte, 4096) buf := make([]byte, 4096)
n, err := conn.Read(buf) n, err := conn.Read(buf)
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
log.WithError(err).Error("Failed to write STREAM CONNECT command")
conn.Close() conn.Close()
return nil, err return nil, err
} }
@@ -225,32 +272,41 @@ func (s *StreamSession) DialI2P(addr i2pkeys.I2PAddr) (*SAMConn, error) {
case "STATUS": case "STATUS":
continue continue
case "RESULT=OK": case "RESULT=OK":
log.Debug("Successfully connected to I2P destination")
return &SAMConn{s.keys.Addr(), addr, conn}, nil return &SAMConn{s.keys.Addr(), addr, conn}, nil
case "RESULT=CANT_REACH_PEER": case "RESULT=CANT_REACH_PEER":
log.Error("Can't reach peer")
conn.Close() conn.Close()
return nil, errors.New("Can not reach peer") return nil, errors.New("Can not reach peer")
case "RESULT=I2P_ERROR": case "RESULT=I2P_ERROR":
log.Error("I2P internal error")
conn.Close() conn.Close()
return nil, errors.New("I2P internal error") return nil, errors.New("I2P internal error")
case "RESULT=INVALID_KEY": case "RESULT=INVALID_KEY":
log.Error("Invalid key - Stream Session")
conn.Close() conn.Close()
return nil, errors.New("Invalid key - Stream Session") return nil, errors.New("Invalid key - Stream Session")
case "RESULT=INVALID_ID": case "RESULT=INVALID_ID":
log.Error("Invalid tunnel ID")
conn.Close() conn.Close()
return nil, errors.New("Invalid tunnel ID") return nil, errors.New("Invalid tunnel ID")
case "RESULT=TIMEOUT": case "RESULT=TIMEOUT":
log.Error("Connection timeout")
conn.Close() conn.Close()
return nil, errors.New("Timeout") return nil, errors.New("Timeout")
default: default:
log.WithField("error", scanner.Text()).Error("Unknown error")
conn.Close() 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()") panic("sam3 go library error in StreamSession.DialI2P()")
} }
// create a new stream listener to accept inbound connections // create a new stream listener to accept inbound connections
func (s *StreamSession) Listen() (*StreamListener, error) { func (s *StreamSession) Listen() (*StreamListener, error) {
log.WithFields(logrus.Fields{"id": s.id, "laddr": s.keys.Addr()}).Debug("Creating new StreamListener")
return &StreamListener{ return &StreamListener{
session: s, session: s,
id: s.id, id: s.id,

View File

@@ -2,14 +2,17 @@ package sam3
import ( import (
"bufio" "bufio"
"errors" "context"
"fmt"
"io" "io"
"log"
"net" "net"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/eyedeekay/i2pkeys" "github.com/sirupsen/logrus"
"github.com/go-i2p/i2pkeys"
) )
type StreamListener struct { type StreamListener struct {
@@ -42,74 +45,134 @@ func (l *StreamListener) Close() error {
// implements net.Listener // implements net.Listener
func (l *StreamListener) Accept() (net.Conn, error) { 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 { func ExtractPairString(input, value string) string {
log.WithFields(logrus.Fields{"input": input, "value": value}).Debug("ExtractPairString called")
parts := strings.Split(input, " ") parts := strings.Split(input, " ")
for _, part := range parts { for _, part := range parts {
if strings.HasPrefix(part, value) { if strings.HasPrefix(part, value) {
kv := strings.SplitN(input, "=", 2) kv := strings.SplitN(input, "=", 2)
if len(kv) == 2 { if len(kv) == 2 {
log.WithFields(logrus.Fields{"key": kv[0], "value": kv[1]}).Debug("Pair extracted")
return kv[1] return kv[1]
} }
} }
} }
log.WithFields(logrus.Fields{"input": input, "value": value}).Debug("No pair found")
return "" return ""
} }
func ExtractPairInt(input, value string) int { func ExtractPairInt(input, value string) int {
rv, err := strconv.Atoi(ExtractPairString(input, value)) rv, err := strconv.Atoi(ExtractPairString(input, value))
if err != nil { if err != nil {
log.WithFields(logrus.Fields{"input": input, "value": value}).Debug("No pair found")
return 0 return 0
} }
log.WithField("result", rv).Debug("Pair extracted and converted to int")
return rv return rv
} }
func ExtractDest(input string) string { 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] return strings.Split(input, " ")[0]
} }
// accept a new inbound connection // accept a new inbound connection
func (l *StreamListener) AcceptI2P() (*SAMConn, error) { func (l *StreamListener) AcceptI2P() (*SAMConn, error) {
s, err := NewSAM(l.session.samAddr) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
if err == nil { defer cancel()
// we connected to sam
// send accept() command done := make(chan struct{})
_, err = io.WriteString(s.conn, "STREAM ACCEPT ID="+l.id+" SILENT=false\n") var conn *SAMConn
// read reply var err error
rd := bufio.NewReader(s.conn)
// read first line go func() {
line, err := rd.ReadString(10) defer close(done)
log.Println(line) log.Debug("StreamListener.AcceptI2P() called")
s, err := NewSAM(l.session.samAddr)
if err == nil { if err == nil {
log.Debug("Connected to SAM bridge")
// we connected to sam
// send accept() command
acceptFmt := fmt.Sprintf("STREAM ACCEPT ID=%s SILENT=false", l.id)
_, err = io.WriteString(s.conn, acceptFmt)
if err != nil {
log.WithError(err).Error("Failed to send STREAM ACCEPT command")
s.Close()
}
// read reply
rd := bufio.NewReader(s.conn)
// read first line
line, err := rd.ReadString(10)
if err != nil {
log.WithError(err).Error("Failed to read SAM bridge response")
s.Close()
return
}
log.WithField("response", line).Debug("Received SAM bridge response")
log.Println(line)
if strings.HasPrefix(line, "STREAM STATUS RESULT=OK") { if strings.HasPrefix(line, "STREAM STATUS RESULT=OK") {
// we gud read destination line // we gud read destination line
destline, err := rd.ReadString(10) 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 { if err == nil {
// Validate destination format
dest := ExtractDest(destline) dest := ExtractDest(destline)
if !strings.HasPrefix(dest, "") {
err = fmt.Errorf("invalid destination format")
}
l.session.from = ExtractPairString(destline, "FROM_PORT") l.session.from = ExtractPairString(destline, "FROM_PORT")
l.session.to = ExtractPairString(destline, "TO_PORT") l.session.to = ExtractPairString(destline, "TO_PORT")
// return wrapped connection // return wrapped connection
dest = strings.Trim(dest, "\n") dest = strings.Trim(dest, "\n")
return &SAMConn{ log.WithFields(logrus.Fields{
"dest": dest,
"from": l.session.from,
"to": l.session.to,
}).Debug("Accepted new I2P connection")
conn = &SAMConn{
laddr: l.laddr, laddr: l.laddr,
raddr: i2pkeys.I2PAddr(dest), raddr: i2pkeys.I2PAddr(dest),
conn: s.conn, Conn: s.conn,
}, nil }
err = nil
} else { } else {
log.WithError(err).Error("Failed to read destination line")
s.Close() s.Close()
return nil, err return
} }
} else { } else {
log.WithField("line", line).Error("Invalid SAM response")
s.Close() s.Close()
return nil, errors.New("invalid sam line: " + line) err = fmt.Errorf("invalid sam line: %s", line)
} }
} else { } else {
log.WithError(err).Error("Failed to connect to SAM bridge")
s.Close() s.Close()
return nil, err
} }
}()
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-done:
return conn, err
} }
s.Close()
return nil, err
} }

View File

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

View File

@@ -1,107 +1,95 @@
package sam3 package sam3
import ( import (
"fmt"
"net" "net"
"net/http" "net/http"
"os" "os"
) "strings"
// Examples and suggestions for options when creating sessions. logger "github.com/go-i2p/sam3/log"
var ( sam3opts "github.com/go-i2p/sam3/opts"
// Suitable options if you are shuffling A LOT of traffic. If unused, this "github.com/sirupsen/logrus"
// 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 PrimarySessionString() string { func PrimarySessionString() string {
log.Debug("Determining primary session type")
_, err := http.Get("http://127.0.0.1:7070") _, err := http.Get("http://127.0.0.1:7070")
if err != nil { 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") _, err := http.Get("http://127.0.0.1:7657")
if err != nil { if err != nil {
return "MASTER" 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 // 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 // have a PRIMARY session. Just to be sure, try to make one, check
// for errors, then immediately close it. // for errors, then immediately close it.
testSam, err := NewSAM(SAMDefaultAddr("")) testSam, err := NewSAM(SAMDefaultAddr(""))
if err != nil { if err != nil {
log.WithError(err).Debug("Failed to create SAM instance, assuming MASTER session")
return "MASTER" return "MASTER"
} }
newKeys, err := testSam.NewKeys() newKeys, err := testSam.NewKeys()
if err != nil { if err != nil {
log.WithError(err).Debug("Failed to create new keys, assuming MASTER session")
return "MASTER" return "MASTER"
} }
primarySession, err := testSam.newPrimarySession("PRIMARY", "primaryTestTunnel", newKeys, Options_Small) primarySession, err := testSam.newPrimarySession("PRIMARY", "primaryTestTunnel", newKeys, sam3opts.Options_Small)
if err != nil { if err != nil {
log.WithError(err).Debug("Failed to create primary session, assuming MASTER session")
return "MASTER" return "MASTER"
} }
primarySession.Close() primarySession.Close()
log.Debug("Successfully created and closed a PRIMARY session")
return "PRIMARY" return "PRIMARY"
} }
log.Debug("Connected to 127.0.0.1:7070, assuming MASTER session")
return "MASTER" return "MASTER"
} }
var PrimarySessionSwitch string = PrimarySessionString() var PrimarySessionSwitch string = PrimarySessionString()
func getEnv(key, fallback string) string { func getEnv(key, fallback string) string {
logger.InitializeSAM3Logger()
value, ok := os.LookupEnv(key) value, ok := os.LookupEnv(key)
if !ok { if !ok {
log.WithFields(logrus.Fields{
"key": key,
"fallback": fallback,
}).Debug("Environment variable not set, using fallback")
return fallback return fallback
} }
log.WithFields(logrus.Fields{
"key": key,
"value": value,
}).Debug("Retrieved environment variable")
return value return value
} }
var SAM_HOST = getEnv("sam_host", "127.0.0.1") var (
var SAM_PORT = getEnv("sam_port", "7656") SAM_HOST = getEnv("sam_host", "127.0.0.1")
SAM_PORT = getEnv("sam_port", "7656")
)
func SAMDefaultAddr(fallforward string) string { func SAMDefaultAddr(fallforward string) string {
if fallforward == "" { 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 return fallforward
} }
func GenerateOptionString(opts []string) string {
optStr := strings.Join(opts, " ")
log.WithField("options", optStr).Debug("Generating option string")
if strings.Contains(optStr, "i2cp.leaseSetEncType") {
log.Debug("i2cp.leaseSetEncType already present in options")
return optStr
}
finalOpts := fmt.Sprintf("%s i2cp.leaseSetEncType=4,0", optStr)
log.WithField("finalOptions", finalOpts).Debug("Added default i2cp.leaseSetEncType to options")
return finalOpts
}