add config stubs
This commit is contained in:
73
config.go
Normal file
73
config.go
Normal file
@ -0,0 +1,73 @@
|
||||
package sam3
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// sam config
|
||||
|
||||
// options map
|
||||
type Options map[string]string
|
||||
|
||||
// obtain sam options as list of strings
|
||||
func (opts Options) AsList() (ls []string) {
|
||||
for k, v := range opts {
|
||||
ls = append(ls, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Config is the config type for the sam connector api for i2p which allows applications to 'speak' with i2p
|
||||
type Config struct {
|
||||
Addr string
|
||||
Opts Options
|
||||
Session string
|
||||
Keyfile string
|
||||
}
|
||||
|
||||
// create new sam connector from config with a stream session
|
||||
func (cfg *Config) StreamSession() (session *StreamSession, err error) {
|
||||
// connect
|
||||
var s *SAM
|
||||
s, err = NewSAM(cfg.Addr)
|
||||
if err == nil {
|
||||
// ensure keys exist
|
||||
var keys I2PKeys
|
||||
keys, err = s.EnsureKeyfile(cfg.Keyfile)
|
||||
if err == nil {
|
||||
// create session
|
||||
session, err = s.NewStreamSession(cfg.Session, keys, cfg.Opts.AsList())
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// create new sam datagram session from config
|
||||
func (cfg *Config) DatagramSession() (session *DatagramSession, err error) {
|
||||
// connect
|
||||
var s *SAM
|
||||
s, err = NewSAM(cfg.Addr)
|
||||
if err == nil {
|
||||
// ensure keys exist
|
||||
var keys I2PKeys
|
||||
keys, err = s.EnsureKeyfile(cfg.Keyfile)
|
||||
if err == nil {
|
||||
// determine udp port
|
||||
var portstr string
|
||||
_, portstr, err = net.SplitHostPort(cfg.Addr)
|
||||
if err == nil {
|
||||
var port int
|
||||
port, err = strconv.Atoi(portstr)
|
||||
if err == nil && port > 0 {
|
||||
// udp port is 1 lower
|
||||
port --
|
||||
// create session
|
||||
session, err = s.NewDatagramSession(cfg.Session, keys, cfg.Opts.AsList(), port)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
@ -13,7 +13,7 @@ import (
|
||||
// also end-to-end encrypted, signed and includes replay-protection. And they
|
||||
// are also built to be surveillance-resistant (yey!).
|
||||
type DatagramSession struct {
|
||||
samAddr string // address to the sam bridge (ipv4:port)
|
||||
samAddr string // address to the sam bridge (ipv4:port)
|
||||
id string // tunnel name
|
||||
conn net.Conn // connection to sam bridge
|
||||
udpconn *net.UDPConn // used to deliver datagrams
|
||||
|
300
sam3.go
300
sam3.go
@ -2,59 +2,59 @@
|
||||
package sam3
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"net"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"bufio"
|
||||
"bytes"
|
||||
"net"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
||||
|
||||
// Used for controlling I2Ps SAMv3.
|
||||
type SAM struct {
|
||||
address string // ipv4:port
|
||||
conn net.Conn
|
||||
keys *I2PKeys
|
||||
address string // ipv4:port
|
||||
conn net.Conn
|
||||
keys *I2PKeys
|
||||
}
|
||||
|
||||
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="
|
||||
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}, 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]))
|
||||
}
|
||||
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}, 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]))
|
||||
}
|
||||
}
|
||||
|
||||
// if keyfile fname does not exist
|
||||
func (sam *SAM) EnsureKeyfile(fname string) (keys I2PKeys, err error) {
|
||||
|
||||
_, err = os.Stat(fname)
|
||||
_, err = os.Stat(fname)
|
||||
if os.IsNotExist(err) {
|
||||
// make the keys
|
||||
keys, err = sam.NewKeys()
|
||||
@ -67,7 +67,7 @@ func (sam *SAM) EnsureKeyfile(fname string) (keys I2PKeys, err error) {
|
||||
err = StoreKeysIncompat(keys, f)
|
||||
f.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if err == nil {
|
||||
// we haz key file
|
||||
var f *os.File
|
||||
@ -83,141 +83,137 @@ func (sam *SAM) EnsureKeyfile(fname string) (keys I2PKeys, err error) {
|
||||
}
|
||||
|
||||
// Creates the I2P-equivalent of an IP address, that is unique and only the one
|
||||
// who has the private keys can send messages from. The public keys are the I2P
|
||||
// who has the private keys can send messages from. The public keys are the I2P
|
||||
// desination (the address) that anyone can send messages to.
|
||||
func (sam *SAM) NewKeys() (I2PKeys, error) {
|
||||
if _, err := sam.conn.Write([]byte("DEST GENERATE\n")); err != nil {
|
||||
return I2PKeys{}, err
|
||||
}
|
||||
buf := make([]byte, 8192)
|
||||
n, err := sam.conn.Read(buf)
|
||||
if err != nil {
|
||||
return 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 I2PKeys{}, errors.New("Failed to parse keys.")
|
||||
}
|
||||
}
|
||||
return I2PKeys{I2PAddr(pub), priv}, nil
|
||||
if _, err := sam.conn.Write([]byte("DEST GENERATE\n")); err != nil {
|
||||
return I2PKeys{}, err
|
||||
}
|
||||
buf := make([]byte, 8192)
|
||||
n, err := sam.conn.Read(buf)
|
||||
if err != nil {
|
||||
return 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 I2PKeys{}, errors.New("Failed to parse keys.")
|
||||
}
|
||||
}
|
||||
return I2PKeys{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) (I2PAddr, error) {
|
||||
if _, err := sam.conn.Write([]byte("NAMING LOOKUP NAME=" + name + "\n")); err != nil {
|
||||
return I2PAddr(""), err
|
||||
}
|
||||
buf := make([]byte, 4096)
|
||||
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)
|
||||
if _, err := sam.conn.Write([]byte("NAMING LOOKUP NAME=" + name + "\n")); err != nil {
|
||||
return I2PAddr(""), err
|
||||
}
|
||||
buf := make([]byte, 4096)
|
||||
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 {
|
||||
errStr := ""
|
||||
for s.Scan() {
|
||||
text := s.Text()
|
||||
if text == "RESULT=OK" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return I2PAddr(""), errors.New(errStr)
|
||||
} 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 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return I2PAddr(""), errors.New(errStr)
|
||||
}
|
||||
|
||||
// Creates a new session with the style of either "STREAM", "DATAGRAM" or "RAW",
|
||||
// Creates a new session with the style of either "STREAM", "DATAGRAM" or "RAW",
|
||||
// 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 *SAM) newGenericSession(style, id string, keys I2PKeys, options []string, extras []string) (net.Conn, error) {
|
||||
|
||||
optStr := ""
|
||||
for _, opt := range options {
|
||||
optStr += opt + " "
|
||||
}
|
||||
|
||||
conn := sam.conn
|
||||
scmsg := []byte("SESSION CREATE STYLE=" + style + " ID=" + id + " DESTINATION=" + keys.String() + " " + optStr + strings.Join(extras, " ") + "\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)
|
||||
}
|
||||
optStr := ""
|
||||
for _, opt := range options {
|
||||
optStr += opt + " "
|
||||
}
|
||||
|
||||
conn := sam.conn
|
||||
scmsg := []byte("SESSION CREATE STYLE=" + style + " ID=" + id + " DESTINATION=" + keys.String() + " " + optStr + strings.Join(extras, " ") + "\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)
|
||||
}
|
||||
}
|
||||
|
||||
// close this sam session
|
||||
func (sam *SAM) Close() error {
|
||||
if err := sam.conn.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
if err := sam.conn.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user