3 Commits
v0.33.4 ... 3.3

Author SHA1 Message Date
idk
b9e4186119 not for listeners 2021-03-13 17:49:34 -05:00
idk
3f57ff4eaf add ephemeral keys to helper lib 2021-03-13 17:46:14 -05:00
idk
4c6e2f5d88 fix custom dialer on primarysession 2021-03-12 17:46:15 -05:00
27 changed files with 733 additions and 936 deletions

View File

View File

@@ -1,16 +1,16 @@
USER_GH=eyedeekay
VERSION=0.33.4
VERSION=0.33.001
packagename=sam3
echo:
@echo "type make version to do release $(VERSION)"
version:
github-release release -s $(GITHUB_TOKEN) -u $(USER_GH) -r $(packagename) -t v$(VERSION) -d "version $(VERSION)"
gothub release -s $(GITHUB_TOKEN) -u $(USER_GH) -r $(packagename) -t v$(VERSION) -d "version $(VERSION)"
del:
github-release delete -s $(GITHUB_TOKEN) -u $(USER_GH) -r $(packagename) -t v$(VERSION)
gothub delete -s $(GITHUB_TOKEN) -u $(USER_GH) -r $(packagename) -t v$(VERSION)
tar:
tar --exclude .git \
@@ -26,4 +26,4 @@ fmt:
find . -name '*.go' -exec gofmt -w -s {} \;
upload-linux:
github-release upload -R -u $(USER_GH) -r "$(packagename)" -t $(VERSION) -l `sha256sum ` -n "$(packagename)" -f "$(packagename)"
gothub upload -R -u $(USER_GH) -r "$(packagename)" -t $(VERSION) -l `sha256sum ` -n "$(packagename)" -f "$(packagename)"

View File

