From 2c3e49def40b41677182fc9243128f11c5e148e9 Mon Sep 17 00:00:00 2001 From: kadlec Date: Mon, 9 Feb 2004 13:47:01 +0000 Subject: [PATCH 1/1] Userspace part of sets: ipset added (JK) --- ChangeLog | 6 + ChangeLog.ippool | 59 ++ Makefile | 48 + ipset | Bin 0 -> 97124 bytes ipset.8 | 289 ++++++ ipset.c | 2326 ++++++++++++++++++++++++++++++++++++++++++++++ ipset.h | 203 ++++ ipset_iphash.c | 477 ++++++++++ ipset_ipmap.c | 384 ++++++++ ipset_macipmap.c | 387 ++++++++ ipset_portmap.c | 293 ++++++ libipt_set.h | 132 +++ 12 files changed, 4604 insertions(+) create mode 100644 ChangeLog create mode 100644 ChangeLog.ippool create mode 100644 Makefile create mode 100755 ipset create mode 100644 ipset.8 create mode 100644 ipset.c create mode 100644 ipset.h create mode 100644 ipset_iphash.c create mode 100644 ipset_ipmap.c create mode 100644 ipset_macipmap.c create mode 100644 ipset_portmap.c create mode 100644 libipt_set.h diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..c6e20fa --- /dev/null +++ b/ChangeLog @@ -0,0 +1,6 @@ +1.0 + - Renamed to ipset (Jozsef Kadlecsik) + - Rewritten to support child pools (Jozsef Kadlecsik) + - portmap, iphash pool support added (Jozsef Kadlecsik) + - too much other mods here and there to list... (Jozsef Kadlecsik) + diff --git a/ChangeLog.ippool b/ChangeLog.ippool new file mode 100644 index 0000000..669c304 --- /dev/null +++ b/ChangeLog.ippool @@ -0,0 +1,59 @@ +Original changelog as ippool: + +0.3.2b +- Fixed missing kfree(pool) (Martin Josefsson) + +0.3.2a +- Added libipt_pool.c and libipt_POOL.c (Martin Josefsson) + + +0.3.2 +- Passes pointers to skb's around instead of ip's in the (Martin Josefsson) + kernel modules. +- Added a new pooltype, macipmap, which matches ip's (Martin Josefsson) + against macaddresses. +- Cleaned up a lot of typedefs. (Martin Josefsson) +- Fixed an unlocking of the wrong lock. (Martin Josefsson) +- Fixed a refcount bug when allocated memory was too (Martin Josefsson) + small. +- Fixed a free() of unallocated memory. (Martin Josefsson) +- Switched from kmalloc/kfree to vmalloc/vfree for (Martin Josefsson) + pool-listings/additions. + + +0.3.1 +- Changed the API between userspace modules and base. (Joakim Axelsson) + Moved the memberdata pointer to module self. + As a result of this Protocolversion is increased to 4. +- Fixed problems with crashing null-pooltype (Joakim Axelsson) +- Fixed problems with compiling warnings (Joakim Axelsson) + in null pooltype. + + +0.3.0: +- Changed the listing to use getsockopt. (Joakim Axelsson) + /proc is left for debuging purpose. + This is a mayor change. + Protocolversion is increased to 3 +- Added support for --quiet (Joakim Axelsson) +- Added support for --sorted (Joakim Axelsson) +- Added support for --numeric (Joakim Axelsson) +- Added support for --exact (Joakim Axelsson) +- Added -Z (Zero) which zero's the counter (Joakim Axelsson) + on one or all pools. +- Added support for --debug that prints all debug-messages (Joakim Axelsosn) + in userspace. Need to be compiled with + IP_POOL_DEBUG tho. +- Added null pooltype. For demostration and (Joakim Axelsson) + pooltype skeleton mostly +- Fixed bug with possibly renaming to an already (Joakim Axelsson) + existing pool. +- Change error to OTHER_PROBLEM on add and del IP. (Joakim Axelsson) + +0.2.1-0.2.3 +- Better handling of references (Patrick Schaaf) +- Various bugfixes (Patrick Schaaf) +- Cleaning up the code in kernelspace (Patrick Schaaf) + +0.2.0: +- Rewrote the entrie system. Modulized it. (Joakim Axelsson) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..23302b0 --- /dev/null +++ b/Makefile @@ -0,0 +1,48 @@ +#!/usr/bin/make + +IPSET_VERSION:=v1.0 +IPSET_LIB_DIR:=$(DESTDIR)$(LIBDIR)/ipset +#IPSET_LIB_DIR:=. +#CFLAGS:=-I$(KERNEL_DIR)/include + +SETTYPES:=ipmap portmap macipmap iphash + +EXTRAS+=$(shell [ -f $(KERNEL_DIR)/include/linux/netfilter_ipv4/ip_set.h ] && echo ipset/ipset) +EXTRAS+=$(foreach T, $(SETTYPES),ipset/libipset_$(T).so) +EXTRA_INSTALLS+=$(DESTDIR)$(BINDIR)/ipset $(DESTDIR)$(MANDIR)/man8/ipset.8 +EXTRA_INSTALLS+=$(foreach T, $(SETTYPES), $(DESTDIR)$(LIBDIR)/ipset/libipset_$(T).so) + +ifndef TOPLEVEL_INCLUDED +local: + cd .. && $(MAKE) $(KERN_TARGETS) $(SHARED_LIBS) $(EXTRAS) + +else +EXTRA_DEPENDS+=$(shell [ -f $(KERNEL_DIR)/include/linux/netfilter_ipv4/ip_set.h ] && echo "") +CFLAGS+=-DIPSET_VERSION=$(IPSET_VERSION) -DIPSET_LIB_DIR=\"$(IPSET_LIB_DIR)\" + +#The ipset(8) self +ipset/ipset.o: ipset/ipset.c + $(CC) $(CFLAGS) -g -c -o $@ $< + +ipset/ipset: ipset/ipset.o + $(CC) $(CFLAGS) -ldl -rdynamic -o $@ $^ + +#Pooltypes +ipset/ipset_%.o: ipset/ipset_%.c + $(CC) $(CFLAGS) -c -o $@ $< + +ipset/libipset_%.so: ipset/ipset_%.o + $(LD) -shared -o $@ $< + +$(DESTDIR)$(LIBDIR)/ipset/libipset_%.so: ipset/libipset_%.so + @[ -d $(DESTDIR)$(LIBDIR)/ipset ] || mkdir -p $(DESTDIR)$(LIBDIR)/ipset + cp $< $@ + +$(DESTDIR)$(BINDIR)/ipset: ipset/ipset + @[ -d $(DESTDIR)$(BINDIR) ] || mkdir -p $(DESTDIR)$(BINDIR) + cp $< $@ + +$(DESTDIR)$(MANDIR)/man8/ipset.8: ipset/ipset.8 + @[ -d $(DESTDIR)$(MANDIR)/man8 ] || mkdir -p $(DESTDIR)$(MANDIR)/man8 + cp $< $@ +endif diff --git a/ipset b/ipset new file mode 100755 index 0000000000000000000000000000000000000000..30a373c0e08f613b1df35bff69ed3cf9fb9a85e5 GIT binary patch literal 97124 zcmd>ndwdi{7H-W<55^EkKu}bWQGPml1%n-f4}?JE$#GI zRi~;>ojP^uRCQ1Di=0{0JRXlw!7F@%Vu72Dq*1sZUM%xQiGb)KhKW=8E(GO`ssRnI z24K4UxC~rAT&;8=kFH~Y>7qQUP1Xp2Ji4~75`r!&FVbXw1L6%m{7H1}Muh`WxQCtZ z#l3wEt|WgFU3(Fyi}I-rx{}aH(t0C_t^ne6sk}4GN-sOJtT>~rv^>0KWOc>Jvt%Bc zX5OaH$p;dG+M$cWR9tzudf}pam>1kBe;zIhW93a;fcv?)M&O!_YY48hah-ung()@| zjQen07uxaja6cB;EISU?#AICO+i|*O;hKai7uQ@|#M^1O=HQ}m3a;t6j;090I9!7m zL5{eeiK`E;8MtU%CDX$y=Ad{gKk!P9?oM3Rl36(=7?t>9|gy2*SzoCPv!#ez=de@c`UU#C4nUTa6^r395f8;scPc>ZggZ6>}Ha z7_0B%r0>I!|9hrK8^{l!J(73ExZ``ujQV zZOCunxDbybUmyQJAl|UwNSbfczlk`>15wtGyV1_W$fvnR^__xy(k3IRKj^5vO~@aB z_)nZK#NUvv*M9*fAgvY176FXcC5R6Ly^`;Vpx*+0qx`s-N_72IAZ8 z{{9{DMTk?sDWB$%iTDgVJ{oju5P!&~zZ&|C^D7PsPxaEM}(V0lTXqB2xaR#CoO z6rh%>P(@imX?dums=TmFEJJp+SXr`?idM=JATO+1E|6nZREI8GTUcCNC6I_*QC(4V zc}a-!%1cxh>Z~dQv1~x$FVz5SXw#Q&) zp=4h6IZ=;SEwh`eEUc<7DJZH~xw5dl7!Fy9C7MQC=X(c)d z!IgljU|DH-X+dFCRpHu#5=hKupr|^`IV4j8#Vk>1M{&tz;pNPFL8xl2SyEPMH&6;t zRk0kSQNV(bjjSvzLx0g?K?p+((JXhUR+Us$lNe+&@(aqtD=#alvg=z}Sbe!{M=DDP zPFY8FUeO82vf#4nYAHKlO42)4Qd}4+6a~{~T{va(tbz-tP0P!fUoe02lvz0iP8T3N zZWp}{t*tC6P%`gWoot;X>h=mciR$gfW#8?gnLcagl&J-0jm#1XvD4?l(ylIv+_(oh zlt=$5EK}TcnJe=k#`Q$X>%z3L}I&7X=yLYD}o(LLp+V+ zKCu#U3e-0FgOo=>=6jv|8rZ5HxzdLF;dIhk?GMN`+JCaN#r~<%hWfEckZtqBskdO` zj{-x7=niM#Fx|0+w$dF=X&c?)sJGLd9AK0?3dYc&I~>>~y2Ie5&>hZt8r}N|(U0y} zECbUzhqAl=ik7oj^G&`i1yhdre`Iy{!{BjD@PJxhpex}Syp z0NvpL&!GDl>__N67CtH6VZ`Ut9nR1qy2IzVgznfdETua(C&hHn#+pg@DKOV`$6{Ma z_w$7a(S0`7P`YCgxti|tgvgJ69yyd`Jpz$gv_@GUfcUjtB0jna5{eHwh`*4Mhz}vj zngpAh;s`@X!Y1W4@8vj2+N9xZZs0gc+$8s+c`L_B@@9bI^&F=WFo#im6US*J%uI^c zaGXZOgakx$CC6!G%xsD;QHkU$7v+ZN{S1P(}aCIZhg8 z(zd^OFULvK%ruHOaGW&G96<4{94E~)0~D|4IBB3cjN+RRM>`y{zP>d2N#x++-1+lN z+OL@8#Lit{lCNsL2gX`AZGz6`5#wU<`dz7ShLAVDH@2kxwOyN>AoV*k z=?(H#!{@g2y>AatU9J)BPar1ZdpRXCR)i03TLX_8)%1$&ZZ+Gm+p}H)YmI&?3`Q|8 zsBO%b*LKPKHrfKBK;OvjHuEm(5SRM6Js#I+9cBQyT)({75{w0Em4E8rHCg&*R=?RoKVid|g}_>b%k z5PKiEbiZJ_w8+1_Ve0A%uT_E(j_gK?FMMKTce~A-TVl3dVt4BTYX|DI<)By^R+p24 zV!81!`mvGv@dg)At#Z=QNP2CT>eIl53-Yp#Y--GDr;*Hx*G=<8bK2_esfXBOIZ3Ja zw^avYIVq|4AFevNX2KcaF{nTF~}5^M3LsvWJK4JU;Lq96&|bUXBsH(d*l%7YRm^EPvxSYA+wPe@z@f} zo(D9R(CDn!?n}WDLS%vn^@+Y;_Z$suWM|ss$aoR{F)JRO-MXWs)rjS%AR(0MVo4uJDQAKD-)bWbI{xNSWathUM#FNZzDRzz%3~{1!~pi3-(TlPDue*ksWDM$NgOO zZY-zWm(%LYIowF>ipFd>4RlMAH{K_KMrnjSeoElVJ5b(@UAd0QLJ%DiSEL`B_OCyj2o)*$d| zYmhhQ*g@z9DDzV^QjF7)ou2TiG=59k^Q<@IXfiFiERZcU+HkSa<3 zQ%INACDCNr-By>=p1aVJ{G~w!`(Pxr##)Lup^}BxV3OpkB*`PGwfA7)BG)8&!#yI` zq!^)NAhw0(QuG!E<8i6?v1w_MHHNVB>Sf+7PF_rO=zY|u24{geJdsENTd@RlAVoErW%@r%Fpe)C zVU5T$(~xVc-9wxf*ziHPXJZctI^O&;2?IkqGS3>%nIy$CY{}+X$ATXcTSMr$j*@MT zprF);_PU(5Tyrp5$+P|{=_73(NOxUZU=>L>&pZX0kZvxkc$#8-RAR=xlJR)B%{=kOWppZFlpwd*ik3Tyvr^S5*Dx!DqS0M)0l@5BAy$ao1y*nLF1iycR0B6^;JCw~0g(x(gpL`P zpR&65g#5JCN5^vdMbohS_<3C2F@-OsI3$yJ*J zoR5CIRkUvhyYcTI|M4$%#+&3U@34H4hSVX4w65gV zV7(VwzhoGIB++LW-4mSx{jCTX&CN3dAF&wj#=uEtbo)cTrd&iundgD9iv-U7o+VH> z&B!%#L0}7EC?{i$YpuJ~$g>_$wx11co>@a}daU6*71CnUhQW@6js^7!4>)R&xji{I za375Wy8-c4Bdl{UucOz|<|;ax2MDgJ&Ed$z9NzUEbBK<9?69{HX&vO=FAwr#BeKE= zotOd9*bDMHI6H0TQj`V5Ye?t!YiksAmV7VL*!ie4*Q}F-G_Q2s4AU}Z-OSQ&&0DY{ z50k~Z1tqAJfa<|S^L`ZXBIEZl=9VAC@k|KE?Zr-{qjcRfr;)*e??8T;bpWaHOlt}L z+Fi8$H(Rg4$I%`6NbbzXX!FxVe9R+0)^YWDrf71cIbQMs1_oM}0>?z8txxzUo}ewX zxaV4bh5|UM-i4pFtR9YBR-ld!av5!oL~C8-vh!Q;^ELjET(Vt$9z_!5q6G3Mrm$wA zkq%=%pfTq#IdzRWt!cPnnCo&5=hnfov@T%*Ihr-fEK1b=6mAE5iFR|f+xiHkl6`^= zRQ`Hwc2cbWA*gYSH@b7f+o9x`7j94MhPT7X(O#TxM2|$1?R9@57B*`cmYryh)tGaT z##Sjvrg>W;Mz()L9d0x4ak=;%Cj}aFny7qT_;B62R&?|z7!teNWE2-#RgelejpiJN ztyIm7O5Dsmt`q$|Y(DC?G*Py+iS}BlwS(a_^Mp8T#b^@c9BSk@0+6;KmUAeVG(VPa z&9gS37!7i!YIJnsMnC)-gPa6nR(`kHSCGzwoLYMmdT4!4@v$l4d{tjJvZ~{9ZKn9d z(cdd&RbVkT`_NpgCl6~{Jf|tLuE|Kf=@p2`x(+m?*D175YG2_u11JxRunfA|(o6bB zP@FHZC(;BtA#tCMrph1;lH^S;AKxG1F4^-rmVaS3j6*ZgCr4D;rityBUc*rfM@^a0_P3 zg1nwZf2_Oy4U9xA|3GA?5zXHpn|*No4oZp6-Z!qXdX}pGoWz{9wovce<73QM+y*}8 z!~n~;(WtwQsvFl>^^B@3D{);9%ewN+*W9XZl~qCQ(f|FqRxOWuS}Z~*XJ9%yB|Y`I zUo}u<_JQc!VX??=;OOKL_8c0Q-&8##>qy;I0einn`;F;s7zMaH2Oo ztFo<|$PS{?;IXsFz`vRjX$(N4Kr`X|VO6_gIs2kH2URb+nQxzdMG|Qg-7qnu{CkdcSe&T`lXSDFD5+l6YMIlRcP%LQr8S z>O50?>P)9IWL>eG!{c%eVxujqANQycCHZ!1;Jr$qi1omX(Dv9sO9e5c{;iH;a-5sou| zm57n)k`WyE%+q_O(Z6AKvg<=y;6%it%%;m_?n&NeC+QW9t&{5nFP&lZ!Yx9%&oAY9N|gF_@Tw zpFjflTCds@XqB^uj7i+1Fj~P%mS=vCg0>e|$Su42o@ZSETNur01v`-L31sp^{jj>t z%`<NZl}>Jpe2SZF8-kAO@T_se>Q257Kq#dV7pmcd|ZU zdu`nP40v@k;2B9WHS)7@&1a4NcJM&MEtk#Lfa>zwqxr3|aQlWMbf(v~V#EULmtU}i zzzsMvaFjgmXM>b3%x!H4PQeyskI{cC6^p4Bn_n%W| zl|QR0#_oh@!WpYv+O_eJxfC}?uY82 znrNdEv(i_xf`d@a!~5qa)W1OVNMx5W?uV+ENE0?tg}JfYsQ!7@^KhkTqSsM$u2p7> zj#e0~+P&IkE*3rOX86Q)4<>=keP$CmMEfZyBQFCxn(!oGKxP!ay<-&G(X%eoVi~j5 zW~P1a7==8^DYj&^g2$$&V}FJR41;LlxFIJcJ#Ayodv)Q1WLD{enb9y8}t839i83ORo~$x=8lN$92kWkO==#*tcha2H@0ubTMMM! zIr}3GSRh!j^PA$XR)4}Ax?0^#T9vSS*Ef;W+RRhk?u9r>sZ(n0mTn85&y+A&2X&Pm z)uBApF=4~KRawg%?Y7og3%%gI3o~wrRqaVOL5d zr!6I(gQs0oSycl{EwnD@mgQqf<%^7vIu6~$ZV^X8;kl7X9Y#+-zK>oQ?Re?*1a$pbfM zx+8lMCkMc%Ghs&4G^ks5aNKv*{j%QfG)=y9rwMbm(AuWwl)vyUHe@EoeHnJ6D09FiKVC{#;9K$0q zJp2oP&@$5z6WbL@`k6EK^+;Yyh3BG^OtWHtAY-vdoQ%x^wRbvO71sZQjU90laU}N)LKi-fgjI=#{hS8)kP4mO?# zqd#rTZ_9eSZb{RIc&Ja^Y%3NX5HoI%8aGcF_fysPcy`h#0$>`dlbfgZlxkw0Z9b4l z1b4m96O(*x?bPl%P7UyGCwx>^L-RJOwryawe;fT!C6?chXG*U13QS`3RlI)@#kMmo zdZ`g@+c<4N*4t3naX(cLsGF0d_SZ|m>aP^5m}THLmruI+nBq1x*y&I=A-iAy4X14b zK-}rP?_H#0%~OZEM-as+_i78Z9#*QZ+2Js@plu8A`U^cchNBR#OgFvcZiigG_rXJvz0Xg)vmy6zv%|Jsco*>1`ciH@+&} zG>BIp@w(@q=wf3cR=;B9L&^5KX_>K<&1iXHUBAV3(}M9)n_`n^#rN`gw|bt#&(jmN z)q~oipEX#QIBkLBfs3*2s|%hqBiCxDDL8FLqrtCx@P^D6o#<25yK*OA8^a#UPn$=_ z0@mdabmugmy}WcHUBt;K&Kr%`x_%xUp!KUW#rPIx}-=WMycP9PrT9Da@P{XfdyHLFT4|$^s8PPGwyXy6;5b5AM8Q) zc(e$P)8eDcroVHmKkhc zkry+LO}&4>@eNoa&vvTATQq3u*jV-=Y_?|iLuaGL4beT>xsV6=ARl`Wk78gd>3qKA z5Jh^&vM=H0$L*^6tx{|I*^aGiXoQ~(rjeA+evhOCQ=3WKLGyS@8;rCUp?R=qfeDum zs~%c+O{Ugg<6b5`nMhmzO2yNrf~@K@N5~&>Y8xI6nnQ@{Esh7`>|~g9ScFNnz*-E& zcFH^o8l}40vnYPo0z-S6vvi*ZcJVCcbZDkpp}%hFI?uSq^44GVSw*wtd=rR$BR`iOT;8#9+*+U&b@|OvQIh}bSdzNeV z1A#r%y*QcnyL1vsJ}t|c4sEST{~)Pi`A8w-iPus_VB*22qJ|rOi*e%WVPj(46XOX5t#gF;$y+hFkD7 zSrE@o*qvZ!cETVMb9I6A*V)i##*EFe>}-#_A0L~ziNF2@LpHk!2hC@>jqE@oxRdow zmDN{0gb|C;QC&~i7Hpg~4eN&eE4i9BbE;eOa#?e1-F~&}Zp=C0_5`~@UrtjrXP@1r zd3B@i#u$k@V{zR?_8PGN$AAUT!WM78k9P9y@ycwBYCM!WYo0Y0(oy-;d*r7@UNs_( zDXJGcY>}tdevWz*%Vg(X>VBI!!{z%4PEzBQTKjOfg*Px|=S_S?hw`Da#0}3>Wjk!* z&+b^Z8)5fist%j@pZ?6l7+bPGh9`x8s-7+P1J3?E;q-ci3w7GRZ&Oc+=hr2aH7B77yddTlpGh9s~&2@7Jue@TYL>zkjafgAR1wchEk{oeX6t*TuY# zU>@4cRqm)9FWDj`&}KZXtQYV*E&?c~n zs9sn%ko{qDkfo|C|DW{zgv9zj0|H=uN4}%)yGY-s$!e=Q>3c%SzWt`u_lPUmKXOth zeNR~UHm2;X@4x6!9{QfJ>_w_<2YsK~v214IvV)kagT5!rg3xz5uxi4A)%oxplv<5R zT+h32Q0F`8djluh@9XmUsXVW0lIK-^xtHmz?l(zwe~h(~)twHs`n~0WchUf2v@*bb{w)98UdSyj+8oK_rHRc=$ zP+hEfyy}m8wfZFaJR!Q{1L%G!-I`E z`>2Sl&Q;{xE{d!)lq~i>lC@^BuY;sjK4~!W({MuAL4(Ob-zz15BK#A2D}ZcGfSw5j z)H9*c5Hls))@_{mP$DDpuD1V$Q|sKyeKlGWOKda?RI4Y^^{O zSs%2}FC6S`&0O;X%9x7`+uP=0r`I3`p<~hgiR>H%UbpUzmxW~{wQo;hQ7(HB21EO~ ziu=PA7rDPj$woE<8sx9$(3{pUE|{B6g^?YtW{(0yWZX!OKWLs=NJ z8>Xmd$bIRh%(gn?`P^xF)@G|E$qpi{4X0^N08uq*ByubIIWZtKGSK;_>@7qX0B5 zKI+qH6w0O#_%);kBNW^9cFz(ew=4s)2uxr#Un)*IW&)ItU(7fqx^UfYT!@zO@F3 z&$0T$c+2;$KoFa4#jgnAg>QadX??;SPD6plnj2Aof|6fmV)^~+RgzJg`ME2$7ETI) zJG>|-A6UkwtZDuX>bSzA{oL#CC}MroNab6xR~UuH8*}zko7OY5-F5t@gafz1Zn*Dv z&UjVHhMoN_q(g&Yy^Uo=y&%Yk@TnWGk>kbRN9Zu7Y z-^Z8$I!vPeuTh7{Jef7rl^m$MY#dK{bKs5#kov=8kl6|k>&*igOx6;VB1Jh4niw;# zB}GZWQXuuD<>XCLA<#k|3a~aTr53%`{ZdU;cg7^{PW~%$pf z8F!#fb`0;q&>@D^k4H*sD$Zh}Q--M)M<;Iav)!`A+uauTa*|x{vp$H0593I30>7b= z8u=TBB#U$*{ygjS7Fr68bBUEk1KSFkAyrMyPl2qHf1OZ2O6=Af+}7vG)|Gx>{KDG! zeN58ySoyM>Jhd@YJVYLhsXj! z;xsuVExF{+!5vs~7_UIYj0jP(a(ug!l_UBQEZiA1Ry@OqFX!>JA&#`@EwrFW`~vuulIoE5K}$mVPsIxWqa&H(v7BVg8et+JAN z+~s>zbjWCAb~SHViFJ6e&DiTMV=0m`%*R%(N!b)$THzDL<{~oN-?Z_7uJE!Fy<|&x z5HAoPgimyqh6$uj1J~C~$OvW1>5jVbTL+xK(!qJ!hIkmq^?0>ymVCA8DcPUsy2CB_ zZ78pc5h!zN?bkSHp<~i1I1wV5rC$31Qh2PJZ@|&Fb&1W9!5p0~IZ|WOjU61ggL(lg zM|_(HTtWCaBM|$qJ9IuBXe8>;C#>daT{|?eONWx34sE9&`p4k9lPOK&4qXI;CfQ*F z1{uDGL$HKRmOD+#H|K=><@1+PYjb6;GeR9F{6X6$s|lZEHDE+|!Uulfsh1|_ZEW4a z1TU-gr&k>D%IQ)`dyw^s77TjR)EYt@=NzJ8Zy>i4n|!?NTrX+mJc08qm$BM*Jkcae zMQk@eNe1+YwdRNvNg9h}8aV4HlHKqV6N+T9WCWv-NF1xgx!Wjd0^0_z0gbQekD1D*g2)C71^5=ECBawPFRybDJX z4`GSjWL5ClMB9o$OK&<~vXQh zIh}+gV)JU*42Cy0-DaqYmcMA5&ddTyHK>ZJ_)Rj~UI`{+(5=BQQ5Jo7;p;zurBb4> z{q}B_`Zr&o5^d&u6t|c97dVNYzB{z;jZ1Xt*;oHn2}!gf=a?F{6*Y8q>2+qt6-JXdARJHg!}>fqd@pzZE)N`cIK{ zmZ1H(6`OK3O!H8TkG;9U;l&v*a2t3+#4ne(934GkZ(}3tT1EII5c8;Gz~iB7+s!3% z##k&q=P;!kk^OqR6j`||vgKceZZ8rRL7}IWc z(zsr<;atb>3e?EvOYP>JcL*Mm>2vLL{2&-_0_3#6fMLaVLj#aMegHPl@?)%_5Nf1z z%!D5JXWNL1lo${8t-Dc%Rrn>h7dx0DQ$0^CeTkZNz#cKoh#9805J|3<7!}F|Huy8ih3ww|~ z7NEJ-X1J2gUvh#RQv9@G56)kj-$9y^7yNy=B}~8ZedIka>FEbGm}2v+sW=mC{w)=d z`(LRYRQ=jdkV6xI+M}OMJcERmE3L2TR;}0HBwT%;xCbygG72x zMqUr6W_<-+&S`BKfp?J}4)9Meuobc9S`QzR8OJ#FqiqZCdbB{@>DLbO=uN)cfukDy z!W$1J>6fmzd`(ZQaaOf(LDrGH$e|Rg9=xyLm4dHWSi$Ui@+>SmFx{Gh*!o?MXF>}@ z>4vOUHZgqfjyAmTS64|-y6JGE2*f@qxK&aYeWk9~-R3cjWNp zKYaYIq^h(?gi5MIqPV0?;A@DY3XzJc5>Z{assvwPL{9bU!b&9bC-#<=g{w`_;tP5e zYei92Nnxm@8wTfBtqm+4QXR;cF*Hz76&Ok|gE$--ScdpYG!me1C|0fHZxfD87NA%) zqFa8=-PeAuz1EKpujQd}JfRRk_834|;0{lDUpVifNb zE~*GKB@tGKD=W*^20~^@U}?sXA~1xHHCB`}BO$Z2I$(dsP_JZWSy{>Q!ZJlF%dIXA znW#oDF}1LKXefYBDF#q;fC&JS#maD5sI;<7CNC>1TwWb0uLuR`>x8QzeEwkH_<;Oc zVPIKdX&EQt^M3SYN|}8o6OAl_WX6*mh6Sfg9}fBOw+)GG*l?0svP{4yC0AEeUEU!= z1oCT)_*i1@vWHXv)MDk(m>`;wi?V=`4N3#_ifS6l=5o@c0{sG=-j zTd}~9@X3LhLste0R}zt%+I7Bn762v*dNSVTsHDmf0lqoPJpY@%QC+(IfT5|XfYQN~ z;6t)xvpN`oks^maSQ}VYRk6}GQlu_I+AFY8UL5K&SRFEl4F{jqlZF%r_=~Lo;I0Ej zfk~fPEx{LDVI3%RoBQwT!pC8WNv*t{lCTi+3e|`WwjL&eB-Y_gN#nSIoNPv^R_BUva>Luj-Lmj@=H{t?_Z!;aLQrHAi?mQ=4TFESw` zyAfglbF8zZMh50#W)tt1t%avBGO!Sf3`#i(0wv@JdkLV%qRV+@pbxJ9n~;a^EaPLw z(tLmgHY1@5j28^S$iVDEEKrvVRlDZWaLk#S-)Cuw~us;H_eDGI^z zgu+$jQgbvDHZsqoB_sgjM`Ilh1y)1nhZJ|JTTWUyJClZl<_uwd%EoXPWZlEsB^O;E=is0G>2WT9jzW0T9ucuLu0!_Fs$Ncy&plbf*a zf~hlR&I%@rnZ>9IO%M;ti?#3EAf-zmm;Xva+xL~XWFhY~&0*Ii1-r=f2T}ZW@|2BB zGz~70&a4!ENpaxHlB$Y;iK#|Q6|CjRKwibl68M^A7a%VcvGkKu606YeprF+5bBU^HE!v}Q?F zpb)xF^A=F5d^J-K8&W!4>K1%Rv_U*E0!L(Nhm`Up+-31=zw=B97o`alP0e*)1?qx*N=yMMZ-ZCDr6?@%r4kC~dIpvJz@@1`7ZN z5vECNPA1H`aK4_{c?<&BPVpWuz#-#Gc~^gP$_p>U=7TAgLx;L}J7@&Et>onV>cZtb z-2xeNuwh!AL7m3M=5DdvC{-^>w(CWyjKzzl&B~jxB(NA%_?wUHJS zfnDno7B94|YOS6%Pmh>^Vf;;Vj5>6} zlGP@8<2Ki;t@8=8ZmO@$R&Y^b|vR3mX+}NK9vnr zppb-0RdrMQ_?fwjN6gIS3hnCuyNZ?N3l#Er5vN*R+?q0N8H;$ZY!YIAq%)!&c!pCz zJ9{u~PfBn^GJ}Mu3)NgJn?@ANnAW-01lrK4`m&Nx$-l4NHm`wVq)mq&zDbEiQgCs_W|89aQp(GFxdGmUC@!aR8ZWP8i} z4@F?WhzYnEA$Xcx2}~rH$e65V$zmvg>Cz5aWNIj5{{Lk=^HBjtahcl?F9KZ#R+<;KVbt6V9!;{c+N?=D zu(|XPd|)+HHgE(llge1whZ;p}9$9p1o1C!#(XPR+PElzFH97f0J{W`11cw>&oRBNX zsK#xMJTMGUI!_Jg+RR+;XvQoqVIL_{k&JS!1pe8UN;r_1PEZ^uAP#$IZ7an5;c|Q~ zNu^Nk3jE2#fgO>dqc{bUg&i%Hz2|@J#5URLd=sUMQ2Ijn34Ev+kOx3?-pJOHMp({Y zIv1tZ5Hd>fX9mSDBUMmhxUt9LIT+)%1XA6)a--O z4hN}lw$6lTxQSt7hX=wT+NXxrju$na(Cfomt80+DZJP1An$(#NGh8sJ4;_s1>%md=5r=$%4-bY#U5-8`{!M zVD{7+uJd@Z&SNqBO7_p0Nu9?I{L$$FygjA6?m1^SjU(Biky8UjNx>30**GaJDO;vD z&6TRQc;CJG+VWa)O&Bd>n?QQpE-ttu?6VjktD35LwUXTwkZ-*Fhpvi1(wobufSnR zA$Ib~BES6da-3^XK4*#Yk~Qc~)pA#z!!8NWRX7ZEB#)`Ao~H0W3I*e4TMGMBpHAP4 zR?FlAeB3ruiD(Jd#oUUKyXir(=3P>e!0B zJt$9fqmX~wpN*uG?Rx$j`M#H69H{-K1nC>Jv^#9PNlWY8-t)*ysH8~Tk0+;z@_1De zStIed^uG!b{KFY|iqByV{$7Q+dV0DD@vs)3hIqL~pM!X*7Ne>ZvAklFZUgX zYiXU4M0gnPeQ_U)>lj=#`1nCj#r_-dJ_znq7H|4gWFijuKLwP>FN5$-gMC;3J}jlZ zjDEd^>myv>;QAM?-oM1Z*NST-u5)qC!F4IFa$MKox(U}kxE{y#GOo99eT3^9T>rw= zdlSmz8j0&%Tyt<;imM#gHMnlVbq}t`alMS|EnFYr`Ucm(aP_`f_L0T_|FYWAk6^;k z&-ZvqzQstQF;ST0H4ztu>9}eLAgsbwLjYkDt{MUeyK&VJK%ieB)et}!hsJ6c;j050 z{)>ix)G+6}h)%GU6|+zLC% z_*5pjk#RcX`xp;l{5<1fjNf8BobeZoY4nBoC*w01_s2x6v7gM0V4T6}IgHO@yoB-D zj8`$nKTd~G$5?96?|`X~DyY8iW}1zh{66C- zI>r|;zKL-WV|wpRh_#GKONF?Z@y(1MVtfnZFB$)eah&n3jE_Bv+P#hOnT&5|9Atb4 z!dD~6%fWk7jF&LR>$XTQWW1Yk zG2?c|C5%V)cH8|*!%x6|)i8w+4`KmS_(Uv%HR4G-#~sJ>I9;yM&oUmvG;cAcRX~U@ z8RH+JMDSt}t>J&2o}-RF3_?1|^Bm~ZT&9^R3Gi3Ru#(e%&*?WXmLy+l_yR2AH8NTB z#6n&pX0UFY!nlC(48|to3dWU;Z)IG~n5t0izQE~H&VL3bK2=aOUw4z9aWfdd8P9u4imArawx@^s#=PY3^W}HyCeb{3+u*86ROR zZFsMN#9ITWr!(Hccmm^{jOQ_ah4JN#Uu9g&xRLSQjQ22pj`8b^_X1PD@-Q@C*_bo% zNAd3;Bx(9W*8;#b?4`rsVhkg12Ss1Y=~4%j++Ik8girTGzKhlYZI?#OA4NV4? zff|v;g=a7x!+0st3p*uhQ^s8h%>CR4frcdV16UNUxWTqil`9AA|uI zsv1V%9hl_vUJoJe<#Z^i9TY#?HTvFA{Te&}1`S^cQ>fCMWw_#6rfGGrpNI zLos=T+ioMU>X)udM5+4qA=B^o%F=Fn&kVP0x{V_*d&Idm-uHmuz1U8>;C!SjyKy;V zxxQS>cob9J$#??et&FEIeuME;#unosV*|@ojhM+ez<4&}NsOf}TFh9^Sv=hVz4UMI zV7!>=pJrUh_|J?lW8A{HnDJ3(QdyI6I^%N2lNnbqzLfD5jIUyR4dY)iUe9<7;~3-J zjO!TFpWoW(Lr#~GhhS=J#3dYQU@X(OKtF24MvlC~Sf(G+@SPBAjZ78+xTgwl*KjYG zylX-84>XYutZ2d-ep$nVVeSFq1@mzv*;<<8^N8HCW zmomjm8vWljylj$NPtS8*yk5iaXn4?hF3lzlf2!g6*)Gk)8Xh^>O(&)l&oQJ2gtYlj zF+Pd1#Q}}}v?*@cH5$%?`KV!P`A5}x_H>*l!^qt1-zySe;MR=3bCkXmSnVT8s3i^kxd(EJBFBSBU zVrMZf6!Zh!{l){Dou&OSCyvFU?ii`Lc?e1=R}L9vz3 zizR;2_C_iFDceIN{EU#6oA9&ZBB##h?A{kJ2Za~H#zuaaR=}q@Rkie z;R0}Iuq4fIeO95PQ%^*{=qzFT0(3g5njCN(B*u;#JXCLf}1ImrQ@n z9#x`wUBEPOXt3{eYr>_{i3h@ynTI!p@hc-~G0XN1=3#}6T{-XLvt$%O{qOSGzQphG zxx2*s#mNK^g5m>v_eS`DCi4$%PnOa@5zsgeLGh`O9xJ7PCKfvB2gOnce{OrPMDw0l z>7-kHZcWjkXckvH>HEa(4*r@?XV;=%+2Sph?X@;0{fq(EARWJ5j8l;Y`eTPC+#j_u z$&K(GpidWv#a77eA>exK1(Ixm^9yX_`v?H;YQo9uDtQ7_L()VY+~I?6-sR@zu^cMQ_6L3+CAv zEXbgT{X9n#KnRNdo?a3HALr@g-~k?Yz8udyNcs~!a0NI7#Xt|1c2Up#q~ZaLz>`b`5rX1W54Nxzf+F2>yn}~$ z1_L)S4<~ta`A~abb6cl-_M$E00@8t@+*W_a!#p%D8H|T(^`EZEY9!N8b3t*YXE*_b zpvdw-y*LELS)M!xkMb;X@MzEF4nD`@+JP}1*A9&JY|&_*bMQpZ=MJ9aISf1%d}f1x z>f?OIXLv~HMK&h6RoR$mt^&sM6i;Ux5@nk49mZ=1=qWVeQn*3GdOBg^hXTo%@a?EG zU1WP2ZZnb|2Cf(9X=6H78`B(5Hs~n?an|a}YNltCOh)=F&#exg?fDJxADHLK&iFRi z`E+Z-MKlzMAHr`lKe?VuZa0#?WPUEx_{r1eMZPwU7kP@P5`>^w=vgiy@Wniit<1xP zEH}?A8jHCdWJu)wIX%BadeE+?33nY|Zl@E^gaas>E|z-sfag)b^`d}zKF`J}HhmB@ zG}aV?qF9^D%RDE_WTY?m1RPxEnd;z`tgCZSXEy4mZ$d>b+&0Xcwho-e7$FZgKzMZIymO3a_}!b8ytMA=S~OT>G{IJ zcX=!a-|cAwehPibM!#rW{%B(#@av4LSl^^`+$mPc#ZQXqeJ}i;F@NF5;g8jh8@8?}6uZai3@4W;I^*JYHWRyl~WLJuwGw)$G8No@bo&XFQD#e$GSxiU|ey z#-9GbaxCiER{Y5Fuh-=NTb92uoAmsC4?eZl!M~F7q)+U~(14rbt*H~HwkVL?1_Gy| z&UEpTXY8FyZZEQYh9bRQyvTBsGJH{6YhKdi*`WCVJ3JX&JN0Xqrsuo0x%`S}nnUxd zCg(=a94GyC&m|82lc&(ZZ)*DgXU{4p{TLcMdQ8rzC?Rf@$ls2%1`}jH1>qQIq@vx1lo}U>X^7I3y z5JXvKhVl2Fp19N5W-ab-pibOCGf|VnBwh^3QaA;aR!-yCmD2{0I`BBrWHf{d? ztm#!duh(*ZwXhB(LD90sA(lyhU_689V&4Kx^8?do+E{Yn%9i3yD$&M^>^RAg@MM$) z_NM$s$uQ0`k+OQP~V*Y!1XE^+)cxm7y|0&)SUs zOZlgGX?>AAr#Stkwl2W6f!pfmy%lZ!8n|8@qt)3@!~MOaOBApd@b-}q_;@eXLm?Hk5xUMFGVlLE<*@SET>T@3N2-KAuhuE}kPhELOEc!n28(j0(kFXw1eS76XW4$ravthE3hBuglKh8t&|AAG z%U|)L6sl` z#cc0<34!N$7dtpl)3^Cvcl}z-a*$;&@&3x8U*dhh!3Ew&9lX@r0xabdWc{4VvYM7q zR!yKKdJ5D>!WVKMi@Y1}R(5z9%baN7himr7^qx$K2tjeVH&a63GB54rmvg(t3Fk!< z@`;85wM#h6?N)dj(C!Vu^`cyp=M^1n0{M`)F@4y35cH2ReTeDhxj{&?KVhw&)tdde zl3vH*5ENH?i4O`vagAmNexd1SjrTg_Z%13%_$VFCeR*yW;yyNV`YOh<@2k6&In_gf z+9doo_odcbc8}^yMC(gL>r1WHm+Q2?M76%ew7%4NOQ<1)pt#8^->M+I$?NXlZ}D!B zBuM|2_a5uRFB9Z0@!%R1XE} z3*j?R56&u2B6Kc~rg zn>NR{d*zcBD*K|>^|fAN-=h|7Wuq-}m^Lwf+N*@vKn>cV3HK2;CY}l3!#w}d+pk{9 zaHscZn2Y=?h)4>P54?4Kn z`;vpd_U>`;x84+6pg8yT(l;n6;N07Lii3}MCp-9`-fJ8j_rBy{ukU3C`+Ofd*wC=w zH-IJv*YDME58rSnJ()4RgI5c>)%UBhd&E=Z&^)T)_ZTa`-KFp8bLmrj)lS)7zF#=F zx9>&=r~2rm=OS&%cmmc#f~ek>O}fZex6|jUDSd z5#=dhukCZ~{CU2ml!y=%**>+_1jQ7e>px8OT`NgIGsU;j!9gFb^~ydB_?@y3)y8Ir zCS=^>;IQF7BUo*`=%in*;dL6W(eQc=$27c2!?$R7vxe`~@Ld|dTf+}&c#DP~XRK`6 z1gDSFeJ=kqd@nnFndx)al-a(+PWl`l{qBlFP+aJvZwX7B>znD|`9AtcuuNazyTZW> zeRRUAK3>yhX#0GgdP1|B%wFg+7=6MH;@?NB?Mxl-pvTyPhub?RC;G^?l*s%Y4la zF7h35@G@U-+a3n-Z%eytZmDmilfJ??4!8vKu11XU(HU$N+l{eoE3V=6u|9fkCGGY& zANhFlS^0RM-BcHQBDwE)j?{oebsDbG@Oll$G`vZ} zw`h2?hVRtyT^hby!w+eAi-sR(tnAEqr@ys6m;VhKzD~3G*ZcnB^zjCtddnjyV!ob^ zJ-N|$l7s7f#SXs7r+#@86gT@eI_bCg?gEy!WewXFX;0R&J&`u#%5HrUn;qUWD%=1- z6m0of#wI{Kk;0?3_ z`t|}-@do;1hX&l0&#dDnExj|QwkVL?2!F?Nd)}A(sFK@rnyj|_QivEKC|>rtd-(<* zig5^v9c*VP5)?aqA>5VC`2uyy^hdP!Odi$ndyLhbb?JBcZl{6>ROcfS0`K-c>)=M; z9~}I;?>z_q$#=-XZ~1<9@SnB0{GKn%nWy_T-|TO`XPor+eQgf@z&Fx&JH0mZBl+kB&-563dz z?#p!apw+H+6%FMvaS(o3wO=<^J^D4)#rr z9hFsbBYJhdL0HL$F!h%L=_=tN=r7(a^cDZ!NSX|ccMDlp<@4EZHC;Wdt*zf{=|5`g z>_2?-xptE6Pnr(@tnD}3eePN7&%Wy%`hW6XiK0RAFHKiH#$67LVLa_%zwt*0CmDZo z@X^M*4(?<8;NZT-*^cfVW4P-}KjR4}{a8aj?Ia$KGuoZ>0UAEu7~|;Au^K+XnChet z((p+}o|8V9$ctY8+lcn)Neo&lZxO?Xz zhHKMKGlo$m2thH_I7>p{Va9X^4>53j!vW_NhUwrD>?29LeKPy8@+|07qw{-iRHO-) z@~Mbt!ZpnES;ma5isvki=d(1PM{7KfG2CY##lf?U zzd86qP`{+d`*OxGCw-AI!oe3A?wl^U#S&vOupHAY*3Vm6=g&4s=ZU6){vb%Z8dR_Ra60LRJWJ1W9?Ul&Ey=FG30Uz! zShY)-+ND6cLHHrojUwa5Cyk`-!1dxX)(vUvF4Od(NYj&LMk?1reKCz04qjp45e^4D z<1_LcTy9+C;0mMI!Ij1e2VY^7JGjaSJ2+(2I5=!v>)=&J)WNHbn1ip>@KwgmPWsh` zyS`tg*-ZWH=o({}L;nj+mud|6d}h6I05sBGUB+W4-zU6`=jJOsj>TQZQI1zx2V11% z(}en{FLJC`cNlBcFQQRA5GEcd&^QwQE05y_!+c83@me0oW**a8Z5%gfjF-HeSEf=HtzpJZ~}H1g+HVT9(OA;2~So8aOR*BNBDsm=!a zV}~Z(sVoKR7vVvmPZzfvW1q%%d4Ta8koz^k#;%RUOQci~At>%NdP@lW8?9e=Y5lre z>({;7{;^3!^EC)YjjCb&}nr(khlk+xmA2|fY3)&of zQIqW-jC&oL2F;%DWZ6;{-UBpVc4&4RyB++BHaB18`6%gMF+O)_UNhXi&+CTAwtGSG zhB3v#ZyM7ayw}Kg@LvpiQ+NmDnT@d^|KZOzrs@wceu#BjK8tyTY2^HUgl%#&Xz)EE zgZvixd}oV6ev5q8{Cl1YR96H2u|pH?@(pFF8-#yCJ?UbfG5=XLuis%km*f5p&ucmE z?--;T6oQ~XT0-Fc+T1^2xMw>b8e<)rPmS{({F!mNgTK(uJ(`U>ob<1Z$AG0A-r>GT zf9zd@&Nii=_SbIx6DmuA^q+9w`GoOw=Q-8KLs}mXX?;AT_3>-1kKbv!{ddhLIbw{V zh7f|{XJe*>z;UAtSlXyV%)ji*As$CLM&I!G%D#NtZC|J!3e*?Er*mJt{xYX89)77( zj+e(zb6Jj;$M24p*YA#355K#%_Vl~{Rf<1|vJ`@%w||a=zP$WInD3-62txK94RPH_iu6NNBCcI@JPSw<7N8yIq7Hl z)n*_lhWmRs^LmUw#lhqK$2)kE|6B*3>(6m;w%MY_ar|e9>yXQLJ|Fl!jEI)nAjRM{c^MB^x8GiTK#B~3c zPWpv@dci{4qCq_FmtyR)#Yv2*o(B4ZP|E2uJESZAn~+afr>VB-sVs#CTtu^x)0HRf zrV~w*O_K<#7ePqJlEL%ss3%>__wR>Hf;u3#}guqMuRSsV2zt+K*`R{aak^gaEIlt!d{E~9c?;t~)ocm7n6sV7c zALl-n`ZsP@eKfT`np$5{^{AKH>2E-#x!r&9zWHs}1$L3A`();dL6W(eQc=$27c2!~dtY zZ-I}ys`CFOGxM8Yp|s^KinUVEf+bBRkERIpP4!5 z+;i_e_uO;OegA%!c<^2izQTjA^x*e;@UB@U?DwhA99(;M~6RwPV zJ^y({>ew)xjS~4LSIO;!D-u-X!*L1nT#+;LXw>>7K>;^Y%ykP5%5l z;KBO=v+nrWk1t`nH{q}OSe^q2w`KI<{T}_vV4wfV;3tdg)1NHvQ_;ta4%r?F9|yf1 zA6%2V`Bv*gu9mi=?d58(UEJWcmydZqa9@gjIzJfar^*Tf{$wh)=TD{1bM#NA_B!}e zsSi5%GpUa__-4;eZb?1j=(na$b~f<&6doxdpPt|gsZ9?4Vv4Us=BFq4eCiSh?@xWe z!Cy}8bMWn{cun#vDL&s+Arnp9=q`!B5mMzsdF6s`_!u( z{77ntga43vwS)hdy4b;gNWI&^fAVbgG0#^2lDf(9Jf8Zhga4Ac+rj^ny4S%$_@ILu z!rwZ$G5nK*6X7yvdtrE*gB!yw4qh1A`5nga9=tfT`;0id7=GXJ9~wT(+1}yd84f-o zJlDZTdhk)?%CTf zJbU}4>+AO$-Kf1S42{W%ueN$1k1fjqz*c_3R%XI>uZs3YxF+^?e0b$;*4~zfT>m*A z^s9p9A?I1@50{5a0P}-+L>P}7SBA@kN_tb6aqy~ejf0!Rbq;O`#~j=o79E@kcRIK| zjOQ#J;Xgb2>hQx3?g(R_*%`)tYj?Qc@thLcbug@}hxa)8OTs@ocx`x;vy&Hx%N^Vk zwmW!37|&%khFcwdb7*fleY2u4Ll)^aLLb<2Ah- z!QxVgKr6MbnvZV?0df$e%aCYhYvXT zU&Dt1zYt|SFSsh?Lt$Pj@CN|@huc+rXTy7fnz`RfM>Vg_x#%r!lS{@ zPfu{btLq8pLq5D)3DXiAM|YCp>VCE|03Mv;9rG<4*qp`mVG&*1dwQRvccee#;5F%QIQW$GuN{19`f&%ZO&?)8(Vk#^Iv$H|OrPfH zo6_ew_@(Lie#GhNYaM+{`V$WBPyg1z1L-hn%hjJg+QDa}U*O>3bhCrcOqU&eR(hv{ zUzWbe!LLZa-@(~5pOMc`PmoLB=HPt#Ck`G@FH2dTi8P;!&kyz))1409o<7&XQ|W03 zm(qMLzVesTpK$Q5^bZ`oCw-K(RHpg5^l1)$LptZ+H>Gzv_|55k4*r+)zd86V>2Est zZ65sg^miQn9q9)hJe~NRgNq(q_274=W1i_mn6_mpCKfuloH*LSI}%GBTurn%__B1H zgD+2aIQWY6iyi##^f?ZGZ~B!E{y=)v!5>ObI{4}|pTV!Xy*7QbgQpX>Ik@P-RS&*C z9rH{l?(_J6;NWuNVF&L>{Kmo6guSSSehN>)vohS6PCNMH=|v9Smp;kCpG>cE@MqI) z4*pzvjf1BX8y#FsY;|xs@p1?6NQ^kRny5MW^Xc6V{zCfo4&I-Br-N@xU+3W4)1P$k zo#}5o_}|jMaPT+MPdNBL(u>T1wI{eceWZiGoqmyn?@4z$_`dXM4t^lr>);=xH#+!7 z>CFzFPQ1*)#l)zC%ZZ|ccO+^Kt|l&U@K4fjb?{Hqmpb^N^feBCIDM;wf06!*gMXd= zPY3@d{TByMCk{Et)^E{+%ZX<>`i{i&99&Ig9Q=oLyMzCjUgzLHr(f#e$I=51{+~2n zP%Izp2c@?;7&>uq(SxfV?DP2aWE96;5}oVPIV{RM`0&WS{SNE5iSr!2=)qMFJ~E1V zrW2QW{O@saIdP4HcO-6fa5b^t!AD15cJMLLR~-DD=q?8z8$Imc=SB8mj@YA#QU_aI zoe&-5;1@*CaqvmeatE)BRycS%vBts0#5xC;6I&d-BQfmYYGT5{&C#nI+!ECs+!4La z!Clcc4t_~=hlAHecRRQzy4S(eiH96qO#H^d<-}tS-jQhFzzZLoT~8e0;7!rd4(^MN zbMTg^%fV+vLk`{+RULdzwA;Zik6!EG>BL12E+#H_a5?co2k%H+@8D|U^A3Jx^d$$M z8-3Nmx#(LC&PTs+@I>^4gA37;Lu?tRqh<$Jqjm?+L|qQv6`khb-BGWDUlVO~@IOY| z9Q@iS>)_W%6ApeuRCDkfqt`k3{OEiKUl3jB;I~E>JNUxreGYzmbd7^Ajy~t$cSK)y z@TJk64t`hEU?xI6!QQCZ!S9Vm9eh=kcktEGUI$+nUFG19M*AH6@#x_NAzk3e>FPK!C#9maqwNy#~u8QNM7J2 zc6)bplbJB|1mBE)>)>xik30C@$UY3QC-_cu+M(7S?u!N-{Jm({!4E{|I{1fC-oZbL zW*q#J=sXAiG`iTq4@H+b_-E184*o^7&%wWpzU1IvN4GopH__b={_p5p4*q>~pM(Dx z9dPiUqTf3BvFN`Y{FlgXp5Z)gls?Rs_lan!gBuqtb8uq83mu$VaGHY`E!gbf#S6{? z{6oyq&I=w0dFJO~fxjPeZ~cz~{~+YKh$JRVrv*QhVV&OdIuP z`je1%fuAGvpN3rTeWAb)%GyYWzz@lK2urYEiy^;+?L6p!2mdwn^^*Pz)A1VeinN&` zNM3dx#5C=k<$wo272VEF$YbFHPXd$2j^};&!2bt2)4bTs5%vSNa|F^Y{MXVM*Uk|b zmt%X~|0>p{|8ZUVq|cW=>HHkP*N$B+ ztm!;bmp;dzHXYyQ?YNY2?U-{mY{!=S!NYN&O$REJ&wl*c@!^3dfi2J98kb|m`Qq9! zB=emQX1*L-&KK8?IT_cECoQZxY>IqcaU3{bI(B@>xOR+ZVNGX4T{;}U&6kcH*D*L1z^umxs# zg>mB%3#{ETF3)LX5qCv!a`35xQqlPWuL|x2jG$l)-~C84kQcl*jRvb~)yk@BWpvf} zu2rKut3dIR^>S`B-y|HXlyQ}1pBNp@WbsPja$zbz))e5~;9WB-npd`*_~MHEbvHL> zn%xIj%WC)O4n`+)72(e)f9AyQ=I)lv>TbNbyEuVYS8Hmm&n&fCv09iY=K0ENjo0SI zb22nHLyA@>gw1PCj&t`QQ)o$Rjx?*|MB{{56i=UZX;mjn z6}&^-Xrfc2cb?dNa%*OFM`t&GQFP^+liM0)U&HE56NM}mh)gVv7 z0?nC3s#aK0_n@|u6^xxCWe0FlVVF$8qoX!SmkdE}=`j4LLC`xbyQ8DRKo>#4-kLG& zrm`%%qq8M|AKDRVI@%0BG9)X`u+i+HR7` zINQl)OXK5~sw!xu3P5#WfXx@%<07NJ#ZRQR{&_gHSNQBjx_IF?q6 zEFqNErSDlcQg>~?V`ZBbT5>?hXmQ45(xwIX0-U1SwD4le?yiKjtvJk^3!Ox2g&)5BD3eQi_%EJZ}$;c2;zT7V-#kx#;Zi!h9O4DBp4 z7*M{GU|1Pe2Yf_%#_F+MizoW6!qTSlMWe2EEgM?dhQ7_caAPgv30vs*c2~1~16iRX zK^M-n7+&A*;lAhg4%v+ug%!sO6YvAO=xQa`mW=VXD9A3zX^C1r5T(wcNIb-^BdRNB zisE6WN2)Wq88z~jRxLp*i@+8*TB>2ZVH+GeS80^BeAf!FqA<0#Y!;aS%^-?)$U+$r z4k##YxUFosmZ=*(wRYAh=XklKEvdz5P*hlL$z-z#5f@Y7v zT)WwDcf|Dm^#fbBZtflKH9@Q6AQPphcWC`!-`3%Q!LH6Yuz$FBupbHb4h|w<&B|_D zJXE+VUf)o5^V*?duSk6x`v(SlHP#TyM!zfi$&Qb5Kmq-T#^GGpj)0+e6z3D;`8qBkNl+B2n=@ji{v(vfV^8~kMj3i#{ znUbAnwHsl)xN%^o5l9^Mc+F1M+E}@>t9oTy+dP%1)*xQD73NxPg$FOyW=3k`swgpC z8UNdw(om?tG9W9>ZG0*>fi6UbjJApj#hs4AqPjN3!CKt!o{SMQb+4_hS;ox9`>E${v##5=4ag9nTQ0Bz)B#3|OvTWzq7~HI z(CLq=rojo}q=c9)j})+0gF?2@t5@e|b3z(>7j{m@JQWK z5RCWSOr%AF@Jl%mGM%ezhnc~q=|V6l$yl$3qDx)IW20j+y9`$>TFzBw3Yc1P#A0drN~KhhL5rmnORRp3 zTabqx%0hgVzO6IsH!SrP;KJg}SQcYvj@dAzwqn~5X`mWn+M*2^$+4x?IZOoYsuVCX z^}}p4bb(%G)_b}zFFWf$5eDVfC22jV3GyOY7mAQ-ZfCt9)_*cqZghLCT$iSGpEjnK zC=6XZlUf8?I4pvV*tkuM%L_#zww;J(u09i!qkPeNU*j^h0y4%uwRLJ8l%~*&jgMFJ z=u>1&v9cXQbNb!US_QQ|RhvfYTnky*-qr@M)t%irlfx37iv{aKqi#n})Y*;x6Kjsv z8qxM+uhMKr^X(m-WTfSb)M)uISuxS})hfLw4+`4R+^G(uoEvLF6lpyxJ60Qi4JyiH zsH>Z`FX5_$Dd_4}O|({oZx&Yi2T!neSs;MzVNsL{q*p;T*eMu`jy4N3MuRSt)MCer z^n_A!gyCPtmThz@}66z-*NY9doOSyO~Bnx1!phegCJ8Z}+j{NeMhNLFVmg(7|3e7QYMP-b>C z*nA;ZoJ|$c=MO@G;F4!YKp~2AmQFPlKZF|`nWna;Oxr451htE5iq)m^q`bN6^r|{e zn+TU=Ir-1eQ6@ubb7$`)+l5rOQrg-n9R}tXCk8cbTd+G&X-{aaq@piV8ari}I%#H= z6pXY7&ancw$_OIt3q&q$)Z#@(&}mtqgWD~!feJeV6|)8`z?;=OP}$KUVJw{%in?A1 zB3lX)0MRZ(f++nfs7SyrIFibq5L7f6w}eQtDH#S7@e(5GS~|>q0=t$;sumBkufP+H z%l?!Y>ah!md#9SbV*MvZqrB8H5NXIV0 zk*a0Hz@@%@aMcY~>y`>z*O+R?ieca{)*(Rk(F&EuYjq%-%En-!7FRZBgmBdmu4dQN zDFu0DF`MHaV?R`!ELel2i|6E|;>4+;Qtb$dbgE89RUn(u5y?~@9XGknN(#w321A{Q zamJ~eAsDOB8?+&YSyW|V7!coD33x2Ch{?3Hw8*5+B}seCPhqk) z+g+$@e2SpL1Ug5=!sM_pkD`ZwKxs6K9hTPJXf8OkrmQiP2a(pAt#tM znbOK?rp&3UcTL2%#zvn#w`qIEl_AA0rjD9SbGqeBmr5DGaF~VOj5f>F6pu< z7@Zx=c&s)(ZL85(uZ?Q^Msl+WN)KcMr)yXswVIGVoNTn63{suubTwbu$@K!?bf$Gh z#t^O9OlM2Gid?0sSHg65dx1+nl7O|A={h~qUSx-$POQ4}ipzm$=;v5ok=sjmR+&r1 zQQa$HX-pRwSFq8c**cxqv*=*4P}-^PU&ad6PT*z&X{J7sEvcCK8H=>edLCy~H#B`_ z>l&Cipt;%Z^O?ng8?KLL$H((i)svR0h21SrI!}Su!aAEbi+R$)xenx?E~ku1%ipSN z*S;46I_b}JAfKggheJak9BY1PVEyUYq2a;awOg<-B3RGHK_D4L2G^d2wGg2X_H4n% zjYjA_W9!=Wr}uKtMhFJO9CE&%nmN-oLF?5Vy!&)v%53negfgtNOe&iUIXWCSI&QME z_w48fRhQFH2DHD}2ejC|HW*V2j;Nr6H#x)2l`6RjQ*hH^SFr^mvecyxKVG%I1Ib`f zz&1zFo&Q1Bbsc<5%NpZi~WJbIfn_6u0gh^k!d2<&X zIq?mB{asuPAv|*}=Yzn}&zj zJb|nq?Ct68AMRVbnH!SXtk4Gf`+L_9%c5m%w1Un>DnYukmJDW(U7T&B6Vj0irk+Z0 zBVo*FmEzQGn(>o*B`z7LL$R*(X;}+0goQhskQHH6GJDTz=Tx>-#Jtz<5a}6(O|_`H z?G8$|Iiq}7595Hd#rF}izE|%ezy>wWb`eyz^AT*PTwSBKH*Fiv4)$Q#SfI1esuUXR z0nGj1x=v`6HI4E!1=KX;$!Gh9YUMHt$mR!Rspx^DWk< z;Z$%gv0%7Ui*aVjUXpblV2tb-nZ^RcgUjG;H~NY8`c>Bqn=TfWF$BbFaF)x^mJ1Xm zV%8}TI9!0CapPhd^9U@X$`BLc$Wjh! z6XR)|^Z@`XGz+TrX;jKN9F3g2k_QsW2(vBOj&8WpTI8BRdV}Ex1!?$;~c43B+F$T7aHeDbt4o5tT;5zRVEdzSSbppwJuXx3XF@% zFfUJ4k0}bnyc~^w;xxVdR6Sg@qOA298ugfplRz&=Rgf*mtSnVSeqfxZ8T4xz(R9Ty zX%DIylbs)+mV(qEBe&8ydtd}L6(hVK;EaWib~UT`#reLiTL%Y*2eN%z;jJyQX<+NX zR&HG5OpIP^eOr1V2=gVSzjs)9 zL=sXqu!dZW;oi+)>B_hWLsUh^aWlE0-oZ0_2fJFGApL6{u}w+{?ofU9m~+RtvxWqHBPYJvwPPY!8pV) z#oauxv9I5eU7^-*>2XA{G78wcVaO7(uWu4q*SBE<4cto;1+#pyUS;ND$#L{SRaTFV zbWEDtz~=(marFbE5g36xl8QQKGlvmi&C&#DYv*jO4O22qAiLQ5F=~}@0^@fmS-b*# zABKaZ)r1fJuGZ2~Q~pIZgB#=c6@{H=8cH%-h-86fEe=;P^|hK|##nOKK^Cd6 z&R4KgPI1tdVsT_ujKf_Fn`B^NhtL>?bQn3%)-YK^A{|d6NXPt=fE~kIIm2Vh?8Gtk zLV0w^Mjc2FvZ|Tod_&db#$AQ-&W`MKCBMT+1#7_BJ5r}BSe=@}*$ry}GLd15CKUZh zzA}!hT^vQhJP-uUK<9NMAX%1?QBI+pEtRoMk7|(351iQnX$+@Zj9SIMEEk#TblPgC z`choZ*uZKf*WHrN!W^OPp+Sat0{ivo5)uoMZ5C`SJatl3O1_G9aX%r+V;qZwd5lJ3 z5_Z%qO|HBh13o!-g@Wl)NTFQF7V7wo1`1j3*Z8W@DrN*%9VnR&%I8z~$ex*ebyu#8 zVI&RA%N%ksch+&C-d?oH2-%a$uh{rB(rTT_$f2bA`o^GkY|gfNr98UI1^~`uS_Lb> znkL&~rc!rQQFKt@S`bd@P{Xnp!SnR-86U)`O^b2IcIr+=NxIebe-ndWp^-H!hWKbPG`!Q=E=DkP0pVY$S|up8BKB( zWMT&A-led*0;9Z`uuJoiReaa}WU1Q1gL@ z5Ofh1Cz9%v2s$(Lmc)z>O#o^aMdlO_gFMu-nZqJJYeFHKM6+wD_7!hTPjeoQt zv+pyjrH)OFkItF?iNCB1dCDaou$RhiVf{&2bvYYLc6!A_8p_i!=V~)Nl*g-ldUaRt z?9Mu_Gm%IBoUP&)M_kdYF>ul@)<4aR{uo`uWDZ7GvFij{XIu~LVk8)<9L2V7P3~iP z7H^dIG*Y8YV&j#2`sk%_E3wRdV<`dw5M|zj>caF>3h1=*R4N#qEKH3-=W({U9X>nPiWWgsgYjgwt@$Z4F6MW+ zBI~G$ny6DYdr~b3N1bqUPnn=QoNl{kuiDTnv+HABvyrqubxO}jCzX78%5IrDKjUS& z6BqDJ%S5iy9XxfeHUf+traw<1q|?zt(A@tXh48n+^^QSZ1u#b}P2&&^cFZ+r_6<){ z5niHCQVMQrYjUOy`IdnB@!Tds+Whw4j@9hW?n+*k)H!P6VO2Oi=QhxLh!?CY8T_Y_ zfD@^=px<%vXg9s3S>Q!V zT7#hJ4voKAgl3l5KIx@l4xQTh0+-HL=-T!8szP#9l~xRU zXEaAtl=xhD7BPEsraIQRIZ2HSb$Efa=Qv0h06Q@R!SC#@T3$JBjoR z$qV11yf6geMNemrn;T}CE()&R&2iJk zQpIeS*btb+sVmEBtB&U^tT^USIa#OkoN&NAAUYRcMRcyVT3#MCN3z=sQ&V-k(1h}$ zypFd`${-s|o(#NYr@Sby* z7>h{KlnBj$N7y|&hSHT8o|s{F2<}wx#k&cf$UJ^2BJ;SV6}eLzELm*^yhGQit+rO> zkpL#klb9OW(@m$D;6OURg4gKk!X(Lcwuo(iiY)=LCGwP>CT&%TNg9SxmeV&av_ky51EAI{RkzA#Qh@v=$7NkYAm9YiZFN8JOx!O>x{=?HZyg zu>NicY|@5^4HuKyj#>epZb(|JxKZ(ELpmc9g7pNLEOO{bR;=|SgL(o626YT3{jvN= zZK7+Vrav)N8o@D{`f{2CN2kZMjKtXv;{HN+)X}vdqQ?Wfn4hPrN6jUnx{iklNn2-1 zRdaM**kC4cT6RH*k8YX9?qM~%)5uKqK{}W~%#DL7q`?|un(YhO!{N5+p2_@FxoZQJ zfPE)!f8yk0nW)3`!4T3V^1=gdG0kD8UE^GObplak!faEjh{}=_*AX+OYZbVRi#maVGAn z*VHybpoO{+la9!5#12EDAzu)d;h#8aOHTf>|!9 zYqR9d7rUh4jKNV{^;c>|9{81(hbT53-qUyKlyg{t6`ji1040Qfh`SQk`I*Rb60>(r zVgofZ32_(5b#!vOi-g;|#r#wjV?I6Y1u3)_!s_O@dIYo@!=?R@a6m7>q-NsqDpZX% z3Cmy{oNH@laG%c2;L;bu5NNF4c$R!g6c7GI3BiUC48hA(lE6JD)J>q>lN7 z0GaM#Igu%wq{cAJwc@crI&5g0~wyZ8oCKB)+;hq$porB)Pe6LAx> zbUPQeWw&%|0dWjgj)dAEr!SNMs*n;C>83?S848G0GUU*Pjqwt?VawuD#6X4>ZDp|Z zIs#i)9Rbb|$Jb+;_BdQ&KsRoa@M5!lJNTCC7Tz3?J3J(QSij&(zp zHnT0y#2F_mlW566GW65upl4inZQJT6XU!SA+Q{M|kGe66K10-404M+T-rRN>#xTZE z+cMBWiM7tm%mtfZW}J`7c4cDJHW0u&0qZ~{zZ*O6w(3m_Vz;P9XXii^F&?%mHtL%l zHv!=F;3>t08}H0+slde4Sl z3P;`WM7M9CkYMBb^aXR03aS3DNd*DlHLb6uO7ii=!LoNGqvHh9pOyG)43^^Q+Zukucasqx z!+-Hb$%wDn=$XaOXCyZUw>bXWz|Y6AUk!fq`YT{T@O*q5gE!#!NUp^*6j{5Sk zKYWaG5b!08MmTwW7lm7gZ&TAM+=Uv$6ME#itE=);75gtRGIFU&}Q8JPJVWBwl0DAjlmrD**XDH$XZr5k(6 z-c6pyS25XB7})qrQZEp&kJNvW^loQ*ZxQebCiGU8q46x(O5#G{y^P@7gn_RBNL(b~ z;pBaLj=zs0UC6kWffq}rFJeybU;$E>fg?&>B5%=5TtcjIc@U(p2Eg!$K!a2IS&Vqe zKauO_%=Vqkpm7z!OGU1KK%B&7A{XBimw1;5Lo-fXF1-5)?iFeuqg^4?S5db2FvG^n z30^7G4>Py-3iU2huM+T0q<%=iFEWd3g!deR9~E#9)4f3oI>Trm7s=jC>OPT-Z~aVs zQm8K__$i@2hu~)<-HTZJw@iZ2_&!Fv^?KyFAo+XBx0_*~zeNc9DauDdh4H+Ej9-%I z&ti@{FG&orPzRG$?PaOCJxIiHrIZ zL5lVeC5OWogO==8xHHzV8RFr%jS;AdfKX?PuyPaS(E3j%(= z#Xj|1rX~Dmfj_l$nEcn{XGv{wQdCHIhj!e-l^_`7?f!;R_a>j1+@K zX-Lqp==&_{qE?oD(L-QOE;<+V)S~MEhl|+2(~BnX8!h?(coqbQEMnQi&P4_MH!Mm( zuZ=-tJO08=$AGjT`8@nC9%Lv>yoAt_)rh;~7XIw#&%g8MKK=*`e-QC(a2`s|PeX!J z!c)WB@E_K$!RrO6hZKUtoj+rS%abAFguD6MxI7BiGXGbzdK;H748KO%U!&NP@T=r} zEyZqJenfaW(hvWMZ_8_3{;cp{p^@-)Qi$cphQ~0K^NyDsmW5l0y-9O8KKu<+`Deve zgx{oOZ&qw&I794w#hSy5h`mLzwr~eix$vJQ%}x~Z#mMbYuaP7M# zY#Bsg6)Z^Jf#1W{p;QZ!%x*za;S;FQ2BvoU!$STweiu_i4YYTyPO=7#r{O2;Uxc6q z$s_Q)n3{_#J!t$GMI2Zq478BLSxpU+M9`Q89G<;d0tfNCm^zAMtI3e2CNq!m1VkS6m{g>}4vScd5j2#pYPZCHz6Rz(~2TN~Etd5{*i;CbZ5G2)JBX7Q4c)Rg?_(t+BVOVe|Yo!4U zM}+G_c!MB(6?H^&UrcQK`GO_Gi&-O6CxM&hE=Dyisl(hKXK{*Jze|uS(?46sA>VTKIm*lxmZAOEjK= zlvC|YsqsOAtFK~K_y(y?vG+GHxi!*_zaD9(y6BFN{vsr9IObi5){sg(hym4MWDfS? z+Za3x@+2GBX*}bRk9k)8$SB7>t48rSy1)OpNn~3gvVHsiBwO+CE*oBzwIn>W;jo58 z^qlB~=x``HXgIWCNq8836N{d+=!8XwV@%L^6uOG8gS!vzI~<+H@jwnf_$8nx0Y(a@ z4sU2U{NRJh1Zz3O@ps|{Ea?V>F>3VupzrWiU|6s?I9L)p$fTTN@WDrWX)KJ>KtGoB zLKdw+>c=9L#Yckr!i9{wB!!e=@sxtnsT_yBK&Xc+ADSGqMN2sx37oPJ22OckRc%h( zQx`TuVCVpPsnbb4J|uOzikuC9S#%J7=|6*pc}C*_xAe8Jg zMO(FxuBKtV8{fu&4S!Y{p6N8DuCWGzX{|=BoQ)+i4NF%pJ!|QhXcM~T)GTovh+T~- zD^KoPD%`vO{?f6%8tFLqOB~i<@`4X*(F8=i~s={6Esy`AJSL>zbfw0o>DuL6HZq z;lIkoM=6LDwrtJOe~drb_>@}w+lMRhIwVm05GH_{Mco6IDHR`bXqc5lDgjrH@z_Fr zj5p%SW5V!n>coE-x@O{pG!Iv19LzaQ!hA5Ci|svj9kkIg{K%v39D_c`UC6T^4-BF_ zdJg~QxqW~{|M=HO@ktP$-3CA(w+BGw_8#pHnpX)kCE(^caDYV7cwjO>7Qz^&JY^`ds}z{ncJ`q|4+_UX%BbZ42G zjy%KoKzE0-fnozLCkChnPE>wlIC82#6>qeo;ud%?r?bewqZx%T?@a8$dAztk2a4uI@I-){h_Q?l9wC^Ts; ziYBQRK{qP*Yrx^Py*lrznpJ*jV~~|QC9wjesbq2XiqGRjO2|q`7;npmy;*ZhQ*#Uc zpD8V9s_vP_lYQ|&Q_+7ss!buJpRbgIrebL(-?VmJ-^nw%iJ%ErK_-Kyu{}jZ)c=`E z(1d4RH<{J^IwXrQEH0B@f%4Q$(8Q)0G*Qbz6E;{22r5aLoAft7iD%H`)Ptmt+d`v3 z(vmebsrge09%ePreDGry*?R5 z2v`Q(elSdv4=a`9G^V!#e4MhJ3&e)=pWoH^ViYZW7rZeUU4UU^>NM)`6V!ZG8 zXu$Sk(=CHw`L4t8?mF<5DY*HNkKfneYxy`CSaxU-T)4?F;g7oG=k@qnK8|j0I41~L z=k{asJs$+}F{=aMI{?1BxtU=;%gjI5+R4W%*dL~%{X3x-PrhZK^K%)#mhaIh6+Mcf zvCWI&7Jffq>V)Y%4!+01cQ;_x2S4Pap0C5#rgztZRCL##*m)qxrT1}8QHmjGa42E80FSMKx(7Cdwo-#gD}2nIZRv3$=pe9b1GpA0nw zAE05F5Bd0|KcOG8pEtg&Avnb%_$LoPXbyFH{u21O$z(qkVFj1ilv`XTnj{hqBt!!2)rL*wo&BB+>!k<42KhD`J1Cqh-o=}Y`8*$&Rh;Q~% zAnv&ii9hl*;j`199e;Lyv-xL-Kb<@unkCOiX5kOd!e8KJ!1B%J_k#Z>ea%j<1qpIF zhhj9sL$CER5PbtLn{ayYmjl1k*n2~;ILHFG_S=Aw(=_nAyzo6B7|ez%w3_kr8?)esyuZ+Xr$?U&{G1^6va z`REMqHtmn`ncs&EKf{?{EdS!*5b)pc_zw@B4LlBC6087jKh)>b)%R?EWamDN|JT~% z?DWQG$x{UWE9j4&mi4&{_-{R&`_GSf_&b0<>f!GO{3tS>(!=)yU+CfY zlK*?jh@Q0}SQPva_#wdQ*@^!Q_!97+21QW5-vK`t{&a7I?1-KNWcArO9Zu!Cy*#;J-CE4@T_2EE&DW;3L5Q z;a$mSyTMDqe+_w5{>8y=;0xcAjJi$u`M@s){u+Z{3Vg|x$>>6Ze*pLe?~U{S1n~X9 zmz(e}0e=wqn+^VT;8X8QMi&|UXTTo^{yKyI5%@p8KN%Gaeh@19Vc;tbekAZCu1ZG# zg@5X2DgMv9IvK5jUWu;+&UcZsCt`bS1^)SKlhKu^4~DM=&i9tv@P6QDU!ROPj$-&f z0N?r%Y%xI&;uFBfZcIiKCjHj{zw6`42*Xbae@DBs;{A4ovoWXAd{s?gTCC2{@@ZnDKJAi{2O&R<~z=wfzUy<^(1Hbv!Wb_B4 zk5hr~`%*IcmC3IU_?5RMqdyz|Vc>gjPezxT^j`sd;PiKI0)9K%E3t;g#lbs(m#u$mTo_yjd@JJfo%V-;pZmu*V|+9L zc{p0((G)wLITZfrUf_2kd=0{m!}pKCH^IJ72h8{lXbATse5r~5Y~T+AKUKm}7T_FD zQT{{G-dlk`itu|(crWni?P~wb|7_qlpgkTh;R}ON;Lk$1P5&Q(?=$6F0{Pwsd#T z1m%VaSiyf#a5RUcmn5T;1z#9&oBPm&* zVZ;A*;M50Dj6Qz%#CHK*hj5NJsJ|Zqk1kF|N1{D4zuy6W7VyU;9LIk!h49mV9&kJU zUj}`j1bhYft$t4dZs!k&1&0LdfOjH%gGp}~_$J_=Gv%ECel~E9OPT*`fKLM7WbjLX z?*@LY;r}S`3xI#qgx?B$FYq>le;v4A-g|)C`32kWFMwYM{+kT{V+{B7^91ml5k6?b z4?PI>?b*k1z&o!w6DJc@Jqe)UIUy*60ARX z3-J5B^1K)Lxv-}*O!^-MZuP$e`o9_Y!{E32{~GW+VK0g=4DJKI*URtc!0l%lfJcEp ziuiqissDoxhCN`s*=+FO6mvp(8J8Yk@;4olun!43g?qL$Cl`t@334NalV$z(uQ+g8 zx0AbyDPZB3PvX&?=4ZTzQlHLWdwiolHa51oolOzAV2llKybl2PtfujDPAmaeX1L*8 zoxv$}1Wk;NX1FU<#{2Nbnu2muD^|;Uh6e_-oBM`_vsvEEEl=UWVq;Bh2pKD7&BbY1 zUC!oeyFu;Ul*Lm6C6HxmL#1#A94}S){)dv?JX!B$ z?q?{g+9pHc>Fw`9Rz_Njs2qy@?b_#tw_KZrhU^t~3&?Q+d%abztjm=HE|v>mkGSg< zbRo&*5+rCy^IbjXWn|s3!+CiJ={qv4fS zbFa7lk}n0+o3sj>URyj<>Tq)?8DlvprZO@c6OL@taeqda!X1Kmw7 zq2YRp`NwWYX^+5iqe>vX6(u0AL%FL%4ls9!9B%FoIow_h(vsBO6_QkiqIZ29uG!&k zj{xRkhq)J`H+CdIF5(CzS8N2*yEOvx?h65_Ro-w3c*~@zwXId0iQGpCHp3UlHs4sa zWU32ou+Y)j(Ihp5H-Ammirbq;cF(<+LJJz5teke*#?5`})@L(K%|W*396ar|Z#~km zmq&F!3tM*MC7(0C3(LCZrP+}^ycz5BF#YMWPr<2(?BuQhkIlz(4E%s`eKvtliZ9B? zInaoF9_+T#x8*98+#c>}vPg5%#>OMAC}eRGPq;<7h&E=uhU~DR#E6Jj1M&gve5{?IU}*t?38wdFt8XOeyCRp(|_>`VV+8x`*ZK0|R_+`%ollAO2GXAQ2uEV_z4i z?s|4Dh>t*-ad&$5)0J2{l*rGD6@@)-1o%`OzQUP=S(I5Z*_7wTl+xopUv;|LIZsuZ zj!p%QFL?*!l*rFnt%~>8S?5shT$N+@9J{=Dq>ya#;>kXKd{Md>04o^Q0=!p!wyl~t!i3W&;*dVL9&TcMLkIz`G;-Qk`J|&yi9sG@(2iC3KoE_M(VW@XF zJG>T8-NHBmmwM`%(TE^CtivbE6(gUZwdx*C;GCK@Zmp|&4bjOo_wK+fGSr?kB za}`XN1cQU>*hF|t!_|g%hJJF|`ZN)74#hD)7)LSOpbbNvu_?T@&8zzs@u&5r_ +.\" +.\" 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. +.\" +.\" This program is distributed in the hope that it will be useful, +.\" but WITHOUT ANY WARRANTY; without even the implied warranty of +.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +.\" GNU General Public License for more details. +.\" +.\" You should have received a copy of the GNU General Public License +.\" along with this program; if not, write to the Free Software +.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +.\" +.\" +.SH NAME +ipset \- IP set administration +.SH SYNOPSIS +.BR "ipset -N " "set type-specification [options]" +.br +.BR "ipset -[XFLSHh] " "[set] [options]" +.br +.BR "ipset -[EW] " "from-set to-set" +.br +.BR "ipset -[ADT] " "set entry" +.br +.BR "ipset -R " +.SH DESCRIPTION +.B Ipset +is used to set up, maintain, and inspect so called IP sets in the Linux +kernel. Each set can contain a fixed number of entries and at every entry +there might be child sets, up to a given level. Child sets at the same +level must have the same type. +.P +In spite it is called IP sets, depending on the type the sets may +store not only IP addresses but (TCP/UDP) port numbers as well, or +additional informations besides IP addresses: the word IP means a general +term here. See the set type definitions below. +.P +Child sets are identified +by `IP', without the possible additional informations stored in the set. +There is no other relationship between the set entry and the child set +at that entry: the set entry can be added, deleted without disturbing +the child set and vice versa. Therefore below we denote pure IPs +identifying a child set by `IP', while entries in a set by `entry'. +.SH OPTIONS +The options that are recognized by +.B ipset +can be divided into several different groups. +.SS COMMANDS +These options specify the specific action to perform. Only one of them +can be specified on the command line unless otherwise specified +below. For all the long versions of the command and option names, you +need to use only enough letters to ensure that +.B ipset +can differentiate it from all other options. +.TP +.BI "-N, --create " "\fIsetname\fP type0[,type1...] type0-options" +Create a set identified with setname, with listed types. +Type0-specific options must be supplied. When more than one set +type is given on the command line, room for the first level child +sets are automatically reserved. +.TP +.BI "-N, --create " "\fIsetname:IP[,...]\fP type type-options" +Create a child set at setname:IP[,...] identified with type, +which must correspond to the set type used when the set was created. +The parent set and parent child sets must exist. When the +.B "-c, --childsets" +option is used, space for child sets will be reserved. +.TP +.BI "-X, --destroy " "[\fIsetname[:IP,...]\fP]" +Destroy the specified set or child set, or all sets if none is +given. +.TP +.BI "-F, --flush " "[\fIsetname[:IP,...]\fP]" +Delete all entries from the specified set or child set, or from +all sets if none is given. When the +.B "-c, --childsets" +option is specified, child sets are recursively flushed too. +.TP +.BI "-E, --rename " "\fIfrom-setname\fP \fIto-setname\fP" +Rename a set. Set identified by to-setname must not exist. +.TP +.BI "-W, --swap " "\fIfrom-setname\fP \fIto-setname\fP" +Swap the content of two sets. Both sets must exist. +.TP +.BI "-L, --list " "[\fIsetname[:IP,...]\fP]" +List the entries from the specified set or child set, or from +all sets if none is given. When the +.B "-c, --childsets" +option is specified, child sets are recursively listed too. +The +.B "-n, --numeric" +option can be used to suppress name lookups and generate numeric +output. When the +.B "-s, --sorted" +option is given, the entries are listed sorted. +.TP +.BI "-S, --save " "[\fIsetname[:IP,...]\fP]" +Save the set or child set, or all sets if none specified to stdout +in a format, that --restore can read. +.TP +.BI "-R, --restore " +Restore from stdin a saved session generated by --save. +.TP +.BI "-A, --add " "\fIsetname[:IP,...]\fP \fIentry[:entry,...]\fP" +Add an entry to a (child) set or multiple entries to the child sets +defined by the entries. In another words, the command +.nf + ipset -A set entry1,entry2 +.fi +is equivalent to +.nf + ipset -A set entry1 + ipset -A set:entry1-IP entry2 +.fi +.TP +.BI "-D, --del " "\fIsetname[:IP,...]\fP \fIentry[:entry,...]\fP" +Delete an entry from a (child) set or multiple entries from the child sets +defined by the entries. +.P +When multiple entries are added or deleted and the command fails at +a deeper level, the successfully added/deleted entries are not restored +to their previous value. +.TP +.BI "-T, --test " "\fIsetname[:IP,...]\fP \fIentry[:entry,...]\fP" +Test if an entry or multiple entries exist in a (child) set. Exit status +number is nonzero if any tested entry is missing from the set and +zero if they are all exists. +.TP +.BI "-H, --help " "[settype] [options]" +Print help and settype specific help. Some set types has additional +help options, see below. +.SS "OTHER OPTIONS" +The following additional options can be specified: +.TP +.B "-s, --sorted" +Sorted output. When listing sets, entries are listed sorted. +.TP +.B "-n, --numeric" +Numeric output. When listing sets, IP addresses and port numbers +will be printed in numeric format. By default the program will +try to display them as host names, network names, or services +(whenever applicable). +.TP +.B "-q, --quiet" +Suppress any output to stdout and stderr. Ipset will still return +possible errors. +.TP +.B "-c, --childsets" +Space will be reserved for child sets when creating a set, or list/flush +operation valid for child sets too. +.TP +.B "-i, --hint" +Hint best settype initialization parameters (for hash type sets). +.SH SET TYPES +ipset supports the following set types: +.SS ipmap +The ipmap set type uses a memory range, where each bit represents +one IP address. An ipmap set can store up to 65535 (B-class network) +IP addresses. The ipmap set type is very fast and memory cheap, great +for use when one want to match certain IPs in a range. Using the +.B "--netmask" +option with a CIDR netmask value between 0-32 when creating an ipmap +set, you will be able to store and match network addresses: i.e an +IP address will be in the set if the value resulted by masking the address +with the specified netmask can be found in the set. +.P +Options to use when creating an ipmap set: +.TP +.BR "--from " from-IP +.TP +.BR "--to " to-IP +Create an ipmap set from the specified range. +.TP +.BR "--network " IP/mask +Create an ipmap set from the specified network. +.TP +.BR "--netmask " CIDR-netmask +When the optional +.B "--netmask" +parameter specified, network addresses will be +stored in the set instead of IP addresses, and the from-IP parameter +must be a network address. +.SS macipmap +The macipmap set type uses a memory range, where each 8 bytes +represents one IP and a MAC addresses. A macipmap set type can store +up to 65535 (B-class network) IP addresses with MAC. +When adding an entry to a macipmap set, you must specify the entry as +.I IP%MAC +When deleting or testing macipmap entries, the +.I %MAC +part is not mandatory. +.P +Options to use when creating an macipmap set: +.TP +.BR "--from " from-IP +.TP +.BR "--to " to-IP +Create a macipmap set from the specified range. +.TP +.BR "--network " IP/mask +Create a macipmap set from the specified network. +.TP +.BR "--matchunset" +When the optional +.B "--matchunset" +parameter specified, IP addresses which could be stored +in the set but not set yet, will always match. +.SS portmap +The portmap set type uses a memory range, where each bit represents +one port. A portmap set type can store up to 65535 ports. +The portmap set type is very fast and memory cheap. +.P +Options to use when creating an portmap set: +.TP +.BR "--from " from-port +.TP +.BR "--to " to-port +Create a portmap set from the specified range. +.SS iphash +The iphash set type uses a fixed size hash to store the IP addresses +and can store up to 65535 (size of a B-class network) addresses. The +iphash set type is fast and great for use to store +random addresses. By supplyig the +.B "--netmask" +option with a CIDR netmask value between 0-32 at creating the set, +you will be able to store and match network addresses instead: i.e +an IP address will be in the set if the value of the address +masked with the specified netmask can be found in the set. +When adding and IP address to the hash, the IP address can be preceded +by `+', by which you can force to overwrite already existing entries +in the hash. +.P +In help mode you can use the +.B "--hint" +option to find the the smallest hashsize with the corresponding initval +for your hash entries. +.P +Options to use when creating an iphash set: +.TP +.BR "--hashsize " hashsize +.TP +.BR "--initval " hash-initval +Create an iphash set with the specified hashsize and initial +(random) hash parameter. (The +.B "--initval" +parameter is optional.) +.TP +.BR "--netmask " CIDR-netmask +When the optional +.B "--netmask" +parameter specified, network addresses will be +stored in the set instead of IP addresses. +.TP +The possible help mode options are: +.TP +.BI "-i, --hint" +Enable hinting mode to find the best (smallest) hash size. The entries +are fed via standard input. +.TP +.BI "--try " number +How many times should the program try the same hash size with +different random initvals (default 8). +.TP +.BI "--factor " number +The starting hash size is so many times the number of the entries +(default 4). +.SH DIAGNOSTICS +Various error messages are printed to standard error. The exit code +is 0 for correct functioning. Errors which appear to be caused by +invalid or abused command line parameters cause an exit code of 2, and +other errors cause an exit code of 1. +.SH BUGS +Bugs? No, just funny features. :-) +OK, just kidding... +.SH SEE ALSO +.BR iptables (8), +.SH AUTHORS +Jozsef Kadlecsik wrote ipset, which is based on ippool by +Joakim Axelsson, Patrick Schaaf and Martin Josefsson. +.\" .. and did I mention that we are incredibly cool people? +.\" .. sexy, too .. +.\" .. witty, charming, powerful .. +.\" .. and most of all, modest .. diff --git a/ipset.c b/ipset.c new file mode 100644 index 0000000..04d2487 --- /dev/null +++ b/ipset.c @@ -0,0 +1,2326 @@ +/* Copyright 2000-2004 Joakim Axelsson (gozem@linux.nu) + * Patrick Schaaf (bof@bof.de) + * Jozsef Kadlecsik (kadlec@blackhole.kfki.hu) + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ipset.h" + +/* The list of all sets */ +static struct set *all_sets = NULL; + +/* The list of loaded set types */ +static struct settype *all_settypes = NULL; + +/* Suppress output to stdout and stderr? */ +static int option_quiet = 0; + +/* Data from the command line: */ +static char *set_name = NULL; /* Name of the set */ +static char *set_typename[IP_SET_LEVELS]; /* Set typenames */ +static unsigned int set_typename_level = 0; /* Size of set_typename */ +static ip_set_ip_t set_ip[IP_SET_LEVELS]; /* IP addresses of child set */ +static unsigned int set_level = 0; /* Size of set_ip */ +static unsigned int ip_level = 0; /* Size of add/del/test addresses */ + +/* Max size of a set when hinting. */ +#define MAX_HINT_SIZE 65536 + +#ifdef IP_SET_DEBUG +int option_debug = 0; +#endif + +#define OPTION_OFFSET 256 +static unsigned int global_option_offset = 0; + +/* Now most of these command parsing functions are borrowed from iptables.c */ + +/* Commands */ +#define CMD_NONE 0x0000U +#define CMD_CREATE 0x0001U /* -N */ +#define CMD_DESTROY 0x0002U /* -X */ +#define CMD_FLUSH 0x0004U /* -F */ +#define CMD_RENAME 0x0008U /* -E */ +#define CMD_SWAP 0x0010U /* -W */ +#define CMD_LIST 0x0020U /* -L */ +#define CMD_SAVE 0x0040U /* -S */ +#define CMD_RESTORE 0x0080U /* -R */ +#define CMD_ADD 0x0100U /* -A */ +#define CMD_DEL 0x0200U /* -D */ +#define CMD_TEST 0x0400U /* -T */ +#define CMD_HELP 0x0800U /* -H */ +#define CMD_VERSION 0x1000U /* -V */ +#define CMD_CREATE_CHILD 0x2000U /* -N !!! */ +#define NUMBER_OF_CMD 13 +static const char cmdflags[] = { + 'N', 'X', 'F', 'E', 'W', 'L', 'S', 'R', + 'A', 'D', 'T', 'H', 'V', +}; + +/* Options */ +#define OPT_NONE 0x0000U +#define OPT_NUMERIC 0x0001U /* -n */ +#define OPT_SORTED 0x0002U /* -s */ +#define OPT_QUIET 0x0004U /* -q */ +#define OPT_DEBUG 0x0008U /* -z */ +#define OPT_CHILDSETS 0x0010U /* -c */ +#define OPT_HINT 0x0020U /* -i */ +#define NUMBER_OF_OPT 6 +static const char optflags[] = + { 'n', 's', 'q', 'z', 'c', 'i' }; + +static struct option opts_long[] = { + /* set operations */ + {"create", 1, 0, 'N'}, + {"destroy", 2, 0, 'X'}, + {"flush", 2, 0, 'F'}, + {"rename", 1, 0, 'E'}, + {"swap", 1, 0, 'W'}, + {"list", 2, 0, 'L'}, + + {"save", 2, 0, 'S'}, + {"restore", 0, 0, 'R'}, + + /* ip in set operations */ + {"add", 1, 0, 'A'}, + {"del", 1, 0, 'D'}, + {"test", 1, 0, 'T'}, + + /* free options */ + {"numeric", 0, 0, 'n'}, + {"sorted", 0, 0, 's'}, + {"quiet", 0, 0, 'q'}, + {"childsets",0, 0, 'c'}, + {"hint", 0, 0, 'i'}, + +#ifdef IP_SET_DEBUG + /* debug (if compiled with it) */ + {"debug", 0, 0, 'z'}, +#endif + + /* version and help */ + {"version", 0, 0, 'V'}, + {"help", 2, 0, 'H'}, + + /* end */ + {0} +}; + +static char opts_short[] = + "-N:X::F::E:W:L::S::RA:D:T:nsqzciVh::H::"; + +/* Table of legal combinations of commands and options. If any of the + * given commands make an option legal, that option is legal (applies to + * CMD_LIST and CMD_ZERO only). + * Key: + * + compulsory + * x illegal + * optional + */ + +static char commands_v_options[NUMBER_OF_CMD][NUMBER_OF_OPT] = { + /* -n -s -q -z -c -i*/ + /*CREATE*/ {'x', 'x', ' ', ' ', ' ', 'x'}, + /*DESTROY*/ {'x', 'x', ' ', ' ', 'x', 'x'}, + /*FLUSH*/ {'x', 'x', ' ', ' ', ' ', 'x'}, + /*RENAME*/ {'x', 'x', ' ', ' ', 'x', 'x'}, + /*SWAP*/ {'x', 'x', ' ', ' ', 'x', 'x'}, + /*LIST*/ {' ', ' ', 'x', ' ', ' ', 'x'}, + /*SAVE*/ {'x', 'x', ' ', ' ', 'x', 'x'}, + /*RESTORE*/ {'x', 'x', ' ', ' ', 'x', 'x'}, + /*ADD*/ {'x', 'x', ' ', ' ', 'x', 'x'}, + /*DEL*/ {'x', 'x', ' ', ' ', 'x', 'x'}, + /*TEST*/ {'x', 'x', ' ', ' ', 'x', 'x'}, + /*HELP*/ {'x', 'x', 'x', ' ', 'x', ' '}, + /*VERSION*/ {'x', 'x', 'x', ' ', 'x', 'x'}, +}; + +void exit_tryhelp(int status) +{ + fprintf(stderr, + "Try `%s -H' or '%s --help' for more information.\n", + program_name, program_name); + exit(status); +} + +void exit_error(enum exittype status, char *msg, ...) +{ + va_list args; + + if (!option_quiet) { + va_start(args, msg); + fprintf(stderr, "%s v%s: ", program_name, program_version); + vfprintf(stderr, msg, args); + va_end(args); + fprintf(stderr, "\n"); + if (status == PARAMETER_PROBLEM) + exit_tryhelp(status); + if (status == VERSION_PROBLEM) + fprintf(stderr, + "Perhaps %s or your kernel needs to be upgraded.\n", + program_name); + } + + exit(status); +} + +void ipset_printf(char *msg, ...) +{ + va_list args; + + if (!option_quiet) { + va_start(args, msg); + vfprintf(stdout, msg, args); + va_end(args); + fprintf(stdout, "\n"); + } +} + +static void generic_opt_check(int command, int options) +{ + int i, j, legal = 0; + + /* Check that commands are valid with options. Complicated by the + * fact that if an option is legal with *any* command given, it is + * legal overall (ie. -z and -l). + */ + for (i = 0; i < NUMBER_OF_OPT; i++) { + legal = 0; /* -1 => illegal, 1 => legal, 0 => undecided. */ + + for (j = 0; j < NUMBER_OF_CMD; j++) { + if (!(command & (1 << j))) + continue; + + if (!(options & (1 << i))) { + if (commands_v_options[j][i] == '+') + exit_error(PARAMETER_PROBLEM, + "You need to supply the `-%c' " + "option for this command\n", + optflags[i]); + } else { + if (commands_v_options[j][i] != 'x') + legal = 1; + else if (legal == 0) + legal = -1; + } + } + if (legal == -1) + exit_error(PARAMETER_PROBLEM, + "Illegal option `-%c' with this command\n", + optflags[i]); + } +} + +static char opt2char(int option) +{ + const char *ptr; + for (ptr = optflags; option > 1; option >>= 1, ptr++); + + return *ptr; +} + +static char cmd2char(int option) +{ + const char *ptr; + for (ptr = cmdflags; option > 1; option >>= 1, ptr++); + + return *ptr; +} + +static void set_command(int *cmd, const int newcmd) +{ + if (*cmd != CMD_NONE) + exit_error(PARAMETER_PROBLEM, "Can't use -%c with -%c\n", + cmd2char(newcmd), cmd2char(newcmd)); + *cmd = newcmd; +} + +static void add_option(unsigned int *options, unsigned int option) +{ + if (*options & option) + exit_error(PARAMETER_PROBLEM, + "multiple -%c flags not allowed", + opt2char(option)); + *options |= option; +} + +void *ipset_malloc(size_t size) +{ + void *p; + + if (size == 0) + return NULL; + + if ((p = malloc(size)) == NULL) { + perror("ipset: malloc failed"); + exit(1); + } + return p; +} + +void ipset_free(void **data) +{ + if (*data == NULL) + return; + + free(*data); + *data = NULL; +} + +static struct option *merge_options(struct option *oldopts, + const struct option *newopts, + unsigned int *option_offset) +{ + unsigned int num_old, num_new, i; + struct option *merge; + + for (num_old = 0; oldopts[num_old].name; num_old++); + for (num_new = 0; newopts[num_new].name; num_new++); + + global_option_offset += OPTION_OFFSET; + *option_offset = global_option_offset; + + merge = malloc(sizeof(struct option) * (num_new + num_old + 1)); + memcpy(merge, oldopts, num_old * sizeof(struct option)); + for (i = 0; i < num_new; i++) { + merge[num_old + i] = newopts[i]; + merge[num_old + i].val += *option_offset; + } + memset(merge + num_old + num_new, 0, sizeof(struct option)); + + return merge; +} + +static char *ip_tohost(const struct in_addr *addr) +{ + struct hostent *host; + + DP("ip_tohost()"); + + if ((host = gethostbyaddr((char *) addr, + sizeof(struct in_addr), + AF_INET)) != NULL) { + DP("%s", host->h_name); + return (char *) host->h_name; + } + + return (char *) NULL; +} + +static char *ip_tonetwork(const struct in_addr *addr) +{ + struct netent *net; + + DP("ip_tonetwork()"); + + if ((net = getnetbyaddr((long) ntohl(addr->s_addr), + AF_INET)) != NULL) + return (char *) net->n_name; + + return (char *) NULL; +} + +/* Return a string representation of an IP address. + * Please notice that a pointer to static char* area is returned. + */ +char *ip_tostring(ip_set_ip_t ip, unsigned options) +{ + struct in_addr addr; + char *name; + + addr.s_addr = htonl(ip); + + if (!(options & OPT_NUMERIC)) { + if ((name = ip_tohost(&addr)) != NULL || + (name = ip_tonetwork(&addr)) != NULL) + return name; + } + + return inet_ntoa(addr); +} + +/* Fills the 'ip' with the parsed ip or host in host byte order */ +void parse_ip(const char *str, ip_set_ip_t * ip) +{ + struct hostent *host; + struct in_addr addr; + + if (inet_aton(str, &addr) != 0) { + *ip = ntohl(addr.s_addr); /* We want host byte order */ + +#ifdef IP_SET_DEBUG + { + /*DEBUG*/ char *p = (char *) ip; + DP("PARSE_IP %x %x %x %x %x %x", *ip, 0xC10BF8A6, + p[0], p[1], p[2], p[3]); + } +#endif + + return; + } + + host = gethostbyname(str); + if (host != NULL) { + if (host->h_addrtype != AF_INET || + host->h_length != sizeof(struct in_addr)) + exit_error(PARAMETER_PROBLEM, + "host/network `%s' not an internet name", + str); + if (host->h_addr_list[1] != 0) + exit_error(PARAMETER_PROBLEM, + "host/network `%s' resolves to serveral ip-addresses. " + "Please specify one.", str); + + *ip = ntohl(((struct in_addr *) host->h_addr_list[0])->s_addr); + +#ifdef IP_SET_DEBUG + { + /*DEBUG*/ char *p = (char *) ip; + DP("PARSE_IP %x %x %x %x %x %x", *ip, 0xC10BF8A6, + p[0], p[1], p[2], p[3]); + } +#endif + + return; + } + + exit_error(PARAMETER_PROBLEM, "host/network `%s' not found", str); +} + +/* Fills 'mask' with the parsed mask in host byte order */ +void parse_mask(const char *str, ip_set_ip_t * mask) +{ + struct in_addr addr; + unsigned int bits; + + DP("parse_mask %s", str); + + if (str == NULL) { + /* no mask at all defaults to 32 bits */ + *mask = 0xFFFFFFFF; + return; + } + if (strchr(str, '.') && inet_aton(str, &addr) != 0) { + *mask = ntohl(addr.s_addr); /* We want host byte order */ + return; + } + if (sscanf(str, "%d", &bits) != 1 || bits < 0 || bits > 32) + exit_error(PARAMETER_PROBLEM, + "invalid mask `%s' specified", str); + + DP("bits: %d", bits); + + *mask = 0xFFFFFFFF << (32 - bits); +} + +/* Combines parse_ip and parse_mask */ +void +parse_ipandmask(const char *str, ip_set_ip_t * ip, ip_set_ip_t * mask) +{ + char buf[256]; + char *p; + + strncpy(buf, str, sizeof(buf) - 1); + buf[255] = '\0'; + + if ((p = strrchr(buf, '/')) != NULL) { + *p = '\0'; + parse_mask(p + 1, mask); + } else + parse_mask(NULL, mask); + + /* if a null mask is given, the name is ignored, like in "any/0" */ + if (*mask == 0U) + *ip = 0U; + else + parse_ip(buf, ip); + + DP("parse_ipandmask: %s ip: %08X (%s) mask: %08X", + str, *ip, ip_tostring(*ip, 0), *mask); + + /* Apply the netmask */ + *ip &= *mask; + + DP("parse_ipandmask: %s ip: %08X (%s) mask: %08X", + str, *ip, ip_tostring(*ip, 0), *mask); +} + +/* Return a string representation of a port + * Please notice that a pointer to static char* area is returned + * and we assume TCP protocol. + */ +char *port_tostring(ip_set_ip_t port, unsigned options) +{ + struct servent *service; + static char name[] = "65535"; + + if (!(options & OPT_NUMERIC) + && (service = getservbyport(htons(port), "tcp"))) + return service->s_name; + + sprintf(name, "%u", port); + return name; +} + +int +string_to_number(const char *str, unsigned int min, unsigned int max, + ip_set_ip_t *port) +{ + long number; + char *end; + + /* Handle hex, octal, etc. */ + errno = 0; + number = strtol(str, &end, 0); + if (*end == '\0' && end != str) { + /* we parsed a number, let's see if we want this */ + if (errno != ERANGE && min <= number && number <= max) { + *port = number; + return 0; + } + } + return -1; +} + +static int +string_to_port(const char *str, ip_set_ip_t *port) +{ + struct servent *service; + + if ((service = getservbyname(str, "tcp")) != NULL) { + *port = ntohs((unsigned short) service->s_port); + return 0; + } + + return -1; +} + +/* Fills the 'ip' with the parsed port in host byte order */ +void parse_port(const char *str, ip_set_ip_t *port) +{ + if ((string_to_number(str, 0, 65535, port) != 0) + && (string_to_port(str, port) != 0)) + exit_error(PARAMETER_PROBLEM, "Invalid TCP port `%s' specified", str); +} + +static struct settype *settype_find(const char *typename) +{ + struct settype *runner = all_settypes; + + DP("settype %s", typename); + + while (runner != NULL) { + if (strncmp(runner->typename, typename, + IP_SET_MAXNAMELEN) == 0) + return runner; + + runner = runner->next; + } + + return NULL; /* not found */ +} + +static struct settype *settype_load(const char *typename) +{ + char path[sizeof(IPSET_LIB_DIR) + sizeof(IPSET_LIB_NAME) + + strlen(typename)]; + struct settype *settype; + + /* do some search in list */ + settype = settype_find(typename); + if (settype != NULL) + return settype; /* found */ + + /* Else we have to load it */ + sprintf(path, IPSET_LIB_DIR IPSET_LIB_NAME, typename); + + if (dlopen(path, RTLD_NOW)) { + /* Found library. */ + + settype = settype_find(typename); + + if (settype != NULL) + return settype; + } + + /* Can't load the settype */ + exit_error(PARAMETER_PROBLEM, + "Couldn't load settype `%s':%s\n", + typename, dlerror()); + + return NULL; /* Never executed, but keep compilers happy */ +} + +/* Register a new set type */ +void settype_register(struct settype *settype) +{ + struct settype *chk; + + DP("register_settype '%s'\n", settype->typename); + + /* Check if this typename already exists */ + chk = settype_find(settype->typename); + + if (chk != NULL) + exit_error(OTHER_PROBLEM, + "Set type '%s' already registred!\n", + settype->typename); + + /* Check version */ + if (settype->protocol_version != IP_SET_PROTOCOL_VERSION) + exit_error(OTHER_PROBLEM, + "Set type is of wrong protocol version %u!" + " I'm am of version %u.\n", settype->typename, + settype->protocol_version, + IP_SET_PROTOCOL_VERSION); + + /* Insert first */ + settype->next = all_settypes; + settype->data = ipset_malloc(settype->create_size); + all_settypes = settype; + + DP("ip_set: register settype end '%s'\n", settype->typename); +} + +static int kernel_getsocket(void) +{ + int sockfd = -1; + + sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); + if (sockfd < 0) + exit_error(OTHER_PROBLEM, + "You need to be root to perform this command."); + + return sockfd; +} + +static void kernel_error(int err) +{ + /* translate errnos, as returned by our *sockopt() functions */ + exit_error(OTHER_PROBLEM, "Error from kernel: %s", strerror(err)); +} + +static void kernel_sendto(void *data, size_t size) +{ + int res; + int sockfd = kernel_getsocket(); + + /* Send! */ + res = setsockopt(sockfd, SOL_IP, SO_IP_SET, data, size); + + DP("kernel_sendto() res=%d errno=%d\n", res, errno); + + if (res != 0) + kernel_error(errno); +} + +/* Used by addip and delip that has to handle the EEXIST error better */ +static int kernel_sendto_handleexist(void *data, size_t size) +{ + int res; + int sockfd = kernel_getsocket(); + + /* Send! */ + res = setsockopt(sockfd, SOL_IP, SO_IP_SET, data, size); + + DP("kernel_sendto_handleexist() res=%d errno=%d\n", res, errno); + + if (res != 0 && errno == EEXIST) + return -1; + else if (res != 0) + kernel_error(errno); + + return 0; /* all ok */ +} + +static void kernel_getfrom(void *data, size_t * size) +{ + int res; + int sockfd = kernel_getsocket(); + + /* Send! */ + res = getsockopt(sockfd, SOL_IP, SO_IP_SET, data, size); + + DP("kernel_getfrom() res=%d errno=%d\n", res, errno); + + if (res != 0) + kernel_error(errno); +} + +static int get_protocolversion(void) +{ + struct ip_set_req_version req_version; + size_t size = sizeof(struct ip_set_req_version); + + req_version.op = IP_SET_OP_VERSION; + + kernel_getfrom(&req_version, &size); + + DP("get_protocolversion() ver=%d", req_version.version); + + return req_version.version; +} + +static void set_append(struct set *set) +{ + struct set *entry = all_sets; + + while (entry != NULL && entry->next != NULL) + entry = entry->next; + + if (entry == NULL) + all_sets = set; + else + entry->next = set; +} + +static void get_sets(void) +{ + void *data = NULL; + struct ip_set_req_listing_size req_list; + int sockfd = kernel_getsocket(); + size_t size; + size_t eaten = 0; + int i, res; + + DP("get_sets()"); + for (i = 0; i < LIST_TRIES; i++) { + req_list.op = IP_SET_OP_LISTING_SIZE; + size = sizeof(req_list); + kernel_getfrom(&req_list, &size); + size = req_list.size; + + DP("got size: %d", size); + + if (req_list.size == 0) + return; /* No sets in kernel */ + + data = ipset_malloc(size); + ((struct ip_set_req_base *) data)->op = IP_SET_OP_LISTING; + + /* Get! */ + res = getsockopt(sockfd, SOL_IP, SO_IP_SET, data, &size); + DP("list_get getsockopt() res=%d errno=%d\n", res, errno); + + if (res == 0) + goto got_sets; /* all OK */ + else if (errno != ENOMEM) + break; /* Not a memory error */ + + DP("not enough mem, looping again"); + free(data); + } + + if (errno == ENOMEM) + exit_error(OTHER_PROBLEM, + "Tried to list sets from kernel %d times" + " and failed. Please try again when the load on" + " the sets has gone down.", LIST_TRIES); + else + kernel_error(errno); + + got_sets: + + DP("get_sets() - size=%d data=%p", size, data); + /* Handle the data */ + while (eaten < size) { + struct ip_set_req_listing *header = + (struct ip_set_req_listing *) (data + eaten); + struct set *set = ipset_malloc(sizeof(struct set)); + + memset(set, 0, sizeof(struct set)); + + DP("fillin %d %p", eaten, header); + +#ifdef IP_SET_DEBUG + /* DEBUG */ + { + void *i; + + DP("SET DATA:"); + + for (i = data + eaten; + i < + data + eaten + + sizeof(struct ip_set_req_listing); i += 4) { + unsigned *j = (unsigned *) i; + DP("%x", *j); + } + } +#endif + + /* Fill in data */ + DP("Processing set '%s'", header->name); + strcpy(set->name, header->name); + set->id = header->id; + set->levels = header->levels; + set->ref = header->ref; + for (i = 0; i < set->levels; i++) { + set->settype[i] = settype_load(header->typename[i]); + set->private[i].adt = + ipset_malloc(set->settype[i]->req_size); + } + + eaten += sizeof(struct ip_set_req_listing); + + DP("insert in list"); + set_append(set); + } + + DP("free"); + + free(data); + + DP("get_sets() eaten = %d, size = %d", eaten, size); + + if (eaten != size) + exit_error(OTHER_PROBLEM, + "Desynched in listing of sets from kernel. " + "Read %d bytes. Worked %d bytes.", size, eaten); +} + +struct set *set_find(const char *setname) +{ + struct set *set = all_sets; + + DP("%s", setname); + + while (set != NULL) { + if (strcmp(setname, set->name) == 0) + return set; + set = set->next; + } + return NULL; +} + +static struct set *set_checkname(const char *setname) +{ + char *saved = strdup(setname); + char *ptr, *tmp = saved; + struct set *set; + int i; + + DP("%s", setname); + + /* Cleanup */ + if (set_name != NULL) + free(set_name); + + for (i = 0; i < IP_SET_SETIP_LEVELS; i++) + set_ip[i] = 0; + set_level = 0; + + /* name[:ip,...] */ + ptr = strsep(&tmp, ":"); + if (strlen(ptr) > IP_SET_MAXNAMELEN - 1) + exit_error(PARAMETER_PROBLEM, + "Setname '%s' in '%s' too long. Max %d characters.", + ptr, setname, IP_SET_MAXNAMELEN - 1); + + DP("%s (%s)", ptr, tmp); + set = set_find(ptr); + if (!set && tmp) + exit_error(PARAMETER_PROBLEM, + "Set '%s' not found for '%s'\n", + ptr, setname); + + set_name = strdup(ptr); + + while (set_level < IP_SET_SETIP_LEVELS && tmp) { + ptr = strsep(&tmp, ","); + switch (set->settype[set_level]->typecode) { + case IPSET_TYPE_IP: + parse_ip(ptr, &set_ip[set_level++]); + break; + case IPSET_TYPE_PORT: + parse_port(ptr, &set_ip[set_level++]); + break; + default: + ; /* Keep compilers happy */ + } + if (set->levels <= set_level) + exit_error(PARAMETER_PROBLEM, + "Subset definition is too deep for set '%s'\n", + set_name); + } + + free(saved); + return set; +} + +static inline struct set *set_find_byname(const char *setname) +{ + struct set *set = set_checkname(setname); + + if (!set) + exit_error(PARAMETER_PROBLEM, + "Set '%s' not found\n", setname); + return set; +} + +static void set_checktype(const char *typename) +{ + char *saved = strdup(typename); + char *ptr, *tmp = saved; + int i; + + /* Cleanup */ + for (i = 0; i < IP_SET_LEVELS; i++) + if (set_typename[i] != NULL) + free(set_typename[i]); + + /* typename[,...] */ + set_typename_level = 0; + while (set_typename_level < IP_SET_LEVELS && tmp) { + ptr = strsep(&tmp, ","); + DP("settype '%s', level %i", ptr, set_typename_level); + if (strlen(ptr) > IP_SET_MAXNAMELEN - 1) + exit_error(PARAMETER_PROBLEM, + "Typename '%s' in '%s' too long. Max %d characters.", + ptr, typename, IP_SET_MAXNAMELEN - 1); + set_typename[set_typename_level++] = strdup(ptr); + } + DP("tmp '%s', level %i", tmp, set_typename_level); + if (set_typename_level >= IP_SET_LEVELS || tmp) + exit_error(PARAMETER_PROBLEM, + "More than %d settypes in '%s'.", + IP_SET_LEVELS - 1 , typename); + free(saved); +} + +/* Get setid from kernel. */ +static void +set_get_setid(struct set *set) +{ + struct ip_set_req_get req_get; + size_t size = sizeof(struct ip_set_req_get); + + DP("set_get_setid()"); + + req_get.op = IP_SET_OP_GETSET_BYNAME; + strcpy(req_get.name, set->name); + + /* Result in the id-field */ + kernel_getfrom(&req_get, &size); + + if (size != sizeof(struct ip_set_req_get)) + exit_error(OTHER_PROBLEM, + "Incorrect return size from kernel." + "Should be %d but was %d.", + sizeof(struct ip_set_req_get), size); + + DP("set_get_setid() result=%u", req_get.id); + + if (req_get.id < 0) + exit_error(OTHER_PROBLEM, + "Set %s cannot be found.", + set->name); + + set->id = req_get.id; + set->ref = req_get.ref; +} + +/* Send create set order to kernel */ +static void +set_create(struct set *set) +{ + struct ip_set_req_create req_create; + struct settype *settype = set->settype[0]; + size_t size; + void *data; + int i; + + DP("set_create()"); + + req_create.op = IP_SET_OP_CREATE; + strcpy(req_create.name, set->name); + for (i = 0; i < set_typename_level; i++) + strcpy(req_create.typename[i], set->settype[i]->typename); + req_create.levels = set_typename_level; + + /* Final checks */ + settype->create_final(settype->data, settype->flags); + + /* Alloc memory for the data to send */ + size = sizeof(struct ip_set_req_create) + settype->create_size; + data = ipset_malloc(size); + + /* Add up ip_set_req_create and the settype data */ + memcpy(data, &req_create, sizeof(struct ip_set_req_create)); + memcpy(data + sizeof(struct ip_set_req_create), + settype->data, settype->create_size); + +#ifdef IP_SET_DEBUG + /* DEBUG */ + { + void *i; + + DP("CMD_CREATE"); + DP("OP %u", req_create.op); + DP("Name: %s", req_create.name); + DP("Typename: %s", req_create.typename[0]); + DP("All data"); + + for (i = data; i < data + size; i += 4) { + unsigned *j = (unsigned *) i; + DP("%x", *j); + } + } +#endif + + kernel_sendto(data, size); + free(data); + + /* Get set id from kernel */ + set_get_setid(set); + /* Success! */ + set_append(set); +} + +/* Send create childset order to kernel */ +static void +set_create_childset(const struct set *set, unsigned options) +{ + struct ip_set_req_sub req_sub; + struct settype *settype = set->settype[set_level]; + size_t size; + void *data; + int i; + + DP("childset_create()"); + + req_sub.op = IP_SET_OP_CREATE_CHILD; + req_sub.id = set->id; + for (i = 0; i < set_level; i++) + req_sub.ip[i] = set_ip[i]; + req_sub.level = set_level; + req_sub.childsets = options & OPT_CHILDSETS; + + /* Final checks */ + settype->create_final(settype->data, settype->flags); + + /* Alloc memory for the data to send */ + size = sizeof(struct ip_set_req_sub) + settype->create_size; + data = ipset_malloc(size); + + /* Add up ip_set_req_sub and the settype data */ + memcpy(data, &req_sub, sizeof(struct ip_set_req_sub)); + memcpy(data + sizeof(struct ip_set_req_sub), + settype->data, settype->create_size); + +#ifdef IP_SET_DEBUG + /* DEBUG */ + { + void *i; + + DP("CMD_CREATE_CHILD"); + DP("OP %u", req_sub.op); + DP("Id: %u", req_sub.id); + DP("All data"); + + for (i = data; i < data + size; i += 4) { + unsigned *j = (unsigned *) i; + DP("%x", *j); + } + } +#endif + + kernel_sendto(data, size); + free(data); +} + +static void set_del(struct set *set) +{ + int i; + + for (i = 0; i < set->levels; i++) { + if (set->private[i].setdata) + set->settype[i]->killmembers(&set->private[i].setdata); + ipset_free(&set->private[i].bitmap); + } + + free(set); +} + +/* Sends destroy order to kernel for one or all sets + * All sets: set == NULL + */ +static void set_destroy(struct set *set) +{ + struct ip_set_req_std req_destroy; + + req_destroy.op = IP_SET_OP_DESTROY; + + if (set == NULL) { + /* Do them all */ + + while (all_sets != NULL) { + set = all_sets; + all_sets = set->next; + req_destroy.id = set->id; + req_destroy.level = 0; + kernel_sendto(&req_destroy, + sizeof(struct ip_set_req_std)); + set_del(set); + } + } else { + int i; + + DP("destroy %s", set->name); + + /* Only destroy one */ + req_destroy.id = set->id; + for (i = 0; i < set_level; i++) + req_destroy.ip[i] = set_ip[i]; + req_destroy.level = set_level; + kernel_sendto(&req_destroy, + sizeof(struct ip_set_req_std)); + if (set_level == 0) { + if (set == all_sets) + all_sets = set->next; + else { + struct set *entry = all_sets; + + while (entry && entry->next && entry->next != set) + entry = entry->next; + if (entry->next == set) + entry->next = set->next; + } + set_del(set); + } + } +} + +/* Sends flush order to kernel for one or all sets + * All sets: set = NULL + */ +static void set_flush(const struct set *set, unsigned options) +{ + struct ip_set_req_sub req_flush; + + DP("flush"); + + req_flush.op = IP_SET_OP_FLUSH; + + if (set == NULL) { + /* Do them all */ + struct set *entry = all_sets; + + while (entry != NULL) { + req_flush.id = entry->id; + req_flush.level = 0; + req_flush.childsets = options & OPT_CHILDSETS; + kernel_sendto(&req_flush, + sizeof(struct ip_set_req_sub)); + entry = entry->next; + } + } else { + int i; + + /* Only one */ + req_flush.id = set->id; + for (i = 0; i < set_level; i++) + req_flush.ip[i] = set_ip[i]; + req_flush.level = set_level; + req_flush.childsets = options & OPT_CHILDSETS; + kernel_sendto(&req_flush, sizeof(struct ip_set_req_sub)); + } +} + +/* Sends rename order to kernel */ +static void set_rename(struct set *set, const char *newname) +{ + struct ip_set_req_rename req_rename; + + DP("rename"); + + req_rename.op = IP_SET_OP_RENAME; + req_rename.id = set->id; + strncpy(req_rename.newname, newname, IP_SET_MAXNAMELEN); + + DP("rename - send"); + kernel_sendto(&req_rename, sizeof(struct ip_set_req_rename)); + + strncpy(set->name, newname, IP_SET_MAXNAMELEN); +} + +/* Sends swap order to kernel for two sets */ +static void set_swap(struct set *from, struct set *to) +{ + struct ip_set_req_swap req_swap; + + DP("swap"); + + req_swap.op = IP_SET_OP_SWAP; + req_swap.id = from->id; + req_swap.to = to->id; + + DP("swap - send"); + kernel_sendto(&req_swap, sizeof(struct ip_set_req_swap)); + + from->id = req_swap.to; + to->id = req_swap.id; +} + +static void *list_get(int opsize, int opget, ip_set_ip_t id, + int *size, ip_set_ip_t *ip, unsigned level) +{ + int res, i; + struct ip_set_req_list req_list; + struct ip_set_req_std *req_data; + int sockfd = kernel_getsocket(); + void *data = NULL; + + req_list.op = opsize; + req_list.id = id; + req_list.level = level; + for (i = 0; i < level; i++) + req_list.ip[i] = ip[i]; + + *size = sizeof(req_list); + kernel_getfrom(&req_list, size); + + DP("got size: %d", req_list.size); + + if (req_list.size == 0) + return data; + + *size = sizeof(struct ip_set_req_std) > req_list.size ? + sizeof(struct ip_set_req_std) : req_list.size; + + data = ipset_malloc(*size); + req_data = (struct ip_set_req_std *) data; + req_data->op = opget; + req_data->id = id; + req_data->level = level; + for (i = 0; i < level; i++) + req_data->ip[i] = ip[i]; + + /* Get! */ + res = getsockopt(sockfd, SOL_IP, SO_IP_SET, data, size); + DP("list_get getsockopt() res=%d errno=%d\n", res, errno); + + if (res != 0) + kernel_error(errno); + + /* All OK, return */ + return data; +} + +static void list_getheaders(struct set *set, ip_set_ip_t *ip, unsigned level) +{ + void *data; + size_t size; + + data = list_get(IP_SET_OP_LIST_HEADER_SIZE, + IP_SET_OP_LIST_HEADER, + set->id, &size, ip, level); + + if (size == 0) + exit_error(OTHER_PROBLEM, + "Kernel returned zero header size. " + "Something screwed up."); + + /* Cleanup setdata */ + if (set->private[level].setdata) + set->settype[level]->killmembers(&set->private[level].setdata); + + /* Handle the data */ + set->settype[level]->initheader(&set->private[level].setdata, + data, size); + + ipset_free(&data); +} + +static void list_getmembers(struct set *set, ip_set_ip_t *ip, unsigned level) +{ + void *data; + size_t size; + + data = list_get(IP_SET_OP_LIST_MEMBERS_SIZE, + IP_SET_OP_LIST_MEMBERS, + set->id, &size, ip, level); + + if (size == 0) + exit_error(OTHER_PROBLEM, + "Kernel returned zero header size. " + "Something screwed up."); + + /* Handle the data */ + set->settype[level]->initmembers(set->private[level].setdata, + data, size); + + /* Next list_getheaders or set_del frees the data */ +} + +static void list_getchildsets(struct set *set, ip_set_ip_t *ip, unsigned level) +{ + void *data; + size_t size; + + data = list_get(IP_SET_OP_LIST_CHILDSETS_SIZE, + IP_SET_OP_LIST_CHILDSETS, + set->id, &size, ip, level); + + /* Cleanup */ + ipset_free(&set->private[level].bitmap); + set->private[level].bitmap = NULL; + + if (size == 0) + return; /* No child set */ + + /* Handle the data */ + set->private[level].bitmap = data; + + /* Next list_getchildsets or set_del frees the data */ +} + +/* Print a child set */ +static void set_list_childset(struct set *set, + unsigned options, + ip_set_ip_t *ip, + unsigned level) +{ + int i; + ip_set_ip_t id; + + /* Load the set header, member and childset data */ + list_getheaders(set, ip, level); + list_getmembers(set, ip, level); + list_getchildsets(set, ip, level); + + /* Pretty print the childset */ + printf("Childset %s %s", + set->private[level].bitmap != NULL ? "+" : "-", + set->name); + for (i = 0; i < level; i++) { + switch (set->settype[i]->typecode) { + case IPSET_TYPE_IP: + printf("%s%s", i == 0 ? ":" : ",", + ip_tostring(ip[i], options)); + break; + case IPSET_TYPE_PORT: + printf("%s%s", i == 0 ? ":" : ",", + port_tostring(ip[i], options)); + break; + default: + ; + } + } + printf("\n"); + + /* Pretty print the type header */ + set->settype[level]->printheader(set->private[level].setdata, options); + + /* Pretty print all IPs */ + if (options & OPT_SORTED) + set->settype[level]->printips_sorted(set->private[level].setdata, options); + else + set->settype[level]->printips(set->private[level].setdata, options); + + /* Pretty print all childsets. */ + if (!(set->private[level].bitmap != NULL && (options & OPT_CHILDSETS))) + return; + + for (id = 0; + id < set->settype[level]->sizeid(set->private[level].setdata); + id++) { + if (test_bit(id, set->private[level].bitmap)) { + ip[level] = set->settype[level]->getipbyid( + set->private[level].setdata, id); + set_list_childset(set, options, ip, level+1); + } + } +} + +/* Help function to set_list() */ +static void set_list_oneset(struct set *set, + unsigned options) +{ + int i; + + /* Pretty print the set */ + printf("Name %s\n", set->name); + printf("Type %s", set->settype[0]->typename); + for (i = 1; i < set->levels; i++) + printf(",%s", set->settype[i]->typename); + printf("\n"); + printf("References %d\n", set->ref); + + /* Pretty print the childset */ + set_list_childset(set, options, set_ip, set_level); + + printf("\n"); /* One newline between sets */ +} + +/* Print a set or all sets + * All sets: set = NULL + */ +static void set_list(struct set *set, unsigned options) +{ + if (set == NULL) { + set = all_sets; + + while (set != NULL) { + set_list_oneset(set, options); + set = set->next; + } + } else + set_list_oneset(set, options); +} + +/* Performs a save to stdout + * All sets are marked with set.name[0] == '\0' + * Area pointed by 'set' will be changed + */ +static void set_save(const struct set *set) +{ + DP("set_save() not yet implemented"); +} + +/* Performs a restore from stdin */ +static void set_restore() +{ + DP("set_restore() not yet implemented"); +} + +static void parse_adt_ip(int cmd, struct set *set, const char *adt_ip) +{ + char *saved = strdup(adt_ip); + char *ptr, *tmp = saved; + + ip_level = set_level; + + while (ip_level <= set->levels && tmp) { + + if (ip_level > set_level) + list_getheaders(set, set_ip, ip_level); + + ptr = strsep(&tmp, ","); + + /* Call the parser function */ + set_ip[ip_level] = set->settype[ip_level]->adt_parser( + cmd, ptr, + set->private[ip_level].adt, + set->private[ip_level].setdata); + DP("%i: (%s)", ip_level, ip_tostring(set_ip[ip_level], 0)); + ip_level++; + } + + if (tmp || ip_level > set->levels) + exit_error(PARAMETER_PROBLEM, + "Specified (child) set and IP levels together" + " are deeper than %s set (%i).", + set->name, set->levels); + + free(saved); +} + +/* Sends addip order to kernel for a set */ +static void set_addip(struct set *set, const char *adt_ip) +{ + struct ip_set_req_std req_addip; + size_t size, offset; + void *data; + int i; + + DP("set_addip() %p", set); + + list_getheaders(set, set_ip, set_level); + + parse_adt_ip(ADT_ADD, set, adt_ip); + + req_addip.op = IP_SET_OP_ADD_IP; + req_addip.id = set->id; + for (i = 0; i < set_level; i++) + req_addip.ip[i] = set_ip[i]; + req_addip.level = set_level; + + DP("%i %i", set_level, ip_level); + /* Alloc memory for the data to send */ + DP("alloc"); + size = sizeof(struct ip_set_req_std); + for (i = set_level; i < ip_level; i++) { + size += set->settype[i]->req_size; + DP("i: %i, size: %u", i, size); + } + data = ipset_malloc(size); + + /* Add up req_addip and the settype data */ + DP("mem"); + memcpy(data, &req_addip, sizeof(struct ip_set_req_std)); + offset = sizeof(struct ip_set_req_std); + for (i = set_level; i < ip_level; i++) { + memcpy(data + offset, + set->private[i].adt, + set->settype[i]->req_size); + offset += set->settype[i]->req_size; + } + +#ifdef IP_SET_DEBUG + /* DEBUG */ + { + void *i; + + DP("CMD_ADDIP"); + DP("OP %u", req_addip.op); + DP("Id: %u", req_addip.id); + DP("All data"); + + for (i = data; i < data + size; i += 4) { + unsigned *j = (unsigned *) i; + DP("%x", *j); + } + } +#endif + + if (kernel_sendto_handleexist(data, size) == -1) + /* Arghh, we can't get the commandline string anymore, oh well */ + exit_error(OTHER_PROBLEM, "Already added in set %s.", + set->name); + free(data); +} + +/* Sends delip order to kernel for a set */ +static void set_delip(struct set *set, const char *adt_ip, unsigned options) +{ + struct ip_set_req_std req_delip; + size_t size, offset; + void *data; + int i; + + DP("set_delip()"); + + list_getheaders(set, set_ip, set_level); + + parse_adt_ip(ADT_DEL, set, adt_ip); + + req_delip.op = IP_SET_OP_DEL_IP; + req_delip.id = set->id; + for (i = 0; i < set_level; i++) + req_delip.ip[i] = set_ip[i]; + req_delip.level = set_level; + + /* Alloc memory for the data to send */ + size = sizeof(struct ip_set_req_std); + for (i = set_level; i < ip_level; i++) + size += set->settype[i]->req_size; + data = ipset_malloc(size); + + /* Add up req_sub and the settype data */ + memcpy(data, &req_delip, sizeof(struct ip_set_req_std)); + offset = sizeof(struct ip_set_req_std); + for (i = set_level; i < ip_level; i++) { + memcpy(data + offset, + set->private[i].adt, + set->settype[i]->req_size); + offset += set->settype[i]->req_size; + } + +#ifdef IP_SET_DEBUG + /* DEBUG */ + { + void *i; + + DP("CMD_DELIP"); + DP("OP %u", req_delip.op); + DP("Id: %u", req_delip.id); + DP("All data"); + + for (i = data; i < data + size; i += 4) { + unsigned *j = (unsigned *) i; + DP("%x", *j); + } + } +#endif + + if (kernel_sendto_handleexist(data, size) == -1) + /* Arghh, we can't get the commandline string anymore, oh well */ + exit_error(OTHER_PROBLEM, "Doesn't exist in set %s.", + set->name); + free(data); +} + +/* Sends test order to kernel for a set */ +static int +set_testip(struct set *set, const char *name, const char *adt_ip) +{ + int i, res; + struct ip_set_req_test req_test; + void *data; + size_t size, offset; + + list_getheaders(set, set_ip, set_level); + + parse_adt_ip(ADT_TEST, set, adt_ip); + + req_test.op = IP_SET_OP_TEST_IP; + req_test.id = set->id; + for (i = 0; i < set_level; i++) + req_test.ip[i] = set_ip[i]; + req_test.level = set_level; + + /* Alloc memory for the data to send */ + size = sizeof(struct ip_set_req_test); + for (i = set_level; i < ip_level; i++) + size += set->settype[i]->req_size; + data = ipset_malloc(size); + + /* Add up req_test and the settype data */ + memcpy(data, &req_test, sizeof(struct ip_set_req_test)); + offset = sizeof(struct ip_set_req_test); + for (i = set_level; i < ip_level; i++) { + memcpy(data + offset, + set->private[i].adt, + set->settype[i]->req_size); + offset += set->settype[i]->req_size; + } + /* Result in the op-field */ + kernel_getfrom(data, &size); + + if (size != sizeof(struct ip_set_req_test)) + exit_error(OTHER_PROBLEM, + "Incorrect return size from kernel." + "Should be %d but was %d.", + sizeof(struct ip_set_req_test), size); + + DP("set_testip() result=%x", req_test.op); + + if (((struct ip_set_req_test *)data)->reply > 0) { + ipset_printf("%s is in set %s.", adt_ip, name); + res = 0; /* Return value for the program */ + } else { + ipset_printf("%s is NOT in set %s.", adt_ip, name); + res = 1; + } + + free(data); + return res; +} + +/* Prints help + * If settype is non null help for that type is printed as well + */ +static void set_help(const struct settype *settype) +{ +#ifdef IP_SET_DEBUG + char debughelp[] = + " --debug -z Enable debugging\n\n"; +#else + char debughelp[] = "\n"; +#endif + + printf("%s v%s\n\n" + "Usage: %s -N new-set settype [options]\n" + " %s -[XFLSH] [set] [options]\n" + " %s -[EW] from-set to-set\n" + " %s -[ADT] set entry\n" + " %s -R\n" + " %s -h (print this help information)\n\n", + program_name, program_version, program_name, program_name, + program_name, program_name, program_name, program_name); + + printf("Commands:\n" + "Either long or short options are allowed.\n" + " --create -N setname settype0[,settype1,...] \n" + " Create a new set\n" + " --create -N setname:IP[,IP...] settype \n" + " Create childset at setname:IP[,IP...]\n" + " --destroy -X [setname:IP,....]\n" + " Destroy a (child)set or all sets\n" + " --flush -F [setname:IP,...] [options]\n" + " Delete a (child)set or all sets\n" + " --rename -E from-set to-set\n" + " Rename from-set to to-set\n" + " --swap -W from-set to-set\n" + " Swap the content of two existing sets\n" + " --list -L [setname:IP,...] [options]\n" + " List the entries in a (child)set or all sets\n" + " --save -S [setname]\n" + " Save the set or all sets to stdout\n" + " --restore -R\n" + " Restores from stdin a saved state\n" + " --add -A setname[:IP,...] entry[,entry...]\n" + " Add an entry to a (child)set\n" + " --del -D setname[:IP,...] entry[,entry...]\n" + " Deletes an entry from a (child)set\n" + " --test -T setname[:IP,...] entry[,entry...]\n" + " Tests if an entry exists in a (child)set.\n" + " --help -H [settype] [options]]\n" + " Prints this help, and settype specific help\n" + " --version -V\n" + " Prints version information\n\n" + "Options:\n" + " --sorted -s Numeric sort of the IPs in -L\n" + " --numeric -n Numeric output of addresses in a -L\n" + " --quiet -q Suppress any output to stdout and stderr.\n" + " --childsets -c Operation valid for child sets\n" + " --hint -i Hint best settype initialization parameters\n"); + printf(debughelp); + + if (settype != NULL) { + printf("Type '%s' specific:\n", settype->typename); + settype->usage(); + } +} + +/* Hint various infos on a given settype */ +static int +settype_hint(const struct settype *settype, unsigned options) +{ + char buffer[1024]; + ip_set_ip_t ip[MAX_HINT_SIZE]; + ip_set_ip_t id = 0; + + if (!settype->hint) + return 0; + + while (fgets(buffer, sizeof(buffer), stdin) != NULL + && id < MAX_HINT_SIZE) { + if (buffer[0] == '\n' || buffer[0] == '#') + continue; + switch (settype->typecode) { + case IPSET_TYPE_IP: + parse_ip(buffer, &ip[id++]); + break; + case IPSET_TYPE_PORT: + parse_port(buffer, &ip[id++]); + break; + default: + ; + } + } + if (id >= MAX_HINT_SIZE) + exit_error(OTHER_PROBLEM, + "More than %ld entries, exiting.", + MAX_HINT_SIZE); + if (id < 1) + exit_error(OTHER_PROBLEM, + "No input, no hint."); + + settype->hint(settype->data, ip, id); + + return 0; +} + +static void init_sets(void) +{ + int version; + static int done = 0; + + if (done) + return; + + version = get_protocolversion(); + if (version != IP_SET_PROTOCOL_VERSION) + exit_error(OTHER_PROBLEM, + "Kernel ipset code is of protocol version %u." + "I'm of protocol version %u.\n" + "Please upgrade your kernel and/or ipset(8) utillity.", + version, IP_SET_PROTOCOL_VERSION); + + /* Get the list of existing sets from the kernel */ + get_sets(); + done = 1; +} + +/* Main worker function */ +int parse_commandline(int argc, char *argv[], int exec_restore) +{ + int res = 0; + unsigned command = 0; + unsigned options = 0; + int c; + + struct set *set = NULL; + struct set *set_to = NULL; /* Used by -W */ + struct settype *settype = NULL; /* Used by -H */ + char *name = NULL; /* Used by -E */ + char *entries = NULL; /* Used by -A, -D, -T */ + + struct option *opts = opts_long; + + /* Suppress error messages: we may add new options if we + demand-load a protocol. */ + opterr = 0; + + while ((c = getopt_long(argc, argv, opts_short, opts, NULL)) != -1) { + + DP("commandline parsed: opt %c (%s)", c, argv[optind]); + + switch (c) { + /* + * Command selection. + */ + case 'h': + case 'H':{ /* Help */ + set_command(&command, CMD_HELP); + + if (optarg) + set_checktype(optarg); + else if (optind < argc + && argv[optind][0] != '-') + set_checktype(argv[optind++]); + + if (!set_typename_level) + break; + + if (set_typename_level != 1) + exit_error(PARAMETER_PROBLEM, + "-%c requires one settype as argument", + cmd2char(CMD_HELP)); + + settype = settype_load(set_typename[0]); + + /* Merge the hint options */ + if (settype->hint_parse) { + opts = merge_options(opts, + settype->hint_opts, + &settype->option_offset); + + /* Reset space for settype create data */ + memset(settype->data, 0, settype->hint_size); + + /* Zero the flags */ + settype->flags = 0; + + DP("call hint_init"); + /* Call the settype hint_init */ + settype->hint_init(settype->data); + } + + break; + } + + case 'V':{ /* Version */ + /* Dont display kernel protocol version because + * that might generate errors if the ipset module + * is not loaded in.*/ + printf("%s v%s Protocol version %u.\n", + program_name, program_version, + IP_SET_PROTOCOL_VERSION); + exit(0); + } + + case 'N':{ /* Create */ + char *typename = NULL; + int i; + + init_sets(); + + DP("check setname"); + /* setname */ + set = set_checkname(optarg); + + if (set_level == 0) { + /* New set to be created */ + + DP(" new set, set_level == 0"); + + if (set != NULL) + exit_error(OTHER_PROBLEM, + "Set %s already exists.", + set_name); + set = ipset_malloc(sizeof(struct set)); + memset(set, 0, sizeof(struct set)); + + set_command(&command, CMD_CREATE); + + /* typename */ + if (optind < argc + && argv[optind][0] != '-') + typename = argv[optind++]; + else + exit_error(PARAMETER_PROBLEM, + "-%c requires new-setname and settype", + cmd2char(CMD_CREATE)); + + DP(" check typename"); + set_checktype(typename); + + strcpy(set->name, set_name); + set->levels = set_typename_level; + + DP("load the settypes"); + for (i = 0; i < set_typename_level; i++) + set->settype[i] = settype_load(set_typename[i]); + + } else {/* set_level != 0 */ + /* New childset to be created */ + + DP(" new childset, set_level != 0"); + + if (set == NULL) + exit_error(OTHER_PROBLEM, + "Set %s does not exist.", + set_name); + set_command(&command, CMD_CREATE_CHILD); + + /* typename */ + if (optind < argc + && argv[optind][0] != '-') + typename = argv[optind++]; + else + exit_error(PARAMETER_PROBLEM, + "-%c requires new-setname and settype", + cmd2char(CMD_CREATE)); + + DP(" check typename"); + set_checktype(typename); + if (set_typename_level > 1) + exit_error(PARAMETER_PROBLEM, + "-%c requires single settype specified", + cmd2char(CMD_CREATE)); + else if (set_level >= set->levels) + exit_error(PARAMETER_PROBLEM, + "specified childset is deeper than " + "%s set itself.", set->name); + else if (strcmp(typename, set->settype[set_level]->typename)) + exit_error(PARAMETER_PROBLEM, + "settype '%s' must be used instead of " + "'%s' in childset '%s'", + set->settype[set_level]->typename, + typename, optarg); + } + + DP("merge options"); + /* Merge the create options */ + opts = merge_options(opts, + set->settype[set_level]->create_opts, + &set->settype[set_level]->option_offset); + + /* Reset space for settype create data */ + memset(set->settype[set_level]->data, 0, + set->settype[set_level]->create_size); + + /* Zero the flags */ + set->settype[set_level]->flags = 0; + + DP("call create_init"); + /* Call the settype create_init */ + set->settype[set_level]->create_init( + set->settype[set_level]->data); + + break; + } + + case 'X':{ /* Destroy */ + init_sets(); + + set_command(&command, CMD_DESTROY); + + if (optarg) + set = set_find_byname(optarg); + else if (optind < argc + && argv[optind][0] != '-') + set = set_find_byname(argv[optind++]); + else + set = NULL; /* Mark to destroy all (empty) sets */ + + if (set && set_level >= set->levels) + exit_error(PARAMETER_PROBLEM, + "specified childset is deeper than " + "%s set itself.", set->name); + break; + } + + case 'F':{ /* Flush */ + init_sets(); + + set_command(&command, CMD_FLUSH); + + DP("flush: %s", optarg); + + if (optarg) + set = set_find_byname(optarg); + else if (optind < argc + && argv[optind][0] != '-') + set = set_find_byname(argv[optind++]); + else + set = NULL; /* Mark to flush all */ + + if (set && set_level >= set->levels) + exit_error(PARAMETER_PROBLEM, + "specified childset is deeper than " + "%s set itself.", set->name); + break; + } + + case 'E':{ /* Rename */ + init_sets(); + + set_command(&command, CMD_RENAME); + + set = set_find_byname(optarg); + if (set_level) + exit_error(PARAMETER_PROBLEM, + "childsets cannot be swapped"); + + if (optind < argc + && argv[optind][0] != '-') + name = argv[optind++]; + else + exit_error(PARAMETER_PROBLEM, + "-%c requires a setname " + "and the new name for that set", + cmd2char(CMD_RENAME)); + if (strlen(name) > IP_SET_MAXNAMELEN - 1) + exit_error(PARAMETER_PROBLEM, + "Setname '%s' is too long. Max %d characters.", + name, IP_SET_MAXNAMELEN - 1); + + /* Set with new name must not exist. */ + set_to = set_checkname(name); + if (set_to) + exit_error(PARAMETER_PROBLEM, + "Set already exists, cannot rename to %s", + name); + if (set_level) + exit_error(PARAMETER_PROBLEM, + "childsets cannot be swapped"); + + break; + } + + case 'W':{ /* Swap */ + char *name = NULL; + + init_sets(); + + set_command(&command, CMD_SWAP); + + set = set_find_byname(optarg); + if (set_level) + exit_error(PARAMETER_PROBLEM, + "childsets cannot be swapped"); + + if (optind < argc + && argv[optind][0] != '-') + name = argv[optind++]; + else + exit_error(PARAMETER_PROBLEM, + "-%c requires the names of two " + "existing sets", + cmd2char(CMD_SWAP)); + if (strlen(name) > IP_SET_MAXNAMELEN - 1) + exit_error(PARAMETER_PROBLEM, + "Setname '%s' is too long. Max %d characters.", + name, IP_SET_MAXNAMELEN - 1); + + /* Both sets must exist. */ + set_to = set_find_byname(name); + if (set_level) + exit_error(PARAMETER_PROBLEM, + "childsets cannot be swapped"); + + break; + } + + case 'L':{ /* List */ + init_sets(); + + set_command(&command, CMD_LIST); + if (optarg) + set = set_find_byname(optarg); + else if (optind < argc + && argv[optind][0] != '-') + set = set_find_byname(argv[optind++]); + else + set = NULL; /* Mark all */ + + if (set && set_level >= set->levels) + exit_error(PARAMETER_PROBLEM, + "specified childset is deeper than " + "%s set itself.", set->name); + break; + } + + case 'S':{ /* Save */ + init_sets(); + + set_command(&command, CMD_SAVE); + if (optarg) + set = set_find_byname(optarg); + else if (optind < argc + && argv[optind][0] != '-') + set = set_find_byname(argv[optind++]); + else + set = NULL; /* Mark to save all */ + + if (set && set_level >= set->levels) + exit_error(PARAMETER_PROBLEM, + "specified childset is deeper than " + "%s set itself.", set->name); + break; + } + + case 'R':{ /* Restore */ + init_sets(); + + set_command(&command, CMD_RESTORE); + break; + } + + case 'A':{ /* Add IP */ + init_sets(); + + set_command(&command, CMD_ADD); + + set = set_find_byname(optarg); + + if (set_level >= set->levels) + exit_error(PARAMETER_PROBLEM, + "specified childset is deeper than " + "%s set itself.", set->name); + + /* entries */ + if (optind < argc + && argv[optind][0] != '-') + entries = argv[optind++]; + else + exit_error(PARAMETER_PROBLEM, + "-%c requires setname and entries", + cmd2char(CMD_ADD)); + + break; + } + + case 'D':{ /* Del IP */ + init_sets(); + + set_command(&command, CMD_DEL); + + set = set_find_byname(optarg); + + if (set_level >= set->levels) + exit_error(PARAMETER_PROBLEM, + "specified childset is deeper than " + "%s set itself.", set->name); + + if (optind < argc + && argv[optind][0] != '-') + entries = argv[optind++]; + else + exit_error(PARAMETER_PROBLEM, + "-%c requires setname and entries", + cmd2char(CMD_DEL)); + + break; + } + + case 'T':{ /* Test IP */ + init_sets(); + + set_command(&command, CMD_TEST); + + set = set_find_byname(optarg); + name = optarg; + + if (set_level >= set->levels) + exit_error(PARAMETER_PROBLEM, + "specified childset is deeper than " + "%s set itself.", set->name); + + if (optind < argc + && argv[optind][0] != '-') + entries = argv[optind++]; + else + exit_error(PARAMETER_PROBLEM, + "-%c requires setname and entries", + cmd2char(CMD_TEST)); + + break; + } + + /* options */ + + case 'n': + add_option(&options, OPT_NUMERIC); + break; + + case 's': + add_option(&options, OPT_SORTED); + break; + + case 'q': + add_option(&options, OPT_QUIET); + option_quiet = 1; + break; + +#ifdef IP_SET_DEBUG + case 'z': /* debug */ + add_option(&options, OPT_DEBUG); + option_debug = 1; + break; +#endif + + case 'c': + add_option(&options, OPT_CHILDSETS); + break; + + case 'i': + add_option(&options, OPT_HINT); + break; + + case 1: /* non option */ + printf("Bad argument `%s'\n", optarg); + exit_tryhelp(2); + break; /*always good */ + + default:{ + DP("default"); + + switch (command) { + case CMD_CREATE: + res = set->settype[set_level]->create_parse( + c - set->settype[set_level]->option_offset, + argv, + set->settype[set_level]->data, + &set->settype[set_level]->flags); + break; + + case CMD_CREATE_CHILD: + res = set->settype[set_level]->create_parse( + c - set->settype[set_level]->option_offset, + argv, + set->settype[set_level]->data, + &set->settype[set_level]->flags); + break; + + case CMD_HELP: { + if (!(settype && settype->hint_parse)) + break; + + res = settype->hint_parse( + c - settype->option_offset, + argv, + settype->data); + break; + } + default: + res = 0; /* failed */ + } /* switch (command) */ + + + if (!res) + exit_error(PARAMETER_PROBLEM, + "Unknown arg `%s'", + argv[optind - 1]); + + } + + DP("next arg"); + } /* switch */ + + } /* while( getopt_long() ) */ + + + if (optind < argc) + exit_error(PARAMETER_PROBLEM, + "unknown arguments found on commandline"); + if (!command) + exit_error(PARAMETER_PROBLEM, "no command specified"); + + /* Check options */ + generic_opt_check(command == CMD_CREATE_CHILD ? CMD_CREATE : command, options); + + DP("cmd: %c", cmd2char(command)); + + switch (command) { + case CMD_CREATE: + DP("CMD_CREATE"); + set_create(set); + break; + + case CMD_CREATE_CHILD: + DP("CMD_CREATE_CHILD"); + set_create_childset(set, options); + break; + + case CMD_DESTROY: + set_destroy(set); + break; + + case CMD_FLUSH: + set_flush(set, options); + break; + + case CMD_RENAME: + set_rename(set, name); + break; + + case CMD_SWAP: + set_swap(set, set_to); + break; + + case CMD_LIST: + set_list(set, options); + break; + + case CMD_SAVE: + set_save(set); + break; + + case CMD_RESTORE: + set_restore(); + break; + + case CMD_ADD: + set_addip(set, entries); + break; + + case CMD_DEL: + set_delip(set, entries, options); + break; + + case CMD_TEST: + res = set_testip(set, name, entries); + break; + + case CMD_HELP: + if (options & OPT_HINT) + res = settype_hint(settype, options); + else + set_help(settype); + break; + + default: + /* Will never happen */ + ; /* Keep the compiler happy */ + + } /* switch( command ) */ + + return res; +} + + +int main(int argc, char *argv[]) +{ + return parse_commandline(argc, argv, 0); + +} diff --git a/ipset.h b/ipset.h new file mode 100644 index 0000000..095f756 --- /dev/null +++ b/ipset.h @@ -0,0 +1,203 @@ +#ifndef __IPSET_H +#define __IPSET_H + +/* Copyright 2000-2004 Joakim Axelsson (gozem@linux.nu) + * Patrick Schaaf (bof@bof.de) + * Jozsef Kadlecsik (kadlec@blackhole.kfki.hu) + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include + +#include + +char program_name[] = "ipset"; +char program_version[] = "1.0"; + +#define IPSET_LIB_NAME "/libipset_%s.so" +#define PROC_SYS_MODPROBE "/proc/sys/kernel/modprobe" + +#define LIST_TRIES 5 + +/* FIXME: move this to Makefile ? */ +#if 1 +#define IP_SET_DEBUG +#endif + +#ifdef IP_SET_DEBUG +extern int option_debug; +#define DP(format, args...) if (option_debug) \ + do { \ + fprintf(stderr, "%s: %s (DBG): ", __FILE__, __FUNCTION__);\ + fprintf(stderr, format "\n" , ## args); \ + } while (0) +#else +#define DP(format, args...) +#endif + +enum exittype { + OTHER_PROBLEM = 1, + PARAMETER_PROBLEM, + VERSION_PROBLEM +}; + +struct set_data { + void *setdata; /* Hook to set speficic data */ + void *bitmap; /* Which elements has got a childset (bitmap) */ + void *adt; /* Add/del/test data */ +}; + +/* The simplistic view of an ipset */ +struct set { + struct set *next; + + char name[IP_SET_MAXNAMELEN]; /* Name of the set */ + unsigned id; /* Pool id in kernel */ + unsigned levels; /* Levels we may have in this set */ + unsigned ref; /* References in kernel */ + struct settype *settype[IP_SET_LEVELS]; /* Pointer to set type functions */ + struct set_data private[IP_SET_LEVELS]; /* Hook to set specific data */ +}; + +#define ADT_ADD 0 +#define ADT_DEL 1 +#define ADT_TEST 2 + +struct settype { + struct settype *next; + + char typename[IP_SET_MAXNAMELEN]; + char typecode; + + int protocol_version; + + /* + * Create set + */ + + /* Size of create data. Will be sent to kernel */ + size_t create_size; + + /* Initialize the create. */ + void (*create_init) (void *data); + + /* Function which parses command options; returns true if it ate an option */ + int (*create_parse) (int c, char *argv[], void *data, + unsigned *flags); + + /* Final check; exit if not ok. */ + void (*create_final) (void *data, unsigned int flags); + + /* Pointer to list of extra command-line options for create */ + struct option *create_opts; + + + /* + * Add/del/test IP + */ + + /* Size of data. Will be sent to kernel */ + size_t req_size; + + /* Function which parses command options */ + ip_set_ip_t (*adt_parser) (int cmd, const char *option, + void *data, const void * setdata); + + /* + * Printing + */ + + /* Set up the sets headerinfo with data coming from kernel */ + void (*initheader) (void ** setdata, void *data, size_t len); + + /* Set up the sets members with data coming from kernel */ + void (*initmembers) (void * setdata, void *data, size_t len); + + /* Remove the members memory usage */ + void (*killmembers) (void ** setdata); + + /* Pretty print the type-header */ + void (*printheader) (const void * setdata, unsigned options); + + /* Returns an IP address from the set */ + ip_set_ip_t (*getipbyid) (const void * setdata, ip_set_ip_t id); + + /* Return the size of the set in ids */ + ip_set_ip_t (*sizeid) (const void * setdata); + + /* Pretty print all IPs */ + void (*printips) (const void * setdata, unsigned options); + + /* Pretty print all IPs sorted */ + void (*printips_sorted) (const void * setdata, + unsigned options); + + /* Print save arguments for creating the set */ + void (*saveheader) (const void * setdata); + + /* Print save for all IPs */ + void (*saveips) (const void * setdata); + + /* Print usage */ + void (*usage) (void); + + /* + * Hint: size of data must be smaller than that of create! + */ + + /* Size of hint data. Will *not* be sent to kernel */ + size_t hint_size; + + /* Initialize the hint data. */ + void (*hint_init) (void *data); + + /* Function which parses command options; returns true if it ate an option */ + int (*hint_parse) (int c, char *argv[], void *data); + + /* Pointer to list of extra command-line options for hinting */ + struct option *hint_opts; + + /* Hint initialization info */ + void (*hint) (const void *data, ip_set_ip_t * ip, ip_set_ip_t id); + + /* Internal data */ + unsigned int option_offset; + unsigned int flags; + void *data; +}; + +extern void settype_register(struct settype *settype); + +/* extern void unregister_settype(set_type_t *set_type); */ + +extern void exit_error(enum exittype status, char *msg, ...); + +extern char *ip_tostring(ip_set_ip_t ip, unsigned options); +extern void parse_ip(const char *str, ip_set_ip_t * ip); +extern void parse_mask(const char *str, ip_set_ip_t * mask); +extern void parse_ipandmask(const char *str, ip_set_ip_t * ip, + ip_set_ip_t * mask); +extern char *port_tostring(ip_set_ip_t port, unsigned options); +extern void parse_port(const char *str, ip_set_ip_t * port); +extern int string_to_number(const char *str, unsigned int min, unsigned int max, + ip_set_ip_t *port); + +extern void *ipset_malloc(size_t size); +extern void ipset_free(void **data); + +#endif /* __IPSET_H */ diff --git a/ipset_iphash.c b/ipset_iphash.c new file mode 100644 index 0000000..ea18296 --- /dev/null +++ b/ipset_iphash.c @@ -0,0 +1,477 @@ +/* Copyright 2004 Jozsef Kadlecsik (kadlec@blackhole.kfki.hu) + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "ipset.h" + +#define BUFLEN 30; + +#define OPT_CREATE_INITVAL 0x01U +#define OPT_CREATE_HASHSIZE 0x02U +#define OPT_CREATE_NETMASK 0x03U + +#define OPT_HINT_TRY 0x01U +#define OPT_HINT_NETMASK 0x02U + +#define HINT_DEFAULT_TRY 8 +#define HINT_DEFAULT_FACTOR 4 + +/* Initialize the create. */ +void create_init(void *data) +{ + struct ip_set_req_iphash_create *mydata = + (struct ip_set_req_iphash_create *) data; + + DP("create INIT"); + + mydata->initval = 0; + mydata->netmask = 0xFFFFFFFF; +} + +/* Function which parses command options; returns true if it ate an option */ +int create_parse(int c, char *argv[], void *data, unsigned *flags) +{ + struct ip_set_req_iphash_create *mydata = + (struct ip_set_req_iphash_create *) data; + char *end; + unsigned int bits; + + DP("create_parse"); + + switch (c) { + case '1': + errno = 0; + mydata->initval = strtoul(optarg, &end, 0); + if (*end == '\0' && end != optarg && errno != ERANGE) { + + *flags |= OPT_CREATE_INITVAL; + + DP("--initval 0x%x)", mydata->initval); + break; + } + exit_error(PARAMETER_PROBLEM, "Invalid initval `%s' specified", optarg); + case '2': + + if (string_to_number(optarg, 1, MAX_RANGE, &mydata->hashsize)) + exit_error(PARAMETER_PROBLEM, "Hashsize `%s' specified", optarg); + + *flags |= OPT_CREATE_HASHSIZE; + + DP("--hashsize %u", mydata->hashsize); + + break; + + case '3': + + if (string_to_number(optarg, 0, 32, &bits)) + exit_error(PARAMETER_PROBLEM, + "Invalid netmask `%s' specified", optarg); + + if (bits != 0) + mydata->netmask = 0xFFFFFFFF << (32 - bits); + + *flags |= OPT_CREATE_NETMASK; + + DP("--netmask %x", mydata->netmask); + + break; + + default: + return 0; + } + + return 1; +} + +/* Final check; exit if not ok. */ +void create_final(void *data, unsigned int flags) +{ + struct ip_set_req_iphash_create *mydata = + (struct ip_set_req_iphash_create *) data; + + if ((flags & OPT_CREATE_HASHSIZE) == 0) + exit_error(PARAMETER_PROBLEM, + "Need to specify --hashsize\n"); + + if (!mydata->initval) { + srand(getpid() | time(NULL)); + mydata->initval = rand(); + } + DP("initval: 0x%x hashsize %d", mydata->initval, mydata->hashsize); +} + +/* Create commandline options */ +static struct option create_opts[] = { + {"initval", 1, 0, '1'}, + {"hashsize", 1, 0, '2'}, + {"netmask", 1, 0, '3'}, + {0} +}; + +/* Add, del, test parser */ +ip_set_ip_t adt_parser(int cmd, const char *optarg, + void *data, const void *setdata) +{ + struct ip_set_req_iphash *mydata = + (struct ip_set_req_iphash *) data; + + mydata->flags = 0; + + if (*optarg == '+') { + if (cmd == ADT_ADD) { + mydata->flags |= IPSET_ADD_OVERWRITE; + optarg++; + } else + exit_error(PARAMETER_PROBLEM, + "The '!' overwrite flag can be used only " + "when adding an IP address to the set\n"); + } + parse_ip(optarg, &mydata->ip); + + return mydata->ip; +}; + +ip_set_ip_t getipbyid(const void *setdata, ip_set_ip_t id) +{ + struct ip_set_iphash *mysetdata = + (struct ip_set_iphash *) setdata; + + return mysetdata->members[id]; +} + +ip_set_ip_t sizeid(const void *setdata) +{ + struct ip_set_iphash *mysetdata = + (struct ip_set_iphash *) setdata; + + return (mysetdata->hashsize); +} + +void initheader(void **setdata, void *data, size_t len) +{ + struct ip_set_req_iphash_create *header = + (struct ip_set_req_iphash_create *) data; + + DP("iphash: initheader() 1"); + + if (len != sizeof(struct ip_set_req_iphash_create)) + exit_error(OTHER_PROBLEM, + "Iphash: incorrect size of header. " + "Got %d, wanted %d.", len, + sizeof(struct ip_set_req_iphash_create)); + + *setdata = ipset_malloc(sizeof(struct ip_set_iphash)); + + DP("iphash: initheader() 2"); + + ((struct ip_set_iphash *) *setdata)->initval = + header->initval; + ((struct ip_set_iphash *) *setdata)->hashsize = + header->hashsize; + ((struct ip_set_iphash *) *setdata)->netmask = + header->netmask; +} + +void initmembers(void *setdata, void *data, size_t len) +{ + struct ip_set_iphash *mysetdata = + (struct ip_set_iphash *) setdata; + size_t size; + + DP("iphash: initmembers()"); + + /* Check so we get the right amount of memberdata */ + size = sizeof(ip_set_ip_t) * mysetdata->hashsize; + + if (len != size) + exit_error(OTHER_PROBLEM, + "Iphash: incorrect size of members. " + "Got %d, wanted %d.", len, size); + + mysetdata->members = data; +} + +void killmembers(void **setdata) +{ + struct ip_set_iphash *mysetdata = + (struct ip_set_iphash *) *setdata; + + DP("iphash: killmembers()"); + + if (mysetdata->members != NULL) + free(mysetdata->members); + + ipset_free(setdata); +} + +unsigned int +mask_to_bits(ip_set_ip_t mask) +{ + unsigned int bits = 32; + ip_set_ip_t maskaddr; + + if (mask == 0xFFFFFFFF) + return bits; + + maskaddr = 0xFFFFFFFE; + while (--bits >= 0 && maskaddr != mask) + maskaddr <<= 1; + + return bits; +} + +void printheader(const void *setdata, unsigned options) +{ + struct ip_set_iphash *mysetdata = + (struct ip_set_iphash *) setdata; + + printf(" initval: 0x%x", mysetdata->initval); + printf(" hashsize: %d", mysetdata->hashsize); + if (mysetdata->netmask == 0xFFFFFFFF) + printf("\n"); + else + printf(" netmask: %d\n", mask_to_bits(mysetdata->netmask)); +} + +void printips(const void *setdata, unsigned options) +{ + struct ip_set_iphash *mysetdata = + (struct ip_set_iphash *) setdata; + ip_set_ip_t id; + + for (id = 0; id < mysetdata->hashsize; id++) + if (mysetdata->members[id]) + printf("%s\n", ip_tostring(mysetdata->members[id], options)); +} + +void saveheader(const void *setdata) +{ + return; +} + +/* Print save for an IP */ +void saveips(const void *setdata) +{ + return; +} + +void usage(void) +{ + printf + ("-N set iphash [--initval hash-initval] --hashsize hashsize [--netmask CIDR-netmask]\n" + "-A set [!]IP\n" + "-D set IP\n" + "-T set IP\n" + "-H iphash -i [--try number] [--factor number] [--netmask CIDR-netmask]\n"); +} + +struct ip_set_iphash_hint { + unsigned int try; + unsigned int factor; + ip_set_ip_t netmask; +}; + +/* Initialize the hint. */ +void hint_init(void *data) +{ + struct ip_set_iphash_hint *mydata = + (struct ip_set_iphash_hint *) data; + + DP("hint INIT"); + + mydata->try = HINT_DEFAULT_TRY; + mydata->factor = HINT_DEFAULT_FACTOR; + mydata->netmask = 0xFFFFFFFF; +} + +/* Function which parses command options; returns true if it ate an option */ +int hint_parse(int c, char *argv[], void *data) +{ + struct ip_set_iphash_hint *mydata = + (struct ip_set_iphash_hint *) data; + unsigned int bits; + + DP("hint_parse"); + + switch (c) { + case '1': + if (string_to_number(optarg, 1, 32, &mydata->try)) + exit_error(PARAMETER_PROBLEM, + "Invalid --try `%s' specified (out of range 1-32", optarg); + + DP("--try %i)", mydata->try); + break; + + case '2': + if (string_to_number(optarg, 1, 64, &mydata->factor)) + exit_error(PARAMETER_PROBLEM, + "Invalid --factor `%s' specified (out of range 1-64", optarg); + + DP("--factor %i)", mydata->factor); + break; + + case '3': + + if (string_to_number(optarg, 0, 32, &bits)) + exit_error(PARAMETER_PROBLEM, + "Invalid netmask `%s' specified", optarg); + + if (bits != 0) + mydata->netmask = 0xFFFFFFFF << (32 - bits); + + DP("--netmask %x", mydata->netmask); + + break; + + default: + return 0; + } + + return 1; +} + +/* Hint commandline options */ +static struct option hint_opts[] = { + {"try", 1, 0, '1'}, + {"factor", 1, 0, '2'}, + {"netmask", 1, 0, '3'}, + {0} +}; + +int hint_try(const ip_set_ip_t *ip, ip_set_ip_t *test, ip_set_ip_t best, + ip_set_ip_t hashsize, unsigned int try, uint32_t *initval, int *found) +{ + ip_set_ip_t id, hash; + int i = 0; + + next_try: + while (i < try) { + memset(test, 0, MAX_RANGE * sizeof(ip_set_ip_t)); + for (id = 0; id < best; id++) { + hash = jhash_1word(ip[id], *initval) % hashsize; + if (test[hash] != 0) { + *initval = rand(); + i++; + goto next_try; + } else + test[hash] = ip[id]; + DP("%i %x", hash, ip[id]); + } + printf("--initval 0x%08x --hashsize %d\n", *initval, hashsize); + *found = 1; + return 1; + } + return 0; +} + +void hint(const void *data, ip_set_ip_t *ip, ip_set_ip_t best) +{ + struct ip_set_iphash_hint *mydata = + (struct ip_set_iphash_hint *) data; + uint32_t initval; + ip_set_ip_t next, prev, curr; + ip_set_ip_t test[MAX_RANGE]; + ip_set_ip_t id; + int found = 0; + + curr = MAX_RANGE > mydata->factor * best ? mydata->factor * best : MAX_RANGE; + prev = next = MAX_RANGE; + + srand(curr); + initval = rand(); + + for (id = 0; id < best; id++) + ip[id] &= mydata->netmask; + + while (1) { + DP("%u %u %u %u", prev, curr, next, best); + + if (hint_try(ip, test, best, curr, mydata->try, &initval, &found)) { + if (curr == best) + return; + next = (curr + best)/2; + } else { + if (curr == prev){ + if (!found) + printf("Cannot find good init values.\n"); + return; + } + next = (curr + prev)/2; + } + prev = curr; + curr = next; + } +} +static struct settype settype_iphash = { + .typename = SETTYPE_NAME, + .typecode = IPSET_TYPE_IP, + .protocol_version = IP_SET_PROTOCOL_VERSION, + + /* Create */ + .create_size = sizeof(struct ip_set_req_iphash_create), + .create_init = &create_init, + .create_parse = &create_parse, + .create_final = &create_final, + .create_opts = create_opts, + + /* Add/del/test */ + .req_size = sizeof(struct ip_set_req_iphash), + .adt_parser = &adt_parser, + + /* Get an IP address by id */ + .getipbyid = &getipbyid, + .sizeid = &sizeid, + + /* Printing */ + .initheader = &initheader, + .initmembers = &initmembers, + .killmembers = &killmembers, + .printheader = &printheader, + .printips = &printips, /* We only have the unsorted version */ + .printips_sorted = &printips, + .saveheader = &saveheader, + .saveips = &saveips, + .usage = &usage, + + /* Hint */ + .hint_size = sizeof(struct ip_set_iphash_hint), + .hint_init = &hint_init, + .hint_parse = &hint_parse, + .hint_opts = hint_opts, + .hint = &hint, +}; + +void _init(void) +{ + settype_register(&settype_iphash); + +} diff --git a/ipset_ipmap.c b/ipset_ipmap.c new file mode 100644 index 0000000..df03a14 --- /dev/null +++ b/ipset_ipmap.c @@ -0,0 +1,384 @@ +/* Copyright 2000-2004 Joakim Axelsson (gozem@linux.nu) + * Patrick Schaaf (bof@bof.de) + * Jozsef Kadlecsik (kadlec@blackhole.kfki.hu) + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include + +#include +#include "ipset.h" + +#define BUFLEN 30; + +#define OPT_CREATE_FROM 0x01U +#define OPT_CREATE_TO 0x02U +#define OPT_CREATE_NETWORK 0x04U +#define OPT_CREATE_NETMASK 0x08U + +#define OPT_ADDDEL_IP 0x01U + +/* Initialize the create. */ +void create_init(void *data) +{ + struct ip_set_req_ipmap_create *mydata = + (struct ip_set_req_ipmap_create *) data; + + DP("create INIT"); + mydata->netmask = 0xFFFFFFFF; +} + +/* Function which parses command options; returns true if it ate an option */ +int create_parse(int c, char *argv[], void *data, unsigned *flags) +{ + struct ip_set_req_ipmap_create *mydata = + (struct ip_set_req_ipmap_create *) data; + unsigned int bits; + + DP("create_parse"); + + switch (c) { + case '1': + parse_ip(optarg, &mydata->from); + + *flags |= OPT_CREATE_FROM; + + DP("--from %x (%s)", mydata->from, + ip_tostring(mydata->from, 0)); + + break; + + case '2': + parse_ip(optarg, &mydata->to); + + *flags |= OPT_CREATE_TO; + + DP("--to %x (%s)", mydata->to, ip_tostring(mydata->to, 0)); + + break; + + case '3': + parse_ipandmask(optarg, &mydata->from, &mydata->to); + + /* Make to the last of from + mask */ + mydata->to = mydata->from | ~(mydata->to); + + *flags |= OPT_CREATE_NETWORK; + + DP("--network from %x (%s)", mydata->from, + ip_tostring(mydata->from, 0)); + DP("--network to %x (%s)", mydata->to, + ip_tostring(mydata->to, 0)); + + break; + + case '4': + if (string_to_number(optarg, 0, 32, &bits)) + exit_error(PARAMETER_PROBLEM, + "Invalid netmask `%s' specified", optarg); + + if (bits != 0) + mydata->netmask = 0xFFFFFFFF << (32 - bits); + + *flags |= OPT_CREATE_NETMASK; + + DP("--netmask %x", mydata->netmask); + + break; + + default: + return 0; + } + + return 1; +} + +/* Final check; exit if not ok. */ +void create_final(void *data, unsigned int flags) +{ + struct ip_set_req_ipmap_create *mydata = + (struct ip_set_req_ipmap_create *) data; + + if (flags == 0) + exit_error(PARAMETER_PROBLEM, + "Need to specify --from and --to, or --network\n"); + + if (flags & OPT_CREATE_NETWORK) { + /* --network */ + if ((flags & OPT_CREATE_FROM) || (flags & OPT_CREATE_TO)) + exit_error(PARAMETER_PROBLEM, + "Can't specify --from or --to with --network\n"); + } else { + /* --from --to */ + if ((flags & OPT_CREATE_FROM) == 0 + || (flags & OPT_CREATE_TO) == 0) + exit_error(PARAMETER_PROBLEM, + "Need to specify both --from and --to\n"); + } + + DP("from : %x to: %x diff: %d", mydata->from, mydata->to, + mydata->to - mydata->from); + + if (mydata->from > mydata->to) + exit_error(PARAMETER_PROBLEM, + "From can't be lower than to.\n", MAX_RANGE); + + if (mydata->to - mydata->from > MAX_RANGE) + exit_error(PARAMETER_PROBLEM, + "Range to large. Max is %d IPs in range\n", + MAX_RANGE); + if (flags & OPT_CREATE_NETMASK) { + unsigned int mask_bits, netmask_bits; + ip_set_ip_t mask; + + if ((mydata->from & mydata->netmask) != mydata->from) + exit_error(PARAMETER_PROBLEM, + "%s is not a network address according to netmask %d\n", + ip_tostring(mydata->from, 0), + mask_to_bits(mydata->netmask)); + + mask = range_to_mask(mydata->from, mydata->to, &mask_bits); + if (!mask) + exit_error(PARAMETER_PROBLEM, + "%s-%s is not a full network\n", + ip_tostring(mydata->from, 0), + ip_tostring(mydata->to, 0)); + + netmask_bits = mask_to_bits(mydata->netmask); + + if (netmask_bits <= mask_bits) + exit_error(PARAMETER_PROBLEM, + "%d netmask specifies larger or equal netblock than %s-%s\n", + netmask_bits, + ip_tostring(mydata->from, 0), + ip_tostring(mydata->to, 0)); + } +} + +/* Create commandline options */ +static struct option create_opts[] = { + {"from", 1, 0, '1'}, + {"to", 1, 0, '2'}, + {"network", 1, 0, '3'}, + {"netmask", 1, 0, '4'}, + {0} +}; + +/* Add, del, test parser */ +ip_set_ip_t adt_parser(int cmd, const char *optarg, + void *data, const void *setdata) +{ + struct ip_set_req_ipmap *mydata = + (struct ip_set_req_ipmap *) data; + struct ip_set_ipmap *mysetdata = + (struct ip_set_ipmap *) setdata; + + DP("ipmap: %p %p", data, setdata); + + parse_ip(optarg, &mydata->ip); + + DP("from %s", ip_tostring(mysetdata->first_ip, 0)); + DP("to %s", ip_tostring(mysetdata->last_ip, 0)); + DP("ip %s", ip_tostring(mydata->ip, 0)); + + if (mydata->ip < mysetdata->first_ip || + mydata->ip > mysetdata->last_ip) + exit_error(PARAMETER_PROBLEM, "IP '%s' is out of range\n", + ip_tostring(mydata->ip, 0)); + + return mydata->ip; +} + +ip_set_ip_t getipbyid(const void *setdata, ip_set_ip_t id) +{ + struct ip_set_ipmap *mysetdata = + (struct ip_set_ipmap *) setdata; + + return (mysetdata->first_ip + id * mysetdata->hosts); +} + +ip_set_ip_t sizeid(const void *setdata) +{ + struct ip_set_ipmap *mysetdata = + (struct ip_set_ipmap *) setdata; + + return (mysetdata->sizeid); +} + +void initheader(void **setdata, void *data, size_t len) +{ + struct ip_set_req_ipmap_create *header = + (struct ip_set_req_ipmap_create *) data; + struct ip_set_ipmap *map; + + DP("ipmap: initheader() 1"); + + if (len != sizeof(struct ip_set_req_ipmap_create)) + exit_error(OTHER_PROBLEM, + "Ipmap: incorrect size of header. " + "Got %d, wanted %d.", len, + sizeof(struct ip_set_req_ipmap_create)); + + *setdata = ipset_malloc(sizeof(struct ip_set_ipmap)); + + DP("ipmap: initheader() 2"); + + map = (struct ip_set_ipmap *) *setdata; + + map->first_ip = header->from; + map->last_ip = header->to; + map->netmask = header->netmask; + + if (map->netmask == 0xFFFFFFFF) { + map->hosts = 1; + map->sizeid = map->last_ip - map->first_ip + 1; + } else { + unsigned int mask_bits, netmask_bits; + ip_set_ip_t mask; + + mask = range_to_mask(header->from, header->to, &mask_bits); + netmask_bits = mask_to_bits(header->netmask); + + DP("bits: %i %i", mask_bits, netmask_bits); + map->hosts = 2 << (32 - netmask_bits - 1); + map->sizeid = 2 << (netmask_bits - mask_bits - 1); + } + + DP("%i %i", map->hosts, map->sizeid ); + +} + +void initmembers(void *setdata, void *data, size_t len) +{ + struct ip_set_ipmap *mysetdata = + (struct ip_set_ipmap *) setdata; + size_t size; + + DP("ipmap: initmembers()"); + + /* Check so we get the right amount of memberdata */ + size = bitmap_bytes(0, mysetdata->sizeid - 1); + + if (len != size) + exit_error(OTHER_PROBLEM, + "Ipmap: incorrect size of members. " + "Got %d, wanted %d.", len, size); + + mysetdata->members = data; +} + +void killmembers(void **setdata) +{ + struct ip_set_ipmap *mysetdata = + (struct ip_set_ipmap *) *setdata; + + DP("ipmap: killmembers()"); + + if (mysetdata->members != NULL) + ipset_free(&mysetdata->members); + + ipset_free(setdata); +} + +void printheader(const void *setdata, unsigned options) +{ + struct ip_set_ipmap *mysetdata = + (struct ip_set_ipmap *) setdata; + + printf(" from: %s", ip_tostring(mysetdata->first_ip, options)); + printf(" to: %s", ip_tostring(mysetdata->last_ip, options)); + if (mysetdata->netmask == 0xFFFFFFFF) + printf("\n"); + else + printf(" netmask: %d\n", mask_to_bits(mysetdata->netmask)); +} + +void printips_sorted(const void *setdata, unsigned options) +{ + struct ip_set_ipmap *mysetdata = + (struct ip_set_ipmap *) setdata; + ip_set_ip_t id; + + for (id = 0; id < mysetdata->sizeid; id++) + if (test_bit(id, mysetdata->members)) + printf("%s\n", ip_tostring(mysetdata->first_ip + id * mysetdata->hosts, + options)); +} + +void saveheader(const void *setdata) +{ + return; +} + +/* Print save for an IP */ +void saveips(const void *setdata) +{ + return; +} + +void usage(void) +{ + printf + ("-N set ipmap --from IP --to IP [--netmask CIDR-netmask]\n" + "-N set ipmap --network IP/mask [--netmask CIDR-netmask]\n" + "-A set IP\n" + "-D set IP\n" + "-T set IP\n"); +} + +static struct settype settype_ipmap = { + .typename = SETTYPE_NAME, + .typecode = IPSET_TYPE_IP, + .protocol_version = IP_SET_PROTOCOL_VERSION, + + /* Create */ + .create_size = sizeof(struct ip_set_req_ipmap_create), + .create_init = &create_init, + .create_parse = &create_parse, + .create_final = &create_final, + .create_opts = create_opts, + + /* Add/del/test */ + .req_size = sizeof(struct ip_set_req_ipmap), + .adt_parser = &adt_parser, + + /* Get an IP address by id */ + .getipbyid = &getipbyid, + .sizeid = &sizeid, + + /* Printing */ + .initheader = &initheader, + .initmembers = &initmembers, + .killmembers = &killmembers, + .printheader = &printheader, + .printips = &printips_sorted, /* We only have sorted version */ + .printips_sorted = &printips_sorted, + .saveheader = &saveheader, + .saveips = &saveips, + .usage = &usage, + .hint = NULL, +}; + +void _init(void) +{ + settype_register(&settype_ipmap); + +} diff --git a/ipset_macipmap.c b/ipset_macipmap.c new file mode 100644 index 0000000..eff42ce --- /dev/null +++ b/ipset_macipmap.c @@ -0,0 +1,387 @@ +/* Copyright 2000, 2001, 2002 Joakim Axelsson (gozem@linux.nu) + * Patrick Schaaf (bof@bof.de) + * Martin Josefsson (gandalf@wlug.westbo.se) + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "ipset.h" + +#define BUFLEN 30; + +#define OPT_CREATE_FROM 0x01U +#define OPT_CREATE_TO 0x02U +#define OPT_CREATE_NETWORK 0x04U +#define OPT_CREATE_MATCHUNSET 0x08U + +#define OPT_ADDDEL_IP 0x01U +#define OPT_ADDDEL_MAC 0x02U + +/* Initialize the create. */ +void create_init(void *data) +{ + DP("create INIT"); + /* Nothing */ +} + +/* Function which parses command options; returns true if it ate an option */ +int create_parse(int c, char *argv[], void *data, unsigned *flags) +{ + struct ip_set_req_macipmap_create *mydata = + (struct ip_set_req_macipmap_create *) data; + + DP("create_parse"); + + switch (c) { + case '1': + parse_ip(optarg, &mydata->from); + + *flags |= OPT_CREATE_FROM; + + DP("--from %x (%s)", mydata->from, + ip_tostring(mydata->from, 0)); + + break; + + case '2': + parse_ip(optarg, &mydata->to); + + *flags |= OPT_CREATE_TO; + + DP("--to %x (%s)", mydata->to, ip_tostring(mydata->to, 0)); + + break; + + case '3': + parse_ipandmask(optarg, &mydata->from, &mydata->to); + + /* Make to the last of from + mask */ + mydata->to = mydata->from | (~mydata->to); + + *flags |= OPT_CREATE_NETWORK; + + DP("--network from %x (%s)", mydata->from, + ip_tostring(mydata->from, 0)); + DP("--network to %x (%s)", mydata->to, + ip_tostring(mydata->to, 0)); + + break; + + case '4': + mydata->flags |= IPSET_MACIP_MATCHUNSET; + + *flags |= OPT_CREATE_MATCHUNSET; + + DP("--matchunset"); + + break; + + default: + return 0; + } + + return 1; +} + +/* Final check; exit if not ok. */ +void create_final(void *data, unsigned int flags) +{ + struct ip_set_req_macipmap_create *mydata = + (struct ip_set_req_macipmap_create *) data; + + if (flags == 0) + exit_error(PARAMETER_PROBLEM, + "Need to specify --from and --to, or --network\n"); + + if (flags & OPT_CREATE_NETWORK) { + /* --network */ + if ((flags & OPT_CREATE_FROM) || (flags & OPT_CREATE_TO)) + exit_error(PARAMETER_PROBLEM, + "Can't specify --from or --to with --network\n"); + } else { + /* --from --to */ + if ((flags & OPT_CREATE_FROM) == 0 + || (flags & OPT_CREATE_TO) == 0) + exit_error(PARAMETER_PROBLEM, + "Need to specify both --from and --to\n"); + } + + + DP("from : %x to: %x diff: %d match unset: %d", mydata->from, + mydata->to, mydata->to - mydata->from, + flags & OPT_CREATE_MATCHUNSET); + + if (mydata->from > mydata->to) + exit_error(PARAMETER_PROBLEM, + "From can't be lower than to.\n", MAX_RANGE); + + if (mydata->to - mydata->from > MAX_RANGE) + exit_error(PARAMETER_PROBLEM, + "Range to large. Max is %d IPs in range\n", + MAX_RANGE); +} + +/* Create commandline options */ +static struct option create_opts[] = { + {"from", 1, 0, '1'}, + {"to", 1, 0, '2'}, + {"network", 1, 0, '3'}, + {"matchunset", 0, 0, '4'}, + {0} +}; + +static void parse_mac(const char *mac, unsigned char *ethernet) +{ + unsigned int i = 0; + + if (strlen(mac) != ETH_ALEN * 3 - 1) + exit_error(PARAMETER_PROBLEM, "Bad mac address `%s'", mac); + + for (i = 0; i < ETH_ALEN; i++) { + long number; + char *end; + + number = strtol(mac + i * 3, &end, 16); + + if (end == mac + i * 3 + 2 && number >= 0 && number <= 255) + ethernet[i] = number; + else + exit_error(PARAMETER_PROBLEM, + "Bad mac address `%s'", mac); + } +} + +/* Add, del, test parser */ +ip_set_ip_t adt_parser(int cmd, const char *optarg, + void *data, const void *setdata) +{ + struct ip_set_req_macipmap *mydata = + (struct ip_set_req_macipmap *) data; + struct ip_set_macipmap *mysetdata = + (struct ip_set_macipmap *) setdata; + char *saved = strdup(optarg); + char *ptr, *tmp = saved; + + DP("macipmap: %p %p", data, setdata); + + ptr = strsep(&tmp, "%"); + parse_ip(ptr, &mydata->ip); + + if (cmd == ADT_ADD && !tmp) + exit_error(PARAMETER_PROBLEM, + "Need to specify ip[mac]\n"); + + if (tmp) + parse_mac(tmp, mydata->ethernet); + else + memset(mydata->ethernet, 0, ETH_ALEN); + + DP("from %s", ip_tostring(mysetdata->first_ip, 0)); + DP("to %s", ip_tostring(mysetdata->last_ip, 0)); + DP("ip %s", ip_tostring(mydata->ip, 0)); + + if (mydata->ip < mysetdata->first_ip || + mydata->ip > mysetdata->last_ip) + exit_error(PARAMETER_PROBLEM, "IP '%s' is out of range\n", + ip_tostring(mydata->ip, 0)); + + free(saved); + + return mydata->ip; +} + +ip_set_ip_t getipbyid(const void *setdata, ip_set_ip_t id) +{ + struct ip_set_macipmap *mysetdata = + (struct ip_set_macipmap *) setdata; + + return (mysetdata->first_ip + id); +} + +ip_set_ip_t sizeid(const void *setdata) +{ + struct ip_set_macipmap *mysetdata = + (struct ip_set_macipmap *) setdata; + + return (mysetdata->last_ip - mysetdata->first_ip + 1); +} + +void initheader(void **setdata, void *data, size_t len) +{ + struct ip_set_req_macipmap_create *header = + (struct ip_set_req_macipmap_create *) data; + + DP("macipmap: initheader() 1"); + + if (len != sizeof(struct ip_set_req_macipmap_create)) + exit_error(OTHER_PROBLEM, + "Macipmap: incorrect size of header. " + "Got %d, wanted %d.", len, + sizeof(struct ip_set_req_macipmap_create)); + + *setdata = ipset_malloc(sizeof(struct ip_set_macipmap)); + + DP("bitmap: initheader() 2"); + + ((struct ip_set_macipmap *) *setdata)->first_ip = header->from; + ((struct ip_set_macipmap *) *setdata)->last_ip = header->to; + ((struct ip_set_macipmap *) *setdata)->flags = header->flags; +} + +void initmembers(void *setdata, void *data, size_t len) +{ + struct ip_set_macipmap *mysetdata = + (struct ip_set_macipmap *) setdata; + size_t size; + + DP("macipmap: initmembers()"); + + /* Check so we get the right amount of memberdata */ + size = (mysetdata->last_ip - mysetdata->first_ip) + * sizeof(struct ip_set_macip); + + if (len != size) + exit_error(OTHER_PROBLEM, + "Macipmap: incorrect size of members. " + "Got %d, wanted %d.", len, size); + + mysetdata->members = data; +} + +void killmembers(void **setdata) +{ + struct ip_set_macipmap *mysetdata = + (struct ip_set_macipmap *) *setdata; + + DP("macipmap: killmembers()"); + + if (mysetdata->members != NULL) + ipset_free(&mysetdata->members); + + ipset_free(setdata); +} + +void printheader(const void *setdata, unsigned options) +{ + struct ip_set_macipmap *mysetdata = + (struct ip_set_macipmap *) setdata; + + printf(" from: %s", ip_tostring(mysetdata->first_ip, options)); + printf(" to: %s", ip_tostring(mysetdata->last_ip, options)); + + if (mysetdata->flags & IPSET_MACIP_MATCHUNSET) + printf(" matchunset"); + printf("\n"); +} + +static void print_mac(unsigned char macaddress[ETH_ALEN]) +{ + unsigned int i; + + printf("%02X", macaddress[0]); + for (i = 1; i < ETH_ALEN; i++) + printf(":%02X", macaddress[i]); +} + +void printips_sorted(const void *setdata, unsigned options) +{ + struct ip_set_macipmap *mysetdata = + (struct ip_set_macipmap *) setdata; + struct ip_set_macip *table = + (struct ip_set_macip *) mysetdata->members; + u_int32_t addr = mysetdata->first_ip; + + while (addr <= mysetdata->last_ip) { + if (test_bit(IPSET_MACIP_ISSET, + &table[addr - mysetdata->first_ip].flags)) { + printf("%s%s", ip_tostring(addr, options), "%"); + print_mac(table[addr - mysetdata->first_ip]. + ethernet); + printf("\n"); + } + addr++; + } +} + +void saveheader(const void *setdata) +{ + return; +} + +/* Print save for an IP */ +void saveips(const void *setdata) +{ + return; +} + +void usage(void) +{ + printf + ("-N set macipmap --from IP --to IP [--matchunset]\n" + "-N set macipmap --network IP/mask [--matchunset]\n" + "-A set IP%%MAC\n" + "-D set IP[%%MAC]\n" + "-T set IP[%%MAC]\n"); +} + +static struct settype settype_macipmap = { + .typename = SETTYPE_NAME, + .typecode = IPSET_TYPE_IP, + .protocol_version = IP_SET_PROTOCOL_VERSION, + + /* Create */ + .create_size = sizeof(struct ip_set_req_macipmap_create), + .create_init = &create_init, + .create_parse = &create_parse, + .create_final = &create_final, + .create_opts = create_opts, + + /* Add/del/test */ + .req_size = sizeof(struct ip_set_req_macipmap), + .adt_parser = &adt_parser, + + /* Get an IP address by id */ + .getipbyid = &getipbyid, + .sizeid = &sizeid, + + /* Printing */ + .initheader = &initheader, + .initmembers = &initmembers, + .killmembers = &killmembers, + .printheader = &printheader, + .printips = &printips_sorted, /* We only have sorted version */ + .printips_sorted = &printips_sorted, + .saveheader = &saveheader, + .saveips = &saveips, + .usage = &usage, + .hint = NULL, +}; + +void _init(void) +{ + settype_register(&settype_macipmap); + +} diff --git a/ipset_portmap.c b/ipset_portmap.c new file mode 100644 index 0000000..fd8746f --- /dev/null +++ b/ipset_portmap.c @@ -0,0 +1,293 @@ +/* Copyright 2004 Jozsef Kadlecsik (kadlec@blackhole.kfki.hu) + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include +#include +#include +#include +#include + +#include +#include "ipset.h" + + +#define BUFLEN 30; + +#define OPT_CREATE_FROM 0x01U +#define OPT_CREATE_TO 0x02U + +#define OPT_ADDDEL_PORT 0x01U + +/* Initialize the create. */ +void create_init(void *data) +{ + DP("create INIT"); + /* Nothing */ +} + +/* Function which parses command options; returns true if it ate an option */ +int create_parse(int c, char *argv[], void *data, unsigned *flags) +{ + struct ip_set_req_portmap_create *mydata = + (struct ip_set_req_portmap_create *) data; + + DP("create_parse"); + + switch (c) { + case '1': + parse_port(optarg, &mydata->from); + + *flags |= OPT_CREATE_FROM; + + DP("--from %x (%s)", mydata->from, + port_tostring(mydata->from, 0)); + + break; + + case '2': + parse_port(optarg, &mydata->to); + + *flags |= OPT_CREATE_TO; + + DP("--to %x (%s)", mydata->to, + port_tostring(mydata->to, 0)); + + break; + + default: + return 0; + } + + return 1; +} + +/* Final check; exit if not ok. */ +void create_final(void *data, unsigned int flags) +{ + struct ip_set_req_portmap_create *mydata = + (struct ip_set_req_portmap_create *) data; + + if (flags == 0) { + exit_error(PARAMETER_PROBLEM, + "Need to specify --from and --to\n"); + } else { + /* --from --to */ + if ((flags & OPT_CREATE_FROM) == 0 + || (flags & OPT_CREATE_TO) == 0) + exit_error(PARAMETER_PROBLEM, + "Need to specify both --from and --to\n"); + } + + DP("from : %x to: %x diff: %d", mydata->from, mydata->to, + mydata->to - mydata->from); + + if (mydata->from > mydata->to) + exit_error(PARAMETER_PROBLEM, + "From can't be lower than to.\n", MAX_RANGE); + + if (mydata->to - mydata->from > MAX_RANGE) + exit_error(PARAMETER_PROBLEM, + "Range to large. Max is %d ports in range\n", + MAX_RANGE); +} + +/* Create commandline options */ +static struct option create_opts[] = { + {"from", 1, 0, '1'}, + {"to", 1, 0, '2'}, + {0} +}; + +/* Add, del, test parser */ +ip_set_ip_t adt_parser(int cmd, const char *optarg, + void *data, const void *setdata) +{ + struct ip_set_req_portmap *mydata = + (struct ip_set_req_portmap *) data; + struct ip_set_portmap *mysetdata = + (struct ip_set_portmap *) setdata; + + DP("portmap: %p %p", data, setdata); + + parse_port(optarg, &mydata->port); + + DP("from %s", port_tostring(mysetdata->first_port, 0)); + DP("to %s", port_tostring(mysetdata->last_port, 0)); + DP("port %s", port_tostring(mydata->port, 0)); + + if (mydata->port < mysetdata->first_port || + mydata->port > mysetdata->last_port) + exit_error(PARAMETER_PROBLEM, "Port '%s' is out of range\n", + port_tostring(mydata->port, 0)); + + return mydata->port; +} + +ip_set_ip_t getportbyid(const void *setdata, ip_set_ip_t id) +{ + struct ip_set_portmap *mysetdata = + (struct ip_set_portmap *) setdata; + + return (mysetdata->first_port + id); +} + +ip_set_ip_t sizeid(const void *setdata) +{ + struct ip_set_portmap *mysetdata = + (struct ip_set_portmap *) setdata; + + return (mysetdata->last_port - mysetdata->first_port + 1); +} + +void initheader(void **setdata, void *data, size_t len) +{ + struct ip_set_req_portmap_create *header = + (struct ip_set_req_portmap_create *) data; + + DP("portmap: initheader() 1"); + + if (len != sizeof(struct ip_set_req_portmap_create)) + exit_error(OTHER_PROBLEM, + "Portmap: incorrect size of header. " + "Got %d, wanted %d.", len, + sizeof(struct ip_set_req_portmap_create)); + + *setdata = ipset_malloc(sizeof(struct ip_set_portmap)); + + DP("portmap: initheader() 2"); + + ((struct ip_set_portmap *) *setdata)->first_port = + header->from; + ((struct ip_set_portmap *) *setdata)->last_port = header->to; + +} + +void initmembers(void *setdata, void *data, size_t len) +{ + struct ip_set_portmap *mysetdata = + (struct ip_set_portmap *) setdata; + size_t size; + + DP("portmap: initmembers()"); + + /* Check so we get the right amount of memberdata */ + size = bitmap_bytes(mysetdata->first_port, mysetdata->last_port); + + if (len != size) + exit_error(OTHER_PROBLEM, + "Portmap: incorrect size of members. " + "Got %d, wanted %d.", len, size); + + mysetdata->members = data; +} + +void killmembers(void **setdata) +{ + struct ip_set_portmap *mysetdata = + (struct ip_set_portmap *) *setdata; + + DP("portmap: killmembers()"); + + if (mysetdata->members != NULL) + ipset_free(&mysetdata->members); + + ipset_free(setdata); +} + + +void printheader(const void *setdata, unsigned options) +{ + struct ip_set_portmap *mysetdata = + (struct ip_set_portmap *) setdata; + + printf(" from: %s", port_tostring(mysetdata->first_port, options)); + printf(" to: %s\n", port_tostring(mysetdata->last_port, options)); +} + +void printports_sorted(const void *setdata, unsigned options) +{ + struct ip_set_portmap *mysetdata = + (struct ip_set_portmap *) setdata; + + u_int32_t addr = mysetdata->first_port; + + while (addr <= mysetdata->last_port) { + if (test_bit(addr - mysetdata->first_port, mysetdata->members)) + printf("%s\n", port_tostring(addr, options)); + addr++; + } +} + +void saveheader(const void *setdata) +{ + return; +} + +/* Print save for an IP */ +void saveports(const void *setdata) +{ + return; +} + +void usage(void) +{ + printf + ("-N set portmap --from PORT --to PORT\n" + "-A set PORT\n" + "-D set PORT\n" + "-T set PORT\n"); +} + +static struct settype settype_portmap = { + .typename = SETTYPE_NAME, + .typecode = IPSET_TYPE_PORT, + .protocol_version = IP_SET_PROTOCOL_VERSION, + + /* Create */ + .create_size = sizeof(struct ip_set_req_portmap_create), + .create_init = &create_init, + .create_parse = &create_parse, + .create_final = &create_final, + .create_opts = create_opts, + + /* Add/del/test */ + .req_size = sizeof(struct ip_set_req_portmap), + .adt_parser = &adt_parser, + + /* Get an IP address by id */ + .getipbyid = &getportbyid, + .sizeid = &sizeid, + + /* Printing */ + .initheader = &initheader, + .initmembers = &initmembers, + .killmembers = &killmembers, + .printheader = &printheader, + .printips = &printports_sorted, /* We only have sorted version */ + .printips_sorted = &printports_sorted, + .saveheader = &saveheader, + .saveips = &saveports, + .usage = &usage, + .hint = NULL, +}; + +void _init(void) +{ + settype_register(&settype_portmap); + +} diff --git a/libipt_set.h b/libipt_set.h new file mode 100644 index 0000000..8180f7c --- /dev/null +++ b/libipt_set.h @@ -0,0 +1,132 @@ +#ifndef _LIBIPT_SET_H +#define _LIBIPT_SET_H + +#include +#include +#include + +static int get_set_getsockopt(void *data, size_t * size) +{ + int sockfd = -1; + sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); + if (sockfd < 0) + exit_error(OTHER_PROBLEM, + "Can't open socket to ipset.\n"); + /* Send! */ + return getsockopt(sockfd, SOL_IP, SO_IP_SET, data, size); +} + +static void get_set_byname(const char *setname, struct ipt_set_info *info) +{ + struct ip_set_req_get req; + int size = sizeof(struct ip_set_req_get); + int res; + + req.op = IP_SET_OP_GETSET_BYNAME; + strncpy(req.name, setname, IP_SET_MAXNAMELEN); + req.name[IP_SET_MAXNAMELEN - 1] = '\0'; + res = get_set_getsockopt(&req, &size); + if (res != 0) + exit_error(OTHER_PROBLEM, + "Problem when communicating with ipset. errno=%d.\n", + errno); + if (size != sizeof(struct ip_set_req_get)) + exit_error(OTHER_PROBLEM, + "Incorrect return size from kernel during ipset lookup, " + "(want %d, got %d)\n", + sizeof(struct ip_set_req_get), size); + if (req.id < 0) + exit_error(PARAMETER_PROBLEM, + "Set %s doesn't exist.\n", setname); + + info->id = req.id; +} + +static void get_set_byid(char * setname, unsigned id) +{ + struct ip_set_req_get req; + int size = sizeof(struct ip_set_req_get); + int res; + + req.op = IP_SET_OP_GETSET_BYID; + req.id = id; + res = get_set_getsockopt(&req, &size); + if (res != 0) + exit_error(OTHER_PROBLEM, + "Problem when communicating with ipset. errno=%d.\n", + errno); + if (size != sizeof(struct ip_set_req_get)) + exit_error(OTHER_PROBLEM, + "Incorrect return size from kernel during ipset lookup, " + "(want %d, got %d)\n", + sizeof(struct ip_set_req_get), size); + if (req.id < 0) + exit_error(PARAMETER_PROBLEM, + "Set id %i in kernel doesn't exist.\n", id); + + strncpy(setname, req.name, IP_SET_MAXNAMELEN); +} + +static void +parse_pool(const char *optarg, struct ipt_set_info *info) +{ + char *saved = strdup(optarg); + char *ptr, *tmp = saved; + + ptr = strsep(&tmp, ":"); + get_set_byname(ptr, info); + + while (info->set_level < IP_SET_SETIP_LEVELS && tmp) { + ptr = strsep(&tmp, ","); + if (strncmp(ptr, "src", 3) == 0) + info->flags[info->set_level++] |= IPSET_SRC; + else if (strncmp(ptr, "dst", 3) == 0) + info->flags[info->set_level++] |= IPSET_DST; + else + exit_error(PARAMETER_PROBLEM, + "You must spefify (the comma separated list of) 'src' or 'dst'."); + } + + if (tmp || info->set_level >= IP_SET_SETIP_LEVELS) + exit_error(PARAMETER_PROBLEM, + "Defined childset level is deeper that %i.", + IP_SET_SETIP_LEVELS); + + free(saved); +} + +static int +parse_ipflags(const char *optarg, struct ipt_set_info *info) +{ + char *saved = strdup(optarg); + char *ptr, *tmp = saved; + int overwrite = 0; + + info->ip_level = info->set_level; + + while (info->ip_level < IP_SET_LEVELS && tmp) { + if (*tmp == '+') { + info->flags[info->ip_level] |= IPSET_ADD_OVERWRITE; + tmp++; + overwrite++; + } + ptr = strsep(&tmp, ","); + if (strncmp(ptr, "src", 3) == 0) + info->flags[info->ip_level++] |= IPSET_SRC; + else if (strncmp(ptr, "dst", 3) == 0) + info->flags[info->ip_level++] |= IPSET_DST; + else + exit_error(PARAMETER_PROBLEM, + "You must spefify (the comma separated list of) 'src' or 'dst'."); + } + + if (tmp || info->ip_level >= IP_SET_LEVELS) + exit_error(PARAMETER_PROBLEM, + "Defined level is deeper that %i.", + IP_SET_LEVELS); + + free(saved); + return overwrite; +} + +#endif /*_LIBIPT_SET_H*/ -- 2.40.0