Please enable JavaScript to view the comments powered by Disqus.

Teddy Hartanto

thoughts and experiments

This is not a how-to-play guide. This is a how-to-win guide.

Five cards are dealt to both my opponent and I.

Game starts.

Now, let's define some notations for convenience:

A, B, C, D, E denotes any random character in Coup
i_have(nX) denotes the event that I was dealt n number of X cards, where X ∈ {A, B, C, D, E}. If undefined, n == 1
u_have(nX) denotes the event that my opponent was dealt n number of X cards, where X ∈ {A, B, C, D, E}. If undefined, n == 1
P(Y) denotes the probability of some event
C(Y) denotes the combination of some event
nCr denotes the number of combination of n pick r

The winning formula is simple:

  1. Guess what cards your opponent have
  2. Guess what cards your opponent pick
  3. Prepare bluffs
  4. Play

1. Guess what cards your opponent have

There are 2 signals:

  1. The cards you are dealt with tells you what cards your opponent might have
  2. How long your opponent spend on card-picking tells you how much choice or even the choices they have
Given i_have(0A), 
    P(u_have(>=1A))
        = 1 - C(u_have(0A)) / C(any)
        = 1 - 7C5 / 10C5
        = 0.92
    P(u_have(2A))
        = C(u_have(2A)) / C(any)
        = 3C2 * 7C3 / 10C5
        = 0.42
    P(u_have(3A))
        = C(u_have(3A)) / C(any)
        = 7C2 / 10C5
        = 0.08
Given i_have(1A),
    P(u_have(>=1A))
        = 1 - C(u_have(0A)) / C(any)
        = 1 - 8C5 / 10C5
        = 0.78
    P(u_have(1A))
        = C(u_have(1A)) / C(any)
        = 8C4 * 2C1 / 10C5
        = 0.56
    P(u_have(2A))
        = C(u_have(2A)) / C(any)
        = 8C3 / 10C5
        = 0.22
Given i_have(2A),
    P(u_have(1A))
        = C(u_have(1A)) / C(any)
        = 9C4 / 10C5
        = 0.5
        which makes sense because your opponent can pick any 1 of the 2 piles containing A

Possible card dealts to you and what they mean:

- i_have(ABCDE)
   - assume opponent has all different
- i_have(2A0BCDE)
    - assume opponent has all different
- i_have(2A2BC0D0E)
    - assume opponent has CDE and either of A or B.
- i_have(3ABC0D0E)
    - assume opponent has DE and either of B or C

Implications:

  • I can lie about the cards I don’t have. There’s only 8% chance my opponent has all 3 of the same cards. It’s still risky though. There’s a 42% chance my opponent has at least 2 of the same. They might just shoot at 42%
  • If I have 2 of the same character, calling my opponent’s bluff of that character is risky. Getting it right is 50-50

I can further guess what cards were dealt to my opponent by the time spent on picking their cards. This is highly subjective, but we need to take into account whose turn it is to make the first move, and what cards the opponent might have. Usually, the more time they spend, the more choices they have. It could also mean that they have bad cards.

Always be the last to pick cards unless I want to throw off my opponent.

2. Guess what cards your opponent pick

This is very subjective, but it gets easier and easier to guess as you play more and learn more about how your opponent plays. If they were dealt >=2 of Duke, do they usually not pick Duke and bluff their way?

Depending on who's the first to move:

  • They are first to move: they would want assassin
  • I am first to move: they would want contessa

3. Prepare to bluff

  • If I don’t have captain & ambassador, and opponent wants to steal coins, what do I say?
  • Assassin & contessa has an asymmetric payoff
  • Ambassador is the most useless

4. Play

  • Getting opponent to reveal their card first is always better. I can validate my guess with a better accuracy. They lose 1 ability. When it’s my turn to reveal, I can plan for the best course of action
  • Ignore bluffs if you can still win without calling their bluffs

Meta-strategy

Remember how your opponent plays:

  • When they keep losing because invalid call of bluffs, do they keep trying or do they stop trying?
  • Do they tend to lie when they have 2 of the same character?
  • Do they tend to be honest?
  • What combination of cards do they usually use? When they reveal 1, it’s easier to guess the other.

Their strategy changes too. When keep getting called on bluffs, they might begin playing honestly.


What is a computer?

At the most basic level, a computer is something that is able to do computations. That implies a key set of hardwares that enables computation: a processor and a random-access memory.

A PC has more than that. It has:

  • a storage device
  • a network interface

