diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c index 09a1f92..52fd702 100644 --- a/fs/proc/proc_sysctl.c +++ b/fs/proc/proc_sysctl.c @@ -129,6 +129,11 @@ out: return err; } + +typedef int proc_handler_extended(struct ctl_table *ctl, int write, + void __user *buffer, size_t *lenp, loff_t *ppos, + struct file *filp); + static ssize_t proc_sys_call_handler(struct file *filp, void __user *buf, size_t count, loff_t *ppos, int write) { @@ -137,6 +142,7 @@ static ssize_t proc_sys_call_handler(struct file *filp, void __user *buf, struct ctl_table *table = PROC_I(inode)->sysctl_entry; ssize_t error; size_t res; + proc_handler_extended *phx = (proc_handler_extended *) table->proc_handler; if (IS_ERR(head)) return PTR_ERR(head); @@ -156,7 +162,15 @@ static ssize_t proc_sys_call_handler(struct file *filp, void __user *buf, /* careful: calling conventions are nasty here */ res = count; - error = table->proc_handler(table, write, buf, &res, ppos); + /* Most handlers only use the first 5 arguments (without @filp). + * Changing all is too much of work, as, at the time of writting only + * the devinet.c proc_handlers know about and use the @filp. + * + * This is just a HACK for now, I did this this way to not + * waste time changing all the handlers, in the final version + * I'll change all the handlers if there's not other solution. + */ + error = phx(table, write, buf, &res, ppos, filp); if (!error) error = res; out: diff --git a/include/linux/inetdevice.h b/include/linux/inetdevice.h index ae8fdc5..caf06b3 100644 --- a/include/linux/inetdevice.h +++ b/include/linux/inetdevice.h @@ -43,8 +43,18 @@ enum #define IPV4_DEVCONF_MAX (__IPV4_DEVCONF_MAX - 1) + +struct devinet_sysctl { + /* dev_name holds a copy of dev_name, because '.procname' is + * regarded as const by sysctl and we wouldn't want anyone to + * change it under our feet (see SIOCSIFNAME). */ + char *dev_name; + struct ctl_table_header *sysctl_header; +}; + + struct ipv4_devconf { - void *sysctl; + struct devinet_sysctl devinet_sysctl; int data[IPV4_DEVCONF_MAX]; DECLARE_BITMAP(state, IPV4_DEVCONF_MAX); }; diff --git a/net/ipv4/devinet.c b/net/ipv4/devinet.c index 748cb5b..b42425d 100644 --- a/net/ipv4/devinet.c +++ b/net/ipv4/devinet.c @@ -147,7 +147,7 @@ void in_dev_finish_destroy(struct in_device *idev) } EXPORT_SYMBOL(in_dev_finish_destroy); -static struct in_device *inetdev_init(struct net_device *dev) +struct in_device *inetdev_init(struct net_device *dev) { struct in_device *in_dev; @@ -158,7 +158,8 @@ static struct in_device *inetdev_init(struct net_device *dev) goto out; memcpy(&in_dev->cnf, dev_net(dev)->ipv4.devconf_dflt, sizeof(in_dev->cnf)); - in_dev->cnf.sysctl = NULL; + in_dev->cnf.devinet_sysctl.dev_name = NULL; + in_dev->cnf.devinet_sysctl.sysctl_header = NULL; in_dev->dev = dev; in_dev->arp_parms = neigh_parms_alloc(dev, &arp_tbl); if (!in_dev->arp_parms) @@ -1375,6 +1376,67 @@ static void inet_forward_change(struct net *net) } } + + +static int devinet_conf_handler(ctl_table *ctl, int write, + void __user *buffer, + size_t *lenp, loff_t *ppos, + struct file *filp, + proc_handler *proc_handler) +{ + /* The path to this file is of the form: + * $PROC_MOUNT/sys/net/ipv4/conf/$DEVNAME/$CTL + * + * The array of 'struct ctl_table' of devinet entries is + * shared between all ipv4 network devices and the 'data' + * field of each structure only hold the offset into the + * 'data' field of 'struct ipv4_devconf'. + * + * To find the propper location of the data that must be + * accessed by this handler we need the device name and the + * network namespace in which it belongs. + */ + + /* We store the network namespace in the parent table's ->extra2 */ + struct inode *parent_inode = filp->f_path.dentry->d_parent->d_inode; + struct ctl_table *parent_table = PROC_I(parent_inode)->sysctl_entry; + struct net *net = parent_table->extra2; + + const char *dev_name = filp->f_path.dentry->d_parent->d_name.name; + struct ctl_table tmp_ctl; + struct net_device *dev = NULL; + struct in_device *in_dev = NULL; + struct ipv4_devconf *cnf; + int ret; + + if (strcmp(dev_name, "all") == 0) { + cnf = net->ipv4.devconf_all; + } else if (strcmp(dev_name, "default") == 0) { + cnf = net->ipv4.devconf_dflt; + } else { + /* the device could have been renamed (SIOCSIFADDR) or + * deleted since we started accessing it's proc sysctl */ + dev = dev_get_by_name(net, dev_name); + if (dev == NULL) + return -ENOENT; + in_dev = in_dev_get(dev); + cnf = &in_dev->cnf; + } + + tmp_ctl = *ctl; + tmp_ctl.data += (char *)cnf - (char *)&ipv4_devconf; + tmp_ctl.extra1 = cnf; + tmp_ctl.extra2 = net; + + ret = proc_handler(&tmp_ctl, write, buffer, lenp, ppos); + + if (in_dev) + in_dev_put(in_dev); + if (dev) + dev_put(dev); + return ret; +} + static int devinet_conf_proc(ctl_table *ctl, int write, void __user *buffer, size_t *lenp, loff_t *ppos) @@ -1445,6 +1507,33 @@ static int ipv4_doint_and_flush(ctl_table *ctl, int write, return ret; } +static int devinet_conf_proc__(ctl_table *ctl, int write, + void __user *buffer, + size_t *lenp, loff_t *ppos, + struct file *filp) +{ + return devinet_conf_handler(ctl, write, buffer, lenp, ppos, filp, + devinet_conf_proc); +} + +static int devinet_sysctl_forward__(ctl_table *ctl, int write, + void __user *buffer, + size_t *lenp, loff_t *ppos, + struct file *filp) +{ + return devinet_conf_handler(ctl, write, buffer, lenp, ppos, filp, + devinet_sysctl_forward); +} + +static int ipv4_doint_and_flush__(ctl_table *ctl, int write, + void __user *buffer, + size_t *lenp, loff_t *ppos, + struct file *filp) +{ + return devinet_conf_handler(ctl, write, buffer, lenp, ppos, filp, + ipv4_doint_and_flush); +} + #define DEVINET_SYSCTL_ENTRY(attr, name, mval, proc) \ { \ .procname = name, \ @@ -1452,67 +1541,60 @@ static int ipv4_doint_and_flush(ctl_table *ctl, int write, IPV4_DEVCONF_ ## attr - 1, \ .maxlen = sizeof(int), \ .mode = mval, \ - .proc_handler = proc, \ - .extra1 = &ipv4_devconf, \ + .proc_handler = (proc_handler *) proc, \ } #define DEVINET_SYSCTL_RW_ENTRY(attr, name) \ - DEVINET_SYSCTL_ENTRY(attr, name, 0644, devinet_conf_proc) + DEVINET_SYSCTL_ENTRY(attr, name, 0644, devinet_conf_proc__) #define DEVINET_SYSCTL_RO_ENTRY(attr, name) \ - DEVINET_SYSCTL_ENTRY(attr, name, 0444, devinet_conf_proc) + DEVINET_SYSCTL_ENTRY(attr, name, 0444, devinet_conf_proc__) #define DEVINET_SYSCTL_COMPLEX_ENTRY(attr, name, proc) \ DEVINET_SYSCTL_ENTRY(attr, name, 0644, proc) #define DEVINET_SYSCTL_FLUSHING_ENTRY(attr, name) \ - DEVINET_SYSCTL_COMPLEX_ENTRY(attr, name, ipv4_doint_and_flush) - -static struct devinet_sysctl_table { - struct ctl_table_header *sysctl_header; - struct ctl_table devinet_vars[__IPV4_DEVCONF_MAX]; - char *dev_name; -} devinet_sysctl = { - .devinet_vars = { - DEVINET_SYSCTL_COMPLEX_ENTRY(FORWARDING, "forwarding", - devinet_sysctl_forward), - DEVINET_SYSCTL_RO_ENTRY(MC_FORWARDING, "mc_forwarding"), - - DEVINET_SYSCTL_RW_ENTRY(ACCEPT_REDIRECTS, "accept_redirects"), - DEVINET_SYSCTL_RW_ENTRY(SECURE_REDIRECTS, "secure_redirects"), - DEVINET_SYSCTL_RW_ENTRY(SHARED_MEDIA, "shared_media"), - DEVINET_SYSCTL_RW_ENTRY(RP_FILTER, "rp_filter"), - DEVINET_SYSCTL_RW_ENTRY(SEND_REDIRECTS, "send_redirects"), - DEVINET_SYSCTL_RW_ENTRY(ACCEPT_SOURCE_ROUTE, - "accept_source_route"), - DEVINET_SYSCTL_RW_ENTRY(ACCEPT_LOCAL, "accept_local"), - DEVINET_SYSCTL_RW_ENTRY(SRC_VMARK, "src_valid_mark"), - DEVINET_SYSCTL_RW_ENTRY(PROXY_ARP, "proxy_arp"), - DEVINET_SYSCTL_RW_ENTRY(MEDIUM_ID, "medium_id"), - DEVINET_SYSCTL_RW_ENTRY(BOOTP_RELAY, "bootp_relay"), - DEVINET_SYSCTL_RW_ENTRY(LOG_MARTIANS, "log_martians"), - DEVINET_SYSCTL_RW_ENTRY(TAG, "tag"), - DEVINET_SYSCTL_RW_ENTRY(ARPFILTER, "arp_filter"), - DEVINET_SYSCTL_RW_ENTRY(ARP_ANNOUNCE, "arp_announce"), - DEVINET_SYSCTL_RW_ENTRY(ARP_IGNORE, "arp_ignore"), - DEVINET_SYSCTL_RW_ENTRY(ARP_ACCEPT, "arp_accept"), - DEVINET_SYSCTL_RW_ENTRY(ARP_NOTIFY, "arp_notify"), - DEVINET_SYSCTL_RW_ENTRY(PROXY_ARP_PVLAN, "proxy_arp_pvlan"), - - DEVINET_SYSCTL_FLUSHING_ENTRY(NOXFRM, "disable_xfrm"), - DEVINET_SYSCTL_FLUSHING_ENTRY(NOPOLICY, "disable_policy"), - DEVINET_SYSCTL_FLUSHING_ENTRY(FORCE_IGMP_VERSION, - "force_igmp_version"), - DEVINET_SYSCTL_FLUSHING_ENTRY(PROMOTE_SECONDARIES, - "promote_secondaries"), - }, + DEVINET_SYSCTL_COMPLEX_ENTRY(attr, name, ipv4_doint_and_flush__) + +struct ctl_table ipv4_devinet_sysctl_table[__IPV4_DEVCONF_MAX] = { + DEVINET_SYSCTL_COMPLEX_ENTRY(FORWARDING, "forwarding", + devinet_sysctl_forward__), + DEVINET_SYSCTL_RO_ENTRY(MC_FORWARDING, "mc_forwarding"), + + DEVINET_SYSCTL_RW_ENTRY(ACCEPT_REDIRECTS, "accept_redirects"), + DEVINET_SYSCTL_RW_ENTRY(SECURE_REDIRECTS, "secure_redirects"), + DEVINET_SYSCTL_RW_ENTRY(SHARED_MEDIA, "shared_media"), + DEVINET_SYSCTL_RW_ENTRY(RP_FILTER, "rp_filter"), + DEVINET_SYSCTL_RW_ENTRY(SEND_REDIRECTS, "send_redirects"), + DEVINET_SYSCTL_RW_ENTRY(ACCEPT_SOURCE_ROUTE, + "accept_source_route"), + DEVINET_SYSCTL_RW_ENTRY(ACCEPT_LOCAL, "accept_local"), + DEVINET_SYSCTL_RW_ENTRY(SRC_VMARK, "src_valid_mark"), + DEVINET_SYSCTL_RW_ENTRY(PROXY_ARP, "proxy_arp"), + DEVINET_SYSCTL_RW_ENTRY(MEDIUM_ID, "medium_id"), + DEVINET_SYSCTL_RW_ENTRY(BOOTP_RELAY, "bootp_relay"), + DEVINET_SYSCTL_RW_ENTRY(LOG_MARTIANS, "log_martians"), + DEVINET_SYSCTL_RW_ENTRY(TAG, "tag"), + DEVINET_SYSCTL_RW_ENTRY(ARPFILTER, "arp_filter"), + DEVINET_SYSCTL_RW_ENTRY(ARP_ANNOUNCE, "arp_announce"), + DEVINET_SYSCTL_RW_ENTRY(ARP_IGNORE, "arp_ignore"), + DEVINET_SYSCTL_RW_ENTRY(ARP_ACCEPT, "arp_accept"), + DEVINET_SYSCTL_RW_ENTRY(ARP_NOTIFY, "arp_notify"), + DEVINET_SYSCTL_RW_ENTRY(PROXY_ARP_PVLAN, "proxy_arp_pvlan"), + + DEVINET_SYSCTL_FLUSHING_ENTRY(NOXFRM, "disable_xfrm"), + DEVINET_SYSCTL_FLUSHING_ENTRY(NOPOLICY, "disable_policy"), + DEVINET_SYSCTL_FLUSHING_ENTRY(FORCE_IGMP_VERSION, + "force_igmp_version"), + DEVINET_SYSCTL_FLUSHING_ENTRY(PROMOTE_SECONDARIES, + "promote_secondaries"), + { } }; static int __devinet_sysctl_register(struct net *net, char *dev_name, - struct ipv4_devconf *p) + struct ipv4_devconf *cnf) { - int i; - struct devinet_sysctl_table *t; + struct devinet_sysctl *t = &cnf->devinet_sysctl; #define DEVINET_CTL_PATH_DEV 3 @@ -1524,16 +1606,6 @@ static int __devinet_sysctl_register(struct net *net, char *dev_name, { }, }; - t = kmemdup(&devinet_sysctl, sizeof(*t), GFP_KERNEL); - if (!t) - goto out; - - for (i = 0; i < ARRAY_SIZE(t->devinet_vars) - 1; i++) { - t->devinet_vars[i].data += (char *)p - (char *)&ipv4_devconf; - t->devinet_vars[i].extra1 = p; - t->devinet_vars[i].extra2 = net; - } - /* * Make a copy of dev_name, because '.procname' is regarded as const * by sysctl and we wouldn't want anyone to change it under our feet @@ -1541,37 +1613,32 @@ static int __devinet_sysctl_register(struct net *net, char *dev_name, */ t->dev_name = kstrdup(dev_name, GFP_KERNEL); if (!t->dev_name) - goto free; + goto out; devinet_ctl_path[DEVINET_CTL_PATH_DEV].procname = t->dev_name; - t->sysctl_header = register_net_sysctl_table(net, devinet_ctl_path, - t->devinet_vars); + t->sysctl_header = register_net_sysctl_table_pathdata(net, + devinet_ctl_path, ipv4_devinet_sysctl_table, net); if (!t->sysctl_header) goto free_procname; - p->sysctl = t; return 0; free_procname: kfree(t->dev_name); -free: - kfree(t); out: return -ENOBUFS; } static void __devinet_sysctl_unregister(struct ipv4_devconf *cnf) { - struct devinet_sysctl_table *t = cnf->sysctl; + struct devinet_sysctl *t = &cnf->devinet_sysctl; if (t == NULL) return; - cnf->sysctl = NULL; unregister_sysctl_table(t->sysctl_header); kfree(t->dev_name); - kfree(t); } static void devinet_sysctl_register(struct in_device *idev)