net

package module
v0.0.0-...-a208e16 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 1, 2018 License: Apache-2.0, MIT Imports: 6 Imported by: 0

README

net

Full networking stack implemented in Go

Copyrights in this project are retained by their contributors. No copyright assignment is required to contribute to this project. For a list of authors, see this repository's version control history.

This project is dual-licensed under the Apache 2.0 license or the MIT license at your option. Copies of these licenses can be found in the LICENSE-APACHE and LICENSE-MIT files respectively.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var BroadcastMAC = MAC{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}

BroadcastMAC is the broadcast MAC address.

Functions

func IsMTU

func IsMTU(err error) bool

IsMTU returns true if err is an MTU-related error.

func IsNoRoute

func IsNoRoute(err error) bool

IsNoRoute returns true if err is a route-related error.

func IsTimeout

func IsTimeout(err error) bool

IsTimeout returns true if err has a Timeout() bool method that returns true.

func ParseCIDR

func ParseCIDR(s string) (IP, IPSubnet, error)

ParseCIDR parses s as a CIDR notation IP address and mask, like "192.0.2.0/24" or "2001:db8::/32", as defined in RFC 4632 and RFC 4291.

It returns the IP address and the network implied by the IP and mask. For example, ParseCIDR("198.51.100.1/24") returns the IP address 198.51.100.1 and the network 198.51.100.0/24.

func ParseCIDRIPv4

func ParseCIDRIPv4(s string) (IPv4, IPv4Subnet, error)

ParseCIDRIPv4 is like ParseCIDR, but for IPv4 addresses only.

func ParseCIDRIPv6

func ParseCIDRIPv6(s string) (IPv6, IPv6Subnet, error)

ParseCIDRIPv6 is like ParseCIDR, but for IPv6 addresses only.

func SubnetEqual

func SubnetEqual(a, b IPSubnet) bool

SubnetEqual is a generic version of IPv4Subnet.Equal or IPv6Subnet.Equal. If a and b are not the same IP version, SubnetEqual returns false.

func SubnetHas

func SubnetHas(sub IPSubnet, addr IP) bool

SubnetHas is a generic version of IPv4Subnet.Has or IPv6Subnet.Has. If sub and addr are not the same IP version, SubnetHas returns false.

Types

type Device

type Device interface {
	// BringUp brings the Device up. If it is already up,
	// BringUp is a no-op.
	BringUp() error
	// BringDown brings the Device down. If it is already down,
	// BringDown is a no-op.
	BringDown() error
	// IsUp returns true if the Device is up.
	IsUp() bool

	// MTU returns the device's maximum transmission unit,
	// or 0 if no MTU is set.
	MTU() int
}

A Device is a handle on a physical or virtual network device. A Device must implement the IPv4Device or IPv6Device interfaces, although it may also implement both.

Devices are safe for concurrent access.

type DeviceSet

type DeviceSet struct {
	// contains filtered or unexported fields
}

A DeviceSet is a set of named Devices. A DeviceSet is safe for concurrent access. The zero value DeviceSet is a valid DeviceSet.

func (*DeviceSet) Get

func (d *DeviceSet) Get(name string) (dev Device, ok bool)

Get gets the named Device.

func (*DeviceSet) GetName

func (d *DeviceSet) GetName(dev Device) (name string, ok bool)

GetName gets the name for the given Device.

func (*DeviceSet) ListNames

func (d *DeviceSet) ListNames() (names []string)

ListNames returns a list of device names.

func (*DeviceSet) Put

func (d *DeviceSet) Put(name string, dev Device)

Put stores a new Device under the given name, overwriting any previous Device by the same name. If dev is nil, any Device with the given name will be removed.

type EtherType

type EtherType uint16

EtherType is a value of 1536 or greater which indicates the protocol type of a packet encapsulated in an Ethernet frame.

const (
	EtherTypeIPv4 EtherType = 0x0800
	EtherTypeARP  EtherType = 0x0806
	EtherTypeIPv6 EtherType = 0x86DD
)

type EthernetDevice

type EthernetDevice struct {
	// contains filtered or unexported fields
}

An EthernetDevice is a device which uses an EthernetInterface as its underlying frame transport mechanism. It implements the Device interface.

func NewEthernetDevice

