Το μουλάρι, ο πινγκουίνος και η γέφυρα.

Ή πώς να κατεβάζετε ανενόχλητοι τα mail σας κι ας οργιάζουν τα P2P στην LAN…

Η κατάσταση

Στο σπίτι εχουμε μια σύνδεση τύπου LAN μέσω ενός proprietary router που επιτρέπει μέσω dhcp μόνο 3 IP addresses. Καθώς είμαστε 3 στο σπίτι και εγώ έχω δύο υπολογιστές (tower και laptop), χρησιμοποιώ ένα custom linux gateway για να συνδεθώ στον router. Οι άλλοι δύο συγκάτοικοι ωστόσο αρνούνται πεισματικά να μπούν «πίσω» από το firewall τόσο εξαιτίας του περιορισμού στις ηλιθιώδεις uPNP συνδέσεις που το λογισμικό της Μ$ χρησιμοποιεί κατά κόρον όσο και, όπως υποπτεύομαι, μιας κουτοπόνηρης απληστίας για το bandwidth (φοβούμενοι μάλλον πως πίσω από ένα firewall του οποίου μόνο εγώ έχω τις τεχνικές γνώσεις να ρυθμίσω θα τους «έριχνα» στο traffic shaping).

Ένας δεύτερος περιορισμός είναι πως το upload bandwidth είναι ιδιαίτερα περιορισμένο σε σχέση με το download (4Mbps down / 512Kbps up) με αποτέλεσμα πως όταν το download πλησιάζει τον κορεσμό, το upload να καταλαμβάνεται σχεδόν εντελώς από τα ACK πακέτα.

Το τρίτο, και ίσως σημαντικότερο, πρόβλημα είναι πως ο router είναι μάλλον της πλάκας και έχει ιδιαίτερα περιορισμένη χωρητικότητα στα ARP tables.

Το πρόβλημα

Το πρώτο πρόβλημα που παρουσιάστηκε, αυτό του κορεσμού του upload εξαιτίας του downloading, θα λυνόταν εύκολα με QoS στο firewall. Λόγω της μη συμμετοχής των δύο κύριων λειτουργών του (προσωπικά θα με δείτε να κάνω massive download μόνο στα update της gentoo) το πρόβλημα τελικά λύθηκε με προσωπικά network traffic limits στα P2P «κατεβαστήρια» του καθενός.

Όταν όμως πρόσφατα και οι δύο πέρασαν από το DC πρωτόκολο σε eMule… άρχισε το δράμα, χειρότερο από ποτέ άλλοτε και ιδίως για μένα που και πέρναγα από ενα επιπλέον hop (τον gateway) και δεν κράταγα live connections αφού ως επί το πλείστον έκανα web browsing και check mails. Η κατάσταση έφτασε στο απροχώρητο: loading web pages αρκετές φορές με μισό kb/s, 3 στις 5 φορές οι mail servers έπεφταν σε timeout connection και γενικά η σύνδεσή μου είχε καταντήσει unusable.

Ομολογώ πως έφτασα σε τέτοια απόγνωση που σε μια συζήτηση για το πρόβλημα με έναν φίλο, επαγγελματία sysadmin, αγγίξαμε την ιδέα μεταξύ σοβαρού και αστείου για ARP poisoning, να DoSάρω δλδ τους συγκάτοικους για να μπορώ να δω και κανα μπλογκ! Έκει όμως κάτι έκανε «κλικ» στο μυαλό μας: δεν ηταν το πνιγμένο download που μου έκανε την ζωή δύσκολη, αφού διαπιστωμένα τα «κατεβαστήρια» είχαν λογικά όρια, αλλά τα connections. Το μουλάρι (eMule) από default αφήνει έναν απίστευτα υψηλό αριθμό ανοιχτών συνδέσεων (με «κόφτη» κάπου στις 70000 αν δεν κάνω λάθος) κι ας μην κατεβάζει στα όρια της γραμμής. Αυτή η συμπεριφορά δεν εμφανίζεται σε άλλα πρωτόκολα p2p και εξ ου η δραματική αλλαγή όταν και οι δύο πέρασαν σε συστηματική του χρήση. Μιλάμε επίσης για δύο εντελώς non-techie ανθρώπους που αποκλείεται να έπαιρναν είδηση του τι και γιατί συμβαίνει ενώ οι πρώτες «διερευνητικές» νύξεις μου έπεσαν εντελώς στο κενό. Όποια λύση και να έβρισκα, θα έπρεπε να την βρώ μόνος μου και να είναι εντελώς transparent στους άλλους δύο.