A lot of things are computers. Sometimes, even components we would normally not think of as a computer (well, at least not me) — a network interface card. A NIC has its own processor and RAM. It's a special-purpose computer meant to be used to connect to a network of computers. It sends & inspects packets. If it receives a packet that is not meant for its host, it discard the packet. It doesn't interrupt its host. Imagine if it can't compute and the host have to attend to every single packet. That would slow down the host.

What else is a computer?

  • A Trusted Platform Module (TPM) is a computer
  • A Baseboard Management Controller (BMC) is a computer
  • A Power Distribution Unit (PDU) is a computer
  • A router is a computer
  • A switch is a computer

Computers that have network interfaces are especially useful, because we can talk to them remotely and get them to do or to say things.


I run WriteFreely as a systemd service. To run it in standalone mode (binding directly to port 80 & 443), I had to add a certain variable in its unit file (configuration file):

[Service]
AmbientCapabilities=CAP_NET_BIND_SERVICE

In Problems When Setting Up WriteFreely Part 2: Firewall, I explained how I couldn't access my blog over https because of the firewall I've set up ages ago and forgotten.

I showed the outputs of two curls:

╭─teddy@teddy-ubuntu ~ 
╰─$ curl -vvv http://teddyh.dev
*   Trying 54.151.219.0:80...
* TCP_NODELAY set
* connect to 54.151.219.0 port 80 failed: Connection refused
* Failed to connect to teddyh.dev port 80: Connection refused
* Closing connection 0
curl: (7) Failed to connect to teddyh.dev port 80: Connection refused
╭─teddy@teddy-ubuntu ~ 
╰─$ curl -vvv https://teddyh.dev
*   Trying 54.151.219.0:443...
* TCP_NODELAY set
* connect to 54.151.219.0 port 443 failed: Connection timed out
* Failed to connect to teddyh.dev port 443: Connection timed out
* Closing connection 0
curl: (28) Failed to connect to teddyh.dev port 443: Connection timed out

I failed to notice this at the time, but there is a subtle difference between the two outputs: Connection refused vs Connection timed out.

What do they mean?

It is easier to understand if we take a peek at the TCP layer when the curl requests are being sent:

Connection refused

─teddy@teddy-ubuntu ~ 
╰─$ sudo tcpdump -nn "host teddyh.dev and port 80"  
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on enp7s0, link-type EN10MB (Ethernet), capture size 262144 bytes
10:33:25.540898 IP 192.168.10.117.43884 > 54.151.219.0.80: Flags [S], seq 2847222361, win 64240, options [mss 1460,sackOK,TS val 564702358 ecr 0,nop,wscale 7], length 0
10:33:25.543802 IP 54.151.219.0.80 > 192.168.10.117.43884: Flags [R.], seq 0, ack 2847222362, win 0, length 0

Connection timed out

(Induced by firewall DROP rule)

╭─teddy@teddy-ubuntu ~ 
╰─$ sudo tcpdump -nn "host teddyh.dev and port 443"                                                                                                                                                          130 ↵
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on enp7s0, link-type EN10MB (Ethernet), capture size 262144 bytes
10:34:34.541405 IP 192.168.10.117.44816 > 54.151.219.0.443: Flags [S], seq 1786361284, win 64240, options [mss 1460,sackOK,TS val 564771358 ecr 0,nop,wscale 7], length 0
10:34:35.569866 IP 192.168.10.117.44816 > 54.151.219.0.443: Flags [S], seq 1786361284, win 64240, options [mss 1460,sackOK,TS val 564772387 ecr 0,nop,wscale 7], length 0
10:34:37.589852 IP 192.168.10.117.44816 > 54.151.219.0.443: Flags [S], seq 1786361284, win 64240, options [mss 1460,sackOK,TS val 564774407 ecr 0,nop,wscale 7], length 0
^C

*Note: Client IP address is 192.168.10.117, Server IP address is 54.151.219.0

Interpretation

Connection refused

The client tried to open a connection (SYN), but the server closed the connection (RST). Put simply, you (the client) knocked on your friend Bob's front door and his mom (the server) told you “Bob's not home!”

How could this happen?

  1. Bob's mom was lying. Bob was grounded, so his mom didn't want you to meet Bob. This is analogous to a firewall REJECT rule
  2. Bob's mom was telling the truth. Bob indeed wasn't at home. This is analogous to no service/process listening to the destined port on the host.

Connection timed out

Connection timed out means the client kept trying to open a connection to the destined port on the host, but there was no reply — so the client gave up trying. The client tried to open a connection (SYN) again and again, with no reply from the server. Put simply, you (the client) kept knocking on your friend Bob's front door and there was no answer.