func NewEthernetDevice(iface EthernetInterface, addr MAC) (*EthernetDevice, error)

NewEthernetDevice creates a new EthernetDevice using iface for frame transport and addr as the interface's MAC address. iface is assumed to be down. After a successful call to NewEthernetDevice, the returned EthernetDevice is considered to own iface; modifications to iface by the caller may result in undefined behavior. The returned device is down, and has no associated IPv4 or IPv6 addresses.

func (*EthernetDevice) BringDown

func (dev *EthernetDevice) BringDown() error

BringDown brings dev down. If it is already up, BringDown is a no-op.

func (*EthernetDevice) BringUp

func (dev *EthernetDevice) BringUp() error

BringUp brings dev up. If it is already up, BringUp is a no-op.

func (*EthernetDevice) IPv4

func (dev *EthernetDevice) IPv4() (addr, netmask IPv4, ok bool)

IPv4 returns dev's IPv4 address and network mask if they have been set.

func (*EthernetDevice) IPv6

func (dev *EthernetDevice) IPv6() (addr, netmask IPv6, ok bool)

IPv6 returns dev's IPv6 address and network mask if they have been set.

func (*EthernetDevice) IsUp

func (dev *EthernetDevice) IsUp() bool

IsUp returns true if dev is up.

func (*EthernetDevice) MTU

func (dev *EthernetDevice) MTU() int

MTU returns dev's maximum transmission unit, or 0 if no MTU is set.

func (*EthernetDevice) RegisterIPv4Callback

func (dev *EthernetDevice) RegisterIPv4Callback(f func(b []byte))

RegisterIPv4Callback implements IPv4Device's RegisterIPv4Callback.

func (*EthernetDevice) RegisterIPv6Callback

func (dev *EthernetDevice) RegisterIPv6Callback(f func(b []byte))

RegisterIPv6Callback implements IPv6Device's RegisterIPv6Callback.

func (*EthernetDevice) SetIPv4

func (dev *EthernetDevice) SetIPv4(addr, netmask IPv4) error

SetIPv4 sets dev's IPv4 address and network mask, returning any error encountered. SetIPv4 can only be called when dev is down.

func (*EthernetDevice) SetIPv6

func (dev *EthernetDevice) SetIPv6(addr, netmask IPv6) error

SetIPv6 sets dev's IPv6 address and network mask, returning any error encountered. SetIPv6 can only be called when dev is down.

func (*EthernetDevice) UnsetIPv4

func (dev *EthernetDevice) UnsetIPv4() error

UnsetIPv4 unsets dev's IPv4 address and network mask, returning any error encountered. UnsetIPv4 can only be called when dev is down.

func (*EthernetDevice) UnsetIPv6

func (dev *EthernetDevice) UnsetIPv6() error

UnsetIPv6 unsets dev's IPv6 address and network mask, returning any error encountered. UnsetIPv4 can only be called when dev is down.

func (*EthernetDevice) WriteToIPv4

func (dev *EthernetDevice) WriteToIPv4(b []byte, dst IPv4) (n int, err error)

func (*EthernetDevice) WriteToIPv6

func (dev *EthernetDevice) WriteToIPv6(b []byte, dst IPv6) (n int, err error)

type EthernetInterface