Η λύση

Ο Γιώργος (ο sysadmin) είχε βρεθεί σε μια αντίστοιχη φάση στην εταιρία που δούλευε παλιότερα όταν μια κατοστάρα pc «τσακώνονταν» για μια θέση στα ARP tables μιας SOHO σύνδεσης στα 12Mbps. Μιλάμε φυσικά για «μεσογειακές καταστάσεις» όπου εταιρικός ή μη υπολογιστής, αφού έχει broadband access, θα έχει και το «μουλάρι» του, με αποτέλεσμα όσοι έφταναν πέντε λεπτά νωρίτερα από τους άλλους να κατεβάζουν της παναγιάς τα μάτια και οι υπόλοιποι να αδυνατούν να στείλουν ένα απλό text mail!

Η λύση που έδωσε τότε ήταν το στήσιμο ενός linux firewall με «patchαρισμένο» netfilter για «connection limit» target. Έτσι, κανείς χρήστης δεν μπορούσε να έχει πάνω από έναν συγκεκριμένο αριθμό ανοιχτών connections με αποτέλεσμα να μην «μπουκώνει» ο router και ενώ τα μουλάρια συνέχιζαν να κατεβάζουν με μια λογική ταχύτητα, όλοι είχαν ουσιαστική access στο internet για «πιο χρήσιμα πράγματα» όπως mails και web.

Εδώ όμως είχαμε ένα επιπρόσθετο πρόβλημα: η δουλειά έπρεπε να γίνει εντελώς transparent, σαν να μην υπήρχε καν ο gateway στην θέση του και τα pc να συνδέονταν κατευθείαν στον router. Η λύση στο πρόβλημα ήρθε με την μορφή του bridging, γεφυρώνοντας την «εξωτερική» eth1 με την «εσωτερική» eth0. Για τον έλεγχο του headless gateway προσθέσαμε μια τρίτη NIC με private address και μέσω της οποιάς μπορώ να συνδεθώ με ssh. Τα πρώτα αποτελέσματα είναι εξαιρετικά ικανοποιητικά αν και χρειάζεται ακόμα πολύ fine tunning.

Το «How-To»

Τι χρειάζεστε:

  • Τα bridge-utils για την δημιουργία και τον έλεγχο της bridge.
  • Έναν πρόσφατο 2.6.x kernel.
  • Τον πηγαίο κώδικα του latest stable iptables (προς το παρόν 1.3.5).
  • Το latest patchset «patch-o-matic» (εμείς χρησιμοποιήσαμε το patch-o-matic-ng-20060216).

Το «στήσιμο»:

  1. «Στήνετε» κανονικά (configure – make – make install) τα bridge-utils.
  2. «Ξεπακεταρετε» στο /usr/src/ τον kernel, το iptables και το patch-o-matic.
  3. Μπαινετε στην directory του patch-o-matic και τρεχετε το εξης command που θα «πατσάρει» kernel sources και το userland iptables με τα «known to work» features:
    KERNEL_DIR=/usr/src/linux IPTABLES_DIR=/usr/src/iptables-1.3.5 ./runme base
  4. Ρυθμίζουμε τον kernel (π.χ. με make menuconfig) ώστε να περιέχει σαν support το bridging καθώς και όλα τα targets του netfilter που μας ενδιαφέρουν κατά προτίμιση σαν modules. ΠΡΟΣΟΧΗ, στον δικό μου 2.6.11-gentoo-r4 το TIME target εκανε fail οπότε και ξύλωσα το support γι’ αυτό (δεν ήταν κάτι που με «έκαιγε» κιόλας). Λογικά ένας πιο recent kernel δεν θα αντιμετωπίσει πρόβλημα με κανένα module αλλά που να κατέβαζα 30+Mb με τέτοιο πρόβλημα που είχα…
  5. Στήνουμε κανονικά τον kernel (make – make modules_install) και τον φορτώνουμε στον bootloader.
  6. Στήνουμε το userspace iptables με make KERNEL_DIR=/usr/src/linux και make install KERNEL_DIR=/usr/src/linux
  7. Reboot, και ειμαστε έτοιμοι να φτιάξουμε ένα bridging firewall με connection limiting.

