27 Commits

Author SHA1 Message Date
eyedeekay
98b94db696 fix NPE when using bare garlic with i2p logger wrapper 2025-08-06 16:22:13 -04:00
eyedeekay
65e43111b7 fix NPE when using bare garlic with i2p logger wrapper 2025-08-06 16:20:11 -04:00
idk
48e87d68a9 Merge pull request #2 from urgentquest/main
Utililize go i2p logger (revised)
2025-05-05 18:02:21 -04:00
Call me Phil
3f93dec9db Utililize go i2p logger (#1)
- Drop log.go in favor of using go-i2p/logger
- Fix an occurrence of ioutil deprecation. Bump go version to a recent one
2025-03-30 23:14:21 +00:00
eyedeekay
d537acfa66 add function for exmitting TLS config to consumers 2025-02-23 16:49:16 -05:00
eyedeekay
8b297d030a update and use external logger module 2024-11-21 18:48:44 -05:00
eyedeekay
daeaa91183 update modules 2024-11-16 16:25:24 -05:00
eyedeekay
ed7e6f56ae update release process 2024-11-16 16:23:33 -05:00
eyedeekay
982a496406 update module 2024-11-16 15:51:42 -05:00
eyedeekay
d7abd8af30 don't log in GetJoinedWD for now because it is called prior to 2024-11-16 15:50:58 -05:00
eyedeekay
0d05b3c3f7 update examples 2024-11-14 10:43:17 -05:00
eyedeekay
00aafc2ede fix the broken tests 2024-11-13 14:55:42 -05:00
eyedeekay
8920b7c847 update version 2024-11-13 14:53:54 -05:00
eyedeekay
2791849c67 update modules 2024-11-13 14:52:44 -05:00
eyedeekay
0d9a9ee10a Fix up import paths 2024-11-09 11:54:29 -05:00
eyedeekay
867026628f setup auto-assign workflow 2024-11-08 15:01:15 -05:00
eyedeekay
56eed1e88f Merge branch 'main' of github.com:eyedeekay/onramp 2024-11-08 12:55:30 -05:00
eyedeekay
c4e23394dc change module path 2024-11-08 12:54:30 -05:00
idk
f9fc572938 Merge pull request #1 from hkh4n/logging
Logging
2024-11-05 00:03:47 +00:00
Haris Khan
e184eda3c1 replaced deprecated function 2024-11-04 15:20:23 -05:00
Haris Khan
a0a35dd81c added logging to tls.go 2024-11-04 15:17:48 -05:00
Haris Khan
7ef5793e5a added logging to proxy.go 2024-11-04 15:11:49 -05:00
Haris Khan
c2b8660d5e added logging to onion.go 2024-11-04 15:08:35 -05:00
Haris Khan
680d5c000b added logging to garlic.go 2024-11-04 14:52:43 -05:00
Haris Khan
c2b56d35e2 updated README.md to reflect new logging paradigm 2024-11-04 14:44:07 -05:00
Haris Khan
2a53e8347c added logging to common.go 2024-11-04 11:05:47 -05:00
Haris Khan
dcb997a327 added logging paradigm to log.go 2024-11-04 11:05:39 -05:00
14 changed files with 655 additions and 94 deletions

20
.github/workflows/auto-assign.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
name: Auto Assign
on:
issues:
types: [opened]
pull_request:
types: [opened]
jobs:
run:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- name: 'Auto-assign issue'
uses: pozil/auto-assign-issue@v1
with:
repo-token:${{ secrets.GITHUB_TOKEN }}
assignees: eyedeekay
numOfAssignee: 1

View File

@@ -12,7 +12,7 @@ import (
"log" "log"
"net/http" "net/http"
"github.com/eyedeekay/onramp" "github.com/go-i2p/onramp"
) )
func main() { func main() {
@@ -44,7 +44,7 @@ import (
"log" "log"
"net/http" "net/http"
"github.com/eyedeekay/onramp" "github.com/go-i2p/onramp"
) )
func main() { func main() {
@@ -135,7 +135,7 @@ import (
"log" "log"
"net/http" "net/http"
"github.com/eyedeekay/onramp" "github.com/go-i2p/onramp"
) )
func main() { func main() {
@@ -167,7 +167,7 @@ import (
"log" "log"
"net/http" "net/http"
"github.com/eyedeekay/onramp" "github.com/go-i2p/onramp"
) )
func main() { func main() {

View File

@@ -1,6 +1,6 @@
USER_GH=eyedeekay USER_GH=go-i2p
VERSION=0.33.8 VERSION=0.33.92
CREDIT='contributors to this release: @hkh4n, @eyedeekay' CREDIT='contributors to this release: @hkh4n, @eyedeekay'
packagename=onramp packagename=onramp

View File

@@ -1,6 +1,8 @@
onramp onramp
====== ======
[![Go Report Card](https://goreportcard.com/badge/github.com/go-i2p/onramp)](https://goreportcard.com/report/github.com/go-i2p/onramp)
High-level, easy-to-use listeners and clients for I2P and onion URL's from Go. High-level, easy-to-use listeners and clients for I2P and onion URL's from Go.
Provides only the most widely-used functions in a basic way. It expects nothing Provides only the most widely-used functions in a basic way. It expects nothing
from the users, an otherwise empty instance of the structs will listen and dial from the users, an otherwise empty instance of the structs will listen and dial
@@ -12,9 +14,9 @@ This means that hidden services will maintain their identities, and that clients
will always have the same return addresses. If you don't want this behavior, will always have the same return addresses. If you don't want this behavior,
make sure to delete the "keystore" when your app closes or when your application make sure to delete the "keystore" when your app closes or when your application
needs to cycle keys by calling the `Garlic.DeleteKeys()` or `Onion.DeleteKeys()` needs to cycle keys by calling the `Garlic.DeleteKeys()` or `Onion.DeleteKeys()`
function. For more information, check out the [godoc](http://pkg.go.dev/github.com/eyedeekay/onramp). function. For more information, check out the [godoc](http://pkg.go.dev/github.com/go-i2p/onramp).
- **[Source Code](https://github.com/eyedeekay/onramp)** - **[Source Code](https://github.com/go-i2p/onramp)**
STATUS: This project is maintained. I will respond to issues, pull requests, and feature requests within a few days. STATUS: This project is maintained. I will respond to issues, pull requests, and feature requests within a few days.
@@ -38,7 +40,7 @@ package main
import ( import (
"log" "log"
"github.com/eyedeekay/onramp" "github.com/go-i2p/onramp"
) )
func main() { func main() {
@@ -64,7 +66,7 @@ package main
import ( import (
"log" "log"
"github.com/eyedeekay/onramp" "github.com/go-i2p/onramp"
) )
func main() { func main() {
@@ -76,4 +78,32 @@ func main() {
} }
defer listener.Close() defer listener.Close()
} }
``` ```
## Verbosity ##
Logging can be enabled and configured using the DEBUG_I2P environment variable. By default, logging is disabled.
There are three available log levels:
- Debug
```shell
export DEBUG_I2P=debug
```
- Warn
```shell
export DEBUG_I2P=warn
```
- Error
```shell
export DEBUG_I2P=error
```
If DEBUG_I2P is set to an unrecognized variable, it will fall back to "debug".
## Contributing
See CONTRIBUTING.md for more information.
## License
This project is licensed under the MIT license, see LICENSE for more information.

View File

@@ -9,24 +9,36 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/go-i2p/logger"
"github.com/sirupsen/logrus"
) )
var log = logger.GetGoI2PLogger()
//go:generate go run -tags gen ./gen.go //go:generate go run -tags gen ./gen.go
// GetJoinedWD returns the working directory joined with the given path. // GetJoinedWD returns the working directory joined with the given path.
func GetJoinedWD(dir string) (string, error) { func GetJoinedWD(dir string) (string, error) {
wd, err := os.Getwd() wd, err := os.Getwd()
if err != nil { if err != nil {
//log.WithError(err).Error("Failed to get working directory")
return "", err return "", err
} }
jwd := filepath.Join(wd, dir) jwd := filepath.Join(wd, dir)
ajwd, err := filepath.Abs(jwd) ajwd, err := filepath.Abs(jwd)
if err != nil { if err != nil {
//log.WithError(err).WithField("path", jwd).Error("Failed to get absolute path")
return "", err return "", err
} }
if _, err := os.Stat(ajwd); err != nil { if _, err := os.Stat(ajwd); err != nil {
os.MkdirAll(ajwd, 0755) //log.WithField("path", ajwd).Debug("Directory does not exist, creating")
if err := os.MkdirAll(ajwd, 0755); err != nil {
//log.WithError(err).WithField("path", ajwd).Error("Failed to create directory")
return "", err
}
} }
//log.WithField("path", ajwd).Debug("Successfully got joined working directory")
return ajwd, nil return ajwd, nil
} }
@@ -53,68 +65,112 @@ var TLS_KEYSTORE_PATH = tlsdefault
// path is not set, it returns the default path. If the path does // path is not set, it returns the default path. If the path does
// not exist, it creates it. // not exist, it creates it.
func I2PKeystorePath() (string, error) { func I2PKeystorePath() (string, error) {
log.WithField("path", I2P_KEYSTORE_PATH).Debug("Checking I2P keystore path")
if _, err := os.Stat(I2P_KEYSTORE_PATH); err != nil { if _, err := os.Stat(I2P_KEYSTORE_PATH); err != nil {
log.WithField("path", I2P_KEYSTORE_PATH).Debug("I2P keystore directory does not exist, creating")
err := os.MkdirAll(I2P_KEYSTORE_PATH, 0755) err := os.MkdirAll(I2P_KEYSTORE_PATH, 0755)
if err != nil { if err != nil {
log.WithError(err).WithField("path", I2P_KEYSTORE_PATH).Error("Failed to create I2P keystore directory")
return "", err return "", err
} }
} }
log.WithField("path", I2P_KEYSTORE_PATH).Debug("I2P keystore path verified")
return I2P_KEYSTORE_PATH, nil return I2P_KEYSTORE_PATH, nil
} }
// DeleteI2PKeyStore deletes the I2P Keystore. // DeleteI2PKeyStore deletes the I2P Keystore.
func DeleteI2PKeyStore() error { func DeleteI2PKeyStore() error {
return os.RemoveAll(I2P_KEYSTORE_PATH) log.WithField("path", I2P_KEYSTORE_PATH).Debug("Attempting to delete I2P keystore")
err := os.RemoveAll(I2P_KEYSTORE_PATH)
if err != nil {
log.WithError(err).WithField("path", I2P_KEYSTORE_PATH).Error("Failed to delete I2P keystore")
return err
}
log.WithField("path", I2P_KEYSTORE_PATH).Debug("Successfully deleted I2P keystore")
return nil
//return os.RemoveAll(I2P_KEYSTORE_PATH)
} }
// TorKeystorePath returns the path to the Onion Keystore. If the // TorKeystorePath returns the path to the Onion Keystore. If the
// path is not set, it returns the default path. If the path does // path is not set, it returns the default path. If the path does
// not exist, it creates it. // not exist, it creates it.
func TorKeystorePath() (string, error) { func TorKeystorePath() (string, error) {
log.WithField("path", ONION_KEYSTORE_PATH).Debug("Checking Tor keystore path")
if _, err := os.Stat(ONION_KEYSTORE_PATH); err != nil { if _, err := os.Stat(ONION_KEYSTORE_PATH); err != nil {
log.WithField("path", ONION_KEYSTORE_PATH).Debug("Tor keystore directory does not exist, creating")
err := os.MkdirAll(ONION_KEYSTORE_PATH, 0755) err := os.MkdirAll(ONION_KEYSTORE_PATH, 0755)
if err != nil { if err != nil {
log.WithError(err).WithField("path", ONION_KEYSTORE_PATH).Error("Failed to create Tor keystore directory")
return "", err return "", err
} }
} }
log.WithField("path", ONION_KEYSTORE_PATH).Debug("Tor keystore path verified")
return ONION_KEYSTORE_PATH, nil return ONION_KEYSTORE_PATH, nil
} }
// DeleteTorKeyStore deletes the Onion Keystore. // DeleteTorKeyStore deletes the Onion Keystore.
func DeleteTorKeyStore() error { func DeleteTorKeyStore() error {
return os.RemoveAll(ONION_KEYSTORE_PATH) log.WithField("path", ONION_KEYSTORE_PATH).Debug("Attempting to delete Tor keystore")
err := os.RemoveAll(ONION_KEYSTORE_PATH)
if err != nil {
log.WithError(err).WithField("path", ONION_KEYSTORE_PATH).Error("Failed to delete Tor keystore")
return err
}
log.WithField("path", ONION_KEYSTORE_PATH).Debug("Successfully deleted Tor keystore")
return nil
//return os.RemoveAll(ONION_KEYSTORE_PATH)
} }
// TLSKeystorePath returns the path to the TLS Keystore. If the // TLSKeystorePath returns the path to the TLS Keystore. If the
// path is not set, it returns the default path. If the path does // path is not set, it returns the default path. If the path does
// not exist, it creates it. // not exist, it creates it.
func TLSKeystorePath() (string, error) { func TLSKeystorePath() (string, error) {
log.WithField("path", TLS_KEYSTORE_PATH).Debug("Checking TLS keystore path")
if _, err := os.Stat(TLS_KEYSTORE_PATH); err != nil { if _, err := os.Stat(TLS_KEYSTORE_PATH); err != nil {
log.WithField("path", TLS_KEYSTORE_PATH).Debug("TLS keystore directory does not exist, creating")
err := os.MkdirAll(TLS_KEYSTORE_PATH, 0755) err := os.MkdirAll(TLS_KEYSTORE_PATH, 0755)
if err != nil { if err != nil {
log.WithError(err).WithField("path", TLS_KEYSTORE_PATH).Error("Failed to create TLS keystore directory")
return "", err return "", err
} }
} }
log.WithField("path", TLS_KEYSTORE_PATH).Debug("TLS keystore path verified")
return TLS_KEYSTORE_PATH, nil return TLS_KEYSTORE_PATH, nil
} }
// DeleteTLSKeyStore deletes the TLS Keystore. // DeleteTLSKeyStore deletes the TLS Keystore.
func DeleteTLSKeyStore() error { func DeleteTLSKeyStore() error {
return os.RemoveAll(TLS_KEYSTORE_PATH) log.WithField("path", TLS_KEYSTORE_PATH).Debug("Attempting to delete TLS keystore")
err := os.RemoveAll(TLS_KEYSTORE_PATH)
if err != nil {
log.WithError(err).WithField("path", TLS_KEYSTORE_PATH).Error("Failed to delete TLS keystore")
return err
}
log.WithField("path", TLS_KEYSTORE_PATH).Debug("Successfully deleted TLS keystore")
return nil
//return os.RemoveAll(TLS_KEYSTORE_PATH)
} }
// Dial returns a connection for the given network and address. // Dial returns a connection for the given network and address.
// network is ignored. If the address ends in i2p, it returns an I2P connection. // network is ignored. If the address ends in i2p, it returns an I2P connection.
// if the address ends in anything else, it returns a Tor connection. // if the address ends in anything else, it returns a Tor connection.
func Dial(network, addr string) (net.Conn, error) { func Dial(network, addr string) (net.Conn, error) {
log.WithFields(logrus.Fields{
"network": network,
"address": addr,
}).Debug("Attempting to dial")
url, err := url.Parse(addr) url, err := url.Parse(addr)
if err != nil { if err != nil {
log.WithError(err).WithField("address", addr).Error("Failed to parse address")
return nil, err return nil, err
} }
hostname := url.Hostname() hostname := url.Hostname()
if strings.HasSuffix(hostname, ".i2p") { if strings.HasSuffix(hostname, ".i2p") {
log.WithField("hostname", hostname).Debug("Using I2P connection for .i2p address")
return DialGarlic(network, addr) return DialGarlic(network, addr)
} }
log.WithField("hostname", hostname).Debug("Using Tor connection for non-i2p address")
return DialOnion(network, addr) return DialOnion(network, addr)
} }
@@ -123,19 +179,31 @@ func Dial(network, addr string) (net.Conn, error) {
// if network is tor or onion, it returns an Onion listener. // if network is tor or onion, it returns an Onion listener.
// if keys ends with ".i2p", it returns an I2P listener. // if keys ends with ".i2p", it returns an I2P listener.
func Listen(network, keys string) (net.Listener, error) { func Listen(network, keys string) (net.Listener, error) {
log.WithFields(logrus.Fields{
"network": network,
"keys": keys,
}).Debug("Attempting to create listener")
if network == "i2p" || network == "garlic" { if network == "i2p" || network == "garlic" {
log.Debug("Creating I2P listener based on network type")
return ListenGarlic(network, keys) return ListenGarlic(network, keys)
} }
if network == "tor" || network == "onion" { if network == "tor" || network == "onion" {
log.Debug("Creating Tor listener based on network type")
return ListenOnion(network, keys) return ListenOnion(network, keys)
} }
url, err := url.Parse(keys) url, err := url.Parse(keys)
if err != nil { if err != nil {
log.WithError(err).WithField("keys", keys).Error("Failed to parse keys URL")
return nil, err return nil, err
} }
hostname := url.Hostname() hostname := url.Hostname()
if strings.HasSuffix(hostname, ".i2p") { if strings.HasSuffix(hostname, ".i2p") {
log.WithField("hostname", hostname).Debug("Creating I2P listener based on .i2p hostname")
return ListenGarlic(network, keys) return ListenGarlic(network, keys)
} }
log.WithField("hostname", hostname).Debug("Creating Tor listener for non-i2p hostname")
return ListenOnion(network, keys) return ListenOnion(network, keys)
} }

271
garlic.go
View File

@@ -7,14 +7,14 @@ import (
"context" "context"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"log"
"net" "net"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/eyedeekay/i2pkeys" "github.com/go-i2p/i2pkeys"
"github.com/eyedeekay/sam3" "github.com/go-i2p/sam3"
"github.com/sirupsen/logrus"
) )
// Garlic is a ready-made I2P streaming manager. Once initialized it always // Garlic is a ready-made I2P streaming manager. Once initialized it always
@@ -58,6 +58,19 @@ func (g *Garlic) addrString(addr string) string {
func (g *Garlic) String() string { func (g *Garlic) String() string {
var r string var r string
if g.ServiceKeys == nil {
if g.StreamSession != nil {
k := g.StreamSession.Keys()
g.ServiceKeys = &k
}
if g.DatagramSession != nil {
k := g.StreamSession.Keys()
g.ServiceKeys = &k
}
if g.ServiceKeys == nil {
return ""
}
}
switch g.AddrMode { switch g.AddrMode {
case DEST_HASH: case DEST_HASH:
r = g.ServiceKeys.Address.DestHash().Hash() r = g.ServiceKeys.Address.DestHash().Hash()
@@ -75,7 +88,7 @@ func (g *Garlic) String() string {
default: default:
r = g.ServiceKeys.Address.DestHash().Hash() r = g.ServiceKeys.Address.DestHash().Hash()
} }
return g.addrString(r) //r //strings.TrimLeft(strings.TrimRight(r, "\n"), "\n") //strings.TrimSpace(r) return g.addrString(r) // r //strings.TrimLeft(strings.TrimRight(r, "\n"), "\n") //strings.TrimSpace(r)
} }
func (g *Garlic) getName() string { func (g *Garlic) getName() string {
@@ -101,27 +114,35 @@ func (g *Garlic) getOptions() []string {
func (g *Garlic) samSession() (*sam3.SAM, error) { func (g *Garlic) samSession() (*sam3.SAM, error) {
if g.SAM == nil { if g.SAM == nil {
log.WithField("address", g.getAddr()).Debug("Creating new SAM session")
var err error var err error
g.SAM, err = sam3.NewSAM(g.getAddr()) g.SAM, err = sam3.NewSAM(g.getAddr())
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create SAM session")
return nil, fmt.Errorf("onramp samSession: %v", err) return nil, fmt.Errorf("onramp samSession: %v", err)
} }
log.Debug("SAM session created successfully")
} }
return g.SAM, nil return g.SAM, nil
} }
func (g *Garlic) setupStreamSession() (*sam3.StreamSession, error) { func (g *Garlic) setupStreamSession() (*sam3.StreamSession, error) {
if g.StreamSession == nil { if g.StreamSession == nil {
log.WithField("name", g.getName()).Debug("Setting up stream session")
var err error var err error
g.ServiceKeys, err = g.Keys() g.ServiceKeys, err = g.Keys()
if err != nil { if err != nil {
log.WithError(err).Error("Failed to get keys for stream session")
return nil, fmt.Errorf("onramp setupStreamSession: %v", err) return nil, fmt.Errorf("onramp setupStreamSession: %v", err)
} }
log.WithField("address", g.ServiceKeys.Address.Base32()).Debug("Creating stream session with keys")
log.Println("Creating stream session with keys:", g.ServiceKeys.Address.Base32()) log.Println("Creating stream session with keys:", g.ServiceKeys.Address.Base32())
g.StreamSession, err = g.SAM.NewStreamSession(g.getName(), *g.ServiceKeys, g.getOptions()) g.StreamSession, err = g.SAM.NewStreamSession(g.getName(), *g.ServiceKeys, g.getOptions())
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create stream session")
return nil, fmt.Errorf("onramp setupStreamSession: %v", err) return nil, fmt.Errorf("onramp setupStreamSession: %v", err)
} }
log.Debug("Stream session created successfully")
return g.StreamSession, nil return g.StreamSession, nil
} }
return g.StreamSession, nil return g.StreamSession, nil
@@ -129,78 +150,131 @@ func (g *Garlic) setupStreamSession() (*sam3.StreamSession, error) {
func (g *Garlic) setupDatagramSession() (*sam3.DatagramSession, error) { func (g *Garlic) setupDatagramSession() (*sam3.DatagramSession, error) {
if g.DatagramSession == nil { if g.DatagramSession == nil {
log.WithField("name", g.getName()).Debug("Setting up datagram session")
var err error var err error
g.ServiceKeys, err = g.Keys() g.ServiceKeys, err = g.Keys()
if err != nil { if err != nil {
log.WithError(err).Error("Failed to get keys for datagram session")
return nil, fmt.Errorf("onramp setupDatagramSession: %v", err) return nil, fmt.Errorf("onramp setupDatagramSession: %v", err)
} }
log.WithField("address", g.ServiceKeys.Address.Base32()).Debug("Creating datagram session with keys")
log.Println("Creating datagram session with keys:", g.ServiceKeys.Address.Base32()) log.Println("Creating datagram session with keys:", g.ServiceKeys.Address.Base32())
g.DatagramSession, err = g.SAM.NewDatagramSession(g.getName(), *g.ServiceKeys, g.getOptions(), 0) g.DatagramSession, err = g.SAM.NewDatagramSession(g.getName(), *g.ServiceKeys, g.getOptions(), 0)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create datagram session")
return nil, fmt.Errorf("onramp setupDatagramSession: %v", err) return nil, fmt.Errorf("onramp setupDatagramSession: %v", err)
} }
log.Debug("Datagram session created successfully")
return g.DatagramSession, nil return g.DatagramSession, nil
} }
log.Debug("Using existing datagram session")
return g.DatagramSession, nil return g.DatagramSession, nil
} }
// NewListener returns a net.Listener for the Garlic structure's I2P keys. // NewListener returns a net.Listener for the Garlic structure's I2P keys.
// accepts a variable list of arguments, arguments after the first one are ignored. // accepts a variable list of arguments, arguments after the first one are ignored.
func (g *Garlic) NewListener(n, addr string) (net.Listener, error) { func (g *Garlic) NewListener(n, addr string) (net.Listener, error) {
return g.Listen(n) log.WithFields(logrus.Fields{
"network": n,
"address": addr,
"name": g.getName(),
}).Debug("Creating new listener")
listener, err := g.Listen(n)
if err != nil {
log.WithError(err).Error("Failed to create listener")
return nil, err
}
log.Debug("Successfully created listener")
return listener, nil
// return g.Listen(n)
} }
// Listen returns a net.Listener for the Garlic structure's I2P keys. // Listen returns a net.Listener for the Garlic structure's I2P keys.
// accepts a variable list of arguments, arguments after the first one are ignored. // accepts a variable list of arguments, arguments after the first one are ignored.
func (g *Garlic) Listen(args ...string) (net.Listener, error) { func (g *Garlic) Listen(args ...string) (net.Listener, error) {
return g.OldListen(args...) log.WithFields(logrus.Fields{
"args": args,
"name": g.getName(),
}).Debug("Setting up listener")
listener, err := g.OldListen(args...)
if err != nil {
log.WithError(err).Error("Failed to create listener")
return nil, err
}
log.Debug("Successfully created listener")
return listener, nil
// return g.OldListen(args...)
} }
// OldListen returns a net.Listener for the Garlic structure's I2P keys. // OldListen returns a net.Listener for the Garlic structure's I2P keys.
// accepts a variable list of arguments, arguments after the first one are ignored. // accepts a variable list of arguments, arguments after the first one are ignored.
func (g *Garlic) OldListen(args ...string) (net.Listener, error) { func (g *Garlic) OldListen(args ...string) (net.Listener, error) {
log.WithField("args", args).Debug("Starting OldListen")
if len(args) > 0 { if len(args) > 0 {
if args[0] == "tcp" || args[0] == "tcp6" || args[0] == "st" || args[0] == "st6" { protocol := args[0]
log.WithField("protocol", protocol).Debug("Checking protocol type")
// if args[0] == "tcp" || args[0] == "tcp6" || args[0] == "st" || args[0] == "st6" {
if protocol == "tcp" || protocol == "tcp6" || protocol == "st" || protocol == "st6" {
log.Debug("Using TCP stream listener")
return g.ListenStream() return g.ListenStream()
} else if args[0] == "udp" || args[0] == "udp6" || args[0] == "dg" || args[0] == "dg6" { //} else if args[0] == "udp" || args[0] == "udp6" || args[0] == "dg" || args[0] == "dg6" {
} else if protocol == "udp" || protocol == "udp6" || protocol == "dg" || protocol == "dg6" {
log.Debug("Using UDP datagram listener")
pk, err := g.ListenPacket() pk, err := g.ListenPacket()
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create packet listener")
return nil, err return nil, err
} }
log.Debug("Successfully created datagram session")
return pk.(*sam3.DatagramSession), nil return pk.(*sam3.DatagramSession), nil
} }
} }
log.Debug("No protocol specified, defaulting to stream listener")
return g.ListenStream() return g.ListenStream()
} }
// Listen returns a net.Listener for the Garlic structure's I2P keys. // Listen returns a net.Listener for the Garlic structure's I2P keys.
func (g *Garlic) ListenStream() (net.Listener, error) { func (g *Garlic) ListenStream() (net.Listener, error) {
log.Debug("Setting up stream listener")
var err error var err error
if g.SAM, err = g.samSession(); err != nil { if g.SAM, err = g.samSession(); err != nil {
log.WithError(err).Error("Failed to create SAM session for stream listener")
return nil, fmt.Errorf("onramp NewGarlic: %v", err) return nil, fmt.Errorf("onramp NewGarlic: %v", err)
} }
if g.StreamSession, err = g.setupStreamSession(); err != nil { if g.StreamSession, err = g.setupStreamSession(); err != nil {
log.WithError(err).Error("Failed to setup stream session")
return nil, fmt.Errorf("onramp Listen: %v", err) return nil, fmt.Errorf("onramp Listen: %v", err)
} }
if g.StreamListener == nil { if g.StreamListener == nil {
log.Debug("Creating new stream listener")
g.StreamListener, err = g.StreamSession.Listen() g.StreamListener, err = g.StreamSession.Listen()
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create stream listener")
return nil, fmt.Errorf("onramp Listen: %v", err) return nil, fmt.Errorf("onramp Listen: %v", err)
} }
log.Debug("Stream listener created successfully")
} }
return g.StreamListener, nil return g.StreamListener, nil
} }
// ListenPacket returns a net.PacketConn for the Garlic structure's I2P keys. // ListenPacket returns a net.PacketConn for the Garlic structure's I2P keys.
func (g *Garlic) ListenPacket() (net.PacketConn, error) { func (g *Garlic) ListenPacket() (net.PacketConn, error) {
log.Debug("Setting up packet connection")
var err error var err error
if g.SAM, err = g.samSession(); err != nil { if g.SAM, err = g.samSession(); err != nil {
log.WithError(err).Error("Failed to create SAM session for packet connection")
return nil, fmt.Errorf("onramp NewGarlic: %v", err) return nil, fmt.Errorf("onramp NewGarlic: %v", err)
} }
if g.DatagramSession, err = g.setupDatagramSession(); err != nil { if g.DatagramSession, err = g.setupDatagramSession(); err != nil {
log.WithError(err).Error("Failed to setup datagram session")
return nil, fmt.Errorf("onramp Listen: %v", err) return nil, fmt.Errorf("onramp Listen: %v", err)
} }
log.Debug("Packet connection successfully established")
return g.DatagramSession, nil return g.DatagramSession, nil
} }
@@ -208,23 +282,32 @@ func (g *Garlic) ListenPacket() (net.PacketConn, error) {
// which also uses TLS either for additional encryption, authentication, // which also uses TLS either for additional encryption, authentication,
// or browser-compatibility. // or browser-compatibility.
func (g *Garlic) ListenTLS(args ...string) (net.Listener, error) { func (g *Garlic) ListenTLS(args ...string) (net.Listener, error) {
log.WithField("args", args).Debug("Starting TLS listener")
listener, err := g.Listen(args...) listener, err := g.Listen(args...)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create base listener")
return nil, err return nil, err
} }
cert, err := g.TLSKeys() cert, err := g.TLSKeys()
if err != nil { if err != nil {
log.WithError(err).Error("Failed to get TLS keys")
return nil, fmt.Errorf("onramp ListenTLS: %v", err) return nil, fmt.Errorf("onramp ListenTLS: %v", err)
} }
if len(args) > 0 { if len(args) > 0 {
if args[0] == "tcp" || args[0] == "tcp6" || args[0] == "st" || args[0] == "st6" { protocol := args[0]
log.WithField("protocol", protocol).Debug("Creating TLS listener for protocol")
// if args[0] == "tcp" || args[0] == "tcp6" || args[0] == "st" || args[0] == "st6" {
if protocol == "tcp" || protocol == "tcp6" || protocol == "st" || protocol == "st6" {
log.Debug("Creating TLS stream listener")
tlsConfig := tlsConfig(cert)
return tls.NewListener( return tls.NewListener(
g.StreamListener, g.StreamListener,
&tls.Config{ tlsConfig,
Certificates: []tls.Certificate{cert},
},
), nil ), nil
} else if args[0] == "udp" || args[0] == "udp6" || args[0] == "dg" || args[0] == "dg6" { //} else if args[0] == "udp" || args[0] == "udp6" || args[0] == "dg" || args[0] == "dg6" {
} else if protocol == "udp" || protocol == "udp6" || protocol == "dg" || protocol == "dg6" {
log.Debug("Creating TLS datagram listener")
return tls.NewListener( return tls.NewListener(
g.DatagramSession, g.DatagramSession,
&tls.Config{ &tls.Config{
@@ -234,8 +317,10 @@ func (g *Garlic) ListenTLS(args ...string) (net.Listener, error) {
} }
} else { } else {
log.Debug("No protocol specified, using stream listener")
g.StreamListener = listener.(*sam3.StreamListener) g.StreamListener = listener.(*sam3.StreamListener)
} }
log.Debug("Successfully created TLS listener")
return tls.NewListener( return tls.NewListener(
g.StreamListener, g.StreamListener,
&tls.Config{ &tls.Config{
@@ -244,78 +329,163 @@ func (g *Garlic) ListenTLS(args ...string) (net.Listener, error) {
), nil ), nil
} }
func tlsConfig(cert tls.Certificate) *tls.Config {
x := &tls.Config{
Certificates: []tls.Certificate{cert},
}
return x
}
// TLSConfig returns the TLS config for the Garlic structure.
// it will return a TLS config even if a service is not currently using TLS.
func (g *Garlic) TLSConfig() (*tls.Config, error) {
cert, err := g.TLSKeys()
if err != nil {
log.WithError(err).Error("Failed to get TLS keys")
return nil, fmt.Errorf("onramp TLSConfig: %v", err)
}
return tlsConfig(cert), nil
}
// Dial returns a net.Conn for the Garlic structure's I2P keys. // Dial returns a net.Conn for the Garlic structure's I2P keys.
func (g *Garlic) Dial(net, addr string) (net.Conn, error) { func (g *Garlic) Dial(net, addr string) (net.Conn, error) {
log.WithFields(logrus.Fields{
"network": net,
"address": addr,
}).Debug("Attempting to dial")
if !strings.Contains(addr, ".i2p") { if !strings.Contains(addr, ".i2p") {
log.Debug("Non-I2P address detected, returning null connection")
return &NullConn{}, nil return &NullConn{}, nil
} }
var err error var err error
if g.SAM, err = g.samSession(); err != nil { if g.SAM, err = g.samSession(); err != nil {
log.WithError(err).Error("Failed to create SAM session")
return nil, fmt.Errorf("onramp NewGarlic: %v", err) return nil, fmt.Errorf("onramp NewGarlic: %v", err)
} }
if g.StreamSession, err = g.setupStreamSession(); err != nil { if g.StreamSession, err = g.setupStreamSession(); err != nil {
log.WithError(err).Error("Failed to setup stream session")
return nil, fmt.Errorf("onramp Dial: %v", err) return nil, fmt.Errorf("onramp Dial: %v", err)
} }
return g.StreamSession.Dial(net, addr) log.Debug("Attempting to establish connection")
conn, err := g.StreamSession.Dial(net, addr)
if err != nil {
log.WithError(err).Error("Failed to establish connection")
return nil, err
}
log.Debug("Successfully established connection")
return conn, nil
// return g.StreamSession.Dial(net, addr)
} }
// DialContext returns a net.Conn for the Garlic structure's I2P keys. // DialContext returns a net.Conn for the Garlic structure's I2P keys.
func (g *Garlic) DialContext(ctx context.Context, net, addr string) (net.Conn, error) { func (g *Garlic) DialContext(ctx context.Context, net, addr string) (net.Conn, error) {
log.WithFields(logrus.Fields{
"network": net,
"address": addr,
}).Debug("Attempting to dial with context")
if !strings.Contains(addr, ".i2p") { if !strings.Contains(addr, ".i2p") {
log.Debug("Non-I2P address detected, returning null connection")
return &NullConn{}, nil return &NullConn{}, nil
} }
var err error var err error
if g.SAM, err = g.samSession(); err != nil { if g.SAM, err = g.samSession(); err != nil {
log.WithError(err).Error("Failed to create SAM session")
return nil, fmt.Errorf("onramp NewGarlic: %v", err) return nil, fmt.Errorf("onramp NewGarlic: %v", err)
} }
if g.StreamSession, err = g.setupStreamSession(); err != nil { if g.StreamSession, err = g.setupStreamSession(); err != nil {
log.WithError(err).Error("Failed to setup stream session")
return nil, fmt.Errorf("onramp Dial: %v", err) return nil, fmt.Errorf("onramp Dial: %v", err)
} }
return g.StreamSession.DialContext(ctx, net, addr) log.Debug("Attempting to establish connection with context")
conn, err := g.StreamSession.DialContext(ctx, net, addr)
if err != nil {
log.WithError(err).Error("Failed to establish connection")
return nil, err
}
log.Debug("Successfully established connection")
return conn, nil
// return g.StreamSession.DialContext(ctx, net, addr)
} }
// Close closes the Garlic structure's sessions and listeners. // Close closes the Garlic structure's sessions and listeners.
func (g *Garlic) Close() error { func (g *Garlic) Close() error {
log.WithField("name", g.getName()).Debug("Closing Garlic sessions")
e1 := g.StreamSession.Close() e1 := g.StreamSession.Close()
var err error var err error
if e1 != nil { if e1 != nil {
log.WithError(e1).Error("Failed to close stream session")
err = fmt.Errorf("onramp Close: %v", e1) err = fmt.Errorf("onramp Close: %v", e1)
} else {
log.Debug("Stream session closed successfully")
} }
e2 := g.SAM.Close() e2 := g.SAM.Close()
if e2 != nil { if e2 != nil {
log.WithError(e2).Error("Failed to close SAM session")
err = fmt.Errorf("onramp Close: %v %v", e1, e2) err = fmt.Errorf("onramp Close: %v %v", e1, e2)
} else {
log.Debug("SAM session closed successfully")
} }
if err == nil {
log.Debug("All sessions closed successfully")
}
return err return err
} }
// Keys returns the I2PKeys for the Garlic structure. If none // Keys returns the I2PKeys for the Garlic structure. If none
// exist, they are created and stored. // exist, they are created and stored.
func (g *Garlic) Keys() (*i2pkeys.I2PKeys, error) { func (g *Garlic) Keys() (*i2pkeys.I2PKeys, error) {
log.WithFields(logrus.Fields{
"name": g.getName(),
"address": g.getAddr(),
}).Debug("Retrieving I2P keys")
keys, err := I2PKeys(g.getName(), g.getAddr()) keys, err := I2PKeys(g.getName(), g.getAddr())
if err != nil { if err != nil {
log.WithError(err).Error("Failed to get I2P keys")
return &i2pkeys.I2PKeys{}, fmt.Errorf("onramp Keys: %v", err) return &i2pkeys.I2PKeys{}, fmt.Errorf("onramp Keys: %v", err)
} }
log.Debug("Successfully retrieved I2P keys")
return &keys, nil return &keys, nil
} }
func (g *Garlic) DeleteKeys() error { func (g *Garlic) DeleteKeys() error {
return DeleteGarlicKeys(g.getName()) // return DeleteGarlicKeys(g.getName())
log.WithField("name", g.getName()).Debug("Attempting to delete Garlic keys")
err := DeleteGarlicKeys(g.getName())
if err != nil {
log.WithError(err).Error("Failed to delete Garlic keys")
}
log.Debug("Successfully deleted Garlic keys")
return err
} }
// NewGarlic returns a new Garlic struct. It is immediately ready to use with // NewGarlic returns a new Garlic struct. It is immediately ready to use with
// I2P streaming. // I2P streaming.
func NewGarlic(tunName, samAddr string, options []string) (*Garlic, error) { func NewGarlic(tunName, samAddr string, options []string) (*Garlic, error) {
log.WithFields(logrus.Fields{
"tunnel_name": tunName,
"sam_address": samAddr,
"options": options,
}).Debug("Creating new Garlic instance")
g := new(Garlic) g := new(Garlic)
g.name = tunName g.name = tunName
g.addr = samAddr g.addr = samAddr
g.opts = options g.opts = options
var err error var err error
if g.SAM, err = g.samSession(); err != nil { if g.SAM, err = g.samSession(); err != nil {
log.WithError(err).Error("Failed to create SAM session")
return nil, fmt.Errorf("onramp NewGarlic: %v", err) return nil, fmt.Errorf("onramp NewGarlic: %v", err)
} }
if g.StreamSession, err = g.setupStreamSession(); err != nil { if g.StreamSession, err = g.setupStreamSession(); err != nil {
log.WithError(err).Error("Failed to setup stream session")
return nil, fmt.Errorf("onramp NewGarlic: %v", err) return nil, fmt.Errorf("onramp NewGarlic: %v", err)
} }
log.Debug("Successfully created new Garlic instance")
return g, nil return g, nil
} }
@@ -324,49 +494,74 @@ func NewGarlic(tunName, samAddr string, options []string) (*Garlic, error) {
// This is permanent and irreversible, and will change the onion service // This is permanent and irreversible, and will change the onion service
// address. // address.
func DeleteGarlicKeys(tunName string) error { func DeleteGarlicKeys(tunName string) error {
log.WithField("tunnel_name", tunName).Debug("Attempting to delete Garlic keys")
keystore, err := I2PKeystorePath() keystore, err := I2PKeystorePath()
if err != nil { if err != nil {
log.WithError(err).Error("Failed to get keystore path")
return fmt.Errorf("onramp DeleteGarlicKeys: discovery error %v", err) return fmt.Errorf("onramp DeleteGarlicKeys: discovery error %v", err)
} }
keyspath := filepath.Join(keystore, tunName+".i2p.private") keyspath := filepath.Join(keystore, tunName+".i2p.private")
log.WithField("path", keyspath).Debug("Deleting key file")
if err := os.Remove(keyspath); err != nil { if err := os.Remove(keyspath); err != nil {
log.WithError(err).WithField("path", keyspath).Error("Failed to delete key file")
return fmt.Errorf("onramp DeleteGarlicKeys: %v", err) return fmt.Errorf("onramp DeleteGarlicKeys: %v", err)
} }
log.Debug("Successfully deleted Garlic keys")
return nil return nil
} }
// I2PKeys returns the I2PKeys at the keystore directory for the given // I2PKeys returns the I2PKeys at the keystore directory for the given
// tunnel name. If none exist, they are created and stored. // tunnel name. If none exist, they are created and stored.
func I2PKeys(tunName, samAddr string) (i2pkeys.I2PKeys, error) { func I2PKeys(tunName, samAddr string) (i2pkeys.I2PKeys, error) {
log.WithFields(logrus.Fields{
"tunnel_name": tunName,
"sam_address": samAddr,
}).Debug("Looking up I2P keys")
keystore, err := I2PKeystorePath() keystore, err := I2PKeystorePath()
if err != nil { if err != nil {
log.WithError(err).Error("Failed to get keystore path")
return i2pkeys.I2PKeys{}, fmt.Errorf("onramp I2PKeys: discovery error %v", err) return i2pkeys.I2PKeys{}, fmt.Errorf("onramp I2PKeys: discovery error %v", err)
} }
keyspath := filepath.Join(keystore, tunName+".i2p.private") keyspath := filepath.Join(keystore, tunName+".i2p.private")
log.WithField("path", keyspath).Debug("Checking for existing keys")
info, err := os.Stat(keyspath) info, err := os.Stat(keyspath)
if info != nil { if info != nil {
if info.Size() == 0 { if info.Size() == 0 {
log.WithField("path", keyspath).Debug("Keystore empty, will regenerate keys")
log.Println("onramp I2PKeys: keystore empty, re-generating keys") log.Println("onramp I2PKeys: keystore empty, re-generating keys")
} else {
log.WithField("path", keyspath).Debug("Found existing keystore")
} }
} }
if err != nil { if err != nil {
log.WithField("path", keyspath).Debug("Keys not found, generating new keys")
sam, err := sam3.NewSAM(samAddr) sam, err := sam3.NewSAM(samAddr)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create SAM connection")
return i2pkeys.I2PKeys{}, fmt.Errorf("onramp I2PKeys: SAM error %v", err) return i2pkeys.I2PKeys{}, fmt.Errorf("onramp I2PKeys: SAM error %v", err)
} }
log.Debug("SAM connection established")
keys, err := sam.NewKeys(tunName) keys, err := sam.NewKeys(tunName)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to generate new keys")
return i2pkeys.I2PKeys{}, fmt.Errorf("onramp I2PKeys: keygen error %v", err) return i2pkeys.I2PKeys{}, fmt.Errorf("onramp I2PKeys: keygen error %v", err)
} }
log.Debug("New keys generated successfully")
if err = i2pkeys.StoreKeys(keys, keyspath); err != nil { if err = i2pkeys.StoreKeys(keys, keyspath); err != nil {
log.WithError(err).WithField("path", keyspath).Error("Failed to store generated keys")
return i2pkeys.I2PKeys{}, fmt.Errorf("onramp I2PKeys: store error %v", err) return i2pkeys.I2PKeys{}, fmt.Errorf("onramp I2PKeys: store error %v", err)
} }
log.WithField("path", keyspath).Debug("Successfully stored new keys")
return keys, nil return keys, nil
} else { } else {
log.WithField("path", keyspath).Debug("Loading existing keys")
keys, err := i2pkeys.LoadKeys(keyspath) keys, err := i2pkeys.LoadKeys(keyspath)
if err != nil { if err != nil {
log.WithError(err).WithField("path", keyspath).Error("Failed to load existing keys")
return i2pkeys.I2PKeys{}, fmt.Errorf("onramp I2PKeys: load error %v", err) return i2pkeys.I2PKeys{}, fmt.Errorf("onramp I2PKeys: load error %v", err)
} }
log.Debug("Successfully loaded existing keys")
return keys, nil return keys, nil
} }
} }
@@ -376,18 +571,35 @@ var garlics map[string]*Garlic
// CloseAllGarlic closes all garlics managed by the onramp package. It does not // CloseAllGarlic closes all garlics managed by the onramp package. It does not
// affect objects instantiated by an app. // affect objects instantiated by an app.
func CloseAllGarlic() { func CloseAllGarlic() {
log.WithField("count", len(garlics)).Debug("Closing all Garlic connections")
for i, g := range garlics { for i, g := range garlics {
log.WithFields(logrus.Fields{
"index": i,
"name": g.name,
}).Debug("Closing Garlic connection")
log.Println("Closing garlic", g.name) log.Println("Closing garlic", g.name)
CloseGarlic(i) CloseGarlic(i)
} }
log.Debug("All Garlic connections closed")
} }
// CloseGarlic closes the Garlic at the given index. It does not affect Garlic // CloseGarlic closes the Garlic at the given index. It does not affect Garlic
// objects instantiated by an app. // objects instantiated by an app.
func CloseGarlic(tunName string) { func CloseGarlic(tunName string) {
log.WithField("tunnel_name", tunName).Debug("Attempting to close Garlic connection")
g, ok := garlics[tunName] g, ok := garlics[tunName]
if ok { if ok {
g.Close() log.Debug("Found Garlic connection, closing")
// g.Close()
err := g.Close()
if err != nil {
log.WithError(err).Error("Error closing Garlic connection")
} else {
log.Debug("Successfully closed Garlic connection")
}
} else {
log.Debug("No Garlic connection found for tunnel name")
} }
} }
@@ -399,11 +611,18 @@ var SAM_ADDR = "127.0.0.1:7656"
// corresponding to a structure managed by the onramp library // corresponding to a structure managed by the onramp library
// and not instantiated by an app. // and not instantiated by an app.
func ListenGarlic(network, keys string) (net.Listener, error) { func ListenGarlic(network, keys string) (net.Listener, error) {
log.WithFields(logrus.Fields{
"network": network,
"keys": keys,
"sam_addr": SAM_ADDR,
}).Debug("Creating new Garlic listener")
g, err := NewGarlic(keys, SAM_ADDR, OPT_DEFAULTS) g, err := NewGarlic(keys, SAM_ADDR, OPT_DEFAULTS)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create new Garlic")
return nil, fmt.Errorf("onramp Listen: %v", err) return nil, fmt.Errorf("onramp Listen: %v", err)
} }
garlics[keys] = g garlics[keys] = g
log.Debug("Successfully created Garlic listener")
return g.Listen() return g.Listen()
} }
@@ -411,10 +630,26 @@ func ListenGarlic(network, keys string) (net.Listener, error) {
// corresponding to a structure managed by the onramp library // corresponding to a structure managed by the onramp library
// and not instantiated by an app. // and not instantiated by an app.
func DialGarlic(network, addr string) (net.Conn, error) { func DialGarlic(network, addr string) (net.Conn, error) {
log.WithFields(logrus.Fields{
"network": network,
"address": addr,
"sam_addr": SAM_ADDR,
}).Debug("Creating new Garlic connection")
g, err := NewGarlic(addr, SAM_ADDR, OPT_DEFAULTS) g, err := NewGarlic(addr, SAM_ADDR, OPT_DEFAULTS)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create new Garlic")
return nil, fmt.Errorf("onramp Dial: %v", err) return nil, fmt.Errorf("onramp Dial: %v", err)
} }
garlics[addr] = g garlics[addr] = g
return g.Dial(network, addr) log.WithField("address", addr).Debug("Attempting to dial")
conn, err := g.Dial(network, addr)
if err != nil {
log.WithError(err).Error("Failed to dial connection")
return nil, err
}
log.Debug("Successfully established Garlic connection")
return conn, nil
// return g.Dial(network, addr)
} }

View File

@@ -1,11 +1,15 @@
package onramp package onramp
import "github.com/eyedeekay/sam3" import "github.com/go-i2p/sam3"
var OPT_DEFAULTS = sam3.Options_Default var (
var OPT_WIDE = sam3.Options_Wide OPT_DEFAULTS = sam3.Options_Default
OPT_WIDE = sam3.Options_Wide
)
var OPT_HUGE = sam3.Options_Humongous var (
var OPT_LARGE = sam3.Options_Large OPT_HUGE = sam3.Options_Humongous
var OPT_MEDIUM = sam3.Options_Medium OPT_LARGE = sam3.Options_Large
var OPT_SMALL = sam3.Options_Small OPT_MEDIUM = sam3.Options_Medium
OPT_SMALL = sam3.Options_Small
)

View File

@@ -6,8 +6,7 @@ package onramp
import ( import (
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"io/ioutil" "io"
"log"
"net" "net"
"net/http" "net/http"
"testing" "testing"
@@ -53,7 +52,7 @@ func TestBareGarlic(t *testing.T) {
} }
defer resp.Body.Close() defer resp.Body.Close()
fmt.Println(resp.Status) fmt.Println(resp.Status)
body, err := ioutil.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }

17
go.mod
View File

@@ -1,16 +1,17 @@
module github.com/eyedeekay/onramp module github.com/go-i2p/onramp
go 1.18 go 1.24.2
require ( require (
github.com/cretz/bine v0.2.0 github.com/cretz/bine v0.2.0
github.com/eyedeekay/i2pkeys v0.33.8 github.com/go-i2p/i2pkeys v0.33.92
github.com/eyedeekay/sam3 v0.33.8 github.com/go-i2p/logger v0.0.0-20241123010126-3050657e5d0c
github.com/go-i2p/sam3 v0.33.92
github.com/sirupsen/logrus v1.9.3
) )
require ( require (
github.com/stretchr/testify v1.8.4 // indirect golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect
golang.org/x/crypto v0.11.0 // indirect golang.org/x/net v0.0.0-20210525063256-abc453219eb5 // indirect
golang.org/x/net v0.12.0 // indirect golang.org/x/sys v0.27.0 // indirect
golang.org/x/sys v0.10.0 // indirect
) )

34
go.sum
View File

@@ -2,35 +2,35 @@ github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI= github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/eyedeekay/i2pkeys v0.33.7 h1:cxqHSkl6b2lHyPJUtIQZBiipYf7NQVYqM1d3ub0MI4k= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/eyedeekay/i2pkeys v0.33.7/go.mod h1:W9KCm9lqZ+Ozwl3dwcgnpPXAML97+I8Jiht7o5A8YBM= github.com/go-i2p/i2pkeys v0.0.0-20241108200332-e4f5ccdff8c4/go.mod h1:m5TlHjPZrU5KbTd7Lr+I2rljyC6aJ88HdkeMQXV0U0E=
github.com/eyedeekay/i2pkeys v0.33.8 h1:f3llyruchFqs1QwCacBYbShArKPpMSSOqo/DVZXcfVs= github.com/go-i2p/i2pkeys v0.33.92 h1:e2vx3vf7tNesaJ8HmAlGPOcfiGM86jzeIGxh27I9J2Y=
github.com/eyedeekay/i2pkeys v0.33.8/go.mod h1:W9KCm9lqZ+Ozwl3dwcgnpPXAML97+I8Jiht7o5A8YBM= github.com/go-i2p/i2pkeys v0.33.92/go.mod h1:BRURQ/twxV0WKjZlFSKki93ivBi+MirZPWudfwTzMpE=
github.com/eyedeekay/sam3 v0.33.7 h1:GPYHG4NHxvhqPbGNJ3wKvUQyZSTCmX17f5L5QvyefGs= github.com/go-i2p/logger v0.0.0-20241123010126-3050657e5d0c h1:VTiECn3dFEmUlZjto+wOwJ7SSJTHPLyNprQMR5HzIMI=
github.com/eyedeekay/sam3 v0.33.7/go.mod h1:25cRGEFawSkbiPNSh7vTUIpRtEYLVLg/4J4He6LndAY= github.com/go-i2p/logger v0.0.0-20241123010126-3050657e5d0c/go.mod h1:te7Zj3g3oMeIl8uBXAgO62UKmZ6m6kHRNg1Mm+X8Hzk=
github.com/eyedeekay/sam3 v0.33.8 h1:emuSZ4qSyoqc1EDjIBFbJ3GXNHOXw6hjbNp2OqdOpgI= github.com/go-i2p/sam3 v0.33.92 h1:TVpi4GH7Yc7nZBiE1QxLjcZfnC4fI/80zxQz1Rk36BA=
github.com/eyedeekay/sam3 v0.33.8/go.mod h1:ytbwLYLJlW6UA92Ffyc6oioWTKnGeeUMr9CLuJbtqSA= github.com/go-i2p/sam3 v0.33.92/go.mod h1:oDuV145l5XWKKafeE4igJHTDpPwA0Yloz9nyKKh92eo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5 h1:wjuX4b5yYQnEQHzd+CBcrcC6OVR2J1CN6mUy0oSxIPo=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

161
onion.go
View File

@@ -7,12 +7,12 @@ import (
"context" "context"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"io/ioutil"
"log"
"net" "net"
"os" "os"
"path/filepath" "path/filepath"
"github.com/sirupsen/logrus"
"github.com/cretz/bine/tor" "github.com/cretz/bine/tor"
"github.com/cretz/bine/torutil/ed25519" "github.com/cretz/bine/torutil/ed25519"
) )
@@ -66,23 +66,29 @@ func (o *Onion) getDialConf() *tor.DialConf {
func (o *Onion) getTor() *tor.Tor { func (o *Onion) getTor() *tor.Tor {
if torp == nil { if torp == nil {
log.Debug("Initializing new Tor instance")
var err error var err error
torp, err = tor.Start(o.getContext(), o.getStartConf()) torp, err = tor.Start(o.getContext(), o.getStartConf())
if err != nil { if err != nil {
panic(err) log.WithError(err).Error("Failed to start Tor")
panic(err) // return nil instead?
} }
log.Debug("Tor instance started successfully")
} }
return torp return torp
} }
func (o *Onion) getDialer() *tor.Dialer { func (o *Onion) getDialer() *tor.Dialer {
//if o.Dialer == nil { // if o.Dialer == nil {
//var err error // var err error
//o.Dialer, err // o.Dialer, err
log.Debug("Creating new Tor dialer")
dialer, err := o.getTor().Dialer(o.getContext(), o.getDialConf()) dialer, err := o.getTor().Dialer(o.getContext(), o.getDialConf())
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create Tor dialer")
panic(err) panic(err)
} }
log.Debug("Tor dialer created successfully")
//} //}
//return o.Dialer //return o.Dialer
return dialer return dialer
@@ -106,25 +112,55 @@ func (o *Onion) NewListener(n, addr string) (net.Listener, error) {
// address, and will automatically generate a keypair and store it. // address, and will automatically generate a keypair and store it.
// the args are always ignored // the args are always ignored
func (o *Onion) Listen(args ...string) (net.Listener, error) { func (o *Onion) Listen(args ...string) (net.Listener, error) {
return o.OldListen(args...) log.WithFields(logrus.Fields{
"args": args,
"name": o.getName(),
}).Debug("Setting up Onion listener")
listener, err := o.OldListen(args...)
if err != nil {
log.WithError(err).Error("Failed to create Onion listener")
return nil, err
}
log.Debug("Successfully created Onion listener")
return listener, nil
// return o.OldListen(args...)
} }
// OldListen returns a net.Listener which will listen on an onion // OldListen returns a net.Listener which will listen on an onion
// address, and will automatically generate a keypair and store it. // address, and will automatically generate a keypair and store it.
// the args are always ignored // the args are always ignored
func (o *Onion) OldListen(args ...string) (net.Listener, error) { func (o *Onion) OldListen(args ...string) (net.Listener, error) {
return o.getTor().Listen(o.getContext(), o.getListenConf()) log.WithField("name", o.getName()).Debug("Creating Tor listener")
listener, err := o.getTor().Listen(o.getContext(), o.getListenConf())
if err != nil {
log.WithError(err).Error("Failed to create Tor listener")
return nil, err
}
log.Debug("Successfully created Tor listener")
return listener, nil
// return o.getTor().Listen(o.getContext(), o.getListenConf())
} }
// ListenTLS returns a net.Listener which will apply TLS encryption // ListenTLS returns a net.Listener which will apply TLS encryption
// to the onion listener, which will not be decrypted until it reaches // to the onion listener, which will not be decrypted until it reaches
// the browser // the browser
func (o *Onion) ListenTLS(args ...string) (net.Listener, error) { func (o *Onion) ListenTLS(args ...string) (net.Listener, error) {
log.WithField("args", args).Debug("Setting up TLS Onion listener")
cert, err := o.TLSKeys() cert, err := o.TLSKeys()
if err != nil { if err != nil {
log.WithError(err).Error("Failed to get TLS keys")
return nil, fmt.Errorf("onramp ListenTLS: %v", err) return nil, fmt.Errorf("onramp ListenTLS: %v", err)
} }
log.Debug("Creating base Tor listener")
l, err := o.getTor().Listen(o.getContext(), o.getListenConf()) l, err := o.getTor().Listen(o.getContext(), o.getListenConf())
if err != nil {
log.WithError(err).Error("Failed to create base Tor listener")
return nil, err
}
log.Debug("Wrapping Tor listener with TLS")
return tls.NewListener( return tls.NewListener(
l, l,
&tls.Config{ &tls.Config{
@@ -135,23 +171,56 @@ func (o *Onion) ListenTLS(args ...string) (net.Listener, error) {
// Dial returns a net.Conn to the given onion address or clearnet address. // Dial returns a net.Conn to the given onion address or clearnet address.
func (o *Onion) Dial(net, addr string) (net.Conn, error) { func (o *Onion) Dial(net, addr string) (net.Conn, error) {
return o.getDialer().DialContext(o.getContext(), net, addr) log.WithFields(logrus.Fields{
"network": net,
"address": addr,
}).Debug("Attempting to dial via Tor")
conn, err := o.getDialer().DialContext(o.getContext(), net, addr)
if err != nil {
log.WithError(err).Error("Failed to establish Tor connection")
return nil, err
}
log.Debug("Successfully established Tor connection")
return conn, nil
// return o.getDialer().DialContext(o.getContext(), net, addr)
} }
// Close closes the Onion Service and all associated resources. // Close closes the Onion Service and all associated resources.
func (o *Onion) Close() error { func (o *Onion) Close() error {
return o.getTor().Close() log.WithField("name", o.getName()).Debug("Closing Onion service")
err := o.getTor().Close()
if err != nil {
log.WithError(err).Error("Failed to close Tor instance")
return err
}
log.Debug("Successfully closed Onion service")
return nil
// return o.getTor().Close()
} }
// Keys returns the keys for the Onion // Keys returns the keys for the Onion
func (o *Onion) Keys() (ed25519.KeyPair, error) { func (o *Onion) Keys() (ed25519.KeyPair, error) {
return TorKeys(o.getName()) log.WithField("name", o.getName()).Debug("Retrieving Onion keys")
keys, err := TorKeys(o.getName())
if err != nil {
log.WithError(err).Error("Failed to get Tor keys")
return nil, err
}
log.Debug("Successfully retrieved Onion keys")
return keys, nil
// return TorKeys(o.getName())
} }
// DeleteKeys deletes the keys at the given key name in the key store. // DeleteKeys deletes the keys at the given key name in the key store.
// This is permanent and irreversible, and will change the onion service // This is permanent and irreversible, and will change the onion service
// address. // address.
func (g *Onion) DeleteKeys() error { func (g *Onion) DeleteKeys() error {
log.WithField("Onion keys", g.getName()).Debug("Deleting Onion keys")
return DeleteOnionKeys(g.getName()) return DeleteOnionKeys(g.getName())
} }
@@ -166,36 +235,50 @@ func NewOnion(name string) (*Onion, error) {
// name in the key store. If the key already exists, it will be // name in the key store. If the key already exists, it will be
// returned. If it does not exist, it will be generated. // returned. If it does not exist, it will be generated.
func TorKeys(keyName string) (ed25519.KeyPair, error) { func TorKeys(keyName string) (ed25519.KeyPair, error) {
log.WithField("key_name", keyName).Debug("Getting Tor keys")
keystore, err := TorKeystorePath() keystore, err := TorKeystorePath()
if err != nil { if err != nil {
log.WithError(err).Error("Failed to get keystore path")
return nil, fmt.Errorf("onramp OnionKeys: discovery error %v", err) return nil, fmt.Errorf("onramp OnionKeys: discovery error %v", err)
} }
var keys ed25519.KeyPair var keys ed25519.KeyPair
keysPath := filepath.Join(keystore, keyName+".tor.private") keysPath := filepath.Join(keystore, keyName+".tor.private")
log.WithField("path", keysPath).Debug("Checking for existing keys")
if _, err := os.Stat(keysPath); os.IsNotExist(err) { if _, err := os.Stat(keysPath); os.IsNotExist(err) {
log.Debug("Generating new Tor keys")
tkeys, err := ed25519.GenerateKey(nil) tkeys, err := ed25519.GenerateKey(nil)
if err != nil { if err != nil {
log.Fatalf("Unable to generate onion service key, %s", err) log.WithError(err).Error("Failed to generate onion service key")
log.Fatal("Unable to generate onion service key")
} }
keys = tkeys keys = tkeys
log.WithField("path", keysPath).Debug("Creating key file")
f, err := os.Create(keysPath) f, err := os.Create(keysPath)
if err != nil { if err != nil {
log.Fatalf("Unable to create Tor keys file for writing, %s", err) log.WithError(err).Error("Failed to create Tor keys file")
log.Fatal("Unable to create Tor keys file for writing")
} }
defer f.Close() defer f.Close()
_, err = f.Write(tkeys.PrivateKey()) _, err = f.Write(tkeys.PrivateKey())
if err != nil { if err != nil {
log.Fatalf("Unable to write Tor keys to disk, %s", err) log.WithError(err).Error("Failed to write Tor keys to disk")
log.Fatal("Unable to write Tor keys to disk")
} }
log.Debug("Successfully generated and stored new keys")
} else if err == nil { } else if err == nil {
tkeys, err := ioutil.ReadFile(keysPath) log.Debug("Loading existing Tor keys")
tkeys, err := os.ReadFile(keysPath)
if err != nil { if err != nil {
log.Fatalf("Unable to read Tor keys from disk") log.WithError(err).Error("Failed to read Tor keys from disk")
log.Fatal("Unable to read Tor keys from disk")
} }
k := ed25519.FromCryptoPrivateKey(tkeys) k := ed25519.FromCryptoPrivateKey(tkeys)
keys = k keys = k
log.Debug("Successfully loaded existing keys")
} else { } else {
log.Fatalf("Unable to set up Tor keys, %s", err) log.WithError(err).Error("Failed to set up Tor keys")
log.Fatal("Unable to set up Tor keys")
} }
return keys, nil return keys, nil
} }
@@ -205,18 +288,34 @@ var onions map[string]*Onion
// CloseAllOnion closes all onions managed by the onramp package. It does not // CloseAllOnion closes all onions managed by the onramp package. It does not
// affect objects instantiated by an app. // affect objects instantiated by an app.
func CloseAllOnion() { func CloseAllOnion() {
log.WithField("count", len(onions)).Debug("Closing all Onion services")
for i, g := range onions { for i, g := range onions {
log.Println("Closing onion", g.name) log.WithFields(logrus.Fields{
"index": i,
"name": g.name,
}).Debug("Closing Onion service")
CloseOnion(i) CloseOnion(i)
} }
log.Debug("All Onion services closed")
} }
// CloseOnion closes the Onion at the given index. It does not affect Onion // CloseOnion closes the Onion at the given index. It does not affect Onion
// objects instantiated by an app. // objects instantiated by an app.
func CloseOnion(tunName string) { func CloseOnion(tunName string) {
log.WithField("tunnel_name", tunName).Debug("Attempting to close Onion service")
g, ok := onions[tunName] g, ok := onions[tunName]
if ok { if ok {
g.Close() log.WithField("name", g.name).Debug("Found Onion service, closing")
err := g.Close()
if err != nil {
log.WithError(err).Error("Failed to close Onion service")
} else {
log.Debug("Successfully closed Onion service")
}
} else {
log.Debug("No Onion service found for tunnel name")
} }
} }
@@ -224,12 +323,28 @@ func CloseOnion(tunName string) {
// corresponding to a structure managed by the onramp library // corresponding to a structure managed by the onramp library
// and not instantiated by an app. // and not instantiated by an app.
func ListenOnion(network, keys string) (net.Listener, error) { func ListenOnion(network, keys string) (net.Listener, error) {
log.WithFields(logrus.Fields{
"network": network,
"keys": keys,
}).Debug("Creating new Onion listener")
g, err := NewOnion(keys) g, err := NewOnion(keys)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create new Onion")
return nil, fmt.Errorf("onramp Listen: %v", err) return nil, fmt.Errorf("onramp Listen: %v", err)
} }
onions[keys] = g onions[keys] = g
return g.Listen() log.Debug("Onion service registered, creating listener")
listener, err := g.Listen()
if err != nil {
log.WithError(err).Error("Failed to create Onion listener")
return nil, err
}
log.Debug("Successfully created Onion listener")
return listener, nil
// return g.Listen()
} }
// DialOnion returns a net.Conn for a onion structure's keys // DialOnion returns a net.Conn for a onion structure's keys
@@ -247,13 +362,19 @@ func DialOnion(network, addr string) (net.Conn, error) {
// DeleteOnionKeys deletes the key file at the given path as determined by // DeleteOnionKeys deletes the key file at the given path as determined by
// keystore + tunName. // keystore + tunName.
func DeleteOnionKeys(tunName string) error { func DeleteOnionKeys(tunName string) error {
log.WithField("tunnel_name", tunName).Debug("Attempting to delete Onion keys")
keystore, err := TorKeystorePath() keystore, err := TorKeystorePath()
if err != nil { if err != nil {
log.WithError(err).Error("Failed to get keystore path")
return fmt.Errorf("onramp DeleteOnionKeys: discovery error %v", err) return fmt.Errorf("onramp DeleteOnionKeys: discovery error %v", err)
} }
keyspath := filepath.Join(keystore, tunName+".i2p.private") keyspath := filepath.Join(keystore, tunName+".i2p.private")
log.WithError(err).Error("Failed to get keystore path")
if err := os.Remove(keyspath); err != nil { if err := os.Remove(keyspath); err != nil {
log.WithError(err).WithField("path", keyspath).Error("Failed to delete key file")
return fmt.Errorf("onramp DeleteOnionKeys: %v", err) return fmt.Errorf("onramp DeleteOnionKeys: %v", err)
} }
log.Debug("Successfully deleted Onion keys")
return nil return nil
} }

View File

@@ -6,8 +6,7 @@ package onramp
import ( import (
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"io/ioutil" "io"
"log"
"net/http" "net/http"
"testing" "testing"
) )
@@ -25,7 +24,7 @@ func TestBareOnion(t *testing.T) {
t.Error(err) t.Error(err)
} }
log.Println("listener:", listener.Addr().String()) log.Println("listener:", listener.Addr().String())
//defer listener.Close() // defer listener.Close()
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %q", r.URL.Path) fmt.Fprintf(w, "Hello, %q", r.URL.Path)
}) })
@@ -45,7 +44,7 @@ func TestBareOnion(t *testing.T) {
t.Error(err) t.Error(err)
} }
fmt.Println("Status:", resp.Status) fmt.Println("Status:", resp.Status)
body, err := ioutil.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }

View File

@@ -2,9 +2,10 @@ package onramp
import ( import (
"io" "io"
"log"
"net" "net"
"strings" "strings"
"github.com/sirupsen/logrus"
) )
type OnrampProxy struct { type OnrampProxy struct {
@@ -21,30 +22,59 @@ type OnrampProxy struct {
// and an I2P or Onion address, and it will act as a tunnel to a // and an I2P or Onion address, and it will act as a tunnel to a
// listening hidden service somewhere. // listening hidden service somewhere.
func (p *OnrampProxy) Proxy(list net.Listener, raddr string) error { func (p *OnrampProxy) Proxy(list net.Listener, raddr string) error {
log.WithFields(logrus.Fields{
"remote_address": raddr,
"local_address": list.Addr().String(),
}).Debug("Starting proxy service")
for { for {
log.Debug("Waiting for incoming connection")
conn, err := list.Accept() conn, err := list.Accept()
if err != nil { if err != nil {
log.WithError(err).Error("Failed to accept connection")
return err return err
} }
log.WithFields(logrus.Fields{
"local_addr": conn.LocalAddr().String(),
"remote_addr": conn.RemoteAddr().String(),
}).Debug("Accepted new connection, starting proxy routine")
go p.proxy(conn, raddr) go p.proxy(conn, raddr)
} }
} }
func (p *OnrampProxy) proxy(conn net.Conn, raddr string) { func (p *OnrampProxy) proxy(conn net.Conn, raddr string) {
log.WithFields(logrus.Fields{
"remote_address": raddr,
"local_addr": conn.LocalAddr().String(),
"remote_addr": conn.RemoteAddr().String(),
}).Debug("Setting up proxy connection")
var remote net.Conn var remote net.Conn
var err error var err error
checkaddr := strings.Split(raddr, ":")[0] checkaddr := strings.Split(raddr, ":")[0]
if strings.HasSuffix(checkaddr, ".i2p") { if strings.HasSuffix(checkaddr, ".i2p") {
log.Debug("Detected I2P address, using Garlic connection")
remote, err = p.Garlic.Dial("tcp", raddr) remote, err = p.Garlic.Dial("tcp", raddr)
} else if strings.HasSuffix(checkaddr, ".onion") { } else if strings.HasSuffix(checkaddr, ".onion") {
log.Debug("Detected Onion address, using Tor connection")
remote, err = p.Onion.Dial("tcp", raddr) remote, err = p.Onion.Dial("tcp", raddr)
} else { } else {
log.Debug("Using standard TCP connection")
remote, err = net.Dial("tcp", raddr) remote, err = net.Dial("tcp", raddr)
} }
if err != nil { if err != nil {
log.Fatalf("cannot dial to remote: %v", err) log.WithError(err).Error("Failed to establish remote connection")
log.Fatal("Cannot dial to remote")
} }
defer remote.Close() defer remote.Close()
log.WithFields(logrus.Fields{
"local_addr": remote.LocalAddr().String(),
"remote_addr": remote.RemoteAddr().String(),
}).Debug("Remote connection established, starting bidirectional copy")
go io.Copy(remote, conn) go io.Copy(remote, conn)
io.Copy(conn, remote) io.Copy(conn, remote)
} }

54
tls.go
View File

@@ -17,6 +17,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/sirupsen/logrus"
"github.com/cretz/bine/torutil" "github.com/cretz/bine/torutil"
) )
@@ -24,11 +26,14 @@ import (
// if no TLS keys exist, they will be generated. They will be valid for // if no TLS keys exist, they will be generated. They will be valid for
// the .b32.i2p domain. // the .b32.i2p domain.
func (g *Garlic) TLSKeys() (tls.Certificate, error) { func (g *Garlic) TLSKeys() (tls.Certificate, error) {
log.WithField("name", g.getName()).Debug("Getting TLS keys for Garlic service")
keys, err := g.Keys() keys, err := g.Keys()
if err != nil { if err != nil {
log.WithError(err).Error("Failed to get I2P keys")
return tls.Certificate{}, err return tls.Certificate{}, err
} }
base32 := keys.Addr().Base32() base32 := keys.Addr().Base32()
log.WithField("base32", base32).Debug("Retrieving TLS certificate for base32 address")
return TLSKeys(base32) return TLSKeys(base32)
} }
@@ -36,31 +41,45 @@ func (g *Garlic) TLSKeys() (tls.Certificate, error) {
// if no TLS keys exist, they will be generated. They will be valid for // if no TLS keys exist, they will be generated. They will be valid for
// the .onion domain. // the .onion domain.
func (o *Onion) TLSKeys() (tls.Certificate, error) { func (o *Onion) TLSKeys() (tls.Certificate, error) {
log.WithField("name", o.getName()).Debug("Getting TLS keys for Onion service")
keys, err := o.Keys() keys, err := o.Keys()
if err != nil { if err != nil {
return tls.Certificate{}, err return tls.Certificate{}, err
} }
onionService := torutil.OnionServiceIDFromPrivateKey(keys) onionService := torutil.OnionServiceIDFromPrivateKey(keys)
log.WithField("onion_service", onionService).Debug("Retrieving TLS certificate for onion service")
return TLSKeys(onionService) return TLSKeys(onionService)
} }
// TLSKeys returns the TLS certificate and key for the given hostname. // TLSKeys returns the TLS certificate and key for the given hostname.
func TLSKeys(tlsHost string) (tls.Certificate, error) { func TLSKeys(tlsHost string) (tls.Certificate, error) {
log.WithField("host", tlsHost).Debug("Getting TLS certificate and key")
tlsCert := tlsHost + ".crt" tlsCert := tlsHost + ".crt"
tlsKey := tlsHost + ".pem" tlsKey := tlsHost + ".pem"
if err := CreateTLSCertificate(tlsHost); nil != err { if err := CreateTLSCertificate(tlsHost); nil != err {
log.WithError(err).Error("Failed to create TLS certificate")
return tls.Certificate{}, err return tls.Certificate{}, err
} }
tlsKeystorePath, err := TLSKeystorePath() tlsKeystorePath, err := TLSKeystorePath()
if err != nil { if err != nil {
log.WithError(err).Error("Failed to get TLS keystore path")
return tls.Certificate{}, err return tls.Certificate{}, err
} }
tlsCertPath := filepath.Join(tlsKeystorePath, tlsCert) tlsCertPath := filepath.Join(tlsKeystorePath, tlsCert)
tlsKeyPath := filepath.Join(tlsKeystorePath, tlsKey) tlsKeyPath := filepath.Join(tlsKeystorePath, tlsKey)
log.WithFields(logrus.Fields{
"cert_path": tlsCertPath,
"key_path": tlsKeyPath,
}).Debug("Loading TLS certificate pair")
cert, err := tls.LoadX509KeyPair(tlsCertPath, tlsKeyPath) cert, err := tls.LoadX509KeyPair(tlsCertPath, tlsKeyPath)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to load TLS certificate pair")
return cert, err return cert, err
} }
log.Debug("Successfully loaded TLS certificate and key")
return cert, nil return cert, nil
} }
@@ -68,6 +87,7 @@ func TLSKeys(tlsHost string) (tls.Certificate, error) {
// and stores it in the TLS keystore for the application. If the keys already // and stores it in the TLS keystore for the application. If the keys already
// exist, generation is skipped. // exist, generation is skipped.
func CreateTLSCertificate(tlsHost string) error { func CreateTLSCertificate(tlsHost string) error {
log.WithField("host", tlsHost).Debug("Creating TLS certificate")
tlsCertName := tlsHost + ".crt" tlsCertName := tlsHost + ".crt"
tlsKeyName := tlsHost + ".pem" tlsKeyName := tlsHost + ".pem"
tlsKeystorePath, err := TLSKeystorePath() tlsKeystorePath, err := TLSKeystorePath()
@@ -79,70 +99,102 @@ func CreateTLSCertificate(tlsHost string) error {
_, certErr := os.Stat(tlsCert) _, certErr := os.Stat(tlsCert)
_, keyErr := os.Stat(tlsKey) _, keyErr := os.Stat(tlsKey)
if certErr != nil || keyErr != nil { if certErr != nil || keyErr != nil {
log.WithFields(logrus.Fields{
"cert_exists": certErr == nil,
"key_exists": keyErr == nil,
"cert_path": tlsCert,
"key_path": tlsKey,
}).Debug("Certificate or key missing, generating new ones")
if certErr != nil { if certErr != nil {
log.WithField("path", tlsCert).Debug("TLS certificate not found")
fmt.Printf("Unable to read TLS certificate '%s'\n", tlsCert) fmt.Printf("Unable to read TLS certificate '%s'\n", tlsCert)
} }
if keyErr != nil { if keyErr != nil {
log.WithField("path", tlsKey).Debug("TLS key not found")
fmt.Printf("Unable to read TLS key '%s'\n", tlsKey) fmt.Printf("Unable to read TLS key '%s'\n", tlsKey)
} }
if err := createTLSCertificate(tlsHost); nil != err { if err := createTLSCertificate(tlsHost); nil != err {
log.WithError(err).Error("Failed to create TLS certificate")
return err return err
} }
} else {
log.Debug("TLS certificate and key already exist")
} }
return nil return nil
} }
func createTLSCertificate(host string) error { func createTLSCertificate(host string) error {
log.WithField("host", host).Debug("Generating new TLS certificate")
fmt.Println("Generating TLS keys. This may take a minute...") fmt.Println("Generating TLS keys. This may take a minute...")
priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to generate private key")
return err return err
} }
tlsCert, err := NewTLSCertificate(host, priv) tlsCert, err := NewTLSCertificate(host, priv)
if nil != err { if nil != err {
log.WithError(err).Error("Failed to create new TLS certificate")
return err return err
} }
privStore, err := TLSKeystorePath() privStore, err := TLSKeystorePath()
if nil != err { if nil != err {
log.WithError(err).Error("Failed to get keystore path")
return err return err
} }
certFile := filepath.Join(privStore, host+".crt") certFile := filepath.Join(privStore, host+".crt")
log.WithField("path", certFile).Debug("Saving TLS certificate")
// save the TLS certificate // save the TLS certificate
certOut, err := os.Create(certFile) certOut, err := os.Create(certFile)
if err != nil { if err != nil {
log.WithError(err).WithField("path", certFile).Error("Failed to create certificate file")
return fmt.Errorf("failed to open %s for writing: %s", host+".crt", err) return fmt.Errorf("failed to open %s for writing: %s", host+".crt", err)
} }
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: tlsCert}) pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: tlsCert})
certOut.Close() certOut.Close()
log.WithField("path", certFile).Debug("TLS certificate saved successfully")
fmt.Printf("\tTLS certificate saved to: %s\n", host+".crt") fmt.Printf("\tTLS certificate saved to: %s\n", host+".crt")
// save the TLS private key // save the TLS private key
privFile := filepath.Join(privStore, host+".pem") privFile := filepath.Join(privStore, host+".pem")
log.WithField("path", privFile).Debug("Saving TLS private key")
keyOut, err := os.OpenFile(privFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) keyOut, err := os.OpenFile(privFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil { if err != nil {
log.WithError(err).WithField("path", privFile).Error("Failed to create private key file")
return fmt.Errorf("failed to open %s for writing: %v", privFile, err) return fmt.Errorf("failed to open %s for writing: %v", privFile, err)
} }
secp384r1, err := asn1.Marshal(asn1.ObjectIdentifier{1, 3, 132, 0, 34}) // http://www.ietf.org/rfc/rfc5480.txt secp384r1, err := asn1.Marshal(asn1.ObjectIdentifier{1, 3, 132, 0, 34}) // http://www.ietf.org/rfc/rfc5480.txt
if err != nil {
log.WithError(err).Error("Failed to marshal EC parameters")
return err
}
pem.Encode(keyOut, &pem.Block{Type: "EC PARAMETERS", Bytes: secp384r1}) pem.Encode(keyOut, &pem.Block{Type: "EC PARAMETERS", Bytes: secp384r1})
ecder, err := x509.MarshalECPrivateKey(priv) ecder, err := x509.MarshalECPrivateKey(priv)
if err != nil {
log.WithError(err).Error("Failed to marshal private key")
return err
}
pem.Encode(keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: ecder}) pem.Encode(keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: ecder})
pem.Encode(keyOut, &pem.Block{Type: "CERTIFICATE", Bytes: tlsCert}) pem.Encode(keyOut, &pem.Block{Type: "CERTIFICATE", Bytes: tlsCert})
keyOut.Close() keyOut.Close()
log.WithField("path", privFile).Debug("TLS private key saved successfully")
fmt.Printf("\tTLS private key saved to: %s\n", privFile) fmt.Printf("\tTLS private key saved to: %s\n", privFile)
// CRL // CRL
crlFile := filepath.Join(privStore, host+".crl") crlFile := filepath.Join(privStore, host+".crl")
log.WithField("path", crlFile).Debug("Creating CRL")
crlOut, err := os.OpenFile(crlFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) crlOut, err := os.OpenFile(crlFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil { if err != nil {
log.WithError(err).WithField("path", crlFile).Error("Failed to create CRL file")
return fmt.Errorf("failed to open %s for writing: %s", crlFile, err) return fmt.Errorf("failed to open %s for writing: %s", crlFile, err)
} }
crlcert, err := x509.ParseCertificate(tlsCert) crlcert, err := x509.ParseCertificate(tlsCert)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to parse certificate for CRL creation")
return fmt.Errorf("Certificate with unknown critical extension was not parsed: %s", err) return fmt.Errorf("Certificate with unknown critical extension was not parsed: %s", err)
} }
@@ -156,10 +208,12 @@ func createTLSCertificate(host string) error {
crlBytes, err := crlcert.CreateCRL(rand.Reader, priv, revokedCerts, now, now) crlBytes, err := crlcert.CreateCRL(rand.Reader, priv, revokedCerts, now, now)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create CRL")
return fmt.Errorf("error creating CRL: %s", err) return fmt.Errorf("error creating CRL: %s", err)
} }
_, err = x509.ParseDERCRL(crlBytes) _, err = x509.ParseDERCRL(crlBytes)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to validate generated CRL")
return fmt.Errorf("error reparsing CRL: %s", err) return fmt.Errorf("error reparsing CRL: %s", err)
} }
pem.Encode(crlOut, &pem.Block{Type: "X509 CRL", Bytes: crlBytes}) pem.Encode(crlOut, &pem.Block{Type: "X509 CRL", Bytes: crlBytes})