How could this happen?

  1. Bob's at home but didn't hear you knocking. The sound of your knocks didn't reach him. This is analogous (well, not really) to a network congestion
  2. Bob's whole family kept quiet and didn't answer the door. They hear you, but they choose not to answer you. This is analogous to a firewall DROP rule
  3. Bob's whole family is not at home. This is analogous to a powered-off host. (I've confirmed this by trying to open a TCP connection to a powered-off host.) Contrast this with the Connection refused scenario in which we received a response from the server — indicating that the host is up.

Commenting seems to be a feature lacking in WriteFreely. As a blog author, I want to know your opinions on my posts. Do you agree with what I wrote? Do you know something I don't? Was I wrong in certain things?

Disqus came to mind because I've seen it on many sites. The installation is supposedly hassle-free:

  1. Sign up
  2. Create a site on Disqus
  3. Embed the JavaScript code
  4. Voila!

I added the JS snippet into the “Post Signature.” Any contents in that field should be rendered in each post.

Well... apparently, any contents but <script></script> 🤦‍♂️

Sigh. That is quite limiting. It was an intentional decision in the name of security. I think the rationale was reasonable. But I'm not sure I agree with the solution. The argument is invalid when WriteFreely is in single-user mode.

I see a couple of ways to achieve my goal:

  1. Request for Custom Javascript to be enabled on single-user mode
  2. Modify the source, build, and deploy a. to disable sanitization on “Post Signature” b. to include the custom Javascript
  3. Find loopholes to inject custom Javascript

Option 2 is slow. Option 1 is viable but would need some time and effort. Meanwhile, option 3 has been discovered. That came in handy 🙂. Ignore for a second that the solution is hacky. I argue that it is actually quite ingenious. Plus, it's the quickest way to achieve my goal. Kudos to infuerno for discovering that loophole.

That solution works well for me particularly because I only display post titles on my blog's index page. By default, Disqus' comment threads are identified by the page URL in which they are loaded. Since I force readers to visit the post page, each post gets its own unique comment thread.

It's dirty, but hey, it works! 🤷‍♂️ I'll leave it until I can get a better solution.

Now go on and leave some comments!

I allowed Guest Commenting if you would like to remain anonymous. Though, pre-moderation is enforced by default for guest comments. In other words, guest comments would require my approval to go public.


In Problems When Setting Up WriteFreely Part 2: Firewall, I posed an unanswered question:

Why did netstat not show me processes listening to port 80 & 443, yet I was able to make an HTTP request to port 80?

In that post, I showed a netstat output as follows:

[email protected]:~$ netstat -t4lpn
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:33060         0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN      -       

I wanted to confirm that my WriteFreely instance was listening to port 80 & 443. But, the above output didn't give me the answer I sought. I didn't see anything listening on port 80, yet I was able to connect to that port successfully!

I only focused on IPv4 at the time because I thought I was connecting to the IPv4 interface of my server. After all, my server wasn't assigned a public IPv6 address. teddyh.dev only has an A record containing the IPv4 address of my server.

Later, I did more research on this seemingly odd phenomenon. I stumbled upon this SO post. Apparently, AF_INET6 sockets can receive connections from IPv4 addresses!

Had I used sudo netstat -tlpn instead, I would have seen the following output:

[email protected]:~$ sudo netstat -tlpn
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      459/systemd-resolve 
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      707/sshd: /usr/sbin 
tcp        0      0 127.0.0.1:33060         0.0.0.0:*               LISTEN      644/mysqld          
tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN      644/mysqld          
tcp6       0      0 :::22                   :::*                    LISTEN      707/sshd: /usr/sbin 
tcp6       0      0 :::443                  :::*                    LISTEN      140750/writefreely  
tcp6       0      0 :::80                   :::*                    LISTEN      140750/writefreely

This output gave way more insights than the netstat -t4lpn. The last 2 lines showed that WriteFreely was listening to :::80 & :::443. That would have given me some clue about this probable relationship between IPv6 sockets and IPv4 client addresses.

To further demonstrate this compatibility, I used telnet to open a TCP connection to my server.

My home PC:

╭─teddy@teddy-ubuntu ~ 
╰─$ telnet teddyh.dev 443                                                                                                                                                                                    130 ↵
Trying 54.151.219.0...
Connected to teddyh.dev.
Escape character is '^]'.

My blog server:

[email protected]:~$ sudo netstat -t6apn
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp6       0      0 :::22                   :::*                    LISTEN      707/sshd: /usr/sbin 
tcp6       0      0 :::443                  :::*                    LISTEN      140750/writefreely  
tcp6       0      0 :::80                   :::*                    LISTEN      140750/writefreely            
tcp6       0      0 172.31.29.106:443       A.B.C.D:36134     ESTABLISHED 140750/writefreely  