Οι firewall rules

Για να μην σπάμε τα @@ μας, βασιζόμενοι στο αρχικό script του Γιώργου για την εταιρία, στήσαμε τα παρακάτω scriptακια. Φυσικά λοίπουν οι rules για το νορμαλ firewalling που θα πρέπει να προσθέσετε εσείς ανάλογα με τις ανάγκες σας, ενώ interfaces και IP addresses θα πρέπει να προσαρμοστούν στο δικό σας περιβάλλον.

Το /etc/init.d/fw-bridge (αυτόματο «ανέβασμα» και «κατέβασμα» του bridging firewall):

#!/bin/bash
#(C) G. Tsarouchas 2005-2006
DATESTAMP=`date`
LOGFILE=/var/log/fw-status.log
flush_rules()
{
ERRORSTATUS=0
echo -n "Flushing Firewall Rules..."
LIMIT=`grep  -v -e '^#' /etc/fw-rules/flush.cfg |wc -l | cut -d " " -f 1`
for ((i=LIMIT ; i > 0 ; --i))
do
RESULT=`grep  -v -e '^#' /etc/fw-rules/flush.cfg | tail -n $i |line`
echo -ne "tiptables $RESULT   " >> $LOGFILE
/sbin/iptables $RESULT 2>> $LOGFILE
RET_VAL=$?
if [ $RET_VAL != "0" ]
then echo "...FAILED" >> $LOGFILE
ERRORSTATUS=1
else
echo "...done!" >> $LOGFILE
fi
done
if [ $ERRORSTATUS = "1" ]
then echo "......FAILED"
else
echo "......done"
fi
}

default_policy()
{
ERRORSTATUS=0
echo -n "Applying Default Policy...."
LIMIT=`grep  -v -e '^#' /etc/fw-rules/policy.cfg|wc -l | cut -d " " -f 1`
for ((i=LIMIT ; i > 0 ; --i))
do
RESULT=`grep  -v -e '^#' /etc/fw-rules/policy.cfg| tail -n $i |line`
echo -ne "tiptables $RESULT   " >> $LOGFILE
/sbin/iptables $RESULT 2>> $LOGFILE
RET_VAL=$?
if [ $RET_VAL != "0" ]
then echo "...FAILED" >> $LOGFILE
ERRORSTATUS=1
else
echo "...done!" >> $LOGFILE
fi
done
if [ $ERRORSTATUS = "1" ]
then echo "......FAILED"
else
echo "......done"
fi
}

create_chains()
{
ERRORSTATUS=0
echo -n "Creating IPTABLES Chains"
LIMIT=`grep  -v -e '^#' /etc/fw-rules/chains-declare.cfg |wc -l | cut -d " " -f 1`
for ((i=LIMIT ; i > 0 ; --i))
do
RESULT=`grep  -v -e '^#' /etc/fw-rules/chains-declare.cfg| tail -n $i |line`
echo -ne "tiptables $RESULT   " >> $LOGFILE
/sbin/iptables $RESULT 2>> $LOGFILE
RET_VAL=$?
if [ $RET_VAL != "0" ]
then echo "...FAILED" >> $LOGFILE
ERRORSTATUS=1
else
echo "...done!" >> $LOGFILE
fi
done
if [ $ERRORSTATUS = "1" ]
then echo "......FAILED"
else
echo "......done"
fi
}

