ip.go
// Copyright 2016-2017 Authors of Cilium
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package addressing
import (
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
"net"
"github.com/cilium/cilium/common/ipam"
)
type CiliumIP interface {
NodeID() uint32
State() uint16
EndpointID() uint16
IPNet(ones int) *net.IPNet
EndpointPrefix() *net.IPNet
IP() net.IP
String() string
StringNoZeroComp() string
IsIPv6() bool
}
type CiliumIPv6 []byte
// NewCiliumIPv6 returns a IPv6 if the given `address` is:
// - An IPv6 address.
// - Node ID, bits from 112 to 120, must be different than 0
// - Endpoint ID, bits from 120 to 128, must be equal to 0
func NewCiliumIPv6(address string) (CiliumIPv6, error) {
ip, _, err := net.ParseCIDR(address)
if err != nil {
ip = net.ParseIP(address)
if ip == nil {
return nil, fmt.Errorf("Invalid IPv6 address: %s", address)
}
}
// As result of ParseIP, ip is either a valid IPv6 or IPv4 address. net.IP
// represents both versions on 16 bytes, so a more reliable way to tell
// IPv4 and IPv6 apart is to see if it fits 4 bytes
ip4 := ip.To4()
if ip4 != nil {
return nil, fmt.Errorf("Not an IPv6 address: %s", address)
}
return DeriveCiliumIPv6(ip.To16()), nil
}
func DeriveCiliumIPv6(src net.IP) CiliumIPv6 {
ip := make(CiliumIPv6, 16)
copy(ip, src.To16())
return ip
}
func (ip CiliumIPv6) IsIPv6() bool {
return true
}
// NodeID returns the node ID portion of the address or 0.
func (ip CiliumIPv6) NodeID() uint32 {
return binary.BigEndian.Uint32(ip[8:12])
}
func (ip CiliumIPv6) State() uint16 {
return binary.BigEndian.Uint16(ip[12:14])
}
func (ip CiliumIPv6) SetState(state uint16) {
binary.BigEndian.PutUint16(ip[12:14], state)
}
// EndpointID returns the container ID portion of the address or 0.
func (ip CiliumIPv6) EndpointID() uint16 {
return binary.BigEndian.Uint16(ip[14:])
}
// ValidContainerIP returns true if IP is a valid IP for a container.
// To be valid must obey to the following rules:
// - Node ID, bits from 64 to 96, must be different than 0
// - State, bits from 96 to 112, must be 0
// - Endpoint ID, bits from 112 to 128, must be different than 0
func (ip CiliumIPv6) ValidContainerIP() bool {
return ip.NodeID() != 0 && ip.State() == 0 && ip.EndpointID() != 0
}
// ValidNodeIP returns true if IP is a valid IP of a node.
// - Node ID, bits from 64 to 96, must be different than 0
// - State, bits from 96 to 112, must be 0
// - Endpoint ID, bits from 112 to 128, must be 0
func (ip *CiliumIPv6) ValidNodeIP() bool {
return ip.NodeID() != 0 && ip.State() == 0 && ip.EndpointID() == 0
}
// NodeIP returns the node's IP based on an endpoint IP of the local node.
func (ip CiliumIPv6) NodeIP() net.IP {
nodeAddr := make(net.IP, len(ip))
copy(nodeAddr, ip)
nodeAddr[14] = 0
nodeAddr[15] = 0
return nodeAddr
}
// HostIP returns the host address from the node ID.
func (ip CiliumIPv6) HostIP() net.IP {
nodeAddr := make(net.IP, len(ip))
copy(nodeAddr, ip)
nodeAddr[14] = 0xff
nodeAddr[15] = 0xff
return nodeAddr
}
func (ip CiliumIPv6) IPNet(ones int) *net.IPNet {
return &net.IPNet{
IP: ip.IP(),
Mask: net.CIDRMask(ones, 128),
}
}
func (ip CiliumIPv6) EndpointPrefix() *net.IPNet {
return ip.IPNet(128)
}
func (ip CiliumIPv6) IP() net.IP {
return net.IP(ip)
}
func (ip CiliumIPv6) IPAMReq() ipam.IPAMReq {
i := ip.IP()
return ipam.IPAMReq{IP: &i}
}
func (ip CiliumIPv6) String() string {
if ip == nil {
return ""
}
return net.IP(ip).String()
}
// StringNoZeroComp is similar to String but without generating
// zero compression in the address dump.
func (ip CiliumIPv6) StringNoZeroComp() string {
const maxLen = len("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
out := make([]byte, 0, maxLen)
raw := ip
if ip == nil {
return ""
}
if len(ip) == 0 {
return "<nil>"
}
for i := 0; i < 16; i += 2 {
if i > 0 {
out = append(out, ':')
}
src := []byte{raw[i], raw[i+1]}
tmp := make([]byte, hex.EncodedLen(len(src)))
hex.Encode(tmp, src)
if tmp[0] == tmp[1] && tmp[2] == tmp[3] &&
tmp[0] == tmp[2] && tmp[0] == '0' {
out = append(out, tmp[0])
} else {
out = append(out, tmp[0], tmp[1], tmp[2], tmp[3])
}
}
return string(out)
}
func (ip CiliumIPv6) MarshalJSON() ([]byte, error) {
return json.Marshal(net.IP(ip))
}
func (ip *CiliumIPv6) UnmarshalJSON(b []byte) error {
if len(b) < len(`""`) {
return fmt.Errorf("Invalid CiliumIPv6 '%s'", string(b))
}
str := string(b[1 : len(b)-1])
if str == "" {
return nil
}
c, err := NewCiliumIPv6(str)
if err != nil {
return fmt.Errorf("Invalid CiliumIPv6 '%s': %s", str, err)
}
*ip = c
return nil
}
type CiliumIPv4 []byte
func NewCiliumIPv4(address string) (CiliumIPv4, error) {
ip, _, err := net.ParseCIDR(address)
if err != nil {
ip = net.ParseIP(address)
if ip == nil {
return nil, fmt.Errorf("Invalid IPv4 address: %s", address)
}
}
ip4 := ip.To4()
if ip4 == nil {
return nil, fmt.Errorf("Not an IPv4 address")
}
return DeriveCiliumIPv4(ip4), nil
}
func DeriveCiliumIPv4(src net.IP) CiliumIPv4 {
ip := make(CiliumIPv4, 4)
copy(ip, src.To4())
return ip
}
func (ip CiliumIPv4) IsIPv6() bool {
return false
}
func (ip CiliumIPv4) NodeID() uint32 {
data := make([]byte, 4)
copy(data, ip[0:2])
return binary.BigEndian.Uint32(data)
}
func (ip CiliumIPv4) EndpointID() uint16 {
return binary.BigEndian.Uint16(ip[2:])
}
func (ip CiliumIPv4) IPNet(ones int) *net.IPNet {
return &net.IPNet{
IP: net.IP(ip),
Mask: net.CIDRMask(ones, 32),
}
}
func (ip CiliumIPv4) EndpointPrefix() *net.IPNet {
return ip.IPNet(32)
}
func (ip CiliumIPv4) IP() net.IP {
return net.IP(ip)
}
func (ip CiliumIPv4) State() uint16 {
// IPv4 addresses can't carry state
return 0
}
func (ip CiliumIPv4) IPAMReq() ipam.IPAMReq {
i := ip.IP()
return ipam.IPAMReq{IP: &i}
}
func (ip CiliumIPv4) String() string {
if ip == nil {
return ""
}
return net.IP(ip).String()
}
// StringNoZeroComp is same as String
func (ip CiliumIPv4) StringNoZeroComp() string {
return ip.String()
}
func (ip CiliumIPv4) MarshalJSON() ([]byte, error) {
return json.Marshal(net.IP(ip))
}
func (ip *CiliumIPv4) UnmarshalJSON(b []byte) error {
if len(b) < len(`""`) {
return fmt.Errorf("Invalid CiliumIPv4 '%s'", string(b))
}
str := string(b[1 : len(b)-1])
if str == "" {
return nil
}
c, err := NewCiliumIPv4(str)
if err != nil {
return fmt.Errorf("Invalid CiliumIPv4 '%s': %s", str, err)
}
*ip = c
return nil
}
// ValidContainerIP returns true if the IPv4 address is a valid IP for a container.
// To be valid must obey to the following rules:
// - Node ID, bits from 0 to 16, must be different than 0
// - Endpoint ID, bits from 16 to 32, must be different than 0
func (ip CiliumIPv4) ValidContainerIP() bool {
return ip.NodeID() != 0 && ip.EndpointID() != 0
}
// ValidNodeIP returns true if the IPv4 address is a valid IP of a node.
func (ip CiliumIPv4) ValidNodeIP() bool {
// Unlike IPv6, a node address looks the same as a container address
return ip.ValidContainerIP()
}
// NodeIP returns the node's IP based on an endpoint IP of the local node.
func (ip CiliumIPv4) NodeIP() net.IP {
nodeIP := make(net.IP, len(ip))
copy(nodeIP, ip)
nodeIP[3] = 1
return nodeIP
}