mirror of
https://github.com/go-i2p/go-meta-dialer.git
synced 2025-06-07 18:24:22 -04:00
Initial commit
This commit is contained in:
111
client.go
Normal file
111
client.go
Normal file
@ -0,0 +1,111 @@
|
||||
package metadialer
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MetaHTTPClient is an HTTP client that skips TLS verification for .i2p and .onion domains
|
||||
// but performs standard verification for all other domains.
|
||||
type MetaHTTPClient struct {
|
||||
*http.Client
|
||||
}
|
||||
|
||||
// NewMetaHTTPClient creates a new client with special handling for .i2p and .onion domains.
|
||||
// It accepts an optional root CA pool for custom certificate authorities.
|
||||
func NewMetaHTTPClient(rootCAs *x509.CertPool) *MetaHTTPClient {
|
||||
// Create a custom transport with our special TLS config
|
||||
transport := &http.Transport{
|
||||
Dial: Dial,
|
||||
TLSClientConfig: &tls.Config{
|
||||
RootCAs: rootCAs, // May be nil, which will use system default
|
||||
VerifyConnection: func(state tls.ConnectionState) error {
|
||||
// Skip verification for .onion and .i2p domains
|
||||
domain := state.ServerName
|
||||
if strings.HasSuffix(domain, ".onion") || strings.HasSuffix(domain, ".i2p") {
|
||||
// Skip verification for these special domains
|
||||
return nil
|
||||
}
|
||||
|
||||
// Use standard verification for all other domains
|
||||
opts := x509.VerifyOptions{
|
||||
DNSName: state.ServerName,
|
||||
Intermediates: x509.NewCertPool(),
|
||||
}
|
||||
for _, cert := range state.PeerCertificates[1:] {
|
||||
opts.Intermediates.AddCert(cert)
|
||||
}
|
||||
_, err := state.PeerCertificates[0].Verify(opts)
|
||||
return err
|
||||
},
|
||||
},
|
||||
// Set reasonable defaults
|
||||
MaxIdleConns: 10,
|
||||
IdleConnTimeout: 30 * time.Second,
|
||||
DisableCompression: false,
|
||||
}
|
||||
|
||||
return &MetaHTTPClient{
|
||||
Client: &http.Client{
|
||||
Transport: transport,
|
||||
Timeout: 30 * time.Second,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Get is a convenience method for making GET requests
|
||||
func (c *MetaHTTPClient) Get(url string) (*http.Response, error) {
|
||||
return c.Client.Get(url)
|
||||
}
|
||||
|
||||
// Post is a convenience method for making POST requests
|
||||
func (c *MetaHTTPClient) Post(url, contentType string, body interface{}) (*http.Response, error) {
|
||||
return c.Client.Post(url, contentType, nil) // Replace nil with actual body handling
|
||||
}
|
||||
|
||||
// PostForm is a convenience method for making POST requests with form data
|
||||
func (c *MetaHTTPClient) PostForm(url string, data url.Values) (*http.Response, error) {
|
||||
return c.Client.PostForm(url, data)
|
||||
}
|
||||
|
||||
// Do is a convenience method for making arbitrary HTTP requests
|
||||
func (c *MetaHTTPClient) Do(req *http.Request) (*http.Response, error) {
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
// Head is a convenience method for making HEAD requests
|
||||
func (c *MetaHTTPClient) Head(url string) (*http.Response, error) {
|
||||
return c.Client.Head(url)
|
||||
}
|
||||
|
||||
// CloseIdleConnections closes any idle connections in the transport
|
||||
func (c *MetaHTTPClient) CloseIdleConnections() {
|
||||
c.Client.CloseIdleConnections()
|
||||
}
|
||||
|
||||
// HTTPClient returns the underlying http.Client.
|
||||
// This is useful for accessing the raw client if needed.
|
||||
// It is anticipated that this will be necessary for some use cases.
|
||||
// For example, to configure the default dialer for the application:
|
||||
//
|
||||
// http.DefaultClient = client.HTTPClient()
|
||||
func (c *MetaHTTPClient) HTTPClient() *http.Client {
|
||||
return c.Client
|
||||
}
|
||||
|
||||
/*
|
||||
// init initializes the MetaHTTPClient and sets it as the default HTTP client.
|
||||
// This would be called when the application starts up, if not for being commented out here.
|
||||
// It is commented out to avoid side effects during package initialization.
|
||||
You can copy this code to your main package or wherever you want to initialize the client.
|
||||
func init() {
|
||||
// Initialize the MetaHTTPClient with default settings
|
||||
client := NewMetaHTTPClient(nil)
|
||||
// Set the default HTTP client to our custom client
|
||||
http.DefaultClient = client.HTTPClient()
|
||||
}
|
||||
*/
|
150
dialer.go
Normal file
150
dialer.go
Normal file
@ -0,0 +1,150 @@
|
||||
package metadialer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/go-i2p/onramp"
|
||||
)
|
||||
|
||||
var (
|
||||
// Garlic and Onion are the dialers for I2P and onion connections respectively.
|
||||
// Garlic is used for I2P connections and Onion is used for onion connections.
|
||||
// GarlicErr and OnionErr are the errors returned by the dialers.
|
||||
// It is important to `defer close` the dialers when you include them in your code.
|
||||
// Otherwise your SAMv3 or Tor sessions may leak. onramp tries to fix it for you but do it anyway.
|
||||
// in your `main` function, do:
|
||||
// defer Garlic.Close()
|
||||
// defer Onion.Close()
|
||||
Garlic, GarlicErr = onramp.NewGarlic(fmt.Sprintf("metadialer-%s", randomString()), "127.0.0.1:7656", onramp.OPT_DEFAULTS)
|
||||
Onion, OnionErr = onramp.NewOnion(fmt.Sprintf("metadialer-%s", randomString()))
|
||||
)
|
||||
|
||||
// randomString generates a random string of 4 characters.
|
||||
// It uses the crypto/rand package to generate a secure random byte slice,
|
||||
// which is then converted to a string using a custom alphabet.
|
||||
// The generated string is suitable for use as a unique identifier or token.
|
||||
func randomString() string {
|
||||
// Define the alphabet for the random string
|
||||
const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
// Create a byte slice to hold the random bytes
|
||||
b := make([]byte, 4)
|
||||
// Generate secure random bytes
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return ""
|
||||
}
|
||||
// Convert the random bytes to a string using the custom alphabet
|
||||
for i := range b {
|
||||
b[i] = alphabet[b[i]%byte(len(alphabet))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// ANON is a flag to indicate whether to use the onion dialer for all non-I2P connections.
|
||||
// If true, all non-I2P connections will be routed through the onion dialer.
|
||||
// If false, regular connection will be made directly.
|
||||
// Default is true.
|
||||
var ANON = true
|
||||
|
||||
// Dial is a custom dialer that handles .i2p and .onion domains differently.
|
||||
// It uses the garlic dialer for .i2p domains and the onion dialer for .onion domains.
|
||||
// For all other domains, it uses the default dialer.
|
||||
// If ANON is true, it will use the onion dialer for all non-I2P connections.
|
||||
// It returns a net.Conn interface for the connection.
|
||||
// If the address is invalid or the connection fails, it returns an error.
|
||||
// The network parameter is ignored for onion connections.
|
||||
var Dial = func(network, addr string) (net.Conn, error) {
|
||||
return dialHelper(network, addr)
|
||||
}
|
||||
|
||||
// DialContext is a custom dialer that handles .i2p and .onion domains differently.
|
||||
// It uses the garlic dialer for .i2p domains and the onion dialer for .onion domains.
|
||||
// For all other domains, it uses the default dialer.
|
||||
// If ANON is true, it will use the onion dialer for all non-I2P connections.
|
||||
// It returns a net.Conn interface for the connection.
|
||||
// If the address is invalid or the connection fails, it returns an error.
|
||||
// The network parameter is ignored for onion connections.
|
||||
// It accepts a context.Context parameter for cancellation and timeout.
|
||||
// The context is ignored.
|
||||
var DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return dialHelper(network, addr)
|
||||
}
|
||||
|
||||
func dialHelper(network, addr string) (net.Conn, error) {
|
||||
// convert the addr to a URL
|
||||
tld, err := GetTLD(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch tld {
|
||||
case "i2p":
|
||||
if GarlicErr != nil {
|
||||
return nil, GarlicErr
|
||||
}
|
||||
// I2P is a special case, we need to use the garlic dialer
|
||||
return Garlic.Dial(network, addr)
|
||||
case "onion":
|
||||
if OnionErr != nil {
|
||||
return nil, OnionErr
|
||||
}
|
||||
// make sure it is a TCP connection
|
||||
if network != "tcp" && network != "tcp4" && network != "tcp6" && network != "onion" {
|
||||
return nil, net.InvalidAddrError("only TCP connections are supported")
|
||||
}
|
||||
// Onion is a special case, we need to use the onion dialer
|
||||
return Onion.Dial("onion", addr)
|
||||
default:
|
||||
// If ANON is true, we need to use the onion dialer
|
||||
if ANON {
|
||||
if OnionErr != nil {
|
||||
return nil, OnionErr
|
||||
}
|
||||
// make sure it is a TCP connection
|
||||
if network != "tcp" {
|
||||
return nil, net.InvalidAddrError("only TCP connections are supported")
|
||||
}
|
||||
// ANON is a special case, we need to use the onion dialer
|
||||
return Onion.Dial(network, addr)
|
||||
} else {
|
||||
// For everything else, we can use the default dialer
|
||||
return net.Dial(network, addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetTLD is a helper function that returns the top-level domain of the given address.
|
||||
// It takes a string address as input, which can be a fully qualified domain name or a URL.
|
||||
// If the address does not include a scheme, "http://" is added by default.
|
||||
// It returns the top-level domain as a string or an error if the address is invalid.
|
||||
// The function also checks if the domain is an IP address and returns "ip" in that case.
|
||||
// If there is no top-level domain found, it returns the entire domain as the TLD.
|
||||
// The function is useful for determining the type of domain (I2P, onion, or regular) for routing purposes.
|
||||
// It uses the net package to parse the address and extract the hostname.
|
||||
func GetTLD(addr string) (string, error) {
|
||||
// Add a default scheme if missing
|
||||
if !strings.Contains(addr, "://") {
|
||||
addr = "http://" + addr
|
||||
}
|
||||
url, err := url.Parse(addr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
domain := url.Hostname()
|
||||
if domain == "" {
|
||||
return "", net.InvalidAddrError("invalid address: no hostname found")
|
||||
}
|
||||
// Check if the domain is an IP address
|
||||
if net.ParseIP(domain) != nil {
|
||||
return "ip", nil
|
||||
}
|
||||
lastDot := strings.LastIndex(domain, ".")
|
||||
if lastDot == -1 || lastDot == len(domain)-1 {
|
||||
return domain, nil
|
||||
}
|
||||
tld := domain[lastDot+1:]
|
||||
return tld, nil
|
||||
}
|
Reference in New Issue
Block a user