type EthernetInterface interface {
	// BringUp brings the interface up. If it is already up,
	// BringUp is a no-op.
	BringUp() error
	// BringDown brings the interface down. If it is already down,
	// BringDown is a no-op.
	BringDown() error
	// IsUp returns true if the interface is up.
	IsUp() bool

	// MAC returns the interface's MAC address, if any.
	MAC() (ok bool, mac MAC)
	// SetMAC sets the interface's MAC address. It is an error
	// to call SetMAC with the broadcast MAC, or while the
	// interface is up.
	SetMAC(mac MAC) error

	// MTU returns the interface's MTU. If no MTU is set, MTU will return 0.
	MTU() int
	// SetMTU sets the interface's MTU. It is an error to set
	// an MTU of 0 or to call SetMTU while the interface is up.
	SetMTU(mtu uint64) error

	// RegisterCallback registers f as the function to be called
	// when a new Ethernet frame arrives. It overwrites any
	// previously-registered callbacks. If f is nil, incoming
	// Ethernet frames will be dropped.
	//
	// If the interface has its MAC set, only Ethernet frames
	// whose destination MAC is equal to the interface's MAC or
	// is the broadcast MAC will be returned.
	//
	// RegisterCallback can only be called while the interface
	// is down.
	RegisterCallback(f func(b []byte, src, dst MAC, et EtherType))
	// WriteFrame writes an Ethernet frame with the payload b.
	// b is expected to contain space preceding the payload itself
	// for the Ethernet header, which WriteFrame is responsible
	// for writing. If the interface has an MTU set, and len(b)
	// is larger than that MTU plus the length of an Ethernet header,
	// the frame will not be written, and instead WriteFrame will
	// return an MTU error (see IsMTU).
	//
	// If the destination MAC is the broadcast MAC, the frame will
	// be broadcast to all devices on the local Ethernet network.
	//
	// If a MAC address has been set, that will be used as the
	// frame's source MAC. Otherwise, WriteFrame will return an error.
	WriteFrame(b []byte, dst MAC, et EtherType) (n int, err error)
	// WriteFrameSrc is like WriteFrame, but allows the source MAC
	// address to be set explicitly.
	WriteFrameSrc(b []byte, src, dst MAC, et EtherType) (n int, err error)
}

An EthernetInterface is a low-level interface capable of reading and writing Ethernet frames.

An EthernetInterface is safe for concurrent access.

type IP

type IP interface {
	// IPVersion is the IP's version - 4 or 6.
	IPVersion() int
	// contains filtered or unexported methods
}

IP is an IPv4 or IPv6 address. It is only implemented by IPv4 and IPv6.

func ParseIP

func ParseIP(s string) (IP, error)

ParseIP parses s as an IP address, returning the result. The string s can be in dotted decimal ("192.0.2.1") or IPv6 ("2001:db8::68") form.

type IPHost

type IPHost struct {
	IPv4Host
	IPv6Host
}

func (*IPHost) AddDevice

func (host *IPHost) AddDevice(dev Device)

func (*IPHost) AddDeviceRoute

func (host *IPHost) AddDeviceRoute(subnet IPSubnet, dev Device) error

func (*IPHost) AddRoute

func (host *IPHost) AddRoute(subnet IPSubnet, nexthop IP) error

func (*IPHost) GetConfigCopy

func (host *IPHost) GetConfigCopy() *IPHost

func (*IPHost) RegisterCallback

func (host *IPHost) RegisterCallback(f func(b []byte, src, dst IP), proto IPProtocol)

func (*IPHost) RemoveDevice

func (host *IPHost) RemoveDevice(dev Device)

func (*IPHost) SetForwarding

func (host *IPHost) SetForwarding(on bool)

func (*IPHost) SetTTL

func (host *IPHost) SetTTL(ttl uint8)

func (*IPHost) WriteTo

func (host *IPHost) WriteTo(b []byte, addr IP, proto IPProtocol) (n int, err error)

type IPProtocol

type IPProtocol uint8

IPProtocol represents the protocol field of an IPv4 packet and the next header field of an IPv6 packet.

const (
	IPProtocolTCP IPProtocol = 6
)

type IPSubnet

type IPSubnet interface {
	// IPVersion returns the subnet's IP version - 4 or 6.
	IPVersion() int
	// contains filtered or unexported methods
}

IPSubnet is an IPv4 or IPv6 subnet. It is only implemented by IPv4Subnet and IPv6Subnet.

type IPv4

type IPv4 [4]byte

IPv4 is an IPv4 address

func ParseIPv4

func ParseIPv4(s string) (IPv4, error)

ParseIPv4 is like ParseIP, but for IPv4 addresses only.

func (IPv4) IPVersion

func (i IPv4) IPVersion() int

IPVersion returns i's IP version - 4.

func (IPv4) String

func (i IPv4) String() string

type IPv4Device

