Compare commits
76 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ca51512ef9 | ||
![]() |
18dc0e6a9f | ||
![]() |
8279daa0e9 | ||
![]() |
bfbe7f638a | ||
![]() |
493db0aaaf | ||
![]() |
d7e8c77275 | ||
![]() |
058a02e6cc | ||
![]() |
86e5b7c368 | ||
![]() |
1c1652f234 | ||
![]() |
1f27cb656e | ||
![]() |
82eeba6275 | ||
![]() |
eea1f916cd | ||
![]() |
ac4ef13405 | ||
![]() |
dd2d0c029f | ||
![]() |
d8dafef068 | ||
![]() |
2dc0890d17 | ||
![]() |
5303ea6c34 | ||
![]() |
fdfa4240fb | ||
![]() |
a22cde30eb | ||
![]() |
88786afa0c | ||
![]() |
b5cacb3ece | ||
![]() |
37205fc8a2 | ||
![]() |
ba5b2ca853 | ||
![]() |
f4ca627cd8 | ||
![]() |
7fc3116088 | ||
![]() |
d3fb670d66 | ||
![]() |
25751504b9 | ||
![]() |
a745742ee1 | ||
![]() |
def28bbf7c | ||
![]() |
e6c161ed99 | ||
![]() |
3ebfb85f8a | ||
![]() |
14d0b22a28 | ||
![]() |
67554060fb | ||
![]() |
8b09ca7502 | ||
![]() |
97d1c812d3 | ||
![]() |
5be3e27599 | ||
![]() |
ecba767d91 | ||
![]() |
5149b7e504 | ||
![]() |
67c0c9288a | ||
![]() |
10f42af061 | ||
![]() |
aa63210c3c | ||
![]() |
4e1b426230 | ||
![]() |
a372049be9 | ||
![]() |
6e2cc71a92 | ||
![]() |
988769ed5a | ||
![]() |
504b7fb48b | ||
![]() |
c10b6b284c | ||
![]() |
c2ad35a952 | ||
![]() |
9e6d0489cf | ||
![]() |
d05428754b | ||
![]() |
41d9bd0150 | ||
![]() |
fd22f227b1 | ||
![]() |
0415adf35d | ||
![]() |
3655462ca4 | ||
![]() |
4bb6f81c40 | ||
![]() |
21e7b5a177 | ||
![]() |
4166a2c827 | ||
![]() |
cd2a4f072e | ||
![]() |
818b5249bc | ||
![]() |
83f9866de4 | ||
![]() |
b4293f755e | ||
![]() |
2bde2687b3 | ||
![]() |
19d8d8e4a8 | ||
![]() |
1cec982a61 | ||
![]() |
b2df466212 | ||
![]() |
bf2a12d78d | ||
![]() |
760e0b44b2 | ||
![]() |
9b7a798782 | ||
![]() |
3dc49e391d | ||
![]() |
b08d519a17 | ||
![]() |
256aaa7430 | ||
![]() |
197aca0ece | ||
![]() |
fff37dbffa | ||
![]() |
dfd7cd886f | ||
![]() |
c998e57a89 | ||
![]() |
41317685c5 |
20
.github/workflows/auto-assign.yml
vendored
Normal file
20
.github/workflows/auto-assign.yml
vendored
Normal 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
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
*~
|
*~
|
||||||
*.swp
|
*.swp
|
||||||
README.md.asc
|
README.md.asc
|
||||||
|
log
|
9
Makefile
9
Makefile
@@ -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)"
|
||||||
|
34
README.md
34
README.md
@@ -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
|
[](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.
|
||||||
|
38
SAMConn.go
38
SAMConn.go
@@ -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
60
common/formatter.go
Normal 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
87
common/reply.go
Normal 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
140
common/udp.go
Normal 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
19
common/version.go
Normal 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
398
config.go
@@ -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
83
config/leaseset.go
Normal 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
5
config/log.go
Normal 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
84
config/session.go
Normal 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
95
config/transport.go
Normal 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
69
config/tunnel.go
Normal 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)
|
||||||
|
}
|
257
datagram.go
257
datagram.go
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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!
|
||||||
}
|
}
|
||||||
|
199
emit-options.go
199
emit-options.go
@@ -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
57
emit.go
@@ -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
18
go.mod
@@ -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
102
go.sum
@@ -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=
|
|
||||||
|
125
helper/helper.go
125
helper/helper.go
@@ -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)
|
|
||||||
}
|
|
55
index.html
55
index.html
@@ -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, I’ll be moving the
|
In the next version, I'll be moving the
|
||||||
<code>
|
<code>
|
||||||
i2pkeys
|
i2pkeys
|
||||||
</code>
|
</code>
|
||||||
directory to it’s own repository
|
directory to it'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 '*.go' -exec sed -i 's|github.com/eyedeekay/sam3/i2pkeys|github.com/eyedeekay/i2pkeys|g' {} \;
|
<div>
|
||||||
</code></pre>
|
<pre>find . -name '*.go' -exec sed -i 's|github.com/eyedeekay/sam3/i2pkeys|github.com/eyedeekay/i2pkeys|g' {} \;
|
||||||
|
</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 (
|
||||||
"github.com/eyedeekay/sam3"
|
"github.com/eyedeekay/sam3"
|
||||||
@@ -193,17 +216,23 @@ func main() {
|
|||||||
n, _ := conn.Read(buf)
|
n, _ := conn.Read(buf)
|
||||||
fmt.Println("Server received: " + string(buf[:n]))
|
fmt.Println("Server received: " + 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
5
log.go
Normal 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
123
opts/suggestedOptions.go
Normal 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"
|
||||||
|
}
|
238
primary.go
238
primary.go
@@ -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
|
||||||
}
|
}
|
||||||
|
@@ -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!
|
||||||
}
|
}
|
||||||
|
@@ -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
69
raw.go
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
160
resolver.go
160
resolver.go
@@ -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
201
sam3.go
@@ -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
10
showhider.css
Normal 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
110
stream.go
@@ -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,
|
||||||
|
@@ -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
|
|
||||||
}
|
}
|
||||||
|
@@ -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!
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user