Initial commit
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*~
|
||||
*.swp
|
91
I2PAddr.go
Normal file
91
I2PAddr.go
Normal file
@ -0,0 +1,91 @@
|
||||
package sam3
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base32"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
)
|
||||
|
||||
|
||||
|
||||
var (
|
||||
i2pB64enc *base64.Encoding = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-~")
|
||||
i2pB32enc *base32.Encoding = base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567")
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
// Base64 representation of the private keys
|
||||
type I2PKeys struct {
|
||||
addr I2PAddr
|
||||
priv string
|
||||
}
|
||||
|
||||
// Returns the string of I2PKeys (Base64-encoded)
|
||||
func (k I2PKeys) String() string {
|
||||
return k.priv
|
||||
}
|
||||
|
||||
// Returns the I2PAddr (I2P destination) belonging to the I2PKeys.
|
||||
func (k I2PKeys) Addr() I2PAddr {
|
||||
return k.addr
|
||||
}
|
||||
|
||||
// Base64 representation of the public keys
|
||||
type I2PAddr string
|
||||
|
||||
// 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.
|
||||
func NewI2PAddrFromString(addr string) (I2PAddr, error) {
|
||||
// very basic check
|
||||
if len(addr) > 4096 || len(addr) < 516 {
|
||||
return I2PAddr(""), errors.New("Not an I2P address")
|
||||
}
|
||||
buf := make([]byte, 4096)
|
||||
if _, err := i2pB64enc.Decode(buf, []byte(addr)); err != nil {
|
||||
return I2PAddr(""), errors.New("Address is not base64-encoded")
|
||||
}
|
||||
return I2PAddr(addr), nil
|
||||
}
|
||||
|
||||
// Creates a new I2P address from a byte array (pure binary, as you would have
|
||||
// if you have *not* base64-encoded it.)
|
||||
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
|
||||
}
|
||||
|
||||
// Returns the *.b32.i2p address of the I2P address.
|
||||
func (addr I2PAddr) Base32() string {
|
||||
hash := sha256.New()
|
||||
hash.Write([]byte(string(addr)))
|
||||
digest := hash.Sum(nil)
|
||||
b32addr := make([]byte, 56)
|
||||
i2pB32enc.Encode(b32addr, digest)
|
||||
return string(b32addr[:52]) + ".b32.i2p"
|
||||
}
|
||||
|
||||
// Shortcut to I2PAddr.Base32()
|
||||
func Base32(addr I2PAddr) string {
|
||||
return addr.Base32()
|
||||
}
|
40
README.md
Normal file
40
README.md
Normal file
@ -0,0 +1,40 @@
|
||||
# README #
|
||||
|
||||
go library for the I2P 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.
|
||||
|
||||
## Support/TODO ##
|
||||
|
||||
**What works:**
|
||||
|
||||
* Utils
|
||||
* Resolving URLs to I2P destinations
|
||||
* .b32.i2p hashes
|
||||
* Generating keys/i2p destinations
|
||||
* Streaming
|
||||
* DialI2P() - Connecting to stuff in I2P
|
||||
* Listen()/Accept() - Handling incomming connections
|
||||
|
||||
**Does not work:**
|
||||
|
||||
* Datagram sockets
|
||||
* Raw sockets
|
||||
|
||||
## Documentation ##
|
||||
|
||||
* Enter `godoc -http=:8081` into your terminal and hit enter.
|
||||
* Goto http://localhost:8081, click packages, and navigate to sam3
|
||||
|
||||
## Testing ##
|
||||
|
||||
* `go test` runs the whole suite
|
||||
* `go test -short` runs the shorter variant
|
||||
|
||||
## License ##
|
||||
|
||||
Public domain.
|
||||
|
||||
## Author ##
|
||||
|
||||
Kalle Vedin `kalle.vedin@fripost.org`
|
48
SAMConn.go
Normal file
48
SAMConn.go
Normal file
@ -0,0 +1,48 @@
|
||||
package sam3
|
||||
|
||||
import (
|
||||
"time"
|
||||
"net"
|
||||
)
|
||||
|
||||
type SAMConn struct {
|
||||
laddr I2PAddr
|
||||
raddr I2PAddr
|
||||
conn net.Conn
|
||||
}
|
||||
|
||||
func (sc SAMConn) Read(buf []byte) (int, error) {
|
||||
n, err := sc.conn.Read(buf)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (sc SAMConn) Write(buf []byte) (int, error) {
|
||||
n, err := sc.conn.Write(buf)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (sc SAMConn) Close() error {
|
||||
return sc.conn.Close()
|
||||
}
|
||||
|
||||
func (sc SAMConn) LocalAddr() I2PAddr {
|
||||
return sc.laddr
|
||||
}
|
||||
|
||||
func (sc SAMConn) RemoteAddr() I2PAddr {
|
||||
return sc.raddr
|
||||
}
|
||||
|
||||
func (sc SAMConn) SetDeadline(t time.Time) error {
|
||||
return sc.conn.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (sc SAMConn) SetReadDeadline(t time.Time) error {
|
||||
return sc.conn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (sc SAMConn) SetWriteDeadline(t time.Time) error {
|
||||
return sc.conn.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
|
54
datagram.go
Normal file
54
datagram.go
Normal file
@ -0,0 +1,54 @@
|
||||
package sam3
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// The DatagramSession implements net.PacketConn. It works almost like ordinary
|
||||
// UDP, except that datagrams may be much larger (max 31kB). These datagrams are
|
||||
// also end-to-end encrypted, signed and includes replay-protection. And they
|
||||
// are also built to be surveillance-resistant (yey!).
|
||||
type DatagramSession struct {
|
||||
Addr I2PAddr
|
||||
Priv I2PKeys
|
||||
lport int
|
||||
}
|
||||
|
||||
// Implements net.PacketConn
|
||||
func (s *DatagramSession) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
|
||||
return 0, I2PAddr(""), errors.New("Not implemented.")
|
||||
}
|
||||
|
||||
// Implements net.PacketConn
|
||||
func (s *DatagramSession) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
||||
return 0, errors.New("Not implemented.")
|
||||
}
|
||||
|
||||
// Implements net.PacketConn
|
||||
func (s *DatagramSession) Close() error {
|
||||
return errors.New("Not implemented.")
|
||||
}
|
||||
|
||||
// Implements net.PacketConn
|
||||
func (s *DatagramSession) LocalAddr() net.Addr {
|
||||
return I2PAddr("")
|
||||
}
|
||||
|
||||
// Implements net.PacketConn
|
||||
func (s *DatagramSession) SetDeadline(t time.Time) error {
|
||||
return errors.New("Not implemented.")
|
||||
}
|
||||
|
||||
// Implements net.PacketConn
|
||||
func (s *DatagramSession) SetReadDeadline(t time.Time) error {
|
||||
return errors.New("Not implemented.")
|
||||
}
|
||||
|
||||
// Implements net.PacketConn
|
||||
func (s *DatagramSession) SetWriteDeadline(t time.Time) error {
|
||||
return errors.New("Not implemented.")
|
||||
}
|
||||
|
||||
|
190
sam3.go
Normal file
190
sam3.go
Normal file
@ -0,0 +1,190 @@
|
||||
package sam3
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"net"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
// "fmt"
|
||||
)
|
||||
|
||||
|
||||
|
||||
// Used for controlling I2Ps SAMv3.
|
||||
type SAM struct {
|
||||
address string // ipv4:port
|
||||
conn net.Conn
|
||||
}
|
||||
|
||||
const (
|
||||
session_OK = "SESSION STATUS RESULT=OK DESTINATION="
|
||||
session_DUPLICATE_ID = "SESSION STATUS RESULT=DUPLICATED_ID\n"
|
||||
session_DUPLICATE_DEST = "SESSION STATUS RESULT=DUPLICATED_DEST\n"
|
||||
session_INVALID_KEY = "SESSION STATUS RESULT=INVALID_KEY\n"
|
||||
session_I2P_ERROR = "SESSION STATUS RESULT=I2P_ERROR MESSAGE="
|
||||
)
|
||||
|
||||
// Creates a new controller for the I2P routers SAM bridge.
|
||||
func NewSAM(address string) (*SAM, error) {
|
||||
conn, err := net.Dial("tcp4", address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := conn.Write([]byte("HELLO VERSION MIN=3.0 MAX=3.0\n")); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf := make([]byte, 256)
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if string(buf[:n]) == "HELLO REPLY RESULT=OK VERSION=3.0\n" {
|
||||
return &SAM{address, conn}, nil
|
||||
} else if string(buf[:n]) == "HELLO REPLY RESULT=NOVERSION\n" {
|
||||
return nil, errors.New("That SAM bridge does not support SAMv3.")
|
||||
} else {
|
||||
return nil, errors.New(string(buf[:n]))
|
||||
}
|
||||
}
|
||||
|
||||
func (sam *SAM) NewKeys() (I2PAddr, I2PKeys, error) {
|
||||
if _, err := sam.conn.Write([]byte("DEST GENERATE\n")); err != nil {
|
||||
return I2PAddr(""), I2PKeys{}, err
|
||||
}
|
||||
buf := make([]byte, 8192)
|
||||
n, err := sam.conn.Read(buf)
|
||||
if err != nil {
|
||||
return I2PAddr(""), I2PKeys{}, err
|
||||
}
|
||||
s := bufio.NewScanner(bytes.NewReader(buf[:n]))
|
||||
s.Split(bufio.ScanWords)
|
||||
|
||||
var pub, priv string
|
||||
for s.Scan() {
|
||||
text := s.Text()
|
||||
if text == "DEST" {
|
||||
continue
|
||||
} else if text == "REPLY" {
|
||||
continue
|
||||
} else if strings.HasPrefix(text, "PUB=") {
|
||||
pub = text[4:]
|
||||
} else if strings.HasPrefix(text, "PRIV=") {
|
||||
priv = text[5:]
|
||||
} else {
|
||||
return I2PAddr(""), I2PKeys{}, errors.New("Failed to parse keys.")
|
||||
}
|
||||
}
|
||||
return I2PAddr(pub), I2PKeys{I2PAddr(pub), priv}, nil
|
||||
}
|
||||
|
||||
func (sam *SAM) Lookup(name string) (I2PAddr, error) {
|
||||
if _, err := sam.conn.Write([]byte("NAMING LOOKUP NAME=" + name + "\n")); err != nil {
|
||||
return I2PAddr(""), err
|
||||
}
|
||||
buf := make([]byte, 8192)
|
||||
n, err := sam.conn.Read(buf)
|
||||
if err != nil {
|
||||
return I2PAddr(""), err
|
||||
}
|
||||
if n <= 13 || !strings.HasPrefix(string(buf[:n]), "NAMING REPLY ") {
|
||||
return I2PAddr(""), errors.New("Failed to parse.")
|
||||
}
|
||||
s := bufio.NewScanner(bytes.NewReader(buf[13:n]))
|
||||
s.Split(bufio.ScanWords)
|
||||
|
||||
errStr := ""
|
||||
for s.Scan() {
|
||||
text := s.Text()
|
||||
if text == "RESULT=OK" {
|
||||
continue
|
||||
} else if text == "RESULT=INVALID_KEY" {
|
||||
errStr += "Invalid key."
|
||||
} else if text == "RESULT=KEY_NOT_FOUND" {
|
||||
errStr += "Unable to resolve " + name
|
||||
} else if text == "NAME=" + name {
|
||||
continue
|
||||
} else if strings.HasPrefix(text, "VALUE=") {
|
||||
return I2PAddr(text[6:]), nil
|
||||
} else if strings.HasPrefix(text, "MESSAGE=") {
|
||||
errStr += " " + text[8:]
|
||||
} else {
|
||||
return I2PAddr(""), errors.New("Failed to parse lookup reply.")
|
||||
}
|
||||
}
|
||||
return I2PAddr(""), errors.New(errStr)
|
||||
}
|
||||
|
||||
// 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. Returns the newly created connection
|
||||
// to the SAMv3 bridge.
|
||||
func (sam *SAM) newGenericSession(style, id string, keys I2PKeys, options []string) (net.Conn, error) {
|
||||
sam2, err := NewSAM(sam.address)
|
||||
if err != nil {
|
||||
return nil, errors.New("Unable to create new streaming tunnel.")
|
||||
}
|
||||
optStr := ""
|
||||
for _, opt := range options {
|
||||
optStr += "OPTION=" + opt + " "
|
||||
}
|
||||
|
||||
conn := sam2.conn
|
||||
scmsg := []byte("SESSION CREATE STYLE=" + style + " ID=" + id + " DESTINATION=" + keys.String() + " " + optStr + "\n")
|
||||
for m, i:=0, 0; m!=len(scmsg); i++ {
|
||||
if i == 15 {
|
||||
conn.Close()
|
||||
return nil, errors.New("writing to SAM failed")
|
||||
}
|
||||
n, err := conn.Write(scmsg[m:])
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
m += n
|
||||
}
|
||||
buf := make([]byte, 4096)
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
text := string(buf[:n])
|
||||
if strings.HasPrefix(text, session_OK) {
|
||||
if keys.String() != text[len(session_OK):len(text)-1] {
|
||||
return nil, errors.New("SAMv3 created a tunnel with keys other than the ones we asked it for")
|
||||
}
|
||||
return conn, nil //&StreamSession{id, conn, keys, nil, sync.RWMutex{}, nil}, nil
|
||||
} else if text == session_DUPLICATE_ID {
|
||||
conn.Close()
|
||||
return nil, errors.New("Duplicate tunnel name")
|
||||
} else if text == session_DUPLICATE_DEST {
|
||||
conn.Close()
|
||||
return nil, errors.New("Duplicate destination")
|
||||
} else if text == session_INVALID_KEY {
|
||||
conn.Close()
|
||||
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):])
|
||||
} else {
|
||||
conn.Close()
|
||||
return nil, errors.New("Unable to parse SAMv3 reply: " + text)
|
||||
}
|
||||
}
|
||||
|
||||
func (sam *SAM) NewDatagramSession(tunnelName string, keys I2PKeys, length, variance, backup int) (*DatagramSession, error) {
|
||||
return nil, errors.New("Not implemented.")
|
||||
}
|
||||
|
||||
func (sam *SAM) Close() error {
|
||||
if err := sam.conn.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
230
sam_test.go
Normal file
230
sam_test.go
Normal file
@ -0,0 +1,230 @@
|
||||
package sam3
|
||||
|
||||
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"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)
|
||||
sam, err := NewSAM(yoursam)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error)
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("\tCreating new keys...")
|
||||
addr, keys, err := sam.NewKeys()
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
t.Fail()
|
||||
} else {
|
||||
fmt.Println("\tAddress created: " + addr.Base32())
|
||||
fmt.Println("\tI2PKeys: " + string(keys.priv)[:50] + "(...etc)")
|
||||
}
|
||||
|
||||
addr2, err := sam.Lookup("zzz.i2p")
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
t.Fail()
|
||||
} else {
|
||||
fmt.Println("\tzzz.i2p = " + addr2.Base32())
|
||||
}
|
||||
|
||||
if err := sam.Close(); err != nil {
|
||||
fmt.Println(err.Error())
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
func Test_GenericSession(t *testing.T) {
|
||||
if testing.Short() {
|
||||
return
|
||||
}
|
||||
fmt.Println("Test_GenericSession")
|
||||
sam, err := NewSAM(yoursam)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error)
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
_, keys, err := sam.NewKeys()
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
t.Fail()
|
||||
} else {
|
||||
conn1, err := sam.newGenericSession("STREAM", "testTun", keys, []string{})
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
t.Fail()
|
||||
} else {
|
||||
conn1.Close()
|
||||
}
|
||||
conn2, err := sam.newGenericSession("STREAM", "testTun", keys, []string{"inbound.length=1", "outbound.length=1", "inbound.lengthVariance=1", "outbound.lengthVariance=1", "inbound.quantity=1", "outbound.quantity=1"})
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
t.Fail()
|
||||
} else {
|
||||
conn2.Close()
|
||||
}
|
||||
conn3, err := sam.newGenericSession("DATAGRAM", "testTun", keys, []string{"inbound.length=1", "outbound.length=1", "inbound.lengthVariance=1", "outbound.lengthVariance=1", "inbound.quantity=1", "outbound.quantity=1"})
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
t.Fail()
|
||||
} else {
|
||||
conn3.Close()
|
||||
}
|
||||
}
|
||||
if err := sam.Close(); err != nil {
|
||||
fmt.Println(err.Error())
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
func Test_StreamingDial(t *testing.T) {
|
||||
if testing.Short() {
|
||||
return
|
||||
}
|
||||
fmt.Println("Test_StreamingDial")
|
||||
sam, err := NewSAM(yoursam)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error)
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
defer sam.Close()
|
||||
_, keys, err := sam.NewKeys()
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
fmt.Println("\tBuilding tunnel")
|
||||
ss, err := sam.NewStreamSession("streamTun", 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())
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
fmt.Println("\tLooking up forum.i2p")
|
||||
forumAddr, err := sam.Lookup("forum.i2p")
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
fmt.Println("\tDialing forum.i2p")
|
||||
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(forum.i2p)? It replied %d bytes, but nothing that looked like http/html", n)
|
||||
} else {
|
||||
fmt.Println("\tRead HTTP/HTML from forum.i2p")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_StreamingServerClient(t *testing.T) {
|
||||
if testing.Short() {
|
||||
return
|
||||
}
|
||||
fmt.Println("Test_StreamingServerClient")
|
||||
sam, err := NewSAM(yoursam)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
defer sam.Close()
|
||||
_, keys, err := sam.NewKeys()
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
fmt.Println("\tServer: Creating tunnel")
|
||||
ss, err := sam.NewStreamSession("serverTun", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
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 := sam2.NewStreamSession("clientTun", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"})
|
||||
if err != nil {
|
||||
c <- false
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
w <- true
|
||||
fmt.Println("\tServer: Accept()ing on tunnel")
|
||||
conn, err := l.Accept()
|
||||
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]))
|
||||
}
|
||||
|
||||
|
||||
|
199
stream.go
Normal file
199
stream.go
Normal file
@ -0,0 +1,199 @@
|
||||
package sam3
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Represents a streaming session.
|
||||
type StreamSession struct {
|
||||
samAddr string // address to the sam bridge (ipv4:port)
|
||||
id string // tunnel name
|
||||
conn net.Conn // connection to sam bridge
|
||||
keys I2PKeys // i2p destination keys
|
||||
listener *StreamListener // used for accepting inbound calls
|
||||
l sync.Mutex // lock for this struct
|
||||
err error
|
||||
}
|
||||
|
||||
// Returns the local tunnel name of the I2P tunnel used to carry the stream session
|
||||
func (ss StreamSession) ID() string {
|
||||
return ss.id
|
||||
}
|
||||
|
||||
// Returns the I2P destination (the address) of the stream session
|
||||
func (ss StreamSession) Addr() I2PAddr {
|
||||
return ss.keys.Addr()
|
||||
}
|
||||
|
||||
// Returns the cypher keys associated with the stream session
|
||||
func (ss StreamSession) Keys() I2PKeys {
|
||||
return ss.keys
|
||||
}
|
||||
|
||||
// Creates a new StreamSession with the I2CP- and streaminglib options as
|
||||
// specified. All other StreamingSession constructors are variants of this one.
|
||||
func (sam *SAM) NewStreamSession(id string, keys I2PKeys, options []string) (*StreamSession, error) {
|
||||
conn, err := sam.newGenericSession("STREAM", id, keys, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &StreamSession{sam.address, id, conn, keys, nil, sync.Mutex{}, nil}, nil
|
||||
}
|
||||
|
||||
// Dials to an I2P destination, returns a SAMConn, which implements a net.Conn.
|
||||
func (s *StreamSession) DialI2P(addr I2PAddr) (*SAMConn, error) {
|
||||
sam, err := NewSAM(s.samAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn := sam.conn
|
||||
_,err = conn.Write([]byte("STREAM CONNECT ID=" + s.id + " DESTINATION=" + addr.Base64() + " SILENT=false\n"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf := make([]byte, 4096)
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
scanner := bufio.NewScanner(bytes.NewReader(buf[:n]))
|
||||
scanner.Split(bufio.ScanWords)
|
||||
for scanner.Scan() {
|
||||
switch scanner.Text() {
|
||||
case "STREAM" :
|
||||
continue
|
||||
case "STATUS" :
|
||||
continue
|
||||
case "RESULT=OK" :
|
||||
return &SAMConn{s.keys.addr, addr, conn}, nil
|
||||
case "RESULT=CANT_REACH_PEER" :
|
||||
return nil, errors.New("Can not reach peer")
|
||||
case "RESULT=I2P_ERROR" :
|
||||
return nil, errors.New("I2P internal error")
|
||||
case "RESULT=INVALID_KEY" :
|
||||
return nil, errors.New("Invalid key")
|
||||
case "RESULT=INVALID_ID" :
|
||||
return nil, errors.New("Invalid tunnel ID")
|
||||
case "RESULT=TIMEOUT" :
|
||||
return nil, errors.New("Timeout")
|
||||
default :
|
||||
return nil, errors.New("Unknown error: " + scanner.Text() + " : " + string(buf[:n]))
|
||||
}
|
||||
}
|
||||
panic("sam3 go library error in StreamSession.DialI2P()")
|
||||
}
|
||||
|
||||
// Returns a listener for the I2P destination (I2PAddr) associated with the
|
||||
// StreamSession.
|
||||
func (s *StreamSession) Listen() (*StreamListener, error) {
|
||||
sam, err := NewSAM(s.conn.RemoteAddr().String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
listener, err := net.Listen("tcp4", ":0")
|
||||
_, lport, err := net.SplitHostPort(listener.Addr().String())
|
||||
if err != nil {
|
||||
sam.Close()
|
||||
return nil, err
|
||||
}
|
||||
conn := sam.conn
|
||||
_, err = conn.Write([]byte("STREAM FORWARD ID=" + s.id + " PORT=" + lport + " SILENT=false\n"))
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
buf := make([]byte, 512)
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
scanner := bufio.NewScanner(bytes.NewReader(buf[:n]))
|
||||
scanner.Split(bufio.ScanWords)
|
||||
for scanner.Scan() {
|
||||
switch strings.TrimSpace(scanner.Text()) {
|
||||
case "STREAM" :
|
||||
continue
|
||||
case "STATUS" :
|
||||
continue
|
||||
case "RESULT=OK" :
|
||||
port,_ := strconv.Atoi(lport)
|
||||
return &StreamListener{conn, listener, port, s.keys.Addr()}, nil
|
||||
case "RESULT=I2P_ERROR" :
|
||||
conn.Close()
|
||||
return nil, errors.New("I2P internal error")
|
||||
case "RESULT=INVALID_ID" :
|
||||
conn.Close()
|
||||
return nil, errors.New("Invalid tunnel ID")
|
||||
default :
|
||||
conn.Close()
|
||||
return nil, errors.New("Unknown error: " + scanner.Text() + " : " + string(buf[:n]))
|
||||
}
|
||||
}
|
||||
panic("sam3 go library error in StreamSession.Listener()")
|
||||
}
|
||||
|
||||
// Implements net.Listener for I2P streaming sessions
|
||||
type StreamListener struct {
|
||||
conn net.Conn
|
||||
listener net.Listener
|
||||
lport int
|
||||
laddr I2PAddr
|
||||
}
|
||||
|
||||
const defaultListenReadLen = 516
|
||||
|
||||
// Accepts incomming connections to your StreamSession tunnel. Implements net.Listener
|
||||
func (l *StreamListener) Accept() (*SAMConn, error) {
|
||||
conn, err := l.listener.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf := make([]byte, defaultListenReadLen)
|
||||
n, err := conn.Read(buf)
|
||||
if n < defaultListenReadLen {
|
||||
return nil, errors.New("Unknown destination type: " + string(buf[:n]))
|
||||
}
|
||||
// I2P inserts the I2P address ("destination") of the connecting peer into the datastream, followed by
|
||||
// a \n. Since the length of a destination may vary, this reads until a newline is found. At the time
|
||||
// of writing, the length is never less then, and almost always equals 516 bytes, which is why
|
||||
// defaultListenReadLen is 516.
|
||||
if rune(buf[defaultListenReadLen - 1]) != '\n' {
|
||||
abuf := make([]byte, 1)
|
||||
for {
|
||||
n, err := conn.Read(abuf)
|
||||
if n != 1 || err != nil {
|
||||
return nil, errors.New("Failed to decode connecting peers I2P destination.")
|
||||
}
|
||||
buf = append(buf, abuf[0])
|
||||
if rune(abuf[0]) == '\n' { break }
|
||||
}
|
||||
}
|
||||
rAddr, err := NewI2PAddrFromString(string(buf[:len(buf)-1])) // the address minus the trailing newline
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, errors.New("Could not determine connecting tunnels address.")
|
||||
}
|
||||
return &SAMConn{l.laddr, rAddr, conn}, nil
|
||||
}
|
||||
|
||||
// Implements net.Listener
|
||||
func (l *StreamListener) Close() error {
|
||||
err := l.listener.Close()
|
||||
err2 := l.conn.Close()
|
||||
if err2 != nil {
|
||||
return err2
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Implements net.Listener
|
||||
func (l *StreamListener) Addr() net.Addr {
|
||||
return l.laddr
|
||||
}
|
36
suggestedOptions.go
Normal file
36
suggestedOptions.go
Normal file
@ -0,0 +1,36 @@
|
||||
package sam3
|
||||
|
||||
var (
|
||||
// Suitable options if you are shuffling A LOT of traffic. If unused, this
|
||||
// will waste your resources.
|
||||
Options_Humongous = []string{"inbound.length=3", "outbound.length=3",
|
||||
"inbound.lengthVariance=1", "outbound.lengthVariance=1",
|
||||
"inbound.backupQuantity=3", "outbound.backupQuantity=3",
|
||||
"inbound.quantity=6", "outbound.quantity=6"}
|
||||
|
||||
// Suitable for shuffling a lot of traffic.
|
||||
Options_Fat = []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 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"}
|
||||
|
||||
// Suitable for small and quick dataflows.
|
||||
Options_Small = []string{"inbound.length=3", "outbound.length=3",
|
||||
"inbound.lengthVariance=1", "outbound.lengthVariance=1",
|
||||
"inbound.backupQuantity=0", "outbound.backupQuantity=0",
|
||||
"inbound.quantity=1", "outbound.quantity=1"}
|
||||
|
||||
// Does not use any anonymization, you connect directly to others tunnel
|
||||
// endpoints, thus revealing your identity but not theirs. Use this only
|
||||
// if you don't care.
|
||||
Options_Warning_ZeroHop = []string{"inbound.length=0", "outbound.length=0",
|
||||
"inbound.lengthVariance=0", "outbound.lengthVariance=0",
|
||||
"inbound.backupQuantity=0", "outbound.backupQuantity=0",
|
||||
"inbound.quantity=2", "outbound.quantity=2"}
|
||||
)
|
Reference in New Issue
Block a user