Subject: [patch 1/1] /proc/$$/ipaddr and per-task networking bits

Ported feature from grSecurity that makes possible to add an ipaddr
entry in each /proc/<pid> (/proc/<pid>/ipaddr), where the task originating
IP address is stored, and subsequently made available (readable) by the process
itself and also the root user with CAP_DAC_OVERRIDE capability (that can be managed
by specific security models implementations like SELinux).
Available also at http://pearls.tuxedo-es.org/patches/task-curr_ip.patch

Signed-off-by: Lorenzo Hernandez Garcia-Hierro <[email protected]>
---

linux-2.6.11-lorenzo/fs/Kconfig | 16 ++
linux-2.6.11-lorenzo/fs/proc/array.c | 10 +
linux-2.6.11-lorenzo/fs/proc/base.c | 12 ++
linux-2.6.11-lorenzo/fs/proc/internal.h | 3
linux-2.6.11-lorenzo/include/linux/sched.h | 8 +
linux-2.6.11-lorenzo/kernel/exit.c | 8 +
linux-2.6.11-lorenzo/net/Makefile | 4
linux-2.6.11-lorenzo/net/ipv4/tcp_ipv4.c | 14 ++
linux-2.6.11-lorenzo/net/proc_ipaddr.c | 166 +++++++++++++++++++++++++++++
linux-2.6.11-lorenzo/net/socket.c | 2
10 files changed, 243 insertions(+)

