10 Commits

20 changed files with 566 additions and 289 deletions

View File

@@ -19,7 +19,7 @@ $(EXE):
$(GO) build -v -o $(EXE)
test:
$(GO) test -v -failfast ./lib/common/data/...
$(GO) test -v -failfast ./lib/transport/noise/...
clean:
$(GO) clean -v

View File

@@ -1,19 +1,16 @@
Weekly notes about what I'm working on
======================================
July 18, 2022:
- Implementation-in-Progress of a pure-Noise TCP transport using flynn/noise.
- This transport is **not** operable with the rest of the I2P network and useful
only for testing the transport interface implementation.
- Most examples/docs on flynn/noise use client-server paradigm and not P2P paradigm, strictly
speaking.
- It does *not* process messages for obfuscation or de-obfuscation, key difference from NTCP2
- It doesn't yet actually manage sending messages on a socket, right now it can:
- Set up a Transport muxer
- Set up a Transport, which is basically a session muxer sharing a common socket
- Set up a Session, which is an individual connection to a peer which speaks the transport protocol
- At some point there needs to be a loop on the socket which reads the socket for input and output.
- At this time the transports *intentionally* do not know about the nature of the underlying socket
beyond the fact that it must implement a `net.Conn` interface so that it can connect over TCP.
- In the future, this will need to be able to accept either a `net.Conn` or a `net.PacketConn`
I finally got back to work on go-i2p last Friday, continuing work on the Noise transport(also enhanced marek's privs as a reddit mod which has been helpful, also the Dread community does appear to have begun to step a little re: onboarding eachother)
I can now make a socket, instantiate a noise transport using the socket, and begin to use it to manage "incoming" and "outgoing" handshakes.
They don't complete yet, still working on that, but it's more like debugging now and less figuring out how to actually do it, I more or less know where the pieces go now
I'm beginning to notice drawbacks of what I've done here already, I think noiseSocket is intended at times as example of a way to use flynn/noise which is not exactly like I would do it or how a P2P application needs to do it
Some of the functions are very long and hard to break down, and they use a different Noise flavor than NTCP2 by default, so I'm breaking things down into steps until I have exactly one step of a Noise handshake, exactly one function
Which should finally give me what I've been hoping for all along, an interface where I can modify steps(i.e. add padding, eventually turn it into NTCP2) by instantiating it with different versions of that function, so I get to think about reusability now
I'm going to attempt to ask a question I don't quite know how to ask yet, and maybe won't know the answer until I try it out for myself:
Supposing:
0. Go network structures can be "nested" if they implement the common interface(`net.Conn`, `net.PacketConn`), and use the common interface to store the information about the socket in their implmentation, and only use the functions of the common interface(This is true)
1. That I can instantiate this Noise-framework Transport struct with functions to modify the process in a granular enough way that turning it into NTCP2 is a matter of writing a few custom functions and plugging them in to an instance of the struct(Sort of like what you would do with inheritance)(which I think is true)
2. that I can instantiate it with both a `net.Conn(TCP Socket interface)` and a `net.PacketConn(UDP Socket interface)` because I only use the common features of those 2 interfaces, (Which isn't true yet but I'm thinking about how to do it)
Does that potential definition of a moddable Noise-over-many-transports library mean that I can approach SSU2 mostly in terms of connection management and peer testing, because the crypto would be similar enough to NTCP2 that I could re-use the custom functions?
I'll find out the hard way eventually the first time I have to do it with SSU2, but it would be exciting to have come up with a design that has accelerating returns in such a way.

View File

@@ -65,7 +65,7 @@ options :: Mapping
type RouterAddress struct {
cost *Integer
expiration *Date
transport_style *I2PString
Transport_Style *I2PString
options *Mapping
parserErr error
}
@@ -75,11 +75,11 @@ func (router_address RouterAddress) Bytes() []byte {
bytes := make([]byte, 0)
bytes = append(bytes, router_address.cost.Bytes()...)
bytes = append(bytes, router_address.expiration.Bytes()...)
strData, err := router_address.transport_style.Data()
strData, err := router_address.Transport_Style.Data()
if err != nil {
log.WithFields(log.Fields{
"error": err,
}).Error("RouterAddress.Bytes: error getting transport_style bytes")
}).Error("RouterAddress.Bytes: error getting Transport_Style bytes")
} else {
bytes = append(bytes, strData...)
}
@@ -99,7 +99,7 @@ func (router_address RouterAddress) Expiration() Date {
// TransportStyle returns the transport style for this RouterAddress as an I2PString.
func (router_address RouterAddress) TransportStyle() I2PString {
return *router_address.transport_style
return *router_address.Transport_Style
}
// Options returns the options for this RouterAddress as an I2P Mapping.
@@ -107,9 +107,7 @@ func (router_address RouterAddress) Options() Mapping {
return *router_address.options
}
//
// Check if the RouterAddress is empty or if it is too small to contain valid data.
//
func (router_address RouterAddress) checkValid() (err error, exit bool) {
/*addr_len := len(router_address)
exit = false
@@ -161,12 +159,12 @@ func ReadRouterAddress(data []byte) (router_address RouterAddress, remainder []b
}).Error("error parsing RouterAddress")
router_address.parserErr = err
}
transport_style, remainder, err := NewI2PString(remainder)
router_address.transport_style = transport_style
Transport_Style, remainder, err := NewI2PString(remainder)
router_address.Transport_Style = Transport_Style
if err != nil {
log.WithFields(log.Fields{
"at": "(RouterAddress) ReadNewRouterAddress",
"reason": "error parsing transport_style",
"reason": "error parsing Transport_Style",
}).Error("error parsing RouterAddress")
router_address.parserErr = err
}

View File

@@ -3,6 +3,8 @@ package router_info
import (
"errors"
"net"
"strings"
. "github.com/go-i2p/go-i2p/lib/common/data"
. "github.com/go-i2p/go-i2p/lib/common/router_address"
@@ -106,6 +108,8 @@ type RouterInfo struct {
signature *Signature
}
var routerInfoTest net.Addr = &RouterInfo{}
// Bytes returns the RouterInfo as a []byte suitable for writing to a stream.
func (router_info RouterInfo) Bytes() ([]byte, error) {
var err error
@@ -124,6 +128,29 @@ func (router_info RouterInfo) Bytes() ([]byte, error) {
return bytes, err
}
// Network Implements net.Addr, returns comma-separated list of transport types
func (router_info *RouterInfo) Network() string {
var str []string
for _, addr := range router_info.addresses {
t, err := addr.Transport_Style.Data()
if err != nil {
return strings.Join(str, ",")
}
str = append(str, t)
}
return strings.Join(str, ",")
}
// String Implements net.Addr, returns router-info `Bytes` converted to a string
func (router_info *RouterInfo) String() string {
bytes, err := router_info.Bytes()
if err != nil {
// TODO handle this issue
return ""
}
return string(bytes)
}
// RouterIdentity returns the router identity as *RouterIdentity.
func (router_info *RouterInfo) RouterIdentity() *RouterIdentity {
return router_info.router_identity

View File

@@ -1,123 +0,0 @@
package noise
import (
"bytes"
"crypto/rand"
"encoding/binary"
"errors"
"io"
"github.com/flynn/noise"
)
func ComposeInitiatorHandshakeMessage(s noise.DHKey, rs []byte, payload []byte, ePrivate []byte) (negData, msg []byte, state *noise.HandshakeState, err error) {
if len(rs) != 0 && len(rs) != noise.DH25519.DHLen() {
return nil, nil, nil, errors.New("only 32 byte curve25519 public keys are supported")
}
var pattern noise.HandshakePattern
negData = make([]byte, 6)
copy(negData, negotiationData)
pattern = noise.HandshakeIK
negData[5] = NOISE_PATTERN_IK
var random io.Reader
if len(ePrivate) == 0 {
random = rand.Reader
} else {
random = bytes.NewBuffer(ePrivate)
}
prologue := make([]byte, 2, uint16Size+len(negData))
binary.BigEndian.PutUint16(prologue, uint16(len(negData)))
prologue = append(prologue, negData...)
prologue = append(initString, prologue...)
state, err = noise.NewHandshakeState(noise.Config{
StaticKeypair: s,
Initiator: true,
Pattern: pattern,
CipherSuite: noise.NewCipherSuite(noise.DH25519, noise.CipherAESGCM, noise.HashBLAKE2b),
PeerStatic: rs,
Prologue: prologue,
Random: random,
})
if err != nil {
return
}
padBuf := make([]byte, 2+len(payload))
copy(padBuf[2:], payload)
msg, _, _, err = state.WriteMessage(msg, padBuf)
return
}
func (c *NoiseSession) RunClientHandshake() error {
var (
negData, msg []byte
state *noise.HandshakeState
err error
)
if negData, msg, state, err = ComposeInitiatorHandshakeMessage(c.StaticKey, nil, nil, nil); err != nil {
return err
}
if _, err = c.Write(negData); err != nil {
return err
}
if _, err = c.Write(msg); err != nil {
return err
}
//read negotiation data
/*if err := c.readPacket(); err != nil {
return err
}
negotiationData := c.handshakeBuffer.Next(c.handshakeBuffer.Len())
//read noise message
if err := c.readPacket(); err != nil {
return err
}
msg = c.handshakeBuffer.Next(c.handshakeBuffer.Len())
if len(negotiationData) != 0 || len(msg) == 0 {
return errors.New("Server returned error")
}
// cannot reuse msg for read, need another buf
inBlock := c.NoiseTransport.newBlock()
inBlock.reserve(len(msg))*/
var payload int
payload, c.CipherState, c.NoiseTransport.CipherState, err = state.ReadMessage(inBlock.data, msg)
/*if err != nil {
c.NoiseTransport.freeBlock(inBlock)
return err
}*/
err = c.processCallback(state.PeerStatic(), payload)
if err != nil {
c.NoiseTransport.freeBlock(inBlock)
return err
}
/*c.NoiseTransport.freeBlock(inBlock)
if c.CipherState == nil && c.NoiseTransport.CipherState == nil {
b := c.newBlock()
if b.data, c.CipherState, c.NoiseTransport.CipherState, err = state.WriteMessage(b.data, pad(c.config.Payload)); err != nil {
c.freeBlock(b)
return err
}
if _, err = c.Write(nil); err != nil {
c.freeBlock(b)
return err
}
if _, err = c.Write(b.data); err != nil {
c.freeBlock(b)
return err
}
c.freeBlock(b)
if c.CipherState == nil || c.NoiseTransport.CipherState == nil {
log.WithFields(log.Fields{
"at": "(NoiseSession) RunClientHandshake",
"reason": "unsupported session",
}).Error("unsupported session")
return errors.New("unsupported session")
}
}
*/
//c.in.padding, c.out.padding = c.config.Padding, c.config.Padding
//c.channelBinding = state.ChannelBinding()
c.handshakeComplete = true
return nil
}

View File

@@ -1,75 +0,0 @@
package noise
import (
"errors"
"sync/atomic"
log "github.com/sirupsen/logrus"
)
func (c *NoiseSession) Write(b []byte) (int, error) {
// interlock with Close below
for {
x := atomic.LoadInt32(&c.activeCall)
if x&1 != 0 {
log.WithFields(log.Fields{
"at": "(NoiseSession) Write",
"reason": "session is closed",
}).Error("session is closed")
return 0, errors.New("session is closed")
}
if atomic.CompareAndSwapInt32(&c.activeCall, x, x+2) {
defer atomic.AddInt32(&c.activeCall, -2)
break
}
}
if err := c.RunClientHandshake(); err != nil {
return 0, err
}
c.Mutex.Lock()
defer c.Mutex.Unlock()
if !c.handshakeComplete {
return 0, errors.New("internal error")
}
n, err := c.writePacketLocked(b)
return n, err
}
func (c *NoiseSession) writePacketLocked(data []byte) (int, error) {
var n int
if len(data) == 0 { //special case to answer when everything is ok during handshake
if _, err := c.Conn.Write(make([]byte, 2)); err != nil {
return 0, err
}
}
for len(data) > 0 {
/*m := len(data)
packet := c.InitializePacket()
maxPayloadSize := c.maxPayloadSizeForWrite(packet)
if m > int(maxPayloadSize) {
m = int(maxPayloadSize)
}
if c.CipherState != nil {
////fmt.Println("writing encrypted packet:", m)
packet.reserve(uint16Size + uint16Size + m + macSize)
packet.resize(uint16Size + uint16Size + m)
copy(packet.data[uint16Size+uint16Size:], data[:m])
binary.BigEndian.PutUint16(packet.data[uint16Size:], uint16(m))
//fmt.Println("encrypt size", uint16(m))
} else {
packet.resize(len(packet.data) + len(data))
copy(packet.data[uint16Size:len(packet.data)], data[:m])
binary.BigEndian.PutUint16(packet.data, uint16(len(data)))
}
b := c.encryptIfNeeded(packet)
c.freeBlock(packet)
////fmt.Println(hex.EncodeToString(b))
if _, err := c.conn.Write(b); err != nil {
return n, err
}
n += m
data = data[m:]
*/
}
return n, nil
}

View File

@@ -9,19 +9,10 @@ import (
func (c *NoiseTransport) Handshake(routerInfo router_info.RouterInfo) error {
c.Mutex.Lock()
defer c.Mutex.Unlock()
session, err := c.GetSession(routerInfo)
session, err := c.getSession(routerInfo)
if err != nil {
return err
}
for {
if session.(*NoiseSession).handshakeComplete {
return nil
}
if session.(*NoiseSession).Cond == nil {
break
}
session.(*NoiseSession).Cond.Wait()
}
// Set handshakeCond to indicate that this goroutine is committing to
// running the handshake.
session.(*NoiseSession).Cond = sync.NewCond(&c.Mutex)
@@ -29,8 +20,8 @@ func (c *NoiseTransport) Handshake(routerInfo router_info.RouterInfo) error {
session.(*NoiseSession).Mutex.Lock()
defer session.(*NoiseSession).Mutex.Unlock()
c.Mutex.Lock()
// if c.config.isClient {
if err := session.(*NoiseSession).RunClientHandshake(); err != nil {
if err := session.(*NoiseSession).RunOutgoingHandshake(); err != nil {
return err
}
// Wake any other goroutines that are waiting for this handshake to

View File

@@ -0,0 +1,15 @@
package noise
import "github.com/go-i2p/go-i2p/lib/i2np"
func (s *NoiseSession) QueueSendI2NP(msg i2np.I2NPMessage) {
s.SendQueue.Enqueue(msg)
}
func (s *NoiseSession) SendQueueSize() int {
return s.SendQueue.Size()
}
func (s *NoiseSession) ReadNextI2NP() (i2np.I2NPMessage, error) {
return i2np.I2NPMessage{}, nil
}

View File

@@ -0,0 +1,64 @@
package noise
import (
"bytes"
"crypto/rand"
"encoding/binary"
"errors"
"io"
"log"
"github.com/flynn/noise"
)
func ComposeRecieverHandshakeMessage(s noise.DHKey, rs []byte, payload []byte, ePrivate []byte) (negData, msg []byte, state *noise.HandshakeState, err error) {
if len(rs) != 0 && len(rs) != noise.DH25519.DHLen() {
return nil, nil, nil, errors.New("only 32 byte curve25519 public keys are supported")
}
negData = make([]byte, 6)
copy(negData, initNegotiationData(nil))
pattern := noise.HandshakeXK
negData[5] = NOISE_PATTERN_XK
var random io.Reader
if len(ePrivate) == 0 {
random = rand.Reader
} else {
random = bytes.NewBuffer(ePrivate)
}
prologue := make([]byte, 2, uint16Size+len(negData))
binary.BigEndian.PutUint16(prologue, uint16(len(negData)))
prologue = append(prologue, negData...)
//prologue = append(initString, prologue...)
state, err = noise.NewHandshakeState(noise.Config{
StaticKeypair: s,
Initiator: false,
Pattern: pattern,
CipherSuite: noise.NewCipherSuite(noise.DH25519, noise.CipherChaChaPoly, noise.HashSHA256),
PeerStatic: rs,
Prologue: prologue,
Random: random,
})
if err != nil {
return
}
padBuf := make([]byte, 2+len(payload))
copy(padBuf[2:], payload)
msg, _, _, err = state.WriteMessage(msg, padBuf)
return
}
func (c *NoiseSession) RunIncomingHandshake() error {
negData, msg, state, err := ComposeRecieverHandshakeMessage(c.HandKey, nil, nil, nil)
if err != nil {
return err
}
if _, err = c.Write(negData); err != nil {
return err
}
if _, err = c.Write(msg); err != nil {
return err
}
log.Println(state)
c.handshakeComplete = true
return nil
}

View File

@@ -0,0 +1,46 @@
package noise
import (
"encoding/binary"
"github.com/flynn/noise"
)
const (
NOISE_DH_CURVE25519 = 1
NOISE_CIPHER_CHACHAPOLY = 1
NOISE_CIPHER_AESGCM = 2
NOISE_HASH_SHA256 = 3
NOISE_PATTERN_XK = 11
uint16Size = 2 // uint16 takes 2 bytes
MaxPayloadSize = 65537
)
var ciphers = map[byte]noise.CipherFunc{
NOISE_CIPHER_CHACHAPOLY: noise.CipherChaChaPoly,
NOISE_CIPHER_AESGCM: noise.CipherAESGCM,
}
var hashes = map[byte]noise.HashFunc{
NOISE_HASH_SHA256: noise.HashSHA256,
}
var patterns = map[byte]noise.HandshakePattern{
NOISE_PATTERN_XK: noise.HandshakeXK,
}
func initNegotiationData(negotiationData []byte) []byte {
if negotiationData != nil {
return negotiationData
}
negotiationData = make([]byte, 6)
binary.BigEndian.PutUint16(negotiationData, 1) //version
negotiationData[2] = NOISE_DH_CURVE25519
negotiationData[3] = NOISE_CIPHER_CHACHAPOLY
negotiationData[4] = NOISE_HASH_SHA256
return negotiationData
}

View File

@@ -0,0 +1,66 @@
package noise
import (
"bytes"
"crypto/rand"
"encoding/binary"
"errors"
"io"
"log"
"github.com/flynn/noise"
)
func ComposeInitiatorHandshakeMessage(s noise.DHKey, rs []byte, payload []byte, ePrivate []byte) (negData, msg []byte, state *noise.HandshakeState, err error) {
if len(rs) != 0 && len(rs) != noise.DH25519.DHLen() {
return nil, nil, nil, errors.New("only 32 byte curve25519 public keys are supported")
}
negData = make([]byte, 6)
copy(negData, initNegotiationData(nil))
pattern := noise.HandshakeXK
negData[5] = NOISE_PATTERN_XK
var random io.Reader
if len(ePrivate) == 0 {
random = rand.Reader
} else {
random = bytes.NewBuffer(ePrivate)
}
prologue := make([]byte, 2, uint16Size+len(negData))
binary.BigEndian.PutUint16(prologue, uint16(len(negData)))
prologue = append(prologue, negData...)
//prologue = append(initString, prologue...)
state, err = noise.NewHandshakeState(noise.Config{
StaticKeypair: s,
Initiator: true,
Pattern: pattern,
CipherSuite: noise.NewCipherSuite(noise.DH25519, noise.CipherChaChaPoly, noise.HashSHA256),
PeerStatic: rs,
Prologue: prologue,
Random: random,
})
if err != nil {
return
}
padBuf := make([]byte, 2+len(payload))
copy(padBuf[2:], payload)
msg, _, _, err = state.WriteMessage(msg, padBuf)
return
}
func (c *NoiseSession) RunOutgoingHandshake() error {
negData, msg, state, err := ComposeInitiatorHandshakeMessage(c.HandKey, nil, nil, nil)
if err != nil {
return err
}
if _, err = c.Write(negData); err != nil {
return err
}
if _, err = c.Write(msg); err != nil {
return err
}
log.Println(state)
c.handshakeComplete = true
return nil
}

View File

@@ -0,0 +1,84 @@
package noise
import (
"errors"
"sync/atomic"
log "github.com/sirupsen/logrus"
)
func (c *NoiseSession) Read(b []byte) (int, error) {
// interlock with Close below
for {
x := atomic.LoadInt32(&c.activeCall)
if x&1 != 0 {
log.WithFields(log.Fields{
"at": "(NoiseSession) Read",
"reason": "session is closed",
}).Error("session is closed")
return 0, errors.New("session is closed")
}
if atomic.CompareAndSwapInt32(&c.activeCall, x, x+2) {
defer atomic.AddInt32(&c.activeCall, -2)
break
}
}
if !c.handshakeComplete {
if err := c.RunIncomingHandshake(); err != nil {
return 0, err
}
}
c.Mutex.Lock()
defer c.Mutex.Unlock()
if !c.handshakeComplete {
return 0, errors.New("internal error")
}
n, err := c.readPacketLocked(b)
return n, err
}
func (c *NoiseSession) decryptPacket(data []byte) (int, []byte, error) {
m := len(data)
/*packet := c.InitializePacket()
maxPayloadSize := c.maxPayloadSizeForRead(packet)
if m > int(maxPayloadSize) {
m = int(maxPayloadSize)
}
if c.CipherState != nil {
////fmt.Println("writing encrypted packet:", m)
packet.reserve(uint16Size + uint16Size + m + macSize)
packet.resize(uint16Size + uint16Size + m)
copy(packet.data[uint16Size+uint16Size:], data[:m])
binary.BigEndian.PutUint16(packet.data[uint16Size:], uint16(m))
//fmt.Println("encrypt size", uint16(m))
} else {
packet.resize(len(packet.data) + len(data))
copy(packet.data[uint16Size:len(packet.data)], data[:m])
binary.BigEndian.PutUint16(packet.data, uint16(len(data)))
}
b := c.encryptIfNeeded(packet)*/
//c.freeBlock(packet)
return m, data, nil
}
func (c *NoiseSession) readPacketLocked(data []byte) (int, error) {
var n int
if len(data) == 0 { //special case to answer when everything is ok during handshake
if _, err := c.Conn.Read(make([]byte, 2)); err != nil {
return 0, err
}
}
for len(data) > 0 {
m, b, err := c.encryptPacket(data)
if err != nil {
return 0, err
}
if n, err := c.Conn.Read(b); err != nil {
return n, err
} else {
n += m
data = data[m:]
}
}
return n, nil
}

View File

@@ -2,6 +2,7 @@ package noise
import (
"bytes"
"fmt"
"net"
"sync"
"time"
@@ -10,46 +11,45 @@ import (
"github.com/flynn/noise"
"github.com/go-i2p/go-i2p/lib/common/router_info"
"github.com/go-i2p/go-i2p/lib/i2np"
"github.com/go-i2p/go-i2p/lib/transport"
)
type NoiseSession struct {
*cb.Queue
router_info.RouterInfo
*noise.CipherState
sync.Mutex
*sync.Cond
*NoiseTransport
*NoiseTransport // The parent transport, which "Dialed" the connection to the peer whith whom we established the session
RecvQueue *cb.Queue
SendQueue *cb.Queue
SendKey noise.DHKey
RecvKey noise.DHKey
HandKey noise.DHKey
VerifyCallback VerifyCallbackFunc
handshakeBuffer bytes.Buffer
activeCall int32
handshakeComplete bool
Conn net.Conn
}
// Read implements net.Conn
func (*NoiseSession) Read(b []byte) (n int, err error) {
panic("unimplemented")
}
// RemoteAddr implements net.Conn
func (*NoiseSession) RemoteAddr() net.Addr {
panic("unimplemented")
func (noise_session *NoiseSession) RemoteAddr() net.Addr {
return &noise_session.RouterInfo
}
// SetDeadline implements net.Conn
func (*NoiseSession) SetDeadline(t time.Time) error {
panic("unimplemented")
func (noise_session *NoiseSession) SetDeadline(t time.Time) error {
return noise_session.Conn.SetDeadline(t)
}
// SetReadDeadline implements net.Conn
func (*NoiseSession) SetReadDeadline(t time.Time) error {
panic("unimplemented")
func (noise_session *NoiseSession) SetReadDeadline(t time.Time) error {
return noise_session.Conn.SetReadDeadline(t)
}
// SetWriteDeadline implements net.Conn
func (*NoiseSession) SetWriteDeadline(t time.Time) error {
panic("unimplemented")
func (noise_session *NoiseSession) SetWriteDeadline(t time.Time) error {
return noise_session.Conn.SetWriteDeadline(t)
}
var exampleNoiseSession transport.TransportSession = &NoiseSession{}
@@ -59,27 +59,40 @@ func (s *NoiseSession) LocalAddr() net.Addr {
return s.Conn.LocalAddr()
}
func (s *NoiseSession) QueueSendI2NP(msg i2np.I2NPMessage) {
s.Queue.Enqueue(msg)
}
func (s *NoiseSession) SendQueueSize() int {
return s.Queue.Size()
}
func (s *NoiseSession) ReadNextI2NP() (i2np.I2NPMessage, error) {
return i2np.I2NPMessage{}, nil
}
func (s *NoiseSession) Close() error {
s.Queue.Clear()
s.SendQueue.Clear()
s.RecvQueue.Clear()
return nil
}
func NewNoiseTransportSession(ri router_info.RouterInfo, socket net.Conn) (transport.TransportSession, error) {
return &NoiseSession{
Queue: cb.New(1024),
RouterInfo: ri,
Conn: socket,
}, nil
func (c *NoiseSession) processCallback(publicKey []byte, payload []byte) error {
if c.VerifyCallback == nil {
return nil
}
err := c.VerifyCallback(publicKey, payload)
return err
}
// newBlock allocates a new packet, from hc's free list if possible.
func newBlock() []byte {
return make([]byte, MaxPayloadSize)
}
type VerifyCallbackFunc func(publicKey []byte, data []byte) error
func NewNoiseTransportSession(ri router_info.RouterInfo) (transport.TransportSession, error) {
//socket, err := DialNoise("noise", ri)
for _, addr := range ri.RouterAddresses() {
socket, err := net.Dial("tcp", string(addr.Bytes()))
if err != nil {
return nil, err
}
return &NoiseSession{
SendQueue: cb.New(1024),
RecvQueue: cb.New(1024),
RouterInfo: ri,
Conn: socket,
}, nil
}
return nil, fmt.Errorf("Transport constructor error")
}

View File

@@ -0,0 +1 @@
package noise

View File

@@ -16,12 +16,13 @@ import (
"github.com/go-i2p/go-i2p/lib/common/router_identity"
"github.com/go-i2p/go-i2p/lib/common/router_info"
"github.com/go-i2p/go-i2p/lib/transport"
log "github.com/sirupsen/logrus"
)
type NoiseTransport struct {
*noise.CipherState
router_identity.RouterIdentity
sync.Mutex
router_identity.RouterIdentity
*noise.CipherState
Listener net.Listener
peerConnections map[data.Hash]transport.TransportSession
}
@@ -33,24 +34,36 @@ var exampleNoiseTransport transport.Transport = &NoiseTransport{}
// implements net.Listener
var ExampleNoiseListener net.Listener = exampleNoiseTransport
// Accept a connection on a listening socket.
func (noopt *NoiseTransport) Accept() (net.Conn, error) {
return noopt.Listener.Accept()
}
// Addr of the transport, for now this is returning the IP:Port the transport is listening on,
// but this might actually be the router identity
func (noopt *NoiseTransport) Addr() net.Addr {
return noopt.Listener.Addr()
}
// Name of the transport TYPE, in this case `noise`
func (noopt *NoiseTransport) Name() string {
return "noise"
}
// Set the router identity for this transport.
// SetIdentity will set the router identity for this transport.
// will bind if the underlying socket is not already
// if the underlying socket is already bound update the RouterIdentity
// returns any errors that happen if they do
func (noopt *NoiseTransport) SetIdentity(ident router_identity.RouterIdentity) error {
func (noopt *NoiseTransport) SetIdentity(ident router_identity.RouterIdentity) (err error) {
noopt.RouterIdentity = ident
if noopt.Listener == nil {
log.WithFields(log.Fields{
"at": "(NoiseTransport) SetIdentity",
"reason": "network socket is null",
}).Error("network socket is null")
err = errors.New("network socket is null")
return
}
return nil
}
@@ -63,19 +76,34 @@ func (noopt *NoiseTransport) GetSession(routerInfo router_info.RouterInfo) (tran
if len(hash) == 0 {
return nil, errors.New("NoiseTransport: GetSession: RouterInfo has no IdentityHash")
}
if t, ok := noopt.peerConnections[hash]; ok == true {
if t, ok := noopt.peerConnections[hash]; ok {
return t, nil
}
conn, err := noopt.Accept()
if err == nil {
if noopt.peerConnections[hash], err = NewNoiseTransportSession(routerInfo, conn); err != nil {
return noopt.peerConnections[hash], err
}
var err error
if noopt.peerConnections[hash], err = NewNoiseTransportSession(routerInfo); err != nil {
return noopt.peerConnections[hash], err
}
return nil, err
}
// return true if a routerInfo is compatable with this transport
func (c *NoiseTransport) getSession(routerInfo router_info.RouterInfo) (transport.TransportSession, error) {
session, err := c.GetSession(routerInfo)
if err != nil {
return nil, err
}
for {
if session.(*NoiseSession).handshakeComplete {
return nil, nil
}
if session.(*NoiseSession).Cond == nil {
break
}
session.(*NoiseSession).Cond.Wait()
}
return session, nil
}
// Compatable return true if a routerInfo is compatable with this transport
func (noopt *NoiseTransport) Compatable(routerInfo router_info.RouterInfo) bool {
_, ok := noopt.peerConnections[routerInfo.IdentHash()]
return ok
@@ -88,9 +116,20 @@ func (noopt *NoiseTransport) Close() error {
return nil
}
// NewNoiseTransport create a NoiseTransport using a supplied net.Listener
func NewNoiseTransport(netSocket net.Listener) *NoiseTransport {
return &NoiseTransport{
peerConnections: make(map[data.Hash]transport.TransportSession),
Listener: netSocket,
}
}
// NewNoiseTransportSocket creates a Noise transport socket with a random
// host and port.
func NewNoiseTransportSocket() (*NoiseTransport, error) {
netSocket, err := net.Listen("tcp", "")
if err != nil {
return nil, err
}
return NewNoiseTransport(netSocket), nil
}

View File

@@ -11,6 +11,20 @@ func TestTransport(t *testing.T) {
t.Error(err)
}
nt := NewNoiseTransport(ln)
t.Log(nt.Name())
go func() {
for {
conn, err := nt.Accept()
if err != nil {
t.Log(err)
}
conn.Write([]byte("World"))
}
}()
lnn, err := net.Listen("tcp", ":42070")
if err != nil {
t.Error(err)
}
ntt := NewNoiseTransport(lnn)
t.Log(ntt.Name())
//ntt.GetSession()
}

View File

@@ -0,0 +1,84 @@
package noise
import (
"errors"
"sync/atomic"
log "github.com/sirupsen/logrus"
)
func (c *NoiseSession) Write(b []byte) (int, error) {
// interlock with Close below
for {
x := atomic.LoadInt32(&c.activeCall)
if x&1 != 0 {
log.WithFields(log.Fields{
"at": "(NoiseSession) Write",
"reason": "session is closed",
}).Error("session is closed")
return 0, errors.New("session is closed")
}
if atomic.CompareAndSwapInt32(&c.activeCall, x, x+2) {
defer atomic.AddInt32(&c.activeCall, -2)
break
}
}
if !c.handshakeComplete {
if err := c.RunOutgoingHandshake(); err != nil {
return 0, err
}
}
c.Mutex.Lock()
defer c.Mutex.Unlock()
if !c.handshakeComplete {
return 0, errors.New("internal error")
}
n, err := c.writePacketLocked(b)
return n, err
}
func (c *NoiseSession) encryptPacket(data []byte) (int, []byte, error) {
m := len(data)
/*packet := c.InitializePacket()
maxPayloadSize := c.maxPayloadSizeForWrite(packet)
if m > int(maxPayloadSize) {
m = int(maxPayloadSize)
}
if c.CipherState != nil {
////fmt.Println("writing encrypted packet:", m)
packet.reserve(uint16Size + uint16Size + m + macSize)
packet.resize(uint16Size + uint16Size + m)
copy(packet.data[uint16Size+uint16Size:], data[:m])
binary.BigEndian.PutUint16(packet.data[uint16Size:], uint16(m))
//fmt.Println("encrypt size", uint16(m))
} else {
packet.resize(len(packet.data) + len(data))
copy(packet.data[uint16Size:len(packet.data)], data[:m])
binary.BigEndian.PutUint16(packet.data, uint16(len(data)))
}
b := c.encryptIfNeeded(packet)*/
//c.freeBlock(packet)
return m, data, nil
}
func (c *NoiseSession) writePacketLocked(data []byte) (int, error) {
var n int
if len(data) == 0 { //special case to answer when everything is ok during handshake
if _, err := c.Conn.Write(make([]byte, 2)); err != nil {
return 0, err
}
}
for len(data) > 0 {
m, b, err := c.encryptPacket(data)
if err != nil {
return 0, err
}
if n, err := c.Conn.Write(b); err != nil {
return n, err
} else {
n += m
data = data[m:]
}
}
return n, nil
}

View File

@@ -0,0 +1,34 @@
package ntcp
import (
"math"
"github.com/flynn/noise"
)
const (
NOISE_DH_CURVE25519 = 1
NOISE_CIPHER_CHACHAPOLY = 1
NOISE_CIPHER_AESGCM = 2
NOISE_HASH_SHA256 = 3
NOISE_PATTERN_XK = 11
uint16Size = 2 // uint16 takes 2 bytes
MaxPayloadSize = math.MaxUint16 - 16 /*mac size*/ - uint16Size /*data len*/
)
var ciphers = map[byte]noise.CipherFunc{
NOISE_CIPHER_CHACHAPOLY: noise.CipherChaChaPoly,
NOISE_CIPHER_AESGCM: noise.CipherAESGCM,
}
var hashes = map[byte]noise.HashFunc{
NOISE_HASH_SHA256: noise.HashSHA256,
}
var patterns = map[byte]noise.HandshakePattern{
NOISE_PATTERN_XK: noise.HandshakeXK,
}

View File

@@ -1,6 +1,7 @@
package ntcp
import "github.com/go-i2p/go-i2p/lib/transport/noise"
// Session implements TransportSession
// An established transport session
type Session struct {
}
type Session noise.NoiseSession

View File

@@ -1,5 +1,7 @@
package ntcp
import "github.com/go-i2p/go-i2p/lib/transport/noise"
/**
* https://geti2p.net/spec/ntcp2
**/
@@ -10,6 +12,5 @@ const (
NTCP_MESSAGE_MAX_SIZE = 65537
)
// Transport is an ntcp transport implementing transport.Transport interface
type Transport struct {
}
// NTCPTransport is an ntcp transport implementing transport.Transport interface
type NTCPTransport noise.NoiseTransport