type IPv4Device interface {
	Device

	// IPv4 returns the device's IPv4 address and network mask
	// if they have been set.
	IPv4() (addr, netmask IPv4, ok bool)
	// SetIPv4 sets the device's IPv4 address and network mask,
	// returning any error encountered. SetIPv4 can only be
	// called when the device is down.
	SetIPv4(addr, netmask IPv4) error
	// UnsetIPv4 unsets the device's IPv4 address and network
	// mask, returning any error encountered. UnsetIPv4 can
	// only be called when the device is down.
	UnsetIPv4() error

	// RegisterIPv4Callback registers f as the function
	// to be called when a new IPv4 packet arrives. It
	// overwrites any previously-registered callbacks.
	// If f is nil, incoming IPv4 packets will be dropped.
	RegisterIPv4Callback(f func([]byte))
	// WriteToIPv4 is like Device's WriteTo,
	// but for IPv4 only.
	WriteToIPv4(b []byte, dst IPv4) (n int, err error)
}

An IPv4Device is a Device with IPv4-specific methods.

type IPv4DeviceRoute

type IPv4DeviceRoute struct {
	Subnet IPv4Subnet
	Device IPv4Device
}

type IPv4Host

type IPv4Host interface {
	AddIPv4Device(dev IPv4Device)
	RemoveIPv4Device(dev IPv4Device)
	RegisterIPv4Callback(f func(b []byte, src, dst IPv4), proto IPProtocol)
	AddIPv4Route(subnet IPv4Subnet, nexthop IPv4)
	AddIPv4DeviceRoute(subnet IPv4Subnet, dev IPv4Device)
	IPv4Routes() []IPv4Route
	IPv4DeviceRoutes() []IPv4DeviceRoute
	SetForwarding(on bool)
	Forwarding() bool
	WriteToIPv4(b []byte, addr IPv4, proto IPProtocol) (n int, err error)

	// SetTTL sets the TTL for all outoing packets. If ttl is 0, a default TTL
	// will be used.
	SetTTL(ttl uint8)

	// GetConfigCopyIPv4 returns an IPv4Host which is simply a wrapper around
	// the original host, but which allows setting configuration values
	// without setting those values on the original host. In particular, all
	// methods except for SetTTL operate directly on the original host.
	GetConfigCopyIPv4() IPv4Host
}

func NewIPv4Host

func NewIPv4Host() IPv4Host

type IPv4Route

type IPv4Route struct {
	Subnet  IPv4Subnet
	Nexthop IPv4
}

type IPv4Subnet

type IPv4Subnet struct {
	Addr    IPv4
	Netmask IPv4
}

IPv4Subnet is an IPv4 address and subnet mask. NOTE: Because address bits that are not in the netmask do not affect equality, it is not safe to determine subnet equality by comparing two IPv4Subnets using ==. Instead, use the Equal method.

func (IPv4Subnet) Equal

func (sub IPv4Subnet) Equal(other IPv4Subnet) bool

Equal determines whether sub is equal to other.

func (IPv4Subnet) Has

func (sub IPv4Subnet) Has(addr IPv4) bool

Has returns true if addr is in the subnet sub.

func (IPv4Subnet) IPVersion

func (sub IPv4Subnet) IPVersion() int

IPVersion returns sub's IP version - 4.

type IPv6

type IPv6 [16]byte

IPv6 is an IPv6 address

func ParseIPv6

func ParseIPv6(s string) (IPv6, error)

ParseIPv6 is like ParseIP, but for IPv6 addresses only.

func (IPv6) IPVersion

func (i IPv6) IPVersion() int

IPVersion returns i's IP version - 6.

func (IPv6) String

func (i IPv6) String() string

type IPv6Device

type IPv6Device interface {
	Device

	// IPv6 returns the device's IPv6 address and network mask
	// if they have been set.
	IPv6() (addr, netmask IPv6, ok bool)
	// SetIPv6 sets the device's IPv6 address and network mask,
	// returning any error encountered. SetIPv6 can only be
	// called when the device is down.
	SetIPv6(addr, netmask IPv6) error
	// UnsetIPv6 unsets the device's IPv6 address and network
	// mask, returning any error encountered. UnsetIPv6 can
	// only be called when the device is down.
	UnsetIPv6() error

	// RegisterIPv6Callback registers f as the function
	// to be called when a new IPv4 packet arrives. It
	// overwrites any previously-registered callbacks.
	// If f is nil, incoming IPv4 packets will be dropped.
	RegisterIPv6Callback(f func([]byte))
	// WriteToIPv6 is like Device's WriteTo,
	// but for IPv6 only.
	WriteToIPv6(b []byte, dst IPv6) (n int, err error)
}