diff -puN fs/proc/base.c~task-curr_ip fs/proc/base.c
--- linux-2.6.11/fs/proc/base.c~task-curr_ip 2005-03-10 14:56:13.931854168 +0100
+++ linux-2.6.11-lorenzo/fs/proc/base.c 2005-03-10 14:56:14.048836384 +0100
@@ -74,6 +74,9 @@ enum pid_directory_inos {
#ifdef CONFIG_AUDITSYSCALL
PROC_TGID_LOGINUID,
#endif
+#ifdef CONFIG_PROC_IPADDR
+ PROC_TGID_IPADDR,
+#endif
PROC_TGID_FD_DIR,
PROC_TGID_OOM_SCORE,
PROC_TGID_OOM_ADJUST,
@@ -134,6 +137,9 @@ static struct pid_entry tgid_base_stuff[
E(PROC_TGID_ROOT, "root", S_IFLNK|S_IRWXUGO),
E(PROC_TGID_EXE, "exe", S_IFLNK|S_IRWXUGO),
E(PROC_TGID_MOUNTS, "mounts", S_IFREG|S_IRUGO),
+#ifdef CONFIG_PROC_IPADDR
+ E(PROC_TGID_IPADDR, "ipaddr", S_IFREG|S_IRUSR),
+#endif
#ifdef CONFIG_SECURITY
E(PROC_TGID_ATTR, "attr", S_IFDIR|S_IRUGO|S_IXUGO),
#endif
@@ -1416,6 +1422,12 @@ static struct dentry *proc_pident_lookup
inode->i_fop = &proc_info_file_operations;
ei->op.proc_read = proc_pid_status;
break;
+#ifdef CONFIG_PROC_IPADDR
+ case PROC_TGID_IPADDR:
+ inode->i_fop = &proc_info_file_operations;
+ ei->op.proc_read = proc_pid_ipaddr;
+ break;
+#endif
case PROC_TID_STAT:
inode->i_fop = &proc_info_file_operations;
ei->op.proc_read = proc_tid_stat;
diff -puN fs/proc/internal.h~task-curr_ip fs/proc/internal.h
--- linux-2.6.11/fs/proc/internal.h~task-curr_ip 2005-03-10 14:56:13.932854016 +0100
+++ linux-2.6.11-lorenzo/fs/proc/internal.h 2005-03-10 14:56:14.048836384 +0100
@@ -36,6 +36,9 @@ extern int proc_tid_stat(struct task_str
extern int proc_tgid_stat(struct task_struct *, char *);
extern int proc_pid_status(struct task_struct *, char *);
extern int proc_pid_statm(struct task_struct *, char *);
+#ifdef CONFIG_PROC_IPADDR
+extern int proc_pid_ipaddr(struct task_struct*,char*);
+#endif

static inline struct task_struct *proc_task(struct inode *inode)
{
diff -puN fs/proc/array.c~task-curr_ip fs/proc/array.c
--- linux-2.6.11/fs/proc/array.c~task-curr_ip 2005-03-10 14:56:13.934853712 +0100
+++ linux-2.6.11-lorenzo/fs/proc/array.c 2005-03-10 14:56:14.048836384 +0100
@@ -473,3 +473,13 @@ int proc_pid_statm(struct task_struct *t
return sprintf(buffer,"%d %d %d %d %d %d %d\n",
size, resident, shared, text, lib, data, 0);
}
+
+#ifdef CONFIG_PROC_IPADDR
+int proc_pid_ipaddr(struct task_struct *task, char * buffer)
+{
+ int len;
+
+ len = sprintf(buffer, "%u.%u.%u.%u\n", NIPQUAD(task->curr_ip));
+ return len;
+}
+#endif
diff -puN fs/Kconfig~task-curr_ip fs/Kconfig
--- linux-2.6.11/fs/Kconfig~task-curr_ip 2005-03-10 14:56:13.936853408 +0100
+++ linux-2.6.11-lorenzo/fs/Kconfig 2005-03-10 14:56:14.049836232 +0100
@@ -716,6 +716,22 @@ config PROC_FS
config PROC_KCORE
bool "/proc/kcore support" if !ARM
depends on PROC_FS && MMU
+
+config PROC_IPADDR
+ bool "/proc/<pid>/ipaddr support"
+ depends on PROC_FS && NET && INET
+ help
+ If you say Y here, a new entry will be added to each /proc/<pid>
+ directory that contains the IP address of the person using the task.
+ The IP is carried across local TCP and AF_UNIX stream sockets.
+ This information can be useful for IDS/IPSes to perform remote response
+ to a local attack. The entry is readable by only the owner of the
+ process (and root if he has CAP_DAC_OVERRIDE, which can be handled in
+ different manners depending on the used model, ie. RBAC and the engine
+ implementing it, ie. SELinux), and thus does not create privacy concerns.
+
+ This feature has been ported out of grSecurity from Brad Spengler by
+ [email protected].

config SYSFS
bool "sysfs file system support" if EMBEDDED
diff -puN net/socket.c~task-curr_ip net/socket.c
--- linux-2.6.11/net/socket.c~task-curr_ip 2005-03-10 14:56:13.938853104 +0100
+++ linux-2.6.11-lorenzo/net/socket.c 2005-03-10 14:56:14.050836080 +0100
@@ -114,6 +114,7 @@ static ssize_t sock_writev(struct file *
static ssize_t sock_sendpage(struct file *file, struct page *page,
int offset, size_t size, loff_t *ppos, int more);

+extern void proc_attach_curr_ip(const struct sock *sk);

/*
* Socket files have a set of 'special' operations as well as the generic file ones. These don't appear
@@ -1386,6 +1387,7 @@ asmlinkage long sys_accept(int fd, struc
goto out_release;

security_socket_post_accept(sock, newsock);
+ proc_attach_curr_ip(newsock->sk);

out_put:
sockfd_put(sock);
diff -puN include/linux/sched.h~task-curr_ip include/linux/sched.h
--- linux-2.6.11/include/linux/sched.h~task-curr_ip 2005-03-10 14:56:13.963849304 +0100
+++ linux-2.6.11-lorenzo/include/linux/sched.h 2005-03-10 14:56:14.051835928 +0100
@@ -685,6 +685,14 @@ struct task_struct {
struct mempolicy *mempolicy;
short il_next;
#endif
+#ifdef CONFIG_PROC_IPADDR
+ u32 curr_ip; /* per-task originating ip address */
+ u8 used_accept:1;
+ u32 net_saddr;
+ u32 net_daddr;
+ u16 net_sport;
+ u16 net_dport;
+#endif
};

static inline pid_t process_group(struct task_struct *tsk)
diff -puN net/ipv4/tcp_ipv4.c~task-curr_ip net/ipv4/tcp_ipv4.c
--- linux-2.6.11/net/ipv4/tcp_ipv4.c~task-curr_ip 2005-03-10 14:56:13.977847176 +0100
+++ linux-2.6.11-lorenzo/net/ipv4/tcp_ipv4.c 2005-03-10 14:56:14.052835776 +0100
@@ -82,6 +82,11 @@ int sysctl_tcp_low_latency;
/* Check TCP sequence numbers in ICMP packets. */
#define ICMP_MIN_LENGTH 8

+#ifdef CONFIG_PROC_IPADDR
+extern void net_add_to_task_ip_table(struct task_struct *p);
+extern void net_del_task_from_ip_table(struct task_struct *p);
+#endif
+
/* Socket used for sending RSTs */
static struct socket *tcp_socket;

@@ -717,6 +722,15 @@ ok:
__tcp_v4_hash(sk, 0);
}
spin_unlock(&head->lock);
+
+#ifdef CONFIG_PROC_IPADDR
+ net_del_task_from_ip_table(current);
+ current->net_saddr = inet_sk(sk)->rcv_saddr;
+ current->net_daddr = inet_sk(sk)->daddr;
+ current->net_sport = inet_sk(sk)->sport;
+ current->net_dport = inet_sk(sk)->dport;
+ net_add_to_task_ip_table(current);
+#endif

if (tw) {
tcp_tw_deschedule(tw);
diff -puN kernel/exit.c~task-curr_ip kernel/exit.c
--- linux-2.6.11/kernel/exit.c~task-curr_ip 2005-03-10 14:56:13.979846872 +0100
+++ linux-2.6.11-lorenzo/kernel/exit.c 2005-03-10 14:56:14.053835624 +0100
@@ -35,6 +35,10 @@
extern void sem_exit (void);
extern struct task_struct *child_reaper;

+#ifdef CONFIG_PROC_IPADDR
+extern void net_del_task_from_ip_table(struct task_struct *p);
+#endif
+
int getrusage(struct task_struct *, int, struct rusage __user *);

static void __unhash_process(struct task_struct *p)
@@ -811,6 +815,10 @@ fastcall NORET_TYPE void do_exit(long co
group_dead = atomic_dec_and_test(&tsk->signal->live);
if (group_dead)
acct_process(code);
+
+#ifdef CONFIG_PROC_IPADDR
+ net_del_task_from_ip_table(tsk);
+#endif
exit_mm(tsk);

exit_sem(tsk);
diff -puN /dev/null net/proc_ipaddr.c
--- /dev/null 2005-03-01 21:41:03.335473768 +0100
+++ linux-2.6.11-lorenzo/net/proc_ipaddr.c 2005-03-10 14:56:14.054835472 +0100
@@ -0,0 +1,166 @@
+/*
+ * Per-task originating IP address & /proc/$$/ipaddr entry support.
+ *
+ * Copyright (C) 2005 Lorenzo Hernandez Garcia-Hierro <[email protected]>
+ * Copyright (C) 2004, 2005 Brad Spengler <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/file.h>
+#include <linux/net.h>
+#include <linux/in.h>
+#include <linux/ip.h>
+#include <net/sock.h>
+
+#ifdef CONFIG_PROC_IPADDR
+#define net_conn_table_size 65521
+struct task_struct *net_conn_table[net_conn_table_size];
+struct task_struct *deleted_conn = (struct task_struct *)~0;
+spinlock_t net_conn_table_lock = SPIN_LOCK_UNLOCKED;
+
+static __inline__ int
+conn_hash(__u32 saddr, __u32 daddr, __u16 sport, __u16 dport, unsigned int size)
+{
+ return ((daddr + saddr + (sport << 8) + (dport << 16)) % size);
+}
+
+static __inline__ int
+conn_match(const struct task_struct *task, __u32 saddr, __u32 daddr,
+ __u16 sport, __u16 dport)
+{
+ if (unlikely(task != deleted_conn && task->net_saddr == saddr &&
+ task->net_daddr == daddr && task->net_sport == sport &&
+ task->net_dport == dport))
+ return 1;
+ else
+ return 0;
+}
+
+void net_add_to_task_ip_table(struct task_struct *task)
+{
+ unsigned int index;
+
+ if (unlikely(net_conn_table == NULL))
+ return;
+
+ if (!thread_group_leader(task))
+ task = task->group_leader;
+
+ index = conn_hash(task->net_saddr, task->net_daddr,
+ task->net_sport, task->net_dport,
+ net_conn_table_size);
+
+ spin_lock(&net_conn_table_lock);
+
+ while (net_conn_table[index] && net_conn_table[index] != deleted_conn) {
+ index = (index + 1) % net_conn_table_size;
+ }
+
+ net_conn_table[index] = task;
+
+ spin_unlock(&net_conn_table_lock);
+
+ return;
+}
+
+void net_del_task_from_ip_table_nolock(struct task_struct *task)
+{
+ unsigned int index;
+
+ if (unlikely(net_conn_table == NULL))
+ return;
+
+ if (!thread_group_leader(task))
+ task = task->group_leader;
+
+ index = conn_hash(task->net_saddr, task->net_daddr,
+ task->net_sport, task->net_dport,
+ net_conn_table_size);
+
+ while (net_conn_table[index] && !conn_match(net_conn_table[index],
+ task->net_saddr, task->net_daddr, task->net_sport,
+ task->net_dport)) {
+ index = (index + 1) % net_conn_table_size;
+ }
+
+ if (net_conn_table[index]) {
+ if (net_conn_table[(index + 1) % net_conn_table_size])
+ net_conn_table[index] = deleted_conn;
+ else
+ net_conn_table[index] = NULL;
+ }
+
+ return;
+}
+
+struct task_struct * net_lookup_task_ip_table(__u32 saddr, __u32 daddr,
+ __u16 sport, __u16 dport)
+{
+ unsigned int index;
+
+ if (unlikely(net_conn_table == NULL))
+ return NULL;
+
+ index = conn_hash(saddr, daddr, sport, dport, net_conn_table_size);
+
+ while (net_conn_table[index] && !conn_match(net_conn_table[index],
+ saddr, daddr, sport, dport)) {
+ index = (index + 1) % net_conn_table_size;
+ }
+
+ return net_conn_table[index];
+}
+#endif
+
+void net_del_task_from_ip_table(struct task_struct *task)
+{
+#ifdef CONFIG_PROC_IPADDR
+ spin_lock(&net_conn_table_lock);
+ if (!thread_group_leader(task))
+ net_del_task_from_ip_table_nolock(task->group_leader);
+ else
+ net_del_task_from_ip_table_nolock(task);
+ spin_unlock(&net_conn_table_lock);
+#endif
+ return;
+}
+
+void
+proc_attach_curr_ip(const struct sock *sk)
+{
+#ifdef CONFIG_PROC_IPADDR
+ struct task_struct *p;
+ struct task_struct *set;
+ const struct inet_sock *inet = inet_sk(sk);
+
+ if (unlikely(sk->sk_protocol != IPPROTO_TCP))
+ return;
+
+ set = current;
+ if (!thread_group_leader(set))
+ set = set->group_leader;
+
+ spin_lock(&net_conn_table_lock);
+ p = net_lookup_task_ip_table(inet->daddr, inet->rcv_saddr,
+ inet->dport, inet->sport);
+ if (unlikely(p != NULL)) {
+ set->curr_ip = p->curr_ip;
+ set->used_accept = 1;
+ net_del_task_from_ip_table_nolock(p);
+ spin_unlock(&net_conn_table_lock);
+ return;
+ }
+ spin_unlock(&net_conn_table_lock);
+
+ set->curr_ip = inet->daddr;
+ set->used_accept = 1;
+#endif
+ return;
+}
diff -puN net/Makefile~task-curr_ip net/Makefile
--- linux-2.6.11/net/Makefile~task-curr_ip 2005-03-10 14:56:13.981846568 +0100
+++ linux-2.6.11-lorenzo/net/Makefile 2005-03-10 14:56:14.054835472 +0100
@@ -46,3 +46,7 @@ obj-$(CONFIG_IP_SCTP) += sctp/
ifeq ($(CONFIG_NET),y)
obj-$(CONFIG_SYSCTL) += sysctl_net.o
endif
+
+ifeq ($(CONFIG_NET),y)
+obj-$(CONFIG_PROC_IPADDR) += proc_ipaddr.o
+endif

Cheers,
--
Lorenzo Hern?ndez Garc?a-Hierro <[email protected]>
[1024D/6F2B2DEC] & [2048g/9AE91A22][http://tuxedo-es.org]


Attachments:
signature.asc (189.00 B)
This is a digitally signed message part

2005-03-10 14:26:30

by Arjan van de Ven

[permalink] [raw]
Subject: Re: [patch 1/1] /proc/$$/ipaddr and per-task networking bits

On Thu, 2005-03-10 at 15:16 +0100, Lorenzo Hernández García-Hierro
wrote:
> Ported feature from grSecurity that makes possible to add an ipaddr
> entry in each /proc/<pid> (/proc/<pid>/ipaddr), where the task originating
> IP address is stored, and subsequently made available (readable) by the process
> itself and also the root user with CAP_DAC_OVERRIDE capability (that can be managed
> by specific security models implementations like SELinux).
> Available also at http://pearls.tuxedo-es.org/patches/task-curr_ip.patch


a few questions
1) Why is this a config option; if it's useful it should just be always
on really
2) Can you explain briefly what this is useful for?
3) How does this work for existing stuff if, say, your dhcp lease
changes and your machine no longer owns a certain IP, what will happen
to the tasks?
4) if a machine has multiple IPs.. which one is chosen ?


2005-03-10 15:20:56

by YOSHIFUJI Hideaki

[permalink] [raw]
Subject: Re: [patch 1/1] /proc/$$/ipaddr and per-task networking bits

In article <[email protected]> (at Thu, 10 Mar 2005 15:16:42 +0100), Lorenzo Hern?ndez Garc?a-Hierro <[email protected]> says:

> Ported feature from grSecurity that makes possible to add an ipaddr
> entry in each /proc/<pid> (/proc/<pid>/ipaddr), where the task originating
> IP address is stored, and subsequently made available (readable) by the process
> itself and also the root user with CAP_DAC_OVERRIDE capability (that can be managed
> by specific security models implementations like SELinux).
> Available also at http://pearls.tuxedo-es.org/patches/task-curr_ip.patch

Please don't.

You already can get this information via procfs; e.g. lsof does,
It does support IPv6 as well.

--yoshfuji

Subject: Re: [patch 1/1] /proc/$$/ipaddr and per-task networking bits

El jue, 10-03-2005 a las 15:26 +0100, Arjan van de Ven escribi?:
> a few questions
> 1) Why is this a config option; if it's useful it should just be always
> on really

Just to be removed if it applies for mainline.

> 2) Can you explain briefly what this is useful for?

For keeping track on the "originating ip address of the
task/process" (the ipv4 address of the user that started the
task/process).
It adds an ipaddr entry if available for each /proc/<pid> entry, among
the API changes.

Example:
root@dg:/usr/src# cat /proc/5907/ipaddr
192.128.102.93

> 3) How does this work for existing stuff if, say, your dhcp lease
> changes and your machine no longer owns a certain IP, what will happen
> to the tasks?
> 4) if a machine has multiple IPs.. which one is chosen ?

The patch has nothing to do with this, as it's objective is different.
See http://lkml.org/lkml/2005/3/10/108 and
http://pearls.tuxedo-es.org/patches/selinux-avc_audit-log-curr_ip.patch
if you want useful and real examples on how it works and helps.

Cheers,
--
Lorenzo Hern?ndez Garc?a-Hierro <[email protected]>
[1024D/6F2B2DEC] & [2048g/9AE91A22][http://tuxedo-es.org]


Attachments:
signature.asc (189.00 B)
This is a digitally signed message part

2005-03-10 15:40:43

by Arjan van de Ven

[permalink] [raw]
Subject: Re: [patch 1/1] /proc/$$/ipaddr and per-task networking bits

On Thu, 2005-03-10 at 16:28 +0100, Lorenzo Hernández García-Hierro
wrote:

> > 2) Can you explain briefly what this is useful for?
>
> For keeping track on the "originating ip address of the
> task/process" (the ipv4 address of the user that started the
> task/process).

but.... tasks don't have an IP address. Hosts do. Hosts can have
multiple IP addresses. Both ipv4 and ipv6. Users don't have IP
addresses either (they do have user IDs so that link is clear).
I think I'm missing something big here. What does it *mean* for a task
to have an IP address. Once that is clear maybe I can start to
understand the rest, but until the meaning of "task has an IP address"
is better explained/more clear I think I'm stuck. (and no the output in
a log isn't a meaning, it's only a result)


Subject: Re: [patch 1/1] /proc/$$/ipaddr and per-task networking bits

El jue, 10-03-2005 a las 16:38 +0100, Arjan van de Ven escribi?:
> but.... tasks don't have an IP address. Hosts do. Hosts can have
> multiple IP addresses. Both ipv4 and ipv6. Users don't have IP
> addresses either (they do have user IDs so that link is clear).
> I think I'm missing something big here. What does it *mean* for a task
> to have an IP address. Once that is clear maybe I can start to
> understand the rest, but until the meaning of "task has an IP address"
> is better explained/more clear I think I'm stuck. (and no the output in
> a log isn't a meaning, it's only a result)

I think I've explained it well, more concretely, it tries to fill the
ipaddr member of the task_struct structure with the IP address
associated to the user running @current task/process,if available.
Then, it will be attached within the proc fs in an entry called ipaddr,
under the proper pid directory.

The whole thing is almost self-explaining by just looking at the code.

Cheers,
--
Lorenzo Hern?ndez Garc?a-Hierro <[email protected]>
[1024D/6F2B2DEC] & [2048g/9AE91A22][http://tuxedo-es.org]


Attachments:
signature.asc (189.00 B)
This is a digitally signed message part

2005-03-10 16:11:40

by Arjan van de Ven

[permalink] [raw]
Subject: Re: [patch 1/1] /proc/$$/ipaddr and per-task networking bits

On Thu, 2005-03-10 at 17:00 +0100, Lorenzo Hernández García-Hierro
wrote: it tries to fill the
> ipaddr member of the task_struct structure with the IP address
> associated to the user running @current task/process,if available.

but... a use doesn't hane an IP. a host does.


2005-03-10 16:25:40

by Paul Komkoff

[permalink] [raw]
Subject: Re: [patch 1/1] /proc/$$/ipaddr and per-task networking bits

Replying to Arjan van de Ven:
> On Thu, 2005-03-10 at 17:00 +0100, Lorenzo Hernández García-Hierro
> wrote: it tries to fill the
> > ipaddr member of the task_struct structure with the IP address
> > associated to the user running @current task/process,if available.
>
> but... a use doesn't hane an IP. a host does.

I don't get it too
With multihomed setup same process I have can send and receive on many
addresses simultaneously. So single ip_addr cannot describe this
situation.

--
Paul P 'Stingray' Komkoff Jr // http://stingr.net/key <- my pgp key
This message represents the official view of the voices in my head

2005-03-10 16:41:43

by Joost Remijn

[permalink] [raw]
Subject: Re: [patch 1/1] /proc/$$/ipaddr and per-task networking bits

On Thu, 2005-03-10 at 17:08 +0100, Arjan van de Ven wrote:
> On Thu, 2005-03-10 at 17:00 +0100, Lorenzo Hernández García-Hierro
> wrote: it tries to fill the
> > ipaddr member of the task_struct structure with the IP address
> > associated to the user running @current task/process,if available.
>
> but... a use doesn't hane an IP. a host does.

I'm not sure i understand but i've just tried to read the code and it
looks like the IP address is the address of the other end of a socket.

This address is set when a process does accept(). So this user IP we are
talking about would be the remote users host IP (or gateway in case of
NAT).

I don't think i fully understand the code but it looks like it only
holds the remote IP address of the last accept()-ed connection.

Joost Remijn


Attachments:
signature.asc (189.00 B)
This is a digitally signed message part

2005-03-11 12:35:39

by Coywolf Qi Hunt

[permalink] [raw]
Subject: Re: [patch 1/1] /proc/$$/ipaddr and per-task networking bits

On Thu, 10 Mar 2005 17:08:55 +0100, Arjan van de Ven
<[email protected]> wrote:
> On Thu, 2005-03-10 at 17:00 +0100, Lorenzo Hern?ndez Garc?a-Hierro
> wrote: it tries to fill the
> > ipaddr member of the task_struct structure with the IP address
> > associated to the user running @current task/process,if available.
>
> but... a use doesn't hane an IP. a host does.
>

The patch is useful in this situation. Suppose node E has two IPs, IP1
and IP2. IP1 is the default and got blocked from some network C.

Now if one try to visit C from E, one has to to use IP2.

--
Coywolf Qi Hunt
Homepage http://sosdg.org/~coywolf/