本文已参与「新人创作礼」活动,一起开启掘金创作之路
引言
接上篇文章,如何实现查询IP属地信息?本文将接着讲解关于IP的一些操作,例如如何获取内网IP、公网IP?IP地址和数字的转换,判断是否是有效IP地址等等。
先来看一道面试题:
力扣 468. 验证IP地址
最直接的方法是使用内置函数和 try/catch 结构检查 IP 地址的正确性:在 Python 中使用 ipaddress ,在 Java 中使用 InetAddress ,在Golang中使用net.ParseIP。
Java代码:
import java.net.*;
class Solution {
public String validIPAddress(String IP) {
try {
return (InetAddress.getByName(IP) instanceof Inet6Address) ? "IPv6": "IPv4";
} catch(Exception e) {}
return "Neither";
}
}
Python代码:
from ipaddress import ip_address, IPv6Address
class Solution:
def validIPAddress(self, IP: str) -> str:
try:
return "IPv6" if type(ip_address(IP)) is IPv6Address else "IPv4"
except ValueError:
return "Neither"
但是在Golang中,To4()判断是允许前导零的,所以需要再判断一下:
Go代码:
func validIPAddress(queryIP string) string {
ip := net.ParseIP(queryIP)
if ip == nil {
return "Neither"
}
if ip.To4() != nil {
for _, s := range strings.Split(queryIP, ".") {
if len(s) > 1 && s[0] == '0' {
return "Neither"
}
}
return "IPv4"
}
for _, s := range strings.Split(queryIP, ":") {
if len(s) > 4 || s == "" {
return "Neither"
}
}
return "IPv6"
}
如果面试题的重点不是对于IP的处理,那么可以使用内置函数实现。但是如果这道题就是想考你对于字符串的操作,很显然是不能使用内置库函数的。
下面是直接翻译原题要求的解法:
- 对于 IPv4 地址,通过界定符 . 将地址分为四块;对于 IPv6 地址,通过界定符 : 将地址分为八块。
- 对于 IPv4 地址的每一块,检查它们是否在 0 - 255 内,且没有前置零。
- 对于 IPv6 地址的每一块,检查其长度是否为 1 - 4 位的十六进制数。
其中IPv6使用正则表达式进行判断:
import (
"regexp"
"strconv"
"strings"
)
func solve( IP string ) string {
if validIPv4(IP) {
return "IPv4"
}else if validIPv6(IP) {
return "IPv6"
}else {
return "Neither"
}
}
func validIPv4(IP string) bool {
strs := strings.Split(IP,".")
if len(strs)!=4 {
return false
}
for _, str := range strs {
if len(str)>3 {
return false
}
if len(str)>1 && str[0]=='0' {
return false
}
val,err := strconv.Atoi(str)
if err!= nil || val<0 || val > 255 {
return false
}
}
return true
}
func validIPv6(IP string) bool {
strs := strings.Split(IP,":")
if len(strs) != 8 {
return false
}
for _, str := range strs {
if len(str)>4 || len(str)==0 {
return false
}
re := regexp.MustCompile(`^([0-9]|[a-f]|[A-F])+$`)
if !re.MatchString(str) {
return false
}
}
return true
}
讲完了面试题,我们来总结一下常见关于IP的操作:
int和net.IP的转换
//将 int 转换成 netIP ([]byte)
func intToNetIP(ipnr int) net.IP {
var bytes [4]byte
bytes[0] = byte(ipnr >> 24)
bytes[1] = byte(ipnr >> 16)
bytes[2] = byte(ipnr >> 8)
bytes[3] = byte(ipnr)
return net.IPv4(bytes[0], bytes[1], bytes[2], bytes[3])
}
//将 netIP ([]byte) 转换成 uint32
func netIPToInt(ipnr net.IP) int {
bits := strings.Split(ipnr.String(), ".")
b0, _ := strconv.Atoi(bits[0])
b1, _ := strconv.Atoi(bits[1])
b2, _ := strconv.Atoi(bits[2])
b3, _ := strconv.Atoi(bits[3])
var sum int
sum |= int(b0) << 24
sum |= int(b1) << 16
sum |= int(b2) << 8
sum |= int(b3)
return sum
}
int和IP(string)的转换
//ipv4地址转int
func ipv4ToInt(s string) int {
arr := strings.Split(s,".")
res := 0
for i, str := range arr {
val,_ := strconv.Atoi(str)
val = val<<(24-8*i)
res |= val
}
return res
}
//int转IPv4 string
func intToIpv4(ipInt int) string {
return fmt.Sprintf("%d.%d.%d.%d",
byte(ipInt>>24), byte(ipInt>>16), byte(ipInt>>8), byte(ipInt))
}
判断ip地址是否处于某个地址区间
// IpBetween 判断ip地址区间
func IpBetween(from net.IP, to net.IP, test net.IP) bool {
if from == nil || to == nil || test == nil {
fmt.Println("An ip input is nil")
return false
}
from16 := from.To16()
to16 := to.To16()
test16 := test.To16()
if from16 == nil || to16 == nil || test16 == nil {
fmt.Println("An ip did not convert to a 16 byte")
return false
}
//如果 from16<=test16<=to16 返回true
if bytes.Compare(test16, from16) >= 0 && bytes.Compare(test16, to16) <= 0 {
return true
}
return false
}
通过dns服务器8.8.8.8:80获取本地IP
// GetLocalIP 通过dns服务器8.8.8.8:80获取本地使用的ip
func GetLocalIP() string {
// Dial() 在网络network上连接地址address,并返回一个Conn接口。
conn, _ := net.Dial("udp", "8.8.8.8:80")
defer conn.Close()
localAddr := conn.LocalAddr().String()
idx := strings.LastIndex(localAddr, ":")
return localAddr[0:idx]
}
获取本机的公网IP
// GetMyPublicIP 通过访问http://myexternalip.com/raw获取公网ip
func GetMyPublicIP() string {
resp, err := http.Get("http://myexternalip.com/raw")
if err != nil {
return ""
}
defer resp.Body.Close()
content, _ := ioutil.ReadAll(resp.Body)
return string(content)
}
获取本地IP,返回网络接口地址列表
// GetIntranetIp 获取本地IP,返回网络接口地址
func GetIntranetIp() {
//InterfaceAddrs返回该系统的网络接口的地址列表。
addrs, err := net.InterfaceAddrs()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
for _, address := range addrs {
// 检查ip地址判断是否回环地址
// IsLoopback() 如果ip是环回地址,则返回真。
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
fmt.Println("ip:", ipnet.IP.String())
}
}
}
}
判断是否是公网ip
// IsPublicIP 判断是否是公网ip
func IsPublicIP(IP net.IP) bool {
//如果ip是环回地址、链路本地组播地址、链路本地单播地址 返回false
if IP.IsLoopback() || IP.IsLinkLocalMulticast() || IP.IsLinkLocalUnicast() {
return false
}
//如果是内网ip返回false
//tcp/ip协议中,专门保留了三个IP地址区域作为私有地址,其地址范围如下:
//10.0.0.0/8: 10.0.0.0 ~ 10.255.255.255
//172.16.0.0/12: 172.16.0.0 ~ 172.31.255.255
//192.168.0.0/16: 192.168.0.0 ~ 192.168.255.255
if ip4 := IP.To4(); ip4 != nil {
switch true {
case ip4[0] == 10:
return false
case ip4[0] == 172 && ip4[1] >= 16 && ip4[1] <= 31:
return false
case ip4[0] == 192 && ip4[1] == 168:
return false
default:
return true
}
}
return false //ip4 == nil
}
golang提供的标准库:net包
推荐一个GO中文文档:studygolang.com/pkgdoc
type IP
type IP []byte
一个IP是一个单一的IP地址,是一个字节的片断。本包中的函数接受4字节(IPv4)或16字节(IPv6)的片断作为输入。
请注意,在本文档中,将一个IP地址称为IPv4地址或IPv6地址是该地址的语义属性,而不仅仅是字节片的长度:一个16字节的片子仍然可以是一个IPv4地址。
func IPv4
func IPv4(a, b, c, d byte) IP
IPv4返回IPv4地址a.b.c.d的IP地址(以16字节的形式)。
func ParseIP
func ParseIP(s string) IP
ParseIP 将 s 解析为一个 IP 地址,并返回结果。字符串s可以是点阵十进制("192.0.2.1")或IPv6("2001:db8::68")形式。如果s不是一个有效的IP地址的文本表示,ParseIP返回nil。
func (IP) [To16]
func (ip IP) To16() IP
To16将一个IP地址转换为16字节表示。如果ip不是一个IP地址(长度错误),To16会返回nil。
func (IP) [To4]
func (ip IP) To4() IP
To4将一个IPv4地址转换为4字节表示。如果ip不是IPv4地址,To4会返回nil。
func InterfaceAddrs
func InterfaceAddrs() ([]Addr, error)
InterfaceAddrs返回一个系统的单播接口地址列表。
返回的列表并不识别相关的接口;使用Interfaces和Interface.Addrs获取更多细节。
type IPNet
type IPNet struct {
IP IP // network number
Mask IPMask // network mask
}
一个IPNet代表一个IP网络。