-
Notifications
You must be signed in to change notification settings - Fork 1
/
sam3.go
188 lines (177 loc) · 5.56 KB
/
sam3.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
// Library for I2Ps SAMv3 bridge (https://geti2p.com)
package sam3
import (
"bufio"
"bytes"
"errors"
"net"
"strings"
)
// 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]))
}
}
// 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
// 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
}
// 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)
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. Extra arguments can be specified by
// setting extra to something else than []string{}. Returns the connection used
// to control the SAMv3 bridge. The SAM-object should be treated as destroyed
// after calling this function on it.
func (sam *SAM) newGenericSession(style, id string, keys I2PKeys, options []string, extras []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 + 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)
}
}
// Closes the connection to SAM. Does not affect sessions or listeners created,
// they need to be closed separately.
func (sam *SAM) Close() error {
if err := sam.conn.Close(); err != nil {
return err
}
return nil
}