An IPv6Device is a Device with IPv6-specific methods.

type IPv6DeviceRoute

type IPv6DeviceRoute struct {
	Subnet IPv6Subnet
	Device IPv6Device
}

type IPv6Host

type IPv6Host interface {
	AddIPv6Device(dev IPv6Device)
	RemoveIPv6Device(dev IPv6Device)
	RegisterIPv6Callback(f func(b []byte, src, dst IPv6), proto IPProtocol)
	AddIPv6Route(subnet IPv6Subnet, nexthop IPv6)
	AddIPv6DeviceRoute(subnet IPv6Subnet, dev IPv6Device)
	IPv6Routes() []IPv6Route
	IPv6DeviceRoutes() []IPv6DeviceRoute
	SetForwarding(on bool)
	Forwarding() bool
	WriteToIPv6(b []byte, addr IPv6, proto IPProtocol) (n int, err error)

	// SetTTL sets the TTL for all outoing packets. If ttl is 0, a default TTL
	// will be used.
	SetTTL(ttl uint8)

	// GetConfigCopyIPv6 returns an IPv6Host which is simply a wrapper around
	// the original host, but which allows setting configuration values
	// without setting those values on the original host. In particular, all
	// methods except for SetTTL operate directly on the original host.
	GetConfigCopyIPv6() IPv6Host
}

func NewIPv6Host

func NewIPv6Host() IPv6Host

type IPv6Route

type IPv6Route struct {
	Subnet  IPv6Subnet
	Nexthop IPv6
}

type IPv6Subnet

type IPv6Subnet struct {
	Addr    IPv6
	Netmask IPv6
}

IPv6Subnet is an IPv6 address and subnet mask. NOTE: Because address bits that are not in the netmask do not affect equality, it is not safe to determine subnet equality by comparing two IPv6Subnets using ==. Instead, use the Equal method.

func (IPv6Subnet) Equal

func (sub IPv6Subnet) Equal(other IPv6Subnet) bool

Equal determines whether sub is equal to other.

func (IPv6Subnet) Has

func (sub IPv6Subnet) Has(addr IPv6) bool

Has returns true if addr is in the subnet sub.

func (IPv6Subnet) IPVersion

func (sub IPv6Subnet) IPVersion() int

IPVersion returns sub's IP version - 6.

type MAC

type MAC [6]byte

MAC is an Ethernet media access control address.

type UDPIPv4Device

type UDPIPv4Device struct {
	// contains filtered or unexported fields
}

UDPIPv4Device represents a device created by sending link-layer packets over UDP. A UDPIPv4Device is only capable of sending and receiving IPv4 packets. UDPIPv4Devices are point-to-point - there is always exactly one other link-local device.

The zero UDPIPv4Device is not a valid UDPIPv4Device. UDPIPv4Devices are safe for concurrent access.

func NewUDPIPv4Device

func NewUDPIPv4Device(laddr, raddr *net.UDPAddr, mtu int) (dev *UDPIPv4Device, err error)

NewUDPIPv4Device creates a new UDPIPv4Device, which is down by default. It is the caller's responsibility to ensure that both sides of the connection are configured with the same MTU, which must be non-zero. Keep in mind that a single MTU-sized buffer will be allocated in order to read incoming packets, so an overly-large MTU will result in significant memory waste.

func (*UDPIPv4Device) BringDown

func (dev *UDPIPv4Device) BringDown() error

BringDown brings dev down. If it is already down, BringDown is a no-op.

func (*UDPIPv4Device) BringUp

func (dev *UDPIPv4Device) BringUp() error

BringUp brings dev up. If it is already up, BringUp is a no-op.

func (*UDPIPv4Device) IPv4

func (dev *UDPIPv4Device) IPv4() (addr, netmask IPv4, ok bool)

IPv4 returns dev's IPv4 address and network mask if they have been set.

func (*UDPIPv4Device) IsUp

func (dev *UDPIPv4Device) IsUp() bool

IsUp returns true if dev is up.

func (*UDPIPv4Device) MTU

func (dev *UDPIPv4Device) MTU() int

MTU returns dev's MTU.

func (*UDPIPv4Device) RegisterIPv4Callback

