From 2f3eea65a29c0fe15e70556160eb15ae7d2c8e23 Mon Sep 17 00:00:00 2001 From: WeebDataHoarder <57538841+WeebDataHoarder@users.noreply.github.com> Date: Sun, 23 Apr 2023 19:34:52 +0200 Subject: [PATCH] Obtain specific outgoing IPv6 address for peering lists --- cmd/p2pool/p2pool.go | 21 +-- p2pool/p2p/client.go | 2 +- p2pool/p2p/server.go | 51 ++++++- utils/network.go | 86 +++++++++++ utils/network_extra.go | 24 ++++ utils/network_extra_linux.go | 269 +++++++++++++++++++++++++++++++++++ 6 files changed, 432 insertions(+), 21 deletions(-) create mode 100644 utils/network.go create mode 100644 utils/network_extra.go create mode 100644 utils/network_extra_linux.go diff --git a/cmd/p2pool/p2pool.go b/cmd/p2pool/p2pool.go index 882a412..7c977a2 100644 --- a/cmd/p2pool/p2pool.go +++ b/cmd/p2pool/p2pool.go @@ -9,6 +9,7 @@ import ( p2poolinstance "git.gammaspectra.live/P2Pool/p2pool-observer/p2pool" "git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain" "git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/types" + "git.gammaspectra.live/P2Pool/p2pool-observer/utils" "log" "net" "net/http" @@ -235,24 +236,10 @@ func main() { } if *addSelf { - if ips, err := net.InterfaceAddrs(); err != nil { - log.Printf("[P2Pool] Could not get interface addresses: %s", err) + if addrs, err := utils.GetOutboundIPv6(); err != nil { + log.Printf("[P2Pool] Could not get interface ipv6 addresses: %s", err) } else { - var cgnatStart = netip.MustParseAddr("100.64.0.0") - var cgnatEnd = netip.MustParseAddr("100.127.255.255") - for _, ip := range ips { - nets := strings.Split(ip.String(), "/") - addr, err := netip.ParseAddr(nets[0]) - if err != nil { - continue - } - - if !addr.IsGlobalUnicast() || addr.IsPrivate() || addr.Is4In6() || (addr.Compare(cgnatStart) >= 0 && addr.Compare(cgnatEnd) <= 0) { - //skip - continue - } - //should have only public ips at this point - log.Printf("[P2Pool] Found interface address %s", addr.String()) + for _, addr := range addrs { if addr.Is6() { //use own port directly? instance.Server().AddToPeerList(netip.AddrPortFrom(addr, instance.Server().ListenPort())) diff --git a/p2pool/p2p/client.go b/p2pool/p2p/client.go index aebc1b7..79b1fe2 100644 --- a/p2pool/p2p/client.go +++ b/p2pool/p2p/client.go @@ -380,7 +380,7 @@ func (c *Client) OnConnection() { return false, nil }(); ok { c.HandshakeComplete.Store(true) - c.SetError(errors.New("already connected")) + c.SetError(fmt.Errorf("already connected as %s (%d)", otherClient.AddressPort, otherClient.PeerId.Load())) //same peer log.Printf("[P2PClient] Connected to other same peer: %s (%d) is also %s (%d)", c.AddressPort, c.PeerId.Load(), otherClient.AddressPort, otherClient.PeerId.Load()) c.Close() diff --git a/p2pool/p2p/server.go b/p2pool/p2p/server.go index 6d0b10b..8b9c062 100644 --- a/p2pool/p2p/server.go +++ b/p2pool/p2p/server.go @@ -72,6 +72,9 @@ type Server struct { useIPv4 bool useIPv6 bool + ipv6AddrsLock sync.RWMutex + ipv6OutgoingAddresses []netip.Addr + close atomic.Bool listener *net.TCPListener @@ -133,10 +136,28 @@ func NewServer(p2pool P2PoolInterface, listenAddress string, externalListenPort } s.PendingOutgoingConnections = utils.NewCircularBuffer[string](int(s.MaxOutgoingPeers)) + s.RefreshOutgoingIPv6() return s, nil } +func (s *Server) RefreshOutgoingIPv6() { + log.Printf("[P2PServer] Refreshing outgoing IPv6") + addrs, _ := utils.GetOutboundIPv6() + s.ipv6AddrsLock.Lock() + defer s.ipv6AddrsLock.Unlock() + s.ipv6OutgoingAddresses = addrs + for _, a := range addrs { + log.Printf("[P2PServer] Outgoing IPv6: %s", a.String()) + } +} + +func (s *Server) GetOutgoingIPv6() []netip.Addr { + s.ipv6AddrsLock.RLock() + defer s.ipv6AddrsLock.RUnlock() + return s.ipv6OutgoingAddresses +} + func (s *Server) ListenPort() uint16 { return s.listenAddress.Port() } @@ -470,6 +491,13 @@ func (s *Server) Listen() (err error) { s.DownloadMissingBlocks() } }() + wg.Add(1) + go func() { + defer wg.Done() + for range time.Tick(time.Hour) { + s.RefreshOutgoingIPv6() + } + }() for !s.close.Load() { if conn, err := s.listener.AcceptTCP(); err != nil { return err @@ -555,11 +583,28 @@ func (s *Server) DirectConnect(addrPort netip.AddrPort) (*Client, error) { return nil, errors.New("peer is already attempting connection") } - log.Printf("[P2PServer] Outgoing connection to %s", addrPort.String()) - s.NumOutgoingConnections.Add(1) - if conn, err := (&net.Dialer{Timeout: time.Second * 5}).DialContext(s.ctx, "tcp", addrPort.String()); err != nil { + var localAddr net.Addr + + //select IPv6 outgoing address + if addr.Is6() { + addrs := s.GetOutgoingIPv6() + if len(addrs) > 1 { + a := addrs[unsafeRandom.Intn(len(addrs))] + localAddr = &net.TCPAddr{IP: a.AsSlice(), Zone: a.Zone()} + } else if len(addrs) == 1 { + localAddr = &net.TCPAddr{IP: addrs[0].AsSlice(), Zone: addrs[0].Zone()} + } + } + + if localAddr != nil { + log.Printf("[P2PServer] Outgoing connection to %s using %s", addrPort.String(), localAddr.String()) + } else { + log.Printf("[P2PServer] Outgoing connection to %s", addrPort.String()) + } + + if conn, err := (&net.Dialer{Timeout: time.Second * 5, LocalAddr: localAddr}).DialContext(s.ctx, "tcp", addrPort.String()); err != nil { s.NumOutgoingConnections.Add(-1) s.PendingOutgoingConnections.Replace(addrPort.Addr().String(), "") if p := s.PeerList().Get(addrPort.Addr()); p != nil { diff --git a/utils/network.go b/utils/network.go new file mode 100644 index 0000000..09a8140 --- /dev/null +++ b/utils/network.go @@ -0,0 +1,86 @@ +package utils + +import ( + "net" + "net/netip" +) + +type ExtendedIPFlags uint + +type ExtendedIPNet struct { + IP net.IP // network number + Mask net.IPMask // network mask + Flags ExtendedIPFlags +} + +func (n *ExtendedIPNet) String() string { + if n == nil { + return "" + } + return (&net.IPNet{IP: n.IP, Mask: n.Mask}).String() +} + +const ( + FlagTemporary ExtendedIPFlags = 1 << iota + FlagPermanent + FlagNoPrefixRoute + FlagManageTempAddress + FlagStablePrivacy + FlagDeprecated +) + +func GetOutboundIPv6() ([]netip.Addr, error) { + ifaces, err := net.Interfaces() + if err != nil { + return nil, err + } + var addresses []netip.Addr + for _, i := range ifaces { + if (i.Flags&net.FlagRunning) > 0 && (i.Flags&net.FlagUp) > 0 && + (i.Flags&net.FlagPointToPoint) == 0 && + (i.Flags&net.FlagLoopback) == 0 { + addrs, err := InterfaceAddrs(&i) + if err != nil { + continue + } + + for _, a := range addrs { + if addr, ok := netip.AddrFromSlice(a.IP); ok && addr.Is6() && !addr.Is4In6() { + //Filter undesired addresses + if addr.IsUnspecified() || addr.IsLoopback() || addr.IsLinkLocalMulticast() || addr.IsLinkLocalUnicast() || addr.IsInterfaceLocalMulticast() { + continue + } + + //Filter generated privacy addresses directly + if onesCount, _ := a.Mask.Size(); onesCount == 128 { + continue + } + + //Filter + if (a.Flags & FlagNoPrefixRoute) > 0 { + continue + } + if (a.Flags & FlagDeprecated) > 0 { + continue + } + addresses = append(addresses, netip.MustParseAddr(a.IP.String())) + } + } + } + } + + return addresses, nil +} + +var cgnatStart = netip.MustParseAddr("100.64.0.0") +var cgnatEnd = netip.MustParseAddr("100.127.255.255") + +func NetIPIsCGNAT(addr netip.Addr) bool { + return addr.Is4() && addr.Compare(cgnatStart) >= 0 && addr.Compare(cgnatEnd) <= 0 +} + +func NetIPGetEUI48Information(addr netip.Addr) { + if !addr.Is6() || addr.Is4In6() { + return + } +} diff --git a/utils/network_extra.go b/utils/network_extra.go new file mode 100644 index 0000000..6bf2838 --- /dev/null +++ b/utils/network_extra.go @@ -0,0 +1,24 @@ +//go:build !linux + +package utils + +import "net" + +// InterfaceAddrs returns a list of unicast interface addresses for a specific +// interface. +func InterfaceAddrs(ifi *net.Interface) ([]*ExtendedIPNet, error) { + a, err := ifi.Addrs() + if err != nil { + return nil, err + } + var addrs []*ExtendedIPNet + for _, e := range a { + if ipNet, ok := e.(*net.IPNet); ok { + addrs = append(addrs, &ExtendedIPNet{ + IP: ipNet.IP, + Mask: ipNet.Mask, + }) + } + } + return addrs, nil +} diff --git a/utils/network_extra_linux.go b/utils/network_extra_linux.go new file mode 100644 index 0000000..808f68d --- /dev/null +++ b/utils/network_extra_linux.go @@ -0,0 +1,269 @@ +//go:build linux + +package utils + +import ( + "encoding/binary" + "errors" + "golang.org/x/sys/unix" + "net" + "os" + "syscall" + "unsafe" +) + +// InterfaceAddrs returns a list of unicast interface addresses for a specific +// interface. +func InterfaceAddrs(ifi *net.Interface) ([]*ExtendedIPNet, error) { + if ifi == nil { + return nil, &net.OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: errors.New("invalid network interface")} + } + ifat, err := interfaceAddrTable(ifi) + if err != nil { + err = &net.OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: err} + } + return ifat, err +} + +// If the ifi is nil, interfaceAddrTable returns addresses for all +// network interfaces. Otherwise it returns addresses for a specific +// interface. +func interfaceAddrTable(ifi *net.Interface) ([]*ExtendedIPNet, error) { + tab, err := syscall.NetlinkRIB(syscall.RTM_GETADDR, syscall.AF_UNSPEC) + if err != nil { + return nil, os.NewSyscallError("netlinkrib", err) + } + msgs, err := syscall.ParseNetlinkMessage(tab) + if err != nil { + return nil, os.NewSyscallError("parsenetlinkmessage", err) + } + var ift []net.Interface + if ifi == nil { + var err error + ift, err = interfaceTable(0) + if err != nil { + return nil, err + } + } + ifat, err := addrTable(ift, ifi, msgs) + if err != nil { + return nil, err + } + return ifat, nil +} + +// If the ifindex is zero, interfaceTable returns mappings of all +// network interfaces. Otherwise it returns a mapping of a specific +// interface. +func interfaceTable(ifindex int) ([]net.Interface, error) { + tab, err := syscall.NetlinkRIB(syscall.RTM_GETLINK, syscall.AF_UNSPEC) + if err != nil { + return nil, os.NewSyscallError("netlinkrib", err) + } + msgs, err := syscall.ParseNetlinkMessage(tab) + if err != nil { + return nil, os.NewSyscallError("parsenetlinkmessage", err) + } + var ift []net.Interface +loop: + for _, m := range msgs { + switch m.Header.Type { + case syscall.NLMSG_DONE: + break loop + case syscall.RTM_NEWLINK: + ifim := (*syscall.IfInfomsg)(unsafe.Pointer(&m.Data[0])) + if ifindex == 0 || ifindex == int(ifim.Index) { + attrs, err := syscall.ParseNetlinkRouteAttr(&m) + if err != nil { + return nil, os.NewSyscallError("parsenetlinkrouteattr", err) + } + ift = append(ift, *newLink(ifim, attrs)) + if ifindex == int(ifim.Index) { + break loop + } + } + } + } + return ift, nil +} + +func interfaceByIndex(ift []net.Interface, index int) (*net.Interface, error) { + for _, ifi := range ift { + if index == ifi.Index { + return &ifi, nil + } + } + return nil, errors.New("no such network interface") +} + +func addrTable(ift []net.Interface, ifi *net.Interface, msgs []syscall.NetlinkMessage) ([]*ExtendedIPNet, error) { + var ifat []*ExtendedIPNet +loop: + for _, m := range msgs { + switch m.Header.Type { + case syscall.NLMSG_DONE: + break loop + case syscall.RTM_NEWADDR: + ifam := (*syscall.IfAddrmsg)(unsafe.Pointer(&m.Data[0])) + if len(ift) != 0 || ifi.Index == int(ifam.Index) { + if len(ift) != 0 { + var err error + ifi, err = interfaceByIndex(ift, int(ifam.Index)) + if err != nil { + return nil, err + } + } + attrs, err := syscall.ParseNetlinkRouteAttr(&m) + if err != nil { + return nil, os.NewSyscallError("parsenetlinkrouteattr", err) + } + ifa := newAddr(ifam, attrs) + if ifa != nil { + ifat = append(ifat, ifa) + } + } + } + } + return ifat, nil +} + +// linux/if_addr.h + +const IFA_FLAGS = 0x8 + +const ( + IFA_F_SECONDARY = 0x01 + IFA_F_TEMPORARY = IFA_F_SECONDARY + + IFA_F_NODAD = 0x02 + IFA_F_OPTIMISTIC = 0x04 + IFA_F_DADFAILED = 0x08 + IFA_F_HOMEADDRESS = 0x10 + IFA_F_DEPRECATED = 0x20 + IFA_F_TENTATIVE = 0x40 + IFA_F_PERMANENT = 0x80 + IFA_F_MANAGETEMPADDR = 0x100 + IFA_F_NOPREFIXROUTE = 0x200 + IFA_F_MCAUTOJOIN = 0x400 +) + +// newAddr altered version +func newAddr(ifam *syscall.IfAddrmsg, attrs []syscall.NetlinkRouteAttr) *ExtendedIPNet { + var ipPointToPoint bool + // Seems like we need to make sure whether the IP interface + // stack consists of IP point-to-point numbered or unnumbered + // addressing. + var flags ExtendedIPFlags + for _, a := range attrs { + if a.Attr.Type == syscall.IFA_LOCAL { + ipPointToPoint = true + break + } else if a.Attr.Type == IFA_FLAGS && len(a.Value) == 4 { + f := binary.LittleEndian.Uint32(a.Value) + if (f & IFA_F_TEMPORARY) > 0 { + flags |= FlagTemporary + } + if (f & IFA_F_PERMANENT) > 0 { + flags |= FlagPermanent + } + if (f & IFA_F_MANAGETEMPADDR) > 0 { + flags |= FlagManageTempAddress + } + if (f & IFA_F_NOPREFIXROUTE) > 0 { + flags |= FlagNoPrefixRoute + } + if (f & unix.IFA_F_STABLE_PRIVACY) > 0 { + flags |= FlagStablePrivacy + } + if (f & unix.IFA_F_DEPRECATED) > 0 { + flags |= FlagDeprecated + } + } + } + for _, a := range attrs { + if ipPointToPoint && a.Attr.Type == syscall.IFA_ADDRESS { + continue + } + switch ifam.Family { + case syscall.AF_INET: + return &ExtendedIPNet{IP: net.IPv4(a.Value[0], a.Value[1], a.Value[2], a.Value[3]), Mask: net.CIDRMask(int(ifam.Prefixlen), 8*net.IPv4len), Flags: flags} + case syscall.AF_INET6: + ifa := &ExtendedIPNet{IP: make(net.IP, net.IPv6len), Mask: net.CIDRMask(int(ifam.Prefixlen), 8*net.IPv6len), Flags: flags} + copy(ifa.IP, a.Value[:]) + return ifa + } + } + return nil +} + +const ( + // See linux/if_arp.h. + // Note that Linux doesn't support IPv4 over IPv6 tunneling. + sysARPHardwareIPv4IPv4 = 768 // IPv4 over IPv4 tunneling + sysARPHardwareIPv6IPv6 = 769 // IPv6 over IPv6 tunneling + sysARPHardwareIPv6IPv4 = 776 // IPv6 over IPv4 tunneling + sysARPHardwareGREIPv4 = 778 // any over GRE over IPv4 tunneling + sysARPHardwareGREIPv6 = 823 // any over GRE over IPv6 tunneling +) + +func linkFlags(rawFlags uint32) net.Flags { + var f net.Flags + if rawFlags&syscall.IFF_UP != 0 { + f |= net.FlagUp + } + if rawFlags&syscall.IFF_RUNNING != 0 { + f |= net.FlagRunning + } + if rawFlags&syscall.IFF_BROADCAST != 0 { + f |= net.FlagBroadcast + } + if rawFlags&syscall.IFF_LOOPBACK != 0 { + f |= net.FlagLoopback + } + if rawFlags&syscall.IFF_POINTOPOINT != 0 { + f |= net.FlagPointToPoint + } + if rawFlags&syscall.IFF_MULTICAST != 0 { + f |= net.FlagMulticast + } + return f +} + +func newLink(ifim *syscall.IfInfomsg, attrs []syscall.NetlinkRouteAttr) *net.Interface { + ifi := &net.Interface{Index: int(ifim.Index), Flags: linkFlags(ifim.Flags)} + for _, a := range attrs { + switch a.Attr.Type { + case syscall.IFLA_ADDRESS: + // We never return any /32 or /128 IP address + // prefix on any IP tunnel interface as the + // hardware address. + switch len(a.Value) { + case net.IPv4len: + switch ifim.Type { + case sysARPHardwareIPv4IPv4, sysARPHardwareGREIPv4, sysARPHardwareIPv6IPv4: + continue + } + case net.IPv6len: + switch ifim.Type { + case sysARPHardwareIPv6IPv6, sysARPHardwareGREIPv6: + continue + } + } + var nonzero bool + for _, b := range a.Value { + if b != 0 { + nonzero = true + break + } + } + if nonzero { + ifi.HardwareAddr = a.Value[:] + } + case syscall.IFLA_IFNAME: + ifi.Name = string(a.Value[:len(a.Value)-1]) + case syscall.IFLA_MTU: + ifi.MTU = int(*(*uint32)(unsafe.Pointer(&a.Value[:4][0]))) + } + } + return ifi +}