Compare commits
117 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
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 | ||
![]() |
56cca2e537 | ||
![]() |
9ca67baa32 | ||
![]() |
9083650fd0 | ||
![]() |
34eafde2e2 | ||
![]() |
1780908fb8 | ||
![]() |
615604699a | ||
![]() |
fb14beb87f | ||
![]() |
6fb498e01e | ||
![]() |
c80e6e7d95 | ||
![]() |
ee9930813d | ||
![]() |
3d289d99c6 | ||
![]() |
089f97b99f | ||
![]() |
eea4aa60a7 | ||
![]() |
00fb462ecd | ||
![]() |
c167f8e26d | ||
![]() |
7d38382735 | ||
![]() |
3c910e4b33 | ||
![]() |
ab0064e92e | ||
![]() |
b41ee5a01f | ||
![]() |
ba5ad234ee | ||
![]() |
eb723e9492 | ||
![]() |
45106d2b70 | ||
![]() |
0e87ddfa4b | ||
![]() |
9e3532c81b | ||
![]() |
ca8d8688cb | ||
![]() |
ef67dc1e44 | ||
![]() |
9a162e9502 | ||
![]() |
31b8d62f04 | ||
![]() |
d191b3404e | ||
![]() |
1de06ec9b9 | ||
![]() |
ed814a2fc6 | ||
![]() |
746084e65f | ||
![]() |
1a55eb6e90 | ||
![]() |
d307d85458 | ||
![]() |
0be404e00f | ||
![]() |
c37b1a099e | ||
![]() |
9c93a9e934 | ||
![]() |
a4c6ef983d | ||
![]() |
5629074653 | ||
![]() |
4e4d930c06 | ||
![]() |
18b8c77c79 | ||
![]() |
aa8028230e | ||
![]() |
6c5a389c9b | ||
![]() |
818f18648a | ||
![]() |
ddbe66bfe9 | ||
![]() |
f46a953bc8 | ||
![]() |
e5f304f552 | ||
![]() |
27810c197c | ||
![]() |
6c76e83617 | ||
![]() |
1748849c87 | ||
![]() |
0f604e88b2 | ||
![]() |
3ffa61bf70 | ||
![]() |
e9ab98c71b | ||
![]() |
6b90389d7f | ||
![]() |
0b4982e1fe | ||
![]() |
06ef087039 | ||
![]() |
2cb9b8f30c | ||
![]() |
bdc1edb224 | ||
![]() |
de74967cd2 | ||
![]() |
e8b472bed9 | ||
![]() |
e45d6f7cb0 | ||
![]() |
f3baac1fbe | ||
![]() |
692064fd13 |
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
|
||||
|
11
Makefile
11
Makefile
@@ -1,16 +1,17 @@
|
||||
|
||||
USER_GH=eyedeekay
|
||||
VERSION=0.32.32
|
||||
USER_GH=go-i2p
|
||||
VERSION=0.33.92
|
||||
CREDIT='contributors to this release: @hkh4n, @eyedeekay'
|
||||
packagename=sam3
|
||||
|
||||
echo:
|
||||
@echo "type make version to do release $(VERSION)"
|
||||
|
||||
version:
|
||||
gothub 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:
|
||||
gothub 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)
|
||||
|
||||
tar:
|
||||
tar --exclude .git \
|
||||
@@ -26,4 +27,4 @@ fmt:
|
||||
find . -name '*.go' -exec gofmt -w -s {} \;
|
||||
|
||||
upload-linux:
|
||||
gothub 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)"
|
||||
|
41
README.md
41
README.md
@@ -1,5 +1,11 @@
|
||||
# README #
|
||||
|
||||
STATUS: This project is maintained. I will respond to issues, pull requests, and feature requests within a few days.
|
||||
|
||||
[](https://goreportcard.com/report/github.com/go-i2p/sam3)
|
||||
|
||||
# README #
|
||||
|
||||
go library for the I2P [SAMv3.0](https://geti2p.net/en/docs/api/samv3) bridge, used to build anonymous/pseudonymous end-to-end encrypted sockets.
|
||||
|
||||
This library is much better than ccondom (that use BOB), much more stable and much easier to maintain.
|
||||
@@ -23,7 +29,7 @@ This library is much better than ccondom (that use BOB), much more stable and mu
|
||||
|
||||
**Does not work:**
|
||||
|
||||
* Everything works! :D
|
||||
* Stream Forwarding
|
||||
* Probably needs some real-world testing
|
||||
|
||||
## Documentation ##
|
||||
@@ -38,16 +44,17 @@ This library is much better than ccondom (that use BOB), much more stable and mu
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/majestrate/i2p-tools/sam3"
|
||||
"github.com/go-i2p/sam3"
|
||||
"github.com/go-i2p/sam3/i2pkeys"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const yoursam = "127.0.0.1:7656" // sam bridge
|
||||
|
||||
func client(server I2PAddr) {
|
||||
sam, _ := NewSAM(yoursam)
|
||||
func client(server i2pkeys.I2PAddr) {
|
||||
sam, _ := sam3.NewSAM(yoursam)
|
||||
keys, _ := sam.NewKeys()
|
||||
stream, _ := sam.NewStreamSession("clientTun", keys, Options_Small)
|
||||
stream, _ := sam.NewStreamSession("clientTun", keys, sam3.Options_Small)
|
||||
fmt.Println("Client: Connecting to " + server.Base32())
|
||||
conn, _ := stream.DialI2P(server)
|
||||
conn.Write([]byte("Hello world!"))
|
||||
@@ -57,9 +64,9 @@ func client(server I2PAddr) {
|
||||
func main() {
|
||||
sam, _ := NewSAM(yoursam)
|
||||
keys, _ := sam.NewKeys()
|
||||
go client(keys.Addr())
|
||||
stream, _ := sam.NewStreamSession("serverTun", keys, Options_Medium)
|
||||
stream, _ := sam.NewStreamSession("serverTun", keys, sam3.Options_Medium)
|
||||
listener, _ := stream.Listen()
|
||||
go client(keys.Addr())
|
||||
conn, _ := listener.Accept()
|
||||
buf := make([]byte, 4096)
|
||||
n, _ := conn.Read(buf)
|
||||
@@ -81,6 +88,26 @@ Error handling was omitted in the above code for readability.
|
||||
* `go test -tags=nettest` runs the whole suite (takes 90+ sec to perform!)
|
||||
* `go test -short` runs the shorter variant, does not connect to anything
|
||||
|
||||
## Verbosity ##
|
||||
Logging can be enabled and configured using the DEBUG_I2P environment variable. By default, logging is disabled.
|
||||
|
||||
There are three available log levels:
|
||||
|
||||
- Debug
|
||||
```shell
|
||||
export DEBUG_I2P=debug
|
||||
```
|
||||
- Warn
|
||||
```shell
|
||||
export DEBUG_I2P=warn
|
||||
```
|
||||
- Error
|
||||
```shell
|
||||
export DEBUG_I2P=error
|
||||
```
|
||||
|
||||
If DEBUG_I2P is set to an unrecognized variable, it will fall back to "debug".
|
||||
|
||||
## License ##
|
||||
|
||||
Public domain.
|
||||
|
@@ -1,14 +1,15 @@
|
||||
package sam3
|
||||
|
||||
import (
|
||||
"github.com/eyedeekay/sam3/i2pkeys"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/go-i2p/i2pkeys"
|
||||
)
|
||||
|
||||
/*
|
||||
import (
|
||||
. "github.com/eyedeekay/sam3/i2pkeys"
|
||||
. "github.com/go-i2p/i2pkeys"
|
||||
)
|
||||
*/
|
||||
// Implements net.Conn
|
||||
|
78
config.go
78
config.go
@@ -2,12 +2,13 @@ package sam3
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/sirupsen/logrus"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/eyedeekay/sam3/i2pkeys"
|
||||
"github.com/go-i2p/i2pkeys"
|
||||
)
|
||||
|
||||
// I2PConfig is a struct which manages I2P configuration options
|
||||
@@ -51,6 +52,8 @@ type I2PConfig struct {
|
||||
ReduceIdle string
|
||||
ReduceIdleTime string
|
||||
ReduceIdleQuantity string
|
||||
LeaseSetEncryption string
|
||||
|
||||
//Streaming Library options
|
||||
AccessListType string
|
||||
AccessList []string
|
||||
@@ -65,6 +68,10 @@ func (f *I2PConfig) Sam() string {
|
||||
if f.SamPort != "" {
|
||||
port = f.SamPort
|
||||
}
|
||||
log.WithFields(logrus.Fields{
|
||||
"host": host,
|
||||
"port": port,
|
||||
}).Debug("SAM address constructed")
|
||||
return host + ":" + port
|
||||
}
|
||||
|
||||
@@ -78,6 +85,10 @@ func (f *I2PConfig) SetSAMAddress(addr string) {
|
||||
}
|
||||
f.SamPort = "7656"
|
||||
f.SamHost = "127.0.0.1"
|
||||
log.WithFields(logrus.Fields{
|
||||
"host": f.SamHost,
|
||||
"port": f.SamPort,
|
||||
}).Debug("SAM address set")
|
||||
}
|
||||
|
||||
func (f *I2PConfig) ID() string {
|
||||
@@ -87,6 +98,7 @@ func (f *I2PConfig) ID() string {
|
||||
b[i] = "abcdefghijklmnopqrstuvwxyz"[rand.Intn(len("abcdefghijklmnopqrstuvwxyz"))]
|
||||
}
|
||||
f.TunName = string(b)
|
||||
log.WithField("TunName", f.TunName).Debug("Generated random tunnel name")
|
||||
}
|
||||
return " ID=" + f.TunName + " "
|
||||
}
|
||||
@@ -102,100 +114,139 @@ func (f *I2PConfig) Leasesetsettings() (string, string, string) {
|
||||
if f.LeaseSetPrivateSigningKey != "" {
|
||||
t = " i2cp.leaseSetPrivateSigningKey=" + f.LeaseSetPrivateSigningKey + " "
|
||||
}
|
||||
log.WithFields(logrus.Fields{
|
||||
"leaseSetKey": r,
|
||||
"leaseSetPrivateKey": s,
|
||||
"leaseSetPrivateSigningKey": t,
|
||||
}).Debug("Lease set settings constructed")
|
||||
return r, s, t
|
||||
}
|
||||
|
||||
func (f *I2PConfig) FromPort() string {
|
||||
if f.samMax() < 3.1 {
|
||||
log.Debug("SAM version < 3.1, FromPort not applicable")
|
||||
return ""
|
||||
}
|
||||
if f.Fromport != "0" {
|
||||
log.WithField("fromPort", f.Fromport).Debug("FromPort set")
|
||||
return " FROM_PORT=" + f.Fromport + " "
|
||||
}
|
||||
log.Debug("FromPort not set")
|
||||
return ""
|
||||
}
|
||||
|
||||
func (f *I2PConfig) ToPort() string {
|
||||
if f.samMax() < 3.1 {
|
||||
log.Debug("SAM version < 3.1, ToPort not applicable")
|
||||
return ""
|
||||
}
|
||||
if f.Toport != "0" {
|
||||
log.WithField("toPort", f.Toport).Debug("ToPort set")
|
||||
return " TO_PORT=" + f.Toport + " "
|
||||
}
|
||||
log.Debug("ToPort not set")
|
||||
return ""
|
||||
}
|
||||
|
||||
func (f *I2PConfig) SessionStyle() string {
|
||||
if f.Style != "" {
|
||||
log.WithField("style", f.Style).Debug("Session style set")
|
||||
return " STYLE=" + f.Style + " "
|
||||
}
|
||||
log.Debug("Using default STREAM style")
|
||||
return " STYLE=STREAM "
|
||||
}
|
||||
|
||||
func (f *I2PConfig) 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)
|
||||
}
|
||||
|
||||
func (f *I2PConfig) MinSAM() string {
|
||||
if f.SamMin == "" {
|
||||
log.Debug("Using default MinSAM: 3.0")
|
||||
return "3.0"
|
||||
}
|
||||
log.WithField("minSAM", f.SamMin).Debug("MinSAM set")
|
||||
return f.SamMin
|
||||
}
|
||||
|
||||
func (f *I2PConfig) MaxSAM() string {
|
||||
if f.SamMax == "" {
|
||||
log.Debug("Using default MaxSAM: 3.1")
|
||||
return "3.1"
|
||||
}
|
||||
log.WithField("maxSAM", f.SamMax).Debug("MaxSAM set")
|
||||
return f.SamMax
|
||||
}
|
||||
|
||||
func (f *I2PConfig) DestinationKey() string {
|
||||
if &f.DestinationKeys != nil {
|
||||
log.WithField("destinationKey", f.DestinationKeys.String()).Debug("Destination key set")
|
||||
return " DESTINATION=" + f.DestinationKeys.String() + " "
|
||||
}
|
||||
log.Debug("Using TRANSIENT destination")
|
||||
return " DESTINATION=TRANSIENT "
|
||||
}
|
||||
|
||||
func (f *I2PConfig) SignatureType() string {
|
||||
if f.samMax() < 3.1 {
|
||||
log.Debug("SAM version < 3.1, SignatureType not applicable")
|
||||
return ""
|
||||
}
|
||||
if f.SigType != "" {
|
||||
log.WithField("sigType", f.SigType).Debug("Signature type set")
|
||||
return " SIGNATURE_TYPE=" + f.SigType + " "
|
||||
}
|
||||
log.Debug("Signature type not set")
|
||||
return ""
|
||||
}
|
||||
|
||||
func (f *I2PConfig) EncryptLease() string {
|
||||
if f.EncryptLeaseSet == "true" {
|
||||
log.Debug("Lease set encryption enabled")
|
||||
return " i2cp.encryptLeaseSet=true "
|
||||
}
|
||||
log.Debug("Lease set encryption not enabled")
|
||||
return ""
|
||||
}
|
||||
|
||||
func (f *I2PConfig) Reliability() string {
|
||||
if f.MessageReliability != "" {
|
||||
log.WithField("reliability", f.MessageReliability).Debug("Message reliability set")
|
||||
return " i2cp.messageReliability=" + f.MessageReliability + " "
|
||||
}
|
||||
log.Debug("Message reliability not set")
|
||||
return ""
|
||||
}
|
||||
|
||||
func (f *I2PConfig) Reduce() string {
|
||||
if f.ReduceIdle == "true" {
|
||||
log.WithFields(logrus.Fields{
|
||||
"reduceIdle": f.ReduceIdle,
|
||||
"reduceIdleTime": f.ReduceIdleTime,
|
||||
"reduceIdleQuantity": f.ReduceIdleQuantity,
|
||||
}).Debug("Reduce idle settings applied")
|
||||
return "i2cp.reduceOnIdle=" + f.ReduceIdle + "i2cp.reduceIdleTime=" + f.ReduceIdleTime + "i2cp.reduceQuantity=" + f.ReduceIdleQuantity
|
||||
}
|
||||
log.Debug("Reduce idle settings not applied")
|
||||
return ""
|
||||
}
|
||||
|
||||
func (f *I2PConfig) Close() string {
|
||||
if f.CloseIdle == "true" {
|
||||
log.WithFields(logrus.Fields{
|
||||
"closeIdle": f.CloseIdle,
|
||||
"closeIdleTime": f.CloseIdleTime,
|
||||
}).Debug("Close idle settings applied")
|
||||
return "i2cp.closeOnIdle=" + f.CloseIdle + "i2cp.closeIdleTime=" + f.CloseIdleTime
|
||||
}
|
||||
log.Debug("Close idle settings not applied")
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -210,6 +261,7 @@ func (f *I2PConfig) DoZero() string {
|
||||
if f.FastRecieve == "true" {
|
||||
r += " " + f.FastRecieve + " "
|
||||
}
|
||||
log.WithField("zeroHopSettings", r).Debug("Zero hop settings applied")
|
||||
return r
|
||||
}
|
||||
func (f *I2PConfig) Print() []string {
|
||||
@@ -234,17 +286,22 @@ func (f *I2PConfig) Print() []string {
|
||||
lsk, lspk, lspsk,
|
||||
f.Accesslisttype(),
|
||||
f.Accesslist(),
|
||||
f.LeaseSetEncryptionType(),
|
||||
}
|
||||
}
|
||||
|
||||
func (f *I2PConfig) Accesslisttype() string {
|
||||
if f.AccessListType == "whitelist" {
|
||||
log.Debug("Access list type set to whitelist")
|
||||
return "i2cp.enableAccessList=true"
|
||||
} else if f.AccessListType == "blacklist" {
|
||||
log.Debug("Access list type set to blacklist")
|
||||
return "i2cp.enableBlackList=true"
|
||||
} else if f.AccessListType == "none" {
|
||||
log.Debug("Access list type set to none")
|
||||
return ""
|
||||
}
|
||||
log.Debug("Access list type not set")
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -254,11 +311,28 @@ func (f *I2PConfig) Accesslist() string {
|
||||
for _, s := range f.AccessList {
|
||||
r += s + ","
|
||||
}
|
||||
log.WithField("accessList", r).Debug("Access list generated")
|
||||
return "i2cp.accessList=" + strings.TrimSuffix(r, ",")
|
||||
}
|
||||
log.Debug("Access list not set")
|
||||
return ""
|
||||
}
|
||||
|
||||
func (f *I2PConfig) 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 "i2cp.leaseSetEncType=" + f.LeaseSetEncryption
|
||||
}
|
||||
|
||||
func NewConfig(opts ...func(*I2PConfig) error) (*I2PConfig, error) {
|
||||
var config I2PConfig
|
||||
config.SamHost = "127.0.0.1"
|
||||
@@ -347,7 +421,7 @@ func (cfg *Config) DatagramSession() (session *DatagramSession, err error) {
|
||||
// determine udp port
|
||||
var portstr string
|
||||
_, portstr, err = net.SplitHostPort(cfg.Addr)
|
||||
if err == nil {
|
||||
if IgnorePortError(err) == nil {
|
||||
var port int
|
||||
port, err = strconv.Atoi(portstr)
|
||||
if err == nil && port > 0 {
|
||||
|
107
datagram.go
107
datagram.go
@@ -3,11 +3,12 @@ package sam3
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/eyedeekay/sam3/i2pkeys"
|
||||
"github.com/go-i2p/i2pkeys"
|
||||
)
|
||||
|
||||
// The DatagramSession implements net.PacketConn. It works almost like ordinary
|
||||
@@ -27,47 +28,111 @@ type DatagramSession struct {
|
||||
// Creates a new datagram session. udpPort is the UDP port SAM is listening on,
|
||||
// and if you set it to zero, it will use SAMs standard UDP port.
|
||||
func (s *SAM) NewDatagramSession(id string, keys i2pkeys.I2PKeys, options []string, udpPort int) (*DatagramSession, error) {
|
||||
log.WithFields(logrus.Fields{
|
||||
"id": id,
|
||||
"udpPort": udpPort,
|
||||
}).Debug("Creating new DatagramSession")
|
||||
|
||||
if udpPort > 65335 || udpPort < 0 {
|
||||
log.WithField("udpPort", udpPort).Error("Invalid UDP port")
|
||||
return nil, errors.New("udpPort needs to be in the intervall 0-65335")
|
||||
}
|
||||
if udpPort == 0 {
|
||||
udpPort = 7655
|
||||
log.Debug("Using default UDP port 7655")
|
||||
}
|
||||
lhost, _, err := net.SplitHostPort(s.conn.LocalAddr().String())
|
||||
lhost, _, err := SplitHostPort(s.conn.LocalAddr().String())
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to split local host port")
|
||||
s.Close()
|
||||
return nil, err
|
||||
}
|
||||
lUDPAddr, err := net.ResolveUDPAddr("udp4", lhost+":0")
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to resolve local UDP address")
|
||||
return nil, err
|
||||
}
|
||||
udpconn, err := net.ListenUDP("udp4", lUDPAddr)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to listen on UDP")
|
||||
return nil, err
|
||||
}
|
||||
rhost, _, err := net.SplitHostPort(s.conn.RemoteAddr().String())
|
||||
rhost, _, err := SplitHostPort(s.conn.RemoteAddr().String())
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to split remote host port")
|
||||
s.Close()
|
||||
return nil, err
|
||||
}
|
||||
rUDPAddr, err := net.ResolveUDPAddr("udp4", rhost+":"+strconv.Itoa(udpPort))
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to resolve remote UDP address")
|
||||
return nil, err
|
||||
}
|
||||
_, lport, err := net.SplitHostPort(udpconn.LocalAddr().String())
|
||||
conn, err := s.newGenericSession("DATAGRAM", id, keys, options, []string{"PORT=" + lport})
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to get local port")
|
||||
s.Close()
|
||||
return nil, err
|
||||
}
|
||||
conn, err := s.newGenericSession("DATAGRAM", id, keys, options, []string{" PORT=" + lport})
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to create generic session")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.WithField("id", id).Info("DatagramSession created successfully")
|
||||
return &DatagramSession{s.address, id, conn, udpconn, keys, rUDPAddr, nil}, nil
|
||||
}
|
||||
|
||||
func (s *DatagramSession) B32() string {
|
||||
return s.keys.Addr().Base32()
|
||||
b32 := s.keys.Addr().Base32()
|
||||
log.WithField("b32", b32).Debug("Generated B32 address")
|
||||
return b32
|
||||
}
|
||||
|
||||
func (s *DatagramSession) Dial(net string, addr string) (*DatagramSession, error) {
|
||||
log.WithFields(logrus.Fields{
|
||||
"net": net,
|
||||
"addr": addr,
|
||||
}).Debug("Dialing address")
|
||||
netaddr, err := s.Lookup(addr)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Lookup failed")
|
||||
return nil, err
|
||||
}
|
||||
return s.DialI2PRemote(net, netaddr)
|
||||
}
|
||||
|
||||
func (s *DatagramSession) DialRemote(net, addr string) (net.PacketConn, error) {
|
||||
log.WithFields(logrus.Fields{
|
||||
"net": net,
|
||||
"addr": addr,
|
||||
}).Debug("Dialing remote address")
|
||||
netaddr, err := s.Lookup(addr)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Lookup failed")
|
||||
return nil, err
|
||||
}
|
||||
return s.DialI2PRemote(net, netaddr)
|
||||
}
|
||||
|
||||
func (s *DatagramSession) DialI2PRemote(net string, addr net.Addr) (*DatagramSession, error) {
|
||||
log.WithFields(logrus.Fields{
|
||||
"net": net,
|
||||
"addr": addr,
|
||||
}).Debug("Dialing I2P remote address")
|
||||
switch addr.(type) {
|
||||
case *i2pkeys.I2PAddr:
|
||||
s.remoteAddr = addr.(*i2pkeys.I2PAddr)
|
||||
case i2pkeys.I2PAddr:
|
||||
i2paddr := addr.(i2pkeys.I2PAddr)
|
||||
s.remoteAddr = &i2paddr
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *DatagramSession) RemoteAddr() net.Addr {
|
||||
log.WithField("remoteAddr", s.remoteAddr).Debug("Getting remote address")
|
||||
return s.remoteAddr
|
||||
}
|
||||
|
||||
@@ -75,6 +140,7 @@ func (s *DatagramSession) RemoteAddr() net.Addr {
|
||||
// the number of bytes read, from what address it was sent, or an error.
|
||||
// implements net.PacketConn
|
||||
func (s *DatagramSession) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
|
||||
log.Debug("Reading datagram")
|
||||
// extra bytes to read the remote address of incomming datagram
|
||||
buf := make([]byte, len(b)+4096)
|
||||
|
||||
@@ -83,6 +149,7 @@ func (s *DatagramSession) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
|
||||
var saddr *net.UDPAddr
|
||||
n, saddr, err = s.udpconn.ReadFromUDP(buf)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to read from UDP")
|
||||
return 0, i2pkeys.I2PAddr(""), err
|
||||
}
|
||||
if bytes.Equal(saddr.IP, s.rUDPAddr.IP) {
|
||||
@@ -92,10 +159,12 @@ func (s *DatagramSession) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
|
||||
}
|
||||
i := bytes.IndexByte(buf, byte('\n'))
|
||||
if i > 4096 || i > n {
|
||||
log.Error("Could not parse incoming message remote address")
|
||||
return 0, i2pkeys.I2PAddr(""), errors.New("Could not parse incomming message remote address.")
|
||||
}
|
||||
raddr, err := i2pkeys.NewI2PAddrFromString(string(buf[:i]))
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not parse incoming message remote address")
|
||||
return 0, i2pkeys.I2PAddr(""), errors.New("Could not parse incomming message remote address: " + err.Error())
|
||||
}
|
||||
// shift out the incomming address to contain only the data received
|
||||
@@ -104,15 +173,18 @@ func (s *DatagramSession) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
|
||||
return n - (i + 1), raddr, errors.New("Datagram did not fit into your buffer.")
|
||||
} else {
|
||||
copy(b, buf[i+1:n])
|
||||
log.WithField("bytesRead", n-(i+1)).Debug("Datagram read successfully")
|
||||
return n - (i + 1), raddr, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DatagramSession) Accept() (net.Conn, error) {
|
||||
log.Debug("Accept called on DatagramSession")
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *DatagramSession) Read(b []byte) (n int, err error) {
|
||||
log.Debug("Reading from DatagramSession")
|
||||
rint, _, rerr := s.ReadFrom(b)
|
||||
return rint, rerr
|
||||
}
|
||||
@@ -121,29 +193,46 @@ func (s *DatagramSession) Read(b []byte) (n int, err error) {
|
||||
// writing, maximum size is 31 kilobyte, but this may change in the future.
|
||||
// Implements net.PacketConn.
|
||||
func (s *DatagramSession) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
||||
log.WithFields(logrus.Fields{
|
||||
"addr": addr,
|
||||
"datagramLen": len(b),
|
||||
}).Debug("Writing datagram")
|
||||
header := []byte("3.1 " + s.id + " " + addr.String() + "\n")
|
||||
msg := append(header, b...)
|
||||
n, err = s.udpconn.WriteToUDP(msg, s.rUDPAddr)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to write to UDP")
|
||||
} else {
|
||||
log.WithField("bytesWritten", n).Debug("Datagram written successfully")
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (s *DatagramSession) Write(b []byte) (int, error) {
|
||||
log.WithField("dataLen", len(b)).Debug("Writing to DatagramSession")
|
||||
return s.WriteTo(b, s.remoteAddr)
|
||||
}
|
||||
|
||||
// Closes the DatagramSession. Implements net.PacketConn
|
||||
func (s *DatagramSession) Close() error {
|
||||
log.Debug("Closing DatagramSession")
|
||||
err := s.conn.Close()
|
||||
err2 := s.udpconn.Close()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to close connection")
|
||||
return err
|
||||
}
|
||||
if err2 != nil {
|
||||
log.WithError(err2).Error("Failed to close UDP connection")
|
||||
}
|
||||
return err2
|
||||
}
|
||||
|
||||
// Returns the I2P destination of the DatagramSession.
|
||||
func (s *DatagramSession) LocalI2PAddr() i2pkeys.I2PAddr {
|
||||
return s.keys.Addr()
|
||||
addr := s.keys.Addr()
|
||||
log.WithField("localI2PAddr", addr).Debug("Getting local I2P address")
|
||||
return addr
|
||||
}
|
||||
|
||||
// Implements net.PacketConn
|
||||
@@ -156,12 +245,14 @@ func (s *DatagramSession) Addr() net.Addr {
|
||||
}
|
||||
|
||||
func (s *DatagramSession) Lookup(name string) (a net.Addr, err error) {
|
||||
log.WithField("name", name).Debug("Looking up address")
|
||||
var sam *SAM
|
||||
sam, err = NewSAM(s.samAddr)
|
||||
if err == nil {
|
||||
defer sam.Close()
|
||||
a, err = sam.Lookup(name)
|
||||
}
|
||||
log.WithField("address", a).Debug("Lookup successful")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -169,19 +260,23 @@ func (s *DatagramSession) Lookup(name string) (a net.Addr, err error) {
|
||||
// net.PacketConn and does the same thing. Setting write deadlines for datagrams
|
||||
// is seldom done.
|
||||
func (s *DatagramSession) SetDeadline(t time.Time) error {
|
||||
log.WithField("deadline", t).Debug("Setting deadline")
|
||||
return s.udpconn.SetDeadline(t)
|
||||
}
|
||||
|
||||
// Sets read deadline for the DatagramSession. Implements net.PacketConn
|
||||
func (s *DatagramSession) SetReadDeadline(t time.Time) error {
|
||||
log.WithField("readDeadline", t).Debug("Setting read deadline")
|
||||
return s.udpconn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
// Sets the write deadline for the DatagramSession. Implements net.Packetconn.
|
||||
func (s *DatagramSession) SetWriteDeadline(t time.Time) error {
|
||||
log.WithField("writeDeadline", t).Debug("Setting write deadline")
|
||||
return s.udpconn.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
func (s *DatagramSession) SetWriteBuffer(bytes int) error {
|
||||
log.WithField("bytes", bytes).Debug("Setting write buffer")
|
||||
return s.udpconn.SetWriteBuffer(bytes)
|
||||
}
|
||||
|
@@ -1,10 +1,7 @@
|
||||
// +build nettest
|
||||
|
||||
package sam3
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -125,7 +122,7 @@ func ExampleDatagramSession() {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
log.Println("Got message: '" + string(buf[:n]) + "'")
|
||||
fmt.Println("Got message: '" + string(buf[:n]) + "'")
|
||||
fmt.Println("Got message: " + string(buf[:n]))
|
||||
|
||||
return
|
||||
|
1
debian/files
vendored
Normal file
1
debian/files
vendored
Normal file
@@ -0,0 +1 @@
|
||||
golang-github-eyedeekay-sam3_0.3.2.32_source.buildinfo devel optional
|
127
emit-options.go
127
emit-options.go
@@ -2,26 +2,31 @@ package sam3
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/sirupsen/logrus"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//Option is a SAMEmit Option
|
||||
// Option is a SAMEmit Option
|
||||
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 {
|
||||
return func(c *SAMEmit) error {
|
||||
if s == "STREAM" {
|
||||
c.Style = s
|
||||
log.WithField("style", s).Debug("Set session style")
|
||||
return nil
|
||||
} else if s == "DATAGRAM" {
|
||||
c.Style = s
|
||||
log.WithField("style", s).Debug("Set session style")
|
||||
return nil
|
||||
} else if s == "RAW" {
|
||||
c.Style = s
|
||||
log.WithField("style", s).Debug("Set session style")
|
||||
return nil
|
||||
}
|
||||
log.WithField("style", s).Error("Invalid session style")
|
||||
return fmt.Errorf("Invalid session STYLE=%s, must be STREAM, DATAGRAM, or RAW", s)
|
||||
}
|
||||
}
|
||||
@@ -31,136 +36,162 @@ func SetSAMAddress(s string) func(*SAMEmit) error {
|
||||
return func(c *SAMEmit) error {
|
||||
sp := strings.Split(s, ":")
|
||||
if len(sp) > 2 {
|
||||
log.WithField("address", s).Error("Invalid SAM address")
|
||||
return fmt.Errorf("Invalid address string: %s", sp)
|
||||
}
|
||||
if len(sp) == 2 {
|
||||
c.I2PConfig.SamPort = sp[1]
|
||||
}
|
||||
c.I2PConfig.SamHost = sp[0]
|
||||
log.WithFields(logrus.Fields{
|
||||
"host": c.I2PConfig.SamHost,
|
||||
"port": c.I2PConfig.SamPort,
|
||||
}).Debug("Set SAM address")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
//SetSAMHost sets the host of the SAMEmit's SAM bridge
|
||||
// SetSAMHost sets the host of the SAMEmit's SAM bridge
|
||||
func SetSAMHost(s string) func(*SAMEmit) error {
|
||||
return func(c *SAMEmit) error {
|
||||
c.I2PConfig.SamHost = s
|
||||
log.WithField("host", s).Debug("Set SAM host")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
//SetSAMPort sets the port of the SAMEmit's SAM bridge using a string
|
||||
// SetSAMPort sets the port of the SAMEmit's SAM bridge using a string
|
||||
func SetSAMPort(s string) func(*SAMEmit) error {
|
||||
return func(c *SAMEmit) error {
|
||||
port, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
log.WithField("port", s).Error("Invalid SAM port: non-number")
|
||||
return fmt.Errorf("Invalid SAM Port %s; non-number", s)
|
||||
}
|
||||
if port < 65536 && port > -1 {
|
||||
c.I2PConfig.SamPort = s
|
||||
log.WithField("port", s).Debug("Set SAM port")
|
||||
return nil
|
||||
}
|
||||
log.WithField("port", port).Error("Invalid SAM port")
|
||||
return fmt.Errorf("Invalid port")
|
||||
}
|
||||
}
|
||||
|
||||
//SetName sets the host of the SAMEmit's SAM bridge
|
||||
// SetName sets the host of the SAMEmit's SAM bridge
|
||||
func SetName(s string) func(*SAMEmit) error {
|
||||
return func(c *SAMEmit) error {
|
||||
c.I2PConfig.TunName = s
|
||||
log.WithField("name", s).Debug("Set tunnel name")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
//SetInLength sets the number of hops inbound
|
||||
// SetInLength sets the number of hops inbound
|
||||
func SetInLength(u int) func(*SAMEmit) error {
|
||||
return func(c *SAMEmit) error {
|
||||
if u < 7 && u >= 0 {
|
||||
c.I2PConfig.InLength = strconv.Itoa(u)
|
||||
log.WithField("inLength", u).Debug("Set inbound tunnel length")
|
||||
return nil
|
||||
}
|
||||
log.WithField("inLength", u).Error("Invalid inbound tunnel length")
|
||||
return fmt.Errorf("Invalid inbound tunnel length")
|
||||
}
|
||||
}
|
||||
|
||||
//SetOutLength sets the number of hops outbound
|
||||
// SetOutLength sets the number of hops outbound
|
||||
func SetOutLength(u int) func(*SAMEmit) error {
|
||||
return func(c *SAMEmit) error {
|
||||
if u < 7 && u >= 0 {
|
||||
c.I2PConfig.OutLength = strconv.Itoa(u)
|
||||
log.WithField("outLength", u).Debug("Set outbound tunnel length")
|
||||
return nil
|
||||
}
|
||||
log.WithField("outLength", u).Error("Invalid outbound tunnel length")
|
||||
return fmt.Errorf("Invalid outbound tunnel length")
|
||||
}
|
||||
}
|
||||
|
||||
//SetInVariance sets the variance of a number of hops inbound
|
||||
// SetInVariance sets the variance of a number of hops inbound
|
||||
func SetInVariance(i int) func(*SAMEmit) error {
|
||||
return func(c *SAMEmit) error {
|
||||
if i < 7 && i > -7 {
|
||||
c.I2PConfig.InVariance = strconv.Itoa(i)
|
||||
log.WithField("inVariance", i).Debug("Set inbound tunnel variance")
|
||||
return nil
|
||||
}
|
||||
log.WithField("inVariance", i).Error("Invalid inbound tunnel variance")
|
||||
return fmt.Errorf("Invalid inbound tunnel length")
|
||||
}
|
||||
}
|
||||
|
||||
//SetOutVariance sets the variance of a number of hops outbound
|
||||
// SetOutVariance sets the variance of a number of hops outbound
|
||||
func SetOutVariance(i int) func(*SAMEmit) error {
|
||||
return func(c *SAMEmit) error {
|
||||
if i < 7 && i > -7 {
|
||||
c.I2PConfig.OutVariance = strconv.Itoa(i)
|
||||
log.WithField("outVariance", i).Debug("Set outbound tunnel variance")
|
||||
return nil
|
||||
}
|
||||
log.WithField("outVariance", i).Error("Invalid outbound tunnel variance")
|
||||
return fmt.Errorf("Invalid outbound tunnel variance")
|
||||
}
|
||||
}
|
||||
|
||||
//SetInQuantity sets the inbound tunnel quantity
|
||||
// SetInQuantity sets the inbound tunnel quantity
|
||||
func SetInQuantity(u int) func(*SAMEmit) error {
|
||||
return func(c *SAMEmit) error {
|
||||
if u <= 16 && u > 0 {
|
||||
c.I2PConfig.InQuantity = strconv.Itoa(u)
|
||||
log.WithField("inQuantity", u).Debug("Set inbound tunnel quantity")
|
||||
return nil
|
||||
}
|
||||
log.WithField("inQuantity", u).Error("Invalid inbound tunnel quantity")
|
||||
return fmt.Errorf("Invalid inbound tunnel quantity")
|
||||
}
|
||||
}
|
||||
|
||||
//SetOutQuantity sets the outbound tunnel quantity
|
||||
// SetOutQuantity sets the outbound tunnel quantity
|
||||
func SetOutQuantity(u int) func(*SAMEmit) error {
|
||||
return func(c *SAMEmit) error {
|
||||
if u <= 16 && u > 0 {
|
||||
c.I2PConfig.OutQuantity = strconv.Itoa(u)
|
||||
log.WithField("outQuantity", u).Debug("Set outbound tunnel quantity")
|
||||
return nil
|
||||
}
|
||||
log.WithField("outQuantity", u).Error("Invalid outbound tunnel quantity")
|
||||
return fmt.Errorf("Invalid outbound tunnel quantity")
|
||||
}
|
||||
}
|
||||
|
||||
//SetInBackups sets the inbound tunnel backups
|
||||
// SetInBackups sets the inbound tunnel backups
|
||||
func SetInBackups(u int) func(*SAMEmit) error {
|
||||
return func(c *SAMEmit) error {
|
||||
if u < 6 && u >= 0 {
|
||||
c.I2PConfig.InBackupQuantity = strconv.Itoa(u)
|
||||
log.WithField("inBackups", u).Debug("Set inbound tunnel backups")
|
||||
return nil
|
||||
}
|
||||
log.WithField("inBackups", u).Error("Invalid inbound tunnel backup quantity")
|
||||
return fmt.Errorf("Invalid inbound tunnel backup quantity")
|
||||
}
|
||||
}
|
||||
|
||||
//SetOutBackups sets the inbound tunnel backups
|
||||
// SetOutBackups sets the inbound tunnel backups
|
||||
func SetOutBackups(u int) func(*SAMEmit) error {
|
||||
return func(c *SAMEmit) error {
|
||||
if u < 6 && u >= 0 {
|
||||
c.I2PConfig.OutBackupQuantity = strconv.Itoa(u)
|
||||
log.WithField("outBackups", u).Debug("Set outbound tunnel backups")
|
||||
return nil
|
||||
}
|
||||
log.WithField("outBackups", u).Error("Invalid outbound tunnel backup quantity")
|
||||
return fmt.Errorf("Invalid outbound tunnel backup quantity")
|
||||
}
|
||||
}
|
||||
|
||||
//SetEncrypt tells the router to use an encrypted leaseset
|
||||
// SetEncrypt tells the router to use an encrypted leaseset
|
||||
func SetEncrypt(b bool) func(*SAMEmit) error {
|
||||
return func(c *SAMEmit) error {
|
||||
if b {
|
||||
@@ -168,43 +199,48 @@ func SetEncrypt(b bool) func(*SAMEmit) error {
|
||||
return nil
|
||||
}
|
||||
c.I2PConfig.EncryptLeaseSet = "false"
|
||||
log.WithField("encrypt", b).Debug("Set lease set encryption")
|
||||
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 {
|
||||
return func(c *SAMEmit) error {
|
||||
c.I2PConfig.LeaseSetKey = s
|
||||
log.WithField("leaseSetKey", s).Debug("Set lease set key")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
//SetLeaseSetPrivateKey sets the host of the SAMEmit's SAM bridge
|
||||
// SetLeaseSetPrivateKey sets the host of the SAMEmit's SAM bridge
|
||||
func SetLeaseSetPrivateKey(s string) func(*SAMEmit) error {
|
||||
return func(c *SAMEmit) error {
|
||||
c.I2PConfig.LeaseSetPrivateKey = s
|
||||
log.WithField("leaseSetPrivateKey", s).Debug("Set lease set private key")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
//SetLeaseSetPrivateSigningKey sets the host of the SAMEmit's SAM bridge
|
||||
// SetLeaseSetPrivateSigningKey sets the host of the SAMEmit's SAM bridge
|
||||
func SetLeaseSetPrivateSigningKey(s string) func(*SAMEmit) error {
|
||||
return func(c *SAMEmit) error {
|
||||
c.I2PConfig.LeaseSetPrivateSigningKey = s
|
||||
log.WithField("leaseSetPrivateSigningKey", s).Debug("Set lease set private signing key")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
//SetMessageReliability sets the host of the SAMEmit's SAM bridge
|
||||
// SetMessageReliability sets the host of the SAMEmit's SAM bridge
|
||||
func SetMessageReliability(s string) func(*SAMEmit) error {
|
||||
return func(c *SAMEmit) error {
|
||||
c.I2PConfig.MessageReliability = s
|
||||
log.WithField("messageReliability", s).Debug("Set message reliability")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
//SetAllowZeroIn tells the tunnel to accept zero-hop peers
|
||||
// SetAllowZeroIn tells the tunnel to accept zero-hop peers
|
||||
func SetAllowZeroIn(b bool) func(*SAMEmit) error {
|
||||
return func(c *SAMEmit) error {
|
||||
if b {
|
||||
@@ -212,11 +248,12 @@ func SetAllowZeroIn(b bool) func(*SAMEmit) error {
|
||||
return nil
|
||||
}
|
||||
c.I2PConfig.InAllowZeroHop = "false"
|
||||
log.WithField("allowZeroIn", b).Debug("Set allow zero-hop inbound")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
//SetAllowZeroOut tells the tunnel to accept zero-hop peers
|
||||
// SetAllowZeroOut tells the tunnel to accept zero-hop peers
|
||||
func SetAllowZeroOut(b bool) func(*SAMEmit) error {
|
||||
return func(c *SAMEmit) error {
|
||||
if b {
|
||||
@@ -224,11 +261,12 @@ func SetAllowZeroOut(b bool) func(*SAMEmit) error {
|
||||
return nil
|
||||
}
|
||||
c.I2PConfig.OutAllowZeroHop = "false"
|
||||
log.WithField("allowZeroOut", b).Debug("Set allow zero-hop outbound")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
//SetCompress tells clients to use compression
|
||||
// SetCompress tells clients to use compression
|
||||
func SetCompress(b bool) func(*SAMEmit) error {
|
||||
return func(c *SAMEmit) error {
|
||||
if b {
|
||||
@@ -236,11 +274,12 @@ func SetCompress(b bool) func(*SAMEmit) error {
|
||||
return nil
|
||||
}
|
||||
c.I2PConfig.UseCompression = "false"
|
||||
log.WithField("compress", b).Debug("Set compression")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
//SetFastRecieve tells clients to use compression
|
||||
// SetFastRecieve tells clients to use compression
|
||||
func SetFastRecieve(b bool) func(*SAMEmit) error {
|
||||
return func(c *SAMEmit) error {
|
||||
if b {
|
||||
@@ -248,11 +287,12 @@ func SetFastRecieve(b bool) func(*SAMEmit) error {
|
||||
return nil
|
||||
}
|
||||
c.I2PConfig.FastRecieve = "false"
|
||||
log.WithField("fastReceive", b).Debug("Set fast receive")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
//SetReduceIdle tells the connection to reduce it's tunnels during extended idle time.
|
||||
// SetReduceIdle tells the connection to reduce it's tunnels during extended idle time.
|
||||
func SetReduceIdle(b bool) func(*SAMEmit) error {
|
||||
return func(c *SAMEmit) error {
|
||||
if b {
|
||||
@@ -260,46 +300,54 @@ func SetReduceIdle(b bool) func(*SAMEmit) error {
|
||||
return nil
|
||||
}
|
||||
c.I2PConfig.ReduceIdle = "false"
|
||||
log.WithField("reduceIdle", b).Debug("Set reduce idle")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
//SetReduceIdleTime sets the time to wait before reducing tunnels to idle levels
|
||||
// SetReduceIdleTime sets the time to wait before reducing tunnels to idle levels
|
||||
func SetReduceIdleTime(u int) func(*SAMEmit) error {
|
||||
return func(c *SAMEmit) error {
|
||||
c.I2PConfig.ReduceIdleTime = "300000"
|
||||
if u >= 6 {
|
||||
c.I2PConfig.ReduceIdleTime = strconv.Itoa((u * 60) * 1000)
|
||||
idleTime := strconv.Itoa((u * 60) * 1000)
|
||||
c.I2PConfig.ReduceIdleTime = idleTime
|
||||
log.WithField("reduceIdleTime", idleTime).Debug("Set reduce idle time")
|
||||
return nil
|
||||
}
|
||||
log.WithField("minutes", u).Error("Invalid reduce idle timeout")
|
||||
return fmt.Errorf("Invalid reduce idle timeout(Measured in minutes) %v", u)
|
||||
}
|
||||
}
|
||||
|
||||
//SetReduceIdleTimeMs sets the time to wait before reducing tunnels to idle levels in milliseconds
|
||||
// SetReduceIdleTimeMs sets the time to wait before reducing tunnels to idle levels in milliseconds
|
||||
func SetReduceIdleTimeMs(u int) func(*SAMEmit) error {
|
||||
return func(c *SAMEmit) error {
|
||||
c.I2PConfig.ReduceIdleTime = "300000"
|
||||
if u >= 300000 {
|
||||
c.I2PConfig.ReduceIdleTime = strconv.Itoa(u)
|
||||
log.WithField("reduceIdleTimeMs", u).Debug("Set reduce idle time in milliseconds")
|
||||
return nil
|
||||
}
|
||||
log.WithField("milliseconds", u).Error("Invalid reduce idle timeout")
|
||||
return fmt.Errorf("Invalid reduce idle timeout(Measured in milliseconds) %v", u)
|
||||
}
|
||||
}
|
||||
|
||||
//SetReduceIdleQuantity sets minimum number of tunnels to reduce to during idle time
|
||||
// SetReduceIdleQuantity sets minimum number of tunnels to reduce to during idle time
|
||||
func SetReduceIdleQuantity(u int) func(*SAMEmit) error {
|
||||
return func(c *SAMEmit) error {
|
||||
if u < 5 {
|
||||
c.I2PConfig.ReduceIdleQuantity = strconv.Itoa(u)
|
||||
log.WithField("reduceIdleQuantity", u).Debug("Set reduce idle quantity")
|
||||
return nil
|
||||
}
|
||||
log.WithField("quantity", u).Error("Invalid reduce tunnel quantity")
|
||||
return fmt.Errorf("Invalid reduce tunnel quantity")
|
||||
}
|
||||
}
|
||||
|
||||
//SetCloseIdle tells the connection to close it's tunnels during extended idle time.
|
||||
// SetCloseIdle tells the connection to close it's tunnels during extended idle time.
|
||||
func SetCloseIdle(b bool) func(*SAMEmit) error {
|
||||
return func(c *SAMEmit) error {
|
||||
if b {
|
||||
@@ -311,59 +359,72 @@ func SetCloseIdle(b bool) func(*SAMEmit) error {
|
||||
}
|
||||
}
|
||||
|
||||
//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 {
|
||||
return func(c *SAMEmit) error {
|
||||
c.I2PConfig.CloseIdleTime = "300000"
|
||||
if u >= 6 {
|
||||
c.I2PConfig.CloseIdleTime = strconv.Itoa((u * 60) * 1000)
|
||||
idleTime := strconv.Itoa((u * 60) * 1000)
|
||||
c.I2PConfig.CloseIdleTime = idleTime
|
||||
log.WithFields(logrus.Fields{
|
||||
"minutes": u,
|
||||
"milliseconds": idleTime,
|
||||
}).Debug("Set close idle time")
|
||||
return nil
|
||||
}
|
||||
log.WithField("minutes", u).Error("Invalid close idle timeout")
|
||||
return fmt.Errorf("Invalid close idle timeout(Measured in minutes) %v", u)
|
||||
}
|
||||
}
|
||||
|
||||
//SetCloseIdleTimeMs sets the time to wait before closing tunnels to idle levels in milliseconds
|
||||
// SetCloseIdleTimeMs sets the time to wait before closing tunnels to idle levels in milliseconds
|
||||
func SetCloseIdleTimeMs(u int) func(*SAMEmit) error {
|
||||
return func(c *SAMEmit) error {
|
||||
c.I2PConfig.CloseIdleTime = "300000"
|
||||
if u >= 300000 {
|
||||
c.I2PConfig.CloseIdleTime = strconv.Itoa(u)
|
||||
log.WithField("closeIdleTimeMs", u).Debug("Set close idle time in milliseconds")
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Invalid close idle timeout(Measured in milliseconds) %v", u)
|
||||
}
|
||||
}
|
||||
|
||||
//SetAccessListType tells the system to treat the AccessList as a whitelist
|
||||
// SetAccessListType tells the system to treat the AccessList as a whitelist
|
||||
func SetAccessListType(s string) func(*SAMEmit) error {
|
||||
return func(c *SAMEmit) error {
|
||||
if s == "whitelist" {
|
||||
c.I2PConfig.AccessListType = "whitelist"
|
||||
log.Debug("Set access list type to whitelist")
|
||||
return nil
|
||||
} else if s == "blacklist" {
|
||||
c.I2PConfig.AccessListType = "blacklist"
|
||||
log.Debug("Set access list type to blacklist")
|
||||
return nil
|
||||
} else if s == "none" {
|
||||
c.I2PConfig.AccessListType = ""
|
||||
log.Debug("Set access list type to none")
|
||||
return nil
|
||||
} else if s == "" {
|
||||
c.I2PConfig.AccessListType = ""
|
||||
log.Debug("Set access list type to none")
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Invalid Access list type(whitelist, blacklist, none)")
|
||||
}
|
||||
}
|
||||
|
||||
//SetAccessList tells the system to treat the AccessList as a whitelist
|
||||
// SetAccessList tells the system to treat the AccessList as a whitelist
|
||||
func SetAccessList(s []string) func(*SAMEmit) error {
|
||||
return func(c *SAMEmit) error {
|
||||
if len(s) > 0 {
|
||||
for _, a := range s {
|
||||
c.I2PConfig.AccessList = append(c.I2PConfig.AccessList, a)
|
||||
}
|
||||
log.WithField("accessList", s).Debug("Set access list")
|
||||
return nil
|
||||
}
|
||||
log.Debug("No access list set (empty list provided)")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
71
emit.go
71
emit.go
@@ -2,7 +2,9 @@ package sam3
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"github.com/sirupsen/logrus"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type SAMEmit struct {
|
||||
@@ -10,15 +12,15 @@ type SAMEmit struct {
|
||||
}
|
||||
|
||||
func (e *SAMEmit) OptStr() string {
|
||||
optStr := ""
|
||||
for _, opt := range e.I2PConfig.Print() {
|
||||
optStr += opt + " "
|
||||
}
|
||||
optStr := strings.Join(e.I2PConfig.Print(), " ")
|
||||
log.WithField("optStr", optStr).Debug("Generated option string")
|
||||
return optStr
|
||||
}
|
||||
|
||||
func (e *SAMEmit) Hello() string {
|
||||
return fmt.Sprintf("HELLO VERSION MIN=%s MAX=%s \n", e.I2PConfig.MinSAM(), e.I2PConfig.MaxSAM())
|
||||
hello := fmt.Sprintf("HELLO VERSION MIN=%s MAX=%s \n", e.I2PConfig.MinSAM(), e.I2PConfig.MaxSAM())
|
||||
log.WithField("hello", hello).Debug("Generated HELLO command")
|
||||
return hello
|
||||
}
|
||||
|
||||
func (e *SAMEmit) HelloBytes() []byte {
|
||||
@@ -26,7 +28,9 @@ func (e *SAMEmit) HelloBytes() []byte {
|
||||
}
|
||||
|
||||
func (e *SAMEmit) GenerateDestination() string {
|
||||
return fmt.Sprintf("DEST GENERATE %s \n", e.I2PConfig.SignatureType())
|
||||
dest := fmt.Sprintf("DEST GENERATE %s \n", e.I2PConfig.SignatureType())
|
||||
log.WithField("destination", dest).Debug("Generated DEST GENERATE command")
|
||||
return dest
|
||||
}
|
||||
|
||||
func (e *SAMEmit) GenerateDestinationBytes() []byte {
|
||||
@@ -34,7 +38,9 @@ func (e *SAMEmit) GenerateDestinationBytes() []byte {
|
||||
}
|
||||
|
||||
func (e *SAMEmit) Lookup(name string) string {
|
||||
return fmt.Sprintf("NAMING LOOKUP NAME=%s \n", name)
|
||||
lookup := fmt.Sprintf("NAMING LOOKUP NAME=%s \n", name)
|
||||
log.WithField("lookup", lookup).Debug("Generated NAMING LOOKUP command")
|
||||
return lookup
|
||||
}
|
||||
|
||||
func (e *SAMEmit) LookupBytes(name string) []byte {
|
||||
@@ -42,32 +48,36 @@ func (e *SAMEmit) LookupBytes(name string) []byte {
|
||||
}
|
||||
|
||||
func (e *SAMEmit) Create() string {
|
||||
return fmt.Sprintf(
|
||||
create := fmt.Sprintf(
|
||||
// //1 2 3 4 5 6 7
|
||||
"SESSION CREATE %s%s%s%s%s%s%s \n",
|
||||
e.I2PConfig.SessionStyle(), //1
|
||||
e.I2PConfig.FromPort(), //2
|
||||
e.I2PConfig.ToPort(), //3
|
||||
e.I2PConfig.ID(), //4
|
||||
e.I2PConfig.DestinationKey(), // 5
|
||||
e.I2PConfig.SignatureType(), // 6
|
||||
e.OptStr(), // 7
|
||||
e.I2PConfig.DestinationKey(), //5
|
||||
e.I2PConfig.SignatureType(), //6
|
||||
e.OptStr(), //7
|
||||
)
|
||||
log.WithField("create", create).Debug("Generated SESSION CREATE command")
|
||||
return create
|
||||
}
|
||||
|
||||
func (e *SAMEmit) CreateBytes() []byte {
|
||||
log.Println("sam command: " + e.Create())
|
||||
fmt.Println("sam command: " + e.Create())
|
||||
return []byte(e.Create())
|
||||
}
|
||||
|
||||
func (e *SAMEmit) Connect(dest string) string {
|
||||
return fmt.Sprintf(
|
||||
connect := fmt.Sprintf(
|
||||
"STREAM CONNECT ID=%s %s %s DESTINATION=%s \n",
|
||||
e.I2PConfig.ID(),
|
||||
e.I2PConfig.FromPort(),
|
||||
e.I2PConfig.ToPort(),
|
||||
dest,
|
||||
)
|
||||
log.WithField("connect", connect).Debug("Generated STREAM CONNECT command")
|
||||
return connect
|
||||
}
|
||||
|
||||
func (e *SAMEmit) ConnectBytes(dest string) []byte {
|
||||
@@ -75,12 +85,14 @@ func (e *SAMEmit) ConnectBytes(dest string) []byte {
|
||||
}
|
||||
|
||||
func (e *SAMEmit) Accept() string {
|
||||
return fmt.Sprintf(
|
||||
accept := fmt.Sprintf(
|
||||
"STREAM ACCEPT ID=%s %s %s",
|
||||
e.I2PConfig.ID(),
|
||||
e.I2PConfig.FromPort(),
|
||||
e.I2PConfig.ToPort(),
|
||||
)
|
||||
log.WithField("accept", accept).Debug("Generated STREAM ACCEPT command")
|
||||
return accept
|
||||
}
|
||||
|
||||
func (e *SAMEmit) AcceptBytes() []byte {
|
||||
@@ -91,8 +103,37 @@ func NewEmit(opts ...func(*SAMEmit) error) (*SAMEmit, error) {
|
||||
var emit SAMEmit
|
||||
for _, o := range opts {
|
||||
if err := o(&emit); err != nil {
|
||||
log.WithError(err).Error("Failed to apply option")
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
log.Debug("New SAMEmit instance created")
|
||||
return &emit, nil
|
||||
}
|
||||
|
||||
func IgnorePortError(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if strings.Contains(err.Error(), "missing port in address") {
|
||||
log.Debug("Ignoring 'missing port in address' error")
|
||||
err = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func SplitHostPort(hostport string) (string, string, error) {
|
||||
host, port, err := net.SplitHostPort(hostport)
|
||||
if err != nil {
|
||||
if IgnorePortError(err) == nil {
|
||||
log.WithField("host", hostport).Debug("Using full string as host, port set to 0")
|
||||
host = hostport
|
||||
port = "0"
|
||||
}
|
||||
}
|
||||
log.WithFields(logrus.Fields{
|
||||
"host": host,
|
||||
"port": port,
|
||||
}).Debug("Split host and port")
|
||||
return host, port, nil
|
||||
}
|
||||
|
7
go.mod
7
go.mod
@@ -1,4 +1,9 @@
|
||||
module github.com/eyedeekay/sam3
|
||||
module github.com/go-i2p/sam3
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/go-i2p/i2pkeys v0.0.0-20241108200332-e4f5ccdff8c4
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
golang.org/x/sys v0.27.0 // indirect
|
||||
)
|
||||
|
19
go.sum
Normal file
19
go.sum
Normal file
@@ -0,0 +1,19 @@
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-i2p/i2pkeys v0.0.0-20241108200332-e4f5ccdff8c4 h1:LRjaRCzg1ieGKZjELlaIg06Fx04RHzQLsWMYp1H6PQ4=
|
||||
github.com/go-i2p/i2pkeys v0.0.0-20241108200332-e4f5ccdff8c4/go.mod h1:m5TlHjPZrU5KbTd7Lr+I2rljyC6aJ88HdkeMQXV0U0E=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
||||
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
119
helper/helper.go
119
helper/helper.go
@@ -6,12 +6,15 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/eyedeekay/sam3"
|
||||
"github.com/eyedeekay/sam3/i2pkeys"
|
||||
"github.com/go-i2p/i2pkeys"
|
||||
"github.com/go-i2p/sam3"
|
||||
)
|
||||
|
||||
// HEY! If you're looking at this, there's a good chance that `github.com/go-i2p/onramp`
|
||||
// is a better fit! Check it out.
|
||||
|
||||
func NetListener(name, samaddr, keyspath string) (net.Listener, error) {
|
||||
return I2PListener(name, samaddr, keyspath)
|
||||
return I2PListener(name, sam3.SAMDefaultAddr(samaddr), keyspath)
|
||||
}
|
||||
|
||||
// I2PListener is a convenience function which takes a SAM tunnel name, a SAM address and a filename.
|
||||
@@ -19,57 +22,82 @@ func NetListener(name, samaddr, keyspath string) (net.Listener, error) {
|
||||
// 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...")
|
||||
sam, err := sam3.NewSAM(samaddr)
|
||||
listener, err := I2PStreamSession(name, sam3.SAMDefaultAddr(samaddr), keyspath)
|
||||
if err != nil {
|
||||
log.Fatalf("error connecting to SAM to %s: %s", samaddr, err)
|
||||
return nil, err
|
||||
}
|
||||
var keys *i2pkeys.I2PKeys
|
||||
if _, err := os.Stat(keyspath + ".i2p.private"); os.IsNotExist(err) {
|
||||
f, err := os.Create(keyspath + ".i2p.private")
|
||||
if keyspath != "" {
|
||||
err = ioutil.WriteFile(keyspath+".i2p.public.txt", []byte(listener.Keys().Addr().Base32()), 0644)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to open I2P keyfile for writing: %s", err)
|
||||
log.Fatalf("error storing I2P base32 address in adjacent text file, %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
|
||||
}
|
||||
stream, err := sam.NewStreamSession(name, *keys, sam3.Options_Medium)
|
||||
if err != nil {
|
||||
log.Fatalf("error creating I2P streaming connection %s: %s, %s.", name, err, *keys)
|
||||
}
|
||||
listener, err := stream.Listen()
|
||||
|
||||
err = ioutil.WriteFile(keyspath+".i2p.public.txt", []byte(keys.Addr().Base32()), 0644)
|
||||
if err != nil {
|
||||
log.Fatalf("error storing I2P base32 address in adjacent text file, %s", err)
|
||||
|
||||
}
|
||||
return listener, 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 service, please wait a couple of minutes...")
|
||||
sam, err := sam3.NewSAM(samaddr)
|
||||
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", samaddr, err)
|
||||
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
|
||||
}
|
||||
}
|
||||
var keys *i2pkeys.I2PKeys
|
||||
if _, err := os.Stat(keyspath + ".i2p.private"); os.IsNotExist(err) {
|
||||
f, err := os.Create(keyspath + ".i2p.private")
|
||||
if err != nil {
|
||||
@@ -92,6 +120,11 @@ func I2PStreamSession(name, samaddr, keyspath string) (*sam3.StreamSession, erro
|
||||
}
|
||||
keys = &tkeys
|
||||
}
|
||||
stream, err := sam.NewStreamSession(name, *keys, sam3.Options_Medium)
|
||||
return stream, err
|
||||
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)
|
||||
}
|
||||
|
@@ -1,235 +0,0 @@
|
||||
package i2pkeys
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/base32"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
i2pB64enc *base64.Encoding = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-~")
|
||||
i2pB32enc *base32.Encoding = base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567")
|
||||
)
|
||||
|
||||
// The public and private keys associated with an I2P destination. I2P hides the
|
||||
// details of exactly what this is, so treat them as blobs, but generally: One
|
||||
// pair of DSA keys, one pair of ElGamal keys, and sometimes (almost never) also
|
||||
// a certificate. String() returns you the full content of I2PKeys and Addr()
|
||||
// returns the public keys.
|
||||
type I2PKeys struct {
|
||||
Address I2PAddr // only the public key
|
||||
Both string // both public and private keys
|
||||
}
|
||||
|
||||
// Creates I2PKeys from an I2PAddr and a public/private keypair string (as
|
||||
// generated by String().)
|
||||
func NewKeys(addr I2PAddr, both string) I2PKeys {
|
||||
return I2PKeys{addr, both}
|
||||
}
|
||||
|
||||
// fileExists checks if a file exists and is not a directory before we
|
||||
// try using it to prevent further errors.
|
||||
func fileExists(filename string) (bool, error) {
|
||||
info, err := os.Stat(filename)
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
} else if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return !info.IsDir(), nil
|
||||
}
|
||||
|
||||
// load keys from non standard format
|
||||
func LoadKeysIncompat(r io.Reader) (k I2PKeys, err error) {
|
||||
var buff bytes.Buffer
|
||||
_, err = io.Copy(&buff, r)
|
||||
if err == nil {
|
||||
parts := strings.Split(buff.String(), "\n")
|
||||
k = I2PKeys{I2PAddr(parts[0]), parts[1]}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// load keys from non-standard format by specifying a text file.
|
||||
// If the file does not exist, generate keys, otherwise, fail
|
||||
// closed.
|
||||
func LoadKeys(r string) (I2PKeys, error) {
|
||||
exists, err := fileExists(r)
|
||||
if err != nil {
|
||||
return I2PKeys{}, err
|
||||
}
|
||||
if exists {
|
||||
fi, err := os.Open(r)
|
||||
if err != nil {
|
||||
return I2PKeys{}, err
|
||||
}
|
||||
defer fi.Close()
|
||||
return LoadKeysIncompat(fi)
|
||||
}
|
||||
return I2PKeys{}, err
|
||||
}
|
||||
|
||||
// store keys in non standard format
|
||||
func StoreKeysIncompat(k I2PKeys, w io.Writer) (err error) {
|
||||
_, err = io.WriteString(w, k.Address.Base64()+"\n"+k.Both)
|
||||
return
|
||||
}
|
||||
|
||||
func StoreKeys(k I2PKeys, r string) error {
|
||||
fi, err := os.Open(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fi.Close()
|
||||
return StoreKeysIncompat(k, fi)
|
||||
}
|
||||
|
||||
// Returns the public keys of the I2PKeys.
|
||||
func (k I2PKeys) Addr() I2PAddr {
|
||||
return k.Address
|
||||
}
|
||||
|
||||
// Returns the keys (both public and private), in I2Ps base64 format. Use this
|
||||
// when you create sessions.
|
||||
func (k I2PKeys) String() string {
|
||||
return k.Both
|
||||
}
|
||||
|
||||
// I2PAddr represents an I2P destination, almost equivalent to an IP address.
|
||||
// This is the humongously huge base64 representation of such an address, which
|
||||
// really is just a pair of public keys and also maybe a certificate. (I2P hides
|
||||
// the details of exactly what it is. Read the I2P specifications for more info.)
|
||||
type I2PAddr string
|
||||
|
||||
// an i2p destination hash, the .b32.i2p address if you will
|
||||
type I2PDestHash [32]byte
|
||||
|
||||
// create a desthash from a string b32.i2p address
|
||||
func DestHashFromString(str string) (dhash I2PDestHash, err error) {
|
||||
if strings.HasSuffix(str, ".b32.i2p") && len(str) == 60 {
|
||||
// valid
|
||||
_, err = i2pB32enc.Decode(dhash[:], []byte(str[:52]+"===="))
|
||||
} else {
|
||||
// invalid
|
||||
err = errors.New("invalid desthash format")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// get string representation of i2p dest hash(base32 version)
|
||||
func (h I2PDestHash) String() string {
|
||||
b32addr := make([]byte, 56)
|
||||
i2pB32enc.Encode(b32addr, h[:])
|
||||
return string(b32addr[:52]) + ".b32.i2p"
|
||||
}
|
||||
|
||||
// get base64 representation of i2p dest sha256 hash(the 44-character one)
|
||||
func (h I2PDestHash) Hash() string {
|
||||
hash := sha256.New()
|
||||
hash.Write(h[:])
|
||||
digest := hash.Sum(nil)
|
||||
buf := make([]byte, 44)
|
||||
i2pB64enc.Encode(buf, digest)
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
// Returns "I2P"
|
||||
func (h *I2PDestHash) Network() string {
|
||||
return "I2P"
|
||||
}
|
||||
|
||||
// Returns the base64 representation of the I2PAddr
|
||||
func (a I2PAddr) Base64() string {
|
||||
return string(a)
|
||||
}
|
||||
|
||||
// Returns the I2P destination (base64-encoded)
|
||||
func (a I2PAddr) String() string {
|
||||
return string(a)
|
||||
}
|
||||
|
||||
// Returns "I2P"
|
||||
func (a I2PAddr) Network() string {
|
||||
return "I2P"
|
||||
}
|
||||
|
||||
// Creates a new I2P address from a base64-encoded string. Checks if the address
|
||||
// addr is in correct format. (If you know for sure it is, use I2PAddr(addr).)
|
||||
func NewI2PAddrFromString(addr string) (I2PAddr, error) {
|
||||
if strings.HasSuffix(addr, ".i2p") {
|
||||
if strings.HasSuffix(addr, ".b32.i2p") {
|
||||
return I2PAddr(""), errors.New("cannot convert .b32.i2p to full destination")
|
||||
}
|
||||
// strip off .i2p if it's there
|
||||
addr = addr[:len(addr)-4]
|
||||
}
|
||||
addr = strings.Trim(addr, "\t\n\r\f ")
|
||||
// very basic check
|
||||
if len(addr) > 4096 || len(addr) < 516 {
|
||||
return I2PAddr(""), errors.New("Not an I2P address")
|
||||
}
|
||||
buf := make([]byte, i2pB64enc.DecodedLen(len(addr)))
|
||||
if _, err := i2pB64enc.Decode(buf, []byte(addr)); err != nil {
|
||||
return I2PAddr(""), errors.New("Address is not base64-encoded")
|
||||
}
|
||||
return I2PAddr(addr), nil
|
||||
}
|
||||
|
||||
func FiveHundredAs() I2PAddr {
|
||||
s := ""
|
||||
for x := 0; x < 517; x++ {
|
||||
s += "A"
|
||||
}
|
||||
r, _ := NewI2PAddrFromString(s)
|
||||
return r
|
||||
}
|
||||
|
||||
// Creates a new I2P address from a byte array. The inverse of ToBytes().
|
||||
func NewI2PAddrFromBytes(addr []byte) (I2PAddr, error) {
|
||||
if len(addr) > 4096 || len(addr) < 384 {
|
||||
return I2PAddr(""), errors.New("Not an I2P address")
|
||||
}
|
||||
buf := make([]byte, i2pB64enc.EncodedLen(len(addr)))
|
||||
i2pB64enc.Encode(buf, addr)
|
||||
return I2PAddr(string(buf)), nil
|
||||
}
|
||||
|
||||
// Turns an I2P address to a byte array. The inverse of NewI2PAddrFromBytes().
|
||||
func (addr I2PAddr) ToBytes() ([]byte, error) {
|
||||
return i2pB64enc.DecodeString(string(addr))
|
||||
}
|
||||
|
||||
func (addr I2PAddr) Bytes() []byte {
|
||||
b, _ := addr.ToBytes()
|
||||
return b
|
||||
}
|
||||
|
||||
// Returns the *.b32.i2p address of the I2P address. It is supposed to be a
|
||||
// somewhat human-manageable 64 character long pseudo-domain name equivalent of
|
||||
// the 516+ characters long default base64-address (the I2PAddr format). It is
|
||||
// not possible to turn the base32-address back into a usable I2PAddr without
|
||||
// performing a Lookup(). Lookup only works if you are using the I2PAddr from
|
||||
// which the b32 address was generated.
|
||||
func (addr I2PAddr) Base32() (str string) {
|
||||
return addr.DestHash().String()
|
||||
}
|
||||
|
||||
func (addr I2PAddr) DestHash() (h I2PDestHash) {
|
||||
hash := sha256.New()
|
||||
b, _ := addr.ToBytes()
|
||||
hash.Write(b)
|
||||
digest := hash.Sum(nil)
|
||||
copy(h[:], digest)
|
||||
return
|
||||
}
|
||||
|
||||
// Makes any string into a *.b32.i2p human-readable I2P address. This makes no
|
||||
// sense, unless "anything" is an I2P destination of some sort.
|
||||
func Base32(anything string) string {
|
||||
return I2PAddr(anything).Base32()
|
||||
}
|
BIN
i2plogo.png
Normal file
BIN
i2plogo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
331
index.html
Normal file
331
index.html
Normal file
@@ -0,0 +1,331 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>
|
||||
README
|
||||
</title>
|
||||
<meta name="author" content="eyedeekay" />
|
||||
<meta name="description" content="sam3" />
|
||||
<meta name="keywords" content="master" />
|
||||
<link rel="stylesheet" type="text/css" href="style.css" />
|
||||
<link rel="stylesheet" type="text/css" href="showhider.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="navbar">
|
||||
<a href="#shownav">
|
||||
Show navigation
|
||||
</a>
|
||||
<div id="shownav">
|
||||
<div id="hidenav">
|
||||
<ul>
|
||||
<li>
|
||||
<a href="index.html">
|
||||
index
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<br>
|
||||
<a href="#hidenav">
|
||||
Hide Navigation
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h1>
|
||||
<a href="#readme" rel="nofollow">
|
||||
<span></span>
|
||||
</a>
|
||||
README
|
||||
</h1>
|
||||
<h2>
|
||||
<a href="#important" rel="nofollow">
|
||||
<span></span>
|
||||
</a>
|
||||
!!IMPORTANT!!
|
||||
</h2>
|
||||
<p>
|
||||
In the next version, I'll be moving the
|
||||
<code>
|
||||
i2pkeys
|
||||
</code>
|
||||
directory to it's own repository
|
||||
so I can avoid import cycle headaches. Please migrate to the new
|
||||
<code>
|
||||
i2pkeys
|
||||
</code>
|
||||
repository
|
||||
before upgrading your sam3 dependencies. You can probably do this by running:
|
||||
</p>
|
||||
<div>
|
||||
<pre>find . -name '*.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>
|
||||
<a href="#readme" rel="nofollow">
|
||||
<span></span>
|
||||
</a>
|
||||
README
|
||||
</h1>
|
||||
<p>
|
||||
go library for the I2P
|
||||
<a href="https://geti2p.net/en/docs/api/samv3" rel="nofollow">
|
||||
SAMv3.0
|
||||
</a>
|
||||
bridge, used to build anonymous/pseudonymous end-to-end encrypted sockets.
|
||||
</p>
|
||||
<p>
|
||||
This library is much better than ccondom (that use BOB), much more stable and much easier to maintain.
|
||||
</p>
|
||||
<h2>
|
||||
<a href="#support-todo" rel="nofollow">
|
||||
<span></span>
|
||||
</a>
|
||||
Support/TODO
|
||||
</h2>
|
||||
<p>
|
||||
<strong>
|
||||
What works:
|
||||
</strong>
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
Utils
|
||||
<ul>
|
||||
<li>
|
||||
Resolving domain names to I2P destinations
|
||||
</li>
|
||||
<li>
|
||||
.b32.i2p hashes
|
||||
</li>
|
||||
<li>
|
||||
Generating keys/i2p destinations
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Streaming
|
||||
<ul>
|
||||
<li>
|
||||
DialI2P() - Connecting to stuff in I2P
|
||||
</li>
|
||||
<li>
|
||||
Listen()/Accept() - Handling incomming connections
|
||||
</li>
|
||||
<li>
|
||||
Implements net.Conn and net.Listener
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Datagrams
|
||||
<ul>
|
||||
<li>
|
||||
Implements net.PacketConn
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Raw datagrams
|
||||
<ul>
|
||||
<li>
|
||||
Like datagrams, but without addresses
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
<strong>
|
||||
Does not work:
|
||||
</strong>
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
Stream Forwarding
|
||||
</li>
|
||||
<li>
|
||||
Probably needs some real-world testing
|
||||
</li>
|
||||
</ul>
|
||||
<h2>
|
||||
<a href="#documentation" rel="nofollow">
|
||||
<span></span>
|
||||
</a>
|
||||
Documentation
|
||||
</h2>
|
||||
<ul>
|
||||
<li>
|
||||
Latest version-documentation:
|
||||
<ul>
|
||||
<li>
|
||||
set your GOPATH
|
||||
</li>
|
||||
<li>
|
||||
Enter
|
||||
<code>
|
||||
godoc -http=:8081
|
||||
</code>
|
||||
into your terminal and hit enter.
|
||||
</li>
|
||||
<li>
|
||||
Goto
|
||||
<a href="http://localhost:8081" rel="nofollow">
|
||||
http://localhost:8081
|
||||
</a>
|
||||
, click packages, and navigate to sam3
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<h2>
|
||||
<a href="#examples" rel="nofollow">
|
||||
<span></span>
|
||||
</a>
|
||||
Examples
|
||||
</h2>
|
||||
<div>
|
||||
<pre>package main
|
||||
|
||||
import (
|
||||
"github.com/eyedeekay/sam3"
|
||||
"github.com/eyedeekay/sam3/i2pkeys"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const yoursam = "127.0.0.1:7656" // sam bridge
|
||||
|
||||
func client(server i2pkeys.I2PAddr) {
|
||||
sam, _ := sam3.NewSAM(yoursam)
|
||||
keys, _ := sam.NewKeys()
|
||||
stream, _ := sam.NewStreamSession("clientTun", keys, sam3.Options_Small)
|
||||
fmt.Println("Client: Connecting to " + server.Base32())
|
||||
conn, _ := stream.DialI2P(server)
|
||||
conn.Write([]byte("Hello world!"))
|
||||
return
|
||||
}
|
||||
|
||||
func main() {
|
||||
sam, _ := NewSAM(yoursam)
|
||||
keys, _ := sam.NewKeys()
|
||||
stream, _ := sam.NewStreamSession("serverTun", keys, sam3.Options_Medium)
|
||||
listener, _ := stream.Listen()
|
||||
go client(keys.Addr())
|
||||
conn, _ := listener.Accept()
|
||||
buf := make([]byte, 4096)
|
||||
n, _ := conn.Read(buf)
|
||||
fmt.Println("Server received: " + string(buf[:n]))
|
||||
}
|
||||
</pre>
|
||||
</div>
|
||||
<p>
|
||||
The above will write to the terminal:
|
||||
</p>
|
||||
<div>
|
||||
<pre>Client: Connecting to zjnvfh4hs3et5vtz35ogwzrws26zvwkcad5uo5esecvg4qpk5b4a.b32.i2p
|
||||
Server received: Hello world!
|
||||
</pre>
|
||||
</div>
|
||||
<p>
|
||||
Error handling was omitted in the above code for readability.
|
||||
</p>
|
||||
<h2>
|
||||
<a href="#testing" rel="nofollow">
|
||||
<span></span>
|
||||
</a>
|
||||
Testing
|
||||
</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<code>
|
||||
go test -tags=nettest
|
||||
</code>
|
||||
runs the whole suite (takes 90+ sec to perform!)
|
||||
</li>
|
||||
<li>
|
||||
<code>
|
||||
go test -short
|
||||
</code>
|
||||
runs the shorter variant, does not connect to anything
|
||||
</li>
|
||||
</ul>
|
||||
<h2>
|
||||
<a href="#license" rel="nofollow">
|
||||
<span></span>
|
||||
</a>
|
||||
License
|
||||
</h2>
|
||||
<p>
|
||||
Public domain.
|
||||
</p>
|
||||
<h2>
|
||||
<a href="#author" rel="nofollow">
|
||||
<span></span>
|
||||
</a>
|
||||
Author
|
||||
</h2>
|
||||
<ul>
|
||||
<li>
|
||||
Kalle Vedin
|
||||
<code>
|
||||
kalle.vedin@fripost.org
|
||||
</code>
|
||||
</li>
|
||||
<li>
|
||||
Unknown Name (majestrate)
|
||||
</li>
|
||||
<li>
|
||||
idk
|
||||
</li>
|
||||
<li>
|
||||
qiwenmin
|
||||
</li>
|
||||
</ul>
|
||||
<div>
|
||||
<a href="#show">
|
||||
Show license
|
||||
</a>
|
||||
<div id="show">
|
||||
<div id="hide">
|
||||
<pre><code>This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org/>
|
||||
</code></pre>
|
||||
<a href="#hide">
|
||||
Hide license
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<iframe src="https://snowflake.torproject.org/embed.html" width="320" height="240" frameborder="0" scrolling="no"></iframe>
|
||||
</div>
|
||||
<div>
|
||||
<a href="https://geti2p.net/">
|
||||
<img src="i2plogo.png"></img>
|
||||
I2P
|
||||
</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
50
log.go
Normal file
50
log.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package sam3
|
||||
|
||||
import (
|
||||
"github.com/sirupsen/logrus"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
log *logrus.Logger
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
func InitializeSAM3Logger() {
|
||||
once.Do(func() {
|
||||
log = logrus.New()
|
||||
// We do not want to log by default
|
||||
log.SetOutput(ioutil.Discard)
|
||||
log.SetLevel(logrus.PanicLevel)
|
||||
// Check if DEBUG_I2P is set
|
||||
if logLevel := os.Getenv("DEBUG_I2P"); logLevel != "" {
|
||||
log.SetOutput(os.Stdout)
|
||||
switch strings.ToLower(logLevel) {
|
||||
case "debug":
|
||||
log.SetLevel(logrus.DebugLevel)
|
||||
case "warn":
|
||||
log.SetLevel(logrus.WarnLevel)
|
||||
case "error":
|
||||
log.SetLevel(logrus.ErrorLevel)
|
||||
default:
|
||||
log.SetLevel(logrus.DebugLevel)
|
||||
}
|
||||
log.WithField("level", log.GetLevel()).Debug("Logging enabled.")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// GetSAM3Logger returns the initialized logger
|
||||
func GetSAM3Logger() *logrus.Logger {
|
||||
if log == nil {
|
||||
InitializeSAM3Logger()
|
||||
}
|
||||
return log
|
||||
}
|
||||
|
||||
func init() {
|
||||
InitializeSAM3Logger()
|
||||
}
|
482
primary.go
Normal file
482
primary.go
Normal file
@@ -0,0 +1,482 @@
|
||||
package sam3
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/sirupsen/logrus"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-i2p/i2pkeys"
|
||||
)
|
||||
|
||||
const (
|
||||
session_ADDOK = "SESSION STATUS RESULT=OK"
|
||||
)
|
||||
|
||||
func randport() string {
|
||||
s := rand.NewSource(time.Now().UnixNano())
|
||||
r := rand.New(s)
|
||||
p := r.Intn(55534) + 10000
|
||||
port := strconv.Itoa(p)
|
||||
log.WithField("port", port).Debug("Generated random port")
|
||||
return strconv.Itoa(p)
|
||||
}
|
||||
|
||||
// Represents a primary session.
|
||||
type PrimarySession struct {
|
||||
samAddr string // address to the sam bridge (ipv4:port)
|
||||
id string // tunnel name
|
||||
conn net.Conn // connection to sam
|
||||
keys i2pkeys.I2PKeys // i2p destination keys
|
||||
Timeout time.Duration
|
||||
Deadline time.Time
|
||||
sigType string
|
||||
Config SAMEmit
|
||||
stsess map[string]*StreamSession
|
||||
dgsess map[string]*DatagramSession
|
||||
// from string
|
||||
// to string
|
||||
}
|
||||
|
||||
func (ss *PrimarySession) From() string {
|
||||
return "0"
|
||||
}
|
||||
|
||||
func (ss *PrimarySession) To() string {
|
||||
return "0"
|
||||
}
|
||||
|
||||
func (ss *PrimarySession) SignatureType() string {
|
||||
return ss.sigType
|
||||
}
|
||||
|
||||
// Returns the local tunnel name of the I2P tunnel used for the stream session
|
||||
func (ss *PrimarySession) ID() string {
|
||||
return ss.id
|
||||
}
|
||||
|
||||
func (ss *PrimarySession) Close() error {
|
||||
return ss.conn.Close()
|
||||
}
|
||||
|
||||
// Returns the I2P destination (the address) of the stream session
|
||||
func (ss *PrimarySession) Addr() i2pkeys.I2PAddr {
|
||||
return ss.keys.Addr()
|
||||
}
|
||||
|
||||
func (ss *PrimarySession) LocalAddr() net.Addr {
|
||||
aa := ss.keys.Addr()
|
||||
return &aa
|
||||
}
|
||||
|
||||
// Returns the keys associated with the stream session
|
||||
func (ss *PrimarySession) Keys() i2pkeys.I2PKeys {
|
||||
return ss.keys
|
||||
}
|
||||
|
||||
func (sam *PrimarySession) Dial(network, addr string) (net.Conn, error) {
|
||||
log.WithFields(logrus.Fields{"network": network, "addr": addr}).Debug("Dial() called")
|
||||
if network == "udp" || network == "udp4" || network == "udp6" {
|
||||
//return sam.DialUDPI2P(network, network+addr[0:4], addr)
|
||||
return sam.DialUDPI2P(network, network+addr[0:4], addr)
|
||||
}
|
||||
if network == "tcp" || network == "tcp4" || network == "tcp6" {
|
||||
//return sam.DialTCPI2P(network, network+addr[0:4], addr)
|
||||
return sam.DialTCPI2P(network, network+addr[0:4], addr)
|
||||
}
|
||||
log.WithField("network", network).Error("Invalid network type")
|
||||
return nil, fmt.Errorf("Error: Must specify a valid network type")
|
||||
}
|
||||
|
||||
// DialTCP implements x/dialer
|
||||
func (sam *PrimarySession) DialTCP(network string, laddr, raddr net.Addr) (net.Conn, error) {
|
||||
log.WithFields(logrus.Fields{"network": network, "laddr": laddr, "raddr": raddr}).Debug("DialTCP() called")
|
||||
ts, ok := sam.stsess[network+raddr.String()[0:4]]
|
||||
var err error
|
||||
if !ok {
|
||||
ts, err = sam.NewUniqueStreamSubSession(network + raddr.String()[0:4])
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to create new unique stream sub-session")
|
||||
return nil, err
|
||||
}
|
||||
sam.stsess[network+raddr.String()[0:4]] = ts
|
||||
ts, _ = sam.stsess[network+raddr.String()[0:4]]
|
||||
}
|
||||
return ts.Dial(network, raddr.String())
|
||||
}
|
||||
|
||||
func (sam *PrimarySession) DialTCPI2P(network string, laddr, raddr string) (net.Conn, error) {
|
||||
log.WithFields(logrus.Fields{"network": network, "laddr": laddr, "raddr": raddr}).Debug("DialTCPI2P() called")
|
||||
ts, ok := sam.stsess[network+raddr[0:4]]
|
||||
var err error
|
||||
if !ok {
|
||||
ts, err = sam.NewUniqueStreamSubSession(network + laddr)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to create new unique stream sub-session")
|
||||
return nil, err
|
||||
}
|
||||
sam.stsess[network+raddr[0:4]] = ts
|
||||
ts, _ = sam.stsess[network+raddr[0:4]]
|
||||
}
|
||||
return ts.Dial(network, raddr)
|
||||
}
|
||||
|
||||
// DialUDP implements x/dialer
|
||||
func (sam *PrimarySession) DialUDP(network string, laddr, raddr net.Addr) (net.PacketConn, error) {
|
||||
log.WithFields(logrus.Fields{"network": network, "laddr": laddr, "raddr": raddr}).Debug("DialUDP() called")
|
||||
ds, ok := sam.dgsess[network+raddr.String()[0:4]]
|
||||
var err error
|
||||
if !ok {
|
||||
ds, err = sam.NewDatagramSubSession(network+raddr.String()[0:4], 0)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to create new datagram sub-session")
|
||||
return nil, err
|
||||
}
|
||||
sam.dgsess[network+raddr.String()[0:4]] = ds
|
||||
ds, _ = sam.dgsess[network+raddr.String()[0:4]]
|
||||
}
|
||||
return ds.Dial(network, raddr.String())
|
||||
}
|
||||
|
||||
func (sam *PrimarySession) DialUDPI2P(network, laddr, raddr string) (*DatagramSession, error) {
|
||||
log.WithFields(logrus.Fields{"network": network, "laddr": laddr, "raddr": raddr}).Debug("DialUDPI2P() called")
|
||||
ds, ok := sam.dgsess[network+raddr[0:4]]
|
||||
var err error
|
||||
if !ok {
|
||||
ds, err = sam.NewDatagramSubSession(network+laddr, 0)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to create new datagram sub-session")
|
||||
return nil, err
|
||||
}
|
||||
sam.dgsess[network+raddr[0:4]] = ds
|
||||
ds, _ = sam.dgsess[network+raddr[0:4]]
|
||||
}
|
||||
return ds.Dial(network, raddr)
|
||||
}
|
||||
|
||||
func (s *PrimarySession) Lookup(name string) (a net.Addr, err error) {
|
||||
log.WithField("name", name).Debug("Lookup() called")
|
||||
var sam *SAM
|
||||
name = strings.Split(name, ":")[0]
|
||||
sam, err = NewSAM(s.samAddr)
|
||||
if err == nil {
|
||||
log.WithField("addr", a).Debug("Lookup successful")
|
||||
defer sam.Close()
|
||||
a, err = sam.Lookup(name)
|
||||
}
|
||||
log.WithError(err).Error("Lookup failed")
|
||||
return
|
||||
}
|
||||
|
||||
func (sam *PrimarySession) Resolve(network, addr string) (net.Addr, error) {
|
||||
log.WithFields(logrus.Fields{"network": network, "addr": addr}).Debug("Resolve() called")
|
||||
return sam.Lookup(addr)
|
||||
}
|
||||
|
||||
func (sam *PrimarySession) ResolveTCPAddr(network, dest string) (net.Addr, error) {
|
||||
log.WithFields(logrus.Fields{"network": network, "dest": dest}).Debug("ResolveTCPAddr() called")
|
||||
return sam.Lookup(dest)
|
||||
}
|
||||
|
||||
func (sam *PrimarySession) ResolveUDPAddr(network, dest string) (net.Addr, error) {
|
||||
log.WithFields(logrus.Fields{"network": network, "dest": dest}).Debug("ResolveUDPAddr() called")
|
||||
return sam.Lookup(dest)
|
||||
}
|
||||
|
||||
// Creates a new PrimarySession with the I2CP- and streaminglib options as
|
||||
// specified. See the I2P documentation for a full list of options.
|
||||
func (sam *SAM) NewPrimarySession(id string, keys i2pkeys.I2PKeys, options []string) (*PrimarySession, error) {
|
||||
log.WithFields(logrus.Fields{"id": id, "options": options}).Debug("NewPrimarySession() called")
|
||||
return sam.newPrimarySession(PrimarySessionSwitch, id, keys, options)
|
||||
}
|
||||
|
||||
func (sam *SAM) newPrimarySession(primarySessionSwitch string, id string, keys i2pkeys.I2PKeys, options []string) (*PrimarySession, error) {
|
||||
log.WithFields(logrus.Fields{
|
||||
"primarySessionSwitch": primarySessionSwitch,
|
||||
"id": id,
|
||||
"options": options,
|
||||
}).Debug("newPrimarySession() called")
|
||||
|
||||
conn, err := sam.newGenericSession(primarySessionSwitch, id, keys, options, []string{})
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to create new generic session")
|
||||
return nil, err
|
||||
}
|
||||
ssesss := make(map[string]*StreamSession)
|
||||
dsesss := make(map[string]*DatagramSession)
|
||||
return &PrimarySession{sam.Config.I2PConfig.Sam(), id, conn, keys, time.Duration(600 * time.Second), time.Now(), Sig_NONE, sam.Config, ssesss, dsesss}, nil
|
||||
}
|
||||
|
||||
// Creates a new PrimarySession with the I2CP- and PRIMARYinglib options as
|
||||
// specified. See the I2P documentation for a full list of options.
|
||||
func (sam *SAM) NewPrimarySessionWithSignature(id string, keys i2pkeys.I2PKeys, options []string, sigType string) (*PrimarySession, error) {
|
||||
log.WithFields(logrus.Fields{
|
||||
"id": id,
|
||||
"options": options,
|
||||
"sigType": sigType,
|
||||
}).Debug("NewPrimarySessionWithSignature() called")
|
||||
|
||||
conn, err := sam.newGenericSessionWithSignature(PrimarySessionSwitch, id, keys, sigType, options, []string{})
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to create new generic session with signature")
|
||||
return nil, err
|
||||
}
|
||||
ssesss := make(map[string]*StreamSession)
|
||||
dsesss := make(map[string]*DatagramSession)
|
||||
return &PrimarySession{sam.Config.I2PConfig.Sam(), id, conn, keys, time.Duration(600 * time.Second), time.Now(), sigType, sam.Config, ssesss, dsesss}, nil
|
||||
}
|
||||
|
||||
// Creates a new session with the style of either "STREAM", "DATAGRAM" or "RAW",
|
||||
// for a new I2P tunnel with name id, using the cypher keys specified, with the
|
||||
// I2CP/streaminglib-options as specified. Extra arguments can be specified by
|
||||
// setting extra to something else than []string{}.
|
||||
// This sam3 instance is now a session
|
||||
func (sam *PrimarySession) newGenericSubSession(style, id string, extras []string) (net.Conn, error) {
|
||||
log.WithFields(logrus.Fields{"style": style, "id": id, "extras": extras}).Debug("newGenericSubSession called")
|
||||
return sam.newGenericSubSessionWithSignature(style, id, extras)
|
||||
}
|
||||
|
||||
func (sam *PrimarySession) newGenericSubSessionWithSignature(style, id string, extras []string) (net.Conn, error) {
|
||||
log.WithFields(logrus.Fields{"style": style, "id": id, "extras": extras}).Debug("newGenericSubSessionWithSignature called")
|
||||
return sam.newGenericSubSessionWithSignatureAndPorts(style, id, "0", "0", extras)
|
||||
}
|
||||
|
||||
// Creates a new session with the style of either "STREAM", "DATAGRAM" or "RAW",
|
||||
// for a new I2P tunnel with name id, using the cypher keys specified, with the
|
||||
// I2CP/streaminglib-options as specified. Extra arguments can be specified by
|
||||
// setting extra to something else than []string{}.
|
||||
// This sam3 instance is now a session
|
||||
func (sam *PrimarySession) newGenericSubSessionWithSignatureAndPorts(style, id, from, to string, extras []string) (net.Conn, error) {
|
||||
log.WithFields(logrus.Fields{"style": style, "id": id, "from": from, "to": to, "extras": extras}).Debug("newGenericSubSessionWithSignatureAndPorts called")
|
||||
|
||||
conn := sam.conn
|
||||
fp := ""
|
||||
tp := ""
|
||||
if from != "0" && from != "" {
|
||||
fp = " FROM_PORT=" + from
|
||||
}
|
||||
if to != "0" && to != "" {
|
||||
tp = " TO_PORT=" + to
|
||||
}
|
||||
scmsg := []byte("SESSION ADD STYLE=" + style + " ID=" + id + fp + tp + " " + strings.Join(extras, " ") + "\n")
|
||||
|
||||
log.WithField("message", string(scmsg)).Debug("Sending SESSION ADD message")
|
||||
|
||||
for m, i := 0, 0; m != len(scmsg); i++ {
|
||||
if i == 15 {
|
||||
conn.Close()
|
||||
log.Error("Writing to SAM failed after 15 attempts")
|
||||
return nil, errors.New("writing to SAM failed")
|
||||
}
|
||||
n, err := conn.Write(scmsg[m:])
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to write to SAM connection")
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
m += n
|
||||
}
|
||||
buf := make([]byte, 4096)
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to read from SAM connection")
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
text := string(buf[:n])
|
||||
log.WithField("response", text).Debug("Received response from SAM")
|
||||
//log.Println("SAM:", text)
|
||||
if strings.HasPrefix(text, session_ADDOK) {
|
||||
//if sam.keys.String() != text[len(session_ADDOK):len(text)-1] {
|
||||
//conn.Close()
|
||||
//return nil, errors.New("SAMv3 created a tunnel with keys other than the ones we asked it for")
|
||||
//}
|
||||
log.Debug("Session added successfully")
|
||||
return conn, nil //&StreamSession{id, conn, keys, nil, sync.RWMutex{}, nil}, nil
|
||||
} else if text == session_DUPLICATE_ID {
|
||||
log.Error("Duplicate tunnel name")
|
||||
conn.Close()
|
||||
return nil, errors.New("Duplicate tunnel name")
|
||||
} else if text == session_DUPLICATE_DEST {
|
||||
log.Error("Duplicate destination")
|
||||
conn.Close()
|
||||
return nil, errors.New("Duplicate destination")
|
||||
} else if text == session_INVALID_KEY {
|
||||
log.Error("Invalid key - Primary Session")
|
||||
conn.Close()
|
||||
return nil, errors.New("Invalid key - Primary Session")
|
||||
} else if strings.HasPrefix(text, session_I2P_ERROR) {
|
||||
log.WithField("error", text[len(session_I2P_ERROR):]).Error("I2P error")
|
||||
conn.Close()
|
||||
return nil, errors.New("I2P error " + text[len(session_I2P_ERROR):])
|
||||
} else {
|
||||
log.WithField("reply", text).Error("Unable to parse SAMv3 reply")
|
||||
conn.Close()
|
||||
return nil, errors.New("Unable to parse SAMv3 reply: " + text)
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a new StreamSession with the I2CP- and streaminglib options as
|
||||
// specified. See the I2P documentation for a full list of options.
|
||||
func (sam *PrimarySession) NewStreamSubSession(id string) (*StreamSession, error) {
|
||||
log.WithField("id", id).Debug("NewStreamSubSession called")
|
||||
conn, err := sam.newGenericSubSession("STREAM", id, []string{})
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to create new generic sub-session")
|
||||
return nil, err
|
||||
}
|
||||
return &StreamSession{sam.Config.I2PConfig.Sam(), id, conn, sam.keys, time.Duration(600 * time.Second), time.Now(), Sig_NONE, "0", "0"}, nil
|
||||
}
|
||||
|
||||
// Creates a new StreamSession with the I2CP- and streaminglib options as
|
||||
// specified. See the I2P documentation for a full list of options.
|
||||
func (sam *PrimarySession) NewUniqueStreamSubSession(id string) (*StreamSession, error) {
|
||||
log.WithField("id", id).Debug("NewUniqueStreamSubSession called")
|
||||
conn, err := sam.newGenericSubSession("STREAM", id, []string{})
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to create new generic sub-session")
|
||||
return nil, err
|
||||
}
|
||||
fromPort, toPort := randport(), randport()
|
||||
log.WithFields(logrus.Fields{"fromPort": fromPort, "toPort": toPort}).Debug("Generated random ports")
|
||||
//return &StreamSession{sam.Config.I2PConfig.Sam(), id, conn, sam.keys, time.Duration(600 * time.Second), time.Now(), Sig_NONE, randport(), randport()}, nil
|
||||
return &StreamSession{sam.Config.I2PConfig.Sam(), id, conn, sam.keys, time.Duration(600 * time.Second), time.Now(), Sig_NONE, fromPort, toPort}, nil
|
||||
}
|
||||
|
||||
// Creates a new StreamSession with the I2CP- and streaminglib options as
|
||||
// specified. See the I2P documentation for a full list of options.
|
||||
func (sam *PrimarySession) NewStreamSubSessionWithPorts(id, from, to string) (*StreamSession, error) {
|
||||
log.WithFields(logrus.Fields{"id": id, "from": from, "to": to}).Debug("NewStreamSubSessionWithPorts called")
|
||||
conn, err := sam.newGenericSubSessionWithSignatureAndPorts("STREAM", id, from, to, []string{})
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to create new generic sub-session with signature and ports")
|
||||
return nil, err
|
||||
}
|
||||
return &StreamSession{sam.Config.I2PConfig.Sam(), id, conn, sam.keys, time.Duration(600 * time.Second), time.Now(), Sig_NONE, from, to}, nil
|
||||
}
|
||||
|
||||
/*
|
||||
func (s *PrimarySession) I2PListener(name string) (*StreamListener, error) {
|
||||
listener, err := s.NewStreamSubSession(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return listener.Listen()
|
||||
}
|
||||
*/
|
||||
|
||||
// Creates a new datagram session. udpPort is the UDP port SAM is listening on,
|
||||
// and if you set it to zero, it will use SAMs standard UDP port.
|
||||
func (s *PrimarySession) NewDatagramSubSession(id string, udpPort int) (*DatagramSession, error) {
|
||||
log.WithFields(logrus.Fields{"id": id, "udpPort": udpPort}).Debug("NewDatagramSubSession called")
|
||||
if udpPort > 65335 || udpPort < 0 {
|
||||
log.WithField("udpPort", udpPort).Error("Invalid UDP port")
|
||||
return nil, errors.New("udpPort needs to be in the intervall 0-65335")
|
||||
}
|
||||
if udpPort == 0 {
|
||||
udpPort = 7655
|
||||
log.Debug("Using default UDP port 7655")
|
||||
}
|
||||
lhost, _, err := SplitHostPort(s.conn.LocalAddr().String())
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to split local host port")
|
||||
s.Close()
|
||||
return nil, err
|
||||
}
|
||||
lUDPAddr, err := net.ResolveUDPAddr("udp4", lhost+":0")
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to resolve local UDP address")
|
||||
return nil, err
|
||||
}
|
||||
udpconn, err := net.ListenUDP("udp4", lUDPAddr)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to listen on UDP")
|
||||
return nil, err
|
||||
}
|
||||
rhost, _, err := SplitHostPort(s.conn.RemoteAddr().String())
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to split remote host port")
|
||||
s.Close()
|
||||
return nil, err
|
||||
}
|
||||
rUDPAddr, err := net.ResolveUDPAddr("udp4", rhost+":"+strconv.Itoa(udpPort))
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to resolve remote UDP address")
|
||||
return nil, err
|
||||
}
|
||||
_, lport, err := net.SplitHostPort(udpconn.LocalAddr().String())
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to get local port")
|
||||
s.Close()
|
||||
return nil, err
|
||||
}
|
||||
conn, err := s.newGenericSubSession("DATAGRAM", id, []string{"PORT=" + lport})
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to create new generic sub-session")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.WithFields(logrus.Fields{"id": id, "localPort": lport}).Debug("Created new datagram sub-session")
|
||||
return &DatagramSession{s.Config.I2PConfig.Sam(), id, conn, udpconn, s.keys, rUDPAddr, nil}, nil
|
||||
}
|
||||
|
||||
// Creates a new raw session. udpPort is the UDP port SAM is listening on,
|
||||
// and if you set it to zero, it will use SAMs standard UDP port.
|
||||
func (s *PrimarySession) NewRawSubSession(id string, udpPort int) (*RawSession, error) {
|
||||
log.WithFields(logrus.Fields{"id": id, "udpPort": udpPort}).Debug("NewRawSubSession called")
|
||||
|
||||
if udpPort > 65335 || udpPort < 0 {
|
||||
log.WithField("udpPort", udpPort).Error("Invalid UDP port")
|
||||
return nil, errors.New("udpPort needs to be in the intervall 0-65335")
|
||||
}
|
||||
if udpPort == 0 {
|
||||
udpPort = 7655
|
||||
log.Debug("Using default UDP port 7655")
|
||||
}
|
||||
lhost, _, err := SplitHostPort(s.conn.LocalAddr().String())
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to split local host port")
|
||||
s.Close()
|
||||
return nil, err
|
||||
}
|
||||
lUDPAddr, err := net.ResolveUDPAddr("udp4", lhost+":0")
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to resolve local UDP address")
|
||||
return nil, err
|
||||
}
|
||||
udpconn, err := net.ListenUDP("udp4", lUDPAddr)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to listen on UDP")
|
||||
return nil, err
|
||||
}
|
||||
rhost, _, err := SplitHostPort(s.conn.RemoteAddr().String())
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to split remote host port")
|
||||
s.Close()
|
||||
return nil, err
|
||||
}
|
||||
rUDPAddr, err := net.ResolveUDPAddr("udp4", rhost+":"+strconv.Itoa(udpPort))
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to resolve remote UDP address")
|
||||
return nil, err
|
||||
}
|
||||
_, lport, err := net.SplitHostPort(udpconn.LocalAddr().String())
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to get local port")
|
||||
s.Close()
|
||||
return nil, err
|
||||
}
|
||||
// conn, err := s.newGenericSubSession("RAW", id, s.keys, options, []string{"PORT=" + lport})
|
||||
conn, err := s.newGenericSubSession("RAW", id, []string{"PORT=" + lport})
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to create new generic sub-session")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.WithFields(logrus.Fields{"id": id, "localPort": lport}).Debug("Created new raw sub-session")
|
||||
return &RawSession{s.Config.I2PConfig.Sam(), id, conn, udpconn, s.keys, rUDPAddr}, nil
|
||||
}
|
148
primary_datagram_test.go
Normal file
148
primary_datagram_test.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package sam3
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Test_PrimaryDatagramServerClient(t *testing.T) {
|
||||
if testing.Short() {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Test_PrimaryDatagramServerClient")
|
||||
earlysam, err := NewSAM(yoursam)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
defer earlysam.Close()
|
||||
keys, err := earlysam.NewKeys()
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
|
||||
sam, err := earlysam.NewPrimarySession("PrimaryTunnel", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"})
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
defer sam.Close()
|
||||
// fmt.Println("\tServer: My address: " + keys.Addr().Base32())
|
||||
fmt.Println("\tServer: Creating tunnel")
|
||||
ds, err := sam.NewDatagramSubSession("PrimaryTunnel"+RandString(), 0)
|
||||
if err != nil {
|
||||
fmt.Println("Server: Failed to create tunnel: " + err.Error())
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
defer ds.Close()
|
||||
c, w := make(chan bool), make(chan bool)
|
||||
go func(c, w chan (bool)) {
|
||||
sam2, err := NewSAM(yoursam)
|
||||
if err != nil {
|
||||
c <- false
|
||||
return
|
||||
}
|
||||
defer sam2.Close()
|
||||
keys, err := sam2.NewKeys()
|
||||
if err != nil {
|
||||
c <- false
|
||||
return
|
||||
}
|
||||
fmt.Println("\tClient: Creating tunnel")
|
||||
ds2, err := sam2.NewDatagramSession("PRIMARYClientTunnel", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"}, 0)
|
||||
if err != nil {
|
||||
c <- false
|
||||
return
|
||||
}
|
||||
defer ds2.Close()
|
||||
// fmt.Println("\tClient: Servers address: " + ds.LocalAddr().Base32())
|
||||
// fmt.Println("\tClient: Clients address: " + ds2.LocalAddr().Base32())
|
||||
fmt.Println("\tClient: Tries to send primary to server")
|
||||
for {
|
||||
select {
|
||||
default:
|
||||
_, err = ds2.WriteTo([]byte("Hello primary-world! <3 <3 <3 <3 <3 <3"), ds.LocalAddr())
|
||||
if err != nil {
|
||||
fmt.Println("\tClient: Failed to send primary: " + err.Error())
|
||||
c <- false
|
||||
return
|
||||
}
|
||||
time.Sleep(5 * time.Second)
|
||||
case <-w:
|
||||
fmt.Println("\tClient: Sent primary, quitting.")
|
||||
return
|
||||
}
|
||||
}
|
||||
c <- true
|
||||
}(c, w)
|
||||
buf := make([]byte, 512)
|
||||
fmt.Println("\tServer: ReadFrom() waiting...")
|
||||
n, _, err := ds.ReadFrom(buf)
|
||||
w <- true
|
||||
if err != nil {
|
||||
fmt.Println("\tServer: Failed to ReadFrom(): " + err.Error())
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
fmt.Println("\tServer: Received primary: " + string(buf[:n]))
|
||||
// fmt.Println("\tServer: Senders address was: " + saddr.Base32())
|
||||
}
|
||||
|
||||
func ExamplePrimaryDatagramSession() {
|
||||
// Creates a new PrimarySession, then creates a Datagram subsession on top of it
|
||||
|
||||
const samBridge = "127.0.0.1:7656"
|
||||
|
||||
earlysam, err := NewSAM(samBridge)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
defer earlysam.Close()
|
||||
keys, err := earlysam.NewKeys()
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
myself := keys.Addr()
|
||||
|
||||
sam, err := earlysam.NewPrimarySession("PrimaryTunnel", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"})
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
defer sam.Close()
|
||||
|
||||
// See the example Option_* variables.
|
||||
dg, err := sam.NewDatagramSubSession("DGTUN"+RandString(), 0)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
defer dg.Close()
|
||||
someone, err := earlysam.Lookup("zzz.i2p")
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
dg.WriteTo([]byte("Hello stranger!"), someone)
|
||||
dg.WriteTo([]byte("Hello myself!"), myself)
|
||||
|
||||
buf := make([]byte, 31*1024)
|
||||
n, _, err := dg.ReadFrom(buf)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
fmt.Println("Got message: '" + string(buf[:n]) + "'")
|
||||
fmt.Println("Got message: " + string(buf[:n]))
|
||||
|
||||
return
|
||||
// Output:
|
||||
//Got message: Hello myself!
|
||||
}
|
307
primary_stream_test.go
Normal file
307
primary_stream_test.go
Normal file
@@ -0,0 +1,307 @@
|
||||
package sam3
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Test_PrimaryStreamingDial(t *testing.T) {
|
||||
if testing.Short() {
|
||||
return
|
||||
}
|
||||
fmt.Println("Test_PrimaryStreamingDial")
|
||||
earlysam, err := NewSAM(yoursam)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
defer earlysam.Close()
|
||||
keys, err := earlysam.NewKeys()
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
|
||||
sam, err := earlysam.NewPrimarySession("PrimaryTunnel", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"})
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
defer sam.Close()
|
||||
fmt.Println("\tBuilding tunnel")
|
||||
ss, err := sam.NewStreamSubSession("primaryStreamTunnel")
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
defer ss.Close()
|
||||
fmt.Println("\tNotice: This may fail if your I2P node is not well integrated in the I2P network.")
|
||||
fmt.Println("\tLooking up i2p-projekt.i2p")
|
||||
forumAddr, err := earlysam.Lookup("i2p-projekt.i2p")
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
fmt.Println("\tDialing i2p-projekt.i2p(", forumAddr.Base32(), forumAddr.DestHash().Hash(), ")")
|
||||
conn, err := ss.DialI2P(forumAddr)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
fmt.Println("\tSending HTTP GET /")
|
||||
if _, err := conn.Write([]byte("GET /\n")); err != nil {
|
||||
fmt.Println(err.Error())
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
buf := make([]byte, 4096)
|
||||
n, err := conn.Read(buf)
|
||||
if !strings.Contains(strings.ToLower(string(buf[:n])), "http") && !strings.Contains(strings.ToLower(string(buf[:n])), "html") {
|
||||
fmt.Printf("\tProbably failed to StreamSession.DialI2P(i2p-projekt.i2p)? It replied %d bytes, but nothing that looked like http/html", n)
|
||||
} else {
|
||||
fmt.Println("\tRead HTTP/HTML from i2p-projekt.i2p")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_PrimaryStreamingServerClient(t *testing.T) {
|
||||
if testing.Short() {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Test_StreamingServerClient")
|
||||
earlysam, err := NewSAM(yoursam)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
defer earlysam.Close()
|
||||
keys, err := earlysam.NewKeys()
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
|
||||
sam, err := earlysam.NewPrimarySession("PrimaryServerClientTunnel", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"})
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
defer sam.Close()
|
||||
fmt.Println("\tServer: Creating tunnel")
|
||||
ss, err := sam.NewUniqueStreamSubSession("PrimaryServerClientTunnel")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer ss.Close()
|
||||
time.Sleep(time.Second * 10)
|
||||
c, w := make(chan bool), make(chan bool)
|
||||
go func(c, w chan (bool)) {
|
||||
if !(<-w) {
|
||||
return
|
||||
}
|
||||
/*
|
||||
sam2, err := NewSAM(yoursam)
|
||||
if err != nil {
|
||||
c <- false
|
||||
return
|
||||
}
|
||||
defer sam2.Close()
|
||||
keys, err := sam2.NewKeys()
|
||||
if err != nil {
|
||||
c <- false
|
||||
return
|
||||
}
|
||||
*/
|
||||
|
||||
fmt.Println("\tClient: Creating tunnel")
|
||||
ss2, err := sam.NewStreamSubSession("primaryExampleClientTun")
|
||||
if err != nil {
|
||||
c <- false
|
||||
return
|
||||
}
|
||||
defer ss2.Close()
|
||||
fmt.Println("\tClient: Connecting to server")
|
||||
conn, err := ss2.DialI2P(ss.Addr())
|
||||
if err != nil {
|
||||
c <- false
|
||||
return
|
||||
}
|
||||
fmt.Println("\tClient: Connected to tunnel")
|
||||
defer conn.Close()
|
||||
_, err = conn.Write([]byte("Hello world <3 <3 <3 <3 <3 <3"))
|
||||
if err != nil {
|
||||
c <- false
|
||||
return
|
||||
}
|
||||
c <- true
|
||||
}(c, w)
|
||||
l, err := ss.Listen()
|
||||
if err != nil {
|
||||
fmt.Println("ss.Listen(): " + err.Error())
|
||||
t.Fail()
|
||||
w <- false
|
||||
return
|
||||
}
|
||||
defer l.Close()
|
||||
w <- true
|
||||
fmt.Println("\tServer: Accept()ing on tunnel")
|
||||
conn, err := l.Accept()
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
fmt.Println("Failed to Accept(): " + err.Error())
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
buf := make([]byte, 512)
|
||||
n, err := conn.Read(buf)
|
||||
fmt.Printf("\tClient exited successfully: %t\n", <-c)
|
||||
fmt.Println("\tServer: received from Client: " + string(buf[:n]))
|
||||
}
|
||||
|
||||
func ExamplePrimaryStreamSession() {
|
||||
// Creates a new StreamingSession, dials to idk.i2p and gets a SAMConn
|
||||
// which behaves just like a normal net.Conn.
|
||||
|
||||
const samBridge = "127.0.0.1:7656"
|
||||
|
||||
earlysam, err := NewSAM(yoursam)
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
return
|
||||
}
|
||||
defer earlysam.Close()
|
||||
keys, err := earlysam.NewKeys()
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
sam, err := earlysam.NewPrimarySession("PrimaryStreamSessionTunnel", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"})
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
return
|
||||
}
|
||||
defer sam.Close()
|
||||
conn, err := sam.Dial("tcp", "idk.i2p") //someone.Base32())
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
fmt.Println("Sending HTTP GET /")
|
||||
if _, err := conn.Write([]byte("GET /\n")); err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
buf := make([]byte, 4096)
|
||||
n, err := conn.Read(buf)
|
||||
if !strings.Contains(strings.ToLower(string(buf[:n])), "http") && !strings.Contains(strings.ToLower(string(buf[:n])), "html") {
|
||||
fmt.Printf("Probably failed to StreamSession.DialI2P(idk.i2p)? It replied %d bytes, but nothing that looked like http/html", n)
|
||||
log.Printf("Probably failed to StreamSession.DialI2P(idk.i2p)? It replied %d bytes, but nothing that looked like http/html", n)
|
||||
} else {
|
||||
fmt.Println("Read HTTP/HTML from idk.i2p")
|
||||
log.Println("Read HTTP/HTML from idk.i2p")
|
||||
}
|
||||
// Output:
|
||||
//Sending HTTP GET /
|
||||
//Read HTTP/HTML from idk.i2p
|
||||
}
|
||||
|
||||
func ExamplePrimaryStreamListener() {
|
||||
// One server Accept()ing on a StreamListener, and one client that Dials
|
||||
// through I2P to the server. Server writes "Hello world!" through a SAMConn
|
||||
// (which implements net.Conn) and the client prints the message.
|
||||
|
||||
const samBridge = "127.0.0.1:7656"
|
||||
|
||||
var ss *StreamSession
|
||||
go func() {
|
||||
earlysam, err := NewSAM(yoursam)
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
return
|
||||
}
|
||||
defer earlysam.Close()
|
||||
keys, err := earlysam.NewKeys()
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
return
|
||||
}
|
||||
sam, err := earlysam.NewPrimarySession("PrimaryListenerTunnel", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"})
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
return
|
||||
}
|
||||
defer sam.Close()
|
||||
ss, err = sam.NewStreamSubSession("PrimaryListenerServerTunnel2")
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
defer ss.Close()
|
||||
l, err := ss.Listen()
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
defer l.Close()
|
||||
//fmt.Println("Serving on primary listener", l.Addr().String())
|
||||
if err := http.Serve(l, &exitHandler{}); err != nil {
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
}()
|
||||
time.Sleep(time.Second * 10)
|
||||
latesam, err := NewSAM(yoursam)
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
return
|
||||
}
|
||||
defer latesam.Close()
|
||||
keys2, err := latesam.NewKeys()
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
return
|
||||
}
|
||||
sc, err := latesam.NewStreamSession("PrimaryListenerClientTunnel2", keys2, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"})
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
defer sc.Close()
|
||||
client := http.Client{
|
||||
Transport: &http.Transport{
|
||||
Dial: sc.Dial,
|
||||
},
|
||||
}
|
||||
//resp, err := client.Get("http://" + "idk.i2p") //ss.Addr().Base32())
|
||||
resp, err := client.Get("http://" + ss.Addr().Base32())
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
r, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
fmt.Println("Got response: " + string(r))
|
||||
|
||||
// Output:
|
||||
// Got response: Hello world!
|
||||
}
|
||||
|
||||
type exitHandler struct {
|
||||
}
|
||||
|
||||
func (e *exitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("Hello world!"))
|
||||
}
|
57
raw.go
57
raw.go
@@ -3,11 +3,12 @@ package sam3
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/eyedeekay/sam3/i2pkeys"
|
||||
"github.com/go-i2p/i2pkeys"
|
||||
)
|
||||
|
||||
// The RawSession provides no authentication of senders, and there is no sender
|
||||
@@ -28,39 +29,59 @@ type RawSession struct {
|
||||
// Creates a new raw session. udpPort is the UDP port SAM is listening on,
|
||||
// and if you set it to zero, it will use SAMs standard UDP port.
|
||||
func (s *SAM) NewRawSession(id string, keys i2pkeys.I2PKeys, options []string, udpPort int) (*RawSession, error) {
|
||||
log.WithFields(logrus.Fields{"id": id, "udpPort": udpPort}).Debug("Creating new RawSession")
|
||||
|
||||
if udpPort > 65335 || udpPort < 0 {
|
||||
return nil, errors.New("udpPort needs to be in the intervall 0-65335")
|
||||
log.WithField("udpPort", udpPort).Error("Invalid UDP port")
|
||||
return nil, errors.New("udpPort needs to be in the interval 0-65335")
|
||||
}
|
||||
if udpPort == 0 {
|
||||
udpPort = 7655
|
||||
log.Debug("Using default UDP port 7655")
|
||||
}
|
||||
lhost, _, err := net.SplitHostPort(s.conn.LocalAddr().String())
|
||||
lhost, _, err := SplitHostPort(s.conn.LocalAddr().String())
|
||||
if err != nil {
|
||||
log.Debug("Using default UDP port 7655")
|
||||
s.Close()
|
||||
return nil, err
|
||||
}
|
||||
lUDPAddr, err := net.ResolveUDPAddr("udp4", lhost+":0")
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to resolve local UDP address")
|
||||
return nil, err
|
||||
}
|
||||
udpconn, err := net.ListenUDP("udp4", lUDPAddr)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to listen on UDP")
|
||||
return nil, err
|
||||
}
|
||||
rhost, _, err := net.SplitHostPort(s.conn.RemoteAddr().String())
|
||||
rhost, _, err := SplitHostPort(s.conn.RemoteAddr().String())
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to split remote host port")
|
||||
s.Close()
|
||||
return nil, err
|
||||
}
|
||||
rUDPAddr, err := net.ResolveUDPAddr("udp4", rhost+":"+strconv.Itoa(udpPort))
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to resolve remote UDP address")
|
||||
return nil, err
|
||||
}
|
||||
_, lport, err := net.SplitHostPort(udpconn.LocalAddr().String())
|
||||
conn, err := s.newGenericSession("RAW", id, keys, options, []string{"PORT=" + lport})
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to get local port")
|
||||
return nil, err
|
||||
}
|
||||
conn, err := s.newGenericSession("RAW", id, keys, options, []string{"PORT=" + lport})
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to create new generic session")
|
||||
return nil, err
|
||||
}
|
||||
log.WithFields(logrus.Fields{
|
||||
"id": id,
|
||||
"localPort": lport,
|
||||
"remoteUDPAddr": rUDPAddr,
|
||||
}).Debug("Created new RawSession")
|
||||
|
||||
return &RawSession{s.Config.I2PConfig.Sam(), id, conn, udpconn, keys, rUDPAddr}, nil
|
||||
}
|
||||
|
||||
@@ -68,37 +89,61 @@ func (s *SAM) NewRawSession(id string, keys i2pkeys.I2PKeys, options []string, u
|
||||
// the number of bytes read. Who sent the raw message can not be determined at
|
||||
// this layer - you need to do it (in a secure way!).
|
||||
func (s *RawSession) Read(b []byte) (n int, err error) {
|
||||
log.Debug("Attempting to read raw datagram")
|
||||
|
||||
for {
|
||||
// very basic protection: only accept incomming UDP messages from the IP of the SAM bridge
|
||||
var saddr *net.UDPAddr
|
||||
n, saddr, err = s.udpconn.ReadFromUDP(b)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to read from UDP")
|
||||
return 0, err
|
||||
}
|
||||
if bytes.Equal(saddr.IP, s.rUDPAddr.IP) {
|
||||
log.WithField("senderIP", saddr.IP).Debug("Received datagram from SAM bridge IP")
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
log.WithField("bytesRead", n).Debug("Successfully read raw datagram")
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Sends one raw datagram to the destination specified. At the time of writing,
|
||||
// maximum size is 32 kilobyte, but this may change in the future.
|
||||
func (s *RawSession) WriteTo(b []byte, addr i2pkeys.I2PAddr) (n int, err error) {
|
||||
log.WithFields(logrus.Fields{
|
||||
"destAddr": addr.String(),
|
||||
"dataLen": len(b),
|
||||
}).Debug("Attempting to write raw datagram")
|
||||
|
||||
header := []byte("3.0 " + s.id + " " + addr.String() + "\n")
|
||||
msg := append(header, b...)
|
||||
n, err = s.udpconn.WriteToUDP(msg, s.rUDPAddr)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to write to UDP")
|
||||
}
|
||||
log.WithField("bytesWritten", n).Debug("Successfully wrote raw datagram")
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Closes the RawSession.
|
||||
func (s *RawSession) Close() error {
|
||||
log.Debug("Closing RawSession")
|
||||
|
||||
err := s.conn.Close()
|
||||
err2 := s.udpconn.Close()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to close connection")
|
||||
return err
|
||||
}
|
||||
|
||||
err2 := s.udpconn.Close()
|
||||
if err2 != nil {
|
||||
log.WithError(err2).Error("Failed to close UDP connection")
|
||||
}
|
||||
|
||||
log.Debug("RawSession closed")
|
||||
return err2
|
||||
}
|
||||
|
||||
|
18
resolver.go
18
resolver.go
@@ -6,7 +6,7 @@ import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/eyedeekay/sam3/i2pkeys"
|
||||
"github.com/go-i2p/i2pkeys"
|
||||
)
|
||||
|
||||
type SAMResolver struct {
|
||||
@@ -14,16 +14,19 @@ type SAMResolver struct {
|
||||
}
|
||||
|
||||
func NewSAMResolver(parent *SAM) (*SAMResolver, error) {
|
||||
log.Debug("Creating new SAMResolver from existing SAM instance")
|
||||
var s SAMResolver
|
||||
s.SAM = parent
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
func NewFullSAMResolver(address string) (*SAMResolver, error) {
|
||||
log.WithField("address", address).Debug("Creating new full SAMResolver")
|
||||
var s SAMResolver
|
||||
var err error
|
||||
s.SAM, err = NewSAM(address)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to create new SAM instance")
|
||||
return nil, err
|
||||
}
|
||||
return &s, nil
|
||||
@@ -32,17 +35,22 @@ func NewFullSAMResolver(address string) (*SAMResolver, error) {
|
||||
// Performs a lookup, probably this order: 1) routers known addresses, cached
|
||||
// addresses, 3) by asking peers in the I2P network.
|
||||
func (sam *SAMResolver) Resolve(name string) (i2pkeys.I2PAddr, error) {
|
||||
log.WithField("name", name).Debug("Resolving name")
|
||||
|
||||
if _, err := sam.conn.Write([]byte("NAMING LOOKUP NAME=" + name + "\r\n")); err != nil {
|
||||
log.WithError(err).Error("Failed to write to SAM connection")
|
||||
sam.Close()
|
||||
return i2pkeys.I2PAddr(""), err
|
||||
}
|
||||
buf := make([]byte, 4096)
|
||||
n, err := sam.conn.Read(buf)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to read from SAM connection")
|
||||
sam.Close()
|
||||
return i2pkeys.I2PAddr(""), err
|
||||
}
|
||||
if n <= 13 || !strings.HasPrefix(string(buf[:n]), "NAMING REPLY ") {
|
||||
log.Error("Failed to parse SAM response")
|
||||
return i2pkeys.I2PAddr(""), errors.New("Failed to parse.")
|
||||
}
|
||||
s := bufio.NewScanner(bytes.NewReader(buf[13:n]))
|
||||
@@ -51,19 +59,25 @@ func (sam *SAMResolver) Resolve(name string) (i2pkeys.I2PAddr, error) {
|
||||
errStr := ""
|
||||
for s.Scan() {
|
||||
text := s.Text()
|
||||
log.WithField("text", text).Debug("Parsing SAM response token")
|
||||
//log.Println("SAM3", text)
|
||||
if text == "RESULT=OK" {
|
||||
continue
|
||||
} else if text == "RESULT=INVALID_KEY" {
|
||||
errStr += "Invalid key."
|
||||
errStr += "Invalid key - resolver."
|
||||
log.Error("Invalid key in resolver")
|
||||
} else if text == "RESULT=KEY_NOT_FOUND" {
|
||||
errStr += "Unable to resolve " + name
|
||||
log.WithField("name", name).Error("Unable to resolve name")
|
||||
} else if text == "NAME="+name {
|
||||
continue
|
||||
} else if strings.HasPrefix(text, "VALUE=") {
|
||||
addr := i2pkeys.I2PAddr(text[6:])
|
||||
log.WithField("addr", addr).Debug("Name resolved successfully")
|
||||
return i2pkeys.I2PAddr(text[6:]), nil
|
||||
} else if strings.HasPrefix(text, "MESSAGE=") {
|
||||
errStr += " " + text[8:]
|
||||
log.WithField("message", text[8:]).Warn("Received message from SAM")
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
108
sam3.go
108
sam3.go
@@ -5,17 +5,22 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/sirupsen/logrus"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/eyedeekay/sam3/i2pkeys"
|
||||
"github.com/go-i2p/i2pkeys"
|
||||
|
||||
. "github.com/go-i2p/i2pkeys"
|
||||
)
|
||||
|
||||
import (
|
||||
. "github.com/eyedeekay/sam3/i2pkeys"
|
||||
)
|
||||
func init() {
|
||||
InitializeSAM3Logger()
|
||||
}
|
||||
|
||||
// Used for controlling I2Ps SAMv3.
|
||||
type SAM struct {
|
||||
@@ -36,7 +41,7 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
Sig_NONE = ""
|
||||
Sig_NONE = "SIGNATURE_TYPE=EdDSA_SHA512_Ed25519"
|
||||
Sig_DSA_SHA1 = "SIGNATURE_TYPE=DSA_SHA1"
|
||||
Sig_ECDSA_SHA256_P256 = "SIGNATURE_TYPE=ECDSA_SHA256_P256"
|
||||
Sig_ECDSA_SHA384_P384 = "SIGNATURE_TYPE=ECDSA_SHA384_P384"
|
||||
@@ -44,38 +49,57 @@ const (
|
||||
Sig_EdDSA_SHA512_Ed25519 = "SIGNATURE_TYPE=EdDSA_SHA512_Ed25519"
|
||||
)
|
||||
|
||||
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
|
||||
func RandString() string {
|
||||
n := 4
|
||||
b := make([]rune, n)
|
||||
for i := range b {
|
||||
b[i] = letters[rand.Intn(len(letters))]
|
||||
}
|
||||
log.WithField("randomString", string(b)).Debug("Generated random string")
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// Creates a new controller for the I2P routers SAM bridge.
|
||||
func NewSAM(address string) (*SAM, error) {
|
||||
log.WithField("address", address).Debug("Creating new SAM instance")
|
||||
var s SAM
|
||||
// TODO: clean this up
|
||||
conn, err := net.Dial("tcp", address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
log.WithError(err).Error("Failed to dial SAM address")
|
||||
return nil, fmt.Errorf("error dialing to address '%s': %w", address, err)
|
||||
}
|
||||
if _, err := conn.Write(s.Config.HelloBytes()); err != nil {
|
||||
log.WithError(err).Error("Failed to write hello message")
|
||||
conn.Close()
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("error writing to address '%s': %w", address, err)
|
||||
}
|
||||
buf := make([]byte, 256)
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to read SAM response")
|
||||
conn.Close()
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("error reading onto buffer: %w", err)
|
||||
}
|
||||
if strings.Contains(string(buf[:n]), "HELLO REPLY RESULT=OK") {
|
||||
log.Debug("SAM hello successful")
|
||||
s.Config.I2PConfig.SetSAMAddress(address)
|
||||
s.conn = conn
|
||||
//s.Config.I2PConfig.DestinationKeys = nil
|
||||
s.resolver, err = NewSAMResolver(&s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
log.WithError(err).Error("Failed to create SAM resolver")
|
||||
return nil, fmt.Errorf("error creating resolver: %w", err)
|
||||
}
|
||||
return &s, nil
|
||||
//return &SAM{address, conn, nil, nil}, nil
|
||||
} else if string(buf[:n]) == "HELLO REPLY RESULT=NOVERSION\n" {
|
||||
log.Error("SAM bridge does not support SAMv3")
|
||||
conn.Close()
|
||||
return nil, errors.New("That SAM bridge does not support SAMv3.")
|
||||
} else {
|
||||
log.WithField("response", string(buf[:n])).Error("Unexpected SAM response")
|
||||
conn.Close()
|
||||
return nil, errors.New(string(buf[:n]))
|
||||
}
|
||||
@@ -83,30 +107,38 @@ func NewSAM(address string) (*SAM, error) {
|
||||
|
||||
func (sam *SAM) Keys() (k *i2pkeys.I2PKeys) {
|
||||
//TODO: copy them?
|
||||
log.Debug("Retrieving SAM keys")
|
||||
k = &sam.Config.I2PConfig.DestinationKeys
|
||||
return
|
||||
}
|
||||
|
||||
// read public/private keys from an io.Reader
|
||||
func (sam *SAM) ReadKeys(r io.Reader) (err error) {
|
||||
log.Debug("Reading keys from io.Reader")
|
||||
var keys i2pkeys.I2PKeys
|
||||
keys, err = i2pkeys.LoadKeysIncompat(r)
|
||||
if err == nil {
|
||||
log.Debug("Keys loaded successfully")
|
||||
sam.Config.I2PConfig.DestinationKeys = keys
|
||||
}
|
||||
log.WithError(err).Error("Failed to load keys")
|
||||
return
|
||||
}
|
||||
|
||||
// if keyfile fname does not exist
|
||||
func (sam *SAM) EnsureKeyfile(fname string) (keys i2pkeys.I2PKeys, err error) {
|
||||
log.WithError(err).Error("Failed to load keys")
|
||||
if fname == "" {
|
||||
// transient
|
||||
keys, err = sam.NewKeys()
|
||||
if err == nil {
|
||||
sam.Config.I2PConfig.DestinationKeys = keys
|
||||
log.WithFields(logrus.Fields{
|
||||
"keys": keys,
|
||||
}).Debug("Generated new transient keys")
|
||||
}
|
||||
} else {
|
||||
// persistant
|
||||
// persistent
|
||||
_, err = os.Stat(fname)
|
||||
if os.IsNotExist(err) {
|
||||
// make the keys
|
||||
@@ -119,6 +151,7 @@ func (sam *SAM) EnsureKeyfile(fname string) (keys i2pkeys.I2PKeys, err error) {
|
||||
if err == nil {
|
||||
err = i2pkeys.StoreKeysIncompat(keys, f)
|
||||
f.Close()
|
||||
log.Debug("Generated and saved new keys")
|
||||
}
|
||||
}
|
||||
} else if err == nil {
|
||||
@@ -129,10 +162,14 @@ func (sam *SAM) EnsureKeyfile(fname string) (keys i2pkeys.I2PKeys, err error) {
|
||||
keys, err = i2pkeys.LoadKeysIncompat(f)
|
||||
if err == nil {
|
||||
sam.Config.I2PConfig.DestinationKeys = keys
|
||||
log.Debug("Loaded existing keys from file")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to ensure keyfile")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -140,17 +177,20 @@ func (sam *SAM) EnsureKeyfile(fname string) (keys i2pkeys.I2PKeys, err error) {
|
||||
// who has the private keys can send messages from. The public keys are the I2P
|
||||
// desination (the address) that anyone can send messages to.
|
||||
func (sam *SAM) NewKeys(sigType ...string) (i2pkeys.I2PKeys, error) {
|
||||
log.WithField("sigType", sigType).Debug("Generating new keys")
|
||||
sigtmp := ""
|
||||
if len(sigType) > 0 {
|
||||
sigtmp = sigType[0]
|
||||
}
|
||||
if _, err := sam.conn.Write([]byte("DEST GENERATE " + sigtmp + "\n")); err != nil {
|
||||
return i2pkeys.I2PKeys{}, err
|
||||
log.WithError(err).Error("Failed to write DEST GENERATE command")
|
||||
return i2pkeys.I2PKeys{}, fmt.Errorf("error with writing in SAM: %w", err)
|
||||
}
|
||||
buf := make([]byte, 8192)
|
||||
n, err := sam.conn.Read(buf)
|
||||
if err != nil {
|
||||
return i2pkeys.I2PKeys{}, err
|
||||
log.WithError(err).Error("Failed to read SAM response for key generation")
|
||||
return i2pkeys.I2PKeys{}, fmt.Errorf("error with reading in SAM: %w", err)
|
||||
}
|
||||
s := bufio.NewScanner(bytes.NewReader(buf[:n]))
|
||||
s.Split(bufio.ScanWords)
|
||||
@@ -167,15 +207,18 @@ func (sam *SAM) NewKeys(sigType ...string) (i2pkeys.I2PKeys, error) {
|
||||
} else if strings.HasPrefix(text, "PRIV=") {
|
||||
priv = text[5:]
|
||||
} else {
|
||||
log.Error("Failed to parse keys from SAM response")
|
||||
return i2pkeys.I2PKeys{}, errors.New("Failed to parse keys.")
|
||||
}
|
||||
}
|
||||
log.Debug("Successfully generated new keys")
|
||||
return NewKeys(I2PAddr(pub), priv), nil
|
||||
}
|
||||
|
||||
// Performs a lookup, probably this order: 1) routers known addresses, cached
|
||||
// addresses, 3) by asking peers in the I2P network.
|
||||
func (sam *SAM) Lookup(name string) (i2pkeys.I2PAddr, error) {
|
||||
log.WithField("name", name).Debug("Looking up address")
|
||||
return sam.resolver.Resolve(name)
|
||||
}
|
||||
|
||||
@@ -185,10 +228,12 @@ func (sam *SAM) Lookup(name string) (i2pkeys.I2PAddr, error) {
|
||||
// setting extra to something else than []string{}.
|
||||
// This sam3 instance is now a session
|
||||
func (sam *SAM) newGenericSession(style, id string, keys i2pkeys.I2PKeys, options []string, extras []string) (net.Conn, error) {
|
||||
log.WithFields(logrus.Fields{"style": style, "id": id}).Debug("Creating new generic session")
|
||||
return sam.newGenericSessionWithSignature(style, id, keys, Sig_NONE, options, extras)
|
||||
}
|
||||
|
||||
func (sam *SAM) newGenericSessionWithSignature(style, id string, keys i2pkeys.I2PKeys, sigType string, options []string, extras []string) (net.Conn, error) {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -198,52 +243,72 @@ func (sam *SAM) newGenericSessionWithSignature(style, id string, keys i2pkeys.I2
|
||||
// setting extra to something else than []string{}.
|
||||
// This sam3 instance is now a session
|
||||
func (sam *SAM) newGenericSessionWithSignatureAndPorts(style, id, from, to string, keys i2pkeys.I2PKeys, sigType string, options []string, extras []string) (net.Conn, error) {
|
||||
log.WithFields(logrus.Fields{"style": style, "id": id, "from": from, "to": to, "sigType": sigType}).Debug("Creating new generic session with signature and ports")
|
||||
|
||||
optStr := ""
|
||||
for _, opt := range options {
|
||||
optStr += opt + " "
|
||||
}
|
||||
optStr := GenerateOptionString(options)
|
||||
|
||||
conn := sam.conn
|
||||
scmsg := []byte("SESSION CREATE STYLE=" + style + " FROM_PORT=" + from + " TO_PORT=" + to + " ID=" + id + " DESTINATION=" + keys.String() + " " + sigType + " " + optStr + strings.Join(extras, " ") + "\n")
|
||||
fp := ""
|
||||
tp := ""
|
||||
if from != "0" {
|
||||
fp = " FROM_PORT=" + from
|
||||
}
|
||||
if to != "0" {
|
||||
tp = " TO_PORT=" + to
|
||||
}
|
||||
scmsg := []byte("SESSION CREATE STYLE=" + style + fp + tp + " ID=" + id + " DESTINATION=" + keys.String() + " " + optStr + strings.Join(extras, " ") + "\n")
|
||||
|
||||
log.WithField("message", string(scmsg)).Debug("Sending SESSION CREATE message")
|
||||
|
||||
for m, i := 0, 0; m != len(scmsg); i++ {
|
||||
if i == 15 {
|
||||
log.Error("Failed to write SESSION CREATE message after 15 attempts")
|
||||
conn.Close()
|
||||
return nil, errors.New("writing to SAM failed")
|
||||
}
|
||||
n, err := conn.Write(scmsg[m:])
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to write to SAM connection")
|
||||
conn.Close()
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("writing to connection failed: %w", err)
|
||||
}
|
||||
m += n
|
||||
}
|
||||
buf := make([]byte, 4096)
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to read SAM response")
|
||||
conn.Close()
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("reading from connection failed: %w", err)
|
||||
}
|
||||
text := string(buf[:n])
|
||||
log.WithField("response", text).Debug("Received SAM response")
|
||||
if strings.HasPrefix(text, session_OK) {
|
||||
if keys.String() != text[len(session_OK):len(text)-1] {
|
||||
log.Error("SAM created a tunnel with different keys than requested")
|
||||
conn.Close()
|
||||
return nil, errors.New("SAMv3 created a tunnel with keys other than the ones we asked it for")
|
||||
}
|
||||
log.Debug("Successfully created new session")
|
||||
return conn, nil //&StreamSession{id, conn, keys, nil, sync.RWMutex{}, nil}, nil
|
||||
} else if text == session_DUPLICATE_ID {
|
||||
log.Error("Duplicate tunnel name")
|
||||
conn.Close()
|
||||
return nil, errors.New("Duplicate tunnel name")
|
||||
} else if text == session_DUPLICATE_DEST {
|
||||
log.Error("Duplicate destination")
|
||||
conn.Close()
|
||||
return nil, errors.New("Duplicate destination")
|
||||
} else if text == session_INVALID_KEY {
|
||||
log.Error("Invalid key for SAM session")
|
||||
conn.Close()
|
||||
return nil, errors.New("Invalid key")
|
||||
return nil, errors.New("Invalid key - SAM session")
|
||||
} else if strings.HasPrefix(text, session_I2P_ERROR) {
|
||||
log.WithField("error", text[len(session_I2P_ERROR):]).Error("I2P error")
|
||||
conn.Close()
|
||||
return nil, errors.New("I2P error " + text[len(session_I2P_ERROR):])
|
||||
} else {
|
||||
log.WithField("reply", text).Error("Unable to parse SAMv3 reply")
|
||||
conn.Close()
|
||||
return nil, errors.New("Unable to parse SAMv3 reply: " + text)
|
||||
}
|
||||
@@ -251,5 +316,6 @@ func (sam *SAM) newGenericSessionWithSignatureAndPorts(style, id, from, to strin
|
||||
|
||||
// close this sam session
|
||||
func (sam *SAM) Close() error {
|
||||
log.Debug("Closing SAM session")
|
||||
return sam.conn.Close()
|
||||
}
|
||||
|
@@ -1,5 +1,3 @@
|
||||
// +build nettest
|
||||
|
||||
package sam3
|
||||
|
||||
import (
|
||||
|
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; }
|
216
stream.go
216
stream.go
@@ -5,14 +5,13 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/eyedeekay/sam3/i2pkeys"
|
||||
"github.com/go-i2p/i2pkeys"
|
||||
)
|
||||
|
||||
// Represents a streaming session.
|
||||
@@ -28,86 +27,132 @@ type StreamSession struct {
|
||||
to string
|
||||
}
|
||||
|
||||
func (ss *StreamSession) From() string {
|
||||
return ss.from
|
||||
// Read reads data from the stream.
|
||||
func (s *StreamSession) Read(buf []byte) (int, error) {
|
||||
return s.conn.Read(buf)
|
||||
}
|
||||
|
||||
func (ss *StreamSession) To() string {
|
||||
return ss.to
|
||||
// Write sends data over the stream.
|
||||
func (s *StreamSession) Write(data []byte) (int, error) {
|
||||
return s.conn.Write(data)
|
||||
}
|
||||
|
||||
func (ss *StreamSession) SignatureType() string {
|
||||
return ss.sigType
|
||||
func (s *StreamSession) SetDeadline(t time.Time) error {
|
||||
log.WithField("deadline", t).Debug("Setting deadline for StreamSession")
|
||||
return s.conn.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (s *StreamSession) SetReadDeadline(t time.Time) error {
|
||||
log.WithField("readDeadline", t).Debug("Setting read deadline for StreamSession")
|
||||
return s.conn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (s *StreamSession) SetWriteDeadline(t time.Time) error {
|
||||
log.WithField("writeDeadline", t).Debug("Setting write deadline for StreamSession")
|
||||
return s.conn.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
func (s *StreamSession) From() string {
|
||||
return s.from
|
||||
}
|
||||
|
||||
func (s *StreamSession) To() string {
|
||||
return s.to
|
||||
}
|
||||
|
||||
func (s *StreamSession) SignatureType() string {
|
||||
return s.sigType
|
||||
}
|
||||
|
||||
// Returns the local tunnel name of the I2P tunnel used for the stream session
|
||||
func (ss *StreamSession) ID() string {
|
||||
return ss.id
|
||||
func (s *StreamSession) ID() string {
|
||||
return s.id
|
||||
}
|
||||
|
||||
func (ss *StreamSession) Close() error {
|
||||
return ss.conn.Close()
|
||||
func (s *StreamSession) Close() error {
|
||||
log.WithField("id", s.id).Debug("Closing StreamSession")
|
||||
return s.conn.Close()
|
||||
}
|
||||
|
||||
// Returns the I2P destination (the address) of the stream session
|
||||
func (ss *StreamSession) Addr() i2pkeys.I2PAddr {
|
||||
return ss.keys.Addr()
|
||||
func (s *StreamSession) Addr() i2pkeys.I2PAddr {
|
||||
return s.keys.Addr()
|
||||
}
|
||||
|
||||
func (s *StreamSession) LocalAddr() net.Addr {
|
||||
return s.keys.Addr()
|
||||
}
|
||||
|
||||
// Returns the keys associated with the stream session
|
||||
func (ss *StreamSession) Keys() i2pkeys.I2PKeys {
|
||||
return ss.keys
|
||||
func (s *StreamSession) Keys() i2pkeys.I2PKeys {
|
||||
return s.keys
|
||||
}
|
||||
|
||||
// Creates a new StreamSession with the I2CP- and streaminglib options as
|
||||
// specified. See the I2P documentation for a full list of options.
|
||||
func (sam *SAM) NewStreamSession(id string, keys i2pkeys.I2PKeys, options []string) (*StreamSession, error) {
|
||||
log.WithFields(logrus.Fields{"id": id, "options": options}).Debug("Creating new StreamSession")
|
||||
conn, err := sam.newGenericSession("STREAM", id, keys, options, []string{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.WithField("id", id).Debug("Created new StreamSession")
|
||||
return &StreamSession{sam.Config.I2PConfig.Sam(), id, conn, keys, time.Duration(600 * time.Second), time.Now(), Sig_NONE, "0", "0"}, nil
|
||||
}
|
||||
|
||||
// Creates a new StreamSession with the I2CP- and streaminglib options as
|
||||
// specified. See the I2P documentation for a full list of options.
|
||||
func (sam *SAM) NewStreamSessionWithSignature(id string, keys i2pkeys.I2PKeys, options []string, sigType string) (*StreamSession, error) {
|
||||
log.WithFields(logrus.Fields{"id": id, "options": options, "sigType": sigType}).Debug("Creating new StreamSession with signature")
|
||||
conn, err := sam.newGenericSessionWithSignature("STREAM", id, keys, sigType, options, []string{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.WithFields(logrus.Fields{"id": id, "sigType": sigType}).Debug("Created new StreamSession with signature")
|
||||
return &StreamSession{sam.Config.I2PConfig.Sam(), id, conn, keys, time.Duration(600 * time.Second), time.Now(), sigType, "0", "0"}, nil
|
||||
}
|
||||
|
||||
// Creates a new StreamSession with the I2CP- and streaminglib options as
|
||||
// specified. See the I2P documentation for a full list of options.
|
||||
func (sam *SAM) NewStreamSessionWithSignatureAndPorts(id, from, to string, keys i2pkeys.I2PKeys, options []string, sigType string) (*StreamSession, error) {
|
||||
log.WithFields(logrus.Fields{"id": id, "from": from, "to": to, "options": options, "sigType": sigType}).Debug("Creating new StreamSession with signature and ports")
|
||||
conn, err := sam.newGenericSessionWithSignatureAndPorts("STREAM", id, from, to, keys, sigType, options, []string{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.WithFields(logrus.Fields{"id": id, "from": from, "to": to, "sigType": sigType}).Debug("Created new StreamSession with signature and ports")
|
||||
return &StreamSession{sam.Config.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) {
|
||||
log.WithField("name", name).Debug("Looking up address")
|
||||
sam, err := NewSAM(s.samAddr)
|
||||
if err == nil {
|
||||
addr, err := sam.Lookup(name)
|
||||
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
|
||||
}
|
||||
log.WithError(err).Error("Failed to create SAM instance for lookup")
|
||||
return i2pkeys.I2PAddr(""), err
|
||||
}
|
||||
|
||||
// context-aware dialer, eventually...
|
||||
func (s *StreamSession) DialContext(ctx context.Context, n, addr string) (net.Conn, error) {
|
||||
log.WithFields(logrus.Fields{"network": n, "addr": addr}).Debug("DialContext called")
|
||||
return s.DialContextI2P(ctx, n, addr)
|
||||
}
|
||||
|
||||
// context-aware dialer, eventually...
|
||||
func (s *StreamSession) DialContextI2P(ctx context.Context, n, addr string) (*SAMConn, error) {
|
||||
log.WithFields(logrus.Fields{"network": n, "addr": addr}).Debug("DialContextI2P called")
|
||||
if ctx == nil {
|
||||
log.Panic("nil context")
|
||||
panic("nil context")
|
||||
}
|
||||
deadline := s.deadline(ctx, time.Now())
|
||||
@@ -121,6 +166,7 @@ func (s *StreamSession) DialContextI2P(ctx context.Context, n, addr string) (*SA
|
||||
|
||||
i2paddr, err := i2pkeys.NewI2PAddrFromString(addr)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to create I2P address from string")
|
||||
return nil, err
|
||||
}
|
||||
return s.DialI2P(i2paddr)
|
||||
@@ -147,6 +193,7 @@ func minNonzeroTime(a, b time.Time) time.Time {
|
||||
// - now+Timeout
|
||||
// - d.Deadline
|
||||
// - the context's deadline
|
||||
//
|
||||
// Or zero, if none of Timeout, Deadline, or context's deadline is set.
|
||||
func (s *StreamSession) deadline(ctx context.Context, now time.Time) (earliest time.Time) {
|
||||
if s.Timeout != 0 { // including negative, for historical reasons
|
||||
@@ -160,41 +207,52 @@ func (s *StreamSession) deadline(ctx context.Context, now time.Time) (earliest t
|
||||
|
||||
// implement net.Dialer
|
||||
func (s *StreamSession) Dial(n, addr string) (c net.Conn, err error) {
|
||||
log.WithFields(logrus.Fields{"network": n, "addr": addr}).Debug("Dial called")
|
||||
|
||||
var i2paddr i2pkeys.I2PAddr
|
||||
var host string
|
||||
host, _, err = net.SplitHostPort(addr)
|
||||
if err == nil {
|
||||
host, _, err = SplitHostPort(addr)
|
||||
//log.Println("Dialing:", host)
|
||||
if err = IgnorePortError(err); err == nil {
|
||||
// check for name
|
||||
if strings.HasSuffix(host, ".b32.i2p") || strings.HasSuffix(host, ".i2p") {
|
||||
// name lookup
|
||||
i2paddr, err = s.Lookup(host)
|
||||
log.WithFields(logrus.Fields{"host": host, "i2paddr": i2paddr}).Debug("Looked up I2P address")
|
||||
} else {
|
||||
// probably a destination
|
||||
i2paddr = i2pkeys.I2PAddr(host)
|
||||
i2paddr, err = i2pkeys.NewI2PAddrFromBytes([]byte(host))
|
||||
//i2paddr = i2pkeys.I2PAddr(host)
|
||||
//log.Println("Destination:", i2paddr, err)
|
||||
log.WithFields(logrus.Fields{"host": host, "i2paddr": i2paddr}).Debug("Created I2P address from bytes")
|
||||
}
|
||||
if err == nil {
|
||||
return s.DialI2P(i2paddr)
|
||||
}
|
||||
}
|
||||
log.WithError(err).Error("Dial failed")
|
||||
return
|
||||
}
|
||||
|
||||
// Dials to an I2P destination and returns a SAMConn, which implements a net.Conn.
|
||||
func (s *StreamSession) DialI2P(addr i2pkeys.I2PAddr) (*SAMConn, error) {
|
||||
log.WithField("addr", addr).Debug("DialI2P called")
|
||||
sam, err := NewSAM(s.samAddr)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to create new SAM instance")
|
||||
return nil, err
|
||||
}
|
||||
conn := sam.conn
|
||||
_, err = conn.Write([]byte("STREAM CONNECT ID=" + s.id + " FROM_PORT=" + s.from + " TO_PORT=" + s.to + " DESTINATION=" + addr.Base64() + " SILENT=false\n"))
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to write STREAM CONNECT command")
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
buf := make([]byte, 4096)
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil && err != io.EOF {
|
||||
log.WithError(err).Error("Failed to write STREAM CONNECT command")
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
@@ -207,138 +265,44 @@ func (s *StreamSession) DialI2P(addr i2pkeys.I2PAddr) (*SAMConn, error) {
|
||||
case "STATUS":
|
||||
continue
|
||||
case "RESULT=OK":
|
||||
log.Debug("Successfully connected to I2P destination")
|
||||
return &SAMConn{s.keys.Addr(), addr, conn}, nil
|
||||
case "RESULT=CANT_REACH_PEER":
|
||||
log.Error("Can't reach peer")
|
||||
conn.Close()
|
||||
return nil, errors.New("Can not reach peer")
|
||||
case "RESULT=I2P_ERROR":
|
||||
log.Error("I2P internal error")
|
||||
conn.Close()
|
||||
return nil, errors.New("I2P internal error")
|
||||
case "RESULT=INVALID_KEY":
|
||||
log.Error("Invalid key - Stream Session")
|
||||
conn.Close()
|
||||
return nil, errors.New("Invalid key")
|
||||
return nil, errors.New("Invalid key - Stream Session")
|
||||
case "RESULT=INVALID_ID":
|
||||
log.Error("Invalid tunnel ID")
|
||||
conn.Close()
|
||||
return nil, errors.New("Invalid tunnel ID")
|
||||
case "RESULT=TIMEOUT":
|
||||
log.Error("Connection timeout")
|
||||
conn.Close()
|
||||
return nil, errors.New("Timeout")
|
||||
default:
|
||||
log.WithField("error", scanner.Text()).Error("Unknown error")
|
||||
conn.Close()
|
||||
return nil, errors.New("Unknown error: " + scanner.Text() + " : " + string(buf[:n]))
|
||||
}
|
||||
}
|
||||
log.Panic("Unexpected end of StreamSession.DialI2P()")
|
||||
panic("sam3 go library error in StreamSession.DialI2P()")
|
||||
}
|
||||
|
||||
// create a new stream listener to accept inbound connections
|
||||
func (s *StreamSession) Listen() (*StreamListener, error) {
|
||||
log.WithFields(logrus.Fields{"id": s.id, "laddr": s.keys.Addr()}).Debug("Creating new StreamListener")
|
||||
return &StreamListener{
|
||||
session: s,
|
||||
id: s.id,
|
||||
laddr: s.keys.Addr(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type StreamListener struct {
|
||||
// parent stream session
|
||||
session *StreamSession
|
||||
// our session id
|
||||
id string
|
||||
// our local address for this sam socket
|
||||
laddr i2pkeys.I2PAddr
|
||||
}
|
||||
|
||||
func (l *StreamListener) From() string {
|
||||
return l.session.from
|
||||
}
|
||||
|
||||
func (l *StreamListener) To() string {
|
||||
return l.session.to
|
||||
}
|
||||
|
||||
// get our address
|
||||
// implements net.Listener
|
||||
func (l *StreamListener) Addr() net.Addr {
|
||||
return l.laddr
|
||||
}
|
||||
|
||||
// implements net.Listener
|
||||
func (l *StreamListener) Close() error {
|
||||
return l.session.Close()
|
||||
}
|
||||
|
||||
// implements net.Listener
|
||||
func (l *StreamListener) Accept() (net.Conn, error) {
|
||||
return l.AcceptI2P()
|
||||
}
|
||||
|
||||
func ExtractPairString(input, value string) string {
|
||||
parts := strings.Split(input, " ")
|
||||
for _, part := range parts {
|
||||
if strings.HasPrefix(part, value) {
|
||||
kv := strings.SplitN(input, "=", 2)
|
||||
if len(kv) == 2 {
|
||||
return kv[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func ExtractPairInt(input, value string) int {
|
||||
rv, err := strconv.Atoi(ExtractPairString(input, value))
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
func ExtractDest(input string) string {
|
||||
return strings.Split(input, " ")[0]
|
||||
}
|
||||
|
||||
// accept a new inbound connection
|
||||
func (l *StreamListener) AcceptI2P() (*SAMConn, error) {
|
||||
s, err := NewSAM(l.session.samAddr)
|
||||
if err == nil {
|
||||
// we connected to sam
|
||||
// send accept() command
|
||||
_, err = io.WriteString(s.conn, "STREAM ACCEPT ID="+l.id+" SILENT=false\n")
|
||||
// read reply
|
||||
rd := bufio.NewReader(s.conn)
|
||||
// read first line
|
||||
line, err := rd.ReadString(10)
|
||||
log.Println(line)
|
||||
if err == nil {
|
||||
if strings.HasPrefix(line, "STREAM STATUS RESULT=OK") {
|
||||
// we gud read destination line
|
||||
destline, err := rd.ReadString(10)
|
||||
log.Println(destline)
|
||||
if err == nil {
|
||||
dest := ExtractDest(destline)
|
||||
l.session.from = ExtractPairString(destline, "FROM_PORT")
|
||||
l.session.to = ExtractPairString(destline, "TO_PORT")
|
||||
// return wrapped connection
|
||||
dest = strings.Trim(dest, "\n")
|
||||
return &SAMConn{
|
||||
laddr: l.laddr,
|
||||
raddr: i2pkeys.I2PAddr(dest),
|
||||
conn: s.conn,
|
||||
}, nil
|
||||
} else {
|
||||
s.Close()
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
s.Close()
|
||||
return nil, errors.New("invalid sam line: " + line)
|
||||
}
|
||||
} else {
|
||||
s.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
s.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
140
streamListener.go
Normal file
140
streamListener.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package sam3
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-i2p/i2pkeys"
|
||||
)
|
||||
|
||||
type StreamListener struct {
|
||||
// parent stream session
|
||||
session *StreamSession
|
||||
// our session id
|
||||
id string
|
||||
// our local address for this sam socket
|
||||
laddr i2pkeys.I2PAddr
|
||||
}
|
||||
|
||||
func (l *StreamListener) From() string {
|
||||
return l.session.from
|
||||
}
|
||||
|
||||
func (l *StreamListener) To() string {
|
||||
return l.session.to
|
||||
}
|
||||
|
||||
// get our address
|
||||
// implements net.Listener
|
||||
func (l *StreamListener) Addr() net.Addr {
|
||||
return l.laddr
|
||||
}
|
||||
|
||||
// implements net.Listener
|
||||
func (l *StreamListener) Close() error {
|
||||
return l.session.Close()
|
||||
}
|
||||
|
||||
// implements net.Listener
|
||||
func (l *StreamListener) Accept() (net.Conn, error) {
|
||||
return l.AcceptI2P()
|
||||
}
|
||||
|
||||
func ExtractPairString(input, value string) string {
|
||||
log.WithFields(logrus.Fields{"input": input, "value": value}).Debug("ExtractPairString called")
|
||||
parts := strings.Split(input, " ")
|
||||
for _, part := range parts {
|
||||
if strings.HasPrefix(part, value) {
|
||||
kv := strings.SplitN(input, "=", 2)
|
||||
if len(kv) == 2 {
|
||||
log.WithFields(logrus.Fields{"key": kv[0], "value": kv[1]}).Debug("Pair extracted")
|
||||
return kv[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
log.WithFields(logrus.Fields{"input": input, "value": value}).Debug("No pair found")
|
||||
return ""
|
||||
}
|
||||
|
||||
func ExtractPairInt(input, value string) int {
|
||||
rv, err := strconv.Atoi(ExtractPairString(input, value))
|
||||
if err != nil {
|
||||
log.WithFields(logrus.Fields{"input": input, "value": value}).Debug("No pair found")
|
||||
return 0
|
||||
}
|
||||
log.WithField("result", rv).Debug("Pair extracted and converted to int")
|
||||
return rv
|
||||
}
|
||||
|
||||
func ExtractDest(input string) string {
|
||||
log.WithField("input", input).Debug("ExtractDest called")
|
||||
dest := strings.Split(input, " ")[0]
|
||||
log.WithField("dest", dest).Debug("Destination extracted")
|
||||
return strings.Split(input, " ")[0]
|
||||
}
|
||||
|
||||
// accept a new inbound connection
|
||||
func (l *StreamListener) AcceptI2P() (*SAMConn, error) {
|
||||
log.Debug("StreamListener.AcceptI2P() called")
|
||||
s, err := NewSAM(l.session.samAddr)
|
||||
if err == nil {
|
||||
log.Debug("Connected to SAM bridge")
|
||||
// we connected to sam
|
||||
// send accept() command
|
||||
_, err = io.WriteString(s.conn, "STREAM ACCEPT ID="+l.id+" SILENT=false\n")
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to send STREAM ACCEPT command")
|
||||
s.Close()
|
||||
return nil, err
|
||||
}
|
||||
// read reply
|
||||
rd := bufio.NewReader(s.conn)
|
||||
// read first line
|
||||
line, err := rd.ReadString(10)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to read SAM bridge response")
|
||||
s.Close()
|
||||
return nil, err
|
||||
}
|
||||
log.WithField("response", line).Debug("Received SAM bridge response")
|
||||
log.Println(line)
|
||||
if strings.HasPrefix(line, "STREAM STATUS RESULT=OK") {
|
||||
// we gud read destination line
|
||||
destline, err := rd.ReadString(10)
|
||||
if err == nil {
|
||||
dest := ExtractDest(destline)
|
||||
l.session.from = ExtractPairString(destline, "FROM_PORT")
|
||||
l.session.to = ExtractPairString(destline, "TO_PORT")
|
||||
// return wrapped connection
|
||||
dest = strings.Trim(dest, "\n")
|
||||
log.WithFields(logrus.Fields{
|
||||
"dest": dest,
|
||||
"from": l.session.from,
|
||||
"to": l.session.to,
|
||||
}).Debug("Accepted new I2P connection")
|
||||
return &SAMConn{
|
||||
laddr: l.laddr,
|
||||
raddr: i2pkeys.I2PAddr(dest),
|
||||
conn: s.conn,
|
||||
}, nil
|
||||
} else {
|
||||
log.WithError(err).Error("Failed to read destination line")
|
||||
s.Close()
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
log.WithField("line", line).Error("Invalid SAM response")
|
||||
s.Close()
|
||||
return nil, errors.New("invalid sam line: " + line)
|
||||
}
|
||||
} else {
|
||||
log.WithError(err).Error("Failed to connect to SAM bridge")
|
||||
s.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
@@ -1,14 +1,11 @@
|
||||
// +build nettest
|
||||
|
||||
package sam3
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/eyedeekay/sam3/i2pkeys"
|
||||
"github.com/go-i2p/i2pkeys"
|
||||
)
|
||||
|
||||
func Test_StreamingDial(t *testing.T) {
|
||||
|
157
style.css
Normal file
157
style.css
Normal file
@@ -0,0 +1,157 @@
|
||||
/* edgar default CSS file */
|
||||
|
||||
body {
|
||||
font-family: "Roboto";
|
||||
font-family: monospace;
|
||||
text-align: justify;
|
||||
background-color: #373636;
|
||||
color: whitesmoke;
|
||||
font-size: 1.15em;
|
||||
}
|
||||
|
||||
ul {
|
||||
width: 55%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
ol {
|
||||
width: 55%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-top: 1%;
|
||||
}
|
||||
|
||||
p {
|
||||
max-width: 90%;
|
||||
margin-top: 1%;
|
||||
margin-left: 3%;
|
||||
margin-right: 3%;
|
||||
}
|
||||
|
||||
img {
|
||||
float: left;
|
||||
top: 5%;
|
||||
left: 5%;
|
||||
max-width: 60%;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.inline {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.link-button:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.link-button:active {
|
||||
color: red;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: monospace;
|
||||
border-radius: 5%;
|
||||
padding: 1%;
|
||||
border-color: darkgray;
|
||||
font-size: .9em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #C6D9FE;
|
||||
padding: 1%;
|
||||
}
|
||||
|
||||
ul li {
|
||||
color: #C6D9FE;
|
||||
}
|
||||
|
||||
iframe {
|
||||
background: aliceblue;
|
||||
border-radius: 15%;
|
||||
margin: 2%;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 36vw;
|
||||
height: 64vh;
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.editor-toolbar a {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
text-decoration: none !important;
|
||||
color: whitesmoke !important;
|
||||
}
|
||||
|
||||
#feed {
|
||||
width: 60vw;
|
||||
height: unset !important;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
float: right;
|
||||
background-color: #373636;
|
||||
color: whitesmoke;
|
||||
border: #C6D9FE solid 1px;
|
||||
}
|
||||
|
||||
.thread-post,
|
||||
.thread {
|
||||
color: whitesmoke !important;
|
||||
background-color: #373636;
|
||||
border: 1px solid darkgray;
|
||||
font-size: inherit;
|
||||
padding-top: 1%;
|
||||
padding-bottom: 1%;
|
||||
}
|
||||
|
||||
.thread-post {
|
||||
margin-left: 4%;
|
||||
}
|
||||
|
||||
input {
|
||||
text-align: center;
|
||||
color: whitesmoke !important;
|
||||
background-color: #373636;
|
||||
border: 1px solid darkgray;
|
||||
font: normal normal normal 14px/1 FontAwesome;
|
||||
font-size: inherit;
|
||||
padding-top: 1%;
|
||||
padding-bottom: 1%;
|
||||
}
|
||||
|
||||
.thread-hash {
|
||||
text-align: right;
|
||||
color: whitesmoke !important;
|
||||
background-color: #373636;
|
||||
border: 1px solid darkgray;
|
||||
font-size: inherit;
|
||||
padding-top: 1%;
|
||||
padding-bottom: 1%;
|
||||
}
|
||||
|
||||
.post-body {
|
||||
text-align: left;
|
||||
color: whitesmoke !important;
|
||||
font-size: inherit;
|
||||
padding-top: 1%;
|
||||
padding-bottom: 1%;
|
||||
}
|
||||
#show {display:none; }
|
||||
#hide {display:block; }
|
||||
#show:target {display: block; }
|
||||
#hide:target {display: none; }
|
||||
|
||||
#shownav {display:none; }
|
||||
#hidenav {display:block; }
|
||||
#shownav:target {display: block; }
|
||||
#hidenav:target {display: none; }
|
||||
|
||||
#navbar {
|
||||
float: right;
|
||||
width: 10%;
|
||||
}
|
@@ -1,5 +1,13 @@
|
||||
package sam3
|
||||
|
||||
import (
|
||||
"github.com/sirupsen/logrus"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Examples and suggestions for options when creating sessions.
|
||||
var (
|
||||
// Suitable options if you are shuffling A LOT of traffic. If unused, this
|
||||
@@ -10,17 +18,30 @@ var (
|
||||
"inbound.quantity=6", "outbound.quantity=6"}
|
||||
|
||||
// Suitable for shuffling a lot of traffic.
|
||||
Options_Fat = []string{"inbound.length=3", "outbound.length=3",
|
||||
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
|
||||
@@ -38,3 +59,84 @@ var (
|
||||
"inbound.backupQuantity=0", "outbound.backupQuantity=0",
|
||||
"inbound.quantity=2", "outbound.quantity=2"}
|
||||
)
|
||||
|
||||
func PrimarySessionString() string {
|
||||
log.Debug("Determining primary session type")
|
||||
_, err := http.Get("http://127.0.0.1:7070")
|
||||
if err != nil {
|
||||
log.WithError(err).Debug("Failed to connect to 127.0.0.1:7070, trying 127.0.0.1:7657")
|
||||
_, err := http.Get("http://127.0.0.1:7657")
|
||||
if err != nil {
|
||||
return "MASTER"
|
||||
}
|
||||
log.Debug("Connected to 127.0.0.1:7657, attempting to create a PRIMARY session")
|
||||
// at this point we're probably running on Java I2P and thus probably
|
||||
// have a PRIMARY session. Just to be sure, try to make one, check
|
||||
// for errors, then immediately close it.
|
||||
testSam, err := NewSAM(SAMDefaultAddr(""))
|
||||
if err != nil {
|
||||
log.WithError(err).Debug("Failed to create SAM instance, assuming MASTER session")
|
||||
return "MASTER"
|
||||
}
|
||||
newKeys, err := testSam.NewKeys()
|
||||
if err != nil {
|
||||
log.WithError(err).Debug("Failed to create new keys, assuming MASTER session")
|
||||
return "MASTER"
|
||||
}
|
||||
primarySession, err := testSam.newPrimarySession("PRIMARY", "primaryTestTunnel", newKeys, Options_Small)
|
||||
if err != nil {
|
||||
log.WithError(err).Debug("Failed to create primary session, assuming MASTER session")
|
||||
return "MASTER"
|
||||
}
|
||||
primarySession.Close()
|
||||
log.Debug("Successfully created and closed a PRIMARY session")
|
||||
return "PRIMARY"
|
||||
}
|
||||
log.Debug("Connected to 127.0.0.1:7070, assuming MASTER session")
|
||||
return "MASTER"
|
||||
}
|
||||
|
||||
var PrimarySessionSwitch string = PrimarySessionString()
|
||||
|
||||
func getEnv(key, fallback string) string {
|
||||
InitializeSAM3Logger()
|
||||
value, ok := os.LookupEnv(key)
|
||||
if !ok {
|
||||
log.WithFields(logrus.Fields{
|
||||
"key": key,
|
||||
"fallback": fallback,
|
||||
}).Debug("Environment variable not set, using fallback")
|
||||
return fallback
|
||||
}
|
||||
log.WithFields(logrus.Fields{
|
||||
"key": key,
|
||||
"value": value,
|
||||
}).Debug("Retrieved environment variable")
|
||||
return value
|
||||
}
|
||||
|
||||
var SAM_HOST = getEnv("sam_host", "127.0.0.1")
|
||||
var SAM_PORT = getEnv("sam_port", "7656")
|
||||
|
||||
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"
|
||||
}
|
||||
|
Reference in New Issue
Block a user