assign_chains()
{
ERRORSTATUS=0
echo -n "Assigning Default Chain Policies"
LIMIT=`grep  -v -e '^#' /etc/fw-rules/chains-policy.cfg |wc -l | cut -d " " -f 1`
for ((i=LIMIT ; i > 0 ; --i))
do
RESULT=`grep  -v -e '^#' /etc/fw-rules/chains-policy.cfg| tail -n $i |line`
echo -ne "tiptables $RESULT   " >> $LOGFILE
/sbin/iptables $(echo "$RESULT") 2>> $LOGFILE
RET_VAL=$?
if [ $RET_VAL != "0" ]
then echo "...FAILED" >> $LOGFILE
ERRORSTATUS=1
else
echo "...done!" >> $LOGFILE
fi
done
if [ $ERRORSTATUS = "1" ]
then echo "......FAILED"
else
echo "......done"
fi
}
outbound_traffic()
{
ERRORSTATUS=0
echo -n "Applying Outbound Policy"
LIMIT=`grep  -v -e '^#' /etc/fw-rules/intranet-internet.cfg |wc -l | cut -d " " -f 1`
for ((i=LIMIT ; i > 0 ; --i))
do
RESULT=`grep  -v -e '^#' /etc/fw-rules/intranet-internet.cfg |tail -n $i |line`
echo -ne "tiptables $RESULT   " >> $LOGFILE
/sbin/iptables $RESULT 2>> $LOGFILE
RET_VAL=$?
if [ $RET_VAL != "0" ]
then echo "...FAILED" >> $LOGFILE
ERRORSTATUS=1
else
echo "...done!" >> $LOGFILE
fi
done
if [ $ERRORSTATUS = "1" ]
then echo "......FAILED"
else
echo "......done"
fi
}

inbound_traffic()
{
ERRORSTATUS=0
echo -n "Applying Inbound Policy"
LIMIT=`grep  -v -e '^#' /etc/fw-rules/internet-intranet.cfg |wc -l | cut -d " " -f 1`
for ((i=LIMIT ; i > 0 ; --i))
do
RESULT=`grep  -v -e '^#' /etc/fw-rules/internet-intranet.cfg |tail -n $i |line`
echo -ne "tiptables $RESULT   " >> $LOGFILE
/sbin/iptables $RESULT 2>> $LOGFILE
RET_VAL=$?
if [ $RET_VAL != "0" ]
then echo "...FAILED" >> $LOGFILE
ERRORSTATUS=1
else
echo "...done!" >> $LOGFILE
fi
done
if [ $ERRORSTATUS = "1" ]
then echo "......FAILED"
else
echo "......done"
fi
}

gateway_traffic()
{
ERRORSTATUS=0
echo -n "Applying Gateway's self Policy"
LIMIT=`grep  -v -e '^#' /etc/fw-rules/gateway.cfg|wc -l | cut -d " " -f 1`
for ((i=LIMIT ; i > 0 ; --i))
do
RESULT=`grep  -v -e '^#' /etc/fw-rules/gateway.cfg |tail -n $i |line`
echo -ne "tiptables $RESULT   " >> $LOGFILE
/sbin/iptables $RESULT 2>> $LOGFILE
RET_VAL=$?
if [ $RET_VAL != "0" ]
then echo "...FAILED" >> $LOGFILE
ERRORSTATUS=1
else
echo "...done!" >> $LOGFILE
fi
done
if [ $ERRORSTATUS = "1" ]
then echo "......FAILED"
else
echo "......done"
fi
}

natting_rules()
{
echo -n "Applying NAT rules"
LIMIT=`grep  -v -e '^#' /etc/fw-rules/nat.cfg|wc -l | cut -d " " -f 1`
for ((i=LIMIT ; i > 0 ; --i))
do
RESULT=`grep  -v -e '^#' /etc/fw-rules/nat.cfg|tail -n $i|line`
echo -ne "tiptables $RESULT   " >> $LOGFILE
/sbin/iptables $RESULT 2>> $LOGFILE
RET_VAL=$?
if [ $RET_VAL != "0" ]
then echo "...FAILED" >> $LOGFILE
ERRORSTATUS=1
else
echo "...done!" >> $LOGFILE
fi
done
if [ $ERRORSTATUS = "1" ]
then echo "......FAILED"
else
echo "......done"
fi
}