I have redacted my home IP address above to A.B.C.D. Notice that both the local & foreign addresses are IPv4. 172.31.29.106 is the private IPv4 address of my server, NAT'ed from the public IPv4 address 54.151.219.0 shown in the output of the telnet command.

The above experiment proves that AF_INET6 sockets are compatible with IPv4 addresses. That said, that compatibility can be overridden by setting the IPV6_V6ONLY option, as pointed out in the linked SO post.


This is a two-part series. Read part 1 here.

In my previous post, I ran into an HSTS issue. I bought a domain name that's apparently HSTS preloaded. That means whenever I use a modern browser to visit my blog, it will be strictly loaded over HTTPS. My plan to simply load my blog over HTTP was foiled. To work around that, I ran WriteFreely in standalone mode, which supports SSL out-of-the-box. With that, I solved the problem. I can now visit https://teddyh.dev!


Or so I thought.

At least, until I found out that I still couldn’t load my blog. I was still seeing the same ERR_CONNECTION_TIMED_OUT in my browser. How come?

╭─teddy@teddy-ubuntu ~ 
╰─$ curl -vvv https://teddyh.dev
*   Trying 54.151.219.0:443...
* TCP_NODELAY set
* connect to 54.151.219.0 port 443 failed: Connection timed out
* Failed to connect to teddyh.dev port 443: Connection timed out
* Closing connection 0
curl: (28) Failed to connect to teddyh.dev port 443: Connection timed out
╭─teddy@teddy-ubuntu ~ 
╰─$ curl -vvv http://teddyh.dev
*   Trying 54.151.219.0:80...
* TCP_NODELAY set
* Connected to teddyh.dev (54.151.219.0) port 80 (#0)
> GET / HTTP/1.1
> Host: teddyh.dev
> User-Agent: curl/7.68.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 302 Found
< Content-Type: text/html; charset=utf-8
< Location: https://teddyh.dev/
< Date: Tue, 14 Jun 2022 08:05:09 GMT
< Content-Length: 42
< 
<a href="https://teddyh.dev/">Found</a>.

* Connection #0 to host teddyh.dev left intact

The fact that I got a proper response over http should indicate that my standalone WriteFreely setup was working as intended. Indeed, the documentation said that WriteFreely should redirect http connections to https. However, I couldn't see any log of the http connections I made. Either WriteFreely doesn't log the http redirects, or something else was handling the redirects.

I was pretty sure that WriteFreely was the one handling the redirects. However, I had to verify. I tried checking for open ports using netstat:

[email protected]:~$ netstat -t4lpn
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:33060         0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN      -                   

I saw nothing was listening to port 80 & 443. I scratched my head. How come? Earlier, I was able to get a response from port 80! (That shall be the topic of my next post)

Anyways, I tried another method to verify — I stopped WriteFreely and tried curling again:

╭─teddy@teddy-ubuntu ~ 
╰─$ curl -vvv http://teddyh.dev
*   Trying 54.151.219.0:80...
* TCP_NODELAY set
* connect to 54.151.219.0 port 80 failed: Connection refused
* Failed to connect to teddyh.dev port 80: Connection refused
* Closing connection 0
curl: (7) Failed to connect to teddyh.dev port 80: Connection refused
╭─teddy@teddy-ubuntu ~ 
╰─$ curl -vvv https://teddyh.dev
*   Trying 54.151.219.0:443...
* TCP_NODELAY set
* connect to 54.151.219.0 port 443 failed: Connection timed out
* Failed to connect to teddyh.dev port 443: Connection timed out
* Closing connection 0
curl: (28) Failed to connect to teddyh.dev port 443: Connection timed out

Seeing how my http connection was refused, I concluded that indeed WriteFreely was the one redirecting http->https. Still, it puzzles me why I couldn't connect to the https port.

I hadn't notice this at the time, but there was a subtle difference in the errors reported above: Connection refused vs Connection timed out. That should've been a clue.

It took me some time and some visits to Google & StackOverflow before it dawned on me: maybe a firewall was blocking https requests!

I checked my iptables and saw the word ufw scattered around. Aha! I remembered that I had once played with ufw on this server — as part of Linux Upskill Challenge. I've completely forgotten it because that was ages ago.

Sure enough, I saw https traffic was treated with the ufw default incoming DENY policy (since only ssh and http traffic is allowed):

[email protected]:~$ sudo ufw status
Status: active

To                         Action      From
--                         ------      ----
22/tcp                     ALLOW       Anywhere                  
80/tcp                     ALLOW       Anywhere                  
22/tcp (v6)                ALLOW       Anywhere (v6)             
80/tcp (v6)                ALLOW       Anywhere (v6)             

Once I configured ufw to allow https traffic, I was able to visit my blog on my browser! :)

