0.3.1
This commit is contained in:
@@ -1,77 +1,77 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
DEFAULT_SHARED_INSTANCE_PORT = 37428
|
||||
DEFAULT_INSTANCE_CONTROL_PORT = 37429
|
||||
DEFAULT_LOG_LEVEL = 20
|
||||
DEFAULT_SHARED_INSTANCE_PORT = 37428
|
||||
DEFAULT_INSTANCE_CONTROL_PORT = 37429
|
||||
DEFAULT_LOG_LEVEL = 20
|
||||
)
|
||||
|
||||
// ConfigProvider interface for accessing configuration
|
||||
type ConfigProvider interface {
|
||||
GetConfigPath() string
|
||||
GetLogLevel() int
|
||||
GetInterfaces() map[string]InterfaceConfig
|
||||
GetConfigPath() string
|
||||
GetLogLevel() int
|
||||
GetInterfaces() map[string]InterfaceConfig
|
||||
}
|
||||
|
||||
// InterfaceConfig represents interface configuration
|
||||
type InterfaceConfig struct {
|
||||
Name string `toml:"name"`
|
||||
Type string `toml:"type"`
|
||||
Enabled bool `toml:"enabled"`
|
||||
Address string `toml:"address"`
|
||||
Port int `toml:"port"`
|
||||
TargetHost string `toml:"target_host"`
|
||||
TargetPort int `toml:"target_port"`
|
||||
TargetAddress string `toml:"target_address"`
|
||||
Interface string `toml:"interface"`
|
||||
KISSFraming bool `toml:"kiss_framing"`
|
||||
I2PTunneled bool `toml:"i2p_tunneled"`
|
||||
PreferIPv6 bool `toml:"prefer_ipv6"`
|
||||
MaxReconnTries int `toml:"max_reconnect_tries"`
|
||||
Bitrate int64 `toml:"bitrate"`
|
||||
MTU int `toml:"mtu"`
|
||||
GroupID string
|
||||
DiscoveryScope string
|
||||
Name string
|
||||
Type string
|
||||
Enabled bool
|
||||
Address string
|
||||
Port int
|
||||
TargetHost string
|
||||
TargetPort int
|
||||
TargetAddress string
|
||||
Interface string
|
||||
KISSFraming bool
|
||||
I2PTunneled bool
|
||||
PreferIPv6 bool
|
||||
MaxReconnTries int
|
||||
Bitrate int64
|
||||
MTU int
|
||||
GroupID string
|
||||
DiscoveryScope string
|
||||
DiscoveryPort int
|
||||
DataPort int
|
||||
}
|
||||
|
||||
// ReticulumConfig represents the main configuration structure
|
||||
type ReticulumConfig struct {
|
||||
ConfigPath string `toml:"-"`
|
||||
EnableTransport bool `toml:"enable_transport"`
|
||||
ShareInstance bool `toml:"share_instance"`
|
||||
SharedInstancePort int `toml:"shared_instance_port"`
|
||||
InstanceControlPort int `toml:"instance_control_port"`
|
||||
PanicOnInterfaceErr bool `toml:"panic_on_interface_error"`
|
||||
LogLevel int `toml:"loglevel"`
|
||||
Interfaces map[string]*InterfaceConfig `toml:"interfaces"`
|
||||
ConfigPath string
|
||||
EnableTransport bool
|
||||
ShareInstance bool
|
||||
SharedInstancePort int
|
||||
InstanceControlPort int
|
||||
PanicOnInterfaceErr bool
|
||||
LogLevel int
|
||||
Interfaces map[string]*InterfaceConfig
|
||||
}
|
||||
|
||||
// NewReticulumConfig creates a new ReticulumConfig with default values
|
||||
func NewReticulumConfig() *ReticulumConfig {
|
||||
return &ReticulumConfig{
|
||||
EnableTransport: true,
|
||||
ShareInstance: false,
|
||||
SharedInstancePort: DEFAULT_SHARED_INSTANCE_PORT,
|
||||
InstanceControlPort: DEFAULT_INSTANCE_CONTROL_PORT,
|
||||
PanicOnInterfaceErr: false,
|
||||
LogLevel: DEFAULT_LOG_LEVEL,
|
||||
Interfaces: make(map[string]*InterfaceConfig),
|
||||
}
|
||||
return &ReticulumConfig{
|
||||
EnableTransport: true,
|
||||
ShareInstance: false,
|
||||
SharedInstancePort: DEFAULT_SHARED_INSTANCE_PORT,
|
||||
InstanceControlPort: DEFAULT_INSTANCE_CONTROL_PORT,
|
||||
PanicOnInterfaceErr: false,
|
||||
LogLevel: DEFAULT_LOG_LEVEL,
|
||||
Interfaces: make(map[string]*InterfaceConfig),
|
||||
}
|
||||
}
|
||||
|
||||
// Validate checks if the configuration is valid
|
||||
func (c *ReticulumConfig) Validate() error {
|
||||
if c.SharedInstancePort < 1 || c.SharedInstancePort > 65535 {
|
||||
return fmt.Errorf("invalid shared instance port: %d", c.SharedInstancePort)
|
||||
}
|
||||
if c.InstanceControlPort < 1 || c.InstanceControlPort > 65535 {
|
||||
return fmt.Errorf("invalid instance control port: %d", c.InstanceControlPort)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if c.SharedInstancePort < 1 || c.SharedInstancePort > 65535 {
|
||||
return fmt.Errorf("invalid shared instance port: %d", c.SharedInstancePort)
|
||||
}
|
||||
if c.InstanceControlPort < 1 || c.InstanceControlPort > 65535 {
|
||||
return fmt.Errorf("invalid instance control port: %d", c.InstanceControlPort)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,49 +1,270 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"gopkg.in/yaml.v3"
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Identity struct {
|
||||
Name string `yaml:"name"`
|
||||
StoragePath string `yaml:"storage_path"`
|
||||
} `yaml:"identity"`
|
||||
Identity struct {
|
||||
Name string
|
||||
StoragePath string
|
||||
}
|
||||
|
||||
Interfaces []struct {
|
||||
Name string `yaml:"name"`
|
||||
Type string `yaml:"type"`
|
||||
Enabled bool `yaml:"enabled"`
|
||||
ListenPort int `yaml:"listen_port"`
|
||||
ListenIP string `yaml:"listen_ip"`
|
||||
KissFraming bool `yaml:"kiss_framing"`
|
||||
I2PTunneled bool `yaml:"i2p_tunneled"`
|
||||
} `yaml:"interfaces"`
|
||||
Interfaces []struct {
|
||||
Name string
|
||||
Type string
|
||||
Enabled bool
|
||||
ListenPort int
|
||||
ListenIP string
|
||||
KissFraming bool
|
||||
I2PTunneled bool
|
||||
}
|
||||
|
||||
Transport struct {
|
||||
AnnounceInterval int `yaml:"announce_interval"`
|
||||
PathRequestTimeout int `yaml:"path_request_timeout"`
|
||||
MaxHops int `yaml:"max_hops"`
|
||||
BitrateLimit int64 `yaml:"bitrate_limit"`
|
||||
} `yaml:"transport"`
|
||||
Transport struct {
|
||||
AnnounceInterval int
|
||||
PathRequestTimeout int
|
||||
MaxHops int
|
||||
BitrateLimit int64
|
||||
}
|
||||
|
||||
Logging struct {
|
||||
Level string `yaml:"level"`
|
||||
File string `yaml:"file"`
|
||||
} `yaml:"logging"`
|
||||
Logging struct {
|
||||
Level string
|
||||
File string
|
||||
}
|
||||
}
|
||||
|
||||
func LoadConfig(path string) (*Config, error) {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var cfg Config
|
||||
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg := &Config{}
|
||||
scanner := bufio.NewScanner(file)
|
||||
var currentSection string
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
|
||||
// Skip comments and empty lines
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle sections
|
||||
if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
|
||||
currentSection = strings.Trim(line, "[]")
|
||||
|
||||
// If this is an interface section, append new interface
|
||||
if strings.HasPrefix(currentSection, "interface ") {
|
||||
cfg.Interfaces = append(cfg.Interfaces, struct {
|
||||
Name string
|
||||
Type string
|
||||
Enabled bool
|
||||
ListenPort int
|
||||
ListenIP string
|
||||
KissFraming bool
|
||||
I2PTunneled bool
|
||||
}{})
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse key-value pairs
|
||||
parts := strings.SplitN(line, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
key := strings.TrimSpace(parts[0])
|
||||
value := strings.TrimSpace(parts[1])
|
||||
|
||||
switch currentSection {
|
||||
case "identity":
|
||||
switch key {
|
||||
case "name":
|
||||
cfg.Identity.Name = value
|
||||
case "storage_path":
|
||||
cfg.Identity.StoragePath = value
|
||||
}
|
||||
|
||||
case "transport":
|
||||
switch key {
|
||||
case "announce_interval":
|
||||
cfg.Transport.AnnounceInterval, _ = strconv.Atoi(value)
|
||||
case "path_request_timeout":
|
||||
cfg.Transport.PathRequestTimeout, _ = strconv.Atoi(value)
|
||||
case "max_hops":
|
||||
cfg.Transport.MaxHops, _ = strconv.Atoi(value)
|
||||
case "bitrate_limit":
|
||||
cfg.Transport.BitrateLimit, _ = strconv.ParseInt(value, 10, 64)
|
||||
}
|
||||
|
||||
case "logging":
|
||||
switch key {
|
||||
case "level":
|
||||
cfg.Logging.Level = value
|
||||
case "file":
|
||||
cfg.Logging.File = value
|
||||
}
|
||||
|
||||
default:
|
||||
// Handle interface sections
|
||||
if strings.HasPrefix(currentSection, "interface ") && len(cfg.Interfaces) > 0 {
|
||||
iface := &cfg.Interfaces[len(cfg.Interfaces)-1]
|
||||
switch key {
|
||||
case "name":
|
||||
iface.Name = value
|
||||
case "type":
|
||||
iface.Type = value
|
||||
case "enabled":
|
||||
iface.Enabled = value == "true"
|
||||
case "listen_port":
|
||||
iface.ListenPort, _ = strconv.Atoi(value)
|
||||
case "listen_ip":
|
||||
iface.ListenIP = value
|
||||
case "kiss_framing":
|
||||
iface.KissFraming = value == "true"
|
||||
case "i2p_tunneled":
|
||||
iface.I2PTunneled = value == "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func SaveConfig(cfg *Config, path string) error {
|
||||
var builder strings.Builder
|
||||
|
||||
// Write Identity section
|
||||
builder.WriteString("[identity]\n")
|
||||
builder.WriteString(fmt.Sprintf("name = %s\n", cfg.Identity.Name))
|
||||
builder.WriteString(fmt.Sprintf("storage_path = %s\n\n", cfg.Identity.StoragePath))
|
||||
|
||||
// Write Transport section
|
||||
builder.WriteString("[transport]\n")
|
||||
builder.WriteString(fmt.Sprintf("announce_interval = %d\n", cfg.Transport.AnnounceInterval))
|
||||
builder.WriteString(fmt.Sprintf("path_request_timeout = %d\n", cfg.Transport.PathRequestTimeout))
|
||||
builder.WriteString(fmt.Sprintf("max_hops = %d\n", cfg.Transport.MaxHops))
|
||||
builder.WriteString(fmt.Sprintf("bitrate_limit = %d\n\n", cfg.Transport.BitrateLimit))
|
||||
|
||||
// Write Logging section
|
||||
builder.WriteString("[logging]\n")
|
||||
builder.WriteString(fmt.Sprintf("level = %s\n", cfg.Logging.Level))
|
||||
builder.WriteString(fmt.Sprintf("file = %s\n\n", cfg.Logging.File))
|
||||
|
||||
// Write Interface sections
|
||||
for _, iface := range cfg.Interfaces {
|
||||
builder.WriteString(fmt.Sprintf("[interface %s]\n", iface.Name))
|
||||
builder.WriteString(fmt.Sprintf("type = %s\n", iface.Type))
|
||||
builder.WriteString(fmt.Sprintf("enabled = %v\n", iface.Enabled))
|
||||
builder.WriteString(fmt.Sprintf("listen_port = %d\n", iface.ListenPort))
|
||||
builder.WriteString(fmt.Sprintf("listen_ip = %s\n", iface.ListenIP))
|
||||
builder.WriteString(fmt.Sprintf("kiss_framing = %v\n", iface.KissFraming))
|
||||
builder.WriteString(fmt.Sprintf("i2p_tunneled = %v\n\n", iface.I2PTunneled))
|
||||
}
|
||||
|
||||
return os.WriteFile(path, []byte(builder.String()), 0644)
|
||||
}
|
||||
|
||||
func GetConfigDir() string {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
// Fallback to current directory if home directory cannot be determined
|
||||
return ".reticulum-go"
|
||||
}
|
||||
return filepath.Join(homeDir, ".reticulum-go")
|
||||
}
|
||||
|
||||
func GetDefaultConfigPath() string {
|
||||
return filepath.Join(GetConfigDir(), "config")
|
||||
}
|
||||
|
||||
func EnsureConfigDir() error {
|
||||
configDir := GetConfigDir()
|
||||
return os.MkdirAll(configDir, 0755)
|
||||
}
|
||||
|
||||
func InitConfig() (*Config, error) {
|
||||
// Ensure config directory exists
|
||||
if err := EnsureConfigDir(); err != nil {
|
||||
return nil, fmt.Errorf("failed to create config directory: %v", err)
|
||||
}
|
||||
|
||||
configPath := GetDefaultConfigPath()
|
||||
|
||||
// Check if config file exists
|
||||
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
||||
// Create default config
|
||||
cfg := &Config{}
|
||||
|
||||
// Set default values
|
||||
cfg.Identity.Name = "reticulum-node"
|
||||
cfg.Identity.StoragePath = filepath.Join(GetConfigDir(), "storage")
|
||||
|
||||
cfg.Transport.AnnounceInterval = 300
|
||||
cfg.Transport.PathRequestTimeout = 15
|
||||
cfg.Transport.MaxHops = 8
|
||||
cfg.Transport.BitrateLimit = 1000000
|
||||
|
||||
cfg.Logging.Level = "info"
|
||||
cfg.Logging.File = filepath.Join(GetConfigDir(), "reticulum.log")
|
||||
|
||||
// Add default interfaces
|
||||
cfg.Interfaces = append(cfg.Interfaces, struct {
|
||||
Name string
|
||||
Type string
|
||||
Enabled bool
|
||||
ListenPort int
|
||||
ListenIP string
|
||||
KissFraming bool
|
||||
I2PTunneled bool
|
||||
}{
|
||||
Name: "Local UDP",
|
||||
Type: "UDPInterface",
|
||||
Enabled: true,
|
||||
ListenPort: 37697,
|
||||
ListenIP: "0.0.0.0",
|
||||
})
|
||||
|
||||
cfg.Interfaces = append(cfg.Interfaces, struct {
|
||||
Name string
|
||||
Type string
|
||||
Enabled bool
|
||||
ListenPort int
|
||||
ListenIP string
|
||||
KissFraming bool
|
||||
I2PTunneled bool
|
||||
}{
|
||||
Name: "Auto Discovery",
|
||||
Type: "AutoInterface",
|
||||
Enabled: true,
|
||||
ListenPort: 29717,
|
||||
})
|
||||
|
||||
// Save default config
|
||||
if err := SaveConfig(cfg, configPath); err != nil {
|
||||
return nil, fmt.Errorf("failed to save default config: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Load config
|
||||
cfg, err := LoadConfig(configPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load config: %v", err)
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
@@ -322,7 +322,8 @@ func (d *Destination) Encrypt(plaintext []byte) ([]byte, error) {
|
||||
if key == nil {
|
||||
return nil, errors.New("no ratchet key available")
|
||||
}
|
||||
return d.identity.EncryptSymmetric(plaintext, key)
|
||||
// CBC encryption with HMAC for group messages
|
||||
return d.identity.EncryptWithHMAC(plaintext, key)
|
||||
default:
|
||||
return nil, errors.New("unsupported destination type for encryption")
|
||||
}
|
||||
|
||||
@@ -57,51 +57,6 @@ var (
|
||||
ratchetPersistLock sync.Mutex
|
||||
)
|
||||
|
||||
func encryptAESGCM(key, plaintext []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nonce := make([]byte, gcm.NonceSize())
|
||||
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ciphertext := gcm.Seal(nonce, nonce, plaintext, nil)
|
||||
return ciphertext, nil
|
||||
}
|
||||
|
||||
func decryptAESGCM(key, ciphertext []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nonceSize := gcm.NonceSize()
|
||||
if len(ciphertext) < nonceSize {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
|
||||
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return plaintext, nil
|
||||
}
|
||||
|
||||
func encryptAESCBC(key, plaintext []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
@@ -412,20 +367,6 @@ func (i *Identity) GetCurrentRatchetKey() []byte {
|
||||
return latestKey
|
||||
}
|
||||
|
||||
func (i *Identity) EncryptSymmetric(plaintext []byte, key []byte) ([]byte, error) {
|
||||
if len(key) != 32 {
|
||||
return nil, errors.New("invalid key length")
|
||||
}
|
||||
return encryptAESGCM(key, plaintext)
|
||||
}
|
||||
|
||||
func (i *Identity) DecryptSymmetric(ciphertext []byte, key []byte) ([]byte, error) {
|
||||
if len(key) != 32 {
|
||||
return nil, errors.New("invalid key length")
|
||||
}
|
||||
return decryptAESGCM(key, ciphertext)
|
||||
}
|
||||
|
||||
func (i *Identity) Decrypt(ciphertextToken []byte, ratchets [][]byte, enforceRatchets bool, ratchetIDReceiver *common.RatchetIDReceiver) ([]byte, error) {
|
||||
if i.privateKey == nil {
|
||||
return nil, errors.New("decryption failed because identity does not hold a private key")
|
||||
|
||||
Reference in New Issue
Block a user