modules_load()
{
ERRORSTATUS=0
echo -n "Loading IPTABLE'S modules "
for i in /lib/modules/`uname -r`/kernel/net/ipv4/netfilter/ip*
do
echo -n "Loading Module " >> $LOGFILE
MDL=$(echo $i|cut -d / -f 9|cut -d "." -f 1)
echo -n " $MDL " >> $LOGFILE
modprobe $MDL 2>&1 >/dev/null
RET_VAL=$?
if [ $RET_VAL != "0" ]
then echo "...FAILED" >> $LOGFILE
ERRORSTATUS=1
else
echo "...done!" >> $LOGFILE
fi
done
if [ $ERRORSTATUS = "1" ]
then echo "......FAILED"
else
echo "......done"
fi
}
modules_unload()
{
ERRORSTATUS=0
echo -n "Unloading IPTABLE'S modules "
for i in /lib/modules/`uname -r`/kernel/net/ipv4/netfilter/ip*
do
echo -n "Unloading Module " >> $LOGFILE
MDL=$(echo $i|cut -d / -f 9|cut -d "." -f 1)
echo -n " $MDL " >> $LOGFILE
modprobe -r $MDL 2>&1 >/dev/null
RET_VAL=$?
if [ $RET_VAL != "0" ]
then echo "...FAILED" >> $LOGFILE
ERRORSTATUS=1
else
echo "...done!" >> $LOGFILE
fi
done
if [ $ERRORSTATUS = "1" ]
then echo "......FAILED"
else
echo "......done"
fi
}
bridge_start()
{
echo -n "Activating Bridging ...."
echo 1 > /proc/sys/net/ipv4/ip_forward
echo -n "Shutting down interfaces "
/etc/init.d/net.eth0 stop 2>&1 >> $LOGFILE
if [ "$?" != "0" ]
then echo -n "ETH0 FAILED"
else
echo -n "eth0"
fi
/etc/init.d/net.eth1 stop 2>&1 >> $LOGFILE
if [ "$?" != "0" ]
then echo  "ETH1 FAILED"
else
echo  "eth1"
fi

echo -n "Getting eth0 in promiscuous mode..."
/sbin/ifconfig eth0 0.0.0.0 up
if [ "$?" != "0" ]
then echo "FAILED"
else
echo "Done"
fi
echo -n "Getting eth1 in promiscuous mode..."
/sbin/ifconfig eth1 0.0.0.0 up
if [ "$?" != "0" ]
then echo "FAILED"
else
echo "Done"
fi
echo -n "Creating bridge br0..."
/sbin/brctl addbr br0
if [ "$?" != "0" ]
then echo "FAILED"
else
echo "Done"
fi
echo -n "adding interfaces "
/sbin/brctl addif br0 eth0
if [ "$?" != "0" ]
then echo -n "ETH0 FAILED"
else
echo -n "eth0"
fi
/sbin/brctl addif br0 eth1
if [ "$?" != "0" ]
then echo  "ETH1 FAILED"
else
echo "eth1"
fi
echo -n "Activating dhcp on br0..."
/sbin/dhcpcd br0
if [ "$?" != "0" ]
then echo "FAILED"
else
echo "Done"
fi

echo -n "Activating Bridge " >> $LOGFILE
}

bridge_stop()
{
echo -n "Deactivating Bridging ...."
echo 0 > /proc/sys/net/ipv4/ip_forward
echo -n "Deactivating Bridging " >> $LOGFILE
/sbin/brctl delif br0 eth0
/sbin/brctl delif br0 eth1
if [ "$?" != "0" ]
then echo "FAILED"
else
echo "Done"
fi
echo -n "Destroying bridge br0..."
/sbin/brctl delbr br0
if [ "$?" != "0" ]
then echo "FAILED"
else
echo "Done"
fi
echo -n "Activating ethernet "
/etc/init.d/net.eth0 start
if [ "$?" != "0" ]
then echo -n "ETH0 FAILED"
else
echo -n "eth0"
fi
/etc/init.d/net.eth1 start
if [ "$?" != "0" ]
then echo  "ETH1 FAILED"
else
echo "eth1"
fi
}

case "$1" in
start)
echo "Starting Gateway..."
echo "Starting Gateway at $DATESTAMP">> $LOGFILE
bridge_start
modules_load
flush_rules
default_policy
create_chains
assign_chains
outbound_traffic
inbound_traffic
gateway_traffic
natting_rules
;;
stop)
echo "Stopping Gateway..."
echo "Stopping Gateway at $DATESTAMP">> $LOGFILE
flush_rules
modules_unload
bridge_stop
;;
restart)
echo "Restarting Gateway"
$0 stop
$0 start
;;

*)
echo "Usage: $0 {start|stop|restart}"
;;
esac

/etc/fw-rules/chains-declare.cfg:

-N intranet-internet
-N internet-intranet
-N icmp-acc
-N external
-N internal
-N limited
-N bridge-in
-N bridge-out
#end here

