More from noahbuscher.com
The 987.2 Porsche Boxster is one of the best values in the used sports car market today. There is so much to love about the vehicle, but the OEM headunit is not one of them. It's very dated, clunky, and doesn't support CarPlay (!), so one of the first things I did after picking up my car was purchase and install a new headunit. I selected the Sony XAV-AX1000 headunit because: It's one of the only aftermarket headunits with a physical volume knob The matte black plastic finish very closely matches my Porsche's interior I didn't need wireless CarPlay It's relatively cheap I also picked up this dash kit from Crutchfield as well to have the install look as factory as possible. I will say that the fit is very tight (it's a friction fit) so triple check wiring before sliding this into the dash. If you do happen to get the kit stuck with the headunit in it, I've had luck freeing it with the help of a metal putty knife. My 987.2 has the Sound Package Plus (SPP), which is the middle tier between the basic speakers and the Bose speakers (which are much more difficult to connect). The connector I needed as a result was the Metra 70-9003 harness. The connector may come with a fuse tap, but I opted to just use a separately purcahsed unit that fit well in the fuse box without needing to drill any holes. Note that the Metra 70-9003 harness may not work with the base package. Other supplies needed: Electrical tape Assorted t-taps Assorted spade connectors Low-profile fuse tap Metra 70-1787 harness (seperate from the Metra 70-9003 harness, this is just a donor for the RCA cables) Stranded red wire (used to connect power from fuse tap to headunit) Banana plug adapter for connecing SiriusXM Tools needed: Heat gun/heat shrink Screwdriver set Soldering iron/solder Wire strippers Scissors Also of note that my Porsche did not come equipped with steering wheel controls. If you vehicle does have steering wheel controls, you may need to purchase extra materials in order to get those to work, if so desired. The first thing I did was assemble the headunit inside of the dash kit and set it aside on my desk. The screws that come with the dash kit don't work great but seem to be able to self-tap into the headunit. Just sure you screw them in flush, otherwise the unit won't fit in the vehicle. Next, it's time to wire the Metra harness. You can view Crutchfield's wiring diagram here. Next, you can splice the donor RCA cables from the 70-1787 harness with the speaker wires in the 70-9003 harness. Using your wire strippers, remove the outer layer of shielding from the RCA cables, exposing the positive wire and the stranded negative wire. Twist the stranded wire together and put the heat shrink tube over the cable. Solder the positive wire to the 70-9003 harness positive wire and solder the negative wire to the 70-9003 negative wire for the same speaker. Wrap each connection tightly with electrical tape, slide the heat shrink over the connections, and use your heat gun to set. Repeat this for the three other pairs of speakers. Moving on to the install, removal of the headunit in the 987.2 is straightfoward, only requiring the removal of a few screws. This YouTube video explains it well. I reccommend disconnecting your battery first if possible, as you should do whenever working with your car's electrical system. Be delicate as the OEM headunit is still valuable and you may want to swap it back before selling your vehicle. You can now remove the trim around the fuse compartment and tap the fuse; I used C6 as it turns on and off with the ignition. Crimp a length of the stranded red wire to the tapped fuse connector and snake it through to the headunit area. I had the wire rest on the trim below the steering wheel and it hasn't caused any issues. Re-attach the fusebox surrounding trim and use a spade connector to attach the fuse tap wire to the power input for the headunit. I wrapped the connection with electrical tape to prevent shorts. At this point you may want to tap the parking lights in order to control automatic dimming of the headunit. I did not do that during my install, so you'll need to look at a diagram to see what color to look out for. I will note, however, that you are able to easily manually dim the display in the settings. Now, plug the RCA connectors into the respective pre-amp outputs on the headunit to connect the speakers. When you disconnected the original factory headunit, there was a twelve-pin blue connector. The pink wire with the red stripe powers the vehicle's amplifier (under passenger seat in SPP-equipped vehicles). I used the "Remote Out" wire in the new headunit to power both the amp as well as the powered AM/FM radio antenna. Use t-taps to connect both the red and white striped wire and the powered antenna wire (thicker, solid blue wire in the main harness). At this point you can also connect the SiriusXM adapter if your vehicle is equipped. Finally, after you've checked all of your connections you can (carefully!) slide your headunit into place, reconnect the battery, and test it out! If you are noticing a fair amount of feedback, you can optionally add an inline ground loop isolator between the pre-amp output and your harness to reduce some of the interference. Enjoy your new radio and feel free to email me and I can try and answer any questions you may have. Please note I am not an expert at this and am not responsible for the safety of yourself or and damage to your vehicle, the headunit, etc.
More in technology
Well, well, well, well, well, well, well, well, well, well, well, well, well, well, well. We're back. Sorry. We've been watching the onslaught of vulnerabilities flood the internet. Every man, dog, and their grandmas (apparently?) are now using LLMs to find and reproduce vulnerabilities - it’
You want less of them. That’s the reason. You may find that it’s too hard to stop people from doing the thing, literally blood, sweat, and tears trying to prosecute people, but that’s a different thing.
Solitaire Alone Together I made a new game. It's called Solitaire Alone Together. It's Windows 98 solitaire, but you can play with everyone else on the internet. Read the full post on my blog! Here's a raw link, if you need it: https://eieio.games/blog/solitaire-alone-together
This post is a living diary of all the times I messed up something with my website in a funny way. I value those who have the confidence to own their mistakes and share the learning with others, and so this is me doing just that! That Time I Accidentally Made a Tarpit That Time I Accidentally Made Really Large Headers That Time I Accidentally Made a Tarpit Back to Top A "tarpit" is an unofficial term used in computing to describe an intentionally slow response to a request. In these modern times many people are using tarpits as a way to combat the relentless theft of data by AI companies, although there's little to no evidence of that actually being in any way effective. I don't use tarpits, at least not intentionally, but there was that one time when I accidentally created a tarpit and trapped all visitors in it. As I've shared previously, I refuse connections from IP addresses that are blocked or belong to a blocked subnet, and I enforce this firewall during the TCP handshake. The logic here is straightforward: there's no reason to waste resources doing a TLS handshake, accepting an HTTP request, and then rejecting the connection if I already know I'm going to reject it at the earliest step. At the time, the code worked like this: the HTTP server would repeatedly call the Accept() function below expecting a new connection. I've added some comments to help explain the logic. func (l *firewallListener) Accept() (net.Conn, error) { // Accept the connection from the TCP listener. This blocks until there is a connection to accept or the listner was closed. conn, err := l.l.AcceptTCP() if err != nil { return conn, err } // Separate the IP address out from the remote address (which includes the port) ip := utils.SocketStringToIPAddress(conn.RemoteAddr().String()) if ip == nil { return nil, nil } // Check if it's blocked, if so close the connection and return a refuseError if IsBlocked(ip, true) { conn.Close() return nil, &refuseError{} } // Otherwise return the connection on to the HTTP server return conn, nil } If the incoming connection was from a blocked IP then I'd return a refuseError. I need to use a specific error interface because the HTTP server will halt if it encounters a non-temporary error from the call to Accept(), so I need to return an error that satisfies the definition of a temporary error. I defined refuseError like this: type refuseError struct{} func (e *refuseError) Error() string { return "." } func (e *refuseError) Timeout() bool { return true } func (e *refuseError) Temporary() bool { return true } func (e *refuseError) Is(err error) bool { return err == context.DeadlineExceeded } This did accomplish the goal of rejecting connections before the TLS handshake for blocked addresses, but it had one really unintended and difficult to track down side-effect. Accepting connections is done serially, after which servers typically then process that request on a dedicated thread (or in Go's case a goroutine). This means that any delays during the accept loop will block all incoming connection. What I had missed while reviewing the code for Go's HTTP server is that when it receives a temporary error from Accept() is that while it doesn't abort, it does sleep for up to a maximum of 1 second. This sleep blocks the entire server for all incoming connections. You can see a trimmed copy of the code that does this below, with some marks I've added which I will explain. // src/net/http/server.go // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. for { // (1) rw, err := l.Accept() if err != nil { if s.shuttingDown() { return ErrServerClosed } // (2) if ne, ok := err.(net.Error); ok && ne.Temporary() { if tempDelay == 0 { tempDelay = 5 * time.Millisecond } else { tempDelay *= 2 } if max := 1 * time.Second; tempDelay > max { tempDelay = max } s.logf("http: Accept error: %v; retrying in %v", err, tempDelay) // (3) time.Sleep(tempDelay) continue } return err } connCtx := ctx if cc := s.ConnContext; cc != nil { connCtx = cc(connCtx, rw) if connCtx == nil { panic("ConnContext returned nil") } } tempDelay = 0 c := s.newConn(rw) c.setState(c.rwc, StateNew, runHooks) // before Serve can return // (4) go c.serve(connCtx) } At mark 1 the server calls the Accept() function, this is the exact function that I defined above where I might return a temporary error. At mark 2 it checks if an error was returned, and if so if that error is temporary. If there was a temporary error, at mark 3 it sleeps for an increasing amount of time up-to 1 second, otherwise, at mark 4 it processes the connection on a dedicated goroutine, which allows the server to accept the next connection. I'm not entirely sure why the Go developers added this sleep delay and the change when it was introduced doesn't provide any meaningful insight. Regardless, it caused significant latency connecting to my website when a flood of rejected requests was coming in. It just goes to show how important it is to write meaningful commit messages, because you never know when somebody might come back years later wondering "why was this done?". I sure home I don't come to eat those words later. Coincidentally, you can actually see this happening if you look carefully at one of the metric graphs I shared in my first post about my server's security model: Securing My Web Infrastructure. This is the graph I shared in that blog post and while I didn't know it at the time, the fact that these request spikes all cap-out at around 60 requests per minute was not a coincidence. These requests were not being made with a limit in mind, attackers rarely ever care about things like that, instead it the accidental tarpit I had created. The downside to this was that while the malicious requests were being rate-limited, all requests were being rate-limited, up to a point of taking so long they timed out. The Fix Fixing the issue was relatively straightforward enough. Instead of returning a temporary error to the HTTP server during the accept loop, just don't return anything at all and wait for the next valid connection. func (l *firewallListener) Accept() (net.Conn, error) { for { conn, err := l.l.AcceptTCP() if err != nil { return conn, err } ip := utils.SocketStringToIPAddress(conn.RemoteAddr().String()) if ip == nil { return nil, nil } if IsBlocked(ip, true) { conn.SetLinger(0) conn.Close() continue } return conn, nil } } Now, when the HTTP server calls Accept(), the only time it returns is with a connection from an IP that isn't blocked, or if there genuinely is an error. No more sleep delays, no more excessive timeouts. That Time I Accidentally Made Really Large Headers Back to Top For about 10 years now all major browsers have support for a security feature known as a Content Security Policy or CSP. A CSP is an HTTP header provided by the server that instructs the browser on where it can load assets from, this could be scripts, images, stylesheets, fonts, etc. The objective of using a CSP is to prevent against injected HTML that tries to load assets, such as a malicious Javascript file, from a remote source. With so much user-provided content being available online, it's very possible for this to happen without an attacker compromising the entire web server. CSP protects against that by saying "scripts can only be loaded from these domains". That's a really simplified way of looking at it, anyways. My web server supports injecting the CSP header automatically, but before I go on I need to explain a little bit about the structure of my web server. When an incoming HTTP request is accepted (having passed all firewall checks and assertions), we look at the destination host for the request. This can either be the value of the Host header or as specified during the TLS handshake. We then look at a map of hosts to apps. Apps are just an interface that accept a few methods: type App interface { Cleanup() ReloadConfig() ServeHTTP(rw http.ResponseWriter, r *http.Request) Setup(dataDir string) error Shutdown() } One of the apps is the Proxy app, which is a reverse proxy - it accepts the incoming HTTP request and then proxies it on to another host. This is a very common design, especially with increasingly complex TLS setups. Because each app is unique to a host, and different hosts have different requirements for CSP rules, the proxy app includes a CSP preset that we use to build the header value, or skip it entirely. When the proxy app was going to copy an HTTP request to the downstream host, it would build the CSP header, however there was a slight bug... func (a *App) ServeHTTP(rw http.ResponseWriter, inRequest *ht2.Request) { // --snip -- if a.CSP != nil { a.CSP.ConnectSrc += " " + inRequest.Origin } CopyHttpRequest(inRequest, outRequest, rw, CopyHttpRequestOptions{ Origin: inRequest.Origin, Csp: a.CSP, Cors: a.CORS, AddHeaders: !a.SkipHeaders, UseHTTP3: a.UseHTTP3, InsecureTLS: a.InsecureTLS, }) } I'm really unsure as to what I was doing with the line to append to the ConnectSrc, but the impact is that I'm appending to a variable that lives on the App, rather than a variable that is per-request. This meant that every time there was a request to the app, any request at all, the origin would be appended to the header value. This went on for quite a long time unnoticed and unresolved, largely because I am constantly tweaking and tinkering with my web server, after all, it's how I made having a website fun again. Each time I restarted the server process, the header value would be reset, but only for it to continue to grow and grow. Eventually, after a period of being busy with other matters, the server process stayed running for long enough that the header value grew too large and HTTP clients began to reject it. There is no defined maximum for an HTTP header value, however most HTTP clients use 100KiB, which is perfectly reasonable, and this header value would continue to grow well beyond that. Diagnosing this issue turned out to be difficult as tools like Curl would fail with errors relating to entities being too large, but stopped short of saying what specifically. I eventually used openssl s_client to send an HTTP request by hand and observed my terminal window being filled with a domain name repeated thousands of times. Looking at the commit history, it was really unclear why I added the culprit lines of code. The commit message just says "Improved CSP support". It just goes to show how important it is to write - hey look it's those words I'm now having to eat! The Fix The fix was to just delete those three lines of code. Yup, it really was that simple, and fixing this bug actually made a larger positive impact than I had expected, as it was immediately clear when I fixed the bug by looking at outbound network bytes: So much traffic was being wasted on excessive header sizes. You might look at these mistakes I've made and think "wow, Ian, these are some obvious mistakes, I never would have made them!" to which I say "good for you!" with the utmost sarcasm and disdain. I enjoy making and refining software, and making anything means making mistakes along the way. Each time I make mistakes such as the ones above, I improve my skills of investigation, diagnosing, and repair. Skills that, judging by my peers in the industry, seemingly everyone is quickly willing to throw away because a robot does it "better" than you. Header Image: "Car accident on the Ffestiniog to Bala road. Nobody was hurt" by Geoff Charles, CC BY-SA 4.0, via Wikimedia Commons.