This ends the two-part series on problems I faced when setting up my WriteFreely instance. Writing these posts have been truly rewarding! I learned a new command (tcpdump), new tricks with old commands, and I gained a better understanding of Linux & networking. It made me better at troubleshooting too.

Running my own blog and writing down my learnings have paid huge dividends! :)

Next, I think I'll cover some things I learned in the process of troubleshooting:

Stay tuned!


PS:

It didn't cross my mind at the time, but I could've ssh into my server and curl my local port 443:

[email protected]:~$ curl -I https://teddyh.dev --resolve 'teddyh.dev:443:127.0.0.1'
HTTP/2 200 
content-type: text/html; charset=utf-8
date: Tue, 14 Jun 2022 08:52:53 GMT

Had I done so, I would have been assured that my WriteFreely setup was working correctly. It would have also pointed me to look at my firewall rules sooner.

On a side note, troubleshooting a confluence of issues can be challenging. It's easy to assume that A is caused by B and B only. But, it could've been caused by B, C, and D. I guess multi-factor problems like these reflect life more accurately, wherein issues are often caused by multiple colluding variables.


There are two ways to set up a WriteFreely instance.

  1. As a standalone application & web server
    • supports SSL out-of-the-box
    • effortless
  2. Behind a reverse proxy (apache2, nginx, etc)
    • requires manual configuration of SSL
    • more effort

Months ago, I already tried the standalone approach. This time, I wanted to try the reverse proxy one.

So, I configured writefreely to run behind nginx. To keep it simple, I ignored setting up SSL. All I wanted initially was to see my blog on http://teddyh.dev.

And... that failed.

Connection timeout for teddyh.dev

I was surprised to see https instead of http in the address. I kept changing it to http, but my browser insisted on https. In retrospect, it was foolish to keep trying the same thing but expecting a different outcome.


It is debug o'clock

It took me a while, but I finally figured it out.

I first tried curling http://teddyh.dev to check whether this issue is related to my browser or my setup. The request went through. I could see the HTML document in the response. That confirmed my setup was fine. So, this must be a browser-related issue.

Why couldn't I access http://teddyh.dev from my browser, then? I tried Chrome, Firefox, and iOS Safari. All of them failed to display my blog.

I pulled up the dev tools to inspect. This is what I saw: Image

A 307 Internal Redirect and HSTS?

HSTS rang a bell... I vaguely recall having read something about HSTS (HTTP Strict Transport Security) on Hackernews. I read about HSTS again to refresh my memory. Apparently, when a modern web browser attempts to load a site whose domain name is in the HSTS preloaded list, it will strictly use https.

So, is teddyh.dev on the HSTS preloaded list?

Apparently! In fact, anything that ends with .dev is HSTS preloaded.

.dev in hsts preloaded list

That means my browser switched from http to https when it tries to load teddyh.dev. Of course, I couldn't access my blog. I configured nginx to only listen to port 80 (http). There was nothing for port 443 (https).

FYI, this redirection from http to https is done by the browser itself. No http packet was sent to the server — only https.

So, to circumvent this issue, I ran WriteFreely on standalone mode. The standalone mode comes with a convenient automatic SSL support. I still want to introduce nginx and certbot, but that comes later.

By the way, in the first screenshot above, notice that the error was a connection timeout. That is the prequel to the next part in this series. Stay tuned!


I've been wanting to set up a blog for the longest time. I spend a lot of time musing on things, but I lack the avenues to crystallize my thoughts. Discussing my ideas with others helps to make them stickier. I thought writing might be another great avenue. So, here I am.


The first order of business was to look for a blogging software.

I have one key criterion for my blog. I want to host it myself on a server. Ideally, my technical know-how should grow together with my writing skill. I want to own a server I can break and experiment on. Plus, self-hosting means total control. What's not to love about that?

Because improving my technical know-how is crucial, I will sometimes opt for a more complex setup, favoring learning over practicality.

Looking around, I came across WriteFreely. I immediately took a liking to it. It's simple, minimalistic, and open-source. I can also host it myself. Perfect!


While setting up my blog, I ran into a few technical problems. Already, I learned a couple of things from the get-go. Those issues have already improved my technical know-how! :)

I'm going to get more in-depth on the problems I faced while setting up my blog. So, stay tuned for some technical juice!


If writefreely suits you, there are plenty of guides out there to get started: