100 lines
3.1 KiB
Go
100 lines
3.1 KiB
Go
package oracle
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"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
|
|
}
|
|
for i := range privateNets {
|
|
if privateNets[i].Contains(ip) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
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: %s", 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
|
|
}
|