func (dev *UDPIPv4Device) RegisterIPv4Callback(f func(b []byte))

RegisterIPv4Callback registers f to be called when IPv4 packets are received.

func (*UDPIPv4Device) SetIPv4

func (dev *UDPIPv4Device) SetIPv4(addr, netmask IPv4) error

SetIPv4 sets dev's IPv4 address and network mask, returning any error encountered. SetIPv4 can only be called when dev is down.

func (*UDPIPv4Device) UDPAddrs

func (dev *UDPIPv4Device) UDPAddrs() (laddr, raddr *net.UDPAddr)

UDPAddrs returns the local and remote UDP addresses used by dev.

func (*UDPIPv4Device) UnsetIPv4

func (dev *UDPIPv4Device) UnsetIPv4() error

UnsetIPv4 unsets dev's IPv4 address and network mask, returning any error encountered. UnsetIPv4 can only be called when dev is down.

func (*UDPIPv4Device) WriteToIPv4

func (dev *UDPIPv4Device) WriteToIPv4(b []byte, dst IPv4) (n int, err error)

WriteToIPv4 writes the payload b in a link-layer frame to the link-layer address corresponding to the destination IPv4 address.

type UDPIPv6Device

type UDPIPv6Device struct {
	// contains filtered or unexported fields
}

UDPIPv6Device represents a device created by sending link-layer packets over UDP. A UDPIPv6Device is only capable of sending and receiving IPv6 packets. UDPIPv6Devices are point-to-point - there is always exactly one other link-local device.

The zero UDPIPv6Device is not a valid UDPIPv6Device. UDPIPv6Devices are safe for concurrent access.

func NewUDPIPv6Device

func NewUDPIPv6Device(laddr, raddr *net.UDPAddr, mtu int) (dev *UDPIPv6Device, err error)

NewUDPIPv6Device creates a new UDPIPv6Device, which is down by default. It is the caller's responsibility to ensure that both sides of the connection are configured with the same MTU, which must be non-zero. Keep in mind that a single MTU-sized buffer will be allocated in order to read incoming packets, so an overly-large MTU will result in significant memory waste.

func (*UDPIPv6Device) BringDown

func (dev *UDPIPv6Device) BringDown() error

BringDown brings dev down. If it is already down, BringDown is a no-op.

func (*UDPIPv6Device) BringUp

func (dev *UDPIPv6Device) BringUp() error

BringUp brings dev up. If it is already up, BringUp is a no-op.

func (*UDPIPv6Device) IPv6

func (dev *UDPIPv6Device) IPv6() (addr, netmask IPv6, ok bool)

IPv6 returns dev's IPv6 address and network mask if they have been set.

func (*UDPIPv6Device) IsUp

func (dev *UDPIPv6Device) IsUp() bool

IsUp returns true if dev is up.

func (*UDPIPv6Device) MTU

func (dev *UDPIPv6Device) MTU() int

MTU returns dev's MTU.

func (*UDPIPv6Device) RegisterIPv6Callback

func (dev *UDPIPv6Device) RegisterIPv6Callback(f func(b []byte))

RegisterIPv6Callback registers f to be called when IPv6 packets are received.

func (*UDPIPv6Device) SetIPv6

func (dev *UDPIPv6Device) SetIPv6(addr, netmask IPv6) error

SetIPv6 sets dev's IPv6 address and network mask, returning any error encountered. SetIPv6 can only be called when dev is down.

func (*UDPIPv6Device) UDPAddrs

func (dev *UDPIPv6Device) UDPAddrs() (laddr, raddr *net.UDPAddr)

UDPAddrs returns the local and remote UDP addresses used by dev.

func (*UDPIPv6Device) UnsetIPv6

func (dev *UDPIPv6Device) UnsetIPv6() error

UnsetIPv6 unsets dev's IPv6 address and network mask, returning any error encountered. UnsetIPv6 can only be called when dev is down.

func (*UDPIPv6Device) WriteToIPv6

func (dev *UDPIPv6Device) WriteToIPv6(b []byte, dst IPv6) (n int, err error)

WriteToIPv6 writes the payload b in a link-layer frame to the link-layer address corresponding to the destination IPv6 address.

Directories

Path Synopsis
example
cli
internal
tcp

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL