2번 모듈 신규(raspberry_pi4_sht30/): SHT30 온습도 → cafe24, raw-body X-Signature HMAC, 폐쇄망 오프라인 설치(vendor/install), systemd, CRC/포맷 자가점검. firmware sht30_fw wire 포맷과 byte-parity 검증. 1번 모듈 통합(raspberry_pi4/): 누수+카메라를 동일 서버로. 레거시 sha256(key+json) body-sig → X-Signature HMAC 전환, 사진 canonical=device_id|sensor_id|log_id|timestamp. 서버 통합(php/): api/sensor_data.php 누수 이벤트 수신, api/photo_upload.php 신규(HMAC·finfo·서버생성 파일명·nonce 멱등·sensor 결속), photo.php 인증 사진서빙, dashboard.php 누수 패널+사진, sms_send.php 누수 SMS, config.php 태그/요약. DB: sql/migration_add_leak.sql(가산·멱등), schema_sht30.sql 통합 스키마. 보안 하드닝(외부 펜테스트 대비, 확정 CRITICAL/HIGH 0): cron_heartbeat api_key fail-closed, setup_mfa 부트스트랩 토큰 post-enrollment 잠금, var/ PHP 실행차단, HSTS. 문서/테스트: SYSTEM_OVERVIEW.md, security_hardening_test.php 갱신. 참고: 사용자 요청으로 세션 외 기존 미커밋 변경도 함께 포함. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
255 lines
9.2 KiB
C
255 lines
9.2 KiB
C
/* =============================================================================
|
|
* net.c - 이더넷/LwIP 네트워크 (net.h 구현)
|
|
*
|
|
* 부트 흐름:
|
|
* net_init():
|
|
* 1) tcpip_init() -> LwIP tcpip 스레드 기동(FreeRTOS).
|
|
* 2) netif_add(ethernetif_init, tcpip_input) 으로 ETH netif 등록.
|
|
* 3) default netif 지정 + status/link 콜백 등록.
|
|
* 4) APP_NET_USE_DHCP 면 dhcp_start, 아니면 static 적용.
|
|
* net_wait_up(): 링크 업 + IP 확보까지 블록(타임아웃). DHCP 타임아웃 시
|
|
* static 으로 폴백.
|
|
*
|
|
* tcpip 스레드 외부(애플리케이션 태스크)에서 LwIP 코어를 만지므로,
|
|
* netifapi_* / tcpip_callback 으로 직렬화한다(LWIP_TCPIP_CORE_LOCKING=1).
|
|
* ===========================================================================*/
|
|
#include "net.h"
|
|
#include "app_config.h"
|
|
#include "applog.h"
|
|
|
|
#include "lwip/tcpip.h"
|
|
#include "lwip/netif.h"
|
|
#include "lwip/netifapi.h"
|
|
#include "lwip/dhcp.h"
|
|
#include "lwip/dns.h"
|
|
#include "lwip/ip_addr.h"
|
|
#include "lwip/init.h"
|
|
|
|
#include "FreeRTOS.h"
|
|
#include "task.h"
|
|
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
/* ethernetif.c 가 제공하는 LwIP netif 초기화 콜백. */
|
|
extern err_t ethernetif_init(struct netif *netif);
|
|
|
|
/* ── 내부 상태 ──────────────────────────────────────────────────────────── */
|
|
|
|
static struct netif s_netif;
|
|
|
|
/* 링크 업 + IP 확보 여부(콜백/태스크 경합 -> volatile). */
|
|
static volatile int s_link_up = 0;
|
|
static volatile int s_addr_set = 0; /* 0.0.0.0 가 아닌 IP 가 할당됨 */
|
|
static int s_dhcp_mode = 0; /* 현재 DHCP 로 시도 중인지 */
|
|
|
|
/* ── 콜백 ───────────────────────────────────────────────────────────────── */
|
|
|
|
/* IP 주소가 바뀌면(특히 0->유효) 호출됨. */
|
|
static void net_status_cb(struct netif *netif)
|
|
{
|
|
if (netif_is_up(netif) && !ip4_addr_isany_val(*netif_ip4_addr(netif))) {
|
|
s_addr_set = 1;
|
|
char ip[16];
|
|
ip4addr_ntoa_r(netif_ip4_addr(netif), ip, sizeof(ip));
|
|
LOGI("net: IP 할당 %s", ip);
|
|
} else {
|
|
s_addr_set = 0;
|
|
}
|
|
}
|
|
|
|
/* 물리 링크(케이블) 상태 변경 시 호출됨(ethernetif 가 PHY 폴링으로 통지). */
|
|
static void net_link_cb(struct netif *netif)
|
|
{
|
|
if (netif_is_link_up(netif)) {
|
|
s_link_up = 1;
|
|
LOGI("net: 링크 업");
|
|
/* 링크가 올라오면 인터페이스 up + 주소 절차 시작.
|
|
* 이 콜백은 ethernetif 의 netifapi_netif_set_link_up 경유로 tcpip 코어락을
|
|
* 이미 보유한 문맥에서 호출된다. 따라서 여기서 netifapi_*(다시 코어락 시도)
|
|
* 를 부르면 비재귀 뮤텍스 자기-데드락이 난다. raw netif_set_up 을 쓴다. */
|
|
netif_set_up(netif);
|
|
} else {
|
|
s_link_up = 0;
|
|
s_addr_set = 0;
|
|
LOGW("net: 링크 다운");
|
|
}
|
|
}
|
|
|
|
/* ── 주소 적용 헬퍼 ─────────────────────────────────────────────────────── */
|
|
|
|
static void net_apply_static(void)
|
|
{
|
|
ip4_addr_t ip, mask, gw, dns;
|
|
|
|
if (!ip4addr_aton(APP_NET_STATIC_IP, &ip)) IP4_ADDR(&ip, 192,168,0,50);
|
|
if (!ip4addr_aton(APP_NET_STATIC_NETMASK,&mask))IP4_ADDR(&mask, 255,255,255,0);
|
|
if (!ip4addr_aton(APP_NET_STATIC_GW, &gw)) IP4_ADDR(&gw, 192,168,0,1);
|
|
|
|
/* DHCP 가 돌고 있었다면 멈춘다(폴백 경로). */
|
|
if (s_dhcp_mode) {
|
|
netifapi_dhcp_stop(&s_netif);
|
|
s_dhcp_mode = 0;
|
|
}
|
|
|
|
netifapi_netif_set_addr(&s_netif, &ip, &mask, &gw);
|
|
|
|
/* DNS 서버 설정. dns_setserver 는 raw 코어 API 이고, net_apply_static 은
|
|
* net_wait_up(앱 태스크, tcpip 스레드 아님)에서도 호출되므로 코어락으로
|
|
* 직렬화한다. (netifapi_* 호출과 중첩되지 않게 이 호출만 짧게 감싼다.) */
|
|
if (ip4addr_aton(APP_NET_STATIC_DNS, &dns)) {
|
|
ip_addr_t d;
|
|
ip_addr_copy_from_ip4(d, dns);
|
|
LOCK_TCPIP_CORE();
|
|
dns_setserver(0, &d);
|
|
UNLOCK_TCPIP_CORE();
|
|
}
|
|
|
|
netifapi_netif_set_up(&s_netif);
|
|
|
|
s_addr_set = 1;
|
|
LOGI("net: static 적용 ip=%s gw=%s dns=%s",
|
|
APP_NET_STATIC_IP, APP_NET_STATIC_GW, APP_NET_STATIC_DNS);
|
|
}
|
|
|
|
/* ── 공개 API ───────────────────────────────────────────────────────────── */
|
|
|
|
void net_init(void)
|
|
{
|
|
ip4_addr_t ip0, mask0, gw0;
|
|
|
|
/* tcpip 스레드 기동. NULL 콜백 = 동기 완료 대기 불필요. */
|
|
tcpip_init(NULL, NULL);
|
|
|
|
/* netif 초기 주소: DHCP 면 0.0.0.0 으로 시작, static 이면 곧바로 채운다.
|
|
* (정적 값은 아래 분기에서 다시 set_addr 로 적용) */
|
|
ip4_addr_set_zero(&ip0);
|
|
ip4_addr_set_zero(&mask0);
|
|
ip4_addr_set_zero(&gw0);
|
|
|
|
/* ethernetif_init 가 MAC/PHY/DMA 를 세팅하고, 수신은 tcpip_input 으로 전달. */
|
|
if (netifapi_netif_add(&s_netif, &ip0, &mask0, &gw0, NULL,
|
|
ethernetif_init, tcpip_input) != ERR_OK) {
|
|
LOGE("net: netif_add 실패");
|
|
return;
|
|
}
|
|
|
|
netifapi_netif_set_default(&s_netif);
|
|
|
|
#if LWIP_NETIF_HOSTNAME
|
|
s_netif.hostname = BOARD_DEVICE_ID; /* DHCP 호스트명(옵션 12) */
|
|
#endif
|
|
|
|
/* 콜백 등록(코어 컨텍스트 안전: netif 핸들 직접 접근은 set 함수로). */
|
|
netif_set_status_callback(&s_netif, net_status_cb);
|
|
netif_set_link_callback(&s_netif, net_link_cb);
|
|
|
|
/* 인터페이스 administratively up. 실제 링크 업은 PHY 폴링이 통지. */
|
|
netifapi_netif_set_up(&s_netif);
|
|
|
|
#if APP_NET_USE_DHCP
|
|
s_dhcp_mode = 1;
|
|
if (netifapi_dhcp_start(&s_netif) != ERR_OK) {
|
|
LOGE("net: dhcp_start 실패 -> static 폴백");
|
|
net_apply_static();
|
|
} else {
|
|
LOGI("net: DHCP 시작(타임아웃 %ums 후 static 폴백)", APP_NET_DHCP_TIMEOUT_MS);
|
|
}
|
|
#else
|
|
net_apply_static();
|
|
#endif
|
|
}
|
|
|
|
int net_wait_up(uint32_t timeout_ms)
|
|
{
|
|
const uint32_t step_ms = 100;
|
|
uint32_t waited = 0;
|
|
|
|
/* 1단계: 물리 링크 업 대기(케이블 연결). */
|
|
while (!s_link_up) {
|
|
if (timeout_ms != 0 && waited >= timeout_ms) {
|
|
LOGW("net: 링크 업 타임아웃");
|
|
return -1;
|
|
}
|
|
vTaskDelay(pdMS_TO_TICKS(step_ms));
|
|
waited += step_ms;
|
|
}
|
|
|
|
/* 2단계: IP 주소 확보 대기. */
|
|
#if APP_NET_USE_DHCP
|
|
uint32_t dhcp_waited = 0;
|
|
while (!s_addr_set) {
|
|
/* DHCP 타임아웃 -> static 폴백(1회). */
|
|
if (s_dhcp_mode && dhcp_waited >= APP_NET_DHCP_TIMEOUT_MS) {
|
|
LOGW("net: DHCP 타임아웃 -> static 폴백");
|
|
net_apply_static();
|
|
}
|
|
if (timeout_ms != 0 && waited >= timeout_ms) {
|
|
LOGW("net: IP 확보 타임아웃");
|
|
return -1;
|
|
}
|
|
vTaskDelay(pdMS_TO_TICKS(step_ms));
|
|
waited += step_ms;
|
|
dhcp_waited += step_ms;
|
|
}
|
|
#else
|
|
while (!s_addr_set) {
|
|
if (timeout_ms != 0 && waited >= timeout_ms) {
|
|
LOGW("net: IP 확보 타임아웃");
|
|
return -1;
|
|
}
|
|
vTaskDelay(pdMS_TO_TICKS(step_ms));
|
|
waited += step_ms;
|
|
}
|
|
#endif
|
|
|
|
char ip[16];
|
|
net_ip_str(ip, sizeof(ip));
|
|
LOGI("net: 준비 완료 ip=%s", ip);
|
|
return 0;
|
|
}
|
|
|
|
net_status_t net_status(void)
|
|
{
|
|
return (s_link_up && s_addr_set && netif_is_up(&s_netif)) ? NET_UP : NET_DOWN;
|
|
}
|
|
|
|
void net_ip_str(char *buf, uint32_t buflen)
|
|
{
|
|
if (buf == NULL || buflen == 0) {
|
|
return;
|
|
}
|
|
if (s_addr_set && !ip4_addr_isany_val(*netif_ip4_addr(&s_netif))) {
|
|
char tmp[16];
|
|
ip4addr_ntoa_r(netif_ip4_addr(&s_netif), tmp, sizeof(tmp));
|
|
/* 안전 복사(널 종료 보장). */
|
|
size_t n = strlen(tmp);
|
|
if (n >= buflen) n = buflen - 1;
|
|
memcpy(buf, tmp, n);
|
|
buf[n] = '\0';
|
|
} else {
|
|
const char *z = "0.0.0.0";
|
|
size_t n = strlen(z);
|
|
if (n >= buflen) n = buflen - 1;
|
|
memcpy(buf, z, n);
|
|
buf[n] = '\0';
|
|
}
|
|
}
|
|
|
|
/* ── LWIP_RAND() 백엔드 (약한 폴백) ─────────────────────────────────────────
|
|
* lwipopts.h 의 LWIP_RAND() 가 bsp_rand32() 를 호출한다. 운영 펌웨어(sht30_fw)는
|
|
* bsp.c 의 *강한* 하드웨어 RNG 구현(bsp_rand32, RNG->DR)이 이 weak 심볼을 덮어쓰므로
|
|
* 실제로는 HW RNG 가 사용된다(TCP ISN / DHCP·DNS 트랜잭션 ID 예측성 완화). 이 weak
|
|
* 구현은 bsp.c 를 링크하지 않는 빌드(예: 호스트 패리티)에서만 쓰이는 폴백이다. */
|
|
__attribute__((weak)) uint32_t bsp_rand32(void)
|
|
{
|
|
/* xorshift32 + FreeRTOS 틱 시드(예측 가능 — TLS/DHCP 트랜잭션 ID 보안엔 부적합).
|
|
* 운영 빌드에서는 bsp.c 의 강한 HW RNG 구현이 이 폴백을 대체한다. */
|
|
static uint32_t s = 0x1234abcdU;
|
|
s ^= (uint32_t)xTaskGetTickCount();
|
|
s ^= s << 13;
|
|
s ^= s >> 17;
|
|
s ^= s << 5;
|
|
if (s == 0) s = 0xa5a5a5a5U;
|
|
return s;
|
|
}
|