/etc/fw-rules/chains-policy.cfg:

-A INPUT -p icmp -j icmp-acc
-A INPUT -i br0 -j external
-A INPUT -i eth2 -j internal
-A FORWARD -p icmp -j icmp-acc
-A FORWARD -i eth2 -o br0 -j intranet-internet
-A FORWARD -i br0 -o eth2 -j internet-intranet
-A limited -j LOG --log-level info --log-prefix Limited_
-A limited -j REJECT
-A FORWARD -m physdev --physdev-in eth1 --physdev-out eth0 -j bridge-in
-A FORWARD -m physdev --physdev-in eth0 --physdev-out eth1 -j bridge-out
-A intranet-internet -j LOG --log-prefix intra-inter_
-A internet-intranet -j LOG --log-prefix out-in_
-A bridge-out -j LOG --log-prefix bridge-out_
-A bridge-in -j LOG --log-prefix bridge-in_
#end here

/etc/fw-rules/flush.cfg:

-Z
-X
-F
-t nat -F
#End Here

/etc/fw-rules/gateway.cfg:

-A external -p tcp -s 0/0 --dport 22 -j ACCEPT
-A internal -p tcp -s 10.0.0.0/24 --dport 22 -j ACCEPT
#end here

/etc/fw-rules/internet-intranet.cfg:

-A internet-intranet -p icmp -m limit --limit 30/minute -j ACCEPT
-A internet-intranet -p icmp -j REJECT
-A internet-intranet -m state --state ESTABLISHED,RELATED -j ACCEPT
-A internet-intranet -j DROP
#end here

/etc/fw-rules/intranet-internet.cfg:

-A icmp-acc -i eth2 -o br0 -m limit --limit 30/minute -j ACCEPT
connlimit --connlimit-above 25 --connlimit-mask 32 -j limited
-A intranet-internet -s 10.0.0.0/24 -d 0/0 -j ACCEPT
-A intranet-internet  -j DROP
#Bridging support rules
-A bridge-in -j ACCEPT
-A bridge-out -p tcp --dport 1024:64000 --syn -m connlimit --connlimit-above 500 --connlimit-mask 32 -j limited
-A bridge-out -j ACCEPT
#end here

/etc/fw-rules/nat.cfg:

-t nat -A POSTROUTING -s 10.0.0.0/24 -o br0 -j MASQUERADE
#end here

/etc/fw-rules/policy.cfg:

-P FORWARD DROP
-P OUTPUT DROP
-A INPUT -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT
-A OUTPUT -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT
-A INPUT -i eth2 -s 10.0.0.0/24 -d 0/0 -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A FORWARD -p udp -s 0.0.0.0/32 -d 255.255.255.255 --sport 67 --dport 68 -j ACCEPT
-A OUTPUT  -o lo -s 0/0 -d 0/0 -j ACCEPT
-A OUTPUT -j ACCEPT
#end here
Advertisements

One comment

  1. Παραδίνομαι!

    6 μήνες κοντά, προσπαθώ να διαβάσω το post σου, αλλά νομίζω από ένα σημείο και μετά… «χτύπησε» η γραμματοσειρά (που λέμε και ‘μεις). Δε βγάζω νόημα (πχ.[ «$?» != «0» ]).

    ;)))))

    Ζεις ρε ‘συ ή να ‘μολήσω τη Νικολούλη πάλι;

Σχολιάστε

Εισάγετε τα παρακάτω στοιχεία ή επιλέξτε ένα εικονίδιο για να συνδεθείτε:

Λογότυπο WordPress.com

Σχολιάζετε χρησιμοποιώντας τον λογαριασμό WordPress.com. Αποσύνδεση / Αλλαγή )

Φωτογραφία Twitter

Σχολιάζετε χρησιμοποιώντας τον λογαριασμό Twitter. Αποσύνδεση / Αλλαγή )

Φωτογραφία Facebook

Σχολιάζετε χρησιμοποιώντας τον λογαριασμό Facebook. Αποσύνδεση / Αλλαγή )

Φωτογραφία Google+

Σχολιάζετε χρησιμοποιώντας τον λογαριασμό Google+. Αποσύνδεση / Αλλαγή )

Σύνδεση με %s