Merge pull request #469 from AlbanBedel/portmap-hairpin-subnet
portmap: Apply the DNAT hairpin to the whole subnet
This commit is contained in:
commit
5af9ff493e
@ -72,7 +72,7 @@ will masquerade traffic as needed.
|
|||||||
The DNAT rule rewrites the destination port and address of new connections.
|
The DNAT rule rewrites the destination port and address of new connections.
|
||||||
There is a top-level chain, `CNI-HOSTPORT-DNAT` which is always created and
|
There is a top-level chain, `CNI-HOSTPORT-DNAT` which is always created and
|
||||||
never deleted. Each plugin execution creates an additional chain for ease
|
never deleted. Each plugin execution creates an additional chain for ease
|
||||||
of cleanup. So, if a single container exists on IP 172.16.30.2 with ports
|
of cleanup. So, if a single container exists on IP 172.16.30.2/24 with ports
|
||||||
8080 and 8043 on the host forwarded to ports 80 and 443 in the container, the
|
8080 and 8043 on the host forwarded to ports 80 and 443 in the container, the
|
||||||
rules look like this:
|
rules look like this:
|
||||||
|
|
||||||
@ -86,10 +86,10 @@ rules look like this:
|
|||||||
- `-j MARK --set-xmark 0x2000/0x2000`
|
- `-j MARK --set-xmark 0x2000/0x2000`
|
||||||
|
|
||||||
`CNI-DN-xxxxxx` chain:
|
`CNI-DN-xxxxxx` chain:
|
||||||
- `-p tcp -s 172.16.30.2 --dport 8080 -j CNI-HOSTPORT-SETMARK` (masquerade hairpin traffic)
|
- `-p tcp -s 172.16.30.0/24 --dport 8080 -j CNI-HOSTPORT-SETMARK` (masquerade hairpin traffic)
|
||||||
- `-p tcp -s 127.0.0.1 --dport 8080 -j CNI-HOSTPORT-SETMARK` (masquerade localhost traffic)
|
- `-p tcp -s 127.0.0.1 --dport 8080 -j CNI-HOSTPORT-SETMARK` (masquerade localhost traffic)
|
||||||
- `-p tcp --dport 8080 -j DNAT --to-destination 172.16.30.2:80` (rewrite destination)
|
- `-p tcp --dport 8080 -j DNAT --to-destination 172.16.30.2:80` (rewrite destination)
|
||||||
- `-p tcp -s 172.16.30.2 --dport 8043 -j CNI-HOSTPORT-SETMARK`
|
- `-p tcp -s 172.16.30.0/24 --dport 8043 -j CNI-HOSTPORT-SETMARK`
|
||||||
- `-p tcp -s 127.0.0.1 --dport 8043 -j CNI-HOSTPORT-SETMARK`
|
- `-p tcp -s 127.0.0.1 --dport 8043 -j CNI-HOSTPORT-SETMARK`
|
||||||
- `-p tcp --dport 8043 -j DNAT --to-destination 172.16.30.2:443`
|
- `-p tcp --dport 8043 -j DNAT --to-destination 172.16.30.2:443`
|
||||||
|
|
||||||
|
@ -61,8 +61,8 @@ type PortMapConf struct {
|
|||||||
// These are fields parsed out of the config or the environment;
|
// These are fields parsed out of the config or the environment;
|
||||||
// included here for convenience
|
// included here for convenience
|
||||||
ContainerID string `json:"-"`
|
ContainerID string `json:"-"`
|
||||||
ContIPv4 net.IP `json:"-"`
|
ContIPv4 net.IPNet `json:"-"`
|
||||||
ContIPv6 net.IP `json:"-"`
|
ContIPv6 net.IPNet `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// The default mark bit to signal that masquerading is required
|
// The default mark bit to signal that masquerading is required
|
||||||
@ -85,13 +85,13 @@ func cmdAdd(args *skel.CmdArgs) error {
|
|||||||
|
|
||||||
netConf.ContainerID = args.ContainerID
|
netConf.ContainerID = args.ContainerID
|
||||||
|
|
||||||
if netConf.ContIPv4 != nil {
|
if netConf.ContIPv4.IP != nil {
|
||||||
if err := forwardPorts(netConf, netConf.ContIPv4); err != nil {
|
if err := forwardPorts(netConf, netConf.ContIPv4); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if netConf.ContIPv6 != nil {
|
if netConf.ContIPv6.IP != nil {
|
||||||
if err := forwardPorts(netConf, netConf.ContIPv6); err != nil {
|
if err := forwardPorts(netConf, netConf.ContIPv6); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -138,13 +138,13 @@ func cmdCheck(args *skel.CmdArgs) error {
|
|||||||
|
|
||||||
conf.ContainerID = args.ContainerID
|
conf.ContainerID = args.ContainerID
|
||||||
|
|
||||||
if conf.ContIPv4 != nil {
|
if conf.ContIPv4.IP != nil {
|
||||||
if err := checkPorts(conf, conf.ContIPv4); err != nil {
|
if err := checkPorts(conf, conf.ContIPv4); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if conf.ContIPv6 != nil {
|
if conf.ContIPv6.IP != nil {
|
||||||
if err := checkPorts(conf, conf.ContIPv6); err != nil {
|
if err := checkPorts(conf, conf.ContIPv6); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -205,9 +205,9 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, *current.Result, er
|
|||||||
|
|
||||||
if conf.PrevResult != nil {
|
if conf.PrevResult != nil {
|
||||||
for _, ip := range result.IPs {
|
for _, ip := range result.IPs {
|
||||||
if ip.Version == "6" && conf.ContIPv6 != nil {
|
if ip.Version == "6" && conf.ContIPv6.IP != nil {
|
||||||
continue
|
continue
|
||||||
} else if ip.Version == "4" && conf.ContIPv4 != nil {
|
} else if ip.Version == "4" && conf.ContIPv4.IP != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,9 +223,9 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, *current.Result, er
|
|||||||
}
|
}
|
||||||
switch ip.Version {
|
switch ip.Version {
|
||||||
case "6":
|
case "6":
|
||||||
conf.ContIPv6 = ip.Address.IP
|
conf.ContIPv6 = ip.Address
|
||||||
case "4":
|
case "4":
|
||||||
conf.ContIPv4 = ip.Address.IP
|
conf.ContIPv4 = ip.Address
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,9 +48,9 @@ const MarkMasqChainName = "CNI-HOSTPORT-MASQ"
|
|||||||
const OldTopLevelSNATChainName = "CNI-HOSTPORT-SNAT"
|
const OldTopLevelSNATChainName = "CNI-HOSTPORT-SNAT"
|
||||||
|
|
||||||
// forwardPorts establishes port forwarding to a given container IP.
|
// forwardPorts establishes port forwarding to a given container IP.
|
||||||
// containerIP can be either v4 or v6.
|
// containerNet.IP can be either v4 or v6.
|
||||||
func forwardPorts(config *PortMapConf, containerIP net.IP) error {
|
func forwardPorts(config *PortMapConf, containerNet net.IPNet) error {
|
||||||
isV6 := (containerIP.To4() == nil)
|
isV6 := (containerNet.IP.To4() == nil)
|
||||||
|
|
||||||
var ipt *iptables.IPTables
|
var ipt *iptables.IPTables
|
||||||
var err error
|
var err error
|
||||||
@ -86,7 +86,7 @@ func forwardPorts(config *PortMapConf, containerIP net.IP) error {
|
|||||||
if !isV6 {
|
if !isV6 {
|
||||||
// Set the route_localnet bit on the host interface, so that
|
// Set the route_localnet bit on the host interface, so that
|
||||||
// 127/8 can cross a routing boundary.
|
// 127/8 can cross a routing boundary.
|
||||||
hostIfName := getRoutableHostIF(containerIP)
|
hostIfName := getRoutableHostIF(containerNet.IP)
|
||||||
if hostIfName != "" {
|
if hostIfName != "" {
|
||||||
if err := enableLocalnetRouting(hostIfName); err != nil {
|
if err := enableLocalnetRouting(hostIfName); err != nil {
|
||||||
return fmt.Errorf("unable to enable route_localnet: %v", err)
|
return fmt.Errorf("unable to enable route_localnet: %v", err)
|
||||||
@ -104,7 +104,7 @@ func forwardPorts(config *PortMapConf, containerIP net.IP) error {
|
|||||||
dnatChain := genDnatChain(config.Name, config.ContainerID)
|
dnatChain := genDnatChain(config.Name, config.ContainerID)
|
||||||
// First, idempotently tear down this chain in case there was some
|
// First, idempotently tear down this chain in case there was some
|
||||||
// sort of collision or bad state.
|
// sort of collision or bad state.
|
||||||
fillDnatRules(&dnatChain, config, containerIP)
|
fillDnatRules(&dnatChain, config, containerNet)
|
||||||
if err := dnatChain.setup(ipt); err != nil {
|
if err := dnatChain.setup(ipt); err != nil {
|
||||||
return fmt.Errorf("unable to setup DNAT: %v", err)
|
return fmt.Errorf("unable to setup DNAT: %v", err)
|
||||||
}
|
}
|
||||||
@ -112,10 +112,10 @@ func forwardPorts(config *PortMapConf, containerIP net.IP) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkPorts(config *PortMapConf, containerIP net.IP) error {
|
func checkPorts(config *PortMapConf, containerNet net.IPNet) error {
|
||||||
|
|
||||||
dnatChain := genDnatChain(config.Name, config.ContainerID)
|
dnatChain := genDnatChain(config.Name, config.ContainerID)
|
||||||
fillDnatRules(&dnatChain, config, containerIP)
|
fillDnatRules(&dnatChain, config, containerNet)
|
||||||
|
|
||||||
ip4t := maybeGetIptables(false)
|
ip4t := maybeGetIptables(false)
|
||||||
ip6t := maybeGetIptables(true)
|
ip6t := maybeGetIptables(true)
|
||||||
@ -180,8 +180,8 @@ func genDnatChain(netName, containerID string) chain {
|
|||||||
|
|
||||||
// dnatRules generates the destination NAT rules, one per port, to direct
|
// dnatRules generates the destination NAT rules, one per port, to direct
|
||||||
// traffic from hostip:hostport to podip:podport
|
// traffic from hostip:hostport to podip:podport
|
||||||
func fillDnatRules(c *chain, config *PortMapConf, containerIP net.IP) {
|
func fillDnatRules(c *chain, config *PortMapConf, containerNet net.IPNet) {
|
||||||
isV6 := (containerIP.To4() == nil)
|
isV6 := (containerNet.IP.To4() == nil)
|
||||||
comment := trimComment(fmt.Sprintf(`dnat name: "%s" id: "%s"`, config.Name, config.ContainerID))
|
comment := trimComment(fmt.Sprintf(`dnat name: "%s" id: "%s"`, config.Name, config.ContainerID))
|
||||||
entries := config.RuntimeConfig.PortMaps
|
entries := config.RuntimeConfig.PortMaps
|
||||||
setMarkChainName := SetMarkChainName
|
setMarkChainName := SetMarkChainName
|
||||||
@ -249,7 +249,7 @@ func fillDnatRules(c *chain, config *PortMapConf, containerIP net.IP) {
|
|||||||
copy(hpRule, ruleBase)
|
copy(hpRule, ruleBase)
|
||||||
|
|
||||||
hpRule = append(hpRule,
|
hpRule = append(hpRule,
|
||||||
"-s", containerIP.String(),
|
"-s", containerNet.String(),
|
||||||
"-j", setMarkChainName,
|
"-j", setMarkChainName,
|
||||||
)
|
)
|
||||||
c.rules = append(c.rules, hpRule)
|
c.rules = append(c.rules, hpRule)
|
||||||
@ -272,7 +272,7 @@ func fillDnatRules(c *chain, config *PortMapConf, containerIP net.IP) {
|
|||||||
copy(dnatRule, ruleBase)
|
copy(dnatRule, ruleBase)
|
||||||
dnatRule = append(dnatRule,
|
dnatRule = append(dnatRule,
|
||||||
"-j", "DNAT",
|
"-j", "DNAT",
|
||||||
"--to-destination", fmtIpPort(containerIP, entry.ContainerPort),
|
"--to-destination", fmtIpPort(containerNet.IP, entry.ContainerPort),
|
||||||
)
|
)
|
||||||
c.rules = append(c.rules, dnatRule)
|
c.rules = append(c.rules, dnatRule)
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,8 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
@ -77,8 +78,10 @@ var _ = Describe("portmapping configuration", func() {
|
|||||||
Expect(c.SNAT).To(Equal(&fvar))
|
Expect(c.SNAT).To(Equal(&fvar))
|
||||||
Expect(c.Name).To(Equal("test"))
|
Expect(c.Name).To(Equal("test"))
|
||||||
|
|
||||||
Expect(c.ContIPv4).To(Equal(net.ParseIP("10.0.0.2")))
|
n, err := types.ParseCIDR("10.0.0.2/24")
|
||||||
Expect(c.ContIPv6).To(Equal(net.ParseIP("2001:db8:1::2")))
|
Expect(c.ContIPv4).To(Equal(*n))
|
||||||
|
n, err = types.ParseCIDR("2001:db8:1::2/64")
|
||||||
|
Expect(c.ContIPv6).To(Equal(*n))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Correctly parses a DEL config", func() {
|
It("Correctly parses a DEL config", func() {
|
||||||
@ -186,7 +189,8 @@ var _ = Describe("portmapping configuration", func() {
|
|||||||
entryChains: []string{"CNI-HOSTPORT-DNAT"},
|
entryChains: []string{"CNI-HOSTPORT-DNAT"},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
fillDnatRules(&ch, conf, net.ParseIP("10.0.0.2"))
|
n, err := types.ParseCIDR("10.0.0.2/24")
|
||||||
|
fillDnatRules(&ch, conf, *n)
|
||||||
|
|
||||||
Expect(ch.entryRules).To(Equal([][]string{
|
Expect(ch.entryRules).To(Equal([][]string{
|
||||||
{"-m", "comment", "--comment",
|
{"-m", "comment", "--comment",
|
||||||
@ -204,16 +208,16 @@ var _ = Describe("portmapping configuration", func() {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
Expect(ch.rules).To(Equal([][]string{
|
Expect(ch.rules).To(Equal([][]string{
|
||||||
{"-p", "tcp", "--dport", "8080", "-s", "10.0.0.2", "-j", "CNI-HOSTPORT-SETMARK"},
|
{"-p", "tcp", "--dport", "8080", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||||
{"-p", "tcp", "--dport", "8080", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
{"-p", "tcp", "--dport", "8080", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||||
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
||||||
{"-p", "tcp", "--dport", "8081", "-s", "10.0.0.2", "-j", "CNI-HOSTPORT-SETMARK"},
|
{"-p", "tcp", "--dport", "8081", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||||
{"-p", "tcp", "--dport", "8081", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
{"-p", "tcp", "--dport", "8081", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||||
{"-p", "tcp", "--dport", "8081", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
{"-p", "tcp", "--dport", "8081", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
||||||
{"-p", "udp", "--dport", "8080", "-s", "10.0.0.2", "-j", "CNI-HOSTPORT-SETMARK"},
|
{"-p", "udp", "--dport", "8080", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||||
{"-p", "udp", "--dport", "8080", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
{"-p", "udp", "--dport", "8080", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||||
{"-p", "udp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:81"},
|
{"-p", "udp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:81"},
|
||||||
{"-p", "udp", "--dport", "8082", "-s", "10.0.0.2", "-j", "CNI-HOSTPORT-SETMARK"},
|
{"-p", "udp", "--dport", "8082", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||||
{"-p", "udp", "--dport", "8082", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
{"-p", "udp", "--dport", "8082", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||||
{"-p", "udp", "--dport", "8082", "-j", "DNAT", "--to-destination", "10.0.0.2:82"},
|
{"-p", "udp", "--dport", "8082", "-j", "DNAT", "--to-destination", "10.0.0.2:82"},
|
||||||
}))
|
}))
|
||||||
@ -221,16 +225,17 @@ var _ = Describe("portmapping configuration", func() {
|
|||||||
ch.rules = nil
|
ch.rules = nil
|
||||||
ch.entryRules = nil
|
ch.entryRules = nil
|
||||||
|
|
||||||
fillDnatRules(&ch, conf, net.ParseIP("2001:db8::2"))
|
n, err = types.ParseCIDR("2001:db8::2/64")
|
||||||
|
fillDnatRules(&ch, conf, *n)
|
||||||
|
|
||||||
Expect(ch.rules).To(Equal([][]string{
|
Expect(ch.rules).To(Equal([][]string{
|
||||||
{"-p", "tcp", "--dport", "8080", "-s", "2001:db8::2", "-j", "CNI-HOSTPORT-SETMARK"},
|
{"-p", "tcp", "--dport", "8080", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||||
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "[2001:db8::2]:80"},
|
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "[2001:db8::2]:80"},
|
||||||
{"-p", "tcp", "--dport", "8081", "-s", "2001:db8::2", "-j", "CNI-HOSTPORT-SETMARK"},
|
{"-p", "tcp", "--dport", "8081", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||||
{"-p", "tcp", "--dport", "8081", "-j", "DNAT", "--to-destination", "[2001:db8::2]:80"},
|
{"-p", "tcp", "--dport", "8081", "-j", "DNAT", "--to-destination", "[2001:db8::2]:80"},
|
||||||
{"-p", "udp", "--dport", "8080", "-s", "2001:db8::2", "-j", "CNI-HOSTPORT-SETMARK"},
|
{"-p", "udp", "--dport", "8080", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||||
{"-p", "udp", "--dport", "8080", "-j", "DNAT", "--to-destination", "[2001:db8::2]:81"},
|
{"-p", "udp", "--dport", "8080", "-j", "DNAT", "--to-destination", "[2001:db8::2]:81"},
|
||||||
{"-p", "udp", "--dport", "8082", "-s", "2001:db8::2", "-j", "CNI-HOSTPORT-SETMARK"},
|
{"-p", "udp", "--dport", "8082", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||||
{"-p", "udp", "--dport", "8082", "-j", "DNAT", "--to-destination", "[2001:db8::2]:82"},
|
{"-p", "udp", "--dport", "8082", "-j", "DNAT", "--to-destination", "[2001:db8::2]:82"},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -240,7 +245,8 @@ var _ = Describe("portmapping configuration", func() {
|
|||||||
fvar := false
|
fvar := false
|
||||||
conf.SNAT = &fvar
|
conf.SNAT = &fvar
|
||||||
|
|
||||||
fillDnatRules(&ch, conf, net.ParseIP("10.0.0.2"))
|
n, err = types.ParseCIDR("10.0.0.2/24")
|
||||||
|
fillDnatRules(&ch, conf, *n)
|
||||||
Expect(ch.rules).To(Equal([][]string{
|
Expect(ch.rules).To(Equal([][]string{
|
||||||
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
||||||
{"-p", "tcp", "--dport", "8081", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
{"-p", "tcp", "--dport", "8081", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
||||||
@ -276,9 +282,10 @@ var _ = Describe("portmapping configuration", func() {
|
|||||||
conf.ContainerID = containerID
|
conf.ContainerID = containerID
|
||||||
|
|
||||||
ch = genDnatChain(conf.Name, containerID)
|
ch = genDnatChain(conf.Name, containerID)
|
||||||
fillDnatRules(&ch, conf, net.ParseIP("10.0.0.2"))
|
n, err := types.ParseCIDR("10.0.0.2/24")
|
||||||
|
fillDnatRules(&ch, conf, *n)
|
||||||
Expect(ch.rules).To(Equal([][]string{
|
Expect(ch.rules).To(Equal([][]string{
|
||||||
{"-p", "tcp", "--dport", "8080", "-s", "10.0.0.2", "-j", "PLZ-SET-MARK"},
|
{"-p", "tcp", "--dport", "8080", "-s", "10.0.0.2/24", "-j", "PLZ-SET-MARK"},
|
||||||
{"-p", "tcp", "--dport", "8080", "-s", "127.0.0.1", "-j", "PLZ-SET-MARK"},
|
{"-p", "tcp", "--dport", "8080", "-s", "127.0.0.1", "-j", "PLZ-SET-MARK"},
|
||||||
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
||||||
}))
|
}))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user