1// SPDX-License-Identifier: ISC 2// Copyright (c) 2014-2020 Bitmark Inc. 3// Use of this source code is governed by an ISC 4// license that can be found in the LICENSE file. 5 6package util 7 8import ( 9 "net" 10 "strconv" 11 "strings" 12 13 "github.com/bitmark-inc/bitmarkd/fault" 14 "github.com/bitmark-inc/logger" 15) 16 17// Connection - type to hold an IP and Port 18type Connection struct { 19 ip net.IP 20 port uint16 21} 22 23// NewConnection - create a connection from an Host:Port string 24func NewConnection(hostPort string) (*Connection, error) { 25 host, port, err := net.SplitHostPort(hostPort) 26 if nil != err { 27 return nil, err 28 } 29 30 host = strings.TrimSpace(host) 31 32 IP := net.ParseIP(host) 33 if "*" == host && nil == IP { 34 IP = net.ParseIP("::") 35 } 36 if nil == IP { 37 ips, err := net.LookupIP(host) 38 if nil != err { 39 return nil, err 40 } 41 if len(ips) < 1 { 42 return nil, fault.InvalidIpAddress 43 } 44 IP = ips[0] 45 } 46 47 numericPort, err := strconv.Atoi(strings.TrimSpace(port)) 48 if nil != err { 49 return nil, err 50 } 51 if numericPort < 1 || numericPort > 65535 { 52 return nil, fault.InvalidPortNumber 53 } 54 c := &Connection{ 55 ip: IP, 56 port: uint16(numericPort), 57 } 58 return c, nil 59} 60 61// NewConnections - convert an array of connections 62func NewConnections(hostPort []string) ([]*Connection, error) { 63 if 0 == len(hostPort) { 64 return nil, fault.InvalidLength 65 } 66 c := make([]*Connection, len(hostPort)) 67 for i, hp := range hostPort { 68 var err error 69 c[i], err = NewConnection(hp) 70 if nil != err { 71 return nil, err 72 } 73 } 74 return c, nil 75} 76 77// ConnectionFromIPandPort - convert an IP and port to a connection 78func ConnectionFromIPandPort(ip net.IP, port uint16) *Connection { 79 return &Connection{ 80 ip: ip, 81 port: port, 82 } 83} 84 85// CanonicalIPandPort - make the IP:Port into canonical string 86// 87// examples: 88// IPv4: 127.0.0.1:1234 89// IPv6: [::1]:1234 90// 91// prefix is optional and can be empty ("") 92// returns prefixed string and IPv6 flag 93func (conn *Connection) CanonicalIPandPort(prefix string) (string, bool) { 94 95 port := int(conn.port) 96 if nil != conn.ip.To4() { 97 return prefix + conn.ip.String() + ":" + strconv.Itoa(port), false 98 } 99 return prefix + "[" + conn.ip.String() + "]:" + strconv.Itoa(port), true 100} 101 102// basic string conversion 103func (conn Connection) String() string { 104 s, _ := conn.CanonicalIPandPort("") 105 return s 106} 107 108// MarshalText - convert to text for JSON 109func (conn Connection) MarshalText() ([]byte, error) { 110 s, _ := conn.CanonicalIPandPort("") 111 return []byte(s), nil 112} 113 114// PackedConnection - type for packed byte buffer IP and Port 115type PackedConnection []byte 116 117// Pack - pack an IP and Port into a byte buffer 118func (conn *Connection) Pack() PackedConnection { 119 b := []byte(conn.ip) 120 length := len(b) 121 if 4 != length && 16 != length { 122 logger.Panicf("connection.Pack: invalid IP length: %d", length) 123 } 124 size := length + 3 // count++port.high++port.low++ip 125 b2 := make([]byte, size) 126 b2[0] = byte(size) // 7 or 19 127 b2[1] = byte(conn.port >> 8) // port high byte 128 b2[2] = byte(conn.port) // port low byte 129 copy(b2[3:], b) // 4 byte IPv4 or 16 byte IPv6 130 return b2 131} 132 133// Unpack - unpack a byte buffer into an IP and Port 134// returns nil if unpack fails 135// if successful returns connection and number of bytes used 136// so an array can be unpacked more easily 137func (packed PackedConnection) Unpack() (*Connection, int) { 138 if nil == packed { 139 return nil, 0 140 } 141 count := len(packed) 142 if count < 7 { 143 return nil, 0 144 } 145 n := packed[0] 146 if 7 != n && 19 != n { // only valid values 147 return nil, 0 148 } 149 150 ip := make([]byte, n-3) // 4 or 16 bytes 151 copy(ip, packed[3:n]) 152 c := &Connection{ 153 ip: ip, 154 port: uint16(packed[1])<<8 + uint16(packed[2]), 155 } 156 return c, int(n) 157} 158 159// Unpack46 - unpack first IPv4 and first IPv6 plus Port 160func (packed PackedConnection) Unpack46() (*Connection, *Connection) { 161 162 // only expect two 163 ipv4Connection := (*Connection)(nil) 164 ipv6Connection := (*Connection)(nil) 165 166 for { 167 conn, n := packed.Unpack() 168 packed = packed[n:] 169 170 if nil == conn { 171 return ipv4Connection, ipv6Connection 172 } 173 174 if nil != conn.ip.To4() { 175 if nil == ipv4Connection { 176 ipv4Connection = conn 177 } 178 } else if nil == ipv6Connection { 179 ipv6Connection = conn 180 } 181 182 // if both kinds found 183 if nil != ipv4Connection && nil != ipv6Connection { 184 return ipv4Connection, ipv6Connection 185 } 186 } 187} 188