neo-go/pkg/services/oracle/network.go
Roman Khimov a30792dbb6 *: use slices.Index/slices.Contains where appropriate
This doesn't touch performance-sensitive parts, but simplifies a lot of code.

Signed-off-by: Roman Khimov <roman@nspcc.ru>
2024-08-27 08:24:52 +03:00

98 lines
3.1 KiB
Go

package oracle
import (
"fmt"
"net"
"net/http"
"slices"
"syscall"
"github.com/nspcc-dev/neo-go/pkg/config"
)
// reservedCIDRs is a list of ip addresses for private networks.
// https://tools.ietf.org/html/rfc6890
var reservedCIDRs = []string{
// IPv4
"10.0.0.0/8",
"100.64.0.0/10",
"172.16.0.0/12",
"192.0.0.0/24",
"192.168.0.0/16",
"198.18.0.0/15",
// IPv6
"fc00::/7",
}
var privateNets = make([]net.IPNet, 0, len(reservedCIDRs))
func init() {
for i := range reservedCIDRs {
_, ipNet, err := net.ParseCIDR(reservedCIDRs[i])
if err != nil {
panic(err)
}
privateNets = append(privateNets, *ipNet)
}
}
func isReserved(ip net.IP) bool {
if !ip.IsGlobalUnicast() {
return true
}
return slices.ContainsFunc(privateNets, func(pn net.IPNet) bool {
return pn.Contains(ip)
})
}
func getDefaultClient(cfg config.OracleConfiguration) *http.Client {
d := &net.Dialer{}
if !cfg.AllowPrivateHost {
// Control is used after request URI is resolved and network connection (network
// file descriptor) is created, but right before listening/dialing
// is started.
// `address` represents a resolved IP address in the format of ip:port. `address`
// is presented in its final (resolved) form that was used directly for network
// connection establishing.
// Control is called for each item in the set of IP addresses got from request
// URI resolving. The first network connection with address that passes Control
// function will be used for further request processing. Network connection
// with address that failed Control will be ignored. If all the connections
// fail Control, the most relevant error (the one from the first address)
// will be returned after `Client.Do`.
d.Control = func(network, address string, c syscall.RawConn) error {
host, _, err := net.SplitHostPort(address)
if err != nil {
return fmt.Errorf("%w: failed to split address %s: %w", ErrRestrictedRedirect, address, err)
}
ip := net.ParseIP(host)
if ip == nil {
return fmt.Errorf("%w: failed to parse IP address %s", ErrRestrictedRedirect, address)
}
if isReserved(ip) {
return fmt.Errorf("%w: IP is not global unicast", ErrRestrictedRedirect)
}
return nil
}
}
var client http.Client
client.Transport = &http.Transport{
DisableKeepAlives: true,
// Do not set DialTLSContext, so that DialContext will be used to establish the
// connection. After that, TLS connection will be added to a persistent connection
// by standard library code and handshaking will be performed.
DialContext: d.DialContext,
}
client.Timeout = cfg.RequestTimeout
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
if len(via) > maxRedirections { // from https://github.com/neo-project/neo-modules/pull/698
return fmt.Errorf("%w: %d redirections are reached", ErrRestrictedRedirect, maxRedirections)
}
if len(via) > 0 && via[0].URL.Scheme == "https" && req.URL.Scheme != "https" {
lastHop := via[len(via)-1].URL
return fmt.Errorf("%w: redirected from secure URL %s to insecure URL %s", ErrRestrictedRedirect, lastHop, req.URL)
}
return nil
}
return &client
}