@@ -1,17 +1,5 @@
# README #
## !!IMPORTANT!!
In the next version, I'll be moving the `i2pkeys` directory to it's own repository
so I can avoid import cycle headaches. Please migrate to the new `i2pkeys` repository
before upgrading your sam3 dependencies. You can probably do this by running:
```sh
find . -name '*.go' -exec sed -i 's|github.com/eyedeekay/sam3/i2pkeys|github.com/eyedeekay/i2pkeys|g' {} \;
```
# 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.
@@ -35,7 +23,7 @@ This library is much better than ccondom (that use BOB), much more stable and mu
**Does not work:**
* Stream Forwarding
* Everything works! :D
* Probably needs some real-world testing
## Documentation ##
@@ -50,17 +38,16 @@ This library is much better than ccondom (that use BOB), much more stable and mu
package main
import (
"github.com/eyedeekay/sam3"
"github.com/eyedeekay/sam3/i2pkeys"
"github.com/majestrate/i2p-tools/sam3"
"fmt"
)
const yoursam = "127.0.0.1:7656" // sam bridge
func client(server i2pkeys.I2PAddr) {
sam, _ := sam3.NewSAM(yoursam)
func client(server I2PAddr) {
sam, _ := NewSAM(yoursam)
keys, _ := sam.NewKeys()
stream, _ := sam.NewStreamSession("clientTun", keys, sam3.Options_Small)
stream, _ := sam.NewStreamSession("clientTun", keys, Options_Small)
fmt.Println("Client: Connecting to " + server.Base32())
conn, _ := stream.DialI2P(server)
conn.Write([]byte("Hello world!"))
@@ -70,9 +57,11 @@ func client(server i2pkeys.I2PAddr) {
func main() {
sam, _ := NewSAM(yoursam)
keys, _ := sam.NewKeys()
stream, _ := sam.NewStreamSession("serverTun", keys, sam3.Options_Medium)
stream, _ := sam.NewStreamSession("serverTun", keys, Options_Medium)
listener, _ := stream.Listen()
go client(keys.Addr())
stream, _ := sam.NewStreamSession("serverTun", keys, Options_Medium)
listener, _ := stream.Listen()
conn, _ := listener.Accept()
buf := make([]byte, 4096)
n, _ := conn.Read(buf)

View File

@@ -1,15 +1,14 @@
package sam3
import (
"github.com/eyedeekay/sam3/i2pkeys"
"net"
"time"
"github.com/eyedeekay/i2pkeys"
)
/*
import (
. "github.com/eyedeekay/i2pkeys"
. "github.com/eyedeekay/sam3/i2pkeys"
)
*/
// Implements net.Conn

View File

@@ -7,7 +7,7 @@ import (
"strconv"
"strings"
"github.com/eyedeekay/i2pkeys"
"github.com/eyedeekay/sam3/i2pkeys"
)
// I2PConfig is a struct which manages I2P configuration options
@@ -347,7 +347,7 @@ func (cfg *Config) DatagramSession() (session *DatagramSession, err error) {
// determine udp port
var portstr string
_, portstr, err = net.SplitHostPort(cfg.Addr)
if IgnorePortError(err) == nil {
if err == nil {
var port int
port, err = strconv.Atoi(portstr)
if err == nil && port > 0 {

View File

@@ -7,7 +7,7 @@ import (
"strconv"
"time"
"github.com/eyedeekay/i2pkeys"
"github.com/eyedeekay/sam3/i2pkeys"
)
// The DatagramSession implements net.PacketConn. It works almost like ordinary
@@ -33,7 +33,7 @@ func (s *SAM) NewDatagramSession(id string, keys i2pkeys.I2PKeys, options []stri
if udpPort == 0 {
udpPort = 7655
}
lhost, _, err := SplitHostPort(s.conn.LocalAddr().String())
lhost, _, err := net.SplitHostPort(s.conn.LocalAddr().String())
if err != nil {
s.Close()
return nil, err
@@ -46,7 +46,7 @@ func (s *SAM) NewDatagramSession(id string, keys i2pkeys.I2PKeys, options []stri
if err != nil {
return nil, err
}
rhost, _, err := SplitHostPort(s.conn.RemoteAddr().String())
rhost, _, err := net.SplitHostPort(s.conn.RemoteAddr().String())
if err != nil {
s.Close()
return nil, err
@@ -56,10 +56,6 @@ func (s *SAM) NewDatagramSession(id string, keys i2pkeys.I2PKeys, options []stri
return nil, err
}
_, lport, err := net.SplitHostPort(udpconn.LocalAddr().String())
if err != nil {
s.Close()
return nil, err
}
conn, err := s.newGenericSession("DATAGRAM", id, keys, options, []string{"PORT=" + lport})
if err != nil {
return nil, err
@@ -88,13 +84,7 @@ func (s *DatagramSession) DialRemote(net, addr string) (net.PacketConn, error) {
}
func (s *DatagramSession) DialI2PRemote(net string, addr net.Addr) (*DatagramSession, error) {
switch addr.(type) {
case *i2pkeys.I2PAddr:
s.remoteAddr = addr.(*i2pkeys.I2PAddr)
case i2pkeys.I2PAddr:
i2paddr := addr.(i2pkeys.I2PAddr)
s.remoteAddr = &i2paddr
}
s.remoteAddr = addr.(*i2pkeys.I2PAddr)
return s, nil
}

View File

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

24
emit.go
View File

@@ -3,8 +3,6 @@ package sam3
import (
"fmt"
"log"
"net"
"strings"
)
type SAMEmit struct {
@@ -98,25 +96,3 @@ func NewEmit(opts ...func(*SAMEmit) error) (*SAMEmit, error) {
}
return &emit, nil
}
func IgnorePortError(err error) error {
if err == nil {
return nil
}
if strings.Contains(err.Error(), "missing port in address") {
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.Println("host: " + hostport)
host = hostport
port = "0"
}
}
return host, port, nil
}

1
go.mod
View File

@@ -4,7 +4,6 @@ go 1.12
require (
github.com/eyedeekay/goSam v0.32.31-0.20210122211817-f97683379f23
github.com/eyedeekay/i2pkeys v0.0.0-20220310052025-204d4ae6dcae // indirect
github.com/google/renameio v1.0.0 // indirect
github.com/riobard/go-x25519 v0.0.0-20190716001027-10cc4d8d0b33
github.com/rogpeppe/go-internal v1.6.2 // indirect

2
go.sum
View File

@@ -12,8 +12,6 @@ github.com/eyedeekay/goSam v0.32.31-0.20210122211024-dddd8ea916d6 h1:seMFdfTWvmA
github.com/eyedeekay/goSam v0.32.31-0.20210122211024-dddd8ea916d6/go.mod h1:UgJnih/LpotwKriwVPOEa6yPDM2NDdVrKfLtS5DOLPE=
github.com/eyedeekay/goSam v0.32.31-0.20210122211817-f97683379f23 h1:AHm/EzBilSQH+RFgEuslnlCpVQd88MQWx7KHW/VIQlc=
github.com/eyedeekay/goSam v0.32.31-0.20210122211817-f97683379f23/go.mod h1:UgJnih/LpotwKriwVPOEa6yPDM2NDdVrKfLtS5DOLPE=
github.com/eyedeekay/i2pkeys v0.0.0-20220310052025-204d4ae6dcae h1:SwegHeaf4pkDMB24UltIvJlj2+nd06QUZAbs8BDyfjM=
github.com/eyedeekay/i2pkeys v0.0.0-20220310052025-204d4ae6dcae/go.mod h1:W9KCm9lqZ+Ozwl3dwcgnpPXAML97+I8Jiht7o5A8YBM=
github.com/eyedeekay/sam3 v0.32.32/go.mod h1:qRA9KIIVxbrHlkj+ZB+OoxFGFgdKeGp1vSgPw26eOVU=
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY=
github.com/getlantern/errors v1.0.1/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A=

View File

@@ -1,17 +1,18 @@
package sam
import (
"fmt"
"io/ioutil"
"log"
"net"
"os"
"github.com/eyedeekay/i2pkeys"
"github.com/eyedeekay/sam3"
"github.com/eyedeekay/sam3/i2pkeys"
)
func NetListener(name, samaddr, keyspath string) (net.Listener, error) {
return I2PListener(name, sam3.SAMDefaultAddr(samaddr), keyspath)
return I2PListener(name, samaddr, keyspath)
}
// I2PListener is a convenience function which takes a SAM tunnel name, a SAM address and a filename.
@@ -19,16 +20,15 @@ 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...")
listener, err := I2PStreamSession(name, sam3.SAMDefaultAddr(samaddr), keyspath)
listener, err := I2PStreamSession(name, samaddr, keyspath)
if keyspath != "" {
err = ioutil.WriteFile(keyspath+".i2p.public.txt", []byte(listener.Keys().Addr().Base32()), 0644)
if err != nil {
log.Fatalf("error storing I2P base32 address in adjacent text file, %s", err)
return nil, fmt.Errorf("error storing I2P base32 address in adjacent text file, %s", err)
}
}
log.Printf("Listening on: %s", listener.Addr().Base32())
return listener.Listen()
return listener.Listen() //, err
}
// I2PStreamSession is a convenience function which returns a sam3.StreamSession instead
@@ -36,13 +36,40 @@ func I2PListener(name, samaddr, keyspath string) (*sam3.StreamListener, error) {
// of the user.
func I2PStreamSession(name, samaddr, keyspath string) (*sam3.StreamSession, error) {
log.Printf("Starting and registering I2P session...")
sam, err := sam3.NewSAM(sam3.SAMDefaultAddr(samaddr))
sam, err := sam3.NewSAM(samaddr)
if err != nil {
log.Fatalf("error connecting to SAM to %s: %s", sam3.SAMDefaultAddr(samaddr), err)
return nil, fmt.Errorf("error connecting to SAM to %s: %s", samaddr, err)
}
keys, err := GenerateOrLoadKeys(keyspath, sam)
if err != nil {
return nil, err
var keys *i2pkeys.I2PKeys
if keyspath != "" {
if _, err := os.Stat(keyspath + ".i2p.private"); os.IsNotExist(err) {
f, err := os.Create(keyspath + ".i2p.private")
if err != nil {
return nil, fmt.Errorf("unable to open I2P keyfile for writing: %s", err)
}
defer f.Close()
tkeys, err := sam.NewKeys()
if err != nil {
return nil, fmt.Errorf("unable to generate I2P Keys, %s", err)
}
keys = &tkeys
err = i2pkeys.StoreKeysIncompat(*keys, f)
if err != nil {
return nil, fmt.Errorf("unable to save newly generated I2P Keys, %s", err)
}
} else {
tkeys, err := i2pkeys.LoadKeys(keyspath + ".i2p.private")
if err != nil {
return nil, fmt.Errorf("unable to load I2P Keys: %e", err)
}
keys = &tkeys
}
} else {
tkeys, err := sam.NewKeys()
if err != nil {
return nil, fmt.Errorf("unable to generate I2P Keys, %s", err)
}
keys = &tkeys
}
stream, err := sam.NewStreamSession(name, *keys, sam3.Options_Medium)
return stream, err
@@ -52,13 +79,40 @@ func I2PStreamSession(name, samaddr, keyspath string) (*sam3.StreamSession, erro
// 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))
sam, err := sam3.NewSAM(samaddr)
if err != nil {
log.Fatalf("error connecting to SAM to %s: %s", sam3.SAMDefaultAddr(samaddr), err)
return nil, fmt.Errorf("error connecting to SAM to %s: %s", samaddr, err)
}
keys, err := GenerateOrLoadKeys(keyspath, sam)
if err != nil {
return nil, err
var keys *i2pkeys.I2PKeys
if keyspath != "" {
if _, err := os.Stat(keyspath + ".i2p.private"); os.IsNotExist(err) {
f, err := os.Create(keyspath + ".i2p.private")
if err != nil {
return nil, fmt.Errorf("unable to open I2P keyfile for writing: %s", err)
}
defer f.Close()
tkeys, err := sam.NewKeys()
if err != nil {
return nil, fmt.Errorf("unable to generate I2P Keys, %s", err)
}
keys = &tkeys
err = i2pkeys.StoreKeysIncompat(*keys, f)
if err != nil {
return nil, fmt.Errorf("unable to save newly generated I2P Keys, %s", err)
}
} else {
tkeys, err := i2pkeys.LoadKeys(keyspath + ".i2p.private")
if err != nil {
return nil, fmt.Errorf("unable to load I2P Keys: %e", err)
}
keys = &tkeys
}
} else {
tkeys, err := sam.NewKeys()
if err != nil {
return nil, fmt.Errorf("unable to generate I2P Keys, %s", err)
}
keys = &tkeys
}
gram, err := sam.NewDatagramSession(name, *keys, sam3.Options_Medium, 0)
return gram, err
@@ -68,58 +122,41 @@ func I2PDatagramSession(name, samaddr, keyspath string) (*sam3.DatagramSession,
// 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))
sam, err := sam3.NewSAM(samaddr)
if err != nil {
log.Fatalf("error connecting to SAM to %s: %s", sam3.SAMDefaultAddr(samaddr), err)
return nil, fmt.Errorf("error connecting to SAM to %s: %s", samaddr, err)
}
keys, err := GenerateOrLoadKeys(keyspath, sam)
if err != nil {
return nil, err
var keys *i2pkeys.I2PKeys
if keyspath != "" {
if _, err := os.Stat(keyspath + ".i2p.private"); os.IsNotExist(err) {
f, err := os.Create(keyspath + ".i2p.private")
if err != nil {
return nil, fmt.Errorf("unable to open I2P keyfile for writing: %s", err)
}
defer f.Close()
tkeys, err := sam.NewKeys()
if err != nil {
return nil, fmt.Errorf("unable to generate I2P Keys, %s", err)
}
keys = &tkeys
err = i2pkeys.StoreKeysIncompat(*keys, f)
if err != nil {
return nil, fmt.Errorf("unable to save newly generated I2P Keys, %s", err)
}
} else {
tkeys, err := i2pkeys.LoadKeys(keyspath + ".i2p.private")
if err != nil {
return nil, fmt.Errorf("unable to load I2P Keys: %e", err)
}
keys = &tkeys
}
} else {
tkeys, err := sam.NewKeys()
if err != nil {
return nil, fmt.Errorf("unable to generate I2P Keys, %s", err)
}
keys = &tkeys
}
gram, err := sam.NewPrimarySession(name, *keys, sam3.Options_Medium)
return gram, err
}
// GenerateOrLoadKeys is a convenience function which takes a filename and a SAM session.
// if the SAM session is nil, a new one will be created with the defaults.
// The keyspath must be the path to a place to store I2P keys. The keyspath will be suffixed with
// .i2p.private for the private keys, and public.txt for the b32 addresses.
// If the keyspath.i2p.private file does not exist, keys will be generated and stored in that file.
// if the keyspath.i2p.private does exist, keys will be loaded from that location and returned
func GenerateOrLoadKeys(keyspath string, sam *sam3.SAM) (keys *i2pkeys.I2PKeys, err error) {
if sam == nil {
sam, err = sam3.NewSAM(sam3.SAMDefaultAddr("127.0.0.1:7656"))
if err != nil {
return nil, err
}
}
if _, err := os.Stat(keyspath + ".i2p.private"); os.IsNotExist(err) {
f, err := os.Create(keyspath + ".i2p.private")
if err != nil {
log.Fatalf("unable to open I2P keyfile for writing: %s", err)
}
defer f.Close()
tkeys, err := sam.NewKeys()
if err != nil {
log.Fatalf("unable to generate I2P Keys, %s", err)
}
keys = &tkeys
err = i2pkeys.StoreKeysIncompat(*keys, f)
if err != nil {
log.Fatalf("unable to save newly generated I2P Keys, %s", err)
}
} else {
tkeys, err := i2pkeys.LoadKeys(keyspath + ".i2p.private")
if err != nil {
log.Fatalf("unable to load I2P Keys: %e", err)
}
keys = &tkeys
}
return keys, nil
}
// GenerateKeys is a shorter version of GenerateOrLoadKeys which generates keys and stores them in a file.
// it always uses a new default SAM session.
func GenerateKeys(keyspath string) (keys *i2pkeys.I2PKeys, err error) {
return GenerateOrLoadKeys(keyspath, nil)
}

334
i2pkeys/I2PAddr.go Normal file
View File

@@ -0,0 +1,334 @@
package i2pkeys
import (
"bytes"
"crypto"
"crypto/ed25519"
"crypto/rand"
"crypto/sha256"
"encoding/base32"
"encoding/base64"
"errors"
"io"
"os"
"strings"
"github.com/eyedeekay/goSam"
)
var (
i2pB64enc *base64.Encoding = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-~")
i2pB32enc *base32.Encoding = base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567")
)
var FakePort = false
// 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)
}
func (k I2PKeys) Network() string {
return k.Address.Network()
}
// Returns the public keys of the I2PKeys.
func (k I2PKeys) Addr() I2PAddr {
return k.Address
}
func (k I2PKeys) Public() crypto.PublicKey {
return k.Address
}
func (k I2PKeys) Private() []byte {
src := strings.Split(k.String(), k.Addr().String())[0]
var dest []byte
_, err := i2pB64enc.Decode(dest, []byte(src))
panic(err)
return dest
}
type SecretKey interface {
Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error)
}
func (k I2PKeys) SecretKey() SecretKey {
var pk ed25519.PrivateKey = k.Private()
return pk
}
func (k I2PKeys) PrivateKey() crypto.PrivateKey {
var pk ed25519.PrivateKey = k.Private()
_, err := pk.Sign(rand.Reader, []byte("nonsense"), crypto.Hash(0))
if err != nil {
//TODO: Elgamal, P256, P384, P512, GOST? keys?
}
return pk
}
func (k I2PKeys) Ed25519PrivateKey() *ed25519.PrivateKey {
return k.SecretKey().(*ed25519.PrivateKey)
}
/*func (k I2PKeys) ElgamalPrivateKey() *ed25519.PrivateKey {
return k.SecretKey().(*ed25519.PrivateKey)
}*/
//func (k I2PKeys) Decrypt(rand io.Reader, msg []byte, opts crypto.DecrypterOpts) (plaintext []byte, err error) {
//return k.SecretKey().(*ed25519.PrivateKey).Decrypt(rand, msg, opts)
//}
func (k I2PKeys) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) {
return k.SecretKey().(*ed25519.PrivateKey).Sign(rand, digest, opts)
}
// 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
}
func (k I2PKeys) HostnameEntry(hostname string, opts crypto.SignerOpts) (string, error) {
sig, err := k.Sign(rand.Reader, []byte(hostname), opts)
if err != nil {
return "", err
}
return string(sig), nil
}
// 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 (base32-encoded)
func (a I2PAddr) String() string {
return string(a.Base32())
}
// 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() + addr.Port()
}
func (addr I2PAddr) Port() (str string) {
if FakePort {
return ":8080"
}
return ""
}
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()
}
func NewDestination(samaddr string, sigType ...string) (I2PKeys, error) {
if samaddr == "" {
samaddr = "127.0.0.1:7656"
}
client, err := goSam.NewClient(samaddr)
if err != nil {
return I2PKeys{}, err
}
var sigtmp string
if len(sigType) > 0 {
sigtmp = sigType[0]
}
pub, priv, err := client.NewDestination(sigtmp)
if err != nil {
return I2PKeys{}, err
}
addr, err := NewI2PAddrFromBytes([]byte(pub))
if err != nil {
return I2PKeys{}, err
}
keys := NewKeys(addr, priv+pub)
if err != nil {
return I2PKeys{}, err
}
return keys, nil
}

21
i2pkeys/I2PAddr_test.go Normal file
View File

@@ -0,0 +1,21 @@
package i2pkeys
import (
"fmt"
"testing"
// "time"
)
const yoursam = "127.0.0.1:7656"
func Test_Basic(t *testing.T) {
fmt.Println("Test_Basic")
fmt.Println("\tAttaching to SAM at " + yoursam)
keys, err := NewDestination("")
if err != nil {
fmt.Println(err.Error())
t.Fail()
return
}
fmt.Println(keys.String())
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -1,296 +0,0 @@
<html>
<head>
<title>
README
</title>
<meta name="author" content="eyedeekay" />
<meta name="description" content="sam3" />
<meta name="keywords" content="master" />
<link rel="stylesheet" type="text/css" href="style.css" />
</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
</a>
</h1>
<h2>
!!IMPORTANT!!
</h2>
<p>
In the next version, Ill be moving the
<code>
i2pkeys
</code>
directory to its 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>
<pre><code>find . -name &#39;*.go&#39; -exec sed -i &#39;s|github.com/eyedeekay/sam3/i2pkeys|github.com/eyedeekay/i2pkeys|g&#39; {} \;
</code></pre>
<h1>
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>
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>
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>
Examples
</h2>
<pre><code>package main
import (
&#34;github.com/eyedeekay/sam3&#34;
&#34;github.com/eyedeekay/sam3/i2pkeys&#34;
&#34;fmt&#34;
)
const yoursam = &#34;127.0.0.1:7656&#34; // sam bridge
func client(server i2pkeys.I2PAddr) {
sam, _ := sam3.NewSAM(yoursam)
keys, _ := sam.NewKeys()
stream, _ := sam.NewStreamSession(&#34;clientTun&#34;, keys, sam3.Options_Small)
fmt.Println(&#34;Client: Connecting to &#34; + server.Base32())
conn, _ := stream.DialI2P(server)
conn.Write([]byte(&#34;Hello world!&#34;))
return
}
func main() {
sam, _ := NewSAM(yoursam)
keys, _ := sam.NewKeys()
stream, _ := sam.NewStreamSession(&#34;serverTun&#34;, keys, sam3.Options_Medium)
listener, _ := stream.Listen()
go client(keys.Addr())
conn, _ := listener.Accept()
buf := make([]byte, 4096)
n, _ := conn.Read(buf)
fmt.Println(&#34;Server received: &#34; + string(buf[:n]))
}
</code></pre>
<p>
The above will write to the terminal:
</p>
<pre><code>Client: Connecting to zjnvfh4hs3et5vtz35ogwzrws26zvwkcad5uo5esecvg4qpk5b4a.b32.i2p
Server received: Hello world!
</code></pre>
<p>
Error handling was omitted in the above code for readability.
</p>
<h2>
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>
License
</h2>
<p>
Public domain.
</p>
<h2>
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>

View File

@@ -9,7 +9,7 @@ import (
"strings"
"time"
"github.com/eyedeekay/i2pkeys"
"github.com/eyedeekay/sam3/i2pkeys"
)
const (
@@ -33,8 +33,8 @@ type PrimarySession struct {
Deadline time.Time
sigType string
Config SAMEmit
stsess map[string]*StreamSession
dgsess map[string]*DatagramSession
stsess *StreamSession
dgsess *DatagramSession
// from string
// to string
}
@@ -62,10 +62,12 @@ func (ss *PrimarySession) Close() error {
// Returns the I2P destination (the address) of the stream session
func (ss *PrimarySession) Addr() i2pkeys.I2PAddr {
// fmt.Println("LOCAL ADDR")
return ss.keys.Addr()
}
func (ss *PrimarySession) LocalAddr() net.Addr {
// fmt.Println("LOCAL ADDR")
aa := ss.keys.Addr()
return &aa
}
@@ -77,11 +79,9 @@ func (ss *PrimarySession) Keys() i2pkeys.I2PKeys {
func (sam *PrimarySession) Dial(network, addr string) (net.Conn, error) {
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)
}
return nil, fmt.Errorf("Error: Must specify a valid network type")
@@ -89,65 +89,59 @@ func (sam *PrimarySession) Dial(network, addr string) (net.Conn, error) {
// DialTCP implements x/dialer
func (sam *PrimarySession) DialTCP(network string, laddr, raddr net.Addr) (net.Conn, error) {
ts, ok := sam.stsess[network+raddr.String()[0:4]]
var err error
if !ok {
ts, err = sam.NewUniqueStreamSubSession(network + raddr.String()[0:4])
if sam.stsess == nil {
sam.stsess, err = sam.NewUniqueStreamSubSession(network + RandString())
if err != nil {
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())
return sam.stsess.Dial(network, raddr.String())
}
func (sam *PrimarySession) DialTCPI2P(network string, laddr, raddr string) (net.Conn, error) {
ts, ok := sam.stsess[network+raddr[0:4]]
func (sam *PrimarySession) DialTCPI2P(network string, laddr, raddr string) (*SAMConn, error) {
var err error
if !ok {
ts, err = sam.NewUniqueStreamSubSession(network + laddr)
if sam.stsess == nil {
sam.stsess, err = sam.NewUniqueStreamSubSession(network + RandString())
if err != nil {
return nil, err
}
sam.stsess[network+raddr[0:4]] = ts
ts, _ = sam.stsess[network+raddr[0:4]]
}
return ts.Dial(network, raddr)
c, err := sam.stsess.Dial(network, raddr)
if err != nil {
return nil, err
}
return c.(*SAMConn), nil
}
// DialUDP implements x/dialer
func (sam *PrimarySession) DialUDP(network string, laddr, raddr net.Addr) (net.PacketConn, error) {
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 sam.dgsess == nil {
sam.dgsess, err = sam.NewDatagramSubSession(network+raddr.String()[0:4], 0)
if err != nil {
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())
return sam.dgsess.Dial(network, raddr.String())
}
func (sam *PrimarySession) DialUDPI2P(network, laddr, raddr string) (*DatagramSession, error) {
ds, ok := sam.dgsess[network+raddr[0:4]]
var err error
if !ok {
ds, err = sam.NewDatagramSubSession(network+laddr, 0)
if sam.dgsess == nil {
sam.dgsess, err = sam.NewDatagramSubSession(network+raddr[0:4], 0)
if err != nil {
return nil, err
}
sam.dgsess[network+raddr[0:4]] = ds
ds, _ = sam.dgsess[network+raddr[0:4]]
}
return ds.Dial(network, raddr)
return sam.dgsess.Dial(network, raddr)
}
func (s *PrimarySession) Lookup(name string) (a net.Addr, err error) {
var sam *SAM
name = strings.Split(name, ":")[0]
if len(strings.Split(name, ":")) <= 1 {
name += ":80"
}
sam, err = NewSAM(s.samAddr)
if err == nil {
defer sam.Close()
@@ -157,43 +151,38 @@ func (s *PrimarySession) Lookup(name string) (a net.Addr, err error) {
}
func (sam *PrimarySession) Resolve(network, addr string) (net.Addr, error) {
fmt.Println("LOGGING RESOLUTION", network, addr)
return sam.Lookup(addr)
}
func (sam *PrimarySession) ResolveTCPAddr(network, dest string) (net.Addr, error) {
fmt.Println("LOGGING RESOLUTION", network, dest)
return sam.Lookup(dest)
}
func (sam *PrimarySession) ResolveUDPAddr(network, dest string) (net.Addr, error) {
fmt.Println("LOGGING RESOLUTION", network, dest)
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) {
return sam.newPrimarySession(PrimarySessionSwitch, id, keys, options)
}
func (sam *SAM) newPrimarySession(primarySessionSwitch string, id string, keys i2pkeys.I2PKeys, options []string) (*PrimarySession, error) {
conn, err := sam.newGenericSession(primarySessionSwitch, id, keys, options, []string{})
conn, err := sam.newGenericSession("PRIMARY", id, keys, options, []string{})
if err != nil {
return nil, err
}
ssesss := make(map[string]*StreamSession)
dsesss := make(map[string]*DatagramSession)
return &PrimarySession{sam.Config.I2PConfig.Sam(), id, conn, keys, time.Duration(600 * time.Second), time.Now(), Sig_NONE, sam.Config, ssesss, dsesss}, nil
return &PrimarySession{sam.Config.I2PConfig.Sam(), id, conn, keys, time.Duration(600 * time.Second), time.Now(), Sig_NONE, sam.Config, nil, nil}, 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) {
conn, err := sam.newGenericSessionWithSignature(PrimarySessionSwitch, id, keys, sigType, options, []string{})
conn, err := sam.newGenericSessionWithSignature("PRIMARY", id, keys, sigType, options, []string{})
if err != nil {
return nil, err
}
ssesss := make(map[string]*StreamSession)
dsesss := make(map[string]*DatagramSession)
return &PrimarySession{sam.Config.I2PConfig.Sam(), id, conn, keys, time.Duration(600 * time.Second), time.Now(), sigType, sam.Config, ssesss, dsesss}, nil
return &PrimarySession{sam.Config.I2PConfig.Sam(), id, conn, keys, time.Duration(600 * time.Second), time.Now(), sigType, sam.Config, nil, nil}, nil
}
// Creates a new session with the style of either "STREAM", "DATAGRAM" or "RAW",
@@ -219,13 +208,13 @@ func (sam *PrimarySession) newGenericSubSessionWithSignatureAndPorts(style, id,
conn := sam.conn
fp := ""
tp := ""
if from != "0" && from != "" {
if from != "0" {
fp = " FROM_PORT=" + from
}
if to != "0" && to != "" {
if to != "0" {
tp = " TO_PORT=" + to
}
scmsg := []byte("SESSION ADD STYLE=" + style + " ID=" + id + fp + tp + " " + strings.Join(extras, " ") + "\n")
scmsg := []byte("SESSION ADD STYLE=" + style + fp + tp + " ID=" + id + " " + strings.Join(extras, " ") + "\n")
for m, i := 0, 0; m != len(scmsg); i++ {
if i == 15 {
conn.Close()
@@ -245,7 +234,6 @@ func (sam *PrimarySession) newGenericSubSessionWithSignatureAndPorts(style, id,
return nil, err
}
text := string(buf[:n])
//log.Println("SAM:", text)
if strings.HasPrefix(text, session_ADDOK) {
//if sam.keys.String() != text[len(session_ADDOK):len(text)-1] {
//conn.Close()
@@ -260,7 +248,7 @@ func (sam *PrimarySession) newGenericSubSessionWithSignatureAndPorts(style, id,
return nil, errors.New("Duplicate destination")
} else if text == session_INVALID_KEY {
conn.Close()
return nil, errors.New("Invalid key - Primary Session")
return nil, errors.New("Invalid key")
} else if strings.HasPrefix(text, session_I2P_ERROR) {
conn.Close()
return nil, errors.New("I2P error " + text[len(session_I2P_ERROR):])
@@ -273,21 +261,23 @@ func (sam *PrimarySession) newGenericSubSessionWithSignatureAndPorts(style, id,
// Creates a new StreamSession with the I2CP- and streaminglib options as
// specified. See the I2P documentation for a full list of options.
func (sam *PrimarySession) NewStreamSubSession(id string) (*StreamSession, error) {
conn, err := sam.newGenericSubSession("STREAM", id, []string{})
s := RandString()
conn, err := sam.newGenericSubSession("STREAM", id+s, []string{})
if err != nil {
return nil, err
}
return &StreamSession{sam.Config.I2PConfig.Sam(), id, conn, sam.keys, time.Duration(600 * time.Second), time.Now(), Sig_NONE, "0", "0"}, nil
return &StreamSession{sam.Config.I2PConfig.Sam(), id + s, 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) {
conn, err := sam.newGenericSubSession("STREAM", id, []string{})
s := RandString()
conn, err := sam.newGenericSubSession("STREAM", id+s, []string{})
if err != nil {
return nil, err
}
return &StreamSession{sam.Config.I2PConfig.Sam(), id, conn, sam.keys, time.Duration(600 * time.Second), time.Now(), Sig_NONE, randport(), randport()}, nil
return &StreamSession{sam.Config.I2PConfig.Sam(), id + s, conn, sam.keys, time.Duration(600 * time.Second), time.Now(), Sig_NONE, randport(), "0"}, nil
}
// Creates a new StreamSession with the I2CP- and streaminglib options as
@@ -319,7 +309,7 @@ func (s *PrimarySession) NewDatagramSubSession(id string, udpPort int) (*Datagra
if udpPort == 0 {
udpPort = 7655
}
lhost, _, err := SplitHostPort(s.conn.LocalAddr().String())
lhost, _, err := net.SplitHostPort(s.conn.LocalAddr().String())
if err != nil {
s.Close()
return nil, err
@@ -332,7 +322,7 @@ func (s *PrimarySession) NewDatagramSubSession(id string, udpPort int) (*Datagra
if err != nil {
return nil, err
}
rhost, _, err := SplitHostPort(s.conn.RemoteAddr().String())
rhost, _, err := net.SplitHostPort(s.conn.RemoteAddr().String())
if err != nil {
s.Close()
return nil, err
@@ -342,10 +332,6 @@ func (s *PrimarySession) NewDatagramSubSession(id string, udpPort int) (*Datagra
return nil, err
}
_, lport, err := net.SplitHostPort(udpconn.LocalAddr().String())
if err != nil {
s.Close()
return nil, err
}
conn, err := s.newGenericSubSession("DATAGRAM", id, []string{"PORT=" + lport})
if err != nil {
return nil, err
@@ -362,7 +348,7 @@ func (s *PrimarySession) NewRawSubSession(id string, udpPort int) (*RawSession,
if udpPort == 0 {
udpPort = 7655
}
lhost, _, err := SplitHostPort(s.conn.LocalAddr().String())
lhost, _, err := net.SplitHostPort(s.conn.LocalAddr().String())
if err != nil {
s.Close()
return nil, err
@@ -375,7 +361,7 @@ func (s *PrimarySession) NewRawSubSession(id string, udpPort int) (*RawSession,
if err != nil {
return nil, err
}
rhost, _, err := SplitHostPort(s.conn.RemoteAddr().String())
rhost, _, err := net.SplitHostPort(s.conn.RemoteAddr().String())
if err != nil {
s.Close()
return nil, err
@@ -385,10 +371,6 @@ func (s *PrimarySession) NewRawSubSession(id string, udpPort int) (*RawSession,
return nil, err
}
_, lport, err := net.SplitHostPort(udpconn.LocalAddr().String())
if err != nil {
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 {

View File

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

View File

@@ -1,13 +1,14 @@
// +build nettest
package sam3
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"strings"
"testing"
"time"
"github.com/eyedeekay/sam3/i2pkeys"
)
func Test_PrimaryStreamingDial(t *testing.T) {
@@ -97,31 +98,27 @@ func Test_PrimaryStreamingServerClient(t *testing.T) {
}
defer sam.Close()
fmt.Println("\tServer: Creating tunnel")
ss, err := sam.NewUniqueStreamSubSession("PrimaryServerClientTunnel")
ss, err := sam.NewStreamSubSession("primaryExampleServerTun")
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
}
*/
/*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 {
@@ -185,13 +182,26 @@ func ExamplePrimaryStreamSession() {
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"})
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 {
log.Fatal(err.Error())
return
}
defer sam.Close()
conn, err := sam.Dial("tcp", "idk.i2p") //someone.Base32())
// See the example Option_* variables.
ss, err := sam.NewStreamSubSession("stream_example")
if err != nil {
fmt.Println(err.Error())
return
}
ss.Close()
someone, err := earlysam.Lookup("idk.i2p")
if err != nil {
fmt.Println(err.Error())
return
}
conn, err := ss.DialI2P(someone)
if err != nil {
fmt.Println(err.Error())
return
@@ -211,6 +221,8 @@ func ExamplePrimaryStreamSession() {
fmt.Println("Read HTTP/HTML from idk.i2p")
log.Println("Read HTTP/HTML from idk.i2p")
}
return
// Output:
//Sending HTTP GET /
//Read HTTP/HTML from idk.i2p
@@ -223,86 +235,73 @@ func ExamplePrimaryStreamListener() {
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)
earlysam, err := NewSAM(yoursam)
if err != nil {
log.Fatal(err.Error())
return
}
defer latesam.Close()
keys2, err := latesam.NewKeys()
defer earlysam.Close()
keys, err := earlysam.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"})
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()
quit := make(chan bool)
// Client connecting to the server
go func(server i2pkeys.I2PAddr) {
cs, err := sam.NewStreamSubSession("client_example")
if err != nil {
fmt.Println(err.Error())
quit <- false
return
}
defer cs.Close()
conn, err := cs.DialI2P(server)
if err != nil {
fmt.Println(err.Error())
quit <- false
return
}
buf := make([]byte, 256)
n, err := conn.Read(buf)
if err != nil {
fmt.Println(err.Error())
quit <- false
return
}
fmt.Println(string(buf[:n]))
quit <- true
}(keys.Addr()) // end of client
ss, err := sam.NewStreamSubSession("server_example")
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())
defer ss.Close()
l, err := ss.Listen()
if err != nil {
fmt.Println(err.Error())
return
}
defer resp.Body.Close()
r, err := ioutil.ReadAll(resp.Body)
conn, err := l.Accept()
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Println("Got response: " + string(r))
conn.Write([]byte("Hello world!"))
<-quit // waits for client to die, for example only
// Output:
// Got response: Hello world!
}
type exitHandler struct {
}
func (e *exitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello world!"))
//Hello world!
}

6
raw.go
View File

@@ -7,7 +7,7 @@ import (
"strconv"
"time"
"github.com/eyedeekay/i2pkeys"
"github.com/eyedeekay/sam3/i2pkeys"
)
// The RawSession provides no authentication of senders, and there is no sender
@@ -34,7 +34,7 @@ func (s *SAM) NewRawSession(id string, keys i2pkeys.I2PKeys, options []string, u
if udpPort == 0 {
udpPort = 7655
}
lhost, _, err := SplitHostPort(s.conn.LocalAddr().String())
lhost, _, err := net.SplitHostPort(s.conn.LocalAddr().String())
if err != nil {
s.Close()
return nil, err
@@ -47,7 +47,7 @@ func (s *SAM) NewRawSession(id string, keys i2pkeys.I2PKeys, options []string, u
if err != nil {
return nil, err
}
rhost, _, err := SplitHostPort(s.conn.RemoteAddr().String())
rhost, _, err := net.SplitHostPort(s.conn.RemoteAddr().String())
if err != nil {
s.Close()
return nil, err

View File

@@ -6,7 +6,7 @@ import (
"errors"
"strings"
"github.com/eyedeekay/i2pkeys"
"github.com/eyedeekay/sam3/i2pkeys"
)
type SAMResolver struct {
@@ -55,7 +55,7 @@ func (sam *SAMResolver) Resolve(name string) (i2pkeys.I2PAddr, error) {
if text == "RESULT=OK" {
continue
} else if text == "RESULT=INVALID_KEY" {
errStr += "Invalid key - resolver."
errStr += "Invalid key."
} else if text == "RESULT=KEY_NOT_FOUND" {
errStr += "Unable to resolve " + name
} else if text == "NAME="+name {

View File

@@ -11,9 +11,11 @@ import (
"os"
"strings"
"github.com/eyedeekay/i2pkeys"
"github.com/eyedeekay/sam3/i2pkeys"
)
. "github.com/eyedeekay/i2pkeys"
import (
. "github.com/eyedeekay/sam3/i2pkeys"
)
// Used for controlling I2Ps SAMv3.
@@ -257,7 +259,7 @@ func (sam *SAM) newGenericSessionWithSignatureAndPorts(style, id, from, to strin
return nil, errors.New("Duplicate destination")
} else if text == session_INVALID_KEY {
conn.Close()
return nil, errors.New("Invalid key - SAM session")
return nil, errors.New("Invalid key")
} else if strings.HasPrefix(text, session_I2P_ERROR) {
conn.Close()
return nil, errors.New("I2P error " + text[len(session_I2P_ERROR):])

View File

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

137
stream.go
View File

@@ -6,11 +6,13 @@ import (
"context"
"errors"
"io"
"log"
"net"
"strconv"
"strings"
"time"
"github.com/eyedeekay/i2pkeys"
"github.com/eyedeekay/sam3/i2pkeys"
)
// Represents a streaming session.
@@ -26,18 +28,6 @@ type StreamSession struct {
to string
}
func (s *StreamSession) SetDeadline(t time.Time) error {
return s.conn.SetDeadline(t)
}
func (s *StreamSession) SetReadDeadline(t time.Time) error {
return s.conn.SetReadDeadline(t)
}
func (s *StreamSession) SetWriteDeadline(t time.Time) error {
return s.conn.SetWriteDeadline(t)
}
func (ss *StreamSession) From() string {
return ss.from
}
@@ -64,10 +54,6 @@ func (ss *StreamSession) Addr() i2pkeys.I2PAddr {
return ss.keys.Addr()
}
func (ss *StreamSession) LocalAddr() net.Addr {
return ss.keys.Addr()
}
// Returns the keys associated with the stream session
func (ss *StreamSession) Keys() i2pkeys.I2PKeys {
return ss.keys
@@ -108,7 +94,7 @@ func (s *StreamSession) Lookup(name string) (i2pkeys.I2PAddr, error) {
sam, err := NewSAM(s.samAddr)
if err == nil {
addr, err := sam.Lookup(name)
defer sam.Close()
sam.Close()
return addr, err
}
return i2pkeys.I2PAddr(""), err
@@ -177,19 +163,15 @@ func (s *StreamSession) Dial(n, addr string) (c net.Conn, err error) {
var i2paddr i2pkeys.I2PAddr
var host string
host, _, err = SplitHostPort(addr)
//log.Println("Dialing:", host)
if err = IgnorePortError(err); err == nil {
host, _, err = net.SplitHostPort(addr)
if err == nil {
// check for name
if strings.HasSuffix(host, ".b32.i2p") || strings.HasSuffix(host, ".i2p") {
// name lookup
i2paddr, err = s.Lookup(host)
//log.Println("Lookup:", i2paddr, err)
} else {
// probably a destination
i2paddr, err = i2pkeys.NewI2PAddrFromBytes([]byte(host))
//i2paddr = i2pkeys.I2PAddr(host)
//log.Println("Destination:", i2paddr, err)
i2paddr = i2pkeys.I2PAddr(host)
}
if err == nil {
return s.DialI2P(i2paddr)
@@ -234,7 +216,7 @@ func (s *StreamSession) DialI2P(addr i2pkeys.I2PAddr) (*SAMConn, error) {
return nil, errors.New("I2P internal error")
case "RESULT=INVALID_KEY":
conn.Close()
return nil, errors.New("Invalid key - Stream Session")
return nil, errors.New("Invalid key")
case "RESULT=INVALID_ID":
conn.Close()
return nil, errors.New("Invalid tunnel ID")
@@ -257,3 +239,106 @@ func (s *StreamSession) Listen() (*StreamListener, error) {
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
}

View File

@@ -1,115 +0,0 @@
package sam3
import (
"bufio"
"errors"
"io"
"log"
"net"
"strconv"
"strings"
"github.com/eyedeekay/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 {
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)
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
}

View File

@@ -1,3 +1,5 @@
// +build nettest
package sam3
import (
@@ -6,7 +8,7 @@ import (
"strings"
"testing"
"github.com/eyedeekay/i2pkeys"
"github.com/eyedeekay/sam3/i2pkeys"
)
func Test_StreamingDial(t *testing.T) {

157
style.css
View File

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

View File

@@ -1,11 +1,5 @@
package sam3
import (
"net"
"net/http"
"os"
)
// Examples and suggestions for options when creating sessions.
var (
// Suitable options if you are shuffling A LOT of traffic. If unused, this
@@ -57,51 +51,3 @@ var (
"inbound.backupQuantity=0", "outbound.backupQuantity=0",
"inbound.quantity=2", "outbound.quantity=2"}
)
func PrimarySessionString() string {
_, err := http.Get("http://127.0.0.1:7070")
if err != nil {
_, err := http.Get("http://127.0.0.1:7657")
if err != nil {
return "MASTER"
}
// 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 {
return "MASTER"
}
newKeys, err := testSam.NewKeys()
if err != nil {
return "MASTER"
}
primarySession, err := testSam.newPrimarySession("PRIMARY", "primaryTestTunnel", newKeys, Options_Small)
if err != nil {
return "MASTER"
}
primarySession.Close()
return "PRIMARY"
}
return "MASTER"
}
var PrimarySessionSwitch string = PrimarySessionString()
func getEnv(key, fallback string) string {
value, ok := os.LookupEnv(key)
if !ok {
return fallback
}
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 == "" {
return net.JoinHostPort(SAM_HOST, SAM_PORT)
}
return fallforward
}